diff --git a/.gitattributes b/.gitattributes index 0995f3ba28f945bb7cdb29489fe724313ac00e63..d424d6fbf704c45ff2e95f88109db7f742655332 100644 --- a/.gitattributes +++ b/.gitattributes @@ -302,3 +302,81 @@ venv/lib/python3.12/site-packages/scipy/optimize/_trlib/_trlib.cpython-312-x86_6 venv/lib/python3.12/site-packages/scipy/optimize/cython_optimize/_zeros.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text venv/lib/python3.12/site-packages/scipy/optimize/tests/__pycache__/test_linprog.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text venv/lib/python3.12/site-packages/scipy/optimize/tests/__pycache__/test_optimize.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_filter_design.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_ltisys.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_short_time_fft.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_signaltools.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_spectral_py.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/signal/_peak_finding_utils.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/signal/_sigtools.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/signal/_sosfilt.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/signal/_upfirdn_apply.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_filter_design.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_signaltools.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_spectral.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/signal/windows/__pycache__/_windows.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/sparse/_csparsetools.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/sparse/_sparsetools.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/sparse/csgraph/_flow.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/sparse/csgraph/_matching.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/sparse/csgraph/_reordering.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/sparse/csgraph/_shortest_path.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/sparse/csgraph/_tools.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/sparse/csgraph/_traversal.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/sparse/linalg/_dsolve/_superlu.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/arpack/_arpacklib.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/sparse/linalg/_propack.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_base.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/spatial/__pycache__/distance.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/spatial/_ckdtree.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/spatial/_distance_pybind.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/spatial/_distance_wrap.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/spatial/_qhull.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/spatial/tests/__pycache__/test_distance.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/spatial/tests/__pycache__/test_kdtree.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/spatial/transform/__pycache__/_rotation.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/spatial/transform/_rigid_transform_cy.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/spatial/transform/_rotation_cy.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/spatial/transform/tests/__pycache__/test_rotation.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/special/__pycache__/_add_newdocs.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/special/__pycache__/_basic.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/special/_ellip_harm_2.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/special/_gufuncs.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/special/_specfun.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/special/_special_ufuncs.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/special/_ufuncs.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/special/_ufuncs_cxx.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/special/cython_special.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_basic.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_mpmath.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_continuous_distns.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_distn_infrastructure.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_distribution_infrastructure.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_morestats.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_mstats_basic.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_multivariate.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_qmc.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_resampling.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_stats_py.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/stats/_ansari_swilk_statistics.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/stats/_biasedurn.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/stats/_qmc_cy.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/stats/_qmvnt_cy.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/stats/_rcont/rcont.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/stats/_sobol.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/stats/_stats.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/stats/_stats_pythran.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/stats/_unuran/unuran_wrapper.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_continuous.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_distributions.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_hypotests.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_morestats.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_mstats_basic.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_multivariate.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_resampling.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_stats.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy.libs/libgfortran-040039e1-0352e75f.so.5.0.0 filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy.libs/libgfortran-83c28eba.so.5.0.0 filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy.libs/libquadmath-2284e583.so.0.0.0 filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy.libs/libquadmath-96973f99-934c22de.so.0.0.0 filter=lfs diff=lfs merge=lfs -text +venv/lib/python3.12/site-packages/scipy.libs/libscipy_openblas-5f890258.so filter=lfs diff=lfs merge=lfs -text diff --git a/venv/lib/python3.12/site-packages/scipy-1.18.0.dist-info/INSTALLER b/venv/lib/python3.12/site-packages/scipy-1.18.0.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy-1.18.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/lib/python3.12/site-packages/scipy-1.18.0.dist-info/LICENSE.txt b/venv/lib/python3.12/site-packages/scipy-1.18.0.dist-info/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..99828059c9523a0c94c7850a92843c0dfc9db548 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy-1.18.0.dist-info/LICENSE.txt @@ -0,0 +1,937 @@ +Copyright (c) 2001-2002 Enthought, Inc. 2003, SciPy Developers. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---- + + +---- + +This binary distribution of SciPy can also bundle the following software +(depending on the build): + + +Name: OpenBLAS +Files: scipy.libs/libscipy_openblas*.so +Description: bundled as a dynamically linked library +Availability: https://github.com/OpenMathLib/OpenBLAS/ +License: BSD-3-Clause + Copyright (c) 2011-2014, The OpenBLAS Project + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + 3. Neither the name of the OpenBLAS project nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written + permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +Name: LAPACK +Files: scipy.libs/libscipy_openblas*.so +Description: bundled in OpenBLAS +Availability: https://github.com/OpenMathLib/OpenBLAS/ +License: BSD-3-Clause-Open-MPI + Copyright (c) 1992-2013 The University of Tennessee and The University + of Tennessee Research Foundation. All rights + reserved. + Copyright (c) 2000-2013 The University of California Berkeley. All + rights reserved. + Copyright (c) 2006-2013 The University of Colorado Denver. All rights + reserved. + + $COPYRIGHT$ + + Additional copyrights may follow + + $HEADER$ + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer listed + in this license in the documentation and/or other materials + provided with the distribution. + + - Neither the name of the copyright holders nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + The copyright holders provide no reassurances that the source code + provided does not infringe any patent, copyright, or any other + intellectual property rights of third parties. The copyright holders + disclaim any liability to any recipient for claims brought against + recipient by any third party for infringement of that parties + intellectual property rights. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +Name: GCC runtime library +Files: scipy.libs/libgfortran*.so +Description: dynamically linked to files compiled with gcc +Availability: https://gcc.gnu.org/git/?p=gcc.git;a=tree;f=libgfortran +License: GPL-3.0-or-later WITH GCC-exception-3.1 + Copyright (C) 2002-2017 Free Software Foundation, Inc. + + Libgfortran is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3, or (at your option) + any later version. + + Libgfortran is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + Under Section 7 of GPL version 3, you are granted additional + permissions described in the GCC Runtime Library Exception, version + 3.1, as published by the Free Software Foundation. + + You should have received a copy of the GNU General Public License and + a copy of the GCC Runtime Library Exception along with this program; + see the files COPYING3 and COPYING.RUNTIME respectively. If not, see + . + +---- + +Full text of license texts referred to above follows (that they are +listed below does not necessarily imply the conditions apply to the +present binary release): + +---- + +GCC RUNTIME LIBRARY EXCEPTION + +Version 3.1, 31 March 2009 + +Copyright (C) 2009 Free Software Foundation, Inc. + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + +This GCC Runtime Library Exception ("Exception") is an additional +permission under section 7 of the GNU General Public License, version +3 ("GPLv3"). It applies to a given file (the "Runtime Library") that +bears a notice placed by the copyright holder of the file stating that +the file is governed by GPLv3 along with this Exception. + +When you use GCC to compile a program, GCC may combine portions of +certain GCC header files and runtime libraries with the compiled +program. The purpose of this Exception is to allow compilation of +non-GPL (including proprietary) programs to use, in this way, the +header files and runtime libraries covered by this Exception. + +0. Definitions. + +A file is an "Independent Module" if it either requires the Runtime +Library for execution after a Compilation Process, or makes use of an +interface provided by the Runtime Library, but is not otherwise based +on the Runtime Library. + +"GCC" means a version of the GNU Compiler Collection, with or without +modifications, governed by version 3 (or a specified later version) of +the GNU General Public License (GPL) with the option of using any +subsequent versions published by the FSF. + +"GPL-compatible Software" is software whose conditions of propagation, +modification and use would permit combination with GCC in accord with +the license of GCC. + +"Target Code" refers to output from any compiler for a real or virtual +target processor architecture, in executable form or suitable for +input to an assembler, loader, linker and/or execution +phase. Notwithstanding that, Target Code does not include data in any +format that is used as a compiler intermediate representation, or used +for producing a compiler intermediate representation. + +The "Compilation Process" transforms code entirely represented in +non-intermediate languages designed for human-written code, and/or in +Java Virtual Machine byte code, into Target Code. Thus, for example, +use of source code generators and preprocessors need not be considered +part of the Compilation Process, since the Compilation Process can be +understood as starting with the output of the generators or +preprocessors. + +A Compilation Process is "Eligible" if it is done using GCC, alone or +with other GPL-compatible software, or if it is done without using any +work based on GCC. For example, using non-GPL-compatible Software to +optimize any GCC intermediate representations would not qualify as an +Eligible Compilation Process. + +1. Grant of Additional Permission. + +You have permission to propagate a work of Target Code formed by +combining the Runtime Library with Independent Modules, even if such +propagation would otherwise violate the terms of GPLv3, provided that +all Target Code was generated by Eligible Compilation Processes. You +may then convey such a combination under terms of your choice, +consistent with the licensing of the Independent Modules. + +2. No Weakening of GCC Copyleft. + +The availability of this Exception does not imply any general +presumption that third-party software is unaffected by the copyleft +requirements of the license of GCC. + +---- + + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. + + +Name: libquadmath +Files: scipy.libs/libquadmath*.so +Description: dynamically linked to files compiled with gcc +Availability: https://gcc.gnu.org/git/?p=gcc.git;a=tree;f=libquadmath +License: LGPL-2.1-or-later + + GCC Quad-Precision Math Library + Copyright (C) 2010-2019 Free Software Foundation, Inc. + Written by Francois-Xavier Coudert + + This file is part of the libquadmath library. + Libquadmath is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + Libquadmath is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html diff --git a/venv/lib/python3.12/site-packages/scipy-1.18.0.dist-info/METADATA b/venv/lib/python3.12/site-packages/scipy-1.18.0.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..102405d833b43b23254d3dc1d5bf061193150280 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy-1.18.0.dist-info/METADATA @@ -0,0 +1,1091 @@ +Metadata-Version: 2.1 +Name: scipy +Version: 1.18.0 +Summary: Fundamental algorithms for scientific computing in Python +Maintainer-Email: SciPy Developers +License: Copyright (c) 2001-2002 Enthought, Inc. 2003, SciPy Developers. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + ---- + + + ---- + + This binary distribution of SciPy can also bundle the following software + (depending on the build): + + + Name: OpenBLAS + Files: scipy.libs/libscipy_openblas*.so + Description: bundled as a dynamically linked library + Availability: https://github.com/OpenMathLib/OpenBLAS/ + License: BSD-3-Clause + Copyright (c) 2011-2014, The OpenBLAS Project + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + 3. Neither the name of the OpenBLAS project nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written + permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + Name: LAPACK + Files: scipy.libs/libscipy_openblas*.so + Description: bundled in OpenBLAS + Availability: https://github.com/OpenMathLib/OpenBLAS/ + License: BSD-3-Clause-Open-MPI + Copyright (c) 1992-2013 The University of Tennessee and The University + of Tennessee Research Foundation. All rights + reserved. + Copyright (c) 2000-2013 The University of California Berkeley. All + rights reserved. + Copyright (c) 2006-2013 The University of Colorado Denver. All rights + reserved. + + $COPYRIGHT$ + + Additional copyrights may follow + + $HEADER$ + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer listed + in this license in the documentation and/or other materials + provided with the distribution. + + - Neither the name of the copyright holders nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + The copyright holders provide no reassurances that the source code + provided does not infringe any patent, copyright, or any other + intellectual property rights of third parties. The copyright holders + disclaim any liability to any recipient for claims brought against + recipient by any third party for infringement of that parties + intellectual property rights. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + Name: GCC runtime library + Files: scipy.libs/libgfortran*.so + Description: dynamically linked to files compiled with gcc + Availability: https://gcc.gnu.org/git/?p=gcc.git;a=tree;f=libgfortran + License: GPL-3.0-or-later WITH GCC-exception-3.1 + Copyright (C) 2002-2017 Free Software Foundation, Inc. + + Libgfortran is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3, or (at your option) + any later version. + + Libgfortran is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + Under Section 7 of GPL version 3, you are granted additional + permissions described in the GCC Runtime Library Exception, version + 3.1, as published by the Free Software Foundation. + + You should have received a copy of the GNU General Public License and + a copy of the GCC Runtime Library Exception along with this program; + see the files COPYING3 and COPYING.RUNTIME respectively. If not, see + . + + ---- + + Full text of license texts referred to above follows (that they are + listed below does not necessarily imply the conditions apply to the + present binary release): + + ---- + + GCC RUNTIME LIBRARY EXCEPTION + + Version 3.1, 31 March 2009 + + Copyright (C) 2009 Free Software Foundation, Inc. + + Everyone is permitted to copy and distribute verbatim copies of this + license document, but changing it is not allowed. + + This GCC Runtime Library Exception ("Exception") is an additional + permission under section 7 of the GNU General Public License, version + 3 ("GPLv3"). It applies to a given file (the "Runtime Library") that + bears a notice placed by the copyright holder of the file stating that + the file is governed by GPLv3 along with this Exception. + + When you use GCC to compile a program, GCC may combine portions of + certain GCC header files and runtime libraries with the compiled + program. The purpose of this Exception is to allow compilation of + non-GPL (including proprietary) programs to use, in this way, the + header files and runtime libraries covered by this Exception. + + 0. Definitions. + + A file is an "Independent Module" if it either requires the Runtime + Library for execution after a Compilation Process, or makes use of an + interface provided by the Runtime Library, but is not otherwise based + on the Runtime Library. + + "GCC" means a version of the GNU Compiler Collection, with or without + modifications, governed by version 3 (or a specified later version) of + the GNU General Public License (GPL) with the option of using any + subsequent versions published by the FSF. + + "GPL-compatible Software" is software whose conditions of propagation, + modification and use would permit combination with GCC in accord with + the license of GCC. + + "Target Code" refers to output from any compiler for a real or virtual + target processor architecture, in executable form or suitable for + input to an assembler, loader, linker and/or execution + phase. Notwithstanding that, Target Code does not include data in any + format that is used as a compiler intermediate representation, or used + for producing a compiler intermediate representation. + + The "Compilation Process" transforms code entirely represented in + non-intermediate languages designed for human-written code, and/or in + Java Virtual Machine byte code, into Target Code. Thus, for example, + use of source code generators and preprocessors need not be considered + part of the Compilation Process, since the Compilation Process can be + understood as starting with the output of the generators or + preprocessors. + + A Compilation Process is "Eligible" if it is done using GCC, alone or + with other GPL-compatible software, or if it is done without using any + work based on GCC. For example, using non-GPL-compatible Software to + optimize any GCC intermediate representations would not qualify as an + Eligible Compilation Process. + + 1. Grant of Additional Permission. + + You have permission to propagate a work of Target Code formed by + combining the Runtime Library with Independent Modules, even if such + propagation would otherwise violate the terms of GPLv3, provided that + all Target Code was generated by Eligible Compilation Processes. You + may then convey such a combination under terms of your choice, + consistent with the licensing of the Independent Modules. + + 2. No Weakening of GCC Copyleft. + + The availability of this Exception does not imply any general + presumption that third-party software is unaffected by the copyleft + requirements of the license of GCC. + + ---- + + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for + software and other kinds of works. + + The licenses for most software and other practical works are designed + to take away your freedom to share and change the works. By contrast, + the GNU General Public License is intended to guarantee your freedom to + share and change all versions of a program--to make sure it remains free + software for all its users. We, the Free Software Foundation, use the + GNU General Public License for most of our software; it applies also to + any other work released this way by its authors. You can apply it to + your programs, too. + + When we speak of free software, we are referring to freedom, not + price. Our General Public Licenses are designed to make sure that you + have the freedom to distribute copies of free software (and charge for + them if you wish), that you receive source code or can get it if you + want it, that you can change the software or use pieces of it in new + free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you + these rights or asking you to surrender the rights. Therefore, you have + certain responsibilities if you distribute copies of the software, or if + you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether + gratis or for a fee, you must pass on to the recipients the same + freedoms that you received. You must make sure that they, too, receive + or can get the source code. And you must show them these terms so they + know their rights. + + Developers that use the GNU GPL protect your rights with two steps: + (1) assert copyright on the software, and (2) offer you this License + giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains + that there is no warranty for this free software. For both users' and + authors' sake, the GPL requires that modified versions be marked as + changed, so that their problems will not be attributed erroneously to + authors of previous versions. + + Some devices are designed to deny users access to install or run + modified versions of the software inside them, although the manufacturer + can do so. This is fundamentally incompatible with the aim of + protecting users' freedom to change the software. The systematic + pattern of such abuse occurs in the area of products for individuals to + use, which is precisely where it is most unacceptable. Therefore, we + have designed this version of the GPL to prohibit the practice for those + products. If such problems arise substantially in other domains, we + stand ready to extend this provision to those domains in future versions + of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. + States should not allow patents to restrict development and use of + software on general-purpose computers, but in those that do, we wish to + avoid the special danger that patents applied to a free program could + make it effectively proprietary. To prevent this, the GPL assures that + patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and + modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of + works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this + License. Each licensee is addressed as "you". "Licensees" and + "recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work + in a fashion requiring copyright permission, other than the making of an + exact copy. The resulting work is called a "modified version" of the + earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based + on the Program. + + To "propagate" a work means to do anything with it that, without + permission, would make you directly or secondarily liable for + infringement under applicable copyright law, except executing it on a + computer or modifying a private copy. Propagation includes copying, + distribution (with or without modification), making available to the + public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other + parties to make or receive copies. Mere interaction with a user through + a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" + to the extent that it includes a convenient and prominently visible + feature that (1) displays an appropriate copyright notice, and (2) + tells the user that there is no warranty for the work (except to the + extent that warranties are provided), that licensees may convey the + work under this License, and how to view a copy of this License. If + the interface presents a list of user commands or options, such as a + menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work + for making modifications to it. "Object code" means any non-source + form of a work. + + A "Standard Interface" means an interface that either is an official + standard defined by a recognized standards body, or, in the case of + interfaces specified for a particular programming language, one that + is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other + than the work as a whole, that (a) is included in the normal form of + packaging a Major Component, but which is not part of that Major + Component, and (b) serves only to enable use of the work with that + Major Component, or to implement a Standard Interface for which an + implementation is available to the public in source code form. A + "Major Component", in this context, means a major essential component + (kernel, window system, and so on) of the specific operating system + (if any) on which the executable work runs, or a compiler used to + produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all + the source code needed to generate, install, and (for an executable + work) run the object code and to modify the work, including scripts to + control those activities. However, it does not include the work's + System Libraries, or general-purpose tools or generally available free + programs which are used unmodified in performing those activities but + which are not part of the work. For example, Corresponding Source + includes interface definition files associated with source files for + the work, and the source code for shared libraries and dynamically + linked subprograms that the work is specifically designed to require, + such as by intimate data communication or control flow between those + subprograms and other parts of the work. + + The Corresponding Source need not include anything that users + can regenerate automatically from other parts of the Corresponding + Source. + + The Corresponding Source for a work in source code form is that + same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of + copyright on the Program, and are irrevocable provided the stated + conditions are met. This License explicitly affirms your unlimited + permission to run the unmodified Program. The output from running a + covered work is covered by this License only if the output, given its + content, constitutes a covered work. This License acknowledges your + rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not + convey, without conditions so long as your license otherwise remains + in force. You may convey covered works to others for the sole purpose + of having them make modifications exclusively for you, or provide you + with facilities for running those works, provided that you comply with + the terms of this License in conveying all material for which you do + not control copyright. Those thus making or running the covered works + for you must do so exclusively on your behalf, under your direction + and control, on terms that prohibit them from making any copies of + your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under + the conditions stated below. Sublicensing is not allowed; section 10 + makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological + measure under any applicable law fulfilling obligations under article + 11 of the WIPO copyright treaty adopted on 20 December 1996, or + similar laws prohibiting or restricting circumvention of such + measures. + + When you convey a covered work, you waive any legal power to forbid + circumvention of technological measures to the extent such circumvention + is effected by exercising rights under this License with respect to + the covered work, and you disclaim any intention to limit operation or + modification of the work as a means of enforcing, against the work's + users, your or third parties' legal rights to forbid circumvention of + technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you + receive it, in any medium, provided that you conspicuously and + appropriately publish on each copy an appropriate copyright notice; + keep intact all notices stating that this License and any + non-permissive terms added in accord with section 7 apply to the code; + keep intact all notices of the absence of any warranty; and give all + recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, + and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to + produce it from the Program, in the form of source code under the + terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent + works, which are not by their nature extensions of the covered work, + and which are not combined with it such as to form a larger program, + in or on a volume of a storage or distribution medium, is called an + "aggregate" if the compilation and its resulting copyright are not + used to limit the access or legal rights of the compilation's users + beyond what the individual works permit. Inclusion of a covered work + in an aggregate does not cause this License to apply to the other + parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms + of sections 4 and 5, provided that you also convey the + machine-readable Corresponding Source under the terms of this License, + in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded + from the Corresponding Source as a System Library, need not be + included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any + tangible personal property which is normally used for personal, family, + or household purposes, or (2) anything designed or sold for incorporation + into a dwelling. In determining whether a product is a consumer product, + doubtful cases shall be resolved in favor of coverage. For a particular + product received by a particular user, "normally used" refers to a + typical or common use of that class of product, regardless of the status + of the particular user or of the way in which the particular user + actually uses, or expects or is expected to use, the product. A product + is a consumer product regardless of whether the product has substantial + commercial, industrial or non-consumer uses, unless such uses represent + the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, + procedures, authorization keys, or other information required to install + and execute modified versions of a covered work in that User Product from + a modified version of its Corresponding Source. The information must + suffice to ensure that the continued functioning of the modified object + code is in no case prevented or interfered with solely because + modification has been made. + + If you convey an object code work under this section in, or with, or + specifically for use in, a User Product, and the conveying occurs as + part of a transaction in which the right of possession and use of the + User Product is transferred to the recipient in perpetuity or for a + fixed term (regardless of how the transaction is characterized), the + Corresponding Source conveyed under this section must be accompanied + by the Installation Information. But this requirement does not apply + if neither you nor any third party retains the ability to install + modified object code on the User Product (for example, the work has + been installed in ROM). + + The requirement to provide Installation Information does not include a + requirement to continue to provide support service, warranty, or updates + for a work that has been modified or installed by the recipient, or for + the User Product in which it has been modified or installed. Access to a + network may be denied when the modification itself materially and + adversely affects the operation of the network or violates the rules and + protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, + in accord with this section must be in a format that is publicly + documented (and with an implementation available to the public in + source code form), and must require no special password or key for + unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this + License by making exceptions from one or more of its conditions. + Additional permissions that are applicable to the entire Program shall + be treated as though they were included in this License, to the extent + that they are valid under applicable law. If additional permissions + apply only to part of the Program, that part may be used separately + under those permissions, but the entire Program remains governed by + this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option + remove any additional permissions from that copy, or from any part of + it. (Additional permissions may be written to require their own + removal in certain cases when you modify the work.) You may place + additional permissions on material, added by you to a covered work, + for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you + add to a covered work, you may (if authorized by the copyright holders of + that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further + restrictions" within the meaning of section 10. If the Program as you + received it, or any part of it, contains a notice stating that it is + governed by this License along with a term that is a further + restriction, you may remove that term. If a license document contains + a further restriction but permits relicensing or conveying under this + License, you may add to a covered work material governed by the terms + of that license document, provided that the further restriction does + not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you + must place, in the relevant source files, a statement of the + additional terms that apply to those files, or a notice indicating + where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the + form of a separately written license, or stated as exceptions; + the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly + provided under this License. Any attempt otherwise to propagate or + modify it is void, and will automatically terminate your rights under + this License (including any patent licenses granted under the third + paragraph of section 11). + + However, if you cease all violation of this License, then your + license from a particular copyright holder is reinstated (a) + provisionally, unless and until the copyright holder explicitly and + finally terminates your license, and (b) permanently, if the copyright + holder fails to notify you of the violation by some reasonable means + prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is + reinstated permanently if the copyright holder notifies you of the + violation by some reasonable means, this is the first time you have + received notice of violation of this License (for any work) from that + copyright holder, and you cure the violation prior to 30 days after + your receipt of the notice. + + Termination of your rights under this section does not terminate the + licenses of parties who have received copies or rights from you under + this License. If your rights have been terminated and not permanently + reinstated, you do not qualify to receive new licenses for the same + material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or + run a copy of the Program. Ancillary propagation of a covered work + occurring solely as a consequence of using peer-to-peer transmission + to receive a copy likewise does not require acceptance. However, + nothing other than this License grants you permission to propagate or + modify any covered work. These actions infringe copyright if you do + not accept this License. Therefore, by modifying or propagating a + covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically + receives a license from the original licensors, to run, modify and + propagate that work, subject to this License. You are not responsible + for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an + organization, or substantially all assets of one, or subdividing an + organization, or merging organizations. If propagation of a covered + work results from an entity transaction, each party to that + transaction who receives a copy of the work also receives whatever + licenses to the work the party's predecessor in interest had or could + give under the previous paragraph, plus a right to possession of the + Corresponding Source of the work from the predecessor in interest, if + the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the + rights granted or affirmed under this License. For example, you may + not impose a license fee, royalty, or other charge for exercise of + rights granted under this License, and you may not initiate litigation + (including a cross-claim or counterclaim in a lawsuit) alleging that + any patent claim is infringed by making, using, selling, offering for + sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this + License of the Program or a work on which the Program is based. The + work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims + owned or controlled by the contributor, whether already acquired or + hereafter acquired, that would be infringed by some manner, permitted + by this License, of making, using, or selling its contributor version, + but do not include claims that would be infringed only as a + consequence of further modification of the contributor version. For + purposes of this definition, "control" includes the right to grant + patent sublicenses in a manner consistent with the requirements of + this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free + patent license under the contributor's essential patent claims, to + make, use, sell, offer for sale, import and otherwise run, modify and + propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express + agreement or commitment, however denominated, not to enforce a patent + (such as an express permission to practice a patent or covenant not to + sue for patent infringement). To "grant" such a patent license to a + party means to make such an agreement or commitment not to enforce a + patent against the party. + + If you convey a covered work, knowingly relying on a patent license, + and the Corresponding Source of the work is not available for anyone + to copy, free of charge and under the terms of this License, through a + publicly available network server or other readily accessible means, + then you must either (1) cause the Corresponding Source to be so + available, or (2) arrange to deprive yourself of the benefit of the + patent license for this particular work, or (3) arrange, in a manner + consistent with the requirements of this License, to extend the patent + license to downstream recipients. "Knowingly relying" means you have + actual knowledge that, but for the patent license, your conveying the + covered work in a country, or your recipient's use of the covered work + in a country, would infringe one or more identifiable patents in that + country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or + arrangement, you convey, or propagate by procuring conveyance of, a + covered work, and grant a patent license to some of the parties + receiving the covered work authorizing them to use, propagate, modify + or convey a specific copy of the covered work, then the patent license + you grant is automatically extended to all recipients of the covered + work and works based on it. + + A patent license is "discriminatory" if it does not include within + the scope of its coverage, prohibits the exercise of, or is + conditioned on the non-exercise of one or more of the rights that are + specifically granted under this License. You may not convey a covered + work if you are a party to an arrangement with a third party that is + in the business of distributing software, under which you make payment + to the third party based on the extent of your activity of conveying + the work, and under which the third party grants, to any of the + parties who would receive the covered work from you, a discriminatory + patent license (a) in connection with copies of the covered work + conveyed by you (or copies made from those copies), or (b) primarily + for and in connection with specific products or compilations that + contain the covered work, unless you entered into that arrangement, + or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting + any implied license or other defenses to infringement that may + otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or + otherwise) that contradict the conditions of this License, they do not + excuse you from the conditions of this License. If you cannot convey a + covered work so as to satisfy simultaneously your obligations under this + License and any other pertinent obligations, then as a consequence you may + not convey it at all. For example, if you agree to terms that obligate you + to collect a royalty for further conveying from those to whom you convey + the Program, the only way you could satisfy both those terms and this + License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have + permission to link or combine any covered work with a work licensed + under version 3 of the GNU Affero General Public License into a single + combined work, and to convey the resulting work. The terms of this + License will continue to apply to the part which is the covered work, + but the special requirements of the GNU Affero General Public License, + section 13, concerning interaction through a network will apply to the + combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of + the GNU General Public License from time to time. Such new versions will + be similar in spirit to the present version, but may differ in detail to + address new problems or concerns. + + Each version is given a distinguishing version number. If the + Program specifies that a certain numbered version of the GNU General + Public License "or any later version" applies to it, you have the + option of following the terms and conditions either of that numbered + version or of any later version published by the Free Software + Foundation. If the Program does not specify a version number of the + GNU General Public License, you may choose any version ever published + by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future + versions of the GNU General Public License can be used, that proxy's + public statement of acceptance of a version permanently authorizes you + to choose that version for the Program. + + Later license versions may give you additional or different + permissions. However, no additional obligations are imposed on any + author or copyright holder as a result of your choosing to follow a + later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY + APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT + HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY + OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, + THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM + IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF + ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING + WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS + THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY + GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE + USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF + DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD + PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), + EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF + SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided + above cannot be given local legal effect according to their terms, + reviewing courts shall apply local law that most closely approximates + an absolute waiver of all civil liability in connection with the + Program, unless a warranty or assumption of liability accompanies a + copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest + possible use to the public, the best way to achieve this is to make it + free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest + to attach them to the start of each source file to most effectively + state the exclusion of warranty; and each file should have at least + the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short + notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + + The hypothetical commands `show w' and `show c' should show the appropriate + parts of the General Public License. Of course, your program's commands + might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, + if any, to sign a "copyright disclaimer" for the program, if necessary. + For more information on this, and how to apply and follow the GNU GPL, see + . + + The GNU General Public License does not permit incorporating your program + into proprietary programs. If your program is a subroutine library, you + may consider it more useful to permit linking proprietary applications with + the library. If this is what you want to do, use the GNU Lesser General + Public License instead of this License. But first, please read + . + + + Name: libquadmath + Files: scipy.libs/libquadmath*.so + Description: dynamically linked to files compiled with gcc + Availability: https://gcc.gnu.org/git/?p=gcc.git;a=tree;f=libquadmath + License: LGPL-2.1-or-later + + GCC Quad-Precision Math Library + Copyright (C) 2010-2019 Free Software Foundation, Inc. + Written by Francois-Xavier Coudert + + This file is part of the libquadmath library. + Libquadmath is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + Libquadmath is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html + +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Science/Research +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Programming Language :: C +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Classifier: Topic :: Software Development :: Libraries +Classifier: Topic :: Scientific/Engineering +Classifier: Operating System :: Microsoft :: Windows +Classifier: Operating System :: POSIX :: Linux +Classifier: Operating System :: POSIX +Classifier: Operating System :: Unix +Classifier: Operating System :: MacOS +Project-URL: homepage, https://scipy.org/ +Project-URL: documentation, https://docs.scipy.org/doc/scipy/ +Project-URL: source, https://github.com/scipy/scipy +Project-URL: download, https://github.com/scipy/scipy/releases +Project-URL: tracker, https://github.com/scipy/scipy/issues +Requires-Python: >=3.12 +Requires-Dist: numpy<2.8,>=2.0.0 +Provides-Extra: test +Requires-Dist: pytest>=8.0.0; extra == "test" +Requires-Dist: pytest-cov; extra == "test" +Requires-Dist: pytest-timeout; extra == "test" +Requires-Dist: pytest-xdist; extra == "test" +Requires-Dist: asv; extra == "test" +Requires-Dist: mpmath; extra == "test" +Requires-Dist: gmpy2; extra == "test" +Requires-Dist: threadpoolctl; extra == "test" +Requires-Dist: scikit-umfpack; extra == "test" +Requires-Dist: pooch; extra == "test" +Requires-Dist: hypothesis>=6.30; extra == "test" +Requires-Dist: array-api-strict>=2.3.1; extra == "test" +Requires-Dist: Cython; extra == "test" +Requires-Dist: meson; extra == "test" +Requires-Dist: ninja; sys_platform != "emscripten" and extra == "test" +Requires-Dist: scipy-doctest>=2.0.0; extra == "test" +Provides-Extra: doc +Requires-Dist: sphinx<8.2.0,>=5.0.0; extra == "doc" +Requires-Dist: intersphinx_registry; extra == "doc" +Requires-Dist: pydata-sphinx-theme>=0.15.2; extra == "doc" +Requires-Dist: sphinx-copybutton; extra == "doc" +Requires-Dist: sphinx-design>=0.4.0; extra == "doc" +Requires-Dist: matplotlib>=3.5; extra == "doc" +Requires-Dist: numpydoc; extra == "doc" +Requires-Dist: jupytext; extra == "doc" +Requires-Dist: myst-nb>=1.2.0; extra == "doc" +Requires-Dist: pooch; extra == "doc" +Requires-Dist: jupyterlite-sphinx>=0.19.1; extra == "doc" +Requires-Dist: jupyterlite-pyodide-kernel; extra == "doc" +Requires-Dist: linkify-it-py; extra == "doc" +Requires-Dist: tabulate; extra == "doc" +Provides-Extra: dev +Requires-Dist: click<8.3.0; extra == "dev" +Requires-Dist: spin; extra == "dev" +Requires-Dist: mypy==1.19.1; extra == "dev" +Requires-Dist: pyrefly==0.63.0; extra == "dev" +Requires-Dist: typing_extensions; extra == "dev" +Requires-Dist: types-psutil; extra == "dev" +Requires-Dist: pycodestyle; extra == "dev" +Requires-Dist: ruff>=0.12.0; extra == "dev" +Requires-Dist: cython-lint>=0.12.2; extra == "dev" +Description-Content-Type: text/x-rst + +.. image:: https://raw.githubusercontent.com/scipy/scipy/main/doc/source/_static/logo.svg + :target: https://scipy.org + :width: 110 + :height: 110 + :align: left + +.. image:: https://img.shields.io/badge/powered%20by-NumFOCUS-orange.svg?style=flat&colorA=E1523D&colorB=007D8A + :target: https://numfocus.org + +.. image:: https://img.shields.io/pypi/dm/scipy.svg?label=Pypi%20downloads + :target: https://pypi.org/project/scipy/ + +.. image:: https://img.shields.io/conda/dn/conda-forge/scipy.svg?label=Conda%20downloads + :target: https://anaconda.org/conda-forge/scipy + +.. image:: https://img.shields.io/badge/stackoverflow-Ask%20questions-blue.svg + :target: https://stackoverflow.com/questions/tagged/scipy + +.. image:: https://img.shields.io/badge/DOI-10.1038%2Fs41592--019--0686--2-blue.svg + :target: https://www.nature.com/articles/s41592-019-0686-2 + +.. image:: https://insights.linuxfoundation.org/api/badge/health-score?project=scipy + :target: https://insights.linuxfoundation.org/project/scipy + +SciPy (pronounced "Sigh Pie") is an open-source software for mathematics, +science, and engineering. It includes modules for statistics, optimization, +integration, linear algebra, Fourier transforms, signal and image processing, +ODE solvers, and more. + +- **Website:** https://scipy.org +- **Documentation:** https://docs.scipy.org/doc/scipy/ +- **Development version of the documentation:** https://scipy.github.io/devdocs +- **SciPy development forum:** https://discuss.scientific-python.org/c/contributor/scipy +- **Stack Overflow:** https://stackoverflow.com/questions/tagged/scipy +- **Source code:** https://github.com/scipy/scipy +- **Contributing:** https://scipy.github.io/devdocs/dev/index.html +- **Bug reports:** https://github.com/scipy/scipy/issues +- **Code of Conduct:** https://docs.scipy.org/doc/scipy/dev/conduct/code_of_conduct.html +- **Report a security vulnerability:** via Tidelift, as explained in + `our docs on Security `__ +- **Citing in your work:** https://www.scipy.org/citing-scipy/ + +SciPy is built to work with +NumPy arrays, and provides many user-friendly and efficient numerical routines, +such as routines for numerical integration and optimization. Together, they +run on all popular operating systems, are quick to install, and are free of +charge. NumPy and SciPy are easy to use, but powerful enough to be depended +upon by some of the world's leading scientists and engineers. If you need to +manipulate numbers on a computer and display or publish the results, give +SciPy a try! + +For the installation instructions, see `our install +guide `__. + + +Call for Contributions +---------------------- + +We appreciate and welcome contributions. Small improvements or fixes are always appreciated; issues labeled as "good +first issue" may be a good starting point. Have a look at `our contributing +guide `__. + +Writing code isn’t the only way to contribute to SciPy. You can also: + +- review pull requests +- triage issues +- develop tutorials, presentations, and other educational materials +- maintain and improve `our website `__ +- develop graphic design for our brand assets and promotional materials +- help with outreach and onboard new contributors +- write grant proposals and help with other fundraising efforts + +If you’re unsure where to start or how your skills fit in, reach out! You can +ask on the `forum `__ +or here, on GitHub, by leaving a comment on a relevant issue that is already +open. + +If you are new to contributing to open source, `this +guide `__ helps explain why, what, +and how to get involved. diff --git a/venv/lib/python3.12/site-packages/scipy-1.18.0.dist-info/RECORD b/venv/lib/python3.12/site-packages/scipy-1.18.0.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..6b0cb7916e784e416c5871793bec4dea4a0b0a7f --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy-1.18.0.dist-info/RECORD @@ -0,0 +1,2421 @@ +scipy-1.18.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +scipy-1.18.0.dist-info/LICENSE.txt,sha256=F-fbn_pJExIfot9IRjOjYkVqSC2nNsmdtHEamEc3cOo,46845 +scipy-1.18.0.dist-info/METADATA,sha256=FXvRUK868ztA7GFIm-gVN1KyafsQxLRbjOpRBPMjVCA,62302 +scipy-1.18.0.dist-info/RECORD,, +scipy-1.18.0.dist-info/WHEEL,sha256=rJCplGeGFjRoUvxJWhVONRgyGYGZoOkhyej0s3KyjoI,138 +scipy-1.18.0.dist-info/sboms/auditwheel.cdx.json,sha256=JacT40MbmnkHb-ZEy5znZyeh8JsXaxy9E4TPR8hExDw,1954 +scipy.libs/libgfortran-040039e1-0352e75f.so.5.0.0,sha256=xgkASOzMdjUiwS7wFvgdprYnyzoET1XPBHmoOcQcCYA,2833617 +scipy.libs/libgfortran-83c28eba.so.5.0.0,sha256=5VSHn-5-2oAFW2yhQHpU0ABQh0-DDMhrK_P1DfXHuys,2714697 +scipy.libs/libquadmath-2284e583.so.0.0.0,sha256=qTB7uqNTYj-gFOi2gtUMRin15cfWHaqahlL30WXNQho,272193 +scipy.libs/libquadmath-96973f99-934c22de.so.0.0.0,sha256=btUTf0Enga14Y0OftUNhP2ILQ8MrYykqACkkYWL1u8Y,250985 +scipy.libs/libscipy_openblas-5f890258.so,sha256=KltMGmMRhzxZEyss6q88JiWKFzULfNJ-qHgqR-MyKYE,25094609 +scipy/__config__.py,sha256=Q4fSqfqwE2Foojw-Vq9vqvbp67ToZVa-6cPFREJcOMk,5762 +scipy/__init__.py,sha256=OcyuMApHOcxTcZvTs5zj-PZuc2xutnIAbjOKTFzD3HY,4193 +scipy/__pycache__/__config__.cpython-312.pyc,, +scipy/__pycache__/__init__.cpython-312.pyc,, +scipy/__pycache__/_distributor_init.cpython-312.pyc,, +scipy/__pycache__/conftest.cpython-312.pyc,, +scipy/__pycache__/version.cpython-312.pyc,, +scipy/_cyutility.cpython-312-x86_64-linux-gnu.so,sha256=6oVIxZPsiANkCeFOiUDoSt8nsMcvlCJAw1J7TmsZPnw,210696 +scipy/_distributor_init.py,sha256=zJThN3Fvof09h24804pNDPd2iN-lCHV3yPlZylSefgQ,611 +scipy/_external/__pycache__/_array_api_compat_vendor.cpython-312.pyc,, +scipy/_external/_array_api_compat_vendor.py,sha256=GDZsj6XPTWFdChwGffF1Yz4UcF-laHbWi8-wbPcg7kM,412 +scipy/_external/array_api_compat/__init__.py,sha256=_jQR3GZmLNZ76akOxCgMWXuCt_-UHKDO5jGzDTQ2FFA,992 +scipy/_external/array_api_compat/__pycache__/__init__.cpython-312.pyc,, +scipy/_external/array_api_compat/__pycache__/_internal.cpython-312.pyc,, +scipy/_external/array_api_compat/_internal.py,sha256=tYJkaxIxVxELYvo9JK2UgCCJBMw01JiVmomTIjVpM5U,2072 +scipy/_external/array_api_compat/common/__init__.py,sha256=4IcMWP5rARLYe2_pgXDWEuj2YpM0c1G6Pb5pkbQ0QS8,38 +scipy/_external/array_api_compat/common/__pycache__/__init__.cpython-312.pyc,, +scipy/_external/array_api_compat/common/__pycache__/_aliases.cpython-312.pyc,, +scipy/_external/array_api_compat/common/__pycache__/_fft.cpython-312.pyc,, +scipy/_external/array_api_compat/common/__pycache__/_helpers.cpython-312.pyc,, +scipy/_external/array_api_compat/common/__pycache__/_linalg.cpython-312.pyc,, +scipy/_external/array_api_compat/common/__pycache__/_typing.cpython-312.pyc,, +scipy/_external/array_api_compat/common/_aliases.py,sha256=0fYL3vEwX0kRdpscJY3l7Q37kHfAlzmxUulkI-e9Sws,18988 +scipy/_external/array_api_compat/common/_fft.py,sha256=ckCR2uHtz0iaOkcuvqVunhz1khIdxQNKuVU0x1bfrq8,4669 +scipy/_external/array_api_compat/common/_helpers.py,sha256=hJ-wSNYS9DTMca2YPMF6HHT90xuBMYPSlzsOR6r2RHc,32568 +scipy/_external/array_api_compat/common/_linalg.py,sha256=htvSfG5LQ5L_Qmo4g0PCk_q3XJx_M79pl8iLLXFDkaE,6882 +scipy/_external/array_api_compat/common/_typing.py,sha256=gFg3AvkvXQ0VVeiZtjejpaU5V4oCXnRlbGzNmHpLPFs,4498 +scipy/_external/array_api_compat/cupy/__init__.py,sha256=mx99f0xUWVMJKZfgdJItJ_lNt2hsvrW9b8omTKSTpKI,736 +scipy/_external/array_api_compat/cupy/__pycache__/__init__.cpython-312.pyc,, +scipy/_external/array_api_compat/cupy/__pycache__/_aliases.cpython-312.pyc,, +scipy/_external/array_api_compat/cupy/__pycache__/_info.cpython-312.pyc,, +scipy/_external/array_api_compat/cupy/__pycache__/_typing.cpython-312.pyc,, +scipy/_external/array_api_compat/cupy/__pycache__/fft.cpython-312.pyc,, +scipy/_external/array_api_compat/cupy/__pycache__/linalg.cpython-312.pyc,, +scipy/_external/array_api_compat/cupy/_aliases.py,sha256=Fu9y7OcaLIimJp5m2tLSM6hQQirLrUnZoNtKt9TFFgw,6400 +scipy/_external/array_api_compat/cupy/_info.py,sha256=KtzxvdB5SlFWlul_9m3RebCS3atrlWeUURnGoK3Ews4,10130 +scipy/_external/array_api_compat/cupy/_typing.py,sha256=L6RUNFoy-ZwLizEqCSqHN-6GC5chKYfQL8xW4FVksKA,607 +scipy/_external/array_api_compat/cupy/fft.py,sha256=D5HJ6rclFA7-CO_NleuCZQy2Lz7InkFuE8CHTrxrXc0,872 +scipy/_external/array_api_compat/cupy/linalg.py,sha256=kzuPCG9_JFInSBF5YO32BXzAY7fHrEmqfPZc6mYKlwU,1851 +scipy/_external/array_api_compat/dask/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +scipy/_external/array_api_compat/dask/__pycache__/__init__.cpython-312.pyc,, +scipy/_external/array_api_compat/dask/array/__init__.py,sha256=ptYpBdBIxltcyGDR2JT_ryJ4Z0I0c70TC4uPaOdtYTk,677 +scipy/_external/array_api_compat/dask/array/__pycache__/__init__.cpython-312.pyc,, +scipy/_external/array_api_compat/dask/array/__pycache__/_aliases.cpython-312.pyc,, +scipy/_external/array_api_compat/dask/array/__pycache__/_info.cpython-312.pyc,, +scipy/_external/array_api_compat/dask/array/__pycache__/fft.cpython-312.pyc,, +scipy/_external/array_api_compat/dask/array/__pycache__/linalg.cpython-312.pyc,, +scipy/_external/array_api_compat/dask/array/_aliases.py,sha256=eJkzntr9_0ew0SFBeAEe3zd1vjdsXb7wpP_HOqVl_GM,10432 +scipy/_external/array_api_compat/dask/array/_info.py,sha256=vf4tefJkdB2lFuMqRXOYTMgwVXaSXfa9ivf42TnxyJY,12242 +scipy/_external/array_api_compat/dask/array/fft.py,sha256=dJEAXxGtfP5Z_-oR8GNuiMwHG1-Hbwax02wxmN95QjQ,333 +scipy/_external/array_api_compat/dask/array/linalg.py,sha256=e6cfmzB0VU1L3Vr9jKukOUYEARzEC1cDE59ylJ5bXCI,2140 +scipy/_external/array_api_compat/numpy/__init__.py,sha256=5-Rk4rJx0ft8R_dAZnjTSkRR68V1GwQJnHSVsTzVrJ0,1064 +scipy/_external/array_api_compat/numpy/__pycache__/__init__.cpython-312.pyc,, +scipy/_external/array_api_compat/numpy/__pycache__/_aliases.cpython-312.pyc,, +scipy/_external/array_api_compat/numpy/__pycache__/_info.cpython-312.pyc,, +scipy/_external/array_api_compat/numpy/__pycache__/_typing.cpython-312.pyc,, +scipy/_external/array_api_compat/numpy/__pycache__/fft.cpython-312.pyc,, +scipy/_external/array_api_compat/numpy/__pycache__/linalg.cpython-312.pyc,, +scipy/_external/array_api_compat/numpy/_aliases.py,sha256=3FhudS9W4PofYTtsf6vTHEtTC_T_i2ecUE4eBMLyqrg,5666 +scipy/_external/array_api_compat/numpy/_info.py,sha256=i0KYN5F9x5_y5g2jxvUmgO6exvAzqELu5UbV7VWaHEY,10795 +scipy/_external/array_api_compat/numpy/_typing.py,sha256=hXWLcYHogQcHzK1tR8B9difxm3UFlHhUUaXBHydqpyE,605 +scipy/_external/array_api_compat/numpy/fft.py,sha256=gx6R3o_ehnMWpsvBI2y0mBNxwZP9KFLTuQ9uP4eSrMk,714 +scipy/_external/array_api_compat/numpy/linalg.py,sha256=0auL21eSpEuVXBW3zrALf4NoIZK9En3XryHXQWkiftQ,6517 +scipy/_external/array_api_compat/torch/__init__.py,sha256=u1uSglKmefyN7BN259a1vt0ctPsbI1KaOi00tFT9G-U,633 +scipy/_external/array_api_compat/torch/__pycache__/__init__.cpython-312.pyc,, +scipy/_external/array_api_compat/torch/__pycache__/_aliases.cpython-312.pyc,, +scipy/_external/array_api_compat/torch/__pycache__/_info.cpython-312.pyc,, +scipy/_external/array_api_compat/torch/__pycache__/_typing.cpython-312.pyc,, +scipy/_external/array_api_compat/torch/__pycache__/fft.cpython-312.pyc,, +scipy/_external/array_api_compat/torch/__pycache__/linalg.cpython-312.pyc,, +scipy/_external/array_api_compat/torch/_aliases.py,sha256=0gnG58ooe1HQlLao2xS1kwXeNZi8UmT_QszEqfnJjgs,32923 +scipy/_external/array_api_compat/torch/_info.py,sha256=5Q3Q_92Oo2SoBYw49NwlIdOiRHPMA7xEn02fzmNF3o8,11896 +scipy/_external/array_api_compat/torch/_typing.py,sha256=-uCkuTie1g9hb4vwPLK9eEnir9Zp67wAhrfaI_o-35E,108 +scipy/_external/array_api_compat/torch/fft.py,sha256=ijW1aJaOys2EMkF04hu3vSMQMqd2ZLUpLvAZUQZf4Lw,1833 +scipy/_external/array_api_compat/torch/linalg.py,sha256=x-qGeIVVbNsmk9ZLSV4JHWZE4WB8HqGeHlQrhNPMGJs,4560 +scipy/_external/array_api_extra/__init__.py,sha256=BdTvscZ-ZvEphgcgRMikFIXDIpgn9QJv0r4WByh6qrs,934 +scipy/_external/array_api_extra/__pycache__/__init__.cpython-312.pyc,, +scipy/_external/array_api_extra/__pycache__/_delegation.cpython-312.pyc,, +scipy/_external/array_api_extra/__pycache__/testing.cpython-312.pyc,, +scipy/_external/array_api_extra/_delegation.py,sha256=Fs_-r5hCmkaR4a5MK0wuq2KpBU6cvmRCfGBPZ-eI0lI,37258 +scipy/_external/array_api_extra/_lib/__init__.py,sha256=Mht4YV9Rpkzg5kORPlgjOOXk4y7bvdngowS6KGSHqNE,36 +scipy/_external/array_api_extra/_lib/__pycache__/__init__.cpython-312.pyc,, +scipy/_external/array_api_extra/_lib/__pycache__/_at.cpython-312.pyc,, +scipy/_external/array_api_extra/_lib/__pycache__/_backends.cpython-312.pyc,, +scipy/_external/array_api_extra/_lib/__pycache__/_funcs.cpython-312.pyc,, +scipy/_external/array_api_extra/_lib/__pycache__/_lazy.cpython-312.pyc,, +scipy/_external/array_api_extra/_lib/__pycache__/_testing.cpython-312.pyc,, +scipy/_external/array_api_extra/_lib/_at.py,sha256=MTwrDjcZz_8---8a37SkCoAoUNMlSjqc40gYvu18k0Y,15316 +scipy/_external/array_api_extra/_lib/_backends.py,sha256=aqUgTDJFDwbrkJtG2V0pKRIdgv8ugnOPY_EvOU2NIk8,2122 +scipy/_external/array_api_extra/_lib/_funcs.py,sha256=KaaWfCShdNzFxbJVJ7qOj7K00_n6vcBPHq6no-QF4oc,25716 +scipy/_external/array_api_extra/_lib/_lazy.py,sha256=RE1DzMqj0j7GpY6GKSLyBuTL9vQaWkLSn7h-iZ9eVd8,13811 +scipy/_external/array_api_extra/_lib/_testing.py,sha256=GtIc8GIDEqw90nn5g72gt71flj5-1BP29ZKzwoduoSQ,10290 +scipy/_external/array_api_extra/_lib/_utils/__init__.py,sha256=8ICffM2MprXpWZd8ia0-5ZTnKtDfeZD0gExLveDrXZs,49 +scipy/_external/array_api_extra/_lib/_utils/__pycache__/__init__.cpython-312.pyc,, +scipy/_external/array_api_extra/_lib/_utils/__pycache__/_compat.cpython-312.pyc,, +scipy/_external/array_api_extra/_lib/_utils/__pycache__/_helpers.cpython-312.pyc,, +scipy/_external/array_api_extra/_lib/_utils/__pycache__/_typing.cpython-312.pyc,, +scipy/_external/array_api_extra/_lib/_utils/_compat.py,sha256=4A5a_S4qo88_H8LKmQal2e_1PVQAY1eInKDZsCgPNYU,1812 +scipy/_external/array_api_extra/_lib/_utils/_compat.pyi,sha256=6QexmJx0rPSEtvVQAt2HbS87F8njDMsD9UhyIJsen8c,1720 +scipy/_external/array_api_extra/_lib/_utils/_helpers.py,sha256=17F6zPZbYDIyhdpBoqPzutOVRCL8JlNWDcvFScFtDcU,19783 +scipy/_external/array_api_extra/_lib/_utils/_typing.py,sha256=0NSYWpdXH58Dx9T7HsNPM0NW2iPlTZS6xOrR-5tFOYc,228 +scipy/_external/array_api_extra/_lib/_utils/_typing.pyi,sha256=tfmj1bK5a1RWFc8qXR10FhLlXBwzyItFrN0zcXTWntM,4665 +scipy/_external/array_api_extra/testing.py,sha256=vkWPMlzUbjKCBqcgCnvrCBHkCYCc2uXIptHsLV1Lo6Y,19464 +scipy/_external/cobyqa/__init__.py,sha256=Uo9FFveH-XgEuhkleW5NIiBwp3YZ71r-Ybv3t2JnqE8,578 +scipy/_external/cobyqa/__pycache__/__init__.cpython-312.pyc,, +scipy/_external/cobyqa/__pycache__/framework.cpython-312.pyc,, +scipy/_external/cobyqa/__pycache__/main.cpython-312.pyc,, +scipy/_external/cobyqa/__pycache__/models.cpython-312.pyc,, +scipy/_external/cobyqa/__pycache__/problem.cpython-312.pyc,, +scipy/_external/cobyqa/__pycache__/settings.cpython-312.pyc,, +scipy/_external/cobyqa/framework.py,sha256=lIeKCkDLxHbMmSTiMcyasvVe77jVvh_YTOYX0HnK4Qk,38900 +scipy/_external/cobyqa/main.py,sha256=wz0M2iqFfzeTaZUq_j1TkF_9V_SJ1t73A-0fdH0eSs4,57527 +scipy/_external/cobyqa/models.py,sha256=LGPk0teoVBse25bkIePklFgOUHo63VnbKLXh4gxZhSA,50694 +scipy/_external/cobyqa/problem.py,sha256=SiPgmiFTxiW5yJ_FVf37Z9GQGo6Gx_fJ3RXMzhsrn40,40203 +scipy/_external/cobyqa/settings.py,sha256=ogfiShxuPHsMfW16OGSwB9-mIPRiuWZSGXBOCO2HDvw,3826 +scipy/_external/cobyqa/subsolvers/__init__.py,sha256=VmFBpi-_tNa8yzNmu_fufewmPTnCU6ycNCGcN34UBcc,341 +scipy/_external/cobyqa/subsolvers/__pycache__/__init__.cpython-312.pyc,, +scipy/_external/cobyqa/subsolvers/__pycache__/geometry.cpython-312.pyc,, +scipy/_external/cobyqa/subsolvers/__pycache__/optim.cpython-312.pyc,, +scipy/_external/cobyqa/subsolvers/geometry.py,sha256=dgS-C0QBUhkzPhHULFIRbnbFOIEB005GyPYE-i-cuFY,14173 +scipy/_external/cobyqa/subsolvers/optim.py,sha256=hIseVqrPyI3ezICGNXkCtKlpqvAO2W6ZQe0n7sxfkss,45512 +scipy/_external/cobyqa/utils/__init__.py,sha256=sw6g402vXaXwX7rMhxrNl5PD5OBs89l5f3XNcYApRHs,359 +scipy/_external/cobyqa/utils/__pycache__/__init__.cpython-312.pyc,, +scipy/_external/cobyqa/utils/__pycache__/exceptions.cpython-312.pyc,, +scipy/_external/cobyqa/utils/__pycache__/math.cpython-312.pyc,, +scipy/_external/cobyqa/utils/__pycache__/versions.cpython-312.pyc,, +scipy/_external/cobyqa/utils/exceptions.py,sha256=N1JdmUxHnME95wEZHyeeF_M6GXPEqH5t3qzuXig49YE,483 +scipy/_external/cobyqa/utils/math.py,sha256=beT-Tib41TJWZecjnKhSfu4foOLLaHlWj5CcyRhdSl4,1611 +scipy/_external/cobyqa/utils/versions.py,sha256=eBOlEGAKFCfjFqVprdali3M1G7l0k_kxb7ku-Lz2bU0,1465 +scipy/_external/packaging_version/__pycache__/_structures.cpython-312.pyc,, +scipy/_external/packaging_version/__pycache__/version.cpython-312.pyc,, +scipy/_external/packaging_version/_structures.py,sha256=q3eVNmbWJGG_S0Dit_S3Ao8qQqz_5PYTXFAKBZe5yr4,1431 +scipy/_external/packaging_version/version.py,sha256=olfyuk_DPbflNkJ4wBWetXQ17c74x3DB501degUv7DY,16676 +scipy/_external/pyprima/__init__.py,sha256=YakFMfftDrq3fBu2-_O3FWjeu41511VEPDTtzJ0IKX0,8461 +scipy/_external/pyprima/__pycache__/__init__.cpython-312.pyc,, +scipy/_external/pyprima/cobyla/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +scipy/_external/pyprima/cobyla/__pycache__/__init__.cpython-312.pyc,, +scipy/_external/pyprima/cobyla/__pycache__/cobyla.cpython-312.pyc,, +scipy/_external/pyprima/cobyla/__pycache__/cobylb.cpython-312.pyc,, +scipy/_external/pyprima/cobyla/__pycache__/geometry.cpython-312.pyc,, +scipy/_external/pyprima/cobyla/__pycache__/initialize.cpython-312.pyc,, +scipy/_external/pyprima/cobyla/__pycache__/trustregion.cpython-312.pyc,, +scipy/_external/pyprima/cobyla/__pycache__/update.cpython-312.pyc,, +scipy/_external/pyprima/cobyla/cobyla.py,sha256=YiuAhtZ5u13uDciL7sc0qxVVpH5t5IKJ2R0X7qTFMwo,22146 +scipy/_external/pyprima/cobyla/cobylb.py,sha256=T9ZRYBYZdAYvRLJ9sjAYyQb3vdcRxMmj3AukIidw9L0,40764 +scipy/_external/pyprima/cobyla/geometry.py,sha256=84zrSvKNCr-WnXqFrnnb-nE11yVAI_CME79nhVc4ZvM,10645 +scipy/_external/pyprima/cobyla/initialize.py,sha256=ft98aJ03PvUUvIXluk-4IUZLbDQPF3ZLdFUhfpRh5Zg,9409 +scipy/_external/pyprima/cobyla/trustregion.py,sha256=6UNiTytfEyxW1-qHpqBGzcegRyrnx0A82XwLS5b9N_U,25200 +scipy/_external/pyprima/cobyla/update.py,sha256=PwDRKMY_VAhBKVSOkMUCOSMhGezpfiafIansW1vqI4I,13882 +scipy/_external/pyprima/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +scipy/_external/pyprima/common/__pycache__/__init__.cpython-312.pyc,, +scipy/_external/pyprima/common/__pycache__/_bounds.cpython-312.pyc,, +scipy/_external/pyprima/common/__pycache__/_linear_constraints.cpython-312.pyc,, +scipy/_external/pyprima/common/__pycache__/_nonlinear_constraints.cpython-312.pyc,, +scipy/_external/pyprima/common/__pycache__/_project.cpython-312.pyc,, +scipy/_external/pyprima/common/__pycache__/checkbreak.cpython-312.pyc,, +scipy/_external/pyprima/common/__pycache__/consts.cpython-312.pyc,, +scipy/_external/pyprima/common/__pycache__/evaluate.cpython-312.pyc,, +scipy/_external/pyprima/common/__pycache__/history.cpython-312.pyc,, +scipy/_external/pyprima/common/__pycache__/infos.cpython-312.pyc,, +scipy/_external/pyprima/common/__pycache__/linalg.cpython-312.pyc,, +scipy/_external/pyprima/common/__pycache__/message.cpython-312.pyc,, +scipy/_external/pyprima/common/__pycache__/powalg.cpython-312.pyc,, +scipy/_external/pyprima/common/__pycache__/preproc.cpython-312.pyc,, +scipy/_external/pyprima/common/__pycache__/present.cpython-312.pyc,, +scipy/_external/pyprima/common/__pycache__/ratio.cpython-312.pyc,, +scipy/_external/pyprima/common/__pycache__/redrho.cpython-312.pyc,, +scipy/_external/pyprima/common/__pycache__/selectx.cpython-312.pyc,, +scipy/_external/pyprima/common/_bounds.py,sha256=ZzvGVDJigjFkxb4Qkct89SRta7VPS7C_XFm0qKSy090,1636 +scipy/_external/pyprima/common/_linear_constraints.py,sha256=SZHsoLLWFVtJ1TPPamd0CPXmKmVlnrgT-qCyYZo2CkQ,2162 +scipy/_external/pyprima/common/_nonlinear_constraints.py,sha256=0OGZwFpa8JqtTm4rZKb2LL8oxx1Sld4ANwfH-00Qy4A,2150 +scipy/_external/pyprima/common/_project.py,sha256=oX1l_vGwYod1knQ08t_j2MZ1hBOT_szT621GXfk67nE,7863 +scipy/_external/pyprima/common/checkbreak.py,sha256=kDbwy_CG-0ZUlrfrQ_SKwlxyPjqZHH1-z0HTvXJq7G0,3451 +scipy/_external/pyprima/common/consts.py,sha256=gw-JKckLhBHPXZLLp1qMrVsqNP9PJ-kcgtBvfCajk-g,1310 +scipy/_external/pyprima/common/evaluate.py,sha256=aKVctlTqacdI_n329ipEH-ZTpQNwAX-01UavdsXlUeY,3150 +scipy/_external/pyprima/common/history.py,sha256=c6vWe7XJ5JMzRm7TzaPfYhmHlaZGoUDZzEvLdfZNbAA,1361 +scipy/_external/pyprima/common/infos.py,sha256=fcIk6zFWQrtUhtvOHYcYUzZCDmgEAY39fjRshjOb9M4,704 +scipy/_external/pyprima/common/linalg.py,sha256=QwbXquUpao34qwFoQreJ7ocxacCs5pO_7p9Gn9TbB78,14280 +scipy/_external/pyprima/common/message.py,sha256=D1UwykQhQ16hekF4-wxC1Xw1Qs9gsm_0hXG-yf3VK20,9663 +scipy/_external/pyprima/common/powalg.py,sha256=BWD8urM3sF_T0e47iHZCKvjIZgcDxsDrLHMtDMcQoUE,6785 +scipy/_external/pyprima/common/preproc.py,sha256=Ge0SyRxy3f47uf1anX8WmFjfkOdw82czkMgC8FlDiJs,13820 +scipy/_external/pyprima/common/present.py,sha256=TvFOVG53WvXwjsTqz2tfZzgmV0Uv7dlX0DQ1e2IO92M,146 +scipy/_external/pyprima/common/ratio.py,sha256=ePcZVLwu4NB29FgVx-qYr3Yvgf_9Wx16Lyz9qbjJDyM,1823 +scipy/_external/pyprima/common/redrho.py,sha256=ES_D2yucGa_uMGcvlSL8-z4ZxPv2BKoK8mCMw1DZqvE,1258 +scipy/_external/pyprima/common/selectx.py,sha256=8IYf6BB-OnOOnuxn8_9YsrllaLFLtIKQY5NqTHRc61Q,14120 +scipy/_lib/__init__.py,sha256=CXrH_YBpZ-HImHHrqXIhQt_vevp4P5NXClp7hnFMVLM,353 +scipy/_lib/__pycache__/__init__.cpython-312.pyc,, +scipy/_lib/__pycache__/_array_api.cpython-312.pyc,, +scipy/_lib/__pycache__/_array_api_docs_tables.cpython-312.pyc,, +scipy/_lib/__pycache__/_array_api_no_0d.cpython-312.pyc,, +scipy/_lib/__pycache__/_array_api_override.cpython-312.pyc,, +scipy/_lib/__pycache__/_bunch.cpython-312.pyc,, +scipy/_lib/__pycache__/_ccallback.cpython-312.pyc,, +scipy/_lib/__pycache__/_disjoint_set.cpython-312.pyc,, +scipy/_lib/__pycache__/_docscrape.cpython-312.pyc,, +scipy/_lib/__pycache__/_elementwise_iterative_method.cpython-312.pyc,, +scipy/_lib/__pycache__/_gcutils.cpython-312.pyc,, +scipy/_lib/__pycache__/_public_api.cpython-312.pyc,, +scipy/_lib/__pycache__/_sparse.cpython-312.pyc,, +scipy/_lib/__pycache__/_testutils.cpython-312.pyc,, +scipy/_lib/__pycache__/_util.cpython-312.pyc,, +scipy/_lib/__pycache__/deprecation.cpython-312.pyc,, +scipy/_lib/__pycache__/doccer.cpython-312.pyc,, +scipy/_lib/__pycache__/uarray.cpython-312.pyc,, +scipy/_lib/_array_api.py,sha256=XItZpgGybff0YJKmNLDPPEjgtqWZYgep4r_Ytp6htR0,44522 +scipy/_lib/_array_api_docs_tables.py,sha256=OuKvCfICKNLzJPREV6dzwiZanyQH7ehbQo_xGCYW3Bg,10810 +scipy/_lib/_array_api_no_0d.py,sha256=GbyXfWGcjlPAvh3DEPeOp4mvegrGreEM2Wxt6ugisGk,4425 +scipy/_lib/_array_api_override.py,sha256=IhaWzDdWu3C6Pk_VAAMnYKh_vbtjJPmRiiTdzcu1cSI,5638 +scipy/_lib/_bunch.py,sha256=KV-kCN6lXFOp7HSiiVGSmDSwzKUITtuy8WWnO_rWLRo,8305 +scipy/_lib/_ccallback.py,sha256=N9CO7kJYzk6IWQR5LHf_YA1-Oq48R38UIhJFIlJ2Qyc,7087 +scipy/_lib/_ccallback_c.cpython-312-x86_64-linux-gnu.so,sha256=H8sN7nR-LNbg2wT3_6KcI9hI-9GrXNpJHEbrUIiviZs,95528 +scipy/_lib/_disjoint_set.py,sha256=4XlXsXigW0mOvk8XB7pWWB660gLSwi4dzM1OOMLALMA,6388 +scipy/_lib/_docscrape.py,sha256=KhKI9QdkI7xsa9BIB3Asj29byRTk-pGRyxh-oUuQSF0,23840 +scipy/_lib/_elementwise_iterative_method.py,sha256=3VtHZ-Qr6mSU2VdJXvoZZ3Ptr7_V4EJ5zVWPqsvXJos,15486 +scipy/_lib/_fpumode.cpython-312-x86_64-linux-gnu.so,sha256=Qomq6JIn7vGZigVxXaTs-DX7N834ptoilBCDxCWwqe0,16288 +scipy/_lib/_gcutils.py,sha256=mtVx9Ucxoqh1XkSxnps0vLR61sZ3iUE3FQ8L4SacZ30,2449 +scipy/_lib/_public_api.py,sha256=tf8VV6eO7Bk_d7Wu6KuWIF1LOsJmqYvTy6ReyRPxiUU,1828 +scipy/_lib/_sparse.py,sha256=OCbT2NmiAI9tEMgtnv--d1uMNk3TKwaJBRIl-MO1C_s,946 +scipy/_lib/_test_ccallback.cpython-312-x86_64-linux-gnu.so,sha256=Ht5IlZccUCjYiHiAE22PpttuuIK-Xyyo0oVY6IXkS9Q,23088 +scipy/_lib/_test_deprecation_call.cpython-312-x86_64-linux-gnu.so,sha256=4tQT_-5ue7Yro4ELCCPGc0quFtXHHfG7Hd21vBJaFzs,49176 +scipy/_lib/_test_deprecation_def.cpython-312-x86_64-linux-gnu.so,sha256=CBhZed8iJaf2yjnQS-cGUGVTsT0DMsXISEvwgeFMTvs,28264 +scipy/_lib/_testutils.py,sha256=wGU-SyNsqvTwkL7LkSqfOaNQ7q_dUykhY6gmVXgmJe4,12472 +scipy/_lib/_uarray/LICENSE,sha256=yAw5tfzga6SJfhTgsKiLVEWDNNlR6xNhQC_60s-4Y7Q,1514 +scipy/_lib/_uarray/__init__.py,sha256=Rww7wLA7FH6Yong7oMgl_sHPpjcRslRaTjh61W_xVg4,4493 +scipy/_lib/_uarray/__pycache__/__init__.cpython-312.pyc,, +scipy/_lib/_uarray/__pycache__/_backend.cpython-312.pyc,, +scipy/_lib/_uarray/_backend.py,sha256=LZnSLJ2UK209jrMtocOMoc5grlNoob3tbb1HbW0XlAQ,20531 +scipy/_lib/_uarray/_uarray.cpython-312-x86_64-linux-gnu.so,sha256=yoNmT5TAnbOwVCYOx2IRKxb8ZUPSWbNHfyb6b2Y5x8A,115080 +scipy/_lib/_util.py,sha256=D-V1ray-O8dC0fL8TnR7nE2QYH2mCc0q9APKxtQB3sk,47032 +scipy/_lib/deprecation.py,sha256=2KeqTdCUmSAcv3QLoTzCK5vkBWcmJl5Wawz1FVxuJ3Q,9854 +scipy/_lib/doccer.py,sha256=W1HgV3SpTFpZJ0oyiN9Yn4vuX5pwxx27anH-sGNv19Y,10721 +scipy/_lib/messagestream.cpython-312-x86_64-linux-gnu.so,sha256=ke_lwAoT5_qaf_Y1docJBqASsTFQYGg2PgmcurjPUbg,83264 +scipy/_lib/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +scipy/_lib/tests/__pycache__/__init__.cpython-312.pyc,, +scipy/_lib/tests/__pycache__/test__gcutils.cpython-312.pyc,, +scipy/_lib/tests/__pycache__/test__testutils.cpython-312.pyc,, +scipy/_lib/tests/__pycache__/test__util.cpython-312.pyc,, +scipy/_lib/tests/__pycache__/test_array_api.cpython-312.pyc,, +scipy/_lib/tests/__pycache__/test_bunch.cpython-312.pyc,, +scipy/_lib/tests/__pycache__/test_ccallback.cpython-312.pyc,, +scipy/_lib/tests/__pycache__/test_config.cpython-312.pyc,, +scipy/_lib/tests/__pycache__/test_deprecation.cpython-312.pyc,, +scipy/_lib/tests/__pycache__/test_doccer.cpython-312.pyc,, +scipy/_lib/tests/__pycache__/test_import_cycles.cpython-312.pyc,, +scipy/_lib/tests/__pycache__/test_public_api.cpython-312.pyc,, +scipy/_lib/tests/__pycache__/test_scipy_version.cpython-312.pyc,, +scipy/_lib/tests/__pycache__/test_warnings.cpython-312.pyc,, +scipy/_lib/tests/__pycache__/test_xp_capabilities.cpython-312.pyc,, +scipy/_lib/tests/test__gcutils.py,sha256=GtbO2GFOP-g8-14QJQSOPNbsmQ-0aAtKGIEWYkjqO5Y,3452 +scipy/_lib/tests/test__testutils.py,sha256=P4WDJpUgy19wD9tknQSjIivuQvZF7YUBGSBWlur2QRA,800 +scipy/_lib/tests/test__util.py,sha256=fTCW5PKvgeyl-4C5R6UgPAdrbfoi9uKUrytyLhN_-xI,22560 +scipy/_lib/tests/test_array_api.py,sha256=m6J-dE81j03fGGei0Wd3a16lpUXWhCtryL2ZeKpJT-M,15715 +scipy/_lib/tests/test_bunch.py,sha256=USL2wl6PT4gAuuNk5CipnNrG45GZPZ8y1B7BqtIDqKM,6389 +scipy/_lib/tests/test_ccallback.py,sha256=wQ0Bwewx7KWJWspmdTYTPjRgm8A3A5mZ1ssLIig5wfE,6040 +scipy/_lib/tests/test_config.py,sha256=j1Ppp6DCZy9wMxTmBEGxq4MScvsQXTQk7268EnNnPFQ,1244 +scipy/_lib/tests/test_deprecation.py,sha256=-7bcTKJauUjBjMpeiFtt5iGwIWlAVu-wZq81iqCePMQ,440 +scipy/_lib/tests/test_doccer.py,sha256=vhmDDKE2ZtfSYGBlF3hd5qL3YU9xqc4FgfgLoEL5qbI,4150 +scipy/_lib/tests/test_import_cycles.py,sha256=B9BwSx-9po3YQBeEjn7f1l4ytddO7W6ykC0Oo5_vFv8,843 +scipy/_lib/tests/test_public_api.py,sha256=7Oyg9RcC1Bz-NEzZ30zdcOCVznK1Wio6qUFV2yyixQ0,17726 +scipy/_lib/tests/test_scipy_version.py,sha256=kVoxuBUidCHsVpvybRPoVJzkv2hUixRwuDAEAqPgpaA,918 +scipy/_lib/tests/test_warnings.py,sha256=LwajZLcv-gC3ACArss6hrsO2aFOJPJ8OieFYBYlqXcY,5005 +scipy/_lib/tests/test_xp_capabilities.py,sha256=GcasaSraT8pIyS_tvpScaW2tNL-i0ZPlfJb7OW_kgtc,1977 +scipy/_lib/uarray.py,sha256=YtAfXUnoIaMP0BBp8NqVMtEkde9daw8Z044y0dSKY5U,838 +scipy/cluster/__init__.py,sha256=pgzWiWR5smQ3rwud2dhnLn6dpkD5lju_moElQp_zhoE,880 +scipy/cluster/__pycache__/__init__.cpython-312.pyc,, +scipy/cluster/hierarchy/__init__.py,sha256=P0lu9YH3LJOXFvoRpZWXullb2mstTuSUArAoEXGg0PQ,2762 +scipy/cluster/hierarchy/__pycache__/__init__.cpython-312.pyc,, +scipy/cluster/hierarchy/__pycache__/_hierarchy_impl.cpython-312.pyc,, +scipy/cluster/hierarchy/_hierarchy.cpython-312-x86_64-linux-gnu.so,sha256=Dobd5wXDPPlSmcIsOi4f18-Yqm5LgTL2aD-jULMJxy4,249824 +scipy/cluster/hierarchy/_hierarchy_impl.py,sha256=aYb_CK3-IQgOl6xA74wL5CG01YS6n5HecdJxtvXFyHM,154767 +scipy/cluster/hierarchy/_optimal_leaf_ordering.cpython-312-x86_64-linux-gnu.so,sha256=gzzuw-sO3vemPLzPuvufMjB9wxmwbBWlK5DOc0ATo7o,174600 +scipy/cluster/hierarchy/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +scipy/cluster/hierarchy/tests/__pycache__/__init__.cpython-312.pyc,, +scipy/cluster/hierarchy/tests/__pycache__/hierarchy_test_data.cpython-312.pyc,, +scipy/cluster/hierarchy/tests/__pycache__/test_disjoint_set.cpython-312.pyc,, +scipy/cluster/hierarchy/tests/__pycache__/test_hierarchy.cpython-312.pyc,, +scipy/cluster/hierarchy/tests/hierarchy_test_data.py,sha256=7syUYdIaDVr7hgvMliX0CW4386utjBJn1DOgX0USXls,6850 +scipy/cluster/hierarchy/tests/test_disjoint_set.py,sha256=EuHGBE3ZVEMnWFbCn8tjI-_6CWrNXfpnv5bUBa9qhWI,5525 +scipy/cluster/hierarchy/tests/test_hierarchy.py,sha256=REjF8XHXHtEzA39COLxLNv6aGffgAH_D_yS5cn5ohaU,51023 +scipy/cluster/vq/__init__.py,sha256=BPKhJF8VSN2ggrSJJCOCHwlcn7wyJhlCGuHp5Zmt_rA,3551 +scipy/cluster/vq/__pycache__/__init__.cpython-312.pyc,, +scipy/cluster/vq/__pycache__/_vq_impl.cpython-312.pyc,, +scipy/cluster/vq/_vq.cpython-312-x86_64-linux-gnu.so,sha256=yW6L1rXqzN8TfuxvlPsK9fNrlxeGh84yVNCYUn3c74c,122552 +scipy/cluster/vq/_vq_impl.py,sha256=4I_tX7pzuNpUeX-me1KRZAoR2oDSoTtCXfN1siV3HkQ,28135 +scipy/cluster/vq/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +scipy/cluster/vq/tests/__pycache__/__init__.cpython-312.pyc,, +scipy/cluster/vq/tests/__pycache__/test_vq.cpython-312.pyc,, +scipy/cluster/vq/tests/test_vq.py,sha256=gz9fk4ZASzH-VxWdpLfewps0iUbtRFs-wtCrt6y8nuI,19075 +scipy/conftest.py,sha256=FuKkwjWDf4PeUBRN7qcsE_Uvg-MfpejvatXuYKc52Jc,30808 +scipy/constants/__init__.py,sha256=k5V3SUgkeRJ8v9PwmWKW_DUV3eYSFmz_Gb2LfUF33vk,14804 +scipy/constants/__pycache__/__init__.cpython-312.pyc,, +scipy/constants/__pycache__/_codata.cpython-312.pyc,, +scipy/constants/__pycache__/_constants.cpython-312.pyc,, +scipy/constants/__pycache__/codata.cpython-312.pyc,, +scipy/constants/__pycache__/constants.cpython-312.pyc,, +scipy/constants/_codata.py,sha256=mY7tch8MhLIzhcSk5UNqeaC2pKPK8hLii4-ppEC8XFg,202505 +scipy/constants/_constants.py,sha256=t-Qp4nHOaTZ2cB9x8OhLcF0qEmcZe-GWheXKcDOdWa4,10572 +scipy/constants/codata.py,sha256=ThmW8ohzndi-4-WtyVXxSrW40MnLIz1XoqRcm2RgSHw,614 +scipy/constants/constants.py,sha256=w7sGxSidD2Q9Ged0Sn1pnL-qqD1ssEP1A8sZWeLWBeI,2250 +scipy/constants/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +scipy/constants/tests/__pycache__/__init__.cpython-312.pyc,, +scipy/constants/tests/__pycache__/test_codata.cpython-312.pyc,, +scipy/constants/tests/__pycache__/test_constants.cpython-312.pyc,, +scipy/constants/tests/test_codata.py,sha256=gxakBdJjXuXFPPgJvJAj50s4IXCJH3whaeXZjEcso-g,2854 +scipy/constants/tests/test_constants.py,sha256=Gz5YfzcqFnZsHuuZDZ4QOcegWL-2IGIZwZ9cNp-ubTM,4247 +scipy/datasets/__init__.py,sha256=X_9AbefPK1_pg-eG7g3nn--JhoHeDsrEFbJfbI5Hyak,2802 +scipy/datasets/__pycache__/__init__.cpython-312.pyc,, +scipy/datasets/__pycache__/_download_all.cpython-312.pyc,, +scipy/datasets/__pycache__/_fetchers.cpython-312.pyc,, +scipy/datasets/__pycache__/_registry.cpython-312.pyc,, +scipy/datasets/__pycache__/_utils.cpython-312.pyc,, +scipy/datasets/_download_all.py,sha256=-cpq9vA6T8-ckm72flcHWnfejmz5ba_4xbiCZ5z9VRA,2182 +scipy/datasets/_fetchers.py,sha256=u6OXMlrSjGl5NvXcFQbOVPBuDaV0YYIJOfL1YXKxWAA,7106 +scipy/datasets/_registry.py,sha256=br0KfyalEbh5yrQLznQ_QvBtmN4rMsm0UxOjnsJp4OQ,1072 +scipy/datasets/_utils.py,sha256=YJ-5gZKiEwVFAPsSnjAswYDlRm9foOSawBp6C2a3WC0,2931 +scipy/datasets/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +scipy/datasets/tests/__pycache__/__init__.cpython-312.pyc,, +scipy/datasets/tests/__pycache__/test_data.cpython-312.pyc,, +scipy/datasets/tests/test_data.py,sha256=X3GNKi-neOrr1n-pqLE49Fria5DSEol9DQYpU0EG-kY,4017 +scipy/differentiate/__init__.py,sha256=nZ3imDWtf1QzImE-xsrYHE4kuOa8tEuc99Hl0zAFqzI,621 +scipy/differentiate/__pycache__/__init__.cpython-312.pyc,, +scipy/differentiate/__pycache__/_differentiate.cpython-312.pyc,, +scipy/differentiate/_differentiate.py,sha256=DqePTojwAKMwta9hWkASObI_tjx-AnxBSmZbfN6ljJc,51639 +scipy/differentiate/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +scipy/differentiate/tests/__pycache__/__init__.cpython-312.pyc,, +scipy/differentiate/tests/__pycache__/test_differentiate.cpython-312.pyc,, +scipy/differentiate/tests/test_differentiate.py,sha256=3HWjNrXLe4pAAyT_CClaGMkhqHWp8NruVuhf_IJI16o,28266 +scipy/fft/__init__.py,sha256=F56ceQzTCZ5MEQFG6okky2E888xyiBEVcaqmGsVuCmo,3630 +scipy/fft/__pycache__/__init__.cpython-312.pyc,, +scipy/fft/__pycache__/_backend.cpython-312.pyc,, +scipy/fft/__pycache__/_basic.cpython-312.pyc,, +scipy/fft/__pycache__/_basic_backend.cpython-312.pyc,, +scipy/fft/__pycache__/_debug_backends.cpython-312.pyc,, +scipy/fft/__pycache__/_fftlog.cpython-312.pyc,, +scipy/fft/__pycache__/_fftlog_backend.cpython-312.pyc,, +scipy/fft/__pycache__/_helper.cpython-312.pyc,, +scipy/fft/__pycache__/_realtransforms.cpython-312.pyc,, +scipy/fft/__pycache__/_realtransforms_backend.cpython-312.pyc,, +scipy/fft/_backend.py,sha256=zXXUjtS1dDy8MGwyJAwG4LdvDfSwa-x6UtAYFMD6bHQ,6964 +scipy/fft/_basic.py,sha256=nDQTXTtMOFM2twntg4kACDbTJMomu4OIDdc4Vthhvx4,64356 +scipy/fft/_basic_backend.py,sha256=ed1xdU_hlSDMSWPJ53S7a8k5eV5FFiS-wQRZfjNcG4w,7401 +scipy/fft/_debug_backends.py,sha256=RlvyunZNqaDDsI3-I6QH6GSBz_faT6EN4OONWsvMtR8,598 +scipy/fft/_duccfft/LICENSE.md,sha256=wlSytf0wrjyJ02ugYXMFY7l2D8oE8bdGobLDFX2ix4k,1498 +scipy/fft/_duccfft/__init__.py,sha256=Km8tmuO6Ibd_09Y8RZMfb8agEoWCY6-cT5FKRS5v8ws,205 +scipy/fft/_duccfft/__pycache__/__init__.cpython-312.pyc,, +scipy/fft/_duccfft/__pycache__/basic.cpython-312.pyc,, +scipy/fft/_duccfft/__pycache__/helper.cpython-312.pyc,, +scipy/fft/_duccfft/__pycache__/realtransforms.cpython-312.pyc,, +scipy/fft/_duccfft/basic.py,sha256=zPx7LrwuR80CMpECMMQL2NLswOgCdxg6SvSZWIK2tac,8654 +scipy/fft/_duccfft/helper.py,sha256=DZxAmm3PnNuFy8Z6ogP3uAkHA2sjc18f3Ot68SaO6cU,7064 +scipy/fft/_duccfft/pyduccfft.cpython-312-x86_64-linux-gnu.so,sha256=N0ERlPgVGjU1kEmA7vy2CjoqOwokDRy9o_c-eob_WQQ,2389288 +scipy/fft/_duccfft/realtransforms.py,sha256=XuQtqfdCUJofmItN_j28oSeCR5FS2t_Xd6Tq5kl6W0Q,3630 +scipy/fft/_duccfft/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +scipy/fft/_duccfft/tests/__pycache__/__init__.cpython-312.pyc,, +scipy/fft/_duccfft/tests/__pycache__/test_basic.cpython-312.pyc,, +scipy/fft/_duccfft/tests/__pycache__/test_real_transforms.cpython-312.pyc,, +scipy/fft/_duccfft/tests/test_basic.py,sha256=_AWSO17VL_4OEbJHHhaVWBZ3r75cguLIwtyhxBxlkdk,35627 +scipy/fft/_duccfft/tests/test_real_transforms.py,sha256=Xnpu6jbY1_odXxqQvqH52BmvCcrsugpqlIkvuVX_O6c,16862 +scipy/fft/_fftlog.py,sha256=Cth8ohZNXtZ8QurKG-OZbI5VxeJThcrPPOjKNczEIsM,7999 +scipy/fft/_fftlog_backend.py,sha256=n1QNDL1P8R4Q_jeuFezvnjYNFojRvxCWeH6vlE5b1Ao,5357 +scipy/fft/_helper.py,sha256=tPZxbklCGvw55kzOVZyu_dZVlB4A8Y_7_beAtfZcb2E,11512 +scipy/fft/_realtransforms.py,sha256=RMVL81t8-SO_hdoqpnnoy_7rOXD3vEAv51YClCHZcxU,28473 +scipy/fft/_realtransforms_backend.py,sha256=613i32LDcTEl9YusGh5df4m-EVf8yVKL2nH3yiMRa14,2367 +scipy/fft/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +scipy/fft/tests/__pycache__/__init__.cpython-312.pyc,, +scipy/fft/tests/__pycache__/mock_backend.cpython-312.pyc,, +scipy/fft/tests/__pycache__/test_backend.cpython-312.pyc,, +scipy/fft/tests/__pycache__/test_basic.cpython-312.pyc,, +scipy/fft/tests/__pycache__/test_fftlog.cpython-312.pyc,, +scipy/fft/tests/__pycache__/test_helper.cpython-312.pyc,, +scipy/fft/tests/__pycache__/test_multithreading.cpython-312.pyc,, +scipy/fft/tests/__pycache__/test_real_transforms.cpython-312.pyc,, +scipy/fft/tests/mock_backend.py,sha256=p17Hfg6xuoF6Ldxwe1PZ-79Lf_r9FyJUR00N4TokM8k,2685 +scipy/fft/tests/test_backend.py,sha256=zQLZnYpS0ssw1FH51xmRO9fgT9BYjA9kJhi2f_1ge-s,4271 +scipy/fft/tests/test_basic.py,sha256=CySy8gjZGi3FmY1wDAqaJN_TSsX8wXcUww0M2XU03-I,23157 +scipy/fft/tests/test_fftlog.py,sha256=ey5W9R47bZ1MHYuTqpAP_w8YfDVRz0uS4Ha6V-dFAVg,7878 +scipy/fft/tests/test_helper.py,sha256=MG9JC8iI76E8dRj3FjJMM6xqLYINaFDbtv_iWCd0qM0,19486 +scipy/fft/tests/test_multithreading.py,sha256=JMSXQocScFghpsy47zov03R5MbEY0Z3ROGt6GxFeWzo,2150 +scipy/fft/tests/test_real_transforms.py,sha256=yC30xpQfApUFWVLqksATJyZrW8p7d5op7UZOAVNycYI,9289 +scipy/fftpack/__init__.py,sha256=rLCBFC5Dx5ij_wmL7ChiGmScYlgu0mhaWtrJaz_rBt0,3155 +scipy/fftpack/__pycache__/__init__.cpython-312.pyc,, +scipy/fftpack/__pycache__/_basic.cpython-312.pyc,, +scipy/fftpack/__pycache__/_helper.cpython-312.pyc,, +scipy/fftpack/__pycache__/_pseudo_diffs.cpython-312.pyc,, +scipy/fftpack/__pycache__/_realtransforms.cpython-312.pyc,, +scipy/fftpack/__pycache__/basic.cpython-312.pyc,, +scipy/fftpack/__pycache__/helper.cpython-312.pyc,, +scipy/fftpack/__pycache__/pseudo_diffs.cpython-312.pyc,, +scipy/fftpack/__pycache__/realtransforms.cpython-312.pyc,, +scipy/fftpack/_basic.py,sha256=jJznvKDEleo-m3L5bH6wUS1XgWA-TF-pq1uB5JVOKA4,13156 +scipy/fftpack/_helper.py,sha256=CMES1ynwn5T6znjlN0p-GgmNpARzJOb3V1Al-eqGodQ,3346 +scipy/fftpack/_pseudo_diffs.py,sha256=Zld67_OjLtg-mPus7t8j1N6dWED8NNM3lyXCQ0k6N8E,16081 +scipy/fftpack/_realtransforms.py,sha256=ox3wR4G9L49MB-6Bq7fTU_EOOoXLO8o-ZCKCVIhJyBA,19204 +scipy/fftpack/basic.py,sha256=i2CMMS__L3UtFFqe57E0cs7AZ4U6VO-Ted1KhU7_wNc,577 +scipy/fftpack/convolve.cpython-312-x86_64-linux-gnu.so,sha256=WCp3v8HnwrgMrnDXUzTSF4A8WDzcL5kd2v0qqSIgTe0,121080 +scipy/fftpack/helper.py,sha256=M7jTN4gQIRWpkArQR13bI7WN6WcW-AabxKgrOHRvfeQ,580 +scipy/fftpack/pseudo_diffs.py,sha256=h0vkjsSqAThy7OdTkYWVxQqZ3rILohg7MXJqf5CGMTE,658 +scipy/fftpack/realtransforms.py,sha256=9-mR-VV3W14oTaD6pB5-RIDV3vkTBQmGCcxfbA8GYH0,595 +scipy/fftpack/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +scipy/fftpack/tests/__pycache__/__init__.cpython-312.pyc,, +scipy/fftpack/tests/__pycache__/test_basic.cpython-312.pyc,, +scipy/fftpack/tests/__pycache__/test_helper.cpython-312.pyc,, +scipy/fftpack/tests/__pycache__/test_import.cpython-312.pyc,, +scipy/fftpack/tests/__pycache__/test_pseudo_diffs.cpython-312.pyc,, +scipy/fftpack/tests/__pycache__/test_real_transforms.cpython-312.pyc,, +scipy/fftpack/tests/fftw_double_ref.npz,sha256=pgxklBW2RSI5JNg0LMxcCXgByGkBKHo2nlP8kln17E4,162120 +scipy/fftpack/tests/fftw_longdouble_ref.npz,sha256=pAbL1NrQTQxZ3Tj1RBb7SUJMgiKcGgdLakTsDN4gAOM,296072 +scipy/fftpack/tests/fftw_single_ref.npz,sha256=J2qRQTGOb8NuSrb_VKYbZAVO-ISbZg8XNZ5fVBtDxSY,95144 +scipy/fftpack/tests/test.npz,sha256=Nt6ASiLY_eoFRZDOSd3zyFmDi32JGTxWs7y2YMv0N5c,11968 +scipy/fftpack/tests/test_basic.py,sha256=xq92TCdPbZbh6DStQ2dTc958GYSe453goeijDJoDGrQ,30472 +scipy/fftpack/tests/test_helper.py,sha256=8JaPSJOwsk5XXOf1zFahJ_ktUTfNGSk2-k3R6e420XI,1675 +scipy/fftpack/tests/test_import.py,sha256=dzyXQHtsdW2WL5ruVp_-MsqSQd_n-tuyq22okrzXlGw,1156 +scipy/fftpack/tests/test_pseudo_diffs.py,sha256=ZJU6AkkH6jKjebu_-Ant-dT6tUGwo1Jx9c5kou1floU,13733 +scipy/fftpack/tests/test_real_transforms.py,sha256=KxSsIxsMzdeif5Jej3MuERF3lC8Cyv_UZnJhLPuKpIU,24460 +scipy/integrate/LICENSE_DOP,sha256=7Zv1jG100_rZ2S0dZ9m_-BQdirYN54RRawcRNk_UM1c,3572 +scipy/integrate/__init__.py,sha256=CmPLfkF66jXhHsKyQPOsvFEc9nxicRYwl6WDAa7cfJk,4373 +scipy/integrate/__pycache__/__init__.cpython-312.pyc,, +scipy/integrate/__pycache__/_bvp.cpython-312.pyc,, +scipy/integrate/__pycache__/_cubature.cpython-312.pyc,, +scipy/integrate/__pycache__/_lebedev.cpython-312.pyc,, +scipy/integrate/__pycache__/_ode.cpython-312.pyc,, +scipy/integrate/__pycache__/_odepack_py.cpython-312.pyc,, +scipy/integrate/__pycache__/_quad_vec.cpython-312.pyc,, +scipy/integrate/__pycache__/_quadpack_py.cpython-312.pyc,, +scipy/integrate/__pycache__/_quadrature.cpython-312.pyc,, +scipy/integrate/__pycache__/_tanhsinh.cpython-312.pyc,, +scipy/integrate/__pycache__/dop.cpython-312.pyc,, +scipy/integrate/__pycache__/lsoda.cpython-312.pyc,, +scipy/integrate/__pycache__/odepack.cpython-312.pyc,, +scipy/integrate/__pycache__/quadpack.cpython-312.pyc,, +scipy/integrate/__pycache__/vode.cpython-312.pyc,, +scipy/integrate/_bvp.py,sha256=MI3-ehdbcndSDHodq-3eZ7gRPLAAFH1SsSxXkHUyfQo,41557 +scipy/integrate/_cubature.py,sha256=2O4HHG3pXAkQFacPDvndpIwXbjUZMTfXqaqh2eB-PAY,25766 +scipy/integrate/_dop.cpython-312-x86_64-linux-gnu.so,sha256=0E06QImbpjbZUSbysfXGPN_olPx3UMN7CRvrCOCQFcM,59384 +scipy/integrate/_ivp/__init__.py,sha256=gKFR_pPjr8fRLgAGY5sOzYKGUFu2nGX8x1RrXT-GZZc,256 +scipy/integrate/_ivp/__pycache__/__init__.cpython-312.pyc,, +scipy/integrate/_ivp/__pycache__/base.cpython-312.pyc,, +scipy/integrate/_ivp/__pycache__/bdf.cpython-312.pyc,, +scipy/integrate/_ivp/__pycache__/common.cpython-312.pyc,, +scipy/integrate/_ivp/__pycache__/dop853_coefficients.cpython-312.pyc,, +scipy/integrate/_ivp/__pycache__/ivp.cpython-312.pyc,, +scipy/integrate/_ivp/__pycache__/lsoda.cpython-312.pyc,, +scipy/integrate/_ivp/__pycache__/radau.cpython-312.pyc,, +scipy/integrate/_ivp/__pycache__/rk.cpython-312.pyc,, +scipy/integrate/_ivp/base.py,sha256=HCceE95JTt9pJrV1fl-NrPdk4mocPMDwHLnQpZ6aZUk,10539 +scipy/integrate/_ivp/bdf.py,sha256=kgFMbduF4HV64rlR39eDpLW5Kpgppc3z8mtByDkP5Lc,17908 +scipy/integrate/_ivp/common.py,sha256=k4o8QaQp5pPKvh6jHgoAeF-jng-IDiQRY4Zft34b-4g,15914 +scipy/integrate/_ivp/dop853_coefficients.py,sha256=OrYvW0Hu6X7sOh37FU58gNkgC77KVpYclewv_ARGMAE,7237 +scipy/integrate/_ivp/ivp.py,sha256=uVLQJN5L_hrv1HAuk0t3Oidnl3LOPCe2CykXqviznx4,32015 +scipy/integrate/_ivp/lsoda.py,sha256=rLxCQ76jJS-3mWPjUM59-wwfi825Hfp0Fet-Wz_gibw,10234 +scipy/integrate/_ivp/radau.py,sha256=BeREQ1hKhEgbsFG377Y4vv2WT1a9PISZY_zTPDVxBRc,20143 +scipy/integrate/_ivp/rk.py,sha256=Kmue7BkU6LHdV9IhTw9vF5ADGdrJrpSkdXeHkhHwJQs,28525 +scipy/integrate/_ivp/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +scipy/integrate/_ivp/tests/__pycache__/__init__.cpython-312.pyc,, +scipy/integrate/_ivp/tests/__pycache__/test_ivp.cpython-312.pyc,, +scipy/integrate/_ivp/tests/__pycache__/test_rk.cpython-312.pyc,, +scipy/integrate/_ivp/tests/test_ivp.py,sha256=sCzYQsOEy1xPO5QSdaoB4I2NGybiCkgWYE7DLJ4aIT0,43465 +scipy/integrate/_ivp/tests/test_rk.py,sha256=K9UxZghBzSL2BzmgLndPJcWOWV4Nr530TGKWakpsoeM,1326 +scipy/integrate/_lebedev.py,sha256=nf4kdsPOziZyaUu_navuh6MijsyvEjy-FfjRhBmNxNw,262106 +scipy/integrate/_ode.py,sha256=kR9u46rT09DSoeC_tHa5Mj_rFmq0ItgoyCsw1KpGBss,50828 +scipy/integrate/_odepack.cpython-312-x86_64-linux-gnu.so,sha256=ByyakyPWLe1TVtahpPi2mCxUPFb43UCYdkvXPRQGtKU,79337 +scipy/integrate/_odepack_py.py,sha256=9KMWur0vrdVrBsmPQRmkiC1U6HsUvEdZakGvxUjQoOE,11236 +scipy/integrate/_quad_vec.py,sha256=wmlClCC33kLYPEUzsBwPjjLSoPxgmddjcAYKG6EY748,21614 +scipy/integrate/_quadpack.cpython-312-x86_64-linux-gnu.so,sha256=l3fY-sMhM7-TabZAhARsKpMZcwPhAY2TV-9MV7GMjG8,119920 +scipy/integrate/_quadpack_py.py,sha256=xZ5BM_I-8nLkiCBMIXyvNqOLWSH-D_7rKo8bgE9boNk,54620 +scipy/integrate/_quadrature.py,sha256=NQgo_U6XLg0pGuJSzp_LbHfdcNrmmJ6agCR75OAwkaA,49527 +scipy/integrate/_rules/__init__.py,sha256=JNlDLTPYR-FVDeWbm9BHOot47OA8tvOj22g2iJlEsBg,328 +scipy/integrate/_rules/__pycache__/__init__.cpython-312.pyc,, +scipy/integrate/_rules/__pycache__/_base.cpython-312.pyc,, +scipy/integrate/_rules/__pycache__/_gauss_kronrod.cpython-312.pyc,, +scipy/integrate/_rules/__pycache__/_gauss_legendre.cpython-312.pyc,, +scipy/integrate/_rules/__pycache__/_genz_malik.cpython-312.pyc,, +scipy/integrate/_rules/_base.py,sha256=iwb872yqwq2Y9LbCTujUKI1CvwZurApCeJAlOR4wrnE,17927 +scipy/integrate/_rules/_gauss_kronrod.py,sha256=ULpHMJRd0J99IFwNufur9BYG8EQhxlGj-OdCBgnE8yk,8473 +scipy/integrate/_rules/_gauss_legendre.py,sha256=KJSMmztXRqTvpmkB-ky-WSVIqAMg_GcWoewTcRxJ1Cw,1733 +scipy/integrate/_rules/_genz_malik.py,sha256=gcqWVwu0QjxRK5iiLwquHW_IxT_DMd7Wg4chhhyUNvs,7299 +scipy/integrate/_tanhsinh.py,sha256=rOY3IQtFRcGQxcjL4oWnnRON5681u0rjkc9hjZb6WKQ,61957 +scipy/integrate/_test_multivariate.cpython-312-x86_64-linux-gnu.so,sha256=2EIZQfAtLn3RiNiH1yQL4bd-GCJoL2JWh7-cOTHRO6c,16816 +scipy/integrate/_vode.cpython-312-x86_64-linux-gnu.so,sha256=3AeuobNIByRVJ91AE48_dZ0z8WInQttIzRR2BXo_geE,120369 +scipy/integrate/dop.py,sha256=Kx5Ed_Te81X09bvGmBUq3-_kQNdTIsOdO7ykjEpEG9c,422 +scipy/integrate/lsoda.py,sha256=sDSCG4ZCdIZqraaFTAuwY21-dKGR5JK1kWBpZoaQ0Hk,438 +scipy/integrate/odepack.py,sha256=G5KiKninKFyYgF756_LtDGB68BGk7IwPidUOywFpLQo,545 +scipy/integrate/quadpack.py,sha256=vQNE5jQ-dFpH26er1i8LJSkylFVbeSgVGLwSRQawfYg,604 +scipy/integrate/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +scipy/integrate/tests/__pycache__/__init__.cpython-312.pyc,, +scipy/integrate/tests/__pycache__/test__quad_vec.cpython-312.pyc,, +scipy/integrate/tests/__pycache__/test_banded_ode_solvers.cpython-312.pyc,, +scipy/integrate/tests/__pycache__/test_bvp.cpython-312.pyc,, +scipy/integrate/tests/__pycache__/test_cubature.cpython-312.pyc,, +scipy/integrate/tests/__pycache__/test_integrate.cpython-312.pyc,, +scipy/integrate/tests/__pycache__/test_quadpack.cpython-312.pyc,, +scipy/integrate/tests/__pycache__/test_quadrature.cpython-312.pyc,, +scipy/integrate/tests/__pycache__/test_tanhsinh.cpython-312.pyc,, +scipy/integrate/tests/test__quad_vec.py,sha256=j0vhUf6x2lPWWVn8h9hG02W9ZOzU_C9w8ZpJ9-ES5aI,7157 +scipy/integrate/tests/test_banded_ode_solvers.py,sha256=77FjY0AikdRhA2FWNsO29Vpgee0CqCQ_57EpkNeBwm8,9244 +scipy/integrate/tests/test_bvp.py,sha256=uVe_zHS7CXDfYlqWsgAzdh6Z1TpOJXQW5RiipgwY8Oc,20219 +scipy/integrate/tests/test_cubature.py,sha256=bx0FDV4HK1g9cz428cjk6h5uhP85WKFjKj3mMRXb1yw,37298 +scipy/integrate/tests/test_integrate.py,sha256=PgdJI4-s2tPJrtVkR6wUP9EVEWt8SgdVajP1AtzfCrA,25634 +scipy/integrate/tests/test_quadpack.py,sha256=69NSMQWbVeIdFNgluwHzWODgqMsqJ94nrgv2OfRD7OI,29837 +scipy/integrate/tests/test_quadrature.py,sha256=CShPcGYHppNDDyc6luT6rIMv0aCCt4n2v9pgu1hSsvw,31846 +scipy/integrate/tests/test_tanhsinh.py,sha256=l1yddN3cqAMUQAU7WM1jaUCvaYwM9BxOczL_o-xf0mE,45608 +scipy/integrate/vode.py,sha256=DPRqm2oBQx6KKi5tl9dDVpXEdAO--W0WpRQEyLeQpf4,424 +scipy/interpolate/__init__.py,sha256=i0mcQWXv0KbAn44vld8w-9NrRy-z_w0xf5_kQnBTtK8,4073 +scipy/interpolate/__pycache__/__init__.cpython-312.pyc,, +scipy/interpolate/__pycache__/_bary_rational.cpython-312.pyc,, +scipy/interpolate/__pycache__/_bsplines.cpython-312.pyc,, +scipy/interpolate/__pycache__/_cubic.cpython-312.pyc,, +scipy/interpolate/__pycache__/_fitpack2.cpython-312.pyc,, +scipy/interpolate/__pycache__/_fitpack_impl.cpython-312.pyc,, +scipy/interpolate/__pycache__/_fitpack_py.cpython-312.pyc,, +scipy/interpolate/__pycache__/_fitpack_repro.cpython-312.pyc,, +scipy/interpolate/__pycache__/_interpolate.cpython-312.pyc,, +scipy/interpolate/__pycache__/_ndbspline.cpython-312.pyc,, +scipy/interpolate/__pycache__/_ndgriddata.cpython-312.pyc,, +scipy/interpolate/__pycache__/_pade.cpython-312.pyc,, +scipy/interpolate/__pycache__/_polyint.cpython-312.pyc,, +scipy/interpolate/__pycache__/_rbf.cpython-312.pyc,, +scipy/interpolate/__pycache__/_rbfinterp.cpython-312.pyc,, +scipy/interpolate/__pycache__/_rbfinterp_common.cpython-312.pyc,, +scipy/interpolate/__pycache__/_rbfinterp_np.cpython-312.pyc,, +scipy/interpolate/__pycache__/_rbfinterp_xp.cpython-312.pyc,, +scipy/interpolate/__pycache__/_regrid.cpython-312.pyc,, +scipy/interpolate/__pycache__/_rgi.cpython-312.pyc,, +scipy/interpolate/__pycache__/dfitpack.cpython-312.pyc,, +scipy/interpolate/__pycache__/fitpack.cpython-312.pyc,, +scipy/interpolate/__pycache__/fitpack2.cpython-312.pyc,, +scipy/interpolate/__pycache__/interpnd.cpython-312.pyc,, +scipy/interpolate/__pycache__/interpolate.cpython-312.pyc,, +scipy/interpolate/__pycache__/ndgriddata.cpython-312.pyc,, +scipy/interpolate/__pycache__/polyint.cpython-312.pyc,, +scipy/interpolate/__pycache__/rbf.cpython-312.pyc,, +scipy/interpolate/_bary_rational.py,sha256=fUz_6Fg2vuwALiiJQxNwc5Ygt45HZBt1HfqjI5w0yKw,29582 +scipy/interpolate/_bsplines.py,sha256=sJkzPR3FfQ1K1TAMBM0nZzUASMrCud7kZOQKtGlRDKo,98442 +scipy/interpolate/_cubic.py,sha256=ZNFwV4YjIdjmcN84zWFLq84jpaZBy_O4hScdsEnNwcM,41164 +scipy/interpolate/_dierckx.cpython-312-x86_64-linux-gnu.so,sha256=8SKIQnyVZS-hH0TpGIO6BNHAfDh5TrIbgePlqE8cBpo,129337 +scipy/interpolate/_fitpack.cpython-312-x86_64-linux-gnu.so,sha256=SGOycKLotzS_MiWzEQL8aFOD9XokK48lPxu6rUiHoss,342696 +scipy/interpolate/_fitpack2.py,sha256=WbwOfBfD4lL0BJLj9T0rtaO2awMnj9L7X35XlzDSSNY,100819 +scipy/interpolate/_fitpack_impl.py,sha256=Leq-8atHqOaivorkHI1gCWeLEdNS_vXTfKXThfE2Ytg,29026 +scipy/interpolate/_fitpack_py.py,sha256=nel0EijErn57Obc-Pc9zlkSi82f5rw3PI8Ps90avVNc,32532 +scipy/interpolate/_fitpack_repro.py,sha256=gkSa4pK7NhZ1Q5tCLOxsCE-fv6ZRnqvsBrEAnFGoGL0,51993 +scipy/interpolate/_interpnd.cpython-312-x86_64-linux-gnu.so,sha256=lNwMRAsZsa_dF6vSwU7f984c4ukaDygpl81QEjE5UJ8,251224 +scipy/interpolate/_interpolate.py,sha256=_CmaVI-6l6eClDpk0ch8brQUU2WkSS2duY-8abyPIKg,96984 +scipy/interpolate/_ndbspline.py,sha256=D5usjgpbp5pnbx12XUnXXk1KaGWlZw9Zx0Cfa6bQ9nw,17844 +scipy/interpolate/_ndgriddata.py,sha256=6OGyuiHGODBrA7g1yt7NDzBGwANwfzG_71a_B3gQRLE,12830 +scipy/interpolate/_pade.py,sha256=QDo29hdGK4Q48M89mTJSbSa1nDXPKi7os55aQmOKrjU,2311 +scipy/interpolate/_polyint.py,sha256=UhnTDZusDJv9mdI7h4_PSFkQXvfruJNFhWTC1ReZXL4,39910 +scipy/interpolate/_ppoly.cpython-312-x86_64-linux-gnu.so,sha256=Yz-4o2MeQLCB22to3-8nL_lOSoZ7rKMD24reIHMdTpY,222272 +scipy/interpolate/_rbf.py,sha256=rKLmfGuZTVDJ3qWIyy_mqI7u5EYijMnFGJ9EUFqnT0g,11767 +scipy/interpolate/_rbfinterp.py,sha256=yMbtK5ewAIfDxzL61IYC8IMHKOawJKuq8Kzbekufd5M,19438 +scipy/interpolate/_rbfinterp_common.py,sha256=YSKWb68Lb9CXfwoCm1u71tq2il-0lxknFfwgufdkHKE,973 +scipy/interpolate/_rbfinterp_np.py,sha256=-XpDAHw2hSsNAiWKMLsWK5lAeABKtkRFCL6wIfpCleY,3056 +scipy/interpolate/_rbfinterp_pythran.cpython-312-x86_64-linux-gnu.so,sha256=ai3vW00x8gXZGCy1XLyEIcgF3ePVEkaaKkQbZISfIS4,247288 +scipy/interpolate/_rbfinterp_xp.py,sha256=A4r4GkCe7LJD4FzZl69FcuIgmrYr1tPlrO6I8dO0TFs,7654 +scipy/interpolate/_regrid.py,sha256=XlyWGWtOslj4i0uMLv2G9-nHReNCEpcIdOCQ2mt-vpk,42227 +scipy/interpolate/_rgi.py,sha256=uVPtOx6DDy-_62KnYa0RqFi6XgZJQzCHzRRoxK7Nz0Q,33923 +scipy/interpolate/_rgi_cython.cpython-312-x86_64-linux-gnu.so,sha256=hkOpUzqVYEAsDdK9rBK1qIU288pwHnEmYJlq6FJIu_k,123408 +scipy/interpolate/dfitpack.py,sha256=5gqJovnbNUuXXMlF9y_HswrmAKN5fOBcRWUa7XponuY,593 +scipy/interpolate/fitpack.py,sha256=aCH6A3dRouuXW47tK5lEdd2pJa39LCkewY-1zTlI8Hc,702 +scipy/interpolate/fitpack2.py,sha256=P15_3gM5eZQYb_-K3c70xKdeIGM81u5WAkVhY8ei4N0,817 +scipy/interpolate/interpnd.py,sha256=3jlLsX7kgW7s2YPBkT-W-iA_qxTbbpm72YCIzZ_B4go,609 +scipy/interpolate/interpolate.py,sha256=Aiu_dJ_oxq-Y1VXns5N5u5K1Wng2hzCgRgRiDhTAiVI,754 +scipy/interpolate/ndgriddata.py,sha256=VbvvoDPdWmrk8871y5olPS9StX0S_B27j_oGMAyj8QQ,636 +scipy/interpolate/polyint.py,sha256=ek1EtbIbLLwehb8XDSKeNvIdjTfDQoQ9CSu4TbY8Vbo,672 +scipy/interpolate/rbf.py,sha256=6oBxdpsKY8bH36nQnRNiLB9C1bNri8b2PHz9IsUIr-w,519 +scipy/interpolate/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +scipy/interpolate/tests/__pycache__/__init__.cpython-312.pyc,, +scipy/interpolate/tests/__pycache__/test_bary_rational.cpython-312.pyc,, +scipy/interpolate/tests/__pycache__/test_bsplines.cpython-312.pyc,, +scipy/interpolate/tests/__pycache__/test_fitpack.cpython-312.pyc,, +scipy/interpolate/tests/__pycache__/test_fitpack2.cpython-312.pyc,, +scipy/interpolate/tests/__pycache__/test_gil.cpython-312.pyc,, +scipy/interpolate/tests/__pycache__/test_interpnd.cpython-312.pyc,, +scipy/interpolate/tests/__pycache__/test_interpolate.cpython-312.pyc,, +scipy/interpolate/tests/__pycache__/test_ndgriddata.cpython-312.pyc,, +scipy/interpolate/tests/__pycache__/test_pade.cpython-312.pyc,, +scipy/interpolate/tests/__pycache__/test_polyint.cpython-312.pyc,, +scipy/interpolate/tests/__pycache__/test_rbf.cpython-312.pyc,, +scipy/interpolate/tests/__pycache__/test_rbfinterp.cpython-312.pyc,, +scipy/interpolate/tests/__pycache__/test_rgi.cpython-312.pyc,, +scipy/interpolate/tests/data/bug-1310.npz,sha256=jWgDwLOY8nBMI28dG56OXt4GvRZaCrsPIoKBq71FWuk,2648 +scipy/interpolate/tests/data/estimate_gradients_hang.npy,sha256=QGwQhXQX_16pjYzSiUXJ0OT1wk-SpIrQ6Pq5Vb8kd_E,35680 +scipy/interpolate/tests/data/gcvspl.npz,sha256=A86BVabLoMG_CiRBoQwigZH5Ft7DbLggcjQpgRKWu6g,3138 +scipy/interpolate/tests/test_bary_rational.py,sha256=rGpVL12-EMGV1xprxGq97RinOd13x08EyjfdLApYlwg,16909 +scipy/interpolate/tests/test_bsplines.py,sha256=L2UrQofAE4vcwqJepqhlMbVYDT15P-1D0ziZEj9QoYw,154773 +scipy/interpolate/tests/test_fitpack.py,sha256=JdEepCjv2syLJNijiMP2ItatNRU_VPcKOa99OVymRnc,17989 +scipy/interpolate/tests/test_fitpack2.py,sha256=Z_LXKVruSrgn-PGY5NRqgEqbOkBbEMUtfETkIWwUqno,75750 +scipy/interpolate/tests/test_gil.py,sha256=pvHntQR6KQrEzJuMjtr_4v2frUuHCf9r4DmLCCPKeqo,2384 +scipy/interpolate/tests/test_interpnd.py,sha256=uJAeyOEGfF7M1_NvecyGEFuWZSOLE_LeriNFAmtKNDg,18467 +scipy/interpolate/tests/test_interpolate.py,sha256=iMP5lVC2bmbp05_oHcmuUhfIrMtf_FkncSD1gq-XvTA,108796 +scipy/interpolate/tests/test_ndgriddata.py,sha256=MqDsDmIoqT3dxQN-NN830zKwb8dPsmt6goT0AyOxRRk,10994 +scipy/interpolate/tests/test_pade.py,sha256=H5xuk2VRJ4v7SdylmyN2JIlxnJg2V4DjAWSAa-6rqCs,4217 +scipy/interpolate/tests/test_polyint.py,sha256=o42QJbtP5VPXEbcv7hq9x01ZuR04V7_UEgpNY_KXkCI,37574 +scipy/interpolate/tests/test_rbf.py,sha256=cg3vMfbKsBZDLCfVe7atEXHbhtaAfk_ks02vioLq3DE,7004 +scipy/interpolate/tests/test_rbfinterp.py,sha256=n60kLgrOIsrly-u6uKjLcU7eP-RX3girgvhNzAuuKNI,21274 +scipy/interpolate/tests/test_rgi.py,sha256=Uw5qJlyvOfTFMKC6aMRn4tgOEjO89E2l22_yl2iwScQ,50735 +scipy/io/__init__.py,sha256=V7XGFwDG6dzpq6Yamyz82M6abAtHQ-XciEyNF8Rga-w,2775 +scipy/io/__pycache__/__init__.cpython-312.pyc,, +scipy/io/__pycache__/_fortran.cpython-312.pyc,, +scipy/io/__pycache__/_idl.cpython-312.pyc,, +scipy/io/__pycache__/_mmio.cpython-312.pyc,, +scipy/io/__pycache__/_netcdf.cpython-312.pyc,, +scipy/io/__pycache__/harwell_boeing.cpython-312.pyc,, +scipy/io/__pycache__/idl.cpython-312.pyc,, +scipy/io/__pycache__/mmio.cpython-312.pyc,, +scipy/io/__pycache__/netcdf.cpython-312.pyc,, +scipy/io/__pycache__/wavfile.cpython-312.pyc,, +scipy/io/_fast_matrix_market/__init__.py,sha256=r7XpgKyg-sQSQkkW4E8ySfP4y65JJU7WCbGnQBZaky4,18378 +scipy/io/_fast_matrix_market/__pycache__/__init__.cpython-312.pyc,, +scipy/io/_fast_matrix_market/_fmm_core.cpython-312-x86_64-linux-gnu.so,sha256=EgoJoE-Yj88rpdZ9wnxgskzX6luIR5FdWUTroF9eIqM,4098088 +scipy/io/_fortran.py,sha256=lXpG8iBJaHg_WYJvl-k1hvh4YE8_03fiyo2IqBQXr_U,10860 +scipy/io/_harwell_boeing/__init__.py,sha256=90qYbBzDEoTMG8ouVLGnTU2GMsY4BYOOtwJdoKT3Zz8,164 +scipy/io/_harwell_boeing/__pycache__/__init__.cpython-312.pyc,, +scipy/io/_harwell_boeing/__pycache__/_fortran_format_parser.cpython-312.pyc,, +scipy/io/_harwell_boeing/__pycache__/hb.cpython-312.pyc,, +scipy/io/_harwell_boeing/_fortran_format_parser.py,sha256=qvHmXonHRYMYTc-sV7TxRwxdrt4WRZk4rgawnwcleQ0,9003 +scipy/io/_harwell_boeing/hb.py,sha256=PHdqqGek2LEIYSIM8HYogkwGOFGGLaHjJidy8MaO5GU,20625 +scipy/io/_harwell_boeing/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +scipy/io/_harwell_boeing/tests/__pycache__/__init__.cpython-312.pyc,, +scipy/io/_harwell_boeing/tests/__pycache__/test_fortran_format.cpython-312.pyc,, +scipy/io/_harwell_boeing/tests/__pycache__/test_hb.cpython-312.pyc,, +scipy/io/_harwell_boeing/tests/test_fortran_format.py,sha256=hPH4AmfUmyBrDU3C_Rx3j7yaGEjefQJOai4rfxMHuV0,2383 +scipy/io/_harwell_boeing/tests/test_hb.py,sha256=Ao5bMBKv0sRQFJdREk-EmgHqdHGFzXKc66ByN6LgeKc,3025 +scipy/io/_idl.py,sha256=NxigbDYMHai4ua_Blf_gcG2U6JVjLZBSnc0f_PpYCqE,27019 +scipy/io/_mmio.py,sha256=ZpyffqCtFgWxX-NsQZ6jjsMHcvRSuCnXLdUAEDE8qWE,33660 +scipy/io/_netcdf.py,sha256=ToO1_GnuJypHf3vzus55EOx_6oYPKv5D5LvDCMdNNz0,39502 +scipy/io/arff/__init__.py,sha256=czaV8hvY6JnmEn2qyU3_fzcy_P55aXVT09OzGnhJT9I,805 +scipy/io/arff/__pycache__/__init__.cpython-312.pyc,, +scipy/io/arff/__pycache__/_arffread.cpython-312.pyc,, +scipy/io/arff/__pycache__/arffread.cpython-312.pyc,, +scipy/io/arff/_arffread.py,sha256=4UZBZgWvAUY0DyoMvj6GbwMVcn6YfzBAsJDV8IYekAQ,26288 +scipy/io/arff/arffread.py,sha256=KW6mASZrW2J1wmC3GYucy1EO7y-rg5MgcGDMyMTpfw4,575 +scipy/io/arff/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +scipy/io/arff/tests/__pycache__/__init__.cpython-312.pyc,, +scipy/io/arff/tests/__pycache__/test_arffread.cpython-312.pyc,, +scipy/io/arff/tests/data/iris.arff,sha256=fTS6VWSX6dwoM16mYoo30dvLoJChriDcLenHAy0ZkVM,7486 +scipy/io/arff/tests/data/missing.arff,sha256=ga__Te95i1Yf-yu2kmYDBVTz0xpSTemz7jS74_OfI4I,120 +scipy/io/arff/tests/data/nodata.arff,sha256=DBXdnIe28vrbf4C-ar7ZgeFIa0kGD4pDBJ4YP-z4QHQ,229 +scipy/io/arff/tests/data/quoted_nominal.arff,sha256=01mPSc-_OpcjXFy3EoIzKdHCmzWSag4oK1Ek2tUc6_U,286 +scipy/io/arff/tests/data/quoted_nominal_spaces.arff,sha256=bcMOl-E0I5uTT27E7bDTbW2mYOp9jS8Yrj0NfFjQdKU,292 +scipy/io/arff/tests/data/test1.arff,sha256=nUFDXUbV3sIkur55rL4qvvBdqUTbzSRrTiIPwmtmG8I,191 +scipy/io/arff/tests/data/test10.arff,sha256=va7cXiWX_AnHf-_yz25ychD8hOgf7-sEMJITGwQla30,199009 +scipy/io/arff/tests/data/test11.arff,sha256=G-cbOUUxuc3859vVkRDNjcLRSnUu8-T-Y8n0dSpvweo,241 +scipy/io/arff/tests/data/test2.arff,sha256=COGWCYV9peOGLqlYWhqG4ANT2UqlAtoVehbJLW6fxHw,300 +scipy/io/arff/tests/data/test3.arff,sha256=jUTWGaZbzoeGBneCmKu6V6RwsRPp9_0sJaSCdBg6tyI,72 +scipy/io/arff/tests/data/test4.arff,sha256=mtyuSFKUeiRR2o3mNlwvDCxWq4DsHEBHj_8IthNzp-M,238 +scipy/io/arff/tests/data/test5.arff,sha256=2Q_prOBCfM_ggsGRavlOaJ_qnWPFf2akFXJFz0NtTIE,365 +scipy/io/arff/tests/data/test6.arff,sha256=V8FNv-WUdurutFXKTOq8DADtNDrzfW65gyOlv-lquOU,195 +scipy/io/arff/tests/data/test7.arff,sha256=rxsqdev8WeqC_nKJNwetjVYXA1-qCzWmaHlMvSaVRGk,559 +scipy/io/arff/tests/data/test8.arff,sha256=c34srlkU8hkXYpdKXVozEutiPryR8bf_5qEmiGQBoG4,429 +scipy/io/arff/tests/data/test9.arff,sha256=ZuXQQzprgmTXxENW7we3wBJTpByBlpakrvRgG8n7fUk,311 +scipy/io/arff/tests/test_arffread.py,sha256=bWB6uAqr6Iadm3fWhhlo6M2fM0WTKrLTC1BmXYKidJ4,13094 +scipy/io/harwell_boeing.py,sha256=BzISbfgVnrO3vYx-mP2xkLqh9r3oq64NNPbEY03P6v0,538 +scipy/io/idl.py,sha256=A1QV5h6xBa1cTIejjsc1NfjG0MqMbxqFqXicC2OLNrM,504 +scipy/io/matlab/__init__.py,sha256=PMqq8WBOEOPH_v5IVpuxrBTqXOFBJVuegOCml76pr1s,2247 +scipy/io/matlab/__pycache__/__init__.cpython-312.pyc,, +scipy/io/matlab/__pycache__/_byteordercodes.cpython-312.pyc,, +scipy/io/matlab/__pycache__/_mio.cpython-312.pyc,, +scipy/io/matlab/__pycache__/_mio4.cpython-312.pyc,, +scipy/io/matlab/__pycache__/_mio5.cpython-312.pyc,, +scipy/io/matlab/__pycache__/_mio5_params.cpython-312.pyc,, +scipy/io/matlab/__pycache__/_miobase.cpython-312.pyc,, +scipy/io/matlab/__pycache__/byteordercodes.cpython-312.pyc,, +scipy/io/matlab/__pycache__/mio.cpython-312.pyc,, +scipy/io/matlab/__pycache__/mio4.cpython-312.pyc,, +scipy/io/matlab/__pycache__/mio5.cpython-312.pyc,, +scipy/io/matlab/__pycache__/mio5_params.cpython-312.pyc,, +scipy/io/matlab/__pycache__/mio5_utils.cpython-312.pyc,, +scipy/io/matlab/__pycache__/mio_utils.cpython-312.pyc,, +scipy/io/matlab/__pycache__/miobase.cpython-312.pyc,, +scipy/io/matlab/__pycache__/streams.cpython-312.pyc,, +scipy/io/matlab/_byteordercodes.py,sha256=AUMjfdIARtCGqyMgDDJBGa_EncP5ioYrEzyZqXOLRxU,1983 +scipy/io/matlab/_mio.py,sha256=0mrk7hbuInEeiqDRcT_ldOCRJtkvF3uawWerFEbqa-k,15405 +scipy/io/matlab/_mio4.py,sha256=W9FaF7ryhbT10TEgHcuovZkm7w2zIU3tDtnb5gIlYlQ,20993 +scipy/io/matlab/_mio5.py,sha256=83juXAODgXJemLZ057BLuxQI7iXwvAjTSoVHOnam6bk,34714 +scipy/io/matlab/_mio5_params.py,sha256=tAFawxBrxilE_2S3UrXSs3khqnI6R9xuyZ-nPf5bFX8,8638 +scipy/io/matlab/_mio5_utils.cpython-312-x86_64-linux-gnu.so,sha256=kqwqsv2Pxwkm4GiYQaLYyQzgJxLuVx9wY5cQYZEeIfw,223600 +scipy/io/matlab/_mio_utils.cpython-312-x86_64-linux-gnu.so,sha256=kH__3KLtdrXB0Il8Py-A7UER7ev7c4Tzthn6VgyEIo8,63808 +scipy/io/matlab/_miobase.py,sha256=S6WuI_I0n-WUqr5shUHJroPy_bDrhKQyuL74TvlQdsE,13103 +scipy/io/matlab/_streams.cpython-312-x86_64-linux-gnu.so,sha256=a2BDZ0-5wZ3jB7QiwZc12dDWs-jIvYzfr9pgP6tB-io,134328 +scipy/io/matlab/byteordercodes.py,sha256=fHZVESDgIeYzGYtRlknPQ2nUqscQQ_4FhQc_ClkjBvQ,528 +scipy/io/matlab/mio.py,sha256=2b0WwgQ0rBkoJ4X0hgPl889PpR7Q0i7ibSLtTQVuTto,539 +scipy/io/matlab/mio4.py,sha256=hkhpBa4p0euf2rUjJviBWJ4TJs1wkUads3mX1fgDYMc,508 +scipy/io/matlab/mio5.py,sha256=jEFeEEkXWOhziPreDt0SqfAtOo9JMauxoODAbbXHmoQ,638 +scipy/io/matlab/mio5_params.py,sha256=2RWROlfc8RmXmcXGyM-be107Tm55ibc_U7DztJ2b4fc,593 +scipy/io/matlab/mio5_utils.py,sha256=DYiQfx5BkyDVnK4nZ3xPa-5tbpZE7WRx4SIdBmPVfSI,520 +scipy/io/matlab/mio_utils.py,sha256=VZPx03BNFbrQjB1CNbDCvvXUuP0_VoNRFV1R0YoB2iw,518 +scipy/io/matlab/miobase.py,sha256=3qQoq8Y7ZQpHIufUCzg6RAeaLqU3qTAozmuYbaOd7BI,565 +scipy/io/matlab/streams.py,sha256=0Aww9GRGGnRmiAMBAzIAXsFGySu5YCUNG-cHP1omYjI,513 +scipy/io/matlab/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +scipy/io/matlab/tests/__pycache__/__init__.cpython-312.pyc,, +scipy/io/matlab/tests/__pycache__/test_byteordercodes.cpython-312.pyc,, +scipy/io/matlab/tests/__pycache__/test_mio.cpython-312.pyc,, +scipy/io/matlab/tests/__pycache__/test_mio5_utils.cpython-312.pyc,, +scipy/io/matlab/tests/__pycache__/test_mio_funcs.cpython-312.pyc,, +scipy/io/matlab/tests/__pycache__/test_mio_utils.cpython-312.pyc,, +scipy/io/matlab/tests/__pycache__/test_miobase.cpython-312.pyc,, +scipy/io/matlab/tests/__pycache__/test_pathological.cpython-312.pyc,, +scipy/io/matlab/tests/__pycache__/test_streams.cpython-312.pyc,, +scipy/io/matlab/tests/data/bad_miuint32.mat,sha256=CVkYHp_U4jxYKRRHSuZ5fREop4tJjnZcQ02DKfObkRA,272 +scipy/io/matlab/tests/data/bad_miutf8_array_name.mat,sha256=V-jfVMkYyy8qRGcOIsNGcoO0GCgTxchrsQUBGBnfWHE,208 +scipy/io/matlab/tests/data/big_endian.mat,sha256=2ttpiaH2B6nmHnq-gsFeMvZ2ZSLOlpzt0IJiqBTcc8M,273 +scipy/io/matlab/tests/data/broken_utf8.mat,sha256=nm8aotRl6NIxlM3IgPegKR3EeevYZoJCrYpV4Sa1T5I,216 +scipy/io/matlab/tests/data/corrupted_zlib_checksum.mat,sha256=X4dvE7K9DmGEF3D6I-48hC86W41jB54H7bD8KTXjtYA,276 +scipy/io/matlab/tests/data/corrupted_zlib_data.mat,sha256=DfE1YBH-pYw-dAaEeKA6wZcyKeo9GlEfrzZtql-fO_w,3451 +scipy/io/matlab/tests/data/debigged_m4.mat,sha256=8QbD-LzoYbKSfOYPRRw-oelDJscwufYp5cqLfZ1hB0c,1024 +scipy/io/matlab/tests/data/japanese_utf8.txt,sha256=rgxiBH7xmEKF91ZkB3oMLrqABBXINEMHPXDKdZXNBEY,270 +scipy/io/matlab/tests/data/little_endian.mat,sha256=FQP_2MNod-FFF-JefN7ZxovQ6QLCdHQ0DPL_qBCP44Y,265 +scipy/io/matlab/tests/data/logical_sparse.mat,sha256=qujUUpYewaNsFKAwGpYS05z7kdUv9TQZTHV5_lWhRrs,208 +scipy/io/matlab/tests/data/malformed1.mat,sha256=DTuTr1-IzpLMBf8u5DPb3HXmw9xJo1aWfayA5S_3zUI,2208 +scipy/io/matlab/tests/data/miuint32_for_miint32.mat,sha256=romrBP_BS46Sl2-pKWsUnxYDad2wehyjq4wwLaVqums,272 +scipy/io/matlab/tests/data/miutf8_array_name.mat,sha256=Vo8JptFr-Kg2f2cEoDg8LtELSjVNyccdJY74WP_kqtc,208 +scipy/io/matlab/tests/data/nasty_duplicate_fieldnames.mat,sha256=bvdmj6zDDUIpOfIP8J4Klo107RYCDd5VK5gtOYx3GsU,8168 +scipy/io/matlab/tests/data/one_by_zero_char.mat,sha256=Z3QdZjTlOojjUpS0cfBP4XfNQI3GTjqU0n_pnAzgQhU,184 +scipy/io/matlab/tests/data/parabola.mat,sha256=ENWuWX_uwo4Av16dIGOwnbMReAMrShDhalkq8QUI8Rg,729 +scipy/io/matlab/tests/data/single_empty_string.mat,sha256=4uTmX0oydTjmtnhxqi9SyPWCG2I24gj_5LarS80bPik,171 +scipy/io/matlab/tests/data/some_functions.mat,sha256=JA736oG3s8PPdKhdsYK-BndLUsGrJCJAIRBseSIEZtM,1397 +scipy/io/matlab/tests/data/sqr.mat,sha256=3DtGl_V4wABKCDQ0P3He5qfOzpUTC-mINdK73MKS7AM,679 +scipy/io/matlab/tests/data/test3dmatrix_6.1_SOL2.mat,sha256=-odiBIQAbOLERg0Vg682QHGfs7C8MaA_gY77OWR8x78,232 +scipy/io/matlab/tests/data/test3dmatrix_6.5.1_GLNX86.mat,sha256=G5siwvZ-7Uv5KJ6h7AA3OHL6eiFsd8Lnjx4IcoByzCU,232 +scipy/io/matlab/tests/data/test3dmatrix_7.1_GLNX86.mat,sha256=EVj1wPnoyWGIdTpkSj3YAwqzTAm27eqZNxCaJAs3pwU,213 +scipy/io/matlab/tests/data/test3dmatrix_7.4_GLNX86.mat,sha256=S_Sd3sxorDd8tZ5CxD5_J8vXbfcksLWzhUQY5b82L9g,213 +scipy/io/matlab/tests/data/test_empty_struct.mat,sha256=WoC7g7TyXqNr2T0d5xE3IUq5PRzatE0mxXjqoHX5Xec,173 +scipy/io/matlab/tests/data/test_mat4_le_floats.mat,sha256=2xvn3Cg4039shJl62T-bH-VeVP_bKtwdqvGfIxv8FJ4,38 +scipy/io/matlab/tests/data/test_skip_variable.mat,sha256=pJLVpdrdEb-9SMZxaDu-uryShlIi90l5LfXhvpVipJ0,20225 +scipy/io/matlab/tests/data/testbool_8_WIN64.mat,sha256=_xBw_2oZA7u9Xs6GJItUpSIEV4jVdfdcwzmLNFWM6ow,185 +scipy/io/matlab/tests/data/testcell_6.1_SOL2.mat,sha256=OWOBzNpWTyAHIcZABRytVMcABiRYgEoMyF9gDaIkFe4,536 +scipy/io/matlab/tests/data/testcell_6.5.1_GLNX86.mat,sha256=7111TN_sh1uMHmYx-bjd_v9uaAnWhJMhrQFAtAw6Nvk,536 +scipy/io/matlab/tests/data/testcell_7.1_GLNX86.mat,sha256=62p6LRW6PbM-Y16aUeGVhclTVqS5IxPUtsohe7MjrYo,283 +scipy/io/matlab/tests/data/testcell_7.4_GLNX86.mat,sha256=NkTA8UW98hIQ0t5hGx_leG-MzNroDelYwqx8MPnO63Q,283 +scipy/io/matlab/tests/data/testcellnest_6.1_SOL2.mat,sha256=AeNaog8HUDCVrIuGICAXYu9SGDsvV6qeGjgvWHrVQho,568 +scipy/io/matlab/tests/data/testcellnest_6.5.1_GLNX86.mat,sha256=Gl4QA0yYwGxjiajjgWS939WVAM-W2ahNIm9wwMaT5oc,568 +scipy/io/matlab/tests/data/testcellnest_7.1_GLNX86.mat,sha256=CUGtkwIU9CBa0Slx13mbaM67_ec0p-unZdu8Z4YYM3c,228 +scipy/io/matlab/tests/data/testcellnest_7.4_GLNX86.mat,sha256=TeTk5yjl5j_bcnmIkpzuYHxGGQXNu-rK6xOsN4t6lX8,228 +scipy/io/matlab/tests/data/testcomplex_4.2c_SOL2.mat,sha256=WOwauWInSVUFBuOJ1Bo3spmUQ3UWUIlsIe4tYGlrU7o,176 +scipy/io/matlab/tests/data/testcomplex_6.1_SOL2.mat,sha256=GpAEccizI8WvlrBPdvlKUv6uKbZOo_cjUK3WVVb2lo4,352 +scipy/io/matlab/tests/data/testcomplex_6.5.1_GLNX86.mat,sha256=3MEbf0zJdQGAO7x-pzFCup2QptfYJHQG59z0vVOdxl4,352 +scipy/io/matlab/tests/data/testcomplex_7.1_GLNX86.mat,sha256=VNHV2AIEkvPuhae1kKIqt5t8AMgUyr0L_CAp-ykLxt4,247 +scipy/io/matlab/tests/data/testcomplex_7.4_GLNX86.mat,sha256=8rWGf5bqY7_2mcd5w5gTYgMkXVePlLL8qT7lh8kApn0,247 +scipy/io/matlab/tests/data/testdouble_4.2c_SOL2.mat,sha256=MzT7OYPEUXHYNPBrVkyKEaG5Cas2aOA0xvrO7l4YTrQ,103 +scipy/io/matlab/tests/data/testdouble_6.1_SOL2.mat,sha256=DpB-mVKx1gsjl-3IbxfxHNuzU5dnuku-MDQCA8kALVI,272 +scipy/io/matlab/tests/data/testdouble_6.5.1_GLNX86.mat,sha256=4hY5VEubavNEv5KvcqQnd7MWWvFUzHXXpYIqUuUt-50,272 +scipy/io/matlab/tests/data/testdouble_7.1_GLNX86.mat,sha256=N2QOOIXPyy0zPZZ_qY7xIDaodMGrTq3oXNBEHZEscw0,232 +scipy/io/matlab/tests/data/testdouble_7.4_GLNX86.mat,sha256=TrkJ4Xx_dC9YrPdewlsOvYs_xag7gT3cN4HkDsJmT8I,232 +scipy/io/matlab/tests/data/testemptycell_5.3_SOL2.mat,sha256=g96Vh9FpNhkiWKsRm4U6KqeKd1hNAEyYSD7IVzdzwsU,472 +scipy/io/matlab/tests/data/testemptycell_6.5.1_GLNX86.mat,sha256=2Zw-cMv-Mjbs2HkSl0ubmh_htFUEpkn7XVHG8iM32o0,472 +scipy/io/matlab/tests/data/testemptycell_7.1_GLNX86.mat,sha256=t5Ar8EgjZ7fkTUHIVpdXg-yYWo_MBaigMDJUGWEIrmU,218 +scipy/io/matlab/tests/data/testemptycell_7.4_GLNX86.mat,sha256=5PPvfOoL-_Q5ou_2nIzIrHgeaOZGFXGxAFdYzCQuwEQ,218 +scipy/io/matlab/tests/data/testfunc_7.4_GLNX86.mat,sha256=ScTKftENe78imbMc0I5ouBlIMcEEmZgu8HVKWAMNr58,381 +scipy/io/matlab/tests/data/testhdf5_7.4_GLNX86.mat,sha256=ZoVbGk38_MCppZ0LRr6OE07HL8ZB4rHXgMj9LwUBgGg,4168 +scipy/io/matlab/tests/data/testmatlabstring_7_WIN64.mat,sha256=XtIsLWJlPSLNUqFF3xh_cs-W3yEz_WQwn2FrRTZXKl4,603 +scipy/io/matlab/tests/data/testmatrix_4.2c_SOL2.mat,sha256=14YMiKAN9JCPTqSDXxa58BK6Un7EM4hEoSGAUuwKWGQ,151 +scipy/io/matlab/tests/data/testmatrix_6.1_SOL2.mat,sha256=ZdjNbcIE75V5Aht5EVBvJX26aabvNqbUH0Q9VBnxBS4,216 +scipy/io/matlab/tests/data/testmatrix_6.5.1_GLNX86.mat,sha256=OB82QgB6SwtsxT4t453OVSj-B777XrHGEGOMgMD1XGc,216 +scipy/io/matlab/tests/data/testmatrix_7.1_GLNX86.mat,sha256=-TYB0kREY7i7gt5x15fOYjXi410pXuDWUFxPYuMwywI,193 +scipy/io/matlab/tests/data/testmatrix_7.4_GLNX86.mat,sha256=l9psDc5K1bpxNeuFlyYIYauswLnOB6dTX6-jvelW0kU,193 +scipy/io/matlab/tests/data/testminus_4.2c_SOL2.mat,sha256=2914WYQajPc9-Guy3jDOLU3YkuE4OXC_63FUSDzJzX0,38 +scipy/io/matlab/tests/data/testminus_6.1_SOL2.mat,sha256=2X2fZKomz0ktBvibj7jvHbEvt2HRA8D6hN9qA1IDicw,200 +scipy/io/matlab/tests/data/testminus_6.5.1_GLNX86.mat,sha256=i364SgUCLSYRjQsyygvY1ArjEaO5uLip3HyU-R7zaLo,200 +scipy/io/matlab/tests/data/testminus_7.1_GLNX86.mat,sha256=gtYNC9_TciYdq8X9IwyGEjiw2f1uCVTGgiOPFOiQbJc,184 +scipy/io/matlab/tests/data/testminus_7.4_GLNX86.mat,sha256=eXcoTM8vKuh4tQnl92lwdDaqssGB6G9boSHh3FOCkng,184 +scipy/io/matlab/tests/data/testmulti_4.2c_SOL2.mat,sha256=Zhyu2KCsseSJ5NARdS00uwddCs4wmjcWNP2LJFns2-Q,240 +scipy/io/matlab/tests/data/testmulti_7.1_GLNX86.mat,sha256=KI3H58BVj6k6MFsj8icSbjy_0Z-jOesWN5cafStLPG8,276 +scipy/io/matlab/tests/data/testmulti_7.4_GLNX86.mat,sha256=Yr4YKCP27yMWlK5UOK3BAEOAyMr-m0yYGcj8v1tCx-I,276 +scipy/io/matlab/tests/data/testobject_6.1_SOL2.mat,sha256=kzLxy_1o1HclPXWyA-SX5gl6LsG1ioHuN4eS6x5iZio,800 +scipy/io/matlab/tests/data/testobject_6.5.1_GLNX86.mat,sha256=dq_6_n0v7cUz9YziXn-gZFNc9xYtNxZ8exTsziWIM7s,672 +scipy/io/matlab/tests/data/testobject_7.1_GLNX86.mat,sha256=3z-boFw0SC5142YPOLo2JqdusPItVzjCFMhXAQNaQUQ,306 +scipy/io/matlab/tests/data/testobject_7.4_GLNX86.mat,sha256=5OwLTMgCBlxsDfiEUzlVjqcSbVQG-X5mIw5JfW3wQXA,306 +scipy/io/matlab/tests/data/testonechar_4.2c_SOL2.mat,sha256=BCvppGhO19-j-vxAvbdsORIiyuJqzCuQog9Ao8V1lvA,40 +scipy/io/matlab/tests/data/testonechar_6.1_SOL2.mat,sha256=ThppTHGJFrUfal5tewS70DL00dSwk1otazuVdJrTioE,200 +scipy/io/matlab/tests/data/testonechar_6.5.1_GLNX86.mat,sha256=SBfN6e7Vz1rAdi8HLguYXcHUHk1viaXTYccdEyhhob4,200 +scipy/io/matlab/tests/data/testonechar_7.1_GLNX86.mat,sha256=m8W9GqvflfAsizkhgAfT0lLcxuegZIWCLNuHVX69Jac,184 +scipy/io/matlab/tests/data/testonechar_7.4_GLNX86.mat,sha256=t9ObKZOLy3vufnER8TlvQcUkd_wmXbJSdQoG4f3rVKY,184 +scipy/io/matlab/tests/data/testscalarcell_7.4_GLNX86.mat,sha256=5LX9sLH7Y6h_N_a1XRN2GuMgp_P7ECpPsXGDOypAJg0,194 +scipy/io/matlab/tests/data/testsimplecell.mat,sha256=Aoeh0PX2yiLDTwkxMEyZ_CNX2mJHZvyfuFJl817pA1c,220 +scipy/io/matlab/tests/data/testsparse_4.2c_SOL2.mat,sha256=dFUcB1gunfWqexgR4YDZ_Ec0w0HffM1DUE1C5PVfDDc,223 +scipy/io/matlab/tests/data/testsparse_6.1_SOL2.mat,sha256=9Sgd_SPkGNim7ZL0xgD71qml3DK0yDHYC7VSNLNQEXA,280 +scipy/io/matlab/tests/data/testsparse_6.5.1_GLNX86.mat,sha256=jp1ILNxLyV6XmCCGxAz529XoZ9dhCqGEO-ExPH70_Pg,328 +scipy/io/matlab/tests/data/testsparse_7.1_GLNX86.mat,sha256=k8QuQ_4Zu7FWTzHjRnHCVZ9Yu5vwNP0WyNzu6TuiY-4,229 +scipy/io/matlab/tests/data/testsparse_7.4_GLNX86.mat,sha256=QbZOCqIvnaK0XOH3kaSXBe-m_1_Rb33psq8E-WMSBTU,229 +scipy/io/matlab/tests/data/testsparsecomplex_4.2c_SOL2.mat,sha256=QMVoBXVyl9RBGvAjLoiW85kAXYJ-hHprUMegEG69A5w,294 +scipy/io/matlab/tests/data/testsparsecomplex_6.1_SOL2.mat,sha256=WfEroAT5YF4HGAKq3jTJxlFrKaTCh3rwlSlKu__VjwA,304 +scipy/io/matlab/tests/data/testsparsecomplex_6.5.1_GLNX86.mat,sha256=e0s6cyoKJeYMArdceHpnKDvtCVcw7XuB44OBDHpoa6U,400 +scipy/io/matlab/tests/data/testsparsecomplex_7.1_GLNX86.mat,sha256=kgHcuq-deI2y8hfkGwlMOkW7lntexdPHfuz0ar6b3jo,241 +scipy/io/matlab/tests/data/testsparsecomplex_7.4_GLNX86.mat,sha256=rYCaWNLXK7f_jjMc6_UvZz6ZDuMCuVRmJV5RyeXiDm8,241 +scipy/io/matlab/tests/data/testsparsefloat_7.4_GLNX86.mat,sha256=hnNV6GZazEeqTXuA9vcOUo4xam_UnKRYGYH9PUGTLv8,219 +scipy/io/matlab/tests/data/teststring_4.2c_SOL2.mat,sha256=cAhec51DlqIYfDXXGaumOE3Hqb3cFWM1UsUK3K_lDP8,375 +scipy/io/matlab/tests/data/teststring_6.1_SOL2.mat,sha256=ciFzNGMO7gjYecony-E8vtOwBY4vXIUhyug6Euaz3Kg,288 +scipy/io/matlab/tests/data/teststring_6.5.1_GLNX86.mat,sha256=yrJrpLiwLvU_LI1D6rw1Pk1qJK1YlC7Cmw7lwyJVLtw,288 +scipy/io/matlab/tests/data/teststring_7.1_GLNX86.mat,sha256=zo7sh-8dMpGqhoNxLEnfz3Oc7RonxiY5j0B3lxk0e8o,224 +scipy/io/matlab/tests/data/teststring_7.4_GLNX86.mat,sha256=igL_CvtAcNEa1nxunDjQZY5wS0rJOlzsUkBiDreJssk,224 +scipy/io/matlab/tests/data/teststringarray_4.2c_SOL2.mat,sha256=pRldk-R0ig1k3ouvaR9oVtBwZsQcDW_b4RBEDYu1-Vk,156 +scipy/io/matlab/tests/data/teststringarray_6.1_SOL2.mat,sha256=B9IdaSsyb0wxjyYyHOj_GDO0laAeWDEJhoEhC9xdm1E,232 +scipy/io/matlab/tests/data/teststringarray_6.5.1_GLNX86.mat,sha256=t4tKGJg2NEg_Ar5MkOjCoQb2hVL8Q_Jdh9FF4TPL_4g,232 +scipy/io/matlab/tests/data/teststringarray_7.1_GLNX86.mat,sha256=lpYkBZX8K-c4FO5z0P9DMfYc7Y-yzyg11J6m-19uYTU,203 +scipy/io/matlab/tests/data/teststringarray_7.4_GLNX86.mat,sha256=lG-c7U-5Bo8j8xZLpd0JAsMYwewT6cAw4eJCZH5xf6E,203 +scipy/io/matlab/tests/data/teststruct_6.1_SOL2.mat,sha256=3GJbA4O7LP57J6IYzmJqTPeSJrEaiNSk-rg7h0ANR1w,608 +scipy/io/matlab/tests/data/teststruct_6.5.1_GLNX86.mat,sha256=fRbqAnzTeOU3dTQx7O24MfMVFr6pM5u594FRrPPkYJE,552 +scipy/io/matlab/tests/data/teststruct_7.1_GLNX86.mat,sha256=mCtI_Yot08NazvWHvehOZbTV4bW_I4-D5jBgJ6T9EbI,314 +scipy/io/matlab/tests/data/teststruct_7.4_GLNX86.mat,sha256=52qaF4HRCtPl1jE6ljbkEl2mofZVAPpmBxrm-J5OTTI,314 +scipy/io/matlab/tests/data/teststructarr_6.1_SOL2.mat,sha256=vneCpWBwApBGfeKzdZcybyajxjR-ZYf64j0l08_hU84,528 +scipy/io/matlab/tests/data/teststructarr_6.5.1_GLNX86.mat,sha256=gqhRpSfNNB5SR9sCp-wWrvokr5VV_heGnvco6dmfOvY,472 +scipy/io/matlab/tests/data/teststructarr_7.1_GLNX86.mat,sha256=6VDU0mtTBEG0bBHqKP1p8xq846eMhSZ_WvBZv8MzE7M,246 +scipy/io/matlab/tests/data/teststructarr_7.4_GLNX86.mat,sha256=ejtyxeeX_W1a2rNrEUUiG9txPW8_UtSgt8IaDOxE2pg,246 +scipy/io/matlab/tests/data/teststructnest_6.1_SOL2.mat,sha256=sbi0wUwOrbU-gBq3lyDwhAbvchdtOJkflOR_MU7uGKA,496 +scipy/io/matlab/tests/data/teststructnest_6.5.1_GLNX86.mat,sha256=uTkKtrYBTuz4kICVisEaG7V5C2nJDKjy92mPDswTLPE,416 +scipy/io/matlab/tests/data/teststructnest_7.1_GLNX86.mat,sha256=o4F2jOhYyNpJCo-BMg6v_ITZQvjenXfXHLq94e7iwRo,252 +scipy/io/matlab/tests/data/teststructnest_7.4_GLNX86.mat,sha256=CNXO12O6tedEuMG0jNma4qfbTgCswAbHwh49a3uE3Yk,252 +scipy/io/matlab/tests/data/testunicode_7.1_GLNX86.mat,sha256=KV97FCW-1XZiXrwXJoZPbgyAht79oIFHa917W1KFLwE,357 +scipy/io/matlab/tests/data/testunicode_7.4_GLNX86.mat,sha256=9-8xzACZleBkMjZnbr8t4Ncs9B6mbzrONDblPnteBPU,357 +scipy/io/matlab/tests/data/testvec_4_GLNX86.mat,sha256=GQzR3mBVS266_NBfrRC9X0dLgmeu8Jl4r4ZYMOrn1V0,93 +scipy/io/matlab/tests/test_byteordercodes.py,sha256=FCHBAxeQZlhvTXw-AO-ukwTWvpN7NzmncBEDJ1P4de4,938 +scipy/io/matlab/tests/test_mio.py,sha256=RuMIWEAvXduxpAXQw2Ij2uGULPbvoYPK7V90lk88aXs,50352 +scipy/io/matlab/tests/test_mio5_utils.py,sha256=eacgGg0TaQXOkG7iaeYovtWyjPgYCY50mHPoPjnHMTI,5389 +scipy/io/matlab/tests/test_mio_funcs.py,sha256=2BgaB9bSwy2M-4gUBrgKsD290UxjLmyuiI3gW4SbVyo,1390 +scipy/io/matlab/tests/test_mio_utils.py,sha256=GX85RuLqr2HxS5_f7ZgrxbhswJy2GPQQoQbiQYg0s14,1594 +scipy/io/matlab/tests/test_miobase.py,sha256=CGefrU6m_GpOwaKr_Q93Z5zKp5nuv791kjxcNNP8iiE,1460 +scipy/io/matlab/tests/test_pathological.py,sha256=-Efeq2x2yAaLK28EKpai1vh4HsZTCteF_hY_vEGWndA,1055 +scipy/io/matlab/tests/test_streams.py,sha256=R8OeacqgwexmDLRHO4eWXJjynD_4VeAdnv7vv6dOJfo,7543 +scipy/io/mmio.py,sha256=Dc5HqR8BXOD0wir63VTVczuZcLjSxEjbSbeZd4y27po,526 +scipy/io/netcdf.py,sha256=RKhmlybZwbFNKA4US6xLX6O2IUDCmdkToosPt4bAUX0,533 +scipy/io/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +scipy/io/tests/__pycache__/__init__.cpython-312.pyc,, +scipy/io/tests/__pycache__/test_fortran.cpython-312.pyc,, +scipy/io/tests/__pycache__/test_idl.cpython-312.pyc,, +scipy/io/tests/__pycache__/test_mmio.cpython-312.pyc,, +scipy/io/tests/__pycache__/test_netcdf.cpython-312.pyc,, +scipy/io/tests/__pycache__/test_paths.cpython-312.pyc,, +scipy/io/tests/__pycache__/test_wavfile.cpython-312.pyc,, +scipy/io/tests/data/Transparent Busy.ani,sha256=vwoK3ysYo87-TwzvjerHjFjSPIGpw83jjiMDXcHPWjA,4362 +scipy/io/tests/data/array_float32_1d.sav,sha256=A_xXWkfS1sQCxP4ONezeEZvlKEXwZ1TPG2rCCFdmBNM,2628 +scipy/io/tests/data/array_float32_2d.sav,sha256=qJmN94pywXznXMHzt-L6DJgaIq_FfruVKJl_LMaI8UU,3192 +scipy/io/tests/data/array_float32_3d.sav,sha256=U7P6As7Nw6LdBY1pTOaW9C-O_NlXLXZwSgbT3H8Z8uk,13752 +scipy/io/tests/data/array_float32_4d.sav,sha256=Tl6erEw_Zq3dwVbVyPXRWqB83u_o4wkIVFOe3wQrSro,6616 +scipy/io/tests/data/array_float32_5d.sav,sha256=VmaBgCD854swYyLouDMHJf4LL6iUNgajEOQf0pUjHjg,7896 +scipy/io/tests/data/array_float32_6d.sav,sha256=lb7modI0OQDweJWbDxEV2OddffKgMgq1tvCy5EK6sOU,19416 +scipy/io/tests/data/array_float32_7d.sav,sha256=pqLWIoxev9sLCs9LLwxFlM4RCFwxHC4Q0dEEz578mpI,3288 +scipy/io/tests/data/array_float32_8d.sav,sha256=R8A004f9XLWvF6eKMNEqIrC6PGP1vLZr9sFqawqM8ZA,13656 +scipy/io/tests/data/array_float32_pointer_1d.sav,sha256=sV7qFNwHK-prG5vODa7m5HYK7HlH_lqdfsI5Y1RWDyg,2692 +scipy/io/tests/data/array_float32_pointer_2d.sav,sha256=b0brvK6xQeezoRuujmEcJNw2v6bfASLM3FSY9u5dMSg,3256 +scipy/io/tests/data/array_float32_pointer_3d.sav,sha256=a_Iyg1YjPBRh6B-N_n_BGIVjFje4K-EPibKV-bPbF7E,13816 +scipy/io/tests/data/array_float32_pointer_4d.sav,sha256=cXrkHHlPyoYstDL_OJ15-55sZOOeDNW2OJ3KWhBv-Kk,6680 +scipy/io/tests/data/array_float32_pointer_5d.sav,sha256=gRVAZ6jeqFZyIQI9JVBHed9Y0sjS-W4bLseb01rIcGs,7960 +scipy/io/tests/data/array_float32_pointer_6d.sav,sha256=9yic-CQiS0YR_ow2yUA2Nix0Nb_YCKMUsIgPhgcJT1c,19480 +scipy/io/tests/data/array_float32_pointer_7d.sav,sha256=Rp1s8RbW8eoEIRTqxba4opAyY0uhTuyy3YkwRlNspQU,3352 +scipy/io/tests/data/array_float32_pointer_8d.sav,sha256=Wk3Dd2ClAwWprXLKZon3blY7aMvMrJqz_NXzK0J5MFY,13720 +scipy/io/tests/data/example_1.nc,sha256=EkfC57dWXeljgXy5sidrJHJG12D1gmQUyPDK18WzlT4,1736 +scipy/io/tests/data/example_2.nc,sha256=wywMDspJ2QT431_sJUr_5DHqG3pt9VTvDJzfR9jeWCk,272 +scipy/io/tests/data/example_3_maskedvals.nc,sha256=P9N92jCJgKJo9VmNd7FeeJSvl4yUUFwBy6JpR4MeuME,1424 +scipy/io/tests/data/fortran-3x3d-2i.dat,sha256=oYCXgtY6qqIqLAhoh_46ob_RVQRcV4uu333pOiLKgRM,451 +scipy/io/tests/data/fortran-mixed.dat,sha256=zTi7RLEnyAat_DdC3iSEcSbyDtAu0aTKwUT-tExjasw,40 +scipy/io/tests/data/fortran-sf8-11x1x10.dat,sha256=KwaOrZOAe-wRhuxvmHIK-Wr59us40MmiA9QyWtIAUaA,888 +scipy/io/tests/data/fortran-sf8-15x10x22.dat,sha256=5ohvjjOUcIsGimSqDhpUUKwflyhVsfwKL5ElQe_SU0I,26408 +scipy/io/tests/data/fortran-sf8-1x1x1.dat,sha256=Djmoip8zn-UcxWGUPKV5wzKOYOf7pbU5L7HaR3BYlec,16 +scipy/io/tests/data/fortran-sf8-1x1x5.dat,sha256=Btgavm3w3c9md_5yFfq6Veo_5IK9KtlLF1JEPeHhZoU,48 +scipy/io/tests/data/fortran-sf8-1x1x7.dat,sha256=L0r9yAEMbfMwYQytzYsS45COqaVk-o_hi6zRY3yIiO4,64 +scipy/io/tests/data/fortran-sf8-1x3x5.dat,sha256=c2LTocHclwTIeaR1Pm3mVMyf5Pl_imfjIFwi4Lpv0Xs,128 +scipy/io/tests/data/fortran-si4-11x1x10.dat,sha256=OesvSIGsZjpKZlZsV74PNwy0Co0KH8-3gxL9-DWoa08,448 +scipy/io/tests/data/fortran-si4-15x10x22.dat,sha256=OJcKyw-GZmhHb8REXMsHDn7W5VP5bhmxgVPIAYG-Fj4,13208 +scipy/io/tests/data/fortran-si4-1x1x1.dat,sha256=1Lbx01wZPCOJHwg99MBDuc6QZKdMnccxNgICt4omfFM,12 +scipy/io/tests/data/fortran-si4-1x1x5.dat,sha256=L1St4yiHTA3v91JjnndYfUrdKfT1bWxckwnnrscEZXc,28 +scipy/io/tests/data/fortran-si4-1x1x7.dat,sha256=Dmqt-tD1v2DiPZkghGGZ9Ss-nJGfei-3yFXPO5Acpk4,36 +scipy/io/tests/data/fortran-si4-1x3x5.dat,sha256=3vl6q93m25jEcZVKD0CuKNHmhZwZKp-rv0tfHoPVP88,68 +scipy/io/tests/data/identification.sav,sha256=uZAgaodtMg_As-eE73hhrqfaXBaIxIuabs-QfPKbIWI,4196 +scipy/io/tests/data/invalid_pointer.sav,sha256=JmgoISXC4r5fSmI5FqyapvmzQ4qpYLf-9N7_Et1p1HQ,1280 +scipy/io/tests/data/null_pointer.sav,sha256=P_3a_sU614F3InwM82jSMtWycSZkvqRn1apwd8XxbtE,2180 +scipy/io/tests/data/scalar_byte.sav,sha256=dNJbcE5OVDY_wHwN_UBUtfIRd13Oqu-RBEO74g5SsBA,2076 +scipy/io/tests/data/scalar_byte_descr.sav,sha256=DNTmDgDWOuzlQnrceER6YJ0NutUUwZ9tozVMBWQmuuY,2124 +scipy/io/tests/data/scalar_complex32.sav,sha256=NGd-EvmFZgt8Ko5MP3T_TLwyby6yS0BXM_OW8197hpU,2076 +scipy/io/tests/data/scalar_complex64.sav,sha256=gFBWtxuAajazupGFSbvlWUPDYK-JdWgZcEWih2-7IYU,2084 +scipy/io/tests/data/scalar_float32.sav,sha256=EwWQw2JTwq99CHVpDAh4R20R0jWaynXABaE2aTRmXrs,2072 +scipy/io/tests/data/scalar_float64.sav,sha256=iPcDlgF1t0HoabvNLWCbSiTPIa9rvVEbOGGmE_3Ilsk,2076 +scipy/io/tests/data/scalar_heap_pointer.sav,sha256=JXZbPmntXILsNOuLIKL8qdu8gDJekYrlN9DQxAWve0E,2204 +scipy/io/tests/data/scalar_int16.sav,sha256=kDBLbPYGo2pzmZDhyl8rlDv0l6TMEWLIoLtmgJXDMkk,2072 +scipy/io/tests/data/scalar_int32.sav,sha256=IzJwLvEoqWLO5JRaHp8qChfptlauU-ll3rb0TfDDM8Y,2072 +scipy/io/tests/data/scalar_int64.sav,sha256=-aSHQRiaE3wjAxINwuLX33_8qmWl4GUkTH45elTkA-8,2076 +scipy/io/tests/data/scalar_string.sav,sha256=AQ7iZ8dKk9QfnLdP9idKv1ojz0M_SwpL7XAUmbHodDQ,2124 +scipy/io/tests/data/scalar_uint16.sav,sha256=928fmxLsQM83ue4eUS3IEnsLSEzmHBklDA59JAUvGK8,2072 +scipy/io/tests/data/scalar_uint32.sav,sha256=X3RbPhS6_e-u-1S1gMyF7s9ys7oV6ZNwPrJqJ6zIJsk,2072 +scipy/io/tests/data/scalar_uint64.sav,sha256=ffVyS2oKn9PDtWjJdOjSRT2KZzy6Mscgd4u540MPHC4,2076 +scipy/io/tests/data/struct_arrays.sav,sha256=TzH-Gf0JgbP_OgeKYbV8ZbJXvWt1VetdUr6C_ziUlzg,2580 +scipy/io/tests/data/struct_arrays_byte_idl80.sav,sha256=oOmhTnmKlE60-JMJRRMv_zfFs4zqioMN8QA0ldlgQZo,1388 +scipy/io/tests/data/struct_arrays_replicated.sav,sha256=kXU8j9QI2Q8D22DVboH9fwwDQSLVvuWMJl3iIOhUAH8,2936 +scipy/io/tests/data/struct_arrays_replicated_3d.sav,sha256=s3ZUwhT6TfiVfk4AGBSyxYR4FRzo4sZQkTxFCJbIQMI,4608 +scipy/io/tests/data/struct_inherit.sav,sha256=4YajBZcIjqMQ4CI0lRUjXpYDY3rI5vzJJzOYpjWqOJk,2404 +scipy/io/tests/data/struct_pointer_arrays.sav,sha256=fkldO6-RO2uAN_AI9hM6SEaBPrBf8TfiodFGJpViaqg,2408 +scipy/io/tests/data/struct_pointer_arrays_replicated.sav,sha256=eKVerR0LoD9CuNlpwoBcn7BIdj3-8x56VNg--Qn7Hgc,2492 +scipy/io/tests/data/struct_pointer_arrays_replicated_3d.sav,sha256=vsqhGpn3YkZEYjQuI-GoX8Jg5Dv8A2uRtP0kzQkq4lg,2872 +scipy/io/tests/data/struct_pointers.sav,sha256=Zq6d5V9ZijpocxJpimrdFTQG827GADBkMB_-6AweDYI,2268 +scipy/io/tests/data/struct_pointers_replicated.sav,sha256=aIXPBIXTfPmd4IaLpYD5W_HUoIOdL5Y3Hj7WOeRM2sA,2304 +scipy/io/tests/data/struct_pointers_replicated_3d.sav,sha256=t1jhVXmhW6VotQMNZ0fv0sDO2pkN4EutGsx5No4VJQs,2456 +scipy/io/tests/data/struct_scalars.sav,sha256=LYICjERzGJ_VvYgtwJ_Up2svQTv8wBzNcVD3nsd_OPg,2316 +scipy/io/tests/data/struct_scalars_replicated.sav,sha256=lw3fC4kppi6BUWAd4n81h8_KgoUdiJl5UIt3CvJIuBs,2480 +scipy/io/tests/data/struct_scalars_replicated_3d.sav,sha256=xVAup6f1dSV_IsSwBQC3KVs0eLEZ6-o5EaZT9yUoDZI,3240 +scipy/io/tests/data/test-1234Hz-le-1ch-10S-20bit-extra.wav,sha256=h8CXsW5_ShKR197t_d-TUTlgDqOZ-7wK_EcVGucR-aY,74 +scipy/io/tests/data/test-44100Hz-2ch-32bit-float-be.wav,sha256=gjv__ng9xH_sm34hyxCbCgO4AP--PZAfDOArH5omkjM,3586 +scipy/io/tests/data/test-44100Hz-2ch-32bit-float-le.wav,sha256=H0LLyv2lc2guzYGnx4DWXU6vB57JrRX-G9Dd4qGh0hM,3586 +scipy/io/tests/data/test-44100Hz-be-1ch-4bytes.wav,sha256=KKz9SXv_R3gX_AVeED2vyhYnj4BvD1uyDiKpCT3ulZ0,17720 +scipy/io/tests/data/test-44100Hz-le-1ch-4bytes-early-eof-no-data.wav,sha256=YX1g8qdCOAG16vX9G6q4SsfCj2ZVk199jzDQ8S0zWYI,72 +scipy/io/tests/data/test-44100Hz-le-1ch-4bytes-early-eof.wav,sha256=bFrsRqw0QXmsaDtjD6TFP8hZ5jEYMyaCmt-ka_C6GNk,1024 +scipy/io/tests/data/test-44100Hz-le-1ch-4bytes-incomplete-chunk.wav,sha256=zMnhvZvrP4kyOWKVKfbBneyv03xvzgqXYhHNxsAxDJ4,13 +scipy/io/tests/data/test-44100Hz-le-1ch-4bytes-rf64.wav,sha256=GSJpCuezlvHbhP3Cr4jNWmz4zG46XZ6jci2fWtiMN0k,17756 +scipy/io/tests/data/test-44100Hz-le-1ch-4bytes.wav,sha256=9qTCvpgdz3raecVN1ViggHPnQjBf47xmXod9iCDsEik,17720 +scipy/io/tests/data/test-48000Hz-2ch-64bit-float-le-wavex.wav,sha256=EqYBnEgTxTKvaTAtdA5HIl47CCFIje93y4hawR6Pyu0,7792 +scipy/io/tests/data/test-8000Hz-be-3ch-5S-24bit.wav,sha256=hGYchxQFjrtvZCBo0ULi-xdZ8krqXcKdTl3NSUfqe8k,90 +scipy/io/tests/data/test-8000Hz-le-1ch-1byte-ulaw.wav,sha256=BoUCDct3GiY_JJV_HoghF3mzAebT18j02c-MOn19KxU,70 +scipy/io/tests/data/test-8000Hz-le-2ch-1byteu.wav,sha256=R6EJshvQp5YVR4GB9u4Khn5HM1VMfJUj082i8tkBIJ8,1644 +scipy/io/tests/data/test-8000Hz-le-3ch-5S-24bit-inconsistent.wav,sha256=t2Mgri3h6JLQDekrwIhDBOaG46OUzHynUz0pKbvOpNU,90 +scipy/io/tests/data/test-8000Hz-le-3ch-5S-24bit-rf64.wav,sha256=iSGyqouX53NaEB33tzKXa11NRIY97GG40_pqWF_k5LQ,126 +scipy/io/tests/data/test-8000Hz-le-3ch-5S-24bit.wav,sha256=yCv0uh-ux_skJsxeOjzog0YBk3ZQO_kw5HJHMqtVyI0,90 +scipy/io/tests/data/test-8000Hz-le-3ch-5S-36bit.wav,sha256=oiMVsQV9-qGBz_ZwsfAkgA9BZXNjXbH4zxCGvvdT0RY,120 +scipy/io/tests/data/test-8000Hz-le-3ch-5S-45bit.wav,sha256=e97XoPrPGJDIh8nO6mii__ViY5yVlmt4OnPQoDN1djs,134 +scipy/io/tests/data/test-8000Hz-le-3ch-5S-53bit.wav,sha256=wbonKlzvzQ_bQYyBsj-GwnihZOhn0uxfKhL_nENCGNc,150 +scipy/io/tests/data/test-8000Hz-le-3ch-5S-64bit.wav,sha256=Uu5QPQcbtnFlnxOd4zFGxpiTC4wgdp6JOoYJ2VMZIU0,164 +scipy/io/tests/data/test-8000Hz-le-4ch-9S-12bit.wav,sha256=1F67h8tr2xz0C5K21T9y9gspcGA0qnSOzsl2vjArAMs,116 +scipy/io/tests/data/test-8000Hz-le-5ch-9S-5bit.wav,sha256=TJvGU7GpgXdCrdrjzMlDtpieDMnDK-lWMMqlWjT23BY,89 +scipy/io/tests/data/various_compressed.sav,sha256=H-7pc-RCQx5y6_IbHk1hB6OfnhvuPyW6EJq4EwI9iMc,1015 +scipy/io/tests/test_fortran.py,sha256=xpwrE71XkNWFhwNR6Bwvi9YXTZsCkAxArG5xQDRsBTo,11363 +scipy/io/tests/test_idl.py,sha256=5qG7ij-xqTD81L74_TFe8snzbY2TTNJorE0eCwwc5mA,21260 +scipy/io/tests/test_mmio.py,sha256=4tnOue-W8Ks5ICfjoQ-tf9-P1LiPoFqxjcU98zlPF-o,30039 +scipy/io/tests/test_netcdf.py,sha256=fysmPr7naoLElXIWXaLhICAyCIz3len7iSb5FUFgO2c,19547 +scipy/io/tests/test_paths.py,sha256=mRMsZvtmZ9FPW9auPG8yD1gxz6NQS48rP_qG00Ojgrg,3278 +scipy/io/tests/test_wavfile.py,sha256=f-6mROqdwJl0GWwvEPSdir0n5E70HGX-5XGNccXBu3U,18775 +scipy/io/wavfile.py,sha256=esidloBvKQmt5pWBHJv5oTPSsdhrwETqZF6_KOeyrHg,30685 +scipy/linalg/__init__.pxd,sha256=0MlO-o_Kr8gg--_ipXEHFGtB8pZdHX8VX4wLYe_UzPg,53 +scipy/linalg/__init__.py,sha256=iEBmLSZ-jQZQMSVgjGMz5fUAU3UPHiwnO2PlpsC3nBw,7426 +scipy/linalg/__pycache__/__init__.cpython-312.pyc,, +scipy/linalg/__pycache__/_basic.cpython-312.pyc,, +scipy/linalg/__pycache__/_decomp.cpython-312.pyc,, +scipy/linalg/__pycache__/_decomp_cholesky.cpython-312.pyc,, +scipy/linalg/__pycache__/_decomp_cossin.cpython-312.pyc,, +scipy/linalg/__pycache__/_decomp_ldl.cpython-312.pyc,, +scipy/linalg/__pycache__/_decomp_lu.cpython-312.pyc,, +scipy/linalg/__pycache__/_decomp_polar.cpython-312.pyc,, +scipy/linalg/__pycache__/_decomp_qr.cpython-312.pyc,, +scipy/linalg/__pycache__/_decomp_qz.cpython-312.pyc,, +scipy/linalg/__pycache__/_decomp_schur.cpython-312.pyc,, +scipy/linalg/__pycache__/_decomp_svd.cpython-312.pyc,, +scipy/linalg/__pycache__/_expm_frechet.cpython-312.pyc,, +scipy/linalg/__pycache__/_matfuncs.cpython-312.pyc,, +scipy/linalg/__pycache__/_matfuncs_inv_ssq.cpython-312.pyc,, +scipy/linalg/__pycache__/_matfuncs_sqrtm.cpython-312.pyc,, +scipy/linalg/__pycache__/_misc.cpython-312.pyc,, +scipy/linalg/__pycache__/_procrustes.cpython-312.pyc,, +scipy/linalg/__pycache__/_sketches.cpython-312.pyc,, +scipy/linalg/__pycache__/_solvers.cpython-312.pyc,, +scipy/linalg/__pycache__/_special_matrices.cpython-312.pyc,, +scipy/linalg/__pycache__/_testutils.cpython-312.pyc,, +scipy/linalg/__pycache__/basic.cpython-312.pyc,, +scipy/linalg/__pycache__/blas.cpython-312.pyc,, +scipy/linalg/__pycache__/decomp.cpython-312.pyc,, +scipy/linalg/__pycache__/decomp_cholesky.cpython-312.pyc,, +scipy/linalg/__pycache__/decomp_lu.cpython-312.pyc,, +scipy/linalg/__pycache__/decomp_qr.cpython-312.pyc,, +scipy/linalg/__pycache__/decomp_schur.cpython-312.pyc,, +scipy/linalg/__pycache__/decomp_svd.cpython-312.pyc,, +scipy/linalg/__pycache__/interpolative.cpython-312.pyc,, +scipy/linalg/__pycache__/lapack.cpython-312.pyc,, +scipy/linalg/__pycache__/matfuncs.cpython-312.pyc,, +scipy/linalg/__pycache__/misc.cpython-312.pyc,, +scipy/linalg/__pycache__/special_matrices.cpython-312.pyc,, +scipy/linalg/_basic.py,sha256=Lx_UpvkHRGlWVbLGPFoi9OM3mUTlt7tadYkVGGDSzXs,76094 +scipy/linalg/_batched_linalg.cpython-312-x86_64-linux-gnu.so,sha256=Ml3kYladussVSD3dvJSlj3aHGwHtGz_JCJSxLhmlKKk,360441 +scipy/linalg/_cythonized_array_utils.cpython-312-x86_64-linux-gnu.so,sha256=cdwLlSTaPwtOMckqmMuw1Rxkj-pZP9QuZ0kjpla5X4s,225472 +scipy/linalg/_cythonized_array_utils.pxd,sha256=HsWBCsjpfGiHhi0jpLyGCBXdaNocztmK8MVQg5tHyOw,814 +scipy/linalg/_cythonized_array_utils.pyi,sha256=YXeC_-p_wGD-O7N_9ucltq8C6fDfveiJHTTmBN-2_8E,284 +scipy/linalg/_decomp.py,sha256=Tb-BMFd35L_X5t-DTkEaqHUbcVp9lXxvQrcICMsLai8,65295 +scipy/linalg/_decomp_cholesky.py,sha256=gEnerWv0CeTg1FzvucPQ4Fl0_G34w_c110z5fEioP5E,16753 +scipy/linalg/_decomp_cossin.py,sha256=SP7prnWk9eqL4q2bXiBgHILa-21SDaJNCCk_hIw9Xcc,9605 +scipy/linalg/_decomp_interpolative.cpython-312-x86_64-linux-gnu.so,sha256=IvfS9FIhMmFGj7w2YOWbgET5RsJqY0g-PNY_MwX7XGE,739872 +scipy/linalg/_decomp_ldl.py,sha256=VZ1KdnXQvDhCHeEVEG9izhV5-3YcEKxz5SbE2tLUDa0,12670 +scipy/linalg/_decomp_lu.py,sha256=cKKlQNadi4ezBGhdlKzv1OCDXg2qZKTB2iEdM-bupBk,12505 +scipy/linalg/_decomp_polar.py,sha256=RRNkQ7Vk6tEtvA6f1wTkP1t-FXQ_COU9lXguzbNpaK0,3654 +scipy/linalg/_decomp_qr.py,sha256=1F8Tgyg4EJvARym8UJAWsoR2sJnNatGH1vHrCT5YVlk,16542 +scipy/linalg/_decomp_qz.py,sha256=-hjeE2BI1U3_rOWI1dPif5MUehMY7kFvVMMMfMhADz4,16656 +scipy/linalg/_decomp_schur.py,sha256=mfJnE_ubJeWt90Ve2r8uxzW_fCTQ0g__wSx2Jn3Snu4,12230 +scipy/linalg/_decomp_svd.py,sha256=e9tI_h3tiKcX5B8Naw0yR-IMwSnAaG64no3ohyD_kWo,19237 +scipy/linalg/_decomp_update.cpython-312-x86_64-linux-gnu.so,sha256=6rcW-6VUebaODHfr1jiu1cTL18e9k5TO-KiGgVwO1aI,328872 +scipy/linalg/_expm_frechet.py,sha256=-jLD09atd1A-AJ_3mVek0Z1PcFIwz6ftqtgwLfl_Ttg,12416 +scipy/linalg/_fblas.cpython-312-x86_64-linux-gnu.so,sha256=hmmbmWXaWBq0gdh-VVHbNkiFPoRAY66U8KGNQkQLZcE,1032665 +scipy/linalg/_flapack.cpython-312-x86_64-linux-gnu.so,sha256=uc6Vu7MUdoX7xA0hGiN8QsXSWU4up2lAHMKz2KYdBV0,2568753 +scipy/linalg/_internal_matfuncs.cpython-312-x86_64-linux-gnu.so,sha256=tExmBS9BC6qV5GxhyBKBMiJukqR_BQ6OabUMW8h0D9E,182105 +scipy/linalg/_linalg_pythran.cpython-312-x86_64-linux-gnu.so,sha256=jwWFlnNWy0uxCEtrV13Mz25z7PIVewWau_io5XAMGZ0,122792 +scipy/linalg/_matfuncs.py,sha256=hiDDN1Qvi4s2sH05lnnvUgGUh2ReciZBOicGgDv7Nw8,26525 +scipy/linalg/_matfuncs_inv_ssq.py,sha256=oG2yRk1HNRZ9V4pkFcaqWDRO-oarXKUsKajWJAn1Gqg,28086 +scipy/linalg/_matfuncs_sqrtm.py,sha256=UmDqzEXAUQFg_YDRlNdPWtQlaBlu3kxCedjCs-_0y7k,3384 +scipy/linalg/_matfuncs_sqrtm_triu.cpython-312-x86_64-linux-gnu.so,sha256=JRrGfdB4HWIdCAisyzwwxZHblhHepGvh-ms53uA6FYs,105952 +scipy/linalg/_misc.py,sha256=sUup-Nf-szZxttQEJTk2n9Dgim6mePYEvBaCu1nE0uo,9448 +scipy/linalg/_procrustes.py,sha256=gipMvbQdlKU8HBAExamtJqFRyUtufO63hYPdi9Jwryc,3859 +scipy/linalg/_sketches.py,sha256=CPXVk2bMbr8fM7z3lyv6TQbPzByh6m0EiG0YdQsYLbw,7198 +scipy/linalg/_solve_toeplitz.cpython-312-x86_64-linux-gnu.so,sha256=vqREts1pWTxirM5nl-J9gsFTMDcVrmn_0AZQTguV8ok,126984 +scipy/linalg/_solvers.py,sha256=tJbmme3v9E5YSayiBNqnr99vCzvsDmP-flzq0V8LzCk,30122 +scipy/linalg/_special_matrices.py,sha256=aftE-FnTH50KGKfUoCY0zV3mNmkPMFsm0drZcgw73l8,41290 +scipy/linalg/_testutils.py,sha256=dS3HB1YLDnvxngEsArerM4qaa27Daab689oJBbkaLew,1943 +scipy/linalg/basic.py,sha256=AuNvDlH8mnAJScycj4mV-Iq1M0bXxidpY4Vud_lRJlM,753 +scipy/linalg/blas.py,sha256=R1qk0Gj2NC8reZzKPoqOEsAJ5kfyG0yoJrZo8D-w-RE,12934 +scipy/linalg/cython_blas.cpython-312-x86_64-linux-gnu.so,sha256=Ceyt1YC5HvLZKCyAIFuALEtL-pqfYQh2dzyViF_xgXw,176769 +scipy/linalg/cython_blas.pxd,sha256=lHT5uEJGolpEaJKRe5vZC5DNLoxfTh-jZDgby_uuqyo,15639 +scipy/linalg/cython_lapack.cpython-312-x86_64-linux-gnu.so,sha256=trWSyEz1pDT04G_8yydrTAoIqSF52seWl9ymeG09ZYM,580673 +scipy/linalg/cython_lapack.pxd,sha256=w-PLgiSFFlxp8pQ5NlxKV0O8Tyb2ToBJacBrhfscXlk,204595 +scipy/linalg/decomp.py,sha256=w9HTI1OxXpX_rL72qcmykc5dUWal7lTlAU8k-9Eq7Dg,708 +scipy/linalg/decomp_cholesky.py,sha256=1g45oc115ZZR3CfMW1bCPseF5ATz4Xf6Ih26NRqyjfs,649 +scipy/linalg/decomp_lu.py,sha256=FPo9NHe9wg1FhCaoVV1_4mdfNj0S4plT4dHr4vMl1U8,593 +scipy/linalg/decomp_qr.py,sha256=EJNpu6lSa36Eo-e4rbYu5kDlRTMse2mmGul_PLRFXHs,567 +scipy/linalg/decomp_schur.py,sha256=vkVK3y-055523Q__ptxVNatDebPBE1HD-DFBe7kEh3w,602 +scipy/linalg/decomp_svd.py,sha256=HrJqbmgde7d7EWxCsa9XkS9QuWgPYMFOHiF4NcAL_Qg,631 +scipy/linalg/interpolative.py,sha256=iukUVTQJL-Oep9j4B-5hhpdR9u_xjli9R4QFJwU3lRg,31169 +scipy/linalg/lapack.py,sha256=hgHxhQcv1M2Y-1FsIDWlD0mEHiZTU6-0ZvWYjvIEKCc,19658 +scipy/linalg/matfuncs.py,sha256=vYw39D2LukCRCFJpx0qx8tgHlRZEDZI2wZfZwhh-Ubo,744 +scipy/linalg/misc.py,sha256=uxpR80jJ5w5mslplWlL6tIathas8mEXvRIwDXYMcTOk,592 +scipy/linalg/special_matrices.py,sha256=FGEXV_qOxy6ZrAsKUwHiDlaSRsvYFaUgkMN1YSEetCo,749 +scipy/linalg/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +scipy/linalg/tests/__pycache__/__init__.cpython-312.pyc,, +scipy/linalg/tests/__pycache__/test_basic.cpython-312.pyc,, +scipy/linalg/tests/__pycache__/test_batch.cpython-312.pyc,, +scipy/linalg/tests/__pycache__/test_blas.cpython-312.pyc,, +scipy/linalg/tests/__pycache__/test_cython_abi.cpython-312.pyc,, +scipy/linalg/tests/__pycache__/test_cython_blas.cpython-312.pyc,, +scipy/linalg/tests/__pycache__/test_cython_lapack.cpython-312.pyc,, +scipy/linalg/tests/__pycache__/test_cythonized_array_utils.cpython-312.pyc,, +scipy/linalg/tests/__pycache__/test_decomp.cpython-312.pyc,, +scipy/linalg/tests/__pycache__/test_decomp_cholesky.cpython-312.pyc,, +scipy/linalg/tests/__pycache__/test_decomp_cossin.cpython-312.pyc,, +scipy/linalg/tests/__pycache__/test_decomp_ldl.cpython-312.pyc,, +scipy/linalg/tests/__pycache__/test_decomp_lu.cpython-312.pyc,, +scipy/linalg/tests/__pycache__/test_decomp_polar.cpython-312.pyc,, +scipy/linalg/tests/__pycache__/test_decomp_update.cpython-312.pyc,, +scipy/linalg/tests/__pycache__/test_deprecations.cpython-312.pyc,, +scipy/linalg/tests/__pycache__/test_extending.cpython-312.pyc,, +scipy/linalg/tests/__pycache__/test_fblas.cpython-312.pyc,, +scipy/linalg/tests/__pycache__/test_interpolative.cpython-312.pyc,, +scipy/linalg/tests/__pycache__/test_lapack.cpython-312.pyc,, +scipy/linalg/tests/__pycache__/test_matfuncs.cpython-312.pyc,, +scipy/linalg/tests/__pycache__/test_matmul_toeplitz.cpython-312.pyc,, +scipy/linalg/tests/__pycache__/test_procrustes.cpython-312.pyc,, +scipy/linalg/tests/__pycache__/test_sketches.cpython-312.pyc,, +scipy/linalg/tests/__pycache__/test_solve_toeplitz.cpython-312.pyc,, +scipy/linalg/tests/__pycache__/test_solvers.cpython-312.pyc,, +scipy/linalg/tests/__pycache__/test_special_matrices.cpython-312.pyc,, +scipy/linalg/tests/_cython_examples/extending.pyx,sha256=q3-cKBgOhQixv5HcC1D2MGcvPWIGdykme8xedUzlYzg,1054 +scipy/linalg/tests/_cython_examples/meson.build,sha256=UArjdmuxF58eN7lO2InHsEuWs6KhsNXRoGg7iqNzOUc,846 +scipy/linalg/tests/cython_abi_signatures.json,sha256=i3ysM9hO3r5O1g3wCoS2OlYSZZ__SuYhSEIQ0ABDYjM,392146 +scipy/linalg/tests/data/carex_15_data.npz,sha256=E_PhSRqHa79Z1-oQrSnB-bWZaiq5khbzHVv81lkBLB4,34462 +scipy/linalg/tests/data/carex_18_data.npz,sha256=Wfg5Rn8nUrffb7bUCUOW7dMqWSm3ZPf_oeZmZDHmysY,161487 +scipy/linalg/tests/data/carex_19_data.npz,sha256=OOj8ewQd8LI9flyhXq0aBl5kZ2Ee-ahIzH25P4Ct_Yc,34050 +scipy/linalg/tests/data/carex_20_data.npz,sha256=FOIi00pxGMcoShZ1xv7O7ne4TflRpca6Kl7p_zBU-h0,31231 +scipy/linalg/tests/data/carex_6_data.npz,sha256=GyoHNrVB6_XEubTADW2rKB5zyfuZE8biWBp4Gze2Avk,15878 +scipy/linalg/tests/data/gendare_20170120_data.npz,sha256=o9-rRR2dXCAkPg7YXNi2yWV2afuaD4O1vhZVhXg9VbU,2164 +scipy/linalg/tests/test_basic.py,sha256=0hg9YArihR5JDIR3z7sTJtINtK-DH8_JoHSh6w1cNrI,115709 +scipy/linalg/tests/test_batch.py,sha256=OxF5Di4sd76kpOHnmwgBqLnSdK6dB8TRvO25yG2WuGs,32743 +scipy/linalg/tests/test_blas.py,sha256=YL3f7fttqaaP1S7HHqfcOWzJtXCT7yPwA2QsCCAUG7M,40878 +scipy/linalg/tests/test_cython_abi.py,sha256=putHhhYUdqurd9aPeMfflgHgeBvTQIl0fnkpRlyxtas,4044 +scipy/linalg/tests/test_cython_blas.py,sha256=5XkzKJmi_Xl5I_I8vVOnOpK3xwvUN_7CbHyGEbIHYh4,5279 +scipy/linalg/tests/test_cython_lapack.py,sha256=3QrWXYHIFdwwG1Tcomg8d8nXK0LhsnzPMcEH27AYoRU,2147 +scipy/linalg/tests/test_cythonized_array_utils.py,sha256=pngKlFV8isa02yRq5wn_ogh-DO2jHt5YroKIc7p0AMg,5136 +scipy/linalg/tests/test_decomp.py,sha256=7pmdaDkSRmTkp1C6VyUqZ1hd1QwJLZ7DDDaWzptKZbI,133495 +scipy/linalg/tests/test_decomp_cholesky.py,sha256=soFlzYlIh3-abSYL4ki1jUhaoTwrNDED3zbJWKbIIng,17020 +scipy/linalg/tests/test_decomp_cossin.py,sha256=b10EQSJzYwGwgEHj_s71tDPr59e3lZmid7M8G9qgv3A,12560 +scipy/linalg/tests/test_decomp_ldl.py,sha256=n9ETZLP6IRVaVt0c5u8aZF-sPpAMTD2EQ4W-za9SNdM,5034 +scipy/linalg/tests/test_decomp_lu.py,sha256=UozTtrIW7VvcMnxC95MfBityqlQ-mvT3Uy-jZsfeP30,12760 +scipy/linalg/tests/test_decomp_polar.py,sha256=fGKl3Skqz6IpHBeFcq6bdqvS8M53rXx2Wh6Kx4f5T3Y,3287 +scipy/linalg/tests/test_decomp_update.py,sha256=oFfZdPAClVvjojpX7PLDYbyUk48F1LqtQth13MXPVBY,68624 +scipy/linalg/tests/test_deprecations.py,sha256=pkEFrJA4MQUhp8v8T_OIT7EKSHqk5458Q7U5KLjbx64,6974 +scipy/linalg/tests/test_extending.py,sha256=hYlyrwP7APDF9IReMqG8-DLvN8-514t6GZrK9aDbM4g,2232 +scipy/linalg/tests/test_fblas.py,sha256=tQIATc6zdgKRMx-dIjJUb2HWDKljrn6yKzryhn0hPq8,18926 +scipy/linalg/tests/test_interpolative.py,sha256=8_OQau0olSNQWi6RUasHgHasQvvvaMjJ7JUl0jh1Lqw,8616 +scipy/linalg/tests/test_lapack.py,sha256=E_rcbMEBKuIzgLY5mHuXA5ROPSk5ClFDcvs2oJzo9eA,138785 +scipy/linalg/tests/test_matfuncs.py,sha256=T6utXKmncHc-iikdDQui1eog6J9UNbghW-vPoF8BSrk,44109 +scipy/linalg/tests/test_matmul_toeplitz.py,sha256=73Qe51lCXEWZGpxk8GYv0owDSlN0IpnLJPlI0nsCdhY,4088 +scipy/linalg/tests/test_procrustes.py,sha256=EW3XeVvPK0uiYEmjuHh8LmwZKhgSaEO7DWhAsH7gnwM,9058 +scipy/linalg/tests/test_sketches.py,sha256=uHiyvqMxZAR5rI_bvh1LbesYvzcu0PnbwNNItpRxGZw,3946 +scipy/linalg/tests/test_solve_toeplitz.py,sha256=UpF5Cem6wYj8Iiu2ooFTkDI9Ozuofa5dx2J6KOsciCw,4579 +scipy/linalg/tests/test_solvers.py,sha256=UtnNel7q2LKHp5pb6z6r4k4AapgpmpplsT9rrJtMIwk,34610 +scipy/linalg/tests/test_special_matrices.py,sha256=tjAGuoTz2Bgo6S8pjsfxEv4uGg2Yaxr7ZdC4x1eEkHQ,24907 +scipy/misc/__init__.py,sha256=dVfULY959nFwpl5NCxyCpiHyNcSNaR7HYOg7QU21a5s,135 +scipy/misc/__pycache__/__init__.cpython-312.pyc,, +scipy/misc/__pycache__/common.cpython-312.pyc,, +scipy/misc/__pycache__/doccer.cpython-312.pyc,, +scipy/misc/common.py,sha256=nAGQOVR9ZEAb703uhOVQZqf-z0iCM4EDhbHK4_h_Tdc,142 +scipy/misc/doccer.py,sha256=wHbpGV8todadz6MIzJHalDfRjiKI164qs6iMcHgsVu0,142 +scipy/ndimage/__init__.py,sha256=FSB4JTZ0mARA8-Ht9x8n9aCG_M-SssLTien-uoDZeOE,5207 +scipy/ndimage/__pycache__/__init__.cpython-312.pyc,, +scipy/ndimage/__pycache__/_delegators.cpython-312.pyc,, +scipy/ndimage/__pycache__/_filters.cpython-312.pyc,, +scipy/ndimage/__pycache__/_fourier.cpython-312.pyc,, +scipy/ndimage/__pycache__/_interpolation.cpython-312.pyc,, +scipy/ndimage/__pycache__/_measurements.cpython-312.pyc,, +scipy/ndimage/__pycache__/_morphology.cpython-312.pyc,, +scipy/ndimage/__pycache__/_ndimage_api.cpython-312.pyc,, +scipy/ndimage/__pycache__/_ni_docstrings.cpython-312.pyc,, +scipy/ndimage/__pycache__/_ni_support.cpython-312.pyc,, +scipy/ndimage/__pycache__/_support_alternative_backends.cpython-312.pyc,, +scipy/ndimage/__pycache__/filters.cpython-312.pyc,, +scipy/ndimage/__pycache__/fourier.cpython-312.pyc,, +scipy/ndimage/__pycache__/interpolation.cpython-312.pyc,, +scipy/ndimage/__pycache__/measurements.cpython-312.pyc,, +scipy/ndimage/__pycache__/morphology.cpython-312.pyc,, +scipy/ndimage/_ctest.cpython-312-x86_64-linux-gnu.so,sha256=CLkzX8SlRILMZscyedWeOoQXb06cBwzjy0e8cwYXr8I,16968 +scipy/ndimage/_cytest.cpython-312-x86_64-linux-gnu.so,sha256=BaiBMKsiO0kK1S-KlhgruSq59Lw2egYGnkn76DGU3PQ,93904 +scipy/ndimage/_delegators.py,sha256=EI2Xsmw6GDL8MnLeQYZ6uK9dVkMv01WOd1fZTLS_BrU,9410 +scipy/ndimage/_filters.py,sha256=uQuA2A0DiFmWaq0bd1Qx_sFvTUW3kVDrjKOF12lYQiM,93373 +scipy/ndimage/_fourier.py,sha256=SoAYRx7ax7Tv51MyYzDlZ3fN682x4T6N8yReX2La4-I,11266 +scipy/ndimage/_interpolation.py,sha256=DoNPO5bd-ArZbzDs07VJn8_T4-BJZ04poIwcS13sgZA,37697 +scipy/ndimage/_measurements.py,sha256=1OsmAoObxinTHf1boIo4Y4jEZ1kJs65DAxMTTjQTWRE,56274 +scipy/ndimage/_morphology.py,sha256=U2HCiXc0ejvEuhJArqCzQacqgDSYpWAFBsmWSmHuua0,100962 +scipy/ndimage/_nd_image.cpython-312-x86_64-linux-gnu.so,sha256=zdHoPiJYzv1ZJ91FLnl6VVkZzal7e9ozfKhnGJbtXBA,175576 +scipy/ndimage/_ndimage_api.py,sha256=S8DBRWydSRfAz-ZlHSMeCSbjYGgCLioa9_Q2VXGeC_g,586 +scipy/ndimage/_ni_docstrings.py,sha256=SggF1WdoDXDGApPF75g78f2Qc3nHrWqp6k9St4S0Yn0,9046 +scipy/ndimage/_ni_label.cpython-312-x86_64-linux-gnu.so,sha256=wth4SkY-GIll-dl7LT4c-2_-n8ENdQpnXQdcjp_meBk,223936 +scipy/ndimage/_ni_support.py,sha256=1JpV6XSyvMP6-rqKHHEBrc83RaVkDzynBHlO672sczY,5216 +scipy/ndimage/_rank_filter_1d.cpython-312-x86_64-linux-gnu.so,sha256=2OT7E6kgPLFcgZs31aOnQHaj_rhPkLtrlNWCkifbG1A,27048 +scipy/ndimage/_support_alternative_backends.py,sha256=f4T2ROTmqyAGJd0L2qLyNgrJt74vYRBXPw-zyvUHt0k,4241 +scipy/ndimage/filters.py,sha256=cAv2zezrTJEm9JzKPV_pmXzZcgczCK_VaYJ4mdNW3FM,976 +scipy/ndimage/fourier.py,sha256=gnifi4S_Epyu4DpNsebz4A5BKzBWoGf11FkXWeXsoqY,599 +scipy/ndimage/interpolation.py,sha256=GHYvxCyQsLfKtNUc8AUN_vqmBhmAPwNnxm2-VpFMayk,664 +scipy/ndimage/measurements.py,sha256=xdSs52Y5RjURLP710iGURXWQFeS3ok4WjoYufKh9OeA,788 +scipy/ndimage/morphology.py,sha256=yFWSo7o_7PuYq61WGQOCIgMppneNLxqhJocyN0bMsVA,965 +scipy/ndimage/tests/__init__.py,sha256=GbIXCsLtZxgmuisjxfFsd3pj6-RQhmauc6AVy6sybDc,314 +scipy/ndimage/tests/__pycache__/__init__.cpython-312.pyc,, +scipy/ndimage/tests/__pycache__/test_c_api.cpython-312.pyc,, +scipy/ndimage/tests/__pycache__/test_datatypes.cpython-312.pyc,, +scipy/ndimage/tests/__pycache__/test_filters.cpython-312.pyc,, +scipy/ndimage/tests/__pycache__/test_fourier.cpython-312.pyc,, +scipy/ndimage/tests/__pycache__/test_interpolation.cpython-312.pyc,, +scipy/ndimage/tests/__pycache__/test_measurements.cpython-312.pyc,, +scipy/ndimage/tests/__pycache__/test_morphology.cpython-312.pyc,, +scipy/ndimage/tests/__pycache__/test_ni_support.cpython-312.pyc,, +scipy/ndimage/tests/__pycache__/test_splines.cpython-312.pyc,, +scipy/ndimage/tests/data/label_inputs.txt,sha256=JPbEnncwUyhlAAv6grN8ysQW9w9M7ZSIn_NPopqU7z4,294 +scipy/ndimage/tests/data/label_results.txt,sha256=Cf2_l7FCWNjIkyi-XU1MaGzmLnf2J7NK2SZ_10O-8d0,4309 +scipy/ndimage/tests/data/label_strels.txt,sha256=AU2FUAg0WghfvnPDW6lhMB1kpNdfv3coCR8blcRNBJ8,252 +scipy/ndimage/tests/dots.png,sha256=sgtW-tx0ccBpTT6BSNniioPXlnusFr-IUglK_qOVBBQ,2114 +scipy/ndimage/tests/test_c_api.py,sha256=7Gv-hR91MWpiGQ32yjXIBjFytuaYLqz3wYiCXcC8ZSk,3738 +scipy/ndimage/tests/test_datatypes.py,sha256=TYMiGyBcdOq3KVLzvjZPjerD1EXonyHFQYBLTWDwN7o,2819 +scipy/ndimage/tests/test_filters.py,sha256=esqGbgEciAVf5q5rSz54DC1cgBXL1t5pqV-vfuZ94Bg,145479 +scipy/ndimage/tests/test_fourier.py,sha256=4e0ngc3Ul2JiLzyRzjW-BJuSX5AF0J87gsSXy5tSulQ,8080 +scipy/ndimage/tests/test_interpolation.py,sha256=Y62CvuowBeOlTGSMlTUPILPuTbRrd-m0Fw5lwLqUImM,62767 +scipy/ndimage/tests/test_measurements.py,sha256=25fLh5j9TfT-hBe9WWtY6WU1EHr0avVUO6BK7AFmA5o,62496 +scipy/ndimage/tests/test_morphology.py,sha256=boWM2orKOVLVVLndM7-DScSD4JYlYdmKnlabgWAYm1c,138149 +scipy/ndimage/tests/test_ni_support.py,sha256=5ZBJivg8-l-OFx02ZCLWEBK56Bdmu0AUqUmWjiPqAMQ,2484 +scipy/ndimage/tests/test_splines.py,sha256=m3GojI8S3nb4bAz1vit3w_zxbYoNzu_nFvFzwMlNY4w,3240 +scipy/odr/__init__.py,sha256=cb97lL9npGTTE1ZufLQTVm74uRUEwMiLu5G_GrXPjRA,6214 +scipy/odr/__odrpack.cpython-312-x86_64-linux-gnu.so,sha256=5jnjcaALSHx8JgUG7STdPfNtm9CbsIQA8fV_15HShg8,630745 +scipy/odr/__pycache__/__init__.cpython-312.pyc,, +scipy/odr/__pycache__/_add_newdocs.cpython-312.pyc,, +scipy/odr/__pycache__/_models.cpython-312.pyc,, +scipy/odr/__pycache__/_odrpack.cpython-312.pyc,, +scipy/odr/__pycache__/models.cpython-312.pyc,, +scipy/odr/__pycache__/odrpack.cpython-312.pyc,, +scipy/odr/_add_newdocs.py,sha256=kviOIY4HPMd6ninCtC82smc4D9fytrYvRwy8AOjl9c0,1328 +scipy/odr/_models.py,sha256=yMrVGgudQf4uBL54wTYNQ8JJprp7WCTOHn9nu14zMl8,8781 +scipy/odr/_odrpack.py,sha256=zxSu2JEa1EUorVSxUxiQ0gj71qIhv98VgR_8eWpfyww,44307 +scipy/odr/models.py,sha256=awdUPgiJ8slzPMzDITmNMNNyIoKsJ8elNryFE3jyf6o,785 +scipy/odr/odrpack.py,sha256=LvvD6GKspFMi-uwlRoBek5DESYWXGVNpJ3o0qs58HGc,828 +scipy/odr/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +scipy/odr/tests/__pycache__/__init__.cpython-312.pyc,, +scipy/odr/tests/__pycache__/test_odr.cpython-312.pyc,, +scipy/odr/tests/test_odr.py,sha256=X51l1F2Fb3KrqvCM3qmNRBHKw3mwALw-Z02ug43u3wU,22478 +scipy/optimize/__init__.pxd,sha256=kFYBK9tveJXql1KXuOkKGvj4Fu67GmuyRP5kMVkMbyk,39 +scipy/optimize/__init__.py,sha256=7ZzePqFF1X1377f_s3dpVdeg51I3YwManuh8Pl4M1mE,13279 +scipy/optimize/__pycache__/__init__.cpython-312.pyc,, +scipy/optimize/__pycache__/_basinhopping.cpython-312.pyc,, +scipy/optimize/__pycache__/_bracket.cpython-312.pyc,, +scipy/optimize/__pycache__/_chandrupatla.cpython-312.pyc,, +scipy/optimize/__pycache__/_cobyla_py.cpython-312.pyc,, +scipy/optimize/__pycache__/_cobyqa_py.cpython-312.pyc,, +scipy/optimize/__pycache__/_constraints.cpython-312.pyc,, +scipy/optimize/__pycache__/_dcsrch.cpython-312.pyc,, +scipy/optimize/__pycache__/_differentiable_functions.cpython-312.pyc,, +scipy/optimize/__pycache__/_differentialevolution.cpython-312.pyc,, +scipy/optimize/__pycache__/_direct_py.cpython-312.pyc,, +scipy/optimize/__pycache__/_dual_annealing.cpython-312.pyc,, +scipy/optimize/__pycache__/_elementwise.cpython-312.pyc,, +scipy/optimize/__pycache__/_hessian_update_strategy.cpython-312.pyc,, +scipy/optimize/__pycache__/_isotonic.cpython-312.pyc,, +scipy/optimize/__pycache__/_lbfgsb_py.cpython-312.pyc,, +scipy/optimize/__pycache__/_linesearch.cpython-312.pyc,, +scipy/optimize/__pycache__/_linprog.cpython-312.pyc,, +scipy/optimize/__pycache__/_linprog_doc.cpython-312.pyc,, +scipy/optimize/__pycache__/_linprog_highs.cpython-312.pyc,, +scipy/optimize/__pycache__/_linprog_ip.cpython-312.pyc,, +scipy/optimize/__pycache__/_linprog_rs.cpython-312.pyc,, +scipy/optimize/__pycache__/_linprog_simplex.cpython-312.pyc,, +scipy/optimize/__pycache__/_linprog_util.cpython-312.pyc,, +scipy/optimize/__pycache__/_milp.cpython-312.pyc,, +scipy/optimize/__pycache__/_minimize.cpython-312.pyc,, +scipy/optimize/__pycache__/_minpack_py.cpython-312.pyc,, +scipy/optimize/__pycache__/_nnls.cpython-312.pyc,, +scipy/optimize/__pycache__/_nonlin.cpython-312.pyc,, +scipy/optimize/__pycache__/_numdiff.cpython-312.pyc,, +scipy/optimize/__pycache__/_optimize.cpython-312.pyc,, +scipy/optimize/__pycache__/_qap.cpython-312.pyc,, +scipy/optimize/__pycache__/_remove_redundancy.cpython-312.pyc,, +scipy/optimize/__pycache__/_root.cpython-312.pyc,, +scipy/optimize/__pycache__/_root_scalar.cpython-312.pyc,, +scipy/optimize/__pycache__/_shgo.cpython-312.pyc,, +scipy/optimize/__pycache__/_slsqp_py.cpython-312.pyc,, +scipy/optimize/__pycache__/_spectral.cpython-312.pyc,, +scipy/optimize/__pycache__/_tnc.cpython-312.pyc,, +scipy/optimize/__pycache__/_trustregion.cpython-312.pyc,, +scipy/optimize/__pycache__/_trustregion_dogleg.cpython-312.pyc,, +scipy/optimize/__pycache__/_trustregion_exact.cpython-312.pyc,, +scipy/optimize/__pycache__/_trustregion_krylov.cpython-312.pyc,, +scipy/optimize/__pycache__/_trustregion_ncg.cpython-312.pyc,, +scipy/optimize/__pycache__/_tstutils.cpython-312.pyc,, +scipy/optimize/__pycache__/_zeros_py.cpython-312.pyc,, +scipy/optimize/__pycache__/cobyla.cpython-312.pyc,, +scipy/optimize/__pycache__/elementwise.cpython-312.pyc,, +scipy/optimize/__pycache__/lbfgsb.cpython-312.pyc,, +scipy/optimize/__pycache__/linesearch.cpython-312.pyc,, +scipy/optimize/__pycache__/minpack.cpython-312.pyc,, +scipy/optimize/__pycache__/minpack2.cpython-312.pyc,, +scipy/optimize/__pycache__/moduleTNC.cpython-312.pyc,, +scipy/optimize/__pycache__/nonlin.cpython-312.pyc,, +scipy/optimize/__pycache__/optimize.cpython-312.pyc,, +scipy/optimize/__pycache__/slsqp.cpython-312.pyc,, +scipy/optimize/__pycache__/tnc.cpython-312.pyc,, +scipy/optimize/__pycache__/zeros.cpython-312.pyc,, +scipy/optimize/_basinhopping.py,sha256=ulKl4gfc3DS8gDaZTL2iGW6oKRqLdjaw5DaU4ETzL7U,29977 +scipy/optimize/_bglu_dense.cpython-312-x86_64-linux-gnu.so,sha256=vaJsh4PxAzxnhuwxtphwGloZRFMZhASQJMaINfkAzV8,190552 +scipy/optimize/_bracket.py,sha256=1cVj9Wt0ozE7tnxKNxHLkjorIkYZFldBz5lrcrPClfs,31340 +scipy/optimize/_chandrupatla.py,sha256=1Dk2t4A3MNBOJDeO6uFr1Hs9mrHv0LY80ua6m4HQ5IY,24747 +scipy/optimize/_cobyla_py.py,sha256=dTfKovLLtjL0wb7oiEWjwAEv_xFPteHR4IeVFXBcn4U,11008 +scipy/optimize/_cobyqa_py.py,sha256=qmQfs62D0LYln8bCWKpTkujwNJqzbbcH-6MvdrLMJEg,2931 +scipy/optimize/_constraints.py,sha256=4h_IZW9q7hD3iaXSBhhtMs3E_UF5k6sJMYtEB2luNks,25257 +scipy/optimize/_dcsrch.py,sha256=tw5Fpc5_bHAzAuPszso66q0iP0efrJRP8KJ8W2C1_2Y,25234 +scipy/optimize/_differentiable_functions.py,sha256=JNoCDfCpX1TYr49sh_3InoX6uecujFiYseedWblH4XI,29965 +scipy/optimize/_differentialevolution.py,sha256=uoPXQg51dZCExdgJ4SQ6wDwaAhWvXfHuYR-kzK9rd8o,90019 +scipy/optimize/_direct.cpython-312-x86_64-linux-gnu.so,sha256=eMPaJEZNYJmsi_hJUgIMCg56J-TNJR3puNjtU4QO9iI,47520 +scipy/optimize/_direct_py.py,sha256=thi8xqSHzBo5lIAj8My05Ii4Ya0ViUcny3G59-bNuAc,11848 +scipy/optimize/_dual_annealing.py,sha256=MwpT3vxBaFIaSp87vZPFjKbY7j4tl_O5Xz2TgMcBKnY,31232 +scipy/optimize/_elementwise.py,sha256=jF0levjrNpPvbg1a7JKli50nlCKsM9tOcEzQ-6JZlfM,34741 +scipy/optimize/_group_columns.cpython-312-x86_64-linux-gnu.so,sha256=aWDfqsQhs6izzgha4ko92Y4D6D9YVPRFStbe5FqQeE0,92256 +scipy/optimize/_hessian_update_strategy.py,sha256=xmtREKGlLgVvlBynjb5eCnPbsH-xbPcprS-ZoziG80M,18423 +scipy/optimize/_highspy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +scipy/optimize/_highspy/__pycache__/__init__.cpython-312.pyc,, +scipy/optimize/_highspy/__pycache__/_highs_wrapper.cpython-312.pyc,, +scipy/optimize/_highspy/_core.cpython-312-x86_64-linux-gnu.so,sha256=M_taaoa4-5FLJuafjwRidLCwQ9ICUREJNl0_3hgMVA0,6755920 +scipy/optimize/_highspy/_highs_options.cpython-312-x86_64-linux-gnu.so,sha256=EW7XK-mszOr83rWNXQIwjlAjubyYEYMDp507wk_hcdY,400104 +scipy/optimize/_highspy/_highs_wrapper.py,sha256=wVqUOgmFv3FthLk3GdCy9XLmmDc2VasCWGFLSyq2cwM,11294 +scipy/optimize/_isotonic.py,sha256=WY-9jtT5VVafVALYIp6lJPQnBfYVNDP9oJpg-kErYYI,6077 +scipy/optimize/_lbfgsb.cpython-312-x86_64-linux-gnu.so,sha256=tkqWQG6RwPewgN0o2u2HlIUyIiSsfD4Q0yWAiu8MzhU,54465 +scipy/optimize/_lbfgsb_py.py,sha256=YbaVVPM6IQKE1K4959ooIVL5MCtCjusWmESYtU44HVc,21077 +scipy/optimize/_linesearch.py,sha256=TfQljgdUpPkqs50kdip-k7ulmdFMc91m-TPPNCCGX5E,27861 +scipy/optimize/_linprog.py,sha256=ZmM9AZPkSCpU849IkPFdBDeuqerMbCrDtBdGuBxyUT0,30240 +scipy/optimize/_linprog_doc.py,sha256=uRPfZVbOzYhZC-OSh158AWveKHXOLyhIHu8Aa6ozisM,61929 +scipy/optimize/_linprog_highs.py,sha256=MpmbukJSGgAdCL2aGgLKZjuLfrk7b3mfN0OOBiSEyJo,17145 +scipy/optimize/_linprog_ip.py,sha256=QBlUjw6jl3mEZnouExtuac2dlIn9Gtki5yJVzkSffSU,46651 +scipy/optimize/_linprog_rs.py,sha256=wRVGZxCSpo4ttw4CPpmXozSvM9WRXD179fGiGh8gOQ4,23146 +scipy/optimize/_linprog_simplex.py,sha256=9_nxcVl-ofHN9p_dDyC1C6jHlPttSfO9kp8WF1ST4JM,24748 +scipy/optimize/_linprog_util.py,sha256=tQ-oi5hlzQ-sC-bPbzRQAMCbn5GDVjJTffYsLQDLxYU,62738 +scipy/optimize/_lsap.cpython-312-x86_64-linux-gnu.so,sha256=npYC721UvlRLih1pfx9gOMor0-KtjkDwRAxbxw9vixI,25128 +scipy/optimize/_lsq/__init__.py,sha256=Yk4FSVEqe1h-qPqVX7XSkQNBYDtZO2veTmMAebCxhIQ,172 +scipy/optimize/_lsq/__pycache__/__init__.cpython-312.pyc,, +scipy/optimize/_lsq/__pycache__/bvls.cpython-312.pyc,, +scipy/optimize/_lsq/__pycache__/common.cpython-312.pyc,, +scipy/optimize/_lsq/__pycache__/dogbox.cpython-312.pyc,, +scipy/optimize/_lsq/__pycache__/least_squares.cpython-312.pyc,, +scipy/optimize/_lsq/__pycache__/lsq_linear.cpython-312.pyc,, +scipy/optimize/_lsq/__pycache__/trf.cpython-312.pyc,, +scipy/optimize/_lsq/__pycache__/trf_linear.cpython-312.pyc,, +scipy/optimize/_lsq/bvls.py,sha256=7u5B8LfUbv3ZRZ8DAZKuDTSNRfDEBmTsn25VZtMMsKk,5195 +scipy/optimize/_lsq/common.py,sha256=ktlTCUrXM2d3p537Ru1srElULKo56lOcMK3QmEFUHdM,20481 +scipy/optimize/_lsq/dogbox.py,sha256=A-Q6_1XV3TbN7wUGS8vT2JFeZfSz-GuS8h9HKKRJUMU,12177 +scipy/optimize/_lsq/givens_elimination.cpython-312-x86_64-linux-gnu.so,sha256=7QtMBYkelKWpYEF3RpSbuTyG54tYUETAtOJqDgaYJRc,64752 +scipy/optimize/_lsq/least_squares.py,sha256=WxUIredFcG3fM165UzMUZk2YRJ_Xy_8fesBdLoImYk0,42812 +scipy/optimize/_lsq/lsq_linear.py,sha256=aAVIMZce8S6_uWdv7SLvm5FgQBks8y0FPFZes4IlOr0,15273 +scipy/optimize/_lsq/trf.py,sha256=DHwp1dP9aRVyDpVH8FHUfhTKWoIeeHJoaqjPHRxOSJM,20516 +scipy/optimize/_lsq/trf_linear.py,sha256=jIs7WviOu_8Kpb7sTln8W7YLgkcndv0eGIP15g_mC4g,7642 +scipy/optimize/_milp.py,sha256=GLwglqjz9TpvrrqIF_ZLowB3cRXM8kbdpkvYXjGSNHk,15252 +scipy/optimize/_minimize.py,sha256=ayDsZk534vj31yXrpUHDdlypA36ddZsYYn2pvfiTPKY,53050 +scipy/optimize/_minpack.cpython-312-x86_64-linux-gnu.so,sha256=BU0Muwgc2JMDBpnfIOVtlptor9mWA7_uUACwueAxIa0,104416 +scipy/optimize/_minpack_py.py,sha256=L2PYVPMIUnminJzkIZ7xTN-3kiduN-wO0c2sKZJ2dYE,45578 +scipy/optimize/_moduleTNC.cpython-312-x86_64-linux-gnu.so,sha256=SjESrSi1iiaUjBxPeHJs8Eeeef5YJyN7kttrIdqcwvg,147048 +scipy/optimize/_nnls.py,sha256=FhoBly23m8hAiZ0ktORh_x1OJJjgWStmAyhfbhpuzK4,2607 +scipy/optimize/_nonlin.py,sha256=5mafO-h04E30vRTFDUrTQKgud9MPE9_yKGMXi5CjdV8,52424 +scipy/optimize/_numdiff.py,sha256=KAujFC9SgLGd5ZAwJzLoMDA1iQcue0FO4AuDQbphOoQ,37231 +scipy/optimize/_optimize.py,sha256=9a1bwOwB2fCtr61iJjGlmrjvyQAZ9B77vvGciy_q9DA,149865 +scipy/optimize/_pava_pybind.cpython-312-x86_64-linux-gnu.so,sha256=rdXkcTo_R7Q_gE80Kw7qh-TBkHI37hcG7RSlD1KF6_s,202576 +scipy/optimize/_qap.py,sha256=3Nb1EknQRhx7pzeNSlHSQN4NiNTtUG0P7CI7RoVXTpA,27361 +scipy/optimize/_remove_redundancy.py,sha256=00_Zc8_5uY6q2pBr8gu2_AYk7kX9YBjF8LqaxgqTKUY,18757 +scipy/optimize/_root.py,sha256=ShG0c7Rv2UBPktLdvfp7cuSS6lvuNnqdEYWerT3mcxg,28706 +scipy/optimize/_root_scalar.py,sha256=bN9ax-4PY5NHOYAK1GKyoOxwudQA6AXoe1vxPiSKiNM,20383 +scipy/optimize/_shgo.py,sha256=JIaAwhaQnMxCdgIIpS6UTKm1Ded8A3pqa1E7kZ530GY,63491 +scipy/optimize/_shgo_lib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +scipy/optimize/_shgo_lib/__pycache__/__init__.cpython-312.pyc,, +scipy/optimize/_shgo_lib/__pycache__/_complex.cpython-312.pyc,, +scipy/optimize/_shgo_lib/__pycache__/_vertex.cpython-312.pyc,, +scipy/optimize/_shgo_lib/_complex.py,sha256=yqXLrqDTIRIQX2konuOEMZkczcR5Kp8y0Wu6VbXWJHY,50260 +scipy/optimize/_shgo_lib/_vertex.py,sha256=I2TAqEEdTK66Km6UIkrDm2-tKpeJUuFX7DAfTk3XvUg,13996 +scipy/optimize/_slsqp_py.py,sha256=npcTK_tXcwlmdfA7CttvXtZ1EGZvfyxdoi0OlmnC4zg,24433 +scipy/optimize/_slsqplib.cpython-312-x86_64-linux-gnu.so,sha256=mutnGtTS6HhL-TFTEiJss6m6S7wTm5Rp5ZykkE1U2vc,63009 +scipy/optimize/_spectral.py,sha256=uV4DgfWAKcEpB7CC1CUgtWJjIGfIbpk8SH1ZwUyGFXo,8128 +scipy/optimize/_tnc.py,sha256=AnDDfkUQb96NLL_8_G31CwnXpbgbGkOO20m5JVj1Hkw,17344 +scipy/optimize/_trlib/__init__.py,sha256=cNGWE1VffijqhPtSaqwagtBJvjJK-XrJ6K80RURLd48,524 +scipy/optimize/_trlib/__pycache__/__init__.cpython-312.pyc,, +scipy/optimize/_trlib/_trlib.cpython-312-x86_64-linux-gnu.so,sha256=QHoSmaiSyqlhf3DYPOfwednzLgJpE9vR4cscw0ws5ZM,220681 +scipy/optimize/_trustregion.py,sha256=uDAmFPNTdgMMTR1MBkyf85N79CHIz-wpvcUP9oIgyl4,11869 +scipy/optimize/_trustregion_constr/__init__.py,sha256=c8J2wYGQZr9WpLIT4zE4MUgEj4YNbHEWYYYsFmxAeXI,180 +scipy/optimize/_trustregion_constr/__pycache__/__init__.cpython-312.pyc,, +scipy/optimize/_trustregion_constr/__pycache__/canonical_constraint.cpython-312.pyc,, +scipy/optimize/_trustregion_constr/__pycache__/equality_constrained_sqp.cpython-312.pyc,, +scipy/optimize/_trustregion_constr/__pycache__/minimize_trustregion_constr.cpython-312.pyc,, +scipy/optimize/_trustregion_constr/__pycache__/projections.cpython-312.pyc,, +scipy/optimize/_trustregion_constr/__pycache__/qp_subproblem.cpython-312.pyc,, +scipy/optimize/_trustregion_constr/__pycache__/report.cpython-312.pyc,, +scipy/optimize/_trustregion_constr/__pycache__/tr_interior_point.cpython-312.pyc,, +scipy/optimize/_trustregion_constr/canonical_constraint.py,sha256=gc5KucHwFCz_-w2IYWd_C1X1E0TFy_sey-_0KBY_djE,12542 +scipy/optimize/_trustregion_constr/equality_constrained_sqp.py,sha256=mNbnrNvKAkj7AzHTUpQQGgAfiUQ5ZSY0uzjk45B91Cs,9160 +scipy/optimize/_trustregion_constr/minimize_trustregion_constr.py,sha256=8v8D596PjOxZTHBlWh1ZRBzgWh8Y-oFfmjH9WB4EpGs,26579 +scipy/optimize/_trustregion_constr/projections.py,sha256=KrUsneu3FX9Cqhbky8UpwvbSGZZtQTPLcgIaexl9fWA,13514 +scipy/optimize/_trustregion_constr/qp_subproblem.py,sha256=fgSPNLvHYcHQXo8iJeS0i5BB6w-aUqEYyK9WC0R89-w,22591 +scipy/optimize/_trustregion_constr/report.py,sha256=_L-HrO5C1lzvKvaijgkOYD210dvM4PkrhBSEQrMhVlw,1782 +scipy/optimize/_trustregion_constr/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +scipy/optimize/_trustregion_constr/tests/__pycache__/__init__.cpython-312.pyc,, +scipy/optimize/_trustregion_constr/tests/__pycache__/test_canonical_constraint.cpython-312.pyc,, +scipy/optimize/_trustregion_constr/tests/__pycache__/test_nested_minimize.cpython-312.pyc,, +scipy/optimize/_trustregion_constr/tests/__pycache__/test_projections.cpython-312.pyc,, +scipy/optimize/_trustregion_constr/tests/__pycache__/test_qp_subproblem.cpython-312.pyc,, +scipy/optimize/_trustregion_constr/tests/__pycache__/test_report.cpython-312.pyc,, +scipy/optimize/_trustregion_constr/tests/test_canonical_constraint.py,sha256=zVPxZDa0WkG_tw9Fm_eo_JzsQ8rQrUJyQicq4J12Nd4,9869 +scipy/optimize/_trustregion_constr/tests/test_nested_minimize.py,sha256=tgBVQe97RwVu_GJACARyg0s9zHiFGVHSPNrXLCjlX7w,1216 +scipy/optimize/_trustregion_constr/tests/test_projections.py,sha256=Agc-mtiLBximfHMI0CTDklb0shxHL8DUXEBuV08CjSs,8869 +scipy/optimize/_trustregion_constr/tests/test_qp_subproblem.py,sha256=5nNaAOTP4Pox6ZIqQ9b9gpOBHALaPY3m4IS--7PX4a0,27642 +scipy/optimize/_trustregion_constr/tests/test_report.py,sha256=hyRnUGBhDhKHR5SKD66ZME4zzCIViIh3_-700p0afXY,1104 +scipy/optimize/_trustregion_constr/tr_interior_point.py,sha256=RjDA6wd5i3j-rxAx5vBuCf4QdglOHnxrb7y8nTTmK04,14740 +scipy/optimize/_trustregion_dogleg.py,sha256=HS783IZYHE-EEuF82c4rkFp9u3MNKUdCeynZ6ap8y8s,4389 +scipy/optimize/_trustregion_exact.py,sha256=G_1CoHDK8xNn0XtaP1Z71Lqk96My0U6r5B9BkW9XaaQ,16702 +scipy/optimize/_trustregion_krylov.py,sha256=KGdudJsoXXROXAc82aZ8ACojD3rimvyx5PYitbo4UzQ,3030 +scipy/optimize/_trustregion_ncg.py,sha256=y7b7QjFBfnB1wDtbwnvKD9DYpz7y7NqVrJ9RhNPcipw,4580 +scipy/optimize/_tstutils.py,sha256=H8S8aAERkeKwyTCygH3fAT5gtG7yLmlGSCDERs6pylE,34060 +scipy/optimize/_zeros.cpython-312-x86_64-linux-gnu.so,sha256=U-SKacXPrlpZL0NvqhXy3_IqiKjqCE4xQs0DQHmSsmo,24920 +scipy/optimize/_zeros_py.py,sha256=ooHJePiPSVsp4mg5LI7es7yCUDWQ2I8Y2pWgTsAHLx0,57031 +scipy/optimize/cobyla.py,sha256=k2io8SM0vahYT5Zu4nS4yfa05_gyH0y-jVVxdWkC4dU,557 +scipy/optimize/cython_optimize.pxd,sha256=ecYJEpT0CXN-2vtaZfGCChD-oiIaJyRDIsTHE8eUG5M,442 +scipy/optimize/cython_optimize/__init__.py,sha256=eehEQNmLGy3e_XjNh6t5vQIC9l_OREeE4tYRRaFZdNs,4887 +scipy/optimize/cython_optimize/__pycache__/__init__.cpython-312.pyc,, +scipy/optimize/cython_optimize/_zeros.cpython-312-x86_64-linux-gnu.so,sha256=sRNQS3Rcr8ZxD_JpKxfacKsw5w2KJYPws_LJVxKDcKc,105216 +scipy/optimize/cython_optimize/_zeros.pxd,sha256=anyu-MgWhq24f1bywI4TlohvJjOnpNpkCtSzpKBJSSo,1239 +scipy/optimize/cython_optimize/c_zeros.pxd,sha256=6Gc0l1q-1nlCO9uKrYeXFiHsbimRZzU3t6EoTa8MVvA,1118 +scipy/optimize/elementwise.py,sha256=8eEQW_PeNkr49YBTROr5xWDLgeJd7rxtdQk3tVuEECQ,1190 +scipy/optimize/lbfgsb.py,sha256=XT7kclUTtom8JASPYyAScx-5irlBd9s9yEnZzRwFqu8,601 +scipy/optimize/linesearch.py,sha256=w5OhOofynUbz7IzHAGEc6huLKV_rMR5eUq77VcskA9o,535 +scipy/optimize/minpack.py,sha256=2S9tkmBI670qqeDN7k_1-ZLYsFZV1yXaDMkrCvMETiQ,664 +scipy/optimize/minpack2.py,sha256=IPIduBcu0LRo75GJ9SiMa_GjfdKCOYzsWUs61_d1HR8,514 +scipy/optimize/moduleTNC.py,sha256=qTEQ4IWtv_LT6fH3-iYmYNwrtrjG1gS4KFbZ73iDcd0,507 +scipy/optimize/nonlin.py,sha256=uoKIYAdmhwNrC6zFbUIBCNdM1a59nn7hb5jxSOuK3rs,710 +scipy/optimize/optimize.py,sha256=SivH06ZYrbIwJLTQj3ZShU4FXft7w2y1a2uYE9ILIMo,877 +scipy/optimize/slsqp.py,sha256=Xei2XAZBNcz8cQdLdK8tTHeyIgpLEElP2ENdTH2QEi8,569 +scipy/optimize/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +scipy/optimize/tests/__pycache__/__init__.cpython-312.pyc,, +scipy/optimize/tests/__pycache__/test__basinhopping.cpython-312.pyc,, +scipy/optimize/tests/__pycache__/test__differential_evolution.cpython-312.pyc,, +scipy/optimize/tests/__pycache__/test__dual_annealing.cpython-312.pyc,, +scipy/optimize/tests/__pycache__/test__linprog_clean_inputs.cpython-312.pyc,, +scipy/optimize/tests/__pycache__/test__numdiff.cpython-312.pyc,, +scipy/optimize/tests/__pycache__/test__remove_redundancy.cpython-312.pyc,, +scipy/optimize/tests/__pycache__/test__root.cpython-312.pyc,, +scipy/optimize/tests/__pycache__/test__shgo.cpython-312.pyc,, +scipy/optimize/tests/__pycache__/test__spectral.cpython-312.pyc,, +scipy/optimize/tests/__pycache__/test_bracket.cpython-312.pyc,, +scipy/optimize/tests/__pycache__/test_chandrupatla.cpython-312.pyc,, +scipy/optimize/tests/__pycache__/test_cobyla.cpython-312.pyc,, +scipy/optimize/tests/__pycache__/test_cobyqa.cpython-312.pyc,, +scipy/optimize/tests/__pycache__/test_constraint_conversion.cpython-312.pyc,, +scipy/optimize/tests/__pycache__/test_constraints.cpython-312.pyc,, +scipy/optimize/tests/__pycache__/test_cython_abi.cpython-312.pyc,, +scipy/optimize/tests/__pycache__/test_cython_optimize.cpython-312.pyc,, +scipy/optimize/tests/__pycache__/test_differentiable_functions.cpython-312.pyc,, +scipy/optimize/tests/__pycache__/test_direct.cpython-312.pyc,, +scipy/optimize/tests/__pycache__/test_extending.cpython-312.pyc,, +scipy/optimize/tests/__pycache__/test_hessian_update_strategy.cpython-312.pyc,, +scipy/optimize/tests/__pycache__/test_isotonic_regression.cpython-312.pyc,, +scipy/optimize/tests/__pycache__/test_lbfgsb_hessinv.cpython-312.pyc,, +scipy/optimize/tests/__pycache__/test_lbfgsb_setulb.cpython-312.pyc,, +scipy/optimize/tests/__pycache__/test_least_squares.cpython-312.pyc,, +scipy/optimize/tests/__pycache__/test_linear_assignment.cpython-312.pyc,, +scipy/optimize/tests/__pycache__/test_linesearch.cpython-312.pyc,, +scipy/optimize/tests/__pycache__/test_linprog.cpython-312.pyc,, +scipy/optimize/tests/__pycache__/test_lsq_common.cpython-312.pyc,, +scipy/optimize/tests/__pycache__/test_lsq_linear.cpython-312.pyc,, +scipy/optimize/tests/__pycache__/test_milp.cpython-312.pyc,, +scipy/optimize/tests/__pycache__/test_minimize_constrained.cpython-312.pyc,, +scipy/optimize/tests/__pycache__/test_minpack.cpython-312.pyc,, +scipy/optimize/tests/__pycache__/test_nnls.cpython-312.pyc,, +scipy/optimize/tests/__pycache__/test_nonlin.cpython-312.pyc,, +scipy/optimize/tests/__pycache__/test_optimize.cpython-312.pyc,, +scipy/optimize/tests/__pycache__/test_quadratic_assignment.cpython-312.pyc,, +scipy/optimize/tests/__pycache__/test_regression.cpython-312.pyc,, +scipy/optimize/tests/__pycache__/test_slsqp.cpython-312.pyc,, +scipy/optimize/tests/__pycache__/test_tnc.cpython-312.pyc,, +scipy/optimize/tests/__pycache__/test_trustregion.cpython-312.pyc,, +scipy/optimize/tests/__pycache__/test_trustregion_exact.cpython-312.pyc,, +scipy/optimize/tests/__pycache__/test_trustregion_krylov.cpython-312.pyc,, +scipy/optimize/tests/__pycache__/test_zeros.cpython-312.pyc,, +scipy/optimize/tests/_cython_examples/extending.pyx,sha256=5TCYF9hvIYu8S9Y7PIql-xdJfcn_LI50yDrf4uh7i2M,1314 +scipy/optimize/tests/_cython_examples/meson.build,sha256=piAjBdhuCnXT6GF5z11r_GBQj6jaPIxWjvCFvYjIbsc,703 +scipy/optimize/tests/cython_abi_signatures.json,sha256=YEQuTLKqT9a9AgtvLeYpzlj-5Uf2WxYldRFEWKqzixo,884 +scipy/optimize/tests/test__basinhopping.py,sha256=5tsmhal5dAxecemRbRVVwhwmEmYMlR84b3_Ep8AgPzA,19230 +scipy/optimize/tests/test__differential_evolution.py,sha256=uceTx0nEQPuIyzMevSorGtUwDcEeNufkeEhByVcSjng,71576 +scipy/optimize/tests/test__dual_annealing.py,sha256=narPlqiULIu0dlWySB9mWn6Y-1sjM_DaiwARgCQeVKA,16603 +scipy/optimize/tests/test__linprog_clean_inputs.py,sha256=kwBb1lKIYrRi07zw_zyXQE8dV3owopmXb4WYaff2qzI,11876 +scipy/optimize/tests/test__numdiff.py,sha256=45imtf8433LUabNfpKfBFYvHFCcvh1lqfGoFHGZ1mUU,36935 +scipy/optimize/tests/test__remove_redundancy.py,sha256=wxGfVJaHD_-5g_aMNU3w92EuWrCrfhCLU1ixhFVio64,6883 +scipy/optimize/tests/test__root.py,sha256=xMNuKf_eNAFN6hwp1PzY7KIXlcFWV8ozPtzGtnZRmjA,4199 +scipy/optimize/tests/test__shgo.py,sha256=9OLUk1OSdp4dZ_Hmd5yGzAHGynm1t3U9j5sZp7bsnBg,42197 +scipy/optimize/tests/test__spectral.py,sha256=V7J3hHkzYPLSgSycFVt2zEXUbO9N3wTfyhjRoTfz3Eo,6639 +scipy/optimize/tests/test_bracket.py,sha256=k61erG0282xVL6n5kWSFSBO1IzIVEir7qI9Cm6Eo15c,37431 +scipy/optimize/tests/test_chandrupatla.py,sha256=-NJxWaQOrqvbz5t-FemVJcnAZpulOJ1L8CxIR8dK-yY,38644 +scipy/optimize/tests/test_cobyla.py,sha256=gLCVj8xmmSoIx5oikBMr9PVpgyGsoyAG5JdcNAZJmmU,6822 +scipy/optimize/tests/test_cobyqa.py,sha256=5sHRoBc4ZVfjZZAYMGObwSAtWq2A53L9KSwHuUUhQLk,8143 +scipy/optimize/tests/test_constraint_conversion.py,sha256=2FUZwIk7TPMBd-ulpVs-puUcgrpKez3b87fgYJOhYs8,12522 +scipy/optimize/tests/test_constraints.py,sha256=7POZDDtRQ9KhXOjjU62QMzPYWA5jN7KKhjh48qH5oSQ,9685 +scipy/optimize/tests/test_cython_abi.py,sha256=qfLdugtKnK83Zos1a8wOX0iw9CqRi1-gEYnR53vJ21U,2981 +scipy/optimize/tests/test_cython_optimize.py,sha256=n-HccBWoUmmBWq_OsNrAVnt4QrdssIYm4PWG29Ocias,2638 +scipy/optimize/tests/test_differentiable_functions.py,sha256=90EVfeZPIHt2_VVIIeBrNvj-Vdng91TWk5J2xn8q2fQ,38600 +scipy/optimize/tests/test_direct.py,sha256=o78qysRSH0nf5Z5Y5sQuELUCWs46GN72wDnFp1rBfik,13749 +scipy/optimize/tests/test_extending.py,sha256=r9Phn1PUn0U3U6QJeMiPreKG6jKmnWFqwpf1Al7w7K0,1104 +scipy/optimize/tests/test_hessian_update_strategy.py,sha256=EiL5ImqkGFmUTjgZjv0FGpGBjTzWXqT3w6eCrzQtPmo,14337 +scipy/optimize/tests/test_isotonic_regression.py,sha256=aJakW5zYcILN3wa--CYFBoZ3MB6n5Rzwd4WfNs_SFQk,7113 +scipy/optimize/tests/test_lbfgsb_hessinv.py,sha256=XnInFBGl9BQZS-wndHuPhNFDkb4kIsd02QMdVsg3Iy4,1934 +scipy/optimize/tests/test_lbfgsb_setulb.py,sha256=91ECB9qF8nXvL8u3k-9dJvrSJ3Oot91LPzl75OK2ln4,3683 +scipy/optimize/tests/test_least_squares.py,sha256=_ozl0YHfvpPHCKbP7Kr8lpw4pVFY-eSUkVihDcJS_EM,38297 +scipy/optimize/tests/test_linear_assignment.py,sha256=vhP5go29wZY-XaszuCkajQzYqYKzqAPs5WSO5zI3j2M,4103 +scipy/optimize/tests/test_linesearch.py,sha256=BGEDLJjO8RUPW6Aek8AHb4VB4voOuTX88gYHEKkJud8,11598 +scipy/optimize/tests/test_linprog.py,sha256=XqBf5HVLQomD6lCg67_RIMOTd9YLsbKskmtSBcboAN0,105114 +scipy/optimize/tests/test_lsq_common.py,sha256=alCLPPQB4mrxLIAo_rn7eg9xrCEH7DerNBozSimOQRA,9500 +scipy/optimize/tests/test_lsq_linear.py,sha256=5K_o46uKUJsprMeeT7lwf9gBIfiFxzeeREoOEe8WSKs,11000 +scipy/optimize/tests/test_milp.py,sha256=JvtivR6ZrI4rB_LxMCCPSmpeJoWos699FF_RC-59u-4,18811 +scipy/optimize/tests/test_minimize_constrained.py,sha256=XypNTVT7VX0zT596IIviFlIIGHuhyLU5SsOg89cfVcE,34786 +scipy/optimize/tests/test_minpack.py,sha256=4hOCKOnfsstwupL-_bmAkTgR3rDypkXVw8fwmf3f4LI,44879 +scipy/optimize/tests/test_nnls.py,sha256=QTJY18X-gS0UQoXI25cplM0KFPnvjqVPY9NxP908plk,27086 +scipy/optimize/tests/test_nonlin.py,sha256=nbAIXMTq-KlH2EjCui1CbgUtFpwftj3cLWkK0Ah58nc,20250 +scipy/optimize/tests/test_optimize.py,sha256=fzfiKb9mh9hH24tzFttm3StetaNnlNNUXnZEZU1_gfs,143191 +scipy/optimize/tests/test_quadratic_assignment.py,sha256=cyGP2ZIcP0vfgA6XuP7w_7p_X_bB3GDzqvP70-9kCYI,16430 +scipy/optimize/tests/test_regression.py,sha256=CSg8X-hq6-6jW8vki6aVfEFYRUGTWOg58silM1XNXbU,1077 +scipy/optimize/tests/test_slsqp.py,sha256=io__JB2LIrNtcV3uwgXq26g5jJY2YV_YQyzW8-YebsU,25662 +scipy/optimize/tests/test_tnc.py,sha256=ahSwu8F1tUcPV09l1MsbacUXXi1avQHzQNniYhZRf4s,12700 +scipy/optimize/tests/test_trustregion.py,sha256=IhrFNgseaMd-R6RizA9obQP9m0zozGgLSaxHSXbBBFA,4638 +scipy/optimize/tests/test_trustregion_exact.py,sha256=c_A4sTnvlkQ2zJnsLI3944eDIY7cPloFtB11MaEbD6s,21796 +scipy/optimize/tests/test_trustregion_krylov.py,sha256=G_p_Kje8awkqw1Z9AV0k9xI9BWRrPUKZgCoeo94g7y0,6571 +scipy/optimize/tests/test_zeros.py,sha256=yP6vSJv7iz9logcA0tefHQUXnl5lX-re2quCdL6IhF8,39066 +scipy/optimize/tnc.py,sha256=aEKhka8wryg4mVlbrGFwzTJF_KYB49joMkSxKgh1KnA,560 +scipy/optimize/zeros.py,sha256=Sc06-J8JUazdfR36UamHhPndJoPK0FkOzHR-unHWoBw,620 +scipy/signal/__init__.py,sha256=UVzvVtIhdMjYpLU7xK6ERgyATsRaeIrsdEVTfMWJo0Y,13700 +scipy/signal/__pycache__/__init__.cpython-312.pyc,, +scipy/signal/__pycache__/_arraytools.cpython-312.pyc,, +scipy/signal/__pycache__/_czt.cpython-312.pyc,, +scipy/signal/__pycache__/_delegators.cpython-312.pyc,, +scipy/signal/__pycache__/_filter_design.cpython-312.pyc,, +scipy/signal/__pycache__/_fir_filter_design.cpython-312.pyc,, +scipy/signal/__pycache__/_lti_conversion.cpython-312.pyc,, +scipy/signal/__pycache__/_ltisys.cpython-312.pyc,, +scipy/signal/__pycache__/_max_len_seq.cpython-312.pyc,, +scipy/signal/__pycache__/_peak_finding.cpython-312.pyc,, +scipy/signal/__pycache__/_polyutils.cpython-312.pyc,, +scipy/signal/__pycache__/_savitzky_golay.cpython-312.pyc,, +scipy/signal/__pycache__/_short_time_fft.cpython-312.pyc,, +scipy/signal/__pycache__/_signal_api.cpython-312.pyc,, +scipy/signal/__pycache__/_signaltools.cpython-312.pyc,, +scipy/signal/__pycache__/_spectral_py.cpython-312.pyc,, +scipy/signal/__pycache__/_spline_filters.cpython-312.pyc,, +scipy/signal/__pycache__/_support_alternative_backends.cpython-312.pyc,, +scipy/signal/__pycache__/_upfirdn.cpython-312.pyc,, +scipy/signal/__pycache__/_waveforms.cpython-312.pyc,, +scipy/signal/__pycache__/_wavelets.cpython-312.pyc,, +scipy/signal/__pycache__/_whittaker.cpython-312.pyc,, +scipy/signal/__pycache__/bsplines.cpython-312.pyc,, +scipy/signal/__pycache__/filter_design.cpython-312.pyc,, +scipy/signal/__pycache__/fir_filter_design.cpython-312.pyc,, +scipy/signal/__pycache__/lti_conversion.cpython-312.pyc,, +scipy/signal/__pycache__/ltisys.cpython-312.pyc,, +scipy/signal/__pycache__/signaltools.cpython-312.pyc,, +scipy/signal/__pycache__/spectral.cpython-312.pyc,, +scipy/signal/__pycache__/spline.cpython-312.pyc,, +scipy/signal/__pycache__/waveforms.cpython-312.pyc,, +scipy/signal/__pycache__/wavelets.cpython-312.pyc,, +scipy/signal/_arraytools.py,sha256=k3kHbl9RzcqsyftIYSFJZvJFL4zlcMAHyaRFUkFxOXY,8294 +scipy/signal/_czt.py,sha256=-nIXQ3HMjF0kBPEmfUMPvp6zk0PoU-NaQeRDb0qKUJ0,19572 +scipy/signal/_delegators.py,sha256=vS9ZpzkeFjJwcSyEar08kPMA0TZf-U6PTLS6RbqNke0,14651 +scipy/signal/_filter_design.py,sha256=LZRs-FOs_8v3kfS-oa1y5p3vx2R7AdOR_hPgpAvF79A,204945 +scipy/signal/_fir_filter_design.py,sha256=u1oVvvL5IasKbI1OVkYB4ntqDU0IBpcJQEzz1WhJY9o,64168 +scipy/signal/_lti_conversion.py,sha256=6K_f5X_5juy6G6hu2lhcb3Y2Xl8o0pcdmkiz57d-AlE,19651 +scipy/signal/_ltisys.py,sha256=jGYYCJ9ffmabPTTamk7Z3i8UMZ-4T5L6rNuLk8JHIBs,132366 +scipy/signal/_max_len_seq.py,sha256=8QkMWoYY3qy3bCKfsuXaS93Bnb2zd-ue6j5i5-3_hi0,5060 +scipy/signal/_max_len_seq_inner.cpython-312-x86_64-linux-gnu.so,sha256=QcT4kMz22l6EVYttjyyuGdwoJ54yJx_CZtwS4TzyEPc,57328 +scipy/signal/_peak_finding.py,sha256=fWVO6I09177M7Tz9YU74T4OP9xMg1BJycRZVLr0RIWM,48762 +scipy/signal/_peak_finding_utils.cpython-312-x86_64-linux-gnu.so,sha256=f1R1NKnD9WCChM-T4Yni9ANq3N8Oc5ifLQBIEuTSX8M,146296 +scipy/signal/_polyutils.py,sha256=2Sn1cnqlOCaOJxZHivT5is-uW9o_ig_-pnu_QHGmrts,8310 +scipy/signal/_savitzky_golay.py,sha256=3FaUx6GUVgp01UjXFunS5Xfffkfn8HrKGRtOzrXJZdo,14682 +scipy/signal/_short_time_fft.py,sha256=HqAe1kWIYIvPpt8uBw1fAFP4E3-sJ9-nJIzGkJQi1Nw,103170 +scipy/signal/_signal_api.py,sha256=j1c6iJiU0aElL1HLT8Kct6qRAVwCvTN0YbQFLFnbXkY,1295 +scipy/signal/_signaltools.py,sha256=rhYsjRxD7pD66Carn5IyQlv2YEK4TZf82AiycNkwmlE,198357 +scipy/signal/_sigtools.cpython-312-x86_64-linux-gnu.so,sha256=uV2N2VuM62zJnlnPJnKg-Y-lFsSM3Eyai27zCunxLEI,108416 +scipy/signal/_sosfilt.cpython-312-x86_64-linux-gnu.so,sha256=3DlddGzJh5qkHc_RFEYTUlpE5WGhHV80tlHCDIo7ugg,110352 +scipy/signal/_spectral_py.py,sha256=Pdrz2OZZ7JOoX7OZRoJ40_Dify3f_IqhR10g-xeEZIo,97147 +scipy/signal/_spline.cpython-312-x86_64-linux-gnu.so,sha256=7lNZ8j926E6R4uNAi3s_1LvQ3-jqTLuqNUbGPjUcjEk,59680 +scipy/signal/_spline.pyi,sha256=9tWZQCI7D84ONLwICZG6psBGtwKxAvLF7JaZ1tQUKoY,948 +scipy/signal/_spline_filters.py,sha256=H0AcM2pPA-Z9V_r_Aq0dfPrHLPq-wxFGISPnLNToCCk,25458 +scipy/signal/_support_alternative_backends.py,sha256=hRRNl4JKDuSwywdK1-rOA_zJqwBnjP7RDUNr1_UmOAQ,18313 +scipy/signal/_upfirdn.py,sha256=bE78hIj-iGh7wCXcMh-3Tus9WM_pptNoGLHKXHLFvu0,7976 +scipy/signal/_upfirdn_apply.cpython-312-x86_64-linux-gnu.so,sha256=tEq6MNfFuO_k1yIEOeLwXs4AChy9LWa6xxVvkAidZOU,202816 +scipy/signal/_waveforms.py,sha256=EO3UtuoXx0vDFRcGJMO1n3fp_62HzPOMeZE34FOxIb8,22954 +scipy/signal/_wavelets.py,sha256=K7wj6hMQgJrJ1sb3b2SB4LRc7JlZx5ej9tX6v7E1YCw,886 +scipy/signal/_whittaker.py,sha256=gMxf-vdtalKJxklnW7NNbMbqtdx4DyjsD-P_S72clCw,18096 +scipy/signal/bsplines.py,sha256=G1sa6en1z_41sU7ckRY8-flJjUKSqJJihaxBlwzUd3s,651 +scipy/signal/filter_design.py,sha256=EyHs8OX4mdeUi6e3Zf7IWuz6r5Re2eR_t0Bi10JuntM,1112 +scipy/signal/fir_filter_design.py,sha256=mJr3FG_K_4qbzLOUasIJmXSycuFmotHkEMCAn812v6I,657 +scipy/signal/lti_conversion.py,sha256=6uQ1qaT7XI75DoFmtRqRS94Hkpm-Qvy66CRNhmQ-Lbw,639 +scipy/signal/ltisys.py,sha256=TFul9jyL0ujEIchiOnDdIiJKIXZ8SSgOV066DvmX_QA,869 +scipy/signal/signaltools.py,sha256=I7U_hMuMf02zpdNi0LcPogucTDf0nUVUSkMZ1eAoq3E,1038 +scipy/signal/spectral.py,sha256=RA3jj6AWV6ptNwXfpVrbuyxxed8P7nWw8bLsD0iZIgw,662 +scipy/signal/spline.py,sha256=rC_E8HwcpDqwIGdaDF0Cb5uC6kPoAQP57jeQNq-aCmI,536 +scipy/signal/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +scipy/signal/tests/__pycache__/__init__.cpython-312.pyc,, +scipy/signal/tests/__pycache__/_scipy_spectral_test_shim.cpython-312.pyc,, +scipy/signal/tests/__pycache__/mpsig.cpython-312.pyc,, +scipy/signal/tests/__pycache__/test_array_tools.cpython-312.pyc,, +scipy/signal/tests/__pycache__/test_bsplines.cpython-312.pyc,, +scipy/signal/tests/__pycache__/test_cont2discrete.cpython-312.pyc,, +scipy/signal/tests/__pycache__/test_czt.cpython-312.pyc,, +scipy/signal/tests/__pycache__/test_dltisys.cpython-312.pyc,, +scipy/signal/tests/__pycache__/test_filter_design.cpython-312.pyc,, +scipy/signal/tests/__pycache__/test_fir_filter_design.cpython-312.pyc,, +scipy/signal/tests/__pycache__/test_ltisys.cpython-312.pyc,, +scipy/signal/tests/__pycache__/test_max_len_seq.cpython-312.pyc,, +scipy/signal/tests/__pycache__/test_peak_finding.cpython-312.pyc,, +scipy/signal/tests/__pycache__/test_result_type.cpython-312.pyc,, +scipy/signal/tests/__pycache__/test_savitzky_golay.cpython-312.pyc,, +scipy/signal/tests/__pycache__/test_short_time_fft.cpython-312.pyc,, +scipy/signal/tests/__pycache__/test_signaltools.cpython-312.pyc,, +scipy/signal/tests/__pycache__/test_spectral.cpython-312.pyc,, +scipy/signal/tests/__pycache__/test_splines.cpython-312.pyc,, +scipy/signal/tests/__pycache__/test_upfirdn.cpython-312.pyc,, +scipy/signal/tests/__pycache__/test_waveforms.cpython-312.pyc,, +scipy/signal/tests/__pycache__/test_wavelets.cpython-312.pyc,, +scipy/signal/tests/__pycache__/test_whittaker.cpython-312.pyc,, +scipy/signal/tests/__pycache__/test_windows.cpython-312.pyc,, +scipy/signal/tests/_scipy_spectral_test_shim.py,sha256=6ijS7_69VFLn5WaN--fdLmpvvsG0-2i47qs48GqRmLA,12677 +scipy/signal/tests/data/GLB.Ts+dSST.csv,sha256=GjVE2Q1-iArEVg3NPY2wIqWKFrKWCEhWTPrKYOwbmPU,12879 +scipy/signal/tests/mpsig.py,sha256=DHB3eHB0KYA-E0SBebKG36YLk-T5egbwwryne3RwIHM,3308 +scipy/signal/tests/test_array_tools.py,sha256=QN4SGbtxDSP2MFvyYl00RasYYyNF4A1g8Y6_1Sij7YQ,3589 +scipy/signal/tests/test_bsplines.py,sha256=Y18U_k59H_rco2IJmA0mtnH_7U8MTxIhSkyKGEYS0jM,19500 +scipy/signal/tests/test_cont2discrete.py,sha256=2e1gmEYWOaipAHK2STuY6cEZGoomPtjSrBpZMsfRKWc,15541 +scipy/signal/tests/test_czt.py,sha256=2-kcWyadICVl_mF0vbq1KYii-rYMtZiuiOSb6HkYn7w,7156 +scipy/signal/tests/test_dltisys.py,sha256=gefhLnAHHlgVJlW1jJRn5d3inR5RX_-ke6q7r_4g1j0,21567 +scipy/signal/tests/test_filter_design.py,sha256=f3mwDkDErXHyE8SUHhhwS_zjQ1NHIw_mpqGsfdBzC1A,231609 +scipy/signal/tests/test_fir_filter_design.py,sha256=23tXnmXYpbPgPDh91oAFWcJWkhvwyNFTt3O9chGvzk4,37387 +scipy/signal/tests/test_ltisys.py,sha256=0egH1Xrz39r6gDQXlc7n2jDzK0RMxJjlh7pz8qD5eyY,47705 +scipy/signal/tests/test_max_len_seq.py,sha256=JzfWWN4n6FO9Axw6H6xWrWyc21LlkqMwkGl23f-V664,3318 +scipy/signal/tests/test_peak_finding.py,sha256=faqAnZkR8uJVyMxN-BWLM5INWtFlTYEmyHeIGWgBi7g,38329 +scipy/signal/tests/test_result_type.py,sha256=F48EQGbFfQfMwcnt-sMofHGNHVTbHntbMlgoeS2vYcY,1573 +scipy/signal/tests/test_savitzky_golay.py,sha256=2F-sr5SGOc3TA_LSMAk6_1G1eZRdDAQ-53KAq_XpFQ8,14329 +scipy/signal/tests/test_short_time_fft.py,sha256=H1QZ2SX1DhVtPobHrJbnbVe1Kg6aObepadduiWXmJsI,48294 +scipy/signal/tests/test_signaltools.py,sha256=AS4iA0f42mOlBK_7x5Ga221ec5Cp16YXDmnyj9fkt8g,197271 +scipy/signal/tests/test_spectral.py,sha256=nhqL27Tpa5WjunBQXaWGljJEPEUZt7rd3QWWQFPnC6w,83384 +scipy/signal/tests/test_splines.py,sha256=hSHmY342efLoP8oGAntOanKTIlGElzh9e_U65go03lc,17014 +scipy/signal/tests/test_upfirdn.py,sha256=CgqjkOTZ1ihP3Yl-0947lXDChyxFKCbIU0rv994VqTk,13027 +scipy/signal/tests/test_waveforms.py,sha256=2HC9qmqwu-pMbKDsUPEjm6vKDtjmY3OfBP2XQvFbuaQ,16991 +scipy/signal/tests/test_wavelets.py,sha256=42yMux80J-K7Ue9QLnzN84U9K3j2GRdywMxGpbLldeM,2145 +scipy/signal/tests/test_whittaker.py,sha256=mB34KNUz4vG175GGpsiAjOcYwUkQgGh5mavVrDZEEoY,15713 +scipy/signal/tests/test_windows.py,sha256=9vP4fgIWntl67zIrLK73QVnFIf78DiFRc6jWwCbHc8g,58720 +scipy/signal/waveforms.py,sha256=jfOXW7kgtGdh1nrMo1YLAh79W_Ln3WgzEN2esrp70wE,599 +scipy/signal/wavelets.py,sha256=7pA7HVMiXwG4fZZ0Q4nzz47hWWALMTYtxwGrIqV3bNE,510 +scipy/signal/windows/__init__.py,sha256=TtLlEQSIXo8WiAPJh0raWYS8_5zLHOjarzCmDhj9Eu8,2126 +scipy/signal/windows/__pycache__/__init__.cpython-312.pyc,, +scipy/signal/windows/__pycache__/_windows.cpython-312.pyc,, +scipy/signal/windows/__pycache__/windows.cpython-312.pyc,, +scipy/signal/windows/_windows.py,sha256=_tPXi5O7tK0JBjcIAjvSOVWHMkzGhgvSUNHjvMlwxbQ,95045 +scipy/signal/windows/windows.py,sha256=FI6w8mt0V1221Rqv3Do3LuWRWrtKo3hYYTvpB_5UB1c,839 +scipy/sparse/__init__.py,sha256=t03U2jvL_NlV9iraLeAKddqQqsNTl_kVNhwaYMpx5nQ,8170 +scipy/sparse/__pycache__/__init__.cpython-312.pyc,, +scipy/sparse/__pycache__/_base.cpython-312.pyc,, +scipy/sparse/__pycache__/_bsr.cpython-312.pyc,, +scipy/sparse/__pycache__/_compressed.cpython-312.pyc,, +scipy/sparse/__pycache__/_construct.cpython-312.pyc,, +scipy/sparse/__pycache__/_coo.cpython-312.pyc,, +scipy/sparse/__pycache__/_csc.cpython-312.pyc,, +scipy/sparse/__pycache__/_csr.cpython-312.pyc,, +scipy/sparse/__pycache__/_data.cpython-312.pyc,, +scipy/sparse/__pycache__/_dia.cpython-312.pyc,, +scipy/sparse/__pycache__/_dok.cpython-312.pyc,, +scipy/sparse/__pycache__/_extract.cpython-312.pyc,, +scipy/sparse/__pycache__/_index.cpython-312.pyc,, +scipy/sparse/__pycache__/_lil.cpython-312.pyc,, +scipy/sparse/__pycache__/_matrix.cpython-312.pyc,, +scipy/sparse/__pycache__/_matrix_io.cpython-312.pyc,, +scipy/sparse/__pycache__/_spfuncs.cpython-312.pyc,, +scipy/sparse/__pycache__/_sputils.cpython-312.pyc,, +scipy/sparse/__pycache__/base.cpython-312.pyc,, +scipy/sparse/__pycache__/bsr.cpython-312.pyc,, +scipy/sparse/__pycache__/compressed.cpython-312.pyc,, +scipy/sparse/__pycache__/construct.cpython-312.pyc,, +scipy/sparse/__pycache__/coo.cpython-312.pyc,, +scipy/sparse/__pycache__/csc.cpython-312.pyc,, +scipy/sparse/__pycache__/csr.cpython-312.pyc,, +scipy/sparse/__pycache__/data.cpython-312.pyc,, +scipy/sparse/__pycache__/dia.cpython-312.pyc,, +scipy/sparse/__pycache__/dok.cpython-312.pyc,, +scipy/sparse/__pycache__/extract.cpython-312.pyc,, +scipy/sparse/__pycache__/lil.cpython-312.pyc,, +scipy/sparse/__pycache__/sparsetools.cpython-312.pyc,, +scipy/sparse/__pycache__/spfuncs.cpython-312.pyc,, +scipy/sparse/__pycache__/sputils.cpython-312.pyc,, +scipy/sparse/_base.py,sha256=J3fue1s8a0eyzHgwyWJDtm4bQmeHcKVLxD8z0h3zh8k,63724 +scipy/sparse/_bsr.py,sha256=OG8r1It2IdqveCT_s8IYHgWG0LLPpxgI2AA6Iax4G2E,32408 +scipy/sparse/_compressed.py,sha256=uAsIzprr0Q8lEsq8TLaERr2t4rCT3HB9NwGcMqODQCs,52650 +scipy/sparse/_construct.py,sha256=HCuvGui5P7uhPwnTIPO0fP7C3L34JI8AkJ6ucD8vU_o,65343 +scipy/sparse/_coo.py,sha256=5W1ZCOF-9Z4CqOQSepcmUAeaEOIZw1UlcrWPJbRbrr4,77494 +scipy/sparse/_csc.py,sha256=f2Xfqj8cD3JoRoXFWD2dt5_amkCJ3PGgSdlnHuc6WGI,12971 +scipy/sparse/_csparsetools.cpython-312-x86_64-linux-gnu.so,sha256=t8o5agaG-HwnZ-W77d8utdbUCJTQKDd0WErtfN6HsZ0,441808 +scipy/sparse/_csr.py,sha256=vZl2rM92m1sZ28bFeh6jRrCQghNrUFbB3lQbjpzfUA0,20410 +scipy/sparse/_data.py,sha256=wRFNkoUPYeg3obpxerAkehEbw4aN8RSk1jjcTquWDG8,21340 +scipy/sparse/_dia.py,sha256=rHb95-eJ783rUrDUJ0ucvG_kuAwYjfO6HA3yZCdxHns,27242 +scipy/sparse/_dok.py,sha256=bQqotjUZQmi6boWTBrX3J5xcaLZy-jDjLeUfA9gApv4,27826 +scipy/sparse/_extract.py,sha256=J4bYmdiuGhB-nNXdFk5uInFpo3KnrdiMUc4X7pAGdUo,8215 +scipy/sparse/_index.py,sha256=6X9Fbke0bcfFOJ9KXPSBAjaVVpIn07QEafcQABaoEZE,18222 +scipy/sparse/_lil.py,sha256=JFy728TDLnce17FP9s-gNWFjFyKZVTaikSifg0FdlgU,22782 +scipy/sparse/_matrix.py,sha256=Fd1bqKm3RqH4vftTV-blzFCTHKh6nHc9znWhb_RAI7Y,5564 +scipy/sparse/_matrix_io.py,sha256=_Z6oqc_uGIiwLrZubXx4o0rTTxCqDEYTZcBI1NtQdCk,6230 +scipy/sparse/_sparsetools.cpython-312-x86_64-linux-gnu.so,sha256=zApol7rgvJ4Li-FeOKPZ0V3LNOOs8bImVhcRF69x_MU,4554512 +scipy/sparse/_spfuncs.py,sha256=lDVTp6CiQIuMxTfSzOi3-k6p97ayXJxdKPTf7j_4GWc,1987 +scipy/sparse/_sputils.py,sha256=rQS5Ymj1AT1RqBiu27BT2REMrFcWBNfLcm0jyHucFNQ,20444 +scipy/sparse/base.py,sha256=Kyn-S8HXKVlf_mHMGhcxbnLYqFyZpx5ydS707bnOhDM,609 +scipy/sparse/bsr.py,sha256=M_uhxnBwHnh0p4C7xE8tj82PiXATa4_gHIfOvLyh3gg,561 +scipy/sparse/compressed.py,sha256=lyEi-UD6ygcufeIqRBHpGfjVckp-vIlojYBjfnLc7Us,550 +scipy/sparse/construct.py,sha256=vfz2WK4r2bviiKZSUiIsfdDFDu9osyFlXF8ZsxSAn3E,812 +scipy/sparse/coo.py,sha256=ui1P-vcbPnLArYHxLWO06N1KFsCfw-CD6TWz_YSQ8-k,592 +scipy/sparse/csc.py,sha256=X9TL9GN1YtbJluM0Iq9_cDyDQBACYlJ7MToGQomvKOA,561 +scipy/sparse/csgraph/__init__.py,sha256=znrEd48JFLdlcevl8IFDSM104Yl1YvXC0O_f8OUWATs,7842 +scipy/sparse/csgraph/__pycache__/__init__.cpython-312.pyc,, +scipy/sparse/csgraph/__pycache__/_laplacian.cpython-312.pyc,, +scipy/sparse/csgraph/__pycache__/_validation.cpython-312.pyc,, +scipy/sparse/csgraph/_flow.cpython-312-x86_64-linux-gnu.so,sha256=6Rb7RVXwl1R5iYB9bk4jl40ZW6nz_4pgthF7xdkDLgI,184304 +scipy/sparse/csgraph/_laplacian.py,sha256=1UK6_5V1UyuWweoC8xQsFa5WoQjsiro_r2NuT0rT9lU,18277 +scipy/sparse/csgraph/_matching.cpython-312-x86_64-linux-gnu.so,sha256=V2NtifyynN9lT_pbbQEg_yXR-40NYIto56qvWyz2yRk,183048 +scipy/sparse/csgraph/_min_spanning_tree.cpython-312-x86_64-linux-gnu.so,sha256=n7fx4HmB2i1jEQXAqDra7Br8RLA-Ajj2RfNeJhiKHHI,95184 +scipy/sparse/csgraph/_reordering.cpython-312-x86_64-linux-gnu.so,sha256=E1RWNkTukgnyl3YWl7whXNsix5txjNV8kWkmA3rByek,153224 +scipy/sparse/csgraph/_shortest_path.cpython-312-x86_64-linux-gnu.so,sha256=QyZ5dX7E6MNKTAkD0eGqzuxtAjIEPiAU8d5DagCUoIg,499264 +scipy/sparse/csgraph/_tools.cpython-312-x86_64-linux-gnu.so,sha256=4exQjWDxLMfThiFRPA-plFk6D_PdqJq4Bvqem7LeSxY,179264 +scipy/sparse/csgraph/_traversal.cpython-312-x86_64-linux-gnu.so,sha256=ihELybJGxltCkKUp5PX4dmfaMHgdy9aQ4IM7fyW_Grs,359392 +scipy/sparse/csgraph/_validation.py,sha256=olThrikvQTZlWfOhH1KocGF5Yhl1xAUmOzLJkR3faS8,2854 +scipy/sparse/csgraph/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +scipy/sparse/csgraph/tests/__pycache__/__init__.cpython-312.pyc,, +scipy/sparse/csgraph/tests/__pycache__/test_connected_components.cpython-312.pyc,, +scipy/sparse/csgraph/tests/__pycache__/test_conversions.cpython-312.pyc,, +scipy/sparse/csgraph/tests/__pycache__/test_flow.cpython-312.pyc,, +scipy/sparse/csgraph/tests/__pycache__/test_graph_laplacian.cpython-312.pyc,, +scipy/sparse/csgraph/tests/__pycache__/test_index_arrays_dtype.cpython-312.pyc,, +scipy/sparse/csgraph/tests/__pycache__/test_matching.cpython-312.pyc,, +scipy/sparse/csgraph/tests/__pycache__/test_pydata_sparse.cpython-312.pyc,, +scipy/sparse/csgraph/tests/__pycache__/test_reordering.cpython-312.pyc,, +scipy/sparse/csgraph/tests/__pycache__/test_shortest_path.cpython-312.pyc,, +scipy/sparse/csgraph/tests/__pycache__/test_spanning_tree.cpython-312.pyc,, +scipy/sparse/csgraph/tests/__pycache__/test_traversal.cpython-312.pyc,, +scipy/sparse/csgraph/tests/test_connected_components.py,sha256=SBGESU0v2hO6TtJNwlqM42yPlWXsjhMAHgtc2z57BlM,5392 +scipy/sparse/csgraph/tests/test_conversions.py,sha256=9q2YqXf0Tq2jnK7S2gkBIt6GqYVuK2IkkiWqmTyANsc,2340 +scipy/sparse/csgraph/tests/test_flow.py,sha256=I7csygtef5f6Uv67t2y3UZsln8Gg4eS1RE5zr7Xm-Eg,7718 +scipy/sparse/csgraph/tests/test_graph_laplacian.py,sha256=2ySsHlIVnHzxtvcpGS8Sp2KLFYzEgkG3dTwtsxWVSDI,10947 +scipy/sparse/csgraph/tests/test_index_arrays_dtype.py,sha256=GbYoYTj8XylZGATZ48rYY695Bm5v-Q7tN_JACiK_mZA,3084 +scipy/sparse/csgraph/tests/test_matching.py,sha256=Jo6GCq7efO34TXG80VxpDAKzReDvrhXAThcq_6AWF9o,12373 +scipy/sparse/csgraph/tests/test_pydata_sparse.py,sha256=8iLiB9nXi3V3CCxX1fsiRA-pMDqhhgwu5LNG0Qyyt34,4990 +scipy/sparse/csgraph/tests/test_reordering.py,sha256=_WNqdGcU-WNhQRpjCq4Nhp8YY6cmVKb13au5sJPpzig,2569 +scipy/sparse/csgraph/tests/test_shortest_path.py,sha256=OskhIlm4irNwaFitjt2F0CKFh_HKWAmmRSaS_HdoJhk,18743 +scipy/sparse/csgraph/tests/test_spanning_tree.py,sha256=q4LYiXxfwWUc1io4vRVBr9uxMacfdefPvcRlb3TOEnw,2164 +scipy/sparse/csgraph/tests/test_traversal.py,sha256=7ma9viR6P2FecuWbZVt1kDXvbuHS6WTp1na9uBHKOBE,6212 +scipy/sparse/csr.py,sha256=MaN7K1B7ejJdKlpOZhrkP5YcBVxVPYk_io4okYc_koQ,561 +scipy/sparse/data.py,sha256=R-tvadwaAxifKWzzxhz0awKv2OA89AduDenjz1B2Z1A,504 +scipy/sparse/dia.py,sha256=KsOmYg5wsX-kOKM_vaarB_3LpeZo0eo1qC1UdsiBEnE,561 +scipy/sparse/dok.py,sha256=ki_m850wPAbA8w3qxkXqAWIDo5p2h5cU14c7Pg9PNos,561 +scipy/sparse/extract.py,sha256=6qT2PNOilsEhDWl6MhmgpveIuQr4QCs3LATwIrBroOQ,567 +scipy/sparse/lil.py,sha256=Gve3XHYPYZavcUPJz1TSOhjv6AtPpkKBHTzCK6FG8ek,562 +scipy/sparse/linalg/__init__.py,sha256=EuUOCB6Y1PFw1_vTfc7F5eMdCOMQcLwO7igl0_Qymik,4228 +scipy/sparse/linalg/__pycache__/__init__.cpython-312.pyc,, +scipy/sparse/linalg/__pycache__/_expm_multiply.cpython-312.pyc,, +scipy/sparse/linalg/__pycache__/_funm_multiply_krylov.cpython-312.pyc,, +scipy/sparse/linalg/__pycache__/_interface.cpython-312.pyc,, +scipy/sparse/linalg/__pycache__/_matfuncs.cpython-312.pyc,, +scipy/sparse/linalg/__pycache__/_norm.cpython-312.pyc,, +scipy/sparse/linalg/__pycache__/_onenormest.cpython-312.pyc,, +scipy/sparse/linalg/__pycache__/_special_sparse_arrays.cpython-312.pyc,, +scipy/sparse/linalg/__pycache__/_svdp.cpython-312.pyc,, +scipy/sparse/linalg/__pycache__/dsolve.cpython-312.pyc,, +scipy/sparse/linalg/__pycache__/eigen.cpython-312.pyc,, +scipy/sparse/linalg/__pycache__/interface.cpython-312.pyc,, +scipy/sparse/linalg/__pycache__/isolve.cpython-312.pyc,, +scipy/sparse/linalg/__pycache__/matfuncs.cpython-312.pyc,, +scipy/sparse/linalg/_dsolve/__init__.py,sha256=PIX7n_d0LOMZZZ65Dz4Mgz9trjKGB2kLaF16PQLkAIs,2039 +scipy/sparse/linalg/_dsolve/__pycache__/__init__.cpython-312.pyc,, +scipy/sparse/linalg/_dsolve/__pycache__/_add_newdocs.cpython-312.pyc,, +scipy/sparse/linalg/_dsolve/__pycache__/linsolve.cpython-312.pyc,, +scipy/sparse/linalg/_dsolve/_add_newdocs.py,sha256=4Nm6RAKQlKI4lQt4z20v0D6m0Vk8eqp0mIzEk5gfztA,3743 +scipy/sparse/linalg/_dsolve/_superlu.cpython-312-x86_64-linux-gnu.so,sha256=t8w2L-GzolofHA-ja1NQhpkBJzx5uGkjkswvjvJz3FQ,436625 +scipy/sparse/linalg/_dsolve/linsolve.py,sha256=3rZzIAw7AQk8hJRQo0LfUs2A19FTaRTjwCV-NrFvPPw,33636 +scipy/sparse/linalg/_dsolve/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +scipy/sparse/linalg/_dsolve/tests/__pycache__/__init__.cpython-312.pyc,, +scipy/sparse/linalg/_dsolve/tests/__pycache__/test_linsolve.cpython-312.pyc,, +scipy/sparse/linalg/_dsolve/tests/test_linsolve.py,sha256=bi7fXrxLHdB8ClyQ6HFDHBd1m_ZUcJNNBoqMkWTsJdg,34449 +scipy/sparse/linalg/_eigen/__init__.py,sha256=SwNho3iWZu_lJvcdSomA5cQdcDU8gocKbmRnm6Bf9-0,460 +scipy/sparse/linalg/_eigen/__pycache__/__init__.cpython-312.pyc,, +scipy/sparse/linalg/_eigen/__pycache__/_svds.cpython-312.pyc,, +scipy/sparse/linalg/_eigen/__pycache__/_svds_doc.cpython-312.pyc,, +scipy/sparse/linalg/_eigen/_svds.py,sha256=IOVsfTBRAMKTJzU8YXXIZ80eSi0TiwEc909RPWo7uVo,19790 +scipy/sparse/linalg/_eigen/_svds_doc.py,sha256=0_sC8kKbu3b5BYpGl16sPLrZu6mDxiFhj8xkbG2w5-U,15003 +scipy/sparse/linalg/_eigen/arpack/__init__.py,sha256=zDxf9LokyPitn3_0d-PUXoBCh6tWK0eUSvsAj6nkXI0,562 +scipy/sparse/linalg/_eigen/arpack/__pycache__/__init__.cpython-312.pyc,, +scipy/sparse/linalg/_eigen/arpack/__pycache__/arpack.cpython-312.pyc,, +scipy/sparse/linalg/_eigen/arpack/_arpacklib.cpython-312-x86_64-linux-gnu.so,sha256=vh8WajC8erUGqBkiQw2CsWrSATyw37eRfqFVEBBrRsM,223721 +scipy/sparse/linalg/_eigen/arpack/arpack.py,sha256=rE9KYu5JOJdq9ou-I3hvwKSJZagWXJtB5QB6Znv2300,70242 +scipy/sparse/linalg/_eigen/arpack/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +scipy/sparse/linalg/_eigen/arpack/tests/__pycache__/__init__.cpython-312.pyc,, +scipy/sparse/linalg/_eigen/arpack/tests/__pycache__/test_arpack.cpython-312.pyc,, +scipy/sparse/linalg/_eigen/arpack/tests/test_arpack.py,sha256=95Bju44Bk8zYeOnwkjZK9e6tRYJG1adoYWhPT6sQkdE,24892 +scipy/sparse/linalg/_eigen/lobpcg/__init__.py,sha256=E5JEPRoVz-TaLrj_rPm5LP3jCwei4XD-RxbcxYwf5lM,420 +scipy/sparse/linalg/_eigen/lobpcg/__pycache__/__init__.cpython-312.pyc,, +scipy/sparse/linalg/_eigen/lobpcg/__pycache__/lobpcg.cpython-312.pyc,, +scipy/sparse/linalg/_eigen/lobpcg/lobpcg.py,sha256=-oC96-3vXNw06Qf6uYOZN-XgLYvq2DyRn3HZgMqsXV4,41982 +scipy/sparse/linalg/_eigen/lobpcg/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +scipy/sparse/linalg/_eigen/lobpcg/tests/__pycache__/__init__.cpython-312.pyc,, +scipy/sparse/linalg/_eigen/lobpcg/tests/__pycache__/test_lobpcg.cpython-312.pyc,, +scipy/sparse/linalg/_eigen/lobpcg/tests/test_lobpcg.py,sha256=FKY5DrDsjeKl9dZ5_InqvgYLiZw85qLa5gkVsr8dB9o,27475 +scipy/sparse/linalg/_eigen/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +scipy/sparse/linalg/_eigen/tests/__pycache__/__init__.cpython-312.pyc,, +scipy/sparse/linalg/_eigen/tests/__pycache__/test_svds.cpython-312.pyc,, +scipy/sparse/linalg/_eigen/tests/test_svds.py,sha256=S1ENTl-9QqJ1BO3XHAiqqqvJL2vW-IJYYr43hoOv-C8,37153 +scipy/sparse/linalg/_expm_multiply.py,sha256=QlxkJdeMv0k5Jpt14LrkffCxj0gp9FO8vqaQhkAy56I,26484 +scipy/sparse/linalg/_funm_multiply_krylov.py,sha256=gi1Oate43OCnX-aDHMX5rp_vUzV5jCBHpjSL2xParSE,12182 +scipy/sparse/linalg/_interface.py,sha256=zUGuZfb0xSgegcXapbQAKroh8qgARcwC6olIYrZUwOw,41375 +scipy/sparse/linalg/_isolve/__init__.py,sha256=Z_eQUYbe6RWMSNi09T9TfPEWm8RsVxcIKYAlihM-U-c,479 +scipy/sparse/linalg/_isolve/__pycache__/__init__.cpython-312.pyc,, +scipy/sparse/linalg/_isolve/__pycache__/_gcrotmk.cpython-312.pyc,, +scipy/sparse/linalg/_isolve/__pycache__/iterative.cpython-312.pyc,, +scipy/sparse/linalg/_isolve/__pycache__/lgmres.cpython-312.pyc,, +scipy/sparse/linalg/_isolve/__pycache__/lsmr.cpython-312.pyc,, +scipy/sparse/linalg/_isolve/__pycache__/lsqr.cpython-312.pyc,, +scipy/sparse/linalg/_isolve/__pycache__/minres.cpython-312.pyc,, +scipy/sparse/linalg/_isolve/__pycache__/tfqmr.cpython-312.pyc,, +scipy/sparse/linalg/_isolve/__pycache__/utils.cpython-312.pyc,, +scipy/sparse/linalg/_isolve/_gcrotmk.py,sha256=rvripzQWdHkuPDU0U2S1nLY_IzW_iSORKIzGoozQfpo,15868 +scipy/sparse/linalg/_isolve/iterative.py,sha256=D-aG7kkwimblmk9hA0aoup9tXrcWb4R0j3VYQgyTinA,33619 +scipy/sparse/linalg/_isolve/lgmres.py,sha256=BxeJaBq3F3BXXiKtLcmBGdA8sr0L_aYbeos9WupbE1E,8704 +scipy/sparse/linalg/_isolve/lsmr.py,sha256=cL9Mj_MOPHlrTrb2ILFXoFHVu6zSk3rv35RzUKARjn4,15841 +scipy/sparse/linalg/_isolve/lsqr.py,sha256=3-PUPB7Wxju8bQCmycocW9MYplfDHlVsdaHPOrC6XGk,21425 +scipy/sparse/linalg/_isolve/minres.py,sha256=cNYvsREvI5LjD9G5dmxLyboqQQ1AYVAwWDFdozLOAwE,11183 +scipy/sparse/linalg/_isolve/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +scipy/sparse/linalg/_isolve/tests/__pycache__/__init__.cpython-312.pyc,, +scipy/sparse/linalg/_isolve/tests/__pycache__/test_gcrotmk.cpython-312.pyc,, +scipy/sparse/linalg/_isolve/tests/__pycache__/test_iterative.cpython-312.pyc,, +scipy/sparse/linalg/_isolve/tests/__pycache__/test_lgmres.cpython-312.pyc,, +scipy/sparse/linalg/_isolve/tests/__pycache__/test_lsmr.cpython-312.pyc,, +scipy/sparse/linalg/_isolve/tests/__pycache__/test_lsqr.cpython-312.pyc,, +scipy/sparse/linalg/_isolve/tests/__pycache__/test_minres.cpython-312.pyc,, +scipy/sparse/linalg/_isolve/tests/__pycache__/test_utils.cpython-312.pyc,, +scipy/sparse/linalg/_isolve/tests/test_gcrotmk.py,sha256=3V-JDPsjIsCce7P06cBZJU0LEK3ioBmD0KSezcOBvQY,6069 +scipy/sparse/linalg/_isolve/tests/test_iterative.py,sha256=jpkNuT1PDDVOlg3wAGf1RzqaxTVM-vU12qt741_hMcc,31851 +scipy/sparse/linalg/_isolve/tests/test_lgmres.py,sha256=0VpxsyGSfTNkdesKkQ3qAwFd7bioma9KFHqxtoUyj4E,7512 +scipy/sparse/linalg/_isolve/tests/test_lsmr.py,sha256=eQC9Tc1HBY1PFPoD3zk6-7ouRONwuTAcB58ZV__2EXk,7079 +scipy/sparse/linalg/_isolve/tests/test_lsqr.py,sha256=30oPY_6ZGpfa6tlM3MyAxh-nTnFjka4lMAA862h6MzQ,4024 +scipy/sparse/linalg/_isolve/tests/test_minres.py,sha256=0iYiMYBIFRfnrAcNXejeMoedVlNATydOx9sA2hbfH2k,4425 +scipy/sparse/linalg/_isolve/tests/test_utils.py,sha256=VlmvctRaQtjuYvQuoe2t2ufib74Tua_7qsiVrs3j-p0,265 +scipy/sparse/linalg/_isolve/tfqmr.py,sha256=H0YV-bkw0IIsQOuK0nO2vBJzdbGFCPVfA-RD2whDyv4,6231 +scipy/sparse/linalg/_isolve/utils.py,sha256=O0qnqWTjNnlaT_Lo7nWUBGunbZpeunWP4amdgfE8trA,3490 +scipy/sparse/linalg/_matfuncs.py,sha256=jf9ORWJKMRGOxztFookCcQQmRp1pe6okmFwbf33lZWM,29333 +scipy/sparse/linalg/_norm.py,sha256=6y-pvAoxutHTfHOVrUimw_QvxBPXXWiPo3teGBLwYZk,6307 +scipy/sparse/linalg/_onenormest.py,sha256=4-0H3UbMoNPLDvUujn6-Hjyu7YrxRSqEbLuQO9r-LFM,11945 +scipy/sparse/linalg/_propack.cpython-312-x86_64-linux-gnu.so,sha256=sPyYoCszZwJ7nyLyr6zKLlrle2Vgd-IWlGOYv8XrAo4,149313 +scipy/sparse/linalg/_special_sparse_arrays.py,sha256=y0MvaC-mySFOPbWfEhO8zYFNvQu2K_zcfp4m9DEwouQ,34350 +scipy/sparse/linalg/_svdp.py,sha256=zddR5LOQwGHkPAj7FsmkPeCc5JEt5wa-qOi7JNy4Qe0,10920 +scipy/sparse/linalg/dsolve.py,sha256=fvCzVUda-h-WzwGWDss4FVuv6TVE-OKHzARBlUCDIJw,654 +scipy/sparse/linalg/eigen.py,sha256=4BTo8Tc9SNQaruyrF4gRdFE5NstiA0XH9I44IyikZQ4,626 +scipy/sparse/linalg/interface.py,sha256=_KXBkGhZWvY_ZmGixqWMZe6J64bCPdjtrqr63HvicUI,573 +scipy/sparse/linalg/isolve.py,sha256=diSAxpbYg8PeH75QOEE-CREO8p39f4BZK2dGynJDKIc,649 +scipy/sparse/linalg/matfuncs.py,sha256=H2qJl4ZZqZ4bI-E9NCbu1oFfto0EdFxCTKTugMPHRHg,570 +scipy/sparse/linalg/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +scipy/sparse/linalg/tests/__pycache__/__init__.cpython-312.pyc,, +scipy/sparse/linalg/tests/__pycache__/test_expm_multiply.cpython-312.pyc,, +scipy/sparse/linalg/tests/__pycache__/test_funm_multiply_krylov.cpython-312.pyc,, +scipy/sparse/linalg/tests/__pycache__/test_interface.cpython-312.pyc,, +scipy/sparse/linalg/tests/__pycache__/test_matfuncs.cpython-312.pyc,, +scipy/sparse/linalg/tests/__pycache__/test_norm.cpython-312.pyc,, +scipy/sparse/linalg/tests/__pycache__/test_onenormest.cpython-312.pyc,, +scipy/sparse/linalg/tests/__pycache__/test_propack.cpython-312.pyc,, +scipy/sparse/linalg/tests/__pycache__/test_pydata_sparse.cpython-312.pyc,, +scipy/sparse/linalg/tests/__pycache__/test_special_sparse_arrays.cpython-312.pyc,, +scipy/sparse/linalg/tests/propack_test_data.npz,sha256=JdQuBbtstWctsi6_jffxE-TLIbGBdJH63JljQaVCNG0,600259 +scipy/sparse/linalg/tests/test_expm_multiply.py,sha256=jZ7vd6Ik3EkqLrL8irNQNd9xsoOPBSLUocn73OF9gQQ,15021 +scipy/sparse/linalg/tests/test_funm_multiply_krylov.py,sha256=qPCtueV6hLU4K8bdAQNj04LswPVpnalqo9sLcxvWA4Y,7301 +scipy/sparse/linalg/tests/test_interface.py,sha256=C12Y_1IaLlLak2ly84o1jWaYJ6GKZUfDhvO3Fc3B798,50900 +scipy/sparse/linalg/tests/test_matfuncs.py,sha256=wvjXUiOFG4BZ95OLSjnAswbizYqG10WTMaZlFtu6iW0,22017 +scipy/sparse/linalg/tests/test_norm.py,sha256=a2TTnTfgmG9lIBo0QxLyK9SWcr0KIXB5urOJ3TMoKwc,6572 +scipy/sparse/linalg/tests/test_onenormest.py,sha256=5Qw42neFLeeXS2eE6ww8zJp4iYrp8NQgbKuL74zYGa4,8637 +scipy/sparse/linalg/tests/test_propack.py,sha256=nIiJNNiC72dAHMWSvOqrH2AsbM5pp2RbRHa5pJGtHqc,7453 +scipy/sparse/linalg/tests/test_pydata_sparse.py,sha256=eawVssB3pRqxRmfzoHAVY7xvJU-Flo7JYsnoxhr76Mw,7302 +scipy/sparse/linalg/tests/test_special_sparse_arrays.py,sha256=H3tXLQ9rDHr2JKgZzfUPrVlgeUYLFBZu2h1G9kL60gE,13125 +scipy/sparse/sparsetools.py,sha256=pCcuyQYvIahrvr43V398XHyqwcGtWCPLFH6n1uSYmB8,516 +scipy/sparse/spfuncs.py,sha256=TWpfkZk3JErNajVFUH5B85d3r6UuSv0Rsx0lMtUSac0,508 +scipy/sparse/sputils.py,sha256=PsqT7RUmiO8ph5jG8GHYmPbacDQFljjc0SL7RMxweJU,508 +scipy/sparse/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +scipy/sparse/tests/__pycache__/__init__.cpython-312.pyc,, +scipy/sparse/tests/__pycache__/test_64bit.cpython-312.pyc,, +scipy/sparse/tests/__pycache__/test_arithmetic1d.cpython-312.pyc,, +scipy/sparse/tests/__pycache__/test_array_api.cpython-312.pyc,, +scipy/sparse/tests/__pycache__/test_base.cpython-312.pyc,, +scipy/sparse/tests/__pycache__/test_common1d.cpython-312.pyc,, +scipy/sparse/tests/__pycache__/test_construct.cpython-312.pyc,, +scipy/sparse/tests/__pycache__/test_coo.cpython-312.pyc,, +scipy/sparse/tests/__pycache__/test_csc.cpython-312.pyc,, +scipy/sparse/tests/__pycache__/test_csr.cpython-312.pyc,, +scipy/sparse/tests/__pycache__/test_dok.cpython-312.pyc,, +scipy/sparse/tests/__pycache__/test_extract.cpython-312.pyc,, +scipy/sparse/tests/__pycache__/test_indexing1d.cpython-312.pyc,, +scipy/sparse/tests/__pycache__/test_matrix_io.cpython-312.pyc,, +scipy/sparse/tests/__pycache__/test_minmax1d.cpython-312.pyc,, +scipy/sparse/tests/__pycache__/test_sparsetools.cpython-312.pyc,, +scipy/sparse/tests/__pycache__/test_spfuncs.cpython-312.pyc,, +scipy/sparse/tests/__pycache__/test_sputils.cpython-312.pyc,, +scipy/sparse/tests/test_64bit.py,sha256=eJvrFc_qq7visPMN5Ol9TSz0U6UbqFdqLsmiJSlTli8,13158 +scipy/sparse/tests/test_arithmetic1d.py,sha256=4woi2qefAsFXR94Tjj_rlvOfdI-e_Vl6wGAFFw01HkI,11984 +scipy/sparse/tests/test_array_api.py,sha256=Kxt_SbxlEtEEp201Wqdqk4QMckqZvUTX7m3xme9dX6U,14811 +scipy/sparse/tests/test_base.py,sha256=CWsnpjDiVhSXzssX2JnwDNGkCzXESBwBybsSb7OIimk,227404 +scipy/sparse/tests/test_common1d.py,sha256=j_gxMwdHb-naLk9OZ9PYexfOJcqswmVtvCs7Vdy9sG4,15653 +scipy/sparse/tests/test_construct.py,sha256=pTEYxMNCndJETODimJEMVA0fq-eS83UIMDdLW6DXA7E,50923 +scipy/sparse/tests/test_coo.py,sha256=k1MVYQMACYcEb0o5x5QajSWfDslzVZbhw3_G2HGyw_M,49052 +scipy/sparse/tests/test_csc.py,sha256=qq-0sTMsKz6_xqPJX-eyYPxvXy0zCgSAP7W_8iXpu_4,4313 +scipy/sparse/tests/test_csr.py,sha256=Pfm8yt5rxbpsf_EDsr1X3XXQrtqZ-rvtF5PdHozD2MU,10220 +scipy/sparse/tests/test_dok.py,sha256=1Jx1P9T6vKgn6lcIaDpQOh_xUtz4lIaeuDwUE-rKU7s,6683 +scipy/sparse/tests/test_extract.py,sha256=65CAfIzmo0YSvJ1yiaqQxfAlB4tr_FAvMt9lK2Ff5IA,2000 +scipy/sparse/tests/test_indexing1d.py,sha256=g_VZO2iFmMz_Yq8AZILOKW3RdRFt6YS67rMLB4ZdT4I,21067 +scipy/sparse/tests/test_matrix_io.py,sha256=52GlblvdBhWxAlxLTcE43o3QyzFdQpyY1GOhiA6EL2w,3449 +scipy/sparse/tests/test_minmax1d.py,sha256=UBeHcN4Pw_VAPXtgsyDev5pK3eXvisiiLjibeaiA8S0,4269 +scipy/sparse/tests/test_sparsetools.py,sha256=S5i2-h98qETawJxE_SRGWvhE4dtA8qMwQ9FplpAnAPg,10613 +scipy/sparse/tests/test_spfuncs.py,sha256=ECs34sgYYhTBWe4hIkx357obH2lLsnJWkh7TfacjThw,3258 +scipy/sparse/tests/test_sputils.py,sha256=RXx_xjrYf0xOUyd6AeeKy4UoeHS3kHxna_2ZXiHGYQs,16486 +scipy/spatial/__init__.py,sha256=046ie57sKOllPbjhGpGOzHXlJ-_JIVuE-9C49p8aNhs,3780 +scipy/spatial/__pycache__/__init__.cpython-312.pyc,, +scipy/spatial/__pycache__/_geometric_slerp.cpython-312.pyc,, +scipy/spatial/__pycache__/_kdtree.cpython-312.pyc,, +scipy/spatial/__pycache__/_plotutils.cpython-312.pyc,, +scipy/spatial/__pycache__/_procrustes.cpython-312.pyc,, +scipy/spatial/__pycache__/_spherical_voronoi.cpython-312.pyc,, +scipy/spatial/__pycache__/ckdtree.cpython-312.pyc,, +scipy/spatial/__pycache__/distance.cpython-312.pyc,, +scipy/spatial/__pycache__/kdtree.cpython-312.pyc,, +scipy/spatial/__pycache__/qhull.cpython-312.pyc,, +scipy/spatial/_ckdtree.cpython-312-x86_64-linux-gnu.so,sha256=PSsGJvJcQZOezVeo-fmbTtfy8HTznpr0H2PDmUYkQJk,777720 +scipy/spatial/_distance_pybind.cpython-312-x86_64-linux-gnu.so,sha256=XvAIAV67p4i5jGRwR6-ppGsMzMQ0DI3EqIDiWH5LW8k,616280 +scipy/spatial/_distance_wrap.cpython-312-x86_64-linux-gnu.so,sha256=SiWmXmPo-tZCBtddjwhfH4dInEU9xAZe2TRXWW8SkAw,120904 +scipy/spatial/_geometric_slerp.py,sha256=Ohc2AeoQWv8rgIJUIO9Il5U6XBj_L772G6hYLzQiPoM,8619 +scipy/spatial/_hausdorff.cpython-312-x86_64-linux-gnu.so,sha256=i75sXc5EDEKhbJ8NOvNNEti0mcx6oFU1XNj7Rx8BKbo,90912 +scipy/spatial/_kdtree.py,sha256=Ka6bcpzXvlmT79x25XfLvkUNTn6WfmKP2GIpXwPD54k,37882 +scipy/spatial/_plotutils.py,sha256=nKwz6-pMo240WMv4EGLGEwPj8oQ_gypUxENEzrnEN70,7191 +scipy/spatial/_procrustes.py,sha256=qvhHPHt_OIKo-ge_k19S4VWqbP6ZgMXLVnNey0JxTb8,4427 +scipy/spatial/_qhull.cpython-312-x86_64-linux-gnu.so,sha256=8Ozg54OOnR7lwxJikv7SJedAc02ZKAsPqj_xz3JrUxo,935776 +scipy/spatial/_qhull.pyi,sha256=ofIDWfFY8M45z00uD1TmBDVTKyK2GJfoWIbdjmf4X0c,5954 +scipy/spatial/_spherical_voronoi.py,sha256=0TWTOakv7d8Czp7ig4x0UX_3xE2KjyAuYPk7UC2_aiU,13512 +scipy/spatial/_voronoi.cpython-312-x86_64-linux-gnu.so,sha256=6LquJdVlxEqm_vGOCLZ-Oi2zLQECOh5IpCUB-whPbh4,82392 +scipy/spatial/_voronoi.pyi,sha256=aAOiF4fvHz18hmuSjieKkRItssD443p2_w1ggXOIs1g,126 +scipy/spatial/ckdtree.py,sha256=0IssUT415ieBOJuvfZJxIra-TeYyd0KxDGLrXDZ_GGw,523 +scipy/spatial/distance.py,sha256=4M0IcdSDo1Xq9lbuCEnmV5r0X1efM-xMoybfJiaz8ZY,92035 +scipy/spatial/distance.pyi,sha256=T_hhLNuJ0TXEykdpgg6mxnQJYW0wggVMBJ5ZrTXbbx0,5007 +scipy/spatial/kdtree.py,sha256=ZYJL8A_WpLyEH29aFQGLbxd9ttFdGBgdglbgAfsvhz8,636 +scipy/spatial/qhull.py,sha256=aFE-KscuINt6QIhFC2dqhwFCYu3HSBkVXDH5exHH71s,622 +scipy/spatial/qhull_src/COPYING_QHULL.txt,sha256=EG1VyTH9aoSCLlNF2QAnPQWfHCcxDQJWfMsxPF0YxV0,1720 +scipy/spatial/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +scipy/spatial/tests/__pycache__/__init__.cpython-312.pyc,, +scipy/spatial/tests/__pycache__/test__plotutils.cpython-312.pyc,, +scipy/spatial/tests/__pycache__/test__procrustes.cpython-312.pyc,, +scipy/spatial/tests/__pycache__/test_distance.cpython-312.pyc,, +scipy/spatial/tests/__pycache__/test_hausdorff.cpython-312.pyc,, +scipy/spatial/tests/__pycache__/test_kdtree.cpython-312.pyc,, +scipy/spatial/tests/__pycache__/test_qhull.cpython-312.pyc,, +scipy/spatial/tests/__pycache__/test_slerp.cpython-312.pyc,, +scipy/spatial/tests/__pycache__/test_spherical_voronoi.cpython-312.pyc,, +scipy/spatial/tests/data/cdist-X1.txt,sha256=ULnYAgX2_AwOVF-VE7XfnW5S0pzhx7UAoocxSnXMaWs,5750 +scipy/spatial/tests/data/cdist-X2.txt,sha256=_IJVjXsp3pvd8NNPNTLmVbHOrzl_RiEXz7cb86NfvZ4,11500 +scipy/spatial/tests/data/degenerate_pointset.npz,sha256=BIq8Hd2SS_LU0fIWAVVS7ZQx-emVRvvzgnaO2lh4gXU,22548 +scipy/spatial/tests/data/iris.txt,sha256=k19QSfkqhMmByqNMzwWDmM6wf5dt6whdGyfAyUO3AW0,15000 +scipy/spatial/tests/data/pdist-boolean-inp.txt,sha256=5Z9SMsXrtmzeUwJlVmGkrPDC_Km7nVpZIbBl7p3Hdc0,50000 +scipy/spatial/tests/data/pdist-chebyshev-ml-iris.txt,sha256=Yerj1wqIzcdyULlha-q02WBNGyS2Q5o2wAr0XVEkzis,178801 +scipy/spatial/tests/data/pdist-chebyshev-ml.txt,sha256=NEd2b-DONqUMV9f8gJ2yod17C_5fXGHHZ38PeFsXkyw,3041 +scipy/spatial/tests/data/pdist-cityblock-ml-iris.txt,sha256=UCWZJeMkMajbpjeG0FW60b0q-4r1geAyguNY6Chx5bM,178801 +scipy/spatial/tests/data/pdist-cityblock-ml.txt,sha256=8Iq7cF8oMJjpqd6qsDt_mKPQK0T8Ldot2P8C5rgbGIU,3041 +scipy/spatial/tests/data/pdist-correlation-ml-iris.txt,sha256=l2kEAu0Pm3OsFJsQtHf9Qdy5jnnoOu1v3MooBISnjP0,178801 +scipy/spatial/tests/data/pdist-correlation-ml.txt,sha256=S4GY3z-rf_BGuHmsnColMvR8KwYDyE9lqEbYT_a3Qag,3041 +scipy/spatial/tests/data/pdist-cosine-ml-iris.txt,sha256=hQzzoZrmw9OXAbqkxC8eTFXtJZrbFzMgcWMLbJlOv7U,178801 +scipy/spatial/tests/data/pdist-cosine-ml.txt,sha256=P92Tm6Ie8xg4jGSP7k7bmFRAP5MfxtVR_KacS73a6PI,3041 +scipy/spatial/tests/data/pdist-double-inp.txt,sha256=0Sx5yL8D8pyYDXTIBZAoTiSsRpG_eJz8uD2ttVrklhU,50000 +scipy/spatial/tests/data/pdist-euclidean-ml-iris.txt,sha256=3-UwBM7WZa4aCgmW_ZAdRSq8KYMq2gnkIUqU73Z0OLI,178801 +scipy/spatial/tests/data/pdist-euclidean-ml.txt,sha256=rkQA2-_d7uByKmw003lFXbXNDjHrUGBplZ8nB_TU5pk,3041 +scipy/spatial/tests/data/pdist-hamming-ml.txt,sha256=IAYroplsdz6n7PZ-vIMIJ4FjG9jC1OSxc3-oVJdSFDM,3041 +scipy/spatial/tests/data/pdist-jaccard-ml.txt,sha256=Zb42SoVEnlTj_N_ndnym3_d4RNZWeHm290hTtpp_zO8,3041 +scipy/spatial/tests/data/pdist-jensenshannon-ml-iris.txt,sha256=L7STTmlRX-z-YvksmiAxEe1UoTmDnQ_lnAjZH53Szp0,172738 +scipy/spatial/tests/data/pdist-jensenshannon-ml.txt,sha256=-sZUikGMWskONojs6fJIMX8VEWpviYYg4u1vipY6Bak,2818 +scipy/spatial/tests/data/pdist-minkowski-3.2-ml-iris.txt,sha256=N5L5CxRT5yf_vq6pFjorJ09Sr-RcnrAlH-_F3kEsyUU,178801 +scipy/spatial/tests/data/pdist-minkowski-3.2-ml.txt,sha256=DRgzqxRtvQVzFnpFAjNC9TDNgRtk2ZRkWPyAaeOx3q4,3041 +scipy/spatial/tests/data/pdist-minkowski-5.8-ml-iris.txt,sha256=jz7SGKU8GuJWASH2u428QL9c-G_-8nZvOFSOUlMdCyA,178801 +scipy/spatial/tests/data/pdist-seuclidean-ml-iris.txt,sha256=37H01o6GibccR_hKIwwbWxGX0Tuxnb-4Qc6rmDxwwUI,178801 +scipy/spatial/tests/data/pdist-seuclidean-ml.txt,sha256=YmcI7LZ6i-Wg1wjAkLVX7fmxzCj621Pc5itO3PvCm_k,3041 +scipy/spatial/tests/data/pdist-spearman-ml.txt,sha256=IrtJmDQliv4lDZ_UUjkZNso3EZyu7pMACxMB-rvHUj0,3041 +scipy/spatial/tests/data/random-bool-data.txt,sha256=MHAQdE4hPVzgu-csVVbm1DNJ80dP7XthJ1kb2In8ImM,6000 +scipy/spatial/tests/data/random-double-data.txt,sha256=GA8hYrHsTBeS864GJf0X6JRTvGlbpM8P8sJairmfnBU,75000 +scipy/spatial/tests/data/random-int-data.txt,sha256=xTUbCgoT4X8nll3kXu7S9lv-eJzZtwewwm5lFepxkdQ,10266 +scipy/spatial/tests/data/random-uint-data.txt,sha256=8IPpXhwglxzinL5PcK-PEqleZRlNKdx3zCVMoDklyrY,8711 +scipy/spatial/tests/data/selfdual-4d-polytope.txt,sha256=rkVhIL1mupGuqDrw1a5QFaODzZkdoaLMbGI_DbLLTzM,480 +scipy/spatial/tests/test__plotutils.py,sha256=fASbg0i7iLiJIEj5vIkiDuTq3wU0z3mKJY019kzKrFk,3814 +scipy/spatial/tests/test__procrustes.py,sha256=wmmnUHRdw_oID0YLi404IEWPH6vEGhvHXSeGPY_idHo,4974 +scipy/spatial/tests/test_distance.py,sha256=s3CDniGMJLAZDmuVn-JmOwpf0bF2mEgof0XZOJCEeq0,84727 +scipy/spatial/tests/test_hausdorff.py,sha256=XcDEzwFuOR9BaLegIj-DPp5GrAi_RsvcW8oGqJf0xkg,8217 +scipy/spatial/tests/test_kdtree.py,sha256=6g9Eks8g1Jkb2vfAHeTuhZWaobt2HpcC0-3KbvIki9g,53068 +scipy/spatial/tests/test_qhull.py,sha256=dAWSqlEgakNorpW0pDMGwEbG7J3QT1neRX18M7v-XMI,50208 +scipy/spatial/tests/test_slerp.py,sha256=KMmqbcefISrTnzWAmY3IBDaJw13fnRXHddLsUKAn5Tk,18455 +scipy/spatial/tests/test_spherical_voronoi.py,sha256=YCVSpO7-RrmKaAivwrLh5rZJ6CTTNKuIJ9iyhXsi178,14500 +scipy/spatial/transform/__init__.py,sha256=XqVBg4ekbA48c9r9A9xmdIkchyxy-nyLEYwGq71IXqQ,826 +scipy/spatial/transform/__pycache__/__init__.cpython-312.pyc,, +scipy/spatial/transform/__pycache__/_rigid_transform.cpython-312.pyc,, +scipy/spatial/transform/__pycache__/_rigid_transform_xp.cpython-312.pyc,, +scipy/spatial/transform/__pycache__/_rotation.cpython-312.pyc,, +scipy/spatial/transform/__pycache__/_rotation_groups.cpython-312.pyc,, +scipy/spatial/transform/__pycache__/_rotation_spline.cpython-312.pyc,, +scipy/spatial/transform/__pycache__/_rotation_xp.cpython-312.pyc,, +scipy/spatial/transform/__pycache__/rotation.cpython-312.pyc,, +scipy/spatial/transform/_rigid_transform.py,sha256=K-QGqx3Qy0KJb-z1kVabu1hhS8QJtpURiEZRKJHP2-w,72976 +scipy/spatial/transform/_rigid_transform_cy.cpython-312-x86_64-linux-gnu.so,sha256=jQJWA9OweXFq9TMeC0z3o_owINVuwQYgDZJY4HlgyGI,245720 +scipy/spatial/transform/_rigid_transform_xp.py,sha256=x71XlT65sHwKBcLr7aj1DUWhq3SfFGWka79L5-97Ym4,16849 +scipy/spatial/transform/_rotation.py,sha256=URbC-1rRJZaOTuFplXN-I1R7rzH6anrhiK5RQE2fYPg,107155 +scipy/spatial/transform/_rotation_cy.cpython-312-x86_64-linux-gnu.so,sha256=vbKnLF2X414KPL9k0gyGyfp-gbyx3fggjNeDLao-mzk,573960 +scipy/spatial/transform/_rotation_groups.py,sha256=XS-9K6xYnnwWywMMYMVznBYc1-0DPhADHQp_FIT3_f8,4422 +scipy/spatial/transform/_rotation_spline.py,sha256=psUsaAMZZXk5g3Rgb7RSOIchYCFQbRGRB6suKbfU6KU,14244 +scipy/spatial/transform/_rotation_xp.py,sha256=n0lndkTTh7Oa8hemU3jeWbdDHuOJV2kSLr5m6aImnik,44387 +scipy/spatial/transform/rotation.py,sha256=co5Bpny89EfCywilEeeLDvJPESBLrSXTCCJqRlfdYzg,556 +scipy/spatial/transform/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +scipy/spatial/transform/tests/__pycache__/__init__.cpython-312.pyc,, +scipy/spatial/transform/tests/__pycache__/test_rigid_transform.cpython-312.pyc,, +scipy/spatial/transform/tests/__pycache__/test_rotation.cpython-312.pyc,, +scipy/spatial/transform/tests/__pycache__/test_rotation_groups.cpython-312.pyc,, +scipy/spatial/transform/tests/__pycache__/test_rotation_spline.cpython-312.pyc,, +scipy/spatial/transform/tests/test_rigid_transform.py,sha256=WnQAvQvleGbOX8uYmWZky-jeZugAZP1Mp1u1XLsmeVM,60931 +scipy/spatial/transform/tests/test_rotation.py,sha256=wd9LYEfgv45rUTdUs5K8sh-dfF8bTDo1TwCdih9Dh5g,117895 +scipy/spatial/transform/tests/test_rotation_groups.py,sha256=cKn8p05weUAg_QXX_Y2F_4SeMZb0yQSD5sWyDXakXC4,5576 +scipy/spatial/transform/tests/test_rotation_spline.py,sha256=9rcVmUBVFmRh9EbxkVwBFsT9LByg5H7-3YwaCs48sH4,5886 +scipy/special/__init__.pxd,sha256=l9Y21wnx5fZLvrxCeCMUWQvBI5gHx7LBhimDWptxke8,42 +scipy/special/__init__.py,sha256=Ipb5wbYsXs2WWoz6pZcJsKEakQeGPwTNmh_wn4FLiL0,31851 +scipy/special/__pycache__/__init__.cpython-312.pyc,, +scipy/special/__pycache__/_add_newdocs.cpython-312.pyc,, +scipy/special/__pycache__/_basic.cpython-312.pyc,, +scipy/special/__pycache__/_ellip_harm.cpython-312.pyc,, +scipy/special/__pycache__/_input_validation.cpython-312.pyc,, +scipy/special/__pycache__/_lambertw.cpython-312.pyc,, +scipy/special/__pycache__/_logsumexp.cpython-312.pyc,, +scipy/special/__pycache__/_mptestutils.cpython-312.pyc,, +scipy/special/__pycache__/_multiufuncs.cpython-312.pyc,, +scipy/special/__pycache__/_orthogonal.cpython-312.pyc,, +scipy/special/__pycache__/_sf_error.cpython-312.pyc,, +scipy/special/__pycache__/_spfun_stats.cpython-312.pyc,, +scipy/special/__pycache__/_spherical_bessel.cpython-312.pyc,, +scipy/special/__pycache__/_support_alternative_backends.cpython-312.pyc,, +scipy/special/__pycache__/_testutils.cpython-312.pyc,, +scipy/special/__pycache__/_ufunc_tools.cpython-312.pyc,, +scipy/special/__pycache__/add_newdocs.cpython-312.pyc,, +scipy/special/__pycache__/basic.cpython-312.pyc,, +scipy/special/__pycache__/orthogonal.cpython-312.pyc,, +scipy/special/__pycache__/sf_error.cpython-312.pyc,, +scipy/special/__pycache__/specfun.cpython-312.pyc,, +scipy/special/__pycache__/spfun_stats.cpython-312.pyc,, +scipy/special/_add_newdocs.py,sha256=vMlBib5_I6KDDEvNQst78J3WV3UDIeuAm3GreFeWj6s,241780 +scipy/special/_basic.py,sha256=1KNmuKYQZZubaJaiJWtRk8j2OFO0Yl96Ex6hYwG4XKU,108195 +scipy/special/_comb.cpython-312-x86_64-linux-gnu.so,sha256=aABk9tLMzinBs5N1Ys24mFYMlK9DTDpqQpOUhY5JFfs,58664 +scipy/special/_ellip_harm.py,sha256=zAGPkiLk1g2_51HPD5N4narv5Q0pBek_tOEJqdjf9do,5396 +scipy/special/_ellip_harm_2.cpython-312-x86_64-linux-gnu.so,sha256=tHSM7l0XVGfDosaPyohs9ge9ClgJZHLdC-ui8vRofaA,123528 +scipy/special/_gufuncs.cpython-312-x86_64-linux-gnu.so,sha256=Ge-8pnMqRImsauhyZAtAe8g5dKZo-kt9S1XjYGyrMB4,975504 +scipy/special/_input_validation.py,sha256=ZEwg_sZaesaqzaVA_btZQAi_uPXtIViL_u3Zms6UnyQ,474 +scipy/special/_lambertw.py,sha256=-oSEnHFQWZiUZXMamxPWjfntWq5tt0rzHmI13DxGHBY,3962 +scipy/special/_logsumexp.py,sha256=fakIMG1jgs2CFGKLJPD3U-oDU3j_YoUnaCi3rzPFsQE,14437 +scipy/special/_mptestutils.py,sha256=ocy_wBXqHGIg311jfjATEA8O29ICl4qPnvTgsmTm5qg,14441 +scipy/special/_multiufuncs.py,sha256=H5SavE6iepDS2vwbt7gbsSwdjokQAXi6_5U9Rh3LX00,22705 +scipy/special/_orthogonal.py,sha256=u1KyA2lSlA0-QRGbyKmOUwiMT6cg9FI4mMvK4KBKIKk,82368 +scipy/special/_orthogonal.pyi,sha256=L-HyMBSyL1WHLoHC9jLUNmQkC2XLWprAW_6x-knU-Tw,8260 +scipy/special/_precompute/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +scipy/special/_precompute/__pycache__/__init__.cpython-312.pyc,, +scipy/special/_precompute/__pycache__/cosine_cdf.cpython-312.pyc,, +scipy/special/_precompute/__pycache__/expn_asy.cpython-312.pyc,, +scipy/special/_precompute/__pycache__/gammainc_asy.cpython-312.pyc,, +scipy/special/_precompute/__pycache__/gammainc_data.cpython-312.pyc,, +scipy/special/_precompute/__pycache__/hyp2f1_data.cpython-312.pyc,, +scipy/special/_precompute/__pycache__/lambertw.cpython-312.pyc,, +scipy/special/_precompute/__pycache__/loggamma.cpython-312.pyc,, +scipy/special/_precompute/__pycache__/struve_convergence.cpython-312.pyc,, +scipy/special/_precompute/__pycache__/utils.cpython-312.pyc,, +scipy/special/_precompute/__pycache__/wright_bessel.cpython-312.pyc,, +scipy/special/_precompute/__pycache__/wright_bessel_data.cpython-312.pyc,, +scipy/special/_precompute/__pycache__/wrightomega.cpython-312.pyc,, +scipy/special/_precompute/__pycache__/zetac.cpython-312.pyc,, +scipy/special/_precompute/cosine_cdf.py,sha256=ZGSeDDpLRsapyx2GbIrqqYR98fvaEQrLn7IE-fuodhE,354 +scipy/special/_precompute/expn_asy.py,sha256=JAz0hY1gBJu3Q_dvscQrSJdgKuwpjqFZVwz-sOQQ21w,1265 +scipy/special/_precompute/gammainc_asy.py,sha256=P5OFRcPkkpjGQeYCaMZ8SFSUmZG_CjrEHv8OLwgcGFc,2502 +scipy/special/_precompute/gammainc_data.py,sha256=jogxBuXLr3uEpMBvpqScDz5TzEEalksH8f-cRGzasck,4077 +scipy/special/_precompute/hyp2f1_data.py,sha256=STSBybQ2pCAu6sh8c9tiHsoDOgnisnSp4tkP2cK4MuI,14707 +scipy/special/_precompute/lambertw.py,sha256=7f4F3ivouVNZwuvVX8TAi2lPB7LirPS8IfN5lEw9zI0,1961 +scipy/special/_precompute/loggamma.py,sha256=iq7ZBrUmk8pXYZwO_wINI4u8ENsLbL9VUShGjGO0Pt0,1094 +scipy/special/_precompute/struve_convergence.py,sha256=z7R0Q5_Ye-EqLI9g-yARdl_j5FooofXMRXPLVrIFJQQ,3624 +scipy/special/_precompute/utils.py,sha256=JXJuI07Jlm4bDHJFVtj0jHq05p-V1ofeXZB16Y05kzI,887 +scipy/special/_precompute/wright_bessel.py,sha256=7z2W3spGANZO31r_xauMA6hIQ0eseRlXx-zJW6du5tU,12868 +scipy/special/_precompute/wright_bessel_data.py,sha256=f1id2Gk5TPyUmSt-Evhoq2_hfRgLUU7Qu_mELKtaXGg,5647 +scipy/special/_precompute/wrightomega.py,sha256=YpmLwtGJ4qazMDY0RXjhnQiuRAISI-Pr9MwKc7pZlhc,955 +scipy/special/_precompute/zetac.py,sha256=LmhJP7JFg7XktHvfm-DgzuiWZFtVdpvYzzLOB1ePG1Q,591 +scipy/special/_sf_error.py,sha256=q_Rbfkws1ttgTQKYLt6zFTdY6DFX2HajJe_lXiNWC0c,375 +scipy/special/_specfun.cpython-312-x86_64-linux-gnu.so,sha256=LCfzROL40-l-OwcO5EBZK6kDJK5CY1JP9OhRaDiNaSQ,228336 +scipy/special/_special_ufuncs.cpython-312-x86_64-linux-gnu.so,sha256=WqWYRFYIJlb6ZgErEeTuv_JeHpk89tUEYKCDCG8GedM,1659864 +scipy/special/_spfun_stats.py,sha256=kf6IhXtSSIcIM7s5GA_U7ngx1OMXSd2duWb29BPHkSY,5624 +scipy/special/_spherical_bessel.py,sha256=qB1Q6zVDwm4dWxNaTG7F19C9X_9U2bkvFKOhUc5dccM,12786 +scipy/special/_support_alternative_backends.py,sha256=AcbGEAEEI_4EpLI8pe9NC6Nh4mQ37XrRadDOnvWnP7g,31149 +scipy/special/_test_internal.cpython-312-x86_64-linux-gnu.so,sha256=8d090Zd6DLyU828hDdaOuhRcero_LCeVb0JtkQIP3z0,92040 +scipy/special/_test_internal.pyi,sha256=OpIYkTEaqK33ikWEGYza9yegkstcQMlxZQ2WXthTmow,415 +scipy/special/_testutils.py,sha256=-HqrArXZ8X_DY_w07kmxlKrrK5fXCrPmevrlqxNHwss,11994 +scipy/special/_ufunc_tools.py,sha256=aXuIiQRR9rmfKPQTNNyhNYDxS9ets6_NALEDkV2_lGU,6346 +scipy/special/_ufuncs.cpython-312-x86_64-linux-gnu.so,sha256=1o-5HSTDeBj2wt5StjyCV8KWF9YqWNGqvcUdydBV1LU,1496216 +scipy/special/_ufuncs.pyi,sha256=GL19VPf1l-XLCJvK15l1NLNaz9WZTvx_z7LuZ2OQXBs,8914 +scipy/special/_ufuncs_cxx.cpython-312-x86_64-linux-gnu.so,sha256=KN1hrEF6u31LmRfXh9hxRrpc5t8c5-QvWpk-eupmydc,2081448 +scipy/special/add_newdocs.py,sha256=Wnd-5R0wQAVxSolD4QY2CamTSbe1k48Aie3XaBWRKKc,436 +scipy/special/basic.py,sha256=_XJm4Y1lArjSQY14bZ93Deep6loN-kYMAJvGSf5ezFk,1546 +scipy/special/cython_special.cpython-312-x86_64-linux-gnu.so,sha256=69gaVFR_V2lDGawXalC3oUjh9Bwdrmh6ZeI0dFS55pk,2446128 +scipy/special/cython_special.pxd,sha256=UrTdQIGs-2O6xKylRVk8vyvLM5ErxZh0ENhRc1kJoKw,16521 +scipy/special/cython_special.pyi,sha256=BQVUCzV8lCylnmLCtnN0Yz_ttlqyzcLc-BZx2KPXPzM,58 +scipy/special/orthogonal.py,sha256=aLzv7PzJgsdLpyTrV6Cu-rpHNHWlUAEqOImiW4fuzuE,1724 +scipy/special/sf_error.py,sha256=wOZqzX7iipkH39hOHqBlkmretJRbYy-K7PsnZPyaJFU,573 +scipy/special/specfun.py,sha256=vEn4jiJMvKiCXWiw_DdUqmyFbY6O_iYCFBTPaa6cLsg,552 +scipy/special/spfun_stats.py,sha256=ESJXGUwH7iijUk6aXZQVI1pnaWiVZ6_l0hVpC4bBSIw,535 +scipy/special/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +scipy/special/tests/__pycache__/__init__.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_basic.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_bdtr.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_boost_ufuncs.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_boxcox.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_cdflib.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_cdft_asymptotic.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_cephes_intp_cast.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_cosine_distr.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_cython_abi.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_cython_special.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_data.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_dd.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_digamma.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_ellip_harm.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_erfinv.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_exponential_integrals.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_extending.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_faddeeva.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_gamma.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_gammainc.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_gen_harmonic.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_hyp2f1.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_hypergeometric.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_iv_ratio.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_kolmogorov.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_lambertw.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_legendre.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_log1mexp.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_loggamma.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_logit.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_logsumexp.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_mpmath.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_nan_inputs.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_ndtr.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_ndtri_exp.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_orthogonal.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_orthogonal_eval.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_owens_t.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_pcf.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_pdtr.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_powm1.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_precompute_expn_asy.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_precompute_gammainc.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_precompute_utils.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_round.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_sf_error.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_sici.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_specfun.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_spence.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_spfun_stats.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_sph_harm.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_spherical_bessel.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_support_alternative_backends.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_trig.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_ufunc_infra.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_ufunc_signatures.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_wright_bessel.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_wrightomega.cpython-312.pyc,, +scipy/special/tests/__pycache__/test_zeta.cpython-312.pyc,, +scipy/special/tests/_cython_examples/extending.pyx,sha256=0ISFhXHFnwuWXg5m9VIYdWGjP_W7hxUE8SwFNkvAM_s,292 +scipy/special/tests/_cython_examples/meson.build,sha256=Pk-N3wvLF7UTcaU1g2obYNKecVBXd6AMxuOqZIi_yEE,810 +scipy/special/tests/cython_abi_signatures.json,sha256=YIPy1WRciBHdc_Mb4RMeKKF2ZDjvX4qRfnw-oL8uqbg,34627 +scipy/special/tests/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +scipy/special/tests/data/__pycache__/__init__.cpython-312.pyc,, +scipy/special/tests/data/boost.npz,sha256=V7XCtn7gHHQVNqrmrZ-PEoEGt_3_FSr889j3dLBkWEQ,1270643 +scipy/special/tests/data/gsl.npz,sha256=y_Gv3SeZmAanECeZEKLrL59_VZAzx-y3lt6qEMRP6zE,51433 +scipy/special/tests/data/local.npz,sha256=bCnljOgnCE-E258bupYEWmHOafHT6j18gop5wTPPiPI,203438 +scipy/special/tests/test_basic.py,sha256=8L_9YkGVOyTD_5Yt7mmyg90YN6fgy3xjbrjwUL4ij5M,200166 +scipy/special/tests/test_bdtr.py,sha256=YrexKxguZwJVkfOxDMfsVqtSkz-N6fkdifoO6qT3ddY,3289 +scipy/special/tests/test_boost_ufuncs.py,sha256=4cEmHpVVl5KQg0qZywhbcH5uYmsvZcnQT9RotJXhnGI,3023 +scipy/special/tests/test_boxcox.py,sha256=kuGjRgfLInydtbFRm-kvPBy2cJGsHFBmX14mrinu87Q,3263 +scipy/special/tests/test_cdflib.py,sha256=423JZ5qeIqTNFi8HfK5XHc1tPa-f1fB8B2Kulj9OLo0,35027 +scipy/special/tests/test_cdft_asymptotic.py,sha256=aj-Hawo0dShzdBRz2AfqGl6UJr9odNMcGISYTa-rpJw,1619 +scipy/special/tests/test_cephes_intp_cast.py,sha256=yllVoacRDDS_mH7E_pvDux_Jpf7_Fdt3F9Jsgj3_BaY,1129 +scipy/special/tests/test_cosine_distr.py,sha256=zL7aWLisIEy1oNKjcynqncgsCxcPKvPb9Odr-J5Xa1M,2690 +scipy/special/tests/test_cython_abi.py,sha256=HK1PwGLjhXqE3F0fDY9KGmkjFXEgbLkCuNZgATljvqg,2948 +scipy/special/tests/test_cython_special.py,sha256=6Sdf1U9KGtM6T7aMJGfmtITlLYcFM7Szoq8NB1uJ5lA,19046 +scipy/special/tests/test_data.py,sha256=XQBpfKQiHwAkr_SRYumMaOEYgew7ptKUlxm_NWCuR7c,28349 +scipy/special/tests/test_dd.py,sha256=I7xSqxTD-GYaO0ol25ZjsGZgqCVt13vbcQlUN7teeG4,1564 +scipy/special/tests/test_digamma.py,sha256=Bm7Hh_aETx6MTN3Wu7Sijy4rYGR_1haNGsi3xfzrAKM,1382 +scipy/special/tests/test_ellip_harm.py,sha256=tBPBdZy7153UvgL4Y-AOUkG6pflmj9uNKAwG9-GG7GM,9969 +scipy/special/tests/test_erfinv.py,sha256=fzdEHd6MxfSyzQDO93qndXukG2jWj-XNY2X4BJRIdBI,3059 +scipy/special/tests/test_exponential_integrals.py,sha256=wMPTu9dK32cbtJWOouF1Dw2e0hjZSNxYtcQbVaWlDTk,3863 +scipy/special/tests/test_extending.py,sha256=7Q8NRxp-QBASTY9y0b8xOcAJmrMKhLaruE_MX7nmJ0M,1184 +scipy/special/tests/test_faddeeva.py,sha256=YLY3Ylp4u_8zxTGxOb5kxNfXXEW0ld_GP2ceOR2ev_Y,2568 +scipy/special/tests/test_gamma.py,sha256=hb-ZlA2ZNz6gUGvVtMBgXFl_w30HPmthuUEAmNcz0sw,258 +scipy/special/tests/test_gammainc.py,sha256=QdOylOmN2CY5cFw0BihCP-05x_d8q0Pitb7-a4DgSic,4441 +scipy/special/tests/test_gen_harmonic.py,sha256=ojbzW6yAV3HOhQhU6mubHduDVlXpkPq2pkdolip1Vsw,3110 +scipy/special/tests/test_hyp2f1.py,sha256=lvERrDZuLSA7tzVo8rzFuHSNCijGjBuDCDTktJVAcKE,92261 +scipy/special/tests/test_hypergeometric.py,sha256=DUDe1YvIXt4IocGlJuqDO5swZ-QOyR2Etj2rwkF-NqQ,9996 +scipy/special/tests/test_iv_ratio.py,sha256=6Wa4PDSboT1srHiGUOR78_cTvStWgct31cGkLFvDT5A,10108 +scipy/special/tests/test_kolmogorov.py,sha256=-Ika_ORUwxDuaCXATLb489T9lDWoPkJR7r7PNRAE0mE,19280 +scipy/special/tests/test_lambertw.py,sha256=KpUZJA3TDejEfeZ5uS2n6eg3R2V0GhGQj30Pl1UxnOk,4572 +scipy/special/tests/test_legendre.py,sha256=vZ5yKiEtbmmGoG5Oc3x-VAvpzeEP8wsEhkndDIE2Dwc,53904 +scipy/special/tests/test_log1mexp.py,sha256=tbkuxQOdAADZlLffpmfo9SvAUpqBpCZFWqnbVtOox_M,3155 +scipy/special/tests/test_loggamma.py,sha256=x6kuJf-bEnn5ECdkDSgvk3An_A-9UxVsZpqa49IwAq8,1992 +scipy/special/tests/test_logit.py,sha256=uCc4Cb7SrUAx01hMyryJIhfh5k0zEozmYhHHky32nwM,6470 +scipy/special/tests/test_logsumexp.py,sha256=-6GLDp5zwWW4Lr_mThq-i_mbCHGg-gtgaZJhYWYjwyY,19398 +scipy/special/tests/test_mpmath.py,sha256=O3Df-lp-D2VvtQAjnhIaJ0NpFVpmgyB-_E0IdDTr204,69366 +scipy/special/tests/test_nan_inputs.py,sha256=D6cWbrzU3Y_9JoqE7fcK43xZCQ3Roe-QxNeL6C6uEiQ,1956 +scipy/special/tests/test_ndtr.py,sha256=-UMxTIi4CaaLoJ5-SGW9THChPIM3e1_fTY0L877ioNA,2680 +scipy/special/tests/test_ndtri_exp.py,sha256=13eabgdbfcL37RReiUH7g9amT9XMsTLOfwxFJXR_2Ww,3708 +scipy/special/tests/test_orthogonal.py,sha256=R4Ap_F5SULTMAgDV13XgOfyqSBfU60RC1SWoG62kF30,33343 +scipy/special/tests/test_orthogonal_eval.py,sha256=U_jLsjciwuDBNXRw2vnPko_oHXhgbGliiOZgNMyhH_E,13011 +scipy/special/tests/test_owens_t.py,sha256=zRbiKje7KrYJ25f1ZuIBfiFSyNtK_bnkIW7dRETIqME,1792 +scipy/special/tests/test_pcf.py,sha256=RNjEWZGFS99DOGZkkPJ8HNqLULko8UkX0nEWFYX26NE,664 +scipy/special/tests/test_pdtr.py,sha256=jCxVZ7anMlXL9_duT1RdBbLqTATYBCRwM-2kA_wepKU,1366 +scipy/special/tests/test_powm1.py,sha256=9hZeiQVKqV63J5oguYXv_vqolpnJX2XRO1JN0ouLWAM,2276 +scipy/special/tests/test_precompute_expn_asy.py,sha256=bCQikPkWbxVUeimvo79ToVPgwaudzxGC7Av-hPBgIU4,583 +scipy/special/tests/test_precompute_gammainc.py,sha256=Kxlp5iX7R4iog69KygYf0gjrtuTXkUJZPvGe0nnhL6M,4620 +scipy/special/tests/test_precompute_utils.py,sha256=MOvdbLbzjN5Z1JQQgtIyjwjuIMPX4s2bTc_kxaX67wc,1165 +scipy/special/tests/test_round.py,sha256=Zv32kFQrDdOPawfGDeZo1PfBG4UsOyKfd3zjbCWLii0,511 +scipy/special/tests/test_sf_error.py,sha256=7-a7XxGX0sz1uZWrvkN5Ysakq8vDQh3Ms8LB7zvp5GU,4562 +scipy/special/tests/test_sici.py,sha256=w4anBf8fiq2fmkwMSz3MX0uy35NLXVqfuW3Fwt2Nqek,1227 +scipy/special/tests/test_specfun.py,sha256=q2JYEnqmUq78rO8no9hXQZ3fc3RuxPrRCcpsLruovDg,1687 +scipy/special/tests/test_spence.py,sha256=fChPw7xncNCTPMUGb0C8BC-lDKHWoEXSz8Rb4Wv8vNo,1099 +scipy/special/tests/test_spfun_stats.py,sha256=LVKumr4w7le78N9DBMHlUPDWt3M5yEQwXX62CMBjY1U,2058 +scipy/special/tests/test_sph_harm.py,sha256=CG_mi1SuQspgkau2UXoYEjavScUShAWtVOcovGlKtDc,1780 +scipy/special/tests/test_spherical_bessel.py,sha256=U60jQUKB2twu2mMZhkhXBz6JjkOuy4G9WWHZwwg9GX4,15195 +scipy/special/tests/test_support_alternative_backends.py,sha256=G6Yq5pLSVjcnfkUDlEUnXl0AGbFYVR43103UE2KlYlk,15806 +scipy/special/tests/test_trig.py,sha256=CKZr--vh4fwiW-ycJf5wODosKYhCpkNqvHfTArz-2K8,2400 +scipy/special/tests/test_ufunc_infra.py,sha256=HEncdJPCps9P63AY1mZhXNUJTgIC7oh2ObrbzQcsXmk,1101 +scipy/special/tests/test_ufunc_signatures.py,sha256=k4u7QV8El10DUuLV9CyD47Woqv1MToVCC_JWeEiT58o,4390 +scipy/special/tests/test_wright_bessel.py,sha256=6WHuXB97skPSsoMgXwRlO7bHydFLnl9iDfctEpZE0uE,7694 +scipy/special/tests/test_wrightomega.py,sha256=BW8TS_CuDjR7exA4l6ADnKhXwgFWUYaN1UIopMBJUZY,3560 +scipy/special/tests/test_zeta.py,sha256=IEPRUdSX5kerDYPmhLWYkYixmUg1ErqHSprQpfkZTP0,11549 +scipy/stats/__init__.py,sha256=jCY2geYWEtTt3WqBzkwFaiQYTP23Z0iigFAkXDYEmGQ,18567 +scipy/stats/__pycache__/__init__.cpython-312.pyc,, +scipy/stats/__pycache__/_axis_nan_policy.cpython-312.pyc,, +scipy/stats/__pycache__/_binned_statistic.cpython-312.pyc,, +scipy/stats/__pycache__/_binomtest.cpython-312.pyc,, +scipy/stats/__pycache__/_bws_test.cpython-312.pyc,, +scipy/stats/__pycache__/_censored_data.cpython-312.pyc,, +scipy/stats/__pycache__/_common.cpython-312.pyc,, +scipy/stats/__pycache__/_constants.cpython-312.pyc,, +scipy/stats/__pycache__/_continued_fraction.cpython-312.pyc,, +scipy/stats/__pycache__/_continuous_distns.cpython-312.pyc,, +scipy/stats/__pycache__/_correlation.cpython-312.pyc,, +scipy/stats/__pycache__/_covariance.cpython-312.pyc,, +scipy/stats/__pycache__/_crosstab.cpython-312.pyc,, +scipy/stats/__pycache__/_discrete_distns.cpython-312.pyc,, +scipy/stats/__pycache__/_distn_infrastructure.cpython-312.pyc,, +scipy/stats/__pycache__/_distr_params.cpython-312.pyc,, +scipy/stats/__pycache__/_distribution_infrastructure.cpython-312.pyc,, +scipy/stats/__pycache__/_entropy.cpython-312.pyc,, +scipy/stats/__pycache__/_finite_differences.cpython-312.pyc,, +scipy/stats/__pycache__/_fit.cpython-312.pyc,, +scipy/stats/__pycache__/_hypotests.cpython-312.pyc,, +scipy/stats/__pycache__/_kde.cpython-312.pyc,, +scipy/stats/__pycache__/_ksstats.cpython-312.pyc,, +scipy/stats/__pycache__/_mannwhitneyu.cpython-312.pyc,, +scipy/stats/__pycache__/_mgc.cpython-312.pyc,, +scipy/stats/__pycache__/_morestats.cpython-312.pyc,, +scipy/stats/__pycache__/_mstats_basic.cpython-312.pyc,, +scipy/stats/__pycache__/_mstats_extras.cpython-312.pyc,, +scipy/stats/__pycache__/_multicomp.cpython-312.pyc,, +scipy/stats/__pycache__/_multivariate.cpython-312.pyc,, +scipy/stats/__pycache__/_new_distributions.cpython-312.pyc,, +scipy/stats/__pycache__/_odds_ratio.cpython-312.pyc,, +scipy/stats/__pycache__/_page_trend_test.cpython-312.pyc,, +scipy/stats/__pycache__/_probability_distribution.cpython-312.pyc,, +scipy/stats/__pycache__/_qmc.cpython-312.pyc,, +scipy/stats/__pycache__/_qmvnt.cpython-312.pyc,, +scipy/stats/__pycache__/_quantile.cpython-312.pyc,, +scipy/stats/__pycache__/_relative_risk.cpython-312.pyc,, +scipy/stats/__pycache__/_resampling.cpython-312.pyc,, +scipy/stats/__pycache__/_result_classes.cpython-312.pyc,, +scipy/stats/__pycache__/_sampling.cpython-312.pyc,, +scipy/stats/__pycache__/_sensitivity_analysis.cpython-312.pyc,, +scipy/stats/__pycache__/_stats_mstats_common.cpython-312.pyc,, +scipy/stats/__pycache__/_stats_py.cpython-312.pyc,, +scipy/stats/__pycache__/_survival.cpython-312.pyc,, +scipy/stats/__pycache__/_tukeylambda_stats.cpython-312.pyc,, +scipy/stats/__pycache__/_variation.cpython-312.pyc,, +scipy/stats/__pycache__/_warnings_errors.cpython-312.pyc,, +scipy/stats/__pycache__/_wilcoxon.cpython-312.pyc,, +scipy/stats/__pycache__/biasedurn.cpython-312.pyc,, +scipy/stats/__pycache__/contingency.cpython-312.pyc,, +scipy/stats/__pycache__/distributions.cpython-312.pyc,, +scipy/stats/__pycache__/kde.cpython-312.pyc,, +scipy/stats/__pycache__/morestats.cpython-312.pyc,, +scipy/stats/__pycache__/mstats.cpython-312.pyc,, +scipy/stats/__pycache__/mstats_basic.cpython-312.pyc,, +scipy/stats/__pycache__/mstats_extras.cpython-312.pyc,, +scipy/stats/__pycache__/mvn.cpython-312.pyc,, +scipy/stats/__pycache__/qmc.cpython-312.pyc,, +scipy/stats/__pycache__/sampling.cpython-312.pyc,, +scipy/stats/__pycache__/stats.cpython-312.pyc,, +scipy/stats/_ansari_swilk_statistics.cpython-312-x86_64-linux-gnu.so,sha256=VhLG9XqOl6Rn2IAZ9UKDFOi4vHkH3scChnHxYp0I23o,107992 +scipy/stats/_axis_nan_policy.py,sha256=WZy8iJb7BaR0PDHlXx9YdoLVpCYU0Rma6lXJdU6ZBDc,33327 +scipy/stats/_biasedurn.cpython-312-x86_64-linux-gnu.so,sha256=XeL9JrKbkMnjCQ8bC3OHgvYBuSIiupTZy2pLGomLRsU,239008 +scipy/stats/_biasedurn.pxd,sha256=bQC6xG4RH1E5h2jCKXRMADfgGctiO5TgNlJegKrR7DY,1046 +scipy/stats/_binned_statistic.py,sha256=UtK0SXB60N1ZDlC3lchF2elP3b-VIUqqKvYGTBAkZDM,32940 +scipy/stats/_binomtest.py,sha256=34n2Y-YjKiENI7JLgxlXQR_WgwkgzpNwObkm8YhapVI,15079 +scipy/stats/_bws_test.py,sha256=uZ0aVqewiiBsR_MsnRgO-3usjvmRa8lrU4WONJ2i9so,8502 +scipy/stats/_censored_data.py,sha256=QX8LbHvyTYlsvZAPOIqTTIH2Ajg4LBcVDUIJLCcovZQ,18539 +scipy/stats/_common.py,sha256=4RqXT04Knp1CoOJuSBV6Uy_XmcmtVr0bImAbSk_VHlQ,172 +scipy/stats/_constants.py,sha256=mBeJgvWcDZBmPFStDNEjlzeZY3aMDMCHWoj7dCmgugQ,1002 +scipy/stats/_continued_fraction.py,sha256=2WyLuQWsx9aIHkYvTE4_VlepAfSKG4otiu_Y5wYbzKA,15508 +scipy/stats/_continuous_distns.py,sha256=1AHiyLYnzojI1iexX8YQO86lh4TUzpFQWI9U1JHLGok,412887 +scipy/stats/_correlation.py,sha256=qhwDzdMp19IA96hCR2xf1tTiE5Rqy_HMKFYyHfar6fI,30619 +scipy/stats/_covariance.py,sha256=0QEbyyisGhhMdm_pxd4EV6hvbpAR3fr03aOmwN2DBlw,23645 +scipy/stats/_crosstab.py,sha256=Sj6atbXIMzSm_36PinwXAlMOEdKvpiZxyfp9LQcA8f4,7035 +scipy/stats/_discrete_distns.py,sha256=H2-E0ln8A1JNzy-mdgTeH2WO3JMUrfD1pogO5Egp8vM,66795 +scipy/stats/_distn_infrastructure.py,sha256=fnewpk1huSWnYqzEgk8ner3VRNgMo6Ft0UIXXMmk-S0,155187 +scipy/stats/_distr_params.py,sha256=ccFQx6dMoVaS6Vrdo2vGcvWZbjH9I4Ms_J0-eHFRlac,9083 +scipy/stats/_distribution_infrastructure.py,sha256=4EicSvHY3TEgzMZ3wOKqHL2vae4DYLzvSSN6mkC76vc,234051 +scipy/stats/_entropy.py,sha256=KFN6BLVrCtmhYjIdfQFq2poQztflQw1hR0LyDpXqQmA,15792 +scipy/stats/_finite_differences.py,sha256=QaA5p36T0oDt4e_oMOE3QGBT8gB2C3E3ziZSWkkBF9g,4168 +scipy/stats/_fit.py,sha256=FcSIJKwFBBjGyZ98Og1QPQZeS6wJqTfsrnUWw5PK-pY,59675 +scipy/stats/_hypotests.py,sha256=3YUgqI1dxtWd_KDIsHfwaN3mdK8ZFycsbmtTVqY-kWE,88494 +scipy/stats/_kde.py,sha256=Wl9I1JTMj54ZrIbJnvkgN9Gcp6pwsDUI9ZEgzedp2RE,26346 +scipy/stats/_ksstats.py,sha256=SwOgu1gHoBcmSgNZcYxGqGFTWA41_Yuzw0H6tJXWeKk,20128 +scipy/stats/_levy_stable/__init__.py,sha256=r1dSFBYF7dYUuLiBBaR5O-4tQXkIrKI-eVjOuXX0QrI,46463 +scipy/stats/_levy_stable/__pycache__/__init__.cpython-312.pyc,, +scipy/stats/_levy_stable/levyst.cpython-312-x86_64-linux-gnu.so,sha256=QVp3n3fSYAOiSittoM6XHtGNWXyApr3se9ADR_WKBew,61536 +scipy/stats/_mannwhitneyu.py,sha256=qeQLfzaOgQNYz1Zv5p6XcK5TBvha07FYBS18Rcj3Ms8,21838 +scipy/stats/_mgc.py,sha256=KTtt-n7nm-NrcNCT8_meU2J5a9KJ-IYbHzwxwDubX0A,21396 +scipy/stats/_morestats.py,sha256=ipM-X-8jPtM-Ku9cyCQD-qJYScbxV5B_gq3ZMjdn5T8,194421 +scipy/stats/_mstats_basic.py,sha256=1g2zf42ScifRMd9LnlDUAaDawqVhV8zNoW13emOgQ98,130187 +scipy/stats/_mstats_extras.py,sha256=U2F6PX6ag31jfFPHs4I7ByfGpi-oMjliuh0-V4ldvHk,16409 +scipy/stats/_multicomp.py,sha256=lplakpE9tKChCnoGayBUbeJW35mjeWtxVrWmfbV0dk8,17093 +scipy/stats/_multivariate.py,sha256=nEBz_47Y1GYoVSQxoJKL4GLR4ciSA8LX0MuxfVp8ICw,274024 +scipy/stats/_new_distributions.py,sha256=BD-JgXj1jUQwaIH2qJ3IxbavcCeSx4Rp8zTd8byQjV4,19418 +scipy/stats/_odds_ratio.py,sha256=zZvZsD7ftKeWUrypXeUapcNoq006XldVAkMMC3RLbWE,17005 +scipy/stats/_page_trend_test.py,sha256=X_32bvqAjBdQwQUToql2CJlLwvKRvL0uPYXRCuT9VLI,19311 +scipy/stats/_probability_distribution.py,sha256=d0LIkl8tBBb-YNDKqOam3vHz4BnpXlMbQDLRJ93RrzE,74830 +scipy/stats/_qmc.py,sha256=VL2q0RRBQyATiGYw3tfRxm0bjYVkNJUQc5KimESlzZ8,107618 +scipy/stats/_qmc_cy.cpython-312-x86_64-linux-gnu.so,sha256=E9cTh9oQphDBGWIRGfJqmBR0c33BBRU9JCSLpWYoX9o,130088 +scipy/stats/_qmc_cy.pyi,sha256=xOpTSlaG_1YDZhkJjQQtukbcgOTAR9FpcRMkU5g9mXc,1134 +scipy/stats/_qmvnt.py,sha256=ORWp7azg6iXQy4ELtPHMKWQpg_tPaxpfY6LzBlEHCOU,16467 +scipy/stats/_qmvnt_cy.cpython-312-x86_64-linux-gnu.so,sha256=8kyUGsBfaDJku8FP8MxNoUaLt1oteI9AtfxoxITnrsk,126432 +scipy/stats/_quantile.py,sha256=qmwAKKx7G47QB52co6ZBA5LQ8FpmmEwAmcoqQ9J-9HU,33448 +scipy/stats/_rcont/__init__.py,sha256=dUzWdRuJNAxnGYVFjDqUB8DMYti3by1WziKEfBDOlB4,84 +scipy/stats/_rcont/__pycache__/__init__.cpython-312.pyc,, +scipy/stats/_rcont/rcont.cpython-312-x86_64-linux-gnu.so,sha256=ysjUIL_54r8k_RK4W9OrbRI6j-E686pHXO9853UgUVQ,107912 +scipy/stats/_relative_risk.py,sha256=5zeYBMshYwtomiLTkaXc1nmWYD0FsaQNjf0iuDadtSc,9571 +scipy/stats/_resampling.py,sha256=wyyNUgfZw61UJHvqnBmvya-_MD_mDKkiroYBT6XQT90,106522 +scipy/stats/_result_classes.py,sha256=_ghuGdpFsCMuEmnfHg1AeorR-fASc77ACXYWEmQzXjI,1085 +scipy/stats/_sampling.py,sha256=Guofi3aVwu59vljrLGPKa5OQfs9p5-hsDEmAzNWK-Jk,46527 +scipy/stats/_sensitivity_analysis.py,sha256=0Dl9etEg6LsWZcffzEFqDzsGvYUXpxiYiB8KL6MBnjc,25124 +scipy/stats/_sobol.cpython-312-x86_64-linux-gnu.so,sha256=8mx9mLS--iTd_fkEBrPXszz_lPYLRz0zoK-C7jKcmnc,184960 +scipy/stats/_sobol.pyi,sha256=TAywylI75AF9th9QZY8TYfHvIQ1cyM5QZi7eBOAkrbg,971 +scipy/stats/_sobol_direction_numbers.npz,sha256=SFmTEUfULORluGBcsnf5V9mLg50DGU_fBleTV5BtGTs,589334 +scipy/stats/_stats.cpython-312-x86_64-linux-gnu.so,sha256=rkWOnnNBqkuI0rsiJd71EItf7MYJKp3uhmNPhQTYnTo,497368 +scipy/stats/_stats.pxd,sha256=T_7IrDqgIahKMECV5WAtxtsoV91XBVRM359kAXPIhww,709 +scipy/stats/_stats_mstats_common.py,sha256=Fub7TYlet6Rd1uMBsHOT5A2xzA96RepIn-uB4_Pc2eY,1185 +scipy/stats/_stats_py.py,sha256=v9WrICQn7BmtOafZhbj-OlsoNxiuGt7AvY_EL2BeWYg,441238 +scipy/stats/_stats_pythran.cpython-312-x86_64-linux-gnu.so,sha256=FDIMWn5C7To5o2BqzesQiq4j8AODB-Na4oCQgII5xTA,136360 +scipy/stats/_survival.py,sha256=gBGsHonhgUxAik7Ww75C-deozqmwdY6RMUen_6J0myI,26051 +scipy/stats/_tukeylambda_stats.py,sha256=l-PPBlFs40R6mw8AaQhXsRGRInrSRIcocHrg__uTG5A,6881 +scipy/stats/_unuran/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +scipy/stats/_unuran/__pycache__/__init__.cpython-312.pyc,, +scipy/stats/_unuran/unuran_wrapper.cpython-312-x86_64-linux-gnu.so,sha256=9vwnfHjAGMpdfvA9CuRjV-avSuvDE3o9DABfn-jAnxs,1335008 +scipy/stats/_unuran/unuran_wrapper.pyi,sha256=KFcmefkNMDwGB6BDttAzcvi7vZrU6XuD23BcYFdsyBQ,5615 +scipy/stats/_variation.py,sha256=k_Yc37nYJBn5zoHikvy5ftCw-bhgq8RQp8c6bHvqvqA,4908 +scipy/stats/_warnings_errors.py,sha256=gBvbtS7bBH2nPmRrzj3ftjIwuANwAV-wCpBV5Kvs8X0,1534 +scipy/stats/_wilcoxon.py,sha256=QyuTSButFShIodypkgflDqZyQVvRQld933F7lFS1Srw,11149 +scipy/stats/biasedurn.py,sha256=ECfilE4KrIhU2sK-KWtr8yxqthfVsyz_-o4F2TnMXU4,431 +scipy/stats/contingency.py,sha256=Go38FE5PZt1TNbvZk2BquGoIE1Oh4_moammgAFkdl2A,18864 +scipy/stats/distributions.py,sha256=9Kt2fyTohorJcf6a7M9DYH8Nu4jEU66nKP01cRhKmuE,859 +scipy/stats/kde.py,sha256=8ZThSc3lz-l1Gb2jzIvy1J87_HTd7eXzxuPLClVpo7c,516 +scipy/stats/morestats.py,sha256=sDSu5RfajS5a-iLN-46xvL_aoWwqI-2TPzCRY4cxQZg,957 +scipy/stats/mstats.py,sha256=aRbrykjrvl-qOBkmGjlFMH4rbWYSqBBQHReanSAomFg,2466 +scipy/stats/mstats_basic.py,sha256=PjgL37PCPwiDx_ptqnmKXc1W3QGlRjjPrG0nI5FA4So,1394 +scipy/stats/mstats_extras.py,sha256=925lNnnf_NTRoyAnXql-k9syzhv7MF6T2kPGsdE2FHc,721 +scipy/stats/mvn.py,sha256=pOcB_Dd_DHpfbYnuJKq-wqmNNGCun1M0294xK1bX0KQ,498 +scipy/stats/qmc.py,sha256=b6gLkc_FSm11Ssb9uIai4XxLk4XL_qqK6Jc2k4RSeN0,11703 +scipy/stats/sampling.py,sha256=VYwxxGosFs-T3qdCmdw4tJYEFLlegwj-JgDin7iwndE,1939 +scipy/stats/stats.py,sha256=1DIRa_bc4ylbVtOXbHBZ1qo3BFKLhPfcffa9K7j2sb0,1475 +scipy/stats/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +scipy/stats/tests/__pycache__/__init__.cpython-312.pyc,, +scipy/stats/tests/__pycache__/common_tests.cpython-312.pyc,, +scipy/stats/tests/__pycache__/test_axis_nan_policy.cpython-312.pyc,, +scipy/stats/tests/__pycache__/test_binned_statistic.cpython-312.pyc,, +scipy/stats/tests/__pycache__/test_censored_data.cpython-312.pyc,, +scipy/stats/tests/__pycache__/test_contingency.cpython-312.pyc,, +scipy/stats/tests/__pycache__/test_continued_fraction.cpython-312.pyc,, +scipy/stats/tests/__pycache__/test_continuous.cpython-312.pyc,, +scipy/stats/tests/__pycache__/test_continuous_basic.cpython-312.pyc,, +scipy/stats/tests/__pycache__/test_continuous_fit_censored.cpython-312.pyc,, +scipy/stats/tests/__pycache__/test_correlation.cpython-312.pyc,, +scipy/stats/tests/__pycache__/test_crosstab.cpython-312.pyc,, +scipy/stats/tests/__pycache__/test_device_dtype.cpython-312.pyc,, +scipy/stats/tests/__pycache__/test_discrete_basic.cpython-312.pyc,, +scipy/stats/tests/__pycache__/test_discrete_distns.cpython-312.pyc,, +scipy/stats/tests/__pycache__/test_distributions.cpython-312.pyc,, +scipy/stats/tests/__pycache__/test_entropy.cpython-312.pyc,, +scipy/stats/tests/__pycache__/test_fast_gen_inversion.cpython-312.pyc,, +scipy/stats/tests/__pycache__/test_fit.cpython-312.pyc,, +scipy/stats/tests/__pycache__/test_hypotests.cpython-312.pyc,, +scipy/stats/tests/__pycache__/test_kdeoth.cpython-312.pyc,, +scipy/stats/tests/__pycache__/test_marray.cpython-312.pyc,, +scipy/stats/tests/__pycache__/test_mgc.cpython-312.pyc,, +scipy/stats/tests/__pycache__/test_morestats.cpython-312.pyc,, +scipy/stats/tests/__pycache__/test_mstats_basic.cpython-312.pyc,, +scipy/stats/tests/__pycache__/test_mstats_extras.cpython-312.pyc,, +scipy/stats/tests/__pycache__/test_multicomp.cpython-312.pyc,, +scipy/stats/tests/__pycache__/test_multivariate.cpython-312.pyc,, +scipy/stats/tests/__pycache__/test_new_distributions.cpython-312.pyc,, +scipy/stats/tests/__pycache__/test_odds_ratio.cpython-312.pyc,, +scipy/stats/tests/__pycache__/test_qmc.cpython-312.pyc,, +scipy/stats/tests/__pycache__/test_quantile.cpython-312.pyc,, +scipy/stats/tests/__pycache__/test_rank.cpython-312.pyc,, +scipy/stats/tests/__pycache__/test_relative_risk.cpython-312.pyc,, +scipy/stats/tests/__pycache__/test_resampling.cpython-312.pyc,, +scipy/stats/tests/__pycache__/test_sampling.cpython-312.pyc,, +scipy/stats/tests/__pycache__/test_sensitivity_analysis.cpython-312.pyc,, +scipy/stats/tests/__pycache__/test_stats.cpython-312.pyc,, +scipy/stats/tests/__pycache__/test_survival.cpython-312.pyc,, +scipy/stats/tests/__pycache__/test_tukeylambda_stats.cpython-312.pyc,, +scipy/stats/tests/__pycache__/test_variation.cpython-312.pyc,, +scipy/stats/tests/common_tests.py,sha256=XktuCGYWP_C1E2IMn0GzsgAsJgBrSxwkgSuavnvgmlk,12310 +scipy/stats/tests/data/__pycache__/_mvt.cpython-312.pyc,, +scipy/stats/tests/data/__pycache__/fisher_exact_results_from_r.cpython-312.pyc,, +scipy/stats/tests/data/_mvt.py,sha256=OvFCmMqI74DWIgo32UV55dP1nzvFvYBSyYcmKJes9pI,6905 +scipy/stats/tests/data/fisher_exact_results_from_r.py,sha256=BKxPAi4h3IOebcZYGxCbutYuAX0tlb40P0DEkfEi918,27349 +scipy/stats/tests/data/jf_skew_t_gamlss_pdf_data.npy,sha256=JU0t7kpNVHuTMcYCQ8b8_K_9JsixBNCNT2BFp2RbO7o,4064 +scipy/stats/tests/data/levy_stable/stable-Z1-cdf-sample-data.npy,sha256=zxjB8tZaIyvyxxISgt8xvyqL6Cevr8TtgQ7TdFfuiYo,183728 +scipy/stats/tests/data/levy_stable/stable-Z1-pdf-sample-data.npy,sha256=_umVErq0zMZWm0e5JOSwNOHNurViT6_H4SBki9X3oSg,183688 +scipy/stats/tests/data/levy_stable/stable-loc-scale-sample-data.npy,sha256=88cZ7dVDH7nnuey20Z48p6kJUpi9GfImaFsPykDwwHM,9328 +scipy/stats/tests/data/nist_anova/AtmWtAg.dat,sha256=Qdd0i7H4cNhAABfFOZPuplhi_9SCquFpO-hNkyRcMD8,3063 +scipy/stats/tests/data/nist_anova/SiRstv.dat,sha256=x9wJ2g1qnzf4DK_w9F_WiOiDMDEg4td2z6uU77G07xM,1947 +scipy/stats/tests/data/nist_anova/SmLs01.dat,sha256=KdnJedRthF7XLA-w7XkIPIMTgzu89yBAMmZA2H4uQOQ,6055 +scipy/stats/tests/data/nist_anova/SmLs02.dat,sha256=nCPyxRk1dAoSPWiC7kG4dLaXs2GL3-KRXRt2NwgXoIA,46561 +scipy/stats/tests/data/nist_anova/SmLs03.dat,sha256=6yPHiQSk0KI4oURQOk99t-uEm-IZN-8eIPHb_y0mQ1U,451566 +scipy/stats/tests/data/nist_anova/SmLs04.dat,sha256=fI-HpgJF9cdGdBinclhVzOcWCCc5ZJZuXalUwirV-lc,6815 +scipy/stats/tests/data/nist_anova/SmLs05.dat,sha256=iJTaAWUFn7DPLTd9bQh_EMKEK1DPG0fnN8xk7BQlPRE,53799 +scipy/stats/tests/data/nist_anova/SmLs06.dat,sha256=riOkYT-LRgmJhPpCK32x7xYnD38gwnh_Eo1X8OK3eN8,523605 +scipy/stats/tests/data/nist_anova/SmLs07.dat,sha256=QtSS11d-vkVvqaIEeJ6oNwyET1CKoyQqjlfBl2sTOJA,7381 +scipy/stats/tests/data/nist_anova/SmLs08.dat,sha256=qrxQQ0I6gnhrefygKwT48x-bz-8laD8Vpn7c81nITRg,59228 +scipy/stats/tests/data/nist_anova/SmLs09.dat,sha256=qmELOQyNlH7CWOMt8PQ0Z_yxgg9Hxc4lqZOuHZxxWuc,577633 +scipy/stats/tests/data/nist_linregress/Norris.dat,sha256=zD_RTRxfqJHVZTAAyddzLDDbhCzKSfwFGr3hwZ1nq30,2591 +scipy/stats/tests/data/rel_breitwigner_pdf_sample_data_ROOT.npy,sha256=7vTccC3YxuMcGMdOH4EoTD6coqtQKC3jnJrTC3u4520,38624 +scipy/stats/tests/data/studentized_range_mpmath_ref.json,sha256=icZGNBodwmJNzOyEki9MreI2lS6nQJNWfnVJiHRNRNM,29239 +scipy/stats/tests/test_axis_nan_policy.py,sha256=77Pft05eEA-sLzEHilDGgijDXCuVwNUhsUWVZI-jjuU,65825 +scipy/stats/tests/test_binned_statistic.py,sha256=VkNeQd5qKGKx5kNJfm6fpOyoaEAJZIPR9-3DwaIkgMo,20314 +scipy/stats/tests/test_censored_data.py,sha256=pAQfSHhmcetcxoS1ZgIHVm1pEbapW7az7I-y_8phb5w,6935 +scipy/stats/tests/test_contingency.py,sha256=00QIN99yybM_HhrLf8kck85gWPUAQmYIKI7XnVzPF94,10937 +scipy/stats/tests/test_continued_fraction.py,sha256=OOngJklJvkxBwEJTYJ6TVy4QU6tHPwz6Y-iUu2sUFuA,6402 +scipy/stats/tests/test_continuous.py,sha256=Wtuc_EJXU1F1d8mnl7Y9SUO_QN2STNkKtx59NJg2irg,99190 +scipy/stats/tests/test_continuous_basic.py,sha256=oz6373gQHwMTBlc6Tj0GoOYuN3oZqqqtTot0UTuhCOQ,43676 +scipy/stats/tests/test_continuous_fit_censored.py,sha256=7hu1sSo9hhh0g9pmPMmjj2BI2rkxvA1h20XdMYZeyog,24188 +scipy/stats/tests/test_correlation.py,sha256=uIAsZphNtHYyliYnoXburlGnGD_HkW_gOVk5tGpE2cY,18998 +scipy/stats/tests/test_crosstab.py,sha256=jXiWpUv3utf5PD42gcaES38MmmEIuj0nAPTNMCHRJoo,4047 +scipy/stats/tests/test_device_dtype.py,sha256=HKVGVB9NUs4jaGJO7CW8QGicy8t22twlPJ143qTqF6g,10022 +scipy/stats/tests/test_discrete_basic.py,sha256=l2e30PPwGTId9sWWf1flZ4xaiqhH8DV13N0YOtWk0xc,22752 +scipy/stats/tests/test_discrete_distns.py,sha256=GRRUe0APFeMq2Q9VnyJ8cF1xKLrHZJENQ2uR_8M70rU,27028 +scipy/stats/tests/test_distributions.py,sha256=VmiwKjSghWvpElJexMfFWYiPqpRubu8NrArvaqgvYjA,425032 +scipy/stats/tests/test_entropy.py,sha256=gNmmi8tu1e9ItIeZ5mb7o2dhvWR9cJVeuv1INvNzdzc,12877 +scipy/stats/tests/test_fast_gen_inversion.py,sha256=IfVEHASa8hWmgJOIDna8lhI5bouQyz2-o2qRfIbetmI,16062 +scipy/stats/tests/test_fit.py,sha256=Bkut4VNcG0qsGuG70fA8dSR0Tipz7nWiBAL1gXMs0YM,48914 +scipy/stats/tests/test_generation/__pycache__/reference_distribution_infrastructure_tests.cpython-312.pyc,, +scipy/stats/tests/test_generation/__pycache__/reference_distributions.cpython-312.pyc,, +scipy/stats/tests/test_generation/__pycache__/studentized_range_mpmath_ref.cpython-312.pyc,, +scipy/stats/tests/test_generation/generate_fisher_exact_results_from_r.R,sha256=9GaIFXwsYTUeIUKMYWUaFNtd_7HD1kqmbVfNryNS8FQ,3059 +scipy/stats/tests/test_generation/reference_distribution_infrastructure_tests.py,sha256=YRvD76sQd9XMoxxnObdkHw9wUb9FCQ3-4VK9DKJ1pGk,3041 +scipy/stats/tests/test_generation/reference_distributions.py,sha256=Hm7guRp_jgKC0wlvzRuv-stUVppz-Nclqj4InXeOSXA,15417 +scipy/stats/tests/test_generation/studentized_range_mpmath_ref.py,sha256=em6Y-vXNoH7-FBFNLRntyLWRnTzZJdczb6ihAxA_gsg,7632 +scipy/stats/tests/test_hypotests.py,sha256=RyO9v6zlJQifj--f4xdsywpx3TZT6v2FMvBh1LynjsU,101185 +scipy/stats/tests/test_kdeoth.py,sha256=vn2Zfzpvhm-tBLt6Wc4w9lVoUREof1KaaH1x0M8GHmg,22921 +scipy/stats/tests/test_marray.py,sha256=kobPZkwG6s9mPmFKT4nFbKnyzQk8E53HD1goTfwHkD8,29441 +scipy/stats/tests/test_mgc.py,sha256=yfezJRKnwI9S23yQo656Plf0hgMvE7_7E_y25b-0edg,8914 +scipy/stats/tests/test_morestats.py,sha256=JZ11QDZ3LKyr6GR7CEqWVxU0zP4Ow3U89090vR4i6lo,179209 +scipy/stats/tests/test_mstats_basic.py,sha256=5b-DJTBXG1LGvZiGFUvrUqqrGsVrK4At1fhs5MGRiNs,90793 +scipy/stats/tests/test_mstats_extras.py,sha256=AWojnbc60PG-JVhWkHp7uwDePeKg-oeLG4VWqjaX9U0,7317 +scipy/stats/tests/test_multicomp.py,sha256=s5mL9NQMvD4khQ12n2_maXKX9Q5pI0HFjcaYMZyhcJ0,17826 +scipy/stats/tests/test_multivariate.py,sha256=TexOlmYrbEZ82y-6FvYPpBalVIOje9O0nTWvZqCeEpw,198851 +scipy/stats/tests/test_new_distributions.py,sha256=vv-L8STWshYz4GEXh0ws2BwgO0A8nfu6us5h0g8CZoE,874 +scipy/stats/tests/test_odds_ratio.py,sha256=ZII-yvP_vhuaNa3qPB0Q5lh9yzRF-08ZcdkAwuu5E94,6727 +scipy/stats/tests/test_qmc.py,sha256=6xX6npWJmdS8Pvb1d09cyQBaYqEHHQi87jcC8z_kJvg,59246 +scipy/stats/tests/test_quantile.py,sha256=jiccec-No-958rduWtwhYQ6jr4_1dJZNcx6p6XRV768,34946 +scipy/stats/tests/test_rank.py,sha256=d-MHCtk2a_8_gSNZbebCaSn3RdT1XTcHCnOLYvXze7c,12853 +scipy/stats/tests/test_relative_risk.py,sha256=jzOGNQ2y9_YfFnXiGAiRDrgahy66qQkw6ZkHgygCJMA,3646 +scipy/stats/tests/test_resampling.py,sha256=SpG9aOFQGwWxmzLqxXPBtyepNqHclb_OfL5cAdQgYj4,93770 +scipy/stats/tests/test_sampling.py,sha256=zrvU2mnX_ZoFi0DsrYVbXswESHmtAMNDslxUuZarvKk,55162 +scipy/stats/tests/test_sensitivity_analysis.py,sha256=nNF_B6Zl5YxmvppI8TEPOGroDsbgyLTF6jBmdJH2AUw,10678 +scipy/stats/tests/test_stats.py,sha256=Pg6zbIZOHYo6ThBv45SUExzM-KwNA08zQdxlvvp3dzk,430333 +scipy/stats/tests/test_survival.py,sha256=Wmig-n93Y2wCuye9btK4QqXwUAdzF0xR_MO9iYZARjU,21958 +scipy/stats/tests/test_tukeylambda_stats.py,sha256=6WUBNVoTseVjfrHfWXtU11gTgmRcdnwAPLQOI0y_5U8,3231 +scipy/stats/tests/test_variation.py,sha256=JFo_dglyAeLQ1EVXk7Ss-19mM0Nw0tfdL8YNKwNSTBg,9154 +scipy/version.py,sha256=arbj6mcvWk7Yr7cccR91o16CoRysK13MAyZ-BUpxRws,318 diff --git a/venv/lib/python3.12/site-packages/scipy-1.18.0.dist-info/WHEEL b/venv/lib/python3.12/site-packages/scipy-1.18.0.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..227a38cf01899aeccdf106d40cea71a6c95d30af --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy-1.18.0.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: meson +Root-Is-Purelib: false +Tag: cp312-cp312-manylinux_2_27_x86_64 +Tag: cp312-cp312-manylinux_2_28_x86_64 + diff --git a/venv/lib/python3.12/site-packages/scipy-1.18.0.dist-info/sboms/auditwheel.cdx.json b/venv/lib/python3.12/site-packages/scipy-1.18.0.dist-info/sboms/auditwheel.cdx.json new file mode 100644 index 0000000000000000000000000000000000000000..83678bc44cd53e5eaca18d9be63b05db7263188e --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy-1.18.0.dist-info/sboms/auditwheel.cdx.json @@ -0,0 +1 @@ +{"bomFormat": "CycloneDX", "specVersion": "1.4", "version": 1, "metadata": {"component": {"type": "library", "bom-ref": "pkg:pypi/scipy@1.18.0?file_name=scipy-1.18.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", "name": "scipy", "version": "1.18.0", "purl": "pkg:pypi/scipy@1.18.0?file_name=scipy-1.18.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl"}, "tools": [{"name": "auditwheel", "version": "6.6.0"}]}, "components": [{"type": "library", "bom-ref": "pkg:pypi/scipy@1.18.0?file_name=scipy-1.18.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", "name": "scipy", "version": "1.18.0", "purl": "pkg:pypi/scipy@1.18.0?file_name=scipy-1.18.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl"}, {"type": "library", "bom-ref": "pkg:rpm/almalinux/libgfortran@8.5.0-28.el8_10.alma.1#3362720ce454cc402813e3cfe54f0eb4efaedcd690fa35129ddc4b3de40e0650", "name": "libgfortran", "version": "8.5.0-28.el8_10.alma.1", "purl": "pkg:rpm/almalinux/libgfortran@8.5.0-28.el8_10.alma.1"}, {"type": "library", "bom-ref": "pkg:rpm/almalinux/libquadmath@8.5.0-28.el8_10.alma.1#97b91febe2a1c7927c6482d6bd6567b83be3467f33c9d2764df1283f7ac9a5fa", "name": "libquadmath", "version": "8.5.0-28.el8_10.alma.1", "purl": "pkg:rpm/almalinux/libquadmath@8.5.0-28.el8_10.alma.1"}], "dependencies": [{"ref": "pkg:pypi/scipy@1.18.0?file_name=scipy-1.18.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", "dependsOn": ["pkg:rpm/almalinux/libgfortran@8.5.0-28.el8_10.alma.1#3362720ce454cc402813e3cfe54f0eb4efaedcd690fa35129ddc4b3de40e0650", "pkg:rpm/almalinux/libquadmath@8.5.0-28.el8_10.alma.1#97b91febe2a1c7927c6482d6bd6567b83be3467f33c9d2764df1283f7ac9a5fa"]}, {"ref": "pkg:rpm/almalinux/libgfortran@8.5.0-28.el8_10.alma.1#3362720ce454cc402813e3cfe54f0eb4efaedcd690fa35129ddc4b3de40e0650"}, {"ref": "pkg:rpm/almalinux/libquadmath@8.5.0-28.el8_10.alma.1#97b91febe2a1c7927c6482d6bd6567b83be3467f33c9d2764df1283f7ac9a5fa"}]} \ No newline at end of file diff --git a/venv/lib/python3.12/site-packages/scipy.libs/libgfortran-040039e1-0352e75f.so.5.0.0 b/venv/lib/python3.12/site-packages/scipy.libs/libgfortran-040039e1-0352e75f.so.5.0.0 new file mode 100644 index 0000000000000000000000000000000000000000..32aeee9a92e063a830909443ffa10a61f8eb4896 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy.libs/libgfortran-040039e1-0352e75f.so.5.0.0 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c6090048eccc763522c12ef016f81da6b627cb3a044f55cf0479a839c41c0980 +size 2833617 diff --git a/venv/lib/python3.12/site-packages/scipy.libs/libgfortran-83c28eba.so.5.0.0 b/venv/lib/python3.12/site-packages/scipy.libs/libgfortran-83c28eba.so.5.0.0 new file mode 100644 index 0000000000000000000000000000000000000000..b281f18db89efdeea2ed31973b32790e4a4259c1 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy.libs/libgfortran-83c28eba.so.5.0.0 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e554879fee7eda80055b6ca1407a54d00050874f830cc86b2bf3f50df5c7bb2b +size 2714697 diff --git a/venv/lib/python3.12/site-packages/scipy.libs/libquadmath-2284e583.so.0.0.0 b/venv/lib/python3.12/site-packages/scipy.libs/libquadmath-2284e583.so.0.0.0 new file mode 100644 index 0000000000000000000000000000000000000000..efadaf74008cbebfe317a816ed4756fc25ed4e0b --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy.libs/libquadmath-2284e583.so.0.0.0 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a9307bbaa353623fa014e8b682d50c4629f5e5c7d61daa9a8652f7d165cd421a +size 272193 diff --git a/venv/lib/python3.12/site-packages/scipy.libs/libquadmath-96973f99-934c22de.so.0.0.0 b/venv/lib/python3.12/site-packages/scipy.libs/libquadmath-96973f99-934c22de.so.0.0.0 new file mode 100644 index 0000000000000000000000000000000000000000..4e1eb5101254f9fcebd5d9b3fec1f6413b0f931c --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy.libs/libquadmath-96973f99-934c22de.so.0.0.0 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6ed5137f412781ad7863439fb543613f620b43c32b63292a0029246162f5bbc6 +size 250985 diff --git a/venv/lib/python3.12/site-packages/scipy.libs/libscipy_openblas-5f890258.so b/venv/lib/python3.12/site-packages/scipy.libs/libscipy_openblas-5f890258.so new file mode 100644 index 0000000000000000000000000000000000000000..39ad85ce0121060aad81e0e6bec504a74f593712 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy.libs/libscipy_openblas-5f890258.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2a5b4c1a6311873c59132b2ceaaf3c26258a17350b7cd27ea8782a47e3322981 +size 25094609 diff --git a/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_filter_design.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_filter_design.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..36335271d9e013d5124831d2c8fb10e9ecae2aaa --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_filter_design.cpython-312.pyc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:55b921e2782ba8dd39a88c30f0018319dd4f72999e6b0fad987bcd6443c1da37 +size 233862 diff --git a/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_fir_filter_design.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_fir_filter_design.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fd4b7d6626f31f017a965e8d83e73161fe7e010e Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_fir_filter_design.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_lti_conversion.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_lti_conversion.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ea036294b05dd73be5415b2f867946996bdb6424 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_lti_conversion.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_ltisys.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_ltisys.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3dc5126664ac61ca1a0103ccfb32417693cbcedb --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_ltisys.cpython-312.pyc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e3d43b2ac7e4040326b82b41dbbfb5a2581ef321e5e83d544f18dd4ebd289e2d +size 145690 diff --git a/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_max_len_seq.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_max_len_seq.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5b1ef878d5111195b342c7b5756b4912b8f6e181 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_max_len_seq.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_peak_finding.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_peak_finding.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fa7337d2d398a5e41dfb6b8e22f8cdf18c8dfe70 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_peak_finding.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_polyutils.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_polyutils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..58a137a794a4d860630fd9be9f54c44526007909 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_polyutils.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_savitzky_golay.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_savitzky_golay.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4a83e740d423924bfe6739f5b59e17b22c5ed7cf Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_savitzky_golay.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_short_time_fft.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_short_time_fft.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d8fc1be8449caf00132d668ce864303f15760a30 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_short_time_fft.cpython-312.pyc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:954defa35a6f080d3bf7ff3f9f31555eb33020f918c684404d8b2f9c9d3e1e81 +size 115538 diff --git a/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_signal_api.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_signal_api.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a140ca05b149c9d47e58e7b9f9c7cab4fd12cd51 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_signal_api.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_signaltools.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_signaltools.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ed82f53e348670fc68f948cc2bb743e5cf031dad --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_signaltools.cpython-312.pyc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b989c729845c9a21a61a26113ca3ebd61813efbed4b8269a7c58bcb4ece0e6c9 +size 216301 diff --git a/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_spectral_py.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_spectral_py.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ffa23bb037c0adbb8640689a4fbe920e80a179db --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_spectral_py.cpython-312.pyc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:461e16d5df558b5e59dee9847ba64640edbd082489ea5732f7bac9f29ed244df +size 100138 diff --git a/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_spline_filters.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_spline_filters.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9275b7153bb5ccdf70afc1fee9693c4fe62a9d65 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_spline_filters.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_support_alternative_backends.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_support_alternative_backends.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..af536351fdfbaa009a6799748dc3225e8ff27ede Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_support_alternative_backends.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_upfirdn.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_upfirdn.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4050c33fb3e6fd735b6e645d53cac35359541f28 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_upfirdn.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_waveforms.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_waveforms.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e955998cee26510bb3a968820a8ecfc72c9356d8 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_waveforms.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_wavelets.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_wavelets.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..305b75f73041b87d6a70964a9a884add3c06a6a5 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_wavelets.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_whittaker.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_whittaker.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..26e9cb195cf0e51c428c969b5bc488d1e8f14057 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/_whittaker.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/bsplines.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/bsplines.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..669b1c89ed5ea9947d04b78fded8a913979ebf0c Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/bsplines.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/filter_design.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/filter_design.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..47a2e2a7431b871da07d106b10e9c9b8fbf46be5 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/filter_design.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/fir_filter_design.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/fir_filter_design.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b0cb1bfdbb20ba6b9c7a408e55f3baf952be3053 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/fir_filter_design.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/lti_conversion.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/lti_conversion.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..447f84b6c19086d5278275218ca9206678657506 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/lti_conversion.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/ltisys.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/ltisys.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eac40f27e163118a58c428d774a7130e370368f5 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/ltisys.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/signaltools.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/signaltools.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b977e6fdc46f5f37e79ad8e8fff0c2140867e4e8 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/signaltools.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/spectral.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/spectral.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0419af8496af5dee4dd1cfd49c2f0367baababcd Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/spectral.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/spline.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/spline.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..559a4836371dea08a13454ed21af72dbfdec216f Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/spline.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/waveforms.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/waveforms.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..73c61137ca516e3991442ae5474b6b65f8b07804 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/waveforms.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/wavelets.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/wavelets.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ed3765613ea028a52dce98626e8fe60dffdfd449 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/signal/__pycache__/wavelets.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/signal/_arraytools.py b/venv/lib/python3.12/site-packages/scipy/signal/_arraytools.py new file mode 100644 index 0000000000000000000000000000000000000000..87ce75d8d892a64021da7abc5d149556c22cf983 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/_arraytools.py @@ -0,0 +1,264 @@ +""" +Functions for acting on a axis of an array. +""" +import numpy as np + + +def axis_slice(a, start=None, stop=None, step=None, axis=-1): + """Take a slice along axis 'axis' from 'a'. + + Parameters + ---------- + a : numpy.ndarray + The array to be sliced. + start, stop, step : int or None + The slice parameters. + axis : int, optional + The axis of `a` to be sliced. + + Examples + -------- + >>> import numpy as np + >>> from scipy.signal._arraytools import axis_slice + >>> a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + >>> axis_slice(a, start=0, stop=1, axis=1) + array([[1], + [4], + [7]]) + >>> axis_slice(a, start=1, axis=0) + array([[4, 5, 6], + [7, 8, 9]]) + + Notes + ----- + The keyword arguments start, stop and step are used by calling + slice(start, stop, step). This implies axis_slice() does not + handle its arguments the exactly the same as indexing. To select + a single index k, for example, use + axis_slice(a, start=k, stop=k+1) + In this case, the length of the axis 'axis' in the result will + be 1; the trivial dimension is not removed. (Use numpy.squeeze() + to remove trivial axes.) + """ + a_slice = [slice(None)] * a.ndim + a_slice[axis] = slice(start, stop, step) + b = a[tuple(a_slice)] + return b + + +def axis_reverse(a, axis=-1): + """Reverse the 1-D slices of `a` along axis `axis`. + + Returns axis_slice(a, step=-1, axis=axis). + """ + return axis_slice(a, step=-1, axis=axis) + + +def odd_ext(x, n, axis=-1): + """ + Odd extension at the boundaries of an array + + Generate a new ndarray by making an odd extension of `x` along an axis. + + Parameters + ---------- + x : ndarray + The array to be extended. + n : int + The number of elements by which to extend `x` at each end of the axis. + axis : int, optional + The axis along which to extend `x`. Default is -1. + + Examples + -------- + >>> import numpy as np + >>> from scipy.signal._arraytools import odd_ext + >>> a = np.array([[1, 2, 3, 4, 5], [0, 1, 4, 9, 16]]) + >>> odd_ext(a, 2) + array([[-1, 0, 1, 2, 3, 4, 5, 6, 7], + [-4, -1, 0, 1, 4, 9, 16, 23, 28]]) + + Odd extension is a "180 degree rotation" at the endpoints of the original + array: + + >>> t = np.linspace(0, 1.5, 100) + >>> a = 0.9 * np.sin(2 * np.pi * t**2) + >>> b = odd_ext(a, 40) + >>> import matplotlib.pyplot as plt + >>> plt.plot(np.arange(-40, 140), b, 'b', lw=1, label='odd extension') + >>> plt.plot(np.arange(100), a, 'r', lw=2, label='original') + >>> plt.legend(loc='best') + >>> plt.show() + """ + if n < 1: + return x + if n > x.shape[axis] - 1: + raise ValueError(("The extension length n (%d) is too big. " + + "It must not exceed x.shape[axis]-1, which is %d.") + % (n, x.shape[axis] - 1)) + left_end = axis_slice(x, start=0, stop=1, axis=axis) + left_ext = axis_slice(x, start=n, stop=0, step=-1, axis=axis) + right_end = axis_slice(x, start=-1, axis=axis) + right_ext = axis_slice(x, start=-2, stop=-(n + 2), step=-1, axis=axis) + ext = np.concatenate((2 * left_end - left_ext, + x, + 2 * right_end - right_ext), + axis=axis) + return ext + + +def even_ext(x, n, axis=-1): + """ + Even extension at the boundaries of an array + + Generate a new ndarray by making an even extension of `x` along an axis. + + Parameters + ---------- + x : ndarray + The array to be extended. + n : int + The number of elements by which to extend `x` at each end of the axis. + axis : int, optional + The axis along which to extend `x`. Default is -1. + + Examples + -------- + >>> import numpy as np + >>> from scipy.signal._arraytools import even_ext + >>> a = np.array([[1, 2, 3, 4, 5], [0, 1, 4, 9, 16]]) + >>> even_ext(a, 2) + array([[ 3, 2, 1, 2, 3, 4, 5, 4, 3], + [ 4, 1, 0, 1, 4, 9, 16, 9, 4]]) + + Even extension is a "mirror image" at the boundaries of the original array: + + >>> t = np.linspace(0, 1.5, 100) + >>> a = 0.9 * np.sin(2 * np.pi * t**2) + >>> b = even_ext(a, 40) + >>> import matplotlib.pyplot as plt + >>> plt.plot(np.arange(-40, 140), b, 'b', lw=1, label='even extension') + >>> plt.plot(np.arange(100), a, 'r', lw=2, label='original') + >>> plt.legend(loc='best') + >>> plt.show() + """ + if n < 1: + return x + if n > x.shape[axis] - 1: + raise ValueError(("The extension length n (%d) is too big. " + + "It must not exceed x.shape[axis]-1, which is %d.") + % (n, x.shape[axis] - 1)) + left_ext = axis_slice(x, start=n, stop=0, step=-1, axis=axis) + right_ext = axis_slice(x, start=-2, stop=-(n + 2), step=-1, axis=axis) + ext = np.concatenate((left_ext, + x, + right_ext), + axis=axis) + return ext + + +def const_ext(x, n, axis=-1): + """ + Constant extension at the boundaries of an array + + Generate a new ndarray that is a constant extension of `x` along an axis. + + The extension repeats the values at the first and last element of + the axis. + + Parameters + ---------- + x : ndarray + The array to be extended. + n : int + The number of elements by which to extend `x` at each end of the axis. + axis : int, optional + The axis along which to extend `x`. Default is -1. + + Examples + -------- + >>> import numpy as np + >>> from scipy.signal._arraytools import const_ext + >>> a = np.array([[1, 2, 3, 4, 5], [0, 1, 4, 9, 16]]) + >>> const_ext(a, 2) + array([[ 1, 1, 1, 2, 3, 4, 5, 5, 5], + [ 0, 0, 0, 1, 4, 9, 16, 16, 16]]) + + Constant extension continues with the same values as the endpoints of the + array: + + >>> t = np.linspace(0, 1.5, 100) + >>> a = 0.9 * np.sin(2 * np.pi * t**2) + >>> b = const_ext(a, 40) + >>> import matplotlib.pyplot as plt + >>> plt.plot(np.arange(-40, 140), b, 'b', lw=1, label='constant extension') + >>> plt.plot(np.arange(100), a, 'r', lw=2, label='original') + >>> plt.legend(loc='best') + >>> plt.show() + """ + if n < 1: + return x + left_end = axis_slice(x, start=0, stop=1, axis=axis) + ones_shape = [1] * x.ndim + ones_shape[axis] = n + ones = np.ones(ones_shape, dtype=x.dtype) + left_ext = ones * left_end + right_end = axis_slice(x, start=-1, axis=axis) + right_ext = ones * right_end + ext = np.concatenate((left_ext, + x, + right_ext), + axis=axis) + return ext + + +def zero_ext(x, n, axis=-1): + """ + Zero padding at the boundaries of an array + + Generate a new ndarray that is a zero-padded extension of `x` along + an axis. + + Parameters + ---------- + x : ndarray + The array to be extended. + n : int + The number of elements by which to extend `x` at each end of the + axis. + axis : int, optional + The axis along which to extend `x`. Default is -1. + + Examples + -------- + >>> import numpy as np + >>> from scipy.signal._arraytools import zero_ext + >>> a = np.array([[1, 2, 3, 4, 5], [0, 1, 4, 9, 16]]) + >>> zero_ext(a, 2) + array([[ 0, 0, 1, 2, 3, 4, 5, 0, 0], + [ 0, 0, 0, 1, 4, 9, 16, 0, 0]]) + """ + if n < 1: + return x + zeros_shape = list(x.shape) + zeros_shape[axis] = n + zeros = np.zeros(zeros_shape, dtype=x.dtype) + ext = np.concatenate((zeros, x, zeros), axis=axis) + return ext + + +def _validate_fs(fs, allow_none=True): + """ + Check if the given sampling frequency is a scalar and raises an exception + otherwise. If allow_none is False, also raises an exception for none + sampling rates. Returns the sampling frequency as float or none if the + input is none. + """ + if fs is None: + if not allow_none: + raise ValueError("Sampling frequency can not be none.") + else: # should be float + if not np.isscalar(fs): + raise ValueError("Sampling frequency fs must be a single scalar.") + fs = float(fs) + return fs diff --git a/venv/lib/python3.12/site-packages/scipy/signal/_czt.py b/venv/lib/python3.12/site-packages/scipy/signal/_czt.py new file mode 100644 index 0000000000000000000000000000000000000000..24b3892dfd5e48dc0679bea1cba0d6cdb24a5ec0 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/_czt.py @@ -0,0 +1,580 @@ +# This program is public domain +# Authors: Paul Kienzle, Nadav Horesh +""" +Chirp z-transform. + +We provide two interfaces to the chirp z-transform: an object interface +which precalculates part of the transform and can be applied efficiently +to many different data sets, and a functional interface which is applied +only to the given data set. + +Transforms +---------- + +CZT : callable (x, axis=-1) -> array + Define a chirp z-transform that can be applied to different signals. +ZoomFFT : callable (x, axis=-1) -> array + Define a Fourier transform on a range of frequencies. + +Functions +--------- + +czt : array + Compute the chirp z-transform for a signal. +zoom_fft : array + Compute the Fourier transform on a range of frequencies. +""" + +import cmath +import numbers +import numpy as np +from numpy import pi, arange +from scipy.fft import fft, ifft, next_fast_len + +__all__ = ['czt', 'zoom_fft', 'CZT', 'ZoomFFT', 'czt_points'] + + +def _validate_sizes(n, m): + if n < 1 or not isinstance(n, numbers.Integral): + raise ValueError('Invalid number of CZT data ' + f'points ({n}) specified. ' + 'n must be positive and integer type.') + + if m is None: + m = n + elif m < 1 or not isinstance(m, numbers.Integral): + raise ValueError('Invalid number of CZT output ' + f'points ({m}) specified. ' + 'm must be positive and integer type.') + + return m + + +def czt_points(m, w=None, a=1+0j): + """ + Return the points at which the chirp z-transform is computed. + + Parameters + ---------- + m : int + The number of points desired. + w : complex, optional + The ratio between points in each step. + Defaults to equally spaced points around the entire unit circle. + a : complex, optional + The starting point in the complex plane. Default is 1+0j. + + Returns + ------- + out : ndarray + The points in the Z plane at which `CZT` samples the z-transform, + when called with arguments `m`, `w`, and `a`, as complex numbers. + + See Also + -------- + CZT : Class that creates a callable chirp z-transform function. + czt : Convenience function for quickly calculating CZT. + + Examples + -------- + Plot the points of a 16-point FFT: + + >>> import numpy as np + >>> from scipy.signal import czt_points + >>> points = czt_points(16) + >>> import matplotlib.pyplot as plt + >>> plt.plot(points.real, points.imag, 'o') + >>> plt.gca().add_patch(plt.Circle((0,0), radius=1, fill=False, alpha=.3)) + >>> plt.axis('equal') + >>> plt.show() + + and a 91-point logarithmic spiral that crosses the unit circle: + + >>> m, w, a = 91, 0.995*np.exp(-1j*np.pi*.05), 0.8*np.exp(1j*np.pi/6) + >>> points = czt_points(m, w, a) + >>> plt.plot(points.real, points.imag, 'o') + >>> plt.gca().add_patch(plt.Circle((0,0), radius=1, fill=False, alpha=.3)) + >>> plt.axis('equal') + >>> plt.show() + """ + m = _validate_sizes(1, m) + + k = arange(m) + + a = 1.0 * a # at least float + + if w is None: + # Nothing specified, default to FFT + return a * np.exp(2j * pi * k / m) + else: + # w specified + w = 1.0 * w # at least float + return a * w**-k + + +class CZT: + """ + Create a callable chirp z-transform function. + + Transform to compute the frequency response around a spiral. + Objects of this class are callables which can compute the + chirp z-transform on their inputs. This object precalculates the constant + chirps used in the given transform. + + Parameters + ---------- + n : int + The size of the signal. + m : int, optional + The number of output points desired. Default is `n`. + w : complex, optional + The ratio between points in each step. This must be precise or the + accumulated error will degrade the tail of the output sequence. + Defaults to equally spaced points around the entire unit circle. + a : complex, optional + The starting point in the complex plane. Default is 1+0j. + + Returns + ------- + f : CZT + Callable object ``f(x, axis=-1)`` for computing the chirp z-transform + on `x`. + + See Also + -------- + czt : Convenience function for quickly calculating CZT. + ZoomFFT : Class that creates a callable partial FFT function. + + Notes + ----- + The defaults are chosen such that ``f(x)`` is equivalent to + ``fft.fft(x)`` and, if ``m > len(x)``, that ``f(x, m)`` is equivalent to + ``fft.fft(x, m)``. + + If `w` does not lie on the unit circle, then the transform will be + around a spiral with exponentially-increasing radius. Regardless, + angle will increase linearly. + + For transforms that do lie on the unit circle, accuracy is better when + using `ZoomFFT`, since any numerical error in `w` is + accumulated for long data lengths, drifting away from the unit circle. + + The chirp z-transform can be faster than an equivalent FFT with + zero padding. Try it with your own array sizes to see. + + However, the chirp z-transform is considerably less precise than the + equivalent zero-padded FFT. + + As this CZT is implemented using the Bluestein algorithm, it can compute + large prime-length Fourier transforms in O(N log N) time, rather than the + O(N**2) time required by the direct DFT calculation. (`scipy.fft` also + uses Bluestein's algorithm'.) + + (The name "chirp z-transform" comes from the use of a chirp in the + Bluestein algorithm. It does not decompose signals into chirps, like + other transforms with "chirp" in the name.) + + References + ---------- + .. [1] Leo I. Bluestein, "A linear filtering approach to the computation + of the discrete Fourier transform," Northeast Electronics Research + and Engineering Meeting Record 10, 218-219 (1968). + .. [2] Rabiner, Schafer, and Rader, "The chirp z-transform algorithm and + its application," Bell Syst. Tech. J. 48, 1249-1292 (1969). + + Examples + -------- + Compute multiple prime-length FFTs: + + >>> from scipy.signal import CZT + >>> import numpy as np + >>> a = np.random.rand(7) + >>> b = np.random.rand(7) + >>> c = np.random.rand(7) + >>> czt_7 = CZT(n=7) + >>> A = czt_7(a) + >>> B = czt_7(b) + >>> C = czt_7(c) + + Display the points at which the FFT is calculated: + + >>> czt_7.points() + array([ 1.00000000+0.j , 0.62348980+0.78183148j, + -0.22252093+0.97492791j, -0.90096887+0.43388374j, + -0.90096887-0.43388374j, -0.22252093-0.97492791j, + 0.62348980-0.78183148j]) + >>> import matplotlib.pyplot as plt + >>> plt.plot(czt_7.points().real, czt_7.points().imag, 'o') + >>> plt.gca().add_patch(plt.Circle((0,0), radius=1, fill=False, alpha=.3)) + >>> plt.axis('equal') + >>> plt.show() + """ + + def __init__(self, n, m=None, w=None, a=1+0j): + m = _validate_sizes(n, m) + + k = arange(max(m, n), dtype=np.min_scalar_type(-max(m, n)**2)) + + if w is None: + # Nothing specified, default to FFT-like + w = cmath.exp(-2j*pi/m) + wk2 = np.exp(-(1j * pi * ((k**2) % (2*m))) / m) + else: + # w specified + wk2 = w**(k**2/2.) + + a = 1.0 * a # at least float + + self.w, self.a = w, a + self.m, self.n = m, n + + nfft = next_fast_len(n + m - 1) + self._Awk2 = a**-k[:n] * wk2[:n] + self._nfft = nfft + self._Fwk2 = fft(1/np.hstack((wk2[n-1:0:-1], wk2[:m])), nfft) + self._wk2 = wk2[:m] + self._yidx = slice(n-1, n+m-1) + + def __call__(self, x, *, axis=-1): + """ + Calculate the chirp z-transform of a signal. + + Parameters + ---------- + x : array + The signal to transform. + axis : int, optional + Axis over which to compute the FFT. If not given, the last axis is + used. + + Returns + ------- + out : ndarray + An array of the same dimensions as `x`, but with the length of the + transformed axis set to `m`. + """ + x = np.asarray(x) + if x.shape[axis] != self.n: + raise ValueError(f"CZT defined for length {self.n}, not " + f"{x.shape[axis]}") + # Calculate transpose coordinates, to allow operation on any given axis + trnsp = np.arange(x.ndim) + trnsp[[axis, -1]] = [-1, axis] + x = x.transpose(*trnsp) + y = ifft(self._Fwk2 * fft(x*self._Awk2, self._nfft)) + y = y[..., self._yidx] * self._wk2 + return y.transpose(*trnsp) + + def points(self): + """ + Return the points at which the chirp z-transform is computed. + + Returns + ------- + ndarray + The points in the Z plane at which `CZT` samples the z-transform. + """ + return czt_points(self.m, self.w, self.a) + + +class ZoomFFT(CZT): + """ + Create a callable zoom FFT transform function. + + This is a specialization of the chirp z-transform (`CZT`) for a set of + equally-spaced frequencies around the unit circle, used to calculate a + section of the FFT more efficiently than calculating the entire FFT and + truncating. + + Parameters + ---------- + n : int + The size of the signal. + fn : array_like + A length-2 sequence [`f1`, `f2`] giving the frequency range, or a + scalar, for which the range [0, `fn`] is assumed. + m : int, optional + The number of points to evaluate. Default is `n`. + fs : float, optional + The sampling frequency. If ``fs=10`` represented 10 kHz, for example, + then `f1` and `f2` would also be given in kHz. + The default sampling frequency is 2, so `f1` and `f2` should be + in the range [0, 1] to keep the transform below the Nyquist + frequency. + endpoint : bool, optional + If True, `f2` is the last sample. Otherwise, it is not included. + Default is False. + + Returns + ------- + f : ZoomFFT + Callable object ``f(x, axis=-1)`` for computing the zoom FFT on `x`. + + See Also + -------- + zoom_fft : Convenience function for calculating a zoom FFT. + + Notes + ----- + The defaults are chosen such that ``f(x, 2)`` is equivalent to + ``fft.fft(x)`` and, if ``m > len(x)``, that ``f(x, 2, m)`` is equivalent to + ``fft.fft(x, m)``. + + Sampling frequency is 1/dt, the time step between samples in the + signal `x`. The unit circle corresponds to frequencies from 0 up + to the sampling frequency. The default sampling frequency of 2 + means that `f1`, `f2` values up to the Nyquist frequency are in the + range [0, 1). For `f1`, `f2` values expressed in radians, a sampling + frequency of 2*pi should be used. + + Remember that a zoom FFT can only interpolate the points of the existing + FFT. It cannot help to resolve two separate nearby frequencies. + Frequency resolution can only be increased by increasing acquisition + time. + + These functions are implemented using Bluestein's algorithm (as is + `scipy.fft`). [2]_ + + References + ---------- + .. [1] Steve Alan Shilling, "A study of the chirp z-transform and its + applications", pg 29 (1970) + https://krex.k-state.edu/dspace/bitstream/handle/2097/7844/LD2668R41972S43.pdf + .. [2] Leo I. Bluestein, "A linear filtering approach to the computation + of the discrete Fourier transform," Northeast Electronics Research + and Engineering Meeting Record 10, 218-219 (1968). + + Examples + -------- + To plot the transform results use something like the following: + + >>> import numpy as np + >>> from scipy.signal import ZoomFFT + >>> t = np.linspace(0, 1, 1021) + >>> x = np.cos(2*np.pi*15*t) + np.sin(2*np.pi*17*t) + >>> f1, f2 = 5, 27 + >>> transform = ZoomFFT(len(x), [f1, f2], len(x), fs=1021) + >>> X = transform(x) + >>> f = np.linspace(f1, f2, len(x)) + >>> import matplotlib.pyplot as plt + >>> plt.plot(f, 20*np.log10(np.abs(X))) + >>> plt.show() + """ + + def __init__(self, n, fn, m=None, *, fs=2, endpoint=False): + m = _validate_sizes(n, m) + + k = arange(max(m, n), dtype=np.min_scalar_type(-max(m, n)**2)) + + if np.size(fn) == 2: + f1, f2 = fn + elif np.size(fn) == 1: + f1, f2 = 0.0, fn + else: + raise ValueError('fn must be a scalar or 2-length sequence') + + self.f1, self.f2, self.fs = f1, f2, fs + + if endpoint: + scale = ((f2 - f1) * m) / (fs * (m - 1)) + else: + scale = (f2 - f1) / fs + a = cmath.exp(2j * pi * f1/fs) + wk2 = np.exp(-(1j * pi * scale * k**2) / m) + + self.w = cmath.exp(-2j*pi/m * scale) + self.a = a + self.m, self.n = m, n + + ak = np.exp(-2j * pi * f1/fs * k[:n]) + self._Awk2 = ak * wk2[:n] + + nfft = next_fast_len(n + m - 1) + self._nfft = nfft + self._Fwk2 = fft(1/np.hstack((wk2[n-1:0:-1], wk2[:m])), nfft) + self._wk2 = wk2[:m] + self._yidx = slice(n-1, n+m-1) + + +def czt(x, m=None, w=None, a=1+0j, *, axis=-1): + """ + Compute the frequency response around a spiral in the Z plane. + + Parameters + ---------- + x : array + The signal to transform. + m : int, optional + The number of output points desired. Default is the length of the + input data. + w : complex, optional + The ratio between points in each step. This must be precise or the + accumulated error will degrade the tail of the output sequence. + Defaults to equally spaced points around the entire unit circle. + a : complex, optional + The starting point in the complex plane. Default is 1+0j. + axis : int, optional + Axis over which to compute the FFT. If not given, the last axis is + used. + + Returns + ------- + out : ndarray + An array of the same dimensions as `x`, but with the length of the + transformed axis set to `m`. + + See Also + -------- + CZT : Class that creates a callable chirp z-transform function. + zoom_fft : Convenience function for partial FFT calculations. + + Notes + ----- + The defaults are chosen such that ``signal.czt(x)`` is equivalent to + ``fft.fft(x)`` and, if ``m > len(x)``, that ``signal.czt(x, m)`` is + equivalent to ``fft.fft(x, m)``. + + If the transform needs to be repeated, use `CZT` to construct a + specialized transform function which can be reused without + recomputing constants. + + An example application is in system identification, repeatedly evaluating + small slices of the z-transform of a system, around where a pole is + expected to exist, to refine the estimate of the pole's true location. [1]_ + + References + ---------- + .. [1] Steve Alan Shilling, "A study of the chirp z-transform and its + applications", pg 20 (1970) + https://krex.k-state.edu/dspace/bitstream/handle/2097/7844/LD2668R41972S43.pdf + + Examples + -------- + Generate a sinusoid: + + >>> import numpy as np + >>> f1, f2, fs = 8, 10, 200 # Hz + >>> t = np.linspace(0, 1, fs, endpoint=False) + >>> x = np.sin(2*np.pi*t*f2) + >>> import matplotlib.pyplot as plt + >>> plt.plot(t, x) + >>> plt.axis([0, 1, -1.1, 1.1]) + >>> plt.show() + + Its discrete Fourier transform has all of its energy in a single frequency + bin: + + >>> from scipy.fft import rfft, rfftfreq + >>> from scipy.signal import czt, czt_points + >>> plt.plot(rfftfreq(fs, 1/fs), abs(rfft(x))) + >>> plt.margins(0, 0.1) + >>> plt.show() + + However, if the sinusoid is logarithmically-decaying: + + >>> x = np.exp(-t*f1) * np.sin(2*np.pi*t*f2) + >>> plt.plot(t, x) + >>> plt.axis([0, 1, -1.1, 1.1]) + >>> plt.show() + + the DFT will have spectral leakage: + + >>> plt.plot(rfftfreq(fs, 1/fs), abs(rfft(x))) + >>> plt.margins(0, 0.1) + >>> plt.show() + + While the DFT always samples the z-transform around the unit circle, the + chirp z-transform allows us to sample the Z-transform along any + logarithmic spiral, such as a circle with radius smaller than unity: + + >>> M = fs // 2 # Just positive frequencies, like rfft + >>> a = np.exp(-f1/fs) # Starting point of the circle, radius < 1 + >>> w = np.exp(-1j*np.pi/M) # "Step size" of circle + >>> points = czt_points(M + 1, w, a) # M + 1 to include Nyquist + >>> plt.plot(points.real, points.imag, '.') + >>> plt.gca().add_patch(plt.Circle((0,0), radius=1, fill=False, alpha=.3)) + >>> plt.axis('equal'); plt.axis([-1.05, 1.05, -0.05, 1.05]) + >>> plt.show() + + With the correct radius, this transforms the decaying sinusoid (and others + with the same decay rate) without spectral leakage: + + >>> z_vals = czt(x, M + 1, w, a) # Include Nyquist for comparison to rfft + >>> freqs = np.angle(points)*fs/(2*np.pi) # angle = omega, radius = sigma + >>> plt.plot(freqs, abs(z_vals)) + >>> plt.margins(0, 0.1) + >>> plt.show() + """ + x = np.asarray(x) + transform = CZT(x.shape[axis], m=m, w=w, a=a) + return transform(x, axis=axis) + + +def zoom_fft(x, fn, m=None, *, fs=2, endpoint=False, axis=-1): + """ + Compute the DFT of `x` only for frequencies in range `fn`. + + Parameters + ---------- + x : array + The signal to transform. + fn : array_like + A length-2 sequence [`f1`, `f2`] giving the frequency range, or a + scalar, for which the range [0, `fn`] is assumed. + m : int, optional + The number of points to evaluate. The default is the length of `x`. + fs : float, optional + The sampling frequency. If ``fs=10`` represented 10 kHz, for example, + then `f1` and `f2` would also be given in kHz. + The default sampling frequency is 2, so `f1` and `f2` should be + in the range [0, 1] to keep the transform below the Nyquist + frequency. + endpoint : bool, optional + If True, `f2` is the last sample. Otherwise, it is not included. + Default is False. + axis : int, optional + Axis over which to compute the FFT. If not given, the last axis is + used. + + Returns + ------- + out : ndarray + The transformed signal. The Fourier transform will be calculated + at the points f1, f1+df, f1+2df, ..., f2, where df=(f2-f1)/m. + + See Also + -------- + ZoomFFT : Class that creates a callable partial FFT function. + + Notes + ----- + The defaults are chosen such that ``signal.zoom_fft(x, 2)`` is equivalent + to ``fft.fft(x)`` and, if ``m > len(x)``, that ``signal.zoom_fft(x, 2, m)`` + is equivalent to ``fft.fft(x, m)``. + + To graph the magnitude of the resulting transform, use:: + + plot(linspace(f1, f2, m, endpoint=False), abs(zoom_fft(x, [f1, f2], m))) + + If the transform needs to be repeated, use `ZoomFFT` to construct + a specialized transform function which can be reused without + recomputing constants. + + Examples + -------- + To plot the transform results use something like the following: + + >>> import numpy as np + >>> from scipy.signal import zoom_fft + >>> t = np.linspace(0, 1, 1021) + >>> x = np.cos(2*np.pi*15*t) + np.sin(2*np.pi*17*t) + >>> f1, f2 = 5, 27 + >>> X = zoom_fft(x, [f1, f2], len(x), fs=1021) + >>> f = np.linspace(f1, f2, len(x)) + >>> import matplotlib.pyplot as plt + >>> plt.plot(f, 20*np.log10(np.abs(X))) + >>> plt.show() + """ + x = np.asarray(x) + transform = ZoomFFT(x.shape[axis], fn, m=m, fs=fs, endpoint=endpoint) + return transform(x, axis=axis) diff --git a/venv/lib/python3.12/site-packages/scipy/signal/_delegators.py b/venv/lib/python3.12/site-packages/scipy/signal/_delegators.py new file mode 100644 index 0000000000000000000000000000000000000000..42b2c22500c322a5e4fd839b447642f7e5bf796c --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/_delegators.py @@ -0,0 +1,573 @@ +"""Delegators for alternative backends in scipy.signal. + +The signature of `func_signature` must match the signature of signal.func. +The job of a `func_signature` is to know which arguments of `signal.func` +are arrays. + +* signatures are generated by + +-------------- + import inspect + from scipy import signal + + names = [x for x in dir(signal) if not x.startswith('_')] + objs = [getattr(signal, name) for name in names] + funcs = [obj for obj in objs if inspect.isroutine(obj)] + + for func in funcs: + try: + sig = inspect.signature(func) + except ValueError: + sig = "( FIXME )" + print(f"def {func.__name__}_signature{sig}:\n\treturn array_namespace(... + )\n\n") +--------------- + +* which arguments to delegate on: manually trawled the documentation for + array-like and array arguments + +""" +import numpy as np +from scipy._lib._array_api import array_namespace, np_compat, is_jax + + +def _skip_if_lti(arg): + """Handle `system` arg overloads. + + ATM, only pass tuples through. Consider updating when cupyx.lti class + is supported. + """ + if isinstance(arg, tuple): + return arg + else: + return (None,) + + +def _skip_if_str_or_tuple(window): + """Handle `window` being a str or a tuple or an array-like. + """ + if isinstance(window, str) or isinstance(window, tuple) or callable(window): + return None + else: + return window + + +def _skip_if_poly1d(arg): + return None if isinstance(arg, np.poly1d) else arg + + +################### + +def abcd_normalize_signature(A=None, B=None, C=None, D=None): + return array_namespace(A, B, C, D) + +def argrelextrema_signature(data, *args, **kwds): + return array_namespace(data) + +argrelmax_signature = argrelextrema_signature +argrelmin_signature = argrelextrema_signature + + +def band_stop_obj_signature(wp, ind, passb, stopb, gpass, gstop, type): + return array_namespace(passb, stopb) + + +def bessel_signature(N, Wn, *args, **kwds): + return array_namespace(Wn) + +butter_signature = bessel_signature + + +def cheby2_signature(N, rs, Wn, *args, **kwds): + return array_namespace(Wn) + + +def cheby1_signature(N, rp, Wn, *args, **kwds): + return array_namespace(Wn) + + +def ellip_signature(N, rp, rs, Wn, *args, **kwds): + return array_namespace(Wn) + + +########################## XXX: no arrays in, arrays out +def besselap_signature(N, norm='phase', *, xp=None, device=None): + return np if xp is None else xp + + +def buttap_signature(N, *, xp=None, device=None): + return np if xp is None else xp + + +def cheb1ap_signature(N, rp, *, xp=None, device=None): + return np if xp is None else xp + + +def cheb2ap_signature(N, rs, *, xp=None, device=None): + return np if xp is None else xp + +def ellipap_signature(N, rp, rs, *, xp=None, device=None): + return np if xp is None else xp + +def correlation_lags_signature(in1_len, in2_len, mode='full'): + return np + + +def czt_points_signature(m, w=None, a=(1+0j)): + return np + + +def gammatone_signature( + freq, ftype, order=None, numtaps=None, fs=None, *, xp=None, device=None + ): + return np_compat if xp is None else xp + + +def iircomb_signature( + w0, Q, ftype='notch', fs=2.0, *, pass_zero=False, xp=None, device=None +): + return np_compat if xp is None else xp + + +def iirnotch_signature(w0, Q, fs=2.0, *, xp=None, device=None): + return np if xp is None else xp + +iirpeak_signature = iirnotch_signature + + +def savgol_coeffs_signature( + window_length, polyorder, deriv=0, delta=1.0, pos=None, use='conv', + *, xp=None, device=None +): + return np if xp is None else xp + + + +def unit_impulse_signature(shape, idx=None, dtype=float): + return np +############################ + + +def buttord_signature(wp, ws, gpass, gstop, analog=False, fs=None): + return array_namespace(wp, ws) + +cheb1ord_signature = buttord_signature +cheb2ord_signature = buttord_signature +ellipord_signature = buttord_signature + + +########### NB: scalars in, scalars out +def kaiser_atten_signature(numtaps, width): + return np + +def kaiser_beta_signature(a): + return np + +def kaiserord_signature(ripple, width): + return np + +def get_window_signature(window, Nx, fftbins=True, *, xp=None, device=None): + return np if xp is None else xp +################################# + + +def bode_signature(system, w=None, n=100): + return array_namespace(*_skip_if_lti(system), w) + +dbode_signature = bode_signature + + +def freqresp_signature(system, w=None, n=10000): + return array_namespace(*_skip_if_lti(system), w) + +dfreqresp_signature = freqresp_signature + + +def impulse_signature(system, X0=None, T=None, N=None): + return array_namespace(*_skip_if_lti(system), X0, T) + + +def dimpulse_signature(system, x0=None, t=None, n=None): + return array_namespace(*_skip_if_lti(system), x0, t) + + +def lsim_signature(system, U, T, X0=None, interp=True): + return array_namespace(*_skip_if_lti(system), U, T, X0) + + +def dlsim_signature(system, u, t=None, x0=None): + return array_namespace(*_skip_if_lti(system), u, t, x0) + + +def step_signature(system, X0=None, T=None, N=None): + return array_namespace(*_skip_if_lti(system), X0, T) + +def dstep_signature(system, x0=None, t=None, n=None): + return array_namespace(*_skip_if_lti(system), x0, t) + + +def cont2discrete_signature(system, dt, method='zoh', alpha=None): + return array_namespace(*_skip_if_lti(system)) + + +def bilinear_signature(b, a, fs=1.0): + return array_namespace(b, a) + + +def bilinear_zpk_signature(z, p, k, fs): + return array_namespace(z, p) + + +def chirp_signature(t,*args, **kwds): + return array_namespace(t) + + +############## XXX: array-likes in, str out +def choose_conv_method_signature(in1, in2, *args, **kwds): + return array_namespace(in1, in2) +############################################ + + +def convolve_signature(in1, in2, *args, **kwds): + return array_namespace(in1, in2) + +fftconvolve_signature = convolve_signature +oaconvolve_signature = convolve_signature +correlate_signature = convolve_signature +correlate_signature = convolve_signature +convolve2d_signature = convolve_signature +correlate2d_signature = convolve_signature + + +def coherence_signature(x, y, fs=1.0, window='hann_periodic', *args, **kwds): + return array_namespace(x, y, _skip_if_str_or_tuple(window)) + + +def csd_signature(x, y, fs=1.0, window='hann_periodic', *args, **kwds): + return array_namespace(x, y, _skip_if_str_or_tuple(window)) + + +def periodogram_signature(x, fs=1.0, window='boxcar', *args, **kwds): + return array_namespace(x, _skip_if_str_or_tuple(window)) + + +def welch_signature(x, fs=1.0, window='hann_periodic', *args, **kwds): + return array_namespace(x, _skip_if_str_or_tuple(window)) + + +def spectrogram_signature(x, fs=1.0, window=('tukey_periodic', 0.25), *args, **kwds): + return array_namespace(x, _skip_if_str_or_tuple(window)) + + +def stft_signature(x, fs=1.0, window='hann_periodic', *args, **kwds): + return array_namespace(x, _skip_if_str_or_tuple(window)) + + +def istft_signature(Zxx, fs=1.0, window='hann_periodic', *args, **kwds): + return array_namespace(Zxx, _skip_if_str_or_tuple(window)) + + +def resample_signature(x, num, t=None, axis=0, window=None, domain='time'): + return array_namespace(x, t, _skip_if_str_or_tuple(window)) + + +def resample_poly_signature(x, up, down, axis=0, window=('kaiser', 5.0), *args, **kwds): + return array_namespace(x, _skip_if_str_or_tuple(window)) + + +def check_COLA_signature(window, nperseg, noverlap, tol=1e-10): + return array_namespace(_skip_if_str_or_tuple(window)) + + +def check_NOLA_signature(window, nperseg, noverlap, tol=1e-10): + return array_namespace(_skip_if_str_or_tuple(window)) + + +def czt_signature(x, *args, **kwds): + return array_namespace(x) + +decimate_signature = czt_signature +gauss_spline_signature = czt_signature + + +def deconvolve_signature(signal, divisor): + return array_namespace(signal, divisor) + + +def detrend_signature(data, axis=1, type='linear', bp=0, *args, **kwds): + xp = array_namespace(data) + # JAX doesn't accept JAX arrays for bp, only ints, lists and NumPy + # arrays. + return xp if is_jax(xp) else array_namespace(data, bp) + + +def filtfilt_signature(b, a, x, *args, **kwds): + return array_namespace(b, a, x) + + +def lfilter_signature(b, a, x, axis=-1, zi=None): + return array_namespace(b, a, x, zi) + + +def envelope_signature(z, *args, **kwds): + return array_namespace(z) + + +def find_peaks_signature( + x, height=None, threshold=None, distance=None, prominence=None, width=None, + wlen=None, rel_height=0.5, plateau_size=None +): + # TODO: fix me - `prominence` is not necessarily an array. + # return array_namespace(x, height, threshold, prominence, width, plateau_size) + # See https://github.com/scipy/scipy/pull/22644#issuecomment-3568443768. For now: + return np_compat + +def find_peaks_cwt_signature( + vector, widths, wavelet=None, max_distances=None, *args, **kwds +): + return array_namespace(vector, widths, max_distances) + + +def findfreqs_signature(num, den, N, kind='ba'): + return array_namespace(num, den) + + +def firls_signature(numtaps, bands, desired, *, weight=None, fs=None): + return array_namespace(bands, desired, weight) + + +def firwin_signature(numtaps, cutoff, *args, **kwds): + if isinstance(cutoff, int | float): + xp = np_compat + else: + xp = array_namespace(cutoff) + return xp + + +def firwin2_signature(numtaps, freq, gain, *args, **kwds): + return array_namespace(freq, gain) + + +def freqs_zpk_signature(z, p, k, worN=200, *args, **kwds): + return array_namespace(z, p, worN) + +freqz_zpk_signature = freqs_zpk_signature + + +def freqs_signature(b, a, worN=200, *args, **kwds): + return array_namespace(b, a, worN) + + +def freqz_signature(b, a=1, worN=512, *args, **kwds): + # differs from freqs: `a` has a default value + return array_namespace(b, a, worN) + + +def freqz_sos_signature(sos, worN=512, *args, **kwds): + return array_namespace(sos, worN) + +sosfreqz_signature = freqz_sos_signature + + +def gausspulse_signature(t, *args, **kwds): + arr_t = None if isinstance(t, str) else t + return array_namespace(arr_t) + + +def group_delay_signature(system, w=512, whole=False, fs=6.283185307179586): + return array_namespace(*system, w) + + +def hilbert_signature(x, *args, **kwds): + return array_namespace(x) + +hilbert2_signature = hilbert_signature + + +def iirdesign_signature(wp, ws, *args, **kwds): + return array_namespace(wp, ws) + + +def iirfilter_signature(N, Wn, *args, **kwds): + return array_namespace(Wn) + + +def invres_signature(r, p, k, tol=0.001, rtype='avg'): + return array_namespace(r, p, k) + +invresz_signature = invres_signature + + +############################### XXX: excluded, blacklisted on CuPy (mismatched API) +def lfilter_zi_signature(b, a): + return array_namespace(b, a) + +def sosfilt_zi_signature(sos): + return array_namespace(sos) + +# needs to be blacklisted on CuPy (is not implemented) +def remez_signature(numtaps, bands, desired, *, weight=None, **kwds): + return array_namespace(bands, desired, weight) +############################################# + +def lfiltic_signature(b, a, y, x=None): + return array_namespace(b, a, y, x) + + +def lombscargle_signature( + x, y, freqs, precenter=False, normalize=False, *, + weights=None, floating_mean=False +): + return array_namespace(x, y, freqs, weights) + + +def lp2bp_signature(b, a, *args, **kwds): + return array_namespace(b, a) + +lp2bs_signature = lp2bp_signature +lp2hp_signature = lp2bp_signature +lp2lp_signature = lp2bp_signature + +tf2zpk_signature = lp2bp_signature +tf2sos_signature = lp2bp_signature + +normalize_signature = lp2bp_signature +residue_signature = lp2bp_signature +residuez_signature = residue_signature + + +def lp2bp_zpk_signature(z, p, k, *args, **kwds): + return array_namespace(z, p) + +lp2bs_zpk_signature = lp2bp_zpk_signature +lp2hp_zpk_signature = lp2bs_zpk_signature +lp2lp_zpk_signature = lp2bs_zpk_signature + + +def zpk2sos_signature(z, p, k, *args, **kwds): + return array_namespace(z, p) + +zpk2ss_signature = zpk2sos_signature +zpk2tf_signature = zpk2sos_signature + + +def max_len_seq_signature(nbits, state=None, length=None, taps=None): + return array_namespace(state, taps) + + +def medfilt_signature(volume, kernel_size=None): + return array_namespace(volume) + + +def medfilt2d_signature(input, kernel_size=3): + return array_namespace(input) + + +def minimum_phase_signature(h, *args, **kwds): + return array_namespace(h) + + +def order_filter_signature(a, domain, rank): + return array_namespace(a, domain) + + +def peak_prominences_signature(x, peaks, *args, **kwds): + return array_namespace(x, peaks) + + +peak_widths_signature = peak_prominences_signature + + +def place_poles_signature(A, B, poles, method='YT', rtol=0.001, maxiter=30): + return array_namespace(A, B, poles) + + +def savgol_filter_signature(x, *args, **kwds): + return array_namespace(x) + + +def sawtooth_signature(t, width=1): + return array_namespace(t) + + +def sepfir2d_signature(input, hrow, hcol): + return array_namespace(input, hrow, hcol) + + +def sos2tf_signature(sos): + return array_namespace(sos) + + +sos2zpk_signature = sos2tf_signature + + +def sosfilt_signature(sos, x, axis=-1, zi=None): + return array_namespace(sos, x, zi) + + +def sosfiltfilt_signature(sos, x, *args, **kwds): + return array_namespace(sos, x) + + +def spline_filter_signature(Iin, lmbda=5.0): + return array_namespace(Iin) + + +def square_signature(t, duty=0.5): + return array_namespace(t, duty) + + +def ss2tf_signature(A, B, C, D, input=0): + return array_namespace(A, B, C, D) + +ss2zpk_signature = ss2tf_signature + + +def sweep_poly_signature(t, poly, phi=0): + return array_namespace(t, _skip_if_poly1d(poly)) + + +def symiirorder1_signature(signal, c0, z1, precision=-1.0): + return array_namespace(signal) + + +def symiirorder2_signature(input, r, omega, precision=-1.0): + return array_namespace(input, r, omega) + + +def cspline1d_signature(signal, *args, **kwds): + return array_namespace(signal) + +qspline1d_signature = cspline1d_signature +cspline2d_signature = cspline1d_signature +qspline2d_signature = qspline1d_signature + + +def cspline1d_eval_signature(cj, newx, *args, **kwds): + return array_namespace(cj, newx) + +qspline1d_eval_signature = cspline1d_eval_signature + + +def tf2ss_signature(num, den): + return array_namespace(num, den) + + +def unique_roots_signature(p, tol=0.001, rtype='min'): + return array_namespace(p) + + +def upfirdn_signature(h, x, up=1, down=1, axis=-1, mode='constant', cval=0): + return array_namespace(h, x) + + +def vectorstrength_signature(events, period): + return array_namespace(events, period) + + +def wiener_signature(im, mysize=None, noise=None): + return array_namespace(im) + + +def zoom_fft_signature(x, fn, m=None, *, fs=2, endpoint=False, axis=-1): + return array_namespace(x, fn) diff --git a/venv/lib/python3.12/site-packages/scipy/signal/_filter_design.py b/venv/lib/python3.12/site-packages/scipy/signal/_filter_design.py new file mode 100644 index 0000000000000000000000000000000000000000..88710c15393ef7b1409862b6a0fc0d57e42afeff --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/_filter_design.py @@ -0,0 +1,6097 @@ +"""Filter design.""" +import math +import cmath +import operator +import warnings +import builtins + +from math import pi + +import numpy as np +from numpy.polynomial.polynomial import polyval as npp_polyval + +from scipy import special, optimize, fft as sp_fft +from scipy.special import comb +from scipy._lib import doccer +from scipy._lib._util import float_factorial +from scipy.signal._arraytools import _validate_fs +from scipy.signal import _polyutils as _pu + +import scipy._external.array_api_extra as xpx +from scipy._lib._array_api import ( + array_namespace, xp_promote, xp_size, xp_default_dtype, is_jax, xp_float_to_complex, + xp_result_type, +) +from scipy._external.array_api_compat import numpy as np_compat + + +__all__ = ['findfreqs', 'freqs', 'freqz', 'tf2zpk', 'zpk2tf', 'normalize', + 'lp2lp', 'lp2hp', 'lp2bp', 'lp2bs', 'bilinear', 'iirdesign', + 'iirfilter', 'butter', 'cheby1', 'cheby2', 'ellip', 'bessel', + 'band_stop_obj', 'buttord', 'cheb1ord', 'cheb2ord', 'ellipord', + 'buttap', 'cheb1ap', 'cheb2ap', 'ellipap', 'besselap', + 'BadCoefficients', 'freqs_zpk', 'freqz_zpk', + 'tf2sos', 'sos2tf', 'zpk2sos', 'sos2zpk', 'group_delay', + 'sosfreqz', 'freqz_sos', 'iirnotch', 'iirpeak', 'bilinear_zpk', + 'lp2lp_zpk', 'lp2hp_zpk', 'lp2bp_zpk', 'lp2bs_zpk', + 'gammatone', 'iircomb'] + + +class BadCoefficients(UserWarning): + """Warning about badly conditioned filter coefficients.""" + pass + + +abs = np.absolute + + +def _is_int_type(x): + """ + Check if input is of a scalar integer type (so ``5`` and ``array(5)`` will + pass, while ``5.0`` and ``array([5])`` will fail. + """ + if np.ndim(x) != 0: + # Older versions of NumPy did not raise for np.array([1]).__index__() + # This is safe to remove when support for those versions is dropped + return False + try: + operator.index(x) + except TypeError: + return False + else: + return True + + +def _real_dtype_for_complex(dtyp, *, xp): + if xp.isdtype(dtyp, 'real floating'): + return dtyp + if dtyp == xp.complex64: + return xp.float32 + elif dtyp == xp.complex128: + return xp.float64 + else: + raise ValueError(f"Unknown dtype {dtyp}.") + + +# https://github.com/numpy/numpy/blob/v2.2.0/numpy/_core/function_base.py#L195-L302 +def _logspace(start, stop, num=50, endpoint=True, base=10.0, dtype=None, *, xp): + if not isinstance(base, float | int) and xp.asarray(base).ndim > 0: + # If base is non-scalar, broadcast it with the others, since it + # may influence how axis is interpreted. + start, stop, base = map(xp.asarray, (start, stop, base)) + ndmax = xp.broadcast_arrays(start, stop, base).ndim + start, stop, base = ( + xpx.atleast_nd(a, ndim=ndmax) + for a in (start, stop, base) + ) + base = xp.expand_dims(base) + try: + result_dt = xp.result_type(start, stop, base) + except ValueError: + # all of start, stop and base are python scalars + result_dt = xp_default_dtype(xp) + y = xp.linspace(start, stop, num=num, endpoint=endpoint, dtype=result_dt) + + yp = xp.pow(base, y) + if dtype is None: + return yp + return xp.astype(yp, dtype, copy=False) + + +def findfreqs(num, den, N, kind='ba'): + """ + Find array of frequencies for computing the response of an analog filter. + + Parameters + ---------- + num, den : array_like, 1-D + The polynomial coefficients of the numerator and denominator of the + transfer function of the filter or LTI system, where the coefficients + are ordered from highest to lowest degree. Or, the roots of the + transfer function numerator and denominator (i.e., zeroes and poles). + N : int + The length of the array to be computed. + kind : str {'ba', 'zp'}, optional + Specifies whether the numerator and denominator are specified by their + polynomial coefficients ('ba'), or their roots ('zp'). + + Returns + ------- + w : (N,) ndarray + A 1-D array of frequencies, logarithmically spaced. + + Examples + -------- + Find a set of nine frequencies that span the "interesting part" of the + frequency response for the filter with the transfer function + + H(s) = s / (s^2 + 8s + 25) + + >>> from scipy import signal + >>> signal.findfreqs([1, 0], [1, 8, 25], N=9) + array([ 1.00000000e-02, 3.16227766e-02, 1.00000000e-01, + 3.16227766e-01, 1.00000000e+00, 3.16227766e+00, + 1.00000000e+01, 3.16227766e+01, 1.00000000e+02]) + """ + xp = array_namespace(num, den) + num, den = map(xp.asarray, (num, den)) + + if kind == 'ba': + ep = xpx.atleast_nd(_pu.polyroots(den, xp=xp), ndim=1, xp=xp) + tz = xpx.atleast_nd(_pu.polyroots(num, xp=xp), ndim=1, xp=xp) + elif kind == 'zp': + ep = xpx.atleast_nd(den, ndim=1, xp=xp) + tz = xpx.atleast_nd(num, ndim=1, xp=xp) + else: + raise ValueError("input must be one of {'ba', 'zp'}") + + ep = xp_float_to_complex(ep, xp=xp) + tz = xp_float_to_complex(tz, xp=xp) + + if ep.shape[0] == 0: + ep = xp.asarray([-1000], dtype=ep.dtype) + + ez = xp.concat(( + ep[xp.imag(ep) >= 0], + tz[(xp.abs(tz) < 1e5) & (xp.imag(tz) >= 0)] + )) + + integ = xp.astype(xp.abs(ez) < 1e-10, ez.dtype) # XXX True->1, False->0 + hfreq = xp.round( + xp.log10(xp.max(3*xp.abs(xp.real(ez) + integ) + 1.5*xp.imag(ez))) + 0.5 + ) + + # the fudge factor is for backwards compatibility: round(-1.5) can be -1 or -2 + # depending on the floating-point jitter in -1.5 + fudge = 1e-14 if is_jax(xp) else 0 + lfreq = xp.round( + xp.log10(0.1*xp.min(xp.abs(xp.real(ez + integ)) + 2*xp.imag(ez))) - 0.5 - fudge + ) + + w = _logspace(lfreq, hfreq, N, xp=xp) + return w + + +def freqs(b, a, worN=200, plot=None): + """ + Compute frequency response of analog filter. + + Given the M-order numerator `b` and N-order denominator `a` of an analog + filter, compute its frequency response:: + + b[0]*(jw)**M + b[1]*(jw)**(M-1) + ... + b[M] + H(w) = ---------------------------------------------- + a[0]*(jw)**N + a[1]*(jw)**(N-1) + ... + a[N] + + Parameters + ---------- + b : array_like + Numerator of a linear filter. + a : array_like + Denominator of a linear filter. + worN : {None, int, array_like}, optional + If None, then compute at 200 frequencies around the interesting parts + of the response curve (determined by pole-zero locations). If a single + integer, then compute at that many frequencies. Otherwise, compute the + response at the angular frequencies (e.g., rad/s) given in `worN`. + plot : callable, optional + A callable that takes two arguments. If given, the return parameters + `w` and `h` are passed to plot. Useful for plotting the frequency + response inside `freqs`. + + Returns + ------- + w : ndarray + The angular frequencies at which `h` was computed. + h : ndarray + The frequency response. + + See Also + -------- + freqz : Compute the frequency response of a digital filter. + + Notes + ----- + Using Matplotlib's "plot" function as the callable for `plot` produces + unexpected results, this plots the real part of the complex transfer + function, not the magnitude. Try ``lambda w, h: plot(w, abs(h))``. + + Examples + -------- + >>> from scipy.signal import freqs, iirfilter + >>> import numpy as np + + >>> b, a = iirfilter(4, [1, 10], 1, 60, analog=True, ftype='cheby1') + + >>> w, h = freqs(b, a, worN=np.logspace(-1, 2, 1000)) + + >>> import matplotlib.pyplot as plt + >>> plt.semilogx(w, 20 * np.log10(abs(h))) + >>> plt.xlabel('Frequency [rad/s]') + >>> plt.ylabel('Amplitude response [dB]') + >>> plt.grid(True) + >>> plt.show() + + """ + xp = array_namespace(b, a, worN) + + if worN is None: + # For backwards compatibility + w = findfreqs(b, a, 200) + elif _is_int_type(worN): + w = findfreqs(b, a, worN) + else: + w = xpx.atleast_nd(xp.asarray(worN), ndim=1, xp=xp) + + s = 1j * w + h = _pu.polyval(b, s, xp=xp) / _pu.polyval(a, s, xp=xp) + if plot is not None: + plot(w, h) + + return w, h + + +def freqs_zpk(z, p, k, worN=200): + """ + Compute frequency response of analog filter. + + Given the zeros `z`, poles `p`, and gain `k` of a filter, compute its + frequency response:: + + (jw-z[0]) * (jw-z[1]) * ... * (jw-z[-1]) + H(w) = k * ---------------------------------------- + (jw-p[0]) * (jw-p[1]) * ... * (jw-p[-1]) + + Parameters + ---------- + z : array_like + Zeroes of a linear filter + p : array_like + Poles of a linear filter + k : scalar + Gain of a linear filter + worN : {None, int, array_like}, optional + If None, then compute at 200 frequencies around the interesting parts + of the response curve (determined by pole-zero locations). If a single + integer, then compute at that many frequencies. Otherwise, compute the + response at the angular frequencies (e.g., rad/s) given in `worN`. + + Returns + ------- + w : ndarray + The angular frequencies at which `h` was computed. + h : ndarray + The frequency response. + + See Also + -------- + freqs : Compute the frequency response of an analog filter in TF form + freqz : Compute the frequency response of a digital filter in TF form + freqz_zpk : Compute the frequency response of a digital filter in ZPK form + + Notes + ----- + .. versionadded:: 0.19.0 + + Examples + -------- + >>> import numpy as np + >>> from scipy.signal import freqs_zpk, iirfilter + + >>> z, p, k = iirfilter(4, [1, 10], 1, 60, analog=True, ftype='cheby1', + ... output='zpk') + + >>> w, h = freqs_zpk(z, p, k, worN=np.logspace(-1, 2, 1000)) + + >>> import matplotlib.pyplot as plt + >>> plt.semilogx(w, 20 * np.log10(abs(h))) + >>> plt.xlabel('Frequency [rad/s]') + >>> plt.ylabel('Amplitude response [dB]') + >>> plt.grid(True) + >>> plt.show() + + """ + xp = array_namespace(z, p) + z, p = map(xp.asarray, (z, p)) + + # NB: k is documented to be a scalar; for backwards compat we keep allowing it + # to be a size-1 array, but it does not influence the namespace calculation. + k = xp.asarray(k, dtype=xp_default_dtype(xp)) + if xp_size(k) > 1: + raise ValueError('k must be a single scalar gain') + + if worN is None: + # For backwards compatibility + w = findfreqs(z, p, 200, kind='zp') + elif _is_int_type(worN): + w = findfreqs(z, p, worN, kind='zp') + else: + w = worN + + w = xpx.atleast_nd(xp.asarray(w), ndim=1, xp=xp) + s = 1j * w + num = _pu.npp_polyvalfromroots(s, z, xp=xp) + den = _pu.npp_polyvalfromroots(s, p, xp=xp) + h = k * num/den + return w, h + + +def freqz(b, a=1, worN=512, whole=False, plot=None, fs=2*pi, + include_nyquist=False): + """ + Compute the frequency response of a digital filter. + + Given the M-order numerator `b` and N-order denominator `a` of a digital + filter, compute its frequency response:: + + jw -jw -jwM + jw B(e ) b[0] + b[1]e + ... + b[M]e + H(e ) = ------ = ----------------------------------- + jw -jw -jwN + A(e ) a[0] + a[1]e + ... + a[N]e + + Parameters + ---------- + b : array_like + Numerator of a linear filter. If `b` has dimension greater than 1, + it is assumed that the coefficients are stored in the first dimension, + and ``b.shape[1:]``, ``a.shape[1:]``, and the shape of the frequencies + array must be compatible for broadcasting. + a : array_like + Denominator of a linear filter. If `b` has dimension greater than 1, + it is assumed that the coefficients are stored in the first dimension, + and ``b.shape[1:]``, ``a.shape[1:]``, and the shape of the frequencies + array must be compatible for broadcasting. + worN : {None, int, array_like}, optional + If a single integer, then compute at that many frequencies (default is + N=512). This is a convenient alternative to:: + + np.linspace(0, fs if whole else fs/2, N, endpoint=include_nyquist) + + Using a number that is fast for FFT computations can result in + faster computations (see Notes). + + If an array_like, compute the response at the frequencies given. + These are in the same units as `fs`. + whole : bool, optional + Normally, frequencies are computed from 0 to the Nyquist frequency, + fs/2 (upper-half of unit-circle). If `whole` is True, compute + frequencies from 0 to fs. Ignored if worN is array_like. + plot : callable + A callable that takes two arguments. If given, the return parameters + `w` and `h` are passed to plot. Useful for plotting the frequency + response inside `freqz`. + fs : float, optional + The sampling frequency of the digital system. Defaults to 2*pi + radians/sample (so w is from 0 to pi). + + .. versionadded:: 1.2.0 + include_nyquist : bool, optional + If `whole` is False and `worN` is an integer, setting `include_nyquist` + to True will include the last frequency (Nyquist frequency) and is + otherwise ignored. + + .. versionadded:: 1.5.0 + + Returns + ------- + w : ndarray + The frequencies at which `h` was computed, in the same units as `fs`. + By default, `w` is normalized to the range [0, pi) (radians/sample). + h : ndarray + The frequency response, as complex numbers. + + See Also + -------- + freqz_zpk + freqz_sos + + Notes + ----- + Using Matplotlib's :func:`matplotlib.pyplot.plot` function as the callable + for `plot` produces unexpected results, as this plots the real part of the + complex transfer function, not the magnitude. + Try ``lambda w, h: plot(w, np.abs(h))``. + + A direct computation via (R)FFT is used to compute the frequency response + when the following conditions are met: + + 1. An integer value is given for `worN`. + 2. `worN` is fast to compute via FFT (i.e., + `next_fast_len(worN) ` equals `worN`). + 3. The denominator coefficients are a single value (``a.shape[0] == 1``). + 4. `worN` is at least as long as the numerator coefficients + (``worN >= b.shape[0]``). + 5. If ``b.ndim > 1``, then ``b.shape[-1] == 1``. + + For long FIR filters, the FFT approach can have lower error and be much + faster than the equivalent direct polynomial calculation. + + Examples + -------- + >>> from scipy import signal + >>> import numpy as np + >>> taps, f_c = 80, 1.0 # number of taps and cut-off frequency + >>> b = signal.firwin(taps, f_c, window=('kaiser', 8), fs=2*np.pi) + >>> w, h = signal.freqz(b) + + >>> import matplotlib.pyplot as plt + >>> fig, ax1 = plt.subplots(tight_layout=True) + >>> ax1.set_title(f"Frequency Response of {taps} tap FIR Filter" + + ... f"($f_c={f_c}$ rad/sample)") + >>> ax1.axvline(f_c, color='black', linestyle=':', linewidth=0.8) + >>> ax1.plot(w, 20 * np.log10(abs(h)), 'C0') + >>> ax1.set_ylabel("Amplitude in dB", color='C0') + >>> ax1.set(xlabel="Frequency in rad/sample", xlim=(0, np.pi)) + + >>> ax2 = ax1.twinx() + >>> phase = np.unwrap(np.angle(h)) + >>> ax2.plot(w, phase, 'C1') + >>> ax2.set_ylabel('Phase [rad]', color='C1') + >>> ax2.grid(True) + >>> ax2.axis('tight') + >>> plt.show() + + Broadcasting Examples + + Suppose we have two FIR filters whose coefficients are stored in the + rows of an array with shape (2, 25). For this demonstration, we'll + use random data: + + >>> rng = np.random.default_rng() + >>> b = rng.random((2, 25)) + + To compute the frequency response for these two filters with one call + to `freqz`, we must pass in ``b.T``, because `freqz` expects the first + axis to hold the coefficients. We must then extend the shape with a + trivial dimension of length 1 to allow broadcasting with the array + of frequencies. That is, we pass in ``b.T[..., np.newaxis]``, which has + shape (25, 2, 1): + + >>> w, h = signal.freqz(b.T[..., np.newaxis], worN=1024) + >>> w.shape + (1024,) + >>> h.shape + (2, 1024) + + Now, suppose we have two transfer functions, with the same numerator + coefficients ``b = [0.5, 0.5]``. The coefficients for the two denominators + are stored in the first dimension of the 2-D array `a`:: + + a = [ 1 1 ] + [ -0.25, -0.5 ] + + >>> b = np.array([0.5, 0.5]) + >>> a = np.array([[1, 1], [-0.25, -0.5]]) + + Only `a` is more than 1-D. To make it compatible for + broadcasting with the frequencies, we extend it with a trivial dimension + in the call to `freqz`: + + >>> w, h = signal.freqz(b, a[..., np.newaxis], worN=1024) + >>> w.shape + (1024,) + >>> h.shape + (2, 1024) + + """ + xp = array_namespace(b, a) + + b, a = map(xp.asarray, (b, a)) + if xp.isdtype(a.dtype, 'integral'): + a = xp.astype(a, xp_default_dtype(xp)) + res_dtype = xp.result_type(b, a) + real_dtype = _real_dtype_for_complex(res_dtype, xp=xp) + + b = xpx.atleast_nd(b, ndim=1, xp=xp) + a = xpx.atleast_nd(a, ndim=1, xp=xp) + + fs = _validate_fs(fs, allow_none=False) + + if worN is None: + # For backwards compatibility + worN = 512 + + h = None + + if _is_int_type(worN): + N = operator.index(worN) + del worN + if N < 0: + raise ValueError(f'worN must be nonnegative, got {N}') + lastpoint = 2 * pi if whole else pi + # if include_nyquist is true and whole is false, w should + # include end point + w = xp.linspace(0, lastpoint, N, + endpoint=include_nyquist and not whole, dtype=real_dtype) + n_fft = N if whole else 2 * (N - 1) if include_nyquist else 2 * N + if (xp_size(a) == 1 and (b.ndim == 1 or (b.shape[-1] == 1)) + and n_fft >= b.shape[0] + and n_fft > 0): # TODO: review threshold acc. to benchmark? + + if (xp.isdtype(b.dtype, "real floating") and + xp.isdtype(a.dtype, "real floating") + ): + fft_func = sp_fft.rfft + else: + fft_func = sp_fft.fft + + h = fft_func(b, n=n_fft, axis=0) + h = h[:min(N, h.shape[0]), ...] + h /= a + + if fft_func is sp_fft.rfft and whole: + # exclude DC and maybe Nyquist (no need to use axis_reverse + # here because we can build reversal with the truncation) + stop = None if n_fft % 2 == 1 else -1 + h_flipped = xp.flip(h[1:stop, ...], axis=0) + h = xp.concat((h, xp.conj(h_flipped))) + if b.ndim > 1: + # Last axis of h has length 1, so drop it. + h = h[..., 0] + # Move the first axis of h to the end. + h = xp.moveaxis(h, 0, -1) + else: + if isinstance(worN, complex): + # backwards compat + worN = worN.real + w = xpx.atleast_nd(xp.asarray(worN, dtype=res_dtype), ndim=1, xp=xp) + if xp.isdtype(w.dtype, 'integral'): + w = xp.astype(w, xp_default_dtype(xp)) + del worN + w = 2 * pi * w / fs + + if h is None: # still need to compute using freqs w + zm1 = xp.exp(-1j * w) + h = (_pu.npp_polyval(zm1, b, tensor=False, xp=xp) / + _pu.npp_polyval(zm1, a, tensor=False, xp=xp)) + + w = w * (fs / (2 * pi)) + + if plot is not None: + plot(w, h) + + return w, h + + +def freqz_zpk(z, p, k, worN=512, whole=False, fs=2*pi): + r""" + Compute the frequency response of a digital filter in ZPK form. + + Given the Zeros, Poles and Gain of a digital filter, compute its frequency + response: + + :math:`H(z)=k \prod_i (z - Z[i]) / \prod_j (z - P[j])` + + where :math:`k` is the `gain`, :math:`Z` are the `zeros` and :math:`P` are + the `poles`. + + Parameters + ---------- + z : array_like + Zeroes of a linear filter + p : array_like + Poles of a linear filter + k : scalar + Gain of a linear filter + worN : {None, int, array_like}, optional + If a single integer, then compute at that many frequencies (default is + N=512). + + If an array_like, compute the response at the frequencies given. + These are in the same units as `fs`. + whole : bool, optional + Normally, frequencies are computed from 0 to the Nyquist frequency, + fs/2 (upper-half of unit-circle). If `whole` is True, compute + frequencies from 0 to fs. Ignored if w is array_like. + fs : float, optional + The sampling frequency of the digital system. Defaults to 2*pi + radians/sample (so w is from 0 to pi). + + .. versionadded:: 1.2.0 + + Returns + ------- + w : ndarray + The frequencies at which `h` was computed, in the same units as `fs`. + By default, `w` is normalized to the range [0, pi) (radians/sample). + h : ndarray + The frequency response, as complex numbers. + + See Also + -------- + freqs : Compute the frequency response of an analog filter in TF form + freqs_zpk : Compute the frequency response of an analog filter in ZPK form + freqz : Compute the frequency response of a digital filter in TF form + + Notes + ----- + .. versionadded:: 0.19.0 + + Examples + -------- + Design a 4th-order digital Butterworth filter with cut-off of 100 Hz in a + system with sample rate of 1000 Hz, and plot the frequency response: + + >>> import numpy as np + >>> from scipy import signal + >>> z, p, k = signal.butter(4, 100, output='zpk', fs=1000) + >>> w, h = signal.freqz_zpk(z, p, k, fs=1000) + + >>> import matplotlib.pyplot as plt + >>> fig = plt.figure() + >>> ax1 = fig.add_subplot(1, 1, 1) + >>> ax1.set_title('Digital filter frequency response') + + >>> ax1.plot(w, 20 * np.log10(abs(h)), 'b') + >>> ax1.set_ylabel('Amplitude [dB]', color='b') + >>> ax1.set_xlabel('Frequency [Hz]') + >>> ax1.grid(True) + + >>> ax2 = ax1.twinx() + >>> phase = np.unwrap(np.angle(h)) + >>> ax2.plot(w, phase, 'g') + >>> ax2.set_ylabel('Phase [rad]', color='g') + + >>> plt.axis('tight') + >>> plt.show() + + """ + xp = array_namespace(z, p) + z, p = map(xp.asarray, (z, p)) + + z = xpx.atleast_nd(z, ndim=1, xp=xp) + p = xpx.atleast_nd(p, ndim=1, xp=xp) + res_dtype = xp.result_type(z, p) + res_dtype = xp.float64 if res_dtype in (xp.float64, xp.complex128) else xp.float32 + + fs = _validate_fs(fs, allow_none=False) + + if whole: + lastpoint = 2 * pi + else: + lastpoint = pi + + if worN is None: + # For backwards compatibility + w = xp.linspace(0, lastpoint, 512, endpoint=False, dtype=res_dtype) + elif _is_int_type(worN): + w = xp.linspace(0, lastpoint, worN, endpoint=False, dtype=res_dtype) + else: + w = xp.asarray(worN) + if xp.isdtype(w.dtype, 'integral'): + w = xp.astype(w, xp_default_dtype(xp)) + w = xpx.atleast_nd(w, ndim=1, xp=xp) + w = 2 * pi * w / fs + + zm1 = xp.exp(1j * w) + func = _pu.npp_polyvalfromroots + h = xp.asarray(k, dtype=res_dtype) * func(zm1, z, xp=xp) / func(zm1, p, xp=xp) + + w = w*(fs/(2*pi)) + + return w, h + + +def group_delay(system, w=512, whole=False, fs=2*pi): + r"""Compute the group delay of a digital filter. + + The group delay measures by how many samples amplitude envelopes of + various spectral components of a signal are delayed by a filter. + It is formally defined as the derivative of continuous (unwrapped) phase:: + + d jw + D(w) = - -- arg H(e) + dw + + Parameters + ---------- + system : tuple of array_like (b, a) + Numerator and denominator coefficients of a filter transfer function. + w : {None, int, array_like}, optional + If a single integer, then compute at that many frequencies (default is + N=512). + + If an array_like, compute the delay at the frequencies given. These + are in the same units as `fs`. + whole : bool, optional + Normally, frequencies are computed from 0 to the Nyquist frequency, + fs/2 (upper-half of unit-circle). If `whole` is True, compute + frequencies from 0 to fs. Ignored if w is array_like. + fs : float, optional + The sampling frequency of the digital system. Defaults to 2*pi + radians/sample (so w is from 0 to pi). + + .. versionadded:: 1.2.0 + + Returns + ------- + w : ndarray + The frequencies at which group delay was computed, in the same units + as `fs`. By default, `w` is normalized to the range [0, pi) + (radians/sample). + gd : ndarray + The group delay. + + See Also + -------- + freqz : Frequency response of a digital filter + + Notes + ----- + The similar function in MATLAB is called `grpdelay`. + + If the transfer function :math:`H(z)` has zeros or poles on the unit + circle, the group delay at corresponding frequencies is undefined. + When such a case arises the warning is raised and the group delay + is set to 0 at those frequencies. + + For the details of numerical computation of the group delay refer to [1]_ or [2]_. + + .. versionadded:: 0.16.0 + + References + ---------- + .. [1] Richard G. Lyons, "Understanding Digital Signal Processing, + 3rd edition", p. 830. + .. [2] Julius O. Smith III, "Numerical Computation of Group Delay", + in "Introduction to Digital Filters with Audio Applications", + online book, 2007, + https://ccrma.stanford.edu/~jos/fp/Numerical_Computation_Group_Delay.html + + Examples + -------- + >>> from scipy import signal + >>> b, a = signal.iirdesign(0.1, 0.3, 5, 50, ftype='cheby1') + >>> w, gd = signal.group_delay((b, a)) + + >>> import matplotlib.pyplot as plt + >>> plt.title('Digital filter group delay') + >>> plt.plot(w, gd) + >>> plt.ylabel('Group delay [samples]') + >>> plt.xlabel('Frequency [rad/sample]') + >>> plt.show() + + """ + xp = array_namespace(*system, w) + b, a = map(np.atleast_1d, system) + + if w is None: + # For backwards compatibility + w = 512 + + fs = _validate_fs(fs, allow_none=False) + + if _is_int_type(w): + if whole: + w = np.linspace(0, 2 * pi, w, endpoint=False) + else: + w = np.linspace(0, pi, w, endpoint=False) + else: + w = np.atleast_1d(w) + w = 2*pi*w/fs + + c = np.convolve(b, np.conjugate(a[::-1])) + cr = c * np.arange(c.size) + z = np.exp(-1j * w) + num = np.polyval(cr[::-1], z) + den = np.polyval(c[::-1], z) + gd = np.real(num / den) - a.size + 1 + singular = ~np.isfinite(gd) + near_singular = np.absolute(den) < 10 * EPSILON + + if np.any(singular): + gd[singular] = 0 + warnings.warn( + "The group delay is singular at frequencies " + f"[{', '.join(f'{ws:.3f}' for ws in w[singular])}], setting to 0", + stacklevel=2 + ) + + elif np.any(near_singular): + warnings.warn( + "The filter's denominator is extremely small at frequencies " + f"[{', '.join(f'{ws:.3f}' for ws in w[near_singular])}], " + "around which a singularity may be present", + stacklevel=2 + ) + + w = w * (fs / (2 * xp.pi)) + + return xp.asarray(w), xp.asarray(gd) + + +def _validate_sos(sos, xp=None): + """Helper to validate a SOS input""" + if xp is None: + xp = np # backcompat, cf sosfilt, sosfiltfilt + + sos = xp.asarray(sos) + sos = xpx.atleast_nd(sos, ndim=2, xp=xp) + if sos.ndim != 2: + raise ValueError('sos array must be 2D') + n_sections, m = sos.shape + if m != 6: + raise ValueError('sos array must be shape (n_sections, 6)') + if not xp.all(sos[:, 3] == 1): + raise ValueError('sos[:, 3] should be all ones') + return sos, n_sections + + +def freqz_sos(sos, worN=512, whole=False, fs=2*pi): + r""" + Compute the frequency response of a digital filter in SOS format. + + Given `sos`, an array with shape (n, 6) of second order sections of + a digital filter, compute the frequency response of the system function:: + + B0(z) B1(z) B{n-1}(z) + H(z) = ----- * ----- * ... * --------- + A0(z) A1(z) A{n-1}(z) + + for z = exp(omega*1j), where B{k}(z) and A{k}(z) are numerator and + denominator of the transfer function of the k-th second order section. + + Parameters + ---------- + sos : array_like + Array of second-order filter coefficients, must have shape + ``(n_sections, 6)``. Each row corresponds to a second-order + section, with the first three columns providing the numerator + coefficients and the last three providing the denominator + coefficients. + worN : {None, int, array_like}, optional + If a single integer, then compute at that many frequencies (default is + N=512). Using a number that is fast for FFT computations can result + in faster computations (see Notes of `freqz`). + + If an array_like, compute the response at the frequencies given (must + be 1-D). These are in the same units as `fs`. + whole : bool, optional + Normally, frequencies are computed from 0 to the Nyquist frequency, + fs/2 (upper-half of unit-circle). If `whole` is True, compute + frequencies from 0 to fs. + fs : float, optional + The sampling frequency of the digital system. Defaults to 2*pi + radians/sample (so w is from 0 to pi). + + .. versionadded:: 1.2.0 + + Returns + ------- + w : ndarray + The frequencies at which `h` was computed, in the same units as `fs`. + By default, `w` is normalized to the range [0, pi) (radians/sample). + h : ndarray + The frequency response, as complex numbers. + + See Also + -------- + freqz, sosfilt, sosfreqz + + Notes + ----- + This function used to be called ``sosfreqz`` in older versions (≥ 0.19.0) + + .. versionadded:: 1.15.0 + + Examples + -------- + Design a 15th-order bandpass filter in SOS format. + + >>> from scipy import signal + >>> import numpy as np + >>> sos = signal.ellip(15, 0.5, 60, (0.2, 0.4), btype='bandpass', + ... output='sos') + + Compute the frequency response at 1500 points from DC to Nyquist. + + >>> w, h = signal.freqz_sos(sos, worN=1500) + + Plot the response. + + >>> import matplotlib.pyplot as plt + >>> plt.subplot(2, 1, 1) + >>> db = 20*np.log10(np.maximum(np.abs(h), 1e-5)) + >>> plt.plot(w/np.pi, db) + >>> plt.ylim(-75, 5) + >>> plt.grid(True) + >>> plt.yticks([0, -20, -40, -60]) + >>> plt.ylabel('Gain [dB]') + >>> plt.title('Frequency Response') + >>> plt.subplot(2, 1, 2) + >>> plt.plot(w/np.pi, np.angle(h)) + >>> plt.grid(True) + >>> plt.yticks([-np.pi, -0.5*np.pi, 0, 0.5*np.pi, np.pi], + ... [r'$-\pi$', r'$-\pi/2$', '0', r'$\pi/2$', r'$\pi$']) + >>> plt.ylabel('Phase [rad]') + >>> plt.xlabel('Normalized frequency (1.0 = Nyquist)') + >>> plt.show() + + If the same filter is implemented as a single transfer function, + numerical error corrupts the frequency response: + + >>> b, a = signal.ellip(15, 0.5, 60, (0.2, 0.4), btype='bandpass', + ... output='ba') + >>> w, h = signal.freqz(b, a, worN=1500) + >>> plt.subplot(2, 1, 1) + >>> db = 20*np.log10(np.maximum(np.abs(h), 1e-5)) + >>> plt.plot(w/np.pi, db) + >>> plt.ylim(-75, 5) + >>> plt.grid(True) + >>> plt.yticks([0, -20, -40, -60]) + >>> plt.ylabel('Gain [dB]') + >>> plt.title('Frequency Response') + >>> plt.subplot(2, 1, 2) + >>> plt.plot(w/np.pi, np.angle(h)) + >>> plt.grid(True) + >>> plt.yticks([-np.pi, -0.5*np.pi, 0, 0.5*np.pi, np.pi], + ... [r'$-\pi$', r'$-\pi/2$', '0', r'$\pi/2$', r'$\pi$']) + >>> plt.ylabel('Phase [rad]') + >>> plt.xlabel('Normalized frequency (1.0 = Nyquist)') + >>> plt.show() + + """ + xp = array_namespace(sos) + + fs = _validate_fs(fs, allow_none=False) + + sos, n_sections = _validate_sos(sos, xp) + if n_sections == 0: + raise ValueError('Cannot compute frequencies with no sections') + h = 1. + for j in range(sos.shape[0]): + row = sos[j, :] + w, rowh = freqz(row[:3], row[3:], worN=worN, whole=whole, fs=fs) + h *= rowh + return w, h + + +def sosfreqz(*args, **kwargs): + """ + Compute the frequency response of a digital filter in SOS format (legacy). + + .. legacy:: function + + This function is an alias, provided for backward compatibility. + New code should use the function :func:`scipy.signal.freqz_sos`. + This function became obsolete from version 1.15.0. + + """ # numpydoc ignore=RT01 + return freqz_sos(*args, **kwargs) + + +def _cplxreal(z, tol=None): + """ + Split into complex and real parts, combining conjugate pairs. + + The 1-D input vector `z` is split up into its complex (`zc`) and real (`zr`) + elements. Every complex element must be part of a complex-conjugate pair, + which are combined into a single number (with positive imaginary part) in + the output. Two complex numbers are considered a conjugate pair if their + real and imaginary parts differ in magnitude by less than ``tol * abs(z)``. + + Parameters + ---------- + z : array_like + Vector of complex numbers to be sorted and split + tol : float, optional + Relative tolerance for testing realness and conjugate equality. + Default is ``100 * spacing(1)`` of `z`'s data type (i.e., 2e-14 for + float64) + + Returns + ------- + zc : ndarray + Complex elements of `z`, with each pair represented by a single value + having positive imaginary part, sorted first by real part, and then + by magnitude of imaginary part. The pairs are averaged when combined + to reduce error. + zr : ndarray + Real elements of `z` (those having imaginary part less than + `tol` times their magnitude), sorted by value. + + Raises + ------ + ValueError + If there are any complex numbers in `z` for which a conjugate + cannot be found. + + See Also + -------- + _cplxpair + + Examples + -------- + >>> from scipy.signal._filter_design import _cplxreal + >>> a = [4, 3, 1, 2-2j, 2+2j, 2-1j, 2+1j, 2-1j, 2+1j, 1+1j, 1-1j] + >>> zc, zr = _cplxreal(a) + >>> print(zc) + [ 1.+1.j 2.+1.j 2.+1.j 2.+2.j] + >>> print(zr) + [ 1. 3. 4.] + """ + + z = np.atleast_1d(z) + if z.size == 0: + return z, z + elif z.ndim != 1: + raise ValueError('_cplxreal only accepts 1-D input') + + if tol is None: + # Get tolerance from dtype of input + tol = 100 * np.finfo((1.0 * z).dtype).eps + + # Sort by real part, magnitude of imaginary part (speed up further sorting) + z = z[np.lexsort((abs(z.imag), z.real))] + + # Split reals from conjugate pairs + real_indices = abs(z.imag) <= tol * abs(z) + zr = z[real_indices].real + + if len(zr) == len(z): + # Input is entirely real + return np.array([]), zr + + # Split positive and negative halves of conjugates + z = z[~real_indices] + zp = z[z.imag > 0] + zn = z[z.imag < 0] + + if len(zp) != len(zn): + raise ValueError('Array contains complex value with no matching ' + 'conjugate.') + + # Find runs of (approximately) the same real part + same_real = np.diff(zp.real) <= tol * abs(zp[:-1]) + diffs = np.diff(np.concatenate(([0], same_real, [0]))) + run_starts = np.nonzero(diffs > 0)[0] + run_stops = np.nonzero(diffs < 0)[0] + + # Sort each run by their imaginary parts + for i in range(len(run_starts)): + start = run_starts[i] + stop = run_stops[i] + 1 + for chunk in (zp[start:stop], zn[start:stop]): + chunk[...] = chunk[np.lexsort([abs(chunk.imag)])] + + # Check that negatives match positives + if any(abs(zp - zn.conj()) > tol * abs(zn)): + raise ValueError('Array contains complex value with no matching ' + 'conjugate.') + + # Average out numerical inaccuracy in real vs imag parts of pairs + zc = (zp + zn.conj()) / 2 + + return zc, zr + + +def _cplxpair(z, tol=None): + """ + Sort into pairs of complex conjugates. + + Complex conjugates in `z` are sorted by increasing real part. In each + pair, the number with negative imaginary part appears first. + + If pairs have identical real parts, they are sorted by increasing + imaginary magnitude. + + Two complex numbers are considered a conjugate pair if their real and + imaginary parts differ in magnitude by less than ``tol * abs(z)``. The + pairs are forced to be exact complex conjugates by averaging the positive + and negative values. + + Purely real numbers are also sorted, but placed after the complex + conjugate pairs. A number is considered real if its imaginary part is + smaller than `tol` times the magnitude of the number. + + Parameters + ---------- + z : array_like + 1-D input array to be sorted. + tol : float, optional + Relative tolerance for testing realness and conjugate equality. + Default is ``100 * spacing(1)`` of `z`'s data type (i.e., 2e-14 for + float64) + + Returns + ------- + y : ndarray + Complex conjugate pairs followed by real numbers. + + Raises + ------ + ValueError + If there are any complex numbers in `z` for which a conjugate + cannot be found. + + See Also + -------- + _cplxreal + + Examples + -------- + >>> from scipy.signal._filter_design import _cplxpair + >>> a = [4, 3, 1, 2-2j, 2+2j, 2-1j, 2+1j, 2-1j, 2+1j, 1+1j, 1-1j] + >>> z = _cplxpair(a) + >>> print(z) + [ 1.-1.j 1.+1.j 2.-1.j 2.+1.j 2.-1.j 2.+1.j 2.-2.j 2.+2.j 1.+0.j + 3.+0.j 4.+0.j] + """ + + z = np.atleast_1d(z) + if z.size == 0 or np.isrealobj(z): + return np.sort(z) + + if z.ndim != 1: + raise ValueError('z must be 1-D') + + zc, zr = _cplxreal(z, tol) + + # Interleave complex values and their conjugates, with negative imaginary + # parts first in each pair + zc = np.dstack((zc.conj(), zc)).flatten() + z = np.append(zc, zr) + return z + + +def tf2zpk(b, a): + r"""Return zero, pole, gain (z, p, k) representation from a numerator, + denominator representation of a linear filter. + + Parameters + ---------- + b : array_like, shape (M,) + Numerator polynomial coefficients. Must be 1-D. + a : array_like, shape (N,) + Denominator polynomial coefficients. Must be 1-D. + + Returns + ------- + z : ndarray + Zeros of the transfer function. + p : ndarray + Poles of the transfer function. + k : float + System gain. + + Notes + ----- + If some values of `b` are too close to 0, they are removed. In that case, + a BadCoefficients warning is emitted. + + The `b` and `a` arrays are interpreted as coefficients for positive, + descending powers of the transfer function variable. So the inputs + :math:`b = [b_0, b_1, ..., b_M]` and :math:`a =[a_0, a_1, ..., a_N]` + can represent an analog filter of the form: + + .. math:: + + H(s) = \frac + {b_0 s^M + b_1 s^{(M-1)} + \cdots + b_M} + {a_0 s^N + a_1 s^{(N-1)} + \cdots + a_N} + + or a discrete-time filter of the form: + + .. math:: + + H(z) = \frac + {b_0 z^M + b_1 z^{(M-1)} + \cdots + b_M} + {a_0 z^N + a_1 z^{(N-1)} + \cdots + a_N} + + This "positive powers" form is found more commonly in controls + engineering. If `M` and `N` are equal (which is true for all filters + generated by the bilinear transform), then this happens to be equivalent + to the "negative powers" discrete-time form preferred in DSP: + + .. math:: + + H(z) = \frac + {b_0 + b_1 z^{-1} + \cdots + b_M z^{-M}} + {a_0 + a_1 z^{-1} + \cdots + a_N z^{-N}} + + Although this is true for common filters, remember that this is not true + in the general case. If `M` and `N` are not equal, the discrete-time + transfer function coefficients must first be converted to the "positive + powers" form before finding the poles and zeros. + + Examples + -------- + Find the zeroes, poles and gain of + a filter with the transfer function + + .. math:: + + H(s) = \frac{3s^2}{s^2 + 5s + 13} + + >>> from scipy.signal import tf2zpk + >>> tf2zpk([3, 0, 0], [1, 5, 13]) + ( array([ 0. , 0. ]), + array([ -2.5+2.59807621j , -2.5-2.59807621j]), + 3.0) + """ + xp = array_namespace(b, a) + b, a = normalize(b, a) + + a, b = xp_promote(a, b, xp=xp, force_floating=True) + + k = b[0] + b = b / b[0] + z = _pu.polyroots(b, xp=xp) + p = _pu.polyroots(a, xp=xp) + return z, p, k + + +def zpk2tf(z, p, k): + r""" + Return polynomial transfer function representation from zeros and poles. + + Parameters + ---------- + z : array_like + Zeros of the transfer function. + p : array_like + Poles of the transfer function. + k : float + System gain. + + Returns + ------- + b : ndarray + Numerator polynomial coefficients. + a : ndarray + Denominator polynomial coefficients. + + Examples + -------- + Find the polynomial representation of a transfer function H(s) + using its 'zpk' (Zero-Pole-Gain) representation. + + .. math:: + + H(z) = 5 \frac + { (s - 2)(s - 6) } + { (s - 1)(s - 8) } + + >>> from scipy.signal import zpk2tf + >>> z = [2, 6] + >>> p = [1, 8] + >>> k = 5 + >>> zpk2tf(z, p, k) + ( array([ 5., -40., 60.]), array([ 1., -9., 8.])) + """ + xp = array_namespace(z, p) + z, p = map(xp.asarray, (z, p)) + k = xp.asarray(k, dtype=xp.result_type(xp.real(z), xp.real(p), k)) + if xp.isdtype(k.dtype, "integral"): + k = xp.astype(k, xp.float64) + + z = xpx.atleast_nd(z, ndim=1, xp=xp) + k = xpx.atleast_nd(k, ndim=1, xp=xp) + + if z.ndim > 1: + temp = _pu.poly(z[0, ...], xp=xp) + result_dtype = xp_result_type(temp, k, force_floating=True, xp=xp) + b = xp.empty((z.shape[0], z.shape[1] + 1), dtype=result_dtype) + if k.shape[0] == 1: + k = [k[0]] * z.shape[0] + for i in range(z.shape[0]): + k_i = xp.asarray(k[i], dtype=result_dtype) + poly_i = xp.asarray(_pu.poly(z[i, ...], xp=xp), dtype=result_dtype) + b_i = k_i * poly_i + b = xpx.at(b)[i, ...].set(b_i) + else: + # Use xp.multiply to work around torch type promotion + # non-compliance for operations between 0d and higher + # dimensional arrays. + b = xp.multiply(k, _pu.poly(z, xp=xp)) + + a = _pu.poly(p, xp=xp) + a = xpx.atleast_nd(xp.asarray(a), ndim=1, xp=xp) + + return b, a + + +def tf2sos(b, a, pairing=None, *, analog=False): + r""" + Return second-order sections from transfer function representation. + + Parameters + ---------- + b : array_like, shape (M,) + Numerator polynomial coefficients. Must be 1-D. + a : array_like, shape (N,) + Denominator polynomial coefficients. Must be 1-D. + pairing : {None, 'nearest', 'keep_odd', 'minimal'}, optional + The method to use to combine pairs of poles and zeros into sections. + See `zpk2sos` for information and restrictions on `pairing` and + `analog` arguments. + analog : bool, optional + If True, system is analog, otherwise discrete. + + .. versionadded:: 1.8.0 + + Returns + ------- + sos : ndarray + Array of second-order filter coefficients, with shape + ``(n_sections, 6)``. See `sosfilt` for the SOS filter format + specification. + + See Also + -------- + zpk2sos, sosfilt + + Notes + ----- + It is generally discouraged to convert from TF to SOS format, since doing + so usually will not improve numerical precision errors. Instead, consider + designing filters in ZPK format and converting directly to SOS. TF is + converted to SOS by first converting to ZPK format, then converting + ZPK to SOS. + + .. versionadded:: 0.16.0 + + Examples + -------- + Find the 'sos' (second-order sections) of the transfer function H(s) + using its polynomial representation. + + .. math:: + + H(s) = \frac{s^2 - 3.5s - 2}{s^4 + 3s^3 - 15s^2 - 19s + 30} + + >>> from scipy.signal import tf2sos + >>> tf2sos([1, -3.5, -2], [1, 3, -15, -19, 30], analog=True) + array([[ 0. , 0. , 1. , 1. , 2. , -15. ], + [ 1. , -3.5, -2. , 1. , 1. , -2. ]]) + """ + return zpk2sos(*tf2zpk(b, a), pairing=pairing, analog=analog) + + +def sos2tf(sos): + r""" + Return a single transfer function from a series of second-order sections. + + Parameters + ---------- + sos : array_like + Array of second-order filter coefficients, must have shape + ``(n_sections, 6)``. See `sosfilt` for the SOS filter format + specification. + + Returns + ------- + b : ndarray + Numerator polynomial coefficients. + a : ndarray + Denominator polynomial coefficients. + + Notes + ----- + .. versionadded:: 0.16.0 + + Examples + -------- + Find the polynomial representation of an elliptic filter + using its 'sos' (second-order sections) format. + + >>> from scipy.signal import sos2tf + >>> from scipy import signal + >>> sos = signal.ellip(1, 0.001, 50, 0.1, output='sos') + >>> sos2tf(sos) + ( array([0.91256522, 0.91256522, 0. ]), + array([1. , 0.82513043, 0. ])) + """ + xp = array_namespace(sos) + sos = xp.asarray(sos) + + result_type = sos.dtype + if xp.isdtype(result_type, 'integral'): + result_type = xp_default_dtype(xp) + + b = xp.asarray([1], dtype=result_type) + a = xp.asarray([1], dtype=result_type) + + n_sections = sos.shape[0] + for section in range(n_sections): + b = _pu.polymul(b, sos[section, :3], xp=xp) + a = _pu.polymul(a, sos[section, 3:], xp=xp) + return b, a + + +def sos2zpk(sos): + """ + Return zeros, poles, and gain of a series of second-order sections. + + Parameters + ---------- + sos : array_like + Array of second-order filter coefficients, must have shape + ``(n_sections, 6)``. See `sosfilt` for the SOS filter format + specification. + + Returns + ------- + z : ndarray + Zeros of the transfer function. + p : ndarray + Poles of the transfer function. + k : float + System gain. + + Notes + ----- + The number of zeros and poles returned will be ``n_sections * 2`` + even if some of these are (effectively) zero. + + .. versionadded:: 0.16.0 + """ + xp = array_namespace(sos) + sos = xp.asarray(sos) + + n_sections = sos.shape[0] + z = xp.zeros(n_sections*2, dtype=xp.complex128) + p = xp.zeros(n_sections*2, dtype=xp.complex128) + k = 1. + for section in range(n_sections): + zpk = tf2zpk(sos[section, :3], sos[section, 3:]) + z = xpx.at(z, slice(2*section, 2*section + zpk[0].shape[0])).set(zpk[0]) + p = xpx.at(p, slice(2*section, 2*section + zpk[1].shape[0])).set(zpk[1]) + k *= zpk[2] + return z, p, k + + +def _nearest_real_complex_idx(fro, to, which): + """Get the next closest real or complex element based on distance""" + assert which in ('real', 'complex', 'any') + order = np.argsort(np.abs(fro - to)) + if which == 'any': + return order[0] + else: + mask = np.isreal(fro[order]) + if which == 'complex': + mask = ~mask + return order[np.nonzero(mask)[0][0]] + + +def _single_zpksos(z, p, k): + """Create one second-order section from up to two zeros and poles""" + sos = np.zeros(6) + b, a = zpk2tf(z, p, k) + sos[3-len(b):3] = b + sos[6-len(a):6] = a + return sos + + +def zpk2sos(z, p, k, pairing=None, *, analog=False): + """Return second-order sections from zeros, poles, and gain of a system. + + Parameters + ---------- + z : array_like + Zeros of the transfer function. + p : array_like + Poles of the transfer function. + k : float + System gain. + pairing : {None, 'nearest', 'keep_odd', 'minimal'}, optional + The method to use to combine pairs of poles and zeros into sections. + If analog is False and pairing is None, pairing is set to 'nearest'; + if analog is True, pairing must be 'minimal', and is set to that if + it is None. + analog : bool, optional + If True, system is analog, otherwise discrete. + + .. versionadded:: 1.8.0 + + Returns + ------- + sos : ndarray + Array of second-order filter coefficients, with shape + ``(n_sections, 6)``. See `sosfilt` for the SOS filter format + specification. + + See Also + -------- + sosfilt + + Notes + ----- + The algorithm used to convert ZPK to SOS format is designed to + minimize errors due to numerical precision issues. The pairing + algorithm attempts to minimize the peak gain of each biquadratic + section. This is done by pairing poles with the nearest zeros, starting + with the poles closest to the unit circle for discrete-time systems, and + poles closest to the imaginary axis for continuous-time systems. + + ``pairing='minimal'`` outputs may not be suitable for `sosfilt`, + and ``analog=True`` outputs will never be suitable for `sosfilt`. + + *Algorithms* + + The steps in the ``pairing='nearest'``, ``pairing='keep_odd'``, + and ``pairing='minimal'`` algorithms are mostly shared. The + ``'nearest'`` algorithm attempts to minimize the peak gain, while + ``'keep_odd'`` minimizes peak gain under the constraint that + odd-order systems should retain one section as first order. + ``'minimal'`` is similar to ``'keep_odd'``, but no additional + poles or zeros are introduced + + The algorithm steps are as follows: + + As a pre-processing step for ``pairing='nearest'``, + ``pairing='keep_odd'``, add poles or zeros to the origin as + necessary to obtain the same number of poles and zeros for + pairing. If ``pairing == 'nearest'`` and there are an odd number + of poles, add an additional pole and a zero at the origin. + + The following steps are then iterated over until no more poles or + zeros remain: + + 1. Take the (next remaining) pole (complex or real) closest to the + unit circle (or imaginary axis, for ``analog=True``) to + begin a new filter section. + 2. If the pole is real and there are no other remaining real poles [#]_, + add the closest real zero to the section and leave it as a first + order section. Note that after this step we are guaranteed to be + left with an even number of real poles, complex poles, real zeros, + and complex zeros for subsequent pairing iterations. + 3. Else: + + 1. If the pole is complex and the zero is the only remaining real + zero*, then pair the pole with the *next* closest zero + (guaranteed to be complex). This is necessary to ensure that + there will be a real zero remaining to eventually create a + first-order section (thus keeping the odd order). + 2. Else pair the pole with the closest remaining zero (complex or real). + 3. Proceed to complete the second-order section by adding another + pole and zero to the current pole and zero in the section: + + 1. If the current pole and zero are both complex, add their conjugates. + 2. Else if the pole is complex and the zero is real, add the + conjugate pole and the next closest real zero. + 3. Else if the pole is real and the zero is complex, add the + conjugate zero and the real pole closest to those zeros. + 4. Else (we must have a real pole and real zero) add the next + real pole closest to the unit circle, and then add the real + zero closest to that pole. + + .. [#] This conditional can only be met for specific odd-order inputs + with the ``pairing = 'keep_odd'`` or ``'minimal'`` methods. + + .. versionadded:: 0.16.0 + + Examples + -------- + + Design a 6th order low-pass elliptic digital filter for a system with a + sampling rate of 8000 Hz that has a pass-band corner frequency of + 1000 Hz. The ripple in the pass-band should not exceed 0.087 dB, and + the attenuation in the stop-band should be at least 90 dB. + + In the following call to `ellip`, we could use ``output='sos'``, + but for this example, we'll use ``output='zpk'``, and then convert + to SOS format with `zpk2sos`: + + >>> from scipy import signal + >>> import numpy as np + >>> z, p, k = signal.ellip(6, 0.087, 90, 1000/(0.5*8000), output='zpk') + + Now convert to SOS format. + + >>> sos = signal.zpk2sos(z, p, k) + + The coefficients of the numerators of the sections: + + >>> sos[:, :3] + array([[0.0014152 , 0.00248677, 0.0014152 ], + [1. , 0.72976874, 1. ], + [1. , 0.17607852, 1. ]]) + + The symmetry in the coefficients occurs because all the zeros are on the + unit circle. + + The coefficients of the denominators of the sections: + + >>> sos[:, 3:] + array([[ 1. , -1.32544025, 0.46989976], + [ 1. , -1.26118294, 0.62625924], + [ 1. , -1.2570723 , 0.8619958 ]]) + + The next example shows the effect of the `pairing` option. We have a + system with three poles and three zeros, so the SOS array will have + shape (2, 6). The means there is, in effect, an extra pole and an extra + zero at the origin in the SOS representation. + + >>> z1 = np.array([-1, -0.5-0.5j, -0.5+0.5j]) + >>> p1 = np.array([0.75, 0.8+0.1j, 0.8-0.1j]) + + With ``pairing='nearest'`` (the default), we obtain + + >>> signal.zpk2sos(z1, p1, 1) + array([[ 1. , 1. , 0.5 , 1. , -0.75, 0. ], + [ 1. , 1. , 0. , 1. , -1.6 , 0.65]]) + + The first section has the zeros {-0.5-0.05j, -0.5+0.5j} and the poles + {0, 0.75}, and the second section has the zeros {-1, 0} and poles + {0.8+0.1j, 0.8-0.1j}. Note that the extra pole and zero at the origin + have been assigned to different sections. + + With ``pairing='keep_odd'``, we obtain: + + >>> signal.zpk2sos(z1, p1, 1, pairing='keep_odd') + array([[ 1. , 1. , 0. , 1. , -0.75, 0. ], + [ 1. , 1. , 0.5 , 1. , -1.6 , 0.65]]) + + The extra pole and zero at the origin are in the same section. + The first section is, in effect, a first-order section. + + With ``pairing='minimal'``, the first-order section doesn't have + the extra pole and zero at the origin: + + >>> signal.zpk2sos(z1, p1, 1, pairing='minimal') + array([[ 0. , 1. , 1. , 0. , 1. , -0.75], + [ 1. , 1. , 0.5 , 1. , -1.6 , 0.65]]) + + """ + # TODO in the near future: + # 1. Add SOS capability to `filtfilt`, `freqz`, etc. somehow (#3259). + # 2. Make `decimate` use `sosfilt` instead of `lfilter`. + # 3. Make sosfilt automatically simplify sections to first order + # when possible. Note this might make `sosfiltfilt` a bit harder (ICs). + # 4. Further optimizations of the section ordering / pole-zero pairing. + # See the wiki for other potential issues. + + xp = array_namespace(z, p) + + # convert to numpy, convert back on exit XXX + z, p = map(np.asarray, (z, p)) + k = np.asarray(k) + + if pairing is None: + pairing = 'minimal' if analog else 'nearest' + + valid_pairings = ['nearest', 'keep_odd', 'minimal'] + if pairing not in valid_pairings: + raise ValueError(f'pairing must be one of {valid_pairings}, not {pairing}') + + if analog and pairing != 'minimal': + raise ValueError('for analog zpk2sos conversion, ' + 'pairing must be "minimal"') + + if len(z) == len(p) == 0: + if not analog: + return xp.asarray(np.asarray([[k, 0., 0., 1., 0., 0.]])) + else: + return xp.asarray(np.asarray([[0., 0., k, 0., 0., 1.]])) + + if pairing != 'minimal': + # ensure we have the same number of poles and zeros, and make copies + p = np.concatenate((p, np.zeros(max(len(z) - len(p), 0)))) + z = np.concatenate((z, np.zeros(max(len(p) - len(z), 0)))) + n_sections = (max(len(p), len(z)) + 1) // 2 + + if len(p) % 2 == 1 and pairing == 'nearest': + p = np.concatenate((p, [0.])) + z = np.concatenate((z, [0.])) + assert len(p) == len(z) + else: + if len(p) < len(z): + raise ValueError('for analog zpk2sos conversion, ' + 'must have len(p)>=len(z)') + + n_sections = (len(p) + 1) // 2 + + # Ensure we have complex conjugate pairs + # (note that _cplxreal only gives us one element of each complex pair): + z = np.concatenate(_cplxreal(z)) + p = np.concatenate(_cplxreal(p)) + if not np.isreal(k): + raise ValueError('k must be real') + k = k.real + + if not analog: + # digital: "worst" is the closest to the unit circle + def idx_worst(p): + return np.argmin(np.abs(1 - np.abs(p))) + else: + # analog: "worst" is the closest to the imaginary axis + def idx_worst(p): + return np.argmin(np.abs(np.real(p))) + + sos = np.zeros((n_sections, 6)) + + # Construct the system, reversing order so the "worst" are last + for si in range(n_sections-1, -1, -1): + # Select the next "worst" pole + p1_idx = idx_worst(p) + p1 = p[p1_idx] + p = np.delete(p, p1_idx) + + # Pair that pole with a zero + + if np.isreal(p1) and np.isreal(p).sum() == 0: + # Special case (1): last remaining real pole + if pairing != 'minimal': + z1_idx = _nearest_real_complex_idx(z, p1, 'real') + z1 = z[z1_idx] + z = np.delete(z, z1_idx) + sos[si] = _single_zpksos([z1, 0], [p1, 0], 1) + elif len(z) > 0: + z1_idx = _nearest_real_complex_idx(z, p1, 'real') + z1 = z[z1_idx] + z = np.delete(z, z1_idx) + sos[si] = _single_zpksos([z1], [p1], 1) + else: + sos[si] = _single_zpksos([], [p1], 1) + + elif (len(p) + 1 == len(z) + and not np.isreal(p1) + and np.isreal(p).sum() == 1 + and np.isreal(z).sum() == 1): + + # Special case (2): there's one real pole and one real zero + # left, and an equal number of poles and zeros to pair up. + # We *must* pair with a complex zero + + z1_idx = _nearest_real_complex_idx(z, p1, 'complex') + z1 = z[z1_idx] + z = np.delete(z, z1_idx) + sos[si] = _single_zpksos([z1, z1.conj()], [p1, p1.conj()], 1) + + else: + if np.isreal(p1): + prealidx = np.flatnonzero(np.isreal(p)) + p2_idx = prealidx[idx_worst(p[prealidx])] + p2 = p[p2_idx] + p = np.delete(p, p2_idx) + else: + p2 = p1.conj() + + # find closest zero + if len(z) > 0: + z1_idx = _nearest_real_complex_idx(z, p1, 'any') + z1 = z[z1_idx] + z = np.delete(z, z1_idx) + + if not np.isreal(z1): + sos[si] = _single_zpksos([z1, z1.conj()], [p1, p2], 1) + else: + if len(z) > 0: + z2_idx = _nearest_real_complex_idx(z, p1, 'real') + z2 = z[z2_idx] + assert np.isreal(z2) + z = np.delete(z, z2_idx) + sos[si] = _single_zpksos([z1, z2], [p1, p2], 1) + else: + sos[si] = _single_zpksos([z1], [p1, p2], 1) + else: + # no more zeros + sos[si] = _single_zpksos([], [p1, p2], 1) + + assert len(p) == len(z) == 0 # we've consumed all poles and zeros + del p, z + + # put gain in first sos + sos[0][:3] *= k + return xp.asarray(sos) + + +def _align_nums(nums, xp): + """Aligns the shapes of multiple numerators. + + Given an array of numerator coefficient arrays [[a_1, a_2,..., + a_n],..., [b_1, b_2,..., b_m]], this function pads shorter numerator + arrays with zero's so that all numerators have the same length. Such + alignment is necessary for functions like 'tf2ss', which needs the + alignment when dealing with SIMO transfer functions. + + Parameters + ---------- + nums: array_like + Numerator or list of numerators. Not necessarily with same length. + + Returns + ------- + nums: array + The numerator. If `nums` input was a list of numerators then a 2-D + array with padded zeros for shorter numerators is returned. Otherwise + returns ``np.asarray(nums)``. + """ + try: + # The statement can throw a ValueError if one + # of the numerators is a single digit and another + # is array-like e.g. if nums = [5, [1, 2, 3]] + nums = xp.asarray(nums) + + if not xp.isdtype(nums.dtype, "numeric"): + raise ValueError("dtype of numerator is non-numeric") + + return nums + + except ValueError: + nums = [xpx.atleast_nd(xp.asarray(num), ndim=1) for num in nums] + max_width = max(xp_size(num) for num in nums) + + # pre-allocate + aligned_nums = xp.zeros((len(nums), max_width)) + + # Create numerators with padded zeros + for index, num in enumerate(nums): + aligned_nums[index, -num.size:] = num + + return aligned_nums + + +def normalize(b, a): + """Normalize numerator/denominator of a continuous-time transfer function. + + If values of `b` are too close to 0, they are removed. In that case, a + BadCoefficients warning is emitted. + + Parameters + ---------- + b : array_like + Numerator of the transfer function. Can be a 2-D array to normalize + multiple transfer functions. + a : array_like + Denominator of the transfer function. At most 1-D. + + Returns + ------- + num : array + The numerator of the normalized transfer function. At least a 1-D + array. A 2-D array if the input `num` is a 2-D array. + den : 1-D array + The denominator of the normalized transfer function. + + Notes + ----- + Coefficients for both the numerator and denominator should be specified in + descending exponent order (e.g., ``s^2 + 3s + 5`` would be represented as + ``[1, 3, 5]``). + + Examples + -------- + >>> from scipy.signal import normalize + + Normalize the coefficients of the transfer function + ``(3*s^2 - 2*s + 5) / (2*s^2 + 3*s + 1)``: + + >>> b = [3, -2, 5] + >>> a = [2, 3, 1] + >>> normalize(b, a) + (array([ 1.5, -1. , 2.5]), array([1. , 1.5, 0.5])) + + A warning is generated if, for example, the first coefficient of + `b` is 0. In the following example, the result is as expected: + + >>> import warnings + >>> with warnings.catch_warnings(record=True, action='always') as w: + ... num, den = normalize([0, 3, 6], [2, -5, 4]) + + >>> num + array([1.5, 3. ]) + >>> den + array([ 1. , -2.5, 2. ]) + + >>> print(w[0].message) + Badly conditioned filter coefficients (numerator): the results may be meaningless + + """ + try: + xp = array_namespace(b, a) + except TypeError: + # object arrays, test_ltisys.py::TestSS2TF::test_simo_round_trip + xp = np_compat + + den = xp.asarray(a) + den = xpx.atleast_nd(den, ndim=1, xp=xp) + + num = xp.asarray(b) + num = xpx.atleast_nd(_align_nums(num, xp), ndim=2, xp=xp) + + if den.ndim != 1: + raise ValueError("Denominator polynomial must be rank-1 array.") + if num.ndim > 2: + raise ValueError("Numerator polynomial must be rank-1 or" + " rank-2 array.") + if xp.all(den == 0): + raise ValueError("Denominator must have at least on nonzero element.") + + # Trim leading zeros in denominator, leave at least one. + den = _pu._trim_zeros(den, 'f') + + # Normalize transfer function + num, den = num / den[0], den / den[0] + + # Count numerator columns that are all zero + leading_zeros = 0 + for j in range(num.shape[-1]): + col = num[:, j] + if xp.all(xp.abs(col) <= 1e-14): + leading_zeros += 1 + else: + break + + # Trim leading zeros of numerator + if leading_zeros > 0: + warnings.warn("Badly conditioned filter coefficients (numerator): the " + "results may be meaningless", + BadCoefficients, stacklevel=2) + # Make sure at least one column remains + if leading_zeros == num.shape[1]: + leading_zeros -= 1 + num = num[:, leading_zeros:] + + # Squeeze first dimension if singular + if num.shape[0] == 1: + num = num[0, :] + + return num, den + + +def lp2lp(b, a, wo=1.0): + r""" + Transform a lowpass filter prototype to a different frequency. + + Return an analog low-pass filter with cutoff frequency `wo` + from an analog low-pass filter prototype with unity cutoff frequency, in + transfer function ('ba') representation. + + Parameters + ---------- + b : array_like, shape (M,) + Numerator polynomial coefficients. Must be 1-D. + a : array_like, shape (N,) + Denominator polynomial coefficients. Must be 1-D. + wo : float + Desired cutoff, as angular frequency (e.g. rad/s). + Defaults to no change. + + Returns + ------- + b : array_like + Numerator polynomial coefficients of the transformed low-pass filter. + a : array_like + Denominator polynomial coefficients of the transformed low-pass filter. + + See Also + -------- + lp2hp, lp2bp, lp2bs, bilinear + lp2lp_zpk + + Notes + ----- + This is derived from the s-plane substitution + + .. math:: s \rightarrow \frac{s}{\omega_0} + + Examples + -------- + + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + + >>> lp = signal.lti([1.0], [1.0, 1.0]) + >>> lp2 = signal.lti(*signal.lp2lp(lp.num, lp.den, 2)) + >>> w, mag_lp, p_lp = lp.bode() + >>> w, mag_lp2, p_lp2 = lp2.bode(w) + + >>> plt.plot(w, mag_lp, label='Lowpass') + >>> plt.plot(w, mag_lp2, label='Transformed Lowpass') + >>> plt.semilogx() + >>> plt.grid(True) + >>> plt.xlabel('Frequency [rad/s]') + >>> plt.ylabel('Amplitude [dB]') + >>> plt.legend() + + """ + xp = array_namespace(a, b) + a, b = map(xp.asarray, (a, b)) + a, b = xp_promote(a, b, force_floating=True, xp=xp) + a = xpx.atleast_nd(a, ndim=1, xp=xp) + b = xpx.atleast_nd(b, ndim=1, xp=xp) + + try: + wo = float(wo) + except TypeError: + wo = float(wo[0]) + d = a.shape[0] + n = b.shape[0] + M = max((d, n)) + pwo = wo ** xp.arange(M - 1, -1, -1, dtype=xp.float64) + start1 = max((n - d, 0)) + start2 = max((d - n, 0)) + b = b * pwo[start1] / pwo[start2:] + a = a * pwo[start1] / pwo[start1:] + return normalize(b, a) + + +def _resize(a, new_shape, xp): + # https://github.com/numpy/numpy/blob/v2.2.4/numpy/_core/fromnumeric.py#L1535 + a = xp.reshape(a, (-1,)) + + new_size = 1 + for dim_length in new_shape: + new_size *= dim_length + if dim_length < 0: + raise ValueError( + 'all elements of `new_shape` must be non-negative' + ) + + if xp_size(a) == 0 or new_size == 0: + # First case must zero fill. The second would have repeats == 0. + return xp.zeros_like(a, shape=new_shape) + + repeats = -(-new_size // xp_size(a)) # ceil division + a = xp.concat((a,) * repeats)[:new_size] + + return xp.reshape(a, new_shape) + + +def lp2hp(b, a, wo=1.0): + r""" + Transform a lowpass filter prototype to a highpass filter. + + Return an analog high-pass filter with cutoff frequency `wo` + from an analog low-pass filter prototype with unity cutoff frequency, in + transfer function ('ba') representation. + + Parameters + ---------- + b : array_like, shape (M,) + Numerator polynomial coefficients. Must be 1-D. + a : array_like, shape (N,) + Denominator polynomial coefficients. Must be 1-D. + wo : float + Desired cutoff, as angular frequency (e.g., rad/s). + Defaults to no change. + + Returns + ------- + b : array_like + Numerator polynomial coefficients of the transformed high-pass filter. + a : array_like + Denominator polynomial coefficients of the transformed high-pass filter. + + See Also + -------- + lp2lp, lp2bp, lp2bs, bilinear + lp2hp_zpk + + Notes + ----- + This is derived from the s-plane substitution + + .. math:: s \rightarrow \frac{\omega_0}{s} + + This maintains symmetry of the lowpass and highpass responses on a + logarithmic scale. + + Examples + -------- + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + + >>> lp = signal.lti([1.0], [1.0, 1.0]) + >>> hp = signal.lti(*signal.lp2hp(lp.num, lp.den)) + >>> w, mag_lp, p_lp = lp.bode() + >>> w, mag_hp, p_hp = hp.bode(w) + + >>> plt.plot(w, mag_lp, label='Lowpass') + >>> plt.plot(w, mag_hp, label='Highpass') + >>> plt.semilogx() + >>> plt.grid(True) + >>> plt.xlabel('Frequency [rad/s]') + >>> plt.ylabel('Amplitude [dB]') + >>> plt.legend() + + """ + xp = array_namespace(a, b) + + a, b = map(xp.asarray, (a, b)) + a, b = xp_promote(a, b, force_floating=True, xp=xp) + a = xpx.atleast_nd(a, ndim=1, xp=xp) + b = xpx.atleast_nd(b, ndim=1, xp=xp) + + try: + wo = float(wo) + except TypeError: + wo = float(wo[0]) + d = a.shape[0] + n = b.shape[0] + if wo != 1: + pwo = wo ** xp.arange(max((d, n)), dtype=b.dtype) + else: + pwo = xp.ones(max((d, n)), dtype=b.dtype) + if d >= n: + outa = xp.flip(a) * pwo + outb = _resize(b, (d,), xp=xp) + outb = xpx.at(outb)[n:].set(0.0) + outb = xpx.at(outb)[:n].set(xp.flip(b) * pwo[:n]) + else: + outb = xp.flip(b) * pwo + outa = _resize(a, (n,), xp=xp) + outa = xpx.at(outa)[d:].set(0.0) + outa = xpx.at(outa)[:d].set(xp.flip(a) * pwo[:d]) + + return normalize(outb, outa) + + +def lp2bp(b, a, wo=1.0, bw=1.0): + r""" + Transform a lowpass filter prototype to a bandpass filter. + + Return an analog band-pass filter with center frequency `wo` and + bandwidth `bw` from an analog low-pass filter prototype with unity + cutoff frequency, in transfer function ('ba') representation. + + Parameters + ---------- + b : array_like, shape (M,) + Numerator polynomial coefficients. Must be 1-D. + a : array_like, shape (N,) + Denominator polynomial coefficients. Must be 1-D. + wo : float + Desired passband center, as angular frequency (e.g., rad/s). + Defaults to no change. + bw : float + Desired passband width, as angular frequency (e.g., rad/s). + Defaults to 1. + + Returns + ------- + b : array_like + Numerator polynomial coefficients of the transformed band-pass filter. + a : array_like + Denominator polynomial coefficients of the transformed band-pass filter. + + See Also + -------- + lp2lp, lp2hp, lp2bs, bilinear + lp2bp_zpk + + Notes + ----- + This is derived from the s-plane substitution + + .. math:: s \rightarrow \frac{s^2 + {\omega_0}^2}{s \cdot \mathrm{BW}} + + This is the "wideband" transformation, producing a passband with + geometric (log frequency) symmetry about `wo`. + + Examples + -------- + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + + >>> lp = signal.lti([1.0], [1.0, 1.0]) + >>> bp = signal.lti(*signal.lp2bp(lp.num, lp.den)) + >>> w, mag_lp, p_lp = lp.bode() + >>> w, mag_bp, p_bp = bp.bode(w) + + >>> plt.plot(w, mag_lp, label='Lowpass') + >>> plt.plot(w, mag_bp, label='Bandpass') + >>> plt.semilogx() + >>> plt.grid(True) + >>> plt.xlabel('Frequency [rad/s]') + >>> plt.ylabel('Amplitude [dB]') + >>> plt.legend() + """ + xp = array_namespace(a, b) + + a, b = map(xp.asarray, (a, b)) + a, b = xp_promote(a, b, force_floating=True, xp=xp) + a = xpx.atleast_nd(a, ndim=1, xp=xp) + b = xpx.atleast_nd(b, ndim=1, xp=xp) + + D = a.shape[0] - 1 + N = b.shape[0] - 1 + ma = max([N, D]) + Np = N + ma + Dp = D + ma + bprime = xp.empty(Np + 1, dtype=b.dtype) + aprime = xp.empty(Dp + 1, dtype=a.dtype) + wosq = wo * wo + for j in range(Np + 1): + val = 0.0 + for i in range(0, N + 1): + for k in range(0, i + 1): + if ma - i + 2 * k == j: + val += comb(i, k) * b[N - i] * (wosq) ** (i - k) / bw ** i + bprime = xpx.at(bprime, Np - j).set(val, xp=xp) + for j in range(Dp + 1): + val = 0.0 + for i in range(0, D + 1): + for k in range(0, i + 1): + if ma - i + 2 * k == j: + val += comb(i, k) * a[D - i] * (wosq) ** (i - k) / bw ** i + aprime = xpx.at(aprime, Dp - j).set(val, xp=xp) + + return normalize(bprime, aprime) + + +def lp2bs(b, a, wo=1.0, bw=1.0): + r""" + Transform a lowpass filter prototype to a bandstop filter. + + Return an analog band-stop filter with center frequency `wo` and + bandwidth `bw` from an analog low-pass filter prototype with unity + cutoff frequency, in transfer function ('ba') representation. + + Parameters + ---------- + b : array_like, shape (M,) + Numerator polynomial coefficients. Must be 1-D. + a : array_like, shape (N,) + Denominator polynomial coefficients. Must be 1-D. + wo : float + Desired stopband center, as angular frequency (e.g., rad/s). + Defaults to no change. + bw : float + Desired stopband width, as angular frequency (e.g., rad/s). + Defaults to 1. + + Returns + ------- + b : array_like + Numerator polynomial coefficients of the transformed band-stop filter. + a : array_like + Denominator polynomial coefficients of the transformed band-stop filter. + + See Also + -------- + lp2lp, lp2hp, lp2bp, bilinear + lp2bs_zpk + + Notes + ----- + This is derived from the s-plane substitution + + .. math:: s \rightarrow \frac{s \cdot \mathrm{BW}}{s^2 + {\omega_0}^2} + + This is the "wideband" transformation, producing a stopband with + geometric (log frequency) symmetry about `wo`. + + Examples + -------- + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + + >>> lp = signal.lti([1.0], [1.0, 1.5]) + >>> bs = signal.lti(*signal.lp2bs(lp.num, lp.den)) + >>> w, mag_lp, p_lp = lp.bode() + >>> w, mag_bs, p_bs = bs.bode(w) + >>> plt.plot(w, mag_lp, label='Lowpass') + >>> plt.plot(w, mag_bs, label='Bandstop') + >>> plt.semilogx() + >>> plt.grid(True) + >>> plt.xlabel('Frequency [rad/s]') + >>> plt.ylabel('Amplitude [dB]') + >>> plt.legend() + """ + xp = array_namespace(a, b) + + a, b = map(xp.asarray, (a, b)) + a, b = xp_promote(a, b, force_floating=True, xp=xp) + a = xpx.atleast_nd(a, ndim=1, xp=xp) + b = xpx.atleast_nd(b, ndim=1, xp=xp) + + D = a.shape[0] - 1 + N = b.shape[0] - 1 + M = max([N, D]) + Np = M + M + Dp = M + M + bprime = xp.empty(Np + 1, dtype=b.dtype) + aprime = xp.empty(Dp + 1, dtype=a.dtype) + wosq = wo * wo + for j in range(Np + 1): + val = 0.0 + for i in range(0, N + 1): + for k in range(0, M - i + 1): + if i + 2 * k == j: + val += (comb(M - i, k) * b[N - i] * + (wosq) ** (M - i - k) * bw ** i) + bprime = xpx.at(bprime, Np - j).set(val, xp=xp) + for j in range(Dp + 1): + val = 0.0 + for i in range(0, D + 1): + for k in range(0, M - i + 1): + if i + 2 * k == j: + val += (comb(M - i, k) * a[D - i] * + (wosq) ** (M - i - k) * bw ** i) + aprime = xpx.at(aprime, Dp - j).set(val, xp=xp) + + return normalize(bprime, aprime) + + +def bilinear(b, a, fs=1.0): + r"""Calculate a digital IIR filter from an analog transfer function by utilizing + the bilinear transform. + + Parameters + ---------- + b : array_like + Coefficients of the numerator polynomial of the analog transfer function in + form of a complex- or real-valued 1d array. + a : array_like + Coefficients of the denominator polynomial of the analog transfer function in + form of a complex- or real-valued 1d array. + fs : float + Sample rate, as ordinary frequency (e.g., hertz). No pre-warping is + done in this function. + + Returns + ------- + beta : ndarray + Coefficients of the numerator polynomial of the digital transfer function in + form of a complex- or real-valued 1d array. + alpha : ndarray + Coefficients of the denominator polynomial of the digital transfer function in + form of a complex- or real-valued 1d array. + + Notes + ----- + The parameters :math:`b = [b_0, \ldots, b_Q]` and :math:`a = [a_0, \ldots, a_P]` + are 1d arrays of length :math:`Q+1` and :math:`P+1`. They define the analog + transfer function + + .. math:: + + H_a(s) = \frac{b_0 s^Q + b_1 s^{Q-1} + \cdots + b_Q}{ + a_0 s^P + a_1 s^{P-1} + \cdots + a_P}\ . + + The bilinear transform [1]_ is applied by substituting + + .. math:: + + s = \kappa \frac{z-1}{z+1}\ , \qquad \kappa := 2 f_s\ , + + into :math:`H_a(s)`, with :math:`f_s` being the sampling rate. + This results in the digital transfer function in the :math:`z`-domain + + .. math:: + + H_d(z) = \frac{b_0 \left(\kappa \frac{z-1}{z+1}\right)^Q + + b_1 \left(\kappa \frac{z-1}{z+1}\right)^{Q-1} + + \cdots + b_Q}{ + a_0 \left(\kappa \frac{z-1}{z+1}\right)^P + + a_1 \left(\kappa \frac{z-1}{z+1}\right)^{P-1} + + \cdots + a_P}\ . + + This expression can be simplified by multiplying numerator and denominator by + :math:`(z+1)^N`, with :math:`N=\max(P, Q)`. This allows :math:`H_d(z)` to be + reformulated as + + .. math:: + + & & \frac{b_0 \big(\kappa (z-1)\big)^Q (z+1)^{N-Q} + + b_1 \big(\kappa (z-1)\big)^{Q-1} (z+1)^{N-Q+1} + + \cdots + b_Q(z+1)^N}{ + a_0 \big(\kappa (z-1)\big)^P (z+1)^{N-P} + + a_1 \big(\kappa (z-1)\big)^{P-1} (z+1)^{N-P+1} + + \cdots + a_P(z+1)^N}\\ + &=:& \frac{\beta_0 + \beta_1 z^{-1} + \cdots + \beta_N z^{-N}}{ + \alpha_0 + \alpha_1 z^{-1} + \cdots + \alpha_N z^{-N}}\ . + + + This is the equation implemented to perform the bilinear transform. Note that for + large :math:`f_s`, :math:`\kappa^Q` or :math:`\kappa^P` can cause a numeric + overflow for sufficiently large :math:`P` or :math:`Q`. + + References + ---------- + .. [1] "Bilinear Transform", Wikipedia, + https://en.wikipedia.org/wiki/Bilinear_transform + + See Also + -------- + lp2lp, lp2hp, lp2bp, lp2bs, bilinear_zpk + + Examples + -------- + The following example shows the frequency response of an analog bandpass filter and + the corresponding digital filter derived by utilitzing the bilinear transform: + + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + >>> import numpy as np + ... + >>> fs = 100 # sampling frequency + >>> om_c = 2 * np.pi * np.array([7, 13]) # corner frequencies + >>> bb_s, aa_s = signal.butter(4, om_c, btype='bandpass', analog=True, output='ba') + >>> bb_z, aa_z = signal.bilinear(bb_s, aa_s, fs) + ... + >>> w_z, H_z = signal.freqz(bb_z, aa_z) # frequency response of digitial filter + >>> w_s, H_s = signal.freqs(bb_s, aa_s, worN=w_z*fs) # analog filter response + ... + >>> f_z, f_s = w_z * fs / (2*np.pi), w_s / (2*np.pi) + >>> Hz_dB, Hs_dB = (20*np.log10(np.abs(H_).clip(1e-10)) for H_ in (H_z, H_s)) + >>> fg0, ax0 = plt.subplots() + >>> ax0.set_title("Frequency Response of 4-th order Bandpass Filter") + >>> ax0.set(xlabel='Frequency $f$ in Hertz', ylabel='Magnitude in dB', + ... xlim=[f_z[1], fs/2], ylim=[-200, 2]) + >>> ax0.semilogx(f_z, Hz_dB, alpha=.5, label=r'$|H_z(e^{j 2 \pi f})|$') + >>> ax0.semilogx(f_s, Hs_dB, alpha=.5, label=r'$|H_s(j 2 \pi f)|$') + >>> ax0.legend() + >>> ax0.grid(which='both', axis='x') + >>> ax0.grid(which='major', axis='y') + >>> plt.show() + + The difference in the higher frequencies shown in the plot is caused by an effect + called "frequency warping". [1]_ describes a method called "pre-warping" to + reduce those deviations. + """ + xp = array_namespace(b, a) + + b, a = map(np.asarray, (b, a)) + b, a = np.atleast_1d(b), np.atleast_1d(a) # convert scalars, if needed + if not a.ndim == 1: + raise ValueError(f"Parameter a is not a 1d array since {a.shape=}") + if not b.ndim == 1: + raise ValueError(f"Parameter b is not a 1d array since {b.shape=}") + b, a = np.trim_zeros(b, 'f'), np.trim_zeros(a, 'f') # remove leading zeros + fs = _validate_fs(fs, allow_none=False) + + # Splitting the factor fs*2 between numerator and denominator reduces the chance of + # numeric overflow for large fs and large N: + fac = np.sqrt(fs*2) + zp1 = np.polynomial.Polynomial((+1, 1)) / fac # Polynomial (z + 1) / fac + zm1 = np.polynomial.Polynomial((-1, 1)) * fac # Polynomial (z - 1) * fac + # Note that NumPy's Polynomial coefficient order is backward compared to a and b. + + N = max(len(a), len(b)) - 1 + numerator = sum(b_ * zp1**(N-q) * zm1**q for q, b_ in enumerate(b[::-1])) + denominator = sum(a_ * zp1**(N-p) * zm1**p for p, a_ in enumerate(a[::-1])) + + return normalize( + xp.asarray(numerator.coef[::-1].copy()), + xp.asarray(denominator.coef[::-1].copy()) + ) + + +def _validate_gpass_gstop(gpass, gstop): + + if gpass <= 0.0: + raise ValueError("gpass should be larger than 0.0") + elif gstop <= 0.0: + raise ValueError("gstop should be larger than 0.0") + elif gpass > gstop: + raise ValueError("gpass should be smaller than gstop") + + +def iirdesign(wp, ws, gpass, gstop, analog=False, ftype='ellip', output='ba', + fs=None): + """Complete IIR digital and analog filter design. + + Given passband and stopband frequencies and gains, construct an analog or + digital IIR filter of minimum order for a given basic type. Return the + output in numerator, denominator ('ba'), pole-zero ('zpk') or second order + sections ('sos') form. + + Parameters + ---------- + wp, ws : float or array like, shape (2,) + Passband and stopband edge frequencies. Possible values are scalars + (for lowpass and highpass filters) or ranges (for bandpass and bandstop + filters). + For digital filters, these are in the same units as `fs`. By default, + `fs` is 2 half-cycles/sample, so these are normalized from 0 to 1, + where 1 is the Nyquist frequency. For example: + + - Lowpass: wp = 0.2, ws = 0.3 + - Highpass: wp = 0.3, ws = 0.2 + - Bandpass: wp = [0.2, 0.5], ws = [0.1, 0.6] + - Bandstop: wp = [0.1, 0.6], ws = [0.2, 0.5] + + For analog filters, `wp` and `ws` are angular frequencies (e.g., rad/s). + Note, that for bandpass and bandstop filters passband must lie strictly + inside stopband or vice versa. Also note that the cutoff at the band edges + for IIR filters is defined as half-power, so -3dB, not half-amplitude (-6dB) + like for `scipy.signal.firwin`. + gpass : float + The maximum loss in the passband (dB). + gstop : float + The minimum attenuation in the stopband (dB). + analog : bool, optional + When True, return an analog filter, otherwise a digital filter is + returned. + ftype : str, optional + The type of IIR filter to design: + + - Butterworth : 'butter' + - Chebyshev I : 'cheby1' + - Chebyshev II : 'cheby2' + - Cauer/elliptic: 'ellip' + + output : {'ba', 'zpk', 'sos'}, optional + Filter form of the output: + + - second-order sections (recommended): 'sos' + - numerator/denominator (default) : 'ba' + - pole-zero : 'zpk' + + In general the second-order sections ('sos') form is + recommended because inferring the coefficients for the + numerator/denominator form ('ba') suffers from numerical + instabilities. For reasons of backward compatibility the default + form is the numerator/denominator form ('ba'), where the 'b' + and the 'a' in 'ba' refer to the commonly used names of the + coefficients used. + + Note: Using the second-order sections form ('sos') is sometimes + associated with additional computational costs: for + data-intense use cases it is therefore recommended to also + investigate the numerator/denominator form ('ba'). + + fs : float, optional + The sampling frequency of the digital system. + + .. versionadded:: 1.2.0 + + Returns + ------- + b, a : ndarray, ndarray + Numerator (`b`) and denominator (`a`) polynomials of the IIR filter. + Only returned if ``output='ba'``. + z, p, k : ndarray, ndarray, float + Zeros, poles, and system gain of the IIR filter transfer + function. Only returned if ``output='zpk'``. + sos : ndarray + Second-order sections representation of the IIR filter. + Only returned if ``output='sos'``. + + See Also + -------- + butter : Filter design using order and critical points + cheby1, cheby2, ellip, bessel + buttord : Find order and critical points from passband and stopband spec + cheb1ord, cheb2ord, ellipord + iirfilter : General filter design using order and critical frequencies + + Notes + ----- + The ``'sos'`` output parameter was added in 0.16.0. + + Examples + -------- + >>> import numpy as np + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + >>> import matplotlib.ticker + + >>> wp = 0.2 + >>> ws = 0.3 + >>> gpass = 1 + >>> gstop = 40 + + >>> system = signal.iirdesign(wp, ws, gpass, gstop) + >>> w, h = signal.freqz(*system) + + >>> fig, ax1 = plt.subplots() + >>> ax1.set_title('Digital filter frequency response') + >>> ax1.plot(w, 20 * np.log10(abs(h)), 'b') + >>> ax1.set_ylabel('Amplitude [dB]', color='b') + >>> ax1.set_xlabel('Frequency [rad/sample]') + >>> ax1.grid(True) + >>> ax1.set_ylim([-120, 20]) + >>> ax2 = ax1.twinx() + >>> phase = np.unwrap(np.angle(h)) + >>> ax2.plot(w, phase, 'g') + >>> ax2.set_ylabel('Phase [rad]', color='g') + >>> ax2.grid(True) + >>> ax2.axis('tight') + >>> ax2.set_ylim([-6, 1]) + >>> nticks = 8 + >>> ax1.yaxis.set_major_locator(matplotlib.ticker.LinearLocator(nticks)) + >>> ax2.yaxis.set_major_locator(matplotlib.ticker.LinearLocator(nticks)) + + """ + xp = array_namespace(wp, ws) + wp, ws = map(xp.asarray, (wp, ws)) + + try: + ordfunc = filter_dict[ftype][1] + except KeyError as e: + raise ValueError(f"Invalid IIR filter type: {ftype}") from e + except IndexError as e: + raise ValueError(f"{ftype} does not have order selection. " + "Use iirfilter function.") from e + + _validate_gpass_gstop(gpass, gstop) + + wp = xpx.atleast_nd(wp, ndim=1, xp=xp) + ws = xpx.atleast_nd(ws, ndim=1, xp=xp) + + fs = _validate_fs(fs, allow_none=True) + + if wp.shape[0] != ws.shape[0] or wp.shape not in [(1,), (2,)]: + raise ValueError("wp and ws must have one or two elements each, and " + f"the same shape, got {wp.shape} and {ws.shape}") + + if xp.any(wp <= 0) or xp.any(ws <= 0): + raise ValueError("Values for wp, ws must be greater than 0") + + if not analog: + if fs is None: + if xp.any(wp >= 1) or xp.any(ws >= 1): + raise ValueError("Values for wp, ws must be less than 1") + elif xp.any(wp >= fs/2) or xp.any(ws >= fs/2): + raise ValueError("Values for wp, ws must be less than fs/2 " + f"(fs={fs} -> fs/2={fs/2})") + + if wp.shape[0] == 2: + if not ((ws[0] < wp[0] and wp[1] < ws[1]) or + (wp[0] < ws[0] and ws[1] < wp[1])): + raise ValueError("Passband must lie strictly inside stopband " + "or vice versa") + + band_type = 2 * (wp.shape[0] - 1) + band_type += 1 + if wp[0] >= ws[0]: + band_type += 1 + + btype = {1: 'lowpass', 2: 'highpass', + 3: 'bandstop', 4: 'bandpass'}[band_type] + + N, Wn = ordfunc(wp, ws, gpass, gstop, analog=analog, fs=fs) + return iirfilter(N, Wn, rp=gpass, rs=gstop, analog=analog, btype=btype, + ftype=ftype, output=output, fs=fs) + + +def iirfilter(N, Wn, rp=None, rs=None, btype='band', analog=False, + ftype='butter', output='ba', fs=None): + """ + IIR digital and analog filter design given order and critical points. + + Design an Nth-order digital or analog filter and return the filter + coefficients. + + Parameters + ---------- + N : int + The order of the filter. + Wn : array_like + A scalar or length-2 sequence giving the critical frequencies. + + For digital filters, `Wn` are in the same units as `fs`. By default, + `fs` is 2 half-cycles/sample, so these are normalized from 0 to 1, + where 1 is the Nyquist frequency. (`Wn` is thus in + half-cycles / sample.) + + For analog filters, `Wn` is an angular frequency (e.g., rad/s). + + When Wn is a length-2 sequence, ``Wn[0]`` must be less than ``Wn[1]``. + rp : float, optional + For Chebyshev and elliptic filters, provides the maximum ripple + in the passband. (dB) + rs : float, optional + For Chebyshev and elliptic filters, provides the minimum attenuation + in the stop band. (dB) + btype : {'bandpass', 'lowpass', 'highpass', 'bandstop'}, optional + The type of filter. Default is 'bandpass'. + analog : bool, optional + When True, return an analog filter, otherwise a digital filter is + returned. + ftype : str, optional + The type of IIR filter to design: + + - Butterworth : 'butter' + - Chebyshev I : 'cheby1' + - Chebyshev II : 'cheby2' + - Cauer/elliptic: 'ellip' + - Bessel/Thomson: 'bessel' + + output : {'ba', 'zpk', 'sos'}, optional + Filter form of the output: + + - second-order sections (recommended): 'sos' + - numerator/denominator (default) : 'ba' + - pole-zero : 'zpk' + + In general the second-order sections ('sos') form is + recommended because inferring the coefficients for the + numerator/denominator form ('ba') suffers from numerical + instabilities. For reasons of backward compatibility the default + form is the numerator/denominator form ('ba'), where the 'b' + and the 'a' in 'ba' refer to the commonly used names of the + coefficients used. + + Note: Using the second-order sections form ('sos') is sometimes + associated with additional computational costs: for + data-intense use cases it is therefore recommended to also + investigate the numerator/denominator form ('ba'). + + fs : float, optional + The sampling frequency of the digital system. + + .. versionadded:: 1.2.0 + + Returns + ------- + b, a : ndarray, ndarray + Numerator (`b`) and denominator (`a`) polynomials of the IIR filter. + Only returned if ``output='ba'``. + z, p, k : ndarray, ndarray, float + Zeros, poles, and system gain of the IIR filter transfer + function. Only returned if ``output='zpk'``. + sos : ndarray + Second-order sections representation of the IIR filter. + Only returned if ``output='sos'``. + + See Also + -------- + butter : Filter design using order and critical points + cheby1, cheby2, ellip, bessel + buttord : Find order and critical points from passband and stopband spec + cheb1ord, cheb2ord, ellipord + iirdesign : General filter design using passband and stopband spec + + Notes + ----- + The ``'sos'`` output parameter was added in 0.16.0. + + The current behavior is for ``ndarray`` outputs to have 64 bit precision + (``float64`` or ``complex128``) regardless of the dtype of `Wn` but + outputs may respect the dtype of `Wn` in a future version. + + Examples + -------- + Generate a 17th-order Chebyshev II analog bandpass filter from 50 Hz to + 200 Hz and plot the frequency response: + + >>> import numpy as np + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + + >>> b, a = signal.iirfilter(17, [2*np.pi*50, 2*np.pi*200], rs=60, + ... btype='band', analog=True, ftype='cheby2') + >>> w, h = signal.freqs(b, a, 1000) + >>> fig = plt.figure() + >>> ax = fig.add_subplot(1, 1, 1) + >>> ax.semilogx(w / (2*np.pi), 20 * np.log10(np.maximum(abs(h), 1e-5))) + >>> ax.set_title('Chebyshev Type II bandpass frequency response') + >>> ax.set_xlabel('Frequency [Hz]') + >>> ax.set_ylabel('Amplitude [dB]') + >>> ax.axis((10, 1000, -100, 10)) + >>> ax.grid(which='both', axis='both') + >>> plt.show() + + Create a digital filter with the same properties, in a system with + sampling rate of 2000 Hz, and plot the frequency response. (Second-order + sections implementation is required to ensure stability of a filter of + this order): + + >>> sos = signal.iirfilter(17, [50, 200], rs=60, btype='band', + ... analog=False, ftype='cheby2', fs=2000, + ... output='sos') + >>> w, h = signal.freqz_sos(sos, 2000, fs=2000) + >>> fig = plt.figure() + >>> ax = fig.add_subplot(1, 1, 1) + >>> ax.semilogx(w, 20 * np.log10(np.maximum(abs(h), 1e-5))) + >>> ax.set_title('Chebyshev Type II bandpass frequency response') + >>> ax.set_xlabel('Frequency [Hz]') + >>> ax.set_ylabel('Amplitude [dB]') + >>> ax.axis((10, 1000, -100, 10)) + >>> ax.grid(which='both', axis='both') + >>> plt.show() + + """ + xp = array_namespace(Wn) + # For now, outputs will have float64 base dtype regardless of + # the dtype of Wn, so cast to float64 here to ensure 64 bit + # precision for all calculations. + Wn = xp.asarray(Wn, dtype=xp.float64) + + fs = _validate_fs(fs, allow_none=True) + ftype, btype, output = (x.lower() for x in (ftype, btype, output)) + if fs is not None: + if analog: + raise ValueError("fs cannot be specified for an analog filter") + Wn = Wn / (fs/2) + + if xp.any(Wn <= 0): + raise ValueError("filter critical frequencies must be greater than 0") + + if xp_size(Wn) > 1 and not Wn[0] < Wn[1]: + raise ValueError("Wn[0] must be less than Wn[1]") + + try: + btype = band_dict[btype] + except KeyError as e: + raise ValueError(f"'{btype}' is an invalid bandtype for filter.") from e + + try: + typefunc = filter_dict[ftype][0] + except KeyError as e: + raise ValueError(f"'{ftype}' is not a valid basic IIR filter.") from e + + if output not in ['ba', 'zpk', 'sos']: + raise ValueError(f"'{output}' is not a valid output form.") + + if rp is not None and rp < 0: + raise ValueError("passband ripple (rp) must be positive") + + if rs is not None and rs < 0: + raise ValueError("stopband attenuation (rs) must be positive") + + # Get analog lowpass prototype + if typefunc == buttap: + z, p, k = typefunc(N, xp=xp) + elif typefunc == besselap: + z, p, k = typefunc(N, norm=bessel_norms[ftype], xp=xp) + elif typefunc == cheb1ap: + if rp is None: + raise ValueError("passband ripple (rp) must be provided to " + "design a Chebyshev I filter.") + z, p, k = typefunc(N, rp, xp=xp) + elif typefunc == cheb2ap: + if rs is None: + raise ValueError("stopband attenuation (rs) must be provided to " + "design a Chebyshev II filter.") + z, p, k = typefunc(N, rs, xp=xp) + elif typefunc == ellipap: + if rs is None or rp is None: + raise ValueError("Both rp and rs must be provided to design an " + "elliptic filter.") + z, p, k = typefunc(N, rp, rs, xp=xp) + else: + raise NotImplementedError(f"'{ftype}' not implemented in iirfilter.") + + # Pre-warp frequencies for digital filter design + if not analog: + if xp.any(Wn <= 0) or xp.any(Wn >= 1): + if fs is not None: + raise ValueError("Digital filter critical frequencies must " + f"be 0 < Wn < fs/2 (fs={fs} -> fs/2={fs/2})") + raise ValueError("Digital filter critical frequencies " + "must be 0 < Wn < 1") + fs = 2.0 + warped = 2 * fs * xp.tan(xp.pi * Wn / fs) + else: + warped = Wn + + # transform to lowpass, bandpass, highpass, or bandstop + if btype in ('lowpass', 'highpass'): + if xp_size(Wn) != 1: + raise ValueError('Must specify a single critical frequency Wn ' + 'for lowpass or highpass filter') + + if btype == 'lowpass': + z, p, k = lp2lp_zpk(z, p, k, wo=warped) + elif btype == 'highpass': + z, p, k = lp2hp_zpk(z, p, k, wo=warped) + elif btype in ('bandpass', 'bandstop'): + try: + bw = warped[1] - warped[0] + wo = xp.sqrt(warped[0] * warped[1]) + except IndexError as e: + raise ValueError('Wn must specify start and stop frequencies for ' + 'bandpass or bandstop filter') from e + + if btype == 'bandpass': + z, p, k = lp2bp_zpk(z, p, k, wo=wo, bw=bw) + elif btype == 'bandstop': + z, p, k = lp2bs_zpk(z, p, k, wo=wo, bw=bw) + else: + raise NotImplementedError(f"'{btype}' not implemented in iirfilter.") + + # Find discrete equivalent if necessary + if not analog: + z, p, k = bilinear_zpk(z, p, k, fs=fs) + + # Transform to proper out type (pole-zero, state-space, numer-denom) + if output == 'zpk': + return z, p, k + elif output == 'ba': + return zpk2tf(z, p, k) + elif output == 'sos': + return zpk2sos(z, p, k, analog=analog) + + +def _relative_degree(z, p): + """ + Return relative degree of transfer function from zeros and poles + """ + degree = p.shape[0] - z.shape[0] + if degree < 0: + raise ValueError("Improper transfer function. " + "Must have at least as many poles as zeros.") + else: + return degree + + +def bilinear_zpk(z, p, k, fs): + r""" + Return a digital IIR filter from an analog one using a bilinear transform. + + Transform a set of poles and zeros from the analog s-plane to the digital + z-plane using Tustin's method, which substitutes ``2*fs*(z-1) / (z+1)`` for + ``s``, maintaining the shape of the frequency response. + + Parameters + ---------- + z : array_like + Zeros of the analog filter transfer function. + p : array_like + Poles of the analog filter transfer function. + k : float + System gain of the analog filter transfer function. + fs : float + Sample rate, as ordinary frequency (e.g., hertz). No prewarping is + done in this function. + + Returns + ------- + z : ndarray + Zeros of the transformed digital filter transfer function. + p : ndarray + Poles of the transformed digital filter transfer function. + k : float + System gain of the transformed digital filter. + + See Also + -------- + lp2lp_zpk, lp2hp_zpk, lp2bp_zpk, lp2bs_zpk + bilinear + + Notes + ----- + .. versionadded:: 1.1.0 + + Examples + -------- + >>> import numpy as np + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + + >>> fs = 100 + >>> bf = 2 * np.pi * np.array([7, 13]) + >>> filts = signal.lti(*signal.butter(4, bf, btype='bandpass', analog=True, + ... output='zpk')) + >>> filtz = signal.lti(*signal.bilinear_zpk(filts.zeros, filts.poles, + ... filts.gain, fs)) + >>> wz, hz = signal.freqz_zpk(filtz.zeros, filtz.poles, filtz.gain) + >>> ws, hs = signal.freqs_zpk(filts.zeros, filts.poles, filts.gain, + ... worN=fs*wz) + >>> plt.semilogx(wz*fs/(2*np.pi), 20*np.log10(np.abs(hz).clip(1e-15)), + ... label=r'$|H_z(e^{j \omega})|$') + >>> plt.semilogx(wz*fs/(2*np.pi), 20*np.log10(np.abs(hs).clip(1e-15)), + ... label=r'$|H(j \omega)|$') + >>> plt.legend() + >>> plt.xlabel('Frequency [Hz]') + >>> plt.ylabel('Amplitude [dB]') + >>> plt.grid(True) + """ + xp = array_namespace(z, p) + + z, p = map(xp.asarray, (z, p)) + z = xpx.atleast_nd(z, ndim=1, xp=xp) + p = xpx.atleast_nd(p, ndim=1, xp=xp) + + fs = _validate_fs(fs, allow_none=False) + + degree = _relative_degree(z, p) + + fs2 = 2.0*fs + + # Bilinear transform the poles and zeros + z_z = (fs2 + z) / (fs2 - z) + p_z = (fs2 + p) / (fs2 - p) + + # Any zeros that were at infinity get moved to the Nyquist frequency + z_z = xp.concat((z_z, -xp.ones(degree))) + + # Compensate for gain change + k_z = k * xp.real(xp.prod(fs2 - z) / xp.prod(fs2 - p)) + + return z_z, p_z, k_z + + +def lp2lp_zpk(z, p, k, wo=1.0): + r""" + Transform a lowpass filter prototype to a different frequency. + + Return an analog low-pass filter with cutoff frequency `wo` + from an analog low-pass filter prototype with unity cutoff frequency, + using zeros, poles, and gain ('zpk') representation. + + Parameters + ---------- + z : array_like + Zeros of the analog filter transfer function. + p : array_like + Poles of the analog filter transfer function. + k : float + System gain of the analog filter transfer function. + wo : float + Desired cutoff, as angular frequency (e.g., rad/s). + Defaults to no change. + + Returns + ------- + z : ndarray + Zeros of the transformed low-pass filter transfer function. + p : ndarray + Poles of the transformed low-pass filter transfer function. + k : float + System gain of the transformed low-pass filter. + + See Also + -------- + lp2hp_zpk, lp2bp_zpk, lp2bs_zpk, bilinear + lp2lp + + Notes + ----- + This is derived from the s-plane substitution + + .. math:: s \rightarrow \frac{s}{\omega_0} + + .. versionadded:: 1.1.0 + + Examples + -------- + Use the 'zpk' (Zero-Pole-Gain) representation of a lowpass filter to + transform it to a new 'zpk' representation associated with a cutoff frequency wo. + + >>> from scipy.signal import lp2lp_zpk + >>> z = [7, 2] + >>> p = [5, 13] + >>> k = 0.8 + >>> wo = 0.4 + >>> lp2lp_zpk(z, p, k, wo) + ( array([2.8, 0.8]), array([2. , 5.2]), 0.8) + """ + xp = array_namespace(z, p) + + z, p = map(xp.asarray, (z, p)) + z = xpx.atleast_nd(z, ndim=1, xp=xp) + p = xpx.atleast_nd(p, ndim=1, xp=xp) + + wo = float(wo) # Avoid int wraparound + + degree = _relative_degree(z, p) + + # Scale all points radially from origin to shift cutoff frequency + z_lp = wo * z + p_lp = wo * p + + # Each shifted pole decreases gain by wo, each shifted zero increases it. + # Cancel out the net change to keep overall gain the same + k_lp = k * wo**degree + + return z_lp, p_lp, k_lp + + +def lp2hp_zpk(z, p, k, wo=1.0): + r""" + Transform a lowpass filter prototype to a highpass filter. + + Return an analog high-pass filter with cutoff frequency `wo` + from an analog low-pass filter prototype with unity cutoff frequency, + using zeros, poles, and gain ('zpk') representation. + + Parameters + ---------- + z : array_like + Zeros of the analog filter transfer function. + p : array_like + Poles of the analog filter transfer function. + k : float + System gain of the analog filter transfer function. + wo : float + Desired cutoff, as angular frequency (e.g., rad/s). + Defaults to no change. + + Returns + ------- + z : ndarray + Zeros of the transformed high-pass filter transfer function. + p : ndarray + Poles of the transformed high-pass filter transfer function. + k : float + System gain of the transformed high-pass filter. + + See Also + -------- + lp2lp_zpk, lp2bp_zpk, lp2bs_zpk, bilinear + lp2hp + + Notes + ----- + This is derived from the s-plane substitution + + .. math:: s \rightarrow \frac{\omega_0}{s} + + This maintains symmetry of the lowpass and highpass responses on a + logarithmic scale. + + .. versionadded:: 1.1.0 + + Examples + -------- + Use the 'zpk' (Zero-Pole-Gain) representation of a lowpass filter to + transform it to a highpass filter with a cutoff frequency wo. + + >>> from scipy.signal import lp2hp_zpk + >>> z = [ -2 + 3j , -0.5 - 0.8j ] + >>> p = [ -1 , -4 ] + >>> k = 10 + >>> wo = 0.6 + >>> lp2hp_zpk(z, p, k, wo) + ( array([-0.09230769-0.13846154j, -0.33707865+0.53932584j]), + array([-0.6 , -0.15]), + 8.5) + """ + xp = array_namespace(z, p) + + z, p = map(xp.asarray, (z, p)) + # XXX: no xp_promote here since that breaks TestButter + z = xpx.atleast_nd(z, ndim=1, xp=xp) + p = xpx.atleast_nd(p, ndim=1, xp=xp) + + wo = float(wo) + + degree = _relative_degree(z, p) + + # Invert positions radially about unit circle to convert LPF to HPF + # Scale all points radially from origin to shift cutoff frequency + z_hp = wo / z + p_hp = wo / p + + # If lowpass had zeros at infinity, inverting moves them to origin. + z_hp = xp.concat((z_hp, xp.zeros(degree))) + + # Cancel out gain change caused by inversion + k_hp = k * xp.real(xp.prod(-z) / xp.prod(-p)) + + return z_hp, p_hp, k_hp + + +def lp2bp_zpk(z, p, k, wo=1.0, bw=1.0): + r""" + Transform a lowpass filter prototype to a bandpass filter. + + Return an analog band-pass filter with center frequency `wo` and + bandwidth `bw` from an analog low-pass filter prototype with unity + cutoff frequency, using zeros, poles, and gain ('zpk') representation. + + Parameters + ---------- + z : array_like + Zeros of the analog filter transfer function. + p : array_like + Poles of the analog filter transfer function. + k : float + System gain of the analog filter transfer function. + wo : float + Desired passband center, as angular frequency (e.g., rad/s). + Defaults to no change. + bw : float + Desired passband width, as angular frequency (e.g., rad/s). + Defaults to 1. + + Returns + ------- + z : ndarray + Zeros of the transformed band-pass filter transfer function. + p : ndarray + Poles of the transformed band-pass filter transfer function. + k : float + System gain of the transformed band-pass filter. + + See Also + -------- + lp2lp_zpk, lp2hp_zpk, lp2bs_zpk, bilinear + lp2bp + + Notes + ----- + This is derived from the s-plane substitution + + .. math:: s \rightarrow \frac{s^2 + {\omega_0}^2}{s \cdot \mathrm{BW}} + + This is the "wideband" transformation, producing a passband with + geometric (log frequency) symmetry about `wo`. + + .. versionadded:: 1.1.0 + + Examples + -------- + Use the 'zpk' (Zero-Pole-Gain) representation of a lowpass filter to + transform it to a bandpass filter with a center frequency wo and + bandwidth bw. + + >>> from scipy.signal import lp2bp_zpk + >>> z = [ 5 + 2j , 5 - 2j ] + >>> p = [ 7 , -16 ] + >>> k = 0.8 + >>> wo = 0.62 + >>> bw = 15 + >>> lp2bp_zpk(z, p, k, wo, bw) + ( array([7.49955815e+01+3.00017676e+01j, 7.49955815e+01-3.00017676e+01j, + 4.41850748e-03-1.76761126e-03j, 4.41850748e-03+1.76761126e-03j]), + array([1.04996339e+02+0.j, -1.60167736e-03+0.j, 3.66108003e-03+0.j, + -2.39998398e+02+0.j]), 0.8) + """ + xp = array_namespace(z, p) + + z, p = map(xp.asarray, (z, p)) + z, p = xp_promote(z, p, force_floating=True, xp=xp) + z = xpx.atleast_nd(z, ndim=1, xp=xp) + p = xpx.atleast_nd(p, ndim=1, xp=xp) + + wo = float(wo) + bw = float(bw) + + degree = _relative_degree(z, p) + + # Scale poles and zeros to desired bandwidth + z_lp = z * bw/2 + p_lp = p * bw/2 + + # Square root needs to produce complex result, not NaN + z_lp = xp.astype(z_lp, xp.complex128) + p_lp = xp.astype(p_lp, xp.complex128) + + # Duplicate poles and zeros and shift from baseband to +wo and -wo + z_bp = xp.concat((z_lp + xp.sqrt(z_lp**2 - wo**2), + z_lp - xp.sqrt(z_lp**2 - wo**2))) + p_bp = xp.concat((p_lp + xp.sqrt(p_lp**2 - wo**2), + p_lp - xp.sqrt(p_lp**2 - wo**2))) + + # Move degree zeros to origin, leaving degree zeros at infinity for BPF + z_bp = xp.concat((z_bp, xp.zeros(degree))) + + # Cancel out gain change from frequency scaling + k_bp = k * bw**degree + + return z_bp, p_bp, k_bp + + +def lp2bs_zpk(z, p, k, wo=1.0, bw=1.0): + r""" + Transform a lowpass filter prototype to a bandstop filter. + + Return an analog band-stop filter with center frequency `wo` and + stopband width `bw` from an analog low-pass filter prototype with unity + cutoff frequency, using zeros, poles, and gain ('zpk') representation. + + Parameters + ---------- + z : array_like + Zeros of the analog filter transfer function. + p : array_like + Poles of the analog filter transfer function. + k : float + System gain of the analog filter transfer function. + wo : float + Desired stopband center, as angular frequency (e.g., rad/s). + Defaults to no change. + bw : float + Desired stopband width, as angular frequency (e.g., rad/s). + Defaults to 1. + + Returns + ------- + z : ndarray + Zeros of the transformed band-stop filter transfer function. + p : ndarray + Poles of the transformed band-stop filter transfer function. + k : float + System gain of the transformed band-stop filter. + + See Also + -------- + lp2lp_zpk, lp2hp_zpk, lp2bp_zpk, bilinear + lp2bs + + Notes + ----- + This is derived from the s-plane substitution + + .. math:: s \rightarrow \frac{s \cdot \mathrm{BW}}{s^2 + {\omega_0}^2} + + This is the "wideband" transformation, producing a stopband with + geometric (log frequency) symmetry about `wo`. + + .. versionadded:: 1.1.0 + + Examples + -------- + Transform a low-pass filter represented in 'zpk' (Zero-Pole-Gain) form + into a bandstop filter represented in 'zpk' form, with a center frequency wo and + bandwidth bw. + + >>> from scipy.signal import lp2bs_zpk + >>> z = [ ] + >>> p = [ 0.7 , -1 ] + >>> k = 9 + >>> wo = 0.5 + >>> bw = 10 + >>> lp2bs_zpk(z, p, k, wo, bw) + ( array([0.+0.5j, 0.+0.5j, 0.-0.5j, 0.-0.5j]), + array([14.2681928 +0.j, -0.02506281+0.j, 0.01752149+0.j, -9.97493719+0.j]), + -12.857142857142858) + """ + xp = array_namespace(z, p) + + z, p = map(xp.asarray, (z, p)) + z, p = xp_promote(z, p, force_floating=True, xp=xp) + z = xpx.atleast_nd(z, ndim=1, xp=xp) + p = xpx.atleast_nd(p, ndim=1, xp=xp) + + wo = float(wo) + bw = float(bw) + + degree = _relative_degree(z, p) + + # Invert to a highpass filter with desired bandwidth + z_hp = (bw/2) / z + p_hp = (bw/2) / p + + # Square root needs to produce complex result, not NaN + z_hp = xp.astype(z_hp, xp.complex128) + p_hp = xp.astype(p_hp, xp.complex128) + + # Duplicate poles and zeros and shift from baseband to +wo and -wo + z_bs = xp.concat((z_hp + xp.sqrt(z_hp**2 - wo**2), + z_hp - xp.sqrt(z_hp**2 - wo**2))) + p_bs = xp.concat((p_hp + xp.sqrt(p_hp**2 - wo**2), + p_hp - xp.sqrt(p_hp**2 - wo**2))) + + # Move any zeros that were at infinity to the center of the stopband + z_bs = xp.concat((z_bs, xp.full(degree, +1j*wo))) + z_bs = xp.concat((z_bs, xp.full(degree, -1j*wo))) + + # Cancel out gain change caused by inversion + k_bs = k * xp.real(xp.prod(-z) / xp.prod(-p)) + + return z_bs, p_bs, k_bs + + +def butter(N, Wn, btype='low', analog=False, output='ba', fs=None): + """ + Butterworth digital and analog filter design. + + Design an Nth-order digital or analog Butterworth filter and return + the filter coefficients. + + Parameters + ---------- + N : int + The order of the filter. For 'bandpass' and 'bandstop' filters, + the resulting order of the final second-order sections ('sos') + matrix is ``2*N``, with `N` the number of biquad sections + of the desired system. + Wn : array_like + The critical frequency or frequencies. For lowpass and highpass + filters, Wn is a scalar; for bandpass and bandstop filters, + Wn is a length-2 sequence. + + For a Butterworth filter, this is the point at which the gain + drops to 1/sqrt(2) that of the passband (the "-3 dB point"). + + For digital filters, if `fs` is not specified, `Wn` units are + normalized from 0 to 1, where 1 is the Nyquist frequency (`Wn` is + thus in half cycles / sample and defined as 2*critical frequencies + / `fs`). If `fs` is specified, `Wn` is in the same units as `fs`. + + For analog filters, `Wn` is an angular frequency (e.g. rad/s). + btype : {'lowpass', 'highpass', 'bandpass', 'bandstop'}, optional + The type of filter. Default is 'lowpass'. + analog : bool, optional + When True, return an analog filter, otherwise a digital filter is + returned. + output : {'ba', 'zpk', 'sos'}, optional + Type of output: numerator/denominator ('ba'), pole-zero ('zpk'), or + second-order sections ('sos'). Default is 'ba' for backwards + compatibility, but 'sos' should be used for general-purpose filtering. + fs : float, optional + The sampling frequency of the digital system. + + .. versionadded:: 1.2.0 + + Returns + ------- + b, a : ndarray, ndarray + Numerator (`b`) and denominator (`a`) polynomials of the IIR filter. + Only returned if ``output='ba'``. + z, p, k : ndarray, ndarray, float + Zeros, poles, and system gain of the IIR filter transfer + function. Only returned if ``output='zpk'``. + + sos : ndarray + Second-order sections representation of the IIR filter. + Only returned if ``output='sos'``. + + See Also + -------- + buttord, buttap + + Notes + ----- + The Butterworth filter has maximally flat frequency response in the + passband. + + The ``'sos'`` output parameter was added in 0.16.0. + + If the transfer function form ``[b, a]`` is requested, numerical + problems can occur since the conversion between roots and + the polynomial coefficients is a numerically sensitive operation, + even for N >= 4. It is recommended to work with the SOS + representation. + + .. warning:: + Designing high-order and narrowband IIR filters in TF form can + result in unstable or incorrect filtering due to floating point + numerical precision issues. Consider inspecting output filter + characteristics `freqz` or designing the filters with second-order + sections via ``output='sos'``. + + The current behavior is for ``ndarray`` outputs to have 64 bit precision + (``float64`` or ``complex128``) regardless of the dtype of `Wn` but + outputs may respect the dtype of `Wn` in a future version. + + Examples + -------- + Design an analog filter and plot its frequency response, showing the + critical points: + + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + >>> import numpy as np + + >>> b, a = signal.butter(4, 100, 'low', analog=True) + >>> w, h = signal.freqs(b, a) + >>> plt.semilogx(w, 20 * np.log10(abs(h))) + >>> plt.title('Butterworth filter frequency response') + >>> plt.xlabel('Frequency [rad/s]') + >>> plt.ylabel('Amplitude [dB]') + >>> plt.margins(0, 0.1) + >>> plt.grid(which='both', axis='both') + >>> plt.axvline(100, color='green') # cutoff frequency + >>> plt.show() + + Generate a signal made up of 10 Hz and 20 Hz, sampled at 1 kHz + + >>> t = np.linspace(0, 1, 1000, False) # 1 second + >>> sig = np.sin(2*np.pi*10*t) + np.sin(2*np.pi*20*t) + >>> fig, (ax1, ax2) = plt.subplots(2, 1, sharex=True) + >>> ax1.plot(t, sig) + >>> ax1.set_title('10 Hz and 20 Hz sinusoids') + >>> ax1.axis([0, 1, -2, 2]) + + Design a digital high-pass filter at 15 Hz to remove the 10 Hz tone, and + apply it to the signal. (It's recommended to use second-order sections + format when filtering, to avoid numerical error with transfer function + (``ba``) format): + + >>> sos = signal.butter(10, 15, 'hp', fs=1000, output='sos') + >>> filtered = signal.sosfilt(sos, sig) + >>> ax2.plot(t, filtered) + >>> ax2.set_title('After 15 Hz high-pass filter') + >>> ax2.axis([0, 1, -2, 2]) + >>> ax2.set_xlabel('Time [s]') + >>> plt.tight_layout() + >>> plt.show() + """ + return iirfilter(N, Wn, btype=btype, analog=analog, + output=output, ftype='butter', fs=fs) + + +def cheby1(N, rp, Wn, btype='low', analog=False, output='ba', fs=None): + """ + Chebyshev type I digital and analog filter design. + + Design an Nth-order digital or analog Chebyshev type I filter and + return the filter coefficients. + + Parameters + ---------- + N : int + The order of the filter. + rp : float + The maximum ripple allowed below unity gain in the passband. + Specified in decibels, as a positive number. + Wn : array_like + A scalar or length-2 sequence giving the critical frequencies. + For Type I filters, this is the point in the transition band at which + the gain first drops below -`rp`. + + For digital filters, `Wn` are in the same units as `fs`. By default, + `fs` is 2 half-cycles/sample, so these are normalized from 0 to 1, + where 1 is the Nyquist frequency. (`Wn` is thus in + half-cycles / sample.) + + For analog filters, `Wn` is an angular frequency (e.g., rad/s). + btype : {'lowpass', 'highpass', 'bandpass', 'bandstop'}, optional + The type of filter. Default is 'lowpass'. + analog : bool, optional + When True, return an analog filter, otherwise a digital filter is + returned. + output : {'ba', 'zpk', 'sos'}, optional + Type of output: numerator/denominator ('ba'), pole-zero ('zpk'), or + second-order sections ('sos'). Default is 'ba' for backwards + compatibility, but 'sos' should be used for general-purpose filtering. + fs : float, optional + The sampling frequency of the digital system. + + .. versionadded:: 1.2.0 + + Returns + ------- + b, a : ndarray, ndarray + Numerator (`b`) and denominator (`a`) polynomials of the IIR filter. + Only returned if ``output='ba'``. + z, p, k : ndarray, ndarray, float + Zeros, poles, and system gain of the IIR filter transfer + function. Only returned if ``output='zpk'``. + sos : ndarray + Second-order sections representation of the IIR filter. + Only returned if ``output='sos'``. + + See Also + -------- + cheb1ord, cheb1ap + + Notes + ----- + The Chebyshev type I filter maximizes the rate of cutoff between the + frequency response's passband and stopband, at the expense of ripple in + the passband and increased ringing in the step response. + + Type I filters roll off faster than Type II (`cheby2`), but Type II + filters do not have any ripple in the passband. + + The equiripple passband has N maxima or minima (for example, a + 5th-order filter has 3 maxima and 2 minima). Consequently, the DC gain is + unity for odd-order filters, or -rp dB for even-order filters. + + The ``'sos'`` output parameter was added in 0.16.0. + + The current behavior is for ``ndarray`` outputs to have 64 bit precision + (``float64`` or ``complex128``) regardless of the dtype of `Wn` but + outputs may respect the dtype of `Wn` in a future version. + + Examples + -------- + Design an analog filter and plot its frequency response, showing the + critical points: + + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + >>> import numpy as np + + >>> b, a = signal.cheby1(4, 5, 100, 'low', analog=True) + >>> w, h = signal.freqs(b, a) + >>> plt.semilogx(w, 20 * np.log10(abs(h))) + >>> plt.title('Chebyshev Type I frequency response (rp=5)') + >>> plt.xlabel('Frequency [rad/s]') + >>> plt.ylabel('Amplitude [dB]') + >>> plt.margins(0, 0.1) + >>> plt.grid(which='both', axis='both') + >>> plt.axvline(100, color='green') # cutoff frequency + >>> plt.axhline(-5, color='green') # rp + >>> plt.show() + + Generate a signal made up of 10 Hz and 20 Hz, sampled at 1 kHz + + >>> t = np.linspace(0, 1, 1000, False) # 1 second + >>> sig = np.sin(2*np.pi*10*t) + np.sin(2*np.pi*20*t) + >>> fig, (ax1, ax2) = plt.subplots(2, 1, sharex=True) + >>> ax1.plot(t, sig) + >>> ax1.set_title('10 Hz and 20 Hz sinusoids') + >>> ax1.axis([0, 1, -2, 2]) + + Design a digital high-pass filter at 15 Hz to remove the 10 Hz tone, and + apply it to the signal. (It's recommended to use second-order sections + format when filtering, to avoid numerical error with transfer function + (``ba``) format): + + >>> sos = signal.cheby1(10, 1, 15, 'hp', fs=1000, output='sos') + >>> filtered = signal.sosfilt(sos, sig) + >>> ax2.plot(t, filtered) + >>> ax2.set_title('After 15 Hz high-pass filter') + >>> ax2.axis([0, 1, -2, 2]) + >>> ax2.set_xlabel('Time [s]') + >>> plt.tight_layout() + >>> plt.show() + """ + return iirfilter(N, Wn, rp=rp, btype=btype, analog=analog, + output=output, ftype='cheby1', fs=fs) + + +def cheby2(N, rs, Wn, btype='low', analog=False, output='ba', fs=None): + """ + Chebyshev type II digital and analog filter design. + + Design an Nth-order digital or analog Chebyshev type II filter and + return the filter coefficients. + + Parameters + ---------- + N : int + The order of the filter. + rs : float + The minimum attenuation required in the stop band. + Specified in decibels, as a positive number. + Wn : array_like + A scalar or length-2 sequence giving the critical frequencies. + For Type II filters, this is the point in the transition band at which + the gain first reaches -`rs`. + + For digital filters, `Wn` are in the same units as `fs`. By default, + `fs` is 2 half-cycles/sample, so these are normalized from 0 to 1, + where 1 is the Nyquist frequency. (`Wn` is thus in + half-cycles / sample.) + + For analog filters, `Wn` is an angular frequency (e.g., rad/s). + btype : {'lowpass', 'highpass', 'bandpass', 'bandstop'}, optional + The type of filter. Default is 'lowpass'. + analog : bool, optional + When True, return an analog filter, otherwise a digital filter is + returned. + output : {'ba', 'zpk', 'sos'}, optional + Type of output: numerator/denominator ('ba'), pole-zero ('zpk'), or + second-order sections ('sos'). Default is 'ba' for backwards + compatibility, but 'sos' should be used for general-purpose filtering. + fs : float, optional + The sampling frequency of the digital system. + + .. versionadded:: 1.2.0 + + Returns + ------- + b, a : ndarray, ndarray + Numerator (`b`) and denominator (`a`) polynomials of the IIR filter. + Only returned if ``output='ba'``. + z, p, k : ndarray, ndarray, float + Zeros, poles, and system gain of the IIR filter transfer + function. Only returned if ``output='zpk'``. + sos : ndarray + Second-order sections representation of the IIR filter. + Only returned if ``output='sos'``. + + See Also + -------- + cheb2ord, cheb2ap + + Notes + ----- + The Chebyshev type II filter maximizes the rate of cutoff between the + frequency response's passband and stopband, at the expense of ripple in + the stopband and increased ringing in the step response. + + Type II filters do not roll off as fast as Type I (`cheby1`). + + The ``'sos'`` output parameter was added in 0.16.0. + + The current behavior is for ``ndarray`` outputs to have 64 bit precision + (``float64`` or ``complex128``) regardless of the dtype of `Wn` but + outputs may respect the dtype of `Wn` in a future version. + + Examples + -------- + Design an analog filter and plot its frequency response, showing the + critical points: + + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + >>> import numpy as np + + >>> b, a = signal.cheby2(4, 40, 100, 'low', analog=True) + >>> w, h = signal.freqs(b, a) + >>> plt.semilogx(w, 20 * np.log10(abs(h))) + >>> plt.title('Chebyshev Type II frequency response (rs=40)') + >>> plt.xlabel('Frequency [rad/s]') + >>> plt.ylabel('Amplitude [dB]') + >>> plt.margins(0, 0.1) + >>> plt.grid(which='both', axis='both') + >>> plt.axvline(100, color='green') # cutoff frequency + >>> plt.axhline(-40, color='green') # rs + >>> plt.show() + + Generate a signal made up of 10 Hz and 20 Hz, sampled at 1 kHz + + >>> t = np.linspace(0, 1, 1000, False) # 1 second + >>> sig = np.sin(2*np.pi*10*t) + np.sin(2*np.pi*20*t) + >>> fig, (ax1, ax2) = plt.subplots(2, 1, sharex=True) + >>> ax1.plot(t, sig) + >>> ax1.set_title('10 Hz and 20 Hz sinusoids') + >>> ax1.axis([0, 1, -2, 2]) + + Design a digital high-pass filter at 17 Hz to remove the 10 Hz tone, and + apply it to the signal. (It's recommended to use second-order sections + format when filtering, to avoid numerical error with transfer function + (``ba``) format): + + >>> sos = signal.cheby2(12, 20, 17, 'hp', fs=1000, output='sos') + >>> filtered = signal.sosfilt(sos, sig) + >>> ax2.plot(t, filtered) + >>> ax2.set_title('After 17 Hz high-pass filter') + >>> ax2.axis([0, 1, -2, 2]) + >>> ax2.set_xlabel('Time [s]') + >>> plt.show() + """ + return iirfilter(N, Wn, rs=rs, btype=btype, analog=analog, + output=output, ftype='cheby2', fs=fs) + + +def ellip(N, rp, rs, Wn, btype='low', analog=False, output='ba', fs=None): + """ + Elliptic (Cauer) digital and analog filter design. + + Design an Nth-order digital or analog elliptic filter and return + the filter coefficients. + + Parameters + ---------- + N : int + The order of the filter. + rp : float + The maximum ripple allowed below unity gain in the passband. + Specified in decibels, as a positive number. + rs : float + The minimum attenuation required in the stop band. + Specified in decibels, as a positive number. + Wn : array_like + A scalar or length-2 sequence giving the critical frequencies. + For elliptic filters, this is the point in the transition band at + which the gain first drops below -`rp`. + + For digital filters, `Wn` are in the same units as `fs`. By default, + `fs` is 2 half-cycles/sample, so these are normalized from 0 to 1, + where 1 is the Nyquist frequency. (`Wn` is thus in + half-cycles / sample.) + + For analog filters, `Wn` is an angular frequency (e.g., rad/s). + btype : {'lowpass', 'highpass', 'bandpass', 'bandstop'}, optional + The type of filter. Default is 'lowpass'. + analog : bool, optional + When True, return an analog filter, otherwise a digital filter is + returned. + output : {'ba', 'zpk', 'sos'}, optional + Type of output: numerator/denominator ('ba'), pole-zero ('zpk'), or + second-order sections ('sos'). Default is 'ba' for backwards + compatibility, but 'sos' should be used for general-purpose filtering. + fs : float, optional + The sampling frequency of the digital system. + + .. versionadded:: 1.2.0 + + Returns + ------- + b, a : ndarray, ndarray + Numerator (`b`) and denominator (`a`) polynomials of the IIR filter. + Only returned if ``output='ba'``. + z, p, k : ndarray, ndarray, float + Zeros, poles, and system gain of the IIR filter transfer + function. Only returned if ``output='zpk'``. + sos : ndarray + Second-order sections representation of the IIR filter. + Only returned if ``output='sos'``. + + See Also + -------- + ellipord, ellipap + + Notes + ----- + Also known as Cauer or Zolotarev filters, the elliptical filter maximizes + the rate of transition between the frequency response's passband and + stopband, at the expense of ripple in both, and increased ringing in the + step response. + + As `rp` approaches 0, the elliptical filter becomes a Chebyshev + type II filter (`cheby2`). As `rs` approaches 0, it becomes a Chebyshev + type I filter (`cheby1`). As both approach 0, it becomes a Butterworth + filter (`butter`). + + The equiripple passband has N maxima or minima (for example, a + 5th-order filter has 3 maxima and 2 minima). Consequently, the DC gain is + unity for odd-order filters, or -rp dB for even-order filters. + + The ``'sos'`` output parameter was added in 0.16.0. + + The current behavior is for ``ndarray`` outputs to have 64 bit precision + (``float64`` or ``complex128``) regardless of the dtype of `Wn` but + outputs may respect the dtype of `Wn` in a future version. + + Examples + -------- + Design an analog filter and plot its frequency response, showing the + critical points: + + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + >>> import numpy as np + + >>> b, a = signal.ellip(4, 5, 40, 100, 'low', analog=True) + >>> w, h = signal.freqs(b, a) + >>> plt.semilogx(w, 20 * np.log10(abs(h))) + >>> plt.title('Elliptic filter frequency response (rp=5, rs=40)') + >>> plt.xlabel('Frequency [rad/s]') + >>> plt.ylabel('Amplitude [dB]') + >>> plt.margins(0, 0.1) + >>> plt.grid(which='both', axis='both') + >>> plt.axvline(100, color='green') # cutoff frequency + >>> plt.axhline(-40, color='green') # rs + >>> plt.axhline(-5, color='green') # rp + >>> plt.show() + + Generate a signal made up of 10 Hz and 20 Hz, sampled at 1 kHz + + >>> t = np.linspace(0, 1, 1000, False) # 1 second + >>> sig = np.sin(2*np.pi*10*t) + np.sin(2*np.pi*20*t) + >>> fig, (ax1, ax2) = plt.subplots(2, 1, sharex=True) + >>> ax1.plot(t, sig) + >>> ax1.set_title('10 Hz and 20 Hz sinusoids') + >>> ax1.axis([0, 1, -2, 2]) + + Design a digital high-pass filter at 17 Hz to remove the 10 Hz tone, and + apply it to the signal. (It's recommended to use second-order sections + format when filtering, to avoid numerical error with transfer function + (``ba``) format): + + >>> sos = signal.ellip(8, 1, 100, 17, 'hp', fs=1000, output='sos') + >>> filtered = signal.sosfilt(sos, sig) + >>> ax2.plot(t, filtered) + >>> ax2.set_title('After 17 Hz high-pass filter') + >>> ax2.axis([0, 1, -2, 2]) + >>> ax2.set_xlabel('Time [s]') + >>> plt.tight_layout() + >>> plt.show() + """ + return iirfilter(N, Wn, rs=rs, rp=rp, btype=btype, analog=analog, + output=output, ftype='elliptic', fs=fs) + + +def bessel(N, Wn, btype='low', analog=False, output='ba', norm='phase', + fs=None): + """ + Bessel/Thomson digital and analog filter design. + + Design an Nth-order digital or analog Bessel filter and return the + filter coefficients. + + Parameters + ---------- + N : int + The order of the filter. + Wn : array_like + A scalar or length-2 sequence giving the critical frequencies (defined + by the `norm` parameter). + For analog filters, `Wn` is an angular frequency (e.g., rad/s). + + For digital filters, `Wn` are in the same units as `fs`. By default, + `fs` is 2 half-cycles/sample, so these are normalized from 0 to 1, + where 1 is the Nyquist frequency. (`Wn` is thus in + half-cycles / sample.) + btype : {'lowpass', 'highpass', 'bandpass', 'bandstop'}, optional + The type of filter. Default is 'lowpass'. + analog : bool, optional + When True, return an analog filter, otherwise a digital filter is + returned. (See Notes.) + output : {'ba', 'zpk', 'sos'}, optional + Type of output: numerator/denominator ('ba'), pole-zero ('zpk'), or + second-order sections ('sos'). Default is 'ba'. + norm : {'phase', 'delay', 'mag'}, optional + Critical frequency normalization: + + ``phase`` + The filter is normalized such that the phase response reaches its + midpoint at angular (e.g. rad/s) frequency `Wn`. This happens for + both low-pass and high-pass filters, so this is the + "phase-matched" case. + + The magnitude response asymptotes are the same as a Butterworth + filter of the same order with a cutoff of `Wn`. + + This is the default, and matches MATLAB's implementation. + + ``delay`` + The filter is normalized such that the group delay in the passband + is 1/`Wn` (e.g., seconds). This is the "natural" type obtained by + solving Bessel polynomials. + + ``mag`` + The filter is normalized such that the gain magnitude is -3 dB at + angular frequency `Wn`. + + .. versionadded:: 0.18.0 + fs : float, optional + The sampling frequency of the digital system. + + .. versionadded:: 1.2.0 + + Returns + ------- + b, a : ndarray, ndarray + Numerator (`b`) and denominator (`a`) polynomials of the IIR filter. + Only returned if ``output='ba'``. + z, p, k : ndarray, ndarray, float + Zeros, poles, and system gain of the IIR filter transfer + function. Only returned if ``output='zpk'``. + sos : ndarray + Second-order sections representation of the IIR filter. + Only returned if ``output='sos'``. + + Notes + ----- + Also known as a Thomson filter, the analog Bessel filter has maximally + flat group delay and maximally linear phase response, with very little + ringing in the step response. [1]_ + + The Bessel is inherently an analog filter. This function generates digital + Bessel filters using the bilinear transform, which does not preserve the + phase response of the analog filter. As such, it is only approximately + correct at frequencies below about fs/4. To get maximally-flat group + delay at higher frequencies, the analog Bessel filter must be transformed + using phase-preserving techniques. + + See `besselap` for implementation details and references. + + The ``'sos'`` output parameter was added in 0.16.0. + + The current behavior is for ``ndarray`` outputs to have 64 bit precision + (``float64`` or ``complex128``) regardless of the dtype of `Wn` but + outputs may respect the dtype of `Wn` in a future version. + + References + ---------- + .. [1] Thomson, W.E., "Delay Networks having Maximally Flat Frequency + Characteristics", Proceedings of the Institution of Electrical + Engineers, Part III, November 1949, Vol. 96, No. 44, pp. 487-490. + + Examples + -------- + Plot the phase-normalized frequency response, showing the relationship + to the Butterworth's cutoff frequency (green): + + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + >>> import numpy as np + + >>> b, a = signal.butter(4, 100, 'low', analog=True) + >>> w, h = signal.freqs(b, a) + >>> plt.semilogx(w, 20 * np.log10(np.abs(h)), color='silver', ls='dashed') + >>> b, a = signal.bessel(4, 100, 'low', analog=True, norm='phase') + >>> w, h = signal.freqs(b, a) + >>> plt.semilogx(w, 20 * np.log10(np.abs(h))) + >>> plt.title('Bessel filter magnitude response (with Butterworth)') + >>> plt.xlabel('Frequency [rad/s]') + >>> plt.ylabel('Amplitude [dB]') + >>> plt.margins(0, 0.1) + >>> plt.grid(which='both', axis='both') + >>> plt.axvline(100, color='green') # cutoff frequency + >>> plt.show() + + and the phase midpoint: + + >>> plt.figure() + >>> plt.semilogx(w, np.unwrap(np.angle(h))) + >>> plt.axvline(100, color='green') # cutoff frequency + >>> plt.axhline(-np.pi, color='red') # phase midpoint + >>> plt.title('Bessel filter phase response') + >>> plt.xlabel('Frequency [rad/s]') + >>> plt.ylabel('Phase [rad]') + >>> plt.margins(0, 0.1) + >>> plt.grid(which='both', axis='both') + >>> plt.show() + + Plot the magnitude-normalized frequency response, showing the -3 dB cutoff: + + >>> b, a = signal.bessel(3, 10, 'low', analog=True, norm='mag') + >>> w, h = signal.freqs(b, a) + >>> plt.semilogx(w, 20 * np.log10(np.abs(h))) + >>> plt.axhline(-3, color='red') # -3 dB magnitude + >>> plt.axvline(10, color='green') # cutoff frequency + >>> plt.title('Amplitude-normalized Bessel filter frequency response') + >>> plt.xlabel('Frequency [rad/s]') + >>> plt.ylabel('Amplitude [dB]') + >>> plt.margins(0, 0.1) + >>> plt.grid(which='both', axis='both') + >>> plt.show() + + Plot the delay-normalized filter, showing the maximally-flat group delay + at 0.1 seconds: + + >>> b, a = signal.bessel(5, 1/0.1, 'low', analog=True, norm='delay') + >>> w, h = signal.freqs(b, a) + >>> plt.figure() + >>> plt.semilogx(w[1:], -np.diff(np.unwrap(np.angle(h)))/np.diff(w)) + >>> plt.axhline(0.1, color='red') # 0.1 seconds group delay + >>> plt.title('Bessel filter group delay') + >>> plt.xlabel('Frequency [rad/s]') + >>> plt.ylabel('Group delay [s]') + >>> plt.margins(0, 0.1) + >>> plt.grid(which='both', axis='both') + >>> plt.show() + + """ + return iirfilter(N, Wn, btype=btype, analog=analog, + output=output, ftype='bessel_'+norm, fs=fs) + + +def maxflat(): + pass + + +def yulewalk(): + pass + + +def band_stop_obj(wp, ind, passb, stopb, gpass, gstop, type): + """ + Band Stop Objective Function for order minimization. + + Returns the non-integer order for an analog band stop filter. + + Parameters + ---------- + wp : scalar + Edge of passband `passb`. + ind : int, {0, 1} + Index specifying which `passb` edge to vary (0 or 1). + passb : ndarray + Two element sequence of fixed passband edges. + stopb : ndarray + Two element sequence of fixed stopband edges. + gpass : float + Amount of ripple in the passband in dB. + gstop : float + Amount of attenuation in stopband in dB. + type : {'butter', 'cheby', 'ellip'} + Type of filter. + + Returns + ------- + n : scalar + Filter order (possibly non-integer). + + Notes + ----- + Band-stop filters are used in applications where certain frequency + components need to be blocked while others are allowed; for instance, + removing noise at specific frequencies while allowing the desired signal + to pass through. The order of a filter often determines its complexity and + accuracy. Determining the right order can be a challenge. This function + aims to provide an appropriate order for an analog band stop filter. + + Examples + -------- + + >>> import numpy as np + >>> from scipy.signal import band_stop_obj + >>> wp = 2 + >>> ind = 1 + >>> passb = np.array([1, 3]) + >>> stopb = np.array([0.5, 4]) + >>> gstop = 30 + >>> gpass = 3 + >>> filter_type = 'butter' + >>> band_stop_obj(wp, ind, passb, stopb, gpass, gstop, filter_type) + np.float64(-2.758504160760643) + + """ + + _validate_gpass_gstop(gpass, gstop) + + passbC = passb.copy() + passbC[ind] = wp + nat = (stopb * (passbC[0] - passbC[1]) / + (stopb ** 2 - passbC[0] * passbC[1])) + nat = min(abs(nat)) + + if type == 'butter': + GSTOP = 10 ** (0.1 * abs(gstop)) + GPASS = 10 ** (0.1 * abs(gpass)) + n = (np.log10((GSTOP - 1.0) / (GPASS - 1.0)) / (2 * np.log10(nat))) + elif type == 'cheby': + GSTOP = 10 ** (0.1 * abs(gstop)) + GPASS = 10 ** (0.1 * abs(gpass)) + n = np.arccosh(np.sqrt((GSTOP - 1.0) / (GPASS - 1.0))) / np.arccosh(nat) + elif type == 'ellip': + GSTOP = 10 ** (0.1 * gstop) + GPASS = 10 ** (0.1 * gpass) + arg1 = np.sqrt((GPASS - 1.0) / (GSTOP - 1.0)) + arg0 = 1.0 / nat + d0 = special.ellipk([arg0 ** 2, 1 - arg0 ** 2]) + d1 = special.ellipk([arg1 ** 2, 1 - arg1 ** 2]) + n = (d0[0] * d1[1] / (d0[1] * d1[0])) + else: + raise ValueError(f"Incorrect type: {type}") + return n + + +def _pre_warp(wp, ws, analog, *, xp): + # Pre-warp frequencies for digital filter design + if not analog: + passb = xp.tan(xp.pi * wp / 2.0) + stopb = xp.tan(xp.pi * ws / 2.0) + else: + passb, stopb = wp, ws + return passb, stopb + + +def _validate_wp_ws(wp, ws, fs, analog, *, xp): + wp = xpx.atleast_nd(wp, ndim=1, xp=xp) + ws = xpx.atleast_nd(ws, ndim=1, xp=xp) + wp, ws = xp_promote(wp, ws, force_floating=True, xp=xp) + + if fs is not None: + if analog: + raise ValueError("fs cannot be specified for an analog filter") + wp = 2 * wp / fs + ws = 2 * ws / fs + + filter_type = 2 * (wp.shape[0] - 1) + 1 + if wp[0] >= ws[0]: + filter_type += 1 + + return wp, ws, filter_type + + +def _find_nat_freq(stopb, passb, gpass, gstop, filter_type, filter_kind, *, xp): + if filter_type == 1: # low + nat = stopb / passb + elif filter_type == 2: # high + nat = passb / stopb + elif filter_type == 3: # stop + + passb, stopb = np.asarray(passb), np.asarray(stopb) # XXX fminbound array API + wp0 = optimize.fminbound(band_stop_obj, passb[0], stopb[0] - 1e-12, + args=(0, passb, stopb, gpass, gstop, + filter_kind), + disp=0) + wp1 = optimize.fminbound(band_stop_obj, stopb[1] + 1e-12, passb[1], + args=(1, passb, stopb, gpass, gstop, + filter_kind), + disp=0) + passb = [float(wp0), float(wp1)] + passb, stopb = xp.asarray(passb), xp.asarray(stopb) + nat = ((stopb * (passb[0] - passb[1])) / + (stopb ** 2 - passb[0] * passb[1])) + elif filter_type == 4: # pass + nat = ((stopb ** 2 - passb[0] * passb[1]) / + (stopb * (passb[0] - passb[1]))) + else: + raise ValueError(f"should not happen: {filter_type =}.") + + nat = xp.min(xp.abs(nat)) + return nat, passb + + +def _postprocess_wn(WN, analog, fs, *, xp): + wn = WN if analog else xp.atan(WN) * 2.0 / xp.pi + if wn.shape[0] == 1: + wn = wn[0] + if fs is not None: + wn = wn * fs / 2 + return wn + + +def buttord(wp, ws, gpass, gstop, analog=False, fs=None): + """Butterworth filter order selection. + + Return the order of the lowest order digital or analog Butterworth filter + that loses no more than `gpass` dB in the passband and has at least + `gstop` dB attenuation in the stopband. + + Parameters + ---------- + wp, ws : float or array-like + Passband and stopband edge frequencies. + + For digital filters, these are in the same units as `fs`. By default, + `fs` is 2 half-cycles/sample, so these are normalized from 0 to 1, + where 1 is the Nyquist frequency. (`wp` and `ws` are thus in + half-cycles / sample.) For example: + + - Lowpass: wp = 0.2, ws = 0.3 + - Highpass: wp = 0.3, ws = 0.2 + - Bandpass: wp = [0.2, 0.5], ws = [0.1, 0.6] + - Bandstop: wp = [0.1, 0.6], ws = [0.2, 0.5] + + For analog filters, `wp` and `ws` are angular frequencies (e.g., rad/s). + gpass : float + The maximum loss in the passband (dB). + gstop : float + The minimum attenuation in the stopband (dB). + analog : bool, optional + When True, return an analog filter, otherwise a digital filter is + returned. + fs : float, optional + The sampling frequency of the digital system. + + .. versionadded:: 1.2.0 + + Returns + ------- + ord : int + The lowest order for a Butterworth filter which meets specs. + wn : ndarray or float + The Butterworth natural frequency (i.e. the "3dB frequency"). Should + be used with `butter` to give filter results. If `fs` is specified, + this is in the same units, and `fs` must also be passed to `butter`. + + See Also + -------- + butter : Filter design using order and critical points + cheb1ord : Find order and critical points from passband and stopband spec + cheb2ord, ellipord + iirfilter : General filter design using order and critical frequencies + iirdesign : General filter design using passband and stopband spec + + Examples + -------- + Design an analog bandpass filter with passband within 3 dB from 20 to + 50 rad/s, while rejecting at least -40 dB below 14 and above 60 rad/s. + Plot its frequency response, showing the passband and stopband + constraints in gray. + + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + >>> import numpy as np + + >>> N, Wn = signal.buttord([20, 50], [14, 60], 3, 40, True) + >>> b, a = signal.butter(N, Wn, 'band', True) + >>> w, h = signal.freqs(b, a, np.logspace(1, 2, 500)) + >>> plt.semilogx(w, 20 * np.log10(abs(h))) + >>> plt.title('Butterworth bandpass filter fit to constraints') + >>> plt.xlabel('Frequency [rad/s]') + >>> plt.ylabel('Amplitude [dB]') + >>> plt.grid(which='both', axis='both') + >>> plt.fill([1, 14, 14, 1], [-40, -40, 99, 99], '0.9', lw=0) # stop + >>> plt.fill([20, 20, 50, 50], [-99, -3, -3, -99], '0.9', lw=0) # pass + >>> plt.fill([60, 60, 1e9, 1e9], [99, -40, -40, 99], '0.9', lw=0) # stop + >>> plt.axis([10, 100, -60, 3]) + >>> plt.show() + + """ + xp = array_namespace(wp, ws) + wp, ws = map(xp.asarray, (wp, ws)) + + _validate_gpass_gstop(gpass, gstop) + fs = _validate_fs(fs, allow_none=True) + wp, ws, filter_type = _validate_wp_ws(wp, ws, fs, analog, xp=xp) + passb, stopb = _pre_warp(wp, ws, analog, xp=xp) + nat, passb = _find_nat_freq( + stopb, passb, gpass, gstop, filter_type, 'butter', xp=xp + ) + + GSTOP = 10 ** (0.1 * builtins.abs(gstop)) + GPASS = 10 ** (0.1 * builtins.abs(gpass)) + ord = int( + math.ceil(math.log10((GSTOP - 1.0) / (GPASS - 1.0)) / (2 * math.log10(nat))) + ) + + # Find the Butterworth natural frequency WN (or the "3dB" frequency") + # to give exactly gpass at passb. + try: + W0 = (GPASS - 1.0) ** (-1.0 / (2.0 * ord)) + except ZeroDivisionError: + W0 = 1.0 + warnings.warn("Order is zero...check input parameters.", + RuntimeWarning, stacklevel=2) + + # now convert this frequency back from lowpass prototype + # to the original analog filter + + if filter_type == 1: # low + WN = W0 * passb + elif filter_type == 2: # high + WN = passb / W0 + elif filter_type == 3: # stop + discr = xp.sqrt((passb[1] - passb[0]) ** 2 + + 4 * W0 ** 2 * passb[0] * passb[1]) + WN0 = ((passb[1] - passb[0]) + discr) / (2 * W0) + WN1 = ((passb[1] - passb[0]) - discr) / (2 * W0) + WN = xp.asarray([float(WN0), float(WN1)]) + WN = xp.sort(xp.abs(WN)) + + elif filter_type == 4: # pass + W0 = xp.asarray([-W0, W0], dtype=xp.float64) + WN = (-W0 * (passb[1] - passb[0]) / 2.0 + + xp.sqrt(W0 ** 2 / 4.0 * (passb[1] - passb[0]) ** 2 + + passb[0] * passb[1])) + WN = xp.sort(xp.abs(WN)) + else: + raise ValueError(f"Bad type: {filter_type}") + + wn = _postprocess_wn(WN, analog, fs, xp=xp) + + return ord, wn + + +def cheb1ord(wp, ws, gpass, gstop, analog=False, fs=None): + """Chebyshev type I filter order selection. + + Return the order of the lowest order digital or analog Chebyshev Type I + filter that loses no more than `gpass` dB in the passband and has at + least `gstop` dB attenuation in the stopband. + + Parameters + ---------- + wp, ws : float + Passband and stopband edge frequencies. + + For digital filters, these are in the same units as `fs`. By default, + `fs` is 2 half-cycles/sample, so these are normalized from 0 to 1, + where 1 is the Nyquist frequency. (`wp` and `ws` are thus in + half-cycles / sample.) For example: + + - Lowpass: wp = 0.2, ws = 0.3 + - Highpass: wp = 0.3, ws = 0.2 + - Bandpass: wp = [0.2, 0.5], ws = [0.1, 0.6] + - Bandstop: wp = [0.1, 0.6], ws = [0.2, 0.5] + + For analog filters, `wp` and `ws` are angular frequencies (e.g., rad/s). + gpass : float + The maximum loss in the passband (dB). + gstop : float + The minimum attenuation in the stopband (dB). + analog : bool, optional + When True, return an analog filter, otherwise a digital filter is + returned. + fs : float, optional + The sampling frequency of the digital system. + + .. versionadded:: 1.2.0 + + Returns + ------- + ord : int + The lowest order for a Chebyshev type I filter that meets specs. + wn : ndarray or float + The Chebyshev natural frequency (the "3dB frequency") for use with + `cheby1` to give filter results. If `fs` is specified, + this is in the same units, and `fs` must also be passed to `cheby1`. + + See Also + -------- + cheby1 : Filter design using order and critical points + buttord : Find order and critical points from passband and stopband spec + cheb2ord, ellipord + iirfilter : General filter design using order and critical frequencies + iirdesign : General filter design using passband and stopband spec + + Examples + -------- + Design a digital lowpass filter such that the passband is within 3 dB up + to 0.2*(fs/2), while rejecting at least -40 dB above 0.3*(fs/2). Plot its + frequency response, showing the passband and stopband constraints in gray. + + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + >>> import numpy as np + + >>> N, Wn = signal.cheb1ord(0.2, 0.3, 3, 40) + >>> b, a = signal.cheby1(N, 3, Wn, 'low') + >>> w, h = signal.freqz(b, a) + >>> plt.semilogx(w / np.pi, 20 * np.log10(abs(h))) + >>> plt.title('Chebyshev I lowpass filter fit to constraints') + >>> plt.xlabel('Normalized frequency') + >>> plt.ylabel('Amplitude [dB]') + >>> plt.grid(which='both', axis='both') + >>> plt.fill([.01, 0.2, 0.2, .01], [-3, -3, -99, -99], '0.9', lw=0) # stop + >>> plt.fill([0.3, 0.3, 2, 2], [ 9, -40, -40, 9], '0.9', lw=0) # pass + >>> plt.axis([0.08, 1, -60, 3]) + >>> plt.show() + + """ + xp = array_namespace(wp, ws) + wp, ws = map(xp.asarray, (wp, ws)) + + fs = _validate_fs(fs, allow_none=True) + _validate_gpass_gstop(gpass, gstop) + wp, ws, filter_type = _validate_wp_ws(wp, ws, fs, analog, xp=xp) + passb, stopb = _pre_warp(wp, ws, analog, xp=xp) + nat, passb = _find_nat_freq(stopb, passb, gpass, gstop, filter_type, 'cheby', xp=xp) + + GSTOP = 10 ** (0.1 * builtins.abs(gstop)) + GPASS = 10 ** (0.1 * builtins.abs(gpass)) + v_pass_stop = math.acosh(math.sqrt((GSTOP - 1.0) / (GPASS - 1.0))) + ord = int(xp.ceil(v_pass_stop / xp.acosh(nat))) + + # Natural frequencies are just the passband edges + wn = _postprocess_wn(passb, analog, fs, xp=xp) + + return ord, wn + + +def cheb2ord(wp, ws, gpass, gstop, analog=False, fs=None): + """Chebyshev type II filter order selection. + + Return the order of the lowest order digital or analog Chebyshev Type II + filter that loses no more than `gpass` dB in the passband and has at least + `gstop` dB attenuation in the stopband. + + Parameters + ---------- + wp, ws : float + Passband and stopband edge frequencies. + + For digital filters, these are in the same units as `fs`. By default, + `fs` is 2 half-cycles/sample, so these are normalized from 0 to 1, + where 1 is the Nyquist frequency. (`wp` and `ws` are thus in + half-cycles / sample.) For example: + + - Lowpass: wp = 0.2, ws = 0.3 + - Highpass: wp = 0.3, ws = 0.2 + - Bandpass: wp = [0.2, 0.5], ws = [0.1, 0.6] + - Bandstop: wp = [0.1, 0.6], ws = [0.2, 0.5] + + For analog filters, `wp` and `ws` are angular frequencies (e.g., rad/s). + gpass : float + The maximum loss in the passband (dB). + gstop : float + The minimum attenuation in the stopband (dB). + analog : bool, optional + When True, return an analog filter, otherwise a digital filter is + returned. + fs : float, optional + The sampling frequency of the digital system. + + .. versionadded:: 1.2.0 + + Returns + ------- + ord : int + The lowest order for a Chebyshev type II filter that meets specs. + wn : ndarray or float + The Chebyshev natural frequency (the "3dB frequency") for use with + `cheby2` to give filter results. If `fs` is specified, + this is in the same units, and `fs` must also be passed to `cheby2`. + + See Also + -------- + cheby2 : Filter design using order and critical points + buttord : Find order and critical points from passband and stopband spec + cheb1ord, ellipord + iirfilter : General filter design using order and critical frequencies + iirdesign : General filter design using passband and stopband spec + + Examples + -------- + Design a digital bandstop filter which rejects -60 dB from 0.2*(fs/2) to + 0.5*(fs/2), while staying within 3 dB below 0.1*(fs/2) or above + 0.6*(fs/2). Plot its frequency response, showing the passband and + stopband constraints in gray. + + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + >>> import numpy as np + + >>> N, Wn = signal.cheb2ord([0.1, 0.6], [0.2, 0.5], 3, 60) + >>> b, a = signal.cheby2(N, 60, Wn, 'stop') + >>> w, h = signal.freqz(b, a) + >>> plt.semilogx(w / np.pi, 20 * np.log10(abs(h))) + >>> plt.title('Chebyshev II bandstop filter fit to constraints') + >>> plt.xlabel('Normalized frequency') + >>> plt.ylabel('Amplitude [dB]') + >>> plt.grid(which='both', axis='both') + >>> plt.fill([.01, .1, .1, .01], [-3, -3, -99, -99], '0.9', lw=0) # stop + >>> plt.fill([.2, .2, .5, .5], [ 9, -60, -60, 9], '0.9', lw=0) # pass + >>> plt.fill([.6, .6, 2, 2], [-99, -3, -3, -99], '0.9', lw=0) # stop + >>> plt.axis([0.06, 1, -80, 3]) + >>> plt.show() + + """ + xp = array_namespace(wp, ws) + wp, ws = map(xp.asarray, (wp, ws)) + + fs = _validate_fs(fs, allow_none=True) + _validate_gpass_gstop(gpass, gstop) + wp, ws, filter_type = _validate_wp_ws(wp, ws, fs, analog, xp=xp) + passb, stopb = _pre_warp(wp, ws, analog, xp=xp) + nat, passb = _find_nat_freq(stopb, passb, gpass, gstop, filter_type, 'cheby', xp=xp) + + GSTOP = 10 ** (0.1 * builtins.abs(gstop)) + GPASS = 10 ** (0.1 * builtins.abs(gpass)) + v_pass_stop = math.acosh(math.sqrt((GSTOP - 1.0) / (GPASS - 1.0))) + ord = int(xp.ceil(v_pass_stop / xp.acosh(nat))) + + # Find frequency where analog response is -gpass dB. + # Then convert back from low-pass prototype to the original filter. + + new_freq = math.cosh(1.0 / ord * v_pass_stop) + new_freq = 1.0 / new_freq + + if filter_type == 1: + nat = passb / new_freq + elif filter_type == 2: + nat = passb * new_freq + elif filter_type == 3: + nat0 = (new_freq / 2.0 * (passb[0] - passb[1]) + + math.sqrt(new_freq ** 2 * (passb[1] - passb[0]) ** 2 / 4.0 + + passb[1] * passb[0])) + nat1 = passb[1] * passb[0] / nat0 + nat = xp.asarray([float(nat0), float(nat1)]) + elif filter_type == 4: + nat0 = (1.0 / (2.0 * new_freq) * (passb[0] - passb[1]) + + math.sqrt((passb[1] - passb[0]) ** 2 / (4.0 * new_freq ** 2) + + passb[1] * passb[0])) + nat1 = passb[0] * passb[1] / nat0 + nat = xp.asarray([float(nat0), float(nat1)]) + + wn = _postprocess_wn(nat, analog, fs, xp=xp) + + return ord, wn + + +_POW10_LOG10 = math.log(10) + + +def _pow10m1(x): + """10 ** x - 1 for x near 0""" + return math.expm1(_POW10_LOG10 * x) + + +def ellipord(wp, ws, gpass, gstop, analog=False, fs=None): + """Elliptic (Cauer) filter order selection. + + Return the order of the lowest order digital or analog elliptic filter + that loses no more than `gpass` dB in the passband and has at least + `gstop` dB attenuation in the stopband. + + Parameters + ---------- + wp, ws : float + Passband and stopband edge frequencies. + + For digital filters, these are in the same units as `fs`. By default, + `fs` is 2 half-cycles/sample, so these are normalized from 0 to 1, + where 1 is the Nyquist frequency. (`wp` and `ws` are thus in + half-cycles / sample.) For example: + + - Lowpass: wp = 0.2, ws = 0.3 + - Highpass: wp = 0.3, ws = 0.2 + - Bandpass: wp = [0.2, 0.5], ws = [0.1, 0.6] + - Bandstop: wp = [0.1, 0.6], ws = [0.2, 0.5] + + For analog filters, `wp` and `ws` are angular frequencies (e.g., rad/s). + gpass : float + The maximum loss in the passband (dB). + gstop : float + The minimum attenuation in the stopband (dB). + analog : bool, optional + When True, return an analog filter, otherwise a digital filter is + returned. + fs : float, optional + The sampling frequency of the digital system. + + .. versionadded:: 1.2.0 + + Returns + ------- + ord : int + The lowest order for an Elliptic (Cauer) filter that meets specs. + wn : ndarray or float + The Chebyshev natural frequency (the "3dB frequency") for use with + `ellip` to give filter results. If `fs` is specified, + this is in the same units, and `fs` must also be passed to `ellip`. + + See Also + -------- + ellip : Filter design using order and critical points + buttord : Find order and critical points from passband and stopband spec + cheb1ord, cheb2ord + iirfilter : General filter design using order and critical frequencies + iirdesign : General filter design using passband and stopband spec + + Examples + -------- + Design an analog highpass filter such that the passband is within 3 dB + above 30 rad/s, while rejecting -60 dB at 10 rad/s. Plot its + frequency response, showing the passband and stopband constraints in gray. + + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + >>> import numpy as np + + >>> N, Wn = signal.ellipord(30, 10, 3, 60, True) + >>> b, a = signal.ellip(N, 3, 60, Wn, 'high', True) + >>> w, h = signal.freqs(b, a, np.logspace(0, 3, 500)) + >>> plt.semilogx(w, 20 * np.log10(abs(h))) + >>> plt.title('Elliptical highpass filter fit to constraints') + >>> plt.xlabel('Frequency [rad/s]') + >>> plt.ylabel('Amplitude [dB]') + >>> plt.grid(which='both', axis='both') + >>> plt.fill([.1, 10, 10, .1], [1e4, 1e4, -60, -60], '0.9', lw=0) # stop + >>> plt.fill([30, 30, 1e9, 1e9], [-99, -3, -3, -99], '0.9', lw=0) # pass + >>> plt.axis([1, 300, -80, 3]) + >>> plt.show() + + """ + xp = array_namespace(wp, ws) + wp, ws = map(xp.asarray, (wp, ws)) + + fs = _validate_fs(fs, allow_none=True) + _validate_gpass_gstop(gpass, gstop) + wp, ws, filter_type = _validate_wp_ws(wp, ws, fs, analog, xp=xp) + passb, stopb = _pre_warp(wp, ws, analog, xp=xp) + nat, passb = _find_nat_freq(stopb, passb, gpass, gstop, filter_type, 'ellip', xp=xp) + + arg1_sq = _pow10m1(0.1 * gpass) / _pow10m1(0.1 * gstop) + arg0 = 1.0 / nat + arg0 = np.asarray(arg0) + d0 = special.ellipk(arg0 ** 2), special.ellipkm1(arg0 ** 2) + d1 = special.ellipk(arg1_sq), special.ellipkm1(arg1_sq) + ord = int(np.ceil(d0[0] * d1[1] / (d0[1] * d1[0]))) + + wn = _postprocess_wn(passb, analog, fs, xp=xp) + + return ord, wn + + +def buttap(N, *, xp=None, device=None): + """Return (z,p,k) for analog prototype of Nth-order Butterworth filter. + + The filter will have an angular (e.g., rad/s) cutoff frequency of 1. + + Parameters + ---------- + N : int + The order of the filter + %(xp_device_snippet)s + + Returns + ------- + z : ndarray[float64] + Zeros of the transfer function. Is always an empty array. + p : ndarray[complex128] + Poles of the transfer function. + k : float + Gain of the transfer function. + + See Also + -------- + butter : Filter design function using this prototype + + """ + if xp is None: + xp = np_compat + if abs(int(N)) != N: + raise ValueError("Filter order must be a nonnegative integer") + z = xp.asarray([], device=device, dtype=xp.float64) + m = xp.arange(-N+1, N, 2, device=device, dtype=xp.float64) + # Middle value is 0 to ensure an exactly real pole + p = -xp.exp(1j * xp.pi * m / (2 * N)) + k = 1.0 + return z, p, k + + +def cheb1ap(N, rp, *, xp=None, device=None): + """ + Return (z,p,k) for Nth-order Chebyshev type I analog lowpass filter. + + The returned filter prototype has `rp` decibels of ripple in the passband. + + The filter's angular (e.g. rad/s) cutoff frequency is normalized to 1, + defined as the point at which the gain first drops below ``-rp``. + + Parameters + ---------- + N : int + The order of the filter + rp : float + The ripple intensity + %(xp_device_snippet)s + + Returns + ------- + z : ndarray[float64] + Zeros of the transfer function. Is always an empty array. + p : ndarray[complex128] + Poles of the transfer function. + k : float + Gain of the transfer function. + + See Also + -------- + cheby1 : Filter design function using this prototype + + """ + if xp is None: + xp = np_compat + if abs(int(N)) != N: + raise ValueError("Filter order must be a nonnegative integer") + elif N == 0: + # Avoid divide-by-zero error + # Even order filters have DC gain of -rp dB + return ( + xp.asarray([], device=device, dtype=xp.float64), + xp.asarray([], device=device, dtype=xp.complex128), 10**(-rp/20) + ) + z = xp.asarray([], device=device, dtype=xp.float64) + + # Ripple factor (epsilon) + eps = math.sqrt(10 ** (0.1 * rp) - 1.0) + mu = 1.0 / N * math.asinh(1 / eps) + + # Arrange poles in an ellipse on the left half of the S-plane + m = xp.arange(-N+1, N, 2, dtype=xp.float64, device=device) + theta = xp.pi * m / (2*N) + p = -xp.sinh(mu + 1j*theta) + + k = xp.real(xp.prod(-p, axis=0)) + if N % 2 == 0: + k = k / math.sqrt(1 + eps * eps) + + return z, p, k + + +def cheb2ap(N, rs, *, xp=None, device=None): + """ + Return (z,p,k) for Nth-order Chebyshev type II analog lowpass filter. + + The returned filter prototype has attenuation of at least ``rs`` decibels + in the stopband. + + The filter's angular (e.g. rad/s) cutoff frequency is normalized to 1, + defined as the point at which the attenuation first reaches ``rs``. + + Parameters + ---------- + N : int + The order of the filter + rs : float + The attenuation in the stopband + %(xp_device_snippet)s + + Returns + ------- + z : ndarray[complex128] + Zeros of the transfer function. + p : ndarray[complex128] + Poles of the transfer function. + k : float + Gain of the transfer function. + + See Also + -------- + cheby2 : Filter design function using this prototype + + """ + if xp is None: + xp = np_compat + if abs(int(N)) != N: + raise ValueError("Filter order must be a nonnegative integer") + elif N == 0: + # Avoid divide-by-zero warning + return ( + xp.asarray([], device=device, dtype=xp.complex128), + xp.asarray([], device=device, dtype=xp.complex128), + 1.0 + ) + + # Ripple factor (epsilon) + de = 1.0 / math.sqrt(10 ** (0.1 * rs) - 1) + mu = math.asinh(1.0 / de) / N + + if N % 2: + m = xp.concat( + (xp.arange(-N + 1, 0, 2, dtype=xp.float64, device=device), + xp.arange(2, N, 2, dtype=xp.float64, device=device) + ) + ) + else: + m = xp.arange(-N+1, N, 2, dtype=xp.float64, device=device) + + z = 1j / xp.sin(m * xp.pi / (2 * N)) + + # Poles around the unit circle like Butterworth + m1 = xp.arange(-N+1, N, 2, dtype=xp.float64, device=device) + theta1 = xp.pi * m1 / (2 * N) + p = -1 / xp.sinh(mu + 1j*theta1) + + k = xp.real(xp.prod(-p, axis=0) / xp.prod(-z, axis=0)) + return z, p, k + + +EPSILON = 2e-16 + +# number of terms in solving degree equation +_ELLIPDEG_MMAX = 7 + + +def _ellipdeg(n, m1): + """Solve degree equation using nomes + + Given n, m1, solve + n * K(m) / K'(m) = K1(m1) / K1'(m1) + for m + + See [1], Eq. (49) + + References + ---------- + .. [1] Orfanidis, "Lecture Notes on Elliptic Filter Design", + https://www.ece.rutgers.edu/~orfanidi/ece521/notes.pdf + """ + K1 = special.ellipk(m1) + K1p = special.ellipkm1(m1) + + q1 = np.exp(-np.pi * K1p / K1) + q = q1 ** (1/n) + + mnum = np.arange(_ELLIPDEG_MMAX + 1) + mden = np.arange(1, _ELLIPDEG_MMAX + 2) + + num = np.sum(q ** (mnum * (mnum+1))) + den = 1 + 2 * np.sum(q ** (mden**2)) + + return 16 * q * (num / den) ** 4 + + +# Maximum number of iterations in Landen transformation recursion +# sequence. 10 is conservative; unit tests pass with 4, Orfanidis +# (see _arc_jac_cn [1]) suggests 5. +_ARC_JAC_SN_MAXITER = 10 + + +def _arc_jac_sn(w, m): + """Inverse Jacobian elliptic sn + + Solve for z in w = sn(z, m) + + Parameters + ---------- + w : complex scalar + argument + + m : scalar + modulus; in interval [0, 1] + + + See [1], Eq. (56) + + References + ---------- + .. [1] Orfanidis, "Lecture Notes on Elliptic Filter Design", + https://www.ece.rutgers.edu/~orfanidi/ece521/notes.pdf + + """ + + def _complement(kx): + # (1-k**2) ** 0.5; the expression below + # works for small kx + return ((1 - kx) * (1 + kx)) ** 0.5 + + k = m ** 0.5 + + if k > 1: + return np.nan + elif k == 1: + return np.arctanh(w) + + ks = [k] + niter = 0 + while ks[-1] != 0: + k_ = ks[-1] + k_p = _complement(k_) + ks.append((1 - k_p) / (1 + k_p)) + niter += 1 + if niter > _ARC_JAC_SN_MAXITER: + raise ValueError('Landen transformation not converging') + + K = np.prod(1 + np.array(ks[1:])) * np.pi/2 + + wns = [w] + + for kn, knext in zip(ks[:-1], ks[1:]): + wn = wns[-1] + wnext = (2 * wn / + ((1 + knext) * (1 + _complement(kn * wn)))) + wns.append(wnext) + + u = 2 / np.pi * np.arcsin(wns[-1]) + + z = K * u + return z + + +def _arc_jac_sc1(w, m): + """Real inverse Jacobian sc, with complementary modulus + + Solve for z in w = sc(z, 1-m) + + w - real scalar + + m - modulus + + From [1], sc(z, m) = -i * sn(i * z, 1 - m) + + References + ---------- + # noqa: E501 + .. [1] https://functions.wolfram.com/EllipticFunctions/JacobiSC/introductions/JacobiPQs/ShowAll.html, + "Representations through other Jacobi functions" + + """ + + zcomplex = _arc_jac_sn(1j * w, m) + if abs(zcomplex.real) > 1e-14: + raise ValueError + + return zcomplex.imag + + +def ellipap(N, rp, rs, *, xp=None, device=None): + """Return (z,p,k) of Nth-order elliptic analog lowpass filter. + + The filter is a normalized prototype that has `rp` decibels of ripple + in the passband and a stopband `rs` decibels down. + + The filter's angular (e.g., rad/s) cutoff frequency is normalized to 1, + defined as the point at which the gain first drops below ``-rp``. + + Parameters + ---------- + N : int + The order of the filter + rp : float + The passband ripple intensity + rs : float + The stopband attenuation + %(xp_device_snippet)s + + Returns + ------- + z : ndarray[complex128] + Zeros of the transfer function. + p : ndarray[complex128] + Poles of the transfer function. + k : float + Gain of the transfer function. + + See Also + -------- + ellip : Filter design function using this prototype + + References + ---------- + .. [1] Lutovac, Tosic, and Evans, "Filter Design for Signal Processing", + Chapters 5 and 12. + + .. [2] Orfanidis, "Lecture Notes on Elliptic Filter Design", + https://www.ece.rutgers.edu/~orfanidi/ece521/notes.pdf + + """ + if xp is None: + xp = np_compat + + if abs(int(N)) != N: + raise ValueError("Filter order must be a nonnegative integer") + elif N == 0: + # Avoid divide-by-zero warning + # Even order filters have DC gain of -rp dB + return ( + xp.asarray([], device=device, dtype=xp.complex128), + xp.asarray([], device=device, dtype=xp.complex128), + 10**(-rp/20) + ) + elif N == 1: + p = -math.sqrt(1.0 / _pow10m1(0.1 * rp)) + k = -p + z = [] + return ( + xp.asarray(z, device=device, dtype=xp.complex128), + xp.asarray(p, device=device, dtype=xp.complex128), k + ) + + eps_sq = _pow10m1(0.1 * rp) + + eps = math.sqrt(eps_sq) + ck1_sq = eps_sq / _pow10m1(0.1 * rs) + if ck1_sq == 0: + raise ValueError("Cannot design a filter with given rp and rs" + " specifications.") + + # do computations with numpy, xp.asarray the return values + + val = special.ellipk(ck1_sq), special.ellipkm1(ck1_sq) + + m = _ellipdeg(N, ck1_sq) + + capk = special.ellipk(m) + + j = np.arange(1 - N % 2, N, 2) + jj = len(j) + + [s, c, d, phi] = special.ellipj(j * capk / N, m * np.ones(jj)) + snew = np.compress(abs(s) > EPSILON, s, axis=-1) + z = 1.0 / (np.sqrt(m) * snew) + z = 1j * z + z = np.concatenate((z, np.conjugate(z))) + + r = _arc_jac_sc1(1. / eps, ck1_sq) + v0 = capk * r / (N * val[0]) + + [sv, cv, dv, phi] = special.ellipj(v0, 1 - m) + p = -(c * d * sv * cv + 1j * s * dv) / (1 - (d * sv) ** 2.0) + + if N % 2: + newp = np.compress( + abs(p.imag) > EPSILON * np.sqrt(np.sum(p * np.conjugate(p), axis=0).real), + p, axis=-1 + ) + p = np.concatenate((p, np.conjugate(newp))) + else: + p = np.concatenate((p, np.conjugate(p))) + + k = (np.prod(-p, axis=0) / np.prod(-z, axis=0)).real + if N % 2 == 0: + k = k / np.sqrt(1 + eps_sq) + + return ( + xp.asarray(z, device=device, dtype=xp.complex128), + xp.asarray(p, device=device, dtype=xp.complex128), float(k) + ) + + +# TODO: Make this a real public function scipy.misc.ff +def _falling_factorial(x, n): + r""" + Return the factorial of `x` to the `n` falling. + + This is defined as: + + .. math:: x^\underline n = (x)_n = x (x-1) \cdots (x-n+1) + + This can more efficiently calculate ratios of factorials, since: + + n!/m! == falling_factorial(n, n-m) + + where n >= m + + skipping the factors that cancel out + + the usual factorial n! == ff(n, n) + """ + val = 1 + for k in range(x - n + 1, x + 1): + val *= k + return val + + +def _bessel_poly(n, reverse=False): + """ + Return the coefficients of Bessel polynomial of degree `n` + + If `reverse` is true, a reverse Bessel polynomial is output. + + Output is a list of coefficients: + [1] = 1 + [1, 1] = 1*s + 1 + [1, 3, 3] = 1*s^2 + 3*s + 3 + [1, 6, 15, 15] = 1*s^3 + 6*s^2 + 15*s + 15 + [1, 10, 45, 105, 105] = 1*s^4 + 10*s^3 + 45*s^2 + 105*s + 105 + etc. + + Output is a Python list of arbitrary precision long ints, so n is only + limited by your hardware's memory. + + Sequence is http://oeis.org/A001498, and output can be confirmed to + match http://oeis.org/A001498/b001498.txt : + + .. code_block:: python + + from scipy.signal._filter_design import _bessel_poly + i = 0 + for n in range(51): + for x in _bessel_poly(n, reverse=True): + print(i, x) + i += 1 + + """ + if abs(int(n)) != n: + raise ValueError("Polynomial order must be a nonnegative integer") + else: + n = int(n) # np.int32 doesn't work, for instance + + out = [] + for k in range(n + 1): + num = _falling_factorial(2*n - k, n) + den = 2**(n - k) * math.factorial(k) + out.append(num // den) + + if reverse: + return out[::-1] + else: + return out + + +def _campos_zeros(n): + """ + Return approximate zero locations of Bessel polynomials y_n(x) for order + `n` using polynomial fit (Campos-Calderon 2011) + """ + if n == 1: + return np.asarray([-1+0j]) + + s = npp_polyval(n, [0, 0, 2, 0, -3, 1]) + b3 = npp_polyval(n, [16, -8]) / s + b2 = npp_polyval(n, [-24, -12, 12]) / s + b1 = npp_polyval(n, [8, 24, -12, -2]) / s + b0 = npp_polyval(n, [0, -6, 0, 5, -1]) / s + + r = npp_polyval(n, [0, 0, 2, 1]) + a1 = npp_polyval(n, [-6, -6]) / r + a2 = 6 / r + + k = np.arange(1, n+1) + x = npp_polyval(k, [0, a1, a2]) + y = npp_polyval(k, [b0, b1, b2, b3]) + + return x + 1j*y + + +def _aberth(f, fp, x0, tol=1e-15, maxiter=50): + """ + Given a function `f`, its first derivative `fp`, and a set of initial + guesses `x0`, simultaneously find the roots of the polynomial using the + Aberth-Ehrlich method. + + ``len(x0)`` should equal the number of roots of `f`. + + (This is not a complete implementation of Bini's algorithm.) + """ + + N = len(x0) + + x = np.array(x0, complex) + beta = np.empty_like(x0) + + for iteration in range(maxiter): + alpha = -f(x) / fp(x) # Newton's method + + # Model "repulsion" between zeros + for k in range(N): + beta[k] = np.sum(1/(x[k] - x[k+1:])) + beta[k] += np.sum(1/(x[k] - x[:k])) + + x += alpha / (1 + alpha * beta) + + if not all(np.isfinite(x)): + raise RuntimeError('Root-finding calculation failed') + + # Mekwi: The iterative process can be stopped when |hn| has become + # less than the largest error one is willing to permit in the root. + if all(abs(alpha) <= tol): + break + else: + raise Exception('Zeros failed to converge') + + return x + + +def _bessel_zeros(N): + """ + Find zeros of ordinary Bessel polynomial of order `N`, by root-finding of + modified Bessel function of the second kind + """ + if N == 0: + return np.asarray([]) + + # Generate starting points + x0 = _campos_zeros(N) + + # Zeros are the same for exp(1/x)*K_{N+0.5}(1/x) and Nth-order ordinary + # Bessel polynomial y_N(x) + def f(x): + return special.kve(N+0.5, 1/x) + + # First derivative of above + def fp(x): + return (special.kve(N-0.5, 1/x)/(2*x**2) - + special.kve(N+0.5, 1/x)/(x**2) + + special.kve(N+1.5, 1/x)/(2*x**2)) + + # Starting points converge to true zeros + x = _aberth(f, fp, x0) + + # Improve precision using Newton's method on each + for i in range(len(x)): + x[i] = optimize.newton(f, x[i], fp, tol=1e-15) + + # Average complex conjugates to make them exactly symmetrical + x = np.mean((x, x[::-1].conj()), 0) + + # Zeros should sum to -1 + if abs(np.sum(x) + 1) > 1e-15: + raise RuntimeError('Generated zeros are inaccurate') + + return x + + +def _norm_factor(p, k): + """ + Numerically find frequency shift to apply to delay-normalized filter such + that -3 dB point is at 1 rad/sec. + + `p` is an array_like of polynomial poles + `k` is a float gain + + First 10 values are listed in "Bessel Scale Factors" table, + "Bessel Filters Polynomials, Poles and Circuit Elements 2003, C. Bond." + """ + p = np.asarray(p, dtype=np.complex128) + + def G(w): + """ + Gain of filter + """ + return np.abs(k / np.prod(1j*w - p)) + + def cutoff(w): + """ + When gain = -3 dB, return 0 + """ + return G(w) - 1/math.sqrt(2) + + return optimize.newton(cutoff, 1.5) + + +def besselap(N, norm='phase', *, xp=None, device=None): + """ + Return (z,p,k) for analog prototype of an Nth-order Bessel filter. + + Parameters + ---------- + N : int + The order of the filter. + norm : {'phase', 'delay', 'mag'}, optional + Frequency normalization: + + ``phase`` + The filter is normalized such that the phase response reaches its + midpoint at an angular (e.g., rad/s) cutoff frequency of 1. This + happens for both low-pass and high-pass filters, so this is the + "phase-matched" case. [6]_ + + The magnitude response asymptotes are the same as a Butterworth + filter of the same order with a cutoff of `Wn`. + + This is the default, and matches MATLAB's implementation. + + ``delay`` + The filter is normalized such that the group delay in the passband + is 1 (e.g., 1 second). This is the "natural" type obtained by + solving Bessel polynomials + + ``mag`` + The filter is normalized such that the gain magnitude is -3 dB at + angular frequency 1. This is called "frequency normalization" by + Bond. [1]_ + + .. versionadded:: 0.18.0 + + %(xp_device_snippet)s + + Returns + ------- + z : ndarray[float64] + Zeros of the transfer function. Is always an empty array. + p : ndarray[complex128] + Poles of the transfer function. + k : float + Gain of the transfer function. For phase-normalized, this is always 1. + + See Also + -------- + bessel : Filter design function using this prototype + + Notes + ----- + To find the pole locations, approximate starting points are generated [2]_ + for the zeros of the ordinary Bessel polynomial [3]_, then the + Aberth-Ehrlich method [4]_ [5]_ is used on the Kv(x) Bessel function to + calculate more accurate zeros, and these locations are then inverted about + the unit circle. + + References + ---------- + .. [1] C.R. Bond, "Bessel Filter Constants", + http://www.crbond.com/papers/bsf.pdf + .. [2] Campos and Calderon, "Approximate closed-form formulas for the + zeros of the Bessel Polynomials", :arXiv:`1105.0957`. + .. [3] Thomson, W.E., "Delay Networks having Maximally Flat Frequency + Characteristics", Proceedings of the Institution of Electrical + Engineers, Part III, November 1949, Vol. 96, No. 44, pp. 487-490. + .. [4] Aberth, "Iteration Methods for Finding all Zeros of a Polynomial + Simultaneously", Mathematics of Computation, Vol. 27, No. 122, + April 1973 + .. [5] Ehrlich, "A modified Newton method for polynomials", Communications + of the ACM, Vol. 10, Issue 2, pp. 107-108, Feb. 1967, + :doi:`10.1145/363067.363115`. + .. [6] Miller and Bohn, "A Bessel Filter Crossover, and Its Relation to + Others", RaneNote 147, 1998, + https://www.ranecommercial.com/legacy/note147.html + + """ + if xp is None: + xp = np_compat + + if abs(int(N)) != N: + raise ValueError("Filter order must be a nonnegative integer") + + N = int(N) # calculation below doesn't always fit in np.int64 + if N == 0: + p = [] + k = 1 + else: + # Find roots of reverse Bessel polynomial + p = 1/_bessel_zeros(N) + + a_last = _falling_factorial(2*N, N) // 2**N + + # Shift them to a different normalization if required + if norm in ('delay', 'mag'): + # Normalized for group delay of 1 + k = a_last + if norm == 'mag': + # -3 dB magnitude point is at 1 rad/sec + norm_factor = _norm_factor(p, k) + p /= norm_factor + k = norm_factor**-N * a_last + elif norm == 'phase': + # Phase-matched (1/2 max phase shift at 1 rad/sec) + # Asymptotes are same as Butterworth filter + p *= 10**(-math.log10(a_last)/N) + k = 1 + else: + raise ValueError('normalization not understood') + + z = xp.asarray([], device=device, dtype=xp.float64) + return ( + z, + xp.asarray(p, device=device, dtype=xp.complex128), + float(k) + ) + + +def iirnotch(w0, Q, fs=2.0, *, xp=None, device=None): + """ + Design second-order IIR notch digital filter. + + A notch filter is a band-stop filter with a narrow bandwidth + (high quality factor). It rejects a narrow frequency band and + leaves the rest of the spectrum little changed. + + Parameters + ---------- + w0 : float + Frequency to remove from a signal. If `fs` is specified, this is in + the same units as `fs`. By default, it is a normalized scalar that must + satisfy ``0 < w0 < 1``, with ``w0 = 1`` corresponding to half of the + sampling frequency. + Q : float + Quality factor. Dimensionless parameter that characterizes + notch filter -3 dB bandwidth ``bw`` relative to its center + frequency, ``Q = w0/bw``. + fs : float, optional + The sampling frequency of the digital system. + + .. versionadded:: 1.2.0 + + %(xp_device_snippet)s + + Returns + ------- + b, a : ndarray, ndarray + Numerator (``b``) and denominator (``a``) polynomials + of the IIR filter. + + See Also + -------- + iirpeak + + Notes + ----- + .. versionadded:: 0.19.0 + + References + ---------- + .. [1] Sophocles J. Orfanidis, "Introduction To Signal Processing", + Prentice-Hall, 1996 + + Examples + -------- + Design and plot filter to remove the 60 Hz component from a + signal sampled at 200 Hz, using a quality factor Q = 30 + + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + >>> import numpy as np + + >>> fs = 200.0 # Sample frequency (Hz) + >>> f0 = 60.0 # Frequency to be removed from signal (Hz) + >>> Q = 30.0 # Quality factor + >>> # Design notch filter + >>> b, a = signal.iirnotch(f0, Q, fs) + + >>> # Frequency response + >>> freq, h = signal.freqz(b, a, fs=fs) + >>> # Plot + >>> fig, ax = plt.subplots(2, 1, figsize=(8, 6)) + >>> ax[0].plot(freq, 20*np.log10(abs(h)), color='blue') + >>> ax[0].set_title("Frequency Response") + >>> ax[0].set_ylabel("Amplitude [dB]", color='blue') + >>> ax[0].set_xlim([0, 100]) + >>> ax[0].set_ylim([-25, 10]) + >>> ax[0].grid(True) + >>> ax[1].plot(freq, np.unwrap(np.angle(h))*180/np.pi, color='green') + >>> ax[1].set_ylabel("Phase [deg]", color='green') + >>> ax[1].set_xlabel("Frequency [Hz]") + >>> ax[1].set_xlim([0, 100]) + >>> ax[1].set_yticks([-90, -60, -30, 0, 30, 60, 90]) + >>> ax[1].set_ylim([-90, 90]) + >>> ax[1].grid(True) + >>> plt.show() + """ + + return _design_notch_peak_filter(w0, Q, "notch", fs, xp=xp, device=device) + + +def iirpeak(w0, Q, fs=2.0, *, xp=None, device=None): + """ + Design second-order IIR peak (resonant) digital filter. + + A peak filter is a band-pass filter with a narrow bandwidth + (high quality factor). It rejects components outside a narrow + frequency band. + + Parameters + ---------- + w0 : float + Frequency to be retained in a signal. If `fs` is specified, this is in + the same units as `fs`. By default, it is a normalized scalar that must + satisfy ``0 < w0 < 1``, with ``w0 = 1`` corresponding to half of the + sampling frequency. + Q : float + Quality factor. Dimensionless parameter that characterizes + peak filter -3 dB bandwidth ``bw`` relative to its center + frequency, ``Q = w0/bw``. + fs : float, optional + The sampling frequency of the digital system. + + .. versionadded:: 1.2.0 + + %(xp_device_snippet)s + + Returns + ------- + b, a : ndarray, ndarray + Numerator (``b``) and denominator (``a``) polynomials + of the IIR filter. + + See Also + -------- + iirnotch + + Notes + ----- + .. versionadded:: 0.19.0 + + References + ---------- + .. [1] Sophocles J. Orfanidis, "Introduction To Signal Processing", + Prentice-Hall, 1996 + + Examples + -------- + Design and plot filter to remove the frequencies other than the 300 Hz + component from a signal sampled at 1000 Hz, using a quality factor Q = 30 + + >>> import numpy as np + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + + >>> fs = 1000.0 # Sample frequency (Hz) + >>> f0 = 300.0 # Frequency to be retained (Hz) + >>> Q = 30.0 # Quality factor + >>> # Design peak filter + >>> b, a = signal.iirpeak(f0, Q, fs) + + >>> # Frequency response + >>> freq, h = signal.freqz(b, a, fs=fs) + >>> # Plot + >>> fig, ax = plt.subplots(2, 1, figsize=(8, 6)) + >>> ax[0].plot(freq, 20*np.log10(np.maximum(abs(h), 1e-5)), color='blue') + >>> ax[0].set_title("Frequency Response") + >>> ax[0].set_ylabel("Amplitude [dB]", color='blue') + >>> ax[0].set_xlim([0, 500]) + >>> ax[0].set_ylim([-50, 10]) + >>> ax[0].grid(True) + >>> ax[1].plot(freq, np.unwrap(np.angle(h))*180/np.pi, color='green') + >>> ax[1].set_ylabel("Phase [deg]", color='green') + >>> ax[1].set_xlabel("Frequency [Hz]") + >>> ax[1].set_xlim([0, 500]) + >>> ax[1].set_yticks([-90, -60, -30, 0, 30, 60, 90]) + >>> ax[1].set_ylim([-90, 90]) + >>> ax[1].grid(True) + >>> plt.show() + """ + + return _design_notch_peak_filter(w0, Q, "peak", fs, xp=xp, device=device) + + +def _design_notch_peak_filter(w0, Q, ftype, fs=2.0, *, xp=None, device=None): + """ + Design notch or peak digital filter. + + Parameters + ---------- + w0 : float + Normalized frequency to remove from a signal. If `fs` is specified, + this is in the same units as `fs`. By default, it is a normalized + scalar that must satisfy ``0 < w0 < 1``, with ``w0 = 1`` + corresponding to half of the sampling frequency. + Q : float + Quality factor. Dimensionless parameter that characterizes + notch filter -3 dB bandwidth ``bw`` relative to its center + frequency, ``Q = w0/bw``. + ftype : str + The type of IIR filter to design: + + - notch filter : ``notch`` + - peak filter : ``peak`` + fs : float, optional + The sampling frequency of the digital system. + + .. versionadded:: 1.2.0: + + Returns + ------- + b, a : ndarray, ndarray + Numerator (``b``) and denominator (``a``) polynomials + of the IIR filter. + """ + if xp is None: + xp = np_compat + + fs = _validate_fs(fs, allow_none=False) + + # Guarantee that the inputs are floats + w0 = float(w0) + Q = float(Q) + w0 = 2 * w0 / fs + + # Checks if w0 is within the range + if w0 > 1.0 or w0 < 0.0: + raise ValueError("w0 should be such that 0 < w0 < 1") + + # Get bandwidth + bw = w0/Q + + # Normalize inputs + bw = bw * xp.pi + w0 = w0 * xp.pi + + if ftype not in ("notch", "peak"): + raise ValueError("Unknown ftype.") + + # Compute beta according to Eqs. 11.3.4 (p.575) and 11.3.19 (p.579) from + # reference [1]. Due to assuming a -3 dB attenuation value, i.e, assuming + # gb = 1 / np.sqrt(2), the following terms simplify to: + # (np.sqrt(1.0 - gb**2.0) / gb) = 1 + # (gb / np.sqrt(1.0 - gb**2.0)) = 1 + beta = math.tan(bw / 2.0) + + # Compute gain: formula 11.3.6 (p.575) from reference [1] + gain = 1.0 / (1.0 + beta) + + # Compute numerator b and denominator a + # formulas 11.3.7 (p.575) and 11.3.21 (p.579) + # from reference [1] + if ftype == "notch": + b = gain * xp.asarray([1.0, -2.0*math.cos(w0), 1.0], device=device) + else: + b = (1.0 - gain) * xp.asarray([1.0, 0.0, -1.0], device=device) + a = xp.asarray([1.0, -2.0 * gain * math.cos(w0), (2.0*gain - 1.0)], device=device) + + return b, a + + +def iircomb(w0, Q, ftype='notch', fs=2.0, *, pass_zero=False, xp=None, device=None): + """ + Design IIR notching or peaking digital comb filter. + + A notching comb filter consists of regularly-spaced band-stop filters with + a narrow bandwidth (high quality factor). Each rejects a narrow frequency + band and leaves the rest of the spectrum little changed. + + A peaking comb filter consists of regularly-spaced band-pass filters with + a narrow bandwidth (high quality factor). Each rejects components outside + a narrow frequency band. + + Parameters + ---------- + w0 : float + The fundamental frequency of the comb filter (the spacing between its + peaks). This must evenly divide the sampling frequency. If `fs` is + specified, this is in the same units as `fs`. By default, it is + a normalized scalar that must satisfy ``0 < w0 < 1``, with + ``w0 = 1`` corresponding to half of the sampling frequency. + Q : float + Quality factor. Dimensionless parameter that characterizes + notch filter -3 dB bandwidth ``bw`` relative to its center + frequency, ``Q = w0/bw``. + ftype : {'notch', 'peak'} + The type of comb filter generated by the function. If 'notch', then + the Q factor applies to the notches. If 'peak', then the `Q` factor + applies to the peaks. Default is 'notch'. + fs : float, optional + The sampling frequency of the signal. Default is 2.0. + pass_zero : bool, optional + If False (default), the notches (nulls) of the filter are centered on + frequencies ``[0, w0, 2*w0, ...]``, and the peaks are centered on the + midpoints ``[w0/2, 3*w0/2, 5*w0/2, ...]``. If True, the peaks are centered + on ``[0, w0, 2*w0, ...]`` (passing zero frequency) and vice versa. + + .. versionadded:: 1.9.0 + + %(xp_device_snippet)s + + Returns + ------- + b, a : ndarray, ndarray + Numerator (``b``) and denominator (``a``) polynomials + of the IIR filter. + + Raises + ------ + ValueError + If `w0` is less than or equal to 0 or greater than or equal to + ``fs/2``, if `fs` is not divisible by `w0`, if `ftype` + is not 'notch' or 'peak' + + See Also + -------- + iirnotch + iirpeak + + Notes + ----- + For implementation details, see [1]_. The TF implementation of the + comb filter is numerically stable even at higher orders due to the + use of a single repeated pole, which won't suffer from precision loss. + + References + ---------- + .. [1] Sophocles J. Orfanidis, "Introduction To Signal Processing", + Prentice-Hall, 1996, ch. 11, "Digital Filter Design" + + Examples + -------- + Design and plot notching comb filter at 20 Hz for a + signal sampled at 200 Hz, using quality factor Q = 30 + + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + >>> import numpy as np + + >>> fs = 200.0 # Sample frequency (Hz) + >>> f0 = 20.0 # Frequency to be removed from signal (Hz) + >>> Q = 30.0 # Quality factor + >>> # Design notching comb filter + >>> b, a = signal.iircomb(f0, Q, ftype='notch', fs=fs) + + >>> # Frequency response + >>> freq, h = signal.freqz(b, a, fs=fs) + >>> response = abs(h) + >>> # To avoid divide by zero when graphing + >>> response[response == 0] = 1e-20 + >>> # Plot + >>> fig, ax = plt.subplots(2, 1, figsize=(8, 6), sharex=True) + >>> ax[0].plot(freq, 20*np.log10(abs(response)), color='blue') + >>> ax[0].set_title("Frequency Response") + >>> ax[0].set_ylabel("Amplitude [dB]", color='blue') + >>> ax[0].set_xlim([0, 100]) + >>> ax[0].set_ylim([-30, 10]) + >>> ax[0].grid(True) + >>> ax[1].plot(freq, np.mod(np.angle(h, deg=True) + 180, 360) - 180, color='green') + >>> ax[1].set_ylabel("Phase [deg]", color='green') + >>> ax[1].set_xlabel("Frequency [Hz]") + >>> ax[1].set_xlim([0, 100]) + >>> ax[1].set_yticks([-90, -60, -30, 0, 30, 60, 90]) + >>> ax[1].set_ylim([-90, 90]) + >>> ax[1].grid(True) + >>> plt.show() + + Design and plot peaking comb filter at 250 Hz for a + signal sampled at 1000 Hz, using quality factor Q = 30 + + >>> fs = 1000.0 # Sample frequency (Hz) + >>> f0 = 250.0 # Frequency to be retained (Hz) + >>> Q = 30.0 # Quality factor + >>> # Design peaking filter + >>> b, a = signal.iircomb(f0, Q, ftype='peak', fs=fs, pass_zero=True) + + >>> # Frequency response + >>> freq, h = signal.freqz(b, a, fs=fs) + >>> response = abs(h) + >>> # To avoid divide by zero when graphing + >>> response[response == 0] = 1e-20 + >>> # Plot + >>> fig, ax = plt.subplots(2, 1, figsize=(8, 6), sharex=True) + >>> ax[0].plot(freq, 20*np.log10(np.maximum(abs(h), 1e-5)), color='blue') + >>> ax[0].set_title("Frequency Response") + >>> ax[0].set_ylabel("Amplitude [dB]", color='blue') + >>> ax[0].set_xlim([0, 500]) + >>> ax[0].set_ylim([-80, 10]) + >>> ax[0].grid(True) + >>> ax[1].plot(freq, np.mod(np.angle(h)*180/np.pi + 180, 360) - 180, color='green') + >>> ax[1].set_ylabel("Phase [deg]", color='green') + >>> ax[1].set_xlabel("Frequency [Hz]") + >>> ax[1].set_xlim([0, 500]) + >>> ax[1].set_yticks([-90, -60, -30, 0, 30, 60, 90]) + >>> ax[1].set_ylim([-90, 90]) + >>> ax[1].grid(True) + >>> plt.show() + """ + if xp is None: + xp = np_compat + + # Convert w0, Q, and fs to float + w0 = float(w0) + Q = float(Q) + fs = _validate_fs(fs, allow_none=False) + + # Check for invalid cutoff frequency or filter type + ftype = ftype.lower() + if not 0 < w0 < fs / 2: + raise ValueError(f"w0 must be between 0 and {fs / 2}" + f" (Nyquist), but given {w0}.") + if ftype not in ('notch', 'peak'): + raise ValueError('ftype must be either notch or peak.') + + # Compute the order of the filter + N = round(fs / w0) + + # Check for cutoff frequency divisibility + if abs(w0 - fs/N)/fs > 1e-14: + raise ValueError('fs must be divisible by w0.') + + # Compute frequency in radians and filter bandwidth + # Eq. 11.3.1 (p. 574) from reference [1] + w0 = (2 * xp.pi * w0) / fs + w_delta = w0 / Q + + # Define base gain values depending on notch or peak filter + # Compute -3dB attenuation + # Eqs. 11.4.1 and 11.4.2 (p. 582) from reference [1] + if ftype == 'notch': + G0, G = 1, 0 + elif ftype == 'peak': + G0, G = 0, 1 + + # Compute beta according to Eq. 11.5.3 (p. 591) from reference [1]. Due to + # assuming a -3 dB attenuation value, i.e, assuming GB = 1 / np.sqrt(2), + # the following term simplifies to: + # np.sqrt((GB**2 - G0**2) / (G**2 - GB**2)) = 1 + beta = math.tan(N * w_delta / 4) + + # Compute filter coefficients + # Eq 11.5.1 (p. 590) variables a, b, c from reference [1] + ax = (1 - beta) / (1 + beta) + bx = (G0 + G * beta) / (1 + beta) + cx = (G0 - G * beta) / (1 + beta) + + # Last coefficients are negative to get peaking comb that passes zero or + # notching comb that doesn't. + negative_coef = ((ftype == 'peak' and pass_zero) or + (ftype == 'notch' and not pass_zero)) + + # Compute numerator coefficients + # Eq 11.5.1 (p. 590) or Eq 11.5.4 (p. 591) from reference [1] + # b - cz^-N or b + cz^-N + b = xp.zeros(N + 1, device=device) + sgn = -1. if negative_coef else 1 + xpx.at(b, 0).set(bx) + xpx.at(b, -1).set(sgn * cx) + + # Compute denominator coefficients + # Eq 11.5.1 (p. 590) or Eq 11.5.4 (p. 591) from reference [1] + # 1 - az^-N or 1 + az^-N + a = xp.zeros(N + 1, device=device) + xpx.at(a, 0).set(1.) + xpx.at(a, -1).set(sgn * ax) + + return b, a + + +def _hz_to_erb(hz): + """ + Utility for converting from frequency (Hz) to the + Equivalent Rectangular Bandwidth (ERB) scale + ERB = frequency / EarQ + minBW + """ + EarQ = 9.26449 + minBW = 24.7 + return hz / EarQ + minBW + + +def gammatone(freq, ftype, order=None, numtaps=None, fs=None, *, xp=None, device=None): + """ + Gammatone filter design. + + This function computes the coefficients of an FIR or IIR gammatone + digital filter [1]_. + + Parameters + ---------- + freq : float + Center frequency of the filter (expressed in the same units + as `fs`). + ftype : {'fir', 'iir'} + The type of filter the function generates. If 'fir', the function + will generate an Nth order FIR gammatone filter. If 'iir', the + function will generate an 8th order digital IIR filter, modeled as + as 4th order gammatone filter. + order : int, optional + The order of the filter. Only used when ``ftype='fir'``. + Default is 4 to model the human auditory system. Must be between + 0 and 24. + numtaps : int, optional + Length of the filter. Only used when ``ftype='fir'``. + Default is ``fs*0.015`` if `fs` is greater than 1000, + 15 if `fs` is less than or equal to 1000. + fs : float, optional + The sampling frequency of the signal. `freq` must be between + 0 and ``fs/2``. Default is 2. + %(xp_device_snippet)s + + Returns + ------- + b, a : ndarray, ndarray + Numerator (``b``) and denominator (``a``) polynomials of the filter. + + Raises + ------ + ValueError + If `freq` is less than or equal to 0 or greater than or equal to + ``fs/2``, if `ftype` is not 'fir' or 'iir', if `order` is less than + or equal to 0 or greater than 24 when ``ftype='fir'`` + + See Also + -------- + firwin + iirfilter + + References + ---------- + .. [1] Slaney, Malcolm, "An Efficient Implementation of the + Patterson-Holdsworth Auditory Filter Bank", Apple Computer + Technical Report 35, 1993, pp.3-8, 34-39. + + Examples + -------- + 16-sample 4th order FIR Gammatone filter centered at 440 Hz + + >>> from scipy import signal + >>> signal.gammatone(440, 'fir', numtaps=16, fs=16000) + (array([ 0.00000000e+00, 2.22196719e-07, 1.64942101e-06, 4.99298227e-06, + 1.01993969e-05, 1.63125770e-05, 2.14648940e-05, 2.29947263e-05, + 1.76776931e-05, 2.04980537e-06, -2.72062858e-05, -7.28455299e-05, + -1.36651076e-04, -2.19066855e-04, -3.18905076e-04, -4.33156712e-04]), + [1.0]) + + IIR Gammatone filter centered at 440 Hz + + >>> import matplotlib.pyplot as plt + >>> import numpy as np + + >>> fc, fs = 440, 16000 + >>> b, a = signal.gammatone(fc, 'iir', fs=fs) + >>> w, h = signal.freqz(b, a) + >>> plt.plot(w * fs / (2 * np.pi), 20 * np.log10(abs(h))) + >>> plt.xscale('log') + >>> plt.title('Gammatone filter frequency response') + >>> plt.xlabel('Frequency [Hz]') + >>> plt.ylabel('Amplitude [dB]') + >>> plt.margins(0, 0.1) + >>> plt.grid(which='both', axis='both') + >>> plt.axvline(fc, color='green') # cutoff frequency + >>> plt.show() + """ + if xp is None: + xp = np_compat + + # Converts freq to float + freq = float(freq) + + # Set sampling rate if not passed + if fs is None: + fs = 2 + fs = _validate_fs(fs, allow_none=False) + + # Check for invalid cutoff frequency or filter type + ftype = ftype.lower() + filter_types = ['fir', 'iir'] + if not 0 < freq < fs / 2: + raise ValueError(f"The frequency must be between 0 and {fs / 2}" + f" (Nyquist), but given {freq}.") + if ftype not in filter_types: + raise ValueError('ftype must be either fir or iir.') + + # Calculate FIR gammatone filter + if ftype == 'fir': + # Set order and numtaps if not passed + if order is None: + order = 4 + order = operator.index(order) + + if numtaps is None: + numtaps = max(int(fs * 0.015), 15) + numtaps = operator.index(numtaps) + + # Check for invalid order + if not 0 < order <= 24: + raise ValueError("Invalid order: order must be > 0 and <= 24.") + + # Gammatone impulse response settings + t = xp.arange(numtaps, device=device, dtype=xp_default_dtype(xp)) / fs + bw = 1.019 * _hz_to_erb(freq) + + # Calculate the FIR gammatone filter + b = (t ** (order - 1)) * xp.exp(-2 * xp.pi * bw * t) + b = b * xp.cos(2 * xp.pi * freq * t) + + # Scale the FIR filter so the frequency response is 1 at cutoff + scale_factor = 2 * (2 * xp.pi * bw) ** (order) + scale_factor /= float_factorial(order - 1) + scale_factor /= fs + b = b * scale_factor + a = xp.asarray([1.0], device=device) + + # Calculate IIR gammatone filter + elif ftype == 'iir': + # Raise warning if order and/or numtaps is passed + if order is not None: + warnings.warn('order is not used for IIR gammatone filter.', stacklevel=2) + if numtaps is not None: + warnings.warn('numtaps is not used for IIR gammatone filter.', stacklevel=2) + + # Gammatone impulse response settings + T = 1./fs + bw = 2 * math.pi * 1.019 * _hz_to_erb(freq) + fr = 2 * freq * math.pi * T + bwT = bw * T + + # Calculate the gain to normalize the volume at the center frequency + g1 = -2 * cmath.exp(2j * fr) * T + g2 = 2 * cmath.exp(-(bwT) + 1j * fr) * T + g3 = math.sqrt(3 + 2 ** (3 / 2)) * math.sin(fr) + g4 = math.sqrt(3 - 2 ** (3 / 2)) * math.sin(fr) + g5 = cmath.exp(2j * fr) + + g = g1 + g2 * (math.cos(fr) - g4) + g *= (g1 + g2 * (math.cos(fr) + g4)) + g *= (g1 + g2 * (math.cos(fr) - g3)) + g *= (g1 + g2 * (math.cos(fr) + g3)) + g /= ((-2 / math.exp(2 * bwT) - 2 * g5 + 2 * (1 + g5) / math.exp(bwT)) ** 4) + g = math.hypot(g.real, g.imag) + + # Create empty filter coefficient lists + b = [None] * 5 #np.empty(5) + a = [None] * 9 # np.empty(9) + + # Calculate the numerator coefficients + b[0] = (T ** 4) / g + b[1] = -4 * T ** 4 * math.cos(fr) / math.exp(bw * T) / g + b[2] = 6 * T ** 4 * math.cos(2 * fr) / math.exp(2 * bw * T) / g + b[3] = -4 * T ** 4 * math.cos(3 * fr) / math.exp(3 * bw * T) / g + b[4] = T ** 4 * math.cos(4 * fr) / math.exp(4 * bw * T) / g + + # Calculate the denominator coefficients + a[0] = 1 + a[1] = -8 * math.cos(fr) / math.exp(bw * T) + a[2] = 4 * (4 + 3 * math.cos(2 * fr)) / math.exp(2 * bw * T) + a[3] = -8 * (6 * math.cos(fr) + math.cos(3 * fr)) + a[3] /= math.exp(3 * bw * T) + a[4] = 2 * (18 + 16 * math.cos(2 * fr) + math.cos(4 * fr)) + a[4] /= math.exp(4 * bw * T) + a[5] = -8 * (6 * math.cos(fr) + math.cos(3 * fr)) + a[5] /= math.exp(5 * bw * T) + a[6] = 4 * (4 + 3 * math.cos(2 * fr)) / math.exp(6 * bw * T) + a[7] = -8 * math.cos(fr) / math.exp(7 * bw * T) + a[8] = math.exp(-8 * bw * T) + + return xp.asarray(b, device=device), xp.asarray(a, device=device) + + +filter_dict = {'butter': [buttap, buttord], + 'butterworth': [buttap, buttord], + + 'cauer': [ellipap, ellipord], + 'elliptic': [ellipap, ellipord], + 'ellip': [ellipap, ellipord], + + 'bessel': [besselap], + 'bessel_phase': [besselap], + 'bessel_delay': [besselap], + 'bessel_mag': [besselap], + + 'cheby1': [cheb1ap, cheb1ord], + 'chebyshev1': [cheb1ap, cheb1ord], + 'chebyshevi': [cheb1ap, cheb1ord], + + 'cheby2': [cheb2ap, cheb2ord], + 'chebyshev2': [cheb2ap, cheb2ord], + 'chebyshevii': [cheb2ap, cheb2ord], + } + +band_dict = {'band': 'bandpass', + 'bandpass': 'bandpass', + 'pass': 'bandpass', + 'bp': 'bandpass', + + 'bs': 'bandstop', + 'bandstop': 'bandstop', + 'bands': 'bandstop', + 'stop': 'bandstop', + + 'l': 'lowpass', + 'low': 'lowpass', + 'lowpass': 'lowpass', + 'lp': 'lowpass', + + 'high': 'highpass', + 'highpass': 'highpass', + 'h': 'highpass', + 'hp': 'highpass', + } + +bessel_norms = {'bessel': 'phase', + 'bessel_phase': 'phase', + 'bessel_delay': 'delay', + 'bessel_mag': 'mag'} + + +########## complete the docstrings, on import +_xp_device_snippet = {'xp_device_snippet': +"""\ +xp : array_namespace, optional + Optional array namespace. + Should be compatible with the array API standard, or supported by array-api-compat. + Default: ``numpy`` +device : any + optional device specification for output. Should match one of the + supported device specification in ``xp``. +""" +} + + +_names = ["buttap", "cheb1ap", "cheb2ap", "ellipap", "besselap", + "iirnotch", "iirpeak", "iircomb", "gammatone", +] +for name in _names: + window = vars()[name] + window.__doc__ = doccer.docformat(window.__doc__, _xp_device_snippet) diff --git a/venv/lib/python3.12/site-packages/scipy/signal/_fir_filter_design.py b/venv/lib/python3.12/site-packages/scipy/signal/_fir_filter_design.py new file mode 100644 index 0000000000000000000000000000000000000000..23d229c323d85bec7d796a66009a416251e68d1d --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/_fir_filter_design.py @@ -0,0 +1,1553 @@ +"""Functions for FIR filter design.""" + +from math import ceil, log, log2 +import warnings +from typing import Literal + +import numpy as np +from scipy.fft import irfft, fft, ifft +from scipy.linalg import (toeplitz, hankel, solve, LinAlgError, LinAlgWarning, + lstsq) +from scipy.signal._arraytools import _validate_fs +from .windows import get_window +from . import _sigtools + +from scipy._lib._array_api import array_namespace, xp_size, xp_default_dtype +import scipy._external.array_api_extra as xpx + + +__all__ = ['kaiser_beta', 'kaiser_atten', 'kaiserord', + 'firwin', 'firwin2', 'firwin_2d', 'remez', 'firls', 'minimum_phase'] + + +# Some notes on function parameters: +# +# `cutoff` and `width` are given as numbers between 0 and 1. These are +# relative frequencies, expressed as a fraction of the Nyquist frequency. +# For example, if the Nyquist frequency is 2 KHz, then width=0.15 is a width +# of 300 Hz. +# +# The `order` of a FIR filter is one less than the number of taps. +# This is a potential source of confusion, so in the following code, +# we will always use the number of taps as the parameterization of +# the 'size' of the filter. The "number of taps" means the number +# of coefficients, which is the same as the length of the impulse +# response of the filter. + + +def kaiser_beta(a): + """Compute the Kaiser parameter `beta`, given the attenuation `a`. + + Parameters + ---------- + a : float + The desired attenuation in the stopband and maximum ripple in + the passband, in dB. This should be a *positive* number. + + Returns + ------- + beta : float + The `beta` parameter to be used in the formula for a Kaiser window. + + References + ---------- + Oppenheim, Schafer, "Discrete-Time Signal Processing", p.475-476. + + Examples + -------- + Suppose we want to design a lowpass filter, with 65 dB attenuation + in the stop band. The Kaiser window parameter to be used in the + window method is computed by ``kaiser_beta(65)``: + + >>> from scipy.signal import kaiser_beta + >>> kaiser_beta(65) + 6.20426 + + """ + if a > 50: + beta = 0.1102 * (a - 8.7) + elif a > 21: + beta = 0.5842 * (a - 21) ** 0.4 + 0.07886 * (a - 21) + else: + beta = 0.0 + return beta + + +def kaiser_atten(numtaps, width): + """Compute the attenuation of a Kaiser FIR filter. + + Given the number of taps `N` and the transition width `width`, compute the + attenuation `a` in dB, given by Kaiser's formula: + + a = 2.285 * (N - 1) * pi * width + 7.95 + + Parameters + ---------- + numtaps : int + The number of taps in the FIR filter. + width : float + The desired width of the transition region between passband and + stopband (or, in general, at any discontinuity) for the filter, + expressed as a fraction of the Nyquist frequency. + + Returns + ------- + a : float + The attenuation of the ripple, in dB. + + See Also + -------- + kaiserord, kaiser_beta + + Examples + -------- + Suppose we want to design a FIR filter using the Kaiser window method + that will have 211 taps and a transition width of 9 Hz for a signal that + is sampled at 480 Hz. Expressed as a fraction of the Nyquist frequency, + the width is 9/(0.5*480) = 0.0375. The approximate attenuation (in dB) + is computed as follows: + + >>> from scipy.signal import kaiser_atten + >>> kaiser_atten(211, 0.0375) + 64.48099630593983 + + """ + a = 2.285 * (numtaps - 1) * np.pi * width + 7.95 + return a + + +def kaiserord(ripple, width): + """ + Determine the filter window parameters for the Kaiser window method. + + The parameters returned by this function are generally used to create + a finite impulse response filter using the window method, with either + `firwin` or `firwin2`. + + Parameters + ---------- + ripple : float + Upper bound for the deviation (in dB) of the magnitude of the + filter's frequency response from that of the desired filter (not + including frequencies in any transition intervals). That is, if w + is the frequency expressed as a fraction of the Nyquist frequency, + A(w) is the actual frequency response of the filter and D(w) is the + desired frequency response, the design requirement is that:: + + abs(A(w) - D(w))) < 10**(-ripple/20) + + for 0 <= w <= 1 and w not in a transition interval. + width : float + Width of transition region, normalized so that 1 corresponds to pi + radians / sample. That is, the frequency is expressed as a fraction + of the Nyquist frequency. + + Returns + ------- + numtaps : int + The length of the Kaiser window. + beta : float + The beta parameter for the Kaiser window. + + See Also + -------- + kaiser_beta, kaiser_atten + + Notes + ----- + There are several ways to obtain the Kaiser window: + + - ``signal.windows.kaiser(numtaps, beta, sym=True)`` + - ``signal.get_window(beta, numtaps)`` + - ``signal.get_window(('kaiser', beta), numtaps)`` + + The empirical equations discovered by Kaiser are used. + + References + ---------- + Oppenheim, Schafer, "Discrete-Time Signal Processing", pp.475-476. + + Examples + -------- + We will use the Kaiser window method to design a lowpass FIR filter + for a signal that is sampled at 1000 Hz. + + We want at least 65 dB rejection in the stop band, and in the pass + band the gain should vary no more than 0.5%. + + We want a cutoff frequency of 175 Hz, with a transition between the + pass band and the stop band of 24 Hz. That is, in the band [0, 163], + the gain varies no more than 0.5%, and in the band [187, 500], the + signal is attenuated by at least 65 dB. + + >>> import numpy as np + >>> from scipy.signal import kaiserord, firwin, freqz + >>> import matplotlib.pyplot as plt + >>> fs = 1000.0 + >>> cutoff = 175 + >>> width = 24 + + The Kaiser method accepts just a single parameter to control the pass + band ripple and the stop band rejection, so we use the more restrictive + of the two. In this case, the pass band ripple is 0.005, or 46.02 dB, + so we will use 65 dB as the design parameter. + + Use `kaiserord` to determine the length of the filter and the + parameter for the Kaiser window. + + >>> numtaps, beta = kaiserord(65, width/(0.5*fs)) + >>> numtaps + 167 + >>> beta + 6.20426 + + Use `firwin` to create the FIR filter. + + >>> taps = firwin(numtaps, cutoff, window=('kaiser', beta), + ... scale=False, fs=fs) + + Compute the frequency response of the filter. ``w`` is the array of + frequencies, and ``h`` is the corresponding complex array of frequency + responses. + + >>> w, h = freqz(taps, worN=8000) + >>> w *= 0.5*fs/np.pi # Convert w to Hz. + + Compute the deviation of the magnitude of the filter's response from + that of the ideal lowpass filter. Values in the transition region are + set to ``nan``, so they won't appear in the plot. + + >>> ideal = w < cutoff # The "ideal" frequency response. + >>> deviation = np.abs(np.abs(h) - ideal) + >>> deviation[(w > cutoff - 0.5*width) & (w < cutoff + 0.5*width)] = np.nan + + Plot the deviation. A close look at the left end of the stop band shows + that the requirement for 65 dB attenuation is violated in the first lobe + by about 0.125 dB. This is not unusual for the Kaiser window method. + + >>> plt.plot(w, 20*np.log10(np.abs(deviation))) + >>> plt.xlim(0, 0.5*fs) + >>> plt.ylim(-90, -60) + >>> plt.grid(alpha=0.25) + >>> plt.axhline(-65, color='r', ls='--', alpha=0.3) + >>> plt.xlabel('Frequency (Hz)') + >>> plt.ylabel('Deviation from ideal (dB)') + >>> plt.title('Lowpass Filter Frequency Response') + >>> plt.show() + + """ + A = abs(ripple) # in case somebody is confused as to what's meant + if A < 8: + # Formula for N is not valid in this range. + raise ValueError("Requested maximum ripple attenuation " + f"{A:f} is too small for the Kaiser formula.") + beta = kaiser_beta(A) + + # Kaiser's formula (as given in Oppenheim and Schafer) is for the filter + # order, so we have to add 1 to get the number of taps. + numtaps = (A - 7.95) / 2.285 / (np.pi * width) + 1 + + return int(ceil(numtaps)), beta + + +def firwin(numtaps, cutoff, *, width=None, window='hamming', pass_zero=True, + scale=True, fs=None): + r"""FIR filter design using the window method. + + This function computes the coefficients of a finite impulse response + filter. The filter will have linear phase; it will be Type I if + `numtaps` is odd and Type II if `numtaps` is even. + + Type II filters always have zero response at the Nyquist frequency, so a + ValueError exception is raised if firwin is called with `numtaps` even and + having a passband whose right end is at the Nyquist frequency. + + Parameters + ---------- + numtaps : int + Length of the filter (number of coefficients, i.e., the filter + order + 1). `numtaps` must be odd if a passband includes the + Nyquist frequency. + cutoff : float or 1-D array_like + Cutoff frequency of filter (expressed in the same units as `fs`) + or an array of cutoff frequencies (that is, band edges). In the + former case, as a float, the cutoff frequency should correspond + with the half-amplitude point, where the attenuation will be -6 dB. + In the latter case, the frequencies in `cutoff` should be positive + and monotonically increasing between 0 and `fs/2`. The values 0 + and `fs/2` must not be included in `cutoff`. It should be noted + that this is different from the behavior of `~scipy.signal.iirdesign`, + where the cutoff is the half-power point (-3 dB). + width : float or None, optional + If not ``None``, then a `~scipy.signal.windows.kaiser` window is calculated + where `width` specifies the approximate width of the transition region + (expressed in the same unit as `fs`). This is achieved by utilizing + `~scipy.signal.kaiser_atten` to calculate an attenuation which is passed to + `~scipy.signal.kaiser_beta` for determining the β parameter for the kaiser + window. In this case, the `window` argument is ignored. + window : str or tuple of str and parameter values, optional + Desired window to use. Default is ``'hamming'``. The window will be symmetric, + unless a suffix ``'_periodic'`` is appended to the window name (e.g., + ``'hamming_perodic'``) Consult `~scipy.signal.get_window` for a list of windows + and required parameters. + pass_zero : {True, False, 'bandpass', 'lowpass', 'highpass', 'bandstop'}, optional + Toggles the zero frequency bin (or DC gain) to be in the passband (``True``) or + in the stopband (``False``). ``'bandstop'``, ``'lowpass'`` are synonyms for + ``True`` and ``'bandpass'``, ``'highpass'`` are synonyms for ``False``. + ``'lowpass'``, ``'highpass'`` additionally require `cutoff` to be a scalar + value or a length-one array. Default: ``True``. + + .. versionadded:: 1.3.0 + Support for string arguments. + scale : bool, optional + Set to ``True`` to scale the coefficients so that the frequency response is + exactly unity at a certain frequency. That frequency is either: + + - 0 (DC) if the first passband starts at 0 (i.e., `pass_zero` is ``True``) + - `fs/2` (the Nyquist frequency) if the first passband ends at `fs/2` + (i.e., the filter is a single band highpass filter); center of first + passband otherwise + + fs : float, optional + The sampling frequency of the signal. Each frequency in `cutoff` + must be between 0 and ``fs/2``. Default is 2. + + Returns + ------- + h : ndarray + FIR filter coefficients as 1d array with `numtaps` entries. + + Raises + ------ + ValueError + If any value in `cutoff` is less than or equal to 0 or greater + than or equal to ``fs/2``, if the values in `cutoff` are not strictly + monotonically increasing, or if `numtaps` is even but a passband + includes the Nyquist frequency. + + See Also + -------- + firwin2: Window method FIR filter design specifying gain-frequency pairs. + firwin_2d: 2D FIR filter design using the window method. + firls: FIR filter design using least-squares error minimization. + minimum_phase: Convert a FIR filter to minimum phase + remez: Calculate the minimax optimal filter using the Remez exchange algorithm. + + Examples + -------- + The following example calculates frequency responses of a 30 Hz low-pass filter + with various numbers of taps. The transition region width is 20 Hz. The upper plot + shows the gain and the lower plot the phase. The vertical dashed line marks the + corner frequency and the gray background the transition region. + + >>> import matplotlib.pyplot as plt + >>> import numpy as np + >>> import scipy.signal as signal + ... + >>> fs = 200 # sampling frequency and number of taps + >>> f_c, width = 30, 20 # corner frequency (-6 dB gain) + >>> taps = [20, 40, 60] # number of taps + ... + >>> fg, (ax0, ax1) = plt.subplots(2, 1, sharex='all', layout="constrained", + ... figsize=(5, 4)) + >>> ax0.set_title(rf"Response of ${f_c}\,$Hz low-pass Filter with " + + ... rf"${width}\,$Hz transition region") + >>> ax0.set(ylabel="Gain in dB", xlim=(0, fs/2)) + >>> ax1.set(xlabel=rf"Frequency $f\,$ in hertz (sampling frequency $f_S={fs}\,$Hz)", + ... ylabel="Phase in Degrees") + ... + >>> for n in taps: # calculate filter and plot response: + ... bb = signal.firwin(n, f_c, width=width, fs=fs) + ... f, H = signal.freqz(bb, fs=fs) # calculate frequency response + ... H_dB, H_ph = 20 * np.log10(abs(H)), np.rad2deg(np.unwrap(np.angle(H))) + ... H_ph[H_dB<-150] = np.nan + ... ax0.plot(f, H_dB, alpha=.5, label=rf"{n} taps") + ... ax1.plot(f, H_ph, alpha=.5, label=rf"{n} taps") + >>> for ax_ in (ax0, ax1): + ... ax_.axvspan(f_c-width/2, f_c+width/2,color='gray', alpha=.25) + ... ax_.axvline(f_c, color='gray', linestyle='--', alpha=.5) + ... ax_.grid() + ... ax_.legend() + >>> plt.show() + + The plots show that with increasing number of taps, the suppression in the stopband + increases and the (negative) slope of the phase steepens, which signifies a longer + signal delay in the filter. Note that the plots contain numeric artifacts caused by + the limited frequency resolution: The phase jumps are not real---in reality the + phase is a straight line with a constant negative slope. Furthermore, the gains + contain zero values (i.e., -∞ dB), which are also not depicted. + + The second example determines the frequency responses of a 30 Hz low-pass filter + with 40 taps. This time the width of the transition region varies: + + >>> import matplotlib.pyplot as plt + >>> import numpy as np + >>> import scipy.signal as signal + ... + >>> fs = 200 # sampling frequency and number of taps + >>> n, f_c = 40, 30 # number of taps and corner frequency (-6 dB gain) + >>> widths = (2, 10, 25) # width of transition region + ... + >>> fg, ax = plt.subplots(1, 1, layout="constrained",) + >>> ax.set(title=rf"Response of {n}-tap ${f_c}\,$Hz low-pass Filter", + ... xlabel=rf"Frequency $f\,$ in hertz (sampling frequency $f_S={fs}\,$Hz)", + ... ylabel="Gain in dB", xlim=(0, fs/2)) + >>> ax.axvline(f_c, color='gray', linestyle='--', alpha=.7) # mark corner frequency + ... + >>> for width in widths: # calculate filter and plot response: + ... bb = signal.firwin(n, f_c, width=width, fs=fs) + ... f, H = signal.freqz(bb, fs=fs) # calculate frequency response + ... H_dB= 20 * np.log10(abs(H)) # convert to dB + ... ax.plot(f, H_dB, alpha=.5, label=rf"width$={width}\,$Hz") + ... + >>> ax.grid() + >>> ax.legend() + >>> plt.show() + + It can be seen in the plot above that with increasing width of the transition + region the suppression in the stopband increases. Since the phase does not vary, it + is not depicted. + + Instead of defining a transition region width, a window function can also be + specified. The plot below depicts the response of an 80 tap band-pass filter having + a passband ranging form 40 Hz to 60 Hz (denoted by a gray background) with + different windows: + + >>> import matplotlib.pyplot as plt + >>> import numpy as np + >>> import scipy.signal as signal + ... + >>> fs, n = 200, 80 # sampling frequency and number of taps + >>> cutoff = [40, 60] # corner frequencies (-6 dB gain) + >>> windows = ('boxcar', 'hamming', 'hann', 'blackman') + ... + >>> fg, ax = plt.subplots(1, 1, layout="constrained") # set up plotting + >>> ax.set(title=rf"Response of {n}-tap Filter with ${cutoff}\,$Hz passband", + ... xlabel=rf"Frequency $f\,$ in hertz (sampling frequency $f_S={fs}\,$Hz)", + ... ylabel="Gain in dB", xlim=(0, fs/2)) + >>> ax.axvspan(*cutoff, color='gray', alpha=.25) # mark passband + ... + >>> for win in windows: # calculate filter and plot response: + ... bb = signal.firwin(n, cutoff, window=win, pass_zero=False, fs=fs) + ... f, H = signal.freqz(bb, fs=fs) # calculate frequency response + ... H_dB = 20 * np.log10(abs(H)) # convert to dB + ... ax.plot(f, H_dB, alpha=.5, label=win) + ... + >>> ax.grid() + >>> ax.legend() + >>> plt.show() + + The plot shows that the choice of window mainly influences the balance of the + suppression in the stopband against the width of the transition region. Note that + utilizing the `~scipy.signal.windows.boxcar` window corresponds to just truncating + the ideal infinite impulse response to the length of `numtaps` samples. + + The last example illustrates how to use the `cutoff` and `pass_zero` parameters to + create a low-pass, a high-pass, a band-stop and a band-pass filter. The desired + ideal frequency gain is drawn as a gray dashed line whereas the response of the FIR + filter is depicted as a blue continuous line: + + >>> from itertools import product + >>> import matplotlib.pyplot as plt + >>> import numpy as np + >>> import scipy.signal as signal + ... + >>> cutoffs = [0.5, (.25, .75)] # cutoff parameters + >>> fg, axx = plt.subplots(4, 1, sharex='all', layout="constrained", + ... figsize=(5, 4)) + >>> for ax, (cutoff, pass_zero) in zip(axx, product(cutoffs, (True, False))): + ... ax.set(title=f"firwin(41, {cutoff=}, {pass_zero=}, fs=2)", ylabel="Gain") + ... ax.set_yticks([0.5], minor=True) # mark gain of 0.5 (= -6 dB) + ... ax.grid(which='minor', axis='y') + ... + ... bb = signal.firwin(41, cutoff, pass_zero=pass_zero, fs=2) + ... ff, HH = signal.freqz(bb, fs=2) + ... ax.plot(ff, abs(HH), 'C0-', label="FIR Response") + ... + ... f_d = np.hstack(([0], np.atleast_1d(cutoff), [1])) + ... H_d = np.tile([1, 0] if pass_zero else [0, 1], 2)[:len(f_d)] + ... H_d[-1] = H_d[-2] # account for symmetry at Nyquist frequency + ... ax.step(f_d, H_d, 'k--', where='post', alpha=.3, label="Desired Response") + >>> axx[-1].set(xlabel=r"Frequency $f\,$ in hertz (sampling frequency $f_S=2\,$Hz)", + ... xlim=(0, 1)) + >>> plt.show() + """ + # NB: scipy's version of array_namespace returns `np_compat` for int or floats + xp = array_namespace(cutoff) + + # The major enhancements to this function added in November 2010 were + # developed by Tom Krauss (see ticket #902). + fs = _validate_fs(fs, allow_none=True) + fs = 2 if fs is None else fs + + nyq = 0.5 * fs + + cutoff = xp.asarray(cutoff, dtype=xp_default_dtype(xp)) + cutoff = xpx.atleast_nd(cutoff, ndim=1, xp=xp) / float(nyq) + + # Check for invalid input. + if cutoff.ndim > 1: + raise ValueError("The cutoff argument must be at most " + "one-dimensional.") + if xp_size(cutoff) == 0: + raise ValueError("At least one cutoff frequency must be given.") + if xp.min(cutoff) <= 0 or xp.max(cutoff) >= 1: + raise ValueError("Invalid cutoff frequency: frequencies must be " + "greater than 0 and less than fs/2.") + if xp.any(cutoff[1:] - cutoff[:-1] <= 0): + raise ValueError("Invalid cutoff frequencies: the frequencies " + "must be strictly increasing.") + + if width is not None: + # A width was given. Find the beta parameter of the Kaiser window + # and set `window`. This overrides the value of `window` passed in. + atten = kaiser_atten(numtaps, float(width) / nyq) + beta = kaiser_beta(atten) + window = ('kaiser', beta) + + if pass_zero in ('bandstop', 'lowpass'): + if pass_zero == 'lowpass': + if xp_size(cutoff) != 1: + raise ValueError('cutoff must have one element if ' + f'pass_zero=="lowpass", got {cutoff.shape}') + elif xp_size(cutoff) <= 1: + raise ValueError('cutoff must have at least two elements if ' + f'pass_zero=="bandstop", got {cutoff.shape}') + pass_zero = True + elif pass_zero in ('bandpass', 'highpass'): + if pass_zero == 'highpass': + if xp_size(cutoff) != 1: + raise ValueError('cutoff must have one element if ' + f'pass_zero=="highpass", got {cutoff.shape}') + elif xp_size(cutoff) <= 1: + raise ValueError('cutoff must have at least two elements if ' + f'pass_zero=="bandpass", got {cutoff.shape}') + pass_zero = False + elif not (pass_zero is True or pass_zero is False): + raise ValueError(f"Parameter {pass_zero=} not in (True, False, 'bandpass', " + + "'lowpass', 'highpass', 'bandstop')") + + pass_nyquist = (xp_size(cutoff) % 2 == 0) == pass_zero + if pass_nyquist and numtaps % 2 == 0: + raise ValueError("A filter with an even number of coefficients must " + "have zero response at the Nyquist frequency.") + + # Insert 0 and/or 1 at the ends of cutoff so that the length of cutoff + # is even, and each pair in cutoff corresponds to passband. + cutoff = xp.concat((xp.zeros(int(pass_zero)), cutoff, xp.ones(int(pass_nyquist)))) + + + # `bands` is a 2-D array; each row gives the left and right edges of + # a passband. + bands = xp.reshape(cutoff, (-1, 2)) + + # Build up the coefficients. + alpha = 0.5 * (numtaps - 1) + m = xp.arange(0, numtaps, dtype=cutoff.dtype) - alpha + h = 0 + for j in range(bands.shape[0]): + left, right = bands[j, 0], bands[j, 1] + h += right * xpx.sinc(right * m, xp=xp) + h -= left * xpx.sinc(left * m, xp=xp) + + # Get and apply the window function. + win = get_window(window, numtaps, fftbins=False, xp=xp) + h *= win + + # Now handle scaling if desired. + if scale: + # Get the first passband. + left, right = bands[0, ...] + if left == 0: + scale_frequency = 0.0 + elif right == 1: + scale_frequency = 1.0 + else: + scale_frequency = 0.5 * (left + right) + c = xp.cos(xp.pi * m * scale_frequency) + s = xp.sum(h * c) + h /= s + + return h + + +# Original version of firwin2 from scipy ticket #457, submitted by "tash". +# +# Rewritten by Warren Weckesser, 2010. +def firwin2(numtaps, freq, gain, *, nfreqs=None, window='hamming', + antisymmetric=False, fs=None): + """ + FIR filter design using the window method. + + From the given frequencies `freq` and corresponding gains `gain`, + this function constructs an FIR filter with linear phase and + (approximately) the given frequency response. + + Parameters + ---------- + numtaps : int + The number of taps in the FIR filter. `numtaps` must be less than + `nfreqs`. + freq : array_like, 1-D + The frequency sampling points. Typically 0.0 to 1.0 with 1.0 being + Nyquist. The Nyquist frequency is half `fs`. + The values in `freq` must be nondecreasing. A value can be repeated + once to implement a discontinuity. The first value in `freq` must + be 0, and the last value must be ``fs/2``. Values 0 and ``fs/2`` must + not be repeated. + gain : array_like + The filter gains at the frequency sampling points. Certain + constraints to gain values, depending on the filter type, are applied, + see Notes for details. + nfreqs : int, optional + The size of the interpolation mesh used to construct the filter. + For most efficient behavior, this should be a power of 2 plus 1 + (e.g, 129, 257, etc). The default is one more than the smallest + power of 2 that is not less than `numtaps`. `nfreqs` must be greater + than `numtaps`. + window : str or (str, float) or float, or None, optional + Desired window to use. Default is ``'hamming'``. The window will be symmetric, + unless a suffix ``'_periodic'`` is appended to the window name (e.g., + ``'hamming_perodic'``) Consult `~scipy.signal.get_window` for a list of windows + and required parameters. If ``None``, no window function is applied. + antisymmetric : bool, optional + Whether resulting impulse response is symmetric/antisymmetric. + See Notes for more details. + fs : float, optional + The sampling frequency of the signal. Each frequency in `cutoff` + must be between 0 and ``fs/2``. Default is 2. + + Returns + ------- + taps : ndarray + The filter coefficients of the FIR filter, as a 1-D array of length + `numtaps`. + + See Also + -------- + firls + firwin + minimum_phase + remez + + Notes + ----- + From the given set of frequencies and gains, the desired response is + constructed in the frequency domain. The inverse FFT is applied to the + desired response to create the associated convolution kernel, and the + first `numtaps` coefficients of this kernel, scaled by `window`, are + returned. + + The FIR filter will have linear phase. The type of filter is determined by + the value of 'numtaps` and `antisymmetric` flag. + There are four possible combinations: + + - odd `numtaps`, `antisymmetric` is False, type I filter is produced + - even `numtaps`, `antisymmetric` is False, type II filter is produced + - odd `numtaps`, `antisymmetric` is True, type III filter is produced + - even `numtaps`, `antisymmetric` is True, type IV filter is produced + + Magnitude response of all but type I filters are subjects to following + constraints: + + - type II -- zero at the Nyquist frequency + - type III -- zero at zero and Nyquist frequencies + - type IV -- zero at zero frequency + + .. versionadded:: 0.9.0 + + References + ---------- + .. [1] Oppenheim, A. V. and Schafer, R. W., "Discrete-Time Signal + Processing", Prentice-Hall, Englewood Cliffs, New Jersey (1989). + (See, for example, Section 7.4.) + + .. [2] Smith, Steven W., "The Scientist and Engineer's Guide to Digital + Signal Processing", Ch. 17. http://www.dspguide.com/ch17/1.htm + + Examples + -------- + A lowpass FIR filter with a response that is 1 on [0.0, 0.5], and + that decreases linearly on [0.5, 1.0] from 1 to 0: + + >>> from scipy import signal + >>> taps = signal.firwin2(150, [0.0, 0.5, 1.0], [1.0, 1.0, 0.0]) + >>> print(taps[72:78]) + [-0.02286961 -0.06362756 0.57310236 0.57310236 -0.06362756 -0.02286961] + + """ + xp = array_namespace(freq, gain) + freq, gain = xp.asarray(freq), xp.asarray(gain) + + fs = _validate_fs(fs, allow_none=True) + fs = 2 if fs is None else fs + nyq = 0.5 * fs + + if freq.shape[0] != gain.shape[0]: + raise ValueError('freq and gain must be of same length.') + + if nfreqs is not None and numtaps >= nfreqs: + raise ValueError( + f'ntaps must be less than nfreqs, but firwin2 was called with ' + f'ntaps={numtaps} and nfreqs={nfreqs}' + ) + + if freq[0] != 0 or freq[-1] != nyq: + raise ValueError('freq must start with 0 and end with fs/2.') + d = freq[1:] - freq[:-1] + if xp.any(d < 0): + raise ValueError('The values in freq must be nondecreasing.') + d2 = d[:-1] + d[1:] + if xp.any(d2 == 0): + raise ValueError('A value in freq must not occur more than twice.') + if freq[1] == 0: + raise ValueError('Value 0 must not be repeated in freq') + if freq[-2] == nyq: + raise ValueError('Value fs/2 must not be repeated in freq') + + if antisymmetric: + if numtaps % 2 == 0: + ftype = 4 + else: + ftype = 3 + else: + if numtaps % 2 == 0: + ftype = 2 + else: + ftype = 1 + + if ftype == 2 and gain[-1] != 0.0: + raise ValueError("A Type II filter must have zero gain at the " + "Nyquist frequency.") + elif ftype == 3 and (gain[0] != 0.0 or gain[-1] != 0.0): + raise ValueError("A Type III filter must have zero gain at zero " + "and Nyquist frequencies.") + elif ftype == 4 and gain[0] != 0.0: + raise ValueError("A Type IV filter must have zero gain at zero " + "frequency.") + + if nfreqs is None: + nfreqs = 1 + 2 ** int(ceil(log(numtaps, 2))) + + if xp.any(d == 0): + # Tweak any repeated values in freq so that interp works. + freq = xp.asarray(freq, copy=True) + eps = xp.finfo(xp_default_dtype(xp)).eps * nyq + for k in range(freq.shape[0] - 1): + if freq[k] == freq[k + 1]: + freq[k] = freq[k] - eps + freq[k + 1] = freq[k + 1] + eps + # Check if freq is strictly increasing after tweak + d = freq[1:] - freq[:-1] + if xp.any(d <= 0): + raise ValueError("freq cannot contain numbers that are too close " + "(within eps * (fs/2): " + f"{eps}) to a repeated value") + + # Linearly interpolate the desired response on a uniform mesh `x`. + x = np.linspace(0.0, nyq, nfreqs) + fx = np.interp(x, np.asarray(freq), np.asarray(gain)) # XXX array-api-extra#193 + x = xp.asarray(x) + fx = xp.asarray(fx) + + # Adjust the phases of the coefficients so that the first `ntaps` of the + # inverse FFT are the desired filter coefficients. + shift = xp.exp(-(numtaps - 1) / 2. * 1j * xp.pi * x / nyq) + if ftype > 2: + shift *= 1j + + fx2 = fx * shift + + # Use irfft to compute the inverse FFT. + out_full = irfft(fx2) + + if window is not None: + # Create the window to apply to the filter coefficients. + wind = get_window(window, numtaps, fftbins=False, xp=xp) + else: + wind = 1 + + # Keep only the first `numtaps` coefficients in `out`, and multiply by + # the window. + out = out_full[:numtaps] * wind + + if ftype == 3: + out[xp_size(out) // 2] = 0.0 + + return out + + +def remez(numtaps, bands, desired, *, weight=None, type='bandpass', + maxiter=25, grid_density=16, fs=None): + """ + Calculate the minimax optimal filter using the Remez exchange algorithm. + + Calculate the filter-coefficients for the finite impulse response + (FIR) filter whose transfer function minimizes the maximum error + between the desired gain and the realized gain in the specified + frequency bands using the Remez exchange algorithm. + + Parameters + ---------- + numtaps : int + The desired number of taps in the filter. The number of taps is + the number of terms in the filter, or the filter order plus one. + bands : array_like + A monotonic sequence containing the band edges. + All elements must be non-negative and less than half the sampling + frequency as given by `fs`. + desired : array_like + A sequence half the size of bands containing the desired gain + in each of the specified bands. + weight : array_like, optional + A relative weighting to give to each band region. The length of + `weight` has to be half the length of `bands`. + type : {'bandpass', 'differentiator', 'hilbert'}, optional + The type of filter: + + * 'bandpass' : flat response in bands. This is the default. + * 'differentiator' : frequency proportional response in bands. + * 'hilbert' : filter with odd symmetry, that is, type III + (for even order) or type IV (for odd order) linear phase filters. + + maxiter : int, optional + Maximum number of iterations of the algorithm. Default is 25. + grid_density : int, optional + Grid density. The dense grid used in `remez` is of size + ``(numtaps + 1) * grid_density``. Default is 16. + fs : float, optional + The sampling frequency of the signal. Default is 1. + + Returns + ------- + out : ndarray + A rank-1 array containing the coefficients of the optimal + (in a minimax sense) filter. + + See Also + -------- + firls + firwin + firwin2 + minimum_phase + + References + ---------- + .. [1] J. H. McClellan and T. W. Parks, "A unified approach to the + design of optimum FIR linear phase digital filters", + IEEE Trans. Circuit Theory, vol. CT-20, pp. 697-701, 1973. + .. [2] J. H. McClellan, T. W. Parks and L. R. Rabiner, "A Computer + Program for Designing Optimum FIR Linear Phase Digital + Filters", IEEE Trans. Audio Electroacoust., vol. AU-21, + pp. 506-525, 1973. + + Examples + -------- + In these examples, `remez` is used to design low-pass, high-pass, + band-pass and band-stop filters. The parameters that define each filter + are the filter order, the band boundaries, the transition widths of the + boundaries, the desired gains in each band, and the sampling frequency. + + We'll use a sample frequency of 22050 Hz in all the examples. In each + example, the desired gain in each band is either 0 (for a stop band) + or 1 (for a pass band). + + `freqz` is used to compute the frequency response of each filter, and + the utility function ``plot_response`` defined below is used to plot + the response. + + >>> import numpy as np + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + + >>> fs = 22050 # Sample rate, Hz + + >>> def plot_response(w, h, title): + ... "Utility function to plot response functions" + ... fig = plt.figure() + ... ax = fig.add_subplot(111) + ... ax.plot(w, 20*np.log10(np.abs(h))) + ... ax.set_ylim(-40, 5) + ... ax.grid(True) + ... ax.set_xlabel('Frequency (Hz)') + ... ax.set_ylabel('Gain (dB)') + ... ax.set_title(title) + + The first example is a low-pass filter, with cutoff frequency 8 kHz. + The filter length is 325, and the transition width from pass to stop + is 100 Hz. + + >>> cutoff = 8000.0 # Desired cutoff frequency, Hz + >>> trans_width = 100 # Width of transition from pass to stop, Hz + >>> numtaps = 325 # Size of the FIR filter. + >>> taps = signal.remez(numtaps, [0, cutoff, cutoff + trans_width, 0.5*fs], + ... [1, 0], fs=fs) + >>> w, h = signal.freqz(taps, [1], worN=2000, fs=fs) + >>> plot_response(w, h, "Low-pass Filter") + >>> plt.show() + + This example shows a high-pass filter: + + >>> cutoff = 2000.0 # Desired cutoff frequency, Hz + >>> trans_width = 250 # Width of transition from pass to stop, Hz + >>> numtaps = 125 # Size of the FIR filter. + >>> taps = signal.remez(numtaps, [0, cutoff - trans_width, cutoff, 0.5*fs], + ... [0, 1], fs=fs) + >>> w, h = signal.freqz(taps, [1], worN=2000, fs=fs) + >>> plot_response(w, h, "High-pass Filter") + >>> plt.show() + + This example shows a band-pass filter with a pass-band from 2 kHz to + 5 kHz. The transition width is 260 Hz and the length of the filter + is 63, which is smaller than in the other examples: + + >>> band = [2000, 5000] # Desired pass band, Hz + >>> trans_width = 260 # Width of transition from pass to stop, Hz + >>> numtaps = 63 # Size of the FIR filter. + >>> edges = [0, band[0] - trans_width, band[0], band[1], + ... band[1] + trans_width, 0.5*fs] + >>> taps = signal.remez(numtaps, edges, [0, 1, 0], fs=fs) + >>> w, h = signal.freqz(taps, [1], worN=2000, fs=fs) + >>> plot_response(w, h, "Band-pass Filter") + >>> plt.show() + + The low order leads to higher ripple and less steep transitions. + + The next example shows a band-stop filter. + + >>> band = [6000, 8000] # Desired stop band, Hz + >>> trans_width = 200 # Width of transition from pass to stop, Hz + >>> numtaps = 175 # Size of the FIR filter. + >>> edges = [0, band[0] - trans_width, band[0], band[1], + ... band[1] + trans_width, 0.5*fs] + >>> taps = signal.remez(numtaps, edges, [1, 0, 1], fs=fs) + >>> w, h = signal.freqz(taps, [1], worN=2000, fs=fs) + >>> plot_response(w, h, "Band-stop Filter") + >>> plt.show() + + """ + xp = array_namespace(bands, desired, weight) + bands = np.asarray(bands) + desired = np.asarray(desired) + if weight is not None: + weight = np.asarray(weight) + + fs = _validate_fs(fs, allow_none=True) + fs = 1.0 if fs is None else fs + + # Convert type + try: + tnum = {'bandpass': 1, 'differentiator': 2, 'hilbert': 3}[type] + except KeyError as e: + raise ValueError("Type must be 'bandpass', 'differentiator', " + "or 'hilbert'") from e + + # Convert weight + if weight is None: + weight = [1] * len(desired) + + bands = np.asarray(bands).copy() + result = _sigtools._remez(numtaps, bands, desired, weight, tnum, fs, + maxiter, grid_density) + return xp.asarray(result) + + +def firls(numtaps, bands, desired, *, weight=None, fs=None): + """ + FIR filter design using least-squares error minimization. + + Calculate the filter coefficients for the linear-phase finite + impulse response (FIR) filter which has the best approximation + to the desired frequency response described by `bands` and + `desired` in the least squares sense (i.e., the integral of the + weighted mean-squared error within the specified bands is + minimized). + + Parameters + ---------- + numtaps : int + The number of taps in the FIR filter. `numtaps` must be odd. + bands : array_like + A monotonic nondecreasing sequence containing the band edges in + Hz. All elements must be non-negative and less than or equal to + the Nyquist frequency given by `nyq`. The bands are specified as + frequency pairs, thus, if using a 1D array, its length must be + even, e.g., `np.array([0, 1, 2, 3, 4, 5])`. Alternatively, the + bands can be specified as an nx2 sized 2D array, where n is the + number of bands, e.g, `np.array([[0, 1], [2, 3], [4, 5]])`. + desired : array_like + A sequence the same size as `bands` containing the desired gain + at the start and end point of each band. + weight : array_like, optional + A relative weighting to give to each band region when solving + the least squares problem. `weight` has to be half the size of + `bands`. + fs : float, optional + The sampling frequency of the signal. Each frequency in `bands` + must be between 0 and ``fs/2`` (inclusive). Default is 2. + + Returns + ------- + coeffs : ndarray + Coefficients of the optimal (in a least squares sense) FIR filter. + + See Also + -------- + firwin + firwin2 + minimum_phase + remez + + Notes + ----- + This implementation follows the algorithm given in [1]_. + As noted there, least squares design has multiple advantages: + + 1. Optimal in a least-squares sense. + 2. Simple, non-iterative method. + 3. The general solution can obtained by solving a linear + system of equations. + 4. Allows the use of a frequency dependent weighting function. + + This function constructs a Type I linear phase FIR filter, which + contains an odd number of `coeffs` satisfying for :math:`n < numtaps`: + + .. math:: coeffs(n) = coeffs(numtaps - 1 - n) + + The odd number of coefficients and filter symmetry avoid boundary + conditions that could otherwise occur at the Nyquist and 0 frequencies + (e.g., for Type II, III, or IV variants). + + .. versionadded:: 0.18 + + References + ---------- + .. [1] Ivan Selesnick, Linear-Phase Fir Filter Design By Least Squares. + OpenStax CNX. Aug 9, 2005. + https://eeweb.engineering.nyu.edu/iselesni/EL713/firls/firls.pdf + + Examples + -------- + We want to construct a band-pass filter. Note that the behavior in the + frequency ranges between our stop bands and pass bands is unspecified, + and thus may overshoot depending on the parameters of our filter: + + >>> import numpy as np + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + >>> fig, axs = plt.subplots(2) + >>> fs = 10.0 # Hz + >>> desired = (0, 0, 1, 1, 0, 0) + >>> for bi, bands in enumerate(((0, 1, 2, 3, 4, 5), (0, 1, 2, 4, 4.5, 5))): + ... fir_firls = signal.firls(73, bands, desired, fs=fs) + ... fir_remez = signal.remez(73, bands, desired[::2], fs=fs) + ... fir_firwin2 = signal.firwin2(73, bands, desired, fs=fs) + ... hs = list() + ... ax = axs[bi] + ... for fir in (fir_firls, fir_remez, fir_firwin2): + ... freq, response = signal.freqz(fir) + ... hs.append(ax.semilogy(0.5*fs*freq/np.pi, np.abs(response))[0]) + ... for band, gains in zip(zip(bands[::2], bands[1::2]), + ... zip(desired[::2], desired[1::2])): + ... ax.semilogy(band, np.maximum(gains, 1e-7), 'k--', linewidth=2) + ... if bi == 0: + ... ax.legend(hs, ('firls', 'remez', 'firwin2'), + ... loc='lower center', frameon=False) + ... else: + ... ax.set_xlabel('Frequency (Hz)') + ... ax.grid(True) + ... ax.set(title='Band-pass %d-%d Hz' % bands[2:4], ylabel='Magnitude') + ... + >>> fig.tight_layout() + >>> plt.show() + + """ + xp = array_namespace(bands, desired) + bands = np.asarray(bands) + desired = np.asarray(desired) + + fs = _validate_fs(fs, allow_none=True) + fs = 2 if fs is None else fs + nyq = 0.5 * fs + + numtaps = int(numtaps) + if numtaps % 2 == 0 or numtaps < 1: + raise ValueError("numtaps must be odd and >= 1") + M = (numtaps-1) // 2 + + # normalize bands 0->1 and make it 2 columns + nyq = float(nyq) + if nyq <= 0: + raise ValueError(f'nyq must be positive, got {nyq} <= 0.') + bands = np.asarray(bands).flatten() / nyq + if len(bands) % 2 != 0: + raise ValueError("bands must contain frequency pairs.") + if (bands < 0).any() or (bands > 1).any(): + raise ValueError("bands must be between 0 and 1 relative to Nyquist") + bands = bands.reshape((-1, 2)) + + # check remaining params + desired = np.asarray(desired).flatten() + if bands.size != desired.size: + raise ValueError( + f"desired must have one entry per frequency, got {desired.size} " + f"gains for {bands.size} frequencies." + ) + desired = desired.reshape((-1, 2)) + if (np.diff(bands) <= 0).any() or (np.diff(bands[:, 0]) < 0).any(): + raise ValueError("bands must be monotonically nondecreasing and have " + "width > 0.") + if (bands[:-1, 1] > bands[1:, 0]).any(): + raise ValueError("bands must not overlap.") + if (desired < 0).any(): + raise ValueError("desired must be non-negative.") + if weight is None: + weight = np.ones(len(desired)) + weight = np.asarray(weight).flatten() + if len(weight) != len(desired): + raise ValueError("weight must be the same size as the number of " + f"band pairs ({len(bands)}).") + if (weight < 0).any(): + raise ValueError("weight must be non-negative.") + + # Set up the linear matrix equation to be solved, Qa = b + + # We can express Q(k,n) = 0.5 Q1(k,n) + 0.5 Q2(k,n) + # where Q1(k,n)=q(k-n) and Q2(k,n)=q(k+n), i.e. a Toeplitz plus Hankel. + + # We omit the factor of 0.5 above, instead adding it during coefficient + # calculation. + + # We also omit the 1/π from both Q and b equations, as they cancel + # during solving. + + # We have that: + # q(n) = 1/π ∫W(ω)cos(nω)dω (over 0->π) + # Using our normalization ω=πf and with a constant weight W over each + # interval f1->f2 we get: + # q(n) = W∫cos(πnf)df (0->1) = Wf sin(πnf)/πnf + # integrated over each f1->f2 pair (i.e., value at f2 - value at f1). + n = np.arange(numtaps)[:, np.newaxis, np.newaxis] + q = np.dot(np.diff(np.sinc(bands * n) * bands, axis=2)[:, :, 0], weight) + + # Now we assemble our sum of Toeplitz and Hankel + Q1 = toeplitz(q[:M+1]) + Q2 = hankel(q[:M+1], q[M:]) + Q = Q1 + Q2 + + # Now for b(n) we have that: + # b(n) = 1/π ∫ W(ω)D(ω)cos(nω)dω (over 0->π) + # Using our normalization ω=πf and with a constant weight W over each + # interval and a linear term for D(ω) we get (over each f1->f2 interval): + # b(n) = W ∫ (mf+c)cos(πnf)df + # = f(mf+c)sin(πnf)/πnf + mf**2 cos(nπf)/(πnf)**2 + # integrated over each f1->f2 pair (i.e., value at f2 - value at f1). + n = n[:M + 1] # only need this many coefficients here + # Choose m and c such that we are at the start and end weights + m = (np.diff(desired, axis=1) / np.diff(bands, axis=1)) + c = desired[:, [0]] - bands[:, [0]] * m + b = bands * (m*bands + c) * np.sinc(bands * n) + # Use L'Hospital's rule here for cos(nπf)/(πnf)**2 @ n=0 + b[0] -= m * bands * bands / 2. + b[1:] += m * np.cos(n[1:] * np.pi * bands) / (np.pi * n[1:]) ** 2 + b = np.dot(np.diff(b, axis=2)[:, :, 0], weight) + + # Now we can solve the equation + try: # try the fast way + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + a = solve(Q, b, assume_a="pos", check_finite=False) + for ww in w: + if (ww.category == LinAlgWarning and + str(ww.message).startswith('Ill-conditioned matrix')): + raise LinAlgError(str(ww.message)) + except LinAlgError: # in case Q is rank deficient + # This is faster than pinvh, even though we don't explicitly use + # the symmetry here. gelsy was faster than gelsd and gelss in + # some non-exhaustive tests. + a = lstsq(Q, b, lapack_driver='gelsy')[0] + + # make coefficients symmetric (linear phase) + coeffs = np.hstack((a[:0:-1], 2 * a[0], a[1:])) + return xp.asarray(coeffs) + + +def _dhtm(mag, xp): + """Compute the modified 1-D discrete Hilbert transform + + Parameters + ---------- + mag : ndarray + The magnitude spectrum. Should be 1-D with an even length, and + preferably a fast length for FFT/IFFT. + """ + # Adapted based on code by Niranjan Damera-Venkata, + # Brian L. Evans and Shawn R. McCaslin (see refs for `minimum_phase`) + sig = xp.zeros(mag.shape[0]) + # Leave Nyquist and DC at 0, knowing np.abs(fftfreq(N)[midpt]) == 0.5 + midpt = mag.shape[0] // 2 + sig = xpx.at(sig)[1:midpt].set(1.0) + sig = xpx.at(sig)[midpt+1:].set(-1.0) + # eventually if we want to support complex filters, we will need a + # np.abs() on the mag inside the log, and should remove the .real + recon = xp.real(ifft(mag * xp.exp(fft(sig * ifft(xp.log(mag)))))) + return recon + + +def minimum_phase(h, + method: Literal['homomorphic', 'hilbert'] = 'homomorphic', + n_fft: int | None = None, *, half: bool = True): + """Convert a linear-phase FIR filter to minimum phase. + + Parameters + ---------- + h : array + Linear-phase FIR filter coefficients. + method : {'hilbert', 'homomorphic'} + The provided methods are: + + - 'homomorphic' (default): + This method [4]_ [5]_ works best with filters with an + odd number of taps, and the resulting minimum phase filter + will have a magnitude response that approximates the square + root of the original filter's magnitude response using half + the number of taps when ``half=True`` (default), or the + original magnitude spectrum using the same number of taps + when ``half=False``. + - 'hilbert' + This method [1]_ is designed to be used with equiripple + filters (e.g., from `remez`) with unity or zero gain + regions. + + n_fft : int + The number of points to use for the FFT. Should be at least a + few times larger than the signal length (see Notes). + half : bool + If ``True``, create a filter that is half the length of the original, with a + magnitude spectrum that is the square root of the original. If ``False``, + create a filter that is the same length as the original, with a magnitude + spectrum that is designed to match the original (only supported when + ``method='homomorphic'``). + + .. versionadded:: 1.14.0 + + Returns + ------- + h_minimum : array + The minimum-phase version of the filter, with length + ``(len(h) + 1) // 2`` when ``half is True`` or ``len(h)`` otherwise. + + See Also + -------- + firwin + firwin2 + remez + + Notes + ----- + Both the Hilbert [1]_ or homomorphic [4]_ [5]_ methods require selection + of an FFT length to estimate the complex cepstrum of the filter. + + In the case of the Hilbert method, the deviation from the ideal + spectrum ``epsilon`` is related to the number of stopband zeros + ``n_stop`` and FFT length ``n_fft`` as:: + + epsilon = 2. * n_stop / n_fft + + For example, with 100 stopband zeros and a FFT length of 2048, + ``epsilon = 0.0976``. If we conservatively assume that the number of + stopband zeros is one less than the filter length, we can take the FFT + length to be the next power of 2 that satisfies ``epsilon=0.01`` as:: + + n_fft = 2 ** int(np.ceil(np.log2(2 * (len(h) - 1) / 0.01))) + + This gives reasonable results for both the Hilbert and homomorphic + methods, and gives the value used when ``n_fft=None``. + + Alternative implementations exist for creating minimum-phase filters, + including zero inversion [2]_ and spectral factorization [3]_ [4]_. + For more information, see `this DSPGuru page + `__. + + References + ---------- + .. [1] N. Damera-Venkata and B. L. Evans, "Optimal design of real and + complex minimum phase digital FIR filters," Acoustics, Speech, + and Signal Processing, 1999. Proceedings., 1999 IEEE International + Conference on, Phoenix, AZ, 1999, pp. 1145-1148 vol.3. + :doi:`10.1109/ICASSP.1999.756179` + .. [2] X. Chen and T. W. Parks, "Design of optimal minimum phase FIR + filters by direct factorization," Signal Processing, + vol. 10, no. 4, pp. 369-383, Jun. 1986. + .. [3] T. Saramaki, "Finite Impulse Response Filter Design," in + Handbook for Digital Signal Processing, chapter 4, + New York: Wiley-Interscience, 1993. + .. [4] J. S. Lim, Advanced Topics in Signal Processing. + Englewood Cliffs, N.J.: Prentice Hall, 1988. + .. [5] A. V. Oppenheim, R. W. Schafer, and J. R. Buck, + "Discrete-Time Signal Processing," 3rd edition. + Upper Saddle River, N.J.: Pearson, 2009. + + Examples + -------- + Create an optimal linear-phase low-pass filter `h` with a transition band of + [0.2, 0.3] (assuming a Nyquist frequency of 1): + + >>> import numpy as np + >>> from scipy.signal import remez, minimum_phase, freqz, group_delay + >>> import matplotlib.pyplot as plt + >>> freq = [0, 0.2, 0.3, 1.0] + >>> desired = [1, 0] + >>> h_linear = remez(151, freq, desired, fs=2) + + Convert it to minimum phase: + + >>> h_hil = minimum_phase(h_linear, method='hilbert') + >>> h_hom = minimum_phase(h_linear, method='homomorphic') + >>> h_hom_full = minimum_phase(h_linear, method='homomorphic', half=False) + + Compare the impulse and frequency response of the four filters: + + >>> fig0, ax0 = plt.subplots(figsize=(6, 3), tight_layout=True) + >>> fig1, axs = plt.subplots(3, sharex='all', figsize=(6, 6), tight_layout=True) + >>> ax0.set_title("Impulse response") + >>> ax0.set(xlabel='Samples', ylabel='Amplitude', xlim=(0, len(h_linear) - 1)) + >>> axs[0].set_title("Frequency Response") + >>> axs[0].set(xlim=(0, .65), ylabel="Magnitude / dB") + >>> axs[1].set(ylabel="Phase / rad") + >>> axs[2].set(ylabel="Group Delay / samples", ylim=(-31, 81), + ... xlabel='Normalized Frequency (Nyqist frequency: 1)') + >>> for h, lb in ((h_linear, f'Linear ({len(h_linear)})'), + ... (h_hil, f'Min-Hilbert ({len(h_hil)})'), + ... (h_hom, f'Min-Homomorphic ({len(h_hom)})'), + ... (h_hom_full, f'Min-Homom. Full ({len(h_hom_full)})')): + ... w_H, H = freqz(h, fs=2) + ... w_gd, gd = group_delay((h, 1), fs=2) + ... + ... alpha = 1.0 if lb == 'linear' else 0.5 # full opacity for 'linear' line + ... ax0.plot(h, '.-', alpha=alpha, label=lb) + ... axs[0].plot(w_H, 20 * np.log10(np.abs(H)), alpha=alpha) + ... axs[1].plot(w_H, np.unwrap(np.angle(H)), alpha=alpha, label=lb) + ... axs[2].plot(w_gd, gd, alpha=alpha) + >>> ax0.grid(True) + >>> ax0.legend(title='Filter Phase (Order)') + >>> axs[1].legend(title='Filter Phase (Order)', loc='lower right') + >>> for ax_ in axs: # shade transition band: + ... ax_.axvspan(freq[1], freq[2], color='y', alpha=.25) + ... ax_.grid(True) + >>> plt.show() + + The impulse response and group delay plot depict the 75 sample delay of the linear + phase filter `h`. The phase should also be linear in the stop band--due to the small + magnitude, numeric noise dominates there. Furthermore, the plots show that the + minimum phase filters clearly show a reduced (negative) phase slope in the pass and + transition band. The plots also illustrate that the filter with parameters + ``method='homomorphic', half=False`` has same order and magnitude response as the + linear filter `h` whereas the other minimum phase filters have only half the order + and the square root of the magnitude response. + """ + xp = array_namespace(h) + + h = xp.asarray(h) + if xp.isdtype(h.dtype, "complex floating"): + raise ValueError('Complex filters not supported') + if h.ndim != 1 or h.shape[0] <= 2: + raise ValueError('h must be 1-D and at least 2 samples long') + n_half = h.shape[0] // 2 + + if not xp.any(xp.flip(h[-n_half:]) - h[:n_half] <= 1e-8 + 1e-6*abs(h[:n_half])): + warnings.warn('h does not appear to by symmetric, conversion may fail', + RuntimeWarning, stacklevel=2) + if not isinstance(method, str) or method not in \ + ('homomorphic', 'hilbert',): + raise ValueError(f'method must be "homomorphic" or "hilbert", got {method!r}') + if method == "hilbert" and not half: + raise ValueError("`half=False` is only supported when `method='homomorphic'`") + if n_fft is None: + n_fft = 2 ** int(ceil(log2(2 * (h.shape[0] - 1) / 0.01))) + n_fft = int(n_fft) + if n_fft < h.shape[0]: + raise ValueError(f'n_fft must be at least len(h)=={len(h)}') + + if method == 'hilbert': + w = xp.arange(n_fft, dtype=xp.float64) * (2 * xp.pi / n_fft * n_half) + H = xp.real(fft(h, n_fft) * xp.exp(1j * w)) + dp = max(H) - 1 + ds = 0 - min(H) + S = 4. / (xp.sqrt(1+dp+ds) + xp.sqrt(1-dp+ds)) ** 2 + H += ds + H *= S + H = xp.sqrt(H) + H += 1e-10 # ensure that the log does not explode + h_minimum = _dhtm(H, xp) + else: # method == 'homomorphic' + # zero-pad; calculate the DFT + h_temp = xp.abs(fft(h, n_fft)) + # take 0.25*log(|H|**2) = 0.5*log(|H|) + h_temp += 1e-7 * xp.min(h_temp[h_temp > 0]) # don't let log blow up + h_temp = xp.log(h_temp) + if half: # halving of magnitude spectrum optional + h_temp *= 0.5 + # IDFT + h_temp = xp.real(ifft(h_temp)) + # multiply pointwise by the homomorphic filter + # lmin[n] = 2u[n] - d[n] + # i.e., double the positive frequencies and zero out the negative ones; + # Oppenheim+Shafer 3rd ed p991 eq13.42b and p1004 fig13.7 + win = xp.zeros(n_fft, dtype=h_temp.dtype) + win = xpx.at(win)[0].set(1) + stop = n_fft // 2 + win = xpx.at(win)[1:stop].set(2) + # Nyquist freq: odd use 2, even use 1 + win = xpx.at(win)[stop].set(1 + (n_fft % 2)) + h_temp *= win + h_temp = ifft(xp.exp(fft(h_temp))) + h_minimum = xp.real(h_temp) + n_out = (n_half + h.shape[0] % 2) if half else h.shape[0] + return h_minimum[:n_out] + + +def firwin_2d(hsize, window, *, fc=None, fs=2, circular=False, + pass_zero=True, scale=True): + """ + 2D FIR filter design using the window method. + + This function computes the coefficients of a 2D finite impulse response + filter. The filter is separable with linear phase; it will be designed + as a product of two 1D filters with dimensions defined by `hsize`. + Additionally, it can create approximately circularly symmetric 2-D windows. + + Parameters + ---------- + hsize : tuple or list of length 2 + Lengths of the filter in each dimension. `hsize[0]` specifies the + number of coefficients in the row direction and `hsize[1]` specifies + the number of coefficients in the column direction. + window : tuple or list of length 2 or str + Desired window to use for each 1D filter or a single window type for creating + circularly symmetric 2-D windows. Each element should be a string or tuple of + string and parameter values. The generated windows will be symmetric, unless a + suffix ``'_periodic'`` is appended to the window name (e.g., + ``'hamming_perodic'``). Consult `~scipy.signal.get_window` for a list of windows + and required parameters. + fc : float or 1-D array_like, optional + Cutoff frequency of the filter in the same units as `fs`. This defines + the frequency at which the filter's gain drops to approximately -6 dB + (half power) in a low-pass or high-pass filter. For multi-band filters, + `fc` can be an array of cutoff frequencies (i.e., band edges) in the + range [0, fs/2], with each band specified in pairs. Required if + `circular` is False. + fs : float, optional + The sampling frequency of the signal. Default is 2. + circular : bool, optional + Whether to create a circularly symmetric 2-D window. Default is ``False``. + pass_zero : {True, False, 'bandpass', 'lowpass', 'highpass', 'bandstop'}, optional + This parameter is directly passed to `firwin` for each scalar frequency axis. + Hence, if ``True``, the DC gain, i.e., the gain at frequency (0, 0), is 1. + If ``False``, the DC gain is 0 at frequency (0, 0) if `circular` is ``True``. + If `circular` is ``False`` the frequencies (0, f1) and (f0, 0) will + have gain 0. + It can also be a string argument for the desired filter type + (equivalent to ``btype`` in IIR design functions). + scale : bool, optional + This parameter is directly passed to `firwin` for each scalar frequency axis. + Set to ``True`` to scale the coefficients so that the frequency + response is exactly unity at a certain frequency on one frequency axis. + That frequency is either: + + - 0 (DC) if the first passband starts at 0 (i.e. pass_zero is ``True``) + - `fs`/2 (the Nyquist frequency) if the first passband ends at `fs`/2 + (i.e., the filter is a single band highpass filter); + center of first passband otherwise + + Returns + ------- + filter_2d : (hsize[0], hsize[1]) ndarray + Coefficients of 2D FIR filter. + + Raises + ------ + ValueError + - If `hsize` and `window` are not 2-element tuples or lists. + - If `cutoff` is None when `circular` is True. + - If `cutoff` is outside the range [0, `fs`/2] and `circular` is ``False``. + - If any of the elements in `window` are not recognized. + RuntimeError + If `firwin` fails to converge when designing the filter. + + See Also + -------- + firwin: FIR filter design using the window method for 1d arrays. + get_window: Return a window of a given length and type. + + Examples + -------- + Generate a 5x5 low-pass filter with cutoff frequency 0.1: + + >>> import numpy as np + >>> from scipy.signal import get_window + >>> from scipy.signal import firwin_2d + >>> hsize = (5, 5) + >>> window = (("kaiser", 5.0), ("kaiser", 5.0)) + >>> fc = 0.1 + >>> filter_2d = firwin_2d(hsize, window, fc=fc) + >>> filter_2d + array([[0.00025366, 0.00401662, 0.00738617, 0.00401662, 0.00025366], + [0.00401662, 0.06360159, 0.11695714, 0.06360159, 0.00401662], + [0.00738617, 0.11695714, 0.21507283, 0.11695714, 0.00738617], + [0.00401662, 0.06360159, 0.11695714, 0.06360159, 0.00401662], + [0.00025366, 0.00401662, 0.00738617, 0.00401662, 0.00025366]]) + + Generate a circularly symmetric 5x5 low-pass filter with Hamming window: + + >>> filter_2d = firwin_2d((5, 5), 'hamming', fc=fc, circular=True) + >>> filter_2d + array([[-0.00020354, -0.00020354, -0.00020354, -0.00020354, -0.00020354], + [-0.00020354, 0.01506844, 0.09907658, 0.01506844, -0.00020354], + [-0.00020354, 0.09907658, -0.00020354, 0.09907658, -0.00020354], + [-0.00020354, 0.01506844, 0.09907658, 0.01506844, -0.00020354], + [-0.00020354, -0.00020354, -0.00020354, -0.00020354, -0.00020354]]) + + Generate Plots comparing the product of two 1d filters with a circular + symmetric filter: + + >>> import matplotlib.pyplot as plt + >>> hsize, fc = (50, 50), 0.05 + >>> window = (("kaiser", 5.0), ("kaiser", 5.0)) + >>> filter0_2d = firwin_2d(hsize, window, fc=fc) + >>> filter1_2d = firwin_2d((50, 50), 'hamming', fc=fc, circular=True) + ... + >>> fg, (ax0, ax1) = plt.subplots(1, 2, tight_layout=True, figsize=(6.5, 3.5)) + >>> ax0.set_title("Product of 2 Windows") + >>> im0 = ax0.imshow(filter0_2d, cmap='viridis', origin='lower', aspect='equal') + >>> fg.colorbar(im0, ax=ax0, shrink=0.7) + >>> ax1.set_title("Circular Window") + >>> im1 = ax1.imshow(filter1_2d, cmap='plasma', origin='lower', aspect='equal') + >>> fg.colorbar(im1, ax=ax1, shrink=0.7) + >>> plt.show() + """ + if len(hsize) != 2: + raise ValueError("hsize must be a 2-element tuple or list") + + if circular: + if fc is None: + raise ValueError("Cutoff frequency `fc` must be " + "provided when `circular` is True") + + n_r = max(hsize[0], hsize[1]) * 8 # oversample 1d window by factor 8 + + win_r = firwin(n_r, cutoff=fc, window=window, fs=fs) + + f1, f2 = np.meshgrid(np.linspace(-1, 1, hsize[0]), np.linspace(-1, 1, hsize[1])) + r = np.sqrt(f1**2 + f2**2) + + win_2d = np.interp(r, np.linspace(0, 1, n_r), win_r) + return win_2d + + if len(window) != 2: + raise ValueError("window must be a 2-element tuple or list") + + row_filter = firwin(hsize[0], cutoff=fc, window=window[0], fs=fs) + col_filter = firwin(hsize[1], cutoff=fc, window=window[1], fs=fs) + + return np.outer(row_filter, col_filter) diff --git a/venv/lib/python3.12/site-packages/scipy/signal/_lti_conversion.py b/venv/lib/python3.12/site-packages/scipy/signal/_lti_conversion.py new file mode 100644 index 0000000000000000000000000000000000000000..22074ba4b47a822f00301a2114aaec3a94bce7b8 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/_lti_conversion.py @@ -0,0 +1,583 @@ +""" +ltisys -- a collection of functions to convert linear time invariant systems +from one representation to another. +""" + +import numpy as np +from numpy import (r_, eye, atleast_2d, poly, dot, + asarray, zeros, array, outer) +from scipy import linalg + +from scipy._lib._array_api import (array_namespace, xp_size, xp_promote, + xp_result_type) +import scipy._external.array_api_extra as xpx +from ._filter_design import tf2zpk, zpk2tf, normalize + + +__all__ = ['tf2ss', 'abcd_normalize', 'ss2tf', 'zpk2ss', 'ss2zpk', + 'cont2discrete'] + + +def tf2ss(num, den): + r"""Transfer function to state-space representation. + + Parameters + ---------- + num, den : array_like + Sequences representing the coefficients of the numerator and + denominator polynomials, in order of descending degree. The + denominator needs to be at least as long as the numerator. + + Returns + ------- + A, B, C, D : ndarray + State space representation of the system, in controller canonical + form. + + Examples + -------- + Convert the transfer function: + + .. math:: H(s) = \frac{s^2 + 3s + 3}{s^2 + 2s + 1} + + >>> num = [1, 3, 3] + >>> den = [1, 2, 1] + + to the state-space representation: + + .. math:: + + \dot{\textbf{x}}(t) = + \begin{bmatrix} -2 & -1 \\ 1 & 0 \end{bmatrix} \textbf{x}(t) + + \begin{bmatrix} 1 \\ 0 \end{bmatrix} \textbf{u}(t) \\ + + \textbf{y}(t) = \begin{bmatrix} 1 & 2 \end{bmatrix} \textbf{x}(t) + + \begin{bmatrix} 1 \end{bmatrix} \textbf{u}(t) + + >>> from scipy.signal import tf2ss + >>> A, B, C, D = tf2ss(num, den) + >>> A + array([[-2., -1.], + [ 1., 0.]]) + >>> B + array([[ 1.], + [ 0.]]) + >>> C + array([[ 1., 2.]]) + >>> D + array([[ 1.]]) + """ + # Controller canonical state-space representation. + # if M+1 = len(num) and K+1 = len(den) then we must have M <= K + # states are found by asserting that X(s) = U(s) / D(s) + # then Y(s) = N(s) * X(s) + # + # A, B, C, and D follow quite naturally. + # + num, den = normalize(num, den) # Strips zeros, checks arrays + nn = len(num.shape) + if nn == 1: + num = asarray([num], num.dtype) + M = num.shape[1] + K = len(den) + if M > K: + msg = "Improper transfer function. `num` is longer than `den`." + raise ValueError(msg) + if M == 0 or K == 0: # Null system + return (array([], float), array([], float), array([], float), + array([], float)) + + # pad numerator to have same number of columns has denominator + num = np.hstack((np.zeros((num.shape[0], K - M), dtype=num.dtype), num)) + + if num.shape[-1] > 0: + D = atleast_2d(num[:, 0]) + + else: + # We don't assign it an empty array because this system + # is not 'null'. It just doesn't have a non-zero D + # matrix. Thus, it should have a non-zero shape so that + # it can be operated on by functions like 'ss2tf' + D = array([[0]], float) + + if K == 1: + D = D.reshape(num.shape) + + return (zeros((1, 1)), zeros((1, D.shape[1])), + zeros((D.shape[0], 1)), D) + + frow = -array([den[1:]]) + A = r_[frow, eye(K - 2, K - 1)] + B = eye(K - 1, 1) + C = num[:, 1:] - outer(num[:, 0], den[1:]) + D = D.reshape((C.shape[0], B.shape[1])) + + return A, B, C, D + + +def abcd_normalize(A=None, B=None, C=None, D=None): + r"""Check state-space matrices compatibility and ensure they are 2d arrays. + + First, the input matrices are converted into two-dimensional arrays with + appropriate dtype as needed. Then the dimensions n, q, p are determined by + investigating the array shapes. If an input is ``None``, or has size zero, it is + set to an array of zeros of compatible shape. Finally, it is verified that all + parameter shapes are compatible with each other. If that fails, a ``ValueError`` is + raised. Note that the dimensions n, q, p are allowed to be zero. + + Parameters + ---------- + A : array_like, optional + Two-dimensional array of shape (n, n). + B : array_like, optional + Two-dimensional array of shape (n, p). + C : array_like, optional + Two-dimensional array of shape (q, n). + D : array_like, optional + Two-dimensional array of shape (q, p). + + Returns + ------- + A, B, C, D : array + State-space matrices as two-dimensional arrays with identical dtype. + The result dtype is determined based on the standard + `dtype promotion rules `_ + except for when the inputs are all of integer dtype, in which case the returned + arrays will have the default floating point dtype of ``float64``. + + Raises + ------ + ValueError + If the dimensions n, q, or p could not be determined or if the shapes are + incompatible with each other. + + Notes + ----- + If a matrix is not modified, the original matrix (not a copy) is returned. + + The :ref:`tutorial_signal_state_space_representation` section of the + :ref:`user_guide` presents the corresponding definitions of continuous-time and + disrcete time state space systems. + + See Also + -------- + StateSpace: Linear Time Invariant system in state-space form. + dlti: Discrete-time linear time invariant system base class. + tf2ss: Transfer function to state-space representation. + ss2tf: State-space to transfer function. + ss2zpk: State-space representation to zero-pole-gain representation. + cont2discrete: Transform a continuous to a discrete state-space system. + + Examples + -------- + The following example demonstrates that the passed lists are converted into + two-dimensional arrays: + + >>> from scipy.signal import abcd_normalize + >>> AA, BB, CC, DD = abcd_normalize(A=[[1, 2], [3, 4]], B=[[-1], [5]], + ... C=[[4, 5]], D=2.5) + >>> AA.shape, BB.shape, CC.shape, DD.shape + ((2, 2), (2, 1), (1, 2), (1, 1)) + + In the following, the missing parameter C is assumed to be an array of zeros + with shape (1, 2): + + >>> from scipy.signal import abcd_normalize + >>> AA, BB, CC, DD = abcd_normalize(A=[[1, 2], [3, 4]], B=[[-1], [5]], D=2.5) + >>> AA.shape, BB.shape, CC.shape, DD.shape + ((2, 2), (2, 1), (1, 2), (1, 1)) + >>> CC + array([[0., 0.]]) + + """ + if A is None and B is None and C is None: + raise ValueError("Dimension n is undefined for parameters A = B = C = None!") + if B is None and D is None: + raise ValueError("Dimension p is undefined for parameters B = D = None!") + if C is None and D is None: + raise ValueError("Dimension q is undefined for parameters C = D = None!") + + xp = array_namespace(A, B, C, D) + A, B, C, D = xp_promote(A, B, C, D, xp=xp, force_floating=True) + dtype = xp_result_type(A, B, C, D, xp=xp) + + # convert inputs into 2d arrays (zero-size 2d array if None): + A, B, C, D = ( + xpx.atleast_nd(xp.asarray(M_), ndim=2, xp=xp) + if M_ is not None else xp.zeros((0, 0), dtype=dtype) + for M_ in (A, B, C, D) + ) + + n = A.shape[0] or B.shape[0] or C.shape[1] # try finding non-zero dimensions + p = B.shape[1] or D.shape[1] + q = C.shape[0] or D.shape[0] + + # Create zero matrices as needed: + A = xp.zeros((n, n), dtype=dtype) if xp_size(A) == 0 else A + B = xp.zeros((n, p), dtype=dtype) if xp_size(B) == 0 else B + C = xp.zeros((q, n), dtype=dtype) if xp_size(C) == 0 else C + D = xp.zeros((q, p), dtype=dtype) if xp_size(D) == 0 else D + + if A.shape != (n, n): + raise ValueError(f"Parameter A has shape {A.shape} but should be ({n}, {n})!") + if B.shape != (n, p): + raise ValueError(f"Parameter B has shape {B.shape} but should be ({n}, {p})!") + if C.shape != (q, n): + raise ValueError(f"Parameter C has shape {C.shape} but should be ({q}, {n})!") + if D.shape != (q, p): + raise ValueError(f"Parameter D has shape {D.shape} but should be ({q}, {p})!") + + return A, B, C, D + + +def ss2tf(A, B, C, D, input=0): + r"""State-space to transfer function. + + A, B, C, D defines a linear state-space system with `p` inputs, + `q` outputs, and `n` state variables. + + Parameters + ---------- + A : array_like + State (or system) matrix of shape ``(n, n)`` + B : array_like + Input matrix of shape ``(n, p)`` + C : array_like + Output matrix of shape ``(q, n)`` + D : array_like + Feedthrough (or feedforward) matrix of shape ``(q, p)`` + input : int, optional + For multiple-input systems, the index of the input to use. + + Returns + ------- + num : 2-D ndarray + Numerator(s) of the resulting transfer function(s). `num` has one row + for each of the system's outputs. Each row is a sequence representation + of the numerator polynomial. + den : 1-D ndarray + Denominator of the resulting transfer function(s). `den` is a sequence + representation of the denominator polynomial. + + Notes + ----- + Before calculating `num` and `den`, the function `abcd_normalize` is called to + convert the parameters `A`, `B`, `C`, `D` into two-dimesional arrays of the + same dtype. The resulting dtype will be based on NumPy's dtype promotion rules, + except in the case where each of `A`, `B`, `C`, and `D` has integer dtype, in which + case the resulting dtype will be the default floating point dtype of ``float64``. + + The :ref:`tutorial_signal_state_space_representation` section of the + :ref:`user_guide` presents the corresponding definitions of continuous-time and + disrcete time state space systems. + + Examples + -------- + Convert the state-space representation: + + .. math:: + + \dot{\textbf{x}}(t) = + \begin{bmatrix} -2 & -1 \\ 1 & 0 \end{bmatrix} \textbf{x}(t) + + \begin{bmatrix} 1 \\ 0 \end{bmatrix} \textbf{u}(t) \\ + + \textbf{y}(t) = \begin{bmatrix} 1 & 2 \end{bmatrix} \textbf{x}(t) + + \begin{bmatrix} 1 \end{bmatrix} \textbf{u}(t) + + >>> A = [[-2, -1], [1, 0]] + >>> B = [[1], [0]] # 2-D column vector + >>> C = [[1, 2]] # 2-D row vector + >>> D = 1 + + to the transfer function: + + .. math:: H(s) = \frac{s^2 + 3s + 3}{s^2 + 2s + 1} + + >>> from scipy.signal import ss2tf + >>> ss2tf(A, B, C, D) + (array([[1., 3., 3.]]), array([ 1., 2., 1.])) + """ + # transfer function is C (sI - A)**(-1) B + D + + # Check consistency and make them all floating point 2d arrays: + A, B, C, D = abcd_normalize(A, B, C, D) + + nout, nin = D.shape + if input >= nin: + raise ValueError("System does not have the input specified.") + + # make SIMO from possibly MIMO system. + B = B[:, input:input + 1] + D = D[:, input:input + 1] + + try: + den = poly(A) + except ValueError: + den = 1 + + if (B.size == 0) and (C.size == 0): + num = np.ravel(D) + if (D.size == 0) and (A.size == 0): + den = [] + return num, den + + num_states = A.shape[0] + type_test = A[:, 0] + B[:, 0] + C[0, :] + D + 0.0 + num = np.empty((nout, num_states + 1), type_test.dtype) + for k in range(nout): + Ck = atleast_2d(C[k, :]) + num[k] = poly(A - dot(B, Ck)) + (D[k] - 1) * den + + return num, den + + +def zpk2ss(z, p, k): + """Zero-pole-gain representation to state-space representation. + + Parameters + ---------- + z, p : sequence + Zeros and poles. + k : float + System gain. + + Returns + ------- + A, B, C, D : ndarray + State space representation of the system, in controller canonical + form. + + """ + return tf2ss(*zpk2tf(z, p, k)) + + +def ss2zpk(A, B, C, D, input=0): + """State-space representation to zero-pole-gain representation. + + A, B, C, D defines a linear state-space system with `p` inputs, + `q` outputs, and `n` state variables. + + Parameters + ---------- + A : array_like + State (or system) matrix of shape ``(n, n)`` + B : array_like + Input matrix of shape ``(n, p)`` + C : array_like + Output matrix of shape ``(q, n)`` + D : array_like + Feedthrough (or feedforward) matrix of shape ``(q, p)`` + input : int, optional + For multiple-input systems, the index of the input to use. + + Returns + ------- + z, p : sequence + Zeros and poles. + k : float + System gain. + + """ + return tf2zpk(*ss2tf(A, B, C, D, input=input)) + + +def cont2discrete(system, dt, method="zoh", alpha=None): + """ + Transform a continuous to a discrete state-space system. + + Parameters + ---------- + system : a tuple describing the system or an instance of `lti` + The following gives the number of elements in the tuple and + the interpretation: + + * 1: (instance of `lti`) + * 2: (num, den) + * 3: (zeros, poles, gain) + * 4: (A, B, C, D) + + dt : float + The discretization time step. + method : str, optional + Which method to use: + + * gbt: generalized bilinear transformation + * bilinear: Tustin's approximation ("gbt" with alpha=0.5) + * euler: Euler (or forward differencing) method ("gbt" with alpha=0) + * backward_diff: Backwards differencing ("gbt" with alpha=1.0) + * zoh: zero-order hold (default) + * foh: first-order hold (*versionadded: 1.3.0*) + * impulse: equivalent impulse response (*versionadded: 1.3.0*) + + alpha : float within [0, 1], optional + The generalized bilinear transformation weighting parameter, which + should only be specified with method="gbt", and is ignored otherwise + + Returns + ------- + sysd : tuple containing the discrete system + Based on the input type, the output will be of the form + + * (num, den, dt) for transfer function input + * (zeros, poles, gain, dt) for zeros-poles-gain input + * (A, B, C, D, dt) for state-space system input + + Notes + ----- + By default, the routine uses a Zero-Order Hold (zoh) method to perform + the transformation. Alternatively, a generalized bilinear transformation + may be used, which includes the common Tustin's bilinear approximation, + an Euler's method technique, or a backwards differencing technique. + + The Zero-Order Hold (zoh) method is based on [1]_, the generalized bilinear + approximation is based on [2]_ and [3]_, the First-Order Hold (foh) method + is based on [4]_. + + References + ---------- + .. [1] https://en.wikipedia.org/wiki/Discretization#Discretization_of_linear_state_space_models + + .. [2] http://techteach.no/publications/discretetime_signals_systems/discrete.pdf + + .. [3] G. Zhang, X. Chen, and T. Chen, Digital redesign via the generalized + bilinear transformation, Int. J. Control, vol. 82, no. 4, pp. 741-754, + 2009. + (https://www.mypolyuweb.hk/~magzhang/Research/ZCC09_IJC.pdf) + + .. [4] G. F. Franklin, J. D. Powell, and M. L. Workman, Digital control + of dynamic systems, 3rd ed. Menlo Park, Calif: Addison-Wesley, + pp. 204-206, 1998. + + Examples + -------- + We can transform a continuous state-space system to a discrete one: + + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from scipy.signal import cont2discrete, lti, dlti, dstep + + Define a continuous state-space system. + + >>> A = np.array([[0, 1],[-10., -3]]) + >>> B = np.array([[0],[10.]]) + >>> C = np.array([[1., 0]]) + >>> D = np.array([[0.]]) + >>> l_system = lti(A, B, C, D) + >>> t, x = l_system.step(T=np.linspace(0, 5, 100)) + >>> fig, ax = plt.subplots() + >>> ax.plot(t, x, label='Continuous', linewidth=3) + + Transform it to a discrete state-space system using several methods. + + >>> dt = 0.1 + >>> for method in ['zoh', 'bilinear', 'euler', 'backward_diff', 'foh', 'impulse']: + ... d_system = cont2discrete((A, B, C, D), dt, method=method) + ... s, x_d = dstep(d_system) + ... ax.step(s, np.squeeze(x_d), label=method, where='post') + >>> ax.axis([t[0], t[-1], x[0], 1.4]) + >>> ax.legend(loc='best') + >>> fig.tight_layout() + >>> plt.show() + + """ + if hasattr(system, 'to_discrete') and callable(system.to_discrete): + return system.to_discrete(dt=dt, method=method, alpha=alpha) + + if len(system) == 2: + sysd = cont2discrete(tf2ss(system[0], system[1]), dt, method=method, + alpha=alpha) + return ss2tf(sysd[0], sysd[1], sysd[2], sysd[3]) + (dt,) + elif len(system) == 3: + sysd = cont2discrete(zpk2ss(system[0], system[1], system[2]), dt, + method=method, alpha=alpha) + return ss2zpk(sysd[0], sysd[1], sysd[2], sysd[3]) + (dt,) + elif len(system) == 4: + a, b, c, d = system + else: + raise ValueError("First argument must either be a tuple of 2 (tf), " + "3 (zpk), or 4 (ss) arrays.") + + if method == 'gbt': + if alpha is None: + raise ValueError("Alpha parameter must be specified for the " + "generalized bilinear transform (gbt) method") + elif alpha < 0 or alpha > 1: + raise ValueError("Alpha parameter must be within the interval " + "[0,1] for the gbt method") + + if method == 'gbt': + # This parameter is used repeatedly - compute once here + ima = np.eye(a.shape[0]) - alpha*dt*a + ad = linalg.solve(ima, np.eye(a.shape[0]) + (1.0-alpha)*dt*a) + bd = linalg.solve(ima, dt*b) + + # Similarly solve for the output equation matrices + cd = linalg.solve(ima.transpose(), c.transpose()) + cd = cd.transpose() + dd = d + alpha*np.dot(c, bd) + + elif method == 'bilinear' or method == 'tustin': + return cont2discrete(system, dt, method="gbt", alpha=0.5) + + elif method == 'euler' or method == 'forward_diff': + return cont2discrete(system, dt, method="gbt", alpha=0.0) + + elif method == 'backward_diff': + return cont2discrete(system, dt, method="gbt", alpha=1.0) + + elif method == 'zoh': + # Build an exponential matrix + em_upper = np.hstack((a, b)) + + # Need to stack zeros under the a and b matrices + em_lower = np.hstack((np.zeros((b.shape[1], a.shape[0])), + np.zeros((b.shape[1], b.shape[1])))) + + em = np.vstack((em_upper, em_lower)) + ms = linalg.expm(dt * em) + + # Dispose of the lower rows + ms = ms[:a.shape[0], :] + + ad = ms[:, 0:a.shape[1]] + bd = ms[:, a.shape[1]:] + + cd = c + dd = d + + elif method == 'foh': + # Size parameters for convenience + n = a.shape[0] + m = b.shape[1] + + # Build an exponential matrix similar to 'zoh' method + em_upper = linalg.block_diag(np.block([a, b]) * dt, np.eye(m)) + em_lower = zeros((m, n + 2 * m)) + em = np.block([[em_upper], [em_lower]]) + + ms = linalg.expm(em) + + # Get the three blocks from upper rows + ms11 = ms[:n, 0:n] + ms12 = ms[:n, n:n + m] + ms13 = ms[:n, n + m:] + + ad = ms11 + bd = ms12 - ms13 + ms11 @ ms13 + cd = c + dd = d + c @ ms13 + + elif method == 'impulse': + if not np.allclose(d, 0): + raise ValueError("Impulse method is only applicable " + "to strictly proper systems") + + ad = linalg.expm(a * dt) + bd = ad @ b * dt + cd = c + dd = c @ b * dt + + else: + raise ValueError(f"Unknown transformation method '{method}'") + + return ad, bd, cd, dd, dt diff --git a/venv/lib/python3.12/site-packages/scipy/signal/_ltisys.py b/venv/lib/python3.12/site-packages/scipy/signal/_ltisys.py new file mode 100644 index 0000000000000000000000000000000000000000..d7c7f4e17c90be6c13c79e4e9db8af595e42c588 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/_ltisys.py @@ -0,0 +1,3851 @@ +""" +ltisys -- a collection of classes and functions for modeling linear +time invariant systems. +""" +# +# Author: Travis Oliphant 2001 +# +# Feb 2010: Warren Weckesser +# Rewrote lsim2 and added impulse2. +# Apr 2011: Jeffrey Armstrong +# Added dlsim, dstep, dimpulse, cont2discrete +# Aug 2013: Juan Luis Cano +# Rewrote abcd_normalize. +# Jan 2015: Irvin Probst irvin DOT probst AT ensta-bretagne DOT fr +# Added pole placement +# Mar 2015: Clancy Rowley +# Rewrote lsim +# May 2015: Felix Berkenkamp +# Split lti class into subclasses +# Merged discrete systems and added dlti + +import warnings +from types import GenericAlias + +# np.linalg.qr fails on some tests with LinAlgError: zgeqrf returns -7 +# use scipy's qr until this is solved + +from scipy.linalg import qr as s_qr +from scipy import linalg +from scipy.interpolate import make_interp_spline +from ._filter_design import (tf2zpk, zpk2tf, normalize, freqs, freqz, freqs_zpk, + freqz_zpk) +from ._lti_conversion import (tf2ss, abcd_normalize, ss2tf, zpk2ss, ss2zpk, + cont2discrete) + +import numpy as np +from numpy import (real, atleast_1d, squeeze, asarray, zeros, + dot, transpose, ones, linspace) +import copy + +__all__ = ['lti', 'dlti', 'TransferFunction', 'ZerosPolesGain', 'StateSpace', + 'lsim', 'impulse', 'step', 'bode', + 'freqresp', 'place_poles', 'dlsim', 'dstep', 'dimpulse', + 'dfreqresp', 'dbode'] + + +class LinearTimeInvariant: + # generic type compatibility with scipy-stubs + __class_getitem__: classmethod = classmethod(GenericAlias) + + def __new__(cls, *system, **kwargs): + """Create a new object, don't allow direct instances.""" + if cls is LinearTimeInvariant: + raise NotImplementedError('The LinearTimeInvariant class is not ' + 'meant to be used directly, use `lti` ' + 'or `dlti` instead.') + return super().__new__(cls) + + def __init__(self): + """ + Initialize the `lti` baseclass. + + The heavy lifting is done by the subclasses. + """ + super().__init__() + + self.inputs = None + self.outputs = None + self._dt = None + + @property + def dt(self): + """Return the sampling time of the system, `None` for `lti` systems.""" + return self._dt + + @property + def _dt_dict(self): + if self.dt is None: + return {} + else: + return {'dt': self.dt} + + @property + def zeros(self): + """Zeros of the system.""" + return self.to_zpk().zeros + + @property + def poles(self): + """Poles of the system.""" + return self.to_zpk().poles + + def _as_ss(self): + """Convert to `StateSpace` system, without copying. + + Returns + ------- + sys: StateSpace + The `StateSpace` system. If the class is already an instance of + `StateSpace` then this instance is returned. + """ + if isinstance(self, StateSpace): + return self + else: + return self.to_ss() + + def _as_zpk(self): + """Convert to `ZerosPolesGain` system, without copying. + + Returns + ------- + sys: ZerosPolesGain + The `ZerosPolesGain` system. If the class is already an instance of + `ZerosPolesGain` then this instance is returned. + """ + if isinstance(self, ZerosPolesGain): + return self + else: + return self.to_zpk() + + def _as_tf(self): + """Convert to `TransferFunction` system, without copying. + + Returns + ------- + sys: ZerosPolesGain + The `TransferFunction` system. If the class is already an instance of + `TransferFunction` then this instance is returned. + """ + if isinstance(self, TransferFunction): + return self + else: + return self.to_tf() + + +class lti(LinearTimeInvariant): + r""" + Continuous-time linear time invariant system base class. + + Parameters + ---------- + *system : arguments + The `lti` class can be instantiated with either 2, 3 or 4 arguments. + The following gives the number of arguments and the corresponding + continuous-time subclass that is created: + + * 2: `TransferFunction`: (numerator, denominator) + * 3: `ZerosPolesGain`: (zeros, poles, gain) + * 4: `StateSpace`: (A, B, C, D) + + Each argument can be an array or a sequence. + + See Also + -------- + ZerosPolesGain, StateSpace, TransferFunction, dlti + + Notes + ----- + `lti` instances do not exist directly. Instead, `lti` creates an instance + of one of its subclasses: `StateSpace`, `TransferFunction` or + `ZerosPolesGain`. + + If (numerator, denominator) is passed in for ``*system``, coefficients for + both the numerator and denominator should be specified in descending + exponent order (e.g., ``s^2 + 3s + 5`` would be represented as ``[1, 3, + 5]``). + + Changing the value of properties that are not directly part of the current + system representation (such as the `zeros` of a `StateSpace` system) is + very inefficient and may lead to numerical inaccuracies. It is better to + convert to the specific system representation first. For example, call + ``sys = sys.to_zpk()`` before accessing/changing the zeros, poles or gain. + + Examples + -------- + >>> from scipy import signal + + >>> signal.lti(1, 2, 3, 4) + StateSpaceContinuous( + array([[1.]]), + array([[2.]]), + array([[3.]]), + array([[4.]]), + dt: None + ) + + Construct the transfer function + :math:`H(s) = \frac{5(s - 1)(s - 2)}{(s - 3)(s - 4)}`: + + >>> signal.lti([1, 2], [3, 4], 5) + ZerosPolesGainContinuous( + array([1, 2]), + array([3, 4]), + 5, + dt: None + ) + + Construct the transfer function :math:`H(s) = \frac{3s + 4}{1s + 2}`: + + >>> signal.lti([3, 4], [1, 2]) + TransferFunctionContinuous( + array([3., 4.]), + array([1., 2.]), + dt: None + ) + + """ + def __new__(cls, *system): + """Create an instance of the appropriate subclass.""" + if cls is lti: + N = len(system) + if N == 2: + return TransferFunctionContinuous.__new__( + TransferFunctionContinuous, *system) + elif N == 3: + return ZerosPolesGainContinuous.__new__( + ZerosPolesGainContinuous, *system) + elif N == 4: + return StateSpaceContinuous.__new__(StateSpaceContinuous, + *system) + else: + raise ValueError("`system` needs to be an instance of `lti` " + "or have 2, 3 or 4 arguments.") + # __new__ was called from a subclass, let it call its own functions + return super().__new__(cls) + + def __init__(self, *system): + """ + Initialize the `lti` baseclass. + + The heavy lifting is done by the subclasses. + """ + super().__init__(*system) + + def impulse(self, X0=None, T=None, N=None): + """ + Return the impulse response of a continuous-time system. + + Parameters + ---------- + X0 : array_like, optional + Initial state-vector. Defaults to zero. + T : array_like, optional + Time points. Computed if not given. + N : int, optional + The number of time points to compute (if `T` is not given). + + Returns + ------- + T : ndarray + A 1-D array of time points. + yout : ndarray + A 1-D array containing the impulse response of the system (except for + singularities at zero). + + See Also + -------- + impulse : Impulse response of continuous-time LTI systems. + """ + return impulse(self, X0=X0, T=T, N=N) + + def step(self, X0=None, T=None, N=None): + """ + Return the step response of a continuous-time system. + + Parameters + ---------- + X0 : array_like, optional + Initial state-vector (default is zero). + T : array_like, optional + Time points (computed if not given). + N : int, optional + Number of time points to compute if `T` is not given. + + Returns + ------- + T : 1D ndarray + Output time points. + yout : 1D ndarray + Step response of system. + + See Also + -------- + step : Step response of continuous-time LTI systems. + """ + return step(self, X0=X0, T=T, N=N) + + def output(self, U, T, X0=None): + """ + Return the response of a continuous-time system to input `U`. + + Parameters + ---------- + U : array_like + An input array describing the input at each time `T` + (interpolation is assumed between given times). If there are + multiple inputs, then each column of the 2D array + represents an input. If U = 0 or None, a zero input is used. + T : array_like + The time steps at which the input is defined and at which the + output is desired. Must be nonnegative, increasing, and equally spaced. + X0 : array_like, optional + The initial conditions on the state vector (zero by default). + + Returns + ------- + T : 1D ndarray + Time values for the output. + yout : 1D ndarray + System response. + xout : ndarray + Time evolution of the state vector. + + See Also + -------- + lsim : Simulate output of continuous-time LTI systems. + """ + return lsim(self, U, T, X0=X0) + + def bode(self, w=None, n=100): + """ + Calculate Bode magnitude and phase data of a continuous-time system. + + Parameters + ---------- + w : array_like, optional + Array of frequencies (in rad/s). Magnitude and phase data is calculated + for every value in this array. If not given a reasonable set will be + calculated. + n : int, optional + Number of frequency points to compute if `w` is not given. The `n` + frequencies are logarithmically spaced in an interval chosen to + include the influence of the poles and zeros of the system. + Defaults to 100. + + Returns + ------- + w : 1D ndarray + Frequency array [rad/s] + mag : 1D ndarray + Magnitude array [dB] + phase : 1D ndarray + Phase array [deg] + + See Also + -------- + bode : Bode plot data for continuous-time LTI systems. + + Examples + -------- + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + + >>> sys = signal.TransferFunction([1], [1, 1]) + >>> w, mag, phase = sys.bode() + + >>> plt.figure() + >>> plt.semilogx(w, mag) # Bode magnitude plot + >>> plt.figure() + >>> plt.semilogx(w, phase) # Bode phase plot + >>> plt.show() + + """ + return bode(self, w=w, n=n) + + def freqresp(self, w=None, n=10000): + """ + Calculate the frequency response of a continuous-time system. + + Parameters + ---------- + w : array_like, optional + Array of frequencies (in rad/s). Magnitude and phase data is + calculated for every value in this array. If not given, a reasonable + set will be calculated. + n : int, optional + Number of frequency points to compute if `w` is not given. The `n` + frequencies are logarithmically spaced in an interval chosen to + include the influence of the poles and zeros of the system. + Defaults to 10000. + + Returns + ------- + w : 1D ndarray + Frequency array [rad/s] + H : 1D ndarray + Array of complex magnitude values + + See Also + -------- + freqresp : Frequency response of continuous-time LTI systems. + """ + return freqresp(self, w=w, n=n) + + def to_discrete(self, dt, method='zoh', alpha=None): + """Return a discretized version of the current system. + + Parameters + ---------- + dt : float + The discretization time step. + method : str, optional + Which method to use: + + * gbt: generalized bilinear transformation + * bilinear: Tustin's approximation ("gbt" with alpha=0.5) + * euler: Euler (or forward differencing) method ("gbt" with alpha=0) + * backward_diff: Backwards differencing ("gbt" with alpha=1.0) + * zoh: zero-order hold (default) + * foh: first-order hold + * impulse: equivalent impulse response + + alpha : float within [0, 1], optional + The generalized bilinear transformation weighting parameter, which + should only be specified with method="gbt", and is ignored otherwise + + Returns + ------- + sys: dlti + Discrete version of the current system. + + See Also + -------- + cont2discrete + """ + raise NotImplementedError('to_discrete is not implemented for this ' + 'system class.') + + +class dlti(LinearTimeInvariant): + r""" + Discrete-time linear time invariant system base class. + + Parameters + ---------- + *system : arguments + The `dlti` class can be instantiated with either 2, 3 or 4 arguments. + The following gives the number of arguments and the corresponding + discrete-time subclass that is created: + + * 2: `TransferFunction`: (numerator, denominator) + * 3: `ZerosPolesGain`: (zeros, poles, gain) + * 4: `StateSpace`: (A, B, C, D) + + Each argument can be an array or a sequence. + dt : float, optional + Sampling time [s] of the discrete-time systems. Defaults to ``True`` + (unspecified sampling time). Must be specified as a keyword argument, + for example, ``dt=0.1``. + + See Also + -------- + ZerosPolesGain, StateSpace, TransferFunction, lti + + Notes + ----- + `dlti` instances do not exist directly. Instead, `dlti` creates an instance + of one of its subclasses: `StateSpace`, `TransferFunction` or + `ZerosPolesGain`. + + Changing the value of properties that are not directly part of the current + system representation (such as the `zeros` of a `StateSpace` system) is + very inefficient and may lead to numerical inaccuracies. It is better to + convert to the specific system representation first. For example, call + ``sys = sys.to_zpk()`` before accessing/changing the zeros, poles or gain. + + If (numerator, denominator) is passed in for ``*system``, coefficients for + both the numerator and denominator should be specified in descending + exponent order (e.g., ``z^2 + 3z + 5`` would be represented as ``[1, 3, + 5]``). + + .. versionadded:: 0.18.0 + + Examples + -------- + >>> from scipy import signal + + >>> signal.dlti(1, 2, 3, 4) + StateSpaceDiscrete( + array([[1.]]), + array([[2.]]), + array([[3.]]), + array([[4.]]), + dt: True + ) + + >>> signal.dlti(1, 2, 3, 4, dt=0.1) + StateSpaceDiscrete( + array([[1.]]), + array([[2.]]), + array([[3.]]), + array([[4.]]), + dt: 0.1 + ) + + Construct the transfer function + :math:`H(z) = \frac{5(z - 1)(z - 2)}{(z - 3)(z - 4)}` with a sampling time + of 0.1 seconds: + + >>> signal.dlti([1, 2], [3, 4], 5, dt=0.1) + ZerosPolesGainDiscrete( + array([1, 2]), + array([3, 4]), + 5, + dt: 0.1 + ) + + Construct the transfer function :math:`H(z) = \frac{3z + 4}{1z + 2}` with + a sampling time of 0.1 seconds: + + >>> signal.dlti([3, 4], [1, 2], dt=0.1) + TransferFunctionDiscrete( + array([3., 4.]), + array([1., 2.]), + dt: 0.1 + ) + + """ + def __new__(cls, *system, dt=None): + """Create an instance of the appropriate subclass.""" + if cls is dlti: + N = len(system) + if N == 2: + return TransferFunctionDiscrete.__new__( + TransferFunctionDiscrete, *system, dt=dt) + elif N == 3: + return ZerosPolesGainDiscrete.__new__(ZerosPolesGainDiscrete, + *system, dt=dt) + elif N == 4: + return StateSpaceDiscrete.__new__(StateSpaceDiscrete, *system, + dt=dt) + else: + raise ValueError("`system` needs to be an instance of `dlti` " + "or have 2, 3 or 4 arguments.") + # __new__ was called from a subclass, let it call its own functions + return super().__new__(cls) + + def __init__(self, *system, **kwargs): + """ + Initialize the `lti` baseclass. + + The heavy lifting is done by the subclasses. + """ + dt = kwargs.pop('dt', True) + super().__init__(*system, **kwargs) + + self.dt = dt + + @property + def dt(self): + """Return the sampling time of the system.""" + return self._dt + + @dt.setter + def dt(self, dt): + self._dt = dt + + def impulse(self, x0=None, t=None, n=None): + """ + Return the impulse response of the discrete-time `dlti` system. + + Parameters + ---------- + x0 : array_like, optional + Initial state-vector. Defaults to zero. + t : array_like, optional + Time points. Computed if not given. + n : int, optional + The number of time points to compute (if `t` is not given). + + Returns + ------- + tout : ndarray + Time values for the output, as a 1-D array. + yout : tuple of ndarray + Impulse response of system. Each element of the tuple represents + the output of the system based on an impulse in each input. + + See Also + -------- + dimpulse : Impulse response of discrete-time LTI systems. + """ + return dimpulse(self, x0=x0, t=t, n=n) + + def step(self, x0=None, t=None, n=None): + """ + Return the step response of the discrete-time `dlti` system. + + Parameters + ---------- + x0 : array_like, optional + Initial state-vector. Defaults to zero. + t : array_like, optional + Time points. Computed if not given. + n : int, optional + The number of time points to compute (if `t` is not given). + + Returns + ------- + tout : ndarray + Output time points, as a 1-D array. + yout : tuple of ndarray + Step response of system. Each element of the tuple represents + the output of the system based on a step response to each input. + + See Also + -------- + dstep : Step response of discrete-time LTI systems. + """ + return dstep(self, x0=x0, t=t, n=n) + + def output(self, u, t, x0=None): + """ + Return the response of the discrete-time system to input `u`. + + Parameters + ---------- + u : array_like + An input array describing the input at each time `t` (interpolation is + assumed between given times). If there are multiple inputs, then each + column of the 2D array represents an input. + t : array_like, optional + The time steps at which the input is defined. If `t` is given, it + must be the same length as `u`, and the final value in `t` determines + the number of steps returned in the output. + x0 : array_like, optional + The initial conditions on the state vector (zero by default). + + Returns + ------- + tout : ndarray + Time values for the output, as a 1-D array. + yout : ndarray + System response, as a 1-D array. + xout : ndarray, optional + Time-evolution of the state-vector. Only generated if the input is a + `StateSpace` system. + + See Also + -------- + dlsim : Simulate output of discrete-time LTI systems. + """ + return dlsim(self, u, t, x0=x0) + + def bode(self, w=None, n=100): + r""" + Calculate Bode magnitude and phase data of a discrete-time system. + + Parameters + ---------- + w : array_like, optional + Array of frequencies normalized to the Nyquist frequency being π, i.e., + having unit radiant / sample. Magnitude and phase data is calculated for + every value in this array. If not given, a reasonable set will be + calculated. + n : int, optional + Number of frequency points to compute if `w` is not given. The `n` + frequencies are logarithmically spaced in an interval chosen to + include the influence of the poles and zeros of the system. + Defaults to 100. + + Returns + ------- + w : 1D ndarray + Array of frequencies normalized to the Nyquist frequency being ``np.pi/dt`` + with ``dt`` being the sampling interval of the `system` parameter. + The unit is rad/s assuming ``dt`` is in seconds. + mag : 1D ndarray + Magnitude array in dB + phase : 1D ndarray + Phase array in degrees + + See Also + -------- + dbode : Bode plot data for discrete-time LTI systems. + + Examples + -------- + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + + Construct the transfer function :math:`H(z) = \frac{1}{z^2 + 2z + 3}` + with sampling time 0.5s: + + >>> sys = signal.TransferFunction([1], [1, 2, 3], dt=0.5) + + Equivalent: signal.dbode(sys) + + >>> w, mag, phase = sys.bode() + + >>> plt.figure() + >>> plt.semilogx(w, mag) # Bode magnitude plot + >>> plt.figure() + >>> plt.semilogx(w, phase) # Bode phase plot + >>> plt.show() + + """ + return dbode(self, w=w, n=n) + + def freqresp(self, w=None, n=10000, whole=False): + """ + Calculate the frequency response of a discrete-time system. + + Parameters + ---------- + w : array_like, optional + Array of frequencies (in radians/sample). Magnitude and phase data is + calculated for every value in this array. If not given a reasonable + set will be calculated. + n : int, optional + Number of frequency points to compute if `w` is not given. The `n` + frequencies are logarithmically spaced in an interval chosen to + include the influence of the poles and zeros of the system. + whole : bool, optional + Normally, if `w` is not given, frequencies are computed from 0 to the + Nyquist frequency, pi radians/sample (upper-half of unit-circle). If + `whole` is True, compute frequencies from 0 to 2*pi radians/sample. + + Returns + ------- + w : 1D ndarray + Frequency array [radians/sample] + H : 1D ndarray + Array of complex magnitude values + + See Also + -------- + dfreqresp : Frequency response of discrete-time LTI systems. + """ + return dfreqresp(self, w=w, n=n, whole=whole) + + +class TransferFunction(LinearTimeInvariant): + r"""Linear Time Invariant system class in transfer function form. + + Represents the system as the continuous-time transfer function + :math:`H(s)=\sum_{i=0}^N b[N-i] s^i / \sum_{j=0}^M a[M-j] s^j` or the + discrete-time transfer function + :math:`H(z)=\sum_{i=0}^N b[N-i] z^i / \sum_{j=0}^M a[M-j] z^j`, where + :math:`b` are elements of the numerator `num`, :math:`a` are elements of + the denominator `den`, and ``N == len(b) - 1``, ``M == len(a) - 1``. + `TransferFunction` systems inherit additional + functionality from the `lti`, respectively the `dlti` classes, depending on + which system representation is used. + + Parameters + ---------- + *system : arguments + The `TransferFunction` class can be instantiated with 1 or 2 + arguments. The following gives the number of input arguments and their + interpretation: + + * 1: `lti` or `dlti` system: (`StateSpace`, `TransferFunction` or + `ZerosPolesGain`) + * 2: array_like: (numerator, denominator) + dt : float, optional + Sampling time [s] of the discrete-time systems. Defaults to `None` + (continuous-time). Must be specified as a keyword argument, for + example, ``dt=0.1``. + + See Also + -------- + ZerosPolesGain, StateSpace, lti, dlti + tf2ss, tf2zpk, tf2sos + + Notes + ----- + Changing the value of properties that are not part of the + `TransferFunction` system representation (such as the `A`, `B`, `C`, `D` + state-space matrices) is very inefficient and may lead to numerical + inaccuracies. It is better to convert to the specific system + representation first. For example, call ``sys = sys.to_ss()`` before + accessing/changing the A, B, C, D system matrices. + + If (numerator, denominator) is passed in for ``*system``, coefficients + for both the numerator and denominator should be specified in descending + exponent order (e.g. ``s^2 + 3s + 5`` or ``z^2 + 3z + 5`` would be + represented as ``[1, 3, 5]``) + + Examples + -------- + Construct the transfer function + :math:`H(s) = \frac{s^2 + 3s + 3}{s^2 + 2s + 1}`: + + >>> from scipy import signal + + >>> num = [1, 3, 3] + >>> den = [1, 2, 1] + + >>> signal.TransferFunction(num, den) + TransferFunctionContinuous( + array([1., 3., 3.]), + array([1., 2., 1.]), + dt: None + ) + + Construct the transfer function + :math:`H(z) = \frac{z^2 + 3z + 3}{z^2 + 2z + 1}` with a sampling time of + 0.1 seconds: + + >>> signal.TransferFunction(num, den, dt=0.1) + TransferFunctionDiscrete( + array([1., 3., 3.]), + array([1., 2., 1.]), + dt: 0.1 + ) + + """ + def __new__(cls, *system, dt=None): + """Handle object conversion if input is an instance of lti.""" + if len(system) == 1 and isinstance(system[0], LinearTimeInvariant): + return system[0].to_tf() + + # Choose whether to inherit from `lti` or from `dlti` + if cls is TransferFunction: + if dt is None: + return TransferFunctionContinuous.__new__( + TransferFunctionContinuous, *system) + else: + return TransferFunctionDiscrete.__new__( + TransferFunctionDiscrete, *system, dt=dt) + + # No special conversion needed + return super().__new__(cls) + + def __init__(self, *system, **kwargs): + """Initialize the state space LTI system.""" + # Conversion of lti instances is handled in __new__ + if isinstance(system[0], LinearTimeInvariant): + return + + # Remove system arguments, not needed by parents anymore + super().__init__(**kwargs) + + self._num = None + self._den = None + + self.num, self.den = normalize(*system) + + def __repr__(self): + """Return representation of the system's transfer function""" + return ( + f'{self.__class__.__name__}(\n' + f'{repr(self.num)},\n' + f'{repr(self.den)},\n' + f'dt: {repr(self.dt)}\n)' + ) + + @property + def num(self): + """Numerator of the `TransferFunction` system.""" + return self._num + + @num.setter + def num(self, num): + self._num = atleast_1d(num) + + # Update dimensions + if len(self.num.shape) > 1: + self.outputs, self.inputs = self.num.shape + else: + self.outputs = 1 + self.inputs = 1 + + @property + def den(self): + """Denominator of the `TransferFunction` system.""" + return self._den + + @den.setter + def den(self, den): + self._den = atleast_1d(den) + + def _copy(self, system): + """ + Copy the parameters of another `TransferFunction` object + + Parameters + ---------- + system : `TransferFunction` + The `StateSpace` system that is to be copied + + """ + self.num = system.num + self.den = system.den + + def to_tf(self): + """ + Return a copy of the current `TransferFunction` system. + + Returns + ------- + sys : instance of `TransferFunction` + The current system (copy) + + """ + return copy.deepcopy(self) + + def to_zpk(self): + """ + Convert system representation to `ZerosPolesGain`. + + Returns + ------- + sys : instance of `ZerosPolesGain` + Zeros, poles, gain representation of the current system + + """ + return ZerosPolesGain(*tf2zpk(self.num, self.den), + **self._dt_dict) + + def to_ss(self): + """ + Convert system representation to `StateSpace`. + + Returns + ------- + sys : instance of `StateSpace` + State space model of the current system + + """ + return StateSpace(*tf2ss(self.num, self.den), + **self._dt_dict) + + @staticmethod + def _z_to_zinv(num, den): + """Change a transfer function from the variable `z` to `z**-1`. + + Parameters + ---------- + num, den: 1d array_like + Sequences representing the coefficients of the numerator and + denominator polynomials, in order of descending degree of 'z'. + That is, ``5z**2 + 3z + 2`` is presented as ``[5, 3, 2]``. + + Returns + ------- + num, den: 1d array_like + Sequences representing the coefficients of the numerator and + denominator polynomials, in order of ascending degree of 'z**-1'. + That is, ``5 + 3 z**-1 + 2 z**-2`` is presented as ``[5, 3, 2]``. + """ + diff = len(num) - len(den) + if diff > 0: + den = np.hstack((np.zeros(diff), den)) + elif diff < 0: + num = np.hstack((np.zeros(-diff), num)) + return num, den + + @staticmethod + def _zinv_to_z(num, den): + """Change a transfer function from the variable `z` to `z**-1`. + + Parameters + ---------- + num, den: 1d array_like + Sequences representing the coefficients of the numerator and + denominator polynomials, in order of ascending degree of 'z**-1'. + That is, ``5 + 3 z**-1 + 2 z**-2`` is presented as ``[5, 3, 2]``. + + Returns + ------- + num, den: 1d array_like + Sequences representing the coefficients of the numerator and + denominator polynomials, in order of descending degree of 'z'. + That is, ``5z**2 + 3z + 2`` is presented as ``[5, 3, 2]``. + """ + diff = len(num) - len(den) + if diff > 0: + den = np.hstack((den, np.zeros(diff))) + elif diff < 0: + num = np.hstack((num, np.zeros(-diff))) + return num, den + + +class TransferFunctionContinuous(TransferFunction, lti): + r""" + Continuous-time Linear Time Invariant system in transfer function form. + + Represents the system as the transfer function + :math:`H(s)=\sum_{i=0}^N b[N-i] s^i / \sum_{j=0}^M a[M-j] s^j`, where + :math:`b` are elements of the numerator `num`, :math:`a` are elements of + the denominator `den`, and ``N == len(b) - 1``, ``M == len(a) - 1``. + Continuous-time `TransferFunction` systems inherit additional + functionality from the `lti` class. + + Parameters + ---------- + *system : arguments + The `TransferFunction` class can be instantiated with 1 or 2 + arguments. The following gives the number of input arguments and their + interpretation: + + * 1: `lti` system: (`StateSpace`, `TransferFunction` or `ZerosPolesGain`) + * 2: array_like: (numerator, denominator) + + See Also + -------- + ZerosPolesGain, StateSpace, lti + tf2ss, tf2zpk, tf2sos + + Notes + ----- + Changing the value of properties that are not part of the + `TransferFunction` system representation (such as the `A`, `B`, `C`, `D` + state-space matrices) is very inefficient and may lead to numerical + inaccuracies. It is better to convert to the specific system + representation first. For example, call ``sys = sys.to_ss()`` before + accessing/changing the A, B, C, D system matrices. + + If (numerator, denominator) is passed in for ``*system``, coefficients + for both the numerator and denominator should be specified in descending + exponent order (e.g. ``s^2 + 3s + 5`` would be represented as + ``[1, 3, 5]``) + + Examples + -------- + Construct the transfer function + :math:`H(s) = \frac{s^2 + 3s + 3}{s^2 + 2s + 1}`: + + >>> from scipy import signal + + >>> num = [1, 3, 3] + >>> den = [1, 2, 1] + + >>> signal.TransferFunction(num, den) + TransferFunctionContinuous( + array([1., 3., 3.]), + array([1., 2., 1.]), + dt: None + ) + + """ + + def to_discrete(self, dt, method='zoh', alpha=None): + """ + Returns the discretized `TransferFunction` system. + + Parameters + ---------- + dt : float + The discretization time step. + method : str, optional + Which method to use: + + * gbt: generalized bilinear transformation + * bilinear: Tustin's approximation ("gbt" with alpha=0.5) + * euler: Euler (or forward differencing) method ("gbt" with alpha=0) + * backward_diff: Backwards differencing ("gbt" with alpha=1.0) + * zoh: zero-order hold (default) + * foh: first-order hold + * impulse: equivalent impulse response + + alpha : float within [0, 1], optional + The generalized bilinear transformation weighting parameter, which + should only be specified with method="gbt", and is ignored otherwise + + Returns + ------- + sys: instance of `dlti` and `StateSpace` + + See Also + -------- + cont2discrete + """ + return TransferFunction(*cont2discrete((self.num, self.den), + dt, + method=method, + alpha=alpha)[:-1], + dt=dt) + + +class TransferFunctionDiscrete(TransferFunction, dlti): + r""" + Discrete-time Linear Time Invariant system in transfer function form. + + Represents the system as the transfer function + :math:`H(z)=\sum_{i=0}^N b[N-i] z^i / \sum_{j=0}^M a[M-j] z^j`, where + :math:`b` are elements of the numerator `num`, :math:`a` are elements of + the denominator `den`, and ``N == len(b) - 1``, ``M == len(a) - 1``. + Discrete-time `TransferFunction` systems inherit additional functionality + from the `dlti` class. + + Parameters + ---------- + *system : arguments + The `TransferFunction` class can be instantiated with 1 or 2 + arguments. The following gives the number of input arguments and their + interpretation: + + * 1: `dlti` system: (`StateSpace`, `TransferFunction` or `ZerosPolesGain`) + * 2: array_like: (numerator, denominator) + dt : float, optional + Sampling time [s] of the discrete-time systems. Defaults to `True` + (unspecified sampling time). Must be specified as a keyword argument, + for example, ``dt=0.1``. + + See Also + -------- + ZerosPolesGain, StateSpace, dlti + tf2ss, tf2zpk, tf2sos + + Notes + ----- + Changing the value of properties that are not part of the + `TransferFunction` system representation (such as the `A`, `B`, `C`, `D` + state-space matrices) is very inefficient and may lead to numerical + inaccuracies. + + If (numerator, denominator) is passed in for ``*system``, coefficients + for both the numerator and denominator should be specified in descending + exponent order (e.g., ``z^2 + 3z + 5`` would be represented as + ``[1, 3, 5]``). + + Examples + -------- + Construct the transfer function + :math:`H(z) = \frac{z^2 + 3z + 3}{z^2 + 2z + 1}` with a sampling time of + 0.5 seconds: + + >>> from scipy import signal + + >>> num = [1, 3, 3] + >>> den = [1, 2, 1] + + >>> signal.TransferFunction(num, den, dt=0.5) + TransferFunctionDiscrete( + array([1., 3., 3.]), + array([1., 2., 1.]), + dt: 0.5 + ) + + """ + pass + + +class ZerosPolesGain(LinearTimeInvariant): + r""" + Linear Time Invariant system class in zeros, poles, gain form. + + Represents the system as the continuous- or discrete-time transfer function + :math:`H(s)=k \prod_i (s - z[i]) / \prod_j (s - p[j])`, where :math:`k` is + the `gain`, :math:`z` are the `zeros` and :math:`p` are the `poles`. + `ZerosPolesGain` systems inherit additional functionality from the `lti`, + respectively the `dlti` classes, depending on which system representation + is used. + + Parameters + ---------- + *system : arguments + The `ZerosPolesGain` class can be instantiated with 1 or 3 + arguments. The following gives the number of input arguments and their + interpretation: + + * 1: `lti` or `dlti` system: (`StateSpace`, `TransferFunction` or + `ZerosPolesGain`) + * 3: array_like: (zeros, poles, gain) + dt : float, optional + Sampling time [s] of the discrete-time systems. Defaults to `None` + (continuous-time). Must be specified as a keyword argument, for + example, ``dt=0.1``. + + + See Also + -------- + TransferFunction, StateSpace, lti, dlti + zpk2ss, zpk2tf, zpk2sos + + Notes + ----- + Changing the value of properties that are not part of the + `ZerosPolesGain` system representation (such as the `A`, `B`, `C`, `D` + state-space matrices) is very inefficient and may lead to numerical + inaccuracies. It is better to convert to the specific system + representation first. For example, call ``sys = sys.to_ss()`` before + accessing/changing the A, B, C, D system matrices. + + Examples + -------- + Construct the transfer function + :math:`H(s) = \frac{5(s - 1)(s - 2)}{(s - 3)(s - 4)}`: + + >>> from scipy import signal + + >>> signal.ZerosPolesGain([1, 2], [3, 4], 5) + ZerosPolesGainContinuous( + array([1, 2]), + array([3, 4]), + 5, + dt: None + ) + + Construct the transfer function + :math:`H(z) = \frac{5(z - 1)(z - 2)}{(z - 3)(z - 4)}` with a sampling time + of 0.1 seconds: + + >>> signal.ZerosPolesGain([1, 2], [3, 4], 5, dt=0.1) + ZerosPolesGainDiscrete( + array([1, 2]), + array([3, 4]), + 5, + dt: 0.1 + ) + + """ + def __new__(cls, *system, dt=None): + """Handle object conversion if input is an instance of `lti`""" + if len(system) == 1 and isinstance(system[0], LinearTimeInvariant): + return system[0].to_zpk() + + # Choose whether to inherit from `lti` or from `dlti` + if cls is ZerosPolesGain: + if dt is None: + return ZerosPolesGainContinuous.__new__( + ZerosPolesGainContinuous, + *system) + else: + return ZerosPolesGainDiscrete.__new__( + ZerosPolesGainDiscrete, + *system, dt=dt) + + # No special conversion needed + return super().__new__(cls) + + def __init__(self, *system, **kwargs): + """Initialize the zeros, poles, gain system.""" + # Conversion of lti instances is handled in __new__ + if isinstance(system[0], LinearTimeInvariant): + return + + super().__init__(**kwargs) + + self._zeros = None + self._poles = None + self._gain = None + + self.zeros, self.poles, self.gain = system + + def __repr__(self): + """Return representation of the `ZerosPolesGain` system.""" + return ( + f'{self.__class__.__name__}(\n' + f'{repr(self.zeros)},\n' + f'{repr(self.poles)},\n' + f'{repr(self.gain)},\n' + f'dt: {repr(self.dt)}\n)' + ) + + @property + def zeros(self): + """Zeros of the `ZerosPolesGain` system.""" + return self._zeros + + @zeros.setter + def zeros(self, zeros): + self._zeros = atleast_1d(zeros) + + # Update dimensions + if len(self.zeros.shape) > 1: + self.outputs, self.inputs = self.zeros.shape + else: + self.outputs = 1 + self.inputs = 1 + + @property + def poles(self): + """Poles of the `ZerosPolesGain` system.""" + return self._poles + + @poles.setter + def poles(self, poles): + self._poles = atleast_1d(poles) + + @property + def gain(self): + """Gain of the `ZerosPolesGain` system.""" + return self._gain + + @gain.setter + def gain(self, gain): + self._gain = gain + + def _copy(self, system): + """ + Copy the parameters of another `ZerosPolesGain` system. + + Parameters + ---------- + system : instance of `ZerosPolesGain` + The zeros, poles gain system that is to be copied + + """ + self.poles = system.poles + self.zeros = system.zeros + self.gain = system.gain + + def to_tf(self): + """ + Convert system representation to `TransferFunction`. + + Returns + ------- + sys : instance of `TransferFunction` + Transfer function of the current system + + """ + return TransferFunction(*zpk2tf(self.zeros, self.poles, self.gain), + **self._dt_dict) + + def to_zpk(self): + """ + Return a copy of the current 'ZerosPolesGain' system. + + Returns + ------- + sys : instance of `ZerosPolesGain` + The current system (copy) + + """ + return copy.deepcopy(self) + + def to_ss(self): + """ + Convert system representation to `StateSpace`. + + Returns + ------- + sys : instance of `StateSpace` + State space model of the current system + + """ + return StateSpace(*zpk2ss(self.zeros, self.poles, self.gain), + **self._dt_dict) + + +class ZerosPolesGainContinuous(ZerosPolesGain, lti): + r""" + Continuous-time Linear Time Invariant system in zeros, poles, gain form. + + Represents the system as the continuous time transfer function + :math:`H(s)=k \prod_i (s - z[i]) / \prod_j (s - p[j])`, where :math:`k` is + the `gain`, :math:`z` are the `zeros` and :math:`p` are the `poles`. + Continuous-time `ZerosPolesGain` systems inherit additional functionality + from the `lti` class. + + Parameters + ---------- + *system : arguments + The `ZerosPolesGain` class can be instantiated with 1 or 3 + arguments. The following gives the number of input arguments and their + interpretation: + + * 1: `lti` system: (`StateSpace`, `TransferFunction` or `ZerosPolesGain`) + * 3: array_like: (zeros, poles, gain) + + See Also + -------- + TransferFunction, StateSpace, lti + zpk2ss, zpk2tf, zpk2sos + + Notes + ----- + Changing the value of properties that are not part of the + `ZerosPolesGain` system representation (such as the `A`, `B`, `C`, `D` + state-space matrices) is very inefficient and may lead to numerical + inaccuracies. It is better to convert to the specific system + representation first. For example, call ``sys = sys.to_ss()`` before + accessing/changing the A, B, C, D system matrices. + + Examples + -------- + Construct the transfer function + :math:`H(s)=\frac{5(s - 1)(s - 2)}{(s - 3)(s - 4)}`: + + >>> from scipy import signal + + >>> signal.ZerosPolesGain([1, 2], [3, 4], 5) + ZerosPolesGainContinuous( + array([1, 2]), + array([3, 4]), + 5, + dt: None + ) + + """ + + def to_discrete(self, dt, method='zoh', alpha=None): + """ + Returns the discretized `ZerosPolesGain` system. + + Parameters + ---------- + dt : float + The discretization time step. + method : str, optional + Which method to use: + + * gbt: generalized bilinear transformation + * bilinear: Tustin's approximation ("gbt" with alpha=0.5) + * euler: Euler (or forward differencing) method ("gbt" with alpha=0) + * backward_diff: Backwards differencing ("gbt" with alpha=1.0) + * zoh: zero-order hold (default) + * foh: first-order hold + * impulse: equivalent impulse response + + alpha : float within [0, 1], optional + The generalized bilinear transformation weighting parameter, which + should only be specified with method="gbt", and is ignored otherwise + + Returns + ------- + sys: instance of `dlti` and `ZerosPolesGain` + + See Also + -------- + cont2discrete + """ + return ZerosPolesGain( + *cont2discrete((self.zeros, self.poles, self.gain), + dt, + method=method, + alpha=alpha)[:-1], + dt=dt) + + +class ZerosPolesGainDiscrete(ZerosPolesGain, dlti): + r""" + Discrete-time Linear Time Invariant system in zeros, poles, gain form. + + Represents the system as the discrete-time transfer function + :math:`H(z)=k \prod_i (z - q[i]) / \prod_j (z - p[j])`, where :math:`k` is + the `gain`, :math:`q` are the `zeros` and :math:`p` are the `poles`. + Discrete-time `ZerosPolesGain` systems inherit additional functionality + from the `dlti` class. + + Parameters + ---------- + *system : arguments + The `ZerosPolesGain` class can be instantiated with 1 or 3 + arguments. The following gives the number of input arguments and their + interpretation: + + * 1: `dlti` system: (`StateSpace`, `TransferFunction` or `ZerosPolesGain`) + * 3: array_like: (zeros, poles, gain) + dt : float, optional + Sampling time [s] of the discrete-time systems. Defaults to `True` + (unspecified sampling time). Must be specified as a keyword argument, + for example, ``dt=0.1``. + + See Also + -------- + TransferFunction, StateSpace, dlti + zpk2ss, zpk2tf, zpk2sos + + Notes + ----- + Changing the value of properties that are not part of the + `ZerosPolesGain` system representation (such as the `A`, `B`, `C`, `D` + state-space matrices) is very inefficient and may lead to numerical + inaccuracies. It is better to convert to the specific system + representation first. For example, call ``sys = sys.to_ss()`` before + accessing/changing the A, B, C, D system matrices. + + Examples + -------- + Construct the transfer function + :math:`H(s) = \frac{5(s - 1)(s - 2)}{(s - 3)(s - 4)}`: + + >>> from scipy import signal + + >>> signal.ZerosPolesGain([1, 2], [3, 4], 5) + ZerosPolesGainContinuous( + array([1, 2]), + array([3, 4]), + 5, + dt: None + ) + + Construct the transfer function + :math:`H(z) = \frac{5(z - 1)(z - 2)}{(z - 3)(z - 4)}` with a sampling time + of 0.1 seconds: + + >>> signal.ZerosPolesGain([1, 2], [3, 4], 5, dt=0.1) + ZerosPolesGainDiscrete( + array([1, 2]), + array([3, 4]), + 5, + dt: 0.1 + ) + + """ + pass + + +class StateSpace(LinearTimeInvariant): + r""" + Linear Time Invariant system in state-space form. + + Represents the system as the continuous-time, first order differential + equation :math:`\dot{x} = A x + B u` or the discrete-time difference + equation :math:`x[k+1] = A x[k] + B u[k]`. `StateSpace` systems + inherit additional functionality from the `lti`, respectively the `dlti` + classes, depending on which system representation is used. + + Parameters + ---------- + *system : arguments + The `StateSpace` class can be instantiated with 1 or 4 arguments. + The following gives the number of input arguments and their + interpretation: + + * 1: `lti` or `dlti` system: (`StateSpace`, `TransferFunction` or + `ZerosPolesGain`) + * 4: array_like: (A, B, C, D) + dt : float, optional + Sampling time [s] of the discrete-time systems. Defaults to `None` + (continuous-time). Must be specified as a keyword argument, for + example, ``dt=0.1``. + + See Also + -------- + TransferFunction, ZerosPolesGain, lti, dlti + ss2zpk, ss2tf, zpk2sos + + Notes + ----- + If the parameter `system` is a tuple (A, B, C, D) with four state space matrices, + then those matrices are converted into two-dimensional arrays of the same dtype + by calling `abcd_normalize`. The resulting dtype will be based on NumPy's dtype + promotion rules, except in the case where each of `A`, `B`, `C`, and `D` has + integer dtype, in which case the resulting dtype will be the default floating point + dtype of ``float64``. + + Changing the value of properties that are not part of the + `StateSpace` system representation (such as `zeros` or `poles`) is very + inefficient and may lead to numerical inaccuracies. It is better to + convert to the specific system representation first. For example, call + ``sys = sys.to_zpk()`` before accessing/changing the zeros, poles or gain. + + The :ref:`tutorial_signal_state_space_representation` section of the + :ref:`user_guide` presents the corresponding definitions of continuous-time and + disrcete time state space systems. + + Examples + -------- + >>> from scipy import signal + >>> import numpy as np + >>> a = np.array([[0, 1], [0, 0]]) + >>> b = np.array([[0], [1]]) + >>> c = np.array([[1, 0]]) + >>> d = np.array([[0]]) + + >>> sys = signal.StateSpace(a, b, c, d) + >>> print(sys) + StateSpaceContinuous( + array([[0., 1.], + [0., 0.]]), + array([[0.], + [1.]]), + array([[1., 0.]]), + array([[0.]]), + dt: None + ) + + >>> sys.to_discrete(0.1) + StateSpaceDiscrete( + array([[1. , 0.1], + [0. , 1. ]]), + array([[0.005], + [0.1 ]]), + array([[1., 0.]]), + array([[0.]]), + dt: 0.1 + ) + + >>> a = np.array([[1, 0.1], [0, 1]]) + >>> b = np.array([[0.005], [0.1]]) + + >>> signal.StateSpace(a, b, c, d, dt=0.1) + StateSpaceDiscrete( + array([[1. , 0.1], + [0. , 1. ]]), + array([[0.005], + [0.1 ]]), + array([[1., 0.]]), + array([[0.]]), + dt: 0.1 + ) + + """ + + # Override NumPy binary operations and ufuncs + __array_priority__ = 100.0 + __array_ufunc__ = None + + def __new__(cls, *system, dt=None): + """Create new StateSpace object and settle inheritance.""" + # Handle object conversion if input is an instance of `lti` + if len(system) == 1 and isinstance(system[0], LinearTimeInvariant): + return system[0].to_ss() + + # Choose whether to inherit from `lti` or from `dlti` + if cls is StateSpace: + if dt is None: + return StateSpaceContinuous.__new__(StateSpaceContinuous, + *system) + else: + return StateSpaceDiscrete.__new__(StateSpaceDiscrete, + *system, dt=dt) + + # No special conversion needed + return super().__new__(cls) + + def __init__(self, *system, **kwargs): + """Initialize the state space lti/dlti system.""" + # Conversion of lti instances is handled in __new__ + if isinstance(system[0], LinearTimeInvariant): + return + + # Remove system arguments, not needed by parents anymore + super().__init__(**kwargs) + + self._A = None + self._B = None + self._C = None + self._D = None + + # Convert A, B, C, D into 2d arrays of dtype float64 or complex128: + self.A, self.B, self.C, self.D = abcd_normalize(*system) + + def __repr__(self): + """Return representation of the `StateSpace` system.""" + return ( + f'{self.__class__.__name__}(\n' + f'{repr(self.A)},\n' + f'{repr(self.B)},\n' + f'{repr(self.C)},\n' + f'{repr(self.D)},\n' + f'dt: {repr(self.dt)}\n)' + ) + + def _check_binop_other(self, other): + return isinstance(other, StateSpace | np.ndarray | float | complex | + np.number | int) + + def __mul__(self, other): + """ + Post-multiply another system or a scalar + + Handles multiplication of systems in the sense of a frequency domain + multiplication. That means, given two systems E1(s) and E2(s), their + multiplication, H(s) = E1(s) * E2(s), means that applying H(s) to U(s) + is equivalent to first applying E2(s), and then E1(s). + + Notes + ----- + For SISO systems the order of system application does not matter. + However, for MIMO systems, where the two systems are matrices, the + order above ensures standard Matrix multiplication rules apply. + """ + if not self._check_binop_other(other): + return NotImplemented + + if isinstance(other, StateSpace): + # Disallow mix of discrete and continuous systems. + if type(other) is not type(self): + return NotImplemented + + if self.dt != other.dt: + raise TypeError('Cannot multiply systems with different `dt`.') + + n1 = self.A.shape[0] + n2 = other.A.shape[0] + + # Interconnection of systems + # x1' = A1 x1 + B1 u1 + # y1 = C1 x1 + D1 u1 + # x2' = A2 x2 + B2 y1 + # y2 = C2 x2 + D2 y1 + # + # Plugging in with u1 = y2 yields + # [x1'] [A1 B1*C2 ] [x1] [B1*D2] + # [x2'] = [0 A2 ] [x2] + [B2 ] u2 + # [x1] + # y2 = [C1 D1*C2] [x2] + D1*D2 u2 + a = np.vstack((np.hstack((self.A, np.dot(self.B, other.C))), + np.hstack((zeros((n2, n1)), other.A)))) + b = np.vstack((np.dot(self.B, other.D), other.B)) + c = np.hstack((self.C, np.dot(self.D, other.C))) + d = np.dot(self.D, other.D) + else: + # Assume that other is a scalar / matrix + # For post multiplication the input gets scaled + a = self.A + b = np.dot(self.B, other) + c = self.C + d = np.dot(self.D, other) + + common_dtype = np.result_type(a.dtype, b.dtype, c.dtype, d.dtype) + return StateSpace(np.asarray(a, dtype=common_dtype), + np.asarray(b, dtype=common_dtype), + np.asarray(c, dtype=common_dtype), + np.asarray(d, dtype=common_dtype), + **self._dt_dict) + + def __rmul__(self, other): + """Pre-multiply a scalar or matrix (but not StateSpace)""" + if not self._check_binop_other(other) or isinstance(other, StateSpace): + return NotImplemented + + # For pre-multiplication only the output gets scaled + a = self.A + b = self.B + c = np.dot(other, self.C) + d = np.dot(other, self.D) + + common_dtype = np.result_type(a.dtype, b.dtype, c.dtype, d.dtype) + return StateSpace(np.asarray(a, dtype=common_dtype), + np.asarray(b, dtype=common_dtype), + np.asarray(c, dtype=common_dtype), + np.asarray(d, dtype=common_dtype), + **self._dt_dict) + + def __neg__(self): + """Negate the system (equivalent to pre-multiplying by -1).""" + return StateSpace(self.A, self.B, -self.C, -self.D, **self._dt_dict) + + def __add__(self, other): + """ + Adds two systems in the sense of frequency domain addition. + """ + if not self._check_binop_other(other): + return NotImplemented + + if isinstance(other, StateSpace): + # Disallow mix of discrete and continuous systems. + if type(other) is not type(self): + raise TypeError(f'Cannot add {type(self)} and {type(other)}') + + if self.dt != other.dt: + raise TypeError('Cannot add systems with different `dt`.') + # Interconnection of systems + # x1' = A1 x1 + B1 u + # y1 = C1 x1 + D1 u + # x2' = A2 x2 + B2 u + # y2 = C2 x2 + D2 u + # y = y1 + y2 + # + # Plugging in yields + # [x1'] [A1 0 ] [x1] [B1] + # [x2'] = [0 A2] [x2] + [B2] u + # [x1] + # y = [C1 C2] [x2] + [D1 + D2] u + a = linalg.block_diag(self.A, other.A) + b = np.vstack((self.B, other.B)) + c = np.hstack((self.C, other.C)) + d = self.D + other.D + else: + other = np.atleast_2d(other) + if self.D.shape == other.shape: + # A scalar/matrix is really just a static system (A=0, B=0, C=0) + a = self.A + b = self.B + c = self.C + d = self.D + other + else: + raise ValueError("Cannot add systems with incompatible " + f"dimensions ({self.D.shape} and {other.shape})") + + common_dtype = np.result_type(a.dtype, b.dtype, c.dtype, d.dtype) + return StateSpace(np.asarray(a, dtype=common_dtype), + np.asarray(b, dtype=common_dtype), + np.asarray(c, dtype=common_dtype), + np.asarray(d, dtype=common_dtype), + **self._dt_dict) + + def __sub__(self, other): + if not self._check_binop_other(other): + return NotImplemented + + return self.__add__(-other) + + def __radd__(self, other): + if not self._check_binop_other(other): + return NotImplemented + + return self.__add__(other) + + def __rsub__(self, other): + if not self._check_binop_other(other): + return NotImplemented + + return (-self).__add__(other) + + def __truediv__(self, other): + """ + Divide by a scalar + """ + # Division by non-StateSpace scalars + if not self._check_binop_other(other) or isinstance(other, StateSpace): + return NotImplemented + + if isinstance(other, np.ndarray) and other.ndim > 0: + # It's ambiguous what this means, so disallow it + raise ValueError("Cannot divide StateSpace by non-scalar numpy arrays") + + return self.__mul__(1/other) + + @property + def A(self): + """State matrix of the `StateSpace` system.""" + return self._A + + @A.setter + def A(self, A): + self._A = np.atleast_2d(A) if A is not None else None + + @property + def B(self): + """Input matrix of the `StateSpace` system.""" + return self._B + + @B.setter + def B(self, B): + self._B = np.atleast_2d(B) if B is not None else None + self.inputs = self.B.shape[-1] + + @property + def C(self): + """Output matrix of the `StateSpace` system.""" + return self._C + + @C.setter + def C(self, C): + self._C = np.atleast_2d(C) if C is not None else None + self.outputs = self.C.shape[0] + + @property + def D(self): + """Feedthrough matrix of the `StateSpace` system.""" + return self._D + + @D.setter + def D(self, D): + self._D = np.atleast_2d(D) if D is not None else None + + def _copy(self, system): + """ + Copy the parameters of another `StateSpace` system. + + Parameters + ---------- + system : instance of `StateSpace` + The state-space system that is to be copied + + """ + self.A = system.A + self.B = system.B + self.C = system.C + self.D = system.D + + def to_tf(self, **kwargs): + """ + Convert system representation to `TransferFunction`. + + Parameters + ---------- + **kwargs + Additional keywords passed to `ss2zpk` + + Returns + ------- + sys : instance of `TransferFunction` + Transfer function of the current system + + """ + return TransferFunction(*ss2tf(self._A, self._B, self._C, self._D, + **kwargs), **self._dt_dict) + + def to_zpk(self, **kwargs): + """ + Convert system representation to `ZerosPolesGain`. + + Parameters + ---------- + **kwargs + Additional keywords passed to `ss2zpk` + + Returns + ------- + sys : instance of `ZerosPolesGain` + Zeros, poles, gain representation of the current system + + """ + return ZerosPolesGain(*ss2zpk(self._A, self._B, self._C, self._D, + **kwargs), **self._dt_dict) + + def to_ss(self): + """ + Return a copy of the current `StateSpace` system. + + Returns + ------- + sys : instance of `StateSpace` + The current system (copy) + + """ + return copy.deepcopy(self) + + +class StateSpaceContinuous(StateSpace, lti): + r""" + Continuous-time Linear Time Invariant system in state-space form. + + Represents the system as the continuous-time, first order differential + equation :math:`\dot{x} = A x + B u`. + Continuous-time `StateSpace` systems inherit additional functionality + from the `lti` class. + + Parameters + ---------- + *system : arguments + The `StateSpace` class can be instantiated with 1 or 3 arguments. + The following gives the number of input arguments and their + interpretation: + + * 1: `lti` system: (`StateSpace`, `TransferFunction` or `ZerosPolesGain`) + * 4: array_like: (A, B, C, D) + + See Also + -------- + TransferFunction, ZerosPolesGain, lti + ss2zpk, ss2tf, zpk2sos + + Notes + ----- + Changing the value of properties that are not part of the + `StateSpace` system representation (such as `zeros` or `poles`) is very + inefficient and may lead to numerical inaccuracies. It is better to + convert to the specific system representation first. For example, call + ``sys = sys.to_zpk()`` before accessing/changing the zeros, poles or gain. + + Examples + -------- + >>> import numpy as np + >>> from scipy import signal + + >>> a = np.array([[0, 1], [0, 0]]) + >>> b = np.array([[0], [1]]) + >>> c = np.array([[1, 0]]) + >>> d = np.array([[0]]) + + >>> sys = signal.StateSpace(a, b, c, d) + >>> print(sys) + StateSpaceContinuous( + array([[0., 1.], + [0., 0.]]), + array([[0.], + [1.]]), + array([[1., 0.]]), + array([[0.]]), + dt: None + ) + + """ + + def to_discrete(self, dt, method='zoh', alpha=None): + """ + Returns the discretized `StateSpace` system. + + Parameters + ---------- + dt : float + The discretization time step. + method : str, optional + Which method to use: + + * gbt: generalized bilinear transformation + * bilinear: Tustin's approximation ("gbt" with alpha=0.5) + * euler: Euler (or forward differencing) method ("gbt" with alpha=0) + * backward_diff: Backwards differencing ("gbt" with alpha=1.0) + * zoh: zero-order hold (default) + * foh: first-order hold + * impulse: equivalent impulse response + + alpha : float within [0, 1], optional + The generalized bilinear transformation weighting parameter, which + should only be specified with method="gbt", and is ignored otherwise + + Returns + ------- + sys: instance of `dlti` and `StateSpace` + + See Also + -------- + cont2discrete + """ + return StateSpace(*cont2discrete((self.A, self.B, self.C, self.D), + dt, + method=method, + alpha=alpha)[:-1], + dt=dt) + + +class StateSpaceDiscrete(StateSpace, dlti): + r""" + Discrete-time Linear Time Invariant system in state-space form. + + Represents the system as the discrete-time difference equation + :math:`x[k+1] = A x[k] + B u[k]`. + `StateSpace` systems inherit additional functionality from the `dlti` + class. + + Parameters + ---------- + *system : arguments + The `StateSpace` class can be instantiated with 1 or 3 arguments. + The following gives the number of input arguments and their + interpretation: + + * 1: `dlti` system: (`StateSpace`, `TransferFunction` or `ZerosPolesGain`) + * 4: array_like: (A, B, C, D) + dt : float, optional + Sampling time [s] of the discrete-time systems. Defaults to `True` + (unspecified sampling time). Must be specified as a keyword argument, + for example, ``dt=0.1``. + + See Also + -------- + TransferFunction, ZerosPolesGain, dlti + ss2zpk, ss2tf, zpk2sos + + Notes + ----- + Changing the value of properties that are not part of the + `StateSpace` system representation (such as `zeros` or `poles`) is very + inefficient and may lead to numerical inaccuracies. It is better to + convert to the specific system representation first. For example, call + ``sys = sys.to_zpk()`` before accessing/changing the zeros, poles or gain. + + Examples + -------- + >>> import numpy as np + >>> from scipy import signal + + >>> a = np.array([[1, 0.1], [0, 1]]) + >>> b = np.array([[0.005], [0.1]]) + >>> c = np.array([[1, 0]]) + >>> d = np.array([[0]]) + + >>> signal.StateSpace(a, b, c, d, dt=0.1) + StateSpaceDiscrete( + array([[1. , 0.1], + [0. , 1. ]]), + array([[0.005], + [0.1 ]]), + array([[1., 0.]]), + array([[0.]]), + dt: 0.1 + ) + + """ + pass + + +def lsim(system, U, T, X0=None, interp=True): + """ + Simulate output of a continuous-time linear system. + + Parameters + ---------- + system : an instance of the LTI class or a tuple describing the system + The following gives the number of elements in the tuple and + the interpretation: + + * 1: (instance of `lti`) + * 2: (num, den) + * 3: (zeros, poles, gain) + * 4: (A, B, C, D) + + U : array_like + An input array describing the input at each time `T` + (interpolation is assumed between given times). If there are + multiple inputs, then each column of the rank-2 array + represents an input. If U = 0 or None, a zero input is used. + T : array_like + The time steps at which the input is defined and at which the + output is desired. Must be nonnegative, increasing, and equally spaced. + X0 : array_like, optional + The initial conditions on the state vector (zero by default). + interp : bool, optional + Whether to use linear (True, the default) or zero-order-hold (False) + interpolation for the input array. + + Returns + ------- + T : 1D ndarray + Time values for the output. + yout : 1D ndarray + System response. + xout : ndarray + Time evolution of the state vector. + + Notes + ----- + If (num, den) is passed in for ``system``, coefficients for both the + numerator and denominator should be specified in descending exponent + order (e.g. ``s^2 + 3s + 5`` would be represented as ``[1, 3, 5]``). + + Examples + -------- + We'll use `lsim` to simulate an analog Bessel filter applied to + a signal. + + >>> import numpy as np + >>> from scipy.signal import bessel, lsim + >>> import matplotlib.pyplot as plt + + Create a low-pass Bessel filter with a cutoff of 12 Hz. + + >>> b, a = bessel(N=5, Wn=2*np.pi*12, btype='lowpass', analog=True) + + Generate data to which the filter is applied. + + >>> t = np.linspace(0, 1.25, 500, endpoint=False) + + The input signal is the sum of three sinusoidal curves, with + frequencies 4 Hz, 40 Hz, and 80 Hz. The filter should mostly + eliminate the 40 Hz and 80 Hz components, leaving just the 4 Hz signal. + + >>> u = (np.cos(2*np.pi*4*t) + 0.6*np.sin(2*np.pi*40*t) + + ... 0.5*np.cos(2*np.pi*80*t)) + + Simulate the filter with `lsim`. + + >>> tout, yout, xout = lsim((b, a), U=u, T=t) + + Plot the result. + + >>> plt.plot(t, u, 'r', alpha=0.5, linewidth=1, label='input') + >>> plt.plot(tout, yout, 'k', linewidth=1.5, label='output') + >>> plt.legend(loc='best', shadow=True, framealpha=1) + >>> plt.grid(alpha=0.3) + >>> plt.xlabel('t') + >>> plt.show() + + In a second example, we simulate a double integrator ``y'' = u``, with + a constant input ``u = 1``. We'll use the state space representation + of the integrator. + + >>> from scipy.signal import lti + >>> A = np.array([[0.0, 1.0], [0.0, 0.0]]) + >>> B = np.array([[0.0], [1.0]]) + >>> C = np.array([[1.0, 0.0]]) + >>> D = 0.0 + >>> system = lti(A, B, C, D) + + `t` and `u` define the time and input signal for the system to + be simulated. + + >>> t = np.linspace(0, 5, num=50) + >>> u = np.ones_like(t) + + Compute the simulation, and then plot `y`. As expected, the plot shows + the curve ``y = 0.5*t**2``. + + >>> tout, y, x = lsim(system, u, t) + >>> plt.plot(t, y) + >>> plt.grid(alpha=0.3) + >>> plt.xlabel('t') + >>> plt.show() + + """ + if isinstance(system, lti): + sys = system._as_ss() + elif isinstance(system, dlti): + raise AttributeError('lsim can only be used with continuous-time ' + 'systems.') + else: + sys = lti(*system)._as_ss() + T = atleast_1d(T) + if len(T.shape) != 1: + raise ValueError("T must be a rank-1 array.") + + A, B, C, D = map(np.asarray, (sys.A, sys.B, sys.C, sys.D)) + n_states = A.shape[0] + n_inputs = B.shape[1] + + n_steps = T.size + if X0 is None: + X0 = zeros(n_states, sys.A.dtype) + xout = np.empty((n_steps, n_states), sys.A.dtype) + + if T[0] == 0: + xout[0] = X0 + elif T[0] > 0: + # step forward to initial time, with zero input + xout[0] = dot(X0, linalg.expm(transpose(A) * T[0])) + else: + raise ValueError("Initial time must be nonnegative") + + no_input = (U is None or + (isinstance(U, int | float) and U == 0.) or + not np.any(U)) + + if n_steps == 1: + yout = squeeze(xout @ C.T) + if not no_input: + yout += squeeze(U @ D.T) + return T, yout, squeeze(xout) + + dt = T[1] - T[0] + if not np.allclose(np.diff(T), dt): + raise ValueError("Time steps are not equally spaced.") + + if no_input: + # Zero input: just use matrix exponential + # take transpose because state is a row vector + expAT_dt = linalg.expm(A.T * dt) + for i in range(1, n_steps): + xout[i] = xout[i-1] @ expAT_dt + yout = squeeze(xout @ C.T) + return T, yout, squeeze(xout) + + # Nonzero input + U = atleast_1d(U) + if U.ndim == 1: + U = U[:, np.newaxis] + + if U.shape[0] != n_steps: + raise ValueError("U must have the same number of rows " + "as elements in T.") + + if U.shape[1] != n_inputs: + raise ValueError("System does not define that many inputs.") + + if not interp: + # Zero-order hold + # Algorithm: to integrate from time 0 to time dt, we solve + # xdot = A x + B u, x(0) = x0 + # udot = 0, u(0) = u0. + # + # Solution is + # [ x(dt) ] [ A*dt B*dt ] [ x0 ] + # [ u(dt) ] = exp [ 0 0 ] [ u0 ] + M = np.vstack([np.hstack([A * dt, B * dt]), + np.zeros((n_inputs, n_states + n_inputs))]) + # transpose everything because the state and input are row vectors + expMT = linalg.expm(M.T) + Ad = expMT[:n_states, :n_states] + Bd = expMT[n_states:, :n_states] + for i in range(1, n_steps): + xout[i] = xout[i-1] @ Ad + U[i-1] @ Bd + else: + # Linear interpolation between steps + # Algorithm: to integrate from time 0 to time dt, with linear + # interpolation between inputs u(0) = u0 and u(dt) = u1, we solve + # xdot = A x + B u, x(0) = x0 + # udot = (u1 - u0) / dt, u(0) = u0. + # + # Solution is + # [ x(dt) ] [ A*dt B*dt 0 ] [ x0 ] + # [ u(dt) ] = exp [ 0 0 I ] [ u0 ] + # [u1 - u0] [ 0 0 0 ] [u1 - u0] + M = np.vstack([np.hstack([A * dt, B * dt, + np.zeros((n_states, n_inputs))]), + np.hstack([np.zeros((n_inputs, n_states + n_inputs)), + np.identity(n_inputs)]), + np.zeros((n_inputs, n_states + 2 * n_inputs))]) + expMT = linalg.expm(M.T) + Ad = expMT[:n_states, :n_states] + Bd1 = expMT[n_states+n_inputs:, :n_states] + Bd0 = expMT[n_states:n_states + n_inputs, :n_states] - Bd1 + for i in range(1, n_steps): + xout[i] = xout[i-1] @ Ad + U[i-1] @ Bd0 + U[i] @ Bd1 + + yout = squeeze(xout @ C.T) + squeeze(U @ D.T) + return T, yout, squeeze(xout) + + +def _default_response_times(A, n): + """Compute a reasonable set of time samples for the response time. + + This function is used by `impulse` and `step` to compute the response time + when the `T` argument to the function is None. + + Parameters + ---------- + A : array_like + The system matrix, which is square. + n : int + The number of time samples to generate. + + Returns + ------- + t : ndarray + The 1-D array of length `n` of time samples at which the response + is to be computed. + """ + # Create a reasonable time interval. + # TODO: This could use some more work. + # For example, what is expected when the system is unstable? + vals = linalg.eigvals(A) + r = min(abs(real(vals))) + if r == 0.0: + r = 1.0 + tc = 1.0 / r + t = linspace(0.0, 7 * tc, n) + return t + + +def impulse(system, X0=None, T=None, N=None): + """Impulse response of continuous-time system. + + Parameters + ---------- + system : an instance of the LTI class or a tuple of array_like + describing the system. + The following gives the number of elements in the tuple and + the interpretation: + + * 1 (instance of `lti`) + * 2 (num, den) + * 3 (zeros, poles, gain) + * 4 (A, B, C, D) + + X0 : array_like, optional + Initial state-vector. Defaults to zero. + T : array_like, optional + Time points. Computed if not given. + N : int, optional + The number of time points to compute (if `T` is not given). + + Returns + ------- + T : ndarray + A 1-D array of time points. + yout : ndarray + A 1-D array containing the impulse response of the system (except for + singularities at zero). + + Notes + ----- + If (num, den) is passed in for ``system``, coefficients for both the + numerator and denominator should be specified in descending exponent + order (e.g. ``s^2 + 3s + 5`` would be represented as ``[1, 3, 5]``). + + Examples + -------- + Compute the impulse response of a second order system with a repeated + root: ``x''(t) + 2*x'(t) + x(t) = u(t)`` + + >>> from scipy import signal + >>> system = ([1.0], [1.0, 2.0, 1.0]) + >>> t, y = signal.impulse(system) + >>> import matplotlib.pyplot as plt + >>> plt.plot(t, y) + + """ + if isinstance(system, lti): + sys = system._as_ss() + elif isinstance(system, dlti): + raise AttributeError('impulse can only be used with continuous-time ' + 'systems.') + else: + sys = lti(*system)._as_ss() + if X0 is None: + X = squeeze(sys.B) + else: + X = squeeze(sys.B + X0) + if N is None: + N = 100 + if T is None: + T = _default_response_times(sys.A, N) + else: + T = asarray(T) + + _, h, _ = lsim(sys, 0., T, X, interp=False) + return T, h + + +def step(system, X0=None, T=None, N=None): + """Step response of continuous-time system. + + Parameters + ---------- + system : an instance of the LTI class or a tuple of array_like + describing the system. + The following gives the number of elements in the tuple and + the interpretation: + + * 1 (instance of `lti`) + * 2 (num, den) + * 3 (zeros, poles, gain) + * 4 (A, B, C, D) + + X0 : array_like, optional + Initial state-vector (default is zero). + T : array_like, optional + Time points (computed if not given). + N : int, optional + Number of time points to compute if `T` is not given. + + Returns + ------- + T : 1D ndarray + Output time points. + yout : 1D ndarray + Step response of system. + + + Notes + ----- + If (num, den) is passed in for ``system``, coefficients for both the + numerator and denominator should be specified in descending exponent + order (e.g. ``s^2 + 3s + 5`` would be represented as ``[1, 3, 5]``). + + Examples + -------- + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + >>> lti = signal.lti([1.0], [1.0, 1.0]) + >>> t, y = signal.step(lti) + >>> plt.plot(t, y) + >>> plt.xlabel('Time [s]') + >>> plt.ylabel('Amplitude') + >>> plt.title('Step response for 1. Order Lowpass') + >>> plt.grid() + + """ + if isinstance(system, lti): + sys = system._as_ss() + elif isinstance(system, dlti): + raise AttributeError('step can only be used with continuous-time ' + 'systems.') + else: + sys = lti(*system)._as_ss() + if N is None: + N = 100 + if T is None: + T = _default_response_times(sys.A, N) + else: + T = asarray(T) + U = ones(T.shape, sys.A.dtype) + vals = lsim(sys, U, T, X0=X0, interp=False) + return vals[0], vals[1] + + +def bode(system, w=None, n=100): + """ + Calculate Bode magnitude and phase data of a continuous-time system. + + Parameters + ---------- + system : an instance of the LTI class or a tuple describing the system + The following gives the number of elements in the tuple and + the interpretation: + + * 1 (instance of `lti`) + * 2 (num, den) + * 3 (zeros, poles, gain) + * 4 (A, B, C, D) + + w : array_like, optional + Array of frequencies (in rad/s). Magnitude and phase data is calculated + for every value in this array. If not given a reasonable set will be + calculated. + n : int, optional + Number of frequency points to compute if `w` is not given. The `n` + frequencies are logarithmically spaced in an interval chosen to + include the influence of the poles and zeros of the system. + + Returns + ------- + w : 1D ndarray + Frequency array [rad/s] + mag : 1D ndarray + Magnitude array [dB] + phase : 1D ndarray + Phase array [deg] + + Notes + ----- + If (num, den) is passed in for ``system``, coefficients for both the + numerator and denominator should be specified in descending exponent + order (e.g. ``s^2 + 3s + 5`` would be represented as ``[1, 3, 5]``). + + .. versionadded:: 0.11.0 + + Examples + -------- + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + + >>> sys = signal.TransferFunction([1], [1, 1]) + >>> w, mag, phase = signal.bode(sys) + + >>> plt.figure() + >>> plt.semilogx(w, mag) # Bode magnitude plot + >>> plt.figure() + >>> plt.semilogx(w, phase) # Bode phase plot + >>> plt.show() + + """ + w, y = freqresp(system, w=w, n=n) + + mag = 20.0 * np.log10(abs(y)) + phase = np.unwrap(np.arctan2(y.imag, y.real)) * 180.0 / np.pi + + return w, mag, phase + + +def freqresp(system, w=None, n=10000): + r"""Calculate the frequency response of a continuous-time system. + + Parameters + ---------- + system : an instance of the `lti` class or a tuple describing the system + The following gives the number of elements in the tuple and + the interpretation: + + * 1 (instance of `lti`) + * 2 (num, den) + * 3 (zeros, poles, gain) + * 4 (A, B, C, D) + + w : array_like, optional + Array of frequencies (in rad/s). Magnitude and phase data is + calculated for every value in this array. If not given, a reasonable + set will be calculated. + n : int, optional + Number of frequency points to compute if `w` is not given. The `n` + frequencies are logarithmically spaced in an interval chosen to + include the influence of the poles and zeros of the system. + + Returns + ------- + w : 1D ndarray + Frequency array [rad/s] + H : 1D ndarray + Array of complex magnitude values + + Notes + ----- + If (num, den) is passed in for ``system``, coefficients for both the + numerator and denominator should be specified in descending exponent + order (e.g. ``s^2 + 3s + 5`` would be represented as ``[1, 3, 5]``). + + Examples + -------- + Generating the Nyquist plot of a transfer function + + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + + Construct the transfer function :math:`H(s) = \frac{5}{(s-1)^3}`: + + >>> s1 = signal.ZerosPolesGain([], [1, 1, 1], [5]) + + >>> w, H = signal.freqresp(s1) + + >>> plt.figure() + >>> plt.plot(H.real, H.imag, "b") + >>> plt.plot(H.real, -H.imag, "r") + >>> plt.show() + """ + if isinstance(system, lti): + if isinstance(system, TransferFunction | ZerosPolesGain): + sys = system + else: + sys = system._as_zpk() + elif isinstance(system, dlti): + raise AttributeError('freqresp can only be used with continuous-time ' + 'systems.') + else: + sys = lti(*system)._as_zpk() + + if sys.inputs != 1 or sys.outputs != 1: + raise ValueError("freqresp() requires a SISO (single input, single " + "output) system.") + + if w is not None: + worN = w + else: + worN = n + + if isinstance(sys, TransferFunction): + # In the call to freqs(), sys.num.ravel() is used because there are + # cases where sys.num is a 2-D array with a single row. + w, h = freqs(sys.num.ravel(), sys.den, worN=worN) + + elif isinstance(sys, ZerosPolesGain): + w, h = freqs_zpk(sys.zeros, sys.poles, sys.gain, worN=worN) + + return w, h + + +# This class will be used by place_poles to return its results +# see https://code.activestate.com/recipes/52308/ +class Bunch: + def __init__(self, **kwds): + self.__dict__.update(kwds) + + +def _valid_inputs(A, B, poles, method, rtol, maxiter): + """ + Check the poles come in complex conjugate pairs + Check shapes of A, B and poles are compatible. + Check the method chosen is compatible with provided poles + Return update method to use and ordered poles + + """ + poles = np.asarray(poles) + if poles.ndim > 1: + raise ValueError("Poles must be a 1D array like.") + # Will raise ValueError if poles do not come in complex conjugates pairs + poles = _order_complex_poles(poles) + if A.ndim > 2: + raise ValueError("A must be a 2D array/matrix.") + if B.ndim > 2: + raise ValueError("B must be a 2D array/matrix") + if A.shape[0] != A.shape[1]: + raise ValueError("A must be square") + if len(poles) > A.shape[0]: + raise ValueError( + f"maximum number of poles is {A.shape[0]} but you asked for {len(poles)}" + ) + if len(poles) < A.shape[0]: + raise ValueError( + f"number of poles is {len(poles)} but you should provide {A.shape[0]}" + ) + r = np.linalg.matrix_rank(B) + for p in poles: + if sum(p == poles) > r: + raise ValueError("at least one of the requested pole is repeated " + "more than rank(B) times") + # Choose update method + update_loop = _YT_loop + if method not in ('KNV0','YT'): + raise ValueError("The method keyword must be one of 'YT' or 'KNV0'") + + if method == "KNV0": + update_loop = _KNV0_loop + if not all(np.isreal(poles)): + raise ValueError("Complex poles are not supported by KNV0") + + if maxiter < 1: + raise ValueError("maxiter must be at least equal to 1") + + # We do not check rtol <= 0 as the user can use a negative rtol to + # force maxiter iterations + if rtol > 1: + raise ValueError("rtol can not be greater than 1") + + return update_loop, poles + + +def _order_complex_poles(poles): + """ + Check we have complex conjugates pairs and reorder P according to YT, ie + real_poles, complex_i, conjugate complex_i, .... + The lexicographic sort on the complex poles is added to help the user to + compare sets of poles. + """ + ordered_poles = np.sort(poles[np.isreal(poles)]) + im_poles = [] + for p in np.sort(poles[np.imag(poles) < 0]): + if np.conj(p) in poles: + im_poles.extend((p, np.conj(p))) + + ordered_poles = np.hstack((ordered_poles, im_poles)) + + if poles.shape[0] != len(ordered_poles): + raise ValueError("Complex poles must come with their conjugates") + return ordered_poles + + +def _KNV0(B, ker_pole, transfer_matrix, j, poles): + """ + Algorithm "KNV0" Kautsky et Al. Robust pole + assignment in linear state feedback, Int journal of Control + 1985, vol 41 p 1129->1155 + https://la.epfl.ch/files/content/sites/la/files/ + users/105941/public/KautskyNicholsDooren + + """ + # Remove xj form the base + transfer_matrix_not_j = np.delete(transfer_matrix, j, axis=1) + # If we QR this matrix in full mode Q=Q0|Q1 + # then Q1 will be a single column orthogonal to + # Q0, that's what we are looking for ! + + # After merge of gh-4249 great speed improvements could be achieved + # using QR updates instead of full QR in the line below + + # To debug with numpy qr uncomment the line below + # Q, R = np.linalg.qr(transfer_matrix_not_j, mode="complete") + Q, R = s_qr(transfer_matrix_not_j, mode="full") + + mat_ker_pj = np.dot(ker_pole[j], ker_pole[j].T) + yj = np.dot(mat_ker_pj, Q[:, -1]) + + # If Q[:, -1] is "almost" orthogonal to ker_pole[j] its + # projection into ker_pole[j] will yield a vector + # close to 0. As we are looking for a vector in ker_pole[j] + # simply stick with transfer_matrix[:, j] (unless someone provides me with + # a better choice ?) + + if not np.allclose(yj, 0): + xj = yj/np.linalg.norm(yj) + transfer_matrix[:, j] = xj + + # KNV does not support complex poles, using YT technique the two lines + # below seem to work 9 out of 10 times but it is not reliable enough: + # transfer_matrix[:, j]=real(xj) + # transfer_matrix[:, j+1]=imag(xj) + + # Add this at the beginning of this function if you wish to test + # complex support: + # if ~np.isreal(P[j]) and (j>=B.shape[0]-1 or P[j]!=np.conj(P[j+1])): + # return + # Problems arise when imag(xj)=>0 I have no idea on how to fix this + + +def _YT_real(ker_pole, Q, transfer_matrix, i, j): + """ + Applies algorithm from YT section 6.1 page 19 related to real pairs + """ + # step 1 page 19 + u = Q[:, -2, np.newaxis] + v = Q[:, -1, np.newaxis] + + # step 2 page 19 + m = np.dot(np.dot(ker_pole[i].T, np.dot(u, v.T) - + np.dot(v, u.T)), ker_pole[j]) + + # step 3 page 19 + um, sm, vm = np.linalg.svd(m) + # mu1, mu2 two first columns of U => 2 first lines of U.T + mu1, mu2 = um.T[:2, :, np.newaxis] + # VM is V.T with numpy we want the first two lines of V.T + nu1, nu2 = vm[:2, :, np.newaxis] + + # what follows is a rough python translation of the formulas + # in section 6.2 page 20 (step 4) + transfer_matrix_j_mo_transfer_matrix_j = np.vstack(( + transfer_matrix[:, i, np.newaxis], + transfer_matrix[:, j, np.newaxis])) + + if not np.allclose(sm[0], sm[1]): + ker_pole_imo_mu1 = np.dot(ker_pole[i], mu1) + ker_pole_i_nu1 = np.dot(ker_pole[j], nu1) + ker_pole_mu_nu = np.vstack((ker_pole_imo_mu1, ker_pole_i_nu1)) + else: + ker_pole_ij = np.vstack(( + np.hstack((ker_pole[i], + np.zeros(ker_pole[i].shape))), + np.hstack((np.zeros(ker_pole[j].shape), + ker_pole[j])) + )) + mu_nu_matrix = np.vstack( + (np.hstack((mu1, mu2)), np.hstack((nu1, nu2))) + ) + ker_pole_mu_nu = np.dot(ker_pole_ij, mu_nu_matrix) + transfer_matrix_ij = np.dot(np.dot(ker_pole_mu_nu, ker_pole_mu_nu.T), + transfer_matrix_j_mo_transfer_matrix_j) + if not np.allclose(transfer_matrix_ij, 0): + transfer_matrix_ij = (np.sqrt(2)*transfer_matrix_ij / + np.linalg.norm(transfer_matrix_ij)) + transfer_matrix[:, i] = transfer_matrix_ij[ + :transfer_matrix[:, i].shape[0], 0 + ] + transfer_matrix[:, j] = transfer_matrix_ij[ + transfer_matrix[:, i].shape[0]:, 0 + ] + else: + # As in knv0 if transfer_matrix_j_mo_transfer_matrix_j is orthogonal to + # Vect{ker_pole_mu_nu} assign transfer_matrixi/transfer_matrix_j to + # ker_pole_mu_nu and iterate. As we are looking for a vector in + # Vect{Matker_pole_MU_NU} (see section 6.1 page 19) this might help + # (that's a guess, not a claim !) + transfer_matrix[:, i] = ker_pole_mu_nu[ + :transfer_matrix[:, i].shape[0], 0 + ] + transfer_matrix[:, j] = ker_pole_mu_nu[ + transfer_matrix[:, i].shape[0]:, 0 + ] + + +def _YT_complex(ker_pole, Q, transfer_matrix, i, j): + """ + Applies algorithm from YT section 6.2 page 20 related to complex pairs + """ + # step 1 page 20 + ur = np.sqrt(2)*Q[:, -2, np.newaxis] + ui = np.sqrt(2)*Q[:, -1, np.newaxis] + u = ur + 1j*ui + + # step 2 page 20 + ker_pole_ij = ker_pole[i] + m = np.dot(np.dot(np.conj(ker_pole_ij.T), np.dot(u, np.conj(u).T) - + np.dot(np.conj(u), u.T)), ker_pole_ij) + + # step 3 page 20 + e_val, e_vec = np.linalg.eig(m) + # sort eigenvalues according to their module + e_val_idx = np.argsort(np.abs(e_val)) + mu1 = e_vec[:, e_val_idx[-1], np.newaxis] + mu2 = e_vec[:, e_val_idx[-2], np.newaxis] + + # what follows is a rough python translation of the formulas + # in section 6.2 page 20 (step 4) + + # remember transfer_matrix_i has been split as + # transfer_matrix[i]=real(transfer_matrix_i) and + # transfer_matrix[j]=imag(transfer_matrix_i) + transfer_matrix_j_mo_transfer_matrix_j = ( + transfer_matrix[:, i, np.newaxis] + + 1j*transfer_matrix[:, j, np.newaxis] + ) + if not np.allclose(np.abs(e_val[e_val_idx[-1]]), + np.abs(e_val[e_val_idx[-2]])): + ker_pole_mu = np.dot(ker_pole_ij, mu1) + else: + mu1_mu2_matrix = np.hstack((mu1, mu2)) + ker_pole_mu = np.dot(ker_pole_ij, mu1_mu2_matrix) + transfer_matrix_i_j = np.dot(np.dot(ker_pole_mu, np.conj(ker_pole_mu.T)), + transfer_matrix_j_mo_transfer_matrix_j) + + if not np.allclose(transfer_matrix_i_j, 0): + transfer_matrix_i_j = (transfer_matrix_i_j / + np.linalg.norm(transfer_matrix_i_j)) + transfer_matrix[:, i] = np.real(transfer_matrix_i_j[:, 0]) + transfer_matrix[:, j] = np.imag(transfer_matrix_i_j[:, 0]) + else: + # same idea as in YT_real + transfer_matrix[:, i] = np.real(ker_pole_mu[:, 0]) + transfer_matrix[:, j] = np.imag(ker_pole_mu[:, 0]) + + +def _YT_loop(ker_pole, transfer_matrix, poles, B, maxiter, rtol): + """ + Algorithm "YT" Tits, Yang. Globally Convergent + Algorithms for Robust Pole Assignment by State Feedback + https://hdl.handle.net/1903/5598 + The poles P have to be sorted accordingly to section 6.2 page 20 + + """ + # The IEEE edition of the YT paper gives useful information on the + # optimal update order for the real poles in order to minimize the number + # of times we have to loop over all poles, see page 1442 + nb_real = poles[np.isreal(poles)].shape[0] + # hnb => Half Nb Real + hnb = nb_real // 2 + + # Stick to the indices in the paper and then remove one to get numpy array + # index it is a bit easier to link the code to the paper this way even if it + # is not very clean. The paper is unclear about what should be done when + # there is only one real pole => use KNV0 on this real pole seem to work + if nb_real > 0: + #update the biggest real pole with the smallest one + update_order = [[nb_real], [1]] + else: + update_order = [[],[]] + + r_comp = np.arange(nb_real+1, len(poles)+1, 2) + # step 1.a + r_p = np.arange(1, hnb+nb_real % 2) + update_order[0].extend(2*r_p) + update_order[1].extend(2*r_p+1) + # step 1.b + update_order[0].extend(r_comp) + update_order[1].extend(r_comp+1) + # step 1.c + r_p = np.arange(1, hnb+1) + update_order[0].extend(2*r_p-1) + update_order[1].extend(2*r_p) + # step 1.d + if hnb == 0 and np.isreal(poles[0]): + update_order[0].append(1) + update_order[1].append(1) + update_order[0].extend(r_comp) + update_order[1].extend(r_comp+1) + # step 2.a + r_j = np.arange(2, hnb+nb_real % 2) + for j in r_j: + for i in range(1, hnb+1): + update_order[0].append(i) + update_order[1].append(i+j) + # step 2.b + if hnb == 0 and np.isreal(poles[0]): + update_order[0].append(1) + update_order[1].append(1) + update_order[0].extend(r_comp) + update_order[1].extend(r_comp+1) + # step 2.c + r_j = np.arange(2, hnb+nb_real % 2) + for j in r_j: + for i in range(hnb+1, nb_real+1): + idx_1 = i+j + if idx_1 > nb_real: + idx_1 = i+j-nb_real + update_order[0].append(i) + update_order[1].append(idx_1) + # step 2.d + if hnb == 0 and np.isreal(poles[0]): + update_order[0].append(1) + update_order[1].append(1) + update_order[0].extend(r_comp) + update_order[1].extend(r_comp+1) + # step 3.a + for i in range(1, hnb+1): + update_order[0].append(i) + update_order[1].append(i+hnb) + # step 3.b + if hnb == 0 and np.isreal(poles[0]): + update_order[0].append(1) + update_order[1].append(1) + update_order[0].extend(r_comp) + update_order[1].extend(r_comp+1) + + update_order = np.array(update_order).T-1 + stop = False + nb_try = 0 + while nb_try < maxiter and not stop: + det_transfer_matrixb = np.abs(np.linalg.det(transfer_matrix)) + for i, j in update_order: + if i == j: + assert i == 0, "i!=0 for KNV call in YT" + assert np.isreal(poles[i]), "calling KNV on a complex pole" + _KNV0(B, ker_pole, transfer_matrix, i, poles) + else: + transfer_matrix_not_i_j = np.delete(transfer_matrix, (i, j), + axis=1) + # after merge of gh-4249 great speed improvements could be + # achieved using QR updates instead of full QR in the line below + + #to debug with numpy qr uncomment the line below + #Q, _ = np.linalg.qr(transfer_matrix_not_i_j, mode="complete") + Q, _ = s_qr(transfer_matrix_not_i_j, mode="full") + + if np.isreal(poles[i]): + assert np.isreal(poles[j]), "mixing real and complex " + \ + "in YT_real" + str(poles) + _YT_real(ker_pole, Q, transfer_matrix, i, j) + else: + assert ~np.isreal(poles[i]), "mixing real and complex " + \ + "in YT_real" + str(poles) + _YT_complex(ker_pole, Q, transfer_matrix, i, j) + + det_transfer_matrix = np.max((np.sqrt(np.spacing(1)), + np.abs(np.linalg.det(transfer_matrix)))) + cur_rtol = np.abs( + (det_transfer_matrix - + det_transfer_matrixb) / + det_transfer_matrix) + if cur_rtol < rtol and det_transfer_matrix > np.sqrt(np.spacing(1)): + # Convergence test from YT page 21 + stop = True + nb_try += 1 + return stop, cur_rtol, nb_try + + +def _KNV0_loop(ker_pole, transfer_matrix, poles, B, maxiter, rtol): + """ + Loop over all poles one by one and apply KNV method 0 algorithm + """ + # This method is useful only because we need to be able to call + # _KNV0 from YT without looping over all poles, otherwise it would + # have been fine to mix _KNV0_loop and _KNV0 in a single function + stop = False + nb_try = 0 + while nb_try < maxiter and not stop: + det_transfer_matrixb = np.abs(np.linalg.det(transfer_matrix)) + for j in range(B.shape[0]): + _KNV0(B, ker_pole, transfer_matrix, j, poles) + + det_transfer_matrix = np.max((np.sqrt(np.spacing(1)), + np.abs(np.linalg.det(transfer_matrix)))) + cur_rtol = np.abs((det_transfer_matrix - det_transfer_matrixb) / + det_transfer_matrix) + if cur_rtol < rtol and det_transfer_matrix > np.sqrt(np.spacing(1)): + # Convergence test from YT page 21 + stop = True + + nb_try += 1 + return stop, cur_rtol, nb_try + + +def place_poles(A, B, poles, method="YT", rtol=1e-3, maxiter=30): + """ + Compute K such that eigenvalues (A - dot(B, K))=poles. + + K is the gain matrix such as the plant described by the linear system + ``AX+BU`` will have its closed-loop poles, i.e the eigenvalues ``A - B*K``, + as close as possible to those asked for in poles. + + SISO, MISO and MIMO systems are supported. + + Parameters + ---------- + A, B : ndarray + State-space representation of linear system ``AX + BU``. + poles : array_like + Desired real poles and/or complex conjugates poles. + Complex poles are only supported with ``method="YT"`` (default). + method : {'YT', 'KNV0'}, optional + Which method to choose to find the gain matrix K. One of: + + - 'YT': Yang Tits + - 'KNV0': Kautsky, Nichols, Van Dooren update method 0 + + See References and Notes for details on the algorithms. + rtol : float, optional + After each iteration the determinant of the eigenvectors of + ``A - B*K`` is compared to its previous value, when the relative + error between these two values becomes lower than `rtol` the algorithm + stops. Default is 1e-3. + maxiter : int, optional + Maximum number of iterations to compute the gain matrix. + Default is 30. + + Returns + ------- + full_state_feedback : Bunch object + full_state_feedback is composed of: + gain_matrix : 2-D ndarray + The closed loop matrix K such as the eigenvalues of ``A-BK`` + are as close as possible to the requested poles. + computed_poles : 1-D ndarray + The poles corresponding to ``A-BK`` sorted as first the real + poles in increasing order, then the complex conjugates in + lexicographic order. + requested_poles : 1-D ndarray + The poles the algorithm was asked to place sorted as above, + they may differ from what was achieved. + X : 2-D ndarray + The transfer matrix such as ``X * diag(poles) = (A - B*K)*X`` + (see Notes) + rtol : float + The relative tolerance achieved on ``det(X)`` (see Notes). + `rtol` will be NaN if it is possible to solve the system + ``diag(poles) = (A - B*K)``, or 0 when the optimization + algorithms can't do anything i.e when ``B.shape[1] == 1``. + nb_iter : int + The number of iterations performed before converging. + `nb_iter` will be NaN if it is possible to solve the system + ``diag(poles) = (A - B*K)``, or 0 when the optimization + algorithms can't do anything i.e when ``B.shape[1] == 1``. + + Notes + ----- + The Tits and Yang (YT), [2]_ paper is an update of the original Kautsky et + al. (KNV) paper [1]_. KNV relies on rank-1 updates to find the transfer + matrix X such that ``X * diag(poles) = (A - B*K)*X``, whereas YT uses + rank-2 updates. This yields on average more robust solutions (see [2]_ + pp 21-22), furthermore the YT algorithm supports complex poles whereas KNV + does not in its original version. Only update method 0 proposed by KNV has + been implemented here, hence the name ``'KNV0'``. + + KNV extended to complex poles is used in Matlab's ``place`` function, YT is + distributed under a non-free licence by Slicot under the name ``robpole``. + It is unclear and undocumented how KNV0 has been extended to complex poles + (Tits and Yang claim on page 14 of their paper that their method can not be + used to extend KNV to complex poles), therefore only YT supports them in + this implementation. + + As the solution to the problem of pole placement is not unique for MIMO + systems, both methods start with a tentative transfer matrix which is + altered in various way to increase its determinant. Both methods have been + proven to converge to a stable solution, however depending on the way the + initial transfer matrix is chosen they will converge to different + solutions and therefore there is absolutely no guarantee that using + ``'KNV0'`` will yield results similar to Matlab's or any other + implementation of these algorithms. + + Using the default method ``'YT'`` should be fine in most cases; ``'KNV0'`` + is only provided because it is needed by ``'YT'`` in some specific cases. + Furthermore ``'YT'`` gives on average more robust results than ``'KNV0'`` + when ``abs(det(X))`` is used as a robustness indicator. + + [2]_ is available as a technical report on the following URL: + https://hdl.handle.net/1903/5598 + + References + ---------- + .. [1] J. Kautsky, N.K. Nichols and P. van Dooren, "Robust pole assignment + in linear state feedback", International Journal of Control, Vol. 41 + pp. 1129-1155, 1985. + .. [2] A.L. Tits and Y. Yang, "Globally convergent algorithms for robust + pole assignment by state feedback", IEEE Transactions on Automatic + Control, Vol. 41, pp. 1432-1452, 1996. + + Examples + -------- + A simple example demonstrating real pole placement using both KNV and YT + algorithms. This is example number 1 from section 4 of the reference KNV + publication ([1]_): + + >>> import numpy as np + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + + >>> A = np.array([[ 1.380, -0.2077, 6.715, -5.676 ], + ... [-0.5814, -4.290, 0, 0.6750 ], + ... [ 1.067, 4.273, -6.654, 5.893 ], + ... [ 0.0480, 4.273, 1.343, -2.104 ]]) + >>> B = np.array([[ 0, 5.679 ], + ... [ 1.136, 1.136 ], + ... [ 0, 0, ], + ... [-3.146, 0 ]]) + >>> P = np.array([-0.2, -0.5, -5.0566, -8.6659]) + + Now compute K with KNV method 0, with the default YT method and with the YT + method while forcing 100 iterations of the algorithm and print some results + after each call. + + >>> fsf1 = signal.place_poles(A, B, P, method='KNV0') + >>> fsf1.gain_matrix + array([[ 0.20071427, -0.96665799, 0.24066128, -0.10279785], + [ 0.50587268, 0.57779091, 0.51795763, -0.41991442]]) + + >>> fsf2 = signal.place_poles(A, B, P) # uses YT method + >>> fsf2.computed_poles + array([-8.6659, -5.0566, -0.5 , -0.2 ]) + + >>> fsf3 = signal.place_poles(A, B, P, rtol=-1, maxiter=100) + >>> fsf3.X + array([[ 0.52072442+0.j, -0.08409372+0.j, -0.56847937+0.j, 0.74823657+0.j], + [-0.04977751+0.j, -0.80872954+0.j, 0.13566234+0.j, -0.29322906+0.j], + [-0.82266932+0.j, -0.19168026+0.j, -0.56348322+0.j, -0.43815060+0.j], + [ 0.22267347+0.j, 0.54967577+0.j, -0.58387806+0.j, -0.40271926+0.j]]) + + The absolute value of the determinant of X is a good indicator to check the + robustness of the results, both ``'KNV0'`` and ``'YT'`` aim at maximizing + it. Below a comparison of the robustness of the results above: + + >>> abs(np.linalg.det(fsf1.X)) < abs(np.linalg.det(fsf2.X)) + True + >>> abs(np.linalg.det(fsf2.X)) < abs(np.linalg.det(fsf3.X)) + True + + Now a simple example for complex poles: + + >>> A = np.array([[ 0, 7/3., 0, 0 ], + ... [ 0, 0, 0, 7/9. ], + ... [ 0, 0, 0, 0 ], + ... [ 0, 0, 0, 0 ]]) + >>> B = np.array([[ 0, 0 ], + ... [ 0, 0 ], + ... [ 1, 0 ], + ... [ 0, 1 ]]) + >>> P = np.array([-3, -1, -2-1j, -2+1j]) / 3. + >>> fsf = signal.place_poles(A, B, P, method='YT') + + We can plot the desired and computed poles in the complex plane: + + >>> t = np.linspace(0, 2*np.pi, 401) + >>> plt.plot(np.cos(t), np.sin(t), 'k--') # unit circle + >>> plt.plot(fsf.requested_poles.real, fsf.requested_poles.imag, + ... 'wo', label='Desired') + >>> plt.plot(fsf.computed_poles.real, fsf.computed_poles.imag, 'bx', + ... label='Placed') + >>> plt.grid() + >>> plt.axis('image') + >>> plt.axis([-1.1, 1.1, -1.1, 1.1]) + >>> plt.legend(bbox_to_anchor=(1.05, 1), loc=2, numpoints=1) + + """ + # Move away all the inputs checking, it only adds noise to the code + update_loop, poles = _valid_inputs(A, B, poles, method, rtol, maxiter) + + # The current value of the relative tolerance we achieved + cur_rtol = 0 + # The number of iterations needed before converging + nb_iter = 0 + + # Step A: QR decomposition of B page 1132 KN + # to debug with numpy qr uncomment the line below + # u, z = np.linalg.qr(B, mode="complete") + u, z = s_qr(B, mode="full") + rankB = np.linalg.matrix_rank(B) + u0 = u[:, :rankB] + u1 = u[:, rankB:] + z = z[:rankB, :] + + # If we can use the identity matrix as X the solution is obvious + if B.shape[0] == rankB: + # if B is square and full rank there is only one solution + # such as (A+BK)=inv(X)*diag(P)*X with X=eye(A.shape[0]) + # i.e K=inv(B)*(diag(P)-A) + # if B has as many lines as its rank (but not square) there are many + # solutions and we can choose one using least squares + # => use lstsq in both cases. + # In both cases the transfer matrix X will be eye(A.shape[0]) and I + # can hardly think of a better one so there is nothing to optimize + # + # for complex poles we use the following trick + # + # |a -b| has for eigenvalues a+b and a-b + # |b a| + # + # |a+bi 0| has the obvious eigenvalues a+bi and a-bi + # |0 a-bi| + # + # e.g solving the first one in R gives the solution + # for the second one in C + diag_poles = np.zeros(A.shape) + idx = 0 + while idx < poles.shape[0]: + p = poles[idx] + diag_poles[idx, idx] = np.real(p) + if ~np.isreal(p): + diag_poles[idx, idx+1] = -np.imag(p) + diag_poles[idx+1, idx+1] = np.real(p) + diag_poles[idx+1, idx] = np.imag(p) + idx += 1 # skip next one + idx += 1 + gain_matrix = np.linalg.lstsq(B, diag_poles-A, rcond=-1)[0] + transfer_matrix = np.eye(A.shape[0]) + cur_rtol = np.nan + nb_iter = np.nan + else: + # step A (p1144 KNV) and beginning of step F: decompose + # dot(U1.T, A-P[i]*I).T and build our set of transfer_matrix vectors + # in the same loop + ker_pole = [] + + # flag to skip the conjugate of a complex pole + skip_conjugate = False + # select orthonormal base ker_pole for each Pole and vectors for + # transfer_matrix + for j in range(B.shape[0]): + if skip_conjugate: + skip_conjugate = False + continue + pole_space_j = np.dot(u1.T, A-poles[j]*np.eye(B.shape[0])).T + + # after QR Q=Q0|Q1 + # only Q0 is used to reconstruct the qr'ed (dot Q, R) matrix. + # Q1 is orthogonal to Q0 and will be multiplied by the zeros in + # R when using mode "complete". In default mode Q1 and the zeros + # in R are not computed + + # To debug with numpy qr uncomment the line below + # Q, _ = np.linalg.qr(pole_space_j, mode="complete") + Q, _ = s_qr(pole_space_j, mode="full") + + ker_pole_j = Q[:, pole_space_j.shape[1]:] + + # We want to select one vector in ker_pole_j to build the transfer + # matrix, however qr returns sometimes vectors with zeros on the + # same line for each pole and this yields very long convergence + # times. + # Or some other times a set of vectors, one with zero imaginary + # part and one (or several) with imaginary parts. After trying + # many ways to select the best possible one (eg ditch vectors + # with zero imaginary part for complex poles) I ended up summing + # all vectors in ker_pole_j, this solves 100% of the problems and + # is a valid choice for transfer_matrix. + # This way for complex poles we are sure to have a non zero + # imaginary part that way, and the problem of lines full of zeros + # in transfer_matrix is solved too as when a vector from + # ker_pole_j has a zero the other one(s) when + # ker_pole_j.shape[1]>1) for sure won't have a zero there. + + transfer_matrix_j = np.sum(ker_pole_j, axis=1)[:, np.newaxis] + transfer_matrix_j = (transfer_matrix_j / + np.linalg.norm(transfer_matrix_j)) + if ~np.isreal(poles[j]): # complex pole + transfer_matrix_j = np.hstack([np.real(transfer_matrix_j), + np.imag(transfer_matrix_j)]) + ker_pole.extend([ker_pole_j, ker_pole_j]) + + # Skip next pole as it is the conjugate + skip_conjugate = True + else: # real pole, nothing to do + ker_pole.append(ker_pole_j) + + if j == 0: + transfer_matrix = transfer_matrix_j + else: + transfer_matrix = np.hstack((transfer_matrix, transfer_matrix_j)) + + if rankB > 1: # otherwise there is nothing we can optimize + stop, cur_rtol, nb_iter = update_loop(ker_pole, transfer_matrix, + poles, B, maxiter, rtol) + if not stop and rtol > 0: + # if rtol<=0 the user has probably done that on purpose, + # don't annoy them + err_msg = ( + "Convergence was not reached after maxiter iterations.\n" + f"You asked for a tolerance of {rtol}, we got {cur_rtol}." + ) + warnings.warn(err_msg, stacklevel=2) + + # reconstruct transfer_matrix to match complex conjugate pairs, + # ie transfer_matrix_j/transfer_matrix_j+1 are + # Re(Complex_pole), Im(Complex_pole) now and will be Re-Im/Re+Im after + transfer_matrix = transfer_matrix.astype(complex) + idx = 0 + while idx < poles.shape[0]-1: + if ~np.isreal(poles[idx]): + rel = transfer_matrix[:, idx].copy() + img = transfer_matrix[:, idx+1] + # rel will be an array referencing a column of transfer_matrix + # if we don't copy() it will changer after the next line and + # and the line after will not yield the correct value + transfer_matrix[:, idx] = rel-1j*img + transfer_matrix[:, idx+1] = rel+1j*img + idx += 1 # skip next one + idx += 1 + + try: + m = np.linalg.solve(transfer_matrix.T, np.dot(np.diag(poles), + transfer_matrix.T)).T + gain_matrix = np.linalg.solve(z, np.dot(u0.T, m-A)) + except np.linalg.LinAlgError as e: + raise ValueError("The poles you've chosen can't be placed. " + "Check the controllability matrix and try " + "another set of poles") from e + + # Beware: Kautsky solves A+BK but the usual form is A-BK + gain_matrix = -gain_matrix + # K still contains complex with ~=0j imaginary parts, get rid of them + gain_matrix = np.real(gain_matrix) + + full_state_feedback = Bunch() + full_state_feedback.gain_matrix = gain_matrix + full_state_feedback.computed_poles = _order_complex_poles( + np.linalg.eig(A - np.dot(B, gain_matrix))[0] + ) + full_state_feedback.requested_poles = poles + full_state_feedback.X = transfer_matrix + full_state_feedback.rtol = cur_rtol + full_state_feedback.nb_iter = nb_iter + + return full_state_feedback + + +def dlsim(system, u, t=None, x0=None): + r"""Simulate output of a discrete-time linear system. + + Parameters + ---------- + system : dlti | tuple + An instance of the LTI class `dlti` or a tuple describing the system. + The number of elements in the tuple determine the interpretation. I.e.: + + * ``system``: Instance of LTI class `dlti`. Note that derived instances, such + as instances of `TransferFunction`, `ZerosPolesGain`, or `StateSpace`, are + allowed as well. + * ``(num, den, dt)``: Rational polynomial as described in `TransferFunction`. + The coefficients of the polynomials should be specified in descending + exponent order, e.g., z² + 3z + 5 would be represented as ``[1, 3, 5]``. + * ``(zeros, poles, gain, dt)``: Zeros, poles, gain form as described + in `ZerosPolesGain`. + * ``(A, B, C, D, dt)``: State-space form as described in `StateSpace`. + + + u : array_like + An input array describing the input at each time `t` (interpolation is + assumed between given times). If there are multiple inputs, then each + column of the rank-2 array represents an input. + t : array_like, optional + The time steps at which the input is defined. If `t` is given, it + must be the same length as `u`, and the final value in `t` determines + the number of steps returned in the output. + x0 : array_like, optional + The initial conditions on the state vector (zero by default). + + Returns + ------- + tout : ndarray + Time values for the output, as a 1-D array. + yout : ndarray + System response, as a 1-D array. + xout : ndarray, optional + Time-evolution of the state-vector. Only generated if the input is a + `StateSpace` system. + + See Also + -------- + lsim, dstep, dimpulse, cont2discrete + + Examples + -------- + A simple integrator transfer function with a discrete time step of 1.0 + could be implemented as: + + >>> import numpy as np + >>> from scipy import signal + >>> tf = ([1.0,], [1.0, -1.0], 1.0) + >>> t_in = [0.0, 1.0, 2.0, 3.0] + >>> u = np.asarray([0.0, 0.0, 1.0, 1.0]) + >>> t_out, y = signal.dlsim(tf, u, t=t_in) + >>> y.T + array([[ 0., 0., 0., 1.]]) + + """ + # Convert system to dlti-StateSpace + if isinstance(system, lti): + raise AttributeError('dlsim can only be used with discrete-time dlti ' + 'systems.') + elif not isinstance(system, dlti): + system = dlti(*system[:-1], dt=system[-1]) + + # Condition needed to ensure output remains compatible + is_ss_input = isinstance(system, StateSpace) + system = system._as_ss() + + u = np.atleast_1d(u) + + if u.ndim == 1: + u = np.atleast_2d(u).T + + if t is None: + out_samples = len(u) + stoptime = (out_samples - 1) * system.dt + else: + stoptime = t[-1] + out_samples = int(np.floor(stoptime / system.dt)) + 1 + + # Pre-build output arrays + xout = np.zeros((out_samples, system.A.shape[0])) + yout = np.zeros((out_samples, system.C.shape[0])) + tout = np.linspace(0.0, stoptime, num=out_samples) + + # Check initial condition + if x0 is None: + xout[0, :] = np.zeros((system.A.shape[1],)) + else: + xout[0, :] = np.asarray(x0) + + # Pre-interpolate inputs into the desired time steps + if t is None: + u_dt = u + else: + if len(u.shape) == 1: + u = u[:, np.newaxis] + + u_dt = make_interp_spline(t, u, k=1)(tout) + + # Simulate the system + for i in range(0, out_samples - 1): + xout[i+1, :] = (np.dot(system.A, xout[i, :]) + + np.dot(system.B, u_dt[i, :])) + yout[i, :] = (np.dot(system.C, xout[i, :]) + + np.dot(system.D, u_dt[i, :])) + + # Last point + yout[out_samples-1, :] = (np.dot(system.C, xout[out_samples-1, :]) + + np.dot(system.D, u_dt[out_samples-1, :])) + + if is_ss_input: + return tout, yout, xout + else: + return tout, yout + + +def dimpulse(system, x0=None, t=None, n=None): + r"""Impulse response of discrete-time system. + + Parameters + ---------- + system : dlti | tuple + An instance of the LTI class `dlti` or a tuple describing the system. + The number of elements in the tuple determine the interpretation. I.e.: + + * ``system``: Instance of LTI class `dlti`. Note that derived instances, such + as instances of `TransferFunction`, `ZerosPolesGain`, or `StateSpace`, are + allowed as well. + * ``(num, den, dt)``: Rational polynomial as described in `TransferFunction`. + The coefficients of the polynomials should be specified in descending + exponent order, e.g., z² + 3z + 5 would be represented as ``[1, 3, 5]``. + * ``(zeros, poles, gain, dt)``: Zeros, poles, gain form as described + in `ZerosPolesGain`. + * ``(A, B, C, D, dt)``: State-space form as described in `StateSpace`. + + x0 : array_like, optional + Initial state-vector. Defaults to zero. + t : array_like, optional + Time points. Computed if not given. + n : int, optional + The number of time points to compute (if `t` is not given). + + Returns + ------- + tout : ndarray + Time values for the output, as a 1-D array. + yout : tuple of ndarray + Impulse response of system. Each element of the tuple represents + the output of the system based on an impulse in each input. + + See Also + -------- + impulse, dstep, dlsim, cont2discrete + + Examples + -------- + >>> import numpy as np + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + ... + >>> dt = 1 # sampling interval is one => time unit is sample number + >>> bb, aa = signal.butter(3, 0.25, fs=1/dt) + >>> t, y = signal.dimpulse((bb, aa, dt), n=25) + ... + >>> fig0, ax0 = plt.subplots() + >>> ax0.step(t, np.squeeze(y), '.-', where='post') + >>> ax0.set_title(r"Impulse Response of a $3^\text{rd}$ Order Butterworth Filter") + >>> ax0.set(xlabel='Sample number', ylabel='Amplitude') + >>> ax0.grid() + >>> plt.show() + """ + # Convert system to dlti-StateSpace + if isinstance(system, dlti): + system = system._as_ss() + elif isinstance(system, lti): + raise AttributeError('dimpulse can only be used with discrete-time ' + 'dlti systems.') + else: + system = dlti(*system[:-1], dt=system[-1])._as_ss() + + # Default to 100 samples if unspecified + if n is None: + n = 100 + + # If time is not specified, use the number of samples + # and system dt + if t is None: + t = np.linspace(0, n * system.dt, n, endpoint=False) + else: + t = np.asarray(t) + + # For each input, implement a step change + yout = None + for i in range(0, system.inputs): + u = np.zeros((t.shape[0], system.inputs)) + u[0, i] = 1.0 + + one_output = dlsim(system, u, t=t, x0=x0) + + if yout is None: + yout = (one_output[1],) + else: + yout = yout + (one_output[1],) + + tout = one_output[0] + + return tout, yout + + +def dstep(system, x0=None, t=None, n=None): + r"""Step response of discrete-time system. + + Parameters + ---------- + system : dlti | tuple + An instance of the LTI class `dlti` or a tuple describing the system. + The number of elements in the tuple determine the interpretation. I.e.: + + * ``system``: Instance of LTI class `dlti`. Note that derived instances, such + as instances of `TransferFunction`, `ZerosPolesGain`, or `StateSpace`, are + allowed as well. + * ``(num, den, dt)``: Rational polynomial as described in `TransferFunction`. + The coefficients of the polynomials should be specified in descending + exponent order, e.g., z² + 3z + 5 would be represented as ``[1, 3, 5]``. + * ``(zeros, poles, gain, dt)``: Zeros, poles, gain form as described + in `ZerosPolesGain`. + * ``(A, B, C, D, dt)``: State-space form as described in `StateSpace`. + + x0 : array_like, optional + Initial state-vector. Defaults to zero. + t : array_like, optional + Time points. Computed if not given. + n : int, optional + The number of time points to compute (if `t` is not given). + + Returns + ------- + tout : ndarray + Output time points, as a 1-D array. + yout : tuple of ndarray + Step response of system. Each element of the tuple represents + the output of the system based on a step response to each input. + + See Also + -------- + step, dimpulse, dlsim, cont2discrete + + Examples + -------- + The following example illustrates how to create a digital Butterworth filer and + plot its step response: + + >>> import numpy as np + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + ... + >>> dt = 1 # sampling interval is one => time unit is sample number + >>> bb, aa = signal.butter(3, 0.25, fs=1/dt) + >>> t, y = signal.dstep((bb, aa, dt), n=25) + ... + >>> fig0, ax0 = plt.subplots() + >>> ax0.step(t, np.squeeze(y), '.-', where='post') + >>> ax0.set_title(r"Step Response of a $3^\text{rd}$ Order Butterworth Filter") + >>> ax0.set(xlabel='Sample number', ylabel='Amplitude', ylim=(0, 1.1*np.max(y))) + >>> ax0.grid() + >>> plt.show() + """ + # Convert system to dlti-StateSpace + if isinstance(system, dlti): + system = system._as_ss() + elif isinstance(system, lti): + raise AttributeError('dstep can only be used with discrete-time dlti ' + 'systems.') + else: + system = dlti(*system[:-1], dt=system[-1])._as_ss() + + # Default to 100 samples if unspecified + if n is None: + n = 100 + + # If time is not specified, use the number of samples + # and system dt + if t is None: + t = np.linspace(0, n * system.dt, n, endpoint=False) + else: + t = np.asarray(t) + + # For each input, implement a step change + yout = None + for i in range(0, system.inputs): + u = np.zeros((t.shape[0], system.inputs)) + u[:, i] = np.ones((t.shape[0],)) + + one_output = dlsim(system, u, t=t, x0=x0) + + if yout is None: + yout = (one_output[1],) + else: + yout = yout + (one_output[1],) + + tout = one_output[0] + + return tout, yout + + +def dfreqresp(system, w=None, n=10000, whole=False): + r""" + Calculate the frequency response of a discrete-time system. + + Parameters + ---------- + system : dlti | tuple + An instance of the LTI class `dlti` or a tuple describing the system. + The number of elements in the tuple determine the interpretation. I.e.: + + * ``system``: Instance of LTI class `dlti`. Note that derived instances, such + as instances of `TransferFunction`, `ZerosPolesGain`, or `StateSpace`, are + allowed as well. + * ``(num, den, dt)``: Rational polynomial as described in `TransferFunction`. + The coefficients of the polynomials should be specified in descending + exponent order, e.g., z² + 3z + 5 would be represented as ``[1, 3, 5]``. + * ``(zeros, poles, gain, dt)``: Zeros, poles, gain form as described + in `ZerosPolesGain`. + * ``(A, B, C, D, dt)``: State-space form as described in `StateSpace`. + + w : array_like, optional + Array of frequencies (in radians/sample). Magnitude and phase data is + calculated for every value in this array. If not given a reasonable + set will be calculated. + n : int, optional + Number of frequency points to compute if `w` is not given. The `n` + frequencies are logarithmically spaced in an interval chosen to + include the influence of the poles and zeros of the system. + whole : bool, optional + Normally, if 'w' is not given, frequencies are computed from 0 to the + Nyquist frequency, pi radians/sample (upper-half of unit-circle). If + `whole` is True, compute frequencies from 0 to 2*pi radians/sample. + + Returns + ------- + w : 1D ndarray + Frequency array [radians/sample] + H : 1D ndarray + Array of complex magnitude values + + Notes + ----- + If (num, den) is passed in for ``system``, coefficients for both the + numerator and denominator should be specified in descending exponent + order (e.g. ``z^2 + 3z + 5`` would be represented as ``[1, 3, 5]``). + + .. versionadded:: 0.18.0 + + Examples + -------- + The following example generates the Nyquist plot of the transfer function + :math:`H(z) = \frac{1}{z^2 + 2z + 3}` with a sampling time of 0.05 seconds: + + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + >>> sys = signal.TransferFunction([1], [1, 2, 3], dt=0.05) # construct H(z) + >>> w, H = signal.dfreqresp(sys) + ... + >>> fig0, ax0 = plt.subplots() + >>> ax0.plot(H.real, H.imag, label=r"$H(z=e^{+j\omega})$") + >>> ax0.plot(H.real, -H.imag, label=r"$H(z=e^{-j\omega})$") + >>> ax0.set_title(r"Nyquist Plot of $H(z) = 1 / (z^2 + 2z + 3)$") + >>> ax0.set(xlabel=r"$\text{Re}\{z\}$", ylabel=r"$\text{Im}\{z\}$", + ... xlim=(-0.2, 0.65), aspect='equal') + >>> ax0.plot(H[0].real, H[0].imag, 'k.') # mark H(exp(1j*w[0])) + >>> ax0.text(0.2, 0, r"$H(e^{j0})$") + >>> ax0.grid(True) + >>> ax0.legend() + >>> plt.show() + """ + if not isinstance(system, dlti): + if isinstance(system, lti): + raise AttributeError('dfreqresp can only be used with ' + 'discrete-time systems.') + + system = dlti(*system[:-1], dt=system[-1]) + + if isinstance(system, StateSpace): + # No SS->ZPK code exists right now, just SS->TF->ZPK + system = system._as_tf() + + if not isinstance(system, TransferFunction | ZerosPolesGain): + raise ValueError('Unknown system type') + + if system.inputs != 1 or system.outputs != 1: + raise ValueError("dfreqresp requires a SISO (single input, single " + "output) system.") + + if w is not None: + worN = w + else: + worN = n + + if isinstance(system, TransferFunction): + # Convert numerator and denominator from polynomials in the variable + # 'z' to polynomials in the variable 'z^-1', as freqz expects. + num, den = TransferFunction._z_to_zinv(system.num.ravel(), system.den) + w, h = freqz(num, den, worN=worN, whole=whole) + + elif isinstance(system, ZerosPolesGain): + w, h = freqz_zpk(system.zeros, system.poles, system.gain, worN=worN, + whole=whole) + + return w, h + + +def dbode(system, w=None, n=100): + r"""Calculate Bode magnitude and phase data of a discrete-time system. + + Parameters + ---------- + system : dlti | tuple + An instance of the LTI class `dlti` or a tuple describing the system. + The number of elements in the tuple determine the interpretation. I.e.: + + * ``system``: Instance of LTI class `dlti`. Note that derived instances, such + as instances of `TransferFunction`, `ZerosPolesGain`, or `StateSpace`, are + allowed as well. + * ``(num, den, dt)``: Rational polynomial as described in `TransferFunction`. + The coefficients of the polynomials should be specified in descending + exponent order, e.g., z² + 3z + 5 would be represented as ``[1, 3, 5]``. + * ``(zeros, poles, gain, dt)``: Zeros, poles, gain form as described + in `ZerosPolesGain`. + * ``(A, B, C, D, dt)``: State-space form as described in `StateSpace`. + + w : array_like, optional + Array of frequencies normalized to the Nyquist frequency being π, i.e., + having unit radiant / sample. Magnitude and phase data is calculated for every + value in this array. If not given, a reasonable set will be calculated. + n : int, optional + Number of frequency points to compute if `w` is not given. The `n` + frequencies are logarithmically spaced in an interval chosen to + include the influence of the poles and zeros of the system. + + Returns + ------- + w : 1D ndarray + Array of frequencies normalized to the Nyquist frequency being ``np.pi/dt`` + with ``dt`` being the sampling interval of the `system` parameter. + The unit is rad/s assuming ``dt`` is in seconds. + mag : 1D ndarray + Magnitude array in dB + phase : 1D ndarray + Phase array in degrees + + Notes + ----- + This function is a convenience wrapper around `dfreqresp` for extracting + magnitude and phase from the calculated complex-valued amplitude of the + frequency response. + + .. versionadded:: 0.18.0 + + See Also + -------- + dfreqresp, dlti, TransferFunction, ZerosPolesGain, StateSpace + + + Examples + -------- + The following example shows how to create a Bode plot of a 5-th order + Butterworth lowpass filter with a corner frequency of 100 Hz: + + >>> import matplotlib.pyplot as plt + >>> import numpy as np + >>> from scipy import signal + ... + >>> T = 1e-4 # sampling interval in s + >>> f_c, o = 1e2, 5 # corner frequency in Hz (i.e., -3 dB value) and filter order + >>> bb, aa = signal.butter(o, f_c, 'lowpass', fs=1/T) + ... + >>> w, mag, phase = signal.dbode((bb, aa, T)) + >>> w /= 2*np.pi # convert unit of frequency into Hertz + ... + >>> fg, (ax0, ax1) = plt.subplots(2, 1, sharex='all', figsize=(5, 4), + ... tight_layout=True) + >>> ax0.set_title("Bode Plot of Butterworth Lowpass Filter " + + ... rf"($f_c={f_c:g}\,$Hz, order={o})") + >>> ax0.set_ylabel(r"Magnitude in dB") + >>> ax1.set(ylabel=r"Phase in Degrees", + ... xlabel="Frequency $f$ in Hertz", xlim=(w[1], w[-1])) + >>> ax0.semilogx(w, mag, 'C0-', label=r"$20\,\log_{10}|G(f)|$") # Magnitude plot + >>> ax1.semilogx(w, phase, 'C1-', label=r"$\angle G(f)$") # Phase plot + ... + >>> for ax_ in (ax0, ax1): + ... ax_.axvline(f_c, color='m', alpha=0.25, label=rf"${f_c=:g}\,$Hz") + ... ax_.grid(which='both', axis='x') # plot major & minor vertical grid lines + ... ax_.grid(which='major', axis='y') + ... ax_.legend() + >>> plt.show() + """ + w, y = dfreqresp(system, w=w, n=n) + + if isinstance(system, dlti): + dt = system.dt + else: + dt = system[-1] + + mag = 20.0 * np.log10(abs(y)) + phase = np.rad2deg(np.unwrap(np.angle(y))) + + return w / dt, mag, phase diff --git a/venv/lib/python3.12/site-packages/scipy/signal/_max_len_seq.py b/venv/lib/python3.12/site-packages/scipy/signal/_max_len_seq.py new file mode 100644 index 0000000000000000000000000000000000000000..4d64beaca86d1da50b563668679b6fc52c954ab0 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/_max_len_seq.py @@ -0,0 +1,139 @@ +# Author: Eric Larson +# 2014 + +"""Tools for MLS generation""" + +import numpy as np + +from ._max_len_seq_inner import _max_len_seq_inner + +__all__ = ['max_len_seq'] + + +# These are definitions of linear shift register taps for use in max_len_seq() +_mls_taps = {2: [1], 3: [2], 4: [3], 5: [3], 6: [5], 7: [6], 8: [7, 6, 1], + 9: [5], 10: [7], 11: [9], 12: [11, 10, 4], 13: [12, 11, 8], + 14: [13, 12, 2], 15: [14], 16: [15, 13, 4], 17: [14], + 18: [11], 19: [18, 17, 14], 20: [17], 21: [19], 22: [21], + 23: [18], 24: [23, 22, 17], 25: [22], 26: [25, 24, 20], + 27: [26, 25, 22], 28: [25], 29: [27], 30: [29, 28, 7], + 31: [28], 32: [31, 30, 10]} + +def max_len_seq(nbits, state=None, length=None, taps=None): + """ + Maximum length sequence (MLS) generator. + + Parameters + ---------- + nbits : int + Number of bits to use. Length of the resulting sequence will + be ``(2**nbits) - 1``. Note that generating long sequences + (e.g., greater than ``nbits == 16``) can take a long time. + state : array_like, optional + If array, must be of length ``nbits``, and will be cast to binary + (bool) representation. If None, a seed of ones will be used, + producing a repeatable representation. If ``state`` is all + zeros, an error is raised as this is invalid. Default: None. + length : int, optional + Number of samples to compute. If None, the entire length + ``(2**nbits) - 1`` is computed. + taps : array_like, optional + Polynomial taps to use (e.g., ``[7, 6, 1]`` for an 8-bit sequence). + If None, taps will be automatically selected (for up to + ``nbits == 32``). + + Returns + ------- + seq : array + Resulting MLS sequence of 0's and 1's. + state : array + The final state of the shift register. + + Notes + ----- + The algorithm for MLS generation is generically described in: + + https://en.wikipedia.org/wiki/Maximum_length_sequence + + The default values for taps are specifically taken from the first + option listed for each value of ``nbits`` in: + + https://web.archive.org/web/20181001062252/http://www.newwaveinstruments.com/resources/articles/m_sequence_linear_feedback_shift_register_lfsr.htm + + .. versionadded:: 0.15.0 + + Examples + -------- + MLS uses binary convention: + + >>> from scipy.signal import max_len_seq + >>> max_len_seq(4)[0] + array([1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0], dtype=int8) + + MLS has a white spectrum (except for DC): + + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from numpy.fft import fft, ifft, fftshift, fftfreq + >>> seq = max_len_seq(6)[0]*2-1 # +1 and -1 + >>> spec = fft(seq) + >>> N = len(seq) + >>> plt.plot(fftshift(fftfreq(N)), fftshift(np.abs(spec)), '.-') + >>> plt.margins(0.1, 0.1) + >>> plt.grid(True) + >>> plt.show() + + Circular autocorrelation of MLS is an impulse: + + >>> acorrcirc = ifft(spec * np.conj(spec)).real + >>> plt.figure() + >>> plt.plot(np.arange(-N/2+1, N/2+1), fftshift(acorrcirc), '.-') + >>> plt.margins(0.1, 0.1) + >>> plt.grid(True) + >>> plt.show() + + Linear autocorrelation of MLS is approximately an impulse: + + >>> acorr = np.correlate(seq, seq, 'full') + >>> plt.figure() + >>> plt.plot(np.arange(-N+1, N), acorr, '.-') + >>> plt.margins(0.1, 0.1) + >>> plt.grid(True) + >>> plt.show() + + """ + taps_dtype = np.int32 if np.intp().itemsize == 4 else np.int64 + if taps is None: + if nbits not in _mls_taps: + known_taps = np.array(list(_mls_taps.keys())) + raise ValueError(f'nbits must be between {known_taps.min()} and ' + f'{known_taps.max()} if taps is None') + taps = np.array(_mls_taps[nbits], taps_dtype) + else: + taps = np.unique(np.array(taps, taps_dtype))[::-1] + if np.any(taps < 0) or np.any(taps > nbits) or taps.size < 1: + raise ValueError('taps must be non-empty with values between ' + 'zero and nbits (inclusive)') + taps = np.array(taps) # needed for Cython and Pythran + n_max = (2**nbits) - 1 + if length is None: + length = n_max + else: + length = int(length) + if length < 0: + raise ValueError('length must be greater than or equal to 0') + # We use int8 instead of bool here because NumPy arrays of bools + # don't seem to work nicely with Cython + if state is None: + state = np.ones(nbits, dtype=np.int8, order='c') + else: + # makes a copy if need be, ensuring it's 0's and 1's + state = np.array(state, dtype=bool, order='c').astype(np.int8) + if state.ndim != 1 or state.size != nbits: + raise ValueError('state must be a 1-D array of size nbits') + if np.all(state == 0): + raise ValueError('state must not be all zeros') + + seq = np.empty(length, dtype=np.int8, order='c') + state = _max_len_seq_inner(taps, state, nbits, length, seq) + return seq, state diff --git a/venv/lib/python3.12/site-packages/scipy/signal/_max_len_seq_inner.cpython-312-x86_64-linux-gnu.so b/venv/lib/python3.12/site-packages/scipy/signal/_max_len_seq_inner.cpython-312-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..514d8d3726cccaaf8c65ad6fea15f96f89f62bf3 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/signal/_max_len_seq_inner.cpython-312-x86_64-linux-gnu.so differ diff --git a/venv/lib/python3.12/site-packages/scipy/signal/_peak_finding.py b/venv/lib/python3.12/site-packages/scipy/signal/_peak_finding.py new file mode 100644 index 0000000000000000000000000000000000000000..a5fe546756dfebddd831de936a110ad05eab2d2b --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/_peak_finding.py @@ -0,0 +1,1307 @@ +""" +Functions for identifying peaks in signals. +""" +import math +import numpy as np + +from scipy.signal._wavelets import _cwt, _ricker +from scipy.stats import scoreatpercentile + +from ._peak_finding_utils import ( + _local_maxima_1d, + _select_by_peak_distance, + _peak_prominences, + _peak_widths +) + + +__all__ = ['argrelmin', 'argrelmax', 'argrelextrema', 'peak_prominences', + 'peak_widths', 'find_peaks', 'find_peaks_cwt'] + + +def _boolrelextrema(data, comparator, axis=0, order=1, mode='clip'): + """ + Calculate the relative extrema of `data`. + + Relative extrema are calculated by finding locations where + ``comparator(data[n], data[n+1:n+order+1])`` is True. + + Parameters + ---------- + data : ndarray + Array in which to find the relative extrema. + comparator : callable + Function to use to compare two data points. + Should take two arrays as arguments. + axis : int, optional + Axis over which to select from `data`. Default is 0. + order : int, optional + How many points on each side to use for the comparison + to consider ``comparator(n,n+x)`` to be True. + mode : str, optional + How the edges of the vector are treated. 'wrap' (wrap around) or + 'clip' (treat overflow as the same as the last (or first) element). + Default 'clip'. See numpy.take. + + Returns + ------- + extrema : ndarray + Boolean array of the same shape as `data` that is True at an extrema, + False otherwise. + + See Also + -------- + argrelmax, argrelmin + + Examples + -------- + >>> import numpy as np + >>> from scipy.signal._peak_finding import _boolrelextrema + >>> testdata = np.array([1,2,3,2,1]) + >>> _boolrelextrema(testdata, np.greater, axis=0) + array([False, False, True, False, False], dtype=bool) + + """ + if (int(order) != order) or (order < 1): + raise ValueError('Order must be an int >= 1') + + datalen = data.shape[axis] + locs = np.arange(0, datalen) + + results = np.ones(data.shape, dtype=bool) + main = data.take(locs, axis=axis, mode=mode) + for shift in range(1, order + 1): + plus = data.take(locs + shift, axis=axis, mode=mode) + minus = data.take(locs - shift, axis=axis, mode=mode) + results &= comparator(main, plus) + results &= comparator(main, minus) + if ~results.any(): + return results + return results + + +def argrelmin(data, axis=0, order=1, mode='clip'): + """ + Calculate the relative minima of `data`. + + Parameters + ---------- + data : ndarray + Array in which to find the relative minima. + axis : int, optional + Axis over which to select from `data`. Default is 0. + order : int, optional + How many points on each side to use for the comparison + to consider ``comparator(n, n+x)`` to be True. + mode : str, optional + How the edges of the vector are treated. + Available options are 'wrap' (wrap around) or 'clip' (treat overflow + as the same as the last (or first) element). + Default 'clip'. See numpy.take. + + Returns + ------- + extrema : tuple of ndarrays + Indices of the minima in arrays of integers. ``extrema[k]`` is + the array of indices of axis `k` of `data`. Note that the + return value is a tuple even when `data` is 1-D. + + See Also + -------- + argrelextrema, argrelmax, find_peaks + + Notes + ----- + This function uses `argrelextrema` with np.less as comparator. Therefore, it + requires a strict inequality on both sides of a value to consider it a + minimum. This means flat minima (more than one sample wide) are not detected. + In case of 1-D `data` `find_peaks` can be used to detect all + local minima, including flat ones, by calling it with negated `data`. + + .. versionadded:: 0.11.0 + + Examples + -------- + >>> import numpy as np + >>> from scipy.signal import argrelmin + >>> x = np.array([2, 1, 2, 3, 2, 0, 1, 0]) + >>> argrelmin(x) + (array([1, 5]),) + >>> y = np.array([[1, 2, 1, 2], + ... [2, 2, 0, 0], + ... [5, 3, 4, 4]]) + ... + >>> argrelmin(y, axis=1) + (array([0, 2]), array([2, 1])) + + """ + return argrelextrema(data, np.less, axis, order, mode) + + +def argrelmax(data, axis=0, order=1, mode='clip'): + """ + Calculate the relative maxima of `data`. + + Parameters + ---------- + data : ndarray + Array in which to find the relative maxima. + axis : int, optional + Axis over which to select from `data`. Default is 0. + order : int, optional + How many points on each side to use for the comparison + to consider ``comparator(n, n+x)`` to be True. + mode : str, optional + How the edges of the vector are treated. + Available options are 'wrap' (wrap around) or 'clip' (treat overflow + as the same as the last (or first) element). + Default 'clip'. See `numpy.take`. + + Returns + ------- + extrema : tuple of ndarrays + Indices of the maxima in arrays of integers. ``extrema[k]`` is + the array of indices of axis `k` of `data`. Note that the + return value is a tuple even when `data` is 1-D. + + See Also + -------- + argrelextrema, argrelmin, find_peaks + + Notes + ----- + This function uses `argrelextrema` with np.greater as comparator. Therefore, + it requires a strict inequality on both sides of a value to consider it a + maximum. This means flat maxima (more than one sample wide) are not detected. + In case of 1-D `data` `find_peaks` can be used to detect all + local maxima, including flat ones. + + .. versionadded:: 0.11.0 + + Examples + -------- + >>> import numpy as np + >>> from scipy.signal import argrelmax + >>> x = np.array([2, 1, 2, 3, 2, 0, 1, 0]) + >>> argrelmax(x) + (array([3, 6]),) + >>> y = np.array([[1, 2, 1, 2], + ... [2, 2, 0, 0], + ... [5, 3, 4, 4]]) + ... + >>> argrelmax(y, axis=1) + (array([0]), array([1])) + """ + return argrelextrema(data, np.greater, axis, order, mode) + + +def argrelextrema(data, comparator, axis=0, order=1, mode='clip'): + """ + Calculate the relative extrema of `data`. + + Parameters + ---------- + data : ndarray + Array in which to find the relative extrema. + comparator : callable + Function to use to compare two data points. + Should take two arrays as arguments. + axis : int, optional + Axis over which to select from `data`. Default is 0. + order : int, optional + How many points on each side to use for the comparison + to consider ``comparator(n, n+x)`` to be True. + mode : str, optional + How the edges of the vector are treated. 'wrap' (wrap around) or + 'clip' (treat overflow as the same as the last (or first) element). + Default is 'clip'. See `numpy.take`. + + Returns + ------- + extrema : tuple of ndarrays + Indices of the maxima in arrays of integers. ``extrema[k]`` is + the array of indices of axis `k` of `data`. Note that the + return value is a tuple even when `data` is 1-D. + + See Also + -------- + argrelmin, argrelmax + + Notes + ----- + + .. versionadded:: 0.11.0 + + Examples + -------- + >>> import numpy as np + >>> from scipy.signal import argrelextrema + >>> x = np.array([2, 1, 2, 3, 2, 0, 1, 0]) + >>> argrelextrema(x, np.greater) + (array([3, 6]),) + >>> y = np.array([[1, 2, 1, 2], + ... [2, 2, 0, 0], + ... [5, 3, 4, 4]]) + ... + >>> argrelextrema(y, np.less, axis=1) + (array([0, 2]), array([2, 1])) + + """ + results = _boolrelextrema(data, comparator, + axis, order, mode) + return np.nonzero(results) + + +def _arg_x_as_expected(value): + """Ensure argument `x` is a 1-D C-contiguous array of dtype('float64'). + + Used in `find_peaks`, `peak_prominences` and `peak_widths` to make `x` + compatible with the signature of the wrapped Cython functions. + + Returns + ------- + value : ndarray + A 1-D C-contiguous array with dtype('float64'). + """ + value = np.asarray(value, order='C', dtype=np.float64) + if value.ndim != 1: + raise ValueError('`x` must be a 1-D array') + return value + + +def _arg_peaks_as_expected(value): + """Ensure argument `peaks` is a 1-D C-contiguous array of dtype('intp'). + + Used in `peak_prominences` and `peak_widths` to make `peaks` compatible + with the signature of the wrapped Cython functions. + + Returns + ------- + value : ndarray + A 1-D C-contiguous array with dtype('intp'). + """ + value = np.asarray(value) + if value.size == 0: + # Empty arrays default to np.float64 but are valid input + value = np.array([], dtype=np.intp) + try: + # Safely convert to C-contiguous array of type np.intp + value = value.astype(np.intp, order='C', casting='safe', + subok=False, copy=False) + except TypeError as e: + raise TypeError("cannot safely cast `peaks` to dtype('intp')") from e + if value.ndim != 1: + raise ValueError('`peaks` must be a 1-D array') + return value + + +def _arg_wlen_as_expected(value): + """Ensure argument `wlen` is of type `np.intp` and larger than 1. + + Used in `peak_prominences` and `peak_widths`. + + Returns + ------- + value : np.intp + The original `value` rounded up to an integer or -1 if `value` was + None. + """ + if value is None: + # _peak_prominences expects an intp; -1 signals that no value was + # supplied by the user + value = -1 + elif 1 < value: + # Round up to a positive integer + if isinstance(value, float): + value = math.ceil(value) + value = np.intp(value) + else: + raise ValueError(f'`wlen` must be larger than 1, was {value}') + return value + + +def peak_prominences(x, peaks, wlen=None): + """ + Calculate the prominence of each peak in a signal. + + The prominence of a peak measures how much a peak stands out from the + surrounding baseline of the signal and is defined as the vertical distance + between the peak and its lowest contour line. + + Parameters + ---------- + x : sequence + A signal with peaks. + peaks : sequence + Indices of peaks in `x`. + wlen : int, optional + A window length in samples that optionally limits the evaluated area for + each peak to a subset of `x`. The peak is always placed in the middle of + the window therefore the given length is rounded up to the next odd + integer. This parameter can speed up the calculation (see Notes). + + Returns + ------- + prominences : ndarray + The calculated prominences for each peak in `peaks`. + left_bases, right_bases : ndarray + The peaks' bases as indices in `x` to the left and right of each peak. + The higher base of each pair is a peak's lowest contour line. + + Raises + ------ + ValueError + If a value in `peaks` is an invalid index for `x`. + + Warns + ----- + PeakPropertyWarning + For indices in `peaks` that don't point to valid local maxima in `x`, + the returned prominence will be 0 and this warning is raised. This + also happens if `wlen` is smaller than the plateau size of a peak. + + Warnings + -------- + This function may return unexpected results for data containing NaNs. To + avoid this, NaNs should either be removed or replaced. + + See Also + -------- + find_peaks + Find peaks inside a signal based on peak properties. + peak_widths + Calculate the width of peaks. + + Notes + ----- + Strategy to compute a peak's prominence: + + 1. Extend a horizontal line from the current peak to the left and right + until the line either reaches the window border (see `wlen`) or + intersects the signal again at the slope of a higher peak. An + intersection with a peak of the same height is ignored. + 2. On each side find the minimal signal value within the interval defined + above. These points are the peak's bases. + 3. The higher one of the two bases marks the peak's lowest contour line. The + prominence can then be calculated as the vertical difference between the + peaks height itself and its lowest contour line. + + Searching for the peak's bases can be slow for large `x` with periodic + behavior because large chunks or even the full signal need to be evaluated + for the first algorithmic step. This evaluation area can be limited with the + parameter `wlen` which restricts the algorithm to a window around the + current peak and can shorten the calculation time if the window length is + short in relation to `x`. + However, this may stop the algorithm from finding the true global contour + line if the peak's true bases are outside this window. Instead, a higher + contour line is found within the restricted window leading to a smaller + calculated prominence. In practice, this is only relevant for the highest set + of peaks in `x`. This behavior may even be used intentionally to calculate + "local" prominences. + + .. versionadded:: 1.1.0 + + References + ---------- + .. [1] Wikipedia Article for Topographic Prominence: + https://en.wikipedia.org/wiki/Topographic_prominence + + Examples + -------- + >>> import numpy as np + >>> from scipy.signal import find_peaks, peak_prominences + >>> import matplotlib.pyplot as plt + + Create a test signal with two overlaid harmonics + + >>> x = np.linspace(0, 6 * np.pi, 1000) + >>> x = np.sin(x) + 0.6 * np.sin(2.6 * x) + + Find all peaks and calculate prominences + + >>> peaks, _ = find_peaks(x) + >>> prominences = peak_prominences(x, peaks)[0] + >>> prominences + array([1.24159486, 0.47840168, 0.28470524, 3.10716793, 0.284603 , + 0.47822491, 2.48340261, 0.47822491]) + + Calculate the height of each peak's contour line and plot the results + + >>> contour_heights = x[peaks] - prominences + >>> plt.plot(x) + >>> plt.plot(peaks, x[peaks], "x") + >>> plt.vlines(x=peaks, ymin=contour_heights, ymax=x[peaks]) + >>> plt.show() + + Let's evaluate a second example that demonstrates several edge cases for + one peak at index 5. + + >>> x = np.array([0, 1, 0, 3, 1, 3, 0, 4, 0]) + >>> peaks = np.array([5]) + >>> plt.plot(x) + >>> plt.plot(peaks, x[peaks], "x") + >>> plt.show() + >>> peak_prominences(x, peaks) # -> (prominences, left_bases, right_bases) + (array([3.]), array([2]), array([6])) + + Note how the peak at index 3 of the same height is not considered as a + border while searching for the left base. Instead, two minima at 0 and 2 + are found in which case the one closer to the evaluated peak is always + chosen. On the right side, however, the base must be placed at 6 because the + higher peak represents the right border to the evaluated area. + + >>> peak_prominences(x, peaks, wlen=3.1) + (array([2.]), array([4]), array([6])) + + Here, we restricted the algorithm to a window from 3 to 7 (the length is 5 + samples because `wlen` was rounded up to the next odd integer). Thus, the + only two candidates in the evaluated area are the two neighboring samples + and a smaller prominence is calculated. + """ + x = _arg_x_as_expected(x) + peaks = _arg_peaks_as_expected(peaks) + wlen = _arg_wlen_as_expected(wlen) + return _peak_prominences(x, peaks, wlen) + + +def peak_widths(x, peaks, rel_height=0.5, prominence_data=None, wlen=None): + """ + Calculate the width of each peak in a signal. + + This function calculates the width of a peak in samples at a relative + distance to the peak's height and prominence. + + Parameters + ---------- + x : sequence + A signal with peaks. + peaks : sequence + Indices of peaks in `x`. + rel_height : float, optional + Chooses the relative height at which the peak width is measured as a + percentage of its prominence. 1.0 calculates the width of the peak at + its lowest contour line while 0.5 evaluates at half the prominence + height. Must be at least 0. See notes for further explanation. + prominence_data : tuple, optional + A tuple of three arrays matching the output of `peak_prominences` when + called with the same arguments `x` and `peaks`. This data are calculated + internally if not provided. + wlen : int, optional + A window length in samples passed to `peak_prominences` as an optional + argument for internal calculation of `prominence_data`. This argument + is ignored if `prominence_data` is given. + + Returns + ------- + widths : ndarray + The widths for each peak in samples. + width_heights : ndarray + The height of the contour lines at which the `widths` where evaluated. + left_ips, right_ips : ndarray + Interpolated positions of left and right intersection points of a + horizontal line at the respective evaluation height. + + Raises + ------ + ValueError + If `prominence_data` is supplied but doesn't satisfy the condition + ``0 <= left_base <= peak <= right_base < x.shape[0]`` for each peak, + has the wrong dtype, is not C-contiguous or does not have the same + shape. + + Warns + ----- + PeakPropertyWarning + Raised if any calculated width is 0. This may stem from the supplied + `prominence_data` or if `rel_height` is set to 0. + + Warnings + -------- + This function may return unexpected results for data containing NaNs. To + avoid this, NaNs should either be removed or replaced. + + See Also + -------- + find_peaks + Find peaks inside a signal based on peak properties. + peak_prominences + Calculate the prominence of peaks. + + Notes + ----- + The basic algorithm to calculate a peak's width is as follows: + + * Calculate the evaluation height :math:`h_{eval}` with the formula + :math:`h_{eval} = h_{Peak} - P \\cdot R`, where :math:`h_{Peak}` is the + height of the peak itself, :math:`P` is the peak's prominence and + :math:`R` a positive ratio specified with the argument `rel_height`. + * Draw a horizontal line at the evaluation height to both sides, starting at + the peak's current vertical position until the lines either intersect a + slope, the signal border or cross the vertical position of the peak's + base (see `peak_prominences` for a definition). For the first case, + intersection with the signal, the true intersection point is estimated + with linear interpolation. + * Calculate the width as the horizontal distance between the chosen + endpoints on both sides. As a consequence of this the maximal possible + width for each peak is the horizontal distance between its bases. + + As shown above to calculate a peak's width its prominence and bases must be + known. You can supply these yourself with the argument `prominence_data`. + Otherwise, they are internally calculated (see `peak_prominences`). + + .. versionadded:: 1.1.0 + + Examples + -------- + >>> import numpy as np + >>> from scipy.signal import chirp, find_peaks, peak_widths + >>> import matplotlib.pyplot as plt + + Create a test signal with two overlaid harmonics + + >>> x = np.linspace(0, 6 * np.pi, 1000) + >>> x = np.sin(x) + 0.6 * np.sin(2.6 * x) + + Find all peaks and calculate their widths at the relative height of 0.5 + (contour line at half the prominence height) and 1 (at the lowest contour + line at full prominence height). + + >>> peaks, _ = find_peaks(x) + >>> results_half = peak_widths(x, peaks, rel_height=0.5) + >>> results_half[0] # widths + array([ 64.25172825, 41.29465463, 35.46943289, 104.71586081, + 35.46729324, 41.30429622, 181.93835853, 45.37078546]) + >>> results_full = peak_widths(x, peaks, rel_height=1) + >>> results_full[0] # widths + array([181.9396084 , 72.99284945, 61.28657872, 373.84622694, + 61.78404617, 72.48822812, 253.09161876, 79.36860878]) + + Plot signal, peaks and contour lines at which the widths where calculated + + >>> plt.plot(x) + >>> plt.plot(peaks, x[peaks], "x") + >>> plt.hlines(*results_half[1:], color="C2") + >>> plt.hlines(*results_full[1:], color="C3") + >>> plt.show() + """ + x = _arg_x_as_expected(x) + peaks = _arg_peaks_as_expected(peaks) + if prominence_data is None: + # Calculate prominence if not supplied and use wlen if supplied. + wlen = _arg_wlen_as_expected(wlen) + prominence_data = _peak_prominences(x, peaks, wlen) + return _peak_widths(x, peaks, rel_height, *prominence_data) + + +def _unpack_condition_args(interval, x, peaks): + """ + Parse condition arguments for `find_peaks`. + + Parameters + ---------- + interval : number or ndarray or sequence + Either a number or ndarray or a 2-element sequence of the former. The + first value is always interpreted as `imin` and the second, if supplied, + as `imax`. + x : ndarray + The signal with `peaks`. + peaks : ndarray + An array with indices used to reduce `imin` and / or `imax` if those are + arrays. + + Returns + ------- + imin, imax : number or ndarray or None + Minimal and maximal value in `argument`. + + Raises + ------ + ValueError : + If interval border is given as array and its size does not match the size + of `x`. + + Notes + ----- + + .. versionadded:: 1.1.0 + """ + try: + imin, imax = interval + except (TypeError, ValueError): + imin, imax = (interval, None) + + # Reduce arrays if arrays + if isinstance(imin, np.ndarray): + if imin.size != x.size: + raise ValueError('array size of lower interval border must match x') + imin = imin[peaks] + if isinstance(imax, np.ndarray): + if imax.size != x.size: + raise ValueError('array size of upper interval border must match x') + imax = imax[peaks] + + return imin, imax + + +def _select_by_property(peak_properties, pmin, pmax): + """ + Evaluate where the generic property of peaks confirms to an interval. + + Parameters + ---------- + peak_properties : ndarray + An array with properties for each peak. + pmin : None or number or ndarray + Lower interval boundary for `peak_properties`. ``None`` is interpreted as + an open border. + pmax : None or number or ndarray + Upper interval boundary for `peak_properties`. ``None`` is interpreted as + an open border. + + Returns + ------- + keep : bool + A boolean mask evaluating to true where `peak_properties` confirms to the + interval. + + See Also + -------- + find_peaks + + Notes + ----- + + .. versionadded:: 1.1.0 + """ + keep = np.ones(peak_properties.size, dtype=bool) + if pmin is not None: + keep &= (pmin <= peak_properties) + if pmax is not None: + keep &= (peak_properties <= pmax) + return keep + + +def _select_by_peak_threshold(x, peaks, tmin, tmax): + """ + Evaluate which peaks fulfill the threshold condition. + + Parameters + ---------- + x : ndarray + A 1-D array which is indexable by `peaks`. + peaks : ndarray + Indices of peaks in `x`. + tmin, tmax : scalar or ndarray or None + Minimal and / or maximal required thresholds. If supplied as ndarrays + their size must match `peaks`. ``None`` is interpreted as an open + border. + + Returns + ------- + keep : bool + A boolean mask evaluating to true where `peaks` fulfill the threshold + condition. + left_thresholds, right_thresholds : ndarray + Array matching `peak` containing the thresholds of each peak on + both sides. + + Notes + ----- + + .. versionadded:: 1.1.0 + """ + # Stack thresholds on both sides to make min / max operations easier: + # tmin is compared with the smaller, and tmax with the greater threshold to + # each peak's side + stacked_thresholds = np.vstack([x[peaks] - x[peaks - 1], + x[peaks] - x[peaks + 1]]) + keep = np.ones(peaks.size, dtype=bool) + if tmin is not None: + min_thresholds = np.min(stacked_thresholds, axis=0) + keep &= (tmin <= min_thresholds) + if tmax is not None: + max_thresholds = np.max(stacked_thresholds, axis=0) + keep &= (max_thresholds <= tmax) + + return keep, stacked_thresholds[0], stacked_thresholds[1] + + +def find_peaks(x, height=None, threshold=None, distance=None, + prominence=None, width=None, wlen=None, rel_height=0.5, + plateau_size=None): + """ + Find peaks inside a signal based on peak properties. + + This function takes a 1-D array and finds all local maxima by + simple comparison of neighboring values. Optionally, a subset of these + peaks can be selected by specifying conditions for a peak's properties. + + Parameters + ---------- + x : sequence + A signal with peaks. + height : number or ndarray or sequence, optional + Required height of peaks. Either a number, ``None``, an array matching + `x` or a 2-element sequence of the former. The first element is + always interpreted as the minimal and the second, if supplied, as the + maximal required height. + threshold : number or ndarray or sequence, optional + Required threshold of peaks, the vertical distance to its neighboring + samples. Either a number, ``None``, an array matching `x` or a + 2-element sequence of the former. The first element is always + interpreted as the minimal and the second, if supplied, as the maximal + required threshold. + distance : number, optional + Required minimal horizontal distance (>= 1) in samples between + neighbouring peaks. Smaller peaks are removed first until the condition + is fulfilled for all remaining peaks. + prominence : number or ndarray or sequence, optional + Required prominence of peaks. Either a number, ``None``, an array + matching `x` or a 2-element sequence of the former. The first + element is always interpreted as the minimal and the second, if + supplied, as the maximal required prominence. + width : number or ndarray or sequence, optional + Required width of peaks in samples. Either a number, ``None``, an array + matching `x` or a 2-element sequence of the former. The first + element is always interpreted as the minimal and the second, if + supplied, as the maximal required width. + wlen : int, optional + Used for calculation of the peaks prominences, thus it is only used if + one of the arguments `prominence` or `width` is given. See argument + `wlen` in `peak_prominences` for a full description of its effects. + rel_height : float, optional + Used for calculation of the peaks width, thus it is only used if `width` + is given. See argument `rel_height` in `peak_widths` for a full + description of its effects. + plateau_size : number or ndarray or sequence, optional + Required size of the flat top of peaks in samples. Either a number, + ``None``, an array matching `x` or a 2-element sequence of the former. + The first element is always interpreted as the minimal and the second, + if supplied as the maximal required plateau size. + + .. versionadded:: 1.2.0 + + Returns + ------- + peaks : ndarray + Indices of peaks in `x` that satisfy all given conditions. + properties : dict + A dictionary containing properties of the returned peaks which were + calculated as intermediate results during evaluation of the specified + conditions: + + * 'peak_heights' + If `height` is given, the height of each peak in `x`. + * 'left_thresholds', 'right_thresholds' + If `threshold` is given, these keys contain a peaks vertical + distance to its neighbouring samples. + * 'prominences', 'right_bases', 'left_bases' + If `prominence` is given, these keys are accessible. See + `peak_prominences` for a description of their content. + * 'widths', 'width_heights', 'left_ips', 'right_ips' + If `width` is given, these keys are accessible. See `peak_widths` + for a description of their content. + * 'plateau_sizes', left_edges', 'right_edges' + If `plateau_size` is given, these keys are accessible and contain + the indices of a peak's edges (edges are still part of the + plateau) and the calculated plateau sizes. + + .. versionadded:: 1.2.0 + + To calculate and return properties without excluding peaks, provide the + open interval ``(None, None)`` as a value to the appropriate argument + (excluding `distance`). + + Warns + ----- + PeakPropertyWarning + Raised if a peak's properties have unexpected values (see + `peak_prominences` and `peak_widths`). + + Warnings + -------- + This function may return unexpected results for data containing NaNs. To + avoid this, NaNs should either be removed or replaced. + + See Also + -------- + find_peaks_cwt + Find peaks using the wavelet transformation. + peak_prominences + Directly calculate the prominence of peaks. + peak_widths + Directly calculate the width of peaks. + + Notes + ----- + In the context of this function, a peak or local maximum is defined as any + sample whose two direct neighbours have a smaller amplitude. For flat peaks + (more than one sample of equal amplitude wide) the index of the middle + sample is returned (rounded down in case the number of samples is even). + For noisy signals the peak locations can be off because the noise might + change the position of local maxima. In those cases consider smoothing the + signal before searching for peaks or use other peak finding and fitting + methods (like `find_peaks_cwt`). + + Some additional comments on specifying conditions: + + * Almost all conditions (excluding `distance`) can be given as half-open or + closed intervals, e.g., ``1`` or ``(1, None)`` defines the half-open + interval :math:`[1, \\infty]` while ``(None, 1)`` defines the interval + :math:`[-\\infty, 1]`. The open interval ``(None, None)`` can be specified + as well, which returns the matching properties without exclusion of peaks. + * The border is always included in the interval used to select valid peaks. + * For several conditions the interval borders can be specified with + arrays matching `x` in shape which enables dynamic constrains based on + the sample position. + * The conditions are evaluated in the following order: `plateau_size`, + `height`, `threshold`, `distance`, `prominence`, `width`. In most cases + this order is the fastest one because faster operations are applied first + to reduce the number of peaks that need to be evaluated later. + * While indices in `peaks` are guaranteed to be at least `distance` samples + apart, edges of flat peaks may be closer than the allowed `distance`. + * Use `wlen` to reduce the time it takes to evaluate the conditions for + `prominence` or `width` if `x` is large or has many local maxima + (see `peak_prominences`). + + .. versionadded:: 1.1.0 + + Examples + -------- + To demonstrate this function's usage we use a signal `x` supplied with + SciPy (see `scipy.datasets.electrocardiogram`). Let's find all peaks (local + maxima) in `x` whose amplitude lies above 0. + + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from scipy.datasets import electrocardiogram + >>> from scipy.signal import find_peaks + >>> x = electrocardiogram()[2000:4000] + >>> peaks, _ = find_peaks(x, height=0) + >>> plt.plot(x) + >>> plt.plot(peaks, x[peaks], "x") + >>> plt.plot(np.zeros_like(x), "--", color="gray") + >>> plt.show() + + We can select peaks below 0 with ``height=(None, 0)`` or use arrays matching + `x` in size to reflect a changing condition for different parts of the + signal. + + >>> border = np.sin(np.linspace(0, 3 * np.pi, x.size)) + >>> peaks, _ = find_peaks(x, height=(-border, border)) + >>> plt.plot(x) + >>> plt.plot(-border, "--", color="gray") + >>> plt.plot(border, ":", color="gray") + >>> plt.plot(peaks, x[peaks], "x") + >>> plt.show() + + Another useful condition for periodic signals can be given with the + `distance` argument. In this case, we can easily select the positions of + QRS complexes within the electrocardiogram (ECG) by demanding a distance of + at least 150 samples. + + >>> peaks, _ = find_peaks(x, distance=150) + >>> np.diff(peaks) + array([186, 180, 177, 171, 177, 169, 167, 164, 158, 162, 172]) + >>> plt.plot(x) + >>> plt.plot(peaks, x[peaks], "x") + >>> plt.show() + + Especially for noisy signals peaks can be easily grouped by their + prominence (see `peak_prominences`). E.g., we can select all peaks except + for the mentioned QRS complexes by limiting the allowed prominence to 0.6. + + >>> peaks, properties = find_peaks(x, prominence=(None, 0.6)) + >>> properties["prominences"].max() + 0.5049999999999999 + >>> plt.plot(x) + >>> plt.plot(peaks, x[peaks], "x") + >>> plt.show() + + And, finally, let's examine a different section of the ECG which contains + beat forms of different shape. To select only the atypical heart beats, we + combine two conditions: a minimal prominence of 1 and width of at least 20 + samples. + + >>> x = electrocardiogram()[17000:18000] + >>> peaks, properties = find_peaks(x, prominence=1, width=20) + >>> properties["prominences"], properties["widths"] + (array([1.495, 2.3 ]), array([36.93773946, 39.32723577])) + >>> plt.plot(x) + >>> plt.plot(peaks, x[peaks], "x") + >>> plt.vlines(x=peaks, ymin=x[peaks] - properties["prominences"], + ... ymax = x[peaks], color = "C1") + >>> plt.hlines(y=properties["width_heights"], xmin=properties["left_ips"], + ... xmax=properties["right_ips"], color = "C1") + >>> plt.show() + """ + # _argmaxima1d expects array of dtype 'float64' + x = _arg_x_as_expected(x) + if distance is not None and distance < 1: + raise ValueError('`distance` must be greater or equal to 1') + + peaks, left_edges, right_edges = _local_maxima_1d(x) + properties = {} + + if plateau_size is not None: + # Evaluate plateau size + plateau_sizes = right_edges - left_edges + 1 + pmin, pmax = _unpack_condition_args(plateau_size, x, peaks) + keep = _select_by_property(plateau_sizes, pmin, pmax) + peaks = peaks[keep] + properties["plateau_sizes"] = plateau_sizes + properties["left_edges"] = left_edges + properties["right_edges"] = right_edges + properties = {key: array[keep] for key, array in properties.items()} + + if height is not None: + # Evaluate height condition + peak_heights = x[peaks] + hmin, hmax = _unpack_condition_args(height, x, peaks) + keep = _select_by_property(peak_heights, hmin, hmax) + peaks = peaks[keep] + properties["peak_heights"] = peak_heights + properties = {key: array[keep] for key, array in properties.items()} + + if threshold is not None: + # Evaluate threshold condition + tmin, tmax = _unpack_condition_args(threshold, x, peaks) + keep, left_thresholds, right_thresholds = _select_by_peak_threshold( + x, peaks, tmin, tmax) + peaks = peaks[keep] + properties["left_thresholds"] = left_thresholds + properties["right_thresholds"] = right_thresholds + properties = {key: array[keep] for key, array in properties.items()} + + if distance is not None: + # Evaluate distance condition + keep = _select_by_peak_distance(peaks, x[peaks], distance) + peaks = peaks[keep] + properties = {key: array[keep] for key, array in properties.items()} + + if prominence is not None or width is not None: + # Calculate prominence (required for both conditions) + wlen = _arg_wlen_as_expected(wlen) + properties.update(zip( + ['prominences', 'left_bases', 'right_bases'], + _peak_prominences(x, peaks, wlen=wlen) + )) + + if prominence is not None: + # Evaluate prominence condition + pmin, pmax = _unpack_condition_args(prominence, x, peaks) + keep = _select_by_property(properties['prominences'], pmin, pmax) + peaks = peaks[keep] + properties = {key: array[keep] for key, array in properties.items()} + + if width is not None: + # Calculate widths + properties.update(zip( + ['widths', 'width_heights', 'left_ips', 'right_ips'], + _peak_widths(x, peaks, rel_height, properties['prominences'], + properties['left_bases'], properties['right_bases']) + )) + # Evaluate width condition + wmin, wmax = _unpack_condition_args(width, x, peaks) + keep = _select_by_property(properties['widths'], wmin, wmax) + peaks = peaks[keep] + properties = {key: array[keep] for key, array in properties.items()} + + return peaks, properties + + +def _identify_ridge_lines(matr, max_distances, gap_thresh): + """ + Identify ridges in the 2-D matrix. + + Expect that the width of the wavelet feature increases with increasing row + number. + + Parameters + ---------- + matr : 2-D ndarray + Matrix in which to identify ridge lines. + max_distances : 1-D sequence + At each row, a ridge line is only connected + if the relative max at row[n] is within + `max_distances`[n] from the relative max at row[n+1]. + gap_thresh : int + If a relative maximum is not found within `max_distances`, + there will be a gap. A ridge line is discontinued if + there are more than `gap_thresh` points without connecting + a new relative maximum. + + Returns + ------- + ridge_lines : tuple + Tuple of 2 1-D sequences. `ridge_lines`[ii][0] are the rows of the + ii-th ridge-line, `ridge_lines`[ii][1] are the columns. Empty if none + found. Each ridge-line will be sorted by row (increasing), but the + order of the ridge lines is not specified. + + References + ---------- + .. [1] Bioinformatics (2006) 22 (17): 2059-2065. + :doi:`10.1093/bioinformatics/btl355` + + Examples + -------- + >>> import numpy as np + >>> from scipy.signal._peak_finding import _identify_ridge_lines + >>> rng = np.random.default_rng() + >>> data = rng.random((5,5)) + >>> max_dist = 3 + >>> max_distances = np.full(20, max_dist) + >>> ridge_lines = _identify_ridge_lines(data, max_distances, 1) + + Notes + ----- + This function is intended to be used in conjunction with `cwt` + as part of `find_peaks_cwt`. + + """ + if len(max_distances) < matr.shape[0]: + raise ValueError('Max_distances must have at least as many rows ' + 'as matr') + + all_max_cols = _boolrelextrema(matr, np.greater, axis=1, order=1) + # Highest row for which there are any relative maxima + has_relmax = np.nonzero(all_max_cols.any(axis=1))[0] + if len(has_relmax) == 0: + return [] + start_row = has_relmax[-1] + # Each ridge line is a 3-tuple: + # rows, cols,Gap number + ridge_lines = [[[start_row], + [col], + 0] for col in np.nonzero(all_max_cols[start_row])[0]] + final_lines = [] + rows = np.arange(start_row - 1, -1, -1) + cols = np.arange(0, matr.shape[1]) + for row in rows: + this_max_cols = cols[all_max_cols[row]] + + # Increment gap number of each line, + # set it to zero later if appropriate + for line in ridge_lines: + line[2] += 1 + + # XXX These should always be all_max_cols[row] + # But the order might be different. Might be an efficiency gain + # to make sure the order is the same and avoid this iteration + prev_ridge_cols = np.array([line[1][-1] for line in ridge_lines]) + # Look through every relative maximum found at current row + # Attempt to connect them with existing ridge lines. + for ind, col in enumerate(this_max_cols): + # If there is a previous ridge line within + # the max_distance to connect to, do so. + # Otherwise start a new one. + line = None + if len(prev_ridge_cols) > 0: + diffs = np.abs(col - prev_ridge_cols) + closest = np.argmin(diffs) + if diffs[closest] <= max_distances[row]: + line = ridge_lines[closest] + if line is not None: + # Found a point close enough, extend current ridge line + line[1].append(col) + line[0].append(row) + line[2] = 0 + else: + new_line = [[row], + [col], + 0] + ridge_lines.append(new_line) + + # Remove the ridge lines with gap_number too high + # XXX Modifying a list while iterating over it. + # Should be safe, since we iterate backwards, but + # still tacky. + for ind in range(len(ridge_lines) - 1, -1, -1): + line = ridge_lines[ind] + if line[2] > gap_thresh: + final_lines.append(line) + del ridge_lines[ind] + + out_lines = [] + for line in (final_lines + ridge_lines): + sortargs = np.argsort(line[0]) + rows = np.array(line[0])[sortargs] + cols = np.array(line[1])[sortargs] + out_lines.append([rows, cols]) + + return out_lines + + +def _filter_ridge_lines(cwt, ridge_lines, window_size=None, min_length=None, + min_snr=1, noise_perc=10): + """ + Filter ridge lines according to prescribed criteria. Intended + to be used for finding relative maxima. + + Parameters + ---------- + cwt : 2-D ndarray + Continuous wavelet transform from which the `ridge_lines` were defined. + ridge_lines : 1-D sequence + Each element should contain 2 sequences, the rows and columns + of the ridge line (respectively). + window_size : int, optional + Size of window to use to calculate noise floor. + Default is ``cwt.shape[1] / 20``. + min_length : int, optional + Minimum length a ridge line needs to be acceptable. + Default is ``cwt.shape[0] / 4``, ie 1/4-th the number of widths. + min_snr : float, optional + Minimum SNR ratio. Default 1. The signal is the value of + the cwt matrix at the shortest length scale (``cwt[0, loc]``), the + noise is the `noise_perc`\\ th percentile of datapoints contained within a + window of `window_size` around ``cwt[0, loc]``. + noise_perc : float, optional + When calculating the noise floor, percentile of data points + examined below which to consider noise. Calculated using + scipy.stats.scoreatpercentile. + + References + ---------- + .. [1] Bioinformatics (2006) 22 (17): 2059-2065. + :doi:`10.1093/bioinformatics/btl355` + + """ + num_points = cwt.shape[1] + if min_length is None: + min_length = np.ceil(cwt.shape[0] / 4) + if window_size is None: + window_size = np.ceil(num_points / 20) + + window_size = int(window_size) + hf_window, odd = divmod(window_size, 2) + + # Filter based on SNR + row_one = cwt[0, :] + noises = np.empty_like(row_one) + for ind, val in enumerate(row_one): + window_start = max(ind - hf_window, 0) + window_end = min(ind + hf_window + odd, num_points) + noises[ind] = scoreatpercentile(row_one[window_start:window_end], + per=noise_perc) + + def filt_func(line): + if len(line[0]) < min_length: + return False + snr = abs(cwt[line[0][0], line[1][0]] / noises[line[1][0]]) + if snr < min_snr: + return False + return True + + return list(filter(filt_func, ridge_lines)) + + +def find_peaks_cwt(vector, widths, wavelet=None, max_distances=None, + gap_thresh=None, min_length=None, + min_snr=1, noise_perc=10, window_size=None): + """ + Find peaks in a 1-D array with wavelet transformation. + + The general approach is to smooth `vector` by convolving it with + `wavelet(width)` for each width in `widths`. Relative maxima which + appear at enough length scales, and with sufficiently high SNR, are + accepted. + + Parameters + ---------- + vector : ndarray + 1-D array in which to find the peaks. + widths : float or sequence + Single width or 1-D array-like of widths to use for calculating + the CWT matrix. In general, + this range should cover the expected width of peaks of interest. + wavelet : callable, optional + Should take two parameters and return a 1-D array to convolve + with `vector`. The first parameter determines the number of points + of the returned wavelet array, the second parameter is the scale + (`width`) of the wavelet. Should be normalized and symmetric. + Default is the ricker wavelet. + max_distances : ndarray, optional + At each row, a ridge line is only connected if the relative max at + ``row[n]`` is within ``max_distances[n]`` from the relative max at + ``row[n+1]``. Default value is ``widths/4``. + gap_thresh : float, optional + If a relative maximum is not found within `max_distances`, + there will be a gap. A ridge line is discontinued if there are more + than `gap_thresh` points without connecting a new relative maximum. + Default is the first value of the widths array i.e. ``widths[0]``. + min_length : int, optional + Minimum length a ridge line needs to be acceptable. + Default is ``cwt.shape[0] / 4``, ie 1/4-th the number of widths. + min_snr : float, optional + Minimum SNR ratio. Default 1. The signal is the maximum CWT coefficient + on the largest ridge line. The noise is `noise_perc` th percentile of + datapoints contained within the same ridge line. + noise_perc : float, optional + When calculating the noise floor, percentile of data points + examined below which to consider noise. Calculated using + `stats.scoreatpercentile`. Default is 10. + window_size : int, optional + Size of window to use to calculate noise floor. + Default is ``cwt.shape[1] / 20``. + + Returns + ------- + peaks_indices : ndarray + Indices of the locations in the `vector` where peaks were found. + The list is sorted. + + See Also + -------- + find_peaks + Find peaks inside a signal based on peak properties. + + Notes + ----- + This approach was designed for finding sharp peaks among noisy data, + however with proper parameter selection it should function well for + different peak shapes. + + The algorithm is as follows: + 1. Perform a continuous wavelet transform on `vector`, for the supplied + `widths`. This is a convolution of `vector` with `wavelet(width)` for + each width in `widths`. + 2. Identify "ridge lines" in the cwt matrix. These are relative maxima + at each row, connected across adjacent rows. See identify_ridge_lines + 3. Filter the ridge_lines using filter_ridge_lines. + + References + ---------- + .. [1] Bioinformatics (2006) 22 (17): 2059-2065. + :doi:`10.1093/bioinformatics/btl355` + + Examples + -------- + >>> import numpy as np + >>> from scipy import signal + >>> xs = np.arange(0, np.pi, 0.05) + >>> data = np.sin(xs) + >>> peakind = signal.find_peaks_cwt(data, np.arange(1,10)) + >>> peakind, xs[peakind], data[peakind] + ([32], array([ 1.6]), array([ 0.9995736])) + + """ + widths = np.atleast_1d(np.asarray(widths)) + + if gap_thresh is None: + gap_thresh = np.ceil(widths[0]) + if max_distances is None: + max_distances = widths / 4.0 + if wavelet is None: + wavelet = _ricker + + cwt_dat = _cwt(vector, wavelet, widths) + ridge_lines = _identify_ridge_lines(cwt_dat, max_distances, gap_thresh) + filtered = _filter_ridge_lines(cwt_dat, ridge_lines, min_length=min_length, + window_size=window_size, min_snr=min_snr, + noise_perc=noise_perc) + max_locs = np.asarray([x[1][0] for x in filtered]) + max_locs.sort() + + return max_locs diff --git a/venv/lib/python3.12/site-packages/scipy/signal/_peak_finding_utils.cpython-312-x86_64-linux-gnu.so b/venv/lib/python3.12/site-packages/scipy/signal/_peak_finding_utils.cpython-312-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..fe27b99e2fa7b9db0f3e712abe4789c88a7e0461 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/_peak_finding_utils.cpython-312-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7f547534a9c3f5608284cf93e189e2f4036adcdf0e73989f2d004812e4d25fc3 +size 146296 diff --git a/venv/lib/python3.12/site-packages/scipy/signal/_polyutils.py b/venv/lib/python3.12/site-packages/scipy/signal/_polyutils.py new file mode 100644 index 0000000000000000000000000000000000000000..28bad89ab4e406d46d736a6f727528e40683fc30 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/_polyutils.py @@ -0,0 +1,257 @@ +"""Partial replacements for numpy polynomial routines, with Array API compatibility. + +This module contains both "old-style", np.poly1d, routines from the main numpy +namespace, and "new-style", np.polynomial.polynomial, routines. + +To distinguish the two sets, the "new-style" routine names start with `npp_` +""" +import warnings +import scipy._external.array_api_extra as xpx +from scipy._lib._array_api import ( + xp_promote, xp_default_dtype, xp_size, xp_device, is_numpy +) + +from numpy.exceptions import RankWarning + + +def _sort_cmplx(arr, xp): + # xp.sort is undefined for complex dtypes. Here we only need some + # consistent way to sort a complex array, including equal magnitude elements. + arr = xp.asarray(arr) + if xp.isdtype(arr.dtype, 'complex floating'): + sorter = abs(arr) + xp.real(arr) + xp.imag(arr)**3 + else: + sorter = arr + + idxs = xp.argsort(sorter) + return arr[idxs] + + +def polyroots(coef, *, xp): + """numpy.roots, best-effor replacement + """ + if coef.shape[0] < 2: + return xp.asarray([], dtype=coef.dtype) + + root_func = getattr(xp, 'roots', None) + if root_func: + # NB: cupy.roots is broken in CuPy 13.x, but CuPy is handled via delegation + # so we never hit this code path with xp being cupy + return root_func(coef) + + # companion matrix + n = coef.shape[0] + a = xp.eye(n - 1, n - 1, k=-1, dtype=coef.dtype) + a[:, -1] = -xp.flip(coef[1:]) / coef[0] + + # non-symmetric eigenvalue problem is not in the spec but is available on e.g. torch + if hasattr(xp.linalg, 'eigvals'): + return xp.linalg.eigvals(a) + else: + import numpy as np + return xp.asarray(np.linalg.eigvals(np.asarray(a))) + + +# https://github.com/numpy/numpy/blob/v2.1.0/numpy/lib/_function_base_impl.py#L1874-L1925 +def _trim_zeros(filt, trim='fb'): + first = 0 + trim = trim.upper() + if 'F' in trim: + for i in filt: + if i != 0.: + break + else: + first = first + 1 + last = filt.shape[0] + if 'B' in trim: + for i in filt[::-1]: + if i != 0.: + break + else: + last = last - 1 + return filt[first:last] + + +# For numpy arrays, use scipy.linalg.lstsq; +# For other backends, +# - use xp.linalg.lstsq, if available (cupy, torch, jax.numpy); +# - otherwise manually compute pseudoinverse via SVD factorization +def _lstsq(a, b, xp=None, rcond=None): + a, b = xp_promote(a, b, force_floating=True, xp=xp) + + if rcond is None: + rcond = xp.finfo(a.dtype).eps + + if is_numpy(xp): + from scipy.linalg import lstsq as s_lstsq + return s_lstsq(a, b, cond=rcond) + elif lstsq_func := getattr(xp.linalg, "lstsq", None): + # cupy, torch, jax.numpy all have xp.linalg.lstsq + return lstsq_func(a, b, rcond=rcond) + else: + # unknown array library: LSQ solve via pseudoinverse + u, s, vt = xp.linalg.svd(a, full_matrices=False) + + sing_val_mask = s > rcond + s = xpx.apply_where(sing_val_mask, (s,), lambda x: 1. / x, fill_value=0.) + + sigma = xp.eye(s.shape[0]) * s # == np.diag(s) + x = vt.T @ sigma @ u.T @ b + + rank = xp.count_nonzero(sing_val_mask) + + # XXX actually compute residuals, when there's a use case + residuals = xp.asarray([]) + return x, residuals, rank, s + + +# ### Old-style routines ### + + +# https://github.com/numpy/numpy/blob/v2.2.0/numpy/lib/_polynomial_impl.py#L1232 +def _poly1d(c_or_r, *, xp): + """ Constructor of np.poly1d object from an array of coefficients (r=False) + """ + c_or_r = xpx.atleast_nd(c_or_r, ndim=1, xp=xp) + if c_or_r.ndim > 1: + raise ValueError("Polynomial must be 1d only.") + c_or_r = _trim_zeros(c_or_r, trim='f') + if c_or_r.shape[0] == 0: + c_or_r = xp.asarray([0], dtype=c_or_r.dtype) + return c_or_r + + +# https://github.com/numpy/numpy/blob/v2.2.0/numpy/lib/_polynomial_impl.py#L702-L779 +def polyval(p, x, *, xp): + """ Old-style polynomial, `np.polyval` + """ + p = xp.asarray(p) + x = xp.asarray(x) + y = xp.zeros_like(x) + + # NB: cannot do `for pv in p` since array API iteration + # is only defined for 1D arrays. + for j in range(p.shape[0]): + y = y * x + p[j, ...] + return y + + +# https://github.com/numpy/numpy/blob/v2.2.0/numpy/lib/_polynomial_impl.py#L34-L157 +def poly(seq_of_zeros, *, xp): + # Only reproduce the 1D variant of np.poly + seq_of_zeros = xp.asarray(seq_of_zeros) + seq_of_zeros = xpx.atleast_nd(seq_of_zeros, ndim=1, xp=xp) + + if seq_of_zeros.shape[0] == 0: + return xp.asarray(1.0, dtype=xp.real(seq_of_zeros).dtype) + + # prefer np.convolve etc, if available + convolve_func = getattr(xp, 'convolve', None) + if convolve_func is None: + from scipy.signal import convolve as convolve_func + + dt = seq_of_zeros.dtype + a = xp.ones((1,), dtype=dt) + one = xp.ones_like(seq_of_zeros[0]) + for zero in seq_of_zeros: + a = convolve_func(a, xp.stack((one, -zero)), mode='full') + + if xp.isdtype(a.dtype, 'complex floating'): + # if complex roots are all complex conjugates, the roots are real. + roots = xp.asarray(seq_of_zeros, dtype=xp.complex128) + if xp.all(xp.sort(xp.imag(roots)) == xp.sort(xp.imag(xp.conj(roots)))): + a = xp.asarray(xp.real(a), copy=True) + + return a + + +# https://github.com/numpy/numpy/blob/v2.2.0/numpy/lib/_polynomial_impl.py#L912 +def polymul(a1, a2, *, xp): + a1, a2 = _poly1d(a1, xp=xp), _poly1d(a2, xp=xp) + + # prefer np.convolve etc, if available + convolve_func = getattr(xp, 'convolve', None) + if convolve_func is None: + from scipy.signal import convolve as convolve_func + + val = convolve_func(a1, a2) + return val + + +# https://github.com/numpy/numpy/blob/v2.3.3/numpy/lib/_polynomial_impl.py#L459 +def polyfit(x, y, deg, *, xp, rcond=None): + # only reproduce the variant with full=False, w=None, cov=False + order = int(deg) + 1 + x = xp.asarray(x) + y = xp.asarray(y) + x, y = xp_promote(x, y, force_floating=True, xp=xp) + + # check arguments. + if deg < 0: + raise ValueError("expected deg >= 0") + if x.ndim != 1: + raise TypeError("expected 1D vector for x") + if xp_size(x) == 0: + raise TypeError("expected non-empty vector for x") + if y.ndim < 1 or y.ndim > 2: + raise TypeError("expected 1D or 2D array for y") + if x.shape[0] != y.shape[0]: + raise TypeError("expected x and y to have same length") + + # set rcond + if rcond is None: + rcond = x.shape[0] * xp.finfo(x.dtype).eps + + # set up least squares equation for powers of x: lhs = vander(x, order) + powers = xp.flip(xp.arange(order, dtype=x.dtype, device=xp_device(x))) + lhs = x[:, None] ** powers[None, :] + + # scale lhs to improve condition number and solve + scale = xp.sqrt(xp.sum(lhs * lhs, axis=0)) + lhs /= scale + + c, _, rank, _ = _lstsq(lhs, y, rcond=rcond, xp=xp) + c = (c.T / scale).T # broadcast scale coefficients + + # warn on rank reduction, which indicates an ill conditioned matrix + if rank != order: + msg = "Polyfit may be poorly conditioned" + warnings.warn(msg, RankWarning, stacklevel=2) + + return c + + +# ### New-style routines ### + + +# https://github.com/numpy/numpy/blob/v2.2.0/numpy/polynomial/polynomial.py#L663 +def npp_polyval(x, c, *, xp, tensor=True): + if xp.isdtype(c.dtype, 'integral'): + c = xp.astype(c, xp_default_dtype(xp)) + + c = xpx.atleast_nd(c, ndim=1, xp=xp) + if isinstance(x, tuple | list): + x = xp.asarray(x) + if tensor: + c = xp.reshape(c, (c.shape + (1,)*x.ndim)) + + c0, _ = xp_promote(c[-1, ...], x, broadcast=True, xp=xp) + for i in range(2, c.shape[0] + 1): + c0 = c[-i, ...] + c0*x + return c0 + + +# https://github.com/numpy/numpy/blob/v2.2.0/numpy/polynomial/polynomial.py#L758-L842 +def npp_polyvalfromroots(x, r, *, xp, tensor=True): + r = xpx.atleast_nd(r, ndim=1, xp=xp) + # if r.dtype.char in '?bBhHiIlLqQpP': + # r = r.astype(np.double) + + if isinstance(x, tuple | list): + x = xp.asarray(x) + + if tensor: + r = xp.reshape(r, r.shape + (1,) * x.ndim) + elif x.ndim >= r.ndim: + raise ValueError("x.ndim must be < r.ndim when tensor == False") + return xp.prod(x - r, axis=0) diff --git a/venv/lib/python3.12/site-packages/scipy/signal/_savitzky_golay.py b/venv/lib/python3.12/site-packages/scipy/signal/_savitzky_golay.py new file mode 100644 index 0000000000000000000000000000000000000000..6c70d9794bbf936526438cd24cabe1d3c7ec5343 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/_savitzky_golay.py @@ -0,0 +1,391 @@ +from scipy._lib._util import float_factorial +from scipy._external.array_api_compat import numpy as np_compat +from scipy._lib._array_api import array_namespace, xp_swapaxes, xp_device +import scipy._external.array_api_extra as xpx + +from scipy.ndimage import convolve1d # type: ignore[attr-defined] +from scipy.signal import _polyutils as _pu +from ._arraytools import axis_slice + + +def savgol_coeffs(window_length, polyorder, deriv=0, delta=1.0, pos=None, + use="conv", *, xp=None, device=None): + """Compute the coefficients for a 1-D Savitzky-Golay FIR filter. + + Parameters + ---------- + window_length : int + The length of the filter window (i.e., the number of coefficients). + polyorder : int + The order of the polynomial used to fit the samples. + `polyorder` must be less than `window_length`. + deriv : int, optional + The order of the derivative to compute. This must be a + nonnegative integer. The default is 0, which means to filter + the data without differentiating. + delta : float, optional + The spacing of the samples to which the filter will be applied. + This is only used if deriv > 0. + pos : int or None, optional + If pos is not None, it specifies evaluation position within the + window. The default is the middle of the window. + use : str, optional + Either 'conv' or 'dot'. This argument chooses the order of the + coefficients. The default is 'conv', which means that the + coefficients are ordered to be used in a convolution. With + use='dot', the order is reversed, so the filter is applied by + dotting the coefficients with the data set. + xp : array_namespace, optional + Optional array namespace. + Should be compatible with the array API standard, or supported by + array-api-compat. + Default: ``numpy`` + device : any + optional device specification for output. Should match one of the + supported device specifications in ``xp``. + + Returns + ------- + coeffs : 1-D ndarray + The filter coefficients. + + See Also + -------- + savgol_filter + + Notes + ----- + .. versionadded:: 0.14.0 + + References + ---------- + A. Savitzky, M. J. E. Golay, Smoothing and Differentiation of Data by + Simplified Least Squares Procedures. Analytical Chemistry, 1964, 36 (8), + pp 1627-1639. + Jianwen Luo, Kui Ying, and Jing Bai. 2005. Savitzky-Golay smoothing and + differentiation filter for even number data. Signal Process. + 85, 7 (July 2005), 1429-1434. + + Examples + -------- + >>> import numpy as np + >>> from scipy.signal import savgol_coeffs + >>> savgol_coeffs(5, 2) + array([-0.08571429, 0.34285714, 0.48571429, 0.34285714, -0.08571429]) + >>> savgol_coeffs(5, 2, deriv=1) + array([ 2.00000000e-01, 1.00000000e-01, 2.07548111e-16, -1.00000000e-01, + -2.00000000e-01]) + + Note that use='dot' simply reverses the coefficients. + + >>> savgol_coeffs(5, 2, pos=3) + array([ 0.25714286, 0.37142857, 0.34285714, 0.17142857, -0.14285714]) + >>> savgol_coeffs(5, 2, pos=3, use='dot') + array([-0.14285714, 0.17142857, 0.34285714, 0.37142857, 0.25714286]) + >>> savgol_coeffs(4, 2, pos=3, deriv=1, use='dot') + array([0.45, -0.85, -0.65, 1.05]) + + `x` contains data from the parabola x = t**2, sampled at + t = -1, 0, 1, 2, 3. `c` holds the coefficients that will compute the + derivative at the last position. When dotted with `x` the result should + be 6. + + >>> x = np.array([1, 0, 1, 4, 9]) + >>> c = savgol_coeffs(5, 2, pos=4, deriv=1, use='dot') + >>> c.dot(x) + 6.0 + """ + + # An alternative method for finding the coefficients when deriv=0 is + # t = np.arange(window_length) + # unit = (t == pos).astype(int) + # coeffs = np.polyval(np.polyfit(t, unit, polyorder), t) + # The method implemented here is faster. + + # To recreate the table of sample coefficients shown in the chapter on + # the Savitzy-Golay filter in the Numerical Recipes book, use + # window_length = nL + nR + 1 + # pos = nL + 1 + # c = savgol_coeffs(window_length, M, pos=pos, use='dot') + + if polyorder >= window_length: + raise ValueError("polyorder must be less than window_length.") + + halflen, rem = divmod(window_length, 2) + + if pos is None: + if rem == 0: + pos = halflen - 0.5 + else: + pos = halflen + + if not (0 <= pos < window_length): + raise ValueError("pos must be nonnegative and less than " + "window_length.") + + if use not in ['conv', 'dot']: + raise ValueError("`use` must be 'conv' or 'dot'") + + # cf windows/_windows.py + xp = np_compat if xp is None else array_namespace(xp.empty(0)) + + if deriv > polyorder: + coeffs = xp.zeros(window_length, dtype=xp.float64, device=device) + return coeffs + + # Form the design matrix A. The columns of A are powers of the integers + # from -pos to window_length - pos - 1. The powers (i.e., rows) range + # from 0 to polyorder. (That is, A is a vandermonde matrix, but not + # necessarily square.) + x = xp.arange(-pos, window_length - pos, dtype=xp.float64, device=device) + + if use == "conv": + # Reverse so that result can be used in a convolution. + x = xp.flip(x) + + order = xp.reshape( + xp.arange(polyorder + 1, dtype=xp.float64, device=device), (-1, 1) + ) + A = x ** order + + # y determines which order derivative is returned. + y = xp.zeros(polyorder + 1, dtype=xp.float64, device=device) + # The coefficient assigned to y[deriv] scales the result to take into + # account the order of the derivative and the sample spacing. + y = xpx.at(y, deriv).set(float_factorial(deriv) / (delta ** deriv)) + + # Find the least-squares solution of A*c = y + coeffs, _, _, _ = _pu._lstsq(A, y, xp=xp) + + return coeffs + + +def _polyder(p, m, *, xp): + """Differentiate polynomials represented with coefficients. + + p must be a 1-D or 2-D array. In the 2-D case, each column gives + the coefficients of a polynomial; the first row holds the coefficients + associated with the highest power. m must be a nonnegative integer. + (numpy.polyder doesn't handle the 2-D case.) + """ + + if m == 0: + result = p + else: + n = p.shape[0] + if n <= m: + result = xp.zeros_like(p[:1, ...]) + else: + dp = xp.asarray(p[:-m, ...], copy=True) + for k in range(m): + rng = xp.arange( + n - k - 1, m - k - 1, -1, dtype=p.dtype, device=xp_device(p) + ) + dp *= xp.reshape(rng, (n - m,) + (1,) * (p.ndim - 1)) + result = dp + return result + + +def _fit_edge(x, window_start, window_stop, interp_start, interp_stop, + axis, polyorder, deriv, delta, y): + """ + Given an N-d array `x` and the specification of a slice of `x` from + `window_start` to `window_stop` along `axis`, create an interpolating + polynomial of each 1-D slice, and evaluate that polynomial in the slice + from `interp_start` to `interp_stop`. Put the result into the + corresponding slice of `y`. + """ + xp = array_namespace(x) + + # Get the edge into a (window_length, -1) array. + x_edge = axis_slice(x, start=window_start, stop=window_stop, axis=axis) + if axis == 0 or axis == -x.ndim: + xx_edge = x_edge + swapped = False + else: + xx_edge = xp_swapaxes(x_edge, axis, 0, xp) + swapped = True + xx_edge = xp.reshape(xx_edge, (xx_edge.shape[0], -1)) + + # Fit the edges. poly_coeffs has shape (polyorder + 1, -1), + # where '-1' is the same as in xx_edge. + poly_coeffs = _pu.polyfit( + xp.arange( + 0, window_stop - window_start, dtype=x.dtype, device=xp_device(x) + ), xx_edge, polyorder, xp=xp + ) + + if deriv > 0: + poly_coeffs = _polyder(poly_coeffs, deriv, xp=xp) + + # Compute the interpolated values for the edge. + i = xp.arange( + interp_start - window_start, interp_stop - window_start, + dtype=poly_coeffs.dtype, device=xp_device(poly_coeffs) + ) + values = _pu.polyval(poly_coeffs, xp.reshape(i, (-1, 1)), xp=xp) / (delta ** deriv) + + # Now put the values into the appropriate slice of y. + # First reshape values to match y. + shp = list(y.shape) + shp[0], shp[axis] = shp[axis], shp[0] + values = xp.reshape(values, (interp_stop - interp_start, *shp[1:])) + if swapped: + values = xp_swapaxes(values, 0, axis, xp) + # Get a view of the data to be replaced by values. + y_slice = [slice(None)] * y.ndim + y_slice[axis] = slice(interp_start, interp_stop) + y = xpx.at(y, tuple(y_slice)).set(values) + + return y + + + +def _fit_edges_polyfit(x, window_length, polyorder, deriv, delta, axis, y): + """ + Use polynomial interpolation of x at the low and high ends of the axis + to fill in the halflen values in y. + + This function just calls _fit_edge twice, once for each end of the axis. + """ + halflen = window_length // 2 + y = _fit_edge(x, 0, window_length, 0, halflen, axis, + polyorder, deriv, delta, y) + n = x.shape[axis] + y = _fit_edge(x, n - window_length, n, n - halflen, n, axis, + polyorder, deriv, delta, y) + + return y + + +def savgol_filter(x, window_length, polyorder, deriv=0, delta=1.0, + axis=-1, mode='interp', cval=0.0): + """ Apply a Savitzky-Golay filter to an array. + + This is a 1-D filter. If `x` has dimension greater than 1, `axis` + determines the axis along which the filter is applied. + + Parameters + ---------- + x : array_like + The data to be filtered. If `x` is not a single or double precision + floating point array, it will be converted to type ``numpy.float64`` + before filtering. + window_length : int + The length of the filter window (i.e., the number of coefficients). + If `mode` is 'interp', `window_length` must be less than or equal + to the size of `x`. + polyorder : int + The order of the polynomial used to fit the samples. + `polyorder` must be less than `window_length`. + deriv : int, optional + The order of the derivative to compute. This must be a + nonnegative integer. The default is 0, which means to filter + the data without differentiating. + delta : float, optional + The spacing of the samples to which the filter will be applied. + This is only used if deriv > 0. Default is 1.0. + axis : int, optional + The axis of the array `x` along which the filter is to be applied. + Default is -1. + mode : str, optional + Must be 'mirror', 'constant', 'nearest', 'wrap' or 'interp'. This + determines the type of extension to use for the padded signal to + which the filter is applied. When `mode` is 'constant', the padding + value is given by `cval`. See the Notes for more details on 'mirror', + 'constant', 'wrap', and 'nearest'. + When the 'interp' mode is selected (the default), no extension + is used. Instead, a degree `polyorder` polynomial is fit to the + last `window_length` values of the edges, and this polynomial is + used to evaluate the last `window_length // 2` output values. + cval : scalar, optional + Value to fill past the edges of the input if `mode` is 'constant'. + Default is 0.0. + + Returns + ------- + y : ndarray, same shape as `x` + The filtered data. + + See Also + -------- + savgol_coeffs + + Notes + ----- + Details on the `mode` options: + + - 'mirror': + Repeats the values at the edges in reverse order. The value + closest to the edge is not included. + - 'nearest': + The extension contains the nearest input value. + - 'constant': + The extension contains the value given by the `cval` argument. + - 'wrap': + The extension contains the values from the other end of the array. + + For example, if the input is [1, 2, 3, 4, 5, 6, 7, 8], and + `window_length` is 7, the following shows the extended data for + the various `mode` options (assuming `cval` is 0):: + + mode | Ext | Input | Ext + -----------+---------+------------------------+--------- + 'mirror' | 4 3 2 | 1 2 3 4 5 6 7 8 | 7 6 5 + 'nearest' | 1 1 1 | 1 2 3 4 5 6 7 8 | 8 8 8 + 'constant' | 0 0 0 | 1 2 3 4 5 6 7 8 | 0 0 0 + 'wrap' | 6 7 8 | 1 2 3 4 5 6 7 8 | 1 2 3 + + .. versionadded:: 0.14.0 + + Examples + -------- + >>> import numpy as np + >>> from scipy.signal import savgol_filter + >>> np.set_printoptions(precision=2) # For compact display. + >>> x = np.array([2, 2, 5, 2, 1, 0, 1, 4, 9]) + + Filter with a window length of 5 and a degree 2 polynomial. Use + the defaults for all other parameters. + + >>> savgol_filter(x, 5, 2) + array([1.66, 3.17, 3.54, 2.86, 0.66, 0.17, 1. , 4. , 9. ]) + + Note that the last five values in x are samples of a parabola, so + when mode='interp' (the default) is used with polyorder=2, the last + three values are unchanged. Compare that to, for example, + `mode='nearest'`: + + >>> savgol_filter(x, 5, 2, mode='nearest') + array([1.74, 3.03, 3.54, 2.86, 0.66, 0.17, 1. , 4.6 , 7.97]) + + """ + if mode not in ["mirror", "constant", "nearest", "interp", "wrap"]: + raise ValueError("mode must be 'mirror', 'constant', 'nearest' " + "'wrap' or 'interp'.") + + xp = array_namespace(x) + x = xp.asarray(x) + # Ensure that x is either single or double precision floating point. + if x.dtype != xp.float64 and x.dtype != xp.float32: + x = xp.astype(x, xp.float64) + + coeffs = savgol_coeffs( + window_length, polyorder, deriv=deriv, delta=delta, xp=xp, device=xp_device(x) + ) + + if mode == "interp": + if window_length > x.shape[axis]: + raise ValueError("If mode is 'interp', window_length must be less " + "than or equal to the size of x.") + + # Do not pad. Instead, for the elements within `window_length // 2` + # of the ends of the sequence, use the polynomial that is fitted to + # the last `window_length` elements. + y = convolve1d(x, coeffs, axis=axis, mode="constant") + y = _fit_edges_polyfit(x, window_length, polyorder, deriv, delta, axis, y) + else: + # Any mode other than 'interp' is passed on to ndimage.convolve1d. + y = convolve1d(x, coeffs, axis=axis, mode=mode, cval=cval) + + return y diff --git a/venv/lib/python3.12/site-packages/scipy/signal/_short_time_fft.py b/venv/lib/python3.12/site-packages/scipy/signal/_short_time_fft.py new file mode 100644 index 0000000000000000000000000000000000000000..533b25edd1e1c0985ccd0fd4b7cb7e6c657f5a2b --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/_short_time_fft.py @@ -0,0 +1,2291 @@ +"""Implementation of an FFT-based Short-time Fourier Transform. """ + +# Implementation Notes for this file (as of 2025-08) +# -------------------------------------------------- +# * Since the method `stft` and `istft` have identical names as the legacy +# functions in the signal module, referencing them as HTML link in the +# docstrings has to be done by an explicit `~ShortTimeFFT.stft` instead of an +# ambiguous `stft` (The ``~`` hides the class / module name). +# * The HTML documentation currently renders each method/property on a separate +# page without reference to the parent class. Thus, a link to `ShortTimeFFT` +# was added to the "See Also" section of each method/property. These links +# can be removed, when SciPy updates ``pydata-sphinx-theme`` to >= 0.13.3 +# (currently 0.9). Consult Issue 18512 and PR 16660 for further details. + + +# Linter does not allow to import ``Generator`` from ``typing`` module: +from collections.abc import Generator, Callable +from functools import partial, cached_property +from types import GenericAlias +from typing import get_args, Literal + +import numpy as np + +import scipy.fft as fft_lib +from scipy.signal._signaltools import detrend +from scipy.signal.windows import get_window + +__all__ = ['closest_STFT_dual_window', 'ShortTimeFFT'] + + +#: Allowed values for parameter `padding` of method `ShortTimeFFT.stft()`: +PAD_TYPE = Literal['zeros', 'edge', 'even', 'odd'] + +#: Allowed values for property `ShortTimeFFT.fft_mode`: +FFT_MODE_TYPE = Literal['twosided', 'centered', 'onesided', 'onesided2X'] + + +def _calc_dual_canonical_window(win: np.ndarray, hop: int) -> np.ndarray: + """Calculate canonical dual window for 1d window `win` and a time step + of `hop` samples. + + A ``ValueError`` is raised if the inversion fails. + + This is a separate function not a method, since it is also used in the + class method ``ShortTimeFFT.from_dual()``. + """ + if hop > len(win): + raise ValueError(f"{hop=} is larger than window length of {len(win)}" + + " => STFT not invertible!") + if issubclass(win.dtype.type, np.integer): + raise ValueError("Parameter 'win' cannot be of integer type, but " + + f"{win.dtype=} => STFT not invertible!") + # The calculation of `relative_resolution` does not work for ints. + # Furthermore, `win / DD` casts the integers away, thus an implicit + # cast is avoided, which can always cause confusion when using 32-Bit + # floats. + + w2 = win.real**2 + win.imag**2 # win*win.conj() does not ensure w2 is real + DD = w2.copy() + for k_ in range(hop, len(win), hop): + DD[k_:] += w2[:-k_] + DD[:-k_] += w2[k_:] + + # check DD > 0: + relative_resolution = np.finfo(win.dtype).resolution * max(DD) + if not np.all(DD >= relative_resolution): + raise ValueError("Short-time Fourier Transform not invertible!") + + return win / DD + + +def closest_STFT_dual_window(win: np.ndarray, hop: int, + desired_dual: np.ndarray | None = None, *, + scaled: bool = True) \ + -> tuple[np.ndarray, float | complex]: + r"""Calculate the STFT dual window of a given window closest to a desired dual + window. + + For a given short-time Fourier transform window `win` incremented by `hop` + samples, the dual window is calculated, which minimizes + ``abs(dual_win - desired_dual)**2`` when `scaled` is ``False``. For `scaled` + set to ``True``, ``abs(alpha*dual_win - desired_dual)**2`` is minimized with + `alpha` being the optimal scaling factor. + A ``ValueError`` is raised if no valid dual window can be determined. + + + Parameters + ---------- + win : np.ndarray + The window must be a real- or complex-valued 1d array. + hop : int + The increment in samples by which the window is shifted in each step. + desired_dual : np.ndarray | None + The desired dual window must be a 1d array of the same length as `win`. + If set to ``None`` (default), then `desired_dual` is assumed to be the + rectangular window, i.e., ``np.ones_like(win)``. + scaled : bool + If set (default), the closest scaled version instead of closest dual window + is calculated. + + Returns + ------- + dual_win : np.ndarray + A dual window of ``alpha*win`` (with hop interval `hop`), which is closest + to `desired_dual`. Note that the dual window of `win` is `dual_win/alpha` + and that the dual window of `dual_win` is `alpha*win`. + `dual_win` has the same shape as `win` and `desired_win`. + alpha : float | complex + Scale factor for `win`. It is always one if `scaled` is set to ``False``. + + Notes + ----- + For a given window and `hop` interval, all possible dual windows are expressed + by the `hop` linear conditions of Eq. :math:numref:`eq_STFT_AllDualWinsCond` in + the :ref:`tutorial_stft` section of the :ref:`user_guide`. Hence, decreasing + `hop`, increases the number of degrees of freedom of the set of all possible + dual windows, improving the ability to better approximate a `desired_dual`. + + This function can also be used to determine windows which fulfill the + so-called "Constant OverLap Add" (COLA) condition [1]_. It states that summing + all touching window values at any given sample position results in the same + constant :math:`\alpha`. Eq. :math:numref:`eq_STFT_AllDualWinsCond` shows that + this is equal to having a rectangular dual window, i.e., the dual being + ``alpha*np.ones(m)``. + + Some examples of windows that satisfy COLA (taken from [2]_): + + - Rectangular window at overlap of 0, 1/2, 2/3, 3/4, ... + - Bartlett window at overlap of 1/2, 3/4, 5/6, ... + - Hann window at 1/2, 2/3, 3/4, ... + - Any Blackman family window at 2/3 overlap + - Any window with ``hop=1`` + + References + ---------- + .. [1] Julius O. Smith III, "Spectral Audio Signal Processing", + online book, 2011, https://www.dsprelated.com/freebooks/sasp/ + .. [2] G. Heinzel, A. Ruediger and R. Schilling, "Spectrum and spectral density + estimation by the Discrete Fourier transform (DFT), including a + comprehensive list of window functions and some new at-top windows", + 2002, http://hdl.handle.net/11858/00-001M-0000-0013-557A-5 + + Examples + -------- + Let's show that a Bartlett window with 75% overlap fulfills the COLA condition: + + >>> import matplotlib.pyplot as plt + >>> import numpy as np + >>> from scipy.signal import closest_STFT_dual_window, windows + ... + >>> m = 24 + >>> win, w_rect = windows.bartlett(m, sym=False), np.ones(m) + >>> d_win, alpha = closest_STFT_dual_window(win, m//4, w_rect, scaled=True) + >>> print(f"COLA: {np.allclose(d_win, w_rect*alpha)}, {alpha = :g}") + COLA: True, alpha = 0.5 + + We can also determine for which hop intervals the COLA condition is fulfilled: + + >>> hops, deviations, alphas = np.arange(1, 16, dtype=int), [], [] + >>> for h_ in hops: + ... w_cola, alpha = closest_STFT_dual_window(w_rect, h_, win, scaled=True) + ... deviations.append(np.linalg.norm(w_cola - win*alpha)) + ... alphas.append(alpha) + ... + >>> fg0, (ax0, ax1) = plt.subplots(2, 1, sharex='all', tight_layout=True) + >>> ax0.set_title(f"COLA Window closest to a {m}-Sample Bartlett Window") + >>> ax0.set(ylabel=r"$||w_\text{cola}-\alpha w||$", xlim=(0, hops[-1]-.5)) + >>> ax1.set(xlabel="Hop Interval", ylabel=r"Scaling factor $\alpha$", + ... ylim=(0, 1.25)) + >>> ax0.plot(hops, deviations, 'C0.-') + >>> ax1.plot(hops, alphas, 'C1.-') + >>> for ax_ in (ax0, ax1): + ... ax_.grid() + >>> plt.show() + + The lower plot shows the calculated scaling factor :math:`\alpha` for different + `hops` whereas the upper displays the :math:`L^2`-norm of the difference + between the scaled Bartlett window and the calculated window. Since for `hops` + 1 to 4 as well as for 6 and 12 the :math:`L^2`-norm of the difference is + practically zero, the COLA condition is fulfilled for those. + + See Also + -------- + ShortTimeFFT: Short-time Fourier transform which is able to utilize a dual + window for calculating the inverse. + ShortTimeFFT.from_win_equals_dual: Create instance where the window and its + dual are equal. + + """ + if desired_dual is None: # default is rectangular window + desired_dual = np.ones_like(win) + if not (win.ndim == 1 and win.shape == desired_dual.shape): + raise ValueError("Parameters `win` and `desired_dual` are not 1d arrays of " + + f"equal length ({win.shape=}, {desired_dual.shape=})!") + if not all(np.isfinite(win)): + raise ValueError("Parameter win must have finite entries!") + if not all(np.isfinite(desired_dual)): + raise ValueError("Parameter desired_dual must have finite entries!") + if not (1 <= hop <= len(win) and isinstance(hop, int | np.integer)): + raise ValueError(f"Parameter {hop=} is not an integer between 1 and " + + f"{len(win)=}!") + + w_d = _calc_dual_canonical_window(win, hop) + wdd = win.conjugate() * desired_dual + q_d = wdd.copy() + for k_ in range(hop, len(win), hop): + q_d[k_:] += wdd[:-k_] + q_d[:-k_] += wdd[k_:] + q_d = w_d * q_d + + if not scaled: + return w_d + desired_dual - q_d, 1. + + numerator = q_d.conjugate().T @ w_d + denominator = q_d.T.real @ q_d.real + q_d.T.imag @ q_d.imag # always >= 0 + if not (abs(numerator) > 0 and denominator > np.finfo(w_d.dtype).resolution): + raise ValueError( + "Unable to calculate scaled closest dual window due to numerically " + + "unstable scaling factor! Try setting parameter `scaled` to False.") + alpha = numerator / denominator + return w_d + alpha * (desired_dual - q_d), alpha + + +# noinspection PyShadowingNames +class ShortTimeFFT: + r"""Provide a parametrized discrete Short-time Fourier transform (stft) + and its inverse (istft). + + .. currentmodule:: scipy.signal.ShortTimeFFT + + The `~ShortTimeFFT.stft` calculates sequential FFTs by sliding a + window (`win`) over an input signal by `hop` increments. It can be used to + quantify the change of the spectrum over time. + + The `~ShortTimeFFT.stft` is represented by a complex-valued matrix S[q,p] + where the p-th column represents an FFT with the window centered at the + time t[p] = p * `delta_t` = p * `hop` * `T` where `T` is the sampling + interval of the input signal. The q-th row represents the values at the + frequency f[q] = q * `delta_f` with `delta_f` = 1 / (`mfft` * `T`) being + the bin width of the FFT. + + The inverse STFT `~ShortTimeFFT.istft` is calculated by reversing the steps + of the STFT: Take the IFFT of the p-th slice of S[q,p] and multiply the + result with the so-called dual window (see `dual_win`). Shift the result by + p * `delta_t` and add the result to previous shifted results to reconstruct + the signal. If only the dual window is known and the STFT is invertible, + `from_dual` can be used to instantiate this class. + + By default, the so-called canonical dual window is used. It is the window with + minimal energy among all possible dual windows. `from_win_equals_dual` and + `~scipy.signal.closest_STFT_dual_window` provide means for utilizing alterantive + dual windows. Note that `win` is also always a dual window of `dual_win`. + + Due to the convention of time t = 0 being at the first sample of the input + signal, the STFT values typically have negative time slots. Hence, + negative indexes like `p_min` or `k_min` do not indicate counting + backwards from an array's end like in standard Python indexing but being + left of t = 0. + + More detailed information can be found in the :ref:`tutorial_stft` + section of the :ref:`user_guide`. + + Note that all parameters of the initializer, except `scale_to` (which uses + `scaling`) have identical named attributes. + + Parameters + ---------- + win : np.ndarray + The window must be a real- or complex-valued 1d array. + hop : int + The increment in samples, by which the window is shifted in each step. + fs : float + Sampling frequency of input signal and window. Its relation to the + sampling interval `T` is ``T = 1 / fs``. + fft_mode : 'twosided', 'centered', 'onesided', 'onesided2X' + Mode of FFT to be used (default 'onesided'). + See property `fft_mode` for details. + mfft : int | None + Length of the FFT used, if a zero padded FFT is desired. + If ``None`` (default), the length of the window `win` is used. + dual_win : np.ndarray | None + The dual window of `win`. If set to ``None``, it is calculated if + needed. + scale_to : 'magnitude', 'psd' | None + If not ``None`` (default) the window function is scaled, so each STFT + column represents either a 'magnitude' or a power spectral density + ('psd') spectrum. This parameter sets the property `scaling` to the + same value. See method `scale_to` for details. + phase_shift : int | None + If set, add a linear phase `phase_shift` / `mfft` * `f` to each + frequency `f`. The default value of 0 ensures that there is no phase shift + on the zeroth slice (in which t=0 is centered). See property + `phase_shift` for more details. + + Notes + ----- + A typical STFT application is the creation of various types of time-frequency + plots, often subsumed under the term "spectrogram". Note that this term is also + used to explecitly refer to the absolute square of a STFT [11]_, as done in + :meth:`spectrogram`. + + The STFT can also be used for filtering and filter banks as discussed in [12]_. + + + References + ---------- + .. [11] Karlheinz Gröchenig: "Foundations of Time-Frequency Analysis", + Birkhäuser Boston 2001, `10.1007/978-1-4612-0003-1` + .. [12] Julius O. Smith III, "Spectral Audio Signal Processing", online book, 2011, + https://www.dsprelated.com/freebooks/sasp/ + + + Examples + -------- + The following example shows the magnitude of the STFT of a sine with + varying frequency :math:`f_i(t)` (marked by a red dashed line in the plot): + + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from scipy.signal import ShortTimeFFT + >>> from scipy.signal.windows import gaussian + ... + >>> T_x, N = 1 / 20, 1000 # 20 Hz sampling rate for 50 s signal + >>> t_x = np.arange(N) * T_x # time indexes for signal + >>> f_i = 1 * np.arctan((t_x - t_x[N // 2]) / 2) + 5 # varying frequency + >>> x = np.sin(2*np.pi*np.cumsum(f_i)*T_x) # the signal + + The utilized Gaussian window is 50 samples or 2.5 s long. The parameter + ``mfft=200`` in `ShortTimeFFT` causes the spectrum to be oversampled + by a factor of 4: + + >>> g_std = 8 # standard deviation for Gaussian window in samples + >>> w = gaussian(50, std=g_std, sym=True) # symmetric Gaussian window + >>> SFT = ShortTimeFFT(w, hop=10, fs=1/T_x, mfft=200, scale_to='magnitude') + >>> Sx = SFT.stft(x) # perform the STFT + + In the plot, the time extent of the signal `x` is marked by vertical dashed + lines. Note that the SFT produces values outside the time range of `x`. The + shaded areas on the left and the right indicate border effects caused + by the window slices in that area not fully being inside time range of + `x`: + + >>> fig1, ax1 = plt.subplots(figsize=(6., 4.)) # enlarge plot a bit + >>> t_lo, t_hi = SFT.extent(N)[:2] # time range of plot + >>> ax1.set_title(rf"STFT ({SFT.m_num*SFT.T:g}$\,s$ Gaussian window, " + + ... rf"$\sigma_t={g_std*SFT.T}\,$s)") + >>> ax1.set(xlabel=f"Time $t$ in seconds ({SFT.p_num(N)} slices, " + + ... rf"$\Delta t = {SFT.delta_t:g}\,$s)", + ... ylabel=f"Freq. $f$ in Hz ({SFT.f_pts} bins, " + + ... rf"$\Delta f = {SFT.delta_f:g}\,$Hz)", + ... xlim=(t_lo, t_hi)) + ... + >>> im1 = ax1.imshow(abs(Sx), origin='lower', aspect='auto', + ... extent=SFT.extent(N), cmap='viridis') + >>> ax1.plot(t_x, f_i, 'r--', alpha=.5, label='$f_i(t)$') + >>> fig1.colorbar(im1, label="Magnitude $|S_x(t, f)|$") + ... + >>> # Shade areas where window slices stick out to the side: + >>> for t0_, t1_ in [(t_lo, SFT.lower_border_end[0] * SFT.T), + ... (SFT.upper_border_begin(N)[0] * SFT.T, t_hi)]: + ... ax1.axvspan(t0_, t1_, color='w', linewidth=0, alpha=.2) + >>> for t_ in [0, N * SFT.T]: # mark signal borders with vertical line: + ... ax1.axvline(t_, color='y', linestyle='--', alpha=0.5) + >>> ax1.legend() + >>> fig1.tight_layout() + >>> plt.show() + + Reconstructing the signal with the `~ShortTimeFFT.istft` is + straightforward, but note that the length of `x1` should be specified, + since the STFT length increases in `hop` steps: + + >>> SFT.invertible # check if invertible + True + >>> x1 = SFT.istft(Sx, k1=N) + >>> np.allclose(x, x1) + True + + It is possible to calculate the STFT of signal parts: + + >>> N2 = SFT.nearest_k_p(N // 2) + >>> Sx0 = SFT.stft(x[:N2]) + >>> Sx1 = SFT.stft(x[N2:]) + + When assembling sequential STFT parts together, the overlap needs to be + considered: + + >>> p0_ub = SFT.upper_border_begin(N2)[1] - SFT.p_min + >>> p1_le = SFT.lower_border_end[1] - SFT.p_min + >>> Sx01 = np.hstack((Sx0[:, :p0_ub], + ... Sx0[:, p0_ub:] + Sx1[:, :p1_le], + ... Sx1[:, p1_le:])) + >>> np.allclose(Sx01, Sx) # Compare with SFT of complete signal + True + + It is also possible to calculate the `itsft` for signal parts: + + >>> y_p = SFT.istft(Sx, N//3, N//2) + >>> np.allclose(y_p, x[N//3:N//2]) + True + + """ + # immutable attributes (only have getters but no setters): + _win: np.ndarray # window + _dual_win: np.ndarray | None = None # canonical dual window + _hop: int # Step of STFT in number of samples + + # mutable attributes: + _fs: float # sampling frequency of input signal and window + _fft_mode: FFT_MODE_TYPE = 'onesided' # Mode of FFT to use + _mfft: int # length of FFT used - defaults to len(win) + _scaling: Literal['magnitude', 'psd', 'unitary'] | None = None # Scaling of _win + _phase_shift: int | None # amount to shift phase of FFT in samples + + # attributes for caching calculated values: + _fac_mag: float | None = None + _fac_psd: float | None = None + _lower_border_end: tuple[int, int] | None = None + # The following tuples store parameter(s) and return value(s) of methods for caching + # (initialized with invalid parameters; should only be accessed by atomic + # read/writes to alleviate potential multithreading issues): + _cache_post_padding: tuple[int, tuple[int, int]] = -1, (0, 0) + _cache_upper_border_begin: tuple[int, tuple[int, int]] = -1, (0, 0) + _cache_t: tuple[tuple[int, int | None, int | None, int, float], np.ndarray] = \ + (-1, None, None, 0, 0.), np.ndarray([]) + _cache_f: tuple[tuple[FFT_MODE_TYPE, int, float], np.ndarray] = \ + ('onesided', -1, 1.), np.ndarray([]) + + # generic type compatibility with scipy-stubs + __class_getitem__: classmethod = classmethod(GenericAlias) + + def __init__(self, win: np.ndarray, hop: int, fs: float, *, + fft_mode: FFT_MODE_TYPE = 'onesided', + mfft: int | None = None, + dual_win: np.ndarray | None = None, + scale_to: Literal['magnitude', 'psd'] | None = None, + phase_shift: int | None = 0): + if not (win.ndim == 1 and win.size > 0): + raise ValueError(f"Parameter win must be 1d, but {win.shape=}!") + if not all(np.isfinite(win)): + raise ValueError("Parameter win must have finite entries!") + if not (hop >= 1 and isinstance(hop, int | np.integer)): + raise ValueError(f"Parameter {hop=} is not an integer >= 1!") + + self._win, self._hop, self.fs = win, hop, fs + self.win.setflags(write=False) + self.mfft = len(win) if mfft is None else mfft + + if dual_win is not None: + if dual_win.shape != win.shape: + raise ValueError(f"{dual_win.shape=} must equal {win.shape=}!") + if not all(np.isfinite(dual_win)): + raise ValueError("Parameter dual_win must be a finite array!") + dual_win.setflags(write=False) + self._dual_win = dual_win # needs to be set before scaling + + if scale_to is not None: # needs to be set before fft_mode + self.scale_to(scale_to) + + self.fft_mode, self.phase_shift = fft_mode, phase_shift + + @classmethod + def from_dual(cls, dual_win: np.ndarray, hop: int, fs: float, *, + fft_mode: FFT_MODE_TYPE = 'onesided', + mfft: int | None = None, + scale_to: Literal['magnitude', 'psd'] | None = None, + phase_shift: int | None = 0): + r"""Instantiate a `ShortTimeFFT` by only providing a dual window. + + If an STFT is invertible, it is possible to calculate the window `win` + from a given dual window `dual_win`. All other parameters have the + same meaning as in the initializer of `ShortTimeFFT`. + + As explained in the :ref:`tutorial_stft` section of the + :ref:`user_guide`, an invertible STFT can be interpreted as series + expansion of time-shifted and frequency modulated dual windows. E.g., + the series coefficient S[q,p] belongs to the term, which shifted + `dual_win` by p * `delta_t` and multiplied it by + exp( 2 * j * pi * t * q * `delta_f`). + + + Examples + -------- + The following example discusses decomposing a signal into time- and + frequency-shifted Gaussians. A Gaussian with standard deviation of + one made up of 51 samples will be used: + + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from scipy.signal import ShortTimeFFT + >>> from scipy.signal.windows import gaussian + ... + >>> T, N = 0.1, 51 + >>> d_win = gaussian(N, std=1/T, sym=True) # symmetric Gaussian window + >>> t = T * (np.arange(N) - N//2) + ... + >>> fg1, ax1 = plt.subplots() + >>> ax1.set_title(r"Dual Window: Gaussian with $\sigma_t=1$") + >>> ax1.set(xlabel=f"Time $t$ in seconds ({N} samples, $T={T}$ s)", + ... xlim=(t[0], t[-1]), ylim=(0, 1.1*np.max(d_win))) + >>> ax1.plot(t, d_win, 'C0-') + + The following plot with the overlap of 41, 11 and 2 samples show how + the `hop` interval affects the shape of the window `win`: + + >>> fig2, axx = plt.subplots(3, 1, sharex='all') + ... + >>> axx[0].set_title(r"Windows for hop$\in\{10, 40, 49\}$") + >>> for c_, h_ in enumerate([10, 40, 49]): + ... SFT = ShortTimeFFT.from_dual(d_win, h_, 1/T) + ... axx[c_].plot(t + h_ * T, SFT.win, 'k--', alpha=.3, label=None) + ... axx[c_].plot(t - h_ * T, SFT.win, 'k:', alpha=.3, label=None) + ... axx[c_].plot(t, SFT.win, f'C{c_+1}', + ... label=r"$\Delta t=%0.1f\,$s" % SFT.delta_t) + ... axx[c_].set_ylim(0, 1.1*max(SFT.win)) + ... axx[c_].legend(loc='center') + >>> axx[-1].set(xlabel=f"Time $t$ in seconds ({N} samples, $T={T}$ s)", + ... xlim=(t[0], t[-1])) + >>> plt.show() + + Beside the window `win` centered at t = 0 the previous (t = -`delta_t`) + and following window (t = `delta_t`) are depicted. It can be seen that + for small `hop` intervals, the window is compact and smooth, having a + good time-frequency concentration in the STFT. For the large `hop` + interval of 4.9 s, the window has small values around t = 0, which are + not covered by the overlap of the adjacent windows, which could lead to + numeric inaccuracies. Furthermore, the peaky shape at the beginning and + the end of the window points to a higher bandwidth, resulting in a + poorer time-frequency resolution of the STFT. + Hence, the choice of the `hop` interval will be a compromise between + a time-frequency resolution and memory requirements demanded by small + `hop` sizes. + + See Also + -------- + from_window: Create instance by wrapping `get_window`. + ShortTimeFFT: Create instance using standard initializer. + """ + win = _calc_dual_canonical_window(dual_win, hop) + return cls(win=win, hop=hop, fs=fs, fft_mode=fft_mode, mfft=mfft, + dual_win=dual_win, scale_to=scale_to, + phase_shift=phase_shift) + + @classmethod + def from_window(cls, win_param: str | tuple | float, + fs: float, nperseg: int, noverlap: int, *, + symmetric_win: bool = False, + fft_mode: FFT_MODE_TYPE = 'onesided', + mfft: int | None = None, + scale_to: Literal['magnitude', 'psd'] | None = None, + phase_shift: int | None = 0): + """Instantiate `ShortTimeFFT` by using `get_window`. + + The method `get_window` is used to create a window of length + `nperseg`. The parameter names `noverlap`, and `nperseg` are used here, + since they more inline with other classical STFT libraries. + + Parameters + ---------- + win_param: Union[str, tuple, float], + Parameters passed to `get_window`. For windows with no parameters, + it may be a string (e.g., ``'hann'``), for parametrized windows a + tuple, (e.g., ``('gaussian', 2.)``) or a single float specifying + the shape parameter of a kaiser window (i.e. ``4.`` and + ``('kaiser', 4.)`` are equal. See `get_window` for more details. + fs : float + Sampling frequency of input signal. Its relation to the + sampling interval `T` is ``T = 1 / fs``. + nperseg: int + Window length in samples, which corresponds to the `m_num`. + noverlap: int + Window overlap in samples. It relates to the `hop` increment by + ``hop = npsereg - noverlap``. + symmetric_win: bool + If ``True`` then a symmetric window is generated, else a periodic window is + generated (default). Though symmetric windows seem for most applications to + be more sensible, the default of a periodic windows was chosen to + correspond to the default of `get_window`. This parameter is ignored, if + the window name in the `window` parameter has a suffix ``'_periodic'`` or + ``'_symmetric'`` appended to it (e.g., ``'hann_symmetric'``). + fft_mode : 'twosided', 'centered', 'onesided', 'onesided2X' + Mode of FFT to be used (default 'onesided'). + See property `fft_mode` for details. + mfft: int | None + Length of the FFT used, if a zero padded FFT is desired. + If ``None`` (default), the length of the window `win` is used. + scale_to : 'magnitude', 'psd' | None + If not ``None`` (default) the window function is scaled, so each + STFT column represents either a 'magnitude' or a power spectral + density ('psd') spectrum. This parameter sets the property + `scaling` to the same value. See method `scale_to` for details. + phase_shift : int | None + If set, add a linear phase `phase_shift` / `mfft` * `f` to each + frequency `f`. The default value 0 ensures that there is no phase + shift on the zeroth slice (in which t=0 is centered). See property + `phase_shift` for more details. + + Examples + -------- + The following instances ``SFT0`` and ``SFT1`` are equivalent: + + >>> from scipy.signal import ShortTimeFFT, get_window + >>> nperseg = 9 # window length + >>> w = get_window(('gaussian_periodic', 2.), nperseg) + >>> fs = 128 # sampling frequency + >>> hop = 3 # increment of STFT time slice + >>> SFT0 = ShortTimeFFT(w, hop, fs=fs) + >>> SFT1 = ShortTimeFFT.from_window(('gaussian', 2.), fs, nperseg, + ... noverlap=nperseg-hop) + + See Also + -------- + scipy.signal.get_window: Return a window of a given length and type. + from_dual: Create instance using dual window. + ShortTimeFFT: Create instance using standard initializer. + """ + win = get_window(win_param, nperseg, fftbins=not symmetric_win) + return cls(win, hop=nperseg-noverlap, fs=fs, fft_mode=fft_mode, + mfft=mfft, scale_to=scale_to, phase_shift=phase_shift) + + @classmethod + def from_win_equals_dual( + cls, desired_win: np.ndarray, hop: int, fs: float, *, + fft_mode: FFT_MODE_TYPE = 'onesided', mfft: int | None = None, + scale_to: Literal['magnitude', 'psd', 'unitary'] | None = None, + phase_shift: int | None = 0): + r"""Create instance where the window and its dual are equal up to a + scaling factor. + + An instance is created were window and dual window are equal as well as being + closest to the parameter `desired_win` in the least-squares sense, i.e., + minimizing ``abs(win-desired_win)**2``. Hence, `win` has the same length as + `desired_win`. Then a scaling factor is applied accoring to the `scale_to` + parameter. + + All other parameters have the identical meaning as in the initializer. + + To be able to calculate a valid window, `desired_win` needs to have a valid + dual STFT window for the given `hop` interval. + If this is not the case, a ``ValueError`` is raised. + + Parameters + ---------- + desired_win : np.ndarray + A real-valued or complex-valued 1d array containing the sample of the + desired window. + hop : int + The increment in samples, by which the window is shifted in each step. + fs : float + Sampling frequency of input signal and window. Its relation to the + sampling interval `T` is ``T = 1 / fs``. + fft_mode : 'twosided', 'centered', 'onesided', 'onesided2X' + Mode of FFT to be used (default 'onesided'). + See property `fft_mode` for details. + mfft : int | None + Length of the FFT used, if a zero padded FFT is desired. + If ``None`` (default), the length of the window `win` is used. + scale_to : 'magnitude' | 'psd' | 'unitary' | None + If not ``None`` (default) the window function is scaled, so each STFT + column represents either a 'magnitude' or a power spectral density ('psd') + spectrum, Alternatively, the STFT can be scaled to a`unitary` mapping, + i.e., dividing the window by ``np.sqrt(mfft)`` and multiplying the dual + window by the same amount. + phase_shift : int | None + If set, add a linear phase `phase_shift` / `mfft` * `f` to each + frequency `f`. The default value of 0 ensures that there is no phase shift + on the zeroth slice (in which t=0 is centered). See property + `phase_shift` for more details. + + + Notes + ----- + The set of all possible windows with identical dual is defined by the set of + linear constraints of Eq. :math:numref:`eq_STFT_AllDualWinsCond` in the + :ref:`tutorial_stft` section of the :ref:`user_guide`. There it is also + derived that ``ShortTimeFFT.dual_win == ShortTimeFFT.m_pts * ShortTimeFFT.win`` + needs to hold for an STFT to be a unitary mapping. + + A unitary mapping preserves the value of the scalar product, i.e., + + .. math:: + + \langle x, y\rangle = \sum_k x[k]\, \overline{y[k]} + \stackrel{\stackrel{\text{unitary}}{\downarrow}}{=} + \sum_{q,p} S_x[q,p]\, \overline{S_y[q,p]} + = \langle S_x[q,p], S_y[q,p]\rangle\ , + + with :math:`S_{x,y}` being the STFT of :math:`x,y`. Hence, the energy + :math:`E_x=T\sum_k |x[k]|^2` of a signal is also preserved. This is also + illustrated in the example below. + + Thie reason of distinguishing between no scaling (i.e., parameter `scale_to` is + ``None``) and unitary scaling (i.e., ``scale_to = 'unitary'``) is due to the + utilized FFT function not being unitary (i.e., using the default value + ``'backward'`` for the `~scipy.fft.fft` parameter `norm`). + + + See Also + -------- + closest_STFT_dual_window: Calculate the STFT dual window of a given window + closest to a desired dual window. + ShortTimeFFT.spectrogram: Calculate squared STFTs + ShortTimeFFT: Class this property belongs to. + + Examples + -------- + The following example shows that an STFT can be indeed unitary: + + >>> import matplotlib.pyplot as plt + >>> import numpy as np + >>> from scipy.signal import ShortTimeFFT, windows + ... + >>> m, hop, std = 36, 8, 5 + >>> desired_win = windows.gaussian(m, std, sym=True) + >>> SFT = ShortTimeFFT.from_win_equals_dual(desired_win, hop, fs=1/m, + ... fft_mode='twosided', + ... scale_to='unitary') + >>> np.allclose(SFT.dual_win, SFT.win * SFT.m_num) # check if STFT is unitary + True + >>> x1, x2 = np.tile([-1, -1, 1, 1], 5), np.tile([1, -1, -1, 1], 5) + >>> np.sum(x1*x2) # scalar product is zero -> orthogonal signals + 0 + >>> np.sum(x1**2) # scalar product of x1 with itself + 20 + >>> Sx11, Sx12 = SFT.spectrogram(x1), SFT.spectrogram(x1, x2) + >>> np.sum(Sx12) # STFT scalar product is also zero + -4.163336342344337e-16+0j # may vary + >>> np.sum(Sx11) # == np.sum(x1**2) + 19.999999999999996 # may vary + ... + ... # Do the plotting: + >>> fg1, (ax11, ax12) = plt.subplots(1, 2, tight_layout=True, figsize=(8, 4)) + >>> s_fac = np.sqrt(SFT.mfft) + >>> _ = fg1.suptitle(f"Scaled Unitary Window of {m} Sample Gaussian with " + + ... rf"{hop=}, $\sigma={std}$, Scale factor: {s_fac}") + >>> ax11.set(ylabel="Amplitude", xlabel="Samples", xlim=(0, m)) + >>> ax12.set(xlabel="Frequency Bins", ylabel="Magnitude Spectrum", + ... xlim=(0, 15), ylim=(1e-5, 1.5)) + >>> u_win_str = rf"Unitary $\times{s_fac:g}$" + >>> for x_, n_ in zip((desired_win, SFT.win*s_fac), ('Desired', u_win_str)): + ... ax11.plot(x_, '.-', alpha=0.5, label=n_) + ... X_ = np.fft.rfft(x_) / np.sum(abs(x_)) + ... ax12.semilogy(abs(X_), '.-', alpha=0.5, label=n_) + >>> for ax_ in (ax11, ax12): + ... ax_.grid(True) + ... ax_.legend() + >>> plt.show() + + Note that ``fftmode='twosided'`` is used, since we need sum over the complete + time frequency plane. Due to passing ``scale_to='unitary'`` the window + ``SFT.win`` is scaled by ``1/np.sqrt(SFT.mfft)``. Hence, ``SFT.win`` needs to + be scaled by `s_fac` in the plot above. + """ + if not (desired_win.ndim == 1 and desired_win.size > 0): + raise ValueError(f"Parameter desired_win is not 1d, but " + f"{desired_win.shape=}!") + if issubclass(desired_win.dtype.type, np.integer): + raise ValueError("Parameter desired_win cannot be of integer type, " + + f"but {desired_win.dtype=} => cast to float | complex ") + if not all(np.isfinite(desired_win)): + raise ValueError("Parameter desired_win must have finite entries!") + if not (1 <= hop <= len(desired_win) and isinstance(hop, int | np.integer)): + raise ValueError(f"Parameter {hop=} is not an integer between 1 and " + + f"{len(desired_win)=}!") + if scale_to not in ['magnitude', 'psd', 'unitary', None]: + raise ValueError(f"Parameter {scale_to=} not in " + + "['magnitude', 'psd', 'unitary', None]!") + + mfft = len(desired_win) if mfft is None else mfft + s_fac = np.sqrt(mfft) if scale_to == 'unitary' else 1 + + win = desired_win.copy() # we do not want to modify input parameters + relative_resolution = np.finfo(win.dtype).resolution * max(win) + for m in range(hop): + a = np.linalg.norm(desired_win[m::hop]) + if not (a > relative_resolution): + raise ValueError("Parameter desired_win does not have valid STFT dual " + f"window for {hop=}!") + win[m::hop] /= a + + SFT = cls(win=win/s_fac, hop=hop, fs=fs, fft_mode=fft_mode, mfft=mfft, + dual_win=win*s_fac, phase_shift=phase_shift, + scale_to=None if scale_to=='unitary' else scale_to) + + if scale_to == 'unitary': + SFT._scaling = scale_to + return SFT + + + @property + def win(self) -> np.ndarray: + """Window function as real- or complex-valued 1d array. + + This attribute is read-only, since `dual_win` depends on it. + To make this array immutable, its WRITEABLE flag is set to ``FALSE``. + + See Also + -------- + dual_win: Dual window. + m_num: Number of samples in window `win`. + m_num_mid: Center index of window `win`. + mfft: Length of input for the FFT used - may be larger than `m_num`. + hop: ime increment in signal samples for sliding window. + win: Window function as real- or complex-valued 1d array. + numpy.ndarray.setflags: Modify array flags. + ShortTimeFFT: Class this property belongs to. + """ + return self._win + + @property + def hop(self) -> int: + """Time increment in signal samples for sliding window. + + This attribute is read only, since `dual_win` depends on it. + + See Also + -------- + delta_t: Time increment of STFT (``hop*T``) + m_num: Number of samples in window `win`. + m_num_mid: Center index of window `win`. + mfft: Length of input for the FFT used - may be larger than `m_num`. + T: Sampling interval of input signal and of the window. + win: Window function as real- or complex-valued 1d array. + ShortTimeFFT: Class this property belongs to. + """ + return self._hop + + @property + def T(self) -> float: + """Sampling interval of input signal and of the window. + + A ``ValueError`` is raised if it is set to a non-positive value. + + See Also + -------- + delta_t: Time increment of STFT (``hop*T``) + hop: Time increment in signal samples for sliding window. + fs: Sampling frequency (being ``1/T``) + t: Times of STFT for an input signal with `n` samples. + ShortTimeFFT: Class this property belongs to. + """ + return 1 / self._fs + + @T.setter + def T(self, v: float): + """Sampling interval of input signal and of the window. + + A ``ValueError`` is raised if it is set to a non-positive value. + """ + if not (v > 0): + raise ValueError(f"Sampling interval T={v} must be positive!") + self._fs = 1 / v + + @property + def fs(self) -> float: + """Sampling frequency of input signal and of the window. + + The sampling frequency is the inverse of the sampling interval `T`. + A ``ValueError`` is raised if it is set to a non-positive value. + + See Also + -------- + delta_t: Time increment of STFT (``hop*T``) + hop: Time increment in signal samples for sliding window. + T: Sampling interval of input signal and of the window (``1/fs``). + ShortTimeFFT: Class this property belongs to. + """ + return self._fs + + @fs.setter + def fs(self, v: float): + """Sampling frequency of input signal and of the window. + + The sampling frequency is the inverse of the sampling interval `T`. + A ``ValueError`` is raised if it is set to a non-positive value. + """ + if not (v > 0): + raise ValueError(f"Sampling frequency fs={v} must be positive!") + self._fs = v + + @property + def fft_mode(self) -> FFT_MODE_TYPE: + """Mode of utilized FFT ('twosided', 'centered', 'onesided' or + 'onesided2X'). + + It can have the following values: + + 'twosided': + Two-sided FFT, where values for the negative frequencies are in + upper half of the array. Corresponds to :func:`~scipy.fft.fft()`. + 'centered': + Two-sided FFT with the values being ordered along monotonically + increasing frequencies. Corresponds to applying + :func:`~scipy.fft.fftshift()` to :func:`~scipy.fft.fft()`. + 'onesided': + Calculates only values for non-negative frequency values. + Corresponds to :func:`~scipy.fft.rfft()`. + 'onesided2X': + Like `onesided`, but the non-zero frequencies are doubled if + `scaling` is set to 'magnitude' or multiplied by ``sqrt(2)`` if + set to 'psd'. If `scaling` is ``None``, setting `fft_mode` to + `onesided2X` is not allowed. + If the FFT length `mfft` is even, the last FFT value is not paired, + and thus it is not scaled. + + Note that `onesided` and `onesided2X` do not work for complex-valued signals or + complex-valued windows. Furthermore, the frequency values can be obtained by + reading the `f` property, and the number of samples by accessing the `f_pts` + property. + + See Also + -------- + delta_f: Width of the frequency bins of the STFT. + f: Frequencies values of the STFT. + f_pts: Width of the frequency bins of the STFT. + onesided_fft: True if a one-sided FFT is used. + scaling: Normalization applied to the window function + ShortTimeFFT: Class this property belongs to. + """ + return self._fft_mode + + @fft_mode.setter + def fft_mode(self, t: FFT_MODE_TYPE): + """Set mode of FFT. + + Allowed values are 'twosided', 'centered', 'onesided', 'onesided2X'. + See the property `fft_mode` for more details. + """ + if t not in (fft_mode_types := get_args(FFT_MODE_TYPE)): + raise ValueError(f"fft_mode='{t}' not in {fft_mode_types}!") + + if t in {'onesided', 'onesided2X'} and np.iscomplexobj(self.win): + raise ValueError(f"One-sided spectra, i.e., fft_mode='{t}', " + + "are not allowed for complex-valued windows!") + + if t == 'onesided2X' and self.scaling is None: + raise ValueError(f"For scaling is None, fft_mode='{t}' is invalid!" + "Do scale_to('psd') or scale_to('magnitude')!") + self._fft_mode = t + + @property + def mfft(self) -> int: + """Length of input for the FFT used - may be larger than window + length `m_num`. + + If not set, `mfft` defaults to the window length `m_num`. + + See Also + -------- + f_pts: Number of points along the frequency axis. + f: Frequencies values of the STFT. + m_num: Number of samples in window `win`. + ShortTimeFFT: Class this property belongs to. + """ + return self._mfft + + @mfft.setter + def mfft(self, n_: int): + """Setter for the length of FFT utilized. + + See the property `mfft` for further details. + """ + if not (n_ >= self.m_num): + raise ValueError(f"Attribute mfft={n_} needs to be at least the " + + f"window length m_num={self.m_num}!") + self._mfft = n_ + + @property + def scaling(self) -> Literal['magnitude', 'psd', 'unitary'] | None: + """Normalization applied to the window function + ('magnitude', 'psd', 'unitary', or ``None``). + + If not ``None``, the FFT slices may be either interpreted as a `magnitude` or + a power spectral density spectrum (`psd`). If set to `unitary`, the STFT may be + interpreted as a unitary mapping, i.e., preserving the value of the scalar + product. + + The window function can be scaled by calling the `scale_to` method, + or it is set by the initializer parameter ``scale_to``. Note that a + window cannot to be scaled to be `unitary`. Use `from_win_equals_dual` + to create a unitary `ShortTimeFFT` instance. + + See Also + -------- + fac_magnitude: Scaling factor for to a magnitude spectrum. + fac_psd: Scaling factor for to a power spectral density spectrum. + fft_mode: Mode of utilized FFT + scale_to: Scale window to obtain 'magnitude' or 'psd' scaling. + from_win_equals_dual: Class-method for creating a unitary instance. + ShortTimeFFT: Class this property belongs to. + """ + return self._scaling + + def scale_to(self, scaling: Literal['magnitude', 'psd']): + """Scale window to obtain 'magnitude' or 'psd' scaling for the STFT. + + The window of a 'magnitude' spectrum has an integral of one, i.e., unit + area for non-negative windows. This ensures that absolute the values of + spectrum does not change if the length of the window changes (given + the input signal is stationary). + + To represent the power spectral density ('psd') for varying length + windows the area of the absolute square of the window needs to be + unity. + + The `scaling` property shows the current scaling. The properties + `fac_magnitude` and `fac_psd` show the scaling factors required to + scale the STFT values to a magnitude or a psd spectrum. + + Note that a window cannot to be scaled to be `unitary`. Use + `from_win_equals_dual` to create a unitary `ShortTimeFFT` instance. + + This method is called, if the initializer parameter `scale_to` is set. + + Parameters + ---------- + scaling : {'magnitude', 'psd'} + The scaling type to which the window is scaled. + + See Also + -------- + fac_magnitude: Scaling factor for to a magnitude spectrum. + fac_psd: Scaling factor for to a power spectral density spectrum. + fft_mode: Mode of utilized FFT + scaling: Normalization applied to the window function. + ShortTimeFFT: Class this method belongs to. + """ + if scaling not in (scaling_values := {'magnitude', 'psd'}): + raise ValueError(f"{scaling=} not in {scaling_values}!") + if self._scaling == scaling: # do nothing + return + + s_fac = self.fac_psd if scaling == 'psd' else self.fac_magnitude + self._win = self._win * s_fac + self.win.setflags(write=False) + if self._dual_win is not None: + self._dual_win = self._dual_win / s_fac + self.dual_win.setflags(write=False) + self._fac_mag, self._fac_psd = None, None # reset scaling factors + self._scaling = scaling + + @property + def phase_shift(self) -> int | None: + """If set, add linear phase `phase_shift` / `mfft` * `f` to each FFT + slice of frequency `f`. + + Shifting (more precisely `rolling`) an `mfft`-point FFT input by + `phase_shift` samples results in a multiplication of the output by + ``np.exp(2j*np.pi*q*phase_shift/mfft)`` at the frequency q * `delta_f`. + + The default value 0 ensures that there is no phase shift on the + zeroth slice (in which t=0 is centered). + No phase shift (``phase_shift is None``) is equivalent to + ``phase_shift = -mfft//2``. In this case slices are not shifted + before calculating the FFT. + + The absolute value of `phase_shift` is limited to be less than `mfft`. + + See Also + -------- + delta_f: Width of the frequency bins of the STFT. + f: Frequencies values of the STFT. + mfft: Length of input for the FFT used + ShortTimeFFT: Class this property belongs to. + """ + return self._phase_shift + + @phase_shift.setter + def phase_shift(self, v: int | None): + """The absolute value of the phase shift needs to be less than mfft + samples. + + See the `phase_shift` getter method for more details. + """ + if v is None: + self._phase_shift = v + return + if not isinstance(v, int | np.integer): + raise ValueError(f"phase_shift={v} has the unit samples. Hence " + + "it needs to be an int or it may be None!") + if not (-self.mfft < v < self.mfft): + raise ValueError("-mfft < phase_shift < mfft does not hold " + + f"for mfft={self.mfft}, phase_shift={v}!") + self._phase_shift = v + + def _x_slices(self, x: np.ndarray, k_off: int, p0: int, p1: int, + padding: PAD_TYPE) -> Generator[np.ndarray, None, None]: + """Generate signal slices along last axis of `x`. + + This method is only used by `stft_detrend`. The parameters are + described in `~ShortTimeFFT.stft`. + """ + if padding not in (padding_types := get_args(PAD_TYPE)): + raise ValueError(f"Parameter {padding=} not in {padding_types}!") + pad_kws: dict[str, dict] = { # possible keywords to pass to np.pad: + 'zeros': dict(mode='constant', constant_values=(0, 0)), + 'edge': dict(mode='edge'), + 'even': dict(mode='reflect', reflect_type='even'), + 'odd': dict(mode='reflect', reflect_type='odd'), + } # typing of pad_kws is needed to make mypy happy + + n, n1 = x.shape[-1], (p1 - p0) * self.hop + k0 = p0 * self.hop - self.m_num_mid + k_off # start sample + k1 = k0 + n1 + self.m_num # end sample + + i0, i1 = max(k0, 0), min(k1, n) # indexes to shorten x + # dimensions for padding x: + pad_width = [(0, 0)] * (x.ndim-1) + [(-min(k0, 0), max(k1 - n, 0))] + + x1 = np.pad(x[..., i0:i1], pad_width, **pad_kws[padding]) + for k_ in range(0, n1, self.hop): + yield x1[..., k_:k_ + self.m_num] + + def stft(self, x: np.ndarray, p0: int | None = None, + p1: int | None = None, *, k_offset: int = 0, + padding: PAD_TYPE = 'zeros', axis: int = -1) \ + -> np.ndarray: + """Perform the short-time Fourier transform. + + A two-dimensional matrix with ``p1-p0`` columns is calculated. + The `f_pts` rows represent value at the frequencies `f`. The q-th + column of the windowed FFT with the window `win` is centered at t[q]. + The columns represent the values at the frequencies `f`. + + Parameters + ---------- + x : np.ndarray + The input signal as real or complex valued array. For complex values, the + property `fft_mode` must be set to 'twosided' or 'centered'. + p0 : int | None + The first element of the range of slices to calculate. If ``None`` + then it is set to :attr:`p_min`, which is the smallest possible + slice. + p1 : int | None + The end of the array. If ``None`` then `p_max(n)` is used. + k_offset : int + Index of first sample (t = 0) in `x`. + padding : 'zeros' | 'edge' | 'even' | 'odd' + Kind of values which are added, when the sliding window sticks out + on either the lower or upper end of the input `x`. Zeros are added + if the default 'zeros' is set. For 'edge' either the first or the + last value of `x` is used. 'even' pads by reflecting the + signal on the first or last sample and 'odd' additionally + multiplies it with -1. + axis : int + The axis of `x` over which to compute the STFT. + If not given, the last axis is used. + + Returns + ------- + S : np.ndarray + A complex array is returned with the dimension always being larger + by one than of `x`. The last axis always represents the time slices + of the STFT. `axis` defines the frequency axis (default second to + last). E.g., for a one-dimensional `x`, a complex 2d array is + returned, with axis 0 representing frequency and axis 1 the time + slices. + + See Also + -------- + delta_f: Width of the frequency bins of the STFT. + delta_t: Time increment of STFT + f: Frequencies values of the STFT. + invertible: Check if STFT is invertible. + :meth:`~ShortTimeFFT.istft`: Inverse short-time Fourier transform. + p_range: Determine and validate slice index range. + stft_detrend: STFT with detrended segments. + t: Times of STFT for an input signal with `n` samples. + :class:`scipy.signal.ShortTimeFFT`: Class this method belongs to. + """ + return self.stft_detrend(x, None, p0, p1, k_offset=k_offset, + padding=padding, axis=axis) + + def stft_detrend(self, x: np.ndarray, + detr: Callable[[np.ndarray], np.ndarray] | Literal['linear', 'constant'] | None, # noqa: E501 + p0: int | None = None, p1: int | None = None, *, + k_offset: int = 0, padding: PAD_TYPE = 'zeros', + axis: int = -1) \ + -> np.ndarray: + """Calculate short-time Fourier transform with a trend being subtracted from + each segment beforehand. + + When the parameter `detr` is ``None``, this method's behavior is identical to + the `~ShortTimeFFT.stft` method. Note that due to the detrending, the original + signal cannot be reconstructed by the `~ShortTimeFFT.istft`. + + Parameters + ---------- + x : np.ndarray + The input signal as real or complex valued array. For complex values, the + property `fft_mode` must be set to 'twosided' or 'centered'. + detr : 'linear' | 'constant' | Callable[[np.ndarray], np.ndarray] | None + If 'constant', the mean is subtracted, if set to "linear", the linear + trend is removed from each segment. This is achieved by calling + `~scipy.signal.detrend`. If `detr` is a function with one parameter, `detr` + is applied to each segment. + p0 : int | None + The first element of the range of slices to calculate. If ``None`` + then it is set to :attr:`p_min`, which is the smallest possible + slice. + p1 : int | None + The end of the array. If ``None`` then `p_max(n)` is used. + k_offset : int + Index of first sample (t = 0) in `x`. + padding : 'zeros' | 'edge' | 'even' | 'odd' + Kind of values which are added, when the sliding window sticks out + on either the lower or upper end of the input `x`. Zeros are added + if the default 'zeros' is set. For 'edge' either the first or the + last value of `x` is used. 'even' pads by reflecting the + signal on the first or last sample and 'odd' additionally + multiplies it with -1. + axis : int + The axis of `x` over which to compute the STFT. + If not given, the last axis is used. + + Returns + ------- + S : np.ndarray + A complex array is returned with the dimension always being larger + by one than of `x`. The last axis always represents the time slices + of the STFT. `axis` defines the frequency axis (default second to + last). E.g., for a one-dimensional `x`, a complex 2d array is + returned, with axis 0 representing frequency and axis 1 the time + slices. + + See Also + -------- + invertible: Check if STFT is invertible. + :meth:`~ShortTimeFFT.istft`: Inverse short-time Fourier transform. + :meth:`~ShortTimeFFT.stft`: Short-time Fourier transform + (without detrending). + :class:`scipy.signal.ShortTimeFFT`: Class this method belongs to. + """ + if self.onesided_fft and np.iscomplexobj(x): + raise ValueError(f"Complex-valued `x` not allowed for {self.fft_mode=}'! " + "Set property `fft_mode` to 'twosided' or 'centered'.") + if isinstance(detr, str): + detr = partial(detrend, type=detr) + elif not (detr is None or callable(detr)): + raise ValueError(f"Parameter {detr=} is not a str, function or " + + "None!") + n = x.shape[axis] + if not (n >= (m2p := self.m_num-self.m_num_mid)): + e_str = f'{len(x)=}' if x.ndim == 1 else f'of {axis=} of {x.shape}' + raise ValueError(f"{e_str} must be >= ceil(m_num/2) = {m2p}!") + + if x.ndim > 1: # motivated by the NumPy broadcasting mechanisms: + x = np.moveaxis(x, axis, -1) + # determine slice index range: + p0, p1 = self.p_range(n, p0, p1) + S_shape_1d = (self.f_pts, p1 - p0) + S_shape = x.shape[:-1] + S_shape_1d if x.ndim > 1 else S_shape_1d + S = np.zeros(S_shape, dtype=complex) + for p_, x_ in enumerate(self._x_slices(x, k_offset, p0, p1, padding)): + if detr is not None: + x_ = detr(x_) + S[..., :, p_] = self._fft_func(x_ * self.win.conj()) + if x.ndim > 1: + return np.moveaxis(S, -2, axis if axis >= 0 else axis-1) + return S + + def spectrogram(self, x: np.ndarray, y: np.ndarray | None = None, + detr: Callable[[np.ndarray], np.ndarray] | Literal['linear', 'constant'] | None = None, # noqa: E501 + *, + p0: int | None = None, p1: int | None = None, + k_offset: int = 0, padding: PAD_TYPE = 'zeros', + axis: int = -1) \ + -> np.ndarray: + r"""Calculate spectrogram or cross-spectrogram. + + The spectrogram is the absolute square of the STFT, i.e., it is + ``abs(S[q,p])**2`` for given ``S[q,p]`` and thus is always + non-negative. + For two STFTs ``Sx[q,p], Sy[q,p]``, the cross-spectrogram is defined + as ``Sx[q,p] * np.conj(Sy[q,p])`` and is complex-valued. + This is a convenience function for calling `~ShortTimeFFT.stft` / + `stft_detrend`, hence all parameters are discussed there. + + Parameters + ---------- + x : np.ndarray + The input signal as real or complex valued array. For complex values, the + property `fft_mode` must be set to 'twosided' or 'centered'. + y : np.ndarray + The second input signal of the same shape as `x`. If ``None``, it is + assumed to be `x`. For complex values, the property `fft_mode` must be + set to 'twosided' or 'centered'. + detr : 'linear' | 'constant' | Callable[[np.ndarray], np.ndarray] | None + If 'constant', the mean is subtracted, if set to "linear", the linear + trend is removed from each segment. This is achieved by calling + `~scipy.signal.detrend`. If `detr` is a function with one parameter, `detr` + is applied to each segment. For ``None`` (default), no trends are removed. + p0 : int | None + The first element of the range of slices to calculate. If ``None`` + then it is set to :attr:`p_min`, which is the smallest possible + slice. + p1 : int | None + The end of the array. If ``None`` then `p_max(n)` is used. + k_offset : int + Index of first sample (t = 0) in `x`. + padding : 'zeros' | 'edge' | 'even' | 'odd' + Kind of values which are added, when the sliding window sticks out + on either the lower or upper end of the input `x`. Zeros are added + if the default 'zeros' is set. For 'edge' either the first or the + last value of `x` is used. 'even' pads by reflecting the + signal on the first or last sample and 'odd' additionally + multiplies it with -1. + axis : int + The axis of `x` over which to compute the STFT. + If not given, the last axis is used. + + Returns + ------- + S_xy : np.ndarray + A real-valued array with non-negative values is returned, if ``x is y`` or + `y` is ``None``. The dimension is always by one larger than of `x`. The + last axis always represents the time slices of the spectrogram. `axis` + defines the frequency axis (default second to last). E.g., for a + one-dimensional `x`, a complex 2d array is returned, with axis 0 + representing frequency and axis 1 the time slices. + + See Also + -------- + :meth:`~ShortTimeFFT.stft`: Perform the short-time Fourier transform. + stft_detrend: STFT with a trend subtracted from each segment. + :class:`scipy.signal.ShortTimeFFT`: Class this method belongs to. + + Notes + ----- + The cross-spectrogram may be interpreted as the time-frequency analogon of the + cross-spectral density (consult `csd`). The absolute square `|Sxy|²` of a + cross-spectrogram `Sxy` divided by the spectrograms `Sxx` and `Syy` can be + interpreted as a coherence spectrogram ``Cxy := abs(Sxy)**2 / (Sxx*Syy)``, + which is the time-frequency analogon to `~coherence`. + + If the STFT is parametrized to be a unitary transform, i.e., utilitzing + `~from_win_equals_dual`, then the value of the scalar product, hence also the + energy, is preserved. + + Examples + -------- + The following example shows the spectrogram of a square wave with varying + frequency :math:`f_i(t)` (marked by a green dashed line in the plot) sampled + with 20 Hz. The utilized Gaussian window is 50 samples or 2.5 s long. For the + `ShortTimeFFT`, the parameter ``mfft=800`` (oversampling factor 16) and the + `hop` interval of 2 in was chosen to produce a sufficient number of points. + + The plot's colormap is logarithmically scaled as the power spectral + density is in dB. The time extent of the signal `x` is marked by + vertical dashed lines, and the shaded areas mark the presence of border + effects. + + >>> import matplotlib.pyplot as plt + >>> import numpy as np + >>> from scipy.signal import square, ShortTimeFFT + >>> from scipy.signal.windows import gaussian + ... + >>> T_x, N = 1 / 20, 1000 # 20 Hz sampling rate for 50 s signal + >>> t_x = np.arange(N) * T_x # time indexes for signal + >>> f_i = 5e-3*(t_x - t_x[N // 3])**2 + 1 # varying frequency + >>> x = square(2*np.pi*np.cumsum(f_i)*T_x) # the signal + ... + >>> g_std = 12 # standard deviation for Gaussian window in samples + >>> win = gaussian(50, std=g_std, sym=True) # symmetric Gaussian wind. + >>> SFT = ShortTimeFFT(win, hop=2, fs=1/T_x, mfft=800, scale_to='psd') + >>> Sx2 = SFT.spectrogram(x) # calculate absolute square of STFT + ... + >>> fig1, ax1 = plt.subplots(figsize=(6., 4.)) # enlarge plot a bit + >>> t_lo, t_hi = SFT.extent(N)[:2] # time range of plot + >>> ax1.set_title(rf"Spectrogram ({SFT.m_num*SFT.T:g}$\,s$ Gaussian " + + ... rf"window, $\sigma_t={g_std*SFT.T:g}\,$s)") + >>> ax1.set(xlabel=f"Time $t$ in seconds ({SFT.p_num(N)} slices, " + + ... rf"$\Delta t = {SFT.delta_t:g}\,$s)", + ... ylabel=f"Freq. $f$ in Hz ({SFT.f_pts} bins, " + + ... rf"$\Delta f = {SFT.delta_f:g}\,$Hz)", + ... xlim=(t_lo, t_hi)) + >>> Sx_dB = 10 * np.log10(np.fmax(Sx2, 1e-4)) # limit range to -40 dB + >>> im1 = ax1.imshow(Sx_dB, origin='lower', aspect='auto', + ... extent=SFT.extent(N), cmap='magma') + >>> ax1.plot(t_x, f_i, 'g--', alpha=.5, label='$f_i(t)$') + >>> fig1.colorbar(im1, label='Power Spectral Density ' + + ... r"$20\,\log_{10}|S_x(t, f)|$ in dB") + ... + >>> # Shade areas where window slices stick out to the side: + >>> for t0_, t1_ in [(t_lo, SFT.lower_border_end[0] * SFT.T), + ... (SFT.upper_border_begin(N)[0] * SFT.T, t_hi)]: + ... ax1.axvspan(t0_, t1_, color='w', linewidth=0, alpha=.3) + >>> for t_ in [0, N * SFT.T]: # mark signal borders with vertical line + ... ax1.axvline(t_, color='c', linestyle='--', alpha=0.5) + >>> ax1.legend() + >>> fig1.tight_layout() + >>> plt.show() + + The logarithmic scaling reveals the odd harmonics of the square wave, + which are reflected at the Nyquist frequency of 10 Hz. This aliasing + is also the main source of the noise artifacts in the plot. + """ + Sx = self.stft_detrend(x, detr, p0, p1, k_offset=k_offset, + padding=padding, axis=axis) + if y is None or y is x: # do spectrogram: + return Sx.real**2 + Sx.imag**2 + # Cross-spectrogram: + Sy = self.stft_detrend(y, detr, p0, p1, k_offset=k_offset, + padding=padding, axis=axis) + return Sx * Sy.conj() + + @property + def dual_win(self) -> np.ndarray: + """Dual window (canonical dual window by default). + + A STFT can be interpreted as the input signal being expressed as a + weighted sum of modulated and time-shifted dual windows. If no dual window is + given on instantiation, the canonical dual window, i.e., the window with the + minimal energy (i.e., minimal L²-norm) is calculated. Alternative means for + determining dual windows are provided by `closest_STFT_dual_window` and the + `from_win_equals_dual` class-method. Note that `win` is also always a + dual window of `dual_win`. + + `dual_win` has same length as `win`, namely `m_num` samples. + + If the dual window cannot be calculated a ``ValueError`` is raised. + This attribute is read only and calculated lazily. + To make this array immutable, its WRITEABLE flag is set to ``FALSE``. + + See Also + -------- + m_num: Number of samples in window `win` and `dual_win`. + win: Window function as real- or complex-valued 1d array. + from_win_equals_dual: Create instance where `win` and `dual_win` are equal. + closest_STFT_dual_window: Calculate dual window closest to a desired window. + numpy.ndarray.setflags: Modify array flags. + ShortTimeFFT: Class this property belongs to. + """ + if self._dual_win is None: + self._dual_win = _calc_dual_canonical_window(self.win, self.hop) + self.dual_win.setflags(write=False) + return self._dual_win + + @property + def invertible(self) -> bool: + """Check if STFT is invertible. + + This is achieved by trying to calculate the canonical dual window. + + See Also + -------- + :meth:`~ShortTimeFFT.istft`: Inverse short-time Fourier transform. + m_num: Number of samples in window `win` and `dual_win`. + dual_win: Dual window. + win: Window for STFT. + ShortTimeFFT: Class this property belongs to. + """ + try: + return len(self.dual_win) > 0 # call self.dual_win() + except ValueError: + return False + + def istft(self, S: np.ndarray, k0: int = 0, k1: int | None = None, *, + f_axis: int = -2, t_axis: int = -1) \ + -> np.ndarray: + """Inverse short-time Fourier transform. + + Parameters + ---------- + S : ndarray + A complex valued array where `f_axis` denotes the frequency + values and the `t_axis` dimension the temporal values of the + STFT values. + k0, k1 : int, optional + The start and the end index of the reconstructed signal. The + default (``k0 = 0``, ``k1 = None``) assumes that the maximum length + signal should be reconstructed. + f_axis, t_axis : int, optional + The axes in `S` denoting the frequency and the time dimension. + + Returns + ------- + ndarray + Array of dimension ``S.ndim - 1`` which is real if `onesided_fft` is set, + else complex. + + Raises + ------ + ValueError + If the STFT is not invertible, or the parameters are out of bounds. + + See Also + -------- + invertible: Check if STFT is invertible. + :meth:`~ShortTimeFFT.stft`: Perform Short-time Fourier transform. + :class:`scipy.signal.ShortTimeFFT`: Class this method belongs to. + + Notes + ----- + It is required that `S` has `f_pts` entries along the `f_axis`. For + the `t_axis` it is assumed that the first entry corresponds to + `p_min` * `delta_t` (being <= 0). The length of `t_axis` needs to be + compatible with `k1`. I.e., ``S.shape[t_axis] >= self.p_max(k1)`` must + hold, if `k1` is not ``None``. Else `k1` is set to `k_max` with:: + + q_max = S.shape[t_range] + self.p_min + k_max = (q_max - 1) * self.hop + self.m_num - self.m_num_mid + + The :ref:`tutorial_stft` section of the :ref:`user_guide` discussed the + slicing behavior by means of an example. + """ + if f_axis == t_axis: + raise ValueError(f"{f_axis=} may not be equal to {t_axis=}!") + if S.shape[f_axis] != self.f_pts: + raise ValueError(f"{S.shape[f_axis]=} must be equal to " + + f"{self.f_pts=} ({S.shape=})!") + n_min = self.m_num-self.m_num_mid # minimum signal length + if not (S.shape[t_axis] >= (q_num := self.p_num(n_min))): + raise ValueError(f"{S.shape[t_axis]=} needs to have at least " + + f"{q_num} slices ({S.shape=})!") + if t_axis != S.ndim - 1 or f_axis != S.ndim - 2: + t_axis = S.ndim + t_axis if t_axis < 0 else t_axis + f_axis = S.ndim + f_axis if f_axis < 0 else f_axis + S = np.moveaxis(S, (f_axis, t_axis), (-2, -1)) + + q_max = S.shape[-1] + self.p_min + k_max = (q_max - 1) * self.hop + self.m_num - self.m_num_mid + + k1 = k_max if k1 is None else k1 + if not (self.k_min <= k0 < k1 <= k_max): + raise ValueError(f"({self.k_min=}) <= ({k0=}) < ({k1=}) <= " + + f"({k_max=}) is false!") + if not (num_pts := k1 - k0) >= n_min: + raise ValueError(f"({k1=}) - ({k0=}) = {num_pts} has to be at " + + f"least the half the window length {n_min}!") + + q0 = (k0 // self.hop + self.p_min if k0 >= 0 else # p_min always <= 0 + k0 // self.hop) + q1 = min(self.p_max(k1), q_max) + k_q0, k_q1 = self.nearest_k_p(k0), self.nearest_k_p(k1, left=False) + n_pts = k_q1 - k_q0 + self.m_num - self.m_num_mid + x = np.zeros(S.shape[:-2] + (n_pts,), + dtype=float if self.onesided_fft else complex) + for q_ in range(q0, q1): + xs = self._ifft_func(S[..., :, q_ - self.p_min]) * self.dual_win + i0 = q_ * self.hop - self.m_num_mid + i1 = min(i0 + self.m_num, n_pts+k0) + j0, j1 = 0, i1 - i0 + if i0 < k0: # xs sticks out to the left on x: + j0 += k0 - i0 + i0 = k0 + x[..., i0-k0:i1-k0] += xs[..., j0:j1] + x = x[..., :k1-k0] + if x.ndim > 1: + x = np.moveaxis(x, -1, f_axis if f_axis < x.ndim else t_axis) + return x + + @property + def fac_magnitude(self) -> float: + """Factor to multiply the STFT values by to scale each frequency slice + to a magnitude spectrum. + + It is 1 if attribute ``scaling == 'magnitude'``. + The window can be scaled to a magnitude spectrum by using the method + `scale_to`. + + See Also + -------- + fac_psd: Scaling factor for to a power spectral density spectrum. + scale_to: Scale window to obtain 'magnitude' or 'psd' scaling. + scaling: Normalization applied to the window function. + ShortTimeFFT: Class this property belongs to. + """ + if self.scaling == 'magnitude': + return 1 + if self._fac_mag is None: + self._fac_mag = 1 / abs(sum(self.win)) + return self._fac_mag + + @property + def fac_psd(self) -> float: + """Factor to multiply the STFT values by to scale each frequency slice + to a power spectral density (PSD). + + It is 1 if attribute ``scaling == 'psd'``. + The window can be scaled to a psd spectrum by using the method + `scale_to`. + + See Also + -------- + fac_magnitude: Scaling factor for to a magnitude spectrum. + scale_to: Scale window to obtain 'magnitude' or 'psd' scaling. + scaling: Normalization applied to the window function. + ShortTimeFFT: Class this property belongs to. + """ + if self.scaling == 'psd': + return 1 + if self._fac_psd is None: + self._fac_psd = 1 / np.sqrt( + sum(self.win.real**2+self.win.imag**2) / self.T) + return self._fac_psd + + @property + def m_num(self) -> int: + """Number of samples in window `win`. + + Note that the FFT can be oversampled by zero-padding. This is achieved + by setting the `mfft` property. + + See Also + -------- + m_num_mid: Center index of window `win`. + mfft: Length of input for the FFT used - may be larger than `m_num`. + hop: Time increment in signal samples for sliding window. + win: Window function as real- or complex-valued 1d array. + ShortTimeFFT: Class this property belongs to. + """ + return len(self.win) + + @property + def m_num_mid(self) -> int: + """Center index of window `win`. + + For odd `m_num`, ``(m_num - 1) / 2`` is returned and + for even `m_num` (per definition) ``m_num / 2`` is returned. + + See Also + -------- + m_num: Number of samples in window `win`. + mfft: Length of input for the FFT used - may be larger than `m_num`. + hop: ime increment in signal samples for sliding window. + win: Window function as real- or complex-valued 1d array. + ShortTimeFFT: Class this property belongs to. + """ + return self.m_num // 2 + + @cached_property + def _pre_padding(self) -> tuple[int, int]: + """Smallest signal index and slice index due to padding. + + Since, per convention, for time t=0, n,q is zero, the returned values + are negative or zero. + """ + w2 = self.win.real**2 + self.win.imag**2 + # move window to the left until the overlap with t >= 0 vanishes: + n0 = -self.m_num_mid + for p_, n_ in enumerate(range(n0, n0-self.m_num-1, -self.hop)): + n_next = n_ - self.hop + if n_next + self.m_num <= 0 or all(w2[n_next:] == 0): + return n_, -p_ + # Make the linter happy: + raise RuntimeError("This code line should never run! Please file a bug.") + + @property + def k_min(self) -> int: + """The smallest possible signal index of the STFT. + + `k_min` is the index of the left-most non-zero value of the lowest + slice `p_min`. Since the zeroth slice is centered over the zeroth + sample of the input signal, `k_min` is never positive. + A detailed example is provided in the :ref:`tutorial_stft_sliding_win` + section of the :ref:`user_guide`. + + See Also + -------- + k_max: First sample index after signal end not touched by a time slice. + lower_border_end: Where pre-padding effects end. + p_min: The smallest possible slice index. + p_max: Index of first non-overlapping upper time slice. + p_num: Number of time slices, i.e., `p_max` - `p_min`. + p_range: Determine and validate slice index range. + upper_border_begin: Where post-padding effects start. + ShortTimeFFT: Class this property belongs to. + """ + return self._pre_padding[0] + + @property + def p_min(self) -> int: + """The smallest possible slice index. + + `p_min` is the index of the left-most slice, where the window still + sticks into the signal, i.e., has non-zero part for t >= 0. + `k_min` is the smallest index where the window function of the slice + `p_min` is non-zero. + + Since, per convention the zeroth slice is centered at t=0, + `p_min` <= 0 always holds. + A detailed example is provided in the :ref:`tutorial_stft_sliding_win` + section of the :ref:`user_guide`. + + See Also + -------- + k_min: The smallest possible signal index. + k_max: First sample index after signal end not touched by a time slice. + p_max: Index of first non-overlapping upper time slice. + p_num: Number of time slices, i.e., `p_max` - `p_min`. + p_range: Determine and validate slice index range. + ShortTimeFFT: Class this property belongs to. + """ + return self._pre_padding[1] + + def _post_padding(self, n: int) -> tuple[int, int]: + """Largest signal index and slice index due to padding. + + Parameters + ---------- + n : int + Number of samples of input signal (must be ≥ half of the window length). + + Notes + ----- + Note that the return values are cached together with the parameter `n` to avoid + unnecessary recalculations. + """ + if not (n >= (m2p := self.m_num - self.m_num_mid)): + raise ValueError(f"Parameter n must be >= ceil(m_num/2) = {m2p}!") + last_arg, last_return_value = self._cache_post_padding + if n == last_arg: # use cached value: + return last_return_value + w2 = self.win.real**2 + self.win.imag**2 + # move window to the right until the overlap for t < t[n] vanishes: + q1 = n // self.hop # last slice index with t[p1] <= t[n] + k1 = q1 * self.hop - self.m_num_mid + for q_, k_ in enumerate(range(k1, n+self.m_num, self.hop), start=q1): + n_next = k_ + self.hop + if n_next >= n or all(w2[:n-n_next] == 0): + return_value = k_ + self.m_num, q_ + 1 + self._cache_post_padding = n, return_value + return return_value + raise RuntimeError("This code line should never run! Please file a bug.") + # If this case is reached, it probably means the last slice should be + # returned, i.e.: return k1 + self.m_num - self.m_num_mid, q1 + 1 + + def k_max(self, n: int) -> int: + """First sample index after signal end not touched by a time slice. + + `k_max` - 1 is the largest sample index of the slice `p_max` - 1 for a + given input signal of `n` samples. + A detailed example is provided in the :ref:`tutorial_stft_sliding_win` + section of the :ref:`user_guide`. + + Parameters + ---------- + n : int + Number of samples of input signal (must be ≥ half of the window length). + + Returns + ------- + int + First sample index after signal end not touched by a time slice. + + See Also + -------- + k_min: The smallest possible signal index. + p_min: The smallest possible slice index. + p_max: Index of first non-overlapping upper time slice. + p_num: Number of time slices, i.e., `p_max` - `p_min`. + p_range: Determine and validate slice index range. + ShortTimeFFT: Class this method belongs to. + """ + return self._post_padding(n)[0] + + def p_max(self, n: int) -> int: + """Index of first non-overlapping upper time slice for `n` sample + input. + + Note that center point t[p_max] = (p_max(n)-1) * `delta_t` is typically + larger than last time index t[n-1] == (`n`-1) * `T`. The upper border + of samples indexes covered by the window slices is given by `k_max`. + Furthermore, `p_max` does not denote the number of slices `p_num` since + `p_min` is typically less than zero. + A detailed example is provided in the :ref:`tutorial_stft_sliding_win` + section of the :ref:`user_guide`. + + Parameters + ---------- + n : int + Number of samples of input. + + Returns + ------- + int + Index of first non-overlapping upper time slice for `n` sample input. + + See Also + -------- + k_min: The smallest possible signal index. + k_max: First sample index after signal end not touched by a time slice. + p_min: The smallest possible slice index. + p_num: Number of time slices, i.e., `p_max` - `p_min`. + p_range: Determine and validate slice index range. + ShortTimeFFT: Class this method belongs to. + """ + return self._post_padding(n)[1] + + def p_num(self, n: int) -> int: + """Number of time slices for an input signal with `n` samples. + + It is given by `p_num` = `p_max` - `p_min` with `p_min` typically + being negative. + A detailed example is provided in the :ref:`tutorial_stft_sliding_win` + section of the :ref:`user_guide`. + + Parameters + ---------- + n : int + Number of samples of input signal + + Returns + ------- + p_num : int + Number of time slices for input signal with `n` samples. + + See Also + -------- + k_min: The smallest possible signal index. + k_max: First sample index after signal end not touched by a time slice. + lower_border_end: Where pre-padding effects end. + p_min: The smallest possible slice index. + p_max: Index of first non-overlapping upper time slice. + p_range: Determine and validate slice index range. + upper_border_begin: Where post-padding effects start. + ShortTimeFFT: Class this method belongs to. + """ + return self.p_max(n) - self.p_min + + @property + def lower_border_end(self) -> tuple[int, int]: + """First signal index and first slice index unaffected by pre-padding. + + Describes the point where the window does not stick out to the left + of the signal domain. + A detailed example is provided in the :ref:`tutorial_stft_sliding_win` + section of the :ref:`user_guide`. + + See Also + -------- + k_min: The smallest possible signal index. + k_max: First sample index after signal end not touched by a time slice. + lower_border_end: Where pre-padding effects end. + p_min: The smallest possible slice index. + p_max: Index of first non-overlapping upper time slice. + p_num: Number of time slices, i.e., `p_max` - `p_min`. + p_range: Determine and validate slice index range. + upper_border_begin: Where post-padding effects start. + ShortTimeFFT: Class this property belongs to. + """ + if self._lower_border_end is not None: + return self._lower_border_end + + # first non-zero element in self.win: + m0 = np.flatnonzero(self.win.real**2 + self.win.imag**2)[0] + + # move window to the right until does not stick out to the left: + k0 = -self.m_num_mid + m0 + for q_, k_ in enumerate(range(k0, self.hop + 1, self.hop)): + if k_ + self.hop >= 0: # next entry does not stick out anymore + self._lower_border_end = (k_ + self.m_num, q_ + 1) + return self._lower_border_end + self._lower_border_end = (0, max(self.p_min, 0)) # ends at first slice + return self._lower_border_end + + def upper_border_begin(self, n: int) -> tuple[int, int]: + """First signal index and first slice index affected by post-padding. + + Describes the point where the window does begin stick out to the right + of the signal domain. + A detailed example is given :ref:`tutorial_stft_sliding_win` section + of the :ref:`user_guide`. + + Parameters + ---------- + n : int + Number of samples of input signal (must be ≥ half of the window length). + + Returns + ------- + k_ub : int + Lowest signal index, where a touching time slice sticks out past the + signal end. + p_ub : int + Lowest index of time slice of which the end sticks out past the signal end. + + See Also + -------- + k_min: The smallest possible signal index. + k_max: First sample index after signal end not touched by a time slice. + lower_border_end: Where pre-padding effects end. + p_min: The smallest possible slice index. + p_max: Index of first non-overlapping upper time slice. + p_num: Number of time slices, i.e., `p_max` - `p_min`. + p_range: Determine and validate slice index range. + ShortTimeFFT: Class this method belongs to. + + Notes + ----- + Note that the return values are cached together with the parameter `n` to avoid + unnecessary recalculations. + """ + if not (n >= (m2p := self.m_num - self.m_num_mid)): + raise ValueError(f"Parameter n must be >= ceil(m_num/2) = {m2p}!") + last_arg, last_return_value = self._cache_upper_border_begin + if n == last_arg: # use cached value: + return last_return_value + w2 = self.win.real**2 + self.win.imag**2 + q2 = n // self.hop + 1 # first t[q] >= t[n] + q1 = max((n-self.m_num) // self.hop - 1, -1) + # move window left until does not stick out to the right: + for q_ in range(q2, q1, -1): + k_ = q_ * self.hop + (self.m_num - self.m_num_mid) + if k_ <= n or all(w2[n-k_:] == 0): + return_value = (q_ + 1) * self.hop - self.m_num_mid, q_ + 1 + self. _cache_upper_border_begin = n, return_value + return return_value + # make linter happy: + raise RuntimeError("This code line should never run! Please file a bug.") + + @property + def delta_t(self) -> float: + """Time increment of STFT. + + The time increment `delta_t` = `T` * `hop` represents the sample + increment `hop` converted to time based on the sampling interval `T`. + + See Also + -------- + delta_f: Width of the frequency bins of the STFT. + hop: Hop size in signal samples for sliding window. + t: Times of STFT for an input signal with `n` samples. + T: Sampling interval of input signal and window `win`. + ShortTimeFFT: Class this property belongs to + """ + return self.T * self.hop + + def p_range(self, n: int, p0: int | None = None, + p1: int | None = None) -> tuple[int, int]: + """Determine and validate slice index range. + + Parameters + ---------- + n : int + Number of samples of input signal, assuming t[0] = 0. + p0 : int | None + First slice index. If 0 then the first slice is centered at t = 0. + If ``None`` then `p_min` is used. Note that p0 may be < 0 if + slices are left of t = 0. + p1 : int | None + End of interval (last value is p1-1). + If ``None`` then `p_max(n)` is used. + + + Returns + ------- + p0_ : int + The fist slice index + p1_ : int + End of interval (last value is p1-1). + + See Also + -------- + k_min: The smallest possible signal index. + k_max: First sample index after signal end not touched by a time slice. + lower_border_end: Where pre-padding effects end. + p_min: The smallest possible slice index. + p_max: Index of first non-overlapping upper time slice. + p_num: Number of time slices, i.e., `p_max` - `p_min`. + upper_border_begin: Where post-padding effects start. + ShortTimeFFT: Class this property belongs to. + + Notes + ----- + A ``ValueError`` is raised if ``p_min <= p0 < p1 <= p_max(n)`` does not + hold. + """ + p_max = self.p_max(n) # shorthand + p0_ = self.p_min if p0 is None else p0 + p1_ = p_max if p1 is None else p1 + if not (self.p_min <= p0_ < p1_ <= p_max): + raise ValueError(f"Invalid Parameter {p0=}, {p1=}, i.e., " + + f"{self.p_min=} <= p0 < p1 <= {p_max=} " + + f"does not hold for signal length {n=}!") + return p0_, p1_ + + def t(self, n: int, p0: int | None = None, p1: int | None = None, + k_offset: int = 0) -> np.ndarray: + """Times of STFT for an input signal with `n` samples. + + Returns a 1d array with times of the `~ShortTimeFFT.stft` values with + the same parametrization. Note that the slices are + ``delta_t = hop * T`` time units apart. + + Parameters + ---------- + n : int + Number of sample of the input signal. + p0 : int, optional + The first element of the range of slices to calculate. If ``None`` + then it is set to :attr:`p_min`, which is the smallest possible + slice. + p1 : int, optional + The end of the array. If ``None`` then `p_max(n)` is used. + k_offset : int + Index of first sample (t = 0) in `x`. + + Returns + ------- + t : ndarray + 1D array of times of the STFT slices from `p0` to `p1` - 1. + + See Also + -------- + delta_t: Time increment of STFT (``hop*T``) + hop: Time increment in signal samples for sliding window. + nearest_k_p: Nearest sample index k_p for which t[k_p] == t[p] holds. + T: Sampling interval of input signal and of the window (``1/fs``). + fs: Sampling frequency (being ``1/T``) + ShortTimeFFT: Class this method belongs to. + + Notes + ----- + Note that the returned array is cached together with the method's call + parameters to avoid unnecessary recalculations. + """ + if not (n > 0 and isinstance(n, int | np.integer)): + raise ValueError(f"Parameter {n=} is not a positive integer!") + args = n, p0, p1, k_offset, self.T # since `self.T` is mutable, it's needed too + last_args, last_return_value = self._cache_t + if args == last_args: # use cached value: + return last_return_value + + p0, p1 = self.p_range(n, p0, p1) + return_value = np.arange(p0, p1) * self.delta_t + k_offset * self.T + + self._cache_t = args, return_value + return return_value + + def nearest_k_p(self, k: int, left: bool = True) -> int: + """Return nearest sample index k_p for which ``t[k_p] == t[p]`` holds. + + The nearest next smaller time sample p (where ``t[p]`` is the center + position of the window of the p-th slice) is ``p_k = k // hop``. + If `hop` is a divisor of `k` then `k` is returned. + If `left` is set then ``p_k * hop`` is returned else ``(p_k+1) * hop``. + + This method can be used to slice an input signal into chunks for + calculating the STFT and iSTFT incrementally. + + Parameters + ---------- + k : int + Sample index for which the nearest slice center index is sought. + left : bool, optional + If ``True`` then the nearest smaller index is returned, else the + nearest larger index is returned. Defaults to ``True``. + + Returns + ------- + int + Nearest sample index k_p for which ``t[k_p] == t[p]`` holds. + + See Also + -------- + delta_t: Time increment of STFT (``hop*T``) + hop: Time increment in signal samples for sliding window. + T: Sampling interval of input signal and of the window (``1/fs``). + fs: Sampling frequency (being ``1/T``) + t: Times of STFT for an input signal with `n` samples. + ShortTimeFFT: Class this method belongs to. + """ + p_q, remainder = divmod(k, self.hop) + if remainder == 0: + return k + return p_q * self.hop if left else (p_q + 1) * self.hop + + @property + def delta_f(self) -> float: + """Width of the frequency bins of the STFT. + + Return the frequency interval `delta_f` = 1 / (`mfft` * `T`). + + See Also + -------- + delta_t: Time increment of STFT. + f_pts: Number of points along the frequency axis. + f: Frequencies values of the STFT. + mfft: Length of the input for FFT used. + T: Sampling interval. + t: Times of STFT for an input signal with `n` samples. + ShortTimeFFT: Class this property belongs to. + """ + return 1 / (self.mfft * self.T) + + @property + def f_pts(self) -> int: + """Number of points along the frequency axis. + + See Also + -------- + delta_f: Width of the frequency bins of the STFT. + f: Frequencies values of the STFT. + mfft: Length of the input for FFT used. + ShortTimeFFT: Class this property belongs to. + """ + return self.mfft // 2 + 1 if self.onesided_fft else self.mfft + + @property + def onesided_fft(self) -> bool: + """Return True if a one-sided FFT is used. + + Returns ``True`` if `fft_mode` is either 'onesided' or 'onesided2X'. + + See Also + -------- + fft_mode: Utilized FFT ('twosided', 'centered', 'onesided' or + 'onesided2X') + ShortTimeFFT: Class this property belongs to. + """ + return self.fft_mode in {'onesided', 'onesided2X'} + + @property + def f(self) -> np.ndarray: + """Frequencies values of the STFT. + + A 1d array of length `f_pts` with `delta_f` spaced entries is returned. + This array is calculated lazily. + + See Also + -------- + delta_f: Width of the frequency bins of the STFT. + f_pts: Number of points along the frequency axis. + mfft: Length of the input for FFT used. + ShortTimeFFT: Class this property belongs to. + """ + last_state, last_return_value = self._cache_f + current_state = self.fft_mode, self.mfft, self.T + if current_state == last_state: # use cached value: + return last_return_value + + if self.fft_mode in {'onesided', 'onesided2X'}: + return_value = fft_lib.rfftfreq(self.mfft, self.T) + elif self.fft_mode == 'twosided': + return_value = fft_lib.fftfreq(self.mfft, self.T) + elif self.fft_mode == 'centered': + return_value = fft_lib.fftshift(fft_lib.fftfreq(self.mfft, self.T)) + else: # This should never happen but makes the Linters happy: + fft_modes = get_args(FFT_MODE_TYPE) + raise RuntimeError(f"{self.fft_mode=} not in {fft_modes}!") + self._cache_f = current_state, return_value + return return_value + + def _fft_func(self, x: np.ndarray) -> np.ndarray: + """FFT based on the `fft_mode`, `mfft`, `scaling` and `phase_shift` + attributes. + + For multidimensional arrays the transformation is carried out on the + last axis. + """ + if self.phase_shift is not None: + if x.shape[-1] < self.mfft: # zero pad if needed + z_shape = list(x.shape) + z_shape[-1] = self.mfft - x.shape[-1] + x = np.hstack((x, np.zeros(z_shape, dtype=x.dtype))) + p_s = (self.phase_shift + self.m_num_mid) % self.m_num + x = np.roll(x, -p_s, axis=-1) + + if self.fft_mode == 'twosided': + return fft_lib.fft(x, n=self.mfft, axis=-1) + if self.fft_mode == 'centered': + return fft_lib.fftshift(fft_lib.fft(x, self.mfft, axis=-1), axes=-1) + if self.fft_mode == 'onesided': + return fft_lib.rfft(x, n=self.mfft, axis=-1) + if self.fft_mode == 'onesided2X': + X = fft_lib.rfft(x, n=self.mfft, axis=-1) + # Either squared magnitude (psd) or magnitude is doubled: + fac = np.sqrt(2) if self.scaling == 'psd' else 2 + # For even input length, the last entry is unpaired: + X[..., 1: -1 if self.mfft % 2 == 0 else None] *= fac + return X + # This should never happen but makes the Linter happy: + fft_modes = get_args(FFT_MODE_TYPE) + raise RuntimeError(f"{self.fft_mode=} not in {fft_modes}!") + + def _ifft_func(self, X: np.ndarray) -> np.ndarray: + """Inverse to `_fft_func`. + + Returned is an array of length `m_num`. If the FFT is `onesided` + then a float array is returned else a complex array is returned. + For multidimensional arrays the transformation is carried out on the + last axis. + """ + if self.fft_mode == 'twosided': + x = fft_lib.ifft(X, n=self.mfft, axis=-1) + elif self.fft_mode == 'centered': + x = fft_lib.ifft(fft_lib.ifftshift(X, axes=-1), n=self.mfft, axis=-1) + elif self.fft_mode == 'onesided': + x = fft_lib.irfft(X, n=self.mfft, axis=-1) + elif self.fft_mode == 'onesided2X': + Xc = X.copy() # we do not want to modify function parameters + fac = np.sqrt(2) if self.scaling == 'psd' else 2 + # For even length X the last value is not paired with a negative + # value on the two-sided FFT: + q1 = -1 if self.mfft % 2 == 0 else None + Xc[..., 1:q1] /= fac + x = fft_lib.irfft(Xc, n=self.mfft, axis=-1) + else: # This should never happen but makes the Linter happy: + raise RuntimeError(f"{self.fft_mode=} not in {get_args(FFT_MODE_TYPE)}!") + + if self.phase_shift is None: + return x[..., :self.m_num] + p_s = (self.phase_shift + self.m_num_mid) % self.m_num + return np.roll(x, p_s, axis=-1)[..., :self.m_num] + + def extent(self, n: int, axes_seq: Literal['tf', 'ft'] = 'tf', + center_bins: bool = False) -> tuple[float, float, float, float]: + """Return minimum and maximum values time-frequency values. + + Parameters + ---------- + n : int + Number of samples in input signal. + axes_seq : {'tf', 'ft'} + Return time extent first and then frequency extent or vice versa. + center_bins : bool + If set (default ``False``), the values of the time slots and + frequency bins are moved from the side the middle. This is useful, + when plotting the `~ShortTimeFFT.stft` values as step functions, + i.e., with no interpolation. + + Returns + ------- + tuple + A tuple with four floats ``(t0, t1, f0, f1)`` for 'tf' and + ``(f0, f1, t0, t1)`` for 'ft' is returned describing the corners + of the time-frequency domain of the `~ShortTimeFFT.stft`. + That tuple can be passed to `matplotlib.pyplot.imshow` as a parameter + with the same name. + + See Also + -------- + :func:`matplotlib.pyplot.imshow`: Display data as an image. + :class:`scipy.signal.ShortTimeFFT`: Class this method belongs to. + + Examples + -------- + The following two plots illustrate the effect of the parameter `center_bins`: + The grid lines represent the three time and the four frequency values of the + STFT. + The left plot, where ``(t0, t1, f0, f1) = (0, 3, 0, 4)`` is passed as parameter + ``extent`` to `~matplotlib.pyplot.imshow`, shows the standard behavior of the + time and frequency values being at the lower edge of the corrsponding bin. + The right plot, with ``(t0, t1, f0, f1) = (-0.5, 2.5, -0.5, 3.5)``, shows that + the bins are centered over the respective values when passing + ``center_bins=True``. + + >>> import matplotlib.pyplot as plt + >>> import numpy as np + >>> from scipy.signal import ShortTimeFFT + ... + >>> n, m = 12, 6 + >>> SFT = ShortTimeFFT.from_window('hann', fs=m, nperseg=m, noverlap=0) + >>> Sxx = SFT.stft(np.cos(np.arange(n))) # produces a colorful plot + ... + >>> fig, axx = plt.subplots(1, 2, tight_layout=True, figsize=(6., 4.)) + >>> for ax_, center_bins in zip(axx, (False, True)): + ... ax_.imshow(abs(Sxx), origin='lower', interpolation=None, aspect='equal', + ... cmap='viridis', extent=SFT.extent(n, 'tf', center_bins)) + ... ax_.set_title(f"{center_bins=}") + ... ax_.set_xlabel(f"Time ({SFT.p_num(n)} points, Δt={SFT.delta_t})") + ... ax_.set_ylabel(f"Frequency ({SFT.f_pts} points, Δf={SFT.delta_f})") + ... ax_.set_xticks(SFT.t(n)) # vertical grid line are timestamps + ... ax_.set_yticks(SFT.f) # horizontal grid line are frequency values + ... ax_.grid(True) + >>> plt.show() + + Note that the step-like behavior with the constant colors is caused by passing + ``interpolation=None`` to `~matplotlib.pyplot.imshow`. + """ + if axes_seq not in ('tf', 'ft'): + raise ValueError(f"Parameter {axes_seq=} not in ['tf', 'ft']!") + + if self.onesided_fft: + q0, q1 = 0, self.f_pts + elif self.fft_mode == 'centered': + q0 = -(self.mfft // 2) + q1 = self.mfft // 2 if self.mfft % 2 == 0 else self.mfft // 2 + 1 + else: + raise ValueError(f"Attribute fft_mode={self.fft_mode} must be " + + "in ['centered', 'onesided', 'onesided2X']") + + p0, p1 = self.p_min, self.p_max(n) # shorthand + if center_bins: + t0, t1 = self.delta_t * (p0 - 0.5), self.delta_t * (p1 - 0.5) + f0, f1 = self.delta_f * (q0 - 0.5), self.delta_f * (q1 - 0.5) + else: + t0, t1 = self.delta_t * p0, self.delta_t * p1 + f0, f1 = self.delta_f * q0, self.delta_f * q1 + return (t0, t1, f0, f1) if axes_seq == 'tf' else (f0, f1, t0, t1) diff --git a/venv/lib/python3.12/site-packages/scipy/signal/_signal_api.py b/venv/lib/python3.12/site-packages/scipy/signal/_signal_api.py new file mode 100644 index 0000000000000000000000000000000000000000..68d19aaf9be016666f20b78e97bebe8e7fd14d85 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/_signal_api.py @@ -0,0 +1,31 @@ +"""This is the 'bare' scipy.signal API. + +This --- private! --- module only collects implementations of public API +for _support_alternative_backends. +The latter --- also private! --- module adds delegation to CuPy etc and +re-exports decorated names to __init__.py +""" + +from . import _sigtools, windows # noqa: F401 +from ._waveforms import * # noqa: F403 +from ._max_len_seq import max_len_seq # noqa: F401 +from ._upfirdn import upfirdn # noqa: F401 + +from ._spline import sepfir2d # noqa: F401 + +from ._spline_filters import * # noqa: F403 +from ._filter_design import * # noqa: F403 +from ._fir_filter_design import * # noqa: F403 +from ._ltisys import * # noqa: F403 +from ._lti_conversion import * # noqa: F403 +from ._signaltools import * # noqa: F403 +from ._savitzky_golay import savgol_coeffs, savgol_filter # noqa: F401 +from ._spectral_py import * # noqa: F403 +from ._short_time_fft import * # noqa: F403 +from ._peak_finding import * # noqa: F403 +from ._czt import * # noqa: F403 +from ._whittaker import whittaker_henderson # noqa: F401 +from .windows import get_window # keep this one in signal namespace # noqa: F401 + + +__all__ = [s for s in dir() if not s.startswith('_')] diff --git a/venv/lib/python3.12/site-packages/scipy/signal/_signaltools.py b/venv/lib/python3.12/site-packages/scipy/signal/_signaltools.py new file mode 100644 index 0000000000000000000000000000000000000000..a230802509a329f5e431d63c8f3766557ab311e9 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/_signaltools.py @@ -0,0 +1,5369 @@ +# Author: Travis Oliphant +# 1999 -- 2002 + +import operator +import math +from math import prod as _prod +import timeit +import warnings +from typing import Literal + +from numpy._typing import ArrayLike + +from scipy.spatial import cKDTree +from . import _sigtools +from ._ltisys import dlti +from ._upfirdn import upfirdn, _output_len, _upfirdn_modes +from scipy import linalg, fft as sp_fft +from scipy import ndimage +from scipy.fft._helper import _init_nd_shape_and_axes +import numpy as np +from scipy.special import lambertw +from .windows import get_window +from ._arraytools import axis_slice, axis_reverse, odd_ext, even_ext, const_ext +from ._filter_design import cheby1, _validate_sos, zpk2sos +from ._fir_filter_design import firwin +from ._sosfilt import _sosfilt + +from scipy._lib._array_api import ( + array_namespace, is_torch, is_numpy, xp_copy, xp_size, xp_default_dtype, + xp_promote, xp_swapaxes,) +from scipy._external.array_api_compat import is_array_api_obj +import scipy._external.array_api_extra as xpx + + +__all__ = ['correlate', 'correlation_lags', 'correlate2d', + 'convolve', 'convolve2d', 'fftconvolve', 'oaconvolve', + 'order_filter', 'medfilt', 'medfilt2d', 'wiener', 'lfilter', + 'lfiltic', 'sosfilt', 'deconvolve', 'hilbert', 'hilbert2', 'envelope', + 'unique_roots', 'invres', 'invresz', 'residue', + 'residuez', 'resample', 'resample_poly', 'detrend', + 'lfilter_zi', 'sosfilt_zi', 'sosfiltfilt', 'choose_conv_method', + 'filtfilt', 'decimate', 'vectorstrength'] + + +_modedict = {'valid': 0, 'same': 1, 'full': 2} + +_boundarydict = {'fill': 0, 'pad': 0, 'wrap': 2, 'circular': 2, 'symm': 1, + 'symmetric': 1, 'reflect': 4} + + +def _valfrommode(mode): + try: + return _modedict[mode] + except KeyError as e: + raise ValueError("Acceptable mode flags are 'valid'," + " 'same', or 'full'.") from e + + +def _bvalfromboundary(boundary): + try: + return _boundarydict[boundary] << 2 + except KeyError as e: + raise ValueError("Acceptable boundary flags are 'fill', 'circular' " + "(or 'wrap'), and 'symmetric' (or 'symm').") from e + + +def _inputs_swap_needed(mode, shape1, shape2, axes=None): + """Determine if inputs arrays need to be swapped in `"valid"` mode. + + If in `"valid"` mode, returns whether or not the input arrays need to be + swapped depending on whether `shape1` is at least as large as `shape2` in + every calculated dimension. + + This is important for some of the correlation and convolution + implementations in this module, where the larger array input needs to come + before the smaller array input when operating in this mode. + + Note that if the mode provided is not 'valid', False is immediately + returned. + + """ + if mode != 'valid': + return False + + if not shape1: + return False + + if axes is None: + axes = range(len(shape1)) + + ok1 = all(shape1[i] >= shape2[i] for i in axes) + ok2 = all(shape2[i] >= shape1[i] for i in axes) + + if not (ok1 or ok2): + raise ValueError("For 'valid' mode, one must be at least " + "as large as the other in every dimension") + + return not ok1 + + +def correlate(in1, in2, mode='full', method='auto'): + r""" + Cross-correlate two N-dimensional arrays. + + Cross-correlate `in1` and `in2`, with the output size determined by the + `mode` argument. + + Parameters + ---------- + in1 : array_like + First input. + in2 : array_like + Second input. Should have the same number of dimensions as `in1`. + mode : str {'full', 'valid', 'same'}, optional + A string indicating the size of the output: + + ``full`` + The output is the full discrete linear cross-correlation + of the inputs. (Default) + ``valid`` + The output consists only of those elements that do not + rely on the zero-padding. In 'valid' mode, either `in1` or `in2` + must be at least as large as the other in every dimension. + ``same`` + Output size will be ``N``, the same size as `in1`, centered + with respect to the 'full' output. + Boundary effects may be visible. + method : str {'auto', 'direct', 'fft'}, optional + A string indicating which method to use to calculate the correlation. + + ``direct`` + The correlation is determined directly from sums, the definition of + correlation. + ``fft`` + The Fast Fourier Transform is used to perform the correlation more + quickly (only available for numerical arrays.) + ``auto`` + Automatically chooses direct or Fourier method based on an estimate + of which is faster (default). See `convolve` Notes for more detail. + + .. versionadded:: 0.19.0 + + Returns + ------- + correlate : array + An N-dimensional array containing a subset of the discrete linear + cross-correlation of `in1` with `in2`. + + See Also + -------- + choose_conv_method : contains more documentation on `method`. + correlation_lags : calculates the lag / displacement indices array for 1D + cross-correlation. + + Notes + ----- + The correlation z of two d-dimensional arrays x and y is defined as:: + + z[...,k,...] = sum[..., i_l, ...] x[..., i_l,...] * conj(y[..., i_l - k,...]) + + This way, if ``x`` and ``y`` are 1-D arrays and ``z = correlate(x, y, 'full')`` + then + + .. math:: + + z[k] = \sum_{l=0}^{N-1} x_l \, y_{l-k}^{*} + + for :math:`k = -(M-1), \dots, (N-1)`, where :math:`N` is the length of ``x``, + :math:`M` is the length of ``y``, and :math:`y_m = 0` when :math:`m` is outside the + valid range :math:`[0, M-1]`. The size of :math:`z` is :math:`N + M - 1` and + :math:`y^*` denotes the complex conjugate of :math:`y`. + + ``method='fft'`` only works for numerical arrays as it relies on + `fftconvolve`. In certain cases (i.e., arrays of objects or when + rounding integers can lose precision), ``method='direct'`` is always used. + + When using ``mode='same'`` with even-length inputs, the outputs of `correlate` + and `correlate2d` differ: There is a 1-index offset between them. + + Examples + -------- + Implement a matched filter using cross-correlation, to recover a signal + that has passed through a noisy channel. + + >>> import numpy as np + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + >>> rng = np.random.default_rng() + + >>> sig = np.repeat([0., 1., 1., 0., 1., 0., 0., 1.], 128) + >>> sig_noise = sig + rng.standard_normal(len(sig)) + >>> corr = signal.correlate(sig_noise, np.ones(128), mode='same') / 128 + + >>> clock = np.arange(64, len(sig), 128) + >>> fig, (ax_orig, ax_noise, ax_corr) = plt.subplots(3, 1, sharex=True) + >>> ax_orig.plot(sig) + >>> ax_orig.plot(clock, sig[clock], 'ro') + >>> ax_orig.set_title('Original signal') + >>> ax_noise.plot(sig_noise) + >>> ax_noise.set_title('Signal with noise') + >>> ax_corr.plot(corr) + >>> ax_corr.plot(clock, corr[clock], 'ro') + >>> ax_corr.axhline(0.5, ls=':') + >>> ax_corr.set_title('Cross-correlated with rectangular pulse') + >>> ax_orig.margins(0, 0.1) + >>> fig.tight_layout() + >>> plt.show() + + Compute the cross-correlation of a noisy signal with the original signal. + + >>> x = np.arange(128) / 128 + >>> sig = np.sin(2 * np.pi * x) + >>> sig_noise = sig + rng.standard_normal(len(sig)) + >>> corr = signal.correlate(sig_noise, sig) + >>> lags = signal.correlation_lags(len(sig), len(sig_noise)) + >>> corr /= np.max(corr) + + >>> fig, (ax_orig, ax_noise, ax_corr) = plt.subplots(3, 1, figsize=(4.8, 4.8)) + >>> ax_orig.plot(sig) + >>> ax_orig.set_title('Original signal') + >>> ax_orig.set_xlabel('Sample Number') + >>> ax_noise.plot(sig_noise) + >>> ax_noise.set_title('Signal with noise') + >>> ax_noise.set_xlabel('Sample Number') + >>> ax_corr.plot(lags, corr) + >>> ax_corr.set_title('Cross-correlated signal') + >>> ax_corr.set_xlabel('Lag') + >>> ax_orig.margins(0, 0.1) + >>> ax_noise.margins(0, 0.1) + >>> ax_corr.margins(0, 0.1) + >>> fig.tight_layout() + >>> plt.show() + + """ + xp = array_namespace(in1, in2) + + in1 = xp.asarray(in1) + in2 = xp.asarray(in2) + + if in1.ndim == in2.ndim == 0: + in2_conj = (xp.conj(in2) + if xp.isdtype(in2.dtype, 'complex floating') + else in2) + return in1 * in2_conj + elif in1.ndim != in2.ndim: + raise ValueError("in1 and in2 should have the same dimensionality") + + # Don't use _valfrommode, since correlate should not accept numeric modes + try: + val = _modedict[mode] + except KeyError as e: + raise ValueError("Acceptable mode flags are 'valid'," + " 'same', or 'full'.") from e + + # this either calls fftconvolve or this function with method=='direct' + if method in ('fft', 'auto'): + return convolve(in1, _reverse_and_conj(in2, xp), mode, method) + + elif method == 'direct': + # fastpath to faster numpy.correlate for 1d inputs when possible + if _np_conv_ok(in1, in2, mode, xp): + a_in1 = np.asarray(in1) + a_in2 = np.asarray(in2) + out = np.correlate(a_in1, a_in2, mode) + return xp.asarray(out) + + # _correlateND is far slower when in2.size > in1.size, so swap them + # and then undo the effect afterward if mode == 'full'. Also, it fails + # with 'valid' mode if in2 is larger than in1, so swap those, too. + # Don't swap inputs for 'same' mode, since shape of in1 matters. + swapped_inputs = ((mode == 'full') and (xp_size(in2) > xp_size(in1)) or + _inputs_swap_needed(mode, in1.shape, in2.shape)) + + if swapped_inputs: + in1, in2 = in2, in1 + + # convert to numpy & back for _sigtools._correlateND + a_in1 = np.asarray(in1) + a_in2 = np.asarray(in2) + + if mode == 'valid': + ps = [i - j + 1 for i, j in zip(in1.shape, in2.shape)] + out = np.empty(ps, a_in1.dtype) + + z = _sigtools._correlateND(a_in1, a_in2, out, val) + + else: + ps = [i + j - 1 for i, j in zip(in1.shape, in2.shape)] + + # zero pad input + in1zpadded = np.zeros(ps, a_in1.dtype) + sc = tuple(slice(0, i) for i in in1.shape) + in1zpadded[sc] = a_in1.copy() + + if mode == 'full': + out = np.empty(ps, a_in1.dtype) + elif mode == 'same': + out = np.empty(in1.shape, a_in1.dtype) + + z = _sigtools._correlateND(in1zpadded, a_in2, out, val) + + z = xp.asarray(z) + + if swapped_inputs: + # Reverse and conjugate to undo the effect of swapping inputs + z = _reverse_and_conj(z, xp) + + return z + + else: + raise ValueError("Acceptable method flags are 'auto'," + " 'direct', or 'fft'.") + + +def correlation_lags(in1_len, in2_len, mode='full'): + r""" + Calculates the lag / displacement indices array for 1D cross-correlation. + + Parameters + ---------- + in1_len : int + First input size. + in2_len : int + Second input size. + mode : str {'full', 'valid', 'same'}, optional + A string indicating the size of the output. + See the documentation `correlate` for more information. + + Returns + ------- + lags : array + Returns an array containing cross-correlation lag/displacement indices. + Indices can be indexed with the np.argmax of the correlation to return + the lag/displacement. + + See Also + -------- + correlate : Compute the N-dimensional cross-correlation. + + Notes + ----- + Cross-correlation for continuous functions :math:`f` and :math:`g` is + defined as: + + .. math:: + + \left ( f\star g \right )\left ( \tau \right ) + \triangleq \int_{t_0}^{t_0 +T} + \overline{f\left ( t \right )}g\left ( t+\tau \right )dt + + Where :math:`\tau` is defined as the displacement, also known as the lag. + + Cross correlation for discrete functions :math:`f` and :math:`g` is + defined as: + + .. math:: + \left ( f\star g \right )\left [ n \right ] + \triangleq \sum_{-\infty}^{\infty} + \overline{f\left [ m \right ]}g\left [ m+n \right ] + + Where :math:`n` is the lag. + + Examples + -------- + Cross-correlation of a signal with its time-delayed self. + + >>> import numpy as np + >>> from scipy import signal + >>> rng = np.random.default_rng() + >>> x = rng.standard_normal(1000) + >>> y = np.concatenate([rng.standard_normal(100), x]) + >>> correlation = signal.correlate(x, y, mode="full") + >>> lags = signal.correlation_lags(x.size, y.size, mode="full") + >>> lag = lags[np.argmax(correlation)] + """ + + # calculate lag ranges in different modes of operation + if mode == "full": + # the output is the full discrete linear convolution + # of the inputs. (Default) + lags = np.arange(-in2_len + 1, in1_len) + elif mode == "same": + # the output is the same size as `in1`, centered + # with respect to the 'full' output. + # calculate the full output + lags = np.arange(-in2_len + 1, in1_len) + # determine the midpoint in the full output + mid = lags.size // 2 + # determine lag_bound to be used with respect + # to the midpoint + lag_bound = in1_len // 2 + # calculate lag ranges for even and odd scenarios + if in1_len % 2 == 0: + lags = lags[(mid-lag_bound):(mid+lag_bound)] + else: + lags = lags[(mid-lag_bound):(mid+lag_bound)+1] + elif mode == "valid": + # the output consists only of those elements that do not + # rely on the zero-padding. In 'valid' mode, either `in1` or `in2` + # must be at least as large as the other in every dimension. + + # the lag_bound will be either negative or positive + # this let's us infer how to present the lag range + lag_bound = in1_len - in2_len + if lag_bound >= 0: + lags = np.arange(lag_bound + 1) + else: + lags = np.arange(lag_bound, 1) + else: + raise ValueError(f"Mode {mode} is invalid") + return lags + + +def _centered(arr, newshape): + # Return the center newshape portion of the array. + newshape = np.asarray(newshape) + currshape = np.array(arr.shape) + startind = (currshape - newshape) // 2 + endind = startind + newshape + myslice = [slice(startind[k], endind[k]) for k in range(len(endind))] + return arr[tuple(myslice)] + + +def _init_freq_conv_axes(in1, in2, mode, axes, sorted_axes=False): + """Handle the axes argument for frequency-domain convolution. + + Returns the inputs and axes in a standard form, eliminating redundant axes, + swapping the inputs if necessary, and checking for various potential + errors. + + Parameters + ---------- + in1 : array + First input. + in2 : array + Second input. + mode : str {'full', 'valid', 'same'}, optional + A string indicating the size of the output. + See the documentation `fftconvolve` for more information. + axes : list of ints + Axes over which to compute the FFTs. + sorted_axes : bool, optional + If `True`, sort the axes. + Default is `False`, do not sort. + + Returns + ------- + in1 : array + The first input, possible swapped with the second input. + in2 : array + The second input, possible swapped with the first input. + axes : list of ints + Axes over which to compute the FFTs. + + """ + s1 = in1.shape + s2 = in2.shape + noaxes = axes is None + + _, axes = _init_nd_shape_and_axes(in1, shape=None, axes=axes) + + if not noaxes and not len(axes): + raise ValueError("when provided, axes cannot be empty") + + # Axes of length 1 can rely on broadcasting rules for multiply, + # no fft needed. + axes = [a for a in axes if s1[a] != 1 and s2[a] != 1] + + if sorted_axes: + axes.sort() + + if not all(s1[a] == s2[a] or s1[a] == 1 or s2[a] == 1 + for a in range(in1.ndim) if a not in axes): + raise ValueError("incompatible shapes for in1 and in2:" + f" {s1} and {s2}") + + # Check that input sizes are compatible with 'valid' mode. + if _inputs_swap_needed(mode, s1, s2, axes=axes): + # Convolution is commutative; order doesn't have any effect on output. + in1, in2 = in2, in1 + + return in1, in2, axes + + +def _freq_domain_conv(xp, in1, in2, axes, shape, calc_fast_len=False): + """Convolve two arrays in the frequency domain. + + This function implements only base the FFT-related operations. + Specifically, it converts the signals to the frequency domain, multiplies + them, then converts them back to the time domain. Calculations of axes, + shapes, convolution mode, etc. are implemented in higher level-functions, + such as `fftconvolve` and `oaconvolve`. Those functions should be used + instead of this one. + + Parameters + ---------- + in1 : array_like + First input. + in2 : array_like + Second input. Should have the same number of dimensions as `in1`. + axes : array_like of ints + Axes over which to compute the FFTs. + shape : array_like of ints + The sizes of the FFTs. + calc_fast_len : bool, optional + If `True`, set each value of `shape` to the next fast FFT length. + Default is `False`, use `axes` as-is. + + Returns + ------- + out : array + An N-dimensional array containing the discrete linear convolution of + `in1` with `in2`. + + """ + if not len(axes): + return in1 * in2 + + complex_result = (xp.isdtype(in1.dtype, 'complex floating') or + xp.isdtype(in2.dtype, 'complex floating')) + + if calc_fast_len: + # Speed up FFT by padding to optimal size. + fshape = [ + sp_fft.next_fast_len(shape[a], not complex_result) for a in axes] + else: + fshape = shape + + if not complex_result: + fft, ifft = sp_fft.rfftn, sp_fft.irfftn + else: + fft, ifft = sp_fft.fftn, sp_fft.ifftn + + if xp.isdtype(in1.dtype, 'integral'): + in1 = xp.astype(in1, xp_default_dtype(xp)) + if xp.isdtype(in2.dtype, 'integral'): + in2 = xp.astype(in2, xp_default_dtype(xp)) + + sp1 = fft(in1, fshape, axes=axes) + sp2 = fft(in2, fshape, axes=axes) + + ret = ifft(sp1 * sp2, fshape, axes=axes) + + if calc_fast_len: + fslice = tuple([slice(sz) for sz in shape]) + ret = ret[fslice] + + return ret + + +def _apply_conv_mode(ret, s1, s2, mode, axes, xp): + """Calculate the convolution result shape based on the `mode` argument. + + Returns the result sliced to the correct size for the given mode. + + Parameters + ---------- + ret : array + The result array, with the appropriate shape for the 'full' mode. + s1 : list of int + The shape of the first input. + s2 : list of int + The shape of the second input. + mode : str {'full', 'valid', 'same'} + A string indicating the size of the output. + See the documentation `fftconvolve` for more information. + axes : list of ints + Axes over which to compute the convolution. + + Returns + ------- + ret : array + A copy of `res`, sliced to the correct size for the given `mode`. + + """ + if mode == "full": + return xp_copy(ret, xp=xp) + elif mode == "same": + return xp_copy(_centered(ret, s1), xp=xp) + elif mode == "valid": + shape_valid = [ret.shape[a] if a not in axes else s1[a] - s2[a] + 1 + for a in range(ret.ndim)] + return xp_copy(_centered(ret, shape_valid), xp=xp) + else: + raise ValueError("acceptable mode flags are 'valid'," + " 'same', or 'full'") + + +def fftconvolve(in1, in2, mode="full", axes=None): + """Convolve two N-dimensional arrays using FFT. + + Convolve `in1` and `in2` using the fast Fourier transform method, with + the output size determined by the `mode` argument. + + This is generally much faster than `convolve` for large arrays (n > ~500), + but can be slower when only a few output values are needed, and can only + output float arrays (int or object array inputs will be cast to float). + + As of v0.19, `convolve` automatically chooses this method or the direct + method based on an estimation of which is faster. + + Parameters + ---------- + in1 : array_like + First input. + in2 : array_like + Second input. Should have the same number of dimensions as `in1`. + mode : str {'full', 'valid', 'same'}, optional + A string indicating the size of the output: + + ``full`` + The output is the full discrete linear convolution + of the inputs. (Default) + ``valid`` + The output consists only of those elements that do not + rely on the zero-padding. In 'valid' mode, either `in1` or `in2` + must be at least as large as the other in every dimension. + ``same`` + Output size will be ``N``, the same size as `in1`, centered + with respect to the 'full' output. + Boundary effects may be visible. + axes : int or array_like of ints or None, optional + Axes over which to compute the convolution. + The default is over all axes. + + Returns + ------- + out : array + An N-dimensional array containing a subset of the discrete linear + convolution of `in1` with `in2`. + + The output size along each axis depends on ``mode``. For input + sizes ``N`` and ``M`` along a given axis: + + - ``full`` : ``N + M - 1`` + - ``same`` : ``N`` (same as the size of `in1`) + - ``valid`` : ``max(N, M) - min(N, M) + 1`` + + See Also + -------- + convolve : Uses the direct convolution or FFT convolution algorithm + depending on which is faster. + oaconvolve : Uses the overlap-add method to do convolution, which is + generally faster when the input arrays are large and + significantly different in size. + + Examples + -------- + Autocorrelation of white noise is an impulse. + + >>> import numpy as np + >>> from scipy import signal + >>> rng = np.random.default_rng() + >>> sig = rng.standard_normal(1000) + >>> autocorr = signal.fftconvolve(sig, sig[::-1], mode='full') + + >>> import matplotlib.pyplot as plt + >>> fig, (ax_orig, ax_mag) = plt.subplots(2, 1) + >>> ax_orig.plot(sig) + >>> ax_orig.set_title('White noise') + >>> ax_mag.plot(np.arange(-len(sig)+1,len(sig)), autocorr) + >>> ax_mag.set_title('Autocorrelation') + >>> fig.tight_layout() + >>> fig.show() + + Gaussian blur implemented using FFT convolution. Notice the dark borders + around the image, due to the zero-padding beyond its boundaries. + The `convolve2d` function allows for other types of image boundaries, + but is far slower. + + >>> from scipy import datasets + >>> face = datasets.face(gray=True) + >>> kernel = np.outer(signal.windows.gaussian(70, 8), + ... signal.windows.gaussian(70, 8)) + >>> blurred = signal.fftconvolve(face, kernel, mode='same') + + >>> fig, (ax_orig, ax_kernel, ax_blurred) = plt.subplots(3, 1, + ... figsize=(6, 15)) + >>> ax_orig.imshow(face, cmap='gray') + >>> ax_orig.set_title('Original') + >>> ax_orig.set_axis_off() + >>> ax_kernel.imshow(kernel, cmap='gray') + >>> ax_kernel.set_title('Gaussian kernel') + >>> ax_kernel.set_axis_off() + >>> ax_blurred.imshow(blurred, cmap='gray') + >>> ax_blurred.set_title('Blurred') + >>> ax_blurred.set_axis_off() + >>> fig.show() + + """ + xp = array_namespace(in1, in2) + + in1 = xp.asarray(in1) + in2 = xp.asarray(in2) + + if in1.ndim == in2.ndim == 0: # scalar inputs + return in1 * in2 + elif in1.ndim != in2.ndim: + raise ValueError("in1 and in2 should have the same dimensionality") + elif xp_size(in1) == 0 or xp_size(in2) == 0: # empty arrays + return xp.asarray([]) + + in1, in2, axes = _init_freq_conv_axes(in1, in2, mode, axes, + sorted_axes=False) + + s1 = in1.shape + s2 = in2.shape + + shape = [max((s1[i], s2[i])) if i not in axes else s1[i] + s2[i] - 1 + for i in range(in1.ndim)] + + ret = _freq_domain_conv(xp, in1, in2, axes, shape, calc_fast_len=True) + + return _apply_conv_mode(ret, s1, s2, mode, axes, xp=xp) + + +def _calc_oa_lens(s1, s2): + """Calculate the optimal FFT lengths for overlap-add convolution. + + The calculation is done for a single dimension. + + Parameters + ---------- + s1 : int + Size of the dimension for the first array. + s2 : int + Size of the dimension for the second array. + + Returns + ------- + block_size : int + The size of the FFT blocks. + overlap : int + The amount of overlap between two blocks. + in1_step : int + The size of each step for the first array. + in2_step : int + The size of each step for the second array. + + """ + # Set up the arguments for the conventional FFT approach. + fallback = (s1+s2-1, None, s1, s2) + + # Use conventional FFT convolve if sizes are same. + if s1 == s2 or s1 == 1 or s2 == 1: + return fallback + + if s2 > s1: + s1, s2 = s2, s1 + swapped = True + else: + swapped = False + + # There cannot be a useful block size if s2 is more than half of s1. + if s2 >= s1/2: + return fallback + + # Derivation of optimal block length + # For original formula see: + # https://en.wikipedia.org/wiki/Overlap-add_method + # + # Formula: + # K = overlap = s2-1 + # N = block_size + # C = complexity + # e = exponential, exp(1) + # + # C = (N*(log2(N)+1))/(N-K) + # C = (N*log2(2N))/(N-K) + # C = N/(N-K) * log2(2N) + # C1 = N/(N-K) + # C2 = log2(2N) = ln(2N)/ln(2) + # + # dC1/dN = (1*(N-K)-N)/(N-K)^2 = -K/(N-K)^2 + # dC2/dN = 2/(2*N*ln(2)) = 1/(N*ln(2)) + # + # dC/dN = dC1/dN*C2 + dC2/dN*C1 + # dC/dN = -K*ln(2N)/(ln(2)*(N-K)^2) + N/(N*ln(2)*(N-K)) + # dC/dN = -K*ln(2N)/(ln(2)*(N-K)^2) + 1/(ln(2)*(N-K)) + # dC/dN = -K*ln(2N)/(ln(2)*(N-K)^2) + (N-K)/(ln(2)*(N-K)^2) + # dC/dN = (-K*ln(2N) + (N-K)/(ln(2)*(N-K)^2) + # dC/dN = (N - K*ln(2N) - K)/(ln(2)*(N-K)^2) + # + # Solve for minimum, where dC/dN = 0 + # 0 = (N - K*ln(2N) - K)/(ln(2)*(N-K)^2) + # 0 * ln(2)*(N-K)^2 = N - K*ln(2N) - K + # 0 = N - K*ln(2N) - K + # 0 = N - K*(ln(2N) + 1) + # 0 = N - K*ln(2Ne) + # N = K*ln(2Ne) + # N/K = ln(2Ne) + # + # e^(N/K) = e^ln(2Ne) + # e^(N/K) = 2Ne + # 1/e^(N/K) = 1/(2*N*e) + # e^(N/-K) = 1/(2*N*e) + # e^(N/-K) = K/N*1/(2*K*e) + # N/K*e^(N/-K) = 1/(2*e*K) + # N/-K*e^(N/-K) = -1/(2*e*K) + # + # Using Lambert W function + # https://en.wikipedia.org/wiki/Lambert_W_function + # x = W(y) It is the solution to y = x*e^x + # x = N/-K + # y = -1/(2*e*K) + # + # N/-K = W(-1/(2*e*K)) + # + # N = -K*W(-1/(2*e*K)) + overlap = s2-1 + opt_size = -overlap*lambertw(-1/(2*math.e*overlap), k=-1).real + block_size = sp_fft.next_fast_len(math.ceil(opt_size)) + + # Use conventional FFT convolve if there is only going to be one block. + if block_size >= s1: + return fallback + + if not swapped: + in1_step = block_size-s2+1 + in2_step = s2 + else: + in1_step = s2 + in2_step = block_size-s2+1 + + return block_size, overlap, in1_step, in2_step + + +# may want to look at moving xp_swapaxes and this to array-api-extra, +# cross-ref https://github.com/data-apis/array-api-extra/issues/97 +def _split(x, indices_or_sections, axis, xp): + """A simplified version of np.split, with `indices` being a list. + """ + # https://github.com/numpy/numpy/blob/v2.2.0/numpy/lib/_shape_base_impl.py#L743 + Ntotal = x.shape[axis] + + # handle array case. + Nsections = len(indices_or_sections) + 1 + div_points = [0] + list(indices_or_sections) + [Ntotal] + + sub_arys = [] + sary = xp_swapaxes(x, axis, 0, xp=xp) + for i in range(Nsections): + st = div_points[i] + end = div_points[i + 1] + sub_arys.append(xp_swapaxes(sary[st:end, ...], axis, 0, xp=xp)) + + return sub_arys + + +def oaconvolve(in1, in2, mode="full", axes=None): + """Convolve two N-dimensional arrays using the overlap-add method. + + Convolve `in1` and `in2` using the overlap-add method, with + the output size determined by the `mode` argument. + + This is generally much faster than `convolve` for large arrays (n > ~500), + and generally much faster than `fftconvolve` when one array is much + larger than the other, but can be slower when only a few output values are + needed or when the arrays are very similar in shape, and can only + output float arrays (int or object array inputs will be cast to float). + + Parameters + ---------- + in1 : array_like + First input. + in2 : array_like + Second input. Should have the same number of dimensions as `in1`. + mode : str {'full', 'valid', 'same'}, optional + A string indicating the size of the output: + + ``full`` + The output is the full discrete linear convolution + of the inputs. (Default) + ``valid`` + The output consists only of those elements that do not + rely on the zero-padding. In 'valid' mode, either `in1` or `in2` + must be at least as large as the other in every dimension. + ``same`` + Output size will be ``N``, the same size as `in1`, centered + with respect to the 'full' output. + Boundary effects may be visible. + axes : int or array_like of ints or None, optional + Axes over which to compute the convolution. + The default is over all axes. + + Returns + ------- + out : array + An N-dimensional array containing a subset of the discrete linear + convolution of `in1` with `in2`. + + The output size along each axis depends on ``mode``. For input + sizes ``N`` and ``M`` along a given axis: + + - ``full`` : ``N + M - 1`` + - ``same`` : ``N`` (same as the size of `in1`) + - ``valid`` : ``max(N, M) - min(N, M) + 1`` + + See Also + -------- + convolve : Uses the direct convolution or FFT convolution algorithm + depending on which is faster. + fftconvolve : An implementation of convolution using FFT. + + Notes + ----- + .. versionadded:: 1.4.0 + + References + ---------- + .. [1] Wikipedia, "Overlap-add_method". + https://en.wikipedia.org/wiki/Overlap-add_method + .. [2] Richard G. Lyons. Understanding Digital Signal Processing, + Third Edition, 2011. Chapter 13.10. + ISBN 13: 978-0137-02741-5 + + Examples + -------- + Convolve a 100,000 sample signal with a 512-sample filter. + + >>> import numpy as np + >>> from scipy import signal + >>> rng = np.random.default_rng() + >>> sig = rng.standard_normal(100000) + >>> filt = signal.firwin(512, 0.01) + >>> fsig = signal.oaconvolve(sig, filt) + + >>> import matplotlib.pyplot as plt + >>> fig, (ax_orig, ax_mag) = plt.subplots(2, 1) + >>> ax_orig.plot(sig) + >>> ax_orig.set_title('White noise') + >>> ax_mag.plot(fsig) + >>> ax_mag.set_title('Filtered noise') + >>> fig.tight_layout() + >>> fig.show() + + """ + xp = array_namespace(in1, in2) + + in1 = xp.asarray(in1) + in2 = xp.asarray(in2) + + if in1.ndim == in2.ndim == 0: # scalar inputs + return in1 * in2 + elif in1.ndim != in2.ndim: + raise ValueError("in1 and in2 should have the same dimensionality") + elif in1.size == 0 or in2.size == 0: # empty arrays + return xp.asarray([]) + elif in1.shape == in2.shape: # Equivalent to fftconvolve + return fftconvolve(in1, in2, mode=mode, axes=axes) + + in1, in2, axes = _init_freq_conv_axes(in1, in2, mode, axes, + sorted_axes=True) + + s1 = in1.shape + s2 = in2.shape + + if not axes: + ret = in1 * in2 + return _apply_conv_mode(ret, s1, s2, mode, axes, xp) + + # Calculate this now since in1 is changed later + shape_final = [None if i not in axes else + s1[i] + s2[i] - 1 for i in range(in1.ndim)] + + # Calculate the block sizes for the output, steps, first and second inputs. + # It is simpler to calculate them all together than doing them in separate + # loops due to all the special cases that need to be handled. + optimal_sizes = ((-1, -1, s1[i], s2[i]) if i not in axes else + _calc_oa_lens(s1[i], s2[i]) for i in range(in1.ndim)) + block_size, overlaps, \ + in1_step, in2_step = zip(*optimal_sizes) + + # Fall back to fftconvolve if there is only one block in every dimension. + if in1_step == s1 and in2_step == s2: + return fftconvolve(in1, in2, mode=mode, axes=axes) + + # Figure out the number of steps and padding. + # This would get too complicated in a list comprehension. + nsteps1 = [] + nsteps2 = [] + pad_size1 = [] + pad_size2 = [] + for i in range(in1.ndim): + if i not in axes: + pad_size1 += [(0, 0)] + pad_size2 += [(0, 0)] + continue + + if s1[i] > in1_step[i]: + curnstep1 = math.ceil((s1[i]+1)/in1_step[i]) + if (block_size[i] - overlaps[i])*curnstep1 < shape_final[i]: + curnstep1 += 1 + + curpad1 = curnstep1*in1_step[i] - s1[i] + else: + curnstep1 = 1 + curpad1 = 0 + + if s2[i] > in2_step[i]: + curnstep2 = math.ceil((s2[i]+1)/in2_step[i]) + if (block_size[i] - overlaps[i])*curnstep2 < shape_final[i]: + curnstep2 += 1 + + curpad2 = curnstep2*in2_step[i] - s2[i] + else: + curnstep2 = 1 + curpad2 = 0 + + nsteps1 += [curnstep1] + nsteps2 += [curnstep2] + pad_size1 += [(0, curpad1)] + pad_size2 += [(0, curpad2)] + + # Pad the array to a size that can be reshaped to the desired shape + # if necessary. + if not all(curpad == (0, 0) for curpad in pad_size1): + in1 = xpx.pad(in1, pad_size1, mode='constant', constant_values=0, xp=xp) + + if not all(curpad == (0, 0) for curpad in pad_size2): + in2 = xpx.pad(in2, pad_size2, mode='constant', constant_values=0, xp=xp) + + # Reshape the overlap-add parts to input block sizes. + split_axes = [iax+i for i, iax in enumerate(axes)] + fft_axes = [iax+1 for iax in split_axes] + + # We need to put each new dimension before the corresponding dimension + # being reshaped in order to get the data in the right layout at the end. + reshape_size1 = list(in1_step) + reshape_size2 = list(in2_step) + for i, iax in enumerate(split_axes): + reshape_size1.insert(iax, nsteps1[i]) + reshape_size2.insert(iax, nsteps2[i]) + + in1 = xp.reshape(in1, tuple(reshape_size1)) + in2 = xp.reshape(in2, tuple(reshape_size2)) + + # Do the convolution. + fft_shape = [block_size[i] for i in axes] + ret = _freq_domain_conv(xp, in1, in2, fft_axes, fft_shape, calc_fast_len=False) + + # Do the overlap-add. + for ax, ax_fft, ax_split in zip(axes, fft_axes, split_axes): + overlap = overlaps[ax] + if overlap is None: + continue + + ret, overpart = _split(ret, [-overlap], ax_fft, xp=xp) + overpart = _split(overpart, [-1], ax_split, xp=xp)[0] + + overlap_slice = [slice(None)] * ret.ndim + overlap_slice[ax_fft] = slice(0, overlap) + overlap_slice[ax_split] = slice(1, None) + ret = xpx.at(ret)[tuple(overlap_slice)].add(overpart) + + # Reshape back to the correct dimensionality. + shape_ret = [ret.shape[i] if i not in fft_axes else + ret.shape[i]*ret.shape[i-1] + for i in range(ret.ndim) if i not in split_axes] + ret = xp.reshape(ret, tuple(shape_ret)) + + # Slice to the correct size. + slice_final = tuple([slice(islice) for islice in shape_final]) + ret = ret[slice_final] + + return _apply_conv_mode(ret, s1, s2, mode, axes, xp) + + +def _numeric_arrays(arrays, kinds='buifc', xp=None): + """ + See if a list of arrays are all numeric. + + Parameters + ---------- + arrays : array or list of arrays + arrays to check if numeric. + kinds : string-like + The dtypes of the arrays to be checked. If the dtype.kind of + the ndarrays are not in this string the function returns False and + otherwise returns True. + """ + if xp is None: + xp = array_namespace(*arrays) + if not is_numpy(xp): + return True + + if type(arrays) is np.ndarray: + return arrays.dtype.kind in kinds + for array_ in arrays: + if array_.dtype.kind not in kinds: + return False + return True + + +def _conv_ops(x_shape, h_shape, mode): + """ + Find the number of operations required for direct/fft methods of + convolution. The direct operations were recorded by making a dummy class to + record the number of operations by overriding ``__mul__`` and ``__add__``. + The FFT operations rely on the (well-known) computational complexity of the + FFT (and the implementation of ``_freq_domain_conv``). + + """ + if mode == "full": + out_shape = [n + k - 1 for n, k in zip(x_shape, h_shape)] + elif mode == "valid": + out_shape = [abs(n - k) + 1 for n, k in zip(x_shape, h_shape)] + elif mode == "same": + out_shape = x_shape + else: + raise ValueError("Acceptable mode flags are 'valid'," + f" 'same', or 'full', not mode={mode}") + + s1, s2 = x_shape, h_shape + if len(x_shape) == 1: + s1, s2 = s1[0], s2[0] + if mode == "full": + direct_ops = s1 * s2 + elif mode == "valid": + direct_ops = (s2 - s1 + 1) * s1 if s2 >= s1 else (s1 - s2 + 1) * s2 + elif mode == "same": + direct_ops = (s1 * s2 if s1 < s2 else + s1 * s2 - (s2 // 2) * ((s2 + 1) // 2)) + else: + if mode == "full": + direct_ops = min(_prod(s1), _prod(s2)) * _prod(out_shape) + elif mode == "valid": + direct_ops = min(_prod(s1), _prod(s2)) * _prod(out_shape) + elif mode == "same": + direct_ops = _prod(s1) * _prod(s2) + + full_out_shape = [n + k - 1 for n, k in zip(x_shape, h_shape)] + N = _prod(full_out_shape) + fft_ops = 3 * N * np.log(N) # 3 separate FFTs of size full_out_shape + return fft_ops, direct_ops + + +def _fftconv_faster(x, h, mode): + """ + See if using fftconvolve or convolve is faster. + + Parameters + ---------- + x : np.ndarray + Signal + h : np.ndarray + Kernel + mode : str + Mode passed to convolve + + Returns + ------- + fft_faster : bool + + Notes + ----- + See docstring of `choose_conv_method` for details on tuning hardware. + + See pull request 11031 for more detail: + https://github.com/scipy/scipy/pull/11031. + + """ + fft_ops, direct_ops = _conv_ops(x.shape, h.shape, mode) + offset = -1e-3 if x.ndim == 1 else -1e-4 + constants = { + "valid": (1.89095737e-9, 2.1364985e-10, offset), + "full": (1.7649070e-9, 2.1414831e-10, offset), + "same": (3.2646654e-9, 2.8478277e-10, offset) + if h.size <= x.size + else (3.21635404e-9, 1.1773253e-8, -1e-5), + } if x.ndim == 1 else { + "valid": (1.85927e-9, 2.11242e-8, offset), + "full": (1.99817e-9, 1.66174e-8, offset), + "same": (2.04735e-9, 1.55367e-8, offset), + } + O_fft, O_direct, O_offset = constants[mode] + return O_fft * fft_ops < O_direct * direct_ops + O_offset + + +def _reverse_and_conj(x, xp): + """ + Reverse array `x` in all dimensions and perform the complex conjugate + """ + if not is_torch(xp): + reverse = (slice(None, None, -1),) * x.ndim + x_rev = x[reverse] + else: + # NB: is a copy, not a view as torch does not allow negative indices + # in slices, x-ref https://github.com/pytorch/pytorch/issues/59786 + x_rev = xp.flip(x) + + # cf https://github.com/data-apis/array-api/issues/824 + if xp.isdtype(x.dtype, 'complex floating'): + return xp.conj(x_rev) + else: + return x_rev + + +def _np_conv_ok(volume, kernel, mode, xp): + """ + See if numpy supports convolution of `volume` and `kernel` (i.e. both are + 1D ndarrays and of the appropriate shape). NumPy's 'same' mode uses the + size of the larger input, while SciPy's uses the size of the first input. + + Invalid mode strings will return False and be caught by the calling func. + """ + if volume.ndim == kernel.ndim == 1: + if mode in ('full', 'valid'): + return True + elif mode == 'same': + return xp_size(volume) >= xp_size(kernel) + else: + return False + + +def _timeit_fast(stmt="pass", setup="pass", repeat=3): + """ + Returns the time the statement/function took, in seconds. + + Faster, less precise version of IPython's timeit. `stmt` can be a statement + written as a string or a callable. + + Will do only 1 loop (like IPython's timeit) with no repetitions + (unlike IPython) for very slow functions. For fast functions, only does + enough loops to take 5 ms, which seems to produce similar results (on + Windows at least), and avoids doing an extraneous cycle that isn't + measured. + + """ + timer = timeit.Timer(stmt, setup) + + # determine number of calls per rep so total time for 1 rep >= 5 ms + x = 0 + for p in range(0, 10): + number = 10**p + x = timer.timeit(number) # seconds + if x >= 5e-3 / 10: # 5 ms for final test, 1/10th that for this one + break + if x > 1: # second + # If it's macroscopic, don't bother with repetitions + best = x + else: + number *= 10 + r = timer.repeat(repeat, number) + best = min(r) + + sec = best / number + return sec + + +def choose_conv_method(in1, in2, mode='full', measure=False): + """ + Find the fastest convolution/correlation method. + + This primarily exists to be called during the ``method='auto'`` option in + `convolve` and `correlate`. It can also be used to determine the value of + ``method`` for many different convolutions of the same dtype/shape. + In addition, it supports timing the convolution to adapt the value of + ``method`` to a particular set of inputs and/or hardware. + + Parameters + ---------- + in1 : array_like + The first argument passed into the convolution function. + in2 : array_like + The second argument passed into the convolution function. + mode : str {'full', 'valid', 'same'}, optional + A string indicating the size of the output: + + ``full`` + The output is the full discrete linear convolution + of the inputs. (Default) + ``valid`` + The output consists only of those elements that do not + rely on the zero-padding. + ``same`` + Output size will be ``N``, the same size as `in1`, centered + with respect to the 'full' output. + Boundary effects may be visible. + measure : bool, optional + If True, run and time the convolution of `in1` and `in2` with both + methods and return the fastest. If False (default), predict the fastest + method using precomputed values. + + Returns + ------- + method : str + A string indicating which convolution method is fastest, either + 'direct' or 'fft' + times : dict, optional + A dictionary containing the times (in seconds) needed for each method. + This value is only returned if ``measure=True``. + + See Also + -------- + convolve + correlate + + Notes + ----- + Generally, this method is 99% accurate for 2D signals and 85% accurate + for 1D signals for randomly chosen input sizes. For precision, use + ``measure=True`` to find the fastest method by timing the convolution. + This can be used to avoid the minimal overhead of finding the fastest + ``method`` later, or to adapt the value of ``method`` to a particular set + of inputs. + + Experiments were run on an Amazon EC2 r5a.2xlarge machine to test this + function. These experiments measured the ratio between the time required + when using ``method='auto'`` and the time required for the fastest method + (i.e., ``ratio = time_auto / min(time_fft, time_direct)``). In these + experiments, we found: + + * There is a 95% chance of this ratio being less than 1.5 for 1D signals + and a 99% chance of being less than 2.5 for 2D signals. + * The ratio was always less than 2.5/5 for 1D/2D signals respectively. + * This function is most inaccurate for 1D convolutions that take between 1 + and 10 milliseconds with ``method='direct'``. A good proxy for this + (at least in our experiments) is ``1e6 <= in1.size * in2.size <= 1e7``. + + The 2D results almost certainly generalize to 3D/4D/etc because the + implementation is the same (the 1D implementation is different). + + All the numbers above are specific to the EC2 machine. However, we did find + that this function generalizes fairly decently across hardware. The speed + tests were of similar quality (and even slightly better) than the same + tests performed on the machine to tune this function's numbers (a mid-2014 + 15-inch MacBook Pro with 16GB RAM and a 2.5GHz Intel i7 processor). + + There are cases when `fftconvolve` supports the inputs but this function + returns `direct` (e.g., to protect against floating point integer + precision). + + .. versionadded:: 0.19 + + Examples + -------- + Estimate the fastest method for a given input: + + >>> import numpy as np + >>> from scipy import signal + >>> rng = np.random.default_rng() + >>> img = rng.random((32, 32)) + >>> filter = rng.random((8, 8)) + >>> method = signal.choose_conv_method(img, filter, mode='same') + >>> method + 'fft' + + This can then be applied to other arrays of the same dtype and shape: + + >>> img2 = rng.random((32, 32)) + >>> filter2 = rng.random((8, 8)) + >>> corr2 = signal.correlate(img2, filter2, mode='same', method=method) + >>> conv2 = signal.convolve(img2, filter2, mode='same', method=method) + + The output of this function (``method``) works with `correlate` and + `convolve`. + + """ + xp = array_namespace(in1, in2) + + volume = xp.asarray(in1) + kernel = xp.asarray(in2) + + if measure: + times = {} + for method in ['fft', 'direct']: + times[method] = _timeit_fast(lambda: convolve(volume, kernel, + mode=mode, method=method)) + + chosen_method = 'fft' if times['fft'] < times['direct'] else 'direct' + return chosen_method, times + + # for integer input, + # catch when more precision required than float provides (representing an + # integer as float can lose precision in fftconvolve if larger than 2**52) + if any([_numeric_arrays([x], kinds='ui', xp=xp) for x in [volume, kernel]]): + max_value = int(xp.max(xp.abs(volume))) * int(xp.max(xp.abs(kernel))) + max_value *= int(min(xp_size(volume), xp_size(kernel))) + if max_value > 2**np.finfo('float').nmant - 1: + return 'direct' + + if _numeric_arrays([volume, kernel], kinds='b', xp=xp): + return 'direct' + + if _numeric_arrays([volume, kernel], xp=xp): + if _fftconv_faster(volume, kernel, mode): + return 'fft' + + return 'direct' + + +def convolve(in1, in2, mode='full', method='auto'): + """ + Convolve two N-dimensional arrays. + + Convolve `in1` and `in2`, with the output size determined by the + `mode` argument. + + Parameters + ---------- + in1 : array_like + First input. + in2 : array_like + Second input. Should have the same number of dimensions as `in1`. + mode : str {'full', 'valid', 'same'}, optional + A string indicating the size of the output: + + ``full`` + The output is the full discrete linear convolution + of the inputs. (Default) + ``valid`` + The output consists only of those elements that do not + rely on the zero-padding. In 'valid' mode, either `in1` or `in2` + must be at least as large as the other in every dimension. + ``same`` + Output size will be ``N``, the same size as `in1`, centered + with respect to the 'full' output. + Boundary effects may be visible. + method : str {'auto', 'direct', 'fft'}, optional + A string indicating which method to use to calculate the convolution. + + ``direct`` + The convolution is determined directly from sums, the definition of + convolution. + ``fft`` + The Fourier Transform is used to perform the convolution by calling + `fftconvolve`. + ``auto`` + Automatically chooses direct or Fourier method based on an estimate + of which is faster (default). See Notes for more detail. + + .. versionadded:: 0.19.0 + + Returns + ------- + convolve : array + An N-dimensional array containing a subset of the discrete linear + convolution of `in1` with `in2`. + + The output size along each axis depends on ``mode``. For input + sizes ``N`` and ``M`` along a given axis: + + - ``full`` : ``N + M - 1`` + - ``same`` : ``N`` (same as the size of `in1`) + - ``valid`` : ``max(N, M) - min(N, M) + 1`` + + Warns + ----- + RuntimeWarning + Use of the FFT convolution on input containing NAN or INF will lead + to the entire output being NAN or INF. Use method='direct' when your + input contains NAN or INF values. + + See Also + -------- + numpy.polymul : performs polynomial multiplication (same operation, but + also accepts poly1d objects) + choose_conv_method : chooses the fastest appropriate convolution method + fftconvolve : Always uses the FFT method. + oaconvolve : Uses the overlap-add method to do convolution, which is + generally faster when the input arrays are large and + significantly different in size. + + Notes + ----- + By default, `convolve` and `correlate` use ``method='auto'``, which calls + `choose_conv_method` to choose the fastest method using pre-computed + values (`choose_conv_method` can also measure real-world timing with a + keyword argument). Because `fftconvolve` relies on floating point numbers, + there are certain constraints that may force ``method='direct'`` (more detail + in `choose_conv_method` docstring). + + Examples + -------- + Smooth a square pulse using a Hann window: + + >>> import numpy as np + >>> from scipy import signal + >>> sig = np.repeat([0., 1., 0.], 100) + >>> win = signal.windows.hann(50) + >>> filtered = signal.convolve(sig, win, mode='same') / sum(win) + + >>> import matplotlib.pyplot as plt + >>> fig, (ax_orig, ax_win, ax_filt) = plt.subplots(3, 1, sharex=True) + >>> ax_orig.plot(sig) + >>> ax_orig.set_title('Original pulse') + >>> ax_orig.margins(0, 0.1) + >>> ax_win.plot(win) + >>> ax_win.set_title('Filter impulse response') + >>> ax_win.margins(0, 0.1) + >>> ax_filt.plot(filtered) + >>> ax_filt.set_title('Filtered signal') + >>> ax_filt.margins(0, 0.1) + >>> fig.tight_layout() + >>> fig.show() + + """ + xp = array_namespace(in1, in2) + + volume = xp.asarray(in1) + kernel = xp.asarray(in2) + + if volume.ndim == kernel.ndim == 0: + return volume * kernel + elif volume.ndim != kernel.ndim: + raise ValueError("volume and kernel should have the same " + "dimensionality") + + if _inputs_swap_needed(mode, volume.shape, kernel.shape): + # Convolution is commutative; order doesn't have any effect on output + volume, kernel = kernel, volume + + if method == 'auto': + method = choose_conv_method(volume, kernel, mode=mode) + + if method == 'fft': + out = fftconvolve(volume, kernel, mode=mode) + result_type = xp.result_type(volume, kernel) + if xp.isdtype(result_type, 'integral'): + out = xp.round(out) + + if xp.isnan(xp.reshape(out, (-1,))[0]) or xp.isinf(xp.reshape(out, (-1,))[0]): + warnings.warn("Use of fft convolution on input with NAN or inf" + " results in NAN or inf output. Consider using" + " method='direct' instead.", + category=RuntimeWarning, stacklevel=2) + + return xp.astype(out, result_type) + elif method == 'direct': + # fastpath to faster numpy.convolve for 1d inputs when possible + if _np_conv_ok(volume, kernel, mode, xp): + # convert to numpy and back + a_volume = np.asarray(volume) + a_kernel = np.asarray(kernel) + out = np.convolve(a_volume, a_kernel, mode) + return xp.asarray(out) + + return correlate(volume, _reverse_and_conj(kernel, xp), mode, 'direct') + else: + raise ValueError("Acceptable method flags are 'auto'," + " 'direct', or 'fft'.") + + +def order_filter(a, domain, rank): + """ + Perform an order filter on an N-D array. + + Perform an order filter on the array in. The domain argument acts as a + mask centered over each pixel. The non-zero elements of domain are + used to select elements surrounding each input pixel which are placed + in a list. The list is sorted, and the output for that pixel is the + element corresponding to rank in the sorted list. + + Parameters + ---------- + a : ndarray + The N-dimensional input array. + domain : array_like + A mask array with the same number of dimensions as `a`. + Each dimension should have an odd number of elements. + rank : int + A non-negative integer which selects the element from the + sorted list (0 corresponds to the smallest element, 1 is the + next smallest element, etc.). + + Returns + ------- + out : ndarray + The results of the order filter in an array with the same + shape as `a`. + + Examples + -------- + >>> import numpy as np + >>> from scipy import signal + >>> x = np.arange(25).reshape(5, 5) + >>> domain = np.identity(3) + >>> x + array([[ 0, 1, 2, 3, 4], + [ 5, 6, 7, 8, 9], + [10, 11, 12, 13, 14], + [15, 16, 17, 18, 19], + [20, 21, 22, 23, 24]]) + >>> signal.order_filter(x, domain, 0) + array([[ 0, 0, 0, 0, 0], + [ 0, 0, 1, 2, 0], + [ 0, 5, 6, 7, 0], + [ 0, 10, 11, 12, 0], + [ 0, 0, 0, 0, 0]]) + >>> signal.order_filter(x, domain, 2) + array([[ 6, 7, 8, 9, 4], + [ 11, 12, 13, 14, 9], + [ 16, 17, 18, 19, 14], + [ 21, 22, 23, 24, 19], + [ 20, 21, 22, 23, 24]]) + + """ + xp = array_namespace(a, domain) + + domain = xp.asarray(domain) + for dimsize in domain.shape: + if (dimsize % 2) != 1: + raise ValueError("Each dimension of domain argument " + "should have an odd number of elements.") + + a = xp.asarray(a) + if not ( + xp.isdtype(a.dtype, "integral") or a.dtype in (xp.float32, xp.float64) + ): + raise ValueError(f"dtype={a.dtype} is not supported by order_filter") + result = ndimage.rank_filter(a, rank, footprint=domain, mode='constant') + return result + + +def medfilt(volume, kernel_size=None): + """ + Perform a median filter on an N-dimensional array. + + Apply a median filter to the input array using a local window-size + given by `kernel_size`. The array will automatically be zero-padded. + + Parameters + ---------- + volume : array_like + An N-dimensional input array. + kernel_size : array_like, optional + A scalar or an N-length list giving the size of the median filter + window in each dimension. Elements of `kernel_size` should be odd. + If `kernel_size` is a scalar, then this scalar is used as the size in + each dimension. Default size is 3 for each dimension. + + Returns + ------- + out : ndarray + An array the same size as input containing the median filtered + result. + + Warns + ----- + UserWarning + If array size is smaller than kernel size along any dimension + + See Also + -------- + scipy.ndimage.median_filter + scipy.signal.medfilt2d + + """ + xp = array_namespace(volume) + volume = xp.asarray(volume) + if volume.ndim == 0: + volume = xpx.atleast_nd(volume, ndim=1, xp=xp) + + if not (xp.isdtype(volume.dtype, "integral") or + volume.dtype in [xp.float32, xp.float64]): + raise ValueError(f"dtype={volume.dtype} is not supported by medfilt") + + if kernel_size is None: + kernel_size = [3] * volume.ndim + kernel_size = xp.asarray(kernel_size) + if kernel_size.shape == (): + kernel_size = xp.repeat(kernel_size, volume.ndim) + + for k in range(volume.ndim): + if (kernel_size[k] % 2) != 1: + raise ValueError("Each element of kernel_size should be odd.") + if any(k > s for k, s in zip(kernel_size, volume.shape)): + warnings.warn('kernel_size exceeds volume extent: the volume will be ' + 'zero-padded.', + stacklevel=2) + + size = math.prod(kernel_size) + result = ndimage.rank_filter(volume, size // 2, size=kernel_size, + mode='constant') + + return result + + +def wiener(im, mysize=None, noise=None): + """ + Perform a Wiener filter on an N-dimensional array. + + Apply a Wiener filter to the N-dimensional array `im`. + + Parameters + ---------- + im : ndarray + An N-dimensional array. + mysize : int or array_like, optional + A scalar or an N-length list giving the size of the Wiener filter + window in each dimension. Elements of mysize should be odd. + If mysize is a scalar, then this scalar is used as the size + in each dimension. + noise : float, optional + The noise-power to use. If None, then noise is estimated as the + average of the local variance of the input. + + Returns + ------- + out : ndarray + Wiener filtered result with the same shape as `im`. + + Notes + ----- + This implementation is similar to wiener2 in Matlab/Octave. + For more details see [1]_ + + References + ---------- + .. [1] Lim, Jae S., Two-Dimensional Signal and Image Processing, + Englewood Cliffs, NJ, Prentice Hall, 1990, p. 548. + + Examples + -------- + >>> from scipy.datasets import face + >>> from scipy.signal import wiener + >>> import matplotlib.pyplot as plt + >>> import numpy as np + >>> rng = np.random.default_rng() + >>> img = rng.random((40, 40)) #Create a random image + >>> filtered_img = wiener(img, (5, 5)) #Filter the image + >>> f, (plot1, plot2) = plt.subplots(1, 2) + >>> plot1.imshow(img) + >>> plot2.imshow(filtered_img) + >>> plt.show() + + """ + xp = array_namespace(im) + + im = xp.asarray(im) + if mysize is None: + mysize = [3] * im.ndim + mysize_arr = xp.asarray(mysize) + if mysize_arr.shape == (): + mysize = [mysize] * im.ndim + + # Estimate the local mean + size = math.prod(mysize) + lMean = correlate(im, xp.ones(mysize), 'same') + lsize = float(size) + lMean = lMean / lsize + + # Estimate the local variance + lVar = (correlate(im ** 2, xp.ones(mysize), 'same') / lsize - lMean ** 2) + + # Estimate the noise power if needed. + if noise is None: + noise = xp.mean(xp.reshape(lVar, (-1,)), axis=0) + + res = (im - lMean) + res *= (1 - noise / lVar) + res += lMean + out = xp.where(lVar < noise, lMean, res) + + return out + + +def convolve2d(in1, in2, mode='full', boundary='fill', fillvalue=0): + """ + Convolve two 2-dimensional arrays. + + Convolve `in1` and `in2` with output size determined by `mode`, and + boundary conditions determined by `boundary` and `fillvalue`. + + Parameters + ---------- + in1 : array_like + First input. + in2 : array_like + Second input. Should have the same number of dimensions as `in1`. + mode : str {'full', 'valid', 'same'}, optional + A string indicating the size of the output: + + ``full`` + The output is the full discrete linear convolution + of the inputs. (Default) + ``valid`` + The output consists only of those elements that do not + rely on the zero-padding. In 'valid' mode, either `in1` or `in2` + must be at least as large as the other in every dimension. + ``same`` + The output is the same size as `in1`, centered + with respect to the 'full' output. + boundary : str {'fill', 'wrap', 'symm'}, optional + A flag indicating how to handle boundaries: + + ``fill`` + pad input arrays with fillvalue. (default) + ``wrap`` + circular boundary conditions. + ``symm`` + symmetrical boundary conditions. + + fillvalue : scalar, optional + Value to fill pad input arrays with. Default is 0. + + Returns + ------- + out : ndarray + A 2-dimensional array containing a subset of the discrete linear + convolution of `in1` with `in2`. + + Examples + -------- + Compute the gradient of an image by 2D convolution with a complex Scharr + operator. (Horizontal operator is real, vertical is imaginary.) Use + symmetric boundary condition to avoid creating edges at the image + boundaries. + + >>> import numpy as np + >>> from scipy import signal + >>> from scipy import datasets + >>> ascent = datasets.ascent() + >>> scharr = np.array([[ -3-3j, 0-10j, +3 -3j], + ... [-10+0j, 0+ 0j, +10 +0j], + ... [ -3+3j, 0+10j, +3 +3j]]) # Gx + j*Gy + >>> grad = signal.convolve2d(ascent, scharr, boundary='symm', mode='same') + + >>> import matplotlib.pyplot as plt + >>> fig, (ax_orig, ax_mag, ax_ang) = plt.subplots(3, 1, figsize=(6, 15)) + >>> ax_orig.imshow(ascent, cmap='gray') + >>> ax_orig.set_title('Original') + >>> ax_orig.set_axis_off() + >>> ax_mag.imshow(np.absolute(grad), cmap='gray') + >>> ax_mag.set_title('Gradient magnitude') + >>> ax_mag.set_axis_off() + >>> ax_ang.imshow(np.angle(grad), cmap='hsv') # hsv is cyclic, like angles + >>> ax_ang.set_title('Gradient orientation') + >>> ax_ang.set_axis_off() + >>> fig.show() + + """ + xp = array_namespace(in1, in2) + + # NB: do work in NumPy, only convert the output + + in1 = np.asarray(in1) + in2 = np.asarray(in2) + + if not in1.ndim == in2.ndim == 2: + raise ValueError('convolve2d inputs must both be 2-D arrays') + + if _inputs_swap_needed(mode, in1.shape, in2.shape): + in1, in2 = in2, in1 + + val = _valfrommode(mode) + bval = _bvalfromboundary(boundary) + out = _sigtools._convolve2d(in1, in2, 1, val, bval, fillvalue) + return xp.asarray(out) + + +def correlate2d(in1, in2, mode='full', boundary='fill', fillvalue=0): + """ + Cross-correlate two 2-dimensional arrays. + + Cross correlate `in1` and `in2` with output size determined by `mode`, and + boundary conditions determined by `boundary` and `fillvalue`. + + Parameters + ---------- + in1 : array_like + First input. + in2 : array_like + Second input. Should have the same number of dimensions as `in1`. + mode : str {'full', 'valid', 'same'}, optional + A string indicating the size of the output: + + ``full`` + The output is the full discrete linear cross-correlation + of the inputs. (Default) + ``valid`` + The output consists only of those elements that do not + rely on the zero-padding. In 'valid' mode, either `in1` or `in2` + must be at least as large as the other in every dimension. + ``same`` + The output is the same size as `in1`, centered + with respect to the 'full' output. + boundary : str {'fill', 'wrap', 'symm'}, optional + A flag indicating how to handle boundaries: + + ``fill`` + pad input arrays with fillvalue. (default) + ``wrap`` + circular boundary conditions. + ``symm`` + symmetrical boundary conditions. + + fillvalue : scalar, optional + Value to fill pad input arrays with. Default is 0. + + Returns + ------- + correlate2d : ndarray + A 2-dimensional array containing a subset of the discrete linear + cross-correlation of `in1` with `in2`. + + Notes + ----- + When using "same" mode with even-length inputs, the outputs of `correlate` + and `correlate2d` differ: There is a 1-index offset between them. + + Examples + -------- + Use 2D cross-correlation to find the location of a template in a noisy + image: + + >>> import numpy as np + >>> from scipy import signal, datasets, ndimage + >>> rng = np.random.default_rng() + >>> face = datasets.face(gray=True) - datasets.face(gray=True).mean() + >>> face = ndimage.zoom(face[30:500, 400:950], 0.5) # extract the face + >>> template = np.copy(face[135:165, 140:175]) # right eye + >>> template -= template.mean() + >>> face = face + rng.standard_normal(face.shape) * 50 # add noise + >>> corr = signal.correlate2d(face, template, boundary='symm', mode='same') + >>> y, x = np.unravel_index(np.argmax(corr), corr.shape) # find the match + + >>> import matplotlib.pyplot as plt + >>> fig, (ax_orig, ax_template, ax_corr) = plt.subplots(3, 1, + ... figsize=(6, 15)) + >>> ax_orig.imshow(face, cmap='gray') + >>> ax_orig.set_title('Original') + >>> ax_orig.set_axis_off() + >>> ax_template.imshow(template, cmap='gray') + >>> ax_template.set_title('Template') + >>> ax_template.set_axis_off() + >>> ax_corr.imshow(corr, cmap='gray') + >>> ax_corr.set_title('Cross-correlation') + >>> ax_corr.set_axis_off() + >>> ax_orig.plot(x, y, 'ro') + >>> fig.show() + + """ + xp = array_namespace(in1, in2) + in1 = np.asarray(in1) + in2 = np.asarray(in2) + + if not in1.ndim == in2.ndim == 2: + raise ValueError('correlate2d inputs must both be 2-D arrays') + + swapped_inputs = _inputs_swap_needed(mode, in1.shape, in2.shape) + if swapped_inputs: + in1, in2 = in2, in1 + + val = _valfrommode(mode) + bval = _bvalfromboundary(boundary) + out = _sigtools._convolve2d(in1, in2.conj(), 0, val, bval, fillvalue) + + if swapped_inputs: + out = out[::-1, ::-1] + + return xp.asarray(out) + + +def medfilt2d(input, kernel_size=3): + """ + Median filter a 2-dimensional array. + + Apply a median filter to the `input` array using a local window-size + given by `kernel_size` (must be odd). The array is zero-padded + automatically. + + Parameters + ---------- + input : array_like + A 2-dimensional input array. + kernel_size : array_like, optional + A scalar or a list of length 2, giving the size of the + median filter window in each dimension. Elements of + `kernel_size` should be odd. If `kernel_size` is a scalar, + then this scalar is used as the size in each dimension. + Default is a kernel of size (3, 3). + + Returns + ------- + out : ndarray + An array the same size as input containing the median filtered + result. + + See Also + -------- + scipy.ndimage.median_filter + + Notes + ----- + This is faster than `medfilt` when the input dtype is ``uint8``, + ``float32``, or ``float64``; for other types, this falls back to + `medfilt`. In some situations, `scipy.ndimage.median_filter` may be + faster than this function. + + Examples + -------- + >>> import numpy as np + >>> from scipy import signal + >>> x = np.arange(25).reshape(5, 5) + >>> x + array([[ 0, 1, 2, 3, 4], + [ 5, 6, 7, 8, 9], + [10, 11, 12, 13, 14], + [15, 16, 17, 18, 19], + [20, 21, 22, 23, 24]]) + + # Replaces i,j with the median out of 5*5 window + + >>> signal.medfilt2d(x, kernel_size=5) + array([[ 0, 0, 2, 0, 0], + [ 0, 3, 7, 4, 0], + [ 2, 8, 12, 9, 4], + [ 0, 8, 12, 9, 0], + [ 0, 0, 12, 0, 0]]) + + # Replaces i,j with the median out of default 3*3 window + + >>> signal.medfilt2d(x) + array([[ 0, 1, 2, 3, 0], + [ 1, 6, 7, 8, 4], + [ 6, 11, 12, 13, 9], + [11, 16, 17, 18, 14], + [ 0, 16, 17, 18, 0]]) + + # Replaces i,j with the median out of default 5*3 window + + >>> signal.medfilt2d(x, kernel_size=[5,3]) + array([[ 0, 1, 2, 3, 0], + [ 0, 6, 7, 8, 3], + [ 5, 11, 12, 13, 8], + [ 5, 11, 12, 13, 8], + [ 0, 11, 12, 13, 0]]) + + # Replaces i,j with the median out of default 3*5 window + + >>> signal.medfilt2d(x, kernel_size=[3,5]) + array([[ 0, 0, 2, 1, 0], + [ 1, 5, 7, 6, 3], + [ 6, 10, 12, 11, 8], + [11, 15, 17, 16, 13], + [ 0, 15, 17, 16, 0]]) + + # As seen in the examples, + # kernel numbers must be odd and not exceed original array dim + + """ + xp = array_namespace(input) + + image = np.asarray(input) + + # checking dtype.type, rather than just dtype, is necessary for + # excluding np.longdouble with MS Visual C. + if image.dtype.type not in (np.ubyte, np.float32, np.float64): + return xp.asarray(medfilt(image, kernel_size)) + + if kernel_size is None: + kernel_size = [3] * 2 + kernel_size = np.asarray(kernel_size) + if kernel_size.shape == (): + kernel_size = np.repeat(kernel_size.item(), 2) + + for size in kernel_size: + if (size % 2) != 1: + raise ValueError("Each element of kernel_size should be odd.") + + result_np = _sigtools._medfilt2d(image, kernel_size) + return xp.asarray(result_np) + + +def lfilter(b, a, x, axis=-1, zi=None): + """ + Filter data along one-dimension with an IIR or FIR filter. + + Filter a data sequence, `x`, using a digital filter. This works for many + fundamental data types (including Object type). The filter is a direct + form II transposed implementation of the standard difference equation + (see Notes). + + The function `sosfilt` (and filter design using ``output='sos'``) should be + preferred over `lfilter` for most filtering tasks, as second-order sections + have fewer numerical problems. + + Parameters + ---------- + b : array_like + The numerator coefficient vector in a 1-D sequence. + a : array_like + The denominator coefficient vector in a 1-D sequence. If ``a[0]`` + is not 1, then both `a` and `b` are normalized by ``a[0]``. + x : array_like + An N-dimensional input array. + axis : int, optional + The axis of the input data array along which to apply the + linear filter. The filter is applied to each subarray along + this axis. Default is -1. + zi : array_like, optional + Initial conditions for the filter delays. It is a vector + (or array of vectors for an N-dimensional input) of length + ``max(len(a), len(b)) - 1``. If `zi` is None or is not given then + initial rest is assumed. See `lfiltic` for more information. + + Returns + ------- + y : array + The output of the digital filter. + zf : array, optional + If `zi` is None, this is not returned, otherwise, `zf` holds the + final filter delay values. + + See Also + -------- + lfiltic : Construct initial conditions for `lfilter`. + lfilter_zi : Compute initial state (steady state of step response) for + `lfilter`. + filtfilt : A forward-backward filter, to obtain a filter with zero phase. + savgol_filter : A Savitzky-Golay filter. + sosfilt: Filter data using cascaded second-order sections. + sosfiltfilt: A forward-backward filter using second-order sections. + + Notes + ----- + The filter function is implemented as a direct II transposed structure. + This means that the filter implements:: + + a[0]*y[n] = b[0]*x[n] + b[1]*x[n-1] + ... + b[M]*x[n-M] + - a[1]*y[n-1] - ... - a[N]*y[n-N] + + where `M` is the degree of the numerator, `N` is the degree of the + denominator, and `n` is the sample number. It is implemented using + the following difference equations (assuming M = N):: + + a[0]*y[n] = b[0] * x[n] + d[0][n-1] + d[0][n] = b[1] * x[n] - a[1] * y[n] + d[1][n-1] + d[1][n] = b[2] * x[n] - a[2] * y[n] + d[2][n-1] + ... + d[N-2][n] = b[N-1]*x[n] - a[N-1]*y[n] + d[N-1][n-1] + d[N-1][n] = b[N] * x[n] - a[N] * y[n] + + where `d` are the state variables. + + The rational transfer function describing this filter in the + z-transform domain is:: + + -1 -M + b[0] + b[1]z + ... + b[M] z + Y(z) = -------------------------------- X(z) + -1 -N + a[0] + a[1]z + ... + a[N] z + + Examples + -------- + Generate a noisy signal to be filtered: + + >>> import numpy as np + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + >>> rng = np.random.default_rng() + >>> t = np.linspace(-1, 1, 201) + >>> x = (np.sin(2*np.pi*0.75*t*(1-t) + 2.1) + + ... 0.1*np.sin(2*np.pi*1.25*t + 1) + + ... 0.18*np.cos(2*np.pi*3.85*t)) + >>> xn = x + rng.standard_normal(len(t)) * 0.08 + + Create an order 3 lowpass butterworth filter: + + >>> b, a = signal.butter(3, 0.05) + + Apply the filter to xn. Use lfilter_zi to choose the initial condition of + the filter: + + >>> zi = signal.lfilter_zi(b, a) + >>> z, _ = signal.lfilter(b, a, xn, zi=zi*xn[0]) + + Apply the filter again, to have a result filtered at an order the same as + filtfilt: + + >>> z2, _ = signal.lfilter(b, a, z, zi=zi*z[0]) + + Use filtfilt to apply the filter: + + >>> y = signal.filtfilt(b, a, xn) + + Plot the original signal and the various filtered versions: + + >>> plt.figure + >>> plt.plot(t, xn, 'b', alpha=0.75) + >>> plt.plot(t, z, 'r--', t, z2, 'r', t, y, 'k') + >>> plt.legend(('noisy signal', 'lfilter, once', 'lfilter, twice', + ... 'filtfilt'), loc='best') + >>> plt.grid(True) + >>> plt.show() + + """ + xp = array_namespace(b, a, x, zi) + + b = np.atleast_1d(b) + a = np.atleast_1d(a) + x = np.asarray(x) + if zi is not None: + zi = np.asarray(zi) + + if not (b.ndim == 1 and xp_size(b) > 0): + raise ValueError(f"Parameter b is not a non-empty 1d array, since {b.shape=}!") + if not (a.ndim == 1 and xp_size(a) > 0): + raise ValueError(f"Parameter a is not a non-empty 1d array, since {a.shape=}!") + + if len(a) == 1: + # This path only supports types fdgFDGO to mirror _linear_filter below. + # Any of b, a, x, or zi can set the dtype, but there is no default + # casting of other types; instead a NotImplementedError is raised. + b = np.asarray(b) + a = np.asarray(a) + x = _validate_x(x) + inputs = [b, a, x] + if zi is not None: + # _linear_filter does not broadcast zi, but does do expansion of + # singleton dims. + zi = np.asarray(zi) + if zi.ndim != x.ndim: + raise ValueError("Dimensions of parameters x and zi must match, but " + + f"{x.ndim=}, {zi.ndim=}!") + expected_shape = list(x.shape) + expected_shape[axis] = b.shape[0] - 1 + expected_shape = tuple(expected_shape) + # check the trivial case where zi is the right shape first + if zi.shape != expected_shape: + strides = zi.ndim * [None] + if axis < 0: + axis += zi.ndim + for k in range(zi.ndim): + if k == axis and zi.shape[k] == expected_shape[k]: + strides[k] = zi.strides[k] + elif k != axis and zi.shape[k] == expected_shape[k]: + strides[k] = zi.strides[k] + elif k != axis and zi.shape[k] == 1: + strides[k] = 0 + else: + raise ValueError('Unexpected shape for parameter zi: expected ' + f'{expected_shape}, found {zi.shape}.') + zi = np.lib.stride_tricks.as_strided(zi, expected_shape, + strides) + inputs.append(zi) + dtype = np.result_type(*inputs) + + if dtype.char not in 'fdgFDGO': + raise NotImplementedError("Parameter's dtypes produced result type " + + f"'{dtype}', which is not supported!") + + b = np.array(b, dtype=dtype) + a = np.asarray(a, dtype=dtype) + b /= a[0] + x = np.asarray(x, dtype=dtype) + + out_full = np.apply_along_axis(lambda y: np.convolve(b, y), axis, x) + ind = out_full.ndim * [slice(None)] + if zi is not None: + ind[axis] = slice(zi.shape[axis]) + out_full[tuple(ind)] += zi + + ind[axis] = slice(out_full.shape[axis] - len(b) + 1) + out = out_full[tuple(ind)] + + if zi is None: + return xp.asarray(out) + else: + ind[axis] = slice(out_full.shape[axis] - len(b) + 1, None) + zf = out_full[tuple(ind)] + return xp.asarray(out), xp.asarray(zf) + else: + if zi is None: + result =_sigtools._linear_filter(b, a, x, axis) + return xp.asarray(result) + else: + out, zf = _sigtools._linear_filter(b, a, x, axis, zi) + return xp.asarray(out), xp.asarray(zf) + + +def lfiltic(b, a, y, x=None): + """ + Construct initial conditions for lfilter given input and output vectors. + + Given a linear filter (b, a) and initial conditions on the output `y` + and the input `x`, return the initial conditions on the state vector zi + which is used by `lfilter` to generate the output given the input. + + Parameters + ---------- + b : array_like + Linear filter term. + a : array_like + Linear filter term. + y : array_like + Initial conditions. + + If ``N = len(a) - 1``, then ``y = {y[-1], y[-2], ..., y[-N]}``. + + If `y` is too short, it is padded with zeros. + x : array_like, optional + Initial conditions. + + If ``M = len(b) - 1``, then ``x = {x[-1], x[-2], ..., x[-M]}``. + + If `x` is not given, its initial conditions are assumed zero. + + If `x` is too short, it is padded with zeros. + + Returns + ------- + zi : ndarray + The state vector ``zi = {z_0[-1], z_1[-1], ..., z_K-1[-1]}``, + where ``K = max(M, N)``. + + See Also + -------- + lfilter, lfilter_zi + + """ + xp = array_namespace(a, b, y, x) + + a = xpx.atleast_nd(xp.asarray(a), ndim=1, xp=xp) + b = xpx.atleast_nd(xp.asarray(b), ndim=1, xp=xp) + if a.ndim > 1: + raise ValueError('Filter coefficients `a` must be 1-D.') + if b.ndim > 1: + raise ValueError('Filter coefficients `b` must be 1-D.') + N = a.shape[0] - 1 + M = b.shape[0] - 1 + K = max(M, N) + y = xp.asarray(y) + + if N < 0: + raise ValueError("There must be at least one `a` coefficient.") + + if x is None: + result_type = xp.result_type(b, a, y) + if xp.isdtype(result_type, ('bool', 'integral')): #'bui': + result_type = xp.float64 + x = xp.zeros(M, dtype=result_type) + else: + x = xp.asarray(x) + + result_type = xp.result_type(b, a, y, x) + if xp.isdtype(result_type, ('bool', 'integral')): #'bui': + result_type = xp.float64 + x = xp.astype(x, result_type) + + L = xp_size(x) + if L < M: + x = xp.concat((x, xp.zeros(M - L))) + + y = xp.astype(y, result_type) + zi = xp.zeros(K, dtype=result_type) + + L = xp_size(y) + if L < N: + y = xp.concat((y, xp.zeros(N - L))) + + for m in range(M): + zi[m] = xp.sum(b[m + 1:] * x[:M - m], axis=0) + + for m in range(N): + zi[m] -= xp.sum(a[m + 1:] * y[:N - m], axis=0) + + if a[0] != 1.: + if a[0] == 0.: + raise ValueError("First `a` filter coefficient must be non-zero.") + zi /= a[0] + + return zi + + +def deconvolve(signal, divisor): + """Deconvolves ``divisor`` out of ``signal`` using inverse filtering. + + Returns the quotient and remainder such that + ``signal = convolve(divisor, quotient) + remainder`` + + Parameters + ---------- + signal : (N,) array_like + Signal data, typically a recorded signal + divisor : (N,) array_like + Divisor data, typically an impulse response or filter that was + applied to the original signal + + Returns + ------- + quotient : ndarray + Quotient, typically the recovered original signal + remainder : ndarray + Remainder + + See Also + -------- + numpy.polydiv : performs polynomial division (same operation, but + also accepts poly1d objects) + + Examples + -------- + Deconvolve a signal that's been filtered: + + >>> from scipy import signal + >>> original = [0, 1, 0, 0, 1, 1, 0, 0] + >>> impulse_response = [2, 1] + >>> recorded = signal.convolve(impulse_response, original) + >>> recorded + array([0, 2, 1, 0, 2, 3, 1, 0, 0]) + >>> recovered, remainder = signal.deconvolve(recorded, impulse_response) + >>> recovered + array([ 0., 1., 0., 0., 1., 1., 0., 0.]) + >>> remainder + array([0., 0., 0., 0., 0., 0., 0., 0., 0.]) + """ + xp = array_namespace(signal, divisor) + + num = xpx.atleast_nd(xp.asarray(signal), ndim=1, xp=xp) + den = xpx.atleast_nd(xp.asarray(divisor), ndim=1, xp=xp) + if not (num.ndim == 1 and xp_size(num) > 0): + raise ValueError("Parameter signal must be non-empty 1d array, " + + f"but its shape is {num.shape}!") + if not (den.ndim == 1 and xp_size(den) > 0): + raise ValueError("Parameter divisor must be non-empty 1d array, " + + f"but its shape is {den.shape}!") + N = num.shape[0] + D = den.shape[0] + if D > N: + quot = [] + rem = num + else: + input = xp.zeros(N - D + 1, dtype=xp.float64) + input = xpx.at(input)[0].set(1) + quot = lfilter(num, den, input) + rem = num - convolve(den, quot, mode='full') + return quot, rem + + +def hilbert(x, N=None, axis=-1): + r"""FFT-based computation of the analytic signal. + + The analytic signal is calculated by zeroing out the negative frequencies and + doubling the amplitudes of the positive frequencies in the FFT domain. + The imaginary part of the result is the hilbert transform of the real-valued input + signal. + + The transformation is done along the last axis by default. + + For numpy arrays, `scipy.fft.set_workers` can be used to change the number of + workers used for the FFTs. + + Parameters + ---------- + x : array_like + Signal data. Must be real. + N : int, optional + Number of output samples. `x` is initially cropped or zero-padded to length + `N` along `axis`. Default: ``x.shape[axis]`` + axis : int, optional + Axis along which to do the transformation. Default: -1. + + Returns + ------- + xa : ndarray + Analytic signal of `x`, of each 1-D array along `axis` + + Notes + ----- + The analytic signal ``x_a(t)`` of a real-valued signal ``x(t)`` + can be expressed as [1]_ + + .. math:: x_a = F^{-1}(F(x) 2U) = x + i y\ , + + where `F` is the Fourier transform, `U` the unit step function, + and `y` the Hilbert transform of `x`. [2]_ + + In other words, the negative half of the frequency spectrum is zeroed + out, turning the real-valued signal into a complex-valued signal. The Hilbert + transformed signal can be obtained from ``np.imag(hilbert(x))``, and the + original signal from ``np.real(hilbert(x))``. + + References + ---------- + .. [1] Wikipedia, "Analytic signal". + https://en.wikipedia.org/wiki/Analytic_signal + .. [2] Wikipedia, "Hilbert Transform". + https://en.wikipedia.org/wiki/Hilbert_transform + .. [3] Leon Cohen, "Time-Frequency Analysis", 1995. Chapter 2. + .. [4] Alan V. Oppenheim, Ronald W. Schafer. Discrete-Time Signal + Processing, Third Edition, 2009. Chapter 12. + ISBN 13: 978-1292-02572-8 + + See Also + -------- + envelope: Compute envelope of a real- or complex-valued signal. + + Examples + -------- + In this example we use the Hilbert transform to determine the amplitude + envelope and instantaneous frequency of an amplitude-modulated signal. + + Let's create a chirp of which the frequency increases from 20 Hz to 100 Hz and + apply an amplitude modulation: + + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from scipy.signal import hilbert, chirp + ... + >>> duration, fs = 1, 400 # 1 s signal with sampling frequency of 400 Hz + >>> t = np.arange(int(fs*duration)) / fs # timestamps of samples + >>> signal = chirp(t, 20.0, t[-1], 100.0) + >>> signal *= (1.0 + 0.5 * np.sin(2.0*np.pi*3.0*t) ) + + The amplitude envelope is given by the magnitude of the analytic signal. The + instantaneous frequency can be obtained by differentiating the + instantaneous phase in respect to time. The instantaneous phase corresponds + to the phase angle of the analytic signal. + + >>> analytic_signal = hilbert(signal) + >>> amplitude_envelope = np.abs(analytic_signal) + >>> instantaneous_phase = np.unwrap(np.angle(analytic_signal)) + >>> instantaneous_frequency = np.diff(instantaneous_phase) / (2.0*np.pi) * fs + ... + >>> fig, (ax0, ax1) = plt.subplots(nrows=2, sharex='all', tight_layout=True) + >>> ax0.set_title("Amplitude-modulated Chirp Signal") + >>> ax0.set_ylabel("Amplitude") + >>> ax0.plot(t, signal, label='Signal') + >>> ax0.plot(t, amplitude_envelope, label='Envelope') + >>> ax0.legend() + >>> ax1.set(xlabel="Time in seconds", ylabel="Frequency in Hz", ylim=(0, 120)) + >>> ax1.plot(t[1:], instantaneous_frequency, 'C2-', + ... label='Instantaneous Frequency') + >>> ax1.legend() + >>> plt.show() + + """ + xp = array_namespace(x) + + x = xp.asarray(x) + if xp.isdtype(x.dtype, 'complex floating'): + raise ValueError("x must be real.") + + if N is None: + N = x.shape[axis] + if N <= 0: + raise ValueError("N must be positive.") + + Xf = sp_fft.fft(x, N, axis=axis) + Xf = xp.moveaxis(Xf, axis, -1) + if N % 2 == 0: + Xf = xpx.at(Xf)[..., 1: N // 2].multiply(2.0) + Xf = xpx.at(Xf)[..., N // 2 + 1:N].set(0.0) + else: + Xf = xpx.at(Xf)[..., 1: (N + 1) // 2].multiply(2.0) + Xf = xpx.at(Xf)[..., (N + 1) // 2:N].set(0.0) + + Xf = xp.moveaxis(Xf, -1, axis) + x = sp_fft.ifft(Xf, axis=axis) + return x + + +def hilbert2(x, N=None, axes=(-2, -1)): + r"""Compute the '2-D' analytic signal of `x`. + + The 2-D analytic signal is calculated as a so-called "single-orthant" transform. + This is achieved by applying one-dimensional Hilbert functions (as in + `~scipy.signal.hilbert`) to the first and to the second array axis in Fourier space. + + For NumPy arrays, `scipy.fft.set_workers` can be used to change the number of + workers used for the FFTs. + + Parameters + ---------- + x : array_like + Input signal. Must be at least two-dimensional. + N : int or tuple of two ints, optional + Number of output samples. `x` is initially cropped or zero-padded to length + `N` along `axes`. Default: ``x.shape[i] for i in axes`` + axes : tuple of two ints, optional + Axes along which to do the transformation. Default: (-2, -1). + + .. versionchanged:: 1.17 + + Added `axes` parameter + + Returns + ------- + xa : ndarray + Analytic signal of `x` taken along given axes. + + Notes + ----- + The "single-orthant" transform, as defined in [2]_, is calculated by performing the + following steps: + + 1. Calculate the two-dimensional FFT of the input, i.e., + + .. math:: + + X[p,q] = \sum_{k,l=0}^{N_0,N_1} x[k,l]\, + e^{-2j\pi k p/N_0}\, e^{-2j\pi l q/N_1} + + 2. Zero negative frequency bins and double their positive counterparts, i.e., + + .. math:: + + X_a[p,q] = \big(1 + s_{N_0}(p)\big) \big(1 + s_{N_1}(q)\big) X[p,q] + + with :math:`s_N(.)` being a modified sign function defined as + + .. math:: + + s_N(p) := \begin{cases} + -1 & \text{ for } p < 0\ ,\\ + \phantom{-}0 & \text{ for } p = 0\ ,\\ + +1 & \text{ for } 1 \leq p < (N+1) // 2\ ,\\ + \phantom{-}0 & \text{ elsewhere.} + \end{cases} + + The limitation of the ":math:`+1`" case to the range of ``[1:(N+1)//2]`` + accounts for the unpaired Nyquist frequency bin at :math:`N/2` for even + :math:`N`. Note that :math:`X_a[p] = \big(1 + s_N(p)\big) X[p]` is the + one-dimensional Hilbert function (as in `~scipy.signal.hilbert`) in Fourier + space. + + 3. Produce the analytic signal by performing the inverse FFT, i.e., + + .. math:: + + x_a[k, l] = \frac{1}{N_0 N_1} + \sum_{p,q=0}^{N_0,N_1} X_a[p,q]\, e^{2j\pi k p/N_0}\, e^{2j\pi l q/N_1} + + The "single-orthant" transform is not the only possible definition of an analytic + signal in multiple dimensions (as noted in [1]_). Consult [3]_ for a description of + properties that this 2-D transform does and does not share with the 1-D transform. + The second example below shows one of the downsides of this approach. + + References + ---------- + .. [1] Wikipedia, "Analytic signal", + https://en.wikipedia.org/wiki/Analytic_signal + .. [2] Hahn, Stefan L. "Multidimensional complex signals with + single-orthant spectra." Proceedings of the IEEE 80.8 + (1992): 1287-1300. + `PDF `__ + .. [3] Bülow, Thomas, and Gerald Sommer. "A novel approach to the 2D analytic + signal." In International Conference on Computer Analysis of Images and + Patterns, pp. 25-32. Berlin, Heidelberg: Springer Berlin Heidelberg, 1999. + `PDF `__ + + Examples + -------- + The following example calculates the two-dimensional analytic signal from a single + impulse with an added constant offset. The impulse produces an FFT where each bin + has a value of one and the constant offset component produces only a non-zero + component at the ``(0,0)`` bin. + + >>> import numpy as np + >>> from scipy.fft import fft2, fftshift, ifftshift + >>> from scipy.signal import hilbert2 + ... + >>> # Input signal is unit impulse with a constant offset: + >>> x = np.ones((5, 5)) / 5 + >>> x[0, 0] += 1 + ... + >>> X = fftshift(fft2(x)) # Zero frequency bin is at center + >>> print(X) + [[1.-0.j 1.-0.j 1.-0.j 1.+0.j 1.+0.j] + [1.-0.j 1.-0.j 1.-0.j 1.+0.j 1.+0.j] + [1.-0.j 1.-0.j 6.-0.j 1.+0.j 1.+0.j] + [1.-0.j 1.-0.j 1.+0.j 1.+0.j 1.+0.j] + [1.-0.j 1.-0.j 1.+0.j 1.+0.j 1.+0.j]] + >>> x_a = hilbert2(x) + >>> X_a = fftshift(fft2(x_a)) + >>> print(np.round(X_a, 3)) + [[ 0.+0.j 0.+0.j -0.+0.j 0.+0.j 0.+0.j] + [ 0.+0.j 0.+0.j -0.+0.j 0.+0.j 0.+0.j] + [ 0.+0.j 0.+0.j 6.+0.j 2.+0.j 2.+0.j] + [ 0.+0.j 0.+0.j 2.+0.j 4.+0.j 4.+0.j] + [ 0.+0.j 0.+0.j 2.+0.j 4.+0.j 4.+0.j]] + + The FFT of the result illustrates that the values of the lower right quadrant (or + orthant) with purely positive frequency bins have been quadrupled. The values at its + borders, where only one frequency component is zero, are doubled. The zero frequency + bin ``(0, 0)`` has not been altered. All other quadrants have been set to zero. + + This second example illustrates a problem with the "single-orthant" convention. A + purely real signal can produce an analytic signal which is completely zero: + + >>> from scipy.fft import fft2, fftshift, ifft2, ifftshift + >>> from scipy.signal import hilbert2 + ... + >>> # Create a real signal by ensuring `Z[-p,-q] == np.conj(Z[p,q])` holds: + >>> Z = np.array([[0, 0, 0, 0, 0], + ... [0, 0, 0, 1, 0], + ... [0, 0, 0, 0, 0], + ... [0, 1, 0, 0, 0], + ... [0, 0, 0, 0, 0]]) * 25 + >>> z = ifft2(ifftshift(Z)) + >>> np.allclose(z.imag, 0) # z is a real signal + True + >>> np.sum(z.real**2) # z.real is non-zero + np.float64(50.0) + >>> z_a = hilbert2(z.real) + >>> np.allclose(z_a, 0) # analytic signal is zero + True + + """ + xp = array_namespace(x) + x = xpx.atleast_nd(xp.asarray(x), ndim=2, xp=xp) + if xp.isdtype(x.dtype, 'complex floating'): + raise ValueError("x must be real.") + if len(axes) != 2: + raise ValueError("axes must be a tuple of length 2") + if axes[0] == axes[1]: + raise ValueError("axes must contain 2 distinct axes") + + if N is None: + N = (x.shape[axes[0]], x.shape[axes[1]]) + elif isinstance(N, int): + if N <= 0: + raise ValueError("N must be positive.") + N = (N, N) + elif len(N) != 2 or np.any(np.asarray(N) <= 0): + raise ValueError("When given as a tuple, N must hold exactly " + "two positive integers") + + Xf = sp_fft.fft2(x, N, axes=axes) + Xf = xp.moveaxis(Xf, axes, (-2, -1)) + k0, k1 = (N[0] + 1) // 2, (N[1] + 1) // 2 + + if k0 > 1: # condition k0 > 1 needed for Dask backend + Xf = xpx.at(Xf)[..., 1:k0, :].multiply(2.0) + if k1 > 1: # condition k1 > 1 needed for Dask backend + Xf = xpx.at(Xf)[..., :, 1:k1].multiply(2.0) + Xf = xpx.at(Xf)[..., k0:, :].set(0.0) + Xf = xpx.at(Xf)[..., :, k1:].set(0.0) + + Xf = xp.moveaxis(Xf, (-2, -1), axes) + x = sp_fft.ifft2(Xf, axes=axes) + return x + + +def envelope(z, bp_in: tuple[int | None, int | None] = (1, None), *, + n_out: int | None = None, squared: bool = False, + residual: Literal['lowpass', 'all', None] = 'lowpass', + axis: int = -1): + r"""Compute the envelope of a real- or complex-valued signal. + + Parameters + ---------- + z : ndarray + Real- or complex-valued input signal, which is assumed to be made up of ``n`` + samples and having sampling interval ``T``. `z` may also be a multidimensional + array with the time axis being defined by `axis`. + bp_in : tuple[int | None, int | None], optional + 2-tuple defining the frequency band ``bp_in[0]:bp_in[1]`` of the input filter. + The corner frequencies are specified as integer multiples of ``1/(n*T)`` with + ``-n//2 <= bp_in[0] < bp_in[1] <= (n+1)//2`` being the allowed frequency range. + ``None`` entries are replaced with ``-n//2`` or ``(n+1)//2`` respectively. The + default of ``(1, None)`` removes the mean value as well as the negative + frequency components. + n_out : int | None, optional + If not ``None`` the output will be resampled to `n_out` samples. The default + of ``None`` sets the output to the same length as the input `z`. + squared : bool, optional + If set, the square of the envelope is returned. The bandwidth of the squared + envelope is often smaller than the non-squared envelope bandwidth due to the + nonlinear nature of the utilized absolute value function. I.e., the embedded + square root function typically produces addiational harmonics. + The default is ``False``. + residual : Literal['lowpass', 'all', None], optional + This option determines what kind of residual, i.e., the signal part which the + input bandpass filter removes, is returned. ``'all'`` returns everything except + the contents of the frequency band ``bp_in[0]:bp_in[1]``, ``'lowpass'`` + returns the contents of the frequency band ``< bp_in[0]``. If ``None`` then + only the envelope is returned. Default: ``'lowpass'``. + axis : int, optional + Axis of `z` over which to compute the envelope. Default is last the axis. + + Returns + ------- + ndarray + If parameter `residual` is ``None`` then an array ``z_env`` with the same shape + as the input `z` is returned, containing its envelope. Otherwise, an array with + shape ``(2, *z.shape)``, containing the arrays ``z_env`` and ``z_res``, stacked + along the first axis, is returned. + It allows unpacking, i.e., ``z_env, z_res = envelope(z, residual='all')``. + The residual ``z_res`` contains the signal part which the input bandpass filter + removed, depending on the parameter `residual`. Note that for real-valued + signals, a real-valued residual is returned. Hence, the negative frequency + components of `bp_in` are ignored. + + Notes + ----- + Any complex-valued signal :math:`z(t)` can be described by a real-valued + instantaneous amplitude :math:`a(t)` and a real-valued instantaneous phase + :math:`\phi(t)`, i.e., :math:`z(t) = a(t) \exp\!\big(j \phi(t)\big)`. The + envelope is defined as the absolute value of the amplitude :math:`|a(t)| = |z(t)|`, + which is at the same time the absolute value of the signal. Hence, :math:`|a(t)|` + "envelopes" the class of all signals with amplitude :math:`a(t)` and arbitrary + phase :math:`\phi(t)`. + For real-valued signals, :math:`x(t) = a(t) \cos\!\big(\phi(t)\big)` is the + analogous formulation. Hence, :math:`|a(t)|` can be determined by converting + :math:`x(t)` into an analytic signal :math:`z_a(t)` by means of a Hilbert + transform, i.e., + :math:`z_a(t) = a(t) \cos\!\big(\phi(t)\big) + j a(t) \sin\!\big(\phi(t) \big)`, + which produces a complex-valued signal with the same envelope :math:`|a(t)|`. + + The implementation is based on computing the FFT of the input signal and then + performing the necessary operations in Fourier space. Hence, the typical :ref:`FFT + caveats ` need to be taken into account. The frequency + spacing ``1 / (n*T)`` for corner frequencies of the bandpass filter corresponds to + the frequencies produced by ``scipy.fft.fftfreq(len(z), T)``. + + If the envelope of a complex-valued signal `z` with no bandpass filtering is + desired, i.e., ``bp_in=(None, None)``, then the envelope corresponds to the + absolute value. Hence, it is more efficient to use ``np.abs(z)`` instead of this + function. + + Although computing the envelope based on the analytic signal [1]_ is the natural + method for real-valued signals, other methods are also frequently used. The most + popular alternative is probably the so-called "square-law" envelope detector and + its relatives [2]_. They do not always compute the correct result for all kinds of + signals, but are usually correct and typically computationally more efficient for + most kinds of narrowband signals. The definition for an envelope presented here is + common where instantaneous amplitude and phase are of interest (e.g., as described + in [3]_). There exist also other concepts, which rely on the general mathematical + idea of an envelope [4]_: A pragmatic approach is to determine all upper and lower + signal peaks and use a spline interpolation to determine the curves [5]_. + + + References + ---------- + .. [1] "Analytic Signal", Wikipedia, + https://en.wikipedia.org/wiki/Analytic_signal + .. [2] Lyons, Richard, "Digital envelope detection: The good, the bad, and the + ugly", IEEE Signal Processing Magazine 34.4 (2017): 183-187. + `PDF `__ + .. [3] T.G. Kincaid, "The complex representation of signals.", + TIS R67# MH5, General Electric Co. (1966). + `PDF `__ + .. [4] "Envelope (mathematics)", Wikipedia, + https://en.wikipedia.org/wiki/Envelope_(mathematics) + .. [5] Yang, Yanli. "A signal theoretic approach for envelope analysis of + real-valued signals." IEEE Access 5 (2017): 5623-5630. + `PDF `__ + + + See Also + -------- + hilbert: Compute analytic signal by means of Hilbert transform. + + + Examples + -------- + The following plot illustrates the envelope of a signal with variable frequency and + a low-frequency drift. To separate the drift from the envelope, a 4 Hz highpass + filter is used. The low-pass residuum of the input bandpass filter is utilized to + determine an asymmetric upper and lower bound to enclose the signal. Due to the + smoothness of the resulting envelope, it is down-sampled from 500 to 40 samples. + Note that the instantaneous amplitude ``a_x`` and the computed envelope ``x_env`` + are not perfectly identical. This is due to the signal not being perfectly periodic + as well as the existence of some spectral overlapping of ``x_carrier`` and + ``x_drift``. Hence, they cannot be completely separated by a bandpass filter. + + >>> import matplotlib.pyplot as plt + >>> import numpy as np + >>> from scipy.signal.windows import gaussian + >>> from scipy.signal import envelope + ... + >>> n, n_out = 500, 40 # number of signal samples and envelope samples + >>> T = 2 / n # sampling interval for 2 s duration + >>> t = np.arange(n) * T # time stamps + >>> a_x = gaussian(len(t), 0.4/T) # instantaneous amplitude + >>> phi_x = 30*np.pi*t + 35*np.cos(2*np.pi*0.25*t) # instantaneous phase + >>> x_carrier = a_x * np.cos(phi_x) + >>> x_drift = 0.3 * gaussian(len(t), 0.4/T) # drift + >>> x = x_carrier + x_drift + ... + >>> bp_in = (int(4 * (n*T)), None) # 4 Hz highpass input filter + >>> x_env, x_res = envelope(x, bp_in, n_out=n_out) + >>> t_out = np.arange(n_out) * (n / n_out) * T + ... + >>> fg0, ax0 = plt.subplots(1, 1, tight_layout=True) + >>> ax0.set_title(r"$4\,$Hz Highpass Envelope of Drifting Signal") + >>> ax0.set(xlabel="Time in seconds", xlim=(0, n*T), ylabel="Amplitude") + >>> ax0.plot(t, x, 'C0-', alpha=0.5, label="Signal") + >>> ax0.plot(t, x_drift, 'C2--', alpha=0.25, label="Drift") + >>> ax0.plot(t_out, x_res+x_env, 'C1.-', alpha=0.5, label="Envelope") + >>> ax0.plot(t_out, x_res-x_env, 'C1.-', alpha=0.5, label=None) + >>> ax0.grid(True) + >>> ax0.legend() + >>> plt.show() + + The second example provides a geometric envelope interpretation of complex-valued + signals: The following two plots show the complex-valued signal as a blue + 3d-trajectory and the envelope as an orange round tube with varying diameter, i.e., + as :math:`|a(t)| \exp(j\rho(t))`, with :math:`\rho(t)\in[-\pi,\pi]`. Also, the + projection into the 2d real and imaginary coordinate planes of trajectory and tube + is depicted. Every point of the complex-valued signal touches the tube's surface. + + The left plot shows an analytic signal, i.e, the phase difference between + imaginary and real part is always 90 degrees, resulting in a spiraling trajectory. + It can be seen that in this case the real part has also the expected envelope, + i.e., representing the absolute value of the instantaneous amplitude. + + The right plot shows the real part of that analytic signal being interpreted + as a complex-vauled signal, i.e., having zero imaginary part. There the resulting + envelope is not as smooth as in the analytic case and the instantaneous amplitude + in the real plane is not recovered. If ``z_re`` had been passed as a real-valued + signal, i.e., as ``z_re = z.real`` instead of ``z_re = z.real + 0j``, the result + would have been identical to the left plot. The reason for this is that real-valued + signals are interpreted as being the real part of a complex-valued analytic signal. + + >>> import matplotlib.pyplot as plt + >>> import numpy as np + >>> from scipy.signal.windows import gaussian + >>> from scipy.signal import envelope + ... + >>> n, T = 1000, 1/1000 # number of samples and sampling interval + >>> t = np.arange(n) * T # time stamps for 1 s duration + >>> f_c = 3 # Carrier frequency for signal + >>> z = gaussian(len(t), 0.3/T) * np.exp(2j*np.pi*f_c*t) # analytic signal + >>> z_re = z.real + 0j # complex signal with zero imaginary part + ... + >>> e_a, e_r = (envelope(z_, (None, None), residual=None) for z_ in (z, z_re)) + ... + >>> # Generate grids to visualize envelopes as 2d and 3d surfaces: + >>> E2d_t, E2_amp = np.meshgrid(t, [-1, 1]) + >>> E2d_1 = np.ones_like(E2_amp) + >>> E3d_t, E3d_phi = np.meshgrid(t, np.linspace(-np.pi, np.pi, 300)) + >>> ma = 1.8 # maximum axis values in real and imaginary direction + ... + >>> fg0 = plt.figure(figsize=(6.2, 4.)) + >>> ax00 = fg0.add_subplot(1, 2, 1, projection='3d') + >>> ax01 = fg0.add_subplot(1, 2, 2, projection='3d', sharex=ax00, + ... sharey=ax00, sharez=ax00) + >>> ax00.set_title("Analytic Signal") + >>> ax00.set(xlim=(0, 1), ylim=(-ma, ma), zlim=(-ma, ma)) + >>> ax01.set_title("Real-valued Signal") + >>> for z_, e_, ax_ in zip((z, z.real), (e_a, e_r), (ax00, ax01)): + ... ax_.set(xlabel="Time $t$", ylabel="Real Amp. $x(t)$", + ... zlabel="Imag. Amp. $y(t)$") + ... ax_.plot(t, z_.real, 'C0-', zs=-ma, zdir='z', alpha=0.5, label="Real") + ... ax_.plot_surface(E2d_t, e_*E2_amp, -ma*E2d_1, color='C1', alpha=0.25) + ... ax_.plot(t, z_.imag, 'C0-', zs=+ma, zdir='y', alpha=0.5, label="Imag.") + ... ax_.plot_surface(E2d_t, ma*E2d_1, e_*E2_amp, color='C1', alpha=0.25) + ... ax_.plot(t, z_.real, z_.imag, 'C0-', label="Signal") + ... ax_.plot_surface(E3d_t, e_*np.cos(E3d_phi), e_*np.sin(E3d_phi), + ... color='C1', alpha=0.5, shade=True, label="Envelope") + ... ax_.view_init(elev=22.7, azim=-114.3) + >>> fg0.subplots_adjust(left=0.08, right=0.97, wspace=0.15) + >>> plt.show() + """ + xp = array_namespace(z) + if not (-z.ndim <= axis < z.ndim): + raise ValueError(f"Invalid parameter {axis=} for {z.shape=}!") + if not (z.shape[axis] > 0): + raise ValueError(f"z.shape[axis] not > 0 for {z.shape=}, {axis=}!") + if len(bp_in) != 2 or not all((isinstance(b_, int) or b_ is None) for b_ in bp_in): + raise ValueError(f"{bp_in=} isn't a 2-tuple of type (int | None, int | None)!") + if not ((isinstance(n_out, int) and 0 < n_out) or n_out is None): + raise ValueError(f"{n_out=} is not a positive integer or None!") + if residual not in ('lowpass', 'all', None): + raise ValueError(f"{residual=} not in ['lowpass', 'all', None]!") + + n = z.shape[axis] # number of time samples of input + n_out = n if n_out is None else n_out + fak = n_out / n # scaling factor for resampling + + bp = slice(bp_in[0] if bp_in[0] is not None else -(n//2), + bp_in[1] if bp_in[1] is not None else (n+1)//2) + if not (-n//2 <= bp.start < bp.stop <= (n+1)//2): + raise ValueError("`-n//2 <= bp_in[0] < bp_in[1] <= (n+1)//2` does not hold " + + f"for n={z.shape[axis]=} and {bp_in=}!") + + # moving active axis to end allows to use `...` for indexing: + z = xp.moveaxis(z, axis, -1) + + if xp.isdtype(z.dtype, 'complex floating'): + Z = sp_fft.fft(z) + else: # avoid calculating negative frequency bins for real signals: + dt = sp_fft.rfft(z[..., :1]).dtype + Z = xp.zeros_like(z, dtype=dt) + Z[..., :n//2 + 1] = sp_fft.rfft(z) + if bp.start > 0: # make signal analytic within bp_in band: + Z[..., bp] *= 2 + elif bp.stop > 0: + Z[..., 1:bp.stop] *= 2 + if not (bp.start <= 0 < bp.stop): # envelope is invariant to freq. shifts. + z_bb = sp_fft.ifft(Z[..., bp], n=n_out) * fak # baseband signal + else: + bp_shift = slice(bp.start + n//2, bp.stop + n//2) + z_bb = sp_fft.ifft(sp_fft.fftshift(Z, axes=-1)[..., bp_shift], n=n_out) * fak + + z_env = xp.abs(z_bb) if not squared else xp.real(z_bb) ** 2 + xp.imag(z_bb) ** 2 + z_env = xp.moveaxis(z_env, -1, axis) + + # Calculate the residual from the input bandpass filter: + if residual is None: + return z_env + if not (bp.start <= 0 < bp.stop): + Z[..., bp] = 0 + else: + Z[..., :bp.stop], Z[..., bp.start:] = 0, 0 + if residual == 'lowpass': + if bp.stop > 0: + Z[..., bp.stop:(n+1) // 2] = 0 + else: + Z[..., bp.start:], Z[..., 0:(n + 1) // 2] = 0, 0 + + if xp.isdtype(z.dtype, 'complex floating'): # resample accounts for unpaired bins: + z_res = resample(Z, n_out, axis=-1, domain='freq') # ifft() with corrections + else: # account for unpaired bin at m//2 before doing irfft(): + if n_out != n and (m := min(n, n_out)) % 2 == 0: + Z[..., m//2] *= 2 if n_out < n else 0.5 + z_res = fak * sp_fft.irfft(Z, n=n_out) + return xp.stack((z_env, xp.moveaxis(z_res, -1, axis)), axis=0) + + +def _cmplx_sort(p): + """Sort roots based on magnitude. + + Parameters + ---------- + p : array_like + The roots to sort, as a 1-D array. + + Returns + ------- + p_sorted : ndarray + Sorted roots. + indx : ndarray + Array of indices needed to sort the input `p`. + + Examples + -------- + >>> from scipy.signal._signaltools import _cmplx_sort + >>> vals = [1, 4, 1+1.j, 3] + >>> p_sorted, indx = _cmplx_sort(vals) + >>> p_sorted + array([1.+0.j, 1.+1.j, 3.+0.j, 4.+0.j]) + >>> indx + array([0, 2, 3, 1]) + """ + p = np.asarray(p) + indx = np.argsort(abs(p)) + return np.take(p, indx, 0), indx + + +def unique_roots(p, tol=1e-3, rtype='min'): + """Determine unique roots and their multiplicities from a list of roots. + + Parameters + ---------- + p : array_like + The list of roots. + tol : float, optional + The tolerance for two roots to be considered equal in terms of + the distance between them. Default is 1e-3. Refer to Notes about + the details on roots grouping. + rtype : {'max', 'maximum', 'min', 'minimum', 'avg', 'mean'}, optional + How to determine the returned root if multiple roots are within + `tol` of each other. + + - 'max', 'maximum': pick the maximum of those roots + - 'min', 'minimum': pick the minimum of those roots + - 'avg', 'mean': take the average of those roots + + When finding minimum or maximum among complex roots they are compared + first by the real part and then by the imaginary part. + + Returns + ------- + unique : ndarray + The list of unique roots. + multiplicity : ndarray + The multiplicity of each root. + + Notes + ----- + If we have 3 roots ``a``, ``b`` and ``c``, such that ``a`` is close to + ``b`` and ``b`` is close to ``c`` (distance is less than `tol`), then it + doesn't necessarily mean that ``a`` is close to ``c``. It means that roots + grouping is not unique. In this function we use "greedy" grouping going + through the roots in the order they are given in the input `p`. + + This utility function is not specific to roots but can be used for any + sequence of values for which uniqueness and multiplicity has to be + determined. For a more general routine, see `numpy.unique`. + + Examples + -------- + >>> from scipy import signal + >>> vals = [0, 1.3, 1.31, 2.8, 1.25, 2.2, 10.3] + >>> uniq, mult = signal.unique_roots(vals, tol=2e-2, rtype='avg') + + Check which roots have multiplicity larger than 1: + + >>> uniq[mult > 1] + array([ 1.305]) + """ + if rtype in ['max', 'maximum']: + reduce = np.max + elif rtype in ['min', 'minimum']: + reduce = np.min + elif rtype in ['avg', 'mean']: + reduce = np.mean + else: + raise ValueError("`rtype` must be one of " + "{'max', 'maximum', 'min', 'minimum', 'avg', 'mean'}") + + p = np.asarray(p) + + points = np.empty((len(p), 2)) + points[:, 0] = np.real(p) + points[:, 1] = np.imag(p) + tree = cKDTree(points) + + p_unique = [] + p_multiplicity = [] + used = np.zeros(len(p), dtype=bool) + for i in range(len(p)): + if used[i]: + continue + + group = tree.query_ball_point(points[i], tol) + group = [x for x in group if not used[x]] + + p_unique.append(reduce(p[group])) + p_multiplicity.append(len(group)) + + used[group] = True + + return np.asarray(p_unique), np.asarray(p_multiplicity) + + +def invres(r, p, k, tol=1e-3, rtype='avg'): + """Compute b(s) and a(s) from partial fraction expansion. + + If `M` is the degree of numerator `b` and `N` the degree of denominator + `a`:: + + b(s) b[0] s**(M) + b[1] s**(M-1) + ... + b[M] + H(s) = ------ = ------------------------------------------ + a(s) a[0] s**(N) + a[1] s**(N-1) + ... + a[N] + + then the partial-fraction expansion H(s) is defined as:: + + r[0] r[1] r[-1] + = -------- + -------- + ... + --------- + k(s) + (s-p[0]) (s-p[1]) (s-p[-1]) + + If there are any repeated roots (closer together than `tol`), then H(s) + has terms like:: + + r[i] r[i+1] r[i+n-1] + -------- + ----------- + ... + ----------- + (s-p[i]) (s-p[i])**2 (s-p[i])**n + + This function is used for polynomials in positive powers of s or z, + such as analog filters or digital filters in controls engineering. For + negative powers of z (typical for digital filters in DSP), use `invresz`. + + Parameters + ---------- + r : array_like + Residues corresponding to the poles. For repeated poles, the residues + must be ordered to correspond to ascending by power fractions. + p : array_like + Poles. Equal poles must be adjacent. + k : array_like + Coefficients of the direct polynomial term. + tol : float, optional + The tolerance for two roots to be considered equal in terms of + the distance between them. Default is 1e-3. See `unique_roots` + for further details. + rtype : {'avg', 'min', 'max'}, optional + Method for computing a root to represent a group of identical roots. + Default is 'avg'. See `unique_roots` for further details. + + Returns + ------- + b : ndarray + Numerator polynomial coefficients. + a : ndarray + Denominator polynomial coefficients. + + See Also + -------- + residue, invresz, unique_roots + + """ + r = np.atleast_1d(r) + p = np.atleast_1d(p) + k = np.trim_zeros(np.atleast_1d(k), 'f') + + unique_poles, multiplicity = _group_poles(p, tol, rtype) + factors, denominator = _compute_factors(unique_poles, multiplicity, + include_powers=True) + + if len(k) == 0: + numerator = 0 + else: + numerator = np.polymul(k, denominator) + + for residue, factor in zip(r, factors): + numerator = np.polyadd(numerator, residue * factor) + + return numerator, denominator + + +def _compute_factors(roots, multiplicity, include_powers=False): + """Compute the total polynomial divided by factors for each root.""" + current = np.array([1]) + suffixes = [current] + for pole, mult in zip(roots[-1:0:-1], multiplicity[-1:0:-1]): + monomial = np.array([1, -pole]) + for _ in range(mult): + current = np.polymul(current, monomial) + suffixes.append(current) + suffixes = suffixes[::-1] + + factors = [] + current = np.array([1]) + for pole, mult, suffix in zip(roots, multiplicity, suffixes): + monomial = np.array([1, -pole]) + block = [] + for i in range(mult): + if i == 0 or include_powers: + block.append(np.polymul(current, suffix)) + current = np.polymul(current, monomial) + factors.extend(reversed(block)) + + return factors, current + + +def _compute_residues(poles, multiplicity, numerator): + denominator_factors, _ = _compute_factors(poles, multiplicity) + numerator = numerator.astype(poles.dtype) + + residues = [] + for pole, mult, factor in zip(poles, multiplicity, + denominator_factors): + if mult == 1: + residues.append(np.polyval(numerator, pole) / + np.polyval(factor, pole)) + else: + numer = numerator.copy() + monomial = np.array([1, -pole]) + factor, d = np.polydiv(factor, monomial) + + block = [] + for _ in range(mult): + numer, n = np.polydiv(numer, monomial) + r = n[0] / d[0] + numer = np.polysub(numer, r * factor) + block.append(r) + + residues.extend(reversed(block)) + + return np.asarray(residues) + + +def residue(b, a, tol=1e-3, rtype='avg'): + """Compute partial-fraction expansion of b(s) / a(s). + + If `M` is the degree of numerator `b` and `N` the degree of denominator + `a`:: + + b(s) b[0] s**(M) + b[1] s**(M-1) + ... + b[M] + H(s) = ------ = ------------------------------------------ + a(s) a[0] s**(N) + a[1] s**(N-1) + ... + a[N] + + then the partial-fraction expansion H(s) is defined as:: + + r[0] r[1] r[-1] + = -------- + -------- + ... + --------- + k(s) + (s-p[0]) (s-p[1]) (s-p[-1]) + + If there are any repeated roots (closer together than `tol`), then H(s) + has terms like:: + + r[i] r[i+1] r[i+n-1] + -------- + ----------- + ... + ----------- + (s-p[i]) (s-p[i])**2 (s-p[i])**n + + This function is used for polynomials in positive powers of s or z, + such as analog filters or digital filters in controls engineering. For + negative powers of z (typical for digital filters in DSP), use `residuez`. + + See Notes for details about the algorithm. + + Parameters + ---------- + b : array_like + Numerator polynomial coefficients. + a : array_like + Denominator polynomial coefficients. + tol : float, optional + The tolerance for two roots to be considered equal in terms of + the distance between them. Default is 1e-3. See `unique_roots` + for further details. + rtype : {'avg', 'min', 'max'}, optional + Method for computing a root to represent a group of identical roots. + Default is 'avg'. See `unique_roots` for further details. + + Returns + ------- + r : ndarray + Residues corresponding to the poles. For repeated poles, the residues + are ordered to correspond to ascending by power fractions. + p : ndarray + Poles ordered by magnitude in ascending order. + k : ndarray + Coefficients of the direct polynomial term. + + See Also + -------- + invres, residuez, numpy.poly, unique_roots + + Notes + ----- + The "deflation through subtraction" algorithm is used for + computations --- method 6 in [1]_. + + The form of partial fraction expansion depends on poles multiplicity in + the exact mathematical sense. However there is no way to exactly + determine multiplicity of roots of a polynomial in numerical computing. + Thus you should think of the result of `residue` with given `tol` as + partial fraction expansion computed for the denominator composed of the + computed poles with empirically determined multiplicity. The choice of + `tol` can drastically change the result if there are close poles. + + References + ---------- + .. [1] J. F. Mahoney, B. D. Sivazlian, "Partial fractions expansion: a + review of computational methodology and efficiency", Journal of + Computational and Applied Mathematics, Vol. 9, 1983. + """ + b = np.asarray(b) + a = np.asarray(a) + if (np.issubdtype(b.dtype, np.complexfloating) + or np.issubdtype(a.dtype, np.complexfloating)): + b = b.astype(complex) + a = a.astype(complex) + else: + b = b.astype(float) + a = a.astype(float) + + b = np.trim_zeros(np.atleast_1d(b), 'f') + a = np.trim_zeros(np.atleast_1d(a), 'f') + + if a.size == 0: + raise ValueError("Denominator `a` is zero.") + + poles = np.roots(a) + if b.size == 0: + return np.zeros(poles.shape), _cmplx_sort(poles)[0], np.array([]) + + if len(b) < len(a): + k = np.empty(0) + else: + k, b = np.polydiv(b, a) + + unique_poles, multiplicity = unique_roots(poles, tol=tol, rtype=rtype) + unique_poles, order = _cmplx_sort(unique_poles) + multiplicity = multiplicity[order] + + residues = _compute_residues(unique_poles, multiplicity, b) + + index = 0 + for pole, mult in zip(unique_poles, multiplicity): + poles[index:index + mult] = pole + index += mult + + return residues / a[0], poles, k + + +def residuez(b, a, tol=1e-3, rtype='avg'): + """Compute partial-fraction expansion of b(z) / a(z). + + If `M` is the degree of numerator `b` and `N` the degree of denominator + `a`:: + + b(z) b[0] + b[1] z**(-1) + ... + b[M] z**(-M) + H(z) = ------ = ------------------------------------------ + a(z) a[0] + a[1] z**(-1) + ... + a[N] z**(-N) + + then the partial-fraction expansion H(z) is defined as:: + + r[0] r[-1] + = --------------- + ... + ---------------- + k[0] + k[1]z**(-1) ... + (1-p[0]z**(-1)) (1-p[-1]z**(-1)) + + If there are any repeated roots (closer than `tol`), then the partial + fraction expansion has terms like:: + + r[i] r[i+1] r[i+n-1] + -------------- + ------------------ + ... + ------------------ + (1-p[i]z**(-1)) (1-p[i]z**(-1))**2 (1-p[i]z**(-1))**n + + This function is used for polynomials in negative powers of z, + such as digital filters in DSP. For positive powers, use `residue`. + + See Notes of `residue` for details about the algorithm. + + Parameters + ---------- + b : array_like + Numerator polynomial coefficients. + a : array_like + Denominator polynomial coefficients. + tol : float, optional + The tolerance for two roots to be considered equal in terms of + the distance between them. Default is 1e-3. See `unique_roots` + for further details. + rtype : {'avg', 'min', 'max'}, optional + Method for computing a root to represent a group of identical roots. + Default is 'avg'. See `unique_roots` for further details. + + Returns + ------- + r : ndarray + Residues corresponding to the poles. For repeated poles, the residues + are ordered to correspond to ascending by power fractions. + p : ndarray + Poles ordered by magnitude in ascending order. + k : ndarray + Coefficients of the direct polynomial term. + + See Also + -------- + invresz, residue, unique_roots + """ + b = np.asarray(b) + a = np.asarray(a) + if (np.issubdtype(b.dtype, np.complexfloating) + or np.issubdtype(a.dtype, np.complexfloating)): + b = b.astype(complex) + a = a.astype(complex) + else: + b = b.astype(float) + a = a.astype(float) + + b = np.trim_zeros(np.atleast_1d(b), 'b') + a = np.trim_zeros(np.atleast_1d(a), 'b') + + if a.size == 0: + raise ValueError("Denominator `a` is zero.") + elif a[0] == 0: + raise ValueError("First coefficient of determinant `a` must be " + "non-zero.") + + poles = np.roots(a) + if b.size == 0: + return np.zeros(poles.shape), _cmplx_sort(poles)[0], np.array([]) + + b_rev = b[::-1] + a_rev = a[::-1] + + if len(b_rev) < len(a_rev): + k_rev = np.empty(0) + else: + k_rev, b_rev = np.polydiv(b_rev, a_rev) + + unique_poles, multiplicity = unique_roots(poles, tol=tol, rtype=rtype) + unique_poles, order = _cmplx_sort(unique_poles) + multiplicity = multiplicity[order] + + residues = _compute_residues(1 / unique_poles, multiplicity, b_rev) + + index = 0 + powers = np.empty(len(residues), dtype=int) + for pole, mult in zip(unique_poles, multiplicity): + poles[index:index + mult] = pole + powers[index:index + mult] = 1 + np.arange(mult) + index += mult + + residues *= (-poles) ** powers / a_rev[0] + + return residues, poles, k_rev[::-1] + + +def _group_poles(poles, tol, rtype): + if rtype in ['max', 'maximum']: + reduce = np.max + elif rtype in ['min', 'minimum']: + reduce = np.min + elif rtype in ['avg', 'mean']: + reduce = np.mean + else: + raise ValueError("`rtype` must be one of " + "{'max', 'maximum', 'min', 'minimum', 'avg', 'mean'}") + + unique = [] + multiplicity = [] + + pole = poles[0] + block = [pole] + for i in range(1, len(poles)): + if abs(poles[i] - pole) <= tol: + block.append(pole) + else: + unique.append(reduce(block)) + multiplicity.append(len(block)) + pole = poles[i] + block = [pole] + + unique.append(reduce(block)) + multiplicity.append(len(block)) + + return np.asarray(unique), np.asarray(multiplicity) + + +def invresz(r, p, k, tol=1e-3, rtype='avg'): + """Compute b(z) and a(z) from partial fraction expansion. + + If `M` is the degree of numerator `b` and `N` the degree of denominator + `a`:: + + b(z) b[0] + b[1] z**(-1) + ... + b[M] z**(-M) + H(z) = ------ = ------------------------------------------ + a(z) a[0] + a[1] z**(-1) + ... + a[N] z**(-N) + + then the partial-fraction expansion H(z) is defined as:: + + r[0] r[-1] + = --------------- + ... + ---------------- + k[0] + k[1]z**(-1) ... + (1-p[0]z**(-1)) (1-p[-1]z**(-1)) + + If there are any repeated roots (closer than `tol`), then the partial + fraction expansion has terms like:: + + r[i] r[i+1] r[i+n-1] + -------------- + ------------------ + ... + ------------------ + (1-p[i]z**(-1)) (1-p[i]z**(-1))**2 (1-p[i]z**(-1))**n + + This function is used for polynomials in negative powers of z, + such as digital filters in DSP. For positive powers, use `invres`. + + Parameters + ---------- + r : array_like + Residues corresponding to the poles. For repeated poles, the residues + must be ordered to correspond to ascending by power fractions. + p : array_like + Poles. Equal poles must be adjacent. + k : array_like + Coefficients of the direct polynomial term. + tol : float, optional + The tolerance for two roots to be considered equal in terms of + the distance between them. Default is 1e-3. See `unique_roots` + for further details. + rtype : {'avg', 'min', 'max'}, optional + Method for computing a root to represent a group of identical roots. + Default is 'avg'. See `unique_roots` for further details. + + Returns + ------- + b : ndarray + Numerator polynomial coefficients. + a : ndarray + Denominator polynomial coefficients. + + See Also + -------- + residuez, unique_roots, invres + + """ + r = np.atleast_1d(r) + p = np.atleast_1d(p) + k = np.trim_zeros(np.atleast_1d(k), 'b') + + unique_poles, multiplicity = _group_poles(p, tol, rtype) + factors, denominator = _compute_factors(unique_poles, multiplicity, + include_powers=True) + + if len(k) == 0: + numerator = 0 + else: + numerator = np.polymul(k[::-1], denominator[::-1]) + + for residue, factor in zip(r, factors): + numerator = np.polyadd(numerator, residue * factor[::-1]) + + return numerator[::-1], denominator + + +def resample(x, num, t=None, axis=0, window=None, domain='time'): + r"""Resample `x` to `num` samples using the Fourier method along the given `axis`. + + The resampling is performed by shortening or zero-padding the FFT of `x`. This has + the advantages of providing an ideal antialiasing filter and allowing arbitrary + up- or down-sampling ratios. The main drawback is the requirement of assuming `x` + to be a periodic signal. + + Parameters + ---------- + x : array_like + The input signal made up of equidistant samples. If `x` is a multidimensional + array, the parameter `axis` specifies the time/frequency axis. It is assumed + here that ``n_x = x.shape[axis]`` specifies the number of samples and ``T`` the + sampling interval. + num : int + The number of samples of the resampled output signal. It may be larger or + smaller than ``n_x``. + t : array_like, optional + If `t` is not ``None``, then the timestamps of the resampled signal are also + returned. `t` must contain at least the first two timestamps of the input + signal `x` (all others are ignored). The timestamps of the output signal are + determined by ``t[0] + T * n_x / num * np.arange(num)`` with + ``T = t[1] - t[0]``. Default is ``None``. + axis : int, optional + The time/frequency axis of `x` along which the resampling take place. + The Default is 0. + window : array_like, callable, str, float, or tuple, optional + If not ``None``, it specifies a filter in the Fourier domain, which is applied + before resampling. I.e., the FFT ``X`` of `x` is calculated by + ``X = W * fft(x, axis=axis)``. ``W`` may be interpreted as a spectral windowing + function ``W(f_X)`` which consumes the frequencies ``f_X = fftfreq(n_x, T)``. + + If `window` is a 1d array of length ``n_x`` then ``W=window``. + If `window` is a callable then ``W = window(f_X)``. + Otherwise, `window` is passed to `~scipy.signal.get_window`, i.e., + ``W = fftshift(signal.get_window(window, n_x))``. Default is ``None``. + + domain : 'time' | 'freq', optional + If set to ``'time'`` (default) then an FFT is applied to `x`, otherwise + (``'freq'``) it is asssmued that an FFT was already applied, i.e., + ``x = fft(x_t, axis=axis)`` with ``x_t`` being the input signal in the time + domain. + + Returns + ------- + x_r : ndarray + The resampled signal made up of `num` samples and sampling interval + ``T * n_x / num``. + t_r : ndarray, optional + The `num` equidistant timestamps of `x_r`. + This is only returned if paramater `t` is not ``None``. + + See Also + -------- + decimate : Downsample a (periodic/non-periodic) signal after applying an FIR + or IIR filter. + resample_poly : Resample a (periodic/non-periodic) signal using polyphase filtering + and an FIR filter. + + Notes + ----- + This function uses the more efficient one-sided FFT, i.e. `~scipy.fft.rfft` / + `~scipy.fft.irfft`, if `x` is real-valued and in the time domain. + Else, the two-sided FFT, i.e., `~scipy.fft.fft` / `~scipy.fft.ifft`, is used + (all FFT functions are taken from the `scipy.fft` module). + + If a `window` is applied to a real-valued `x`, the one-sided spectral windowing + function is determined by taking the average of the negative and the positive + frequency component. This ensures that real-valued signals and complex signals with + zero imaginary part are treated identically. I.e., passing `x` or passing + ``x.astype(np.complex128)`` produce the same numeric result. + + If the number of input or output samples are prime or have few prime factors, this + function may be slow due to utilizing FFTs. Consult `~scipy.fft.prev_fast_len` and + `~scipy.fft.next_fast_len` for determining efficient signals lengths. + Alternatively, utilizing `resample_poly` to calculate an intermediate signal (as + illustrated in the example below) can result in significant speed increases. + + `resample` is intended to be used for periodic signals with equidistant sampling + intervals. For non-periodic signals, `resample_poly` may be a better choice. + Consult the `scipy.interpolate` module for methods of resampling signals with + non-constant sampling intervals. + + Examples + -------- + The following example depicts a signal being up-sampled from 20 samples to 100 + samples. The ringing at the beginning of the up-sampled signal is due to + interpreting the signal being periodic. The red square in the plot illustrates that + periodictiy by showing the first sample of the next cycle of the signal. + + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from scipy.signal import resample + ... + >>> n0, n1 = 20, 100 # number of samples + >>> t0 = np.linspace(0, 10, n0, endpoint=False) # input time stamps + >>> x0 = np.cos(-t0**2/6) # input signal + ... + >>> x1 = resample(x0, n1) # resampled signal + >>> t1 = np.linspace(0, 10, n1, endpoint=False) # timestamps of x1 + ... + >>> fig0, ax0 = plt.subplots(1, 1, tight_layout=True) + >>> ax0.set_title(f"Resampling $x(t)$ from {n0} samples to {n1} samples") + >>> ax0.set(xlabel="Time $t$", ylabel="Amplitude $x(t)$") + >>> ax0.plot(t1, x1, '.-', alpha=.5, label=f"Resampled") + >>> ax0.plot(t0, x0, 'o-', alpha=.5, label="Original") + >>> ax0.plot(10, x0[0], 'rs', alpha=.5, label="Next Cycle") + >>> ax0.legend(loc='best') + >>> ax0.grid(True) + >>> plt.show() + + The following example compares this function with a naive `~scipy.fft.rfft` / + `~scipy.fft.irfft` combination: An input signal with a sampling interval of one + second is upsampled by a factor of eight. The first figure depicts an odd number of + input samples whereas the second figure an even number. The upper subplots show the + signals over time: The input samples are marked by large green dots, the upsampled + signals by a continuous and a dashed line. The lower subplots show the magnitude + spectrum: The FFT values of the input are depicted by large green dots, which lie + in the frequency interval [-0.5, 0.5] Hz, whereas the frequency interval of the + upsampled signal is [-4, 4] Hz. The continuous green line depicts the upsampled + spectrum without antialiasing filter, which is a periodic continuation of the input + spectrum. The blue x's and orange dots depict the FFT values of the signal created + by the naive approach as well as this function's result. + + >>> import matplotlib.pyplot as plt + >>> import numpy as np + >>> from scipy.fft import fftshift, fftfreq, fft, rfft, irfft + >>> from scipy.signal import resample, resample_poly + ... + >>> fac, T0, T1 = 8, 1, 1/8 # upsampling factor and sampling intervals + >>> for n0 in (15, 16): # number of samples of input signal + ... n1 = fac * n0 # number of samples of upsampled signal + ... t0, t1 = T0 * np.arange(n0), T1 * np.arange(n1) # time stamps + ... x0 = np.zeros(n0) # input signal has two non-zero sample values + ... x0[n0//2], x0[n0//2+1] = n0 // 2, -(n0 // 2) + ... + ... x1n = irfft(rfft(x0), n=n1) * n1 / n0 # naive resampling + ... x1r = resample(x0, n1) # resample signal + ... + ... # Determine magnitude spectrum: + ... x0_up = np.zeros_like(x1r) # upsampling without antialiasing filter + ... x0_up[::n1 // n0] = x0 + ... X0, X0_up = (fftshift(fft(x_)) / n0 for x_ in (x0, x0_up)) + ... XX1 = (fftshift(fft(x_)) / n1 for x_ in (x1n, x1r)) + ... f0, f1 = fftshift(fftfreq(n0, T0)), fftshift(fftfreq(n1, T1)) # frequencies + ... df = f0[1] - f0[0] # frequency resolution + ... + ... fig, (ax0, ax1) = plt.subplots(2, 1, layout='constrained', figsize=(5, 4)) + ... ax0.set_title(rf"Upsampling ${fac}\times$ from {n0} to {n1} samples") + ... ax0.set(xlabel="Time $t$ in seconds", ylabel="Amplitude $x(t)$", + ... xlim=(0, n1*T1)) + ... ax0.step(t0, x0, 'C2o-', where='post', alpha=.3, linewidth=2, + ... label="$x_0(t)$ / $X_0(f)$") + ... for x_, l_ in zip((x1n, x1r), ('C0--', 'C1-')): + ... ax0.plot(t1, x_, l_, alpha=.5, label=None) + ... ax0.grid() + ... ax1.set(xlabel=rf"Frequency $f$ in hertz ($\Delta f = {df*1e3:.1f}\,$mHz)", + ... ylabel="Magnitude $|X(f)|$", xlim=(-0.7, 0.7)) + ... ax1.axvspan(0.5/T0, f1[-1], color='gray', alpha=.2) + ... ax1.axvspan(f1[0], -0.5/T0, color='gray', alpha=.2) + ... ax1.plot(f1, abs(X0_up), 'C2-', f0, abs(X0), 'C2o', alpha=.3, linewidth=2) + ... for X_, n_, l_ in zip(XX1, ("naive", "resample"), ('C0x--', 'C1.-')): + ... ax1.plot(f1, abs(X_), l_, alpha=.5, label=n_) + ... ax1.grid() + ... fig.legend(loc='outside lower center', ncols=4) + >>> plt.show() + + The first figure shows that upsampling an odd number of samples produces identical + results. The second figure illustrates that the signal produced with the naive + approach (dashed blue line) from an even number of samples does not touch all + original samples. This deviation is due to `resample` correctly treating unpaired + frequency bins. I.e., the input `x1` has a bin pair ±0.5 Hz, whereas the output has + only one unpaired bin at -0.5 Hz, which demands rescaling of that bin pair. + Generally, special treatment is required if ``n_x != num`` and ``min(n_x, num)`` is + even. If the bin values at `±m` are zero, obviously, no special treatment is + needed. Consult the source code of `resample` for details. + + The final example shows how to utilize `resample_poly` to speed up the + down-sampling: The input signal a non-zero value at :math:`t=0` and is downsampled + from 19937 to 128 samples. Since 19937 is prime, the FFT is expected to be slow. To + speed matters up, `resample_poly` is used to downsample first by a factor of ``n0 + // n1 = 155`` and then pass the result to `resample`. Two parameterization of + `resample_poly` are used: Passing ``padtype='wrap'`` treats the input as being + periodic wheras the default parametrization performs zero-padding. The upper + subplot shows the resulting signals over time whereas the lower subplot depicts the + resulting one-sided magnitude spectra. + + >>> import matplotlib.pyplot as plt + >>> import numpy as np + >>> from scipy.fft import rfftfreq, rfft + >>> from scipy.signal import resample, resample_poly + ... + >>> n0 = 19937 # number of input samples - prime + >>> n1 = 128 # number of output samples - fast FFT length + >>> T0, T1 = 1/n0, 1/n1 # sampling intervals + >>> t0, t1 = np.arange(n0)*T0, np.arange(n1)*T1 # time stamps + ... + >>> x0 = np.zeros(n0) # Input has one non-zero sample + >>> x0[0] = n0 + >>> + >>> x1r = resample(x0, n1) # slow due to n0 being prime + >>> # This is faster: + >>> x1p = resample(resample_poly(x0, 1, n0 // n1, padtype='wrap'), n1) # periodic + >>> x2p = resample(resample_poly(x0, 1, n0 // n1), n1) # with zero-padding + ... + >>> X0 = rfft(x0) / n0 + >>> X1r, X1p, X2p = rfft(x1r) / n1, rfft(x1p) / n1, rfft(x2p) / n1 + >>> f0, f1 = rfftfreq(n0, T0), rfftfreq(n1, T1) + ... + >>> fig, (ax0, ax1) = plt.subplots(2, 1, layout='constrained', figsize=(5, 4)) + >>> ax0.set_title(f"Dowsampled Impulse response (from {n0} to {n1} samples)") + >>> ax0.set(xlabel="Time $t$ in seconds", ylabel="Amplitude $x(t)$", xlim=(-T1, 1)) + >>> for x_ in (x1r, x1p, x2p): + ... ax0.plot(t1, x_, alpha=.5) + >>> ax0.grid() + >>> ax1.set(xlabel=rf"Frequency $f$ in hertz ($\Delta f = {f1[1]}\,$Hz)", + ... ylabel="Magnitude $|X(f)|$", xlim=(0, 0.55/T1)) + >>> ax1.axvspan(0.5/T1, f0[-1], color='gray', alpha=.2) + >>> ax1.plot(f1, abs(X1r), 'C0.-', alpha=.5, label="resample") + >>> ax1.plot(f1, abs(X1p), 'C1.-', alpha=.5, label="resample_poly(padtype='wrap')") + >>> ax1.plot(f1, abs(X2p), 'C2x-', alpha=.5, label="resample_poly") + >>> ax1.grid() + >>> fig.legend(loc='outside lower center', ncols=2) + >>> plt.show() + + The plots show that the results of the "pure" `resample` and the usage of the + default parameters of `resample_poly` agree well. The periodic padding of + `resample_poly` (``padtype='wrap'``) on the other hand produces significant + deviations. This is caused by the disconiuity at the beginning of the signal, for + which the default filter of `resample_poly` is not suited well. This example + illustrates that for some use cases, adapting the `resample_poly` parameters may + be beneficial. `resample` has a big advantage in this regard: It uses the ideal + antialiasing filter with the maximum bandwidth by default. + + Note that the doubled spectral magnitude at the Nyqist frequency of 64 Hz is due the + even number of ``n1=128`` output samples, which requires a special treatment as + discussed in the previous example. + """ + if domain not in ('time', 'freq'): + raise ValueError(f"Parameter {domain=} not in ('time', 'freq')!") + + xp = array_namespace(x, t) + x = xp.asarray(x) + if x.ndim > 1: # moving active axis to end allows to use `...` in indexing: + x = xp.moveaxis(x, axis, -1) + n_x = x.shape[-1] # number of samples along the time/frequency axis + s_fac = n_x / num # scaling factor represents sample interval dilatation + m = min(num, n_x) # number of relevant frequency bins + m2 = m // 2 + 1 # number of relevant frequency bins of a one-sided FFT + + if window is None: # Determine spectral windowing function: + W = None + elif callable(window): + W = window(sp_fft.fftfreq(n_x)) + elif hasattr(window, 'shape'): # must be an array object + if window.shape != (n_x,): + raise ValueError(f"{window.shape=} != ({n_x},), i.e., window length " + + "is not equal to number of frequency bins!") + W = xp.asarray(window, copy=True) # prevent modifying the function parameters + else: + W = sp_fft.fftshift(get_window(window, n_x, xp=xp)) + W = xp.astype(W, xp_default_dtype(xp)) # get_window always returns float64 + + if domain == 'time' and not xp.isdtype(x.dtype, 'complex floating'): # use rfft(): + X = sp_fft.rfft(x) + if W is not None: # fold window, i.e., W1[l] = (W[l] + W[-l]) / 2 for l > 0 + n_X = X.shape[-1] + W[1:n_X] += xp.flip(W[-n_X+1:]) #W[:-n_X:-1] + W[1:n_X] /= 2 + X *= W[:n_X] # apply window + X = X[..., :m2] # extract relevant data + if m % 2 == 0 and num != n_x: # Account for unpaired bin at m//2: + X[..., m//2] *= 2 if num < n_x else 0.5 + x_r = sp_fft.irfft(X / s_fac, n=num, overwrite_x=True) + else: # use standard two-sided FFT: + X = sp_fft.fft(x) if domain == 'time' else x + if W is not None: + X = X * W # writing X *= W could modify parameter x + Y = xp.zeros(X.shape[:-1] + (num,), dtype=X.dtype) + Y[..., :m2] = X[..., :m2] # copy part up to Nyquist frequency + if m2 < m: # == m > 2 + Y[..., m2-m:] = X[..., m2-m:] # copy negative frequency part + if m % 2 == 0: # Account for unpaired bin at m//2: + if num < n_x: # down-sampling: unite bin pair into one unpaired bin + Y[..., -m//2] += X[..., -m//2] + elif n_x < num: # up-sampling: split unpaired bin into bin pair + Y[..., m//2] /= 2 + Y[..., num-m//2] = Y[..., m//2] + x_r = sp_fft.ifft(Y / s_fac, n=num, overwrite_x=True) + + if x_r.ndim > 1: # moving active axis back to original position: + x_r = xp.moveaxis(x_r, -1, axis) + if t is not None: + return x_r, t[0] + (t[1] - t[0]) * s_fac * xp.arange(num) + return x_r + + +def resample_poly(x, up, down, axis=0, window=('kaiser', 5.0), + padtype='constant', cval=None): + """ + Resample `x` along the given axis using polyphase filtering. + + The signal `x` is upsampled by the factor `up`, a zero-phase low-pass + FIR filter is applied, and then it is downsampled by the factor `down`. + The resulting sample rate is ``up / down`` times the original sample + rate. By default, values beyond the boundary of the signal are assumed + to be zero during the filtering step. + + Parameters + ---------- + x : array_like + The data to be resampled. + up : int + The upsampling factor. + down : int + The downsampling factor. + axis : int, optional + The axis of `x` that is resampled. Default is 0. + window : str, tuple, or array_like, optional + Desired window to use to design the low-pass filter, or the FIR filter + coefficients to employ. See below for details. + padtype : str, optional + `constant`, `line`, `mean`, `median`, `maximum`, `minimum` or any of + the other signal extension modes supported by `scipy.signal.upfirdn`. + Changes assumptions on values beyond the boundary. If `constant`, + assumed to be `cval` (default zero). If `line` assumed to continue a + linear trend defined by the first and last points. `mean`, `median`, + `maximum` and `minimum` work as in `np.pad` and assume that the values + beyond the boundary are the mean, median, maximum or minimum + respectively of the array along the axis. + + .. versionadded:: 1.4.0 + cval : float, optional + Value to use if `padtype='constant'`. Default is zero. + + .. versionadded:: 1.4.0 + + Returns + ------- + resampled_x : array + The resampled array. + + See Also + -------- + decimate : Downsample the signal after applying an FIR or IIR filter. + resample : Resample up or down using the FFT method. + + Notes + ----- + This polyphase method will likely be faster than the Fourier method + in `scipy.signal.resample` when the number of samples is large and + prime, or when the number of samples is large and `up` and `down` + share a large greatest common denominator. The length of the FIR + filter used will depend on ``max(up, down) // gcd(up, down)``, and + the number of operations during polyphase filtering will depend on + the filter length and `down` (see `scipy.signal.upfirdn` for details). + + The argument `window` specifies the FIR low-pass filter design. + + If `window` is an array_like it is assumed to be the FIR filter + coefficients. Note that the FIR filter is applied after the upsampling + step, so it should be designed to operate on a signal at a sampling + frequency higher than the original by a factor of `up//gcd(up, down)`. + This function's output will be centered with respect to this array, so it + is best to pass a symmetric filter with an odd number of samples if, as + is usually the case, a zero-phase filter is desired. + + For any other type of `window`, the functions `scipy.signal.get_window` + and `scipy.signal.firwin` are called to generate the appropriate filter + coefficients. + + The first sample of the returned vector is the same as the first + sample of the input vector. The spacing between samples is changed + from ``dx`` to ``dx * down / float(up)``. + + Examples + -------- + By default, the end of the resampled data rises to meet the first + sample of the next cycle for the FFT method, and gets closer to zero + for the polyphase method: + + >>> import numpy as np + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + + >>> x = np.linspace(0, 10, 20, endpoint=False) + >>> y = np.cos(-x**2/6.0) + >>> f_fft = signal.resample(y, 100) + >>> f_poly = signal.resample_poly(y, 100, 20) + >>> xnew = np.linspace(0, 10, 100, endpoint=False) + + >>> plt.plot(xnew, f_fft, 'b.-', xnew, f_poly, 'r.-') + >>> plt.plot(x, y, 'ko-') + >>> plt.plot(10, y[0], 'bo', 10, 0., 'ro') # boundaries + >>> plt.legend(['resample', 'resamp_poly', 'data'], loc='best') + >>> plt.show() + + This default behaviour can be changed by using the padtype option: + + >>> N = 5 + >>> x = np.linspace(0, 1, N, endpoint=False) + >>> y = 2 + x**2 - 1.7*np.sin(x) + .2*np.cos(11*x) + >>> y2 = 1 + x**3 + 0.1*np.sin(x) + .1*np.cos(11*x) + >>> Y = np.stack([y, y2], axis=-1) + >>> up = 4 + >>> xr = np.linspace(0, 1, N*up, endpoint=False) + + >>> y2 = signal.resample_poly(Y, up, 1, padtype='constant') + >>> y3 = signal.resample_poly(Y, up, 1, padtype='mean') + >>> y4 = signal.resample_poly(Y, up, 1, padtype='line') + + >>> for i in [0,1]: + ... plt.figure() + ... plt.plot(xr, y4[:,i], 'g.', label='line') + ... plt.plot(xr, y3[:,i], 'y.', label='mean') + ... plt.plot(xr, y2[:,i], 'r.', label='constant') + ... plt.plot(x, Y[:,i], 'k-') + ... plt.legend() + >>> plt.show() + + """ + xp = array_namespace(x) + + x = xp.asarray(x) + if up != int(up): + raise ValueError("up must be an integer") + if down != int(down): + raise ValueError("down must be an integer") + up = int(up) + down = int(down) + if up < 1 or down < 1: + raise ValueError('up and down must be >= 1') + if cval is not None and padtype != 'constant': + raise ValueError('cval has no effect when padtype is ', padtype) + + # Determine our up and down factors + # Use a rational approximation to save computation time on really long + # signals + g_ = math.gcd(up, down) + up //= g_ + down //= g_ + if up == down == 1: + return xp.asarray(x, copy=True) + n_in = x.shape[axis] + n_out = n_in * up + n_out = n_out // down + bool(n_out % down) + + if isinstance(window, list) or is_array_api_obj(window): + window = xp.asarray(window, copy=True) # force a copy (we modify `window`) + if window.ndim > 1: + raise ValueError('window must be 1-D') + half_len = (xp_size(window) - 1) // 2 + h = window + else: + # Design a linear-phase low-pass FIR filter + max_rate = max(up, down) + f_c = 1. / max_rate # cutoff of FIR filter (rel. to Nyquist) + half_len = 10 * max_rate # reasonable cutoff for sinc-like function + if xp.isdtype(x.dtype, ("real floating", "complex floating")): + h = firwin(2 * half_len + 1, f_c, window=window) + h = xp.asarray(h, dtype=x.dtype) # match dtype of x + else: + h = firwin(2 * half_len + 1, f_c, window=window) + h = xp.asarray(h) + + h *= up + + # Zero-pad our filter to put the output samples at the center + n_pre_pad = (down - half_len % down) + n_post_pad = 0 + n_pre_remove = (half_len + n_pre_pad) // down + # We should rarely need to do this given our filter lengths... + while _output_len(h.shape[0] + n_pre_pad + n_post_pad, n_in, + up, down) < n_out + n_pre_remove: + n_post_pad += 1 + h = xp.concat((xp.zeros(n_pre_pad, dtype=h.dtype), h, + xp.zeros(n_post_pad, dtype=h.dtype))) + n_pre_remove_end = n_pre_remove + n_out + + # XXX consider using stats.quantile, which is natively Array API compatible + def _median(x, *args, **kwds): + return xp.asarray(np.median(np.asarray(x), *args, **kwds)) + + # Remove background depending on the padtype option + funcs = {'mean': xp.mean, 'median': _median, + 'minimum': xp.min, 'maximum': xp.max} + upfirdn_kwargs = {'mode': 'constant', 'cval': 0} + if padtype in funcs: + background_values = funcs[padtype](x, axis=axis, keepdims=True) + elif padtype in _upfirdn_modes: + upfirdn_kwargs = {'mode': padtype} + if padtype == 'constant': + if cval is None: + cval = 0 + upfirdn_kwargs['cval'] = cval + else: + raise ValueError( + 'padtype must be one of: maximum, mean, median, minimum, ' + + ', '.join(_upfirdn_modes)) + + if padtype in funcs: + x = x - background_values + + # filter then remove excess + y = upfirdn(h, x, up, down, axis=axis, **upfirdn_kwargs) + keep = [slice(None), ]*x.ndim + keep[axis] = slice(n_pre_remove, n_pre_remove_end) + y_keep = y[tuple(keep)] + + # Add background back + if padtype in funcs: + y_keep += background_values + + return y_keep + + +def _angle(z, xp): + """np.angle replacement + """ + # XXX: https://github.com/data-apis/array-api/issues/595 + zimag = xp.imag(z) if xp.isdtype(z.dtype, 'complex floating') else 0. + a = xp.atan2(zimag, xp.real(z)) + return a + + +def vectorstrength(events, period): + ''' + Determine the vector strength of the events corresponding to the given + period. + + The vector strength is a measure of phase synchrony, how well the + timing of the events is synchronized to a single period of a periodic + signal. + + If multiple periods are used, calculate the vector strength of each. + This is called the "resonating vector strength". + + Parameters + ---------- + events : 1D array_like + An array of time points containing the timing of the events. + period : float or array_like + The period of the signal that the events should synchronize to. + The period is in the same units as `events`. It can also be an array + of periods, in which case the outputs are arrays of the same length. + + Returns + ------- + strength : float or 1D array + The strength of the synchronization. 1.0 is perfect synchronization + and 0.0 is no synchronization. If `period` is an array, this is also + an array with each element containing the vector strength at the + corresponding period. + phase : float or array + The phase that the events are most strongly synchronized to in radians. + If `period` is an array, this is also an array with each element + containing the phase for the corresponding period. + + References + ---------- + van Hemmen, JL, Longtin, A, and Vollmayr, AN. Testing resonating vector + strength: Auditory system, electric fish, and noise. + Chaos 21, 047508 (2011); + :doi:`10.1063/1.3670512`. + van Hemmen, JL. Vector strength after Goldberg, Brown, and von Mises: + biological and mathematical perspectives. Biol Cybern. + 2013 Aug;107(4):385-96. :doi:`10.1007/s00422-013-0561-7`. + van Hemmen, JL and Vollmayr, AN. Resonating vector strength: what happens + when we vary the "probing" frequency while keeping the spike times + fixed. Biol Cybern. 2013 Aug;107(4):491-94. + :doi:`10.1007/s00422-013-0560-8`. + ''' + xp = array_namespace(events, period) + + events = xp.asarray(events) + period = xp.asarray(period) + if xp.isdtype(period.dtype, 'integral'): + period = xp.astype(period, xp.float64) + + if events.ndim > 1: + raise ValueError('events cannot have dimensions more than 1') + if period.ndim > 1: + raise ValueError('period cannot have dimensions more than 1') + + # we need to know later if period was originally a scalar + scalarperiod = not period.ndim + + events = xpx.atleast_nd(events, ndim=2, xp=xp) + period = xpx.atleast_nd(period, ndim=2, xp=xp) + if xp.any(period <= 0): + raise ValueError('periods must be positive') + + # this converts the times to vectors + events_ = xp.astype(events, period.dtype) + vectors = xp.exp(2j * (xp.pi / period.T @ events_)) + + # the vector strength is just the magnitude of the mean of the vectors + # the vector phase is the angle of the mean of the vectors + vectormean = xp.mean(vectors, axis=1) + strength = xp.abs(vectormean) + phase = _angle(vectormean, xp) + + # if the original period was a scalar, return scalars + if scalarperiod: + strength = strength[0] + phase = phase[0] + return strength, phase + + +def detrend(data: np.ndarray, axis: int = -1, + type: Literal['linear', 'constant'] = 'linear', + bp: ArrayLike | int = 0, overwrite_data: bool = False) -> np.ndarray: + r"""Remove linear or constant trend along axis from data. + + Parameters + ---------- + data : array_like + The input data. + axis : int, optional + The axis along which to detrend the data. By default this is the + last axis (-1). + type : {'linear', 'constant'}, optional + The type of detrending. If ``type == 'linear'`` (default), + the result of a linear least-squares fit to `data` is subtracted + from `data`. + If ``type == 'constant'``, only the mean of `data` is subtracted. + bp : array_like of ints, optional + A sequence of break points. If given, an individual linear fit is + performed for each part of `data` between two break points. + Break points are specified as indices into `data`. This parameter + only has an effect when ``type == 'linear'``. + overwrite_data : bool, optional + If True, allow in place detrending and avoid a copy. Default is + False. In place modification applies only if ``type == 'linear'`` + and `data` is of the floating point dtype ``float32``, ``float64``, + ``complex64`` or ``complex128``. + + Returns + ------- + ret : ndarray + The detrended input data. + + Notes + ----- + Detrending can be interpreted as subtracting a least squares fit polynomial: + Setting the parameter `type` to 'constant' corresponds to fitting a zeroth degree + polynomial, 'linear' to a first degree polynomial. Consult the example below. + + See Also + -------- + :meth:`numpy.polynomial.polynomial.Polynomial.fit` : Create least squares fit polynomial. + + + Examples + -------- + The following example detrends the function :math:`x(t) = \sin(\pi t) + 1/4`: + + >>> import matplotlib.pyplot as plt + >>> import numpy as np + >>> from scipy.signal import detrend + ... + >>> t = np.linspace(-0.5, 0.5, 21) + >>> x = np.sin(np.pi*t) + 1/4 + ... + >>> x_d_const = detrend(x, type='constant') + >>> x_d_linear = detrend(x, type='linear') + ... + >>> fig1, ax1 = plt.subplots() + >>> ax1.set_title(r"Detrending $x(t)=\sin(\pi t) + 1/4$") + >>> ax1.set(xlabel="t", ylabel="$x(t)$", xlim=(t[0], t[-1])) + >>> ax1.axhline(y=0, color='black', linewidth=.5) + >>> ax1.axvline(x=0, color='black', linewidth=.5) + >>> ax1.plot(t, x, 'C0.-', label="No detrending") + >>> ax1.plot(t, x_d_const, 'C1x-', label="type='constant'") + >>> ax1.plot(t, x_d_linear, 'C2+-', label="type='linear'") + >>> ax1.legend() + >>> plt.show() + + Alternatively, NumPy's `~numpy.polynomial.polynomial.Polynomial` can be used for + detrending as well: + + >>> pp0 = np.polynomial.Polynomial.fit(t, x, deg=0) # fit degree 0 polynomial + >>> np.allclose(x_d_const, x - pp0(t)) # compare with constant detrend + True + >>> pp1 = np.polynomial.Polynomial.fit(t, x, deg=1) # fit degree 1 polynomial + >>> np.allclose(x_d_linear, x - pp1(t)) # compare with linear detrend + True + + Note that `~numpy.polynomial.polynomial.Polynomial` also allows fitting higher + degree polynomials. Consult its documentation on how to extract the polynomial + coefficients. + """ # noqa: E501 + if type not in ['linear', 'l', 'constant', 'c']: + raise ValueError("Trend type must be 'linear' or 'constant'.") + + xp = array_namespace(data, bp) + + data = np.asarray(data) + dtype = data.dtype.char + if dtype not in 'dfDF': + dtype = 'd' + if type in ['constant', 'c']: + ret = data - np.mean(data, axis, keepdims=True) + return xp.asarray(ret) + else: + dshape = data.shape + N = dshape[axis] + bp = np.asarray(bp) + bp = np.sort(np.unique(np.concatenate(np.atleast_1d(0, bp, N)))) + if np.any(bp > N): + raise ValueError("Breakpoints must be less than length " + "of data along given axis.") + + # Restructure data so that axis is along first dimension and + # all other dimensions are collapsed into second dimension + rnk = len(dshape) + if axis < 0: + axis = axis + rnk + newdata = np.moveaxis(data, axis, 0) + newdata_shape = newdata.shape + newdata = newdata.reshape(N, -1) + + if not overwrite_data: + newdata = newdata.copy() # make sure we have a copy + if newdata.dtype.char not in 'dfDF': + newdata = newdata.astype(dtype) + +# Nreg = len(bp) - 1 + # Find leastsq fit and remove it for each piece + for m in range(len(bp) - 1): + Npts = bp[m + 1] - bp[m] + A = np.ones((Npts, 2), dtype) + A[:, 0] = np.arange(1, Npts + 1, dtype=dtype) / Npts + sl = slice(bp[m], bp[m + 1]) + coef, resids, rank, s = linalg.lstsq(A, newdata[sl]) + newdata[sl] = newdata[sl] - A @ coef + + # Put data back in original shape. + newdata = newdata.reshape(newdata_shape) + ret = np.moveaxis(newdata, 0, axis) + return xp.asarray(ret) + + +def lfilter_zi(b, a): + r"""Construct initial conditions for `lfilter` for step response steady-state. + + Compute an initial state `zi` for the `lfilter` function that corresponds + to the steady state of the step response. + + A typical use of this function is to set the initial state so that the + output of the filter starts at the same value as the first element of + the signal to be filtered. + + Parameters + ---------- + b : array_like + The numerator coefficient vector as a 1-D sequence. + a : array_like + The denominator coefficient vector as a 1-D sequence. If ``a[0]`` + is not 1, then both `a` and `b` are normalized by ``a[0]``. + Hence, ``a[0] != 0`` must hold. + + Returns + ------- + zi : 1-D ndarray + The initial state for the filter. + + Raises + ------ + ValueError + If ``a[0] == 0`` (invalid denominator polynomial) or + ``sum(a) == 0`` (unstable filter). + + See Also + -------- + lfilter, lfiltic, filtfilt + + Notes + ----- + The parameters `b` and `a` represent a transfer function :math:`H(z) = Y(z)/X(z)` + which is defined in the :ref:`tutorial_signal_TransferFunctionRepresentation` + section of the :ref:`user_guide`. As discussed in [1]_, the final value of + filtering a step response :math:`X(z) = z / (z-1)`, i.e., steady state, is given by + + .. math:: + + y_\infty := \lim_{k\to\infty} y[k] + = \lim_{z\to 1}\ (z-1)\, Y(z) + = \frac{\sum_{i=0}^M b_i}{\sum_{j=0}^N a_j} \,. + + If the denominator is zero, :math:`H(z)` has a pole at :math:`z_\infty=1`, which + makes the filter unstable. For the transposed Direct Form II, which is implemented + in `lfilter`, the initialization values :math:`z_k` for :math:`H(z)` can be + determined by the recurrence equation + + .. math:: + + z_k = z_{k+1} + x[0] \big( b_k - y_\infty a_k \big) \,, + + with :math:`x[0]` being the height of the input step function. Note that + :math:`a_0=1` is assumed here, which is incorporated into this function by + performing a normalization step. + + Examples + -------- + The following code creates a lowpass Butterworth filter to filter a signal made up + of ones. As expected of a lowpass filter, the output is also all ones. If the `zi` + argument of `lfilter` had not been given, a transient signal would have been + produced. The second signal illustrates that using the parameter `zi` supresses + transients at the beginning of the output signal: + + >>> import numpy as np + >>> from scipy.signal import lfilter, lfilter_zi, butter + ... + >>> b, a = butter(5, 0.25) + >>> zi = lfilter_zi(b, a) + >>> y0, zi0 = lfilter(b, a, np.ones(10), zi=zi) + >>> y0 + array([1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]) + >>> # Another signal: + >>> x = np.array([0.5, 0.5, 0.5, 0.0, 0.0, 0.0, 0.0]) + >>> y1, zi1 = lfilter(b, a, x, zi=zi*x[0]) + >>> y1 + array([ 0.5 , 0.5 , 0.5 , 0.49836039, 0.48610528, + 0.44399389, 0.35505241]) + + Note that the `zi` argument to `lfilter` is computed using `lfilter_zi` and scaled + by ``x[0]``. As a result, the output `y1` has no transient until the input drops + from 0.5 to 0. + + References + ---------- + .. [1] Boris Likhterov and Norman Kopeika. "Hardware-efficient technique for + minimizing startup transients in Direct Form II digital filters". In: + International Journal of Electronics -- Volume 90(7), July 2003, pp. + 471--479. :doi:`10.1080/00207210310001612482` + """ + xp = array_namespace(b, a) + + # Note: As an alternative to this function, using `lfiltic` might work as well. + # For example, when b,a = butter(N,Wn), then + # lfiltic(b, a, y=numpy.ones_like(a)*y_inf, x=numpy.ones_like(b)). + # should produce the same result. Though, no obvious algorithmic advantages over + # this implementation could be identified. + + # We could use scipy.signal.normalize, but it uses warnings in + # cases where a ValueError is more appropriate, and it allows + # b to be 2D. + b, a = xp_promote(b, a, xp=xp, force_floating=True) # need floats for division + b = xpx.atleast_nd(b, ndim=1, xp=xp) + a = xpx.atleast_nd(a, ndim=1, xp=xp) + + if not (b.ndim == a.ndim == 1): + raise ValueError("Numerator `b` and Denominator `a` must be 1-D arrays, " + + f"but {b.shape = }, {a.shape = }!") + + if a[0] == 0: + raise ValueError("First coefficient of parameter `a` must be non-zero!") + + if a[0] != 1: # Normalize the coefficients so a[0] == 1: + b, a = b / a[0], a / a[0] + + if (sum_a := xp.sum(a)) == 0: + raise ValueError("Filter not stable due to sum(a) == 0, i.e., " + + "having a pole at z = 1!") + + y_inf = xp.sum(b) / sum_a # y[k → ∞] for unit-step input + + # Calculate `zi[k] = zi[k+1] + b - y_inf*a` allowing different length for a, b: + n_a, n_b = a.shape[0], b.shape[0] + n = max(n_a, n_b) + b = xpx.pad(b, (0, n-n_b)) + a = xpx.pad(a, (0, n-n_a)) + # `xp.cumulative_sum((b - y_inf*a)[::-1])[-2::-1]` does not work in torch due to + # unsupported slicing with a negative step index. Hence, `flip` is used: + return xp.flip(xp.cumulative_sum(xp.flip(b - y_inf*a)))[1:] + +def sosfilt_zi(sos): + """ + Construct initial conditions for sosfilt for step response steady-state. + + Compute an initial state `zi` for the `sosfilt` function that corresponds + to the steady state of the step response. + + A typical use of this function is to set the initial state so that the + output of the filter starts at the same value as the first element of + the signal to be filtered. + + Parameters + ---------- + sos : array_like + Array of second-order filter coefficients, must have shape + ``(n_sections, 6)``. See `sosfilt` for the SOS filter format + specification. + + Returns + ------- + zi : ndarray + Initial conditions suitable for use with ``sosfilt``, shape + ``(n_sections, 2)``. + + See Also + -------- + sosfilt, zpk2sos + + Notes + ----- + .. versionadded:: 0.16.0 + + Examples + -------- + Filter a rectangular pulse that begins at time 0, with and without + the use of the `zi` argument of `scipy.signal.sosfilt`. + + >>> import numpy as np + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + + >>> sos = signal.butter(9, 0.125, output='sos') + >>> zi = signal.sosfilt_zi(sos) + >>> x = (np.arange(250) < 100).astype(int) + >>> f1 = signal.sosfilt(sos, x) + >>> f2, zo = signal.sosfilt(sos, x, zi=zi) + + >>> plt.plot(x, 'k--', label='x') + >>> plt.plot(f1, 'b', alpha=0.5, linewidth=2, label='filtered') + >>> plt.plot(f2, 'g', alpha=0.25, linewidth=4, label='filtered with zi') + >>> plt.legend(loc='best') + >>> plt.show() + + """ + xp = array_namespace(sos) + + sos = xp.asarray(sos) + if sos.ndim != 2 or sos.shape[1] != 6: + raise ValueError('sos must be shape (n_sections, 6)') + + if xp.isdtype(sos.dtype, ("integral", "bool")): + sos = xp.astype(sos, xp.float64) + + n_sections = sos.shape[0] + zi = xp.empty((n_sections, 2), dtype=sos.dtype) + scale = 1.0 + for section in range(n_sections): + b = sos[section, :3] + a = sos[section, 3:] + zi[section, ...] = scale * lfilter_zi(b, a) + # If H(z) = B(z)/A(z) is this section's transfer function, then + # b.sum()/a.sum() is H(1), the gain at omega=0. That's the steady + # state value of this section's step response. + scale *= xp.sum(b) / xp.sum(a) + + return zi + + +def _filtfilt_gust(b, a, x, axis=-1, irlen=None): + """Forward-backward IIR filter that uses Gustafsson's method. + + Apply the IIR filter defined by ``(b,a)`` to `x` twice, first forward + then backward, using Gustafsson's initial conditions [1]_. + + Let ``y_fb`` be the result of filtering first forward and then backward, + and let ``y_bf`` be the result of filtering first backward then forward. + Gustafsson's method is to compute initial conditions for the forward + pass and the backward pass such that ``y_fb == y_bf``. + + Parameters + ---------- + b : scalar or 1-D ndarray + Numerator coefficients of the filter. + a : scalar or 1-D ndarray + Denominator coefficients of the filter. + x : ndarray + Data to be filtered. + axis : int, optional + Axis of `x` to be filtered. Default is -1. + irlen : int or None, optional + The length of the nonnegligible part of the impulse response. + If `irlen` is None, or if the length of the signal is less than + ``2 * irlen``, then no part of the impulse response is ignored. + + Returns + ------- + y : ndarray + The filtered data. + x0 : ndarray + Initial condition for the forward filter. + x1 : ndarray + Initial condition for the backward filter. + + Notes + ----- + Typically the return values `x0` and `x1` are not needed by the + caller. The intended use of these return values is in unit tests. + + References + ---------- + .. [1] F. Gustaffson. Determining the initial states in forward-backward + filtering. Transactions on Signal Processing, 46(4):988-992, 1996. + + """ + # In the comments, "Gustafsson's paper" and [1] refer to the + # paper referenced in the docstring. + + b = np.atleast_1d(b) + a = np.atleast_1d(a) + + order = max(len(b), len(a)) - 1 + if order == 0: + # The filter is just scalar multiplication, with no state. + scale = (b[0] / a[0])**2 + y = scale * x + return y, np.array([]), np.array([]) + + if axis != -1 or axis != x.ndim - 1: + # Move the axis containing the data to the end. + x = np.swapaxes(x, axis, x.ndim - 1) + + # n is the number of samples in the data to be filtered. + n = x.shape[-1] + + if irlen is None or n <= 2*irlen: + m = n + else: + m = irlen + + # Create Obs, the observability matrix (called O in the paper). + # This matrix can be interpreted as the operator that propagates + # an arbitrary initial state to the output, assuming the input is + # zero. + # In Gustafsson's paper, the forward and backward filters are not + # necessarily the same, so he has both O_f and O_b. We use the same + # filter in both directions, so we only need O. The same comment + # applies to S below. + Obs = np.zeros((m, order)) + zi = np.zeros(order) + zi[0] = 1 + Obs[:, 0] = lfilter(b, a, np.zeros(m), zi=zi)[0] + for k in range(1, order): + Obs[k:, k] = Obs[:-k, 0] + + # Obsr is O^R (Gustafsson's notation for row-reversed O) + Obsr = Obs[::-1] + + # Create S. S is the matrix that applies the filter to the reversed + # propagated initial conditions. That is, + # out = S.dot(zi) + # is the same as + # tmp, _ = lfilter(b, a, zeros(), zi=zi) # Propagate ICs. + # out = lfilter(b, a, tmp[::-1]) # Reverse and filter. + + # Equations (5) & (6) of [1] + S = lfilter(b, a, Obs[::-1], axis=0) + + # Sr is S^R (row-reversed S) + Sr = S[::-1] + + # M is [(S^R - O), (O^R - S)] + if m == n: + M = np.hstack((Sr - Obs, Obsr - S)) + else: + # Matrix described in section IV of [1]. + M = np.zeros((2*m, 2*order)) + M[:m, :order] = Sr - Obs + M[m:, order:] = Obsr - S + + # Naive forward-backward and backward-forward filters. + # These have large transients because the filters use zero initial + # conditions. + y_f = lfilter(b, a, x) + y_fb = lfilter(b, a, y_f[..., ::-1])[..., ::-1] + + y_b = lfilter(b, a, x[..., ::-1])[..., ::-1] + y_bf = lfilter(b, a, y_b) + + delta_y_bf_fb = y_bf - y_fb + if m == n: + delta = delta_y_bf_fb + else: + start_m = delta_y_bf_fb[..., :m] + end_m = delta_y_bf_fb[..., -m:] + delta = np.concatenate((start_m, end_m), axis=-1) + + # ic_opt holds the "optimal" initial conditions. + # The following code computes the result shown in the formula + # of the paper between equations (6) and (7). + if delta.ndim == 1: + ic_opt = linalg.lstsq(M, delta)[0] + else: + # Reshape delta so it can be used as an array of multiple + # right-hand-sides in linalg.lstsq. + delta2d = delta.reshape(-1, delta.shape[-1]).T + ic_opt0 = linalg.lstsq(M, delta2d)[0].T + ic_opt = ic_opt0.reshape(delta.shape[:-1] + (M.shape[-1],)) + + # Now compute the filtered signal using equation (7) of [1]. + # First, form [S^R, O^R] and call it W. + if m == n: + W = np.hstack((Sr, Obsr)) + else: + W = np.zeros((2*m, 2*order)) + W[:m, :order] = Sr + W[m:, order:] = Obsr + + # Equation (7) of [1] says + # Y_fb^opt = Y_fb^0 + W * [x_0^opt; x_{N-1}^opt] + # `wic` is (almost) the product on the right. + # W has shape (m, 2*order), and ic_opt has shape (..., 2*order), + # so we can't use W.dot(ic_opt). Instead, we dot ic_opt with W.T, + # so wic has shape (..., m). + wic = ic_opt.dot(W.T) + + # `wic` is "almost" the product of W and the optimal ICs in equation + # (7)--if we're using a truncated impulse response (m < n), `wic` + # contains only the adjustments required for the ends of the signal. + # Here we form y_opt, taking this into account if necessary. + y_opt = y_fb + if m == n: + y_opt += wic + else: + y_opt[..., :m] += wic[..., :m] + y_opt[..., -m:] += wic[..., -m:] + + x0 = ic_opt[..., :order] + x1 = ic_opt[..., -order:] + if axis != -1 or axis != x.ndim - 1: + # Restore the data axis to its original position. + x0 = np.swapaxes(x0, axis, x.ndim - 1) + x1 = np.swapaxes(x1, axis, x.ndim - 1) + y_opt = np.swapaxes(y_opt, axis, x.ndim - 1) + + return y_opt, x0, x1 + + +def filtfilt(b, a, x, axis=-1, padtype='odd', padlen=None, method='pad', + irlen=None): + """ + Apply a digital filter forward and backward to a signal. + + This function applies a linear digital filter twice, once forward and + once backwards. The combined filter has zero phase and a filter order + twice that of the original. + + The function provides options for handling the edges of the signal. + + The function `sosfiltfilt` (and filter design using ``output='sos'``) + should be preferred over `filtfilt` for most filtering tasks, as + second-order sections have fewer numerical problems. + + Parameters + ---------- + b : (N,) array_like + The numerator coefficient vector of the filter. + a : (N,) array_like + The denominator coefficient vector of the filter. If ``a[0]`` + is not 1, then both `a` and `b` are normalized by ``a[0]``. + x : array_like + The array of data to be filtered. + axis : int, optional + The axis of `x` to which the filter is applied. + Default is -1. + padtype : str or None, optional + Must be 'odd', 'even', 'constant', or None. This determines the + type of extension to use for the padded signal to which the filter + is applied. If `padtype` is None, no padding is used. The default + is 'odd'. + padlen : int or None, optional + The number of elements by which to extend `x` at both ends of + `axis` before applying the filter. This value must be less than + ``x.shape[axis] - 1``. ``padlen=0`` implies no padding. + The default value is ``3 * max(len(a), len(b))``. + method : str, optional + Determines the method for handling the edges of the signal, either + "pad" or "gust". When `method` is "pad", the signal is padded; the + type of padding is determined by `padtype` and `padlen`, and `irlen` + is ignored. When `method` is "gust", Gustafsson's method is used, + and `padtype` and `padlen` are ignored. + irlen : int or None, optional + When `method` is "gust", `irlen` specifies the length of the + impulse response of the filter. If `irlen` is None, no part + of the impulse response is ignored. For a long signal, specifying + `irlen` can significantly improve the performance of the filter. + + Returns + ------- + y : ndarray + The filtered output with the same shape as `x`. + + See Also + -------- + sosfiltfilt, lfilter_zi, lfilter, lfiltic, savgol_filter, sosfilt + + Notes + ----- + When `method` is "pad", the function pads the data along the given axis + in one of three ways: odd, even or constant. The odd and even extensions + have the corresponding symmetry about the end point of the data. The + constant extension extends the data with the values at the end points. On + both the forward and backward passes, the initial condition of the + filter is found by using `lfilter_zi` and scaling it by the end point of + the extended data. + + When `method` is "gust", Gustafsson's method [1]_ is used. Initial + conditions are chosen for the forward and backward passes so that the + forward-backward filter gives the same result as the backward-forward + filter. + + The option to use Gustaffson's method was added in scipy version 0.16.0. + + References + ---------- + .. [1] F. Gustaffson, "Determining the initial states in forward-backward + filtering", Transactions on Signal Processing, Vol. 46, pp. 988-992, + 1996. + + Examples + -------- + The examples will use several functions from `scipy.signal`. + + >>> import numpy as np + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + + First we create a one second signal that is the sum of two pure sine + waves, with frequencies 5 Hz and 250 Hz, sampled at 2000 Hz. + + >>> t = np.linspace(0, 1.0, 2001) + >>> xlow = np.sin(2 * np.pi * 5 * t) + >>> xhigh = np.sin(2 * np.pi * 250 * t) + >>> x = xlow + xhigh + + Now create a lowpass Butterworth filter with a cutoff of 0.125 times + the Nyquist frequency, or 125 Hz, and apply it to ``x`` with `filtfilt`. + The result should be approximately ``xlow``, with no phase shift. + + >>> b, a = signal.butter(8, 0.125) + >>> y = signal.filtfilt(b, a, x, padlen=150) + >>> np.abs(y - xlow).max() + 9.1086182074789912e-06 + + We get a fairly clean result for this artificial example because + the odd extension is exact, and with the moderately long padding, + the filter's transients have dissipated by the time the actual data + is reached. In general, transient effects at the edges are + unavoidable. + + The following example demonstrates the option ``method="gust"``. + + First, create a filter. + + >>> b, a = signal.ellip(4, 0.01, 120, 0.125) # Filter to be applied. + + `sig` is a random input signal to be filtered. + + >>> rng = np.random.default_rng() + >>> n = 60 + >>> sig = rng.standard_normal(n)**3 + 3*rng.standard_normal(n).cumsum() + + Apply `filtfilt` to `sig`, once using the Gustafsson method, and + once using padding, and plot the results for comparison. + + >>> fgust = signal.filtfilt(b, a, sig, method="gust") + >>> fpad = signal.filtfilt(b, a, sig, padlen=50) + >>> plt.plot(sig, 'k-', label='input') + >>> plt.plot(fgust, 'b-', linewidth=4, label='gust') + >>> plt.plot(fpad, 'c-', linewidth=1.5, label='pad') + >>> plt.legend(loc='best') + >>> plt.show() + + The `irlen` argument can be used to improve the performance + of Gustafsson's method. + + Estimate the impulse response length of the filter. + + >>> z, p, k = signal.tf2zpk(b, a) + >>> eps = 1e-9 + >>> r = np.max(np.abs(p)) + >>> approx_impulse_len = int(np.ceil(np.log(eps) / np.log(r))) + >>> approx_impulse_len + 137 + + Apply the filter to a longer signal, with and without the `irlen` + argument. The difference between `y1` and `y2` is small. For long + signals, using `irlen` gives a significant performance improvement. + + >>> x = rng.standard_normal(4000) + >>> y1 = signal.filtfilt(b, a, x, method='gust') + >>> y2 = signal.filtfilt(b, a, x, method='gust', irlen=approx_impulse_len) + >>> print(np.max(np.abs(y1 - y2))) + 2.875334415008979e-10 + + """ + xp = array_namespace(b, a, x) + + b = np.atleast_1d(np.asarray(b)) + a = np.atleast_1d(np.asarray(a)) + x = np.asarray(x) + + if method not in ["pad", "gust"]: + raise ValueError("method must be 'pad' or 'gust'.") + + if method == "gust": + y, z1, z2 = _filtfilt_gust(b, a, x, axis=axis, irlen=irlen) + return xp.asarray(y) + + # method == "pad" + edge, ext = _validate_pad(padtype, padlen, x, axis, + ntaps=max(len(a), len(b))) + + # Get the steady state of the filter's step response. + zi = lfilter_zi(b, a) + + # Reshape zi and create x0 so that zi*x0 broadcasts + # to the correct value for the 'zi' keyword argument + # to lfilter. + zi_shape = [1] * x.ndim + zi_shape[axis] = zi.size + zi = np.reshape(zi, zi_shape) + x0 = axis_slice(ext, stop=1, axis=axis) + + # Forward filter. + (y, zf) = lfilter(b, a, ext, axis=axis, zi=zi * x0) + + # Backward filter. + # Create y0 so zi*y0 broadcasts appropriately. + y0 = axis_slice(y, start=-1, axis=axis) + (y, zf) = lfilter(b, a, axis_reverse(y, axis=axis), axis=axis, zi=zi * y0) + + # Reverse y. + y = axis_reverse(y, axis=axis) + + if edge > 0: + # Slice the actual signal from the extended signal. + y = axis_slice(y, start=edge, stop=-edge, axis=axis) + if is_torch(xp): + y = y.copy() # pytorch/pytorch#59786 : no negative strides in pytorch + + return xp.asarray(y) + + +def _validate_pad(padtype, padlen, x, axis, ntaps): + """Helper to validate padding for filtfilt""" + if padtype not in ['even', 'odd', 'constant', None]: + raise ValueError(f"Unknown value '{padtype}' given to padtype. " + "padtype must be 'even', 'odd', 'constant', or None.") + + if padtype is None: + padlen = 0 + + if padlen is None: + # Original padding; preserved for backwards compatibility. + edge = ntaps * 3 + else: + edge = padlen + + # x's 'axis' dimension must be bigger than edge. + if x.shape[axis] <= edge: + raise ValueError( + f"The length of the input vector x must be greater than padlen, " + f"which is {edge}." + ) + + if padtype is not None and edge > 0: + # Make an extension of length `edge` at each + # end of the input array. + if padtype == 'even': + ext = even_ext(x, edge, axis=axis) + elif padtype == 'odd': + ext = odd_ext(x, edge, axis=axis) + else: + ext = const_ext(x, edge, axis=axis) + else: + ext = x + return edge, ext + + +def _validate_x(x): + x = np.asarray(x) + if x.ndim == 0: + raise ValueError('x must be at least 1-D') + return x + + +def sosfilt(sos, x, axis=-1, zi=None): + """ + Filter data along one dimension using cascaded second-order sections. + + Filter a data sequence, `x`, using a digital IIR filter defined by + `sos`. + + Parameters + ---------- + sos : array_like + Array of second-order filter coefficients, must have shape + ``(n_sections, 6)``. Each row corresponds to a second-order + section, with the first three columns providing the numerator + coefficients and the last three providing the denominator + coefficients. + x : array_like + An N-dimensional input array. + axis : int, optional + The axis of the input data array along which to apply the + linear filter. The filter is applied to each subarray along + this axis. Default is -1. + zi : array_like, optional + Initial conditions for the cascaded filter delays. It is a (at + least 2D) vector of shape ``(n_sections, ..., 2, ...)``, where + ``..., 2, ...`` denotes the shape of `x`, but with ``x.shape[axis]`` + replaced by 2. If `zi` is None or is not given then initial rest + (i.e. all zeros) is assumed. + Note that these initial conditions are *not* the same as the initial + conditions given by `lfiltic` or `lfilter_zi`. + + Returns + ------- + y : ndarray + The output of the digital filter. + zf : ndarray, optional + If `zi` is None, this is not returned, otherwise, `zf` holds the + final filter delay values. + + See Also + -------- + zpk2sos, sos2zpk, sosfilt_zi, sosfiltfilt, freqz_sos + + Notes + ----- + The filter function is implemented as a series of second-order filters + with direct-form II transposed structure. It is designed to minimize + numerical precision errors for high-order filters. + + .. versionadded:: 0.16.0 + + Examples + -------- + Plot a 13th-order filter's impulse response using both `lfilter` and + `sosfilt`, showing the instability that results from trying to do a + 13th-order filter in a single stage (the numerical error pushes some poles + outside of the unit circle): + + >>> import matplotlib.pyplot as plt + >>> from scipy import signal + >>> b, a = signal.ellip(13, 0.009, 80, 0.05, output='ba') + >>> sos = signal.ellip(13, 0.009, 80, 0.05, output='sos') + >>> x = signal.unit_impulse(700) + >>> y_tf = signal.lfilter(b, a, x) + >>> y_sos = signal.sosfilt(sos, x) + >>> plt.plot(y_tf, 'r', label='TF') + >>> plt.plot(y_sos, 'k', label='SOS') + >>> plt.legend(loc='best') + >>> plt.show() + + """ + xp = array_namespace(sos, x, zi) + + x = _validate_x(x) + sos, n_sections = _validate_sos(sos) + x_zi_shape = list(x.shape) + x_zi_shape[axis] = 2 + x_zi_shape = tuple([n_sections] + x_zi_shape) + inputs = [sos, x] + if zi is not None: + inputs.append(np.asarray(zi)) + dtype = np.result_type(*inputs) + if dtype.char not in 'fdgFDGO': + raise NotImplementedError(f"input type '{dtype}' not supported") + if zi is not None: + zi = np.asarray(zi, dtype=dtype) + + # make a copy so that we can operate in place + # NB: 1. use xp_copy to paper over numpy 1/2 copy= keyword + # 2. make sure the copied zi remains a numpy array + zi = xp_copy(zi, xp=array_namespace(zi)) + if zi.shape != x_zi_shape: + raise ValueError( + f"Invalid zi shape. With axis={axis!r}, " + f"an input with shape {x.shape!r}, " + f"and an sos array with {n_sections} sections, zi must have " + f"shape {x_zi_shape!r}, got {zi.shape!r}." + ) + return_zi = True + else: + zi = np.zeros(x_zi_shape, dtype=dtype) + return_zi = False + axis = axis % x.ndim # make positive + x = np.moveaxis(x, axis, -1) + zi = np.moveaxis(zi, (0, axis + 1), (-2, -1)) + x_shape, zi_shape = x.shape, zi.shape + x = np.reshape(x, (-1, x.shape[-1])) + x = np.array(x, dtype, order='C') # make a copy, can modify in place + zi = np.ascontiguousarray(np.reshape(zi, (-1, n_sections, 2))) + sos = sos.astype(dtype, copy=False) + _sosfilt(sos, x, zi) + x = x.reshape(x_shape) + x = np.moveaxis(x, -1, axis) + if return_zi: + zi = zi.reshape(zi_shape) + zi = np.moveaxis(zi, (-2, -1), (0, axis + 1)) + out = (xp.asarray(x), xp.asarray(zi)) + else: + out = xp.asarray(x) + return out + + +def sosfiltfilt(sos, x, axis=-1, padtype='odd', padlen=None): + """ + A forward-backward digital filter using cascaded second-order sections. + + See `filtfilt` for more complete information about this method. + + Parameters + ---------- + sos : array_like + Array of second-order filter coefficients, must have shape + ``(n_sections, 6)``. Each row corresponds to a second-order + section, with the first three columns providing the numerator + coefficients and the last three providing the denominator + coefficients. + x : array_like + The array of data to be filtered. + axis : int, optional + The axis of `x` to which the filter is applied. + Default is -1. + padtype : str or None, optional + Must be 'odd', 'even', 'constant', or None. This determines the + type of extension to use for the padded signal to which the filter + is applied. If `padtype` is None, no padding is used. The default + is 'odd'. + padlen : int or None, optional + The number of elements by which to extend `x` at both ends of + `axis` before applying the filter. This value must be less than + ``x.shape[axis] - 1``. ``padlen=0`` implies no padding. + The default value is:: + + 3 * (2 * len(sos) + 1 - min((sos[:, 2] == 0).sum(), + (sos[:, 5] == 0).sum())) + + The extra subtraction at the end attempts to compensate for poles + and zeros at the origin (e.g. for odd-order filters) to yield + equivalent estimates of `padlen` to those of `filtfilt` for + second-order section filters built with `scipy.signal` functions. + + Returns + ------- + y : ndarray + The filtered output with the same shape as `x`. + + See Also + -------- + filtfilt, sosfilt, sosfilt_zi, freqz_sos + + Notes + ----- + .. versionadded:: 0.18.0 + + Examples + -------- + >>> import numpy as np + >>> from scipy.signal import sosfiltfilt, butter + >>> import matplotlib.pyplot as plt + >>> rng = np.random.default_rng() + + Create an interesting signal to filter. + + >>> n = 201 + >>> t = np.linspace(0, 1, n) + >>> x = 1 + (t < 0.5) - 0.25*t**2 + 0.05*rng.standard_normal(n) + + Create a lowpass Butterworth filter, and use it to filter `x`. + + >>> sos = butter(4, 0.125, output='sos') + >>> y = sosfiltfilt(sos, x) + + For comparison, apply an 8th order filter using `sosfilt`. The filter + is initialized using the mean of the first four values of `x`. + + >>> from scipy.signal import sosfilt, sosfilt_zi + >>> sos8 = butter(8, 0.125, output='sos') + >>> zi = x[:4].mean() * sosfilt_zi(sos8) + >>> y2, zo = sosfilt(sos8, x, zi=zi) + + Plot the results. Note that the phase of `y` matches the input, while + `y2` has a significant phase delay. + + >>> plt.plot(t, x, alpha=0.5, label='x(t)') + >>> plt.plot(t, y, label='y(t)') + >>> plt.plot(t, y2, label='y2(t)') + >>> plt.legend(framealpha=1, shadow=True) + >>> plt.grid(alpha=0.25) + >>> plt.xlabel('t') + >>> plt.show() + + """ + xp = array_namespace(sos, x) + + sos, n_sections = _validate_sos(sos) + x = _validate_x(x) + + # `method` is "pad"... + ntaps = 2 * n_sections + 1 + ntaps -= min((sos[:, 2] == 0).sum(), (sos[:, 5] == 0).sum()) + edge, ext = _validate_pad(padtype, padlen, x, axis, + ntaps=ntaps) + + # These steps follow the same form as filtfilt with modifications + zi = sosfilt_zi(sos) # shape (n_sections, 2) --> (n_sections, ..., 2, ...) + zi_shape = [1] * x.ndim + zi_shape[axis] = 2 + zi = zi.reshape([n_sections] + zi_shape) + x_0 = axis_slice(ext, stop=1, axis=axis) + (y, zf) = sosfilt(sos, ext, axis=axis, zi=zi * x_0) + y_0 = axis_slice(y, start=-1, axis=axis) + (y, zf) = sosfilt(sos, axis_reverse(y, axis=axis), axis=axis, zi=zi * y_0) + y = axis_reverse(y, axis=axis) + if edge > 0: + y = axis_slice(y, start=edge, stop=-edge, axis=axis) + return xp.asarray(y) + + +def decimate(x, q, n=None, ftype='iir', axis=-1, zero_phase=True): + """ + Downsample the signal after applying an anti-aliasing filter. + + By default, an order 8 Chebyshev type I filter is used. A 30 point FIR + filter with Hamming window is used if `ftype` is 'fir'. + + Parameters + ---------- + x : array_like + The input signal made up of equidistant samples. If `x` is a multidimensional + array, the parameter `axis` specifies the time axis. + q : int + The downsampling factor, which is a postive integer. When using IIR + downsampling, it is recommended to call `decimate` multiple times for + downsampling factors higher than 13. + n : int, optional + The order of the filter (1 less than the length for 'fir'). Defaults to + 8 for 'iir' and 20 times the downsampling factor for 'fir'. + ftype : str {'iir', 'fir'} or ``dlti`` instance, optional + If 'iir' or 'fir', specifies the type of lowpass filter. If an instance + of an `dlti` object, uses that object to filter before downsampling. + axis : int, optional + The axis along which to decimate. + zero_phase : bool, optional + Prevent phase shift by filtering with `filtfilt` instead of `lfilter` + when using an IIR filter, and shifting the outputs back by the filter's + group delay when using an FIR filter. The default value of ``True`` is + recommended, since a phase shift is generally not desired. + + .. versionadded:: 0.18.0 + + Returns + ------- + y : ndarray + The down-sampled signal. + + See Also + -------- + resample : Resample up or down using the FFT method. + resample_poly : Resample using polyphase filtering and an FIR filter. + + Notes + ----- + For non-integer downsampling factors, `~scipy.signal.resample` can be used. Consult + the `scipy.interpolate` module for methods of resampling signals with non-constant + sampling intervals. + + The ``zero_phase`` keyword was added in 0.18.0. + The possibility to use instances of ``dlti`` as ``ftype`` was added in + 0.18.0. + + Examples + -------- + + >>> import numpy as np + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + + Define wave parameters. + + >>> wave_duration = 3 + >>> sample_rate = 100 + >>> freq = 2 + >>> q = 5 + + Calculate number of samples. + + >>> samples = wave_duration*sample_rate + >>> samples_decimated = int(samples/q) + + Create cosine wave. + + >>> x = np.linspace(0, wave_duration, samples, endpoint=False) + >>> y = np.cos(x*np.pi*freq*2) + + Decimate cosine wave. + + >>> ydem = signal.decimate(y, q) + >>> xnew = np.linspace(0, wave_duration, samples_decimated, endpoint=False) + + Plot original and decimated waves. + + >>> plt.plot(x, y, '.-', xnew, ydem, 'o-') + >>> plt.xlabel('Time, Seconds') + >>> plt.legend(['data', 'decimated'], loc='best') + >>> plt.show() + + """ + + x = np.asarray(x) + q = operator.index(q) + + if n is not None: + n = operator.index(n) + + result_type = x.dtype + if not np.issubdtype(result_type, np.inexact) \ + or result_type.type == np.float16: + # upcast integers and float16 to float64 + result_type = np.float64 + + if ftype == 'fir': + if n is None: + half_len = 10 * q # reasonable cutoff for our sinc-like function + n = 2 * half_len + b, a = firwin(n+1, 1. / q, window='hamming'), 1. + b = np.asarray(b, dtype=result_type) + a = np.asarray(a, dtype=result_type) + elif ftype == 'iir': + iir_use_sos = True + if n is None: + n = 8 + sos = cheby1(n, 0.05, 0.8 / q, output='sos') + sos = np.asarray(sos, dtype=result_type) + elif isinstance(ftype, dlti): + system = ftype._as_zpk() + if system.poles.shape[0] == 0: + # FIR + system = ftype._as_tf() + b, a = system.num, system.den + ftype = 'fir' + elif (any(np.iscomplex(system.poles)) + or any(np.iscomplex(system.poles)) + or np.iscomplex(system.gain)): + # sosfilt & sosfiltfilt don't handle complex coeffs + iir_use_sos = False + system = ftype._as_tf() + b, a = system.num, system.den + else: + iir_use_sos = True + sos = zpk2sos(system.zeros, system.poles, system.gain) + sos = np.asarray(sos, dtype=result_type) + else: + raise ValueError('invalid ftype') + + sl = [slice(None)] * x.ndim + + if ftype == 'fir': + b = b / a + if zero_phase: + y = resample_poly(x, 1, q, axis=axis, window=b) + else: + # upfirdn is generally faster than lfilter by a factor equal to the + # downsampling factor, since it only calculates the needed outputs + n_out = x.shape[axis] // q + bool(x.shape[axis] % q) + y = upfirdn(b, x, up=1, down=q, axis=axis) + sl[axis] = slice(None, n_out, None) + + else: # IIR case + if zero_phase: + if iir_use_sos: + y = sosfiltfilt(sos, x, axis=axis) + else: + y = filtfilt(b, a, x, axis=axis) + else: + if iir_use_sos: + y = sosfilt(sos, x, axis=axis) + else: + y = lfilter(b, a, x, axis=axis) + + sl[axis] = slice(None, None, q) + + return y[tuple(sl)] diff --git a/venv/lib/python3.12/site-packages/scipy/signal/_sigtools.cpython-312-x86_64-linux-gnu.so b/venv/lib/python3.12/site-packages/scipy/signal/_sigtools.cpython-312-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..9fe4c737b6436b61989096d7ae423f944884ff30 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/_sigtools.cpython-312-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b95d8dd95b8ceb6cc99e59cf2672a0f98fa516c48cdc4c9a8b6ef30ae9f12c42 +size 108416 diff --git a/venv/lib/python3.12/site-packages/scipy/signal/_sosfilt.cpython-312-x86_64-linux-gnu.so b/venv/lib/python3.12/site-packages/scipy/signal/_sosfilt.cpython-312-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..02e3ca59c75f2af5affcc53bc47c3e8563307fb7 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/_sosfilt.cpython-312-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dc395d746cc9879aa41dcfd1144613525a44e561a11d5f34b651c20c8a3bba08 +size 110352 diff --git a/venv/lib/python3.12/site-packages/scipy/signal/_spectral_py.py b/venv/lib/python3.12/site-packages/scipy/signal/_spectral_py.py new file mode 100644 index 0000000000000000000000000000000000000000..7f0dcfe9bad9bedba4b9db33ba479f415a3f3ad5 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/_spectral_py.py @@ -0,0 +1,2489 @@ +"""Tools for spectral analysis. +""" +import numpy as np +import numpy.typing as npt +from scipy import fft as sp_fft +from scipy._lib.deprecation import _deprecate_positional_args, _NoValue +from . import _signaltools +from ._short_time_fft import ShortTimeFFT, FFT_MODE_TYPE +from .windows import get_window +from ._arraytools import const_ext, even_ext, odd_ext, zero_ext +import warnings +from typing import cast, Literal + + +__all__ = ['periodogram', 'welch', 'lombscargle', 'csd', 'coherence', + 'spectrogram', 'stft', 'istft', 'check_COLA', 'check_NOLA'] + + +@_deprecate_positional_args(version="1.19.0") +def lombscargle( + x: npt.ArrayLike, + y: npt.ArrayLike, + freqs: npt.ArrayLike, + *, + precenter: bool = _NoValue, # type:ignore[assignment] + normalize: bool | Literal["power", "normalize", "amplitude"] = False, + weights: npt.NDArray | None = None, + floating_mean: bool = False, +) -> npt.NDArray: + """ + Compute the generalized Lomb-Scargle periodogram. + + The Lomb-Scargle periodogram was developed by Lomb [1]_ and further + extended by Scargle [2]_ to find, and test the significance of weak + periodic signals with uneven temporal sampling. The algorithm used + here is based on a weighted least-squares fit of the form + ``y(ω) = a*cos(ω*x) + b*sin(ω*x) + c``, where the fit is calculated for + each frequency independently. This algorithm was developed by Zechmeister + and Kürster which improves the Lomb-Scargle periodogram by enabling + the weighting of individual samples and calculating an unknown y offset + (also called a "floating-mean" model) [3]_. For more details, and practical + considerations, see the excellent reference on the Lomb-Scargle periodogram [4]_. + + When *normalize* is False (or "power") (default) the computed periodogram + is unnormalized, it takes the value ``(A**2) * N/4`` for a harmonic + signal with amplitude A for sufficiently large N. Where N is the length of x or y. + + When *normalize* is True (or "normalize") the computed periodogram is normalized + by the residuals of the data around a constant reference model (at zero). + + When *normalize* is "amplitude" the computed periodogram is the complex + representation of the amplitude and phase. + + Input arrays should be 1-D of a real floating data type, which are converted into + float64 arrays before processing. + + Parameters + ---------- + x : array_like + Sample times. + y : array_like + Measurement values. Values are assumed to have a baseline of ``y = 0``. If + there is a possibility of a y offset, it is recommended to set `floating_mean` + to True. + freqs : array_like + Angular frequencies (e.g., having unit rad/s=2π/s for `x` having unit s) for + output periodogram. Frequencies are normally >= 0, as any peak at ``-freq`` will + also exist at ``+freq``. + precenter : bool, optional + Pre-center measurement values by subtracting the mean, if True. This is + a legacy parameter and unnecessary if `floating_mean` is True. + + .. deprecated:: 1.17.0 + The `precenter` argument is deprecated and will be removed in SciPy 1.19.0. + The functionality can be substituted by passing ``y - y.mean()`` to `y`. + + normalize : bool | str, optional + Compute normalized or complex (amplitude + phase) periodogram. + Valid options are: ``False``/``"power"``, ``True``/``"normalize"``, or + ``"amplitude"``. + weights : array_like, optional + Weights for each sample. Weights must be nonnegative. + floating_mean : bool, optional + Determines a y offset for each frequency independently, if True. + Else the y offset is assumed to be `0`. + + Returns + ------- + pgram : array_like + Lomb-Scargle periodogram. + + Raises + ------ + ValueError + If any of the input arrays x, y, freqs, or weights are not 1D, or if any are + zero length. Or, if the input arrays x, y, and weights do not have the same + shape as each other. + ValueError + If any weight is < 0, or the sum of the weights is <= 0. + ValueError + If the normalize parameter is not one of the allowed options. + + See Also + -------- + periodogram: Power spectral density using a periodogram + welch: Power spectral density by Welch's method + csd: Cross spectral density by Welch's method + + Notes + ----- + The algorithm used will not automatically account for any unknown y offset, unless + `floating_mean` is ``True``. Therefore, for most use cases, if there is a + possibility of a y offset, it is recommended to set `floating_mean` to ``True``. + Furthermore, `floating_mean` accounts for sample weights, and will also correct for + any bias due to consistently missing observations at peaks and/or troughs. + + The legacy concept of "pre-centering" entails removing the mean from parameter `y` + before processing, i.e., passing ``y - y.mean()`` instead of setting the parameter + `floating_mean` to ``True``. + + When the normalize parameter is "amplitude", for any frequency in freqs that is + below ``(2*pi)/(x.max() - x.min())``, the predicted amplitude will tend towards + infinity. The concept of a "Nyquist frequency" limit (see Nyquist-Shannon sampling + theorem) is not generally applicable to unevenly sampled data. Therefore, with + unevenly sampled data, valid frequencies in freqs can often be much higher than + expected for those familiar with methods like FFT. + + References + ---------- + .. [1] N.R. Lomb "Least-squares frequency analysis of unequally spaced + data", Astrophysics and Space Science, vol 39, pp. 447-462, 1976 + :doi:`10.1007/bf00648343` + + .. [2] J.D. Scargle "Studies in astronomical time series analysis. II - + Statistical aspects of spectral analysis of unevenly spaced data", + The Astrophysical Journal, vol 263, pp. 835-853, 1982 + :doi:`10.1086/160554` + + .. [3] M. Zechmeister and M. Kürster, "The generalised Lomb-Scargle periodogram. + A new formalism for the floating-mean and Keplerian periodograms," + Astronomy and Astrophysics, vol. 496, pp. 577-584, 2009 + :doi:`10.1051/0004-6361:200811296` + + .. [4] J.T. VanderPlas, "Understanding the Lomb-Scargle Periodogram," + The Astrophysical Journal Supplement Series, vol. 236, no. 1, p. 16, + May 2018 + :doi:`10.3847/1538-4365/aab766` + + + Examples + -------- + >>> import numpy as np + >>> rng = np.random.default_rng() + + First define some input parameters for the signal: + + >>> A = 2. # amplitude + >>> c = 2. # offset + >>> w0 = 1. # rad/sec + >>> nin = 150 + >>> nout = 1002 + + Randomly generate sample times: + + >>> x = rng.uniform(0, 10*np.pi, nin) + + Plot a sine wave for the selected times: + + >>> y = A * np.cos(w0*x) + c + + Define the array of frequencies for which to compute the periodogram: + + >>> w = np.linspace(0.25, 10, nout) + + Calculate Lomb-Scargle periodogram for each of the normalize options: + + >>> from scipy.signal import lombscargle + >>> pgram_power = lombscargle(x, y, w, normalize=False) + >>> pgram_norm = lombscargle(x, y, w, normalize=True) + >>> pgram_amp = lombscargle(x, y, w, normalize='amplitude') + ... + >>> pgram_power_f = lombscargle(x, y, w, normalize=False, floating_mean=True) + >>> pgram_norm_f = lombscargle(x, y, w, normalize=True, floating_mean=True) + >>> pgram_amp_f = lombscargle(x, y, w, normalize='amplitude', floating_mean=True) + + Now make a plot of the input data: + + >>> import matplotlib.pyplot as plt + >>> fig, (ax_t, ax_p, ax_n, ax_a) = plt.subplots(4, 1, figsize=(5, 6)) + >>> ax_t.plot(x, y, 'b+') + >>> ax_t.set_xlabel('Time [s]') + >>> ax_t.set_ylabel('Amplitude') + + Then plot the periodogram for each of the normalize options, as well as with and + without floating_mean=True: + + >>> ax_p.plot(w, pgram_power, label='default') + >>> ax_p.plot(w, pgram_power_f, label='floating_mean=True') + >>> ax_p.set_xlabel('Angular frequency [rad/s]') + >>> ax_p.set_ylabel('Power') + >>> ax_p.legend(prop={'size': 7}) + ... + >>> ax_n.plot(w, pgram_norm, label='default') + >>> ax_n.plot(w, pgram_norm_f, label='floating_mean=True') + >>> ax_n.set_xlabel('Angular frequency [rad/s]') + >>> ax_n.set_ylabel('Normalized') + >>> ax_n.legend(prop={'size': 7}) + ... + >>> ax_a.plot(w, np.abs(pgram_amp), label='default') + >>> ax_a.plot(w, np.abs(pgram_amp_f), label='floating_mean=True') + >>> ax_a.set_xlabel('Angular frequency [rad/s]') + >>> ax_a.set_ylabel('Amplitude') + >>> ax_a.legend(prop={'size': 7}) + ... + >>> plt.tight_layout() + >>> plt.show() + + """ + + # if no weights are provided, assume all data points are equally important + if weights is None: + weights = np.ones_like(y, dtype=np.float64) + else: + # if provided, make sure weights is an array and cast to float64 + weights = np.asarray(weights, dtype=np.float64) + + # make sure other inputs are arrays and cast to float64 + # done before validation, in case they were not arrays + x = np.asarray(x, dtype=np.float64) + y = np.asarray(y, dtype=np.float64) + freqs = np.asarray(freqs, dtype=np.float64) + + # validate input shapes + if not (x.ndim == 1 and x.size > 0 and x.shape == y.shape == weights.shape): + raise ValueError("Parameters x, y, weights must be 1-D arrays of " + "equal non-zero length!") + if not (freqs.ndim == 1 and freqs.size > 0): + raise ValueError("Parameter freqs must be a 1-D array of non-zero length!") + + # validate weights + if not (np.all(weights >= 0) and np.sum(weights) > 0): + raise ValueError("Parameter weights must have only non-negative entries " + "which sum to a positive value!") + + # validate normalize parameter + if isinstance(normalize, bool): + # if bool, convert to str literal + normalize = "normalize" if normalize else "power" + + if normalize not in ["power", "normalize", "amplitude"]: + raise ValueError( + "Normalize must be: False (or 'power'), True (or 'normalize'), " + "or 'amplitude'." + ) + + # weight vector must sum to 1 + weights = weights * (1.0 / weights.sum()) + + # if requested, perform precenter + if precenter is not _NoValue: + msg = ("Use of parameter 'precenter' is deprecated as of SciPy 1.17.0 and " + "will be removed in 1.19.0. Please leave 'precenter' unspecified. " + "Passing True to 'precenter' " + "can be exactly substituted by passing 'y = (y - y.mean())' into " + "the input. Consider setting `floating_mean` to True instead.") + warnings.warn(msg, DeprecationWarning, stacklevel=2) + if precenter: + y = y - y.mean() + + # transform arrays + # row vector + freqs = freqs.reshape(1, -1) + # column vectors + x = x.reshape(-1, 1) + y = y.reshape(-1, 1) # type:ignore[union-attr] + weights = weights.reshape(-1, 1) + + # store frequent intermediates + weights_y = weights * y + freqst = freqs * x + coswt = np.cos(freqst) + sinwt = np.sin(freqst) + + Y = np.dot(weights.T, y) # Eq. 7 + CC = np.dot(weights.T, coswt * coswt) # Eq. 13 + SS = 1.0 - CC # trig identity: S^2 = 1 - C^2 Eq.14 + CS = np.dot(weights.T, coswt * sinwt) # Eq. 15 + + if floating_mean: + C = np.dot(weights.T, coswt) # Eq. 8 + S = np.dot(weights.T, sinwt) # Eq. 9 + CC -= C * C # Eq. 13 + SS -= S * S # Eq. 14 + CS -= C * S # Eq. 15 + + # calculate tau (phase offset to eliminate CS variable) + tau = 0.5 * np.arctan2(2.0 * CS, CC - SS) # Eq. 19 + freqst_tau = freqst - tau + + # coswt and sinwt are now offset by tau, which eliminates CS + coswt_tau = np.cos(freqst_tau) + sinwt_tau = np.sin(freqst_tau) + + YC = np.dot(weights_y.T, coswt_tau) # Eq. 11 + YS = np.dot(weights_y.T, sinwt_tau) # Eq. 12 + CC = np.dot(weights.T, coswt_tau * coswt_tau) # Eq. 13, CC range is [0.5, 1.0] + SS = 1.0 - CC # trig identity: S^2 = 1 - C^2 Eq. 14, SS range is [0.0, 0.5] + + if floating_mean: + C = np.dot(weights.T, coswt_tau) # Eq. 8 + S = np.dot(weights.T, sinwt_tau) # Eq. 9 + YC -= Y * C # Eq. 11 + YS -= Y * S # Eq. 12 + CC -= C * C # Eq. 13, CC range is now [0.0, 1.0] + SS -= S * S # Eq. 14, SS range is now [0.0, 0.5] + + # to prevent division by zero errors with a and b, as well as correcting for + # numerical precision errors that lead to CC or SS being approximately -0.0, + # make sure CC and SS are both > 0 + epsneg = np.finfo(dtype=y.dtype).epsneg # type:ignore[union-attr] + CC[CC < epsneg] = epsneg + SS[SS < epsneg] = epsneg + + # calculate a and b + # where: y(w) = a*cos(w) + b*sin(w) + c + a = YC / CC # Eq. A.4 and 6, eliminating CS + b = YS / SS # Eq. A.4 and 6, eliminating CS + # c = Y - a * C - b * S + + # store final value as power in A^2 (i.e., (y units)^2) + pgram = 2.0 * (a * YC + b * YS) + + # squeeze back to a vector + pgram = np.squeeze(pgram) + + if normalize == "power": # (default) + # return the legacy power units ((A**2) * N/4) + + pgram *= float(x.shape[0]) / 4.0 + + elif normalize == "normalize": + # return the normalized power (power at current frequency wrt the entire signal) + # range will be [0, 1] + + YY = np.dot(weights_y.T, y) # Eq. 10 + if floating_mean: + YY -= Y * Y # Eq. 10 + + pgram *= 0.5 / np.squeeze(YY) # Eq. 20 + + else: # normalize == "amplitude": + # return the complex representation of the best-fit amplitude and phase + + # squeeze back to vectors + a = np.squeeze(a) + b = np.squeeze(b) + tau = np.squeeze(tau) + + # calculate the complex representation, and correct for tau rotation + pgram = (a + 1j * b) * np.exp(1j * tau) + + return pgram + + +def periodogram(x, fs=1.0, window='boxcar', nfft=None, detrend='constant', + return_onesided=True, scaling='density', axis=-1): + """ + Estimate power spectral density using a periodogram. + + Parameters + ---------- + x : array_like + Time series of measurement values + fs : float, optional + Sampling frequency of the `x` time series. Defaults to 1.0. + window : str or tuple or array_like, optional + Desired window to use. If `window` is a string or tuple, it is + passed to `get_window` to generate the window values, which are + DFT-even by default. See `get_window` for a list of windows and + required parameters. If `window` is array_like it will be used + directly as the window and its length must be equal to the length + of the axis over which the periodogram is computed. Defaults + to 'boxcar'. + nfft : int, optional + Length of the FFT used. If `None` the length of `x` will be + used. + detrend : str or function or `False`, optional + Specifies how to detrend each segment. If `detrend` is a + string, it is passed as the `type` argument to the `detrend` + function. If it is a function, it takes a segment and returns a + detrended segment. If `detrend` is `False`, no detrending is + done. Defaults to 'constant'. + return_onesided : bool, optional + If `True`, return a one-sided spectrum for real data. If + `False` return a two-sided spectrum. Defaults to `True`, but for + complex data, a two-sided spectrum is always returned. + scaling : { 'density', 'spectrum' }, optional + Selects between computing the power spectral density ('density') + where `Pxx` has units of V²/Hz and computing the squared magnitude + spectrum ('spectrum') where `Pxx` has units of V², if `x` + is measured in V and `fs` is measured in Hz. Defaults to + 'density' + axis : int, optional + Axis along which the periodogram is computed; the default is + over the last axis (i.e. ``axis=-1``). + + Returns + ------- + f : ndarray + Array of sample frequencies. + Pxx : ndarray + Power spectral density or power spectrum of `x`. + + See Also + -------- + welch: Estimate power spectral density using Welch's method + lombscargle: Lomb-Scargle periodogram for unevenly sampled data + + Notes + ----- + The ratio of the squared magnitude (``scaling='spectrum'``) divided by the spectral + power density (``scaling='density'``) is the constant factor of + ``sum(abs(window)**2)*fs / abs(sum(window))**2``. + If `return_onesided` is ``True``, the values of the negative frequencies are added + to values of the corresponding positive ones. + + Consult the :ref:`tutorial_SpectralAnalysis` section of the :ref:`user_guide` + for a discussion of the scalings of the power spectral density and + the magnitude (squared) spectrum. + + .. versionadded:: 0.12.0 + + Examples + -------- + >>> import numpy as np + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + >>> rng = np.random.default_rng() + + Generate a test signal, a 2 Vrms sine wave at 1234 Hz, corrupted by + 0.001 V**2/Hz of white noise sampled at 10 kHz. + + >>> fs = 10e3 + >>> N = 1e5 + >>> amp = 2*np.sqrt(2) + >>> freq = 1234.0 + >>> noise_power = 0.001 * fs / 2 + >>> time = np.arange(N) / fs + >>> x = amp*np.sin(2*np.pi*freq*time) + >>> x += rng.normal(scale=np.sqrt(noise_power), size=time.shape) + + Compute and plot the power spectral density. + + >>> f, Pxx_den = signal.periodogram(x, fs) + >>> plt.semilogy(f, Pxx_den) + >>> plt.ylim([1e-7, 1e2]) + >>> plt.xlabel('frequency [Hz]') + >>> plt.ylabel('PSD [V**2/Hz]') + >>> plt.show() + + If we average the last half of the spectral density, to exclude the + peak, we can recover the noise power on the signal. + + >>> np.mean(Pxx_den[25000:]) + 0.000985320699252543 + + Now compute and plot the power spectrum. + + >>> f, Pxx_spec = signal.periodogram(x, fs, 'flattop', scaling='spectrum') + >>> plt.figure() + >>> plt.semilogy(f, np.sqrt(Pxx_spec)) + >>> plt.ylim([1e-4, 1e1]) + >>> plt.xlabel('frequency [Hz]') + >>> plt.ylabel('Linear spectrum [V RMS]') + >>> plt.show() + + The peak height in the power spectrum is an estimate of the RMS + amplitude. + + >>> np.sqrt(Pxx_spec.max()) + 2.0077340678640727 + + """ + x = np.asarray(x) + + if x.size == 0: + return np.empty(x.shape), np.empty(x.shape) + + if window is None: + window = 'boxcar' + + if nfft is None: + nperseg = x.shape[axis] + elif nfft == x.shape[axis]: + nperseg = nfft + elif nfft > x.shape[axis]: + nperseg = x.shape[axis] + elif nfft < x.shape[axis]: + s = [np.s_[:]]*len(x.shape) + s[axis] = np.s_[:nfft] + x = x[tuple(s)] + nperseg = nfft + nfft = None + + if hasattr(window, 'size'): + if window.size != nperseg: + raise ValueError('the size of the window must be the same size ' + 'of the input on the specified axis') + + return welch(x, fs=fs, window=window, nperseg=nperseg, noverlap=0, + nfft=nfft, detrend=detrend, return_onesided=return_onesided, + scaling=scaling, axis=axis) + + +def welch(x, fs=1.0, window='hann_periodic', nperseg=None, noverlap=None, nfft=None, + detrend='constant', return_onesided=True, scaling='density', + axis=-1, average='mean'): + r""" + Estimate power spectral density using Welch's method. + + Welch's method [1]_ computes an estimate of the power spectral + density by dividing the data into overlapping segments, computing a + modified periodogram for each segment and averaging the + periodograms. + + Parameters + ---------- + x : array_like + Time series of measurement values + fs : float, optional + Sampling frequency of the `x` time series. Defaults to 1.0. + window : str or tuple or array_like, optional + Desired window to use. If `window` is a string or tuple, it is + passed to `get_window` to generate the window values, which are + DFT-even by default. See `get_window` for a list of windows and + required parameters. If `window` is array_like it will be used + directly as the window and its length must be nperseg. Defaults + to a periodic Hann window. + nperseg : int, optional + Length of each segment. Defaults to None, but if window is str or + tuple, is set to 256, and if window is array_like, is set to the + length of the window. + noverlap : int, optional + Number of points to overlap between segments. If `None`, + ``noverlap = nperseg // 2``. Defaults to `None`. + nfft : int, optional + Length of the FFT used, if a zero padded FFT is desired. If + `None`, the FFT length is `nperseg`. Defaults to `None`. + detrend : str or function or `False`, optional + Specifies how to detrend each segment. If `detrend` is a + string, it is passed as the `type` argument to the `detrend` + function. If it is a function, it takes a segment and returns a + detrended segment. If `detrend` is `False`, no detrending is + done. Defaults to 'constant'. + return_onesided : bool, optional + If `True`, return a one-sided spectrum for real data. If + `False` return a two-sided spectrum. Defaults to `True`, but for + complex data, a two-sided spectrum is always returned. + scaling : { 'density', 'spectrum' }, optional + Selects between computing the power spectral density ('density') + where `Pxx` has units of V**2/Hz and computing the squared magnitude + spectrum ('spectrum') where `Pxx` has units of V**2, if `x` + is measured in V and `fs` is measured in Hz. Defaults to + 'density' + axis : int, optional + Axis along which the periodogram is computed; the default is + over the last axis (i.e. ``axis=-1``). + average : { 'mean', 'median' }, optional + Method to use when averaging periodograms. Defaults to 'mean'. + + .. versionadded:: 1.2.0 + + Returns + ------- + f : ndarray + Array of sample frequencies. + Pxx : ndarray + Power spectral density or power spectrum of x. + + See Also + -------- + csd: Cross power spectral density using Welch's method + periodogram: Simple, optionally modified periodogram + lombscargle: Lomb-Scargle periodogram for unevenly sampled data + + Notes + ----- + An appropriate amount of overlap will depend on the choice of window + and on your requirements. For the default Hann window an overlap of + 50% is a reasonable trade-off between accurately estimating the + signal power, while not over counting any of the data. Narrower + windows may require a larger overlap. If `noverlap` is 0, this + method is equivalent to Bartlett's method [2]_. + + The ratio of the squared magnitude (``scaling='spectrum'``) divided by the spectral + power density (``scaling='density'``) is the constant factor of + ``sum(abs(window)**2)*fs / abs(sum(window))**2``. + If `return_onesided` is ``True``, the values of the negative frequencies are added + to values of the corresponding positive ones. + + Consult the :ref:`tutorial_SpectralAnalysis` section of the :ref:`user_guide` + for a discussion of the scalings of the power spectral density and + the (squared) magnitude spectrum. + + .. versionadded:: 0.12.0 + + References + ---------- + .. [1] P. Welch, "The use of the fast Fourier transform for the + estimation of power spectra: A method based on time averaging + over short, modified periodograms", IEEE Trans. Audio + Electroacoust. vol. 15, pp. 70-73, 1967. + .. [2] M.S. Bartlett, "Periodogram Analysis and Continuous Spectra", + Biometrika, vol. 37, pp. 1-16, 1950. + + Examples + -------- + >>> import numpy as np + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + >>> rng = np.random.default_rng() + + Generate a test signal, a 2 Vrms sine wave at 1234 Hz, corrupted by + 0.001 V**2/Hz of white noise sampled at 10 kHz. + + >>> fs = 10e3 + >>> N = 1e5 + >>> amp = 2*np.sqrt(2) + >>> freq = 1234.0 + >>> noise_power = 0.001 * fs / 2 + >>> time = np.arange(N) / fs + >>> x = amp*np.sin(2*np.pi*freq*time) + >>> x += rng.normal(scale=np.sqrt(noise_power), size=time.shape) + + Compute and plot the power spectral density. + + >>> f, Pxx_den = signal.welch(x, fs, nperseg=1024) + >>> plt.semilogy(f, Pxx_den) + >>> plt.ylim([0.5e-3, 1]) + >>> plt.xlabel('frequency [Hz]') + >>> plt.ylabel('PSD [V**2/Hz]') + >>> plt.show() + + If we average the last half of the spectral density, to exclude the + peak, we can recover the noise power on the signal. + + >>> np.mean(Pxx_den[256:]) + 0.0009924865443739191 + + Now compute and plot the power spectrum. + + >>> f, Pxx_spec = signal.welch(x, fs, 'flattop', 1024, scaling='spectrum') + >>> plt.figure() + >>> plt.semilogy(f, np.sqrt(Pxx_spec)) + >>> plt.xlabel('frequency [Hz]') + >>> plt.ylabel('Linear spectrum [V RMS]') + >>> plt.show() + + The peak height in the power spectrum is an estimate of the RMS + amplitude. + + >>> np.sqrt(Pxx_spec.max()) + 2.0077340678640727 + + If we now introduce a discontinuity in the signal, by increasing the + amplitude of a small portion of the signal by 50, we can see the + corruption of the mean average power spectral density, but using a + median average better estimates the normal behaviour. + + >>> x[int(N//2):int(N//2)+10] *= 50. + >>> f, Pxx_den = signal.welch(x, fs, nperseg=1024) + >>> f_med, Pxx_den_med = signal.welch(x, fs, nperseg=1024, average='median') + >>> plt.semilogy(f, Pxx_den, label='mean') + >>> plt.semilogy(f_med, Pxx_den_med, label='median') + >>> plt.ylim([0.5e-3, 1]) + >>> plt.xlabel('frequency [Hz]') + >>> plt.ylabel('PSD [V**2/Hz]') + >>> plt.legend() + >>> plt.show() + + """ + freqs, Pxx = csd(x, x, fs=fs, window=window, nperseg=nperseg, + noverlap=noverlap, nfft=nfft, detrend=detrend, + return_onesided=return_onesided, scaling=scaling, + axis=axis, average=average) + + return freqs, Pxx.real + + +def csd(x, y, fs=1.0, window='hann_periodic', nperseg=None, noverlap=None, nfft=None, + detrend='constant', return_onesided=True, scaling='density', + axis=-1, average='mean'): + r""" + Estimate the cross power spectral density, Pxy, using Welch's method. + + Parameters + ---------- + x : array_like + Time series of measurement values + y : array_like + Time series of measurement values + fs : float, optional + Sampling frequency of the `x` and `y` time series. Defaults + to 1.0. + window : str or tuple or array_like, optional + Desired window to use. If `window` is a string or tuple, it is + passed to `get_window` to generate the window values, which are + DFT-even by default. See `get_window` for a list of windows and + required parameters. If `window` is array_like it will be used + directly as the window and its length must be nperseg. Defaults + to a periodic Hann window. + nperseg : int, optional + Length of each segment. Defaults to None, but if window is str or + tuple, is set to 256, and if window is array_like, is set to the + length of the window. + noverlap : int, optional + Number of points to overlap between segments. If `None`, + ``noverlap = nperseg // 2``. Defaults to `None` and may + not be greater than `nperseg`. + nfft : int, optional + Length of the FFT used, if a zero padded FFT is desired. If + `None`, the FFT length is `nperseg`. Defaults to `None`. + detrend : str or function or `False`, optional + Specifies how to detrend each segment. If `detrend` is a + string, it is passed as the `type` argument to the `detrend` + function. If it is a function, it takes a segment and returns a + detrended segment. If `detrend` is `False`, no detrending is + done. Defaults to 'constant'. + return_onesided : bool, optional + If `True`, return a one-sided spectrum for real data. If + `False` return a two-sided spectrum. Defaults to `True`, but for + complex data, a two-sided spectrum is always returned. + scaling : { 'density', 'spectrum' }, optional + Selects between computing the cross spectral density ('density') + where `Pxy` has units of V**2/Hz and computing the cross spectrum + ('spectrum') where `Pxy` has units of V**2, if `x` and `y` are + measured in V and `fs` is measured in Hz. Defaults to 'density' + axis : int, optional + Axis along which the CSD is computed for both inputs; the + default is over the last axis (i.e. ``axis=-1``). + average : { 'mean', 'median' }, optional + Method to use when averaging periodograms. If the spectrum is + complex, the average is computed separately for the real and + imaginary parts. Defaults to 'mean'. + + .. versionadded:: 1.2.0 + + Returns + ------- + f : ndarray + Array of sample frequencies. + Pxy : ndarray + Cross spectral density or cross power spectrum of x,y. + + See Also + -------- + periodogram: Simple, optionally modified periodogram + lombscargle: Lomb-Scargle periodogram for unevenly sampled data + welch: Power spectral density by Welch's method. [Equivalent to + csd(x,x)] + coherence: Magnitude squared coherence by Welch's method. + + Notes + ----- + By convention, Pxy is computed with the conjugate FFT of X + multiplied by the FFT of Y. + + If the input series differ in length, the shorter series will be + zero-padded to match. + + An appropriate amount of overlap will depend on the choice of window + and on your requirements. For the default Hann window an overlap of + 50% is a reasonable trade-off between accurately estimating the + signal power, while not over counting any of the data. Narrower + windows may require a larger overlap. + + The ratio of the cross spectrum (``scaling='spectrum'``) divided by the cross + spectral density (``scaling='density'``) is the constant factor of + ``sum(abs(window)**2)*fs / abs(sum(window))**2``. + If `return_onesided` is ``True``, the values of the negative frequencies are added + to values of the corresponding positive ones. + + Consult the :ref:`tutorial_SpectralAnalysis` section of the :ref:`user_guide` + for a discussion of the scalings of a spectral density and an (amplitude) spectrum. + + Welch's method may be interpreted as taking the average over the time slices of a + (cross-) spectrogram. Internally, this function utilizes the `ShortTimeFFT` to + determine the required (cross-) spectrogram. An example below illustrates that it + is straightforward to calculate `Pxy` directly with the `ShortTimeFFT`. However, + there are some notable differences in the behavior of the `ShortTimeFFT`: + + * There is no direct `ShortTimeFFT` equivalent for the `csd` parameter + combination ``return_onesided=True, scaling='density'``, since + ``fft_mode='onesided2X'`` requires ``'psd'`` scaling. The is due to `csd` + returning the doubled squared magnitude in this case, which does not have a + sensible interpretation. + * `ShortTimeFFT` uses `float64` / `complex128` internally, which is due to the + behavior of the utilized `~scipy.fft` module. Thus, those are the dtypes being + returned. The `csd` function casts the return values to `float32` / `complex64` + if the input is `float32` / `complex64` as well. + * The `csd` function calculates ``np.conj(Sx[q,p]) * Sy[q,p]``, whereas + `~ShortTimeFFT.spectrogram` calculates ``Sx[q,p] * np.conj(Sy[q,p])`` where + ``Sx[q,p]``, ``Sy[q,p]`` are the STFTs of `x` and `y`. Also, the window + positioning is different. + + .. versionadded:: 0.16.0 + + References + ---------- + .. [1] P. Welch, "The use of the fast Fourier transform for the + estimation of power spectra: A method based on time averaging + over short, modified periodograms", IEEE Trans. Audio + Electroacoust. vol. 15, pp. 70-73, 1967. + .. [2] Rabiner, Lawrence R., and B. Gold. "Theory and Application of + Digital Signal Processing" Prentice-Hall, pp. 414-419, 1975 + + Examples + -------- + The following example plots the cross power spectral density of two signals with + some common features: + + >>> import numpy as np + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + >>> rng = np.random.default_rng() + ... + ... # Generate two test signals with some common features: + >>> N, fs = 100_000, 10e3 # number of samples and sampling frequency + >>> amp, freq = 20, 1234.0 # amplitude and frequency of utilized sine signal + >>> noise_power = 0.001 * fs / 2 + >>> time = np.arange(N) / fs + >>> b, a = signal.butter(2, 0.25, 'low') + >>> x = rng.normal(scale=np.sqrt(noise_power), size=time.shape) + >>> y = signal.lfilter(b, a, x) + >>> x += amp*np.sin(2*np.pi*freq*time) + >>> y += rng.normal(scale=0.1*np.sqrt(noise_power), size=time.shape) + ... + ... # Compute and plot the magnitude of the cross spectral density: + >>> nperseg, noverlap, win = 1024, 512, 'hann' + >>> f, Pxy = signal.csd(x, y, fs, win, nperseg, noverlap) + >>> fig0, ax0 = plt.subplots(tight_layout=True) + >>> ax0.set_title(f"CSD ({win.title()}-window, {nperseg=}, {noverlap=})") + >>> ax0.set(xlabel="Frequency $f$ in kHz", ylabel="CSD Magnitude in V²/Hz") + >>> ax0.semilogy(f/1e3, np.abs(Pxy)) + >>> ax0.grid() + >>> plt.show() + + The cross spectral density is calculated by taking the average over the time slices + of a spectrogram: + + >>> SFT = signal.ShortTimeFFT.from_window('hann', fs, nperseg, noverlap, + ... scale_to='psd', fft_mode='onesided2X', + ... phase_shift=None) + >>> Sxy1 = SFT.spectrogram(y, x, detr='constant', k_offset=nperseg//2, + ... p0=0, p1=(N-noverlap) // SFT.hop) + >>> Pxy1 = Sxy1.mean(axis=-1) + >>> np.allclose(Pxy, Pxy1) # same result as with csd() + True + + As discussed in the Notes section, the results of using an approach analogous to + the code snippet above and the `csd` function may deviate due to implementation + details. + + Note that the code snippet above can be easily adapted to determine other + statistical properties than the mean value. + """ + # The following lines are resembling the behavior of the originally utilized + # `_spectral_helper()` function: + same_data, axis = y is x, int(axis) + x = np.asarray(x) + + if not same_data: + y = np.asarray(y) + # Check if we can broadcast the outer axes together + x_outer, y_outer = list(x.shape), list(y.shape) + x_outer.pop(axis) + y_outer.pop(axis) + try: + outer_shape = np.broadcast_shapes(x_outer, y_outer) + except ValueError as e: + raise ValueError('x and y cannot be broadcast together.') from e + if x.size == 0 or y.size == 0: + out_shape = outer_shape + (min([x.shape[axis], y.shape[axis]]),) + empty_out = np.moveaxis(np.empty(out_shape), -1, axis) + return empty_out, empty_out + out_dtype = np.result_type(x, y, np.complex64) + else: # x is y: + if x.size == 0: + return np.empty(x.shape), np.empty(x.shape) + out_dtype = np.result_type(x, np.complex64) + + n = x.shape[axis] if same_data else max(x.shape[axis], y.shape[axis]) + if isinstance(window, str) or isinstance(window, tuple): + nperseg = int(nperseg) if nperseg is not None else 256 + if nperseg < 1: + raise ValueError(f"Parameter {nperseg=} is not a positive integer!") + elif n < nperseg: + warnings.warn(f"{nperseg=} is greater than signal length max(len(x), " + + f"len(y)) = {n}, using nperseg = {n}", stacklevel=3) + nperseg = n + win = get_window(window, nperseg) + else: + win = np.asarray(window) + if nperseg is None: + nperseg = len(win) + if nperseg != len(win): + raise ValueError(f"{nperseg=} does not equal {len(win)=}") + + nfft = int(nfft) if nfft is not None else nperseg + if nfft < nperseg: + raise ValueError(f"{nfft=} must be greater than or equal to {nperseg=}!") + noverlap = int(noverlap) if noverlap is not None else nperseg // 2 + if noverlap >= nperseg: + raise ValueError(f"{noverlap=} must be less than {nperseg=}!") + if np.iscomplexobj(x) and return_onesided: + return_onesided = False + + if x.shape[axis] < y.shape[axis]: # zero-pad x to shape of y: + z_shape = list(y.shape) + z_shape[axis] = y.shape[axis] - x.shape[axis] + x = np.concatenate((x, np.zeros(z_shape)), axis=axis) + elif y.shape[axis] < x.shape[axis]: # zero-pad y to shape of x: + z_shape = list(x.shape) + z_shape[axis] = x.shape[axis] - y.shape[axis] + y = np.concatenate((y, np.zeros(z_shape)), axis=axis) + + # using cast() to make mypy happy: + fft_mode = cast(FFT_MODE_TYPE, 'onesided' if return_onesided else 'twosided') + if scaling not in (scales := {'spectrum': 'magnitude', 'density': 'psd'}): + raise ValueError(f"Parameter {scaling=} not in {scales}!") + + SFT = ShortTimeFFT(win, nperseg - noverlap, fs, fft_mode=fft_mode, mfft=nfft, + scale_to=scales[scaling], phase_shift=None) + # csd() calculates X.conj()*Y instead of X*Y.conj(): + Pxy = SFT.spectrogram(y, x, detr=None if detrend is False else detrend, + p0=0, p1=(n - noverlap) // SFT.hop, k_offset=nperseg // 2, + axis=axis) + + # Note: + # 'onesided2X' scaling of ShortTimeFFT conflicts with the + # scaling='spectrum' parameter, since it doubles the squared magnitude, + # which in the view of the ShortTimeFFT implementation does not make sense. + # Hence, the doubling of the square is implemented here: + if return_onesided: + f_axis = Pxy.ndim - 1 + axis if axis < 0 else axis + Pxy = np.moveaxis(Pxy, f_axis, -1) + Pxy[..., 1:-1 if SFT.mfft % 2 == 0 else None] *= 2 + Pxy = np.moveaxis(Pxy, -1, f_axis) + + # Average over windows. + if Pxy.shape[-1] > 1: + if average == 'median': + # np.median must be passed real arrays for the desired result + bias = _median_bias(Pxy.shape[-1]) + if np.iscomplexobj(Pxy): + Pxy = (np.median(np.real(Pxy), axis=-1) + + np.median(np.imag(Pxy), axis=-1) * 1j) + else: + Pxy = np.median(Pxy, axis=-1) + Pxy /= bias + elif average == 'mean': + Pxy = Pxy.mean(axis=-1) + else: + raise ValueError(f"Parameter {average=} must be 'median' or 'mean'!") + else: + Pxy = np.reshape(Pxy, Pxy.shape[:-1]) + + # cast output type; + Pxy = Pxy.astype(out_dtype) + if same_data: + Pxy = Pxy.real + return SFT.f, Pxy + + +def spectrogram(x, fs=1.0, window=('tukey_periodic', .25), nperseg=None, noverlap=None, + nfft=None, detrend='constant', return_onesided=True, + scaling='density', axis=-1, mode='psd'): + """Compute a spectrogram with consecutive Fourier transforms (legacy function). + + Spectrograms can be used as a way of visualizing the change of a + nonstationary signal's frequency content over time. + + .. legacy:: function + + :class:`ShortTimeFFT` is a newer STFT / ISTFT implementation with more + features also including a :meth:`~ShortTimeFFT.spectrogram` method. + A :ref:`comparison ` between the + implementations can be found in the :ref:`tutorial_stft` section of + the :ref:`user_guide`. + + Parameters + ---------- + x : array_like + Time series of measurement values + fs : float, optional + Sampling frequency of the `x` time series. Defaults to 1.0. + window : str or tuple or array_like, optional + Desired window to use. If `window` is a string or tuple, it is + passed to `get_window` to generate the window values, which are + DFT-even by default. See `get_window` for a list of windows and + required parameters. If `window` is array_like it will be used + directly as the window and its length must be nperseg. + Defaults to a periodic Tukey window with shape parameter of 0.25. + nperseg : int, optional + Length of each segment. Defaults to None, but if window is str or + tuple, is set to 256, and if window is array_like, is set to the + length of the window. + noverlap : int, optional + Number of points to overlap between segments. If `None`, + ``noverlap = nperseg // 8``. Defaults to `None`. + nfft : int, optional + Length of the FFT used, if a zero padded FFT is desired. If + `None`, the FFT length is `nperseg`. Defaults to `None`. + detrend : str or function or `False`, optional + Specifies how to detrend each segment. If `detrend` is a + string, it is passed as the `type` argument to the `detrend` + function. If it is a function, it takes a segment and returns a + detrended segment. If `detrend` is `False`, no detrending is + done. Defaults to 'constant'. + return_onesided : bool, optional + If `True`, return a one-sided spectrum for real data. If + `False` return a two-sided spectrum. Defaults to `True`, but for + complex data, a two-sided spectrum is always returned. + scaling : { 'density', 'spectrum' }, optional + Selects between computing the power spectral density ('density') + where `Sxx` has units of V**2/Hz and computing the power + spectrum ('spectrum') where `Sxx` has units of V**2, if `x` + is measured in V and `fs` is measured in Hz. Defaults to + 'density'. + axis : int, optional + Axis along which the spectrogram is computed; the default is over + the last axis (i.e. ``axis=-1``). + mode : str, optional + Defines what kind of return values are expected. Options are + ['psd', 'complex', 'magnitude', 'angle', 'phase']. 'complex' is + equivalent to the output of `stft` with no padding or boundary + extension. 'magnitude' returns the absolute magnitude of the + STFT. 'angle' and 'phase' return the complex angle of the STFT, + with and without unwrapping, respectively. + + Returns + ------- + f : ndarray + Array of sample frequencies. + t : ndarray + Array of segment times. + Sxx : ndarray + Spectrogram of x. By default, the last axis of Sxx corresponds + to the segment times. + + See Also + -------- + periodogram: Simple, optionally modified periodogram + lombscargle: Lomb-Scargle periodogram for unevenly sampled data + welch: Power spectral density by Welch's method. + csd: Cross spectral density by Welch's method. + ShortTimeFFT: Newer STFT/ISTFT implementation providing more features, + which also includes a :meth:`~ShortTimeFFT.spectrogram` + method. + + Notes + ----- + An appropriate amount of overlap will depend on the choice of window + and on your requirements. In contrast to welch's method, where the + entire data stream is averaged over, one may wish to use a smaller + overlap (or perhaps none at all) when computing a spectrogram, to + maintain some statistical independence between individual segments. + It is for this reason that the default window is a Tukey window with + 1/8th of a window's length overlap at each end. + + + .. versionadded:: 0.16.0 + + References + ---------- + .. [1] Oppenheim, Alan V., Ronald W. Schafer, John R. Buck + "Discrete-Time Signal Processing", Prentice Hall, 1999. + + Examples + -------- + >>> import numpy as np + >>> from scipy import signal + >>> from scipy.fft import fftshift + >>> import matplotlib.pyplot as plt + >>> rng = np.random.default_rng() + + Generate a test signal, a 2 Vrms sine wave whose frequency is slowly + modulated around 3kHz, corrupted by white noise of exponentially + decreasing magnitude sampled at 10 kHz. + + >>> fs = 10e3 + >>> N = 1e5 + >>> amp = 2 * np.sqrt(2) + >>> noise_power = 0.01 * fs / 2 + >>> time = np.arange(N) / float(fs) + >>> mod = 500*np.cos(2*np.pi*0.25*time) + >>> carrier = amp * np.sin(2*np.pi*3e3*time + mod) + >>> noise = rng.normal(scale=np.sqrt(noise_power), size=time.shape) + >>> noise *= np.exp(-time/5) + >>> x = carrier + noise + + Compute and plot the spectrogram. + + >>> f, t, Sxx = signal.spectrogram(x, fs) + >>> plt.pcolormesh(t, f, Sxx, shading='gouraud') + >>> plt.ylabel('Frequency [Hz]') + >>> plt.xlabel('Time [sec]') + >>> plt.show() + + Note, if using output that is not one sided, then use the following: + + >>> f, t, Sxx = signal.spectrogram(x, fs, return_onesided=False) + >>> plt.pcolormesh(t, fftshift(f), fftshift(Sxx, axes=0), shading='gouraud') + >>> plt.ylabel('Frequency [Hz]') + >>> plt.xlabel('Time [sec]') + >>> plt.show() + + """ + modelist = ['psd', 'complex', 'magnitude', 'angle', 'phase'] + if mode not in modelist: + raise ValueError(f'unknown value for mode {mode}, must be one of {modelist}') + + # need to set default for nperseg before setting default for noverlap below + window, nperseg = _triage_segments(window, nperseg, + input_length=x.shape[axis]) + + # Less overlap than welch, so samples are more statistically independent + if noverlap is None: + noverlap = nperseg // 8 + + if mode == 'psd': + freqs, time, Sxx = _spectral_helper(x, x, fs, window, nperseg, + noverlap, nfft, detrend, + return_onesided, scaling, axis, + mode='psd') + + else: + freqs, time, Sxx = _spectral_helper(x, x, fs, window, nperseg, + noverlap, nfft, detrend, + return_onesided, scaling, axis, + mode='stft') + + if mode == 'magnitude': + Sxx = np.abs(Sxx) + elif mode in ['angle', 'phase']: + Sxx = np.angle(Sxx) + if mode == 'phase': + # Sxx has one additional dimension for time strides + if axis < 0: + axis -= 1 + Sxx = np.unwrap(Sxx, axis=axis) + + # mode =='complex' is same as `stft`, doesn't need modification + + return freqs, time, Sxx + + +def check_COLA(window, nperseg, noverlap, tol=1e-10): + r"""Check whether the Constant OverLap Add (COLA) constraint is met + (legacy function). + + .. legacy:: function + + The COLA constraint is equivalent of having a constant dual window, i.e., + ``all(ShortTimeFFT.dual_win == ShortTimeFFT.dual_win[0])``. Hence, + `closest_STFT_dual_window` generalizes this function, as the following + example shows: + + >>> import numpy as np + >>> from scipy.signal import check_COLA, closest_STFT_dual_window, windows + ... + >>> w, w_rect, hop = windows.hann(12, sym=False), np.ones(12), 6 + >>> dual_win, alpha = closest_STFT_dual_window(w, hop, w_rect, scaled=True) + >>> np.allclose(dual_win/alpha, w_rect, atol=1e-10, rtol=0) + True + >>> check_COLA(w, len(w), len(w) - hop) # equivalent legacy function call + True + + + Parameters + ---------- + window : str or tuple or array_like + Desired window to use. If `window` is a string or tuple, it is + passed to `get_window` to generate the window values, which are + DFT-even by default. See `get_window` for a list of windows and + required parameters. If `window` is array_like it will be used + directly as the window and its length must be nperseg. + nperseg : int + Length of each segment. + noverlap : int + Number of points to overlap between segments. + tol : float, optional + The allowed variance of a bin's weighted sum from the median bin + sum. + + Returns + ------- + verdict : bool + `True` if chosen combination satisfies COLA within `tol`, + `False` otherwise + + See Also + -------- + closest_STFT_dual_window: Allows determining the closest window meeting the + COLA constraint for a given window + check_NOLA: Check whether the Nonzero Overlap Add (NOLA) constraint is met + ShortTimeFFT: Provide short-time Fourier transform and its inverse + stft: Short-time Fourier transform (legacy) + istft: Inverse Short-time Fourier transform (legacy) + + Notes + ----- + In order to invert a short-time Fourier transfrom (STFT) with the so-called + "overlap-add method", the signal windowing must obey the constraint of + "Constant OverLap Add" (COLA). This ensures that every point in the input + data is equally weighted, thereby avoiding aliasing and allowing full + reconstruction. Note that the algorithms implemented in `ShortTimeFFT.istft` + and in `istft` (legacy) only require that the weaker "nonzero overlap-add" + condition (as in `check_NOLA`) is met. + + Some examples of windows that satisfy COLA: + - Rectangular window at overlap of 0, 1/2, 2/3, 3/4, ... + - Bartlett window at overlap of 1/2, 3/4, 5/6, ... + - Hann window at 1/2, 2/3, 3/4, ... + - Any Blackman family window at 2/3 overlap + - Any window with ``noverlap = nperseg-1`` + + A very comprehensive list of other windows may be found in [2]_, + wherein the COLA condition is satisfied when the "Amplitude + Flatness" is unity. + + .. versionadded:: 0.19.0 + + References + ---------- + .. [1] Julius O. Smith III, "Spectral Audio Signal Processing", W3K + Publishing, 2011,ISBN 978-0-9745607-3-1. + .. [2] G. Heinzel, A. Ruediger and R. Schilling, "Spectrum and + spectral density estimation by the Discrete Fourier transform + (DFT), including a comprehensive list of window functions and + some new at-top windows", 2002, + http://hdl.handle.net/11858/00-001M-0000-0013-557A-5 + + Examples + -------- + >>> from scipy import signal + + Confirm COLA condition for rectangular window of 75% (3/4) overlap: + + >>> signal.check_COLA(signal.windows.boxcar(100), 100, 75) + True + + COLA is not true for 25% (1/4) overlap, though: + + >>> signal.check_COLA(signal.windows.boxcar(100), 100, 25) + False + + "Symmetrical" Hann window (for filter design) is not COLA: + + >>> signal.check_COLA(signal.windows.hann(120, sym=True), 120, 60) + False + + "Periodic" or "DFT-even" Hann window (for FFT analysis) is COLA for + overlap of 1/2, 2/3, 3/4, etc.: + + >>> signal.check_COLA(signal.windows.hann(120, sym=False), 120, 60) + True + + >>> signal.check_COLA(signal.windows.hann(120, sym=False), 120, 80) + True + + >>> signal.check_COLA(signal.windows.hann(120, sym=False), 120, 90) + True + + """ + nperseg = int(nperseg) + + if nperseg < 1: + raise ValueError('nperseg must be a positive integer') + + if noverlap >= nperseg: + raise ValueError('noverlap must be less than nperseg.') + noverlap = int(noverlap) + + if isinstance(window, str) or type(window) is tuple: + win = get_window(window, nperseg) + else: + win = np.asarray(window) + if len(win.shape) != 1: + raise ValueError('window must be 1-D') + if win.shape[0] != nperseg: + raise ValueError('window must have length of nperseg') + + step = nperseg - noverlap + binsums = sum(win[ii*step:(ii+1)*step] for ii in range(nperseg//step)) + + if nperseg % step != 0: + binsums[:nperseg % step] += win[-(nperseg % step):] + + deviation = binsums - np.median(binsums) + return np.max(np.abs(deviation)) < tol + + +def check_NOLA(window, nperseg, noverlap, tol=1e-10): + r"""Check whether the Nonzero Overlap Add (NOLA) constraint is met. + + Parameters + ---------- + window : str or tuple or array_like + Desired window to use. If `window` is a string or tuple, it is + passed to `get_window` to generate the window values, which are + DFT-even by default. See `get_window` for a list of windows and + required parameters. If `window` is array_like it will be used + directly as the window and its length must be nperseg. + nperseg : int + Length of each segment. + noverlap : int + Number of points to overlap between segments. + tol : float, optional + The allowed variance of a bin's weighted sum from the median bin + sum. + + Returns + ------- + verdict : bool + `True` if chosen combination satisfies the NOLA constraint within + `tol`, `False` otherwise + + See Also + -------- + check_COLA: Check whether the Constant OverLap Add (COLA) constraint is met + stft: Short Time Fourier Transform + istft: Inverse Short Time Fourier Transform + + Notes + ----- + In order to enable inversion of an STFT via the inverse STFT in + `istft`, the signal windowing must obey the constraint of "nonzero + overlap add" (NOLA): + + .. math:: \sum_{t}w^{2}[n-tH] \ne 0 + + for all :math:`n`, where :math:`w` is the window function, :math:`t` is the + frame index, and :math:`H` is the hop size (:math:`H` = `nperseg` - + `noverlap`). + + This ensures that the normalization factors in the denominator of the + overlap-add inversion equation are not zero. Only very pathological windows + will fail the NOLA constraint. + + .. versionadded:: 1.2.0 + + References + ---------- + .. [1] Julius O. Smith III, "Spectral Audio Signal Processing", W3K + Publishing, 2011,ISBN 978-0-9745607-3-1. + .. [2] G. Heinzel, A. Ruediger and R. Schilling, "Spectrum and + spectral density estimation by the Discrete Fourier transform + (DFT), including a comprehensive list of window functions and + some new at-top windows", 2002, + http://hdl.handle.net/11858/00-001M-0000-0013-557A-5 + + Examples + -------- + >>> import numpy as np + >>> from scipy import signal + + Confirm NOLA condition for rectangular window of 75% (3/4) overlap: + + >>> signal.check_NOLA(signal.windows.boxcar(100), 100, 75) + True + + NOLA is also true for 25% (1/4) overlap: + + >>> signal.check_NOLA(signal.windows.boxcar(100), 100, 25) + True + + "Symmetrical" Hann window (for filter design) is also NOLA: + + >>> signal.check_NOLA(signal.windows.hann(120, sym=True), 120, 60) + True + + As long as there is overlap, it takes quite a pathological window to fail + NOLA: + + >>> w = np.ones(64, dtype="float") + >>> w[::2] = 0 + >>> signal.check_NOLA(w, 64, 32) + False + + If there is not enough overlap, a window with zeros at the ends will not + work: + + >>> signal.check_NOLA(signal.windows.hann(64), 64, 0) + False + >>> signal.check_NOLA(signal.windows.hann(64), 64, 1) + False + >>> signal.check_NOLA(signal.windows.hann(64), 64, 2) + True + + """ + nperseg = int(nperseg) + + if nperseg < 1: + raise ValueError('nperseg must be a positive integer') + + if noverlap >= nperseg: + raise ValueError('noverlap must be less than nperseg') + if noverlap < 0: + raise ValueError('noverlap must be a nonnegative integer') + noverlap = int(noverlap) + + if isinstance(window, str) or type(window) is tuple: + win = get_window(window, nperseg) + else: + win = np.asarray(window) + if len(win.shape) != 1: + raise ValueError('window must be 1-D') + if win.shape[0] != nperseg: + raise ValueError('window must have length of nperseg') + + step = nperseg - noverlap + binsums = sum(win[ii*step:(ii+1)*step]**2 for ii in range(nperseg//step)) + + if nperseg % step != 0: + binsums[:nperseg % step] += win[-(nperseg % step):]**2 + + return np.min(binsums) > tol + + +def stft(x, fs=1.0, window='hann_periodic', nperseg=256, noverlap=None, nfft=None, + detrend=False, return_onesided=True, boundary='zeros', padded=True, + axis=-1, scaling='spectrum'): + r"""Compute the Short Time Fourier Transform (legacy function). + + STFTs can be used as a way of quantifying the change of a + nonstationary signal's frequency and phase content over time. + + .. legacy:: function + + `ShortTimeFFT` is a newer STFT / ISTFT implementation with more + features. A :ref:`comparison ` between the + implementations can be found in the :ref:`tutorial_stft` section of the + :ref:`user_guide`. + + Parameters + ---------- + x : array_like + Time series of measurement values + fs : float, optional + Sampling frequency of the `x` time series. Defaults to 1.0. + window : str or tuple or array_like, optional + Desired window to use. If `window` is a string or tuple, it is + passed to `get_window` to generate the window values, which are + DFT-even by default. See `get_window` for a list of windows and + required parameters. If `window` is array_like it will be used + directly as the window and its length must be nperseg. Defaults + to a periodic Hann window. + nperseg : int, optional + Length of each segment. Defaults to 256. + noverlap : int, optional + Number of points to overlap between segments. If `None`, + ``noverlap = nperseg // 2``. Defaults to `None`. When + specified, the COLA constraint must be met (see Notes below). + nfft : int, optional + Length of the FFT used, if a zero padded FFT is desired. If + `None`, the FFT length is `nperseg`. Defaults to `None`. + detrend : str or function or `False`, optional + Specifies how to detrend each segment. If `detrend` is a + string, it is passed as the `type` argument to the `detrend` + function. If it is a function, it takes a segment and returns a + detrended segment. If `detrend` is `False`, no detrending is + done. Defaults to `False`. + return_onesided : bool, optional + If `True`, return a one-sided spectrum for real data. If + `False` return a two-sided spectrum. Defaults to `True`, but for + complex data, a two-sided spectrum is always returned. + boundary : str or None, optional + Specifies whether the input signal is extended at both ends, and + how to generate the new values, in order to center the first + windowed segment on the first input point. This has the benefit + of enabling reconstruction of the first input point when the + employed window function starts at zero. Valid options are + ``['even', 'odd', 'constant', 'zeros', None]``. Defaults to + 'zeros', for zero padding extension. I.e. ``[1, 2, 3, 4]`` is + extended to ``[0, 1, 2, 3, 4, 0]`` for ``nperseg=3``. + padded : bool, optional + Specifies whether the input signal is zero-padded at the end to + make the signal fit exactly into an integer number of window + segments, so that all of the signal is included in the output. + Defaults to `True`. Padding occurs after boundary extension, if + `boundary` is not `None`, and `padded` is `True`, as is the + default. + axis : int, optional + Axis along which the STFT is computed; the default is over the + last axis (i.e. ``axis=-1``). + scaling : {'spectrum', 'psd'} + The default 'spectrum' scaling allows each frequency line of `Zxx` to + be interpreted as a magnitude spectrum. The 'psd' option scales each + line to a power spectral density - it allows to calculate the signal's + energy by numerically integrating over ``abs(Zxx)**2``. + + .. versionadded:: 1.9.0 + + Returns + ------- + f : ndarray + Array of sample frequencies. + t : ndarray + Array of segment times. + Zxx : ndarray + STFT of `x`. By default, the last axis of `Zxx` corresponds + to the segment times. + + See Also + -------- + istft: Inverse Short Time Fourier Transform + ShortTimeFFT: Newer STFT/ISTFT implementation providing more features. + check_COLA: Check whether the Constant OverLap Add (COLA) constraint + is met + check_NOLA: Check whether the Nonzero Overlap Add (NOLA) constraint is met + welch: Power spectral density by Welch's method. + spectrogram: Spectrogram by Welch's method. + csd: Cross spectral density by Welch's method. + lombscargle: Lomb-Scargle periodogram for unevenly sampled data + + Notes + ----- + In order to enable inversion of an STFT via the inverse STFT in + `istft`, the signal windowing must obey the constraint of "Nonzero + OverLap Add" (NOLA), and the input signal must have complete + windowing coverage (i.e. ``(x.shape[axis] - nperseg) % + (nperseg-noverlap) == 0``). The `padded` argument may be used to + accomplish this. + + Given a time-domain signal :math:`x[n]`, a window :math:`w[n]`, and a hop + size :math:`H` = `nperseg - noverlap`, the windowed frame at time index + :math:`t` is given by + + .. math:: x_{t}[n]=x[n]w[n-tH] + + The overlap-add (OLA) reconstruction equation is given by + + .. math:: x[n]=\frac{\sum_{t}x_{t}[n]w[n-tH]}{\sum_{t}w^{2}[n-tH]} + + The NOLA constraint ensures that every normalization term that appears + in the denominator of the OLA reconstruction equation is nonzero. Whether a + choice of `window`, `nperseg`, and `noverlap` satisfy this constraint can + be tested with `check_NOLA`. + + + .. versionadded:: 0.19.0 + + References + ---------- + .. [1] Oppenheim, Alan V., Ronald W. Schafer, John R. Buck + "Discrete-Time Signal Processing", Prentice Hall, 1999. + .. [2] Daniel W. Griffin, Jae S. Lim "Signal Estimation from + Modified Short-Time Fourier Transform", IEEE 1984, + 10.1109/TASSP.1984.1164317 + + Examples + -------- + >>> import numpy as np + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + >>> rng = np.random.default_rng() + + Generate a test signal, a 2 Vrms sine wave whose frequency is slowly + modulated around 3kHz, corrupted by white noise of exponentially + decreasing magnitude sampled at 10 kHz. + + >>> fs = 10e3 + >>> N = 1e5 + >>> amp = 2 * np.sqrt(2) + >>> noise_power = 0.01 * fs / 2 + >>> time = np.arange(N) / float(fs) + >>> mod = 500*np.cos(2*np.pi*0.25*time) + >>> carrier = amp * np.sin(2*np.pi*3e3*time + mod) + >>> noise = rng.normal(scale=np.sqrt(noise_power), + ... size=time.shape) + >>> noise *= np.exp(-time/5) + >>> x = carrier + noise + + Compute and plot the STFT's magnitude. + + >>> f, t, Zxx = signal.stft(x, fs, nperseg=1000) + >>> plt.pcolormesh(t, f, np.abs(Zxx), vmin=0, vmax=amp, shading='gouraud') + >>> plt.title('STFT Magnitude') + >>> plt.ylabel('Frequency [Hz]') + >>> plt.xlabel('Time [sec]') + >>> plt.show() + + Compare the energy of the signal `x` with the energy of its STFT: + + >>> E_x = sum(x**2) / fs # Energy of x + >>> # Calculate a two-sided STFT with PSD scaling: + >>> f, t, Zxx = signal.stft(x, fs, nperseg=1000, return_onesided=False, + ... scaling='psd') + >>> # Integrate numerically over abs(Zxx)**2: + >>> df, dt = f[1] - f[0], t[1] - t[0] + >>> E_Zxx = sum(np.sum(Zxx.real**2 + Zxx.imag**2, axis=0) * df) * dt + >>> # The energy is the same, but the numerical errors are quite large: + >>> np.isclose(E_x, E_Zxx, rtol=1e-2) + True + + """ + if scaling == 'psd': + scaling = 'density' + elif scaling != 'spectrum': + raise ValueError(f"Parameter {scaling=} not in ['spectrum', 'psd']!") + + freqs, time, Zxx = _spectral_helper(x, x, fs, window, nperseg, noverlap, + nfft, detrend, return_onesided, + scaling=scaling, axis=axis, + mode='stft', boundary=boundary, + padded=padded) + + return freqs, time, Zxx + + +def istft(Zxx, fs=1.0, window='hann_periodic', nperseg=None, noverlap=None, nfft=None, + input_onesided=True, boundary=True, time_axis=-1, freq_axis=-2, + scaling='spectrum'): + r"""Perform the inverse Short Time Fourier transform (legacy function). + + .. legacy:: function + + `ShortTimeFFT` is a newer STFT / ISTFT implementation with more + features. A :ref:`comparison ` between the + implementations can be found in the :ref:`tutorial_stft` section of the + :ref:`user_guide`. + + Parameters + ---------- + Zxx : array_like + STFT of the signal to be reconstructed. If a purely real array + is passed, it will be cast to a complex data type. + fs : float, optional + Sampling frequency of the time series. Defaults to 1.0. + window : str or tuple or array_like, optional + Desired window to use. If `window` is a string or tuple, it is + passed to `get_window` to generate the window values, which are + DFT-even by default. See `get_window` for a list of windows and + required parameters. If `window` is array_like it will be used + directly as the window and its length must be nperseg. Defaults + to a periodic Hann window. Must match the window used to generate the + STFT for faithful inversion. + nperseg : int, optional + Number of data points corresponding to each STFT segment. This + parameter must be specified if the number of data points per + segment is odd, or if the STFT was padded via ``nfft > + nperseg``. If `None`, the value depends on the shape of + `Zxx` and `input_onesided`. If `input_onesided` is `True`, + ``nperseg=2*(Zxx.shape[freq_axis] - 1)``. Otherwise, + ``nperseg=Zxx.shape[freq_axis]``. Defaults to `None`. + noverlap : int, optional + Number of points to overlap between segments. If `None`, half + of the segment length. Defaults to `None`. When specified, the + COLA constraint must be met (see Notes below), and should match + the parameter used to generate the STFT. Defaults to `None`. + nfft : int, optional + Number of FFT points corresponding to each STFT segment. This + parameter must be specified if the STFT was padded via ``nfft > + nperseg``. If `None`, the default values are the same as for + `nperseg`, detailed above, with one exception: if + `input_onesided` is True and + ``nperseg==2*Zxx.shape[freq_axis] - 1``, `nfft` also takes on + that value. This case allows the proper inversion of an + odd-length unpadded STFT using ``nfft=None``. Defaults to + `None`. + input_onesided : bool, optional + If `True`, interpret the input array as one-sided FFTs, such + as is returned by `stft` with ``return_onesided=True`` and + `numpy.fft.rfft`. If `False`, interpret the input as a a + two-sided FFT. Defaults to `True`. + boundary : bool, optional + Specifies whether the input signal was extended at its + boundaries by supplying a non-`None` ``boundary`` argument to + `stft`. Defaults to `True`. + time_axis : int, optional + Where the time segments of the STFT is located; the default is + the last axis (i.e. ``axis=-1``). + freq_axis : int, optional + Where the frequency axis of the STFT is located; the default is + the penultimate axis (i.e. ``axis=-2``). + scaling : {'spectrum', 'psd'} + The default 'spectrum' scaling allows each frequency line of `Zxx` to + be interpreted as a magnitude spectrum. The 'psd' option scales each + line to a power spectral density - it allows to calculate the signal's + energy by numerically integrating over ``abs(Zxx)**2``. + + Returns + ------- + t : ndarray + Array of output data times. + x : ndarray + iSTFT of `Zxx`. + + See Also + -------- + stft: Short Time Fourier Transform + ShortTimeFFT: Newer STFT/ISTFT implementation providing more features. + check_COLA: Check whether the Constant OverLap Add (COLA) constraint + is met + check_NOLA: Check whether the Nonzero Overlap Add (NOLA) constraint is met + + Notes + ----- + In order to enable inversion of an STFT via the inverse STFT with + `istft`, the signal windowing must obey the constraint of "nonzero + overlap add" (NOLA): + + .. math:: \sum_{t}w^{2}[n-tH] \ne 0 + + This ensures that the normalization factors that appear in the denominator + of the overlap-add reconstruction equation + + .. math:: x[n]=\frac{\sum_{t}x_{t}[n]w[n-tH]}{\sum_{t}w^{2}[n-tH]} + + are not zero. The NOLA constraint can be checked with the `check_NOLA` + function. + + An STFT which has been modified (via masking or otherwise) is not + guaranteed to correspond to an exactly realizible signal. This + function implements the iSTFT via the least-squares estimation + algorithm detailed in [2]_, which produces a signal that minimizes + the mean squared error between the STFT of the returned signal and + the modified STFT. + + + .. versionadded:: 0.19.0 + + References + ---------- + .. [1] Oppenheim, Alan V., Ronald W. Schafer, John R. Buck + "Discrete-Time Signal Processing", Prentice Hall, 1999. + .. [2] Daniel W. Griffin, Jae S. Lim "Signal Estimation from + Modified Short-Time Fourier Transform", IEEE 1984, + 10.1109/TASSP.1984.1164317 + + Examples + -------- + >>> import numpy as np + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + >>> rng = np.random.default_rng() + + Generate a test signal, a 2 Vrms sine wave at 50Hz corrupted by + 0.001 V**2/Hz of white noise sampled at 1024 Hz. + + >>> fs = 1024 + >>> N = 10*fs + >>> nperseg = 512 + >>> amp = 2 * np.sqrt(2) + >>> noise_power = 0.001 * fs / 2 + >>> time = np.arange(N) / float(fs) + >>> carrier = amp * np.sin(2*np.pi*50*time) + >>> noise = rng.normal(scale=np.sqrt(noise_power), + ... size=time.shape) + >>> x = carrier + noise + + Compute the STFT, and plot its magnitude + + >>> f, t, Zxx = signal.stft(x, fs=fs, nperseg=nperseg) + >>> plt.figure() + >>> plt.pcolormesh(t, f, np.abs(Zxx), vmin=0, vmax=amp, shading='gouraud') + >>> plt.ylim([f[1], f[-1]]) + >>> plt.title('STFT Magnitude') + >>> plt.ylabel('Frequency [Hz]') + >>> plt.xlabel('Time [sec]') + >>> plt.yscale('log') + >>> plt.show() + + Zero the components that are 10% or less of the carrier magnitude, + then convert back to a time series via inverse STFT + + >>> Zxx = np.where(np.abs(Zxx) >= amp/10, Zxx, 0) + >>> _, xrec = signal.istft(Zxx, fs) + + Compare the cleaned signal with the original and true carrier signals. + + >>> plt.figure() + >>> plt.plot(time, x, time, xrec, time, carrier) + >>> plt.xlim([2, 2.1]) + >>> plt.xlabel('Time [sec]') + >>> plt.ylabel('Signal') + >>> plt.legend(['Carrier + Noise', 'Filtered via STFT', 'True Carrier']) + >>> plt.show() + + Note that the cleaned signal does not start as abruptly as the original, + since some of the coefficients of the transient were also removed: + + >>> plt.figure() + >>> plt.plot(time, x, time, xrec, time, carrier) + >>> plt.xlim([0, 0.1]) + >>> plt.xlabel('Time [sec]') + >>> plt.ylabel('Signal') + >>> plt.legend(['Carrier + Noise', 'Filtered via STFT', 'True Carrier']) + >>> plt.show() + + """ + # Make sure input is an ndarray of appropriate complex dtype + Zxx = np.asarray(Zxx) + 0j + freq_axis = int(freq_axis) + time_axis = int(time_axis) + + if Zxx.ndim < 2: + raise ValueError('Input stft must be at least 2d!') + + if freq_axis == time_axis: + raise ValueError('Must specify differing time and frequency axes!') + + nseg = Zxx.shape[time_axis] + + if input_onesided: + # Assume even segment length + n_default = 2*(Zxx.shape[freq_axis] - 1) + else: + n_default = Zxx.shape[freq_axis] + + # Check windowing parameters + if nperseg is None: + nperseg = n_default + else: + nperseg = int(nperseg) + if nperseg < 1: + raise ValueError('nperseg must be a positive integer') + + if nfft is None: + if (input_onesided) and (nperseg == n_default + 1): + # Odd nperseg, no FFT padding + nfft = nperseg + else: + nfft = n_default + elif nfft < nperseg: + raise ValueError('nfft must be greater than or equal to nperseg.') + else: + nfft = int(nfft) + + if noverlap is None: + noverlap = nperseg//2 + else: + noverlap = int(noverlap) + if noverlap >= nperseg: + raise ValueError('noverlap must be less than nperseg.') + nstep = nperseg - noverlap + + # Rearrange axes if necessary + if time_axis != Zxx.ndim-1 or freq_axis != Zxx.ndim-2: + # Turn negative indices to positive for the call to transpose + if freq_axis < 0: + freq_axis = Zxx.ndim + freq_axis + if time_axis < 0: + time_axis = Zxx.ndim + time_axis + zouter = list(range(Zxx.ndim)) + for ax in sorted([time_axis, freq_axis], reverse=True): + zouter.pop(ax) + Zxx = np.transpose(Zxx, zouter+[freq_axis, time_axis]) + + # Get window as array + if isinstance(window, str) or type(window) is tuple: + win = get_window(window, nperseg) + else: + win = np.asarray(window) + if len(win.shape) != 1: + raise ValueError('window must be 1-D') + if win.shape[0] != nperseg: + raise ValueError(f'window must have length of {nperseg}') + + ifunc = sp_fft.irfft if input_onesided else sp_fft.ifft + xsubs = ifunc(Zxx, axis=-2, n=nfft)[..., :nperseg, :] + + # Initialize output and normalization arrays + outputlength = nperseg + (nseg-1)*nstep + x = np.zeros(list(Zxx.shape[:-2])+[outputlength], dtype=xsubs.dtype) + norm = np.zeros(outputlength, dtype=xsubs.dtype) + + if np.result_type(win, xsubs) != xsubs.dtype: + win = win.astype(xsubs.dtype) + + if scaling == 'spectrum': + xsubs *= win.sum() + elif scaling == 'psd': + xsubs *= np.sqrt(fs * sum(win**2)) + else: + raise ValueError(f"Parameter {scaling=} not in ['spectrum', 'psd']!") + + # Construct the output from the ifft segments + # This loop could perhaps be vectorized/strided somehow... + for ii in range(nseg): + # Window the ifft + x[..., ii*nstep:ii*nstep+nperseg] += xsubs[..., ii] * win + norm[..., ii*nstep:ii*nstep+nperseg] += win**2 + + # Remove extension points + if boundary: + x = x[..., nperseg//2:-(nperseg//2)] + norm = norm[..., nperseg//2:-(nperseg//2)] + + # Divide out normalization where non-tiny + if np.sum(norm > 1e-10) != len(norm): + warnings.warn( + "NOLA condition failed, STFT may not be invertible." + + (" Possibly due to missing boundary" if not boundary else ""), + stacklevel=2 + ) + x /= np.where(norm > 1e-10, norm, 1.0) + + if input_onesided: + x = x.real + + # Put axes back + if x.ndim > 1: + if time_axis != Zxx.ndim-1: + if freq_axis < time_axis: + time_axis -= 1 + x = np.moveaxis(x, -1, time_axis) + + time = np.arange(x.shape[0])/float(fs) + return time, x + + +def coherence(x, y, fs=1.0, window='hann_periodic', nperseg=None, noverlap=None, + nfft=None, detrend='constant', axis=-1): + r""" + Estimate the magnitude squared coherence estimate, Cxy, of + discrete-time signals X and Y using Welch's method. + + ``Cxy = abs(Pxy)**2/(Pxx*Pyy)``, where `Pxx` and `Pyy` are power + spectral density estimates of X and Y, and `Pxy` is the cross + spectral density estimate of X and Y. + + Parameters + ---------- + x : array_like + Time series of measurement values + y : array_like + Time series of measurement values + fs : float, optional + Sampling frequency of the `x` and `y` time series. Defaults + to 1.0. + window : str or tuple or array_like, optional + Desired window to use. If `window` is a string or tuple, it is + passed to `get_window` to generate the window values, which are + DFT-even by default. See `get_window` for a list of windows and + required parameters. If `window` is array_like it will be used + directly as the window and its length must be nperseg. Defaults + to a periodic Hann window. + nperseg : int, optional + Length of each segment. Defaults to None, but if window is str or + tuple, is set to 256, and if window is array_like, is set to the + length of the window. + noverlap : int, optional + Number of points to overlap between segments. If `None`, + ``noverlap = nperseg // 2``. Defaults to `None`. + nfft : int, optional + Length of the FFT used, if a zero padded FFT is desired. If + `None`, the FFT length is `nperseg`. Defaults to `None`. + detrend : str or function or `False`, optional + Specifies how to detrend each segment. If `detrend` is a + string, it is passed as the `type` argument to the `detrend` + function. If it is a function, it takes a segment and returns a + detrended segment. If `detrend` is `False`, no detrending is + done. Defaults to 'constant'. + axis : int, optional + Axis along which the coherence is computed for both inputs; the + default is over the last axis (i.e. ``axis=-1``). + + Returns + ------- + f : ndarray + Array of sample frequencies. + Cxy : ndarray + Magnitude squared coherence of x and y. + + See Also + -------- + periodogram: Simple, optionally modified periodogram + lombscargle: Lomb-Scargle periodogram for unevenly sampled data + welch: Power spectral density by Welch's method. + csd: Cross spectral density by Welch's method. + + Notes + ----- + An appropriate amount of overlap will depend on the choice of window + and on your requirements. For the default Hann window an overlap of + 50% is a reasonable trade-off between accurately estimating the + signal power, while not over counting any of the data. Narrower + windows may require a larger overlap. + + .. versionadded:: 0.16.0 + + References + ---------- + .. [1] P. Welch, "The use of the fast Fourier transform for the + estimation of power spectra: A method based on time averaging + over short, modified periodograms", IEEE Trans. Audio + Electroacoust. vol. 15, pp. 70-73, 1967. + .. [2] Stoica, Petre, and Randolph Moses, "Spectral Analysis of + Signals" Prentice Hall, 2005 + + Examples + -------- + >>> import numpy as np + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + >>> rng = np.random.default_rng() + + Generate two test signals with some common features. + + >>> fs = 10e3 + >>> N = 1e5 + >>> amp = 20 + >>> freq = 1234.0 + >>> noise_power = 0.001 * fs / 2 + >>> time = np.arange(N) / fs + >>> b, a = signal.butter(2, 0.25, 'low') + >>> x = rng.normal(scale=np.sqrt(noise_power), size=time.shape) + >>> y = signal.lfilter(b, a, x) + >>> x += amp*np.sin(2*np.pi*freq*time) + >>> y += rng.normal(scale=0.1*np.sqrt(noise_power), size=time.shape) + + Compute and plot the coherence. + + >>> f, Cxy = signal.coherence(x, y, fs, nperseg=1024) + >>> plt.semilogy(f, Cxy) + >>> plt.xlabel('frequency [Hz]') + >>> plt.ylabel('Coherence') + >>> plt.show() + + """ + freqs, Pxx = welch(x, fs=fs, window=window, nperseg=nperseg, + noverlap=noverlap, nfft=nfft, detrend=detrend, + axis=axis) + _, Pyy = welch(y, fs=fs, window=window, nperseg=nperseg, noverlap=noverlap, + nfft=nfft, detrend=detrend, axis=axis) + _, Pxy = csd(x, y, fs=fs, window=window, nperseg=nperseg, + noverlap=noverlap, nfft=nfft, detrend=detrend, axis=axis) + + Cxy = np.abs(Pxy)**2 / Pxx / Pyy + + return freqs, Cxy + + +def _spectral_helper(x, y, fs=1.0, window='hann', nperseg=None, noverlap=None, + nfft=None, detrend='constant', return_onesided=True, + scaling='density', axis=-1, mode='psd', boundary=None, + padded=False): + """Calculate various forms of windowed FFTs for PSD, CSD, etc. + + .. legacy:: function + + This function is soley used by the legacy functions `spectrogram` and `stft` + (which are also in this same source file `scipy/signal/_spectral_py.py`). + + This is a helper function that implements the commonality between + the stft, psd, csd, and spectrogram functions. It is not designed to + be called externally. The windows are not averaged over; the result + from each window is returned. + + Parameters + ---------- + x : array_like + Array or sequence containing the data to be analyzed. + y : array_like + Array or sequence containing the data to be analyzed. If this is + the same object in memory as `x` (i.e. ``_spectral_helper(x, + x, ...)``), the extra computations are spared. + fs : float, optional + Sampling frequency of the time series. Defaults to 1.0. + window : str or tuple or array_like, optional + Desired window to use. If `window` is a string or tuple, it is + passed to `get_window` to generate the window values, which are + DFT-even by default. See `get_window` for a list of windows and + required parameters. If `window` is array_like it will be used + directly as the window and its length must be nperseg. Defaults + to a Hann window. + nperseg : int, optional + Length of each segment. Defaults to None, but if window is str or + tuple, is set to 256, and if window is array_like, is set to the + length of the window. + noverlap : int, optional + Number of points to overlap between segments. If `None`, + ``noverlap = nperseg // 2``. Defaults to `None`. + nfft : int, optional + Length of the FFT used, if a zero padded FFT is desired. If + `None`, the FFT length is `nperseg`. Defaults to `None`. + detrend : str or function or `False`, optional + Specifies how to detrend each segment. If `detrend` is a + string, it is passed as the `type` argument to the `detrend` + function. If it is a function, it takes a segment and returns a + detrended segment. If `detrend` is `False`, no detrending is + done. Defaults to 'constant'. + return_onesided : bool, optional + If `True`, return a one-sided spectrum for real data. If + `False` return a two-sided spectrum. Defaults to `True`, but for + complex data, a two-sided spectrum is always returned. + scaling : { 'density', 'spectrum' }, optional + Selects between computing the cross spectral density ('density') + where `Pxy` has units of V²/Hz and computing the cross + spectrum ('spectrum') where `Pxy` has units of V², if `x` + and `y` are measured in V and `fs` is measured in Hz. + Defaults to 'density' + axis : int, optional + Axis along which the FFTs are computed; the default is over the + last axis (i.e. ``axis=-1``). + mode: str {'psd', 'stft'}, optional + Defines what kind of return values are expected. Defaults to + 'psd'. + boundary : str or None, optional + Specifies whether the input signal is extended at both ends, and + how to generate the new values, in order to center the first + windowed segment on the first input point. This has the benefit + of enabling reconstruction of the first input point when the + employed window function starts at zero. Valid options are + ``['even', 'odd', 'constant', 'zeros', None]``. Defaults to + `None`. + padded : bool, optional + Specifies whether the input signal is zero-padded at the end to + make the signal fit exactly into an integer number of window + segments, so that all of the signal is included in the output. + Defaults to `False`. Padding occurs after boundary extension, if + `boundary` is not `None`, and `padded` is `True`. + + Returns + ------- + freqs : ndarray + Array of sample frequencies. + t : ndarray + Array of times corresponding to each data segment + result : ndarray + Array of output data, contents dependent on *mode* kwarg. + + Notes + ----- + Adapted from matplotlib.mlab + + .. versionadded:: 0.16.0 + """ + if mode not in ['psd', 'stft']: + raise ValueError(f"Unknown value for mode {mode}, must be one of: " + "{'psd', 'stft'}") + + boundary_funcs = {'even': even_ext, + 'odd': odd_ext, + 'constant': const_ext, + 'zeros': zero_ext, + None: None} + + if boundary not in boundary_funcs: + raise ValueError(f"Unknown boundary option '{boundary}', " + f"must be one of: {list(boundary_funcs.keys())}") + + # If x and y are the same object we can save ourselves some computation. + same_data = y is x + + if not same_data and mode != 'psd': + raise ValueError("x and y must be equal if mode is 'stft'") + + axis = int(axis) + + # Ensure we have np.arrays, get outdtype + x = np.asarray(x) + if not same_data: + y = np.asarray(y) + outdtype = np.result_type(x, y, np.complex64) + else: + outdtype = np.result_type(x, np.complex64) + + if not same_data: + # Check if we can broadcast the outer axes together + xouter = list(x.shape) + youter = list(y.shape) + xouter.pop(axis) + youter.pop(axis) + try: + outershape = np.broadcast(np.empty(xouter), np.empty(youter)).shape + except ValueError as e: + raise ValueError('x and y cannot be broadcast together.') from e + + if same_data: + if x.size == 0: + return np.empty(x.shape), np.empty(x.shape), np.empty(x.shape) + else: + if x.size == 0 or y.size == 0: + outshape = outershape + (min([x.shape[axis], y.shape[axis]]),) + emptyout = np.moveaxis(np.empty(outshape), -1, axis) + return emptyout, emptyout, emptyout + + if x.ndim > 1: + if axis != -1: + x = np.moveaxis(x, axis, -1) + if not same_data and y.ndim > 1: + y = np.moveaxis(y, axis, -1) + + # Check if x and y are the same length, zero-pad if necessary + if not same_data: + if x.shape[-1] != y.shape[-1]: + if x.shape[-1] < y.shape[-1]: + pad_shape = list(x.shape) + pad_shape[-1] = y.shape[-1] - x.shape[-1] + x = np.concatenate((x, np.zeros(pad_shape)), -1) + else: + pad_shape = list(y.shape) + pad_shape[-1] = x.shape[-1] - y.shape[-1] + y = np.concatenate((y, np.zeros(pad_shape)), -1) + + if nperseg is not None: # if specified by user + nperseg = int(nperseg) + if nperseg < 1: + raise ValueError('nperseg must be a positive integer') + + # parse window; if array like, then set nperseg = win.shape + win, nperseg = _triage_segments(window, nperseg, input_length=x.shape[-1]) + + if nfft is None: + nfft = nperseg + elif nfft < nperseg: + raise ValueError('nfft must be greater than or equal to nperseg.') + else: + nfft = int(nfft) + + if noverlap is None: + noverlap = nperseg//2 + else: + noverlap = int(noverlap) + if noverlap >= nperseg: + raise ValueError('noverlap must be less than nperseg.') + nstep = nperseg - noverlap + + # Padding occurs after boundary extension, so that the extended signal ends + # in zeros, instead of introducing an impulse at the end. + # I.e. if x = [..., 3, 2] + # extend then pad -> [..., 3, 2, 2, 3, 0, 0, 0] + # pad then extend -> [..., 3, 2, 0, 0, 0, 2, 3] + + if boundary is not None: + ext_func = boundary_funcs[boundary] + x = ext_func(x, nperseg//2, axis=-1) + if not same_data: + y = ext_func(y, nperseg//2, axis=-1) + + if padded: + # Pad to integer number of windowed segments + # I.e. make x.shape[-1] = nperseg + (nseg-1)*nstep, with integer nseg + nadd = (-(x.shape[-1]-nperseg) % nstep) % nperseg + zeros_shape = list(x.shape[:-1]) + [nadd] + x = np.concatenate((x, np.zeros(zeros_shape)), axis=-1) + if not same_data: + zeros_shape = list(y.shape[:-1]) + [nadd] + y = np.concatenate((y, np.zeros(zeros_shape)), axis=-1) + + # Handle detrending and window functions + if not detrend: + def detrend_func(d): + return d + elif not hasattr(detrend, '__call__'): + def detrend_func(d): + return _signaltools.detrend(d, type=detrend, axis=-1) + elif axis != -1: + # Wrap this function so that it receives a shape that it could + # reasonably expect to receive. + def detrend_func(d): + d = np.moveaxis(d, -1, axis) + d = detrend(d) + return np.moveaxis(d, axis, -1) + else: + detrend_func = detrend + + if np.result_type(win, np.complex64) != outdtype: + win = win.astype(outdtype) + + if scaling == 'density': + scale = 1.0 / (fs * (win*win).sum()) + elif scaling == 'spectrum': + scale = 1.0 / win.sum()**2 + else: + raise ValueError(f'Unknown scaling: {scaling!r}') + + if mode == 'stft': + scale = np.sqrt(scale) + + if return_onesided: + if np.iscomplexobj(x): + sides = 'twosided' + warnings.warn('Input data is complex, switching to return_onesided=False', + stacklevel=3) + else: + sides = 'onesided' + if not same_data: + if np.iscomplexobj(y): + sides = 'twosided' + warnings.warn('Input data is complex, switching to ' + 'return_onesided=False', + stacklevel=3) + else: + sides = 'twosided' + + if sides == 'twosided': + freqs = sp_fft.fftfreq(nfft, 1/fs) + elif sides == 'onesided': + freqs = sp_fft.rfftfreq(nfft, 1/fs) + + # Perform the windowed FFTs + result = _fft_helper(x, win, detrend_func, nperseg, noverlap, nfft, sides) + + if not same_data: + # All the same operations on the y data + result_y = _fft_helper(y, win, detrend_func, nperseg, noverlap, nfft, + sides) + result = np.conjugate(result) * result_y + elif mode == 'psd': + result = np.conjugate(result) * result + + result *= scale + if sides == 'onesided' and mode == 'psd': + if nfft % 2: + result[..., 1:] *= 2 + else: + # Last point is unpaired Nyquist freq point, don't double + result[..., 1:-1] *= 2 + + time = np.arange(nperseg/2, x.shape[-1] - nperseg/2 + 1, + nperseg - noverlap)/float(fs) + if boundary is not None: + time -= (nperseg/2) / fs + + result = result.astype(outdtype) + + # All imaginary parts are zero anyways + if same_data and mode != 'stft': + result = result.real + + # Output is going to have new last axis for time/window index, so a + # negative axis index shifts down one + if axis < 0: + axis -= 1 + + # Roll frequency axis back to axis where the data came from + result = np.moveaxis(result, -1, axis) + + return freqs, time, result + + +def _fft_helper(x, win, detrend_func, nperseg, noverlap, nfft, sides): + """ + Calculate windowed FFT, for internal use by + `scipy.signal._spectral_helper`. + + .. legacy:: function + + This function is solely used by the legacy `_spectral_helper` function, + which is located also in this file. + + This is a helper function that does the main FFT calculation for + `_spectral helper`. All input validation is performed there, and the + data axis is assumed to be the last axis of x. It is not designed to + be called externally. The windows are not averaged over; the result + from each window is returned. + + Returns + ------- + result : ndarray + Array of FFT data + + Notes + ----- + Adapted from matplotlib.mlab + + .. versionadded:: 0.16.0 + """ + # Created sliding window view of array + if nperseg == 1 and noverlap == 0: + result = x[..., np.newaxis] + else: + step = nperseg - noverlap + result = np.lib.stride_tricks.sliding_window_view( + x, window_shape=nperseg, axis=-1, writeable=True + ) + result = result[..., 0::step, :] + + # Detrend each data segment individually + result = detrend_func(result) + + # Apply window by multiplication + result = win * result + + # Perform the fft. Acts on last axis by default. Zero-pads automatically + if sides == 'twosided': + func = sp_fft.fft + else: + result = result.real + func = sp_fft.rfft + result = func(result, n=nfft) + + return result + + +def _triage_segments(window, nperseg, input_length): + """ + Parses window and nperseg arguments for spectrogram and _spectral_helper. + This is a helper function, not meant to be called externally. + + .. legacy:: function + + This function is soley used by the legacy functions `spectrogram` and + `_spectral_helper` (which are also in this file). + + Parameters + ---------- + window : str, tuple, or ndarray + If window is specified by a string or tuple and nperseg is not + specified, nperseg is set to the default of 256 and returns a window of + that length. + If instead the window is array_like and nperseg is not specified, then + nperseg is set to the length of the window. A ValueError is raised if + the user supplies both an array_like window and a value for nperseg but + nperseg does not equal the length of the window. + + nperseg : int + Length of each segment + + input_length: int + Length of input signal, i.e. x.shape[-1]. Used to test for errors. + + Returns + ------- + win : ndarray + window. If function was called with string or tuple than this will hold + the actual array used as a window. + + nperseg : int + Length of each segment. If window is str or tuple, nperseg is set to + 256. If window is array_like, nperseg is set to the length of the + window. + """ + # parse window; if array like, then set nperseg = win.shape + if isinstance(window, str) or isinstance(window, tuple): + # if nperseg not specified + if nperseg is None: + nperseg = 256 # then change to default + if nperseg > input_length: + warnings.warn(f'nperseg = {nperseg:d} is greater than input length ' + f' = {input_length:d}, using nperseg = {input_length:d}', + stacklevel=3) + nperseg = input_length + win = get_window(window, nperseg) + else: + win = np.asarray(window) + if len(win.shape) != 1: + raise ValueError('window must be 1-D') + if input_length < win.shape[-1]: + raise ValueError('window is longer than input signal') + if nperseg is None: + nperseg = win.shape[0] + elif nperseg is not None: + if nperseg != win.shape[0]: + raise ValueError("value specified for nperseg is different" + " from length of window") + return win, nperseg + + +def _median_bias(n): + """ + Returns the bias of the median of a set of periodograms relative to + the mean. + + See Appendix B from [1]_ for details. + + Parameters + ---------- + n : int + Numbers of periodograms being averaged. + + Returns + ------- + bias : float + Calculated bias. + + References + ---------- + .. [1] B. Allen, W.G. Anderson, P.R. Brady, D.A. Brown, J.D.E. Creighton. + "FINDCHIRP: an algorithm for detection of gravitational waves from + inspiraling compact binaries", Physical Review D 85, 2012, + :arxiv:`gr-qc/0509116` + """ + ii_2 = 2 * np.arange(1., (n-1) // 2 + 1) + return 1 + np.sum(1. / (ii_2 + 1) - 1. / ii_2) diff --git a/venv/lib/python3.12/site-packages/scipy/signal/_spline.cpython-312-x86_64-linux-gnu.so b/venv/lib/python3.12/site-packages/scipy/signal/_spline.cpython-312-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..11ff1cada50622e82b0d8733c599bef355109fa6 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/signal/_spline.cpython-312-x86_64-linux-gnu.so differ diff --git a/venv/lib/python3.12/site-packages/scipy/signal/_spline.pyi b/venv/lib/python3.12/site-packages/scipy/signal/_spline.pyi new file mode 100644 index 0000000000000000000000000000000000000000..c4225577db7ea188a2add225ecec1fbec855de06 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/_spline.pyi @@ -0,0 +1,34 @@ + +import numpy as np +from numpy.typing import NDArray + +FloatingArray = NDArray[np.float32] | NDArray[np.float64] +ComplexArray = NDArray[np.complex64] | NDArray[np.complex128] +FloatingComplexArray = FloatingArray | ComplexArray + + +def symiirorder1_ic(signal: FloatingComplexArray, + c0: float, + z1: float, + precision: float) -> FloatingComplexArray: + ... + + +def symiirorder2_ic_fwd(signal: FloatingArray, + r: float, + omega: float, + precision: float) -> FloatingArray: + ... + + +def symiirorder2_ic_bwd(signal: FloatingArray, + r: float, + omega: float, + precision: float) -> FloatingArray: + ... + + +def sepfir2d(input: FloatingComplexArray, + hrow: FloatingComplexArray, + hcol: FloatingComplexArray) -> FloatingComplexArray: + ... diff --git a/venv/lib/python3.12/site-packages/scipy/signal/_spline_filters.py b/venv/lib/python3.12/site-packages/scipy/signal/_spline_filters.py new file mode 100644 index 0000000000000000000000000000000000000000..53100b55bf1e8adab44645db0fe0485e59fb3e69 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/_spline_filters.py @@ -0,0 +1,847 @@ +import math + +from numpy import (zeros_like, array, tan, arange, floor, + r_, atleast_1d, greater, cos, add, sin, + moveaxis, abs, complex64, float32) +import numpy as np + +from scipy._lib._array_api import array_namespace, xp_promote + +from scipy._lib._util import normalize_axis_index + +# From splinemodule.c +from ._spline import sepfir2d, symiirorder1_ic, symiirorder2_ic_fwd, symiirorder2_ic_bwd +from ._signaltools import lfilter, sosfilt, lfiltic +from ._arraytools import axis_slice, axis_reverse + +from scipy.interpolate import BSpline + + +__all__ = ['spline_filter', 'gauss_spline', + 'cspline1d', 'qspline1d', 'qspline2d', 'cspline2d', + 'cspline1d_eval', 'qspline1d_eval', 'symiirorder1', 'symiirorder2'] + + +def spline_filter(Iin, lmbda=5.0): + """Smoothing spline (cubic) filtering of a rank-2 array. + + Filter an input data set, `Iin`, using a (cubic) smoothing spline of + fall-off `lmbda`. + + Parameters + ---------- + Iin : array_like + input data set + lmbda : float, optional + spline smoothing fall-off value, default is `5.0`. + + Returns + ------- + res : ndarray + filtered input data + + Examples + -------- + We can filter an multi dimensional signal (ex: 2D image) using cubic + B-spline filter: + + >>> import numpy as np + >>> from scipy.signal import spline_filter + >>> import matplotlib.pyplot as plt + >>> orig_img = np.eye(20) # create an image + >>> orig_img[10, :] = 1.0 + >>> sp_filter = spline_filter(orig_img, lmbda=0.1) + >>> f, ax = plt.subplots(1, 2, sharex=True) + >>> for ind, data in enumerate([[orig_img, "original image"], + ... [sp_filter, "spline filter"]]): + ... ax[ind].imshow(data[0], cmap='gray_r') + ... ax[ind].set_title(data[1]) + >>> plt.tight_layout() + >>> plt.show() + + """ + xp = array_namespace(Iin) + Iin = np.asarray(Iin) + + if Iin.dtype not in [np.float32, np.float64, np.complex64, np.complex128]: + raise TypeError(f"Invalid data type for Iin: {Iin.dtype = }") + + # XXX: note that complex-valued computations are done in single precision + # this is historic, and the root reason is unclear, + # see https://github.com/scipy/scipy/issues/9209 + # Attempting to work in complex double precision leads to symiirorder1 + # failing to converge for the boundary conditions. + intype = Iin.dtype + hcol = array([1.0, 4.0, 1.0], np.float32) / 6.0 + if intype == np.complex128: + Iin = Iin.astype(np.complex64) + + ck = cspline2d(Iin, lmbda) + out = sepfir2d(ck, hcol, hcol) + out = out.astype(intype) + return xp.asarray(out) + + +_splinefunc_cache = {} + + +def gauss_spline(x, n): + r"""Gaussian approximation to B-spline basis function of order n. + + Parameters + ---------- + x : array_like + a knot vector + n : int + The order of the spline. Must be non-negative, i.e., n >= 0 + + Returns + ------- + res : ndarray + B-spline basis function values approximated by a zero-mean Gaussian + function. + + Notes + ----- + The B-spline basis function can be approximated well by a zero-mean + Gaussian function with standard-deviation equal to :math:`\sigma=(n+1)/12` + for large `n` : + + .. math:: \frac{1}{\sqrt {2\pi\sigma^2}}exp(-\frac{x^2}{2\sigma}) + + References + ---------- + .. [1] Bouma H., Vilanova A., Bescos J.O., ter Haar Romeny B.M., Gerritsen + F.A. (2007) Fast and Accurate Gaussian Derivatives Based on B-Splines. In: + Sgallari F., Murli A., Paragios N. (eds) Scale Space and Variational + Methods in Computer Vision. SSVM 2007. Lecture Notes in Computer + Science, vol 4485. Springer, Berlin, Heidelberg. + + Examples + -------- + We can calculate B-Spline basis functions approximated by a Gaussian + distribution: + + >>> import numpy as np + >>> from scipy.signal import gauss_spline + >>> knots = np.array([-1.0, 0.0, -1.0]) + >>> gauss_spline(knots, 3) + array([0.15418033, 0.6909883, 0.15418033]) # may vary + + """ + xp = array_namespace(x) + x = xp.asarray(x) + signsq = (n + 1) / 12.0 + return 1 / math.sqrt(2 * math.pi * signsq) * xp.exp(-x ** 2 / 2 / signsq) + + +def _cubic(x): + xp = array_namespace(x) + + x = np.asarray(x, dtype=float) + b = BSpline.basis_element([-2, -1, 0, 1, 2], extrapolate=False) + out = b(x) + out[(x < -2) | (x > 2)] = 0 + return xp.asarray(out) + + +def _quadratic(x): + xp = array_namespace(x) + + x = abs(np.asarray(x, dtype=float)) + b = BSpline.basis_element([-1.5, -0.5, 0.5, 1.5], extrapolate=False) + out = b(x) + out[(x < -1.5) | (x > 1.5)] = 0 + return xp.asarray(out) + + +def _coeff_smooth(lam): + xi = 1 - 96 * lam + 24 * lam * math.sqrt(3 + 144 * lam) + omeg = math.atan2(math.sqrt(144 * lam - 1), math.sqrt(xi)) + rho = (24 * lam - 1 - math.sqrt(xi)) / (24 * lam) + rho = rho * math.sqrt((48 * lam + 24 * lam * math.sqrt(3 + 144 * lam)) / xi) + return rho, omeg + + +def _hc(k, cs, rho, omega): + return (cs / sin(omega) * (rho ** k) * sin(omega * (k + 1)) * + greater(k, -1)) + + +def _hs(k, cs, rho, omega): + c0 = (cs * cs * (1 + rho * rho) / (1 - rho * rho) / + (1 - 2 * rho * rho * cos(2 * omega) + rho ** 4)) + gamma = (1 - rho * rho) / (1 + rho * rho) / tan(omega) + ak = abs(k) + return c0 * rho ** ak * (cos(omega * ak) + gamma * sin(omega * ak)) + + +def _cubic_smooth_coeff(signal, lamb): + signal = np.asarray(signal) + + rho, omega = _coeff_smooth(lamb) + cs = 1 - 2 * rho * cos(omega) + rho * rho + K = len(signal) + k = arange(K) + + zi_2 = (_hc(0, cs, rho, omega) * signal[0] + + add.reduce(_hc(k + 1, cs, rho, omega) * signal)) + zi_1 = (_hc(0, cs, rho, omega) * signal[0] + + _hc(1, cs, rho, omega) * signal[1] + + add.reduce(_hc(k + 2, cs, rho, omega) * signal)) + + # Forward filter: + # for n in range(2, K): + # yp[n] = (cs * signal[n] + 2 * rho * cos(omega) * yp[n - 1] - + # rho * rho * yp[n - 2]) + zi = lfiltic(cs, r_[1, -2 * rho * cos(omega), rho * rho], r_[zi_1, zi_2]) + zi = zi.reshape(1, -1) + + sos = r_[cs, 0, 0, 1, -2 * rho * cos(omega), rho * rho] + sos = sos.reshape(1, -1) + + yp, _ = sosfilt(sos, signal[2:], zi=zi) + yp = r_[zi_2, zi_1, yp] + + # Reverse filter: + # for n in range(K - 3, -1, -1): + # y[n] = (cs * yp[n] + 2 * rho * cos(omega) * y[n + 1] - + # rho * rho * y[n + 2]) + + zi_2 = add.reduce((_hs(k, cs, rho, omega) + + _hs(k + 1, cs, rho, omega)) * signal[::-1]) + zi_1 = add.reduce((_hs(k - 1, cs, rho, omega) + + _hs(k + 2, cs, rho, omega)) * signal[::-1]) + + zi = lfiltic(cs, r_[1, -2 * rho * cos(omega), rho * rho], r_[zi_1, zi_2]) + zi = zi.reshape(1, -1) + y, _ = sosfilt(sos, yp[-3::-1], zi=zi) + y = r_[y[::-1], zi_1, zi_2] + return y + + +def _cubic_coeff(signal): + signal = np.asarray(signal) + + zi = -2 + math.sqrt(3) + K = len(signal) + powers = zi ** np.arange(K) + + if K == 1: + yplus = signal[0] + zi * add.reduce(powers * signal) + output = zi / (zi - 1) * yplus + return atleast_1d(output) + + # Forward filter: + # yplus[0] = signal[0] + zi * add.reduce(powers * signal) + # for k in range(1, K): + # yplus[k] = signal[k] + zi * yplus[k - 1] + + state = lfiltic(1, np.r_[1, -zi], np.atleast_1d(add.reduce(powers * signal))) + + b = np.ones(1) + a = np.r_[1, -zi] + yplus, _ = lfilter(b, a, signal, zi=state) + + # Reverse filter: + # output[K - 1] = zi / (zi - 1) * yplus[K - 1] + # for k in range(K - 2, -1, -1): + # output[k] = zi * (output[k + 1] - yplus[k]) + out_last = zi / (zi - 1) * yplus[K - 1] + state = lfiltic(-zi, r_[1, -zi], np.atleast_1d(out_last)) + + b = np.asarray([-zi]) + output, _ = lfilter(b, a, yplus[-2::-1], zi=state) + output = np.r_[output[::-1], out_last] + return output * 6.0 + + +def _quadratic_coeff(signal): + signal = np.asarray(signal) + + zi = -3 + 2 * math.sqrt(2.0) + K = len(signal) + powers = zi ** np.arange(K) + + if K == 1: + yplus = signal[0] + zi * np.add.reduce(powers * signal) + output = zi / (zi - 1) * yplus + return atleast_1d(output) + + # Forward filter: + # yplus[0] = signal[0] + zi * add.reduce(powers * signal) + # for k in range(1, K): + # yplus[k] = signal[k] + zi * yplus[k - 1] + + state = lfiltic(1, np.r_[1, -zi], np.atleast_1d(np.add.reduce(powers * signal))) + + b = np.ones(1) + a = np.r_[1, -zi] + yplus, _ = lfilter(b, a, signal, zi=state) + + # Reverse filter: + # output[K - 1] = zi / (zi - 1) * yplus[K - 1] + # for k in range(K - 2, -1, -1): + # output[k] = zi * (output[k + 1] - yplus[k]) + out_last = zi / (zi - 1) * yplus[K - 1] + state = lfiltic(-zi, r_[1, -zi], np.atleast_1d(out_last)) + + b = np.asarray([-zi]) + output, _ = lfilter(b, a, yplus[-2::-1], zi=state) + output = np.r_[output[::-1], out_last] + return output * 8.0 + + +def compute_root_from_lambda(lamb): + tmp = math.sqrt(3 + 144 * lamb) + xi = 1 - 96 * lamb + 24 * lamb * tmp + omega = math.atan(math.sqrt((144 * lamb - 1.0) / xi)) + tmp2 = math.sqrt(xi) + r = ((24 * lamb - 1 - tmp2) / (24 * lamb) * + math.sqrt(48*lamb + 24 * lamb * tmp) / tmp2) + return r, omega + + +def cspline1d(signal, lamb=0.0): + """ + Compute cubic spline coefficients for rank-1 array. + + Find the cubic spline coefficients for a 1-D signal assuming + mirror-symmetric boundary conditions. To obtain the signal back from the + spline representation mirror-symmetric-convolve these coefficients with a + length 3 FIR window [1.0, 4.0, 1.0]/ 6.0 . + + Parameters + ---------- + signal : ndarray + A rank-1 array representing samples of a signal. + lamb : float, optional + Smoothing coefficient, default is 0.0. + + Returns + ------- + c : ndarray + Cubic spline coefficients. + + See Also + -------- + cspline1d_eval : Evaluate a cubic spline at the new set of points. + + Examples + -------- + We can filter a signal to reduce and smooth out high-frequency noise with + a cubic spline: + + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from scipy.signal import cspline1d, cspline1d_eval + >>> rng = np.random.default_rng() + >>> sig = np.repeat([0., 1., 0.], 100) + >>> sig += rng.standard_normal(len(sig))*0.05 # add noise + >>> time = np.linspace(0, len(sig)) + >>> filtered = cspline1d_eval(cspline1d(sig), time) + >>> plt.plot(sig, label="signal") + >>> plt.plot(time, filtered, label="filtered") + >>> plt.legend() + >>> plt.show() + + """ + xp = array_namespace(signal) + + if lamb != 0.0: + ret = _cubic_smooth_coeff(signal, lamb) + else: + ret = _cubic_coeff(signal) + return xp.asarray(ret) + + +def qspline1d(signal, lamb=0.0): + """Compute quadratic spline coefficients for rank-1 array. + + Parameters + ---------- + signal : ndarray + A rank-1 array representing samples of a signal. + lamb : float, optional + Smoothing coefficient (must be zero for now). + + Returns + ------- + c : ndarray + Quadratic spline coefficients. + + See Also + -------- + qspline1d_eval : Evaluate a quadratic spline at the new set of points. + + Notes + ----- + Find the quadratic spline coefficients for a 1-D signal assuming + mirror-symmetric boundary conditions. To obtain the signal back from the + spline representation mirror-symmetric-convolve these coefficients with a + length 3 FIR window [1.0, 6.0, 1.0]/ 8.0 . + + Examples + -------- + We can filter a signal to reduce and smooth out high-frequency noise with + a quadratic spline: + + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from scipy.signal import qspline1d, qspline1d_eval + >>> rng = np.random.default_rng() + >>> sig = np.repeat([0., 1., 0.], 100) + >>> sig += rng.standard_normal(len(sig))*0.05 # add noise + >>> time = np.linspace(0, len(sig)) + >>> filtered = qspline1d_eval(qspline1d(sig), time) + >>> plt.plot(sig, label="signal") + >>> plt.plot(time, filtered, label="filtered") + >>> plt.legend() + >>> plt.show() + + """ + xp = array_namespace(signal) + + if lamb != 0.0: + raise ValueError("Smoothing quadratic splines not supported yet.") + else: + return xp.asarray(_quadratic_coeff(signal)) + + +def collapse_2d(x, axis): + x = moveaxis(x, axis, -1) + x_shape = x.shape + x = x.reshape(-1, x.shape[-1]) + if not x.flags.c_contiguous: + x = x.copy() + return x, x_shape + + +def symiirorder_nd(func, input, *args, axis=-1, **kwargs): + axis = normalize_axis_index(axis, input.ndim) + input_shape = input.shape + input_ndim = input.ndim + if input.ndim > 1: + input, input_shape = collapse_2d(input, axis) + + out = func(input, *args, **kwargs) + + if input_ndim > 1: + out = out.reshape(input_shape) + out = moveaxis(out, -1, axis) + if not out.flags.c_contiguous: + out = out.copy() + return out + + +def qspline2d(signal, lamb=0.0, precision=-1.0): + """ + Coefficients for 2-D quadratic (2nd order) B-spline. + + Return the second-order B-spline coefficients over a regularly spaced + input grid for the two-dimensional input image. + + Parameters + ---------- + signal : ndarray + The input signal. + lamb : float + Specifies the amount of smoothing in the transfer function. + precision : float + Specifies the precision for computing the infinite sum needed to apply + mirror-symmetric boundary conditions. + + Returns + ------- + output : ndarray + The filtered signal. + """ + if precision < 0.0 or precision >= 1.0: + if signal.dtype in [float32, complex64]: + precision = 1e-3 + else: + precision = 1e-6 + + if lamb > 0: + raise ValueError('lambda must be negative or zero') + + # normal quadratic spline + r = -3 + 2 * math.sqrt(2.0) + c0 = -r * 8.0 + z1 = r + + out = symiirorder_nd(symiirorder1, signal, c0, z1, precision, axis=-1) + out = symiirorder_nd(symiirorder1, out, c0, z1, precision, axis=0) + return out + + +def cspline2d(signal, lamb=0.0, precision=-1.0): + """ + Coefficients for 2-D cubic (3rd order) B-spline. + + Return the third-order B-spline coefficients over a regularly spaced + input grid for the two-dimensional input image. + + Parameters + ---------- + signal : ndarray + The input signal. + lamb : float + Specifies the amount of smoothing in the transfer function. + precision : float + Specifies the precision for computing the infinite sum needed to apply + mirror-symmetric boundary conditions. + + Returns + ------- + output : ndarray + The filtered signal. + """ + xp = array_namespace(signal) + signal = np.asarray(signal) + + if precision < 0.0 or precision >= 1.0: + if signal.dtype in [np.float32, np.complex64]: + precision = 1e-3 + else: + precision = 1e-6 + + if lamb <= 1 / 144.0: + # Normal cubic spline + r = -2 + math.sqrt(3.0) + out = symiirorder_nd( + symiirorder1, signal, -r * 6.0, r, precision=precision, axis=-1) + out = symiirorder_nd( + symiirorder1, out, -r * 6.0, r, precision=precision, axis=0) + return out + + r, omega = compute_root_from_lambda(lamb) + out = symiirorder_nd(symiirorder2, signal, r, omega, + precision=precision, axis=-1) + out = symiirorder_nd(symiirorder2, out, r, omega, + precision=precision, axis=0) + return xp.asarray(out) + + +def cspline1d_eval(cj, newx, dx=1.0, x0=0): + """Evaluate a cubic spline at the new set of points. + + `dx` is the old sample-spacing while `x0` was the old origin. In + other-words the old-sample points (knot-points) for which the `cj` + represent spline coefficients were at equally-spaced points of:: + + oldx = x0 + j*dx j=0...N-1, with N=len(cj) + + Edges are handled using mirror-symmetric boundary conditions. + + Parameters + ---------- + cj : ndarray + cublic spline coefficients + newx : ndarray + New set of points. + dx : float, optional + Old sample-spacing, the default value is 1.0. + x0 : int, optional + Old origin, the default value is 0. + + Returns + ------- + res : ndarray + Evaluated a cubic spline points. + + See Also + -------- + cspline1d : Compute cubic spline coefficients for rank-1 array. + + Examples + -------- + We can filter a signal to reduce and smooth out high-frequency noise with + a cubic spline: + + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from scipy.signal import cspline1d, cspline1d_eval + >>> rng = np.random.default_rng() + >>> sig = np.repeat([0., 1., 0.], 100) + >>> sig += rng.standard_normal(len(sig))*0.05 # add noise + >>> time = np.linspace(0, len(sig)) + >>> filtered = cspline1d_eval(cspline1d(sig), time) + >>> plt.plot(sig, label="signal") + >>> plt.plot(time, filtered, label="filtered") + >>> plt.legend() + >>> plt.show() + + """ + xp = array_namespace(cj, newx) + + newx = (np.asarray(newx) - x0) / float(dx) + cj = np.asarray(cj) + + if cj.size == 0: + raise ValueError("Spline coefficients 'cj' must not be empty.") + + res = zeros_like(newx, dtype=cj.dtype) + if res.size == 0: + return xp.asarray(res) + N = len(cj) + cond1 = newx < 0 + cond2 = newx > (N - 1) + cond3 = ~(cond1 | cond2) + # handle general mirror-symmetry + res[cond1] = cspline1d_eval(cj, -newx[cond1]) + res[cond2] = cspline1d_eval(cj, 2 * (N - 1) - newx[cond2]) + newx = newx[cond3] + if newx.size == 0: + return xp.asarray(res) + result = zeros_like(newx, dtype=cj.dtype) + jlower = floor(newx - 2).astype(int) + 1 + for i in range(4): + thisj = jlower + i + indj = thisj.clip(0, N - 1) # handle edge cases + result += cj[indj] * _cubic(newx - thisj) + res[cond3] = result + return xp.asarray(res) + + +def qspline1d_eval(cj, newx, dx=1.0, x0=0): + """Evaluate a quadratic spline at the new set of points. + + Parameters + ---------- + cj : ndarray + Quadratic spline coefficients + newx : ndarray + New set of points. + dx : float, optional + Old sample-spacing, the default value is 1.0. + x0 : int, optional + Old origin, the default value is 0. + + Returns + ------- + res : ndarray + Evaluated a quadratic spline points. + + See Also + -------- + qspline1d : Compute quadratic spline coefficients for rank-1 array. + + Notes + ----- + `dx` is the old sample-spacing while `x0` was the old origin. In + other-words the old-sample points (knot-points) for which the `cj` + represent spline coefficients were at equally-spaced points of:: + + oldx = x0 + j*dx j=0...N-1, with N=len(cj) + + Edges are handled using mirror-symmetric boundary conditions. + + Examples + -------- + We can filter a signal to reduce and smooth out high-frequency noise with + a quadratic spline: + + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from scipy.signal import qspline1d, qspline1d_eval + >>> rng = np.random.default_rng() + >>> sig = np.repeat([0., 1., 0.], 100) + >>> sig += rng.standard_normal(len(sig))*0.05 # add noise + >>> time = np.linspace(0, len(sig)) + >>> filtered = qspline1d_eval(qspline1d(sig), time) + >>> plt.plot(sig, label="signal") + >>> plt.plot(time, filtered, label="filtered") + >>> plt.legend() + >>> plt.show() + + """ + xp = array_namespace(newx, cj) + + newx = (np.asarray(newx) - x0) / dx + res = np.zeros_like(newx) + if res.size == 0: + return xp.asarray(res) + + cj = np.asarray(cj) + if cj.size == 0: + raise ValueError("Spline coefficients 'cj' must not be empty.") + + N = len(cj) + cond1 = newx < 0 + cond2 = newx > (N - 1) + cond3 = ~(cond1 | cond2) + # handle general mirror-symmetry + res[cond1] = qspline1d_eval(cj, -newx[cond1]) + res[cond2] = qspline1d_eval(cj, 2 * (N - 1) - newx[cond2]) + newx = newx[cond3] + if newx.size == 0: + return xp.asarray(res) + result = zeros_like(newx) + jlower = floor(newx - 1.5).astype(int) + 1 + for i in range(3): + thisj = jlower + i + indj = thisj.clip(0, N - 1) # handle edge cases + result += cj[indj] * _quadratic(newx - thisj) + res[cond3] = result + return xp.asarray(res) + + +def symiirorder1(signal, c0, z1, precision=-1.0): + """ + Implement a smoothing IIR filter with mirror-symmetric boundary conditions + using a cascade of first-order sections. + + The second section uses a reversed sequence. This implements a system with + the following transfer function and mirror-symmetric boundary conditions:: + + c0 + H(z) = --------------------- + (1-z1/z) (1 - z1 z) + + The resulting signal will have mirror symmetric boundary conditions + as well. + + Parameters + ---------- + signal : ndarray + The input signal. If 2D, then the filter will be applied in a batched + fashion across the last axis. + c0, z1 : scalar + Parameters in the transfer function. + precision : float, optional + Specifies the precision for calculating initial conditions + of the recursive filter based on mirror-symmetric input. + + Returns + ------- + output : ndarray + The filtered signal. + """ + xp = array_namespace(signal) + signal = xp_promote(signal, force_floating=True, xp=xp) + # This function uses C internals + signal = np.asarray(signal) + + if abs(z1) >= 1: + raise ValueError('|z1| must be less than 1.0') + + if signal.ndim > 2: + raise ValueError('Input must be 1D or 2D') + + squeeze_dim = False + if signal.ndim == 1: + signal = signal[None, :] + squeeze_dim = True + + y0 = symiirorder1_ic(signal, z1, precision) + + # Apply first the system 1 / (1 - z1 * z^-1) + b = np.ones(1, dtype=signal.dtype) + a = np.r_[1, -z1] + a = a.astype(signal.dtype) + + # Compute the initial state for lfilter. + zii = y0 * z1 + + y1, _ = lfilter(b, a, axis_slice(signal, 1), zi=zii) + y1 = np.c_[y0, y1] + + # Compute backward symmetric condition and apply the system + # c0 / (1 - z1 * z) + b = np.asarray([c0], dtype=signal.dtype) + out_last = -c0 / (z1 - 1.0) * axis_slice(y1, -1) + + # Compute the initial state for lfilter. + zii = out_last * z1 + + # Apply the system c0 / (1 - z1 * z) by reversing the output of the previous stage + out, _ = lfilter(b, a, axis_slice(y1, -2, step=-1), zi=zii) + out = np.c_[axis_reverse(out), out_last] + + if squeeze_dim: + out = out[0] + + return xp.asarray(out) + + +def symiirorder2(input, r, omega, precision=-1.0): + """ + Implement a smoothing IIR filter with mirror-symmetric boundary conditions + using a cascade of second-order sections. + + The second section uses a reversed sequence. This implements the following + transfer function:: + + cs^2 + H(z) = --------------------------------------- + (1 - a2/z - a3/z^2) (1 - a2 z - a3 z^2 ) + + where:: + + a2 = 2 * r * cos(omega) + a3 = - r ** 2 + cs = 1 - 2 * r * cos(omega) + r ** 2 + + Parameters + ---------- + input : ndarray + The input signal. + r, omega : float + Parameters in the transfer function. + precision : float + Specifies the precision for calculating initial conditions + of the recursive filter based on mirror-symmetric input. + + Returns + ------- + output : ndarray + The filtered signal. + """ + xp = array_namespace(input) + input = xp_promote(input, force_floating=True, xp=xp) + # This function uses C internals + input = np.ascontiguousarray(input) + + if r >= 1.0: + raise ValueError('r must be less than 1.0') + + if input.ndim > 2: + raise ValueError('Input must be 1D or 2D') + + squeeze_dim = False + if input.ndim == 1: + input = input[None, :] + squeeze_dim = True + + rsq = r * r + a2 = 2 * r * math.cos(omega) + a3 = -rsq + cs = 1 - 2 * r * math.cos(omega) + rsq + sos = np.asarray([cs, 0, 0, 1, -a2, -a3], dtype=input.dtype) + + # Find the starting (forward) conditions. + ic_fwd = symiirorder2_ic_fwd(input, r, omega, precision) + + # Apply first the system cs / (1 - a2 * z^-1 - a3 * z^-2) + # Compute the initial conditions in the form expected by sosfilt + # coef = np.asarray([[a3, a2], [0, a3]], dtype=input.dtype) + coef = np.asarray([[a3, a2], [0, a3]], dtype=input.dtype) + zi = np.matmul(coef, ic_fwd[:, :, None])[:, :, 0] + + y_fwd, _ = sosfilt(sos, axis_slice(input, 2), zi=zi[None]) + y_fwd = np.c_[ic_fwd, y_fwd] + + # Then compute the symmetric backward starting conditions + ic_bwd = symiirorder2_ic_bwd(input, r, omega, precision) + + # Apply the system cs / (1 - a2 * z^1 - a3 * z^2) + # Compute the initial conditions in the form expected by sosfilt + zi = np.matmul(coef, ic_bwd[:, :, None])[:, :, 0] + y, _ = sosfilt(sos, axis_slice(y_fwd, -3, step=-1), zi=zi[None]) + out = np.c_[axis_reverse(y), axis_reverse(ic_bwd)] + + if squeeze_dim: + out = out[0] + + return xp.asarray(out) diff --git a/venv/lib/python3.12/site-packages/scipy/signal/_support_alternative_backends.py b/venv/lib/python3.12/site-packages/scipy/signal/_support_alternative_backends.py new file mode 100644 index 0000000000000000000000000000000000000000..87540c013c891fc21612a07ffb6cd58f93b7702c --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/_support_alternative_backends.py @@ -0,0 +1,391 @@ +import functools +import types +from scipy._lib._array_api import ( + is_cupy, is_jax, scipy_namespace_for, SCIPY_ARRAY_API, xp_capabilities +) + +from ._signal_api import * # noqa: F403 +from . import _signal_api +from . import _delegators +__all__ = _signal_api.__all__ + + +MODULE_NAME = 'signal' + +# jax.scipy.signal has only partial coverage of scipy.signal, so we keep the list +# of functions we can delegate to JAX +# https://jax.readthedocs.io/en/latest/jax.scipy.html +JAX_SIGNAL_FUNCS = [ + 'fftconvolve', 'convolve', 'convolve2d', 'correlate', 'correlate2d', + 'csd', 'detrend', 'istft', 'welch' +] + +# some cupyx.scipy.signal functions are incompatible with their scipy counterparts +CUPY_BLACKLIST = [ + 'abcd_normalize', 'bessel', 'besselap', 'envelope', 'get_window', 'lfilter_zi', + 'sosfilt_zi', 'remez', +] + +# freqz_sos is a sosfreqz rename, and cupy does not have the new name yet (in v13.x) +CUPY_RENAMES = {'freqz_sos': 'sosfreqz'} + + +def delegate_xp(delegator, module_name): + def inner(func): + @functools.wraps(func) + def wrapper(*args, **kwds): + try: + xp = delegator(*args, **kwds) + except TypeError: + # object arrays + if func.__name__ == "tf2ss": + import numpy as np + xp = np + else: + raise + + # try delegating to a cupyx/jax namesake + if is_cupy(xp) and func.__name__ not in CUPY_BLACKLIST: + func_name = CUPY_RENAMES.get(func.__name__, func.__name__) + + # https://github.com/cupy/cupy/issues/8336 + import importlib + cupyx_module = importlib.import_module(f"cupyx.scipy.{module_name}") + cupyx_func = getattr(cupyx_module, func_name) + kwds.pop('xp', None) + return cupyx_func(*args, **kwds) + elif is_jax(xp) and func.__name__ in JAX_SIGNAL_FUNCS: + spx = scipy_namespace_for(xp) + jax_module = getattr(spx, module_name) + jax_func = getattr(jax_module, func.__name__) + kwds.pop('xp', None) + return jax_func(*args, **kwds) + else: + # the original function + return func(*args, **kwds) + return wrapper + return inner + + +# Although most of these functions currently exist in CuPy and some in JAX, +# there are no alternative backend tests for any of them in the current +# test suite. Each will be documented as np_only until tests are added. +untested = { + "argrelextrema", + "argrelmax", + "argrelmin", + "band_stop_obj", + "bode", + "check_NOLA", + "chirp", + "coherence", + "csd", + "czt", + "czt_points", + "dbode", + "dfreqresp", + "dlsim", + "dstep", + "find_peaks", + "find_peaks_cwt", + "freqresp", + "gausspulse", + "iirdesign", # There's no reason this shouldn't work. It just needs tests. + "istft", + "lombscargle", + "lsim", + "max_len_seq", + "peak_prominences", + "peak_widths", + "periodogram", + "place_poles", + "sawtooth", + "sepfir2d", + "ss2tf", + "ss2zpk", + "step", + "sweep_poly", + "symiirorder1", + "symiirorder2", + "tf2ss", + "unit_impulse", + "welch", + "zoom_fft", + "zpk2ss", +} + + +def get_default_capabilities(func_name, delegator): + if delegator is None or func_name in untested: + return xp_capabilities(np_only=True) + return xp_capabilities() + +bilinear_extra_note = \ + """CuPy does not accept complex inputs. + + """ + +uses_choose_conv_extra_note = \ + """CuPy does not support inputs with ``ndim>1`` when ``method="auto"`` + but does support higher dimensional arrays for ``method="direct"`` + and ``method="fft"``. + + """ + +resample_poly_extra_note = \ + """CuPy only supports ``padtype="constant"``. + + """ + +upfirdn_extra_note = \ + """CuPy only supports ``mode="constant"`` and ``cval=0.0``. + + """ + +xord_extra_note = \ + """The ``torch`` backend on GPU does not support the case where + `wp` and `ws` specify a Bandstop filter. + + """ + +convolve2d_extra_note = \ + """The JAX backend only supports ``boundary="fill"`` and ``fillvalue=0``. + + """ + +zpk2tf_extra_note = \ + """The CuPy and JAX backends both support only 1d input. + + """ + +abcd_normalize_extra_note = \ + """The result dtype when all array inputs are of integer dtype is the + backend's current default floating point dtype. + + """ + +capabilities_overrides = { + "abcd_normalize": xp_capabilities(extra_note=abcd_normalize_extra_note), + "bessel": xp_capabilities(cpu_only=True, jax_jit=False, allow_dask_compute=True), + "bilinear": xp_capabilities(cpu_only=True, exceptions=["cupy"], + jax_jit=False, allow_dask_compute=True, + reason="Uses np.polynomial.Polynomial", + extra_note=bilinear_extra_note), + "bilinear_zpk": xp_capabilities(cpu_only=True, exceptions=["cupy", "torch"], + jax_jit=False, allow_dask_compute=True), + "butter": xp_capabilities(cpu_only=True, exceptions=["cupy"], jax_jit=False, + allow_dask_compute=True), + "buttord": xp_capabilities(cpu_only=True, exceptions=["cupy", "torch"], + jax_jit=False, allow_dask_compute=True, + extra_note=xord_extra_note), + "cheb1ord": xp_capabilities(cpu_only=True, exceptions=["cupy", "torch"], + jax_jit=False, allow_dask_compute=True, + extra_note=xord_extra_note), + "cheb2ord": xp_capabilities(cpu_only=True, exceptions=["cupy", "torch"], + jax_jit=False, allow_dask_compute=True, + extra_note=xord_extra_note), + "cheby1": xp_capabilities(cpu_only=True, exceptions=["cupy"], jax_jit=False, + allow_dask_compute=True), + + "cheby2": xp_capabilities(cpu_only=True, exceptions=["cupy"], jax_jit=False, + allow_dask_compute=True), + "cont2discrete": xp_capabilities(np_only=True, exceptions=["cupy"]), + "convolve": xp_capabilities(cpu_only=True, exceptions=["cupy", "jax.numpy"], + allow_dask_compute=True, + extra_note=uses_choose_conv_extra_note), + "convolve2d": xp_capabilities(cpu_only=True, exceptions=["cupy", "jax.numpy"], + allow_dask_compute=True, + extra_note=convolve2d_extra_note), + "correlate": xp_capabilities(cpu_only=True, exceptions=["cupy", "jax.numpy"], + allow_dask_compute=True, + extra_note=uses_choose_conv_extra_note), + "correlate2d": xp_capabilities(cpu_only=True, exceptions=["cupy", "jax.numpy"], + allow_dask_compute=True, + extra_note=convolve2d_extra_note), + "correlation_lags": xp_capabilities(out_of_scope=True), + "cspline1d": xp_capabilities(cpu_only=True, exceptions=["cupy"], + jax_jit=False, allow_dask_compute=True), + "cspline1d_eval": xp_capabilities(cpu_only=True, exceptions=["cupy"], + jax_jit=False, allow_dask_compute=True), + "cspline2d": xp_capabilities(cpu_only=True, exceptions=["cupy"], + jax_jit=False, allow_dask_compute=True), + "deconvolve": xp_capabilities(cpu_only=True, exceptions=["cupy"], + jax_jit=False, allow_dask_compute=True), + "decimate": xp_capabilities(np_only=True, exceptions=["cupy"]), + "detrend": xp_capabilities(cpu_only=True, exceptions=["cupy", "jax.numpy"], + allow_dask_compute=True), + "dimpulse": xp_capabilities(np_only=True, exceptions=["cupy"]), + "dlti": xp_capabilities(np_only=True, + reason="works in CuPy but delegation isn't set up yet"), + "ellip": xp_capabilities(cpu_only=True, exceptions=["cupy"], jax_jit=False, + allow_dask_compute=True, + reason="scipy.special.ellipk"), + "ellipord": xp_capabilities(cpu_only=True, exceptions=["cupy"], + jax_jit=False, allow_dask_compute=True, + reason="scipy.special.ellipk"), + "findfreqs": xp_capabilities(cpu_only=True, exceptions=["cupy", "torch"], + jax_jit=False, allow_dask_compute=True), + "firls": xp_capabilities(cpu_only=True, allow_dask_compute=True, jax_jit=False, + reason="lstsq"), + "firwin": xp_capabilities(cpu_only=True, exceptions=["cupy", "torch"], + jax_jit=False, allow_dask_compute=True), + "firwin2": xp_capabilities(cpu_only=True, exceptions=["cupy"], + jax_jit=False, allow_dask_compute=True, + reason="firwin uses np.interp"), + "fftconvolve": xp_capabilities(cpu_only=True, + exceptions=["cupy", "jax.numpy", "torch"]), + "freqs": xp_capabilities(cpu_only=True, exceptions=["cupy", "torch"], + jax_jit=False, allow_dask_compute=True), + "freqs_zpk": xp_capabilities(cpu_only=True, exceptions=["cupy", "torch"], + jax_jit=False, allow_dask_compute=True), + "freqz": xp_capabilities(cpu_only=True, exceptions=["cupy", "torch"], + jax_jit=False, allow_dask_compute=True), + "freqz_sos": xp_capabilities(cpu_only=True, exceptions=["cupy", "torch"], + jax_jit=False, allow_dask_compute=True), + "group_delay": xp_capabilities(cpu_only=True, exceptions=["cupy"], + jax_jit=False, allow_dask_compute=True), + "invres": xp_capabilities(np_only=True, exceptions=["cupy"]), + "invresz": xp_capabilities(np_only=True, exceptions=["cupy"]), + "iircomb": xp_capabilities(xfail_backends=[("jax.numpy", "inaccurate")]), + "iirfilter": xp_capabilities(cpu_only=True, exceptions=["cupy", "torch"], + jax_jit=False, allow_dask_compute=True), + "kaiser_atten": xp_capabilities( + out_of_scope=True, reason="scalars in, scalars out" + ), + "kaiser_beta": xp_capabilities(out_of_scope=True, reason="scalars in, scalars out"), + "kaiserord": xp_capabilities(out_of_scope=True, reason="scalars in, scalars out"), + "lfilter": xp_capabilities(cpu_only=True, exceptions=["cupy"], + allow_dask_compute=True, jax_jit=False), + "lfilter_zi": xp_capabilities(cpu_only=True, allow_dask_compute=True, + jax_jit=False), + "lfiltic": xp_capabilities(cpu_only=True, exceptions=["cupy"], + allow_dask_compute=True), + "lp2bp": xp_capabilities(cpu_only=True, exceptions=["cupy", "torch"], + allow_dask_compute=True, jax_jit=False), + "lp2bp_zpk": xp_capabilities(cpu_only=True, exceptions=["cupy", "torch"], + allow_dask_compute=True, jax_jit=False), + "lp2bs": xp_capabilities(cpu_only=True, exceptions=["cupy", "torch"], + allow_dask_compute=True, jax_jit=False), + "lp2bs_zpk": xp_capabilities(cpu_only=True, exceptions=["cupy", "torch"], + allow_dask_compute=True, jax_jit=False), + "lp2lp": xp_capabilities(cpu_only=True, exceptions=["cupy", "torch"], + allow_dask_compute=True, jax_jit=False), + "lp2lp_zpk": xp_capabilities(cpu_only=True, exceptions=["cupy", "torch"], + allow_dask_compute=True, jax_jit=False), + "lp2hp": xp_capabilities(cpu_only=True, exceptions=["cupy", "torch"], + allow_dask_compute=True, jax_jit=False), + "lp2hp_zpk": xp_capabilities(cpu_only=True, exceptions=["cupy", "torch"], + allow_dask_compute=True, jax_jit=False), + "lti": xp_capabilities(np_only=True, + reason="works in CuPy but delegation isn't set up yet"), + "medfilt": xp_capabilities(cpu_only=True, exceptions=["cupy"], + allow_dask_compute=True, jax_jit=False, + reason="uses scipy.ndimage.rank_filter"), + "medfilt2d": xp_capabilities(cpu_only=True, exceptions=["cupy"], + allow_dask_compute=True, jax_jit=False, + reason="c extension module"), + "minimum_phase": xp_capabilities(cpu_only=True, exceptions=["cupy", "torch"], + allow_dask_compute=True, jax_jit=False), + "normalize": xp_capabilities(cpu_only=True, exceptions=["cupy", "torch"], + jax_jit=False, allow_dask_compute=True), + "oaconvolve": xp_capabilities( + cpu_only=True, exceptions=["cupy", "torch"], + xfail_backends=[("dask.array", "wrong answer")], + ), + "order_filter": xp_capabilities(cpu_only=True, exceptions=["cupy"], + allow_dask_compute=True, jax_jit=False, + reason="uses scipy.ndimage.rank_filter"), + "qspline1d": xp_capabilities(cpu_only=True, exceptions=["cupy"], + jax_jit=False, allow_dask_compute=True), + "qspline1d_eval": xp_capabilities(cpu_only=True, exceptions=["cupy"], + jax_jit=False, allow_dask_compute=True), + "qspline2d": xp_capabilities(np_only=True, exceptions=["cupy"]), + "remez": xp_capabilities(cpu_only=True, allow_dask_compute=True, jax_jit=False), + "resample": xp_capabilities( + cpu_only=True, exceptions=["cupy"], + skip_backends=[ + ("dask.array", "XXX something in dask"), + ("jax.numpy", "XXX: immutable arrays"), + ] + ), + "resample_poly": xp_capabilities( + cpu_only=True, exceptions=["cupy"], + jax_jit=False, skip_backends=[("dask.array", "XXX something in dask")], + extra_note=resample_poly_extra_note, + ), + "residue": xp_capabilities(np_only=True, exceptions=["cupy"]), + "residuez": xp_capabilities(np_only=True, exceptions=["cupy"]), + "savgol_filter": xp_capabilities(cpu_only=True, exceptions=["cupy"], + jax_jit=False, + reason="convolve1d is cpu-only"), + "sawtooth": xp_capabilities(jax_jit=False, + skip_backends=[("dask.array", "dask tests fail")]), + "sepfir2d": xp_capabilities(np_only=True), + "sos2zpk": xp_capabilities(cpu_only=True, exceptions=["cupy"], jax_jit=False, + allow_dask_compute=True), + "sos2tf": xp_capabilities(cpu_only=True, exceptions=["cupy"], jax_jit=False, + allow_dask_compute=True), + "sosfilt": xp_capabilities(cpu_only=True, exceptions=["cupy"], jax_jit=False, + allow_dask_compute=True), + "sosfilt_zi": xp_capabilities(cpu_only=True, allow_dask_compute=True, + jax_jit=False), + "sosfiltfilt": xp_capabilities( + cpu_only=True, exceptions=["cupy"], + skip_backends=[ + ( + "dask.array", + "sosfiltfilt directly sets shape attributes on arrays" + " which dask doesn't like" + ), + ("torch", "negative strides"), + ("jax.numpy", "sosfilt works in-place"), + ], + ), + "sosfreqz": xp_capabilities(cpu_only=True, exceptions=["cupy", "torch"], + jax_jit=False, allow_dask_compute=True), + "spline_filter": xp_capabilities(cpu_only=True, exceptions=["cupy"], + jax_jit=False, allow_dask_compute=True), + "tf2sos": xp_capabilities(cpu_only=True, exceptions=["cupy"], jax_jit=False, + allow_dask_compute=True), + "tf2zpk": xp_capabilities(cpu_only=True, exceptions=["cupy"], jax_jit=False, + allow_dask_compute=True), + "unique_roots": xp_capabilities(np_only=True, exceptions=["cupy"]), + "upfirdn": xp_capabilities(cpu_only=True, exceptions=["cupy"], jax_jit=False, + allow_dask_compute=True, + reason="Cython implementation", + extra_note=upfirdn_extra_note), + "vectorstrength": xp_capabilities(cpu_only=True, exceptions=["cupy", "torch"], + allow_dask_compute=True, jax_jit=False), + "wiener": xp_capabilities(cpu_only=True, exceptions=["cupy", "jax.numpy"], + allow_dask_compute=True, jax_jit=False, + reason="uses scipy.signal.correlate"), + "zpk2sos": xp_capabilities(cpu_only=True, exceptions=["cupy"], jax_jit=False, + allow_dask_compute=True), + "zpk2tf": xp_capabilities(cpu_only=True, exceptions=["cupy"], jax_jit=False, + allow_dask_compute=True, + extra_note=zpk2tf_extra_note), + "spectrogram": xp_capabilities(out_of_scope=True), # legacy + "stft": xp_capabilities(out_of_scope=True), # legacy + "istft": xp_capabilities(out_of_scope=True), # legacy + "check_COLA": xp_capabilities(out_of_scope=True), # legacy +} + + +# ### decorate ### +for obj_name in _signal_api.__all__: + bare_obj = getattr(_signal_api, obj_name) + delegator = getattr(_delegators, obj_name + "_signature", None) + + if SCIPY_ARRAY_API and delegator is not None: + f = delegate_xp(delegator, MODULE_NAME)(bare_obj) + else: + f = bare_obj + + if not isinstance(f, types.ModuleType): + capabilities = capabilities_overrides.get( + obj_name, get_default_capabilities(obj_name, delegator) + ) + f = capabilities(f) # pyrefly:ignore[not-callable] + + # add the decorated function to the namespace, to be imported in __init__.py + vars()[obj_name] = f diff --git a/venv/lib/python3.12/site-packages/scipy/signal/_upfirdn.py b/venv/lib/python3.12/site-packages/scipy/signal/_upfirdn.py new file mode 100644 index 0000000000000000000000000000000000000000..d01a3a36b15c0172907a49afb16236cf75b565ab --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/_upfirdn.py @@ -0,0 +1,219 @@ +# Code adapted from "upfirdn" python library with permission: +# +# Copyright (c) 2009, Motorola, Inc +# +# All Rights Reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of Motorola nor the names of its contributors may be +# used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import numpy as np + +from scipy._lib._array_api import array_namespace +from ._upfirdn_apply import _output_len, _apply, mode_enum + +__all__ = ['upfirdn', '_output_len'] + +_upfirdn_modes = [ + 'constant', 'wrap', 'edge', 'smooth', 'symmetric', 'reflect', + 'antisymmetric', 'antireflect', 'line', +] + + +def _pad_h(h, up): + """Store coefficients in a transposed, flipped arrangement. + + For example, suppose upRate is 3, and the + input number of coefficients is 10, represented as h[0], ..., h[9]. + + Then the internal buffer will look like this:: + + h[9], h[6], h[3], h[0], // flipped phase 0 coefs + 0, h[7], h[4], h[1], // flipped phase 1 coefs (zero-padded) + 0, h[8], h[5], h[2], // flipped phase 2 coefs (zero-padded) + + """ + h_padlen = len(h) + (-len(h) % up) + h_full = np.zeros(h_padlen, h.dtype) + h_full[:len(h)] = h + h_full = h_full.reshape(-1, up).T[:, ::-1].ravel() + return h_full + + +def _check_mode(mode): + mode = mode.lower() + enum = mode_enum(mode) + return enum + + +class _UpFIRDn: + """Helper for resampling.""" + + def __init__(self, h, x_dtype, up, down): + h = np.asarray(h) + if h.ndim != 1 or h.size == 0: + raise ValueError('h must be 1-D with non-zero length') + self._output_type = np.result_type(h.dtype, x_dtype, np.float32) + h = np.asarray(h, self._output_type) + self._up = int(up) + self._down = int(down) + if self._up < 1 or self._down < 1: + raise ValueError('Both up and down must be >= 1') + # This both transposes, and "flips" each phase for filtering + self._h_trans_flip = _pad_h(h, self._up) + self._h_trans_flip = np.ascontiguousarray(self._h_trans_flip) + self._h_len_orig = len(h) + + def apply_filter(self, x, axis=-1, mode='constant', cval=0): + """Apply the prepared filter to the specified axis of N-D signal x.""" + output_len = _output_len(self._h_len_orig, x.shape[axis], + self._up, self._down) + # Explicit use of np.int64 for output_shape dtype avoids OverflowError + # when allocating large array on platforms where intp is 32 bits. + output_shape = np.asarray(x.shape, dtype=np.int64) + output_shape[axis] = output_len + out = np.zeros(output_shape, dtype=self._output_type, order='C') + axis = axis % x.ndim + mode = _check_mode(mode) + _apply(np.asarray(x, self._output_type), + self._h_trans_flip, out, + self._up, self._down, axis, mode, cval) + return out + + +def upfirdn(h, x, up=1, down=1, axis=-1, mode='constant', cval=0): + """Upsample, FIR filter, and downsample. + + Parameters + ---------- + h : array_like + 1-D FIR (finite-impulse response) filter coefficients. + x : array_like + Input signal array. + up : int, optional + Upsampling rate. Default is 1. + down : int, optional + Downsampling rate. Default is 1. + axis : int, optional + The axis of the input data array along which to apply the + linear filter. The filter is applied to each subarray along + this axis. Default is -1. + mode : str, optional + The signal extension mode to use. The set + ``{"constant", "symmetric", "reflect", "edge", "wrap"}`` correspond to + modes provided by `numpy.pad`. ``"smooth"`` implements a smooth + extension by extending based on the slope of the last 2 points at each + end of the array. ``"antireflect"`` and ``"antisymmetric"`` are + anti-symmetric versions of ``"reflect"`` and ``"symmetric"``. The mode + `"line"` extends the signal based on a linear trend defined by the + first and last points along the ``axis``. + + .. versionadded:: 1.4.0 + cval : float, optional + The constant value to use when ``mode == "constant"``. + + .. versionadded:: 1.4.0 + + Returns + ------- + y : ndarray + The output signal array. Dimensions will be the same as `x` except + for along `axis`, which will change size according to the `h`, + `up`, and `down` parameters. + + Notes + ----- + The algorithm is an implementation of the block diagram shown on page 129 + of the Vaidyanathan text [1]_ (Figure 4.3-8d). + + The direct approach of upsampling by factor of P with zero insertion, + FIR filtering of length ``N``, and downsampling by factor of Q is + O(N*Q) per output sample. The polyphase implementation used here is + O(N/P). + + .. versionadded:: 0.18 + + References + ---------- + .. [1] P. P. Vaidyanathan, Multirate Systems and Filter Banks, + Prentice Hall, 1993. + + Examples + -------- + Simple operations: + + >>> import numpy as np + >>> from scipy.signal import upfirdn + >>> upfirdn([1, 1, 1], [1, 1, 1]) # FIR filter + array([ 1., 2., 3., 2., 1.]) + >>> upfirdn([1], [1, 2, 3], 3) # upsampling with zeros insertion + array([ 1., 0., 0., 2., 0., 0., 3.]) + >>> upfirdn([1, 1, 1], [1, 2, 3], 3) # upsampling with sample-and-hold + array([ 1., 1., 1., 2., 2., 2., 3., 3., 3.]) + >>> upfirdn([.5, 1, .5], [1, 1, 1], 2) # linear interpolation + array([ 0.5, 1. , 1. , 1. , 1. , 1. , 0.5]) + >>> upfirdn([1], np.arange(10), 1, 3) # decimation by 3 + array([ 0., 3., 6., 9.]) + >>> upfirdn([.5, 1, .5], np.arange(10), 2, 3) # linear interp, rate 2/3 + array([ 0. , 1. , 2.5, 4. , 5.5, 7. , 8.5]) + + Apply a single filter to multiple signals: + + >>> x = np.reshape(np.arange(8), (4, 2)) + >>> x + array([[0, 1], + [2, 3], + [4, 5], + [6, 7]]) + + Apply along the last dimension of ``x``: + + >>> h = [1, 1] + >>> upfirdn(h, x, 2) + array([[ 0., 0., 1., 1.], + [ 2., 2., 3., 3.], + [ 4., 4., 5., 5.], + [ 6., 6., 7., 7.]]) + + Apply along the 0th dimension of ``x``: + + >>> upfirdn(h, x, 2, axis=0) + array([[ 0., 1.], + [ 0., 1.], + [ 2., 3.], + [ 2., 3.], + [ 4., 5.], + [ 4., 5.], + [ 6., 7.], + [ 6., 7.]]) + """ + xp = array_namespace(h, x) + + x = np.asarray(x) + ufd = _UpFIRDn(h, x.dtype, up, down) + # This is equivalent to (but faster than) using np.apply_along_axis + return xp.asarray(ufd.apply_filter(x, axis, mode, cval)) diff --git a/venv/lib/python3.12/site-packages/scipy/signal/_upfirdn_apply.cpython-312-x86_64-linux-gnu.so b/venv/lib/python3.12/site-packages/scipy/signal/_upfirdn_apply.cpython-312-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..0216b37580109e96b1695c3a99151f0ff364bbeb --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/_upfirdn_apply.cpython-312-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b44aba30d7c5b8efe4d7220439e2f05ece000a1cbd2d66bac7156f90089d64e5 +size 202816 diff --git a/venv/lib/python3.12/site-packages/scipy/signal/_waveforms.py b/venv/lib/python3.12/site-packages/scipy/signal/_waveforms.py new file mode 100644 index 0000000000000000000000000000000000000000..a41c40b57a330ee9b642970fd9bd6ae802850b42 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/_waveforms.py @@ -0,0 +1,685 @@ +# Author: Travis Oliphant +# 2003 +# +# Feb. 2010: Updated by Warren Weckesser: +# Rewrote much of chirp() +# Added sweep_poly() +import numpy as np +from numpy import asarray, zeros, pi, log, sqrt, \ + exp, cos, sin, polyval, polyint + +from scipy._lib._array_api import array_namespace, xp_promote +import scipy._external.array_api_extra as xpx + + +__all__ = ['sawtooth', 'square', 'gausspulse', 'chirp', 'sweep_poly', + 'unit_impulse'] + + +def sawtooth(t, width=1.): + """ + Return a periodic sawtooth or triangle waveform. + + The sawtooth waveform has a period ``2*pi``, rises from -1 to 1 on the + interval 0 to ``width*2*pi``, then drops from 1 to -1 on the interval + ``width*2*pi`` to ``2*pi``. `width` must be in the interval ``[0, 1]``. + + Note that this is not band-limited. It produces an infinite number + of harmonics, which are aliased back and forth across the frequency + spectrum. + + Parameters + ---------- + t : array_like + Time. + width : array_like, optional + Width of the rising ramp as a proportion of the total cycle. + Default is 1, producing a rising ramp, while 0 produces a falling + ramp. ``width=0.5`` produces a triangle wave. + If an array, causes wave shape to change over time, and must be the + same length as `t`. + + Returns + ------- + y : ndarray + Output array containing the sawtooth waveform. + + Examples + -------- + A 5 Hz waveform sampled at 500 Hz for 1 second: + + >>> import numpy as np + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + >>> t = np.linspace(0, 1, 500) + >>> plt.plot(t, signal.sawtooth(2 * np.pi * 5 * t)) + + """ + xp = array_namespace(t, width) + t, w = xp_promote(t, width, broadcast=True, force_floating=True, xp=xp) + y = xp.zeros_like(t) + + # width must be between 0 and 1 inclusive + mask1 = (w > 1) | (w < 0) + y = xpx.at(y, mask1).set(xp.nan) + + # take t modulo 2*pi + tmod = t % (2*xp.pi) + + # on the interval 0 to width*2*pi function is tmod / (pi*w) - 1 + mask2 = ~mask1 & (tmod < w*2*xp.pi) + y = xpx.at(y, mask2).set(tmod[mask2]/(xp.pi*w[mask2]) - 1) + + # on the interval width*2*pi to 2*pi function is (pi*(w+1)-tmod) / (pi*(1-w)) + mask3 = ~(mask1 | mask2) + y = xpx.at(y, mask3).set( + (xp.pi*(w[mask3] + 1) - tmod[mask3])/(xp.pi*(1 - w[mask3])) + ) + return y + + +def square(t, duty=0.5): + """ + Return a periodic square-wave waveform. + + The square wave has a period ``2*pi``, has value +1 from 0 to + ``2*pi*duty`` and -1 from ``2*pi*duty`` to ``2*pi``. `duty` must be in + the interval ``[0,1]``. + + Note that this is not band-limited. It produces an infinite number + of harmonics, which are aliased back and forth across the frequency + spectrum. + + Parameters + ---------- + t : array_like + The input time array. + duty : array_like, optional + Duty cycle. Default is 0.5 (50% duty cycle). + If an array, causes wave shape to change over time, and must be the + same length as t. + + Returns + ------- + y : ndarray + Output array containing the square waveform. + + Examples + -------- + A 5 Hz waveform sampled at 500 Hz for 1 second: + + >>> import numpy as np + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + >>> t = np.linspace(0, 1, 500, endpoint=False) + >>> plt.plot(t, signal.square(2 * np.pi * 5 * t)) + >>> plt.ylim(-2, 2) + + A pulse-width modulated sine wave: + + >>> plt.figure() + >>> sig = np.sin(2 * np.pi * t) + >>> pwm = signal.square(2 * np.pi * 30 * t, duty=(sig + 1)/2) + >>> plt.subplot(2, 1, 1) + >>> plt.plot(t, sig) + >>> plt.subplot(2, 1, 2) + >>> plt.plot(t, pwm) + >>> plt.ylim(-1.5, 1.5) + + """ + xp = array_namespace(t, duty) + t, w = xp_promote(t, duty, xp=xp, force_floating=True, broadcast=True) + + y = xp.zeros_like(t) + + # width must be between 0 and 1 inclusive + mask1 = (w > 1) | (w < 0) + y = xpx.at(y, mask1).set(xp.nan) + + # on the interval 0 to duty*2*pi function is 1 + tmod = t % (2 * xp.pi) + mask2 = ~mask1 & (tmod < w*2*xp.pi) + y = xpx.at(y, mask2).set(1) + + # on the interval duty*2*pi to 2*pi function is -1 + mask3 = ~(mask1 | mask2) + y = xpx.at(y, mask3).set(-1) + return y + + +def gausspulse(t, fc=1000, bw=0.5, bwr=-6, tpr=-60, retquad=False, + retenv=False): + """ + Return a Gaussian modulated sinusoid. + + The formula for the returned signal is given by:: + + exp(-a t^2) exp(1j*2*pi*fc*t) + + If `retquad` is True, then return the real and imaginary parts + (in-phase and quadrature). + If `retenv` is True, then return the envelope (unmodulated signal). + Otherwise, return the real part of the modulated sinusoid. + + Parameters + ---------- + t : ndarray or 'cutoff' + Input array. + fc : float, optional + Center frequency (e.g. Hz). Default is 1000. + bw : float, optional + Fractional bandwidth in frequency domain of pulse (e.g. Hz). + Default is 0.5. + bwr : float, optional + Reference level at which fractional bandwidth is calculated (dB). + Default is -6. + tpr : float, optional + If `t` is 'cutoff', then the function returns the cutoff + time for when the pulse amplitude falls below `tpr` (in dB). + Default is -60. + retquad : bool, optional + If True, return the quadrature (imaginary) as well as the real part + of the signal. Default is False. + retenv : bool, optional + If True, return the envelope of the signal. Default is False. + + Returns + ------- + yI : ndarray + Real part of signal. Always returned. + yQ : ndarray + Imaginary part of signal. Only returned if `retquad` is True. + yenv : ndarray + Envelope of signal. Only returned if `retenv` is True. + + Examples + -------- + Plot real component, imaginary component, and envelope for a 5 Hz pulse, + sampled at 100 Hz for 2 seconds: + + >>> import numpy as np + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + >>> t = np.linspace(-1, 1, 2 * 100, endpoint=False) + >>> i, q, e = signal.gausspulse(t, fc=5, retquad=True, retenv=True) + >>> plt.plot(t, i, t, q, t, e, '--') + + """ + if fc < 0: + raise ValueError(f"Center frequency (fc={fc:.2f}) must be >=0.") + if bw <= 0: + raise ValueError(f"Fractional bandwidth (bw={bw:.2f}) must be > 0.") + if bwr >= 0: + raise ValueError(f"Reference level for bandwidth (bwr={bwr:.2f}) " + "must be < 0 dB") + + # exp(-a t^2) <-> sqrt(pi/a) exp(-pi^2/a * f^2) = g(f) + + ref = pow(10.0, bwr / 20.0) + # fdel = fc*bw/2: g(fdel) = ref --- solve this for a + # + # pi^2/a * fc^2 * bw^2 /4=-log(ref) + a = -(pi * fc * bw) ** 2 / (4.0 * log(ref)) + + if isinstance(t, str): + if t == 'cutoff': # compute cut_off point + # Solve exp(-a tc**2) = tref for tc + # tc = sqrt(-log(tref) / a) where tref = 10^(tpr/20) + if tpr >= 0: + raise ValueError("Reference level for time cutoff must " + "be < 0 dB") + tref = pow(10.0, tpr / 20.0) + return sqrt(-log(tref) / a) + else: + raise ValueError("If `t` is a string, it must be 'cutoff'") + + yenv = exp(-a * t * t) + yI = yenv * cos(2 * pi * fc * t) + yQ = yenv * sin(2 * pi * fc * t) + if not retquad and not retenv: + return yI + if not retquad and retenv: + return yI, yenv + if retquad and not retenv: + return yI, yQ + if retquad and retenv: + return yI, yQ, yenv + + +def chirp(t, f0, t1, f1, method='linear', phi=0, vertex_zero=True, *, + complex=False): + r"""Frequency-swept cosine generator. + + In the following, 'Hz' should be interpreted as 'cycles per unit'; + there is no requirement here that the unit is one second. The + important distinction is that the units of rotation are cycles, not + radians. Likewise, `t` could be a measurement of space instead of time. + + Parameters + ---------- + t : array_like + Times at which to evaluate the waveform. + f0 : float + Frequency (e.g. Hz) at time t=0. + t1 : float + Time at which `f1` is specified. + f1 : float + Frequency (e.g. Hz) of the waveform at time `t1`. + method : {'linear', 'quadratic', 'logarithmic', 'hyperbolic'}, optional + Kind of frequency sweep. If not given, `linear` is assumed. See + Notes below for more details. + phi : float, optional + Phase offset, in degrees. Default is 0. + vertex_zero : bool, optional + This parameter is only used when `method` is 'quadratic'. + It determines whether the vertex of the parabola that is the graph + of the frequency is at t=0 or t=t1. + complex : bool, optional + This parameter creates a complex-valued analytic signal instead of a + real-valued signal. It allows the use of complex baseband (in communications + domain). Default is False. + + .. versionadded:: 1.15.0 + + Returns + ------- + y : ndarray + A numpy array containing the signal evaluated at `t` with the requested + time-varying frequency. More precisely, the function returns + ``exp(1j*phase + 1j*(pi/180)*phi) if complex else cos(phase + (pi/180)*phi)`` + where `phase` is the integral (from 0 to `t`) of ``2*pi*f(t)``. + The instantaneous frequency ``f(t)`` is defined below. + + See Also + -------- + sweep_poly + + Notes + ----- + There are four possible options for the parameter `method`, which have a (long) + standard form and some allowed abbreviations. The formulas for the instantaneous + frequency :math:`f(t)` of the generated signal are as follows: + + 1. Parameter `method` in ``('linear', 'lin', 'li')``: + + .. math:: + f(t) = f_0 + \beta\, t \quad\text{with}\quad + \beta = \frac{f_1 - f_0}{t_1} + + Frequency :math:`f(t)` varies linearly over time with a constant rate + :math:`\beta`. + + 2. Parameter `method` in ``('quadratic', 'quad', 'q')``: + + .. math:: + f(t) = + \begin{cases} + f_0 + \beta\, t^2 & \text{if vertex_zero is True,}\\ + f_1 + \beta\, (t_1 - t)^2 & \text{otherwise,} + \end{cases} + \quad\text{with}\quad + \beta = \frac{f_1 - f_0}{t_1^2} + + The graph of the frequency f(t) is a parabola through :math:`(0, f_0)` and + :math:`(t_1, f_1)`. By default, the vertex of the parabola is at + :math:`(0, f_0)`. If `vertex_zero` is ``False``, then the vertex is at + :math:`(t_1, f_1)`. + To use a more general quadratic function, or an arbitrary + polynomial, use the function `scipy.signal.sweep_poly`. + + 3. Parameter `method` in ``('logarithmic', 'log', 'lo')``: + + .. math:: + f(t) = f_0 \left(\frac{f_1}{f_0}\right)^{t/t_1} + + :math:`f_0` and :math:`f_1` must be nonzero and have the same sign. + This signal is also known as a geometric or exponential chirp. + + 4. Parameter `method` in ``('hyperbolic', 'hyp')``: + + .. math:: + f(t) = \frac{\alpha}{\beta\, t + \gamma} \quad\text{with}\quad + \alpha = f_0 f_1 t_1, \ \beta = f_0 - f_1, \ \gamma = f_1 t_1 + + :math:`f_0` and :math:`f_1` must be nonzero. + + + Examples + -------- + For the first example, a linear chirp ranging from 6 Hz to 1 Hz over 10 seconds is + plotted: + + >>> import numpy as np + >>> from matplotlib.pyplot import tight_layout + >>> from scipy.signal import chirp, square, ShortTimeFFT + >>> from scipy.signal.windows import gaussian + >>> import matplotlib.pyplot as plt + ... + >>> N, T = 1000, 0.01 # number of samples and sampling interval for 10 s signal + >>> t = np.arange(N) * T # timestamps + ... + >>> x_lin = chirp(t, f0=6, f1=1, t1=10, method='linear') + ... + >>> fg0, ax0 = plt.subplots() + >>> ax0.set_title(r"Linear Chirp from $f(0)=6\,$Hz to $f(10)=1\,$Hz") + >>> ax0.set(xlabel="Time $t$ in Seconds", ylabel=r"Amplitude $x_\text{lin}(t)$") + >>> ax0.plot(t, x_lin) + >>> plt.show() + + The following four plots each show the short-time Fourier transform of a chirp + ranging from 45 Hz to 5 Hz with different values for the parameter `method` + (and `vertex_zero`): + + >>> x_qu0 = chirp(t, f0=45, f1=5, t1=N*T, method='quadratic', vertex_zero=True) + >>> x_qu1 = chirp(t, f0=45, f1=5, t1=N*T, method='quadratic', vertex_zero=False) + >>> x_log = chirp(t, f0=45, f1=5, t1=N*T, method='logarithmic') + >>> x_hyp = chirp(t, f0=45, f1=5, t1=N*T, method='hyperbolic') + ... + >>> win = gaussian(50, std=12, sym=True) + >>> SFT = ShortTimeFFT(win, hop=2, fs=1/T, mfft=800, scale_to='magnitude') + >>> ts = ("'quadratic', vertex_zero=True", "'quadratic', vertex_zero=False", + ... "'logarithmic'", "'hyperbolic'") + >>> fg1, ax1s = plt.subplots(2, 2, sharex='all', sharey='all', + ... figsize=(6, 5), layout="constrained") + >>> for x_, ax_, t_ in zip([x_qu0, x_qu1, x_log, x_hyp], ax1s.ravel(), ts): + ... aSx = abs(SFT.stft(x_)) + ... im_ = ax_.imshow(aSx, origin='lower', aspect='auto', extent=SFT.extent(N), + ... cmap='plasma') + ... ax_.set_title(t_) + ... if t_ == "'hyperbolic'": + ... fg1.colorbar(im_, ax=ax1s, label='Magnitude $|S_z(t,f)|$') + >>> _ = fg1.supxlabel("Time $t$ in Seconds") # `_ =` is needed to pass doctests + >>> _ = fg1.supylabel("Frequency $f$ in Hertz") + >>> plt.show() + + Finally, the short-time Fourier transform of a complex-valued linear chirp + ranging from -30 Hz to 30 Hz is depicted: + + >>> z_lin = chirp(t, f0=-30, f1=30, t1=N*T, method="linear", complex=True) + >>> SFT.fft_mode = 'centered' # needed to work with complex signals + >>> aSz = abs(SFT.stft(z_lin)) + ... + >>> fg2, ax2 = plt.subplots() + >>> ax2.set_title(r"Linear Chirp from $-30\,$Hz to $30\,$Hz") + >>> ax2.set(xlabel="Time $t$ in Seconds", ylabel="Frequency $f$ in Hertz") + >>> im2 = ax2.imshow(aSz, origin='lower', aspect='auto', + ... extent=SFT.extent(N), cmap='viridis') + >>> fg2.colorbar(im2, label='Magnitude $|S_z(t,f)|$') + >>> plt.show() + + Note that using negative frequencies makes only sense with complex-valued signals. + Furthermore, the magnitude of the complex exponential function is one whereas the + magnitude of the real-valued cosine function is only 1/2. + """ + # 'phase' is computed in _chirp_phase, to make testing easier. + phase = _chirp_phase(t, f0, t1, f1, method, vertex_zero) + np.deg2rad(phi) + return np.exp(1j*phase) if complex else np.cos(phase) + + +def _chirp_phase(t, f0, t1, f1, method='linear', vertex_zero=True): + """ + Calculate the phase used by `chirp` to generate its output. + + See `chirp` for a description of the arguments. + + """ + t = asarray(t) + f0 = float(f0) + t1 = float(t1) + f1 = float(f1) + if method in ['linear', 'lin', 'li']: + beta = (f1 - f0) / t1 + phase = 2 * pi * (f0 * t + 0.5 * beta * t * t) + + elif method in ['quadratic', 'quad', 'q']: + beta = (f1 - f0) / (t1 ** 2) + if vertex_zero: + phase = 2 * pi * (f0 * t + beta * t ** 3 / 3) + else: + phase = 2 * pi * (f1 * t + beta * ((t1 - t) ** 3 - t1 ** 3) / 3) + + elif method in ['logarithmic', 'log', 'lo']: + if f0 * f1 <= 0.0: + raise ValueError("For a logarithmic chirp, f0 and f1 must be " + "nonzero and have the same sign.") + if f0 == f1: + phase = 2 * pi * f0 * t + else: + beta = t1 / log(f1 / f0) + phase = 2 * pi * beta * f0 * (pow(f1 / f0, t / t1) - 1.0) + + elif method in ['hyperbolic', 'hyp']: + if f0 == 0 or f1 == 0: + raise ValueError("For a hyperbolic chirp, f0 and f1 must be " + "nonzero.") + if f0 == f1: + # Degenerate case: constant frequency. + phase = 2 * pi * f0 * t + else: + # Singular point: the instantaneous frequency blows up + # when t == sing. + sing = -f1 * t1 / (f0 - f1) + phase = 2 * pi * (-sing * f0) * log(np.abs(1 - t/sing)) + + else: + raise ValueError("method must be 'linear', 'quadratic', 'logarithmic', " + f"or 'hyperbolic', but a value of {method!r} was given.") + + return phase + + +def sweep_poly(t, poly, phi=0): + """ + Frequency-swept cosine generator, with a time-dependent frequency. + + This function generates a sinusoidal function whose instantaneous + frequency varies with time. The frequency at time `t` is given by + the polynomial `poly`. + + Parameters + ---------- + t : ndarray + Times at which to evaluate the waveform. + poly : 1-D array_like or instance of numpy.poly1d + The desired frequency expressed as a polynomial. If `poly` is + a list or ndarray of length n, then the elements of `poly` are + the coefficients of the polynomial, and the instantaneous + frequency is:: + + f(t) = poly[0]*t**(n-1) + poly[1]*t**(n-2) + ... + poly[n-1] + + If `poly` is an instance of numpy.poly1d, then the + instantaneous frequency is:: + + f(t) = poly(t) + + phi : float, optional + Phase offset, in degrees, Default: 0. + + Returns + ------- + sweep_poly : ndarray + A numpy array containing the signal evaluated at `t` with the + requested time-varying frequency. More precisely, the function + returns ``cos(phase + (pi/180)*phi)``, where `phase` is the integral + (from 0 to t) of ``2 * pi * f(t)``; ``f(t)`` is defined above. + + See Also + -------- + chirp + + Notes + ----- + .. versionadded:: 0.8.0 + + If `poly` is a list or ndarray of length `n`, then the elements of + `poly` are the coefficients of the polynomial, and the instantaneous + frequency is: + + ``f(t) = poly[0]*t**(n-1) + poly[1]*t**(n-2) + ... + poly[n-1]`` + + If `poly` is an instance of `numpy.poly1d`, then the instantaneous + frequency is: + + ``f(t) = poly(t)`` + + Finally, the output `s` is: + + ``cos(phase + (pi/180)*phi)`` + + where `phase` is the integral from 0 to `t` of ``2 * pi * f(t)``, + ``f(t)`` as defined above. + + Examples + -------- + Compute the waveform with instantaneous frequency:: + + f(t) = 0.025*t**3 - 0.36*t**2 + 1.25*t + 2 + + over the interval 0 <= t <= 10. + + >>> import numpy as np + >>> from scipy.signal import sweep_poly + >>> p = np.poly1d([0.025, -0.36, 1.25, 2.0]) + >>> t = np.linspace(0, 10, 5001) + >>> w = sweep_poly(t, p) + + Plot it: + + >>> import matplotlib.pyplot as plt + >>> plt.subplot(2, 1, 1) + >>> plt.plot(t, w) + >>> plt.title("Sweep Poly\\nwith frequency " + + ... "$f(t) = 0.025t^3 - 0.36t^2 + 1.25t + 2$") + >>> plt.subplot(2, 1, 2) + >>> plt.plot(t, p(t), 'r', label='f(t)') + >>> plt.legend() + >>> plt.xlabel('t') + >>> plt.tight_layout() + >>> plt.show() + + """ + # 'phase' is computed in _sweep_poly_phase, to make testing easier. + phase = _sweep_poly_phase(t, poly) + # Convert to radians. + phi *= pi / 180 + return cos(phase + phi) + + +def _sweep_poly_phase(t, poly): + """ + Calculate the phase used by sweep_poly to generate its output. + + See `sweep_poly` for a description of the arguments. + + """ + # polyint handles lists, ndarrays and instances of poly1d automatically. + intpoly = polyint(poly) + phase = 2 * pi * polyval(intpoly, t) + return phase + + +def unit_impulse(shape, idx=None, dtype=float): + r""" + Unit impulse signal (discrete delta function) or unit basis vector. + + Parameters + ---------- + shape : int or tuple of int + Number of samples in the output (1-D), or a tuple that represents the + shape of the output (N-D). + idx : None or int or tuple of int or 'mid', optional + Index at which the value is 1. If None, defaults to the 0th element. + If ``idx='mid'``, the impulse will be centered at ``shape // 2`` in + all dimensions. If an int, the impulse will be at `idx` in all + dimensions. + dtype : data-type, optional + The desired data-type for the array, e.g., ``numpy.int8``. Default is + ``numpy.float64``. + + Returns + ------- + y : ndarray + Output array containing an impulse signal. + + Notes + ----- + In digital signal processing literature the unit impulse signal is often + represented by the Kronecker delta. [1]_ I.e., a signal :math:`u_k[n]`, + which is zero everywhere except being one at the :math:`k`-th sample, + can be expressed as + + .. math:: + + u_k[n] = \delta[n-k] \equiv \delta_{n,k}\ . + + Furthermore, the unit impulse is frequently interpreted as the discrete-time + version of the continuous-time Dirac distribution. [2]_ + + References + ---------- + .. [1] "Kronecker delta", *Wikipedia*, + https://en.wikipedia.org/wiki/Kronecker_delta#Digital_signal_processing + .. [2] "Dirac delta function" *Wikipedia*, + https://en.wikipedia.org/wiki/Dirac_delta_function#Relationship_to_the_Kronecker_delta + + .. versionadded:: 0.19.0 + + Examples + -------- + An impulse at the 0th element (:math:`\\delta[n]`): + + >>> from scipy import signal + >>> signal.unit_impulse(8) + array([ 1., 0., 0., 0., 0., 0., 0., 0.]) + + Impulse offset by 2 samples (:math:`\\delta[n-2]`): + + >>> signal.unit_impulse(7, 2) + array([ 0., 0., 1., 0., 0., 0., 0.]) + + 2-dimensional impulse, centered: + + >>> signal.unit_impulse((3, 3), 'mid') + array([[ 0., 0., 0.], + [ 0., 1., 0.], + [ 0., 0., 0.]]) + + Impulse at (2, 2), using broadcasting: + + >>> signal.unit_impulse((4, 4), 2) + array([[ 0., 0., 0., 0.], + [ 0., 0., 0., 0.], + [ 0., 0., 1., 0.], + [ 0., 0., 0., 0.]]) + + Plot the impulse response of a 4th-order Butterworth lowpass filter: + + >>> imp = signal.unit_impulse(100, 'mid') + >>> b, a = signal.butter(4, 0.2) + >>> response = signal.lfilter(b, a, imp) + + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> plt.plot(np.arange(-50, 50), imp) + >>> plt.plot(np.arange(-50, 50), response) + >>> plt.margins(0.1, 0.1) + >>> plt.xlabel('Time [samples]') + >>> plt.ylabel('Amplitude') + >>> plt.grid(True) + >>> plt.show() + + """ + out = zeros(shape, dtype) + + shape = np.atleast_1d(shape) + + if idx is None: + idx = (0,) * len(shape) + elif idx == 'mid': + idx = tuple(shape // 2) + elif not hasattr(idx, "__iter__"): + idx = (idx,) * len(shape) + + out[idx] = 1 + return out diff --git a/venv/lib/python3.12/site-packages/scipy/signal/_wavelets.py b/venv/lib/python3.12/site-packages/scipy/signal/_wavelets.py new file mode 100644 index 0000000000000000000000000000000000000000..9da68aed88ed380dfe04f59ce0acad2471c20cfb --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/_wavelets.py @@ -0,0 +1,29 @@ +import numpy as np +from scipy.signal._signaltools import convolve + + +def _ricker(points, a): + A = 2 / (np.sqrt(3 * a) * (np.pi**0.25)) + wsq = a**2 + vec = np.arange(0, points) - (points - 1.0) / 2 + xsq = vec**2 + mod = (1 - xsq / wsq) + gauss = np.exp(-xsq / (2 * wsq)) + total = A * mod * gauss + return total + + +def _cwt(data, wavelet, widths, dtype=None, **kwargs): + # Determine output type + if dtype is None: + if np.asarray(wavelet(1, widths[0], **kwargs)).dtype.char in 'FDG': + dtype = np.complex128 + else: + dtype = np.float64 + + output = np.empty((len(widths), len(data)), dtype=dtype) + for ind, width in enumerate(widths): + N = np.min([10 * width, len(data)]) + wavelet_data = np.conj(wavelet(N, width, **kwargs)[::-1]) + output[ind] = convolve(data, wavelet_data, mode='same') + return output diff --git a/venv/lib/python3.12/site-packages/scipy/signal/_whittaker.py b/venv/lib/python3.12/site-packages/scipy/signal/_whittaker.py new file mode 100644 index 0000000000000000000000000000000000000000..7984f8ba5edbe89b765bc683e87a2a765b40b11b --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/_whittaker.py @@ -0,0 +1,451 @@ +import warnings +import numpy as np +from scipy._lib._util import _RichResult, _validate_int +from scipy.linalg.lapack import get_lapack_funcs +from scipy.optimize import minimize_scalar +from scipy.special import binom + + +def _solveh_banded(ab, b, calc_logdet=False): + """ + Solve the equation ``a @ x = b`` for ``x``, where ``a`` is the + Hermitian positive-definite banded matrix defined by `ab`. + + Same as scipy.linalg.solveh_banded(lower=True, check_finite=False), but: + - also returns the log of the determinant and info + - no error is raised if info > 0 + - no input validation + - only real values, no complex + - only `lower = True` code path + - always overwrite_XX = False + - b only a 1-dim array + + Parameters + ---------- + ab : (``u`` + 1, M) array_like + Banded matrix + b : (M,) array_like + Right-hand side + + Returns + ------- + x : (M,) ndarray + The solution to the system ``a x = b``. Shape of return matches shape of `b`. + logdet : float + Logarithm of the determinant of `ab`. Returns 0 if ``calc_logdet=False``. + info : int + """ + a1 = ab + b1 = b + overwrite_b = False + overwrite_ab = False + logdet = 0.0 + + if a1.shape[0] == 2: + method = "ptsv" + ptsv = get_lapack_funcs(method, (a1, b1), ilp64="preferred") + # We assume lower=True and real arrays + d = a1[0, :] + e = a1[1, :-1] + # ptsv uses LDL', returnes d=diag(D), du=diag(L, -1) + d, du, x, info = ptsv(d, e, b1, overwrite_ab, overwrite_ab, overwrite_b) + if calc_logdet and info == 0: + logdet = np.log(d).sum() + else: + method = "pbsv" + pbsv = get_lapack_funcs(method, (a1, b1), ilp64="preferred") + # pbsv uses Cholesky LL', returns c=L in ab-storage format + c, x, info = pbsv(a1, b1, lower=True, overwrite_ab=overwrite_ab, + overwrite_b=overwrite_b) + if calc_logdet and info == 0: + logdet = 2 * np.log(c[0, :]).sum() + if info < 0: + raise ValueError(f"illegal value in {-info}th argument of internal {method}") + return x, logdet, info + + +def whittaker_henderson(signal, *, lamb="reml", order=2, weights=None): + r""" + Whittaker-Henderson (WH) smoothing/graduation of a discrete signal. + + This implements WH smoothing with a difference penalty of the specified ``order`` + and penalty strength ``lamb``, see [1]_, [2]_ and [3]_. WH can be seen as a + penalized B-Spline (P-Spline) of degree zero for equidistant knots at the signal + positions. + + In econometrics, the WH graduation of order 2 is referred to as the + Hodrick-Prescott filter [4]_. + + Parameters + ---------- + signal : array_like + A one-dimensional array of length ``order + 1`` representing equidistant data + points of a signal, e.g. a time series with constant time lag. + + lamb : str or float, optional + Smoothing or penalty parameter, default is ``"reml"`` which minimizes the + restricted maximum likelihood (REML) criterion to find the parameter ``lamb``. + If a number is passed, it must be non-negative and it is used directly. + + order : int, default: 2 + The order of the difference penalty, must be at least 1. + + weights : array_like or None, optional + A one-dimensional array of case weights with the same length as `signal`. + ``None`` is equivalent to an array of all ones, ``np.ones_like(signal)``. + + Returns + ------- + res : _RichResult + An object similar to an instance of `scipy.optimize.OptimizeResult` with the + following attributes: + + x : ndarray + The WH smoothed signal. + lamb : float + The penalty parameter. If the input ``lamb`` is a number, this value is + returned. If the input was ``"reml"``, the penalty parameter that minimized + the REML criterion is returned. + + Notes + ----- + For the signal :math:`y = (y_1, y_2, \ldots, y_n)` and weights + :math:`w = (w_1, w_2, \ldots, w_n)`, WH of order :math:`p` with smoothing or + penalty parameter :math:`\lambda` solves the following optimization problem for + :math:`x_i`: + + .. math:: + + \operatorname{argmin}_{x_i} \sum_i^n w_i (y_i - x_i)^2 + + \lambda \sum_i^{n-p} (\Delta^p x_i)^2 \,, + + where :math:`\Delta^p` is the forward difference operator of order :math:`p`, + :math:`\Delta x_i = x_{i+1} - x_i` and + :math:`\Delta^2 x_i = \Delta(\Delta x_i) = x_{i+2} - 2x_{i+1} + x_i`. + For every input value :math:`y_i`, it generates a smoothed value :math:`x_i`. + + One of the nice properties of WH is that it automatically performs inter- and + extrapolation of data. Interpolation means filling in values when signal data is + only available on the left and on the right. Extrapolation means filling in values + after the observed signal ends. + Set ``weights = 0`` for regions where you want to extra- or interpolate. The values + of `signal` don't matter if ``weights = 0``. + WH interpolates a polynomial of ``degree = 2 * order - 1`` and it extrapolates a + polynomial of ``degree = order - 1``. For ``order = 2``, this means cubic + interpolation and linear extrapolation. + + References + ---------- + .. [1] Whittaker-Henderson smoothing, + https://en.wikipedia.org/wiki/Whittaker%E2%80%93Henderson_smoothing + .. [2] Eilers, P.H.C. (2003). + "A perfect smoother". Analytical Chem. 75, 3631-3636. + :doi:`10.1021/AC034173T` + .. [3] Weinert, Howard L. (2007). + "Efficient computation for Whittaker-Henderson smoothing". + Computational Statistics and Data Analysis 52:959-74. + :doi:`10.1016/j.csda.2006.11.038` + .. [4] Hodrick, R. J., and Prescott, E. C. (1997). + "Postwar U.S. Business Cycles: An Empirical Investigation". + :doi:`10.2307/2953682` + + Examples + -------- + We use data from https://data.giss.nasa.gov/gistemp/ for global temperature + anomalies, i.e., deviations from the corresponding 1951-1980 means (Combined + Land-Surface Air and Sea-Surface Water Temperature Anomalies, Land-Ocean Temperature + Index, L-OTI). + We use weights to indicate where to inter- and extrapolate the missing data. + + >>> from pathlib import Path + >>> import numpy as np + >>> import scipy + >>> from scipy.signal import whittaker_henderson + >>> # For the most recent data, use + ... # fname="https://data.giss.nasa.gov/gistemp/tabledata_v4/GLB.Ts+dSST.csv" + ... # Here, we instead use a copy made in 2026. + ... fname = Path(scipy.signal.__file__).parent / "tests/data/GLB.Ts+dSST.csv" + >>> data = np.genfromtxt( + ... fname=fname, + ... delimiter=",", skip_header=2, missing_values="***" + ... ) + >>> year = data[:, 0] + >>> temperature = data[:, 1:13].ravel() # monthly temperature anomalies + >>> w = np.ones_like(temperature) + >>> # We might have some nan values. + ... np.sum(np.isnan(temperature)) + np.int64(10) + >>> w[np.isnan(temperature)] = 0 + >>> res = whittaker_henderson(temperature, weights=w) + >>> temperature[:5] + array([-0.19, -0.25, -0.09, -0.16, -0.1]) + >>> res.x[:5] + array([-0.18244619, -0.17823282, -0.17409373, -0.17080896, -0.16833158]) + + Let us plot measurements and Whittaker-Henderson smoothing. + + >>> import matplotlib.pyplot as plt + >>> x = year[0] + np.arange(len(temperature)) / 12 + >>> plt.plot(x, temperature, label="measurement") + >>> plt.plot(x, res.x, label="WH smooth") + >>> # Above, we set w = 0 for nan values of temperature. WH automatically + ... # inter- or extrapolates for all data points with w = 0. + ... plt.plot(x[w==0], res.x[w==0], color="red", label="inter-/extrapolation") + >>> plt.xlabel("year") + >>> plt.ylabel("temperature deviation [°C]") + >>> plt.title("Global Temperature Anomalies (ref. 1951-1980)") + >>> plt.legend() + >>> plt.show() + + We can see that extrapolation has occurred at the right end of the signal, meaning + that NaNs existed in the data for the most recent dates, in particular months of + 2026 that have not yet happened at the time of the data download in March 2026. + + """ + order = _validate_int(order, name="order", minimum=1) + + signal = np.asarray(signal) + if signal.ndim != 1: + msg = f"Input array signal must be of shape (n,); got {signal.shape}" + raise ValueError(msg) + + n = signal.shape[0] + if n < order + 1: + msg = f"Input array signal must be at least of shape ({order + 1},); got {n}." + raise ValueError(msg) + + if weights is not None: + weights = np.asarray(weights) + if weights.shape != signal.shape: + msg = "Input array weights must have the same shape as the signal array." + raise ValueError(msg) + + if weights is None: + if not np.isfinite(signal).all(): + raise ValueError("Input array signal must be finite.") + else: + if not np.isfinite(weights).all(): + raise ValueError("Input array weights must be finite.") + if (mask := ~np.isfinite(signal)).any(): + # Only weights * y matter in the end and weights must be 0 for all + # non-finite elements of signal. + if not (weights[mask] == 0).all(): + raise ValueError("Input array weights must be zero for all non-finite " + "elements of signal.") + signal = np.nan_to_num(signal) + + + msg = f"Parameter lamb must be string 'reml' or a non-negative float; got {lamb=}." + if isinstance(lamb, str): + if lamb != "reml": + raise ValueError(msg) + def criterion(loglamb): + return -_reml(lamb=np.exp(loglamb), y=signal, order=order, weights=weights) + opt = minimize_scalar(criterion, bracket=[-10, 10]) + lamb = np.exp(opt.x) + + if lamb < 0: + raise ValueError(msg) + elif lamb == 0.0: + x = np.asarray(signal).copy() + else: + x, _ = _solve_WH_banded(signal, lamb=lamb, order=order, weights=weights) + return _RichResult(x=x, lamb=lamb) + + +def _polynomial_fit(y, lamb, order=2, weights=None, calc_logdet=False): + """Polynomial fit equivalent to WH for lamb -> infinity.""" + n = len(y) + x_range = np.arange(n) + poly = np.polynomial.Polynomial.fit(x=x_range, y=y, deg=order - 1, w=weights) + if calc_logdet: + # For large lambda, log|W + lambda D'D| ~ log|lambda D'D| + # (with determinant understood as product of non-zero eigenvalues). + logdet_DtD = _logdet_difference_matrix(order=order, n=n) + logdet = (n - order) * np.log(lamb) + logdet_DtD + else: + logdet = 0.0 + return poly(x_range), logdet + + +def _solve_WH_banded(y, lamb, order=2, weights=None, calc_logdet=False, warn_user=True): + """ + Solve the WH optimization problem via the normal equations. + + A @ x = y + A = I + lamb * P = I + lamb * D' @ D + D = difference matrix of order=`order` + + With weights W = diag(weights): + A = W + lamb * P + A @ x = W @ y + + Returns + ------- + x : ndarray + The solution. + logdet : float + Logarithm of the determinant of matrix A. Returns 0 if ``calc_logdet=False``. + """ + n = y.shape[0] # n >= p + 1 was already checked + p = order # order of difference penalty + # Construct penalty matrix P = D'D of shape (n-p, n) as if n = 2p+1 (to save + # memory). + if n < 2*p + 1: + D = np.diff(np.eye(n), n=p, axis=0) # shape (n-p, n) + else: + D = np.diff(np.eye(2*p + 1), n=p, axis=0) # shape (p+1, 2p+1) + P_raw = D.T @ D # shape (2p+1, 2p+1) if n >= 2p+1 else (n, n) + + # Because our matrix A = np.eye(n, dtype=np.float64) + lamb * (D.T @ D) is + # symmetric and banded with u = l = p, we construct it in the lower "ab"-format + # for use in solveh_banded, i.e. each row in ab is a subdiagonal of A: + # ab[0, :] = np.diagonal(A, 0) + # ab[1, :-1] = np.diagonal(A, 1) + # ab[2, :-2] = np.diagonal(A, 2) + # .. + ab = np.zeros((p + 1, min(2*p + 1, n))) + for i in range(p + 1): + ab[i, :ab.shape[1] - i] = np.diagonal(P_raw, i) + ab *= lamb + if n > 2*p + 1: + ab = np.concat( + [ + ab[:, :p+1], + np.repeat(ab[:, p:p+1], n - (2*p+1), axis=1), + ab[:, -p:], + ], + axis=1, + ) + + if weights is None: + # Check if lambda is so large that A = I + lambda D'D = lambda D'D. We even add + # a factor of 8, i.e. A should have at least 4 bits from I (not only D'D). + # Note that the minimal diagonal element of D'D is always 1. + if lamb * np.finfo(np.float64).eps > 8: + # If lambda approaches infinity, WH approaches a polynomial fit. + x, logdet = _polynomial_fit( + y, lamb=lamb, order=order, calc_logdet=calc_logdet + ) + info = 0 + else: + ab[0, :] += 1.0 # This corresponds to np.eye(n). + x, logdet, info = _solveh_banded(ab, y, calc_logdet=calc_logdet) + else: + if (ab[0, :] == ab[0, :] + weights * 8).all(): + # If lambda approaches infinity, WH approaches a polynomial fit. + x, logdet = _polynomial_fit( + y, lamb=lamb, order=order, weights=weights, calc_logdet=calc_logdet + ) + info = 0 + else: + ab[0, :] += weights + x, logdet, info = _solveh_banded(ab, weights * y, calc_logdet=calc_logdet) + + if info > 0: + # LinAlgError(f"{info}th leading minor not positive definite") + # For very large values of lamb, we know that + # - the linear solver breaks down + # - the solution approaches a polynomial least squares fit of degree + # order - 1. + # Note that for a certain large lamb, WH already reaches the polynomial least + # squares fit almost exactly. For larger lamb, WH starts to deviate from the + # polynomial (=worse solution due to numerical instability), until the solver + # breaks down and reports info > 0. + if warn_user: + msg_weights = "" if weights is None else " or due to the weights" + msg = ( + "The linear solver in Whittaker-Henderson smoothing detected a " + "numerical instability. This is likely due to a very large value of " + f"{lamb=}" + + msg_weights + ". " + "As Whittaker-Henderson approaches a polynomial of degree 'order - 1' " + "for large lamb, this polynomial (via least squares) is returned." + ) + warnings.warn(msg, UserWarning, stacklevel=2) + x, logdet = _polynomial_fit( + y, lamb, order=order, weights=weights, calc_logdet=calc_logdet + ) + return x, logdet + + +def _logdet_difference_matrix(order, n): + """Logarithm of the determinant of the difference matrix. + + If D is the difference matrix of order=p, then this computes `log det(D @ D.T)` + which equals the sum of the log of non-zero eigenvalues of `D.T @ D`. + """ + # product of eigenvalues = + # prod(binom(n+i-1, 2i-1), i=1..p) / prod(binom(2i, i), i=1..p-1) + # How to derive this formula? Well, ... some magic. + p = order + if order == 1: + return np.log(n) + logdet = 0.0 + for i in range(1, p+1): + logdet += np.log(binom(n + i - 1, 2*i - 1) / binom(2*i, i)) + logdet += np.log(binom(2*p, p)) + return logdet + + +def _reml(lamb, y, order, weights=None): + """Calculate the restricted maximum likelihood (REML). + + Parameters + ---------- + lamb : penalty + y : signal + x : smoothed signal + order : order of the difference penalty. + weights : case weights + + Returns + ------- + reml : REML criterion + + References + ---------- + - Biessy https://arxiv.org/abs/2306.06932 (version 4) + - Wood https://doi.org/10.1111/j.1467-9868.2010.00749.x + """ + n = y.shape[0] + x, logdet = _solve_WH_banded( + y=y, lamb=lamb, order=order, weights=weights, calc_logdet=True, warn_user=False + ) + logdet_DtD = _logdet_difference_matrix(order=order, n=n) + residual = y - x + # Eq. 12 of Biessy gives the REML criterion: + # REML(lambda, sigma) = (log of restriced maximum likelihood) + # = -1/2 ((y - theta) W (y - theta) / sigma^2 + lambda theta D'D theta / sigma^2 + # - log|lambda D'D| + log|(W + lambda D'D)| + (n - p) log(sigma^2) + # + const + # ) + # where the constant term "const" does not depend on lambda or sigma and p is the + # order of the difference penalty. + # Note that Biessy then does not mention to use the profiled REML criterion, i.e., + # analytically plug in the optimal sigma^2. This gives us + # sigma^2 = r2 / (n - p) + # r^2 = (y - theta) W (y - theta) + lambda theta D'D theta + # profiled REML(lambda) = + # -1/2 ( + # (n-p) (1 + log(r^2 / (n-p))) + # -log|lambda D'D| + log|W + lambda D'D| + const + # ) + # This can be compared to Eq. 41 of Bates et al + # https://doi.org/10.18637/jss.v067.i01. + # An alternative derivation stems from a mixed model formulation of P-splines, see + # Currie and Durban https://doi.org/10.1191/1471082x02st039ob or Boer + # https://doi.org/10.1177/1471082X231178591. One then has 2 variance parameters + # sigma^2 (from y) and tau^2 (from the random effect) leading to + # -1/2 ((y - theta) W (y - theta) / sigma^2 + theta D'D theta / tau^2 + ... + # One then sets tau^2 = sigma^2 / lambda. + if weights is None: + r2 = residual @ residual + else: + r2 = residual @ (weights * residual) + r2 += lamb * np.sum(np.diff(x, n=order)**2) # + lambda theta D'D theta + reml = (n - order) * (1 + np.log(r2 / (n - order))) + reml -= (n - order) * np.log(lamb) + logdet_DtD # -log|lambda D'D| + reml += logdet # +log|W + lambda D'D| + reml *= -0.5 + return reml diff --git a/venv/lib/python3.12/site-packages/scipy/signal/bsplines.py b/venv/lib/python3.12/site-packages/scipy/signal/bsplines.py new file mode 100644 index 0000000000000000000000000000000000000000..0328d45c107bda78cbdbd374148237ca09ac411d --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/bsplines.py @@ -0,0 +1,21 @@ +# This file is not meant for public use and will be removed in SciPy v2.0.0. +# Use the `scipy.signal` namespace for importing the functions +# included below. + +from scipy._lib.deprecation import _sub_module_deprecation + +__all__ = [ # noqa: F822 + 'spline_filter', 'gauss_spline', + 'cspline1d', 'qspline1d', 'cspline1d_eval', 'qspline1d_eval', + 'cspline2d', 'sepfir2d' +] + + +def __dir__(): + return __all__ + + +def __getattr__(name): + return _sub_module_deprecation(sub_package="signal", module="bsplines", + private_modules=["_spline_filters"], all=__all__, + attribute=name) diff --git a/venv/lib/python3.12/site-packages/scipy/signal/filter_design.py b/venv/lib/python3.12/site-packages/scipy/signal/filter_design.py new file mode 100644 index 0000000000000000000000000000000000000000..41dc230a7f24a7ac3209f821d8d0f9417130afbd --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/filter_design.py @@ -0,0 +1,28 @@ +# This file is not meant for public use and will be removed in SciPy v2.0.0. +# Use the `scipy.signal` namespace for importing the functions +# included below. + +from scipy._lib.deprecation import _sub_module_deprecation + +__all__ = [ # noqa: F822 + 'findfreqs', 'freqs', 'freqz', 'tf2zpk', 'zpk2tf', 'normalize', + 'lp2lp', 'lp2hp', 'lp2bp', 'lp2bs', 'bilinear', 'iirdesign', + 'iirfilter', 'butter', 'cheby1', 'cheby2', 'ellip', 'bessel', + 'band_stop_obj', 'buttord', 'cheb1ord', 'cheb2ord', 'ellipord', + 'buttap', 'cheb1ap', 'cheb2ap', 'ellipap', 'besselap', + 'BadCoefficients', 'freqs_zpk', 'freqz_zpk', + 'tf2sos', 'sos2tf', 'zpk2sos', 'sos2zpk', 'group_delay', + 'sosfreqz', 'freqz_sos', 'iirnotch', 'iirpeak', 'bilinear_zpk', + 'lp2lp_zpk', 'lp2hp_zpk', 'lp2bp_zpk', 'lp2bs_zpk', + 'gammatone', 'iircomb', +] + + +def __dir__(): + return __all__ + + +def __getattr__(name): + return _sub_module_deprecation(sub_package="signal", module="filter_design", + private_modules=["_filter_design"], all=__all__, + attribute=name) diff --git a/venv/lib/python3.12/site-packages/scipy/signal/fir_filter_design.py b/venv/lib/python3.12/site-packages/scipy/signal/fir_filter_design.py new file mode 100644 index 0000000000000000000000000000000000000000..4c7be58b151835a55eb494e31047e6b846e2dae8 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/fir_filter_design.py @@ -0,0 +1,21 @@ +# This file is not meant for public use and will be removed in SciPy v2.0.0. +# Use the `scipy.signal` namespace for importing the functions +# included below. + +from scipy._lib.deprecation import _sub_module_deprecation + +__all__ = [ # noqa: F822 + 'kaiser_beta', 'kaiser_atten', 'kaiserord', + 'firwin', 'firwin2', 'remez', 'firls', 'minimum_phase', + 'firwin_2d', +] + + +def __dir__(): + return __all__ + + +def __getattr__(name): + return _sub_module_deprecation(sub_package="signal", module="fir_filter_design", + private_modules=["_fir_filter_design"], all=__all__, + attribute=name) diff --git a/venv/lib/python3.12/site-packages/scipy/signal/lti_conversion.py b/venv/lib/python3.12/site-packages/scipy/signal/lti_conversion.py new file mode 100644 index 0000000000000000000000000000000000000000..7080990afc9e23e51e8a45aaa146b64c58dda3cf --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/lti_conversion.py @@ -0,0 +1,20 @@ +# This file is not meant for public use and will be removed in SciPy v2.0.0. +# Use the `scipy.signal` namespace for importing the functions +# included below. + +from scipy._lib.deprecation import _sub_module_deprecation + +__all__ = [ # noqa: F822 + 'tf2ss', 'abcd_normalize', 'ss2tf', 'zpk2ss', 'ss2zpk', + 'cont2discrete', 'tf2zpk', 'zpk2tf', 'normalize' +] + + +def __dir__(): + return __all__ + + +def __getattr__(name): + return _sub_module_deprecation(sub_package="signal", module="lti_conversion", + private_modules=["_lti_conversion"], all=__all__, + attribute=name) diff --git a/venv/lib/python3.12/site-packages/scipy/signal/ltisys.py b/venv/lib/python3.12/site-packages/scipy/signal/ltisys.py new file mode 100644 index 0000000000000000000000000000000000000000..5123068de559f124bf444c12ef9824c3d14de64f --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/ltisys.py @@ -0,0 +1,25 @@ +# This file is not meant for public use and will be removed in SciPy v2.0.0. +# Use the `scipy.signal` namespace for importing the functions +# included below. + +from scipy._lib.deprecation import _sub_module_deprecation + +__all__ = [ # noqa: F822 + 'lti', 'dlti', 'TransferFunction', 'ZerosPolesGain', 'StateSpace', + 'lsim', 'impulse', 'step', 'bode', + 'freqresp', 'place_poles', 'dlsim', 'dstep', 'dimpulse', + 'dfreqresp', 'dbode', + 'tf2zpk', 'zpk2tf', 'normalize', 'freqs', + 'freqz', 'freqs_zpk', 'freqz_zpk', 'tf2ss', 'abcd_normalize', + 'ss2tf', 'zpk2ss', 'ss2zpk', 'cont2discrete', +] + + +def __dir__(): + return __all__ + + +def __getattr__(name): + return _sub_module_deprecation(sub_package="signal", module="ltisys", + private_modules=["_ltisys"], all=__all__, + attribute=name) diff --git a/venv/lib/python3.12/site-packages/scipy/signal/signaltools.py b/venv/lib/python3.12/site-packages/scipy/signal/signaltools.py new file mode 100644 index 0000000000000000000000000000000000000000..85d426f5fb2605c639fc6dbd1b4d0284a3f11e1b --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/signaltools.py @@ -0,0 +1,27 @@ +# This file is not meant for public use and will be removed in SciPy v2.0.0. +# Use the `scipy.signal` namespace for importing the functions +# included below. + +from scipy._lib.deprecation import _sub_module_deprecation + +__all__ = [ # noqa: F822 + 'correlate', 'correlation_lags', 'correlate2d', + 'convolve', 'convolve2d', 'fftconvolve', 'oaconvolve', + 'order_filter', 'medfilt', 'medfilt2d', 'wiener', 'lfilter', + 'lfiltic', 'sosfilt', 'deconvolve', 'hilbert', 'hilbert2', + 'unique_roots', 'invres', 'invresz', 'residue', + 'residuez', 'resample', 'resample_poly', 'detrend', + 'lfilter_zi', 'sosfilt_zi', 'sosfiltfilt', 'choose_conv_method', + 'filtfilt', 'decimate', 'vectorstrength', + 'dlti', 'upfirdn', 'get_window', 'cheby1', 'firwin' +] + + +def __dir__(): + return __all__ + + +def __getattr__(name): + return _sub_module_deprecation(sub_package="signal", module="signaltools", + private_modules=["_signaltools"], all=__all__, + attribute=name) diff --git a/venv/lib/python3.12/site-packages/scipy/signal/spectral.py b/venv/lib/python3.12/site-packages/scipy/signal/spectral.py new file mode 100644 index 0000000000000000000000000000000000000000..299ebed781b00a1f1f35e96c54f4c20d9bd9d0fc --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/spectral.py @@ -0,0 +1,21 @@ +# This file is not meant for public use and will be removed in SciPy v2.0.0. +# Use the `scipy.signal` namespace for importing the functions +# included below. + +from scipy._lib.deprecation import _sub_module_deprecation + +__all__ = [ # noqa: F822 + 'periodogram', 'welch', 'lombscargle', 'csd', 'coherence', + 'spectrogram', 'stft', 'istft', 'check_COLA', 'check_NOLA', + 'get_window', +] + + +def __dir__(): + return __all__ + + +def __getattr__(name): + return _sub_module_deprecation(sub_package="signal", module="spectral", + private_modules=["_spectral_py"], all=__all__, + attribute=name) diff --git a/venv/lib/python3.12/site-packages/scipy/signal/spline.py b/venv/lib/python3.12/site-packages/scipy/signal/spline.py new file mode 100644 index 0000000000000000000000000000000000000000..30fb91ef8bbc17242959947b917dc41d7245cdc2 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/spline.py @@ -0,0 +1,18 @@ +# This file is not meant for public use and will be removed in the future +# versions of SciPy. Use the `scipy.signal` namespace for importing the +# functions included below. + +from scipy._lib.deprecation import _sub_module_deprecation + + +__all__ = ['sepfir2d'] # noqa: F822 + + +def __dir__(): + return __all__ + + +def __getattr__(name): + return _sub_module_deprecation(sub_package="signal", module="spline", + private_modules=["_spline"], all=__all__, + attribute=name) diff --git a/venv/lib/python3.12/site-packages/scipy/signal/tests/__init__.py b/venv/lib/python3.12/site-packages/scipy/signal/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..67f7753a082ea85c881232ef98797a30f393937c Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/__init__.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/_scipy_spectral_test_shim.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/_scipy_spectral_test_shim.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ac3cb35b38d25b171de4beb9f69ddcf677b4a7c2 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/_scipy_spectral_test_shim.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/mpsig.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/mpsig.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fc934e0bc7134f932000e07272ae8dfafae80d11 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/mpsig.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_array_tools.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_array_tools.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..61a82c0c69203ca674f1460c076a53a8c05b6992 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_array_tools.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_bsplines.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_bsplines.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f97068b3548704e8cae548d8e585355187ab377a Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_bsplines.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_cont2discrete.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_cont2discrete.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..831e5a84e6241f8cde87c9d643f4018b7bf4cc81 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_cont2discrete.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_czt.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_czt.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7c768032a63b379104c389fc3f751f41b952b638 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_czt.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_dltisys.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_dltisys.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b0da7a8d2b8e7bfec61c28a97e25f93921f61cef Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_dltisys.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_filter_design.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_filter_design.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c66ef1d92b3cc06203e5419cdc76c0ab99e7c72b --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_filter_design.cpython-312.pyc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3b05783e3779ad8aef88ced1d04b93127e48b0859cfe400c144ac05c5c954814 +size 261584 diff --git a/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_fir_filter_design.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_fir_filter_design.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b090710909e3561847840030348e49733eed7778 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_fir_filter_design.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_ltisys.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_ltisys.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5eea6ea31080fe3633628b3e2d6d95952a3c321d Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_ltisys.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_max_len_seq.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_max_len_seq.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e8589199fe8cd89d261361a2857dc8b26177df90 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_max_len_seq.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_peak_finding.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_peak_finding.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..74084ed3a453963dbb5bc065feedd974e1e3671c Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_peak_finding.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_result_type.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_result_type.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..00463d4e6070886afae22e7cf29807488e61b039 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_result_type.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_savitzky_golay.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_savitzky_golay.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bfe249c2eb3e16c43d2d911da33be9bd1bf32a2f Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_savitzky_golay.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_short_time_fft.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_short_time_fft.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9a76b8bb87c5b274e5adb10067f5fa9514cbbf1c Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_short_time_fft.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_signaltools.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_signaltools.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2ad704182d4d88c8f35ebffcf99205a986cb1d08 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_signaltools.cpython-312.pyc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:086cd3b3e9e2dd21b594c533d6cd34a4f6b889b0da201b98c7020d80e4cd0157 +size 292538 diff --git a/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_spectral.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_spectral.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..03b11df5a90954a6b8ac9cce02dec11f9d0dd231 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_spectral.cpython-312.pyc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c89f4d7f7ffbc3c959ab9228b9a7e7b17f703f391b3b2c7710176f9d6689e01a +size 114781 diff --git a/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_splines.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_splines.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f146d3e9de72a4bb3104815b6485ceee462ab8de Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_splines.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_upfirdn.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_upfirdn.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..90ddd64c6bcad508b11f9183e5831eddd5fa9294 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_upfirdn.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_waveforms.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_waveforms.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7b5a6844fe4d4c7072f2854c0b3423a1343cad9b Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_waveforms.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_wavelets.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_wavelets.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3e1f6a69b08f73a234cd8adfbd57dbce629ac1f0 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_wavelets.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_whittaker.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_whittaker.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2aa807f0e05fbf7a78407accc5f8400244de686c Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_whittaker.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_windows.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_windows.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b47f0d4ebb91783d6a75cf8d741d6b83f5f196ac Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/signal/tests/__pycache__/test_windows.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/signal/tests/_scipy_spectral_test_shim.py b/venv/lib/python3.12/site-packages/scipy/signal/tests/_scipy_spectral_test_shim.py new file mode 100644 index 0000000000000000000000000000000000000000..5cfddd4a21ab11ff31d9d58982d0d30b4b09cf6b --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/tests/_scipy_spectral_test_shim.py @@ -0,0 +1,311 @@ +"""Helpers to utilize existing stft / istft tests for testing `ShortTimeFFT`. + +This module provides the functions stft_compare() and istft_compare(), which, +compares the output between the existing (i)stft() and the shortTimeFFT based +_(i)stft_wrapper() implementations in this module. + +For testing add the following imports to the file ``tests/test_spectral.py``:: + + from ._scipy_spectral_test_shim import stft_compare as stft + from ._scipy_spectral_test_shim import istft_compare as istft + +and remove the existing imports of stft and istft. + +The idea of these wrappers is not to provide a backward-compatible interface +but to demonstrate that the ShortTimeFFT implementation is at least as capable +as the existing one and delivers comparable results. Furthermore, the +wrappers highlight the different philosophies of the implementations, +especially in the border handling. +""" +import platform +from typing import cast, Literal + +import numpy as np +from numpy.testing import assert_allclose + +from scipy.signal import ShortTimeFFT +from scipy.signal import get_window, stft, istft +from scipy.signal._arraytools import const_ext, even_ext, odd_ext, zero_ext +from scipy.signal._short_time_fft import FFT_MODE_TYPE +from scipy.signal._spectral_py import _triage_segments + + +def _stft_wrapper(x, fs=1.0, window='hann', nperseg=256, noverlap=None, + nfft=None, detrend=False, return_onesided=True, + boundary='zeros', padded=True, axis=-1, scaling='spectrum'): + """Wrapper for the SciPy `stft()` function based on `ShortTimeFFT` for + unit testing. + + Handling the boundary and padding is where `ShortTimeFFT` and `stft()` + differ in behavior. Parts of `_spectral_helper()` were copied to mimic + the` stft()` behavior. + + This function is meant to be solely used by `stft_compare()`. + """ + if scaling not in ('psd', 'spectrum'): # same errors as in original stft: + raise ValueError(f"Parameter {scaling=} not in ['spectrum', 'psd']!") + + # The following lines are taken from the original _spectral_helper(): + boundary_funcs = {'even': even_ext, + 'odd': odd_ext, + 'constant': const_ext, + 'zeros': zero_ext, + None: None} + + if boundary not in boundary_funcs: + raise ValueError(f"Unknown boundary option '{boundary}', must be one" + + f" of: {list(boundary_funcs.keys())}") + if x.size == 0: + return np.empty(x.shape), np.empty(x.shape), np.empty(x.shape) + + if nperseg is not None: # if specified by user + nperseg = int(nperseg) + if nperseg < 1: + raise ValueError('nperseg must be a positive integer') + + # parse window; if array like, then set nperseg = win.shape + win, nperseg = _triage_segments(window, nperseg, + input_length=x.shape[axis]) + + if nfft is None: + nfft = nperseg + elif nfft < nperseg: + raise ValueError('nfft must be greater than or equal to nperseg.') + else: + nfft = int(nfft) + + if noverlap is None: + noverlap = nperseg//2 + else: + noverlap = int(noverlap) + if noverlap >= nperseg: + raise ValueError('noverlap must be less than nperseg.') + nstep = nperseg - noverlap + n = x.shape[axis] + + # Padding occurs after boundary extension, so that the extended signal ends + # in zeros, instead of introducing an impulse at the end. + # I.e. if x = [..., 3, 2] + # extend then pad -> [..., 3, 2, 2, 3, 0, 0, 0] + # pad then extend -> [..., 3, 2, 0, 0, 0, 2, 3] + + if boundary is not None: + ext_func = boundary_funcs[boundary] + # Extend by nperseg//2 in front and back: + x = ext_func(x, nperseg//2, axis=axis) + + if padded: + # Pad to integer number of windowed segments + # I.e make x.shape[-1] = nperseg + (nseg-1)*nstep, with integer nseg + x = np.moveaxis(x, axis, -1) + + # This is an edge case where shortTimeFFT returns one more time slice + # than the Scipy stft() shorten to remove last time slice: + if n % 2 == 1 and nperseg % 2 == 1 and noverlap % 2 == 1: + x = x[..., : -1] + + nadd = (-(x.shape[-1]-nperseg) % nstep) % nperseg + zeros_shape = list(x.shape[:-1]) + [nadd] + x = np.concatenate((x, np.zeros(zeros_shape)), axis=-1) + x = np.moveaxis(x, -1, axis) + + # ... end original _spectral_helper() code. + scale_to = {'spectrum': 'magnitude', 'psd': 'psd'}[scaling] + + if np.iscomplexobj(x) and return_onesided: + return_onesided = False + # using cast() to make mypy happy: + fft_mode = cast(FFT_MODE_TYPE, 'onesided' if return_onesided else 'twosided') + + ST = ShortTimeFFT(win, nstep, fs, fft_mode=fft_mode, mfft=nfft, + scale_to=scale_to, phase_shift=None) + + k_off = nperseg // 2 + p0 = 0 # ST.lower_border_end[1] + 1 + nn = x.shape[axis] if padded else n+k_off+1 + # number of frames akin to legacy stft computation + p1 = (x.shape[axis] - nperseg) // nstep + 1 + + detr = None if detrend is False else detrend + Sxx = ST.stft_detrend(x, detr, p0, p1, k_offset=k_off, axis=axis) + t = ST.t(nn, 0, p1 - p0, k_offset=0 if boundary is not None else k_off) + if x.dtype in (np.float32, np.complex64): + Sxx = Sxx.astype(np.complex64) + + return ST.f, t, Sxx + + +def _istft_wrapper(Zxx, fs=1.0, window='hann', nperseg=None, noverlap=None, + nfft=None, input_onesided=True, boundary=True, time_axis=-1, + freq_axis=-2, scaling='spectrum') -> \ + tuple[np.ndarray, np.ndarray, tuple[int, int]]: + """Wrapper for the SciPy `istft()` function based on `ShortTimeFFT` for + unit testing. + + Note that only option handling is implemented as far as to handle the unit + tests. E.g., the case ``nperseg=None`` is not handled. + + This function is meant to be solely used by `istft_compare()`. + """ + # *** Lines are taken from _spectral_py.istft() ***: + if Zxx.ndim < 2: + raise ValueError('Input stft must be at least 2d!') + + if freq_axis == time_axis: + raise ValueError('Must specify differing time and frequency axes!') + + nseg = Zxx.shape[time_axis] + + if input_onesided: + # Assume even segment length + n_default = 2*(Zxx.shape[freq_axis] - 1) + else: + n_default = Zxx.shape[freq_axis] + + # Check windowing parameters + if nperseg is None: + nperseg = n_default + else: + nperseg = int(nperseg) + if nperseg < 1: + raise ValueError('nperseg must be a positive integer') + + if nfft is None: + if input_onesided and (nperseg == n_default + 1): + # Odd nperseg, no FFT padding + nfft = nperseg + else: + nfft = n_default + elif nfft < nperseg: + raise ValueError('nfft must be greater than or equal to nperseg.') + else: + nfft = int(nfft) + + if noverlap is None: + noverlap = nperseg//2 + else: + noverlap = int(noverlap) + if noverlap >= nperseg: + raise ValueError('noverlap must be less than nperseg.') + nstep = nperseg - noverlap + + # Get window as array + if isinstance(window, str) or type(window) is tuple: + win = get_window(window, nperseg) + else: + win = np.asarray(window) + if len(win.shape) != 1: + raise ValueError('window must be 1-D') + if win.shape[0] != nperseg: + raise ValueError(f'window must have length of {nperseg}') + + outputlength = nperseg + (nseg-1)*nstep + # *** End block of: Taken from _spectral_py.istft() *** + + # Using cast() to make mypy happy: + fft_mode = cast(FFT_MODE_TYPE, 'onesided' if input_onesided else 'twosided') + scale_to = cast(Literal['magnitude', 'psd'], + {'spectrum': 'magnitude', 'psd': 'psd'}[scaling]) + + ST = ShortTimeFFT(win, nstep, fs, fft_mode=fft_mode, mfft=nfft, + scale_to=scale_to, phase_shift=None) + + if boundary: + j = nperseg if nperseg % 2 == 0 else nperseg - 1 + k0 = ST.k_min + nperseg // 2 + k1 = outputlength - j + k0 + else: + raise NotImplementedError("boundary=False does not make sense with" + + "ShortTimeFFT.istft()!") + + x = ST.istft(Zxx, k0=k0, k1=k1, f_axis=freq_axis, t_axis=time_axis) + t = np.arange(k1 - k0) * ST.T + k_hi = ST.upper_border_begin(k1 - k0)[0] + # using cast() to make mypy happy: + return t, x, (ST.lower_border_end[0], k_hi) + + +def stft_compare(x, fs=1.0, window='hann', nperseg=256, noverlap=None, + nfft=None, detrend=False, return_onesided=True, + boundary='zeros', padded=True, axis=-1, scaling='spectrum'): + """Assert that the results from the existing `stft()` and `_stft_wrapper()` + are close to each other. + + For comparing the STFT values an absolute tolerance of the floating point + resolution was added to circumvent problems with the following tests: + * For float32 the tolerances are much higher in + TestSTFT.test_roundtrip_float32()). + * The TestSTFT.test_roundtrip_scaling() has a high relative deviation. + Interestingly this did not appear in Scipy 1.9.1 but only in the current + development version. + """ + kw = dict(x=x, fs=fs, window=window, nperseg=nperseg, noverlap=noverlap, + nfft=nfft, detrend=detrend, return_onesided=return_onesided, + boundary=boundary, padded=padded, axis=axis, scaling=scaling) + f, t, Zxx = stft(**kw) + f_wrapper, t_wrapper, Zxx_wrapper = _stft_wrapper(**kw) + + e_msg_part = " of `stft_wrapper()` differ from `stft()`." + assert_allclose(f_wrapper, f, err_msg=f"Frequencies {e_msg_part}") + assert_allclose(t_wrapper, t, err_msg=f"Time slices {e_msg_part}") + + # Adapted tolerances to account for: + atol = np.finfo(Zxx.dtype).resolution * 2 + assert_allclose(Zxx_wrapper, Zxx, atol=atol, + err_msg=f"STFT values {e_msg_part}") + return f, t, Zxx + + +def istft_compare(Zxx, fs=1.0, window='hann', nperseg=None, noverlap=None, + nfft=None, input_onesided=True, boundary=True, time_axis=-1, + freq_axis=-2, scaling='spectrum'): + """Assert that the results from the existing `istft()` and + `_istft_wrapper()` are close to each other. + + Quirks: + * If ``boundary=False`` the comparison is skipped, since it does not + make sense with ShortTimeFFT.istft(). Only used in test + TestSTFT.test_roundtrip_boundary_extension(). + * If ShortTimeFFT.istft() decides the STFT is not invertible, the + comparison is skipped, since istft() only emits a warning and does not + return a correct result. Only used in + ShortTimeFFT.test_roundtrip_not_nola(). + * For comparing the signals an absolute tolerance of the floating point + resolution was added to account for the low accuracy of float32 (Occurs + only in TestSTFT.test_roundtrip_float32()). + """ + kw = dict(Zxx=Zxx, fs=fs, window=window, nperseg=nperseg, + noverlap=noverlap, nfft=nfft, input_onesided=input_onesided, + boundary=boundary, time_axis=time_axis, freq_axis=freq_axis, + scaling=scaling) + + t, x = istft(**kw) + if not boundary: # skip test_roundtrip_boundary_extension(): + return t, x # _istft_wrapper does() not implement this case + try: # if inversion fails, istft() only emits a warning: + t_wrapper, x_wrapper, (k_lo, k_hi) = _istft_wrapper(**kw) + except ValueError as v: # Do nothing if inversion fails: + if v.args[0] == "Short-time Fourier Transform not invertible!": + return t, x + raise v + + e_msg_part = " of `istft_wrapper()` differ from `istft()`" + assert_allclose(t, t_wrapper, err_msg=f"Sample times {e_msg_part}") + + # Adapted tolerances to account for resolution loss: + atol = np.finfo(x.dtype).resolution*2 # instead of default atol = 0 + rtol = 1e-7 # default for np.allclose() + + # Relax atol on 32-Bit platforms a bit to pass CI tests. + # - Not clear why there are discrepancies (in the FFT maybe?) + # - Not sure what changed on 'i686' since earlier on those test passed + if x.dtype == np.float32 and platform.machine() == 'i686': + # float32 gets only used by TestSTFT.test_roundtrip_float32() so + # we are using the tolerances from there to circumvent CI problems + atol, rtol = 1e-4, 1e-5 + else: + atol = max(atol, 1e-12) # 2e-15 seems too tight + + assert_allclose(x_wrapper[k_lo:k_hi], x[k_lo:k_hi], atol=atol, rtol=rtol, + err_msg=f"Signal values {e_msg_part}") + return t, x diff --git a/venv/lib/python3.12/site-packages/scipy/signal/tests/data/GLB.Ts+dSST.csv b/venv/lib/python3.12/site-packages/scipy/signal/tests/data/GLB.Ts+dSST.csv new file mode 100644 index 0000000000000000000000000000000000000000..7d12a2c67d593bf84b1504d12141af16104d5786 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/tests/data/GLB.Ts+dSST.csv @@ -0,0 +1,149 @@ +Land-Ocean: Global Means +Year,Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec,J-D,D-N,DJF,MAM,JJA,SON +1880,-.19,-.25,-.09,-.16,-.10,-.21,-.18,-.10,-.14,-.23,-.22,-.19,-.17,***,***,-.12,-.17,-.20 +1881,-.20,-.15,.02,.04,.07,-.19,.01,-.04,-.15,-.21,-.18,-.07,-.09,-.10,-.18,.04,-.07,-.18 +1882,.16,.14,.04,-.16,-.14,-.23,-.16,-.08,-.15,-.23,-.17,-.36,-.11,-.09,.08,-.09,-.16,-.18 +1883,-.29,-.36,-.12,-.18,-.18,-.07,-.08,-.14,-.22,-.11,-.24,-.12,-.18,-.20,-.34,-.16,-.10,-.19 +1884,-.13,-.08,-.37,-.40,-.34,-.35,-.31,-.28,-.27,-.25,-.33,-.31,-.28,-.27,-.11,-.37,-.31,-.28 +1885,-.59,-.34,-.27,-.42,-.46,-.44,-.34,-.31,-.28,-.23,-.24,-.10,-.34,-.35,-.41,-.38,-.36,-.25 +1886,-.44,-.51,-.43,-.28,-.25,-.35,-.19,-.31,-.24,-.28,-.28,-.25,-.32,-.30,-.35,-.32,-.28,-.27 +1887,-.72,-.57,-.36,-.35,-.31,-.25,-.26,-.36,-.26,-.35,-.26,-.33,-.36,-.36,-.52,-.34,-.29,-.29 +1888,-.34,-.37,-.41,-.21,-.22,-.17,-.11,-.16,-.12,.01,.02,-.05,-.18,-.20,-.35,-.28,-.15,-.03 +1889,-.10,.16,.06,.09,-.02,-.11,-.08,-.21,-.24,-.26,-.34,-.30,-.11,-.09,.01,.04,-.13,-.28 +1890,-.43,-.45,-.41,-.30,-.40,-.25,-.28,-.39,-.37,-.25,-.44,-.32,-.36,-.36,-.39,-.37,-.31,-.35 +1891,-.34,-.46,-.18,-.27,-.16,-.21,-.18,-.17,-.16,-.22,-.32,-.04,-.23,-.25,-.38,-.21,-.19,-.23 +1892,-.30,-.12,-.40,-.34,-.23,-.22,-.32,-.27,-.17,-.14,-.43,-.40,-.28,-.25,-.15,-.32,-.27,-.25 +1893,-.81,-.57,-.23,-.28,-.34,-.26,-.14,-.26,-.22,-.19,-.19,-.32,-.32,-.33,-.59,-.29,-.22,-.20 +1894,-.53,-.30,-.24,-.46,-.32,-.41,-.25,-.24,-.28,-.23,-.27,-.22,-.31,-.32,-.39,-.34,-.30,-.26 +1895,-.42,-.43,-.33,-.23,-.28,-.22,-.17,-.17,-.12,-.11,-.18,-.15,-.23,-.24,-.36,-.28,-.19,-.14 +1896,-.23,-.13,-.27,-.31,-.17,-.12,-.03,-.06,-.08,.05,-.05,-.06,-.12,-.13,-.17,-.25,-.07,-.03 +1897,-.15,-.18,-.14,-.03,-.02,-.13,-.03,-.10,-.09,-.14,-.19,-.22,-.12,-.11,-.13,-.07,-.09,-.14 +1898,-.04,-.31,-.51,-.31,-.31,-.20,-.22,-.27,-.22,-.35,-.39,-.25,-.28,-.28,-.19,-.38,-.23,-.32 +1899,-.18,-.38,-.37,-.21,-.24,-.32,-.16,-.08,-.05,-.04,.11,-.27,-.18,-.18,-.27,-.27,-.19,.01 +1900,-.37,-.07,-.01,-.10,-.10,-.11,-.14,-.10,-.06,.09,-.08,-.08,-.09,-.11,-.24,-.07,-.12,-.02 +1901,-.24,-.11,.05,-.04,-.16,-.13,-.15,-.21,-.23,-.30,-.18,-.29,-.17,-.15,-.14,-.05,-.16,-.23 +1902,-.18,-.09,-.28,-.28,-.33,-.33,-.29,-.30,-.29,-.31,-.37,-.43,-.29,-.28,-.19,-.29,-.31,-.33 +1903,-.25,-.07,-.24,-.42,-.41,-.43,-.36,-.46,-.50,-.47,-.43,-.52,-.38,-.37,-.25,-.36,-.42,-.47 +1904,-.63,-.59,-.52,-.51,-.53,-.49,-.51,-.49,-.56,-.39,-.19,-.34,-.48,-.49,-.58,-.52,-.50,-.38 +1905,-.35,-.60,-.22,-.34,-.30,-.29,-.27,-.21,-.19,-.25,-.07,-.14,-.27,-.29,-.43,-.29,-.26,-.17 +1906,-.28,-.30,-.19,-.05,-.26,-.20,-.24,-.20,-.28,-.20,-.38,-.15,-.23,-.23,-.24,-.17,-.21,-.29 +1907,-.43,-.52,-.28,-.37,-.47,-.42,-.35,-.35,-.34,-.23,-.48,-.48,-.39,-.37,-.37,-.37,-.38,-.35 +1908,-.45,-.33,-.56,-.45,-.38,-.39,-.36,-.46,-.36,-.44,-.52,-.50,-.43,-.43,-.42,-.46,-.40,-.44 +1909,-.74,-.46,-.55,-.60,-.55,-.53,-.46,-.34,-.39,-.40,-.33,-.57,-.49,-.49,-.57,-.57,-.44,-.37 +1910,-.44,-.42,-.51,-.44,-.35,-.39,-.35,-.37,-.39,-.41,-.58,-.68,-.45,-.44,-.48,-.43,-.37,-.46 +1911,-.63,-.59,-.62,-.55,-.53,-.52,-.43,-.44,-.41,-.27,-.22,-.23,-.45,-.49,-.63,-.56,-.46,-.30 +1912,-.28,-.16,-.39,-.19,-.22,-.25,-.43,-.55,-.58,-.58,-.41,-.44,-.37,-.36,-.22,-.27,-.41,-.52 +1913,-.41,-.46,-.44,-.41,-.45,-.46,-.37,-.34,-.35,-.32,-.21,-.04,-.36,-.39,-.44,-.43,-.39,-.30 +1914,.03,-.11,-.25,-.31,-.22,-.26,-.25,-.17,-.18,-.04,-.17,-.05,-.16,-.16,-.04,-.26,-.23,-.13 +1915,-.22,-.04,-.10,.05,-.06,-.23,-.13,-.22,-.20,-.25,-.13,-.21,-.15,-.13,-.10,-.04,-.19,-.19 +1916,-.14,-.16,-.29,-.32,-.36,-.50,-.37,-.28,-.37,-.34,-.47,-.82,-.37,-.32,-.17,-.32,-.39,-.39 +1917,-.57,-.63,-.64,-.56,-.56,-.44,-.26,-.23,-.23,-.45,-.35,-.68,-.47,-.48,-.68,-.59,-.31,-.34 +1918,-.48,-.36,-.26,-.45,-.44,-.37,-.32,-.32,-.18,-.06,-.12,-.30,-.31,-.34,-.51,-.38,-.34,-.12 +1919,-.21,-.24,-.22,-.13,-.29,-.37,-.30,-.34,-.26,-.21,-.42,-.43,-.28,-.27,-.25,-.21,-.34,-.30 +1920,-.25,-.27,-.13,-.25,-.27,-.36,-.31,-.27,-.22,-.27,-.27,-.47,-.28,-.28,-.32,-.22,-.31,-.25 +1921,-.05,-.18,-.24,-.30,-.31,-.28,-.15,-.27,-.19,-.04,-.15,-.18,-.19,-.22,-.24,-.28,-.23,-.13 +1922,-.35,-.45,-.16,-.24,-.34,-.31,-.28,-.34,-.36,-.32,-.15,-.20,-.29,-.29,-.33,-.24,-.31,-.28 +1923,-.28,-.40,-.34,-.42,-.35,-.30,-.32,-.34,-.32,-.14,-.03,-.04,-.27,-.29,-.30,-.37,-.32,-.16 +1924,-.23,-.24,-.09,-.31,-.19,-.27,-.30,-.36,-.32,-.35,-.21,-.44,-.28,-.24,-.17,-.20,-.31,-.30 +1925,-.38,-.40,-.28,-.26,-.30,-.33,-.27,-.21,-.20,-.18,.05,.06,-.22,-.27,-.41,-.28,-.27,-.11 +1926,.20,.02,.11,-.14,-.24,-.27,-.28,-.13,-.15,-.11,-.07,-.30,-.11,-.08,.10,-.09,-.23,-.11 +1927,-.27,-.18,-.39,-.31,-.26,-.28,-.19,-.24,-.13,-.01,-.06,-.34,-.22,-.22,-.25,-.32,-.24,-.07 +1928,-.03,-.09,-.25,-.29,-.30,-.39,-.20,-.22,-.21,-.19,-.10,-.17,-.20,-.22,-.15,-.28,-.27,-.17 +1929,-.45,-.59,-.33,-.42,-.38,-.44,-.37,-.33,-.25,-.15,-.12,-.55,-.36,-.33,-.40,-.38,-.38,-.17 +1930,-.30,-.27,-.11,-.25,-.24,-.22,-.21,-.15,-.16,-.12,.17,-.06,-.16,-.20,-.37,-.20,-.20,-.03 +1931,-.10,-.21,-.11,-.23,-.20,-.08,-.03,-.03,-.06,.05,-.06,-.06,-.09,-.09,-.12,-.18,-.05,-.02 +1932,.15,-.17,-.18,-.06,-.18,-.29,-.25,-.22,-.10,-.09,-.28,-.27,-.16,-.14,-.03,-.14,-.25,-.16 +1933,-.23,-.30,-.30,-.25,-.29,-.34,-.22,-.24,-.29,-.25,-.31,-.45,-.29,-.27,-.27,-.28,-.27,-.28 +1934,-.21,-.03,-.29,-.31,-.10,-.15,-.11,-.12,-.16,-.07,.03,-.03,-.13,-.16,-.23,-.23,-.13,-.06 +1935,-.34,.14,-.14,-.37,-.29,-.27,-.22,-.22,-.21,-.06,-.26,-.17,-.20,-.19,-.08,-.27,-.23,-.18 +1936,-.28,-.38,-.21,-.20,-.16,-.22,-.10,-.13,-.09,-.03,.01,-.01,-.15,-.16,-.28,-.19,-.15,-.04 +1937,-.07,.03,-.21,-.16,-.06,-.05,-.03,.01,.09,.08,.08,-.08,-.03,-.02,-.02,-.14,-.02,.08 +1938,.08,.03,.10,.06,-.10,-.17,-.09,-.06,.01,.15,.07,-.13,.00,.00,.01,.02,-.11,.07 +1939,-.05,-.07,-.18,-.10,-.05,-.07,-.06,-.06,-.07,-.04,.07,.42,-.02,-.07,-.08,-.11,-.06,-.01 +1940,-.01,.08,.09,.17,.10,.11,.12,.06,.15,.11,.16,.31,.12,.13,.16,.12,.10,.14 +1941,.17,.30,.10,.16,.16,.12,.22,.14,.02,.35,.22,.21,.18,.19,.26,.14,.16,.20 +1942,.29,.02,.05,.09,.11,.04,.00,-.04,-.03,.01,.09,.12,.06,.07,.17,.08,.00,.02 +1943,-.01,.17,-.04,.11,.06,-.05,.08,.00,.05,.23,.19,.23,.09,.08,.09,.04,.01,.16 +1944,.35,.24,.26,.19,.18,.15,.17,.18,.28,.26,.11,.03,.20,.22,.27,.21,.17,.22 +1945,.10,.01,.06,.19,.06,.01,.03,.26,.20,.18,.07,-.07,.09,.10,.05,.10,.10,.15 +1946,.15,.02,.01,.05,-.07,-.21,-.12,-.20,-.08,-.08,-.06,-.31,-.07,-.05,.03,.00,-.18,-.07 +1947,-.07,-.08,.06,.06,-.02,-.02,-.04,-.07,-.12,.07,.02,-.13,-.03,-.04,-.15,.03,-.04,-.01 +1948,.06,-.15,-.24,-.12,-.01,-.05,-.11,-.12,-.14,-.05,-.12,-.24,-.11,-.10,-.07,-.12,-.09,-.11 +1949,.06,-.14,-.02,-.11,-.10,-.27,-.13,-.13,-.14,-.07,-.11,-.18,-.11,-.12,-.11,-.08,-.18,-.11 +1950,-.26,-.27,-.08,-.21,-.11,-.05,-.09,-.16,-.11,-.20,-.34,-.22,-.17,-.17,-.24,-.13,-.10,-.22 +1951,-.34,-.41,-.20,-.14,-.01,-.07,-.01,.06,.05,.08,-.01,.15,-.07,-.10,-.33,-.12,-.01,.04 +1952,.11,.11,-.08,.03,-.03,-.03,.04,.05,.07,.00,-.13,-.02,.01,.03,.13,-.03,.02,-.02 +1953,.07,.15,.11,.19,.11,.12,.01,.05,.04,.08,-.03,.05,.08,.07,.07,.14,.06,.03 +1954,-.24,-.10,-.15,-.14,-.20,-.19,-.19,-.17,-.10,-.02,.08,-.18,-.13,-.11,-.10,-.16,-.18,-.01 +1955,.13,-.16,-.32,-.22,-.20,-.13,-.11,.02,-.11,-.05,-.25,-.28,-.14,-.13,-.07,-.25,-.07,-.14 +1956,-.13,-.24,-.21,-.28,-.29,-.15,-.09,-.25,-.19,-.23,-.15,-.06,-.19,-.21,-.22,-.26,-.17,-.19 +1957,-.09,-.03,-.05,.00,.09,.15,.02,.15,.09,.01,.08,.15,.05,.03,-.06,.01,.11,.06 +1958,.39,.22,.09,.02,.06,-.09,.05,-.05,-.03,.04,.02,.01,.06,.07,.25,.05,-.03,.01 +1959,.08,.07,.18,.16,.04,.03,.03,-.01,-.06,-.07,-.08,.00,.03,.03,.05,.13,.02,-.07 +1960,.00,.13,-.35,-.15,-.08,-.04,-.04,.02,.07,.06,-.11,.19,-.02,-.04,.04,-.19,-.02,.01 +1961,.07,.19,.09,.13,.12,.12,.01,.01,.08,.00,.03,-.16,.06,.09,.15,.12,.04,.04 +1962,.05,.15,.10,.05,-.06,.03,.02,-.01,.00,.01,.06,-.03,.03,.02,.01,.03,.01,.02 +1963,-.03,.18,-.14,-.07,-.06,.05,.06,.22,.17,.15,.15,-.03,.05,.05,.04,-.09,.11,.16 +1964,-.09,-.10,-.21,-.32,-.25,-.04,-.04,-.22,-.29,-.31,-.21,-.29,-.20,-.18,-.07,-.26,-.10,-.27 +1965,-.08,-.17,-.13,-.19,-.12,-.08,-.13,-.04,-.15,-.05,-.06,-.08,-.11,-.13,-.18,-.14,-.09,-.09 +1966,-.19,-.04,.04,-.13,-.12,.01,.08,-.09,-.03,-.17,.00,-.02,-.06,-.06,-.10,-.07,.00,-.07 +1967,-.07,-.20,.05,-.05,.12,-.08,.02,.01,-.06,.09,-.05,-.05,-.02,-.02,-.10,.04,-.01,-.01 +1968,-.26,-.14,.20,-.05,-.14,-.09,-.13,-.09,-.19,.09,-.05,-.14,-.08,-.07,-.15,.00,-.10,-.05 +1969,-.11,-.18,.01,.17,.18,.03,-.05,.04,.08,.09,.12,.24,.05,.02,-.14,.12,.01,.10 +1970,.08,.22,.06,.05,-.03,-.02,.02,-.10,.12,.03,.02,-.12,.03,.06,.18,.03,-.04,.06 +1971,-.02,-.15,-.18,-.07,-.05,-.16,-.08,-.01,-.05,-.04,-.07,-.08,-.08,-.08,-.10,-.10,-.08,-.06 +1972,-.23,-.18,.02,.00,-.03,.04,.01,.16,.02,.08,.02,.18,.01,-.01,-.16,.00,.07,.04 +1973,.29,.32,.29,.27,.23,.19,.13,.05,.09,.10,.04,-.07,.16,.18,.26,.26,.12,.08 +1974,-.10,-.27,-.05,-.11,-.04,-.05,-.03,.10,-.08,-.06,-.08,-.08,-.07,-.07,-.14,-.07,.01,-.07 +1975,.10,.08,.12,.04,.16,-.01,-.01,-.17,-.02,-.11,-.17,-.17,-.01,-.01,.03,.11,-.06,-.10 +1976,-.03,-.06,-.21,-.07,-.20,-.12,-.10,-.12,-.07,-.24,-.06,.11,-.10,-.12,-.09,-.16,-.11,-.12 +1977,.18,.22,.24,.26,.33,.27,.20,.18,.02,.03,.16,.03,.18,.18,.17,.28,.21,.07 +1978,.06,.10,.19,.17,.09,-.01,.04,-.14,.06,.03,.13,.08,.07,.06,.06,.15,-.04,.07 +1979,.08,-.10,.19,.15,.03,.14,.04,.16,.25,.26,.28,.47,.16,.13,.02,.12,.11,.26 +1980,.29,.39,.29,.30,.35,.20,.22,.18,.20,.13,.29,.21,.25,.28,.39,.31,.20,.21 +1981,.53,.42,.48,.32,.24,.29,.32,.35,.14,.12,.23,.41,.32,.30,.39,.35,.32,.16 +1982,.04,.15,.03,.15,.18,.05,.14,.03,.13,.13,.17,.42,.14,.13,.20,.12,.07,.15 +1983,.53,.43,.41,.27,.34,.22,.18,.35,.37,.17,.30,.16,.31,.33,.46,.34,.25,.28 +1984,.31,.14,.26,.05,.33,.02,.19,.19,.21,.13,.06,-.05,.15,.17,.20,.21,.13,.13 +1985,.22,-.04,.17,.12,.14,.15,.04,.16,.13,.11,.05,.13,.12,.10,.04,.14,.12,.10 +1986,.26,.37,.30,.22,.21,.12,.11,.15,.03,.15,.10,.13,.18,.18,.25,.24,.13,.09 +1987,.31,.43,.18,.24,.25,.35,.40,.24,.35,.32,.29,.46,.32,.29,.29,.23,.33,.32 +1988,.56,.44,.51,.42,.44,.40,.33,.39,.37,.37,.12,.29,.39,.40,.49,.46,.37,.29 +1989,.12,.29,.36,.29,.17,.15,.34,.33,.34,.29,.19,.37,.27,.26,.24,.27,.27,.27 +1990,.41,.43,.80,.56,.45,.37,.46,.34,.23,.44,.46,.40,.45,.44,.41,.60,.39,.38 +1991,.42,.50,.35,.51,.34,.53,.47,.39,.44,.29,.29,.32,.40,.41,.44,.40,.46,.34 +1992,.47,.40,.48,.27,.31,.25,.09,.08,-.01,.06,.02,.21,.22,.23,.40,.35,.14,.03 +1993,.34,.37,.36,.27,.28,.22,.25,.11,.11,.23,.03,.18,.23,.23,.31,.30,.19,.13 +1994,.26,.02,.29,.41,.28,.43,.30,.21,.31,.42,.44,.38,.31,.30,.15,.33,.31,.39 +1995,.52,.79,.46,.46,.27,.43,.45,.45,.33,.46,.44,.26,.44,.45,.56,.40,.44,.41 +1996,.24,.46,.32,.33,.27,.25,.36,.48,.25,.22,.41,.37,.33,.32,.32,.31,.37,.29 +1997,.32,.40,.52,.33,.34,.54,.33,.41,.52,.61,.64,.59,.46,.44,.36,.40,.43,.59 +1998,.58,.88,.63,.63,.68,.77,.66,.65,.42,.42,.43,.54,.61,.61,.68,.65,.69,.42 +1999,.48,.64,.32,.31,.26,.36,.38,.31,.38,.34,.37,.40,.38,.39,.55,.30,.35,.36 +2000,.25,.55,.55,.56,.35,.40,.39,.42,.38,.26,.30,.27,.39,.40,.40,.49,.40,.32 +2001,.45,.43,.55,.50,.57,.52,.59,.48,.51,.49,.72,.55,.53,.51,.38,.54,.53,.57 +2002,.76,.78,.88,.57,.64,.53,.61,.53,.63,.54,.59,.44,.62,.63,.70,.70,.56,.59 +2003,.74,.58,.60,.55,.60,.47,.58,.64,.62,.72,.52,.74,.61,.59,.59,.58,.56,.62 +2004,.57,.72,.63,.60,.36,.43,.25,.44,.50,.60,.72,.50,.53,.55,.68,.53,.38,.61 +2005,.75,.59,.74,.66,.62,.64,.61,.60,.71,.76,.74,.67,.68,.66,.61,.68,.62,.74 +2006,.56,.73,.63,.46,.48,.66,.54,.71,.66,.70,.73,.79,.64,.63,.65,.52,.63,.70 +2007,1.01,.70,.71,.77,.69,.61,.59,.60,.60,.59,.59,.49,.66,.69,.83,.72,.60,.59 +2008,.30,.38,.74,.53,.49,.49,.60,.45,.60,.66,.69,.53,.54,.54,.39,.59,.51,.65 +2009,.64,.53,.53,.61,.65,.63,.73,.68,.71,.65,.79,.67,.65,.64,.57,.60,.68,.72 +2010,.74,.83,.92,.85,.75,.67,.63,.66,.62,.71,.81,.45,.72,.74,.75,.84,.65,.71 +2011,.52,.47,.65,.64,.53,.62,.70,.74,.55,.65,.59,.60,.61,.59,.48,.61,.69,.60 +2012,.47,.48,.56,.72,.78,.64,.58,.65,.71,.79,.78,.52,.64,.65,.52,.69,.63,.76 +2013,.71,.61,.67,.53,.60,.69,.60,.70,.76,.69,.83,.70,.67,.66,.61,.60,.66,.76 +2014,.76,.55,.78,.80,.86,.67,.58,.83,.87,.80,.67,.78,.75,.74,.67,.81,.69,.78 +2015,.86,.91,.96,.75,.79,.81,.73,.79,.83,1.07,1.05,1.17,.89,.86,.85,.83,.78,.99 +2016,1.17,1.36,1.35,1.08,.95,.80,.84,1.01,.90,.86,.91,.86,1.01,1.03,1.23,1.13,.89,.89 +2017,1.02,1.13,1.16,.93,.91,.71,.82,.87,.74,.88,.87,.93,.91,.91,1.00,1.00,.80,.83 +2018,.82,.84,.88,.88,.81,.76,.83,.78,.80,1.01,.83,.92,.85,.85,.86,.86,.79,.88 +2019,.93,.95,1.17,1.01,.86,.90,.94,.95,.93,1.00,.99,1.11,.98,.96,.94,1.01,.93,.97 +2020,1.18,1.24,1.18,1.12,.99,.91,.89,.86,.96,.87,1.08,.79,1.01,1.03,1.17,1.10,.89,.97 +2021,.81,.64,.89,.76,.79,.85,.92,.81,.92,.98,.92,.87,.85,.84,.75,.81,.86,.94 +2022,.91,.89,1.04,.83,.84,.92,.94,.94,.88,.97,.73,.79,.89,.90,.89,.90,.93,.86 +2023,.87,.96,1.23,.99,.94,1.08,1.19,1.19,1.48,1.34,1.40,1.35,1.17,1.12,.88,1.05,1.15,1.41 +2024,1.25,1.43,1.39,1.31,1.16,1.24,1.20,1.30,1.23,1.34,1.29,1.26,1.28,1.29,1.34,1.29,1.25,1.29 +2025,1.37,1.26,1.37,1.24,1.08,1.06,1.02,1.17,1.25,1.19,1.21,1.07,1.19,1.21,1.30,1.23,1.08,1.21 +2026,1.08,1.24,***,***,***,***,***,***,***,***,***,***,***,***,1.13,***,***,*** diff --git a/venv/lib/python3.12/site-packages/scipy/signal/tests/mpsig.py b/venv/lib/python3.12/site-packages/scipy/signal/tests/mpsig.py new file mode 100644 index 0000000000000000000000000000000000000000..d129de74e5df00c22bc0b82c7d3f7b52483941f9 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/tests/mpsig.py @@ -0,0 +1,122 @@ +""" +Some signal functions implemented using mpmath. +""" + +try: + import mpmath +except ImportError: + mpmath = None + + +def _prod(seq): + """Returns the product of the elements in the sequence `seq`.""" + p = 1 + for elem in seq: + p *= elem + return p + + +def _relative_degree(z, p): + """ + Return relative degree of transfer function from zeros and poles. + + This is simply len(p) - len(z), which must be nonnegative. + A ValueError is raised if len(p) < len(z). + """ + degree = len(p) - len(z) + if degree < 0: + raise ValueError("Improper transfer function. " + "Must have at least as many poles as zeros.") + return degree + + +def _zpkbilinear(z, p, k, fs): + """Bilinear transformation to convert a filter from analog to digital.""" + + degree = _relative_degree(z, p) + + fs2 = 2*fs + + # Bilinear transform the poles and zeros + z_z = [(fs2 + z1) / (fs2 - z1) for z1 in z] + p_z = [(fs2 + p1) / (fs2 - p1) for p1 in p] + + # Any zeros that were at infinity get moved to the Nyquist frequency + z_z.extend([-1] * degree) + + # Compensate for gain change + numer = _prod(fs2 - z1 for z1 in z) + denom = _prod(fs2 - p1 for p1 in p) + k_z = k * numer / denom + + return z_z, p_z, k_z.real + + +def _zpklp2lp(z, p, k, wo=1): + """Transform a lowpass filter to a different cutoff frequency.""" + + degree = _relative_degree(z, p) + + # Scale all points radially from origin to shift cutoff frequency + z_lp = [wo * z1 for z1 in z] + p_lp = [wo * p1 for p1 in p] + + # Each shifted pole decreases gain by wo, each shifted zero increases it. + # Cancel out the net change to keep overall gain the same + k_lp = k * wo**degree + + return z_lp, p_lp, k_lp + + +def _butter_analog_poles(n): + """ + Poles of an analog Butterworth lowpass filter. + + This is the same calculation as scipy.signal.buttap(n) or + scipy.signal.butter(n, 1, analog=True, output='zpk'), but mpmath is used, + and only the poles are returned. + """ + poles = [-mpmath.exp(1j*mpmath.pi*k/(2*n)) for k in range(-n+1, n, 2)] + return poles + + +def butter_lp(n, Wn): + """ + Lowpass Butterworth digital filter design. + + This computes the same result as scipy.signal.butter(n, Wn, output='zpk'), + but it uses mpmath, and the results are returned in lists instead of NumPy + arrays. + """ + zeros = [] + poles = _butter_analog_poles(n) + k = 1 + fs = 2 + warped = 2 * fs * mpmath.tan(mpmath.pi * Wn / fs) + z, p, k = _zpklp2lp(zeros, poles, k, wo=warped) + z, p, k = _zpkbilinear(z, p, k, fs=fs) + return z, p, k + + +def zpkfreqz(z, p, k, worN=None): + """ + Frequency response of a filter in zpk format, using mpmath. + + This is the same calculation as scipy.signal.freqz, but the input is in + zpk format, the calculation is performed using mpath, and the results are + returned in lists instead of NumPy arrays. + """ + if worN is None or isinstance(worN, int): + N = worN or 512 + ws = [mpmath.pi * mpmath.mpf(j) / N for j in range(N)] + else: + ws = worN + + h = [] + for wk in ws: + zm1 = mpmath.exp(1j * wk) + numer = _prod([zm1 - t for t in z]) + denom = _prod([zm1 - t for t in p]) + hk = k * numer / denom + h.append(hk) + return ws, h diff --git a/venv/lib/python3.12/site-packages/scipy/signal/tests/test_array_tools.py b/venv/lib/python3.12/site-packages/scipy/signal/tests/test_array_tools.py new file mode 100644 index 0000000000000000000000000000000000000000..4bda9716e0bc4b6ad3ed0c3954147043b74c421a --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/tests/test_array_tools.py @@ -0,0 +1,111 @@ +import numpy as np + +from scipy._lib._array_api import xp_assert_equal +from pytest import raises as assert_raises + +from scipy.signal._arraytools import (axis_slice, axis_reverse, + odd_ext, even_ext, const_ext, zero_ext) + + +class TestArrayTools: + + def test_axis_slice(self): + a = np.arange(12).reshape(3, 4) + + s = axis_slice(a, start=0, stop=1, axis=0) + xp_assert_equal(s, a[0:1, :]) + + s = axis_slice(a, start=-1, axis=0) + xp_assert_equal(s, a[-1:, :]) + + s = axis_slice(a, start=0, stop=1, axis=1) + xp_assert_equal(s, a[:, 0:1]) + + s = axis_slice(a, start=-1, axis=1) + xp_assert_equal(s, a[:, -1:]) + + s = axis_slice(a, start=0, step=2, axis=0) + xp_assert_equal(s, a[::2, :]) + + s = axis_slice(a, start=0, step=2, axis=1) + xp_assert_equal(s, a[:, ::2]) + + def test_axis_reverse(self): + a = np.arange(12).reshape(3, 4) + + r = axis_reverse(a, axis=0) + xp_assert_equal(r, a[::-1, :]) + + r = axis_reverse(a, axis=1) + xp_assert_equal(r, a[:, ::-1]) + + def test_odd_ext(self): + a = np.array([[1, 2, 3, 4, 5], + [9, 8, 7, 6, 5]]) + + odd = odd_ext(a, 2, axis=1) + expected = np.array([[-1, 0, 1, 2, 3, 4, 5, 6, 7], + [11, 10, 9, 8, 7, 6, 5, 4, 3]]) + xp_assert_equal(odd, expected) + + odd = odd_ext(a, 1, axis=0) + expected = np.array([[-7, -4, -1, 2, 5], + [1, 2, 3, 4, 5], + [9, 8, 7, 6, 5], + [17, 14, 11, 8, 5]]) + xp_assert_equal(odd, expected) + + assert_raises(ValueError, odd_ext, a, 2, axis=0) + assert_raises(ValueError, odd_ext, a, 5, axis=1) + + def test_even_ext(self): + a = np.array([[1, 2, 3, 4, 5], + [9, 8, 7, 6, 5]]) + + even = even_ext(a, 2, axis=1) + expected = np.array([[3, 2, 1, 2, 3, 4, 5, 4, 3], + [7, 8, 9, 8, 7, 6, 5, 6, 7]]) + xp_assert_equal(even, expected) + + even = even_ext(a, 1, axis=0) + expected = np.array([[9, 8, 7, 6, 5], + [1, 2, 3, 4, 5], + [9, 8, 7, 6, 5], + [1, 2, 3, 4, 5]]) + xp_assert_equal(even, expected) + + assert_raises(ValueError, even_ext, a, 2, axis=0) + assert_raises(ValueError, even_ext, a, 5, axis=1) + + def test_const_ext(self): + a = np.array([[1, 2, 3, 4, 5], + [9, 8, 7, 6, 5]]) + + const = const_ext(a, 2, axis=1) + expected = np.array([[1, 1, 1, 2, 3, 4, 5, 5, 5], + [9, 9, 9, 8, 7, 6, 5, 5, 5]]) + xp_assert_equal(const, expected) + + const = const_ext(a, 1, axis=0) + expected = np.array([[1, 2, 3, 4, 5], + [1, 2, 3, 4, 5], + [9, 8, 7, 6, 5], + [9, 8, 7, 6, 5]]) + xp_assert_equal(const, expected) + + def test_zero_ext(self): + a = np.array([[1, 2, 3, 4, 5], + [9, 8, 7, 6, 5]]) + + zero = zero_ext(a, 2, axis=1) + expected = np.array([[0, 0, 1, 2, 3, 4, 5, 0, 0], + [0, 0, 9, 8, 7, 6, 5, 0, 0]]) + xp_assert_equal(zero, expected) + + zero = zero_ext(a, 1, axis=0) + expected = np.array([[0, 0, 0, 0, 0], + [1, 2, 3, 4, 5], + [9, 8, 7, 6, 5], + [0, 0, 0, 0, 0]]) + xp_assert_equal(zero, expected) + diff --git a/venv/lib/python3.12/site-packages/scipy/signal/tests/test_bsplines.py b/venv/lib/python3.12/site-packages/scipy/signal/tests/test_bsplines.py new file mode 100644 index 0000000000000000000000000000000000000000..2338132a7f5cf2de4757dcd7656a739b39a32522 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/tests/test_bsplines.py @@ -0,0 +1,392 @@ +# pylint: disable=missing-docstring +import math +import numpy as np + +from scipy._lib._array_api import ( + assert_almost_equal, xp_assert_close, xp_assert_equal, make_xp_test_case, + xp_default_dtype, array_namespace, _xp_copy_to_numpy +) +import pytest +from pytest import raises + +from scipy import signal + +skip_xp_backends = pytest.mark.skip_xp_backends +xfail_xp_backends = pytest.mark.xfail_xp_backends + +lazy_xp_modules = [signal] + + +class TestBSplines: + """Test behaviors of B-splines. Some of the values tested against were + returned as of SciPy 1.1.0 and are included for regression testing + purposes. Others (at integer points) are compared to theoretical + expressions (cf. Unser, Aldroubi, Eden, IEEE TSP 1993, Table 1).""" + + @make_xp_test_case(signal.spline_filter) # type:ignore[attr-defined] + def test_spline_filter(self, xp): + rng = np.random.RandomState(12457) + # Test the type-error branch + raises(TypeError, signal.spline_filter, xp.asarray([0]), 0) + # Test the real branch + data_array_real = rng.rand(12, 12) + # make the magnitude exceed 1, and make some negative + data_array_real = 10*(1-2*data_array_real) + data_array_real = xp.asarray(data_array_real) + result_array_real = xp.asarray( + [[-.463312621, 8.33391222, .697290949, 5.28390836, + 5.92066474, 6.59452137, 9.84406950, -8.78324188, + 7.20675750, -8.17222994, -4.38633345, 9.89917069], + [2.67755154, 6.24192170, -3.15730578, 9.87658581, + -9.96930425, 3.17194115, -4.50919947, 5.75423446, + 9.65979824, -8.29066885, .971416087, -2.38331897], + [-7.08868346, 4.89887705, -1.37062289, 7.70705838, + 2.51526461, 3.65885497, 5.16786604, -8.77715342e-03, + 4.10533325, 9.04761993, -.577960351, 9.86382519], + [-4.71444301, -1.68038985, 2.84695116, 1.14315938, + -3.17127091, 1.91830461, 7.13779687, -5.35737482, + -9.66586425, -9.87717456, 9.93160672, 4.71948144], + [9.49551194, -1.92958436, 6.25427993, -9.05582911, + 3.97562282, 7.68232426, -1.04514824, -5.86021443, + -8.43007451, 5.47528997, 2.06330736, -8.65968112], + [-8.91720100, 8.87065356, 3.76879937, 2.56222894, + -.828387146, 8.72288903, 6.42474741, -6.84576083, + 9.94724115, 6.90665380, -6.61084494, -9.44907391], + [9.25196790, -.774032030, 7.05371046, -2.73505725, + 2.53953305, -1.82889155, 2.95454824, -1.66362046, + 5.72478916, -3.10287679, 1.54017123, -7.87759020], + [-3.98464539, -2.44316992, -1.12708657, 1.01725672, + -8.89294671, -5.42145629, -6.16370321, 2.91775492, + 9.64132208, .702499998, -2.02622392, 1.56308431], + [-2.22050773, 7.89951554, 5.98970713, -7.35861835, + 5.45459283, -7.76427957, 3.67280490, -4.05521315, + 4.51967507, -3.22738749, -3.65080177, 3.05630155], + [-6.21240584, -.296796126, -8.34800163, 9.21564563, + -3.61958784, -4.77120006, -3.99454057, 1.05021988e-03, + -6.95982829, 6.04380797, 8.43181250, -2.71653339], + [1.19638037, 6.99718842e-02, 6.72020394, -2.13963198, + 3.75309875, -5.70076744, 5.92143551, -7.22150575, + -3.77114594, -1.11903194, -5.39151466, 3.06620093], + [9.86326886, 1.05134482, -7.75950607, -3.64429655, + 7.81848957, -9.02270373, 3.73399754, -4.71962549, + -7.71144306, 3.78263161, 6.46034818, -4.43444731]], dtype=xp.float64) + xp_assert_close(signal.spline_filter(data_array_real, 0), + result_array_real) + + @xfail_xp_backends("cupy", reason="https://github.com/cupy/cupy/issues/9758") + @make_xp_test_case(signal.spline_filter) # type:ignore[attr-defined] + def test_spline_filter_complex(self, xp): + rng = np.random.RandomState(12457) + data_array_complex = rng.rand(7, 7) + rng.rand(7, 7)*1j + # make the magnitude exceed 1, and make some negative + data_array_complex = 10*(1+1j-2*data_array_complex) + data_array_complex = xp.asarray(data_array_complex) + + result_array_complex = xp.asarray( + [[-4.61489230e-01-1.92994022j, 8.33332443+6.25519943j, + 6.96300745e-01-9.05576038j, 5.28294849+3.97541356j, + 5.92165565+7.68240595j, 6.59493160-1.04542804j, + 9.84503460-5.85946894j], + [-8.78262329-8.4295969j, 7.20675516+5.47528982j, + -8.17223072+2.06330729j, -4.38633347-8.65968037j, + 9.89916801-8.91720295j, 2.67755103+8.8706522j, + 6.24192142+3.76879835j], + [-3.15627527+2.56303072j, 9.87658501-0.82838702j, + -9.96930313+8.72288895j, 3.17193985+6.42474651j, + -4.50919819-6.84576082j, 5.75423431+9.94723988j, + 9.65979767+6.90665293j], + [-8.28993416-6.61064005j, 9.71416473e-01-9.44907284j, + -2.38331890+9.25196648j, -7.08868170-0.77403212j, + 4.89887714+7.05371094j, -1.37062311-2.73505688j, + 7.70705748+2.5395329j], + [2.51528406-1.82964492j, 3.65885472+2.95454836j, + 5.16786575-1.66362023j, -8.77737999e-03+5.72478867j, + 4.10533333-3.10287571j, 9.04761887+1.54017115j, + -5.77960968e-01-7.87758923j], + [9.86398506-3.98528528j, -4.71444130-2.44316983j, + -1.68038976-1.12708664j, 2.84695053+1.01725709j, + 1.14315915-8.89294529j, -3.17127085-5.42145538j, + 1.91830420-6.16370344j], + [7.13875294+2.91851187j, -5.35737514+9.64132309j, + -9.66586399+0.70250005j, -9.87717438-2.0262239j, + 9.93160629+1.5630846j, 4.71948051-2.22050714j, + 9.49550819+7.8995142j]], dtype=xp.complex128) + # FIXME: for complex types, the computations are done in + # single precision (reason unclear). When this is changed, + # this test needs updating. + xp_assert_close(signal.spline_filter(data_array_complex, 0), + result_array_complex, rtol=1e-6) + + @make_xp_test_case(signal.gauss_spline) # type:ignore[attr-defined] + def test_gauss_spline(self, xp): + assert math.isclose(signal.gauss_spline(0, 0), 1.381976597885342) + + xp_assert_close(signal.gauss_spline(xp.asarray([1.]), 1), + xp.asarray([0.04865217]), atol=1e-9 + ) + + @skip_xp_backends(np_only=True, reason="deliberate: array-likes are accepted") + @make_xp_test_case(signal.gauss_spline) # type:ignore[attr-defined] + def test_gauss_spline_list(self, xp): + # regression test for gh-12152 (accept array_like) + knots = [-1.0, 0.0, -1.0] + assert_almost_equal(signal.gauss_spline(knots, 3), + np.asarray([0.15418033, 0.6909883, 0.15418033]) + ) + + @make_xp_test_case(signal.cspline1d) # type:ignore[attr-defined] + def test_cspline1d(self, xp): + xp_assert_equal(signal.cspline1d(xp.asarray([0])), + xp.asarray([0.], dtype=xp.float64)) + c1d = xp.asarray([1.21037185, 1.86293902, 2.98834059, 4.11660378, + 4.78893826], dtype=xp.float64) + # test lamda != 0 + xp_assert_close(signal.cspline1d(xp.asarray([1., 2, 3, 4, 5]), 1), c1d) + c1d0 = xp.asarray([0.78683946, 2.05333735, 2.99981113, 3.94741812, + 5.21051638], dtype=xp.float64) + xp_assert_close(signal.cspline1d(xp.asarray([1., 2, 3, 4, 5])), c1d0) + + @make_xp_test_case(signal.qspline1d) # type:ignore[attr-defined] + def test_qspline1d(self, xp): + xp_assert_equal(signal.qspline1d(xp.asarray([0])), + xp.asarray([0.], dtype=xp.float64)) + # test lamda != 0 + raises(ValueError, signal.qspline1d, xp.asarray([1., 2, 3, 4, 5]), 1.) + raises(ValueError, signal.qspline1d, xp.asarray([1., 2, 3, 4, 5]), -1.) + q1d0 = xp.asarray([0.85350007, 2.02441743, 2.99999534, 3.97561055, + 5.14634135], dtype=xp.float64) + xp_assert_close( + signal.qspline1d(xp.asarray([1., 2, 3, 4, 5], dtype=xp.float64)), q1d0 + ) + + @xfail_xp_backends("cupy", reason="https://github.com/cupy/cupy/pull/9484") + @make_xp_test_case(signal.cspline1d_eval) # type:ignore[attr-defined] + def test_cspline1d_eval(self, xp): + r = signal.cspline1d_eval(xp.asarray([0., 0], dtype=xp.float64), + xp.asarray([0.], dtype=xp.float64)) + xp_assert_close(r, xp.asarray([0.], dtype=xp.float64)) + + r = signal.cspline1d_eval(xp.asarray([1., 0, 1], dtype=xp.float64), + xp.asarray([], dtype=xp.float64)) + xp_assert_equal(r, xp.asarray([], dtype=xp.float64)) + + # Test case for newx that gets filtered down to empty + r = signal.cspline1d_eval(xp.asarray([1.0, 0, 1], dtype=xp.float64), + xp.asarray([-1.0], dtype=xp.float64)) + xp_assert_close(r, xp.asarray([0.33333333], dtype=xp.float64)) + + x = [-3, -2, -1, 0, 1, 2, 3, 4, 5, 6] + dx = x[1] - x[0] + newx = [-6., -5.5, -5., -4.5, -4., -3.5, -3., -2.5, -2., -1.5, -1., + -0.5, 0., 0.5, 1., 1.5, 2., 2.5, 3., 3.5, 4., 4.5, 5., 5.5, 6., + 6.5, 7., 7.5, 8., 8.5, 9., 9.5, 10., 10.5, 11., 11.5, 12., + 12.5] + y = xp.asarray([4.216, 6.864, 3.514, 6.203, 6.759, 7.433, 7.874, 5.879, + 1.396, 4.094]) + cj = xp.asarray(signal.cspline1d(_xp_copy_to_numpy(y))) + newy = xp.asarray([6.203, 4.41570658, 3.514, 5.16924703, 6.864, 6.04643068, + 4.21600281, 6.04643068, 6.864, 5.16924703, 3.514, + 4.41570658, 6.203, 6.80717667, 6.759, 6.98971173, 7.433, + 7.79560142, 7.874, 7.41525761, 5.879, 3.18686814, 1.396, + 2.24889482, 4.094, 2.24889482, 1.396, 3.18686814, 5.879, + 7.41525761, 7.874, 7.79560142, 7.433, 6.98971173, 6.759, + 6.80717667, 6.203, 4.41570658], dtype=xp.float64) + xp_assert_close( + signal.cspline1d_eval(cj, xp.asarray(newx), dx=dx, x0=x[0]), newy + ) + + with pytest.raises(ValueError, + match="Spline coefficients 'cj' must not be empty."): + signal.cspline1d_eval(xp.asarray([], dtype=xp.float64), + xp.asarray([0.0], dtype=xp.float64)) + + @xfail_xp_backends("cupy", reason="https://github.com/cupy/cupy/pull/9484") + @make_xp_test_case(signal.qspline1d_eval) # type:ignore[attr-defined] + def test_qspline1d_eval(self, xp): + xp_assert_close(signal.qspline1d_eval(xp.asarray([0., 0]), xp.asarray([0.])), + xp.asarray([0.]) + ) + xp_assert_equal(signal.qspline1d_eval(xp.asarray([1., 0, 1]), xp.asarray([])), + xp.asarray([]) + ) + + # Test case for newx that gets filtered down to empty + r = signal.qspline1d_eval(xp.asarray([1.0, 0, 1], dtype=xp.float64), + xp.asarray([-1.0], dtype=xp.float64)) + xp_assert_equal(r, xp.asarray([0.25], dtype=xp.float64)) + + x = [-3, -2, -1, 0, 1, 2, 3, 4, 5, 6] + dx = x[1] - x[0] + newx = [-6., -5.5, -5., -4.5, -4., -3.5, -3., -2.5, -2., -1.5, -1., + -0.5, 0., 0.5, 1., 1.5, 2., 2.5, 3., 3.5, 4., 4.5, 5., 5.5, 6., + 6.5, 7., 7.5, 8., 8.5, 9., 9.5, 10., 10.5, 11., 11.5, 12., + 12.5] + y = xp.asarray([4.216, 6.864, 3.514, 6.203, 6.759, 7.433, 7.874, 5.879, + 1.396, 4.094]) + cj = signal.qspline1d(y) + newy = xp.asarray([6.203, 4.49418159, 3.514, 5.18390821, 6.864, 5.91436915, + 4.21600002, 5.91436915, 6.864, 5.18390821, 3.514, + 4.49418159, 6.203, 6.71900226, 6.759, 7.03980488, 7.433, + 7.81016848, 7.874, 7.32718426, 5.879, 3.23872593, 1.396, + 2.34046013, 4.094, 2.34046013, 1.396, 3.23872593, 5.879, + 7.32718426, 7.874, 7.81016848, 7.433, 7.03980488, 6.759, + 6.71900226, 6.203, 4.49418159], dtype=xp.float64) + r = signal.qspline1d_eval( + cj, xp.asarray(newx, dtype=xp.float64), dx=dx, x0=x[0] + ) + xp_assert_close(r, newy) + + with pytest.raises(ValueError, + match="Spline coefficients 'cj' must not be empty."): + signal.qspline1d_eval(xp.asarray([], dtype=xp.float64), + xp.asarray([0.0], dtype=xp.float64)) + + +# i/o dtypes with scipy 1.9.1, likely fixed by backwards compat +sepfir_dtype_map = {np.uint8: np.float32, int: np.float64, + np.float32: np.float32, float: float, + np.complex64: np.complex64, complex: complex} + + +@make_xp_test_case(signal.sepfir2d) # type:ignore[attr-defined] +class TestSepfir2d: + def test_sepfir2d_invalid_filter(self, xp): + filt = xp.asarray([1.0, 2.0, 4.0, 2.0, 1.0]) + image = np.random.rand(7, 9) + image = xp.asarray(image) + # No error for odd lengths + signal.sepfir2d(image, filt, filt[2:]) + + # Row or column filter must be odd + with pytest.raises(ValueError, match="odd length"): + signal.sepfir2d(image, filt, filt[1:]) + with pytest.raises(ValueError, match="odd length"): + signal.sepfir2d(image, filt[1:], filt) + + # Filters must be 1-dimensional + with pytest.raises(ValueError, match="object too deep"): + signal.sepfir2d(image, xp.reshape(filt, (1, -1)), filt) + with pytest.raises(ValueError, match="object too deep"): + signal.sepfir2d(image, filt, xp.reshape(filt, (1, -1))) + + def test_sepfir2d_invalid_image(self, xp): + filt = xp.asarray([1.0, 2.0, 4.0, 2.0, 1.0]) + image = np.random.rand(8, 8) + image = xp.asarray(image) + + # Image must be 2 dimensional + with pytest.raises(ValueError, match="object too deep"): + signal.sepfir2d(xp.reshape(image, (4, 4, 4)), filt, filt) + + with pytest.raises(ValueError, match="object of too small depth"): + signal.sepfir2d(image[0, :], filt, filt) + + @pytest.mark.parametrize('dtyp', + [np.uint8, int, np.float32, float, np.complex64, complex] + ) + def test_simple(self, dtyp, xp): + # test values on a paper-and-pencil example + a = np.array([[1, 2, 3, 3, 2, 1], + [1, 2, 3, 3, 2, 1], + [1, 2, 3, 3, 2, 1], + [1, 2, 3, 3, 2, 1]], dtype=dtyp) + h1 = [0.5, 1, 0.5] + h2 = [1] + result = signal.sepfir2d(a, h1, h2) + dt = sepfir_dtype_map[dtyp] + expected = np.asarray([[2.5, 4. , 5.5, 5.5, 4. , 2.5], + [2.5, 4. , 5.5, 5.5, 4. , 2.5], + [2.5, 4. , 5.5, 5.5, 4. , 2.5], + [2.5, 4. , 5.5, 5.5, 4. , 2.5]], dtype=dt) + xp_assert_close(result, expected, atol=1e-16) + + result = signal.sepfir2d(a, h2, h1) + expected = np.asarray([[2., 4., 6., 6., 4., 2.], + [2., 4., 6., 6., 4., 2.], + [2., 4., 6., 6., 4., 2.], + [2., 4., 6., 6., 4., 2.]], dtype=dt) + xp_assert_close(result, expected, atol=1e-16) + + @skip_xp_backends(np_only=True, reason="TODO: convert this test") + @pytest.mark.parametrize('dtyp', + [np.uint8, int, np.float32, float, np.complex64, complex] + ) + def test_strided(self, dtyp, xp): + a = np.array([[1, 2, 3, 3, 2, 1, 1, 2, 3], + [1, 2, 3, 3, 2, 1, 1, 2, 3], + [1, 2, 3, 3, 2, 1, 1, 2, 3], + [1, 2, 3, 3, 2, 1, 1, 2, 3]]) + h1, h2 = [0.5, 1, 0.5], [1] + result_strided = signal.sepfir2d(a[:, ::2], h1, h2) + result_contig = signal.sepfir2d(a[:, ::2].copy(), h1, h2) + xp_assert_close(result_strided, result_contig, atol=1e-15) + assert result_strided.dtype == result_contig.dtype + + @skip_xp_backends(np_only=True, reason="TODO: convert this test") + @pytest.mark.xfail(reason="XXX: filt.size > image.shape: flaky") + def test_sepfir2d_strided_2(self, xp): + # XXX: this test is flaky: fails on some reruns, with + # result[0, 1] and result[1, 1] being ~1e+224. + filt = np.array([1.0, 2.0, 4.0, 2.0, 1.0, 3.0, 2.0]) + image = np.random.rand(4, 4) + + expected = np.asarray([[36.018162, 30.239061, 38.71187 , 43.878183], + [38.180999, 35.824583, 43.525247, 43.874945], + [43.269533, 40.834018, 46.757772, 44.276423], + [49.120928, 39.681844, 43.596067, 45.085854]]) + xp_assert_close(signal.sepfir2d(image, filt, filt[::3]), expected) + + @skip_xp_backends(np_only=True, reason="TODO: convert this test") + @pytest.mark.xfail(reason="XXX: flaky. pointers OOB on some platforms") + @pytest.mark.fail_asan + @pytest.mark.parametrize('dtyp', + [np.uint8, int, np.float32, float, np.complex64, complex] + ) + def test_sepfir2d_strided_3(self, dtyp, xp): + # NB: 'image' and 'filt' dtypes match here. Otherwise we can run into + # unsafe casting errors for many combinations. Historically, dtype handling + # in `sepfir2d` is a tad baroque; fixing it is an enhancement. + filt = np.array([1, 2, 4, 2, 1, 3, 2], dtype=dtyp) + image = np.asarray([[0, 3, 0, 1, 2], + [2, 2, 3, 3, 3], + [0, 1, 3, 0, 3], + [2, 3, 0, 1, 3], + [3, 3, 2, 1, 2]], dtype=dtyp) + + expected = [[123., 101., 91., 136., 127.], + [133., 125., 126., 152., 160.], + [136., 137., 150., 162., 177.], + [133., 124., 132., 148., 147.], + [173., 158., 152., 164., 141.]] + expected = np.asarray(expected) + result = signal.sepfir2d(image, filt, filt[::3]) + xp_assert_close(result, expected, atol=1e-15) + assert result.dtype == sepfir_dtype_map[dtyp] + + expected = [[22., 35., 41., 31., 47.], + [27., 39., 48., 47., 55.], + [33., 42., 49., 53., 59.], + [39., 44., 41., 36., 48.], + [67., 62., 47., 34., 46.]] + expected = np.asarray(expected) + result = signal.sepfir2d(image, filt[::3], filt[::3]) + xp_assert_close(result, expected, atol=1e-15) + assert result.dtype == sepfir_dtype_map[dtyp] + + +@make_xp_test_case(signal.cspline2d) # type:ignore[attr-defined] +def test_cspline2d(xp): + rng = np.random.RandomState(181819142) + image = rng.rand(71, 73) + image = xp.asarray(image, dtype=xp_default_dtype(xp)) + result = signal.cspline2d(image, 8.0) + assert array_namespace(result) == xp + + +@make_xp_test_case(signal.qspline2d) # type:ignore[attr-defined] +def test_qspline2d(xp): + rng = np.random.RandomState(181819143) + image = rng.rand(71, 73) + image = xp.asarray(image, dtype=xp_default_dtype(xp)) + result = signal.qspline2d(image) + assert array_namespace(result) == xp diff --git a/venv/lib/python3.12/site-packages/scipy/signal/tests/test_cont2discrete.py b/venv/lib/python3.12/site-packages/scipy/signal/tests/test_cont2discrete.py new file mode 100644 index 0000000000000000000000000000000000000000..11ddb4388ad80394f03b89e20cdac5f5566102c7 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/tests/test_cont2discrete.py @@ -0,0 +1,439 @@ +import math +import numpy as np +from scipy._lib._array_api import ( + assert_array_almost_equal, xp_assert_close, + make_xp_test_case +) + +import pytest +from scipy.signal import cont2discrete as c2d +from scipy.signal import dlsim, ss2tf, ss2zpk, lsim, lti +from scipy.signal import tf2ss, impulse, dimpulse, step, dstep + +# Author: Jeffrey Armstrong +# March 29, 2011 + +skip_xp_backends = pytest.mark.skip_xp_backends + + +@make_xp_test_case(c2d) +class TestC2D: + def test_zoh(self, xp): + ac = xp.eye(2, dtype=xp.float64) + bc = xp.full((2, 1), 0.5, dtype=xp.float64) + cc = xp.asarray([[0.75, 1.0], [1.0, 1.0], [1.0, 0.25]]) + dc = xp.asarray([[0.0], [0.0], [-0.33]]) + + ad_truth = 1.648721270700128 * xp.eye(2) + bd_truth = xp.full((2, 1), 0.324360635350064) + # c and d in discrete should be equal to their continuous counterparts + dt_requested = 0.5 + + ad, bd, cd, dd, dt = c2d((ac, bc, cc, dc), dt_requested, method='zoh') + + assert_array_almost_equal(ad_truth, ad) + assert_array_almost_equal(bd_truth, bd) + assert_array_almost_equal(cc, cd) + assert_array_almost_equal(dc, dd) + assert math.isclose(dt, dt_requested, abs_tol=1e-14) + + def test_foh(self, xp): + ac = xp.eye(2) + bc = xp.full((2, 1), 0.5) + cc = xp.asarray([[0.75, 1.0], [1.0, 1.0], [1.0, 0.25]]) + dc = xp.asarray([[0.0], [0.0], [-0.33]]) + + # True values are verified with Matlab + ad_truth = 1.648721270700128 * xp.eye(2) + bd_truth = xp.full((2, 1), 0.420839287058789) + cd_truth = cc + dd_truth = xp.asarray([[0.260262223725224], + [0.297442541400256], + [-0.144098411624840]]) + dt_requested = 0.5 + + ad, bd, cd, dd, dt = c2d((ac, bc, cc, dc), dt_requested, method='foh') + + assert_array_almost_equal(ad_truth, ad) + assert_array_almost_equal(bd_truth, bd) + assert_array_almost_equal(cd_truth, cd) + assert_array_almost_equal(dd_truth, dd) + assert math.isclose(dt, dt_requested, abs_tol=1e-14) + + def test_impulse(self, xp): + ac = xp.eye(2) + bc = xp.full((2, 1), 0.5) + cc = xp.asarray([[0.75, 1.0], [1.0, 1.0], [1.0, 0.25]]) + dc = xp.asarray([[0.0], [0.0], [0.0]]) + + # True values are verified with Matlab + ad_truth = 1.648721270700128 * xp.eye(2) + bd_truth = xp.full((2, 1), 0.412180317675032) + cd_truth = cc + dd_truth = xp.asarray([[0.4375], [0.5], [0.3125]]) + dt_requested = 0.5 + + ad, bd, cd, dd, dt = c2d((ac, bc, cc, dc), dt_requested, + method='impulse') + + assert_array_almost_equal(ad_truth, ad) + assert_array_almost_equal(bd_truth, bd) + assert_array_almost_equal(cd_truth, cd) + assert_array_almost_equal(dd_truth, dd) + assert math.isclose(dt, dt_requested, abs_tol=1e-14) + + def test_gbt(self, xp): + ac = xp.eye(2) + bc = xp.full((2, 1), 0.5) + cc = xp.asarray([[0.75, 1.0], [1.0, 1.0], [1.0, 0.25]]) + dc = xp.asarray([[0.0], [0.0], [-0.33]]) + + dt_requested = 0.5 + alpha = 1.0 / 3.0 + + ad_truth = 1.6 * xp.eye(2) + bd_truth = xp.full((2, 1), 0.3) + cd_truth = xp.asarray([[0.9, 1.2], + [1.2, 1.2], + [1.2, 0.3]]) + dd_truth = xp.asarray([[0.175], + [0.2], + [-0.205]]) + + ad, bd, cd, dd, dt = c2d((ac, bc, cc, dc), dt_requested, + method='gbt', alpha=alpha) + + assert_array_almost_equal(ad_truth, ad) + assert_array_almost_equal(bd_truth, bd) + assert_array_almost_equal(cd_truth, cd) + assert_array_almost_equal(dd_truth, dd) + + def test_euler(self, xp): + ac = xp.eye(2) + bc = xp.full((2, 1), 0.5) + cc = xp.asarray([[0.75, 1.0], [1.0, 1.0], [1.0, 0.25]]) + dc = xp.asarray([[0.0], [0.0], [-0.33]]) + + dt_requested = 0.5 + + ad_truth = 1.5 * xp.eye(2) + bd_truth = xp.full((2, 1), 0.25) + cd_truth = xp.asarray([[0.75, 1.0], + [1.0, 1.0], + [1.0, 0.25]]) + dd_truth = dc + + ad, bd, cd, dd, dt = c2d((ac, bc, cc, dc), dt_requested, + method='euler') + + assert_array_almost_equal(ad_truth, ad) + assert_array_almost_equal(bd_truth, bd) + assert_array_almost_equal(cd_truth, cd) + assert_array_almost_equal(dd_truth, dd) + assert math.isclose(dt, dt_requested, abs_tol=1e-14) + + def test_backward_diff(self, xp): + ac = xp.eye(2) + bc = xp.full((2, 1), 0.5) + cc = xp.asarray([[0.75, 1.0], [1.0, 1.0], [1.0, 0.25]]) + dc = xp.asarray([[0.0], [0.0], [-0.33]]) + + dt_requested = 0.5 + + ad_truth = 2.0 * xp.eye(2) + bd_truth = xp.full((2, 1), 0.5) + cd_truth = xp.asarray([[1.5, 2.0], + [2.0, 2.0], + [2.0, 0.5]]) + dd_truth = xp.asarray([[0.875], + [1.0], + [0.295]]) + + ad, bd, cd, dd, dt = c2d((ac, bc, cc, dc), dt_requested, + method='backward_diff') + + assert_array_almost_equal(ad_truth, ad) + assert_array_almost_equal(bd_truth, bd) + assert_array_almost_equal(cd_truth, cd) + assert_array_almost_equal(dd_truth, dd) + + def test_bilinear(self, xp): + ac = xp.eye(2) + bc = xp.full((2, 1), 0.5) + cc = xp.asarray([[0.75, 1.0], [1.0, 1.0], [1.0, 0.25]]) + dc = xp.asarray([[0.0], [0.0], [-0.33]]) + + dt_requested = 0.5 + + ad_truth = (5.0 / 3.0) * xp.eye(2) + bd_truth = xp.full((2, 1), 1.0 / 3.0) + cd_truth = xp.asarray([[1.0, 4.0 / 3.0], + [4.0 / 3.0, 4.0 / 3.0], + [4.0 / 3.0, 1.0 / 3.0]]) + dd_truth = xp.asarray([[0.291666666666667], + [1.0 / 3.0], + [-0.121666666666667]]) + + ad, bd, cd, dd, dt = c2d((ac, bc, cc, dc), dt_requested, + method='bilinear') + + assert_array_almost_equal(ad_truth, ad) + assert_array_almost_equal(bd_truth, bd) + assert_array_almost_equal(cd_truth, cd) + assert_array_almost_equal(dd_truth, dd) + assert math.isclose(dt, dt_requested, abs_tol=1e-14) + + # Same continuous system again, but change sampling rate + + ad_truth = 1.4 * xp.eye(2) + bd_truth = xp.full((2, 1), 0.2) + cd_truth = xp.asarray([[0.9, 1.2], [1.2, 1.2], [1.2, 0.3]]) + dd_truth = xp.asarray([[0.175], [0.2], [-0.205]]) + + dt_requested = 1.0 / 3.0 + + ad, bd, cd, dd, dt = c2d((ac, bc, cc, dc), dt_requested, + method='bilinear') + + assert_array_almost_equal(ad_truth, ad) + assert_array_almost_equal(bd_truth, bd) + assert_array_almost_equal(cd_truth, cd) + assert_array_almost_equal(dd_truth, dd) + assert math.isclose(dt_requested, dt) + + def test_transferfunction(self, xp): + numc = xp.asarray([0.25, 0.25, 0.5]) + denc = xp.asarray([0.75, 0.75, 1.0]) + + numd = xp.asarray([[1.0 / 3.0, -0.427419169438754, 0.221654141101125]]) + dend = xp.asarray([1.0, -1.351394049721225, 0.606530659712634]) + + dt_requested = 0.5 + + num, den, dt = c2d((numc, denc), dt_requested, method='zoh') + + assert_array_almost_equal(numd, num) + assert_array_almost_equal(dend, den) + assert math.isclose(dt_requested, dt) + + def test_zerospolesgain(self, xp): + zeros_c = xp.array([0.5, -0.5]) + poles_c = xp.array([1.j / xp.sqrt(2), -1.j / xp.sqrt(2)]) + k_c = 1.0 + + zeros_d = xp.asarray([1.23371727305860, 0.735356894461267]) + polls_d = xp.asarray([0.938148335039729 + 0.346233593780536j, + 0.938148335039729 - 0.346233593780536j]) + k_d = 1.0 + + dt_requested = 0.5 + + zeros, poles, k, dt = c2d((zeros_c, poles_c, k_c), dt_requested, + method='zoh') + + assert_array_almost_equal(zeros_d, zeros) + assert_array_almost_equal(polls_d, poles) + assert math.isclose(k_d, k) + assert math.isclose(dt_requested, dt) + + @skip_xp_backends(np_only=True) + def test_gbt_with_sio_tf_and_zpk(self, xp): + """Test method='gbt' with alpha=0.25 for tf and zpk cases.""" + # State space coefficients for the continuous SIO system. + A = -1.0 + B = 1.0 + C = 1.0 + D = 0.5 + + # The continuous transfer function coefficients. + cnum, cden = ss2tf(A, B, C, D) + + # Continuous zpk representation + cz, cp, ck = ss2zpk(A, B, C, D) + + h = 1.0 + alpha = 0.25 + + # Explicit formulas, in the scalar case. + Ad = (1 + (1 - alpha) * h * A) / (1 - alpha * h * A) + Bd = h * B / (1 - alpha * h * A) + Cd = C / (1 - alpha * h * A) + Dd = D + alpha * C * Bd + + # Convert the explicit solution to tf + dnum, dden = ss2tf(Ad, Bd, Cd, Dd) + + # Compute the discrete tf using cont2discrete. + c2dnum, c2dden, dt = c2d((cnum, cden), h, method='gbt', alpha=alpha) + + xp_assert_close(dnum, c2dnum) + xp_assert_close(dden, c2dden) + + # Convert explicit solution to zpk. + dz, dp, dk = ss2zpk(Ad, Bd, Cd, Dd) + + # Compute the discrete zpk using cont2discrete. + c2dz, c2dp, c2dk, dt = c2d((cz, cp, ck), h, method='gbt', alpha=alpha) + + xp_assert_close(dz, c2dz) + xp_assert_close(dp, c2dp) + xp_assert_close(dk, c2dk) + + def test_discrete_approx(self, xp): + """ + Test that the solution to the discrete approximation of a continuous + system actually approximates the solution to the continuous system. + This is an indirect test of the correctness of the implementation + of cont2discrete. + """ + + def u(t): + return xp.sin(2.5 * t) + + a = xp.asarray([[-0.01]]) + b = xp.asarray([[1.0]]) + c = xp.asarray([[1.0]]) + d = xp.asarray([[0.2]]) + x0 = 1.0 + + t = xp.linspace(0, 10.0, 101) + dt = t[1] - t[0] + u1 = u(t) + + # Use lsim to compute the solution to the continuous system. + t, yout, xout = lsim((a, b, c, d), T=t, U=u1, X0=x0) + + # Convert the continuous system to a discrete approximation. + dsys = c2d((a, b, c, d), dt, method='bilinear') + + # Use dlsim with the pairwise averaged input to compute the output + # of the discrete system. + u2 = 0.5 * (u1[:-1] + u1[1:]) + t2 = t[:-1] + td2, yd2, xd2 = dlsim(dsys, u=u2.reshape(-1, 1), t=t2, x0=x0) + + # ymid is the average of consecutive terms of the "exact" output + # computed by lsim2. This is what the discrete approximation + # actually approximates. + ymid = 0.5 * (yout[:-1] + yout[1:]) + + xp_assert_close(yd2.ravel(), ymid, rtol=1e-4) + + @skip_xp_backends(np_only=True) + def test_simo_tf(self): + # See gh-5753 + tf = ([[1, 0], [1, 1]], [1, 1]) + num, den, dt = c2d(tf, 0.01) + + assert dt == 0.01 # sanity check + xp_assert_close(den, [1, -0.990404983], rtol=1e-3) + xp_assert_close(num, [[1, -1], [1, -0.99004983]], rtol=1e-3) + + @skip_xp_backends(np_only=True) + def test_multioutput(self): + ts = 0.01 # time step + + tf = ([[1, -3], [1, 5]], [1, 1]) + num, den, dt = c2d(tf, ts) + + tf1 = (tf[0][0], tf[1]) + num1, den1, dt1 = c2d(tf1, ts) + + tf2 = (tf[0][1], tf[1]) + num2, den2, dt2 = c2d(tf2, ts) + + # Sanity checks + assert dt == dt1 + assert dt == dt2 + + # Check that we get the same results + xp_assert_close(num, np.vstack((num1, num2)), rtol=1e-13) + + # Single input, so the denominator should + # not be multidimensional like the numerator + xp_assert_close(den, den1, rtol=1e-13) + xp_assert_close(den, den2, rtol=1e-13) + + +class TestC2dLti: + def test_c2d_ss(self): + # StateSpace + A = np.array([[-0.3, 0.1], [0.2, -0.7]]) + B = np.array([[0], [1]]) + C = np.array([[1, 0]]) + D = 0 + dt = 0.05 + + A_res = np.array([[0.985136404135682, 0.004876671474795], + [0.009753342949590, 0.965629718236502]]) + B_res = np.array([[0.000122937599964], [0.049135527547844]]) + C_res = np.astype(C, np.float64) + + sys_ssc = lti(A, B, C, D) + sys_ssd = sys_ssc.to_discrete(dt=dt) + + xp_assert_close(sys_ssd.A, A_res) + xp_assert_close(sys_ssd.B, B_res) + xp_assert_close(sys_ssd.C, C_res) + xp_assert_close(sys_ssd.D, np.zeros_like(sys_ssd.D)) + + sys_ssd2 = c2d(sys_ssc, dt=dt) + + xp_assert_close(sys_ssd2.A, A_res) + xp_assert_close(sys_ssd2.B, B_res) + xp_assert_close(sys_ssd2.C, C_res) + xp_assert_close(sys_ssd2.D, np.zeros_like(sys_ssd.D)) + + def test_c2d_tf(self): + + sys = lti([0.5, 0.3], [1.0, 0.4]) + sys = sys.to_discrete(0.005) + + # Matlab results + num_res = np.array([0.5, -0.485149004980066]) + den_res = np.array([1.0, -0.980198673306755]) + + # Somehow a lot of numerical errors + xp_assert_close(sys.den, den_res, atol=0.02) + xp_assert_close(sys.num, num_res, atol=0.02) + + +@make_xp_test_case(c2d) +class TestC2dInvariants: + # Some test cases for checking the invariances. + # Array of triplets: (system, sample time, number of samples) + cases = [ + (tf2ss([1, 1], [1, 1.5, 1]), 0.25, 10), + (tf2ss([1, 2], [1, 1.5, 3, 1]), 0.5, 10), + (tf2ss(0.1, [1, 1, 2, 1]), 0.5, 10), + ] + + # Check that systems discretized with the impulse-invariant + # method really hold the invariant + @make_xp_test_case(impulse, dimpulse) + @pytest.mark.parametrize("sys,sample_time,samples_number", cases) + def test_impulse_invariant(self, xp, sys, sample_time, samples_number): + sys = tuple(map(xp.asarray, sys)) + time = xp.arange(samples_number) * sample_time + _, yout_cont = impulse(sys, T=time) + _, yout_disc = dimpulse(c2d(sys, sample_time, method='impulse'), + n=len(time)) + xp_assert_close(sample_time * yout_cont.ravel(), yout_disc[0].ravel()) + + # Step invariant should hold for ZOH discretized systems + @pytest.mark.parametrize("sys,sample_time,samples_number", cases) + def test_step_invariant(self, xp, sys, sample_time, samples_number): + sys = tuple(map(xp.asarray, sys)) + time = xp.arange(samples_number) * sample_time + _, yout_cont = step(sys, T=time) + _, yout_disc = dstep(c2d(sys, sample_time, method='zoh'), n=len(time)) + xp_assert_close(yout_cont.ravel(), yout_disc[0].ravel()) + + # Linear invariant should hold for FOH discretized systems + @pytest.mark.parametrize("sys,sample_time,samples_number", cases) + def test_linear_invariant(self, xp, sys, sample_time, samples_number): + sys = tuple(map(xp.asarray, sys)) + time = xp.arange(samples_number) * sample_time + _, yout_cont, _ = lsim(sys, T=time, U=time) + _, yout_disc, _ = dlsim(c2d(sys, sample_time, method='foh'), u=time) + xp_assert_close(yout_cont.ravel(), yout_disc.ravel()) diff --git a/venv/lib/python3.12/site-packages/scipy/signal/tests/test_czt.py b/venv/lib/python3.12/site-packages/scipy/signal/tests/test_czt.py new file mode 100644 index 0000000000000000000000000000000000000000..35087d99fec5057131f0735d43e0faa33a74ef82 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/tests/test_czt.py @@ -0,0 +1,221 @@ +# This program is public domain +# Authors: Paul Kienzle, Nadav Horesh +''' +A unit test module for czt.py +''' +import pytest +from scipy._lib._array_api import xp_assert_close +from scipy.fft import fft +from scipy.signal import (czt, zoom_fft, czt_points, CZT, ZoomFFT) +import numpy as np + + +def check_czt(x): + # Check that czt is the equivalent of normal fft + y = fft(x) + y1 = czt(x) + xp_assert_close(y1, y, rtol=1e-13) + + # Check that interpolated czt is the equivalent of normal fft + y = fft(x, 100*len(x)) + y1 = czt(x, 100*len(x)) + xp_assert_close(y1, y, rtol=1e-12) + + +def check_zoom_fft(x): + # Check that zoom_fft is the equivalent of normal fft + y = fft(x) + y1 = zoom_fft(x, [0, 2-2./len(y)], endpoint=True) + xp_assert_close(y1, y, rtol=1e-11, atol=1e-14) + y1 = zoom_fft(x, [0, 2]) + xp_assert_close(y1, y, rtol=1e-11, atol=1e-14) + + # Test fn scalar + y1 = zoom_fft(x, 2-2./len(y), endpoint=True) + xp_assert_close(y1, y, rtol=1e-11, atol=1e-14) + y1 = zoom_fft(x, 2) + xp_assert_close(y1, y, rtol=1e-11, atol=1e-14) + + # Check that zoom_fft with oversampling is equivalent to zero padding + over = 10 + yover = fft(x, over*len(x)) + y2 = zoom_fft(x, [0, 2-2./len(yover)], m=len(yover), endpoint=True) + xp_assert_close(y2, yover, rtol=1e-12, atol=1e-10) + y2 = zoom_fft(x, [0, 2], m=len(yover)) + xp_assert_close(y2, yover, rtol=1e-12, atol=1e-10) + + # Check that zoom_fft works on a subrange + w = np.linspace(0, 2-2./len(x), len(x)) + f1, f2 = w[3], w[6] + y3 = zoom_fft(x, [f1, f2], m=3*over+1, endpoint=True) + idx3 = slice(3*over, 6*over+1) + xp_assert_close(y3, yover[idx3], rtol=1e-13) + + +def test_1D(): + # Test of 1D version of the transforms + + rng = np.random.RandomState(0) # Deterministic randomness + + # Random signals + lengths = rng.randint(8, 200, 20) + np.append(lengths, 1) + for length in lengths: + x = rng.random(length) + check_zoom_fft(x) + check_czt(x) + + # Gauss + t = np.linspace(-2, 2, 128) + x = np.exp(-t**2/0.01) + check_zoom_fft(x) + + # Linear + x = [1, 2, 3, 4, 5, 6, 7] + check_zoom_fft(x) + + # Check near powers of two + check_zoom_fft(range(126-31)) + check_zoom_fft(range(127-31)) + check_zoom_fft(range(128-31)) + check_zoom_fft(range(129-31)) + check_zoom_fft(range(130-31)) + + # Check transform on n-D array input + x = np.reshape(np.arange(3*2*28), (3, 2, 28)) + y1 = zoom_fft(x, [0, 2-2./28]) + y2 = zoom_fft(x[2, 0, :], [0, 2-2./28]) + xp_assert_close(y1[2, 0], y2, rtol=1e-13, atol=1e-12) + + y1 = zoom_fft(x, [0, 2], endpoint=False) + y2 = zoom_fft(x[2, 0, :], [0, 2], endpoint=False) + xp_assert_close(y1[2, 0], y2, rtol=1e-13, atol=1e-12) + + # Random (not a test condition) + x = rng.rand(101) + check_zoom_fft(x) + + # Spikes + t = np.linspace(0, 1, 128) + x = np.sin(2*np.pi*t*5)+np.sin(2*np.pi*t*13) + check_zoom_fft(x) + + # Sines + x = np.zeros(100, dtype=complex) + x[[1, 5, 21]] = 1 + check_zoom_fft(x) + + # Sines plus complex component + x += 1j*np.linspace(0, 0.5, x.shape[0]) + check_zoom_fft(x) + + +def test_large_prime_lengths(): + rng = np.random.RandomState(0) # Deterministic randomness + for N in (101, 1009, 10007): + x = rng.rand(N) + y = fft(x) + y1 = czt(x) + xp_assert_close(y, y1, rtol=1e-12) + + +@pytest.mark.slow +def test_czt_vs_fft(): + rng = np.random.RandomState(123) # Deterministic randomness + random_lengths = rng.exponential(100000, size=10).astype('int') + for n in random_lengths: + a = rng.randn(n) + xp_assert_close(czt(a), fft(a), rtol=1e-11) + + +def test_empty_input(): + with pytest.raises(ValueError, match='Invalid number of CZT'): + czt([]) + with pytest.raises(ValueError, match='Invalid number of CZT'): + zoom_fft([], 0.5) + + +def test_0_rank_input(): + with pytest.raises(IndexError, match='tuple index out of range'): + czt(5) + with pytest.raises(IndexError, match='tuple index out of range'): + zoom_fft(5, 0.5) + + +@pytest.mark.parametrize('impulse', ([0, 0, 1], [0, 0, 1, 0, 0], + np.concatenate((np.array([0, 0, 1]), + np.zeros(100))))) +@pytest.mark.parametrize('m', (1, 3, 5, 8, 101, 1021)) +@pytest.mark.parametrize('a', (1, 2, 0.5, 1.1)) +# Step that tests away from the unit circle, but not so far it explodes from +# numerical error +@pytest.mark.parametrize('w', (None, 0.98534 + 0.17055j)) +def test_czt_math(impulse, m, w, a): + # z-transform of an impulse is 1 everywhere + xp_assert_close(czt(impulse[2:], m=m, w=w, a=a), + np.ones(m, dtype=np.complex128), rtol=1e-10) + + # z-transform of a delayed impulse is z**-1 + xp_assert_close(czt(impulse[1:], m=m, w=w, a=a), + czt_points(m=m, w=w, a=a)**-1, rtol=1e-10) + + # z-transform of a 2-delayed impulse is z**-2 + xp_assert_close(czt(impulse, m=m, w=w, a=a), + czt_points(m=m, w=w, a=a)**-2, rtol=1e-10) + + +def test_int_args(): + # Integer argument `a` was producing all 0s + xp_assert_close(abs(czt([0, 1], m=10, a=2)), 0.5*np.ones(10), rtol=1e-15) + xp_assert_close(czt_points(11, w=2), + 1/(2**np.arange(11, dtype=np.complex128)), rtol=1e-30) + + +def test_czt_points(): + for N in (1, 2, 3, 8, 11, 100, 101, 10007): + xp_assert_close(czt_points(N), np.exp(2j*np.pi*np.arange(N)/N), + rtol=1e-30) + + xp_assert_close(czt_points(7, w=1), np.ones(7, dtype=np.complex128), rtol=1e-30) + xp_assert_close(czt_points(11, w=2.), + 1/(2**np.arange(11, dtype=np.complex128)), rtol=1e-30) + + func = CZT(12, m=11, w=2., a=1) + xp_assert_close(func.points(), 1/(2**np.arange(11)), rtol=1e-30) + + +@pytest.mark.parametrize('cls, args', [(CZT, (100,)), (ZoomFFT, (100, 0.2))]) +def test_CZT_size_mismatch(cls, args): + # Data size doesn't match function's expected size + myfunc = cls(*args) + with pytest.raises(ValueError, match='CZT defined for'): + myfunc(np.arange(5)) + + +def test_invalid_range(): + with pytest.raises(ValueError, match='2-length sequence'): + ZoomFFT(100, [1, 2, 3]) + + +@pytest.mark.parametrize('m', [0, -11, 5.5, 4.0]) +def test_czt_points_errors(m): + # Invalid number of points + with pytest.raises(ValueError, match='Invalid number of CZT'): + czt_points(m) + + +@pytest.mark.parametrize('size', [0, -5, 3.5, 4.0]) +def test_nonsense_size(size): + # Numpy and Scipy fft() give ValueError for 0 output size, so we do, too + with pytest.raises(ValueError, match='Invalid number of CZT'): + CZT(size, 3) + with pytest.raises(ValueError, match='Invalid number of CZT'): + ZoomFFT(size, 0.2, 3) + with pytest.raises(ValueError, match='Invalid number of CZT'): + CZT(3, size) + with pytest.raises(ValueError, match='Invalid number of CZT'): + ZoomFFT(3, 0.2, size) + with pytest.raises(ValueError, match='Invalid number of CZT'): + czt([1, 2, 3], size) + with pytest.raises(ValueError, match='Invalid number of CZT'): + zoom_fft([1, 2, 3], 0.2, size) diff --git a/venv/lib/python3.12/site-packages/scipy/signal/tests/test_dltisys.py b/venv/lib/python3.12/site-packages/scipy/signal/tests/test_dltisys.py new file mode 100644 index 0000000000000000000000000000000000000000..d18313c34b6a261fd688fa913971f94667c7f5cb --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/tests/test_dltisys.py @@ -0,0 +1,601 @@ +# Author: Jeffrey Armstrong +# April 4, 2011 +import warnings + +import numpy as np +from pytest import raises as assert_raises +from scipy._lib._array_api import ( + assert_array_almost_equal, assert_almost_equal, xp_assert_close, xp_assert_equal, +) + +from scipy.signal import (dlsim, dstep, dimpulse, tf2zpk, lti, dlti, + StateSpace, TransferFunction, ZerosPolesGain, + dfreqresp, dbode, BadCoefficients) + + +class TestDLTI: + + def test_dlsim(self): + + a = np.asarray([[0.9, 0.1], [-0.2, 0.9]]) + b = np.asarray([[0.4, 0.1, -0.1], [0.0, 0.05, 0.0]]) + c = np.asarray([[0.1, 0.3]]) + d = np.asarray([[0.0, -0.1, 0.0]]) + dt = 0.5 + + # Create an input matrix with inputs down the columns (3 cols) and its + # respective time input vector + u = np.hstack((np.linspace(0, 4.0, num=5)[:, np.newaxis], + np.full((5, 1), 0.01), + np.full((5, 1), -0.002))) + t_in = np.linspace(0, 2.0, num=5) + + # Define the known result + yout_truth = np.array([[-0.001, + -0.00073, + 0.039446, + 0.0915387, + 0.13195948]]).T + xout_truth = np.asarray([[0, 0], + [0.0012, 0.0005], + [0.40233, 0.00071], + [1.163368, -0.079327], + [2.2402985, -0.3035679]]) + + tout, yout, xout = dlsim((a, b, c, d, dt), u, t_in) + + assert_array_almost_equal(yout_truth, yout) + assert_array_almost_equal(xout_truth, xout) + assert_array_almost_equal(t_in, tout) + + # Make sure input with single-dimension doesn't raise error + dlsim((1, 2, 3), 4) + + # Interpolated control - inputs should have different time steps + # than the discrete model uses internally + u_sparse = u[[0, 4], :] + t_sparse = np.asarray([0.0, 2.0]) + + tout, yout, xout = dlsim((a, b, c, d, dt), u_sparse, t_sparse) + + assert_array_almost_equal(yout_truth, yout) + assert_array_almost_equal(xout_truth, xout) + assert len(tout) == len(yout) + + # Transfer functions (assume dt = 0.5) + num = np.asarray([1.0, -0.1]) + den = np.asarray([0.3, 1.0, 0.2]) + yout_truth = np.array([[0.0, + 0.0, + 3.33333333333333, + -4.77777777777778, + 23.0370370370370]]).T + + # Assume use of the first column of the control input built earlier + tout, yout = dlsim((num, den, 0.5), u[:, 0], t_in) + + assert_array_almost_equal(yout, yout_truth) + assert_array_almost_equal(t_in, tout) + + # Retest the same with a 1-D input vector + uflat = np.asarray(u[:, 0]) + uflat = uflat.reshape((5,)) + tout, yout = dlsim((num, den, 0.5), uflat, t_in) + + assert_array_almost_equal(yout, yout_truth) + assert_array_almost_equal(t_in, tout) + + # zeros-poles-gain representation + zd = np.array([0.5, -0.5]) + pd = np.array([1.j / np.sqrt(2), -1.j / np.sqrt(2)]) + k = 1.0 + yout_truth = np.array([[0.0, 1.0, 2.0, 2.25, 2.5]]).T + + tout, yout = dlsim((zd, pd, k, 0.5), u[:, 0], t_in) + + assert_array_almost_equal(yout, yout_truth) + assert_array_almost_equal(t_in, tout) + + # Raise an error for continuous-time systems + system = lti([1], [1, 1]) + assert_raises(AttributeError, dlsim, system, u) + + def test_dstep(self): + + a = np.asarray([[0.9, 0.1], [-0.2, 0.9]]) + b = np.asarray([[0.4, 0.1, -0.1], [0.0, 0.05, 0.0]]) + c = np.asarray([[0.1, 0.3]]) + d = np.asarray([[0.0, -0.1, 0.0]]) + dt = 0.5 + + # Because b.shape[1] == 3, dstep should result in a tuple of three + # result vectors + yout_step_truth = (np.asarray([0.0, 0.04, 0.052, 0.0404, 0.00956, + -0.036324, -0.093318, -0.15782348, + -0.226628324, -0.2969374948]), + np.asarray([-0.1, -0.075, -0.058, -0.04815, + -0.04453, -0.0461895, -0.0521812, + -0.061588875, -0.073549579, + -0.08727047595]), + np.asarray([0.0, -0.01, -0.013, -0.0101, -0.00239, + 0.009081, 0.0233295, 0.03945587, + 0.056657081, 0.0742343737])) + + tout, yout = dstep((a, b, c, d, dt), n=10) + + assert len(yout) == 3 + + for i in range(0, len(yout)): + assert yout[i].shape[0] == 10 + assert_array_almost_equal(yout[i].flatten(), yout_step_truth[i]) + + # Check that the other two inputs (tf, zpk) will work as well + tfin = ([1.0], [1.0, 1.0], 0.5) + yout_tfstep = np.asarray([0.0, 1.0, 0.0]) + tout, yout = dstep(tfin, n=3) + assert len(yout) == 1 + assert_array_almost_equal(yout[0].flatten(), yout_tfstep) + + zpkin = tf2zpk(tfin[0], tfin[1]) + (0.5,) + tout, yout = dstep(zpkin, n=3) + assert len(yout) == 1 + assert_array_almost_equal(yout[0].flatten(), yout_tfstep) + + # Raise an error for continuous-time systems + system = lti([1], [1, 1]) + assert_raises(AttributeError, dstep, system) + + def test_dimpulse(self): + + a = np.asarray([[0.9, 0.1], [-0.2, 0.9]]) + b = np.asarray([[0.4, 0.1, -0.1], [0.0, 0.05, 0.0]]) + c = np.asarray([[0.1, 0.3]]) + d = np.asarray([[0.0, -0.1, 0.0]]) + dt = 0.5 + + # Because b.shape[1] == 3, dimpulse should result in a tuple of three + # result vectors + yout_imp_truth = (np.asarray([0.0, 0.04, 0.012, -0.0116, -0.03084, + -0.045884, -0.056994, -0.06450548, + -0.068804844, -0.0703091708]), + np.asarray([-0.1, 0.025, 0.017, 0.00985, 0.00362, + -0.0016595, -0.0059917, -0.009407675, + -0.011960704, -0.01372089695]), + np.asarray([0.0, -0.01, -0.003, 0.0029, 0.00771, + 0.011471, 0.0142485, 0.01612637, + 0.017201211, 0.0175772927])) + + tout, yout = dimpulse((a, b, c, d, dt), n=10) + + assert len(yout) == 3 + + for i in range(0, len(yout)): + assert yout[i].shape[0] == 10 + assert_array_almost_equal(yout[i].flatten(), yout_imp_truth[i]) + + # Check that the other two inputs (tf, zpk) will work as well + tfin = ([1.0], [1.0, 1.0], 0.5) + yout_tfimpulse = np.asarray([0.0, 1.0, -1.0]) + tout, yout = dimpulse(tfin, n=3) + assert len(yout) == 1 + assert_array_almost_equal(yout[0].flatten(), yout_tfimpulse) + + zpkin = tf2zpk(tfin[0], tfin[1]) + (0.5,) + tout, yout = dimpulse(zpkin, n=3) + assert len(yout) == 1 + assert_array_almost_equal(yout[0].flatten(), yout_tfimpulse) + + # Raise an error for continuous-time systems + system = lti([1], [1, 1]) + assert_raises(AttributeError, dimpulse, system) + + def test_dlsim_trivial(self): + a = np.array([[0.0]]) + b = np.array([[0.0]]) + c = np.array([[0.0]]) + d = np.array([[0.0]]) + n = 5 + u = np.zeros(n).reshape(-1, 1) + tout, yout, xout = dlsim((a, b, c, d, 1), u) + xp_assert_equal(tout, np.arange(float(n))) + xp_assert_equal(yout, np.zeros((n, 1))) + xp_assert_equal(xout, np.zeros((n, 1))) + + def test_dlsim_simple1d(self): + a = np.array([[0.5]]) + b = np.array([[0.0]]) + c = np.array([[1.0]]) + d = np.array([[0.0]]) + n = 5 + u = np.zeros(n).reshape(-1, 1) + tout, yout, xout = dlsim((a, b, c, d, 1), u, x0=1) + xp_assert_equal(tout, np.arange(float(n))) + expected = (0.5 ** np.arange(float(n))).reshape(-1, 1) + xp_assert_equal(yout, expected) + xp_assert_equal(xout, expected) + + def test_dlsim_simple2d(self): + lambda1 = 0.5 + lambda2 = 0.25 + a = np.array([[lambda1, 0.0], + [0.0, lambda2]]) + b = np.array([[0.0], + [0.0]]) + c = np.array([[1.0, 0.0], + [0.0, 1.0]]) + d = np.array([[0.0], + [0.0]]) + n = 5 + u = np.zeros(n).reshape(-1, 1) + tout, yout, xout = dlsim((a, b, c, d, 1), u, x0=1) + xp_assert_equal(tout, np.arange(float(n))) + # The analytical solution: + expected = (np.array([lambda1, lambda2]) ** + np.arange(float(n)).reshape(-1, 1)) + xp_assert_equal(yout, expected) + xp_assert_equal(xout, expected) + + def test_more_step_and_impulse(self): + lambda1 = 0.5 + lambda2 = 0.75 + a = np.array([[lambda1, 0.0], + [0.0, lambda2]]) + b = np.array([[1.0, 0.0], + [0.0, 1.0]]) + c = np.array([[1.0, 1.0]]) + d = np.array([[0.0, 0.0]]) + + n = 10 + + # Check a step response. + ts, ys = dstep((a, b, c, d, 1), n=n) + + # Create the exact step response. + stp0 = (1.0 / (1 - lambda1)) * (1.0 - lambda1 ** np.arange(n)) + stp1 = (1.0 / (1 - lambda2)) * (1.0 - lambda2 ** np.arange(n)) + + xp_assert_close(ys[0][:, 0], stp0) + xp_assert_close(ys[1][:, 0], stp1) + + # Check an impulse response with an initial condition. + x0 = np.array([1.0, 1.0]) + ti, yi = dimpulse((a, b, c, d, 1), n=n, x0=x0) + + # Create the exact impulse response. + imp = (np.array([lambda1, lambda2]) ** + np.arange(-1, n + 1).reshape(-1, 1)) + imp[0, :] = 0.0 + # Analytical solution to impulse response + y0 = imp[:n, 0] + np.dot(imp[1:n + 1, :], x0) + y1 = imp[:n, 1] + np.dot(imp[1:n + 1, :], x0) + + xp_assert_close(yi[0][:, 0], y0) + xp_assert_close(yi[1][:, 0], y1) + + # Check that dt=0.1, n=3 gives 3 time values. + system = ([1.0], [1.0, -0.5], 0.1) + t, (y,) = dstep(system, n=3) + xp_assert_close(t, [0, 0.1, 0.2]) + xp_assert_equal(y.T, [[0, 1.0, 1.5]]) + t, (y,) = dimpulse(system, n=3) + xp_assert_close(t, [0, 0.1, 0.2]) + xp_assert_equal(y.T, [[0, 1, 0.5]]) + + +class TestDlti: + def test_dlti_instantiation(self): + # Test that lti can be instantiated. + + dt = 0.05 + # TransferFunction + s = dlti([1], [-1], dt=dt) + assert isinstance(s, TransferFunction) + assert isinstance(s, dlti) + assert not isinstance(s, lti) + assert s.dt == dt + + # ZerosPolesGain + s = dlti(np.array([]), np.array([-1]), 1, dt=dt) + assert isinstance(s, ZerosPolesGain) + assert isinstance(s, dlti) + assert not isinstance(s, lti) + assert s.dt == dt + + # StateSpace + s = dlti([1], [-1], 1, 3, dt=dt) + assert isinstance(s, StateSpace) + assert isinstance(s, dlti) + assert not isinstance(s, lti) + assert s.dt == dt + + # Number of inputs + assert_raises(ValueError, dlti, 1) + assert_raises(ValueError, dlti, 1, 1, 1, 1, 1) + + +class TestStateSpaceDisc: + def test_initialization(self): + # Check that all initializations work + dt = 0.05 + StateSpace(1, 1, 1, 1, dt=dt) + StateSpace([1], [2], [3], [4], dt=dt) + StateSpace(np.array([[1, 2], [3, 4]]), np.array([[1], [2]]), + np.array([[1, 0]]), np.array([[0]]), dt=dt) + StateSpace(1, 1, 1, 1, dt=True) + + def test_conversion(self): + # Check the conversion functions + s = StateSpace(1, 2, 3, 4, dt=0.05) + assert isinstance(s.to_ss(), StateSpace) + assert isinstance(s.to_tf(), TransferFunction) + assert isinstance(s.to_zpk(), ZerosPolesGain) + + # Make sure copies work + assert StateSpace(s) is not s + assert s.to_ss() is not s + + def test_properties(self): + # Test setters/getters for cross class properties. + # This implicitly tests to_tf() and to_zpk() + + # Getters + s = StateSpace(1, 1, 1, 1, dt=0.05) + xp_assert_equal(s.poles, [1.]) + xp_assert_equal(s.zeros, [0.]) + + +class TestTransferFunction: + def test_initialization(self): + # Check that all initializations work + dt = 0.05 + TransferFunction(1, 1, dt=dt) + TransferFunction([1], [2], dt=dt) + TransferFunction(np.array([1]), np.array([2]), dt=dt) + TransferFunction(1, 1, dt=True) + + def test_conversion(self): + # Check the conversion functions + s = TransferFunction([1, 0], [1, -1], dt=0.05) + assert isinstance(s.to_ss(), StateSpace) + assert isinstance(s.to_tf(), TransferFunction) + assert isinstance(s.to_zpk(), ZerosPolesGain) + + # Make sure copies work + assert TransferFunction(s) is not s + assert s.to_tf() is not s + + def test_properties(self): + # Test setters/getters for cross class properties. + # This implicitly tests to_ss() and to_zpk() + + # Getters + s = TransferFunction([1, 0], [1, -1], dt=0.05) + xp_assert_equal(s.poles, [1.]) + xp_assert_equal(s.zeros, [0.]) + + +class TestZerosPolesGain: + def test_initialization(self): + # Check that all initializations work + dt = 0.05 + ZerosPolesGain(1, 1, 1, dt=dt) + ZerosPolesGain([1], [2], 1, dt=dt) + ZerosPolesGain(np.array([1]), np.array([2]), 1, dt=dt) + ZerosPolesGain(1, 1, 1, dt=True) + + def test_conversion(self): + # Check the conversion functions + s = ZerosPolesGain(1, 2, 3, dt=0.05) + assert isinstance(s.to_ss(), StateSpace) + assert isinstance(s.to_tf(), TransferFunction) + assert isinstance(s.to_zpk(), ZerosPolesGain) + + # Make sure copies work + assert ZerosPolesGain(s) is not s + assert s.to_zpk() is not s + + +class Test_dfreqresp: + + def test_manual(self): + # Test dfreqresp() real part calculation (manual sanity check). + # 1st order low-pass filter: H(z) = 1 / (z - 0.2), + system = TransferFunction(1, [1, -0.2], dt=0.1) + w = [0.1, 1, 10] + w, H = dfreqresp(system, w=w) + + # test real + expected_re = [1.2383, 0.4130, -0.7553] + assert_almost_equal(H.real, expected_re, decimal=4) + + # test imag + expected_im = [-0.1555, -1.0214, 0.3955] + assert_almost_equal(H.imag, expected_im, decimal=4) + + def test_auto(self): + # Test dfreqresp() real part calculation. + # 1st order low-pass filter: H(z) = 1 / (z - 0.2), + system = TransferFunction(1, [1, -0.2], dt=0.1) + w = [0.1, 1, 10, 100] + w, H = dfreqresp(system, w=w) + jw = np.exp(w * 1j) + y = np.polyval(system.num, jw) / np.polyval(system.den, jw) + + # test real + expected_re = y.real + assert_almost_equal(H.real, expected_re) + + # test imag + expected_im = y.imag + assert_almost_equal(H.imag, expected_im) + + def test_freq_range(self): + # Test that freqresp() finds a reasonable frequency range. + # 1st order low-pass filter: H(z) = 1 / (z - 0.2), + # Expected range is from 0.01 to 10. + system = TransferFunction(1, [1, -0.2], dt=0.1) + n = 10 + expected_w = np.linspace(0, np.pi, 10, endpoint=False) + w, H = dfreqresp(system, n=n) + assert_almost_equal(w, expected_w) + + def test_pole_one(self): + # Test that freqresp() doesn't fail on a system with a pole at 0. + # integrator, pole at zero: H(s) = 1 / s + system = TransferFunction([1], [1, -1], dt=0.1) + + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", "divide by zero", RuntimeWarning) + warnings.filterwarnings( + "ignore", "invalid value encountered", RuntimeWarning) + w, H = dfreqresp(system, n=2) + assert w[0] == 0. # a fail would give not-a-number + + def test_error(self): + # Raise an error for continuous-time systems + system = lti([1], [1, 1]) + assert_raises(AttributeError, dfreqresp, system) + + def test_from_state_space(self): + # H(z) = 2 / z^3 - 0.5 * z^2 + + system_TF = dlti([2], [1, -0.5, 0, 0]) + + A = np.array([[0.5, 0, 0], + [1, 0, 0], + [0, 1, 0]]) + B = np.array([[1, 0, 0]]).T + C = np.array([[0, 0, 2]]) + D = 0 + + system_SS = dlti(A, B, C, D) + w = 10.0**np.arange(-3,0,.5) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", BadCoefficients) + w1, H1 = dfreqresp(system_TF, w=w) + w2, H2 = dfreqresp(system_SS, w=w) + + assert_almost_equal(H1, H2) + + def test_from_zpk(self): + # 1st order low-pass filter: H(s) = 0.3 / (z - 0.2), + system_ZPK = dlti([],[0.2],0.3) + system_TF = dlti(0.3, [1, -0.2]) + w = [0.1, 1, 10, 100] + w1, H1 = dfreqresp(system_ZPK, w=w) + w2, H2 = dfreqresp(system_TF, w=w) + assert_almost_equal(H1, H2) + + +class Test_bode: + + def test_manual(self): + # Test bode() magnitude calculation (manual sanity check). + # 1st order low-pass filter: H(s) = 0.3 / (z - 0.2), + dt = 0.1 + system = TransferFunction(0.3, [1, -0.2], dt=dt) + w = [0.1, 0.5, 1, np.pi] + w2, mag, phase = dbode(system, w=w) + + # Test mag + expected_mag = [-8.5329, -8.8396, -9.6162, -12.0412] + assert_almost_equal(mag, expected_mag, decimal=4) + + # Test phase + expected_phase = [-7.1575, -35.2814, -67.9809, -180.0000] + assert_almost_equal(phase, expected_phase, decimal=4) + + # Test frequency + xp_assert_equal(np.array(w) / dt, w2) + + def test_auto(self): + # Test bode() magnitude calculation. + # 1st order low-pass filter: H(s) = 0.3 / (z - 0.2), + system = TransferFunction(0.3, [1, -0.2], dt=0.1) + w = np.array([0.1, 0.5, 1, np.pi]) + w2, mag, phase = dbode(system, w=w) + jw = np.exp(w * 1j) + y = np.polyval(system.num, jw) / np.polyval(system.den, jw) + + # Test mag + expected_mag = 20.0 * np.log10(abs(y)) + assert_almost_equal(mag, expected_mag) + + # Test phase + expected_phase = np.rad2deg(np.angle(y)) + assert_almost_equal(phase, expected_phase) + + def test_range(self): + # Test that bode() finds a reasonable frequency range. + # 1st order low-pass filter: H(s) = 0.3 / (z - 0.2), + dt = 0.1 + system = TransferFunction(0.3, [1, -0.2], dt=0.1) + n = 10 + # Expected range is from 0.01 to 10. + expected_w = np.linspace(0, np.pi, n, endpoint=False) / dt + w, mag, phase = dbode(system, n=n) + assert_almost_equal(w, expected_w) + + def test_pole_one(self): + # Test that freqresp() doesn't fail on a system with a pole at 0. + # integrator, pole at zero: H(s) = 1 / s + system = TransferFunction([1], [1, -1], dt=0.1) + + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", "divide by zero", RuntimeWarning) + warnings.filterwarnings( + "ignore", "invalid value encountered", RuntimeWarning) + w, mag, phase = dbode(system, n=2) + assert w[0] == 0. # a fail would give not-a-number + + def test_imaginary(self): + # bode() should not fail on a system with pure imaginary poles. + # The test passes if bode doesn't raise an exception. + system = TransferFunction([1], [1, 0, 100], dt=0.1) + dbode(system, n=2) + + def test_error(self): + # Raise an error for continuous-time systems + system = lti([1], [1, 1]) + assert_raises(AttributeError, dbode, system) + + +class TestTransferFunctionZConversion: + """Test private conversions between 'z' and 'z**-1' polynomials.""" + + def test_full(self): + # Numerator and denominator same order + num = np.asarray([2.0, 3, 4]) + den = np.asarray([5.0, 6, 7]) + num2, den2 = TransferFunction._z_to_zinv(num, den) + xp_assert_equal(num, num2) + xp_assert_equal(den, den2) + + num2, den2 = TransferFunction._zinv_to_z(num, den) + xp_assert_equal(num, num2) + xp_assert_equal(den, den2) + + def test_numerator(self): + # Numerator lower order than denominator + num = np.asarray([2.0, 3]) + den = np.asarray([50, 6, 7]) + num2, den2 = TransferFunction._z_to_zinv(num, den) + xp_assert_equal([0.0, 2, 3], num2) + xp_assert_equal(den, den2) + + num2, den2 = TransferFunction._zinv_to_z(num, den) + xp_assert_equal([2.0, 3, 0], num2) + xp_assert_equal(den, den2) + + def test_denominator(self): + # Numerator higher order than denominator + num = np.asarray([2., 3, 4]) + den = np.asarray([5.0, 6]) + num2, den2 = TransferFunction._z_to_zinv(num, den) + xp_assert_equal(num, num2) + xp_assert_equal([0.0, 5, 6], den2) + + num2, den2 = TransferFunction._zinv_to_z(num, den) + xp_assert_equal(num, num2) + xp_assert_equal([5.0, 6, 0], den2) + diff --git a/venv/lib/python3.12/site-packages/scipy/signal/tests/test_filter_design.py b/venv/lib/python3.12/site-packages/scipy/signal/tests/test_filter_design.py new file mode 100644 index 0000000000000000000000000000000000000000..265aa19a0017648998535a3515bdfa9a44d29920 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/tests/test_filter_design.py @@ -0,0 +1,5194 @@ +import math +import cmath +import warnings +import os + +from itertools import product + +from scipy._external.packaging_version import version +import numpy as np +import pytest +from pytest import raises as assert_raises +from scipy._lib._array_api import ( + xp_assert_close, xp_assert_equal, array_namespace, + assert_array_almost_equal, xp_size, xp_default_dtype, is_numpy, + make_xp_test_case, make_xp_pytest_param, is_cupy, is_torch, scipy_namespace_for, + _xp_copy_to_numpy, xp_assert_close_nulp +) +import scipy._external.array_api_extra as xpx + +from numpy import array, spacing, sin, pi +from scipy.signal import (argrelextrema, BadCoefficients, bessel, besselap, bilinear, + buttap, butter, buttord, cheb1ap, cheb1ord, cheb2ap, + cheb2ord, cheby1, cheby2, ellip, ellipap, ellipord, + findfreqs, firwin, freqs_zpk, freqs, freqz, freqz_zpk, + gammatone, group_delay, iircomb, iirdesign, iirfilter, + iirnotch, iirpeak, lp2bp, lp2bs, lp2hp, lp2lp, normalize, + sos2tf, sos2zpk, sosfreqz, freqz_sos, tf2sos, tf2zpk, zpk2sos, + zpk2tf, bilinear_zpk, lp2lp_zpk, lp2hp_zpk, lp2bp_zpk, + lp2bs_zpk) +from scipy.signal._filter_design import (_cplxreal, _cplxpair, _norm_factor, + _bessel_poly, _bessel_zeros) +from scipy.signal._filter_design import _logspace +from scipy.signal import _polyutils as _pu +from scipy.signal._polyutils import _sort_cmplx + +skip_xp_backends = pytest.mark.skip_xp_backends +xfail_xp_backends = pytest.mark.xfail_xp_backends + + +DEFAULT_F32 = os.getenv('SCIPY_DEFAULT_DTYPE', default='float64') == 'float32' + +try: + import mpmath +except ImportError: + mpmath = None + + +def mpmath_check(min_ver): + return pytest.mark.skipif( + mpmath is None + or version.parse(mpmath.__version__) < version.Version(min_ver), + reason=f"mpmath version >= {min_ver} required", + ) + + +class TestCplxPair: + + def test_trivial_input(self): + assert _cplxpair([]).size == 0 + assert _cplxpair(1) == 1 + + def test_output_order(self): + xp_assert_close(_cplxpair([1+1j, 1-1j]), [1-1j, 1+1j]) + + a = [1+1j, 1+1j, 1, 1-1j, 1-1j, 2] + b = [1-1j, 1+1j, 1-1j, 1+1j, 1, 2] + xp_assert_close(_cplxpair(a), b) + + # points spaced around the unit circle + z = np.exp(2j*pi*array([4, 3, 5, 2, 6, 1, 0])/7) + z1 = np.copy(z) + np.random.shuffle(z) + xp_assert_close(_cplxpair(z), z1) + np.random.shuffle(z) + xp_assert_close(_cplxpair(z), z1) + np.random.shuffle(z) + xp_assert_close(_cplxpair(z), z1) + + # Should be able to pair up all the conjugates + x = np.random.rand(10000) + 1j * np.random.rand(10000) + y = x.conj() + z = np.random.rand(10000) + x = np.concatenate((x, y, z)) + np.random.shuffle(x) + c = _cplxpair(x) + + # Every other element of head should be conjugates: + xp_assert_close(c[0:20000:2], np.conj(c[1:20000:2])) + # Real parts of head should be in sorted order: + xp_assert_close(c[0:20000:2].real, np.sort(c[0:20000:2].real)) + # Tail should be sorted real numbers: + xp_assert_close(c[20000:], np.sort(c[20000:])) + + def test_real_integer_input(self): + xp_assert_equal(_cplxpair([2, 0, 1]), [0, 1, 2]) + + def test_tolerances(self): + eps = spacing(1) + xp_assert_close(_cplxpair([1j, -1j, 1+1j*eps], tol=2*eps), + [-1j, 1j, 1+1j*eps]) + + # sorting close to 0 + xp_assert_close(_cplxpair([-eps+1j, +eps-1j]), [-1j, +1j]) + xp_assert_close(_cplxpair([+eps+1j, -eps-1j]), [-1j, +1j]) + xp_assert_close(_cplxpair([+1j, -1j]), [-1j, +1j]) + + def test_unmatched_conjugates(self): + # 1+2j is unmatched + assert_raises(ValueError, _cplxpair, [1+3j, 1-3j, 1+2j]) + + # 1+2j and 1-3j are unmatched + assert_raises(ValueError, _cplxpair, [1+3j, 1-3j, 1+2j, 1-3j]) + + # 1+3j is unmatched + assert_raises(ValueError, _cplxpair, [1+3j, 1-3j, 1+3j]) + + # Not conjugates + assert_raises(ValueError, _cplxpair, [4+5j, 4+5j]) + assert_raises(ValueError, _cplxpair, [1-7j, 1-7j]) + + # No pairs + assert_raises(ValueError, _cplxpair, [1+3j]) + assert_raises(ValueError, _cplxpair, [1-3j]) + + +class TestCplxReal: + + def test_trivial_input(self): + assert all(x.size == 0 for x in _cplxreal([])) + + x = _cplxreal(1) + assert x[0].size == 0 + xp_assert_equal(x[1], np.asarray([1])) + + + def test_output_order(self): + zc, zr = _cplxreal(np.roots(array([1, 0, 0, 1]))) + xp_assert_close(np.append(zc, zr), [1/2 + 1j*sin(pi/3), -1]) + + eps = spacing(1) + + a = [0+1j, 0-1j, eps + 1j, eps - 1j, -eps + 1j, -eps - 1j, + 1, 4, 2, 3, 0, 0, + 2+3j, 2-3j, + 1-eps + 1j, 1+2j, 1-2j, 1+eps - 1j, # sorts out of order + 3+1j, 3+1j, 3+1j, 3-1j, 3-1j, 3-1j, + 2-3j, 2+3j] + zc, zr = _cplxreal(a) + xp_assert_close(zc, [1j, 1j, 1j, 1+1j, 1+2j, 2+3j, 2+3j, 3+1j, 3+1j, + 3+1j]) + xp_assert_close(zr, [0.0, 0, 1, 2, 3, 4]) + + z = array([1-eps + 1j, 1+2j, 1-2j, 1+eps - 1j, 1+eps+3j, 1-2*eps-3j, + 0+1j, 0-1j, 2+4j, 2-4j, 2+3j, 2-3j, 3+7j, 3-7j, 4-eps+1j, + 4+eps-2j, 4-1j, 4-eps+2j]) + + zc, zr = _cplxreal(z) + xp_assert_close(zc, [1j, 1+1j, 1+2j, 1+3j, 2+3j, 2+4j, 3+7j, 4+1j, + 4+2j]) + xp_assert_equal(zr, np.asarray([])) + + def test_unmatched_conjugates(self): + # 1+2j is unmatched + assert_raises(ValueError, _cplxreal, [1+3j, 1-3j, 1+2j]) + + # 1+2j and 1-3j are unmatched + assert_raises(ValueError, _cplxreal, [1+3j, 1-3j, 1+2j, 1-3j]) + + # 1+3j is unmatched + assert_raises(ValueError, _cplxreal, [1+3j, 1-3j, 1+3j]) + + # No pairs + assert_raises(ValueError, _cplxreal, [1+3j]) + assert_raises(ValueError, _cplxreal, [1-3j]) + + def test_real_integer_input(self): + zc, zr = _cplxreal([2, 0, 1, 4]) + xp_assert_equal(zc, np.asarray([])) + xp_assert_equal(zr, [0, 1, 2, 4]) + + +@make_xp_test_case(tf2zpk) +class TestTf2zpk: + + @skip_xp_backends("dask.array", reason="https://github.com/dask/dask/issues/11883") + @pytest.mark.parametrize('dt', ('float64', 'complex128')) + def test_simple(self, dt, xp): + dtyp = getattr(xp, dt) + + z_r = xp.asarray([0.5, -0.5]) + p_r = xp.asarray([1.j / math.sqrt(2), -1.j / math.sqrt(2)]) + # Sort the zeros/poles so that we don't fail the test if the order + # changes + z_r = _sort_cmplx(z_r, xp=xp) + p_r = _sort_cmplx(p_r, xp=xp) + + b = xp.astype(_pu.poly(z_r, xp=xp), dtyp) + a = xp.astype(_pu.poly(p_r, xp=xp), dtyp) + + z, p, k = tf2zpk(b, a) + z = _sort_cmplx(z, xp=xp) + # The real part of `p` is ~0.0, so sort by imaginary part + p = p[xp.argsort(xp.imag(p))] + + assert_array_almost_equal(z, z_r) + assert_array_almost_equal(p, p_r) + assert math.isclose(xp.real(k), 1.) + assert k.dtype == dtyp + + def test_bad_filter(self): + # Regression test for #651: better handling of badly conditioned + # filter coefficients. + with warnings.catch_warnings(): + warnings.simplefilter("error", BadCoefficients) + assert_raises(BadCoefficients, tf2zpk, [1e-15], [1.0, 1.0]) + + +@make_xp_test_case(zpk2tf) +class TestZpk2Tf: + + def test_identity(self, xp): + """Test the identity transfer function.""" + z = xp.asarray([]) + p = xp.asarray([]) + k = 1. + b, a = zpk2tf(z, p, k) + b_r = xp.asarray([1.]) # desired result + a_r = xp.asarray([1.]) # desired result + # The test for the *type* of the return values is a regression + # test for ticket #1095. In the case p=[], zpk2tf used to + # return the scalar 1.0 instead of array([1.0]). + xp_assert_equal(b, b_r) + xp_assert_equal(a, a_r) + if is_numpy(xp): + assert isinstance(b, np.ndarray) + assert isinstance(a, np.ndarray) + + @skip_xp_backends("dask.array", reason="https://github.com/dask/dask/issues/11883") + def test_conj_pair(self, xp): + # conjugate pairs give real-coeff num & den + z = xp.asarray([1j, -1j, 2j, -2j]) + z_np = _xp_copy_to_numpy(z) + # shouldn't need elements of pairs to be adjacent + p = xp.asarray([1+1j, 3-100j, 3+100j, 1-1j]) + p_np = _xp_copy_to_numpy(p) + + k = 23 + + # np.poly should do the right thing, but be explicit about + # taking real part + b_np = k * np.poly(z_np).real + a_np = np.poly(p_np).real + b, a = map(xp.asarray, (b_np, a_np)) + + bp, ap = zpk2tf(z, p, k) + + atol = 5e-13 if is_cupy(xp) else 0.0 + xp_assert_close(b, bp, atol=atol) + xp_assert_close(a, ap) + + assert xp.isdtype(bp.dtype, 'real floating') + assert xp.isdtype(ap.dtype, 'real floating') + + @skip_xp_backends("dask.array", reason="https://github.com/dask/dask/issues/11883") + def test_complexk(self, xp): + # regression: z, p real, k complex k gave real b, a + b, a = xp.asarray([1j, 1j]), xp.asarray([1.0, 2]) + b_np, a_np = map(_xp_copy_to_numpy, (b, a)) + z_np, p_np, k_np = tf2zpk(b_np, a_np) + z, p, k = map(xp.asarray, (z_np, p_np, k_np)) + xp_assert_close(k, xp.asarray(1j), check_0d=False) + bp, ap = zpk2tf(z, p, k) + xp_assert_close(b, bp) + xp_assert_close(a, ap) + + @skip_xp_backends("jax.numpy", + reason="zpk2tf not compatible with jax yet on multi-dim arrays") + @skip_xp_backends("cupy", + reason="multi-dim arrays not supported yet on cupy") + def test_zpk2tf_with_multi_dimensional_array(self, xp): + z = xp.asarray([[1, 2], [3, 4]]) # Multi-dimensional input + p = xp.asarray([1, 2]) + k = 1 + b, a = zpk2tf(z, p, k) + b_ref = xp.asarray([[1, -3, 2], [1, -7, 12]]) + a_ref = xp.asarray([1, -3, 2]) + xp_assert_close(b, b_ref, check_dtype=False) + xp_assert_close(a, a_ref, check_dtype=False) + + @skip_xp_backends("cupy", + reason="multi-dim arrays not supported yet on cupy") + def test_zpk2tf_int_truncation(self, xp): + # regression test for gh-24382 + z = xp.asarray([[ 1, 2.], [ 0., -1.]], dtype=xp.float64) + p = xp.asarray([3., 4.], dtype=xp.float64) + k = 2.5 + + b, a = zpk2tf(z, p, k) + + # reference values from scipy 1.15.3 + b_ref = xp.asarray([[ 2.5, -7.5, 5. ], [ 2.5, 2.5, 0. ]], dtype=xp.float64) + a_ref = xp.asarray([ 1., -7., 12.], dtype=xp.float64) + + xp_assert_close(b, b_ref, atol=1e-14) + xp_assert_close(a, a_ref, atol=1e-14) + + @skip_xp_backends("jax.numpy", + reason="zpk2tf not compatible with jax yet on multi-dim arrays") + @skip_xp_backends("cupy", + reason="multi-dim arrays not supported yet on cupy") + def test_zpk2tf_complex_k_multi_dim_z(self, xp): + # Regression test for gh-24395 + k = 1j + z = xp.asarray([[1., 2.], [0., -1.]]) + p = xp.asarray([3., 4.]) + + b, a = zpk2tf(z, p, k) + + b1, a1 = zpk2tf(z[0, :], p, k) + b2, a2 = zpk2tf(z[1, :], p, k) + + xp_assert_close(a, a1) + xp_assert_close(a, a2) + xp_assert_close(b[0, :], b1) + xp_assert_close(b[1, :], b2) + assert xp.isdtype(b.dtype, 'complex floating') + + +@make_xp_test_case(sos2zpk) +class TestSos2Zpk: + + @skip_xp_backends("dask.array", reason="it https://github.com/dask/dask/issues/11883") + @xfail_xp_backends("jax.numpy", reason="inaccurate with jax") + def test_basic(self, xp): + sos = [[1, 0, 1, 1, 0, -0.81], + [1, 0, 0, 1, 0, +0.49]] + sos = xp.asarray(sos) + z, p, k = sos2zpk(sos) + z2 = xp.asarray([1j, -1j, 0, 0]) + p2 = xp.asarray([0.9, -0.9, 0.7j, -0.7j]) + k2 = 1. + assert_array_almost_equal(_sort_cmplx(z, xp), _sort_cmplx(z2, xp), decimal=4) + assert_array_almost_equal(_sort_cmplx(p, xp), _sort_cmplx(p2, xp), decimal=4) + assert math.isclose(k, k2) + + sos = [[1.00000, +0.61803, 1.0000, 1.00000, +0.60515, 0.95873], + [1.00000, -1.61803, 1.0000, 1.00000, -1.58430, 0.95873], + [1.00000, +1.00000, 0.0000, 1.00000, +0.97915, 0.00000]] + sos = xp.asarray(sos) + z, p, k = sos2zpk(sos) + z2 = [-0.3090 + 0.9511j, -0.3090 - 0.9511j, 0.8090 + 0.5878j, + 0.8090 - 0.5878j, -1.0000 + 0.0000j, 0] + p2 = [-0.3026 + 0.9312j, -0.3026 - 0.9312j, 0.7922 + 0.5755j, + 0.7922 - 0.5755j, -0.9791 + 0.0000j, 0] + z2 = xp.asarray(z2) + p2 = xp.asarray(p2) + k2 = 1 + assert_array_almost_equal(_sort_cmplx(z, xp), _sort_cmplx(z2, xp), decimal=4) + assert_array_almost_equal(_sort_cmplx(p, xp), _sort_cmplx(p2, xp), decimal=4) + + sos = array([[1, 2, 3, 1, 0.2, 0.3], + [4, 5, 6, 1, 0.4, 0.5]]) + z = array([-1 - 1.41421356237310j, -1 + 1.41421356237310j, + -0.625 - 1.05326872164704j, -0.625 + 1.05326872164704j]) + p = array([-0.2 - 0.678232998312527j, -0.2 + 0.678232998312527j, + -0.1 - 0.538516480713450j, -0.1 + 0.538516480713450j]) + sos, z, p = map(xp.asarray, (sos, z, p)) + k = 4 + z2, p2, k2 = sos2zpk(sos) + + xp_assert_close(_sort_cmplx(z2, xp=xp), _sort_cmplx(z, xp=xp)) + xp_assert_close(_sort_cmplx(p2, xp=xp), _sort_cmplx(p, xp=xp)) + assert k2 == k + + def test_fewer_zeros(self, xp): + """Test not the expected number of p/z (effectively at origin).""" + sos = butter(3, 0.1, output='sos') + z, p, k = sos2zpk(xp.asarray(sos)) + assert z.shape[0] == 4 + assert p.shape[0] == 4 + + sos = butter(12, [5., 30.], 'bandpass', fs=1200., analog=False, output='sos') + e = BadCoefficients + if is_cupy(xp): + e = scipy_namespace_for(xp).signal.BadCoefficients + with pytest.warns(e, match='Badly conditioned'): + z, p, k = sos2zpk(xp.asarray(sos)) + assert z.shape[0] == 24 + assert p.shape[0] == 24 + + +@make_xp_test_case(sos2tf) +class TestSos2Tf: + + def test_basic(self, xp): + sos = [[1.0, 1, 1, 1, 0, -1], + [-2, 3, 1, 1, 10, 1]] + sos = xp.asarray(sos) + b, a = sos2tf(sos) + assert_array_almost_equal(b, xp.asarray([-2.0, 1, 2, 4, 1])) + assert_array_almost_equal(a, xp.asarray([1.0, 10, 0, -10, -1])) + + +@make_xp_test_case(tf2sos) +class TestTf2Sos: + + def test_basic(self, xp): + num = xp.asarray([2., 16, 44, 56, 32]) + den = xp.asarray([3., 3, -15, 18, -12]) + sos = tf2sos(num, den) + sos2 = [[0.6667, 4.0000, 5.3333, 1.0000, +2.0000, -4.0000], + [1.0000, 2.0000, 2.0000, 1.0000, -1.0000, +1.0000]] + sos2 = xp.asarray(sos2) + assert_array_almost_equal(sos, sos2, decimal=4) + + b = xp.asarray([1.0, -3, 11, -27, 18]) + a = xp.asarray([16.0, 12, 2, -4, -1]) + sos = tf2sos(b, a) + sos2 = [[0.0625, -0.1875, 0.1250, 1.0000, -0.2500, -0.1250], + [1.0000, +0.0000, 9.0000, 1.0000, +1.0000, +0.5000]] + sos2 = xp.asarray(sos2) + #assert_array_almost_equal(sos, sos2, decimal=4) + + @pytest.mark.parametrize('b, a, analog, sos', + [([1.0], [1.0], False, [[1., 0., 0., 1., 0., 0.]]), + ([1.0], [1.0], True, [[0., 0., 1., 0., 0., 1.]]), + ([1.0], [1., 0., -1.01, 0, 0.01], False, + [[1., 0., 0., 1., 0., -0.01], + [1., 0., 0., 1., 0., -1]]), + ([1.0], [1., 0., -1.01, 0, 0.01], True, + [[0., 0., 1., 1., 0., -1], + [0., 0., 1., 1., 0., -0.01]])]) + @skip_xp_backends("cupy", reason="https://github.com/cupy/cupy/issues/9376") + def test_analog(self, b, a, analog, sos, xp): + b, a, sos = map(xp.asarray, (b, a, sos)) + sos2 = tf2sos(b, a, analog=analog) + assert_array_almost_equal(sos, sos2, decimal=4) + + +@make_xp_test_case(zpk2sos) +class TestZpk2Sos: + +# @pytest.mark.parametrize('dt', 'fdgFDG') + # XXX: quietly remove float128 and complex256 + @pytest.mark.parametrize('dt', ['float32', 'float64', 'complex64', 'complex128']) + @pytest.mark.parametrize('pairing, analog', + [('nearest', False), + ('keep_odd', False), + ('minimal', False), + ('minimal', True)]) + def test_dtypes(self, dt, pairing, analog, xp): + dtype = getattr(xp, dt) + # the poles have to be complex + cdtype = (1j*xp.empty(0, dtype=dtype)).dtype + + z = xp.asarray([-1, -1], dtype=dtype) + p = xp.asarray([0.57149 + 0.29360j, 0.57149 - 0.29360j], dtype=cdtype) + k = xp.asarray(1, dtype=dtype) + sos = zpk2sos(z, p, k, pairing=pairing, analog=analog) + # octave & MATLAB + sos2 = xp.asarray([[1, 2, 1, 1, -1.14298, 0.41280]], dtype=dtype) + assert_array_almost_equal(sos, sos2, decimal=4) + + def test_basic(self, xp): + for pairing in ('nearest', 'keep_odd'): + # + # Cases that match octave + # + + z = xp.asarray([-1.0, -1.0]) + p = xp.asarray([0.57149 + 0.29360j, 0.57149 - 0.29360j]) + k = 1 + sos = zpk2sos(z, p, k, pairing=pairing) + sos2 = xp.asarray([[1, 2, 1, 1, -1.14298, 0.41280]]) # octave & MATLAB + assert_array_almost_equal(sos, sos2, decimal=4) + + z = xp.asarray([1j, -1j]) + p = xp.asarray([0.9, -0.9, 0.7j, -0.7j]) + k = 1 + sos = zpk2sos(z, p, k, pairing=pairing) + sos2 = [[1, 0, 1, 1, 0, +0.49], + [1, 0, 0, 1, 0, -0.81]] # octave + sos2 = xp.asarray(sos2) + # sos2 = [[0, 0, 1, 1, -0.9, 0], + # [1, 0, 1, 1, 0.9, 0]] # MATLAB + assert_array_almost_equal(sos, sos2, decimal=4) + + z = xp.asarray([]) + p = xp.asarray([0.8, -0.5+0.25j, -0.5-0.25j]) + k = 1. + sos = zpk2sos(z, p, k, pairing=pairing) + sos2 = [[1., 0., 0., 1., 1., 0.3125], + [1., 0., 0., 1., -0.8, 0.]] # octave, MATLAB fails + sos2 = xp.asarray(sos2) + assert_array_almost_equal(sos, sos2, decimal=4) + + z = xp.asarray([1., 1., 0.9j, -0.9j]) + p = xp.asarray([0.99+0.01j, 0.99-0.01j, 0.1+0.9j, 0.1-0.9j]) + k = 1 + sos = zpk2sos(z, p, k, pairing=pairing) + sos2 = [[1, 0, 0.81, 1, -0.2, 0.82], + [1, -2, 1, 1, -1.98, 0.9802]] # octave + sos2 = xp.asarray(sos2) + # sos2 = [[1, -2, 1, 1, -0.2, 0.82], + # [1, 0, 0.81, 1, -1.98, 0.9802]] # MATLAB + assert_array_almost_equal(sos, sos2, decimal=4) + + z = xp.asarray([0.9+0.1j, 0.9-0.1j, -0.9]) + p = xp.asarray([0.75+0.25j, 0.75-0.25j, 0.9]) + k = 1 + sos = zpk2sos(z, p, k, pairing=pairing) + if pairing == 'keep_odd': + sos2 = [[1, -1.8, 0.82, 1, -1.5, 0.625], + [1, 0.9, 0, 1, -0.9, 0]] # octave; MATLAB fails + sos2 = xp.asarray(sos2) + assert_array_almost_equal(sos, sos2, decimal=4) + else: # pairing == 'nearest' + sos2 = [[1, 0.9, 0, 1, -1.5, 0.625], + [1, -1.8, 0.82, 1, -0.9, 0]] # our algorithm + sos2 = xp.asarray(sos2) + assert_array_almost_equal(sos, sos2, decimal=4) + + # + # Cases that differ from octave: + # + + z = [-0.3090 + 0.9511j, -0.3090 - 0.9511j, 0.8090 + 0.5878j, + +0.8090 - 0.5878j, -1.0000 + 0.0000j] + p = [-0.3026 + 0.9312j, -0.3026 - 0.9312j, 0.7922 + 0.5755j, + +0.7922 - 0.5755j, -0.9791 + 0.0000j] + z = xp.asarray(z) + p = xp.asarray(p) + k = 1 + sos = zpk2sos(z, p, k, pairing=pairing) + # sos2 = [[1, 0.618, 1, 1, 0.6052, 0.95870], + # [1, -1.618, 1, 1, -1.5844, 0.95878], + # [1, 1, 0, 1, 0.9791, 0]] # octave, MATLAB fails + sos2 = [[1, 1, 0, 1, +0.97915, 0], + [1, 0.61803, 1, 1, +0.60515, 0.95873], + [1, -1.61803, 1, 1, -1.58430, 0.95873]] + sos2 = xp.asarray(sos2) + assert_array_almost_equal(sos, sos2, decimal=4) + + z = [-1 - 1.4142j, -1 + 1.4142j, + -0.625 - 1.0533j, -0.625 + 1.0533j] + p = [-0.2 - 0.6782j, -0.2 + 0.6782j, + -0.1 - 0.5385j, -0.1 + 0.5385j] + z = xp.asarray(z) + p = xp.asarray(p) + k = 4 + sos = zpk2sos(z, p, k, pairing=pairing) + sos2 = [[4, 8, 12, 1, 0.2, 0.3], + [1, 1.25, 1.5, 1, 0.4, 0.5]] # MATLAB + sos2 = xp.asarray(sos2, dtype=xp.float64) + # sos2 = [[4, 8, 12, 1, 0.4, 0.5], + # [1, 1.25, 1.5, 1, 0.2, 0.3]] # octave + xp_assert_close(sos, sos2, rtol=1e-4, atol=1e-4) + + z = xp.asarray([]) + p = xp.asarray([0.2, -0.5+0.25j, -0.5-0.25j]) + k = 1. + sos = zpk2sos(z, p, k, pairing=pairing) + sos2 = [[1., 0., 0., 1., -0.2, 0.], + [1., 0., 0., 1., 1., 0.3125]] + sos2 = xp.asarray(sos2) + # sos2 = [[1., 0., 0., 1., 1., 0.3125], + # [1., 0., 0., 1., -0.2, 0]] # octave, MATLAB fails + assert_array_almost_equal(sos, sos2, decimal=4) + + # The next two examples are adapted from Leland B. Jackson, + # "Digital Filters and Signal Processing (1995) p.400: + # http://books.google.com/books?id=VZ8uabI1pNMC&lpg=PA400&ots=gRD9pi8Jua&dq=Pole%2Fzero%20pairing%20for%20minimum%20roundoff%20noise%20in%20BSF.&pg=PA400#v=onepage&q=Pole%2Fzero%20pairing%20for%20minimum%20roundoff%20noise%20in%20BSF.&f=false + + deg2rad = xp.pi / 180. + k = 1. + + # first example + thetas = xp.asarray([22.5, 45, 77.5]) + mags = xp.asarray([0.8, 0.6, 0.9]) + z = xp.exp(1j * deg2rad * thetas) + z = xp.concat((z, xp.conj(z))) + p = xp.exp(1j * deg2rad * thetas) * mags + p = xp.concat((p, xp.conj(p))) + sos = zpk2sos(z, p, k) + # sos2 = [[1, -0.43288, 1, 1, -0.38959, 0.81], # octave, + # [1, -1.41421, 1, 1, -0.84853, 0.36], # MATLAB fails + # [1, -1.84776, 1, 1, -1.47821, 0.64]] + # Note that pole-zero pairing matches, but ordering is different + sos2 = [[1, -1.41421, 1, 1, -0.84853, 0.36], + [1, -1.84776, 1, 1, -1.47821, 0.64], + [1, -0.43288, 1, 1, -0.38959, 0.81]] + sos2 = xp.asarray(sos2) + assert_array_almost_equal(sos, sos2, decimal=4) + + # second example + thetas = xp.asarray([85., 10.]) + z = xp.exp(1j * deg2rad * thetas) + z = xp.concat((z, xp.conj(z), xp.asarray([1.0, -1.0]))) + sos = zpk2sos(z, p, k) + + # sos2 = [[1, -0.17431, 1, 1, -0.38959, 0.81], # octave "wrong", + # [1, -1.96962, 1, 1, -0.84853, 0.36], # MATLAB fails + # [1, 0, -1, 1, -1.47821, 0.64000]] + # Our pole-zero pairing matches the text, Octave does not + sos2 = [[1, 0, -1, 1, -0.84853, 0.36], + [1, -1.96962, 1, 1, -1.47821, 0.64], + [1, -0.17431, 1, 1, -0.38959, 0.81]] + sos2 = xp.asarray(sos2) + assert_array_almost_equal(sos, sos2, decimal=4) + + # these examples are taken from the doc string, and show the + # effect of the 'pairing' argument + @pytest.mark.parametrize('pairing, sos', + [('nearest', + np.array([[1., 1., 0.5, 1., -0.75, 0.], + [1., 1., 0., 1., -1.6, 0.65]])), + ('keep_odd', + np.array([[1., 1., 0, 1., -0.75, 0.], + [1., 1., 0.5, 1., -1.6, 0.65]])), + ('minimal', + np.array([[0., 1., 1., 0., 1., -0.75], + [1., 1., 0.5, 1., -1.6, 0.65]]))]) + def test_pairing(self, pairing, sos, xp): + sos = xp.asarray(sos) + z1 = xp.asarray([-1, -0.5-0.5j, -0.5+0.5j]) + p1 = xp.asarray([0.75, 0.8+0.1j, 0.8-0.1j]) + sos2 = zpk2sos(z1, p1, 1, pairing=pairing) + assert_array_almost_equal(sos, sos2, decimal=4) + + @pytest.mark.parametrize('p, sos_dt', + [([-1, 1, -0.1, 0.1], + [[0., 0., 1., 1., 0., -0.01], + [0., 0., 1., 1., 0., -1]]), + ([-0.7071+0.7071j, -0.7071-0.7071j, -0.1j, 0.1j], + [[0., 0., 1., 1., 0., 0.01], + [0., 0., 1., 1., 1.4142, 1.]])]) + def test_analog(self, p, sos_dt, xp): + # test `analog` argument + # for discrete time, poles closest to unit circle should appear last + # for cont. time, poles closest to imaginary axis should appear last + z, p = xp.asarray([]), xp.asarray(p) + sos_dt = xp.asarray(sos_dt) + sos2_dt = zpk2sos(z, p, 1, pairing='minimal', analog=False) + sos2_ct = zpk2sos(z, p, 1, pairing='minimal', analog=True) + assert_array_almost_equal(sos_dt, sos2_dt, decimal=4) + assert_array_almost_equal(xp.flip(sos_dt, axis=0), sos2_ct, decimal=4) + + def test_bad_args(self): + with pytest.raises(ValueError, match=r'pairing must be one of'): + zpk2sos([1], [2], 1, pairing='no_such_pairing') + + with pytest.raises(ValueError, match=r'.*pairing must be "minimal"'): + zpk2sos([1], [2], 1, pairing='keep_odd', analog=True) + + with pytest.raises(ValueError, + match=r'.*must have len\(p\)>=len\(z\)'): + zpk2sos([1, 1], [2], 1, analog=True) + + with pytest.raises(ValueError, match=r'k must be real'): + zpk2sos([1], [2], k=1j) + + +@make_xp_test_case(freqs) +class TestFreqs: + + def test_basic(self, xp): + _, h = freqs(xp.asarray([1.0]), xp.asarray([1.0]), worN=8) + assert_array_almost_equal(h, xp.ones(8)) + + def test_output(self, xp): + # 1st order low-pass filter: H(s) = 1 / (s + 1) + w = xp.asarray([0.1, 1, 10, 100]) + num = xp.asarray([1.]) + den = xp.asarray([1, 1.]) + w, H = freqs(num, den, worN=w) + s = w * 1j + expected = 1 / (s + 1) + assert_array_almost_equal(xp.real(H), xp.real(expected)) + assert_array_almost_equal(xp.imag(H), xp.imag(expected)) + + def test_freq_range(self, xp): + # Test that freqresp() finds a reasonable frequency range. + # 1st order low-pass filter: H(s) = 1 / (s + 1) + # Expected range is from 0.01 to 10. + num = xp.asarray([1.]) + den = xp.asarray([1, 1.]) + n = 10 + expected_w = _logspace(-2, 1, n, xp=xp) + w, H = freqs(num, den, worN=n) + assert_array_almost_equal(w, expected_w) + + def test_plot(self, xp): + + def plot(w, h): + assert_array_almost_equal(h, xp.ones(8)) + + with assert_raises(ZeroDivisionError): + freqs([1.0], [1.0], worN=8, plot=lambda w, h: 1 / 0) + + freqs(xp.asarray([1.0]), xp.asarray([1.0]), worN=8, plot=plot) + + def test_backward_compat(self, xp): + # For backward compatibility, test if None act as a wrapper for default + w1, h1 = freqs(xp.asarray([1.0]), xp.asarray([1.0])) + w2, h2 = freqs(xp.asarray([1.0]), xp.asarray([1.0]), None) + assert_array_almost_equal(w1, w2) + assert_array_almost_equal(h1, h2) + + def test_w_or_N_types(self): + # Measure at 8 equally-spaced points + for N in (8, np.int8(8), np.int16(8), np.int32(8), np.int64(8), + np.array(8)): + w, h = freqs([1.0], [1.0], worN=N) + assert len(w) == 8 + assert_array_almost_equal(h, np.ones(8)) + + # Measure at frequency 8 rad/sec + for w in (8.0, 8.0+0j): + w_out, h = freqs([1.0], [1.0], worN=w) + assert_array_almost_equal(w_out, [8]) + assert_array_almost_equal(h, [1]) + + +@make_xp_test_case(freqs_zpk) +class TestFreqs_zpk: + + def test_basic(self, xp): + _, h = freqs_zpk( + xp.asarray([1.0]), xp.asarray([1.0]), xp.asarray([1.0]), worN=8 + ) + assert_array_almost_equal(h, xp.ones(8)) + + def test_output(self, xp): + # 1st order low-pass filter: H(s) = 1 / (s + 1) + w = xp.asarray([0.1, 1, 10, 100]) + z = xp.asarray([]) + p = xp.asarray([-1.0]) + k = 1 + w, H = freqs_zpk(z, p, k, worN=w) + s = w * 1j + expected = 1 / (s + 1) + assert_array_almost_equal(xp.real(H), xp.real(expected)) + assert_array_almost_equal(xp.imag(H), xp.imag(expected)) + + def test_freq_range(self, xp): + # Test that freqresp() finds a reasonable frequency range. + # 1st order low-pass filter: H(s) = 1 / (s + 1) + # Expected range is from 0.01 to 10. + z = xp.asarray([]) + p = xp.asarray([-1.]) + k = 1 + n = 10 + expected_w = _logspace(-2, 1, n, xp=xp) + w, H = freqs_zpk(z, p, k, worN=n) + assert_array_almost_equal(w, expected_w) + + def test_vs_freqs(self, xp): + b, a = cheby1(4, 5, 100., analog=True, output='ba') + z, p, k = cheby1(4, 5, 100., analog=True, output='zpk') + + w1, h1 = map(xp.asarray, freqs(b, a)) + z, p, k = map(xp.asarray, (z, p, k)) + w2, h2 = freqs_zpk(z, p, k) + xp_assert_close(w1, w2) + xp_assert_close(h1, h2, rtol=1e-6) + + def test_backward_compat(self, xp): + # For backward compatibility, test if None act as a wrapper for default + # Also, keep testing `k` a length-one list: it is documented as a scalar, + # but the implementation was allowing for a one-element array-likes + w1, h1 = freqs_zpk(xp.asarray([1.0]), xp.asarray([1.0]), [1.0]) + w2, h2 = freqs_zpk(xp.asarray([1.0]), xp.asarray([1.0]), [1.0], None) + assert_array_almost_equal(w1, w2) + assert_array_almost_equal(h1, h2) + + def test_w_or_N_types(self): + # Measure at 8 equally-spaced points + for N in (8, np.int8(8), np.int16(8), np.int32(8), np.int64(8), + np.array(8)): + w, h = freqs_zpk([], [], 1, worN=N) + assert len(w) == 8 + assert_array_almost_equal(h, np.ones(8)) + + # Measure at frequency 8 rad/sec + for w in (8.0, 8.0+0j): + w_out, h = freqs_zpk([], [], 1, worN=w) + assert_array_almost_equal(w_out, [8]) + assert_array_almost_equal(h, [1]) + + +@make_xp_test_case(freqz) +class TestFreqz: + + def test_ticket1441(self, xp): + """Regression test for ticket 1441.""" + # Because freqz previously used arange instead of linspace, + # when N was large, it would return one more point than + # requested. + N = 100000 + w, h = freqz(xp.asarray([1.0]), worN=N) + assert w.shape == (N,) + + def test_gh_22886(self, xp): + w, h = freqz(xp.asarray([1.]), worN=xp.asarray([0., 0.1])) + xp_assert_equal(w, xp.asarray([0. , 0.1])) + xp_assert_equal(h, xp.asarray([1.+0.j, 1.+0.j])) + + def test_basic(self, xp): + w, h = freqz(xp.asarray([1.0]), worN=8) + assert_array_almost_equal(w, xp.pi * xp.arange(8, dtype=w.dtype) / 8.) + assert_array_almost_equal(h, xp.ones(8)) + w, h = freqz(xp.asarray([1.0]), worN=9) + assert_array_almost_equal(w, xp.pi * xp.arange(9, dtype=w.dtype) / 9.) + assert_array_almost_equal(h, xp.ones(9)) + + for a in [1, xp.ones(2)]: + w, h = freqz(xp.ones(2), a, worN=0) + assert w.shape == (0,) + assert h.shape == (0,) + hdt = xp.complex128 if xp_default_dtype(xp) == xp.float64 else xp.complex64 + assert h.dtype == hdt + + def test_basic2(self, xp): + t = xp.linspace(0, 1, 4, endpoint=False) + for b, a, h_whole in zip( + (xp.asarray([1., 0, 0, 0]), xp.sin(2 * xp.pi * t)), + (xp.asarray([1., 0, 0, 0]), xp.asarray([0.5, 0, 0, 0])), + (xp.asarray([1., 1., 1., 1.]), xp.asarray([0, -4j, 0, 4j])) + ): + + w, h = freqz(b, a, worN=4, whole=True) + expected_w = xp.linspace(0, 2 * xp.pi, 4, endpoint=False) + assert_array_almost_equal(w, expected_w) + assert_array_almost_equal(h, h_whole) + + if is_numpy(xp): + # simultaneously check int-like support + w, h = freqz(b, a, worN=np.int32(4), whole=True) + assert_array_almost_equal(w, expected_w) + assert_array_almost_equal(h, h_whole) + + w, h = freqz(b, a, worN=w, whole=True) + assert_array_almost_equal(w, expected_w) + assert_array_almost_equal(h, h_whole) + + def test_basic3(self): + t = np.linspace(0, 1, 4, endpoint=False) + expected_w = np.linspace(0, 2 * np.pi, 4, endpoint=False) + for b, a, h_whole in zip( + (np.asarray([1., 0, 0, 0]), np.sin(2 * np.pi * t)), + (np.asarray([1., 0, 0, 0]), np.asarray([0.5, 0, 0, 0])), + (np.asarray([1., 1., 1., 1.]), np.asarray([0, -4j, 0, 4j])) + ): + + w, h = freqz(b, a, worN=np.int32(4), whole=True) + assert_array_almost_equal(w, expected_w) + assert_array_almost_equal(h, h_whole) + + w, h = freqz(b, a, worN=w, whole=True) + assert_array_almost_equal(w, expected_w) + assert_array_almost_equal(h, h_whole) + + def test_basic_whole(self, xp): + w, h = freqz(xp.asarray([1.0]), worN=8, whole=True) + assert_array_almost_equal(w, 2 * xp.pi * xp.arange(8.0) / 8) + assert_array_almost_equal(h, xp.ones(8)) + + def test_plot(self, xp): + + def plot(w, h): + assert_array_almost_equal(w, xp.pi * xp.arange(8.0) / 8) + assert_array_almost_equal(h, xp.ones(8)) + + with assert_raises(ZeroDivisionError): + freqz(xp.asarray([1.0]), worN=8, plot=lambda w, h: 1 / 0) + + freqz(xp.asarray([1.0]), worN=8, plot=plot) + + def test_fft_wrapping(self, xp): + # Some simple real FIR filters + bs = list() # filters + as_ = list() + hs_whole = list() + hs_half = list() + # 3 taps + t = xp.linspace(0, 1, 3, endpoint=False) + bs.append(xp.sin(2 * xp.pi * t)) + as_.append(3.) + hs_whole.append(xp.asarray([0, -0.5j, 0.5j])) + hs_half.append(xp.asarray([0, math.sqrt(1./12.), -0.5j])) + # 4 taps + t = xp.linspace(0, 1, 4, endpoint=False) + bs.append(xp.sin(2 * xp.pi * t)) + as_.append(0.5) + hs_whole.append(xp.asarray([0, -4j, 0, 4j])) + hs_half.append(xp.asarray([0, math.sqrt(8), -4j, -math.sqrt(8)])) + del t + for ii, b in enumerate(bs): + # whole + a = as_[ii] + expected_w = xp.linspace(0, 2 * xp.pi, b.shape[0], endpoint=False) + w, h = freqz(b, a, worN=expected_w, whole=True) # polyval + err_msg = f'b = {b}, a={a}' + assert_array_almost_equal(w, expected_w, err_msg=err_msg) + assert_array_almost_equal(h, hs_whole[ii], err_msg=err_msg) + + w, h = freqz(b, a, worN=b.shape[0], whole=True) # FFT + assert_array_almost_equal(w, expected_w, err_msg=err_msg) + assert_array_almost_equal(h, hs_whole[ii], err_msg=err_msg) + + # non-whole + expected_w = xp.linspace(0, xp.pi, b.shape[0], endpoint=False) + w, h = freqz(b, a, worN=expected_w, whole=False) # polyval + assert_array_almost_equal(w, expected_w, err_msg=err_msg) + assert_array_almost_equal(h, hs_half[ii], err_msg=err_msg) + + w, h = freqz(b, a, worN=b.shape[0], whole=False) # FFT + assert_array_almost_equal(w, expected_w, err_msg=err_msg) + assert_array_almost_equal(h, hs_half[ii], err_msg=err_msg) + + # some random FIR filters (real + complex) + # assume polyval is accurate + rng = np.random.RandomState(0) + for ii in range(2, 10): # number of taps + b = xp.asarray(rng.randn(ii)) + for kk in range(2): + a = xp.asarray(rng.randn(1) if kk == 0 else rng.randn(3)) + for jj in range(2): + if jj == 1: + b = b + xp.asarray(rng.randn(ii)) * 1j + + # whole + expected_w = xp.linspace(0, 2 * xp.pi, ii, endpoint=False) + w, expected_h = freqz(b, a, worN=expected_w, whole=True) + assert_array_almost_equal(w, expected_w) + w, h = freqz(b, a, worN=ii, whole=True) + assert_array_almost_equal(w, expected_w) + assert_array_almost_equal(h, expected_h, decimal=4) + + # half + expected_w = xp.linspace(0, xp.pi, ii, endpoint=False) + w, expected_h = freqz(b, a, worN=expected_w, whole=False) + assert_array_almost_equal(w, expected_w) + w, h = freqz(b, a, worN=ii, whole=False) + assert_array_almost_equal(w, expected_w) + assert_array_almost_equal(h, expected_h, decimal=4) + + def test_broadcasting1(self, xp): + # Test broadcasting with worN an integer or a 1-D array, + # b and a are n-dimensional arrays. + np.random.seed(123) + b = np.random.rand(3, 5, 1) + a = np.random.rand(2, 1) + b, a = map(xp.asarray, (b, a)) + + for whole in [False, True]: + # Test with worN being integers (one fast for FFT and one not), + # a 1-D array, and an empty array. + for worN in [16, 17, xp.linspace(0, 1, 10), xp.asarray([])]: + w, h = freqz(b, a, worN=worN, whole=whole) + for k in range(b.shape[1]): + bk = b[:, k, 0] + ak = a[:, 0] + ww, hh = freqz(bk, ak, worN=worN, whole=whole) + xp_assert_close(ww, w) + xp_assert_close(hh, h[k, ...]) + + def test_broadcasting2(self, xp): + # Test broadcasting with worN an integer or a 1-D array, + # b is an n-dimensional array, and a is left at the default value. + np.random.seed(123) + b = np.random.rand(3, 5, 1) + b = xp.asarray(b) + for whole in [False, True]: + for worN in [16, 17, xp.linspace(0, 1, 10)]: + w, h = freqz(b, worN=worN, whole=whole) + for k in range(b.shape[1]): + bk = b[:, k, 0] + ww, hh = freqz(bk, worN=worN, whole=whole) + xp_assert_close(ww, w) + xp_assert_close(hh, h[k, :]) + + def test_broadcasting3(self, xp): + # Test broadcasting where b.shape[-1] is the same length + # as worN, and a is left at the default value. + np.random.seed(123) + N = 16 + b = np.random.rand(3, N) + b = xp.asarray(b) + for whole in [False, True]: + for worN in [N, xp.linspace(0, 1, N)]: + w, h = freqz(b, worN=worN, whole=whole) + assert xp_size(w) == N + for k in range(N): + bk = b[:, k] + ww, hh = freqz(bk, worN=w[k], whole=whole) + xp_assert_close(ww, xp.asarray(w[k])[None]) + xp_assert_close(hh, xp.asarray(h[k])[None]) + + def test_broadcasting4(self, xp): + # Test broadcasting with worN a 2-D array. + np.random.seed(123) + b = np.random.rand(4, 2, 1, 1) + a = np.random.rand(5, 2, 1, 1) + b, a = map(xp.asarray, (b, a)) + + for whole in [False, True]: + for worN in [np.random.rand(6, 7), np.empty((6, 0))]: + worN = xp.asarray(worN) + w, h = freqz(b, a, worN=worN, whole=whole) + xp_assert_close(w, worN, rtol=1e-14) + assert h.shape == (2,) + worN.shape + for k in range(2): + ww, hh = freqz(b[:, k, 0, 0], a[:, k, 0, 0], + worN=xp.reshape(worN, (-1,)), + whole=whole) + xp_assert_close(ww, xp.reshape(worN, (-1,)), rtol=1e-14) + xp_assert_close(hh, xp.reshape(h[k, :, :], (-1,))) + + def test_backward_compat(self): + # For backward compatibility, test if None act as a wrapper for default + w1, h1 = freqz([1.0], 1) + w2, h2 = freqz([1.0], 1, None) + assert_array_almost_equal(w1, w2) + assert_array_almost_equal(h1, h2) + + def test_fs_param(self, xp): + fs = 900 + b = xp.asarray([0.039479155677484369, 0.11843746703245311, 0.11843746703245311, + 0.039479155677484369]) + a = xp.asarray([1.0, -1.3199152021838287, 0.80341991081938424, + -0.16767146321568049]) + + # N = None, whole=False + w1, h1 = freqz(b, a, fs=fs) + w2, h2 = freqz(b, a) + xp_assert_close(h1, h2) + xp_assert_close(w1, xp.linspace(0, fs/2, 512, endpoint=False)) + + # N = None, whole=True + w1, h1 = freqz(b, a, whole=True, fs=fs) + w2, h2 = freqz(b, a, whole=True) + xp_assert_close(h1, h2) + xp_assert_close(w1, xp.linspace(0, fs, 512, endpoint=False)) + + # N = 5, whole=False + w1, h1 = freqz(b, a, 5, fs=fs) + w2, h2 = freqz(b, a, 5) + xp_assert_close(h1, h2) + xp_assert_close(w1, xp.linspace(0, fs/2, 5, endpoint=False)) + + # N = 5, whole=True + w1, h1 = freqz(b, a, 5, whole=True, fs=fs) + w2, h2 = freqz(b, a, 5, whole=True) + xp_assert_close(h1, h2) + xp_assert_close(w1, xp.linspace(0, fs, 5, endpoint=False)) + + if is_numpy(xp): + # w is an array_like + for w in ([123], (123,), xp.asarray([123]), (50, 123, 230), + xp.asarray([50, 123, 230])): + w1, h1 = freqz(b, a, w, fs=fs) + w2, h2 = freqz(b, a, 2*pi*xp.asarray(w, dtype=xp.float64)/ fs) + xp_assert_close(h1, h2) + xp_assert_close(w1, xp.asarray(w), check_dtype=False) + + def test_w_or_N_types(self): + # Measure at 7 (polyval) or 8 (fft) equally-spaced points + for N in (7, np.int8(7), np.int16(7), np.int32(7), np.int64(7), + np.array(7), + 8, np.int8(8), np.int16(8), np.int32(8), np.int64(8), + np.array(8)): + + w, h = freqz([1.0], worN=N) + assert_array_almost_equal(w, np.pi * np.arange(N) / N) + assert_array_almost_equal(h, np.ones(N)) + + w, h = freqz([1.0], worN=N, fs=100) + assert_array_almost_equal(w, np.linspace(0, 50, N, endpoint=False)) + assert_array_almost_equal(h, np.ones(N)) + + # Measure at frequency 8 Hz + for w in (8.0, 8.0+0j): + # Only makes sense when fs is specified + w_out, h = freqz(np.asarray([1.0]), worN=w, fs=100) + assert_array_almost_equal(w_out, np.asarray([8])) + assert_array_almost_equal(h, np.asarray(1.), check_0d=False) + + def test_nyquist(self, xp): + w, h = freqz(xp.asarray([1.0]), worN=8, include_nyquist=True) + assert_array_almost_equal(w, xp.pi * xp.arange(8, dtype=w.dtype) / 7.) + assert_array_almost_equal(h, xp.ones(8)) + w, h = freqz(xp.asarray([1.0]), worN=9, include_nyquist=True) + assert_array_almost_equal(w, xp.pi * xp.arange(9, dtype=w.dtype) / 8.) + assert_array_almost_equal(h, xp.ones(9)) + + for a in [1, xp.ones(2)]: + w, h = freqz(xp.ones(2), a, worN=0, include_nyquist=True) + assert w.shape == (0,) + assert h.shape == (0,) + hdt = xp.complex128 if xp_default_dtype(xp) == xp.float64 else xp.complex64 + assert h.dtype == hdt + + w1, h1 = freqz(xp.asarray([1.0]), worN=8, whole = True, include_nyquist=True) + w2, h2 = freqz(xp.asarray([1.0]), worN=8, whole = True, include_nyquist=False) + assert_array_almost_equal(w1, w2) + assert_array_almost_equal(h1, h2) + + # https://github.com/scipy/scipy/issues/17289 + # https://github.com/scipy/scipy/issues/15273 + @pytest.mark.parametrize('whole,nyquist,worN', + [(False, False, 32), + (False, True, 32), + (True, False, 32), + (True, True, 32), + (False, False, 257), + (False, True, 257), + (True, False, 257), + (True, True, 257)]) + + @xfail_xp_backends("cupy", reason="XXX: CuPy's version suspect") + def test_17289(self, whole, nyquist, worN, xp): + d = xp.asarray([0.0, 1.0]) + w, Drfft = freqz(d, worN=32, whole=whole, include_nyquist=nyquist) + _, Dpoly = freqz(d, worN=w) + xp_assert_close(Drfft, Dpoly) + + def test_fs_validation(self): + with pytest.raises(ValueError, match="Sampling.*single scalar"): + freqz([1.0], fs=np.array([10, 20])) + + with pytest.raises(ValueError, match="Sampling.*be none."): + freqz([1.0], fs=None) + + + +@make_xp_test_case(freqz_sos) +class TestFreqz_sos: + + def test_freqz_sos_basic(self, xp): + # Compare the results of freqz and freqz_sos for a low order + # Butterworth filter. + + N = 500 + + b, a = butter(4, 0.2) + sos = butter(4, 0.2, output='sos') + w, h = freqz(b, a, worN=N) + + w, h, sos = map(xp.asarray, (w, h, sos)) + w2, h2 = freqz_sos(sos, worN=N) + xp_assert_close(w2, w, rtol=1e-15) + xp_assert_close(h2, h, rtol=1e-10, atol=1e-14) + + b, a = ellip(3, 1, 30, (0.2, 0.3), btype='bandpass') + sos = ellip(3, 1, 30, (0.2, 0.3), btype='bandpass', output='sos') + w, h = freqz(b, a, worN=N) + + w, h, sos = map(xp.asarray, (w, h, sos)) + w2, h2 = freqz_sos(sos, worN=N) + xp_assert_close(w2, w, rtol=1e-15) + xp_assert_close(h2, h, rtol=1e-10, atol=1e-14) + + # must have at least one section + with assert_raises(ValueError): + freqz_sos(sos[:0, ...]) + + @make_xp_test_case(sosfreqz) + def test_backward_compat(self, xp): + # For backward compatibility, test if None act as a wrapper for default + N = 500 + + sos = butter(4, 0.2, output='sos') + w2, h2 = sosfreqz(sos, worN=N) + sos, w2, h2 = map(xp.asarray, (sos, w2, h2)) + w1, h1 = freqz_sos(sos, worN=N) + assert_array_almost_equal(w1, w2) + assert_array_almost_equal(h1, h2) + + @skip_xp_backends("dask.array", reason="float cannot be interpreted as an integer") + def test_freqz_sos_design(self, xp): + # Compare freqz_sos output against expected values for different + # filter types + # TODO: split into multiple tests, or parameterize across filter types + # in some way. + + # from cheb2ord + N, Wn = cheb2ord([0.1, 0.6], [0.2, 0.5], 3, 60) + sos = cheby2(N, 60, Wn, 'stop', output='sos') + sos = xp.asarray(sos) # XXX + zero = xp.asarray(0., dtype=xp.float64) + + w, h = freqz_sos(sos) + h = xp.abs(h) + w = w / xp.pi + xp_assert_close(20 * xp.log10(h[w <= 0.1]), + zero, atol=3.01, + check_shape=False) + xp_assert_close(20 * xp.log10(h[w >= 0.6]), + zero, atol=3.01, + check_shape=False) + xp_assert_close(h[(w >= 0.2) & (w <= 0.5)], + zero, atol=1e-3, + check_shape=False) # <= -60 dB + + N, Wn = cheb2ord([0.1, 0.6], [0.2, 0.5], 3, 150) + sos = cheby2(N, 150, Wn, 'stop', output='sos') + sos = xp.asarray(sos) + + w, h = freqz_sos(sos) + dB = 20*xp.log10(xp.abs(h)) + w = w / xp.pi + xp_assert_close(dB[w <= 0.1], zero, atol=3.01, check_shape=False) + xp_assert_close(dB[w >= 0.6], zero, atol=3.01, check_shape=False) + assert xp.all(dB[(w >= 0.2) & (w <= 0.5)] < -149.9) + + # from cheb1ord + N, Wn = cheb1ord(0.2, 0.3, 3, 40) + sos = cheby1(N, 3, Wn, 'low', output='sos') + sos = xp.asarray(sos) + + w, h = freqz_sos(sos) + h = xp.abs(h) + w = w / xp.pi + xp_assert_close(20 * xp.log10(h[w <= 0.2]), zero, atol=3.01, + check_shape=False) + xp_assert_close(h[w >= 0.3], zero, atol=1e-2, + check_shape=False) # <= -40 dB + + N, Wn = cheb1ord(0.2, 0.3, 1, 150) + sos = cheby1(N, 1, Wn, 'low', output='sos') + sos = xp.asarray(sos) + + w, h = freqz_sos(sos) + dB = 20*xp.log10(xp.abs(h)) + w /= np.pi + xp_assert_close(dB[w <= 0.2], zero, atol=1.01, check_shape=False) + assert xp.all(dB[w >= 0.3] < -149.9) + + # adapted from ellipord + N, Wn = ellipord(0.3, 0.2, 3, 60) + sos = ellip(N, 0.3, 60, Wn, 'high', output='sos') + sos = xp.asarray(sos) + + w, h = freqz_sos(sos) + h = xp.abs(h) + w = w / xp.pi + xp_assert_close(20 * xp.log10(h[w >= 0.3]), zero, atol=3.01, + check_shape=False) + xp_assert_close(h[w <= 0.1], zero, atol=1.5e-3, + check_shape=False) # <= -60 dB (approx) + + # adapted from buttord + N, Wn = buttord([0.2, 0.5], [0.14, 0.6], 3, 40) + sos = butter(N, Wn, 'band', output='sos') + sos = xp.asarray(sos) + + w, h = freqz_sos(sos) + h = xp.abs(h) + w = w / xp.pi + + h014 = h[w <= 0.14] + xp_assert_close(h014, xp.zeros_like(h014), atol=1e-2) # <= -40 dB + h06 = h[w >= 0.6] + xp_assert_close(h06, xp.zeros_like(h06), atol=1e-2) # <= -40 dB + h0205 = 20 * xp.log10(h[(w >= 0.2) & (w <= 0.5)]) + xp_assert_close(h0205, xp.zeros_like(h0205), atol=3.01) + + N, Wn = buttord([0.2, 0.5], [0.14, 0.6], 3, 100) + sos = butter(N, Wn, 'band', output='sos') + sos = xp.asarray(sos) + + w, h = freqz_sos(sos) + dB = 20*xp.log10(xp.maximum(xp.abs(h), xp.asarray(1e-10))) + w = w / xp.pi + + assert xp.all(dB[(w > 0) & (w <= 0.14)] < -99.9) + assert xp.all(dB[w >= 0.6] < -99.9) + db0205 = dB[(w >= 0.2) & (w <= 0.5)] + xp_assert_close(db0205, xp.zeros_like(db0205), atol=3.01) + + def test_freqz_sos_design_ellip(self, xp): + N, Wn = ellipord(0.3, 0.1, 3, 60) + sos = ellip(N, 0.3, 60, Wn, 'high', output='sos') + sos = xp.asarray(sos) + + w, h = freqz_sos(sos) + h = xp.abs(h) + w = w / xp.pi + + h03 = 20 * xp.log10(h[w >= 0.3]) + xp_assert_close(h03, xp.zeros_like(h03), atol=3.01) + h01 = h[w <= 0.1] + xp_assert_close(h01, xp.zeros_like(h01), atol=1.5e-3) # <= -60 dB (approx) + + N, Wn = ellipord(0.3, 0.2, .5, 150) + sos = ellip(N, .5, 150, Wn, 'high', output='sos') + sos = xp.asarray(sos) + + w, h = freqz_sos(sos) + dB = 20*xp.log10(xp.maximum(xp.abs(h), xp.asarray(1e-10))) + w = w / xp.pi + + db03 = dB[w >= 0.3] + xp_assert_close(db03, xp.zeros_like(db03), atol=.55) + # Allow some numerical slop in the upper bound -150, so this is + # a check that dB[w <= 0.2] is less than or almost equal to -150. + assert xp.max(dB[w <= 0.2]) < -150*(1 - 1e-12) + + @pytest.mark.thread_unsafe( + reason=("mpmath gmpy2 backend is not thread-safe, " + "see https://github.com/mpmath/mpmath/issues/974")) + @mpmath_check("0.10") + def test_freqz_sos_against_mp(self, xp): + # Compare the result of freqz_sos applied to a high order Butterworth + # filter against the result computed using mpmath. (signal.freqz fails + # miserably with such high order filters.) + from . import mpsig + N = 500 + order = 25 + Wn = 0.15 + with mpmath.workdps(80): + z_mp, p_mp, k_mp = mpsig.butter_lp(order, Wn) + w_mp, h_mp = mpsig.zpkfreqz(z_mp, p_mp, k_mp, N) + w_mp = xp.asarray([float(x) for x in w_mp], dtype=xp.float64) + h_mp = xp.asarray([complex(x) for x in h_mp], dtype=xp.complex128) + + sos = butter(order, Wn, output='sos') + sos = xp.asarray(sos, dtype=xp.float64) + w, h = freqz_sos(sos, worN=N) + xp_assert_close(w, w_mp, rtol=1e-12, atol=1e-14) + xp_assert_close(h, h_mp, rtol=1e-12, atol=1e-14) + + def test_fs_param(self, xp): + fs = 900 + sos = xp.asarray( + [[0.03934683014103762, 0.07869366028207524, 0.03934683014103762, + 1.0, -0.37256600288916636, 0.0], + [1.0, 1.0, 0.0, 1.0, -0.9495739996946778, 0.45125966317124144]] + ) + + # N = None, whole=False + w1, h1 = freqz_sos(sos, fs=fs) + w2, h2 = freqz_sos(sos) + xp_assert_close(h1, h2) + xp_assert_close(w1, xp.linspace(0, fs/2, 512, endpoint=False)) + + # N = None, whole=True + w1, h1 = freqz_sos(sos, whole=True, fs=fs) + w2, h2 = freqz_sos(sos, whole=True) + xp_assert_close(h1, h2, atol=1e-27) + xp_assert_close(w1, xp.linspace(0, fs, 512, endpoint=False)) + + # N = 5, whole=False + w1, h1 = freqz_sos(sos, 5, fs=fs) + w2, h2 = freqz_sos(sos, 5) + xp_assert_close(h1, h2) + xp_assert_close(w1, xp.linspace(0, fs/2, 5, endpoint=False)) + + # N = 5, whole=True + w1, h1 = freqz_sos(sos, 5, whole=True, fs=fs) + w2, h2 = freqz_sos(sos, 5, whole=True) + xp_assert_close(h1, h2) + xp_assert_close(w1, xp.linspace(0, fs, 5, endpoint=False)) + + @skip_xp_backends(np_only=True, reason="array-likes") + def test_fs_param2(self, xp): + fs = 900 + sos = xp.asarray( + [[0.03934683014103762, 0.07869366028207524, 0.03934683014103762, + 1.0, -0.37256600288916636, 0.0], + [1.0, 1.0, 0.0, 1.0, -0.9495739996946778, 0.45125966317124144]] + ) + + # w is an array_like + for w in ([123], (123,), xp.asarray([123]), (50, 123, 230), + xp.asarray([50, 123, 230])): + w1, h1 = freqz_sos(sos, w, fs=fs) + w1, h1 = map(xp.asarray, (w1, h1)) + + w2, h2 = freqz_sos(sos, 2*pi*xp.asarray(w, dtype=sos.dtype)/fs) + xp_assert_close(h1, h2) + xp_assert_close(w, w1, check_dtype=False) + + def test_w_or_N_types(self): + # Measure at 7 (polyval) or 8 (fft) equally-spaced points + for N in (7, np.int8(7), np.int16(7), np.int32(7), np.int64(7), + np.array(7), + 8, np.int8(8), np.int16(8), np.int32(8), np.int64(8), + np.array(8)): + + w, h = freqz_sos([1, 0, 0, 1, 0, 0], worN=N) + assert_array_almost_equal(w, np.pi * np.arange(N) / N) + assert_array_almost_equal(h, np.ones(N)) + + w, h = freqz_sos([1, 0, 0, 1, 0, 0], worN=N, fs=100) + assert_array_almost_equal(w, np.linspace(0, 50, N, endpoint=False)) + assert_array_almost_equal(h, np.ones(N)) + + # Measure at frequency 8 Hz + for w in (8.0, 8.0+0j): + # Only makes sense when fs is specified + w_out, h = freqz_sos([1, 0, 0, 1, 0, 0], worN=w, fs=100) + assert_array_almost_equal(w_out, [8]) + assert_array_almost_equal(h, [1]) + + def test_fs_validation(self): + sos = butter(4, 0.2, output='sos') + with pytest.raises(ValueError, match="Sampling.*single scalar"): + freqz_sos(sos, fs=np.array([10, 20])) + + +@make_xp_test_case(freqz_zpk) +class TestFreqz_zpk: + + def test_ticket1441(self, xp): + """Regression test for ticket 1441.""" + # Because freqz previously used arange instead of linspace, + # when N was large, it would return one more point than + # requested. + N = 100000 + w, h = freqz_zpk(xp.asarray([0.5]), xp.asarray([0.5]), 1.0, worN=N) + assert w.shape == (N,) + + def test_basic(self, xp): + w, h = freqz_zpk(xp.asarray([0.5]), xp.asarray([0.5]), 1.0, worN=8) + assert_array_almost_equal(w, xp.pi * xp.arange(8.0) / 8) + assert_array_almost_equal(h, xp.ones(8)) + + def test_basic_whole(self, xp): + w, h = freqz_zpk(xp.asarray([0.5]), xp.asarray([0.5]), 1.0, worN=8, whole=True) + assert_array_almost_equal(w, 2 * xp.pi * xp.arange(8.0) / 8) + assert_array_almost_equal(h, xp.ones(8)) + + @pytest.mark.xfail(DEFAULT_F32, reason="wrong answer with torch/float32") + def test_vs_freqz(self, xp): + b, a = cheby1(4, 5, 0.5, analog=False, output='ba') + z, p, k = cheby1(4, 5, 0.5, analog=False, output='zpk') + + w1, h1 = freqz(b, a) + z, p, k, w1, h1 = map(xp.asarray, (z, p, k, w1, h1)) + w2, h2 = freqz_zpk(z, p, k) + xp_assert_close(w1, w2) + xp_assert_close(h1, h2, rtol=1.3e-6) + + def test_backward_compat(self, xp): + # For backward compatibility, test if None act as a wrapper for default + w1, h1 = freqz_zpk(xp.asarray([0.5]), xp.asarray([0.5]), 1.0) + w2, h2 = freqz_zpk(xp.asarray([0.5]), xp.asarray([0.5]), 1.0, None) + assert_array_almost_equal(w1, w2) + assert_array_almost_equal(h1, h2) + + def test_fs_param(self, xp): + fs = 900 + z = xp.asarray([-1, -1, -1.0]) + p = xp.asarray( + [0.4747869998473389 + 0.4752230717749344j, + 0.37256600288916636, + 0.4747869998473389 - 0.4752230717749344j] + ) + k = 0.03934683014103762 + + # N = None, whole=False + w1, h1 = freqz_zpk(z, p, k, whole=False, fs=fs) + w2, h2 = freqz_zpk(z, p, k, whole=False) + xp_assert_close(h1, h2) + xp_assert_close(w1, xp.linspace(0, fs/2, 512, endpoint=False)) + + # N = None, whole=True + w1, h1 = freqz_zpk(z, p, k, whole=True, fs=fs) + w2, h2 = freqz_zpk(z, p, k, whole=True) + xp_assert_close(h1, h2) + xp_assert_close(w1, xp.linspace(0, fs, 512, endpoint=False)) + + # N = 5, whole=False + w1, h1 = freqz_zpk(z, p, k, 5, fs=fs) + w2, h2 = freqz_zpk(z, p, k, 5) + xp_assert_close(h1, h2) + xp_assert_close(w1, xp.linspace(0, fs/2, 5, endpoint=False)) + + # N = 5, whole=True + w1, h1 = freqz_zpk(z, p, k, 5, whole=True, fs=fs) + w2, h2 = freqz_zpk(z, p, k, 5, whole=True) + xp_assert_close(h1, h2) + xp_assert_close(w1, xp.linspace(0, fs, 5, endpoint=False)) + + @skip_xp_backends(np_only=True, reason="array_likes") + def test_fs_param2(self, xp): + fs = 900 + z = xp.asarray([-1, -1, -1.0]) + p = xp.asarray( + [0.4747869998473389 + 0.4752230717749344j, + 0.37256600288916636, + 0.4747869998473389 - 0.4752230717749344j] + ) + k = 0.03934683014103762 + + # w is an array_like + for w in ([123], (123,), xp.asarray([123]), (50, 123, 230), + xp.asarray([50, 123, 230])): + w1, h1 = freqz_zpk(z, p, k, w, fs=fs) + w2, h2 = freqz_zpk(z, p, k, 2*pi*xp.asarray(w)/fs) + xp_assert_close(h1, h2) + xp_assert_close(w, w1, check_dtype=False) + + def test_w_or_N_types(self): + # Measure at 8 equally-spaced points + for N in (8, np.int8(8), np.int16(8), np.int32(8), np.int64(8), + np.array(8)): + + w, h = freqz_zpk([], [], 1, worN=N) + assert_array_almost_equal(w, np.pi * np.arange(8) / 8.) + assert_array_almost_equal(h, np.ones(8)) + + w, h = freqz_zpk([], [], 1, worN=N, fs=100) + assert_array_almost_equal(w, np.linspace(0, 50, 8, endpoint=False)) + assert_array_almost_equal(h, np.ones(8)) + + # Measure at frequency 8 Hz + for w in (8.0, 8.0+0j): + # Only makes sense when fs is specified + w_out, h = freqz_zpk([], [], 1, worN=w, fs=100) + assert_array_almost_equal(w_out, [8]) + assert_array_almost_equal(h, [1]) + + def test_fs_validation(self): + with pytest.raises(ValueError, match="Sampling.*single scalar"): + freqz_zpk([1.0], [1.0], [1.0], fs=np.array([10., 20])) + + with pytest.raises(ValueError, match="Sampling.*be none."): + freqz_zpk([1.0], [1.0], [1.0], fs=None) + + +@make_xp_test_case(normalize) +class TestNormalize: + + def test_allclose(self, xp): + """Test for false positive on allclose in normalize() in + filter_design.py""" + # Test to make sure the allclose call within signal.normalize does not + # choose false positives. Then check against a known output from MATLAB + # to make sure the fix doesn't break anything. + + # These are the coefficients returned from + # `[b,a] = cheby1(8, 0.5, 0.048)' + # in MATLAB. There are at least 15 significant figures in each + # coefficient, so it makes sense to test for errors on the order of + # 1e-13 (this can always be relaxed if different platforms have + # different rounding errors) + b_matlab = xp.asarray([2.150733144728282e-11, 1.720586515782626e-10, + 6.022052805239190e-10, 1.204410561047838e-09, + 1.505513201309798e-09, 1.204410561047838e-09, + 6.022052805239190e-10, 1.720586515782626e-10, + 2.150733144728282e-11]) + a_matlab = xp.asarray([1.000000000000000e+00, -7.782402035027959e+00, + 2.654354569747454e+01, -5.182182531666387e+01, + 6.334127355102684e+01, -4.963358186631157e+01, + 2.434862182949389e+01, -6.836925348604676e+00, + 8.412934944449140e-01]) + + # This is the input to signal.normalize after passing through the + # equivalent steps in signal.iirfilter as was done for MATLAB + b_norm_in = xp.asarray([1.5543135865293012e-06, 1.2434508692234413e-05, + 4.3520780422820447e-05, 8.7041560845640893e-05, + 1.0880195105705122e-04, 8.7041560845640975e-05, + 4.3520780422820447e-05, 1.2434508692234413e-05, + 1.5543135865293012e-06]) + a_norm_in = xp.asarray([7.2269025909127173e+04, -5.6242661430467968e+05, + 1.9182761917308895e+06, -3.7451128364682454e+06, + 4.5776121393762771e+06, -3.5869706138592605e+06, + 1.7596511818472347e+06, -4.9409793515707983e+05, + 6.0799461347219651e+04]) + + b_output, a_output = normalize(b_norm_in, a_norm_in) + + # The test on b works for decimal=14 but the one for a does not. For + # the sake of consistency, both of these are decimal=13. If something + # breaks on another platform, it is probably fine to relax this lower. + decimal = 13 if xp_default_dtype(xp) == xp.float64 else 5 + assert_array_almost_equal(b_matlab, b_output, decimal=decimal) + assert_array_almost_equal(a_matlab, a_output, decimal=decimal) + + def test_errors(self): + """Test the error cases.""" + # all zero denominator + assert_raises(ValueError, normalize, [1, 2], 0) + + # denominator not 1 dimensional + assert_raises(ValueError, normalize, [1, 2], [[1]]) + + # numerator too many dimensions + assert_raises(ValueError, normalize, [[[1, 2]]], 1) + + +@make_xp_test_case(lp2lp) +class TestLp2lp: + + def test_basic(self, xp): + b = xp.asarray([1]) + a = xp.asarray([1, math.sqrt(2), 1]) + b_lp, a_lp = lp2lp(b, a, 0.38574256627112119) + assert_array_almost_equal(b_lp, xp.asarray([0.1488]), decimal=4) + assert_array_almost_equal(a_lp, xp.asarray([1, 0.5455, 0.1488]), decimal=4) + + +@make_xp_test_case(lp2hp) +class TestLp2hp: + + def test_denominator_order_greater_than_numerator_order(self, xp): + b = xp.asarray([0.25059432325190018]) + a = xp.asarray( + [1, 0.59724041654134863, 0.92834805757524175, 0.25059432325190018] + ) + b_hp, a_hp = lp2hp(b, a, 2*math.pi*5000) + xp_assert_close(b_hp, xp.asarray([1.0, 0, 0, 0])) + xp_assert_close( + a_hp, xp.asarray([1, 1.1638e5, 2.3522e9, 1.2373e14]), rtol=1e-4 + ) + + def test_numerator_order_greater_than_denominator_order(self, xp): + b = xp.asarray([1.0, 2.0, 3.0]) + a = xp.asarray([1.0]) + b_hp, a_hp = lp2hp(b, a, 2.0) + xp_assert_close(b_hp, xp.asarray([3.0, 4.0, 4.0])) + xp_assert_close(a_hp, xp.asarray([1.0, 0.0, 0.0])) + + +@make_xp_test_case(lp2bp) +class TestLp2bp: + + def test_denominator_order_greater_than_numerator_order(self, xp): + b = xp.asarray([1]) + a = xp.asarray([1, 2, 2, 1]) + b_bp, a_bp = lp2bp(b, a, 2*math.pi*4000, 2*math.pi*2000) + xp_assert_close(b_bp, xp.asarray([1.9844e12, 0, 0, 0]), rtol=1e-6) + xp_assert_close( + a_bp, + xp.asarray([1, 2.5133e4, 2.2108e9, 3.3735e13, + 1.3965e18, 1.0028e22, 2.5202e26]), rtol=1e-4 + ) + + def test_numerator_order_greater_than_denominator_order(self, xp): + b = xp.asarray([1.0, 2.0, 3.0]) + a = xp.asarray([1.0]) + b_bp, a_bp = lp2bp(b, a, 2.0, 3.0) + xp_assert_close(b_bp, xp.asarray([1/9, 2/3, 35/9, 8/3, 16/9])) + xp_assert_close(a_bp, xp.asarray([1.0, 0.0, 0.0])) + + +@make_xp_test_case(lp2bs) +class TestLp2bs: + + def test_denominator_order_greater_than_numerator_order(self, xp): + b = xp.asarray([1]) + a = xp.asarray([1, 1]) + b_bs, a_bs = lp2bs(b, a, 0.41722257286366754, 0.18460575326152251) + assert_array_almost_equal(b_bs, xp.asarray([1, 0, 0.17407]), decimal=5) + assert_array_almost_equal(a_bs, xp.asarray([1, 0.18461, 0.17407]), decimal=5) + + def test_numerator_order_greater_than_denominator_order(self, xp): + b = xp.asarray([1.0, 2.0, 3.0]) + a = xp.asarray([1.0]) + b_bs, a_bs = lp2bs(b, a, 2.0, 3.0) + xp_assert_close(b_bs, xp.asarray([3.0, 6.0, 33.0, 24.0, 48.0])) + xp_assert_close(a_bs, xp.asarray([1.0, 0.0, 8.0, 0.0, 16.0])) + + +@make_xp_test_case(bilinear) +class TestBilinear: + """Tests for function `signal.bilinear`. """ + + def test_exceptions(self): + """Raise all exceptions in `bilinear()`. """ + with pytest.raises(ValueError, match="Parameter a is not .*"): + bilinear(1., np.array([[1, 2, 3]])) + with pytest.raises(ValueError, match="Parameter b is not .*"): + bilinear(np.ones((2,3)), 1. ) + + @pytest.mark.xfail(DEFAULT_F32, reason="wrong answer with torch/float32") + def test_basic(self, xp): + # reference output values computed with sympy + b = [0.14879732743343033] + a = [1, 0.54552236880522209, 0.14879732743343033] + b, a = map(xp.asarray, (b, a)) + + b_zref = xp.asarray( + [0.08782128175913713, 0.17564256351827426, 0.08782128175913713] + ) + a_zref = xp.asarray( + [1.0, -1.0047722097030667, 0.3560573367396151] + ) + + b_z, a_z = bilinear(b, a, 0.5) + + xp_assert_close_nulp(b_z, b_zref) + xp_assert_close_nulp(a_z, a_zref) + + b = xp.asarray([1, 0, 0.17407467530697837]) + a = xp.asarray([1, 0.18460575326152251, 0.17407467530697837]) + + b_zref = xp.asarray( + [0.8641286432189045, -1.2157757001964216, 0.8641286432189045] + ) + a_zref = xp.asarray([1.0, -1.2157757001964216, 0.7282572864378091]) + + b_z, a_z = bilinear(b, a, 0.5) + + xp_assert_close_nulp(b_z, xp.asarray(b_zref)) + xp_assert_close_nulp(a_z, xp.asarray(a_zref)) + + @pytest.mark.xfail(DEFAULT_F32, reason="wrong answer with torch/float32") + @xfail_xp_backends("cupy", reason="https://github.com/cupy/cupy/issues/9404") + def test_ignore_leading_zeros(self, xp): + # regression for gh-6606 + # results shouldn't change when leading zeros are added to + # input numerator or denominator + b = [0.14879732743343033] + a = [1, 0.54552236880522209, 0.14879732743343033] + b, a = map(xp.asarray, (b, a)) + + b_zref = xp.asarray( + [0.08782128175913713, 0.17564256351827426, 0.08782128175913713] + ) + a_zref = xp.asarray([1.0, -1.0047722097030667, 0.3560573367396151]) + + for lzn, lzd in product(range(4), range(4)): + b_z, a_z = bilinear(xpx.pad(b, (lzn, 0), xp=xp), + xpx.pad(a, (lzd, 0), xp=xp), + 0.5) + xp_assert_close_nulp(b_z, b_zref) + xp_assert_close_nulp(a_z, a_zref) + + @pytest.mark.xfail(DEFAULT_F32, reason="wrong answer with torch/float32") + @xfail_xp_backends("cupy", reason="complex inputs not supported") + def test_complex(self, xp): + # reference output values computed with sympy + # this is an elliptical filter, 5Hz width, centered at +50Hz: + # z, p, k = signal.ellip(2, 0.5, 20, 2*np.pi*5/2, output='zpk', analog=True) + # z = z.astype(complex) + 2j * np.pi * 50 + # p = p.astype(complex) + 2j * np.pi * 50 + # b, a = signal.zpk2tf(z, p, k) + b = [(0.09999999999999991+0j), + -62.831853071795805j, + (-9505.857007071314+0j)] + a = [(1+0j), + (21.09511000343942-628.3185307179587j), + (-98310.74322875646-6627.2242613473845j)] + b, a = map(xp.asarray, (b, a)) + + # sample frequency + fs = 1000 + b_zref = [(0.09905575106715676-0.00013441423112828688j), + (-0.18834281923181084-0.06032810039049478j), + (0.08054306669414343+0.05766172295523972j)] + a_zref = [(1+0j), + (-1.8839476369292854-0.606808151331815j), + (0.7954687330018285+0.5717459398142481j)] + + b_zref, a_zref = map(xp.asarray, (b_zref, a_zref)) + + b_z, a_z = bilinear(b, a, fs) + + # the 3 ulp difference determined from testing + xp_assert_close_nulp(b_z, b_zref, nulp=3) + xp_assert_close_nulp(a_z, a_zref, nulp=3) + + def test_fs_validation(self): + b = [0.14879732743343033] + a = [1, 0.54552236880522209, 0.14879732743343033] + with pytest.raises(ValueError, match="Sampling.*single scalar"): + bilinear(b, a, fs=np.array([10, 20])) + + with pytest.raises(ValueError, match="Sampling.*be none"): + bilinear(b, a, fs=None) + + +@make_xp_test_case(lp2lp_zpk) +class TestLp2lp_zpk: + + @xfail_xp_backends( + 'dask.array', reason='https://github.com/dask/dask/issues/11883' + ) + def test_basic(self, xp): + z = xp.asarray([]) + p = xp.asarray([(-1+1j) / math.sqrt(2), (-1-1j) / math.sqrt(2)]) + k = 1 + z_lp, p_lp, k_lp = lp2lp_zpk(z, p, k, 5) + xp_assert_equal(z_lp, xp.asarray([])) + xp_assert_close(_sort_cmplx(p_lp, xp=xp), _sort_cmplx(p, xp=xp) * 5) + assert k_lp == 25. + + # Pseudo-Chebyshev with both poles and zeros + z = xp.asarray([-2j, +2j]) + p = xp.asarray([-0.75, -0.5-0.5j, -0.5+0.5j]) + k = 3 + z_lp, p_lp, k_lp = lp2lp_zpk(z, p, k, 20) + xp_assert_close( + _sort_cmplx(z_lp, xp=xp), _sort_cmplx([-40j, +40j], xp=xp) + ) + xp_assert_close( + _sort_cmplx(p_lp, xp=xp), _sort_cmplx([-15, -10-10j, -10+10j], xp=xp) + ) + assert k_lp == 60. + + def test_fs_validation(self): + z = [-2j, +2j] + p = [-0.75, -0.5 - 0.5j, -0.5 + 0.5j] + k = 3 + + with pytest.raises(ValueError, match="Sampling.*single scalar"): + bilinear_zpk(z, p, k, fs=np.array([10, 20])) + + with pytest.raises(ValueError, match="Sampling.*be none"): + bilinear_zpk(z, p, k, fs=None) + + +@make_xp_test_case(lp2hp_zpk) +class TestLp2hp_zpk: + + @xfail_xp_backends( + 'dask.array', reason='https://github.com/dask/dask/issues/11883' + ) + def test_basic(self, xp): + z = xp.asarray([]) + p = xp.asarray([(-1+1j) / math.sqrt(2), (-1-1j) / math.sqrt(2)]) + k = 1 + + z_hp, p_hp, k_hp = lp2hp_zpk(z, p, k, 5) + xp_assert_equal(z_hp, xp.asarray([0.0, 0.0], dtype=z_hp.dtype)) + xp_assert_close(_sort_cmplx(p_hp, xp=xp), _sort_cmplx(p, xp=xp) * 5) + assert math.isclose(k_hp, 1.0, rel_tol=4e-7) + + z = xp.asarray([-2j, +2j]) + p = xp.asarray([-0.75, -0.5-0.5j, -0.5+0.5j]) + k = 3 + z_hp, p_hp, k_hp = lp2hp_zpk(z, p, k, 6) + xp_assert_close( + _sort_cmplx(z_hp, xp=xp), _sort_cmplx([-3j, 0, +3j], xp=xp) + ) + xp_assert_close( + _sort_cmplx(p_hp, xp=xp), _sort_cmplx([-8, -6-6j, -6+6j], xp=xp) + ) + assert k_hp == 32.0 + + +@make_xp_test_case(lp2bp_zpk) +class TestLp2bp_zpk: + + @xfail_xp_backends( + 'dask.array', reason='https://github.com/dask/dask/issues/11883' + ) + def test_basic(self, xp): + z = xp.asarray([-2j, +2j]) + p = xp.asarray([-0.75, -0.5-0.5j, -0.5+0.5j]) + k = 3 + z_bp, p_bp, k_bp = lp2bp_zpk(z, p, k, 15, 8) + xp_assert_close( + _sort_cmplx(z_bp, xp=xp), + _sort_cmplx([-25j, -9j, 0, +9j, +25j], xp=xp), check_dtype=False + ) + xp_assert_close( + _sort_cmplx(p_bp, xp=xp), + _sort_cmplx( + [-3 + 6j*math.sqrt(6), -3 - 6j*math.sqrt(6), + +2j + cmath.sqrt(-8j - 225) - 2, -2j + cmath.sqrt(+8j - 225) - 2, + +2j - cmath.sqrt(-8j - 225) - 2, -2j - cmath.sqrt(+8j - 225) - 2 + ], xp=xp + ), check_dtype=False + ) + assert math.isclose(k_bp, 24.0) + + +@make_xp_test_case(lp2bs_zpk) +class TestLp2bs_zpk: + + @xfail_xp_backends( + 'dask.array', reason='https://github.com/dask/dask/issues/11883' + ) + def test_basic(self, xp): + z = xp.asarray([-2j, +2j]) + p = xp.asarray([-0.75, -0.5-0.5j, -0.5+0.5j]) + k = 3 + + z_bs, p_bs, k_bs = lp2bs_zpk(z, p, k, 35, 12) + + xp_assert_close( + _sort_cmplx(z_bs, xp=xp), + _sort_cmplx([+35j, -35j, + +3j + math.sqrt(1234)*1j, + -3j + math.sqrt(1234)*1j, + +3j - math.sqrt(1234)*1j, + -3j - math.sqrt(1234)*1j], xp=xp), check_dtype=False + ) + xp_assert_close( + _sort_cmplx(p_bs, xp=xp), + _sort_cmplx([+3j*math.sqrt(129) - 8, + -3j*math.sqrt(129) - 8, + (-6 + 6j) - cmath.sqrt(-1225 - 72j), + (-6 - 6j) - cmath.sqrt(-1225 + 72j), + (-6 + 6j) + cmath.sqrt(-1225 - 72j), + (-6 - 6j) + cmath.sqrt(-1225 + 72j), ], xp=xp), + check_dtype=False + ) + assert math.isclose(k_bs, 32.0) + + +@make_xp_test_case(bilinear_zpk) +class TestBilinear_zpk: + + @xfail_xp_backends( + 'dask.array', reason='https://github.com/dask/dask/issues/11883' + ) + def test_basic(self, xp): + z = xp.asarray([-2j, +2j]) + p = xp.asarray([-0.75, -0.5-0.5j, -0.5+0.5j]) + k = 3 + + z_d, p_d, k_d = bilinear_zpk(z, p, k, 10) + + xp_assert_close( + _sort_cmplx(z_d, xp=xp), + _sort_cmplx([(20-2j) / (20+2j), (20+2j) / (20-2j), -1], xp=xp) + ) + xp_assert_close( + _sort_cmplx(p_d, xp=xp), + _sort_cmplx( + [77/83, (1j/2 + 39/2) / (41/2 - 1j/2), (39/2 - 1j/2) / (1j/2 + 41/2)], + xp=xp + ) + ) + assert math.isclose(k_d, 9696/69803, rel_tol=4e-7) + + +class TestPrototypeType: + + def test_output_type(self): + # Prototypes should consistently output arrays, not lists + # https://github.com/scipy/scipy/pull/441 + for func in (buttap, + besselap, + lambda N: cheb1ap(N, 1), + lambda N: cheb2ap(N, 20), + lambda N: ellipap(N, 1, 20)): + for N in range(7): + z, p, k = func(N) + assert isinstance(z, np.ndarray) + assert isinstance(p, np.ndarray) + + @pytest.mark.parametrize( + 'base_func,func', [ + make_xp_pytest_param(buttap, buttap), + make_xp_pytest_param(besselap, besselap), + make_xp_pytest_param(cheb1ap, lambda N, xp: cheb1ap(N, 1, xp=xp)), + make_xp_pytest_param(cheb2ap, lambda N, xp: cheb2ap(N, 20, xp=xp)), + make_xp_pytest_param(ellipap, lambda N, xp: ellipap(N, 1, 20, xp=xp)), + ], + ids=['butter', 'bessel', 'cheb1', 'cheb2', 'ellip'] + ) + def test_with_xp(self, base_func, func, xp): + func(7, xp=xp) + + +def dB(x): + # Return magnitude in decibels, avoiding divide-by-zero warnings + # (and deal with some "not less-ordered" errors when -inf shows up) + xp = array_namespace(x) + tiny = xp.asarray(np.finfo(np.float64).tiny) + return 20 * xp.log10(xp.maximum(xp.abs(x), tiny)) + + +@make_xp_test_case(buttord) +@pytest.mark.skipif(DEFAULT_F32, reason="XXX needs figuring out") +@skip_xp_backends("dask.array", reason="https://github.com/dask/dask/issues/11883") +class TestButtord: + + def test_lowpass(self, xp): + wp = 0.2 + ws = 0.3 + rp = 3 + rs = 60 + N, Wn = buttord(xp.asarray(wp), ws, rp, rs, False) + b, a = butter(N, _xp_copy_to_numpy(Wn), 'lowpass', False) + w, h = freqz(b, a) + w /= np.pi + assert np.all(-rp < dB(h[w <= wp])) + assert np.all(dB(h[ws <= w]) < -rs) + + assert N == 16 + xp_assert_close(Wn, + xp.asarray(2.0002776782743284e-01), rtol=1e-15, check_0d=False) + + def test_highpass(self, xp): + wp = 0.3 + ws = 0.2 + rp = 3 + rs = 70 + N, Wn = buttord(xp.asarray(wp), ws, rp, rs, False) + b, a = butter(N, _xp_copy_to_numpy(Wn), 'highpass', False) + w, h = freqz(b, a) + w /= np.pi + assert np.all(-rp < dB(h[wp <= w])) + assert np.all(dB(h[w <= ws]) < -rs) + + assert N == 18 + xp_assert_close(Wn, + xp.asarray(2.9996603079132672e-01), rtol=1e-15, check_0d=False) + + def test_bandpass(self, xp): + wp = [0.2, 0.5] + ws = [0.1, 0.6] + rp = 3 + rs = 80 + N, Wn = buttord(xp.asarray(wp), xp.asarray(ws), rp, rs, False) + b, a = butter(N, _xp_copy_to_numpy(Wn), 'bandpass', False) + w, h = freqz(b, a) + w /= np.pi + + assert np.all((-rp - 0.1) < dB(h[np.logical_and(wp[0] <= w, w <= wp[1])])) + + assert np.all(dB(h[np.logical_or(w <= ws[0], ws[1] <= w)]) < (-rs + 0.1)) + + assert N == 18 + xp_assert_close( + Wn, xp.asarray([1.9998742411409134e-01, 5.0002139595676276e-01]), + rtol=1e-15, + ) + + @skip_xp_backends( + cpu_only=True, exceptions=["cupy"], reason="optimize.fminbound" + ) + def test_bandstop(self, xp): + wp = [0.1, 0.6] + ws = [0.2, 0.5] + rp = 3 + rs = 90 + N, Wn = buttord(xp.asarray(wp), xp.asarray(ws), rp, rs, False) + b, a = butter(N, _xp_copy_to_numpy(Wn), 'bandstop', False) + w, h = freqz(b, a) + w /= np.pi + + assert np.all(-rp < dB(h[np.logical_or(w <= wp[0], wp[1] <= w)])) + assert np.all(dB(h[np.logical_and(ws[0] <= w, w <= ws[1])]) < -rs) + + assert N == 20 + xp_assert_close( + Wn, xp.asarray([1.4759432329294042e-01, 5.9997365985276407e-01]), + rtol=1e-6 + ) + + def test_analog(self, xp): + wp = 200. + ws = 600. + rp = 3 + rs = 60 + N, Wn = buttord(xp.asarray(wp), ws, rp, rs, True) + b, a = butter(N, _xp_copy_to_numpy(Wn), 'lowpass', True) + w, h = freqs(b, a) + assert np.all(-rp < dB(h[w <= wp])) + assert np.all(dB(h[ws <= w]) < -rs) + + assert N == 7 + xp_assert_close( + Wn, xp.asarray(2.0006785355671877e+02), rtol=1e-15, check_0d=False + ) + + n, Wn = buttord(1., xp.asarray(550/450), 1, 26, analog=True) + assert n == 19 + xp_assert_close( + Wn, xp.asarray(1.0361980524629517), rtol=1e-15, check_0d=False + ) + + assert buttord(1, xp.asarray(1.2), 1, 80, analog=True)[0] == 55 + + def test_fs_param(self, xp): + wp = [4410, 11025] + ws = [2205, 13230] + rp = 3 + rs = 80 + fs = 44100 + N, Wn = buttord(xp.asarray(wp), xp.asarray(ws), rp, rs, False, fs=fs) + b, a = butter(N, _xp_copy_to_numpy(Wn), 'bandpass', False, fs=fs) + w, h = freqz(b, a, fs=fs) + + assert np.all(-rp - 0.1 < dB(h[np.logical_and(wp[0] <= w, w <= wp[1])])) + assert np.all(dB(h[np.logical_or(w <= ws[0], ws[1] <= w)]) < -rs + 0.1) + + assert N == 18 + xp_assert_close(Wn, xp.asarray([4409.722701715714, 11025.47178084662]), + rtol=1e-15) + + def test_invalid_input(self): + with pytest.raises(ValueError) as exc_info: + buttord([20, 50], [14, 60], 3, 2) + assert "gpass should be smaller than gstop" in str(exc_info.value) + + with pytest.raises(ValueError) as exc_info: + buttord([20, 50], [14, 60], -1, 2) + assert "gpass should be larger than 0.0" in str(exc_info.value) + + with pytest.raises(ValueError) as exc_info: + buttord([20, 50], [14, 60], 1, -2) + assert "gstop should be larger than 0.0" in str(exc_info.value) + + def test_runtime_warnings(self): + msg = "Order is zero.*|divide by zero encountered" + with pytest.warns(RuntimeWarning, match=msg): + buttord(0.0, 1.0, 3, 60) + + def test_ellip_butter(self): + # The purpose of the test is to compare to some known output from past + # scipy versions. The values to compare to are generated with scipy + # 1.9.1 (there is nothing special about this particular version though) + n, wn = buttord([0.1, 0.6], [0.2, 0.5], 3, 60) + assert n == 14 + + def test_fs_validation(self): + wp = 0.2 + ws = 0.3 + rp = 3 + rs = 60 + + with pytest.raises(ValueError, match="Sampling.*single scalar"): + buttord(wp, ws, rp, rs, False, fs=np.array([10, 20])) + + +@make_xp_test_case(cheb1ord) +@skip_xp_backends("dask.array", reason="https://github.com/dask/dask/issues/11883") +class TestCheb1ord: + + @xfail_xp_backends("torch", reason="accuracy is bad") + def test_lowpass(self, xp): + wp = 0.2 + ws = 0.3 + rp = 3 + rs = 60 + N, Wn = cheb1ord(xp.asarray(wp), ws, rp, rs, False) + b, a = cheby1(N, rp, _xp_copy_to_numpy(Wn), 'low', False) + w, h = freqz(b, a) + w /= np.pi + assert np.all(-rp - 0.1 < dB(h[w <= wp])) + assert np.all(dB(h[ws <= w]) < -rs + 0.1) + + assert N == 8 + xp_assert_close(Wn, xp.asarray(0.2), rtol=1e-15, check_0d=False) + + @xfail_xp_backends("torch", reason="accuracy is bad") + def test_highpass(self, xp): + wp = 0.3 + ws = 0.2 + rp = 3 + rs = 70 + N, Wn = cheb1ord(xp.asarray(wp), ws, rp, rs, False) + b, a = cheby1(N, rp, _xp_copy_to_numpy(Wn), 'high', False) + w, h = freqz(b, a) + w /= np.pi + assert np.all(-rp - 0.1 < dB(h[wp <= w])) + assert np.all(dB(h[w <= ws]) < -rs + 0.1) + + assert N == 9 + xp_assert_close(Wn, xp.asarray(0.3), rtol=1e-15, check_0d=False) + + def test_bandpass(self, xp): + wp = [0.2, 0.5] + ws = [0.1, 0.6] + rp = 3 + rs = 80 + N, Wn = cheb1ord(xp.asarray(wp), xp.asarray(ws), rp, rs, False) + b, a = cheby1(N, rp, _xp_copy_to_numpy(Wn), 'band', False) + w, h = freqz(b, a) + w /= np.pi + assert np.all(-rp - 0.1 < dB(h[np.logical_and(wp[0] <= w, w <= wp[1])])) + assert np.all(dB(h[np.logical_or(w <= ws[0], ws[1] <= w)]) < -rs + 0.1) + + assert N == 9 + xp_assert_close(Wn, xp.asarray([0.2, 0.5]), rtol=1e-15) + + @skip_xp_backends( + cpu_only=True, exceptions=["cupy"], reason="optimize.fminbound" + ) + def test_bandstop(self, xp): + wp = [0.1, 0.6] + ws = [0.2, 0.5] + rp = 3 + rs = 90 + N, Wn = cheb1ord(xp.asarray(wp), xp.asarray(ws), rp, rs, False) + b, a = cheby1(N, rp, _xp_copy_to_numpy(Wn), 'stop', False) + w, h = freqz(b, a) + w /= np.pi + assert np.all(-rp - 0.1 < dB(h[np.logical_or(w <= wp[0], wp[1] <= w)])) + assert np.all(dB(h[np.logical_and(ws[0] <= w, w <= ws[1])]) < -rs + 0.1) + + assert N == 10 + xp_assert_close(Wn, xp.asarray([0.14758232569947785, 0.6]), rtol=1e-5) + + def test_analog(self, xp): + wp = 700 + ws = 100. + rp = 3 + rs = 70 + N, Wn = cheb1ord(wp, xp.asarray(ws), rp, rs, True) + b, a = cheby1(N, rp, _xp_copy_to_numpy(Wn), 'high', True) + w, h = freqs(b, a) + assert np.all(-rp - 0.1 < dB(h[wp <= w])) + assert np.all(dB(h[w <= ws]) < -rs + 0.1) + + assert N == 4 + assert math.isclose(Wn, 700.0, rel_tol=1e-15) + assert array_namespace(Wn) == xp + assert cheb1ord(xp.asarray(1), 1.2, 1, 80, analog=True)[0] == 17 + + @xfail_xp_backends("torch", reason="accuracy issues") + def test_fs_param(self, xp): + wp = 4800 + ws = 7200. + rp = 3 + rs = 60 + fs = 48000 + N, Wn = cheb1ord(wp, xp.asarray(ws), rp, rs, False, fs=fs) + b, a = cheby1(N, rp, _xp_copy_to_numpy(Wn), 'low', False, fs=fs) + w, h = freqz(b, a, fs=fs) + assert np.all(-rp - 0.1 < dB(h[w <= wp])) + assert np.all(dB(h[ws <= w]) < -rs + 0.1) + + assert N == 8 + assert math.isclose(Wn, 4800.0, rel_tol=1e-15) + assert array_namespace(Wn) == xp + + def test_invalid_input(self): + with pytest.raises(ValueError) as exc_info: + cheb1ord(0.2, 0.3, 3, 2) + assert "gpass should be smaller than gstop" in str(exc_info.value) + + with pytest.raises(ValueError) as exc_info: + cheb1ord(0.2, 0.3, -1, 2) + assert "gpass should be larger than 0.0" in str(exc_info.value) + + with pytest.raises(ValueError) as exc_info: + cheb1ord(0.2, 0.3, 1, -2) + assert "gstop should be larger than 0.0" in str(exc_info.value) + + def test_ellip_cheb1(self): + # The purpose of the test is to compare to some known output from past + # scipy versions. The values to compare to are generated with scipy + # 1.9.1 (there is nothing special about this particular version though) + n, wn = cheb1ord([0.1, 0.6], [0.2, 0.5], 3, 60) + assert n == 7 + + n2, w2 = cheb2ord([0.1, 0.6], [0.2, 0.5], 3, 60) + assert not (wn == w2).all() + + def test_fs_validation(self): + wp = 0.2 + ws = 0.3 + rp = 3 + rs = 60 + + with pytest.raises(ValueError, match="Sampling.*single scalar"): + cheb1ord(wp, ws, rp, rs, False, fs=np.array([10, 20])) + + +@make_xp_test_case(cheb2ord) +@pytest.mark.skipif(DEFAULT_F32, reason="XXX needs figuring out") +@skip_xp_backends("dask.array", reason="https://github.com/dask/dask/issues/11883") +class TestCheb2ord: + + def test_lowpass(self, xp): + wp = 0.2 + ws = 0.3 + rp = 3 + rs = 60 + N, Wn = cheb2ord(wp, xp.asarray(ws), rp, rs, False) + b, a = cheby2(N, rs, _xp_copy_to_numpy(Wn), 'lp', False) + w, h = freqz(b, a) + w /= np.pi + assert np.all(-rp - 0.1 < dB(h[w <= wp])) + assert np.all(dB(h[ws <= w]) < -rs + 0.1) + + assert N == 8 + xp_assert_close(Wn, xp.asarray(0.28647639976553163), rtol=1e-15, check_0d=False) + + def test_highpass(self, xp): + wp = 0.3 + ws = 0.2 + rp = 3 + rs = 70 + N, Wn = cheb2ord(wp, xp.asarray(ws), rp, rs, False) + b, a = cheby2(N, rs, _xp_copy_to_numpy(Wn), 'hp', False) + w, h = freqz(b, a) + w /= np.pi + assert np.all(-rp - 0.1 < dB(h[wp <= w])) + assert np.all(dB(h[w <= ws]) < -rs + 0.1) + + assert N == 9 + xp_assert_close(Wn, xp.asarray(0.20697492182903282), rtol=1e-15, check_0d=False) + + def test_bandpass(self, xp): + wp = [0.2, 0.5] + ws = [0.1, 0.6] + rp = 3 + rs = 80 + N, Wn = cheb2ord(xp.asarray(wp), xp.asarray(ws), rp, rs, False) + b, a = cheby2(N, rs, _xp_copy_to_numpy(Wn), 'bp', False) + w, h = freqz(b, a) + w /= np.pi + assert np.all(-rp - 0.1 < dB(h[np.logical_and(wp[0] <= w, w <= wp[1])])) + assert np.all(dB(h[np.logical_or(w <= ws[0], ws[1] <= w)]) < -rs + 0.1) + + assert N == 9 + xp_assert_close(Wn, xp.asarray([0.14876937565923479, 0.59748447842351482]), + rtol=1e-15) + + @skip_xp_backends( + cpu_only=True, exceptions=["cupy"], reason="optimize.fminbound" + ) + def test_bandstop(self, xp): + wp = [0.1, 0.6] + ws = [0.2, 0.5] + rp = 3 + rs = 90 + N, Wn = cheb2ord(xp.asarray(wp), xp.asarray(ws), rp, rs, False) + b, a = cheby2(N, rs, _xp_copy_to_numpy(Wn), 'bs', False) + w, h = freqz(b, a) + w /= np.pi + assert np.all(-rp - 0.1 < dB(h[np.logical_or(w <= wp[0], wp[1] <= w)])) + assert np.all(dB(h[np.logical_and(ws[0] <= w, w <= ws[1])]) < -rs + 0.1) + + assert N == 10 + xp_assert_close(Wn, xp.asarray([0.19926249974781743, 0.50125246585567362]), + rtol=1e-6) + + def test_analog(self, xp): + wp = [20., 50] + ws = [10., 60] + rp = 3 + rs = 80 + N, Wn = cheb2ord(xp.asarray(wp), xp.asarray(ws), rp, rs, True) + b, a = cheby2(N, rs, _xp_copy_to_numpy(Wn), 'bp', True) + w, h = freqs(b, a) + assert np.all(-rp - 0.1 < dB(h[np.logical_and(wp[0] <= w, w <= wp[1])])) + assert np.all(dB(h[np.logical_or(w <= ws[0], ws[1] <= w)]) < -rs + 0.1) + + assert N == 11 + xp_assert_close(Wn, xp.asarray([1.673740595370124e+01, 5.974641487254268e+01]), + rtol=1e-15) + + def test_fs_param(self, xp): + wp = 150 + ws = 100. + rp = 3 + rs = 70 + fs = 1000 + N, Wn = cheb2ord(wp, xp.asarray(ws), rp, rs, False, fs=fs) + b, a = cheby2(N, rs, _xp_copy_to_numpy(Wn), 'hp', False, fs=fs) + w, h = freqz(b, a, fs=fs) + assert np.all(-rp - 0.1 < dB(h[wp <= w])) + assert np.all(dB(h[w <= ws]) < -rs + 0.1) + + assert N == 9 + assert math.isclose(Wn, 103.4874609145164, rel_tol=1e-15) + assert array_namespace(Wn) == xp + + def test_invalid_input(self): + with pytest.raises(ValueError) as exc_info: + cheb2ord([0.1, 0.6], [0.2, 0.5], 3, 2) + assert "gpass should be smaller than gstop" in str(exc_info.value) + + with pytest.raises(ValueError) as exc_info: + cheb2ord([0.1, 0.6], [0.2, 0.5], -1, 2) + assert "gpass should be larger than 0.0" in str(exc_info.value) + + with pytest.raises(ValueError) as exc_info: + cheb2ord([0.1, 0.6], [0.2, 0.5], 1, -2) + assert "gstop should be larger than 0.0" in str(exc_info.value) + + def test_ellip_cheb2(self): + # The purpose of the test is to compare to some known output from past + # scipy versions. The values to compare to are generated with scipy + # 1.9.1 (there is nothing special about this particular version though) + n, wn = cheb2ord([0.1, 0.6], [0.2, 0.5], 3, 60) + assert n == 7 + + n1, w1 = cheb1ord([0.1, 0.6], [0.2, 0.5], 3, 60) + assert not (wn == w1).all() + + def test_fs_validation(self): + wp = 0.2 + ws = 0.3 + rp = 3 + rs = 60 + + with pytest.raises(ValueError, match="Sampling.*single scalar"): + cheb2ord(wp, ws, rp, rs, False, fs=np.array([10, 20])) + + +@make_xp_test_case(ellipord) +@pytest.mark.skipif(DEFAULT_F32, reason="XXX needs figuring out") +@skip_xp_backends("dask.array", reason="https://github.com/dask/dask/issues/11883") +class TestEllipord: + + def test_lowpass(self, xp): + wp = 0.2 + ws = 0.3 + rp = 3 + rs = 60 + N, Wn = ellipord(wp, xp.asarray(ws), rp, rs, False) + b, a = ellip(N, rp, rs, _xp_copy_to_numpy(Wn), 'lp', False) + w, h = freqz(b, a) + w /= np.pi + assert np.all(-rp - 0.1 < dB(h[w <= wp])) + assert np.all(dB(h[ws <= w]) < -rs + 0.1) + + assert N == 5 + xp_assert_close(Wn, xp.asarray(0.2), rtol=1e-15, check_0d=False) + + def test_lowpass_1000dB(self, xp): + # failed when ellipkm1 wasn't used in ellipord and ellipap + wp = 0.2 + ws = 0.3 + rp = 3 + rs = 1000 + N, Wn = ellipord(wp, xp.asarray(ws), rp, rs, False) + sos = ellip(N, rp, rs, _xp_copy_to_numpy(Wn), 'lp', False, output='sos') + w, h = freqz_sos(sos) + w /= np.pi + assert np.all(-rp - 0.1 < dB(h[w <= wp])) + assert np.all(dB(h[ws <= w]) < -rs + 0.1) + assert array_namespace(Wn) == xp + + def test_highpass(self, xp): + wp = 0.3 + ws = 0.2 + rp = 3 + rs = 70 + N, Wn = ellipord(wp, xp.asarray(ws), rp, rs, False) + b, a = ellip(N, rp, rs, _xp_copy_to_numpy(Wn), 'hp', False) + w, h = freqz(b, a) + w /= np.pi + assert np.all(-rp - 0.1 < dB(h[wp <= w])) + assert np.all(dB(h[w <= ws]) < -rs + 0.1) + + assert N == 6 + xp_assert_close(Wn, xp.asarray(0.3), rtol=1e-15, check_0d=False) + assert array_namespace(Wn) == xp + + def test_bandpass(self, xp): + wp = [0.2, 0.5] + ws = [0.1, 0.6] + rp = 3 + rs = 80 + N, Wn = ellipord(xp.asarray(wp), xp.asarray(ws), rp, rs, False) + b, a = ellip(N, rp, rs, _xp_copy_to_numpy(Wn), 'bp', False) + w, h = freqz(b, a) + w /= np.pi + assert np.all(-rp - 0.1 < dB(h[np.logical_and(wp[0] <= w, w <= wp[1])])) + assert np.all(dB(h[np.logical_or(w <= ws[0], ws[1] <= w)]) < -rs + 0.1) + + assert N == 6 + xp_assert_close(Wn, xp.asarray([0.2, 0.5]), rtol=1e-15) + + @skip_xp_backends( + cpu_only=True, exceptions=["cupy"], reason="optimize.fminbound" + ) + def test_bandstop(self, xp): + wp = [0.1, 0.6] + ws = [0.2, 0.5] + rp = 3 + rs = 90 + N, Wn = ellipord(xp.asarray(wp), xp.asarray(ws), rp, rs, False) + b, a = ellip(N, rp, rs, _xp_copy_to_numpy(Wn), 'bs', False) + w, h = freqz(b, a) + w /= np.pi + assert np.all(-rp - 0.1 < dB(h[np.logical_or(w <= wp[0], wp[1] <= w)])) + assert np.all(dB(h[np.logical_and(ws[0] <= w, w <= ws[1])]) < -rs + 0.1) + + assert N == 7 + xp_assert_close(Wn, xp.asarray([0.14758232794342988, 0.6]), rtol=1e-5) + + def test_analog(self, xp): + wp = [1000.0, 6000] + ws = [2000.0, 5000] + rp = 3 + rs = 90 + N, Wn = ellipord(xp.asarray(wp), xp.asarray(ws), rp, rs, True) + b, a = ellip(N, rp, rs, _xp_copy_to_numpy(Wn), 'bs', True) + w, h = freqs(b, a) + assert np.all(-rp - 0.1 < dB(h[np.logical_or(w <= wp[0], wp[1] <= w)])) + assert np.all(dB(h[np.logical_and(ws[0] <= w, w <= ws[1])]) < -rs + 0.1) + + assert N == 8 + xp_assert_close(Wn, xp.asarray([1666.6666, 6000])) + + assert ellipord(xp.asarray(1), 1.2, 1, 80, analog=True)[0] == 9 + + def test_fs_param(self, xp): + wp = [400.0, 2400] + ws = [800.0, 2000] + rp = 3 + rs = 90 + fs = 8000 + N, Wn = ellipord(xp.asarray(wp), xp.asarray(ws), rp, rs, False, fs=fs) + b, a = ellip(N, rp, rs, _xp_copy_to_numpy(Wn), 'bs', False, fs=fs) + w, h = freqz(b, a, fs=fs) + assert np.all(-rp - 0.1 < dB(h[np.logical_or(w <= wp[0], wp[1] <= w)])) + assert np.all(dB(h[np.logical_and(ws[0] <= w, w <= ws[1])]) < -rs + 0.1) + + assert N == 7 + xp_assert_close(Wn, xp.asarray([590.3293117737195, 2400]), rtol=1e-5) + + def test_invalid_input(self): + with pytest.raises(ValueError) as exc_info: + ellipord(0.2, 0.5, 3, 2) + assert "gpass should be smaller than gstop" in str(exc_info.value) + + with pytest.raises(ValueError) as exc_info: + ellipord(0.2, 0.5, -1, 2) + assert "gpass should be larger than 0.0" in str(exc_info.value) + + with pytest.raises(ValueError) as exc_info: + ellipord(0.2, 0.5, 1, -2) + assert "gstop should be larger than 0.0" in str(exc_info.value) + + def test_ellip_butter(self, xp): + # The purpose of the test is to compare to some known output from past + # scipy versions. The values to compare to are generated with scipy + # 1.9.1 (there is nothing special about this particular version though) + n, Wn = ellipord(xp.asarray([0.1, 0.6]), xp.asarray([0.2, 0.5]), 3, 60) + assert array_namespace(Wn) == xp + assert n == 5 + + def test_fs_validation(self): + wp = 0.2 + ws = 0.3 + rp = 3 + rs = 60 + + with pytest.raises(ValueError, match="Sampling.*single scalar"): + ellipord(wp, ws, rp, rs, False, fs=np.array([10, 20])) + + +# Currently the filter functions tested below (bessel, butter, cheby1, cheby2, +# and ellip) all return float64 (or complex128) output regardless of input +# dtype. Therefore reference arrays in these tests are all given an explicit 64 +# bit dtype, because the output will not match the xp_default_dtype when the +# default dtype is float32. Although the output arrays and all internal +# calculations are in 64 bit precision, tolerances are still loosened for the +# float32 case when results are impacted by reduced precision in the inputs. + + +@skip_xp_backends("dask.array", reason="https://github.com/dask/dask/issues/11883") +@make_xp_test_case(bessel) +class TestBessel: + + def test_degenerate(self, xp): + for norm in ('delay', 'phase', 'mag'): + # 0-order filter is just a passthrough + b, a = bessel(0, xp.asarray(1), analog=True, norm=norm) + xp_assert_equal(b, xp.asarray([1.0], dtype=xp.float64)) + xp_assert_equal(a, xp.asarray([1.0], dtype=xp.float64)) + + # 1-order filter is same for all types + b, a = bessel(1, xp.asarray(1.), analog=True, norm=norm) + + xp_assert_close(b, xp.asarray([1.0], dtype=xp.float64), rtol=1e-15) + xp_assert_close(a, xp.asarray([1.0, 1], dtype=xp.float64), rtol=1e-15) + + z, p, k = bessel(1, xp.asarray(0.3), analog=True, output='zpk', norm=norm) + xp_assert_equal(z, xp.asarray([], dtype=xp.float64)) + xp_assert_close( + p, xp.asarray([-0.3+0j], dtype=xp.complex128), + rtol=1e-14 if not DEFAULT_F32 else 1e-7 + ) + assert math.isclose( + k, 0.3, rel_tol=1e-14 if not DEFAULT_F32 else 1e-6 + ) + + @pytest.mark.xfail(reason="Failing in mypy workflow - see gh-23902") + def test_high_order(self, xp): + # high even order, 'phase' + z, p, k = bessel(24, xp.asarray(100), analog=True, output='zpk') + z2 = xp.asarray([], dtype=xp.float64) + p2 = [ + -9.055312334014323e+01 + 4.844005815403969e+00j, + -8.983105162681878e+01 + 1.454056170018573e+01j, + -8.837357994162065e+01 + 2.426335240122282e+01j, + -8.615278316179575e+01 + 3.403202098404543e+01j, + -8.312326467067703e+01 + 4.386985940217900e+01j, + -7.921695461084202e+01 + 5.380628489700191e+01j, + -7.433392285433246e+01 + 6.388084216250878e+01j, + -6.832565803501586e+01 + 7.415032695116071e+01j, + -6.096221567378025e+01 + 8.470292433074425e+01j, + -5.185914574820616e+01 + 9.569048385258847e+01j, + -4.027853855197555e+01 + 1.074195196518679e+02j, + -2.433481337524861e+01 + 1.207298683731973e+02j, + ] + + p2 = np.union1d(p2, np.conj(p2)) + p2 = xp.asarray(p2, dtype=xp.complex128) + k2 = 9.999999999999989e+47 + xp_assert_equal(z, z2) + xp_assert_close(_sort_cmplx(p, xp=xp), + _sort_cmplx(p2, xp=xp)) + assert math.isclose(k, k2, rel_tol=1e-14) + + # high odd order, 'phase' + z, p, k = bessel(23, xp.asarray(1000.), analog=True, output='zpk') + z2 = xp.asarray([], dtype=xp.float64) + p2 = [ + -2.497697202208956e+02 + 1.202813187870698e+03j, + -4.126986617510172e+02 + 1.065328794475509e+03j, + -5.304922463809596e+02 + 9.439760364018479e+02j, + -9.027564978975828e+02 + 1.010534334242318e+02j, + -8.909283244406079e+02 + 2.023024699647598e+02j, + -8.709469394347836e+02 + 3.039581994804637e+02j, + -8.423805948131370e+02 + 4.062657947488952e+02j, + -8.045561642249877e+02 + 5.095305912401127e+02j, + -7.564660146766259e+02 + 6.141594859516342e+02j, + -6.965966033906477e+02 + 7.207341374730186e+02j, + -6.225903228776276e+02 + 8.301558302815096e+02j, + -9.066732476324988e+02] + p2 = np.union1d(p2, np.conj(p2)) + p2 = xp.asarray(p2, dtype=xp.complex128) + k2 = 9.999999999999983e+68 + xp_assert_equal(z, z2) + xp_assert_close(_sort_cmplx(p, xp=xp), + _sort_cmplx(p2, xp=xp)) + assert math.isclose(k, k2, rel_tol=1e-14) + + # high even order, 'delay' (Orchard 1965 "The Roots of the + # Maximally Flat-Delay Polynomials" Table 1) + z, p, k = bessel(31, xp.asarray(1.), analog=True, output='zpk', norm='delay') + p2 = [-20.876706, + -20.826543 + 1.735732j, + -20.675502 + 3.473320j, + -20.421895 + 5.214702j, + -20.062802 + 6.961982j, + -19.593895 + 8.717546j, + -19.009148 + 10.484195j, + -18.300400 + 12.265351j, + -17.456663 + 14.065350j, + -16.463032 + 15.889910j, + -15.298849 + 17.746914j, + -13.934466 + 19.647827j, + -12.324914 + 21.610519j, + -10.395893 + 23.665701j, + - 8.005600 + 25.875019j, + - 4.792045 + 28.406037j, + ] + p2 = np.union1d(p2, np.conj(p2)) + p2 = xp.asarray(p2, dtype=xp.complex128) + xp_assert_close(_sort_cmplx(p, xp=xp), + _sort_cmplx(p2, xp=xp)) + + # high odd order, 'delay' + z, p, k = bessel(30, xp.asarray(1.), analog=True, output='zpk', norm='delay') + p2 = [-20.201029 + 0.867750j, + -20.097257 + 2.604235j, + -19.888485 + 4.343721j, + -19.572188 + 6.088363j, + -19.144380 + 7.840570j, + -18.599342 + 9.603147j, + -17.929195 + 11.379494j, + -17.123228 + 13.173901j, + -16.166808 + 14.992008j, + -15.039580 + 16.841580j, + -13.712245 + 18.733902j, + -12.140295 + 20.686563j, + -10.250119 + 22.729808j, + - 7.901170 + 24.924391j, + - 4.734679 + 27.435615j, + ] + p2 = np.union1d(p2, np.conj(p2)) + p2 = xp.asarray(p2, dtype=xp.complex128) + xp_assert_close(_sort_cmplx(p, xp=xp), + _sort_cmplx(p2, xp=xp)) + + def test_refs(self, xp): + # Compare to http://www.crbond.com/papers/bsf2.pdf + # "Delay Normalized Bessel Polynomial Coefficients" + bond_b = xp.asarray([10395.0], dtype=xp.float64) + bond_a = xp.asarray([1.0, 21, 210, 1260, 4725, 10395, 10395], dtype=xp.float64) + b, a = bessel(6, xp.asarray(1.0), norm='delay', analog=True) + xp_assert_close(b, bond_b) + xp_assert_close(a, bond_a) + + # "Delay Normalized Bessel Pole Locations" + bond_poles = { + 1: [-1.0000000000], + 2: [-1.5000000000 + 0.8660254038j], + 3: [-1.8389073227 + 1.7543809598j, -2.3221853546], + 4: [-2.1037893972 + 2.6574180419j, -2.8962106028 + 0.8672341289j], + 5: [-2.3246743032 + 3.5710229203j, -3.3519563992 + 1.7426614162j, + -3.6467385953], + 6: [-2.5159322478 + 4.4926729537j, -3.7357083563 + 2.6262723114j, + -4.2483593959 + 0.8675096732j], + 7: [-2.6856768789 + 5.4206941307j, -4.0701391636 + 3.5171740477j, + -4.7582905282 + 1.7392860611j, -4.9717868585], + 8: [-2.8389839489 + 6.3539112986j, -4.3682892172 + 4.4144425005j, + -5.2048407906 + 2.6161751526j, -5.5878860433 + 0.8676144454j], + 9: [-2.9792607982 + 7.2914636883j, -4.6384398872 + 5.3172716754j, + -5.6044218195 + 3.4981569179j, -6.1293679043 + 1.7378483835j, + -6.2970191817], + 10: [-3.1089162336 + 8.2326994591j, -4.8862195669 + 6.2249854825j, + -5.9675283286 + 4.3849471889j, -6.6152909655 + 2.6115679208j, + -6.9220449054 + 0.8676651955j] + } + + for N in range(1, 11): + p1 = np.sort(bond_poles[N]) + z, p, k = besselap(N, 'delay', xp=xp) + assert array_namespace(z) == array_namespace(p) == xp + p2 = np.sort(np.concatenate(_cplxreal(_xp_copy_to_numpy(p)))) + assert_array_almost_equal(xp.asarray(p2), xp.asarray(p1), decimal=10) + + # "Frequency Normalized Bessel Pole Locations" + bond_poles = { + 1: [-1.0000000000], + 2: [-1.1016013306 + 0.6360098248j], + 3: [-1.0474091610 + 0.9992644363j, -1.3226757999], + 4: [-0.9952087644 + 1.2571057395j, -1.3700678306 + 0.4102497175j], + 5: [-0.9576765486 + 1.4711243207j, -1.3808773259 + 0.7179095876j, + -1.5023162714], + 6: [-0.9306565229 + 1.6618632689j, -1.3818580976 + 0.9714718907j, + -1.5714904036 + 0.3208963742j], + 7: [-0.9098677806 + 1.8364513530j, -1.3789032168 + 1.1915667778j, + -1.6120387662 + 0.5892445069j, -1.6843681793], + 8: [-0.8928697188 + 1.9983258436j, -1.3738412176 + 1.3883565759j, + -1.6369394181 + 0.8227956251j, -1.7574084004 + 0.2728675751j], + 9: [-0.8783992762 + 2.1498005243j, -1.3675883098 + 1.5677337122j, + -1.6523964846 + 1.0313895670j, -1.8071705350 + 0.5123837306j, + -1.8566005012], + 10: [-0.8657569017 + 2.2926048310j, -1.3606922784 + 1.7335057427j, + -1.6618102414 + 1.2211002186j, -1.8421962445 + 0.7272575978j, + -1.9276196914 + 0.2416234710j] + } + + for N in range(1, 11): + p1 = np.sort(bond_poles[N]) + z, p, k = besselap(N, 'mag', xp=xp) + assert array_namespace(z) == array_namespace(p) == xp + p2 = np.sort(np.concatenate(_cplxreal(_xp_copy_to_numpy(p)))) + assert_array_almost_equal(xp.asarray(p2), xp.asarray(p1), decimal=10) + + # Compare to https://www.ranecommercial.com/legacy/note147.html + # "Table 1 - Bessel Crossovers of Second, Third, and Fourth-Order" + a = xp.asarray([1, 1, 1/3], dtype=xp.float64) + b2, a2 = bessel(2, xp.asarray(1.), norm='delay', analog=True) + xp_assert_close(a2/b2, xp.flip(a)) + + a = xp.asarray([1, 1, 2/5, 1/15], dtype=xp.float64) + b2, a2 = bessel(3, xp.asarray(1.), norm='delay', analog=True) + xp_assert_close(a2/b2, xp.flip(a)) + + a = xp.asarray([1, 1, 9/21, 2/21, 1/105], dtype=xp.float64) + b2, a2 = bessel(4, xp.asarray(1.), norm='delay', analog=True) + xp_assert_close(a2/b2, xp.flip(a)) + + a = xp.asarray([1, math.sqrt(3), 1], dtype=xp.float64) + b2, a2 = bessel(2, xp.asarray(1.), norm='phase', analog=True) + xp_assert_close(a2/b2, xp.flip(a)) + + # TODO: Why so inaccurate? Is reference flawed? + a = xp.asarray([1, 2.481, 2.463, 1.018], dtype=xp.float64) + b2, a2 = bessel(3, xp.asarray(1.), norm='phase', analog=True) + assert_array_almost_equal(a2/b2, xp.flip(a), decimal=1) + + # TODO: Why so inaccurate? Is reference flawed? + a = xp.asarray([1, 3.240, 4.5, 3.240, 1.050], dtype=xp.float64) + b2, a2 = bessel(4, xp.asarray(1.), norm='phase', analog=True) + assert_array_almost_equal(a2/b2, xp.flip(a), decimal=1) + + # Table of -3 dB factors: + N, scale = 2, xp.asarray([1.272, 1.272], dtype=xp.complex128) + scale2 = besselap(N, 'mag', xp=xp)[1] / besselap(N, 'phase', xp=xp)[1] + assert_array_almost_equal(scale2, scale, decimal=3) + + # TODO: Why so inaccurate? Is reference flawed? + N, scale = 3, xp.asarray([1.413, 1.413, 1.413], dtype=xp.complex128) + scale2 = besselap(N, 'mag', xp=xp)[1] / besselap(N, 'phase', xp=xp)[1] + assert_array_almost_equal(scale2, scale, decimal=2) + + # TODO: Why so inaccurate? Is reference flawed? + N, scale = 4, xp.asarray([1.533]*4, dtype=xp.complex128) + scale2 = besselap(N, 'mag', xp=xp)[1] / besselap(N, 'phase', xp=xp)[1] + assert_array_almost_equal(scale2, scale, decimal=1) + + @pytest.mark.xfail(reason="Failing in mypy workflow - see gh-23902") + def test_hardcoded(self, xp): + # Compare to values from original hardcoded implementation + originals = { + 0: [], + 1: [-1], + 2: [-.8660254037844386467637229 + .4999999999999999999999996j], + 3: [-.9416000265332067855971980, + -.7456403858480766441810907 + .7113666249728352680992154j], + 4: [-.6572111716718829545787788 + .8301614350048733772399715j, + -.9047587967882449459642624 + .2709187330038746636700926j], + 5: [-.9264420773877602247196260, + -.8515536193688395541722677 + .4427174639443327209850002j, + -.5905759446119191779319432 + .9072067564574549539291747j], + 6: [-.9093906830472271808050953 + .1856964396793046769246397j, + -.7996541858328288520243325 + .5621717346937317988594118j, + -.5385526816693109683073792 + .9616876881954277199245657j], + 7: [-.9194871556490290014311619, + -.8800029341523374639772340 + .3216652762307739398381830j, + -.7527355434093214462291616 + .6504696305522550699212995j, + -.4966917256672316755024763 + 1.002508508454420401230220j], + 8: [-.9096831546652910216327629 + .1412437976671422927888150j, + -.8473250802359334320103023 + .4259017538272934994996429j, + -.7111381808485399250796172 + .7186517314108401705762571j, + -.4621740412532122027072175 + 1.034388681126901058116589j], + 9: [-.9154957797499037686769223, + -.8911217017079759323183848 + .2526580934582164192308115j, + -.8148021112269012975514135 + .5085815689631499483745341j, + -.6743622686854761980403401 + .7730546212691183706919682j, + -.4331415561553618854685942 + 1.060073670135929666774323j], + 10: [-.9091347320900502436826431 + .1139583137335511169927714j, + -.8688459641284764527921864 + .3430008233766309973110589j, + -.7837694413101441082655890 + .5759147538499947070009852j, + -.6417513866988316136190854 + .8175836167191017226233947j, + -.4083220732868861566219785 + 1.081274842819124562037210j], + 11: [-.9129067244518981934637318, + -.8963656705721166099815744 + .2080480375071031919692341j, + -.8453044014712962954184557 + .4178696917801248292797448j, + -.7546938934722303128102142 + .6319150050721846494520941j, + -.6126871554915194054182909 + .8547813893314764631518509j, + -.3868149510055090879155425 + 1.099117466763120928733632j], + 12: [-.9084478234140682638817772 + 95506365213450398415258360e-27j, + -.8802534342016826507901575 + .2871779503524226723615457j, + -.8217296939939077285792834 + .4810212115100676440620548j, + -.7276681615395159454547013 + .6792961178764694160048987j, + -.5866369321861477207528215 + .8863772751320727026622149j, + -.3679640085526312839425808 + 1.114373575641546257595657j], + 13: [-.9110914665984182781070663, + -.8991314665475196220910718 + .1768342956161043620980863j, + -.8625094198260548711573628 + .3547413731172988997754038j, + -.7987460692470972510394686 + .5350752120696801938272504j, + -.7026234675721275653944062 + .7199611890171304131266374j, + -.5631559842430199266325818 + .9135900338325109684927731j, + -.3512792323389821669401925 + 1.127591548317705678613239j], + 14: [-.9077932138396487614720659 + 82196399419401501888968130e-27j, + -.8869506674916445312089167 + .2470079178765333183201435j, + -.8441199160909851197897667 + .4131653825102692595237260j, + -.7766591387063623897344648 + .5819170677377608590492434j, + -.6794256425119233117869491 + .7552857305042033418417492j, + -.5418766775112297376541293 + .9373043683516919569183099j, + -.3363868224902037330610040 + 1.139172297839859991370924j], + 15: [-.9097482363849064167228581, + -.9006981694176978324932918 + .1537681197278439351298882j, + -.8731264620834984978337843 + .3082352470564267657715883j, + -.8256631452587146506294553 + .4642348752734325631275134j, + -.7556027168970728127850416 + .6229396358758267198938604j, + -.6579196593110998676999362 + .7862895503722515897065645j, + -.5224954069658330616875186 + .9581787261092526478889345j, + -.3229963059766444287113517 + 1.149416154583629539665297j], + 16: [-.9072099595087001356491337 + 72142113041117326028823950e-27j, + -.8911723070323647674780132 + .2167089659900576449410059j, + -.8584264231521330481755780 + .3621697271802065647661080j, + -.8074790293236003885306146 + .5092933751171800179676218j, + -.7356166304713115980927279 + .6591950877860393745845254j, + -.6379502514039066715773828 + .8137453537108761895522580j, + -.5047606444424766743309967 + .9767137477799090692947061j, + -.3108782755645387813283867 + 1.158552841199330479412225j], + 17: [-.9087141161336397432860029, + -.9016273850787285964692844 + .1360267995173024591237303j, + -.8801100704438627158492165 + .2725347156478803885651973j, + -.8433414495836129204455491 + .4100759282910021624185986j, + -.7897644147799708220288138 + .5493724405281088674296232j, + -.7166893842372349049842743 + .6914936286393609433305754j, + -.6193710717342144521602448 + .8382497252826992979368621j, + -.4884629337672704194973683 + .9932971956316781632345466j, + -.2998489459990082015466971 + 1.166761272925668786676672j], + 18: [-.9067004324162775554189031 + 64279241063930693839360680e-27j, + -.8939764278132455733032155 + .1930374640894758606940586j, + -.8681095503628830078317207 + .3224204925163257604931634j, + -.8281885016242836608829018 + .4529385697815916950149364j, + -.7726285030739558780127746 + .5852778162086640620016316j, + -.6987821445005273020051878 + .7204696509726630531663123j, + -.6020482668090644386627299 + .8602708961893664447167418j, + -.4734268069916151511140032 + 1.008234300314801077034158j, + -.2897592029880489845789953 + 1.174183010600059128532230j], + 19: [-.9078934217899404528985092, + -.9021937639390660668922536 + .1219568381872026517578164j, + -.8849290585034385274001112 + .2442590757549818229026280j, + -.8555768765618421591093993 + .3672925896399872304734923j, + -.8131725551578197705476160 + .4915365035562459055630005j, + -.7561260971541629355231897 + .6176483917970178919174173j, + -.6818424412912442033411634 + .7466272357947761283262338j, + -.5858613321217832644813602 + .8801817131014566284786759j, + -.4595043449730988600785456 + 1.021768776912671221830298j, + -.2804866851439370027628724 + 1.180931628453291873626003j], + 20: [-.9062570115576771146523497 + 57961780277849516990208850e-27j, + -.8959150941925768608568248 + .1740317175918705058595844j, + -.8749560316673332850673214 + .2905559296567908031706902j, + -.8427907479956670633544106 + .4078917326291934082132821j, + -.7984251191290606875799876 + .5264942388817132427317659j, + -.7402780309646768991232610 + .6469975237605228320268752j, + -.6658120544829934193890626 + .7703721701100763015154510j, + -.5707026806915714094398061 + .8982829066468255593407161j, + -.4465700698205149555701841 + 1.034097702560842962315411j, + -.2719299580251652601727704 + 1.187099379810885886139638j], + 21: [-.9072262653142957028884077, + -.9025428073192696303995083 + .1105252572789856480992275j, + -.8883808106664449854431605 + .2213069215084350419975358j, + -.8643915813643204553970169 + .3326258512522187083009453j, + -.8299435470674444100273463 + .4448177739407956609694059j, + -.7840287980408341576100581 + .5583186348022854707564856j, + -.7250839687106612822281339 + .6737426063024382240549898j, + -.6506315378609463397807996 + .7920349342629491368548074j, + -.5564766488918562465935297 + .9148198405846724121600860j, + -.4345168906815271799687308 + 1.045382255856986531461592j, + -.2640041595834031147954813 + 1.192762031948052470183960j], + 22: [-.9058702269930872551848625 + 52774908289999045189007100e-27j, + -.8972983138153530955952835 + .1584351912289865608659759j, + -.8799661455640176154025352 + .2644363039201535049656450j, + -.8534754036851687233084587 + .3710389319482319823405321j, + -.8171682088462720394344996 + .4785619492202780899653575j, + -.7700332930556816872932937 + .5874255426351153211965601j, + -.7105305456418785989070935 + .6982266265924524000098548j, + -.6362427683267827226840153 + .8118875040246347267248508j, + -.5430983056306302779658129 + .9299947824439872998916657j, + -.4232528745642628461715044 + 1.055755605227545931204656j, + -.2566376987939318038016012 + 1.197982433555213008346532j], + 23: [-.9066732476324988168207439, + -.9027564979912504609412993 + .1010534335314045013252480j, + -.8909283242471251458653994 + .2023024699381223418195228j, + -.8709469395587416239596874 + .3039581993950041588888925j, + -.8423805948021127057054288 + .4062657948237602726779246j, + -.8045561642053176205623187 + .5095305912227258268309528j, + -.7564660146829880581478138 + .6141594859476032127216463j, + -.6965966033912705387505040 + .7207341374753046970247055j, + -.6225903228771341778273152 + .8301558302812980678845563j, + -.5304922463810191698502226 + .9439760364018300083750242j, + -.4126986617510148836149955 + 1.065328794475513585531053j, + -.2497697202208956030229911 + 1.202813187870697831365338j], + 24: [-.9055312363372773709269407 + 48440066540478700874836350e-27j, + -.8983105104397872954053307 + .1454056133873610120105857j, + -.8837358034555706623131950 + .2426335234401383076544239j, + -.8615278304016353651120610 + .3403202112618624773397257j, + -.8312326466813240652679563 + .4386985933597305434577492j, + -.7921695462343492518845446 + .5380628490968016700338001j, + -.7433392285088529449175873 + .6388084216222567930378296j, + -.6832565803536521302816011 + .7415032695091650806797753j, + -.6096221567378335562589532 + .8470292433077202380020454j, + -.5185914574820317343536707 + .9569048385259054576937721j, + -.4027853855197518014786978 + 1.074195196518674765143729j, + -.2433481337524869675825448 + 1.207298683731972524975429j], + 25: [-.9062073871811708652496104, + -.9028833390228020537142561 + 93077131185102967450643820e-27j, + -.8928551459883548836774529 + .1863068969804300712287138j, + -.8759497989677857803656239 + .2798521321771408719327250j, + -.8518616886554019782346493 + .3738977875907595009446142j, + -.8201226043936880253962552 + .4686668574656966589020580j, + -.7800496278186497225905443 + .5644441210349710332887354j, + -.7306549271849967721596735 + .6616149647357748681460822j, + -.6704827128029559528610523 + .7607348858167839877987008j, + -.5972898661335557242320528 + .8626676330388028512598538j, + -.5073362861078468845461362 + .9689006305344868494672405j, + -.3934529878191079606023847 + 1.082433927173831581956863j, + -.2373280669322028974199184 + 1.211476658382565356579418j], + } + for N in originals: + p1 = xp.asarray( + np.union1d(originals[N], np.conj(originals[N])), + dtype=xp.complex128 + ) + p2 = besselap(N, xp=xp)[1] + xp_assert_close(_sort_cmplx(p2, xp=xp), + _sort_cmplx(p1, xp=xp), rtol=1e-14) + + def test_norm_phase(self, xp): + # Test some orders and frequencies and see that they have the right + # phase at w0 + if is_torch(xp) and DEFAULT_F32: + pytest.xfail(reason="inaccurate on torch with float32") + for N in (1, 2, 3, 4, 5, 51, 72): + for w0 in (1, 100): + b, a = bessel(N, xp.asarray(w0), analog=True, norm='phase') + assert array_namespace(b) == array_namespace(a) == xp + w = np.linspace(0, w0, 100) + w, h = freqs(_xp_copy_to_numpy(b), _xp_copy_to_numpy(a), w) + phase = np.unwrap(np.angle(h)) + xp_assert_close( + xp.asarray(phase[[0, -1]], dtype=xp.float64), + xp.asarray([0, -N*xp.pi/4], dtype=xp.float64), rtol=1e-1 + ) + + def test_norm_mag(self, xp): + # Test some orders and frequencies and see that they have the right + # mag at w0 + if DEFAULT_F32 and is_torch(xp): + pytest.skip(reason="overflow occurs with float32 on torch") + for N in (1, 2, 3, 4, 5, 51, 72): + for w0 in (1, 100): + b, a = bessel(N, xp.asarray(w0), analog=True, norm='mag') + assert array_namespace(b) == array_namespace(a) == xp + w = [0.0, w0] + w, h = freqs(_xp_copy_to_numpy(b), _xp_copy_to_numpy(a), w) + mag = np.abs(h) + xp_assert_close( + xp.asarray(mag), xp.asarray([1, 1/math.sqrt(2)], dtype=xp.float64) + ) + + def test_norm_delay(self, xp): + # Test some orders and frequencies and see that they have the right + # delay at DC + if DEFAULT_F32 and is_torch(xp): + pytest.skip(reason="overflow occurs with float32 on torch") + for N in (1, 2, 3, 4, 5, 51, 72): + for w0 in (1, 100): + b, a = bessel(N, xp.asarray(w0), analog=True, norm='delay') + w = np.linspace(0, 10*w0, 1000) + w, h = freqs(_xp_copy_to_numpy(b), _xp_copy_to_numpy(a), w) + unwr_h = np.unwrap(np.angle(h)) + delay = -np.diff(unwr_h) / np.diff(w) + assert math.isclose(delay[0], 1/w0, rel_tol=1e-4) + + def test_norm_factor(self): + mpmath_values = { + 1: 1.0, 2: 1.361654128716130520, 3: 1.755672368681210649, + 4: 2.113917674904215843, 5: 2.427410702152628137, + 6: 2.703395061202921876, 7: 2.951722147038722771, + 8: 3.179617237510651330, 9: 3.391693138911660101, + 10: 3.590980594569163482, 11: 3.779607416439620092, + 12: 3.959150821144285315, 13: 4.130825499383535980, + 14: 4.295593409533637564, 15: 4.454233021624377494, + 16: 4.607385465472647917, 17: 4.755586548961147727, + 18: 4.899289677284488007, 19: 5.038882681488207605, + 20: 5.174700441742707423, 21: 5.307034531360917274, + 22: 5.436140703250035999, 23: 5.562244783787878196, + 24: 5.685547371295963521, 25: 5.806227623775418541, + 50: 8.268963160013226298, 51: 8.352374541546012058, + } + for N in mpmath_values: + z, p, k = besselap(N, 'delay') + xp_assert_close(_norm_factor(p, k), mpmath_values[N], rtol=1e-13) + + def test_bessel_poly(self): + xp_assert_equal(_bessel_poly(5), [945, 945, 420, 105, 15, 1]) + xp_assert_equal(_bessel_poly(4, True), [1, 10, 45, 105, 105]) + + def test_bessel_zeros(self): + xp_assert_equal(_bessel_zeros(0), []) + + def test_invalid(self): + assert_raises(ValueError, besselap, 5, 'nonsense') + assert_raises(ValueError, besselap, -5) + assert_raises(ValueError, besselap, 3.2) + assert_raises(ValueError, _bessel_poly, -3) + assert_raises(ValueError, _bessel_poly, 3.3) + + @pytest.mark.fail_slow(10) + def test_fs_param(self): + for norm in ('phase', 'mag', 'delay'): + for fs in (900, 900.1, 1234.567): + for N in (0, 1, 2, 3, 10): + for fc in (100, 100.1, 432.12345): + for btype in ('lp', 'hp'): + ba1 = bessel(N, fc, btype, norm=norm, fs=fs) + ba2 = bessel(N, fc/(fs/2), btype, norm=norm) + for ba1_, ba2_ in zip(ba1, ba2): + xp_assert_close(ba1_, ba2_) + for fc in ((100, 200), (100.1, 200.2), (321.123, 432.123)): + for btype in ('bp', 'bs'): + ba1 = bessel(N, fc, btype, norm=norm, fs=fs) + for seq in (list, tuple, array): + fcnorm = seq([f/(fs/2) for f in fc]) + ba2 = bessel(N, fcnorm, btype, norm=norm) + for ba1_, ba2_ in zip(ba1, ba2): + xp_assert_close(ba1_, ba2_) + + +@skip_xp_backends("dask.array", reason="https://github.com/dask/dask/issues/11883") +@make_xp_test_case(butter) +class TestButter: + + def test_degenerate(self, xp): + # 0-order filter is just a passthrough + b, a = butter(0, xp.asarray(1), analog=True) + xp_assert_equal(b, xp.asarray([1.0], dtype=xp.float64)) + xp_assert_equal(a, xp.asarray([1.0], dtype=xp.float64)) + + # 1-order filter is same for all types + b, a = butter(1, xp.asarray(1), analog=True) + assert_array_almost_equal(b, xp.asarray([1.0], dtype=xp.float64)) + assert_array_almost_equal(a, xp.asarray([1.0, 1.0], dtype=xp.float64)) + + z, p, k = butter(1, xp.asarray(0.3), output='zpk') + xp_assert_equal(z, xp.asarray([-1.0], dtype=xp.float64)) + xp_assert_close( + p, + xp.asarray([3.249196962329063e-01 + 0j], dtype=xp.complex128), + rtol=1e-14 if not DEFAULT_F32 else 1e-7 + ) + assert math.isclose( + k, 3.375401518835469e-01, rel_tol=1e-14 if not DEFAULT_F32 else 1e-7 + ) + + def test_basic(self, xp): + # analog s-plane + for N in range(25): + wn = 0.01 + z, p, k = butter(N, xp.asarray(wn), 'low', analog=True, output='zpk') + assert_array_almost_equal(z, xp.asarray([], dtype=xp.float64)) + assert p.shape[0] == N + # All poles should be at distance wn from origin + assert_array_almost_equal(xp.abs(p), xp.asarray(wn, dtype=xp.float64)) + assert all(xp.real(p) <= 0) # No poles in right half of S-plane + assert math.isclose(k, wn**N, rel_tol=1e-9 if not DEFAULT_F32 else 1e-6) + + # digital z-plane + for N in range(25): + wn = 0.01 + z, p, k = butter(N, xp.asarray(wn), 'high', analog=False, output='zpk') + xp_assert_equal(z, xp.ones(N, dtype=xp.float64)) # All zeros exactly at DC + assert xp.all(xp.abs(p) <= 1) # No poles outside unit circle + + b1, a1 = butter(2, xp.asarray(1), analog=True) + assert_array_almost_equal(b1, xp.asarray([1.0], dtype=xp.float64)) + assert_array_almost_equal( + a1, xp.asarray([1, math.sqrt(2), 1], dtype=xp.float64) + ) + + b2, a2 = butter(5, xp.asarray(1.), analog=True) + assert_array_almost_equal(b2, xp.asarray([1], dtype=xp.float64)) + assert_array_almost_equal( + a2, xp.asarray([1, 3.2361, 5.2361, + 5.2361, 3.2361, 1], dtype=xp.float64), + decimal=4 + ) + + b3, a3 = butter(10, xp.asarray(1.0), analog=True) + assert_array_almost_equal(b3, xp.asarray([1.0], dtype=xp.float64)) + assert_array_almost_equal( + a3, xp.asarray([1, 6.3925, 20.4317, 42.8021, 64.8824, + 74.2334, 64.8824, 42.8021, 20.4317, + 6.3925, 1], dtype=xp.float64), + decimal=4 + ) + + b2, a2 = butter(19, xp.asarray(1.0441379169150726), analog=True) + assert_array_almost_equal( + b2, xp.asarray([2.2720], dtype=xp.float64), decimal=4 + ) + assert_array_almost_equal( + a2, 1.0e+004 * xp.asarray([ + 0.0001, 0.0013, 0.0080, 0.0335, 0.1045, 0.2570, + 0.5164, 0.8669, 1.2338, 1.5010, 1.5672, 1.4044, + 1.0759, 0.6986, 0.3791, 0.1681, 0.0588, 0.0153, + 0.0026, 0.0002], dtype=xp.float64), + decimal=0 + ) + + b, a = butter(5, xp.asarray(0.4)) + assert_array_almost_equal( + b, xp.asarray([0.0219, 0.1097, 0.2194, + 0.2194, 0.1097, 0.0219], dtype=xp.float64), + decimal=4 + ) + assert_array_almost_equal( + a, xp.asarray([1.0000, -0.9853, 0.9738, + -0.3864, 0.1112, -0.0113], dtype=xp.float64), + decimal=4 + ) + + def test_highpass(self, xp): + # highpass, high even order + z, p, k = butter(28, xp.asarray(0.43), 'high', output='zpk') + z2 = xp.ones(28, dtype=xp.float64) + p2 = [ + 2.068257195514592e-01 + 9.238294351481734e-01j, + 2.068257195514592e-01 - 9.238294351481734e-01j, + 1.874933103892023e-01 + 8.269455076775277e-01j, + 1.874933103892023e-01 - 8.269455076775277e-01j, + 1.717435567330153e-01 + 7.383078571194629e-01j, + 1.717435567330153e-01 - 7.383078571194629e-01j, + 1.588266870755982e-01 + 6.564623730651094e-01j, + 1.588266870755982e-01 - 6.564623730651094e-01j, + 1.481881532502603e-01 + 5.802343458081779e-01j, + 1.481881532502603e-01 - 5.802343458081779e-01j, + 1.394122576319697e-01 + 5.086609000582009e-01j, + 1.394122576319697e-01 - 5.086609000582009e-01j, + 1.321840881809715e-01 + 4.409411734716436e-01j, + 1.321840881809715e-01 - 4.409411734716436e-01j, + 1.262633413354405e-01 + 3.763990035551881e-01j, + 1.262633413354405e-01 - 3.763990035551881e-01j, + 1.214660449478046e-01 + 3.144545234797277e-01j, + 1.214660449478046e-01 - 3.144545234797277e-01j, + 1.104868766650320e-01 + 2.771505404367791e-02j, + 1.104868766650320e-01 - 2.771505404367791e-02j, + 1.111768629525075e-01 + 8.331369153155753e-02j, + 1.111768629525075e-01 - 8.331369153155753e-02j, + 1.125740630842972e-01 + 1.394219509611784e-01j, + 1.125740630842972e-01 - 1.394219509611784e-01j, + 1.147138487992747e-01 + 1.963932363793666e-01j, + 1.147138487992747e-01 - 1.963932363793666e-01j, + 1.176516491045901e-01 + 2.546021573417188e-01j, + 1.176516491045901e-01 - 2.546021573417188e-01j, + ] + p2 = xp.asarray(p2, dtype=xp.complex128) + k2 = 1.446671081817286e-06 + xp_assert_equal(z, z2) + xp_assert_close(_sort_cmplx(p, xp), _sort_cmplx(p2, xp), rtol=1e-7) + assert math.isclose(k, k2, rel_tol=1e-10 if not DEFAULT_F32 else 1e-6) + + # highpass, high odd order + z, p, k = butter(27, xp.asarray(0.56), 'high', output='zpk') + z2 = xp.ones(27, dtype=xp.float64) + p2 = [ + -1.772572785680147e-01 + 9.276431102995948e-01j, + -1.772572785680147e-01 - 9.276431102995948e-01j, + -1.600766565322114e-01 + 8.264026279893268e-01j, + -1.600766565322114e-01 - 8.264026279893268e-01j, + -1.461948419016121e-01 + 7.341841939120078e-01j, + -1.461948419016121e-01 - 7.341841939120078e-01j, + -1.348975284762046e-01 + 6.493235066053785e-01j, + -1.348975284762046e-01 - 6.493235066053785e-01j, + -1.256628210712206e-01 + 5.704921366889227e-01j, + -1.256628210712206e-01 - 5.704921366889227e-01j, + -1.181038235962314e-01 + 4.966120551231630e-01j, + -1.181038235962314e-01 - 4.966120551231630e-01j, + -1.119304913239356e-01 + 4.267938916403775e-01j, + -1.119304913239356e-01 - 4.267938916403775e-01j, + -1.069237739782691e-01 + 3.602914879527338e-01j, + -1.069237739782691e-01 - 3.602914879527338e-01j, + -1.029178030691416e-01 + 2.964677964142126e-01j, + -1.029178030691416e-01 - 2.964677964142126e-01j, + -9.978747500816100e-02 + 2.347687643085738e-01j, + -9.978747500816100e-02 - 2.347687643085738e-01j, + -9.743974496324025e-02 + 1.747028739092479e-01j, + -9.743974496324025e-02 - 1.747028739092479e-01j, + -9.580754551625957e-02 + 1.158246860771989e-01j, + -9.580754551625957e-02 - 1.158246860771989e-01j, + -9.484562207782568e-02 + 5.772118357151691e-02j, + -9.484562207782568e-02 - 5.772118357151691e-02j, + -9.452783117928215e-02 + ] + p2 = xp.asarray(p2, dtype=xp.complex128) + k2 = 9.585686688851069e-09 + xp_assert_equal(z, z2) + xp_assert_close( + _sort_cmplx(p, xp), _sort_cmplx(p2, xp), + rtol=1e-8 if not DEFAULT_F32 else 1e-6 + ) + assert math.isclose(k, k2, abs_tol=1e-13) + + def test_bandpass(self, xp): + z, p, k = butter(8, xp.asarray([0.25, 0.33]), 'band', output='zpk') + z2 = [1 + 0j, 1, 1, 1, 1, 1, 1, 1, + -1, -1, -1, -1, -1, -1, -1, -1] + p2 = [ + 4.979909925436156e-01 + 8.367609424799387e-01j, + 4.979909925436156e-01 - 8.367609424799387e-01j, + 4.913338722555539e-01 + 7.866774509868817e-01j, + 4.913338722555539e-01 - 7.866774509868817e-01j, + 5.035229361778706e-01 + 7.401147376726750e-01j, + 5.035229361778706e-01 - 7.401147376726750e-01j, + 5.307617160406101e-01 + 7.029184459442954e-01j, + 5.307617160406101e-01 - 7.029184459442954e-01j, + 5.680556159453138e-01 + 6.788228792952775e-01j, + 5.680556159453138e-01 - 6.788228792952775e-01j, + 6.100962560818854e-01 + 6.693849403338664e-01j, + 6.100962560818854e-01 - 6.693849403338664e-01j, + 6.904694312740631e-01 + 6.930501690145245e-01j, + 6.904694312740631e-01 - 6.930501690145245e-01j, + 6.521767004237027e-01 + 6.744414640183752e-01j, + 6.521767004237027e-01 - 6.744414640183752e-01j, + ] + z2 = xp.asarray(z2, dtype=xp.complex128) + p2 = xp.asarray(p2, dtype=xp.complex128) + k2 = 3.398854055800844e-08 + xp_assert_equal(z, z2, check_dtype=False) + xp_assert_close( + _sort_cmplx(p, xp), _sort_cmplx(p2, xp), + rtol=1e-13 if not DEFAULT_F32 else 1e-7 + ) + assert math.isclose(k, k2, rel_tol=1e-13 if not DEFAULT_F32 else 1e-5) + + # bandpass analog + z, p, k = butter(4, xp.asarray([90.5, 110.5]), 'bp', analog=True, output='zpk') + z2 = xp.zeros(4, dtype=xp.complex128) + p2 = [ + -4.179137760733086e+00 + 1.095935899082837e+02j, + -4.179137760733086e+00 - 1.095935899082837e+02j, + -9.593598668443835e+00 + 1.034745398029734e+02j, + -9.593598668443835e+00 - 1.034745398029734e+02j, + -8.883991981781929e+00 + 9.582087115567160e+01j, + -8.883991981781929e+00 - 9.582087115567160e+01j, + -3.474530886568715e+00 + 9.111599925805801e+01j, + -3.474530886568715e+00 - 9.111599925805801e+01j, + ] + p2 = xp.asarray(p2, dtype=xp.complex128) + k2 = 1.600000000000001e+05 + xp_assert_equal(z, z2) + xp_assert_close(_sort_cmplx(p, xp), _sort_cmplx(p2, xp)) + assert math.isclose(k, k2, rel_tol=1e-15) + + def test_bandstop(self, xp): + z, p, k = butter(7, xp.asarray([0.45, 0.56]), 'stop', output='zpk') + z2 = [-1.594474531383421e-02 + 9.998728744679880e-01j, + -1.594474531383421e-02 - 9.998728744679880e-01j, + -1.594474531383421e-02 + 9.998728744679880e-01j, + -1.594474531383421e-02 - 9.998728744679880e-01j, + -1.594474531383421e-02 + 9.998728744679880e-01j, + -1.594474531383421e-02 - 9.998728744679880e-01j, + -1.594474531383421e-02 + 9.998728744679880e-01j, + -1.594474531383421e-02 - 9.998728744679880e-01j, + -1.594474531383421e-02 + 9.998728744679880e-01j, + -1.594474531383421e-02 - 9.998728744679880e-01j, + -1.594474531383421e-02 + 9.998728744679880e-01j, + -1.594474531383421e-02 - 9.998728744679880e-01j, + -1.594474531383421e-02 + 9.998728744679880e-01j, + -1.594474531383421e-02 - 9.998728744679880e-01j] + p2 = [-1.766850742887729e-01 + 9.466951258673900e-01j, + -1.766850742887729e-01 - 9.466951258673900e-01j, + 1.467897662432886e-01 + 9.515917126462422e-01j, + 1.467897662432886e-01 - 9.515917126462422e-01j, + -1.370083529426906e-01 + 8.880376681273993e-01j, + -1.370083529426906e-01 - 8.880376681273993e-01j, + 1.086774544701390e-01 + 8.915240810704319e-01j, + 1.086774544701390e-01 - 8.915240810704319e-01j, + -7.982704457700891e-02 + 8.506056315273435e-01j, + -7.982704457700891e-02 - 8.506056315273435e-01j, + 5.238812787110331e-02 + 8.524011102699969e-01j, + 5.238812787110331e-02 - 8.524011102699969e-01j, + -1.357545000491310e-02 + 8.382287744986582e-01j, + -1.357545000491310e-02 - 8.382287744986582e-01j] + z2 = xp.asarray(z2, dtype=xp.complex128) + p2 = xp.asarray(p2, dtype=xp.complex128) + k2 = 4.577122512960063e-01 + xp_assert_close(_sort_cmplx(z, xp), _sort_cmplx(z2, xp)) + xp_assert_close(_sort_cmplx(p, xp), _sort_cmplx(p2, xp)) + assert math.isclose(k, k2, rel_tol=1e-14 if not DEFAULT_F32 else 1e-6) + + @xfail_xp_backends("cupy", reason="inaccurate on CuPy") + def test_ba_output(self, xp): + b, a = butter(4, xp.asarray([100, 300]), 'bandpass', analog=True) + b2 = [1.6e+09, 0, 0, 0, 0] + a2 = [1.000000000000000e+00, 5.226251859505511e+02, + 2.565685424949238e+05, 6.794127417357160e+07, + 1.519411254969542e+10, 2.038238225207147e+12, + 2.309116882454312e+14, 1.411088002066486e+16, + 8.099999999999991e+17] + b2 = xp.asarray(b2, dtype=xp.float64) + a2 = xp.asarray(a2, dtype=xp.float64) + xp_assert_close(b, b2, rtol=1e-14) + xp_assert_close(a, a2, rtol=1e-14) + + def test_fs_param(self): + for fs in (900, 900.1, 1234.567): + for N in (0, 1, 2, 3, 10): + for fc in (100, 100.1, 432.12345): + for btype in ('lp', 'hp'): + ba1 = butter(N, fc, btype, fs=fs) + ba2 = butter(N, fc/(fs/2), btype) + for ba1_, ba2_ in zip(ba1, ba2): + xp_assert_close(ba1_, ba2_) + for fc in ((100, 200), (100.1, 200.2), (321.123, 432.123)): + for btype in ('bp', 'bs'): + ba1 = butter(N, fc, btype, fs=fs) + for seq in (list, tuple, array): + fcnorm = seq([f/(fs/2) for f in fc]) + ba2 = butter(N, fcnorm, btype) + for ba1_, ba2_ in zip(ba1, ba2): + xp_assert_close(ba1_, ba2_) + + +@skip_xp_backends("dask.array", reason="https://github.com/dask/dask/issues/11883") +@make_xp_test_case(cheby1) +class TestCheby1: + + def test_degenerate(self, xp): + # 0-order filter is just a passthrough + # Even-order filters have DC gain of -rp dB + b, a = cheby1(0, 10*math.log10(2), xp.asarray(1), analog=True) + assert_array_almost_equal( + b, xp.asarray([1 / math.sqrt(2)], dtype=xp.float64) + ) + xp_assert_equal(a, xp.asarray([1.0], dtype=xp.float64)) + + # 1-order filter is same for all types + b, a = cheby1(1, 10*math.log10(2), xp.asarray(1), analog=True) + assert_array_almost_equal(b, xp.asarray([1.], dtype=xp.float64)) + assert_array_almost_equal(a, xp.asarray([1., 1], dtype=xp.float64)) + + z, p, k = cheby1(1, 0.1, xp.asarray(0.3), output='zpk') + xp_assert_equal(z, xp.asarray([-1.0], dtype=xp.float64)) + xp_assert_close( + p, xp.asarray([-5.390126972799615e-01 + 0j], dtype=xp.complex128), + rtol=1e-14 if not DEFAULT_F32 else 1e-7 + ) + assert math.isclose( + k, 7.695063486399808e-01, rel_tol=1e-14 if not DEFAULT_F32 else 1e-7 + ) + + def test_basic(self, xp): + for N in range(25): + wn = xp.asarray(0.01) + z, p, k = cheby1(N, 1, wn, 'low', analog=True, output='zpk') + assert_array_almost_equal(z, xp.asarray([], dtype=xp.float64)) + assert p.shape[0] == N + assert xp.all(xp.real(p) <= 0) # No poles in right half of S-plane + + for N in range(25): + wn = xp.asarray(0.01) + z, p, k = cheby1(N, 1, wn, 'high', analog=False, output='zpk') + xp_assert_equal(z, xp.ones(N, dtype=xp.float64)) # All zeros exactly at DC + assert xp.all(xp.abs(p) <= 1) # No poles outside unit circle + + # Same test as TestNormalize + b, a = cheby1(8, 0.5, xp.asarray(0.048)) + xp_assert_close( + b, xp.asarray([2.150733144728282e-11, 1.720586515782626e-10, + 6.022052805239190e-10, 1.204410561047838e-09, + 1.505513201309798e-09, 1.204410561047838e-09, + 6.022052805239190e-10, 1.720586515782626e-10, + 2.150733144728282e-11], dtype=xp.float64), + rtol=0, atol=1.5e-14 + ) + xp_assert_close( + a, xp.asarray([1.000000000000000e+00, -7.782402035027959e+00, + 2.654354569747454e+01, -5.182182531666387e+01, + 6.334127355102684e+01, -4.963358186631157e+01, + 2.434862182949389e+01, -6.836925348604676e+00, + 8.412934944449140e-01], dtype=xp.float64), + rtol=0, atol=5e-14 if not DEFAULT_F32 else 1e-7 + ) + + b, a = cheby1(4, 1, xp.asarray([0.4, 0.7]), btype='band') + assert_array_almost_equal( + b, xp.asarray([0.0084, 0, -0.0335, 0, 0.0502, 0, + -0.0335, 0, 0.0084], dtype=xp.float64), + decimal=4 + ) + assert_array_almost_equal( + a, xp.asarray([1.0, 1.1191, 2.862, 2.2986, 3.4137, + 1.8653, 1.8982, 0.5676, 0.4103], dtype=xp.float64), + decimal=4 + ) + + b2, a2 = cheby1(5, 3, xp.asarray(1), analog=True) + assert_array_almost_equal( + b2, xp.asarray([0.0626], dtype=xp.float64), decimal=4 + ) + assert_array_almost_equal( + a2, xp.asarray([1, 0.5745, 1.4150, 0.5489, 0.4080, + 0.0626], dtype=xp.float64), + decimal=4) + + b, a = cheby1(8, 0.5, xp.asarray(0.1)) + assert_array_almost_equal( + b, + 1.0e-006 * xp.asarray( + [0.00703924326028, 0.05631394608227, 0.19709881128793, + 0.39419762257586, 0.49274702821983, 0.39419762257586, + 0.19709881128793, 0.05631394608227, 0.00703924326028 + ], dtype=xp.float64 + ), + decimal=13 + ) + assert_array_almost_equal( + a, xp.asarray( + [1.00000000000000, -7.44912258934158, 24.46749067762108, + -46.27560200466141, 55.11160187999928, -42.31640010161038, + 20.45543300484147, -5.69110270561444, 0.69770374759022 + ], dtype=xp.float64 + ), + decimal=13 if not DEFAULT_F32 else 6 + ) + + b, a = cheby1(8, 0.5, xp.asarray(0.25)) + assert_array_almost_equal( + b, + 1.0e-003 * xp.asarray( + [0.00895261138923, 0.07162089111382, 0.25067311889837, + 0.50134623779673, 0.62668279724591, 0.50134623779673, + 0.25067311889837, 0.07162089111382, 0.00895261138923 + ], dtype=xp.float64 + ), + decimal=13) + assert_array_almost_equal( + a, + xp.asarray( + [1.00000000000000, -5.97529229188545, + 16.58122329202101, -27.71423273542923, + 30.39509758355313, -22.34729670426879, + 10.74509800434910, -3.08924633697497, + 0.40707685889802 + ], dtype=xp.float64 + ), decimal=13) + + def test_highpass(self, xp): + # high even order + z, p, k = cheby1(24, 0.7, xp.asarray(0.2), 'high', output='zpk') + z2 = xp.ones(24, dtype=xp.float64) + p2 = [-6.136558509657073e-01 + 2.700091504942893e-01j, + -6.136558509657073e-01 - 2.700091504942893e-01j, + -3.303348340927516e-01 + 6.659400861114254e-01j, + -3.303348340927516e-01 - 6.659400861114254e-01j, + 8.779713780557169e-03 + 8.223108447483040e-01j, + 8.779713780557169e-03 - 8.223108447483040e-01j, + 2.742361123006911e-01 + 8.356666951611864e-01j, + 2.742361123006911e-01 - 8.356666951611864e-01j, + 4.562984557158206e-01 + 7.954276912303594e-01j, + 4.562984557158206e-01 - 7.954276912303594e-01j, + 5.777335494123628e-01 + 7.435821817961783e-01j, + 5.777335494123628e-01 - 7.435821817961783e-01j, + 6.593260977749194e-01 + 6.955390907990932e-01j, + 6.593260977749194e-01 - 6.955390907990932e-01j, + 7.149590948466562e-01 + 6.559437858502012e-01j, + 7.149590948466562e-01 - 6.559437858502012e-01j, + 7.532432388188739e-01 + 6.256158042292060e-01j, + 7.532432388188739e-01 - 6.256158042292060e-01j, + 7.794365244268271e-01 + 6.042099234813333e-01j, + 7.794365244268271e-01 - 6.042099234813333e-01j, + 7.967253874772997e-01 + 5.911966597313203e-01j, + 7.967253874772997e-01 - 5.911966597313203e-01j, + 8.069756417293870e-01 + 5.862214589217275e-01j, + 8.069756417293870e-01 - 5.862214589217275e-01j] + p2 = xp.asarray(p2, dtype=xp.complex128) + k2 = 6.190427617192018e-04 + xp_assert_equal(z, z2) + xp_assert_close( + _sort_cmplx(p, xp=xp), _sort_cmplx(p2, xp=xp), + rtol=1e-10 if not DEFAULT_F32 else 1e-7 + ) + assert math.isclose(k, k2, rel_tol=1e-10 if not DEFAULT_F32 else 1e-6) + + # high odd order + z, p, k = cheby1(23, 0.8, xp.asarray(0.3), 'high', output='zpk') + z2 = xp.ones(23, dtype=xp.float64) + p2 = [-7.676400532011010e-01, + -6.754621070166477e-01 + 3.970502605619561e-01j, + -6.754621070166477e-01 - 3.970502605619561e-01j, + -4.528880018446727e-01 + 6.844061483786332e-01j, + -4.528880018446727e-01 - 6.844061483786332e-01j, + -1.986009130216447e-01 + 8.382285942941594e-01j, + -1.986009130216447e-01 - 8.382285942941594e-01j, + 2.504673931532608e-02 + 8.958137635794080e-01j, + 2.504673931532608e-02 - 8.958137635794080e-01j, + 2.001089429976469e-01 + 9.010678290791480e-01j, + 2.001089429976469e-01 - 9.010678290791480e-01j, + 3.302410157191755e-01 + 8.835444665962544e-01j, + 3.302410157191755e-01 - 8.835444665962544e-01j, + 4.246662537333661e-01 + 8.594054226449009e-01j, + 4.246662537333661e-01 - 8.594054226449009e-01j, + 4.919620928120296e-01 + 8.366772762965786e-01j, + 4.919620928120296e-01 - 8.366772762965786e-01j, + 5.385746917494749e-01 + 8.191616180796720e-01j, + 5.385746917494749e-01 - 8.191616180796720e-01j, + 5.855636993537203e-01 + 8.060680937701062e-01j, + 5.855636993537203e-01 - 8.060680937701062e-01j, + 5.688812849391721e-01 + 8.086497795114683e-01j, + 5.688812849391721e-01 - 8.086497795114683e-01j] + p2 = xp.asarray(p2, dtype=xp.complex128) + k2 = 1.941697029206324e-05 + xp_assert_equal(z, z2) + xp_assert_close( + _sort_cmplx(p, xp=xp), _sort_cmplx(p2, xp=xp), + rtol=1e-10 if not DEFAULT_F32 else 1e-7 + ) + assert math.isclose( + k, k2, rel_tol=1e-10 if not DEFAULT_F32 else 1e-6 + ) + + z, p, k = cheby1(10, 1, xp.asarray(1000), 'high', analog=True, output='zpk') + z2 = xp.zeros(10, dtype=xp.float64) + p2 = [-3.144743169501551e+03 + 3.511680029092744e+03j, + -3.144743169501551e+03 - 3.511680029092744e+03j, + -5.633065604514602e+02 + 2.023615191183945e+03j, + -5.633065604514602e+02 - 2.023615191183945e+03j, + -1.946412183352025e+02 + 1.372309454274755e+03j, + -1.946412183352025e+02 - 1.372309454274755e+03j, + -7.987162953085479e+01 + 1.105207708045358e+03j, + -7.987162953085479e+01 - 1.105207708045358e+03j, + -2.250315039031946e+01 + 1.001723931471477e+03j, + -2.250315039031946e+01 - 1.001723931471477e+03j] + p2 = xp.asarray(p2, dtype=xp.complex128) + k2 = 8.912509381337453e-01 + xp_assert_equal(z, z2) + xp_assert_close(_sort_cmplx(p, xp=xp), _sort_cmplx(p2, xp=xp), rtol=1e-13) + assert math.isclose(k, k2, rel_tol=1e-15) + + def test_bandpass(self, xp): + z, p, k = cheby1(8, 1, xp.asarray([0.3, 0.4]), 'bp', output='zpk') + z2 = [1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1] + p2 = [3.077784854851463e-01 + 9.453307017592942e-01j, + 3.077784854851463e-01 - 9.453307017592942e-01j, + 3.280567400654425e-01 + 9.272377218689016e-01j, + 3.280567400654425e-01 - 9.272377218689016e-01j, + 3.677912763284301e-01 + 9.038008865279966e-01j, + 3.677912763284301e-01 - 9.038008865279966e-01j, + 4.194425632520948e-01 + 8.769407159656157e-01j, + 4.194425632520948e-01 - 8.769407159656157e-01j, + 4.740921994669189e-01 + 8.496508528630974e-01j, + 4.740921994669189e-01 - 8.496508528630974e-01j, + 5.234866481897429e-01 + 8.259608422808477e-01j, + 5.234866481897429e-01 - 8.259608422808477e-01j, + 5.844717632289875e-01 + 8.052901363500210e-01j, + 5.844717632289875e-01 - 8.052901363500210e-01j, + 5.615189063336070e-01 + 8.100667803850766e-01j, + 5.615189063336070e-01 - 8.100667803850766e-01j] + z2 = xp.asarray(z2, dtype=xp.float64) + p2 = xp.asarray(p2, dtype=xp.complex128) + k2 = 5.007028718074307e-09 + xp_assert_equal(z, z2, check_dtype=False) + xp_assert_close( + _sort_cmplx(p, xp=xp), _sort_cmplx(p2, xp=xp), + rtol=1e-13 if not DEFAULT_F32 else 1e-7 + ) + assert math.isclose(k, k2, rel_tol=1e-13 if not DEFAULT_F32 else 1e-6) + + def test_bandstop(self, xp): + z, p, k = cheby1(7, 1, xp.asarray([0.5, 0.6]), 'stop', output='zpk') + z2 = [-1.583844403245361e-01 + 9.873775210440450e-01j, + -1.583844403245361e-01 - 9.873775210440450e-01j, + -1.583844403245361e-01 + 9.873775210440450e-01j, + -1.583844403245361e-01 - 9.873775210440450e-01j, + -1.583844403245361e-01 + 9.873775210440450e-01j, + -1.583844403245361e-01 - 9.873775210440450e-01j, + -1.583844403245361e-01 + 9.873775210440450e-01j, + -1.583844403245361e-01 - 9.873775210440450e-01j, + -1.583844403245361e-01 + 9.873775210440450e-01j, + -1.583844403245361e-01 - 9.873775210440450e-01j, + -1.583844403245361e-01 + 9.873775210440450e-01j, + -1.583844403245361e-01 - 9.873775210440450e-01j, + -1.583844403245361e-01 + 9.873775210440450e-01j, + -1.583844403245361e-01 - 9.873775210440450e-01j] + p2 = [-8.942974551472813e-02 + 3.482480481185926e-01j, + -8.942974551472813e-02 - 3.482480481185926e-01j, + 1.293775154041798e-01 + 8.753499858081858e-01j, + 1.293775154041798e-01 - 8.753499858081858e-01j, + 3.399741945062013e-02 + 9.690316022705607e-01j, + 3.399741945062013e-02 - 9.690316022705607e-01j, + 4.167225522796539e-04 + 9.927338161087488e-01j, + 4.167225522796539e-04 - 9.927338161087488e-01j, + -3.912966549550960e-01 + 8.046122859255742e-01j, + -3.912966549550960e-01 - 8.046122859255742e-01j, + -3.307805547127368e-01 + 9.133455018206508e-01j, + -3.307805547127368e-01 - 9.133455018206508e-01j, + -3.072658345097743e-01 + 9.443589759799366e-01j, + -3.072658345097743e-01 - 9.443589759799366e-01j] + z2 = xp.asarray(z2, dtype=xp.complex128) + p2 = xp.asarray(p2, dtype=xp.complex128) + k2 = 3.619438310405028e-01 + xp_assert_close( + _sort_cmplx(z, xp=xp), _sort_cmplx(z2, xp=xp), + rtol=1e-13 if not DEFAULT_F32 else 1e-6 + ) + xp_assert_close( + _sort_cmplx(p, xp=xp), _sort_cmplx(p2, xp=xp), + rtol=1e-13 if not DEFAULT_F32 else 1e-6 + ) + assert math.isclose( + k, k2, rel_tol=0, abs_tol=5e-16 if not DEFAULT_F32 else 1e-7 + ) + + @xfail_xp_backends("cupy", reason="inaccurate on CuPy") + def test_ba_output(self, xp): + # with transfer function conversion, without digital conversion + b, a = cheby1(5, 0.9, xp.asarray([210, 310.0]), 'stop', analog=True) + b2 = [1.000000000000006e+00, 0, + 3.255000000000020e+05, 0, + 4.238010000000026e+10, 0, + 2.758944510000017e+15, 0, + 8.980364380050052e+19, 0, + 1.169243442282517e+24 + ] + a2 = [1.000000000000000e+00, 4.630555945694342e+02, + 4.039266454794788e+05, 1.338060988610237e+08, + 5.844333551294591e+10, 1.357346371637638e+13, + 3.804661141892782e+15, 5.670715850340080e+17, + 1.114411200988328e+20, 8.316815934908471e+21, + 1.169243442282517e+24 + ] + b2, a2 = map(lambda t: xp.asarray(t, dtype=xp.float64), (b2, a2)) + xp_assert_close(b, b2, rtol=1e-14 if not DEFAULT_F32 else 1e-7) + xp_assert_close(a, a2, rtol=1e-14 if not DEFAULT_F32 else 1e-7) + + def test_fs_param(self): + for fs in (900, 900.1, 1234.567): + for N in (0, 1, 2, 3, 10): + for fc in (100, 100.1, 432.12345): + for btype in ('lp', 'hp'): + ba1 = cheby1(N, 1, fc, btype, fs=fs) + ba2 = cheby1(N, 1, fc/(fs/2), btype) + for ba1_, ba2_ in zip(ba1, ba2): + xp_assert_close(ba1_, ba2_) + for fc in ((100, 200), (100.1, 200.2), (321.123, 432.123)): + for btype in ('bp', 'bs'): + ba1 = cheby1(N, 1, fc, btype, fs=fs) + for seq in (list, tuple, array): + fcnorm = seq([f/(fs/2) for f in fc]) + ba2 = cheby1(N, 1, fcnorm, btype) + for ba1_, ba2_ in zip(ba1, ba2): + xp_assert_close(ba1_, ba2_) + + +@skip_xp_backends("dask.array", reason="https://github.com/dask/dask/issues/11883") +@make_xp_test_case(cheby2) +class TestCheby2: + + def test_degenerate(self, xp): + # 0-order filter is just a passthrough + # Stopband ripple factor doesn't matter + b, a = cheby2(0, 123.456, xp.asarray(1), analog=True) + xp_assert_equal(b, xp.asarray([1.0], dtype=xp.float64)) + xp_assert_equal(a, xp.asarray([1.0], dtype=xp.float64)) + + # 1-order filter is same for all types + b, a = cheby2(1, 10*math.log10(2), xp.asarray(1.), analog=True) + assert_array_almost_equal(b, xp.asarray([1], dtype=xp.float64)) + assert_array_almost_equal(a, xp.asarray([1, 1], dtype=xp.float64)) + + z, p, k = cheby2(1, 50, xp.asarray(0.3), output='zpk') + xp_assert_equal(z, xp.asarray([-1], dtype=xp.complex128)) + xp_assert_close( + p, xp.asarray([9.967826460175649e-01 + 0j], dtype=xp.complex128), + rtol=1e-14 if not DEFAULT_F32 else 1e-7 + ) + assert math.isclose( + k, 1.608676991217512e-03, rel_tol=1e-14 if not DEFAULT_F32 else 1e-6 + ) + + def test_basic(self, xp): + for N in range(25): + wn = xp.asarray(0.01) + z, p, k = cheby2(N, 40, wn, 'low', analog=True, output='zpk') + assert p.shape[0] == N + assert all(xp.real(p) <= 0) # No poles in right half of S-plane + + for N in range(25): + wn = xp.asarray(0.01) + z, p, k = cheby2(N, 40, wn, 'high', analog=False, output='zpk') + assert all(xp.abs(p) <= 1) # No poles outside unit circle + + B, A = cheby2(18, 100, xp.asarray(0.5)) + assert_array_almost_equal( + B, xp.asarray([ + 0.00167583914216, 0.01249479541868, 0.05282702120282, + 0.15939804265706, 0.37690207631117, 0.73227013789108, + 1.20191856962356, 1.69522872823393, 2.07598674519837, + 2.21972389625291, 2.07598674519838, 1.69522872823395, + 1.20191856962359, 0.73227013789110, 0.37690207631118, + 0.15939804265707, 0.05282702120282, 0.01249479541868, + 0.00167583914216], dtype=xp.float64), + decimal=13 if not DEFAULT_F32 else 6 + ) + assert_array_almost_equal( + A, xp.asarray([ + 1.00000000000000, -0.27631970006174, 3.19751214254060, + -0.15685969461355, 4.13926117356269, 0.60689917820044, + 2.95082770636540, 0.89016501910416, 1.32135245849798, + 0.51502467236824, 0.38906643866660, 0.15367372690642, + 0.07255803834919, 0.02422454070134, 0.00756108751837, + 0.00179848550988, 0.00033713574499, 0.00004258794833, + 0.00000281030149], dtype=xp.float64), + decimal=13 if not DEFAULT_F32 else 6 + ) + + def test_highpass(self, xp): + # high even order + z, p, k = cheby2(26, 60, xp.asarray(0.3), 'high', output='zpk') + z2 = [9.981088955489852e-01 + 6.147058341984388e-02j, + 9.981088955489852e-01 - 6.147058341984388e-02j, + 9.832702870387426e-01 + 1.821525257215483e-01j, + 9.832702870387426e-01 - 1.821525257215483e-01j, + 9.550760158089112e-01 + 2.963609353922882e-01j, + 9.550760158089112e-01 - 2.963609353922882e-01j, + 9.162054748821922e-01 + 4.007087817803773e-01j, + 9.162054748821922e-01 - 4.007087817803773e-01j, + 8.700619897368064e-01 + 4.929423232136168e-01j, + 8.700619897368064e-01 - 4.929423232136168e-01j, + 5.889791753434985e-01 + 8.081482110427953e-01j, + 5.889791753434985e-01 - 8.081482110427953e-01j, + 5.984900456570295e-01 + 8.011302423760501e-01j, + 5.984900456570295e-01 - 8.011302423760501e-01j, + 6.172880888914629e-01 + 7.867371958365343e-01j, + 6.172880888914629e-01 - 7.867371958365343e-01j, + 6.448899971038180e-01 + 7.642754030030161e-01j, + 6.448899971038180e-01 - 7.642754030030161e-01j, + 6.804845629637927e-01 + 7.327624168637228e-01j, + 6.804845629637927e-01 - 7.327624168637228e-01j, + 8.202619107108660e-01 + 5.719881098737678e-01j, + 8.202619107108660e-01 - 5.719881098737678e-01j, + 7.228410452536148e-01 + 6.910143437705678e-01j, + 7.228410452536148e-01 - 6.910143437705678e-01j, + 7.702121399578629e-01 + 6.377877856007792e-01j, + 7.702121399578629e-01 - 6.377877856007792e-01j] + p2 = [7.365546198286450e-01 + 4.842085129329526e-02j, + 7.365546198286450e-01 - 4.842085129329526e-02j, + 7.292038510962885e-01 + 1.442201672097581e-01j, + 7.292038510962885e-01 - 1.442201672097581e-01j, + 7.151293788040354e-01 + 2.369925800458584e-01j, + 7.151293788040354e-01 - 2.369925800458584e-01j, + 6.955051820787286e-01 + 3.250341363856910e-01j, + 6.955051820787286e-01 - 3.250341363856910e-01j, + 6.719122956045220e-01 + 4.070475750638047e-01j, + 6.719122956045220e-01 - 4.070475750638047e-01j, + 6.461722130611300e-01 + 4.821965916689270e-01j, + 6.461722130611300e-01 - 4.821965916689270e-01j, + 5.528045062872224e-01 + 8.162920513838372e-01j, + 5.528045062872224e-01 - 8.162920513838372e-01j, + 5.464847782492791e-01 + 7.869899955967304e-01j, + 5.464847782492791e-01 - 7.869899955967304e-01j, + 5.488033111260949e-01 + 7.520442354055579e-01j, + 5.488033111260949e-01 - 7.520442354055579e-01j, + 6.201874719022955e-01 + 5.500894392527353e-01j, + 6.201874719022955e-01 - 5.500894392527353e-01j, + 5.586478152536709e-01 + 7.112676877332921e-01j, + 5.586478152536709e-01 - 7.112676877332921e-01j, + 5.958145844148228e-01 + 6.107074340842115e-01j, + 5.958145844148228e-01 - 6.107074340842115e-01j, + 5.747812938519067e-01 + 6.643001536914696e-01j, + 5.747812938519067e-01 - 6.643001536914696e-01j] + z2 = xp.asarray(z2, dtype=xp.complex128) + p2 = xp.asarray(p2, dtype=xp.complex128) + k2 = 6.190427617192018e-04 + k2 = 9.932997786497189e-02 + xp_assert_close( + _sort_cmplx(z, xp=xp), _sort_cmplx(z2, xp=xp), + rtol=1e-13 if not DEFAULT_F32 else 1e-6 + ) + xp_assert_close( + _sort_cmplx(p, xp=xp), _sort_cmplx(p2, xp=xp), + rtol=1e-12 if not DEFAULT_F32 else 1e-6 + ) + assert math.isclose( + k, k2, rel_tol=1e-11 if not DEFAULT_F32 else 1e-6 + ) + + # high odd order + z, p, k = cheby2(25, 80, xp.asarray(0.5), 'high', output='zpk') + z2 = [9.690690376586687e-01 + 2.467897896011971e-01j, + 9.690690376586687e-01 - 2.467897896011971e-01j, + 9.999999999999492e-01, + 8.835111277191199e-01 + 4.684101698261429e-01j, + 8.835111277191199e-01 - 4.684101698261429e-01j, + 7.613142857900539e-01 + 6.483830335935022e-01j, + 7.613142857900539e-01 - 6.483830335935022e-01j, + 6.232625173626231e-01 + 7.820126817709752e-01j, + 6.232625173626231e-01 - 7.820126817709752e-01j, + 4.864456563413621e-01 + 8.737108351316745e-01j, + 4.864456563413621e-01 - 8.737108351316745e-01j, + 3.618368136816749e-01 + 9.322414495530347e-01j, + 3.618368136816749e-01 - 9.322414495530347e-01j, + 2.549486883466794e-01 + 9.669545833752675e-01j, + 2.549486883466794e-01 - 9.669545833752675e-01j, + 1.676175432109457e-01 + 9.858520980390212e-01j, + 1.676175432109457e-01 - 9.858520980390212e-01j, + 1.975218468277521e-03 + 9.999980492540941e-01j, + 1.975218468277521e-03 - 9.999980492540941e-01j, + 1.786959496651858e-02 + 9.998403260399917e-01j, + 1.786959496651858e-02 - 9.998403260399917e-01j, + 9.967933660557139e-02 + 9.950196127985684e-01j, + 9.967933660557139e-02 - 9.950196127985684e-01j, + 5.013970951219547e-02 + 9.987422137518890e-01j, + 5.013970951219547e-02 - 9.987422137518890e-01j] + p2 = [4.218866331906864e-01, + 4.120110200127552e-01 + 1.361290593621978e-01j, + 4.120110200127552e-01 - 1.361290593621978e-01j, + 3.835890113632530e-01 + 2.664910809911026e-01j, + 3.835890113632530e-01 - 2.664910809911026e-01j, + 3.399195570456499e-01 + 3.863983538639875e-01j, + 3.399195570456499e-01 - 3.863983538639875e-01j, + 2.855977834508353e-01 + 4.929444399540688e-01j, + 2.855977834508353e-01 - 4.929444399540688e-01j, + 2.255765441339322e-01 + 5.851631870205766e-01j, + 2.255765441339322e-01 - 5.851631870205766e-01j, + 1.644087535815792e-01 + 6.637356937277153e-01j, + 1.644087535815792e-01 - 6.637356937277153e-01j, + -7.293633845273095e-02 + 9.739218252516307e-01j, + -7.293633845273095e-02 - 9.739218252516307e-01j, + 1.058259206358626e-01 + 7.304739464862978e-01j, + 1.058259206358626e-01 - 7.304739464862978e-01j, + -5.703971947785402e-02 + 9.291057542169088e-01j, + -5.703971947785402e-02 - 9.291057542169088e-01j, + 5.263875132656864e-02 + 7.877974334424453e-01j, + 5.263875132656864e-02 - 7.877974334424453e-01j, + -3.007943405982616e-02 + 8.846331716180016e-01j, + -3.007943405982616e-02 - 8.846331716180016e-01j, + 6.857277464483946e-03 + 8.383275456264492e-01j, + 6.857277464483946e-03 - 8.383275456264492e-01j] + z2 = xp.asarray(z2, dtype=xp.complex128) + p2 = xp.asarray(p2, dtype=xp.complex128) + k2 = 6.507068761705037e-03 + xp_assert_close( + _sort_cmplx(z, xp=xp), _sort_cmplx(z2, xp=xp), + rtol=1e-13 if not DEFAULT_F32 else 1e-6 + ) + xp_assert_close( + _sort_cmplx(p, xp=xp), _sort_cmplx(p2, xp=xp), + rtol=1e-12 if not DEFAULT_F32 else 1e-6 + ) + assert math.isclose(k, k2, rel_tol=1e-11 if not DEFAULT_F32 else 1e-6) + + def test_bandpass(self, xp): + z, p, k = cheby2(9, 40, xp.asarray([0.07, 0.2]), 'pass', output='zpk') + z2 = [-9.999999999999999e-01, + 3.676588029658514e-01 + 9.299607543341383e-01j, + 3.676588029658514e-01 - 9.299607543341383e-01j, + 7.009689684982283e-01 + 7.131917730894889e-01j, + 7.009689684982283e-01 - 7.131917730894889e-01j, + 7.815697973765858e-01 + 6.238178033919218e-01j, + 7.815697973765858e-01 - 6.238178033919218e-01j, + 8.063793628819866e-01 + 5.913986160941200e-01j, + 8.063793628819866e-01 - 5.913986160941200e-01j, + 1.000000000000001e+00, + 9.944493019920448e-01 + 1.052168511576739e-01j, + 9.944493019920448e-01 - 1.052168511576739e-01j, + 9.854674703367308e-01 + 1.698642543566085e-01j, + 9.854674703367308e-01 - 1.698642543566085e-01j, + 9.762751735919308e-01 + 2.165335665157851e-01j, + 9.762751735919308e-01 - 2.165335665157851e-01j, + 9.792277171575134e-01 + 2.027636011479496e-01j, + 9.792277171575134e-01 - 2.027636011479496e-01j] + p2 = [8.143803410489621e-01 + 5.411056063397541e-01j, + 8.143803410489621e-01 - 5.411056063397541e-01j, + 7.650769827887418e-01 + 5.195412242095543e-01j, + 7.650769827887418e-01 - 5.195412242095543e-01j, + 6.096241204063443e-01 + 3.568440484659796e-01j, + 6.096241204063443e-01 - 3.568440484659796e-01j, + 6.918192770246239e-01 + 4.770463577106911e-01j, + 6.918192770246239e-01 - 4.770463577106911e-01j, + 6.986241085779207e-01 + 1.146512226180060e-01j, + 6.986241085779207e-01 - 1.146512226180060e-01j, + 8.654645923909734e-01 + 1.604208797063147e-01j, + 8.654645923909734e-01 - 1.604208797063147e-01j, + 9.164831670444591e-01 + 1.969181049384918e-01j, + 9.164831670444591e-01 - 1.969181049384918e-01j, + 9.630425777594550e-01 + 2.317513360702271e-01j, + 9.630425777594550e-01 - 2.317513360702271e-01j, + 9.438104703725529e-01 + 2.193509900269860e-01j, + 9.438104703725529e-01 - 2.193509900269860e-01j] + z2 = xp.asarray(z2, dtype=xp.complex128) + p2 = xp.asarray(p2, dtype=xp.complex128) + k2 = 9.345352824659604e-03 + xp_assert_close( + _sort_cmplx(z, xp=xp), _sort_cmplx(z2, xp=xp), + rtol=1e-13 if not DEFAULT_F32 else 1e-6 + ) + xp_assert_close( + _sort_cmplx(p, xp=xp), _sort_cmplx(p2, xp=xp), + rtol=1e-13 if not DEFAULT_F32 else 1e-6 + ) + assert math.isclose(k, k2, rel_tol=1e-11 if not DEFAULT_F32 else 1e-6) + + def test_bandstop(self, xp): + z, p, k = cheby2(6, 55, xp.asarray([0.1, 0.9]), 'stop', output='zpk') + z2 = [6.230544895101009e-01 + 7.821784343111114e-01j, + 6.230544895101009e-01 - 7.821784343111114e-01j, + 9.086608545660115e-01 + 4.175349702471991e-01j, + 9.086608545660115e-01 - 4.175349702471991e-01j, + 9.478129721465802e-01 + 3.188268649763867e-01j, + 9.478129721465802e-01 - 3.188268649763867e-01j, + -6.230544895100982e-01 + 7.821784343111109e-01j, + -6.230544895100982e-01 - 7.821784343111109e-01j, + -9.086608545660116e-01 + 4.175349702472088e-01j, + -9.086608545660116e-01 - 4.175349702472088e-01j, + -9.478129721465784e-01 + 3.188268649763897e-01j, + -9.478129721465784e-01 - 3.188268649763897e-01j] + p2 = [-9.464094036167638e-01 + 1.720048695084344e-01j, + -9.464094036167638e-01 - 1.720048695084344e-01j, + -8.715844103386737e-01 + 1.370665039509297e-01j, + -8.715844103386737e-01 - 1.370665039509297e-01j, + -8.078751204586425e-01 + 5.729329866682983e-02j, + -8.078751204586425e-01 - 5.729329866682983e-02j, + 9.464094036167665e-01 + 1.720048695084332e-01j, + 9.464094036167665e-01 - 1.720048695084332e-01j, + 8.078751204586447e-01 + 5.729329866683007e-02j, + 8.078751204586447e-01 - 5.729329866683007e-02j, + 8.715844103386721e-01 + 1.370665039509331e-01j, + 8.715844103386721e-01 - 1.370665039509331e-01j] + z2 = xp.asarray(z2, dtype=xp.complex128) + p2 = xp.asarray(p2, dtype=xp.complex128) + k2 = 2.917823332763358e-03 + xp_assert_close( + _sort_cmplx(z, xp=xp), _sort_cmplx(z2, xp=xp), + rtol=1e-13 if not DEFAULT_F32 else 1e-6 + ) + xp_assert_close( + _sort_cmplx(p, xp=xp), _sort_cmplx(p2, xp=xp), + rtol=1e-13 if not DEFAULT_F32 else 1e-6 + ) + assert math.isclose(k, k2, rel_tol=1e-11 if not DEFAULT_F32 else 1e-6) + + @xfail_xp_backends("cupy", reason="inaccurate on CuPy") + def test_ba_output(self, xp): + # with transfer function conversion, without digital conversion + b, a = cheby2(5, 20, xp.asarray([2010, 2100]), 'stop', True) + b2 = [1.000000000000000e+00, 0, # Matlab: 6.683253076978249e-12, + 2.111512500000000e+07, 0, # Matlab: 1.134325604589552e-04, + 1.782966433781250e+14, 0, # Matlab: 7.216787944356781e+02, + 7.525901316990656e+20, 0, # Matlab: 2.039829265789886e+09, + 1.587960565565748e+27, 0, # Matlab: 2.161236218626134e+15, + 1.339913493808585e+33] + a2 = [1.000000000000000e+00, 1.849550755473371e+02, + 2.113222918998538e+07, 3.125114149732283e+09, + 1.785133457155609e+14, 1.979158697776348e+16, + 7.535048322653831e+20, 5.567966191263037e+22, + 1.589246884221346e+27, 5.871210648525566e+28, + 1.339913493808590e+33] + b2 = xp.asarray(b2, dtype=xp.float64) + a2 = xp.asarray(a2, dtype=xp.float64) + xp_assert_close(b, b2, rtol=5e-14 if not DEFAULT_F32 else 1e-7) + xp_assert_close(a, a2, rtol=5e-14 if not DEFAULT_F32 else 1e-7) + + def test_fs_param(self): + for fs in (900, 900.1, 1234.567): + for N in (0, 1, 2, 3, 10): + for fc in (100, 100.1, 432.12345): + for btype in ('lp', 'hp'): + ba1 = cheby2(N, 20, fc, btype, fs=fs) + ba2 = cheby2(N, 20, fc/(fs/2), btype) + for ba1_, ba2_ in zip(ba1, ba2): + xp_assert_close(ba1_, ba2_) + for fc in ((100, 200), (100.1, 200.2), (321.123, 432.123)): + for btype in ('bp', 'bs'): + ba1 = cheby2(N, 20, fc, btype, fs=fs) + for seq in (list, tuple, array): + fcnorm = seq([f/(fs/2) for f in fc]) + ba2 = cheby2(N, 20, fcnorm, btype) + for ba1_, ba2_ in zip(ba1, ba2): + xp_assert_close(ba1_, ba2_) + + +@skip_xp_backends("dask.array", reason="https://github.com/dask/dask/issues/11883") +@make_xp_test_case(ellip) +class TestEllip: + + @xfail_xp_backends("cupy", reason="dtypes do not match") + def test_degenerate(self, xp): + # 0-order filter is just a passthrough + # Even-order filters have DC gain of -rp dB + # Stopband ripple factor doesn't matter + b, a = ellip(0, 10*math.log10(2), 123.456, xp.asarray(1.), analog=True) + assert_array_almost_equal(b, xp.asarray([1/math.sqrt(2)], dtype=xp.float64)) + xp_assert_equal(a, xp.asarray([1.0], dtype=xp.float64)) + + # 1-order filter is same for all types + b, a = ellip(1, 10*math.log10(2), 1, xp.asarray(1.), analog=True) + assert_array_almost_equal(b, xp.asarray([1.], dtype=xp.float64)) + assert_array_almost_equal(a, xp.asarray([1., 1], dtype=xp.float64)) + + z, p, k = ellip(1, 1, 55, xp.asarray(0.3), output='zpk') + xp_assert_close( + z, xp.asarray([-9.999999999999998e-01 + 0j], dtype=xp.complex128), + rtol=1e-14 + ) + xp_assert_close( + p, xp.asarray([-6.660721153525525e-04 + 0j], dtype=xp.complex128), + rtol=1e-10 if not DEFAULT_F32 else 5e-5 + ) + assert math.isclose( + k, 5.003330360576763e-01, rel_tol=1e-14 if not DEFAULT_F32 else 1e-7 + ) + + def test_basic(self, xp): + for N in range(25): + wn = xp.asarray(0.01) + z, p, k = ellip(N, 1, 40, wn, 'low', analog=True, output='zpk') + assert p.shape[0] == N + assert xp.all(xp.real(p) <= 0) # No poles in right half of S-plane + + for N in range(25): + wn = xp.asarray(0.01) + z, p, k = ellip(N, 1, 40, wn, 'high', analog=False, output='zpk') + assert xp.all(xp.abs(p) <= 1) # No poles outside unit circle + + b3, a3 = ellip(5, 3, 26, xp.asarray(1.), analog=True) + assert_array_almost_equal( + b3, xp.asarray([0.1420, 0, 0.3764, 0, 0.2409], dtype=xp.float64), + decimal=4 + ) + assert_array_almost_equal( + a3, + xp.asarray([1, 0.5686, 1.8061, 0.8017, 0.8012, 0.2409], dtype=xp.float64), + decimal=4 + ) + + b, a = ellip(3, 1, 60, xp.asarray([0.4, 0.7]), 'stop') + assert_array_almost_equal( + b, xp.asarray([0.3310, 0.3469, 1.1042, 0.7044, 1.1042, + 0.3469, 0.3310], dtype=xp.float64), + decimal=4 + ) + assert_array_almost_equal( + a, xp.asarray([1.0000, 0.6973, 1.1441, 0.5878, 0.7323, + 0.1131, -0.0060], dtype=xp.float64), + decimal=4 + ) + + def test_highpass(self, xp): + # high even order + z, p, k = ellip(24, 1, 80, xp.asarray(0.3), 'high', output='zpk') + z2 = [9.761875332501075e-01 + 2.169283290099910e-01j, + 9.761875332501075e-01 - 2.169283290099910e-01j, + 8.413503353963494e-01 + 5.404901600661900e-01j, + 8.413503353963494e-01 - 5.404901600661900e-01j, + 7.160082576305009e-01 + 6.980918098681732e-01j, + 7.160082576305009e-01 - 6.980918098681732e-01j, + 6.456533638965329e-01 + 7.636306264739803e-01j, + 6.456533638965329e-01 - 7.636306264739803e-01j, + 6.127321820971366e-01 + 7.902906256703928e-01j, + 6.127321820971366e-01 - 7.902906256703928e-01j, + 5.983607817490196e-01 + 8.012267936512676e-01j, + 5.983607817490196e-01 - 8.012267936512676e-01j, + 5.922577552594799e-01 + 8.057485658286990e-01j, + 5.922577552594799e-01 - 8.057485658286990e-01j, + 5.896952092563588e-01 + 8.076258788449631e-01j, + 5.896952092563588e-01 - 8.076258788449631e-01j, + 5.886248765538837e-01 + 8.084063054565607e-01j, + 5.886248765538837e-01 - 8.084063054565607e-01j, + 5.881802711123132e-01 + 8.087298490066037e-01j, + 5.881802711123132e-01 - 8.087298490066037e-01j, + 5.879995719101164e-01 + 8.088612386766461e-01j, + 5.879995719101164e-01 - 8.088612386766461e-01j, + 5.879354086709576e-01 + 8.089078780868164e-01j, + 5.879354086709576e-01 - 8.089078780868164e-01j] + p2 = [-3.184805259081650e-01 + 4.206951906775851e-01j, + -3.184805259081650e-01 - 4.206951906775851e-01j, + 1.417279173459985e-01 + 7.903955262836452e-01j, + 1.417279173459985e-01 - 7.903955262836452e-01j, + 4.042881216964651e-01 + 8.309042239116594e-01j, + 4.042881216964651e-01 - 8.309042239116594e-01j, + 5.128964442789670e-01 + 8.229563236799665e-01j, + 5.128964442789670e-01 - 8.229563236799665e-01j, + 5.569614712822724e-01 + 8.155957702908510e-01j, + 5.569614712822724e-01 - 8.155957702908510e-01j, + 5.750478870161392e-01 + 8.118633973883931e-01j, + 5.750478870161392e-01 - 8.118633973883931e-01j, + 5.825314018170804e-01 + 8.101960910679270e-01j, + 5.825314018170804e-01 - 8.101960910679270e-01j, + 5.856397379751872e-01 + 8.094825218722543e-01j, + 5.856397379751872e-01 - 8.094825218722543e-01j, + 5.869326035251949e-01 + 8.091827531557583e-01j, + 5.869326035251949e-01 - 8.091827531557583e-01j, + 5.874697218855733e-01 + 8.090593298213502e-01j, + 5.874697218855733e-01 - 8.090593298213502e-01j, + 5.876904783532237e-01 + 8.090127161018823e-01j, + 5.876904783532237e-01 - 8.090127161018823e-01j, + 5.877753105317594e-01 + 8.090050577978136e-01j, + 5.877753105317594e-01 - 8.090050577978136e-01j] + z2 = xp.asarray(z2, dtype=xp.complex128) + p2 = xp.asarray(p2, dtype=xp.complex128) + k2 = 4.918081266957108e-02 + xp_assert_close(_sort_cmplx(z, xp=xp), + _sort_cmplx(z2, xp=xp), rtol=1e-4) + xp_assert_close(_sort_cmplx(p, xp=xp), + _sort_cmplx(p2, xp=xp), rtol=1e-4) + assert math.isclose(k, k2, rel_tol=1e-3) + + # high odd order + z, p, k = ellip(23, 1, 70, xp.asarray(0.5), 'high', output='zpk') + z2 = [9.999999999998661e-01, + 6.603717261750994e-01 + 7.509388678638675e-01j, + 6.603717261750994e-01 - 7.509388678638675e-01j, + 2.788635267510325e-01 + 9.603307416968041e-01j, + 2.788635267510325e-01 - 9.603307416968041e-01j, + 1.070215532544218e-01 + 9.942567008268131e-01j, + 1.070215532544218e-01 - 9.942567008268131e-01j, + 4.049427369978163e-02 + 9.991797705105507e-01j, + 4.049427369978163e-02 - 9.991797705105507e-01j, + 1.531059368627931e-02 + 9.998827859909265e-01j, + 1.531059368627931e-02 - 9.998827859909265e-01j, + 5.808061438534933e-03 + 9.999831330689181e-01j, + 5.808061438534933e-03 - 9.999831330689181e-01j, + 2.224277847754599e-03 + 9.999975262909676e-01j, + 2.224277847754599e-03 - 9.999975262909676e-01j, + 8.731857107534554e-04 + 9.999996187732845e-01j, + 8.731857107534554e-04 - 9.999996187732845e-01j, + 3.649057346914968e-04 + 9.999999334218996e-01j, + 3.649057346914968e-04 - 9.999999334218996e-01j, + 1.765538109802615e-04 + 9.999999844143768e-01j, + 1.765538109802615e-04 - 9.999999844143768e-01j, + 1.143655290967426e-04 + 9.999999934602630e-01j, + 1.143655290967426e-04 - 9.999999934602630e-01j] + p2 = [-6.322017026545028e-01, + -4.648423756662754e-01 + 5.852407464440732e-01j, + -4.648423756662754e-01 - 5.852407464440732e-01j, + -2.249233374627773e-01 + 8.577853017985717e-01j, + -2.249233374627773e-01 - 8.577853017985717e-01j, + -9.234137570557621e-02 + 9.506548198678851e-01j, + -9.234137570557621e-02 - 9.506548198678851e-01j, + -3.585663561241373e-02 + 9.821494736043981e-01j, + -3.585663561241373e-02 - 9.821494736043981e-01j, + -1.363917242312723e-02 + 9.933844128330656e-01j, + -1.363917242312723e-02 - 9.933844128330656e-01j, + -5.131505238923029e-03 + 9.975221173308673e-01j, + -5.131505238923029e-03 - 9.975221173308673e-01j, + -1.904937999259502e-03 + 9.990680819857982e-01j, + -1.904937999259502e-03 - 9.990680819857982e-01j, + -6.859439885466834e-04 + 9.996492201426826e-01j, + -6.859439885466834e-04 - 9.996492201426826e-01j, + -2.269936267937089e-04 + 9.998686250679161e-01j, + -2.269936267937089e-04 - 9.998686250679161e-01j, + -5.687071588789117e-05 + 9.999527573294513e-01j, + -5.687071588789117e-05 - 9.999527573294513e-01j, + -6.948417068525226e-07 + 9.999882737700173e-01j, + -6.948417068525226e-07 - 9.999882737700173e-01j] + z2 = xp.asarray(z2, dtype=xp.complex128) + p2 = xp.asarray(p2, dtype=xp.complex128) + k2 = 1.220910020289434e-02 + xp_assert_close(_sort_cmplx(z, xp=xp), + _sort_cmplx(z2, xp=xp), rtol=1e-4) + xp_assert_close(_sort_cmplx(p, xp=xp), + _sort_cmplx(p2, xp=xp), rtol=1e-4) + assert math.isclose(k, k2, rel_tol=1e-3) + + def test_bandpass(self, xp): + z, p, k = ellip(7, 1, 40, [0.07, 0.2], 'pass', output='zpk') + z2 = [-9.999999999999991e-01, + 6.856610961780020e-01 + 7.279209168501619e-01j, + 6.856610961780020e-01 - 7.279209168501619e-01j, + 7.850346167691289e-01 + 6.194518952058737e-01j, + 7.850346167691289e-01 - 6.194518952058737e-01j, + 7.999038743173071e-01 + 6.001281461922627e-01j, + 7.999038743173071e-01 - 6.001281461922627e-01j, + 9.999999999999999e-01, + 9.862938983554124e-01 + 1.649980183725925e-01j, + 9.862938983554124e-01 - 1.649980183725925e-01j, + 9.788558330548762e-01 + 2.045513580850601e-01j, + 9.788558330548762e-01 - 2.045513580850601e-01j, + 9.771155231720003e-01 + 2.127093189691258e-01j, + 9.771155231720003e-01 - 2.127093189691258e-01j] + p2 = [8.063992755498643e-01 + 5.858071374778874e-01j, + 8.063992755498643e-01 - 5.858071374778874e-01j, + 8.050395347071724e-01 + 5.639097428109795e-01j, + 8.050395347071724e-01 - 5.639097428109795e-01j, + 8.113124936559144e-01 + 4.855241143973142e-01j, + 8.113124936559144e-01 - 4.855241143973142e-01j, + 8.665595314082394e-01 + 3.334049560919331e-01j, + 8.665595314082394e-01 - 3.334049560919331e-01j, + 9.412369011968871e-01 + 2.457616651325908e-01j, + 9.412369011968871e-01 - 2.457616651325908e-01j, + 9.679465190411238e-01 + 2.228772501848216e-01j, + 9.679465190411238e-01 - 2.228772501848216e-01j, + 9.747235066273385e-01 + 2.178937926146544e-01j, + 9.747235066273385e-01 - 2.178937926146544e-01j] + z2 = xp.asarray(z2, dtype=xp.complex128) + p2 = xp.asarray(p2, dtype=xp.complex128) + k2 = 8.354782670263239e-03 + xp_assert_close(_sort_cmplx(z, xp=xp), + _sort_cmplx(z2, xp=xp), rtol=1e-4) + xp_assert_close(_sort_cmplx(p, xp=xp), + _sort_cmplx(p2, xp=xp), rtol=1e-4) + assert math.isclose(k, k2, rel_tol=1e-3) + + z, p, k = ellip(5, 1, 75, [90.5, 110.5], 'pass', True, 'zpk') + z2 = [-5.583607317695175e-14 + 1.433755965989225e+02j, + -5.583607317695175e-14 - 1.433755965989225e+02j, + 5.740106416459296e-14 + 1.261678754570291e+02j, + 5.740106416459296e-14 - 1.261678754570291e+02j, + -2.199676239638652e-14 + 6.974861996895196e+01j, + -2.199676239638652e-14 - 6.974861996895196e+01j, + -3.372595657044283e-14 + 7.926145989044531e+01j, + -3.372595657044283e-14 - 7.926145989044531e+01j, + 0] + p2 = [-8.814960004852743e-01 + 1.104124501436066e+02j, + -8.814960004852743e-01 - 1.104124501436066e+02j, + -2.477372459140184e+00 + 1.065638954516534e+02j, + -2.477372459140184e+00 - 1.065638954516534e+02j, + -3.072156842945799e+00 + 9.995404870405324e+01j, + -3.072156842945799e+00 - 9.995404870405324e+01j, + -2.180456023925693e+00 + 9.379206865455268e+01j, + -2.180456023925693e+00 - 9.379206865455268e+01j, + -7.230484977485752e-01 + 9.056598800801140e+01j, + -7.230484977485752e-01 - 9.056598800801140e+01j] + z2 = xp.asarray(z2, dtype=xp.complex128) + p2 = xp.asarray(p2, dtype=xp.complex128) + k2 = 3.774571622827070e-02 + xp_assert_close(_sort_cmplx(z, xp=xp), + _sort_cmplx(z2, xp=xp), rtol=1e-4) + xp_assert_close(_sort_cmplx(p, xp=xp), + _sort_cmplx(p2, xp=xp), rtol=1e-6) + assert math.isclose(k, k2, rel_tol=1e-3) + + def test_bandstop(self, xp): + z, p, k = ellip(8, 1, 65, [0.2, 0.4], 'stop', output='zpk') + z2 = [3.528578094286510e-01 + 9.356769561794296e-01j, + 3.528578094286510e-01 - 9.356769561794296e-01j, + 3.769716042264783e-01 + 9.262248159096587e-01j, + 3.769716042264783e-01 - 9.262248159096587e-01j, + 4.406101783111199e-01 + 8.976985411420985e-01j, + 4.406101783111199e-01 - 8.976985411420985e-01j, + 5.539386470258847e-01 + 8.325574907062760e-01j, + 5.539386470258847e-01 - 8.325574907062760e-01j, + 6.748464963023645e-01 + 7.379581332490555e-01j, + 6.748464963023645e-01 - 7.379581332490555e-01j, + 7.489887970285254e-01 + 6.625826604475596e-01j, + 7.489887970285254e-01 - 6.625826604475596e-01j, + 7.913118471618432e-01 + 6.114127579150699e-01j, + 7.913118471618432e-01 - 6.114127579150699e-01j, + 7.806804740916381e-01 + 6.249303940216475e-01j, + 7.806804740916381e-01 - 6.249303940216475e-01j] + + p2 = [-1.025299146693730e-01 + 5.662682444754943e-01j, + -1.025299146693730e-01 - 5.662682444754943e-01j, + 1.698463595163031e-01 + 8.926678667070186e-01j, + 1.698463595163031e-01 - 8.926678667070186e-01j, + 2.750532687820631e-01 + 9.351020170094005e-01j, + 2.750532687820631e-01 - 9.351020170094005e-01j, + 3.070095178909486e-01 + 9.457373499553291e-01j, + 3.070095178909486e-01 - 9.457373499553291e-01j, + 7.695332312152288e-01 + 2.792567212705257e-01j, + 7.695332312152288e-01 - 2.792567212705257e-01j, + 8.083818999225620e-01 + 4.990723496863960e-01j, + 8.083818999225620e-01 - 4.990723496863960e-01j, + 8.066158014414928e-01 + 5.649811440393374e-01j, + 8.066158014414928e-01 - 5.649811440393374e-01j, + 8.062787978834571e-01 + 5.855780880424964e-01j, + 8.062787978834571e-01 - 5.855780880424964e-01j] + z2 = xp.asarray(z2, dtype=xp.complex128) + p2 = xp.asarray(p2, dtype=xp.complex128) + k2 = 2.068622545291259e-01 + xp_assert_close(_sort_cmplx(z, xp=xp), + _sort_cmplx(z2, xp=xp), rtol=1e-6) + xp_assert_close(_sort_cmplx(p, xp=xp), + _sort_cmplx(p2, xp=xp), rtol=1e-5) + assert math.isclose(k, k2, rel_tol=1e-5) + + @xfail_xp_backends("cupy", reason="inaccurate on CuPy") + def test_ba_output(self, xp): + # with transfer function conversion, without digital conversion + b, a = ellip(5, 1, 40, xp.asarray([201, 240]), 'stop', True) + b2 = [ + 1.000000000000000e+00, 0, # Matlab: 1.743506051190569e-13, + 2.426561778314366e+05, 0, # Matlab: 3.459426536825722e-08, + 2.348218683400168e+10, 0, # Matlab: 2.559179747299313e-03, + 1.132780692872241e+15, 0, # Matlab: 8.363229375535731e+01, + 2.724038554089566e+19, 0, # Matlab: 1.018700994113120e+06, + 2.612380874940186e+23 + ] + a2 = [ + 1.000000000000000e+00, 1.337266601804649e+02, + 2.486725353510667e+05, 2.628059713728125e+07, + 2.436169536928770e+10, 1.913554568577315e+12, + 1.175208184614438e+15, 6.115751452473410e+16, + 2.791577695211466e+19, 7.241811142725384e+20, + 2.612380874940182e+23 + ] + b2 = xp.asarray(b2, dtype=xp.float64) + a2 = xp.asarray(a2, dtype=xp.float64) + xp_assert_close(b, b2, rtol=1e-6) + xp_assert_close(a, a2, rtol=1e-4) + + def test_fs_param(self): + for fs in (900, 900.1, 1234.567): + for N in (0, 1, 2, 3, 10): + for fc in (100, 100.1, 432.12345): + for btype in ('lp', 'hp'): + ba1 = ellip(N, 1, 20, fc, btype, fs=fs) + ba2 = ellip(N, 1, 20, fc/(fs/2), btype) + for ba1_, ba2_ in zip(ba1, ba2): + xp_assert_close(ba1_, ba2_) + for fc in ((100, 200), (100.1, 200.2), (321.123, 432.123)): + for btype in ('bp', 'bs'): + ba1 = ellip(N, 1, 20, fc, btype, fs=fs) + for seq in (list, tuple, array): + fcnorm = seq([f/(fs/2) for f in fc]) + ba2 = ellip(N, 1, 20, fcnorm, btype) + for ba1_, ba2_ in zip(ba1, ba2): + xp_assert_close(ba1_, ba2_) + + def test_fs_validation(self): + with pytest.raises(ValueError, match="Sampling.*single scalar"): + iirnotch(0.06, 30, fs=np.array([10, 20])) + + with pytest.raises(ValueError, match="Sampling.*be none"): + iirnotch(0.06, 30, fs=None) + + +def test_sos_consistency(): + # Consistency checks of output='sos' for the specialized IIR filter + # design functions. + design_funcs = [(bessel, (0.1,)), + (butter, (0.1,)), + (cheby1, (45.0, 0.1)), + (cheby2, (0.087, 0.1)), + (ellip, (0.087, 45, 0.1))] + for func, args in design_funcs: + name = func.__name__ + + b, a = func(2, *args, output='ba') + sos = func(2, *args, output='sos') + xp_assert_close(sos, [np.hstack((b, a))], err_msg=f"{name}(2,...)") + + zpk = func(3, *args, output='zpk') + sos = func(3, *args, output='sos') + xp_assert_close(sos, zpk2sos(*zpk), err_msg=f"{name}(3,...)") + + zpk = func(4, *args, output='zpk') + sos = func(4, *args, output='sos') + xp_assert_close(sos, zpk2sos(*zpk), err_msg=f"{name}(4,...)") + + +@make_xp_test_case(iirnotch) +class TestIIRNotch: + + def test_ba_output(self, xp): + # Compare coefficients with Matlab ones + # for the equivalent input: + b, a = iirnotch(0.06, 30, xp=xp) + b2 = xp.asarray([ + 9.9686824e-01, -1.9584219e+00, + 9.9686824e-01 + ]) + a2 = xp.asarray([ + 1.0000000e+00, -1.9584219e+00, + 9.9373647e-01 + ]) + + xp_assert_close(b, b2, rtol=1e-8) + xp_assert_close(a, a2, rtol=1e-8) + + @pytest.mark.xfail(DEFAULT_F32, reason="wrong answer with torch/float32") + @skip_xp_backends("dask.array", reason="https://github.com/dask/dask/issues/11883") + def test_frequency_response(self, xp): + # Get filter coefficients + b, a = iirnotch(0.3, 30, xp=xp) + + # Get frequency response + w, h = freqz(b, a, 1000) + + # Pick 5 point + p = [200, # w0 = 0.200 + 295, # w0 = 0.295 + 300, # w0 = 0.300 + 305, # w0 = 0.305 + 400] # w0 = 0.400 + + # Get frequency response correspondent to each of those points + hp = h[xp.asarray(p)] + + # Check if the frequency response fulfill the specifications: + # hp[0] and hp[4] correspond to frequencies distant from + # w0 = 0.3 and should be close to 1 + assert math.isclose(xp.abs(hp[0]), 1., rel_tol=1e-2) + assert math.isclose(xp.abs(hp[4]), 1., rel_tol=1e-2) + + # hp[1] and hp[3] correspond to frequencies approximately + # on the edges of the passband and should be close to -3dB + assert math.isclose(xp.abs(hp[1]), 1 / math.sqrt(2), rel_tol=1e-2) + assert math.isclose(xp.abs(hp[3]), 1 / math.sqrt(2), rel_tol=1e-2) + + # hp[2] correspond to the frequency that should be removed + # the frequency response should be very close to 0 + assert math.isclose(xp.abs(hp[2]), 0.0, abs_tol=1e-10) + + def test_errors(self): + # Exception should be raised if w0 > 1 or w0 <0 + assert_raises(ValueError, iirnotch, w0=2, Q=30) + assert_raises(ValueError, iirnotch, w0=-1, Q=30) + + # Exception should be raised if any of the parameters + # are not float (or cannot be converted to one) + assert_raises(ValueError, iirnotch, w0="blabla", Q=30) + assert_raises(TypeError, iirnotch, w0=-1, Q=[1, 2, 3]) + + @pytest.mark.xfail(DEFAULT_F32, reason="wrong answer with torch/float32") + @skip_xp_backends("dask.array", reason="https://github.com/dask/dask/issues/11883") + def test_fs_param(self, xp): + # Get filter coefficients + b, a = iirnotch(1500, 30, fs=10000, xp=xp) + + # Get frequency response + w, h = freqz(b, a, 1000, fs=10000) + + # Pick 5 point + p = [200, # w0 = 1000 + 295, # w0 = 1475 + 300, # w0 = 1500 + 305, # w0 = 1525 + 400] # w0 = 2000 + + # Get frequency response correspondent to each of those points + hp = h[xp.asarray(p)] + + # Check if the frequency response fulfill the specifications: + # hp[0] and hp[4] correspond to frequencies distant from + # w0 = 1500 and should be close to 1 + assert math.isclose(xp.abs(hp[0]), 1.0, rel_tol=1e-2) + assert math.isclose(xp.abs(hp[4]), 1.0, rel_tol=1e-2) + + # hp[1] and hp[3] correspond to frequencies approximately + # on the edges of the passband and should be close to -3dB + assert math.isclose(xp.abs(hp[1]), 1 / math.sqrt(2), rel_tol=1e-2) + assert math.isclose(xp.abs(hp[3]), 1 / math.sqrt(2), rel_tol=1e-2) + + # hp[2] correspond to the frequency that should be removed + # the frequency response should be very close to 0 + assert math.isclose(xp.abs(hp[2]), 0.0, abs_tol=1e-10) + + +@make_xp_test_case(iirpeak) +class TestIIRPeak: + + def test_ba_output(self, xp): + # Compare coefficients with Matlab ones + # for the equivalent input: + b, a = iirpeak(0.06, 30, xp=xp) + b2 = xp.asarray([ + 3.131764229e-03, 0, + -3.131764229e-03 + ]) + a2 = xp.asarray([ + 1.0000000e+00, -1.958421917e+00, + 9.9373647e-01 + ]) + xp_assert_close(b, b2, rtol=1e-8) + xp_assert_close(a, a2, rtol=1e-8) + + @pytest.mark.xfail(DEFAULT_F32, reason="wrong answer with torch/float32") + @skip_xp_backends("dask.array", reason="https://github.com/dask/dask/issues/11883") + def test_frequency_response(self, xp): + # Get filter coefficients + b, a = iirpeak(0.3, 30, xp=xp) + + # Get frequency response + w, h = freqz(b, a, 1000) + + # Pick 5 point + p = [30, # w0 = 0.030 + 295, # w0 = 0.295 + 300, # w0 = 0.300 + 305, # w0 = 0.305 + 800] # w0 = 0.800 + + # Get frequency response correspondent to each of those points + hp = h[xp.asarray(p)] + + # Check if the frequency response fulfill the specifications: + # hp[0] and hp[4] correspond to frequencies distant from + # w0 = 0.3 and should be close to 0 + assert math.isclose(xp.abs(hp[0]), 0., abs_tol=1e-2) + assert math.isclose(xp.abs(hp[4]), 0., abs_tol=1e-2) + + # hp[1] and hp[3] correspond to frequencies approximately + # on the edges of the passband and should be close to 10**(-3/20) + assert math.isclose(xp.abs(hp[1]), 1 / math.sqrt(2), rel_tol=1e-2) + assert math.isclose(xp.abs(hp[3]), 1 / math.sqrt(2), rel_tol=1e-2) + + # hp[2] correspond to the frequency that should be retained and + # the frequency response should be very close to 1 + assert math.isclose(xp.abs(hp[2]), 1.0, rel_tol=1e-10) + + @skip_xp_backends(np_only=True) + def test_errors(self): + # Exception should be raised if w0 > 1 or w0 <0 + assert_raises(ValueError, iirpeak, w0=2, Q=30) + assert_raises(ValueError, iirpeak, w0=-1, Q=30) + + # Exception should be raised if any of the parameters + # are not float (or cannot be converted to one) + assert_raises(ValueError, iirpeak, w0="blabla", Q=30) + assert_raises(TypeError, iirpeak, w0=-1, Q=[1, 2, 3]) + + @pytest.mark.xfail(DEFAULT_F32, reason="wrong answer with torch/float32") + @skip_xp_backends("dask.array", reason="https://github.com/dask/dask/issues/11883") + def test_fs_param(self, xp): + # Get filter coefficients + b, a = iirpeak(1200, 30, fs=8000, xp=xp) + + # Get frequency response + w, h = freqz(b, a, 1000, fs=8000) + + # Pick 5 point + p = [30, # w0 = 120 + 295, # w0 = 1180 + 300, # w0 = 1200 + 305, # w0 = 1220 + 800] # w0 = 3200 + + # Get frequency response correspondent to each of those points + hp = h[xp.asarray(p)] + + # Check if the frequency response fulfill the specifications: + # hp[0] and hp[4] correspond to frequencies distant from + # w0 = 1200 and should be close to 0 + assert math.isclose(abs(hp[0]), 0.0, abs_tol=1e-2) + assert math.isclose(abs(hp[4]), 0.0, abs_tol=1e-2) + + # hp[1] and hp[3] correspond to frequencies approximately + # on the edges of the passband and should be close to 10**(-3/20) + assert math.isclose(abs(hp[1]), 1 / math.sqrt(2), rel_tol=1e-2) + assert math.isclose(abs(hp[3]), 1 / math.sqrt(2), rel_tol=1e-2) + + # hp[2] correspond to the frequency that should be retained and + # the frequency response should be very close to 1 + assert math.isclose(abs(hp[2]), 1.0, rel_tol=1e-10) + + +@pytest.mark.xfail(DEFAULT_F32, reason="wrong answers with torch/float32") +@skip_xp_backends("dask.array", reason="https://github.com/dask/dask/issues/11883") +@make_xp_test_case(iircomb) +class TestIIRComb: + # Test erroneous input cases + @skip_xp_backends(np_only=True) + def test_invalid_input(self): + # w0 is <= 0 or >= fs / 2 + fs = 1000 + for args in [(-fs, 30), (0, 35), (fs / 2, 40), (fs, 35)]: + with pytest.raises(ValueError, match='w0 must be between '): + iircomb(*args, fs=fs) + + # fs is not divisible by w0 + for args in [(120, 30), (157, 35)]: + with pytest.raises(ValueError, match='fs must be divisible '): + iircomb(*args, fs=fs) + + # https://github.com/scipy/scipy/issues/14043#issuecomment-1107349140 + # Previously, fs=44100, w0=49.999 was rejected, but fs=2, + # w0=49.999/int(44100/2) was accepted. Now it is rejected, too. + with pytest.raises(ValueError, match='fs must be divisible '): + iircomb(w0=49.999/int(44100/2), Q=30) + + with pytest.raises(ValueError, match='fs must be divisible '): + iircomb(w0=49.999, Q=30, fs=44100) + + # Filter type is not notch or peak + for args in [(0.2, 30, 'natch'), (0.5, 35, 'comb')]: + with pytest.raises(ValueError, match='ftype must be '): + iircomb(*args) + + # Verify that the filter's frequency response contains a + # notch at the cutoff frequency + @pytest.mark.parametrize('ftype', ('notch', 'peak')) + def test_frequency_response(self, ftype, xp): + # Create a notching or peaking comb filter at 1000 Hz + b, a = iircomb(1000, 30, ftype=ftype, fs=10000, xp=xp) + + # Compute the frequency response + freqs, response = freqz(b, a, 1000, fs=10000) + + # Find the notch using argrelextrema + comb_points = argrelextrema(abs(_xp_copy_to_numpy(response)), np.less)[0] + comb_points = xp.asarray(comb_points) + + # Verify that the first notch sits at 1000 Hz + comb1 = comb_points[0] + xp_assert_close(freqs[comb1], xp.asarray(1000.), check_0d=False) + + # Verify pass_zero parameter + @pytest.mark.parametrize('ftype,pass_zero,peak,notch', + [('peak', True, 123.45, 61.725), + ('peak', False, 61.725, 123.45), + ('peak', None, 61.725, 123.45), + ('notch', None, 61.725, 123.45), + ('notch', True, 123.45, 61.725), + ('notch', False, 61.725, 123.45)]) + def test_pass_zero(self, ftype, pass_zero, peak, notch, xp): + # Create a notching or peaking comb filter + b, a = iircomb(123.45, 30, ftype=ftype, fs=1234.5, pass_zero=pass_zero, xp=xp) + + # Compute the frequency response + freqs, response = freqz(b, a, xp.asarray([peak, notch]), fs=1234.5) + + # Verify that expected notches are notches and peaks are peaks + assert xp.abs(response[0]) > 0.99 + assert xp.abs(response[1]) < 1e-10 + + # All built-in IIR filters are real, so should have perfectly + # symmetrical poles and zeros. Then ba representation (using + # numpy.poly) will be purely real instead of having negligible + # imaginary parts. + def test_iir_symmetry(self, xp): + b, a = iircomb(400, 30, fs=24000, xp=xp) + z, p, k = tf2zpk(b, a) + xp_assert_equal(_sort_cmplx(z, xp=xp), _sort_cmplx(xp.conj(z), xp=xp)) + xp_assert_equal(_sort_cmplx(p, xp=xp), _sort_cmplx(xp.conj(p), xp=xp)) + xp_assert_equal(k, xp.real(k)) + + isdtype = array_namespace(b).isdtype + assert isdtype(b.dtype, ('real floating', 'complex floating')) + assert isdtype(a.dtype, ('real floating', 'complex floating')) + + # Verify filter coefficients with MATLAB's iircomb function + def test_ba_output(self, xp): + b_notch, a_notch = iircomb(60, 35, ftype='notch', fs=600, xp=xp) + b_notch2 = [0.957020174408697, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, -0.957020174408697] + a_notch2 = [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, -0.914040348817395] + b_notch2 = xp.asarray(b_notch2) + a_notch2 = xp.asarray(a_notch2) + xp_assert_close(b_notch, b_notch2) + xp_assert_close(a_notch, a_notch2) + + b_peak, a_peak = iircomb(60, 35, ftype='peak', fs=600, xp=xp) + b_peak2 = [0.0429798255913026, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, -0.0429798255913026] + a_peak2 = [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.914040348817395] + b_peak2 = xp.asarray(b_peak2) + a_peak2 = xp.asarray(a_peak2) + xp_assert_close(b_peak, b_peak2) + xp_assert_close(a_peak, a_peak2) + + # Verify that https://github.com/scipy/scipy/issues/14043 is fixed + def test_nearest_divisor(self, xp): + # Create a notching comb filter + b, a = iircomb(50/int(44100/2), 50.0, ftype='notch', xp=xp) + + # Compute the frequency response at an upper harmonic of 50 + freqs, response = freqz(b, a, xp.asarray([22000]), fs=44100) + + # Before bug fix, this would produce N = 881, so that 22 kHz was ~0 dB. + # Now N = 882 correctly and 22 kHz should be a notch <-220 dB + assert xp.abs(response[0]) < 1e-10 + + def test_fs_validation(self): + with pytest.raises(ValueError, match="Sampling.*single scalar"): + iircomb(1000, 30, fs=np.array([10, 20])) + + with pytest.raises(ValueError, match="Sampling.*be none"): + iircomb(1000, 30, fs=None) + + +class TestIIRDesign: + + def test_exceptions(self): + with pytest.raises(ValueError, match="the same shape"): + iirdesign(0.2, [0.1, 0.3], 1, 40) + with pytest.raises(ValueError, match="the same shape"): + iirdesign(np.array([[0.3, 0.6], [0.3, 0.6]]), + np.array([[0.4, 0.5], [0.4, 0.5]]), 1, 40) + + # discrete filter with non-positive frequency + with pytest.raises(ValueError, match="must be greater than 0"): + iirdesign(0, 0.5, 1, 40) + with pytest.raises(ValueError, match="must be greater than 0"): + iirdesign(-0.1, 0.5, 1, 40) + with pytest.raises(ValueError, match="must be greater than 0"): + iirdesign(0.1, 0, 1, 40) + with pytest.raises(ValueError, match="must be greater than 0"): + iirdesign(0.1, -0.5, 1, 40) + with pytest.raises(ValueError, match="must be greater than 0"): + iirdesign([0, 0.3], [0.1, 0.5], 1, 40) + with pytest.raises(ValueError, match="must be greater than 0"): + iirdesign([-0.1, 0.3], [0.1, 0.5], 1, 40) + with pytest.raises(ValueError, match="must be greater than 0"): + iirdesign([0.1, 0], [0.1, 0.5], 1, 40) + with pytest.raises(ValueError, match="must be greater than 0"): + iirdesign([0.1, -0.3], [0.1, 0.5], 1, 40) + with pytest.raises(ValueError, match="must be greater than 0"): + iirdesign([0.1, 0.3], [0, 0.5], 1, 40) + with pytest.raises(ValueError, match="must be greater than 0"): + iirdesign([0.1, 0.3], [-0.1, 0.5], 1, 40) + with pytest.raises(ValueError, match="must be greater than 0"): + iirdesign([0.1, 0.3], [0.1, 0], 1, 40) + with pytest.raises(ValueError, match="must be greater than 0"): + iirdesign([0.1, 0.3], [0.1, -0.5], 1, 40) + + # analog filter with negative frequency + with pytest.raises(ValueError, match="must be greater than 0"): + iirdesign(-0.1, 0.5, 1, 40, analog=True) + with pytest.raises(ValueError, match="must be greater than 0"): + iirdesign(0.1, -0.5, 1, 40, analog=True) + with pytest.raises(ValueError, match="must be greater than 0"): + iirdesign([-0.1, 0.3], [0.1, 0.5], 1, 40, analog=True) + with pytest.raises(ValueError, match="must be greater than 0"): + iirdesign([0.1, -0.3], [0.1, 0.5], 1, 40, analog=True) + with pytest.raises(ValueError, match="must be greater than 0"): + iirdesign([0.1, 0.3], [-0.1, 0.5], 1, 40, analog=True) + with pytest.raises(ValueError, match="must be greater than 0"): + iirdesign([0.1, 0.3], [0.1, -0.5], 1, 40, analog=True) + + # discrete filter with fs=None, freq > 1 + with pytest.raises(ValueError, match="must be less than 1"): + iirdesign(1, 0.5, 1, 40) + with pytest.raises(ValueError, match="must be less than 1"): + iirdesign(1.1, 0.5, 1, 40) + with pytest.raises(ValueError, match="must be less than 1"): + iirdesign(0.1, 1, 1, 40) + with pytest.raises(ValueError, match="must be less than 1"): + iirdesign(0.1, 1.5, 1, 40) + with pytest.raises(ValueError, match="must be less than 1"): + iirdesign([1, 0.3], [0.1, 0.5], 1, 40) + with pytest.raises(ValueError, match="must be less than 1"): + iirdesign([1.1, 0.3], [0.1, 0.5], 1, 40) + with pytest.raises(ValueError, match="must be less than 1"): + iirdesign([0.1, 1], [0.1, 0.5], 1, 40) + with pytest.raises(ValueError, match="must be less than 1"): + iirdesign([0.1, 1.1], [0.1, 0.5], 1, 40) + with pytest.raises(ValueError, match="must be less than 1"): + iirdesign([0.1, 0.3], [1, 0.5], 1, 40) + with pytest.raises(ValueError, match="must be less than 1"): + iirdesign([0.1, 0.3], [1.1, 0.5], 1, 40) + with pytest.raises(ValueError, match="must be less than 1"): + iirdesign([0.1, 0.3], [0.1, 1], 1, 40) + with pytest.raises(ValueError, match="must be less than 1"): + iirdesign([0.1, 0.3], [0.1, 1.5], 1, 40) + + # discrete filter with fs>2, wp, ws < fs/2 must pass + iirdesign(100, 500, 1, 40, fs=2000) + iirdesign(500, 100, 1, 40, fs=2000) + iirdesign([200, 400], [100, 500], 1, 40, fs=2000) + iirdesign([100, 500], [200, 400], 1, 40, fs=2000) + + # discrete filter with fs>2, freq > fs/2: this must raise + with pytest.raises(ValueError, match="must be less than fs/2"): + iirdesign(1000, 400, 1, 40, fs=2000) + with pytest.raises(ValueError, match="must be less than fs/2"): + iirdesign(1100, 500, 1, 40, fs=2000) + with pytest.raises(ValueError, match="must be less than fs/2"): + iirdesign(100, 1000, 1, 40, fs=2000) + with pytest.raises(ValueError, match="must be less than fs/2"): + iirdesign(100, 1100, 1, 40, fs=2000) + with pytest.raises(ValueError, match="must be less than fs/2"): + iirdesign([1000, 400], [100, 500], 1, 40, fs=2000) + with pytest.raises(ValueError, match="must be less than fs/2"): + iirdesign([1100, 400], [100, 500], 1, 40, fs=2000) + with pytest.raises(ValueError, match="must be less than fs/2"): + iirdesign([200, 1000], [100, 500], 1, 40, fs=2000) + with pytest.raises(ValueError, match="must be less than fs/2"): + iirdesign([200, 1100], [100, 500], 1, 40, fs=2000) + with pytest.raises(ValueError, match="must be less than fs/2"): + iirdesign([200, 400], [1000, 500], 1, 40, fs=2000) + with pytest.raises(ValueError, match="must be less than fs/2"): + iirdesign([200, 400], [1100, 500], 1, 40, fs=2000) + with pytest.raises(ValueError, match="must be less than fs/2"): + iirdesign([200, 400], [100, 1000], 1, 40, fs=2000) + with pytest.raises(ValueError, match="must be less than fs/2"): + iirdesign([200, 400], [100, 1100], 1, 40, fs=2000) + + with pytest.raises(ValueError, match="strictly inside stopband"): + iirdesign([0.1, 0.4], [0.5, 0.6], 1, 40) + with pytest.raises(ValueError, match="strictly inside stopband"): + iirdesign([0.5, 0.6], [0.1, 0.4], 1, 40) + with pytest.raises(ValueError, match="strictly inside stopband"): + iirdesign([0.3, 0.6], [0.4, 0.7], 1, 40) + with pytest.raises(ValueError, match="strictly inside stopband"): + iirdesign([0.4, 0.7], [0.3, 0.6], 1, 40) + + def test_fs_validation(self): + with pytest.raises(ValueError, match="Sampling.*single scalar"): + iirfilter(1, 1, btype="low", fs=np.array([10, 20])) + + +@skip_xp_backends("dask.array", reason="https://github.com/dask/dask/issues/11883") +@make_xp_test_case(iirfilter) +class TestIIRFilter: + + @pytest.mark.parametrize( + "func,ftype", + [ + make_xp_pytest_param(butter, "butter"), + make_xp_pytest_param(bessel, "bessel"), + make_xp_pytest_param(cheby1, "cheby1"), + make_xp_pytest_param(cheby2, "cheby2"), + make_xp_pytest_param(ellip, "ellip"), + ] + ) + def test_symmetry(self, func, ftype, xp): + # All built-in IIR filters are real, so should have perfectly + # symmetrical poles and zeros. Then ba representation (using + # numpy.poly) will be purely real instead of having negligible + # imaginary parts. + for N in range(1, 26): + z, p, k = iirfilter(N, xp.asarray(1.1), 1, 20, 'low', analog=True, + ftype=ftype, output='zpk') + xp_assert_close(_sort_cmplx(z, xp=xp), _sort_cmplx(xp.conj(z), xp=xp)) + xp_assert_close(_sort_cmplx(p, xp=xp), _sort_cmplx(xp.conj(p), xp=xp)) + assert complex(k).imag == 0 + + b, a = iirfilter(N, xp.asarray(1.1), 1, 20, 'low', analog=True, + ftype=ftype, output='ba') + + isdtype = array_namespace(b).isdtype + assert isdtype(b.dtype, ('real floating', 'complex floating')) + assert isdtype(a.dtype, ('real floating', 'complex floating')) + + @make_xp_test_case(bessel) + def test_int_inputs(self, xp): + # Using integer frequency arguments and large N should not produce + # numpy integers that wraparound to negative numbers + k = iirfilter(24, xp.asarray(100), btype='low', analog=True, ftype='bessel', + output='zpk')[2] + k2 = 9.999999999999989e+47 + assert math.isclose(k, k2) + # if fs is specified then the normalization of Wn to have + # 0 <= Wn <= 1 should not cause an integer overflow + # the following line should not raise an exception + iirfilter(20, xp.asarray([1000000000, 1100000000]), btype='bp', + analog=False, fs=6250000000) + + def test_invalid_wn_size(self): + # low and high have 1 Wn, band and stop have 2 Wn + assert_raises(ValueError, iirfilter, 1, [0.1, 0.9], btype='low') + assert_raises(ValueError, iirfilter, 1, [0.2, 0.5], btype='high') + assert_raises(ValueError, iirfilter, 1, 0.2, btype='bp') + assert_raises(ValueError, iirfilter, 1, 400, btype='bs', analog=True) + + def test_invalid_wn_range(self): + # For digital filters, 0 <= Wn <= 1 + assert_raises(ValueError, iirfilter, 1, 2, btype='low') + assert_raises(ValueError, iirfilter, 1, [0.5, 1], btype='band') + assert_raises(ValueError, iirfilter, 1, [0., 0.5], btype='band') + assert_raises(ValueError, iirfilter, 1, -1, btype='high') + assert_raises(ValueError, iirfilter, 1, [1, 2], btype='band') + assert_raises(ValueError, iirfilter, 1, [10, 20], btype='stop') + + # analog=True with non-positive critical frequencies + with pytest.raises(ValueError, match="must be greater than 0"): + iirfilter(2, 0, btype='low', analog=True) + with pytest.raises(ValueError, match="must be greater than 0"): + iirfilter(2, -1, btype='low', analog=True) + with pytest.raises(ValueError, match="must be greater than 0"): + iirfilter(2, [0, 100], analog=True) + with pytest.raises(ValueError, match="must be greater than 0"): + iirfilter(2, [-1, 100], analog=True) + with pytest.raises(ValueError, match="must be greater than 0"): + iirfilter(2, [10, 0], analog=True) + with pytest.raises(ValueError, match="must be greater than 0"): + iirfilter(2, [10, -1], analog=True) + + @make_xp_test_case(butter) + def test_analog_sos(self, xp): + # first order Butterworth filter with Wn = 1 has tf 1/(s+1) + sos = xp.asarray([[0., 0., 1., 0., 1., 1.]]) + sos2 = iirfilter(N=1, Wn=xp.asarray(1), btype='low', analog=True, output='sos') + assert_array_almost_equal(sos, sos2) + + def test_wn1_ge_wn0(self): + # gh-15773: should raise error if Wn[0] >= Wn[1] + with pytest.raises(ValueError, + match=r"Wn\[0\] must be less than Wn\[1\]"): + iirfilter(2, [0.5, 0.5]) + with pytest.raises(ValueError, + match=r"Wn\[0\] must be less than Wn\[1\]"): + iirfilter(2, [0.6, 0.5]) + + +@skip_xp_backends("dask.array", reason="https://github.com/dask/dask/issues/11883") +@make_xp_test_case(group_delay) +class TestGroupDelay: + def test_identity_filter(self, xp): + w, gd = group_delay((1, xp.asarray(1))) + assert_array_almost_equal(w, xp.pi * xp.arange(512, dtype=w.dtype) / 512) + assert_array_almost_equal(gd, xp.zeros(512)) + + w, gd = group_delay((1, xp.asarray(1)), whole=True) + assert_array_almost_equal(w, 2 * xp.pi * xp.arange(512, dtype=w.dtype) / 512) + assert_array_almost_equal(gd, xp.zeros(512)) + + @xfail_xp_backends("cupy", reason="slightly inaccurate") + @pytest.mark.xfail(DEFAULT_F32, reason="wrong answer with torch/float32") + def test_fir(self, xp): + # Let's design linear phase FIR and check that the group delay + # is constant. + N = 100 + b = firwin(N + 1, 0.1) + b = xp.asarray(b) + w, gd = group_delay((b, 1)) + xp_assert_close(gd, xp.ones_like(gd)*(0.5 * N), rtol=5e-7) + + @pytest.mark.xfail(DEFAULT_F32, reason="wrong answer with torch/float32") + def test_iir(self, xp): + # Let's design Butterworth filter and test the group delay at + # some points against MATLAB answer. + b, a = butter(4, 0.1) + b, a = map(xp.asarray, (b, a)) + w = xp.linspace(0, xp.pi, num=10, endpoint=False) + w, gd = group_delay((b, a), w=w) + matlab_gd = xp.asarray([8.249313898506037, 11.958947880907104, + 2.452325615326005, 1.048918665702008, + 0.611382575635897, 0.418293269460578, + 0.317932917836572, 0.261371844762525, + 0.229038045801298, 0.212185774208521]) + assert_array_almost_equal(gd, matlab_gd) + + @xfail_xp_backends("cupy", reason="does not warn") + @xfail_xp_backends("torch", reason="does not warn") + def test_singular(self, xp): + # Let's create a filter with zeros and poles on the unit circle and + # check if warnings are raised at those frequencies. + z1 = np.exp(1j * 0.1 * pi) + z2 = np.exp(1j * 0.25 * pi) + p1 = np.exp(1j * 0.5 * pi) + p2 = np.exp(1j * 0.8 * pi) + + b = np.convolve([1, -z1], [1, -z2]) + a = np.convolve([1, -p1], [1, -p2]) + b, a = map(xp.asarray, (b, a)) + + w = xp.asarray([0.1 * xp.pi, 0.25 * xp.pi, -0.5 * xp.pi, -0.8 * xp.pi]) + + with pytest.warns(UserWarning): + w, gd = group_delay((b, a), w=w) + + def test_backward_compat(self, xp): + # For backward compatibility, test if None act as a wrapper for default + w1, gd1 = group_delay((1, xp.asarray(1))) + w2, gd2 = group_delay((1, xp.asarray(1)), None) + assert_array_almost_equal(w1, w2) + assert_array_almost_equal(gd1, gd2) + + @pytest.mark.xfail(DEFAULT_F32, reason="wrong answer with torch/float32") + def test_fs_param(self, xp): + # Let's design Butterworth filter and test the group delay at + # some points against the normalized frequency answer. + b, a = butter(4, 4800, fs=96000) + b, a = map(xp.asarray, (b, a)) + w = xp.linspace(0, 96000/2, num=10, endpoint=False) + w, gd = group_delay((b, a), w=w, fs=96000) + norm_gd = xp.asarray([8.249313898506037, 11.958947880907104, + 2.452325615326005, 1.048918665702008, + 0.611382575635897, 0.418293269460578, + 0.317932917836572, 0.261371844762525, + 0.229038045801298, 0.212185774208521]) + assert_array_almost_equal(gd, norm_gd) + + @skip_xp_backends(np_only=True, reason='numpy scalars') + def test_w_or_N_types(self, xp): + # Measure at 8 equally-spaced points + for N in (8, np.int8(8), np.int16(8), np.int32(8), np.int64(8), + np.array(8)): + w, gd = group_delay((1, 1), N) + assert_array_almost_equal(w, pi * np.arange(8) / 8) + assert_array_almost_equal(gd, np.zeros(8)) + + # Measure at frequency 8 rad/sec + for w in (8.0, 8.0+0j): + w_out, gd = group_delay((1, 1), w) + assert_array_almost_equal(w_out, [8]) + assert_array_almost_equal(gd, [0]) + + @pytest.mark.xfail(DEFAULT_F32, reason="with torch/float32, the rtol is ~1e-7") + @xfail_xp_backends("cupy", reason="inaccurate") + def test_complex_coef(self, xp): + # gh-19586: handle complex coef TFs + # + # for g(z) = (alpha*z+1)/(1+conjugate(alpha)), group delay is + # given by function below. + # + # def gd_expr(w, alpha): + # num = 1j*(abs(alpha)**2-1)*np.exp(1j*w) + # den = (alpha*np.exp(1j*w)+1)*(np.exp(1j*w)+np.conj(alpha)) + # return -np.imag(num/den) + + # arbitrary non-real alpha + alpha = -0.6143077933232609 + 0.3355978770229421j + # 8 points from from -pi to pi + wref = xp.asarray([-3.141592653589793 , + -2.356194490192345 , + -1.5707963267948966, + -0.7853981633974483, + 0. , + 0.7853981633974483, + 1.5707963267948966, + 2.356194490192345 ]) + gdref = xp.asarray([0.18759548150354619, + 0.17999770352712252, + 0.23598047471879877, + 0.46539443069907194, + 1.9511492420564165 , + 3.478129975138865 , + 0.6228594960517333 , + 0.27067831839471224]) + b = xp.asarray([alpha, 1]) + a = xp.asarray([1, alpha.conjugate()]) + gdtest = group_delay((b, a), wref)[1] + # need nulp=14 for macOS arm64 wheel builds; added 2 for some + # robustness on other platforms. + xp_assert_close_nulp(gdtest, gdref, nulp=16) + + def test_fs_validation(self): + with pytest.raises(ValueError, match="Sampling.*single scalar"): + group_delay((1, 1), fs=np.array([10, 20])) + + with pytest.raises(ValueError, match="Sampling.*be none"): + group_delay((1, 1), fs=None) + + +@skip_xp_backends("dask.array", reason="https://github.com/dask/dask/issues/11883") +@make_xp_test_case(gammatone) +class TestGammatone: + # Test erroneous input cases. + @skip_xp_backends(np_only=True) + def test_invalid_input(self, xp): + # Cutoff frequency is <= 0 or >= fs / 2. + fs = 16000 + for args in [(-fs, 'iir'), (0, 'fir'), (fs / 2, 'iir'), (fs, 'fir')]: + with pytest.raises(ValueError, match='The frequency must be ' + 'between '): + gammatone(*args, fs=fs) + + # Filter type is not fir or iir + for args in [(440, 'fie'), (220, 'it')]: + with pytest.raises(ValueError, match='ftype must be '): + gammatone(*args, fs=fs) + + # Order is <= 0 or > 24 for FIR filter. + for args in [(440, 'fir', -50), (220, 'fir', 0), (110, 'fir', 25), + (55, 'fir', 50)]: + with pytest.raises(ValueError, match='Invalid order: '): + gammatone(*args, numtaps=None, fs=fs) + + # Verify that the filter's frequency response is approximately + # 1 at the cutoff frequency. + @pytest.mark.xfail(DEFAULT_F32, reason="wrong answer with torch/float32") + @xfail_xp_backends("cupy", reason="inaccurate") + def test_frequency_response(self, xp): + fs = 16000 + ftypes = ['fir', 'iir'] + for ftype in ftypes: + # Create a gammatone filter centered at 1000 Hz. + b, a = gammatone(1000, ftype, fs=fs, xp=xp) + + # Calculate the frequency response. + freqs, response = freqz(_xp_copy_to_numpy(b), _xp_copy_to_numpy(a)) + + # Determine peak magnitude of the response + # and corresponding frequency. + response_max = np.max(np.abs(response)) + freq_hz = freqs[np.argmax(np.abs(response))] / ((2 * np.pi) / fs) + response_max, freq_hz = map(xp.asarray, (response_max, freq_hz)) + + # Check that the peak magnitude is 1 and the frequency is 1000 Hz. + xp_assert_close(response_max, + xp.ones_like(response_max), rtol=1e-2, check_0d=False) + xp_assert_close(freq_hz, + 1000*xp.ones_like(freq_hz), rtol=1e-2, check_0d=False) + + # All built-in IIR filters are real, so should have perfectly + # symmetrical poles and zeros. Then ba representation (using + # numpy.poly) will be purely real instead of having negligible + # imaginary parts. + def test_iir_symmetry(self, xp): + b, a = gammatone(440, 'iir', fs=24000, xp=xp) + z, p, k = tf2zpk(_xp_copy_to_numpy(b), _xp_copy_to_numpy(a)) + z, p, k = map(xp.asarray, (z, p, k)) + xp_assert_equal(_sort_cmplx(z, xp=xp), _sort_cmplx(xp.conj(z), xp=xp)) + xp_assert_equal(_sort_cmplx(p, xp=xp), _sort_cmplx(xp.conj(p), xp=xp)) + xp_assert_equal(k, xp.real(k)) + + isdtype = array_namespace(b).isdtype + assert isdtype(b.dtype, ('real floating', 'complex floating')) + assert isdtype(a.dtype, ('real floating', 'complex floating')) + + # Verify FIR filter coefficients with the paper's + # Mathematica implementation + def test_fir_ba_output(self, xp): + b, _ = gammatone(15, 'fir', fs=1000, xp=xp) + b2 = [0.0, 2.2608075649884e-04, + 1.5077903981357e-03, 4.2033687753998e-03, + 8.1508962726503e-03, 1.2890059089154e-02, + 1.7833890391666e-02, 2.2392613558564e-02, + 2.6055195863104e-02, 2.8435872863284e-02, + 2.9293319149544e-02, 2.852976858014e-02, + 2.6176557156294e-02, 2.2371510270395e-02, + 1.7332485267759e-02] + b2 = xp.asarray(b2) + xp_assert_close(b, b2) + + # Verify IIR filter coefficients with the paper's MATLAB implementation + def test_iir_ba_output(self, xp): + b, a = gammatone(440, 'iir', fs=16000, xp=xp) + b2 = [1.31494461367464e-06, -5.03391196645395e-06, + 7.00649426000897e-06, -4.18951968419854e-06, + 9.02614910412011e-07] + a2 = [1.0, -7.65646235454218, + 25.7584699322366, -49.7319214483238, + 60.2667361289181, -46.9399590980486, + 22.9474798808461, -6.43799381299034, + 0.793651554625368] + b2, a2 = map(xp.asarray, (b2, a2)) + xp_assert_close(b, b2, atol=1e-10, rtol=5e-5) + xp_assert_close(a, a2, atol=1e-10, rtol=5e-5) + + def test_fs_validation(self): + with pytest.raises(ValueError, match="Sampling.*single scalar"): + gammatone(440, 'iir', fs=np.asarray([10, 20])) + + +@make_xp_test_case(findfreqs) +class TestFindFreqs: + @pytest.mark.parametrize("kind", ["ba", "zp"]) + def test_findfreqs_log_spacing(self, xp, kind): + N = 30 + w = findfreqs(xp.asarray([1.2], dtype=xp.float64), + xp.asarray([3.6, 9.8], dtype=xp.float64), N=N, kind=kind) + ratio = xp.diff(xp.log10(w)) + xp_assert_close(ratio, ratio[0]*xp.ones(N - 1, dtype=xp.float64)) + + def test_findfreqs_ba_zp_equiv(self, xp): + num, den = [1.4, 2], [1, 3.8, 2] + zeros = xp.asarray(np.roots(num)) + poles = xp.asarray(np.roots(den)) + + ba = findfreqs(xp.asarray(num, dtype=xp.float64), + xp.asarray(den, dtype=xp.float64), N=30, kind="ba") + zp = findfreqs(zeros, poles, N=30, kind="zp") + + xp_assert_close(ba, zp) diff --git a/venv/lib/python3.12/site-packages/scipy/signal/tests/test_fir_filter_design.py b/venv/lib/python3.12/site-packages/scipy/signal/tests/test_fir_filter_design.py new file mode 100644 index 0000000000000000000000000000000000000000..11e9b237ee018a29dbcdf6fa53624073c586f191 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/tests/test_fir_filter_design.py @@ -0,0 +1,884 @@ +import math +import numpy as np + +from pytest import raises as assert_raises, warns as assert_warns +import pytest + +import scipy._external.array_api_extra as xpx +import scipy.signal as signal +from scipy._lib._array_api import ( + xp_assert_close, xp_assert_equal, assert_almost_equal, assert_array_almost_equal, + array_namespace, xp_default_dtype, make_xp_test_case, _xp_copy_to_numpy +) +from scipy.fft import fft, fft2, rfft +from scipy.signal import (kaiser_beta, kaiser_atten, kaiserord, + firwin, firwin2, freqz, remez, firls, minimum_phase, convolve2d, firwin_2d +) + +skip_xp_backends = pytest.mark.skip_xp_backends +xfail_xp_backends = pytest.mark.xfail_xp_backends + +lazy_xp_modules = [signal] + + +def test_kaiser_beta(): + b = kaiser_beta(58.7) + assert_almost_equal(b, 0.1102 * 50.0) + b = kaiser_beta(22.0) + assert_almost_equal(b, 0.5842 + 0.07886) + b = kaiser_beta(21.0) + assert b == 0.0 + b = kaiser_beta(10.0) + assert b == 0.0 + + +def test_kaiser_atten(): + a = kaiser_atten(1, 1.0) + assert a == 7.95 + a = kaiser_atten(2, 1/np.pi) + assert a == 2.285 + 7.95 + + +def test_kaiserord(): + assert_raises(ValueError, kaiserord, 1.0, 1.0) + numtaps, beta = kaiserord(2.285 + 7.95 - 0.001, 1/np.pi) + assert (numtaps, beta) == (2, 0.0) + + +@make_xp_test_case(firwin) +class TestFirwin: + + def check_response(self, h, expected_response, tol=.05): + xp = array_namespace(h) + N = h.shape[0] + alpha = 0.5 * (N-1) + m = xp.arange(0, N, dtype=xp_default_dtype(xp)) - alpha # time indices of taps + for freq, expected in expected_response: + actual = abs(xp.sum(h * xp.exp(-1j * xp.pi * m * freq))) + mse = abs(actual - expected)**2 + assert mse < tol, f'response not as expected, mse={mse:g} > {tol:g}' + + def test_response(self, xp): + N = 51 + f = xp.asarray(.5) + + # increase length just to try even/odd + h = firwin(N, f) # low-pass from 0 to f + self.check_response(h, [(.25,1), (.75,0)]) + + h = firwin(N+1, f, window='nuttall') # specific window + self.check_response(h, [(.25,1), (.75,0)]) + + h = firwin(N+2, f, pass_zero=False) # stop from 0 to f --> high-pass + self.check_response(h, [(.25,0), (.75,1)]) + + f1, f2, f3, f4 = .2, .4, .6, .8 + h = firwin(N+3, [f1, f2], pass_zero=False) # band-pass filter + self.check_response(h, [(.1,0), (.3,1), (.5,0)]) + + h = firwin(N+4, [f1, f2]) # band-stop filter + self.check_response(h, [(.1,1), (.3,0), (.5,1)]) + + h = firwin(N+5, [f1, f2, f3, f4], pass_zero=False, scale=False) + self.check_response(h, [(.1,0), (.3,1), (.5,0), (.7,1), (.9,0)]) + + h = firwin(N+6, [f1, f2, f3, f4]) # multiband filter + self.check_response(h, [(.1,1), (.3,0), (.5,1), (.7,0), (.9,1)]) + + h = firwin(N+7, 0.1, width=.03) # low-pass + self.check_response(h, [(.05,1), (.75,0)]) + + h = firwin(N+8, 0.1, pass_zero=False) # high-pass + self.check_response(h, [(.05,0), (.75,1)]) + + def mse(self, h, bands): + """Compute mean squared error versus ideal response across frequency + band. + h -- coefficients + bands -- list of (left, right) tuples relative to 1==Nyquist of + passbands + """ + xp = array_namespace(h) + h_np = _xp_copy_to_numpy(h) + w, H = freqz(h_np, worN=1024) + w, H = map(xp.asarray, (w, H)) + f = w/xp.pi + passIndicator = xp.zeros(w.shape[0], dtype=xp.bool) + for left, right in bands: + passIndicator |= (f >= left) & (f < right) + Hideal = xp.where(passIndicator, xp.ones_like(passIndicator), + xp.zeros_like(passIndicator)) + Hideal = xp.astype(Hideal, H.dtype) + mse = xp.mean(abs(abs(H)-Hideal)**2) + return mse + + @pytest.mark.parametrize( + "cutoff,pass_zero,expected_response", + [ + ([0.5], True, (0, 1)), + ([0.2, .6], False, (.4, 1)), + ([.5], False, (1, 1)), + ] + ) + def test_scaling(self, cutoff, pass_zero, expected_response, xp): + """ + For one lowpass, bandpass, and highpass example filter, this test + checks two things: + - the mean squared error over the frequency domain of the unscaled + filter is smaller than the scaled filter (true for rectangular + window) + - the response of the scaled filter is exactly unity at the center + of the first passband + """ + N = 11 + cutoff = xp.asarray(cutoff) + h = firwin(N, cutoff, scale=False, pass_zero=pass_zero, window='ones') + hs = firwin(N, cutoff, scale=True, pass_zero=pass_zero, window='ones') + if cutoff.shape[0] == 1: + if pass_zero: + cutoff = xp.concat([xp.asarray([0], dtype=cutoff.dtype), cutoff]) + else: + cutoff = xp.concat([cutoff, xp.asarray([1], dtype=cutoff.dtype)]) + msg = 'least squares violation' + assert self.mse(h, [cutoff]) < self.mse(hs, [cutoff]), msg + self.check_response(hs, [expected_response], 1e-12) + + def test_fs_validation(self): + with pytest.raises(ValueError, match="Sampling.*single scalar"): + firwin(51, .5, fs=np.array([10, 20])) + + +@make_xp_test_case(firwin) +class TestFirWinMore: + """Different author, different style, different tests...""" + + def test_lowpass(self, xp): + width = 0.04 + ntaps, beta = kaiserord(120, width) + cutoff = xp.asarray(0.5) + kwargs = dict(cutoff=cutoff, window=('kaiser', beta), scale=False) + taps = firwin(ntaps, **kwargs) + + # Check the symmetry of taps. + assert_array_almost_equal(taps[:ntaps//2], xp.flip(taps)[:ntaps//2]) + + # Check the gain at a few samples where + # we know it should be approximately 0 or 1. + freq_samples = xp.asarray([0.0, 0.25, 0.5-width/2, 0.5+width/2, 0.75, 1.0]) + freq_samples = _xp_copy_to_numpy(freq_samples) + freqs, response = freqz(_xp_copy_to_numpy(taps), worN=np.pi*freq_samples) + + assert_array_almost_equal( + xp.abs(xp.asarray(response)), + xp.asarray([1.0, 1.0, 1.0, 0.0, 0.0, 0.0]), decimal=5 + ) + + taps_str = firwin(ntaps, pass_zero='lowpass', **kwargs) + xp_assert_close(taps, taps_str) + + def test_highpass(self, xp): + width = 0.04 + ntaps, beta = kaiserord(120, width) + + # Ensure that ntaps is odd. + ntaps |= 1 + + cutoff = xp.asarray(0.5) + kwargs = dict(cutoff=cutoff, window=('kaiser', beta), scale=False) + taps = firwin(ntaps, pass_zero=False, **kwargs) + + # Check the symmetry of taps. + assert_array_almost_equal(taps[:ntaps//2], xp.flip(taps)[:ntaps//2]) + + # Check the gain at a few samples where + # we know it should be approximately 0 or 1. + freq_samples = xp.asarray([0.0, 0.25, 0.5 - width/2, 0.5 + width/2, 0.75, 1.0]) + freq_samples = _xp_copy_to_numpy(freq_samples) + freqs, response = freqz(_xp_copy_to_numpy(taps), worN=np.pi*freq_samples) + + assert_array_almost_equal(xp.abs(xp.asarray(response)), + xp.asarray([0.0, 0.0, 0.0, 1.0, 1.0, 1.0]), decimal=5) + + taps_str = firwin(ntaps, pass_zero='highpass', **kwargs) + xp_assert_close(taps, taps_str) + + def test_bandpass(self, xp): + width = 0.04 + ntaps, beta = kaiserord(120, width) + kwargs = dict( + cutoff=xp.asarray([0.3, 0.7]), window=('kaiser', beta), scale=False + ) + taps = firwin(ntaps, pass_zero=False, **kwargs) + + # Check the symmetry of taps. + assert_array_almost_equal(taps[:ntaps//2], xp.flip(taps)[:ntaps//2]) + + # Check the gain at a few samples where + # we know it should be approximately 0 or 1. + freq_samples = xp.asarray([0.0, 0.2, 0.3 - width/2, 0.3 + width/2, 0.5, + 0.7 - width/2, 0.7 + width/2, 0.8, 1.0]) + freq_samples = _xp_copy_to_numpy(freq_samples) + freqs, response = freqz(_xp_copy_to_numpy(taps), worN=np.pi*freq_samples) + + assert_array_almost_equal(xp.abs(xp.asarray(response)), + xp.asarray([0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0]), decimal=5) + + taps_str = firwin(ntaps, pass_zero='bandpass', **kwargs) + xp_assert_close(taps, taps_str) + + def test_bandstop_multi(self, xp): + width = 0.04 + ntaps, beta = kaiserord(120, width) + kwargs = dict(cutoff=xp.asarray([0.2, 0.5, 0.8]), window=('kaiser', beta), + scale=False) + taps = firwin(ntaps, **kwargs) + + # Check the symmetry of taps. + assert_array_almost_equal(taps[:ntaps//2], xp.flip(taps)[:ntaps//2]) + + # Check the gain at a few samples where + # we know it should be approximately 0 or 1. + freq_samples = xp.asarray([0.0, 0.1, 0.2 - width/2, 0.2 + width/2, 0.35, + 0.5 - width/2, 0.5 + width/2, 0.65, + 0.8 - width/2, 0.8 + width/2, 0.9, 1.0]) + freq_samples = _xp_copy_to_numpy(freq_samples) + freqs, response = freqz(_xp_copy_to_numpy(taps), worN=np.pi*freq_samples) + + assert_array_almost_equal( + xp.abs(xp.asarray(response)), + xp.asarray([1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0]), + decimal=5 + ) + + taps_str = firwin(ntaps, pass_zero='bandstop', **kwargs) + xp_assert_close(taps, taps_str) + + def test_fs_nyq(self, xp): + """Test the fs and nyq keywords.""" + nyquist = 1000 + width = 40.0 + relative_width = width/nyquist + ntaps, beta = kaiserord(120, relative_width) + taps = firwin(ntaps, cutoff=xp.asarray([300, 700]), window=('kaiser', beta), + pass_zero=False, scale=False, fs=2*nyquist) + + # Check the symmetry of taps. + assert_array_almost_equal(taps[:ntaps//2], xp.flip(taps)[:ntaps//2]) + + # Check the gain at a few samples where + # we know it should be approximately 0 or 1. + freq_samples = xp.asarray([0.0, 200, 300 - width/2, 300 + width/2, 500, + 700 - width/2, 700 + width/2, 800, 1000]) + freq_samples = _xp_copy_to_numpy(freq_samples) + freqs, response = freqz( + _xp_copy_to_numpy(taps), worN=np.pi*freq_samples/nyquist + ) + + assert_array_almost_equal(xp.abs(xp.asarray(response)), + xp.asarray([0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0]), decimal=5) + + def test_array_cutoff(self, xp): + taps = firwin(3, xp.asarray([.1, .2])) + # smoke test against the value computed by scipy==1.5.2 + xp_assert_close( + taps, xp.asarray([-0.00801395, 1.0160279, -0.00801395]), atol=1e-8 + ) + + def test_bad_cutoff(self): + """Test that invalid cutoff argument raises ValueError.""" + # cutoff values must be greater than 0 and less than 1. + assert_raises(ValueError, firwin, 99, -0.5) + assert_raises(ValueError, firwin, 99, 1.5) + # Don't allow 0 or 1 in cutoff. + assert_raises(ValueError, firwin, 99, [0, 0.5]) + assert_raises(ValueError, firwin, 99, [0.5, 1]) + # cutoff values must be strictly increasing. + assert_raises(ValueError, firwin, 99, [0.1, 0.5, 0.2]) + assert_raises(ValueError, firwin, 99, [0.1, 0.5, 0.5]) + # Must have at least one cutoff value. + assert_raises(ValueError, firwin, 99, []) + # 2D array not allowed. + assert_raises(ValueError, firwin, 99, [[0.1, 0.2],[0.3, 0.4]]) + # cutoff values must be less than nyq. + assert_raises(ValueError, firwin, 99, 50.0, fs=80) + assert_raises(ValueError, firwin, 99, [10, 20, 30], fs=50) + + def test_even_highpass_raises_value_error(self): + """Test that attempt to create a highpass filter with an even number + of taps raises a ValueError exception.""" + assert_raises(ValueError, firwin, 40, 0.5, pass_zero=False) + assert_raises(ValueError, firwin, 40, [.25, 0.5]) + + def test_bad_pass_zero(self): + """Test degenerate pass_zero cases.""" + with assert_raises(ValueError, match="^Parameter pass_zero='foo' not in "): + firwin(41, 0.5, pass_zero='foo') + with assert_raises(ValueError, match="^Parameter pass_zero=1.0 not in "): + firwin(41, 0.5, pass_zero=1.) + for pass_zero in ('lowpass', 'highpass'): + with assert_raises(ValueError, match='cutoff must have one'): + firwin(41, [0.5, 0.6], pass_zero=pass_zero) + for pass_zero in ('bandpass', 'bandstop'): + with assert_raises(ValueError, match='must have at least two'): + firwin(41, [0.5], pass_zero=pass_zero) + + def test_fs_validation(self): + with pytest.raises(ValueError, match="Sampling.*single scalar"): + firwin2(51, .5, 1, fs=np.array([10, 20])) + + +@make_xp_test_case(firwin2) +class TestFirwin2: + + def test_invalid_args(self): + # `freq` and `gain` have different lengths. + with assert_raises(ValueError, match='must be of same length'): + firwin2(50, [0, 0.5, 1], [0.0, 1.0]) + # `nfreqs` is less than `ntaps`. + with assert_raises(ValueError, match='ntaps must be less than nfreqs'): + firwin2(50, [0, 0.5, 1], [0.0, 1.0, 1.0], nfreqs=33) + # Decreasing value in `freq` + with assert_raises(ValueError, match='must be nondecreasing'): + firwin2(50, [0, 0.5, 0.4, 1.0], [0, .25, .5, 1.0]) + # Value in `freq` repeated more than once. + with assert_raises(ValueError, match='must not occur more than twice'): + firwin2(50, [0, .1, .1, .1, 1.0], [0.0, 0.5, 0.75, 1.0, 1.0]) + # `freq` does not start at 0.0. + with assert_raises(ValueError, match='start with 0'): + firwin2(50, [0.5, 1.0], [0.0, 1.0]) + # `freq` does not end at fs/2. + with assert_raises(ValueError, match='end with fs/2'): + firwin2(50, [0.0, 0.5], [0.0, 1.0]) + # Value 0 is repeated in `freq` + with assert_raises(ValueError, match='0 must not be repeated'): + firwin2(50, [0.0, 0.0, 0.5, 1.0], [1.0, 1.0, 0.0, 0.0]) + # Value fs/2 is repeated in `freq` + with assert_raises(ValueError, match='fs/2 must not be repeated'): + firwin2(50, [0.0, 0.5, 1.0, 1.0], [1.0, 1.0, 0.0, 0.0]) + # Value in `freq` that is too close to a repeated number + with assert_raises(ValueError, match='cannot contain numbers ' + 'that are too close'): + firwin2(50, [0.0, 0.5 - np.finfo(float).eps * 0.5, 0.5, 0.5, 1.0], + [1.0, 1.0, 1.0, 0.0, 0.0]) + + # Type II filter, but the gain at nyquist frequency is not zero. + with assert_raises(ValueError, match='Type II filter'): + firwin2(16, [0.0, 0.5, 1.0], [0.0, 1.0, 1.0]) + + # Type III filter, but the gains at nyquist and zero rate are not zero. + with assert_raises(ValueError, match='Type III filter'): + firwin2(17, [0.0, 0.5, 1.0], [0.0, 1.0, 1.0], antisymmetric=True) + with assert_raises(ValueError, match='Type III filter'): + firwin2(17, [0.0, 0.5, 1.0], [1.0, 1.0, 0.0], antisymmetric=True) + with assert_raises(ValueError, match='Type III filter'): + firwin2(17, [0.0, 0.5, 1.0], [1.0, 1.0, 1.0], antisymmetric=True) + + # Type IV filter, but the gain at zero rate is not zero. + with assert_raises(ValueError, match='Type IV filter'): + firwin2(16, [0.0, 0.5, 1.0], [1.0, 1.0, 0.0], antisymmetric=True) + + def test01(self, xp): + width = 0.04 + beta = 12.0 + ntaps = 400 + # Filter is 1 from w=0 to w=0.5, then decreases linearly from 1 to 0 as w + # increases from w=0.5 to w=1 (w=1 is the Nyquist frequency). + freq = xp.asarray([0.0, 0.5, 1.0]) + gain = xp.asarray([1.0, 1.0, 0.0]) + taps = firwin2(ntaps, freq, gain, window=('kaiser', beta)) + freq_samples = xp.asarray([0.0, 0.25, 0.5 - width/2, 0.5 + width/2, + 0.75, 1.0 - width/2]) + freq_samples = _xp_copy_to_numpy(freq_samples) + freqs, response = freqz(_xp_copy_to_numpy(taps), worN=np.pi*freq_samples) + assert_array_almost_equal( + xp.abs(xp.asarray(response)), + xp.asarray([1.0, 1.0, 1.0, 1.0 - width, 0.5, width]), decimal=5 + ) + + @skip_xp_backends("jax.numpy", reason="immutable arrays") + def test02(self, xp): + width = 0.04 + beta = 12.0 + # ntaps must be odd for positive gain at Nyquist. + ntaps = 401 + # An ideal highpass filter. + freq = xp.asarray([0.0, 0.5, 0.5, 1.0]) + gain = xp.asarray([0.0, 0.0, 1.0, 1.0]) + taps = firwin2(ntaps, freq, gain, window=('kaiser', beta)) + freq_samples = xp.asarray([0.0, 0.25, 0.5 - width, 0.5 + width, 0.75, 1.0]) + freq_samples = _xp_copy_to_numpy(freq_samples) + freqs, response = freqz(_xp_copy_to_numpy(taps), worN=np.pi*freq_samples) + assert_array_almost_equal( + xp.abs(xp.asarray(response)), + xp.asarray([0.0, 0.0, 0.0, 1.0, 1.0, 1.0]), decimal=5 + ) + + @skip_xp_backends("jax.numpy", reason="immutable arrays") + def test03(self, xp): + width = 0.02 + ntaps, beta = kaiserord(120, width) + # ntaps must be odd for positive gain at Nyquist. + ntaps = int(ntaps) | 1 + freq = xp.asarray([0.0, 0.4, 0.4, 0.5, 0.5, 1.0]) + gain = xp.asarray([1.0, 1.0, 0.0, 0.0, 1.0, 1.0]) + taps = firwin2(ntaps, freq, gain, window=('kaiser', beta)) + freq_samples = np.array([0.0, 0.4 - width, 0.4 + width, 0.45, + 0.5 - width, 0.5 + width, 0.75, 1.0]) + freqs, response = freqz(_xp_copy_to_numpy(taps), worN=np.pi*freq_samples) + assert_array_almost_equal( + xp.abs(xp.asarray(response)), + xp.asarray([1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0]), decimal=5 + ) + + @skip_xp_backends("jax.numpy", reason="immutable arrays") + def test04(self, xp): + """Test firwin2 when window=None.""" + ntaps = 5 + # Ideal lowpass: gain is 1 on [0,0.5], and 0 on [0.5, 1.0] + freq = xp.asarray([0.0, 0.5, 0.5, 1.0]) + gain = xp.asarray([1.0, 1.0, 0.0, 0.0]) + + taps = firwin2(ntaps, freq, gain, window=None, nfreqs=8193) + alpha = 0.5 * (ntaps - 1) + m = xp.arange(0, ntaps, dtype=freq.dtype) - alpha + h = 0.5 * xpx.sinc(0.5 * m) + assert_array_almost_equal(h, taps) + + def test05(self, xp): + """Test firwin2 for calculating Type IV filters""" + ntaps = 1500 + + freq = xp.asarray([0.0, 1.0]) + gain = xp.asarray([0.0, 1.0]) + taps = firwin2(ntaps, freq, gain, window=None, antisymmetric=True) + + flip = array_namespace(freq).flip + dec = {'decimal': 4.5} if xp_default_dtype(xp) == xp.float32 else {} + assert_array_almost_equal(taps[: ntaps // 2], flip(-taps[ntaps // 2:]), **dec) + + freqs, response = freqz(_xp_copy_to_numpy(taps), worN=2048) + freqs, response = map(xp.asarray, (freqs, response)) + assert_array_almost_equal(xp.abs(response), freqs / xp.pi, decimal=4) + + @skip_xp_backends("jax.numpy", reason="immutable arrays") + def test06(self, xp): + """Test firwin2 for calculating Type III filters""" + ntaps = 1501 + + freq = xp.asarray([0.0, 0.5, 0.55, 1.0]) + gain = xp.asarray([0.0, 0.5, 0.0, 0.0]) + taps = firwin2(ntaps, freq, gain, window=None, antisymmetric=True) + assert taps[ntaps // 2] == 0.0 + + flip = array_namespace(freq).flip + dec = {'decimal': 4.5} if xp_default_dtype(xp) == xp.float32 else {} + assert_array_almost_equal(taps[: ntaps // 2], + flip(-taps[ntaps // 2 + 1:]), **dec + ) + + freqs, response1 = freqz(_xp_copy_to_numpy(taps), worN=2048) + response2 = xp.asarray( + np.interp(freqs / np.pi, _xp_copy_to_numpy(freq), _xp_copy_to_numpy(gain)) + ) + assert_array_almost_equal(xp.abs(xp.asarray(response1)), response2, decimal=3) + + def test_fs_nyq(self, xp): + taps1 = firwin2(80, xp.asarray([0.0, 0.5, 1.0]), xp.asarray([1.0, 1.0, 0.0])) + taps2 = firwin2(80, xp.asarray([0.0, 30.0, 60.0]), xp.asarray([1.0, 1.0, 0.0]), + fs=120.0) + assert_array_almost_equal(taps1, taps2) + + def test_tuple(self): + taps1 = firwin2(150, (0.0, 0.5, 0.5, 1.0), (1.0, 1.0, 0.0, 0.0)) + taps2 = firwin2(150, [0.0, 0.5, 0.5, 1.0], [1.0, 1.0, 0.0, 0.0]) + assert_array_almost_equal(taps1, taps2) + + @skip_xp_backends("jax.numpy", reason="immutable arrays") + def test_input_modyfication(self, xp): + freq1 = xp.asarray([0.0, 0.5, 0.5, 1.0]) + freq2 = xp.asarray(freq1) + firwin2(80, freq1, xp.asarray([1.0, 1.0, 0.0, 0.0])) + xp_assert_equal(freq1, freq2) + + +@make_xp_test_case(remez) +class TestRemez: + + def test_bad_args(self): + assert_raises(ValueError, remez, 11, [0.1, 0.4], [1], type='pooka') + + def test_hilbert(self): + N = 11 # number of taps in the filter + a = 0.1 # width of the transition band + + # design a unity gain hilbert bandpass filter from w to 0.5-w + h = remez(11, [a, 0.5-a], [1], type='hilbert') + + # make sure the filter has correct # of taps + assert len(h) == N, "Number of Taps" + + # make sure it is type III (anti-symmetric tap coefficients) + assert_array_almost_equal(h[:(N-1)//2], -h[:-(N-1)//2-1:-1]) + + # Since the requested response is symmetric, all even coefficients + # should be zero (or in this case really small) + assert (abs(h[1::2]) < 1e-15).all(), "Even Coefficients Equal Zero" + + # now check the frequency response + w, H = freqz(h, 1) + f = w/2/np.pi + Hmag = abs(H) + + # should have a zero at 0 and pi (in this case close to zero) + assert (Hmag[[0, -1]] < 0.02).all(), "Zero at zero and pi" + + # check that the pass band is close to unity + idx = np.logical_and(f > a, f < 0.5-a) + assert (abs(Hmag[idx] - 1) < 0.015).all(), "Pass Band Close To Unity" + + def test_compare(self, xp): + # test comparison to MATLAB + k = [0.024590270518440, -0.041314581814658, -0.075943803756711, + -0.003530911231040, 0.193140296954975, 0.373400753484939, + 0.373400753484939, 0.193140296954975, -0.003530911231040, + -0.075943803756711, -0.041314581814658, 0.024590270518440] + h = remez(12, xp.asarray([0, 0.3, 0.5, 1]), xp.asarray([1, 0]), fs=2.) + atol_arg = {'atol': 1e-8} if xp_default_dtype(xp) == xp.float32 else {} + xp_assert_close(h, xp.asarray(k, dtype=xp.float64), **atol_arg) + + h = [-0.038976016082299, 0.018704846485491, -0.014644062687875, + 0.002879152556419, 0.016849978528150, -0.043276706138248, + 0.073641298245579, -0.103908158578635, 0.129770906801075, + -0.147163447297124, 0.153302248456347, -0.147163447297124, + 0.129770906801075, -0.103908158578635, 0.073641298245579, + -0.043276706138248, 0.016849978528150, 0.002879152556419, + -0.014644062687875, 0.018704846485491, -0.038976016082299] + atol_arg = {'atol': 3e-8} if xp_default_dtype(xp) == xp.float32 else {} + xp_assert_close( + remez(21, xp.asarray([0, 0.8, 0.9, 1]), xp.asarray([0, 1]), fs=2.), + xp.asarray(h, dtype=xp.float64), **atol_arg + ) + + def test_fs_validation(self): + with pytest.raises(ValueError, match="Sampling.*single scalar"): + remez(11, .1, 1, fs=np.array([10, 20])) + + def test_gh_23266(self, xp): + bands = xp.asarray([0.0, 0.2, 0.3, 0.5]) + desired = xp.asarray([1.0, 0.0]) + weight = xp.asarray([1.0, 2.0]) + remez(21, bands, desired, weight=weight) + + +@make_xp_test_case(firls) +class TestFirls: + + def test_bad_args(self): + # even numtaps + assert_raises(ValueError, firls, 10, [0.1, 0.2], [0, 0]) + # odd bands + assert_raises(ValueError, firls, 11, [0.1, 0.2, 0.4], [0, 0, 0]) + # len(bands) != len(desired) + assert_raises(ValueError, firls, 11, [0.1, 0.2, 0.3, 0.4], [0, 0, 0]) + # non-monotonic bands + assert_raises(ValueError, firls, 11, [0.2, 0.1], [0, 0]) + assert_raises(ValueError, firls, 11, [0.1, 0.2, 0.3, 0.3], [0] * 4) + assert_raises(ValueError, firls, 11, [0.3, 0.4, 0.1, 0.2], [0] * 4) + assert_raises(ValueError, firls, 11, [0.1, 0.3, 0.2, 0.4], [0] * 4) + # negative desired + assert_raises(ValueError, firls, 11, [0.1, 0.2], [-1, 1]) + # len(weight) != len(pairs) + assert_raises(ValueError, firls, 11, [0.1, 0.2], [0, 0], weight=[1, 2]) + # negative weight + assert_raises(ValueError, firls, 11, [0.1, 0.2], [0, 0], weight=[-1]) + + @skip_xp_backends("dask.array", reason="dask fancy indexing shape=(nan,)") + def test_firls(self, xp): + N = 11 # number of taps in the filter + a = 0.1 # width of the transition band + + # design a halfband symmetric low-pass filter + h = firls(11, xp.asarray([0, a, 0.5 - a, 0.5]), xp.asarray([1, 1, 0, 0]), + fs=1.0) + + # make sure the filter has correct # of taps + assert h.shape[0] == N + + # make sure it is symmetric + midx = (N-1) // 2 + flip = array_namespace(h).flip + assert_array_almost_equal(h[:midx], flip(h[midx+1:])) # h[:-midx-1:-1]) + + # make sure the center tap is 0.5 + assert math.isclose(h[midx], 0.5, abs_tol=1e-8) + + # For halfband symmetric, odd coefficients (except the center) + # should be zero (really small) + hodd = xp.stack((h[1:midx:2], h[-midx+1::2])) + assert_array_almost_equal(hodd, xp.zeros_like(hodd)) + + # now check the frequency response + w, H = freqz(_xp_copy_to_numpy(h), 1) + w, H = xp.asarray(w), xp.asarray(H) + f = w/2/xp.pi + Hmag = xp.abs(H) + + # check that the pass band is close to unity + idx = xp.logical_and(f > 0, f < a) + assert_array_almost_equal(Hmag[idx], xp.ones_like(Hmag[idx]), decimal=3) + + # check that the stop band is close to zero + idx = xp.logical_and(f > 0.5 - a, f < 0.5) + assert_array_almost_equal(Hmag[idx], xp.zeros_like(Hmag[idx]), decimal=3) + + def test_compare(self, xp): + # compare to OCTAVE output + taps = firls(9, xp.asarray([0, 0.5, 0.55, 1]), + xp.asarray([1, 1, 0, 0]), weight=xp.asarray([1, 2])) + # >> taps = firls(8, [0 0.5 0.55 1], [1 1 0 0], [1, 2]); + known_taps = [-6.26930101730182e-04, -1.03354450635036e-01, + -9.81576747564301e-03, 3.17271686090449e-01, + 5.11409425599933e-01, 3.17271686090449e-01, + -9.81576747564301e-03, -1.03354450635036e-01, + -6.26930101730182e-04] + atol_arg = {'atol': 5e-8} if xp_default_dtype(xp) == xp.float32 else {} + known_taps = xp.asarray(known_taps, dtype=xp.float64) + xp_assert_close(taps, known_taps, **atol_arg) + + # compare to MATLAB output + taps = firls(11, xp.asarray([0, 0.5, 0.5, 1]), + xp.asarray([1, 1, 0, 0]), weight=xp.asarray([1, 2])) + # >> taps = firls(10, [0 0.5 0.5 1], [1 1 0 0], [1, 2]); + known_taps = [ + 0.058545300496815, -0.014233383714318, -0.104688258464392, + 0.012403323025279, 0.317930861136062, 0.488047220029700, + 0.317930861136062, 0.012403323025279, -0.104688258464392, + -0.014233383714318, 0.058545300496815] + known_taps = xp.asarray(known_taps, dtype=xp.float64) + atol_arg = {'atol': 3e-8} if xp_default_dtype(xp) == xp.float32 else {} + xp_assert_close(taps, known_taps, **atol_arg) + + # With linear changes: + taps = firls(7, xp.asarray((0, 1, 2, 3, 4, 5)), + xp.asarray([1, 0, 0, 1, 1, 0]), fs=20) + # >> taps = firls(6, [0, 0.1, 0.2, 0.3, 0.4, 0.5], [1, 0, 0, 1, 1, 0]) + known_taps = [ + 1.156090832768218, -4.1385894727395849, 7.5288619164321826, + -8.5530572592947856, 7.5288619164321826, -4.1385894727395849, + 1.156090832768218] + known_taps = xp.asarray(known_taps, dtype=xp.float64) + xp_assert_close(taps, known_taps) + + def test_rank_deficient(self, xp): + # solve() runs but warns (only sometimes, so here we don't use match) + x = firls(21, xp.asarray([0, 0.1, 0.9, 1]), xp.asarray([1, 1, 0, 0])) + w, h = freqz(_xp_copy_to_numpy(x), fs=2.) + w, h = map(xp.asarray, (w, h)) + absh2 = xp.abs(h[:2]) + xp_assert_close(absh2, xp.ones_like(absh2), atol=1e-5) + absh2 = xp.abs(h[-2:]) + xp_assert_close(absh2, xp.zeros_like(absh2), atol=1e-6, rtol=1e-7) + # switch to pinvh (tolerances could be higher with longer + # filters, but using shorter ones is faster computationally and + # the idea is the same) + x = firls(101, xp.asarray([0, 0.01, 0.99, 1]), xp.asarray([1, 1, 0, 0])) + w, h = freqz(_xp_copy_to_numpy(x), fs=2.) + w, h = map(xp.asarray, (w, h)) + mask = xp.asarray(w < 0.01) + h = xp.asarray(h) + assert xp.sum(xp.astype(mask, xp.int64)) > 3 + habs = xp.abs(h[mask]) + xp_assert_close(habs, xp.ones_like(habs), atol=1e-4) + mask = xp.asarray(w > 0.99) + assert xp.sum(xp.astype(mask, xp.int64)) > 3 + habs = xp.abs(h[mask]) + xp_assert_close(habs, xp.zeros_like(habs), atol=1e-4) + + def test_fs_validation(self): + with pytest.raises(ValueError, match="Sampling.*single scalar"): + firls(11, .1, 1, fs=np.array([10, 20])) + +@make_xp_test_case(minimum_phase) +class TestMinimumPhase: + + def test_bad_args(self): + # not enough taps + assert_raises(ValueError, minimum_phase, [1.]) + assert_raises(ValueError, minimum_phase, [1., 1.]) + assert_raises(ValueError, minimum_phase, np.full(10, 1j)) + assert_raises((ValueError, TypeError), minimum_phase, 'foo') + assert_raises(ValueError, minimum_phase, np.ones(10), n_fft=8) + assert_raises(ValueError, minimum_phase, np.ones(10), method='foo') + assert_warns(RuntimeWarning, minimum_phase, np.arange(3)) + with pytest.raises(ValueError, match="is only supported when"): + minimum_phase(np.ones(3), method='hilbert', half=False) + + def test_homomorphic(self): + # check that it can recover frequency responses of arbitrary + # linear-phase filters + + # for some cases we can get the actual filter back + h = [1, -1] + h_new = minimum_phase(np.convolve(h, h[::-1])) + xp_assert_close(h_new, np.asarray(h, dtype=np.float64), rtol=0.05) + + # but in general we only guarantee we get the magnitude back + rng = np.random.RandomState(0) + for n in (2, 3, 10, 11, 15, 16, 17, 20, 21, 100, 101): + h = rng.randn(n) + h_linear = np.convolve(h, h[::-1]) + h_new = minimum_phase(h_linear) + xp_assert_close(np.abs(fft(h_new)), np.abs(fft(h)), rtol=1e-4) + h_new = minimum_phase(h_linear, half=False) + assert len(h_linear) == len(h_new) + xp_assert_close(np.abs(fft(h_new)), np.abs(fft(h_linear)), rtol=1e-4) + + @skip_xp_backends("dask.array", reason="too slow") + @make_xp_test_case(signal.hilbert) + def test_hilbert(self, xp): + # compare to MATLAB output of reference implementation + + # f=[0 0.3 0.5 1]; + # a=[1 1 0 0]; + # h=remez(11,f,a); + h = remez(12, [0, 0.3, 0.5, 1], [1, 0], fs=2.) + k = [0.349585548646686, 0.373552164395447, 0.326082685363438, + 0.077152207480935, -0.129943946349364, -0.059355880509749] + h = xp.asarray(h) + k = xp.asarray(k, dtype=xp.float64) + m = minimum_phase(h, 'hilbert') + xp_assert_close(m, k, rtol=5e-3) + + # f=[0 0.8 0.9 1]; + # a=[0 0 1 1]; + # h=remez(20,f,a); + h = remez(21, [0, 0.8, 0.9, 1], [0, 1], fs=2.) + k = [0.232486803906329, -0.133551833687071, 0.151871456867244, + -0.157957283165866, 0.151739294892963, -0.129293146705090, + 0.100787844523204, -0.065832656741252, 0.035361328741024, + -0.014977068692269, -0.158416139047557] + h = xp.asarray(h) + k = xp.asarray(k, dtype=xp.float64) + m = minimum_phase(h, 'hilbert', n_fft=2**19) + xp_assert_close(m, k, rtol=2e-3) + + @xfail_xp_backends("cupy", reason="cupy/cupy#9795") + @pytest.mark.parametrize("N", (963, 964)) + @pytest.mark.parametrize("dtype", ("float32", "float64")) + def test_nyquist(self, N, dtype, xp): + fc = xp.asarray(10) + fs = 100 + h = firwin(N, fc, window="hann", pass_zero="lowpass", scale=False, fs=fs) + xp_dtype = getattr(xp, dtype) + h = xp.astype(h, xp_dtype) + h_min_sig = minimum_phase(h, method="homomorphic", n_fft=N, half=False) + H_mag = xp.abs(rfft(h, N)) + H_min_mag = xp.abs(rfft(h_min_sig, N)) + error = H_mag - H_min_mag + atol = dict(float32=1e-5, float64=1e-13)[dtype] + xp_assert_close(error, xp.zeros_like(error), atol=atol) + + +class Testfirwin_2d: + def test_invalid_args(self): + with pytest.raises(ValueError, + match="hsize must be a 2-element tuple or list"): + firwin_2d((50,), window=(("kaiser", 5.0), "boxcar"), fc=0.4) + + with pytest.raises(ValueError, + match="window must be a 2-element tuple or list"): + firwin_2d((51, 51), window=("hamming",), fc=0.5) + + with pytest.raises(ValueError, + match="window must be a 2-element tuple or list"): + firwin_2d((51, 51), window="invalid_window", fc=0.5) + + def test_filter_design(self): + hsize = (51, 51) + window = (("kaiser", 8.0), ("kaiser", 8.0)) + fc = 0.4 + taps_kaiser = firwin_2d(hsize, window, fc=fc) + assert taps_kaiser.shape == (51, 51) + + window = ("hamming", "hamming") + taps_hamming = firwin_2d(hsize, window, fc=fc) + assert taps_hamming.shape == (51, 51) + + def test_impulse_response(self): + hsize = (31, 31) + window = ("hamming", "hamming") + fc = 0.4 + taps = firwin_2d(hsize, window, fc=fc) + + impulse = np.zeros((63, 63)) + impulse[31, 31] = 1 + + response = convolve2d(impulse, taps, mode='same') + + expected_response = taps + xp_assert_close(response[16:47, 16:47], expected_response, rtol=1e-5) + + def test_frequency_response(self): + """Compare 1d and 2d frequency response. """ + hsize = (31, 31) + windows = ("hamming", "hamming") + fc = 0.4 + taps_1d = firwin(numtaps=hsize[0], cutoff=fc, window=windows[0]) + taps_2d = firwin_2d(hsize, windows, fc=fc) + + f_resp_1d = fft(taps_1d) + f_resp_2d = fft2(taps_2d) + + xp_assert_close(f_resp_2d[0, :], f_resp_1d, + err_msg='DC Gain at (0, f1) is not unity!') + xp_assert_close(f_resp_2d[:, 0], f_resp_1d, + err_msg='DC Gain at (f0, 0) is not unity!') + xp_assert_close(f_resp_2d, np.outer(f_resp_1d, f_resp_1d), + atol=np.finfo(f_resp_2d.dtype).resolution, + err_msg='2d frequency response is not product of 1d responses') + + def test_symmetry(self): + hsize = (51, 51) + window = ("hamming", "hamming") + fc = 0.4 + taps = firwin_2d(hsize, window, fc=fc) + xp_assert_close(taps, np.flip(taps), rtol=1e-5) + + def test_circular_symmetry(self): + hsize = (51, 51) + window = "hamming" + taps = firwin_2d(hsize, window, circular=True, fc=0.5) + center = hsize[0] // 2 + for i in range(hsize[0]): + for j in range(hsize[1]): + xp_assert_close(taps[i, j], + taps[center - (i - center), center - (j - center)], + rtol=1e-5) + + def test_edge_case_circular(self): + hsize = (3, 3) + window = "hamming" + taps_small = firwin_2d(hsize, window, circular=True, fc=0.5) + assert taps_small.shape == (3, 3) + + hsize = (101, 101) + taps_large = firwin_2d(hsize, window, circular=True, fc=0.5) + assert taps_large.shape == (101, 101) + + def test_known_result(self): + hsize = (5, 5) + window = ('kaiser', 8.0) + fc = 0.1 + fs = 2 + + row_filter = firwin(hsize[0], cutoff=fc, window=window, fs=fs) + col_filter = firwin(hsize[1], cutoff=fc, window=window, fs=fs) + known_result = np.outer(row_filter, col_filter) + + taps = firwin_2d(hsize, (window, window), fc=fc) + assert taps.shape == known_result.shape, ( + f"Shape mismatch: {taps.shape} vs {known_result.shape}" + ) + assert np.allclose(taps, known_result, rtol=1e-1), ( + f"Filter shape mismatch: {taps} vs {known_result}" + ) diff --git a/venv/lib/python3.12/site-packages/scipy/signal/tests/test_ltisys.py b/venv/lib/python3.12/site-packages/scipy/signal/tests/test_ltisys.py new file mode 100644 index 0000000000000000000000000000000000000000..1fbf5217f09d03fa42e779be0ec79bdb4ae38459 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/tests/test_ltisys.py @@ -0,0 +1,1297 @@ +import warnings +from types import GenericAlias + +import numpy as np +import pytest +from pytest import raises as assert_raises +from scipy._lib._array_api import( + assert_almost_equal, xp_assert_equal, xp_assert_close, make_xp_test_case, + xp_result_type +) + +from scipy.signal import (ss2tf, tf2ss, lti, + dlti, bode, freqresp, lsim, impulse, step, + abcd_normalize, place_poles, + TransferFunction, StateSpace, ZerosPolesGain) + +from scipy.signal._filter_design import BadCoefficients +import scipy.linalg as linalg + + +def _assert_poles_close(P1,P2, rtol=1e-8, atol=1e-8): + """ + Check each pole in P1 is close to a pole in P2 with a 1e-8 + relative tolerance or 1e-8 absolute tolerance (useful for zero poles). + These tolerances are very strict but the systems tested are known to + accept these poles so we should not be far from what is requested. + """ + P2 = P2.copy() + for p1 in P1: + found = False + for p2_idx in range(P2.shape[0]): + if np.allclose([np.real(p1), np.imag(p1)], + [np.real(P2[p2_idx]), np.imag(P2[p2_idx])], + rtol, atol): + found = True + np.delete(P2, p2_idx) + break + if not found: + raise ValueError("Can't find pole " + str(p1) + " in " + str(P2)) + + +class TestPlacePoles: + + def _check(self, A, B, P, **kwargs): + """ + Perform the most common tests on the poles computed by place_poles + and return the Bunch object for further specific tests + """ + fsf = place_poles(A, B, P, **kwargs) + expected, _ = np.linalg.eig(A - np.dot(B, fsf.gain_matrix)) + _assert_poles_close(expected, fsf.requested_poles) + _assert_poles_close(expected, fsf.computed_poles) + _assert_poles_close(P,fsf.requested_poles) + return fsf + + def test_real(self): + # Test real pole placement using KNV and YT0 algorithm and example 1 in + # section 4 of the reference publication (see place_poles docstring) + A = np.array([1.380, -0.2077, 6.715, -5.676, -0.5814, -4.290, 0, + 0.6750, 1.067, 4.273, -6.654, 5.893, 0.0480, 4.273, + 1.343, -2.104]).reshape(4, 4) + B = np.array([0, 5.679, 1.136, 1.136, 0, 0, -3.146,0]).reshape(4, 2) + P = np.array([-0.2, -0.5, -5.0566, -8.6659]) + + # Check that both KNV and YT compute correct K matrix + self._check(A, B, P, method='KNV0') + self._check(A, B, P, method='YT') + + # Try to reach the specific case in _YT_real where two singular + # values are almost equal. This is to improve code coverage but I + # have no way to be sure this code is really reached + + # on some architectures this can lead to a RuntimeWarning invalid + # value in divide (see gh-7590), so suppress it for now + with np.errstate(invalid='ignore'): + self._check(A, B, (2,2,3,3)) + + def test_complex(self): + # Test complex pole placement on a linearized car model, taken from L. + # Jaulin, Automatique pour la robotique, Cours et Exercices, iSTE + # editions p 184/185 + A = np.array([[0, 7, 0, 0], + [0, 0, 0, 7/3.], + [0, 0, 0, 0], + [0, 0, 0, 0]]) + B = np.array([[0, 0], + [0, 0], + [1, 0], + [0, 1]]) + # Test complex poles on YT + P = np.array([-3, -1, -2-1j, -2+1j]) + # on macOS arm64 this can lead to a RuntimeWarning invalid + # value in divide, so suppress it for now + with np.errstate(divide='ignore', invalid='ignore'): + self._check(A, B, P) + + # Try to reach the specific case in _YT_complex where two singular + # values are almost equal. This is to improve code coverage but I + # have no way to be sure this code is really reached + + P = [0-1e-6j,0+1e-6j,-10,10] + with np.errstate(divide='ignore', invalid='ignore'): + self._check(A, B, P, maxiter=1000) + + # Try to reach the specific case in _YT_complex where the rank two + # update yields two null vectors. This test was found via Monte Carlo. + + A = np.array( + [-2148,-2902, -2267, -598, -1722, -1829, -165, -283, -2546, + -167, -754, -2285, -543, -1700, -584, -2978, -925, -1300, + -1583, -984, -386, -2650, -764, -897, -517, -1598, 2, -1709, + -291, -338, -153, -1804, -1106, -1168, -867, -2297] + ).reshape(6,6) + + B = np.array( + [-108, -374, -524, -1285, -1232, -161, -1204, -672, -637, + -15, -483, -23, -931, -780, -1245, -1129, -1290, -1502, + -952, -1374, -62, -964, -930, -939, -792, -756, -1437, + -491, -1543, -686] + ).reshape(6,5) + P = [-25.-29.j, -25.+29.j, 31.-42.j, 31.+42.j, 33.-41.j, 33.+41.j] + self._check(A, B, P) + + # Use a lot of poles to go through all cases for update_order + # in _YT_loop + + big_A = np.ones((11,11))-np.eye(11) + big_B = np.ones((11,10))-np.diag([1]*10,1)[:,1:] + big_A[:6,:6] = A + big_B[:6,:5] = B + + P = [-10,-20,-30,40,50,60,70,-20-5j,-20+5j,5+3j,5-3j] + with np.errstate(divide='ignore', invalid='ignore'): + self._check(big_A, big_B, P) + + #check with only complex poles and only real poles + P = [-10,-20,-30,-40,-50,-60,-70,-80,-90,-100] + self._check(big_A[:-1,:-1], big_B[:-1,:-1], P) + P = [-10+10j,-20+20j,-30+30j,-40+40j,-50+50j, + -10-10j,-20-20j,-30-30j,-40-40j,-50-50j] + self._check(big_A[:-1,:-1], big_B[:-1,:-1], P) + + # need a 5x5 array to ensure YT handles properly when there + # is only one real pole and several complex + A = np.array([0,7,0,0,0,0,0,7/3.,0,0,0,0,0,0,0,0, + 0,0,0,5,0,0,0,0,9]).reshape(5,5) + B = np.array([0,0,0,0,1,0,0,1,2,3]).reshape(5,2) + P = np.array([-2, -3+1j, -3-1j, -1+1j, -1-1j]) + with np.errstate(divide='ignore', invalid='ignore'): + place_poles(A, B, P) + + # same test with an odd number of real poles > 1 + # this is another specific case of YT + P = np.array([-2, -3, -4, -1+1j, -1-1j]) + with np.errstate(divide='ignore', invalid='ignore'): + self._check(A, B, P) + + def test_tricky_B(self): + # check we handle as we should the 1 column B matrices and + # n column B matrices (with n such as shape(A)=(n, n)) + A = np.array([1.380, -0.2077, 6.715, -5.676, -0.5814, -4.290, 0, + 0.6750, 1.067, 4.273, -6.654, 5.893, 0.0480, 4.273, + 1.343, -2.104]).reshape(4, 4) + B = np.array([0, 5.679, 1.136, 1.136, 0, 0, -3.146, 0, 1, 2, 3, 4, + 5, 6, 7, 8]).reshape(4, 4) + + # KNV or YT are not called here, it's a specific case with only + # one unique solution + P = np.array([-0.2, -0.5, -5.0566, -8.6659]) + fsf = self._check(A, B, P) + # rtol and nb_iter should be set to np.nan as the identity can be + # used as transfer matrix + assert np.isnan(fsf.rtol) + assert np.isnan(fsf.nb_iter) + + # check with complex poles too as they trigger a specific case in + # the specific case :-) + P = np.array((-2+1j,-2-1j,-3,-2)) + fsf = self._check(A, B, P) + assert np.isnan(fsf.rtol) + assert np.isnan(fsf.nb_iter) + + #now test with a B matrix with only one column (no optimisation) + B = B[:,0].reshape(4,1) + P = np.array((-2+1j,-2-1j,-3,-2)) + fsf = self._check(A, B, P) + + # we can't optimize anything, check they are set to 0 as expected + assert fsf.rtol == 0 + assert fsf.nb_iter == 0 + + def test_errors(self): + # Test input mistakes from user + A = np.array([0,7,0,0,0,0,0,7/3.,0,0,0,0,0,0,0,0]).reshape(4,4) + B = np.array([0,0,0,0,1,0,0,1]).reshape(4,2) + + #should fail as the method keyword is invalid + assert_raises(ValueError, place_poles, A, B, (-2.1,-2.2,-2.3,-2.4), + method="foo") + + #should fail as poles are not 1D array + assert_raises(ValueError, place_poles, A, B, + np.array((-2.1,-2.2,-2.3,-2.4)).reshape(4,1)) + + #should fail as A is not a 2D array + assert_raises(ValueError, place_poles, A[:,:,np.newaxis], B, + (-2.1,-2.2,-2.3,-2.4)) + + #should fail as B is not a 2D array + assert_raises(ValueError, place_poles, A, B[:,:,np.newaxis], + (-2.1,-2.2,-2.3,-2.4)) + + #should fail as there are too many poles + assert_raises(ValueError, place_poles, A, B, (-2.1,-2.2,-2.3,-2.4,-3)) + + #should fail as there are not enough poles + assert_raises(ValueError, place_poles, A, B, (-2.1,-2.2,-2.3)) + + #should fail as the rtol is greater than 1 + assert_raises(ValueError, place_poles, A, B, (-2.1,-2.2,-2.3,-2.4), + rtol=42) + + #should fail as maxiter is smaller than 1 + assert_raises(ValueError, place_poles, A, B, (-2.1,-2.2,-2.3,-2.4), + maxiter=-42) + + # should fail as ndim(B) is two + assert_raises(ValueError, place_poles, A, B, (-2,-2,-2,-2)) + + # uncontrollable system + assert_raises(ValueError, place_poles, np.ones((4,4)), + np.ones((4,2)), (1,2,3,4)) + + # Should not raise ValueError as the poles can be placed but should + # raise a warning as the convergence is not reached + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + fsf = place_poles(A, B, (-1,-2,-3,-4), rtol=1e-16, maxiter=42) + assert len(w) == 1 + assert issubclass(w[-1].category, UserWarning) + assert ("Convergence was not reached after maxiter iterations" + in str(w[-1].message)) + assert fsf.nb_iter == 42 + + # should fail as a complex misses its conjugate + assert_raises(ValueError, place_poles, A, B, (-2+1j,-2-1j,-2+3j,-2)) + + # should fail as A is not square + assert_raises(ValueError, place_poles, A[:,:3], B, (-2,-3,-4,-5)) + + # should fail as B has not the same number of lines as A + assert_raises(ValueError, place_poles, A, B[:3,:], (-2,-3,-4,-5)) + + # should fail as KNV0 does not support complex poles + assert_raises(ValueError, place_poles, A, B, + (-2+1j,-2-1j,-2+3j,-2-3j), method="KNV0") + + +class TestSS2TF: + + def check_matrix_shapes(self, p, q, r): + ss2tf(np.zeros((p, p)), + np.zeros((p, q)), + np.zeros((r, p)), + np.zeros((r, q)), 0) + + def test_shapes(self): + # Each tuple holds: + # number of states, number of inputs, number of outputs + for p, q, r in [(3, 3, 3), (1, 3, 3), (1, 1, 1)]: + self.check_matrix_shapes(p, q, r) + + def test_basic(self): + # Test a round trip through tf2ss and ss2tf. + b = np.array([1.0, 3.0, 5.0]) + a = np.array([1.0, 2.0, 3.0]) + + A, B, C, D = tf2ss(b, a) + xp_assert_close(A, [[-2., -3], [1, 0]], rtol=1e-13) + xp_assert_close(B, [[1.], [0]], rtol=1e-13) + xp_assert_close(C, [[1., 2]], rtol=1e-13) + xp_assert_close(D, [[1.]], rtol=1e-14) + + bb, aa = ss2tf(A, B, C, D) + xp_assert_close(bb[0], b, rtol=1e-13) + xp_assert_close(aa, a, rtol=1e-13) + + def test_zero_order_round_trip(self): + # See gh-5760 + tf = (2, 1) + A, B, C, D = tf2ss(*tf) + xp_assert_close(A, [[0.]], rtol=1e-13) + xp_assert_close(B, [[0.]], rtol=1e-13) + xp_assert_close(C, [[0.]], rtol=1e-13) + xp_assert_close(D, [[2.]], rtol=1e-13) + + num, den = ss2tf(A, B, C, D) + xp_assert_close(num, [[2., 0]], rtol=1e-13) + xp_assert_close(den, [1., 0], rtol=1e-13) + + tf = ([[5], [2]], 1) + A, B, C, D = tf2ss(*tf) + xp_assert_close(A, [[0.]], rtol=1e-13) + xp_assert_close(B, [[0.]], rtol=1e-13) + xp_assert_close(C, [[0.], [0]], rtol=1e-13) + xp_assert_close(D, [[5.], [2]], rtol=1e-13) + + num, den = ss2tf(A, B, C, D) + xp_assert_close(num, [[5., 0], [2, 0]], rtol=1e-13) + xp_assert_close(den, [1., 0], rtol=1e-13) + + def test_simo_round_trip(self): + # See gh-5753 + tf = ([[1, 2], [1, 1]], [1, 2]) + A, B, C, D = tf2ss(*tf) + xp_assert_close(A, [[-2.]], rtol=1e-13) + xp_assert_close(B, [[1.]], rtol=1e-13) + xp_assert_close(C, [[0.], [-1.]], rtol=1e-13) + xp_assert_close(D, [[1.], [1.]], rtol=1e-13) + + num, den = ss2tf(A, B, C, D) + xp_assert_close(num, [[1., 2.], [1., 1.]], rtol=1e-13) + xp_assert_close(den, [1., 2.], rtol=1e-13) + + tf = ([[1, 0, 1], [1, 1, 1]], [1, 1, 1]) + A, B, C, D = tf2ss(*tf) + xp_assert_close(A, [[-1., -1.], [1., 0.]], rtol=1e-13) + xp_assert_close(B, [[1.], [0.]], rtol=1e-13) + xp_assert_close(C, [[-1., 0.], [0., 0.]], rtol=1e-13) + xp_assert_close(D, [[1.], [1.]], rtol=1e-13) + + num, den = ss2tf(A, B, C, D) + xp_assert_close(num, [[1., 0., 1.], [1., 1., 1.]], rtol=1e-13) + xp_assert_close(den, [1., 1., 1.], rtol=1e-13) + + tf = ([[1, 2, 3], [1, 2, 3]], [1, 2, 3, 4]) + A, B, C, D = tf2ss(*tf) + xp_assert_close(A, [[-2., -3, -4], [1, 0, 0], [0, 1, 0]], rtol=1e-13) + xp_assert_close(B, [[1.], [0], [0]], rtol=1e-13) + xp_assert_close(C, [[1., 2, 3], [1, 2, 3]], rtol=1e-13) + xp_assert_close(D, [[0.], [0]], rtol=1e-13) + + num, den = ss2tf(A, B, C, D) + xp_assert_close(num, [[0., 1, 2, 3], [0, 1, 2, 3]], rtol=1e-13) + xp_assert_close(den, [1., 2, 3, 4], rtol=1e-13) + + tf = (np.array([1, [2, 3]], dtype=object), [1, 6]) + A, B, C, D = tf2ss(*tf) + xp_assert_close(A, [[-6.]], rtol=1e-31) + xp_assert_close(B, [[1.]], rtol=1e-31) + xp_assert_close(C, [[1.], [-9]], rtol=1e-31) + xp_assert_close(D, [[0.], [2]], rtol=1e-31) + + num, den = ss2tf(A, B, C, D) + xp_assert_close(num, [[0., 1], [2, 3]], rtol=1e-13) + xp_assert_close(den, [1., 6], rtol=1e-13) + + tf = (np.array([[1, -3], [1, 2, 3]], dtype=object), [1, 6, 5]) + A, B, C, D = tf2ss(*tf) + xp_assert_close(A, [[-6., -5], [1, 0]], rtol=1e-13) + xp_assert_close(B, [[1.], [0]], rtol=1e-13) + xp_assert_close(C, [[1., -3], [-4, -2]], rtol=1e-13) + xp_assert_close(D, [[0.], [1]], rtol=1e-13) + + num, den = ss2tf(A, B, C, D) + xp_assert_close(num, [[0., 1, -3], [1, 2, 3]], rtol=1e-13) + xp_assert_close(den, [1., 6, 5], rtol=1e-13) + + def test_all_int_arrays(self): + A = [[0, 1, 0], [0, 0, 1], [-3, -4, -2]] + B = [[0], [0], [1]] + C = [[5, 1, 0]] + D = [[0]] + num, den = ss2tf(A, B, C, D) + xp_assert_close(num, [[0.0, 0.0, 1.0, 5.0]], rtol=1e-13, atol=1e-14) + xp_assert_close(den, [1.0, 2.0, 4.0, 3.0], rtol=1e-13) + + def test_multioutput(self): + # Regression test for gh-2669. + + # 4 states + A = np.array([[-1.0, 0.0, 1.0, 0.0], + [-1.0, 0.0, 2.0, 0.0], + [-4.0, 0.0, 3.0, 0.0], + [-8.0, 8.0, 0.0, 4.0]]) + + # 1 input + B = np.array([[0.3], + [0.0], + [7.0], + [0.0]]) + + # 3 outputs + C = np.array([[0.0, 1.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 1.0], + [8.0, 8.0, 0.0, 0.0]]) + + D = np.array([[0.0], + [0.0], + [1.0]]) + + # Get the transfer functions for all the outputs in one call. + b_all, a = ss2tf(A, B, C, D) + + # Get the transfer functions for each output separately. + b0, a0 = ss2tf(A, B, C[0], D[0]) + b1, a1 = ss2tf(A, B, C[1], D[1]) + b2, a2 = ss2tf(A, B, C[2], D[2]) + + # Check that we got the same results. + xp_assert_close(a0, a, rtol=1e-13) + xp_assert_close(a1, a, rtol=1e-13) + xp_assert_close(a2, a, rtol=1e-13) + xp_assert_close(b_all, np.vstack((b0, b1, b2)), rtol=1e-13, atol=1e-14) + + +class TestLsim: + digits_accuracy = 7 + + def lti_nowarn(self, *args): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", BadCoefficients) + system = lti(*args) + return system + + def test_first_order(self): + # y' = -y + # exact solution is y(t) = exp(-t) + system = self.lti_nowarn(-1.,1.,1.,0.) + t = np.linspace(0,5) + u = np.zeros_like(t) + tout, y, x = lsim(system, u, t, X0=[1.0]) + expected_x = np.exp(-tout) + assert_almost_equal(x, expected_x) + assert_almost_equal(y, expected_x) + + def test_second_order(self): + t = np.linspace(0, 10, 1001) + u = np.zeros_like(t) + # Second order system with a repeated root: x''(t) + 2*x(t) + x(t) = 0. + # With initial conditions x(0)=1.0 and x'(t)=0.0, the exact solution + # is (1-t)*exp(-t). + system = self.lti_nowarn([1.0], [1.0, 2.0, 1.0]) + tout, y, x = lsim(system, u, t, X0=[1.0, 0.0]) + expected_x = (1.0 - tout) * np.exp(-tout) + assert_almost_equal(x[:, 0], expected_x) + + def test_integrator(self): + # integrator: y' = u + system = self.lti_nowarn(0., 1., 1., 0.) + t = np.linspace(0,5) + u = t + tout, y, x = lsim(system, u, t) + expected_x = 0.5 * tout**2 + assert_almost_equal(x, expected_x, decimal=self.digits_accuracy) + assert_almost_equal(y, expected_x, decimal=self.digits_accuracy) + + def test_two_states(self): + # A system with two state variables, two inputs, and one output. + A = np.array([[-1.0, 0.0], [0.0, -2.0]]) + B = np.array([[1.0, 0.0], [0.0, 1.0]]) + C = np.array([1.0, 0.0]) + D = np.zeros((1, 2)) + + system = self.lti_nowarn(A, B, C, D) + + t = np.linspace(0, 10.0, 21) + u = np.zeros((len(t), 2)) + tout, y, x = lsim(system, U=u, T=t, X0=[1.0, 1.0]) + expected_y = np.exp(-tout) + expected_x0 = np.exp(-tout) + expected_x1 = np.exp(-2.0 * tout) + assert_almost_equal(y, expected_y) + assert_almost_equal(x[:, 0], expected_x0) + assert_almost_equal(x[:, 1], expected_x1) + + def test_double_integrator(self): + # double integrator: y'' = 2u + A = np.array([[0., 1.], [0., 0.]]) + B = np.array([[0.], [1.]]) + C = np.array([[2., 0.]]) + system = self.lti_nowarn(A, B, C, 0.) + t = np.linspace(0,5) + u = np.ones_like(t) + tout, y, x = lsim(system, u, t) + expected_x = np.transpose(np.array([0.5 * tout**2, tout])) + expected_y = tout**2 + assert_almost_equal(x, expected_x, decimal=self.digits_accuracy) + assert_almost_equal(y, expected_y, decimal=self.digits_accuracy) + + def test_jordan_block(self): + # Non-diagonalizable A matrix + # x1' + x1 = x2 + # x2' + x2 = u + # y = x1 + # Exact solution with u = 0 is y(t) = t exp(-t) + A = np.array([[-1., 1.], [0., -1.]]) + B = np.array([[0.], [1.]]) + C = np.array([[1., 0.]]) + system = self.lti_nowarn(A, B, C, 0.) + t = np.linspace(0,5) + u = np.zeros_like(t) + tout, y, x = lsim(system, u, t, X0=[0.0, 1.0]) + expected_y = tout * np.exp(-tout) + assert_almost_equal(y, expected_y) + + def test_miso(self): + # A system with two state variables, two inputs, and one output. + A = np.array([[-1.0, 0.0], [0.0, -2.0]]) + B = np.array([[1.0, 0.0], [0.0, 1.0]]) + C = np.array([1.0, 0.0]) + D = np.zeros((1,2)) + system = self.lti_nowarn(A, B, C, D) + + t = np.linspace(0, 5.0, 101) + u = np.zeros((len(t), 2)) + tout, y, x = lsim(system, u, t, X0=[1.0, 1.0]) + expected_y = np.exp(-tout) + expected_x0 = np.exp(-tout) + expected_x1 = np.exp(-2.0*tout) + assert_almost_equal(y, expected_y) + assert_almost_equal(x[:,0], expected_x0) + assert_almost_equal(x[:,1], expected_x1) + + def test_nonzero_initial_time(self): + system = self.lti_nowarn(-1.,1.,1.,0.) + t = np.linspace(1,2) + u = np.zeros_like(t) + tout, y, x = lsim(system, u, t, X0=[1.0]) + expected_y = np.exp(-tout) + assert_almost_equal(y, expected_y) + + def test_nonequal_timesteps(self): + t = np.array([0.0, 1.0, 1.0, 3.0]) + u = np.array([0.0, 0.0, 1.0, 1.0]) + # Simple integrator: x'(t) = u(t) + system = ([1.0], [1.0, 0.0]) + with assert_raises(ValueError, + match="Time steps are not equally spaced."): + tout, y, x = lsim(system, u, t, X0=[1.0]) + + +class TestImpulse: + def test_first_order(self): + # First order system: x'(t) + x(t) = u(t) + # Exact impulse response is x(t) = exp(-t). + system = ([1.0], [1.0,1.0]) + tout, y = impulse(system) + expected_y = np.exp(-tout) + assert_almost_equal(y, expected_y) + + def test_first_order_fixed_time(self): + # Specify the desired time values for the output. + + # First order system: x'(t) + x(t) = u(t) + # Exact impulse response is x(t) = exp(-t). + system = ([1.0], [1.0,1.0]) + n = 21 + t = np.linspace(0, 2.0, n) + tout, y = impulse(system, T=t) + assert tout.shape == (n,) + assert_almost_equal(tout, t) + expected_y = np.exp(-t) + assert_almost_equal(y, expected_y) + + def test_first_order_initial(self): + # Specify an initial condition as a scalar. + + # First order system: x'(t) + x(t) = u(t), x(0)=3.0 + # Exact impulse response is x(t) = 4*exp(-t). + system = ([1.0], [1.0,1.0]) + tout, y = impulse(system, X0=3.0) + expected_y = 4.0 * np.exp(-tout) + assert_almost_equal(y, expected_y) + + def test_first_order_initial_list(self): + # Specify an initial condition as a list. + + # First order system: x'(t) + x(t) = u(t), x(0)=3.0 + # Exact impulse response is x(t) = 4*exp(-t). + system = ([1.0], [1.0,1.0]) + tout, y = impulse(system, X0=[3.0]) + expected_y = 4.0 * np.exp(-tout) + assert_almost_equal(y, expected_y) + + def test_integrator(self): + # Simple integrator: x'(t) = u(t) + system = ([1.0], [1.0,0.0]) + tout, y = impulse(system) + expected_y = np.ones_like(tout) + assert_almost_equal(y, expected_y) + + def test_second_order(self): + # Second order system with a repeated root: + # x''(t) + 2*x(t) + x(t) = u(t) + # The exact impulse response is t*exp(-t). + system = ([1.0], [1.0, 2.0, 1.0]) + tout, y = impulse(system) + expected_y = tout * np.exp(-tout) + assert_almost_equal(y, expected_y) + + def test_array_like(self): + # Test that function can accept sequences, scalars. + system = ([1.0], [1.0, 2.0, 1.0]) + # TODO: add meaningful test where X0 is a list + tout, y = impulse(system, X0=[3], T=[5, 6]) + tout, y = impulse(system, X0=[3], T=[5]) + + def test_array_like2(self): + system = ([1.0], [1.0, 2.0, 1.0]) + tout, y = impulse(system, X0=3, T=5) + + +class TestStep: + def test_first_order(self): + # First order system: x'(t) + x(t) = u(t) + # Exact step response is x(t) = 1 - exp(-t). + system = ([1.0], [1.0,1.0]) + tout, y = step(system) + expected_y = 1.0 - np.exp(-tout) + assert_almost_equal(y, expected_y) + + def test_first_order_fixed_time(self): + # Specify the desired time values for the output. + + # First order system: x'(t) + x(t) = u(t) + # Exact step response is x(t) = 1 - exp(-t). + system = ([1.0], [1.0,1.0]) + n = 21 + t = np.linspace(0, 2.0, n) + tout, y = step(system, T=t) + assert tout.shape == (n,) + assert_almost_equal(tout, t) + expected_y = 1 - np.exp(-t) + assert_almost_equal(y, expected_y) + + def test_first_order_initial(self): + # Specify an initial condition as a scalar. + + # First order system: x'(t) + x(t) = u(t), x(0)=3.0 + # Exact step response is x(t) = 1 + 2*exp(-t). + system = ([1.0], [1.0,1.0]) + tout, y = step(system, X0=3.0) + expected_y = 1 + 2.0*np.exp(-tout) + assert_almost_equal(y, expected_y) + + def test_first_order_initial_list(self): + # Specify an initial condition as a list. + + # First order system: x'(t) + x(t) = u(t), x(0)=3.0 + # Exact step response is x(t) = 1 + 2*exp(-t). + system = ([1.0], [1.0,1.0]) + tout, y = step(system, X0=[3.0]) + expected_y = 1 + 2.0*np.exp(-tout) + assert_almost_equal(y, expected_y) + + def test_integrator(self): + # Simple integrator: x'(t) = u(t) + # Exact step response is x(t) = t. + system = ([1.0],[1.0,0.0]) + tout, y = step(system) + expected_y = tout + assert_almost_equal(y, expected_y) + + def test_second_order(self): + # Second order system with a repeated root: + # x''(t) + 2*x(t) + x(t) = u(t) + # The exact step response is 1 - (1 + t)*exp(-t). + system = ([1.0], [1.0, 2.0, 1.0]) + tout, y = step(system) + expected_y = 1 - (1 + tout) * np.exp(-tout) + assert_almost_equal(y, expected_y) + + def test_array_like(self): + # Test that function can accept sequences, scalars. + system = ([1.0], [1.0, 2.0, 1.0]) + # TODO: add meaningful test where X0 is a list + tout, y = step(system, T=[5, 6]) + + def test_complex_input(self): + # Test that complex input doesn't raise an error. + # `step` doesn't seem to have been designed for complex input, but this + # works and may be used, so add regression test. See gh-2654. + step(([], [-1], 1+0j)) + + +class TestLti: + def test_lti_instantiation(self): + # Test that lti can be instantiated with sequences, scalars. + # See PR-225. + + # TransferFunction + s = lti([1], [-1]) + assert isinstance(s, TransferFunction) + assert isinstance(s, lti) + assert not isinstance(s, dlti) + assert s.dt is None + + # ZerosPolesGain + s = lti(np.array([]), np.array([-1]), 1) + assert isinstance(s, ZerosPolesGain) + assert isinstance(s, lti) + assert not isinstance(s, dlti) + assert s.dt is None + + # StateSpace + s = lti([], [-1], 1) + s = lti([1], [-1], 1, 3) + assert isinstance(s, StateSpace) + assert isinstance(s, lti) + assert not isinstance(s, dlti) + assert s.dt is None + + +class TestStateSpace: + def test_initialization(self): + # Check that all initializations work + StateSpace(1, 1, 1, 1) + StateSpace([1], [2], [3], [4]) + StateSpace(np.array([[1, 2], [3, 4]]), np.array([[1], [2]]), + np.array([[1, 0]]), np.array([[0]])) + + # Verify that parameter are casted correctly: + AA = np.array([1], dtype=np.int64) + ss0 = StateSpace(AA, 1, 1, 1) + assert ss0.A.dtype == ss0.B.dtype == ss0.C.dtype == ss0.D.dtype == np.float64 + ss1 = StateSpace(AA, 1, 1, 1+1j) + assert ss1.A.dtype == ss1.B.dtype == ss1.C.dtype == ss1.D.dtype == np.complex128 + + def test_conversion(self): + # Check the conversion functions + s = StateSpace(1, 2, 3, 4) + assert isinstance(s.to_ss(), StateSpace) + assert isinstance(s.to_tf(), TransferFunction) + assert isinstance(s.to_zpk(), ZerosPolesGain) + + # Make sure copies work + assert StateSpace(s) is not s + assert s.to_ss() is not s + + def test_properties(self): + # Test setters/getters for cross class properties. + # This implicitly tests to_tf() and to_zpk() + + # Getters + s = StateSpace(1, 1, 1, 1) + xp_assert_equal(s.poles, [1.]) + xp_assert_equal(s.zeros, [0.]) + assert s.dt is None + + def test_operators(self): + # Test +/-/* operators on systems + + class BadType: + pass + + s1 = StateSpace(np.array([[-0.5, 0.7], [0.3, -0.8]]), + np.array([[1], [0]]), + np.array([[1, 0]]), + np.array([[0]]), + ) + + s2 = StateSpace(np.array([[-0.2, -0.1], [0.4, -0.1]]), + np.array([[1], [0]]), + np.array([[1, 0]]), + np.array([[0]]) + ) + + s_discrete = s1.to_discrete(0.1) + s2_discrete = s2.to_discrete(0.2) + s3_discrete = s2.to_discrete(0.1) + + # Impulse response + t = np.linspace(0, 1, 100) + u = np.zeros_like(t) + u[0] = 1 + + # Test multiplication + for typ in (int, float, complex, np.float32, np.complex128, np.array): + xp_assert_close(lsim(typ(2) * s1, U=u, T=t)[1], + typ(2) * lsim(s1, U=u, T=t)[1]) + + xp_assert_close(lsim(s1 * typ(2), U=u, T=t)[1], + lsim(s1, U=u, T=t)[1] * typ(2)) + + xp_assert_close(lsim(s1 / typ(2), U=u, T=t)[1], + lsim(s1, U=u, T=t)[1] / typ(2)) + + with assert_raises(TypeError): + typ(2) / s1 + + xp_assert_close(lsim(s1 * 2, U=u, T=t)[1], + lsim(s1, U=2 * u, T=t)[1]) + + xp_assert_close(lsim(s1 * s2, U=u, T=t)[1], + lsim(s1, U=lsim(s2, U=u, T=t)[1], T=t)[1], + atol=1e-5) + + with assert_raises(TypeError): + s1 / s1 + + with assert_raises(TypeError): + s1 * s_discrete + + with assert_raises(TypeError): + # Check different discretization constants + s_discrete * s2_discrete + + with assert_raises(TypeError): + s1 * BadType() + + with assert_raises(TypeError): + BadType() * s1 + + with assert_raises(TypeError): + s1 / BadType() + + with assert_raises(TypeError): + BadType() / s1 + + # Test addition + xp_assert_close(lsim(s1 + 2, U=u, T=t)[1], + 2 * u + lsim(s1, U=u, T=t)[1]) + + # Check for dimension mismatch + with assert_raises(ValueError): + s1 + np.array([1, 2]) + + with assert_raises(ValueError): + np.array([1, 2]) + s1 + + with assert_raises(TypeError): + s1 + s_discrete + + with assert_raises(ValueError): + s1 / np.array([[1, 2], [3, 4]]) + + with assert_raises(TypeError): + # Check different discretization constants + s_discrete + s2_discrete + + with assert_raises(TypeError): + s1 + BadType() + + with assert_raises(TypeError): + BadType() + s1 + + xp_assert_close(lsim(s1 + s2, U=u, T=t)[1], + lsim(s1, U=u, T=t)[1] + lsim(s2, U=u, T=t)[1]) + + # Test subtraction + xp_assert_close(lsim(s1 - 2, U=u, T=t)[1], + -2 * u + lsim(s1, U=u, T=t)[1]) + + xp_assert_close(lsim(2 - s1, U=u, T=t)[1], + 2 * u + lsim(-s1, U=u, T=t)[1]) + + xp_assert_close(lsim(s1 - s2, U=u, T=t)[1], + lsim(s1, U=u, T=t)[1] - lsim(s2, U=u, T=t)[1]) + + with assert_raises(TypeError): + s1 - BadType() + + with assert_raises(TypeError): + BadType() - s1 + + s = s_discrete + s3_discrete + assert s.dt == 0.1 + + s = s_discrete * s3_discrete + assert s.dt == 0.1 + + s = 3 * s_discrete + assert s.dt == 0.1 + + s = -s_discrete + assert s.dt == 0.1 + +class TestTransferFunction: + def test_initialization(self): + # Check that all initializations work + TransferFunction(1, 1) + TransferFunction([1], [2]) + TransferFunction(np.array([1]), np.array([2])) + + def test_conversion(self): + # Check the conversion functions + s = TransferFunction([1, 0], [1, -1]) + assert isinstance(s.to_ss(), StateSpace) + assert isinstance(s.to_tf(), TransferFunction) + assert isinstance(s.to_zpk(), ZerosPolesGain) + + # Make sure copies work + assert TransferFunction(s) is not s + assert s.to_tf() is not s + + def test_properties(self): + # Test setters/getters for cross class properties. + # This implicitly tests to_ss() and to_zpk() + + # Getters + s = TransferFunction([1, 0], [1, -1]) + xp_assert_equal(s.poles, [1.]) + xp_assert_equal(s.zeros, [0.]) + + +class TestZerosPolesGain: + def test_initialization(self): + # Check that all initializations work + ZerosPolesGain(1, 1, 1) + ZerosPolesGain([1], [2], 1) + ZerosPolesGain(np.array([1]), np.array([2]), 1) + + def test_conversion(self): + #Check the conversion functions + s = ZerosPolesGain(1, 2, 3) + assert isinstance(s.to_ss(), StateSpace) + assert isinstance(s.to_tf(), TransferFunction) + assert isinstance(s.to_zpk(), ZerosPolesGain) + + # Make sure copies work + assert ZerosPolesGain(s) is not s + assert s.to_zpk() is not s + + +@make_xp_test_case(abcd_normalize) +class Test_abcd_normalize: + A = [[1.0, 2.0], [3.0, 4.0]] + B = [[-1.0], [5.0]] + C = [[4.0, 5.0]] + D = [[2.5]] + + def test_no_matrix_fails(self): + assert_raises(ValueError, abcd_normalize) + + def test_A_nosquare_fails(self): + assert_raises(ValueError, abcd_normalize, + [1, -1], self.B, self.C, self.D) + + def test_AB_mismatch_fails(self): + assert_raises(ValueError, abcd_normalize, + self.A, [-1, 5], self.C, self.D) + + def test_AC_mismatch_fails(self): + assert_raises(ValueError, abcd_normalize, + self.A, self.B, [[4.0], [5.0]], self.D) + + def test_CD_mismatch_fails(self): + assert_raises(ValueError, abcd_normalize, + self.A, self.B, self.C, [2.5, 0]) + + def test_BD_mismatch_fails(self, xp): + assert_raises(ValueError, abcd_normalize, + self.A, [-1, 5], self.C, self.D) + + def test_normalized_matrices_unchanged(self, xp): + A_, B_, C_, D_ = map( + lambda t: xp.asarray(t, dtype=xp.float64), + (self.A, self.B, self.C, self.D), + ) + A, B, C, D = abcd_normalize(A=A_, B=B_, C=C_, D=D_) + xp_assert_equal(A, A_) + xp_assert_equal(B, B_) + xp_assert_equal(C, C_) + xp_assert_equal(D, D_) + + def test_shapes(self): + A, B, C, D = abcd_normalize(self.A, self.B, [1, 0], 0) + assert A.shape[0] == A.shape[1] + assert A.shape[0] == B.shape[0] + assert A.shape[0] == C.shape[1] + assert C.shape[0] == D.shape[0] + assert B.shape[1] == D.shape[1] + + def test_zero_dimension_is_not_none1(self, xp): + A_ = xp.asarray(self.A, dtype=xp.float64) + B_ = xp.zeros((2, 0), dtype=xp.float64) + D_ = xp.zeros((0, 0), dtype=xp.float64) + A, B, C, D = abcd_normalize(A=A_, B=B_, D=D_) + xp_assert_equal(A, A_) + xp_assert_equal(B, B_) + xp_assert_equal(D, D_) + assert C.shape[0] == D_.shape[0] + assert C.shape[1] == A_.shape[0] + + def test_zero_dimension_is_not_none2(self, xp): + A_ = xp.asarray(self.A, dtype=xp.float64) + B_ = xp.zeros((2, 0), dtype=xp.float64) + C_ = xp.zeros((0, 2), dtype=xp.float64) + A, B, C, D = abcd_normalize(A=A_, B=B_, C=C_) + xp_assert_equal(A, A_) + xp_assert_equal(B, B_) + xp_assert_equal(C, C_) + assert D.shape[0] == C_.shape[0] + assert D.shape[1] == B_.shape[1] + + def test_missing_A(self, xp): + B_, C_, D_ = map( + lambda t: xp.asarray(t, dtype=xp.float64), + (self.B, self.C, self.D), + ) + A, B, C, D = abcd_normalize(B=B_, C=C_, D=D_) + assert A.shape[0] == A.shape[1] + assert A.shape[0] == B.shape[0] + assert A.shape == (B_.shape[0], B_.shape[0]) + + def test_missing_B(self, xp): + A_, C_, D_ = map(xp.asarray, (self.A, self.C, self.D)) + A, B, C, D = abcd_normalize(A=A_, C=C_, D=D_) + assert B.shape[0] == A.shape[0] + assert B.shape[1] == D.shape[1] + assert B.shape == (A_.shape[0], D_.shape[1]) + + def test_missing_C(self, xp): + A_, B_, D_ = map( + lambda t: xp.asarray(t, dtype=xp.float64), + (self.A, self.B, self.D), + ) + A, B, C, D = abcd_normalize(A=A_, B=B_, D=D_) + assert C.shape[0] == D.shape[0] + assert C.shape[1] == A.shape[0] + assert C.shape == (D_.shape[0], A_.shape[0]) + + def test_missing_D(self, xp): + A_, B_, C_ = map(xp.asarray, (self.A, self.B, self.C)) + A, B, C, D = abcd_normalize(A=A_, B=B_, C=C_) + assert D.shape[0] == C.shape[0] + assert D.shape[1] == B.shape[1] + assert D.shape == (C_.shape[0], B_.shape[1]) + + def test_missing_AB(self, xp): + C_, D_ = map(xp.asarray, (self.C, self.D)) + A, B, C, D = abcd_normalize(C=C_, D=D_) + assert A.shape[0] == A.shape[1] + assert A.shape[0] == B.shape[0] + assert B.shape[1] == D.shape[1] + assert A.shape == (C_.shape[1], C_.shape[1]) + assert B.shape == (C_.shape[1], D_.shape[1]) + + def test_missing_AC(self, xp): + B_, D_ = map(xp.asarray, (self.B, self.D)) + A, B, C, D = abcd_normalize(B=B_, D=D_) + assert A.shape[0] == A.shape[1] + assert A.shape[0] == B.shape[0] + assert C.shape[0] == D.shape[0] + assert C.shape[1] == A.shape[0] + assert A.shape == (B_.shape[0], B_.shape[0]) + assert C.shape == (D_.shape[0], B_.shape[0]) + + def test_missing_AD(self, xp): + B_, C_ = map(xp.asarray, (self.B, self.C)) + A, B, C, D = abcd_normalize(B=B_, C=C_) + assert A.shape[0] == A.shape[1] + assert A.shape[0] == B.shape[0] + assert D.shape[0] == C.shape[0] + assert D.shape[1] == B.shape[1] + assert A.shape == (B_.shape[0], B_.shape[0]) + assert D.shape == (C_.shape[0], B_.shape[1]) + + def test_missing_BC(self, xp): + A_, D_ = map(xp.asarray, (self.A, self.D)) + A, B, C, D = abcd_normalize(A=A_, D=D_) + assert B.shape[0] == A.shape[0] + assert B.shape[1] == D.shape[1] + assert C.shape[0] == D.shape[0] + assert C.shape[1] == A.shape[0] + assert B.shape == (A_.shape[0], D_.shape[1]) + assert C.shape == (D_.shape[0], A_.shape[0]) + + def test_missing_ABC_fails(self, xp): + assert_raises(ValueError, abcd_normalize, D=xp.asarray(self.D)) + + def test_missing_BD_fails(self, xp): + A, C = xp.asarray(self.A),xp.asarray(self.C) + assert_raises(ValueError, abcd_normalize, A=A, C=C) + + def test_missing_CD_fails(self, xp): + A, B = xp.asarray(self.A), xp.asarray(self.B) + assert_raises(ValueError, abcd_normalize, A=A, B=B) + + + @pytest.mark.parametrize( + "A_dtype", + # A_dtype=None here means to pass None as the value for A + [None, "int64", "float32", "float64", "complex64", "complex128"], + ) + @pytest.mark.parametrize( + "B_dtype", + [None, "int64", "float32", "float64", "complex64", "complex128"], + ) + @pytest.mark.parametrize( + "C_dtype", + ["int64", "float32", "float64", "complex64", "complex128"], + ) + @pytest.mark.parametrize( + "D_dtype", + ["int64", "float32", "float64", "complex64", "complex128"], + ) + # Also check case where one input array has size zero. + @pytest.mark.parametrize("D", [[[2.5]], [[]]]) + def test_dtypes(self, D, A_dtype, B_dtype, C_dtype, D_dtype, xp): + + args = [] + for X, X_dtype in zip( + [self.A, self.B, self.C, D], + [A_dtype, B_dtype, C_dtype, D_dtype] + ): + args.append( + xp.asarray(X, dtype=getattr(xp, X_dtype)) + if X_dtype is not None else None + ) + A, B, C, D = args + expected_dtype = xp_result_type(A, B, C, D, xp=xp, force_floating=True) + A, B, C, D = abcd_normalize(A=A, B=B, C=C, D=D) + assert A.dtype == B.dtype == C.dtype == D.dtype == expected_dtype + + +class Test_bode: + + def test_01(self): + # Test bode() magnitude calculation (manual sanity check). + # 1st order low-pass filter: H(s) = 1 / (s + 1), + # cutoff: 1 rad/s, slope: -20 dB/decade + # H(s=0.1) ~= 0 dB + # H(s=1) ~= -3 dB + # H(s=10) ~= -20 dB + # H(s=100) ~= -40 dB + system = lti([1], [1, 1]) + w = [0.1, 1, 10, 100] + w, mag, phase = bode(system, w=w) + expected_mag = [0, -3, -20, -40] + assert_almost_equal(mag, expected_mag, decimal=1) + + def test_02(self): + # Test bode() phase calculation (manual sanity check). + # 1st order low-pass filter: H(s) = 1 / (s + 1), + # angle(H(s=0.1)) ~= -5.7 deg + # angle(H(s=1)) ~= -45 deg + # angle(H(s=10)) ~= -84.3 deg + system = lti([1], [1, 1]) + w = [0.1, 1, 10] + w, mag, phase = bode(system, w=w) + expected_phase = [-5.7, -45, -84.3] + assert_almost_equal(phase, expected_phase, decimal=1) + + def test_03(self): + # Test bode() magnitude calculation. + # 1st order low-pass filter: H(s) = 1 / (s + 1) + system = lti([1], [1, 1]) + w = [0.1, 1, 10, 100] + w, mag, phase = bode(system, w=w) + jw = w * 1j + y = np.polyval(system.num, jw) / np.polyval(system.den, jw) + expected_mag = 20.0 * np.log10(abs(y)) + assert_almost_equal(mag, expected_mag) + + def test_04(self): + # Test bode() phase calculation. + # 1st order low-pass filter: H(s) = 1 / (s + 1) + system = lti([1], [1, 1]) + w = [0.1, 1, 10, 100] + w, mag, phase = bode(system, w=w) + jw = w * 1j + y = np.polyval(system.num, jw) / np.polyval(system.den, jw) + expected_phase = np.arctan2(y.imag, y.real) * 180.0 / np.pi + assert_almost_equal(phase, expected_phase) + + def test_05(self): + # Test that bode() finds a reasonable frequency range. + # 1st order low-pass filter: H(s) = 1 / (s + 1) + system = lti([1], [1, 1]) + n = 10 + # Expected range is from 0.01 to 10. + expected_w = np.logspace(-2, 1, n) + w, mag, phase = bode(system, n=n) + assert_almost_equal(w, expected_w) + + def test_06(self): + # Test that bode() doesn't fail on a system with a pole at 0. + # integrator, pole at zero: H(s) = 1 / s + system = lti([1], [1, 0]) + w, mag, phase = bode(system, n=2) + assert w[0] == 0.01 # a fail would give not-a-number + + def test_07(self): + # bode() should not fail on a system with pure imaginary poles. + # The test passes if bode doesn't raise an exception. + system = lti([1], [1, 0, 100]) + w, mag, phase = bode(system, n=2) + + def test_08(self): + # Test that bode() return continuous phase, issues/2331. + system = lti([], [-10, -30, -40, -60, -70], 1) + w, mag, phase = system.bode(w=np.logspace(-3, 40, 100)) + assert_almost_equal(min(phase), -450, decimal=15) + + def test_from_state_space(self): + # Ensure that bode works with a system that was created from the + # state space representation matrices A, B, C, D. In this case, + # system.num will be a 2-D array with shape (1, n+1), where (n,n) + # is the shape of A. + # A Butterworth lowpass filter is used, so we know the exact + # frequency response. + a = np.array([1.0, 2.0, 2.0, 1.0]) + A = linalg.companion(a).T + B = np.array([[0.0], [0.0], [1.0]]) + C = np.array([[1.0, 0.0, 0.0]]) + D = np.array([[0.0]]) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", BadCoefficients) + system = lti(A, B, C, D) + w, mag, phase = bode(system, n=100) + + expected_magnitude = 20 * np.log10(np.sqrt(1.0 / (1.0 + w**6))) + assert_almost_equal(mag, expected_magnitude) + + +class Test_freqresp: + + def test_output_manual(self): + # Test freqresp() output calculation (manual sanity check). + # 1st order low-pass filter: H(s) = 1 / (s + 1), + # re(H(s=0.1)) ~= 0.99 + # re(H(s=1)) ~= 0.5 + # re(H(s=10)) ~= 0.0099 + system = lti([1], [1, 1]) + w = [0.1, 1, 10] + w, H = freqresp(system, w=w) + expected_re = [0.99, 0.5, 0.0099] + expected_im = [-0.099, -0.5, -0.099] + assert_almost_equal(H.real, expected_re, decimal=1) + assert_almost_equal(H.imag, expected_im, decimal=1) + + def test_output(self): + # Test freqresp() output calculation. + # 1st order low-pass filter: H(s) = 1 / (s + 1) + system = lti([1], [1, 1]) + w = [0.1, 1, 10, 100] + w, H = freqresp(system, w=w) + s = w * 1j + expected = np.polyval(system.num, s) / np.polyval(system.den, s) + assert_almost_equal(H.real, expected.real) + assert_almost_equal(H.imag, expected.imag) + + def test_freq_range(self): + # Test that freqresp() finds a reasonable frequency range. + # 1st order low-pass filter: H(s) = 1 / (s + 1) + # Expected range is from 0.01 to 10. + system = lti([1], [1, 1]) + n = 10 + expected_w = np.logspace(-2, 1, n) + w, H = freqresp(system, n=n) + assert_almost_equal(w, expected_w) + + def test_pole_zero(self): + # Test that freqresp() doesn't fail on a system with a pole at 0. + # integrator, pole at zero: H(s) = 1 / s + system = lti([1], [1, 0]) + w, H = freqresp(system, n=2) + assert w[0] == 0.01 # a fail would give not-a-number + + def test_from_state_space(self): + # Ensure that freqresp works with a system that was created from the + # state space representation matrices A, B, C, D. In this case, + # system.num will be a 2-D array with shape (1, n+1), where (n,n) is + # the shape of A. + # A Butterworth lowpass filter is used, so we know the exact + # frequency response. + a = np.array([1.0, 2.0, 2.0, 1.0]) + A = linalg.companion(a).T + B = np.array([[0.0],[0.0],[1.0]]) + C = np.array([[1.0, 0.0, 0.0]]) + D = np.array([[0.0]]) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", BadCoefficients) + system = lti(A, B, C, D) + w, H = freqresp(system, n=100) + s = w * 1j + expected = (1.0 / (1.0 + 2*s + 2*s**2 + s**3)) + assert_almost_equal(H.real, expected.real) + assert_almost_equal(H.imag, expected.imag) + + def test_from_zpk(self): + # 4th order low-pass filter: H(s) = 1 / (s + 1) + system = lti([],[-1]*4,[1]) + w = [0.1, 1, 10, 100] + w, H = freqresp(system, w=w) + s = w * 1j + expected = 1 / (s + 1)**4 + assert_almost_equal(H.real, expected.real) + assert_almost_equal(H.imag, expected.imag) + +@pytest.mark.parametrize( + "cls", [StateSpace, TransferFunction, ZerosPolesGain, lti, dlti] +) +def test_subscriptable_generic_types(cls): + assert isinstance(cls[np.float64], GenericAlias) diff --git a/venv/lib/python3.12/site-packages/scipy/signal/tests/test_max_len_seq.py b/venv/lib/python3.12/site-packages/scipy/signal/tests/test_max_len_seq.py new file mode 100644 index 0000000000000000000000000000000000000000..7610b3f898571d10d75a64f00d900168c7142fbe --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/tests/test_max_len_seq.py @@ -0,0 +1,71 @@ +import numpy as np +from pytest import raises as assert_raises +from scipy._lib._array_api import xp_assert_close, xp_assert_equal + +from numpy.fft import fft, ifft + +from scipy.signal import max_len_seq + + +class TestMLS: + + def test_mls_inputs(self): + # can't all be zero state + assert_raises(ValueError, max_len_seq, + 10, state=np.zeros(10)) + # wrong size state + assert_raises(ValueError, max_len_seq, 10, + state=np.ones(3)) + # wrong length + assert_raises(ValueError, max_len_seq, 10, length=-1) + xp_assert_equal(max_len_seq(10, length=0)[0], + np.asarray([], dtype=np.int8) + ) + # unknown taps + assert_raises(ValueError, max_len_seq, 64) + # bad taps + assert_raises(ValueError, max_len_seq, 10, taps=[-1, 1]) + + def test_mls_output(self): + # define some alternate working taps + alt_taps = {2: [1], 3: [2], 4: [3], 5: [4, 3, 2], 6: [5, 4, 1], 7: [4], + 8: [7, 5, 3]} + # assume the other bit levels work, too slow to test higher orders... + for nbits in range(2, 8): + for state in [None, np.round(np.random.rand(nbits))]: + for taps in [None, alt_taps[nbits]]: + if state is not None and np.all(state == 0): + state[0] = 1 # they can't all be zero + orig_m = max_len_seq(nbits, state=state, + taps=taps)[0] + m = 2. * orig_m - 1. # convert to +/- 1 representation + # First, make sure we got all 1's or -1 + err_msg = "mls had non binary terms" + xp_assert_equal(np.abs(m), np.ones_like(m), + err_msg=err_msg) + # Test via circular cross-correlation, which is just mult. + # in the frequency domain with one signal conjugated + tester = np.real(ifft(fft(m) * np.conj(fft(m)))) + out_len = 2**nbits - 1 + # impulse amplitude == test_len + err_msg = "mls impulse has incorrect value" + xp_assert_close(tester[0], + float(out_len), + err_msg=err_msg + ) + # steady-state is -1 + err_msg = "mls steady-state has incorrect value" + xp_assert_close(tester[1:], + np.full(out_len - 1, -1, dtype=tester.dtype), + err_msg=err_msg) + # let's do the split thing using a couple options + for n in (1, 2**(nbits - 1)): + m1, s1 = max_len_seq(nbits, state=state, taps=taps, + length=n) + m2, s2 = max_len_seq(nbits, state=s1, taps=taps, + length=1) + m3, s3 = max_len_seq(nbits, state=s2, taps=taps, + length=out_len - n - 1) + new_m = np.concatenate((m1, m2, m3)) + xp_assert_equal(orig_m, new_m) + diff --git a/venv/lib/python3.12/site-packages/scipy/signal/tests/test_peak_finding.py b/venv/lib/python3.12/site-packages/scipy/signal/tests/test_peak_finding.py new file mode 100644 index 0000000000000000000000000000000000000000..7aa04f3a8ca47616f1456664fc9ae0f5d0b8e69d --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/tests/test_peak_finding.py @@ -0,0 +1,970 @@ +import copy + +import numpy as np +import pytest +from pytest import raises, warns +from scipy._lib._array_api import xp_assert_close, xp_assert_equal + +from scipy.signal._peak_finding import ( + argrelmax, + argrelmin, + peak_prominences, + peak_widths, + _unpack_condition_args, + find_peaks, + find_peaks_cwt, + _identify_ridge_lines +) +from scipy.signal.windows import gaussian +from scipy.signal._peak_finding_utils import _local_maxima_1d, PeakPropertyWarning + + +def _gen_gaussians(center_locs, sigmas, total_length): + xdata = np.arange(0, total_length).astype(float) + out_data = np.zeros(total_length, dtype=float) + for ind, sigma in enumerate(sigmas): + tmp = (xdata - center_locs[ind]) / sigma + out_data += np.exp(-(tmp**2)) + return out_data + + +def _gen_gaussians_even(sigmas, total_length): + num_peaks = len(sigmas) + delta = total_length / (num_peaks + 1) + center_locs = np.linspace(delta, total_length - delta, num=num_peaks).astype(int) + out_data = _gen_gaussians(center_locs, sigmas, total_length) + return out_data, center_locs + + +def _gen_ridge_line(start_locs, max_locs, length, distances, gaps): + """ + Generate coordinates for a ridge line. + + Will be a series of coordinates, starting a start_loc (length 2). + The maximum distance between any adjacent columns will be + `max_distance`, the max distance between adjacent rows + will be `map_gap'. + + `max_locs` should be the size of the intended matrix. The + ending coordinates are guaranteed to be less than `max_locs`, + although they may not approach `max_locs` at all. + """ + + def keep_bounds(num, max_val): + out = max(num, 0) + out = min(out, max_val) + return out + + gaps = copy.deepcopy(gaps) + distances = copy.deepcopy(distances) + + locs = np.zeros([length, 2], dtype=int) + locs[0, :] = start_locs + total_length = max_locs[0] - start_locs[0] - sum(gaps) + if total_length < length: + raise ValueError('Cannot generate ridge line according to constraints') + dist_int = length / len(distances) - 1 + gap_int = length / len(gaps) - 1 + for ind in range(1, length): + nextcol = locs[ind - 1, 1] + nextrow = locs[ind - 1, 0] + 1 + if (ind % dist_int == 0) and (len(distances) > 0): + nextcol += ((-1)**ind)*distances.pop() + if (ind % gap_int == 0) and (len(gaps) > 0): + nextrow += gaps.pop() + nextrow = keep_bounds(nextrow, max_locs[0]) + nextcol = keep_bounds(nextcol, max_locs[1]) + locs[ind, :] = [nextrow, nextcol] + + return [locs[:, 0], locs[:, 1]] + + +class TestLocalMaxima1d: + + def test_empty(self): + """Test with empty signal.""" + x = np.array([], dtype=np.float64) + for array in _local_maxima_1d(x): + xp_assert_equal(array, np.array([]), check_dtype=False) + assert array.base is None + + def test_linear(self): + """Test with linear signal.""" + x = np.linspace(0, 100) + for array in _local_maxima_1d(x): + xp_assert_equal(array, np.array([], dtype=np.intp)) + assert array.base is None + + def test_simple(self): + """Test with simple signal.""" + x = np.linspace(-10, 10, 50) + x[2::3] += 1 + expected = np.arange(2, 50, 3, dtype=np.intp) + for array in _local_maxima_1d(x): + # For plateaus of size 1, the edges are identical with the + # midpoints + xp_assert_equal(array, expected, check_dtype=False) + assert array.base is None + + def test_flat_maxima(self): + """Test if flat maxima are detected correctly.""" + x = np.array([-1.3, 0, 1, 0, 2, 2, 0, 3, 3, 3, 2.99, 4, 4, 4, 4, -10, + -5, -5, -5, -5, -5, -10]) + midpoints, left_edges, right_edges = _local_maxima_1d(x) + xp_assert_equal(midpoints, np.array([2, 4, 8, 12, 18]), check_dtype=False) + xp_assert_equal(left_edges, np.array([2, 4, 7, 11, 16]), check_dtype=False) + xp_assert_equal(right_edges, np.array([2, 5, 9, 14, 20]), check_dtype=False) + + @pytest.mark.parametrize('x', [ + np.array([1., 0, 2]), + np.array([3., 3, 0, 4, 4]), + np.array([5., 5, 5, 0, 6, 6, 6]), + ]) + def test_signal_edges(self, x): + """Test if behavior on signal edges is correct.""" + for array in _local_maxima_1d(x): + xp_assert_equal(array, np.array([], dtype=np.intp)) + assert array.base is None + + def test_exceptions(self): + """Test input validation and raised exceptions.""" + with raises(ValueError, match="wrong number of dimensions"): + _local_maxima_1d(np.ones((1, 1))) + with raises(ValueError, match="expected 'const float64_t'"): + _local_maxima_1d(np.ones(1, dtype=int)) + with raises(TypeError, match="list"): + _local_maxima_1d([1., 2.]) + with raises(TypeError, match="'x' must not be None"): + _local_maxima_1d(None) + + +class TestRidgeLines: + + def test_empty(self): + test_matr = np.zeros([20, 100]) + lines = _identify_ridge_lines(test_matr, np.full(20, 2), 1) + assert len(lines) == 0 + + def test_minimal(self): + test_matr = np.zeros([20, 100]) + test_matr[0, 10] = 1 + lines = _identify_ridge_lines(test_matr, np.full(20, 2), 1) + assert len(lines) == 1 + for line in lines: + xp_assert_equal(line[0], np.sort(line[0])) + + test_matr = np.zeros([20, 100]) + test_matr[0:2, 10] = 1 + lines = _identify_ridge_lines(test_matr, np.full(20, 2), 1) + assert len(lines) == 1 + for line in lines: + xp_assert_equal(line[0], np.sort(line[0])) + + def test_single_pass(self): + distances = [0, 1, 2, 5] + gaps = [0, 1, 2, 0, 1] + test_matr = np.zeros([20, 50]) + 1e-12 + length = 12 + line = _gen_ridge_line([0, 25], test_matr.shape, length, distances, gaps) + test_matr[line[0], line[1]] = 1 + max_distances = np.full(20, max(distances)) + identified_lines = _identify_ridge_lines(test_matr, + max_distances, + max(gaps) + 1) + assert len(identified_lines) == 1 + for iline in identified_lines: + xp_assert_equal(iline[0], np.sort(iline[0])) + for iline_, line_ in zip(identified_lines[0], line): + xp_assert_equal(iline_, line_, check_dtype=False) + + def test_single_bigdist(self): + distances = [0, 1, 2, 5] + gaps = [0, 1, 2, 4] + test_matr = np.zeros([20, 50]) + length = 12 + line = _gen_ridge_line([0, 25], test_matr.shape, length, distances, gaps) + test_matr[line[0], line[1]] = 1 + max_dist = 3 + max_distances = np.full(20, max_dist) + #This should get 2 lines, since the distance is too large + identified_lines = _identify_ridge_lines(test_matr, + max_distances, + max(gaps) + 1) + assert len(identified_lines) == 2 + + for iline in identified_lines: + xp_assert_equal(iline[0], np.sort(iline[0])) + adists = np.diff(iline[1]) + np.testing.assert_array_less(np.abs(adists), max_dist) + + agaps = np.diff(iline[0]) + np.testing.assert_array_less(np.abs(agaps), max(gaps) + 0.1) + + def test_single_biggap(self): + distances = [0, 1, 2, 5] + max_gap = 3 + gaps = [0, 4, 2, 1] + test_matr = np.zeros([20, 50]) + length = 12 + line = _gen_ridge_line([0, 25], test_matr.shape, length, distances, gaps) + test_matr[line[0], line[1]] = 1 + max_dist = 6 + max_distances = np.full(20, max_dist) + #This should get 2 lines, since the gap is too large + identified_lines = _identify_ridge_lines(test_matr, max_distances, max_gap) + assert len(identified_lines) == 2 + + for iline in identified_lines: + xp_assert_equal(iline[0], np.sort(iline[0])) + adists = np.diff(iline[1]) + np.testing.assert_array_less(np.abs(adists), max_dist) + + agaps = np.diff(iline[0]) + np.testing.assert_array_less(np.abs(agaps), max(gaps) + 0.1) + + def test_single_biggaps(self): + distances = [0] + max_gap = 1 + gaps = [3, 6] + test_matr = np.zeros([50, 50]) + length = 30 + line = _gen_ridge_line([0, 25], test_matr.shape, length, distances, gaps) + test_matr[line[0], line[1]] = 1 + max_dist = 1 + max_distances = np.full(50, max_dist) + #This should get 3 lines, since the gaps are too large + identified_lines = _identify_ridge_lines(test_matr, max_distances, max_gap) + assert len(identified_lines) == 3 + + for iline in identified_lines: + xp_assert_equal(iline[0], np.sort(iline[0])) + adists = np.diff(iline[1]) + np.testing.assert_array_less(np.abs(adists), max_dist) + + agaps = np.diff(iline[0]) + np.testing.assert_array_less(np.abs(agaps), max(gaps) + 0.1) + + def test_sorting(self): + test_matr = np.zeros([5, 10]) + test_matr[4, 5] = 1.0 + test_matr[3, 4] = 1.0 + test_matr[3, 6] = 1.0 + ridge_lines = _identify_ridge_lines(test_matr, np.full(5, 2), 1) + for line in ridge_lines: + xp_assert_equal(line[0], np.sort(line[0])) + + test_matr = np.zeros([5, 10]) + test_matr[0, 3] = 1.0 + test_matr[0, 7] = 1.0 + test_matr[1, 5] = 1.0 + ridge_lines = _identify_ridge_lines(test_matr, np.full(5, 3), 1) + for line in ridge_lines: + xp_assert_equal(line[0], np.sort(line[0])) + + test_matr = np.zeros([5, 15]) + test_matr[0, 2] = 1.0 + test_matr[0, 7] = 1.0 + test_matr[0, 12] = 1.0 + test_matr[1, 7] = 1.0 + ridge_lines = _identify_ridge_lines(test_matr, np.full(5, 6), 1) + for line in ridge_lines: + xp_assert_equal(line[0], np.sort(line[0])) + + test_matr = np.zeros([6, 12]) + test_matr[5, 5] = 1.0 + test_matr[4, 3] = 1.0 + test_matr[4, 7] = 1.0 + test_matr[3, 5] = 1.0 + test_matr[2, 5] = 1.0 + ridge_lines = _identify_ridge_lines(test_matr, np.full(6, 3), 1) + for line in ridge_lines: + xp_assert_equal(line[0], np.sort(line[0])) + + +class TestArgrel: + + def test_empty(self): + # Regression test for gh-2832. + # When there are no relative extrema, make sure that + # the number of empty arrays returned matches the + # dimension of the input. + + empty_array = np.array([], dtype=int) + + z1 = np.zeros(5) + + i = argrelmin(z1) + xp_assert_equal(len(i), 1) + xp_assert_equal(i[0], empty_array, check_dtype=False) + + z2 = np.zeros((3, 5)) + + row, col = argrelmin(z2, axis=0) + xp_assert_equal(row, empty_array, check_dtype=False) + xp_assert_equal(col, empty_array, check_dtype=False) + + row, col = argrelmin(z2, axis=1) + xp_assert_equal(row, empty_array, check_dtype=False) + xp_assert_equal(col, empty_array, check_dtype=False) + + def test_basic(self): + # Note: the docstrings for the argrel{min,max,extrema} functions + # do not give a guarantee of the order of the indices, so we'll + # sort them before testing. + + x = np.array([[1, 2, 2, 3, 2], + [2, 1, 2, 2, 3], + [3, 2, 1, 2, 2], + [2, 3, 2, 1, 2], + [1, 2, 3, 2, 1]]) + + row, col = argrelmax(x, axis=0) + order = np.argsort(row) + xp_assert_equal(row[order], [1, 2, 3], check_dtype=False) + xp_assert_equal(col[order], [4, 0, 1], check_dtype=False) + + row, col = argrelmax(x, axis=1) + order = np.argsort(row) + xp_assert_equal(row[order], [0, 3, 4], check_dtype=False) + xp_assert_equal(col[order], [3, 1, 2], check_dtype=False) + + row, col = argrelmin(x, axis=0) + order = np.argsort(row) + xp_assert_equal(row[order], [1, 2, 3], check_dtype=False) + xp_assert_equal(col[order], [1, 2, 3], check_dtype=False) + + row, col = argrelmin(x, axis=1) + order = np.argsort(row) + xp_assert_equal(row[order], [1, 2, 3], check_dtype=False) + xp_assert_equal(col[order], [1, 2, 3], check_dtype=False) + + def test_highorder(self): + order = 2 + sigmas = [1.0, 2.0, 10.0, 5.0, 15.0] + test_data, act_locs = _gen_gaussians_even(sigmas, 500) + test_data[act_locs + order] = test_data[act_locs]*0.99999 + test_data[act_locs - order] = test_data[act_locs]*0.99999 + rel_max_locs = argrelmax(test_data, order=order, mode='clip')[0] + + assert len(rel_max_locs) == len(act_locs) + assert (rel_max_locs == act_locs).all() + + def test_2d_gaussians(self): + sigmas = [1.0, 2.0, 10.0] + test_data, act_locs = _gen_gaussians_even(sigmas, 100) + rot_factor = 20 + rot_range = np.arange(0, len(test_data)) - rot_factor + test_data_2 = np.vstack([test_data, test_data[rot_range]]) + rel_max_rows, rel_max_cols = argrelmax(test_data_2, axis=1, order=1) + + for rw in range(0, test_data_2.shape[0]): + inds = (rel_max_rows == rw) + + assert len(rel_max_cols[inds]) == len(act_locs) + assert (act_locs == (rel_max_cols[inds] - rot_factor*rw)).all() + + +class TestPeakProminences: + + def test_empty(self): + """ + Test if an empty array is returned if no peaks are provided. + """ + out = peak_prominences([1, 2, 3], []) + for arr, dtype in zip(out, [np.float64, np.intp, np.intp]): + assert arr.size == 0 + assert arr.dtype == dtype + + out = peak_prominences([], []) + for arr, dtype in zip(out, [np.float64, np.intp, np.intp]): + assert arr.size == 0 + assert arr.dtype == dtype + + def test_basic(self): + """ + Test if height of prominences is correctly calculated in signal with + rising baseline (peak widths are 1 sample). + """ + # Prepare basic signal + x = np.array([-1, 1.2, 1.2, 1, 3.2, 1.3, 2.88, 2.1]) + peaks = np.array([1, 2, 4, 6]) + lbases = np.array([0, 0, 0, 5]) + rbases = np.array([3, 3, 5, 7]) + proms = x[peaks] - np.max([x[lbases], x[rbases]], axis=0) + # Test if calculation matches handcrafted result + out = peak_prominences(x, peaks) + xp_assert_equal(out[0], proms, check_dtype=False) + xp_assert_equal(out[1], lbases, check_dtype=False) + xp_assert_equal(out[2], rbases, check_dtype=False) + + def test_edge_cases(self): + """ + Test edge cases. + """ + # Peaks have same height, prominence and bases + x = [0, 2, 1, 2, 1, 2, 0] + peaks = [1, 3, 5] + proms, lbases, rbases = peak_prominences(x, peaks) + xp_assert_equal(proms, np.asarray([2.0, 2, 2]), check_dtype=False) + xp_assert_equal(lbases, [0, 0, 0], check_dtype=False) + xp_assert_equal(rbases, [6, 6, 6], check_dtype=False) + + # Peaks have same height & prominence but different bases + x = [0, 1, 0, 1, 0, 1, 0] + peaks = np.array([1, 3, 5]) + proms, lbases, rbases = peak_prominences(x, peaks) + xp_assert_equal(proms, np.asarray([1.0, 1, 1])) + xp_assert_equal(lbases, peaks - 1, check_dtype=False) + xp_assert_equal(rbases, peaks + 1, check_dtype=False) + + def test_non_contiguous(self): + """ + Test with non-C-contiguous input arrays. + """ + x = np.repeat([-9, 9, 9, 0, 3, 1], 2) + peaks = np.repeat([1, 2, 4], 2) + proms, lbases, rbases = peak_prominences(x[::2], peaks[::2]) + xp_assert_equal(proms, np.asarray([9.0, 9, 2])) + xp_assert_equal(lbases, [0, 0, 3], check_dtype=False) + xp_assert_equal(rbases, [3, 3, 5], check_dtype=False) + + def test_wlen(self): + """ + Test if wlen actually shrinks the evaluation range correctly. + """ + x = [0, 1, 2, 3, 1, 0, -1] + peak = [3] + # Test rounding behavior of wlen + proms = peak_prominences(x, peak) + for prom, val in zip(proms, [3.0, 0, 6]): + assert prom == val + + for wlen, i in [(8, 0), (7, 0), (6, 0), (5, 1), (3.2, 1), (3, 2), (1.1, 2)]: + proms = peak_prominences(x, peak, wlen) + for prom, val in zip(proms, [3. - i, 0 + i, 6 - i]): + assert prom == val + + def test_exceptions(self): + """ + Verify that exceptions and warnings are raised. + """ + # x with dimension > 1 + with raises(ValueError, match='1-D array'): + peak_prominences([[0, 1, 1, 0]], [1, 2]) + # peaks with dimension > 1 + with raises(ValueError, match='1-D array'): + peak_prominences([0, 1, 1, 0], [[1, 2]]) + # x with dimension < 1 + with raises(ValueError, match='1-D array'): + peak_prominences(3, [0,]) + + # empty x with supplied + with raises(ValueError, match='not a valid index'): + peak_prominences([], [0]) + # invalid indices with non-empty x + for p in [-100, -1, 3, 1000]: + with raises(ValueError, match='not a valid index'): + peak_prominences([1, 0, 2], [p]) + + # peaks is not cast-able to np.intp + with raises(TypeError, match='cannot safely cast'): + peak_prominences([0, 1, 1, 0], [1.1, 2.3]) + + # wlen < 3 + with raises(ValueError, match='wlen'): + peak_prominences(np.arange(10), [3, 5], wlen=1) + + def test_warnings(self): + """ + Verify that appropriate warnings are raised. + """ + msg = "some peaks have a prominence of 0" + for p in [0, 1, 2]: + with warns(PeakPropertyWarning, match=msg): + peak_prominences([1, 0, 2], [p,]) + with warns(PeakPropertyWarning, match=msg): + peak_prominences([0, 1, 1, 1, 0], [2], wlen=2) + + +class TestPeakWidths: + + def test_empty(self): + """ + Test if an empty array is returned if no peaks are provided. + """ + widths = peak_widths([], [])[0] + assert isinstance(widths, np.ndarray) + assert widths.size == 0 + widths = peak_widths([1, 2, 3], [])[0] + assert isinstance(widths, np.ndarray) + assert widths.size == 0 + out = peak_widths([], []) + for arr in out: + assert isinstance(arr, np.ndarray) + assert arr.size == 0 + + @pytest.mark.filterwarnings("ignore:some peaks have a width of 0") + def test_basic(self): + """ + Test a simple use case with easy to verify results at different relative + heights. + """ + x = np.array([1, 0, 1, 2, 1, 0, -1]) + prominence = 2 + for rel_height, width_true, lip_true, rip_true in [ + (0., 0., 3., 3.), # raises warning + (0.25, 1., 2.5, 3.5), + (0.5, 2., 2., 4.), + (0.75, 3., 1.5, 4.5), + (1., 4., 1., 5.), + (2., 5., 1., 6.), + (3., 5., 1., 6.) + ]: + width_calc, height, lip_calc, rip_calc = peak_widths( + x, [3], rel_height) + xp_assert_close(width_calc, np.asarray([width_true])) + xp_assert_close(height, np.asarray([2 - rel_height * prominence])) + xp_assert_close(lip_calc, np.asarray([lip_true])) + xp_assert_close(rip_calc, np.asarray([rip_true])) + + def test_non_contiguous(self): + """ + Test with non-C-contiguous input arrays. + """ + x = np.repeat([0, 100, 50], 4) + peaks = np.repeat([1], 3) + result = peak_widths(x[::4], peaks[::3]) + xp_assert_equal(result, + np.asarray([[0.75], [75], [0.75], [1.5]]) + ) + + def test_exceptions(self): + """ + Verify that argument validation works as intended. + """ + with raises(ValueError, match='1-D array'): + # x with dimension > 1 + peak_widths(np.zeros((3, 4)), np.ones(3)) + with raises(ValueError, match='1-D array'): + # x with dimension < 1 + peak_widths(3, [0]) + with raises(ValueError, match='1-D array'): + # peaks with dimension > 1 + peak_widths(np.arange(10), np.ones((3, 2), dtype=np.intp)) + with raises(ValueError, match='1-D array'): + # peaks with dimension < 1 + peak_widths(np.arange(10), 3) + with raises(ValueError, match='not a valid index'): + # peak pos exceeds x.size + peak_widths(np.arange(10), [8, 11]) + with raises(ValueError, match='not a valid index'): + # empty x with peaks supplied + peak_widths([], [1, 2]) + with raises(TypeError, match='cannot safely cast'): + # peak cannot be safely cast to intp + peak_widths(np.arange(10), [1.1, 2.3]) + with raises(ValueError, match='rel_height'): + # rel_height is < 0 + peak_widths([0, 1, 0, 1, 0], [1, 3], rel_height=-1) + with raises(TypeError, match='None'): + # prominence data contains None + peak_widths([1, 2, 1], [1], prominence_data=(None, None, None)) + + def test_warnings(self): + """ + Verify that appropriate warnings are raised. + """ + msg = "some peaks have a width of 0" + with warns(PeakPropertyWarning, match=msg): + # Case: rel_height is 0 + peak_widths([0, 1, 0], [1], rel_height=0) + with warns(PeakPropertyWarning, match=msg): + # Case: prominence is 0 and bases are identical + peak_widths( + [0, 1, 1, 1, 0], [2], + prominence_data=(np.array([0.], np.float64), + np.array([2], np.intp), + np.array([2], np.intp)) + ) + + def test_mismatching_prominence_data(self): + """Test with mismatching peak and / or prominence data.""" + x = [0, 1, 0] + peak = [1] + for i, (prominences, left_bases, right_bases) in enumerate([ + ((1.,), (-1,), (2,)), # left base not in x + ((1.,), (0,), (3,)), # right base not in x + ((1.,), (2,), (0,)), # swapped bases same as peak + ((1., 1.), (0, 0), (2, 2)), # array shapes don't match peaks + ((1., 1.), (0,), (2,)), # arrays with different shapes + ((1.,), (0, 0), (2,)), # arrays with different shapes + ((1.,), (0,), (2, 2)) # arrays with different shapes + ]): + # Make sure input is matches output of signal.peak_prominences + prominence_data = (np.array(prominences, dtype=np.float64), + np.array(left_bases, dtype=np.intp), + np.array(right_bases, dtype=np.intp)) + # Test for correct exception + if i < 3: + match = "prominence data is invalid for peak" + else: + match = "arrays in `prominence_data` must have the same shape" + with raises(ValueError, match=match): + peak_widths(x, peak, prominence_data=prominence_data) + + def test_no_zero_division_with_prominence_data(self): + # Test with special prominence data that cause zero division error + # regression test for gh-20720 + # note this is probably isn't a realistic input but reproduces + # the original issue + x = np.array([0, 1, 1]) + peak = np.array([1]) + prominence_data = (np.array([-1.0]), np.array([0]), np.array([2])) + result = peak_widths(x, peak, prominence_data=prominence_data) + result_expected = np.asarray([[0.5], [1.5], [1.], [1.5]]) + xp_assert_equal(result, result_expected) + + @pytest.mark.filterwarnings("ignore:some peaks have a width of 0") + def test_intersection_rules(self): + """Test if x == eval_height counts as an intersection.""" + # Flatt peak with two possible intersection points if evaluated at 1 + x = [0, 1, 2, 1, 3, 3, 3, 1, 2, 1, 0] + # relative height is 0 -> width is 0 as well, raises warning + xp_assert_close(peak_widths(x, peaks=[5], rel_height=0), + [(0.,), (3.,), (5.,), (5.,)]) + # width_height == x counts as intersection -> nearest 1 is chosen + xp_assert_close(peak_widths(x, peaks=[5], rel_height=2/3), + [(4.,), (1.,), (3.,), (7.,)]) + + +def test_unpack_condition_args(): + """ + Verify parsing of condition arguments for `scipy.signal.find_peaks` function. + """ + x = np.arange(10) + amin_true = x + amax_true = amin_true + 10 + peaks = amin_true[1::2] + + # Test unpacking with None or interval + assert (None, None) == _unpack_condition_args((None, None), x, peaks) + assert (1, None) == _unpack_condition_args(1, x, peaks) + assert (1, None) == _unpack_condition_args((1, None), x, peaks) + assert (None, 2) == _unpack_condition_args((None, 2), x, peaks) + assert (3., 4.5) == _unpack_condition_args((3., 4.5), x, peaks) + + # Test if borders are correctly reduced with `peaks` + amin_calc, amax_calc = _unpack_condition_args((amin_true, amax_true), x, peaks) + xp_assert_equal(amin_calc, amin_true[peaks]) + xp_assert_equal(amax_calc, amax_true[peaks]) + + # Test raises if array borders don't match x + with raises(ValueError, match="array size of lower"): + _unpack_condition_args(amin_true, np.arange(11), peaks) + with raises(ValueError, match="array size of upper"): + _unpack_condition_args((None, amin_true), np.arange(11), peaks) + + +class TestFindPeaks: + + # Keys of optionally returned properties + property_keys = {'peak_heights', 'left_thresholds', 'right_thresholds', + 'prominences', 'left_bases', 'right_bases', 'widths', + 'width_heights', 'left_ips', 'right_ips'} + + def test_constant(self): + """ + Test behavior for signal without local maxima. + """ + open_interval = (None, None) + peaks, props = find_peaks(np.ones(10), + height=open_interval, threshold=open_interval, + prominence=open_interval, width=open_interval) + assert peaks.size == 0 + for key in self.property_keys: + assert props[key].size == 0 + + def test_plateau_size(self): + """ + Test plateau size condition for peaks. + """ + # Prepare signal with peaks with peak_height == plateau_size + plateau_sizes = np.array([1, 2, 3, 4, 8, 20, 111]) + x = np.zeros(plateau_sizes.size * 2 + 1) + x[1::2] = plateau_sizes + repeats = np.ones(x.size, dtype=int) + repeats[1::2] = x[1::2] + x = np.repeat(x, repeats) + + # Test full output + peaks, props = find_peaks(x, plateau_size=(None, None)) + xp_assert_equal(peaks, [1, 3, 7, 11, 18, 33, 100], check_dtype=False) + xp_assert_equal(props["plateau_sizes"], plateau_sizes, check_dtype=False) + xp_assert_equal(props["left_edges"], peaks - (plateau_sizes - 1) // 2, + check_dtype=False) + xp_assert_equal(props["right_edges"], peaks + plateau_sizes // 2, + check_dtype=False) + + # Test conditions + xp_assert_equal(find_peaks(x, plateau_size=4)[0], [11, 18, 33, 100], + check_dtype=False) + xp_assert_equal(find_peaks(x, plateau_size=(None, 3.5))[0], [1, 3, 7], + check_dtype=False) + xp_assert_equal(find_peaks(x, plateau_size=(5, 50))[0], [18, 33], + check_dtype=False) + + def test_height_condition(self): + """ + Test height condition for peaks. + """ + x = (0., 1/3, 0., 2.5, 0, 4., 0) + peaks, props = find_peaks(x, height=(None, None)) + xp_assert_equal(peaks, np.array([1, 3, 5]), check_dtype=False) + xp_assert_equal(props['peak_heights'], np.array([1/3, 2.5, 4.]), + check_dtype=False) + xp_assert_equal(find_peaks(x, height=0.5)[0], np.array([3, 5]), + check_dtype=False) + xp_assert_equal(find_peaks(x, height=(None, 3))[0], np.array([1, 3]), + check_dtype=False) + xp_assert_equal(find_peaks(x, height=(2, 3))[0], np.array([3]), + check_dtype=False) + + def test_threshold_condition(self): + """ + Test threshold condition for peaks. + """ + x = (0, 2, 1, 4, -1) + peaks, props = find_peaks(x, threshold=(None, None)) + xp_assert_equal(peaks, np.array([1, 3]), check_dtype=False) + xp_assert_equal(props['left_thresholds'], np.array([2.0, 3.0])) + xp_assert_equal(props['right_thresholds'], np.array([1.0, 5.0])) + xp_assert_equal(find_peaks(x, threshold=2)[0], np.array([3]), + check_dtype=False) + xp_assert_equal(find_peaks(x, threshold=3.5)[0], np.array([], dtype=int), + check_dtype=False) + xp_assert_equal(find_peaks(x, threshold=(None, 5))[0], np.array([1, 3]), + check_dtype=False) + xp_assert_equal(find_peaks(x, threshold=(None, 4))[0], np.array([1]), + check_dtype=False) + xp_assert_equal(find_peaks(x, threshold=(2, 4))[0], np.array([], dtype=int), + check_dtype=False) + + def test_distance_condition(self): + """ + Test distance condition for peaks. + """ + # Peaks of different height with constant distance 3 + peaks_all = np.arange(1, 21, 3) + x = np.zeros(21) + x[peaks_all] += np.linspace(1, 2, peaks_all.size) + + # Test if peaks with "minimal" distance are still selected (distance = 3) + xp_assert_equal(find_peaks(x, distance=3)[0], peaks_all, check_dtype=False) + + # Select every second peak (distance > 3) + peaks_subset = find_peaks(x, distance=3.0001)[0] + # Test if peaks_subset is subset of peaks_all + assert np.setdiff1d(peaks_subset, peaks_all, assume_unique=True).size == 0 + + # Test if every second peak was removed + dfs = np.diff(peaks_subset) + xp_assert_equal(dfs, 6*np.ones_like(dfs)) + + # Test priority of peak removal + x = [-2, 1, -1, 0, -3] + peaks_subset = find_peaks(x, distance=10)[0] # use distance > x size + assert peaks_subset.size == 1 and peaks_subset[0] == 1 + + def test_prominence_condition(self): + """ + Test prominence condition for peaks. + """ + x = np.linspace(0, 10, 100) + peaks_true = np.arange(1, 99, 2) + offset = np.linspace(1, 10, peaks_true.size) + x[peaks_true] += offset + prominences = x[peaks_true] - x[peaks_true + 1] + interval = (3, 9) + keep = np.nonzero( + (interval[0] <= prominences) & (prominences <= interval[1])) + + peaks_calc, properties = find_peaks(x, prominence=interval) + xp_assert_equal(peaks_calc, peaks_true[keep], check_dtype=False) + xp_assert_equal(properties['prominences'], prominences[keep], check_dtype=False) + xp_assert_equal(properties['left_bases'], + np.zeros_like(properties['left_bases'])) + xp_assert_equal(properties['right_bases'], peaks_true[keep] + 1, + check_dtype=False) + + def test_width_condition(self): + """ + Test width condition for peaks. + """ + x = np.array([1, 0, 1, 2, 1, 0, -1, 4, 0]) + peaks, props = find_peaks(x, width=(None, 2), rel_height=0.75) + assert peaks.size == 1 + xp_assert_equal(peaks, 7*np.ones_like(peaks)) + xp_assert_close(props['widths'], np.asarray([1.35])) + xp_assert_close(props['width_heights'], np.asarray([1.])) + xp_assert_close(props['left_ips'], np.asarray([6.4])) + xp_assert_close(props['right_ips'], np.asarray([7.75])) + + def test_properties(self): + """ + Test returned properties. + """ + open_interval = (None, None) + x = [0, 1, 0, 2, 1.5, 0, 3, 0, 5, 9] + peaks, props = find_peaks(x, + height=open_interval, threshold=open_interval, + prominence=open_interval, width=open_interval) + assert len(props) == len(self.property_keys) + for key in self.property_keys: + assert peaks.size == props[key].size + + def test_raises(self): + """ + Test exceptions raised by function. + """ + with raises(ValueError, match="1-D array"): + find_peaks(np.array(1)) + with raises(ValueError, match="1-D array"): + find_peaks(np.ones((2, 2))) + with raises(ValueError, match="distance"): + find_peaks(np.arange(10), distance=-1) + + @pytest.mark.filterwarnings("ignore:some peaks have a prominence of 0", + "ignore:some peaks have a width of 0") + def test_wlen_smaller_plateau(self): + """ + Test behavior of prominence and width calculation if the given window + length is smaller than a peak's plateau size. + + Regression test for gh-9110. + """ + peaks, props = find_peaks([0, 1, 1, 1, 0], prominence=(None, None), + width=(None, None), wlen=2) + xp_assert_equal(peaks, 2 * np.ones_like(peaks)) + xp_assert_equal(props["prominences"], np.zeros_like(props["prominences"])) + xp_assert_equal(props["widths"], np.zeros_like(props["widths"])) + xp_assert_equal(props["width_heights"], np.ones_like(props["width_heights"])) + for key in ("left_bases", "right_bases", "left_ips", "right_ips"): + xp_assert_equal(props[key], peaks, check_dtype=False) + + @pytest.mark.parametrize("kwargs", [ + {}, + {"distance": 3.0}, + {"prominence": (None, None)}, + {"width": (None, 2)}, + + ]) + def test_readonly_array(self, kwargs): + """ + Test readonly arrays are accepted. + """ + x = np.linspace(0, 10, 15) + x_readonly = x.copy() + x_readonly.flags.writeable = False + + peaks, _ = find_peaks(x) + peaks_readonly, _ = find_peaks(x_readonly, **kwargs) + + xp_assert_close(peaks, peaks_readonly) + + +class TestFindPeaksCwt: + + def test_find_peaks_exact(self): + """ + Generate a series of gaussians and attempt to find the peak locations. + """ + sigmas = [5.0, 3.0, 10.0, 20.0, 10.0, 50.0] + num_points = 500 + test_data, act_locs = _gen_gaussians_even(sigmas, num_points) + widths = np.arange(0.1, max(sigmas)) + found_locs = find_peaks_cwt(test_data, widths, gap_thresh=2, min_snr=0, + min_length=None) + xp_assert_equal(found_locs, act_locs, + check_dtype=False, + err_msg="Found maximum locations did not equal those expected" + ) + + def test_find_peaks_withnoise(self): + """ + Verify that peak locations are (approximately) found + for a series of gaussians with added noise. + """ + sigmas = [5.0, 3.0, 10.0, 20.0, 10.0, 50.0] + num_points = 500 + test_data, act_locs = _gen_gaussians_even(sigmas, num_points) + widths = np.arange(0.1, max(sigmas)) + noise_amp = 0.07 + rng = np.random.default_rng(18181911) + test_data += (rng.random(num_points) - 0.5)*(2*noise_amp) + found_locs = find_peaks_cwt(test_data, widths, min_length=15, + gap_thresh=1, min_snr=noise_amp / 5) + + err_msg ='Different number of peaks found than expected' + assert len(found_locs) == len(act_locs), err_msg + diffs = np.abs(found_locs - act_locs) + max_diffs = np.array(sigmas) / 5 + np.testing.assert_array_less(diffs, max_diffs, 'Maximum location differed' + + f'by more than {max_diffs}') + + def test_find_peaks_nopeak(self): + """ + Verify that no peak is found in + data that's just noise. + """ + noise_amp = 1.0 + num_points = 100 + rng = np.random.RandomState(181819141) + test_data = (rng.rand(num_points) - 0.5)*(2*noise_amp) + widths = np.arange(10, 50) + found_locs = find_peaks_cwt(test_data, widths, min_snr=5, noise_perc=30) + assert len(found_locs) == 0 + + def test_find_peaks_with_non_default_wavelets(self): + x = gaussian(200, 2) + widths = np.array([1, 2, 3, 4]) + a = find_peaks_cwt(x, widths, wavelet=gaussian) + + xp_assert_equal(a, np.asarray([100]), check_dtype=False) + + def test_find_peaks_window_size(self): + """ + Verify that window_size is passed correctly to private function and + affects the result. + """ + sigmas = [2.0, 2.0] + num_points = 1000 + test_data, act_locs = _gen_gaussians_even(sigmas, num_points) + widths = np.arange(0.1, max(sigmas), 0.2) + noise_amp = 0.05 + rng = np.random.RandomState(18181911) + test_data += (rng.rand(num_points) - 0.5)*(2*noise_amp) + + # Possibly contrived negative region to throw off peak finding + # when window_size is too large + test_data[250:320] -= 1 + + found_locs = find_peaks_cwt(test_data, widths, gap_thresh=2, min_snr=3, + min_length=None, window_size=None) + with pytest.raises(AssertionError): + assert found_locs.size == act_locs.size + + found_locs = find_peaks_cwt(test_data, widths, gap_thresh=2, min_snr=3, + min_length=None, window_size=20) + assert found_locs.size == act_locs.size + + def test_find_peaks_with_one_width(self): + """ + Verify that the `width` argument + in `find_peaks_cwt` can be a float + """ + xs = np.arange(0, np.pi, 0.05) + test_data = np.sin(xs) + widths = 1 + found_locs = find_peaks_cwt(test_data, widths) + + np.testing.assert_equal(found_locs, 32) diff --git a/venv/lib/python3.12/site-packages/scipy/signal/tests/test_result_type.py b/venv/lib/python3.12/site-packages/scipy/signal/tests/test_result_type.py new file mode 100644 index 0000000000000000000000000000000000000000..a2cadd325a7e36c877df8532ba957712831c2dad --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/tests/test_result_type.py @@ -0,0 +1,51 @@ +# Regressions tests on result types of some signal functions + +import numpy as np + +from scipy.signal import (decimate, + lfilter_zi, + lfiltic, + sos2tf, + sosfilt_zi) + + +def test_decimate(): + ones_f32 = np.ones(32, dtype=np.float32) + assert decimate(ones_f32, 2).dtype == np.float32 + + ones_i64 = np.ones(32, dtype=np.int64) + assert decimate(ones_i64, 2).dtype == np.float64 + + +def test_lfilter_zi(): + b_f32 = np.array([1, 2, 3], dtype=np.float32) + a_f32 = np.array([4, 5, 6], dtype=np.float32) + assert lfilter_zi(b_f32, a_f32).dtype == np.float32 + + +def test_lfiltic(): + # this would return f32 when given a mix of f32 / f64 args + b_f32 = np.array([1, 2, 3], dtype=np.float32) + a_f32 = np.array([4, 5, 6], dtype=np.float32) + x_f32 = np.ones(32, dtype=np.float32) + + b_f64 = b_f32.astype(np.float64) + a_f64 = a_f32.astype(np.float64) + x_f64 = x_f32.astype(np.float64) + + assert lfiltic(b_f64, a_f32, x_f32).dtype == np.float64 + assert lfiltic(b_f32, a_f64, x_f32).dtype == np.float64 + assert lfiltic(b_f32, a_f32, x_f64).dtype == np.float64 + assert lfiltic(b_f32, a_f32, x_f32, x_f64).dtype == np.float64 + + +def test_sos2tf(): + sos_f32 = np.array([[4, 5, 6, 1, 2, 3]], dtype=np.float32) + b, a = sos2tf(sos_f32) + assert b.dtype == np.float32 + assert a.dtype == np.float32 + + +def test_sosfilt_zi(): + sos_f32 = np.array([[4, 5, 6, 1, 2, 3]], dtype=np.float32) + assert sosfilt_zi(sos_f32).dtype == np.float32 diff --git a/venv/lib/python3.12/site-packages/scipy/signal/tests/test_savitzky_golay.py b/venv/lib/python3.12/site-packages/scipy/signal/tests/test_savitzky_golay.py new file mode 100644 index 0000000000000000000000000000000000000000..30096ba9fc771efb378ff755cfd96bb35d9d2070 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/tests/test_savitzky_golay.py @@ -0,0 +1,397 @@ +import pytest +import numpy as np + +from scipy._lib._array_api import ( + xp_assert_close, xp_assert_equal, xp_swapaxes, is_torch, make_xp_test_case, + _xp_copy_to_numpy +) + +from scipy.ndimage import convolve1d # type: ignore[attr-defined] + +from scipy.signal import savgol_coeffs, savgol_filter +from scipy.signal._savitzky_golay import _polyder + +skip_xp_backends = pytest.mark.skip_xp_backends +xfail_xp_backends = pytest.mark.xfail_xp_backends + +pytestmark = pytest.mark.skip_xp_backends( + "dask.array", reason="linalg.lstsq is missing rcond" +) + + +def check_polyder(p, m, expected, xp): + dp = _polyder(p, m, xp=xp) + xp_assert_equal(dp, expected) + + +@pytest.mark.uses_xp_capabilities(False, reason="private") +def test_polyder(xp): + cases = [ + ([5], 0, [5]), + ([5], 1, [0]), + ([3, 2, 1], 0, [3, 2, 1]), + ([3, 2, 1], 1, [6, 2]), + ([3, 2, 1], 2, [6]), + ([3, 2, 1], 3, [0]), + ([[3, 2, 1], [5, 6, 7]], 0, [[3, 2, 1], [5, 6, 7]]), + ([[3, 2, 1], [5, 6, 7]], 1, [[6, 2], [10, 6]]), + ([[3, 2, 1], [5, 6, 7]], 2, [[6], [10]]), + ([[3, 2, 1], [5, 6, 7]], 3, [[0], [0]]), + ] + for p, m, expected in cases: + pp = xp.asarray(p) + ee = xp.asarray(expected) + check_polyder( + pp.T if pp.ndim == 2 else pp, + m, + ee.T if ee.ndim ==2 else ee, + xp + ) + + +#-------------------------------------------------------------------- +# savgol_coeffs tests +#-------------------------------------------------------------------- + +def alt_sg_coeffs(window_length, polyorder, pos, xp): + """This is an alternative implementation of the SG coefficients. + + It uses numpy.polyfit and numpy.polyval. The results should be + equivalent to those of savgol_coeffs(), but this implementation + is slower. + + window_length should be odd. + + """ + if pos is None: + pos = window_length // 2 + t = np.arange(window_length) + unit = (t == pos).astype(int) + h = np.polyval(np.polyfit(t, unit, polyorder), t) + return xp.asarray(h) + + +@make_xp_test_case(savgol_coeffs) +def test_sg_coeffs_trivial(xp): + # Test a trivial case of savgol_coeffs: polyorder = window_length - 1 + h = savgol_coeffs(1, 0, xp=xp) + xp_assert_close(h, xp.asarray([1.0], dtype=xp.float64)) + + h = savgol_coeffs(3, 2, xp=xp) + xp_assert_close(h, xp.asarray([0.0, 1, 0], dtype=xp.float64), atol=1e-10) + + h = savgol_coeffs(5, 4, xp=xp) + xp_assert_close(h, xp.asarray([0.0, 0, 1, 0, 0], dtype=xp.float64), atol=1e-10) + + h = savgol_coeffs(5, 4, pos=1, xp=xp) + xp_assert_close(h, xp.asarray([0.0, 0, 0, 1, 0], dtype=xp.float64), atol=1e-10) + + h = savgol_coeffs(5, 4, pos=1, use='dot', xp=xp) + xp_assert_close(h, xp.asarray([0.0, 1, 0, 0, 0], dtype=xp.float64), atol=1e-10) + + +def compare_coeffs_to_alt(window_length, order, xp): + # For the given window_length and order, compare the results + # of savgol_coeffs and alt_sg_coeffs for pos from 0 to window_length - 1. + # Also include pos=None. + for pos in [None] + list(range(window_length)): + h1 = savgol_coeffs(window_length, order, pos=pos, use='dot', xp=xp) + h2 = alt_sg_coeffs(window_length, order, pos=pos, xp=xp) + xp_assert_close( + h1, h2, atol=2e-10, + err_msg=f"window_length = {window_length}, order = {order}, pos = {pos}" + ) + + +@make_xp_test_case(savgol_coeffs) +def test_sg_coeffs_compare(xp): + # Compare savgol_coeffs() to alt_sg_coeffs(). + for window_length in range(1, 8, 2): + for order in range(window_length): + compare_coeffs_to_alt(window_length, order, xp=xp) + + +@make_xp_test_case(savgol_coeffs) +def test_sg_coeffs_exact(xp): + polyorder = 4 + window_length = 9 + halflen = window_length // 2 + + x = xp.linspace(0, 21, 43) + delta = x[1] - x[0] + + # The data is a cubic polynomial. We'll use an order 4 + # SG filter, so the filtered values should equal the input data + # (except within half window_length of the edges). + y = 0.5 * x ** 3 - x + h = savgol_coeffs(window_length, polyorder, xp=xp) + y0 = xp.asarray(convolve1d(_xp_copy_to_numpy(y), _xp_copy_to_numpy(h))) + xp_assert_close(y0[halflen:-halflen], y[halflen:-halflen]) + + # Check the same input, but use deriv=1. dy is the exact result. + dy = 1.5 * x ** 2 - 1 + h = savgol_coeffs(window_length, polyorder, deriv=1, delta=delta, xp=xp) + y1 = xp.asarray(convolve1d(_xp_copy_to_numpy(y), _xp_copy_to_numpy(h))) + xp_assert_close(y1[halflen:-halflen], dy[halflen:-halflen]) + + # Check the same input, but use deriv=2. d2y is the exact result. + d2y = 3.0 * x + h = savgol_coeffs(window_length, polyorder, deriv=2, delta=delta, xp=xp) + y2 = xp.asarray(convolve1d(_xp_copy_to_numpy(y), _xp_copy_to_numpy(h))) + xp_assert_close(y2[halflen:-halflen], d2y[halflen:-halflen]) + + +@make_xp_test_case(savgol_coeffs) +def test_sg_coeffs_deriv(xp): + # The data in `x` is a sampled parabola, so using savgol_coeffs with an + # order 2 or higher polynomial should give exact results. + i = xp.asarray([-2.0, 0.0, 2.0, 4.0, 6.0], dtype=xp.float64) + x = i ** 2 / 4 + dx = i / 2 + d2x = xp.full_like(i, 0.5) + for pos in range(x.shape[0]): + coeffs0 = savgol_coeffs(5, 3, pos=pos, delta=2.0, use='dot', xp=xp) + xp_assert_close(coeffs0 @ x, x[pos], atol=1e-10) + coeffs1 = savgol_coeffs(5, 3, pos=pos, delta=2.0, use='dot', deriv=1, xp=xp) + xp_assert_close(coeffs1 @ x, dx[pos], atol=1e-10) + coeffs2 = savgol_coeffs(5, 3, pos=pos, delta=2.0, use='dot', deriv=2, xp=xp) + xp_assert_close(coeffs2 @ x , d2x[pos], atol=1e-10) + + +@make_xp_test_case(savgol_coeffs) +def test_sg_coeffs_deriv_gt_polyorder(xp): + """ + If deriv > polyorder, the coefficients should be all 0. + This is a regression test for a bug where, e.g., + savgol_coeffs(5, polyorder=1, deriv=2) + raised an error. + """ + coeffs = savgol_coeffs(5, polyorder=1, deriv=2, xp=xp) + xp_assert_equal(coeffs, xp.zeros(5, dtype=xp.float64)) + coeffs = savgol_coeffs(7, polyorder=4, deriv=6, xp=xp) + xp_assert_equal(coeffs, xp.zeros(7, dtype=xp.float64)) + + +@xfail_xp_backends( + "torch", reason="torch loses precision (worse with f32 default dtype)" +) +@make_xp_test_case(savgol_coeffs) +def test_sg_coeffs_large(xp): + # Test that for large values of window_length and polyorder the array of + # coefficients returned is symmetric. The aim is to ensure that + # no potential numeric overflow occurs. + coeffs0 = savgol_coeffs(31, 9, xp=xp) + xp_assert_close( + coeffs0, xp.flip(coeffs0), atol=5e-4 if is_torch(xp) else 1.5e-6 + ) + + coeffs1 = savgol_coeffs(31, 9, deriv=1, xp=xp) + xp_assert_close( + coeffs1, -xp.flip(coeffs1), atol=1.2e-2 if is_torch(xp) else 1.5e-6 + ) + +# -------------------------------------------------------------------- +# savgol_coeffs tests for even window length +# -------------------------------------------------------------------- + +@make_xp_test_case(savgol_coeffs) +def test_sg_coeffs_even_window_length(xp): + # Simple case - deriv=0, polyorder=0, 1 + window_lengths = [4, 6, 8, 10, 12, 14, 16] + for length in window_lengths: + h_p_d = savgol_coeffs(length, 0, 0, xp=xp) + xp_assert_close(h_p_d, xp.ones_like(h_p_d) / length) + + # Verify with closed forms + # deriv=1, polyorder=1, 2 + def h_p_d_closed_form_1(k, m): + return 6*(k - 0.5)/((2*m + 1)*m*(2*m - 1)) + + # deriv=2, polyorder=2 + def h_p_d_closed_form_2(k, m): + numer = 15*(-4*m**2 + 1 + 12*(k - 0.5)**2) + denom = 4*(2*m + 1)*(m + 1)*m*(m - 1)*(2*m - 1) + return numer/denom + + for length in window_lengths: + m = length//2 + expected_output = [h_p_d_closed_form_1(k, m) + for k in range(-m + 1, m + 1)][::-1] + expected_output = xp.asarray(expected_output, dtype=xp.float64) + actual_output = savgol_coeffs(length, 1, 1, xp=xp) + xp_assert_close(actual_output, expected_output) + actual_output = savgol_coeffs(length, 2, 1, xp=xp) + xp_assert_close(actual_output, expected_output) + + expected_output = [h_p_d_closed_form_2(k, m) + for k in range(-m + 1, m + 1)][::-1] + expected_output = xp.asarray(expected_output, dtype=xp.float64) + actual_output = savgol_coeffs(length, 2, 2, xp=xp) + xp_assert_close(actual_output, expected_output) + actual_output = savgol_coeffs(length, 3, 2, xp=xp) + xp_assert_close(actual_output, expected_output) + +#-------------------------------------------------------------------- +# savgol_filter tests +#-------------------------------------------------------------------- + +@make_xp_test_case(savgol_filter) +def test_sg_filter_trivial(xp): + """ Test some trivial edge cases for savgol_filter().""" + x = xp.asarray([1.0]) + y = savgol_filter(x, 1, 0) + xp_assert_equal(y, xp.asarray([1.0])) + + # Input is a single value. With a window length of 3 and polyorder 1, + # the value in y is from the straight-line fit of (-1,0), (0,3) and + # (1, 0) at 0. This is just the average of the three values, hence 1.0. + x = xp.asarray([3.0]) + y = savgol_filter(x, 3, 1, mode='constant') + xp_assert_close(y, xp.asarray([1.0]), atol=1.5e-15) + + x = xp.asarray([3.0]) + y = savgol_filter(x, 3, 1, mode='nearest') + xp_assert_close(y, xp.asarray([3.0]), atol=1.5e-15) + + x = xp.asarray([1.0] * 3) + y = savgol_filter(x, 3, 1, mode='wrap') + xp_assert_close(y, xp.asarray([1.0, 1.0, 1.0]), atol=1.5e-15) + + +@make_xp_test_case(savgol_filter) +def test_sg_filter_basic(xp): + # Some basic test cases for savgol_filter(). + x = xp.asarray([1.0, 2.0, 1.0]) + y = savgol_filter(x, 3, 1, mode='constant') + xp_assert_close(y, xp.asarray([1.0, 4.0 / 3, 1.0])) + + y = savgol_filter(x, 3, 1, mode='mirror') + xp_assert_close(y, xp.asarray([5.0 / 3, 4.0 / 3, 5.0 / 3])) + + y = savgol_filter(x, 3, 1, mode='wrap') + xp_assert_close(y, xp.asarray([4.0 / 3, 4.0 / 3, 4.0 / 3])) + + +@make_xp_test_case(savgol_filter) +def test_sg_filter_2d(xp): + x = xp.asarray([[1.0, 2.0, 1.0], + [2.0, 4.0, 2.0]]) + expected = xp.asarray([[1.0, 4.0 / 3, 1.0], + [2.0, 8.0 / 3, 2.0]]) + y = savgol_filter(x, 3, 1, mode='constant') + xp_assert_close(y, expected) + + y = savgol_filter(x.T, 3, 1, mode='constant', axis=0) + xp_assert_close(y, expected.T) + + +@make_xp_test_case(savgol_filter) +def test_sg_filter_interp_edges(xp): + # Another test with low degree polynomial data, for which we can easily + # give the exact results. In this test, we use mode='interp', so + # savgol_filter should match the exact solution for the entire data set, + # including the edges. + t = xp.linspace(-5, 5, 21, dtype=xp.float64) + delta = t[1] - t[0] + # Polynomial test data. + x = xp.stack([t, + 3 * t ** 2, + t ** 3 - t]) + dx = xp.stack([xp.ones_like(t), + 6 * t, + 3 * t ** 2 - 1.0]) + d2x = xp.stack([xp.zeros_like(t), + xp.full_like(t, 6), + 6 * t]) + + window_length = 7 + + y = savgol_filter(x, window_length, 3, axis=-1, mode='interp') + xp_assert_close(y, x, atol=1e-12) + + y1 = savgol_filter(x, window_length, 3, axis=-1, mode='interp', + deriv=1, delta=delta) + xp_assert_close(y1, dx, atol=1e-12) + + y2 = savgol_filter(x, window_length, 3, axis=-1, mode='interp', + deriv=2, delta=delta) + xp_assert_close(y2, d2x, atol=1e-12) + + # Transpose everything, and test again with axis=0. + + x = x.T + dx = dx.T + d2x = d2x.T + + y = savgol_filter(x, window_length, 3, axis=0, mode='interp') + xp_assert_close(y, x, atol=1e-12) + + y1 = savgol_filter(x, window_length, 3, axis=0, mode='interp', + deriv=1, delta=delta) + xp_assert_close(y1, dx, atol=1e-12) + + y2 = savgol_filter(x, window_length, 3, axis=0, mode='interp', + deriv=2, delta=delta) + xp_assert_close(y2, d2x, atol=1e-12) + + +@make_xp_test_case(savgol_filter) +def test_sg_filter_interp_edges_3d(xp): + # Test mode='interp' with a 3-D array. + t = xp.linspace(-5, 5, 21) + delta = t[1] - t[0] + x1 = xp.stack([t, -t]) + x2 = xp.stack([t ** 2, 3 * t ** 2 + 5]) + x3 = xp.stack([t ** 3, 2 * t ** 3 + t ** 2 - 0.5 * t]) + dx1 = xp.stack([xp.ones_like(t), -xp.ones_like(t)]) + dx2 = xp.stack([2 * t, 6 * t]) + dx3 = xp.stack([3 * t ** 2, 6 * t ** 2 + 2 * t - 0.5]) + + # z has shape (3, 2, 21) + z = xp.stack([x1, x2, x3]) + dz = xp.stack([dx1, dx2, dx3]) + + y = savgol_filter(z, 7, 3, axis=-1, mode='interp', delta=delta) + xp_assert_close(y, z, atol=1e-10) + + dy = savgol_filter(z, 7, 3, axis=-1, mode='interp', deriv=1, delta=delta) + xp_assert_close(dy, dz, atol=1e-10) + + # z has shape (3, 21, 2) + z = xp.stack([x1.T, x2.T, x3.T]) + dz = xp.stack([dx1.T, dx2.T, dx3.T]) + + y = savgol_filter(z, 7, 3, axis=1, mode='interp', delta=delta) + xp_assert_close(y, z, atol=1e-10) + + dy = savgol_filter(z, 7, 3, axis=1, mode='interp', deriv=1, delta=delta) + xp_assert_close(dy, dz, atol=1e-10) + + # z has shape (21, 3, 2) + z = xp.asarray(xp_swapaxes(z, 0, 1, xp=xp), copy=True) + dz = xp.asarray(xp_swapaxes(dz, 0, 1, xp=xp), copy=True) + + y = savgol_filter(z, 7, 3, axis=0, mode='interp', delta=delta) + xp_assert_close(y, z, atol=1e-10) + + dy = savgol_filter(z, 7, 3, axis=0, mode='interp', deriv=1, delta=delta) + xp_assert_close(dy, dz, atol=1e-10) + + +@make_xp_test_case(savgol_filter) +def test_sg_filter_valid_window_length_3d(xp): + """Tests that the window_length check is using the correct axis.""" + + x = xp.ones((10, 20, 30)) + + savgol_filter(x, window_length=29, polyorder=3, mode='interp') + + with pytest.raises(ValueError, match='window_length must be less than'): + # window_length is more than x.shape[-1]. + savgol_filter(x, window_length=31, polyorder=3, mode='interp') + + savgol_filter(x, window_length=9, polyorder=3, axis=0, mode='interp') + + with pytest.raises(ValueError, match='window_length must be less than'): + # window_length is more than x.shape[0]. + savgol_filter(x, window_length=11, polyorder=3, axis=0, mode='interp') diff --git a/venv/lib/python3.12/site-packages/scipy/signal/tests/test_short_time_fft.py b/venv/lib/python3.12/site-packages/scipy/signal/tests/test_short_time_fft.py new file mode 100644 index 0000000000000000000000000000000000000000..063f44f66d896e7c714d487abcc32654ee11d4b7 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/tests/test_short_time_fft.py @@ -0,0 +1,1116 @@ +"""Unit tests for module `_short_time_fft`. + +This file's structure loosely groups the tests into the following sequential +categories: + +1. Test function `_calc_dual_canonical_window`. +2. Test for invalid parameters and exceptions in `ShortTimeFFT` (until the + `test_from_window` function). +3. Test algorithmic properties of STFT/ISTFT. Some tests were ported from + ``test_spectral.py``. + +Notes +----- +* Mypy 0.990 does interpret the line:: + + from scipy.stats import norm as normal_distribution + + incorrectly (but the code works), hence a ``type: ignore`` was appended. +""" +import math +from itertools import product +from types import GenericAlias +from typing import cast, get_args, Literal + +import numpy as np +import pytest +from scipy._lib._array_api import xp_assert_close, xp_assert_equal +from scipy.fft import fftshift +from scipy.stats import norm as normal_distribution # type: ignore +from scipy.signal import check_COLA, get_window, welch, stft, istft, spectrogram + +from scipy.signal._short_time_fft import FFT_MODE_TYPE, \ + _calc_dual_canonical_window, closest_STFT_dual_window, ShortTimeFFT, PAD_TYPE +from scipy.signal.windows import blackman, gaussian, hamming, nuttall, triang + + +def test__calc_dual_canonical_window_roundtrip(): + """Test dual window calculation with a round trip to verify duality. + + Note that this works only for canonical window pairs (having minimal + energy) like a Gaussian. + + The window is the same as in the example of `from ShortTimeFFT.from_dual`. + """ + win = gaussian(51, std=10, sym=True) + d_win = _calc_dual_canonical_window(win, 10) + win2 = _calc_dual_canonical_window(d_win, 10) + xp_assert_close(win2, win) + + +def test__calc_dual_canonical_window_exceptions(): + """Raise all exceptions in `_calc_dual_canonical_window`.""" + # Verify that calculation can fail: + with pytest.raises(ValueError, match="hop=5 is larger than window len.*"): + _calc_dual_canonical_window(np.ones(4), 5) + with pytest.raises(ValueError, match=".* Transform not invertible!"): + _calc_dual_canonical_window(np.array([.1, .2, .3, 0]), 4) + + # Verify that parameter `win` may not be integers: + with pytest.raises(ValueError, match="Parameter 'win' cannot be of int.*"): + _calc_dual_canonical_window(np.ones(4, dtype=int), 1) + +def test_closest_STFT_dual_window_exceptions(): + """Raise all exceptions in `closest_STFT_dual_window`.""" + with pytest.raises(ValueError, match="Parameters `win` and `desired_dual` are.*"): + closest_STFT_dual_window(np.ones(4), 2, np.ones(5)) + with pytest.raises(ValueError, match="Parameters `win` and `desired_dual` are.*"): + closest_STFT_dual_window(np.ones((4, 1)), 2, np.ones(4)) + with pytest.raises(ValueError, match="Parameter win must have finite entries!"): + closest_STFT_dual_window(np.ones(4)*np.nan, 2, np.ones(4)) + with pytest.raises(ValueError, match="Parameter desired_dual must have finite.*"): + closest_STFT_dual_window(np.ones(4), 2, np.ones(4)*np.nan) + with pytest.raises(ValueError, match="Parameter hop=20 is not an integer.*"): + closest_STFT_dual_window(np.ones(4), 20, np.ones(4)) + with pytest.raises(ValueError, match="Parameter hop=2.0 is not an integer.*"): + # noinspection PyTypeChecker + closest_STFT_dual_window(np.ones(4), 2., np.ones(4)) + + with pytest.raises(ValueError, match="Unable to calculate scaled closest dual.*"): + closest_STFT_dual_window(np.ones(4), 2, np.zeros(4), scaled=True) + + +@pytest.mark.parametrize("scaled", (True, False)) +@pytest.mark.parametrize('sym_win', (False, True)) +@pytest.mark.parametrize('hop', (8, 9)) +@pytest.mark.parametrize('m', (16, 17)) +@pytest.mark.parametrize('win_name', ('hann', 'hamming')) +def test_closest_STFT_dual_window_roundtrip(win_name, m, hop, sym_win, scaled): + """Do roundtrip, i.e., compare dual of dual windows. + + The default for parameter `desired_dual` is also verified. + """ + win = get_window(win_name, m, not sym_win) + d1, s1 = closest_STFT_dual_window(win, hop, np.ones_like(win), scaled=scaled) + d2, s2 = closest_STFT_dual_window(win, hop, scaled=scaled) # equals d1, s1 + d3, s3 = closest_STFT_dual_window(d1, hop, win * s1, scaled=True) # roundtrip + + # (d1, s1) == (d2, s2) should hold, but the BLAS backend used to calculate the inner + # product in `closest_STFT_dual_window` does not guarantee exact reproducibility + # due to indeterministic summation order during parallel execution. Hence, only + # approximate equality is verified. Consult NumPy 29873 issue for discussion. + res = np.finfo(win.dtype).resolution # 1e-15 for float64 + err_msg = "Default for parameter `desired_dual` is not ok!" + xp_assert_close(d2, d1, atol=res, err_msg=err_msg) + xp_assert_close(s2, s1, atol=res, err_msg=err_msg) + + xp_assert_close(s1*s3, 1., atol=res*5, err_msg="Invalid Scale factors") + xp_assert_close(d3, win, atol=res*5, err_msg="Roundtrip failed!") + + if scaled: # check that scaling factor is correct: + d3, _ = closest_STFT_dual_window(win, hop, np.ones(m) * s1, scaled=False) + xp_assert_close(d3, d1, atol=res, err_msg="Roundtrip failed!") + + +@pytest.mark.parametrize('scaled', (False, True)) +def test_closest_STFT_dual_window_STFT_roundtrip(scaled): + """STFT Roundtrip correctness of closest dual window. """ + np.random.seed(5613830) + n, m_num, hop = 15, 7, 3 + w, desires_dual = hamming(m_num), triang(m_num) + d, _ = closest_STFT_dual_window(w, hop, desires_dual, scaled=scaled) + + SFT = ShortTimeFFT(w, hop=hop, dual_win=d, fs=1, scale_to=None, phase_shift=None) + x = 10 * np.random.randn(n) + Sx = SFT.stft(x) + y = SFT.istft(Sx, 0, n) + + atol = np.finfo(w.dtype).resolution * 10 + xp_assert_close(y, x, atol=atol, err_msg="Invalid closest window") + + +@pytest.mark.parametrize('scaled', (False, True)) +def test_closest_STFT_dual_window_STFT_roundtrip_complex(scaled): + """STFT Roundtrip correctness of closest dual window with complex values. """ + np.random.seed(34905436) + n, m_num, hop = 15, 7, 3 + win = 1j*hamming(m_num) + nuttall(m_num) + desires_dual = 1j*triang(m_num) + blackman(m_num) + dual, s = closest_STFT_dual_window(win, hop, desires_dual, scaled=scaled) + + SFT = ShortTimeFFT(win, hop=hop, dual_win=dual, fs=1, fft_mode='twosided', + scale_to=None, phase_shift=None) + x = 10 * np.random.randn(n) + 10j * np.random.randn(n) + Sx = SFT.stft(x) + y = SFT.istft(Sx, 0, n) + + atol = np.finfo(win.dtype).resolution * 10 + xp_assert_close(y, x, atol=atol, err_msg=f"Invalid complex closest window ({s=})") + + +@pytest.mark.parametrize("win_name, nperseg, noverlap, scale_fac", ([ + ('boxcar', 16, 8, 1/2), + ('boxcar', 18, 12, 1/3), + ('boxcar', 16, 12, 1/4), + ('bartlett', 16, 8, 1.), + ('bartlett', 16, 12, 1/2), + ('bartlett', 30, 25, 1/3), + ('hann', 16, 8, 1.), + ('hann', 18, 12, 2/3), + ('hann', 16, 12, 1/2), + ('blackman', 9, 6, 50/63), + ('boxcar', 8, 7, 1/8)])) # hop = 1 +def test_closest_STFT_dual_window_cola(win_name, nperseg, noverlap, scale_fac): + """Test if `closest_STFT_dual_window` generalizes `check_COLA`. + + The parameters were taken from the `check_COLA` documentation. + Note that `check_COLA` only guarantees the existence of a dual window with constant + values but not that those values are unity (which is clear, when investigating the + 'boxcar' examples). The values for `scale_fac` were determined empirically. + """ + desired_dual = get_window(win_name, nperseg, fftbins=True) + assert check_COLA(desired_dual, nperseg, noverlap), "COLA cond. violated!" + + win = np.ones(nperseg) # check scaled window: + d_s, s = closest_STFT_dual_window(win, nperseg-noverlap, desired_dual, scaled=True) + + res = np.finfo(desired_dual.dtype).resolution + rel_tol_d = max(abs(d_s))*res*3 + xp_assert_close(s, scale_fac, atol=res*10, + err_msg=f"Scale factor off by {s/scale_fac}") + xp_assert_close(d_s, desired_dual*scale_fac, atol=res*10, rtol=rel_tol_d, + err_msg="Calculated incorrect scaled window!") + + # check unscaled window: + d_u, u = closest_STFT_dual_window(win * scale_fac, nperseg - noverlap, + desired_dual, scaled=False) + + # this should be hard-coded not computed, so no need for allclose + assert u == 1., "Scaling factor not 1 for parameter `scaled=True`!" + xp_assert_close(d_u, desired_dual, atol=res*10, rtol=rel_tol_d, + err_msg="Calculated incorrect unscaled window!") + + +def test_invalid_initializer_parameters(): + """Verify that exceptions get raised on invalid parameters when + instantiating ShortTimeFFT. """ + with pytest.raises(ValueError, match=r"Parameter win must be 1d, " + + r"but win.shape=\(2, 2\)!"): + ShortTimeFFT(np.ones((2, 2)), hop=4, fs=1) + with pytest.raises(ValueError, match="Parameter win must have " + + "finite entries"): + ShortTimeFFT(np.array([1, np.inf, 2, 3]), hop=4, fs=1) + with pytest.raises(ValueError, match="Parameter hop=0 is not " + + "an integer >= 1!"): + ShortTimeFFT(np.ones(4), hop=0, fs=1) + with pytest.raises(ValueError, match="Parameter hop=2.0 is not " + + "an integer >= 1!"): + # noinspection PyTypeChecker + ShortTimeFFT(np.ones(4), hop=2.0, fs=1) + with pytest.raises(ValueError, match=r"dual_win.shape=\(5,\) must equal " + + r"win.shape=\(4,\)!"): + ShortTimeFFT(np.ones(4), hop=2, fs=1, dual_win=np.ones(5)) + with pytest.raises(ValueError, match="Parameter dual_win must be " + + "a finite array!"): + ShortTimeFFT(np.ones(3), hop=2, fs=1, + dual_win=np.array([np.nan, 2, 3])) + + +def test_exceptions_properties_methods(): + """Verify that exceptions get raised when setting properties or calling + method of ShortTimeFFT to/with invalid values.""" + SFT = ShortTimeFFT(np.ones(8), hop=4, fs=1) + with pytest.raises(ValueError, match="Sampling interval T=-1 must be " + + "positive!"): + SFT.T = -1 + with pytest.raises(ValueError, match="Sampling frequency fs=-1 must be " + + "positive!"): + SFT.fs = -1 + with pytest.raises(ValueError, match="fft_mode='invalid_typ' not in " + + r"\('twosided', 'centered', " + + r"'onesided', 'onesided2X'\)!"): + SFT.fft_mode = 'invalid_typ' + with pytest.raises(ValueError, match="For scaling is None, " + + "fft_mode='onesided2X' is invalid.*"): + SFT.fft_mode = 'onesided2X' + with pytest.raises(ValueError, match="Attribute mfft=7 needs to be " + + "at least the window length.*"): + SFT.mfft = 7 + with pytest.raises(ValueError, match="scaling='invalid' not in.*"): + # noinspection PyTypeChecker + SFT.scale_to('invalid') + with pytest.raises(ValueError, match="phase_shift=3.0 has the unit .*"): + SFT.phase_shift = 3.0 + with pytest.raises(ValueError, match="-mfft < phase_shift < mfft " + + "does not hold.*"): + SFT.phase_shift = 2*SFT.mfft + with pytest.raises(ValueError, match="Parameter padding='invalid' not.*"): + # noinspection PyTypeChecker + g = SFT._x_slices(np.zeros(16), k_off=0, p0=0, p1=1, padding='invalid') + next(g) # execute generator + with pytest.raises(ValueError, match="Trend type must be 'linear' " + + "or 'constant'"): + # noinspection PyTypeChecker + SFT.stft_detrend(np.zeros(16), detr='invalid') + with pytest.raises(ValueError, match="Parameter detr=nan is not a str, " + + "function or None!"): + # noinspection PyTypeChecker + SFT.stft_detrend(np.zeros(16), detr=np.nan) + with pytest.raises(ValueError, match="Invalid Parameter p0=0, p1=200.*"): + SFT.p_range(100, 0, 200) + + with pytest.raises(ValueError, match="f_axis=0 may not be equal to " + + "t_axis=0!"): + SFT.istft(np.zeros((SFT.f_pts, 2)), t_axis=0, f_axis=0) + with pytest.raises(ValueError, match=r"S.shape\[f_axis\]=2 must be equal" + + " to self.f_pts=5.*"): + SFT.istft(np.zeros((2, 2))) + with pytest.raises(ValueError, match=r"S.shape\[t_axis\]=1 needs to have" + + " at least 2 slices.*"): + SFT.istft(np.zeros((SFT.f_pts, 1))) + with pytest.raises(ValueError, match=r".*\(k1=100\) <= \(k_max=12\) " + + "is false!$"): + SFT.istft(np.zeros((SFT.f_pts, 3)), k1=100) + with pytest.raises(ValueError, match=r"\(k1=1\) - \(k0=0\) = 1 has to " + + "be at least.* length 4!"): + SFT.istft(np.zeros((SFT.f_pts, 3)), k0=0, k1=1) + + with pytest.raises(ValueError, match=r"Parameter axes_seq='invalid' " + + r"not in \['tf', 'ft'\]!"): + # noinspection PyTypeChecker + SFT.extent(n=100, axes_seq='invalid') + with pytest.raises(ValueError, match="Attribute fft_mode=twosided must.*"): + SFT.fft_mode = 'twosided' + SFT.extent(n=100) + + +@pytest.mark.parametrize('m', ('onesided', 'onesided2X')) +def test_exceptions_fft_mode_complex_win(m: FFT_MODE_TYPE): + """Verify that one-sided spectra are not allowed with complex-valued + windows or with complex-valued signals. + + The reason being, the `rfft` function only accepts real-valued input. + """ + with pytest.raises(ValueError, + match=f"One-sided spectra, i.e., fft_mode='{m}'.*"): + ShortTimeFFT(np.ones(8)*1j, hop=4, fs=1, fft_mode=m) + + SFT = ShortTimeFFT(np.ones(8)*1j, hop=4, fs=1, fft_mode='twosided') + with pytest.raises(ValueError, + match=f"One-sided spectra, i.e., fft_mode='{m}'.*"): + SFT.fft_mode = m + + SFT = ShortTimeFFT(np.ones(8), hop=4, fs=1, scale_to='psd', fft_mode='onesided') + with pytest.raises(ValueError, match="Complex-valued `x` not allowed for self.*"): + SFT.stft(np.ones(8)*1j) + SFT.fft_mode = 'onesided2X' + with pytest.raises(ValueError, match="Complex-valued `x` not allowed for self.*"): + SFT.stft(np.ones(8)*1j) + + +def test_invalid_fft_mode_RuntimeError(): + """Ensure exception gets raised when property `fft_mode` is invalid. """ + SFT = ShortTimeFFT(np.ones(8), hop=4, fs=1) + # noinspection PyTypeChecker + SFT._fft_mode = 'invalid_typ' + + with pytest.raises(RuntimeError): + _ = SFT.f + with pytest.raises(RuntimeError): + SFT._fft_func(np.ones(8)) + with pytest.raises(RuntimeError): + SFT._ifft_func(np.ones(8)) + + +@pytest.mark.parametrize('win_params, Nx', [(('gaussian', 2.), 9), # in docstr + ('triang', 7), + (('kaiser', 4.0), 9), + (('exponential', None, 1.), 9), + (4.0, 9)]) +def test_from_window(win_params, Nx: int): + """Verify that `from_window()` handles parameters correctly. + + The window parameterizations are documented in the `get_window` docstring. + """ + w_sym, fs = get_window(win_params, Nx, fftbins=False), 16. + w_per = get_window(win_params, Nx, fftbins=True) + SFT0 = ShortTimeFFT(w_sym, hop=3, fs=fs, fft_mode='twosided', + scale_to='psd', phase_shift=1) + nperseg = len(w_sym) + noverlap = nperseg - SFT0.hop + SFT1 = ShortTimeFFT.from_window(win_params, fs, nperseg, noverlap, + symmetric_win=True, fft_mode='twosided', + scale_to='psd', phase_shift=1) + # periodic window: + SFT2 = ShortTimeFFT.from_window(win_params, fs, nperseg, noverlap, + symmetric_win=False, fft_mode='twosided', + scale_to='psd', phase_shift=1) + # Be informative when comparing instances: + xp_assert_equal(SFT1.win, SFT0.win) + xp_assert_close(SFT2.win, w_per / np.sqrt(sum(w_per**2) * fs)) + for n_ in ('hop', 'T', 'fft_mode', 'mfft', 'scaling', 'phase_shift'): + v0, v1, v2 = (getattr(SFT_, n_) for SFT_ in (SFT0, SFT1, SFT2)) + assert v1 == v0, f"SFT1.{n_}={v1} does not equal SFT0.{n_}={v0}" + assert v2 == v0, f"SFT2.{n_}={v2} does not equal SFT0.{n_}={v0}" + + +def test_from_win_equals_dual_exceptions(): + """Raise all occurring exceptions in `ShortTimeFFT.from_closest_win_equals_dual`. + """ + with pytest.raises(ValueError, match="Parameter desired_win is not 1d, but.*"): + ShortTimeFFT.from_win_equals_dual(np.ones((3, 4)), hop=1, fs=1) + with pytest.raises(ValueError, match="Parameter desired_win cannot be of int.*"): + ShortTimeFFT.from_win_equals_dual(np.ones(4, dtype=int), hop=1, fs=1) + with pytest.raises(ValueError, match="Parameter desired_win is not 1d, but.*"): + ShortTimeFFT.from_win_equals_dual(np.array([]), hop=1, fs=1) + with pytest.raises(ValueError, match="Parameter desired_win must have finite.*"): + ShortTimeFFT.from_win_equals_dual(np.ones(3) * np.inf, hop=1, fs=1) + with pytest.raises(ValueError, match="Parameter hop=0 is not an integer .*"): + ShortTimeFFT.from_win_equals_dual(np.ones(4), hop=0, fs=1) + with pytest.raises(ValueError, match="Parameter hop=5 is not an integer .*"): + ShortTimeFFT.from_win_equals_dual(np.ones(4), hop=5, fs=1) + + with pytest.raises(ValueError, match="P.+ desired_win does not have valid.*"): + w = np.array([1, 0, 1, 0, 1], dtype=float) + ShortTimeFFT.from_win_equals_dual(w, hop=2, fs=1) + with pytest.raises(ValueError, match="Parameter scale_to='invalid' not in.*"): + # noinspection PyTypeChecker + ShortTimeFFT.from_win_equals_dual(w, hop=2, fs=1, scale_to='invalid') + +@pytest.mark.parametrize('fft_bins', (True, False)) +@pytest.mark.parametrize('m, hop', [(16, 8), (16, 7), (17, 8), (17, 9), (16, 16)]) +def test_from_win_equals_dual_params(m, hop, fft_bins): + """Test windows parameterizations for `ShortTimeFFT.from_closest_win_equals_dual`. + + The flattop window is used since it also has negative values. + """ + desired_win = get_window('flattop', m, fftbins=fft_bins) + SFT0 = ShortTimeFFT.from_win_equals_dual(desired_win, hop, fs=1) + xp_assert_close(SFT0.dual_win, SFT0.win, err_msg="win must equals dual window!") + + SFT1 = ShortTimeFFT(SFT0.win, hop, fs=1) + xp_assert_close(SFT1.dual_win, SFT0.win, err_msg="dual win isn't canonical win!") + + +@pytest.mark.parametrize('fft_bins', (True, False)) +@pytest.mark.parametrize('m, hop', [(16, 8), (16, 7), (17, 8), (17, 9), (16, 16)]) +@pytest.mark.parametrize('scale_to', (None, 'unitary')) +def test_from_win_equals_dual_roundtrip(m, hop, fft_bins, scale_to): + """Testing roundtrip verifies that the dual window is correct. + """ + desired_win = get_window('flattop', m, fftbins=fft_bins) + SFT0 = ShortTimeFFT.from_win_equals_dual(desired_win, hop, fs=1) + + x = np.cos(np.arange(2*m)**2) + x1 = SFT0.istft(SFT0.stft(x), 0, len(x)) + xp_assert_close(x1, x, err_msg="Roundtrip for win equaling its dual failed!") + + +def test_from_win_equals_dual_unitary(): + """Check that STFT can be unitary mapping. """ + m, hop = 8, 4 + des_win = get_window('hann', m) + SFT = ShortTimeFFT.from_win_equals_dual(des_win, hop, 1, fft_mode='twosided', + scale_to='unitary') + # Orthogonal signals: + x, y = np.tile([-1, -1, 1, 1], 4), np.tile([1, -1, -1, 1], 4) + Sxx, Sxy = SFT.spectrogram(x), SFT.spectrogram(x, y) + + atol = np.finfo(Sxx.dtype).resolution + assert sum(x * y) == 0 + xp_assert_close(np.sum(Sxx), np.sum(x ** 2, dtype=Sxx.dtype), atol=atol, + err_msg="Energies do not match!") + xp_assert_close(np.sum(Sxy), 0.0j, atol=atol, + err_msg="STFT scalar product of Sx and Sy not 0!") + xp_assert_close(SFT.dual_win, SFT.win*SFT.m_num, atol=atol, + err_msg="Wrong factor for dual_win/win!") + + +def test_dual_win_roundtrip(): + """Verify the duality of `win` and `dual_win`. + + Note that this test does not work for arbitrary windows, since dual windows + are not unique. It always works for invertible STFTs if the windows do not + overlap. + """ + # Non-standard values for keyword arguments (except for `scale_to`): + kw = dict(hop=4, fs=1, fft_mode='twosided', mfft=8, scale_to=None, + phase_shift=2) + SFT0 = ShortTimeFFT(np.ones(4), **kw) + SFT1 = ShortTimeFFT.from_dual(SFT0.dual_win, **kw) + xp_assert_close(SFT1.dual_win, SFT0.win) + + +@pytest.mark.parametrize('scale_to, fac_psd, fac_mag', + [(None, 0.25, 0.125), + ('magnitude', 2.0, 1), + ('psd', 1, 0.5)]) +def test_scaling(scale_to: Literal['magnitude', 'psd'], fac_psd, fac_mag): + """Verify scaling calculations. + + * Verify passing `scale_to`parameter to ``__init__(). + * Roundtrip while changing scaling factor. + """ + SFT = ShortTimeFFT(np.ones(4) * 2, hop=4, fs=1, scale_to=scale_to) + assert SFT.fac_psd == fac_psd + assert SFT.fac_magnitude == fac_mag + # increase coverage by accessing properties twice: + assert SFT.fac_psd == fac_psd + assert SFT.fac_magnitude == fac_mag + + x = np.fft.irfft([0, 0, 7, 0, 0, 0, 0]) # periodic signal + Sx = SFT.stft(x) + Sx_mag, Sx_psd = Sx * SFT.fac_magnitude, Sx * SFT.fac_psd + + SFT.scale_to('magnitude') + x_mag = SFT.istft(Sx_mag, k1=len(x)) + xp_assert_close(x_mag, x) + + SFT.scale_to('psd') + x_psd = SFT.istft(Sx_psd, k1=len(x)) + xp_assert_close(x_psd, x) + + +def test_scale_to(): + """Verify `scale_to()` method.""" + SFT = ShortTimeFFT(np.ones(4) * 2, hop=4, fs=1, scale_to=None) + + SFT.scale_to('magnitude') + assert SFT.scaling == 'magnitude' + assert SFT.fac_psd == 2.0 + assert SFT.fac_magnitude == 1 + + SFT.scale_to('psd') + assert SFT.scaling == 'psd' + assert SFT.fac_psd == 1 + assert SFT.fac_magnitude == 0.5 + + SFT.scale_to('psd') # needed for coverage + + for scale, s_fac in zip(('magnitude', 'psd'), (8, 4)): + SFT = ShortTimeFFT(np.ones(4) * 2, hop=4, fs=1, scale_to=None) + dual_win = SFT.dual_win.copy() + + SFT.scale_to(cast(Literal['magnitude', 'psd'], scale)) + xp_assert_close(SFT.dual_win, dual_win * s_fac) + + +def test_x_slices_padding(): + """Verify padding. + + The reference arrays were taken from the docstrings of `zero_ext`, + `const_ext`, `odd_ext()`, and `even_ext()` from the _array_tools module. + """ + SFT = ShortTimeFFT(np.ones(5), hop=4, fs=1) + x = np.array([[1, 2, 3, 4, 5], [0, 1, 4, 9, 16]], dtype=float) + d = {'zeros': [[[0, 0, 1, 2, 3], [0, 0, 0, 1, 4]], + [[3, 4, 5, 0, 0], [4, 9, 16, 0, 0]]], + 'edge': [[[1, 1, 1, 2, 3], [0, 0, 0, 1, 4]], + [[3, 4, 5, 5, 5], [4, 9, 16, 16, 16]]], + 'even': [[[3, 2, 1, 2, 3], [4, 1, 0, 1, 4]], + [[3, 4, 5, 4, 3], [4, 9, 16, 9, 4]]], + 'odd': [[[-1, 0, 1, 2, 3], [-4, -1, 0, 1, 4]], + [[3, 4, 5, 6, 7], [4, 9, 16, 23, 28]]]} + for p_, xx in d.items(): + gen = SFT._x_slices(np.array(x), 0, 0, 2, padding=cast(PAD_TYPE, p_)) + yy = np.array([y_.copy() for y_ in gen]) # due to inplace copying + xx = np.asarray(xx, dtype=np.float64) + xp_assert_equal(yy, xx, err_msg=f"Failed '{p_}' padding.") + + +def test_invertible(): + """Verify `invertible` property. """ + SFT = ShortTimeFFT(np.ones(8), hop=4, fs=1) + assert SFT.invertible + SFT = ShortTimeFFT(np.ones(8), hop=9, fs=1) + assert not SFT.invertible + + +def test_border_values(): + """Ensure that minimum and maximum values of slices are correct.""" + SFT = ShortTimeFFT(np.ones(8), hop=4, fs=1) + assert SFT.p_min == 0 + assert SFT.k_min == -4 + assert SFT.lower_border_end == (4, 1) + assert SFT.lower_border_end == (4, 1) # needed to test caching + assert SFT.p_max(10) == 4 + assert SFT.k_max(10) == 16 + assert SFT.upper_border_begin(10) == (4, 2) + assert SFT.upper_border_begin(10) == (4, 2) # needed to test caching + # Raise exceptions: + with pytest.raises(ValueError, match="^Parameter n must be"): + SFT.upper_border_begin(3) + with pytest.raises(ValueError, match="^Parameter n must be"): + SFT._post_padding(3) + with pytest.raises(RuntimeError): + SFT._hop = -1 # illegal hop interval + SFT.upper_border_begin(8) + +def test_border_values_exotic(): + """Ensure that the border calculations are correct for windows with + zeros. """ + w = np.array([0, 0, 0, 0, 0, 0, 0, 1.]) + SFT = ShortTimeFFT(w, hop=1, fs=1) + assert SFT.lower_border_end == (0, 0) + + SFT = ShortTimeFFT(np.flip(w), hop=20, fs=1) + assert SFT.upper_border_begin(4) == (16, 1) + assert SFT.upper_border_begin(5) == (16, 1) + assert SFT.upper_border_begin(23) == (36, 2) + assert SFT.upper_border_begin(24) == (36, 2) + assert SFT.upper_border_begin(25) == (36, 2) + + SFT._hop = -1 # provoke unreachable line + with pytest.raises(RuntimeError): + _ = SFT.k_max(4) + with pytest.raises(RuntimeError): + _ = SFT.k_min + + +def test_t(): + """Verify that the times of the slices are correct. """ + SFT = ShortTimeFFT(np.ones(8), hop=4, fs=2) + assert SFT.T == 1/2 + assert SFT.fs == 2. + assert SFT.delta_t == 4 * 1/2 + t_stft = np.arange(0, SFT.p_max(10)) * SFT.delta_t + xp_assert_equal(SFT.t(10), t_stft) + xp_assert_equal(SFT.t(10, 1, 3), t_stft[1:3]) + SFT.T = 1/4 + assert SFT.T == 1/4 + assert SFT.fs == 4 + SFT.fs = 1/8 + assert SFT.fs == 1/8 + assert SFT.T == 8 + with pytest.raises(ValueError): + # noinspection PyTypeChecker + SFT.t(1.5) # only integers allowed + with pytest.raises(ValueError): + SFT.t(-1) # only positive `n` allowed + + +@pytest.mark.parametrize('fft_mode, f', + [('onesided', [0., 1., 2.]), + ('onesided2X', [0., 1., 2.]), + ('twosided', [0., 1., 2., -2., -1.]), + ('centered', [-2., -1., 0., 1., 2.])]) +def test_f(fft_mode: FFT_MODE_TYPE, f): + """Verify the frequency values property `f`.""" + SFT = ShortTimeFFT(np.ones(5), hop=4, fs=5, fft_mode=fft_mode, + scale_to='psd') + xp_assert_equal(SFT.f, f) + + +@pytest.mark.parametrize('n', [20, 21]) +@pytest.mark.parametrize('m', [5, 6]) +@pytest.mark.parametrize('fft_mode', ['onesided', 'centered']) +def test_extent(n, m, fft_mode: FFT_MODE_TYPE): + """Ensure that the `extent()` method is correct. """ + SFT = ShortTimeFFT(np.ones(m), hop=m, fs=m, fft_mode=fft_mode) + + t0 = SFT.t(n)[0] # first timestamp + t1 = SFT.t(n)[-1] + SFT.delta_t # last timestamp + 1 + t0c, t1c = t0 - SFT.delta_t / 2, t1 - SFT.delta_t / 2 # centered timestamps + + f0 = SFT.f[0] # first frequency + f1 = SFT.f[-1] + SFT.delta_f # last frequency + 1 + f0c, f1c = f0 - SFT.delta_f / 2, f1 - SFT.delta_f / 2 # centered frequencies + + assert SFT.extent(n, 'tf', False) == (t0, t1, f0, f1) + assert SFT.extent(n, 'ft', False) == (f0, f1, t0, t1) + assert SFT.extent(n, 'tf', True) == (t0c, t1c, f0c, f1c) + assert SFT.extent(n, 'ft', True) == (f0c, f1c, t0c, t1c) + + +def test_spectrogram(): + """Verify spectrogram and cross-spectrogram methods. """ + SFT = ShortTimeFFT(np.ones(8), hop=4, fs=1) + x, y = np.ones(10), np.arange(10) + X, Y = SFT.stft(x), SFT.stft(y) + xp_assert_close(SFT.spectrogram(x), X.real**2+X.imag**2) + xp_assert_close(SFT.spectrogram(x, y), X * Y.conj()) + + +@pytest.mark.parametrize('n', [8, 9]) +def test_fft_func_roundtrip(n: int): + """Test roundtrip `ifft_func(fft_func(x)) == x` for all permutations of + relevant parameters. """ + np.random.seed(2394795) + x0 = np.random.rand(n) + w, h_n = np.ones(n), 4 + + pp = dict( + fft_mode=get_args(FFT_MODE_TYPE), + mfft=[None, n, n+1, n+2], + scaling=[None, 'magnitude', 'psd'], + phase_shift=[None, -n+1, 0, n // 2, n-1]) + for f_typ, mfft, scaling, phase_shift in product(*pp.values()): + if f_typ == 'onesided2X' and scaling is None: + continue # this combination is forbidden + SFT = ShortTimeFFT(w, h_n, fs=n, fft_mode=f_typ, mfft=mfft, + scale_to=scaling, phase_shift=phase_shift) + X0 = SFT._fft_func(x0) + x1 = SFT._ifft_func(X0) + xp_assert_close(x0.astype(x1.dtype), x1, + err_msg="_fft_func() roundtrip failed for " + + f"{f_typ=}, {mfft=}, {scaling=}, {phase_shift=}") + + SFT = ShortTimeFFT(w, h_n, fs=1) + SFT._fft_mode = 'invalid_fft' # type: ignore + with pytest.raises(RuntimeError): + SFT._fft_func(x0) + with pytest.raises(RuntimeError): + SFT._ifft_func(x0) + + +@pytest.mark.parametrize('i', range(19)) +def test_impulse_roundtrip(i): + """Roundtrip for an impulse being at different positions `i`.""" + n = 19 + w, h_n = np.ones(8), 3 + x = np.zeros(n) + x[i] = 1 + + SFT = ShortTimeFFT(w, hop=h_n, fs=1, scale_to=None, phase_shift=None) + Sx = SFT.stft(x) + # test slicing the input signal into two parts: + n_q = SFT.nearest_k_p(n // 2) + Sx0 = SFT.stft(x[:n_q], padding='zeros') + Sx1 = SFT.stft(x[n_q:], padding='zeros') + q0_ub = SFT.upper_border_begin(n_q)[1] - SFT.p_min + q1_le = SFT.lower_border_end[1] - SFT.p_min + xp_assert_close(Sx0[:, :q0_ub], Sx[:, :q0_ub], err_msg=f"{i=}") + xp_assert_close(Sx1[:, q1_le:], Sx[:, q1_le-Sx1.shape[1]:], + err_msg=f"{i=}") + + Sx01 = np.hstack((Sx0[:, :q0_ub], + Sx0[:, q0_ub:] + Sx1[:, :q1_le], + Sx1[:, q1_le:])) + xp_assert_close(Sx, Sx01, atol=1e-8, err_msg=f"{i=}") + + y = SFT.istft(Sx, 0, n) + xp_assert_close(y, x, atol=1e-8, err_msg=f"{i=}") + y0 = SFT.istft(Sx, 0, n//2) + xp_assert_close(x[:n//2], y0, atol=1e-8, err_msg=f"{i=}") + y1 = SFT.istft(Sx, n // 2, n) + xp_assert_close(x[n // 2:], y1, atol=1e-8, err_msg=f"{i=}") + + +@pytest.mark.parametrize('hop', [1, 7, 8]) +def test_asymmetric_window_roundtrip(hop: int): + """An asymmetric window could uncover indexing problems. """ + np.random.seed(23371) + + w = np.arange(16) / 8 # must be of type float + w[len(w)//2:] = 1 + SFT = ShortTimeFFT(w, hop, fs=1) + + x = 10 * np.random.randn(64) + Sx = SFT.stft(x) + x1 = SFT.istft(Sx, k1=len(x)) + xp_assert_close(x1, x1, err_msg="Roundtrip for asymmetric window with " + + f" {hop=} failed!") + + +@pytest.mark.parametrize('m_num', [6, 7]) +def test_minimal_length_signal(m_num): + """Verify that the shortest allowed signal works. """ + SFT = ShortTimeFFT(np.ones(m_num), m_num//2, fs=1) + n = math.ceil(m_num/2) + x = np.ones(n) + Sx = SFT.stft(x) + x1 = SFT.istft(Sx, k1=n) + xp_assert_close(x1, x, err_msg=f"Roundtrip minimal length signal ({n=})" + + f" for {m_num} sample window failed!") + with pytest.raises(ValueError, match=rf"len\(x\)={n-1} must be >= ceil.*"): + SFT.stft(x[:-1]) + with pytest.raises(ValueError, match=rf"S.shape\[t_axis\]={Sx.shape[1]-1}" + f" needs to have at least {Sx.shape[1]} slices"): + SFT.istft(Sx[:, :-1], k1=n) + + +def test_compare_stft_detrend(): + """Test the detrending in `ShortTimeFFT.stft_detrend()`. """ + SFT = ShortTimeFFT(np.ones(4), 4, fs=1) + x0 = np.zeros(4) # signal without trend + x1 = x0 + 3 # signal with constant trend + x2 = x0 + np.arange(len(x0)) # signal with linear trend + + kw = dict(k_offset=2, p1=1) # we want only one slice + Sx0 = SFT.stft(x0, **kw) # no trend + Sx1 = SFT.stft_detrend(x1, detr='constant', **kw) + Sx2 = SFT.stft_detrend(x2, detr='linear', **kw) + Sx3 = SFT.stft_detrend(x1, detr=lambda x: x - np.mean(x), **kw) + + atol = np.finfo(Sx0.dtype).resolution * 5 # needed to compare with array of zeros + xp_assert_close(Sx1, Sx0, atol=atol, err_msg="Constant detrending failed!") + xp_assert_close(Sx0, Sx2, atol=atol, err_msg="Linear detrending failed!") + xp_assert_close(Sx0, Sx3, atol=atol, err_msg="Detrending using a function failed!") + + +def test_tutorial_stft_sliding_win(): + """Verify example in "Sliding Windows" subsection from the "User Guide". + + In :ref:`tutorial_stft_sliding_win` (file ``signal.rst``) of the + :ref:`user_guide` the behavior the border behavior of + ``ShortTimeFFT(np.ones(6), 2, fs=1)`` with a 50 sample signal is discussed. + This test verifies the presented indexes. + """ + SFT = ShortTimeFFT(np.ones(6), 2, fs=1) + + # Lower border: + assert SFT.m_num_mid == 3, f"Slice middle is not 3 but {SFT.m_num_mid=}" + assert SFT.p_min == -1, f"Lowest slice {SFT.p_min=} is not -1" + assert SFT.k_min == -5, f"Lowest slice sample {SFT.p_min=} is not -5" + k_lb, p_lb = SFT.lower_border_end + assert p_lb == 2, f"First unaffected slice {p_lb=} is not 2" + assert k_lb == 5, f"First unaffected sample {k_lb=} is not 5" + + n = 50 # upper signal border + assert (p_max := SFT.p_max(n)) == 27, f"Last slice {p_max=} must be 27" + assert (k_max := SFT.k_max(n)) == 55, f"Last sample {k_max=} must be 55" + k_ub, p_ub = SFT.upper_border_begin(n) + assert p_ub == 24, f"First upper border slice {p_ub=} must be 24" + assert k_ub == 45, f"First upper border slice {k_ub=} must be 45" + + +def test_tutorial_stft_legacy_stft(): + """Verify STFT example in "Comparison with Legacy Implementation" from the + "User Guide". + + In :ref:`tutorial_stft_legacy_stft` (file ``signal.rst``) of the + :ref:`user_guide` the legacy and the new implementation are compared. + """ + fs, N = 200, 1001 # # 200 Hz sampling rate for 5 s signal + t_z = np.arange(N) / fs # time indexes for signal + z = np.exp(2j*np.pi * 70 * (t_z - 0.2 * t_z ** 2)) # complex-valued chirp + + nperseg, noverlap = 50, 40 + win = ('gaussian', 1e-2 * fs) # Gaussian with 0.01 s standard deviation + + # Legacy STFT: + f0_u, t0, Sz0_u = stft(z, fs, win, nperseg, noverlap, + return_onesided=False, scaling='spectrum') + Sz0 = fftshift(Sz0_u, axes=0) + + # New STFT: + SFT = ShortTimeFFT.from_window(win, fs, nperseg, noverlap, + fft_mode='centered', + scale_to='magnitude', phase_shift=None) + Sz1 = SFT.stft(z) + + xp_assert_close(Sz0, Sz1[:, 2:-1]) + + xp_assert_close((abs(Sz1[:, 1]).min(), abs(Sz1[:, 1]).max()), + (6.925060911593139e-07, 8.00271269218721e-07)) + + t0_r, z0_r = istft(Sz0_u, fs, win, nperseg, noverlap, input_onesided=False, + scaling='spectrum') + z1_r = SFT.istft(Sz1, k1=N) + assert len(z0_r) == N + 9 + xp_assert_close(z0_r[:N], z) + xp_assert_close(z1_r, z) + + # Spectrogram is just the absolute square of th STFT: + xp_assert_close(SFT.spectrogram(z), abs(Sz1) ** 2) + + +def test_tutorial_stft_legacy_spectrogram(): + """Verify spectrogram example in "Comparison with Legacy Implementation" + from the "User Guide". + + In :ref:`tutorial_stft_legacy_stft` (file ``signal.rst``) of the + :ref:`user_guide` the legacy and the new implementation are compared. + """ + fs, N = 200, 1001 # 200 Hz sampling rate for almost 5 s signal + t_z = np.arange(N) / fs # time indexes for signal + z = np.exp(2j*np.pi*70 * (t_z - 0.2*t_z**2)) # complex-valued sweep + + nperseg, noverlap = 50, 40 + win = ('gaussian', 1e-2 * fs) # Gaussian with 0.01 s standard dev. + + # Legacy spectrogram: + f2_u, t2, Sz2_u = spectrogram(z, fs, win, nperseg, noverlap, detrend=None, + return_onesided=False, scaling='spectrum', + mode='complex') + + f2, Sz2 = fftshift(f2_u), fftshift(Sz2_u, axes=0) + + # New STFT: + SFT = ShortTimeFFT.from_window(win, fs, nperseg, noverlap, + fft_mode='centered', scale_to='magnitude', + phase_shift=None) + Sz3 = SFT.stft(z, p0=0, p1=(N-noverlap) // SFT.hop, k_offset=nperseg // 2) + t3 = SFT.t(N, p0=0, p1=(N-noverlap) // SFT.hop, k_offset=nperseg // 2) + + xp_assert_close(t2, t3) + xp_assert_close(f2, SFT.f) + xp_assert_close(Sz2, Sz3) + + +def test_permute_axes(): + """Verify correctness of four-dimensional signal by permuting its + shape. """ + n = 25 + SFT = ShortTimeFFT(np.ones(8)/8, hop=3, fs=n) + x0 = np.arange(n, dtype=np.float64) + Sx0 = SFT.stft(x0) + Sx0 = Sx0.reshape((Sx0.shape[0], 1, 1, 1, Sx0.shape[-1])) + SxT = np.moveaxis(Sx0, (0, -1), (-1, 0)) + + atol = 2 * np.finfo(SFT.win.dtype).resolution + for i in range(4): + y = np.reshape(x0, np.roll((n, 1, 1, 1), i)) + Sy = SFT.stft(y, axis=i) + xp_assert_close(Sy, np.moveaxis(Sx0, 0, i)) + + yb0 = SFT.istft(Sy, k1=n, f_axis=i) + xp_assert_close(yb0, y, atol=atol) + # explicit t-axis parameter (for coverage): + yb1 = SFT.istft(Sy, k1=n, f_axis=i, t_axis=Sy.ndim-1) + xp_assert_close(yb1, y, atol=atol) + + SyT = np.moveaxis(Sy, (i, -1), (-1, i)) + xp_assert_close(SyT, np.moveaxis(SxT, 0, i)) + + ybT = SFT.istft(SyT, k1=n, t_axis=i, f_axis=-1) + xp_assert_close(ybT, y, atol=atol) + + +@pytest.mark.parametrize("fft_mode", + ('twosided', 'centered', 'onesided', 'onesided2X')) +def test_roundtrip_multidimensional(fft_mode: FFT_MODE_TYPE): + """Test roundtrip of a multidimensional input signal versus its components. + + This test can uncover potential problems with `fftshift()`. + """ + n = 9 + x = np.arange(4*n*2, dtype=np.float64).reshape(4, n, 2) + SFT = ShortTimeFFT(get_window('hann', 4), hop=2, fs=1, + scale_to='magnitude', fft_mode=fft_mode) + Sx = SFT.stft(x, axis=1) + y = SFT.istft(Sx, k1=n, f_axis=1, t_axis=-1) + xp_assert_close(y, x.astype(y.dtype), err_msg='Multidim. roundtrip failed!') + + for i, j in product(range(x.shape[0]), range(x.shape[2])): + y_ = SFT.istft(Sx[i, :, j, :], k1=n) + xp_assert_close(y_, x[i, :, j].astype(y_.dtype), + err_msg="Multidim. roundtrip for component " + + f"x[{i}, :, {j}] and {fft_mode=} failed!") + +@pytest.mark.parametrize("phase_shift", (0, 4, None)) +def test_roundtrip_two_dimensional(phase_shift: int|None): + """Test roundtrip of a 2 channel input signal with `mfft` set with different + values for `phase_shift` + + Tests for Issue https://github.com/scipy/scipy/issues/21671 + """ + n = 21 + SFT = ShortTimeFFT.from_window('hann', fs=1, nperseg=13, noverlap=7, + mfft=16, phase_shift=phase_shift) + x = np.arange(2*n, dtype=float).reshape(2, n) + Sx = SFT.stft(x) + y = SFT.istft(Sx, k1=n) + xp_assert_close(y, x, atol=2 * np.finfo(SFT.win.dtype).resolution, + err_msg='2-dim. roundtrip failed!') + + +@pytest.mark.parametrize('window, n, nperseg, noverlap', + [('boxcar', 100, 10, 0), # Test no overlap + ('boxcar', 100, 10, 9), # Test high overlap + ('bartlett', 101, 51, 26), # Test odd nperseg + ('hann', 1024, 256, 128), # Test defaults + (('tukey', 0.5), 1152, 256, 64), # Test Tukey + ('hann', 1024, 256, 255), # Test overlapped hann + ('boxcar', 100, 10, 3), # NOLA True, COLA False + ('bartlett', 101, 51, 37), # NOLA True, COLA False + ('hann', 1024, 256, 127), # NOLA True, COLA False + # NOLA True, COLA False: + (('tukey', 0.5), 1152, 256, 14), + ('hann', 1024, 256, 5)]) # NOLA True, COLA False +def test_roundtrip_windows(window, n: int, nperseg: int, noverlap: int): + """Roundtrip test adapted from `test_spectral.TestSTFT`. + + The parameters are taken from the methods test_roundtrip_real(), + test_roundtrip_nola_not_cola(), test_roundtrip_float32(), + test_roundtrip_complex(). + """ + np.random.seed(2394655) + + w = get_window(window, nperseg) + SFT = ShortTimeFFT(w, nperseg - noverlap, fs=1, fft_mode='twosided', + phase_shift=None) + + z = 10 * np.random.randn(n) + 10j * np.random.randn(n) + Sz = SFT.stft(z) + z1 = SFT.istft(Sz, k1=len(z)) + xp_assert_close(z, z1, err_msg="Roundtrip for complex values failed") + + x = 10 * np.random.randn(n) + Sx = SFT.stft(x) + x1 = SFT.istft(Sx, k1=len(z)) + xp_assert_close(x.astype(np.complex128), x1, + err_msg="Roundtrip for float values failed") + + x32 = x.astype(np.float32) + Sx32 = SFT.stft(x32) + x32_1 = SFT.istft(Sx32, k1=len(x32)) + x32_1_r = x32_1.real + xp_assert_close(x32, x32_1_r.astype(np.float32), + err_msg="Roundtrip for 32 Bit float values failed") + xp_assert_close(x32.imag, np.zeros_like(x32.imag), + err_msg="Roundtrip for 32 Bit float values failed") + + +@pytest.mark.parametrize('signal_type', ('real', 'complex')) +def test_roundtrip_complex_window(signal_type): + """Test roundtrip for complex-valued window function + + The purpose of this test is to check if the dual window is calculated + correctly for complex-valued windows. + """ + np.random.seed(1354654) + win = np.exp(2j*np.linspace(0, np.pi, 8)) + SFT = ShortTimeFFT(win, 3, fs=1, fft_mode='twosided') + + z = 10 * np.random.randn(11) + if signal_type == 'complex': + z = z + 2j * z + Sz = SFT.stft(z) + z1 = SFT.istft(Sz, k1=len(z)) + xp_assert_close(z.astype(np.complex128), z1, + err_msg="Roundtrip for complex-valued window failed") + + +def test_average_all_segments(): + """Compare `welch` function with stft mean. + + Ported from `TestSpectrogram.test_average_all_segments` from file + ``test__spectral.py``. + """ + x = np.random.randn(1024) + + fs = 1.0 + window = ('tukey', 0.25) + nperseg, noverlap = 16, 2 + fw, Pw = welch(x, fs, window, nperseg, noverlap) + SFT = ShortTimeFFT.from_window(window, fs, nperseg, noverlap, + fft_mode='onesided2X', scale_to='psd', + phase_shift=None) + # `welch` positions the window differently than the STFT: + P = SFT.spectrogram(x, detr='constant', p0=0, + p1=(len(x)-noverlap)//SFT.hop, k_offset=nperseg//2) + + xp_assert_close(SFT.f, fw) + xp_assert_close(np.mean(P, axis=-1), Pw) + + +@pytest.mark.parametrize('window, N, nperseg, noverlap, mfft', + # from test_roundtrip_padded_FFT: + [('hann', 1024, 256, 128, 512), + ('hann', 1024, 256, 128, 501), + ('boxcar', 100, 10, 0, 33), + (('tukey', 0.5), 1152, 256, 64, 1024), + # from test_roundtrip_padded_signal: + ('boxcar', 101, 10, 0, None), + ('hann', 1000, 256, 128, None), + # from test_roundtrip_boundary_extension: + ('boxcar', 100, 10, 0, None), + ('boxcar', 100, 10, 9, None)]) +@pytest.mark.parametrize('padding', get_args(PAD_TYPE)) +def test_stft_padding_roundtrip(window, N: int, nperseg: int, noverlap: int, + mfft: int, padding): + """Test the parameter 'padding' of `stft` with roundtrips. + + The STFT parametrizations were taken from the methods + `test_roundtrip_padded_FFT`, `test_roundtrip_padded_signal` and + `test_roundtrip_boundary_extension` from class `TestSTFT` in file + ``test_spectral.py``. Note that the ShortTimeFFT does not need the + concept of "boundary extension". + """ + x = normal_distribution.rvs(size=N, random_state=2909) # real signal + z = x * np.exp(1j * np.pi / 4) # complex signal + + SFT = ShortTimeFFT.from_window(window, 1, nperseg, noverlap, + fft_mode='twosided', mfft=mfft) + Sx = SFT.stft(x, padding=padding) + x1 = SFT.istft(Sx, k1=N) + xp_assert_close(x1, x.astype(np.complex128), + err_msg=f"Failed real roundtrip with '{padding}' padding") + + Sz = SFT.stft(z, padding=padding) + z1 = SFT.istft(Sz, k1=N) + xp_assert_close(z1, z, err_msg="Failed complex roundtrip with " + + f" '{padding}' padding") + + +@pytest.mark.parametrize('N_x', (128, 129, 255, 256, 1337)) # signal length +@pytest.mark.parametrize('w_size', (128, 256)) # window length +@pytest.mark.parametrize('t_step', (4, 64)) # SFT time hop +@pytest.mark.parametrize('f_c', (7., 23.)) # frequency of input sine +def test_energy_conservation(N_x: int, w_size: int, t_step: int, f_c: float): + """Test if a `psd`-scaled STFT conserves the L2 norm. + + This test is adapted from MNE-Python [1]_. Besides being battle-tested, + this test has the benefit of using non-standard window including + non-positive values and a 2d input signal. + + Since `ShortTimeFFT` requires the signal length `N_x` to be at least the + window length `w_size`, the parameter `N_x` was changed from + ``(127, 128, 255, 256, 1337)`` to ``(128, 129, 255, 256, 1337)`` to be + more useful. + + .. [1] File ``test_stft.py`` of MNE-Python + https://github.com/mne-tools/mne-python/blob/main/mne/time_frequency/tests/test_stft.py + """ + window = np.sin(np.arange(.5, w_size + .5) / w_size * np.pi) + SFT = ShortTimeFFT(window, t_step, fs=1000, fft_mode='onesided2X', + scale_to='psd') + atol = 2*np.finfo(window.dtype).resolution + N_x = max(N_x, w_size) # minimal sing + # Test with low frequency signal + t = np.arange(N_x).astype(np.float64) + x = np.sin(2 * np.pi * f_c * t * SFT.T) + x = np.array([x, x + 1.]) + X = SFT.stft(x) + xp = SFT.istft(X, k1=N_x) + + max_freq = SFT.f[np.argmax(np.sum(np.abs(X[0]) ** 2, axis=1))] + + assert X.shape[1] == SFT.f_pts + assert np.all(SFT.f >= 0.) + assert np.abs(max_freq - f_c) < 1. + xp_assert_close(x, xp, atol=atol) + + # check L2-norm squared (i.e., energy) conservation: + E_x = np.sum(x**2, axis=-1) * SFT.T # numerical integration + aX2 = X.real**2 + X.imag.real**2 + E_X = np.sum(np.sum(aX2, axis=-1) * SFT.delta_t, axis=-1) * SFT.delta_f + xp_assert_close(E_X, E_x, atol=atol) + + # Test with random signal + np.random.seed(2392795) + x = np.random.randn(2, N_x) + X = SFT.stft(x) + xp = SFT.istft(X, k1=N_x) + + assert X.shape[1] == SFT.f_pts + assert np.all(SFT.f >= 0.) + assert np.abs(max_freq - f_c) < 1. + xp_assert_close(x, xp, atol=atol) + + # check L2-norm squared (i.e., energy) conservation: + E_x = np.sum(x**2, axis=-1) * SFT.T # numeric integration + aX2 = X.real ** 2 + X.imag.real ** 2 + E_X = np.sum(np.sum(aX2, axis=-1) * SFT.delta_t, axis=-1) * SFT.delta_f + xp_assert_close(E_X, E_x, atol=atol) + + # Try with empty array + x = np.zeros((0, N_x)) + X = SFT.stft(x) + xp = SFT.istft(X, k1=N_x) + assert xp.shape == x.shape + + +def test_subscriptable_generic_type(): + assert isinstance(ShortTimeFFT[np.float64], GenericAlias) diff --git a/venv/lib/python3.12/site-packages/scipy/signal/tests/test_signaltools.py b/venv/lib/python3.12/site-packages/scipy/signal/tests/test_signaltools.py new file mode 100644 index 0000000000000000000000000000000000000000..41651947281f8ffcbd32223f3c5475726f28e28c --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/tests/test_signaltools.py @@ -0,0 +1,4782 @@ +import sys +import math +import warnings + +from concurrent.futures import ThreadPoolExecutor, as_completed +from itertools import product +from math import gcd + +import pytest +from pytest import raises as assert_raises +import numpy as np +from numpy.exceptions import ComplexWarning + +from scipy import fft as sp_fft +from scipy.ndimage import correlate1d +from scipy.optimize import fmin, linear_sum_assignment +from scipy import signal +from scipy.signal import ( + correlate, correlate2d, correlation_lags, convolve, convolve2d, + fftconvolve, oaconvolve, choose_conv_method, envelope, + hilbert, hilbert2, lfilter, lfilter_zi, filtfilt, butter, zpk2tf, zpk2sos, + invres, invresz, vectorstrength, lfiltic, tf2sos, sosfilt, sosfiltfilt, + sosfilt_zi, tf2zpk, BadCoefficients, detrend, unique_roots, residue, + residuez) +from scipy.signal.windows import hann +from scipy.signal._signaltools import _filtfilt_gust, _compute_factors, _group_poles +from scipy.signal._upfirdn import _upfirdn_modes +from scipy._lib import _testutils +import scipy._external.array_api_extra as xpx +from scipy._lib._array_api import ( + xp_assert_close, xp_assert_equal, is_numpy, is_torch, is_jax, is_cupy, + assert_array_almost_equal, assert_almost_equal, + xp_copy, xp_size, xp_default_dtype, array_namespace, make_xp_test_case, + make_xp_pytest_param, SCIPY_DEVICE, _xp_copy_to_numpy +) +skip_xp_backends = pytest.mark.skip_xp_backends +xfail_xp_backends = pytest.mark.xfail_xp_backends + +lazy_xp_modules = [signal] + + +@make_xp_test_case(convolve) +class TestConvolve: + + @skip_xp_backends("jax.numpy", + reason="jax returns floats; scipy returns ints; cf gh-6076") + def test_basic(self, xp): + a = xp.asarray([3, 4, 5, 6, 5, 4]) + b = xp.asarray([1, 2, 3]) + c = convolve(a, b) + xp_assert_equal(c, xp.asarray([3, 10, 22, 28, 32, 32, 23, 12])) + + @skip_xp_backends("jax.numpy", + reason="jax returns floats; scipy returns ints; cf gh-6076") + def test_same(self, xp): + a = xp.asarray([3, 4, 5]) + b = xp.asarray([1, 2, 3, 4]) + c = convolve(a, b, mode="same") + xp_assert_equal(c, xp.asarray([10, 22, 34])) + + @skip_xp_backends("jax.numpy", + reason="jax returns floats; scipy returns ints; cf gh-6076") + def test_same_eq(self, xp): + a = xp.asarray([3, 4, 5]) + b = xp.asarray([1, 2, 3]) + c = convolve(a, b, mode="same") + xp_assert_equal(c, xp.asarray([10, 22, 22])) + + def test_complex(self, xp): + x = xp.asarray([1 + 1j, 2 + 1j, 3 + 1j]) + y = xp.asarray([1 + 1j, 2 + 1j]) + z = convolve(x, y) + xp_assert_equal(z, xp.asarray([2j, 2 + 6j, 5 + 8j, 5 + 5j])) + + @xfail_xp_backends("jax.numpy", reason="wrong output dtype") + def test_zero_rank(self, xp): + a = xp.asarray(1289) + b = xp.asarray(4567) + c = convolve(a, b) + xp_assert_equal(c, a * b) + + @skip_xp_backends(np_only=True, reason="pure python") + def test_zero_rank_python_scalars(self, xp): + a = 1289 + b = 4567 + c = convolve(a, b) + assert c == a * b + + @xfail_xp_backends("jax.numpy", reason="disagreement between methods") + def test_broadcastable(self, xp): + a = xp.reshape(xp.arange(27), (3, 3, 3)) + b = xp.arange(3) + for i in range(3): + b_shape = [1]*3 + b_shape[i] = 3 + + x = convolve(a, xp.reshape(b, tuple(b_shape)), method='direct') + y = convolve(a, xp.reshape(b, tuple(b_shape)), method='fft') + xp_assert_close(x, y, atol=1e-14) + + @xfail_xp_backends("jax.numpy", reason="wrong output dtype") + def test_single_element(self, xp): + a = xp.asarray([4967]) + b = xp.asarray([3920]) + c = convolve(a, b) + xp_assert_equal(c, a * b) + + @skip_xp_backends("jax.numpy",) + @skip_xp_backends("cupy") + def test_2d_arrays(self, xp): + a = xp.asarray([[1, 2, 3], [3, 4, 5]]) + b = xp.asarray([[2, 3, 4], [4, 5, 6]]) + c = convolve(a, b) + d = xp.asarray([[2, 7, 16, 17, 12], + [10, 30, 62, 58, 38], + [12, 31, 58, 49, 30]]) + xp_assert_equal(c, d) + + @skip_xp_backends("torch") + @skip_xp_backends("cupy") + def test_input_swapping(self, xp): + small = xp.reshape(xp.arange(8), (2, 2, 2)) + big = 1j * xp.reshape(xp.arange(27, dtype=xp.complex128), (3, 3, 3)) + big += xp.reshape(xp.arange(27, dtype=xp.complex128)[::-1], (3, 3, 3)) + + out_array = xp.asarray( + [[[0 + 0j, 26 + 0j, 25 + 1j, 24 + 2j], + [52 + 0j, 151 + 5j, 145 + 11j, 93 + 11j], + [46 + 6j, 133 + 23j, 127 + 29j, 81 + 23j], + [40 + 12j, 98 + 32j, 93 + 37j, 54 + 24j]], + + [[104 + 0j, 247 + 13j, 237 + 23j, 135 + 21j], + [282 + 30j, 632 + 96j, 604 + 124j, 330 + 86j], + [246 + 66j, 548 + 180j, 520 + 208j, 282 + 134j], + [142 + 66j, 307 + 161j, 289 + 179j, 153 + 107j]], + + [[68 + 36j, 157 + 103j, 147 + 113j, 81 + 75j], + [174 + 138j, 380 + 348j, 352 + 376j, 186 + 230j], + [138 + 174j, 296 + 432j, 268 + 460j, 138 + 278j], + [70 + 138j, 145 + 323j, 127 + 341j, 63 + 197j]], + + [[32 + 72j, 68 + 166j, 59 + 175j, 30 + 100j], + [68 + 192j, 139 + 433j, 117 + 455j, 57 + 255j], + [38 + 222j, 73 + 499j, 51 + 521j, 21 + 291j], + [12 + 144j, 20 + 318j, 7 + 331j, 0 + 182j]]]) + + xp_assert_equal(convolve(small, big, 'full'), out_array) + xp_assert_equal(convolve(big, small, 'full'), out_array) + xp_assert_equal(convolve(small, big, 'same'), + out_array[1:3, 1:3, 1:3]) + xp_assert_equal(convolve(big, small, 'same'), + out_array[0:3, 0:3, 0:3]) + xp_assert_equal(convolve(small, big, 'valid'), + out_array[1:3, 1:3, 1:3]) + xp_assert_equal(convolve(big, small, 'valid'), + out_array[1:3, 1:3, 1:3]) + + def test_invalid_params(self, xp): + a = xp.asarray([3, 4, 5]) + b = xp.asarray([1, 2, 3]) + assert_raises(ValueError, convolve, a, b, mode='spam') + assert_raises(ValueError, convolve, a, b, mode='eggs', method='fft') + assert_raises(ValueError, convolve, a, b, mode='ham', method='direct') + assert_raises(ValueError, convolve, a, b, mode='full', method='bacon') + assert_raises(ValueError, convolve, a, b, mode='same', method='bacon') + + @skip_xp_backends("jax.numpy", reason="dtypes do not match") + def test_valid_mode2(self, xp): + # See gh-5897 + a = xp.asarray([1, 2, 3, 6, 5, 3]) + b = xp.asarray([2, 3, 4, 5, 3, 4, 2, 2, 1]) + expected = xp.asarray([70, 78, 73, 65]) + + out = convolve(a, b, 'valid') + xp_assert_equal(out, expected) + + out = convolve(b, a, 'valid') + xp_assert_equal(out, expected) + + a = xp.asarray([1 + 5j, 2 - 1j, 3 + 0j]) + b = xp.asarray([2 - 3j, 1 + 0j]) + expected = xp.asarray([2 - 3j, 8 - 10j]) + + out = convolve(a, b, 'valid') + xp_assert_equal(out, expected) + + out = convolve(b, a, 'valid') + xp_assert_equal(out, expected) + + @skip_xp_backends("jax.numpy", reason="dtypes do not match") + def test_same_mode(self, xp): + a = xp.asarray([1, 2, 3, 3, 1, 2]) + b = xp.asarray([1, 4, 3, 4, 5, 6, 7, 4, 3, 2, 1, 1, 3]) + c = convolve(a, b, 'same') + d = xp.asarray([57, 61, 63, 57, 45, 36]) + xp_assert_equal(c, d) + + @skip_xp_backends("cupy", reason="different exception") + def test_invalid_shapes(self, xp): + # By "invalid," we mean that no one + # array has dimensions that are all at + # least as large as the corresponding + # dimensions of the other array. This + # setup should throw a ValueError. + a = xp.reshape(xp.arange(1, 7), (2, 3)) + b = xp.reshape(xp.arange(-6, 0), (3, 2)) + + assert_raises(ValueError, convolve, *(a, b), **{'mode': 'valid'}) + assert_raises(ValueError, convolve, *(b, a), **{'mode': 'valid'}) + + @skip_xp_backends(np_only=True, reason="TODO: convert this test") + def test_convolve_method(self, xp, n=100): + # this types data structure was manually encoded instead of + # using custom filters on the soon-to-be-removed np.sctypes + types = {'uint16', 'uint64', 'int64', 'int32', + 'complex128', 'float64', 'float16', + 'complex64', 'float32', 'int16', + 'uint8', 'uint32', 'int8', 'bool'} + args = [(t1, t2, mode) for t1 in types for t2 in types + for mode in ['valid', 'full', 'same']] + + # These are random arrays, which means test is much stronger than + # convolving testing by convolving two np.ones arrays + rng = np.random.RandomState(42) + array_types = {'i': rng.choice([0, 1], size=n), + 'f': rng.randn(n)} + array_types['b'] = array_types['u'] = array_types['i'] + array_types['c'] = array_types['f'] + 0.5j*array_types['f'] + + for t1, t2, mode in args: + x1 = array_types[np.dtype(t1).kind].astype(t1) + x2 = array_types[np.dtype(t2).kind].astype(t2) + + results = {key: convolve(x1, x2, method=key, mode=mode) + for key in ['fft', 'direct']} + + assert results['fft'].dtype == results['direct'].dtype + + if 'bool' in t1 and 'bool' in t2: + assert choose_conv_method(x1, x2) == 'direct' + continue + + # Found by experiment. Found approx smallest value for (rtol, atol) + # threshold to have tests pass. + if any([t in {'complex64', 'float32'} for t in [t1, t2]]): + kwargs = {'rtol': 1.0e-4, 'atol': 1e-6} + elif 'float16' in [t1, t2]: + # atol is default for np.allclose + kwargs = {'rtol': 1e-3, 'atol': 1e-3} + else: + # defaults for np.allclose (different from assert_allclose) + kwargs = {'rtol': 1e-5, 'atol': 1e-8} + + xp_assert_close(results['fft'], results['direct'], **kwargs) + + @skip_xp_backends("jax.numpy", reason="dtypes do not match") + def test_convolve_method_large_input(self, xp): + # This is really a test that convolving two large integers goes to the + # direct method even if they're in the fft method. + for n in [10, 20, 50, 51, 52, 53, 54, 60, 62]: + z = xp.asarray([2**n], dtype=xp.int64) + fft = convolve(z, z, method='fft') + direct = convolve(z, z, method='direct') + + # this is the case when integer precision gets to us + # issue #6076 has more detail, hopefully more tests after resolved + # # XXX: revisit check_dtype under np 2.0: 32bit linux & windows + if n < 50: + val = xp.asarray([2**(2*n)]) + xp_assert_equal(fft, direct) + xp_assert_equal(fft, val, check_dtype=False) + xp_assert_equal(direct, val, check_dtype=False) + + @skip_xp_backends(np_only=True) + def test_mismatched_dims(self, xp): + # Input arrays should have the same number of dimensions + assert_raises(ValueError, convolve, [1], 2, method='direct') + assert_raises(ValueError, convolve, 1, [2], method='direct') + assert_raises(ValueError, convolve, [1], 2, method='fft') + assert_raises(ValueError, convolve, 1, [2], method='fft') + assert_raises(ValueError, convolve, [1], [[2]]) + assert_raises(ValueError, convolve, [3], 2) + + +@make_xp_test_case(convolve2d) +class TestConvolve2d: + + @skip_xp_backends("jax.numpy", reason="dtypes do not match") + def test_2d_arrays(self, xp): + a = xp.asarray([[1, 2, 3], [3, 4, 5]]) + b = xp.asarray([[2, 3, 4], [4, 5, 6]]) + d = xp.asarray([[2, 7, 16, 17, 12], + [10, 30, 62, 58, 38], + [12, 31, 58, 49, 30]]) + e = convolve2d(a, b) + xp_assert_equal(e, d) + + @skip_xp_backends("jax.numpy", reason="dtypes do not match") + def test_valid_mode(self, xp): + e = xp.asarray([[2, 3, 4, 5, 6, 7, 8], [4, 5, 6, 7, 8, 9, 10]]) + f = xp.asarray([[1, 2, 3], [3, 4, 5]]) + h = xp.asarray([[62, 80, 98, 116, 134]]) + + g = convolve2d(e, f, 'valid') + xp_assert_equal(g, h) + + # See gh-5897 + g = convolve2d(f, e, 'valid') + xp_assert_equal(g, h) + + @skip_xp_backends("torch", reason="dtypes do not match") + def test_valid_mode_complx(self, xp): + e = xp.asarray([[2, 3, 4, 5, 6, 7, 8], [4, 5, 6, 7, 8, 9, 10]]) + f = xp.asarray([[1, 2, 3], [3, 4, 5]], dtype=xp.complex128) + 1j + h = xp.asarray([[62.+24.j, 80.+30.j, 98.+36.j, 116.+42.j, 134.+48.j]]) + + g = convolve2d(e, f, 'valid') + xp_assert_close(g, h) + + # See gh-5897 + g = convolve2d(f, e, 'valid') + xp_assert_equal(g, h) + + @skip_xp_backends("jax.numpy", reason="jax only allows fillvalue=0") + def test_fillvalue(self, xp): + a = xp.asarray([[1, 2, 3], [3, 4, 5]]) + b = xp.asarray([[2, 3, 4], [4, 5, 6]]) + fillval = 1 + c = convolve2d(a, b, 'full', 'fill', fillval) + d = xp.asarray([[24, 26, 31, 34, 32], + [28, 40, 62, 64, 52], + [32, 46, 67, 62, 48]]) + xp_assert_equal(c, d) + + def test_fillvalue_errors(self, xp): + msg = "could not cast `fillvalue` directly to the output " + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", "Casting complex values", ComplexWarning) + with assert_raises(ValueError, match=msg): + convolve2d([[1]], [[1, 2]], fillvalue=1j) + + msg = "`fillvalue` must be scalar or an array with " + with assert_raises(ValueError, match=msg): + convolve2d([[1]], [[1, 2]], fillvalue=[1, 2]) + + def test_fillvalue_empty(self, xp): + # Check that fillvalue being empty raises an error: + assert_raises(ValueError, convolve2d, [[1]], [[1, 2]], + fillvalue=[]) + + @skip_xp_backends("jax.numpy", reason="jax only supports boundary='fill'") + def test_wrap_boundary(self, xp): + a = xp.asarray([[1, 2, 3], [3, 4, 5]]) + b = xp.asarray([[2, 3, 4], [4, 5, 6]]) + c = convolve2d(a, b, 'full', 'wrap') + d = xp.asarray([[80, 80, 74, 80, 80], + [68, 68, 62, 68, 68], + [80, 80, 74, 80, 80]]) + xp_assert_equal(c, d) + + @skip_xp_backends("jax.numpy", reason="jax only supports boundary='fill'") + def test_sym_boundary(self, xp): + a = xp.asarray([[1, 2, 3], [3, 4, 5]]) + b = xp.asarray([[2, 3, 4], [4, 5, 6]]) + c = convolve2d(a, b, 'full', 'symm') + d = xp.asarray([[34, 30, 44, 62, 66], + [52, 48, 62, 80, 84], + [82, 78, 92, 110, 114]]) + xp_assert_equal(c, d) + + @skip_xp_backends("jax.numpy", reason="jax only supports boundary='fill'") + @pytest.mark.parametrize('func', [convolve2d, correlate2d]) + @pytest.mark.parametrize('boundary, expected', + [('symm', [[37.0, 42.0, 44.0, 45.0]]), + ('wrap', [[43.0, 44.0, 42.0, 39.0]])]) + def test_same_with_boundary(self, func, boundary, expected, xp): + # Test boundary='symm' and boundary='wrap' with a "long" kernel. + # The size of the kernel requires that the values in the "image" + # be extended more than once to handle the requested boundary method. + # This is a regression test for gh-8684 and gh-8814. + image = xp.asarray([[2.0, -1.0, 3.0, 4.0]]) + kernel = xp.ones((1, 21)) + result = func(image, kernel, mode='same', boundary=boundary) + # The expected results were calculated "by hand". Because the + # kernel is all ones, the same result is expected for convolve2d + # and correlate2d. + xp_assert_equal(result, xp.asarray(expected)) + + @skip_xp_backends("jax.numpy", reason="jax only supports boundary='fill'") + def test_boundary_extension_same(self, xp): + # Regression test for gh-12686. + # Use ndimage.convolve with appropriate arguments to create the + # expected result. + import scipy.ndimage as ndi + a = xp.reshape(xp.arange(1, 10*3+1, dtype=xp.float64), (10, 3)) + b = xp.reshape(xp.arange(1, 10*10+1, dtype=xp.float64), (10, 10)) + c = convolve2d(a, b, mode='same', boundary='wrap') + xp_assert_equal(c, ndi.convolve(a, b, mode='wrap', origin=(-1, -1))) + + @skip_xp_backends("jax.numpy", reason="jax only supports boundary='fill'") + def test_boundary_extension_full(self, xp): + # Regression test for gh-12686. + # Use ndimage.convolve with appropriate arguments to create the + # expected result. + import scipy.ndimage as ndi + a = xp.reshape(xp.arange(1, 3*3+1, dtype=xp.float64), (3, 3)) + b = xp.reshape(xp.arange(1, 6*6+1, dtype=xp.float64), (6, 6)) + c = convolve2d(a, b, mode='full', boundary='wrap') + + a_np = np.arange(1, 3*3 +1, dtype=float).reshape(3, 3) + apad_np = np.pad(a_np, ((3, 3), (3, 3)), 'wrap') + apad = xp.asarray(apad_np) + xp_assert_equal(c, xp.asarray(ndi.convolve(apad, b, mode='wrap')[:-1, :-1])) + + def test_invalid_shapes(self, xp): + # By "invalid," we mean that no one + # array has dimensions that are all at + # least as large as the corresponding + # dimensions of the other array. This + # setup should throw a ValueError. + a = xp.reshape(xp.arange(1, 7), (2, 3)) + b = xp.reshape(xp.arange(-6, 0), (3, 2)) + + assert_raises(ValueError, convolve2d, *(a, b), **{'mode': 'valid'}) + assert_raises(ValueError, convolve2d, *(b, a), **{'mode': 'valid'}) + + @skip_xp_backends("jax.numpy", + reason="jax returns floats; scipy returns ints; cf gh-6076") + def test_same_mode(self, xp): + e = xp.asarray([[1, 2, 3], [3, 4, 5]]) + f = xp.asarray([[2, 3, 4, 5, 6, 7, 8], [4, 5, 6, 7, 8, 9, 10]]) + g = convolve2d(e, f, 'same') + h = xp.asarray([[22, 28, 34], + [80, 98, 116]]) + xp_assert_equal(g, h) + + @skip_xp_backends("jax.numpy", + reason="jax returns floats; scipy returns ints; cf gh-6076") + def test_valid_mode2(self, xp): + # See gh-5897 + e = xp.asarray([[1, 2, 3], [3, 4, 5]]) + f = xp.asarray([[2, 3, 4, 5, 6, 7, 8], [4, 5, 6, 7, 8, 9, 10]]) + expected = xp.asarray([[62, 80, 98, 116, 134]]) + + out = convolve2d(e, f, 'valid') + xp_assert_equal(out, expected) + + out = convolve2d(f, e, 'valid') + xp_assert_equal(out, expected) + + e = xp.asarray([[1 + 1j, 2 - 3j], [3 + 1j, 4 + 0j]]) + f = xp.asarray([[2 - 1j, 3 + 2j, 4 + 0j], [4 - 0j, 5 + 1j, 6 - 3j]]) + expected = xp.asarray([[27 - 1j, 46. + 2j]]) + + out = convolve2d(e, f, 'valid') + xp_assert_equal(out, expected) + + # See gh-5897 + out = convolve2d(f, e, 'valid') + xp_assert_equal(out, expected) + + @skip_xp_backends("torch", + reason="only integer tensors of a single element can be converted" + ) + def test_consistency_convolve_funcs(self, xp): + # Compare np.convolve, signal.convolve, signal.convolve2d + a = xp.arange(5) + b = xp.asarray([3.2, 1.4, 3]) + a_np = _xp_copy_to_numpy(a) + b_np = _xp_copy_to_numpy(b) + + for mode in ['full', 'valid', 'same']: + xp_assert_close( + xp.asarray(np.convolve(a_np, b_np, mode=mode)), + signal.convolve(a, b, mode=mode) + ) + xp_assert_close( + xp.squeeze( + signal.convolve2d(a[None, :], b[None, :], mode=mode), + axis=0 + ), + signal.convolve(a, b, mode=mode) + ) + + def test_invalid_dims(self, xp): + assert_raises(ValueError, convolve2d, 3, 4) + assert_raises(ValueError, convolve2d, [3], [4]) + assert_raises(ValueError, convolve2d, [[[3]]], [[[4]]]) + + @pytest.mark.slow + @pytest.mark.xfail_on_32bit("Can't create large array for test") + @skip_xp_backends(np_only=True, reason="stride_tricks") + def test_large_array(self, xp): + # Test indexing doesn't overflow an int (gh-10761) + n = 2**31 // (1000 * xp.int64().itemsize) + _testutils.check_free_memory(2 * n * 1001 * np.int64().itemsize / 1e6) + + # Create a chequered pattern of 1s and 0s + a = xp.zeros(1001 * n, dtype=xp.int64) + a[::2] = 1 + a = np.lib.stride_tricks.as_strided(a, shape=(n, 1000), strides=(8008, 8)) + + count = signal.convolve2d(a, [[1, 1]]) + fails = np.where(count > 1) + assert fails[0].size == 0 + + +@make_xp_test_case(fftconvolve) +class TestFFTConvolve: + + @pytest.mark.parametrize('axes', ['', None, 0, [0], -1, [-1]]) + def test_real(self, axes, xp): + a = xp.asarray([1, 2, 3]) + expected = xp.asarray([1, 4, 10, 12, 9.]) + + if axes == '': + out = fftconvolve(a, a) + else: + if isinstance(axes, list): + axes = tuple(axes) + out = fftconvolve(a, a, axes=axes) + + xp_assert_close(out, expected, atol=1.5e-6) + + @pytest.mark.parametrize('axes', [1, [1], -1, [-1]]) + def test_real_axes(self, axes, xp): + a = xp.asarray([1, 2, 3]) + expected = xp.asarray([1, 4, 10, 12, 9.]) + + a = xp.tile(a, (2, 1)) + expected = xp.tile(expected, (2, 1)) + + if isinstance(axes, list): + axes = tuple(axes) + + out = fftconvolve(a, a, axes=axes) + xp_assert_close(out, expected, atol=1.5e-6) + + @pytest.mark.parametrize('axes', ['', None, 0, [0], -1, [-1]]) + def test_complex(self, axes, xp): + a = xp.asarray([1 + 1j, 2 + 2j, 3 + 3j]) + expected = xp.asarray([0 + 2j, 0 + 8j, 0 + 20j, 0 + 24j, 0 + 18j]) + + if axes == '': + out = fftconvolve(a, a) + else: + if isinstance(axes, list): + axes = tuple(axes) + out = fftconvolve(a, a, axes=axes) + xp_assert_close(out, expected, atol=1.5e-6) + + @pytest.mark.parametrize('axes', [1, [1], -1, [-1]]) + def test_complex_axes(self, axes, xp): + a = xp.asarray([1 + 1j, 2 + 2j, 3 + 3j]) + expected = xp.asarray([0 + 2j, 0 + 8j, 0 + 20j, 0 + 24j, 0 + 18j]) + + a = xp.tile(a, (2, 1)) + expected = xp.tile(expected, (2, 1)) + + if isinstance(axes, list): + axes = tuple(axes) + + out = fftconvolve(a, a, axes=axes) + xp_assert_close(out, expected, atol=1.5e-6) + + @pytest.mark.parametrize('axes', ['', + None, + [0, 1], + [1, 0], + [0, -1], + [-1, 0], + [-2, 1], + [1, -2], + [-2, -1], + [-1, -2]]) + def test_2d_real_same(self, axes, xp): + a = xp.asarray([[1.0, 2, 3], + [4, 5, 6]]) + expected = xp.asarray([[1.0, 4, 10, 12, 9], + [8, 26, 56, 54, 36], + [16, 40, 73, 60, 36]]) + + if axes == '': + out = fftconvolve(a, a) + else: + if isinstance(axes, list): + axes = tuple(axes) + out = fftconvolve(a, a, axes=axes) + xp_assert_close(out, expected) + + @pytest.mark.parametrize('axes', [[1, 2], + [2, 1], + [1, -1], + [-1, 1], + [-2, 2], + [2, -2], + [-2, -1], + [-1, -2]]) + def test_2d_real_same_axes(self, axes, xp): + a = xp.asarray([[1, 2, 3], + [4, 5, 6]]) + expected = xp.asarray([[1, 4, 10, 12, 9], + [8, 26, 56, 54, 36], + [16, 40, 73, 60, 36]]) + + a = xp.tile(a, (2, 1, 1)) + expected = xp.tile(expected, (2, 1, 1)) + + if isinstance(axes, list): + axes = tuple(axes) + + out = fftconvolve(a, a, axes=axes) + xp_assert_close(out, expected, atol=1.5e-6, check_dtype=False) + + @pytest.mark.parametrize('axes', ['', + None, + [0, 1], + [1, 0], + [0, -1], + [-1, 0], + [-2, 1], + [1, -2], + [-2, -1], + [-1, -2]]) + def test_2d_complex_same(self, axes, xp): + a = xp.asarray([[1 + 2j, 3 + 4j, 5 + 6j], + [2 + 1j, 4 + 3j, 6 + 5j]]) + expected = xp.asarray([ + [-3 + 4j, -10 + 20j, -21 + 56j, -18 + 76j, -11 + 60j], + [10j, 44j, 118j, 156j, 122j], + [3 + 4j, 10 + 20j, 21 + 56j, 18 + 76j, 11 + 60j] + ]) + + if axes == '': + out = fftconvolve(a, a) + else: + if isinstance(axes, list): + axes = tuple(axes) + out = fftconvolve(a, a, axes=axes) + + xp_assert_close(out, expected, atol=1.5e-6) + + @pytest.mark.parametrize('axes', [[1, 2], + [2, 1], + [1, -1], + [-1, 1], + [-2, 2], + [2, -2], + [-2, -1], + [-1, -2]]) + def test_2d_complex_same_axes(self, axes, xp): + a = xp.asarray([[1 + 2j, 3 + 4j, 5 + 6j], + [2 + 1j, 4 + 3j, 6 + 5j]]) + expected = xp.asarray([ + [-3 + 4j, -10 + 20j, -21 + 56j, -18 + 76j, -11 + 60j], + [10j, 44j, 118j, 156j, 122j], + [3 + 4j, 10 + 20j, 21 + 56j, 18 + 76j, 11 + 60j] + ]) + + a = xp.tile(a, (2, 1, 1)) + expected = xp.tile(expected, (2, 1, 1)) + + if isinstance(axes, list): + axes = tuple(axes) + + out = fftconvolve(a, a, axes=axes) + xp_assert_close(out, expected, atol=1.5e-6) + + @pytest.mark.parametrize('axes', ['', None, 0, [0], -1, [-1]]) + def test_real_same_mode(self, axes, xp): + a = xp.asarray([1, 2, 3]) + b = xp.asarray([3, 3, 5, 6, 8, 7, 9, 0, 1]) + expected_1 = xp.asarray([35., 41., 47.]) + expected_2 = xp.asarray([9., 20., 25., 35., 41., 47., 39., 28., 2.]) + + if axes == '': + out = fftconvolve(a, b, 'same') + else: + if isinstance(axes, list): + axes = tuple(axes) + out = fftconvolve(a, b, 'same', axes=axes) + xp_assert_close(out, expected_1) + + if axes == '': + out = fftconvolve(b, a, 'same') + else: + if isinstance(axes, list): + axes = tuple(axes) + out = fftconvolve(b, a, 'same', axes=axes) + xp_assert_close(out, expected_2, atol=1.5e-6) + + @pytest.mark.parametrize('axes', [1, -1, [1], [-1]]) + def test_real_same_mode_axes(self, axes, xp): + a = xp.asarray([1, 2, 3]) + b = xp.asarray([3, 3, 5, 6, 8, 7, 9, 0, 1]) + expected_1 = xp.asarray([35., 41., 47.]) + expected_2 = xp.asarray([9., 20., 25., 35., 41., 47., 39., 28., 2.]) + + a = xp.tile(a, (2, 1)) + b = xp.tile(b, (2, 1)) + expected_1 = xp.tile(expected_1, (2, 1)) + expected_2 = xp.tile(expected_2, (2, 1)) + + if isinstance(axes, list): + axes = tuple(axes) + + out = fftconvolve(a, b, 'same', axes=axes) + xp_assert_close(out, expected_1, atol=1.5e-6) + + out = fftconvolve(b, a, 'same', axes=axes) + xp_assert_close(out, expected_2, atol=1.5e-6) + + @pytest.mark.parametrize('axes', ['', None, 0, [0], -1, [-1]]) + def test_valid_mode_real(self, axes, xp): + # See gh-5897 + a = xp.asarray([3, 2, 1]) + b = xp.asarray([3, 3, 5, 6, 8, 7, 9, 0, 1]) + expected = xp.asarray([24., 31., 41., 43., 49., 25., 12.]) + + if axes == '': + out = fftconvolve(a, b, 'valid') + else: + if isinstance(axes, list): + axes = tuple(axes) + out = fftconvolve(a, b, 'valid', axes=axes) + xp_assert_close(out, expected, atol=1.5e-6) + + if axes == '': + out = fftconvolve(b, a, 'valid') + else: + if isinstance(axes, list): + axes = tuple(axes) + out = fftconvolve(b, a, 'valid', axes=axes) + xp_assert_close(out, expected, atol=1.5e-6) + + @pytest.mark.parametrize('axes', [1, [1]]) + def test_valid_mode_real_axes(self, axes, xp): + # See gh-5897 + a = xp.asarray([3, 2, 1]) + b = xp.asarray([3, 3, 5, 6, 8, 7, 9, 0, 1]) + expected = xp.asarray([24., 31., 41., 43., 49., 25., 12.]) + + a = xp.tile(a, (2, 1)) + b = xp.tile(b, (2, 1)) + expected = xp.tile(expected, (2, 1)) + + if isinstance(axes, list): + axes = tuple(axes) + + out = fftconvolve(a, b, 'valid', axes=axes) + xp_assert_close(out, expected, atol=1.5e-6) + + @pytest.mark.parametrize('axes', ['', None, 0, [0], -1, [-1]]) + def test_valid_mode_complex(self, axes, xp): + a = xp.asarray([3 - 1j, 2 + 7j, 1 + 0j]) + b = xp.asarray([3 + 2j, 3 - 3j, 5 + 0j, 6 - 1j, 8 + 0j]) + expected = xp.asarray([45. + 12.j, 30. + 23.j, 48 + 32.j]) + + if axes == '': + out = fftconvolve(a, b, 'valid') + else: + if isinstance(axes, list): + axes = tuple(axes) + out = fftconvolve(a, b, 'valid', axes=axes) + xp_assert_close(out, expected, atol=1.5e-6) + + if axes == '': + out = fftconvolve(b, a, 'valid') + else: + if isinstance(axes, list): + axes = tuple(axes) + out = fftconvolve(b, a, 'valid', axes=axes) + xp_assert_close(out, expected, atol=1.5e-6) + + @pytest.mark.parametrize('axes', [1, [1], -1, [-1]]) + def test_valid_mode_complex_axes(self, axes, xp): + a = xp.asarray([3 - 1j, 2 + 7j, 1 + 0j]) + b = xp.asarray([3 + 2j, 3 - 3j, 5 + 0j, 6 - 1j, 8 + 0j]) + expected = xp.asarray([45. + 12.j, 30. + 23.j, 48 + 32.j]) + + a = xp.tile(a, (2, 1)) + b = xp.tile(b, (2, 1)) + expected = xp.tile(expected, (2, 1)) + + if isinstance(axes, list): + axes = tuple(axes) + + out = fftconvolve(a, b, 'valid', axes=axes) + xp_assert_close(out, expected, atol=1.5e-6) + + out = fftconvolve(b, a, 'valid', axes=axes) + xp_assert_close(out, expected, atol=1.5e-6) + + @skip_xp_backends("jax.numpy", reason="mapped axes must have same shape") + def test_valid_mode_ignore_nonaxes(self, xp): + # See gh-5897 + a = xp.asarray([3, 2, 1]) + b = xp.asarray([3, 3, 5, 6, 8, 7, 9, 0, 1]) + expected = xp.asarray([24., 31., 41., 43., 49., 25., 12.]) + + a = xp.tile(a, (2, 1)) + b = xp.tile(b, (1, 1)) + expected = xp.tile(expected, (2, 1)) + + out = fftconvolve(a, b, 'valid', axes=1) + xp_assert_close(out, expected, atol=1.5e-6) + + @xfail_xp_backends("cupy", reason="dtypes do not match") + @xfail_xp_backends("jax.numpy", reason="assorted error messages") + @pytest.mark.parametrize("a,b", [([], []), ([5, 6], []), ([], [7])]) + def test_empty(self, a, b, xp): + # Regression test for #1745: crashes with 0-length input. + xp_assert_equal( + fftconvolve(xp.asarray(a), xp.asarray(b)), + xp.asarray([]), + ) + + @skip_xp_backends("jax.numpy", reason="jnp.pad: pad_width with nd=0") + def test_zero_rank(self, xp): + a = xp.asarray(4967) + b = xp.asarray(3920) + out = fftconvolve(a, b) + xp_assert_equal(out, a * b) + + def test_single_element(self, xp): + a = xp.asarray([4967]) + b = xp.asarray([3920]) + out = fftconvolve(a, b) + xp_assert_equal(out, + xp.asarray(a * b, dtype=out.dtype)) + + @pytest.mark.parametrize('axes', ['', None, 0, [0], -1, [-1]]) + def test_random_data(self, axes, xp): + rng = np.random.default_rng(1234) + a_np = np.random.rand(1233) + 1j * rng.random(1233) + b_np = np.random.rand(1321) + 1j * rng.random(1321) + expected = xp.asarray(np.convolve(a_np, b_np, 'full')) + a = xp.asarray(a_np) + b = xp.asarray(b_np) + + if axes == '': + out = fftconvolve(a, b, 'full') + else: + if isinstance(axes, list): + axes = tuple(axes) + out = fftconvolve(a, b, 'full', axes=axes) + xp_assert_close(out, expected, rtol=1e-10) + + @pytest.mark.parametrize('axes', [1, [1], -1, [-1]]) + def test_random_data_axes(self, axes, xp): + rng = np.random.default_rng(1234) + a_np = np.random.rand(1233) + 1j * rng.random(1233) + b_np = np.random.rand(1321) + 1j * rng.random(1321) + expected = np.convolve(a_np, b_np, 'full') + a_np = np.tile(a_np, [2, 1]) + b_np = np.tile(b_np, [2, 1]) + expected = xp.asarray(np.tile(expected, [2, 1])) + + a = xp.asarray(a_np) + b = xp.asarray(b_np) + + if isinstance(axes, list): + axes = tuple(axes) + + out = fftconvolve(a, b, 'full', axes=axes) + xp_assert_close(out, expected, rtol=1e-10) + + @xfail_xp_backends(np_only=True, reason="TODO: swapaxes") + @pytest.mark.parametrize('axes', [[1, 4], + [4, 1], + [1, -1], + [-1, 1], + [-4, 4], + [4, -4], + [-4, -1], + [-1, -4]]) + def test_random_data_multidim_axes(self, axes, xp): + a_shape, b_shape = (123, 22), (132, 11) + rng = np.random.default_rng(1234) + a = xp.asarray(np.random.rand(*a_shape) + 1j * rng.random(a_shape)) + b = xp.asarray(np.random.rand(*b_shape) + 1j * rng.random(b_shape)) + expected = convolve2d(a, b, 'full') + + a = a[:, :, None, None, None] + b = b[:, :, None, None, None] + expected = expected[:, :, None, None, None] + + a = xp.moveaxis(a.swapaxes(0, 2), 1, 4) + b = xp.moveaxis(b.swapaxes(0, 2), 1, 4) + expected = xp.moveaxis(expected.swapaxes(0, 2), 1, 4) + + # use 1 for dimension 2 in a and 3 in b to test broadcasting + a = xp.tile(a, (2, 1, 3, 1, 1)) + b = xp.tile(b, (2, 1, 1, 4, 1)) + expected = xp.tile(expected, (2, 1, 3, 4, 1)) + + out = fftconvolve(a, b, 'full', axes=axes) + xp_assert_close(out, expected, rtol=1e-10, atol=1e-10) + + @pytest.mark.slow + @pytest.mark.parametrize( + 'n', + list(range(1, 100)) + + list(range(1000, 1500)) + + np.random.RandomState(1234).randint(1001, 10000, 5).tolist()) + def test_many_sizes(self, n, xp): + a_np = np.random.rand(n) + 1j * np.random.rand(n) + b_np = np.random.rand(n) + 1j * np.random.rand(n) + expected = xp.asarray(np.convolve(a_np, b_np, 'full')) + a = xp.asarray(a_np) + b = xp.asarray(b_np) + + out = fftconvolve(a, b, 'full') + xp_assert_close(out, expected, atol=1e-10) + + out = fftconvolve(a, b, 'full', axes=(0,)) + xp_assert_close(out, expected, atol=1e-10) + + @skip_xp_backends(np_only=True) + def test_fft_nan(self, xp): + n = 1000 + rng = np.random.default_rng(43876432987) + sig_nan = xp.asarray(rng.standard_normal(n)) + + for val in [np.nan, np.inf]: + sig_nan[100] = val + coeffs = xp.asarray(signal.firwin(200, 0.2)) + + msg = "Use of fft convolution.*|invalid value encountered.*" + with pytest.warns(RuntimeWarning, match=msg): + signal.convolve(sig_nan, coeffs, mode='same', method='fft') + + +def fftconvolve_err(*args, **kwargs): + raise RuntimeError('Fell back to fftconvolve') + + +def gen_oa_shapes(sizes): + return [(a, b) for a, b in product(sizes, repeat=2) + if abs(a - b) > 3] + + +def gen_oa_shapes_2d(sizes): + shapes0 = gen_oa_shapes(sizes) + shapes1 = gen_oa_shapes(sizes) + shapes = [ishapes0+ishapes1 for ishapes0, ishapes1 in + zip(shapes0, shapes1)] + + modes = ['full', 'valid', 'same'] + return [ishapes+(imode,) for ishapes, imode in product(shapes, modes) + if imode != 'valid' or + (ishapes[0] > ishapes[1] and ishapes[2] > ishapes[3]) or + (ishapes[0] < ishapes[1] and ishapes[2] < ishapes[3])] + + +def gen_oa_shapes_eq(sizes): + return [(a, b) for a, b in product(sizes, repeat=2) + if a >= b] + + +@make_xp_test_case(oaconvolve) +class TestOAConvolve: + @pytest.mark.slow() + @pytest.mark.parametrize('shape_a_0, shape_b_0', + gen_oa_shapes_eq(list(range(1, 100, 1)) + + list(range(100, 1000, 23))) + ) + def test_real_manylens(self, shape_a_0, shape_b_0, xp): + a = np.random.rand(shape_a_0) + b = np.random.rand(shape_b_0) + expected = xp.asarray(fftconvolve(a, b)) + a = xp.asarray(a) + b = xp.asarray(b) + + out = oaconvolve(a, b) + + assert_array_almost_equal(out, expected) + + @pytest.mark.parametrize('shape_a_0, shape_b_0', + gen_oa_shapes([50, 47, 6, 4, 1])) + @pytest.mark.parametrize('is_complex', [True, False]) + @pytest.mark.parametrize('mode', ['full', 'valid', 'same']) + def test_1d_noaxes(self, shape_a_0, shape_b_0, + is_complex, mode, monkeypatch, xp): + a = np.random.rand(shape_a_0) + b = np.random.rand(shape_b_0) + if is_complex: + a = a + 1j*np.random.rand(shape_a_0) + b = b + 1j*np.random.rand(shape_b_0) + expected = xp.asarray(fftconvolve(a, b, mode=mode)) + a = xp.asarray(a) + b = xp.asarray(b) + + monkeypatch.setattr(signal._signaltools, 'fftconvolve', + fftconvolve_err) + out = oaconvolve(a, b, mode=mode) + + assert_array_almost_equal(out, expected) + + @pytest.mark.parametrize('axes', [0, 1]) + @pytest.mark.parametrize('shape_a_0, shape_b_0', + gen_oa_shapes([50, 47, 6, 4])) + @pytest.mark.parametrize('shape_a_extra', [1, 3]) + @pytest.mark.parametrize('shape_b_extra', [1, 3]) + @pytest.mark.parametrize('is_complex', [True, False]) + @pytest.mark.parametrize('mode', ['full', 'valid', 'same']) + def test_1d_axes(self, axes, shape_a_0, shape_b_0, + shape_a_extra, shape_b_extra, + is_complex, mode, monkeypatch, xp): + ax_a = [shape_a_extra]*2 + ax_b = [shape_b_extra]*2 + ax_a[axes] = shape_a_0 + ax_b[axes] = shape_b_0 + + a = np.random.rand(*ax_a) + b = np.random.rand(*ax_b) + if is_complex: + a = a + 1j*np.random.rand(*ax_a) + b = b + 1j*np.random.rand(*ax_b) + + expected = xp.asarray(fftconvolve(a, b, mode=mode, axes=axes)) + a = xp.asarray(a) + b = xp.asarray(b) + + monkeypatch.setattr(signal._signaltools, 'fftconvolve', + fftconvolve_err) + out = oaconvolve(a, b, mode=mode, axes=axes) + + assert_array_almost_equal(out, expected) + + @pytest.mark.parametrize('shape_a_0, shape_b_0, ' + 'shape_a_1, shape_b_1, mode', + gen_oa_shapes_2d([50, 47, 6, 4])) + @pytest.mark.parametrize('is_complex', [True, False]) + def test_2d_noaxes(self, shape_a_0, shape_b_0, + shape_a_1, shape_b_1, mode, + is_complex, monkeypatch, xp): + a = np.random.rand(shape_a_0, shape_a_1) + b = np.random.rand(shape_b_0, shape_b_1) + if is_complex: + a = a + 1j*np.random.rand(shape_a_0, shape_a_1) + b = b + 1j*np.random.rand(shape_b_0, shape_b_1) + + expected = xp.asarray(fftconvolve(a, b, mode=mode)) + a = xp.asarray(a) + b = xp.asarray(b) + + monkeypatch.setattr(signal._signaltools, 'fftconvolve', + fftconvolve_err) + out = oaconvolve(a, b, mode=mode) + + assert_array_almost_equal(out, expected) + + @pytest.mark.parametrize('axes', [[0, 1], [0, 2], [1, 2]]) + @pytest.mark.parametrize('shape_a_0, shape_b_0, ' + 'shape_a_1, shape_b_1, mode', + gen_oa_shapes_2d([50, 47, 6, 4])) + @pytest.mark.parametrize('shape_a_extra', [1, 3]) + @pytest.mark.parametrize('shape_b_extra', [1, 3]) + @pytest.mark.parametrize('is_complex', [True, False]) + def test_2d_axes(self, axes, shape_a_0, shape_b_0, + shape_a_1, shape_b_1, mode, + shape_a_extra, shape_b_extra, + is_complex, monkeypatch, xp): + ax_a = [shape_a_extra]*3 + ax_b = [shape_b_extra]*3 + ax_a[axes[0]] = shape_a_0 + ax_b[axes[0]] = shape_b_0 + ax_a[axes[1]] = shape_a_1 + ax_b[axes[1]] = shape_b_1 + + a = np.random.rand(*ax_a) + b = np.random.rand(*ax_b) + if is_complex: + a = a + 1j*np.random.rand(*ax_a) + b = b + 1j*np.random.rand(*ax_b) + + axes = tuple(axes) # XXX for CuPy + expected = xp.asarray(fftconvolve(a, b, mode=mode, axes=axes)) + a = xp.asarray(a) + b = xp.asarray(b) + + monkeypatch.setattr(signal._signaltools, 'fftconvolve', + fftconvolve_err) + out = oaconvolve(a, b, mode=mode, axes=axes) + + assert_array_almost_equal(out, expected) + + @xfail_xp_backends("torch", reason="ValueError: Target length must be positive") + @pytest.mark.parametrize("a,b", [([], []), ([5, 6], []), ([], [7])]) + def test_empty(self, a, b, xp): + # Regression test for #1745: crashes with 0-length input. + xp_assert_equal( + oaconvolve(xp.asarray(a), xp.asarray(b)), + xp.asarray([]), check_dtype=False + ) + + def test_zero_rank(self, xp): + a = xp.asarray(4967) + b = xp.asarray(3920) + out = oaconvolve(a, b) + xp_assert_equal(out, a * b) + + def test_single_element(self, xp): + a = xp.asarray([4967]) + b = xp.asarray([3920]) + out = oaconvolve(a, b) + xp_assert_equal(out, a * b) + + +@skip_xp_backends(np_only=True, reason="assertions may differ on backends") +@pytest.mark.parametrize('convapproach', + [make_xp_pytest_param(fftconvolve), + make_xp_pytest_param(oaconvolve)]) +class TestAllFreqConvolves: + + def test_invalid_shapes(self, convapproach, xp): + a = np.arange(1, 7).reshape((2, 3)) + b = np.arange(-6, 0).reshape((3, 2)) + with assert_raises(ValueError, + match="For 'valid' mode, one must be at least " + "as large as the other in every dimension"): + convapproach(a, b, mode='valid') + + def test_invalid_shapes_axes(self, convapproach, xp): + a = np.zeros([5, 6, 2, 1]) + b = np.zeros([5, 6, 3, 1]) + with assert_raises(ValueError, + match=r"incompatible shapes for in1 and in2:" + r" \(5L?, 6L?, 2L?, 1L?\) and" + r" \(5L?, 6L?, 3L?, 1L?\)"): + convapproach(a, b, axes=[0, 1]) + + @pytest.mark.parametrize('a,b', + [([1], 2), + (1, [2]), + ([3], [[2]])]) + def test_mismatched_dims(self, a, b, convapproach, xp): + with assert_raises(ValueError, + match="in1 and in2 should have the same" + " dimensionality"): + convapproach(a, b) + + def test_invalid_flags(self, convapproach, xp): + with assert_raises(ValueError, + match="acceptable mode flags are 'valid'," + " 'same', or 'full'"): + convapproach([1], [2], mode='chips') + + with assert_raises(ValueError, + match="when provided, axes cannot be empty"): + convapproach([1], [2], axes=[]) + + with assert_raises(ValueError, match="axes must be a scalar or " + "iterable of integers"): + convapproach([1], [2], axes=[[1, 2], [3, 4]]) + + with assert_raises(ValueError, match="axes must be a scalar or " + "iterable of integers"): + convapproach([1], [2], axes=[1., 2., 3., 4.]) + + with assert_raises(ValueError, + match="axes exceeds dimensionality of input"): + convapproach([1], [2], axes=[1]) + + with assert_raises(ValueError, + match="axes exceeds dimensionality of input"): + convapproach([1], [2], axes=[-2]) + + with assert_raises(ValueError, + match="all axes must be unique"): + convapproach([1], [2], axes=[0, 0]) + + +@skip_xp_backends(np_only=True, reason="assertions may differ on backends") +@pytest.mark.filterwarnings('ignore::DeprecationWarning') +@pytest.mark.parametrize('dtype', [np.longdouble, np.clongdouble]) +@make_xp_test_case(convolve, fftconvolve) +def test_convolve_longdtype_input(dtype, xp): + x = np.random.random((27, 27)).astype(dtype) + y = np.random.random((4, 4)).astype(dtype) + if np.iscomplexobj(dtype()): + x += .1j + y -= .1j + + res = fftconvolve(x, y) + xp_assert_close(res, convolve(x, y, method='direct')) + assert res.dtype == dtype + + +class TestMedFilt: + + IN = [[50, 50, 50, 50, 50, 92, 18, 27, 65, 46], + [50, 50, 50, 50, 50, 0, 72, 77, 68, 66], + [50, 50, 50, 50, 50, 46, 47, 19, 64, 77], + [50, 50, 50, 50, 50, 42, 15, 29, 95, 35], + [50, 50, 50, 50, 50, 46, 34, 9, 21, 66], + [70, 97, 28, 68, 78, 77, 61, 58, 71, 42], + [64, 53, 44, 29, 68, 32, 19, 68, 24, 84], + [3, 33, 53, 67, 1, 78, 74, 55, 12, 83], + [7, 11, 46, 70, 60, 47, 24, 43, 61, 26], + [32, 61, 88, 7, 39, 4, 92, 64, 45, 61]] + + OUT = [[0, 50, 50, 50, 42, 15, 15, 18, 27, 0], + [0, 50, 50, 50, 50, 42, 19, 21, 29, 0], + [50, 50, 50, 50, 50, 47, 34, 34, 46, 35], + [50, 50, 50, 50, 50, 50, 42, 47, 64, 42], + [50, 50, 50, 50, 50, 50, 46, 55, 64, 35], + [33, 50, 50, 50, 50, 47, 46, 43, 55, 26], + [32, 50, 50, 50, 50, 47, 46, 45, 55, 26], + [7, 46, 50, 50, 47, 46, 46, 43, 45, 21], + [0, 32, 33, 39, 32, 32, 43, 43, 43, 0], + [0, 7, 11, 7, 4, 4, 19, 19, 24, 0]] + + KERNEL_SIZE = [7,3] + + @make_xp_test_case(signal.medfilt, signal.medfilt2d) + def test_basic(self, xp): + + in_ = xp.asarray(self.IN) + out_ = xp.asarray(self.OUT) + kernel_size = xp.asarray(self.KERNEL_SIZE) + + d = signal.medfilt(in_, kernel_size) + e = signal.medfilt2d(xp.asarray(in_, dtype=xp.float64), kernel_size) + xp_assert_equal(d, out_) + xp_assert_equal(d, e, check_dtype=False) + + @pytest.mark.parametrize('dtype', ["uint8", "int8", "uint16", "int16", + "uint32", "int32", "uint64", "int64", + "float32", "float64"]) + @make_xp_test_case(signal.medfilt, signal.medfilt2d) + def test_types(self, dtype, xp): + # volume input and output types match + if is_torch(xp) and dtype in ["uint16", "uint32", "uint64"]: + pytest.skip("torch does not support unisigned ints") + + dtype = getattr(xp, dtype) + in_typed = xp.asarray(self.IN, dtype=dtype) + assert signal.medfilt(in_typed).dtype == dtype + assert signal.medfilt2d(in_typed).dtype == dtype + + @skip_xp_backends(np_only=True, reason="assertions may differ") + @pytest.mark.parametrize('dtype', [np.bool_, np.complex64, np.complex128, + np.clongdouble, np.float16, + "float96", "float128"]) + @make_xp_test_case(signal.medfilt, signal.medfilt2d) + def test_invalid_dtypes(self, dtype, xp): + # We can only test this on platforms that support a native type of float96 or + # float128; comparing to np.longdouble allows us to filter out non-native types + if (dtype in ["float96", "float128"] + and np.finfo(np.longdouble).dtype != dtype): + pytest.skip(f"Platform does not support {dtype}") + + in_typed = np.array(self.IN, dtype=dtype) + with pytest.raises(ValueError, match="not supported"): + signal.medfilt(in_typed) + + with pytest.raises(ValueError, match="not supported"): + signal.medfilt2d(in_typed) + + @skip_xp_backends(np_only=True, reason="object arrays") + @make_xp_test_case(signal.medfilt) + def test_none(self, xp): + # gh-1651, trac #1124. Ensure this does not segfault. + with assert_raises((ValueError, TypeError)): + signal.medfilt(None) + + @skip_xp_backends(np_only=True, reason="strides are only writeable in NumPy") + @make_xp_test_case(signal.medfilt) + def test_odd_strides(self, xp): + # Avoid a regression with possible contiguous + # numpy arrays that have odd strides. The stride value below gets + # us into wrong memory if used (but it does not need to be used) + dummy = xp.arange(10, dtype=xp.float64) + a = dummy[5:6] + a = np.lib.stride_tricks.as_strided(a, strides=(16,)) + xp_assert_close(signal.medfilt(a, 1), xp.asarray([5.])) + + @skip_xp_backends( + "jax.numpy", + reason="chunk assignment does not work on jax immutable arrays" + ) + @pytest.mark.parametrize("dtype", ["uint8", "float32", "float64"]) + @make_xp_test_case(signal.medfilt2d) + def test_medfilt2d_parallel(self, dtype, xp): + dtype = getattr(xp, dtype) + in_typed = xp.asarray(self.IN, dtype=dtype) + expected = xp.asarray(self.OUT, dtype=dtype) + + # This is used to simplify the indexing calculations. + assert in_typed.shape == expected.shape + + # We'll do the calculation in four chunks. M1 and N1 are the dimensions + # of the first output chunk. We have to extend the input by half the + # kernel size to be able to calculate the full output chunk. + M1 = expected.shape[0] // 2 + N1 = expected.shape[1] // 2 + offM = self.KERNEL_SIZE[0] // 2 + 1 + offN = self.KERNEL_SIZE[1] // 2 + 1 + + def apply(chunk): + # in = slice of in_typed to use. + # sel = slice of output to crop it to the correct region. + # out = slice of output array to store in. + M, N = chunk + if M == 0: + Min = slice(0, M1 + offM) + Msel = slice(0, -offM) + Mout = slice(0, M1) + else: + Min = slice(M1 - offM, None) + Msel = slice(offM, None) + Mout = slice(M1, None) + if N == 0: + Nin = slice(0, N1 + offN) + Nsel = slice(0, -offN) + Nout = slice(0, N1) + else: + Nin = slice(N1 - offN, None) + Nsel = slice(offN, None) + Nout = slice(N1, None) + + # Do the calculation, but do not write to the output in the threads. + chunk_data = in_typed[Min, Nin] + med = signal.medfilt2d(chunk_data, self.KERNEL_SIZE) + return med[Msel, Nsel], Mout, Nout + + # Give each chunk to a different thread. + output = xp.zeros_like(expected) + with ThreadPoolExecutor(max_workers=4) as pool: + chunks = {(0, 0), (0, 1), (1, 0), (1, 1)} + futures = {pool.submit(apply, chunk) for chunk in chunks} + + # Store each result in the output as it arrives. + for future in as_completed(futures): + data, Mslice, Nslice = future.result() + output[Mslice, Nslice] = data + + xp_assert_equal(output, expected) + + +@make_xp_test_case(signal.wiener) +class TestWiener: + + @skip_xp_backends("cupy", reason="XXX: can_cast in cupy <= 13.2") + def test_basic(self, xp): + g = xp.asarray([[5, 6, 4, 3], + [3, 5, 6, 2], + [2, 3, 5, 6], + [1, 6, 9, 7]], dtype=xp.float64) + h = xp.asarray([[2.16374269, 3.2222222222, 2.8888888889, 1.6666666667], + [2.666666667, 4.33333333333, 4.44444444444, 2.8888888888], + [2.222222222, 4.4444444444, 5.4444444444, 4.801066874837], + [1.33333333333, 3.92735042735, 6.0712560386, 5.0404040404]]) + assert_array_almost_equal(signal.wiener(g), h, decimal=6) + assert_array_almost_equal(signal.wiener(g, mysize=3), h, decimal=6) + + +padtype_options = ["mean", "median", "minimum", "maximum", "line"] +padtype_options += _upfirdn_modes + + +class TestResample: + + @make_xp_test_case(signal.resample, signal.resample_poly) + @xfail_xp_backends("cupy", reason="does not raise with non-int upsampling factor") + def test_basic(self, xp): + # Some basic tests + + # Regression test for issue #3603. + # window.shape must equal to sig.shape[0] + sig = xp.arange(128, dtype=xp.float64) + num = 256 + win = signal.get_window(('kaiser', 8.0), 160, xp=xp) + assert_raises(ValueError, signal.resample, sig, num, window=win) + assert_raises(ValueError, signal.resample, sig, num, domain='INVALID') + + # Other degenerate conditions + assert_raises(ValueError, signal.resample_poly, sig, 'yo', 1) + assert_raises(ValueError, signal.resample_poly, sig, 1, 0) + assert_raises(ValueError, signal.resample_poly, sig, 1.3, 2) + assert_raises(ValueError, signal.resample_poly, sig, 2, 1.3) + assert_raises(ValueError, signal.resample_poly, sig, 2, 1, padtype='') + assert_raises(ValueError, signal.resample_poly, sig, 2, 1, + padtype='mean', cval=10) + assert_raises(ValueError, signal.resample_poly, sig, 2, 1, window=xp.eye(2)) + + # test for issue #6505 - should not modify window.shape when axis ≠ 0 + sig2 = xp.tile(xp.arange(160, dtype=xp.float64), (2, 1)) + signal.resample(sig2, num, axis=-1, window=win) + assert win.shape == (160,) + + # Ensure coverage for parameter cval=None and cval != None: + x_ref = signal.resample_poly(sig, 2, 1) + x0 = signal.resample_poly(sig, 2, 1, padtype='constant') + x1 = signal.resample_poly(sig, 2, 1, padtype='constant', cval=0) + xp_assert_equal(x1, x_ref) + xp_assert_equal(x0, x_ref) + + @pytest.mark.parametrize('window', (None, 'hamming')) + @pytest.mark.parametrize('N', (20, 19)) + @pytest.mark.parametrize('num', (100, 101, 10, 11)) + @make_xp_test_case(signal.resample) + def test_rfft(self, N, num, window, xp): + # Make sure the speed up using rfft gives the same result as the normal + # way using fft + dt_r = xp_default_dtype(xp) + dt_c = xp.complex64 if dt_r == xp.float32 else xp.complex128 + + x = xp.linspace(0, 10, N, endpoint=False) + y = xp.cos(-x**2/6.0) + desired = signal.resample(xp.astype(y, dt_c), num, window=window) + xp_assert_close(signal.resample(y, num, window=window), + xp.real(desired)) + + y = xp.stack([xp.cos(-x**2/6.0), xp.sin(-x**2/6.0)]) + y_complex = xp.astype(y, dt_c) + resampled = signal.resample(y_complex, num, axis=1, window=window) + + atol = 1e-9 if dt_r == xp.float64 else 3e-7 + + xp_assert_close( + signal.resample(y, num, axis=1, window=window), + xp.real(resampled), + atol=atol) + + @make_xp_test_case(signal.resample) + def test_input_domain(self, xp): + # Test if both input domain modes produce the same results. + tsig = xp.astype(xp.arange(256), xp.complex128) + fsig = sp_fft.fft(tsig) + num = 256 + xp_assert_close( + signal.resample(fsig, num, domain='freq'), + signal.resample(tsig, num, domain='time'), + atol=1e-9) + + @pytest.mark.parametrize('nx', (1, 2, 3, 5, 8)) + @pytest.mark.parametrize('ny', (1, 2, 3, 5, 8)) + @pytest.mark.parametrize('dtype', ('float64', 'complex128')) + @make_xp_test_case(signal.resample) + def test_dc(self, nx, ny, dtype, xp): + dtype = getattr(xp, dtype) + x = xp.asarray([1] * nx, dtype=dtype) + y = signal.resample(x, ny) + xp_assert_close(y, xp.asarray([1] * ny, dtype=y.dtype)) + + @skip_xp_backends("cupy", reason="padtype not supported by upfirdn") + @pytest.mark.parametrize('padtype', padtype_options) + @make_xp_test_case(signal.resample_poly) + def test_mutable_window(self, padtype, xp): + # Test that a mutable window is not modified + impulse = xp.zeros(3) + window = xp.asarray(np.random.RandomState(0).randn(2)) + window_orig = xp.asarray(window, copy=True) + signal.resample_poly(impulse, 5, 1, window=window, padtype=padtype) + xp_assert_equal(window, window_orig) + + @skip_xp_backends("cupy", reason="padtype not supported by upfirdn") + @make_xp_test_case(signal.resample_poly) + @pytest.mark.parametrize('padtype', padtype_options) + def test_output_float32(self, padtype, xp): + # Test that float32 inputs yield a float32 output + x = xp.arange(10, dtype=xp.float32) + h = xp.asarray([1, 1, 1], dtype=xp.float32) + y = signal.resample_poly(x, 1, 2, window=h, padtype=padtype) + assert y.dtype == xp.float32 + + @pytest.mark.parametrize('padtype', padtype_options) + @pytest.mark.parametrize('dtype', ['float32', 'float64']) + @skip_xp_backends("cupy", reason="padtype not supported by upfirdn") + @make_xp_test_case(signal.resample_poly) + def test_output_match_dtype(self, padtype, dtype, xp): + # Test that the dtype of x is preserved per issue #14733 + dtype = getattr(xp, dtype) + x = xp.arange(10, dtype=dtype) + y = signal.resample_poly(x, 1, 2, padtype=padtype) + assert y.dtype == x.dtype + + @skip_xp_backends("cupy", reason="padtype not supported by upfirdn") + @pytest.mark.parametrize( + "method, ext, padtype", + [("fft", False, None)] + + list( + product( + ["polyphase"], [False, True], padtype_options, + ) + ), + ) + @make_xp_test_case(signal.resample, signal.resample_poly) + def test_resample_methods(self, method, ext, padtype, xp): + # Test resampling of sinusoids and random noise (1-sec) + rate = 100 + rates_to = [49, 50, 51, 99, 100, 101, 199, 200, 201] + + # Sinusoids, windowed to avoid edge artifacts + t = xp.arange(rate, dtype=xp.float64) / float(rate) + freqs = xp.asarray((1., 10., 40.))[:, xp.newaxis] + x = xp.sin(2 * xp.pi * freqs * t) * hann(rate, xp=xp) + + for rate_to in rates_to: + t_to = xp.arange(rate_to, dtype=xp.float64) / float(rate_to) + y_tos = xp.sin(2 * xp.pi * freqs * t_to) * hann(rate_to, xp=xp) + if method == 'fft': + y_resamps = signal.resample(x, rate_to, axis=-1) + else: + if ext and rate_to != rate: + # Match default window design + g = gcd(rate_to, rate) + up = rate_to // g + down = rate // g + max_rate = max(up, down) + f_c = 1. / max_rate + half_len = 10 * max_rate + window = signal.firwin(2 * half_len + 1, f_c, + window=('kaiser', 5.0)) + window = xp.asarray(window) + polyargs = {'window': window, 'padtype': padtype} + else: + polyargs = {'padtype': padtype} + + y_resamps = signal.resample_poly(x, rate_to, rate, axis=-1, + **polyargs) + + for i in range(y_tos.shape[0]): + y_to = y_tos[i, :] + y_resamp = y_resamps[i, :] + freq = float(freqs[i, 0]) + if freq >= 0.5 * rate_to: + #y_to.fill(0.) # mostly low-passed away + y_to = xp.zeros_like(y_to) # mostly low-passed away + if padtype in ['minimum', 'maximum']: + xp_assert_close(y_resamp, y_to, atol=3e-1) + else: + xp_assert_close(y_resamp, y_to, atol=1e-3) + else: + assert y_to.shape == y_resamp.shape + corr = np.corrcoef(y_to, y_resamp)[0, 1] + assert corr > 0.99, (corr, rate, rate_to) + + # Random data + rng = np.random.RandomState(0) + x = hann(rate) * np.cumsum(rng.randn(rate)) # low-pass, wind + x = xp.asarray(x) + for rate_to in rates_to: + # random data + t_to = xp.arange(rate_to, dtype=xp.float64) / float(rate_to) + y_to = np.interp(t_to, t, x) + if method == 'fft': + y_resamp = signal.resample(x, rate_to) + else: + y_resamp = signal.resample_poly(x, rate_to, rate, + padtype=padtype) + assert y_to.shape == y_resamp.shape + corr = xp.asarray(np.corrcoef(y_to, y_resamp)[0, 1]) + assert corr > 0.99, corr + + # More tests of fft method (Master 0.18.1 fails these) + if method == 'fft': + x1 = xp.asarray([1.+0.j, 0.+0.j]) + y1_test = signal.resample(x1, 4) + # upsampling a complex array + y1_true = xp.asarray([1.+0.j, 0.5+0.j, 0.+0.j, 0.5+0.j]) + xp_assert_close(y1_test, y1_true, atol=1e-12) + x2 = xp.asarray([1., 0.5, 0., 0.5]) + y2_test = signal.resample(x2, 2) # downsampling a real array + y2_true = xp.asarray([1., 0.]) + xp_assert_close(y2_test, y2_true, atol=1e-12) + + @pytest.mark.parametrize("n_in", (8, 9)) + @pytest.mark.parametrize("n_out", (3, 4)) + @make_xp_test_case(signal.resample) + def test_resample_win_func(self, n_in, n_out): + """Test callable window function. """ + x_in = np.ones(n_in) + + def win(freqs): + """Scale input by 1/2""" + return 0.5 * np.ones_like(freqs) + + y0 = signal.resample(x_in, n_out) + y1 = signal.resample(x_in, n_out, window=win) + + xp_assert_close(2*y1, y0, atol=1e-12) + + @pytest.mark.parametrize("n_in", (6, 12)) + @pytest.mark.parametrize("n_out", (3, 4)) + @make_xp_test_case(signal.resample) + def test__resample_param_t(self, n_in, n_out): + """Verify behavior for parameter `t`. + + Note that only `t[0]` and `t[1]` are utilized. + """ + t0, dt = 10, 2 + x_in = np.ones(n_in) + + y0 = signal.resample(x_in, n_out) + y1, t1 = signal.resample(x_in, n_out, t=[t0, t0+dt]) + t_ref = 10 + np.arange(len(y0)) * dt * n_in / n_out + + xp_assert_equal(y1, y0) # no influence of `t` + xp_assert_close(t1, t_ref, atol=1e-12) + + @pytest.mark.parametrize("n1", (2, 3, 7, 8)) + @pytest.mark.parametrize("n0", (2, 3, 7, 8)) + @make_xp_test_case(signal.resample) + def test_resample_nyquist(self, n0, n1): + """Test behavior at Nyquist frequency to ensure issue #14569 is fixed. """ + f_ny = min(n0, n1) // 2 + tt = (np.arange(n_) / n_ for n_ in (n0, n1)) + x0, x1 = (np.cos(2 * np.pi * f_ny * t_) for t_ in tt) + + y1_r = signal.resample(x0, n1) + y1_c = signal.resample(x0 + 0j, n1) + + xp_assert_close(y1_r, x1, atol=1e-12) + xp_assert_close(y1_c.real, x1, atol=1e-12) + + @pytest.mark.parametrize('down_factor', [2, 11, 79]) + @pytest.mark.parametrize("dtype", [int, np.float32, np.complex64, float, complex]) + @make_xp_test_case(signal.resample_poly) + def test_poly_vs_filtfilt(self, down_factor, dtype, xp): + # Check that up=1.0 gives same answer as filtfilt + slicing + random_state = np.random.RandomState(17) + size = 10000 + + x = random_state.randn(size).astype(dtype) + if dtype in (np.complex64, np.complex128): + x += 1j * random_state.randn(size) + + # resample_poly assumes zeros outside of signl, whereas filtfilt + # can only constant-pad. Make them equivalent: + x[0] = 0 + x[-1] = 0 + + h = signal.firwin(31, 1. / down_factor, window='hamming') + yf = filtfilt(h, 1.0, x, padtype='constant')[::down_factor] + + # Need to pass convolved version of filter to resample_poly, + # since filtfilt does forward and backward, but resample_poly + # only goes forward + hc = convolve(h, np.flip(h)) + + # Use yf.copy() to avoid negative strides, which are unsupported + # in torch. + x, hc, yf = map(xp.asarray, (x, hc, yf.copy())) + y = signal.resample_poly(x, 1, down_factor, window=hc) + xp_assert_close(yf, y, atol=3e-7, rtol=6e-7) + + @make_xp_test_case(signal.resample_poly) + def test_correlate1d(self, xp): + for down in [2, 4]: + for nx in range(1, 40, down): + for nweights in (32, 33): + x = np.random.random((nx,)) + weights = np.random.random((nweights,)) + y_g = correlate1d(x, np.flip(weights), mode='constant') + x, weights, y_g = map(xp.asarray, (x, weights, y_g)) + y_s = signal.resample_poly( + x, up=1, down=down, window=weights) + xp_assert_close(y_g[::down], y_s) + + @make_xp_test_case(signal.resample_poly) + @pytest.mark.parametrize('dtype', ['int32', 'float32']) + @skip_xp_backends("cupy", reason="padtype not supported by upfirdn") + def test_gh_15620(self, dtype, xp): + dtype = getattr(xp, dtype) + data = xp.asarray([0, 1, 2, 3, 2, 1, 0], dtype=dtype) + actual = signal.resample_poly(data, + up=2, + down=1, + padtype='smooth') + assert np.count_nonzero(actual) > 0 + + +@make_xp_test_case(signal.cspline1d_eval) +class TestCSpline1DEval: + + def test_basic(self, xp): + y = np.asarray([1, 2, 3, 4, 3, 2, 1, 2, 3.0]) + x = np.arange(y.shape[0]) + dx = x[1] - x[0] + cj = xp.asarray(signal.cspline1d(y)) + x2 = xp.arange(len(y) * 10.0) / 10.0 + y2 = signal.cspline1d_eval(cj, x2, dx=dx, x0=x[0]) + + # make sure interpolated values are on knot points + assert_array_almost_equal(y2[::10], xp.asarray(y), decimal=5) + + def test_complex(self, xp): + # create some smoothly varying complex signal to interpolate + x = np.arange(2.0) + y = np.zeros(x.shape, dtype=np.complex64) + T = 10.0 + f = 1.0 / T + y = np.exp(2.0J * np.pi * f * x) + + # get the cspline transform + cy = xp.asarray(signal.cspline1d(y)) + + # determine new test x value and interpolate + xnew = xp.asarray([0.5]) + ynew = signal.cspline1d_eval(cy, xnew) + + assert ynew.dtype == xp.asarray(y).dtype + + +@make_xp_test_case(signal.order_filter) +class TestOrderFilt: + + def test_basic(self, xp): + actual = signal.order_filter(xp.asarray([1, 2, 3]), xp.asarray([1, 0, 1]), 1) + expect = xp.asarray([2, 3, 2]) + xp_assert_equal(actual, expect) + + def test_doc_example(self, xp): + x = xp.reshape(xp.arange(25, dtype=xp_default_dtype(xp)), (5, 5)) + domain = xp.eye(3, dtype=xp_default_dtype(xp)) + + # minimum of elements 1,3,9 (zero-padded) on phone pad + # 7,5,3 on numpad + expected = xp.asarray( + [[0., 0., 0., 0., 0.], + [0., 0., 1., 2., 0.], + [0., 5., 6., 7., 0.], + [0., 10., 11., 12., 0.], + [0., 0., 0., 0., 0.]], + dtype=xp_default_dtype(xp) + ) + xp_assert_close(signal.order_filter(x, domain, 0), expected) + + # maximum of elements 1,3,9 (zero-padded) on phone pad + # 7,5,3 on numpad + expected = xp.asarray( + [[6., 7., 8., 9., 4.], + [11., 12., 13., 14., 9.], + [16., 17., 18., 19., 14.], + [21., 22., 23., 24., 19.], + [20., 21., 22., 23., 24.]], + ) + xp_assert_close(signal.order_filter(x, domain, 2), expected) + + # and, just to complete the set, median of zero-padded elements + expected = xp.asarray( + [[0, 1, 2, 3, 0], + [5, 6, 7, 8, 3], + [10, 11, 12, 13, 8], + [15, 16, 17, 18, 13], + [0, 15, 16, 17, 18]], + dtype=xp_default_dtype(xp) + ) + xp_assert_close(signal.order_filter(x, domain, 1), expected) + + @xfail_xp_backends('dask.array', reason='repeat requires an axis') + @xfail_xp_backends('torch', reason='array-api-compat#292') + @make_xp_test_case(signal.medfilt) + def test_medfilt_order_filter(self, xp): + x = xp.reshape(xp.arange(25), (5, 5)) + + # median of zero-padded elements 1,5,9 on phone pad + # 7,5,3 on numpad + expected = xp.asarray( + [[0, 1, 2, 3, 0], + [1, 6, 7, 8, 4], + [6, 11, 12, 13, 9], + [11, 16, 17, 18, 14], + [0, 16, 17, 18, 0]], + ) + xp_assert_close(signal.medfilt(x, 3), expected) + + xp_assert_close( + signal.order_filter(x, xp.ones((3, 3)), 4), + expected + ) + + def test_order_filter_asymmetric(self, xp): + x = xp.reshape(xp.arange(25), (5, 5)) + domain = xp.asarray( + [[1, 1, 0], + [0, 1, 0], + [0, 0, 0]], + ) + + expected = xp.asarray( + [[0, 0, 0, 0, 0], + [0, 0, 1, 2, 3], + [0, 5, 6, 7, 8], + [0, 10, 11, 12, 13], + [0, 15, 16, 17, 18]] + ) + xp_assert_close(signal.order_filter(x, domain, 0), expected) + + expected = xp.asarray( + [[0, 0, 0, 0, 0], + [0, 1, 2, 3, 4], + [5, 6, 7, 8, 9], + [10, 11, 12, 13, 14], + [15, 16, 17, 18, 19]] + ) + xp_assert_close(signal.order_filter(x, domain, 1), expected) + + +@make_xp_test_case(lfilter) +class _TestLinearFilter: + def generate(self, shape, xp): + prodshape = shape if isinstance(shape, int) else math.prod(shape) + x = xp.linspace(0, prodshape - 1, prodshape) + if not isinstance(shape, int): + x = xp.reshape(x, shape) + return self.convert_dtype(x, xp) + + def convert_dtype(self, arr, xp): + if self.dtype == np.dtype('O'): + arr = np.asarray(arr) + out = np.empty(arr.shape, self.dtype) + iter = np.nditer([arr, out], ['refs_ok','zerosize_ok'], + [['readonly'],['writeonly']]) + for x, y in iter: + y[...] = self.type(x[()]) + return out + else: + dtype = (getattr(xp, self.dtype) + if isinstance(self.dtype, str) + else self.dtype) + return xp.asarray(arr, dtype=dtype) + + @skip_xp_backends('cupy', reason='XXX https://github.com/scipy/scipy/issues/23539') + def test_invalid_params(self, xp): + """Verify all exceptions are raised. """ + b, a, x = xp.asarray([1]), xp.asarray([2]), xp.asarray([3, 4]) + with pytest.raises(ValueError, match="^Parameter b is not"): + lfilter(xp.eye(2), a, x) # b not one-dimensional + with pytest.raises(ValueError, match="^Parameter b is not"): + lfilter(xp.asarray([]), a, x) # b empty + with pytest.raises(ValueError, match="^Parameter a is not"): + lfilter(b, xp.eye(2), x) # a not one-dimensional + with pytest.raises(ValueError, match="^Parameter a is not"): + lfilter(b, xp.asarray([]), x) # a empty + with pytest.raises(NotImplementedError, match="^Parameter's dtypes produced "): + b, a, x = (xp.astype(v_, xp.uint64, copy=False) for v_ in (b, a, x)) + lfilter(b, a, x) # fails with uint64 dtype + + def test_rank_1_IIR(self, xp): + x = self.generate((6,), xp) + b = self.convert_dtype([1, -1], xp) + a = self.convert_dtype([0.5, -0.5], xp) + y_r = self.convert_dtype([0, 2, 4, 6, 8, 10.], xp) + assert_array_almost_equal(lfilter(b, a, x), y_r) + + def test_rank_1_FIR(self, xp): + x = self.generate((6,), xp) + b = self.convert_dtype([1, 1], xp) + a = self.convert_dtype([1], xp) + y_r = self.convert_dtype([0, 1, 3, 5, 7, 9.], xp) + assert_array_almost_equal(lfilter(b, a, x), y_r) + + @skip_xp_backends('cupy', reason='XXX https://github.com/cupy/cupy/pull/8677') + def test_rank_1_IIR_init_cond(self, xp): + x = self.generate((6,), xp) + b = self.convert_dtype([1, 0, -1], xp) + a = self.convert_dtype([0.5, -0.5], xp) + zi = self.convert_dtype([1, 2], xp) + y_r = self.convert_dtype([1, 5, 9, 13, 17, 21], xp) + zf_r = self.convert_dtype([13, -10], xp) + y, zf = lfilter(b, a, x, zi=zi) + assert_array_almost_equal(y, y_r) + assert_array_almost_equal(zf, zf_r) + + @skip_xp_backends('cupy', reason='XXX https://github.com/cupy/cupy/pull/8677') + def test_rank_1_FIR_init_cond(self, xp): + x = self.generate((6,), xp) + b = self.convert_dtype([1, 1, 1], xp) + a = self.convert_dtype([1], xp) + zi = self.convert_dtype([1, 1], xp) + y_r = self.convert_dtype([1, 2, 3, 6, 9, 12.], xp) + zf_r = self.convert_dtype([9, 5], xp) + y, zf = lfilter(b, a, x, zi=zi) + assert_array_almost_equal(y, y_r) + assert_array_almost_equal(zf, zf_r) + + def test_rank_2_IIR_axis_0(self, xp): + x = self.generate((4, 3), xp) + b = self.convert_dtype([1, -1], xp) + a = self.convert_dtype([0.5, 0.5], xp) + y_r2_a0 = self.convert_dtype([[0, 2, 4], [6, 4, 2], [0, 2, 4], + [6, 4, 2]], xp) + y = lfilter(b, a, x, axis=0) + assert_array_almost_equal(y_r2_a0, y) + + def test_rank_2_IIR_axis_1(self, xp): + x = self.generate((4, 3), xp) + b = self.convert_dtype([1, -1], xp) + a = self.convert_dtype([0.5, 0.5], xp) + y_r2_a1 = self.convert_dtype([[0, 2, 0], [6, -4, 6], [12, -10, 12], + [18, -16, 18]], xp) + y = lfilter(b, a, x, axis=1) + assert_array_almost_equal(y_r2_a1, y) + + @skip_xp_backends('cupy', reason='XXX https://github.com/cupy/cupy/pull/8677') + def test_rank_2_IIR_axis_0_init_cond(self, xp): + x = self.generate((4, 3), xp) + b = self.convert_dtype([1, -1], xp) + a = self.convert_dtype([0.5, 0.5], xp) + zi = self.convert_dtype(np.ones((4,1)), xp) + + y_r2_a0_1 = self.convert_dtype([[1, 1, 1], [7, -5, 7], [13, -11, 13], + [19, -17, 19]], xp) + zf_r = self.convert_dtype([-5, -17, -29, -41], xp)[:, np.newaxis] + y, zf = lfilter(b, a, x, axis=1, zi=zi) + assert_array_almost_equal(y_r2_a0_1, y) + assert_array_almost_equal(zf, zf_r) + + @skip_xp_backends('cupy', reason='XXX https://github.com/cupy/cupy/pull/8677') + def test_rank_2_IIR_axis_1_init_cond(self, xp): + x = self.generate((4, 3), xp) + b = self.convert_dtype([1, -1], xp) + a = self.convert_dtype([0.5, 0.5], xp) + zi = self.convert_dtype(np.ones((1, 3)), xp) + + y_r2_a0_0 = self.convert_dtype([[1, 3, 5], [5, 3, 1], + [1, 3, 5], [5, 3, 1]], xp) + zf_r = self.convert_dtype([[-23, -23, -23]], xp) + y, zf = lfilter(b, a, x, axis=0, zi=zi) + assert_array_almost_equal(y_r2_a0_0, y) + assert_array_almost_equal(zf, zf_r) + + def test_rank_3_IIR(self, xp): + x = self.generate((4, 3, 2), xp) + b = self.convert_dtype([1, -1], xp) + a = self.convert_dtype([0.5, 0.5], xp) + a_np, b_np, x_np = map(_xp_copy_to_numpy, (a, b, x)) + for axis in range(x.ndim): + y = lfilter(b, a, x, axis) + y_r = np.apply_along_axis(lambda w: lfilter(b_np, a_np, w), axis, x_np) + assert_array_almost_equal(y, xp.asarray(y_r)) + + @xfail_xp_backends("cupy", reason="inaccurate") + def test_rank_3_IIR_init_cond(self, xp): + x = self.generate((4, 3, 2), xp) + b = self.convert_dtype([1, -1], xp) + a = self.convert_dtype([0.5, 0.5], xp) + + for axis in range(x.ndim): + zi_shape = list(x.shape) + zi_shape[axis] = 1 + zi = self.convert_dtype(xp.ones(zi_shape), xp) + zi1 = self.convert_dtype([1], xp) + y, zf = lfilter(b, a, x, axis, zi) + b_np, a_np, zi1_np = map(_xp_copy_to_numpy, (b, a, zi1)) + def lf0(w): + return lfilter(b_np, a_np, w, zi=zi1_np)[0] + def lf1(w): + return lfilter(b_np, a_np, w, zi=zi1_np)[1] + y_r = np.apply_along_axis(lf0, axis, _xp_copy_to_numpy(x)) + zf_r = np.apply_along_axis(lf1, axis, _xp_copy_to_numpy(x)) + assert_array_almost_equal(y, xp.asarray(y_r)) + assert_array_almost_equal(zf, xp.asarray(zf_r)) + + def test_rank_3_FIR(self, xp): + x = self.generate((4, 3, 2), xp) + b = self.convert_dtype([1, 0, -1], xp) + a = self.convert_dtype([1], xp) + + a_np, b_np, x_np = map(_xp_copy_to_numpy, (a, b, x)) + for axis in range(x.ndim): + y = lfilter(b, a, x, axis) + y_r = np.apply_along_axis(lambda w: lfilter(b_np, a_np, w), axis, x_np) + assert_array_almost_equal(y, xp.asarray(y_r)) + + @xfail_xp_backends("cupy", reason="inaccurate") + def test_rank_3_FIR_init_cond(self, xp): + x = self.generate((4, 3, 2), xp) + b = self.convert_dtype([1, 0, -1], xp) + a = self.convert_dtype([1], xp) + + x_np, b_np, a_np = map(_xp_copy_to_numpy, (x, b, a)) + for axis in range(x.ndim): + zi_shape = list(x.shape) + zi_shape[axis] = 2 + zi = self.convert_dtype(xp.ones(zi_shape), xp) + zi1 = self.convert_dtype([1, 1], xp) + zi1_np = _xp_copy_to_numpy(zi1) + y, zf = lfilter(b, a, x, axis, zi) + b_np, a_np, zi1_np = map(_xp_copy_to_numpy, (b, a, zi1)) + def lf0(w): + return lfilter(b_np, a_np, w, zi=zi1_np)[0] + def lf1(w): + return lfilter(b_np, a_np, w, zi=zi1_np)[1] + y_r = np.apply_along_axis(lf0, axis, x_np) + zf_r = np.apply_along_axis(lf1, axis, x_np) + assert_array_almost_equal(y, xp.asarray(y_r)) + assert_array_almost_equal(zf, xp.asarray(zf_r)) + + @skip_xp_backends('cupy', reason='XXX https://github.com/cupy/cupy/pull/8677') + def test_zi_pseudobroadcast(self, xp): + x = self.generate((4, 5, 20), xp) + b, a = signal.butter(8, 0.2, output='ba') + b = self.convert_dtype(b, xp) + a = self.convert_dtype(a, xp) + zi_size = b.shape[0] - 1 + + # lfilter requires x.ndim == zi.ndim exactly. However, zi can have + # length 1 dimensions. + zi_full = self.convert_dtype(xp.ones((4, 5, zi_size)), xp) + zi_sing = self.convert_dtype(xp.ones((1, 1, zi_size)), xp) + + y_full, zf_full = lfilter(b, a, x, zi=zi_full) + y_sing, zf_sing = lfilter(b, a, x, zi=zi_sing) + + assert_array_almost_equal(y_sing, y_full) + assert_array_almost_equal(zf_full, zf_sing) + + # lfilter does not prepend ones + assert_raises(ValueError, lfilter, b, a, x, -1, xp.ones(zi_size)) + + @skip_xp_backends('cupy', reason='XXX https://github.com/cupy/cupy/pull/8677') + def test_scalar_a(self, xp): + # a can be a scalar. + x = self.generate(6, xp) + b = self.convert_dtype([1, 0, -1], xp) + a = self.convert_dtype([1], xp) + y_r = self.convert_dtype([0, 1, 2, 2, 2, 2], xp) + + y = lfilter(b, a[0], x) + assert_array_almost_equal(y, y_r) + + @skip_xp_backends('cupy', reason='XXX https://github.com/cupy/cupy/pull/8677') + def test_zi_some_singleton_dims(self, xp): + # lfilter doesn't really broadcast (no prepending of 1's). But does + # do singleton expansion if x and zi have the same ndim. This was + # broken only if a subset of the axes were singletons (gh-4681). + x = self.convert_dtype(xp.zeros((3, 2, 5), dtype=xp.int64), xp) + b = self.convert_dtype(xp.ones(5, dtype=xp.int64), xp) + a = self.convert_dtype(xp.asarray([1, 0, 0]), xp) + zi = np.ones((3, 1, 4), dtype=np.int64) + zi[1, :, :] *= 2 + zi[2, :, :] *= 3 + zi = xp.asarray(zi) + zi = self.convert_dtype(zi, xp) + + zf_expected = self.convert_dtype(xp.zeros((3, 2, 4), dtype=xp.int64), xp) + y_expected = np.zeros((3, 2, 5), dtype=np.int64) + y_expected[:, :, :4] = [[[1]], [[2]], [[3]]] + y_expected = xp.asarray(y_expected) + y_expected = self.convert_dtype(y_expected, xp) + + # IIR + y_iir, zf_iir = lfilter(b, a, x, -1, zi) + assert_array_almost_equal(y_iir, y_expected) + assert_array_almost_equal(zf_iir, zf_expected) + + # FIR + y_fir, zf_fir = lfilter(b, a[0], x, -1, zi) + assert_array_almost_equal(y_fir, y_expected) + assert_array_almost_equal(zf_fir, zf_expected) + + def base_bad_size_zi(self, b, a, x, axis, zi, xp): + b = self.convert_dtype(b, xp) + a = self.convert_dtype(a, xp) + x = self.convert_dtype(x, xp) + zi = self.convert_dtype(zi, xp) + assert_raises(ValueError, lfilter, b, a, x, axis, zi) + + @skip_xp_backends('cupy', reason='cupy does not raise') + def test_bad_size_zi(self, xp): + # rank 1 + x1 = xp.arange(6) + self.base_bad_size_zi([1], [1], x1, -1, [1], xp) + self.base_bad_size_zi([1, 1], [1], x1, -1, [0, 1], xp) + self.base_bad_size_zi([1, 1], [1], x1, -1, [[0]], xp) + self.base_bad_size_zi([1, 1], [1], x1, -1, [0, 1, 2], xp) + self.base_bad_size_zi([1, 1, 1], [1], x1, -1, [[0]], xp) + self.base_bad_size_zi([1, 1, 1], [1], x1, -1, [0, 1, 2], xp) + self.base_bad_size_zi([1], [1, 1], x1, -1, [0, 1], xp) + self.base_bad_size_zi([1], [1, 1], x1, -1, [[0]], xp) + self.base_bad_size_zi([1], [1, 1], x1, -1, [0, 1, 2], xp) + self.base_bad_size_zi([1, 1, 1], [1, 1], x1, -1, [0], xp) + self.base_bad_size_zi([1, 1, 1], [1, 1], x1, -1, [[0], [1]], xp) + self.base_bad_size_zi([1, 1, 1], [1, 1], x1, -1, [0, 1, 2], xp) + self.base_bad_size_zi([1, 1, 1], [1, 1], x1, -1, [0, 1, 2, 3], xp) + self.base_bad_size_zi([1, 1], [1, 1, 1], x1, -1, [0], xp) + self.base_bad_size_zi([1, 1], [1, 1, 1], x1, -1, [[0], [1]], xp) + self.base_bad_size_zi([1, 1], [1, 1, 1], x1, -1, [0, 1, 2], xp) + self.base_bad_size_zi([1, 1], [1, 1, 1], x1, -1, [0, 1, 2, 3], xp) + + # rank 2 + x2 = np.arange(12).reshape((4,3)) + x2 = xp.asarray(x2) + # for axis=0 zi.shape should == (max(len(a),len(b))-1, 3) + self.base_bad_size_zi([1], [1], x2, 0, [0], xp) + + # for each of these there are 5 cases tested (in this order): + # 1. not deep enough, right # elements + # 2. too deep, right # elements + # 3. right depth, right # elements, transposed + # 4. right depth, too few elements + # 5. right depth, too many elements + + self.base_bad_size_zi([1, 1], [1], x2, 0, [0, 1, 2], xp) + self.base_bad_size_zi([1, 1], [1], x2, 0, [[[0, 1, 2]]], xp) + self.base_bad_size_zi([1, 1], [1], x2, 0, [[0], [1], [2]], xp) + self.base_bad_size_zi([1, 1], [1], x2, 0, [[0, 1]], xp) + self.base_bad_size_zi([1, 1], [1], x2, 0, [[0, 1, 2, 3]], xp) + + self.base_bad_size_zi([1, 1, 1], [1], x2, 0, [0, 1, 2, 3, 4, 5], xp) + self.base_bad_size_zi([1, 1, 1], [1], x2, 0, [[[0, 1, 2], [3, 4, 5]]], xp) + self.base_bad_size_zi([1, 1, 1], [1], x2, 0, [[0, 1], [2, 3], [4, 5]], xp) + self.base_bad_size_zi([1, 1, 1], [1], x2, 0, [[0, 1], [2, 3]], xp) + self.base_bad_size_zi([1, 1, 1], [1], x2, 0, [[0, 1, 2, 3], [4, 5, 6, 7]], xp) + + self.base_bad_size_zi([1], [1, 1], x2, 0, [0, 1, 2], xp) + self.base_bad_size_zi([1], [1, 1], x2, 0, [[[0, 1, 2]]], xp) + self.base_bad_size_zi([1], [1, 1], x2, 0, [[0], [1], [2]], xp) + self.base_bad_size_zi([1], [1, 1], x2, 0, [[0, 1]], xp) + self.base_bad_size_zi([1], [1, 1], x2, 0, [[0, 1, 2, 3]], xp) + + self.base_bad_size_zi([1], [1, 1, 1], x2, 0, [0, 1, 2, 3, 4, 5], xp) + self.base_bad_size_zi([1], [1, 1, 1], x2, 0, [[[0, 1, 2], [3, 4, 5]]], xp) + self.base_bad_size_zi([1], [1, 1, 1], x2, 0, [[0, 1], [2, 3], [4, 5]], xp) + self.base_bad_size_zi([1], [1, 1, 1], x2, 0, [[0, 1], [2, 3]], xp) + self.base_bad_size_zi([1], [1, 1, 1], x2, 0, [[0, 1, 2, 3], [4, 5, 6, 7]], xp) + + self.base_bad_size_zi([1, 1, 1], [1, 1], x2, 0, [0, 1, 2, 3, 4, 5], xp) + self.base_bad_size_zi([1, 1, 1], [1, 1], x2, 0, [[[0, 1, 2], [3, 4, 5]]], xp) + self.base_bad_size_zi([1, 1, 1], [1, 1], x2, 0, [[0, 1], [2, 3], [4, 5]], xp) + self.base_bad_size_zi([1, 1, 1], [1, 1], x2, 0, [[0, 1], [2, 3]], xp) + self.base_bad_size_zi([1, 1, 1], [1, 1], x2, 0, + [[0, 1, 2, 3], [4, 5, 6, 7]], xp) + + # for axis=1 zi.shape should == (4, max(len(a),len(b))-1) + self.base_bad_size_zi([1], [1], x2, 1, [0], xp) + + self.base_bad_size_zi([1, 1], [1], x2, 1, [0, 1, 2, 3], xp) + self.base_bad_size_zi([1, 1], [1], x2, 1, [[[0], [1], [2], [3]]], xp) + self.base_bad_size_zi([1, 1], [1], x2, 1, [[0, 1, 2, 3]], xp) + self.base_bad_size_zi([1, 1], [1], x2, 1, [[0], [1], [2]], xp) + self.base_bad_size_zi([1, 1], [1], x2, 1, [[0], [1], [2], [3], [4]], xp) + + self.base_bad_size_zi([1, 1, 1], [1], x2, 1, [0, 1, 2, 3, 4, 5, 6, 7], xp) + self.base_bad_size_zi([1, 1, 1], [1], x2, 1, + [[[0, 1], [2, 3], [4, 5], [6, 7]]], xp) + self.base_bad_size_zi([1, 1, 1], [1], x2, 1, [[0, 1, 2, 3], [4, 5, 6, 7]], xp) + self.base_bad_size_zi([1, 1, 1], [1], x2, 1, [[0, 1], [2, 3], [4, 5]], xp) + self.base_bad_size_zi([1, 1, 1], [1], x2, 1, + [[0, 1], [2, 3], [4, 5], [6, 7], [8, 9]], xp) + + self.base_bad_size_zi([1], [1, 1], x2, 1, [0, 1, 2, 3], xp) + self.base_bad_size_zi([1], [1, 1], x2, 1, [[[0], [1], [2], [3]]], xp) + self.base_bad_size_zi([1], [1, 1], x2, 1, [[0, 1, 2, 3]], xp) + self.base_bad_size_zi([1], [1, 1], x2, 1, [[0], [1], [2]], xp) + self.base_bad_size_zi([1], [1, 1], x2, 1, [[0], [1], [2], [3], [4]], xp) + + self.base_bad_size_zi([1], [1, 1, 1], x2, 1, [0, 1, 2, 3, 4, 5, 6, 7], xp) + self.base_bad_size_zi([1], [1, 1, 1], x2, 1, + [[[0, 1], [2, 3], [4, 5], [6, 7]]], xp) + self.base_bad_size_zi([1], [1, 1, 1], x2, 1, [[0, 1, 2, 3], [4, 5, 6, 7]], xp) + self.base_bad_size_zi([1], [1, 1, 1], x2, 1, [[0, 1], [2, 3], [4, 5]], xp) + self.base_bad_size_zi([1], [1, 1, 1], x2, 1, [[0, 1], + [2, 3], [4, 5], [6, 7], [8, 9]], xp) + + self.base_bad_size_zi([1, 1, 1], [1, 1], x2, 1, [0, 1, 2, 3, 4, 5, 6, 7], xp) + self.base_bad_size_zi([1, 1, 1], [1, 1], x2, 1, + [[[0, 1], [2, 3], [4, 5], [6, 7]]], xp) + self.base_bad_size_zi([1, 1, 1], [1, 1], x2, 1, + [[0, 1, 2, 3], [4, 5, 6, 7]], xp) + self.base_bad_size_zi([1, 1, 1], [1, 1], x2, 1, [[0, 1], [2, 3], [4, 5]], xp) + self.base_bad_size_zi([1, 1, 1], [1, 1], x2, 1, + [[0, 1], [2, 3], [4, 5], [6, 7], [8, 9]], xp) + + def test_empty_zi(self, xp): + # Regression test for #880: empty array for zi crashes. + x = self.generate((5,), xp) + a = self.convert_dtype([1], xp) + b = self.convert_dtype([1], xp) + zi = self.convert_dtype([], xp) + y, zf = lfilter(b, a, x, zi=zi) + assert_array_almost_equal(y, x) + assert zf.dtype == (getattr(xp, self.dtype) + if isinstance(self.dtype, str) + else self.dtype) + assert xp_size(zf) == 0 + + @skip_xp_backends('jax.numpy', reason='jax does not support inplace ops') + @pytest.mark.parametrize('a', (1, [1], [1, .5, 1.5], 2, [2], [2, 1, 3]), + ids=str) + @make_xp_test_case(lfiltic) + def test_lfiltic(self, a, xp): + # Test for #22470: lfiltic does not handle `a[0] != 1` + # and, more in general, test that lfiltic behaves consistently with lfilter + if is_cupy(xp) and isinstance(a, int | float): + pytest.skip('cupy does not supoprt scalar filter coefficients') + x = self.generate(6, xp) # arbitrary input + b = self.convert_dtype([.5, 1., .2], xp) # arbitrary b + a = self.convert_dtype(a, xp) + N = xp_size(a) - 1 + M = xp_size(b) - 1 + K = M + N if is_cupy(xp) else max(N, M) + # compute reference initial conditions as final conditions of lfilter + y1, zi_1 = lfilter(b, a, x, zi=self.generate(K, xp)) + # copute initial conditions from lfiltic + zi_2 = lfiltic(b, a, xp.flip(y1), xp.flip(x)) + # compare lfiltic's output with reference + assert_array_almost_equal(zi_1, zi_2) + + @make_xp_test_case(lfiltic) + def test_lfiltic_bad_coeffs(xp): + # Test for invalid filter coefficients (wrong shape or zero `a[0]`) + assert_raises(ValueError, lfiltic, [1, 2], [], [0, 0], [0, 1]) + assert_raises(ValueError, lfiltic, [1, 2], [0, 2], [0, 0], [0, 1]) + assert_raises(ValueError, lfiltic, [1, 2], [[1], [2]], [0, 0], [0, 1]) + assert_raises(ValueError, lfiltic, [[1], [2]], [1], [0, 0], [0, 1]) + + @skip_xp_backends( + 'array_api_strict', reason='int64 and float64 cannot be promoted together' + ) + @skip_xp_backends('jax.numpy', reason='jax dtype defaults differ') + @make_xp_test_case(lfiltic) + def test_lfiltic_bad_zi(self, xp): + # Regression test for #3699: bad initial conditions + a = self.convert_dtype([1], xp) + b = self.convert_dtype([1], xp) + # "y" sets the datatype of zi, so it truncates if int + zi = lfiltic(b, a, xp.asarray([1., 0])) + zi_1 = lfiltic(b, a, xp.asarray([1.0, 0])) + zi_2 = lfiltic(b, a, xp.asarray([True, False])) + xp_assert_equal(zi, zi_1) + + check_dtype_arg = {} if self.dtype == object else {'check_dtype': False} + xp_assert_equal(zi, zi_2, **check_dtype_arg) + + @skip_xp_backends('cupy', reason='XXX https://github.com/cupy/cupy/pull/8677') + def test_short_x_FIR(self, xp): + # regression test for #5116 + # x shorter than b, with non None zi fails + a = self.convert_dtype([1], xp) + b = self.convert_dtype([1, 0, -1], xp) + zi = self.convert_dtype([2, 7], xp) + x = self.convert_dtype([72], xp) + ye = self.convert_dtype([74], xp) + zfe = self.convert_dtype([7, -72], xp) + y, zf = lfilter(b, a, x, zi=zi) + assert_array_almost_equal(y, ye) + assert_array_almost_equal(zf, zfe) + + @skip_xp_backends('cupy', reason='XXX https://github.com/cupy/cupy/pull/8677') + def test_short_x_IIR(self, xp): + # regression test for #5116 + # x shorter than b, with non None zi fails + a = self.convert_dtype([1, 1], xp) + b = self.convert_dtype([1, 0, -1], xp) + zi = self.convert_dtype([2, 7], xp) + x = self.convert_dtype([72], xp) + ye = self.convert_dtype([74], xp) + zfe = self.convert_dtype([-67, -72], xp) + y, zf = lfilter(b, a, x, zi=zi) + assert_array_almost_equal(y, ye) + assert_array_almost_equal(zf, zfe) + + def test_do_not_modify_a_b_IIR(self, xp): + x = self.generate((6,), xp) + b = self.convert_dtype([1, -1], xp) + b0 = xp_copy(b, xp=xp) + a = self.convert_dtype([0.5, -0.5], xp) + a0 = xp_copy(a, xp=xp) + y_r = self.convert_dtype([0, 2, 4, 6, 8, 10.], xp) + y_f = lfilter(b, a, x) + assert_array_almost_equal(y_f, y_r) + xp_assert_equal(b, b0) + xp_assert_equal(a, a0) + + def test_do_not_modify_a_b_FIR(self, xp): + x = self.generate((6,), xp) + b = self.convert_dtype([1, 0, 1], xp) + b0 = xp_copy(b, xp=xp) + a = self.convert_dtype([2], xp) + a0 = xp_copy(a, xp=xp) + y_r = self.convert_dtype([0, 0.5, 1, 2, 3, 4.], xp) + y_f = lfilter(b, a, x) + assert_array_almost_equal(y_f, y_r) + xp_assert_equal(b, b0) + xp_assert_equal(a, a0) + + @skip_xp_backends(np_only=True) + @pytest.mark.parametrize("a", [1.0, [1.0], np.array(1.0)]) + @pytest.mark.parametrize("b", [1.0, [1.0], np.array(1.0)]) + def test_scalar_input(self, a, b, xp): + data = np.random.randn(10) + data = xp.asarray(data) + xp_assert_close( + lfilter(xp.asarray([1.0]), xp.asarray([1.0]), data), + lfilter(b, a, data) + ) + + +class TestLinearFilterFloat32(_TestLinearFilter): + dtype = 'float32' + + +class TestLinearFilterFloat64(_TestLinearFilter): + dtype = 'float64' + + +@skip_xp_backends(np_only=True) +class TestLinearFilterFloatExtended(_TestLinearFilter): + dtype = np.dtype('g') + + +class TestLinearFilterComplex64(_TestLinearFilter): + dtype = 'complex64' + + +class TestLinearFilterComplex128(_TestLinearFilter): + dtype = 'complex128' + + +@skip_xp_backends(np_only=True) +class TestLinearFilterComplexExtended(_TestLinearFilter): + dtype = np.dtype('G') + + +@make_xp_test_case(lfilter) +def test_lfilter_bad_object(xp): + # lfilter: object arrays with non-numeric objects raise TypeError. + # Regression test for ticket #1452. + if hasattr(sys, 'abiflags') and 'd' in sys.abiflags: + pytest.skip('test is flaky when run with python3-dbg') + assert_raises(TypeError, lfilter, [1.0], [1.0], [1.0, None, 2.0]) + assert_raises(TypeError, lfilter, [1.0], [None], [1.0, 2.0, 3.0]) + assert_raises(TypeError, lfilter, [None], [1.0], [1.0, 2.0, 3.0]) + + +@make_xp_test_case(lfilter) +def test_lfilter_notimplemented_input(xp): + # Should not crash, gh-7991 + assert_raises(NotImplementedError, lfilter, [2,3], [4,5], [1,2,3,4,5]) + + +@pytest.mark.parametrize('dt', ["uint8", "int8", "uint16", "int16", + "uint32", "int32", + "uint64", "int64", + "float32", "float64", + ]) + +@xfail_xp_backends("jax.numpy", reason="fails all around") +@make_xp_test_case(correlate) +class TestCorrelateReal: + def _setup_rank1(self, dt, xp): + a = xp.linspace(0, 3, 4, dtype=dt) + b = xp.linspace(1, 2, 2, dtype=dt) + + y_r = xp.asarray([0, 2, 5, 8, 3], dtype=dt) + return a, b, y_r + + def equal_tolerance(self, res_dt): + # default value of keyword + decimal = 6 + try: + dt_info = np.finfo(res_dt) + if hasattr(dt_info, 'resolution'): + decimal = int(-0.5*np.log10(dt_info.resolution)) + except Exception: + pass + return decimal + + def equal_tolerance_fft(self, res_dt): + # FFT implementations convert longdouble arguments down to + # double so don't expect better precision, see gh-9520 + if res_dt == np.longdouble: + return self.equal_tolerance(np.float64) + else: + return self.equal_tolerance(res_dt) + + @skip_xp_backends(np_only=True, reason="order='F'") + def test_method(self, dt, xp): + dt = getattr(xp, dt) + + a, b, y_r = self._setup_rank3(dt, xp) + y_fft = correlate(a, b, method='fft') + y_direct = correlate(a, b, method='direct') + + assert_array_almost_equal(y_r, y_fft, + decimal=self.equal_tolerance_fft(y_fft.dtype),) + assert_array_almost_equal(y_r, y_direct, + decimal=self.equal_tolerance(y_direct.dtype),) + assert y_fft.dtype == dt + assert y_direct.dtype == dt + + def test_rank1_valid(self, dt, xp): + if is_torch(xp) and dt in ["uint16", "uint32", "uint64"]: + pytest.skip("torch does not support unsigned ints") + + dt = getattr(xp, dt) if isinstance(dt, str) else dt + a, b, y_r = self._setup_rank1(dt, xp) + y = correlate(a, b, 'valid') + assert_array_almost_equal(y, y_r[1:4]) + assert y.dtype == dt + + # See gh-5897 + y = correlate(b, a, 'valid') + assert_array_almost_equal(y, xp.flip(y_r[1:4])) + assert y.dtype == dt + + def test_rank1_same(self, dt, xp): + if is_torch(xp) and dt in ["uint16", "uint32", "uint64"]: + pytest.skip("torch does not support unsigned ints") + + dt = getattr(xp, dt) if isinstance(dt, str) else dt + + a, b, y_r = self._setup_rank1(dt, xp) + y = correlate(a, b, 'same') + assert_array_almost_equal(y, y_r[:-1]) + assert y.dtype == dt + + def test_rank1_full(self, dt, xp): + if is_torch(xp) and dt in ["uint16", "uint32", "uint64"]: + pytest.skip("torch does not support unsigned ints") + + dt = getattr(xp, dt) if isinstance(dt, str) else dt + + a, b, y_r = self._setup_rank1(dt, xp) + y = correlate(a, b, 'full') + assert_array_almost_equal(y, y_r) + assert y.dtype == dt + + def _setup_rank3(self, dt, xp): + a = np.linspace(0, 39, 40).reshape((2, 4, 5), order='F').astype( + dt) + b = np.linspace(0, 23, 24).reshape((2, 3, 4), order='F').astype( + dt) + + y_r = np.array([[[0., 184., 504., 912., 1360., 888., 472., 160.], + [46., 432., 1062., 1840., 2672., 1698., 864., 266.], + [134., 736., 1662., 2768., 3920., 2418., 1168., 314.], + [260., 952., 1932., 3056., 4208., 2580., 1240., 332.], + [202., 664., 1290., 1984., 2688., 1590., 712., 150.], + [114., 344., 642., 960., 1280., 726., 296., 38.]], + + [[23., 400., 1035., 1832., 2696., 1737., 904., 293.], + [134., 920., 2166., 3680., 5280., 3306., 1640., 474.], + [325., 1544., 3369., 5512., 7720., 4683., 2192., 535.], + [571., 1964., 3891., 6064., 8272., 4989., 2324., 565.], + [434., 1360., 2586., 3920., 5264., 3054., 1312., 230.], + [241., 700., 1281., 1888., 2496., 1383., 532., 39.]], + + [[22., 214., 528., 916., 1332., 846., 430., 132.], + [86., 484., 1098., 1832., 2600., 1602., 772., 206.], + [188., 802., 1698., 2732., 3788., 2256., 1018., 218.], + [308., 1006., 1950., 2996., 4052., 2400., 1078., 230.], + [230., 692., 1290., 1928., 2568., 1458., 596., 78.], + [126., 354., 636., 924., 1212., 654., 234., 0.]]], + dtype=np.float64).astype(dt) + + return a, b, y_r + + @skip_xp_backends(np_only=True, reason="order='F'") + def test_rank3_valid(self, dt, xp): + dt = getattr(xp, dt) if isinstance(dt, str) else dt + a, b, y_r = self._setup_rank3(dt, xp) + y = correlate(a, b, "valid") + assert_array_almost_equal(y, y_r[1:2, 2:4, 3:5]) + assert y.dtype == dt + + # See gh-5897 + y = correlate(b, a, "valid") + assert_array_almost_equal(y, y_r[1:2, 2:4, 3:5][::-1, ::-1, ::-1]) + assert y.dtype == dt + + @skip_xp_backends(np_only=True, reason="order='F'") + def test_rank3_same(self, dt, xp): + dt = getattr(xp, dt) if isinstance(dt, str) else dt + a, b, y_r = self._setup_rank3(dt, xp) + y = correlate(a, b, "same") + xp_assert_close(y, y_r[0:-1, 1:-1, 1:-2]) + assert y.dtype == dt + + @skip_xp_backends(np_only=True, reason="order='F'") + def test_rank3_all(self, dt, xp): + dt = getattr(xp, dt) if isinstance(dt, str) else dt + a, b, y_r = self._setup_rank3(dt, xp) + y = correlate(a, b) + xp_assert_close(y, y_r) + assert y.dtype == dt + + +@make_xp_test_case(correlate) +class TestCorrelate: + # Tests that don't depend on dtype + + @skip_xp_backends(np_only=True) + def test_invalid_shapes(self, xp): + # By "invalid," we mean that no one + # array has dimensions that are all at + # least as large as the corresponding + # dimensions of the other array. This + # setup should throw a ValueError. + a = np.arange(1, 7).reshape((2, 3)) + b = np.arange(-6, 0).reshape((3, 2)) + + assert_raises(ValueError, correlate, *(a, b), **{'mode': 'valid'}) + assert_raises(ValueError, correlate, *(b, a), **{'mode': 'valid'}) + + @skip_xp_backends(np_only=True) + def test_invalid_params(self, xp): + a = [3, 4, 5] + b = [1, 2, 3] + assert_raises(ValueError, correlate, a, b, mode='spam') + assert_raises(ValueError, correlate, a, b, mode='eggs', method='fft') + assert_raises(ValueError, correlate, a, b, mode='ham', method='direct') + assert_raises(ValueError, correlate, a, b, mode='full', method='bacon') + assert_raises(ValueError, correlate, a, b, mode='same', method='bacon') + + @skip_xp_backends(np_only=True) + def test_mismatched_dims(self, xp): + # Input arrays should have the same number of dimensions + assert_raises(ValueError, correlate, [1], 2, method='direct') + assert_raises(ValueError, correlate, 1, [2], method='direct') + assert_raises(ValueError, correlate, [1], 2, method='fft') + assert_raises(ValueError, correlate, 1, [2], method='fft') + assert_raises(ValueError, correlate, [1], [[2]]) + assert_raises(ValueError, correlate, [3], 2) + + @skip_xp_backends(cpu_only=True, exceptions=['cupy']) + @skip_xp_backends("jax.numpy", reason="dtype differs") + def test_numpy_fastpath(self, xp): + a = xp.asarray([1, 2, 3]) + b = xp.asarray([4, 5]) + xp_assert_close(correlate(a, b, mode='same'), xp.asarray([5, 14, 23])) + + a = xp.asarray([1, 2, 3]) + b = xp.asarray([4, 5, 6]) + xp_assert_close(correlate(a, b, mode='same'), xp.asarray([17, 32, 23])) + xp_assert_close(correlate(a, b, mode='full'), xp.asarray([6, 17, 32, 23, 12])) + xp_assert_close(correlate(a, b, mode='valid'), xp.asarray([32])) + + +@make_xp_test_case(correlation_lags) +@pytest.mark.parametrize("mode", ["valid", "same", "full"]) +@pytest.mark.parametrize("behind", [True, False]) +@pytest.mark.parametrize("input_size", [100, 101, 1000, 1001, + pytest.param(10000, marks=[pytest.mark.slow]), + pytest.param(10001, marks=[pytest.mark.slow])] +) +def test_correlation_lags(mode, behind, input_size, xp): + # generate random data + rng = np.random.RandomState(0) + in1 = rng.standard_normal(input_size) + offset = int(input_size/10) + # generate offset version of array to correlate with + if behind: + # y is behind x + in2 = np.concatenate([rng.standard_normal(offset), in1]) + expected = -offset + else: + # y is ahead of x + in2 = in1[offset:] + expected = offset + # cross correlate, returning lag information + correlation = correlate(in1, in2, mode=mode) + lags = correlation_lags(in1.size, in2.size, mode=mode) + # identify the peak + lag_index = np.argmax(correlation) + # Check as expected + xp_assert_equal(lags[lag_index], expected) + # Correlation and lags shape should match + assert lags.shape == correlation.shape + + +@make_xp_test_case(correlation_lags) +def test_correlation_lags_invalid_mode(xp): + with pytest.raises(ValueError, match="Mode asdfgh is invalid"): + correlation_lags(100, 100, mode="asdfgh") + + +@make_xp_test_case(correlate) +@pytest.mark.parametrize('dt_name', ['complex64', 'complex128']) +class TestCorrelateComplex: + # The decimal precision to be used for comparing results. + # This value will be passed as the 'decimal' keyword argument of + # assert_array_almost_equal(). + # Since correlate may chose to use FFT method which converts + # longdoubles to doubles internally don't expect better precision + # for longdouble than for double (see gh-9520). + + def decimal(self, dt, xp): + if is_numpy(xp) and dt == np.clongdouble: + dt = np.cdouble + + # emulate np.finfo(dt).precision for complex64 and complex128 + prec = {64: 15, 32: 6}[xp.finfo(dt).bits] + return int(2 * prec / 3) + + def _setup_rank1(self, dt, mode, xp): + rng = np.random.default_rng(9) + a = np.random.randn(10).astype(dt) + a += 1j * rng.standard_normal(10).astype(dt) + b = np.random.randn(8).astype(dt) + b += 1j * rng.standard_normal(8).astype(dt) + + y_r = (correlate(a.real, b.real, mode=mode) + + correlate(a.imag, b.imag, mode=mode)).astype(dt) + y_r += 1j * (-correlate(a.real, b.imag, mode=mode) + + correlate(a.imag, b.real, mode=mode)) + + a, b, y_r = xp.asarray(a), xp.asarray(b), xp.asarray(y_r) + return a, b, y_r + + def test_rank1_valid(self, dt_name, xp): + a, b, y_r = self._setup_rank1(dt_name, 'valid', xp) + dt = getattr(xp, dt_name) + y = correlate(a, b, 'valid') + assert_array_almost_equal(y, y_r, decimal=self.decimal(dt, xp)) + assert y.dtype == dt + + # See gh-5897 + y = correlate(b, a, 'valid') + assert_array_almost_equal(y, xp.conj(xp.flip(y_r)), + decimal=self.decimal(dt, xp)) + assert y.dtype == dt + + def test_rank1_same(self, dt_name, xp): + a, b, y_r = self._setup_rank1(dt_name, 'same', xp) + dt = getattr(xp, dt_name) + + y = correlate(a, b, 'same') + assert_array_almost_equal(y, y_r, decimal=self.decimal(dt, xp)) + assert y.dtype == dt + + def test_rank1_full(self, dt_name, xp): + a, b, y_r = self._setup_rank1(dt_name, 'full', xp) + dt = getattr(xp, dt_name) + y = correlate(a, b, 'full') + assert_array_almost_equal(y, y_r, decimal=self.decimal(dt, xp)) + assert y.dtype == dt + + def test_swap_full(self, dt_name, xp): + dt = getattr(xp, dt_name) + d = xp.asarray([0.+0.j, 1.+1.j, 2.+2.j], dtype=dt) + k = xp.asarray([1.+3.j, 2.+4.j, 3.+5.j, 4.+6.j], dtype=dt) + y = correlate(d, k) + xp_assert_close( + y, xp.asarray([0.+0.j, 10.-2.j, 28.-6.j, 22.-6.j, 16.-6.j, 8.-4.j]), + atol=1e-6, check_dtype=False + ) + + def test_swap_same(self, dt_name, xp): + d = xp.asarray([0.+0.j, 1.+1.j, 2.+2.j]) + k = xp.asarray([1.+3.j, 2.+4.j, 3.+5.j, 4.+6.j]) + y = correlate(d, k, mode="same") + xp_assert_close(y, xp.asarray([10.-2.j, 28.-6.j, 22.-6.j])) + + @skip_xp_backends("cupy", reason="notimplementederror") + def test_rank3(self, dt_name, xp): + if is_jax(xp) and SCIPY_DEVICE != "cpu": + pytest.xfail(reason="error tolerances exceeded with JAX on gpu") + a = np.random.randn(10, 8, 6).astype(dt_name) + a += 1j * np.random.randn(10, 8, 6).astype(dt_name) + b = np.random.randn(8, 6, 4).astype(dt_name) + b += 1j * np.random.randn(8, 6, 4).astype(dt_name) + + y_r = (correlate(a.real, b.real) + + correlate(a.imag, b.imag)).astype(dt_name) + y_r += 1j * (-correlate(a.real, b.imag) + correlate(a.imag, b.real)) + + a, b, y_r = xp.asarray(a), xp.asarray(b), xp.asarray(y_r) + dt = getattr(xp, dt_name) + + y = correlate(a, b, 'full') + assert_array_almost_equal(y, y_r, decimal=self.decimal(dt, xp) - 1) + assert y.dtype == dt + + @skip_xp_backends(np_only=True) # XXX: check 0D/scalars on backends. + def test_rank0(self, dt_name, xp): + a = np.array(np.random.randn()).astype(dt_name) + a += 1j * np.array(np.random.randn()).astype(dt_name) + b = np.array(np.random.randn()).astype(dt_name) + b += 1j * np.array(np.random.randn()).astype(dt_name) + dt = getattr(xp, dt_name) + + y_r = (correlate(a.real, b.real) + + correlate(a.imag, b.imag)).astype(dt) + y_r += 1j * np.array(-correlate(a.real, b.imag) + + correlate(a.imag, b.real)) + + a, b = xp.asarray(a), xp.asarray(b) + + y = correlate(a, b, 'full') + assert_array_almost_equal(y, y_r, decimal=self.decimal(dt, xp) - 1) + assert y.dtype == dt + + xp_assert_equal(correlate([1], [2j]), np.asarray(correlate(1, 2j)), + check_shape=False) + xp_assert_equal(correlate([2j], [3j]), np.asarray(correlate(2j, 3j)), + check_shape=False) + xp_assert_equal(correlate([3j], [4]), np.asarray(correlate(3j, 4)), + check_shape=False) + + + +class TestCorrelate2d: + + @make_xp_test_case(signal.correlate) + def test_consistency_correlate_funcs(self, xp): + # Compare np.correlate, signal.correlate, signal.correlate2d + a = np.arange(5) + b = np.array([3.2, 1.4, 3]) + for mode in ['full', 'valid', 'same']: + a_xp, b_xp = xp.asarray(a), xp.asarray(b) + np_corr_result = np.correlate(a, b, mode=mode) + assert_almost_equal(signal.correlate(a_xp, b_xp, mode=mode), + xp.asarray(np_corr_result)) + + # See gh-5897 + if mode == 'valid': + np_corr_result = np.correlate(b, a, mode=mode) + assert_almost_equal(signal.correlate(b_xp, a_xp, mode=mode), + xp.asarray(np_corr_result)) + + @skip_xp_backends(np_only=True) + @make_xp_test_case(signal.correlate2d) + def test_consistency_correlate_funcs_2(self, xp): + # Compare np.correlate, signal.correlate, signal.correlate2d + a = np.arange(5) + b = np.array([3.2, 1.4, 3]) + for mode in ['full', 'valid', 'same']: + assert_almost_equal(np.squeeze(signal.correlate2d([a], [b], + mode=mode)), + signal.correlate(a, b, mode=mode)) + + # See gh-5897 + if mode == 'valid': + assert_almost_equal(np.squeeze(signal.correlate2d([b], [a], + mode=mode)), + signal.correlate(b, a, mode=mode)) + + + @skip_xp_backends(np_only=True) + @make_xp_test_case(signal.correlate2d) + def test_invalid_shapes(self, xp): + # By "invalid," we mean that no one + # array has dimensions that are all at + # least as large as the corresponding + # dimensions of the other array. This + # setup should throw a ValueError. + a = np.arange(1, 7).reshape((2, 3)) + b = np.arange(-6, 0).reshape((3, 2)) + + assert_raises(ValueError, signal.correlate2d, *(a, b), **{'mode': 'valid'}) + assert_raises(ValueError, signal.correlate2d, *(b, a), **{'mode': 'valid'}) + + @make_xp_test_case(signal.correlate2d) + def test_complex_input(self, xp): + xp_assert_equal(signal.correlate2d(xp.asarray([[1]]), xp.asarray([[2j]])), + xp.asarray([-2j]), check_shape=False, check_dtype=False) + xp_assert_equal(signal.correlate2d(xp.asarray([[2j]]), xp.asarray([[3j]])), + xp.asarray([6+0j]), check_shape=False, check_dtype=False) + xp_assert_equal(signal.correlate2d(xp.asarray([[3j]]), xp.asarray([[4]])), + xp.asarray([12j]), check_shape=False, check_dtype=False) + + +@make_xp_test_case(lfilter_zi) +class TestLFilterZI: + + def test_exceptions(self, xp): + """Raise all exceptions in `lfilter_zi`. """ + with pytest.raises(ValueError, match="^Numerator `b` and Denominator `a` must"): + lfilter_zi(b=xp.eye(3), a=1) # b must be 1-d array + with pytest.raises(ValueError, match="^Numerator `b` and Denominator `a` must"): + lfilter_zi(b=1, a=xp.eye(3)) # a must be 1-d array + with pytest.raises(ValueError, match="^First coefficient of parameter `a` "): + lfilter_zi(b=1, a=xp.asarray([0, 0, 0])) + with pytest.raises(ValueError, match="^Filter not stable due to sum"): + lfilter_zi(b=1, a=xp.asarray([-1, 1, -2, 2])) + + @skip_xp_backends(np_only=True, reason='list inputs are numpy specific') + def test_array_like(self, xp): + zi_expected = xp.asarray([5.0, -1.0]) + zi = lfilter_zi([1.0, 0.0, 2.0], [1.0, -1.0, 0.5]) + assert_array_almost_equal(zi, zi_expected) + + @skip_xp_backends("dask.array", reason="FutureWarning emitted") + @pytest.mark.parametrize('b, a, zi_expected,',[ + ([1., 0., 2.], [1., -1., 0.5], [5., -1.]), + ([1., 2., 3., 4.], [1.,], [9., 7., 4.]), + ([1., 9.], [1., 2., 3., 4.], [0. ,-7., -4.])]) + def test_basic(self, b, a, zi_expected, xp): + b, a, zi_expected = (xp.asarray(v_) for v_ in (b, a, zi_expected)) + + zi = lfilter_zi(b, a) + assert_array_almost_equal(zi, zi_expected) + + y = lfilter(b, a, xp.ones(9), zi=zi)[0] # test for constant filter response + + fill_value = xp.sum(b) / xp.sum(a) + y_expected = xp.full_like(y, fill_value=float(fill_value)) + assert_array_almost_equal(y, y_expected) + + def test_scale_invariance(self, xp): + # Regression test. There was a bug in which b was not correctly + # rescaled when a[0] was nonzero. + b = xp.asarray([2.0, 8, 5]) + a = xp.asarray([1.0, 1, 8]) + zi1 = lfilter_zi(b, a) + zi2 = lfilter_zi(2*b, 2*a) + xp_assert_close(zi2, zi1, rtol=1e-12) + + @pytest.mark.parametrize('dtype', ['float32', 'float64']) + def test_types(self, dtype, xp): + dtype = getattr(xp, dtype) + b = xp.zeros((8), dtype=dtype) + a = xp.asarray([1], dtype=dtype) + assert signal.lfilter_zi(b, a).dtype == dtype + + +@make_xp_test_case(filtfilt, sosfiltfilt) +class TestFiltFilt: + filtfilt_kind = 'tf' + + def filtfilt(self, zpk, x, axis=-1, padtype='odd', padlen=None, + method='pad', irlen=None, xp=None): + if self.filtfilt_kind == 'tf': + b, a = zpk2tf(*zpk) + b, a = xp.asarray(b), xp.asarray(a) + return filtfilt(b, a, x, axis, padtype, padlen, method, irlen) + elif self.filtfilt_kind == 'sos': + sos = zpk2sos(*zpk) + sos = xp.asarray(sos) + return sosfiltfilt(sos, x, axis, padtype, padlen) + + @skip_xp_backends('torch', reason='negative strides') + def test_basic(self, xp): + if is_jax(xp) and self.filtfilt_kind == 'sos': + pytest.skip(reason='sosfilt works in-place') + + zpk = tf2zpk(xp.asarray([1., 2, 3]), xp.asarray([1., 2, 3])) + out = self.filtfilt(zpk, xp.arange(12), xp=xp) + atol= 4e-9 if is_cupy(xp) else 5.28e-11 + xp_assert_close(out, xp.arange(12, dtype=xp.float64), atol=atol) + + @skip_xp_backends('torch', reason='negative strides') + def test_sine(self, xp): + if is_jax(xp) and self.filtfilt_kind == 'sos': + pytest.skip(reason='sosfilt works in-place') + + rate = 2000 + t = xp.linspace(0, 1.0, rate + 1) + # A signal with low frequency and a high frequency. + xlow = xp.sin(5 * 2 * np.pi * t) + xhigh = xp.sin(250 * 2 * np.pi * t) + x = xlow + xhigh + + zpk = butter(8, xp.asarray(0.125), output='zpk') + # r is the magnitude of the largest pole. + r = np.abs(zpk[1]).max() + eps = 1e-5 + # n estimates the number of steps for the + # transient to decay by a factor of eps. + n = int(np.ceil(np.log(eps) / np.log(r))) + + # High order lowpass filter... + y = self.filtfilt(zpk, x, padlen=n, xp=xp) + # Result should be just xlow. + err = np.abs(y - xlow).max() + assert err < 1e-4 + + # A 2D case. + x2d = xp.asarray(np.vstack([xlow, xlow + xhigh])) + y2d = self.filtfilt(zpk, x2d, padlen=n, axis=1, xp=xp) + assert y2d.shape == x2d.shape + err = np.abs(y2d - xlow).max() + assert err < 1e-4 + + # Use the previous result to check the use of the axis keyword. + # (Regression test for ticket #1620) + y2dt = self.filtfilt(zpk, x2d.T, padlen=n, axis=0, xp=xp) + xp_assert_equal(y2d, y2dt.T) + + @skip_xp_backends('torch', reason='negative strides') + def test_axis(self, xp): + if is_jax(xp) and self.filtfilt_kind == 'sos': + pytest.skip(reason='sosfilt works in-place') + + # Test the 'axis' keyword on a 3D array. + x = np.arange(10.0 * 11.0 * 12.0).reshape(10, 11, 12) + x = xp.asarray(x) + zpk = butter(3, xp.asarray(0.125), output='zpk') + y0 = self.filtfilt(zpk, x, padlen=0, axis=0, xp=xp) + y1 = self.filtfilt( + zpk, xp.asarray(np.swapaxes(x, 0, 1)), padlen=0, axis=1, xp=xp + ) + xp_assert_equal(y0, xp.asarray(np.swapaxes(y1, 0, 1))) + y2 = self.filtfilt( + zpk, xp.asarray(np.swapaxes(x, 0, 2)), padlen=0, axis=2, xp=xp + ) + xp_assert_equal(y0, xp.asarray(np.swapaxes(y2, 0, 2))) + + @skip_xp_backends(np_only=True, + reason='python scalars in array_namespace are np-only') + def test_acoeff(self, xp): + if self.filtfilt_kind != 'tf': + return # only necessary for TF + # test for 'a' coefficient as single number + out = signal.filtfilt( + xp.asarray([.5, .5]), 1, xp.arange(10, dtype=xp.float64) + ) + xp_assert_close(out, xp.arange(10, dtype=xp.float64), rtol=1e-14, atol=1e-14) + + @skip_xp_backends(np_only=True, reason='_filtfilt_gust is np-only') + def test_gust_simple(self, xp): + if self.filtfilt_kind != 'tf': + pytest.skip('gust only implemented for TF systems') + # The input array has length 2. The exact solution for this case + # was computed "by hand". + x = xp.asarray([1.0, 2.0]) + b = xp.asarray([0.5]) + a = xp.asarray([1.0, -0.5]) + y, z1, z2 = _filtfilt_gust(b, a, x) + xp_assert_close(z1[0], 0.3*x[0] + 0.2*x[1]) + xp_assert_close(z2[0], 0.2*x[0] + 0.3*x[1]) + xp_assert_close(y, + xp.asarray([z1[0] + 0.25*z2[0] + 0.25*x[0] + 0.125*x[1], + 0.25*z1[0] + z2[0] + 0.125*x[0] + 0.25*x[1]]) + ) + + @skip_xp_backends(np_only=True, + reason='python scalars in array_namespace are np-only') + def test_gust_scalars(self, xp): + if self.filtfilt_kind != 'tf': + pytest.skip('gust only implemented for TF systems') + # The filter coefficients are both scalars, so the filter simply + # multiplies its input by b/a. When it is used in filtfilt, the + # factor is (b/a)**2. + x = xp.arange(12) + b = 3.0 + a = 2.0 + y = filtfilt(b, a, x, method="gust") + expected = (b/a)**2 * x + xp_assert_close(y, expected) + + +@make_xp_test_case(sosfiltfilt, filtfilt) +class TestSOSFiltFilt(TestFiltFilt): + filtfilt_kind = 'sos' + + @skip_xp_backends('jax.numpy', reason='sosfilt works in-place') + @skip_xp_backends('torch', reason='negative strides') + def test_equivalence(self, xp): + """Test equivalence between sosfiltfilt and filtfilt""" + x = np.random.RandomState(0).randn(1000) + x = xp.asarray(x) + for order in range(1, 6): + zpk = signal.butter(order, 0.35, output='zpk') + b, a = zpk2tf(*zpk) + sos = zpk2sos(*zpk) + + b, a, sos = map(xp.asarray, (b, a, sos)) + y = filtfilt(b, a, x) + y_sos = sosfiltfilt(sos, x) + xp_assert_close(y, y_sos, atol=1e-12, err_msg=f'order={order}') + + +def filtfilt_gust_opt(b, a, x): + """ + An alternative implementation of filtfilt with Gustafsson edges. + + This function computes the same result as + `scipy.signal._signaltools._filtfilt_gust`, but only 1-d arrays + are accepted. The problem is solved using `fmin` from `scipy.optimize`. + `_filtfilt_gust` is significantly faster than this implementation. + """ + def filtfilt_gust_opt_func(ics, b, a, x): + """Objective function used in filtfilt_gust_opt.""" + m = max(len(a), len(b)) - 1 + z0f = ics[:m] + z0b = ics[m:] + y_f = lfilter(b, a, x, zi=z0f)[0] + y_fb = lfilter(b, a, y_f[::-1], zi=z0b)[0][::-1] + + y_b = lfilter(b, a, x[::-1], zi=z0b)[0][::-1] + y_bf = lfilter(b, a, y_b, zi=z0f)[0] + value = np.sum((y_fb - y_bf)**2) + return value + + m = max(len(a), len(b)) - 1 + zi = lfilter_zi(b, a) + ics = np.concatenate((x[:m].mean()*zi, x[-m:].mean()*zi)) + result = fmin(filtfilt_gust_opt_func, ics, args=(b, a, x), + xtol=1e-10, ftol=1e-12, + maxfun=10000, maxiter=10000, + full_output=True, disp=False) + opt, fopt, niter, funcalls, warnflag = result + if warnflag > 0: + raise RuntimeError( + f"minimization failed in filtfilt_gust_opt: warnflag={warnflag}" + ) + z0f = opt[:m] + z0b = opt[m:] + + # Apply the forward-backward filter using the computed initial + # conditions. + y_b = lfilter(b, a, x[::-1], zi=z0b)[0][::-1] + y = lfilter(b, a, y_b, zi=z0f)[0] + + return y, z0f, z0b + + +def check_filtfilt_gust(b, a, shape, axis, irlen=None): + # Generate x, the data to be filtered. + rng = np.random.default_rng(123) + x = rng.standard_normal(shape) + + # Apply filtfilt to x. This is the main calculation to be checked. + y = filtfilt(b, a, x, axis=axis, method="gust", irlen=irlen) + + # Also call the private function so we can test the ICs. + yg, zg1, zg2 = _filtfilt_gust(b, a, x, axis=axis, irlen=irlen) + + # filtfilt_gust_opt is an independent implementation that gives the + # expected result, but it only handles 1-D arrays, so use some looping + # and reshaping shenanigans to create the expected output arrays. + xx = np.swapaxes(x, axis, -1) + out_shape = xx.shape[:-1] + yo = np.empty_like(xx) + m = max(len(a), len(b)) - 1 + zo1 = np.empty(out_shape + (m,)) + zo2 = np.empty(out_shape + (m,)) + for indx in product(*[range(d) for d in out_shape]): + yo[indx], zo1[indx], zo2[indx] = filtfilt_gust_opt(b, a, xx[indx]) + yo = np.swapaxes(yo, -1, axis) + zo1 = np.swapaxes(zo1, -1, axis) + zo2 = np.swapaxes(zo2, -1, axis) + + xp_assert_close(y, yo, rtol=1e-8, atol=1e-9) + xp_assert_close(yg, yo, rtol=1e-8, atol=1e-9) + xp_assert_close(zg1, zo1, rtol=1e-8, atol=1e-9) + xp_assert_close(zg2, zo2, rtol=1e-8, atol=1e-9) + + +@make_xp_test_case(choose_conv_method) +@pytest.mark.fail_slow(10) +def test_choose_conv_method(xp): + for mode in ['valid', 'same', 'full']: + for ndim in [1, 2]: + n, k, true_method = 8, 6, 'direct' + x = np.random.randn(*((n,) * ndim)) + h = np.random.randn(*((k,) * ndim)) + + method = choose_conv_method(x, h, mode=mode) + assert method == true_method + + method_try, times = choose_conv_method(x, h, mode=mode, measure=True) + assert method_try in {'fft', 'direct'} + assert isinstance(times, dict) + assert 'fft' in times.keys() and 'direct' in times.keys() + + n = 10 + for not_fft_conv_supp in ["complex256", "complex192"]: + if hasattr(np, not_fft_conv_supp): + x = np.ones(n, dtype=not_fft_conv_supp) + h = x.copy() + assert choose_conv_method(x, h, mode=mode) == 'direct' + + x = np.array([2**51], dtype=np.int64) + h = x.copy() + assert choose_conv_method(x, h, mode=mode) == 'direct' + + +@make_xp_test_case(choose_conv_method) +def test_choose_conv_method_2(xp): + for mode in ['valid', 'same', 'full']: + n = 10 + for not_fft_conv_supp in ["complex256", "complex192"]: + if hasattr(np, not_fft_conv_supp): + x = np.ones(n, dtype=not_fft_conv_supp) + h = x.copy() + assert choose_conv_method(x, h, mode=mode) == 'direct' + + +@pytest.mark.fail_slow(10) +def test_filtfilt_gust(): + # Design a filter. + z, p, k = signal.ellip(3, 0.01, 120, 0.0875, output='zpk') + + # Find the approximate impulse response length of the filter. + eps = 1e-10 + r = np.max(np.abs(p)) + approx_impulse_len = int(np.ceil(np.log(eps) / np.log(r))) + + b, a = zpk2tf(z, p, k) + for irlen in [None, approx_impulse_len]: + signal_len = 5 * approx_impulse_len + + # 1-d test case + check_filtfilt_gust(b, a, (signal_len,), 0, irlen) + + # 3-d test case; test each axis. + for axis in range(3): + shape = [2, 2, 2] + shape[axis] = signal_len + check_filtfilt_gust(b, a, shape, axis, irlen) + + # Test case with length less than 2*approx_impulse_len. + # In this case, `filtfilt_gust` should behave the same as if + # `irlen=None` was given. + length = 2*approx_impulse_len - 50 + check_filtfilt_gust(b, a, (length,), 0, approx_impulse_len) + + +@make_xp_test_case(signal.decimate) +class TestDecimate: + def test_bad_args(self, xp): + x = xp.arange(12) + assert_raises(TypeError, signal.decimate, x, q=0.5, n=1) + assert_raises(TypeError, signal.decimate, x, q=2, n=0.5) + + def test_basic_IIR(self, xp): + x = xp.arange(12) + y = signal.decimate(x, 2, n=1, ftype='iir', zero_phase=False).round() + xp_assert_equal(y, x[::2].astype(float)) + + def test_basic_FIR(self, xp): + x = xp.arange(12) + y = signal.decimate(x, 2, n=1, ftype='fir', zero_phase=False).round() + xp_assert_equal(y, x[::2].astype(float)) + + def test_shape(self, xp): + # Regression test for ticket #1480. + z = xp.zeros((30, 30)) + d0 = signal.decimate(z, 2, axis=0, zero_phase=False) + assert d0.shape == (15, 30) + d1 = signal.decimate(z, 2, axis=1, zero_phase=False) + assert d1.shape == (30, 15) + + @skip_xp_backends(np_only=True, reason="test code is NumPy specific") + def test_phaseshift_FIR(self, xp): + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", "Badly conditioned filter", BadCoefficients) + self._test_phaseshift(method='fir', zero_phase=False) + + @skip_xp_backends(np_only=True, reason="test code is NumPy specific") + def test_zero_phase_FIR(self, xp): + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", "Badly conditioned filter", BadCoefficients) + self._test_phaseshift(method='fir', zero_phase=True) + + @skip_xp_backends(np_only=True, reason="test code is NumPy specific") + def test_phaseshift_IIR(self, xp): + self._test_phaseshift(method='iir', zero_phase=False) + + @skip_xp_backends(np_only=True, reason="test code is NumPy specific") + def test_zero_phase_IIR(self, xp): + self._test_phaseshift(method='iir', zero_phase=True) + + def _test_phaseshift(self, method, zero_phase): + # TODO. Look into making tests using this work for CuPy. + rate = 120 + rates_to = [15, 20, 30, 40] # q = 8, 6, 4, 3 + + t_tot = 100 # Need to let antialiasing filters settle + t = np.arange(rate*t_tot+1) / float(rate) + + # Sinusoids at 0.8*nyquist, windowed to avoid edge artifacts + freqs = np.array(rates_to) * 0.8 / 2 + d = (np.exp(1j * 2 * np.pi * freqs[:, np.newaxis] * t) + * signal.windows.tukey(t.size, 0.1)) + + for rate_to in rates_to: + q = rate // rate_to + t_to = np.arange(rate_to*t_tot+1) / float(rate_to) + d_tos = (np.exp(1j * 2 * np.pi * freqs[:, np.newaxis] * t_to) + * signal.windows.tukey(t_to.size, 0.1)) + + # Set up downsampling filters, match v0.17 defaults + if method == 'fir': + n = 30 + system = signal.dlti(signal.firwin(n + 1, 1. / q, + window='hamming'), 1.) + elif method == 'iir': + n = 8 + wc = 0.8*np.pi/q + system = signal.dlti(*signal.cheby1(n, 0.05, wc/np.pi)) + + # Calculate expected phase response, as unit complex vector + if zero_phase is False: + _, h_resps = signal.freqz(system.num, system.den, + freqs/rate*2*np.pi) + h_resps /= np.abs(h_resps) + else: + h_resps = np.ones_like(freqs) + + y_resamps = signal.decimate(d.real, q, n, ftype=system, + zero_phase=zero_phase) + + # Get phase from complex inner product, like CSD + h_resamps = np.sum(d_tos.conj() * y_resamps, axis=-1) + h_resamps /= np.abs(h_resamps) + subnyq = freqs < 0.5*rate_to + + # Complex vectors should be aligned, only compare below nyquist + result = np.angle(h_resps.conj()*h_resamps)[subnyq] + xp_assert_close(result, np.zeros_like(result), + atol=1e-3, rtol=1e-3) + + def test_auto_n(self, xp): + # Test that our value of n is a reasonable choice (depends on + # the downsampling factor) + sfreq = 100. + n = 1000 + t = xp.arange(n) / sfreq + # will alias for decimations (>= 15) + x = xp.asarray(xp.sqrt(2. / n) * xp.sin(2 * xp.pi * (sfreq / 30.) * t)) + # Use xp.sqrt(x.dot(x)) instead of xp.linalg.vector_norm(x) because + # linear algebra extension is not universally available. + xp_assert_close(xp.sqrt(x.dot(x)), xp.asarray(1.), rtol=1e-3, check_0d=False) + x_out = signal.decimate(x, 30, ftype='fir') + assert xp.sqrt(x_out.dot(x_out)) < 0.01 + + def test_long_float32(self, xp): + # regression: gh-15072. With 32-bit float and either lfilter + # or filtfilt, this is numerically unstable + x = signal.decimate(xp.ones(10_000, dtype=xp.float32), 10) + assert not any(xp.isnan(x)) + + def test_float16_upcast(self): + # float16 must be upcast to float64 + x = signal.decimate(np.ones(100, dtype=np.float16), 10) + assert x.dtype.type == np.float64 + + @skip_xp_backends(np_only=True, reason="dlti") + def test_complex_iir_dlti(self, xp): + # regression: gh-17845 + # centre frequency for filter [Hz] + fcentre = 50 + # filter passband width [Hz] + fwidth = 5 + # sample rate [Hz] + fs = 1e3 + + z, p, k = signal.butter(2, xp.asarray(2*xp.pi*fwidth/2), + output='zpk', fs=fs) + z = z.astype(complex) * xp.exp(xp.asarray(2j * xp.pi * fcentre/fs)) + p = p.astype(complex) * xp.exp(xp.asarray(2j * xp.pi * fcentre/fs)) + system = signal.dlti(z, p, k) + + t = xp.arange(200) / fs + + # input + u = (xp.exp(2j * xp.pi * fcentre * t) + + 0.5 * xp.exp(-2j * xp.pi * fcentre * t)) + + ynzp = signal.decimate(u, 2, ftype=system, zero_phase=False) + ynzpref = signal.lfilter(*signal.zpk2tf(z, p, k), + u)[::2] + + xp_assert_equal(ynzp, ynzpref) + + yzp = signal.decimate(u, 2, ftype=system, zero_phase=True) + yzpref = signal.filtfilt(*signal.zpk2tf(z, p, k), + u)[::2] + + xp_assert_close(yzp, yzpref, rtol=1e-10, atol=1e-13) + + @skip_xp_backends(np_only=True, reason="dlti") + def test_complex_fir_dlti(self, xp): + # centre frequency for filter [Hz] + fcentre = 50 + # filter passband width [Hz] + fwidth = 5 + # sample rate [Hz] + fs = 1e3 + numtaps = 20 + + # FIR filter about 0Hz + bbase = signal.firwin(numtaps, fwidth/2, fs=fs) + + # rotate these to desired frequency + zbase = np.roots(bbase) + zrot = zbase * np.exp(2j * np.pi * fcentre/fs) + # FIR filter about 50Hz, maintaining passband gain of 0dB + bz = bbase[0] * np.poly(zrot) + + system = signal.dlti(bz, 1) + + t = np.arange(200) / fs + + # input + u = (np.exp(2j * np.pi * fcentre * t) + + 0.5 * np.exp(-2j * np.pi * fcentre * t)) + + ynzp = signal.decimate(u, 2, ftype=system, zero_phase=False) + ynzpref = signal.upfirdn(bz, u, up=1, down=2)[:100] + + xp_assert_equal(ynzp, ynzpref) + + yzp = signal.decimate(u, 2, ftype=system, zero_phase=True) + yzpref = signal.resample_poly(u, 1, 2, window=bz) + + xp_assert_equal(yzp, yzpref) + +@make_xp_test_case(hilbert) +class TestHilbert: + + def test_bad_args(self, xp): + x = xp.asarray([1.0 + 0.0j]) + assert_raises(ValueError, hilbert, x) + x = xp.arange(8.0) + assert_raises(ValueError, hilbert, x, N=0) + + def test_hilbert_theoretical(self, xp): + # test cases by Ariel Rokem + decimal = 14 + + pi = xp.pi + t = xp.arange(0, 2 * pi, pi / 256, dtype=xp.float64) + a0 = xp.sin(t) + a1 = xp.cos(t) + a2 = xp.sin(2 * t) + a3 = xp.cos(2 * t) + a = xp.stack([a0, a1, a2, a3]) + + h = hilbert(a) + h_abs = xp.abs(h) + + h_angle = xp.atan2(xp.imag(h), xp.real(h)) # np.angle(h) + h_real = xp.real(h) + + # The real part should be equal to the original signals: + assert_almost_equal(h_real, a, decimal) + # The absolute value should be one everywhere, for this input: + assert_almost_equal(h_abs, xp.ones(a.shape), decimal) + # For the 'slow' sine - the phase should go from -pi/2 to pi/2 in + # the first 256 bins: + assert_almost_equal(h_angle[0, :256], + xp.arange(-pi / 2, pi / 2, pi / 256, dtype=xp.float64), + decimal) + # For the 'slow' cosine - the phase should go from 0 to pi in the + # same interval: + assert_almost_equal( + h_angle[1, :256], xp.arange(0, pi, pi / 256, dtype=xp.float64), decimal) + # The 'fast' sine should make this phase transition in half the time: + assert_almost_equal(h_angle[2, :128], + xp.arange(-pi / 2, pi / 2, pi / 128, dtype=xp.float64), + decimal) + # Ditto for the 'fast' cosine: + assert_almost_equal( + h_angle[3, :128], xp.arange(0, pi, pi / 128, dtype=xp.float64), decimal) + + # The imaginary part of hilbert(cos(t)) = sin(t) Wikipedia + assert_almost_equal(xp.imag(h[1, :]), a0, decimal) + + def test_hilbert_axisN(self, xp): + # tests for axis and N arguments + a = xp.reshape(xp.arange(18, dtype=xp.float64), (3, 6)) + # test axis + aa = hilbert(a, axis=-1) + xp_assert_equal(hilbert(a.T, axis=0), aa.T) + # test 1d + assert_almost_equal(hilbert(a[0, :]), aa[0, :], 14) + + # test N + aan = hilbert(a, N=20, axis=-1) + assert aan.shape == (3, 20) + assert hilbert(a.T, N=20, axis=0).shape == (20, 3) + # the next test is just a regression test, + # no idea whether numbers make sense + a0hilb = np.array([0.000000000000000e+00 - 1.72015830311905j, + 1.000000000000000e+00 - 2.047794505137069j, + 1.999999999999999e+00 - 2.244055555687583j, + 3.000000000000000e+00 - 1.262750302935009j, + 4.000000000000000e+00 - 1.066489252384493j, + 5.000000000000000e+00 + 2.918022706971047j, + 8.881784197001253e-17 + 3.845658908989067j, + -9.444121133484362e-17 + 0.985044202202061j, + -1.776356839400251e-16 + 1.332257797702019j, + -3.996802888650564e-16 + 0.501905089898885j, + 1.332267629550188e-16 + 0.668696078880782j, + -1.192678053963799e-16 + 0.235487067862679j, + -1.776356839400251e-16 + 0.286439612812121j, + 3.108624468950438e-16 + 0.031676888064907j, + 1.332267629550188e-16 - 0.019275656884536j, + -2.360035624836702e-16 - 0.1652588660287j, + 0.000000000000000e+00 - 0.332049855010597j, + 3.552713678800501e-16 - 0.403810179797771j, + 8.881784197001253e-17 - 0.751023775297729j, + 9.444121133484362e-17 - 0.79252210110103j]) + a0hilb = xp.asarray(a0hilb) + assert_almost_equal(aan[0, :], a0hilb, 14, err_msg='N regression') + + def test_hilbert_axis_3d(self, xp): + a = xp.reshape(xp.arange(3 * 5 * 7, dtype=xp.float64), (3, 5, 7)) + # test axis + aa = hilbert(a, axis=-1) + for axis in [0, 1]: + aap = hilbert(xp.moveaxis(a, -1, axis), axis=axis) + aap = xp.moveaxis(aap, axis, -1) + xp_assert_equal(aa, aap) + + @pytest.mark.parametrize('dtype', ['float32', 'float64']) + def test_hilbert_types(self, dtype, xp): + dtype = getattr(xp, dtype) + in_typed = xp.zeros(8, dtype=dtype) + assert xp.real(hilbert(in_typed)).dtype == dtype + + +@make_xp_test_case(hilbert2) +class TestHilbert2: + """Test function `signal.hilbert2`. """ + + @skip_xp_backends(np_only=True, reason='list inputs are numpy-specific') + def test_array_like(self, xp): + hilbert2([[1, 2, 3], [4, 5, 6]]) + + def test_bad_args(self, xp): + """Raise all exceptions in `hilbert2`. """ + x = xp.reshape(xp.arange(16), (4, 4)) + with pytest.raises(ValueError, match="^x must be real."): + hilbert2(xp.asarray([[1.0 + 0.0j]])) + with pytest.raises(ValueError, match="^N must be positive."): + hilbert2(x, N=-1) + with pytest.raises(ValueError, match="^When given as a tuple, N must hold"): + hilbert2(x, N=(1, 1, 1)) + with pytest.raises(ValueError, match="^When given as a tuple, N must hold"): + hilbert2(x, N=(0, 1)) + + @skip_xp_backends("cupy", reason="CuPy's hilbert2 does not have axes= argument") + def test_bad_args2(self, xp): + x = xp.reshape(xp.arange(16), (4, 4)) + with pytest.raises(ValueError, match="^axes must be a tuple of length 2"): + hilbert2(x, axes=(0, 1, 2)) + with pytest.raises(ValueError, match="^axes must contain 2 distinct axes"): + hilbert2(x, axes=(0, 0)) + + @pytest.mark.parametrize('dtype', ['float32', 'float64']) + def test_hilbert2_types(self, dtype, xp): + dtype = getattr(xp, dtype) + in_typed = xp.zeros((2, 32), dtype=dtype) + out = xp.real(signal.hilbert2(in_typed)) + assert out.dtype == dtype + + def test_1d_input(self, xp): + """Needed for 100% coverage """ + x = xp.asarray([0., 1., 1., 0., -1., -1.]) + x0a = signal.hilbert2(xp.reshape(x, (6, 1))) + x1a = signal.hilbert2(xp.reshape(x, (1, 6))) + xp_assert_close(x0a, x1a.T) + + def test_parameter_N(self, xp): + """Compare passing tuple to single int. """ + x = xp.zeros((5, 5)) + x0_a = hilbert2(x, N=4) + x1_a = hilbert2(x, N=(4, 4)) + xp_assert_equal(x1_a, x0_a) + + @pytest.mark.parametrize('shape', [(4, 5), (5, 4), (4, 4), (5, 5)]) + @skip_xp_backends("cupy", reason="Bug in cupy implementation, see cupy#9396") + def test_quadrant_values(self, shape, xp): + """Compare desired and calculated values in Fourier space. """ + x_f = xp.ones(shape, dtype=xp.complex128) # FFT of input signal + x_f = xpx.at(x_f)[0, 0].add(7) + x = xp.real(sp_fft.ifft2(x_f)) # x.imag is zero + + x_as = hilbert2(x) + x_as_f = sp_fft.fft2(x_as) + + # Create slices for bins with purely positive and purely negative frequencies + # (can be verified with `sp_fft.fftfreq()`): + f0_pos, f0_neg = slice(1, (shape[0] + 1) // 2), slice((shape[0] + 1) // 2, None) + f1_pos, f1_neg = slice(1, (shape[1] + 1) // 2), slice((shape[1] + 1) // 2, None) + # Verify all values: + atol = 1e-12 # for x of dtype complex128 + xp_assert_close(x_as_f[f0_pos, f1_pos], x_f[f0_pos, f1_pos] * 4, atol=atol) + xp_assert_close(x_as_f[0, f1_pos], x_f[0, f1_pos] * 2, atol=atol) + xp_assert_close(x_as_f[f0_pos, 0], x_f[f0_pos, 0] * 2, atol=atol) + xp_assert_close(x_as_f[0, 0], x_f[0, 0], atol=atol) + zz_as_f = x_as_f[f0_neg, f1_neg] # check for zeroed orthants + xp_assert_close(zz_as_f, xp.zeros_like(zz_as_f), atol=atol) + + @pytest.mark.parametrize('shape', [(4, 5), (5, 4), (4, 4), (5, 5)]) + def test_zero_analytic_signal(self, shape, xp): + """Test that a real signal with Z[-p,-q] == np.conj(Z[p,q]) + produces a zero analytic signal.""" + c0 = shape[0] // 2 + c1 = shape[1] // 2 + x_f = xp.zeros(shape) + x_f = xpx.at(x_f)[c0 - 1, c1 + 1].set(1.0) + x_f = xpx.at(x_f)[c0 + 1, c1 - 1].set(1.0) + x_f = sp_fft.ifftshift(x_f) + x = xp.real(sp_fft.ifft2(x_f)) + assert xp.sum(abs(x)) > 0.0 + x_as = hilbert2(x) + xp_assert_close(x_as, xp.zeros_like(x_as), atol=xp.finfo(x_as.dtype).eps*16) + + @pytest.mark.parametrize('sh0', [4, 5]) + @pytest.mark.parametrize('sh1', [6, 7]) + @pytest.mark.parametrize('sh2', [8, 9]) + @pytest.mark.parametrize('not_axis', [0, 1, 2]) + @skip_xp_backends("cupy", reason="cupy implementation does not have axes kwarg") + def test_3d_vs_slice(self, sh0, sh1, sh2, not_axis, xp): + """2d transform on 3d array is equal to 2d transform on 2d slices.""" + x = xp.reshape(xp.arange(sh0 * sh1 * sh2, dtype=xp.float64), (sh0, sh1, sh2)) + transform_axes = [0, 1, 2] + transform_axes.pop(not_axis) + x_as_3d = hilbert2(x, axes=transform_axes) + parts = xp.unstack(x, axis=not_axis) + x_as_2d = [hilbert2(p) for p in parts] + x_as_2d = xp.stack(x_as_2d, axis=not_axis) + xp_assert_close(x_as_3d, x_as_2d) + + @skip_xp_backends("cupy", reason="cupy implementation does not have axes kwarg") + def test_3d_axis_order(self, xp): + """2d transform on equal arrays with moved axis are equal.""" + x0 = xp.reshape(xp.arange(5 * 7 * 9, dtype=xp.float64), (5, 7, 9)) + x0_as = hilbert2(x0) + + x1 = xp.moveaxis(x0, 0, 1) + x1_as = hilbert2(x1, axes=(0, 2)) + x1_as = xp.moveaxis(x1_as, 1, 0) + xp_assert_close(x0_as, x1_as) + + x2 = xp.moveaxis(x0, 0, 2) + x2_as = hilbert2(x2, axes=(0, 1)) + x2_as = xp.moveaxis(x2_as, 2, 0) + xp_assert_close(x0_as, x2_as) + + +@make_xp_test_case(envelope) +class TestEnvelope: + """Unit tests for function `._signaltools.envelope()`. """ + + @staticmethod + def assert_close(actual, desired, msg, xp): + a_r_tol = ({'atol': 1e-12, 'rtol': 1e-12} + if xp_default_dtype(xp) == xp.float64 + else {'atol': 1e-5, 'rtol': 1e-5} + ) + + """Little helper to compare to arrays with proper tolerances""" + xp_assert_close(actual, desired, **a_r_tol, err_msg=msg) + + def test_envelope_invalid_parameters(self, xp): + """For `envelope()` Raise all exceptions that are used to verify function + parameters. """ + with pytest.raises(ValueError, + match=r"Invalid parameter axis=2 for z.shape=.*"): + envelope(np.ones(3), axis=2) + with pytest.raises(ValueError, + match=r"z.shape\[axis\] not > 0 for z.shape=.*"): + envelope(xp.ones((3, 0)), axis=1) + for bp_in in [(0, 1, 2), (0, 2.), (None, 2.)]: + ts = ', '.join(map(str, bp_in)) + with pytest.raises(ValueError, + match=rf"bp_in=\({ts}\) isn't a 2-tuple of.*"): + # noinspection PyTypeChecker + envelope(xp.ones(4), bp_in=bp_in) + with pytest.raises(ValueError, + match="n_out=10.0 is not a positive integer or.*"): + # noinspection PyTypeChecker + envelope(xp.ones(4), n_out=10.) + for bp_in in [(-1, 3), (1, 1), (0, 10)]: + with pytest.raises(ValueError, + match=r"`-n//2 <= bp_in\[0\] < bp_in\[1\] <=.*"): + envelope(xp.ones(4), bp_in=bp_in) + with pytest.raises(ValueError, match="residual='undefined' not in .*"): + # noinspection PyTypeChecker + envelope(xp.ones(4), residual='undefined') + + @skip_xp_backends("jax.numpy", reason="XXX: immutable arrays") + def test_envelope_verify_parameters(self, xp): + """Ensure that the various parametrizations produce compatible results. """ + dt_r = xp_default_dtype(xp) + dt_c = xp.complex64 if dt_r == xp.float32 else xp.complex128 + + Z = xp.asarray([4.0, 2, 2, 3, 0], dtype=dt_r) + Zr_a = xp.asarray([4.0, 0, 0, 6, 0, 0, 0, 0], dtype=dt_r) + z = sp_fft.irfft(Z) + n = z.shape[0] + + # the reference envelope: + ze2_0, zr_0 = xp.unstack(envelope(z, (1, 3), residual='all', squared=True)) + self.assert_close(sp_fft.rfft(ze2_0), + xp.asarray([4, 2, 0, 0, 0], dtype=dt_c), + msg="Envelope calculation error", xp=xp) + self.assert_close(sp_fft.rfft(zr_0), + xp.asarray([4, 0, 0, 3, 0], dtype=dt_c), + msg="Residual calculation error", xp=xp) + + ze_1, zr_1 = xp.unstack(envelope(z, (1, 3), residual='all', squared=False)) + self.assert_close(ze_1**2, ze2_0, + msg="Unsquared versus Squared envelope calculation error", + xp=xp) + self.assert_close(zr_1, zr_0, + msg="Unsquared versus Squared residual calculation error", + xp=xp) + + ze2_2, zr_2 = xp.unstack( + envelope(z, (1, 3), residual='all', squared=True, n_out=3*n) + ) + self.assert_close(ze2_2[::3], ze2_0, + msg="3x up-sampled envelope calculation error", xp=xp) + self.assert_close(zr_2[::3], zr_0, + msg="3x up-sampled residual calculation error", xp=xp) + + ze2_3, zr_3 = xp.unstack(envelope(z, (1, 3), residual='lowpass', squared=True)) + self.assert_close(ze2_3, ze2_0, + msg="`residual='lowpass'` envelope calculation error", xp=xp) + self.assert_close(sp_fft.rfft(zr_3), + xp.asarray([4, 0, 0, 0, 0], dtype=dt_c), + msg="`residual='lowpass'` residual calculation error", xp=xp) + + ze2_4 = envelope(z, (1, 3), residual=None, squared=True) + self.assert_close(ze2_4, ze2_0, + msg="`residual=None` envelope calculation error", xp=xp) + + # compare complex analytic signal to real version + Z_a = xp.asarray(Z, copy=True) + Z_a[1:] *= 2 + z_a = sp_fft.ifft(Z_a, n=n) # analytic signal of Z + self.assert_close(xp.real(z_a), z, + msg="Reference analytic signal error", xp=xp) + ze2_a, zr_a = xp.unstack(envelope(z_a, (1, 3), residual='all', squared=True)) + self.assert_close(ze2_a, xp.astype(ze2_0, dt_c), # dtypes must match + msg="Complex envelope calculation error", xp=xp) + self.assert_close(sp_fft.fft(zr_a), xp.asarray(Zr_a, dtype=dt_c), + msg="Complex residual calculation error", xp=xp) + + @skip_xp_backends("jax.numpy", reason="XXX: immutable arrays") + @pytest.mark.parametrize( + " Z, bp_in, Ze2_desired, Zr_desired", + [([1, 0, 2, 2, 0], (1, None), [4, 2, 0, 0, 0], [1, 0, 0, 0, 0]), + ([4, 0, 2, 0, 0], (0, None), [4, 0, 2, 0, 0], [0, 0, 0, 0, 0]), + ([4, 0, 0, 2, 0], (None, None), [4, 0, 0, 2, 0], [0, 0, 0, 0, 0]), + ([0, 0, 2, 2, 0], (1, 3), [2, 0, 0, 0, 0], [0, 0, 0, 2, 0]), + ([4, 0, 2, 2, 0], (-3, 3), [4, 0, 2, 0, 0], [0, 0, 0, 2, 0]), + ([4, 0, 3, 4, 0], (None, 1), [2, 0, 0, 0, 0], [0, 0, 3, 4, 0]), + ([4, 0, 3, 4, 0], (None, 0), [0, 0, 0, 0, 0], [4, 0, 3, 4, 0])]) + def test_envelope_real_signals(self, Z, bp_in, Ze2_desired, Zr_desired, xp): + """Test envelope calculation with real-valued test signals. + + The comparisons are performed in the Fourier space, since it makes evaluating + the bandpass filter behavior straightforward. Note that also the squared + envelope can be easily calculated by hand, if one recalls that coefficients of + a complex-valued Fourier series representing the signal can be directly + determined by an FFT and that the absolute square of a Fourier series is again + a Fourier series. + """ + Z = xp.asarray(Z, dtype=xp.float64) + Ze2_desired = xp.asarray(Ze2_desired, dtype=xp.float64) + Zr_desired = xp.asarray(Zr_desired, dtype=xp.float64) + + z = sp_fft.irfft(Z) + ze2, zr = xp.unstack(envelope(z, bp_in, residual='all', squared=True)) + ze2_lp, zr_lp = xp.unstack(envelope(z, bp_in, residual='lowpass', squared=True)) + Ze2, Zr, Ze2_lp, Zr_lp = (sp_fft.rfft(z_) for z_ in (ze2, zr, ze2_lp, zr_lp)) + + Ze2_desired = xp.asarray(Ze2_desired, dtype=xp.complex128) + Zr_desired = xp.asarray(Zr_desired, dtype=xp.complex128) + self.assert_close(Ze2, Ze2_desired, + msg="Envelope calculation error (residual='all')", xp=xp) + self.assert_close(Zr, Zr_desired, + msg="Residual calculation error (residual='all')", xp=xp) + + if bp_in[1] is not None: + Zr_desired[bp_in[1]:] = 0 + self.assert_close(Ze2_lp, Ze2_desired, + msg="Envelope calculation error (residual='lowpass')", xp=xp) + self.assert_close(Zr_lp, Zr_desired, + msg="Residual calculation error (residual='lowpass')", xp=xp) + + @skip_xp_backends("jax.numpy", reason="XXX: immutable arrays") + @pytest.mark.parametrize( + " Z, bp_in, Ze2_desired, Zr_desired", + [([0, 5, 0, 5, 0], (None, None), [5, 0, 10, 0, 5], [0, 0, 0, 0, 0]), + ([1, 5, 0, 5, 2], (-1, 2), [5, 0, 10, 0, 5], [1, 0, 0, 0, 2]), + ([1, 2, 6, 0, 6, 3], (-1, 2), [0, 6, 0, 12, 0, 6], [1, 2, 0, 0, 0, 3]) + ]) + def test_envelope_complex_signals(self, Z, bp_in, Ze2_desired, Zr_desired, xp): + """Test envelope calculation with complex-valued test signals. + + We only need to test for the complex envelope here, since the ``Nones``s in the + bandpass filter were already tested in the previous test. + """ + Z = xp.asarray(Z, dtype=xp.float64) + Ze2_desired = xp.asarray(Ze2_desired, dtype=xp.complex128) + Zr_desired = xp.asarray(Zr_desired, dtype=xp.complex128) + + z = sp_fft.ifft(sp_fft.ifftshift(Z)) + ze2, zr = xp.unstack(envelope(z, bp_in, residual='all', squared=True)) + Ze2, Zr = (sp_fft.fftshift(sp_fft.fft(z_)) for z_ in (ze2, zr)) + + self.assert_close(Ze2, Ze2_desired, + msg="Envelope calculation error", xp=xp) + self.assert_close(Zr, Zr_desired, + msg="Residual calculation error", xp=xp) + + @skip_xp_backends("jax.numpy", reason="XXX: immutable arrays") + def test_envelope_verify_axis_parameter(self, xp): + """Test for multi-channel envelope calculations. """ + dt_r = xp_default_dtype(xp) + dt_c = xp.complex64 if dt_r == xp.float32 else xp.complex128 + + z = sp_fft.irfft(xp.asarray([[1.0, 0, 2, 2, 0], [7, 0, 4, 4, 0]], dtype=dt_r)) + Ze2_desired = xp.asarray([[4, 2, 0, 0, 0], [16, 8, 0, 0, 0]], + dtype=dt_c) + Zr_desired = xp.asarray([[1, 0, 0, 0, 0], [7, 0, 0, 0, 0]], dtype=dt_c) + + ze2, zr = xp.unstack(envelope(z, squared=True, axis=1)) + ye2T, yrT = xp.unstack(envelope(z.T, squared=True, axis=0)) + Ze2, Ye2, Zr, Yr = (sp_fft.rfft(z_) for z_ in (ze2, ye2T.T, zr, yrT.T)) + + self.assert_close(Ze2, Ze2_desired, msg="2d envelope calculation error", xp=xp) + self.assert_close(Zr, Zr_desired, msg="2d residual calculation error", xp=xp) + self.assert_close( + Ye2, Ze2_desired, msg="Transposed 2d envelope calc. error", xp=xp + ) + self.assert_close( + Yr, Zr_desired, msg="Transposed 2d residual calc. error", xp=xp + ) + + @skip_xp_backends("jax.numpy", reason="XXX: immutable arrays") + def test_envelope_verify_axis_parameter_complex(self, xp): + """Test for multi-channel envelope calculations with complex values. """ + dt_r = xp_default_dtype(xp) + dt_c = xp.complex64 if dt_r == xp.float32 else xp.complex128 + inp = xp.asarray([[1.0, 5, 0, 5, 2], [1, 10, 0, 10, 2]], dtype=dt_r) + z = sp_fft.ifft(sp_fft.ifftshift(inp, axes=1)) + Ze2_des = xp.asarray([[5, 0, 10, 0, 5], [20, 0, 40, 0, 20],], dtype=dt_c) + Zr_des = xp.asarray([[1, 0, 0, 0, 2], [1, 0, 0, 0, 2]], dtype=dt_c) + + kw = dict(bp_in=(-1, 2), residual='all', squared=True) + ze2, zr = xp.unstack(envelope(z, axis=1, **kw)) + ye2T, yrT = xp.unstack(envelope(z.T, axis=0, **kw)) + Ze2, Ye2, Zr, Yr = (sp_fft.fftshift(sp_fft.fft(z_), axes=1) + for z_ in (ze2, ye2T.T, zr, yrT.T)) + + self.assert_close(Ze2, Ze2_des, msg="2d envelope calculation error", xp=xp) + self.assert_close(Zr, Zr_des, msg="2d residual calculation error", xp=xp) + self.assert_close( + Ye2, Ze2_des, msg="Transposed 2d envelope calc. error", xp=xp + ) + self.assert_close(Yr, Zr_des, msg="Transposed 2d residual calc. error", xp=xp) + + @skip_xp_backends("jax.numpy", reason="XXX: immutable arrays") + @pytest.mark.parametrize('X', [[4, 0, 0, 1, 2], [4, 0, 0, 2, 1, 2]]) + def test_compare_envelope_hilbert(self, X, xp): + """Compare output of `envelope()` and `hilbert()`. """ + X = xp.asarray(X, dtype=xp.float64) + x = sp_fft.irfft(X) + e_hil = xp.abs(hilbert(x)) + e_env = envelope(x, (None, None), residual=None) + self.assert_close(e_hil, e_env, msg="Hilbert-Envelope comparison error", xp=xp) + + def test_nyquist(self): + """Test behavior when input is a cosine at the Nyquist frequency. + + Resampling even length signals, requires accounting for unpaired bins at the + Nyquist frequency (consults the source code of `resample`). + + Since `envelope` excludes the Nyquist frequency from the envelope calculation, + only the residues need to be investigated. + """ + x4 = sp_fft.irfft([0, 0, 8]) # = [2, -2, 2, -2] + x6 = signal.resample(x4, num=6) # = [2, -1, -1, 2, -1, -1] + y6, y6_res = envelope(x4, n_out=6, residual='all') # real-valued case + z6, z6_res = envelope(x4 + 0j, n_out=6, residual='all') # complex-valued case + + xp_assert_close(y6, np.zeros(6), atol=1e-12) + xp_assert_close(y6_res, x6, atol=1e-12) + + xp_assert_close(z6, np.zeros(6, dtype=z6.dtype), atol=1e-12) + xp_assert_close(z6_res, x6.astype(z6.dtype), atol=1e-12) + + +class TestPartialFractionExpansion: + @staticmethod + def assert_rp_almost_equal(r, p, r_true, p_true, decimal=7): + xp = array_namespace(r, p) + r_true = xp.asarray(r_true) + p_true = xp.asarray(p_true) + + distance = xp.hypot(abs(p[:, None] - p_true), + abs(r[:, None] - r_true)) + + rows, cols = linear_sum_assignment(_xp_copy_to_numpy(distance)) + assert_almost_equal(p[rows], p_true[cols], decimal=decimal) + assert_almost_equal(r[rows], r_true[cols], decimal=decimal) + + def test_compute_factors(self): + factors, poly = _compute_factors([1, 2, 3], [3, 2, 1]) + assert len(factors) == 3 + assert_almost_equal(factors[0], np.poly([2, 2, 3])) + assert_almost_equal(factors[1], np.poly([1, 1, 1, 3])) + assert_almost_equal(factors[2], np.poly([1, 1, 1, 2, 2])) + assert_almost_equal(poly, np.poly([1, 1, 1, 2, 2, 3])) + + factors, poly = _compute_factors([1, 2, 3], [3, 2, 1], + include_powers=True) + assert len(factors) == 6 + assert_almost_equal(factors[0], np.poly([1, 1, 2, 2, 3])) + assert_almost_equal(factors[1], np.poly([1, 2, 2, 3])) + assert_almost_equal(factors[2], np.poly([2, 2, 3])) + assert_almost_equal(factors[3], np.poly([1, 1, 1, 2, 3])) + assert_almost_equal(factors[4], np.poly([1, 1, 1, 3])) + assert_almost_equal(factors[5], np.poly([1, 1, 1, 2, 2])) + assert_almost_equal(poly, np.poly([1, 1, 1, 2, 2, 3])) + + def test_group_poles(self): + unique, multiplicity = _group_poles( + [1.0, 1.001, 1.003, 2.0, 2.003, 3.0], 0.1, 'min') + xp_assert_close(unique, [1.0, 2.0, 3.0]) + xp_assert_close(multiplicity, [3, 2, 1]) + + @make_xp_test_case(residue) + def test_residue_general(self, xp): + # Test are taken from issue #4464, note that poles in scipy are + # in increasing by absolute value order, opposite to MATLAB. + r, p, k = residue(xp.asarray([5, 3, -2, 7]), xp.asarray([-4, 0, 8, 3])) + assert_almost_equal(r, xp.asarray([1.3320, -0.6653, -1.4167]), decimal=4) + assert_almost_equal(p, xp.asarray([-0.4093, -1.1644, 1.5737]), decimal=4) + assert_almost_equal(k, xp.asarray([-1.2500]), decimal=4) + + r, p, k = residue(xp.asarray([-4, 8]), xp.asarray([1, 6, 8])) + assert_almost_equal(r, xp.asarray([8, -12])) + assert_almost_equal(p, xp.asarray([-2, -4])) + assert k.size == 0 + + r, p, k = residue(xp.asarray([4, 1]), xp.asarray([1, -1, -2])) + assert_almost_equal(r, xp.asarray([1, 3])) + assert_almost_equal(p, xp.asarray([-1, 2])) + assert k.size == 0 + + r, p, k = residue(xp.asarray([4, 3]), + xp.asarray([2, -3.4, 1.98, -0.406])) + self.assert_rp_almost_equal( + r, p, [-18.125 - 13.125j, -18.125 + 13.125j, 36.25], + [0.5 - 0.2j, 0.5 + 0.2j, 0.7]) + assert k.size == 0 + + r, p, k = residue(xp.asarray([2, 1]), xp.asarray([1, 5, 8, 4])) + self.assert_rp_almost_equal(r, p, [-1, 1, 3], + [-1, -2, -2]) + assert k.size == 0 + + r, p, k = residue(xp.asarray([3, -1.1, 0.88, -2.396, 1.348]), + xp.asarray([1, -0.7, -0.14, 0.048])) + assert_almost_equal(r, xp.asarray([-3, 4, 1])) + assert_almost_equal(p, xp.asarray([0.2, -0.3, 0.8])) + assert_almost_equal(k, xp.asarray([3, 1])) + + r, p, k = residue(xp.asarray([1]), xp.asarray([1, 2, -3])) + assert_almost_equal(r, xp.asarray([0.25, -0.25])) + assert_almost_equal(p, xp.asarray([1, -3])) + assert k.size == 0 + + r, p, k = residue(xp.asarray([1, 0, -5]), xp.asarray([1, 0, 0, 0, -1])) + self.assert_rp_almost_equal(r, p, + [1, 1.5j, -1.5j, -1], + [-1, -1j, 1j, 1]) + assert k.size == 0 + + r, p, k = residue(xp.asarray([3, 8, 6]), xp.asarray([1, 3, 3, 1])) + self.assert_rp_almost_equal(r, p, [1, 2, 3], + [-1, -1, -1]) + assert k.size == 0 + + r, p, k = residue(xp.asarray([3, -1]), xp.asarray([1, -3, 2])) + assert_almost_equal(r, xp.asarray([-2, 5])) + assert_almost_equal(p, xp.asarray([1, 2])) + assert k.size == 0 + + r, p, k = residue(xp.asarray([2, 3, -1]), xp.asarray([1, -3, 2])) + assert_almost_equal(r, xp.asarray([-4, 13])) + assert_almost_equal(p, xp.asarray([1, 2])) + assert_almost_equal(k, xp.asarray([2])) + + r, p, k = residue(xp.asarray([7, 2, 3, -1]), xp.asarray([1, -3, 2])) + assert_almost_equal(r, xp.asarray([-11, 69])) + assert_almost_equal(p, xp.asarray([1, 2])) + assert_almost_equal(k, xp.asarray([7, 23])) + + r, p, k = residue(xp.asarray([2, 3, -1]), xp.asarray([1, -3, 4, -2])) + self.assert_rp_almost_equal(r, p, [4, -1 + 3.5j, -1 - 3.5j], + [1, 1 - 1j, 1 + 1j]) + assert k.size == 0 + + @make_xp_test_case(residue) + def test_residue_leading_zeros(self, xp): + # Leading zeros in numerator or denominator must not affect the answer. + r0, p0, k0 = residue(xp.asarray([5, 3, -2, 7]), xp.asarray([-4, 0, 8, 3])) + r1, p1, k1 = residue(xp.asarray([0, 5, 3, -2, 7]), xp.asarray([-4, 0, 8, 3])) + r2, p2, k2 = residue(xp.asarray([5, 3, -2, 7]), xp.asarray([0, -4, 0, 8, 3])) + r3, p3, k3 = residue(xp.asarray([0, 0, 5, 3, -2, 7]), + xp.asarray([0, 0, 0, -4, 0, 8, 3])) + assert_almost_equal(r0, r1) + assert_almost_equal(r0, r2) + assert_almost_equal(r0, r3) + assert_almost_equal(p0, p1) + assert_almost_equal(p0, p2) + assert_almost_equal(p0, p3) + assert_almost_equal(k0, k1) + assert_almost_equal(k0, k2) + assert_almost_equal(k0, k3) + + @make_xp_test_case(residue) + def test_residue_degenerate(self, xp): + # Several tests for zero numerator and denominator. + r, p, k = residue(xp.asarray([0, 0]), xp.asarray([1, 6, 8])) + assert_almost_equal(r, xp.asarray([0, 0])) + assert_almost_equal(p, xp.asarray([-2, -4])) + assert k.size == 0 + + r, p, k = residue(xp.asarray(0), xp.asarray(1)) + assert r.size == 0 + assert p.size == 0 + assert k.size == 0 + + with pytest.raises(ValueError, match="Denominator `a` is zero."): + residue(1, 0) + + @make_xp_test_case(residuez) + def test_residuez_general(self, xp): + r, p, k = residuez(xp.asarray([1, 6, 6, 2]), + xp.asarray([1, -(2 + 1j), (1 + 2j), -1j])) + self.assert_rp_almost_equal(r, p, [-2+2.5j, 7.5+7.5j, -4.5-12j], + [1j, 1, 1]) + assert_almost_equal(k, xp.asarray([2j])) + + r, p, k = residuez(xp.asarray([1, 2, 1]), xp.asarray([1, -1, 0.3561])) + self.assert_rp_almost_equal(r, p, + [-0.9041 - 5.9928j, -0.9041 + 5.9928j], + [0.5 + 0.3257j, 0.5 - 0.3257j], + decimal=4) + assert_almost_equal(k, xp.asarray([2.8082]), decimal=4) + + r, p, k = residuez(xp.asarray([1, -1]), xp.asarray([1, -5, 6])) + assert_almost_equal(r, xp.asarray([-1, 2])) + assert_almost_equal(p, xp.asarray([2, 3])) + assert k.size == 0 + + r, p, k = residuez(xp.asarray([2, 3, 4]), xp.asarray([1, 3, 3, 1])) + self.assert_rp_almost_equal(r, p, [4, -5, 3], [-1, -1, -1]) + assert k.size == 0 + + r, p, k = residuez(xp.asarray([1, -10, -4, 4]), xp.asarray([2, -2, -4])) + assert_almost_equal(r, xp.asarray([0.5, -1.5])) + assert_almost_equal(p, xp.asarray([-1, 2])) + assert_almost_equal(k, xp.asarray([1.5, -1])) + + r, p, k = residuez(xp.asarray([18]), xp.asarray([18, 3, -4, -1])) + self.assert_rp_almost_equal(r, p, + [0.36, 0.24, 0.4], [0.5, -1/3, -1/3]) + assert k.size == 0 + + r, p, k = residuez(xp.asarray([2, 3]), + xp.asarray(np.polymul([1, -1/2], [1, 1/4]))) + assert_almost_equal(r, xp.asarray([-10/3, 16/3])) + assert_almost_equal(p, xp.asarray([-0.25, 0.5])) + assert k.size == 0 + + r, p, k = residuez(xp.asarray([1, -2, 1]), xp.asarray([1, -1])) + assert_almost_equal(r, xp.asarray([0])) + assert_almost_equal(p, xp.asarray([1])) + assert_almost_equal(k, xp.asarray([1, -1])) + + r, p, k = residuez(xp.asarray(1), xp.asarray([1, -1j])) + assert_almost_equal(r, xp.asarray([1])) + assert_almost_equal(p, xp.asarray([1j])) + assert k.size == 0 + + r, p, k = residuez(xp.asarray(1), xp.asarray([1, -1, 0.25])) + assert_almost_equal(r, xp.asarray([0, 1])) + assert_almost_equal(p, xp.asarray([0.5, 0.5])) + assert k.size == 0 + + r, p, k = residuez(xp.asarray(1), xp.asarray([1, -0.75, .125])) + assert_almost_equal(r, xp.asarray([-1, 2])) + assert_almost_equal(p, xp.asarray([0.25, 0.5])) + assert k.size == 0 + + r, p, k = residuez(xp.asarray([1, 6, 2]), xp.asarray([1, -2, 1])) + assert_almost_equal(r, xp.asarray([-10, 9])) + assert_almost_equal(p, xp.asarray([1, 1])) + assert_almost_equal(k, xp.asarray([2])) + + r, p, k = residuez(xp.asarray([6, 2]), xp.asarray([1, -2, 1])) + assert_almost_equal(r, xp.asarray([-2, 8])) + assert_almost_equal(p, xp.asarray([1, 1])) + assert k.size == 0 + + r, p, k = residuez(xp.asarray([1, 6, 6, 2]), xp.asarray([1, -2, 1])) + assert_almost_equal(r, xp.asarray([-24, 15])) + assert_almost_equal(p, xp.asarray([1, 1])) + assert_almost_equal(k, xp.asarray([10, 2])) + + r, p, k = residuez(xp.asarray([1, 0, 1]), xp.asarray([1, 0, 0, 0, 0, -1])) + self.assert_rp_almost_equal(r, p, + [0.2618 + 0.1902j, 0.2618 - 0.1902j, + 0.4, 0.0382 - 0.1176j, 0.0382 + 0.1176j], + [-0.8090 + 0.5878j, -0.8090 - 0.5878j, + 1.0, 0.3090 + 0.9511j, 0.3090 - 0.9511j], + decimal=4) + assert k.size == 0 + + @make_xp_test_case(residuez) + def test_residuez_trailing_zeros(self, xp): + # Trailing zeros in numerator or denominator must not affect the + # answer. + r0, p0, k0 = residuez(xp.asarray([5, 3, -2, 7]), + xp.asarray([-4, 0, 8, 3])) + r1, p1, k1 = residuez(xp.asarray([5, 3, -2, 7, 0]), + xp.asarray([-4, 0, 8, 3])) + r2, p2, k2 = residuez(xp.asarray([5, 3, -2, 7]), + xp.asarray([-4, 0, 8, 3, 0])) + r3, p3, k3 = residuez(xp.asarray([5, 3, -2, 7, 0, 0]), + xp.asarray([-4, 0, 8, 3, 0, 0, 0])) + assert_almost_equal(r0, r1) + assert_almost_equal(r0, r2) + assert_almost_equal(r0, r3) + assert_almost_equal(p0, p1) + assert_almost_equal(p0, p2) + assert_almost_equal(p0, p3) + assert_almost_equal(k0, k1) + assert_almost_equal(k0, k2) + assert_almost_equal(k0, k3) + + @make_xp_test_case(residuez) + def test_residuez_degenerate(self, xp): + r, p, k = residuez(xp.asarray([0, 0]), xp.asarray([1, 6, 8])) + assert_almost_equal(r, xp.asarray([0, 0])) + assert_almost_equal(p, xp.asarray([-2, -4])) + assert k.size == 0 + + r, p, k = residuez(xp.asarray(0), xp.asarray(1)) + assert r.size == 0 + assert p.size == 0 + assert k.size == 0 + + with pytest.raises(ValueError, match="Denominator `a` is zero."): + residuez(xp.asarray(1), xp.asarray(0)) + + with pytest.raises(ValueError, + match="First coefficient of determinant `a` must " + "be non-zero."): + residuez(xp.asarray(1), xp.asarray([0, 1, 2, 3])) + + @make_xp_test_case(invres, invresz) + def test_inverse_unique_roots_different_rtypes(self, xp): + # This test was inspired by github issue 2496. + r = xp.asarray([3 / 10, -1 / 6, -2 / 15]) + p = xp.asarray([0, -2, -5]) + k = xp.asarray([]) + b_expected = xp.asarray([0.0, 1, 3]) + a_expected = xp.asarray([1, 7, 10, 0]) + + # With the default tolerance, the rtype does not matter + # for this example. + for rtype in ('avg', 'mean', 'min', 'minimum', 'max', 'maximum'): + b, a = invres(r, p, k, rtype=rtype) + xp_assert_close(b, b_expected, atol=5e-16) + xp_assert_close(a, a_expected, check_dtype=False, atol=5e-16) + + b, a = invresz(r, p, k, rtype=rtype) + xp_assert_close(b, b_expected, atol=5e-16) + xp_assert_close(a, a_expected, check_dtype=False, atol=5e-16) + + @make_xp_test_case(invres, invresz) + def test_inverse_repeated_roots_different_rtypes(self, xp): + r = xp.asarray([3 / 20, -7 / 36, -1 / 6, 2 / 45]) + p = xp.asarray([0, -2, -2, -5]) + k = xp.asarray([]) + b_expected = xp.asarray([0.0, 0, 1, 3]) + b_expected_z = xp.asarray([-1/6, -2/3, 11/6, 3]) + a_expected = xp.asarray([1, 9, 24, 20, 0]) + + for rtype in ('avg', 'mean', 'min', 'minimum', 'max', 'maximum'): + b, a = invres(r, p, k, rtype=rtype) + xp_assert_close(b, b_expected, atol=1e-14) + xp_assert_close(a, a_expected, check_dtype=False) + + b, a = invresz(r, p, k, rtype=rtype) + xp_assert_close(b, b_expected_z, atol=1e-14) + xp_assert_close(a, a_expected, check_dtype=False) + + @make_xp_test_case(invres, invresz) + def test_inverse_bad_rtype(self, xp): + r = xp.asarray([3 / 20, -7 / 36, -1 / 6, 2 / 45]) + p = xp.asarray([0, -2, -2, -5]) + k = xp.asarray([]) + with pytest.raises(ValueError, match="`rtype` must be one of"): + invres(r, p, k, rtype='median') + with pytest.raises(ValueError, match="`rtype` must be one of"): + invresz(r, p, k, rtype='median') + + @make_xp_test_case(invresz) + def test_invresz_one_coefficient_bug(self, xp): + # Regression test for issue in gh-4646. + r = xp.asarray([1]) + p = xp.asarray([2]) + k = xp.asarray([0]) + b, a = invresz(r, p, k) + xp_assert_close(b, xp.asarray([1])) + xp_assert_close(a, xp.asarray([1.0, -2.0])) + + @make_xp_test_case(invres) + def test_invres(self, xp): + b, a = invres(xp.asarray([1]), xp.asarray([1]), xp.asarray([])) + assert_almost_equal(b, xp.asarray([1])) + assert_almost_equal(a, xp.asarray([1, -1])) + + b, a = invres(xp.asarray([1 - 1j, 2, 0.5 - 3j]), + xp.asarray([1, 0.5j, 1 + 1j]), xp.asarray([])) + assert_almost_equal(b, xp.asarray([3.5 - 4j, -8.5 + 0.25j, 3.5 + 3.25j])) + assert_almost_equal(a, xp.asarray([1, -2 - 1.5j, 0.5 + 2j, 0.5 - 0.5j])) + + b, a = invres(xp.asarray([0.5, 1]), xp.asarray([1 - 1j, 2 + 2j]), + xp.asarray([1, 2, 3])) + assert_almost_equal(b, xp.asarray([1, -1 - 1j, 1 - 2j, 0.5 - 3j, 10])) + assert_almost_equal(a, xp.asarray([1, -3 - 1j, 4])) + + b, a = invres(xp.asarray([-1, 2, 1j, 3 - 1j, 4, -2]), + xp.asarray([-1, 2 - 1j, 2 - 1j, 3, 3, 3]), xp.asarray([])) + assert_almost_equal(b, + xp.asarray([4 - 1j, -28 + 16j, 40 - 62j, 100 + 24j, + -292 + 219j, 192 - 268j])) + assert_almost_equal(a, + xp.asarray([1, -12 + 2j, 53 - 20j, -96 + 68j, 27 - 72j, + 108 - 54j, -81 + 108j])) + + b, a = invres(xp.asarray([-1, 1j]), xp.asarray([1, 1]), xp.asarray([1, 2])) + assert_almost_equal(b, xp.asarray([1, 0, -4, 3 + 1j])) + assert_almost_equal(a, xp.asarray([1, -2, 1])) + + @make_xp_test_case(invresz) + def test_invresz(self, xp): + b, a = invresz(xp.asarray([1]), xp.asarray([1]), xp.asarray([])) + assert_almost_equal(b, xp.asarray([1])) + assert_almost_equal(a, xp.asarray([1, -1])) + + b, a = invresz(xp.asarray([1 - 1j, 2, 0.5 - 3j]), + xp.asarray([1, 0.5j, 1 + 1j]), xp.asarray([])) + assert_almost_equal(b, xp.asarray([3.5 - 4j, -8.5 + 0.25j, 3.5 + 3.25j])) + assert_almost_equal(a, xp.asarray([1, -2 - 1.5j, 0.5 + 2j, 0.5 - 0.5j])) + + b, a = invresz(xp.asarray([0.5, 1]), + xp.asarray([1 - 1j, 2 + 2j]), + xp.asarray([1, 2, 3])) + assert_almost_equal(b, xp.asarray([2.5, -3 - 1j, 1 - 2j, -1 - 3j, 12])) + assert_almost_equal(a, xp.asarray([1, -3 - 1j, 4])) + + b, a = invresz(xp.asarray([-1, 2, 1j, 3 - 1j, 4, -2]), + xp.asarray([-1, 2 - 1j, 2 - 1j, 3, 3, 3]), + xp.asarray([])) + assert_almost_equal(b, + xp.asarray([6, -50 + 11j, 100 - 72j, 80 + 58j, + -354 + 228j, 234 - 297j])) + assert_almost_equal(a, + xp.asarray([1, -12 + 2j, 53 - 20j, -96 + 68j, 27 - 72j, + 108 - 54j, -81 + 108j])) + + b, a = invresz(xp.asarray([-1, 1j]), + xp.asarray([1, 1]), + xp.asarray([1, 2])) + assert_almost_equal(b, xp.asarray([1j, 1, -3, 2])) + assert_almost_equal(a, xp.asarray([1, -2, 1])) + + @skip_xp_backends(np_only=True) + @make_xp_test_case(invres, invresz) + def test_inverse_scalar_arguments(self, xp): + b, a = invres(1, 1, 1) + assert_almost_equal(b, [1, 0]) + assert_almost_equal(a, [1, -1]) + + b, a = invresz(1, 1, 1) + assert_almost_equal(b, [2, -1]) + assert_almost_equal(a, [1, -1]) + + +@make_xp_test_case(vectorstrength) +class TestVectorstrength: + + def test_single_1dperiod(self, xp): + events = xp.asarray([.5]) + period = 5. + targ_strength = 1. + targ_phase = .1 + + strength, phase = vectorstrength(events, period) + + assert strength.ndim == 0 + assert phase.ndim == 0 + + assert math.isclose(strength, targ_strength, abs_tol=1.5e-7) + assert math.isclose(phase, 2 * math.pi * targ_phase, abs_tol=1.5e-7) + + @xfail_xp_backends('torch', reason="phase modulo 2*pi") + def test_single_2dperiod(self, xp): + events = xp.asarray([.5]) + period = xp.asarray([1, 2, 5.]) + targ_strength = xp.asarray([1.] * 3) + targ_phase = xp.asarray([.5, .25, .1]) + + strength, phase = vectorstrength(events, period) + + assert strength.ndim == 1 + assert phase.ndim == 1 + assert_array_almost_equal(strength, targ_strength) + assert_almost_equal(phase, 2 * xp.pi * targ_phase) + + def test_equal_1dperiod(self, xp): + events = xp.asarray([.25, .25, .25, .25, .25, .25]) + period = 2 + targ_strength = 1. + targ_phase = .125 + + strength, phase = vectorstrength(events, period) + + assert strength.ndim == 0 + assert phase.ndim == 0 + + assert math.isclose(strength, targ_strength, abs_tol=1.5e-7) + assert math.isclose(phase, 2 * math.pi * targ_phase, abs_tol=1.5e-7) + + def test_equal_2dperiod(self, xp): + events = xp.asarray([.25, .25, .25, .25, .25, .25]) + period = xp.asarray([1, 2, ]) + targ_strength = xp.asarray([1.] * 2) + targ_phase = xp.asarray([.25, .125]) + + strength, phase = vectorstrength(events, period) + + assert strength.ndim == 1 + assert phase.ndim == 1 + assert_almost_equal(strength, targ_strength) + assert_almost_equal(phase, 2 * xp.pi * targ_phase) + + def test_spaced_1dperiod(self, xp): + events = xp.asarray([.1, 1.1, 2.1, 4.1, 10.1]) + period = 1 + targ_strength = 1. + targ_phase = .1 + + strength, phase = vectorstrength(events, period) + + assert strength.ndim == 0 + assert phase.ndim == 0 + + assert math.isclose(strength, targ_strength, abs_tol=1.5e-7) + assert math.isclose(phase, 2 * math.pi * targ_phase, abs_tol=1.5e-6) + + def test_spaced_2dperiod(self, xp): + events = xp.asarray([.1, 1.1, 2.1, 4.1, 10.1]) + period = xp.asarray([1, .5]) + targ_strength = xp.asarray([1.] * 2) + targ_phase = xp.asarray([.1, .2]) + + strength, phase = vectorstrength(events, period) + + assert strength.ndim == 1 + assert phase.ndim == 1 + assert_almost_equal(strength, targ_strength) + rtol_kw = {'rtol': 2e-6} if xp_default_dtype(xp) == xp.float32 else {} + xp_assert_close(phase, 2 * xp.pi * targ_phase, **rtol_kw) + + def test_partial_1dperiod(self, xp): + events = xp.asarray([.25, .5, .75]) + period = 1 + targ_strength = 1. / 3. + targ_phase = .5 + + strength, phase = vectorstrength(events, period) + + assert strength.ndim == 0 + assert phase.ndim == 0 + + assert math.isclose(strength, targ_strength) + assert math.isclose(phase, 2 * math.pi * targ_phase) + + + @xfail_xp_backends("torch", reason="phase modulo 2*pi") + def test_partial_2dperiod(self, xp): + events = xp.asarray([.25, .5, .75]) + period = xp.asarray([1., 1., 1., 1.]) + targ_strength = xp.asarray([1. / 3.] * 4) + targ_phase = xp.asarray([.5, .5, .5, .5]) + + strength, phase = vectorstrength(events, period) + + assert strength.ndim == 1 + assert phase.ndim == 1 + assert_almost_equal(strength, targ_strength) + assert_almost_equal(phase, 2 * xp.pi * targ_phase) + + def test_opposite_1dperiod(self, xp): + events = xp.asarray([0, .25, .5, .75]) + period = 1. + targ_strength = 0 + + strength, phase = vectorstrength(events, period) + + assert strength.ndim == 0 + assert phase.ndim == 0 + assert math.isclose(strength, targ_strength, abs_tol=1.5e-7) + + def test_opposite_2dperiod(self, xp): + events = xp.asarray([0, .25, .5, .75]) + period = xp.asarray([1.] * 10) + targ_strength = xp.asarray([0.] * 10) + + strength, phase = vectorstrength(events, period) + + assert strength.ndim == 1 + assert phase.ndim == 1 + assert_almost_equal(strength, targ_strength) + + def test_2d_events_ValueError(self, xp): + events = xp.asarray([[1, 2]]) + period = 1. + assert_raises(ValueError, vectorstrength, events, period) + + def test_2d_period_ValueError(self, xp): + events = 1. + period = xp.asarray([[1]]) + assert_raises(ValueError, vectorstrength, events, period) + + def test_zero_period_ValueError(self, xp): + events = 1. + period = 0 + assert_raises(ValueError, vectorstrength, events, period) + + def test_negative_period_ValueError(self, xp): + events = 1. + period = -1 + assert_raises(ValueError, vectorstrength, events, period) + + +# XXX: restore testing on CuPy, where possible. Multiple issues in this test: +# 1. _zi functions deliberately incompatible in cupy +# (https://github.com/scipy/scipy/pull/21713#issuecomment-2417494443) +# 2. a CuPy issue to be fixed in 14.0 only +# (https://github.com/cupy/cupy/pull/8677) +# 3. an issue with CuPy's __array__ not numpy-2.0 compatible +@skip_xp_backends(cpu_only=True) +@make_xp_test_case(sosfilt) +@pytest.mark.parametrize('dt', ['float32', 'float64', 'complex64', 'complex128']) +class TestSOSFilt: + + # The test_rank* tests are pulled from _TestLinearFilter + @skip_xp_backends('jax.numpy', reason='buffer array is read-only') + def test_rank1(self, dt, xp): + dt = getattr(xp, dt) + x = xp.linspace(0, 5, 6, dtype=dt) + b = xp.asarray([1, -1], dtype=dt) + a = xp.asarray([0.5, -0.5], dtype=dt) + + # Test simple IIR + y_r = xp.asarray([0, 2, 4, 6, 8, 10.], dtype=dt) + bb, aa = map(np.asarray, (b, a)) + sos = tf2sos(bb, aa) + sos = xp.asarray(sos) # XXX while tf2sos is numpy only + assert_array_almost_equal(sosfilt(sos, x), y_r) + + # Test simple FIR + b = xp.asarray([1, 1], dtype=dt) + # NOTE: This was changed (rel. to TestLinear...) to add a pole @zero: + a = xp.asarray([1, 0], dtype=dt) + y_r = xp.asarray([0, 1, 3, 5, 7, 9.], dtype=dt) + bb, aa = map(np.asarray, (b, a)) + sos = tf2sos(bb, aa) + sos = xp.asarray(sos) # XXX while tf2sos is numpy only + assert_array_almost_equal(sosfilt(sos, x), y_r) + + b = xp.asarray([1.0, 1, 0]) + a = xp.asarray([1.0, 0, 0]) + x = xp.ones(8) + + sos = xp.concat((b, a)) + sos = xp.reshape(sos, (1, 6)) + y = sosfilt(sos, x) + xp_assert_close(y, xp.asarray([1.0, 2, 2, 2, 2, 2, 2, 2])) + + @skip_xp_backends('jax.numpy', reason='buffer array is read-only') + def test_rank2(self, dt, xp): + dt = getattr(xp, dt) + shape = (4, 3) + prodshape = math.prod(shape) + x = xp.linspace(0, prodshape - 1, prodshape, dtype=dt) + x = xp.reshape(x, shape) + + b = xp.asarray([1, -1], dtype=dt) + a = xp.asarray([0.5, 0.5], dtype=dt) + + y_r2_a0 = xp.asarray([[0, 2, 4], [6, 4, 2], [0, 2, 4], [6, 4, 2]], + dtype=dt) + + y_r2_a1 = xp.asarray([[0, 2, 0], [6, -4, 6], [12, -10, 12], + [18, -16, 18]], dtype=dt) + + bb, aa = map(np.asarray, (b, a)) + sos = tf2sos(bb, aa) + sos = xp.asarray(sos) # XXX + y = sosfilt(sos, x, axis=0) + assert_array_almost_equal(y_r2_a0, y) + + sos = tf2sos(bb, aa) + sos = xp.asarray(sos) # XXX + y = sosfilt(sos, x, axis=1) + assert_array_almost_equal(y_r2_a1, y) + + @skip_xp_backends('jax.numpy', reason='buffer array is read-only') + def test_rank3(self, dt, xp): + dt = getattr(xp, dt) + shape = (4, 3, 2) + prodshape = math.prod(shape) + x = xp.linspace(0, prodshape - 1, prodshape) + x = xp.reshape(x, shape) + + b = xp.asarray([1, -1], dtype=dt) + a = xp.asarray([0.5, 0.5], dtype=dt) + + # Test last axis + bb, aa = map(np.asarray, (b, a)) # XXX until tf2sos is array api compatible + sos = tf2sos(bb, aa) + sos = xp.asarray(sos) # XXX + y = sosfilt(sos, x) + for i in range(x.shape[0]): + for j in range(x.shape[1]): + assert_array_almost_equal(y[i, j, ...], lfilter(b, a, x[i, j, ...])) + + def _get_ab_sos(self, xp): + b1, a1 = signal.butter(2, 0.25, 'low') + b2, a2 = signal.butter(2, 0.75, 'low') + b3, a3 = signal.butter(2, 0.75, 'low') + b = np.convolve(np.convolve(b1, b2), b3) + a = np.convolve(np.convolve(a1, a2), a3) + sos = np.array((np.r_[b1, a1], np.r_[b2, a2], np.r_[b3, a3])) + + a, b, sos = map(xp.asarray, (a, b, sos)) + return a, b, sos + + @skip_xp_backends('jax.numpy', reason='item assignment') + @make_xp_test_case(sosfilt_zi) + def test_initial_conditions(self, dt, xp): + a, b, sos = self._get_ab_sos(xp) + + x = np.random.rand(50).astype(dt) + x = xp.asarray(x) + + dt = getattr(xp, dt) + + # Stopping filtering and continuing + y_true, zi = lfilter(b, a, x[:20], zi=xp.zeros(6)) + y_true = xp.concat((y_true, lfilter(b, a, x[20:], zi=zi)[0])) + xp_assert_close(y_true, lfilter(b, a, x)) + + y_sos, zi = sosfilt(sos, x[:20], zi=xp.zeros((3, 2))) + y_sos = xp.concat((y_sos, sosfilt(sos, x[20:], zi=zi)[0])) + xp_assert_close(y_true, y_sos) + + # Use a step function + zi = sosfilt_zi(sos) + x = xp.ones(8, dtype=dt) + y, zf = sosfilt(sos, x, zi=zi) + + xp_assert_close(y, xp.ones(8), check_dtype=False) + xp_assert_close(zf, zi, check_dtype=False) + + @skip_xp_backends('jax.numpy', reason='item assignment') + @skip_xp_backends('array_api_strict', reason='fancy indexing not supported') + @make_xp_test_case(sosfilt_zi) + def test_initial_conditions_2(self, dt, xp): + dt = getattr(xp, dt) + x = xp.ones(8, dtype=dt) + + _, _, sos = self._get_ab_sos(xp) + zi = sosfilt_zi(sos) + + # Initial condition shape matching + x = xp.reshape(x, (1, 1) + x.shape) # 3D + with pytest.raises(ValueError): + sosfilt(sos, x, zi=zi) + + zi_nd = xp_copy(zi, xp=xp) + zi_nd = xp.reshape(zi_nd, (zi.shape[0], 1, 1, zi.shape[-1])) + + with pytest.raises(ValueError): + sosfilt(sos, x, zi=zi_nd[:, :, :, [0, 1, 1]]) + + y, zf = sosfilt(sos, x, zi=zi_nd) + xp_assert_close(y[0, 0], xp.ones(8), check_dtype=False) + xp_assert_close(zf[:, 0, 0, :], zi, check_dtype=False) + + @skip_xp_backends('jax.numpy', reason='item assignment') + @make_xp_test_case(sosfilt_zi) + def test_initial_conditions_3d_axis1(self, dt, xp): + # Test the use of zi when sosfilt is applied to axis 1 of a 3-d input. + + # Input array is x. + x = np.random.RandomState(159).randint(0, 5, size=(2, 15, 3)) + x = x.astype(dt) + x = xp.asarray(x) + + # Design a filter in ZPK format and convert to SOS + zpk = signal.butter(6, 0.35, output='zpk') + sos = zpk2sos(*zpk) + sos = xp.asarray(sos) # XXX while zpk2sos is numpy-only + + nsections = sos.shape[0] + + # Filter along this axis. + axis = 1 + + # Initial conditions, all zeros. + shp = list(x.shape) + shp[axis] = 2 + shp = tuple([nsections] + shp) + z0 = xp.zeros(shp) + + # Apply the filter to x. + yf, zf = sosfilt(sos, x, axis=axis, zi=z0) + + # Apply the filter to x in two stages. + y1, z1 = sosfilt(sos, x[:, :5, :], axis=axis, zi=z0) + y2, z2 = sosfilt(sos, x[:, 5:, :], axis=axis, zi=z1) + + # y should equal yf, and z2 should equal zf. + y = xp.concat((y1, y2), axis=axis) + xp_assert_close(y, yf, rtol=1e-10, atol=1e-13) + xp_assert_close(z2, zf, rtol=1e-10, atol=1e-13) + + # let's try the "step" initial condition + zi = sosfilt_zi(sos) + zi = xp.reshape(zi, (nsections, 1, 2, 1)) + zi = zi * x[:, 0:1, :] + y = sosfilt(sos, x, axis=axis, zi=zi)[0] + # check it against the TF form + b, a = zpk2tf(*zpk) + b, a = xp.asarray(b), xp.asarray(a) # XXX while zpk2tf is numpy-only + + zi = lfilter_zi(b, a) + zi = xp.reshape(zi, (1, xp_size(zi), 1)) + zi = zi * x[:, 0:1, :] + y_tf = lfilter(b, a, x, axis=axis, zi=zi)[0] + xp_assert_close(y, y_tf, rtol=1e-10, atol=1e-13) + + @skip_xp_backends('torch', reason='issues a RuntimeWarning') + @skip_xp_backends('jax.numpy', reason='item assignment') + def test_bad_zi_shape(self, dt, xp): + dt = getattr(xp, dt) + # The shape of zi is checked before using any values in the + # arguments, so np.empty is fine for creating the arguments. + x = xp.empty((3, 15, 3), dtype=dt) + sos = xp.zeros((4, 6)) + zi = xp.empty((4, 3, 3, 2)) # Correct shape is (4, 3, 2, 3) + with pytest.raises(ValueError, match='should be all ones'): + sosfilt(sos, x, zi=zi, axis=1) + sos[:, 3] = 1. + with pytest.raises(ValueError, match='Invalid zi shape'): + sosfilt(sos, x, zi=zi, axis=1) + + @skip_xp_backends('jax.numpy', reason='item assignment') + @make_xp_test_case(sosfilt_zi) + def test_sosfilt_zi(self, dt, xp): + dt = getattr(xp, dt) + sos = signal.butter(6, 0.2, output='sos') + sos = xp.asarray(sos) # XXX while butter is np-only + zi = sosfilt_zi(sos) + + y, zf = sosfilt(sos, xp.ones(40, dtype=dt), zi=zi) + xp_assert_close(zf, zi, rtol=1e-13, check_dtype=False) + + # Expected steady state value of the step response of this filter: + ss = xp.prod(xp.sum(sos[:, :3], axis=-1) / xp.sum(sos[:, 3:], axis=-1)) + xp_assert_close(y, ss * xp.ones_like(y), rtol=1e-13) + + @skip_xp_backends(np_only=True) + @make_xp_test_case(sosfilt_zi) + def test_sosfilt_zi_2(self, dt, xp): + # zi as array-like + dt = getattr(xp, dt) + sos = signal.butter(6, 0.2, output='sos') + sos = xp.asarray(sos) # XXX while butter is np-only + zi = sosfilt_zi(sos) + + _, zf = sosfilt(sos, xp.ones(40, dtype=dt), zi=zi.tolist()) + xp_assert_close(zf, zi, rtol=1e-13, check_dtype=False) + + +@make_xp_test_case(signal.deconvolve) +class TestDeconvolve: + + @skip_xp_backends(np_only=True, reason="list inputs are numpy-specific") + def test_array_like(self, xp): + # From docstring example: with lists + original = [0.0, 1, 0, 0, 1, 1, 0, 0] + impulse_response = [2, 1] + recorded = xp.asarray([0.0, 2, 1, 0, 2, 3, 1, 0, 0]) + recovered, remainder = signal.deconvolve(recorded, impulse_response) + xp_assert_close(recovered, original) + + def test_basic(self, xp): + # From docstring example + original = xp.asarray([0.0, 1, 0, 0, 1, 1, 0, 0], dtype=xp.float64) + impulse_response = xp.asarray([2, 1]) + recorded = xp.asarray([0.0, 2, 1, 0, 2, 3, 1, 0, 0]) + recovered, remainder = signal.deconvolve(recorded, impulse_response) + xp_assert_close(recovered, original) + + @xfail_xp_backends("cupy", reason="different error message") + def test_n_dimensional_signal(self, xp): + recorded = xp.asarray([[0, 0], [0, 0]]) + impulse_response = xp.asarray([0, 0]) + with pytest.raises(ValueError, match="^Parameter signal must be non-empty"): + quotient, remainder = signal.deconvolve(recorded, impulse_response) + + @xfail_xp_backends("cupy", reason="different error message") + def test_n_dimensional_divisor(self, xp): + recorded = xp.asarray([0, 0]) + impulse_response = xp.asarray([[0, 0], [0, 0]]) + with pytest.raises(ValueError, match="^Parameter divisor must be non-empty"): + quotient, remainder = signal.deconvolve(recorded, impulse_response) + + def test_divisor_greater_signal(self, xp): + """Return signal as `remainder` when ``len(divisior) > len(signal)``. """ + sig, div = xp.asarray([0, 1, 2]), xp.asarray([0, 1, 2, 4, 5]) + quotient, remainder = signal.deconvolve(sig, div) + xp_assert_equal(remainder, sig) + assert xp_size(xp.asarray(quotient)) == 0 + + +@make_xp_test_case(detrend) +class TestDetrend: + + def test_basic(self, xp): + detrended = detrend(xp.asarray([1, 2, 3])) + detrended_exact = xp.asarray([0, 0, 0]) + assert_array_almost_equal(detrended, detrended_exact) + + @skip_xp_backends("jax.numpy", reason="overwrite_data not implemented") + def test_copy(self, xp): + x = xp.asarray([1, 1.2, 1.5, 1.6, 2.4]) + copy_array = detrend(x, overwrite_data=False) + inplace = detrend(x, overwrite_data=True) + assert_array_almost_equal(copy_array, inplace) + + @pytest.mark.parametrize('kind', ['linear', 'constant']) + @pytest.mark.parametrize('axis', [0, 1, 2]) + def test_axis(self, axis, kind, xp): + data = xp.reshape(xp.arange(5*6*7), (5, 6, 7)) + detrended = detrend(data, type=kind, axis=axis) + assert detrended.shape == data.shape + + def test_bp(self, xp): + data = [0, 1, 2] + [5, 0, -5, -10] + data = xp.asarray(data) + detrended = detrend(data, type='linear', bp=3) + xp_assert_close(detrended, xp.zeros_like(detrended), atol=1e-14) + + # repeat with ndim > 1 and axis + data = xp.asarray(data)[None, :, None] + + detrended = detrend(data, type="linear", bp=3, axis=1) + xp_assert_close(detrended, xp.zeros_like(detrended), atol=1e-14) + + # breakpoint index > shape[axis]: raises + with assert_raises(ValueError): + detrend(data, type="linear", bp=3) + + @pytest.mark.parametrize('bp', [np.array([0, 2]), [0, 2]]) + def test_detrend_array_bp(self, bp, xp): + # regression test for https://github.com/scipy/scipy/issues/18675 + rng = np.random.RandomState(12345) + x = rng.rand(10) + x = xp.asarray(x, dtype=xp_default_dtype(xp)) + if isinstance(bp, np.ndarray) and not is_jax(xp): + # JAX expects a static array for bp, so don't call xp.asarray + # for JAX. + bp = xp.asarray(bp) + else: + if not (is_numpy(xp) or is_jax(xp)): + pytest.skip("list bp is currently numpy and jax only") + + res = detrend(x, bp=bp) + res_scipy_191 = xp.asarray([-4.44089210e-16, -2.22044605e-16, + -1.11128506e-01, -1.69470553e-01, 1.14710683e-01, 6.35468419e-02, + 3.53533144e-01, -3.67877935e-02, -2.00417675e-02, -1.94362049e-01]) + + atol = 3e-7 if xp_default_dtype(xp) == xp.float32 else 1e-14 + xp_assert_close(res, res_scipy_191, atol=atol) + + +@make_xp_test_case(unique_roots) +class TestUniqueRoots: + def test_real_no_repeat(self, xp): + p = xp.asarray([-1.0, -0.5, 0.3, 1.2, 10.0]) + unique, multiplicity = unique_roots(p) + assert_almost_equal(unique, p, decimal=15) + xp_assert_equal(multiplicity, xp.ones(len(p), dtype=int)) + + def test_real_repeat(self, xp): + p = xp.asarray([-1.0, -0.95, -0.89, -0.8, 0.5, 1.0, 1.05]) + + unique, multiplicity = unique_roots(p, tol=1e-1, rtype='min') + assert_almost_equal(unique, xp.asarray([-1.0, -0.89, 0.5, 1.0]), decimal=15) + xp_assert_equal(multiplicity, xp.asarray([2, 2, 1, 2])) + + unique, multiplicity = unique_roots(p, tol=1e-1, rtype='max') + assert_almost_equal(unique, xp.asarray([-0.95, -0.8, 0.5, 1.05]), decimal=15) + xp_assert_equal(multiplicity, xp.asarray([2, 2, 1, 2])) + + unique, multiplicity = unique_roots(p, tol=1e-1, rtype='avg') + assert_almost_equal(unique, xp.asarray([-0.975, -0.845, 0.5, 1.025]), + decimal=15) + xp_assert_equal(multiplicity, xp.asarray([2, 2, 1, 2])) + + def test_complex_no_repeat(self, xp): + p = xp.asarray([-1.0, 1.0j, 0.5 + 0.5j, -1.0 - 1.0j, 3.0 + 2.0j]) + unique, multiplicity = unique_roots(p) + assert_almost_equal(unique, p, decimal=15) + xp_assert_equal(multiplicity, xp.ones(len(p), dtype=int)) + + def test_complex_repeat(self, xp): + p = xp.asarray([-1.0, -1.0 + 0.05j, -0.95 + 0.15j, -0.90 + 0.15j, 0.0, + 0.5 + 0.5j, 0.45 + 0.55j]) + + unique, multiplicity = unique_roots(p, tol=1e-1, rtype='min') + assert_almost_equal(unique, + xp.asarray([-1.0, -0.95 + 0.15j, 0.0, 0.45 + 0.55j]), + decimal=15) + xp_assert_equal(multiplicity, xp.asarray([2, 2, 1, 2])) + + unique, multiplicity = unique_roots(p, tol=1e-1, rtype='max') + assert_almost_equal( + unique, + xp.asarray( + [-1.0 + 0.05j, -0.90 + 0.15j, 0.0, 0.5 + 0.5j] + ), + decimal=15, + ) + xp_assert_equal(multiplicity, xp.asarray([2, 2, 1, 2])) + + unique, multiplicity = unique_roots(p, tol=1e-1, rtype='avg') + assert_almost_equal( + unique, + xp.asarray([-1.0 + 0.025j, -0.925 + 0.15j, 0.0, 0.475 + 0.525j]), + decimal=15, + ) + xp_assert_equal(multiplicity, xp.asarray([2, 2, 1, 2])) + + def test_gh_4915(self, xp): + p = xp.asarray(np.roots(np.convolve(np.ones(5), np.ones(5)))) + true_roots = xp.asarray( + [-(-1)**(1/5), (-1)**(4/5), -(-1)**(3/5), (-1)**(2/5)] + ) + + unique, multiplicity = unique_roots(p) + unique = xp.sort(unique) + + assert_almost_equal(xp.sort(unique), true_roots, decimal=7) + xp_assert_equal(multiplicity, xp.asarray([2, 2, 2, 2])) + + def test_complex_roots_extra(self, xp): + unique, multiplicity = unique_roots(xp.asarray([1.0, 1.0j, 1.0])) + assert_almost_equal(unique, xp.asarray([1.0, 1.0j]), decimal=15) + xp_assert_equal(multiplicity, xp.asarray([2, 1])) + + unique, multiplicity = unique_roots( + xp.asarray([1, 1 + 2e-9, 1e-9 + 1j]), tol=0.1 + ) + assert_almost_equal(unique, xp.asarray([1.0, 1e-9 + 1.0j]), decimal=15) + xp_assert_equal(multiplicity, xp.asarray([2, 1])) + + def test_single_unique_root(self, xp): + p = xp.asarray(np.random.rand(100) + 1j * np.random.rand(100)) + unique, multiplicity = unique_roots(p, 2) + assert_almost_equal(unique, xp.asarray([np.min(p)]), decimal=15) + xp_assert_equal(multiplicity, xp.asarray([100])) + + +def test_gh_22684(): + actual = signal.resample_poly(np.arange(2000, dtype=np.complex64), 6, 4) + assert actual.dtype == np.complex64 diff --git a/venv/lib/python3.12/site-packages/scipy/signal/tests/test_spectral.py b/venv/lib/python3.12/site-packages/scipy/signal/tests/test_spectral.py new file mode 100644 index 0000000000000000000000000000000000000000..3e1c2284a98ddcf7909a2f8398d7d014cdb7c6b6 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/tests/test_spectral.py @@ -0,0 +1,2188 @@ +import sys +import warnings + +import numpy as np +from numpy.testing import (assert_, + assert_allclose, assert_array_equal, assert_equal, + assert_array_almost_equal_nulp) +import pytest +from pytest import raises as assert_raises + +from scipy import signal +from scipy._lib._array_api import xp_assert_close +from scipy.fft import fftfreq, rfftfreq, fft, irfft +from scipy.integrate import trapezoid +from scipy.signal import (periodogram, welch, lombscargle, coherence, csd, + spectrogram, check_COLA, check_NOLA) +from scipy.signal.windows import hann +from scipy.signal._spectral_py import _spectral_helper + +# Compare ShortTimeFFT.stft() / ShortTimeFFT.istft() with stft() / istft(): +from scipy.signal.tests._scipy_spectral_test_shim import stft_compare as stft +from scipy.signal.tests._scipy_spectral_test_shim import istft_compare as istft + + +class TestPeriodogram: + def test_real_onesided_even(self): + x = np.zeros(16) + x[0] = 1 + f, p = periodogram(x) + assert_allclose(f, np.linspace(0, 0.5, 9)) + q = np.ones(9) + q[0] = 0 + q[-1] /= 2.0 + q /= 8 + assert_allclose(p, q) + + def test_real_onesided_odd(self): + x = np.zeros(15) + x[0] = 1 + f, p = periodogram(x) + assert_allclose(f, np.arange(8.0)/15.0) + q = np.ones(8) + q[0] = 0 + q *= 2.0/15.0 + assert_allclose(p, q, atol=1e-15) + + def test_real_twosided(self): + x = np.zeros(16) + x[0] = 1 + f, p = periodogram(x, return_onesided=False) + assert_allclose(f, fftfreq(16, 1.0)) + q = np.full(16, 1/16.0) + q[0] = 0 + assert_allclose(p, q) + + def test_real_spectrum(self): + x = np.zeros(16) + x[0] = 1 + f, p = periodogram(x, scaling='spectrum') + g, q = periodogram(x, scaling='density') + assert_allclose(f, np.linspace(0, 0.5, 9)) + assert_allclose(p, q/16.0) + + def test_integer_even(self): + x = np.zeros(16, dtype=int) + x[0] = 1 + f, p = periodogram(x) + assert_allclose(f, np.linspace(0, 0.5, 9)) + q = np.ones(9) + q[0] = 0 + q[-1] /= 2.0 + q /= 8 + assert_allclose(p, q) + + def test_integer_odd(self): + x = np.zeros(15, dtype=int) + x[0] = 1 + f, p = periodogram(x) + assert_allclose(f, np.arange(8.0)/15.0) + q = np.ones(8) + q[0] = 0 + q *= 2.0/15.0 + assert_allclose(p, q, atol=1e-15) + + def test_integer_twosided(self): + x = np.zeros(16, dtype=int) + x[0] = 1 + f, p = periodogram(x, return_onesided=False) + assert_allclose(f, fftfreq(16, 1.0)) + q = np.full(16, 1/16.0) + q[0] = 0 + assert_allclose(p, q) + + def test_complex(self): + x = np.zeros(16, np.complex128) + x[0] = 1.0 + 2.0j + f, p = periodogram(x, return_onesided=False) + assert_allclose(f, fftfreq(16, 1.0)) + q = np.full(16, 5.0/16.0) + q[0] = 0 + assert_allclose(p, q) + + def test_unk_scaling(self): + assert_raises(ValueError, periodogram, np.zeros(4, np.complex128), + scaling='foo') + + @pytest.mark.skipif( + sys.maxsize <= 2**32, + reason="On some 32-bit tolerance issue" + ) + def test_nd_axis_m1(self): + x = np.zeros(20, dtype=np.float64) + x = x.reshape((2,1,10)) + x[:,:,0] = 1.0 + f, p = periodogram(x) + assert_array_equal(p.shape, (2, 1, 6)) + assert_array_almost_equal_nulp(p[0,0,:], p[1,0,:], 60) + f0, p0 = periodogram(x[0,0,:]) + assert_array_almost_equal_nulp(p0[np.newaxis,:], p[1,:], 60) + + @pytest.mark.skipif( + sys.maxsize <= 2**32, + reason="On some 32-bit tolerance issue" + ) + def test_nd_axis_0(self): + x = np.zeros(20, dtype=np.float64) + x = x.reshape((10,2,1)) + x[0,:,:] = 1.0 + f, p = periodogram(x, axis=0) + assert_array_equal(p.shape, (6,2,1)) + assert_array_almost_equal_nulp(p[:,0,0], p[:,1,0], 60) + f0, p0 = periodogram(x[:,0,0]) + assert_array_almost_equal_nulp(p0, p[:,1,0]) + + def test_window_external(self): + x = np.zeros(16) + x[0] = 1 + f, p = periodogram(x, 10, 'hann') + win = signal.get_window('hann', 16) + fe, pe = periodogram(x, 10, win) + assert_array_almost_equal_nulp(p, pe) + assert_array_almost_equal_nulp(f, fe) + win_err = signal.get_window('hann', 32) + assert_raises(ValueError, periodogram, x, + 10, win_err) # win longer than signal + + def test_padded_fft(self): + x = np.zeros(16) + x[0] = 1 + f, p = periodogram(x) + fp, pp = periodogram(x, nfft=32) + assert_allclose(f, fp[::2]) + assert_allclose(p, pp[::2]) + assert_array_equal(pp.shape, (17,)) + + def test_empty_input(self): + f, p = periodogram([]) + assert_array_equal(f.shape, (0,)) + assert_array_equal(p.shape, (0,)) + for shape in [(0,), (3,0), (0,5,2)]: + f, p = periodogram(np.empty(shape)) + assert_array_equal(f.shape, shape) + assert_array_equal(p.shape, shape) + + def test_empty_input_other_axis(self): + for shape in [(3,0), (0,5,2)]: + f, p = periodogram(np.empty(shape), axis=1) + assert_array_equal(f.shape, shape) + assert_array_equal(p.shape, shape) + + def test_short_nfft(self): + x = np.zeros(18) + x[0] = 1 + f, p = periodogram(x, nfft=16) + assert_allclose(f, np.linspace(0, 0.5, 9)) + q = np.ones(9) + q[0] = 0 + q[-1] /= 2.0 + q /= 8 + assert_allclose(p, q) + + def test_nfft_is_xshape(self): + x = np.zeros(16) + x[0] = 1 + f, p = periodogram(x, nfft=16) + assert_allclose(f, np.linspace(0, 0.5, 9)) + q = np.ones(9) + q[0] = 0 + q[-1] /= 2.0 + q /= 8 + assert_allclose(p, q) + + def test_real_onesided_even_32(self): + x = np.zeros(16, 'f') + x[0] = 1 + f, p = periodogram(x) + assert_allclose(f, np.linspace(0, 0.5, 9)) + q = np.ones(9, 'f') + q[0] = 0 + q[-1] /= 2.0 + q /= 8 + assert_allclose(p, q) + assert_(p.dtype == q.dtype) + + def test_real_onesided_odd_32(self): + x = np.zeros(15, 'f') + x[0] = 1 + f, p = periodogram(x) + assert_allclose(f, np.arange(8.0)/15.0) + q = np.ones(8, 'f') + q[0] = 0 + q *= 2.0/15.0 + assert_allclose(p, q, atol=1e-7) + assert_(p.dtype == q.dtype) + + def test_real_twosided_32(self): + x = np.zeros(16, 'f') + x[0] = 1 + f, p = periodogram(x, return_onesided=False) + assert_allclose(f, fftfreq(16, 1.0)) + q = np.full(16, 1/16.0, 'f') + q[0] = 0 + assert_allclose(p, q) + assert_(p.dtype == q.dtype) + + def test_complex_32(self): + x = np.zeros(16, 'F') + x[0] = 1.0 + 2.0j + f, p = periodogram(x, return_onesided=False) + assert_allclose(f, fftfreq(16, 1.0)) + q = np.full(16, 5.0/16.0, 'f') + q[0] = 0 + assert_allclose(p, q) + assert_(p.dtype == q.dtype) + + def test_shorter_window_error(self): + x = np.zeros(16) + x[0] = 1 + win = signal.get_window('hann', 10) + expected_msg = ('the size of the window must be the same size ' + 'of the input on the specified axis') + with assert_raises(ValueError, match=expected_msg): + periodogram(x, window=win) + + +class TestWelch: + def test_real_onesided_even(self): + x = np.zeros(16) + x[0] = 1 + x[8] = 1 + f, p = welch(x, nperseg=8) + assert_allclose(f, np.linspace(0, 0.5, 5)) + q = np.array([0.08333333, 0.15277778, 0.22222222, 0.22222222, + 0.11111111]) + assert_allclose(p, q, atol=1e-7, rtol=1e-7) + + def test_real_onesided_odd(self): + x = np.zeros(16) + x[0] = 1 + x[8] = 1 + f, p = welch(x, nperseg=9) + assert_allclose(f, np.arange(5.0)/9.0) + q = np.array([0.12477455, 0.23430933, 0.17072113, 0.17072113, + 0.17072113]) + assert_allclose(p, q, atol=1e-7, rtol=1e-7) + + def test_real_twosided(self): + x = np.zeros(16) + x[0] = 1 + x[8] = 1 + f, p = welch(x, nperseg=8, return_onesided=False) + assert_allclose(f, fftfreq(8, 1.0)) + q = np.array([0.08333333, 0.07638889, 0.11111111, 0.11111111, + 0.11111111, 0.11111111, 0.11111111, 0.07638889]) + assert_allclose(p, q, atol=1e-7, rtol=1e-7) + + def test_real_spectrum(self): + x = np.zeros(16) + x[0] = 1 + x[8] = 1 + f, p = welch(x, nperseg=8, scaling='spectrum') + assert_allclose(f, np.linspace(0, 0.5, 5)) + q = np.array([0.015625, 0.02864583, 0.04166667, 0.04166667, + 0.02083333]) + assert_allclose(p, q, atol=1e-7, rtol=1e-7) + + def test_integer_onesided_even(self): + x = np.zeros(16, dtype=int) + x[0] = 1 + x[8] = 1 + f, p = welch(x, nperseg=8) + assert_allclose(f, np.linspace(0, 0.5, 5)) + q = np.array([0.08333333, 0.15277778, 0.22222222, 0.22222222, + 0.11111111]) + assert_allclose(p, q, atol=1e-7, rtol=1e-7) + + def test_integer_onesided_odd(self): + x = np.zeros(16, dtype=int) + x[0] = 1 + x[8] = 1 + f, p = welch(x, nperseg=9) + assert_allclose(f, np.arange(5.0)/9.0) + q = np.array([0.12477455, 0.23430933, 0.17072113, 0.17072113, + 0.17072113]) + assert_allclose(p, q, atol=1e-7, rtol=1e-7) + + def test_integer_twosided(self): + x = np.zeros(16, dtype=int) + x[0] = 1 + x[8] = 1 + f, p = welch(x, nperseg=8, return_onesided=False) + assert_allclose(f, fftfreq(8, 1.0)) + q = np.array([0.08333333, 0.07638889, 0.11111111, 0.11111111, + 0.11111111, 0.11111111, 0.11111111, 0.07638889]) + assert_allclose(p, q, atol=1e-7, rtol=1e-7) + + def test_complex(self): + x = np.zeros(16, np.complex128) + x[0] = 1.0 + 2.0j + x[8] = 1.0 + 2.0j + f, p = welch(x, nperseg=8, return_onesided=False) + assert_allclose(f, fftfreq(8, 1.0)) + q = np.array([0.41666667, 0.38194444, 0.55555556, 0.55555556, + 0.55555556, 0.55555556, 0.55555556, 0.38194444]) + assert_allclose(p, q, atol=1e-7, rtol=1e-7) + + def test_unk_scaling(self): + assert_raises(ValueError, welch, np.zeros(4, np.complex128), + scaling='foo', nperseg=4) + + def test_detrend_linear(self): + x = np.arange(10, dtype=np.float64) + 0.04 + f, p = welch(x, nperseg=10, detrend='linear') + assert_allclose(p, np.zeros_like(p), atol=1e-15) + + def test_no_detrending(self): + x = np.arange(10, dtype=np.float64) + 0.04 + f1, p1 = welch(x, nperseg=10, detrend=False) + f2, p2 = welch(x, nperseg=10, detrend=lambda x: x) + assert_allclose(f1, f2, atol=1e-15) + assert_allclose(p1, p2, atol=1e-15) + + def test_detrend_external(self): + x = np.arange(10, dtype=np.float64) + 0.04 + f, p = welch(x, nperseg=10, + detrend=lambda seg: signal.detrend(seg, type='l')) + assert_allclose(p, np.zeros_like(p), atol=1e-15) + + def test_detrend_external_nd_m1(self): + x = np.arange(40, dtype=np.float64) + 0.04 + x = x.reshape((2,2,10)) + f, p = welch(x, nperseg=10, + detrend=lambda seg: signal.detrend(seg, type='l')) + assert_allclose(p, np.zeros_like(p), atol=1e-15) + + def test_detrend_external_nd_0(self): + x = np.arange(20, dtype=np.float64) + 0.04 + x = x.reshape((2,1,10)) + x = np.moveaxis(x, 2, 0) + f, p = welch(x, nperseg=10, axis=0, + detrend=lambda seg: signal.detrend(seg, axis=0, type='l')) + assert_allclose(p, np.zeros_like(p), atol=1e-15) + + def test_nd_axis_m1(self): + x = np.arange(20, dtype=np.float64) + 0.04 + x = x.reshape((2,1,10)) + f, p = welch(x, nperseg=10) + assert_array_equal(p.shape, (2, 1, 6)) + assert_allclose(p[0,0,:], p[1,0,:], atol=1e-13, rtol=1e-13) + f0, p0 = welch(x[0,0,:], nperseg=10) + assert_allclose(p0[np.newaxis,:], p[1,:], atol=1e-13, rtol=1e-13) + + def test_nd_axis_0(self): + x = np.arange(20, dtype=np.float64) + 0.04 + x = x.reshape((10,2,1)) + f, p = welch(x, nperseg=10, axis=0) + assert_array_equal(p.shape, (6,2,1)) + assert_allclose(p[:,0,0], p[:,1,0], atol=1e-13, rtol=1e-13) + f0, p0 = welch(x[:,0,0], nperseg=10) + assert_allclose(p0, p[:,1,0], atol=1e-13, rtol=1e-13) + + def test_window_external(self): + x = np.zeros(16) + x[0] = 1 + x[8] = 1 + f, p = welch(x, 10, 'hann', nperseg=8) + win = signal.get_window('hann', 8) + fe, pe = welch(x, 10, win, nperseg=None) + assert_array_almost_equal_nulp(p, pe) + assert_array_almost_equal_nulp(f, fe) + assert_array_equal(fe.shape, (5,)) # because win length used as nperseg + assert_array_equal(pe.shape, (5,)) + assert_raises(ValueError, welch, x, + 10, win, nperseg=4) # because nperseg != win.shape[-1] + win_err = signal.get_window('hann', 32) + assert_raises(ValueError, welch, x, + 10, win_err, nperseg=None) # win longer than signal + + def test_empty_input(self): + f, p = welch([]) + assert_array_equal(f.shape, (0,)) + assert_array_equal(p.shape, (0,)) + for shape in [(0,), (3,0), (0,5,2)]: + f, p = welch(np.empty(shape)) + assert_array_equal(f.shape, shape) + assert_array_equal(p.shape, shape) + + def test_empty_input_other_axis(self): + for shape in [(3,0), (0,5,2)]: + f, p = welch(np.empty(shape), axis=1) + assert_array_equal(f.shape, shape) + assert_array_equal(p.shape, shape) + + def test_short_data(self): + x = np.zeros(8) + x[0] = 1 + #for string-like window, input signal length < nperseg value gives + #UserWarning, sets nperseg to x.shape[-1] + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", "nperseg=256 is greater than signal.*", UserWarning) + f, p = welch(x,window='hann') # default nperseg + f1, p1 = welch(x,window='hann', nperseg=256) # user-specified nperseg + f2, p2 = welch(x, nperseg=8) # valid nperseg, doesn't give warning + assert_allclose(f, f2) + assert_allclose(p, p2) + assert_allclose(f1, f2) + assert_allclose(p1, p2) + + def test_window_long_or_nd(self): + assert_raises(ValueError, welch, np.zeros(4), 1, np.array([1,1,1,1,1])) + assert_raises(ValueError, welch, np.zeros(4), 1, + np.arange(6).reshape((2,3))) + + def test_nondefault_noverlap(self): + x = np.zeros(64) + x[::8] = 1 + f, p = welch(x, nperseg=16, noverlap=4) + q = np.array([0, 1./12., 1./3., 1./5., 1./3., 1./5., 1./3., 1./5., + 1./6.]) + assert_allclose(p, q, atol=1e-12) + + def test_bad_noverlap(self): + assert_raises(ValueError, welch, np.zeros(4), 1, 'hann', 2, 7) + + def test_nfft_too_short(self): + assert_raises(ValueError, welch, np.ones(12), nfft=3, nperseg=4) + + def test_real_onesided_even_32(self): + x = np.zeros(16, 'f') + x[0] = 1 + x[8] = 1 + f, p = welch(x, nperseg=8) + assert_allclose(f, np.linspace(0, 0.5, 5)) + q = np.array([0.08333333, 0.15277778, 0.22222222, 0.22222222, + 0.11111111], 'f') + assert_allclose(p, q, atol=1e-7, rtol=1e-7) + assert_(p.dtype == q.dtype) + + def test_real_onesided_odd_32(self): + x = np.zeros(16, 'f') + x[0] = 1 + x[8] = 1 + f, p = welch(x, nperseg=9) + assert_allclose(f, np.arange(5.0)/9.0) + q = np.array([0.12477458, 0.23430935, 0.17072113, 0.17072116, + 0.17072113], 'f') + assert_allclose(p, q, atol=1e-7, rtol=1e-7) + assert_(p.dtype == q.dtype) + + def test_real_twosided_32(self): + x = np.zeros(16, 'f') + x[0] = 1 + x[8] = 1 + f, p = welch(x, nperseg=8, return_onesided=False) + assert_allclose(f, fftfreq(8, 1.0)) + q = np.array([0.08333333, 0.07638889, 0.11111111, + 0.11111111, 0.11111111, 0.11111111, 0.11111111, + 0.07638889], 'f') + assert_allclose(p, q, atol=1e-7, rtol=1e-7) + assert_(p.dtype == q.dtype) + + def test_complex_32(self): + x = np.zeros(16, 'F') + x[0] = 1.0 + 2.0j + x[8] = 1.0 + 2.0j + f, p = welch(x, nperseg=8, return_onesided=False) + assert_allclose(f, fftfreq(8, 1.0)) + q = np.array([0.41666666, 0.38194442, 0.55555552, 0.55555552, + 0.55555558, 0.55555552, 0.55555552, 0.38194442], 'f') + assert_allclose(p, q, atol=1e-7, rtol=1e-7) + assert_(p.dtype == q.dtype, + f'dtype mismatch, {p.dtype}, {q.dtype}') + + def test_padded_freqs(self): + x = np.zeros(12) + + nfft = 24 + f = fftfreq(nfft, 1.0)[:nfft//2+1] + f[-1] *= -1 + fodd, _ = welch(x, nperseg=5, nfft=nfft) + feven, _ = welch(x, nperseg=6, nfft=nfft) + assert_allclose(f, fodd) + assert_allclose(f, feven) + + nfft = 25 + f = fftfreq(nfft, 1.0)[:(nfft + 1)//2] + fodd, _ = welch(x, nperseg=5, nfft=nfft) + feven, _ = welch(x, nperseg=6, nfft=nfft) + assert_allclose(f, fodd) + assert_allclose(f, feven) + + def test_window_correction(self): + A = 20 + fs = 1e4 + nperseg = int(fs//10) + fsig = 300 + ii = int(fsig*nperseg//fs) # Freq index of fsig + + tt = np.arange(fs)/fs + x = A*np.sin(2*np.pi*fsig*tt) + + for window in ['hann', 'bartlett', ('tukey', 0.1), 'flattop']: + _, p_spec = welch(x, fs=fs, nperseg=nperseg, window=window, + scaling='spectrum') + freq, p_dens = welch(x, fs=fs, nperseg=nperseg, window=window, + scaling='density') + + # Check peak height at signal frequency for 'spectrum' + assert_allclose(p_spec[ii], A**2/2.0) + # Check integrated spectrum RMS for 'density' + assert_allclose(np.sqrt(trapezoid(p_dens, freq)), A*np.sqrt(2)/2, + rtol=1e-3) + + def test_axis_rolling(self): + np.random.seed(1234) + + x_flat = np.random.randn(1024) + _, p_flat = welch(x_flat) + + for a in range(3): + newshape = [1,]*3 + newshape[a] = -1 + x = x_flat.reshape(newshape) + + _, p_plus = welch(x, axis=a) # Positive axis index + _, p_minus = welch(x, axis=a-x.ndim) # Negative axis index + + assert_equal(p_flat, p_plus.squeeze(), err_msg=a) + assert_equal(p_flat, p_minus.squeeze(), err_msg=a-x.ndim) + + def test_average(self): + x = np.zeros(16) + x[0] = 1 + x[8] = 1 + f, p = welch(x, nperseg=8, average='median') + assert_allclose(f, np.linspace(0, 0.5, 5)) + q = np.array([.1, .05, 0., 1.54074396e-33, 0.]) + assert_allclose(p, q, atol=1e-7, rtol=1e-7) + + assert_raises(ValueError, welch, x, nperseg=8, + average='unrecognised-average') + + def test_ratio_scale_to(self): + """Verify the factor of ``sum(abs(window)**2)*fs / abs(sum(window))**2`` + used in the `welch` and `csd` docstrs. """ + x, win, fs = np.array([1., 0, 0, 0]), np.ones(4), 12 + params = dict(fs=fs, window=win, return_onesided=False, detrend=None) + p_dens = welch(x, scaling='density', **params)[1] + p_spec = welch(x, scaling='spectrum', **params)[1] + p_fac = sum(win**2)*fs / abs(sum(win))**2 + assert_allclose(p_spec / p_dens, p_fac) + +class TestCSD: + def test_pad_shorter_x(self): + x = np.zeros(8) + y = np.zeros(12) + + f = np.linspace(0, 0.5, 7) + c = np.zeros(7,dtype=np.complex128) + f1, c1 = csd(x, y, nperseg=12) + + assert_allclose(f, f1) + assert_allclose(c, c1) + + def test_pad_shorter_y(self): + x = np.zeros(12) + y = np.zeros(8) + + f = np.linspace(0, 0.5, 7) + c = np.zeros(7,dtype=np.complex128) + f1, c1 = csd(x, y, nperseg=12) + + assert_allclose(f, f1) + assert_allclose(c, c1) + + def test_unequal_length_input_1D(self): + """Test zero-padding for input `x.shape[axis] != y.shape[axis]` for 1d arrays. + + This test ensures that issue 23036 is fixed. + """ + x = np.tile([4, 0, -4, 0], 4) + + kw = dict(fs=len(x), window='boxcar', nperseg=4) + X0 = signal.csd(x, np.copy(x), **kw)[1] # `x is x` must be False + X1 = signal.csd(x, x[:8], **kw)[1] + X2 = signal.csd(x[:8], x, **kw)[1] + xp_assert_close(X1, X0 / 2) + xp_assert_close(X2, X0 / 2) + + def test_unequal_length_input_3D(self): + """Test zero-padding for input `x.shape[axis] != y.shape[axis]` for 3d arrays. + + This test ensures that issue 23036 is fixed. + """ + n = 8 + x = np.zeros(2 * 3 * n).reshape(2, n, 3) + x[:, 0, :] = n + + kw = dict(fs=n, window='boxcar', nperseg=n, detrend=None, axis=1) + X0 = signal.csd(x, x.copy(), **kw)[1] # `x is x` must be False + X1 = signal.csd(x, x[:, :2, :], **kw)[1] + X2 = signal.csd(x[:, :2, :], x, **kw)[1] + xp_assert_close(X1, X0) + xp_assert_close(X2, X0) + + def test_real_onesided_even(self): + x = np.zeros(16) + x[0] = 1 + x[8] = 1 + f, p = csd(x, x, nperseg=8) + assert_allclose(f, np.linspace(0, 0.5, 5)) + q = np.array([0.08333333, 0.15277778, 0.22222222, 0.22222222, + 0.11111111]) + assert_allclose(p, q, atol=1e-7, rtol=1e-7) + + def test_real_onesided_odd(self): + x = np.zeros(16) + x[0] = 1 + x[8] = 1 + f, p = csd(x, x, nperseg=9) + assert_allclose(f, np.arange(5.0)/9.0) + q = np.array([0.12477455, 0.23430933, 0.17072113, 0.17072113, + 0.17072113]) + assert_allclose(p, q, atol=1e-7, rtol=1e-7) + + def test_real_twosided(self): + x = np.zeros(16) + x[0] = 1 + x[8] = 1 + f, p = csd(x, x, nperseg=8, return_onesided=False) + assert_allclose(f, fftfreq(8, 1.0)) + q = np.array([0.08333333, 0.07638889, 0.11111111, 0.11111111, + 0.11111111, 0.11111111, 0.11111111, 0.07638889]) + assert_allclose(p, q, atol=1e-7, rtol=1e-7) + + def test_real_spectrum(self): + x = np.zeros(16) + x[0] = 1 + x[8] = 1 + f, p = csd(x, x, nperseg=8, scaling='spectrum') + assert_allclose(f, np.linspace(0, 0.5, 5)) + q = np.array([0.015625, 0.02864583, 0.04166667, 0.04166667, + 0.02083333]) + assert_allclose(p, q, atol=1e-7, rtol=1e-7) + + def test_integer_onesided_even(self): + x = np.zeros(16, dtype=int) + x[0] = 1 + x[8] = 1 + f, p = csd(x, x, nperseg=8) + assert_allclose(f, np.linspace(0, 0.5, 5)) + q = np.array([0.08333333, 0.15277778, 0.22222222, 0.22222222, + 0.11111111]) + assert_allclose(p, q, atol=1e-7, rtol=1e-7) + + def test_integer_onesided_odd(self): + x = np.zeros(16, dtype=int) + x[0] = 1 + x[8] = 1 + f, p = csd(x, x, nperseg=9) + assert_allclose(f, np.arange(5.0)/9.0) + q = np.array([0.12477455, 0.23430933, 0.17072113, 0.17072113, + 0.17072113]) + assert_allclose(p, q, atol=1e-7, rtol=1e-7) + + def test_integer_twosided(self): + x = np.zeros(16, dtype=int) + x[0] = 1 + x[8] = 1 + f, p = csd(x, x, nperseg=8, return_onesided=False) + assert_allclose(f, fftfreq(8, 1.0)) + q = np.array([0.08333333, 0.07638889, 0.11111111, 0.11111111, + 0.11111111, 0.11111111, 0.11111111, 0.07638889]) + assert_allclose(p, q, atol=1e-7, rtol=1e-7) + + def test_complex(self): + x = np.zeros(16, np.complex128) + x[0] = 1.0 + 2.0j + x[8] = 1.0 + 2.0j + f, p = csd(x, x, nperseg=8, return_onesided=False) + assert_allclose(f, fftfreq(8, 1.0)) + q = np.array([0.41666667, 0.38194444, 0.55555556, 0.55555556, + 0.55555556, 0.55555556, 0.55555556, 0.38194444]) + assert_allclose(p, q, atol=1e-7, rtol=1e-7) + + def test_unk_scaling(self): + assert_raises(ValueError, csd, np.zeros(4, np.complex128), + np.ones(4, np.complex128), scaling='foo', nperseg=4) + + def test_detrend_linear(self): + x = np.arange(10, dtype=np.float64) + 0.04 + f, p = csd(x, x, nperseg=10, detrend='linear') + assert_allclose(p, np.zeros_like(p), atol=1e-15) + + def test_no_detrending(self): + x = np.arange(10, dtype=np.float64) + 0.04 + f1, p1 = csd(x, x, nperseg=10, detrend=False) + f2, p2 = csd(x, x, nperseg=10, detrend=lambda x: x) + assert_allclose(f1, f2, atol=1e-15) + assert_allclose(p1, p2, atol=1e-15) + + def test_detrend_external(self): + x = np.arange(10, dtype=np.float64) + 0.04 + f, p = csd(x, x, nperseg=10, + detrend=lambda seg: signal.detrend(seg, type='l')) + assert_allclose(p, np.zeros_like(p), atol=1e-15) + + def test_detrend_external_nd_m1(self): + x = np.arange(40, dtype=np.float64) + 0.04 + x = x.reshape((2,2,10)) + f, p = csd(x, x, nperseg=10, + detrend=lambda seg: signal.detrend(seg, type='l')) + assert_allclose(p, np.zeros_like(p), atol=1e-15) + + def test_detrend_external_nd_0(self): + x = np.arange(20, dtype=np.float64) + 0.04 + x = x.reshape((2,1,10)) + x = np.moveaxis(x, 2, 0) + f, p = csd(x, x, nperseg=10, axis=0, + detrend=lambda seg: signal.detrend(seg, axis=0, type='l')) + assert_allclose(p, np.zeros_like(p), atol=1e-15) + + def test_nd_axis_m1(self): + x = np.arange(20, dtype=np.float64) + 0.04 + x = x.reshape((2,1,10)) + f, p = csd(x, x, nperseg=10) + assert_array_equal(p.shape, (2, 1, 6)) + assert_allclose(p[0,0,:], p[1,0,:], atol=1e-13, rtol=1e-13) + f0, p0 = csd(x[0,0,:], x[0,0,:], nperseg=10) + assert_allclose(p0[np.newaxis,:], p[1,:], atol=1e-13, rtol=1e-13) + + def test_nd_axis_0(self): + x = np.arange(20, dtype=np.float64) + 0.04 + x = x.reshape((10,2,1)) + f, p = csd(x, x, nperseg=10, axis=0) + assert_array_equal(p.shape, (6,2,1)) + assert_allclose(p[:,0,0], p[:,1,0], atol=1e-13, rtol=1e-13) + f0, p0 = csd(x[:,0,0], x[:,0,0], nperseg=10) + assert_allclose(p0, p[:,1,0], atol=1e-13, rtol=1e-13) + + def test_window_external(self): + x = np.zeros(16) + x[0] = 1 + x[8] = 1 + f, p = csd(x, x, 10, 'hann', 8) + win = signal.get_window('hann', 8) + fe, pe = csd(x, x, 10, win, nperseg=None) + assert_array_almost_equal_nulp(p, pe) + assert_array_almost_equal_nulp(f, fe) + assert_array_equal(fe.shape, (5,)) # because win length used as nperseg + assert_array_equal(pe.shape, (5,)) + assert_raises(ValueError, csd, x, x, + 10, win, nperseg=256) # because nperseg != win.shape[-1] + win_err = signal.get_window('hann', 32) + assert_raises(ValueError, csd, x, x, + 10, win_err, nperseg=None) # because win longer than signal + with pytest.raises(ValueError, match="Parameter nperseg=0.*"): + csd(x, x, 0, nperseg=0) + + def test_empty_input(self): + f, p = csd([],np.zeros(10)) + assert_array_equal(f.shape, (0,)) + assert_array_equal(p.shape, (0,)) + + f, p = csd(np.zeros(10),[]) + assert_array_equal(f.shape, (0,)) + assert_array_equal(p.shape, (0,)) + + for shape in [(0,), (3,0), (0,5,2)]: + f, p = csd(np.empty(shape), np.empty(shape)) + assert_array_equal(f.shape, shape) + assert_array_equal(p.shape, shape) + + f, p = csd(np.ones(10), np.empty((5,0))) + assert_array_equal(f.shape, (5,0)) + assert_array_equal(p.shape, (5,0)) + + f, p = csd(np.empty((5,0)), np.ones(10)) + assert_array_equal(f.shape, (5,0)) + assert_array_equal(p.shape, (5,0)) + + def test_empty_input_other_axis(self): + for shape in [(3,0), (0,5,2)]: + f, p = csd(np.empty(shape), np.empty(shape), axis=1) + assert_array_equal(f.shape, shape) + assert_array_equal(p.shape, shape) + + f, p = csd(np.empty((10,10,3)), np.zeros((10,0,1)), axis=1) + assert_array_equal(f.shape, (10,0,3)) + assert_array_equal(p.shape, (10,0,3)) + + f, p = csd(np.empty((10,0,1)), np.zeros((10,10,3)), axis=1) + assert_array_equal(f.shape, (10,0,3)) + assert_array_equal(p.shape, (10,0,3)) + + def test_short_data(self): + x = np.zeros(8) + x[0] = 1 + + #for string-like window, input signal length < nperseg value gives + #UserWarning, sets nperseg to x.shape[-1] + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", "nperseg=256 is greater than signal length.*", UserWarning) + f, p = csd(x, x, window='hann') # default nperseg + f1, p1 = csd(x, x, window='hann', nperseg=256) # user-specified nperseg + f2, p2 = csd(x, x, nperseg=8) # valid nperseg, doesn't give warning + assert_allclose(f, f2) + assert_allclose(p, p2) + assert_allclose(f1, f2) + assert_allclose(p1, p2) + + def test_window_long_or_nd(self): + assert_raises(ValueError, csd, np.zeros(4), np.ones(4), 1, + np.array([1,1,1,1,1])) + assert_raises(ValueError, csd, np.zeros(4), np.ones(4), 1, + np.arange(6).reshape((2,3))) + + def test_nondefault_noverlap(self): + x = np.zeros(64) + x[::8] = 1 + f, p = csd(x, x, nperseg=16, noverlap=4) + q = np.array([0, 1./12., 1./3., 1./5., 1./3., 1./5., 1./3., 1./5., + 1./6.]) + assert_allclose(p, q, atol=1e-12) + + def test_bad_noverlap(self): + assert_raises(ValueError, csd, np.zeros(4), np.ones(4), 1, 'hann', + 2, 7) + + def test_nfft_too_short(self): + assert_raises(ValueError, csd, np.ones(12), np.zeros(12), nfft=3, + nperseg=4) + + def test_incompatible_inputs(self): + with pytest.raises(ValueError, match='x and y cannot be broadcast.*'): + csd(np.ones((1, 8, 1)), np.ones((2, 8)), nperseg=4) + + + def test_real_onesided_even_32(self): + x = np.zeros(16, 'f') + x[0] = 1 + x[8] = 1 + f, p = csd(x, x, nperseg=8) + assert_allclose(f, np.linspace(0, 0.5, 5)) + q = np.array([0.08333333, 0.15277778, 0.22222222, 0.22222222, + 0.11111111], 'f') + assert_allclose(p, q, atol=1e-7, rtol=1e-7) + assert_(p.dtype == q.dtype) + + def test_real_onesided_odd_32(self): + x = np.zeros(16, 'f') + x[0] = 1 + x[8] = 1 + f, p = csd(x, x, nperseg=9) + assert_allclose(f, np.arange(5.0)/9.0) + q = np.array([0.12477458, 0.23430935, 0.17072113, 0.17072116, + 0.17072113], 'f') + assert_allclose(p, q, atol=1e-7, rtol=1e-7) + assert_(p.dtype == q.dtype) + + def test_real_twosided_32(self): + x = np.zeros(16, 'f') + x[0] = 1 + x[8] = 1 + f, p = csd(x, x, nperseg=8, return_onesided=False) + assert_allclose(f, fftfreq(8, 1.0)) + q = np.array([0.08333333, 0.07638889, 0.11111111, + 0.11111111, 0.11111111, 0.11111111, 0.11111111, + 0.07638889], 'f') + assert_allclose(p, q, atol=1e-7, rtol=1e-7) + assert_(p.dtype == q.dtype) + + def test_complex_32(self): + x = np.zeros(16, 'F') + x[0] = 1.0 + 2.0j + x[8] = 1.0 + 2.0j + f, p = csd(x, x, nperseg=8, return_onesided=False) + assert_allclose(f, fftfreq(8, 1.0)) + q = np.array([0.41666666, 0.38194442, 0.55555552, 0.55555552, + 0.55555558, 0.55555552, 0.55555552, 0.38194442], 'f') + assert_allclose(p, q, atol=1e-7, rtol=1e-7) + assert_(p.dtype == q.dtype, + f'dtype mismatch, {p.dtype}, {q.dtype}') + + def test_padded_freqs(self): + x = np.zeros(12) + y = np.ones(12) + + nfft = 24 + f = fftfreq(nfft, 1.0)[:nfft//2+1] + f[-1] *= -1 + fodd, _ = csd(x, y, nperseg=5, nfft=nfft) + feven, _ = csd(x, y, nperseg=6, nfft=nfft) + assert_allclose(f, fodd) + assert_allclose(f, feven) + + nfft = 25 + f = fftfreq(nfft, 1.0)[:(nfft + 1)//2] + fodd, _ = csd(x, y, nperseg=5, nfft=nfft) + feven, _ = csd(x, y, nperseg=6, nfft=nfft) + assert_allclose(f, fodd) + assert_allclose(f, feven) + + def test_copied_data(self): + x = np.random.randn(64) + y = x.copy() + + _, p_same = csd(x, x, nperseg=8, average='mean', + return_onesided=False) + _, p_copied = csd(x, y, nperseg=8, average='mean', + return_onesided=False) + assert_allclose(p_same, p_copied) + + _, p_same = csd(x, x, nperseg=8, average='median', + return_onesided=False) + _, p_copied = csd(x, y, nperseg=8, average='median', + return_onesided=False) + assert_allclose(p_same, p_copied) + + +class TestCoherence: + def test_identical_input(self): + x = np.random.randn(20) + y = np.copy(x) # So `y is x` -> False + + f = np.linspace(0, 0.5, 6) + C = np.ones(6) + f1, C1 = coherence(x, y, nperseg=10) + + assert_allclose(f, f1) + assert_allclose(C, C1) + + def test_phase_shifted_input(self): + x = np.random.randn(20) + y = -x + + f = np.linspace(0, 0.5, 6) + C = np.ones(6) + f1, C1 = coherence(x, y, nperseg=10) + + assert_allclose(f, f1) + assert_allclose(C, C1) + + +class TestSpectrogram: + def test_average_all_segments(self): + x = np.random.randn(1024) + + fs = 1.0 + window = ('tukey', 0.25) + nperseg = 16 + noverlap = 2 + + f, _, P = spectrogram(x, fs, window, nperseg, noverlap) + fw, Pw = welch(x, fs, window, nperseg, noverlap) + assert_allclose(f, fw) + assert_allclose(np.mean(P, axis=-1), Pw) + + def test_window_external(self): + x = np.random.randn(1024) + + fs = 1.0 + window = ('tukey', 0.25) + nperseg = 16 + noverlap = 2 + f, _, P = spectrogram(x, fs, window, nperseg, noverlap) + + win = signal.get_window(('tukey', 0.25), 16) + fe, _, Pe = spectrogram(x, fs, win, nperseg=None, noverlap=2) + assert_array_equal(fe.shape, (9,)) # because win length used as nperseg + assert_array_equal(Pe.shape, (9,73)) + assert_raises(ValueError, spectrogram, x, + fs, win, nperseg=8) # because nperseg != win.shape[-1] + win_err = signal.get_window(('tukey', 0.25), 2048) + assert_raises(ValueError, spectrogram, x, + fs, win_err, nperseg=None) # win longer than signal + + def test_short_data(self): + x = np.random.randn(1024) + fs = 1.0 + + #for string-like window, input signal length < nperseg value gives + #UserWarning, sets nperseg to x.shape[-1] + f, _, p = spectrogram(x, fs, window=('tukey',0.25)) # default nperseg + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", + "nperseg = 1025 is greater than input length = 1024, " + "using nperseg = 1024", + UserWarning, + ) + f1, _, p1 = spectrogram(x, fs, window=('tukey',0.25), + nperseg=1025) # user-specified nperseg + f2, _, p2 = spectrogram(x, fs, nperseg=256) # to compare w/default + f3, _, p3 = spectrogram(x, fs, nperseg=1024) # compare w/user-spec'd + assert_allclose(f, f2) + assert_allclose(p, p2) + assert_allclose(f1, f3) + assert_allclose(p1, p3) + +class TestLombscargle: + def test_frequency(self): + """Test if frequency location of peak corresponds to frequency of + generated input signal. + """ + + # Input parameters + ampl = 2. + w = 1. + phi = 0.5 * np.pi + nin = 100 + nout = 1000 + p = 0.7 # Fraction of points to select + + # Randomly select a fraction of an array with timesteps + rng = np.random.RandomState(2353425) + r = rng.rand(nin) + t = np.linspace(0.01*np.pi, 10.*np.pi, nin)[r >= p] + + # Plot a sine wave for the selected times + y = ampl * np.sin(w*t + phi) + + # Define the array of frequencies for which to compute the periodogram + f = np.linspace(0.01, 10., nout) + + # Calculate Lomb-Scargle periodogram + P = lombscargle(t, y, f) + + # Check if difference between found frequency maximum and input + # frequency is less than accuracy + delta = f[1] - f[0] + assert(w - f[np.argmax(P)] < (delta/2.)) + + # also, check that it works with weights + P = lombscargle(t, y, f, weights=np.ones_like(t, dtype=f.dtype)) + + # Check if difference between found frequency maximum and input + # frequency is less than accuracy + delta = f[1] - f[0] + assert(w - f[np.argmax(P)] < (delta/2.)) + + def test_amplitude(self): + # Test if height of peak in unnormalized Lomb-Scargle periodogram + # corresponds to amplitude of the generated input signal. + + # Input parameters + ampl = 2. + w = 1. + phi = 0.5 * np.pi + nin = 1000 + nout = 1000 + p = 0.7 # Fraction of points to select + + # Randomly select a fraction of an array with timesteps + rng = np.random.RandomState(2353425) + r = rng.rand(nin) + t = np.linspace(0.01*np.pi, 10.*np.pi, nin)[r >= p] + + # Plot a sine wave for the selected times + y = ampl * np.sin(w*t + phi) + + # Define the array of frequencies for which to compute the periodogram + f = np.linspace(0.01, 10., nout) + + # Calculate Lomb-Scargle periodogram + pgram = lombscargle(t, y, f) + + # convert to the amplitude + pgram = np.sqrt(4.0 * pgram / t.shape[0]) + + # Check if amplitude is correct (this will not exactly match, due to + # numerical differences when data is removed) + assert_allclose(pgram[f==w], ampl, rtol=5e-2) + + @pytest.mark.filterwarnings("ignore::DeprecationWarning") + def test_precenter(self): + # Test if precenter gives the same result as manually precentering + # (for a very simple offset) + + # Input parameters + ampl = 2. + w = 1. + phi = 0.5 * np.pi + nin = 100 + nout = 1000 + p = 0.7 # Fraction of points to select + offset = 0.15 # Offset to be subtracted in pre-centering + + # Randomly select a fraction of an array with timesteps + rng = np.random.RandomState(2353425) + r = rng.rand(nin) + t = np.linspace(0.01*np.pi, 10.*np.pi, nin)[r >= p] + + # Plot a sine wave for the selected times + y = ampl * np.sin(w*t + phi) + offset + + # Define the array of frequencies for which to compute the periodogram + f = np.linspace(0.01, 10., nout) + + # Calculate Lomb-Scargle periodogram + pgram = lombscargle(t, y, f, precenter=True) + pgram2 = lombscargle(t, y - y.mean(), f, precenter=False) + + # check if centering worked + assert_allclose(pgram, pgram2) + + # do this again, but with floating_mean=True + + # Calculate Lomb-Scargle periodogram + pgram = lombscargle(t, y, f, precenter=True, floating_mean=True) + pgram2 = lombscargle(t, y - y.mean(), f, precenter=False, floating_mean=True) + + # check if centering worked + assert_allclose(pgram, pgram2) + + def test_normalize(self): + # Test normalize option of Lomb-Scarge. + + # Input parameters + ampl = 2. + w = 1. + phi = 0.5 * np.pi + nin = 100 + nout = 1000 + p = 0.7 # Fraction of points to select + + # Randomly select a fraction of an array with timesteps + rng = np.random.RandomState(2353425) + r = rng.rand(nin) + t = np.linspace(0.01*np.pi, 10.*np.pi, nin)[r >= p] + + # Plot a sine wave for the selected times + y = ampl * np.sin(w*t + phi) + + # Define the array of frequencies for which to compute the periodogram + f = np.linspace(0.01, 10., nout) + + # Calculate Lomb-Scargle periodogram + pgram = lombscargle(t, y, f) + pgram2 = lombscargle(t, y, f, normalize=True) + + # Calculate the scale to convert from unnormalized to normalized + weights = np.ones_like(t)/float(t.shape[0]) + YY_hat = (weights * y * y).sum() + YY = YY_hat # correct formula for floating_mean=False + scale_to_use = 2/(YY*t.shape[0]) + + # check if normalization works as expected + assert_allclose(pgram * scale_to_use, pgram2) + assert_allclose(np.max(pgram2), 1.0) + + def test_wrong_shape(self): + + # different length t and y + t = np.linspace(0, 1, 1) + y = np.linspace(0, 1, 2) + f = np.linspace(0, 1, 3) + 0.1 + assert_raises(ValueError, lombscargle, t, y, f) + + # t is 2D, with both axes length > 1 + t = np.repeat(np.expand_dims(np.linspace(0, 1, 2), 1), 2, axis=1) + y = np.linspace(0, 1, 2) + f = np.linspace(0, 1, 3) + 0.1 + assert_raises(ValueError, lombscargle, t, y, f) + + # y is 2D, with both axes length > 1 + t = np.linspace(0, 1, 2) + y = np.repeat(np.expand_dims(np.linspace(0, 1, 2), 1), 2, axis=1) + f = np.linspace(0, 1, 3) + 0.1 + assert_raises(ValueError, lombscargle, t, y, f) + + # f is 2D, with both axes length > 1 + t = np.linspace(0, 1, 2) + y = np.linspace(0, 1, 2) + f = np.repeat(np.expand_dims(np.linspace(0, 1, 3), 1) + 0.1, 2, axis=1) + assert_raises(ValueError, lombscargle, t, y, f) + + # weights is 2D, with both axes length > 1 + t = np.linspace(0, 1, 2) + y = np.linspace(0, 1, 2) + f = np.linspace(0, 1, 3) + 0.1 + weights = np.repeat(np.expand_dims(np.linspace(0, 1, 2), 1), 2, axis=1) + assert_raises(ValueError, lombscargle, t, y, f, weights=weights) + + def test_lombscargle_atan_vs_atan2(self): + # https://github.com/scipy/scipy/issues/3787 + # This raised a ZeroDivisionError. + t = np.linspace(0, 10, 1000, endpoint=False) + y = np.sin(4*t) + f = np.linspace(0, 50, 500, endpoint=False) + 0.1 + lombscargle(t, y, f*2*np.pi) + + def test_wrong_shape_weights(self): + # Weights must be the same shape as t + + t = np.linspace(0, 1, 1) + y = np.linspace(0, 1, 1) + f = np.linspace(0, 1, 3) + 0.1 + weights = np.linspace(1, 2, 2) + assert_raises(ValueError, lombscargle, t, y, f, weights=weights) + + def test_zero_division_weights(self): + # Weights cannot sum to 0 + + t = np.zeros(1) + y = np.zeros(1) + f = np.ones(1) + weights = np.zeros(1) + assert_raises(ValueError, lombscargle, t, y, f, weights=weights) + + def test_normalize_parameter(self): + # Test the validity of the normalize parameter input + + # Input parameters + ampl = 2. + w = 1. + phi = 0 + nin = 100 + nout = 1000 + p = 0.7 # Fraction of points to select + + # Randomly select a fraction of an array with timesteps + rng = np.random.RandomState(2353425) + r = rng.rand(nin) + t = np.linspace(0.01*np.pi, 10.*np.pi, nin)[r >= p] + + # Plot a sine wave for the selected times + y = ampl * np.sin(w*t + phi) + + # Define the array of frequencies for which to compute the periodogram + f = np.linspace(0.01, 10., nout) + + # check each of the valid inputs + pgram_false = lombscargle(t, y, f, normalize=False) + pgram_true = lombscargle(t, y, f, normalize=True) + pgram_power = lombscargle(t, y, f, normalize='power') + pgram_norm = lombscargle(t, y, f, normalize='normalize') + pgram_amp = lombscargle(t, y, f, normalize='amplitude') + + # validate the results that should be the same + assert_allclose(pgram_false, pgram_power) + assert_allclose(pgram_true, pgram_norm) + + # validate that the power and norm outputs are proper wrt each other + weights = np.ones_like(y)/float(y.shape[0]) + YY_hat = (weights * y * y).sum() + YY = YY_hat # correct formula for floating_mean=False + assert_allclose(pgram_power * 2.0 / (float(t.shape[0]) * YY), pgram_norm) + + # validate that the amp output is correct for the given input + f_i = np.where(f==w)[0][0] + assert_allclose(np.abs(pgram_amp[f_i]), ampl) + + # check invalid inputs + # 1) a string that is not allowed + assert_raises(ValueError, lombscargle, t, y, f, normalize='lomb') + # 2) something besides a bool or str + assert_raises(ValueError, lombscargle, t, y, f, normalize=2) + + def test_offset_removal(self): + # Verify that the amplitude is the same, even with an offset + # must use floating_mean=True, otherwise it will not remove an offset + + # Input parameters + ampl = 2. + w = 1. + phi = 0.5 * np.pi + nin = 100 + nout = 1000 + p = 0.7 # Fraction of points to select + offset = 2.15 # Large offset + + # Randomly select a fraction of an array with timesteps + rng = np.random.RandomState(2353425) + r = rng.rand(nin) + t = np.linspace(0.01*np.pi, 10.*np.pi, nin)[r >= p] + + # Plot a sine wave for the selected times + y = ampl * np.sin(w*t + phi) + + # Define the array of frequencies for which to compute the periodogram + f = np.linspace(0.01, 10., nout) + + # Calculate Lomb-Scargle periodogram + pgram = lombscargle(t, y, f, floating_mean=True) + pgram_offset = lombscargle(t, y + offset, f, floating_mean=True) + + # check if offset removal works as expected + assert_allclose(pgram, pgram_offset) + + def test_floating_mean_false(self): + # Verify that when disabling the floating_mean, the calculations are correct + + # Input parameters + ampl = 2. + w = 1. + phi = 0 + nin = 1000 + nout = 1000 + p = 0.7 # Fraction of points to select + offset = 2 # Large offset + + # Randomly select a fraction of an array with timesteps + rng = np.random.RandomState(2353425) + r = rng.rand(nin) + t = np.linspace(0.01*np.pi, 10.*np.pi, nin)[r >= p] + + # Plot a cos wave for the selected times + y = ampl * np.cos(w*t + phi) + + # Define the array of frequencies for which to compute the periodogram + f = np.linspace(0.01, 10., nout) + + # Calculate Lomb-Scargle periodogram + pgram = lombscargle(t, y, f, normalize=True, floating_mean=False) + pgram_offset = lombscargle(t, y + offset, f, normalize=True, + floating_mean=False) + + # check if disabling floating_mean works as expected + # nearly-zero for no offset, exact value will change based on seed + assert(pgram[0] < 0.01) + # significant value with offset, exact value will change based on seed + assert(pgram_offset[0] > 0.5) + + def test_amplitude_is_correct(self): + # Verify that the amplitude is correct (when normalize='amplitude') + + # Input parameters + ampl = 2. + w = 1. + phi = 0.12 + nin = 100 + nout = 1000 + p = 0.7 # Fraction of points to select + offset = 2.15 # Large offset + + # Randomly select a fraction of an array with timesteps + rng = np.random.RandomState(2353425) + r = rng.rand(nin) + t = np.linspace(0.01*np.pi, 10.*np.pi, nin)[r >= p] + + # Plot a sine wave for the selected times + y = ampl * np.cos(w*t + phi) + offset + + # Define the array of frequencies for which to compute the periodogram + f = np.linspace(0.01, 10., nout) + + # Get the index of where the exact result should be + f_indx = np.where(f==w)[0][0] + + # Calculate Lomb-Scargle periodogram (amplitude + phase) + pgram = lombscargle(t, y, f, normalize='amplitude', floating_mean=True) + + # Check if amplitude is correct + assert_allclose(np.abs(pgram[f_indx]), ampl) + + # Check if phase is correct + # (phase angle is the negative of the phase offset) + assert_allclose(-np.angle(pgram[f_indx]), phi) + + def test_negative_weight(self): + # Test that a negative weight produces an error + + t = np.zeros(1) + y = np.zeros(1) + f = np.ones(1) + weights = -np.ones(1) + assert_raises(ValueError, lombscargle, t, y, f, weights=weights) + + @pytest.mark.filterwarnings("ignore::DeprecationWarning") + def test_list_input(self): + # Test that input can be passsed in as lists and with a numerical issue + # https://github.com/scipy/scipy/issues/8787 + + t = [1.98201652e+09, 1.98201752e+09, 1.98201852e+09, 1.98201952e+09, + 1.98202052e+09, 1.98202152e+09, 1.98202252e+09, 1.98202352e+09, + 1.98202452e+09, 1.98202552e+09, 1.98202652e+09, 1.98202752e+09, + 1.98202852e+09, 1.98202952e+09, 1.98203052e+09, 1.98203152e+09, + 1.98203252e+09, 1.98203352e+09, 1.98203452e+09, 1.98203552e+09, + 1.98205452e+09, 1.98205552e+09, 1.98205652e+09, 1.98205752e+09, + 1.98205852e+09, 1.98205952e+09, 1.98206052e+09, 1.98206152e+09, + 1.98206252e+09, 1.98206352e+09, 1.98206452e+09, 1.98206552e+09, + 1.98206652e+09, 1.98206752e+09, 1.98206852e+09, 1.98206952e+09, + 1.98207052e+09, 1.98207152e+09, 1.98207252e+09, 1.98207352e+09, + 1.98209652e+09, 1.98209752e+09, 1.98209852e+09, 1.98209952e+09, + 1.98210052e+09, 1.98210152e+09, 1.98210252e+09, 1.98210352e+09, + 1.98210452e+09, 1.98210552e+09, 1.98210652e+09, 1.98210752e+09, + 1.98210852e+09, 1.98210952e+09, 1.98211052e+09, 1.98211152e+09, + 1.98211252e+09, 1.98211352e+09, 1.98211452e+09, 1.98211552e+09, + 1.98217252e+09, 1.98217352e+09, 1.98217452e+09, 1.98217552e+09, + 1.98217652e+09, 1.98217752e+09, 1.98217852e+09, 1.98217952e+09, + 1.98218052e+09, 1.98218152e+09, 1.98218252e+09, 1.98218352e+09, + 1.98218452e+09, 1.98218552e+09, 1.98218652e+09, 1.98218752e+09, + 1.98218852e+09, 1.98218952e+09, 1.98219052e+09, 1.98219152e+09, + 1.98219352e+09, 1.98219452e+09, 1.98219552e+09, 1.98219652e+09, + 1.98219752e+09, 1.98219852e+09, 1.98219952e+09, 1.98220052e+09, + 1.98220152e+09, 1.98220252e+09, 1.98220352e+09, 1.98220452e+09, + 1.98220552e+09, 1.98220652e+09, 1.98220752e+09, 1.98220852e+09, + 1.98220952e+09, 1.98221052e+09, 1.98221152e+09, 1.98221252e+09, + 1.98222752e+09, 1.98222852e+09, 1.98222952e+09, 1.98223052e+09, + 1.98223152e+09, 1.98223252e+09, 1.98223352e+09, 1.98223452e+09, + 1.98223552e+09, 1.98223652e+09, 1.98223752e+09, 1.98223852e+09, + 1.98223952e+09, 1.98224052e+09, 1.98224152e+09, 1.98224252e+09, + 1.98224352e+09, 1.98224452e+09, 1.98224552e+09, 1.98224652e+09, + 1.98224752e+09] + y = [2.97600000e+03, 3.18200000e+03, 3.74900000e+03, 4.53500000e+03, + 5.43300000e+03, 6.38000000e+03, 7.34000000e+03, 8.29200000e+03, + 9.21900000e+03, 1.01120000e+04, 1.09620000e+04, 1.17600000e+04, + 1.25010000e+04, 1.31790000e+04, 1.37900000e+04, 1.43290000e+04, + 1.47940000e+04, 1.51800000e+04, 1.54870000e+04, 1.57110000e+04, + 5.74200000e+03, 4.82300000e+03, 3.99100000e+03, 3.33600000e+03, + 2.99600000e+03, 3.08400000e+03, 3.56700000e+03, 4.30700000e+03, + 5.18200000e+03, 6.11900000e+03, 7.07900000e+03, 8.03400000e+03, + 8.97000000e+03, 9.87300000e+03, 1.07350000e+04, 1.15480000e+04, + 1.23050000e+04, 1.30010000e+04, 1.36300000e+04, 1.41890000e+04, + 6.00000000e+03, 5.06800000e+03, 4.20500000e+03, 3.49000000e+03, + 3.04900000e+03, 3.01600000e+03, 3.40400000e+03, 4.08800000e+03, + 4.93500000e+03, 5.86000000e+03, 6.81700000e+03, 7.77500000e+03, + 8.71800000e+03, 9.63100000e+03, 1.05050000e+04, 1.13320000e+04, + 1.21050000e+04, 1.28170000e+04, 1.34660000e+04, 1.40440000e+04, + 1.32730000e+04, 1.26040000e+04, 1.18720000e+04, 1.10820000e+04, + 1.02400000e+04, 9.35300000e+03, 8.43000000e+03, 7.48100000e+03, + 6.52100000e+03, 5.57000000e+03, 4.66200000e+03, 3.85400000e+03, + 3.24600000e+03, 2.97900000e+03, 3.14700000e+03, 3.68800000e+03, + 4.45900000e+03, 5.35000000e+03, 6.29400000e+03, 7.25400000e+03, + 9.13800000e+03, 1.00340000e+04, 1.08880000e+04, 1.16910000e+04, + 1.24370000e+04, 1.31210000e+04, 1.37380000e+04, 1.42840000e+04, + 1.47550000e+04, 1.51490000e+04, 1.54630000e+04, 1.56950000e+04, + 1.58430000e+04, 1.59070000e+04, 1.58860000e+04, 1.57800000e+04, + 1.55910000e+04, 1.53190000e+04, 1.49650000e+04, 1.45330000e+04, + 3.01000000e+03, 3.05900000e+03, 3.51200000e+03, 4.23400000e+03, + 5.10000000e+03, 6.03400000e+03, 6.99300000e+03, 7.95000000e+03, + 8.88800000e+03, 9.79400000e+03, 1.06600000e+04, 1.14770000e+04, + 1.22400000e+04, 1.29410000e+04, 1.35770000e+04, 1.41430000e+04, + 1.46350000e+04, 1.50500000e+04, 1.53850000e+04, 1.56400000e+04, + 1.58110000e+04] + + periods = np.linspace(400, 120, 1000) + angular_freq = 2 * np.pi / periods + + lombscargle(t, y, angular_freq, precenter=True, normalize=True) + + def test_zero_freq(self): + # Verify that function works when freqs includes 0 + # The value at f=0 will depend on the seed + + # Input parameters + ampl = 2. + w = 1. + phi = 0.12 + nin = 100 + nout = 1001 + p = 0.7 # Fraction of points to select + offset = 0 + + # Randomly select a fraction of an array with timesteps + rng = np.random.RandomState(2353425) + r = rng.rand(nin) + t = np.linspace(0.01*np.pi, 10.*np.pi, nin)[r >= p] + + # Plot a sine wave for the selected times + y = ampl * np.cos(w*t + phi) + offset + + # Define the array of frequencies for which to compute the periodogram + f = np.linspace(0, 10., nout) + + # Calculate Lomb-Scargle periodogram + pgram = lombscargle(t, y, f, normalize=True, floating_mean=True) + + # exact value will change based on seed + # testing to make sure it is very small + assert(pgram[0] < 1e-4) + + def test_simple_div_zero(self): + # these are bare-minimum examples that would, without the eps adjustments, + # cause division-by-zero errors + + # first, test with example that will cause first SS sum to be 0.0 + t = [t + 1 for t in range(0, 32)] + y = np.ones(len(t)) + freqs = [2.0*np.pi] * 2 # must have 2+ elements + lombscargle(t, y, freqs) + + # second, test with example that will cause first CC sum to be 0.0 + t = [t*4 + 1 for t in range(0, 32)] + y = np.ones(len(t)) + freqs = [np.pi/2.0] * 2 # must have 2+ elements + + lombscargle(t, y, freqs) + + @pytest.mark.filterwarnings("ignore::DeprecationWarning") + def test_input_mutation(self): + # this tests for mutation of the input arrays + # https://github.com/scipy/scipy/issues/23474 + + # Input parameters + ampl = 2. + w = 1. + phi = 0.5 * np.pi + nin = 100 + nout = 1000 + p = 0.7 # Fraction of points to select + + # Randomly select a fraction of an array with timesteps + rng = np.random.default_rng() + r = rng.random(nin) + t = np.linspace(0.01*np.pi, 10.*np.pi, nin)[r >= p] + + # Plot a sine wave for the selected times + y = ampl * np.sin(w*t + phi) + + # Define the array of frequencies for which to compute the periodogram + f = np.linspace(0.01, 10., nout) + + weights = np.ones_like(y) + + # create original copies before passing + t_org = t.copy() + y_org = y.copy() + f_org = f.copy() + weights_org = weights.copy() + + lombscargle(t, y, f, precenter=True, weights=weights) + + # check all 4 array inputs + assert_array_equal(t, t_org) + assert_array_equal(y, y_org) + assert_array_equal(f, f_org) + assert_array_equal(weights, weights_org) + + def test_precenter_deprecation(self): + # test that precenter deprecation warning is raised + + # Input parameters + ampl = 2. + w = 1. + phi = 0.5 * np.pi + nin = 100 + nout = 1000 + p = 0.7 # Fraction of points to select + offset = 0.15 # Offset to be subtracted in pre-centering + + # Randomly select a fraction of an array with timesteps + rng = np.random.default_rng() + r = rng.random(nin) + t = np.linspace(0.01*np.pi, 10.*np.pi, nin)[r >= p] + + # Plot a sine wave for the selected times + y = ampl * np.sin(w*t + phi) + offset + + # Define the array of frequencies for which to compute the periodogram + f = np.linspace(0.01, 10., nout) + + # Calculate Lomb-Scargle periodogram + with pytest.deprecated_call(match="leave 'precenter' unspecified"): + lombscargle(t, y, f, precenter=True) + # Should warn for explicit `False` too + with pytest.deprecated_call(match="leave 'precenter' unspecified"): + lombscargle(t, y, f, precenter=False) + + @pytest.mark.filterwarnings( + "ignore:.*leave 'precenter' unspecified.*:DeprecationWarning" + ) + def test_positional_args_deprecation(self): + with pytest.deprecated_call(match="use keyword arguments"): + one = np.asarray([1.0]) + lombscargle(one, one, one, False) + + +class TestSTFT: + def test_input_validation(self): + + def chk_VE(match): + """Assert for a ValueError matching regexp `match`. + + This little wrapper allows a more concise code layout. + """ + return pytest.raises(ValueError, match=match) + + # Checks for check_COLA(): + with chk_VE('nperseg must be a positive integer'): + check_COLA('hann', -10, 0) + with chk_VE('noverlap must be less than nperseg.'): + check_COLA('hann', 10, 20) + with chk_VE('window must be 1-D'): + check_COLA(np.ones((2, 2)), 10, 0) + with chk_VE('window must have length of nperseg'): + check_COLA(np.ones(20), 10, 0) + + # Checks for check_NOLA(): + with chk_VE('nperseg must be a positive integer'): + check_NOLA('hann', -10, 0) + with chk_VE('noverlap must be less than nperseg'): + check_NOLA('hann', 10, 20) + with chk_VE('window must be 1-D'): + check_NOLA(np.ones((2, 2)), 10, 0) + with chk_VE('window must have length of nperseg'): + check_NOLA(np.ones(20), 10, 0) + with chk_VE('noverlap must be a nonnegative integer'): + check_NOLA('hann', 64, -32) + + x = np.zeros(1024) + z = stft(x)[2] + + # Checks for stft(): + with chk_VE('window must be 1-D'): + stft(x, window=np.ones((2, 2))) + with chk_VE('value specified for nperseg is different ' + + 'from length of window'): + stft(x, window=np.ones(10), nperseg=256) + with chk_VE('nperseg must be a positive integer'): + stft(x, nperseg=-256) + with chk_VE('noverlap must be less than nperseg.'): + stft(x, nperseg=256, noverlap=1024) + with chk_VE('nfft must be greater than or equal to nperseg.'): + stft(x, nperseg=256, nfft=8) + + # Checks for istft(): + with chk_VE('Input stft must be at least 2d!'): + istft(x) + with chk_VE('window must be 1-D'): + istft(z, window=np.ones((2, 2))) + with chk_VE('window must have length of 256'): + istft(z, window=np.ones(10), nperseg=256) + with chk_VE('nperseg must be a positive integer'): + istft(z, nperseg=-256) + with chk_VE('noverlap must be less than nperseg.'): + istft(z, nperseg=256, noverlap=1024) + with chk_VE('nfft must be greater than or equal to nperseg.'): + istft(z, nperseg=256, nfft=8) + with pytest.warns(UserWarning, match="NOLA condition failed, " + + "STFT may not be invertible"): + istft(z, nperseg=256, noverlap=0, window='hann') + with chk_VE('Must specify differing time and frequency axes!'): + istft(z, time_axis=0, freq_axis=0) + + # Checks for _spectral_helper(): + with chk_VE("Unknown value for mode foo, must be one of: " + + r"\{'psd', 'stft'\}"): + _spectral_helper(x, x, mode='foo') + with chk_VE("x and y must be equal if mode is 'stft'"): + _spectral_helper(x[:512], x[512:], mode='stft') + with chk_VE("Unknown boundary option 'foo', must be one of: " + + r"\['even', 'odd', 'constant', 'zeros', None\]"): + _spectral_helper(x, x, boundary='foo') + + scaling = "not_valid" + with chk_VE(fr"Parameter {scaling=} not in \['spectrum', 'psd'\]!"): + stft(x, scaling=scaling) + with chk_VE(fr"Parameter {scaling=} not in \['spectrum', 'psd'\]!"): + istft(z, scaling=scaling) + + def test_check_COLA(self): + settings = [ + ('boxcar', 10, 0), + ('boxcar', 10, 9), + ('bartlett', 51, 26), + ('hann', 256, 128), + ('hann', 256, 192), + ('blackman', 300, 200), + (('tukey', 0.5), 256, 64), + ('hann', 256, 255), + ] + + for setting in settings: + msg = '{}, {}, {}'.format(*setting) + assert_equal(True, check_COLA(*setting), err_msg=msg) + + def test_check_NOLA(self): + settings_pass = [ + ('boxcar', 10, 0), + ('boxcar', 10, 9), + ('boxcar', 10, 7), + ('bartlett', 51, 26), + ('bartlett', 51, 10), + ('hann', 256, 128), + ('hann', 256, 192), + ('hann', 256, 37), + ('blackman', 300, 200), + ('blackman', 300, 123), + (('tukey', 0.5), 256, 64), + (('tukey', 0.5), 256, 38), + ('hann', 256, 255), + ('hann', 256, 39), + ] + for setting in settings_pass: + msg = '{}, {}, {}'.format(*setting) + assert_equal(True, check_NOLA(*setting), err_msg=msg) + + w_fail = np.ones(16) + w_fail[::2] = 0 + settings_fail = [ + (w_fail, len(w_fail), len(w_fail) // 2), + ('hann', 64, 0), + ] + for setting in settings_fail: + msg = '{}, {}, {}'.format(*setting) + assert_equal(False, check_NOLA(*setting), err_msg=msg) + + def test_average_all_segments(self): + rng = np.random.RandomState(1234) + x = rng.randn(1024) + + fs = 1.0 + window = 'hann' + nperseg = 16 + noverlap = 8 + + # Compare twosided, because onesided welch doubles non-DC terms to + # account for power at negative frequencies. stft doesn't do this, + # because it breaks invertibility. + f, _, Z = stft(x, fs, window, nperseg, noverlap, padded=False, + return_onesided=False, boundary=None) + fw, Pw = welch(x, fs, window, nperseg, noverlap, return_onesided=False, + scaling='spectrum', detrend=False) + + assert_allclose(f, fw) + assert_allclose(np.mean(np.abs(Z)**2, axis=-1), Pw) + + def test_permute_axes(self): + rng = np.random.RandomState(1234) + x = rng.randn(1024) + + fs = 1.0 + window = 'hann' + nperseg = 16 + noverlap = 8 + + f1, t1, Z1 = stft(x, fs, window, nperseg, noverlap) + f2, t2, Z2 = stft(x.reshape((-1, 1, 1)), fs, window, nperseg, noverlap, + axis=0) + + t3, x1 = istft(Z1, fs, window, nperseg, noverlap) + t4, x2 = istft(Z2.T, fs, window, nperseg, noverlap, time_axis=0, + freq_axis=-1) + + assert_allclose(f1, f2) + assert_allclose(t1, t2) + assert_allclose(t3, t4) + assert_allclose(Z1, Z2[:, 0, 0, :]) + assert_allclose(x1, x2[:, 0, 0]) + + @pytest.mark.parametrize('scaling', ['spectrum', 'psd']) + def test_roundtrip_real(self, scaling): + rng = np.random.RandomState(1234) + + settings = [ + ('boxcar', 100, 10, 0), # Test no overlap + ('boxcar', 100, 10, 9), # Test high overlap + ('bartlett', 101, 51, 26), # Test odd nperseg + ('hann', 1024, 256, 128), # Test defaults + (('tukey', 0.5), 1152, 256, 64), # Test Tukey + ('hann', 1024, 256, 255), # Test overlapped hann + ] + + for window, N, nperseg, noverlap in settings: + t = np.arange(N) + x = 10*rng.randn(t.size) + + _, _, zz = stft(x, nperseg=nperseg, noverlap=noverlap, + window=window, detrend=None, padded=False, + scaling=scaling) + + tr, xr = istft(zz, nperseg=nperseg, noverlap=noverlap, + window=window, scaling=scaling) + + msg = f'{window}, {noverlap}' + assert_allclose(t, tr, err_msg=msg) + assert_allclose(x, xr, err_msg=msg) + + def test_roundtrip_not_nola(self): + rng = np.random.RandomState(1234) + + w_fail = np.ones(16) + w_fail[::2] = 0 + settings = [ + (w_fail, 256, len(w_fail), len(w_fail) // 2), + ('hann', 256, 64, 0), + ] + + for window, N, nperseg, noverlap in settings: + msg = f'{window}, {N}, {nperseg}, {noverlap}' + assert not check_NOLA(window, nperseg, noverlap), msg + + t = np.arange(N) + x = 10 * rng.randn(t.size) + + _, _, zz = stft(x, nperseg=nperseg, noverlap=noverlap, + window=window, detrend=None, padded=True, + boundary='zeros') + with pytest.warns(UserWarning, match='NOLA'): + tr, xr = istft(zz, nperseg=nperseg, noverlap=noverlap, + window=window, boundary=True) + + assert np.allclose(t, tr[:len(t)]), msg + assert not np.allclose(x, xr[:len(x)]), msg + + def test_roundtrip_nola_not_cola(self): + rng = np.random.RandomState(1234) + + settings = [ + ('boxcar', 100, 10, 3), # NOLA True, COLA False + ('bartlett', 101, 51, 37), # NOLA True, COLA False + ('hann', 1024, 256, 127), # NOLA True, COLA False + (('tukey', 0.5), 1152, 256, 14), # NOLA True, COLA False + ('hann', 1024, 256, 5), # NOLA True, COLA False + ] + + for window, N, nperseg, noverlap in settings: + msg = f'{window}, {nperseg}, {noverlap}' + assert check_NOLA(window, nperseg, noverlap), msg + assert not check_COLA(window, nperseg, noverlap), msg + + t = np.arange(N) + x = 10 * rng.randn(t.size) + + _, _, zz = stft(x, nperseg=nperseg, noverlap=noverlap, + window=window, detrend=None, padded=True, + boundary='zeros') + + tr, xr = istft(zz, nperseg=nperseg, noverlap=noverlap, + window=window, boundary=True) + + msg = f'{window}, {noverlap}' + assert_allclose(t, tr[:len(t)], err_msg=msg) + assert_allclose(x, xr[:len(x)], err_msg=msg) + + def test_roundtrip_float32(self): + rng = np.random.RandomState(1234) + + settings = [('hann', 1024, 256, 128)] + + for window, N, nperseg, noverlap in settings: + t = np.arange(N) + x = 10*rng.randn(t.size) + x = x.astype(np.float32) + + _, _, zz = stft(x, nperseg=nperseg, noverlap=noverlap, + window=window, detrend=None, padded=False) + + tr, xr = istft(zz, nperseg=nperseg, noverlap=noverlap, + window=window) + + msg = f'{window}, {noverlap}' + assert_allclose(t, t, err_msg=msg) + assert_allclose(x, xr, err_msg=msg, rtol=1e-4, atol=1e-5) + assert_(x.dtype == xr.dtype) + + @pytest.mark.parametrize('scaling', ['spectrum', 'psd']) + def test_roundtrip_complex(self, scaling): + rng = np.random.RandomState(1234) + + settings = [ + ('boxcar', 100, 10, 0), # Test no overlap + ('boxcar', 100, 10, 9), # Test high overlap + ('bartlett', 101, 51, 26), # Test odd nperseg + ('hann', 1024, 256, 128), # Test defaults + (('tukey', 0.5), 1152, 256, 64), # Test Tukey + ('hann', 1024, 256, 255), # Test overlapped hann + ] + + for window, N, nperseg, noverlap in settings: + t = np.arange(N) + x = 10*rng.randn(t.size) + 10j*rng.randn(t.size) + + _, _, zz = stft(x, nperseg=nperseg, noverlap=noverlap, + window=window, detrend=None, padded=False, + return_onesided=False, scaling=scaling) + + tr, xr = istft(zz, nperseg=nperseg, noverlap=noverlap, + window=window, input_onesided=False, + scaling=scaling) + + msg = f'{window}, {nperseg}, {noverlap}' + assert_allclose(t, tr, err_msg=msg) + assert_allclose(x, xr, err_msg=msg) + + # Check that asking for onesided switches to twosided + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", + "Input data is complex, switching to return_onesided=False", + UserWarning, + ) + _, _, zz = stft(x, nperseg=nperseg, noverlap=noverlap, + window=window, detrend=None, padded=False, + return_onesided=True, scaling=scaling) + + tr, xr = istft(zz, nperseg=nperseg, noverlap=noverlap, + window=window, input_onesided=False, scaling=scaling) + + msg = f'{window}, {nperseg}, {noverlap}' + assert_allclose(t, tr, err_msg=msg) + assert_allclose(x, xr, err_msg=msg) + + def test_roundtrip_boundary_extension(self): + rng = np.random.RandomState(1234) + + # Test against boxcar, since window is all ones, and thus can be fully + # recovered with no boundary extension + + settings = [ + ('boxcar', 100, 10, 0), # Test no overlap + ('boxcar', 100, 10, 9), # Test high overlap + ] + + for window, N, nperseg, noverlap in settings: + t = np.arange(N) + x = 10*rng.randn(t.size) + + _, _, zz = stft(x, nperseg=nperseg, noverlap=noverlap, + window=window, detrend=None, padded=True, + boundary=None) + + _, xr = istft(zz, noverlap=noverlap, window=window, boundary=False) + + for boundary in ['even', 'odd', 'constant', 'zeros']: + _, _, zz_ext = stft(x, nperseg=nperseg, noverlap=noverlap, + window=window, detrend=None, padded=True, + boundary=boundary) + + _, xr_ext = istft(zz_ext, noverlap=noverlap, window=window, + boundary=True) + + msg = f'{window}, {noverlap}, {boundary}' + assert_allclose(x, xr, err_msg=msg) + assert_allclose(x, xr_ext, err_msg=msg) + + def test_roundtrip_padded_signal(self): + rng = np.random.RandomState(1234) + + settings = [ + ('boxcar', 101, 10, 0), + ('hann', 1000, 256, 128), + ] + + for window, N, nperseg, noverlap in settings: + t = np.arange(N) + x = 10*rng.randn(t.size) + + _, _, zz = stft(x, nperseg=nperseg, noverlap=noverlap, + window=window, detrend=None, padded=True) + + tr, xr = istft(zz, noverlap=noverlap, window=window) + + msg = f'{window}, {noverlap}' + # Account for possible zero-padding at the end + assert_allclose(t, tr[:t.size], err_msg=msg) + assert_allclose(x, xr[:x.size], err_msg=msg) + + def test_roundtrip_padded_FFT(self): + rng = np.random.RandomState(1234) + + settings = [ + ('hann', 1024, 256, 128, 512), + ('hann', 1024, 256, 128, 501), + ('boxcar', 100, 10, 0, 33), + (('tukey', 0.5), 1152, 256, 64, 1024), + ] + + for window, N, nperseg, noverlap, nfft in settings: + t = np.arange(N) + x = 10*rng.randn(t.size) + xc = x*np.exp(1j*np.pi/4) + + # real signal + _, _, z = stft(x, nperseg=nperseg, noverlap=noverlap, nfft=nfft, + window=window, detrend=None, padded=True) + + # complex signal + _, _, zc = stft(xc, nperseg=nperseg, noverlap=noverlap, nfft=nfft, + window=window, detrend=None, padded=True, + return_onesided=False) + + tr, xr = istft(z, nperseg=nperseg, noverlap=noverlap, nfft=nfft, + window=window) + + tr, xcr = istft(zc, nperseg=nperseg, noverlap=noverlap, nfft=nfft, + window=window, input_onesided=False) + + msg = f'{window}, {noverlap}' + assert_allclose(t, tr, err_msg=msg) + assert_allclose(x, xr, err_msg=msg) + assert_allclose(xc, xcr, err_msg=msg) + + def test_axis_rolling(self): + rng = np.random.RandomState(1234) + + x_flat = rng.randn(1024) + _, _, z_flat = stft(x_flat) + + for a in range(3): + newshape = [1,]*3 + newshape[a] = -1 + x = x_flat.reshape(newshape) + + _, _, z_plus = stft(x, axis=a) # Positive axis index + _, _, z_minus = stft(x, axis=a-x.ndim) # Negative axis index + + assert_equal(z_flat, z_plus.squeeze(), err_msg=a) + assert_equal(z_flat, z_minus.squeeze(), err_msg=a-x.ndim) + + # z_flat has shape [n_freq, n_time] + + # Test vs. transpose + _, x_transpose_m = istft(z_flat.T, time_axis=-2, freq_axis=-1) + _, x_transpose_p = istft(z_flat.T, time_axis=0, freq_axis=1) + + assert_allclose(x_flat, x_transpose_m, err_msg='istft transpose minus') + assert_allclose(x_flat, x_transpose_p, err_msg='istft transpose plus') + + def test_roundtrip_scaling(self): + """Verify behavior of scaling parameter. """ + # Create 1024 sample cosine signal with amplitude 2: + X = np.zeros(513, dtype=complex) + X[256] = 1024 + x = np.fft.irfft(X) + power_x = sum(x**2) / len(x) # power of signal x is 2 + + # Calculate magnitude-scaled STFT: + Zs = stft(x, boundary='even', scaling='spectrum')[2] + + # Test round trip: + x1 = istft(Zs, boundary=True, scaling='spectrum')[1] + assert_allclose(x1, x) + + # For a Hann-windowed 256 sample length FFT, we expect a peak at + # frequency 64 (since it is 1/4 the length of X) with a height of 1 + # (half the amplitude). A Hann window of a perfectly centered sine has + # the magnitude [..., 0, 0, 0.5, 1, 0.5, 0, 0, ...]. + # Note that in this case the 'even' padding works for the beginning + # but not for the end of the STFT. + assert_allclose(abs(Zs[63, :-1]), 0.5) + assert_allclose(abs(Zs[64, :-1]), 1) + assert_allclose(abs(Zs[65, :-1]), 0.5) + # All other values should be zero: + Zs[63:66, :-1] = 0 + # Note since 'rtol' does not have influence here, atol needs to be set: + assert_allclose(Zs[:, :-1], 0, atol=np.finfo(Zs.dtype).resolution) + + # Calculate two-sided psd-scaled STFT: + # - using 'even' padding since signal is axis symmetric - this ensures + # stationary behavior on the boundaries + # - using the two-sided transform allows determining the spectral + # power by `sum(abs(Zp[:, k])**2) / len(f)` for the k-th time slot. + Zp = stft(x, return_onesided=False, boundary='even', scaling='psd')[2] + + # Calculate spectral power of Zd by summing over the frequency axis: + psd_Zp = np.sum(Zp.real**2 + Zp.imag**2, axis=0) / Zp.shape[0] + # Spectral power of Zp should be equal to the signal's power: + assert_allclose(psd_Zp, power_x) + + # Test round trip: + x1 = istft(Zp, input_onesided=False, boundary=True, scaling='psd')[1] + assert_allclose(x1, x) + + # The power of the one-sided psd-scaled STFT can be determined + # analogously (note that the two sides are not of equal shape): + Zp0 = stft(x, return_onesided=True, boundary='even', scaling='psd')[2] + + # Since x is real, its Fourier transform is conjugate symmetric, i.e., + # the missing 'second side' can be expressed through the 'first side': + Zp1 = np.conj(Zp0[-2:0:-1, :]) # 'second side' is conjugate reversed + assert_allclose(Zp[:129, :], Zp0) + assert_allclose(Zp[129:, :], Zp1) + + # Calculate the spectral power: + s2 = (np.sum(Zp0.real ** 2 + Zp0.imag ** 2, axis=0) + + np.sum(Zp1.real ** 2 + Zp1.imag ** 2, axis=0)) + psd_Zp01 = s2 / (Zp0.shape[0] + Zp1.shape[0]) + assert_allclose(psd_Zp01, power_x) + + # Test round trip: + x1 = istft(Zp0, input_onesided=True, boundary=True, scaling='psd')[1] + assert_allclose(x1, x) + + +class TestSampledSpectralRepresentations: + """Check energy/power relations from `Spectral Analysis` section in the user guide. + + A 32 sample cosine signal is used to compare the numerical to the expected results + stated in :ref:`tutorial_SpectralAnalysis` in + file ``doc/source/tutorial/signal.rst`` + """ + n: int = 32 #: number of samples + T: float = 1/16 #: sampling interval + a_ref: float = 3 #: amplitude of reference + l_a: int = 3 #: index in fft for defining frequency of test signal + + x_ref: np.ndarray #: reference signal + X_ref: np.ndarray #: two-sided FFT of x_ref + E_ref: float #: energy of signal + P_ref: float #: power of signal + + def setup_method(self): + """Create Cosine signal with amplitude a from spectrum. """ + f = rfftfreq(self.n, self.T) + X_ref = np.zeros_like(f) + self.l_a = 3 + X_ref[self.l_a] = self.a_ref/2 * self.n # set amplitude + self.x_ref = irfft(X_ref) + self.X_ref = fft(self.x_ref) + + # Closed form expression for continuous-time signal: + self.E_ref = self.tau * self.a_ref**2 / 2 # energy of signal + self.P_ref = self.a_ref**2 / 2 # power of signal + + @property + def tau(self) -> float: + """Duration of signal. """ + return self.n * self.T + + @property + def delta_f(self) -> float: + """Bin width """ + return 1 / (self.n * self.T) + + def test_reference_signal(self): + """Test energy and power formulas. """ + # Verify that amplitude is a: + assert_allclose(2*self.a_ref, np.ptp(self.x_ref), rtol=0.1) + # Verify that energy expression for sampled signal: + assert_allclose(self.T * sum(self.x_ref ** 2), self.E_ref) + + # Verify that spectral energy and power formulas are correct: + sum_X_ref_squared = sum(self.X_ref.real**2 + self.X_ref.imag**2) + assert_allclose(self.T/self.n * sum_X_ref_squared, self.E_ref) + assert_allclose(1/self.n**2 * sum_X_ref_squared, self.P_ref) + + def test_windowed_DFT(self): + """Verify spectral representations of windowed DFT. + + Furthermore, the scalings of `periodogram` and `welch` are verified. + """ + w = hann(self.n, sym=False) + c_amp, c_rms = abs(sum(w)), np.sqrt(sum(w.real**2 + w.imag**2)) + Xw = fft(self.x_ref*w) # unnormalized windowed DFT + + # Verify that the *spectrum* peak is consistent: + assert_allclose(self.tau * Xw[self.l_a] / c_amp, self.a_ref * self.tau / 2) + # Verify that the *amplitude spectrum* peak is consistent: + assert_allclose(Xw[self.l_a] / c_amp, self.a_ref/2) + + # Verify spectral power/energy equals signal's power/energy: + X_ESD = self.tau * self.T * abs(Xw / c_rms)**2 # Energy Spectral Density + X_PSD = self.T * abs(Xw / c_rms)**2 # Power Spectral Density + assert_allclose(self.delta_f * sum(X_ESD), self.E_ref) + assert_allclose(self.delta_f * sum(X_PSD), self.P_ref) + + # Verify scalings of periodogram: + kw = dict(fs=1/self.T, window=w, detrend=False, return_onesided=False) + _, P_mag = periodogram(self.x_ref, scaling='spectrum', **kw) + _, P_psd = periodogram(self.x_ref, scaling='density', **kw) + + # Verify that periodogram calculates a squared magnitude spectrum: + float_res = np.finfo(P_mag.dtype).resolution + assert_allclose(P_mag, abs(Xw/c_amp)**2, atol=float_res*max(P_mag)) + # Verify that periodogram calculates a PSD: + assert_allclose(P_psd, X_PSD, atol=float_res*max(P_psd)) + + # Ensure that scaling of welch is the same as of periodogram: + kw = dict(nperseg=len(self.x_ref), noverlap=0, **kw) + assert_allclose(welch(self.x_ref, scaling='spectrum', **kw)[1], P_mag, + atol=float_res*max(P_mag)) + assert_allclose(welch(self.x_ref, scaling='density', **kw)[1], P_psd, + atol=float_res*max(P_psd)) diff --git a/venv/lib/python3.12/site-packages/scipy/signal/tests/test_splines.py b/venv/lib/python3.12/site-packages/scipy/signal/tests/test_splines.py new file mode 100644 index 0000000000000000000000000000000000000000..22f703bf2787896b43f81173bb79abcecae46742 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/tests/test_splines.py @@ -0,0 +1,423 @@ +# pylint: disable=missing-docstring +import math +import numpy as np +import pytest +from scipy._lib._array_api import is_cupy, xp_assert_close, xp_default_dtype, concat_1d + +from scipy.signal._spline import ( + symiirorder1_ic, symiirorder2_ic_fwd, symiirorder2_ic_bwd) +from scipy.signal import symiirorder1, symiirorder2 + +skip_xp_backends = pytest.mark.skip_xp_backends +xfail_xp_backends = pytest.mark.xfail_xp_backends + + +def _compute_symiirorder2_bwd_hs(k, cs, rsq, omega): + cssq = cs * cs + k = np.abs(k) + rsupk = np.power(rsq, k / 2.0) + + c0 = (cssq * (1.0 + rsq) / (1.0 - rsq) / + (1 - 2 * rsq * np.cos(2 * omega) + rsq * rsq)) + gamma = (1.0 - rsq) / (1.0 + rsq) / np.tan(omega) + return c0 * rsupk * (np.cos(omega * k) + gamma * np.sin(omega * k)) + + +@pytest.mark.uses_xp_capabilities(False, reason="private") +class TestSymIIR: + + @skip_xp_backends(np_only=True, reason="_ic functions are private and numpy-only") + @pytest.mark.parametrize( + 'dtype', ['float32', 'float64', 'complex64', 'complex128']) + @pytest.mark.parametrize('precision', [-1.0, 0.7, 0.5, 0.25, 0.0075]) + def test_symiir1_ic(self, dtype, precision, xp): + + dtype = getattr(xp, dtype) + + c_precision = precision + if precision <= 0.0 or precision > 1.0: + if dtype in {xp.float32, xp.complex64}: + c_precision = 1e-6 + else: + c_precision = 1e-11 + + # Symmetrical initial conditions for a IIR filter of order 1 are: + # x[0] + z1 * \sum{k = 0}^{n - 1} x[k] * z1^k + + # Check the initial condition for a low-pass filter + # with coefficient b = 0.85 on a step signal. The initial condition is + # a geometric series: 1 + b * \sum_{k = 0}^{n - 1} u[k] b^k. + + # Finding the initial condition corresponds to + # 1. Computing the index n such that b**n < precision, which + # corresponds to ceil(log(precision) / log(b)) + # 2. Computing the geometric series until n, this can be computed + # using the partial sum formula: (1 - b**n) / (1 - b) + # This holds due to the input being a step signal. + b = 0.85 + n_exp = int(math.ceil(math.log(c_precision) / math.log(b))) + expected = xp.asarray([[(1 - b ** n_exp) / (1 - b)]], dtype=dtype) + expected = 1 + b * expected + + # Create a step signal of size n + 1 + x = xp.ones(n_exp + 1, dtype=dtype) + xp_assert_close(symiirorder1_ic(x, b, precision), expected, + atol=2e-6, rtol=2e-7) + + # Check the conditions for an exponential decreasing signal with base 2. + # Same conditions hold, as the product of 0.5^n * 0.85^n is + # still a geometric series + b_d = xp.asarray(b, dtype=dtype) + expected = np.asarray( + [[(1 - (0.5 * b_d) ** n_exp) / (1 - (0.5 * b_d))]], dtype=dtype) + expected = 1 + b_d * expected + + # Create an exponential decreasing signal of size n + 1 + x = 2 ** -xp.arange(n_exp + 1, dtype=dtype) + xp_assert_close(symiirorder1_ic(x, b, precision), expected, + atol=2e-6, rtol=2e-7) + + @skip_xp_backends(np_only=True, reason="_ic functions are private and numpy-only") + def test_symiir1_ic_fails(self, xp): + # Test that symiirorder1_ic fails whenever \sum_{n = 1}^{n} b^n > eps + b = 0.85 + # Create a step signal of size 100 + x = xp.ones(100, dtype=xp.float64) + + # Compute the closed form for the geometrical series + precision = 1 / (1 - b) + pytest.raises(ValueError, symiirorder1_ic, x, b, precision) + + # Test that symiirorder1_ic fails when |z1| >= 1 + pytest.raises(ValueError, symiirorder1_ic, x, 1.0, -1) + pytest.raises(ValueError, symiirorder1_ic, x, 2.0, -1) + + @skip_xp_backends( + cpu_only=True, exceptions=["cupy"], reason="internals are numpy-only" + ) + @xfail_xp_backends("cupy", reason="sum did not converge") + @skip_xp_backends("jax.numpy", reason="item assignment in tests") + @pytest.mark.parametrize( + 'dtype', ['float32', 'float64', 'complex64', 'complex128']) + @pytest.mark.parametrize('precision', [-1.0, 0.7, 0.5, 0.25, 0.0075]) + def test_symiir1(self, dtype, precision, xp): + dtype = getattr(xp, dtype) + + c_precision = precision + if precision <= 0.0 or precision > 1.0: + if dtype in {xp.float32, xp.complex64}: + c_precision = 1e-6 + else: + c_precision = 1e-11 + + # Test for a low-pass filter with c0 = 0.15 and z1 = 0.85 + # using a unit step over 200 samples. + c0 = 0.15 + z1 = 0.85 + n = 200 + signal = xp.ones(n, dtype=dtype) + + # Find the initial condition. See test_symiir1_ic for a detailed + # explanation + n_exp = int(math.ceil(math.log(c_precision) / math.log(z1))) + initial = xp.asarray((1 - z1 ** n_exp) / (1 - z1), dtype=dtype) + initial = 1 + z1 * initial + + # Forward pass + # The transfer function for the system 1 / (1 - z1 * z^-1) when + # applied to a unit step with initial conditions y0 is + # 1 / (1 - z1 * z^-1) * (z^-1 / (1 - z^-1) + y0) + + # Solving the inverse Z-transform for the given expression yields: + # y[n] = y0 * z1**n * u[n] + + # -z1 / (1 - z1) * z1**(k - 1) * u[k - 1] + + # 1 / (1 - z1) * u[k - 1] + # d is the Kronecker delta function, and u is the unit step + + # y0 * z1**n * u[n] + pos = xp.astype(xp.arange(n), dtype) + comp1 = initial * z1**pos + + # -z1 / (1 - z1) * z1**(k - 1) * u[k - 1] + comp2 = xp.zeros(n, dtype=dtype) + comp2[1:] = -z1 / (1 - z1) * z1**pos[:-1] + + # 1 / (1 - z1) * u[k - 1] + comp3 = xp.zeros(n, dtype=dtype) + comp3[1:] = 1 / (1 - z1) + + expected_fwd = comp1 + comp2 + comp3 + + # Reverse condition + sym_cond = -c0 / (z1 - 1.0) * expected_fwd[-1] + + # Backward pass + # The transfer function for the forward result is equivalent to + # the forward system times c0 / (1 - z1 * z). + + # Computing a closed form for the complete expression is difficult + # The result will be computed iteratively from the difference equation + exp_out = xp.zeros(n, dtype=dtype) + exp_out[0] = sym_cond + + for i in range(1, n): + exp_out[i] = c0 * expected_fwd[n - 1 - i] + z1 * exp_out[i - 1] + + exp_out = xp.flip(exp_out) + + out = symiirorder1(signal, c0, z1, precision) + xp_assert_close(out, exp_out, atol=4e-6, rtol=6e-7) + + @xfail_xp_backends("cupy", reason="sum did not converge") + @skip_xp_backends( + cpu_only=True, exceptions=["cupy"], reason="internals are numpy-only" + ) + @pytest.mark.parametrize('dtype', ['float32', 'float64']) + def test_symiir1_values(self, dtype, xp): + rng = np.random.RandomState(1234) + s = rng.uniform(size=16).astype(dtype) + dtype = getattr(xp, dtype) + s = xp.asarray(s) + res = symiirorder1(s, 0.5, 0.1) + + # values from scipy 1.9.1 + exp_res = xp.asarray([ + 0.14387447, 0.35166047, 0.29735238, 0.46295986, 0.45174927, + 0.19982875, 0.20355805, 0.47378628, 0.57232247, 0.51597393, + 0.25935107, 0.31438554, 0.41096728, 0.4190693 , 0.25812255, + 0.33671467], dtype=res.dtype) + atol = {xp.float64: 1e-15, xp.float32: 1e-7}[dtype] + xp_assert_close(res, exp_res, atol=atol) + + I1 = xp.asarray( + 1 + 1j, dtype=xp.result_type(s, xp.complex64) + ) + s = s * I1 + res = symiirorder1(s, 0.5, 0.1) + assert res.dtype == xp.complex64 if dtype == xp.float32 else xp.complex128 + xp_assert_close(res, I1 * exp_res, atol=atol) + + @skip_xp_backends(np_only=True, + reason="_initial_fwd functions are private and numpy-only") + @pytest.mark.parametrize( + 'dtype', ['float32', 'float64']) + @pytest.mark.parametrize('precision', [-1.0, 0.7, 0.5, 0.25, 0.0075]) + def test_symiir2_initial_fwd(self, dtype, precision, xp): + dtype = getattr(xp, dtype) + c_precision = precision + if precision <= 0.0 or precision > 1.0: + if dtype in {xp.float32, xp.complex64}: + c_precision = 1e-6 + else: + c_precision = 1e-11 + + # Compute the initial conditions for a order-two symmetrical low-pass + # filter with r = 0.5 and omega = pi / 3 for an unit step input. + r = xp.asarray(0.5, dtype=dtype) + omega = xp.asarray(np.pi / 3.0, dtype=dtype) + cs = 1 - 2 * r * xp.cos(omega) + r**2 + + # The index n for the initial condition is bound from 0 to the + # first position where sin(omega * (n + 2)) = 0 => omega * (n + 2) = pi + # For omega = pi / 3, the maximum initial condition occurs when + # sqrt(3) / 2 * r**n < precision. + # => n = log(2 * sqrt(3) / 3 * precision) / log(r) + ub = xp.ceil(xp.log(c_precision / xp.sin(omega)) / math.log(c_precision)) + lb = xp.ceil(math.pi / omega) - 2 + n_exp = min(ub, lb) + + # The forward initial condition for a filter of order two is: + # \frac{cs}{\sin(\omega)} \sum_{n = 0}^{N - 1} { + # r^(n + 1) \sin{\omega(n + 2)}} + cs + # The closed expression for this sum is: + # s[n] = 2 * r * np.cos(omega) - + # r**2 - r**(n + 2) * np.sin(omega * (n + 3)) / np.sin(omega) + + # r**(n + 3) * np.sin(omega * (n + 2)) / np.sin(omega) + cs + fwd_initial_1 = ( + cs + + 2 * r * xp.cos(omega) - + r**2 - + r**(n_exp + 2) * xp.sin(omega * (n_exp + 3)) / xp.sin(omega) + + r**(n_exp + 3) * xp.sin(omega * (n_exp + 2)) / xp.sin(omega)) + + # The second initial condition is given by + # s[n] = 1 / np.sin(omega) * ( + # r**2 * np.sin(3 * omega) - + # r**3 * np.sin(2 * omega) - + # r**(n + 3) * np.sin(omega * (n + 4)) + + # r**(n + 4) * np.sin(omega * (n + 3))) + ub = xp.ceil(xp.log(c_precision / xp.sin(omega)) / math.log(c_precision)) + lb = xp.ceil(xp.pi / omega) - 3 + n_exp = min(ub, lb) + + fwd_initial_2 = ( + cs + cs * 2 * r * xp.cos(omega) + + (r**2 * xp.sin(3 * omega) - + r**3 * xp.sin(2 * omega) - + r**(n_exp + 3) * xp.sin(omega * (n_exp + 4)) + + r**(n_exp + 4) * xp.sin(omega * (n_exp + 3))) / xp.sin(omega)) + + expected = concat_1d(xp, fwd_initial_1, fwd_initial_2)[None, :] + expected = xp.astype(expected, dtype) + + n = 100 + signal = np.ones(n, dtype=dtype) + + out = symiirorder2_ic_fwd(signal, r, omega, precision) + xp_assert_close(out, expected, atol=4e-6, rtol=6e-7) + + @skip_xp_backends(np_only=True, + reason="_initial_bwd functions are private and numpy-only") + @pytest.mark.parametrize( + 'dtype', ['float32', 'float64']) + @pytest.mark.parametrize('precision', [-1.0, 0.7, 0.5, 0.25, 0.0075]) + def test_symiir2_initial_bwd(self, dtype, precision, xp): + dtype = getattr(xp, dtype) + + c_precision = precision + if precision <= 0.0 or precision > 1.0: + if dtype in {xp.float32, xp.complex64}: + c_precision = 1e-6 + else: + c_precision = 1e-11 + + r = xp.asarray(0.5, dtype=dtype) + omega = xp.asarray(xp.pi / 3.0, dtype=dtype) + cs = 1 - 2 * r * xp.cos(omega) + r * r + a2 = 2 * r * xp.cos(omega) + a3 = -r * r + + n = 100 + signal = xp.ones(n, dtype=dtype) + + # Compute initial forward conditions + ic = symiirorder2_ic_fwd(signal, r, omega, precision) + out = xp.zeros(n + 2, dtype=dtype) + out[:2] = ic[0] + + # Apply the forward system cs / (1 - a2 * z^-1 - a3 * z^-2)) + for i in range(2, n + 2): + out[i] = cs * signal[i - 2] + a2 * out[i - 1] + a3 * out[i - 2] + + # Find the backward initial conditions + ic2 = xp.zeros(2, dtype=dtype) + idx = xp.arange(n) + + diff = (_compute_symiirorder2_bwd_hs(idx, cs, r * r, omega) + + _compute_symiirorder2_bwd_hs(idx + 1, cs, r * r, omega)) + ic2_0_all = np.cumsum(diff * out[:1:-1]) + pos = xp.nonzero(diff ** 2 < c_precision)[0] + ic2[0] = ic2_0_all[pos[0]] + + diff = (_compute_symiirorder2_bwd_hs(idx - 1, cs, r * r, omega) + + _compute_symiirorder2_bwd_hs(idx + 2, cs, r * r, omega)) + + ic2_1_all = xp.cumulative_sum(diff * out[:1:-1]) + pos = xp.nonzero(diff ** 2 < c_precision)[0] + ic2[1] = ic2_1_all[pos[0]] + + out_ic = symiirorder2_ic_bwd(out, r, omega, precision)[0] + xp_assert_close(out_ic, ic2, atol=4e-6, rtol=6e-7) + + @skip_xp_backends(cpu_only=True, reason="internals are numpy-only") + @skip_xp_backends("jax.numpy", reason="item assignment in tests") + @pytest.mark.parametrize( + 'dtype', ['float32', 'float64']) + @pytest.mark.parametrize('precision', [-1.0, 0.7, 0.5, 0.25, 0.0075]) + def test_symiir2(self, dtype, precision, xp): + dtype = getattr(xp, dtype) + + r = 0.5 + omega = math.pi / 3.0 + cs = 1 - 2 * r * math.cos(omega) + r * r + a2 = 2 * r * math.cos(omega) + a3 = -r * r + + n = 100 + signal = xp.ones(n, dtype=dtype) + + # Compute initial forward conditions + signal_np = np.asarray(signal) + ic = symiirorder2_ic_fwd(signal_np, r, omega, precision) + ic = xp.asarray(ic) + out1 = xp.zeros(n + 2, dtype=dtype) + out1[:2] = ic[0, :] + + # Apply the forward system cs / (1 - a2 * z^-1 - a3 * z^-2)) + for i in range(2, n + 2): + out1[i] = cs * signal[i - 2] + a2 * out1[i - 1] + a3 * out1[i - 2] + + # Find the backward initial conditions + ic2 = symiirorder2_ic_bwd(np.asarray(out1), r, omega, precision)[0] + ic2 = xp.asarray(ic2) + + # Apply the system cs / (1 - a2 * z - a3 * z^2)) in backwards + exp = xp.empty(n, dtype=dtype) + + exp[-2:] = xp.flip(ic2) + + for i in range(n - 3, -1, -1): + exp[i] = cs * out1[i] + a2 * exp[i + 1] + a3 * exp[i + 2] + + out = symiirorder2(signal, r, omega, precision) + xp_assert_close(out, exp, atol=4e-6, rtol=6e-7) + + @skip_xp_backends(cpu_only=True, exceptions=["cupy"], reason="C internals") + @pytest.mark.parametrize('dtyp', ['float32', 'float64']) + def test_symiir2_values(self, dtyp, xp): + rng = np.random.RandomState(1234) + s = rng.uniform(size=16).astype(dtyp) + s = xp.asarray(s) + + # cupy returns f64 for f32 inputs + dtype = xp.float64 if is_cupy(xp) else getattr(xp, dtyp) + + res = symiirorder2(s, 0.1, 0.1, precision=1e-10) + + # values from scipy 1.9.1 + exp_res = xp.asarray( + [0.26572609, 0.53408018, 0.51032696, 0.72115829, 0.69486885, + 0.3649055 , 0.37349478, 0.74165032, 0.89718521, 0.80582483, + 0.46758053, 0.51898709, 0.65025605, 0.65394321, 0.45273595, + 0.53539183], dtype=dtype + ) + + # The values in SciPy 1.14 agree with those in SciPy 1.9.1 to this + # accuracy only. Implementation differences are twofold: + # 1. boundary conditions are computed differently + # 2. the filter itself uses sosfilt instead of a hardcoded iteration + # The boundary conditions seem are tested separately (see + # test_symiir2_initial_{fwd,bwd} above, so the difference is likely + # due to a different way roundoff errors accumulate in the filter. + # In that respect, sosfilt is likely doing a better job. + xp_assert_close(res, exp_res, atol=2e-6) + + I1 = xp.asarray(1 + 1j, dtype=xp.result_type(s, xp.complex64)) + s = s * I1 + + with pytest.raises((TypeError, ValueError)): + res = symiirorder2(s, 0.5, 0.1) + + @skip_xp_backends(cpu_only=True, exceptions=["cupy"], reason="C internals") + @xfail_xp_backends("cupy", reason="cupy does not accept integer arrays") + def test_symiir1_integer_input(self, xp): + s = xp.where( + xp.astype(xp.arange(100) % 2, xp.bool), + xp.asarray(-1), + xp.asarray(1), + ) + expected = symiirorder1(xp.astype(s, xp_default_dtype(xp)), 0.5, 0.5) + out = symiirorder1(s, 0.5, 0.5) + xp_assert_close(out, expected) + + @skip_xp_backends(cpu_only=True, exceptions=["cupy"], reason="C internals") + @xfail_xp_backends("cupy", reason="cupy does not accept integer arrays") + def test_symiir2_integer_input(self, xp): + s = xp.where( + xp.astype(xp.arange(100) % 2, xp.bool), + xp.asarray(-1), + xp.asarray(1), + ) + expected = symiirorder2(xp.astype(s, xp_default_dtype(xp)), 0.5, xp.pi / 3.0) + out = symiirorder2(s, 0.5, xp.pi / 3.0) + xp_assert_close(out, expected) diff --git a/venv/lib/python3.12/site-packages/scipy/signal/tests/test_upfirdn.py b/venv/lib/python3.12/site-packages/scipy/signal/tests/test_upfirdn.py new file mode 100644 index 0000000000000000000000000000000000000000..57d8f0accc86c7bc1e93f2f330be0db2d02d0c7f --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/tests/test_upfirdn.py @@ -0,0 +1,335 @@ +# Code adapted from "upfirdn" python library with permission: +# +# Copyright (c) 2009, Motorola, Inc +# +# All Rights Reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of Motorola nor the names of its contributors may be +# used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +import numpy as np +from itertools import product + +from pytest import raises as assert_raises +import pytest + +from scipy._external import array_api_extra as xpx +from scipy._lib._array_api import ( + xp_assert_close, array_namespace, _xp_copy_to_numpy, is_cupy, + make_xp_test_case +) +from scipy._external.array_api_compat import numpy as np_compat +from scipy.signal import upfirdn, firwin +from scipy.signal._upfirdn import _output_len, _upfirdn_modes +from scipy.signal._upfirdn_apply import _pad_test + +skip_xp_backends = pytest.mark.skip_xp_backends + + + +def upfirdn_naive(x, h, up=1, down=1): + """Naive upfirdn processing in Python. + + Note: arg order (x, h) differs to facilitate apply_along_axis use. + """ + x = np.asarray(x) + h = np.asarray(h) + out = np.zeros(len(x) * up, x.dtype) + out[::up] = x + out = np.convolve(h, out)[::down][:_output_len(len(h), len(x), up, down)] + return out + + +class UpFIRDnCase: + """Test _UpFIRDn object""" + def __init__(self, up, down, h, x_dtype, *, xp=None): + if xp is None: + xp = np_compat + self.up = up + self.down = down + self.h = np.atleast_1d(h) + self.x_dtype = x_dtype + self.rng = np.random.RandomState(17) + self.xp = xp + + def __call__(self): + # tiny signal + self.scrub(np.ones(1, self.x_dtype)) + # ones + self.scrub(np.ones(10, self.x_dtype)) # ones + # randn + x = self.rng.randn(10).astype(self.x_dtype) + if self.x_dtype in (np.complex64, np.complex128): + x += 1j * self.rng.randn(10) + self.scrub(x) + # ramp + self.scrub(np.arange(10).astype(self.x_dtype)) + # 3D, random + if is_cupy(self.xp): + # ndim > 2 is unsupported in CuPy. + return + size = (2, 3, 5) + x = self.rng.randn(*size).astype(self.x_dtype) + if self.x_dtype in (np.complex64, np.complex128): + x += 1j * self.rng.randn(*size) + for axis in range(len(size)): + self.scrub(x, axis=axis) + x = x[:, ::2, 1::3].T + for axis in range(len(size)): + self.scrub(x, axis=axis) + + def scrub(self, x, axis=-1): + xp = self.xp + yr = np.apply_along_axis(upfirdn_naive, axis, x, + self.h, self.up, self.down) + want_len = _output_len(len(self.h), x.shape[axis], self.up, self.down) + assert yr.shape[axis] == want_len + y = upfirdn(xp.asarray(self.h), xp.asarray(x), self.up, self.down, + axis=axis) + assert y.shape[axis] == want_len + assert y.shape == yr.shape + dtypes = (self.h.dtype, x.dtype) + if all(d == np.complex64 for d in dtypes): + assert y.dtype == xp.complex64 + elif np.complex64 in dtypes and np.float32 in dtypes: + assert y.dtype == xp.complex64 + elif all(d == np.float32 for d in dtypes): + assert y.dtype == xp.float32 + elif np.complex128 in dtypes or np.complex64 in dtypes: + assert y.dtype == xp.complex128 + else: + assert y.dtype == xp.float64 + yr = xp.asarray(yr, dtype=y.dtype) + xp_assert_close(yr, y) + + +_UPFIRDN_TYPES = ("int64", "float32", "complex64", "float64", "complex128") + + +@make_xp_test_case(upfirdn) +class TestUpfirdn: + + @skip_xp_backends(np_only=True, reason="enough to only test on numpy") + def test_valid_input(self, xp): + assert_raises(ValueError, upfirdn, [1], [1], 1, 0) # up or down < 1 + assert_raises(ValueError, upfirdn, [], [1], 1, 1) # h.ndim != 1 + assert_raises(ValueError, upfirdn, [[1]], [1], 1, 1) + + @pytest.mark.parametrize('len_h', [1, 2, 3, 4, 5]) + @pytest.mark.parametrize('len_x', [1, 2, 3, 4, 5]) + def test_singleton(self, len_h, len_x, xp): + # gh-9844: lengths producing expected outputs + h = xp.zeros(len_h) + h = xpx.at(h)[len_h // 2].set(1.) # make h a delta + x = xp.ones(len_x) + y = upfirdn(h, x, 1, 1) + want = xpx.pad(x, (len_h // 2, (len_h - 1) // 2), 'constant', xp=xp) + xp_assert_close(y, want) + + def test_shift_x(self, xp): + # gh-9844: shifted x can change values? + y = upfirdn(xp.asarray([1, 1]), xp.asarray([1.]), 1, 1) + xp_assert_close( + y, xp.asarray([1.0, 1.0], dtype=xp.float64) # was [0, 1] in the issue + ) + y = upfirdn(xp.asarray([1, 1]), xp.asarray([0., 1.]), 1, 1) + xp_assert_close(y, xp.asarray([0.0, 1.0, 1.0], dtype=xp.float64)) + + # A bunch of lengths/factors chosen because they exposed differences + # between the "old way" and new way of computing length, and then + # got `expected` from MATLAB + @pytest.mark.parametrize('len_h, len_x, up, down, expected', [ + (2, 2, 5, 2, [1, 0, 0, 0]), + (2, 3, 6, 3, [1, 0, 1, 0, 1]), + (2, 4, 4, 3, [1, 0, 0, 0, 1]), + (3, 2, 6, 2, [1, 0, 0, 1, 0]), + (4, 11, 3, 5, [1, 0, 0, 1, 0, 0, 1]), + ]) + def test_length_factors(self, len_h, len_x, up, down, expected, xp): + # gh-9844: weird factors + h = xp.zeros(len_h) + h = xpx.at(h)[0].set(1.) + x = xp.ones(len_x, dtype=xp.float64) + y = upfirdn(h, x, up, down) + expected = xp.asarray(expected, dtype=xp.float64) + xp_assert_close(y, expected) + + @pytest.mark.parametrize( + 'dtype', ["int64", "float32", "complex64", "float64", "complex128"] + ) + @pytest.mark.parametrize('down, want_len', [ # lengths from MATLAB + (2, 5015), + (11, 912), + (79, 127), + ]) + def test_vs_convolve(self, down, want_len, dtype, xp): + # Check that up=1.0 gives same answer as convolve + slicing + random_state = np.random.RandomState(17) + size = 10000 + + np_dtype = getattr(np, dtype) + x = random_state.randn(size).astype(np_dtype) + if np_dtype in (np.complex64, np.complex128): + x += 1j * random_state.randn(size) + + dtype = getattr(xp, dtype) + x = xp.asarray(x, dtype=dtype) + + h = xp.asarray(firwin(31, 1. / down, window='hamming')) + yl = xp.asarray( + upfirdn_naive(_xp_copy_to_numpy(x), _xp_copy_to_numpy(h), 1, down) + ) + y = upfirdn(h, x, up=1, down=down) + assert y.shape == (want_len,) + assert yl.shape[0] == y.shape[0] + xp_assert_close(yl, y, atol=1e-7, rtol=1e-7) + + @pytest.mark.parametrize('x_dtype', _UPFIRDN_TYPES) + @pytest.mark.parametrize('h', (1., 1j)) + @pytest.mark.parametrize('up, down', [(1, 1), (2, 2), (3, 2), (2, 3)]) + def test_vs_naive_delta(self, x_dtype, h, up, down, xp): + UpFIRDnCase(up, down, h, x_dtype, xp=xp)() + + @pytest.mark.parametrize('x_dtype', _UPFIRDN_TYPES) + @pytest.mark.parametrize('h_dtype', _UPFIRDN_TYPES) + @pytest.mark.parametrize('p_max, q_max', + list(product((10, 100), (10, 100)))) + def test_vs_naive(self, x_dtype, h_dtype, p_max, q_max, xp): + tests = self._random_factors(p_max, q_max, h_dtype, x_dtype, xp=xp) + for test in tests: + test() + + def _random_factors(self, p_max, q_max, h_dtype, x_dtype, *, xp): + n_rep = 3 + longest_h = 25 + random_state = np.random.RandomState(17) + tests = [] + + for _ in range(n_rep): + # Randomize the up/down factors somewhat + p_add = q_max if p_max > q_max else 1 + q_add = p_max if q_max > p_max else 1 + p = random_state.randint(p_max) + p_add + q = random_state.randint(q_max) + q_add + + # Generate random FIR coefficients + len_h = random_state.randint(longest_h) + 1 + h = np.atleast_1d(random_state.randint(len_h)) + h = h.astype(h_dtype) + if h_dtype is complex: + h += 1j * random_state.randint(len_h) + + tests.append(UpFIRDnCase(p, q, h, x_dtype, xp=xp)) + + return tests + + @pytest.mark.parametrize('mode', _upfirdn_modes) + def test_extensions(self, mode, xp): + """Test vs. manually computed results for modes not in numpy's pad.""" + x = np.asarray([1, 2, 3, 1], dtype=np.float64) + npre, npost = 6, 6 + y = _pad_test(x, npre=npre, npost=npost, mode=mode) + + x = xp.asarray(x) + y = xp.asarray(y) + if mode == 'antisymmetric': + y_expected = xp.asarray( + [3.0, 1, -1, -3, -2, -1, 1, 2, 3, 1, -1, -3, -2, -1, 1, 2]) + elif mode == 'antireflect': + y_expected = xp.asarray( + [1.0, 2, 3, 1, -1, 0, 1, 2, 3, 1, -1, 0, 1, 2, 3, 1]) + elif mode == 'smooth': + y_expected = xp.asarray( + [-5.0, -4, -3, -2, -1, 0, 1, 2, 3, 1, -1, -3, -5, -7, -9, -11]) + elif mode == "line": + lin_slope = (x[-1] - x[0]) / (x.shape[0] - 1) + left = x[0] + xp.arange(-npre, 0, 1, dtype=xp.float64) * lin_slope + right = x[-1] + xp.arange(1, npost + 1, dtype=xp.float64) * lin_slope + concat = array_namespace(left).concat + y_expected = concat((left, x, right)) + else: + y_expected = np.pad(_xp_copy_to_numpy(x), (npre, npost), mode=mode) + y_expected = xp.asarray(y_expected) + + y_expected = xp.asarray(y_expected, dtype=xp.float64) + xp_assert_close(y, y_expected) + + @pytest.mark.parametrize( + 'size, h_len, mode, dtype', + list(product( + [8], + [4, 5, 26], # include cases with h_len > 2*size + _upfirdn_modes, + ["float32", "float64", "complex64", "complex128"], + )) + ) + def test_modes(self, size, h_len, mode, dtype, xp): + if is_cupy(xp) and mode != "constant": + pytest.skip(reason="only mode='constant' supported by CuPy") + dtype_np = getattr(np, dtype) + dtype_xp = getattr(xp, dtype) + + random_state = np.random.RandomState(5) + x = random_state.randn(size).astype(dtype_np) + if dtype in ("complex64", "complex128"): + x += 1j * random_state.randn(size) + h = np.arange(1, 1 + h_len, dtype=x.real.dtype) + + x = xp.asarray(x, dtype=dtype_xp) + h = xp.asarray(h) + + y = upfirdn(h, x, up=1, down=1, mode=mode) + # expected result: pad the input, filter with zero padding, then crop + npad = h_len - 1 + if mode in ['antisymmetric', 'antireflect', 'smooth', 'line']: + # use _pad_test test function for modes not supported by np.pad. + xpad = _pad_test(_xp_copy_to_numpy(x), npre=npad, npost=npad, mode=mode) + else: + xpad = np.pad(_xp_copy_to_numpy(x), npad, mode=mode) + + xpad = xp.asarray(xpad) + ypad = upfirdn(h, xpad, up=1, down=1, mode='constant') + y_expected = ypad[npad:-npad] + + atol = rtol = xp.finfo(dtype_xp).eps * 1e2 + xp_assert_close(y, y_expected, atol=atol, rtol=rtol) + + +@make_xp_test_case(upfirdn) +def test_output_len_long_input(xp): + # Regression test for gh-17375. On Windows, a large enough input + # that should have been well within the capabilities of 64 bit integers + # would result in a 32 bit overflow because of a bug in Cython 0.29.32. + len_h = 1001 + in_len = 10**8 + up = 320 + down = 441 + out_len = _output_len(len_h, in_len, up, down) + # The expected value was computed "by hand" from the formula + # (((in_len - 1) * up + len_h) - 1) // down + 1 + assert out_len == 72562360 diff --git a/venv/lib/python3.12/site-packages/scipy/signal/tests/test_waveforms.py b/venv/lib/python3.12/site-packages/scipy/signal/tests/test_waveforms.py new file mode 100644 index 0000000000000000000000000000000000000000..ba57f52dcd6253c69ab67b8f2ba717b93372e82d --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/tests/test_waveforms.py @@ -0,0 +1,476 @@ +import numpy as np +import pytest +from pytest import raises as assert_raises +from scipy._lib._array_api import ( + assert_almost_equal, xp_assert_equal, xp_assert_close, _xp_copy_to_numpy, + make_xp_test_case, xp_default_dtype +) + +import scipy.signal._waveforms as waveforms +from scipy.signal import square, sawtooth # type:ignore[attr-defined] + + +# These chirp_* functions are the instantaneous frequencies of the signals +# returned by chirp(). + +def chirp_linear(t, f0, f1, t1): + f = f0 + (f1 - f0) * t / t1 + return f + + +def chirp_quadratic(t, f0, f1, t1, vertex_zero=True): + if vertex_zero: + f = f0 + (f1 - f0) * t**2 / t1**2 + else: + f = f1 - (f1 - f0) * (t1 - t)**2 / t1**2 + return f + + +def chirp_geometric(t, f0, f1, t1): + f = f0 * (f1/f0)**(t/t1) + return f + + +def chirp_hyperbolic(t, f0, f1, t1): + f = f0*f1*t1 / ((f0 - f1)*t + f1*t1) + return f + + +def compute_frequency(t, theta): + """ + Compute theta'(t)/(2*pi), where theta'(t) is the derivative of theta(t). + """ + # Assume theta and t are 1-D NumPy arrays. + # Assume that t is uniformly spaced. + dt = t[1] - t[0] + f = np.diff(theta)/(2*np.pi) / dt + tf = 0.5*(t[1:] + t[:-1]) + return tf, f + + +class TestChirp: + + def test_linear_at_zero(self): + w = waveforms.chirp(t=0, f0=1.0, f1=2.0, t1=1.0, method='linear') + assert_almost_equal(w, 1.0) + + def test_linear_freq_01(self): + method = 'linear' + f0 = 1.0 + f1 = 2.0 + t1 = 1.0 + t = np.linspace(0, t1, 100) + phase = waveforms._chirp_phase(t, f0, t1, f1, method) + tf, f = compute_frequency(t, phase) + abserr = np.max(np.abs(f - chirp_linear(tf, f0, f1, t1))) + assert abserr < 1e-6 + + def test_linear_freq_02(self): + method = 'linear' + f0 = 200.0 + f1 = 100.0 + t1 = 10.0 + t = np.linspace(0, t1, 100) + phase = waveforms._chirp_phase(t, f0, t1, f1, method) + tf, f = compute_frequency(t, phase) + abserr = np.max(np.abs(f - chirp_linear(tf, f0, f1, t1))) + assert abserr < 1e-6 + + def test_linear_complex_power(self): + method = 'linear' + f0 = 1.0 + f1 = 2.0 + t1 = 1.0 + t = np.linspace(0, t1, 100) + w_real = waveforms.chirp(t, f0, t1, f1, method, complex=False) + w_complex = waveforms.chirp(t, f0, t1, f1, method, complex=True) + w_pwr_r = np.var(w_real) + w_pwr_c = np.var(w_complex) + + # Making sure that power of the real part is not affected with + # complex conversion operation + err = w_pwr_r - np.real(w_pwr_c) + + assert(err < 1e-6) + + def test_linear_complex_at_zero(self): + w = waveforms.chirp(t=0, f0=-10.0, f1=1.0, t1=1.0, method='linear', + complex=True) + xp_assert_close(w, 1.0+0.0j) # dtype must match + + def test_quadratic_at_zero(self): + w = waveforms.chirp(t=0, f0=1.0, f1=2.0, t1=1.0, method='quadratic') + assert_almost_equal(w, 1.0) + + def test_quadratic_at_zero2(self): + w = waveforms.chirp(t=0, f0=1.0, f1=2.0, t1=1.0, method='quadratic', + vertex_zero=False) + assert_almost_equal(w, 1.0) + + def test_quadratic_complex_at_zero(self): + w = waveforms.chirp(t=0, f0=-1.0, f1=2.0, t1=1.0, method='quadratic', + complex=True) + xp_assert_close(w, 1.0+0j) + + def test_quadratic_freq_01(self): + method = 'quadratic' + f0 = 1.0 + f1 = 2.0 + t1 = 1.0 + t = np.linspace(0, t1, 2000) + phase = waveforms._chirp_phase(t, f0, t1, f1, method) + tf, f = compute_frequency(t, phase) + abserr = np.max(np.abs(f - chirp_quadratic(tf, f0, f1, t1))) + assert abserr < 1e-6 + + def test_quadratic_freq_02(self): + method = 'quadratic' + f0 = 20.0 + f1 = 10.0 + t1 = 10.0 + t = np.linspace(0, t1, 2000) + phase = waveforms._chirp_phase(t, f0, t1, f1, method) + tf, f = compute_frequency(t, phase) + abserr = np.max(np.abs(f - chirp_quadratic(tf, f0, f1, t1))) + assert abserr < 1e-6 + + def test_logarithmic_at_zero(self): + w = waveforms.chirp(t=0, f0=1.0, f1=2.0, t1=1.0, method='logarithmic') + assert_almost_equal(w, 1.0) + + def test_logarithmic_freq_01(self): + method = 'logarithmic' + f0 = 1.0 + f1 = 2.0 + t1 = 1.0 + t = np.linspace(0, t1, 10000) + phase = waveforms._chirp_phase(t, f0, t1, f1, method) + tf, f = compute_frequency(t, phase) + abserr = np.max(np.abs(f - chirp_geometric(tf, f0, f1, t1))) + assert abserr < 1e-6 + + def test_logarithmic_freq_02(self): + method = 'logarithmic' + f0 = 200.0 + f1 = 100.0 + t1 = 10.0 + t = np.linspace(0, t1, 10000) + phase = waveforms._chirp_phase(t, f0, t1, f1, method) + tf, f = compute_frequency(t, phase) + abserr = np.max(np.abs(f - chirp_geometric(tf, f0, f1, t1))) + assert abserr < 1e-6 + + def test_logarithmic_freq_03(self): + method = 'logarithmic' + f0 = 100.0 + f1 = 100.0 + t1 = 10.0 + t = np.linspace(0, t1, 10000) + phase = waveforms._chirp_phase(t, f0, t1, f1, method) + tf, f = compute_frequency(t, phase) + abserr = np.max(np.abs(f - chirp_geometric(tf, f0, f1, t1))) + assert abserr < 1e-6 + + def test_hyperbolic_at_zero(self): + w = waveforms.chirp(t=0, f0=10.0, f1=1.0, t1=1.0, method='hyperbolic') + assert_almost_equal(w, 1.0) + + def test_hyperbolic_freq_01(self): + method = 'hyperbolic' + t1 = 1.0 + t = np.linspace(0, t1, 10000) + # f0 f1 + cases = [[10.0, 1.0], + [1.0, 10.0], + [-10.0, -1.0], + [-1.0, -10.0]] + for f0, f1 in cases: + phase = waveforms._chirp_phase(t, f0, t1, f1, method) + tf, f = compute_frequency(t, phase) + expected = chirp_hyperbolic(tf, f0, f1, t1) + xp_assert_close(f, expected, atol=1e-7) + + def test_hyperbolic_zero_freq(self): + # f0=0 or f1=0 must raise a ValueError. + method = 'hyperbolic' + t1 = 1.0 + t = np.linspace(0, t1, 5) + assert_raises(ValueError, waveforms.chirp, t, 0, t1, 1, method) + assert_raises(ValueError, waveforms.chirp, t, 1, t1, 0, method) + + def test_unknown_method(self): + method = "foo" + f0 = 10.0 + f1 = 20.0 + t1 = 1.0 + t = np.linspace(0, t1, 10) + assert_raises(ValueError, waveforms.chirp, t, f0, t1, f1, method) + + def test_integer_t1(self): + f0 = 10.0 + f1 = 20.0 + t = np.linspace(-1, 1, 11) + t1 = 3.0 + float_result = waveforms.chirp(t, f0, t1, f1) + t1 = 3 + int_result = waveforms.chirp(t, f0, t1, f1) + err_msg = "Integer input 't1=3' gives wrong result" + xp_assert_equal(int_result, float_result, err_msg=err_msg) + + def test_integer_f0(self): + f1 = 20.0 + t1 = 3.0 + t = np.linspace(-1, 1, 11) + f0 = 10.0 + float_result = waveforms.chirp(t, f0, t1, f1) + f0 = 10 + int_result = waveforms.chirp(t, f0, t1, f1) + err_msg = "Integer input 'f0=10' gives wrong result" + xp_assert_equal(int_result, float_result, err_msg=err_msg) + + def test_integer_f1(self): + f0 = 10.0 + t1 = 3.0 + t = np.linspace(-1, 1, 11) + f1 = 20.0 + float_result = waveforms.chirp(t, f0, t1, f1) + f1 = 20 + int_result = waveforms.chirp(t, f0, t1, f1) + err_msg = "Integer input 'f1=20' gives wrong result" + xp_assert_equal(int_result, float_result, err_msg=err_msg) + + def test_integer_all(self): + f0 = 10 + t1 = 3 + f1 = 20 + t = np.linspace(-1, 1, 11) + float_result = waveforms.chirp(t, float(f0), float(t1), float(f1)) + int_result = waveforms.chirp(t, f0, t1, f1) + err_msg = "Integer input 'f0=10, t1=3, f1=20' gives wrong result" + xp_assert_equal(int_result, float_result, err_msg=err_msg) + + +class TestSweepPoly: + + def test_sweep_poly_quad1(self): + p = np.poly1d([1.0, 0.0, 1.0]) + t = np.linspace(0, 3.0, 10000) + phase = waveforms._sweep_poly_phase(t, p) + tf, f = compute_frequency(t, phase) + expected = p(tf) + abserr = np.max(np.abs(f - expected)) + assert abserr < 1e-6 + + def test_sweep_poly_const(self): + p = np.poly1d(2.0) + t = np.linspace(0, 3.0, 10000) + phase = waveforms._sweep_poly_phase(t, p) + tf, f = compute_frequency(t, phase) + expected = p(tf) + abserr = np.max(np.abs(f - expected)) + assert abserr < 1e-6 + + def test_sweep_poly_linear(self): + p = np.poly1d([-1.0, 10.0]) + t = np.linspace(0, 3.0, 10000) + phase = waveforms._sweep_poly_phase(t, p) + tf, f = compute_frequency(t, phase) + expected = p(tf) + abserr = np.max(np.abs(f - expected)) + assert abserr < 1e-6 + + def test_sweep_poly_quad2(self): + p = np.poly1d([1.0, 0.0, -2.0]) + t = np.linspace(0, 3.0, 10000) + phase = waveforms._sweep_poly_phase(t, p) + tf, f = compute_frequency(t, phase) + expected = p(tf) + abserr = np.max(np.abs(f - expected)) + assert abserr < 1e-6 + + def test_sweep_poly_cubic(self): + p = np.poly1d([2.0, 1.0, 0.0, -2.0]) + t = np.linspace(0, 2.0, 10000) + phase = waveforms._sweep_poly_phase(t, p) + tf, f = compute_frequency(t, phase) + expected = p(tf) + abserr = np.max(np.abs(f - expected)) + assert abserr < 1e-6 + + def test_sweep_poly_cubic2(self): + """Use an array of coefficients instead of a poly1d.""" + p = np.array([2.0, 1.0, 0.0, -2.0]) + t = np.linspace(0, 2.0, 10000) + phase = waveforms._sweep_poly_phase(t, p) + tf, f = compute_frequency(t, phase) + expected = np.poly1d(p)(tf) + abserr = np.max(np.abs(f - expected)) + assert abserr < 1e-6 + + def test_sweep_poly_cubic3(self): + """Use a list of coefficients instead of a poly1d.""" + p = [2.0, 1.0, 0.0, -2.0] + t = np.linspace(0, 2.0, 10000) + phase = waveforms._sweep_poly_phase(t, p) + tf, f = compute_frequency(t, phase) + expected = np.poly1d(p)(tf) + abserr = np.max(np.abs(f - expected)) + assert abserr < 1e-6 + + +class TestGaussPulse: + + def test_integer_fc(self): + float_result = waveforms.gausspulse('cutoff', fc=1000.0) + int_result = waveforms.gausspulse('cutoff', fc=1000) + err_msg = "Integer input 'fc=1000' gives wrong result" + xp_assert_equal(int_result, float_result, err_msg=err_msg) + + def test_integer_bw(self): + float_result = waveforms.gausspulse('cutoff', bw=1.0) + int_result = waveforms.gausspulse('cutoff', bw=1) + err_msg = "Integer input 'bw=1' gives wrong result" + xp_assert_equal(int_result, float_result, err_msg=err_msg) + + def test_integer_bwr(self): + float_result = waveforms.gausspulse('cutoff', bwr=-6.0) + int_result = waveforms.gausspulse('cutoff', bwr=-6) + err_msg = "Integer input 'bwr=-6' gives wrong result" + xp_assert_equal(int_result, float_result, err_msg=err_msg) + + def test_integer_tpr(self): + float_result = waveforms.gausspulse('cutoff', tpr=-60.0) + int_result = waveforms.gausspulse('cutoff', tpr=-60) + err_msg = "Integer input 'tpr=-60' gives wrong result" + xp_assert_equal(int_result, float_result, err_msg=err_msg) + + +class TestUnitImpulse: + + def test_no_index(self): + xp_assert_equal(waveforms.unit_impulse(7), + np.asarray([1.0, 0, 0, 0, 0, 0, 0])) + xp_assert_equal(waveforms.unit_impulse((3, 3)), + np.asarray([[1.0, 0, 0], [0, 0, 0], [0, 0, 0]])) + + def test_index(self): + xp_assert_equal(waveforms.unit_impulse(10, 3), + np.asarray([0.0, 0, 0, 1, 0, 0, 0, 0, 0, 0])) + xp_assert_equal(waveforms.unit_impulse((3, 3), (1, 1)), + np.asarray([[0.0, 0, 0], [0, 1, 0], [0, 0, 0]])) + + # Broadcasting + imp = waveforms.unit_impulse((4, 4), 2) + xp_assert_equal(imp, np.asarray([[0.0, 0, 0, 0], + [0.0, 0, 0, 0], + [0.0, 0, 1, 0], + [0.0, 0, 0, 0]])) + + def test_mid(self): + xp_assert_equal(waveforms.unit_impulse((3, 3), 'mid'), + np.asarray([[0.0, 0, 0], [0, 1, 0], [0, 0, 0]])) + xp_assert_equal(waveforms.unit_impulse(9, 'mid'), + np.asarray([0.0, 0, 0, 0, 1, 0, 0, 0, 0])) + + def test_dtype(self): + imp = waveforms.unit_impulse(7) + assert np.issubdtype(imp.dtype, np.floating) + + imp = waveforms.unit_impulse(5, 3, dtype=int) + assert np.issubdtype(imp.dtype, np.integer) + + imp = waveforms.unit_impulse((5, 2), (3, 1), dtype=complex) + assert np.issubdtype(imp.dtype, np.complexfloating) + + +@make_xp_test_case(sawtooth) +class TestSawtoothWaveform: + @pytest.mark.xfail_xp_backends("cupy", reason="cupy/cupy/issues/9541") + @pytest.mark.parametrize("t_dtype", ["float32", "float64"]) + @pytest.mark.parametrize("width_dtype", ["float32", "float64"]) + def test_dtype(self, t_dtype, width_dtype, xp): + t_dtype = getattr(xp, t_dtype) + width_dtype = getattr(xp, width_dtype) + waveform = sawtooth( + xp.asarray(1, dtype=t_dtype), width=xp.asarray(1, dtype=width_dtype) + ) + assert waveform.dtype == xp.result_type(t_dtype, width_dtype) + + def test_periodic(self, xp): + t = xp.linspace(0, 2*xp.pi, 100, dtype=xp.float64) + y1 = sawtooth(t, width=0.4) + y2 = sawtooth(t + 2*xp.pi, width=0.4) + xp_assert_close(y1, y2) + + def test_known_values(self, xp): + eps = xp.finfo(xp_default_dtype(xp)).eps + t = xp.asarray([0, xp.pi, 2*xp.pi - 10*eps, 2*xp.pi]) + y = sawtooth(t) + xp_assert_close(y, xp.asarray([-1., 0., 1., -1.])) + + def test_invalid_width_nan(self, xp): + t = xp.linspace(0, 2*xp.pi, 10) + y = sawtooth(t, width=1.5) + assert xp.all(xp.isnan(y)) + + @pytest.mark.xfail_xp_backends("cupy", reason="cupy/cupy/issues/9568") + def test_triangle_symmetry(self, xp): + t = xp.linspace(0, 2*xp.pi, 1000) + y = sawtooth(t, width=0.5) + xp_assert_close(y, xp.flip(y)) + xp_assert_close(sawtooth(-t, 0.)[1:-1], sawtooth(t)[1:-1]) + + +@make_xp_test_case(square) +class TestSquareWaveform: + def test_unique(self, xp): + t = xp.linspace(0, 2*np.pi, 1000) + y = square(t) + assert y.shape == t.shape + unique = np.unique(_xp_copy_to_numpy(y)) + assert set(unique).issubset({-1.0, 1.0}) + + @pytest.mark.xfail_xp_backends("cupy", reason="cupy/cupy/issues/9541") + @pytest.mark.parametrize("t_dtype", ["float32", "float64"]) + @pytest.mark.parametrize("duty_dtype", ["float32", "float64"]) + def test_dtype(self, t_dtype, duty_dtype, xp): + t_dtype = getattr(xp, t_dtype) + duty_dtype = getattr(xp, duty_dtype) + waveform = square(xp.asarray(1, dtype=t_dtype), + duty=xp.asarray(0.5, dtype=duty_dtype)) + assert waveform.dtype == xp.result_type(t_dtype, duty_dtype) + + @pytest.mark.xfail_xp_backends("cupy", reason="cupy/cupy/issues/9541") + @pytest.mark.parametrize("dtype", [None, "float32", "float64"]) + @pytest.mark.parametrize("duty", [0.1, 0.25, 0.5, 0.75]) + def test_duty_cycle_fraction(self, dtype, duty, xp): + dtype = dtype if dtype is None else getattr(xp, dtype) # duty is Python float + # imperfect arithmetic w/ JAX `float32` causes trouble with duty=0.5 + # t = xp.linspace(0, 2*xp.pi, 10000, endpoint=False, dtype=dtype) + t = xp.arange(10000., dtype=dtype) / 10000 * 2*xp.pi + y = square(t, duty=duty) + + dtype = xp_default_dtype(xp) if dtype is None else dtype + fraction_high = xp.mean(xp.asarray(y == 1.0, dtype=dtype)) + xp_assert_close(fraction_high, xp.asarray(duty, dtype=dtype)[()]) + xp_assert_close(xp.mean(y), xp.asarray(2*duty - 1, dtype=dtype)[()]) + + @pytest.mark.parametrize("duty, expected", [(1., 1.), (0., -1.)]) + def test_duty_edge_cases(self, duty, expected, xp): + t = xp.linspace(0, 2*xp.pi, 100) + y = square(t, duty=duty) + xp_assert_equal(y, xp.full(t.shape, expected)) + + def test_periodic(self, xp): + t = xp.linspace(0, 2*xp.pi, 10, endpoint=False, dtype=xp.float64) + y1 = square(t, duty=0.4) + y2 = square(t + 2*xp.pi, duty=0.4) + + xp_assert_equal(y1, y2) + + @pytest.mark.parametrize("duty", [-0.1, 1.1, 2.0]) + def test_invalid_duty(self, duty, xp): + t = xp.linspace(0, 2*np.pi, 10) + y = square(t, duty=duty) + + assert xp.all(xp.isnan(y)) diff --git a/venv/lib/python3.12/site-packages/scipy/signal/tests/test_wavelets.py b/venv/lib/python3.12/site-packages/scipy/signal/tests/test_wavelets.py new file mode 100644 index 0000000000000000000000000000000000000000..7a357d2eaf4a530930d612358b8ca69a18b5248e --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/tests/test_wavelets.py @@ -0,0 +1,59 @@ +import numpy as np +from numpy.testing import assert_array_equal, assert_array_almost_equal + +import scipy.signal._wavelets as wavelets + + +class TestWavelets: + def test_ricker(self): + w = wavelets._ricker(1.0, 1) + expected = 2 / (np.sqrt(3 * 1.0) * (np.pi ** 0.25)) + assert_array_equal(w, expected) + + lengths = [5, 11, 15, 51, 101] + for length in lengths: + w = wavelets._ricker(length, 1.0) + assert len(w) == length + max_loc = np.argmax(w) + assert max_loc == (length // 2) + + points = 100 + w = wavelets._ricker(points, 2.0) + half_vec = np.arange(0, points // 2) + # Wavelet should be symmetric + assert_array_almost_equal(w[half_vec], w[-(half_vec + 1)]) + + # Check zeros + aas = [5, 10, 15, 20, 30] + points = 99 + for a in aas: + w = wavelets._ricker(points, a) + vec = np.arange(0, points) - (points - 1.0) / 2 + exp_zero1 = np.argmin(np.abs(vec - a)) + exp_zero2 = np.argmin(np.abs(vec + a)) + assert_array_almost_equal(w[exp_zero1], 0) + assert_array_almost_equal(w[exp_zero2], 0) + + def test_cwt(self): + widths = [1.0] + def delta_wavelet(s, t): + return np.array([1]) + len_data = 100 + test_data = np.sin(np.pi * np.arange(0, len_data) / 10.0) + + # Test delta function input gives same data as output + cwt_dat = wavelets._cwt(test_data, delta_wavelet, widths) + assert cwt_dat.shape == (len(widths), len_data) + assert_array_almost_equal(test_data, cwt_dat.flatten()) + + # Check proper shape on output + widths = [1, 3, 4, 5, 10] + cwt_dat = wavelets._cwt(test_data, wavelets._ricker, widths) + assert cwt_dat.shape == (len(widths), len_data) + + widths = [len_data * 10] + # Note: this wavelet isn't defined quite right, but is fine for this test + def flat_wavelet(l, w): + return np.full(w, 1 / w) + cwt_dat = wavelets._cwt(test_data, flat_wavelet, widths) + assert_array_almost_equal(cwt_dat, np.mean(test_data)) diff --git a/venv/lib/python3.12/site-packages/scipy/signal/tests/test_whittaker.py b/venv/lib/python3.12/site-packages/scipy/signal/tests/test_whittaker.py new file mode 100644 index 0000000000000000000000000000000000000000..a6573d54106071dac1410934661a7720cf39cd3e --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/tests/test_whittaker.py @@ -0,0 +1,425 @@ +import numpy as np +import pytest +from numpy.testing import assert_allclose +from scipy.signal import whittaker_henderson # type: ignore[attr-defined] +from scipy.signal._whittaker import ( + _logdet_difference_matrix, _polynomial_fit, _reml, _solveh_banded, _solve_WH_banded, +) +from scipy.stats import special_ortho_group + + +def _solve_WH_order2_fast(y, lamb): + """Efficiently solve WH of order 2 according to Weinert. + + Needs order = 2 and n >= 3 (data points). + + Weinert (2007) + "Efficient computation for Whittaker-Henderson smoothing". + Computational Statistics and Data Analysis 52:959-74. + https://doi.org/10.1016/j.csda.2006.11.038 + """ + n = y.shape[0] + # Convert penalty to convention of Weinert (2007), i.e. A = lambda I + D'D. + # Note that Weinert denotes the difference matrix M instead of D. + lamb = 1 / lamb + # A = LDL' decompositon, i.e. L is unit lower triangular and banded + # (bandwith=2) and D diagonal. + # First subdiagonal of L is (-e_1, .., -e_{n-1}) and 2nd subdiagonal + # (f_1, .., f_{n-2}). Diagonal of D is (d_1, .., d_n). + # The equation A @ x = lamb * y becomes + # LD @ b = lamb * y (I) + # L.T @ x = b (II) + # We shift Weinert's 1-based indices to 0-based indices, Eq. 2.2-2.6 + # Solve problem (I) + b = np.empty(n) + e = np.empty(n) + f = np.empty(n) + # i=0 + d = 1 + lamb + f[0] = 1 / d + mu = 2 + e[0] = mu * f[0] + b[0] = f[0] * lamb * y[0] + mu_old = mu + # i=1 + if n == 3: + d = 4 + lamb - mu_old * e[0] + mu = 2 - e[0] + else: + d = 5 + lamb - mu_old * e[0] + mu = 4 - e[0] + f[1] = 1 / d + e[1] = mu * f[1] + b[1] = f[1] * (lamb * y[1] + mu_old * b[0]) + mu_old = mu + for i in range(2, n-2): + d = 6 + lamb - mu_old * e[i-1] - f[i-2] + f[i] = 1 / d + mu = 4 - e[i-1] + e[i] = mu * f[i] + b[i] = f[i] * (lamb * y[i] + mu_old * b[i-1] - b[i-2]) + mu_old = mu + # i=n-2 + if n >= 4: + i = n - 2 + d = 5 + lamb - mu_old * e[i-1] - f[i-2] + f[i] = 1 / d + mu = 2 - e[i-1] + e[i] = mu * f[i] + b[i] = f[i] * (lamb * y[i] + mu_old * b[i-1] - b[i-2]) + mu_old = mu + # i=n-1 + i = n - 1 + d = 1 + lamb - mu_old * e[i-1] - f[i-2] + f[i] = 1 / d + b[i] = f[i] * (lamb * y[i] + mu_old * b[i-1] - b[i-2]) + + # Solve problem (II) + x = np.empty(n) + x[n-1] = b[n-1] + x[n-2] = b[n-2] + e[n-2] * x[n-1] + for i in range(n-3, -1, -1): + x[i] = b[i] + e[i] * x[i+1] - f[i] * x[i+2] + return x + + +def test_solveh_banded(): + n = 10 + # construct positive definite matrix A + Q = special_ortho_group(n, seed=1234).rvs() + A = Q @ np.diag(1 + np.arange(n)) @ Q.T + b = np.arange(n) + ab = np.zeros((n, n)) + ab[0, :] = np.diagonal(A, 0) + for i in range(1, n): + ab[i, :-i] = np.diagonal(A, i) + x, logdet, _ = _solveh_banded(ab, b, calc_logdet=True) + assert_allclose(x, np.linalg.solve(A, b), rtol=1e-11) + assert_allclose(logdet, np.log(np.linalg.det(A)), rtol=1e-11) + + # tridiagonal case + x, logdet, _ = _solveh_banded(ab[:2], b, calc_logdet=True) + A_tri = ( + np.diag(np.diag(A)) + np.diag(np.diag(A, -1), -1) + np.diag(np.diag(A, 1), 1) + ) + assert_allclose(logdet, np.log(np.linalg.det(A_tri)), rtol=1e-11) + + +@pytest.mark.parametrize( + ["order", "n"], + [ + (1, 2), (1, 10), + (2, 3), (2, 4), (2, 5), (2, 20), + (5, 6), (5, 7), (5, 10), (5, 20), + (6, 7), (6, 10), (6, 20), + ]) +def test_logdet_difference_matrix(order, n): + p = order + logdet = _logdet_difference_matrix(order=p, n=n) + D = np.diff(np.eye(n), n=p, axis=0) # shape (n-p, n) + assert_allclose(logdet, np.log(np.linalg.det(D @ D.T)), rtol=1e-11) + eigvals = np.linalg.eigvals(D.T @ D) + assert_allclose(logdet, np.sum(np.log(eigvals[eigvals > 1e-8])), rtol=1e-11) + + +@pytest.mark.parametrize("calc_logdet", [False, True]) +@pytest.mark.parametrize("lamb", [0.1, 1e20]) +def test_polynomial_fit(calc_logdet, lamb): + """Test that _polynomial_fit works as expected.""" + n = 20 + y = np.sin(2 * np.pi * np.linspace(0, 1, n)) + poly, logdet = _polynomial_fit(y, lamb, calc_logdet=calc_logdet) + + if not calc_logdet: + assert logdet == 0 + + poly2, logdet2 = _polynomial_fit( + y, lamb, weights=1.23 * np.ones(n), calc_logdet=calc_logdet + ) + assert logdet2 == pytest.approx(logdet, rel=1e-12) + assert_allclose(poly2, poly) + + +@pytest.mark.parametrize( + ["signal", "lamb", "order", "weights", "err", "msg"], + [ + ([[1, 2, 3] * 3], 1, 2, None, ValueError, + "Input array signal must be of shape \\(n,\\)"), + (np.zeros(2), 1, 2, None, ValueError, + "Input array signal must be at least of shape"), + (np.arange(10), -0.9, 2, None, ValueError, + "Parameter lamb must be string"), + ([1, 2, 3], "XXX", 1, None, ValueError, + "Parameter lamb must be string 'reml'"), + ([1, 2, 3], 1, 1.5, None, TypeError, + "order must be an integer."), + ([1, 2, 3], 1, 0, None, ValueError, + "order must be an integer not less than 1"), + ([1, 2, 3], 1, 2, [0, 1], ValueError, + "Input array weights must have the same shape as the signal array."), + ([1, 2, 3], 1, 2, [[0]], ValueError, + "Input array weights must have the same shape as the signal array."), + ([1, 2, np.nan], 1, 2, None, ValueError, + "Input array signal must be finite."), + ([1, 2, 3], 1, 2, [1, 2, np.nan], ValueError, + "Input array weights must be finite."), + ([1, 2, np.nan], 1, 2, [1, 2, 3], ValueError, + "Input array weights must be zero for all non-finite"), + ]) +def test_whittaker_raises(signal, lamb, order, weights, err, msg): + """Test that whittaker raises errors.""" + with pytest.raises(err, match=msg): + whittaker_henderson(signal, lamb=lamb, order=order, weights=weights) + + +def test_whittaker_small_data(): + """Test that whittaker works on a few data points.""" + # Should work on order + 1 data points. The first 2*order+1 are special. + la = 1 + y = np.arange(2) + wh = whittaker_henderson(y, order=1, lamb=la) + # Analytical solution for order=1 and n=2 + res = (np.array([[1 + la, la], [la, 1 + la]]) / (1 + 2 * la)) @ y + assert_allclose(wh.x, res, atol=1e-15) + whittaker_henderson(np.arange(3), order=1, lamb=la) + + y = np.arange(3) + wh = whittaker_henderson(y, order=2, lamb=la) + # Analytical solution for order=2 and n=3: + # import numpy as np + # from sympy import eye, Symbol + # n, order = 3, 2 + # la = Symbol("la") + # D = np.diff(eye(n), n=order, axis=0) + # A = eye(n) + la * D.T @ D + # A.inv() + res = ( + np.array([ + [1 + 5 * la, 2 * la, -la], + [ 2 * la, 1 + 2 * la, 2 * la], + [ -la, 2 * la, 1 + 5 * la], + ]) / (1 + 6 * la)) @ y + assert_allclose(wh.x, res, atol=1e-15) + assert wh.lamb == la + + y = np.arange(4) + wh = whittaker_henderson(y, order=2, lamb=la) + la2 = la ** 2 + res =(np.array([ + [14*la2 + 11*la + 1, 8*la2 + 2*la , 2*la2 - la, -4*la2 ], + [ 8*la2 + 2*la , 6*la2 + 7*la + 1, 4*la2 + 4*la, 2*la2 - la ], + [ 2*la2 - la , 4*la2 + 4*la , 6*la2 + 7*la + 1, 8*la2 + 2*la ], + [-4*la2 , 2*la2 - la , 8*la2 + 2*la , 14*la2 + 11*la + 1], + ]) /(20*la2 + 12*la + 1)) @ y + assert_allclose(wh.x, res, atol=1e-15) + whittaker_henderson(np.arange(5), order=2, lamb=la) + + whittaker_henderson(np.arange(4), order=3, lamb=la) + whittaker_henderson(np.arange(5), order=3, lamb=la) + whittaker_henderson(np.arange(6), order=3, lamb=la) + whittaker_henderson(np.arange(7), order=3, lamb=la) + + +@pytest.mark.parametrize("n", [3, 4, 5, 100]) +def test_whittaker_direct_vs_fast_order2(n): + """Test equivalent results""" + rng = np.random.default_rng(42) + signal = np.sin(2 * np.pi * np.linspace(0, 1, n)) + rng.standard_normal(n) + x1, _ = _solve_WH_banded(signal, lamb=1.23, order=2) + x2 = _solve_WH_order2_fast(signal, lamb=1.23) + assert_allclose(x1, x2, rtol=1e-11) + + wh = whittaker_henderson(signal, lamb=1.23, order=2) + assert_allclose(wh.x, x1, rtol=1e-11) + + +@pytest.mark.parametrize("order", [1, 2, 3, 4]) +def test_whittaker_limit_zero_penalty(order): + """Test whittaker for penalty lamb close to zero.""" + rng = np.random.default_rng(42) + n = 100 + y = np.sin(2*np.pi * np.linspace(0, 1, n)) + noise = rng.standard_normal(n) + signal = y + noise + + # In the limit of a zero penalty, the smoothing results in reproducing the signal. + wh = whittaker_henderson(signal, lamb=1e-7, order=order) + assert_allclose(wh.x, signal, rtol=1e-4, atol=1e-5) + assert wh.lamb == 1e-7 + + +@pytest.mark.parametrize("weights", [None, "ones"]) +@pytest.mark.parametrize("order", [1, 2, 3, 4]) +def test_whittaker_limit_infinity_penalty(weights, order): + """Test whittaker for penalty lamb close to infinity.""" + n = 100 + y = np.sin(2*np.pi * np.linspace(0, 1, n)) + w = None if weights is None else np.ones_like(y) + + # In the limit of an infinite penalty, the smoothing results in an interpolation + # polynom of degree = penalty order - 1 (order=2 => linear) + wh = whittaker_henderson(y, lamb=1e11, order=order, weights=w) + x_poly = np.arange(len(y)) + poly = np.polynomial.Polynomial.fit(x=x_poly, y=y, deg=order - 1) + assert_allclose(wh.x, poly(x_poly), rtol=10**(-6 + order), atol=1e-5) + if order == 2: + # Linear interpolation: + # As the sine is positive fom 0 to pi and negative from pi to 2*pi, we expect + # a negative slope. + assert np.diff(wh.x)[0] < 0 + + if order > 2: + # Very large lambda should trigger a user warning and produce the exact + # polynomial fit (separate code path where linear solver breaks down). + with pytest.warns( + UserWarning, + match="The linear solver in Whittaker-Henderson smoothing detected", + ): + wh = whittaker_henderson(y, lamb=1e15, order=order, weights=w) + else: + wh = whittaker_henderson(y, lamb=1e20, order=order, weights=w) + assert_allclose(wh.x, poly(x_poly), rtol=1e-10) + + +def test_whittaker_unpenalized(): + """Test whittaker for lamb=0.""" + n = 10 + y = np.sin(2*np.pi * np.linspace(0, 1, n)) + wh = whittaker_henderson(y, lamb=0) + assert_allclose(wh.x, y, rtol=1e-11) + assert not np.may_share_memory(wh.x, y) + + +def test_whittaker_weights(): + """Test that whittaker with weights of 1 is same as without weights.""" + rng = np.random.default_rng(42) + n = 100 + y = np.sin(2*np.pi * np.linspace(0, 1, n)) + noise = rng.standard_normal(n) + signal = y + noise + wh1 = whittaker_henderson(signal, lamb=1) + w = np.ones_like(signal) + wh2 = whittaker_henderson(signal, lamb=1, weights=w) + assert_allclose(wh1.x, wh2.x, rtol=1e-11) + + # Multiplying penalty and weights by the same number does not change the result. + wh3 = whittaker_henderson(signal, lamb=3, weights=3*w) + assert_allclose(wh3.x, wh1.x, rtol=1e-11) + + +@pytest.mark.parametrize("missing_value", [np.nan, np.inf, 1e-20]) +def test_whittaker_zero_weight_interpolation(missing_value): + """Test that whittaker interpolates where weights are zero.""" + n = 100 + signal = np.sin(2*np.pi * np.linspace(0, 1, n)) + signal[50:] += 2 + w = np.ones_like(signal) + signal[40:60] = missing_value # value does not matter + w[40:60] = 0 + # Note: interpolation is a polynomial of degree = 2 * order - 1. + + # order = 1 => linear interpolation + wh = whittaker_henderson(signal, lamb=1, order=1, weights=w) + interp = np.interp(np.arange(40, 60), [39, 60], [wh.x[39], wh.x[60]]) + assert_allclose(wh.x[40:60], interp, rtol=1e-11) + + # order = 2 => cubic interpolation + wh = whittaker_henderson(signal, lamb=1, order=2, weights=w) + poly = np.polynomial.Polynomial.fit( + x=[38, 39, 60, 61], y=[wh.x[38], wh.x[39], wh.x[60], wh.x[61]], deg=3 + ) + assert_allclose(wh.x[40:60], poly(np.arange(40, 60)), rtol=1e-11) + + +def test_whittaker_zero_weight_extrapolation(): + """Test that whittaker extrapolates where weights are zero.""" + n = 100 + signal = np.sin(2*np.pi * np.linspace(0, 1, n)) + signal[50:] += 2 + w = np.ones_like(signal) + signal[80:] = np.nan # value does not matter, np.nan to check arg validation + w[80:] = 0 + # Note: extrapolation is a polynomial of degree = order - 1. + + # order = 1 => constant extrapolation + wh = whittaker_henderson(signal, lamb=1, order=1, weights=w) + assert_allclose(wh.x[80:], wh.x[79], rtol=1e-11) + + # order 2 => linear extrapolation + wh = whittaker_henderson(signal, lamb=1, order=2, weights=w) + poly = np.polynomial.Polynomial.fit(x=[78, 79], y=wh.x[78:80], deg=1) + assert_allclose(wh.x[80:], poly(np.arange(80, 100)), rtol=1e-11) + + # order = 3 => quadratic extrapolation + wh = whittaker_henderson(signal, lamb=1, order=3, weights=w) + poly = np.polynomial.Polynomial.fit(x=[77, 78, 79], y=wh.x[77:80], deg=2) + assert_allclose(wh.x[80:], poly(np.arange(80, 100)), rtol=1e-9) + + +@pytest.mark.parametrize("order", [1, 2, 3]) +def test_reml_criterion(order): + la = 1.2345 + n = 10 + y = np.sin(2*np.pi * np.linspace(0, 1, n)) + D = np.diff(np.eye(n), n=order, axis=0) + M = D.T @ D + + def test_reml(la, y): + # See docstring and comment of _reml. + A = np.eye(n) + la * M + x = np.linalg.solve(A, y) + resid = y - x + r2 = resid @ resid + la * x @ M @ x + return -0.5 * ((n-order) * (1 + np.log(r2 / (n - order))) + - np.log(np.linalg.det(la * D @ D.T)) + + np.log(np.linalg.det(A))) + + r1 = _reml(lamb=la, y=y, order=order) + r2 = test_reml(la=la, y=y) + assert_allclose(r1, r2, rtol=1e-11) + + +@pytest.mark.parametrize("weights", [None, "ones"]) +def test_whittaker_reml(weights): + """Test that whittaker with REML criterion works.""" + n = 10 + order = 2 + signal = np.sin(2*np.pi * np.linspace(0, 1, n)) + y = signal.copy() + # add some (deterministic) noise + y[::2] += 0.2 + y[1::2] -= 0.2 + w = None if weights is None else np.ones_like(y) + + wh = whittaker_henderson(y, lamb="reml", order=order, weights=w) + assert wh.lamb > 0 + + # Validate against R mgcv + # library(mgcv) + # n = 10 + # order = 2 + # signal = sin(2*pi * 0:(n-1) / (n-1)) + # y = signal + # # add some (deterministic) noise + # y[seq(1, n, 2)] = y[seq(1, n, 2)] + 0.2 + # y[seq(2, n, 2)] = y[seq(2, n, 2)] - 0.2 + # x <- 1:n + # b <- gam(y ~ s(x, bs="ps", k=n, m=c(0, 2)), family = gaussian(), + # knots = list(x=0:11), method="REML", + # control = list(epsilon = 1e-12, mgcv.tol = 1e-12, efs.tol = 1e-12)) + # options(digits=12) + # b$fitted.values + expected = [ + 0.209959334732, 0.589996568382, 0.938965773497, 0.766582291904, 0.349462653088, + -0.349462653088, -0.766582291904, -0.938965773497, -0.589996568382, + -0.209959334732, + ] + assert_allclose(wh.x, expected, rtol=1e-7) + # b$sp + expected_sp = 5.12904626508 + expected_sp /= 16 # not 100% sure how to justify this 16. + assert wh.lamb == pytest.approx(expected_sp, rel=1e-7) + + # Double weights should result in double lamb. + wh2 = whittaker_henderson(y, lamb="reml", order=order, weights=2 * np.ones_like(y)) + assert wh2.lamb == pytest.approx(2 * wh.lamb, rel=1e-7) diff --git a/venv/lib/python3.12/site-packages/scipy/signal/tests/test_windows.py b/venv/lib/python3.12/site-packages/scipy/signal/tests/test_windows.py new file mode 100644 index 0000000000000000000000000000000000000000..d5434173978fa5dca88a52bfb44bdf3cfcfa13c4 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/tests/test_windows.py @@ -0,0 +1,1186 @@ +import math +import warnings + +import numpy as np +from numpy import array +import pytest +from pytest import raises as assert_raises + +from scipy.fft import fft +from scipy.signal import windows, get_window, resample +from scipy.signal.windows._windows import _WIN_FUNC_DATA, _WIN_FUNCS +from scipy._lib._array_api import ( + xp_assert_close, xp_assert_equal, array_namespace, is_torch, is_jax, is_cupy, + assert_array_almost_equal, SCIPY_DEVICE, is_numpy, make_xp_test_case, + make_xp_pytest_param, _xp_copy_to_numpy +) + +skip_xp_backends = pytest.mark.skip_xp_backends +xfail_xp_backends = pytest.mark.xfail_xp_backends + +lazy_xp_modules = [windows] + + +window_funcs = [ + ('boxcar', ()), + ('triang', ()), + ('parzen', ()), + ('bohman', ()), + ('blackman', ()), + ('nuttall', ()), + ('blackmanharris', ()), + ('flattop', ()), + ('bartlett', ()), + ('barthann', ()), + ('hamming', ()), + ('kaiser', (1,)), + ('dpss', (2,)), + ('gaussian', (0.5,)), + ('general_gaussian', (1.5, 2)), + ('chebwin', (1,)), + ('cosine', ()), + ('hann', ()), + ('exponential', ()), + ('taylor', ()), + ('tukey', (0.5,)), + ('lanczos', ()), + ] + + +@make_xp_test_case(windows.barthann) +class TestBartHann: + + def test_basic(self, xp): + xp_assert_close(windows.barthann(6, sym=True, xp=xp), + xp.asarray([0, 0.35857354213752, 0.8794264578624801, + 0.8794264578624801, 0.3585735421375199, 0], dtype=xp.float64), + rtol=1e-15, atol=1e-15) + xp_assert_close(windows.barthann(7, xp=xp), + xp.asarray([0, 0.27, 0.73, 1.0, 0.73, 0.27, 0], + dtype=xp.float64), + rtol=1e-15, atol=1e-15) + xp_assert_close(windows.barthann(6, False, xp=xp), + xp.asarray([0, 0.27, 0.73, 1.0, 0.73, 0.27], dtype=xp.float64), + rtol=1e-15, atol=1e-15) + + +@make_xp_test_case(windows.bartlett) +class TestBartlett: + + def test_basic(self, xp): + xp_assert_close(windows.bartlett(6, xp=xp), + xp.asarray([0, 0.4, 0.8, 0.8, 0.4, 0], dtype=xp.float64)) + xp_assert_close(windows.bartlett(7, xp=xp), + xp.asarray([0, 1/3, 2/3, 1.0, 2/3, 1/3, 0], dtype=xp.float64)) + xp_assert_close(windows.bartlett(6, False, xp=xp), + xp.asarray([0, 1/3, 2/3, 1.0, 2/3, 1/3], dtype=xp.float64)) + + +@make_xp_test_case(windows.blackman) +class TestBlackman: + + def test_basic(self, xp): + xp_assert_close(windows.blackman(6, sym=False, xp=xp), + xp.asarray([0, 0.13, 0.63, 1.0, 0.63, 0.13], dtype=xp.float64), + atol=1e-14) + xp_assert_close(windows.blackman(7, sym=False, xp=xp), + xp.asarray([0, 0.09045342435412804, 0.4591829575459636, + 0.9203636180999081, 0.9203636180999081, + 0.4591829575459636, 0.09045342435412804], + dtype=xp.float64), + atol=1e-8) + xp_assert_close(windows.blackman(6, xp=xp), + xp.asarray([0, 0.2007701432625305, 0.8492298567374694, + 0.8492298567374694, 0.2007701432625305, 0], + dtype=xp.float64), + atol=1e-14) + xp_assert_close(windows.blackman(7, True, xp=xp), + xp.asarray([0, 0.13, 0.63, 1.0, 0.63, 0.13, 0], + dtype=xp.float64), atol=1e-14) + + +@make_xp_test_case(windows.blackmanharris) +class TestBlackmanHarris: + + def test_basic(self, xp): + xp_assert_close(windows.blackmanharris(6, False, xp=xp), + xp.asarray([6.0e-05, 0.055645, 0.520575, + 1.0, 0.520575, 0.055645], dtype=xp.float64)) + xp_assert_close(windows.blackmanharris(7, sym=False, xp=xp), + xp.asarray([6.0e-05, 0.03339172347815117, 0.332833504298565, + 0.8893697722232837, 0.8893697722232838, + 0.3328335042985652, 0.03339172347815122], + dtype=xp.float64)) + xp_assert_close(windows.blackmanharris(6, xp=xp), + xp.asarray([6.0e-05, 0.1030114893456638, 0.7938335106543362, + 0.7938335106543364, 0.1030114893456638, 6.0e-05], + dtype=xp.float64)) + xp_assert_close(windows.blackmanharris(7, sym=True, xp=xp), + xp.asarray([6.0e-05, 0.055645, 0.520575, 1.0, 0.520575, + 0.055645, 6.0e-05], dtype=xp.float64)) + + +@make_xp_test_case(windows.taylor) +class TestTaylor: + + def test_normalized(self, xp): + """Tests windows of small length that are normalized to 1. See the + documentation for the Taylor window for more information on + normalization. + """ + xp_assert_close(windows.taylor(1, 2, 15, xp=xp), + xp.asarray([1.0], dtype=xp.float64)) + xp_assert_close( + windows.taylor(5, 2, 15, xp=xp), + xp.asarray([0.75803341, 0.90757699, 1.0, 0.90757699, 0.75803341], + dtype=xp.float64) + ) + xp_assert_close( + windows.taylor(6, 2, 15, xp=xp), + xp.asarray([ + 0.7504082, 0.86624416, 0.98208011, 0.98208011, 0.86624416, + 0.7504082 + ], dtype=xp.float64) + ) + + def test_non_normalized(self, xp): + """Test windows of small length that are not normalized to 1. See + the documentation for the Taylor window for more information on + normalization. + """ + xp_assert_close( + windows.taylor(5, 2, 15, norm=False, xp=xp), + xp.asarray([ + 0.87508054, 1.04771499, 1.15440894, 1.04771499, 0.87508054 + ], dtype=xp.float64) + ) + xp_assert_close( + windows.taylor(6, 2, 15, norm=False, xp=xp), + xp.asarray([ + 0.86627793, 1.0, 1.13372207, 1.13372207, 1.0, 0.86627793 + ], dtype=xp.float64) + ) + + def test_correctness(self, xp): + """This test ensures the correctness of the implemented Taylor + Windowing function. A Taylor Window of 1024 points is created, its FFT + is taken, and the Peak Sidelobe Level (PSLL) and 3dB and 18dB bandwidth + are found and checked. + + A publication from Sandia National Laboratories was used as reference + for the correctness values [1]_. + + References + ----- + .. [1] Armin Doerry, "Catalog of Window Taper Functions for + Sidelobe Control", 2017. + https://www.researchgate.net/profile/Armin_Doerry/publication/316281181_Catalog_of_Window_Taper_Functions_for_Sidelobe_Control/links/58f92cb2a6fdccb121c9d54d/Catalog-of-Window-Taper-Functions-for-Sidelobe-Control.pdf + """ + M_win = 1024 + N_fft = 131072 + # Set norm=False for correctness as the values obtained from the + # scientific publication do not normalize the values. Normalizing + # changes the sidelobe level from the desired value. + w = windows.taylor(M_win, nbar=4, sll=35, norm=False, sym=False, xp=xp) + f_np = fft(_xp_copy_to_numpy(w), N_fft) + + spec = 20 * np.log10(np.abs(f_np / np.max(f_np))) + + first_zero = np.argmax(np.diff(spec) > 0) + + PSLL = np.max(spec[first_zero:-first_zero]) + + BW_3dB = 2*np.argmax(spec <= -3.0102999566398121) / N_fft * M_win + BW_18dB = 2*np.argmax(spec <= -18.061799739838872) / N_fft * M_win + + assert math.isclose(PSLL, -35.1672, abs_tol=1) + assert math.isclose(BW_3dB, 1.1822, abs_tol=0.1) + assert math.isclose(BW_18dB, 2.6112, abs_tol=0.1) + + +@make_xp_test_case(windows.bohman) +class TestBohman: + + def test_basic(self, xp): + xp_assert_close(windows.bohman(6, xp=xp), + xp.asarray([0, 0.1791238937062839, 0.8343114522576858, + 0.8343114522576858, 0.1791238937062838, 0], + dtype=xp.float64)) + xp_assert_close(windows.bohman(7, sym=True, xp=xp), + xp.asarray([0, 0.1089977810442293, 0.6089977810442293, 1.0, + 0.6089977810442295, 0.1089977810442293, 0], + dtype=xp.float64)) + xp_assert_close(windows.bohman(6, False, xp=xp), + xp.asarray([0, 0.1089977810442293, 0.6089977810442293, 1.0, + 0.6089977810442295, 0.1089977810442293], + dtype=xp.float64)) + + +@make_xp_test_case(windows.boxcar) +class TestBoxcar: + + def test_basic(self, xp): + xp_assert_close(windows.boxcar(6, xp=xp), + xp.asarray([1.0, 1, 1, 1, 1, 1], dtype=xp.float64)) + xp_assert_close(windows.boxcar(7, xp=xp), + xp.asarray([1.0, 1, 1, 1, 1, 1, 1], dtype=xp.float64)) + xp_assert_close(windows.boxcar(6, False, xp=xp), + xp.asarray([1.0, 1, 1, 1, 1, 1], dtype=xp.float64)) + + +cheb_odd_true = [0.200938, 0.107729, 0.134941, 0.165348, + 0.198891, 0.235450, 0.274846, 0.316836, + 0.361119, 0.407338, 0.455079, 0.503883, + 0.553248, 0.602637, 0.651489, 0.699227, + 0.745266, 0.789028, 0.829947, 0.867485, + 0.901138, 0.930448, 0.955010, 0.974482, + 0.988591, 0.997138, 1.000000, 0.997138, + 0.988591, 0.974482, 0.955010, 0.930448, + 0.901138, 0.867485, 0.829947, 0.789028, + 0.745266, 0.699227, 0.651489, 0.602637, + 0.553248, 0.503883, 0.455079, 0.407338, + 0.361119, 0.316836, 0.274846, 0.235450, + 0.198891, 0.165348, 0.134941, 0.107729, + 0.200938] + +cheb_even_true = [0.203894, 0.107279, 0.133904, + 0.163608, 0.196338, 0.231986, + 0.270385, 0.311313, 0.354493, + 0.399594, 0.446233, 0.493983, + 0.542378, 0.590916, 0.639071, + 0.686302, 0.732055, 0.775783, + 0.816944, 0.855021, 0.889525, + 0.920006, 0.946060, 0.967339, + 0.983557, 0.994494, 1.000000, + 1.000000, 0.994494, 0.983557, + 0.967339, 0.946060, 0.920006, + 0.889525, 0.855021, 0.816944, + 0.775783, 0.732055, 0.686302, + 0.639071, 0.590916, 0.542378, + 0.493983, 0.446233, 0.399594, + 0.354493, 0.311313, 0.270385, + 0.231986, 0.196338, 0.163608, + 0.133904, 0.107279, 0.203894] + + +@make_xp_test_case(windows.chebwin) +class TestChebWin: + + def test_basic(self, xp): + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", "This window is not suitable", UserWarning) + xp_assert_close(windows.chebwin(6, 100, xp=xp), + xp.asarray([0.1046401879356917, 0.5075781475823447, + 1.0, 1.0, + 0.5075781475823447, 0.1046401879356917], + dtype=xp.float64), + atol=1e-8 + ) + xp_assert_close(windows.chebwin(7, 100, xp=xp), + xp.asarray([0.05650405062850233, 0.316608530648474, + 0.7601208123539079, 1.0, 0.7601208123539079, + 0.316608530648474, 0.05650405062850233], + dtype=xp.float64)) + xp_assert_close(windows.chebwin(6, 10, xp=xp), + xp.asarray([1.0, 0.6071201674458373, 0.6808391469897297, + 0.6808391469897297, 0.6071201674458373, 1.0], + dtype=xp.float64)) + xp_assert_close(windows.chebwin(7, 10, xp=xp), + xp.asarray([1.0, 0.5190521247588651, 0.5864059018130382, + 0.6101519801307441, 0.5864059018130382, + 0.5190521247588651, 1.0], dtype=xp.float64)) + xp_assert_close(windows.chebwin(6, 10, False, xp=xp), + xp.asarray([1.0, 0.5190521247588651, 0.5864059018130382, + 0.6101519801307441, 0.5864059018130382, + 0.5190521247588651], dtype=xp.float64)) + + def test_cheb_odd_high_attenuation(self, xp): + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", "This window is not suitable", UserWarning) + cheb_odd = windows.chebwin(53, at=-40, xp=xp) + assert_array_almost_equal(cheb_odd, xp.asarray(cheb_odd_true), decimal=4) + + def test_cheb_even_high_attenuation(self, xp): + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", "This window is not suitable", UserWarning) + cheb_even = windows.chebwin(54, at=40, xp=xp) + assert_array_almost_equal(cheb_even, xp.asarray(cheb_even_true), decimal=4) + + def test_cheb_odd_low_attenuation(self, xp): + cheb_odd_low_at_true = xp.asarray([1.000000, 0.519052, 0.586405, + 0.610151, 0.586405, 0.519052, + 1.000000], dtype=xp.float64) + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", "This window is not suitable", UserWarning) + cheb_odd = windows.chebwin(7, at=10, xp=xp) + assert_array_almost_equal(cheb_odd, cheb_odd_low_at_true, decimal=4) + + def test_cheb_even_low_attenuation(self, xp): + cheb_even_low_at_true = xp.asarray([1.000000, 0.451924, 0.51027, + 0.541338, 0.541338, 0.51027, + 0.451924, 1.000000], dtype=xp.float64) + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", "This window is not suitable", UserWarning) + cheb_even = windows.chebwin(8, at=-10, xp=xp) + assert_array_almost_equal(cheb_even, cheb_even_low_at_true, decimal=4) + + +exponential_data = { + (4, None, 0.2, False): + array([4.53999297624848542e-05, + 6.73794699908546700e-03, 1.00000000000000000e+00, + 6.73794699908546700e-03]), + (4, None, 0.2, True): array([0.00055308437014783, 0.0820849986238988, + 0.0820849986238988, 0.00055308437014783]), + (4, None, 1.0, False): array([0.1353352832366127, 0.36787944117144233, 1., + 0.36787944117144233]), + (4, None, 1.0, True): array([0.22313016014842982, 0.60653065971263342, + 0.60653065971263342, 0.22313016014842982]), + (4, 2, 0.2, False): + array([4.53999297624848542e-05, 6.73794699908546700e-03, + 1.00000000000000000e+00, 6.73794699908546700e-03]), + (4, 2, 0.2, True): None, + (4, 2, 1.0, False): array([0.1353352832366127, 0.36787944117144233, 1., + 0.36787944117144233]), + (4, 2, 1.0, True): None, + (5, None, 0.2, True): + array([4.53999297624848542e-05, + 6.73794699908546700e-03, 1.00000000000000000e+00, + 6.73794699908546700e-03, 4.53999297624848542e-05]), + (5, None, 1.0, True): array([0.1353352832366127, 0.36787944117144233, 1., + 0.36787944117144233, 0.1353352832366127]), + (5, 2, 0.2, True): None, + (5, 2, 1.0, True): None +} + + +@make_xp_test_case(windows.exponential) +def test_exponential(xp): + for k, v in exponential_data.items(): + if v is None: + assert_raises(ValueError, windows.exponential, *k, xp=xp) + else: + win = windows.exponential(*k, xp=xp) + xp_assert_close(win, xp.asarray(v), rtol=1e-14) + + +@make_xp_test_case(windows.flattop) +class TestFlatTop: + + def test_basic(self, xp): + xp_assert_close(windows.flattop(6, sym=False, xp=xp), + xp.asarray([-0.000421051, -0.051263156, 0.19821053, 1.0, + 0.19821053, -0.051263156], dtype=xp.float64)) + xp_assert_close(windows.flattop(7, sym=False, xp=xp), + xp.asarray([-0.000421051, -0.03684078115492348, + 0.01070371671615342, 0.7808739149387698, + 0.7808739149387698, 0.01070371671615342, + -0.03684078115492348], dtype=xp.float64)) + xp_assert_close(windows.flattop(6, xp=xp), + xp.asarray([-0.000421051, -0.0677142520762119, + 0.6068721525762117, 0.6068721525762117, + -0.0677142520762119, -0.000421051], + dtype=xp.float64)) + xp_assert_close(windows.flattop(7, True, xp=xp), + xp.asarray([-0.000421051, -0.051263156, 0.19821053, 1.0, + 0.19821053, -0.051263156, -0.000421051], + dtype=xp.float64)) + + +@make_xp_test_case(windows.gaussian) +class TestGaussian: + + def test_basic(self, xp): + xp_assert_close(windows.gaussian(6, 1.0, xp=xp), + xp.asarray([0.04393693362340742, 0.3246524673583497, + 0.8824969025845955, 0.8824969025845955, + 0.3246524673583497, 0.04393693362340742], + dtype=xp.float64)) + xp_assert_close(windows.gaussian(7, 1.2, xp=xp), + xp.asarray([0.04393693362340742, 0.2493522087772962, + 0.7066482778577162, 1.0, 0.7066482778577162, + 0.2493522087772962, 0.04393693362340742], + dtype=xp.float64)) + xp_assert_close(windows.gaussian(7, 3, xp=xp), + xp.asarray([0.6065306597126334, 0.8007374029168081, + 0.9459594689067654, 1.0, 0.9459594689067654, + 0.8007374029168081, 0.6065306597126334], + dtype=xp.float64)) + xp_assert_close(windows.gaussian(6, 3, False, xp=xp), + xp.asarray([0.6065306597126334, 0.8007374029168081, + 0.9459594689067654, 1.0, 0.9459594689067654, + 0.8007374029168081], dtype=xp.float64)) + + +@make_xp_test_case(windows.general_cosine) +class TestGeneralCosine: + + def test_basic(self, xp): + a = xp.asarray([0.5, 0.3, 0.2]) + xp_assert_close(windows.general_cosine(5, a), + xp.asarray([0.4, 0.3, 1, 0.3, 0.4], dtype=xp.float64)) + + a = xp.asarray([0.5, 0.3, 0.2]) + xp_assert_close(windows.general_cosine(4, a, sym=False), + xp.asarray([0.4, 0.3, 1, 0.3], dtype=xp.float64)) + + +@make_xp_test_case(windows.general_hamming) +class TestGeneralHamming: + + def test_basic(self, xp): + xp_assert_close(windows.general_hamming(5, 0.7, xp=xp), + xp.asarray([0.4, 0.7, 1.0, 0.7, 0.4], dtype=xp.float64)) + xp_assert_close(windows.general_hamming(5, 0.75, sym=False, xp=xp), + xp.asarray([0.5, 0.6727457514, 0.9522542486, + 0.9522542486, 0.6727457514], dtype=xp.float64)) + xp_assert_close(windows.general_hamming(6, 0.75, sym=True, xp=xp), + xp.asarray([0.5, 0.6727457514, 0.9522542486, + 0.9522542486, 0.6727457514, 0.5], dtype=xp.float64)) + + +@make_xp_test_case(windows.hamming) +class TestHamming: + + def test_basic(self, xp): + xp_assert_close(windows.hamming(6, False, xp=xp), + xp.asarray([0.08, 0.31, 0.77, 1.0, 0.77, 0.31], + dtype=xp.float64)) + xp_assert_close(windows.hamming(7, sym=False, xp=xp), + xp.asarray([0.08, 0.2531946911449826, 0.6423596296199047, + 0.9544456792351128, 0.9544456792351128, + 0.6423596296199047, 0.2531946911449826], + dtype=xp.float64)) + xp_assert_close(windows.hamming(6, xp=xp), + xp.asarray([0.08, 0.3978521825875242, 0.9121478174124757, + 0.9121478174124757, 0.3978521825875242, 0.08], + dtype=xp.float64)) + xp_assert_close(windows.hamming(7, sym=True, xp=xp), + xp.asarray([0.08, 0.31, 0.77, 1.0, 0.77, 0.31, 0.08], + dtype=xp.float64)) + + +@make_xp_test_case(windows.hann) +class TestHann: + + def test_basic(self, xp): + xp_assert_close(windows.hann(6, sym=False, xp=xp), + xp.asarray([0, 0.25, 0.75, 1.0, 0.75, 0.25], dtype=xp.float64), + rtol=1e-15, atol=1e-15) + xp_assert_close(windows.hann(7, sym=False, xp=xp), + xp.asarray([0, 0.1882550990706332, 0.6112604669781572, + 0.9504844339512095, 0.9504844339512095, + 0.6112604669781572, 0.1882550990706332], + dtype=xp.float64), + rtol=1e-15, atol=1e-15) + xp_assert_close(windows.hann(6, True, xp=xp), + xp.asarray([0, 0.3454915028125263, 0.9045084971874737, + 0.9045084971874737, 0.3454915028125263, 0], + dtype=xp.float64), + rtol=1e-15, atol=1e-15) + xp_assert_close(windows.hann(7, xp=xp), + xp.asarray([0, 0.25, 0.75, 1.0, 0.75, 0.25, 0], + dtype=xp.float64), + rtol=1e-15, atol=1e-15) + + +@make_xp_test_case(windows.kaiser) +class TestKaiser: + + def test_basic(self, xp): + xp_assert_close(windows.kaiser(6, 0.5, xp=xp), + xp.asarray([0.9403061933191572, 0.9782962393705389, + 0.9975765035372042, 0.9975765035372042, + 0.9782962393705389, 0.9403061933191572], + dtype=xp.float64)) + xp_assert_close(windows.kaiser(7, 0.5, xp=xp), + xp.asarray([0.9403061933191572, 0.9732402256999829, + 0.9932754654413773, 1.0, 0.9932754654413773, + 0.9732402256999829, 0.9403061933191572], + dtype=xp.float64)) + xp_assert_close(windows.kaiser(6, 2.7, xp=xp), + xp.asarray([0.2603047507678832, 0.6648106293528054, + 0.9582099802511439, 0.9582099802511439, + 0.6648106293528054, 0.2603047507678832], + dtype=xp.float64)) + xp_assert_close(windows.kaiser(7, 2.7, xp=xp), + xp.asarray([0.2603047507678832, 0.5985765418119844, + 0.8868495172060835, 1.0, 0.8868495172060835, + 0.5985765418119844, 0.2603047507678832], + dtype=xp.float64)) + xp_assert_close(windows.kaiser(6, 2.7, False, xp=xp), + xp.asarray([0.2603047507678832, 0.5985765418119844, + 0.8868495172060835, 1.0, 0.8868495172060835, + 0.5985765418119844], dtype=xp.float64)) + + +@make_xp_test_case(windows.kaiser_bessel_derived) +class TestKaiserBesselDerived: + + def test_basic(self, xp): + # cover case `M < 1` + w = windows.kaiser_bessel_derived(0.5, beta=4.0, xp=xp) + xp_assert_equal(w, xp.asarray([])) + + M = 100 + w = windows.kaiser_bessel_derived(M, beta=4.0, xp=xp) + w2 = windows.get_window(('kaiser bessel derived', 4.0), + M, fftbins=False, xp=xp) + xp_assert_close(w, w2) + + # Test for Princen-Bradley condition + actual = w[:M // 2] ** 2 + w[-M // 2:] ** 2 + xp_assert_close(actual, xp.ones(actual.shape, dtype=actual.dtype)) + + # Test actual values from other implementations + # M = 2: sqrt(2) / 2 + # M = 4: 0.518562710536, 0.855039598640 + # M = 6: 0.436168993154, 0.707106781187, 0.899864772847 + # Ref:https://github.com/scipy/scipy/pull/4747#issuecomment-172849418 + actual = windows.kaiser_bessel_derived(2, beta=np.pi / 2, xp=xp)[:1] + desired = xp.ones_like(actual) * math.sqrt(2) / 2.0 + xp_assert_close(actual, desired) + + xp_assert_close(windows.kaiser_bessel_derived(4, beta=np.pi / 2, xp=xp)[:2], + xp.asarray([0.518562710536, 0.855039598640], dtype=xp.float64)) + + xp_assert_close(windows.kaiser_bessel_derived(6, beta=np.pi / 2, xp=xp)[:3], + xp.asarray([0.436168993154, 0.707106781187, 0.899864772847], + dtype=xp.float64)) + + def test_exceptions(self, xp): + M = 100 + # Assert ValueError for odd window length + msg = ("Kaiser-Bessel Derived windows are only defined for even " + "number of points") + with assert_raises(ValueError, match=msg): + windows.kaiser_bessel_derived(M + 1, beta=4., xp=xp) + + # Assert ValueError for non-symmetric setting + msg = ("Kaiser-Bessel Derived windows are only defined for " + "symmetric shapes") + with assert_raises(ValueError, match=msg): + windows.kaiser_bessel_derived(M + 1, beta=4., sym=False, xp=xp) + + +@make_xp_test_case(windows.nuttall) +class TestNuttall: + + def test_basic(self, xp): + xp_assert_close(windows.nuttall(6, sym=False, xp=xp), + xp.asarray([0.0003628, 0.0613345, 0.5292298, 1.0, 0.5292298, + 0.0613345], dtype=xp.float64)) + xp_assert_close(windows.nuttall(7, sym=False, xp=xp), + xp.asarray([0.0003628, 0.03777576895352025, + 0.3427276199688195, + 0.8918518610776603, 0.8918518610776603, + 0.3427276199688196, 0.0377757689535203], + dtype=xp.float64)) + xp_assert_close(windows.nuttall(6, xp=xp), + xp.asarray([0.0003628, 0.1105152530498718, + 0.7982580969501282, 0.7982580969501283, + 0.1105152530498719, 0.0003628], dtype=xp.float64)) + xp_assert_close(windows.nuttall(7, True, xp=xp), + xp.asarray([0.0003628, 0.0613345, 0.5292298, 1.0, + 0.5292298, 0.0613345, 0.0003628], dtype=xp.float64)) + + +@make_xp_test_case(windows.parzen) +class TestParzen: + + def test_basic(self, xp): + xp_assert_close(windows.parzen(6, xp=xp), + xp.asarray([0.009259259259259254, 0.25, 0.8611111111111112, + 0.8611111111111112, 0.25, 0.009259259259259254], + dtype=xp.float64)) + xp_assert_close(windows.parzen(7, sym=True, xp=xp), + xp.asarray([0.00583090379008747, 0.1574344023323616, + 0.6501457725947521, 1.0, 0.6501457725947521, + 0.1574344023323616, 0.00583090379008747], + dtype=xp.float64)) + xp_assert_close(windows.parzen(6, False, xp=xp), + xp.asarray([0.00583090379008747, 0.1574344023323616, + 0.6501457725947521, 1.0, 0.6501457725947521, + 0.1574344023323616], dtype=xp.float64)) + + +@make_xp_test_case(windows.triang) +class TestTriang: + + def test_basic(self, xp): + + xp_assert_close(windows.triang(6, True, xp=xp), + xp.asarray([1/6, 1/2, 5/6, 5/6, 1/2, 1/6], dtype=xp.float64)) + xp_assert_close(windows.triang(7, xp=xp), + xp.asarray([1/4, 1/2, 3/4, 1, 3/4, 1/2, 1/4], dtype=xp.float64)) + xp_assert_close(windows.triang(6, sym=False, xp=xp), + xp.asarray([1/4, 1/2, 3/4, 1, 3/4, 1/2], dtype=xp.float64)) + + +tukey_data = { + (4, 0.5, True): array([0.0, 1.0, 1.0, 0.0]), + (4, 0.9, True): array([0.0, 0.84312081893436686, + 0.84312081893436686, 0.0]), + (4, 1.0, True): array([0.0, 0.75, 0.75, 0.0]), + (4, 0.5, False): array([0.0, 1.0, 1.0, 1.0]), + (4, 0.9, False): array([0.0, 0.58682408883346526, + 1.0, 0.58682408883346526]), + (4, 1.0, False): array([0.0, 0.5, 1.0, 0.5]), + (5, 0.0, True): array([1.0, 1.0, 1.0, 1.0, 1.0]), + (5, 0.8, True): array([0.0, 0.69134171618254492, + 1.0, 0.69134171618254492, 0.0]), + (5, 1.0, True): array([0.0, 0.5, 1.0, 0.5, 0.0]), + + (6, 0): [1.0, 1, 1, 1, 1, 1], + (7, 0): [1.0, 1, 1, 1, 1, 1, 1], + (6, .25): [0.0, 1, 1, 1, 1, 0], + (7, .25): [0.0, 1, 1, 1, 1, 1, 0], + (6,): [0, 0.9045084971874737, 1.0, 1.0, 0.9045084971874735, 0], + (7,): [0, 0.75, 1.0, 1.0, 1.0, 0.75, 0], + (6, .75): [0, 0.5522642316338269, 1.0, 1.0, 0.5522642316338267, 0], + (7, .75): [0, 0.4131759111665348, 0.9698463103929542, 1.0, + 0.9698463103929542, 0.4131759111665347, 0], + (6, 1): [0, 0.3454915028125263, 0.9045084971874737, 0.9045084971874737, + 0.3454915028125263, 0], + (7, 1): [0, 0.25, 0.75, 1.0, 0.75, 0.25, 0], +} + + +@make_xp_test_case(windows.tukey) +class TestTukey: + + def test_basic(self, xp): + # Test against hardcoded data + for k, v in tukey_data.items(): + if v is None: + assert_raises(ValueError, windows.tukey, *k, xp=xp) + else: + if is_torch(xp) and k in [(6,), (6, .75), (7, .75), (6,1)]: + atol_rtol = {'rtol': 3e-8, 'atol': 1e-8} + else: + atol_rtol = {'rtol': 1e-15, 'atol': 1e-15 } + + win = windows.tukey(*k, xp=xp) + xp_assert_close(win, xp.asarray(v), + check_dtype=False, **atol_rtol) + + def test_extremes(self, xp): + # Test extremes of alpha correspond to boxcar and hann + tuk0 = windows.tukey(100, 0, xp=xp) + box0 = windows.boxcar(100, xp=xp) + xp_assert_close(tuk0, box0) + + tuk1 = windows.tukey(100, 1, xp=xp) + han1 = windows.hann(100, xp=xp) + xp_assert_close(tuk1, han1) + + +dpss_data = { + # All values from MATLAB: + # * taper[1] of (3, 1.4, 3) sign-flipped + # * taper[3] of (5, 1.5, 5) sign-flipped + (4, 0.1, 2): ([[0.497943898, 0.502047681, 0.502047681, 0.497943898], [0.670487993, 0.224601537, -0.224601537, -0.670487993]], [0.197961815, 0.002035474]), # noqa: E501 + (3, 1.4, 3): ([[0.410233151, 0.814504464, 0.410233151], [0.707106781, 0.0, -0.707106781], [0.575941629, -0.580157287, 0.575941629]], [0.999998093, 0.998067480, 0.801934426]), # noqa: E501 + (5, 1.5, 5): ([[0.1745071052, 0.4956749177, 0.669109327, 0.495674917, 0.174507105], [0.4399493348, 0.553574369, 0.0, -0.553574369, -0.439949334], [0.631452756, 0.073280238, -0.437943884, 0.073280238, 0.631452756], [0.553574369, -0.439949334, 0.0, 0.439949334, -0.553574369], [0.266110290, -0.498935248, 0.600414741, -0.498935248, 0.266110290147157]], [0.999728571, 0.983706916, 0.768457889, 0.234159338, 0.013947282907567]), # noqa: E501 + (100, 2, 4): ([[0.0030914414, 0.0041266922, 0.005315076, 0.006665149, 0.008184854, 0.0098814158, 0.011761239, 0.013829809, 0.016091597, 0.018549973, 0.02120712, 0.02406396, 0.027120092, 0.030373728, 0.033821651, 0.037459181, 0.041280145, 0.045276872, 0.049440192, 0.053759447, 0.058222524, 0.062815894, 0.067524661, 0.072332638, 0.077222418, 0.082175473, 0.087172252, 0.092192299, 0.097214376, 0.1022166, 0.10717657, 0.11207154, 0.11687856, 0.12157463, 0.12613686, 0.13054266, 0.13476986, 0.13879691, 0.14260302, 0.14616832, 0.14947401, 0.1525025, 0.15523755, 0.15766438, 0.15976981, 0.16154233, 0.16297223, 0.16405162, 0.16477455, 0.16513702, 0.16513702, 0.16477455, 0.16405162, 0.16297223, 0.16154233, 0.15976981, 0.15766438, 0.15523755, 0.1525025, 0.14947401, 0.14616832, 0.14260302, 0.13879691, 0.13476986, 0.13054266, 0.12613686, 0.12157463, 0.11687856, 0.11207154, 0.10717657, 0.1022166, 0.097214376, 0.092192299, 0.087172252, 0.082175473, 0.077222418, 0.072332638, 0.067524661, 0.062815894, 0.058222524, 0.053759447, 0.049440192, 0.045276872, 0.041280145, 0.037459181, 0.033821651, 0.030373728, 0.027120092, 0.02406396, 0.02120712, 0.018549973, 0.016091597, 0.013829809, 0.011761239, 0.0098814158, 0.008184854, 0.006665149, 0.005315076, 0.0041266922, 0.0030914414], [0.018064449, 0.022040342, 0.026325013, 0.030905288, 0.035764398, 0.040881982, 0.046234148, 0.051793558, 0.057529559, 0.063408356, 0.069393216, 0.075444716, 0.081521022, 0.087578202, 0.093570567, 0.099451049, 0.10517159, 0.11068356, 0.11593818, 0.12088699, 0.12548227, 0.12967752, 0.1334279, 0.13669069, 0.13942569, 0.1415957, 0.14316686, 0.14410905, 0.14439626, 0.14400686, 0.14292389, 0.1411353, 0.13863416, 0.13541876, 0.13149274, 0.12686516, 0.12155045, 0.1155684, 0.10894403, 0.10170748, 0.093893752, 0.08554251, 0.076697768, 0.067407559, 0.057723559, 0.04770068, 0.037396627, 0.026871428, 0.016186944, 0.0054063557, -0.0054063557, -0.016186944, -0.026871428, -0.037396627, -0.04770068, -0.057723559, -0.067407559, -0.076697768, -0.08554251, -0.093893752, -0.10170748, -0.10894403, -0.1155684, -0.12155045, -0.12686516, -0.13149274, -0.13541876, -0.13863416, -0.1411353, -0.14292389, -0.14400686, -0.14439626, -0.14410905, -0.14316686, -0.1415957, -0.13942569, -0.13669069, -0.1334279, -0.12967752, -0.12548227, -0.12088699, -0.11593818, -0.11068356, -0.10517159, -0.099451049, -0.093570567, -0.087578202, -0.081521022, -0.075444716, -0.069393216, -0.063408356, -0.057529559, -0.051793558, -0.046234148, -0.040881982, -0.035764398, -0.030905288, -0.026325013, -0.022040342, -0.018064449], [0.064817553, 0.072567801, 0.080292992, 0.087918235, 0.095367076, 0.10256232, 0.10942687, 0.1158846, 0.12186124, 0.12728523, 0.13208858, 0.13620771, 0.13958427, 0.14216587, 0.14390678, 0.14476863, 0.1447209, 0.14374148, 0.14181704, 0.13894336, 0.13512554, 0.13037812, 0.1247251, 0.11819984, 0.11084487, 0.10271159, 0.093859853, 0.084357497, 0.074279719, 0.063708406, 0.052731374, 0.041441525, 0.029935953, 0.018314987, 0.0066811877, -0.0048616765, -0.016209689, -0.027259848, -0.037911124, -0.048065512, -0.05762905, -0.066512804, -0.0746338, -0.081915903, -0.088290621, -0.09369783, -0.098086416, -0.10141482, -0.10365146, -0.10477512, -0.10477512, -0.10365146, -0.10141482, -0.098086416, -0.09369783, -0.088290621, -0.081915903, -0.0746338, -0.066512804, -0.05762905, -0.048065512, -0.037911124, -0.027259848, -0.016209689, -0.0048616765, 0.0066811877, 0.018314987, 0.029935953, 0.041441525, 0.052731374, 0.063708406, 0.074279719, 0.084357497, 0.093859853, 0.10271159, 0.11084487, 0.11819984, 0.1247251, 0.13037812, 0.13512554, 0.13894336, 0.14181704, 0.14374148, 0.1447209, 0.14476863, 0.14390678, 0.14216587, 0.13958427, 0.13620771, 0.13208858, 0.12728523, 0.12186124, 0.1158846, 0.10942687, 0.10256232, 0.095367076, 0.087918235, 0.080292992, 0.072567801, 0.064817553], [0.14985551, 0.15512305, 0.15931467, 0.16236806, 0.16423291, 0.16487165, 0.16426009, 0.1623879, 0.1592589, 0.15489114, 0.14931693, 0.14258255, 0.13474785, 0.1258857, 0.11608124, 0.10543095, 0.094041635, 0.082029213, 0.069517411, 0.056636348, 0.043521028, 0.030309756, 0.017142511, 0.0041592774, -0.0085016282, -0.020705223, -0.032321494, -0.043226982, -0.053306291, -0.062453515, -0.070573544, -0.077583253, -0.083412547, -0.088005244, -0.091319802, -0.093329861, -0.094024602, -0.093408915, -0.091503383, -0.08834406, -0.08398207, -0.078483012, -0.071926192, -0.064403681, -0.056019215, -0.046886954, -0.037130106, -0.026879442, -0.016271713, -0.005448, 0.005448, 0.016271713, 0.026879442, 0.037130106, 0.046886954, 0.056019215, 0.064403681, 0.071926192, 0.078483012, 0.08398207, 0.08834406, 0.091503383, 0.093408915, 0.094024602, 0.093329861, 0.091319802, 0.088005244, 0.083412547, 0.077583253, 0.070573544, 0.062453515, 0.053306291, 0.043226982, 0.032321494, 0.020705223, 0.0085016282, -0.0041592774, -0.017142511, -0.030309756, -0.043521028, -0.056636348, -0.069517411, -0.082029213, -0.094041635, -0.10543095, -0.11608124, -0.1258857, -0.13474785, -0.14258255, -0.14931693, -0.15489114, -0.1592589, -0.1623879, -0.16426009, -0.16487165, -0.16423291, -0.16236806, -0.15931467, -0.15512305, -0.14985551]], [0.999943140, 0.997571533, 0.959465463, 0.721862496]), # noqa: E501 +} + + +@make_xp_test_case(windows.dpss) +class TestDPSS: + + def test_basic(self, xp): + # Test against hardcoded data + for k, v in dpss_data.items(): + win, ratios = windows.dpss(*k, return_ratios=True, xp=xp) + xp_assert_close(win, v[0], atol=1e-7, err_msg=k) + xp_assert_close(ratios, v[1], rtol=1e-5, atol=1e-7, err_msg=k) + + def test_unity(self, xp): + # Test unity value handling (gh-2221) + for M in range(1, 21): + # corrected w/approximation (default) + win = windows.dpss(M, M / 2.1, xp=xp) + expected = M % 2 # one for odd, none for even + xp_assert_equal(np.isclose(win, 1.).sum(), expected, + err_msg=f'{win}') + # corrected w/subsample delay (slower) + win_sub = windows.dpss(M, M / 2.1, norm='subsample', xp=xp) + if M > 2: + # @M=2 the subsample doesn't do anything + xp_assert_equal(np.isclose(win_sub, 1.).sum(), expected, + err_msg=f'{win_sub}') + xp_assert_close(win, win_sub, rtol=0.03) # within 3% + # not the same, l2-norm + win_2 = windows.dpss(M, M / 2.1, norm=2, xp=xp) + expected = 1 if M == 1 else 0 + xp_assert_equal(np.isclose(win_2, 1.).sum(), expected, + err_msg=f'{win_2}') + + def test_extremes(self, xp): + # Test extremes of alpha + lam = windows.dpss(31, 6, 4, return_ratios=True, xp=xp)[1] + xp_assert_close(lam, xp.ones_like(lam)) + lam = windows.dpss(31, 7, 4, return_ratios=True, xp=xp)[1] + xp_assert_close(lam, xp.ones_like(lam)) + lam = windows.dpss(31, 8, 4, return_ratios=True, xp=xp)[1] + xp_assert_close(lam, xp.ones_like(lam)) + + def test_degenerate(self, xp): + # Test failures + assert_raises(ValueError, windows.dpss, 4, 1.5, -1) # Bad Kmax + assert_raises(ValueError, windows.dpss, 4, 1.5, -5) + assert_raises(TypeError, windows.dpss, 4, 1.5, 1.1) + assert_raises(ValueError, windows.dpss, 3, 1.5, 3) # NW must be < N/2. + assert_raises(ValueError, windows.dpss, 3, -1, 3) # NW must be pos + assert_raises(ValueError, windows.dpss, 3, 0, 3) + assert_raises(ValueError, windows.dpss, -1, 1, 3) # negative M + + @skip_xp_backends(np_only=True) + def test_degenerate_signle_samples(self, xp): + # Single samples + w = windows.dpss(1, 1.) + xp_assert_equal(w, [1.]) + w, ratio = windows.dpss(1, 1., return_ratios=True) + xp_assert_equal(w, [1.]) + assert ratio == 1. + w, ratio = windows.dpss(1, 1., Kmax=4, return_ratios=True) + xp_assert_equal(w, [1.]) + assert isinstance(ratio, np.ndarray) + xp_assert_equal(ratio, [1.]) + + assert_raises(ValueError, windows.dpss, 4, 1.5, -1, xp=xp) # Bad Kmax + assert_raises(ValueError, windows.dpss, 4, 1.5, -5, xp=xp) + assert_raises(TypeError, windows.dpss, 4, 1.5, 1.1, xp=xp) + assert_raises(ValueError, windows.dpss, 3, 1.5, 3, xp=xp) # NW must be < N/2. + assert_raises(ValueError, windows.dpss, 3, -1, 3, xp=xp) # NW must be pos + assert_raises(ValueError, windows.dpss, 3, 0, 3, xp=xp) + assert_raises(ValueError, windows.dpss, -1, 1, 3, xp=xp) # negative M + + +@make_xp_test_case(windows.lanczos) +class TestLanczos: + + def test_basic(self, xp): + # Analytical results: + # sinc(x) = sinc(-x) + # sinc(pi) = 0, sinc(0) = 1 + # Hand computation on WolframAlpha: + # sinc(2 pi / 3) = 0.413496672 + # sinc(pi / 3) = 0.826993343 + # sinc(3 pi / 5) = 0.504551152 + # sinc(pi / 5) = 0.935489284 + xp_assert_close(windows.lanczos(6, sym=False, xp=xp), + xp.asarray([0., 0.413496672, + 0.826993343, 1., 0.826993343, + 0.413496672], dtype=xp.float64), + atol=1e-9) + xp_assert_close(windows.lanczos(6, xp=xp), + xp.asarray([0., 0.504551152, + 0.935489284, 0.935489284, + 0.504551152, 0.], dtype=xp.float64), + atol=1e-9) + xp_assert_close(windows.lanczos(7, sym=True, xp=xp), + xp.asarray([0., 0.413496672, + 0.826993343, 1., 0.826993343, + 0.413496672, 0.], dtype=xp.float64), + atol=1e-9) + + def test_array_size(self, xp): + for n in [0, 10, 11]: + assert windows.lanczos(n, sym=False, xp=xp).shape[0] == n + assert windows.lanczos(n, sym=True, xp=xp).shape[0] == n + + +@make_xp_test_case(windows.get_window) +class TestGetWindow: + """Unit test for `scipy.signal.get_windows`. """ + + def test_WIN_FUNC_DATA_integrity(self): + """Verify that the `_windows._WIN_FUNC_DATA` dict is consistent. + + The keys of _WIN_FUNC_DATA are made of tuples of strings of allowed window + names. Its values are 2-tuples made up of the window function and a + entry characterizing the existence of window parameters as ``True``, + ``False`` or ``'OPTIONAL'``. + + + It is verified that the correct window name (i.e., corresponding to the + function in the value tuple) is included in the key tuple. It is also checked + that the second entry in the value tuple is either ``True``, ``False`` or + ``'OPTIONAL'``. + """ + for nn_, v_ in _WIN_FUNC_DATA.items(): + func_name = v_[0].__name__ + msg = f"Function name in {nn_} does not contain name of actual function!" + assert func_name in nn_, msg + assert v_[1] in (True, False, 'OPTIONAL') + + @make_xp_test_case(windows.boxcar) + def test_boxcar(self, xp): + w = windows.get_window('boxcar', 12, xp=xp) + xp_assert_equal(w, xp.ones_like(w)) + + # window is a tuple of len 1 + w = windows.get_window(('boxcar',), 16, xp=xp) + xp_assert_equal(w, xp.ones_like(w)) + + @make_xp_test_case(windows.chebwin) + def test_cheb_odd(self, xp): + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", "This window is not suitable", UserWarning) + w = windows.get_window(('chebwin', -40), 53, fftbins=False, xp=xp) + assert_array_almost_equal( + w, xp.asarray(cheb_odd_true, dtype=xp.float64), decimal=4 + ) + + @make_xp_test_case(windows.chebwin) + def test_cheb_even(self, xp): + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", "This window is not suitable", UserWarning) + w = windows.get_window(('chebwin', 40), 54, fftbins=False, xp=xp) + assert_array_almost_equal(w, xp.asarray(cheb_even_true), decimal=4) + + @make_xp_test_case(windows.dpss) + def test_dpss(self, xp): + win1 = windows.get_window(('dpss', 3), 64, fftbins=False, xp=xp) + win2 = windows.dpss(64, 3, xp=xp) + xp_assert_equal(win1, win2) + + @make_xp_test_case(windows.kaiser) + def test_kaiser_float(self, xp): + win1 = windows.get_window(7.2, 64, xp=xp) + win2 = windows.kaiser(64, 7.2, False, xp=xp) + if is_jax(xp): + # On JAX with jit enabled, there is a very small discrepancy + # in the results. + xp_assert_close(win1, win2, rtol=xp.finfo(win1.dtype).eps) + else: + xp_assert_equal(win1, win2) + + @pytest.mark.parametrize('Nx', [-1, 3.0, np.float64(3)]) + @make_xp_test_case(windows.hann) + def test_invalid_parameter_NX(self, Nx, xp): + with pytest.raises(ValueError, match="^Parameter Nx=.*"): + windows.get_window('hann', Nx, xp=xp) + + # noinspection PyTypeChecker + def test_invalid_inputs(self, xp): + """Raise all exceptions (except those concerning parameter `Nx`). """ + with pytest.raises(ValueError, match="^Parameter fftbins=.*"): + windows.get_window('hann', 5, fftbins=1, xp=xp) + with pytest.raises(ValueError, match="^Parameter window=.*"): + windows.get_window(['hann',], 5, xp=xp) + with pytest.raises(ValueError, match="^First tuple entry of parameter win.*"): + windows.get_window((42,), 5, xp=xp) + with pytest.raises(ValueError, match="^Invalid window name 'INVALID'.*"): + windows.get_window('INVALID', 5, xp=xp) + with pytest.raises(ValueError, match="^'hann' does not allow parameters.*"): + windows.get_window(('hann', 1), 5, xp=xp) + with pytest.raises(ValueError, match="^'kaiser' must have parameters.*"): + windows.get_window('kaiser', 5, xp=xp) + with pytest.raises(ValueError, match="^Window dpss must have one.*"): + windows.get_window(('dpss', 1, 2), 5, xp=xp) + with pytest.raises(ValueError, match="^'general_cosine' does not accept.*"): + xp_ = xp or np # ensure parameter xp_ is not None + windows.get_window(('general cosine', [1, 2]), 5, xp=xp_) + + @make_xp_test_case(windows.bartlett) + def test_symmetric_periodic(self, xp): + """Ensure that suffixes `_periodic` and `_symmetric` work for window names. """ + w_sym = windows.bartlett(5, sym=True, xp=xp) + xp_assert_close(get_window('bartlett', 5, fftbins=False, xp=xp), w_sym) + xp_assert_close(get_window('bartlett_symmetric', 5, xp=xp), w_sym) + # overwrite parameter `fftbins`: + xp_assert_close(get_window('bartlett_symmetric', 5, fftbins=True, xp=xp), w_sym) + + w_per = windows.bartlett(5, sym=False, xp=xp) + xp_assert_close(get_window('bartlett', 5, xp=xp), w_per) + xp_assert_close(get_window('bartlett', 5, fftbins=True, xp=xp), w_per) + xp_assert_close(get_window('bartlett_periodic', 5, xp=xp), w_per) + # overwrite parameter `fftbins`: + xp_assert_close(get_window('bartlett_periodic', 5, fftbins=False, xp=xp), + w_per) + + @make_xp_test_case(windows.kaiser) + def test_array_as_window(self, xp): + # github issue 3603 + osfactor = 128 + sig = xp.arange(128) + + win = windows.get_window(('kaiser', 8.0), osfactor // 2, xp=xp) + mesg = "^window must|^window.shape=" + with assert_raises(ValueError, match=mesg): + resample(sig, sig.shape[0] * osfactor, window=win) + + @make_xp_test_case(windows.general_cosine) + def test_general_cosine(self, xp): + xp_assert_close(get_window(('general_cosine', xp.asarray([0.5, 0.3, 0.2])), 4), + xp.asarray([0.4, 0.3, 1, 0.3], dtype=xp.float64)) + xp_assert_close(get_window(('general_cosine', xp.asarray([0.5, 0.3, 0.2])), 4, + fftbins=False), + xp.asarray([0.4, 0.55, 0.55, 0.4], dtype=xp.float64)) + + with pytest.raises(ValueError): + get_window(('general_cosine', [0.5, 0.3, 0.2]), 4, xp=xp) + + @make_xp_test_case(windows.general_hamming) + def test_general_hamming(self, xp): + xp_assert_close(get_window(('general_hamming', 0.7), 5, xp=xp), + xp.asarray([0.4, 0.6072949, 0.9427051, 0.9427051, 0.6072949], + dtype=xp.float64)) + xp_assert_close(get_window(('general_hamming', 0.7), 5, fftbins=False, xp=xp), + xp.asarray([0.4, 0.7, 1.0, 0.7, 0.4], dtype=xp.float64)) + + @make_xp_test_case(windows.lanczos) + def test_lanczos(self, xp): + xp_assert_close(get_window('lanczos', 6, xp=xp), + xp.asarray([0., 0.413496672, 0.826993343, 1., 0.826993343, + 0.413496672], dtype=xp.float64), atol=1e-9) + xp_assert_close(get_window('lanczos', 6, fftbins=False, xp=xp), + xp.asarray([0., 0.504551152, 0.935489284, 0.935489284, + 0.504551152, 0.], dtype=xp.float64), atol=1e-9) + xp_assert_close(get_window('lanczos', 6, xp=xp), + get_window('sinc', 6, xp=xp)) + + def test_xp_default(self, xp): + # no explicit xp= argument, default to numpy + win = get_window('lanczos', 6) + assert isinstance(win, np.ndarray) + + win = get_window('lanczos', 6, xp=xp) + if not is_numpy(xp): + assert not isinstance(win, np.ndarray) + + +@make_xp_test_case(windows.general_gaussian) +class TestGeneralGaussian: + + @pytest.mark.parametrize('M', (6, 7)) + @pytest.mark.parametrize('p', (1, 1.5)) + @pytest.mark.parametrize('sig', (1., 2.)) + @pytest.mark.parametrize('sym', (True, False)) + def test_basic(self, M, p, sig, sym, xp): + """Verify basic parametrizations. """ + if sym: + t = xp.arange(M, dtype=xp.float64) - (M-1) / 2 + else: + t = xp.arange(M+1, dtype=xp.float64) - M / 2 + t = t[:-1] + x_ref = xp.exp(-0.5 * abs(t / sig) ** (2*p)) + x = windows.general_gaussian(M, p, sig, sym, xp=xp) + xp_assert_close(x, x_ref, atol=1e-12) + + def test_default_func_parameter(self, xp): + """Verify the correctness of the default value of function parameter `sym`. """ + x = windows.general_gaussian(6, 1, 1, xp=xp) + x_ref = windows.general_gaussian(6, 1, 1, sym=True, xp=xp) + xp_assert_equal(x, x_ref) + + def test_p1_equals_gaussian(self,xp): + """Testing that `general_gaussian` with p = 1 is equivalent to the normal + `gaussian` window. """ + w1 = windows.general_gaussian(7, p=1.0, sig=2.0, xp=xp) + w2 = windows.gaussian(7, std=2.0, xp=xp) + xp_assert_close(w1, w2) + + def test_peak_value(self, xp): + """ Testing that if M is odd, the peak is at 1. """ + w = windows.general_gaussian(7, p=3, sig=2.0, xp=xp) + assert float(xp.max(w)) == 1.0 + + # Testing that if M is even, the peak is less than 1.0 + w = windows.general_gaussian(6, p=1.5, sig=2.0, xp=xp) + assert float(xp.max(w)) < 1.0 + + def test_len_edge_cases(self, xp): + """Test the length edge cases are handled correctly. """ + # length = 0 should return an empty array: + assert windows.general_gaussian(0, 1, 1, xp=xp).shape[0] == 0 + + # length = 1 should return an array of length 1 containing 1.0: + xp_assert_close(windows.general_gaussian(1, 1, 1, xp=xp), + xp.asarray([1.0], dtype=xp.float64)) + + with pytest.raises(ValueError): # Negative values should raise an error: + windows.general_gaussian(-1, 1, 1, xp=xp) + + +@make_xp_test_case(windows.cosine) +class TestCosine: + @pytest.mark.parametrize('M', (3, 4)) + @pytest.mark.parametrize('sym', (True, False)) + def test_basic(self, M, sym, xp): + """Verify basic parametrizations. """ + if sym: + t = (xp.arange(M, dtype=xp.float64) + 0.5) / M + else: + t = (xp.arange(M+1, dtype=xp.float64) + 0.5) / (M+1) + t = t[:-1] + x_ref = xp.sin(xp.pi * t) + x = windows.cosine(M, sym=sym, xp=xp) + xp_assert_close(x, x_ref, atol=1e-12) + + def test_default_func_parameter(self, xp): + """Verify the correctness of the default value of function parameter `sym`. """ + x = windows.cosine(6, xp=xp) + x_ref = windows.cosine(6, sym=True, xp=xp) + xp_assert_equal(x, x_ref) + + def test_len_edge_cases(self, xp): + """Testing that the length edge cases are handled correctly.""" + # length = 0 should return an empty array: + assert windows.cosine(0, xp=xp).shape[0] == 0 + + # length = 1 should return an array of length 1 containing 1.0: + xp_assert_close(windows.cosine(1, xp=xp), + xp.asarray([1.0], dtype=xp.float64)) + + with pytest.raises(ValueError): + windows.cosine(-1, xp=xp) # negative values should return an error + + +@skip_xp_backends("dask.array", reason="https://github.com/dask/dask/issues/2620") +@pytest.mark.parametrize( + "window,window_name,params", + [ + make_xp_pytest_param(getattr(windows, window_name), window_name, params) + for window_name, params in window_funcs + ] +) +def test_windowfunc_basics(window, window_name, params, xp): + window = getattr(windows, window_name) + if is_jax(xp) and window_name in ['taylor', 'chebwin']: + pytest.skip(reason=f'{window_name = }: item assignment') + if window_name in ['dpss']: + if is_cupy(xp): + pytest.skip(reason='dpss window is not implemented for cupy') + if is_torch(xp) and SCIPY_DEVICE != 'cpu': + pytest.skip(reason='needs eight_tridiagonal which is CPU only') + + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", "This window is not suitable", UserWarning) + # Check symmetry for odd and even lengths + w1 = window(8, *params, sym=True, xp=xp) + w2 = window(7, *params, sym=False, xp=xp) + xp_assert_close(w1[:-1], w2) + + w1 = window(9, *params, sym=True, xp=xp) + w2 = window(8, *params, sym=False, xp=xp) + xp_assert_close(w1[:-1], w2) + + # Check that functions run and output lengths are correct + assert window(6, *params, sym=True, xp=xp).shape[0] == 6 + assert window(6, *params, sym=False, xp=xp).shape[0] == 6 + assert window(7, *params, sym=True, xp=xp).shape[0] == 7 + assert window(7, *params, sym=False, xp=xp).shape[0] == 7 + + # Check invalid lengths + assert_raises(ValueError, window, 5.5, *params, xp=xp) + assert_raises(ValueError, window, -7, *params, xp=xp) + + # Check degenerate cases + xp_assert_equal(window(0, *params, sym=True, xp=xp), + xp.asarray([], dtype=xp.float64)) + xp_assert_equal(window(0, *params, sym=False, xp=xp), + xp.asarray([], dtype=xp.float64)) + xp_assert_equal(window(1, *params, sym=True, xp=xp), + xp.asarray([1.], dtype=xp.float64)) + xp_assert_equal(window(1, *params, sym=False, xp=xp), + xp.asarray([1.], dtype=xp.float64)) + + # Check dtype + assert window(0, *params, sym=True, xp=xp).dtype == xp.float64 + assert window(0, *params, sym=False, xp=xp).dtype == xp.float64 + assert window(1, *params, sym=True, xp=xp).dtype == xp.float64 + assert window(1, *params, sym=False, xp=xp).dtype == xp.float64 + assert window(6, *params, sym=True, xp=xp).dtype == xp.float64 + assert window(6, *params, sym=False, xp=xp).dtype == xp.float64 + + # Check normalization + assert xp.all(window(10, *params, sym=True, xp=xp) < 1.01) + assert xp.all(window(10, *params, sym=False, xp=xp) < 1.01) + assert xp.all(window(9, *params, sym=True, xp=xp) < 1.01) + assert xp.all(window(9, *params, sym=False, xp=xp) < 1.01) + + # Check that DFT-even spectrum is purely real for odd and even + res = fft(window(10, *params, sym=False, xp=xp)) + res = xp.imag(res) + xp_assert_close(res, xp.zeros_like(res), atol=1e-14) + + res = fft(window(11, *params, sym=False, xp=xp)) + res = xp.imag(res) + xp_assert_close(res, xp.zeros_like(res), atol=1e-14) + + +@make_xp_test_case(get_window) +def test_needs_params(xp): + for winstr in ['kaiser', 'ksr', 'kaiser_bessel_derived', 'kbd', + 'gaussian', 'gauss', 'gss', + 'general gaussian', 'general_gaussian', + 'general gauss', 'general_gauss', 'ggs', + 'dss', 'dpss', 'general cosine', 'general_cosine', + 'chebwin', 'cheb', 'general hamming', 'general_hamming', + ]: + assert_raises(ValueError, get_window, winstr, 7, xp=xp) + + +_winstr = ['barthann', + 'bartlett', + 'blackman', + 'blackmanharris', + 'bohman', + 'boxcar', + 'cosine', + 'flattop', + 'hamming', + 'nuttall', + 'parzen', + 'taylor', + 'exponential', + 'poisson', + 'tukey', + 'tuk', + 'triangle', + 'lanczos', + 'sinc', +] + + +@pytest.mark.parametrize( + 'window,winstr', + [ + make_xp_pytest_param(_WIN_FUNCS[winstr][0], winstr) + for winstr in _winstr + ] +) +@make_xp_test_case(get_window) +def test_not_needs_params(xp, window, winstr): + if is_jax(xp) and winstr in ['taylor']: + pytest.skip(reason=f'{winstr}: item assignment') + win = get_window(winstr, 7, xp=xp) + assert win.shape[0] == 7 + + +@make_xp_test_case(windows.lanczos) +def test_symmetric(xp): + + for win in [windows.lanczos]: + # Even sampling points + w = win(4096, xp=xp) + flip = array_namespace(w).flip + error = xp.max(xp.abs(w - flip(w))) + xp_assert_equal(error, xp.asarray(0.0), check_dtype=False, check_0d=False) + + # Odd sampling points + w = win(4097, xp=xp) + error = xp.max(xp.abs(w - flip(w))) + xp_assert_equal(error, xp.asarray(0.0), check_dtype=False, check_0d=False) diff --git a/venv/lib/python3.12/site-packages/scipy/signal/waveforms.py b/venv/lib/python3.12/site-packages/scipy/signal/waveforms.py new file mode 100644 index 0000000000000000000000000000000000000000..30e71348d04276a66470a4053d97cefc60f7136e --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/waveforms.py @@ -0,0 +1,20 @@ +# This file is not meant for public use and will be removed in SciPy v2.0.0. +# Use the `scipy.signal` namespace for importing the functions +# included below. + +from scipy._lib.deprecation import _sub_module_deprecation + +__all__ = [ # noqa: F822 + 'sawtooth', 'square', 'gausspulse', 'chirp', 'sweep_poly', + 'unit_impulse', +] + + +def __dir__(): + return __all__ + + +def __getattr__(name): + return _sub_module_deprecation(sub_package="signal", module="waveforms", + private_modules=["_waveforms"], all=__all__, + attribute=name) diff --git a/venv/lib/python3.12/site-packages/scipy/signal/wavelets.py b/venv/lib/python3.12/site-packages/scipy/signal/wavelets.py new file mode 100644 index 0000000000000000000000000000000000000000..fc897a2483536df7e995faaa29af621e25fe38c7 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/wavelets.py @@ -0,0 +1,17 @@ +# This file is not meant for public use and will be removed in SciPy v2.0.0. +# Use the `scipy.signal` namespace for importing the functions +# included below. + +from scipy._lib.deprecation import _sub_module_deprecation + +__all__: list[str] = [] + + +def __dir__(): + return __all__ + + +def __getattr__(name): + return _sub_module_deprecation(sub_package="signal", module="wavelets", + private_modules=["_wavelets"], all=__all__, + attribute=name) diff --git a/venv/lib/python3.12/site-packages/scipy/signal/windows/__init__.py b/venv/lib/python3.12/site-packages/scipy/signal/windows/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..f260df1367a5f9457b1f6e916def853c6c461924 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/windows/__init__.py @@ -0,0 +1,52 @@ +""" +Window functions (:mod:`scipy.signal.windows`) +============================================== + +The suite of window functions for filtering and spectral estimation. + +.. currentmodule:: scipy.signal.windows + +.. autosummary:: + :toctree: generated/ + + get_window -- Convenience function for creating various windows. + + barthann -- Bartlett-Hann window + bartlett -- Bartlett window + blackman -- Blackman window + blackmanharris -- Minimum 4-term Blackman-Harris window + bohman -- Bohman window + boxcar -- Boxcar window + chebwin -- Dolph-Chebyshev window + cosine -- Cosine window + dpss -- Discrete prolate spheroidal sequences + exponential -- Exponential window + flattop -- Flat top window + gaussian -- Gaussian window + general_cosine -- Generalized Cosine window + general_gaussian -- Generalized Gaussian window + general_hamming -- Generalized Hamming window + hamming -- Hamming window + hann -- Hann window + kaiser -- Kaiser window + kaiser_bessel_derived -- Kaiser-Bessel derived window + lanczos -- Lanczos window also known as a sinc window + nuttall -- Nuttall's minimum 4-term Blackman-Harris window + parzen -- Parzen window + taylor -- Taylor window + triang -- Triangular window + tukey -- Tukey window + +""" + +from ._windows import * + +# Deprecated namespaces, to be removed in v2.0.0 +from . import windows + +__all__ = ['boxcar', 'triang', 'parzen', 'bohman', 'blackman', 'nuttall', + 'blackmanharris', 'flattop', 'bartlett', 'barthann', + 'hamming', 'kaiser', 'kaiser_bessel_derived', 'gaussian', + 'general_gaussian', 'general_cosine', 'general_hamming', + 'chebwin', 'cosine', 'hann', 'exponential', 'tukey', 'taylor', + 'get_window', 'dpss', 'lanczos'] diff --git a/venv/lib/python3.12/site-packages/scipy/signal/windows/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/windows/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b02fd1c4ea8f3f1462ee8b1294afcc8ef19c6d27 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/signal/windows/__pycache__/__init__.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/signal/windows/__pycache__/_windows.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/windows/__pycache__/_windows.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..668a0e28058f24d02756db1e4bfd79f57174549a --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/windows/__pycache__/_windows.cpython-312.pyc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dbb2a464ebf8261df0573828b4b4c8a48d3be20250ebc892a9594b5121501a0a +size 103532 diff --git a/venv/lib/python3.12/site-packages/scipy/signal/windows/__pycache__/windows.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/signal/windows/__pycache__/windows.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d35897741f253e12dc513a96044b525e3bc9a145 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/signal/windows/__pycache__/windows.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/signal/windows/_windows.py b/venv/lib/python3.12/site-packages/scipy/signal/windows/_windows.py new file mode 100644 index 0000000000000000000000000000000000000000..be9acf3ac5f0cfc5dd4a3728203e87a1be1babb8 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/windows/_windows.py @@ -0,0 +1,2608 @@ +"""The suite of window functions.""" + +import math +import numbers +import operator +import warnings +from scipy._lib import doccer + +from scipy import linalg, special, fft as sp_fft +from scipy._external.array_api_compat import numpy as np_compat +from scipy._lib._array_api import array_namespace, xp_device +from scipy._lib._array_api import xp_capabilities +from scipy._external import array_api_extra as xpx + +__all__ = ['boxcar', 'triang', 'parzen', 'bohman', 'blackman', 'nuttall', + 'blackmanharris', 'flattop', 'bartlett', 'barthann', + 'hamming', 'kaiser', 'kaiser_bessel_derived', 'gaussian', + 'general_cosine', 'general_gaussian', 'general_hamming', + 'chebwin', 'cosine', 'hann', 'exponential', 'tukey', 'taylor', + 'dpss', 'get_window', 'lanczos'] + + +def _len_guards(M): + """Handle small or incorrect window lengths""" + if int(M) != M or M < 0: + raise ValueError('Window length M must be a non-negative integer') + return M <= 1 + + +def _extend(M, sym): + """Extend window by 1 sample if needed for DFT-even symmetry""" + if not sym: + return M + 1, True + else: + return M, False + + +def _truncate(w, needed): + """Truncate window by 1 sample if needed for DFT-even symmetry""" + if needed: + return w[:-1] + else: + return w + + +def _namespace(xp): + """A shim for the `device` arg of `np.asarray(x, device=device)` and acos/arccos. + + Will be able to replace with `np_compat if xp is None else xp` when we drop + support for numpy 1.x and cupy 13.x + """ + return np_compat if xp is None else array_namespace(xp.empty(0)) + + +def _general_cosine_impl(M, a, xp, device, sym=True): + if _len_guards(M): + return xp.ones(M, dtype=xp.float64, device=device) + M, needs_trunc = _extend(M, sym) + + fac = xp.linspace(-xp.pi, xp.pi, M, dtype=xp.float64, device=device) + w = xp.zeros(M, dtype=xp.float64, device=device) + for k in range(a.shape[0]): + w += a[k] * xp.cos(k * fac) + + return _truncate(w, needs_trunc) + + +@xp_capabilities() +def general_cosine(M, a, sym=True): + r""" + Generic weighted sum of cosine terms window. + + Parameters + ---------- + M : int + Number of points in the output window + a : array_like + Sequence of weighting coefficients. This uses the convention of being + centered on the origin, so these will typically all be positive + numbers, not alternating sign. + sym : bool, optional + When True (default), generates a symmetric window, for use in filter + design. + When False, generates a periodic window, for use in spectral analysis. + + Returns + ------- + w : ndarray + The array of window values. + + References + ---------- + .. [1] A. Nuttall, "Some windows with very good sidelobe behavior," IEEE + Transactions on Acoustics, Speech, and Signal Processing, vol. 29, + no. 1, pp. 84-91, Feb 1981. :doi:`10.1109/TASSP.1981.1163506`. + .. [2] Heinzel G. et al., "Spectrum and spectral density estimation by the + Discrete Fourier transform (DFT), including a comprehensive list of + window functions and some new flat-top windows", February 15, 2002 + https://holometer.fnal.gov/GH_FFT.pdf + + Examples + -------- + Heinzel describes a flat-top window named "HFT90D" with formula: [2]_ + + .. math:: w_j = 1 - 1.942604 \cos(z) + 1.340318 \cos(2z) + - 0.440811 \cos(3z) + 0.043097 \cos(4z) + + where + + .. math:: z = \frac{2 \pi j}{N}, j = 0...N - 1 + + Since this uses the convention of starting at the origin, to reproduce the + window, we need to convert every other coefficient to a positive number: + + >>> HFT90D = [1, 1.942604, 1.340318, 0.440811, 0.043097] + + The paper states that the highest sidelobe is at -90.2 dB. Reproduce + Figure 42 by plotting the window and its frequency response, and confirm + the sidelobe level in red: + + >>> import numpy as np + >>> from scipy.signal.windows import general_cosine + >>> from scipy.fft import fft, fftshift + >>> import matplotlib.pyplot as plt + + >>> window = general_cosine(1000, HFT90D, sym=False) + >>> plt.plot(window) + >>> plt.title("HFT90D window") + >>> plt.ylabel("Amplitude") + >>> plt.xlabel("Sample") + + >>> plt.figure() + >>> A = fft(window, 10000) / (len(window)/2.0) + >>> freq = np.linspace(-0.5, 0.5, len(A)) + >>> response = np.abs(fftshift(A / abs(A).max())) + >>> response = 20 * np.log10(np.maximum(response, 1e-10)) + >>> plt.plot(freq, response) + >>> plt.axis([-50/1000, 50/1000, -140, 0]) + >>> plt.title("Frequency response of the HFT90D window") + >>> plt.ylabel("Normalized magnitude [dB]") + >>> plt.xlabel("Normalized frequency [cycles per sample]") + >>> plt.axhline(-90.2, color='red') + >>> plt.show() + """ + xp = array_namespace(a) + a = xp.asarray(a) + device = xp_device(a) + return _general_cosine_impl(M, a, xp, device, sym=sym) + + +@xp_capabilities() +def boxcar(M, sym=True, *, xp=None, device=None): + """Return a boxcar or rectangular window. + + Also known as a rectangular window or Dirichlet window, this is equivalent + to no window at all. + + Parameters + ---------- + M : int + Number of points in the output window. If zero, an empty array + is returned. An exception is thrown when it is negative. + sym : bool, optional + Whether the window is symmetric. (Has no effect for boxcar.) + %(xp_device_snippet)s + + Returns + ------- + w : ndarray + The window, with the maximum value normalized to 1. + + Examples + -------- + Plot the window and its frequency response: + + >>> import numpy as np + >>> from scipy import signal + >>> from scipy.fft import fft, fftshift + >>> import matplotlib.pyplot as plt + + >>> window = signal.windows.boxcar(51) + >>> plt.plot(window) + >>> plt.title("Boxcar window") + >>> plt.ylabel("Amplitude") + >>> plt.xlabel("Sample") + + >>> plt.figure() + >>> A = fft(window, 2048) / (len(window)/2.0) + >>> freq = np.linspace(-0.5, 0.5, len(A)) + >>> response = 20 * np.log10(np.abs(fftshift(A / abs(A).max()))) + >>> plt.plot(freq, response) + >>> plt.axis([-0.5, 0.5, -120, 0]) + >>> plt.title("Frequency response of the boxcar window") + >>> plt.ylabel("Normalized magnitude [dB]") + >>> plt.xlabel("Normalized frequency [cycles per sample]") + + """ + xp = _namespace(xp) + + if _len_guards(M): + return xp.ones(M, dtype=xp.float64, device=device) + M, needs_trunc = _extend(M, sym) + + w = xp.ones(M, dtype=xp.float64, device=device) + + return _truncate(w, needs_trunc) + + +@xp_capabilities() +def triang(M, sym=True, *, xp=None, device=None): + """Return a triangular window. + + Parameters + ---------- + M : int + Number of points in the output window. If zero, an empty array + is returned. An exception is thrown when it is negative. + sym : bool, optional + When True (default), generates a symmetric window, for use in filter + design. + When False, generates a periodic window, for use in spectral analysis. + %(xp_device_snippet)s + + Returns + ------- + w : ndarray + The window, with the maximum value normalized to 1 (though the value 1 + does not appear if `M` is even and `sym` is True). + + See Also + -------- + bartlett : A triangular window that touches zero + + Examples + -------- + Plot the window and its frequency response: + + >>> import numpy as np + >>> from scipy import signal + >>> from scipy.fft import fft, fftshift + >>> import matplotlib.pyplot as plt + + >>> window = signal.windows.triang(51) + >>> plt.plot(window) + >>> plt.title("Triangular window") + >>> plt.ylabel("Amplitude") + >>> plt.xlabel("Sample") + + >>> plt.figure() + >>> A = fft(window, 2048) / (len(window)/2.0) + >>> freq = np.linspace(-0.5, 0.5, len(A)) + >>> response = np.abs(fftshift(A / abs(A).max())) + >>> response = 20 * np.log10(np.maximum(response, 1e-10)) + >>> plt.plot(freq, response) + >>> plt.axis([-0.5, 0.5, -120, 0]) + >>> plt.title("Frequency response of the triangular window") + >>> plt.ylabel("Normalized magnitude [dB]") + >>> plt.xlabel("Normalized frequency [cycles per sample]") + + """ + xp = _namespace(xp) + + if _len_guards(M): + return xp.ones(M, dtype=xp.float64, device=device) + M, needs_trunc = _extend(M, sym) + + n = xp.arange(1, (M + 1) // 2 + 1, dtype=xp.float64, device=device) + if M % 2 == 0: + w = (2 * n - 1.0) / M + w = xp.concat([w, xp.flip(w)]) + else: + w = 2 * n / (M + 1.0) + w = xp.concat([w, xp.flip(w[:-1])]) + + return _truncate(w, needs_trunc) + + +@xp_capabilities() +def parzen(M, sym=True, *, xp=None, device=None): + """Return a Parzen window. + + Parameters + ---------- + M : int + Number of points in the output window. If zero, an empty array + is returned. An exception is thrown when it is negative. + sym : bool, optional + When True (default), generates a symmetric window, for use in filter + design. + When False, generates a periodic window, for use in spectral analysis. + %(xp_device_snippet)s + + Returns + ------- + w : ndarray + The window, with the maximum value normalized to 1 (though the value 1 + does not appear if `M` is even and `sym` is True). + + References + ---------- + .. [1] E. Parzen, "Mathematical Considerations in the Estimation of + Spectra", Technometrics, Vol. 3, No. 2 (May, 1961), pp. 167-190 + + Examples + -------- + Plot the window and its frequency response: + + >>> import numpy as np + >>> from scipy import signal + >>> from scipy.fft import fft, fftshift + >>> import matplotlib.pyplot as plt + + >>> window = signal.windows.parzen(51) + >>> plt.plot(window) + >>> plt.title("Parzen window") + >>> plt.ylabel("Amplitude") + >>> plt.xlabel("Sample") + + >>> plt.figure() + >>> A = fft(window, 2048) / (len(window)/2.0) + >>> freq = np.linspace(-0.5, 0.5, len(A)) + >>> response = 20 * np.log10(np.abs(fftshift(A / abs(A).max()))) + >>> plt.plot(freq, response) + >>> plt.axis([-0.5, 0.5, -120, 0]) + >>> plt.title("Frequency response of the Parzen window") + >>> plt.ylabel("Normalized magnitude [dB]") + >>> plt.xlabel("Normalized frequency [cycles per sample]") + + """ + xp = _namespace(xp) + + if _len_guards(M): + return xp.ones(M, dtype=xp.float64, device=device) + M, needs_trunc = _extend(M, sym) + + n = xp.arange(-(M - 1) / 2.0, (M - 1) / 2.0 + 0.5, 1.0, + dtype=xp.float64, device=device) + w = xp.where(abs(n) <= (M - 1) / 4.0, + (1 - 6 * (abs(n) / (M / 2.0)) ** 2.0 + + 6 * (abs(n) / (M / 2.0)) ** 3.0), + 2 * (1 - abs(n) / (M / 2.0)) ** 3.0) + return _truncate(w, needs_trunc) + + +@xp_capabilities() +def bohman(M, sym=True, *, xp=None, device=None): + """Return a Bohman window. + + Parameters + ---------- + M : int + Number of points in the output window. If zero, an empty array + is returned. An exception is thrown when it is negative. + sym : bool, optional + When True (default), generates a symmetric window, for use in filter + design. + When False, generates a periodic window, for use in spectral analysis. + %(xp_device_snippet)s + + Returns + ------- + w : ndarray + The window, with the maximum value normalized to 1 (though the value 1 + does not appear if `M` is even and `sym` is True). + + Examples + -------- + Plot the window and its frequency response: + + >>> import numpy as np + >>> from scipy import signal + >>> from scipy.fft import fft, fftshift + >>> import matplotlib.pyplot as plt + + >>> window = signal.windows.bohman(51) + >>> plt.plot(window) + >>> plt.title("Bohman window") + >>> plt.ylabel("Amplitude") + >>> plt.xlabel("Sample") + + >>> plt.figure() + >>> A = fft(window, 2047) / (len(window)/2.0) + >>> freq = np.linspace(-0.5, 0.5, len(A)) + >>> response = 20 * np.log10(np.abs(fftshift(A / abs(A).max()))) + >>> plt.plot(freq, response) + >>> plt.axis([-0.5, 0.5, -120, 0]) + >>> plt.title("Frequency response of the Bohman window") + >>> plt.ylabel("Normalized magnitude [dB]") + >>> plt.xlabel("Normalized frequency [cycles per sample]") + + """ + xp = _namespace(xp) + + if _len_guards(M): + return xp.ones(M, dtype=xp.float64, device=device) + M, needs_trunc = _extend(M, sym) + + fac = abs(xp.linspace(-1, 1, M, dtype=xp.float64, device=device)[1:-1]) + w = (1 - fac) * xp.cos(xp.pi * fac) + 1.0 / xp.pi * xp.sin(xp.pi * fac) + one = xp.zeros(1, dtype=xp.float64, device=device) + w = xp.concat([one, w, one]) + + return _truncate(w, needs_trunc) + + +@xp_capabilities() +def blackman(M, sym=True, *, xp=None, device=None): + r""" + Return a Blackman window. + + The Blackman window is a taper formed by using the first three terms of + a summation of cosines. It was designed to have close to the minimal + leakage possible. It is close to optimal, only slightly worse than a + Kaiser window. + + Parameters + ---------- + M : int + Number of points in the output window. If zero, an empty array + is returned. An exception is thrown when it is negative. + sym : bool, optional + When True (default), generates a symmetric window, for use in filter + design. + When False, generates a periodic window, for use in spectral analysis. + %(xp_device_snippet)s + + Returns + ------- + w : ndarray + The window, with the maximum value normalized to 1 (though the value 1 + does not appear if `M` is even and `sym` is True). + + Notes + ----- + The Blackman window is defined as + + .. math:: w(n) = 0.42 - 0.5 \cos(2\pi n/M) + 0.08 \cos(4\pi n/M) + + The "exact Blackman" window was designed to null out the third and fourth + sidelobes, but has discontinuities at the boundaries, resulting in a + 6 dB/oct fall-off. This window is an approximation of the "exact" window, + which does not null the sidelobes as well, but is smooth at the edges, + improving the fall-off rate to 18 dB/oct. [3]_ + + Most references to the Blackman window come from the signal processing + literature, where it is used as one of many windowing functions for + smoothing values. It is also known as an apodization (which means + "removing the foot", i.e. smoothing discontinuities at the beginning + and end of the sampled signal) or tapering function. It is known as a + "near optimal" tapering function, almost as good (by some measures) + as the Kaiser window. + + References + ---------- + .. [1] Blackman, R.B. and Tukey, J.W., (1958) The measurement of power + spectra, Dover Publications, New York. + .. [2] Oppenheim, A.V., and R.W. Schafer. Discrete-Time Signal Processing. + Upper Saddle River, NJ: Prentice-Hall, 1999, pp. 468-471. + .. [3] Harris, Fredric J. (Jan 1978). "On the use of Windows for Harmonic + Analysis with the Discrete Fourier Transform". Proceedings of the + IEEE 66 (1): 51-83. :doi:`10.1109/PROC.1978.10837`. + + Examples + -------- + Plot the window and its frequency response: + + >>> import numpy as np + >>> from scipy import signal + >>> from scipy.fft import fft, fftshift + >>> import matplotlib.pyplot as plt + + >>> window = signal.windows.blackman(51) + >>> plt.plot(window) + >>> plt.title("Blackman window") + >>> plt.ylabel("Amplitude") + >>> plt.xlabel("Sample") + + >>> plt.figure() + >>> A = fft(window, 2048) / (len(window)/2.0) + >>> freq = np.linspace(-0.5, 0.5, len(A)) + >>> response = np.abs(fftshift(A / abs(A).max())) + >>> response = 20 * np.log10(np.maximum(response, 1e-10)) + >>> plt.plot(freq, response) + >>> plt.axis([-0.5, 0.5, -120, 0]) + >>> plt.title("Frequency response of the Blackman window") + >>> plt.ylabel("Normalized magnitude [dB]") + >>> plt.xlabel("Normalized frequency [cycles per sample]") + + """ + # Docstring adapted from NumPy's blackman function + xp = _namespace(xp) + a = xp.asarray([0.42, 0.50, 0.08], dtype=xp.float64, device=device) + device = xp_device(a) + return _general_cosine_impl(M, a, xp, device, sym=sym) + + +@xp_capabilities() +def nuttall(M, sym=True, *, xp=None, device=None): + """Return a minimum 4-term Blackman-Harris window according to Nuttall. + + This variation is called "Nuttall4c" by Heinzel. [2]_ + + Parameters + ---------- + M : int + Number of points in the output window. If zero, an empty array + is returned. An exception is thrown when it is negative. + sym : bool, optional + When True (default), generates a symmetric window, for use in filter + design. + When False, generates a periodic window, for use in spectral analysis. + %(xp_device_snippet)s + + Returns + ------- + w : ndarray + The window, with the maximum value normalized to 1 (though the value 1 + does not appear if `M` is even and `sym` is True). + + References + ---------- + .. [1] A. Nuttall, "Some windows with very good sidelobe behavior," IEEE + Transactions on Acoustics, Speech, and Signal Processing, vol. 29, + no. 1, pp. 84-91, Feb 1981. :doi:`10.1109/TASSP.1981.1163506`. + .. [2] Heinzel G. et al., "Spectrum and spectral density estimation by the + Discrete Fourier transform (DFT), including a comprehensive list of + window functions and some new flat-top windows", February 15, 2002 + https://holometer.fnal.gov/GH_FFT.pdf + + Examples + -------- + Plot the window and its frequency response: + + >>> import numpy as np + >>> from scipy import signal + >>> from scipy.fft import fft, fftshift + >>> import matplotlib.pyplot as plt + + >>> window = signal.windows.nuttall(51) + >>> plt.plot(window) + >>> plt.title("Nuttall window") + >>> plt.ylabel("Amplitude") + >>> plt.xlabel("Sample") + + >>> plt.figure() + >>> A = fft(window, 2048) / (len(window)/2.0) + >>> freq = np.linspace(-0.5, 0.5, len(A)) + >>> response = 20 * np.log10(np.abs(fftshift(A / abs(A).max()))) + >>> plt.plot(freq, response) + >>> plt.axis([-0.5, 0.5, -120, 0]) + >>> plt.title("Frequency response of the Nuttall window") + >>> plt.ylabel("Normalized magnitude [dB]") + >>> plt.xlabel("Normalized frequency [cycles per sample]") + + """ + xp = _namespace(xp) + a = xp.asarray( + [0.3635819, 0.4891775, 0.1365995, 0.0106411], dtype=xp.float64, device=device + ) + device = xp_device(a) + return _general_cosine_impl(M, a, xp, device, sym=sym) + + +@xp_capabilities() +def blackmanharris(M, sym=True, *, xp=None, device=None): + """Return a minimum 4-term Blackman-Harris window. + + Parameters + ---------- + M : int + Number of points in the output window. If zero, an empty array + is returned. An exception is thrown when it is negative. + sym : bool, optional + When True (default), generates a symmetric window, for use in filter + design. + When False, generates a periodic window, for use in spectral analysis. + %(xp_device_snippet)s + + Returns + ------- + w : ndarray + The window, with the maximum value normalized to 1 (though the value 1 + does not appear if `M` is even and `sym` is True). + + Examples + -------- + Plot the window and its frequency response: + + >>> import numpy as np + >>> from scipy import signal + >>> from scipy.fft import fft, fftshift + >>> import matplotlib.pyplot as plt + + >>> window = signal.windows.blackmanharris(51) + >>> plt.plot(window) + >>> plt.title("Blackman-Harris window") + >>> plt.ylabel("Amplitude") + >>> plt.xlabel("Sample") + + >>> plt.figure() + >>> A = fft(window, 2048) / (len(window)/2.0) + >>> freq = np.linspace(-0.5, 0.5, len(A)) + >>> response = 20 * np.log10(np.abs(fftshift(A / abs(A).max()))) + >>> plt.plot(freq, response) + >>> plt.axis([-0.5, 0.5, -120, 0]) + >>> plt.title("Frequency response of the Blackman-Harris window") + >>> plt.ylabel("Normalized magnitude [dB]") + >>> plt.xlabel("Normalized frequency [cycles per sample]") + + """ + xp = _namespace(xp) + a = xp.asarray( + [0.35875, 0.48829, 0.14128, 0.01168], dtype=xp.float64, device=device + ) + device = xp_device(a) + return _general_cosine_impl(M, a, xp, device, sym=sym) + + +@xp_capabilities() +def flattop(M, sym=True, *, xp=None, device=None): + """Return a flat top window. + + Parameters + ---------- + M : int + Number of points in the output window. If zero, an empty array + is returned. An exception is thrown when it is negative. + sym : bool, optional + When True (default), generates a symmetric window, for use in filter + design. + When False, generates a periodic window, for use in spectral analysis. + %(xp_device_snippet)s + + Returns + ------- + w : ndarray + The window, with the maximum value normalized to 1 (though the value 1 + does not appear if `M` is even and `sym` is True). + + Notes + ----- + Flat top windows are used for taking accurate measurements of signal + amplitude in the frequency domain, with minimal scalloping error from the + center of a frequency bin to its edges, compared to others. This is a + 5th-order cosine window, with the 5 terms optimized to make the main lobe + maximally flat. [1]_ + + References + ---------- + .. [1] D'Antona, Gabriele, and A. Ferrero, "Digital Signal Processing for + Measurement Systems", Springer Media, 2006, p. 70 + :doi:`10.1007/0-387-28666-7`. + + Examples + -------- + Plot the window and its frequency response: + + >>> import numpy as np + >>> from scipy import signal + >>> from scipy.fft import fft, fftshift + >>> import matplotlib.pyplot as plt + + >>> window = signal.windows.flattop(51) + >>> plt.plot(window) + >>> plt.title("Flat top window") + >>> plt.ylabel("Amplitude") + >>> plt.xlabel("Sample") + + >>> plt.figure() + >>> A = fft(window, 2048) / (len(window)/2.0) + >>> freq = np.linspace(-0.5, 0.5, len(A)) + >>> response = 20 * np.log10(np.abs(fftshift(A / abs(A).max()))) + >>> plt.plot(freq, response) + >>> plt.axis([-0.5, 0.5, -120, 0]) + >>> plt.title("Frequency response of the flat top window") + >>> plt.ylabel("Normalized magnitude [dB]") + >>> plt.xlabel("Normalized frequency [cycles per sample]") + + """ + xp = _namespace(xp) + a = xp.asarray( + [0.21557895, 0.41663158, 0.277263158, 0.083578947, 0.006947368], + dtype=xp.float64, device=device + ) + device = xp_device(a) + return _general_cosine_impl(M, a, xp, device, sym=sym) + + +@xp_capabilities() +def bartlett(M, sym=True, *, xp=None, device=None): + r""" + Return a Bartlett window. + + The Bartlett window is very similar to a triangular window, except + that the end points are at zero. It is often used in signal + processing for tapering a signal, without generating too much + ripple in the frequency domain. + + Parameters + ---------- + M : int + Number of points in the output window. If zero, an empty array + is returned. An exception is thrown when it is negative. + sym : bool, optional + When True (default), generates a symmetric window, for use in filter + design. + When False, generates a periodic window, for use in spectral analysis. + %(xp_device_snippet)s + + Returns + ------- + w : ndarray + The triangular window, with the first and last samples equal to zero + and the maximum value normalized to 1 (though the value 1 does not + appear if `M` is even and `sym` is True). + + See Also + -------- + triang : A triangular window that does not touch zero at the ends + + Notes + ----- + The Bartlett window is defined as + + .. math:: w(n) = \frac{2}{M-1} \left( + \frac{M-1}{2} - \left|n - \frac{M-1}{2}\right| + \right) + + Most references to the Bartlett window come from the signal + processing literature, where it is used as one of many windowing + functions for smoothing values. Note that convolution with this + window produces linear interpolation. It is also known as an + apodization (which means"removing the foot", i.e. smoothing + discontinuities at the beginning and end of the sampled signal) or + tapering function. The Fourier transform of the Bartlett is the product + of two sinc functions. + Note the excellent discussion in Kanasewich. [2]_ + + References + ---------- + .. [1] M.S. Bartlett, "Periodogram Analysis and Continuous Spectra", + Biometrika 37, 1-16, 1950. + .. [2] E.R. Kanasewich, "Time Sequence Analysis in Geophysics", + The University of Alberta Press, 1975, pp. 109-110. + .. [3] A.V. Oppenheim and R.W. Schafer, "Discrete-Time Signal + Processing", Prentice-Hall, 1999, pp. 468-471. + .. [4] Wikipedia, "Window function", + https://en.wikipedia.org/wiki/Window_function + .. [5] W.H. Press, B.P. Flannery, S.A. Teukolsky, and W.T. Vetterling, + "Numerical Recipes", Cambridge University Press, 1986, page 429. + + Examples + -------- + Plot the window and its frequency response: + + >>> import numpy as np + >>> from scipy import signal + >>> from scipy.fft import fft, fftshift + >>> import matplotlib.pyplot as plt + + >>> window = signal.windows.bartlett(51) + >>> plt.plot(window) + >>> plt.title("Bartlett window") + >>> plt.ylabel("Amplitude") + >>> plt.xlabel("Sample") + + >>> plt.figure() + >>> A = fft(window, 2048) / (len(window)/2.0) + >>> freq = np.linspace(-0.5, 0.5, len(A)) + >>> response = 20 * np.log10(np.abs(fftshift(A / abs(A).max()))) + >>> plt.plot(freq, response) + >>> plt.axis([-0.5, 0.5, -120, 0]) + >>> plt.title("Frequency response of the Bartlett window") + >>> plt.ylabel("Normalized magnitude [dB]") + >>> plt.xlabel("Normalized frequency [cycles per sample]") + + """ + # Docstring adapted from NumPy's bartlett function + xp = _namespace(xp) + + if _len_guards(M): + return xp.ones(M, dtype=xp.float64, device=device) + M, needs_trunc = _extend(M, sym) + + n = xp.arange(0, M, dtype=xp.float64, device=device) + + # cf https://github.com/data-apis/array-api-strict/issues/77 + w = xp.where(n <= (M - 1) / 2.0, + 2.0 * n / (M - 1), 2.0 - 2.0 * n / (M - 1)) + + return _truncate(w, needs_trunc) + + +@xp_capabilities() +def hann(M, sym=True, *, xp=None, device=None): + r""" + Return a Hann window. + + The Hann window is a taper formed by using a raised cosine or sine-squared + with ends that touch zero. + + Parameters + ---------- + M : int + Number of points in the output window. If zero, an empty array + is returned. An exception is thrown when it is negative. + sym : bool, optional + When True (default), generates a symmetric window, for use in filter + design. + When False, generates a periodic window, for use in spectral analysis. + %(xp_device_snippet)s + + Returns + ------- + w : ndarray + The window, with the maximum value normalized to 1 (though the value 1 + does not appear if `M` is even and `sym` is True). + + Notes + ----- + The Hann window is defined as + + .. math:: w(n) = 0.5 - 0.5 \cos\left(\frac{2\pi{n}}{M-1}\right) + \qquad 0 \leq n \leq M-1 + + The window was named for Julius von Hann, an Austrian meteorologist. It is + also known as the Cosine Bell. It is sometimes erroneously referred to as + the "Hanning" window, from the use of "hann" as a verb in the original + paper and confusion with the very similar Hamming window. + + Most references to the Hann window come from the signal processing + literature, where it is used as one of many windowing functions for + smoothing values. It is also known as an apodization (which means + "removing the foot", i.e. smoothing discontinuities at the beginning + and end of the sampled signal) or tapering function. + + References + ---------- + .. [1] Blackman, R.B. and Tukey, J.W., (1958) The measurement of power + spectra, Dover Publications, New York. + .. [2] E.R. Kanasewich, "Time Sequence Analysis in Geophysics", + The University of Alberta Press, 1975, pp. 106-108. + .. [3] Wikipedia, "Window function", + https://en.wikipedia.org/wiki/Window_function + .. [4] W.H. Press, B.P. Flannery, S.A. Teukolsky, and W.T. Vetterling, + "Numerical Recipes", Cambridge University Press, 1986, page 425. + + Examples + -------- + Plot the window and its frequency response: + + >>> import numpy as np + >>> from scipy import signal + >>> from scipy.fft import fft, fftshift + >>> import matplotlib.pyplot as plt + + >>> window = signal.windows.hann(51) + >>> plt.plot(window) + >>> plt.title("Hann window") + >>> plt.ylabel("Amplitude") + >>> plt.xlabel("Sample") + + >>> plt.figure() + >>> A = fft(window, 2048) / (len(window)/2.0) + >>> freq = np.linspace(-0.5, 0.5, len(A)) + >>> response = np.abs(fftshift(A / abs(A).max())) + >>> response = 20 * np.log10(np.maximum(response, 1e-10)) + >>> plt.plot(freq, response) + >>> plt.axis([-0.5, 0.5, -120, 0]) + >>> plt.title("Frequency response of the Hann window") + >>> plt.ylabel("Normalized magnitude [dB]") + >>> plt.xlabel("Normalized frequency [cycles per sample]") + + """ + # Docstring adapted from NumPy's hanning function + return general_hamming(M, 0.5, sym, xp=xp, device=device) + + +@xp_capabilities() +def tukey(M, alpha=0.5, sym=True, *, xp=None, device=None): + r"""Return a Tukey window, also known as a tapered cosine window. + + Parameters + ---------- + M : int + Number of points in the output window. If zero, an empty array + is returned. An exception is thrown when it is negative. + alpha : float, optional + Shape parameter of the Tukey window, representing the fraction of the + window inside the cosine tapered region. + If zero, the Tukey window is equivalent to a rectangular window. + If one, the Tukey window is equivalent to a Hann window. + sym : bool, optional + When True (default), generates a symmetric window, for use in filter + design. + When False, generates a periodic window, for use in spectral analysis. + %(xp_device_snippet)s + + Returns + ------- + w : ndarray + The window, with the maximum value normalized to 1 (though the value 1 + does not appear if `M` is even and `sym` is True). + + References + ---------- + .. [1] Harris, Fredric J. (Jan 1978). "On the use of Windows for Harmonic + Analysis with the Discrete Fourier Transform". Proceedings of the + IEEE 66 (1): 51-83. :doi:`10.1109/PROC.1978.10837` + .. [2] Wikipedia, "Window function", + https://en.wikipedia.org/wiki/Window_function#Tukey_window + + Examples + -------- + Plot the window and its frequency response: + + >>> import numpy as np + >>> from scipy import signal + >>> from scipy.fft import fft, fftshift + >>> import matplotlib.pyplot as plt + + >>> window = signal.windows.tukey(51) + >>> plt.plot(window) + >>> plt.title("Tukey window") + >>> plt.ylabel("Amplitude") + >>> plt.xlabel("Sample") + >>> plt.ylim([0, 1.1]) + + >>> plt.figure() + >>> A = fft(window, 2048) / (len(window)/2.0) + >>> freq = np.linspace(-0.5, 0.5, len(A)) + >>> response = 20 * np.log10(np.abs(fftshift(A / abs(A).max()))) + >>> plt.plot(freq, response) + >>> plt.axis([-0.5, 0.5, -120, 0]) + >>> plt.title("Frequency response of the Tukey window") + >>> plt.ylabel("Normalized magnitude [dB]") + >>> plt.xlabel("Normalized frequency [cycles per sample]") + + """ + xp = _namespace(xp) + + if _len_guards(M): + return xp.ones(M, dtype=xp.float64, device=device) + + if alpha <= 0: + return xp.ones(M, dtype=xp.float64, device=device) + elif alpha >= 1.0: + return hann(M, sym=sym, xp=xp, device=device) + + M, needs_trunc = _extend(M, sym) + + n = xp.arange(0, M, dtype=xp.float64, device=device) + width = int(math.floor(alpha*(M-1)/2.0)) + n1 = n[0:width+1] + n2 = n[width+1:M-width-1] + n3 = n[M-width-1:] + + w1 = 0.5 * (1 + xp.cos(xp.pi * (-1 + 2.0*n1/alpha/(M-1)))) + w2 = xp.ones(n2.shape, device=device) + w3 = 0.5 * (1 + xp.cos(xp.pi * (-2.0/alpha + 1 + 2.0*n3/alpha/(M-1)))) + + w = xp.concat((w1, w2, w3)) + + return _truncate(w, needs_trunc) + + +@xp_capabilities() +def barthann(M, sym=True, *, xp=None, device=None): + """Return a modified Bartlett-Hann window. + + Parameters + ---------- + M : int + Number of points in the output window. If zero, an empty array + is returned. An exception is thrown when it is negative. + sym : bool, optional + When True (default), generates a symmetric window, for use in filter + design. + When False, generates a periodic window, for use in spectral analysis. + %(xp_device_snippet)s + + Returns + ------- + w : ndarray + The window, with the maximum value normalized to 1 (though the value 1 + does not appear if `M` is even and `sym` is True). + + Examples + -------- + Plot the window and its frequency response: + + >>> import numpy as np + >>> from scipy import signal + >>> from scipy.fft import fft, fftshift + >>> import matplotlib.pyplot as plt + + >>> window = signal.windows.barthann(51) + >>> plt.plot(window) + >>> plt.title("Bartlett-Hann window") + >>> plt.ylabel("Amplitude") + >>> plt.xlabel("Sample") + + >>> plt.figure() + >>> A = fft(window, 2048) / (len(window)/2.0) + >>> freq = np.linspace(-0.5, 0.5, len(A)) + >>> response = 20 * np.log10(np.abs(fftshift(A / abs(A).max()))) + >>> plt.plot(freq, response) + >>> plt.axis([-0.5, 0.5, -120, 0]) + >>> plt.title("Frequency response of the Bartlett-Hann window") + >>> plt.ylabel("Normalized magnitude [dB]") + >>> plt.xlabel("Normalized frequency [cycles per sample]") + + """ + xp = _namespace(xp) + + if _len_guards(M): + return xp.ones(M, dtype=xp.float64, device=device) + M, needs_trunc = _extend(M, sym) + + n = xp.arange(0, M, dtype=xp.float64, device=device) + fac = abs(n / (M - 1.0) - 0.5) + w = 0.62 - 0.48 * fac + 0.38 * xp.cos(2 * xp.pi * fac) + + return _truncate(w, needs_trunc) + + +@xp_capabilities() +def general_hamming(M, alpha, sym=True, *, xp=None, device=None): + r"""Return a generalized Hamming window. + + The generalized Hamming window is constructed by multiplying a rectangular + window by one period of a cosine function [1]_. + + Parameters + ---------- + M : int + Number of points in the output window. If zero, an empty array + is returned. An exception is thrown when it is negative. + alpha : float + The window coefficient, :math:`\alpha` + sym : bool, optional + When True (default), generates a symmetric window, for use in filter + design. + When False, generates a periodic window, for use in spectral analysis. + %(xp_device_snippet)s + + Returns + ------- + w : ndarray + The window, with the maximum value normalized to 1 (though the value 1 + does not appear if `M` is even and `sym` is True). + + See Also + -------- + hamming, hann + + Notes + ----- + The generalized Hamming window is defined as + + .. math:: w(n) = \alpha - \left(1 - \alpha\right) + \cos\left(\frac{2\pi{n}}{M-1}\right) \qquad 0 \leq n \leq M-1 + + Both the common Hamming window and Hann window are special cases of the + generalized Hamming window with :math:`\alpha` = 0.54 and :math:`\alpha` = + 0.5, respectively [2]_. + + References + ---------- + .. [1] DSPRelated, "Generalized Hamming Window Family", + https://www.dsprelated.com/freebooks/sasp/Generalized_Hamming_Window_Family.html + .. [2] Wikipedia, "Window function", + https://en.wikipedia.org/wiki/Window_function + .. [3] Riccardo Piantanida ESA, "Sentinel-1 Level 1 Detailed Algorithm + Definition", + https://sentinel.esa.int/documents/247904/1877131/Sentinel-1-Level-1-Detailed-Algorithm-Definition + .. [4] Matthieu Bourbigot ESA, "Sentinel-1 Product Definition", + https://sentinel.esa.int/documents/247904/1877131/Sentinel-1-Product-Definition + + Examples + -------- + The Sentinel-1A/B Instrument Processing Facility uses generalized Hamming + windows in the processing of spaceborne Synthetic Aperture Radar (SAR) + data [3]_. The facility uses various values for the :math:`\alpha` + parameter based on operating mode of the SAR instrument. Some common + :math:`\alpha` values include 0.75, 0.7 and 0.52 [4]_. As an example, we + plot these different windows. + + >>> import numpy as np + >>> from scipy.signal.windows import general_hamming + >>> from scipy.fft import fft, fftshift + >>> import matplotlib.pyplot as plt + + >>> fig1, spatial_plot = plt.subplots() + >>> spatial_plot.set_title("Generalized Hamming Windows") + >>> spatial_plot.set_ylabel("Amplitude") + >>> spatial_plot.set_xlabel("Sample") + + >>> fig2, freq_plot = plt.subplots() + >>> freq_plot.set_title("Frequency Responses") + >>> freq_plot.set_ylabel("Normalized magnitude [dB]") + >>> freq_plot.set_xlabel("Normalized frequency [cycles per sample]") + + >>> for alpha in [0.75, 0.7, 0.52]: + ... window = general_hamming(41, alpha) + ... spatial_plot.plot(window, label="{:.2f}".format(alpha)) + ... A = fft(window, 2048) / (len(window)/2.0) + ... freq = np.linspace(-0.5, 0.5, len(A)) + ... response = 20 * np.log10(np.abs(fftshift(A / abs(A).max()))) + ... freq_plot.plot(freq, response, label="{:.2f}".format(alpha)) + >>> freq_plot.legend(loc="upper right") + >>> spatial_plot.legend(loc="upper right") + + """ + xp = _namespace(xp) + a = xp.asarray([alpha, 1. - alpha], dtype=xp.float64, device=device) + device = xp_device(a) + return _general_cosine_impl(M, a, xp, device, sym=sym) + + +@xp_capabilities() +def hamming(M, sym=True, *, xp=None, device=None): + r"""Return a Hamming window. + + The Hamming window is a taper formed by using a raised cosine with + non-zero endpoints, optimized to minimize the nearest side lobe. + + Parameters + ---------- + M : int + Number of points in the output window. If zero, an empty array + is returned. An exception is thrown when it is negative. + sym : bool, optional + When True (default), generates a symmetric window, for use in filter + design. + When False, generates a periodic window, for use in spectral analysis. + %(xp_device_snippet)s + + Returns + ------- + w : ndarray + The window, with the maximum value normalized to 1 (though the value 1 + does not appear if `M` is even and `sym` is True). + + Notes + ----- + The Hamming window is defined as + + .. math:: w(n) = 0.54 - 0.46 \cos\left(\frac{2\pi{n}}{M-1}\right) + \qquad 0 \leq n \leq M-1 + + The Hamming was named for R. W. Hamming, an associate of J. W. Tukey and + is described in Blackman and Tukey. It was recommended for smoothing the + truncated autocovariance function in the time domain. + Most references to the Hamming window come from the signal processing + literature, where it is used as one of many windowing functions for + smoothing values. It is also known as an apodization (which means + "removing the foot", i.e. smoothing discontinuities at the beginning + and end of the sampled signal) or tapering function. + + References + ---------- + .. [1] Blackman, R.B. and Tukey, J.W., (1958) The measurement of power + spectra, Dover Publications, New York. + .. [2] E.R. Kanasewich, "Time Sequence Analysis in Geophysics", The + University of Alberta Press, 1975, pp. 109-110. + .. [3] Wikipedia, "Window function", + https://en.wikipedia.org/wiki/Window_function + .. [4] W.H. Press, B.P. Flannery, S.A. Teukolsky, and W.T. Vetterling, + "Numerical Recipes", Cambridge University Press, 1986, page 425. + + Examples + -------- + Plot the window and its frequency response: + + >>> import numpy as np + >>> from scipy import signal + >>> from scipy.fft import fft, fftshift + >>> import matplotlib.pyplot as plt + + >>> window = signal.windows.hamming(51) + >>> plt.plot(window) + >>> plt.title("Hamming window") + >>> plt.ylabel("Amplitude") + >>> plt.xlabel("Sample") + + >>> plt.figure() + >>> A = fft(window, 2048) / (len(window)/2.0) + >>> freq = np.linspace(-0.5, 0.5, len(A)) + >>> response = 20 * np.log10(np.abs(fftshift(A / abs(A).max()))) + >>> plt.plot(freq, response) + >>> plt.axis([-0.5, 0.5, -120, 0]) + >>> plt.title("Frequency response of the Hamming window") + >>> plt.ylabel("Normalized magnitude [dB]") + >>> plt.xlabel("Normalized frequency [cycles per sample]") + + """ + # Docstring adapted from NumPy's hamming function + return general_hamming(M, 0.54, sym, xp=xp, device=device) + + +@xp_capabilities() +def kaiser(M, beta, sym=True, *, xp=None, device=None): + r"""Return a Kaiser window. + + The Kaiser window is a taper formed by using a Bessel function. + + Parameters + ---------- + M : int + Number of points in the output window. If zero, an empty array + is returned. An exception is thrown when it is negative. + beta : float + Shape parameter, determines trade-off between main-lobe width and + side lobe level. As beta gets large, the window narrows. + sym : bool, optional + When True (default), generates a symmetric window, for use in filter + design. + When False, generates a periodic window, for use in spectral analysis. + %(xp_device_snippet)s + + Returns + ------- + w : ndarray + The window, with the maximum value normalized to 1 (though the value 1 + does not appear if `M` is even and `sym` is True). + + Notes + ----- + The Kaiser window is defined as + + .. math:: w(n) = I_0\left( \beta \sqrt{1-\frac{4n^2}{(M-1)^2}} + \right)/I_0(\beta) + + with + + .. math:: \quad -\frac{M-1}{2} \leq n \leq \frac{M-1}{2}, + + where :math:`I_0` is the modified zeroth-order Bessel function. + + The Kaiser was named for Jim Kaiser, who discovered a simple approximation + to the DPSS window based on Bessel functions. + The Kaiser window is a very good approximation to the discrete prolate + spheroidal sequence, or Slepian window, which is the transform which + maximizes the energy in the main lobe of the window relative to total + energy. + + The Kaiser can approximate other windows by varying the beta parameter. + (Some literature uses alpha = beta/pi.) [4]_ + + ==== ======================= + beta Window shape + ==== ======================= + 0 Rectangular + 5 Similar to a Hamming + 6 Similar to a Hann + 8.6 Similar to a Blackman + ==== ======================= + + A beta value of 14 is probably a good starting point. Note that as beta + gets large, the window narrows, and so the number of samples needs to be + large enough to sample the increasingly narrow spike, otherwise NaNs will + be returned. + + Most references to the Kaiser window come from the signal processing + literature, where it is used as one of many windowing functions for + smoothing values. It is also known as an apodization (which means + "removing the foot", i.e. smoothing discontinuities at the beginning + and end of the sampled signal) or tapering function. + + References + ---------- + .. [1] J. F. Kaiser, "Digital Filters" - Ch 7 in "Systems analysis by + digital computer", Editors: F.F. Kuo and J.F. Kaiser, p 218-285. + John Wiley and Sons, New York, (1966). + .. [2] E.R. Kanasewich, "Time Sequence Analysis in Geophysics", The + University of Alberta Press, 1975, pp. 177-178. + .. [3] Wikipedia, "Window function", + https://en.wikipedia.org/wiki/Window_function + .. [4] F. J. Harris, "On the use of windows for harmonic analysis with the + discrete Fourier transform," Proceedings of the IEEE, vol. 66, + no. 1, pp. 51-83, Jan. 1978. :doi:`10.1109/PROC.1978.10837`. + + Examples + -------- + Plot the window and its frequency response: + + >>> import numpy as np + >>> from scipy import signal + >>> from scipy.fft import fft, fftshift + >>> import matplotlib.pyplot as plt + + >>> window = signal.windows.kaiser(51, beta=14) + >>> plt.plot(window) + >>> plt.title(r"Kaiser window ($\beta$=14)") + >>> plt.ylabel("Amplitude") + >>> plt.xlabel("Sample") + + >>> plt.figure() + >>> A = fft(window, 2048) / (len(window)/2.0) + >>> freq = np.linspace(-0.5, 0.5, len(A)) + >>> response = 20 * np.log10(np.abs(fftshift(A / abs(A).max()))) + >>> plt.plot(freq, response) + >>> plt.axis([-0.5, 0.5, -120, 0]) + >>> plt.title(r"Frequency response of the Kaiser window ($\beta$=14)") + >>> plt.ylabel("Normalized magnitude [dB]") + >>> plt.xlabel("Normalized frequency [cycles per sample]") + + """ + xp = _namespace(xp) + + # Docstring adapted from NumPy's kaiser function + if _len_guards(M): + return xp.ones(M, dtype=xp.float64, device=device) + M, needs_trunc = _extend(M, sym) + + n = xp.arange(0, M, dtype=xp.float64, device=device) + alpha = (M - 1) / 2.0 + w = (special.i0(beta * xp.sqrt(1 - ((n - alpha) / alpha) ** 2.0)) / + special.i0(xp.asarray(beta, dtype=xp.float64))) + + return _truncate(w, needs_trunc) + + +@xp_capabilities() +def kaiser_bessel_derived(M, beta, *, sym=True, xp=None, device=None): + """Return a Kaiser-Bessel derived window. + + Parameters + ---------- + M : int + Number of points in the output window. If zero, an empty array + is returned. An exception is thrown when it is negative. + Note that this window is only defined for an even + number of points. + beta : float + Kaiser window shape parameter. + sym : bool, optional + This parameter only exists to comply with the interface offered by + the other window functions and to be callable by `get_window`. + When True (default), generates a symmetric window, for use in filter + design. + %(xp_device_snippet)s + + Returns + ------- + w : ndarray + The window, normalized to fulfil the Princen-Bradley condition. + + See Also + -------- + kaiser + + Notes + ----- + It is designed to be suitable for use with the modified discrete cosine + transform (MDCT) and is mainly used in audio signal processing and + audio coding. + + .. versionadded:: 1.9.0 + + References + ---------- + .. [1] Bosi, Marina, and Richard E. Goldberg. Introduction to Digital + Audio Coding and Standards. Dordrecht: Kluwer, 2003. + .. [2] Wikipedia, "Kaiser window", + https://en.wikipedia.org/wiki/Kaiser_window + + Examples + -------- + Plot the Kaiser-Bessel derived window based on the wikipedia + reference [2]_: + + >>> import numpy as np + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + >>> fig, ax = plt.subplots() + >>> N = 50 + >>> for alpha in [0.64, 2.55, 7.64, 31.83]: + ... ax.plot(signal.windows.kaiser_bessel_derived(2*N, np.pi*alpha), + ... label=f"{alpha=}") + >>> ax.grid(True) + >>> ax.set_title("Kaiser-Bessel derived window") + >>> ax.set_ylabel("Amplitude") + >>> ax.set_xlabel("Sample") + >>> ax.set_xticks([0, N, 2*N-1]) + >>> ax.set_xticklabels(["0", "N", "2N+1"]) # doctest: +SKIP + >>> ax.set_yticks([0.0, 0.2, 0.4, 0.6, 0.707, 0.8, 1.0]) + >>> fig.legend(loc="center") + >>> fig.tight_layout() + >>> fig.show() + """ + xp = _namespace(xp) + + if not sym: + raise ValueError( + "Kaiser-Bessel Derived windows are only defined for symmetric " + "shapes" + ) + elif M < 1: + return xp.asarray([]) + elif M % 2: + raise ValueError( + "Kaiser-Bessel Derived windows are only defined for even number " + "of points" + ) + + kaiser_window = kaiser(M // 2 + 1, beta, xp=xp, device=device) + csum = xp.cumulative_sum(kaiser_window) + half_window = xp.sqrt(csum[:-1] / csum[-1]) + w = xp.concat((half_window, xp.flip(half_window)), axis=0) + return xp.asarray(w, device=device) + + +@xp_capabilities() +def gaussian(M, std, sym=True, *, xp=None, device=None): + r"""Return a Gaussian window. + + Parameters + ---------- + M : int + Number of points in the output window. If zero, an empty array + is returned. An exception is thrown when it is negative. + std : float + The standard deviation, sigma. + sym : bool, optional + When True (default), generates a symmetric window, for use in filter + design. + When False, generates a periodic window, for use in spectral analysis. + %(xp_device_snippet)s + + Returns + ------- + w : ndarray + The window, with the maximum value normalized to 1 (though the value 1 + does not appear if `M` is even and `sym` is True). + + Notes + ----- + The Gaussian window is defined as + + .. math:: w(n) = e^{ -\frac{1}{2}\left(\frac{n}{\sigma}\right)^2 } + + Examples + -------- + Plot the window and its frequency response: + + >>> import numpy as np + >>> from scipy import signal + >>> from scipy.fft import fft, fftshift + >>> import matplotlib.pyplot as plt + + >>> window = signal.windows.gaussian(51, std=7) + >>> plt.plot(window) + >>> plt.title(r"Gaussian window ($\sigma$=7)") + >>> plt.ylabel("Amplitude") + >>> plt.xlabel("Sample") + + >>> plt.figure() + >>> A = fft(window, 2048) / (len(window)/2.0) + >>> freq = np.linspace(-0.5, 0.5, len(A)) + >>> response = 20 * np.log10(np.abs(fftshift(A / abs(A).max()))) + >>> plt.plot(freq, response) + >>> plt.axis([-0.5, 0.5, -120, 0]) + >>> plt.title(r"Frequency response of the Gaussian window ($\sigma$=7)") + >>> plt.ylabel("Normalized magnitude [dB]") + >>> plt.xlabel("Normalized frequency [cycles per sample]") + + """ + xp = _namespace(xp) + + if _len_guards(M): + return xp.ones(M, dtype=xp.float64, device=device) + M, needs_trunc = _extend(M, sym) + + n = xp.arange(0, M, dtype=xp.float64, device=device) - (M - 1.0) / 2.0 + sig2 = 2 * std * std + w = xp.exp(-n ** 2 / sig2) + + return _truncate(w, needs_trunc) + + +@xp_capabilities() +def general_gaussian(M, p, sig, sym=True, *, xp=None, device=None): + r"""Return a window with a generalized Gaussian shape. + + Parameters + ---------- + M : int + Number of points in the output window. If zero, an empty array + is returned. An exception is thrown when it is negative. + p : float + Shape parameter. p = 1 is identical to `gaussian`, p = 0.5 is + the same shape as the Laplace distribution. + sig : float + The standard deviation, sigma. + sym : bool, optional + When True (default), generates a symmetric window, for use in filter + design. + When False, generates a periodic window, for use in spectral analysis. + %(xp_device_snippet)s + + Returns + ------- + w : ndarray + The window, with the maximum value normalized to 1 (though the value 1 + does not appear if `M` is even and `sym` is True). + + Notes + ----- + The generalized Gaussian window is defined as + + .. math:: w(n) = e^{ -\frac{1}{2}\left|\frac{n}{\sigma}\right|^{2p} } + + the half-power point is at + + .. math:: (2 \log(2))^{1/(2 p)} \sigma + + Examples + -------- + Plot the window and its frequency response: + + >>> import numpy as np + >>> from scipy import signal + >>> from scipy.fft import fft, fftshift + >>> import matplotlib.pyplot as plt + + >>> window = signal.windows.general_gaussian(51, p=1.5, sig=7) + >>> plt.plot(window) + >>> plt.title(r"Generalized Gaussian window (p=1.5, $\sigma$=7)") + >>> plt.ylabel("Amplitude") + >>> plt.xlabel("Sample") + + >>> plt.figure() + >>> A = fft(window, 2048) / (len(window)/2.0) + >>> freq = np.linspace(-0.5, 0.5, len(A)) + >>> response = 20 * np.log10(np.abs(fftshift(A / abs(A).max()))) + >>> plt.plot(freq, response) + >>> plt.axis([-0.5, 0.5, -120, 0]) + >>> plt.title(r"Freq. resp. of the gen. Gaussian " + ... r"window (p=1.5, $\sigma$=7)") + >>> plt.ylabel("Normalized magnitude [dB]") + >>> plt.xlabel("Normalized frequency [cycles per sample]") + + """ + xp = _namespace(xp) + + if _len_guards(M): + return xp.ones(M, dtype=xp.float64, device=device) + M, needs_trunc = _extend(M, sym) + + n = xp.arange(0, M, dtype=xp.float64, device=device) - (M - 1.0) / 2.0 + w = xp.exp(-0.5 * abs(n / sig) ** (2 * p)) + + return _truncate(w, needs_trunc) + + +# `chebwin` contributed by Kumar Appaiah. +@xp_capabilities(skip_backends=(("jax.numpy", "item assignment"), + ("dask.array", "data-dependent output shapes"))) +def chebwin(M, at, sym=True, *, xp=None, device=None): + r"""Return a Dolph-Chebyshev window. + + Parameters + ---------- + M : int + Number of points in the output window. If zero, an empty array + is returned. An exception is thrown when it is negative. + at : float + Attenuation (in dB). + sym : bool, optional + When True (default), generates a symmetric window, for use in filter + design. + When False, generates a periodic window, for use in spectral analysis. + %(xp_device_snippet)s + + Returns + ------- + w : ndarray + The window, with the maximum value always normalized to 1 + + Notes + ----- + This window optimizes for the narrowest main lobe width for a given order + `M` and sidelobe equiripple attenuation `at`, using Chebyshev + polynomials. It was originally developed by Dolph to optimize the + directionality of radio antenna arrays. + + Unlike most windows, the Dolph-Chebyshev is defined in terms of its + frequency response: + + .. math:: W(k) = \frac + {\cos\{M \cos^{-1}[\beta \cos(\frac{\pi k}{M})]\}} + {\cosh[M \cosh^{-1}(\beta)]} + + where + + .. math:: \beta = \cosh \left [\frac{1}{M} + \cosh^{-1}(10^\frac{A}{20}) \right ] + + and 0 <= abs(k) <= M-1. A is the attenuation in decibels (`at`). + + The time domain window is then generated using the IFFT, so + power-of-two `M` are the fastest to generate, and prime number `M` are + the slowest. + + The equiripple condition in the frequency domain creates impulses in the + time domain, which appear at the ends of the window. + + References + ---------- + .. [1] C. Dolph, "A current distribution for broadside arrays which + optimizes the relationship between beam width and side-lobe level", + Proceedings of the IEEE, Vol. 34, Issue 6 + .. [2] Peter Lynch, "The Dolph-Chebyshev Window: A Simple Optimal Filter", + American Meteorological Society (April 1997) + http://mathsci.ucd.ie/~plynch/Publications/Dolph.pdf + .. [3] F. J. Harris, "On the use of windows for harmonic analysis with the + discrete Fourier transforms", Proceedings of the IEEE, Vol. 66, + No. 1, January 1978 + + Examples + -------- + Plot the window and its frequency response: + + >>> import numpy as np + >>> from scipy import signal + >>> from scipy.fft import fft, fftshift + >>> import matplotlib.pyplot as plt + + >>> window = signal.windows.chebwin(51, at=100) + >>> plt.plot(window) + >>> plt.title("Dolph-Chebyshev window (100 dB)") + >>> plt.ylabel("Amplitude") + >>> plt.xlabel("Sample") + + >>> plt.figure() + >>> A = fft(window, 2048) / (len(window)/2.0) + >>> freq = np.linspace(-0.5, 0.5, len(A)) + >>> response = 20 * np.log10(np.abs(fftshift(A / abs(A).max()))) + >>> plt.plot(freq, response) + >>> plt.axis([-0.5, 0.5, -120, 0]) + >>> plt.title("Frequency response of the Dolph-Chebyshev window (100 dB)") + >>> plt.ylabel("Normalized magnitude [dB]") + >>> plt.xlabel("Normalized frequency [cycles per sample]") + + """ + xp = _namespace(xp) + + if abs(at) < 45: + warnings.warn("This window is not suitable for spectral analysis " + "for attenuation values lower than about 45dB because " + "the equivalent noise bandwidth of a Chebyshev window " + "does not grow monotonically with increasing sidelobe " + "attenuation when the attenuation is smaller than " + "about 45 dB.", + stacklevel=2) + if _len_guards(M): + return xp.ones(M, dtype=xp.float64, device=device) + M, needs_trunc = _extend(M, sym) + + # compute the parameter beta + order = M - 1.0 + _val = xp.asarray(10 ** (abs(at) / 20.), dtype=xp.float64, device=device) + beta = xp.cosh(1.0 / order * xp.acosh(_val)) + k = xp.arange(M, dtype=xp.float64, device=device) + x = beta * xp.cos(xp.pi * k / M) + # Find the window's DFT coefficients + # Use analytic definition of Chebyshev polynomial instead of expansion + # from scipy.special. Using the expansion in scipy.special leads to errors. + p = xp.zeros_like(x) + p[x > 1] = xp.cosh(order * xp.acosh(x[x > 1])) + p[x < -1] = (2 * (M % 2) - 1) * xp.cosh(order * xp.acosh(-x[x < -1])) + p[abs(x) <= 1] = xp.cos(order * xp.acos(x[abs(x) <= 1])) + + # Appropriate IDFT and filling up + # depending on even/odd M + if M % 2: + w = xp.real(sp_fft.fft(p)) + n = (M + 1) // 2 + w = w[:n] + w = xp.concat((xp.flip(w[1:n]), w)) + else: + p = p * xp.exp(1j * xp.pi / M * xp.arange(M, dtype=xp.float64, device=device)) + w = xp.real(sp_fft.fft(p)) + n = M // 2 + 1 + w = xp.concat((xp.flip(w[1:n]), w[1:n])) + w = w / xp.max(w) + + return _truncate(w, needs_trunc) + + +@xp_capabilities() +def cosine(M, sym=True, *, xp=None, device=None): + """Return a window with a simple cosine shape. + + Parameters + ---------- + M : int + Number of points in the output window. If zero, an empty array + is returned. An exception is thrown when it is negative. + sym : bool, optional + When True (default), generates a symmetric window, for use in filter + design. + When False, generates a periodic window, for use in spectral analysis. + %(xp_device_snippet)s + + Returns + ------- + w : ndarray + The window, with the maximum value normalized to 1 (though the value 1 + does not appear if `M` is even and `sym` is True). + + Notes + ----- + + .. versionadded:: 0.13.0 + + Examples + -------- + Plot the window and its frequency response: + + >>> import numpy as np + >>> from scipy import signal + >>> from scipy.fft import fft, fftshift + >>> import matplotlib.pyplot as plt + + >>> window = signal.windows.cosine(51) + >>> plt.plot(window) + >>> plt.title("Cosine window") + >>> plt.ylabel("Amplitude") + >>> plt.xlabel("Sample") + + >>> plt.figure() + >>> A = fft(window, 2047) / (len(window)/2.0) + >>> freq = np.linspace(-0.5, 0.5, len(A)) + >>> response = 20 * np.log10(np.abs(fftshift(A / abs(A).max()))) + >>> plt.plot(freq, response) + >>> plt.axis([-0.5, 0.5, -120, 0]) + >>> plt.title("Frequency response of the cosine window") + >>> plt.ylabel("Normalized magnitude [dB]") + >>> plt.xlabel("Normalized frequency [cycles per sample]") + >>> plt.show() + + """ + xp = _namespace(xp) + + if _len_guards(M): + return xp.ones(M, dtype=xp.float64, device=device) + M, needs_trunc = _extend(M, sym) + + w = xp.sin(xp.pi / M * (xp.arange(M, dtype=xp.float64, device=device) + .5)) + + return _truncate(w, needs_trunc) + + +@xp_capabilities() +def exponential(M, center=None, tau=1., sym=True, *, xp=None, device=None): + r"""Return an exponential (or Poisson) window. + + Parameters + ---------- + M : int + Number of points in the output window. If zero, an empty array + is returned. An exception is thrown when it is negative. + center : float, optional + Parameter defining the center location of the window function. + The default value if not given is ``center = (M-1) / 2``. This + parameter must take its default value for symmetric windows. + tau : float, optional + Parameter defining the decay. For ``center = 0`` use + ``tau = -(M-1) / ln(x)`` if ``x`` is the fraction of the window + remaining at the end. + sym : bool, optional + When True (default), generates a symmetric window, for use in filter + design. + When False, generates a periodic window, for use in spectral analysis. + %(xp_device_snippet)s + + Returns + ------- + w : ndarray + The window, with the maximum value normalized to 1 (though the value 1 + does not appear if `M` is even and `sym` is True). + + Notes + ----- + The Exponential window is defined as + + .. math:: w(n) = e^{-|n-center| / \tau} + + References + ---------- + .. [1] S. Gade and H. Herlufsen, "Windows to FFT analysis (Part I)", + Technical Review 3, Bruel & Kjaer, 1987. + + Examples + -------- + Plot the symmetric window and its frequency response: + + >>> import numpy as np + >>> from scipy import signal + >>> from scipy.fft import fft, fftshift + >>> import matplotlib.pyplot as plt + + >>> M = 51 + >>> tau = 3.0 + >>> window = signal.windows.exponential(M, tau=tau) + >>> plt.plot(window) + >>> plt.title("Exponential Window (tau=3.0)") + >>> plt.ylabel("Amplitude") + >>> plt.xlabel("Sample") + + >>> plt.figure() + >>> A = fft(window, 2048) / (len(window)/2.0) + >>> freq = np.linspace(-0.5, 0.5, len(A)) + >>> response = 20 * np.log10(np.abs(fftshift(A / abs(A).max()))) + >>> plt.plot(freq, response) + >>> plt.axis([-0.5, 0.5, -35, 0]) + >>> plt.title("Frequency response of the Exponential window (tau=3.0)") + >>> plt.ylabel("Normalized magnitude [dB]") + >>> plt.xlabel("Normalized frequency [cycles per sample]") + + This function can also generate non-symmetric windows: + + >>> tau2 = -(M-1) / np.log(0.01) + >>> window2 = signal.windows.exponential(M, 0, tau2, False) + >>> plt.figure() + >>> plt.plot(window2) + >>> plt.ylabel("Amplitude") + >>> plt.xlabel("Sample") + """ + xp = _namespace(xp) + + if sym and center is not None: + raise ValueError("If sym==True, center must be None.") + if _len_guards(M): + return xp.ones(M, dtype=xp.float64, device=device) + M, needs_trunc = _extend(M, sym) + + if center is None: + center = (M-1) / 2 + + n = xp.arange(0, M, dtype=xp.float64, device=device) + w = xp.exp(-abs(n-center) / tau) + + return _truncate(w, needs_trunc) + + +@xp_capabilities(skip_backends=[("jax.numpy", "item assignment")]) +def taylor(M, nbar=4, sll=30, norm=True, sym=True, *, xp=None, device=None): + """ + Return a Taylor window. + + The Taylor window taper function approximates the Dolph-Chebyshev window's + constant sidelobe level for a parameterized number of near-in sidelobes, + but then allows a taper beyond [2]_. + + The SAR (synthetic aperture radar) community commonly uses Taylor + weighting for image formation processing because it provides strong, + selectable sidelobe suppression with minimum broadening of the + mainlobe [1]_. + + Parameters + ---------- + M : int + Number of points in the output window. If zero, an empty array + is returned. An exception is thrown when it is negative. + nbar : int, optional + Number of nearly constant level sidelobes adjacent to the mainlobe. + sll : float, optional + Desired suppression of sidelobe level in decibels (dB) relative to the + DC gain of the mainlobe. This should be a positive number. + norm : bool, optional + When True (default), divides the window by the largest (middle) value + for odd-length windows or the value that would occur between the two + repeated middle values for even-length windows such that all values + are less than or equal to 1. When False the DC gain will remain at 1 + (0 dB) and the sidelobes will be `sll` dB down. + sym : bool, optional + When True (default), generates a symmetric window, for use in filter + design. + When False, generates a periodic window, for use in spectral analysis. + %(xp_device_snippet)s + + Returns + ------- + out : array + The window. When `norm` is True (default), the maximum value is + normalized to 1 (though the value 1 does not appear if `M` is + even and `sym` is True). + + See Also + -------- + chebwin, kaiser, bartlett, blackman, hamming, hann + + References + ---------- + .. [1] W. Carrara, R. Goodman, and R. Majewski, "Spotlight Synthetic + Aperture Radar: Signal Processing Algorithms" Pages 512-513, + July 1995. + .. [2] Armin Doerry, "Catalog of Window Taper Functions for + Sidelobe Control", 2017. + https://www.researchgate.net/profile/Armin_Doerry/publication/316281181_Catalog_of_Window_Taper_Functions_for_Sidelobe_Control/links/58f92cb2a6fdccb121c9d54d/Catalog-of-Window-Taper-Functions-for-Sidelobe-Control.pdf + + Examples + -------- + Plot the window and its frequency response: + + >>> import numpy as np + >>> from scipy import signal + >>> from scipy.fft import fft, fftshift + >>> import matplotlib.pyplot as plt + + >>> window = signal.windows.taylor(51, nbar=20, sll=100, norm=False) + >>> plt.plot(window) + >>> plt.title("Taylor window (100 dB)") + >>> plt.ylabel("Amplitude") + >>> plt.xlabel("Sample") + + >>> plt.figure() + >>> A = fft(window, 2048) / (len(window)/2.0) + >>> freq = np.linspace(-0.5, 0.5, len(A)) + >>> response = 20 * np.log10(np.abs(fftshift(A / abs(A).max()))) + >>> plt.plot(freq, response) + >>> plt.axis([-0.5, 0.5, -120, 0]) + >>> plt.title("Frequency response of the Taylor window (100 dB)") + >>> plt.ylabel("Normalized magnitude [dB]") + >>> plt.xlabel("Normalized frequency [cycles per sample]") + + """ # noqa: E501 + xp = _namespace(xp) + + if _len_guards(M): + return xp.ones(M, dtype=xp.float64, device=device) + M, needs_trunc = _extend(M, sym) + + # Original text uses a negative sidelobe level parameter and then negates + # it in the calculation of B. To keep consistent with other methods we + # assume the sidelobe level parameter to be positive. + B = xp.asarray(10**(sll / 20), device=device) + A = xp.acosh(B) / xp.pi + s2 = nbar**2 / (A**2 + (nbar - 0.5)**2) + ma = xp.arange(1, nbar, dtype=xp.float64, device=device) + + Fm = xp.empty(nbar - 1, dtype=xp.float64, device=device) + signs = xp.empty_like(ma) + signs[::2] = 1 + signs[1::2] = -1 + m2 = ma*ma + for mi, m in enumerate(ma): + numer = signs[mi] * xp.prod(1 - m2[mi]/s2/(A**2 + (ma - 0.5)**2)) + denom = 2 * xp.prod(1 - m2[mi]/m2[:mi]) * xp.prod(1 - m2[mi]/m2[mi+1:]) + Fm[mi] = numer / denom + + def W(n): + return 1 + 2*xp.matmul(Fm, xp.cos( + 2*xp.pi*ma[:, xp.newaxis]*(n-M/2.+0.5)/M)) + + w = W(xp.arange(M, dtype=xp.float64, device=device)) + + # normalize (Note that this is not described in the original text [1]) + if norm: + scale = 1.0 / W((M - 1) / 2) + w *= scale + + return _truncate(w, needs_trunc) + + +@xp_capabilities(np_only=True, reason='banded linear algebra is numpy-only') +def dpss(M, NW, Kmax=None, sym=True, norm=None, return_ratios=False, + *, xp=None, device=None): + """ + Compute the Discrete Prolate Spheroidal Sequences (DPSS). + + DPSS (or Slepian sequences) are often used in multitaper power spectral + density estimation (see [1]_). The first window in the sequence can be + used to maximize the energy concentration in the main lobe, and is also + called the Slepian window. + + Parameters + ---------- + M : int + Window length. + NW : float + Standardized half bandwidth corresponding to ``2*NW = BW/f0 = BW*M*dt`` + where ``dt`` is taken as 1. + Kmax : int | None, optional + Number of DPSS windows to return (orders ``0`` through ``Kmax-1``). + If None (default), return only a single window of shape ``(M,)`` + instead of an array of windows of shape ``(Kmax, M)``. + sym : bool, optional + When True (default), generates a symmetric window, for use in filter + design. + When False, generates a periodic window, for use in spectral analysis. + norm : {2, 'approximate', 'subsample'} | None, optional + If 'approximate' or 'subsample', then the windows are normalized by the + maximum, and a correction scale-factor for even-length windows + is applied either using ``M**2/(M**2+NW)`` ("approximate") or + a FFT-based subsample shift ("subsample"), see Notes for details. + If None, then "approximate" is used when ``Kmax=None`` and 2 otherwise + (which uses the l2 norm). + return_ratios : bool, optional + If True, also return the concentration ratios in addition to the + windows. + %(xp_device_snippet)s + + Returns + ------- + v : ndarray, shape (Kmax, M) or (M,) + The DPSS windows. Will be 1D if `Kmax` is None. + r : ndarray, shape (Kmax,) or float, optional + The concentration ratios for the windows. Only returned if + `return_ratios` evaluates to True. Will be 0D if `Kmax` is None. + + Notes + ----- + This computation uses the tridiagonal eigenvector formulation given + in [2]_. + + The default normalization for ``Kmax=None``, i.e. window-generation mode, + simply using the l-infinity norm would create a window with two unity + values, which creates slight normalization differences between even and odd + orders. The approximate correction of ``M**2/float(M**2+NW)`` for even + sample numbers is used to counteract this effect (see Examples below). + + For very long signals (e.g., 1e6 elements), it can be useful to compute + windows orders of magnitude shorter and use interpolation (e.g., + `scipy.interpolate.interp1d`) to obtain tapers of length `M`, + but this in general will not preserve orthogonality between the tapers. + + .. versionadded:: 1.1 + + References + ---------- + .. [1] Percival DB, Walden WT. Spectral Analysis for Physical Applications: + Multitaper and Conventional Univariate Techniques. + Cambridge University Press; 1993. + .. [2] Slepian, D. Prolate spheroidal wave functions, Fourier analysis, and + uncertainty V: The discrete case. Bell System Technical Journal, + Volume 57 (1978), 1371430. + .. [3] Kaiser, JF, Schafer RW. On the Use of the I0-Sinh Window for + Spectrum Analysis. IEEE Transactions on Acoustics, Speech and + Signal Processing. ASSP-28 (1): 105-107; 1980. + + Examples + -------- + We can compare the window to `kaiser`, which was invented as an alternative + that was easier to calculate [3]_ (example adapted from + `here `_): + + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from scipy.signal import windows, freqz + >>> M = 51 + >>> fig, axes = plt.subplots(3, 2, figsize=(5, 7)) + >>> for ai, alpha in enumerate((1, 3, 5)): + ... win_dpss = windows.dpss(M, alpha) + ... beta = alpha*np.pi + ... win_kaiser = windows.kaiser(M, beta) + ... for win, c in ((win_dpss, 'k'), (win_kaiser, 'r')): + ... win /= win.sum() + ... axes[ai, 0].plot(win, color=c, lw=1.) + ... axes[ai, 0].set(xlim=[0, M-1], title=rf'$\\alpha$ = {alpha}', + ... ylabel='Amplitude') + ... w, h = freqz(win) + ... axes[ai, 1].plot(w, 20 * np.log10(np.abs(h)), color=c, lw=1.) + ... axes[ai, 1].set(xlim=[0, np.pi], + ... title=rf'$\\beta$ = {beta:0.2f}', + ... ylabel='Magnitude (dB)') + >>> for ax in axes.ravel(): + ... ax.grid(True) + >>> axes[2, 1].legend(['DPSS', 'Kaiser']) + >>> fig.tight_layout() + >>> plt.show() + + And here are examples of the first four windows, along with their + concentration ratios: + + >>> M = 512 + >>> NW = 2.5 + >>> win, eigvals = windows.dpss(M, NW, 4, return_ratios=True) + >>> fig, ax = plt.subplots(1) + >>> ax.plot(win.T, linewidth=1.) + >>> ax.set(xlim=[0, M-1], ylim=[-0.1, 0.1], xlabel='Samples', + ... title=f'DPSS, {M:d}, {NW:0.1f}') + >>> ax.legend([f'win[{ii}] ({ratio:0.4f})' + ... for ii, ratio in enumerate(eigvals)]) + >>> fig.tight_layout() + >>> plt.show() + + Using a standard :math:`l_{\\infty}` norm would produce two unity values + for even `M`, but only one unity value for odd `M`. This produces uneven + window power that can be counteracted by the approximate correction + ``M**2/float(M**2+NW)``, which can be selected by using + ``norm='approximate'`` (which is the same as ``norm=None`` when + ``Kmax=None``, as is the case here). Alternatively, the slower + ``norm='subsample'`` can be used, which uses subsample shifting in the + frequency domain (FFT) to compute the correction: + + >>> Ms = np.arange(1, 41) + >>> factors = (50, 20, 10, 5, 2.0001) + >>> energy = np.empty((3, len(Ms), len(factors))) + >>> for mi, M in enumerate(Ms): + ... for fi, factor in enumerate(factors): + ... NW = M / float(factor) + ... # Corrected using empirical approximation (default) + ... win = windows.dpss(M, NW) + ... energy[0, mi, fi] = np.sum(win ** 2) / np.sqrt(M) + ... # Corrected using subsample shifting + ... win = windows.dpss(M, NW, norm='subsample') + ... energy[1, mi, fi] = np.sum(win ** 2) / np.sqrt(M) + ... # Uncorrected (using l-infinity norm) + ... win /= win.max() + ... energy[2, mi, fi] = np.sum(win ** 2) / np.sqrt(M) + >>> fig, ax = plt.subplots(1) + >>> hs = ax.plot(Ms, energy[2], '-o', markersize=4, + ... markeredgecolor='none') + >>> leg = [hs[-1]] + >>> for hi, hh in enumerate(hs): + ... h1 = ax.plot(Ms, energy[0, :, hi], '-o', markersize=4, + ... color=hh.get_color(), markeredgecolor='none', + ... alpha=0.66) + ... h2 = ax.plot(Ms, energy[1, :, hi], '-o', markersize=4, + ... color=hh.get_color(), markeredgecolor='none', + ... alpha=0.33) + ... if hi == len(hs) - 1: + ... leg.insert(0, h1[0]) + ... leg.insert(0, h2[0]) + >>> ax.set(xlabel='M (samples)', ylabel=r'Power / $\\sqrt{M}$') + >>> ax.legend(leg, ['Uncorrected', r'Corrected: $\\frac{M^2}{M^2+NW}$', + ... 'Corrected (subsample)']) + >>> fig.tight_layout() + + """ + xp = _namespace(xp) + + if norm is None: + norm = 'approximate' if Kmax is None else 2 + known_norms = (2, 'approximate', 'subsample') + if norm not in known_norms: + raise ValueError(f'norm must be one of {known_norms}, got {norm}') + if Kmax is None: + singleton = True + Kmax = 1 + else: + singleton = False + if _len_guards(M): + if not return_ratios: + return xp.ones(M, dtype=xp.float64) + elif singleton: + return xp.ones(M, dtype=xp.float64), 1. + else: + return xp.ones(M, dtype=xp.float64), xp.ones(1, dtype=xp.float64) + Kmax = operator.index(Kmax) + if not 0 < Kmax <= M: + raise ValueError('Kmax must be greater than 0 and less than M') + if NW >= M/2.: + raise ValueError('NW must be less than M/2.') + if NW <= 0: + raise ValueError('NW must be positive') + M, needs_trunc = _extend(M, sym) + W = float(NW) / M + nidx = xp.arange(M, dtype=xp.float64, device=device) + + # Here we want to set up an optimization problem to find a sequence + # whose energy is maximally concentrated within band [-W,W]. + # Thus, the measure lambda(T,W) is the ratio between the energy within + # that band, and the total energy. This leads to the eigen-system + # (A - (l1)I)v = 0, where the eigenvector corresponding to the largest + # eigenvalue is the sequence with maximally concentrated energy. The + # collection of eigenvectors of this system are called Slepian + # sequences, or discrete prolate spheroidal sequences (DPSS). Only the + # first K, K = 2NW/dt orders of DPSS will exhibit good spectral + # concentration + # [see https://en.wikipedia.org/wiki/Spectral_concentration_problem] + + # Here we set up an alternative symmetric tri-diagonal eigenvalue + # problem such that + # (B - (l2)I)v = 0, and v are our DPSS (but eigenvalues l2 != l1) + # the main diagonal = ([M-1-2*t]/2)**2 cos(2PIW), t=[0,1,2,...,M-1] + # and the first off-diagonal = t(M-t)/2, t=[1,2,...,M-1] + # [see Percival and Walden, 1993] + d = ((M - 1 - 2 * nidx) / 2.) ** 2 * xp.cos(xp.asarray(2 * xp.pi * W)) + e = nidx[1:] * (M - nidx[1:]) / 2. + + # only calculate the highest Kmax eigenvalues + w, windows = linalg.eigh_tridiagonal( + d, e, select='i', select_range=(M - Kmax, M - 1)) + w = w[::-1] + windows = windows[:, ::-1].T + + # By convention (Percival and Walden, 1993 pg 379) + # * symmetric tapers (k=0,2,4,...) should have a positive average. + fix_even = (windows[::2, ...].sum(axis=1) < 0) + for i, f in enumerate(fix_even): + if f: + windows[2 * i] *= -1 + # * antisymmetric tapers should begin with a positive lobe + # (this depends on the definition of "lobe", here we'll take the first + # point above the numerical noise, which should be good enough for + # sufficiently smooth functions, and more robust than relying on an + # algorithm that uses max(abs(w)), which is susceptible to numerical + # noise problems) + thresh = max(1e-7, 1. / M) + for i, w in enumerate(windows[1::2, ...]): + if w[w * w > thresh][0] < 0: + windows[2 * i + 1] *= -1 + + # Now find the eigenvalues of the original spectral concentration problem + # Use the autocorr sequence technique from Percival and Walden, 1993 pg 390 + if return_ratios: + dpss_rxx = _fftautocorr(xp.asarray(windows)) + r = 4 * W * xpx.sinc(xp.asarray(2 * W * nidx), xp=xp) + r[0] = 2 * W + ratios = xp.matmul(dpss_rxx, r) + if singleton: + ratios = ratios[0] + ratios = xp.asarray(ratios, device=device) + # Deal with sym and Kmax=None + if norm != 2: + windows /= windows.max() + if M % 2 == 0: + if norm == 'approximate': + correction = M**2 / float(M**2 + NW) + else: + s = sp_fft.rfft(windows[0]) + shift = -(1 - 1./M) * xp.arange(1, M//2 + 1, dtype=xp.float64) + s[1:] *= 2 * xp.exp(-1j * xp.pi * shift) + correction = M / s.real.sum() + windows *= correction + # else we're already l2 normed, so do nothing + if needs_trunc: + windows = windows[:, :-1] + if singleton: + windows = windows[0] + windows = xp.asarray(windows, device=device) + return (windows, ratios) if return_ratios else windows + + +@xp_capabilities() +def lanczos(M, *, sym=True, xp=None, device=None): + r"""Return a Lanczos window also known as a sinc window. + + Parameters + ---------- + M : int + Number of points in the output window. If zero, an empty array + is returned. An exception is thrown when it is negative. + sym : bool, optional + When True (default), generates a symmetric window, for use in filter + design. + When False, generates a periodic window, for use in spectral analysis. + %(xp_device_snippet)s + + Returns + ------- + w : ndarray + The window, with the maximum value normalized to 1 (though the value 1 + does not appear if `M` is even and `sym` is True). + + Notes + ----- + The Lanczos window is defined as + + .. math:: w(n) = sinc \left( \frac{2n}{M - 1} - 1 \right) + + where + + .. math:: sinc(x) = \frac{\sin(\pi x)}{\pi x} + + The Lanczos window has reduced Gibbs oscillations and is widely used for + filtering climate timeseries with good properties in the physical and + spectral domains. + + .. versionadded:: 1.10 + + References + ---------- + .. [1] Lanczos, C., and Teichmann, T. (1957). Applied analysis. + Physics Today, 10, 44. + .. [2] Duchon C. E. (1979) Lanczos Filtering in One and Two Dimensions. + Journal of Applied Meteorology, Vol 18, pp 1016-1022. + .. [3] Thomson, R. E. and Emery, W. J. (2014) Data Analysis Methods in + Physical Oceanography (Third Edition), Elsevier, pp 593-637. + .. [4] Wikipedia, "Window function", + http://en.wikipedia.org/wiki/Window_function + + Examples + -------- + Plot the window + + >>> import numpy as np + >>> from scipy.signal.windows import lanczos + >>> from scipy.fft import fft, fftshift + >>> import matplotlib.pyplot as plt + >>> fig, ax = plt.subplots(1) + >>> window = lanczos(51) + >>> ax.plot(window) + >>> ax.set_title("Lanczos window") + >>> ax.set_ylabel("Amplitude") + >>> ax.set_xlabel("Sample") + >>> fig.tight_layout() + >>> plt.show() + + and its frequency response: + + >>> fig, ax = plt.subplots(1) + >>> A = fft(window, 2048) / (len(window)/2.0) + >>> freq = np.linspace(-0.5, 0.5, len(A)) + >>> response = 20 * np.log10(np.abs(fftshift(A / abs(A).max()))) + >>> ax.plot(freq, response) + >>> ax.set_xlim(-0.5, 0.5) + >>> ax.set_ylim(-120, 0) + >>> ax.set_title("Frequency response of the lanczos window") + >>> ax.set_ylabel("Normalized magnitude [dB]") + >>> ax.set_xlabel("Normalized frequency [cycles per sample]") + >>> fig.tight_layout() + >>> plt.show() + """ + xp = _namespace(xp) + + if _len_guards(M): + return xp.ones(M, dtype=xp.float64, device=device) + M, needs_trunc = _extend(M, sym) + + # To make sure that the window is symmetric, we concatenate the right hand + # half of the window and the flipped one which is the left hand half of + # the window. + def _calc_right_side_lanczos(n, m): + return xpx.sinc(2. * xp.arange(n, m, dtype=xp.float64) / (m - 1) - 1.0, xp=xp) + + if M % 2 == 0: + wh = _calc_right_side_lanczos(M/2, M) + w = xp.concat([xp.flip(wh), wh]) + else: + wh = _calc_right_side_lanczos((M+1)/2, M) + w = xp.concat([xp.flip(wh), xp.ones(1), wh]) + + return _truncate(w, needs_trunc) + + +def _fftautocorr(x): + """Compute the autocorrelation of a real array and crop the result.""" + N = x.shape[-1] + use_N = sp_fft.next_fast_len(2*N-1) + x_fft = sp_fft.rfft(x, use_N, axis=-1) + cxy = sp_fft.irfft(x_fft * x_fft.conj(), n=use_N)[:, :N] + # Or equivalently (but in most cases slower): + # cxy = xp.asarray([xp.convolve(xx, yy[::-1], mode='full') + # for xx, yy in zip(x, x)])[:, N-1:2*N-1] + return cxy + +_WIN_FUNC_DATA = { # Format: {(name0, name1, ...): (function, needs parameters) + ('barthann', 'brthan', 'bth'): (barthann, False), + ('bartlett', 'bart', 'brt'): (bartlett, False), + ('blackman', 'black', 'blk'): (blackman, False), + ('blackmanharris', 'blackharr', 'bkh'): (blackmanharris, False), + ('bohman', 'bman', 'bmn'): (bohman, False), + ('boxcar', 'box', 'ones', 'rect', 'rectangular'): (boxcar, False), + ('chebwin', 'cheb'): (chebwin, True), + ('cosine', 'halfcosine'): (cosine, False), + ('dpss',): (dpss, True), + ('exponential', 'poisson'): (exponential, 'OPTIONAL'), + ('flattop', 'flat', 'flt'): (flattop, False), + ('gaussian', 'gauss', 'gss'): (gaussian, True), + ('general cosine', 'general_cosine'): (general_cosine, True), + ('general gaussian', 'general_gaussian', 'general gauss', + 'general_gauss', 'ggs'): (general_gaussian, True), + ('general hamming', 'general_hamming'): (general_hamming, True), + ('hamming', 'hamm', 'ham'): (hamming, False), + ('hann', 'han'): (hann, False), + ('kaiser', 'ksr'): (kaiser, True), + ('kaiser bessel derived', 'kaiser_bessel_derived', + 'kbd'): (kaiser_bessel_derived, True), + ('lanczos', 'sinc'): (lanczos, False), + ('nuttall', 'nutl', 'nut'): (nuttall, False), + ('parzen', 'parz', 'par'): (parzen, False), + ('taylor', 'taylorwin'): (taylor, 'OPTIONAL'), + ('triangle', 'triang', 'tri'): (triang, False), + ('tukey', 'tuk'): (tukey, 'OPTIONAL'), } +_WIN_FUNCS = dict() +for nn_, v_ in _WIN_FUNC_DATA.items(): + _WIN_FUNCS.update({n_: v_ for n_ in nn_}) + + +@xp_capabilities() +def get_window(window, Nx, fftbins=True, *, xp=None, device=None): + r"""Convenience function for creating various windows. + + This function is a wrapper for the window functions provided in the + `scipy.signal.windows` namespace. + + Parameters + ---------- + window : str | tuple | float + Either a string with the window name or a tuple consisting of window name and + window parameters. If it is a float, a `~scipy.singal.kaiser` window is + created with `window` being the shape parameter. Consult the Notes below for + more details. + Nx : int + The number of samples in the window. + fftbins : bool, optional + If ``True`` (default), create a periodic window, ready to use with `ifftshift` + and be multiplied by the result of an FFT (see also + :func:`~scipy.fft.fftfreq`). If ``False``, create a symmetric window, for use + in filter design. This parameter is ignored, if the window name in the + `window` parameter has a suffix ``'_periodic'`` or ``'_symmetric'`` appended to + it (e.g., ``'hann_symmetric'``). + %(xp_device_snippet)s + + Returns + ------- + get_window : ndarray + Returns the created window as a one-dimensional array made of `Nx` samples. + + Raises + ------ + ValueError + If the provided parameters do not allow to choose a valid window function + with valid parameters. + + Notes + ----- + Note that by default this function returns a periodic window, whereas the wrapped + window functions return a symmetric window by default. This is caused by the + `fftbins` parameter having the inverse meaning of the `sym` parameter of the + wrapped function, which are both ``True`` by default. + + .. currentmodule:: scipy.signal.windows + + The following list shows the wrapped window functions with the respective settings + of the `window` parameter followed by a short description. Aliases for alternative + window names are given in parenthesis. + + `barthann` / ``'barthann'``: + Modified Bartlett-Hann window (aliases: ``'brthan', 'bth'``) + `bartlett` / ``'bartlett'``: + Bartlett window (aliases: ``'bart', 'brt'``) + `blackman`/ ``'blackman'``: + Blackman window (aliases: ``'black', 'blk'``) + `blackmanharris` / ``'blackmanharris'``: + 4-term Blackman-Harris window (aliases: ``'blackharr', 'bkh'``) + `bohman` / ``'bohman'``: + Bohman window (aliases: ``'bman', 'bmn'``) + `boxcar` / ``'boxcar'``: + Rectangular window (aliases: ``'box', 'ones', 'rect', 'rectangular'``) + `chebwin` / ``('chebwin', at)``: + Dolph-Chebyshev window with `at` dB attenuation (aliases: ``'cheb'``) + `cosine` / ``'cosine'``: + Cosine window (aliases: ``'halfcosine'``) + `dpss` / ``('dpss', NW)``: + First window of discrete prolate spheroidal sequence with standardized half + bandwidth ``NW`` and "approximate" norm. + `exponential` / ``'exponential'`` / ``('exponential', center, tau)``: + Exponential / Poisson window centered at ``center`` (default: ``None``) with + deacy ``tau`` (default: ``1``) (aliases: ``'poisson'``) + `flattop`/ ``'flattop'``: + Flat top window (aliases: ``'flat', 'flt'``) + `gaussian` / ``('gaussian', std)``: + Gaussian with standard deviation `std` (aliases: ``'gauss', 'gss'``) + `general_cosine` / ``('general cosine', a)``: + Generic weighted sum of cosine terms with weighting coefficients ``a`` + (aliases: ``'general_cosine'``) + `general_gaussian` / ``('general gaussian', p, sig)``: + Generalized Gaussian with shape parameter ``p`` and standard deviation ``sig`` + (aliases: ``'general_gaussian', 'general gauss', 'general_gauss', 'ggs'``) + `general_hamming` / ``('general hamming', alpha)``: + Generalized Hamming window with coefficent ``alpha`` + (aliases: ``'general_hamming'``) + `hamming` / ``'hamming'``: + Hamming window (aliases: ``'hamm', 'ham'``) + `hann` / ``'hann'``: + Hann window (aliases: ``'han'``) + `kaiser` / ``('kaiser', beta)``: + Kaiser window with shape parameter ``beta`` (aliases: ``'ksr'``) + `kaiser_bessel_derived` / ``('kaiser bessel derived', beta)``: + Kaiser-Bessel derived window with shape parameter ``beta`` + (aliases: ``'kaiser_bessel_derived', 'kbd'``) + `lanczos` / ``'lanczos'``: + Lanczos / sinc window (aliases: ``'sinc'``) + `nuttall`/ ``'nuttall'``: + Minimum 4-term Blackman-Harris window according to Nuttall + (aliases: ``'nutl', 'nut'``) + `parzen` / ``'parzen'``: + Parzen window (aliases: ``'parz', 'par'``) + `taylor` / ``'taylor'`` / (``'taylor', nbar, sll, norm)``: + Taylor window with ``nbar`` adjascent sidelobes (default: ``4``)), ``sll`` dB + suppression level (default: ``30``) and boolean value ``norm`` + (default: ``True``) (aliases: ``taylorwin``) + `triang`/ ``'triangle'``: + Triangle window (aliases: ``'triang', 'tri'``) + `tukey` / ``'tukey'`` / ``('tukey', alpha)``: + Tukey window with shape parameter ``alpha`` (default: ``0.5``) + (aliases: ``'tuk'``) + + + Examples + -------- + This example shows different usages of the `window` parameter: + + >>> from scipy.signal import get_window + >>> get_window('triang', 7) + array([ 0.125, 0.375, 0.625, 0.875, 0.875, 0.625, 0.375]) + >>> get_window(('exponential', None, 1.), 9) + array([ 0.011109 , 0.03019738, 0.082085 , 0.22313016, 0.60653066, + 0.60653066, 0.22313016, 0.082085 , 0.03019738]) + >>> get_window(('kaiser', 4.0), 9) + array([ 0.08848053, 0.29425961, 0.56437221, 0.82160913, 0.97885093, + 0.97885093, 0.82160913, 0.56437221, 0.29425961]) + >>> get_window(4.0, 9) # same as previous call + array([ 0.08848053, 0.29425961, 0.56437221, 0.82160913, 0.97885093, + 0.97885093, 0.82160913, 0.56437221, 0.29425961]) + + The following snippet shows different ways to create identical symmetric and + periodic Bartlett windows: + + >>> from scipy.signal import get_window, windows + >>> # Symmetric window: + >>> windows.bartlett(5) # Parameter `sym` defaults to True + array([0. , 0.5, 1. , 0.5, 0. ]) + >>> get_window('bartlett', 5, fftbins=False) + array([0. , 0.5, 1. , 0.5, 0. ]) + >>> get_window('bartlett_symmetric', 5) + array([0. , 0.5, 1. , 0.5, 0. ]) + >>> # Periodic window: + >>> windows.bartlett(4, sym=False) + array([0. , 0.5, 1. , 0.5]) + >>> get_window('bartlett', 4) # Parameter `fftbins` defaults to True + array([0. , 0.5, 1. , 0.5]) + >>> get_window('bartlett_periodic', 4) + array([0. , 0.5, 1. , 0.5]) + >>> # `_periodic' suffix overrides `fftbins` parameter: + >>> get_window('bartlett_periodic', 4, fftbins=False) + array([0. , 0.5, 1. , 0.5]) + + Note that a periodic window can be created out of a symmetric window by discarding + the last sample. + """ + if not (Nx > 0 and isinstance(Nx, numbers.Integral)): + raise ValueError(f"Parameter {Nx=} is not a positive integer") + if not isinstance(fftbins, bool): + raise ValueError(f"Parameter {fftbins=} is not of type bool!") + + if not isinstance(window, str | tuple): + try: # if parameter window can be converted to a float, return kaiser window: + beta = float(window) + except Exception as float_exception: + err_msg = f"Parameter {window=} must be a tuple, a string or a float!" + raise ValueError(err_msg) from float_exception + return kaiser(Nx, beta, not fftbins, xp=xp, device=device) + + if isinstance(window, tuple) and not isinstance(window[0], str): + raise ValueError(f"First tuple entry of parameter {window=} is not a str!") + + sym = not fftbins + win_name = window if isinstance(window, str) else window[0] + if win_name.endswith('_symmetric'): # overwrite `fftbins` / `sym` if needed + sym, win_name = True, win_name[:-10] # remove '_symmetric' from `win_name` + elif win_name.endswith('_periodic'): + sym, win_name = False, win_name[:-9] # remove '_periodic' from `win_name` + + if win_name not in _WIN_FUNCS: + raise ValueError(f"Invalid window name '{win_name}' in parameter {window=}!") + + func, has_args = _WIN_FUNCS[win_name] + args = window[1:] if isinstance(window, tuple) else tuple() + if len(args) > 0 and has_args is False: + raise ValueError(f"'{win_name}' does not allow parameters, but {window=}!") + if len(args) == 0 and has_args is True: + raise ValueError(f"'{win_name}' must have parameters, but {window=}!") + # has_args == 'OPTIONAL' allows len(args) == 0 as well as len(args) > 0 + + if not has_args: + return func(Nx, sym=sym, xp=xp, device=device) + + # special cases taken from original implementation: + if func is dpss: + if len(args) != 1: + raise ValueError(f"Window {win_name} must have one parameter but {window=}") + return dpss(Nx, args[0], Kmax=None, sym=sym, xp=xp, device=device) + if func is general_cosine: + if not (xp is None and device is None): + raise ValueError("'general_cosine' does not accept the parameters xp " + + "and device not being None!") + return general_cosine(Nx, *args, sym=sym) + + return func(Nx, *args, sym=sym, xp=xp, device=device) + + +########## complete the docstrings, on import +_xp_device_snippet = {'xp_device_snippet': +"""\ +xp : array_namespace, optional + Optional array namespace. + Should be compatible with the array API standard, or supported by array-api-compat. + Default: ``numpy`` +device : any + optional device specification for output. Should match one of the + supported device specification in ``xp``. +""" +} + + +_names = [x for x in __all__ if x != 'general_cosine'] +for name in _names: + window = vars()[name] + window.__doc__ = doccer.docformat(window.__doc__, _xp_device_snippet) diff --git a/venv/lib/python3.12/site-packages/scipy/signal/windows/windows.py b/venv/lib/python3.12/site-packages/scipy/signal/windows/windows.py new file mode 100644 index 0000000000000000000000000000000000000000..6858f71aceeb29ca6110864d01fb250e8c8ce403 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/signal/windows/windows.py @@ -0,0 +1,23 @@ +# This file is not meant for public use and will be removed in SciPy v2.0.0. +# Use the `scipy.signal.windows` namespace for importing the functions +# included below. + +from scipy._lib.deprecation import _sub_module_deprecation + +__all__ = [ # noqa: F822 + 'boxcar', 'triang', 'parzen', 'bohman', 'blackman', 'nuttall', + 'blackmanharris', 'flattop', 'bartlett', 'barthann', + 'hamming', 'kaiser', 'gaussian', 'general_cosine', + 'general_gaussian', 'general_hamming', 'chebwin', 'cosine', + 'hann', 'exponential', 'tukey', 'taylor', 'dpss', 'get_window', +] + + +def __dir__(): + return __all__ + + +def __getattr__(name): + return _sub_module_deprecation(sub_package="signal.windows", module="windows", + private_modules=["_windows"], all=__all__, + attribute=name) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/__init__.py b/venv/lib/python3.12/site-packages/scipy/sparse/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..d7438fa56b398877592e6485fd99974f70660f78 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/__init__.py @@ -0,0 +1,296 @@ +""" +=================================== +Sparse arrays (:mod:`scipy.sparse`) +=================================== + +.. currentmodule:: scipy.sparse + +.. toctree:: + :hidden: + + sparse.csgraph + sparse.linalg + sparse.migration_to_sparray + sparse.spmatrix_api + +SciPy 2-D sparse array package for numeric data. + +.. warning:: + + SciPy sparse is shifting from a sparse matrix interface to a sparse + array interface. In the next few releases we expect to deprecate the + sparse matrix interface. For documentation of the matrix + interface, see the :ref:`spmatrix interface docs `. + For guidance on converting existing code to sparse arrays, see + :ref:`Migration from spmatrix to sparray `. + + +Submodules +========== + +.. autosummary:: + + csgraph - Compressed sparse graph routines + linalg - Sparse linear algebra routines + + +Sparse array classes +==================== + +.. autosummary:: + :toctree: generated/ + + bsr_array - Block Sparse Row array + coo_array - A sparse array in COOrdinate format + csc_array - Compressed Sparse Column array + csr_array - Compressed Sparse Row array + dia_array - Sparse array with DIAgonal storage + dok_array - Dictionary Of Keys based sparse array + lil_array - Row-based list of lists sparse array + sparray - Sparse array base class + +.. _sparse-construction-functions: + +Building sparse arrays +---------------------- + +.. autosummary:: + :toctree: generated/ + + diags_array - Return a sparse array from diagonals + eye_array - Sparse MxN array whose k-th diagonal is all ones + random_array - Random values in a given shape array + block_array - Build a sparse array from sub-blocks + +.. _combining-arrays: + +Combining arrays +---------------- + +.. autosummary:: + :toctree: generated/ + + kron - Kronecker product of two sparse arrays + kronsum - Kronecker sum of sparse arrays + block_diag - Build a block diagonal sparse array + tril - Lower triangular portion of a sparse array + triu - Upper triangular portion of a sparse array + hstack - Stack sparse arrays horizontally (column wise) + vstack - Stack sparse arrays vertically (row wise) + +Manipulating arrays +------------------- + +.. autosummary:: + :toctree: generated/ + + matrix_transpose - Transpose a matrix (or a batch of matrices) + swapaxes - swap two axes of a sparse array + expand_dims - add a new (trivial) axis to a sparse array + permute_dims - reorder the axes of a sparse array + +Sparse tools +------------ + +.. autosummary:: + :toctree: generated/ + + save_npz - Save a sparse array to a file using ``.npz`` format. + load_npz - Load a sparse array from a file using ``.npz`` format. + find - Return the indices and values of the nonzero elements + get_index_dtype - determine a good dtype for index arrays. + safely_cast_index_arrays - cast index array dtype or raise if shape too big + +Identifying sparse arrays +------------------------- + +.. autosummary:: + :toctree: generated/ + + issparse - Check if the argument is a sparse object (array or matrix). + isspmatrix - Check if the argument is an old style sparse matrix. + + +Warnings +======== + +.. autosummary:: + :toctree: generated/ + + SparseEfficiencyWarning + SparseWarning + + +Usage information +================= + +There are seven available sparse array types: + +1. csc_array: Compressed Sparse Column format +2. csr_array: Compressed Sparse Row format +3. bsr_array: Block Sparse Row format +4. lil_array: List of Lists format +5. dok_array: Dictionary of Keys format +6. coo_array: COOrdinate format (aka IJV, triplet format) +7. dia_array: DIAgonal format + +To construct an array efficiently, use any of `coo_array`, +`dok_array` or `lil_array`. They each support basic slicing +and fancy indexing with a similar syntax to NumPy arrays. +The COO format is recommended and other formats use it under the hood to +allow efficient construction using data values and coordinate arrays. + +Despite their similarity to NumPy arrays, it is **strongly discouraged** +to use NumPy functions directly on these arrays because NumPy typically +treats them as generic Python objects rather than arrays, leading to +unexpected (and incorrect) results. If you are tempted to apply a NumPy +function to these arrays, check if SciPy has its own implementation +for the given sparse array class and, if not, **convert the sparse array +to a NumPy array** (e.g., using the `toarray` method of the class) +before applying the method. + +All conversions among the CSR, CSC, and COO formats are efficient, +linear-time operations. + +To perform manipulations such as multiplication or inversion, first +convert the array to either CSC or CSR format. The `lil_array` +format is row-based, so conversion to CSR is efficient, but +conversion to CSC is less so. + +Matrix vector product +--------------------- + +To do a vector product between a 2D sparse array and a vector use +the matmul operator (i.e., ``@``) which performs a dot product (like the +``dot`` method): + +>>> import numpy as np +>>> from scipy.sparse import csr_array +>>> A = csr_array([[1, 2, 0], [0, 0, 3], [4, 0, 5]]) +>>> v = np.array([1, 0, -1]) +>>> A @ v +array([ 1, -3, -1], dtype=int64) + +The CSR format is especially suitable for fast matrix vector products. + +Example 1 +--------- + +Construct a 1000x1000 `lil_array` and add some values to it: + +>>> from scipy.sparse import lil_array +>>> from scipy.sparse.linalg import spsolve +>>> from numpy.linalg import solve, norm +>>> from numpy.random import rand + +>>> A = lil_array((1000, 1000)) +>>> A[0, :100] = rand(100) +>>> A.setdiag(rand(1000)) + +Now convert it to CSR format and solve A x = b for x: + +>>> A = A.tocsr() +>>> b = rand(1000) +>>> x = spsolve(A, b) + +Convert it to a dense array and solve, and check that the result +is the same: + +>>> x_ = solve(A.toarray(), b) + +Now we can compute norm of the error with: + +>>> err = norm(x-x_) +>>> err < 1e-9 +True + +It should be small :) + + +Example 2 +--------- + +Construct an array in COO format: + +>>> from scipy import sparse +>>> from numpy import array +>>> I = array([0,3,1,0]) +>>> J = array([0,3,1,2]) +>>> V = array([4,5,7,9]) +>>> A = sparse.coo_array((V,(I,J)),shape=(4,4)) + +Notice that the indices do not need to be sorted. + +Duplicate (i,j) entries are summed when converting to CSR or CSC. + +>>> I = array([0,0,1,3,1,0,0]) +>>> J = array([0,2,1,3,1,0,0]) +>>> V = array([1,1,1,1,1,1,1]) +>>> B = sparse.coo_array((V,(I,J)),shape=(4,4)).tocsr() + +This is useful for constructing finite-element stiffness and mass matrices. + +Further details +--------------- + +CSR column indices are not necessarily sorted. Likewise for CSC row +indices. And similarly for COO coordinates. Use the ``.sorted_indices()`` +and ``.sort_indices()`` methods when sorted indices are required (e.g., +when passing data to other libraries). + +""" + +# Original code by Travis Oliphant. +# Modified and extended by Ed Schofield, Robert Cimrman, +# Nathan Bell, and Jake Vanderplas. + +import warnings as _warnings +import importlib as _importlib + +from ._base import * +from ._csr import * +from ._csc import * +from ._lil import * +from ._dok import * +from ._coo import * +from ._dia import * +from ._bsr import * +from ._construct import * +from ._extract import * +from ._matrix import spmatrix +from ._matrix_io import * +from ._sputils import get_index_dtype, safely_cast_index_arrays + + +# Deprecated namespaces, to be removed in v2.0.0 +from . import ( + base, bsr, compressed, construct, coo, csc, csr, data, dia, dok, extract, + lil, sparsetools, sputils +) + +_submodules = ["csgraph", "linalg"] + +__all__ = [s for s in dir() if not s.startswith('_')] + _submodules + +# Filter PendingDeprecationWarning for np.matrix introduced with numpy 1.15 +msg = 'the matrix subclass is not the recommended way' +_warnings.filterwarnings('ignore', message=msg) + +def __dir__(): + return __all__ + + +def __getattr__(name): + if name in _submodules: + return _importlib.import_module(f'scipy.sparse.{name}') + else: + try: + return globals()[name] + except KeyError: + raise AttributeError( + f"Module 'scipy.sparse' has no attribute '{name}'" + ) + +from scipy._lib._testutils import PytestTester +test = PytestTester(__name__) +del PytestTester diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..da69f81672ff93fe388e425aa1a176777f98858a Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/__init__.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_base.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_base.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..58380d347b4af61707d832ec974e1389c141c446 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_base.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_bsr.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_bsr.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a74665162578f4f7eaf7ee5524e2b83a69c60ce1 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_bsr.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_compressed.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_compressed.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8a13868f3505e3a83add1c3975ba44b8c17ba1fe Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_compressed.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_construct.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_construct.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..12e1280d289111cc69559d2c285825b05a8e6664 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_construct.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_coo.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_coo.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..51054936e5f7d193d269bacc6feb6edf7364c488 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_coo.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_csc.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_csc.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..80fc74c25304b14a505b9364a9b3e8b0f1bf83b7 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_csc.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_csr.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_csr.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ce130ed9f26248710f2ff426419f5b731b6fee50 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_csr.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_data.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_data.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a4d5538f6a17f32d127b401cf9faa215b83166d6 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_data.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_dia.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_dia.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..97a9a63737e5b5bad13b0e0938b769df57881095 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_dia.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_dok.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_dok.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0575cbbb9a808bb0c211a8993fce816e88808149 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_dok.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_extract.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_extract.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8151e89d14fd47b28df2e3d8fc7cafea28340b11 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_extract.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_index.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_index.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f36dc3316ebb9116e968152a121684c62d8490a3 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_index.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_lil.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_lil.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..36c3dc621399bbfd10fa3bfaa9d6871cea256b97 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_lil.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_matrix.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_matrix.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5e2d55cdc8100e6a4fa6b7f490a976fc943cf28c Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_matrix.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_matrix_io.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_matrix_io.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9fbf87d847f5e7b914fdd00cb7c304b72c95df54 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_matrix_io.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_spfuncs.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_spfuncs.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3e2639af2fbaa37f0842b2207e01377c1cb3c2b2 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_spfuncs.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_sputils.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_sputils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8a9b2a255dd6e5cd8eef4ede75acb620456ee5cf Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/_sputils.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/base.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/base.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ba737c6263d0e7e845c9c87acf8ff145e4cd6e2c Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/base.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/bsr.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/bsr.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5b2e1bf416115bd1200947a6730534932b6645cd Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/bsr.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/compressed.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/compressed.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e7ea3eebf1ca9ad43cf1b4fcaabc19a2e89da6df Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/compressed.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/construct.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/construct.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..05d6aabb37ee99f1cd2d7162930dd01078ac1fe0 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/construct.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/coo.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/coo.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..65761f3cb9f7de2ada63010da12e5d615f9256de Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/coo.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/csc.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/csc.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..61094cd4b1b9d4a39b7ba1009bea8d644d9915ae Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/csc.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/csr.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/csr.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ee3ab98c8de763929fb4f2e68f049937892a4eb5 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/csr.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/data.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/data.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..53a0c07a766103fe94c288320fc2a87fcfdc8cf9 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/data.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/dia.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/dia.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..70b9946bf0ad29b9a930f259437c77b2019f0548 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/dia.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/dok.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/dok.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..97e66317fd5409567e02a3e36d0a956b3d77e676 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/dok.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/extract.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/extract.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..777582190924ac4b18ad38b5fda3e413ced044a2 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/extract.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/lil.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/lil.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3faf4c3fdb0e246b2d042d2229b2c3f89b953988 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/lil.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/sparsetools.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/sparsetools.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7852beccb38244df3c47b4e8efa2a2e037e8b0a4 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/sparsetools.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/spfuncs.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/spfuncs.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..07d84833d8a1c09239958a3028d60a2cc825fc10 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/spfuncs.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/sputils.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/sputils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5e129a9f1455d5b9fcf8cbcadf7d9bfa7399379f Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/__pycache__/sputils.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/_base.py b/venv/lib/python3.12/site-packages/scipy/sparse/_base.py new file mode 100644 index 0000000000000000000000000000000000000000..455f707c33876dd48e387f498e960bdf2feb46b5 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/_base.py @@ -0,0 +1,1792 @@ +"""Base class for sparse matrices""" + +from warnings import warn +import math +import numpy as np +import operator + +from ._sputils import (asmatrix, check_shape, + get_sum_dtype, isdense, isscalarlike, _todata, + matrix, validateaxis, getdtype, is_pydata_spmatrix) +from scipy._lib._sparse import SparseABC, issparse + +from ._matrix import spmatrix + +__all__ = ['isspmatrix', 'issparse', 'sparray', + 'SparseWarning', 'SparseEfficiencyWarning'] + + +class SparseWarning(Warning): + """General warning for :mod:`scipy.sparse`.""" + pass + + +class SparseFormatWarning(SparseWarning): + pass + + +class SparseEfficiencyWarning(SparseWarning): + """The warning emitted when the operation is + inefficient for sparse matrices. + """ + pass + + +# The formats that we might potentially understand. +_formats = {'csc': [0, "Compressed Sparse Column"], + 'csr': [1, "Compressed Sparse Row"], + 'dok': [2, "Dictionary Of Keys"], + 'lil': [3, "List of Lists"], + 'dod': [4, "Dictionary of Dictionaries"], + 'sss': [5, "Symmetric Sparse Skyline"], + 'coo': [6, "COOrdinate"], + 'lba': [7, "Linpack BAnded"], + 'egd': [8, "Ellpack-itpack Generalized Diagonal"], + 'dia': [9, "DIAgonal"], + 'bsr': [10, "Block Sparse Row"], + 'msr': [11, "Modified compressed Sparse Row"], + 'bsc': [12, "Block Sparse Column"], + 'msc': [13, "Modified compressed Sparse Column"], + 'ssk': [14, "Symmetric SKyline"], + 'nsk': [15, "Nonsymmetric SKyline"], + 'jad': [16, "JAgged Diagonal"], + 'uss': [17, "Unsymmetric Sparse Skyline"], + 'vbr': [18, "Variable Block Row"], + 'und': [19, "Undefined"] + } + + +# These univariate ufuncs preserve zeros. +_ufuncs_with_fixed_point_at_zero = frozenset([ + np.sin, np.tan, np.arcsin, np.arctan, np.sinh, np.tanh, np.arcsinh, + np.arctanh, np.rint, np.sign, np.expm1, np.log1p, np.deg2rad, + np.rad2deg, np.floor, np.ceil, np.trunc, np.sqrt]) + + +MAXPRINT = 50 + + +# helper dicts to manipulate comparison operators +# We negate operators (with warning) when all implicit values would be True +op_neg = {operator.eq: operator.ne, operator.ne: operator.eq, + operator.lt: operator.ge, operator.ge: operator.lt, + operator.gt: operator.le, operator.le: operator.gt} + + +# We use symbolic version of operators in warning messages. +op_sym = {operator.eq: '==', operator.ge: '>=', operator.le: '<=', + operator.ne: '!=', operator.gt: '>', operator.lt: '<'} + + +# `_spbase` is a subclass of `SparseABC`. +# This allows other submodules to check for instances of sparse subclasses +# via `scipy._lib._sparse.issparse`, without introducing +# an import dependency on `scipy.sparse`. +class _spbase(SparseABC): + """ This class provides a base class for all sparse array and matrix classes. + It cannot be instantiated. Most of the work is provided by subclasses. + """ + + __array_priority__ = 10.1 + _format = 'und' # undefined + _allow_nd: tuple[int, ...] = (2,) + + @property + def ndim(self) -> int: + return len(self._shape) + + @property + def _shape_as_2d(self): + s = self._shape + return (1, s[-1]) if len(s) == 1 else s + + @property + def _bsr_container(self): + from ._bsr import bsr_array + return bsr_array + + @property + def _coo_container(self): + from ._coo import coo_array + return coo_array + + @property + def _csc_container(self): + from ._csc import csc_array + return csc_array + + @property + def _csr_container(self): + from ._csr import csr_array + return csr_array + + @property + def _dia_container(self): + from ._dia import dia_array + return dia_array + + @property + def _dok_container(self): + from ._dok import dok_array + return dok_array + + @property + def _lil_container(self): + from ._lil import lil_array + return lil_array + + def __init__(self, arg1, *, maxprint=None): + self._shape = None + if self.__class__.__name__ == '_spbase': + raise ValueError("This class is not intended" + " to be instantiated directly.") + if isinstance(self, sparray) and np.isscalar(arg1): + raise ValueError( + "scipy sparse array classes do not support instantiation from a scalar" + ) + self.maxprint = MAXPRINT if maxprint is None else maxprint + + @property + def shape(self): + return self._shape + + def reshape(self, *shape, order="C", copy=False): + """reshape(self, shape, order='C', copy=False) + + Gives a new shape to a sparse array/matrix without changing its data. + + Parameters + ---------- + *shape : int + Integers specifying the new shape. + The new shape should be compatible with the original shape. + order : {'C', 'F'}, optional + Read the elements using this index order. 'C' means to read and + write the elements using C-like index order; e.g., read entire first + row, then second row, etc. 'F' means to read and write the elements + using Fortran-like index order; e.g., read entire first column, then + second column, etc. + copy : bool, optional + Indicates whether or not attributes of self should be copied + whenever possible. The degree to which attributes are copied varies + depending on the type of sparse array being used. + + Returns + ------- + reshaped : sparse array/matrix + A sparse array/matrix with the given `shape`, not necessarily of the same + format as the current object. + + See Also + -------- + numpy.reshape : NumPy's implementation of 'reshape' for ndarrays + """ + # If the shape already matches, don't bother doing an actual reshape + # Otherwise, the default is to convert to COO and use its reshape + # Don't restrict ndim on this first call. That happens in constructor + shape = check_shape(shape, self.shape, allow_nd=range(1, 65)) + if shape == self.shape: + if copy: + return self.copy() + else: + return self + + return self.tocoo(copy=copy).reshape(shape, order=order, copy=False) + + def resize(self, *shape): + """Resize the array/matrix in-place to dimensions given by ``shape``. + + Any elements that lie within the new shape will remain at the same + indices, while non-zero elements lying outside the new shape are + removed. + + Parameters + ---------- + *shape : int + number of rows and columns in the new array/matrix + + Notes + ----- + The semantics are not identical to `numpy.ndarray.resize` or + `numpy.resize`. Here, the same data will be maintained at each index + before and after reshape, if that index is within the new bounds. In + numpy, resizing maintains contiguity of the array, moving elements + around in the logical array but not within a flattened representation. + + We give no guarantees about whether the underlying data attributes + (arrays, etc.) will be modified in place or replaced with new objects. + """ + # As an inplace operation, this requires implementation in each format. + raise NotImplementedError( + f'{type(self).__name__}.resize is not implemented') + + def astype(self, dtype, casting='unsafe', copy=True): + """Cast the array/matrix elements to a specified type. + + Parameters + ---------- + dtype : str or numpy dtype + Typecode or data-type to which to cast the data. + casting : {'no', 'equiv', 'safe', 'same_kind', 'unsafe'}, optional + Controls what kind of data casting may occur. + Defaults to 'unsafe' for backwards compatibility. + 'no' means the data types should not be cast at all. + 'equiv' means only byte-order changes are allowed. + 'safe' means only casts which can preserve values are allowed. + 'same_kind' means only safe casts or casts within a kind, + like float64 to float32, are allowed. + 'unsafe' means any data conversions may be done. + copy : bool, optional + If `copy` is `False`, the result might share some memory with this + array/matrix. If `copy` is `True`, it is guaranteed that the result and + this array/matrix do not share any memory. + + Returns + ------- + array/matrix + This array/matrix with data type `dtype`. + """ + + dtype = getdtype(dtype) + if self.dtype != dtype: + return self.tocsr().astype( + dtype, casting=casting, copy=copy).asformat(self.format) + elif copy: + return self.copy() + else: + return self + + @classmethod + def _ascontainer(cls, X, **kwargs): + if issubclass(cls, sparray): + return np.asarray(X, **kwargs) + else: + return asmatrix(X, **kwargs) + + @classmethod + def _container(cls, X, **kwargs): + if issubclass(cls, sparray): + return np.array(X, **kwargs) + else: + return matrix(X, **kwargs) + + def _asfptype(self): + """Upcast array to a floating point format (if necessary)""" + + fp_types = ['f', 'd', 'F', 'D'] + + if self.dtype.char in fp_types: + return self + else: + for fp_type in fp_types: + if self.dtype <= np.dtype(fp_type): + return self.astype(fp_type, copy=False) + + raise TypeError( + f'cannot upcast [{self.dtype.name}] to a floating point format' + ) + + def __iter__(self): + for r in range(self.shape[0]): + yield self[r] + + def _getmaxprint(self): + """Maximum number of elements to display when printed.""" + return self.maxprint + + def count_nonzero(self, axis=None): + """Number of non-zero entries. + + This is equivalent to:: + + np.count_nonzero(a.toarray(), axis=axis) + + Unlike the nnz property, which return the number of stored + entries (the length of the data attribute), this method counts the + actual number of non-zero entries in data. + + Duplicate entries are summed before counting. + + Parameters + ---------- + axis : {-2, -1, 0, 1, None} optional + Count nonzeros for the whole array, or along a specified axis. + + .. versionadded:: 1.15.0 + + Returns + ------- + numpy array + A reduced array (no axis `axis`) holding the number of nonzero values + for each of the indices of the nonaxis dimensions. + + Notes + ----- + If you want to count nonzero and explicit zero stored values (e.g. nnz) + along an axis, two fast idioms are provided by `numpy` functions for the + common CSR, CSC, COO formats. + + For the major axis in CSR (rows) and CSC (cols) use `np.diff`: + + >>> import numpy as np + >>> import scipy as sp + >>> A = sp.sparse.csr_array([[4, 5, 0], [7, 0, 0]]) + >>> major_axis_stored_values = np.diff(A.indptr) # -> np.array([2, 1]) + + For the minor axis in CSR (cols) and CSC (rows) use `numpy.bincount` with + minlength ``A.shape[1]`` for CSR and ``A.shape[0]`` for CSC: + + >>> csr_minor_stored_values = np.bincount(A.indices, minlength=A.shape[1]) + + For COO, use the minor axis approach for either `axis`: + + >>> A = A.tocoo() + >>> coo_axis0_stored_values = np.bincount(A.coords[0], minlength=A.shape[1]) + >>> coo_axis1_stored_values = np.bincount(A.coords[1], minlength=A.shape[0]) + + Examples + -------- + + >>> A = sp.sparse.csr_array([[4, 5, 0], [7, 0, 0]]) + >>> A.count_nonzero(axis=0) + array([2, 1, 0]) + """ + clsname = self.__class__.__name__ + raise NotImplementedError(f"count_nonzero not implemented for {clsname}.") + + def _getnnz(self, axis=None): + """Number of stored values, including explicit zeros. + + Parameters + ---------- + axis : {-2, -1, 0, 1, None} optional + Report stored values for the whole array, or along a specified axis. + + See Also + -------- + count_nonzero : Number of non-zero entries + """ + clsname = self.__class__.__name__ + raise NotImplementedError(f"getnnz not implemented for {clsname}.") + + @property + def nnz(self) -> int: + """Number of stored values, including explicit zeros. + + See Also + -------- + count_nonzero : Number of non-zero entries + """ + return self._getnnz() + + @property + def size(self) -> int: + """Number of stored values. + + See Also + -------- + count_nonzero : Number of non-zero values. + """ + return self._getnnz() + + @property + def format(self) -> str: + """Format string for matrix.""" + return self._format + + @property + def T(self): + """Transpose.""" + return self.transpose() + + @property + def mT(self): + """Matrix transpose. + + See Also + -------- + scipy.sparse.matrix_transpose : equivalent function + """ + if (n := self.ndim) < 2: + raise ValueError(f"Array must be at least 2-dimensional, but it is {n}-D") + assert n == 2 + return self.transpose() + + @property + def real(self): + return self._real() + + @property + def imag(self): + return self._imag() + + def __repr__(self): + _, format_name = _formats[self.format] + sparse_cls = 'array' if isinstance(self, sparray) else 'matrix' + return ( + f"<{format_name} sparse {sparse_cls} of dtype '{self.dtype}'\n" + f"\twith {self.nnz} stored elements and shape {self.shape}>" + ) + + def __str__(self): + maxprint = self._getmaxprint() + + A = self.tocoo() + + # helper function, outputs "(i,j) v" + def tostr(coords, data): + pairs = zip(zip(*(c.tolist() for c in coords)), data) + return '\n'.join(f' {idx}\t{val}' for idx, val in pairs) + + out = repr(self) + if self.nnz == 0: + return out + + out += '\n Coords\tValues\n' + if self.nnz > maxprint: + half = maxprint // 2 + out += tostr(tuple(c[:half] for c in A.coords), A.data[:half]) + out += "\n :\t:\n" + half = maxprint - half + out += tostr(tuple(c[-half:] for c in A.coords), A.data[-half:]) + else: + out += tostr(A.coords, A.data) + + return out + + def __bool__(self): # Simple -- other ideas? + if self.shape == (1, 1): + return self.nnz != 0 + else: + raise ValueError("The truth value of an array with more than one " + "element is ambiguous. Use a.any() or a.all().") + + # What should len(sparse) return? For consistency with dense matrices, + # perhaps it should be the number of rows? But for some uses the number of + # non-zeros is more important. For now, raise an exception! + def __len__(self): + raise TypeError("sparse array length is ambiguous; use getnnz()" + " or shape[0]") + + def asformat(self, format, copy=False): + """Return this array/matrix in the passed format. + + Parameters + ---------- + format : {str, None} + The desired sparse format ("csr", "csc", "lil", "dok", "array", ...) + or None for no conversion. + copy : bool, optional + If True, the result is guaranteed to not share data with self. + + Returns + ------- + A : array/matrix + This array/matrix in the passed format. + """ + if format is None or format == self.format: + if copy: + return self.copy() + else: + return self + else: + try: + convert_method = getattr(self, 'to' + format) + except AttributeError as e: + raise ValueError(f'Format {format} is unknown.') from e + + # Forward the copy kwarg, if it's accepted. + try: + return convert_method(copy=copy) + except TypeError: + return convert_method() + + ################################################################### + # NOTE: All arithmetic operations use csr_matrix by default. + # Therefore a new sparse array format just needs to define a + # .tocsr() method to provide arithmetic support. Any of these + # methods can be overridden for efficiency. + #################################################################### + + def multiply(self, other): + """Element-wise multiplication by another array/matrix. + + Parameters + ---------- + other : sparse array or array_like + Array to multiply by. + + Returns + ------- + sparse array/matrix + Result of element-wise multiplication. + """ + if isscalarlike(other): + return self._mul_scalar(other) + + if self.ndim < 3: + try: + return self._multiply_2d_with_broadcasting(other) + except AttributeError: + return self.tocsr()._multiply_2d_with_broadcasting(other) + + if not (issparse(other) or isdense(other)): + # If it's a list or whatever, treat it like an array + other_a = np.asanyarray(other) + if other_a.ndim == 0 and other_a.dtype == np.object_: + # numpy creates a 0d object array if all else fails. + # Not interpretable as an array; return NotImplemented so + # other's __rmul__ can kick in if that's implemented. + return NotImplemented + # Allow custom sparse class indicated by attr sparse gh-6520 + try: + other.shape + except AttributeError: + other = other_a + + if self.shape != other.shape: + raise ValueError("inconsistent shapes: >2D multiply() does not yet " + "support broadcasting") + + # self is >2D so must be COO + if isdense(other): + data = np.multiply(self.data, other[self.coords]) + result = self.copy() + result.data = data.view(np.ndarray).ravel() + return result + + elif issparse(other): + csr_self = self.reshape(1, -1).tocsr() + csr_other = other.reshape(1, -1).tocsr() + return csr_self._binopt(csr_other, '_elmul_').reshape(self.shape) + + else: + # Not scalar, dense or sparse. Return NotImplemented so that + # other's __rmul__ can kick in if that's implemented. + return NotImplemented + + def _maximum_minimum(self, other, np_op): + if not (issparse(other) or isdense(other) or isscalarlike(other)): + # If it's a list or whatever, treat it like an array + other_a = np.asanyarray(other) + if other_a.ndim == 0 and other_a.dtype == np.object_: + # numpy creates a 0d object array if all else fails. + # We don't know how to handle it either. + raise NotImplementedError('maximum or minimum with an unrecognized ' + 'array type is not supported') + # Allow custom sparse class indicated by attr sparse gh-6520 + try: + other.shape + except AttributeError: + other = other_a + + if isscalarlike(other): + if np_op(0, other): + pos_neg = 'positive' if np_op == np.maximum else 'negative' + warn(f"Taking {np_op.__name__} with a {pos_neg} number results in a" + " dense matrix.", SparseEfficiencyWarning, stacklevel=3) + return self.__class__(np_op(self.toarray(), other)) + else: + if self.ndim < 3: + cs_self = self if self.format in ('csr', 'csc') else self.tocsr() + return cs_self._scalar_binopt(other, np_op) + csr_self = self.reshape(1, -1).tocsr() + result = csr_self._scalar_binopt(other, np_op) + return result.tocoo().reshape(self.shape) + elif isdense(other): + return np_op(self.todense(), other) + elif issparse(other): + if self.shape != other.shape: + raise ValueError(f"inconsistent shapes {self.shape=} {other.shape=}") + if self.ndim < 3: # shape is same so other.ndim < 3 + cs_self = self if self.format in ('csr', 'csc') else self.tocsr() + return cs_self._binopt(other, f'_{np_op.__name__}_') + csr_self = self.reshape(1, -1).tocsr() + csr_other = other.reshape(1, -1).tocsr() + result = csr_self._binopt(csr_other, f'_{np_op.__name__}_') + return result.tocoo().reshape(self.shape) + else: + raise ValueError("Operands not compatible.") + + def maximum(self, other): + """Element-wise maximum between this and another array/matrix. + + Parameters + ---------- + other : array_like or sparse array + Array to compare against. + + Returns + ------- + sparse array/matrix or ndarray + Result of element-wise minimum. The type depends on `other`: + + - If `other` is a scalar, the result is a sparse array/matrix. + - If `other` is an ndarray, the result is an ndarray. + """ + return self._maximum_minimum(other, np.maximum) + + def minimum(self, other): + """Element-wise minimum between this and another array/matrix. + + Parameters + ---------- + other : array_like or sparse array + Array to compare against. + + Returns + ------- + sparse array/matrix or ndarray + Result of element-wise minimum. The type depends on `other`: + + - If `other` is a scalar, the result is a sparse array/matrix. + - If `other` is an ndarray, the result is an ndarray. + """ + return self._maximum_minimum(other, np.minimum) + + def dot(self, other): + """Ordinary dot product. + + Parameters + ---------- + other : array_like or sparse array + Array to take dot product with. + + Returns + ------- + sparse array/matrix or ndarray + Result of dot product. The type depends on `other`: + + - If `other` is a scalar, the result is a sparse array/matrix. + - If `other` is an ndarray, the result is an ndarray. + + Examples + -------- + >>> import numpy as np + >>> from scipy.sparse import csr_array + >>> A = csr_array([[1, 2, 0], [0, 0, 3], [4, 0, 5]]) + >>> v = np.array([1, 0, -1]) + >>> A.dot(v) + array([ 1, -3, -1], dtype=int64) + + """ + if np.isscalar(other): + return self * other + else: + return self @ other + + def power(self, n, dtype=None): + """Element-wise power. + + Parameters + ---------- + n : scalar + `n` is a non-zero scalar (nonzero avoids dense ones creation) + If zero power is desired, special case it to use `numpy.ones` + dtype : dtype, optional + If `dtype` is not specified, the current dtype will be preserved. + + Returns + ------- + csr array/matrix + Result of raising all elements to the power `n`. + """ + return self.tocsr().power(n, dtype=dtype) + + def _broadcast_to(self, shape, /, copy=False): + if self.shape == shape: + return self.copy() if copy else self + else: + return self.tocsr()._broadcast_to(shape, copy) + + def _comparison(self, other, op): + # We convert to CSR format and use methods _binopt or _scalar_binopt + # If ndim>2 we reshape to 2D, compare and then reshape back to nD + if not (issparse(other) or isdense(other) or isscalarlike(other)): + if is_pydata_spmatrix(other): + # cannot compare with pydata other, but it might compare with us. + return NotImplemented + # If it's a list or whatever, treat it like an array + other_a = np.asanyarray(other) + if other_a.ndim == 0 and other_a.dtype == np.object_: + # numpy creates a 0d object array if all else fails. + # Not interpretable as an array; return NotImplemented so + # other's dunder methods can kick in if implemented. + return NotImplemented + # Allow custom sparse class indicated by attr sparse gh-6520 + try: + other.shape + except AttributeError: + other = other_a + + if isscalarlike(other): + if not op(0, other): + if np.isnan(other): # op is not `ne`, so results are all False. + return self.__class__(self.shape, dtype=np.bool_) + if self.ndim < 3: + cs_self = self if self.format in ('csc', 'csr') else self.tocsr() + return cs_self._scalar_binopt(other, op) + csr_self = self.reshape(1, -1).tocsr() + result = csr_self._scalar_binopt(other, op) + return result.tocoo().reshape(self.shape) + else: + warn(f"Comparing a sparse matrix with {other} using {op_sym[op]} " + f"is inefficient. Try using {op_sym[op_neg[op]]} instead.", + SparseEfficiencyWarning, stacklevel=3) + if np.isnan(other): + # op is `ne` cuz op(0, other) and isnan(other). Return all True. + return self.__class__(np.ones(self.shape, dtype=np.bool_)) + + # op is eq, le, or ge. Use negated op and then negate. + if self.ndim < 3: + cs_self = self if self.format in ('csc', 'csr') else self.tocsr() + inv = cs_self._scalar_binopt(other, op_neg[op]) + all_true = cs_self.__class__(np.ones(cs_self.shape, dtype=np.bool_)) + return all_true - inv + + csr_self = self.reshape(1, -1).tocsr() + inv = csr_self._scalar_binopt(other, op_neg[op]) + all_true = csr_self.__class__(np.ones(csr_self.shape, dtype=np.bool_)) + result = all_true - inv + return result.tocoo().reshape(self.shape) + + elif isdense(other): + return op(self.todense(), other) + + elif issparse(other): + # TODO sparse broadcasting + if self.shape != other.shape: + # eq and ne return True or False instead of an array when the shapes + # don't match. Numpy doesn't do this. Is this what we want? + if op in (operator.eq, operator.ne): + return op is operator.ne + raise ValueError("inconsistent shape") + + if self.ndim < 3: + cs_self = self if self.format in ('csc', 'csr') else self.tocsr() + cs_other = other + else: + cs_self = self.reshape(1, -1).tocsr() + cs_other = other.reshape(1, -1).tocsr() + if not op(0, 0): + result = cs_self._binopt(cs_other, f'_{op.__name__}_') + return result if self.ndim < 3 else result.tocoo().reshape(self.shape) + else: + # result will not be sparse. Use negated op and then negate. + warn(f"Comparing two sparse matrices using {op_sym[op]} " + f"is inefficient. Try using {op_sym[op_neg[op]]} instead.", + SparseEfficiencyWarning, stacklevel=3) + inv = cs_self._binopt(cs_other, f'_{op_neg[op].__name__}_') + all_true = cs_self.__class__(np.ones(cs_self.shape, dtype=np.bool_)) + result = all_true - inv + return result if self.ndim < 3 else result.tocoo().reshape(self.shape) + else: + # cannot compare with other, but it might compare with us. + return NotImplemented + + def __eq__(self, other): + return self._comparison(other, operator.eq) + + def __ne__(self, other): + return self._comparison(other, operator.ne) + + def __lt__(self, other): + return self._comparison(other, operator.lt) + + def __gt__(self, other): + return self._comparison(other, operator.gt) + + def __le__(self, other): + return self._comparison(other, operator.le) + + def __ge__(self, other): + return self._comparison(other, operator.ge) + + def __abs__(self): + return abs(self.tocsr()) + + def __round__(self, ndigits=0): + return round(self.tocsr(), ndigits=ndigits) + + def _add_sparse(self, other): + return self.tocsr()._add_sparse(other) + + def _add_dense(self, other): + return self.tocoo()._add_dense(other) + + def _sub_sparse(self, other): + return self.tocsr()._sub_sparse(other) + + def _sub_dense(self, other): + return self.todense() - other + + def _rsub_dense(self, other): + # note: this can't be replaced by other + (-self) for unsigned types + return other - self.todense() + + def __add__(self, other): # self + other + if isscalarlike(other): + if other == 0: + return self.copy() + # Now we would add this scalar to every element. + raise NotImplementedError('adding a nonzero scalar to a ' + 'sparse array is not supported') + elif issparse(other): + if other.shape != self.shape: + raise ValueError("inconsistent shapes") + return self._add_sparse(other) + elif isdense(other): + other = np.broadcast_to(other, self.shape) + return self._add_dense(other) + else: + return NotImplemented + + def __radd__(self,other): # other + self + return self.__add__(other) + + def __sub__(self, other): # self - other + if isscalarlike(other): + if other == 0: + return self.copy() + raise NotImplementedError('subtracting a nonzero scalar from a ' + 'sparse array is not supported') + elif issparse(other): + if other.shape != self.shape: + raise ValueError("inconsistent shapes") + return self._sub_sparse(other) + elif isdense(other): + other = np.broadcast_to(other, self.shape) + return self._sub_dense(other) + else: + return NotImplemented + + def __rsub__(self,other): # other - self + if isscalarlike(other): + if other == 0: + return -self.copy() + raise NotImplementedError('subtracting a sparse array from a ' + 'nonzero scalar is not supported') + elif isdense(other): + other = np.broadcast_to(other, self.shape) + return self._rsub_dense(other) + else: + return NotImplemented + + def _matmul_dispatch(self, other): + """np.array-like matmul & `np.matrix`-like mul, i.e. `dot` or `NotImplemented` + + interpret other and call one of the following + self._mul_scalar() + self._matmul_vector() + self._matmul_multivector() + self._matmul_sparse() + """ + # This method has to be different from `__matmul__` because it is also + # called by sparse matrix classes. + + # Currently matrix multiplication is only supported + # for 2D arrays. Hence we unpacked and use only the + # two last axes' lengths. + M, N = self._shape_as_2d + + if other.__class__ is np.ndarray: + # Fast path for the most common case + if other.shape == (N,): + return self._matmul_vector(other) + elif other.shape == (N, 1): + result = self._matmul_vector(other.ravel()) + if self.ndim == 1: + return result.reshape(1) + return result.reshape(M, 1) + elif other.ndim == 2 and other.shape[0] == N: + return self._matmul_multivector(other) + + if isscalarlike(other): + # scalar value + return self._mul_scalar(other) + + err_prefix = "matmul: dimension mismatch with signature" + if issparse(other): + if N != other.shape[0]: + raise ValueError( + f"{err_prefix} (n,k={N}),(k={other.shape[0]},m)->(n,m)" + ) + return self._matmul_sparse(other) + + # If it's a list or whatever, treat it like an array + other_a = np.asanyarray(other) + if other_a.ndim == 0 and other_a.dtype == np.object_: + # numpy creates a 0d object array if all else fails. + # Not interpretable as an array; return NotImplemented so that + # other's __rmatmul__ can kick in if that's implemented. + return NotImplemented + # Allow custom sparse class indicated by attr sparse gh-6520 + try: + other.shape + except AttributeError: + other = other_a + + if other.ndim == 1 or other.ndim == 2 and other.shape[1] == 1: + # dense row or column vector + if other.shape[0] != N: + raise ValueError( + f"{err_prefix} (n,k={N}),(k={other.shape[0]},1?)->(n,1?)" + ) + + result = self._matmul_vector(np.ravel(other)) + + if isinstance(other, np.matrix): + result = self._ascontainer(result) + + if other.ndim == 2 and other.shape[1] == 1: + # If 'other' was an (nx1) column vector, reshape the result + if self.ndim == 1: + result = result.reshape(1) + else: + result = result.reshape(-1, 1) + + return result + + elif other.ndim == 2: + ## + # dense 2D array or matrix ("multivector") + + if other.shape[0] != N: + raise ValueError( + f"{err_prefix} (n,k={N}),(k={other.shape[0]},m)->(n,m)" + ) + + result = self._matmul_multivector(np.asarray(other)) + + if isinstance(other, np.matrix): + result = self._ascontainer(result) + + return result + + else: + raise ValueError('could not interpret dimensions') + + def __mul__(self, other): + return self.multiply(other) + + def __rmul__(self, other): # other * self + return self.multiply(other) + + # by default, use CSR for __mul__ handlers + def _mul_scalar(self, other): + return self.tocsr()._mul_scalar(other) + + def _matmul_vector(self, other): + return self.tocsr()._matmul_vector(other) + + def _matmul_multivector(self, other): + return self.tocsr()._matmul_multivector(other) + + def _matmul_sparse(self, other): + return self.tocsr()._matmul_sparse(other) + + def _rmatmul_dispatch(self, other): + if isscalarlike(other): + return self._mul_scalar(other) + else: + # Don't use asarray unless we have to + try: + tr = other.transpose() + except AttributeError: + tr = np.asarray(other).transpose() + ret = self.transpose()._matmul_dispatch(tr) + if ret is NotImplemented: + return NotImplemented + return ret.transpose() + + ####################### + # matmul (@) operator # + ####################### + + def __matmul__(self, other): + if isscalarlike(other): + raise ValueError("Scalar operands are not allowed, " + "use '*' instead") + return self._matmul_dispatch(other) + + def __rmatmul__(self, other): + if isscalarlike(other): + raise ValueError("Scalar operands are not allowed, " + "use '*' instead") + return self._rmatmul_dispatch(other) + + #################### + # Other Arithmetic # + #################### + + def _divide(self, other, *, rdivide=False): + if not (issparse(other) or isdense(other) or isscalarlike(other)): + # If it's a list or whatever, treat it like an array + other_a = np.asanyarray(other) + if other_a.ndim == 0 and other_a.dtype == np.object_: + # numpy creates a 0d object array if all else fails. + # Not interpretable as an array; return NotImplemented so that + # other's __rtruediv__ can kick in if that's implemented. + return NotImplemented + # Allow custom sparse class indicated by attr sparse gh-6520 + try: + other.shape + except AttributeError: + other = other_a + + if isscalarlike(other): + if rdivide: + return np.divide(other, self.todense()) + + if np.can_cast(self.dtype, np.float64): + return self.astype(np.float64, copy=False)._mul_scalar(1 / other) + else: + r = self._mul_scalar(1 / other) + + scalar_dtype = np.asarray(other).dtype + if (np.issubdtype(self.dtype, np.integer) and + np.issubdtype(scalar_dtype, np.integer)): + return r.astype(self.dtype, copy=False) + else: + return r + + elif isdense(other): + if rdivide: + return np.divide(other, self.todense()) + + return self.multiply(np.divide(1, other)) + + elif issparse(other): + if rdivide: + return other._divide(self, rdivide=False) + + csr_self = (self if self.ndim < 3 else self.reshape(1, -1)).tocsr() + csr_other = (other if self.ndim < 3 else other.reshape(1, -1)).tocsr() + if np.can_cast(self.dtype, np.float64): + csr_self = csr_self.astype(np.float64, copy=False) + result = csr_self._divide_sparse(csr_other) + return result if self.ndim < 3 else result.reshape(self.shape) + else: + # not scalar, dense or sparse. Return NotImplemented so + # other's __rtruediv__ can kick in if that's implemented. + return NotImplemented + + def __truediv__(self, other): + return self._divide(other) + + def __rtruediv__(self, other): + # Implementing this as the inverse would be too magical -- bail out + return NotImplemented + + def __neg__(self): + return -self.tocsr() + + def __iadd__(self, other): + return NotImplemented + + def __isub__(self, other): + return NotImplemented + + def __imul__(self, other): + return NotImplemented + + def __itruediv__(self, other): + return NotImplemented + + def __pow__(self, *args, **kwargs): + return self.power(*args, **kwargs) + + def transpose(self, axes=None, copy=False): + """ + Reverses the dimensions of the sparse array/matrix. + + Parameters + ---------- + axes : None, optional + This argument is in the signature *solely* for NumPy + compatibility reasons. Do not pass in anything except + for the default value. + copy : bool, optional + Indicates whether or not attributes of `self` should be + copied whenever possible. The degree to which attributes + are copied varies depending on the type of sparse array/matrix + being used. + + Returns + ------- + p : sparse array/matrix + The transpose of the array/matrix. + + See Also + -------- + numpy.transpose : NumPy's implementation of 'transpose' for ndarrays + + Notes + ----- + If `self` is a `csr_array` or a `csc_array`, then this will return a + `csc_array` or a `csr_array`, respectively. + """ + return self.tocsr(copy=copy).transpose(axes=axes, copy=False) + + def conjugate(self, copy=True): + """Element-wise complex conjugation. + + If the array/matrix is of non-complex data type and `copy` is False, + this method does nothing and the data is not copied. + + Parameters + ---------- + copy : bool, optional + If True, the result is guaranteed to not share data with self. + + Returns + ------- + A : sparse array/matrix + The element-wise complex conjugate. + """ + if np.issubdtype(self.dtype, np.complexfloating): + return self.tocsr(copy=copy).conjugate(copy=False) + elif copy: + return self.copy() + else: + return self + + def conj(self, copy=True): + return self.conjugate(copy=copy) + + conj.__doc__ = conjugate.__doc__ + + def _real(self): + return self.tocsr()._real() + + def _imag(self): + return self.tocsr()._imag() + + def nonzero(self): + """Nonzero indices of the array/matrix. + + Returns + ------- + row : ndarray + Row indices of non-zero elements. + col : ndarray + Column indices of non-zero elements. + + Examples + -------- + >>> from scipy.sparse import csr_array + >>> A = csr_array([[1, 2, 0], [0, 0, 3], [4, 0, 5]]) + >>> A.nonzero() + (array([0, 0, 1, 2, 2], dtype=int32), array([0, 1, 2, 0, 2], dtype=int32)) + + """ + + # convert to COOrdinate format + A = self.tocoo() + nz_mask = A.data != 0 + return tuple(idx[nz_mask] for idx in A.coords) + + def _getcol(self, j, /): + """Returns a copy of column j of the array, as an (m x 1) sparse + array (column vector). + """ + if self.ndim == 1: + raise ValueError("getcol not provided for 1d arrays. Use indexing A[j]") + # Subclasses should override this method for efficiency. + # Post-multiply by a (n x 1) column vector 'a' containing all zeros + # except for a_j = 1 + N = self.shape[-1] + if j < 0: + j += N + if j < 0 or j >= N: + raise IndexError("index out of bounds") + col_selector = self._csc_container(([1], [[j], [0]]), + shape=(N, 1), dtype=self.dtype) + result = self @ col_selector + return result + + def _getrow(self, i): + """Returns a copy of row i of the array, as a (1 x n) sparse + array (row vector). + """ + if self.ndim == 1: + raise ValueError("getrow not meaningful for a 1d array") + # Subclasses should override this method for efficiency. + # Pre-multiply by a (1 x m) row vector 'a' containing all zeros + # except for a_i = 1 + M = self.shape[0] + if i < 0: + i += M + if i < 0 or i >= M: + raise IndexError("index out of bounds") + row_selector = self._csr_container(([1], [[0], [i]]), + shape=(1, M), dtype=self.dtype) + return row_selector @ self + + # The following dunder methods cannot be implemented. + # + # def __array__(self): + # # Sparse matrices rely on NumPy wrapping them in object arrays under + # # the hood to make unary ufuncs work on them. So we cannot raise + # # TypeError here - which would be handy to not give users object + # # arrays they probably don't want (they're looking for `.toarray()`). + # # + # # Conversion with `toarray()` would also break things because of the + # # behavior discussed above, plus we want to avoid densification by + # # accident because that can too easily blow up memory. + # + # def __array_ufunc__(self): + # # We cannot implement __array_ufunc__ due to mismatching semantics. + # # See gh-7707 and gh-7349 for details. + # + # def __array_function__(self): + # # We cannot implement __array_function__ due to mismatching semantics. + # # See gh-10362 for details. + + def todense(self, order=None, out=None): + """ + Return a dense representation of this sparse array. + + Parameters + ---------- + order : {'C', 'F'}, optional + Whether to store multi-dimensional data in C (row-major) + or Fortran (column-major) order in memory. The default + is 'None', which provides no ordering guarantees. + Cannot be specified in conjunction with the `out` + argument. + + out : ndarray, 2-D, optional + If specified, uses this array as the output buffer + instead of allocating a new array to return. The + provided array must have the same shape and dtype as + the sparse array on which you are calling the method. + + Returns + ------- + arr : ndarray, 2-D + An array with the same shape and containing the same + data represented by the sparse array, with the requested + memory order. If `out` was passed, the same object is + returned after being modified in-place to contain the + appropriate values. + """ + return self._ascontainer(self.toarray(order=order, out=out)) + + def toarray(self, order=None, out=None): + """ + Return a dense ndarray representation of this sparse array/matrix. + + Parameters + ---------- + order : {'C', 'F'}, optional + Whether to store multidimensional data in C (row-major) + or Fortran (column-major) order in memory. The default + is 'None', which provides no ordering guarantees. + Cannot be specified in conjunction with the `out` + argument. + + out : ndarray, 2-D, optional + If specified, uses this array as the output buffer + instead of allocating a new array to return. The provided + array must have the same shape and dtype as the sparse + array/matrix on which you are calling the method. For most + sparse types, `out` is required to be memory contiguous + (either C or Fortran ordered). + + Returns + ------- + arr : ndarray, 2-D + An array with the same shape and containing the same + data represented by the sparse array/matrix, with the requested + memory order. If `out` was passed, the same object is + returned after being modified in-place to contain the + appropriate values. + """ + return self.tocoo(copy=False).toarray(order=order, out=out) + + # Any sparse array format deriving from _spbase must define one of + # tocsr or tocoo. The other conversion methods may be implemented for + # efficiency, but are not required. + def tocsr(self, copy=False): + """Convert this array/matrix to Compressed Sparse Row format. + + Parameters + ---------- + copy : bool, optional + With ``copy=False``, the data/indices may be shared between this + array/matrix and the resultant csr_array/matrix. + + Returns + ------- + csr array/matrix + The converted array/matrix in CSR format. + """ + return self.tocoo(copy=copy).tocsr(copy=False) + + def todok(self, copy=False): + """Convert this array/matrix to Dictionary Of Keys format. + + Parameters + ---------- + copy : bool, optional + With ``copy=False``, the data/indices may be shared between this + array/matrix and the resultant dok_array/matrix. + + Returns + ------- + dok array/matrix + The converted array/matrix in DOK format. + """ + return self.tocoo(copy=copy).todok(copy=False) + + def tocoo(self, copy=False): + """Convert this array/matrix to COOrdinate format. + + Parameters + ---------- + copy : bool, optional + With ``copy=False``, the data/indices may be shared between this + array/matrix and the resultant coo_array/matrix. + + Returns + ------- + coo array/matrix + The converted array/matrix in COO format. + """ + return self.tocsr(copy=False).tocoo(copy=copy) + + def tolil(self, copy=False): + """Convert this array/matrix to List of Lists format. + + Parameters + ---------- + copy : bool, optional + With ``copy=False``, the data/indices may be shared between this + array/matrix and the resultant lil_array/matrix. + + Returns + ------- + lil array/matrix + The converted array/matrix in LIL format. + """ + return self.tocsr(copy=False).tolil(copy=copy) + + def todia(self, copy=False): + """Convert this array/matrix to sparse DIAgonal format. + + Parameters + ---------- + copy : bool, optional + With ``copy=False``, the data/indices may be shared between this + array/matrix and the resultant dia_array/matrix. + + Returns + ------- + dia array/matrix + The converted array/matrix in DIA format. + """ + return self.tocoo(copy=copy).todia(copy=False) + + def tobsr(self, blocksize=None, copy=False): + """Convert this array/matrix to Block Sparse Row format. + + Parameters + ---------- + blocksize : tuple of ints, optional + When ``blocksize=(R, C)`` is provided, it will be used for construction of + the bsr_array/matrix. + copy : bool, option + With ``copy=False``, the data/indices may be shared between this + array/matrix and the resultant bsr_array/matrix. + + Returns + ------- + bsr array/matrix + The converted array/matrix in BSR format. + """ + return self.tocsr(copy=False).tobsr(blocksize=blocksize, copy=copy) + + def tocsc(self, copy=False): + """Convert this array/matrix to Compressed Sparse Column format. + + Parameters + ---------- + copy : bool, optional + With ``copy=False``, the data/indices may be shared between this + array/matrix and the resultant csc_array/matrix. + + Returns + ------- + csc array/matrix + The converted array/matrix in CSC format. + """ + return self.tocsr(copy=copy).tocsc(copy=False) + + def copy(self): + """Returns a copy of this array/matrix. + + No data/indices will be shared between the returned value and current + array/matrix. + + Returns + ------- + sparse array/matrix + A copy of this array/matrix. + """ + return self.__class__(self, copy=True) + + def sum(self, axis=None, dtype=None, out=None): + """ + Sum the array/matrix elements over a given axis. + + Parameters + ---------- + axis : {-2, -1, 0, 1, None} optional + Axis along which the sum is computed. The default is to + compute the sum of all the array/matrix elements, returning a scalar + (i.e., `axis` = `None`). + dtype : dtype, optional + The type of the returned array/matrix and of the accumulator in which + the elements are summed. The dtype of `a` is used by default + unless `a` has an integer dtype of less precision than the default + platform integer. In that case, if `a` is signed then the platform + integer is used while if `a` is unsigned then an unsigned integer + of the same precision as the platform integer is used. + + .. versionadded:: 0.18.0 + + out : np.matrix, optional + Alternative output matrix in which to place the result. It must + have the same shape as the expected output, but the type of the + output values will be cast if necessary. + + .. versionadded:: 0.18.0 + + Returns + ------- + sum_along_axis : np.matrix + A matrix with the same shape as `self`, with the specified + axis removed. + + See Also + -------- + numpy.matrix.sum : NumPy's implementation of 'sum' for matrices + + """ + axis = validateaxis(axis, ndim=self.ndim) + + # Mimic numpy's casting. + res_dtype = get_sum_dtype(self.dtype) + + if dtype is not None: + # Before casting to the requested dtype, canonicalize duplicates and zeros. + if hasattr(self, 'sum_duplicates'): + self.sum_duplicates() + temp = self.astype(dtype, copy=False).sum(axis=axis, dtype=None, out=out) + return temp.astype(dtype, copy=False) + + # Note: all valid 1D axis values are canonically `None`. + if axis is None: + if self.nnz == 0: + return np.sum(self._ascontainer([0]), dtype=dtype or res_dtype, out=out) + return np.sum(self._ascontainer(_todata(self)), dtype=dtype, out=out) + elif isinstance(self, sparray): + new_shape = tuple(self.shape[i] for i in range(self.ndim) if i not in axis) + else: + # Ensure spmatrix sums stay 2D + new_shape = (1, self.shape[1]) if axis == (0,) else (self.shape[0], 1) + + if out is None: + # create out array with desired dtype + out = self._ascontainer(np.zeros(new_shape, dtype=dtype or res_dtype)) + else: + if out.shape != new_shape: + raise ValueError("out dimensions do not match shape") + + if self.ndim > 2: + return self._sum_nd(axis, res_dtype, out) + + # We use multiplication by a matrix of ones to sum. + # For some sparse array formats more efficient methods are + # possible -- these should override this function. + if axis == (0,): + ones = self._ascontainer(np.ones((1, self.shape[0]), dtype=res_dtype)) + # sets dtype while loading into out + out[...] = (ones @ self).reshape(new_shape) + else: # axis == (1,) + ones = self._ascontainer(np.ones((self.shape[1], 1), dtype=res_dtype)) + # sets dtype while loading into out + out[...] = (self @ ones).reshape(new_shape) + return out + + def mean(self, axis=None, dtype=None, out=None): + """ + Compute the arithmetic mean along the specified axis. + + Returns the average of the array/matrix elements. The average is taken + over all elements in the array/matrix by default, otherwise over the + specified axis. `float64` intermediate and return values are used + for integer inputs. + + Parameters + ---------- + axis : {-2, -1, 0, 1, None} optional + Axis along which the mean is computed. The default is to compute + the mean of all elements in the array/matrix (i.e., `axis` = `None`). + dtype : data-type, optional + Type to use in computing the mean. For integer inputs, the default + is `float64`; for floating point inputs, it is the same as the + input dtype. + + .. versionadded:: 0.18.0 + + out : np.matrix, optional + Alternative output matrix in which to place the result. It must + have the same shape as the expected output, but the type of the + output values will be cast if necessary. + + .. versionadded:: 0.18.0 + + Returns + ------- + m : np.matrix + Mean along the specified axis. + + See Also + -------- + numpy.matrix.mean : NumPy's implementation of 'mean' for matrices + + """ + axis = validateaxis(axis, ndim=self.ndim) + + integral = (np.issubdtype(self.dtype, np.integer) or + np.issubdtype(self.dtype, np.bool_)) + + # intermediate dtype for summation + inter_dtype = np.float64 if integral else self.dtype + inter_self = self.astype(inter_dtype, copy=False) + + if axis is None: + denom = math.prod(self.shape) + else: + denom = math.prod(self.shape[ax] for ax in axis) + res = (inter_self * (1.0 / denom)).sum(axis=axis, dtype=inter_dtype, out=out) + if dtype is not None and out is None: + return res.astype(dtype, copy=False) + return res + + + def diagonal(self, k=0): + """Returns the kth diagonal of the array/matrix. + + Parameters + ---------- + k : int, optional + Which diagonal to get, corresponding to elements a[i, i+k]. + Default: 0 (the main diagonal). + + .. versionadded:: 1.0 + + Returns + ------- + d : ndarray + The specified diagonal of the array/matrix. + + See Also + -------- + numpy.diagonal : Equivalent numpy function. + + Examples + -------- + >>> from scipy.sparse import csr_array + >>> A = csr_array([[1, 2, 0], [0, 0, 3], [4, 0, 5]]) + >>> A.diagonal() + array([1, 0, 5]) + >>> A.diagonal(k=1) + array([2, 3]) + """ + return self.tocsr().diagonal(k=k) + + def trace(self, offset=0): + """Returns the sum along diagonals of the sparse array/matrix. + + Parameters + ---------- + offset : int, optional + Which diagonal to get, corresponding to elements a[i, i+offset]. + Default: 0 (the main diagonal). + + Returns + ------- + scalar + The sum of the specified diagonal of the array/matrix. + """ + return self.diagonal(k=offset).sum() + + def setdiag(self, values, k=0): + """ + Set diagonal or off-diagonal elements of the array/matrix. + + Parameters + ---------- + values : array_like + New values of the diagonal elements. + + Values may have any length. If the diagonal is longer than values, + then the remaining diagonal entries will not be set. If values are + longer than the diagonal, then the remaining values are ignored. + + If a scalar value is given, all of the diagonal is set to it. + + k : int, optional + Which off-diagonal to set, corresponding to elements a[i,i+k]. + Default: 0 (the main diagonal). + + """ + M, N = self.shape + if (k > 0 and k >= N) or (k < 0 and -k >= M): + raise ValueError("k exceeds array dimensions") + self._setdiag(np.asarray(values), k) + + def _setdiag(self, values, k): + """This part of the implementation gets overridden by the + different formats. + """ + M, N = self.shape + if k < 0: + if values.ndim == 0: + # broadcast + max_index = min(M+k, N) + for i in range(max_index): + self[i - k, i] = values + else: + max_index = min(M+k, N, len(values)) + if max_index <= 0: + return + for i, v in enumerate(values[:max_index]): + self[i - k, i] = v + else: + if values.ndim == 0: + # broadcast + max_index = min(M, N-k) + for i in range(max_index): + self[i, i + k] = values + else: + max_index = min(M, N-k, len(values)) + if max_index <= 0: + return + for i, v in enumerate(values[:max_index]): + self[i, i + k] = v + + def _process_toarray_args(self, order, out): + if out is not None: + if order is not None: + raise ValueError('order cannot be specified if out ' + 'is not None') + if out.shape != self.shape or out.dtype != self.dtype: + raise ValueError('out array must be same dtype and shape as ' + 'sparse array') + out[...] = 0. + return out + else: + return np.zeros(self.shape, dtype=self.dtype, order=order) + + def _get_index_dtype(self, arrays=(), maxval=None, check_contents=False): + """ + Determine index dtype for array. + + This wraps _sputils.get_index_dtype, providing compatibility for both + array and matrix API sparse matrices. Matrix API sparse matrices would + attempt to downcast the indices - which can be computationally + expensive and undesirable for users. The array API changes this + behaviour. + + See discussion: https://github.com/scipy/scipy/issues/16774 + + The get_index_dtype import is due to implementation details of the test + suite. It allows the decorator ``with_64bit_maxval_limit`` to mock a + lower int32 max value for checks on the matrix API's downcasting + behaviour. + """ + from ._sputils import get_index_dtype + + # Don't check contents for array API + return get_index_dtype(arrays, + maxval, + (check_contents and not isinstance(self, sparray))) + + +class sparray: + """A namespace class to separate sparray from spmatrix. + + This class serves as the namespace for SciPy sparse array types. + It cannot be instantiated and is designed as a mixin class. + """ + + @classmethod + def __class_getitem__(cls, arg, /): + """ + Return a parametrized wrapper around the `~scipy.sparse.sparray` type. + + .. versionadded:: 1.16.0 + + Returns + ------- + alias : types.GenericAlias + A parametrized `~scipy.sparse.sparray` type. + + Examples + -------- + >>> import numpy as np + >>> from scipy.sparse import coo_array + + >>> coo_array[np.int8, tuple[int]] + scipy.sparse._coo.coo_array[numpy.int8, tuple[int]] + """ + from types import GenericAlias + return GenericAlias(cls, arg) + + +sparray.__doc__ = _spbase.__doc__ + + +def isspmatrix(x): + """Is `x` of a sparse matrix type? + + .. warning:: + + scipy.sparse is switching to the sparse array interface. + + The ``isspmatrix`` function returns ``False`` for sparse arrays. + It will remain after the switch to sparse arrays (sparray). + So this is a future proof way to check for sparray vs spmatrix. + If you just want to check for sparse, use ``issparse(A)``. + For more general information about sparrays, see + :ref:`Migration from spmatrix to sparray `. + The switch to sparse arrays will occur no earlier than v1.21. + + Parameters + ---------- + x + object to check for being a sparse matrix + + Returns + ------- + bool + True if `x` is a sparse matrix, False otherwise + + Examples + -------- + >>> import numpy as np + >>> from scipy.sparse import csr_array, csr_matrix, isspmatrix + >>> isspmatrix(csr_matrix([[5]])) + True + >>> isspmatrix(csr_array([[5]])) + False + >>> isspmatrix(np.array([[5]])) + False + >>> isspmatrix(5) + False + """ + return isinstance(x, spmatrix) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/_bsr.py b/venv/lib/python3.12/site-packages/scipy/sparse/_bsr.py new file mode 100644 index 0000000000000000000000000000000000000000..2375ccc4e2c98d70924bb1a072c39be316a453c9 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/_bsr.py @@ -0,0 +1,904 @@ +"""Compressed Block Sparse Row format""" + +__docformat__ = "restructuredtext en" + +__all__ = ['bsr_array', 'bsr_matrix', 'isspmatrix_bsr'] + +from warnings import warn + +import numpy as np + +from scipy._lib._util import copy_if_needed +from ._matrix import spmatrix +from ._data import _data_matrix, _minmax_mixin +from ._compressed import _cs_matrix +from ._base import issparse, _formats, _spbase, sparray +from ._sputils import (isshape, getdtype, getdata, to_native, upcast, + check_shape) +from . import _sparsetools +from ._sparsetools import (bsr_matvec, bsr_matvecs, csr_matmat_maxnnz, + bsr_matmat, bsr_transpose, bsr_sort_indices, + bsr_tocsr) + + +class _bsr_base(_cs_matrix, _minmax_mixin): + _format = 'bsr' + + def __init__(self, arg1, shape=None, dtype=None, copy=False, + blocksize=None, *, maxprint=None): + _data_matrix.__init__(self, arg1, maxprint=maxprint) + + if issparse(arg1): + if arg1.format == self.format and copy: + arg1 = arg1.copy() + else: + arg1 = arg1.tobsr(blocksize=blocksize) + self.indptr, self.indices, self.data, self._shape = ( + arg1.indptr, arg1.indices, arg1.data, arg1._shape + ) + + elif isinstance(arg1,tuple): + if isshape(arg1): + # it's a tuple of matrix dimensions (M,N) + self._shape = check_shape(arg1) + M,N = self.shape + # process blocksize + if blocksize is None: + blocksize = (1,1) + else: + if not isshape(blocksize): + raise ValueError(f'invalid blocksize={blocksize}') + blocksize = tuple(blocksize) + self.data = np.zeros((0,) + blocksize, getdtype(dtype, default=float)) + + R,C = blocksize + if (M % R) != 0 or (N % C) != 0: + raise ValueError('shape must be multiple of blocksize') + + # Select index dtype large enough to pass array and + # scalar parameters to sparsetools + idx_dtype = self._get_index_dtype(maxval=max(M//R, N//C, R, C)) + self.indices = np.zeros(0, dtype=idx_dtype) + self.indptr = np.zeros(M//R + 1, dtype=idx_dtype) + + elif len(arg1) == 2: + # (data,(row,col)) format + coo = self._coo_container(arg1, dtype=dtype, shape=shape) + bsr = coo.tobsr(blocksize=blocksize) + self.indptr, self.indices, self.data, self._shape = ( + bsr.indptr, bsr.indices, bsr.data, bsr._shape + ) + + elif len(arg1) == 3: + # (data,indices,indptr) format + (data, indices, indptr) = arg1 + + # Select index dtype large enough to pass array and + # scalar parameters to sparsetools + maxval = 1 + if shape is not None: + maxval = max(shape) + if blocksize is not None: + maxval = max(maxval, max(blocksize)) + idx_dtype = self._get_index_dtype((indices, indptr), maxval=maxval, + check_contents=True) + if not copy: + copy = copy_if_needed + self.indices = np.array(indices, copy=copy, dtype=idx_dtype) + self.indptr = np.array(indptr, copy=copy, dtype=idx_dtype) + self.data = getdata(data, copy=copy, dtype=dtype) + if self.data.ndim != 3: + raise ValueError( + f'BSR data must be 3-dimensional, got shape={self.data.shape}' + ) + if blocksize is not None: + if not isshape(blocksize): + raise ValueError(f'invalid blocksize={blocksize}') + if tuple(blocksize) != self.data.shape[1:]: + raise ValueError( + f'mismatching blocksize={blocksize}' + f' vs {self.data.shape[1:]}' + ) + else: + raise ValueError('unrecognized bsr_array constructor usage') + else: + # must be dense + try: + arg1 = np.asarray(arg1) + except Exception as e: + raise ValueError("unrecognized form for " + f"{self.format}_matrix constructor") from e + if isinstance(self, sparray) and arg1.ndim != 2: + raise ValueError(f"BSR arrays don't support {arg1.ndim}D input. Use 2D") + arg1 = self._coo_container(arg1, dtype=dtype).tobsr(blocksize=blocksize) + self.indptr, self.indices, self.data, self._shape = ( + arg1.indptr, arg1.indices, arg1.data, arg1._shape + ) + + if shape is not None: + self._shape = check_shape(shape) + else: + if self.shape is None: + # shape not already set, try to infer dimensions + try: + M = len(self.indptr) - 1 + N = self.indices.max() + 1 + except Exception as e: + raise ValueError('unable to infer matrix dimensions') from e + else: + R,C = self.blocksize + self._shape = check_shape((M*R,N*C)) + + if self.shape is None: + if shape is None: + # TODO infer shape here + raise ValueError('need to infer shape') + else: + self._shape = check_shape(shape) + + if dtype is not None: + self.data = self.data.astype(getdtype(dtype, self.data), copy=False) + + self.check_format(full_check=False) + + def check_format(self, full_check=True): + """Check whether the array/matrix respects the BSR format. + + Parameters + ---------- + full_check : bool, optional + If `True`, run rigorous check, scanning arrays for valid values. + Note that activating those check might copy arrays for casting, + modifying indices and index pointers' inplace. + If `False`, run basic checks on attributes. O(1) operations. + Default is `True`. + """ + M,N = self.shape + R,C = self.blocksize + + # index arrays should have integer data types + if self.indptr.dtype.kind != 'i': + warn(f"indptr array has non-integer dtype ({self.indptr.dtype.name})", + stacklevel=2) + if self.indices.dtype.kind != 'i': + warn(f"indices array has non-integer dtype ({self.indices.dtype.name})", + stacklevel=2) + + # check array shapes + if self.indices.ndim != 1 or self.indptr.ndim != 1: + raise ValueError("indices, and indptr should be 1-D") + if self.data.ndim != 3: + raise ValueError("data should be 3-D") + + # check index pointer + if (len(self.indptr) != M//R + 1): + raise ValueError( + f"index pointer size ({len(self.indptr)}) should be ({M//R + 1})" + ) + if (self.indptr[0] != 0): + raise ValueError("index pointer should start with 0") + + # check index and data arrays + if (len(self.indices) != len(self.data)): + raise ValueError("indices and data should have the same size") + if (self.indptr[-1] > len(self.indices)): + raise ValueError("Last value of index pointer should be less than " + "the size of index and data arrays") + + self.prune() + + if full_check: + # check format validity (more expensive) + if self.nnz > 0: + if self.indices.max() >= N//C: + raise ValueError( + f"column index values must be < {N//C}" + f" (now max {self.indices.max()})" + ) + if self.indices.min() < 0: + raise ValueError("column index values must be >= 0") + if np.diff(self.indptr).min() < 0: + raise ValueError("index pointer values must form a " + "non-decreasing sequence") + + idx_dtype = self._get_index_dtype((self.indices, self.indptr)) + self.indptr = np.asarray(self.indptr, dtype=idx_dtype) + self.indices = np.asarray(self.indices, dtype=idx_dtype) + self.data = to_native(self.data) + # if not self.has_sorted_indices(): + # warn('Indices were not in sorted order. Sorting indices.') + # self.sort_indices(check_first=False) + + @property + def blocksize(self) -> tuple: + """Block size of the matrix.""" + return self.data.shape[1:] + + def _getnnz(self, axis=None): + if axis is not None: + raise NotImplementedError("_getnnz over an axis is not implemented " + "for BSR format") + R, C = self.blocksize + return int(self.indptr[-1]) * R * C + + _getnnz.__doc__ = _spbase._getnnz.__doc__ + + def count_nonzero(self, axis=None): + if axis is not None: + return self.tocsr().count_nonzero(axis=axis) + return np.count_nonzero(self._deduped_data()) + + count_nonzero.__doc__ = _spbase.count_nonzero.__doc__ + + def __repr__(self): + _, fmt = _formats[self.format] + sparse_cls = 'array' if isinstance(self, sparray) else 'matrix' + b = 'x'.join(str(x) for x in self.blocksize) + return ( + f"<{fmt} sparse {sparse_cls} of dtype '{self.dtype}'\n" + f"\twith {self.nnz} stored elements (blocksize={b}) and shape {self.shape}>" + ) + + def diagonal(self, k=0): + rows, cols = self.shape + if k <= -rows or k >= cols: + return np.empty(0, dtype=self.data.dtype) + R, C = self.blocksize + y = np.zeros(min(rows + min(k, 0), cols - max(k, 0)), + dtype=upcast(self.dtype)) + _sparsetools.bsr_diagonal(k, rows // R, cols // C, R, C, + self.indptr, self.indices, + np.ravel(self.data), y) + return y + + diagonal.__doc__ = _spbase.diagonal.__doc__ + + ########################## + # NotImplemented methods # + ########################## + + def __getitem__(self,key): + raise NotImplementedError + + def __setitem__(self,key,val): + raise NotImplementedError + + ###################### + # Arithmetic methods # + ###################### + + def _add_dense(self, other): + return self.tocoo(copy=False)._add_dense(other) + + def _matmul_vector(self, other): + M,N = self.shape + R,C = self.blocksize + + result = np.zeros(self.shape[0], dtype=upcast(self.dtype, other.dtype)) + + bsr_matvec(M//R, N//C, R, C, + self.indptr, self.indices, self.data.ravel(), + other, result) + + return result + + def _matmul_multivector(self,other): + R,C = self.blocksize + M,N = self.shape + n_vecs = other.shape[1] # number of column vectors + + result = np.zeros((M,n_vecs), dtype=upcast(self.dtype,other.dtype)) + + bsr_matvecs(M//R, N//C, n_vecs, R, C, + self.indptr, self.indices, self.data.ravel(), + other.ravel(), result.ravel()) + + return result + + def _matmul_sparse(self, other): + M, K1 = self.shape + K2, N = other.shape + + R,n = self.blocksize + + # convert to this format + if other.format == "bsr": + C = other.blocksize[1] + else: + C = 1 + + if other.format == "csr" and n == 1: + other = other.tobsr(blocksize=(n,C), copy=False) # lightweight conversion + else: + other = other.tobsr(blocksize=(n,C)) + + idx_dtype = self._get_index_dtype((self.indptr, self.indices, + other.indptr, other.indices)) + + n_brow = M // R + n_bcol = N // C + bnnz = csr_matmat_maxnnz(n_brow, n_bcol, + self.indptr.astype(idx_dtype, copy=False), + self.indices.astype(idx_dtype, copy=False), + other.indptr.astype(idx_dtype, copy=False), + other.indices.astype(idx_dtype, copy=False)) + + idx_dtype = self._get_index_dtype((self.indptr, self.indices, + other.indptr, other.indices), + maxval=bnnz) + indptr = np.empty(self.indptr.shape, dtype=idx_dtype) + indices = np.empty(bnnz, dtype=idx_dtype) + data = np.empty(R*C*bnnz, dtype=upcast(self.dtype,other.dtype)) + + bsr_matmat(bnnz, n_brow, n_bcol, R, C, n, + self.indptr.astype(idx_dtype, copy=False), + self.indices.astype(idx_dtype, copy=False), + np.ravel(self.data), + other.indptr.astype(idx_dtype, copy=False), + other.indices.astype(idx_dtype, copy=False), + np.ravel(other.data), + indptr, + indices, + data) + + data = data.reshape(-1,R,C) + + # TODO eliminate zeros + + return self._bsr_container( + (data, indices, indptr), shape=(M, N), blocksize=(R, C) + ) + + ###################### + # Conversion methods # + ###################### + + def tobsr(self, blocksize=None, copy=False): + if blocksize not in [None, self.blocksize]: + return self.tocsr().tobsr(blocksize=blocksize) + if copy: + return self.copy() + else: + return self + + tobsr.__doc__ = _spbase.tobsr.__doc__ + + def tocsr(self, copy=False): + M, N = self.shape + R, C = self.blocksize + nnz = self.nnz + idx_dtype = self._get_index_dtype((self.indptr, self.indices), + maxval=max(nnz, N)) + indptr = np.empty(M + 1, dtype=idx_dtype) + indices = np.empty(nnz, dtype=idx_dtype) + data = np.empty(nnz, dtype=upcast(self.dtype)) + + bsr_tocsr(M // R, # n_brow + N // C, # n_bcol + R, C, + self.indptr.astype(idx_dtype, copy=False), + self.indices.astype(idx_dtype, copy=False), + self.data, + indptr, + indices, + data) + return self._csr_container((data, indices, indptr), shape=self.shape) + + tocsr.__doc__ = _spbase.tocsr.__doc__ + + def tocsc(self, copy=False): + return self.tocsr(copy=False).tocsc(copy=copy) + + tocsc.__doc__ = _spbase.tocsc.__doc__ + + def tocoo(self, copy=True): + M,N = self.shape + R,C = self.blocksize + + indptr_diff = np.diff(self.indptr) + if indptr_diff.dtype.itemsize > np.dtype(np.intp).itemsize: + # Check for potential overflow + indptr_diff_limited = indptr_diff.astype(np.intp) + if np.any(indptr_diff_limited != indptr_diff): + raise ValueError("Matrix too big to convert") + indptr_diff = indptr_diff_limited + + idx_dtype = self._get_index_dtype(maxval=max(M, N)) + row = (R * np.arange(M//R, dtype=idx_dtype)).repeat(indptr_diff) + row = row.repeat(R*C).reshape(-1,R,C) + row += np.tile(np.arange(R, dtype=idx_dtype).reshape(-1,1), (1,C)) + row = row.reshape(-1) + + col = ((C * self.indices).astype(idx_dtype, copy=False) + .repeat(R*C).reshape(-1,R,C)) + col += np.tile(np.arange(C, dtype=idx_dtype), (R,1)) + col = col.reshape(-1) + + data = self.data.reshape(-1) + + if copy: + data = data.copy() + + return self._coo_container( + (data, (row, col)), shape=self.shape + ) + + tocoo.__doc__ = _spbase.tocoo.__doc__ + + def toarray(self, order=None, out=None): + return self.tocoo(copy=False).toarray(order=order, out=out) + + toarray.__doc__ = _spbase.toarray.__doc__ + + def transpose(self, axes=None, copy=False): + if axes is not None and axes != (1, 0): + raise ValueError("Sparse matrices do not support " + "an 'axes' parameter because swapping " + "dimensions is the only logical permutation.") + + R, C = self.blocksize + M, N = self.shape + NBLK = self.nnz//(R*C) + + if self.nnz == 0: + return self._bsr_container((N, M), blocksize=(C, R), + dtype=self.dtype, copy=copy) + + indptr = np.empty(N//C + 1, dtype=self.indptr.dtype) + indices = np.empty(NBLK, dtype=self.indices.dtype) + data = np.empty((NBLK, C, R), dtype=self.data.dtype) + + bsr_transpose(M//R, N//C, R, C, + self.indptr, self.indices, self.data.ravel(), + indptr, indices, data.ravel()) + + return self._bsr_container((data, indices, indptr), + shape=(N, M), copy=copy) + + transpose.__doc__ = _spbase.transpose.__doc__ + + ############################################################## + # methods that examine or modify the internal data structure # + ############################################################## + + def eliminate_zeros(self): + """Remove zero elements in-place.""" + + if not self.nnz: + return # nothing to do + + R,C = self.blocksize + M,N = self.shape + + mask = (self.data != 0).reshape(-1,R*C).sum(axis=1) # nonzero blocks + + nonzero_blocks = mask.nonzero()[0] + + self.data[:len(nonzero_blocks)] = self.data[nonzero_blocks] + + # modifies self.indptr and self.indices *in place* + _sparsetools.csr_eliminate_zeros(M//R, N//C, self.indptr, + self.indices, mask) + self.prune() + + def sum_duplicates(self): + """Eliminate duplicate array/matrix entries by adding them together. + + The is an *in place* operation + """ + if self.has_canonical_format: + return + self.sort_indices() + R, C = self.blocksize + M, N = self.shape + + # port of _sparsetools.csr_sum_duplicates + n_row = M // R + nnz = 0 + row_end = 0 + for i in range(n_row): + jj = row_end + row_end = self.indptr[i+1] + while jj < row_end: + j = self.indices[jj] + x = self.data[jj] + jj += 1 + while jj < row_end and self.indices[jj] == j: + x += self.data[jj] + jj += 1 + self.indices[nnz] = j + self.data[nnz] = x + nnz += 1 + self.indptr[i+1] = nnz + + self.prune() # nnz may have changed + self.has_canonical_format = True + + def sort_indices(self): + """Sort the indices of this array/matrix *in place*. + """ + if self.has_sorted_indices: + return + + R,C = self.blocksize + M,N = self.shape + + bsr_sort_indices(M//R, N//C, R, C, self.indptr, self.indices, self.data.ravel()) + + self.has_sorted_indices = True + + def prune(self): + """Remove empty space after all non-zero elements. + """ + + R,C = self.blocksize + M,N = self.shape + + if len(self.indptr) != M//R + 1: + raise ValueError("index pointer has invalid length") + + bnnz = self.indptr[-1] + + if len(self.indices) < bnnz: + raise ValueError("indices array has too few elements") + if len(self.data) < bnnz: + raise ValueError("data array has too few elements") + + self.data = self.data[:bnnz] + self.indices = self.indices[:bnnz] + + # utility functions + def _binopt(self, other, op, in_shape=None, out_shape=None): + """Apply the binary operation fn to two sparse matrices.""" + + # Ideally we'd take the GCDs of the blocksize dimensions + # and explode self and other to match. + other = self.__class__(other, blocksize=self.blocksize) + + # e.g. bsr_plus_bsr, etc. + fn = getattr(_sparsetools, self.format + op + self.format) + + R,C = self.blocksize + + max_bnnz = len(self.data) + len(other.data) + idx_dtype = self._get_index_dtype((self.indptr, self.indices, + other.indptr, other.indices), + maxval=max_bnnz) + indptr = np.empty(self.indptr.shape, dtype=idx_dtype) + indices = np.empty(max_bnnz, dtype=idx_dtype) + + bool_ops = ['_ne_', '_lt_', '_gt_', '_le_', '_ge_'] + if op in bool_ops: + data = np.empty(R*C*max_bnnz, dtype=np.bool_) + else: + data = np.empty(R*C*max_bnnz, dtype=upcast(self.dtype,other.dtype)) + + fn(self.shape[0]//R, self.shape[1]//C, R, C, + self.indptr.astype(idx_dtype, copy=False), + self.indices.astype(idx_dtype, copy=False), + self.data, + other.indptr.astype(idx_dtype, copy=False), + other.indices.astype(idx_dtype, copy=False), + np.ravel(other.data), + indptr, + indices, + data) + + actual_bnnz = indptr[-1] + indices = indices[:actual_bnnz] + data = data[:R*C*actual_bnnz] + + if actual_bnnz < max_bnnz/2: + indices = indices.copy() + data = data.copy() + + data = data.reshape(-1,R,C) + + return self.__class__((data, indices, indptr), shape=self.shape) + + # needed by _data_matrix + def _with_data(self,data,copy=True): + """Returns a matrix with the same sparsity structure as self, + but with different data. By default the structure arrays + (i.e. .indptr and .indices) are copied. + """ + if copy: + return self.__class__((data,self.indices.copy(),self.indptr.copy()), + shape=self.shape,dtype=data.dtype) + else: + return self.__class__((data,self.indices,self.indptr), + shape=self.shape,dtype=data.dtype) + +# # these functions are used by the parent class +# # to remove redundancy between bsc_matrix and bsr_matrix +# def _swap(self,x): +# """swap the members of x if this is a column-oriented matrix +# """ +# return (x[0],x[1]) + + def _broadcast_to(self, shape, copy=False): + return _spbase._broadcast_to(self, shape, copy) + + +def isspmatrix_bsr(x): + """Is `x` of a bsr_matrix type? + + .. warning:: + + SciPy sparse is shifting from a sparse matrix interface to a sparse + array interface. In the next few releases we expect to deprecate the + sparse matrix interface. For documentation of the matrix + interface, see the :ref:`spmatrix interface docs `. + For guidance on converting existing code to sparse arrays, see + :ref:`Migration from spmatrix to sparray `. + + Parameters + ---------- + x + object to check for being a bsr matrix + + Returns + ------- + bool + True if `x` is a bsr matrix, False otherwise + + Examples + -------- + >>> from scipy.sparse import bsr_array, bsr_matrix, csr_matrix, isspmatrix_bsr + >>> isspmatrix_bsr(bsr_matrix([[5]])) + True + >>> isspmatrix_bsr(bsr_array([[5]])) + False + >>> isspmatrix_bsr(csr_matrix([[5]])) + False + """ + return isinstance(x, bsr_matrix) + + +# This namespace class separates array from matrix with isinstance +class bsr_array(_bsr_base, sparray): + """ + Block Sparse Row format sparse array. + + This can be instantiated in several ways: + bsr_array(D, [blocksize=(R,C)]) + where D is a 2-D ndarray. + + bsr_array(S, [blocksize=(R,C)]) + with another sparse array or matrix S (equivalent to S.tobsr()) + + bsr_array((M, N), [blocksize=(R,C), dtype]) + to construct an empty sparse array with shape (M, N) + dtype is optional, defaulting to dtype='d'. + + bsr_array((data, ij), [blocksize=(R,C), shape=(M, N)]) + where ``data`` and ``ij`` satisfy ``a[ij[0, k], ij[1, k]] = data[k]`` + + bsr_array((data, indices, indptr), [shape=(M, N)]) + is the standard BSR representation where the block column + indices for row i are stored in ``indices[indptr[i]:indptr[i+1]]`` + and their corresponding block values are stored in + ``data[ indptr[i]: indptr[i+1] ]``. If the shape parameter is not + supplied, the array dimensions are inferred from the index arrays. + + Attributes + ---------- + data : ndarray + BSR format data array of the array + indices : ndarray + BSR format index array of the array + indptr : ndarray + BSR format index pointer array of the array + blocksize : 2-tuple of integers + Block size (R, C) + has_sorted_indices : bool + Whether indices are sorted + has_canonical_format : bool + Whether indices are sorted and no duplicate entries exist + dtype : dtype + Data type of the array + shape : 2-tuple + Shape of the array + ndim : int + Number of dimensions (this is always 2) + format : str + Three letter code for the format of the array storage, e.g. 'bsr' + nnz : int + Number of values stored in the array + size : int + Number of values stored in the array + T : bsr_array + The transpose of the array + mT : bsr_array + The matrix transpose of the array + + Notes + ----- + Sparse arrays can be used in arithmetic operations: they support + addition, subtraction, multiplication, division, and matrix power. + + **Summary of BSR format** + + The Block Sparse Row (BSR) format is very similar to the Compressed + Sparse Row (CSR) format. BSR is appropriate for sparse matrices with dense + sub matrices like the last example below. Such sparse block matrices often + arise in vector-valued finite element discretizations. In such cases, BSR is + considerably more efficient than CSR and CSC for many sparse arithmetic + operations. + + **Blocksize** + + The blocksize (R,C) must evenly divide the shape of the sparse array (M,N). + That is, R and C must satisfy the relationship ``M % R = 0`` and + ``N % C = 0``. + + If no blocksize is specified, a simple heuristic is applied to determine + an appropriate blocksize. + + **Canonical Format** + + In canonical format, there are no duplicate blocks and indices are sorted + per row. + + **Limitations** + + Block Sparse Row format sparse arrays do not support slicing. + + Examples + -------- + >>> import numpy as np + >>> from scipy.sparse import bsr_array + >>> bsr_array((3, 4), dtype=np.int8).toarray() + array([[0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0]], dtype=int8) + + >>> row = np.array([0, 0, 1, 2, 2, 2]) + >>> col = np.array([0, 2, 2, 0, 1, 2]) + >>> data = np.array([1, 2, 3 ,4, 5, 6]) + >>> bsr_array((data, (row, col)), shape=(3, 3)).toarray() + array([[1, 0, 2], + [0, 0, 3], + [4, 5, 6]]) + + >>> indptr = np.array([0, 2, 3, 6]) + >>> indices = np.array([0, 2, 2, 0, 1, 2]) + >>> data = np.array([1, 2, 3, 4, 5, 6]).repeat(4).reshape(6, 2, 2) + >>> bsr_array((data,indices,indptr), shape=(6, 6)).toarray() + array([[1, 1, 0, 0, 2, 2], + [1, 1, 0, 0, 2, 2], + [0, 0, 0, 0, 3, 3], + [0, 0, 0, 0, 3, 3], + [4, 4, 5, 5, 6, 6], + [4, 4, 5, 5, 6, 6]]) + + """ # numpydoc ignore=PR01 + + +class bsr_matrix(spmatrix, _bsr_base): + """ + Block Sparse Row format sparse matrix. + + .. warning:: + + SciPy sparse is shifting from a sparse matrix interface to a sparse + array interface. In the next few releases we expect to deprecate the + sparse matrix interface. For documentation of the matrix + interface, see the :ref:`spmatrix interface docs `. + For guidance on converting existing code to sparse arrays, see + :ref:`Migration from spmatrix to sparray `. + + This can be instantiated in several ways: + bsr_matrix(D, [blocksize=(R,C)]) + where D is a 2-D ndarray. + + bsr_matrix(S, [blocksize=(R,C)]) + with another sparse array or matrix S (equivalent to S.tobsr()) + + bsr_matrix((M, N), [blocksize=(R,C), dtype]) + to construct an empty sparse matrix with shape (M, N) + dtype is optional, defaulting to dtype='d'. + + bsr_matrix((data, ij), [blocksize=(R,C), shape=(M, N)]) + where ``data`` and ``ij`` satisfy ``a[ij[0, k], ij[1, k]] = data[k]`` + + bsr_matrix((data, indices, indptr), [shape=(M, N)]) + is the standard BSR representation where the block column + indices for row i are stored in ``indices[indptr[i]:indptr[i+1]]`` + and their corresponding block values are stored in + ``data[ indptr[i]: indptr[i+1] ]``. If the shape parameter is not + supplied, the matrix dimensions are inferred from the index arrays. + + Attributes + ---------- + data : ndarray + BSR format data array of the matrix + indices : ndarray + BSR format index array of the matrix + indptr : ndarray + BSR format index pointer array of the matrix + blocksize : 2-tuple of integers + Block size (R, C) + has_sorted_indices : bool + Whether indices are sorted + has_canonical_format : bool + Whether indices are sorted and no duplicate entries exist + dtype : dtype + Data type of the matrix + shape : 2-tuple + Shape of the matrix + ndim : int + Number of dimensions (this is always 2) + format : str + Three letter code for the format of the matrix storage, e.g. 'bsr' + nnz : int + Number of values stored in the matrix + size : int + Number of values stored in the matrix + T : bsr_matrix + The transpose of the matrix + mT : bsr_matrix + The matrix transpose + + Notes + ----- + Sparse matrices can be used in arithmetic operations: they support + addition, subtraction, multiplication, division, and matrix power. + + **Summary of BSR format** + + The Block Sparse Row (BSR) format is very similar to the Compressed + Sparse Row (CSR) format. BSR is appropriate for sparse matrices with dense + sub matrices like the last example below. Such sparse block matrices often + arise in vector-valued finite element discretizations. In such cases, BSR is + considerably more efficient than CSR and CSC for many sparse arithmetic + operations. + + **Blocksize** + + The blocksize (R,C) must evenly divide the shape of the sparse matrix (M,N). + That is, R and C must satisfy the relationship ``M % R = 0`` and + ``N % C = 0``. + + If no blocksize is specified, a simple heuristic is applied to determine + an appropriate blocksize. + + **Canonical Format** + + In canonical format, there are no duplicate blocks and indices are sorted + per row. + + **Limitations** + + Block Sparse Row format sparse matrices do not support slicing. + + Examples + -------- + >>> import numpy as np + >>> from scipy.sparse import bsr_matrix + >>> bsr_matrix((3, 4), dtype=np.int8).toarray() + array([[0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0]], dtype=int8) + + >>> row = np.array([0, 0, 1, 2, 2, 2]) + >>> col = np.array([0, 2, 2, 0, 1, 2]) + >>> data = np.array([1, 2, 3 ,4, 5, 6]) + >>> bsr_matrix((data, (row, col)), shape=(3, 3)).toarray() + array([[1, 0, 2], + [0, 0, 3], + [4, 5, 6]]) + + >>> indptr = np.array([0, 2, 3, 6]) + >>> indices = np.array([0, 2, 2, 0, 1, 2]) + >>> data = np.array([1, 2, 3, 4, 5, 6]).repeat(4).reshape(6, 2, 2) + >>> bsr_matrix((data,indices,indptr), shape=(6, 6)).toarray() + array([[1, 1, 0, 0, 2, 2], + [1, 1, 0, 0, 2, 2], + [0, 0, 0, 0, 3, 3], + [0, 0, 0, 0, 3, 3], + [4, 4, 5, 5, 6, 6], + [4, 4, 5, 5, 6, 6]]) + + """ + diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/_compressed.py b/venv/lib/python3.12/site-packages/scipy/sparse/_compressed.py new file mode 100644 index 0000000000000000000000000000000000000000..99c1d459f8b08efe840912073d1c919eccb6130f --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/_compressed.py @@ -0,0 +1,1351 @@ +"""Base class for sparse matrix formats using compressed storage.""" +__all__ = [] + +from warnings import warn +import itertools + +import numpy as np +from scipy._lib._util import _prune_array, copy_if_needed + +from ._base import _spbase, issparse, sparray, SparseEfficiencyWarning +from ._data import _data_matrix, _minmax_mixin +from . import _sparsetools +from ._sparsetools import (get_csr_submatrix, csr_sample_offsets, csr_todense, + csr_sample_values, csr_row_index, csr_row_slice, + csr_column_index1, csr_column_index2, csr_diagonal, + expandptr, csr_has_canonical_format, csr_eliminate_zeros, + csr_sum_duplicates, csr_has_sorted_indices, csr_sort_indices, + csr_matmat_maxnnz, csr_matmat) +from ._index import IndexMixin +from ._sputils import (upcast, upcast_char, to_native, isshape, + getdtype, isintlike, downcast_intp_index, + get_sum_dtype, check_shape, get_index_dtype, broadcast_shapes) + + +class _cs_matrix(_data_matrix, _minmax_mixin, IndexMixin): + """ + base array/matrix class for compressed row- and column-oriented arrays/matrices + """ + + def __init__(self, arg1, shape=None, dtype=None, copy=False, *, maxprint=None): + _data_matrix.__init__(self, arg1, maxprint=maxprint) + + if issparse(arg1): + if arg1.format == self.format and copy: + arg1 = arg1.copy() + else: + arg1 = arg1.asformat(self.format) + self.indptr, self.indices, self.data, self._shape = ( + arg1.indptr, arg1.indices, arg1.data, arg1._shape + ) + + elif isinstance(arg1, tuple): + if isshape(arg1, allow_nd=self._allow_nd): + # It's a tuple of matrix dimensions (M, N) + # create empty matrix + self._shape = check_shape(arg1, allow_nd=self._allow_nd) + M, N = self._swap(self._shape_as_2d) + # Select index dtype large enough to pass array and + # scalar parameters to sparsetools + idx_dtype = self._get_index_dtype(maxval=max(self.shape)) + self.data = np.zeros(0, getdtype(dtype, default=float)) + self.indices = np.zeros(0, idx_dtype) + self.indptr = np.zeros(M + 1, dtype=idx_dtype) + else: + if len(arg1) == 2: + # (data, ij) format + coo = self._coo_container(arg1, shape=shape, dtype=dtype) + arrays = coo._coo_to_compressed(self._swap) + self.indptr, self.indices, self.data, self._shape = arrays + self.sum_duplicates() + elif len(arg1) == 3: + # (data, indices, indptr) format + (data, indices, indptr) = arg1 + + # Select index dtype large enough to pass array and + # scalar parameters to sparsetools + maxval = None + if shape is not None and 0 not in shape: + maxval = max(shape) + idx_dtype = self._get_index_dtype((indices, indptr), + maxval=maxval, + check_contents=True) + + if not copy: + copy = copy_if_needed + self.indices = np.array(indices, copy=copy, dtype=idx_dtype) + self.indptr = np.array(indptr, copy=copy, dtype=idx_dtype) + self.data = np.array(data, copy=copy, dtype=dtype) + else: + raise ValueError(f"unrecognized {self.__class__.__name__} " + f"constructor input: {arg1}") + + else: + # must be dense + try: + arg1 = np.asarray(arg1) + except Exception as e: + raise ValueError(f"unrecognized {self.__class__.__name__} " + f"constructor input: {arg1}") from e + if isinstance(self, sparray) and arg1.ndim != 2 and self.format == "csc": + raise ValueError(f"CSC arrays don't support {arg1.ndim}D input. Use 2D") + if arg1.ndim > 2: + raise ValueError(f"CSR arrays don't yet support {arg1.ndim}D.") + + coo = self._coo_container(arg1, dtype=dtype) + arrays = coo._coo_to_compressed(self._swap) + self.indptr, self.indices, self.data, self._shape = arrays + + # Read matrix dimensions given, if any + if shape is not None: + self._shape = check_shape(shape, allow_nd=self._allow_nd) + elif self.shape is None: + # shape not already set, try to infer dimensions + try: + M = len(self.indptr) - 1 + N = self.indices.max() + 1 + except Exception as e: + raise ValueError('unable to infer matrix dimensions') from e + + self._shape = check_shape(self._swap((M, N)), allow_nd=self._allow_nd) + + if dtype is not None: + newdtype = getdtype(dtype) + self.data = self.data.astype(newdtype, copy=False) + + self.check_format(full_check=False) + + def _getnnz(self, axis=None): + if axis is None: + return int(self.indptr[-1]) + elif self.ndim == 1: + if axis in (0, -1): + return int(self.indptr[-1]) + raise ValueError('axis out of bounds') + else: + if axis < 0: + axis += 2 + axis, _ = self._swap((axis, 1 - axis)) + _, N = self._swap(self.shape) + if axis == 0: + return np.bincount(downcast_intp_index(self.indices), minlength=N) + elif axis == 1: + return np.diff(self.indptr) + raise ValueError('axis out of bounds') + + _getnnz.__doc__ = _spbase._getnnz.__doc__ + + def count_nonzero(self, axis=None): + self.sum_duplicates() + if axis is None: + return np.count_nonzero(self.data) + + if self.ndim == 1: + if axis not in (0, -1): + raise ValueError('axis out of bounds') + return np.count_nonzero(self.data) + + if axis < 0: + axis += 2 + axis, _ = self._swap((axis, 1 - axis)) + if axis == 0: + _, N = self._swap(self.shape) + mask = self.data != 0 + idx = self.indices if mask.all() else self.indices[mask] + return np.bincount(downcast_intp_index(idx), minlength=N) + elif axis == 1: + if self.data.all(): + return np.diff(self.indptr) + pairs = itertools.pairwise(self.indptr) + return np.array([np.count_nonzero(self.data[i:j]) for i, j in pairs]) + else: + raise ValueError('axis out of bounds') + + count_nonzero.__doc__ = _spbase.count_nonzero.__doc__ + + def check_format(self, full_check=True): + """Check whether the array/matrix respects the CSR or CSC format. + + Parameters + ---------- + full_check : bool, optional + If `True`, run rigorous check, scanning arrays for valid values. + Note that activating those check might copy arrays for casting, + modifying indices and index pointers' inplace. + If `False`, run basic checks on attributes. O(1) operations. + Default is `True`. + """ + # index arrays should have integer data types + if self.indptr.dtype.kind != 'i': + warn(f"indptr array has non-integer dtype ({self.indptr.dtype.name})", + stacklevel=3) + if self.indices.dtype.kind != 'i': + warn(f"indices array has non-integer dtype ({self.indices.dtype.name})", + stacklevel=3) + + # check array shapes + for x in [self.data.ndim, self.indices.ndim, self.indptr.ndim]: + if x != 1: + raise ValueError('data, indices, and indptr should be 1-D') + + # check index pointer. Use _swap to determine proper bounds + M, N = self._swap(self._shape_as_2d) + + if (len(self.indptr) != M + 1): + raise ValueError(f"index pointer size {len(self.indptr)} should be {M + 1}") + if (self.indptr[0] != 0): + raise ValueError("index pointer should start with 0") + + # check index and data arrays + if (len(self.indices) != len(self.data)): + raise ValueError("indices and data should have the same size") + if (self.indptr[-1] > len(self.indices)): + raise ValueError("Last value of index pointer should be less than " + "the size of index and data arrays") + + self.prune() + + if full_check: + # check format validity (more expensive) + if self.nnz > 0: + if self.indices.max() >= N: + raise ValueError(f"indices must be < {N}") + if self.indices.min() < 0: + raise ValueError("indices must be >= 0") + if np.diff(self.indptr).min() < 0: + raise ValueError("indptr must be a non-decreasing sequence") + + idx_dtype = self._get_index_dtype((self.indptr, self.indices)) + self.indptr = np.asarray(self.indptr, dtype=idx_dtype) + self.indices = np.asarray(self.indices, dtype=idx_dtype) + self.data = to_native(self.data) + + # if not self.has_sorted_indices(): + # warn('Indices were not in sorted order. Sorting indices.') + # self.sort_indices() + # assert(self.has_sorted_indices()) + # TODO check for duplicates? + + ####################### + # Boolean comparisons # + ####################### + + def _scalar_binopt(self, other, op): + """Scalar version of self._binopt, for cases in which no new nonzeros + are added. Produces a new sparse array in canonical form. + """ + self.sum_duplicates() + res = self._with_data(op(self.data, other), copy=True) + res.eliminate_zeros() + return res + + ################################# + # Arithmetic operator overrides # + ################################# + + def _add_dense(self, other): + if other.shape != self.shape: + raise ValueError(f'Incompatible shapes ({self.shape} and {other.shape})') + dtype = upcast_char(self.dtype.char, other.dtype.char) + order = self._swap('CF')[0] + result = np.array(other, dtype=dtype, order=order, copy=True) + y = result if result.flags.c_contiguous else result.T + M, N = self._swap(self._shape_as_2d) + csr_todense(M, N, self.indptr, self.indices, self.data, y) + return self._container(result, copy=False) + + def _add_sparse(self, other): + return self._binopt(other, '_plus_') + + def _sub_sparse(self, other): + return self._binopt(other, '_minus_') + + def _multiply_2d_with_broadcasting(self, other): + """Element-wise multiplication by array/matrix, vector, or scalar.""" + # Called after checking that other is not scalarlike and self.ndim <=2 + if issparse(other): + if self.shape == other.shape: + other = self.__class__(other) + return self._binopt(other, '_elmul_') + # Single element. + if other.shape == (1, 1): + result = self._mul_scalar(other.toarray()[0, 0]) + if self.ndim == 1: + return result.reshape((1, self.shape[0])) + return result + if other.shape == (1,): + return self._mul_scalar(other.toarray()[0]) + if self.shape in ((1,), (1, 1)): + return other._mul_scalar(self.data.sum()) + + # broadcast. treat 1d like a row + sM, sN = self._shape_as_2d + oM, oN = other._shape_as_2d + # A row times a column. + if sM == 1 and oN == 1: + return other._matmul_sparse(self.reshape(sM, sN).tocsc()) + if sN == 1 and oM == 1: + return self._matmul_sparse(other.reshape(oM, oN).tocsc()) + + is_array = isinstance(self, sparray) + # Other is a row. + if oM == 1 and sN == oN: + new_other = _make_diagonal_csr(other.toarray().ravel(), is_array) + result = self._matmul_sparse(new_other) + return result if self.ndim == 2 else result.reshape((1, oN)) + # self is a row. + if sM == 1 and sN == oN: + copy = _make_diagonal_csr(self.toarray().ravel(), is_array) + return other._matmul_sparse(copy) + + # Other is a column. + if oN == 1 and sM == oM: + new_other = _make_diagonal_csr(other.toarray().ravel(), is_array) + return new_other._matmul_sparse(self) + # self is a column. + if sN == 1 and sM == oM: + new_self = _make_diagonal_csr(self.toarray().ravel(), is_array) + return new_self._matmul_sparse(other) + raise ValueError(f"inconsistent shapes {self.shape} and {other.shape}") + + # Assume other is a dense matrix/array, which produces a single-item + # object array if other isn't convertible to ndarray. + other = np.asanyarray(other) + + if other.ndim > 2: + return np.multiply(self.toarray(), other) + # Single element / wrapped object. + if other.size == 1: + if other.dtype == np.object_: + # 'other' not convertible to ndarray. + return NotImplemented + bshape = broadcast_shapes(self.shape, other.shape) + return self._mul_scalar(other.flat[0]).reshape(bshape) + # Fast case for trivial sparse matrix. + if self.shape in ((1,), (1, 1)): + bshape = broadcast_shapes(self.shape, other.shape) + return np.multiply(self.data.sum(), other).reshape(bshape) + + ret = self.tocoo() + # Matching shapes. + if self.shape == other.shape: + data = np.multiply(ret.data, other[ret.coords]) + ret.data = data.view(np.ndarray).ravel() + return ret + + # convert other to 2d + other2d = np.atleast_2d(other) + # Sparse row vector times... + if self.shape[0] == 1 or self.ndim == 1: + if other2d.shape[1] == 1: # Dense column vector. + data = np.multiply(ret.data, other2d) + elif other2d.shape[1] == self.shape[-1]: # Dense 2d matrix. + data = np.multiply(ret.data, other2d[:, ret.col]) + else: + raise ValueError(f"inconsistent shapes {self.shape} and {other.shape}") + idx_dtype = self._get_index_dtype(ret.col, + maxval=ret.nnz * other2d.shape[0]) + row = np.repeat(np.arange(other2d.shape[0], dtype=idx_dtype), ret.nnz) + col = np.tile(ret.col.astype(idx_dtype, copy=False), other2d.shape[0]) + return self._coo_container( + (data.view(np.ndarray).ravel(), (row, col)), + shape=(other2d.shape[0], self.shape[-1]), + copy=False + ) + # Sparse column vector times... + if self.shape[1] == 1: + if other2d.shape[0] == 1: # Dense row vector. + data = np.multiply(ret.data[:, None], other2d) + elif other2d.shape[0] == self.shape[0]: # Dense 2d array. + data = np.multiply(ret.data[:, None], other2d[ret.row]) + else: + raise ValueError(f"inconsistent shapes {self.shape} and {other.shape}") + idx_dtype = self._get_index_dtype(ret.row, + maxval=ret.nnz * other2d.shape[1]) + row = np.repeat(ret.row.astype(idx_dtype, copy=False), other2d.shape[1]) + col = np.tile(np.arange(other2d.shape[1], dtype=idx_dtype), ret.nnz) + return self._coo_container( + (data.view(np.ndarray).ravel(), (row, col)), + shape=(self.shape[0], other2d.shape[1]), + copy=False + ) + # Sparse matrix times dense row vector. + if other2d.shape[0] == 1 and self.shape[1] == other2d.shape[1]: + data = np.multiply(ret.data, other2d[:, ret.col].ravel()) + # Sparse matrix times dense column vector. + elif other2d.shape[1] == 1 and self.shape[0] == other2d.shape[0]: + data = np.multiply(ret.data, other2d[ret.row].ravel()) + else: + raise ValueError(f"inconsistent shapes {self.shape} and {other.shape}") + ret.data = data.view(np.ndarray).ravel() + return ret + + ########################### + # Multiplication handlers # + ########################### + + def _matmul_vector(self, other): + M, N = self._shape_as_2d + + # output array + result = np.zeros(M, dtype=upcast_char(self.dtype.char, other.dtype.char)) + + # csr_matvec or csc_matvec + fn = getattr(_sparsetools, self.format + '_matvec') + fn(M, N, self.indptr, self.indices, self.data, other, result) + + return result[0] if self.ndim == 1 else result + + def _matmul_multivector(self, other): + M, N = self._shape_as_2d + n_vecs = other.shape[-1] # number of column vectors + + result = np.zeros((M, n_vecs), + dtype=upcast_char(self.dtype.char, other.dtype.char)) + + # csr_matvecs or csc_matvecs + fn = getattr(_sparsetools, self.format + '_matvecs') + fn(M, N, n_vecs, self.indptr, self.indices, self.data, + other.ravel(), result.ravel()) + + if self.ndim == 1: + return result.reshape((n_vecs,)) + return result + + def _matmul_sparse(self, other): + M, K1 = self._shape_as_2d + # if other is 1d, treat as a **column** + o_ndim = other.ndim + if o_ndim == 1: + # convert 1d array to a 2d column when on the right of @ + other = other.reshape((1, other.shape[0])).T # Note: converts to CSC + K2, N = other._shape if other.ndim == 2 else (other.shape[0], 1) + + # find new_shape: (M, N), (M,), (N,) or () + new_shape = () + if self.ndim == 2: + new_shape += (M,) + if o_ndim == 2: + new_shape += (N,) + faux_shape = (M if self.ndim == 2 else 1, N if o_ndim == 2 else 1) + + other = self.__class__(other) # convert to this format + index_arrays = (self.indptr, self.indices, other.indptr, other.indices) + + M, N = self._swap((M, N)) + s, o = self._swap((self, other)) + + idx_dtype = self._get_index_dtype(index_arrays) + s_indptr = np.asarray(s.indptr, dtype=idx_dtype) + s_indices = np.asarray(s.indices, dtype=idx_dtype) + o_indptr = np.asarray(o.indptr, dtype=idx_dtype) + o_indices = np.asarray(o.indices, dtype=idx_dtype) + + nnz = csr_matmat_maxnnz(M, N, s_indptr, s_indices, o_indptr, o_indices) + if nnz == 0: + if new_shape == (): + return np.array(0, dtype=upcast(self.dtype, other.dtype)) + return self.__class__(new_shape, dtype=upcast(self.dtype, other.dtype)) + + new_idx_dtype = self._get_index_dtype(index_arrays, maxval=nnz) + if new_idx_dtype != idx_dtype: + idx_dtype = new_idx_dtype + s_indptr = np.asarray(s.indptr, dtype=idx_dtype) + s_indices = np.asarray(s.indices, dtype=idx_dtype) + o_indptr = np.asarray(o.indptr, dtype=idx_dtype) + o_indices = np.asarray(o.indices, dtype=idx_dtype) + + indptr = np.empty(M + 1, dtype=idx_dtype) + indices = np.empty(nnz, dtype=idx_dtype) + data = np.empty(nnz, dtype=upcast(self.dtype, other.dtype)) + + csr_matmat(M, N, + s_indptr, s_indices, s.data, + o_indptr, o_indices, o.data, + indptr, indices, data) + + if new_shape == (): + return np.array(data[0]) + res = self.__class__((data, indices, indptr), shape=faux_shape) + if faux_shape != new_shape: + if res.format != 'csr': + res = res.tocsr() + res = res.reshape(new_shape) + return res + + def diagonal(self, k=0): + M, N = self._swap(self.shape) + k, _ = self._swap((k, -k)) + + if k <= -M or k >= N: + return np.empty(0, dtype=self.data.dtype) + y = np.empty(min(M + min(k, 0), N - max(k, 0)), dtype=upcast(self.dtype)) + csr_diagonal(k, M, N, self.indptr, self.indices, self.data, y) + return y + + diagonal.__doc__ = _spbase.diagonal.__doc__ + + ##################### + # Reduce operations # + ##################### + + def sum(self, axis=None, dtype=None, out=None): + """Sum the array/matrix over the given axis. If the axis is None, sum + over both rows and columns, returning a scalar. + """ + # The _spbase base class already does axis=None and major axis efficiently + # so we only do the case axis= minor axis + if (self.ndim == 2 and not hasattr(self, 'blocksize') and + axis in self._swap(((1, -1), (0, -2)))[0]): + # faster than multiplication for large minor axis in CSC/CSR + + res_dtype = get_sum_dtype(self.dtype) if dtype is None else dtype + self_to_reduce = self.astype(res_dtype, copy=False) + + # Fast path: reduce along minor axis + ret = np.zeros(len(self_to_reduce.indptr) - 1, dtype=res_dtype) + major_index, value = self_to_reduce._minor_reduce(np.add) + ret[major_index] = value + ret = self_to_reduce._ascontainer(ret) + if axis % 2 == 1: + ret = ret.T + + return ret.sum(axis=(), dtype=res_dtype, out=out) + else: + return _spbase.sum(self, axis=axis, dtype=dtype, out=out) + + sum.__doc__ = _spbase.sum.__doc__ + + def _minor_reduce(self, ufunc, data=None): + """Reduce nonzeros with a ufunc over the minor axis when non-empty + + Can be applied to a function of self.data by supplying data parameter. + + Warning: this does not call sum_duplicates() + + Returns + ------- + major_index : array of ints + Major indices where nonzero + + value : array of self.dtype + Reduce result for nonzeros in each major_index + """ + if data is None: + data = self.data + major_index = np.flatnonzero(np.diff(self.indptr)) + value = ufunc.reduceat(data, + downcast_intp_index(self.indptr[major_index])) + return major_index, value + + ####################### + # Getting and Setting # + ####################### + + def _get_intXint(self, row, col): + M, N = self._swap(self.shape) + major, minor = self._swap((row, col)) + indptr, indices, data = get_csr_submatrix( + M, N, self.indptr, self.indices, self.data, + major, major + 1, minor, minor + 1) + return data.sum(dtype=self.dtype) + + def _get_sliceXslice(self, row, col): + major, minor = self._swap((row, col)) + if major.step in (1, None) and minor.step in (1, None): + return self._get_submatrix(major, minor, copy=True) + return self._major_slice(major)._minor_slice(minor) + + def _get_arrayXarray(self, row, col): + # inner indexing + idx_dtype = self.indices.dtype + M, N = self._swap(self.shape) + major, minor = self._swap((row, col)) + major = np.asarray(major, dtype=idx_dtype) + minor = np.asarray(minor, dtype=idx_dtype) + + val = np.empty(major.size, dtype=self.dtype) + csr_sample_values(M, N, self.indptr, self.indices, self.data, + major.size, major.ravel(), minor.ravel(), val) + if major.ndim == 1: + return self._ascontainer(val) + return self.__class__(val.reshape(major.shape)) + + def _get_columnXarray(self, row, col): + # outer indexing + major, minor = self._swap((row, col)) + return self._major_index_fancy(major)._minor_index_fancy(minor) + + def _major_index_fancy(self, idx): + """Index along the major axis where idx is an array of ints. + """ + idx_dtype = self._get_index_dtype((self.indptr, self.indices)) + indices = np.asarray(idx, dtype=idx_dtype).ravel() + + N = self._swap(self._shape_as_2d)[1] + M = len(indices) + new_shape = self._swap((M, N)) if self.ndim == 2 else (M,) + if M == 0: + return self.__class__(new_shape, dtype=self.dtype) + + self_indptr = self.indptr.astype(idx_dtype, copy=False) + self_indices = self.indices.astype(idx_dtype, copy=False) + + row_nnz = self_indptr[indices + 1] - self_indptr[indices] + res_indptr = np.zeros(M + 1, dtype=idx_dtype) + np.cumsum(row_nnz, out=res_indptr[1:]) + + nnz = res_indptr[-1] + res_indices = np.empty(nnz, dtype=idx_dtype) + res_data = np.empty(nnz, dtype=self.dtype) + csr_row_index( + M, + indices, + self_indptr, + self_indices, + self.data, + res_indices, + res_data + ) + + return self.__class__((res_data, res_indices, res_indptr), + shape=new_shape, copy=False) + + def _major_slice(self, idx, copy=False): + """Index along the major axis where idx is a slice object. + """ + if idx == slice(None): + return self.copy() if copy else self + + M, N = self._swap(self._shape_as_2d) + start, stop, step = idx.indices(M) + M = len(range(start, stop, step)) + new_shape = self._swap((M, N)) if self.ndim == 2 else (M,) + if M == 0: + return self.__class__(new_shape, dtype=self.dtype) + + # Work out what slices are needed for `row_nnz` + # start,stop can be -1, only if step is negative + start0, stop0 = start, stop + if stop == -1 and start >= 0: + stop0 = None + start1, stop1 = start + 1, stop + 1 + + row_nnz = self.indptr[start1:stop1:step] - \ + self.indptr[start0:stop0:step] + idx_dtype = self.indices.dtype + res_indptr = np.zeros(M+1, dtype=idx_dtype) + np.cumsum(row_nnz, out=res_indptr[1:]) + + if step == 1: + all_idx = slice(self.indptr[start], self.indptr[stop]) + res_indices = np.array(self.indices[all_idx], copy=copy) + res_data = np.array(self.data[all_idx], copy=copy) + else: + nnz = res_indptr[-1] + res_indices = np.empty(nnz, dtype=idx_dtype) + res_data = np.empty(nnz, dtype=self.dtype) + csr_row_slice(start, stop, step, self.indptr, self.indices, + self.data, res_indices, res_data) + + return self.__class__((res_data, res_indices, res_indptr), + shape=new_shape, copy=False) + + def _minor_index_fancy(self, idx): + """Index along the minor axis where idx is an array of ints. + """ + idx_dtype = self._get_index_dtype((self.indices, self.indptr)) + indices = self.indices.astype(idx_dtype, copy=False) + indptr = self.indptr.astype(idx_dtype, copy=False) + + idx = np.asarray(idx, dtype=idx_dtype).ravel() + + M, N = self._swap(self._shape_as_2d) + k = len(idx) + new_shape = self._swap((M, k)) if self.ndim == 2 else (k,) + if k == 0: + return self.__class__(new_shape, dtype=self.dtype) + + # pass 1: count idx entries and compute new indptr + col_offsets = np.zeros(N, dtype=idx_dtype) + res_indptr = np.empty_like(self.indptr, dtype=idx_dtype) + csr_column_index1( + k, + idx, + M, + N, + indptr, + indices, + col_offsets, + res_indptr, + ) + + # pass 2: copy indices/data for selected idxs + col_order = np.argsort(idx).astype(idx_dtype, copy=False) + nnz = res_indptr[-1] + res_indices = np.empty(nnz, dtype=idx_dtype) + res_data = np.empty(nnz, dtype=self.dtype) + csr_column_index2(col_order, col_offsets, len(self.indices), + indices, self.data, res_indices, res_data) + return self.__class__((res_data, res_indices, res_indptr), + shape=new_shape, copy=False) + + def _minor_slice(self, idx, copy=False): + """Index along the minor axis where idx is a slice object. + """ + if idx == slice(None): + return self.copy() if copy else self + + M, N = self._swap(self._shape_as_2d) + start, stop, step = idx.indices(N) + N = len(range(start, stop, step)) + if N == 0: + return self.__class__(self._swap((M, N)), dtype=self.dtype) + if step == 1: + return self._get_submatrix(minor=idx, copy=copy) + # TODO: don't fall back to fancy indexing here + return self._minor_index_fancy(np.arange(start, stop, step)) + + def _get_submatrix(self, major=None, minor=None, copy=False): + """Return a submatrix of this matrix. + + major, minor: None, int, or slice with step 1 + """ + M, N = self._swap(self._shape_as_2d) + i0, i1 = _process_slice(major, M) + j0, j1 = _process_slice(minor, N) + + if i0 == 0 and j0 == 0 and i1 == M and j1 == N: + return self.copy() if copy else self + + indptr, indices, data = get_csr_submatrix( + M, N, self.indptr, self.indices, self.data, i0, i1, j0, j1) + + shape = self._swap((i1 - i0, j1 - j0)) + if self.ndim == 1: + shape = (shape[1],) + return self.__class__((data, indices, indptr), shape=shape, + dtype=self.dtype, copy=False) + + def _set_intXint(self, row, col, x): + i, j = self._swap((row, col)) + self._set_many(i, j, x) + + def _set_arrayXarray(self, row, col, x): + i, j = self._swap((row, col)) + self._set_many(i, j, x) + + def _set_arrayXarray_sparse(self, row, col, x): + # clear entries that will be overwritten + self._zero_many(*self._swap((row, col))) + + M, N = row.shape # matches col.shape + broadcast_row = M != 1 and x.shape[0] == 1 + broadcast_col = N != 1 and x.shape[1] == 1 + r, c = x.row, x.col + + x = np.asarray(x.data, dtype=self.dtype) + if x.size == 0: + return + + if broadcast_row: + r = np.repeat(np.arange(M), len(r)) + c = np.tile(c, M) + x = np.tile(x, M) + if broadcast_col: + r = np.repeat(r, N) + c = np.tile(np.arange(N), len(c)) + x = np.repeat(x, N) + # only assign entries in the new sparsity structure + i, j = self._swap((row[r, c], col[r, c])) + self._set_many(i, j, x) + + def _setdiag(self, values, k): + if 0 in self.shape: + return + if self.ndim == 1: + raise NotImplementedError('diagonals cant be set in 1d arrays') + + M, N = self.shape + broadcast = (values.ndim == 0) + + if k < 0: + if broadcast: + max_index = min(M + k, N) + else: + max_index = min(M + k, N, len(values)) + i = np.arange(-k, max_index - k, dtype=self.indices.dtype) + j = np.arange(max_index, dtype=self.indices.dtype) + + else: + if broadcast: + max_index = min(M, N - k) + else: + max_index = min(M, N - k, len(values)) + i = np.arange(max_index, dtype=self.indices.dtype) + j = np.arange(k, k + max_index, dtype=self.indices.dtype) + + if not broadcast: + values = values[:len(i)] + + x = np.atleast_1d(np.asarray(values, dtype=self.dtype)).ravel() + if x.squeeze().shape != i.squeeze().shape: + x = np.broadcast_to(x, i.shape) + if x.size == 0: + return + + M, N = self._swap((M, N)) + i, j = self._swap((i, j)) + n_samples = x.size + offsets = np.empty(n_samples, dtype=self.indices.dtype) + ret = csr_sample_offsets(M, N, self.indptr, self.indices, n_samples, + i, j, offsets) + if ret == 1: + # rinse and repeat + self.sum_duplicates() + csr_sample_offsets(M, N, self.indptr, self.indices, n_samples, + i, j, offsets) + if -1 not in offsets: + # only affects existing non-zero cells + self.data[offsets] = x + return + + is_existing = (offsets >= 0) + N_new = len(is_existing) - np.count_nonzero(is_existing) + + # Boundary between csc and convert to coo + # The value 0.001 is justified in gh-19962#issuecomment-1920499678 + if N_new < self.nnz * 0.001: + # replace existing entries + self.data[offsets[is_existing]] = x[is_existing] + # create new entries + is_new = np.logical_not(is_existing, out=is_existing) + del is_existing + self._insert_many(i[is_new], j[is_new], x[is_new]) + else: + # convert to coo for _set_diag + do_sort = self.has_sorted_indices + coo = self.tocoo() + coo._setdiag(values, k) + arrays = coo._coo_to_compressed(self._swap) + self.indptr, self.indices, self.data, _ = arrays + # Sort the indices (like in _insert_many) + if do_sort: + self.has_sorted_indices = False # force a sort + self.sort_indices() + + def _prepare_indices(self, i, j): + M, N = self._swap(self._shape_as_2d) + + def check_bounds(indices, bound): + idx = indices.max() + if idx >= bound: + raise IndexError(f'index ({idx}) out of range (>= {bound})') + idx = indices.min() + if idx < -bound: + raise IndexError(f'index ({idx}) out of range (< -{bound})') + + i = np.atleast_1d(np.asarray(i, dtype=self.indices.dtype)).ravel() + j = np.atleast_1d(np.asarray(j, dtype=self.indices.dtype)).ravel() + check_bounds(i, M) + check_bounds(j, N) + return i, j, M, N + + def _set_many(self, i, j, x): + """Sets value at each (i, j) to x + + Here (i,j) index major and minor respectively, and must not contain + duplicate entries. + """ + i, j, M, N = self._prepare_indices(i, j) + x = np.atleast_1d(np.asarray(x, dtype=self.dtype)).ravel() + + n_samples = x.size + offsets = np.empty(n_samples, dtype=self.indices.dtype) + ret = csr_sample_offsets(M, N, self.indptr, self.indices, n_samples, + i, j, offsets) + if ret == 1: + # rinse and repeat + self.sum_duplicates() + csr_sample_offsets(M, N, self.indptr, self.indices, n_samples, + i, j, offsets) + + if -1 not in offsets: + # only affects existing non-zero cells + self.data[offsets] = x + return + + else: + warn(f"Changing the sparsity structure of a {self.__class__.__name__} is" + " expensive. lil and dok are more efficient.", + SparseEfficiencyWarning, stacklevel=3) + # replace where possible + mask = offsets > -1 + self.data[offsets[mask]] = x[mask] + # only insertions remain + mask = ~mask + i = i[mask] + i[i < 0] += M + j = j[mask] + j[j < 0] += N + self._insert_many(i, j, x[mask]) + + def _zero_many(self, i, j): + """Sets value at each (i, j) to zero, preserving sparsity structure. + + Here (i,j) index major and minor respectively. + """ + i, j, M, N = self._prepare_indices(i, j) + + n_samples = len(i) + offsets = np.empty(n_samples, dtype=self.indices.dtype) + ret = csr_sample_offsets(M, N, self.indptr, self.indices, n_samples, + i, j, offsets) + if ret == 1: + # rinse and repeat + self.sum_duplicates() + csr_sample_offsets(M, N, self.indptr, self.indices, n_samples, + i, j, offsets) + + # only assign zeros to the existing sparsity structure + self.data[offsets[offsets > -1]] = 0 + + def _insert_many(self, i, j, x): + """Inserts new nonzero at each (i, j) with value x + + Here (i,j) index major and minor respectively. + i, j and x must be non-empty, 1d arrays. + Inserts each major group (e.g. all entries per row) at a time. + Maintains has_sorted_indices property. + Modifies i, j, x in place. + """ + order = np.argsort(i, kind='mergesort') # stable for duplicates + i = i.take(order, mode='clip') + j = j.take(order, mode='clip') + x = x.take(order, mode='clip') + + do_sort = self.has_sorted_indices + + # Update index data type + idx_dtype = self._get_index_dtype((self.indices, self.indptr), + maxval=(self.indptr[-1].item() + x.size)) + self.indptr = np.asarray(self.indptr, dtype=idx_dtype) + self.indices = np.asarray(self.indices, dtype=idx_dtype) + i = np.asarray(i, dtype=idx_dtype) + j = np.asarray(j, dtype=idx_dtype) + + # Collate old and new in chunks by major index + indices_parts = [] + data_parts = [] + + # TODO: explore creating a native sparsetools version of: + # ui, ui_indptr = np.unique(i, return_index=True) + # These 2 lines are equiv to np.unique but avoid big memory use. See gh-24951 + ui_indptr = np.diff(np.concatenate(([-1], i))).nonzero()[0] + ui = i[ui_indptr] + + ui_indptr = np.concatenate((ui_indptr, [len(j)])) + new_nnzs = np.diff(ui_indptr) + prev = 0 + for c, (ii, js, je) in enumerate(zip(ui, ui_indptr, ui_indptr[1:])): + # old entries + start = self.indptr[prev] + stop = self.indptr[ii] + indices_parts.append(self.indices[start:stop]) + data_parts.append(self.data[start:stop]) + + # handle duplicate j: keep last setting + uj, uj_indptr = np.unique(j[js:je][::-1], return_index=True) + if len(uj) == je - js: + indices_parts.append(j[js:je]) + data_parts.append(x[js:je]) + else: + indices_parts.append(j[js:je][::-1][uj_indptr]) + data_parts.append(x[js:je][::-1][uj_indptr]) + new_nnzs[c] = len(uj) + + prev = ii + + # remaining old entries + start = self.indptr[ii] + indices_parts.append(self.indices[start:]) + data_parts.append(self.data[start:]) + + # update attributes + self.indices = np.concatenate(indices_parts) + self.data = np.concatenate(data_parts) + nnzs = np.empty(self.indptr.shape, dtype=idx_dtype) + nnzs[0] = idx_dtype(0) + indptr_diff = np.diff(self.indptr) + indptr_diff[ui] += new_nnzs + nnzs[1:] = indptr_diff + self.indptr = np.cumsum(nnzs, out=nnzs) + + if do_sort: + # TODO: only sort where necessary + self.has_sorted_indices = False + self.sort_indices() + + self.check_format(full_check=False) + + ###################### + # Conversion methods # + ###################### + + def tocoo(self, copy=True): + if self.ndim == 1: + csr = self.tocsr() + return self._coo_container((csr.data, (csr.indices,)), csr.shape, copy=copy) + major_dim, minor_dim = self._swap(self.shape) + minor_indices = self.indices + major_indices = np.empty(len(minor_indices), dtype=self.indices.dtype) + expandptr(major_dim, self.indptr, major_indices) + coords = self._swap((major_indices, minor_indices)) + + return self._coo_container( + (self.data, coords), self.shape, copy=copy, dtype=self.dtype + ) + + tocoo.__doc__ = _spbase.tocoo.__doc__ + + def toarray(self, order=None, out=None): + if out is None and order is None: + order = self._swap('cf')[0] + out = self._process_toarray_args(order, out) + if not (out.flags.c_contiguous or out.flags.f_contiguous): + raise ValueError('Output array must be C or F contiguous') + # align ideal order with output array order + if out.flags.c_contiguous: + x = self.tocsr() + y = out + else: + x = self.tocsc() + y = out.T + M, N = x._swap(x._shape_as_2d) + csr_todense(M, N, x.indptr, x.indices, x.data, y) + return out + + toarray.__doc__ = _spbase.toarray.__doc__ + + ############################################################## + # methods that examine or modify the internal data structure # + ############################################################## + + def eliminate_zeros(self): + """Remove zero entries from the array/matrix. + + This is an *in place* operation. + """ + M, N = self._swap(self._shape_as_2d) + csr_eliminate_zeros(M, N, self.indptr, self.indices, self.data) + self.prune() # nnz may have changed + + @property + def has_canonical_format(self) -> bool: + """Whether the array/matrix has sorted indices and no duplicates + + Returns + - True: if the above applies + - False: otherwise + + has_canonical_format implies has_sorted_indices, so if the latter flag + is False, so will the former be; if the former is found True, the + latter flag is also set. + """ + # first check to see if result was cached + if not getattr(self, '_has_sorted_indices', True): + # not sorted => not canonical + self._has_canonical_format = False + elif not hasattr(self, '_has_canonical_format'): + M = len(self.indptr) - 1 + self.has_canonical_format = bool( + csr_has_canonical_format(M, self.indptr, self.indices) + ) + return self._has_canonical_format + + @has_canonical_format.setter + def has_canonical_format(self, val: bool): + self._has_canonical_format = bool(val) + if val: + self.has_sorted_indices = True + + def sum_duplicates(self): + """Eliminate duplicate entries by adding them together. + + This is an *in place* operation. + """ + if self.has_canonical_format: + return + self.sort_indices() + + M, N = self._swap(self._shape_as_2d) + csr_sum_duplicates(M, N, self.indptr, self.indices, self.data) + + self.prune() # nnz may have changed + self.has_canonical_format = True + + @property + def has_sorted_indices(self) -> bool: + """Whether the indices are sorted + + Returns + - True: if the indices of the array/matrix are in sorted order + - False: otherwise + """ + # first check to see if result was cached + if not hasattr(self, '_has_sorted_indices'): + M = len(self.indptr) - 1 + self._has_sorted_indices = bool( + csr_has_sorted_indices(M, self.indptr, self.indices) + ) + return self._has_sorted_indices + + @has_sorted_indices.setter + def has_sorted_indices(self, val: bool): + self._has_sorted_indices = bool(val) + + + def sorted_indices(self): + """Return a copy of this array/matrix with sorted indices. + + Returns + ------- + sparse array/matrix + A copy of this array/matrix with sorted indices. + """ + A = self.copy() + A.sort_indices() + return A + + # an alternative that has linear complexity is the following + # although the previous option is typically faster + # return self.toother().toother() + + def sort_indices(self): + """Sort the indices of this array/matrix *in place*. + """ + if not self.has_sorted_indices: + M = len(self.indptr) - 1 + csr_sort_indices(M, self.indptr, self.indices, self.data) + self.has_sorted_indices = True + + def prune(self): + """Remove empty space after all non-zero elements. + """ + major_dim = self._swap(self._shape_as_2d)[0] + + if len(self.indptr) != major_dim + 1: + raise ValueError('index pointer has invalid length') + if len(self.indices) < self.nnz: + raise ValueError('indices array has fewer than nnz elements') + if len(self.data) < self.nnz: + raise ValueError('data array has fewer than nnz elements') + + self.indices = _prune_array(self.indices[:self.nnz]) + self.data = _prune_array(self.data[:self.nnz]) + + def resize(self, *shape): + shape = check_shape(shape, allow_nd=self._allow_nd) + + if hasattr(self, 'blocksize'): + bm, bn = self.blocksize + new_M, rm = divmod(shape[0], bm) + new_N, rn = divmod(shape[1], bn) + if rm or rn: + raise ValueError(f"shape must be divisible into {self.blocksize}" + f" blocks. Got {shape}") + M, N = self.shape[0] // bm, self.shape[1] // bn + else: + new_M, new_N = self._swap(shape if len(shape)>1 else (1, shape[0])) + M, N = self._swap(self._shape_as_2d) + + if new_M < M: + self.indices = self.indices[:self.indptr[new_M]] + self.data = self.data[:self.indptr[new_M]] + self.indptr = self.indptr[:new_M + 1] + elif new_M > M: + self.indptr = np.resize(self.indptr, new_M + 1) + self.indptr[M + 1:].fill(self.indptr[M]) + + if new_N < N: + mask = self.indices < new_N + if not np.all(mask): + self.indices = self.indices[mask] + self.data = self.data[mask] + major_index, val = self._minor_reduce(np.add, mask) + self.indptr.fill(0) + self.indptr[1:][major_index] = val + np.cumsum(self.indptr, out=self.indptr) + + self._shape = shape + + resize.__doc__ = _spbase.resize.__doc__ + + ################### + # utility methods # + ################### + + # needed by _data_matrix + def _with_data(self, data, copy=True): + """Returns a matrix with the same sparsity structure as self, + but with different data. By default the structure arrays + (i.e. .indptr and .indices) are copied. + """ + if copy: + return self.__class__((data, self.indices.copy(), + self.indptr.copy()), + shape=self.shape, + dtype=data.dtype) + else: + return self.__class__((data, self.indices, self.indptr), + shape=self.shape, dtype=data.dtype) + + def _binopt(self, other, op): + """apply the binary operation fn to two sparse matrices.""" + other = self.__class__(other) + + # e.g. csr_plus_csr, csr_minus_csr, etc. + fn = getattr(_sparsetools, "csr" + op + "csr") + + maxnnz = self.nnz + other.nnz + idx_dtype = self._get_index_dtype((self.indptr, self.indices, + other.indptr, other.indices), + maxval=maxnnz) + indptr = np.empty(self.indptr.shape, dtype=idx_dtype) + indices = np.empty(maxnnz, dtype=idx_dtype) + + bool_ops = ['_ne_', '_lt_', '_gt_', '_le_', '_ge_'] + if op in bool_ops: + data = np.empty(maxnnz, dtype=np.bool_) + else: + data = np.empty(maxnnz, dtype=upcast(self.dtype, other.dtype)) + + M, N = self._swap(self._shape_as_2d) + fn(M, N, + np.asarray(self.indptr, dtype=idx_dtype), + np.asarray(self.indices, dtype=idx_dtype), + self.data, + np.asarray(other.indptr, dtype=idx_dtype), + np.asarray(other.indices, dtype=idx_dtype), + other.data, + indptr, indices, data) + + A = self.__class__((data, indices, indptr), shape=self.shape) + A.prune() + + return A + + def _divide_sparse(self, other): + """ + Divide this matrix by a second sparse matrix. + """ + if other.shape != self.shape: + raise ValueError(f"inconsistent shapes {self.shape} and {other.shape}") + + r = self._binopt(other, '_eldiv_') + + if np.issubdtype(r.dtype, np.inexact): + # Eldiv leaves entries outside the combined sparsity + # pattern empty, so they must be filled manually. + # Everything outside of other's sparsity is NaN, and everything + # inside it is either zero or defined by eldiv. + out = np.empty(self.shape, dtype=self.dtype) + out.fill(np.nan) + coords = other.nonzero() + if self.ndim == 1: + coords = (coords[-1],) + out[coords] = 0 + r = r.tocoo() + out[r.coords] = r.data + return self._container(out) + else: + # integers types go with nan <-> 0 + out = r + return out + + def _broadcast_to(self, shape, copy=False): + if self.shape == shape: + return self.copy() if copy else self + + shape = check_shape(shape, allow_nd=(self._allow_nd)) + + if broadcast_shapes(self.shape, shape) != shape: + raise ValueError("cannot be broadcast") + + if len(self.shape) == 1 and len(shape) == 1: + self.sum_duplicates() + if self.nnz == 0: # array has no non zero elements + return self.__class__(shape, dtype=self.dtype, copy=False) + + N = shape[0] + data = np.full(N, self.data[0]) + indices = np.arange(0,N) + indptr = np.array([0, N]) + return self._csr_container((data, indices, indptr), shape=shape, copy=False) + + # treat 1D as a 2D row + old_shape = self._shape_as_2d + + if len(shape) != 2: + ndim = len(shape) + raise ValueError(f'CSR/CSC broadcast_to cannot have shape >2D. Got {ndim}D') + + if self.nnz == 0: # array has no non zero elements + return self.__class__(shape, dtype=self.dtype, copy=False) + + self.sum_duplicates() + M, N = self._swap(shape) + oM, oN = self._swap(old_shape) + if all(s == 1 for s in old_shape): + # Broadcast a single element to the entire shape + data = np.full(M * N, self.data[0]) + indices = np.tile(np.arange(N), M) + indptr = np.arange(0, len(data) + 1, N) + elif oM == 1 and oN == N: + # Broadcast row-wise (columns for CSC) + data = np.tile(self.data, M) + indices = np.tile(self.indices, M) + indptr = np.arange(0, len(data) + 1, len(self.data)) + elif oN == 1 and oM == M: + # Broadcast column-wise (rows for CSC) + data = np.repeat(self.data, N) + indices = np.tile(np.arange(N), len(self.data)) + indptr = self.indptr * N + return self.__class__((data, indices, indptr), shape=shape, copy=False) + + +def _make_diagonal_csr(data, is_array=False): + """build diagonal csc_array/csr_array => self._csr_container + + Parameter `data` should be a raveled numpy array holding the + values on the diagonal of the resulting sparse matrix. + """ + from ._csr import csr_array, csr_matrix + csr_array = csr_array if is_array else csr_matrix + + N = len(data) + idx_dtype = get_index_dtype(maxval=N) + indptr = np.arange(N + 1, dtype=idx_dtype) + indices = indptr[:-1] + + return csr_array((data, indices, indptr), shape=(N, N)) + + +def _process_slice(sl, num): + if sl is None: + i0, i1 = 0, num + elif isinstance(sl, slice): + i0, i1, stride = sl.indices(num) + if stride != 1: + raise ValueError('slicing with step != 1 not supported') + i0 = min(i0, i1) # give an empty slice when i0 > i1 + elif isintlike(sl): + if sl < 0: + sl += num + i0, i1 = sl, sl + 1 + if i0 < 0 or i1 > num: + raise IndexError(f'index out of bounds: 0 <= {i0} < {i1} <= {num}') + else: + raise TypeError('expected slice or scalar') + + return i0, i1 diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/_construct.py b/venv/lib/python3.12/site-packages/scipy/sparse/_construct.py new file mode 100644 index 0000000000000000000000000000000000000000..78c07cfd1e76613535871ae965c82e777498d54e --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/_construct.py @@ -0,0 +1,1839 @@ +"""Functions to construct sparse matrices and arrays +""" + +__docformat__ = "restructuredtext en" + +__all__ = ['kron', 'kronsum', 'hstack', 'vstack', 'block_diag', + 'diags_array', 'block_array', 'eye_array', 'random_array', + 'expand_dims', 'permute_dims', 'swapaxes', 'matrix_transpose', + 'spdiags', 'eye', 'identity', 'bmat', 'rand', 'random', 'diags'] + +import numbers +import math +import os +from warnings import warn +import numpy as np + +from scipy._lib._util import check_random_state, rng_integers, _transition_to_rng +from scipy._lib.deprecation import _NoValue +from ._sputils import upcast, get_index_dtype, isscalarlike, isintlike + +from ._sparsetools import csr_hstack +from ._bsr import bsr_matrix, bsr_array +from ._coo import coo_matrix, coo_array +from ._csc import csc_matrix, csc_array +from ._csr import csr_matrix, csr_array +from ._dia import dia_matrix, dia_array + +from ._base import issparse, sparray, spmatrix + + +def expand_dims(A, /, *, axis=0): + """ + Add trivial axes to an array. Shape gets a ``1`` inserted at position `axis`. + + Parameters + ---------- + A : sparse array + Input array. + axis : int + Position in the expanded axes where the new axis (or axes) is placed. + For a dimension ``N`` array, a valid axis is an integer on the + closed-interval ``[-N-1, N]``. Negative values work from the end of + the shape. ``0`` prepends an axis, as does ``-N-1``. ``-1`` appends + an axis, as does ``N``. The new axis has shape ``1`` and indices are + created with the value ``0``. + + Returns + ------- + out : sparse array + An expanded copy output in COO format with the same dtype as `A`. + + Raises + ------ + ValueError + If provided a non-integer or out of range ``[-N-1, N]`` axis, + where ``N`` is ``A.ndim``. + + Examples + -------- + >>> from scipy.sparse import csr_array, expand_dims + >>> A = csr_array([[1, 2], [2, 0]]) + >>> A.shape + (2, 2) + >>> expand_dims(A, axis=1).shape + (2, 1, 2) + + """ + if not isintlike(axis): + raise ValueError(f"Invalid axis {axis}. Must be an integer.") + idx = axis if axis >= 0 else axis + A.ndim + 1 + if idx < 0 or idx > A.ndim: + raise ValueError(f"Invalid axis {axis} for N={A.ndim}. Must be in [-N-1, N].") + + newA = A.tocoo(copy=True) + new_coord = np.zeros_like(newA.coords[0]) + + newA.coords = newA.coords[:idx] + (new_coord,) + newA.coords[idx:] + newA._shape = newA.shape[:idx] + (1,) + newA.shape[idx:] + if A.format == "coo": + newA.has_canonical_format = A.has_canonical_format + return newA + + +def swapaxes(A, axis1, axis2): + """Interchange two axes of an array. + + Parameters + ---------- + A : sparse array + Input array. + axis1 : int + First axis. + axis2 : int + Second axis. + + Returns + ------- + a_swapped : sparse array in COO format + A copy of the input array with the two identified axes swapped. + + Raises + ------ + ValueError + If provided a non-integer or out of range ``[-N, N-1]`` axis, + where ``N`` is ``A.ndim``. + + Examples + -------- + >>> from scipy.sparse import coo_array, swapaxes + >>> A = coo_array([[[1, 2, 3], [2, 0, 0]]]) + >>> A.shape + (1, 2, 3) + >>> swapaxes(A, 1, 2).shape + (1, 3, 2) + + """ + axes = np.arange(A.ndim) + try: + axes[[axis1, axis2]] = axes[[axis2, axis1]] + except IndexError as err: + # original msg looks like: "index -4 is out of bounds for axis 0 with size 2" + msg = str(err) + msg = msg.removeprefix('index ').split(' axis ', 1)[0] + # Final error is: "Invalid axis: -4 is out of bounds for ndim=2" + raise ValueError(f"Invalid axis: {msg} ndim={A.ndim}") + + axes = tuple(axes) + return permute_dims(A, axes=axes, copy=True) + + +def permute_dims(A, axes=None, copy=False): + """Permute the axes of the sparse array `A` to the order `axes`. + + Parameters + ---------- + A : sparse array + Input array. + axes : tuple or list of ints, optional + If specified, it must be a tuple or list which contains a permutation + of ``[0, 1, ..., N-1]`` where ``N`` is ``A.ndim``. The ith + axis of the returned array will correspond to the axis numbered ``axes[i]`` + of the input. If not specified, defaults to ``range(A.ndim)[::-1]``, + which reverses the order of the axes. + copy : bool, optional (default: False) + Whether to return the permutation as a copy. If False, an in-place + permutation is provided if possible depending on format. + + Returns + ------- + out : sparse array in COO format + A copy of `A` with permuted axes. + + Raises + ------ + ValueError + If provided a non-integer or out of range ``[-N, N-1]`` axis, + where ``N`` is ``A.ndim``. + + Examples + -------- + >>> from scipy.sparse import coo_array, permute_dims + >>> A = coo_array([[[1, 2, 3], [2, 0, 0]]]) + >>> A.shape + (1, 2, 3) + >>> permute_dims(A, axes=(1, 2, 0)).shape + (2, 3, 1) + + """ + ndim = A.ndim + if axes is None: + axes = tuple(range(ndim)[::-1]) + elif len(axes) != ndim: + raise ValueError(f"Incorrect number of axes: {ndim} instead of {A.ndim}") + + # -------------This is from _sputils.validateaxis which almost does what we want + # TODO stop _sputils.validateaxis from returning `None` when len(axes)==ndim + if not isinstance(axes, tuple | list): + # If not a tuple, check that the provided axes is actually + # an integer and raise a TypeError similar to NumPy's + if not np.issubdtype(np.dtype(type(axes)), np.integer): + raise TypeError(f'axis must be an integer/tuple of ints, not {type(axes)}') + axes = (axes,) + + canon_axes = [] + for ax in axes: + if not isintlike(ax): + raise TypeError(f"axis must be an integer. (given {ax})") + if ax < 0: + ax += ndim + if ax < 0 or ax >= ndim: + raise ValueError("axis out of range for ndim") + canon_axes.append(ax) + + if len(canon_axes) != len(set(canon_axes)): + raise ValueError("duplicate value in axis") + # -------------End of code from _sputils.validateaxis + axes = canon_axes + if axes == list(range(ndim)): + return A if not copy else A.copy() + + A = A.tocoo(copy=copy) + A._shape = tuple(A.shape[idx] for idx in axes) + A.coords = tuple(A.coords[idx] for idx in axes) + A.has_canonical_format = False # data usually no longer sorted + return A + + +def matrix_transpose(A): + """Return the matrix transpose of `A`. + + Parameters + ---------- + A : sparse array + Input array. + + Returns + ------- + sparse array + The matrix transpose of `A`. + + See Also + -------- + coo_array.mT : equivalent attribute + coo_array.T : full transposition reversing all dimensions + numpy.matrix_transpose : equivalent function in NumPy + + Notes + ----- + This is equivalent to ``A.T`` for 2-D arrays, and interprets + greater dimensional `A` as a stack of 2-D arrays on which + to perform the transpose. + + Examples + -------- + >>> import numpy as np + >>> from scipy.sparse import coo_array, matrix_transpose + >>> data = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]]) + >>> array = coo_array(data) + >>> array.T.toarray() + array([[[1, 5], + [3, 7]], + + [[2, 6], + [4, 8]]]) + >>> matrix_transpose(array).toarray() + array([[[1, 3], + [2, 4]], + + [[5, 7], + [6, 8]]]) + """ + return A.mT + + +def spdiags(data, diags, m=None, n=None, format=None): + """ + Return a sparse matrix from diagonals. + + .. warning:: + + This function returns a sparse matrix -- not a sparse array. + You are encouraged to use `dia_array` to take advantage + of the sparse array functionality. (See Notes below.) + + Parameters + ---------- + data : array_like + Matrix diagonals stored row-wise + diags : sequence of int or an int + Diagonals to set: + + * k = 0 the main diagonal + * k > 0 the kth upper diagonal + * k < 0 the kth lower diagonal + m, n : int, tuple, optional + Shape of the result. If `n` is None and `m` is a given tuple, + the shape is this tuple. If omitted, the matrix is square and + its shape is ``len(data[0])``. + format : str, optional + Format of the result. By default (format=None) an appropriate sparse + matrix format is returned. This choice is subject to change. + + Returns + ------- + new_matrix : sparse matrix + `dia_matrix` format with values in ``data`` on diagonals from ``diags``. + + See Also + -------- + diags_array : more convenient form of this function. + diags : matrix version of diags_array. + dia_matrix : the sparse DIAgonal format. + + Notes + ----- + This function can be replaced by an equivalent call to `dia_matrix` as:: + + dia_matrix((data, diags), shape=(m, n)).asformat(format) + + Examples + -------- + >>> import numpy as np + >>> from scipy.sparse import spdiags + >>> data = np.array([[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]]) + >>> diags = np.array([0, -1, 2]) + >>> spdiags(data, diags, 4, 4).toarray() + array([[1, 0, 3, 0], + [1, 2, 0, 4], + [0, 2, 3, 0], + [0, 0, 3, 4]]) + """ + if m is None and n is None: + m = n = len(data[0]) + elif n is None: + m, n = m + return dia_matrix((data, diags), shape=(m, n)).asformat(format) + + +def diags_array(diagonals, /, *, offsets=0, shape=None, format=None, dtype=_NoValue): + """ + Construct a sparse array from diagonals. + + Parameters + ---------- + diagonals : sequence of array_like + Sequence of arrays containing the array diagonals, + corresponding to `offsets`. + offsets : sequence of int or an int, optional + Diagonals to set (repeated offsets are not allowed): + - k = 0 the main diagonal (default) + - k > 0 the kth upper diagonal + - k < 0 the kth lower diagonal + shape : tuple of int, optional + Shape of the result. If omitted, a square array large enough + to contain the diagonals is returned. + format : {"dia", "csr", "csc", "lil", ...}, optional + Matrix format of the result. By default (format=None) an + appropriate sparse array format is returned. This choice is + subject to change. + dtype : dtype, optional + Data type of the array. If `dtype` is None, the output + data type is determined by the data type of the input diagonals. + + Up until SciPy 1.19, the default behavior will be to return an array + with an inexact (floating point) data type. In particular, integer + input will be converted to double precision floating point. This + behavior is deprecated, and in SciPy 1.19, the default behavior + will be changed to return an array with the same data type as the + input diagonals. To adopt this behavior before version 1.19, use + ``dtype=None``. + + Returns + ------- + new_array : dia_array + `dia_array` holding the values in `diagonals` offset from the main diagonal + as indicated in `offsets`. + + See Also + -------- + dia_array : constructor for the sparse DIAgonal format. + + Notes + ----- + Repeated diagonal offsets are disallowed. + + The result from ``diags_array`` is the sparse equivalent of:: + + np.diag(diagonals[0], offsets[0]) + + ... + + np.diag(diagonals[k], offsets[k]) + + ``diags_array`` differs from `dia_array` in the way it handles off-diagonals. + Specifically, `dia_array` assumes the data input includes padding + (ignored values) at the start/end of the rows for positive/negative + offset, while ``diags_array`` assumes the input data has no padding. + Each value in the input `diagonals` is used. + + .. versionadded:: 1.11 + + Examples + -------- + >>> from scipy.sparse import diags_array + >>> diagonals = [[1.0, 2.0, 3.0, 4.0], [1.0, 2.0, 3.0], [1.0, 2.0]] + >>> diags_array(diagonals, offsets=[0, -1, 2]).toarray() + array([[1., 0., 1., 0.], + [1., 2., 0., 2.], + [0., 2., 3., 0.], + [0., 0., 3., 4.]]) + + Broadcasting of scalars is supported (but shape needs to be + specified): + + >>> diags_array([1.0, -2.0, 1.0], offsets=[-1, 0, 1], shape=(4, 4)).toarray() + array([[-2., 1., 0., 0.], + [ 1., -2., 1., 0.], + [ 0., 1., -2., 1.], + [ 0., 0., 1., -2.]]) + + + If only one diagonal is wanted (as in `numpy.diag`), the following + works as well: + + >>> diags_array([1.0, 2.0, 3.0], offsets=1).toarray() + array([[ 0., 1., 0., 0.], + [ 0., 0., 2., 0.], + [ 0., 0., 0., 3.], + [ 0., 0., 0., 0.]]) + """ + # if offsets is not a sequence, assume that there's only one diagonal + if isscalarlike(offsets): + # now check that there's actually only one diagonal + if len(diagonals) == 0 or isscalarlike(diagonals[0]): + diagonals = [np.atleast_1d(diagonals)] + else: + raise ValueError("Different number of diagonals and offsets.") + else: + diagonals = list(map(np.atleast_1d, diagonals)) + + offsets = np.atleast_1d(offsets) + + # Basic check + if len(diagonals) != len(offsets): + raise ValueError("Different number of diagonals and offsets.") + + # Determine shape, if omitted + if shape is None: + m = len(diagonals[0]) + abs(int(offsets[0])) + shape = (m, m) + + # Determine data type, if omitted + if dtype is None: + dtype = np.result_type(*diagonals) + elif dtype is _NoValue: + # This is the old deprecated behavior that uses np.common_type(). + # After the deprecation period, this elif branch can be removed, + # and the default for the `dtype` parameter changed back to `None`. + dtype = np.dtype(np.common_type(*diagonals)) + future_dtype = np.result_type(*diagonals) + if (dtype != future_dtype): + warn( + f"Input has data type {future_dtype}, but the output has been cast " + f"to {dtype}. In the future, the output data type will match the " + "input. To avoid this warning, set the `dtype` parameter to `None` " + "to have the output dtype match the input, or set it to the " + "desired output data type.", + FutureWarning, + skip_file_prefixes=(os.path.dirname(__file__),) + ) + + # Construct data array + m, n = shape + + M = max([min(m + offset, n - offset) + max(0, offset) + for offset in offsets]) + M = max(0, M) + data_arr = np.zeros((len(offsets), M), dtype=dtype) + + K = min(m, n) + + for j, diagonal in enumerate(diagonals): + offset = offsets[j] + k = max(0, offset) + length = min(m + offset, n - offset, K) + if length < 0: + raise ValueError(f"Offset {offset} (index {j}) out of bounds") + try: + data_arr[j, k:k+length] = diagonal[...,:length] + except ValueError as e: + if len(diagonal) != length and len(diagonal) != 1: + raise ValueError( + f"Diagonal length (index {j}: {len(diagonal)} at" + f" offset {offset}) does not agree with array size ({m}, {n})." + ) from e + raise + + return dia_array((data_arr, offsets), shape=(m, n)).asformat(format) + + +def diags(diagonals, offsets=0, shape=None, format=None, dtype=_NoValue): + """ + Construct a sparse matrix from diagonals. + + .. warning:: + + This function returns a sparse matrix -- not a sparse array. + You are encouraged to use `diags_array` to take advantage + of the sparse array functionality. + + Parameters + ---------- + diagonals : sequence of array_like + Sequence of arrays containing the matrix diagonals, + corresponding to `offsets`. + offsets : sequence of int or an int, optional + Diagonals to set (repeated offsets are not allowed): + - k = 0 the main diagonal (default) + - k > 0 the kth upper diagonal + - k < 0 the kth lower diagonal + shape : tuple of int, optional + Shape of the result. If omitted, a square matrix large enough + to contain the diagonals is returned. + format : {"dia", "csr", "csc", "lil", ...}, optional + Matrix format of the result. By default (format=None) an + appropriate sparse matrix format is returned. This choice is + subject to change. + dtype : dtype, optional + Data type of the matrix. If `dtype` is None, the output + data type is determined by the data type of the input diagonals. + + Up until SciPy 1.19, the default behavior will be to return a matrix + with an inexact (floating point) data type. In particular, integer + input will be converted to double precision floating point. This + behavior is deprecated, and in SciPy 1.19, the default behavior + will be changed to return a matrix with the same data type as the + input diagonals. To adopt this behavior before version 1.19, use + `dtype=None`. + + Returns + ------- + new_matrix : dia_matrix + `dia_matrix` holding the values in `diagonals` offset from the main diagonal + as indicated in `offsets`. + + See Also + -------- + spdiags : construct matrix from diagonals + diags_array : construct sparse array instead of sparse matrix + + Notes + ----- + Repeated diagonal offsets are disallowed. + + The result from ``diags`` is the sparse equivalent of:: + + np.diag(diagonals[0], offsets[0]) + + ... + + np.diag(diagonals[k], offsets[k]) + + ``diags`` differs from `dia_matrix` in the way it handles off-diagonals. + Specifically, `dia_matrix` assumes the data input includes padding + (ignored values) at the start/end of the rows for positive/negative + offset, while ``diags`` assumes the input data has no padding. + Each value in the input `diagonals` is used. + + .. versionadded:: 0.11 + + Examples + -------- + >>> from scipy.sparse import diags + >>> diagonals = [[1.0, 2.0, 3.0, 4.0], [1.0, 2.0, 3.0], [1.0, 2.0]] + >>> diags(diagonals, [0, -1, 2]).toarray() + array([[1., 0., 1., 0.], + [1., 2., 0., 2.], + [0., 2., 3., 0.], + [0., 0., 3., 4.]]) + + Broadcasting of scalars is supported (but shape needs to be + specified): + + >>> diags([1.0, -2.0, 1.0], [-1, 0, 1], shape=(4, 4)).toarray() + array([[-2., 1., 0., 0.], + [ 1., -2., 1., 0.], + [ 0., 1., -2., 1.], + [ 0., 0., 1., -2.]]) + + + If only one diagonal is wanted (as in `numpy.diag`), the following + works as well: + + >>> diags([1.0, 2.0, 3.0], 1).toarray() + array([[ 0., 1., 0., 0.], + [ 0., 0., 2., 0.], + [ 0., 0., 0., 3.], + [ 0., 0., 0., 0.]]) + """ + A = diags_array(diagonals, offsets=offsets, shape=shape, dtype=dtype) + return dia_matrix(A).asformat(format) + + +def identity(n, dtype='d', format=None): + """Identity matrix in sparse format. + + Returns an identity matrix with shape ``(n, n)`` using a given + sparse format and dtype. This differs from `eye_array` in + that it has a square shape with ones only on the main diagonal. + It is thus the multiplicative identity. `eye_array` allows + rectangular shapes and the diagonal can be offset from the main one. + + .. warning:: + + This function returns a sparse matrix -- not a sparse array. + You are encouraged to use `eye_array` to take advantage + of the sparse array functionality. + + Parameters + ---------- + n : int + Shape of the identity matrix. + dtype : dtype, optional + Data type of the matrix + format : str, optional + Sparse format of the result, e.g., format="csr", etc. + + Returns + ------- + new_matrix : sparse matrix + A square sparse matrix with ones on the main diagonal and zeros elsewhere. + + See Also + -------- + eye_array : Sparse array of chosen shape with ones on a specified diagonal. + eye : Sparse matrix of chosen shape with ones on a specified diagonal. + + Examples + -------- + >>> import scipy as sp + >>> sp.sparse.identity(3).toarray() + array([[ 1., 0., 0.], + [ 0., 1., 0.], + [ 0., 0., 1.]]) + >>> sp.sparse.identity(3, dtype='int8', format='dia') + + >>> sp.sparse.eye_array(3, dtype='int8', format='dia') + + + """ + return eye(n, n, dtype=dtype, format=format) + + +def eye_array(m, n=None, *, k=0, dtype=float, format=None): + """Sparse array of chosen shape with ones on the kth diagonal and zeros elsewhere. + + Return a sparse array with ones on diagonal. + Specifically a sparse array (m x n) where the kth diagonal + is all ones and everything else is zeros. + + Parameters + ---------- + m : int + Number of rows requested. + n : int, optional + Number of columns. Default: `m`. + k : int, optional + Diagonal to place ones on. Default: 0 (main diagonal). + dtype : dtype, optional + Data type of the array + format : str, optional (default: "dia") + Sparse format of the result, e.g., format="csr", etc. + + Returns + ------- + new_array : sparse array + Sparse array of chosen shape with ones on the kth diagonal and zeros elsewhere. + + Examples + -------- + >>> import numpy as np + >>> import scipy as sp + >>> sp.sparse.eye_array(3).toarray() + array([[ 1., 0., 0.], + [ 0., 1., 0.], + [ 0., 0., 1.]]) + >>> sp.sparse.eye_array(3, dtype=np.int8) + + + """ + # TODO: delete next 15 lines [combine with _eye()] once spmatrix removed + return _eye(m, n, k, dtype, format) + + +def _eye(m, n, k, dtype, format, as_sparray=True): + if as_sparray: + csr_sparse = csr_array + csc_sparse = csc_array + coo_sparse = coo_array + diags_sparse = diags_array + else: + csr_sparse = csr_matrix + csc_sparse = csc_matrix + coo_sparse = coo_matrix + diags_sparse = diags + + if n is None: + n = m + m, n = int(m), int(n) + + if m == n and k == 0: + # fast branch for special formats + if format in ['csr', 'csc']: + idx_dtype = get_index_dtype(maxval=n) + indptr = np.arange(n+1, dtype=idx_dtype) + indices = np.arange(n, dtype=idx_dtype) + data = np.ones(n, dtype=dtype) + cls = {'csr': csr_sparse, 'csc': csc_sparse}[format] + return cls((data, indices, indptr), (n, n)) + + elif format == 'coo': + idx_dtype = get_index_dtype(maxval=n) + row = np.arange(n, dtype=idx_dtype) + col = np.arange(n, dtype=idx_dtype) + data = np.ones(n, dtype=dtype) + return coo_sparse((data, (row, col)), (n, n)) + + data = np.ones((1, max(0, min(m + k, n))), dtype=dtype) + return diags_sparse(data, offsets=[k], shape=(m, n), dtype=dtype).asformat(format) + + +def eye(m, n=None, k=0, dtype=float, format=None): + """Sparse matrix of chosen shape with ones on the kth diagonal and zeros elsewhere. + + Returns a sparse matrix (m x n) where the kth diagonal + is all ones and everything else is zeros. + + .. warning:: + + This function returns a sparse matrix -- not a sparse array. + You are encouraged to use `eye_array` to take advantage + of the sparse array functionality. + + Parameters + ---------- + m : int + Number of rows in the matrix. + n : int, optional + Number of columns. Default: `m`. + k : int, optional + Diagonal to place ones on. Default: 0 (main diagonal). + dtype : dtype, optional + Data type of the matrix. + format : str, optional + Sparse format of the result, e.g., format="csr", etc. + + Returns + ------- + new_matrix : sparse matrix + Sparse matrix of chosen shape with ones on the kth diagonaland zeros elsewhere. + + See Also + -------- + eye_array : Sparse array of chosen shape with ones on a specified diagonal. + + Examples + -------- + >>> import numpy as np + >>> import scipy as sp + >>> sp.sparse.eye(3).toarray() + array([[ 1., 0., 0.], + [ 0., 1., 0.], + [ 0., 0., 1.]]) + >>> sp.sparse.eye(3, dtype=np.int8) + + + """ + return _eye(m, n, k, dtype, format, False) + + +def kron(A, B, format=None): + """Sparse representation of the Kronecker product of `A` and `B`. + + Computes the Kronecker product, a composite sparse array + made of blocks consisting of the second input array multiplied + by each element of the first input array. + + .. warning:: + + `kron` is switching to the sparse array interface. + + For the case where no input arrays are sparse, this function is + switching to returning a sparse array instead of sparse matrix. + Control the sparse return class by making at least one input sparse, + e.g., ``kron(coo_matrix(A), B)``, or ``kron(coo_array(A), B)``. + That removes any deprecation warnings as well. + For more general information about sparrays, see + :ref:`Migration from spmatrix to sparray `. + Handling of this no sparse input case will change no earlier than v1.20. + + Parameters + ---------- + A : sparse or dense array + first array of the product + B : sparse or dense array + second array of the product + format : str, optional (default: 'bsr' or 'coo') + format of the result (e.g. "csr") + If None, choose 'bsr' for relatively dense 2D arrays and 'coo' for others + + Returns + ------- + sparse matrix or array + kronecker product in a sparse format. Returns a sparse matrix unless either + `A` or `B` is a sparse array in which case returns a sparse array. + + Examples + -------- + >>> import numpy as np + >>> import scipy as sp + >>> A = sp.sparse.csr_array(np.array([[0, 2], [5, 0]])) + >>> B = sp.sparse.csr_array(np.array([[1, 2], [3, 4]])) + >>> sp.sparse.kron(A, B).toarray() + array([[ 0, 0, 2, 4], + [ 0, 0, 6, 8], + [ 5, 10, 0, 0], + [15, 20, 0, 0]]) + + >>> sp.sparse.kron(A, [[1, 2], [3, 4]]).toarray() + array([[ 0, 0, 2, 4], + [ 0, 0, 6, 8], + [ 5, 10, 0, 0], + [15, 20, 0, 0]]) + + """ + # TODO: delete this if-clause and replace _sparse with _array when spmatrix removed + if isinstance(A, sparray) or isinstance(B, sparray): + # convert to local variables + bsr_sparse = bsr_array + csr_sparse = csr_array + coo_sparse = coo_array + elif isinstance(A, spmatrix) or isinstance(B, spmatrix): + bsr_sparse = bsr_matrix + csr_sparse = csr_matrix + coo_sparse = coo_matrix + else: # all dense + msg = """`kron` is switching to the sparse array interface. + + For the case where input arrays are numpy arrays, this function is + switching to returning a sparse array instead of sparse matrix. + Recover the sparse matrix return value by making one input a sparse matrix. + For example, kron(coo_matrix(A), B). + Avoid this message for sparse array output by using kron(coo_array(A), B). + For more information, see the spmatrix to sparray migration guide + https://docs.scipy.org/doc/scipy/reference/sparse.migration_to_sparray.html + + This function will be changed no earlier than v1.20. + """ + prefixes = (os.path.dirname(__file__),) + warn(msg, category=DeprecationWarning, skip_file_prefixes=prefixes) + # default when all input are ndarray + bsr_sparse = bsr_matrix + csr_sparse = csr_matrix + coo_sparse = coo_matrix + + B = coo_sparse(B) + + # B is 2D and fairly dense, and format aligns with bsr, compute using BSR + if ( + (format is None or format == "bsr") and + B.ndim == 2 and 2*B.nnz >= math.prod(B.shape) + ): + if not hasattr(A, 'ndim') or A.ndim != 2: + # CSR routes thru COO in constructor so can make COO to check ndim + A = coo_sparse(A) + if A.ndim == 2: + A = csr_sparse(A, copy=True) + output_shape = (A.shape[0]*B.shape[0], A.shape[1]*B.shape[1]) + + if A.nnz == 0 or B.nnz == 0: + # kronecker product is the zero matrix + return coo_sparse(output_shape).asformat(format) + + B = B.toarray() + data = A.data.repeat(B.size).reshape(-1, B.shape[0], B.shape[1]) + data = data * B + + return bsr_sparse((data, A.indices, A.indptr), shape=output_shape) + else: + A = coo_sparse(A) # no copy needed as we use np.repeat below + + # compute using COO (convert to desired format just before return) + if coo_sparse is coo_matrix: + output_shape = (A.shape[0] * B.shape[0], A.shape[1] * B.shape[1]) + ndim_diff = 0 + else: + ndim_diff = A.ndim - B.ndim + A_shape = A.shape if ndim_diff >= 0 else (1,) * (-ndim_diff) + A.shape + B_shape = B.shape if ndim_diff <= 0 else (1,) * ndim_diff + B.shape + output_shape = tuple(a * b for a, b in zip(A_shape, B_shape)) + + if A.nnz == 0 or B.nnz == 0: + # kronecker product is the zero matrix + return coo_sparse(output_shape).asformat(format) + + # expand entries of a into blocks + data = A.data.repeat(B.nnz) + idx_dtype = get_index_dtype(A.coords, maxval=max(output_shape)) + coords = [np.asarray(co, dtype=idx_dtype).repeat(B.nnz) for co in A.coords] + if ndim_diff < 0: + new_co = np.zeros_like(coords[0]) + coords = [new_co] + [new_co.copy() for _ in range(-ndim_diff - 1)] + coords + + # The last B.ndim coords need to be updated. Any previous columns in B are from + # prepending (1,)s, so coord from A is what we need (B coord axis is all 0) + for co, B_shape_i in zip(coords[-B.ndim:], B.shape): + co *= B_shape_i + + # increment block indices + coords[-B.ndim:] = [(co.reshape(-1, B.nnz) + Bco).ravel() + for co, Bco in zip(coords[-B.ndim:], B.coords)] + # compute block entries + data = (data.reshape(-1, B.nnz) * B.data).ravel() + + return coo_sparse((data, tuple(coords)), shape=output_shape).asformat(format) + + +def kronsum(A, B, format=None): + """Kronecker sum of square sparse matrices `A` and `B`. + + Kronecker sum of two sparse matrices is a sum of two Kronecker + products ``kron(I_n,A) + kron(B,I_m)`` where `A` has shape ``(m, m)`` + and `B` has shape ``(n, n)`` and ``I_m`` and ``I_n`` are identity matrices + of shape ``(m, m)`` and ``(n, n)``, respectively. + + .. warning:: + + `kronsum` is switching to the sparse array interface. + + For the case where no input arrays are sparse, this function is + switching to returning a sparse array instead of sparse matrix. + Control the sparse return class by making at least one input sparse, + e.g., ``kronsum(coo_matrix(A), B)``, or ``kronsum(coo_array(A), B)``. + That removes any deprecation warnings as well. + For more general information about sparrays, see + :ref:`Migration from spmatrix to sparray `. + Handling of this no sparse input case will change no earlier than v1.20. + + Parameters + ---------- + A : sparse matrix or array + Square matrix + B : sparse array or array + Square matrix + format : str + format of the result (e.g. "csr") + + Returns + ------- + sparse matrix or array + kronecker sum in a sparse format. Returns a sparse matrix unless either + `A` or `B` is a sparse array in which case returns a sparse array. + + Examples + -------- + `kronsum` can be used to construct a finite difference discretization of the 2D + Laplacian from a 1D discretization. + + >>> from scipy.sparse import diags_array, kronsum + >>> from matplotlib import pyplot as plt + >>> import numpy as np + >>> ex = np.ones(10) + >>> D_x = diags_array([ex, -ex[1:]], offsets=[0, -1]) # 1D first derivative + >>> D_xx = D_x.T @ D_x # 1D second derivative + >>> L = kronsum(D_xx, D_xx) # 2D Laplacian + >>> plt.spy(L.toarray()) + >>> plt.show() + + """ + # TODO: delete this if-clause and replace _sparse with _array when spmatrix removed + if isinstance(A, sparray) or isinstance(B, sparray): + # convert to local variables + coo_sparse = coo_array + identity_sparse = eye_array + elif isinstance(A, spmatrix) or isinstance(B, spmatrix): + coo_sparse = coo_matrix + identity_sparse = identity + else: # all dense + msg = """`kronsum` is switching to the sparse array interface. + + For the case where input arrays are numpy arrays, this function is + switching to returning a sparse array instead of sparse matrix. + Recover the sparse matrix return value by making one input a sparse matrix. + For example, kronsum(coo_matrix(A), B). + Avoid this message for sparse array output by using kronsum(coo_array(A), B). + For more information, see the spmatrix to sparray migration guide + https://docs.scipy.org/doc/scipy/reference/sparse.migration_to_sparray.html + + This function will be changed no earlier than v1.20. + """ + prefixes = (os.path.dirname(__file__),) + warn(msg, category=DeprecationWarning, skip_file_prefixes=prefixes) + # default when all input are ndarray + coo_sparse = coo_matrix + identity_sparse = identity + + A = coo_sparse(A) + B = coo_sparse(B) + + if A.ndim != 2: + raise ValueError(f"kronsum requires 2D inputs. `A` is {A.ndim}D.") + if B.ndim != 2: + raise ValueError(f"kronsum requires 2D inputs. `B` is {B.ndim}D.") + if A.shape[0] != A.shape[1]: + raise ValueError('A is not square') + if B.shape[0] != B.shape[1]: + raise ValueError('B is not square') + + dtype = upcast(A.dtype, B.dtype) + + I_n = identity_sparse(A.shape[0], dtype=dtype) + I_m = identity_sparse(B.shape[0], dtype=dtype) + L = kron(I_m, A, format='coo') + R = kron(B, I_n, format='coo') + + return (L + R).asformat(format) + + +def _compressed_sparse_stack(blocks, axis, return_spmatrix): + """ + Stacking fast path for CSR/CSC matrices or arrays + (i) vstack for CSR, (ii) hstack for CSC. + """ + other_axis = 1 if axis == 0 else 0 + data = np.concatenate([b.data for b in blocks]) + constant_dim = blocks[0]._shape_as_2d[other_axis] + idx_dtype = get_index_dtype(arrays=[b.indptr for b in blocks], + maxval=max(data.size, constant_dim)) + indices = np.empty(data.size, dtype=idx_dtype) + indptr = np.empty(sum(b._shape_as_2d[axis] for b in blocks) + 1, dtype=idx_dtype) + last_indptr = idx_dtype(0) + sum_dim = 0 + sum_indices = 0 + for b in blocks: + if b._shape_as_2d[other_axis] != constant_dim: + raise ValueError(f'incompatible dimensions for axis {other_axis}') + indices[sum_indices:sum_indices+b.indices.size] = b.indices + sum_indices += b.indices.size + idxs = slice(sum_dim, sum_dim + b._shape_as_2d[axis]) + indptr[idxs] = b.indptr[:-1] + indptr[idxs] += last_indptr + sum_dim += b._shape_as_2d[axis] + last_indptr += b.indptr[-1] + indptr[-1] = last_indptr + # TODO remove this if-structure when sparse matrices removed + if return_spmatrix: + if axis == 0: + return csr_matrix((data, indices, indptr), + shape=(sum_dim, constant_dim)) + else: + return csc_matrix((data, indices, indptr), + shape=(constant_dim, sum_dim)) + + if axis == 0: + return csr_array((data, indices, indptr), + shape=(sum_dim, constant_dim)) + else: + return csc_array((data, indices, indptr), + shape=(constant_dim, sum_dim)) + + +def _stack_along_minor_axis(blocks, axis): + """ + Stacking fast path for CSR/CSC matrices along the minor axis + (i) hstack for CSR, (ii) vstack for CSC. + """ + n_blocks = len(blocks) + if n_blocks == 0: + raise ValueError('Missing block matrices') + + if n_blocks == 1: + return blocks[0] + + # check for incompatible dimensions + other_axis = 1 if axis == 0 else 0 + other_axis_dims = {b._shape_as_2d[other_axis] for b in blocks} + if len(other_axis_dims) > 1: + raise ValueError(f'Mismatching dimensions along axis {other_axis}: ' + f'{other_axis_dims}') + constant_dim, = other_axis_dims + + # Do the stacking + indptr_list = [b.indptr for b in blocks] + data_cat = np.concatenate([b.data for b in blocks]) + + # Need to check if any indices/indptr, would be too large post- + # concatenation for np.int32: + # - The max value of indices is the output array's stacking-axis length - 1 + # - The max value in indptr is the number of non-zero entries. This is + # exceedingly unlikely to require int64, but is checked out of an + # abundance of caution. + sum_dim = sum(b._shape_as_2d[axis] for b in blocks) + nnz = sum(len(b.indices) for b in blocks) + idx_dtype = get_index_dtype(indptr_list, maxval=max(sum_dim - 1, nnz)) + stack_dim_cat = np.array([b._shape_as_2d[axis] for b in blocks], dtype=idx_dtype) + if data_cat.size > 0: + indptr_cat = np.concatenate(indptr_list, dtype=idx_dtype) + indices_cat = np.concatenate([b.indices for b in blocks], dtype=idx_dtype) + indptr = np.empty(constant_dim + 1, dtype=idx_dtype) + indices = np.empty_like(indices_cat) + data = np.empty_like(data_cat) + csr_hstack(n_blocks, constant_dim, stack_dim_cat, + indptr_cat, indices_cat, data_cat, + indptr, indices, data) + else: + indptr = np.zeros(constant_dim + 1, dtype=idx_dtype) + indices = np.empty(0, dtype=idx_dtype) + data = np.empty(0, dtype=data_cat.dtype) + + if axis == 0: + return blocks[0]._csc_container((data, indices, indptr), + shape=(sum_dim, constant_dim)) + else: + return blocks[0]._csr_container((data, indices, indptr), + shape=(constant_dim, sum_dim)) + + +def hstack(blocks, format=None, dtype=None): + """ + Stack sparse matrices horizontally (column wise). + + Parameters + ---------- + blocks : sequence of sparse matrices or arrays + sequence of sparse matrices with compatible shapes + format : str + sparse format of the result (e.g., "csr") + by default an appropriate sparse matrix format is returned. + This choice is subject to change. + dtype : dtype, optional + The data-type of the output matrix. If not given, the dtype is + determined from that of `blocks`. + + Returns + ------- + new_array : sparse matrix or array + If any block in blocks is a sparse array, return a sparse array. + Otherwise return a sparse matrix. + + If you want a sparse array built from blocks that are not sparse + arrays, use ``block(hstack(blocks))`` or convert one block + e.g. ``blocks[0] = csr_array(blocks[0])``. + + See Also + -------- + vstack : stack sparse matrices vertically (row wise) + + Examples + -------- + >>> from scipy.sparse import coo_matrix, hstack + >>> A = coo_matrix([[1, 2], [3, 4]]) + >>> B = coo_matrix([[5], [6]]) + >>> hstack([A,B]).toarray() + array([[1, 2, 5], + [3, 4, 6]]) + + """ + blocks = np.asarray(blocks, dtype='object') + if any(isinstance(b, sparray) for b in blocks.flat): + return _block([blocks], format, dtype) + else: + return _block([blocks], format, dtype, return_spmatrix=True) + + +def vstack(blocks, format=None, dtype=None): + """ + Stack sparse arrays vertically (row wise). + + Parameters + ---------- + blocks : sequence of sparse matrices or arrays + sequence of sparse arrays with compatible shapes + format : str, optional + sparse format of the result (e.g., "csr") + by default an appropriate sparse array format is returned. + This choice is subject to change. + dtype : dtype, optional + The data-type of the output array. If not given, the dtype is + determined from that of `blocks`. + + Returns + ------- + new_array : sparse matrix or array + If any block in blocks is a sparse array, return a sparse array. + Otherwise return a sparse matrix. + + If you want a sparse array built from blocks that are not sparse + arrays, use ``block(vstack(blocks))`` or convert one block + e.g. ``blocks[0] = csr_array(blocks[0])``. + + See Also + -------- + hstack : stack sparse matrices horizontally (column wise) + + Examples + -------- + >>> from scipy.sparse import coo_array, vstack + >>> A = coo_array([[1, 2], [3, 4]]) + >>> B = coo_array([[5, 6]]) + >>> vstack([A, B]).toarray() + array([[1, 2], + [3, 4], + [5, 6]]) + + """ + blocks = np.asarray(blocks, dtype='object') + if any(isinstance(b, sparray) for b in blocks.flat): + return _block([[b] for b in blocks], format, dtype) + else: + return _block([[b] for b in blocks], format, dtype, return_spmatrix=True) + + +def bmat(blocks, format=None, dtype=None): + """ + Build a sparse array or matrix from sparse sub-blocks. + + Note: `block_array` is preferred over ``bmat``. They are the same function + except that ``bmat`` returns a deprecated sparse matrix when none of the + inputs are sparse arrays. + + .. warning:: + + This function returns a sparse matrix when no inputs are sparse arrays. + You are encouraged to use `block_array` to take advantage + of the sparse array functionality. + + Parameters + ---------- + blocks : array_like + Grid of sparse matrices with compatible shapes. + An entry of None implies an all-zero matrix. + format : {'bsr', 'coo', 'csc', 'csr', 'dia', 'dok', 'lil'}, optional + The sparse format of the result (e.g. "csr"). By default an + appropriate sparse matrix format is returned. + This choice is subject to change. + dtype : dtype, optional + The data-type of the output matrix. If not given, the dtype is + determined from that of `blocks`. + + Returns + ------- + bmat : sparse matrix or array + If any block in blocks is a sparse array, return a sparse array. + Otherwise return a sparse matrix. + + If you want a sparse array built from blocks that are not sparse + arrays, use ``block_array()``. + + See Also + -------- + block_array + + Examples + -------- + >>> from scipy.sparse import coo_array, bmat + >>> A = coo_array([[1, 2], [3, 4]]) + >>> B = coo_array([[5], [6]]) + >>> C = coo_array([[7]]) + >>> bmat([[A, B], [None, C]]).toarray() + array([[1, 2, 5], + [3, 4, 6], + [0, 0, 7]]) + + >>> bmat([[A, None], [None, C]]).toarray() + array([[1, 2, 0], + [3, 4, 0], + [0, 0, 7]]) + + """ + blocks = np.asarray(blocks, dtype='object') + if any(isinstance(b, sparray) for b in blocks.flat): + return _block(blocks, format, dtype) + else: + return _block(blocks, format, dtype, return_spmatrix=True) + + +def block_array(blocks, *, format=None, dtype=None): + """ + Build a sparse array from sparse sub-blocks. + + Parameters + ---------- + blocks : array_like + Grid of sparse arrays with compatible shapes. + An entry of None implies an all-zero array. + format : {'bsr', 'coo', 'csc', 'csr', 'dia', 'dok', 'lil'}, optional + The sparse format of the result (e.g. "csr"). By default an + appropriate sparse array format is returned. + This choice is subject to change. + dtype : dtype, optional + The data-type of the output array. If not given, the dtype is + determined from that of `blocks`. + + Returns + ------- + block : sparse array + Sparse array formed from specified blocks. + + See Also + -------- + block_diag : specify blocks along the main diagonals + diags : specify (possibly offset) diagonals + + Examples + -------- + >>> from scipy.sparse import coo_array, block_array + >>> A = coo_array([[1, 2], [3, 4]]) + >>> B = coo_array([[5], [6]]) + >>> C = coo_array([[7]]) + >>> block_array([[A, B], [None, C]]).toarray() + array([[1, 2, 5], + [3, 4, 6], + [0, 0, 7]]) + + >>> block_array([[A, None], [None, C]]).toarray() + array([[1, 2, 0], + [3, 4, 0], + [0, 0, 7]]) + + """ + return _block(blocks, format, dtype) + + +def _block(blocks, format, dtype, return_spmatrix=False): + blocks = np.asarray(blocks, dtype='object') + + if blocks.ndim != 2: + raise ValueError('blocks must be 2-D, and some must be sparse') + + M,N = blocks.shape + + # check for fast path cases + if (format in (None, 'csr') and + all(issparse(b) and b.format == 'csr' for b in blocks.flat) + ): + if N > 1: + # stack along columns (axis 1): must have shape (M, 1) + blocks = [[_stack_along_minor_axis(blocks[b, :], 1)] for b in range(M)] + blocks = np.asarray(blocks, dtype='object') + + # stack along rows (axis 0): + A = _compressed_sparse_stack(blocks[:, 0], 0, return_spmatrix) + if dtype is not None: + A = A.astype(dtype, copy=False) + return A + elif (format in (None, 'csc') and + all(issparse(b) and b.format == 'csc' for b in blocks.flat) + ): + if M > 1: + # stack along rows (axis 0): must have shape (1, N) + blocks = [[_stack_along_minor_axis(blocks[:, b], 0) for b in range(N)]] + blocks = np.asarray(blocks, dtype='object') + + # stack along columns (axis 1): + A = _compressed_sparse_stack(blocks[0, :], 1, return_spmatrix) + if dtype is not None: + A = A.astype(dtype, copy=False) + return A + + block_mask = np.zeros(blocks.shape, dtype=bool) + brow_lengths = np.zeros(M, dtype=np.int64) + bcol_lengths = np.zeros(N, dtype=np.int64) + + # convert everything to COO format + for i in range(M): + for j in range(N): + if blocks[i,j] is not None: + A = coo_array(blocks[i,j]) + blocks[i,j] = A + block_mask[i,j] = True + + if brow_lengths[i] == 0: + brow_lengths[i] = A._shape_as_2d[0] + elif brow_lengths[i] != A._shape_as_2d[0]: + msg = (f'blocks[{i},:] has incompatible row dimensions. ' + f'Got blocks[{i},{j}].shape[0] == {A._shape_as_2d[0]}, ' + f'expected {brow_lengths[i]}.') + raise ValueError(msg) + + if bcol_lengths[j] == 0: + bcol_lengths[j] = A._shape_as_2d[1] + elif bcol_lengths[j] != A._shape_as_2d[1]: + msg = (f'blocks[:,{j}] has incompatible column ' + f'dimensions. ' + f'Got blocks[{i},{j}].shape[1] == {A._shape_as_2d[1]}, ' + f'expected {bcol_lengths[j]}.') + raise ValueError(msg) + + nnz = sum(block.nnz for block in blocks[block_mask]) + if dtype is None: + all_dtypes = [blk.dtype for blk in blocks[block_mask]] + dtype = upcast(*all_dtypes) if all_dtypes else None + + row_offsets = np.append(0, np.cumsum(brow_lengths)) + col_offsets = np.append(0, np.cumsum(bcol_lengths)) + + shape = (row_offsets[-1], col_offsets[-1]) + + data = np.empty(nnz, dtype=dtype) + idx_dtype = get_index_dtype([b.coords[0] for b in blocks[block_mask]], + maxval=max(shape)) + row = np.empty(nnz, dtype=idx_dtype) + col = np.empty(nnz, dtype=idx_dtype) + + nnz = 0 + ii, jj = np.nonzero(block_mask) + for i, j in zip(ii, jj): + B = blocks[i, j] + idx = slice(nnz, nnz + B.nnz) + data[idx] = B.data + np.add(B.row, row_offsets[i], out=row[idx], dtype=idx_dtype) + np.add(B.col, col_offsets[j], out=col[idx], dtype=idx_dtype) + nnz += B.nnz + + if return_spmatrix: + return coo_matrix((data, (row, col)), shape=shape).asformat(format) + return coo_array((data, (row, col)), shape=shape).asformat(format) + + +def block_diag(mats, format=None, dtype=None): + """ + Build a block diagonal sparse matrix or array from provided matrices. + + .. warning:: + + `block_diag` is switching to the sparse array interface. + + For the case where no input arrays are sparse, this function is + switching to returning a sparse array instead of sparse matrix. + Control the sparse return class by making at least one input sparse, + e.g., ``block_diag([coo_matrix(A), B])``, or ``block_diag([coo_array(A), B])``. + That removes any deprecation warnings as well. + For more general information about sparrays, see + :ref:`Migration from spmatrix to sparray `. + Handling of this no sparse input case will change no earlier than v1.20. + + Parameters + ---------- + mats : sequence of matrices or arrays + Input matrices or arrays. + format : str, optional + The sparse format of the result (e.g., "csr"). If not given, the result + is returned in "coo" format. + dtype : dtype specifier, optional + The data-type of the output. If not given, the dtype is + determined from that of `blocks`. + + Returns + ------- + res : sparse matrix or array + If at least one input is a sparse array, the output is a sparse array. + Otherwise the output is a sparse matrix. + + See Also + -------- + block_array + diags_array + + Notes + ----- + + .. versionadded:: 0.11.0 + + Examples + -------- + >>> from scipy.sparse import coo_array, block_diag + >>> A = coo_array([[1, 2], [3, 4]]) + >>> B = coo_array([[5], [6]]) + >>> C = coo_array([[7]]) + >>> block_diag((A, B, C)).toarray() + array([[1, 2, 0, 0], + [3, 4, 0, 0], + [0, 0, 5, 0], + [0, 0, 6, 0], + [0, 0, 0, 7]]) + """ + if any(isinstance(a, sparray) for a in mats): + container = coo_array + elif any(isinstance(a, spmatrix) for a in mats): + container = coo_matrix + else: # all dense + msg = """`block_diag` is switching to the sparse array interface. + + For the case where input arrays are numpy arrays, this function is + switching to returning a sparse array instead of sparse matrix. + Recover the sparse matrix return value by making one input a sparse matrix. + For example, block_diag([coo_matrix(A), B]). + Avoid this message for sparse array output using block_diag([coo_array(A), B]). + For more information, see the spmatrix to sparray migration guide + https://docs.scipy.org/doc/scipy/reference/sparse.migration_to_sparray.html + + This function will be changed no earlier than v1.20. + """ + prefixes = (os.path.dirname(__file__),) + warn(msg, category=DeprecationWarning, skip_file_prefixes=prefixes) + + # default when all input are ndarray + container = coo_matrix + + row = [] + col = [] + data = [] + idx_arrays = [] # track idx_dtype of incoming sparse arrays + r_idx = 0 + c_idx = 0 + for a in mats: + if isinstance(a, (list | numbers.Number)): + a = coo_array(np.atleast_2d(a)) + if issparse(a): + a = a.tocoo() + if not idx_arrays and a.coords[0].dtype == np.int64: + idx_arrays.append(a.coords[0]) + nrows, ncols = a._shape_as_2d + row.append(a.row + r_idx) + col.append(a.col + c_idx) + data.append(a.data) + else: + nrows, ncols = a.shape + a_row, a_col = np.divmod(np.arange(nrows*ncols), ncols) + row.append(a_row + r_idx) + col.append(a_col + c_idx) + data.append(a.ravel()) + r_idx += nrows + c_idx += ncols + idx_dtype = get_index_dtype(idx_arrays, maxval=max(r_idx, c_idx)) + row = np.concatenate(row, dtype=idx_dtype) + col = np.concatenate(col, dtype=idx_dtype) + data = np.concatenate(data) + new_shape = (r_idx, c_idx) + + return container((data, (row, col)), shape=new_shape, dtype=dtype).asformat(format) + + +@_transition_to_rng("random_state") +def random_array(shape, *, density=0.01, format='coo', dtype=None, + rng=None, data_sampler=None): + """Return a sparse array of uniformly random numbers in [0, 1). + + Returns a sparse array with the given shape and density + where values are generated uniformly randomly in the range [0, 1). + + Parameters + ---------- + shape : tuple of int + shape of the array. + density : real, optional (default: 0.01) + density of the generated matrix: density equal to one means a full + matrix, density of 0 means a matrix with no non-zero items. + format : str, optional (default: 'coo') + sparse matrix format. + dtype : dtype, optional (default: np.float64) + type of the returned matrix values. + rng : `numpy.random.Generator`, optional + Pseudorandom number generator state. When `rng` is None, a new + `numpy.random.Generator` is created using entropy from the + operating system. Types other than `numpy.random.Generator` are + passed to `numpy.random.default_rng` to instantiate a ``Generator``. + + This random state will be used for sampling ``indices`` (the sparsity + structure), and by default for the data values too (see `data_sampler`). + data_sampler : callable, optional (default depends on dtype) + Sampler of random data values with keyword arg ``size``. + This function should take a single keyword argument ``size`` specifying + the length of its returned ndarray. It is used to generate the nonzero + values in the matrix after the locations of those values are chosen. + By default, uniform [0, 1) random values are used unless `dtype` is + an integer (default uniform integers from that dtype) or + complex (default uniform over the unit square in the complex plane). + For these, the `rng` is used e.g. ``rng.uniform(size=size)``. + + Returns + ------- + res : sparse array + Random sparse array. + + Examples + -------- + + Passing a ``np.random.Generator`` instance for better performance: + + >>> import numpy as np + >>> import scipy as sp + >>> rng = np.random.default_rng() + + Default sampling uniformly from [0, 1): + + >>> S = sp.sparse.random_array((3, 4), density=0.25, rng=rng) + + Providing a sampler for the values: + + >>> rvs = sp.stats.poisson(25, loc=10).rvs + >>> S = sp.sparse.random_array((3, 4), density=0.25, + ... rng=rng, data_sampler=rvs) + >>> S.toarray() + array([[ 36., 0., 33., 0.], # random + [ 0., 0., 0., 0.], + [ 0., 0., 36., 0.]]) + + Providing a sampler for uint values: + + >>> def random_uint32_to_100(size=None): + ... return rng.integers(100, size=size, dtype=np.uint32) + >>> S = sp.sparse.random_array((3, 4), density=0.25, rng=rng, + ... data_sampler=random_uint32_to_100) + + Building a custom distribution. + This example builds a squared normal from np.random: + + >>> def np_normal_squared(size=None, rng=rng): + ... return rng.standard_normal(size) ** 2 + >>> S = sp.sparse.random_array((3, 4), density=0.25, rng=rng, + ... data_sampler=np_normal_squared) + + Or we can build it from sp.stats style rvs functions: + + >>> def sp_stats_normal_squared(size=None, rng=rng): + ... std_normal = sp.stats.distributions.norm_gen().rvs + ... return std_normal(size=size, random_state=rng) ** 2 + >>> S = sp.sparse.random_array((3, 4), density=0.25, rng=rng, + ... data_sampler=sp_stats_normal_squared) + + Or we can subclass sp.stats rv_continuous or rv_discrete: + + >>> class NormalSquared(sp.stats.rv_continuous): + ... def _rvs(self, size=None, random_state=rng): + ... return rng.standard_normal(size) ** 2 + >>> X = NormalSquared() + >>> Y = X().rvs + >>> S = sp.sparse.random_array((3, 4), density=0.25, + ... rng=rng, data_sampler=Y) + """ + data, ind = _random(shape, density, format, dtype, rng, data_sampler) + + # downcast, if safe, before calling coo_constructor + idx_dtype = get_index_dtype(maxval=max(shape)) + ind = tuple(np.asarray(co, dtype=idx_dtype) for co in ind) + return coo_array((data, ind), shape=shape).asformat(format) + + +def _random(shape, density=0.01, format=None, dtype=None, + rng=None, data_sampler=None): + if density < 0 or density > 1: + raise ValueError("density expected to be 0 <= density <= 1") + + tot_prod = math.prod(shape) # use `math` for when prod is >= 2**64 + + # Number of non zero values + size = int(round(density * tot_prod)) + + rng = check_random_state(rng) + + if data_sampler is None: + if np.issubdtype(dtype, np.integer): + def data_sampler(size): + return rng_integers(rng, + np.iinfo(dtype).min, + np.iinfo(dtype).max, + size, + dtype=dtype) + elif np.issubdtype(dtype, np.complexfloating): + def data_sampler(size): + return (rng.uniform(size=size) + + rng.uniform(size=size) * 1j) + else: + data_sampler = rng.uniform + + idx_dtype = get_index_dtype(maxval=max(shape)) + # rng.choice uses int64 if first arg is an int + if tot_prod <= np.iinfo(np.int64).max: + raveled_ind = rng.choice(tot_prod, size=size, replace=False) + ind = np.unravel_index(raveled_ind, shape=shape, order='F') + ind = tuple(np.asarray(co, idx_dtype) for co in ind) + else: + # for ravel indices bigger than dtype max, use sets to remove duplicates + ndim = len(shape) + seen = set() + while len(seen) < size: + dsize = size - len(seen) + seen.update(map(tuple, rng_integers(rng, shape, size=(dsize, ndim)))) + ind = tuple(np.array(list(seen), dtype=idx_dtype).T) + + # size kwarg allows eg data_sampler=partial(np.random.poisson, lam=5) + vals = data_sampler(size=size).astype(dtype, copy=False) + return vals, ind + + +@_transition_to_rng("random_state", position_num=5) +def random(m, n, density=0.01, format='coo', dtype=None, + rng=None, data_rvs=None): + """Generate a sparse matrix of the given shape and density with randomly + distributed values. + + .. warning:: + + This function returns a sparse matrix -- not a sparse array. + You are encouraged to use `random_array` to take advantage of the + sparse array functionality. + + Parameters + ---------- + m, n : int + shape of the matrix + density : real, optional + density of the generated matrix: density equal to one means a full + matrix, density of 0 means a matrix with no non-zero items. + format : str, optional + sparse matrix format. + dtype : dtype, optional + type of the returned matrix values. + rng : `numpy.random.Generator`, optional + Pseudorandom number generator state. When `rng` is None, a new + `numpy.random.Generator` is created using entropy from the + operating system. Types other than `numpy.random.Generator` are + passed to `numpy.random.default_rng` to instantiate a ``Generator``. + + This random state will be used for sampling the sparsity structure, but + not necessarily for sampling the values of the structurally nonzero + entries of the matrix. + data_rvs : callable, optional + Samples a requested number of random values. + This function should take a single argument specifying the length + of the ndarray that it will return. The structurally nonzero entries + of the sparse random matrix will be taken from the array sampled + by this function. By default, uniform [0, 1) random values will be + sampled using the same random state as is used for sampling + the sparsity structure. + + Returns + ------- + res : sparse matrix + Random sparse matrix. + + See Also + -------- + random_array : constructs sparse arrays instead of sparse matrices + + Examples + -------- + + Passing a ``np.random.Generator`` instance for better performance: + + >>> import scipy as sp + >>> import numpy as np + >>> rng = np.random.default_rng() + >>> S = sp.sparse.random(3, 4, density=0.25, rng=rng) + + Providing a sampler for the values: + + >>> rvs = sp.stats.poisson(25, loc=10).rvs + >>> S = sp.sparse.random(3, 4, density=0.25, rng=rng, data_rvs=rvs) + >>> S.toarray() + array([[ 36., 0., 33., 0.], # random + [ 0., 0., 0., 0.], + [ 0., 0., 36., 0.]]) + + Building a custom distribution. + This example builds a squared normal from np.random: + + >>> def np_normal_squared(size=None, rng=rng): + ... return rng.standard_normal(size) ** 2 + >>> S = sp.sparse.random(3, 4, density=0.25, rng=rng, + ... data_rvs=np_normal_squared) + + Or we can build it from sp.stats style rvs functions: + + >>> def sp_stats_normal_squared(size=None, rng=rng): + ... std_normal = sp.stats.distributions.norm_gen().rvs + ... return std_normal(size=size, random_state=rng) ** 2 + >>> S = sp.sparse.random(3, 4, density=0.25, rng=rng, + ... data_rvs=sp_stats_normal_squared) + + Or we can subclass sp.stats rv_continuous or rv_discrete: + + >>> class NormalSquared(sp.stats.rv_continuous): + ... def _rvs(self, size=None, random_state=rng): + ... return rng.standard_normal(size) ** 2 + >>> X = NormalSquared() + >>> Y = X() # get a frozen version of the distribution + >>> S = sp.sparse.random(3, 4, density=0.25, rng=rng, data_rvs=Y.rvs) + """ + if n is None: + n = m + m, n = int(m), int(n) + # make keyword syntax work for data_rvs e.g. data_rvs(size=7) + if data_rvs is not None: + def data_rvs_kw(size): + return data_rvs(size) + else: + data_rvs_kw = None + vals, ind = _random((m, n), density, format, dtype, rng, data_rvs_kw) + return coo_matrix((vals, ind), shape=(m, n)).asformat(format) + + +@_transition_to_rng("random_state", position_num=5) +def rand(m, n, density=0.01, format="coo", dtype=None, rng=None): + """Generate a sparse matrix of the given shape and density with uniformly + distributed values. + + .. warning:: + + This function returns a sparse matrix -- not a sparse array. + You are encouraged to use `random_array` to take advantage + of the sparse array functionality. + + Parameters + ---------- + m, n : int + shape of the matrix + density : real, optional + density of the generated matrix: density equal to one means a full + matrix, density of 0 means a matrix with no non-zero items. + format : str, optional + sparse matrix format. + dtype : dtype, optional + type of the returned matrix values. + rng : `numpy.random.Generator`, optional + Pseudorandom number generator state. When `rng` is None, a new + `numpy.random.Generator` is created using entropy from the + operating system. Types other than `numpy.random.Generator` are + passed to `numpy.random.default_rng` to instantiate a ``Generator``. + + Returns + ------- + res : sparse matrix + Random sparse matrix. + + Notes + ----- + Only float types are supported for now. + + See Also + -------- + random : Similar function allowing a custom random data sampler + random_array : Similar to random() but returns a sparse array + + Examples + -------- + >>> from scipy.sparse import rand + >>> matrix = rand(3, 4, density=0.25, format="csr", rng=42) + >>> matrix + + >>> matrix.toarray() + array([[0.05641158, 0. , 0. , 0.65088847], # random + [0. , 0. , 0. , 0.14286682], + [0. , 0. , 0. , 0. ]]) + + """ + return random(m, n, density, format, dtype, rng) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/_coo.py b/venv/lib/python3.12/site-packages/scipy/sparse/_coo.py new file mode 100644 index 0000000000000000000000000000000000000000..a27f5e0ed104ccf9a9360eb0d60508349421278a --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/_coo.py @@ -0,0 +1,1948 @@ +""" A sparse matrix in COOrdinate or 'triplet' format""" + +__docformat__ = "restructuredtext en" + +__all__ = ['coo_array', 'coo_matrix', 'isspmatrix_coo'] + +import math +from warnings import warn + +import numpy as np + +from .._lib._util import copy_if_needed +from ._matrix import spmatrix +from ._sparsetools import (coo_tocsr, coo_todense, coo_todense_nd, + coo_matvec, coo_matvec_nd, coo_matmat_dense, + coo_matmat_dense_nd) +from ._base import issparse, SparseEfficiencyWarning, _spbase, sparray +from ._data import _data_matrix, _minmax_mixin +from ._sputils import (upcast_char, to_native, isshape, getdtype, + getdata, downcast_intp_index, get_index_dtype, + check_shape, isscalarlike, + isintlike, isdense) +from ._index import _validate_indices, _broadcast_arrays + +import operator + + +class _coo_base(_data_matrix, _minmax_mixin): + _format = 'coo' + _allow_nd = tuple(range(1, 65)) + + def __init__(self, arg1, shape=None, dtype=None, copy=False, *, maxprint=None): + _data_matrix.__init__(self, arg1, maxprint=maxprint) + if not copy: + copy = copy_if_needed + + if isinstance(arg1, tuple): + if isshape(arg1, allow_nd=self._allow_nd): + self._shape = check_shape(arg1, allow_nd=self._allow_nd) + idx_dtype = self._get_index_dtype(maxval=max(self._shape)) + data_dtype = getdtype(dtype, default=float) + self.coords = tuple(np.array([], dtype=idx_dtype) + for _ in range(len(self._shape))) + self.data = np.array([], dtype=data_dtype) + self.has_canonical_format = True + else: + try: + obj, coords = arg1 + except (TypeError, ValueError) as e: + raise TypeError('invalid input format') from e + + if shape is None: + if any(len(idx) == 0 for idx in coords): + raise ValueError('cannot infer dimensions from zero ' + 'sized index arrays') + shape = tuple(operator.index(np.max(idx)) + 1 + for idx in coords) + self._shape = check_shape(shape, allow_nd=self._allow_nd) + idx_dtype = self._get_index_dtype(coords, + maxval=max(self.shape), + check_contents=True) + self.coords = tuple(np.array(idx, copy=copy, dtype=idx_dtype) + for idx in coords) + self.data = getdata(obj, copy=copy, dtype=dtype) + self.has_canonical_format = False + else: + if issparse(arg1): + if arg1.format == self.format and copy: + self.coords = tuple(idx.copy() for idx in arg1.coords) + self.data = arg1.data.astype(getdtype(dtype, arg1)) # copy=True + self._shape = check_shape(arg1.shape, allow_nd=self._allow_nd) + self.has_canonical_format = arg1.has_canonical_format + else: + coo = arg1.tocoo(copy=copy) + self.coords = tuple(coo.coords) + self.data = coo.data.astype(getdtype(dtype, coo), copy=False) + self._shape = check_shape(coo.shape, allow_nd=self._allow_nd) + self.has_canonical_format = False + else: + # dense argument + M = np.asarray(arg1) + if not isinstance(self, sparray): + M = np.atleast_2d(M) + if M.ndim != 2: + raise TypeError(f'expected 2D array or matrix, not {M.ndim}D') + + self._shape = check_shape(M.shape, allow_nd=self._allow_nd) + if shape is not None: + if check_shape(shape, allow_nd=self._allow_nd) != self._shape: + message = f'inconsistent shapes: {shape} != {self._shape}' + raise ValueError(message) + + index_dtype = self._get_index_dtype(maxval=max(self._shape)) + coords = M.nonzero() + self.coords = tuple(idx.astype(index_dtype, copy=False) + for idx in coords) + self.data = getdata(M[coords], copy=copy, dtype=dtype) + self.has_canonical_format = True + + if len(self._shape) > 2: + self.coords = tuple(idx.astype(np.int64, copy=False) for idx in self.coords) + + self._check() + + @property + def row(self): + if self.ndim > 1: + return self.coords[-2] + result = np.zeros_like(self.col) + result.setflags(write=False) + return result + + + @row.setter + def row(self, new_row): + if self.ndim < 2: + raise ValueError('cannot set row attribute of a 1-dimensional sparse array') + new_row = np.asarray(new_row, dtype=self.coords[-2].dtype) + self.coords = self.coords[:-2] + (new_row,) + self.coords[-1:] + + @property + def col(self): + return self.coords[-1] + + @col.setter + def col(self, new_col): + new_col = np.asarray(new_col, dtype=self.coords[-1].dtype) + self.coords = self.coords[:-1] + (new_col,) + + def reshape(self, *shape, order="C", copy=False): + shape = check_shape(shape, self.shape, allow_nd=self._allow_nd) + + # Return early if reshape is not required + if shape == self.shape: + if copy: + return self.copy() + else: + return self + + # When reducing the number of dimensions, we need to be careful about + # index overflow. This is why we can't simply call + # `np.ravel_multi_index()` followed by `np.unravel_index()` here. + flat_coords = _ravel_coords(self.coords, self.shape, order=order) + if len(shape) == 2: + if order == 'C': + new_coords = divmod(flat_coords, shape[1]) + else: + new_coords = divmod(flat_coords, shape[0])[::-1] + else: + new_coords = np.unravel_index(flat_coords, shape, order=order) + + idx_dtype = self._get_index_dtype(self.coords, maxval=max(shape)) + new_coords = tuple(np.asarray(co, dtype=idx_dtype) for co in new_coords) + + # Handle copy here rather than passing on to the constructor so that no + # copy will be made of `new_coords` regardless. + if copy: + new_data = self.data.copy() + else: + new_data = self.data + + return self.__class__((new_data, new_coords), shape=shape, copy=False) + + reshape.__doc__ = _spbase.reshape.__doc__ + + def _getnnz(self, axis=None): + if axis is None or (axis == 0 and self.ndim == 1): + nnz = len(self.data) + if any(len(idx) != nnz for idx in self.coords): + raise ValueError('all index and data arrays must have the ' + 'same length') + + if self.data.ndim != 1 or any(idx.ndim != 1 for idx in self.coords): + raise ValueError('coordinates and data arrays must be 1-D') + + return int(nnz) + + if axis < 0: + axis += self.ndim + if axis >= self.ndim: + raise ValueError('axis out of bounds') + + return np.bincount(downcast_intp_index(self.coords[1 - axis]), + minlength=self.shape[1 - axis]) + + _getnnz.__doc__ = _spbase._getnnz.__doc__ + + def count_nonzero(self, axis=None): + self.sum_duplicates() + if axis is None: + return np.count_nonzero(self.data) + + if axis < 0: + axis += self.ndim + if axis < 0 or axis >= self.ndim: + raise ValueError('axis out of bounds') + mask = self.data != 0 + coord = self.coords[1 - axis][mask] + return np.bincount(downcast_intp_index(coord), minlength=self.shape[1 - axis]) + + count_nonzero.__doc__ = _spbase.count_nonzero.__doc__ + + def _check(self): + """ Checks data structure for consistency """ + if self.ndim != len(self.coords): + raise ValueError('mismatching number of index arrays for shape; ' + f'got {len(self.coords)}, expected {self.ndim}') + + # index arrays should have integer data types + for i, idx in enumerate(self.coords): + if idx.dtype.kind != 'i': + warn(f'index array {i} has non-integer dtype ({idx.dtype.name})', + stacklevel=3) + + idx_dtype = self._get_index_dtype(self.coords, maxval=max(self.shape)) + self.coords = tuple(np.asarray(idx, dtype=idx_dtype) + for idx in self.coords) + self.data = to_native(self.data) + + if self.nnz > 0: + for i, idx in enumerate(self.coords): + if idx.max() >= self.shape[i]: + raise ValueError(f'axis {i} index {idx.max()} exceeds ' + f'matrix dimension {self.shape[i]}') + if idx.min() < 0: + raise ValueError(f'negative axis {i} index: {idx.min()}') + + def transpose(self, axes=None, copy=False): + if axes is None: + axes = range(self.ndim)[::-1] + elif isinstance(self, sparray): + if not hasattr(axes, "__len__") or len(axes) != self.ndim: + raise ValueError("axes don't match matrix dimensions") + if len(set(axes)) != self.ndim: + raise ValueError("repeated axis in transpose") + elif axes != (1, 0): + raise ValueError("Sparse matrices do not support an 'axes' " + "parameter because swapping dimensions is the " + "only logical permutation.") + + permuted_shape = tuple(self._shape[i] for i in axes) + permuted_coords = tuple(self.coords[i] for i in axes) + return self.__class__((self.data, permuted_coords), + shape=permuted_shape, copy=copy) + + transpose.__doc__ = _spbase.transpose.__doc__ + + @property + def mT(self): + if (n := self.ndim) < 2: + raise ValueError(f"Array must be at least 2-dimensional, but it is {n}-D") + axes = None if n == 2 else tuple(range(n - 2)) + (-1, -2) + return self.transpose(axes=axes) + + mT.__doc__ = _spbase.mT.__doc__ + + def resize(self, *shape) -> None: + shape = check_shape(shape, allow_nd=self._allow_nd) + if self.ndim > 2: + raise ValueError("only 1-D or 2-D input accepted") + if len(shape) > 2: + raise ValueError("shape argument must be 1-D or 2-D") + # Check for added dimensions. + if len(shape) > self.ndim: + flat_coords = _ravel_coords(self.coords, self.shape) + max_size = math.prod(shape) + self.coords = np.unravel_index(flat_coords[:max_size], shape) + self.data = self.data[:max_size] + self._shape = shape + return + + # Check for removed dimensions. + if len(shape) < self.ndim: + tmp_shape = ( + self._shape[:len(shape) - 1] # Original shape without last axis + + (-1,) # Last axis is used to flatten the array + + (1,) * (self.ndim - len(shape)) # Pad with ones + ) + tmp = self.reshape(tmp_shape) + self.coords = tmp.coords[:len(shape)] + self._shape = tmp.shape[:len(shape)] + + # Handle truncation of existing dimensions. + is_truncating = any(old > new for old, new in zip(self.shape, shape)) + if is_truncating: + mask = np.logical_and.reduce([ + idx < size for idx, size in zip(self.coords, shape) + ]) + if not mask.all(): + self.coords = tuple(idx[mask] for idx in self.coords) + self.data = self.data[mask] + + self._shape = shape + + resize.__doc__ = _spbase.resize.__doc__ + + def toarray(self, order=None, out=None): + B = self._process_toarray_args(order, out) + fortran = int(B.flags.f_contiguous) + if not fortran and not B.flags.c_contiguous: + raise ValueError("Output array must be C or F contiguous") + # This handles both 0D and 1D cases correctly regardless of the + # original shape. + if self.ndim == 1: + coo_todense_nd(np.array([1]), self.nnz, self.ndim, + self.coords[0], self.data, B.ravel('A'), fortran) + elif self.ndim == 2: + M, N = self.shape + coo_todense(M, N, self.nnz, self.row, self.col, self.data, + B.ravel('A'), fortran) + else: # dim>2 + if fortran: + strides = np.append(1, np.cumprod(self.shape[:-1])) + else: + strides = np.append(np.cumprod(self.shape[1:][::-1])[::-1], 1) + coords = np.concatenate(self.coords) + coo_todense_nd(strides, self.nnz, self.ndim, + coords, self.data, B.ravel('A'), fortran) + # Note: reshape() doesn't copy here, but does return a new array (view). + return B.reshape(self.shape) + + toarray.__doc__ = _spbase.toarray.__doc__ + + def tocsc(self, copy=False): + """Convert this array/matrix to Compressed Sparse Column format. + + Duplicate entries will be summed together. + + Parameters + ---------- + copy : bool, optional + Unused. A copy is always made in the 2D case. And CSC is 2D. + + Returns + ------- + csc array/matrix + The converted array/matrix in CSC format. + + Examples + -------- + >>> from numpy import array + >>> from scipy.sparse import coo_array + >>> row = array([0, 0, 1, 3, 1, 0, 0]) + >>> col = array([0, 2, 1, 3, 1, 0, 0]) + >>> data = array([1, 1, 1, 1, 1, 1, 1]) + >>> A = coo_array((data, (row, col)), shape=(4, 4)).tocsc() + >>> A.toarray() + array([[3, 0, 1, 0], + [0, 2, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 1]]) + + """ + if self.ndim != 2: + raise ValueError(f'Cannot convert. CSC format must be 2D. Got {self.ndim}D') + if self.nnz == 0: + return self._csc_container(self.shape, dtype=self.dtype) + else: + # _coo_to_compressed copy kwarg only for 1D. CSC cant be 1D. See gh-24676 + from ._csc import csc_array + indptr, indices, data, shape = self._coo_to_compressed(csc_array._swap) + + x = self._csc_container((data, indices, indptr), shape=shape) + if not self.has_canonical_format: + x.sum_duplicates() + return x + + def tocsr(self, copy=False): + """Convert this array/matrix to Compressed Sparse Row format. + + Duplicate entries will be summed together. + + Parameters + ---------- + copy : bool, optional + With ``copy=False``, the data/indices may be shared between this + array/matrix and the resultant csr_array/matrix. But only for 1D. + For 2D, a copy will always be made. + + Returns + ------- + csr array/matrix + The converted array/matrix in CSR format. + + Examples + -------- + >>> from numpy import array + >>> from scipy.sparse import coo_array + >>> row = array([0, 0, 1, 3, 1, 0, 0]) + >>> col = array([0, 2, 1, 3, 1, 0, 0]) + >>> data = array([1, 1, 1, 1, 1, 1, 1]) + >>> A = coo_array((data, (row, col)), shape=(4, 4)).tocsr() + >>> A.toarray() + array([[3, 0, 1, 0], + [0, 2, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 1]]) + + """ + if self.ndim > 2: + raise ValueError(f'Cannot convert. CSR must be 1D or 2D. Got {self.ndim}D') + if self.nnz == 0: + return self._csr_container(self.shape, dtype=self.dtype) + else: + from ._csr import csr_array + arrays = self._coo_to_compressed(csr_array._swap, copy=copy) + indptr, indices, data, shape = arrays + + x = self._csr_container((data, indices, indptr), shape=self.shape) + if not self.has_canonical_format: + x.sum_duplicates() + return x + + def _coo_to_compressed(self, swap, copy=False): + """convert (shape, coords, data) to (indptr, indices, data, shape)""" + M, N = swap(self._shape_as_2d) + # convert idx_dtype intc to int32 for pythran. + # tested in scipy/optimize/tests/test__numdiff.py::test_group_columns + idx_dtype = self._get_index_dtype(self.coords, maxval=max(self.nnz, N)) + + if self.ndim == 1: + indices = self.coords[0].copy() if copy else self.coords[0] + nnz = len(indices) + indptr = np.array([0, nnz], dtype=idx_dtype) + data = self.data.copy() if copy else self.data + return indptr, indices, data, self.shape + + # ndim == 2 + major, minor = swap(self.coords) + nnz = len(major) + major = major.astype(idx_dtype, copy=False) + minor = minor.astype(idx_dtype, copy=False) + + indptr = np.empty(M + 1, dtype=idx_dtype) + indices = np.empty_like(minor, dtype=idx_dtype) + data = np.empty_like(self.data, dtype=self.dtype) + + coo_tocsr(M, N, nnz, major, minor, self.data, indptr, indices, data) + return indptr, indices, data, self.shape + + def tocoo(self, copy=False): + if copy: + return self.copy() + else: + return self + + tocoo.__doc__ = _spbase.tocoo.__doc__ + + def todia(self, copy=False): + if self.ndim != 2: + raise ValueError(f'Cannot convert. DIA format must be 2D. Got {self.ndim}D') + self.sum_duplicates() + ks = self.col - self.row # the diagonal for each nonzero + diags, diag_idx = np.unique(ks, return_inverse=True) + + if len(diags) > 100: + # probably undesired, should todia() have a maxdiags parameter? + warn(f"Constructing a DIA matrix with {len(diags)} diagonals " + "is inefficient", + SparseEfficiencyWarning, stacklevel=2) + + #initialize and fill in data array + if self.data.size == 0: + data = np.zeros((0, 0), dtype=self.dtype) + else: + data = np.zeros((len(diags), self.col.max()+1), dtype=self.dtype) + data[diag_idx, self.col] = self.data + + return self._dia_container((data, diags), shape=self.shape) + + todia.__doc__ = _spbase.todia.__doc__ + + def todok(self, copy=False): + if self.ndim > 2: + raise ValueError(f'Cannot convert. DOK must be 1D or 2D. Got {self.ndim}D') + self.sum_duplicates() + dok = self._dok_container(self.shape, dtype=self.dtype) + # ensure that 1d coordinates are not tuples + if self.ndim == 1: + coords = self.coords[0] + else: + coords = zip(*self.coords) + + dok._dict = dict(zip(coords, self.data)) + return dok + + todok.__doc__ = _spbase.todok.__doc__ + + def diagonal(self, k=0): + if self.ndim != 2: + raise ValueError("diagonal requires two dimensions") + rows, cols = self.shape + if k <= -rows or k >= cols: + return np.empty(0, dtype=self.data.dtype) + diag = np.zeros(min(rows + min(k, 0), cols - max(k, 0)), + dtype=self.dtype) + diag_mask = (self.row + k) == self.col + + if self.has_canonical_format: + row = self.row[diag_mask] + data = self.data[diag_mask] + else: + inds = tuple(idx[diag_mask] for idx in self.coords) + (row, _), data = self._sum_duplicates(inds, self.data[diag_mask]) + diag[row + min(k, 0)] = data + + return diag + + diagonal.__doc__ = _data_matrix.diagonal.__doc__ + + def _setdiag(self, values, k): + if self.ndim != 2: + raise ValueError("setting a diagonal requires two dimensions") + M, N = self.shape + if values.ndim and not len(values): + return + idx_dtype = self.row.dtype + + # Determine which triples to keep and where to put the new ones. + full_keep = self.col - self.row != k + if k < 0: + max_index = min(M+k, N) + if values.ndim: + max_index = min(max_index, len(values)) + keep = np.logical_or(full_keep, self.col >= max_index) + new_row = np.arange(-k, -k + max_index, dtype=idx_dtype) + new_col = np.arange(max_index, dtype=idx_dtype) + else: + max_index = min(M, N-k) + if values.ndim: + max_index = min(max_index, len(values)) + keep = np.logical_or(full_keep, self.row >= max_index) + new_row = np.arange(max_index, dtype=idx_dtype) + new_col = np.arange(k, k + max_index, dtype=idx_dtype) + + # Define the array of data consisting of the entries to be added. + if values.ndim: + new_data = values[:max_index] + else: + new_data = np.empty(max_index, dtype=self.dtype) + new_data[:] = values + + # Update the internal structure. + self.coords = (np.concatenate((self.row[keep], new_row)), + np.concatenate((self.col[keep], new_col))) + self.data = np.concatenate((self.data[keep], new_data)) + self.has_canonical_format = False + + # needed by _data_matrix + def _with_data(self, data, copy=True): + """Returns a matrix with the same sparsity structure as self, + but with different data. By default the index arrays are copied. + """ + if copy: + coords = tuple(idx.copy() for idx in self.coords) + else: + coords = self.coords + return self.__class__((data, coords), shape=self.shape, dtype=data.dtype) + + def __getitem__(self, key): + index, new_shape, arr_int_pos, none_pos = _validate_indices( + key, self.shape, self.format + ) + # handle int, slice and int-array indices + index_mask = np.ones(len(self.data), dtype=np.bool_) + slice_coords = [] + arr_coords = [] + arr_indices = [] + for i, (idx, co) in enumerate(zip(index, self.coords)): + if isinstance(idx, int): + index_mask &= (co == idx) + elif isinstance(idx, slice): + if idx == slice(None): + slice_coords.append(co) + else: + start, stop, step = idx.indices(self.shape[i]) + if step != 1: + if step < 0: + in_range = (co <= start) & (co > stop) + else: + in_range = (co >= start) & (co < stop) + new_ix, m = np.divmod(co - start, step) + index_mask &= (m == 0) & in_range + else: + in_range = (co >= start) & (co < stop) + new_ix = co - start + index_mask &= in_range + slice_coords.append(new_ix) + else: # array + arr_coords.append(co) + arr_indices.append(idx) + # shortcut for scalar output + if new_shape == (): + return self.data[index_mask].sum().astype(self.dtype, copy=False) + + new_coords = [co[index_mask] for co in slice_coords] + new_data = self.data[index_mask] + + # handle array indices + if arr_indices: + arr_indices = _broadcast_arrays(*arr_indices) + arr_shape = arr_indices[0].shape # already broadcast in validate_indices + # There are three dimensions required to check array indices against coords + # Their lengths are described as: + # a) number of indices that are arrays - arr_dim + # b) number of coords to check - masked_nnz (already masked by slices) + # c) size of the index arrays - arr_size + # Note for this, integer indices are treated like slices, not like arrays. + # + # Goal: + # Find new_coords and index positions that match across all arr_dim axes. + # Approach: Track matches using bool array. Size: masked_nnz by arr_size. + # True means all arr_indices match at that coord and index position. + # Equate with broadcasting and check for all equal across (arr_dim) axis 0. + # 1st array is "keyarr" (arr_dim by 1 by arr_size) from arr_indices. + # 2nd array is "arr_coords" (arr_dim by masked_nnz by 1) from arr_coords. + keyarr = np.array(arr_indices).reshape(len(arr_indices), 1, -1) + arr_coords = np.array([co[index_mask] for co in arr_coords])[:, :, None] + found = (keyarr == arr_coords).all(axis=0) + arr_co, arr_ix = found.nonzero() + new_data = new_data[arr_co] + new_coords = [co[arr_co] for co in new_coords] + new_arr_coords = list(np.unravel_index(arr_ix, shape=arr_shape)) + + # check for contiguous positions of array and int indices + if len(arr_int_pos) == arr_int_pos[-1] - arr_int_pos[0] + 1: + # Contiguous. Put all array index shape at pos of array indices + pos = arr_int_pos[0] + new_coords = new_coords[:pos] + new_arr_coords + new_coords[pos:] + else: + # Not contiguous. Put all array coords at front + new_coords = new_arr_coords + new_coords + + if none_pos: + if new_coords: + coord_like = np.zeros_like(new_coords[0]) + else: + coord_like = np.zeros(len(new_data), dtype=self.coords[0].dtype) + new_coords.insert(none_pos[0], coord_like) + for i in none_pos[1:]: + new_coords.insert(i, coord_like.copy()) + return coo_array((new_data, new_coords), shape=new_shape, dtype=self.dtype) + + def __setitem__(self, key, x): + # enact self[key] = x + index, new_shape, arr_int_pos, none_pos = _validate_indices( + key, self.shape, self.format + ) + + # remove None's at beginning of index. Should not impact indexing coords + # and will mistakenly align with x_coord columns if not removed. + if none_pos: + new_shape = list(new_shape) + for j in none_pos[::-1]: + new_shape.pop(j) + new_shape = tuple(new_shape) + + # broadcast arrays + if arr_int_pos: + index = list(index) + arr_pos = {i: arr for i in arr_int_pos if not isintlike(arr := index[i])} + arr_indices = _broadcast_arrays(*arr_pos.values()) + for i, arr in zip(arr_pos, arr_indices): + index[i] = arr + + # get coords and data from x + if issparse(x): + if 0 in x.shape: + return # Nothing to set. + x_data, x_coords = _get_sparse_data_and_coords(x, new_shape, self.dtype) + else: + x = np.asarray(x, dtype=self.dtype) + if x.size == 0: + return # Nothing to set. + x_data, x_coords = _get_dense_data_and_coords(x, new_shape) + + # Approach: + # Set indexed values to zero (drop from `self.coords` and `self.data`) + # create new coords and data arrays for setting nonzeros + # concatenate old (undropped) values with new coords and data + + old_data, old_coords = self._zero_many(index) + + if len(x_coords) == 1 and len(x_coords[0]) == 0: + self.data, self.coords = old_data, old_coords + # leave self.has_canonical_format unchanged + return + + # To process array indices, need the x_coords for those axes + # and need to ravel the array part of x_coords to build new_coords + # Along the way, Find pos and shape of array-index portion of key. + # arr_shape is None and pos = -1 when no arrays are used as indices. + arr_shape = None + pos = -1 + if arr_int_pos: + # Get arr_shape if any arrays are in the index. + # Also ravel the corresponding x_coords. + for idx in index: + if not isinstance(idx, slice) and not isintlike(idx): + arr_shape = idx.shape + + # Find x_coord pos of integer and array portion of index. + # If contiguous put int and array axes at pos of those indices. + # If not contiguous, put all int and array axes at pos=0. + if len(arr_int_pos) == (arr_int_pos[-1] - arr_int_pos[0] + 1): + pos = arr_int_pos[0] + else: + pos = 0 + + # compute the raveled coords of the array part of x_coords. + # Used to build the new coords from the index arrays. + x_arr_coo = x_coords[pos:pos + len(arr_shape)] + # could use np.ravel_multi_index but _ravel_coords avoids overflow + x_arr_coo_ravel = _ravel_coords(x_arr_coo, arr_shape) + break + + # find map from x_coord slice axes to index axes + x_ax = 0 + x_axes = {} + for i, idx in enumerate(index): + if i == pos: + x_ax += len(arr_shape) + if isinstance(idx, slice): + x_axes[i] = x_ax + x_ax += 1 + + # Build new_coords and new_data + new_coords = [None] * self.ndim + new_nnz = len(x_data) + for i, idx in enumerate(index): + if isintlike(idx): + new_coords[i] = (np.broadcast_to(idx, (new_nnz,))) + continue + elif isinstance(idx, slice): + start, stop, step = idx.indices(self.shape[i]) + new_coords[i] = (start + x_coords[x_axes[i]] * step) + else: # array idx + new_coords[i] = idx.ravel()[x_arr_coo_ravel] + # seems like a copy is prudent when setting data in this array + new_data = x_data.copy() + + # deduplicate entries created by multiple coords matching in the array index + # NumPy does not specify which value is put into the spot (last one assigned) + # so only one value should appear if we want to match NumPy. + # If matching NumPy is not crucial, we could make dups a feature where + # integer array indices with repeated indices creates duplicate values. + # Not doing that here. We are just removing duplicates (keep 1st data found) + new_coords = np.array(new_coords) + _, ind = np.unique(new_coords, axis=1, return_index=True) + + # update values with stack of old and new data and coords. + self.data = np.hstack([old_data, new_data[ind]]) + self.coords = tuple(np.hstack(c) for c in zip(old_coords, new_coords[:, ind])) + self.has_canonical_format = False + + def _zero_many(self, index): + # handle int, slice and integer-array indices + # index_mask accumulates a bool array of nonzeros that match index + index_mask=np.ones(len(self.data), dtype=np.bool_) + arr_coords = [] + arr_indices = [] + for i, (idx, co) in enumerate(zip(index, self.coords)): + if isinstance(idx, int): + index_mask &= (co == idx) + elif isinstance(idx, slice) and idx != slice(None): + start, stop, step = idx.indices(self.shape[i]) + if step != 1: + if step < 0: + in_range = (co <= start) & (co > stop) + else: + in_range = (co >= start) & (co < stop) + m = np.mod(co - start, step) + index_mask &= (m == 0) & in_range + else: + in_range = (co >= start) & (co < stop) + index_mask &= in_range + elif isinstance(idx, slice) and idx == slice(None): + # slice is full axis so no changes to index_mask + pass + else: # array + arr_coords.append(co) + arr_indices.append(idx) + + # match array indices with masked coords. See comments in __getitem__ + if arr_indices: + keyarr = np.array(arr_indices).reshape(len(arr_indices), 1, -1) + arr_coords = np.array([co[index_mask] for co in arr_coords])[:, :, None] + found = (keyarr == arr_coords).all(axis=0) + arr_coo, _ = found.nonzero() + arr_index_mask = np.zeros_like(index_mask) + arr_index_mask[index_mask.nonzero()[0][arr_coo]] = True + index_mask &= arr_index_mask + + # remove matching coords and data to set them to zero + pruned_coords = [co[~index_mask] for co in self.coords] + pruned_data = self.data[~index_mask] + return pruned_data, pruned_coords + + def sum_duplicates(self) -> None: + """Eliminate duplicate entries by adding them together. + + This is an *in place* operation + """ + if self.has_canonical_format: + return + summed = self._sum_duplicates(self.coords, self.data) + self.coords, self.data = summed + self.has_canonical_format = True + + def _sum_duplicates(self, coords, data): + # Assumes coords not in canonical format. + if len(data) == 0: + return coords, data + # Sort coords w.r.t. rows, then cols. This corresponds to C-order, + # which we rely on for argmin/argmax to return the first index in the + # same way that numpy does (in the case of ties). + order = np.lexsort(coords[::-1]) + coords = tuple(idx[order] for idx in coords) + data = data[order] + unique_mask = np.logical_or.reduce([ + idx[1:] != idx[:-1] for idx in coords + ]) + unique_mask = np.append(True, unique_mask) + coords = tuple(idx[unique_mask] for idx in coords) + unique_inds, = np.nonzero(unique_mask) + data = np.add.reduceat(data, downcast_intp_index(unique_inds), dtype=self.dtype) + return coords, data + + def eliminate_zeros(self): + """Remove zero entries from the array/matrix. + + This is an *in place* operation. + """ + mask = self.data != 0 + self.data = self.data[mask] + self.coords = tuple(idx[mask] for idx in self.coords) + + ####################### + # Arithmetic handlers # + ####################### + + def _add_dense(self, other): + if other.shape != self.shape: + raise ValueError(f'Incompatible shapes ({self.shape} and {other.shape})') + dtype = upcast_char(self.dtype.char, other.dtype.char) + result = np.array(other, dtype=dtype, copy=True) + fortran = int(result.flags.f_contiguous) + if self.ndim == 1: + coo_todense_nd(np.array([1]), self.nnz, self.ndim, + self.coords[0], self.data, result.ravel('A'), fortran) + elif self.ndim == 2: + M, N = self._shape_as_2d + coo_todense(M, N, self.nnz, self.row, self.col, self.data, + result.ravel('A'), fortran) + else: + if fortran: + strides = np.append(1, np.cumprod(self.shape[:-1])) + else: + strides = np.append(np.cumprod(self.shape[1:][::-1])[::-1], 1) + coords = np.concatenate(self.coords) + coo_todense_nd(strides, self.nnz, self.ndim, + coords, self.data, result.ravel('A'), fortran) + return self._container(result, copy=False) + + + def _add_sparse(self, other): + if self.ndim < 3: + return self.tocsr()._add_sparse(other) + + if other.shape != self.shape: + raise ValueError(f'Incompatible shapes ({self.shape} and {other.shape})') + other = self.__class__(other) + new_data = np.concatenate((self.data, other.data)) + new_coords = tuple(np.concatenate((self.coords, other.coords), axis=1)) + A = self.__class__((new_data, new_coords), shape=self.shape) + return A + + def _sub_sparse(self, other): + if self.ndim < 3: + return self.tocsr()._sub_sparse(other) + + if other.shape != self.shape: + raise ValueError(f'Incompatible shapes ({self.shape} and {other.shape})') + other = self.__class__(other) + new_data = np.concatenate((self.data, -other.data)) + new_coords = tuple(np.concatenate((self.coords, other.coords), axis=1)) + A = coo_array((new_data, new_coords), shape=self.shape) + return A + + def _matmul_vector(self, other): + if self.ndim > 2: + result = np.zeros(math.prod(self.shape[:-1]), + dtype=upcast_char(self.dtype.char, other.dtype.char)) + shape = np.array(self.shape) + strides = np.append(np.cumprod(shape[:-1][::-1])[::-1][1:], 1) + coords = np.concatenate(self.coords) + coo_matvec_nd(self.nnz, len(self.shape), strides, coords, self.data, + other, result) + + result = result.reshape(self.shape[:-1]) + return result + + # self.ndim <= 2 + result_shape = self.shape[0] if self.ndim > 1 else 1 + result = np.zeros(result_shape, + dtype=upcast_char(self.dtype.char, other.dtype.char)) + if self.ndim == 2: + col = self.col + row = self.row + elif self.ndim == 1: + col = self.coords[0] + row = np.zeros_like(col) + else: + raise NotImplementedError( + f"coo_matvec not implemented for ndim={self.ndim}") + + coo_matvec(self.nnz, row, col, self.data, other, result) + # Array semantics return a scalar here, not a single-element array. + if isinstance(self, sparray) and result_shape == 1: + return result[0] + return result + + def _rmatmul_dispatch(self, other): + if isscalarlike(other): + return self._mul_scalar(other) + else: + # Don't use asarray unless we have to + try: + o_ndim = other.ndim + except AttributeError: + other = np.asarray(other) + o_ndim = other.ndim + perm = tuple(range(o_ndim)[:-2]) + tuple(range(o_ndim)[-2:][::-1]) + tr = other.transpose(perm) + + s_ndim = self.ndim + perm = tuple(range(s_ndim)[:-2]) + tuple(range(s_ndim)[-2:][::-1]) + ret = self.transpose(perm)._matmul_dispatch(tr) + if ret is NotImplemented: + return NotImplemented + + if s_ndim == 1 or o_ndim == 1: + perm = range(ret.ndim) + else: + perm = tuple(range(ret.ndim)[:-2]) + tuple(range(ret.ndim)[-2:][::-1]) + return ret.transpose(perm) + + def _matmul_dispatch(self, other): + if isscalarlike(other): + return self.multiply(other) + + if not (issparse(other) or isdense(other)): + # If it's a list or whatever, treat it like an array + other_a = np.asanyarray(other) + + if other_a.ndim == 0 and other_a.dtype == np.object_: + # Not interpretable as an array; return NotImplemented so that + # other's __rmatmul__ can kick in if that's implemented. + return NotImplemented + # Allow custom sparse class indicated by attr sparse gh-6520 + try: + other.shape + except AttributeError: + other = other_a + + if self.ndim < 3 and other.ndim < 3: + return _spbase._matmul_dispatch(self, other) + + N = self.shape[-1] + err_prefix = "matmul: dimension mismatch with signature" + if other.__class__ is np.ndarray: + if other.shape == (N,): + return self._matmul_vector(other) + if other.shape == (N, 1): + result = self._matmul_vector(other.ravel()) + return result.reshape(*self.shape[:-1], 1) + if other.ndim == 1: + msg = f"{err_prefix} (n,k={N}),(k={other.shape[0]},)->(n,)" + raise ValueError(msg) + if other.shape[-2] == N: + # check for batch dimensions compatibility + batch_shape_A = self.shape[:-2] + batch_shape_B = other.shape[:-2] + if batch_shape_A != batch_shape_B: + try: + # This will raise an error if the shapes are not broadcastable + np.broadcast_shapes(batch_shape_A, batch_shape_B) + except ValueError: + raise ValueError("Batch dimensions are not broadcastable") + + return self._matmul_multivector(other) + else: + raise ValueError( + f"{err_prefix} (n,..,k={N}),(k={other.shape[-2]},..,m)->(n,..,m)" + ) + + if isscalarlike(other): + # scalar value + return self._mul_scalar(other) + + if issparse(other): + self_is_1d = self.ndim == 1 + other_is_1d = other.ndim == 1 + + # reshape to 2-D if self or other is 1-D + if self_is_1d: + self = self.reshape(self._shape_as_2d) # prepend 1 to shape + + if other_is_1d: + other = other.reshape((other.shape[0], 1)) # append 1 to shape + + # Check if the inner dimensions match for matrix multiplication + if N != other.shape[-2]: + raise ValueError( + f"{err_prefix} (n,..,k={N}),(k={other.shape[-2]},..,m)->(n,..,m)" + ) + + # If A or B has more than 2 dimensions, check for + # batch dimensions compatibility + if self.ndim > 2 or other.ndim > 2: + batch_shape_A = self.shape[:-2] + batch_shape_B = other.shape[:-2] + if batch_shape_A != batch_shape_B: + try: + # This will raise an error if the shapes are not broadcastable + np.broadcast_shapes(batch_shape_A, batch_shape_B) + except ValueError: + raise ValueError("Batch dimensions are not broadcastable") + + result = self._matmul_sparse(other) + + # reshape back if a or b were originally 1-D + if self_is_1d: + # if self was originally 1-D, reshape result accordingly + result = result.reshape(tuple(result.shape[:-2]) + + tuple(result.shape[-1:])) + if other_is_1d: + result = result.reshape(result.shape[:-1]) + return result + + def _matmul_multivector(self, other): + result_dtype = upcast_char(self.dtype.char, other.dtype.char) + if self.ndim >= 3 or other.ndim >= 3: + # if self has shape (N,), reshape to (1,N) + if self.ndim == 1: + result = self.reshape(1, self.shape[0])._matmul_multivector(other) + return result.reshape(tuple(other.shape[:-2]) + tuple(other.shape[-1:])) + + broadcast_shape = np.broadcast_shapes(self.shape[:-2], other.shape[:-2]) + self_shape = broadcast_shape + self.shape[-2:] + other_shape = broadcast_shape + other.shape[-2:] + + self = self._broadcast_to(self_shape) + other = np.broadcast_to(other, other_shape) + result_shape = broadcast_shape + self.shape[-2:-1] + other.shape[-1:] + result = np.zeros(result_shape, dtype=result_dtype) + coo_matmat_dense_nd(self.nnz, len(self.shape), other.shape[-1], + np.array(other_shape), np.array(result_shape), + np.concatenate(self.coords), + self.data, other.ravel('C'), result) + return result + + if self.ndim == 2: + result_shape = (self.shape[0], other.shape[1]) + col = self.col + row = self.row + elif self.ndim == 1: + result_shape = (other.shape[1],) + col = self.coords[0] + row = np.zeros_like(col) + result = np.zeros(result_shape, dtype=result_dtype) + coo_matmat_dense(self.nnz, other.shape[-1], row, col, + self.data, other.ravel('C'), result) + return result.view(type=type(other)) + + def dot(self, other): + """Return the dot product of two arrays. + + Strictly speaking a dot product involves two vectors. + But in the sense that an array with ndim >= 1 is a collection + of vectors, the function computes the collection of dot products + between each vector in the first array with each vector in the + second array. The axis upon which the sum of products is performed + is the last axis of the first array and the second to last axis of + the second array. If the second array is 1-D, the last axis is used. + + Thus, if both arrays are 1-D, the inner product is returned. + If both are 2-D, we have matrix multiplication. If `other` is 1-D, + the sum product is taken along the last axis of each array. If + `other` is N-D for N>=2, the sum product is over the last axis of + the first array and the second-to-last axis of the second array. + + Parameters + ---------- + other : array_like or sparse array + Second array + + Returns + ------- + output : array (sparse or dense) + The dot product of this array with `other`. + It will be dense/sparse if `other` is dense/sparse. + + Examples + -------- + + >>> import numpy as np + >>> from scipy.sparse import coo_array + >>> A = coo_array([[1, 2, 0], [0, 0, 3], [4, 0, 5]]) + >>> v = np.array([1, 0, -1]) + >>> A.dot(v) + array([ 1, -3, -1], dtype=int64) + + For 2-D arrays it is the matrix product: + + >>> A = coo_array([[1, 0], [0, 1]]) + >>> B = coo_array([[4, 1], [2, 2]]) + >>> A.dot(B).toarray() + array([[4, 1], + [2, 2]]) + + For 3-D arrays the shape extends unused axes by other unused axes. + + >>> A = coo_array(np.arange(3*4*5*6)).reshape((3,4,5,6)) + >>> B = coo_array(np.arange(3*4*5*6)).reshape((5,4,6,3)) + >>> A.dot(B).shape + (3, 4, 5, 5, 4, 3) + """ + # handle non-array input: lists, ints, etc + if not (issparse(other) or isdense(other) or isscalarlike(other)): + # If it's a list or whatever, treat it like an array + o_array = np.asanyarray(other) + + if o_array.ndim == 0 and o_array.dtype == np.object_: + raise TypeError(f"dot argument not supported type: '{type(other)}'") + try: + other.shape + except AttributeError: + other = o_array + + # Handle scalar multiplication + if isscalarlike(other): + return self * other + + # other.shape[-2:][0] gets last index of 1d, next to last index of >1d + if self.shape[-1] != other.shape[-2:][0]: + raise ValueError(f"shapes {self.shape} and {other.shape}" + " are not aligned for n-D dot") + + if self.ndim < 3 and other.ndim < 3: + return self @ other + if isdense(other): + return self._dense_dot(other) + return self._sparse_dot(other.tocoo()) + + def _sparse_dot(self, other): + # already checked: at least one is >2d, neither scalar, both are coo + # Ravel non-reduced axes coordinates + self_2d, s_new_shape = _convert_to_2d(self, [self.ndim - 1]) + other_2d, o_new_shape = _convert_to_2d(other, [max(0, other.ndim - 2)]) + + prod = self_2d @ other_2d.T # routes via 2-D CSR + prod = prod.tocoo() + + # Combine the shapes of the non-contracted axes + combined_shape = s_new_shape + o_new_shape + + # Unravel the 2D coordinates to get multi-dimensional coordinates + coords = [] + new_shapes = (s_new_shape, o_new_shape) if s_new_shape else (o_new_shape,) + for c, s in zip(prod.coords, new_shapes): + coords.extend(np.unravel_index(c, s)) + + # Construct the resulting COO array with coords and shape + return coo_array((prod.data, coords), shape=combined_shape) + + def _dense_dot(self, other): + # already checked: self is >0d, other is dense and >0d + # Ravel non-reduced axes coordinates + s_ndim = self.ndim + if s_ndim <= 2: + s_new_shape = () if s_ndim == 1 else (self.shape[0],) + self_2d = self + else: + self_2d, s_new_shape = _convert_to_2d(self, [self.ndim - 1]) + + o_ndim = other.ndim + if o_ndim <= 2: + o_new_shape = () if o_ndim == 1 else (other.shape[-1],) + other_2d = other + else: + o_new_shape = other.shape[:-2] + other.shape[-1:] + reorder_dims = (o_ndim - 2, *range(o_ndim - 2), o_ndim - 1) + o_reorg = np.transpose(other, reorder_dims) + other_2d = o_reorg.reshape((other.shape[-2], math.prod(o_new_shape))) + + prod = self_2d @ other_2d # routes via 2-D CSR + + # Combine the shapes of the non-contracted axes + combined_shape = s_new_shape + o_new_shape + return prod.reshape(combined_shape) + + + def tensordot(self, other, axes=2): + """Return the tensordot product with another array along the given axes. + + The tensordot differs from dot and matmul in that any axis can be + chosen for each of the first and second array and the sum of the + products is computed just like for matrix multiplication, only not + just for the rows of the first times the columns of the second. It + takes the dot product of the collection of vectors along the specified + axes. Here we can even take the sum of the products along two or even + more axes if desired. So, tensordot is a dot product computation + applied to arrays of any dimension >= 1. It is like matmul but over + arbitrary axes for each matrix. + + Given two tensors, `a` and `b`, and the desired axes specified as a + 2-tuple/list/array containing two sequences of axis numbers, + ``(a_axes, b_axes)``, sum the products of `a`'s and `b`'s elements + (components) over the axes specified by ``a_axes`` and ``b_axes``. + The `axes` input can be a single non-negative integer, ``N``; + if it is, then the last ``N`` dimensions of `a` and the first + ``N`` dimensions of `b` are summed over. + + Parameters + ---------- + other : array_like + Tensor to "dot". + axes : int or (2,) array_like + * integer_like + If an int N, sum over the last N axes of `a` and the first N axes + of `b` in order. The sizes of the corresponding axes must match. + * (2,) array_like + A 2-tuple of sequences of axes to be summed over, the first applying + to `a`, the second to `b`. The sequences must be the same length. + The shape of the corresponding axes must match between `a` and `b`. + + Returns + ------- + output : coo_array + The tensor dot product of this array with `other`. + It will be dense/sparse if `other` is dense/sparse. + + See Also + -------- + dot + + Examples + -------- + >>> import numpy as np + >>> import scipy.sparse + >>> A = scipy.sparse.coo_array([[[2, 3], [0, 0]], [[0, 1], [0, 5]]]) + >>> A.shape + (2, 2, 2) + + Integer axes N are shorthand for (range(-N, 0), range(0, N)): + + >>> A.tensordot(A, axes=1).toarray() + array([[[[ 4, 9], + [ 0, 15]], + + [[ 0, 0], + [ 0, 0]]], + + + [[[ 0, 1], + [ 0, 5]], + + [[ 0, 5], + [ 0, 25]]]]) + >>> A.tensordot(A, axes=2).toarray() + array([[ 4, 6], + [ 0, 25]]) + >>> A.tensordot(A, axes=3) + array(39) + + Using tuple for axes: + + >>> a = scipy.sparse.coo_array(np.arange(60).reshape(3,4,5)) + >>> b = np.arange(24).reshape(4,3,2) + >>> c = a.tensordot(b, axes=([1,0],[0,1])) + >>> c.shape + (5, 2) + >>> c + array([[4400, 4730], + [4532, 4874], + [4664, 5018], + [4796, 5162], + [4928, 5306]]) + + """ + if not isdense(other) and not issparse(other): + # If it's a list or whatever, treat it like an array + other_array = np.asanyarray(other) + + if other_array.ndim == 0 and other_array.dtype == np.object_: + raise TypeError(f"tensordot arg not supported type: '{type(other)}'") + try: + other.shape + except AttributeError: + other = other_array + + axes_self, axes_other = _process_axes(self.ndim, other.ndim, axes) + + # Check for shape compatibility along specified axes + if any(self.shape[ax] != other.shape[bx] + for ax, bx in zip(axes_self, axes_other)): + raise ValueError("sizes of the corresponding axes must match") + + if isdense(other): + return self._dense_tensordot(other, axes_self, axes_other) + else: + return self._sparse_tensordot(other, axes_self, axes_other) + + def _sparse_tensordot(self, other, s_axes, o_axes): + # Prepare the tensors for tensordot operation + # Ravel non-reduced axes coordinates + self_2d, s_new_shape = _convert_to_2d(self, s_axes) + other_2d, o_new_shape = _convert_to_2d(other, o_axes) + + # Perform matrix multiplication (routed via 2-D CSR) + prod = self_2d @ other_2d.T + # handle case of scalar result (axis includes all axes for both) + if not issparse(prod): + return prod + prod = prod.tocoo() + + # Combine the shapes of the non-contracted axes + combined_shape = s_new_shape + o_new_shape + + # Unravel the 2D coordinates to get multi-dimensional coordinates + coords = [] + new_shapes = (s_new_shape, o_new_shape) if s_new_shape else (o_new_shape,) + for c, s in zip(prod.coords, new_shapes): + if s: + coords.extend(np.unravel_index(c, s)) + + # Construct the resulting COO array with coords and shape + return coo_array((prod.data, coords), shape=combined_shape) + + def _dense_tensordot(self, other, s_axes, o_axes): + s_ndim = len(self.shape) + o_ndim = len(other.shape) + + s_non_axes = [i for i in range(s_ndim) if i not in s_axes] + s_axes_shape = [self.shape[i] for i in s_axes] + s_non_axes_shape = [self.shape[i] for i in s_non_axes] + + o_non_axes = [i for i in range(o_ndim) if i not in o_axes] + o_axes_shape = [other.shape[i] for i in o_axes] + o_non_axes_shape = [other.shape[i] for i in o_non_axes] + + left = self.transpose(s_non_axes + s_axes) + right = np.transpose(other, o_non_axes[:-1] + o_axes + o_non_axes[-1:]) + + reshape_left = (*s_non_axes_shape, math.prod(s_axes_shape)) + reshape_right = (*o_non_axes_shape[:-1], math.prod(o_axes_shape), + *o_non_axes_shape[-1:]) + + return left.reshape(reshape_left).dot(right.reshape(reshape_right)) + + def _matmul_sparse(self, other): + """ + Perform sparse-sparse matrix multiplication for two n-D COO arrays. + The method converts input n-D arrays to 2-D block array format, + uses csr_matmat to multiply them, and then converts the + result back to n-D COO array. + + Parameters: + self (COO): The first n-D sparse array in COO format. + other (COO): The second n-D sparse array in COO format. + + Returns: + prod (COO): The resulting n-D sparse array after multiplication. + """ + if self.ndim < 3 and other.ndim < 3: + return _spbase._matmul_sparse(self, other) + + # Get the shapes of self and other + self_shape = self.shape + other_shape = other.shape + + # Determine the new shape to broadcast self and other + broadcast_shape = np.broadcast_shapes(self_shape[:-2], other_shape[:-2]) + self_new_shape = tuple(broadcast_shape) + self_shape[-2:] + other_new_shape = tuple(broadcast_shape) + other_shape[-2:] + + self_broadcasted = self._broadcast_to(self_new_shape) + other_broadcasted = other._broadcast_to(other_new_shape) + + # Convert n-D COO arrays to 2-D block diagonal arrays + self_block_diag = _block_diag(self_broadcasted) + other_block_diag = _block_diag(other_broadcasted) + + # Use csr_matmat to perform sparse matrix multiplication + prod_block_diag = (self_block_diag @ other_block_diag).tocoo() + + # Convert the 2-D block diagonal array back to n-D + return _extract_block_diag( + prod_block_diag, + shape=(*broadcast_shape, self.shape[-2], other.shape[-1]), + ) + + def _broadcast_to(self, new_shape, copy=False): + if self.shape == new_shape: + return self.copy() if copy else self + + old_shape = self.shape + + # Check if the new shape is compatible for broadcasting + if len(new_shape) < len(old_shape): + raise ValueError("New shape must have at least as many dimensions" + " as the current shape") + + # Add leading ones to shape to ensure same length as `new_shape` + shape = (1,) * (len(new_shape) - len(old_shape)) + tuple(old_shape) + + # Ensure the old shape can be broadcast to the new shape + if any((o != 1 and o != n) for o, n in zip(shape, new_shape)): + raise ValueError(f"current shape {old_shape} cannot be " + "broadcast to new shape {new_shape}") + + # Reshape the COO array to match the new dimensions + self = self.reshape(shape) + + idx_dtype = get_index_dtype(self.coords, maxval=max(new_shape)) + coords = self.coords + new_data = self.data + new_coords = coords[-1:] # Copy last coordinate to start + cum_repeat = 1 # Cumulative repeat factor for broadcasting + + if shape[-1] != new_shape[-1]: # broadcasting the n-th (col) dimension + repeat_count = new_shape[-1] + cum_repeat *= repeat_count + new_data = np.tile(new_data, repeat_count) + new_dim = np.repeat(np.arange(0, repeat_count, dtype=idx_dtype), self.nnz) + new_coords = (new_dim,) + + for i in range(-2, -(len(shape)+1), -1): + if shape[i] != new_shape[i]: + repeat_count = new_shape[i] # number of times to repeat data, coords + cum_repeat *= repeat_count # update cumulative repeat factor + nnz = len(new_data) # Number of non-zero elements so far + + # Tile data and coordinates to match the new repeat count + new_data = np.tile(new_data, repeat_count) + new_coords = tuple(np.tile(new_coords[i+1:], repeat_count)) + + # Create new dimensions and stack them + new_dim = np.repeat(np.arange(0, repeat_count, dtype=idx_dtype), nnz) + new_coords = (new_dim,) + new_coords + else: + # If no broadcasting needed, tile the coordinates + new_dim = np.tile(coords[i], cum_repeat) + new_coords = (new_dim,) + new_coords + + return coo_array((new_data, new_coords), new_shape) + + def _sum_nd(self, axis, res_dtype, out): + # axis and out are preprocessed. out.shape is new_shape + A2d, new_shape = _convert_to_2d(self, axis) + ones = np.ones((A2d.shape[1], 1), dtype=res_dtype) + # sets dtype while loading into out + out[...] = (A2d @ ones).reshape(new_shape) + return out + + def _min_or_max_axis_nd(self, axis, min_or_max, explicit): + A2d, new_shape = _convert_to_2d(self, axis) + res = A2d._min_or_max_axis(1, min_or_max, explicit) + unraveled_coords = np.unravel_index(res.coords[0], new_shape) + + return coo_array((res.data, unraveled_coords), new_shape) + + def _argminmax_axis_nd(self, axis, argminmax, compare, explicit): + A2d, new_shape = _convert_to_2d(self, axis) + res_flat = A2d._argminmax_axis(1, argminmax, compare, explicit) + return res_flat.reshape(new_shape) + + +def _block_diag(self): + """ + Converts an N-D COO array into a 2-D COO array in block diagonal form. + + Parameters: + self (coo_array): An N-Dimensional COO sparse array. + + Returns: + coo_array: A 2-Dimensional COO sparse array in block diagonal form. + """ + if self.ndim<2: + raise ValueError("array must have atleast dim=2") + num_blocks = math.prod(self.shape[:-2]) + n_col = self.shape[-1] + n_row = self.shape[-2] + res_arr = self.reshape((num_blocks, n_row, n_col)) + new_coords = ( + res_arr.coords[1] + res_arr.coords[0] * res_arr.shape[1], + res_arr.coords[2] + res_arr.coords[0] * res_arr.shape[2], + ) + + new_shape = (num_blocks * n_row, num_blocks * n_col) + return coo_array((self.data, tuple(new_coords)), shape=new_shape) + + +def _extract_block_diag(self, shape): + n_row, n_col = shape[-2], shape[-1] + + # Extract data and coordinates from the block diagonal COO array + data = self.data + row, col = self.row, self.col + + # Initialize new coordinates array + new_coords = np.empty((len(shape), self.nnz), dtype=int) + + # Calculate within-block indices + new_coords[-2] = row % n_row + new_coords[-1] = col % n_col + + # Calculate coordinates for higher dimensions + temp_block_idx = row // n_row + for i in range(len(shape) - 3, -1, -1): + size = shape[i] + new_coords[i] = temp_block_idx % size + temp_block_idx = temp_block_idx // size + + # Create the new COO array with the original n-D shape + return coo_array((data, tuple(new_coords)), shape=shape) + + +def _get_sparse_data_and_coords(x, new_shape, dtype): + x = x.tocoo() + x.sum_duplicates() + + x_coords = list(x.coords) + x_data = x.data.astype(dtype, copy=False) + x_shape = x.shape + + if new_shape == x_shape: + return x_data, x_coords + + # broadcasting needed + len_diff = len(new_shape) - len(x_shape) + if len_diff > 0: + # prepend ones to shape of x to match ndim + x_shape = [1] * len_diff + list(x_shape) + coord_zeros = np.zeroslike(x_coords[0]) + x_coords = tuple([coord_zeros] * len_diff + x_coords) + # taking away axes (squeezing) is not part of broadcasting, but long + # spmatrix history of using 2d vectors in 1d space, so we manually + # squeeze the front and back axes here to be compatible + if len_diff < 0: + for _ in range(-len_diff): + if x_shape[0] == 1: + x_shape = x_shape[1:] + x_coords = x_coords[1:] + elif x_shape[-1] == 1: + x_shape = x_shape[:-1] + x_coords = x_coords[:-1] + else: + raise ValueError("shape mismatch in assignment") + # broadcast with copy (will need to copy eventually anyway) + tot_expand = 1 + for i, (nn, nx) in enumerate(zip(new_shape, x_shape)): + if nn == nx: + continue + if nx != 1: + raise ValueError("shape mismatch in assignment") + x_nnz = len(x_coords[0]) + x_coords[i] = np.repeat(np.arange(nn), x_nnz) + for j, co in enumerate(x_coords): + if j == i: + continue + x_coords[j] = np.tile(co, nn) + tot_expand *= nn + x_data = np.tile(x_data.ravel(), tot_expand) + return x_data, x_coords + + +def _get_dense_data_and_coords(x, new_shape): + if x.shape != new_shape: + x = np.broadcast_to(x.squeeze(), new_shape) + # shift scalar input to 1d so has coords + if new_shape == (): + x_coords = tuple([np.array([0])] * len(new_shape)) + x_data = x.ravel() + else: + x_coords = x.nonzero() + x_data = x[x_coords] + return x_data, x_coords + + +def _process_axes(ndim_a, ndim_b, axes): + if isinstance(axes, int): + if axes < 1 or axes > min(ndim_a, ndim_b): + raise ValueError("axes integer is out of bounds for input arrays") + axes_a = list(range(ndim_a - axes, ndim_a)) + axes_b = list(range(axes)) + elif isinstance(axes, tuple | list): + if len(axes) != 2: + raise ValueError("axes must be a tuple/list of length 2") + axes_a, axes_b = axes + if len(axes_a) != len(axes_b): + raise ValueError("axes lists/tuples must be of the same length") + if any(ax >= ndim_a or ax < -ndim_a for ax in axes_a) or \ + any(bx >= ndim_b or bx < -ndim_b for bx in axes_b): + raise ValueError("axes indices are out of bounds for input arrays") + else: + raise TypeError("axes must be an integer or a tuple/list of integers") + + axes_a = [axis + ndim_a if axis < 0 else axis for axis in axes_a] + axes_b = [axis + ndim_b if axis < 0 else axis for axis in axes_b] + return axes_a, axes_b + + +def _convert_to_2d(coo, axis): + axis_coords = tuple(coo.coords[i] for i in axis) + axis_shape = tuple(coo.shape[i] for i in axis) + axis_ravel = _ravel_coords(axis_coords, axis_shape) + + ndim = len(coo.coords) + non_axis = tuple(i for i in range(ndim) if i not in axis) + if non_axis: + non_axis_coords = tuple(coo.coords[i] for i in non_axis) + non_axis_shape = tuple(coo.shape[i] for i in non_axis) + non_axis_ravel = _ravel_coords(non_axis_coords, non_axis_shape) + coords_2d = (non_axis_ravel, axis_ravel) + shape_2d = (math.prod(non_axis_shape), math.prod(axis_shape)) + else: # all axes included in axis so result will have 1 element + coords_2d = (axis_ravel,) + shape_2d = (math.prod(axis_shape),) + non_axis_shape = () + + new_coo = coo_array((coo.data, coords_2d), shape=shape_2d) + return new_coo, non_axis_shape + + +def _ravel_coords(coords, shape, order='C'): + """Like np.ravel_multi_index, but avoids some overflow issues.""" + if len(coords) == 1: + return coords[0] + # Handle overflow as in https://github.com/scipy/scipy/pull/9132 + if len(coords) == 2: + nrows, ncols = shape + row, col = coords + if order == 'C': + maxval = (ncols * max(0, nrows - 1) + max(0, ncols - 1)) + idx_dtype = get_index_dtype(maxval=maxval) + return np.multiply(ncols, row, dtype=idx_dtype) + col + elif order == 'F': + maxval = (nrows * max(0, ncols - 1) + max(0, nrows - 1)) + idx_dtype = get_index_dtype(maxval=maxval) + return np.multiply(nrows, col, dtype=idx_dtype) + row + else: + raise ValueError("'order' must be 'C' or 'F'") + return np.ravel_multi_index(coords, shape, order=order) + + +def isspmatrix_coo(x): + """Is `x` of coo_matrix type? + + .. warning:: + + SciPy sparse is shifting from a sparse matrix interface to a sparse + array interface. In the next few releases we expect to deprecate the + sparse matrix interface. For documentation of the matrix + interface, see the :ref:`spmatrix interface docs `. + For guidance on converting existing code to sparse arrays, see + :ref:`Migration from spmatrix to sparray `. + + Parameters + ---------- + x + object to check for being a coo matrix + + Returns + ------- + bool + True if `x` is a coo matrix, False otherwise + + Examples + -------- + >>> from scipy.sparse import coo_array, coo_matrix, csr_matrix, isspmatrix_coo + >>> isspmatrix_coo(coo_matrix([[5]])) + True + >>> isspmatrix_coo(coo_array([[5]])) + False + >>> isspmatrix_coo(csr_matrix([[5]])) + False + """ + return isinstance(x, coo_matrix) + + +# This namespace class separates array from matrix with isinstance +class coo_array(_coo_base, sparray): + """ + A sparse array in COOrdinate format. + + Also known as the 'ijv' or 'triplet' format. + + This can be instantiated in several ways: + coo_array(D) + where D is an ndarray + + coo_array(S) + with another sparse array or matrix S (equivalent to S.tocoo()) + + coo_array(shape, [dtype]) + to construct an empty sparse array with shape `shape` + dtype is optional, defaulting to dtype='d'. + + coo_array((data, coords), [shape]) + to construct from existing data and index arrays: + 1. data[:] the entries of the sparse array, in any order + 2. coords[i][:] the axis-i coordinates of the data entries + + Where ``A[coords] = data``, and coords is a tuple of index arrays. + When shape is not specified, it is inferred from the index arrays. + + Attributes + ---------- + data : ndarray + COO format data array of the sparse array + coords : tuple of ndarray + COO format tuple of index arrays + has_canonical_format : bool + Whether the matrix has sorted coordinates and no duplicates + dtype : dtype + Data type of the array + shape : tuple of integers + Shape of the array + ndim : int + Number of dimensions of the array + format : str + Three letter code for the format of the array storage, e.g. 'coo' + nnz : int + Number of values stored in the array + size : int + Number of values stored in the array + T : coo_array + The transpose of the array + mT : coo_array + The matrix transpose of the array + + Notes + ----- + + Sparse arrays can be used in arithmetic operations: they support + addition, subtraction, multiplication, division, and matrix power. + + Advantages of the COO format + - facilitates fast conversion among sparse formats + - permits duplicate entries (see example) + - very fast conversion to and from CSR/CSC formats + + Disadvantages of the COO format + - does not directly support: + + arithmetic operations + + slicing + + Intended Usage + - COO is a fast format for constructing sparse arrays + - Once a COO array has been constructed, convert to CSR or + CSC format for fast arithmetic and matrix vector operations + - By default when converting to CSR or CSC format, duplicate (i,j) + entries will be summed together. This facilitates efficient + construction of finite element matrices and the like. (see example) + + Canonical format + - Entries and coordinates sorted by row, then column. + - There are no duplicate entries (i.e. duplicate (i,j) locations) + - Data arrays MAY have explicit zeros. + + Examples + -------- + + >>> # Constructing an empty sparse array + >>> import numpy as np + >>> from scipy.sparse import coo_array + >>> coo_array((3, 4), dtype=np.int8).toarray() + array([[0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0]], dtype=int8) + + >>> # Constructing a sparse array using ijv format + >>> row = np.array([0, 3, 1, 0]) + >>> col = np.array([0, 3, 1, 2]) + >>> data = np.array([4, 5, 7, 9]) + >>> coo_array((data, (row, col)), shape=(4, 4)).toarray() + array([[4, 0, 9, 0], + [0, 7, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 5]]) + + >>> # Constructing a sparse array with duplicate coordinates + >>> row = np.array([0, 0, 1, 3, 1, 0, 0]) + >>> col = np.array([0, 2, 1, 3, 1, 0, 0]) + >>> data = np.array([1, 1, 1, 1, 1, 1, 1]) + >>> coo = coo_array((data, (row, col)), shape=(4, 4)) + >>> # Duplicate coordinates are maintained until implicitly or explicitly summed + >>> np.max(coo.data) + 1 + >>> coo.toarray() + array([[3, 0, 1, 0], + [0, 2, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 1]]) + + """ # numpydoc ignore=PR01 + + +class coo_matrix(spmatrix, _coo_base): + """ + A sparse matrix in COOrdinate format. + + Also known as the 'ijv' or 'triplet' format. + + .. warning:: + + SciPy sparse is shifting from a sparse matrix interface to a sparse + array interface. In the next few releases we expect to deprecate the + sparse matrix interface. For documentation of the matrix + interface, see the :ref:`spmatrix interface docs `. + For guidance on converting existing code to sparse arrays, see + :ref:`Migration from spmatrix to sparray `. + + This can be instantiated in several ways: + coo_matrix(D) + where D is a 2-D ndarray + + coo_matrix(S) + with another sparse array or matrix S (equivalent to S.tocoo()) + + coo_matrix((M, N), [dtype]) + to construct an empty matrix with shape (M, N) + dtype is optional, defaulting to dtype='d'. + + coo_matrix((data, (i, j)), [shape=(M, N)]) + to construct from three arrays: + 1. data[:] the entries of the matrix, in any order + 2. i[:] the row indices of the matrix entries + 3. j[:] the column indices of the matrix entries + + Where ``A[i[k], j[k]] = data[k]``. When shape is not + specified, it is inferred from the index arrays + + Attributes + ---------- + data : ndarray + COO format data array of the sparse matrix + coords : tuple of ndarray + COO format tuple of index matrix + has_canonical_format : bool + Whether the matrix has sorted coordinates and no duplicates + dtype : dtype + Data type of the matrix + shape : tuple of integers + Shape of the matrix + ndim : int + Number of dimensions of the matrix + format : str + Three letter code for the format of the matrix storage, e.g. 'coo' + nnz : int + Number of values stored in the matrix + size : int + Number of values stored in the matrix + T : coo_matrix + The transpose of the matrix + mT : coo_matrix + The matrix transpose + + Notes + ----- + + Sparse matrices can be used in arithmetic operations: they support + addition, subtraction, multiplication, division, and matrix power. + + Advantages of the COO format + - facilitates fast conversion among sparse formats + - permits duplicate entries (see example) + - very fast conversion to and from CSR/CSC formats + + Disadvantages of the COO format + - does not directly support: + + arithmetic operations + + slicing + + Intended Usage + - COO is a fast format for constructing sparse matrices + - Once a COO matrix has been constructed, convert to CSR or + CSC format for fast arithmetic and matrix vector operations + - By default when converting to CSR or CSC format, duplicate (i,j) + entries will be summed together. This facilitates efficient + construction of finite element matrices and the like. (see example) + + Canonical format + - Entries and coordinates sorted by row, then column. + - There are no duplicate entries (i.e. duplicate (i,j) locations) + - Data arrays MAY have explicit zeros. + + Examples + -------- + + >>> # Constructing an empty matrix + >>> import numpy as np + >>> from scipy.sparse import coo_matrix + >>> coo_matrix((3, 4), dtype=np.int8).toarray() + array([[0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0]], dtype=int8) + + >>> # Constructing a matrix using ijv format + >>> row = np.array([0, 3, 1, 0]) + >>> col = np.array([0, 3, 1, 2]) + >>> data = np.array([4, 5, 7, 9]) + >>> coo_matrix((data, (row, col)), shape=(4, 4)).toarray() + array([[4, 0, 9, 0], + [0, 7, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 5]]) + + >>> # Constructing a matrix with duplicate coordinates + >>> row = np.array([0, 0, 1, 3, 1, 0, 0]) + >>> col = np.array([0, 2, 1, 3, 1, 0, 0]) + >>> data = np.array([1, 1, 1, 1, 1, 1, 1]) + >>> coo = coo_matrix((data, (row, col)), shape=(4, 4)) + >>> # Duplicate coordinates are maintained until implicitly or explicitly summed + >>> np.max(coo.data) + 1 + >>> coo.toarray() + array([[3, 0, 1, 0], + [0, 2, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 1]]) + + """ + + def __setstate__(self, state): + if 'coords' not in state: + # For retro-compatibility with the previous attributes + # storing nnz coordinates for 2D COO matrix. + state['coords'] = (state.pop('row'), state.pop('col')) + self.__dict__.update(state) + + def __getitem__(self, key): + raise TypeError("'coo_matrix' object is not subscriptable") + + def __setitem__(self, key, x): + raise TypeError("'coo_matrix' object does not support item assignment") diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/_csc.py b/venv/lib/python3.12/site-packages/scipy/sparse/_csc.py new file mode 100644 index 0000000000000000000000000000000000000000..a9917071a9e2cebfc0da6dfb75ddfae14c86c92a --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/_csc.py @@ -0,0 +1,403 @@ +"""Compressed Sparse Column matrix format""" +__docformat__ = "restructuredtext en" + +__all__ = ['csc_array', 'csc_matrix', 'isspmatrix_csc'] + + +import numpy as np + +from ._matrix import spmatrix +from ._base import _spbase, sparray +from ._sparsetools import csr_tocsc, expandptr +from ._sputils import upcast + +from ._compressed import _cs_matrix + + +class _csc_base(_cs_matrix): + _format = 'csc' + + def transpose(self, axes=None, copy=False): + if axes is not None and axes != (1, 0): + raise ValueError("Sparse arrays/matrices do not support " + "an 'axes' parameter because swapping " + "dimensions is the only logical permutation.") + + M, N = self.shape + + return self._csr_container((self.data, self.indices, + self.indptr), (N, M), copy=copy) + + transpose.__doc__ = _spbase.transpose.__doc__ + + def __iter__(self): + yield from self.tocsr() + + def tocsc(self, copy=False): + if copy: + return self.copy() + else: + return self + + tocsc.__doc__ = _spbase.tocsc.__doc__ + + def tocsr(self, copy=False): + M,N = self.shape + idx_dtype = self._get_index_dtype((self.indptr, self.indices), + maxval=max(self.nnz, N)) + indptr = np.empty(M + 1, dtype=idx_dtype) + indices = np.empty(self.nnz, dtype=idx_dtype) + data = np.empty(self.nnz, dtype=upcast(self.dtype)) + + csr_tocsc(N, M, + self.indptr.astype(idx_dtype, copy=False), + self.indices.astype(idx_dtype, copy=False), + self.data, + indptr, + indices, + data) + + A = self._csr_container( + (data, indices, indptr), + shape=self.shape, copy=False + ) + A.has_sorted_indices = True + return A + + tocsr.__doc__ = _spbase.tocsr.__doc__ + + def nonzero(self): + # CSC can't use _cs_matrix's .nonzero method because it + # returns the indices sorted for self transposed. + + # Get row and col indices, from _cs_matrix.tocoo + major_dim, minor_dim = self._swap(self.shape) + minor_indices = self.indices + major_indices = np.empty(len(minor_indices), dtype=self.indices.dtype) + expandptr(major_dim, self.indptr, major_indices) + row, col = self._swap((major_indices, minor_indices)) + + # Remove explicit zeros + nz_mask = self.data != 0 + row = row[nz_mask] + col = col[nz_mask] + + # Sort them to be in C-style order + ind = np.argsort(row, kind='mergesort') + row = row[ind] + col = col[ind] + + return row, col + + nonzero.__doc__ = _cs_matrix.nonzero.__doc__ + + def _getrow(self, i): + """Returns a copy of row i of the matrix, as a (1 x n) + CSR matrix (row vector). + """ + M, N = self.shape + i = int(i) + if i < 0: + i += M + if i < 0 or i >= M: + raise IndexError(f'index ({i}) out of range') + return self._get_submatrix(minor=i).tocsr() + + def _getcol(self, i): + """Returns a copy of column i of the matrix, as a (m x 1) + CSC matrix (column vector). + """ + M, N = self.shape + i = int(i) + if i < 0: + i += N + if i < 0 or i >= N: + raise IndexError(f'index ({i}) out of range') + return self._get_submatrix(major=i, copy=True) + + def _get_intXarray(self, row, col): + return self._major_index_fancy(col)._get_submatrix(minor=row) + + def _get_intXslice(self, row, col): + if col.step in (1, None): + return self._get_submatrix(major=col, minor=row, copy=True) + return self._major_slice(col)._get_submatrix(minor=row) + + def _get_sliceXint(self, row, col): + if row.step in (1, None): + return self._get_submatrix(major=col, minor=row, copy=True) + return self._get_submatrix(major=col)._minor_slice(row) + + def _get_sliceXarray(self, row, col): + return self._major_index_fancy(col)._minor_slice(row) + + def _get_arrayXint(self, row, col): + res = self._get_submatrix(major=col)._minor_index_fancy(row) + if row.ndim > 1: + return res.reshape(row.shape) + return res + + def _get_arrayXslice(self, row, col): + return self._major_slice(col)._minor_index_fancy(row) + + # these functions are used by the parent class (_cs_matrix) + # to remove redundancy between csc_array and csr_matrix + @staticmethod + def _swap(x): + """swap the members of x if this is a column-oriented matrix + """ + return x[1], x[0] + + +def isspmatrix_csc(x): + """Is `x` of csc_matrix type? + + .. warning:: + + SciPy sparse is shifting from a sparse matrix interface to a sparse + array interface. In the next few releases we expect to deprecate the + sparse matrix interface. For documentation of the matrix + interface, see the :ref:`spmatrix interface docs `. + For guidance on converting existing code to sparse arrays, see + :ref:`Migration from spmatrix to sparray `. + + Parameters + ---------- + x + object to check for being a csc matrix + + Returns + ------- + bool + True if `x` is a csc matrix, False otherwise + + Examples + -------- + >>> from scipy.sparse import csc_array, csc_matrix, coo_matrix, isspmatrix_csc + >>> isspmatrix_csc(csc_matrix([[5]])) + True + >>> isspmatrix_csc(csc_array([[5]])) + False + >>> isspmatrix_csc(coo_matrix([[5]])) + False + """ + return isinstance(x, csc_matrix) + + +# This namespace class separates array from matrix with isinstance +class csc_array(_csc_base, sparray): + """ + Compressed Sparse Column array. + + This can be instantiated in several ways: + csc_array(D) + where D is a 2-D ndarray + + csc_array(S) + with another sparse array or matrix S (equivalent to S.tocsc()) + + csc_array((M, N), [dtype]) + to construct an empty array with shape (M, N) + dtype is optional, defaulting to dtype='d'. + + csc_array((data, (row_ind, col_ind)), [shape=(M, N)]) + where ``data``, ``row_ind`` and ``col_ind`` satisfy the + relationship ``a[row_ind[k], col_ind[k]] = data[k]``. + + csc_array((data, indices, indptr), [shape=(M, N)]) + is the standard CSC representation where the row indices for + column i are stored in ``indices[indptr[i]:indptr[i+1]]`` + and their corresponding values are stored in + ``data[indptr[i]:indptr[i+1]]``. If the shape parameter is + not supplied, the array dimensions are inferred from + the index arrays. + + Attributes + ---------- + data : ndarray + CSC format data array of the array + indices : ndarray + CSC format index array of the array + indptr : ndarray + CSC format index pointer array of the array + has_sorted_indices : bool + Whether indices are sorted + has_canonical_format : bool + Whether indices are sorted and no duplicate entries exist + dtype : dtype + Data type of the array + shape : 2-tuple + Shape of the array + ndim : int + Number of dimensions (this is always 2) + format : str + Three letter code for the format of the array storage, e.g. 'csc' + nnz : int + Number of values stored in the array + size : int + Number of values stored in the array + T : csc_array + The transpose of the array + mT : csc_array + The matrix transpose of the array + + Notes + ----- + + Sparse arrays can be used in arithmetic operations: they support + addition, subtraction, multiplication, division, and matrix power. + + Advantages of the CSC format + - efficient arithmetic operations CSC + CSC, CSC * CSC, etc. + - efficient column slicing + - fast matrix vector products (CSR, BSR may be faster) + + Disadvantages of the CSC format + - slow row slicing operations (consider CSR) + - changes to the sparsity structure are expensive (consider LIL or DOK) + + Canonical format + - Within each column, indices are sorted by row. + - There are no duplicate entries. + + Examples + -------- + + >>> import numpy as np + >>> from scipy.sparse import csc_array + >>> csc_array((3, 4), dtype=np.int8).toarray() + array([[0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0]], dtype=int8) + + >>> row = np.array([0, 2, 2, 0, 1, 2]) + >>> col = np.array([0, 0, 1, 2, 2, 2]) + >>> data = np.array([1, 2, 3, 4, 5, 6]) + >>> csc_array((data, (row, col)), shape=(3, 3)).toarray() + array([[1, 0, 4], + [0, 0, 5], + [2, 3, 6]]) + + >>> indptr = np.array([0, 2, 3, 6]) + >>> indices = np.array([0, 2, 2, 0, 1, 2]) + >>> data = np.array([1, 2, 3, 4, 5, 6]) + >>> csc_array((data, indices, indptr), shape=(3, 3)).toarray() + array([[1, 0, 4], + [0, 0, 5], + [2, 3, 6]]) + + """ # numpydoc ignore=PR01 + + +class csc_matrix(spmatrix, _csc_base): + """ + Compressed Sparse Column matrix. + + .. warning:: + + SciPy sparse is shifting from a sparse matrix interface to a sparse + array interface. In the next few releases we expect to deprecate the + sparse matrix interface. For documentation of the matrix + interface, see the :ref:`spmatrix interface docs `. + For guidance on converting existing code to sparse arrays, see + :ref:`Migration from spmatrix to sparray `. + + This can be instantiated in several ways: + csc_matrix(D) + where D is a 2-D ndarray + + csc_matrix(S) + with another sparse array or matrix S (equivalent to S.tocsc()) + + csc_matrix((M, N), [dtype]) + to construct an empty matrix with shape (M, N) + dtype is optional, defaulting to dtype='d'. + + csc_matrix((data, (row_ind, col_ind)), [shape=(M, N)]) + where ``data``, ``row_ind`` and ``col_ind`` satisfy the + relationship ``a[row_ind[k], col_ind[k]] = data[k]``. + + csc_matrix((data, indices, indptr), [shape=(M, N)]) + is the standard CSC representation where the row indices for + column i are stored in ``indices[indptr[i]:indptr[i+1]]`` + and their corresponding values are stored in + ``data[indptr[i]:indptr[i+1]]``. If the shape parameter is + not supplied, the matrix dimensions are inferred from + the index arrays. + + Attributes + ---------- + data : ndarray + CSC format data array of the matrix + indices : ndarray + CSC format index array of the matrix + indptr : ndarray + CSC format index pointer array of the matrix + has_sorted_indices : bool + Whether indices are sorted + has_canonical_format : bool + Whether indices are sorted and no duplicate entries exist + dtype : dtype + Data type of the matrix + shape : 2-tuple + Shape of the matrix + ndim : int + Number of dimensions (this is always 2) + format : str + Three letter code for the format of the matrix storage, e.g. 'csc' + nnz : int + Number of values stored in the matrix + size : int + Number of values stored in the matrix + T : csc_matrix + The transpose of the matrix + mT : csc_matrix + The matrix transpose + + Notes + ----- + + Sparse matrices can be used in arithmetic operations: they support + addition, subtraction, multiplication, division, and matrix power. + + Advantages of the CSC format + - efficient arithmetic operations CSC + CSC, CSC * CSC, etc. + - efficient column slicing + - fast matrix vector products (CSR, BSR may be faster) + + Disadvantages of the CSC format + - slow row slicing operations (consider CSR) + - changes to the sparsity structure are expensive (consider LIL or DOK) + + Canonical format + - Within each column, indices are sorted by row. + - There are no duplicate entries. + + Examples + -------- + + >>> import numpy as np + >>> from scipy.sparse import csc_matrix + >>> csc_matrix((3, 4), dtype=np.int8).toarray() + array([[0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0]], dtype=int8) + + >>> row = np.array([0, 2, 2, 0, 1, 2]) + >>> col = np.array([0, 0, 1, 2, 2, 2]) + >>> data = np.array([1, 2, 3, 4, 5, 6]) + >>> csc_matrix((data, (row, col)), shape=(3, 3)).toarray() + array([[1, 0, 4], + [0, 0, 5], + [2, 3, 6]]) + + >>> indptr = np.array([0, 2, 3, 6]) + >>> indices = np.array([0, 2, 2, 0, 1, 2]) + >>> data = np.array([1, 2, 3, 4, 5, 6]) + >>> csc_matrix((data, indices, indptr), shape=(3, 3)).toarray() + array([[1, 0, 4], + [0, 0, 5], + [2, 3, 6]]) + + """ + diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/_csparsetools.cpython-312-x86_64-linux-gnu.so b/venv/lib/python3.12/site-packages/scipy/sparse/_csparsetools.cpython-312-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..910b4ba1d676077c68b7cc68f9cf46b657cd0bfb --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/_csparsetools.cpython-312-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b7ca396a0686f87c2767e5bbeddf2eb5d6d40894d0283774584aed7cde87b19d +size 441808 diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/_csr.py b/venv/lib/python3.12/site-packages/scipy/sparse/_csr.py new file mode 100644 index 0000000000000000000000000000000000000000..fd1e6ac6d2c8ecab7ed000e1f527dc317bb0a3a8 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/_csr.py @@ -0,0 +1,604 @@ +"""Compressed Sparse Row matrix format""" + +__docformat__ = "restructuredtext en" + +__all__ = ['csr_array', 'csr_matrix', 'isspmatrix_csr'] + +import numpy as np + +from ._matrix import spmatrix +from ._base import _spbase, sparray +from ._sparsetools import (csr_tocsc, csr_tobsr, csr_count_blocks, + get_csr_submatrix, csr_sample_values) +from ._sputils import upcast + +from ._compressed import _cs_matrix + + +class _csr_base(_cs_matrix): + _format = 'csr' + _allow_nd = (1, 2) + + def transpose(self, axes=None, copy=False): + if axes is not None and axes != (1, 0): + raise ValueError("Sparse arrays/matrices do not support " + "an 'axes' parameter because swapping " + "dimensions is the only logical permutation.") + + if self.ndim == 1: + return self.copy() if copy else self + M, N = self.shape + return self._csc_container((self.data, self.indices, + self.indptr), shape=(N, M), copy=copy) + + transpose.__doc__ = _spbase.transpose.__doc__ + + def tolil(self, copy=False): + if self.ndim != 2: + raise ValueError("Cannot convert a 1d sparse array to lil format") + lil = self._lil_container(self.shape, dtype=self.dtype) + + self.sum_duplicates() + ptr,ind,dat = self.indptr,self.indices,self.data + rows, data = lil.rows, lil.data + + for n in range(self.shape[0]): + start = ptr[n] + end = ptr[n+1] + rows[n] = ind[start:end].tolist() + data[n] = dat[start:end].tolist() + + return lil + + tolil.__doc__ = _spbase.tolil.__doc__ + + def tocsr(self, copy=False): + if copy: + return self.copy() + else: + return self + + tocsr.__doc__ = _spbase.tocsr.__doc__ + + def tocoo(self, copy=False): + A = super().tocoo(copy=copy) + # CSR-to-COO conversion always preserves [non-]canonicity + # (indices sorting, presense of duplicate elements). + # Handled here instead of _cs_matrix because CSC-to-COO generally does not. + A.has_canonical_format = self.has_canonical_format + return A + + tocoo.__doc__ = _spbase.tocoo.__doc__ + + def tocsc(self, copy=False): + if self.ndim != 2: + raise ValueError("Cannot convert a 1d sparse array to csc format") + M, N = self.shape + idx_dtype = self._get_index_dtype((self.indptr, self.indices), + maxval=max(self.nnz, M)) + indptr = np.empty(N + 1, dtype=idx_dtype) + indices = np.empty(self.nnz, dtype=idx_dtype) + data = np.empty(self.nnz, dtype=upcast(self.dtype)) + + csr_tocsc(M, N, + self.indptr.astype(idx_dtype, copy=False), + self.indices.astype(idx_dtype, copy=False), + self.data, + indptr, + indices, + data) + + A = self._csc_container((data, indices, indptr), shape=self.shape) + A.has_sorted_indices = True + return A + + tocsc.__doc__ = _spbase.tocsc.__doc__ + + def tobsr(self, blocksize=None, copy=True): + if self.ndim != 2: + raise ValueError("Cannot convert a 1d sparse array to bsr format") + if blocksize is None: + from ._spfuncs import estimate_blocksize + return self.tobsr(blocksize=estimate_blocksize(self)) + + elif blocksize == (1,1): + arg1 = (self.data.reshape(-1,1,1),self.indices,self.indptr) + return self._bsr_container(arg1, shape=self.shape, copy=copy) + + else: + R,C = blocksize + M,N = self.shape + + if R < 1 or C < 1 or M % R != 0 or N % C != 0: + raise ValueError(f'invalid blocksize {blocksize}') + + blks = csr_count_blocks(M,N,R,C,self.indptr,self.indices) + + idx_dtype = self._get_index_dtype((self.indptr, self.indices), + maxval=max(N//C, blks)) + indptr = np.empty(M//R+1, dtype=idx_dtype) + indices = np.empty(blks, dtype=idx_dtype) + data = np.zeros((blks,R,C), dtype=self.dtype) + + csr_tobsr(M, N, R, C, + self.indptr.astype(idx_dtype, copy=False), + self.indices.astype(idx_dtype, copy=False), + self.data, + indptr, indices, data.ravel()) + + return self._bsr_container( + (data, indices, indptr), shape=self.shape + ) + + tobsr.__doc__ = _spbase.tobsr.__doc__ + + # these functions are used by the parent class (_cs_matrix) + # to remove redundancy between csc_matrix and csr_array + @staticmethod + def _swap(x): + """swap the members of x if this is a column-oriented matrix + """ + return x + + def __iter__(self): + if self.ndim == 1: + zero = self.dtype.type(0) + u = 0 + for v, d in zip(self.indices, self.data): + for _ in range(v - u): + yield zero + yield d + u = v + 1 + for _ in range(self.shape[0] - u): + yield zero + return + + indptr = np.zeros(2, dtype=self.indptr.dtype) + # return 1d (sparray) or 2drow (spmatrix) + shape = self.shape[1:] if isinstance(self, sparray) else (1, self.shape[1]) + i0 = 0 + for i1 in self.indptr[1:]: + indptr[1] = i1 - i0 + indices = self.indices[i0:i1] + data = self.data[i0:i1] + yield self.__class__((data, indices, indptr), shape=shape, copy=True) + i0 = i1 + + def _getrow(self, i): + """Returns a copy of row i of the matrix, as a (1 x n) + CSR matrix (row vector). + """ + if self.ndim == 1: + if i not in (0, -1): + raise IndexError(f'index ({i}) out of range') + return self.reshape((1, self.shape[0]), copy=True) + + M, N = self.shape + i = int(i) + if i < 0: + i += M + if i < 0 or i >= M: + raise IndexError(f'index ({i}) out of range') + indptr, indices, data = get_csr_submatrix( + M, N, self.indptr, self.indices, self.data, i, i + 1, 0, N) + return self.__class__((data, indices, indptr), shape=(1, N), + dtype=self.dtype, copy=False) + + def _getcol(self, i): + """Returns a copy of column i. A (m x 1) sparse array (column vector). + """ + if self.ndim == 1: + raise ValueError("getcol not provided for 1d arrays. Use indexing A[j]") + M, N = self.shape + i = int(i) + if i < 0: + i += N + if i < 0 or i >= N: + raise IndexError(f'index ({i}) out of range') + indptr, indices, data = get_csr_submatrix( + M, N, self.indptr, self.indices, self.data, 0, M, i, i + 1) + return self.__class__((data, indices, indptr), shape=(M, 1), + dtype=self.dtype, copy=False) + + def _get_int(self, idx): + spot = np.flatnonzero(self.indices == idx) + if spot.size: + return self.data[spot[0]] + return self.data.dtype.type(0) + + def _get_slice(self, idx): + if idx == slice(None): + return self.copy() + if idx.step in (1, None): + ret = self._get_submatrix(0, idx, copy=True) + return ret.reshape(ret.shape[-1]) + return self._minor_slice(idx) + + def _get_array(self, idx): + idx_dtype = self._get_index_dtype(self.indices) + idx = np.asarray(idx, dtype=idx_dtype) + if idx.size == 0: + return self.__class__([], dtype=self.dtype) + + M, N = 1, self.shape[0] + row = np.zeros_like(idx, dtype=idx_dtype) + col = np.asarray(idx, dtype=idx_dtype) + val = np.empty(row.size, dtype=self.dtype) + csr_sample_values(M, N, self.indptr, self.indices, self.data, + row.size, row, col, val) + + new_shape = col.shape if col.shape[0] > 1 else (col.shape[0],) + return self.__class__(val.reshape(new_shape)) + + def _get_intXarray(self, row, col): + return self._getrow(row)._minor_index_fancy(col) + + def _get_intXslice(self, row, col): + if col.step in (1, None): + return self._get_submatrix(row, col, copy=True) + # TODO: uncomment this once it's faster: + # return self._getrow(row)._minor_slice(col) + + M, N = self.shape + start, stop, stride = col.indices(N) + + ii, jj = self.indptr[row:row+2] + row_indices = self.indices[ii:jj] + row_data = self.data[ii:jj] + + if stride > 0: + ind = (row_indices >= start) & (row_indices < stop) + else: + ind = (row_indices <= start) & (row_indices > stop) + + if abs(stride) > 1: + ind &= (row_indices - start) % stride == 0 + + row_indices = (row_indices[ind] - start) // stride + row_data = row_data[ind] + row_indptr = np.array([0, len(row_indices)]) + + if stride < 0: + row_data = row_data[::-1] + row_indices = abs(row_indices[::-1]) + + shape = (1, max(0, int(np.ceil(float(stop - start) / stride)))) + return self.__class__((row_data, row_indices, row_indptr), shape=shape, + dtype=self.dtype, copy=False) + + def _get_sliceXint(self, row, col): + if row.step in (1, None): + return self._get_submatrix(row, col, copy=True) + return self._major_slice(row)._get_submatrix(minor=col) + + def _get_sliceXarray(self, row, col): + return self._major_slice(row)._minor_index_fancy(col) + + def _get_arrayXint(self, row, col): + res = self._major_index_fancy(row)._get_submatrix(minor=col) + if row.ndim > 1: + return res.reshape(row.shape) + return res + + def _get_arrayXslice(self, row, col): + if col.step not in (1, None): + col = np.arange(*col.indices(self.shape[1])) + return self._get_arrayXarray(row, col) + return self._major_index_fancy(row)._get_submatrix(minor=col) + + def _set_int(self, idx, x): + self._set_many(0, idx, x) + + def _set_array(self, idx, x): + x = np.broadcast_to(x, idx.shape) + self._set_many(np.zeros_like(idx), idx, x) + + +def isspmatrix_csr(x): + """Is `x` of csr_matrix type? + + .. warning:: + + SciPy sparse is shifting from a sparse matrix interface to a sparse + array interface. In the next few releases we expect to deprecate the + sparse matrix interface. For documentation of the matrix + interface, see the :ref:`spmatrix interface docs `. + For guidance on converting existing code to sparse arrays, see + :ref:`Migration from spmatrix to sparray `. + + Parameters + ---------- + x + object to check for being a csr matrix + + Returns + ------- + bool + True if `x` is a csr matrix, False otherwise + + Examples + -------- + >>> from scipy.sparse import csr_array, csr_matrix, coo_matrix, isspmatrix_csr + >>> isspmatrix_csr(csr_matrix([[5]])) + True + >>> isspmatrix_csr(csr_array([[5]])) + False + >>> isspmatrix_csr(coo_matrix([[5]])) + False + """ + return isinstance(x, csr_matrix) + + +# This namespace class separates array from matrix with isinstance +class csr_array(_csr_base, sparray): + """ + Compressed Sparse Row array. + + This can be instantiated in several ways: + csr_array(D) + where D is a 2-D ndarray + + csr_array(S) + with another sparse array or matrix S (equivalent to S.tocsr()) + + csr_array((M, N), [dtype]) + to construct an empty array with shape (M, N) + dtype is optional, defaulting to dtype='d'. + + csr_array((data, (row_ind, col_ind)), [shape=(M, N)]) + where ``data``, ``row_ind`` and ``col_ind`` satisfy the + relationship ``a[row_ind[k], col_ind[k]] = data[k]``. + + csr_array((data, indices, indptr), [shape=(M, N)]) + is the standard CSR representation where the column indices for + row i are stored in ``indices[indptr[i]:indptr[i+1]]`` and their + corresponding values are stored in ``data[indptr[i]:indptr[i+1]]``. + If the shape parameter is not supplied, the array dimensions + are inferred from the index arrays. + + Attributes + ---------- + data : ndarray + CSR format data array of the array + indices : ndarray + CSR format index array of the array + indptr : ndarray + CSR format index pointer array of the array + has_sorted_indices : bool + Whether indices are sorted + has_canonical_format : bool + Whether indices are sorted and no duplicate entries exist + dtype : dtype + Data type of the array + shape : 2-tuple + Shape of the array + ndim : int + Number of dimensions (this is always 2) + format : str + Three letter code for the format of the array storage, e.g. 'csr' + nnz : int + Number of values stored in the array + size : int + Number of values stored in the array + T : csr_array + The transpose of the array + mT : csr_array + The matrix transpose of the array + + Notes + ----- + + Sparse arrays can be used in arithmetic operations: they support + addition, subtraction, multiplication, division, and matrix power. + + Advantages of the CSR format + - efficient arithmetic operations CSR + CSR, CSR * CSR, etc. + - efficient row slicing + - fast matrix vector products + + Disadvantages of the CSR format + - slow column slicing operations (consider CSC) + - changes to the sparsity structure are expensive (consider LIL or DOK) + + Canonical Format + - Within each row, indices are sorted by column. + - There are no duplicate entries. + + Examples + -------- + + >>> import numpy as np + >>> from scipy.sparse import csr_array + >>> csr_array((3, 4), dtype=np.int8).toarray() + array([[0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0]], dtype=int8) + + >>> row = np.array([0, 0, 1, 2, 2, 2]) + >>> col = np.array([0, 2, 2, 0, 1, 2]) + >>> data = np.array([1, 2, 3, 4, 5, 6]) + >>> csr_array((data, (row, col)), shape=(3, 3)).toarray() + array([[1, 0, 2], + [0, 0, 3], + [4, 5, 6]]) + + >>> indptr = np.array([0, 2, 3, 6]) + >>> indices = np.array([0, 2, 2, 0, 1, 2]) + >>> data = np.array([1, 2, 3, 4, 5, 6]) + >>> csr_array((data, indices, indptr), shape=(3, 3)).toarray() + array([[1, 0, 2], + [0, 0, 3], + [4, 5, 6]]) + + Duplicate entries are summed together: + + >>> row = np.array([0, 1, 2, 0]) + >>> col = np.array([0, 1, 1, 0]) + >>> data = np.array([1, 2, 4, 8]) + >>> csr_array((data, (row, col)), shape=(3, 3)).toarray() + array([[9, 0, 0], + [0, 2, 0], + [0, 4, 0]]) + + As an example of how to construct a CSR array incrementally, + the following snippet builds a term-document array from texts: + + >>> docs = [["hello", "world", "hello"], ["goodbye", "cruel", "world"]] + >>> indptr = [0] + >>> indices = [] + >>> data = [] + >>> vocabulary = {} + >>> for d in docs: + ... for term in d: + ... index = vocabulary.setdefault(term, len(vocabulary)) + ... indices.append(index) + ... data.append(1) + ... indptr.append(len(indices)) + ... + >>> csr_array((data, indices, indptr), dtype=int).toarray() + array([[2, 1, 0, 0], + [0, 1, 1, 1]]) + + """ # numpydoc ignore=PR01 + + +class csr_matrix(spmatrix, _csr_base): + """ + Compressed Sparse Row matrix. + + .. warning:: + + SciPy sparse is shifting from a sparse matrix interface to a sparse + array interface. In the next few releases we expect to deprecate the + sparse matrix interface. For documentation of the matrix + interface, see the :ref:`spmatrix interface docs `. + For guidance on converting existing code to sparse arrays, see + :ref:`Migration from spmatrix to sparray `. + + This can be instantiated in several ways: + csr_matrix(D) + where D is a 2-D ndarray + + csr_matrix(S) + with another sparse array or matrix S (equivalent to S.tocsr()) + + csr_matrix((M, N), [dtype]) + to construct an empty matrix with shape (M, N) + dtype is optional, defaulting to dtype='d'. + + csr_matrix((data, (row_ind, col_ind)), [shape=(M, N)]) + where ``data``, ``row_ind`` and ``col_ind`` satisfy the + relationship ``a[row_ind[k], col_ind[k]] = data[k]``. + + csr_matrix((data, indices, indptr), [shape=(M, N)]) + is the standard CSR representation where the column indices for + row i are stored in ``indices[indptr[i]:indptr[i+1]]`` and their + corresponding values are stored in ``data[indptr[i]:indptr[i+1]]``. + If the shape parameter is not supplied, the matrix dimensions + are inferred from the index arrays. + + Attributes + ---------- + data : ndarray + CSR format data array of the matrix + indices : ndarray + CSR format index array of the matrix + indptr : ndarray + CSR format index pointer array of the matrix + has_sorted_indices : bool + Whether indices are sorted + has_canonical_format : bool + Whether indices are sorted and no duplicate entries exist + dtype : dtype + Data type of the matrix + shape : 2-tuple + Shape of the matrix + ndim : int + Number of dimensions (this is always 2) + format : str + Three letter code for the format of the matrix storage, e.g. 'csr' + nnz : int + Number of values stored in the matrix + size : int + Number of values stored in the matrix + T : csr_matrix + The transpose of the matrix + mT : csr_matrix + The matrix transpose + + Notes + ----- + + Sparse matrices can be used in arithmetic operations: they support + addition, subtraction, multiplication, division, and matrix power. + + Advantages of the CSR format + - efficient arithmetic operations CSR + CSR, CSR * CSR, etc. + - efficient row slicing + - fast matrix vector products + + Disadvantages of the CSR format + - slow column slicing operations (consider CSC) + - changes to the sparsity structure are expensive (consider LIL or DOK) + + Canonical Format + - Within each row, indices are sorted by column. + - There are no duplicate entries. + + Examples + -------- + + >>> import numpy as np + >>> from scipy.sparse import csr_matrix + >>> csr_matrix((3, 4), dtype=np.int8).toarray() + array([[0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0]], dtype=int8) + + >>> row = np.array([0, 0, 1, 2, 2, 2]) + >>> col = np.array([0, 2, 2, 0, 1, 2]) + >>> data = np.array([1, 2, 3, 4, 5, 6]) + >>> csr_matrix((data, (row, col)), shape=(3, 3)).toarray() + array([[1, 0, 2], + [0, 0, 3], + [4, 5, 6]]) + + >>> indptr = np.array([0, 2, 3, 6]) + >>> indices = np.array([0, 2, 2, 0, 1, 2]) + >>> data = np.array([1, 2, 3, 4, 5, 6]) + >>> csr_matrix((data, indices, indptr), shape=(3, 3)).toarray() + array([[1, 0, 2], + [0, 0, 3], + [4, 5, 6]]) + + Duplicate entries are summed together: + + >>> row = np.array([0, 1, 2, 0]) + >>> col = np.array([0, 1, 1, 0]) + >>> data = np.array([1, 2, 4, 8]) + >>> csr_matrix((data, (row, col)), shape=(3, 3)).toarray() + array([[9, 0, 0], + [0, 2, 0], + [0, 4, 0]]) + + As an example of how to construct a CSR matrix incrementally, + the following snippet builds a term-document matrix from texts: + + >>> docs = [["hello", "world", "hello"], ["goodbye", "cruel", "world"]] + >>> indptr = [0] + >>> indices = [] + >>> data = [] + >>> vocabulary = {} + >>> for d in docs: + ... for term in d: + ... index = vocabulary.setdefault(term, len(vocabulary)) + ... indices.append(index) + ... data.append(1) + ... indptr.append(len(indices)) + ... + >>> csr_matrix((data, indices, indptr), dtype=int).toarray() + array([[2, 1, 0, 0], + [0, 1, 1, 1]]) + + """ + diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/_data.py b/venv/lib/python3.12/site-packages/scipy/sparse/_data.py new file mode 100644 index 0000000000000000000000000000000000000000..0edea989fee4ab800303550e3772d239dfec616a --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/_data.py @@ -0,0 +1,583 @@ +"""Base class for sparse matrice with a .data attribute + + subclasses must provide a _with_data() method that + creates a new matrix with the same sparsity pattern + as self but with a different data array + +""" + +import math +import numpy as np + +from ._base import _spbase, sparray, _ufuncs_with_fixed_point_at_zero +from ._sputils import isscalarlike, validateaxis + +__all__ = [] + + +# TODO implement all relevant operations +# use .data.__methods__() instead of /=, *=, etc. +class _data_matrix(_spbase): + def __init__(self, arg1, *, maxprint=None): + _spbase.__init__(self, arg1, maxprint=maxprint) + + @property + def dtype(self): + return self.data.dtype + + @dtype.setter + def dtype(self, newtype): + self.data = self.data.view(newtype) + + def _deduped_data(self): + if hasattr(self, 'sum_duplicates'): + self.sum_duplicates() + return self.data + + def __abs__(self): + return self._with_data(abs(self._deduped_data())) + + def __round__(self, ndigits=0): + return self._with_data(np.around(self._deduped_data(), decimals=ndigits)) + + def _real(self): + return self._with_data(self.data.real) + + def _imag(self): + return self._with_data(self.data.imag) + + def __neg__(self): + if self.dtype.kind == 'b': + raise NotImplementedError('negating a boolean sparse array is not ' + 'supported') + return self._with_data(-self.data) + + def __imul__(self, other): # self *= other + if isscalarlike(other): + self.data *= other + return self + return NotImplemented + + def __itruediv__(self, other): # self /= other + if isscalarlike(other): + recip = 1.0 / other + self.data *= recip + return self + else: + return NotImplemented + + def astype(self, dtype, casting='unsafe', copy=True): + dtype = np.dtype(dtype) + if self.dtype != dtype: + matrix = self._with_data( + self.data.astype(dtype, casting=casting, copy=True), + copy=True + ) + return matrix._with_data(matrix._deduped_data(), copy=False) + elif copy: + return self.copy() + else: + return self + + astype.__doc__ = _spbase.astype.__doc__ + + def conjugate(self, copy=True): + if np.issubdtype(self.dtype, np.complexfloating): + return self._with_data(self.data.conjugate(), copy=copy) + elif copy: + return self.copy() + else: + return self + + conjugate.__doc__ = _spbase.conjugate.__doc__ + + def copy(self): + return self._with_data(self.data.copy(), copy=True) + + copy.__doc__ = _spbase.copy.__doc__ + + def power(self, n, dtype=None): + """ + This function performs element-wise power. + + Parameters + ---------- + n : scalar + n is a non-zero scalar (nonzero avoids dense ones creation) + If zero power is desired, special case it to use `numpy.ones` + + dtype : dtype, optional + If dtype is not specified, the current dtype will be preserved. + + Returns + ------- + sparse array/matrix + Result of raising all elements to the power `n`. + + Raises + ------ + NotImplementedError : if n is a zero scalar + If zero power is desired, special case it to use + ``np.ones(A.shape, dtype=A.dtype)`` + """ + if not isscalarlike(n): + raise NotImplementedError("input is not scalar") + if not n: + raise NotImplementedError( + "zero power is not supported as it would densify the matrix.\n" + "Use `np.ones(A.shape, dtype=A.dtype)` for this case." + ) + + data = self._deduped_data() + if dtype is not None: + data = data.astype(dtype, copy=False) + return self._with_data(data ** n) + + ########################### + # Multiplication handlers # + ########################### + + def _mul_scalar(self, other): + return self._with_data(self.data * other) + + +# Add the numpy unary ufuncs for which func(0) = 0 to _data_matrix. +for npfunc in _ufuncs_with_fixed_point_at_zero: + name = npfunc.__name__ + + def _create_method(op): + def method(self): + result = op(self._deduped_data()) + return self._with_data(result, copy=True) + + method.__doc__ = ( + f"""Element-wise {name}. + See `numpy.{name}` for more information. + + Returns + ------- + {name} : sparse array / matrix + Result of applying `numpy.{name}` to the data of this sparse matrix. + """ + ) + method.__name__ = name + + return method + + setattr(_data_matrix, name, _create_method(npfunc)) + + +def _find_missing_index(ind, n): + for k, a in enumerate(ind): + if k != a: + return k + + k += 1 + if k < n: + return k + else: + return -1 + + +class _minmax_mixin: + """Mixin for min and max methods. + + These are not implemented for dia_matrix, hence the separate class. + """ + + def _min_or_max_axis(self, axis, min_or_max, explicit): + # already checked that self.shape[axis] is not zero + N = self.shape[axis] + M = self.shape[1 - axis] + idx_dtype = self._get_index_dtype(maxval=M) + + mat = self.tocsc() if axis == 0 else self.tocsr() + mat.sum_duplicates() + + major_index, value = mat._minor_reduce(min_or_max) + if not explicit: + not_full = np.diff(mat.indptr)[major_index] < N + value[not_full] = min_or_max(value[not_full], 0) + + mask = value != 0 + major_index = np.compress(mask, major_index).astype(idx_dtype, copy=False) + value = np.compress(mask, value) + + if isinstance(self, sparray): + coords = (major_index,) + shape = (M,) + return self._coo_container((value, coords), shape=shape, dtype=self.dtype) + + if axis == 0: + return self._coo_container( + (value, (np.zeros(len(value), dtype=idx_dtype), major_index)), + dtype=self.dtype, shape=(1, M) + ) + else: + return self._coo_container( + (value, (major_index, np.zeros(len(value), dtype=idx_dtype))), + dtype=self.dtype, shape=(M, 1) + ) + + def _min_or_max(self, axis, out, min_or_max, explicit): + if out is not None: + raise ValueError("Sparse min/max does not support an 'out' parameter.") + + axis = validateaxis(axis, ndim=self.ndim) + + if axis is None: + if 0 in self.shape: + raise ValueError("zero-size array to reduction operation") + + zero = self.dtype.type(0) + if self.nnz == 0: + return zero + m = min_or_max.reduce(self._deduped_data().ravel()) + if self.nnz != math.prod(self.shape) and not explicit: + m = min_or_max(zero, m) + return m + + if any(self.shape[d] == 0 for d in axis): + raise ValueError("zero-size array to reduction operation") + + if self.ndim == 2: + # note: 2D ensures that len(axis)==1 so we pass in the int axis[0] + return self._min_or_max_axis(axis[0], min_or_max, explicit) + return self._min_or_max_axis_nd(axis, min_or_max, explicit) + + def _argminmax_axis(self, axis, argminmax, compare, explicit): + zero = self.dtype.type(0) + + mat = self.tocsc() if axis == 0 else self.tocsr() + mat.sum_duplicates() + + ret_size, line_size = mat._swap(mat.shape) + ret = np.zeros(ret_size, dtype=int) + + nz_lines, = np.nonzero(np.diff(mat.indptr)) + for i in nz_lines: + p, q = mat.indptr[i:i + 2] + data = mat.data[p:q] + indices = mat.indices[p:q] + extreme_index = argminmax(data) + extreme_value = data[extreme_index] + if explicit: + if q - p > 0: + ret[i] = indices[extreme_index] + else: + if compare(extreme_value, zero) or q - p == line_size: + ret[i] = indices[extreme_index] + else: + zero_ind = _find_missing_index(indices, line_size) + if extreme_value == zero: + ret[i] = min(extreme_index, zero_ind) + else: + ret[i] = zero_ind + + if isinstance(self, sparray): + return ret + + if axis == 1: + ret = ret.reshape(-1, 1) + + return self._ascontainer(ret) + + def _argminmax(self, axis, out, argminmax, compare, explicit): + if out is not None: + minmax = "argmin" if argminmax == np.argmin else "argmax" + raise ValueError(f"Sparse {minmax} does not support an 'out' parameter.") + + axis = validateaxis(axis, ndim=self.ndim) + + if axis is not None: + if any(self.shape[i] == 0 for i in axis): + minmax = "argmin" if argminmax == np.argmin else "argmax" + raise ValueError(f"Cannot apply {minmax} along a zero-sized dimension.") + + if self.ndim == 2: + # note: 2D ensures that len(axis)==1 so we pass in the int axis[0] + return self._argminmax_axis(axis[0], argminmax, compare, explicit) + return self._argminmax_axis_nd(axis, argminmax, compare, explicit) + + if 0 in self.shape: + minmax = "argmin" if argminmax == np.argmin else "argmax" + raise ValueError(f"Cannot apply {minmax} to an empty matrix.") + + if self.nnz == 0: + if explicit: + minmax = "argmin" if argminmax == np.argmin else "argmax" + raise ValueError(f"Cannot apply {minmax} to zero matrix " + "when explicit=True.") + return 0 + + zero = self.dtype.type(0) + mat = self.tocoo() + # Convert to canonical form: no duplicates, sorted indices. + mat.sum_duplicates() + extreme_index = argminmax(mat.data) + if explicit: + return extreme_index + extreme_value = mat.data[extreme_index] + + if mat.ndim > 2: + mat = mat.reshape(-1) + # If the min value is less than zero, or max is greater than zero, + # then we do not need to worry about implicit zeros. + # And we use a "cheap test" for the rare case of no implicit zeros. + maxnnz = math.prod(self.shape) + if compare(extreme_value, zero) or mat.nnz == maxnnz: + # cast to Python int to avoid overflow and RuntimeError + if mat.ndim == 1: # includes nD case that was reshaped above + return int(mat.col[extreme_index]) + # ndim == 2 + num_col = mat.shape[-1] + return int(mat.row[extreme_index]) * num_col + int(mat.col[extreme_index]) + + # At this stage, any implicit zero could be the min or max value. + # After sum_duplicates(), the `row` and `col` arrays are guaranteed to + # be sorted in C-order, which means the linearized indices are sorted. + if mat.ndim == 1: # includes nD case that was reshaped above + linear_indices = mat.coords[-1] + else: # ndim == 2 + num_col = mat.shape[-1] + linear_indices = mat.row * num_col + mat.col + first_implicit_zero_index = _find_missing_index(linear_indices, maxnnz) + if extreme_value == zero: + return min(first_implicit_zero_index, extreme_index) + return first_implicit_zero_index + + def max(self, axis=None, out=None, *, explicit=False): + """Return the maximum of the array/matrix or maximum along an axis. + + By default, all elements are taken into account, not just the non-zero ones. + But with `explicit` set, only the stored elements are considered. + + Parameters + ---------- + axis : {-2, -1, 0, 1, None} optional + Axis along which the sum is computed. The default is to + compute the maximum over all elements, returning + a scalar (i.e., `axis` = `None`). + + out : None, optional + This argument is in the signature *solely* for NumPy + compatibility reasons. Do not pass in anything except + for the default value, as this argument is not used. + + explicit : {False, True} optional (default: False) + When set to True, only the stored elements will be considered. + If a row/column is empty, the sparse.coo_array returned + has no stored element (i.e. an implicit zero) for that row/column. + + .. versionadded:: 1.15.0 + + Returns + ------- + amax : coo_array or scalar + Maximum of `a`. If `axis` is None, the result is a scalar value. + If `axis` is given, the result is a sparse.coo_array of dimension + ``a.ndim - 1``. + + See Also + -------- + min : The minimum value of a sparse array/matrix along a given axis. + numpy.max : NumPy's implementation of 'max' + + """ + return self._min_or_max(axis, out, np.maximum, explicit) + + def min(self, axis=None, out=None, *, explicit=False): + """Return the minimum of the array/matrix or maximum along an axis. + + By default, all elements are taken into account, not just the non-zero ones. + But with `explicit` set, only the stored elements are considered. + + Parameters + ---------- + axis : {-2, -1, 0, 1, None} optional + Axis along which the sum is computed. The default is to + compute the minimum over all elements, returning + a scalar (i.e., `axis` = `None`). + + out : None, optional + This argument is in the signature *solely* for NumPy + compatibility reasons. Do not pass in anything except for + the default value, as this argument is not used. + + explicit : {False, True} optional (default: False) + When set to True, only the stored elements will be considered. + If a row/column is empty, the sparse.coo_array returned + has no stored element (i.e. an implicit zero) for that row/column. + + .. versionadded:: 1.15.0 + + Returns + ------- + amin : coo_matrix or scalar + Minimum of `a`. If `axis` is None, the result is a scalar value. + If `axis` is given, the result is a sparse.coo_array of dimension + ``a.ndim - 1``. + + See Also + -------- + max : The maximum value of a sparse array/matrix along a given axis. + numpy.min : NumPy's implementation of 'min' + + """ + return self._min_or_max(axis, out, np.minimum, explicit) + + def nanmax(self, axis=None, out=None, *, explicit=False): + """Return the maximum, ignoring any Nans, along an axis. + + Return the maximum, ignoring any Nans, of the array/matrix along an axis. + By default this takes all elements into account, but with `explicit` set, + only stored elements are considered. + + .. versionadded:: 1.11.0 + + Parameters + ---------- + axis : {-2, -1, 0, 1, None} optional + Axis along which the maximum is computed. The default is to + compute the maximum over all elements, returning + a scalar (i.e., `axis` = `None`). + + out : None, optional + This argument is in the signature *solely* for NumPy + compatibility reasons. Do not pass in anything except + for the default value, as this argument is not used. + + explicit : {False, True} optional (default: False) + When set to True, only the stored elements will be considered. + If a row/column is empty, the sparse.coo_array returned + has no stored element (i.e. an implicit zero) for that row/column. + + .. versionadded:: 1.15.0 + + Returns + ------- + amax : coo_array or scalar + Maximum of `a`. If `axis` is None, the result is a scalar value. + If `axis` is given, the result is a sparse.coo_array of dimension + ``a.ndim - 1``. + + See Also + -------- + nanmin : The minimum value of a sparse array/matrix along a given axis, + ignoring NaNs. + max : The maximum value of a sparse array/matrix along a given axis, + propagating NaNs. + numpy.nanmax : NumPy's implementation of 'nanmax'. + + """ + return self._min_or_max(axis, out, np.fmax, explicit) + + def nanmin(self, axis=None, out=None, *, explicit=False): + """Return the minimum, ignoring any Nans, along an axis. + + Return the minimum, ignoring any Nans, of the array/matrix along an axis. + By default this takes all elements into account, but with `explicit` set, + only stored elements are considered. + + .. versionadded:: 1.11.0 + + Parameters + ---------- + axis : {-2, -1, 0, 1, None} optional + Axis along which the minimum is computed. The default is to + compute the minimum over all elements, returning + a scalar (i.e., `axis` = `None`). + + out : None, optional + This argument is in the signature *solely* for NumPy + compatibility reasons. Do not pass in anything except for + the default value, as this argument is not used. + + explicit : {False, True} optional (default: False) + When set to True, only the stored elements will be considered. + If a row/column is empty, the sparse.coo_array returned + has no stored element (i.e. an implicit zero) for that row/column. + + .. versionadded:: 1.15.0 + + Returns + ------- + amin : coo_array or scalar + Minimum of `a`. If `axis` is None, the result is a scalar value. + If `axis` is given, the result is a sparse.coo_array of dimension + ``a.ndim - 1``. + + See Also + -------- + nanmax : The maximum value of a sparse array/matrix along a given axis, + ignoring NaNs. + min : The minimum value of a sparse array/matrix along a given axis, + propagating NaNs. + numpy.nanmin : NumPy's implementation of 'nanmin'. + + """ + return self._min_or_max(axis, out, np.fmin, explicit) + + def argmax(self, axis=None, out=None, *, explicit=False): + """Return indices of maximum elements along an axis. + + By default, implicit zero elements are taken into account. If there are + several minimum values, the index of the first occurrence is returned. + If `explicit` is set, only explicitly stored elements will be considered. + + Parameters + ---------- + axis : {-2, -1, 0, 1, None}, optional + Axis along which the argmax is computed. If None (default), index + of the maximum element in the flatten data is returned. + + out : None, optional + This argument is in the signature *solely* for NumPy + compatibility reasons. Do not pass in anything except for + the default value, as this argument is not used. + + explicit : {False, True} optional (default: False) + When set to True, only explicitly stored elements will be considered. + If axis is not None and an axis has no stored elements, argmax + is undefined, so the index ``0`` is returned for that row/column. + + .. versionadded:: 1.15.0 + + Returns + ------- + ind : numpy.matrix or int + Indices of maximum elements. If matrix, its size along `axis` is 1. + """ + return self._argminmax(axis, out, np.argmax, np.greater, explicit) + + def argmin(self, axis=None, out=None, *, explicit=False): + """Return indices of minimum elements along an axis. + + By default, implicit zero elements are taken into account. If there are + several minimum values, the index of the first occurrence is returned. + If `explicit` is set, only explicitly stored elements will be considered. + + Parameters + ---------- + axis : {-2, -1, 0, 1, None}, optional + Axis along which the argmin is computed. If None (default), index + of the minimum element in the flatten data is returned. + + out : None, optional + This argument is in the signature *solely* for NumPy + compatibility reasons. Do not pass in anything except for + the default value, as this argument is not used. + + explicit : {False, True} optional (default: False) + When set to True, only explicitly stored elements will be considered. + If axis is not None and an axis has no stored elements, argmin + is undefined, so the index ``0`` is returned for that row/column. + + .. versionadded:: 1.15.0 + + Returns + ------- + ind : numpy.matrix or int + Indices of minimum elements. If matrix, its size along `axis` is 1. + """ + return self._argminmax(axis, out, np.argmin, np.less, explicit) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/_dia.py b/venv/lib/python3.12/site-packages/scipy/sparse/_dia.py new file mode 100644 index 0000000000000000000000000000000000000000..42fa23cddad8c18a6400d0b53e0784d35bb0ab47 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/_dia.py @@ -0,0 +1,731 @@ +"""Sparse DIAgonal format""" + +__docformat__ = "restructuredtext en" + +__all__ = ['dia_array', 'dia_matrix', 'isspmatrix_dia'] + +import numpy as np + +from .._lib._util import _prune_array, copy_if_needed +from ._matrix import spmatrix +from ._base import issparse, _formats, _spbase, sparray +from ._data import _data_matrix +from ._sputils import ( + isdense, isscalarlike, isshape, upcast_char, getdtype, get_sum_dtype, + validateaxis, check_shape +) +from ._sparsetools import dia_matmat, dia_matvec, dia_matvecs, dia_tocsr + + +class _dia_base(_data_matrix): + _format = 'dia' + + def __init__(self, arg1, shape=None, dtype=None, copy=False, *, maxprint=None): + _data_matrix.__init__(self, arg1, maxprint=maxprint) + + if issparse(arg1): + if arg1.format == "dia": + if copy: + arg1 = arg1.copy() + self.data = arg1.data + self.offsets = arg1.offsets + self._shape = check_shape(arg1.shape) + else: + if arg1.format == self.format and copy: + A = arg1.copy() + else: + A = arg1.todia() + self.data = A.data + self.offsets = A.offsets + self._shape = check_shape(A.shape) + elif isinstance(arg1, tuple): + if isshape(arg1): + # It's a tuple of matrix dimensions (M, N) + # create empty matrix + self._shape = check_shape(arg1) + self.data = np.zeros((0,0), getdtype(dtype, default=float)) + idx_dtype = self._get_index_dtype(maxval=max(self.shape)) + self.offsets = np.zeros((0), dtype=idx_dtype) + else: + try: + # Try interpreting it as (data, offsets) + data, offsets = arg1 + except Exception as e: + message = 'unrecognized form for dia_array constructor' + raise ValueError(message) from e + else: + if shape is None: + raise ValueError('expected a shape argument') + if not copy: + copy = copy_if_needed + self.data = np.atleast_2d(np.array(arg1[0], dtype=dtype, copy=copy)) + offsets = np.array(arg1[1], + dtype=self._get_index_dtype(maxval=max(shape)), + copy=copy) + self.offsets = np.atleast_1d(offsets) + self._shape = check_shape(shape) + else: + # must be dense, convert to COO first, then to DIA + try: + arg1 = np.asarray(arg1) + except Exception as e: + raise ValueError("unrecognized form for " + f"{self.format}_matrix constructor") from e + if isinstance(self, sparray) and arg1.ndim != 2: + raise ValueError(f"DIA arrays don't support {arg1.ndim}D input. Use 2D") + A = self._coo_container(arg1, dtype=dtype, shape=shape).todia() + self.data = A.data + self.offsets = A.offsets + self._shape = check_shape(A.shape) + + if dtype is not None: + newdtype = getdtype(dtype) + self.data = self.data.astype(newdtype, copy=False) + + # check format + if self.offsets.ndim != 1: + raise ValueError('offsets array must have rank 1') + + if self.data.ndim != 2: + raise ValueError('data array must have rank 2') + + if self.data.shape[0] != len(self.offsets): + raise ValueError( + f'number of diagonals ({self.data.shape[0]}) does not match the number ' + f'of offsets ({len(self.offsets)})' + ) + if len(np.unique(self.offsets)) != len(self.offsets): + raise ValueError('offset array contains duplicate values') + + def __repr__(self): + _, fmt = _formats[self.format] + sparse_cls = 'array' if isinstance(self, sparray) else 'matrix' + d = self.data.shape[0] + return ( + f"<{fmt} sparse {sparse_cls} of dtype '{self.dtype}'\n" + f"\twith {self.nnz} stored elements ({d} diagonals) and shape {self.shape}>" + ) + + def _data_mask(self): + """Returns a mask of the same shape as self.data, where + mask[i,j] is True when data[i,j] corresponds to a stored element.""" + num_rows, num_cols = self.shape + offset_inds = np.arange(self.data.shape[1]) + row = offset_inds - self.offsets[:,None] + mask = (row >= 0) + mask &= (row < num_rows) + mask &= (offset_inds < num_cols) + return mask + + def count_nonzero(self, axis=None): + if axis is not None: + raise NotImplementedError( + "count_nonzero over an axis is not implemented for DIA format" + ) + mask = self._data_mask() + return np.count_nonzero(self.data[mask]) + + count_nonzero.__doc__ = _spbase.count_nonzero.__doc__ + + def _getnnz(self, axis=None): + if axis is not None: + raise NotImplementedError("_getnnz over an axis is not implemented " + "for DIA format") + M, N = self.shape + L = min(self.data.shape[1], N) + return int(np.maximum(np.minimum(M + self.offsets, L) - + np.maximum(self.offsets, 0), + 0).sum()) + + _getnnz.__doc__ = _spbase._getnnz.__doc__ + + def sum(self, axis=None, dtype=None, out=None): + axis = validateaxis(axis) + + res_dtype = dtype if dtype is not None else get_sum_dtype(self.dtype) + num_rows, num_cols = self.shape + ret = None + + if axis == (0,): + mask = self._data_mask() + x = (self.data * mask).sum(dtype=res_dtype, axis=0) + if x.shape[0] == num_cols: + res = x + else: + res = np.zeros(num_cols, dtype=x.dtype) + res[:x.shape[0]] = x + ret = self._ascontainer(res, dtype=res_dtype) + + else: # axis is None or (1,) + row_sums = np.zeros((num_rows, 1), dtype=res_dtype) + one = np.ones(num_cols, dtype=res_dtype) + dia_matvec(num_rows, num_cols, len(self.offsets), + self.data.shape[1], self.offsets, + self.data.astype(res_dtype), one, row_sums) + + row_sums = self._ascontainer(row_sums) + + if axis is None: + return row_sums.sum(dtype=dtype, out=out) + + ret = self._ascontainer(row_sums.sum(axis=axis)) + + return ret.sum(axis=(), dtype=dtype, out=out) + + sum.__doc__ = _spbase.sum.__doc__ + + def _add_sparse(self, other, sub=False): + # If other is not DIA format, let them handle us instead. + if not isinstance(other, _dia_base): + return other._add_sparse(self) + + # Fast path for exact equality of the sparsity structure. + if np.array_equal(self.offsets, other.offsets): + return self._with_data(self.data - other.data if sub else + self.data + other.data) + + # Find the union of the offsets (which will be sorted and unique). + new_offsets = np.union1d(self.offsets, other.offsets) + self_idx = np.searchsorted(new_offsets, self.offsets) + other_idx = np.searchsorted(new_offsets, other.offsets) + + self_d = self.data.shape[1] + other_d = other.data.shape[1] + # Fast path for a sparsity structure where the final offsets are a + # permutation of the existing offsets and the diagonal lengths match. + if self_d == other_d and len(new_offsets) == len(self.offsets): + new_data = self.data[_invert_index(self_idx)] + if sub: + new_data[other_idx, :] -= other.data + else: + new_data[other_idx, :] += other.data + elif self_d == other_d and len(new_offsets) == len(other.offsets): + if sub: + new_data = -other.data[_invert_index(other_idx)] + else: + new_data = other.data[_invert_index(other_idx)] + new_data[self_idx, :] += self.data + else: + # Maximum diagonal length of the result. + d = min(self.shape[0] + new_offsets[-1], self.shape[1]) + + # Add all diagonals to a freshly allocated data array. + new_data = np.zeros( + (len(new_offsets), d), + dtype=np.result_type(self.data, other.data), + ) + new_data[self_idx, :self_d] += self.data[:, :d] + if sub: + new_data[other_idx, :other_d] -= other.data[:, :d] + else: + new_data[other_idx, :other_d] += other.data[:, :d] + return self._dia_container((new_data, new_offsets), shape=self.shape) + + def _sub_sparse(self, other): + # If other is not DIA format, use default handler. + if not isinstance(other, _dia_base): + return super()._sub_sparse(other) + + return self._add_sparse(other, sub=True) + + def _mul_scalar(self, other): + return self._with_data(self.data * other) + + def multiply(self, other): + if isscalarlike(other): + return self._mul_scalar(other) + + if isdense(other): + if other.ndim > 2: + return self.toarray() * other + + # Use default handler for pathological cases. + if 0 in self.shape or 1 in self.shape or 0 in other.shape: + return super().multiply(other) + + other = np.atleast_2d(other) + other_rows, other_cols = other.shape + rows, cols = self.shape + L = min(self.data.shape[1], cols) + data = self.data[:, :L].astype(np.result_type(self.data, other)) # copy + if other_rows == 1: + data *= other[0, :L] + elif other_rows != rows: + raise ValueError('inconsistent shapes') + else: + j = np.arange(L) + if L > rows: + i = (j - self.offsets[:, None]) % rows + else: # can use faster method + i = j - self.offsets[:, None] % rows + if other_cols == 1: + j = 0 + elif other_cols != cols: + raise ValueError('inconsistent shapes') + data *= other[i, j] + return self._with_data(data) + + # If other is not DIA format or needs broadcasting (unreasonable + # use case for DIA anyway), use default handler. + if not isinstance(other, _dia_base) or other.shape != self.shape: + return super().multiply(other) + + # Find common offsets (unique diagonals don't contribute) + # and indices corresponding to them in multiplicand and multiplier. + offsets, self_idx, other_idx = \ + np.intersect1d(self.offsets, other.offsets, + assume_unique=True, return_indices=True) + # Only overlapping length of diagonals can have non-zero products. + L = min(self.data.shape[1], other.data.shape[1]) + data = self.data[self_idx, :L] * other.data[other_idx, :L] + return self._dia_container((data, offsets), shape=self.shape) + + multiply.__doc__ = _data_matrix.multiply.__doc__ + + def _matmul_vector(self, other): + x = other + + y = np.zeros(self.shape[0], dtype=upcast_char(self.dtype.char, + x.dtype.char)) + + L = self.data.shape[1] + + M,N = self.shape + + dia_matvec(M,N, len(self.offsets), L, self.offsets, self.data, + x.ravel(), y.ravel()) + + return y + + def _matmul_multivector(self, other): + res = np.zeros((self.shape[0], other.shape[1]), + dtype=np.result_type(self.data, other)) + dia_matvecs(*self.shape, *self.data.shape, self.offsets, self.data, + other.shape[1], other, res) + return res + + def _matmul_sparse(self, other): + # If other is not DIA format, use default handler. + if not isinstance(other, _dia_base): + return super()._matmul_sparse(other) + + # If any dimension is zero, return empty array immediately. + if 0 in self.shape or 0 in other.shape: + return self._dia_container((self.shape[0], other.shape[1])) + + offsets, data = dia_matmat(*self.shape, *self.data.shape, + self.offsets, self.data, + other.shape[1], *other.data.shape, + other.offsets, other.data) + return self._dia_container((data.reshape(len(offsets), -1), offsets), + (self.shape[0], other.shape[1])) + + def _setdiag(self, values, k=0): + M, N = self.shape + + if values.ndim == 0: + # broadcast + values_n = np.inf + else: + values_n = len(values) + + if k < 0: + n = min(M + k, N, values_n) + min_index = 0 + max_index = n + else: + n = min(M, N - k, values_n) + min_index = k + max_index = k + n + + if values.ndim != 0: + # allow also longer sequences + values = values[:n] + + data_rows, data_cols = self.data.shape + if k in self.offsets: + if max_index > data_cols: + data = np.zeros((data_rows, max_index), dtype=self.data.dtype) + data[:, :data_cols] = self.data + self.data = data + self.data[self.offsets == k, min_index:max_index] = values + else: + self.offsets = np.append(self.offsets, self.offsets.dtype.type(k)) + m = max(max_index, data_cols) + data = np.zeros((data_rows + 1, m), dtype=self.data.dtype) + data[:-1, :data_cols] = self.data + data[-1, min_index:max_index] = values + self.data = data + + def todia(self, copy=False): + if copy: + return self.copy() + else: + return self + + todia.__doc__ = _spbase.todia.__doc__ + + def transpose(self, axes=None, copy=False): + if axes is not None and axes != (1, 0): + raise ValueError("Sparse arrays/matrices do not support " + "an 'axes' parameter because swapping " + "dimensions is the only logical permutation.") + + num_rows, num_cols = self.shape + max_dim = max(self.shape) + + # flip diagonal offsets + offsets = -self.offsets + + # re-align the data matrix + r = np.arange(len(offsets), dtype=np.intc)[:, None] + c = np.arange(num_rows, dtype=np.intc) - (offsets % max_dim)[:, None] + pad_amount = max(0, max_dim-self.data.shape[1]) + data = np.hstack((self.data, np.zeros((self.data.shape[0], pad_amount), + dtype=self.data.dtype))) + data = data[r, c] + return self._dia_container((data, offsets), shape=( + num_cols, num_rows), copy=copy) + + transpose.__doc__ = _spbase.transpose.__doc__ + + def diagonal(self, k=0): + rows, cols = self.shape + if k <= -rows or k >= cols: + return np.empty(0, dtype=self.data.dtype) + idx, = np.nonzero(self.offsets == k) + first_col = max(0, k) + last_col = min(rows + k, cols) + result_size = last_col - first_col + if idx.size == 0: + return np.zeros(result_size, dtype=self.data.dtype) + result = self.data[idx[0], first_col:last_col] + padding = result_size - len(result) + if padding > 0: + result = np.pad(result, (0, padding), mode='constant') + return result + + diagonal.__doc__ = _spbase.diagonal.__doc__ + + def tocsr(self, copy=False): + if 0 in self.shape or len(self.offsets) == 0: + return self._csr_container(self.shape, dtype=self.dtype) + + n_rows, n_cols = self.shape + max_nnz = self.nnz + # np.argsort always returns dtype=int, which can cause automatic dtype + # expansion for everything else even if not needed (see gh19245), but + # CSR wants common dtype for indices, indptr and shape, so care should + # be taken to use appropriate indexing dtype throughout. + idx_dtype = self._get_index_dtype(maxval=max(max_nnz, n_rows, n_cols)) + order = np.argsort(self.offsets).astype(idx_dtype, copy=False) + csr_data = np.empty(max_nnz, dtype=self.dtype) + indices = np.empty(max_nnz, dtype=idx_dtype) + indptr = np.empty(1 + n_rows, dtype=idx_dtype) + # Conversion eliminates explicit zeros and returns actual nnz. + nnz = dia_tocsr(n_rows, n_cols, *self.data.shape, + self.offsets.astype(idx_dtype, copy=False), self.data, + order, csr_data, indices, indptr) + # Shrink indexing dtype, if needed, and prune arrays. + idx_dtype = self._get_index_dtype(maxval=max(nnz, n_rows, n_cols)) + csr_data = _prune_array(csr_data[:nnz]) + indices = _prune_array(indices[:nnz].astype(idx_dtype, copy=False)) + indptr = indptr.astype(idx_dtype, copy=False) + out = self._csr_container((csr_data, indices, indptr), + shape=self.shape, dtype=self.dtype) + out.has_canonical_format = True + return out + + tocsr.__doc__ = _spbase.tocsr.__doc__ + + # needed by _data_matrix + def _with_data(self, data, copy=True): + """Returns a matrix with the same sparsity structure as self, + but with different data. By default the structure arrays are copied. + """ + if copy: + return self._dia_container( + (data, self.offsets.copy()), shape=self.shape + ) + else: + return self._dia_container( + (data, self.offsets), shape=self.shape + ) + + def resize(self, *shape): + shape = check_shape(shape) + M, N = shape + # we do not need to handle the case of expanding N + self.data = self.data[:, :N] + + if (M > self.shape[0] and + np.any(self.offsets + self.shape[0] < self.data.shape[1])): + # explicitly clear values that were previously hidden + mask = (self.offsets[:, None] + self.shape[0] <= + np.arange(self.data.shape[1])) + self.data[mask] = 0 + + self._shape = shape + + resize.__doc__ = _spbase.resize.__doc__ + + +def _invert_index(idx): + """Helper function to invert an index array.""" + inv = np.zeros_like(idx) + inv[idx] = np.arange(len(idx)) + return inv + + +def isspmatrix_dia(x): + """Is `x` of dia_matrix type? + + .. warning:: + + SciPy sparse is shifting from a sparse matrix interface to a sparse + array interface. In the next few releases we expect to deprecate the + sparse matrix interface. For documentation of the matrix + interface, see the :ref:`spmatrix interface docs `. + For guidance on converting existing code to sparse arrays, see + :ref:`Migration from spmatrix to sparray `. + + Parameters + ---------- + x + object to check for being a dia matrix + + Returns + ------- + bool + True if `x` is a dia matrix, False otherwise + + Examples + -------- + >>> from scipy.sparse import dia_array, dia_matrix, coo_matrix, isspmatrix_dia + >>> isspmatrix_dia(dia_matrix([[5]])) + True + >>> isspmatrix_dia(dia_array([[5]])) + False + >>> isspmatrix_dia(coo_matrix([[5]])) + False + """ + return isinstance(x, dia_matrix) + + +# This namespace class separates array from matrix with isinstance +class dia_array(_dia_base, sparray): + """ + Sparse array with DIAgonal storage. + + This can be instantiated in several ways: + dia_array(D) + where D is a 2-D ndarray + + dia_array(S) + with another sparse array or matrix S (equivalent to S.todia()) + + dia_array((M, N), [dtype]) + to construct an empty array with shape (M, N), + dtype is optional, defaulting to dtype='d'. + + dia_array((data, offsets), shape=(M, N)) + where the ``data[k,:]`` stores the diagonal entries for + diagonal ``offsets[k]`` (See example below) + + Attributes + ---------- + data + DIA format data array of the array + offsets + DIA format offset array of the array + dtype : dtype + Data type of the array + shape : 2-tuple + Shape of the array + ndim : int + Number of dimensions (this is always 2) + format : str + Three letter code for the format of the array storage, e.g. 'dia' + nnz : int + Number of values stored in the array + size : int + Number of values stored in the array + T : dia_array + The transpose of the array + mT : dia_array + The matrix transpose of the array + + Notes + ----- + + Sparse arrays can be used in arithmetic operations: they support + addition, subtraction, multiplication, division, and matrix power. + Sparse arrays with DIAgonal storage do not support slicing. + + **Format details** + + The ``data`` array stores the diagonal elements. The alignment of these + elements within the rows of ``data`` depends on their position relative + to the main diagonal: + + * **Main diagonal** (``offsets[i] == 0``): Elements start at column 0. + * **Super-diagonals** (``offsets[i] > 0``): Elements are right-aligned + (padded with zeros on the left). + * **Sub-diagonals** (``offsets[i] < 0``): Elements are left-aligned + (padded with zeros on the right). + + Each column of ``data`` corresponds to a diagonal in the resulting matrix. + + Mathematically, the element at row ``r`` and column ``c`` of the matrix is + stored in the ``data`` array at row ``i`` and column + ``c - max(0, -offsets[i])``, where ``i`` is the index of the diagonal + in ``offsets``. + + Note that if ``offsets`` is provided in decreasing order, this format + matches the BLAS/LAPACK general band format (e.g., as used in ``dgbmv``). + + Examples + -------- + + >>> import numpy as np + >>> from scipy.sparse import dia_array + >>> dia_array((3, 4), dtype=np.int8).toarray() + array([[0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0]], dtype=int8) + + >>> data = np.array([[1, 2, 3, 4]]).repeat(3, axis=0) + >>> offsets = np.array([0, -1, 2]) + >>> dia_array((data, offsets), shape=(4, 4)).toarray() + array([[1, 0, 3, 0], + [1, 2, 0, 4], + [0, 2, 3, 0], + [0, 0, 3, 4]]) + + >>> from scipy.sparse import dia_array + >>> n = 10 + >>> ex = np.ones(n) + >>> data = np.array([ex, 2 * ex, ex]) + >>> offsets = np.array([-1, 0, 1]) + >>> dia_array((data, offsets), shape=(n, n)).toarray() + array([[2., 1., 0., ..., 0., 0., 0.], + [1., 2., 1., ..., 0., 0., 0.], + [0., 1., 2., ..., 0., 0., 0.], + ..., + [0., 0., 0., ..., 2., 1., 0.], + [0., 0., 0., ..., 1., 2., 1.], + [0., 0., 0., ..., 0., 1., 2.]]) + """ # numpydoc ignore=PR01 + + +class dia_matrix(spmatrix, _dia_base): + """ + Sparse matrix with DIAgonal storage. + + .. warning:: + + SciPy sparse is shifting from a sparse matrix interface to a sparse + array interface. In the next few releases we expect to deprecate the + sparse matrix interface. For documentation of the matrix + interface, see the :ref:`spmatrix interface docs `. + For guidance on converting existing code to sparse arrays, see + :ref:`Migration from spmatrix to sparray `. + + This can be instantiated in several ways: + dia_matrix(D) + where D is a 2-D ndarray + + dia_matrix(S) + with another sparse array or matrix S (equivalent to S.todia()) + + dia_matrix((M, N), [dtype]) + to construct an empty matrix with shape (M, N), + dtype is optional, defaulting to dtype='d'. + + dia_matrix((data, offsets), shape=(M, N)) + where the ``data[k,:]`` stores the diagonal entries for + diagonal ``offsets[k]`` (See example below) + + Attributes + ---------- + data + DIA format data array of the matrix + offsets + DIA format offset array of the matrix + dtype : dtype + Data type of the matrix + shape : 2-tuple + Shape of the matrix + ndim : int + Number of dimensions (this is always 2) + format : str + Three letter code for the format of the matrix storage, e.g. 'dia' + nnz : int + Number of values stored in the matrix + size : int + Number of values stored in the matrix + T : dia_matrix + The transpose of the matrix + mT : dia_matrix + The matrix transpose + + Notes + ----- + Sparse matrices can be used in arithmetic operations: they support + addition, subtraction, multiplication, division, and matrix power. + Sparse arrays with DIAgonal storage do not support slicing. + + **Format details** + + The ``data`` array stores the diagonal elements. The alignment of these + elements within the rows of ``data`` depends on their position relative + to the main diagonal: + + * **Main diagonal** (``offsets[i] == 0``): Elements start at column 0. + * **Super-diagonals** (``offsets[i] > 0``): Elements are right-aligned + (padded with zeros on the left). + * **Sub-diagonals** (``offsets[i] < 0``): Elements are left-aligned + (padded with zeros on the right). + + Each column of ``data`` corresponds to a diagonal in the resulting matrix. + + Mathematically, the element at row ``r`` and column ``c`` of the matrix is + stored in the ``data`` array at row ``i`` and column + ``c - max(0, -offsets[i])``, where ``i`` is the index of the diagonal + in ``offsets``. + + Note that if ``offsets`` is provided in decreasing order, this format + matches the BLAS/LAPACK general band format (e.g., as used in ``dgbmv``). + + Examples + -------- + + >>> import numpy as np + >>> from scipy.sparse import dia_matrix + >>> dia_matrix((3, 4), dtype=np.int8).toarray() + array([[0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0]], dtype=int8) + + >>> data = np.array([[1, 2, 3, 4]]).repeat(3, axis=0) + >>> offsets = np.array([0, -1, 2]) + >>> dia_matrix((data, offsets), shape=(4, 4)).toarray() + array([[1, 0, 3, 0], + [1, 2, 0, 4], + [0, 2, 3, 0], + [0, 0, 3, 4]]) + + >>> from scipy.sparse import dia_matrix + >>> n = 10 + >>> ex = np.ones(n) + >>> data = np.array([ex, 2 * ex, ex]) + >>> offsets = np.array([-1, 0, 1]) + >>> dia_matrix((data, offsets), shape=(n, n)).toarray() + array([[2., 1., 0., ..., 0., 0., 0.], + [1., 2., 1., ..., 0., 0., 0.], + [0., 1., 2., ..., 0., 0., 0.], + ..., + [0., 0., 0., ..., 2., 1., 0.], + [0., 0., 0., ..., 1., 2., 1.], + [0., 0., 0., ..., 0., 1., 2.]]) + """ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/_dok.py b/venv/lib/python3.12/site-packages/scipy/sparse/_dok.py new file mode 100644 index 0000000000000000000000000000000000000000..405ffaa57d93ee3e36b17d6be546feb214c6a992 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/_dok.py @@ -0,0 +1,828 @@ +"""Dictionary Of Keys based matrix""" + +__docformat__ = "restructuredtext en" + +__all__ = ['dok_array', 'dok_matrix', 'isspmatrix_dok'] + +import itertools +import numpy as np + +from ._matrix import spmatrix +from ._base import _spbase, sparray, issparse +from ._index import IndexMixin +from ._sputils import (isdense, getdtype, isshape, isintlike, isscalarlike, + upcast, upcast_scalar, check_shape) + + +_NoValue = object() + + +class _dok_base(_spbase, IndexMixin, dict): + _format = 'dok' + _allow_nd = (1, 2) + + def __init__(self, arg1, shape=None, dtype=None, copy=False, *, maxprint=None): + _spbase.__init__(self, arg1, maxprint=maxprint) + + if isinstance(arg1, tuple) and isshape(arg1, allow_nd=self._allow_nd): + self._shape = check_shape(arg1, allow_nd=self._allow_nd) + self._dict = {} + self.dtype = getdtype(dtype, default=float) + elif issparse(arg1): # Sparse ctor + if arg1.format == self.format: + arg1 = arg1.copy() if copy else arg1 + else: + arg1 = arg1.todok() + + if dtype is not None: + arg1 = arg1.astype(dtype, copy=False) + + self._dict = arg1._dict + self._shape = check_shape(arg1.shape, allow_nd=self._allow_nd) + self.dtype = getdtype(arg1.dtype) + else: # Dense ctor + try: + arg1 = np.asarray(arg1) + except Exception as e: + raise TypeError('Invalid input format.') from e + + if arg1.ndim > 2: + raise ValueError(f"DOK arrays don't yet support {arg1.ndim}D input.") + + if arg1.ndim == 1: + if dtype is not None: + arg1 = arg1.astype(dtype, copy=False) + self._dict = {i: v for i, v in enumerate(arg1) if v != 0} + self.dtype = getdtype(arg1.dtype) + else: + d = self._coo_container(arg1, shape=shape, dtype=dtype).todok() + self._dict = d._dict + self.dtype = getdtype(d.dtype) + self._shape = check_shape(arg1.shape, allow_nd=self._allow_nd) + + def update(self, val): # pyrefly:ignore[bad-override] + """Update values from a dict, sparse dok or iterable of 2-tuples like + ``.items()``. + + Parameters + ---------- + val : dict, dok_array, iterable of 2-tuples + The values to update in the dok_array. If a dict or dok_array is + provided, the keys and values will be taken from it. If an iterable + of 2-tuples is provided, each tuple should contain a key and a value. + Keys of the input must be sequences of nonnegative integers less than + the shape for each axis. + """ + if isinstance(val, dict): + inputs = val.items() + else: + inputs = val + + for key, value in inputs: + index = (key,) if isintlike(key) else tuple(key) + if len(index) != self.ndim: + raise IndexError(f'Index {key} length needs to match self.shape') + if not all( + isintlike(idx) and 0 <= idx < max_idx + for idx, max_idx in zip(index, self.shape) + ): + # Error handling. Re-search to find which error occurred + for idx, max_idx in zip(index, self.shape): + if not isintlike(idx): + raise IndexError(f'integer keys required for update. Got {key}') + if idx < 0: + raise IndexError(f'negative index {key} not allowed in update') + if idx >= max_idx: + raise IndexError(f'index {key} is too large for self.shape') + # do the update + self._dict.update(inputs) + + def _getnnz(self, axis=None): + if axis is not None: + raise NotImplementedError( + "_getnnz over an axis is not implemented for DOK format." + ) + return len(self._dict) + + def count_nonzero(self, axis=None): + if axis is not None: + return self.tocoo().count_nonzero(axis=axis) + return sum(x != 0 for x in self.values()) + + _getnnz.__doc__ = _spbase._getnnz.__doc__ + count_nonzero.__doc__ = _spbase.count_nonzero.__doc__ + + def __len__(self): + return len(self._dict) + + def __contains__(self, key): + return key in self._dict + + def setdefault(self, key, default=None, /): + """Insert key with a value of default if key is not in the dictionary. + + Parameters + ---------- + key : int or tuple of int + The key to look for in the dok_array. + default : optional + The value to insert if `key` is not found. Default is None. + + Returns + ------- + scalar + The value for `key` if `key` is in the dictionary, else `default`. + """ + return self._dict.setdefault(key, default) + + def __delitem__(self, key, /): + del self._dict[key] + + def clear(self): + """Remove all items from the dok_array.""" + self._dict.clear() + + def pop(self, key, default=_NoValue, /): + """Remove specified key and return the corresponding value. + + Parameters + ---------- + key : tuple + The key to remove from the dok_array. + default : optional + The value to return if `key` is not found. If not provided and + `key` is not found, a KeyError is raised. + + Returns + ------- + scalar + The value associated with the removed key. + + Raises + ------ + KeyError + If the key is not found and default is not provided. + """ + if default is _NoValue: + return self._dict.pop(key) + else: + return self._dict.pop(key, default) + + def __reversed__(self): + raise TypeError("reversed is not defined for dok_array type") + + def __or__(self, other): + type_names = f"{type(self).__name__} and {type(other).__name__}" + raise TypeError(f"unsupported operand type for |: {type_names}") + + def __ror__(self, other): + type_names = f"{type(self).__name__} and {type(other).__name__}" + raise TypeError(f"unsupported operand type for |: {type_names}") + + def __ior__(self, other): + type_names = f"{type(self).__name__} and {type(other).__name__}" + raise TypeError(f"unsupported operand type for |: {type_names}") + + def popitem(self): + """ + Remove and return a (key, value) pair as a 2-tuple, returned in LIFO (last-in, + first-out) order. + + Returns + ------- + key : tuple + The key of the removed item. + value : scalar + The value of the removed item. + + Raises + ------ + KeyError + If the dict is empty. + """ + return self._dict.popitem() + + def items(self): + """ + Return a set-like object providing a view on the array's items. + + Returns + ------- + dict_items + A view object displaying a list of a array's key-value tuple pairs. + """ + return self._dict.items() + + def keys(self): + """ + Return a set-like object providing a view on the dict's keys. + + Returns + ------- + dict_keys + A view object displaying a list of all the keys in the dictionary. + """ + return self._dict.keys() + + def values(self): + """ + Return an object providing a view on the dict's values. + + Returns + ------- + dict_values + A view object displaying a list of all the values in the dictionary. + """ + return self._dict.values() + + def get(self, key, default=0.0): + """This provides dict.get method functionality with type checking. + + Parameters + ---------- + key : int or tuple of int + The key to look for in the dok_array. + default : optional + The value to return if `key` is not found. Default is 0.0. + + Returns + ------- + value + The value associated with `key` if it exists, otherwise `default`. + """ + if key in self._dict: + return self._dict[key] + if isintlike(key) and self.ndim == 1: + key = (key,) + if self.ndim != len(key): + raise IndexError(f'Index {key} length needs to match self.shape') + try: + for i in key: + assert isintlike(i) + except (AssertionError, TypeError, ValueError) as e: + raise IndexError('Index must be or consist of integers.') from e + key = tuple(i + M if i < 0 else i for i, M in zip(key, self.shape)) + if any(i < 0 or i >= M for i, M in zip(key, self.shape)): + raise IndexError('Index out of bounds.') + if self.ndim == 1: + key = key[0] + return self._dict.get(key, default) + + # 1D get methods + def _get_int(self, idx): + return self._dict.get(idx, self.dtype.type(0)) + + def _get_slice(self, idx): + i_range = range(*idx.indices(self.shape[0])) + return self._get_array(list(i_range)) + + def _get_array(self, idx): + idx = np.asarray(idx) + if idx.ndim == 0: + val = self._dict.get(int(idx), self.dtype.type(0)) + return np.array(val, stype=self.dtype) + new_dok = self._dok_container(idx.shape, dtype=self.dtype) + dok_vals = [self._dict.get(i, 0) for i in idx.ravel()] + if dok_vals: + if len(idx.shape) == 1: + for i, v in enumerate(dok_vals): + if v: + new_dok._dict[i] = v + else: + new_idx = np.unravel_index(np.arange(len(dok_vals)), idx.shape) + new_idx = new_idx[0] if len(new_idx) == 1 else zip(*new_idx) + for i, v in zip(new_idx, dok_vals, strict=True): + if v: + new_dok._dict[i] = v + return new_dok + + # 2D get methods + def _get_intXint(self, row, col): + return self._dict.get((row, col), self.dtype.type(0)) + + def _get_intXslice(self, row, col): + return self._get_sliceXslice(slice(row, row + 1), col) + + def _get_sliceXint(self, row, col): + return self._get_sliceXslice(row, slice(col, col + 1)) + + def _get_sliceXslice(self, row, col): + row_start, row_stop, row_step = row.indices(self.shape[0]) + col_start, col_stop, col_step = col.indices(self.shape[1]) + row_range = range(row_start, row_stop, row_step) + col_range = range(col_start, col_stop, col_step) + shape = (len(row_range), len(col_range)) + # Switch paths only when advantageous + # (count the iterations in the loops, adjust for complexity) + if len(self) >= 2 * shape[0] * shape[1]: + # O(nr*nc) path: loop over + return self._get_columnXarray(row_range, col_range) + # O(nnz) path: loop over entries of self + newdok = self._dok_container(shape, dtype=self.dtype) + for key in self.keys(): + i, ri = divmod(int(key[0]) - row_start, row_step) + if ri != 0 or i < 0 or i >= shape[0]: + continue + j, rj = divmod(int(key[1]) - col_start, col_step) + if rj != 0 or j < 0 or j >= shape[1]: + continue + newdok._dict[i, j] = self._dict[key] + return newdok + + def _get_intXarray(self, row, col): + return self._get_columnXarray([row], col.ravel()) + + def _get_arrayXint(self, row, col): + res = self._get_columnXarray(row.ravel(), [col]) + if row.ndim > 1: + return res.reshape(row.shape) + return res + + def _get_sliceXarray(self, row, col): + row = list(range(*row.indices(self.shape[0]))) + return self._get_columnXarray(row, col) + + def _get_arrayXslice(self, row, col): + col = list(range(*col.indices(self.shape[1]))) + return self._get_columnXarray(row, col) + + def _get_columnXarray(self, row, col): + # outer indexing + newdok = self._dok_container((len(row), len(col)), dtype=self.dtype) + + for i, r in enumerate(row): + for j, c in enumerate(col): + v = self._dict.get((r, c), 0) + if v: + newdok._dict[i, j] = v + return newdok + + def _get_arrayXarray(self, row, col): + # inner indexing + i, j = map(np.atleast_2d, np.broadcast_arrays(row, col)) + newdok = self._dok_container(i.shape, dtype=self.dtype) + + for key in itertools.product(range(i.shape[0]), range(i.shape[1])): + v = self._dict.get((i[key], j[key]), 0) + if v: + newdok._dict[key] = v + return newdok + + # 1D set methods + def _set_int(self, idx, x): + if x: + self._dict[idx] = x + elif idx in self._dict: + del self._dict[idx] + + def _set_array(self, idx, x): + idx_set = idx.ravel() + x_set = x.ravel() + if len(idx_set) != len(x_set): + if len(x_set) == 1: + x_set = np.full(len(idx_set), x_set[0], dtype=self.dtype) + else: + raise ValueError("Need len(index)==len(data) or len(data)==1") + for i, v in zip(idx_set, x_set): + if v: + self._dict[i] = v + elif i in self._dict: + del self._dict[i] + + # 2D set methods + def _set_intXint(self, row, col, x): + key = (row, col) + if x: + self._dict[key] = x + elif key in self._dict: + del self._dict[key] + + def _set_arrayXarray(self, row, col, x): + row = list(map(int, row.ravel())) + col = list(map(int, col.ravel())) + x = x.ravel() + self._dict.update(zip(zip(row, col), x)) + + for i in np.nonzero(x == 0)[0]: + key = (row[i], col[i]) + if self._dict[key] == 0: + # may have been superseded by later update + del self._dict[key] + + def __add__(self, other): + if isscalarlike(other): + res_dtype = upcast_scalar(self.dtype, other) + new = self._dok_container(self.shape, dtype=res_dtype) + # Add this scalar to each element. + for key in itertools.product(*[range(d) for d in self.shape]): + aij = self._dict.get(key, 0) + other + if aij: + new[key] = aij + elif issparse(other): + if other.shape != self.shape: + raise ValueError("Matrix dimensions are not equal.") + res_dtype = upcast(self.dtype, other.dtype) + new = self._dok_container(self.shape, dtype=res_dtype) + new._dict = self._dict.copy() + if other.format == "dok": + o_items = other.items() + else: + other = other.tocoo() + if self.ndim == 1: + o_items = zip(other.coords[0], other.data) + else: + o_items = zip(zip(*other.coords), other.data) + with np.errstate(over='ignore'): + new._dict.update((k, new[k] + v) for k, v in o_items) + elif isdense(other): + new = self.todense() + other + else: + return NotImplemented + return new + + def __radd__(self, other): + return self + other # addition is commutative + + def __neg__(self): + if self.dtype.kind == 'b': + raise NotImplementedError( + 'Negating a sparse boolean matrix is not supported.' + ) + new = self._dok_container(self.shape, dtype=self.dtype) + new._dict.update((k, -v) for k, v in self.items()) + return new + + def _mul_scalar(self, other): + res_dtype = upcast_scalar(self.dtype, other) + # Multiply this scalar by every element. + new = self._dok_container(self.shape, dtype=res_dtype) + new._dict.update(((k, v * other) for k, v in self.items())) + return new + + def _matmul_vector(self, other): + res_dtype = upcast(self.dtype, other.dtype) + + # vector @ vector + if self.ndim == 1: + if issparse(other): + if other.format == "dok": + keys = self.keys() & other.keys() + else: + keys = self.keys() & other.tocoo().coords[0] + return res_dtype(sum(self._dict[k] * other._dict[k] for k in keys)) + elif isdense(other): + return res_dtype(sum(other[k] * v for k, v in self.items())) + else: + return NotImplemented + + # matrix @ vector + result = np.zeros(self.shape[0], dtype=res_dtype) + for (i, j), v in self.items(): + result[i] += v * other[j] + return result + + def _matmul_multivector(self, other): + result_dtype = upcast(self.dtype, other.dtype) + # vector @ multivector + if self.ndim == 1: + # works for other 1d or 2d + return sum(v * other[j] for j, v in self._dict.items()) + + # matrix @ multivector + M = self.shape[0] + new_shape = (M,) if other.ndim == 1 else (M, other.shape[1]) + result = np.zeros(new_shape, dtype=result_dtype) + for (i, j), v in self.items(): + result[i] += v * other[j] + return result + + def __imul__(self, other): + if isscalarlike(other): + self._dict.update((k, v * other) for k, v in self.items()) + return self + return NotImplemented + + def __truediv__(self, other): + if isscalarlike(other): + res_dtype = upcast_scalar(self.dtype, other) + new = self._dok_container(self.shape, dtype=res_dtype) + new._dict.update(((k, v / other) for k, v in self.items())) + return new + return self.tocsr() / other + + def __itruediv__(self, other): + if isscalarlike(other): + self._dict.update((k, v / other) for k, v in self.items()) + return self + return NotImplemented + + def __reduce__(self): + # this approach is necessary because __setstate__ is called after + # __setitem__ upon unpickling and since __init__ is not called there + # is no shape attribute hence it is not possible to unpickle it. + return dict.__reduce__(self) + + def diagonal(self, k=0): + if self.ndim == 2: + return super().diagonal(k) + raise ValueError("diagonal requires two dimensions") + + diagonal.__doc__ = _spbase.diagonal.__doc__ + + def transpose(self, axes=None, copy=False): + if self.ndim == 1: + return self.copy() + + if axes is not None and axes != (1, 0): + raise ValueError( + "Sparse arrays/matrices do not support " + "an 'axes' parameter because swapping " + "dimensions is the only logical permutation." + ) + + M, N = self.shape + new = self._dok_container((N, M), dtype=self.dtype, copy=copy) + new._dict.update((((right, left), val) for (left, right), val in self.items())) + return new + + transpose.__doc__ = _spbase.transpose.__doc__ + + def copy(self): + new = self._dok_container(self.shape, dtype=self.dtype) + new._dict.update(self._dict) + return new + + copy.__doc__ = _spbase.copy.__doc__ + + @classmethod + def fromkeys(cls, iterable, value=1, /): + tmp = dict.fromkeys(iterable, value) + if isinstance(next(iter(tmp)), tuple): + shape = tuple(max(idx) + 1 for idx in zip(*tmp)) + else: + shape = (max(tmp) + 1,) + result = cls(shape, dtype=type(value)) + result._dict = tmp + return result + + def tocoo(self, copy=False): + nnz = self.nnz + if nnz == 0: + return self._coo_container(self.shape, dtype=self.dtype) + + idx_dtype = self._get_index_dtype(maxval=max(self.shape)) + data = np.fromiter(self.values(), dtype=self.dtype, count=nnz) + # handle 1d keys specially b/c not a tuple + inds = zip(*self.keys()) if self.ndim > 1 else (self.keys(),) + coords = tuple(np.fromiter(ix, dtype=idx_dtype, count=nnz) for ix in inds) + A = self._coo_container((data, coords), shape=self.shape, dtype=self.dtype) + A.has_canonical_format = True + return A + + tocoo.__doc__ = _spbase.tocoo.__doc__ + + def todok(self, copy=False): + if copy: + return self.copy() + return self + + todok.__doc__ = _spbase.todok.__doc__ + + def tocsc(self, copy=False): + if self.ndim == 1: + raise NotImplementedError("tocsr() not valid for 1d sparse array") + return self.tocoo(copy=False).tocsc(copy=copy) + + tocsc.__doc__ = _spbase.tocsc.__doc__ + + def resize(self, *shape): + shape = check_shape(shape, allow_nd=self._allow_nd) + if len(shape) != len(self.shape): + # TODO implement resize across dimensions + raise NotImplementedError + + if self.ndim == 1: + newN = shape[-1] + for i in list(self._dict): + if i >= newN: + del self._dict[i] + self._shape = shape + return + + newM, newN = shape + M, N = self.shape + if newM < M or newN < N: + # Remove all elements outside new dimensions + for i, j in list(self.keys()): + if i >= newM or j >= newN: + del self._dict[i, j] + self._shape = shape + + resize.__doc__ = _spbase.resize.__doc__ + + # Added for 1d to avoid `tocsr` from _base.py + def astype(self, dtype, casting='unsafe', copy=True): + dtype = np.dtype(dtype) + if self.dtype != dtype: + result = self._dok_container(self.shape, dtype=dtype) + data = np.array(list(self._dict.values()), dtype=dtype) + result._dict = dict(zip(self._dict, data)) + return result + elif copy: + return self.copy() + return self + + astype.__doc__ = _spbase.astype.__doc__ + + +def isspmatrix_dok(x): + """Is `x` of dok_array type? + + .. warning:: + + SciPy sparse is shifting from a sparse matrix interface to a sparse + array interface. In the next few releases we expect to deprecate the + sparse matrix interface. For documentation of the matrix + interface, see the :ref:`spmatrix interface docs `. + For guidance on converting existing code to sparse arrays, see + :ref:`Migration from spmatrix to sparray `. + + Parameters + ---------- + x + object to check for being a dok matrix + + Returns + ------- + bool + True if `x` is a dok matrix, False otherwise + + Examples + -------- + >>> from scipy.sparse import dok_array, dok_matrix, coo_matrix, isspmatrix_dok + >>> isspmatrix_dok(dok_matrix([[5]])) + True + >>> isspmatrix_dok(dok_array([[5]])) + False + >>> isspmatrix_dok(coo_matrix([[5]])) + False + """ + return isinstance(x, dok_matrix) + + +# This namespace class separates array from matrix with isinstance +class dok_array(_dok_base, sparray): + """ + Dictionary Of Keys based sparse array. + + This is an efficient structure for constructing sparse + arrays incrementally. + + This can be instantiated in several ways: + dok_array(D) + where D is a 2-D ndarray + + dok_array(S) + with another sparse array or matrix S (equivalent to S.todok()) + + dok_array((M,N), [dtype]) + create the array with initial shape (M,N) + dtype is optional, defaulting to dtype='d' + + Attributes + ---------- + dtype : dtype + Data type of the array + shape : 2-tuple + Shape of the array + ndim : int + Number of dimensions (this is always 2) + format : str + Three letter code for the format of the array storage, e.g. 'dok' + nnz : int + Number of values stored in the array + size : int + Number of values stored in the array + T : dok_array + The transpose of the array + mT : dok_array + The matrix transpose of the array + + Notes + ----- + + Sparse arrays can be used in arithmetic operations: they support + addition, subtraction, multiplication, division, and matrix power. + + - Allows for efficient O(1) access of individual elements. + - Duplicates are not allowed. + - Can be efficiently converted to a coo_array once constructed. + + Examples + -------- + >>> import numpy as np + >>> from scipy.sparse import dok_array + >>> S = dok_array((5, 5), dtype=np.float32) + >>> for i in range(5): + ... for j in range(5): + ... S[i, j] = i + j # Update element + + """ # numpydoc ignore=PR01 + + +class dok_matrix(spmatrix, _dok_base): + """ + Dictionary Of Keys based sparse matrix. + + This is an efficient structure for constructing sparse + matrices incrementally. + + .. warning:: + + SciPy sparse is shifting from a sparse matrix interface to a sparse + array interface. In the next few releases we expect to deprecate the + sparse matrix interface. For documentation of the matrix + interface, see the :ref:`spmatrix interface docs `. + For guidance on converting existing code to sparse arrays, see + :ref:`Migration from spmatrix to sparray `. + + This can be instantiated in several ways: + dok_matrix(D) + where D is a 2-D ndarray + + dok_matrix(S) + with another sparse array or matrix S (equivalent to S.todok()) + + dok_matrix((M,N), [dtype]) + create the matrix with initial shape (M,N) + dtype is optional, defaulting to dtype='d' + + Attributes + ---------- + dtype : dtype + Data type of the matrix + shape : 2-tuple + Shape of the matrix + ndim : int + Number of dimensions (this is always 2) + format : str + Three letter code for the format of the matrix storage, e.g. 'dok' + nnz : int + Number of values stored in the matrix + size : int + Number of values stored in the matrix + T : dok_matrix + The transpose of the matrix + mT : dok_matrix + The matrix transpose + + Notes + ----- + + Sparse matrices can be used in arithmetic operations: they support + addition, subtraction, multiplication, division, and matrix power. + + - Allows for efficient O(1) access of individual elements. + - Duplicates are not allowed. + - Can be efficiently converted to a coo_matrix once constructed. + + Examples + -------- + >>> import numpy as np + >>> from scipy.sparse import dok_matrix + >>> S = dok_matrix((5, 5), dtype=np.float32) + >>> for i in range(5): + ... for j in range(5): + ... S[i, j] = i + j # Update element + + """ + + def set_shape(self, shape): + new_matrix = self.reshape(shape, copy=False).asformat(self.format) + self.__dict__ = new_matrix.__dict__ + + def get_shape(self): + """Get shape of a sparse matrix.""" + return self._shape + + shape = property(fget=get_shape, fset=set_shape) + + def __reversed__(self): + return self._dict.__reversed__() + + def __or__(self, other): + if isinstance(other, _dok_base): + return self._dict | other._dict + return self._dict | other + + def __ror__(self, other): + if isinstance(other, _dok_base): + return self._dict | other._dict + return self._dict | other + + def __ior__(self, other): + if isinstance(other, _dok_base): + self._dict |= other._dict + else: + self._dict |= other + return self diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/_extract.py b/venv/lib/python3.12/site-packages/scipy/sparse/_extract.py new file mode 100644 index 0000000000000000000000000000000000000000..656e98adc59f8d4bd471f6d3286017c0fffe1237 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/_extract.py @@ -0,0 +1,246 @@ +"""Functions to extract parts of sparse matrices +""" + +__docformat__ = "restructuredtext en" + +__all__ = ['find', 'tril', 'triu'] + +import os +from warnings import warn + +from ._coo import coo_array, coo_matrix +from ._base import sparray, spmatrix + + +def find(A): + """Return the indices and values of the nonzero elements of a matrix. + + Parameters + ---------- + A : dense or sparse array or matrix + Matrix whose nonzero elements are desired. + + Returns + ------- + (I,J,V) : tuple of arrays + I,J, and V contain the row indices, column indices, and values + of the nonzero entries. + + + Examples + -------- + >>> from scipy.sparse import csr_array, find + >>> A = csr_array([[7.0, 8.0, 0],[0, 0, 9.0]]) + >>> find(A) + (array([0, 0, 1], dtype=int32), + array([0, 1, 2], dtype=int32), + array([ 7., 8., 9.])) + + """ + + A = coo_array(A, copy=True) + A.sum_duplicates() + # remove explicit zeros + nz_mask = A.data != 0 + return A.row[nz_mask], A.col[nz_mask], A.data[nz_mask] + + +def tril(A, k=0, format=None): + """Return the lower triangular portion of a sparse array or matrix. + + Returns the elements on or below the k-th diagonal of A. + - k = 0 corresponds to the main diagonal + - k > 0 is above the main diagonal + - k < 0 is below the main diagonal + + .. warning:: + + `tril` is switching to the sparse array interface. + + For the case where no input arrays are sparse, this function is + switching to returning a sparse array instead of sparse matrix. + Control the sparse return class by making at least one input sparse, + e.g., ``tril(coo_matrix(A))``, or ``tril(coo_array(A))``. + That removes any deprecation warnings as well. + For more general information about sparrays, see + :ref:`Migration from spmatrix to sparray `. + Handling of this no sparse input case will change no earlier than v1.20. + + Parameters + ---------- + A : dense or sparse array or matrix + Matrix whose lower trianglar portion is desired. + k : int : optional + The top-most diagonal of the lower triangle. + format : str + Sparse format of the result, e.g. format="csr", etc. + + Returns + ------- + L : sparse matrix + Lower triangular portion of A in sparse format. + + See Also + -------- + triu : upper triangle in sparse format + + Examples + -------- + >>> from scipy.sparse import csr_array, tril + >>> A = csr_array([[1, 2, 0, 0, 3], [4, 5, 0, 6, 7], [0, 0, 8, 9, 0]], + ... dtype='int32') + >>> A.toarray() + array([[1, 2, 0, 0, 3], + [4, 5, 0, 6, 7], + [0, 0, 8, 9, 0]], dtype=int32) + >>> tril(A).toarray() + array([[1, 0, 0, 0, 0], + [4, 5, 0, 0, 0], + [0, 0, 8, 0, 0]], dtype=int32) + >>> tril(A).nnz + 4 + >>> tril(A, k=1).toarray() + array([[1, 2, 0, 0, 0], + [4, 5, 0, 0, 0], + [0, 0, 8, 9, 0]], dtype=int32) + >>> tril(A, k=-1).toarray() + array([[0, 0, 0, 0, 0], + [4, 0, 0, 0, 0], + [0, 0, 0, 0, 0]], dtype=int32) + >>> tril(A, format='csc') + + + """ + if isinstance(A, sparray): + coo_sparse = coo_array + elif isinstance(A, spmatrix): + coo_sparse = coo_matrix + else: # dense + msg = """`tril` is switching to the sparse array interface. + + For the case where input arrays are numpy arrays, this function is + switching to returning a sparse array instead of sparse matrix. + Recover the sparse matrix return value by making one input a sparse matrix. + For example, tril(coo_matrix(A)). + Avoid this message for sparse array output by using tril(coo_array(A)). + For more information, see the spmatrix to sparray migration guide + https://docs.scipy.org/doc/scipy/reference/sparse.migration_to_sparray.html + + This function will be changed no earlier than v1.20. + """ + prefixes = (os.path.dirname(__file__),) + warn(msg, category=DeprecationWarning, skip_file_prefixes=prefixes) + # default when input is ndarray + coo_sparse = coo_matrix + + # convert to COOrdinate format where things are easy + A = coo_sparse(A, copy=False) + mask = A.row + k >= A.col + + row = A.row[mask] + col = A.col[mask] + data = A.data[mask] + new_coo = coo_sparse((data, (row, col)), shape=A.shape, dtype=A.dtype) + return new_coo.asformat(format) + + +def triu(A, k=0, format=None): + """Return the upper triangular portion of a sparse array or matrix. + + Returns the elements on or above the k-th diagonal of A. + - k = 0 corresponds to the main diagonal + - k > 0 is above the main diagonal + - k < 0 is below the main diagonal + + .. warning:: + + `triu` is switching to the sparse array interface. + + For the case where no input arrays are sparse, this function is + switching to returning a sparse array instead of sparse matrix. + Control the sparse return class by making at least one input sparse, + e.g., ``triu(coo_matrix(A))``, or ``triu(coo_array(A))``. + That removes any deprecation warnings as well. + For more general information about sparrays, see + :ref:`Migration from spmatrix to sparray `. + Handling of this no sparse input case will change no earlier than v1.20. + + Parameters + ---------- + A : dense or sparse array or matrix + Matrix whose upper trianglar portion is desired. + k : int : optional + The bottom-most diagonal of the upper triangle. + format : str + Sparse format of the result, e.g. format="csr", etc. + + Returns + ------- + L : sparse array or matrix + Upper triangular portion of A in sparse format. + Sparse array if A is a sparse array, otherwise matrix. + + See Also + -------- + tril : lower triangle in sparse format + + Examples + -------- + >>> from scipy.sparse import csr_array, triu + >>> A = csr_array([[1, 2, 0, 0, 3], [4, 5, 0, 6, 7], [0, 0, 8, 9, 0]], + ... dtype='int32') + >>> A.toarray() + array([[1, 2, 0, 0, 3], + [4, 5, 0, 6, 7], + [0, 0, 8, 9, 0]], dtype=int32) + >>> triu(A).toarray() + array([[1, 2, 0, 0, 3], + [0, 5, 0, 6, 7], + [0, 0, 8, 9, 0]], dtype=int32) + >>> triu(A).nnz + 8 + >>> triu(A, k=1).toarray() + array([[0, 2, 0, 0, 3], + [0, 0, 0, 6, 7], + [0, 0, 0, 9, 0]], dtype=int32) + >>> triu(A, k=-1).toarray() + array([[1, 2, 0, 0, 3], + [4, 5, 0, 6, 7], + [0, 0, 8, 9, 0]], dtype=int32) + >>> triu(A, format='csc') + + + """ + if isinstance(A, sparray): + coo_sparse = coo_array + elif isinstance(A, spmatrix): + coo_sparse = coo_matrix + else: # dense + msg = """`triu` is switching to the sparse array interface. + + For the case where input arrays are numpy arrays, this function is + switching to returning a sparse array instead of sparse matrix. + Recover the sparse matrix return value by making one input a sparse matrix. + For example, triu(coo_matrix(A)). + Avoid this message for sparse array output by using triu(coo_array(A)). + For more information, see the spmatrix to sparray migration guide + https://docs.scipy.org/doc/scipy/reference/sparse.migration_to_sparray.html + + This function will be changed no earlier than v1.20. + """ + prefixes = (os.path.dirname(__file__),) + warn(msg, category=DeprecationWarning, skip_file_prefixes=prefixes) + # default when input is ndarray + coo_sparse = coo_matrix + + # convert to COOrdinate format where things are easy + A = coo_sparse(A, copy=False) + mask = A.row + k <= A.col + + row = A.row[mask] + col = A.col[mask] + data = A.data[mask] + new_coo = coo_sparse((data, (row, col)), shape=A.shape, dtype=A.dtype) + return new_coo.asformat(format) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/_index.py b/venv/lib/python3.12/site-packages/scipy/sparse/_index.py new file mode 100644 index 0000000000000000000000000000000000000000..0121bc3420816807c15e8c8c9fa0a35c9e3b7bdf --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/_index.py @@ -0,0 +1,491 @@ +"""Indexing mixin for sparse array/matrix classes. +""" +import numpy as np +from ._sputils import isintlike +from ._base import sparray, issparse + +INT_TYPES = (int, np.integer) + + +def _broadcast_arrays(*arrays): + """ + Same as np.broadcast_arrays(a, b) but old writeability rules. + + NumPy >= 1.17.0 transitions broadcast_arrays to return + read-only arrays. Set writeability explicitly to avoid warnings. + Retain the old writeability rules, as our Cython code assumes + the old behavior. + """ + broadcast_arrays = np.broadcast_arrays(*arrays) + for x, a in zip(broadcast_arrays, arrays): + x.flags.writeable = a.flags.writeable + return broadcast_arrays + + +class IndexMixin: + """ + This class provides common dispatching and validation logic for indexing. + """ + def __getitem__(self, key, /): + index, new_shape, _, _ = _validate_indices(key, self.shape, self.format) + if len(new_shape) > 2: + raise IndexError("Indexing that leads to >2D is not supported by " + f"{self.format} format. Try converting to COO format") + + # 1D array + if len(index) == 1: + idx = index[0] + if isinstance(idx, np.ndarray): + if idx.shape == (): + idx = idx.item() + if isinstance(idx, INT_TYPES): + res = self._get_int(idx) + elif isinstance(idx, slice): + res = self._get_slice(idx) + else: # assume array idx + res = self._get_array(idx) + + # package the result and return + if not isinstance(self, sparray): + return res + # handle np.newaxis in idx when result would otherwise be a scalar + if res.shape == () and new_shape != (): + if len(new_shape) == 1: + return self.__class__([res], shape=new_shape, dtype=self.dtype) + if len(new_shape) == 2: + return self.__class__([[res]], shape=new_shape, dtype=self.dtype) + return res.reshape(new_shape) + + # 2D array + row, col = index + + # Dispatch to specialized methods. + if isinstance(row, INT_TYPES): + if isinstance(col, INT_TYPES): + res = self._get_intXint(row, col) + elif isinstance(col, slice): + res = self._get_intXslice(row, col) + elif col.ndim == 1: + res = self._get_intXarray(row, col) + elif col.ndim == 2: + res = self._get_intXarray(row, col) + else: + raise IndexError('index results in >2 dimensions') + elif isinstance(row, slice): + if isinstance(col, INT_TYPES): + res = self._get_sliceXint(row, col) + elif isinstance(col, slice): + if row == slice(None) and row == col: + res = self.copy() + else: + res = self._get_sliceXslice(row, col) + elif col.ndim == 1: + res = self._get_sliceXarray(row, col) + else: + raise IndexError('index results in >2 dimensions') + else: + if isinstance(col, INT_TYPES): + res = self._get_arrayXint(row, col) + elif isinstance(col, slice): + res = self._get_arrayXslice(row, col) + # arrayXarray preprocess + elif (row.ndim == 2 and row.shape[1] == 1 + and (col.ndim == 1 or col.shape[0] == 1)): + # outer indexing + res = self._get_columnXarray(row[:, 0], col.reshape(-1)) + else: + # inner indexing + row, col = _broadcast_arrays(row, col) + if row.shape != col.shape: + raise IndexError('number of row and column indices differ') + if row.size == 0: + res = self.__class__(np.atleast_2d(row).shape, dtype=self.dtype) + else: + res = self._get_arrayXarray(row, col) + + # handle spmatrix (must be 2d, dont let 1d new_shape start reshape) + if not isinstance(self, sparray): + if new_shape == () or (len(new_shape) == 1 and res.ndim != 0): + # res handles cases not inflated by None + return res + if len(new_shape) == 1: + # shape inflated to 1D by None in index. Make 2D + new_shape = (1,) + new_shape + # reshape if needed (when None changes shape, e.g. A[1,:,None]) + return res if new_shape == res.shape else res.reshape(new_shape) + + # package the result and return + if res.shape != new_shape: + # handle formats that support indexing but not 1D (lil for now) + if self.format == "lil" and len(new_shape) != 2: + if res.shape == (): + return self._coo_container([res], shape = new_shape) + return res.tocoo().reshape(new_shape) + return res.reshape(new_shape) + return res + + def __setitem__(self, key, x, /): + index, new_shape, _, _ = _validate_indices(key, self.shape, self.format) + + # 1D array + if len(index) == 1: + idx = index[0] + + if issparse(x): + x = x.toarray() + else: + x = np.asarray(x, dtype=self.dtype) + + if isinstance(idx, INT_TYPES): + if x.size != 1: + raise ValueError('Trying to assign a sequence to an item') + self._set_int(idx, x.flat[0]) + return + + if isinstance(idx, slice): + # check for simple case of slice that gives 1 item + # Note: Python `range` does not use lots of memory + idx_range = range(*idx.indices(self.shape[0])) + N = len(idx_range) + if N == 1 and x.size == 1: + self._set_int(idx_range[0], x.flat[0]) + return + idx = np.arange(*idx.indices(self.shape[0])) + idx_shape = idx.shape + else: + idx_shape = idx.squeeze().shape + # broadcast scalar to full 1d + if x.squeeze().shape != idx_shape: + x = np.broadcast_to(x, idx.shape) + if x.size != 0: + self._set_array(idx, x) + return + + # 2D array + row, col = index + + if isinstance(row, INT_TYPES) and isinstance(col, INT_TYPES): + if issparse(x): + x = x.toarray() + else: + x = np.asarray(x, dtype=self.dtype) + if x.size != 1: + raise ValueError('Trying to assign a sequence to an item') + self._set_intXint(row, col, x.flat[0]) + return + + if isinstance(row, slice): + row = np.arange(*row.indices(self.shape[0]))[:, None] + else: + row = np.atleast_1d(row) + + if isinstance(col, slice): + col = np.arange(*col.indices(self.shape[1]))[None, :] + if row.ndim == 1: + row = row[:, None] + else: + col = np.atleast_1d(col) + + i, j = _broadcast_arrays(row, col) + if i.shape != j.shape: + raise IndexError('number of row and column indices differ') + + if issparse(x): + if 0 in x.shape: + return + if i.ndim == 1: + # Inner indexing, so treat them like row vectors. + i = i[None] + j = j[None] + x = x.tocoo(copy=False).reshape(x._shape_as_2d, copy=True) + broadcast_row = x.shape[0] == 1 and i.shape[0] != 1 + broadcast_col = x.shape[1] == 1 and i.shape[1] != 1 + if not ((broadcast_row or x.shape[0] == i.shape[0]) and + (broadcast_col or x.shape[1] == i.shape[1])): + raise ValueError('shape mismatch in assignment') + x.sum_duplicates() + self._set_arrayXarray_sparse(i, j, x) + else: + # Make x and i into the same shape + x = np.asarray(x, dtype=self.dtype) + if x.squeeze().shape != i.squeeze().shape: + x = np.broadcast_to(x, i.shape) + if x.size == 0: + return + x = x.reshape(i.shape) + self._set_arrayXarray(i, j, x) + + def _getrow(self, i): + """Return a copy of row i of the matrix, as a (1 x n) row vector. + """ + M, N = self.shape + i = int(i) + if i < -M or i >= M: + raise IndexError(f'index ({i}) out of range') + if i < 0: + i += M + return self._get_intXslice(i, slice(None)) + + def _getcol(self, i): + """Return a copy of column i of the matrix, as a (m x 1) column vector. + """ + M, N = self.shape + i = int(i) + if i < -N or i >= N: + raise IndexError(f'index ({i}) out of range') + if i < 0: + i += N + return self._get_sliceXint(slice(None), i) + + def _get_int(self, idx): + raise NotImplementedError() + + def _get_slice(self, idx): + raise NotImplementedError() + + def _get_array(self, idx): + raise NotImplementedError() + + def _get_intXint(self, row, col): + raise NotImplementedError() + + def _get_intXarray(self, row, col): + raise NotImplementedError() + + def _get_intXslice(self, row, col): + raise NotImplementedError() + + def _get_sliceXint(self, row, col): + raise NotImplementedError() + + def _get_sliceXslice(self, row, col): + raise NotImplementedError() + + def _get_sliceXarray(self, row, col): + raise NotImplementedError() + + def _get_arrayXint(self, row, col): + raise NotImplementedError() + + def _get_arrayXslice(self, row, col): + raise NotImplementedError() + + def _get_columnXarray(self, row, col): + raise NotImplementedError() + + def _get_arrayXarray(self, row, col): + raise NotImplementedError() + + def _set_int(self, idx, x): + raise NotImplementedError() + + def _set_array(self, idx, x): + raise NotImplementedError() + + def _set_intXint(self, row, col, x): + raise NotImplementedError() + + def _set_arrayXarray(self, row, col, x): + raise NotImplementedError() + + def _set_arrayXarray_sparse(self, row, col, x): + # Fall back to densifying x + x = np.asarray(x.toarray(), dtype=self.dtype) + x, _ = _broadcast_arrays(x, row) + self._set_arrayXarray(row, col, x) + + +def _validate_indices(key, self_shape, self_format): + """Returns four sequences: (index, requested shape, arrays, nones) + + index : tuple of validated idx objects. bool arrays->nonzero(), + arrays broadcast, ints and slices as they are, Nones removed + requested shape : the shape of the indexed space, including Nones + arr_pos : position within index of all arrays or ints (for array fancy indexing) + none_pos : insert positions to put newaxis coords in indexed space. + """ + self_ndim = len(self_shape) + # single ellipsis + if key is Ellipsis: + return (slice(None),) * self_ndim, self_shape, [], [] + + if not isinstance(key, tuple): + key = [key] + + # pass 1: + # - expand ellipsis to allow matching to self_shape + # - preprocess boolean array index + # - error on sparse array as an index + # - count the ndim of the index and check if too long + ellps_pos = None + index_1st = [] + prelim_ndim = 0 + for i, idx in enumerate(key): + if idx is Ellipsis: + if ellps_pos is not None: + raise IndexError('an index can only have a single ellipsis') + ellps_pos = i + elif idx is None: + index_1st.append(idx) + elif isinstance(idx, slice) or isintlike(idx): + index_1st.append(idx) + prelim_ndim += 1 + elif (ix := _compatible_boolean_index(idx, self_ndim)) is not None: + # can't check the shape of ix until we resolve ellipsis (pass 2) + index_1st.append(ix) + prelim_ndim += ix.ndim + elif issparse(idx): + # TODO: make sparse indexing work for sparray + raise IndexError( + 'Indexing with sparse matrices is not supported ' + 'except boolean indexing where matrix and index ' + 'are equal shapes.') + else: # dense array + index_1st.append(np.asarray(idx)) + prelim_ndim += 1 + if prelim_ndim > self_ndim: + raise IndexError( + 'Too many indices for array or tuple index out of range. ' + f'Key {key} needs {prelim_ndim}D. Array is {self_ndim}D' + ) + ellip_slices = (self_ndim - prelim_ndim) * [slice(None)] + if ellip_slices: + if ellps_pos is None: + index_1st.extend(ellip_slices) + else: + index_1st = index_1st[:ellps_pos] + ellip_slices + index_1st[ellps_pos:] + + # second pass (have processed ellipsis and preprocessed arrays) + # pass 2: + # note: integer arrays provide info for one axis even if >1D array. + # The shape of array affects outgoing(get)/incoming(set) shape only + # - form `new_shape` (shape of outgo/incom-ing result of key + # - form `index` (validated form of each slice/int/array index) + # - validate and make canonical: slice and int + # - turn bool arrays to int arrays via `.nonzero()` + # - collect positions of Newaxis/None in `none_positions` + # - collect positions of "array or int" in `arr_int_pos` + idx_shape = [] + index_ndim = 0 + index = [] + array_indices = [] + none_positions = [] + arr_int_pos = [] # track positions of arrays and integers + + for i, idx in enumerate(index_1st): + if idx is None: + none_positions.append(len(idx_shape)) + idx_shape.append(1) + elif isinstance(idx, slice): + index.append(idx) + Ms = self_shape[index_ndim] + len_slice = len(range(*idx.indices(Ms))) + idx_shape.append(len_slice) + index_ndim += 1 + elif isintlike(idx): + N = self_shape[index_ndim] + if not (-N <= idx < N): + raise IndexError(f'index ({idx}) out of range') + idx = int(idx + N if idx < 0 else idx) + index.append(idx) + arr_int_pos.append(index_ndim) + index_ndim += 1 + # bool array (checked in first pass) + elif idx.dtype.kind == 'b': + tmp_ndim = index_ndim + idx.ndim + mid_shape = self_shape[index_ndim:tmp_ndim] + if idx.shape != mid_shape: + raise IndexError( + f"bool index {i} has shape {mid_shape} instead of {idx.shape}" + ) + index.extend(idx.nonzero()) + array_indices.extend(range(index_ndim, tmp_ndim)) + arr_int_pos.extend(range(index_ndim, tmp_ndim)) + index_ndim = tmp_ndim + else: # dense array + N = self_shape[index_ndim] + idx = _asindices(idx, N, self_format) + index.append(idx) + arr_int_pos.append(index_ndim) + array_indices.append(index_ndim) + index_ndim += 1 + if len(array_indices) > 1: + arr_shapes = [index[i].shape for i in array_indices] + try: + arr_shape = np.broadcast_shapes(*arr_shapes) + except ValueError: + shapes = " ".join(str(shp) for shp in arr_shapes) + msg = (f'shape mismatch: indexing arrays could not be broadcast ' + f'together with shapes {shapes}') + raise IndexError(msg) + # len(array_indices) implies arr_int_pos has at least one element + # if arrays and ints not adjacent, move to front of shape + if len(arr_int_pos) != (arr_int_pos[-1] - arr_int_pos[0] + 1): + idx_shape = list(arr_shape) + idx_shape + else: + arr_pos = arr_int_pos[0] + idx_shape = idx_shape[:arr_pos] + list(arr_shape) + idx_shape[arr_pos:] + elif len(array_indices) == 1: + arr_shape = index[array_indices[0]].shape + arr_pos = arr_int_pos[0] + idx_shape = idx_shape[:arr_pos] + list(arr_shape) + idx_shape[arr_pos:] + return tuple(index), tuple(idx_shape), arr_int_pos, none_positions + + +def _asindices(idx, length, format): + """Convert `idx` to a valid index for an axis with a given length. + + Subclasses that need special validation can override this method. + """ + try: + ix = np.asarray(idx) + except (ValueError, TypeError, MemoryError) as e: + raise IndexError('invalid index') from e + + if format != "coo" and ix.ndim not in (1, 2) or format == "coo" and ix.ndim == 0: + raise IndexError(f'Index dimension must be 1 or 2. Got {ix.ndim}') + + # LIL routines handle bounds-checking for us, so don't do it here. + if format == "lil": + return ix + + if ix.size == 0: + return ix + + # Check bounds + max_indx = ix.max() + if max_indx >= length: + raise IndexError(f'index ({max_indx}) out of range') + + min_indx = ix.min() + if min_indx < 0: + if min_indx < -length: + raise IndexError(f'index ({min_indx}) out of range') + if ix is idx or not ix.flags.owndata: + ix = ix.copy() + ix[ix < 0] += length + return ix + + +def _compatible_boolean_index(idx, desired_ndim): + """Check for boolean array or array-like. peek before asarray for array-like""" + # use attribute ndim to indicate a compatible array and check dtype + # if not, look at 1st element as quick rejection of bool, else slower asanyarray + if not hasattr(idx, 'ndim'): + # is first element boolean? + try: + ix = next(iter(idx), None) + for _ in range(desired_ndim): + if isinstance(ix, bool): + break + ix = next(iter(ix), None) + else: + return None + except TypeError: + return None + # since first is boolean, construct array and check all elements + idx = np.asanyarray(idx) + + if idx.dtype.kind == 'b': + return idx + return None diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/_lil.py b/venv/lib/python3.12/site-packages/scipy/sparse/_lil.py new file mode 100644 index 0000000000000000000000000000000000000000..1fb85280af3242b52b2a4123a0bb161c13d93dec --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/_lil.py @@ -0,0 +1,676 @@ +"""List of Lists sparse matrix class +""" + +__docformat__ = "restructuredtext en" + +__all__ = ['lil_array', 'lil_matrix', 'isspmatrix_lil'] + +from bisect import bisect_left + +import numpy as np + +from ._matrix import spmatrix +from ._base import _spbase, sparray, issparse +from ._index import IndexMixin, INT_TYPES, _broadcast_arrays +from ._sputils import (getdtype, isshape, isscalarlike, upcast_scalar, + check_shape) +from . import _csparsetools + + +class _lil_base(_spbase, IndexMixin): + _format = 'lil' + + def __init__(self, arg1, shape=None, dtype=None, copy=False, *, maxprint=None): + _spbase.__init__(self, arg1, maxprint=maxprint) + self.dtype = getdtype(dtype, arg1, default=float) + + # First get the shape + if issparse(arg1): + if arg1.format == "lil" and copy: + A = arg1.copy() + else: + A = arg1.tolil() + + if dtype is not None: + newdtype = getdtype(dtype) + A = A.astype(newdtype, copy=False) + + self._shape = check_shape(A.shape) + self.dtype = A.dtype + self.rows = A.rows + self.data = A.data + elif isinstance(arg1,tuple): + if isshape(arg1): + if shape is not None: + raise ValueError('invalid use of shape parameter') + M, N = arg1 + self._shape = check_shape((M, N)) + self.rows = np.empty((M,), dtype=object) + self.data = np.empty((M,), dtype=object) + for i in range(M): + self.rows[i] = [] + self.data[i] = [] + else: + raise TypeError('unrecognized lil_array constructor usage') + else: + # assume A is dense + try: + A = self._ascontainer(arg1) + except TypeError as e: + raise TypeError('unsupported matrix type') from e + if isinstance(self, sparray) and A.ndim != 2: + raise ValueError(f"LIL arrays don't support {A.ndim}D input. Use 2D") + A = self._csr_container(A, dtype=dtype).tolil() + + self._shape = check_shape(A.shape) + self.dtype = getdtype(A.dtype) + self.rows = A.rows + self.data = A.data + + def __iadd__(self,other): + self[:,:] = self + other + return self + + def __isub__(self,other): + self[:,:] = self - other + return self + + def __imul__(self,other): + if isscalarlike(other): + self[:,:] = self * other + return self + else: + return NotImplemented + + def __itruediv__(self,other): + if isscalarlike(other): + self[:,:] = self / other + return self + else: + return NotImplemented + + # Whenever the dimensions change, empty lists should be created for each + # row + + def _getnnz(self, axis=None): + if axis is None: + return sum([len(rowvals) for rowvals in self.data]) + if axis < 0: + axis += 2 + if axis == 0: + out = np.zeros(self.shape[1], dtype=np.intp) + for row in self.rows: + out[row] += 1 + return out + elif axis == 1: + return np.array([len(rowvals) for rowvals in self.data], dtype=np.intp) + else: + raise ValueError('axis out of bounds') + + _getnnz.__doc__ = _spbase._getnnz.__doc__ + + def count_nonzero(self, axis=None): + if axis is None: + return sum(np.count_nonzero(rowvals) for rowvals in self.data) + + if axis < 0: + axis += 2 + if axis == 0: + out = np.zeros(self.shape[1], dtype=np.intp) + for row, data in zip(self.rows, self.data): + mask = [c for c, d in zip(row, data) if d != 0] + out[mask] += 1 + return out + elif axis == 1: + return np.array( + [np.count_nonzero(rowvals) for rowvals in self.data], dtype=np.intp, + ) + else: + raise ValueError('axis out of bounds') + + count_nonzero.__doc__ = _spbase.count_nonzero.__doc__ + + def getrowview(self, i): + """Returns a view of the 'i'th row (without copying). + + Parameters + ---------- + i : int + Row to return view of. + + Returns + ------- + lil_array or lil_matrix + A view of the 'i'th row. + """ + new = self._lil_container((1, self.shape[1]), dtype=self.dtype) + new.rows[0] = self.rows[i] + new.data[0] = self.data[i] + return new + + def getrow(self, i): + """Returns a copy of the 'i'th row. + + Parameters + ---------- + i : int + Row to return a copy of. + + Returns + ------- + lil_array or lil_matrix + A copy of the 'i'th row. + """ + M, N = self.shape + if i < 0: + i += M + if i < 0 or i >= M: + raise IndexError('row index out of bounds') + new = self._lil_container((1, N), dtype=self.dtype) + new.rows[0] = self.rows[i][:] + new.data[0] = self.data[i][:] + return new + + def __getitem__(self, key): + # Fast path for simple (int, int) indexing. + if (isinstance(key, tuple) and len(key) == 2 and + isinstance(key[0], INT_TYPES) and + isinstance(key[1], INT_TYPES)): + # lil_get1 handles validation for us. + return self._get_intXint(*key) + # Everything else takes the normal path. + return IndexMixin.__getitem__(self, key) + + def _get_intXint(self, row, col): + v = _csparsetools.lil_get1(self.shape[0], self.shape[1], self.rows, + self.data, row, col) + return self.dtype.type(v) + + def _get_sliceXint(self, row, col): + row = range(*row.indices(self.shape[0])) + return self._get_row_ranges(row, slice(col, col+1)) + + def _get_arrayXint(self, row, col): + res = self._get_row_ranges(row.ravel(), slice(col, col+1)) + if row.ndim > 1: + return res.reshape(row.shape) + return res + + def _get_intXslice(self, row, col): + return self._get_row_ranges((row,), col) + + def _get_sliceXslice(self, row, col): + row = range(*row.indices(self.shape[0])) + return self._get_row_ranges(row, col) + + def _get_arrayXslice(self, row, col): + return self._get_row_ranges(row, col) + + def _get_intXarray(self, row, col): + row = np.array(row, dtype=col.dtype, ndmin=1) + return self._get_columnXarray(row, col) + + def _get_sliceXarray(self, row, col): + row = np.arange(*row.indices(self.shape[0])) + return self._get_columnXarray(row, col) + + def _get_columnXarray(self, row, col): + # outer indexing + row, col = _broadcast_arrays(row[:,None], col) + return self._get_arrayXarray(row, col) + + def _get_arrayXarray(self, row, col): + # inner indexing + i, j = map(np.atleast_2d, _prepare_index_for_memoryview(row, col)) + new = self._lil_container(i.shape, dtype=self.dtype) + _csparsetools.lil_fancy_get(self.shape[0], self.shape[1], + self.rows, self.data, + new.rows, new.data, + i, j) + return new + + def _get_row_ranges(self, rows, col_slice): + """ + Fast path for indexing in the case where column index is slice. + + This gains performance improvement over brute force by more + efficient skipping of zeros, by accessing the elements + column-wise in order. + + Parameters + ---------- + rows : sequence or range + Rows indexed. If range, must be within valid bounds. + col_slice : slice + Columns indexed + + """ + j_start, j_stop, j_stride = col_slice.indices(self.shape[1]) + col_range = range(j_start, j_stop, j_stride) + nj = len(col_range) + new = self._lil_container((len(rows), nj), dtype=self.dtype) + + _csparsetools.lil_get_row_ranges(self.shape[0], self.shape[1], + self.rows, self.data, + new.rows, new.data, + rows, + j_start, j_stop, j_stride, nj) + + return new + + def _set_intXint(self, row, col, x): + _csparsetools.lil_insert(self.shape[0], self.shape[1], self.rows, + self.data, row, col, x) + + def _set_arrayXarray(self, row, col, x): + i, j, x = map(np.atleast_2d, _prepare_index_for_memoryview(row, col, x)) + _csparsetools.lil_fancy_set(self.shape[0], self.shape[1], + self.rows, self.data, + i, j, x) + + def _set_arrayXarray_sparse(self, row, col, x): + # Fall back to densifying x + x = np.asarray(x.toarray(), dtype=self.dtype) + x, _ = _broadcast_arrays(x, row) + self._set_arrayXarray(row, col, x) + + def __setitem__(self, key, x): + if isinstance(key, tuple) and len(key) == 2: + row, col = key + # Fast path for simple (int, int) indexing. + if isinstance(row, INT_TYPES) and isinstance(col, INT_TYPES): + if issparse(x): + x = x.toarray() + if isinstance(x, np.ndarray): + x = x.item() + x = self.dtype.type(x) + if x.size > 1: + raise ValueError("Trying to assign a sequence to an item") + return self._set_intXint(row, col, x) + # Fast path for full-matrix sparse assignment. + if (isinstance(row, slice) and isinstance(col, slice) and + row == slice(None) and col == slice(None) and + issparse(x) and x.shape == self.shape): + x = self._lil_container(x, dtype=self.dtype) + self.rows = x.rows + self.data = x.data + return + # Everything else takes the normal path. + IndexMixin.__setitem__(self, key, x) + + def _mul_scalar(self, other): + if other == 0: + # Multiply by zero: return the zero matrix + new = self._lil_container(self.shape, dtype=self.dtype) + else: + res_dtype = upcast_scalar(self.dtype, other) + + new = self.astype(res_dtype) # sure to make a copy + # Multiply this scalar by every element. + for j, rowvals in enumerate(new.data): + new.data[j] = [val*other for val in rowvals] + return new + + def __truediv__(self, other): # self / other + if isscalarlike(other): + new = self.copy() + new.dtype = np.result_type(self, other) + # Divide every element by this scalar + for j, rowvals in enumerate(new.data): + new.data[j] = [val/other for val in rowvals] + return new + else: + return self.tocsr() / other + + def copy(self): + M, N = self.shape + new = self._lil_container(self.shape, dtype=self.dtype) + # This is ~14x faster than calling deepcopy() on rows and data. + _csparsetools.lil_get_row_ranges(M, N, self.rows, self.data, + new.rows, new.data, range(M), + 0, N, 1, N) + return new + + copy.__doc__ = _spbase.copy.__doc__ + + def reshape(self, *shape, order="C", copy=False): + shape = check_shape(shape, self.shape) + + # Return early if reshape is not required + if shape == self.shape: + if copy: + return self.copy() + else: + return self + + new = self._lil_container(shape, dtype=self.dtype) + + if order == 'C': + ncols = self.shape[1] + for i, row in enumerate(self.rows): + for col, j in enumerate(row): + new_r, new_c = np.unravel_index(i * ncols + j, shape) + new[new_r, new_c] = self[i, j] + elif order == 'F': + nrows = self.shape[0] + for i, row in enumerate(self.rows): + for col, j in enumerate(row): + new_r, new_c = np.unravel_index(i + j * nrows, shape, order) + new[new_r, new_c] = self[i, j] + else: + raise ValueError("'order' must be 'C' or 'F'") + + return new + + reshape.__doc__ = _spbase.reshape.__doc__ + + def resize(self, *shape): + shape = check_shape(shape) + new_M, new_N = shape + M, N = self.shape + + if new_M < M: + self.rows = self.rows[:new_M] + self.data = self.data[:new_M] + elif new_M > M: + self.rows = np.resize(self.rows, new_M) + self.data = np.resize(self.data, new_M) + for i in range(M, new_M): + self.rows[i] = [] + self.data[i] = [] + + if new_N < N: + for row, data in zip(self.rows, self.data): + trunc = bisect_left(row, new_N) + del row[trunc:] + del data[trunc:] + + self._shape = shape + + resize.__doc__ = _spbase.resize.__doc__ + + def toarray(self, order=None, out=None): + d = self._process_toarray_args(order, out) + for i, row in enumerate(self.rows): + for pos, j in enumerate(row): + d[i, j] = self.data[i][pos] + return d + + toarray.__doc__ = _spbase.toarray.__doc__ + + def transpose(self, axes=None, copy=False): + return self.tocsr(copy=copy).transpose(axes=axes, copy=False).tolil(copy=False) + + transpose.__doc__ = _spbase.transpose.__doc__ + + def tolil(self, copy=False): + if copy: + return self.copy() + else: + return self + + tolil.__doc__ = _spbase.tolil.__doc__ + + def tocsr(self, copy=False): + M, N = self.shape + if M == 0 or N == 0: + return self._csr_container((M, N), dtype=self.dtype) + + # construct indptr array + if M*N <= np.iinfo(np.int32).max: + # fast path: it is known that 64-bit indexing will not be needed. + idx_dtype = np.int32 + indptr = np.empty(M + 1, dtype=idx_dtype) + indptr[0] = 0 + _csparsetools.lil_get_lengths(self.rows, indptr[1:]) + np.cumsum(indptr, out=indptr) + nnz = indptr[-1] + else: + idx_dtype = self._get_index_dtype(maxval=N) + lengths = np.empty(M, dtype=idx_dtype) + _csparsetools.lil_get_lengths(self.rows, lengths) + nnz = lengths.sum(dtype=np.int64) + idx_dtype = self._get_index_dtype(maxval=max(N, nnz)) + indptr = np.empty(M + 1, dtype=idx_dtype) + indptr[0] = 0 + np.cumsum(lengths, dtype=idx_dtype, out=indptr[1:]) + + indices = np.empty(nnz, dtype=idx_dtype) + data = np.empty(nnz, dtype=self.dtype) + _csparsetools.lil_flatten_to_array(self.rows, indices) + _csparsetools.lil_flatten_to_array(self.data, data) + + # init csr matrix + return self._csr_container((data, indices, indptr), shape=self.shape) + + tocsr.__doc__ = _spbase.tocsr.__doc__ + + +def _prepare_index_for_memoryview(i, j, x=None): + """ + Convert index and data arrays to form suitable for passing to the + Cython fancy getset routines. + + The conversions are necessary since to (i) ensure the integer + index arrays are in one of the accepted types, and (ii) to ensure + the arrays are writable so that Cython memoryview support doesn't + choke on them. + + Parameters + ---------- + i, j + Index arrays + x : optional + Data arrays + + Returns + ------- + i, j, x + Re-formatted arrays (x is omitted, if input was None) + + """ + if i.dtype > j.dtype: + j = j.astype(i.dtype) + elif i.dtype < j.dtype: + i = i.astype(j.dtype) + + if not i.flags.writeable or i.dtype not in (np.int32, np.int64): + i = i.astype(np.intp) + if not j.flags.writeable or j.dtype not in (np.int32, np.int64): + j = j.astype(np.intp) + + if x is not None: + if not x.flags.writeable: + x = x.copy() + return i, j, x + else: + return i, j + + +def isspmatrix_lil(x): + """Is `x` of lil_matrix type? + + .. warning:: + + SciPy sparse is shifting from a sparse matrix interface to a sparse + array interface. In the next few releases we expect to deprecate the + sparse matrix interface. For documentation of the matrix + interface, see the :ref:`spmatrix interface docs `. + For guidance on converting existing code to sparse arrays, see + :ref:`Migration from spmatrix to sparray `. + + Parameters + ---------- + x + object to check for being a lil matrix + + Returns + ------- + bool + True if `x` is a lil matrix, False otherwise + + Examples + -------- + >>> from scipy.sparse import lil_array, lil_matrix, coo_matrix, isspmatrix_lil + >>> isspmatrix_lil(lil_matrix([[5]])) + True + >>> isspmatrix_lil(lil_array([[5]])) + False + >>> isspmatrix_lil(coo_matrix([[5]])) + False + """ + return isinstance(x, lil_matrix) + + +# This namespace class separates array from matrix with isinstance +class lil_array(_lil_base, sparray): + """ + Row-based LIst of Lists sparse array. + + This is a structure for constructing sparse arrays incrementally. + Note that inserting a single item can take linear time in the worst case; + to construct the array efficiently, make sure the items are pre-sorted by + index, per row. + + This can be instantiated in several ways: + lil_array(D) + where D is a 2-D ndarray + + lil_array(S) + with another sparse array or matrix S (equivalent to S.tolil()) + + lil_array((M, N), [dtype]) + to construct an empty array with shape (M, N) + dtype is optional, defaulting to dtype='d'. + + Attributes + ---------- + data : ndarray + LIL format data array of the array + rows : ndarray + LIL format row index array of the array + dtype : dtype + Data type of the array + shape : 2-tuple + Shape of the array + ndim : int + Number of dimensions (this is always 2) + format : str + Three letter code for the format of the array storage, e.g. 'lil' + nnz : int + Number of values stored in the array + size : int + Number of values stored in the array + T : lil_array + The transpose of the array + mT : lil_array + The matrix transpose of the array + + Notes + ----- + Sparse arrays can be used in arithmetic operations: they support + addition, subtraction, multiplication, division, and matrix power. + + Advantages of the LIL format + - supports flexible slicing + - changes to the array sparsity structure are efficient + + Disadvantages of the LIL format + - arithmetic operations LIL + LIL are slow (consider CSR or CSC) + - slow column slicing (consider CSC) + - slow matrix vector products (consider CSR or CSC) + + Intended Usage + - LIL is a convenient format for constructing sparse arrays + - once an array has been constructed, convert to CSR or + CSC format for fast arithmetic and matrix vector operations + - consider using the COO format when constructing large arrays + + Data Structure + - An array (``self.rows``) of rows, each of which is a sorted + list of column indices of non-zero elements. + - The corresponding nonzero values are stored in similar + fashion in ``self.data``. + + """ # numpydoc ignore=PR01 + + +class lil_matrix(spmatrix, _lil_base): + """ + Row-based LIst of Lists sparse matrix. + + This is a structure for constructing sparse matrices incrementally. + Note that inserting a single item can take linear time in the worst case; + to construct the matrix efficiently, make sure the items are pre-sorted by + index, per row. + + .. warning:: + + SciPy sparse is shifting from a sparse matrix interface to a sparse + array interface. In the next few releases we expect to deprecate the + sparse matrix interface. For documentation of the matrix + interface, see the :ref:`spmatrix interface docs `. + For guidance on converting existing code to sparse arrays, see + :ref:`Migration from spmatrix to sparray `. + + This can be instantiated in several ways: + lil_matrix(D) + where D is a 2-D ndarray + + lil_matrix(S) + with another sparse array or matrix S (equivalent to S.tolil()) + + lil_matrix((M, N), [dtype]) + to construct an empty matrix with shape (M, N) + dtype is optional, defaulting to dtype='d'. + + Attributes + ---------- + data : ndarray + LIL format data array of the matrix + rows : ndarray + LIL format row index array of the matrix + dtype : dtype + Data type of the matrix + shape : 2-tuple + Shape of the matrix + ndim : int + Number of dimensions (this is always 2) + format : str + Three letter code for the format of the matrix storage, e.g. 'lil' + nnz : int + Number of values stored in the matrix + size : int + Number of values stored in the matrix + T : lil_matrix + The transpose of the matrix + mT : lil_array + The transpose of the matrix + + Notes + ----- + Sparse matrices can be used in arithmetic operations: they support + addition, subtraction, multiplication, division, and matrix power. + + Advantages of the LIL format + - supports flexible slicing + - changes to the matrix sparsity structure are efficient + + Disadvantages of the LIL format + - arithmetic operations LIL + LIL are slow (consider CSR or CSC) + - slow column slicing (consider CSC) + - slow matrix vector products (consider CSR or CSC) + + Intended Usage + - LIL is a convenient format for constructing sparse matrices + - once a matrix has been constructed, convert to CSR or + CSC format for fast arithmetic and matrix vector operations + - consider using the COO format when constructing large matrices + + Data Structure + - An array (``self.rows``) of rows, each of which is a sorted + list of column indices of non-zero elements. + - The corresponding nonzero values are stored in similar + fashion in ``self.data``. + + """ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/_matrix.py b/venv/lib/python3.12/site-packages/scipy/sparse/_matrix.py new file mode 100644 index 0000000000000000000000000000000000000000..4840d33d91a652fa69f567411a88cce1d5da842b --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/_matrix.py @@ -0,0 +1,179 @@ +class spmatrix: + """This class provides a base class for all sparse matrix classes. + + .. warning:: + + SciPy sparse is shifting from a sparse matrix interface to a sparse + array interface. In the next few releases we expect to deprecate the + sparse matrix interface. For documentation of the matrix + interface, see the :ref:`spmatrix interface docs `. + For guidance on converting existing code to sparse arrays, see + :ref:`Migration from spmatrix to sparray `. + + This class also serves as the namespace for SciPy sparse matrix types. + It cannot be instantiated. Most of the work is provided by subclasses. + """ + _allow_nd: tuple[int, ...] = (2,) + + @property + def _bsr_container(self): + from ._bsr import bsr_matrix + return bsr_matrix + + @property + def _coo_container(self): + from ._coo import coo_matrix + return coo_matrix + + @property + def _csc_container(self): + from ._csc import csc_matrix + return csc_matrix + + @property + def _csr_container(self): + from ._csr import csr_matrix + return csr_matrix + + @property + def _dia_container(self): + from ._dia import dia_matrix + return dia_matrix + + @property + def _dok_container(self): + from ._dok import dok_matrix + return dok_matrix + + @property + def _lil_container(self): + from ._lil import lil_matrix + return lil_matrix + + # Restore matrix multiplication + def __mul__(self, other): + return self._matmul_dispatch(other) + + def __rmul__(self, other): + return self._rmatmul_dispatch(other) + + # Restore matrix power + def __pow__(self, power): + from .linalg import matrix_power + + return matrix_power(self, power) + + ## Backward compatibility + + def set_shape(self, shape): + """Set the shape of the matrix in-place""" + # Make sure copy is False since this is in place + # Make sure format is unchanged because we are doing a __dict__ swap + new_self = self.reshape(shape, copy=False).asformat(self.format) + self.__dict__ = new_self.__dict__ + + def get_shape(self): + """Get the shape of the matrix""" + return self._shape + + shape = property(fget=get_shape, fset=set_shape, + doc="Shape of the matrix") + + def asfptype(self): + """Upcast matrix to a floating point format (if necessary)""" + return self._asfptype() + + def getmaxprint(self): + """Maximum number of elements to display when printed.""" + return self._getmaxprint() + + def getformat(self): + """Matrix storage format""" + return self.format + + def getnnz(self, axis=None): + """Number of stored values, including explicit zeros. + + Parameters + ---------- + axis : None, 0, or 1 + Select between the number of values across the whole array, in + each column, or in each row. + """ + return self._getnnz(axis=axis) + + def getH(self): + """Return the Hermitian transpose of this matrix. + + See Also + -------- + numpy.matrix.getH : NumPy's implementation of `getH` for matrices + """ + return self.conjugate().transpose() + + def getcol(self, j): + """Returns a copy of column j of the matrix, as an (m x 1) sparse + matrix (column vector). + """ + return self._getcol(j) + + def getrow(self, i): + """Returns a copy of row i of the matrix, as a (1 x n) sparse + matrix (row vector). + """ + return self._getrow(i) + + def todense(self, order=None, out=None): + """ + Return a dense representation of this sparse matrix. + + Parameters + ---------- + order : {'C', 'F'}, optional + Whether to store multi-dimensional data in C (row-major) + or Fortran (column-major) order in memory. The default + is 'None', which provides no ordering guarantees. + Cannot be specified in conjunction with the `out` + argument. + + out : ndarray, 2-D, optional + If specified, uses this array (or `numpy.matrix`) as the + output buffer instead of allocating a new array to + return. The provided array must have the same shape and + dtype as the sparse matrix on which you are calling the + method. + + Returns + ------- + arr : numpy.matrix, 2-D + A NumPy matrix object with the same shape and containing + the same data represented by the sparse matrix, with the + requested memory order. If `out` was passed and was an + array (rather than a `numpy.matrix`), it will be filled + with the appropriate values and returned wrapped in a + `numpy.matrix` object that shares the same memory. + """ + return super().todense(order, out) + + @classmethod + def __class_getitem__(cls, arg, /): + """ + Return a parametrized wrapper around the `~scipy.sparse.spmatrix` type. + + .. versionadded:: 1.16.0 + + Returns + ------- + alias : types.GenericAlias + A parametrized `~scipy.sparse.spmatrix` type. + + Examples + -------- + >>> import numpy as np + >>> from scipy.sparse import coo_matrix + + >>> coo_matrix[np.int8] + scipy.sparse._coo.coo_matrix[numpy.int8] + """ + from types import GenericAlias + return GenericAlias(cls, arg) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/_matrix_io.py b/venv/lib/python3.12/site-packages/scipy/sparse/_matrix_io.py new file mode 100644 index 0000000000000000000000000000000000000000..424af914a4336bc9102c16059d4ed827fe4ac614 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/_matrix_io.py @@ -0,0 +1,172 @@ +import numpy as np +import scipy as sp + +__all__ = ['save_npz', 'load_npz'] + + +# Make loading safe vs. malicious input +PICKLE_KWARGS = dict(allow_pickle=False) + + +def save_npz(file, matrix, compressed=True): + """ Save a sparse matrix or array to a file using ``.npz`` format. + + Parameters + ---------- + file : str or file-like object + Either the file name (string) or an open file (file-like object) + where the data will be saved. If file is a string, the ``.npz`` + extension will be appended to the file name if it is not already + there. + matrix : spmatrix or sparray + The sparse matrix or array to save. + Supported formats: ``csc``, ``csr``, ``bsr``, ``dia`` or ``coo``. + compressed : bool, optional + Allow compressing the file. Default: True + + See Also + -------- + scipy.sparse.load_npz: Load a sparse matrix from a file using ``.npz`` format. + numpy.savez: Save several arrays into a ``.npz`` archive. + numpy.savez_compressed : Save several arrays into a compressed ``.npz`` archive. + + Examples + -------- + Store sparse matrix to disk, and load it again: + + >>> import numpy as np + >>> import scipy as sp + >>> sparse_array = sp.sparse.csc_array([[0, 0, 3], [4, 0, 0]]) + >>> sparse_array + + >>> sparse_array.toarray() + array([[0, 0, 3], + [4, 0, 0]], dtype=int64) + + >>> sp.sparse.save_npz('/tmp/sparse_array.npz', sparse_array) + >>> sparse_array = sp.sparse.load_npz('/tmp/sparse_array.npz') + + >>> sparse_array + + >>> sparse_array.toarray() + array([[0, 0, 3], + [4, 0, 0]], dtype=int64) + """ + arrays_dict = {} + if matrix.format in ('csc', 'csr', 'bsr'): + arrays_dict.update(indices=matrix.indices, indptr=matrix.indptr) + elif matrix.format == 'dia': + arrays_dict.update(offsets=matrix.offsets) + elif matrix.format == 'coo': + if matrix.ndim == 2: + # TODO: After a few releases, switch 2D case to save with coords only. + arrays_dict.update(row=matrix.row, col=matrix.col) + else: + arrays_dict.update(coords=matrix.coords) + else: + msg = f'Save is not implemented for sparse matrix of format {matrix.format}.' + raise NotImplementedError(msg) + arrays_dict.update( + format=matrix.format.encode('ascii'), + shape=matrix.shape, + data=matrix.data + ) + if isinstance(matrix, sp.sparse.sparray): + arrays_dict.update(_is_array=True) + if compressed: + np.savez_compressed(file, **arrays_dict) + else: + np.savez(file, **arrays_dict) + + +def load_npz(file): + """ Load a sparse array/matrix from a file using ``.npz`` format. + + Parameters + ---------- + file : str or file-like object + Either the file name (string) or an open file (file-like object) + where the data will be loaded. + + Returns + ------- + result : csc_array, csr_array, bsr_array, dia_array or coo_array + A sparse array/matrix containing the loaded data. + + Raises + ------ + OSError + If the input file does not exist or cannot be read. + + See Also + -------- + scipy.sparse.save_npz: Save a sparse array/matrix to a file using ``.npz`` format. + numpy.load: Load several arrays from a ``.npz`` archive. + + Examples + -------- + Store sparse array/matrix to disk, and load it again: + + >>> import numpy as np + >>> import scipy as sp + >>> sparse_array = sp.sparse.csc_array([[0, 0, 3], [4, 0, 0]]) + >>> sparse_array + + >>> sparse_array.toarray() + array([[0, 0, 3], + [4, 0, 0]], dtype=int64) + + >>> sp.sparse.save_npz('/tmp/sparse_array.npz', sparse_array) + >>> sparse_array = sp.sparse.load_npz('/tmp/sparse_array.npz') + + >>> sparse_array + + >>> sparse_array.toarray() + array([[0, 0, 3], + [4, 0, 0]], dtype=int64) + + In this example we force the result to be csr_array from csr_matrix + >>> sparse_matrix = sp.sparse.csc_matrix([[0, 0, 3], [4, 0, 0]]) + >>> sp.sparse.save_npz('/tmp/sparse_matrix.npz', sparse_matrix) + >>> tmp = sp.sparse.load_npz('/tmp/sparse_matrix.npz') + >>> sparse_array = sp.sparse.csr_array(tmp) + """ + with np.load(file, **PICKLE_KWARGS) as loaded: + sparse_format = loaded.get('format') + if sparse_format is None: + raise ValueError(f'The file {file} does not contain ' + f'a sparse array or matrix.') + sparse_format = sparse_format.item() + + if not isinstance(sparse_format, str): + # Play safe with Python 2 vs 3 backward compatibility; + # files saved with SciPy < 1.0.0 may contain unicode or bytes. + sparse_format = sparse_format.decode('ascii') + + if loaded.get('_is_array'): + sparse_type = sparse_format + '_array' + else: + sparse_type = sparse_format + '_matrix' + + try: + cls = getattr(sp.sparse, f'{sparse_type}') + except AttributeError as e: + raise ValueError(f'Unknown format "{sparse_type}"') from e + + if sparse_format in ('csc', 'csr', 'bsr'): + return cls((loaded['data'], loaded['indices'], loaded['indptr']), + shape=loaded['shape']) + elif sparse_format == 'dia': + return cls((loaded['data'], loaded['offsets']), shape=loaded['shape']) + elif sparse_format == 'coo': + if 'coords' in loaded: + return cls((loaded['data'], loaded['coords']), shape=loaded['shape']) + return cls((loaded['data'], (loaded['row'], loaded['col'])), + shape=loaded['shape']) + else: + raise NotImplementedError(f'Load is not implemented for ' + f'sparse matrix of format {sparse_format}.') diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/_sparsetools.cpython-312-x86_64-linux-gnu.so b/venv/lib/python3.12/site-packages/scipy/sparse/_sparsetools.cpython-312-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..131eeb6bfb98f738727235c1eb8a774dc5bcc100 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/_sparsetools.cpython-312-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cc0a6897bae0bc9e0b8be15e38a3d9d15dcb34e3acf1b22656171117af71fcc5 +size 4554512 diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/_spfuncs.py b/venv/lib/python3.12/site-packages/scipy/sparse/_spfuncs.py new file mode 100644 index 0000000000000000000000000000000000000000..8e9b0abcede6387e74538baf839a303c6cc1b6be --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/_spfuncs.py @@ -0,0 +1,76 @@ +""" Functions that operate on sparse matrices +""" + +__all__ = ['count_blocks','estimate_blocksize'] + +from ._base import issparse +from ._csr import csr_array +from ._sparsetools import csr_count_blocks + + +def estimate_blocksize(A,efficiency=0.7): + """Attempt to determine the blocksize of a sparse matrix + + Returns a blocksize=(r,c) such that + - A.nnz / A.tobsr( (r,c) ).nnz > efficiency + """ + if not (issparse(A) and A.format in ("csc", "csr")): + A = csr_array(A) + + if A.nnz == 0: + return (1,1) + + if not 0 < efficiency < 1.0: + raise ValueError('efficiency must satisfy 0.0 < efficiency < 1.0') + + high_efficiency = (1.0 + efficiency) / 2.0 + nnz = float(A.nnz) + M,N = A.shape + + if M % 2 == 0 and N % 2 == 0: + e22 = nnz / (4 * count_blocks(A,(2,2))) + else: + e22 = 0.0 + + if M % 3 == 0 and N % 3 == 0: + e33 = nnz / (9 * count_blocks(A,(3,3))) + else: + e33 = 0.0 + + if e22 > high_efficiency and e33 > high_efficiency: + e66 = nnz / (36 * count_blocks(A,(6,6))) + if e66 > efficiency: + return (6,6) + else: + return (3,3) + else: + if M % 4 == 0 and N % 4 == 0: + e44 = nnz / (16 * count_blocks(A,(4,4))) + else: + e44 = 0.0 + + if e44 > efficiency: + return (4,4) + elif e33 > efficiency: + return (3,3) + elif e22 > efficiency: + return (2,2) + else: + return (1,1) + + +def count_blocks(A,blocksize): + """For a given blocksize=(r,c) count the number of occupied + blocks in a sparse matrix A + """ + r,c = blocksize + if r < 1 or c < 1: + raise ValueError('r and c must be positive') + + if issparse(A): + if A.format == "csr": + M,N = A.shape + return csr_count_blocks(M,N,r,c,A.indptr,A.indices) + elif A.format == "csc": + return count_blocks(A.T,(c,r)) + return count_blocks(csr_array(A),blocksize) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/_sputils.py b/venv/lib/python3.12/site-packages/scipy/sparse/_sputils.py new file mode 100644 index 0000000000000000000000000000000000000000..6930aee29c201e1b3636bec3629c0a9e00f1ab94 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/_sputils.py @@ -0,0 +1,614 @@ +""" Utility functions for sparse matrix module +""" + +import sys +from typing import Any, Literal +import operator +import numpy as np +from math import prod +import scipy.sparse as sp + + +__all__ = ['upcast', 'getdtype', 'getdata', 'isscalarlike', 'isintlike', + 'isshape', 'issequence', 'isdense', 'ismatrix', 'get_sum_dtype', + 'broadcast_shapes'] + +supported_dtypes = [np.bool_, np.byte, np.ubyte, np.short, np.ushort, np.intc, + np.uintc, np.long, np.ulong, np.longlong, np.ulonglong, + np.float32, np.float64, np.longdouble, + np.complex64, np.complex128, np.clongdouble] + +_upcast_memo = {} + + +def upcast(*args): + """Returns the nearest supported sparse dtype for the + combination of one or more types. + + upcast(t0, t1, ..., tn) -> T where T is a supported dtype + + Examples + -------- + >>> from scipy.sparse._sputils import upcast + >>> upcast('int32') + + >>> upcast('bool') + + >>> upcast('int32','float32') + + >>> upcast('bool',complex,float) + + + """ + + t = _upcast_memo.get(hash(args)) + if t is not None: + return t + + upcast = np.result_type(*args) + + for t in supported_dtypes: + if np.can_cast(upcast, t): + _upcast_memo[hash(args)] = t + return t + + raise TypeError(f'no supported conversion for types: {args!r}') + + +def upcast_char(*args): + """Same as `upcast` but taking dtype.char as input (faster).""" + t = _upcast_memo.get(args) + if t is not None: + return t + t = upcast(*map(np.dtype, args)) + _upcast_memo[args] = t + return t + + +def upcast_scalar(dtype, scalar): + """Determine data type for binary operation between an array of + type `dtype` and a scalar. + """ + return (np.array([0], dtype=dtype) * scalar).dtype + + +def downcast_intp_index(arr): + """ + Down-cast index array to np.intp dtype if it is of a larger dtype. + + Raise an error if the array contains a value that is too large for + intp. + """ + if arr.dtype.itemsize > np.dtype(np.intp).itemsize: + if arr.size == 0: + return arr.astype(np.intp) + maxval = arr.max() + minval = arr.min() + if maxval > np.iinfo(np.intp).max or minval < np.iinfo(np.intp).min: + raise ValueError("Cannot deal with arrays with indices larger " + "than the machine maximum address size " + "(e.g. 64-bit indices on 32-bit machine).") + return arr.astype(np.intp) + return arr + + +def to_native(A): + """ + Ensure that the data type of the NumPy array `A` has native byte order. + + `A` must be a NumPy array. If the data type of `A` does not have native + byte order, a copy of `A` with a native byte order is returned. Otherwise + `A` is returned. + """ + dt = A.dtype + if dt.isnative: + # Don't call `asarray()` if A is already native, to avoid unnecessarily + # creating a view of the input array. + return A + return np.asarray(A, dtype=dt.newbyteorder('native')) + + +def getdtype(dtype, a=None, default=None): + """Form a supported numpy dtype based on input arguments. + + Returns a valid ``numpy.dtype`` from `dtype` if not None, + or else ``a.dtype`` if possible, or else the given `default` + if not None, or else raise a ``TypeError``. + + The resulting ``dtype`` must be in ``supported_dtypes``: + bool_, int8, uint8, int16, uint16, int32, uint32, + int64, uint64, longlong, ulonglong, float32, float64, + longdouble, complex64, complex128, clongdouble + """ + if dtype is None: + try: + newdtype = a.dtype + except AttributeError as e: + if default is not None: + newdtype = np.dtype(default) + else: + raise TypeError("could not interpret data type") from e + else: + newdtype = np.dtype(dtype) + + if newdtype not in supported_dtypes: + supported_dtypes_fmt = ", ".join(t.__name__ for t in supported_dtypes) + raise ValueError(f"scipy.sparse does not support dtype {newdtype}. " + f"The only supported types are: {supported_dtypes_fmt}.") + return newdtype + + +def getdata(obj, dtype=None, copy=False) -> np.ndarray: + """ + This is a wrapper of `np.array(obj, dtype=dtype, copy=copy)` + that will generate a warning if the result is an object array. + """ + data = np.array(obj, dtype=dtype, copy=copy) + # Defer to getdtype for checking that the dtype is OK. + # This is called for the validation only; we don't need the return value. + getdtype(data.dtype) + return data + + +def safely_cast_index_arrays(A, idx_dtype=np.int32, msg=""): + """Safely cast sparse array indices to `idx_dtype`. + + Check the shape of `A` to determine if it is safe to cast its index + arrays to dtype `idx_dtype`. If any dimension in shape is larger than + fits in the dtype, casting is unsafe so raise ``ValueError``. + If safe, cast the index arrays to `idx_dtype` and return the result + without changing the input `A`. The caller can assign results to `A` + attributes if desired or use the recast index arrays directly. + + Unless downcasting is needed, the original index arrays are returned. + You can test e.g. ``A.indptr is new_indptr`` to see if downcasting occurred. + + .. versionadded:: 1.15.0 + + Parameters + ---------- + A : sparse array or matrix + The array for which index arrays should be downcast. + idx_dtype : dtype + Desired dtype. Should be an integer dtype (default: ``np.int32``). + Most of scipy.sparse uses either int64 or int32. + msg : str, optional + A string to be added to the end of the ValueError message + if the array shape is too big to fit in `idx_dtype`. + The error message is ``f" values too large for {msg}"`` + It should indicate why the downcasting is needed, e.g. "SuperLU", + and defaults to f"dtype {idx_dtype}". + + Returns + ------- + idx_arrays : ndarray or tuple of ndarrays + Based on ``A.format``, index arrays are returned after casting to `idx_dtype`. + For CSC/CSR, returns ``(indices, indptr)``. + For COO, returns ``coords``. + For DIA, returns ``offsets``. + For BSR, returns ``(indices, indptr)``. + + Raises + ------ + ValueError + If the array has shape that would not fit in the new dtype, or if + the sparse format does not use index arrays. + + Examples + -------- + >>> import numpy as np + >>> from scipy import sparse + >>> data = [3] + >>> coords = (np.array([3]), np.array([1])) # Note: int64 arrays + >>> A = sparse.coo_array((data, coords)) + >>> A.coords[0].dtype + dtype('int64') + + >>> # rescast after construction, raising exception if shape too big + >>> coords = sparse.safely_cast_index_arrays(A, np.int32) + >>> A.coords[0] is coords[0] # False if casting is needed + False + >>> A.coords = coords # set the index dtype of A + >>> A.coords[0].dtype + dtype('int32') + """ + if not msg: + msg = f"dtype {idx_dtype}" + # check for safe downcasting + max_value = np.iinfo(idx_dtype).max + + if A.format in ("csc", "csr"): + # indptr[-1] is max b/c indptr always sorted + if A.indptr[-1] > max_value: + raise ValueError(f"indptr values too large for {msg}") + + # check shape vs dtype + if max(*A.shape) > max_value: + if (A.indices > max_value).any(): + raise ValueError(f"indices values too large for {msg}") + + indices = A.indices.astype(idx_dtype, copy=False) + indptr = A.indptr.astype(idx_dtype, copy=False) + return indices, indptr + + elif A.format == "coo": + if max(*A.shape) > max_value: + if any((co > max_value).any() for co in A.coords): + raise ValueError(f"coords values too large for {msg}") + return tuple(co.astype(idx_dtype, copy=False) for co in A.coords) + + elif A.format == "dia": + if max(*A.shape) > max_value: + if (A.offsets > max_value).any(): + raise ValueError(f"offsets values too large for {msg}") + offsets = A.offsets.astype(idx_dtype, copy=False) + return offsets + + elif A.format == 'bsr': + R, C = A.blocksize + if A.indptr[-1] * R > max_value: + raise ValueError("indptr values too large for {msg}") + if max(*A.shape) > max_value: + if (A.indices * C > max_value).any(): + raise ValueError(f"indices values too large for {msg}") + indices = A.indices.astype(idx_dtype, copy=False) + indptr = A.indptr.astype(idx_dtype, copy=False) + return indices, indptr + + else: + raise TypeError(f'Format {A.format} is not associated with index arrays. ' + 'DOK and LIL have dict and list, not array.') + + +def get_index_dtype(arrays=(), maxval=None, check_contents=False): + """ + Based on input (integer) arrays `a`, determine a suitable index data + type that can hold the data in the arrays. + + Parameters + ---------- + arrays : tuple of array_like + Input arrays whose types/contents to check + maxval : float, optional + Maximum value needed + check_contents : bool, optional + Whether to check the values in the arrays and not just their types. + Default: False (check only the types) + + Returns + ------- + dtype : dtype + Suitable index data type (int32 or int64) + + Examples + -------- + >>> import numpy as np + >>> from scipy import sparse + >>> # select index dtype based on shape + >>> shape = (3, 3) + >>> idx_dtype = sparse.get_index_dtype(maxval=max(shape)) + >>> data = [1.1, 3.0, 1.5] + >>> indices = np.array([0, 1, 0], dtype=idx_dtype) + >>> indptr = np.array([0, 2, 3, 3], dtype=idx_dtype) + >>> A = sparse.csr_array((data, indices, indptr), shape=shape) + >>> A.indptr.dtype + dtype('int32') + + >>> # select based on larger of existing arrays and shape + >>> shape = (3, 3) + >>> idx_dtype = sparse.get_index_dtype(A.indptr, maxval=max(shape)) + >>> idx_dtype + + """ + # not using intc directly due to misinteractions with pythran + if np.intc().itemsize != 4: + return np.int64 + + int32min = np.int32(np.iinfo(np.int32).min) + int32max = np.int32(np.iinfo(np.int32).max) + + if maxval is not None: + maxval = np.int64(maxval) + if maxval > int32max: + return np.int64 + + if isinstance(arrays, np.ndarray): + arrays = (arrays,) + + for arr in arrays: + arr = np.asarray(arr) + if not np.can_cast(arr.dtype, np.int32): + if check_contents: + if arr.size == 0: + # a bigger type not needed + continue + elif np.issubdtype(arr.dtype, np.integer): + maxval = arr.max() + minval = arr.min() + if minval >= int32min and maxval <= int32max: + # a bigger type not needed + continue + return np.int64 + return np.int32 + + +def get_sum_dtype(dtype: np.dtype) -> np.dtype | type[np.generic]: + """Mimic numpy's casting for np.sum""" + if dtype.kind == 'u' and np.can_cast(dtype, np.uint): + return np.uint + if np.can_cast(dtype, np.int_): + return np.int_ + return dtype + + +def isscalarlike(x) -> bool: + """Is x either a scalar, an array scalar, or a 0-dim array?""" + return np.isscalar(x) or (isdense(x) and x.ndim == 0) + + +def isintlike(x) -> bool: + """Is x appropriate as an index into a sparse matrix? Returns True + if it can be cast safely to a machine int. + """ + # Fast-path check to eliminate non-scalar values. operator.index would + # catch this case too, but the exception catching is slow. + if np.ndim(x) != 0: + return False + try: + operator.index(x) + except (TypeError, ValueError): + try: + loose_int = bool(int(x) == x) + except (TypeError, ValueError): + return False + if loose_int: + msg = "Inexact indices into sparse matrices are not allowed" + raise ValueError(msg) + return loose_int + return True + + +def isshape(x, nonneg=False, *, allow_nd=(2,), check_nd=True) -> bool: + """Is x a valid tuple of dimensions? + + If nonneg, also checks that the dimensions are non-negative. + Shapes of length in the tuple allow_nd are allowed. + """ + ndim = len(x) + if check_nd and ndim not in allow_nd: + return False + + for d in x: + if not isintlike(d): + return False + if nonneg and d < 0: + return False + return True + + +def issequence(t) -> bool: + return ((isinstance(t, list | tuple) and + (len(t) == 0 or np.isscalar(t[0]))) or + (isinstance(t, np.ndarray) and (t.ndim == 1))) + + +def ismatrix(t) -> bool: + return ((isinstance(t, list | tuple) and + len(t) > 0 and issequence(t[0])) or + (isinstance(t, np.ndarray) and t.ndim == 2)) + + +def isdense(x) -> bool: + return isinstance(x, np.ndarray) + + +def validateaxis(axis, *, ndim=2) -> tuple[int, ...] | None: + if axis is None: + return None + + if axis == (): + raise ValueError( + "sparse does not accept 0D axis (). Either use toarray (for dense) " + "or copy (for sparse)." + ) + + if not isinstance(axis, tuple): + # If not a tuple, check that the provided axis is actually + # an integer and raise a TypeError similar to NumPy's + if not np.issubdtype(np.dtype(type(axis)), np.integer): + raise TypeError(f'axis must be an integer/tuple of ints, not {type(axis)}') + axis = (axis,) + + canon_axis = [] + for ax in axis: + if not isintlike(ax): + raise TypeError(f"axis must be an integer. (given {ax})") + if ax < 0: + ax += ndim + if ax < 0 or ax >= ndim: + raise ValueError("axis out of range for ndim") + canon_axis.append(ax) + + len_axis = len(canon_axis) + if len_axis != len(set(canon_axis)): + raise ValueError("duplicate value in axis") + elif len_axis > ndim: + raise ValueError("axis tuple has too many elements") + elif len_axis == ndim: + return None + else: + return tuple(canon_axis) + + +def check_shape(args, current_shape=None, *, allow_nd=(2,)) -> tuple[int, ...]: + """Imitate numpy.matrix handling of shape arguments + + Parameters + ---------- + args : array_like + Data structures providing information about the shape of the sparse array. + current_shape : tuple, optional + The current shape of the sparse array or matrix. + If None (default), the current shape will be inferred from args. + allow_nd : tuple of ints, optional default: (2,) + If shape does not have a length in the tuple allow_nd an error is raised. + + Returns + ------- + new_shape: tuple + The new shape after validation. + """ + if len(args) == 0: + raise TypeError("function missing 1 required positional argument: 'shape'") + if len(args) == 1: + try: + shape_iter = iter(args[0]) + except TypeError: + new_shape = (operator.index(args[0]), ) + else: + new_shape = tuple(operator.index(arg) for arg in shape_iter) + else: + new_shape = tuple(operator.index(arg) for arg in args) + + if current_shape is None: + if len(new_shape) not in allow_nd: + raise ValueError(f'shape must have length in {allow_nd}. Got {new_shape=}') + if any(d < 0 for d in new_shape): + raise ValueError("'shape' elements cannot be negative") + else: + # Check the current size only if needed + current_size = prod(current_shape) + + # Check for negatives + negative_indexes = [i for i, x in enumerate(new_shape) if x < 0] + if not negative_indexes: + new_size = prod(new_shape) + if new_size != current_size: + raise ValueError(f'cannot reshape array of size {current_size}' + f' into shape {new_shape}') + elif len(negative_indexes) == 1: + skip = negative_indexes[0] + specified = prod(new_shape[:skip] + new_shape[skip+1:]) + unspecified, remainder = divmod(current_size, specified) + if remainder != 0: + err_shape = tuple('newshape' if x < 0 else x for x in new_shape) + raise ValueError(f'cannot reshape array of size {current_size}' + f' into shape {err_shape}') + new_shape = new_shape[:skip] + (unspecified,) + new_shape[skip+1:] + else: + raise ValueError('can only specify one unknown dimension') + + if len(new_shape) not in allow_nd: + raise ValueError(f'shape must have length in {allow_nd}. Got {new_shape=}') + + return new_shape + + +def broadcast_shapes(*shapes): + """Check if shapes can be broadcast and return resulting shape + + This is similar to the NumPy ``broadcast_shapes`` function but + does not check memory consequences of the resulting dense matrix. + + Parameters + ---------- + *shapes : tuple of shape tuples + The tuple of shapes to be considered for broadcasting. + Shapes should be tuples of non-negative integers. + + Returns + ------- + new_shape : tuple of integers + The shape that results from broadcasting th input shapes. + """ + if not shapes: + return () + shapes = [shp if isinstance(shp, tuple | list) else (shp,) for shp in shapes] + big_shp = max(shapes, key=len) + out = list(big_shp) + for shp in shapes: + if shp is big_shp: + continue + for i, x in enumerate(shp, start=-len(shp)): + if x != 1 and x != out[i]: + if out[i] != 1: + raise ValueError("shapes cannot be broadcast to a single shape.") + out[i] = x + return (*out,) + + +def is_pydata_spmatrix(m) -> bool: + """ + Check whether object is pydata/sparse matrix, avoiding importing the module. + """ + base_cls = getattr(sys.modules.get('sparse'), 'SparseArray', None) + return base_cls is not None and isinstance(m, base_cls) + + +def convert_pydata_sparse_to_scipy( + arg: Any, + target_format: None | Literal["csc", "csr"] = None, + accept_fv: Any = None, +) -> "sp.spmatrix | Any": + """ + Convert a pydata/sparse array to scipy sparse matrix, + pass through anything else. + """ + if is_pydata_spmatrix(arg): + # The `accept_fv` keyword is new in PyData Sparse 0.15.4 (May 2024), + # remove the `except` once the minimum supported version is >=0.15.4 + try: + arg = arg.to_scipy_sparse(accept_fv=accept_fv) + except TypeError: + arg = arg.to_scipy_sparse() + if target_format is not None: + arg = arg.asformat(target_format) + elif arg.format not in ("csc", "csr"): + arg = arg.tocsc() + return arg + + +############################################################################### +# Wrappers for NumPy types that are deprecated + +# Numpy versions of these functions raise deprecation warnings, the +# ones below do not. + +def matrix(*args, **kwargs): + return np.array(*args, **kwargs).view(np.matrix) + + +def asmatrix(data, dtype=None): + if isinstance(data, np.matrix) and (dtype is None or data.dtype == dtype): + return data + return np.asarray(data, dtype=dtype).view(np.matrix) + +############################################################################### + + +def _todata(s) -> np.ndarray: + """Access nonzero values, possibly after summing duplicates. + + Parameters + ---------- + s : sparse array + Input sparse array. + + Returns + ------- + data: ndarray + Nonzero values of the array, with shape (s.nnz,) + + """ + if isinstance(s, sp._data._data_matrix): + return s._deduped_data() + + if isinstance(s, sp.dok_array): + return np.fromiter(s.values(), dtype=s.dtype, count=s.nnz) + + if isinstance(s, sp.lil_array): + data = np.empty(s.nnz, dtype=s.dtype) + sp._csparsetools.lil_flatten_to_array(s.data, data) + return data + + return s.tocoo()._deduped_data() diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/base.py b/venv/lib/python3.12/site-packages/scipy/sparse/base.py new file mode 100644 index 0000000000000000000000000000000000000000..0550455cd291073208f4e55bb684b1db655faec0 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/base.py @@ -0,0 +1,24 @@ +# This file is not meant for public use and will be removed in SciPy v2.0.0. +# Use the `scipy.sparse` namespace for importing the functions +# included below. + +from scipy._lib.deprecation import _sub_module_deprecation + + +__all__ = [ # noqa: F822 + 'SparseEfficiencyWarning', + 'SparseWarning', + 'issparse', + 'isspmatrix', + 'spmatrix', +] + + +def __dir__(): + return __all__ + + +def __getattr__(name): + return _sub_module_deprecation(sub_package="sparse", module="base", + private_modules=["_base"], all=__all__, + attribute=name) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/bsr.py b/venv/lib/python3.12/site-packages/scipy/sparse/bsr.py new file mode 100644 index 0000000000000000000000000000000000000000..4cac6f7a3b94e3b0ef7d1fb3c8f1b48caa2dd28e --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/bsr.py @@ -0,0 +1,22 @@ +# This file is not meant for public use and will be removed in SciPy v2.0.0. +# Use the `scipy.sparse` namespace for importing the functions +# included below. + +from scipy._lib.deprecation import _sub_module_deprecation + + +__all__ = [ # noqa: F822 + 'bsr_matrix', + 'isspmatrix_bsr', + 'spmatrix', +] + + +def __dir__(): + return __all__ + + +def __getattr__(name): + return _sub_module_deprecation(sub_package="sparse", module="bsr", + private_modules=["_bsr"], all=__all__, + attribute=name) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/compressed.py b/venv/lib/python3.12/site-packages/scipy/sparse/compressed.py new file mode 100644 index 0000000000000000000000000000000000000000..dd240c40188acfbc8505c6d21c18eb689498061a --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/compressed.py @@ -0,0 +1,20 @@ +# This file is not meant for public use and will be removed in SciPy v2.0.0. +# Use the `scipy.sparse` namespace for importing the functions +# included below. + +from scipy._lib.deprecation import _sub_module_deprecation + + +__all__ = [ # noqa: F822 + 'SparseEfficiencyWarning', +] + + +def __dir__(): + return __all__ + + +def __getattr__(name): + return _sub_module_deprecation(sub_package="sparse", module="compressed", + private_modules=["_compressed"], all=__all__, + attribute=name) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/construct.py b/venv/lib/python3.12/site-packages/scipy/sparse/construct.py new file mode 100644 index 0000000000000000000000000000000000000000..3f70e57b2677cc8107854222034184f57aa64c5a --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/construct.py @@ -0,0 +1,38 @@ +# This file is not meant for public use and will be removed in SciPy v2.0.0. +# Use the `scipy.sparse` namespace for importing the functions +# included below. + +from scipy._lib.deprecation import _sub_module_deprecation + + +__all__ = [ # noqa: F822 + 'block_diag', + 'bmat', + 'bsr_matrix', + 'coo_matrix', + 'csc_matrix', + 'csr_matrix', + 'dia_matrix', + 'diags', + 'eye', + 'get_index_dtype', + 'hstack', + 'identity', + 'issparse', + 'kron', + 'kronsum', + 'rand', + 'random', + 'spdiags', + 'vstack', +] + + +def __dir__(): + return __all__ + + +def __getattr__(name): + return _sub_module_deprecation(sub_package="sparse", module="construct", + private_modules=["_construct"], all=__all__, + attribute=name) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/coo.py b/venv/lib/python3.12/site-packages/scipy/sparse/coo.py new file mode 100644 index 0000000000000000000000000000000000000000..ccdcf78879fe8957ea339228131ed95e8fb88d8f --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/coo.py @@ -0,0 +1,23 @@ +# This file is not meant for public use and will be removed in SciPy v2.0.0. +# Use the `scipy.sparse` namespace for importing the functions +# included below. + +from scipy._lib.deprecation import _sub_module_deprecation + + +__all__ = [ # noqa: F822 + 'SparseEfficiencyWarning', + 'coo_matrix', + 'isspmatrix_coo', + 'spmatrix', +] + + +def __dir__(): + return __all__ + + +def __getattr__(name): + return _sub_module_deprecation(sub_package="sparse", module="coo", + private_modules=["_coo"], all=__all__, + attribute=name) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/csc.py b/venv/lib/python3.12/site-packages/scipy/sparse/csc.py new file mode 100644 index 0000000000000000000000000000000000000000..b3815acdc1b66f18ce45261175bf61896cbdd996 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/csc.py @@ -0,0 +1,22 @@ +# This file is not meant for public use and will be removed in SciPy v2.0.0. +# Use the `scipy.sparse` namespace for importing the functions +# included below. + +from scipy._lib.deprecation import _sub_module_deprecation + + +__all__ = [ # noqa: F822 + 'csc_matrix', + 'isspmatrix_csc', + 'spmatrix', +] + + +def __dir__(): + return __all__ + + +def __getattr__(name): + return _sub_module_deprecation(sub_package="sparse", module="csc", + private_modules=["_csc"], all=__all__, + attribute=name) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/__init__.py b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..00ab19af4748147d748fccb51a3710d5c711f4b4 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/__init__.py @@ -0,0 +1,210 @@ +r""" +Compressed sparse graph routines (:mod:`scipy.sparse.csgraph`) +============================================================== + +.. currentmodule:: scipy.sparse.csgraph + +Fast graph algorithms based on sparse matrix representations. + +Contents +-------- + +.. autosummary:: + :toctree: generated/ + + connected_components -- determine connected components of a graph + laplacian -- compute the laplacian of a graph + shortest_path -- compute the shortest path between points on a positive graph + dijkstra -- use Dijkstra's algorithm for shortest path + floyd_warshall -- use the Floyd-Warshall algorithm for shortest path + bellman_ford -- use the Bellman-Ford algorithm for shortest path + johnson -- use Johnson's algorithm for shortest path + yen -- use Yen's algorithm for K-shortest paths between to nodes. + breadth_first_order -- compute a breadth-first order of nodes + depth_first_order -- compute a depth-first order of nodes + breadth_first_tree -- construct the breadth-first tree from a given node + depth_first_tree -- construct a depth-first tree from a given node + minimum_spanning_tree -- construct the minimum spanning tree of a graph + reverse_cuthill_mckee -- compute permutation for reverse Cuthill-McKee ordering + maximum_flow -- solve the maximum flow problem for a graph + maximum_bipartite_matching -- compute a maximum matching of a bipartite graph + min_weight_full_bipartite_matching - compute a minimum weight full matching of a bipartite graph + structural_rank -- compute the structural rank of a graph + NegativeCycleError + +.. autosummary:: + :toctree: generated/ + + construct_dist_matrix + csgraph_from_dense + csgraph_from_masked + csgraph_masked_from_dense + csgraph_to_dense + csgraph_to_masked + reconstruct_path + +Graph Representations +--------------------- +This module uses graphs which are stored in a matrix format. A +graph with N nodes can be represented by an (N x N) adjacency matrix G. +If there is a connection from node i to node j, then G[i, j] = w, where +w is the weight of the connection. For nodes i and j which are +not connected, the value depends on the representation: + +- for dense array representations, non-edges are represented by + G[i, j] = 0, infinity, or NaN. + +- for dense masked representations (of type np.ma.MaskedArray), non-edges + are represented by masked values. This can be useful when graphs with + zero-weight edges are desired. + +- for sparse array representations, non-edges are represented by + non-entries in the matrix. This sort of sparse representation also + allows for edges with zero weights. + +As a concrete example, imagine that you would like to represent the following +undirected graph:: + + G + + (0) + / \ + 1 2 + / \ + (2) (1) + +This graph has three nodes, where node 0 and 1 are connected by an edge of +weight 2, and nodes 0 and 2 are connected by an edge of weight 1. +We can construct the dense, masked, and sparse representations as follows, +keeping in mind that an undirected graph is represented by a symmetric matrix:: + + >>> import numpy as np + >>> G_dense = np.array([[0, 2, 1], + ... [2, 0, 0], + ... [1, 0, 0]]) + >>> G_masked = np.ma.masked_values(G_dense, 0) + >>> from scipy.sparse import csr_array + >>> G_sparse = csr_array(G_dense) + +This becomes more difficult when zero edges are significant. For example, +consider the situation when we slightly modify the above graph:: + + G2 + + (0) + / \ + 0 2 + / \ + (2) (1) + +This is identical to the previous graph, except nodes 0 and 2 are connected +by an edge of zero weight. In this case, the dense representation above +leads to ambiguities: how can non-edges be represented if zero is a meaningful +value? In this case, either a masked or sparse representation must be used +to eliminate the ambiguity:: + + >>> import numpy as np + >>> G2_data = np.array([[np.inf, 2, 0 ], + ... [2, np.inf, np.inf], + ... [0, np.inf, np.inf]]) + >>> G2_masked = np.ma.masked_invalid(G2_data) + >>> from scipy.sparse.csgraph import csgraph_from_dense + >>> # G2_sparse = csr_array(G2_data) would give the wrong result + >>> G2_sparse = csgraph_from_dense(G2_data, null_value=np.inf) + >>> G2_sparse.data + array([ 2., 0., 2., 0.]) + +Here we have used a utility routine from the csgraph submodule in order to +convert the dense representation to a sparse representation which can be +understood by the algorithms in submodule. By viewing the data array, we +can see that the zero values are explicitly encoded in the graph. + +Directed vs. undirected +^^^^^^^^^^^^^^^^^^^^^^^ +Matrices may represent either directed or undirected graphs. This is +specified throughout the csgraph module by a boolean keyword. Graphs are +assumed to be directed by default. In a directed graph, traversal from node +i to node j can be accomplished over the edge G[i, j], but not the edge +G[j, i]. Consider the following dense graph:: + + >>> import numpy as np + >>> G_dense = np.array([[0, 1, 0], + ... [2, 0, 3], + ... [0, 4, 0]]) + +When ``directed=True`` we get the graph:: + + ---1--> ---3--> + (0) (1) (2) + <--2--- <--4--- + +In a non-directed graph, traversal from node i to node j can be +accomplished over either G[i, j] or G[j, i]. If both edges are not null, +and the two have unequal weights, then the smaller of the two is used. + +So for the same graph, when ``directed=False`` we get the graph:: + + (0)--1--(1)--3--(2) + +Note that a symmetric matrix will represent an undirected graph, regardless +of whether the 'directed' keyword is set to True or False. In this case, +using ``directed=True`` generally leads to more efficient computation. + +The routines in this module accept as input either scipy.sparse representations +(csr, csc, or lil format), masked representations, or dense representations +with non-edges indicated by zeros, infinities, and NaN entries. +""" # noqa: E501 + +__docformat__ = "restructuredtext en" + +__all__ = ['connected_components', + 'laplacian', + 'shortest_path', + 'floyd_warshall', + 'dijkstra', + 'bellman_ford', + 'johnson', + 'yen', + 'breadth_first_order', + 'depth_first_order', + 'breadth_first_tree', + 'depth_first_tree', + 'minimum_spanning_tree', + 'reverse_cuthill_mckee', + 'maximum_flow', + 'maximum_bipartite_matching', + 'min_weight_full_bipartite_matching', + 'structural_rank', + 'construct_dist_matrix', + 'reconstruct_path', + 'csgraph_masked_from_dense', + 'csgraph_from_dense', + 'csgraph_from_masked', + 'csgraph_to_dense', + 'csgraph_to_masked', + 'NegativeCycleError'] + +from ._laplacian import laplacian +from ._shortest_path import ( + shortest_path, floyd_warshall, dijkstra, bellman_ford, johnson, yen, + NegativeCycleError +) +from ._traversal import ( + breadth_first_order, depth_first_order, breadth_first_tree, + depth_first_tree, connected_components +) +from ._min_spanning_tree import minimum_spanning_tree +from ._flow import maximum_flow +from ._matching import ( + maximum_bipartite_matching, min_weight_full_bipartite_matching +) +from ._reordering import reverse_cuthill_mckee, structural_rank +from ._tools import ( + construct_dist_matrix, reconstruct_path, csgraph_from_dense, + csgraph_to_dense, csgraph_masked_from_dense, csgraph_from_masked, + csgraph_to_masked +) + +from scipy._lib._testutils import PytestTester +test = PytestTester(__name__) +del PytestTester diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ef5634752b42185e1704bcf665a727bc24736667 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/__pycache__/__init__.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/__pycache__/_laplacian.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/__pycache__/_laplacian.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..994f39cb58ce76828b65e6eb2e340eb882b282cc Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/__pycache__/_laplacian.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/__pycache__/_validation.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/__pycache__/_validation.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4c29a4190ca004ae9f415553e35e492ed87e4ce7 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/__pycache__/_validation.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/_flow.cpython-312-x86_64-linux-gnu.so b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/_flow.cpython-312-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..e8a28472de5b6f2e41bdb4609ed0fc6206452eae --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/_flow.cpython-312-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e916fb4555f097547989807d6e4e23978d195ba9f3ff8a60b6117bc5d9032e02 +size 184304 diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/_laplacian.py b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/_laplacian.py new file mode 100644 index 0000000000000000000000000000000000000000..762158e0d86ff92293a9f181b1ad444bd02706c9 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/_laplacian.py @@ -0,0 +1,563 @@ +""" +Laplacian of a compressed-sparse graph +""" + +import numpy as np +from scipy.sparse import issparse +from scipy.sparse.linalg import LinearOperator +from scipy.sparse._sputils import convert_pydata_sparse_to_scipy, is_pydata_spmatrix + + +############################################################################### +# Graph laplacian +def laplacian( + csgraph, + normed=False, + return_diag=False, + use_out_degree=False, + *, + copy=True, + form="array", + dtype=None, + symmetrized=False, +): + """ + Return the Laplacian of a directed graph. + + Parameters + ---------- + csgraph : array_like or sparse array or matrix, 2 dimensions + compressed-sparse graph, with shape (N, N). + normed : bool, optional + If True, then compute symmetrically normalized Laplacian. + Default: False. + return_diag : bool, optional + If True, then also return an array related to vertex degrees. + Default: False. + use_out_degree : bool, optional + If True, then use out-degree instead of in-degree. + This distinction matters only if the graph is asymmetric. + Default: False. + copy : bool, optional + If False, then change `csgraph` in place if possible, + avoiding doubling the memory use. + Default: True, for backward compatibility. + form : 'array', or 'function', or 'lo' + Determines the format of the output Laplacian: + + * 'array' is a numpy array; + * 'function' is a pointer to evaluating the Laplacian-vector + or Laplacian-matrix product; + * 'lo' results in the format of the `LinearOperator`. + + Choosing 'function' or 'lo' always avoids doubling + the memory use, ignoring `copy` value. + Default: 'array', for backward compatibility. + dtype : None or one of numeric numpy dtypes, optional + The dtype of the output. If ``dtype=None``, the dtype of the + output matches the dtype of the input csgraph, except for + the case ``normed=True`` and integer-like csgraph, where + the output dtype is 'float' allowing accurate normalization, + but dramatically increasing the memory use. + Default: None, for backward compatibility. + symmetrized : bool, optional + If True, then the output Laplacian is symmetric/Hermitian. + The symmetrization is done by ``csgraph + csgraph.T.conj`` + without dividing by 2 to preserve integer dtypes if possible + prior to the construction of the Laplacian. + The symmetrization will increase the memory footprint of + sparse matrices unless the sparsity pattern is symmetric or + `form` is 'function' or 'lo'. + Default: False, for backward compatibility. + + Returns + ------- + lap : ndarray, or sparse array or matrix, or `LinearOperator` + The N x N Laplacian of csgraph. It will be a NumPy array (dense) + if the input was dense, or a sparse array otherwise, or + the format of a function or `LinearOperator` if + `form` equals 'function' or 'lo', respectively. + diag : ndarray, optional + The length-N main diagonal of the Laplacian matrix. + For the normalized Laplacian, this is the array of square roots + of vertex degrees or 1 if the degree is zero. + + Notes + ----- + The Laplacian matrix of a graph is sometimes referred to as the + "Kirchhoff matrix" or just the "Laplacian", and is useful in many + parts of spectral graph theory. + In particular, the eigen-decomposition of the Laplacian can give + insight into many properties of the graph, e.g., + is commonly used for spectral data embedding and clustering. + + The constructed Laplacian doubles the memory use if ``copy=True`` and + ``form="array"`` which is the default. + Choosing ``copy=False`` has no effect unless ``form="array"`` + or the matrix is sparse in the ``coo`` format, or dense array, except + for the integer input with ``normed=True`` that forces the float output. + + Sparse input is reformatted into ``coo`` if ``form="array"``, + which is the default. + + If the input adjacency matrix is not symmetric, the Laplacian is + also non-symmetric unless ``symmetrized=True`` is used. + + Diagonal entries of the input adjacency matrix are ignored and + replaced with zeros for the purpose of normalization where ``normed=True``. + The normalization uses the inverse square roots of row-sums of the input + adjacency matrix, and thus may fail if the row-sums contain + negative or complex with a non-zero imaginary part values. + + The normalization is symmetric, making the normalized Laplacian also + symmetric if the input csgraph was symmetric. + + References + ---------- + .. [1] Laplacian matrix. https://en.wikipedia.org/wiki/Laplacian_matrix + + Examples + -------- + >>> import numpy as np + >>> from scipy.sparse import csgraph + + Our first illustration is the symmetric graph + + >>> G = np.arange(4) * np.arange(4)[:, np.newaxis] + >>> G + array([[0, 0, 0, 0], + [0, 1, 2, 3], + [0, 2, 4, 6], + [0, 3, 6, 9]]) + + and its symmetric Laplacian matrix + + >>> csgraph.laplacian(G) + array([[ 0, 0, 0, 0], + [ 0, 5, -2, -3], + [ 0, -2, 8, -6], + [ 0, -3, -6, 9]]) + + The non-symmetric graph + + >>> G = np.arange(9).reshape(3, 3) + >>> G + array([[0, 1, 2], + [3, 4, 5], + [6, 7, 8]]) + + has different row- and column sums, resulting in two varieties + of the Laplacian matrix, using an in-degree, which is the default + + >>> L_in_degree = csgraph.laplacian(G) + >>> L_in_degree + array([[ 9, -1, -2], + [-3, 8, -5], + [-6, -7, 7]]) + + or alternatively an out-degree + + >>> L_out_degree = csgraph.laplacian(G, use_out_degree=True) + >>> L_out_degree + array([[ 3, -1, -2], + [-3, 8, -5], + [-6, -7, 13]]) + + Constructing a symmetric Laplacian matrix, one can add the two as + + >>> L_in_degree + L_out_degree.T + array([[ 12, -4, -8], + [ -4, 16, -12], + [ -8, -12, 20]]) + + or use the ``symmetrized=True`` option + + >>> csgraph.laplacian(G, symmetrized=True) + array([[ 12, -4, -8], + [ -4, 16, -12], + [ -8, -12, 20]]) + + that is equivalent to symmetrizing the original graph + + >>> csgraph.laplacian(G + G.T) + array([[ 12, -4, -8], + [ -4, 16, -12], + [ -8, -12, 20]]) + + The goal of normalization is to make the non-zero diagonal entries + of the Laplacian matrix to be all unit, also scaling off-diagonal + entries correspondingly. The normalization can be done manually, e.g., + + >>> G = np.array([[0, 1, 1], [1, 0, 1], [1, 1, 0]]) + >>> L, d = csgraph.laplacian(G, return_diag=True) + >>> L + array([[ 2, -1, -1], + [-1, 2, -1], + [-1, -1, 2]]) + >>> d + array([2, 2, 2]) + >>> scaling = np.sqrt(d) + >>> scaling + array([1.41421356, 1.41421356, 1.41421356]) + >>> (1/scaling)*L*(1/scaling) + array([[ 1. , -0.5, -0.5], + [-0.5, 1. , -0.5], + [-0.5, -0.5, 1. ]]) + + Or using ``normed=True`` option + + >>> L, d = csgraph.laplacian(G, return_diag=True, normed=True) + >>> L + array([[ 1. , -0.5, -0.5], + [-0.5, 1. , -0.5], + [-0.5, -0.5, 1. ]]) + + which now instead of the diagonal returns the scaling coefficients + + >>> d + array([1.41421356, 1.41421356, 1.41421356]) + + Zero scaling coefficients are substituted with 1s, where scaling + has thus no effect, e.g., + + >>> G = np.array([[0, 0, 0], [0, 0, 1], [0, 1, 0]]) + >>> G + array([[0, 0, 0], + [0, 0, 1], + [0, 1, 0]]) + >>> L, d = csgraph.laplacian(G, return_diag=True, normed=True) + >>> L + array([[ 0., -0., -0.], + [-0., 1., -1.], + [-0., -1., 1.]]) + >>> d + array([1., 1., 1.]) + + Only the symmetric normalization is implemented, resulting + in a symmetric Laplacian matrix if and only if its graph is symmetric + and has all non-negative degrees, like in the examples above. + + The output Laplacian matrix is by default a dense array or a sparse + array or matrix inferring its class, shape, format, and dtype from + the input graph matrix: + + >>> G = np.array([[0, 1, 1], [1, 0, 1], [1, 1, 0]]).astype(np.float32) + >>> G + array([[0., 1., 1.], + [1., 0., 1.], + [1., 1., 0.]], dtype=float32) + >>> csgraph.laplacian(G) + array([[ 2., -1., -1.], + [-1., 2., -1.], + [-1., -1., 2.]], dtype=float32) + + but can alternatively be generated matrix-free as a LinearOperator: + + >>> L = csgraph.laplacian(G, form="lo") + >>> L + <3x3 _CustomLinearOperator with dtype=float32> + >>> L(np.eye(3)) + array([[ 2., -1., -1.], + [-1., 2., -1.], + [-1., -1., 2.]]) + + or as a lambda-function: + + >>> L = csgraph.laplacian(G, form="function") + >>> L + . at 0x0000012AE6F5A598> + >>> L(np.eye(3)) + array([[ 2., -1., -1.], + [-1., 2., -1.], + [-1., -1., 2.]]) + + The Laplacian matrix is used for + spectral data clustering and embedding + as well as for spectral graph partitioning. + Our final example illustrates the latter + for a noisy directed linear graph. + + >>> from scipy.sparse import diags_array, random_array + >>> from scipy.sparse.linalg import lobpcg + + Create a directed linear graph with ``N=35`` vertices + using a sparse adjacency matrix ``G``: + + >>> N = 35 + >>> G = diags_array(np.ones(N - 1), offsets=1, format="csr") + + Fix a random seed ``rng`` and add a random sparse noise to the graph ``G``: + + >>> rng = np.random.default_rng() + >>> G += 1e-2 * random_array((N, N), density=0.1, rng=rng) + + Set initial approximations for eigenvectors: + + >>> X = rng.random((N, 2)) + + The constant vector of ones is always a trivial eigenvector + of the non-normalized Laplacian to be filtered out: + + >>> Y = np.ones((N, 1)) + + Alternating (1) the sign of the graph weights allows determining + labels for spectral max- and min- cuts in a single loop. + Since the graph is undirected, the option ``symmetrized=True`` + must be used in the construction of the Laplacian. + The option ``normed=True`` cannot be used in (2) for the negative weights + here as the symmetric normalization evaluates square roots. + The option ``form="lo"`` in (2) is matrix-free, i.e., guarantees + a fixed memory footprint and read-only access to the graph. + Calling the eigenvalue solver ``lobpcg`` (3) computes the Fiedler vector + that determines the labels as the signs of its components in (5). + Since the sign in an eigenvector is not deterministic and can flip, + we fix the sign of the first component to be always +1 in (4). + + >>> for cut in ["max", "min"]: + ... G = -G # 1. + ... L = csgraph.laplacian(G, symmetrized=True, form="lo") # 2. + ... _, eves = lobpcg(L, X, Y=Y, largest=False, tol=1e-2) # 3. + ... eves *= np.sign(eves[0, 0]) # 4. + ... print(cut + "-cut labels:\\n", 1 * (eves[:, 0]>0)) # 5. + max-cut labels: + [1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1] + min-cut labels: + [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] + + As anticipated for a (slightly noisy) linear graph, + the max-cut strips all the edges of the graph coloring all + odd vertices into one color and all even vertices into another one, + while the balanced min-cut partitions the graph + in the middle by deleting a single edge. + Both determined partitions are optimal. + """ + is_pydata_sparse = is_pydata_spmatrix(csgraph) + if is_pydata_sparse: + pydata_sparse_cls = csgraph.__class__ + csgraph = convert_pydata_sparse_to_scipy(csgraph) + if csgraph.ndim != 2 or csgraph.shape[0] != csgraph.shape[1]: + raise ValueError('csgraph must be a square matrix or array') + + if normed and ( + np.issubdtype(csgraph.dtype, np.signedinteger) + or np.issubdtype(csgraph.dtype, np.uint) + ): + csgraph = csgraph.astype(np.float64) + + if form == "array": + create_lap = ( + _laplacian_sparse if issparse(csgraph) else _laplacian_dense + ) + else: + create_lap = ( + _laplacian_sparse_flo + if issparse(csgraph) + else _laplacian_dense_flo + ) + + degree_axis = 1 if use_out_degree else 0 + + lap, d = create_lap( + csgraph, + normed=normed, + axis=degree_axis, + copy=copy, + form=form, + dtype=dtype, + symmetrized=symmetrized, + ) + if is_pydata_sparse: + lap = pydata_sparse_cls.from_scipy_sparse(lap) + if return_diag: + return lap, d + return lap + + +def _setdiag_dense(m, d): + step = len(d) + 1 + m.flat[::step] = d + + +def _laplace(m, d): + return lambda v: v * d[:, np.newaxis] - m @ v + + +def _laplace_normed(m, d, nd): + laplace = _laplace(m, d) + return lambda v: nd[:, np.newaxis] * laplace(v * nd[:, np.newaxis]) + + +def _laplace_sym(m, d): + return ( + lambda v: v * d[:, np.newaxis] + - m @ v + - np.transpose(np.conjugate(np.transpose(np.conjugate(v)) @ m)) + ) + + +def _laplace_normed_sym(m, d, nd): + laplace_sym = _laplace_sym(m, d) + return lambda v: nd[:, np.newaxis] * laplace_sym(v * nd[:, np.newaxis]) + + +def _linearoperator(mv, shape, dtype): + return LinearOperator(matvec=mv, matmat=mv, shape=shape, dtype=dtype) + + +def _laplacian_sparse_flo(graph, normed, axis, copy, form, dtype, symmetrized): + # The keyword argument `copy` is unused and has no effect here. + del copy + + if dtype is None: + dtype = graph.dtype + + graph_sum = np.asarray(graph.sum(axis=axis)).ravel() + graph_diagonal = graph.diagonal() + diag = graph_sum - graph_diagonal + if symmetrized: + graph_sum += np.asarray(graph.sum(axis=1 - axis)).ravel() + diag = graph_sum - graph_diagonal - graph_diagonal + + if normed: + isolated_node_mask = diag == 0 + w = np.where(isolated_node_mask, 1, np.sqrt(diag)) + if symmetrized: + md = _laplace_normed_sym(graph, graph_sum, 1.0 / w) + else: + md = _laplace_normed(graph, graph_sum, 1.0 / w) + if form == "function": + return md, w.astype(dtype, copy=False) + elif form == "lo": + m = _linearoperator(md, shape=graph.shape, dtype=dtype) + return m, w.astype(dtype, copy=False) + else: + raise ValueError(f"Invalid form: {form!r}") + else: + if symmetrized: + md = _laplace_sym(graph, graph_sum) + else: + md = _laplace(graph, graph_sum) + if form == "function": + return md, diag.astype(dtype, copy=False) + elif form == "lo": + m = _linearoperator(md, shape=graph.shape, dtype=dtype) + return m, diag.astype(dtype, copy=False) + else: + raise ValueError(f"Invalid form: {form!r}") + + +def _laplacian_sparse(graph, normed, axis, copy, form, dtype, symmetrized): + # The keyword argument `form` is unused and has no effect here. + del form + + if dtype is None: + dtype = graph.dtype + + needs_copy = False + if graph.format in ('lil', 'dok'): + m = graph.tocoo() + else: + m = graph + if copy: + needs_copy = True + + if symmetrized: + m += m.T.conj() + + w = np.asarray(m.sum(axis=axis)).ravel() - m.diagonal() + if normed: + m = m.tocoo(copy=needs_copy) + isolated_node_mask = (w == 0) + w = np.where(isolated_node_mask, 1, np.sqrt(w)) + m.data /= w[m.row] + m.data /= w[m.col] + m.data *= -1 + m.setdiag(1 - isolated_node_mask) + else: + if m.format == 'dia': + m = m.copy() + else: + m = m.tocoo(copy=needs_copy) + m.data *= -1 + m.setdiag(w) + + return m.astype(dtype, copy=False), w.astype(dtype) + + +def _laplacian_dense_flo(graph, normed, axis, copy, form, dtype, symmetrized): + + if copy: + m = np.array(graph) + else: + m = np.asarray(graph) + + if dtype is None: + dtype = m.dtype + + graph_sum = m.sum(axis=axis) + graph_diagonal = m.diagonal() + diag = graph_sum - graph_diagonal + if symmetrized: + graph_sum += m.sum(axis=1 - axis) + diag = graph_sum - graph_diagonal - graph_diagonal + + if normed: + isolated_node_mask = diag == 0 + w = np.where(isolated_node_mask, 1, np.sqrt(diag)) + if symmetrized: + md = _laplace_normed_sym(m, graph_sum, 1.0 / w) + else: + md = _laplace_normed(m, graph_sum, 1.0 / w) + if form == "function": + return md, w.astype(dtype, copy=False) + elif form == "lo": + m = _linearoperator(md, shape=graph.shape, dtype=dtype) + return m, w.astype(dtype, copy=False) + else: + raise ValueError(f"Invalid form: {form!r}") + else: + if symmetrized: + md = _laplace_sym(m, graph_sum) + else: + md = _laplace(m, graph_sum) + if form == "function": + return md, diag.astype(dtype, copy=False) + elif form == "lo": + m = _linearoperator(md, shape=graph.shape, dtype=dtype) + return m, diag.astype(dtype, copy=False) + else: + raise ValueError(f"Invalid form: {form!r}") + + +def _laplacian_dense(graph, normed, axis, copy, form, dtype, symmetrized): + + if form != "array": + raise ValueError(f'{form!r} must be "array"') + + if dtype is None: + dtype = graph.dtype + + if copy: + m = np.array(graph) + else: + m = np.asarray(graph) + + if dtype is None: + dtype = m.dtype + + if symmetrized: + m += m.T.conj() + np.fill_diagonal(m, 0) + w = m.sum(axis=axis) + if normed: + isolated_node_mask = (w == 0) + w = np.where(isolated_node_mask, 1, np.sqrt(w)) + m /= w + m /= w[:, np.newaxis] + m *= -1 + _setdiag_dense(m, 1 - isolated_node_mask) + else: + m *= -1 + _setdiag_dense(m, w) + + return m.astype(dtype, copy=False), w.astype(dtype, copy=False) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/_matching.cpython-312-x86_64-linux-gnu.so b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/_matching.cpython-312-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..29ea54615131f05f69b308f2235bfbabcb61b3b9 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/_matching.cpython-312-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:57636d89fcb29cdf654ffa5b6d0120ff25d1fb8d0d608b68e7aaaf5b2cf6c919 +size 183048 diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/_min_spanning_tree.cpython-312-x86_64-linux-gnu.so b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/_min_spanning_tree.cpython-312-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..09ccfe197cb35fb78e99a8bbed6923b8827a4030 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/_min_spanning_tree.cpython-312-x86_64-linux-gnu.so differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/_reordering.cpython-312-x86_64-linux-gnu.so b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/_reordering.cpython-312-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..34a7101c59b42dc6f09fcc7c7f92ef3b7ffae67e --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/_reordering.cpython-312-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1354563644ee9209f297761697bc215cdb22c79b718cd57c916926037ac1c9e9 +size 153224 diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/_shortest_path.cpython-312-x86_64-linux-gnu.so b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/_shortest_path.cpython-312-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..fe89d671a357f697c9cac0c9b3caac9792f78440 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/_shortest_path.cpython-312-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:432679757ec4e8c34a4c0903d1e1aaceec6d0232043e2014f1de436a0094a088 +size 499264 diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/_tools.cpython-312-x86_64-linux-gnu.so b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/_tools.cpython-312-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..ee77a995bc5e1c0bf8b96b6e1eca0e0f8e9c7461 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/_tools.cpython-312-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e1ec508d60f12cc7d38621513c0fa994593a0ff3dda89ab806fa9e9bb2de4b16 +size 179264 diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/_traversal.cpython-312-x86_64-linux-gnu.so b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/_traversal.cpython-312-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..511bc79911fbba40e48f3be2eb62591bacc8134f --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/_traversal.cpython-312-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a110bc9b246c65b4290a529e4f5f87667da30781dcbd690e0833b7f25bf1abb +size 359392 diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/_validation.py b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/_validation.py new file mode 100644 index 0000000000000000000000000000000000000000..57ce3951ed4fae5f600f683bd3a98b43ac7f653b --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/_validation.py @@ -0,0 +1,71 @@ +import numpy as np +from scipy.sparse import issparse +from scipy.sparse._sputils import convert_pydata_sparse_to_scipy +from scipy.sparse.csgraph._tools import ( + csgraph_to_dense, csgraph_from_dense, + csgraph_masked_from_dense, csgraph_from_masked +) + +DTYPE = np.float64 + + +def validate_graph(csgraph, directed, dtype=DTYPE, + csr_output=True, dense_output=True, + copy_if_dense=False, copy_if_sparse=False, + null_value_in=0, null_value_out=np.inf, + infinity_null=True, nan_null=True): + """Routine for validation and conversion of csgraph inputs""" + if not (csr_output or dense_output): + raise ValueError("Internal: dense or csr output must be true") + + accept_fv = [null_value_in] + if infinity_null: + accept_fv.append(np.inf) + if nan_null: + accept_fv.append(np.nan) + csgraph = convert_pydata_sparse_to_scipy(csgraph, accept_fv=accept_fv) + + # if undirected and csc storage, then transposing in-place + # is quicker than later converting to csr. + if (not directed) and issparse(csgraph) and csgraph.format == "csc": + csgraph = csgraph.T + + if issparse(csgraph): + if csr_output: + if csgraph.format == "bsr": + csgraph = csgraph.tocsr(copy=copy_if_sparse) + csgraph.eliminate_zeros() + csgraph = csgraph.astype(dtype, copy=False) + else: + csgraph = csgraph.tocsr(copy=copy_if_sparse).astype(dtype, copy=False) + else: + csgraph = csgraph_to_dense(csgraph, null_value=null_value_out) + elif np.ma.isMaskedArray(csgraph): + if dense_output: + mask = csgraph.mask + csgraph = np.array(csgraph.data, dtype=dtype, copy=copy_if_dense) + csgraph[mask] = null_value_out + else: + csgraph = csgraph_from_masked(csgraph) + else: + if dense_output: + csgraph = csgraph_masked_from_dense(csgraph, + copy=copy_if_dense, + null_value=null_value_in, + nan_null=nan_null, + infinity_null=infinity_null) + mask = csgraph.mask + csgraph = np.asarray(csgraph.data, dtype=dtype) + csgraph[mask] = null_value_out + else: + csgraph = csgraph_from_dense(csgraph, null_value=null_value_in, + infinity_null=infinity_null, + nan_null=nan_null) + + if csgraph.ndim != 2: + raise ValueError("compressed-sparse graph must be 2-D") + + if csgraph.shape[0] != csgraph.shape[1]: + raise ValueError("compressed-sparse graph must be shape (N, N)") + + return csgraph diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/__init__.py b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f221b3758559e77a36c31de8794bc619cf66dd15 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/__pycache__/__init__.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/__pycache__/test_connected_components.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/__pycache__/test_connected_components.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..195eba9c50eaf2a574b69cf958fa95b5fbfc3e08 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/__pycache__/test_connected_components.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/__pycache__/test_conversions.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/__pycache__/test_conversions.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ea284d68e440d1e6644565aa6162bc64aa2a7713 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/__pycache__/test_conversions.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/__pycache__/test_flow.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/__pycache__/test_flow.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3fd51e45f928248010366cf8e6e5140dd06447f9 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/__pycache__/test_flow.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/__pycache__/test_graph_laplacian.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/__pycache__/test_graph_laplacian.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a6be114dbd335bd0f99f5429ec8b94d6a8354e13 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/__pycache__/test_graph_laplacian.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/__pycache__/test_index_arrays_dtype.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/__pycache__/test_index_arrays_dtype.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f0ee5c8b7c5e31fe39ad328da587881ebe5111d1 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/__pycache__/test_index_arrays_dtype.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/__pycache__/test_matching.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/__pycache__/test_matching.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..291c4d463b0f31ef9fb98dbdedcd847767318465 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/__pycache__/test_matching.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/__pycache__/test_pydata_sparse.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/__pycache__/test_pydata_sparse.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..356943690bf574f61cd9cbc6c8d974138fb20731 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/__pycache__/test_pydata_sparse.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/__pycache__/test_reordering.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/__pycache__/test_reordering.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..69e786787005f93cff5b2433f348ee022b40c13d Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/__pycache__/test_reordering.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/__pycache__/test_shortest_path.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/__pycache__/test_shortest_path.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..078f987690c98619456a15566c72578d13260cdf Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/__pycache__/test_shortest_path.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/__pycache__/test_spanning_tree.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/__pycache__/test_spanning_tree.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..792c790907fd4a5bebf971b32ffbcfe029dfab29 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/__pycache__/test_spanning_tree.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/__pycache__/test_traversal.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/__pycache__/test_traversal.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ad47a323e902858600745bd478f40019fb7fb8d3 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/__pycache__/test_traversal.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/test_connected_components.py b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/test_connected_components.py new file mode 100644 index 0000000000000000000000000000000000000000..9dce1c56d7411e4069f562ff7aa89629c73fd413 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/test_connected_components.py @@ -0,0 +1,162 @@ +import numpy as np +from numpy.testing import assert_allclose, assert_equal, assert_array_almost_equal +import pytest +from scipy.sparse import bsr_array, csgraph, csr_array + + +def test_weak_connections(): + Xde = np.array([[0, 1, 0], + [0, 0, 0], + [0, 0, 0]]) + + Xsp = csgraph.csgraph_from_dense(Xde, null_value=0) + + for X in Xsp, Xde: + n_components, labels =\ + csgraph.connected_components(X, directed=True, + connection='weak') + + assert_equal(n_components, 2) + assert_array_almost_equal(labels, [0, 0, 1]) + + +def test_strong_connections(): + X1de = np.array([[0, 1, 0], + [0, 0, 0], + [0, 0, 0]]) + X2de = X1de + X1de.T + + X1sp = csgraph.csgraph_from_dense(X1de, null_value=0) + X2sp = csgraph.csgraph_from_dense(X2de, null_value=0) + + for X in X1sp, X1de: + n_components, labels =\ + csgraph.connected_components(X, directed=True, + connection='strong') + + assert_equal(n_components, 3) + labels.sort() + assert_array_almost_equal(labels, [0, 1, 2]) + + for X in X2sp, X2de: + n_components, labels =\ + csgraph.connected_components(X, directed=True, + connection='strong') + + assert_equal(n_components, 2) + labels.sort() + assert_array_almost_equal(labels, [0, 0, 1]) + + +def test_strong_connections2(): + X = np.array([[0, 0, 0, 0, 0, 0], + [1, 0, 1, 0, 0, 0], + [0, 0, 0, 1, 0, 0], + [0, 0, 1, 0, 1, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 1, 0]]) + n_components, labels =\ + csgraph.connected_components(X, directed=True, + connection='strong') + assert_equal(n_components, 5) + labels.sort() + assert_array_almost_equal(labels, [0, 1, 2, 2, 3, 4]) + + +def test_weak_connections2(): + X = np.array([[0, 0, 0, 0, 0, 0], + [1, 0, 0, 0, 0, 0], + [0, 0, 0, 1, 0, 0], + [0, 0, 1, 0, 1, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 1, 0]]) + n_components, labels =\ + csgraph.connected_components(X, directed=True, + connection='weak') + assert_equal(n_components, 2) + labels.sort() + assert_array_almost_equal(labels, [0, 0, 1, 1, 1, 1]) + + +def test_ticket1876(): + # Regression test: this failed in the original implementation + # There should be two strongly-connected components; previously gave one + g = np.array([[0, 1, 1, 0], + [1, 0, 0, 1], + [0, 0, 0, 1], + [0, 0, 1, 0]]) + n_components, labels = csgraph.connected_components(g, connection='strong') + + assert_equal(n_components, 2) + assert_equal(labels[0], labels[1]) + assert_equal(labels[2], labels[3]) + + +def test_fully_connected_graph(): + # Fully connected dense matrices raised an exception. + # https://github.com/scipy/scipy/issues/3818 + g = np.ones((4, 4)) + n_components, labels = csgraph.connected_components(g) + assert_equal(n_components, 1) + + +def test_int64_indices_undirected(): + # See https://github.com/scipy/scipy/issues/18716 + g = csr_array(([1], np.array([[0], [1]], dtype=np.int64)), shape=(2, 2)) + assert g.indices.dtype == np.int64 + n, labels = csgraph.connected_components(g, directed=False) + assert n == 1 + assert_array_almost_equal(labels, [0, 0]) + + +def test_int64_indices_directed(): + # See https://github.com/scipy/scipy/issues/18716 + g = csr_array(([1], np.array([[0], [1]], dtype=np.int64)), shape=(2, 2)) + assert g.indices.dtype == np.int64 + n, labels = csgraph.connected_components(g, directed=True, + connection='strong') + assert n == 2 + assert_array_almost_equal(labels, [1, 0]) + + +def test_single_scc_early_exit(): + # Exercises the early-exit check + rows = [0, 1, 1, 2, 3, 3, 4, 5] + cols = [1, 2, 5, 3, 1, 4, 0, 2] + data = np.ones(len(rows), dtype=np.int32) + g = csr_array((data, (rows, cols)), shape=(6, 6)) + n_components, labels = csgraph.connected_components( + g, directed=True, connection='strong') + assert_equal(n_components, 1) + assert_equal(len(np.unique(labels)), 1) + + +# regression test for gh-23142 +@pytest.mark.parametrize("graph", [ + np.array([[1, 0, 1, 0], + [0, 1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1]]), + np.array([[1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1]]), + np.array([[1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0]]), +]) +def test_bsr_blocksize_connected_components(graph): + reference_graph = bsr_array(graph, blocksize=(1, 1)).astype(bool) + sparse_graph = bsr_array(graph, blocksize=(2, 2)).astype(bool) + + n_expected, lbl_expected = csgraph.connected_components( + reference_graph, directed=False, return_labels=True, connection="weak" + ) + n_actual, lbl_actual = csgraph.connected_components( + sparse_graph, directed=False, return_labels=True, connection="weak" + ) + + assert_equal(n_actual, n_expected) + assert_allclose(lbl_actual, lbl_expected) + diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/test_conversions.py b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/test_conversions.py new file mode 100644 index 0000000000000000000000000000000000000000..705f036c66b9b66115fd3d7c07a9506b1aad1bba --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/test_conversions.py @@ -0,0 +1,75 @@ +import numpy as np +import pytest +from numpy.testing import assert_array_almost_equal +from scipy.sparse import csr_array +from scipy.sparse.csgraph import csgraph_from_dense, csgraph_to_dense, reconstruct_path + + +def test_csgraph_from_dense(): + np.random.seed(1234) + G = np.random.random((10, 10)) + some_nulls = (G < 0.4) + all_nulls = (G < 0.8) + + for null_value in [0, np.nan, np.inf]: + G[all_nulls] = null_value + with np.errstate(invalid="ignore"): + G_csr = csgraph_from_dense(G, null_value=0) + + G[all_nulls] = 0 + assert_array_almost_equal(G, G_csr.toarray()) + + for null_value in [np.nan, np.inf]: + G[all_nulls] = 0 + G[some_nulls] = null_value + with np.errstate(invalid="ignore"): + G_csr = csgraph_from_dense(G, null_value=0) + + G[all_nulls] = 0 + assert_array_almost_equal(G, G_csr.toarray()) + + +def test_csgraph_to_dense(): + np.random.seed(1234) + G = np.random.random((10, 10)) + nulls = (G < 0.8) + G[nulls] = np.inf + + G_csr = csgraph_from_dense(G) + + for null_value in [0, 10, -np.inf, np.inf]: + G[nulls] = null_value + assert_array_almost_equal(G, csgraph_to_dense(G_csr, null_value)) + + +def test_multiple_edges(): + # create a random square matrix with an even number of elements + np.random.seed(1234) + X = np.random.random((10, 10)) + Xcsr = csr_array(X) + + # now double-up every other column + Xcsr.indices[::2] = Xcsr.indices[1::2] + + # normal sparse toarray() will sum the duplicated edges + Xdense = Xcsr.toarray() + assert_array_almost_equal(Xdense[:, 1::2], + X[:, ::2] + X[:, 1::2]) + + # csgraph_to_dense chooses the minimum of each duplicated edge + Xdense = csgraph_to_dense(Xcsr) + assert_array_almost_equal(Xdense[:, 1::2], + np.minimum(X[:, ::2], X[:, 1::2])) + + +class TestReconstructPath: + def test_gh23577(self): + row_indices = [0, 1, 1, 3, 3] + col_indices = [0, 2, 3, 1, 4] + data = [9, -6, -8, 6, 8] + matrix = csr_array((data, (row_indices, col_indices)), shape=(5, 5), + dtype=np.int64) + predecessors = np.array([np.nan, np.nan, np.nan]) + with pytest.raises(ValueError, match="integral"): + reconstruct_path(matrix, predecessors) + diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/test_flow.py b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/test_flow.py new file mode 100644 index 0000000000000000000000000000000000000000..c92eb985a1145c4b7c1777f0449bb423402f6d66 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/test_flow.py @@ -0,0 +1,209 @@ +import numpy as np +from numpy.testing import assert_array_equal +import pytest + +from scipy.sparse import csr_array, csc_array, csr_matrix +from scipy.sparse.csgraph import maximum_flow +from scipy.sparse.csgraph._flow import ( + _add_reverse_edges, _make_edge_pointers, _make_tails +) + +methods = ['edmonds_karp', 'dinic'] + +def test_raises_on_dense_input(): + with pytest.raises(TypeError): + graph = np.array([[0, 1], [0, 0]]) + maximum_flow(graph, 0, 1) + maximum_flow(graph, 0, 1, method='edmonds_karp') + + +def test_raises_on_csc_input(): + with pytest.raises(TypeError): + graph = csc_array([[0, 1], [0, 0]]) + maximum_flow(graph, 0, 1) + maximum_flow(graph, 0, 1, method='edmonds_karp') + + +def test_raises_on_floating_point_input(): + with pytest.raises(ValueError): + graph = csr_array([[0, 1.5], [0, 0]], dtype=np.float64) + maximum_flow(graph, 0, 1) + maximum_flow(graph, 0, 1, method='edmonds_karp') + + +def test_raises_on_non_square_input(): + with pytest.raises(ValueError): + graph = csr_array([[0, 1, 2], [2, 1, 0]]) + maximum_flow(graph, 0, 1) + + +def test_raises_when_source_is_sink(): + with pytest.raises(ValueError): + graph = csr_array([[0, 1], [0, 0]]) + maximum_flow(graph, 0, 0) + maximum_flow(graph, 0, 0, method='edmonds_karp') + + +@pytest.mark.parametrize('method', methods) +@pytest.mark.parametrize('source', [-1, 2, 3]) +def test_raises_when_source_is_out_of_bounds(source, method): + with pytest.raises(ValueError): + graph = csr_array([[0, 1], [0, 0]]) + maximum_flow(graph, source, 1, method=method) + + +@pytest.mark.parametrize('method', methods) +@pytest.mark.parametrize('sink', [-1, 2, 3]) +def test_raises_when_sink_is_out_of_bounds(sink, method): + with pytest.raises(ValueError): + graph = csr_array([[0, 1], [0, 0]]) + maximum_flow(graph, 0, sink, method=method) + + +@pytest.mark.parametrize('method', methods) +def test_simple_graph(method): + # This graph looks as follows: + # (0) --5--> (1) + graph = csr_array([[0, 5], [0, 0]]) + res = maximum_flow(graph, 0, 1, method=method) + assert res.flow_value == 5 + expected_flow = np.array([[0, 5], [-5, 0]]) + assert_array_equal(res.flow.toarray(), expected_flow) + + +@pytest.mark.parametrize('method', methods) +def test_return_type(method): + graph = csr_array([[0, 5], [0, 0]]) + assert isinstance(maximum_flow(graph, 0, 1, method=method).flow, csr_array) + graph = csr_matrix([[0, 5], [0, 0]]) + assert isinstance(maximum_flow(graph, 0, 1, method=method).flow, csr_matrix) + + +@pytest.mark.parametrize('method', methods) +def test_bottle_neck_graph(method): + # This graph cannot use the full capacity between 0 and 1: + # (0) --5--> (1) --3--> (2) + graph = csr_array([[0, 5, 0], [0, 0, 3], [0, 0, 0]]) + res = maximum_flow(graph, 0, 2, method=method) + assert res.flow_value == 3 + expected_flow = np.array([[0, 3, 0], [-3, 0, 3], [0, -3, 0]]) + assert_array_equal(res.flow.toarray(), expected_flow) + + +@pytest.mark.parametrize('method', methods) +def test_backwards_flow(method): + # This example causes backwards flow between vertices 3 and 4, + # and so this test ensures that we handle that accordingly. See + # https://stackoverflow.com/q/38843963/5085211 + # for more information. + graph = csr_array([[0, 10, 0, 0, 10, 0, 0, 0], + [0, 0, 10, 0, 0, 0, 0, 0], + [0, 0, 0, 10, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 10], + [0, 0, 0, 10, 0, 10, 0, 0], + [0, 0, 0, 0, 0, 0, 10, 0], + [0, 0, 0, 0, 0, 0, 0, 10], + [0, 0, 0, 0, 0, 0, 0, 0]]) + res = maximum_flow(graph, 0, 7, method=method) + assert res.flow_value == 20 + expected_flow = np.array([[0, 10, 0, 0, 10, 0, 0, 0], + [-10, 0, 10, 0, 0, 0, 0, 0], + [0, -10, 0, 10, 0, 0, 0, 0], + [0, 0, -10, 0, 0, 0, 0, 10], + [-10, 0, 0, 0, 0, 10, 0, 0], + [0, 0, 0, 0, -10, 0, 10, 0], + [0, 0, 0, 0, 0, -10, 0, 10], + [0, 0, 0, -10, 0, 0, -10, 0]]) + assert_array_equal(res.flow.toarray(), expected_flow) + + +@pytest.mark.parametrize('method', methods) +def test_example_from_clrs_chapter_26_1(method): + # See page 659 in CLRS second edition, but note that the maximum flow + # we find is slightly different than the one in CLRS; we push a flow of + # 12 to v_1 instead of v_2. + graph = csr_array([[0, 16, 13, 0, 0, 0], + [0, 0, 10, 12, 0, 0], + [0, 4, 0, 0, 14, 0], + [0, 0, 9, 0, 0, 20], + [0, 0, 0, 7, 0, 4], + [0, 0, 0, 0, 0, 0]]) + res = maximum_flow(graph, 0, 5, method=method) + assert res.flow_value == 23 + expected_flow = np.array([[0, 12, 11, 0, 0, 0], + [-12, 0, 0, 12, 0, 0], + [-11, 0, 0, 0, 11, 0], + [0, -12, 0, 0, -7, 19], + [0, 0, -11, 7, 0, 4], + [0, 0, 0, -19, -4, 0]]) + assert_array_equal(res.flow.toarray(), expected_flow) + + +@pytest.mark.parametrize('method', methods) +def test_disconnected_graph(method): + # This tests the following disconnected graph: + # (0) --5--> (1) (2) --3--> (3) + graph = csr_array([[0, 5, 0, 0], + [0, 0, 0, 0], + [0, 0, 9, 3], + [0, 0, 0, 0]]) + res = maximum_flow(graph, 0, 3, method=method) + assert res.flow_value == 0 + expected_flow = np.zeros((4, 4), dtype=np.int32) + assert_array_equal(res.flow.toarray(), expected_flow) + + +@pytest.mark.parametrize('method', methods) +def test_add_reverse_edges_large_graph(method): + # Regression test for https://github.com/scipy/scipy/issues/14385 + n = 100_000 + indices = np.arange(1, n) + indptr = np.array(list(range(n)) + [n - 1]) + data = np.ones(n - 1, dtype=np.int32) + graph = csr_array((data, indices, indptr), shape=(n, n)) + res = maximum_flow(graph, 0, n - 1, method=method) + assert res.flow_value == 1 + expected_flow = graph - graph.transpose() + assert_array_equal(res.flow.data, expected_flow.data) + assert_array_equal(res.flow.indices, expected_flow.indices) + assert_array_equal(res.flow.indptr, expected_flow.indptr) + + +@pytest.mark.parametrize("a,b_data_expected", [ + ([[]], []), + ([[0], [0]], []), + ([[1, 0, 2], [0, 0, 0], [0, 3, 0]], [1, 2, 0, 0, 3]), + ([[9, 8, 7], [4, 5, 6], [0, 0, 0]], [9, 8, 7, 4, 5, 6, 0, 0])]) +def test_add_reverse_edges(a, b_data_expected): + """Test that the reversal of the edges of the input graph works + as expected. + """ + a = csr_array(a, dtype=np.int32, shape=(len(a), len(a))) + b = _add_reverse_edges(a) + assert_array_equal(b.data, b_data_expected) + + +@pytest.mark.parametrize("a,expected", [ + ([[]], []), + ([[0]], []), + ([[1]], [0]), + ([[0, 1], [10, 0]], [1, 0]), + ([[1, 0, 2], [0, 0, 3], [4, 5, 0]], [0, 3, 4, 1, 2]) +]) +def test_make_edge_pointers(a, expected): + a = csr_array(a, dtype=np.int32) + rev_edge_ptr = _make_edge_pointers(a) + assert_array_equal(rev_edge_ptr, expected) + + +@pytest.mark.parametrize("a,expected", [ + ([[]], []), + ([[0]], []), + ([[1]], [0]), + ([[0, 1], [10, 0]], [0, 1]), + ([[1, 0, 2], [0, 0, 3], [4, 5, 0]], [0, 0, 1, 2, 2]) +]) +def test_make_tails(a, expected): + a = csr_array(a, dtype=np.int32) + tails = _make_tails(a) + assert_array_equal(tails, expected) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/test_graph_laplacian.py b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/test_graph_laplacian.py new file mode 100644 index 0000000000000000000000000000000000000000..46536ff7dac6b8775c29b6918128420a02f53dab --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/test_graph_laplacian.py @@ -0,0 +1,367 @@ +import pytest +import numpy as np +from numpy.testing import assert_allclose +from pytest import raises as assert_raises +from scipy import sparse + +from scipy.sparse import csgraph + + +def check_int_type(mat): + return np.issubdtype(mat.dtype, np.signedinteger) or np.issubdtype( + mat.dtype, np.ulong + ) + + +def test_laplacian_value_error(): + for t in int, float, complex: + for m in ([1, 1], + [[[1]]], + [[1, 2, 3], [4, 5, 6]], + [[1, 2], [3, 4], [5, 5]]): + A = np.array(m, dtype=t) + assert_raises(ValueError, csgraph.laplacian, A) + + +def _explicit_laplacian(x, normed=False): + if sparse.issparse(x): + x = x.toarray() + x = np.asarray(x) + y = -1.0 * x + for j in range(y.shape[0]): + y[j,j] = x[j,j+1:].sum() + x[j,:j].sum() + if normed: + d = np.diag(y).copy() + d[d == 0] = 1.0 + y /= d[:,None]**.5 + y /= d[None,:]**.5 + return y + + +def _check_symmetric_graph_laplacian(mat, normed, copy=True): + if not hasattr(mat, 'shape'): + mat = eval(mat, dict(np=np, sparse=sparse)) + + if sparse.issparse(mat): + sp_mat = mat + mat = sp_mat.toarray() + else: + sp_mat = sparse.csr_array(mat) + + mat_copy = np.copy(mat) + sp_mat_copy = sparse.csr_array(sp_mat, copy=True) + + n_nodes = mat.shape[0] + explicit_laplacian = _explicit_laplacian(mat, normed=normed) + laplacian = csgraph.laplacian(mat, normed=normed, copy=copy) + sp_laplacian = csgraph.laplacian(sp_mat, normed=normed, + copy=copy) + + if copy: + assert_allclose(mat, mat_copy) + _assert_allclose_sparse(sp_mat, sp_mat_copy) + else: + if not (normed and check_int_type(mat)): + assert_allclose(laplacian, mat) + if sp_mat.format == 'coo': + _assert_allclose_sparse(sp_laplacian, sp_mat) + + assert_allclose(laplacian, sp_laplacian.toarray()) + + for tested in [laplacian, sp_laplacian.toarray()]: + if not normed: + assert_allclose(tested.sum(axis=0), np.zeros(n_nodes)) + assert_allclose(tested.T, tested) + assert_allclose(tested, explicit_laplacian) + + +def test_symmetric_graph_laplacian(): + symmetric_mats = ( + 'np.arange(10) * np.arange(10)[:, np.newaxis]', + 'np.ones((7, 7))', + 'np.eye(19)', + 'sparse.diags_array([1.0, 1.0], offsets=[-1, 1], shape=(4, 4))', + 'sparse.diags_array([1.0, 1.0], offsets=[-1, 1], shape=(4, 4)).toarray()', + 'sparse.diags_array([1.0, 1.0], offsets=[-1, 1], shape=(4, 4)).todense()', + 'np.vander(np.arange(4)) + np.vander(np.arange(4)).T' + ) + for mat in symmetric_mats: + for normed in True, False: + for copy in True, False: + _check_symmetric_graph_laplacian(mat, normed, copy) + + +def _assert_allclose_sparse(a, b, **kwargs): + # helper function that can deal with sparse matrices + if sparse.issparse(a): + a = a.toarray() + if sparse.issparse(b): + b = b.toarray() + assert_allclose(a, b, **kwargs) + + +def _check_laplacian_dtype_none( + A, desired_L, desired_d, normed, use_out_degree, copy, dtype, arr_type +): + mat = arr_type(A, dtype=dtype) + L, d = csgraph.laplacian( + mat, + normed=normed, + return_diag=True, + use_out_degree=use_out_degree, + copy=copy, + dtype=None, + ) + if normed and check_int_type(mat): + assert L.dtype == np.float64 + assert d.dtype == np.float64 + _assert_allclose_sparse(L, desired_L, atol=1e-12) + _assert_allclose_sparse(d, desired_d, atol=1e-12) + else: + assert L.dtype == dtype + assert d.dtype == dtype + desired_L = np.asarray(desired_L).astype(dtype) + desired_d = np.asarray(desired_d).astype(dtype) + _assert_allclose_sparse(L, desired_L, atol=1e-12) + _assert_allclose_sparse(d, desired_d, atol=1e-12) + + if not copy: + if not (normed and check_int_type(mat)): + if type(mat) is np.ndarray: + assert_allclose(L, mat) + elif mat.format == "coo": + _assert_allclose_sparse(L, mat) + + +def _check_laplacian_dtype( + A, desired_L, desired_d, normed, use_out_degree, copy, dtype, arr_type +): + mat = arr_type(A, dtype=dtype) + L, d = csgraph.laplacian( + mat, + normed=normed, + return_diag=True, + use_out_degree=use_out_degree, + copy=copy, + dtype=dtype, + ) + assert L.dtype == dtype + assert d.dtype == dtype + desired_L = np.asarray(desired_L).astype(dtype) + desired_d = np.asarray(desired_d).astype(dtype) + _assert_allclose_sparse(L, desired_L, atol=1e-12) + _assert_allclose_sparse(d, desired_d, atol=1e-12) + + if not copy: + if not (normed and check_int_type(mat)): + if type(mat) is np.ndarray: + assert_allclose(L, mat) + elif mat.format == 'coo': + _assert_allclose_sparse(L, mat) + + +INT_DTYPES = (np.intc, np.long, np.longlong) +REAL_DTYPES = (np.float32, np.float64, np.longdouble) +COMPLEX_DTYPES = (np.complex64, np.complex128, np.clongdouble) +DTYPES = INT_DTYPES + REAL_DTYPES + COMPLEX_DTYPES + + +@pytest.mark.parametrize("dtype", DTYPES) +@pytest.mark.parametrize("arr_type", [np.array, + sparse.csr_matrix, + sparse.coo_matrix, + sparse.csr_array, + sparse.coo_array]) +@pytest.mark.parametrize("copy", [True, False]) +@pytest.mark.parametrize("normed", [True, False]) +@pytest.mark.parametrize("use_out_degree", [True, False]) +def test_asymmetric_laplacian(use_out_degree, normed, + copy, dtype, arr_type): + # adjacency matrix + A = [[0, 1, 0], + [4, 2, 0], + [0, 0, 0]] + A = arr_type(np.array(A), dtype=dtype) + A_copy = A.copy() + + if not normed and use_out_degree: + # Laplacian matrix using out-degree + L = [[1, -1, 0], + [-4, 4, 0], + [0, 0, 0]] + d = [1, 4, 0] + + if normed and use_out_degree: + # normalized Laplacian matrix using out-degree + L = [[1, -0.5, 0], + [-2, 1, 0], + [0, 0, 0]] + d = [1, 2, 1] + + if not normed and not use_out_degree: + # Laplacian matrix using in-degree + L = [[4, -1, 0], + [-4, 1, 0], + [0, 0, 0]] + d = [4, 1, 0] + + if normed and not use_out_degree: + # normalized Laplacian matrix using in-degree + L = [[1, -0.5, 0], + [-2, 1, 0], + [0, 0, 0]] + d = [2, 1, 1] + + _check_laplacian_dtype_none( + A, + L, + d, + normed=normed, + use_out_degree=use_out_degree, + copy=copy, + dtype=dtype, + arr_type=arr_type, + ) + + _check_laplacian_dtype( + A_copy, + L, + d, + normed=normed, + use_out_degree=use_out_degree, + copy=copy, + dtype=dtype, + arr_type=arr_type, + ) + + +@pytest.mark.parametrize("fmt", ['csr', 'csc', 'coo', 'lil', + 'dok', 'dia', 'bsr']) +@pytest.mark.parametrize("normed", [True, False]) +@pytest.mark.parametrize("copy", [True, False]) +def test_sparse_formats(fmt, normed, copy): + mat = sparse.diags_array([1.0, 1.0], offsets=[-1, 1], shape=(4, 4), format=fmt) + _check_symmetric_graph_laplacian(mat, normed, copy) + + +@pytest.mark.parametrize( + "arr_type", [np.asarray, + sparse.csr_matrix, + sparse.coo_matrix, + sparse.csr_array, + sparse.coo_array] +) +@pytest.mark.parametrize("form", ["array", "function", "lo"]) +def test_laplacian_symmetrized(arr_type, form): + # adjacency matrix + n = 3 + mat = arr_type(np.arange(n * n).reshape(n, n)) + L_in, d_in = csgraph.laplacian( + mat, + return_diag=True, + form=form, + ) + L_out, d_out = csgraph.laplacian( + mat, + return_diag=True, + use_out_degree=True, + form=form, + ) + Ls, ds = csgraph.laplacian( + mat, + return_diag=True, + symmetrized=True, + form=form, + ) + Ls_normed, ds_normed = csgraph.laplacian( + mat, + return_diag=True, + symmetrized=True, + normed=True, + form=form, + ) + mat += mat.T + Lss, dss = csgraph.laplacian(mat, return_diag=True, form=form) + Lss_normed, dss_normed = csgraph.laplacian( + mat, + return_diag=True, + normed=True, + form=form, + ) + + assert_allclose(ds, d_in + d_out) + assert_allclose(ds, dss) + assert_allclose(ds_normed, dss_normed) + + d = {} + for L in ["L_in", "L_out", "Ls", "Ls_normed", "Lss", "Lss_normed"]: + if form == "array": + d[L] = eval(L) + else: + d[L] = eval(L)(np.eye(n, dtype=mat.dtype)) + + _assert_allclose_sparse(d["Ls"], d["L_in"] + d["L_out"].T) + _assert_allclose_sparse(d["Ls"], d["Lss"]) + _assert_allclose_sparse(d["Ls_normed"], d["Lss_normed"]) + + +@pytest.mark.parametrize( + "arr_type", [np.asarray, + sparse.csr_matrix, + sparse.coo_matrix, + sparse.csr_array, + sparse.coo_array] +) +@pytest.mark.parametrize("dtype", DTYPES) +@pytest.mark.parametrize("normed", [True, False]) +@pytest.mark.parametrize("symmetrized", [True, False]) +@pytest.mark.parametrize("use_out_degree", [True, False]) +@pytest.mark.parametrize("form", ["function", "lo"]) +def test_format(dtype, arr_type, normed, symmetrized, use_out_degree, form): + n = 3 + mat = [[0, 1, 0], [4, 2, 0], [0, 0, 0]] + mat = arr_type(np.array(mat), dtype=dtype) + Lo, do = csgraph.laplacian( + mat, + return_diag=True, + normed=normed, + symmetrized=symmetrized, + use_out_degree=use_out_degree, + dtype=dtype, + ) + La, da = csgraph.laplacian( + mat, + return_diag=True, + normed=normed, + symmetrized=symmetrized, + use_out_degree=use_out_degree, + dtype=dtype, + form="array", + ) + assert_allclose(do, da) + _assert_allclose_sparse(Lo, La) + + L, d = csgraph.laplacian( + mat, + return_diag=True, + normed=normed, + symmetrized=symmetrized, + use_out_degree=use_out_degree, + dtype=dtype, + form=form, + ) + assert_allclose(d, do) + assert d.dtype == dtype + Lm = L(np.eye(n, dtype=mat.dtype)).astype(dtype) + _assert_allclose_sparse(Lm, Lo, rtol=2e-7, atol=2e-7) + x = np.arange(6).reshape(3, 2) + if not (normed and dtype in INT_DTYPES): + assert_allclose(L(x), Lo @ x) + else: + # Normalized Lo is casted to integer, but L() is not + pass + + +def test_format_error_message(): + with pytest.raises(ValueError, match="Invalid form: 'toto'"): + _ = csgraph.laplacian(np.eye(1), form='toto') diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/test_index_arrays_dtype.py b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/test_index_arrays_dtype.py new file mode 100644 index 0000000000000000000000000000000000000000..5148f1e11390ceb50a19a3ad9dc30e15e3c96e64 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/test_index_arrays_dtype.py @@ -0,0 +1,107 @@ +"""Test of csgraph public API with int64 index arrays in csr format. + +See gh-24629 +""" +import numpy as np +import pytest + +from scipy.sparse import csr_array +import scipy.sparse.csgraph as csgraph_subpackage +from scipy.sparse.csgraph import ( + connected_components, + laplacian, + shortest_path, + floyd_warshall, + dijkstra, + bellman_ford, + johnson, + yen, + breadth_first_order, + depth_first_order, + breadth_first_tree, + depth_first_tree, + minimum_spanning_tree, + reverse_cuthill_mckee, + maximum_flow, + maximum_bipartite_matching, + min_weight_full_bipartite_matching, + structural_rank, + construct_dist_matrix, + reconstruct_path, + csgraph_to_dense, + csgraph_to_masked, +) + + +@pytest.fixture() +def A(): + A = csr_array([[0, 1, 2], [2, 1, 0], [0, 1, 0]]) + A.indptr = A.indptr.astype(np.int64) + A.indices = A.indices.astype(np.int64) + return A + + +check_these_functions = [ + # (csgraph_function, args), + (connected_components, ()), + (laplacian, ()), + (floyd_warshall, ()), + (dijkstra, ()), + (bellman_ford, ()), + (johnson, ()), + (yen, (0, 1, 1)), + (breadth_first_order, (0,)), + (depth_first_order, (0,)), + (breadth_first_tree, (0,)), + (depth_first_tree, (0,)), + (minimum_spanning_tree, ()), + (reverse_cuthill_mckee, ()), + (maximum_flow, (0, 1)), + (maximum_bipartite_matching, ()), + (min_weight_full_bipartite_matching, ()), + (structural_rank, ()), + (csgraph_to_dense, ()), + (csgraph_to_masked, ()), +] + + +def test_new_functions_untested_here(): + #This tests whether any new code has arrived that isn't in the list above. + + # funcs tested individually outside of check_these_functions + handled = ["shortest_path", "construct_dist_matrix", "reconstruct_path", + "csgraph_from_dense", "csgraph_from_masked", "csgraph_masked_from_dense"] + + # look for functions that should be tested + functions = [fname for fname in dir(csgraph_subpackage) + if fname[0] != "_" + if not fname.endswith("Error") + if not fname.startswith("test") + if fname not in handled] + + checked_funcs = [x[0].__name__ for x in check_these_functions] + for func in functions: + if func not in checked_funcs: + msg = (f'function "{func}" in csgraph is not tested. ' + 'It should be added to the `check_these_functions` list above or ' + 'handled separately if its signature has many required parameters') + raise ValueError(msg) + + +@pytest.mark.parametrize("csgraph_function, args", check_these_functions) +def test_smoke_from_int64_index_arrays(csgraph_function, args, A): + csgraph_function(A, *args) + + +def test_smoke_from_int64_construct_dist_matrix(A): + _, preds = shortest_path(A, return_predecessors=True) + construct_dist_matrix(A, preds) + + +def test_smoke_from_int64_shortest_path(A): + shortest_path(A, method='auto') + + +def test_smoke_from_int64_reconstruct_path(A): + pred = np.array([-9999, 0, 1]) + reconstruct_path(A, pred) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/test_matching.py b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/test_matching.py new file mode 100644 index 0000000000000000000000000000000000000000..502927788540cc58138da8c948fcd0a570515af5 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/test_matching.py @@ -0,0 +1,306 @@ +from itertools import product + +import numpy as np +from numpy.testing import assert_array_equal, assert_equal +import pytest + +from scipy.sparse import csr_array, coo_array, diags_array +from scipy.sparse.csgraph import ( + maximum_bipartite_matching, min_weight_full_bipartite_matching +) + + +def test_maximum_bipartite_matching_raises_on_dense_input(): + with pytest.raises(TypeError): + graph = np.array([[0, 1], [0, 0]]) + maximum_bipartite_matching(graph) + + +def test_maximum_bipartite_matching_empty_graph(): + graph = csr_array((0, 0)) + x = maximum_bipartite_matching(graph, perm_type='row') + y = maximum_bipartite_matching(graph, perm_type='column') + expected_matching = np.array([]) + assert_array_equal(expected_matching, x) + assert_array_equal(expected_matching, y) + + +def test_maximum_bipartite_matching_empty_left_partition(): + graph = csr_array((2, 0)) + x = maximum_bipartite_matching(graph, perm_type='row') + y = maximum_bipartite_matching(graph, perm_type='column') + assert_array_equal(np.array([]), x) + assert_array_equal(np.array([-1, -1]), y) + + +def test_maximum_bipartite_matching_empty_right_partition(): + graph = csr_array((0, 3)) + x = maximum_bipartite_matching(graph, perm_type='row') + y = maximum_bipartite_matching(graph, perm_type='column') + assert_array_equal(np.array([-1, -1, -1]), x) + assert_array_equal(np.array([]), y) + + +def test_maximum_bipartite_matching_graph_with_no_edges(): + graph = csr_array((2, 2)) + x = maximum_bipartite_matching(graph, perm_type='row') + y = maximum_bipartite_matching(graph, perm_type='column') + assert_array_equal(np.array([-1, -1]), x) + assert_array_equal(np.array([-1, -1]), y) + + +def test_maximum_bipartite_matching_graph_that_causes_augmentation(): + # In this graph, column 1 is initially assigned to row 1, but it should be + # reassigned to make room for row 2. + graph = csr_array([[1, 1], [1, 0]]) + x = maximum_bipartite_matching(graph, perm_type='column') + y = maximum_bipartite_matching(graph, perm_type='row') + expected_matching = np.array([1, 0]) + assert_array_equal(expected_matching, x) + assert_array_equal(expected_matching, y) + + +def test_maximum_bipartite_matching_graph_with_more_rows_than_columns(): + graph = csr_array([[1, 1], [1, 0], [0, 1]]) + x = maximum_bipartite_matching(graph, perm_type='column') + y = maximum_bipartite_matching(graph, perm_type='row') + assert_array_equal(np.array([0, -1, 1]), x) + assert_array_equal(np.array([0, 2]), y) + + +def test_maximum_bipartite_matching_graph_with_more_columns_than_rows(): + graph = csr_array([[1, 1, 0], [0, 0, 1]]) + x = maximum_bipartite_matching(graph, perm_type='column') + y = maximum_bipartite_matching(graph, perm_type='row') + assert_array_equal(np.array([0, 2]), x) + assert_array_equal(np.array([0, -1, 1]), y) + + +def test_maximum_bipartite_matching_explicit_zeros_count_as_edges(): + data = [0, 0] + indices = [1, 0] + indptr = [0, 1, 2] + graph = csr_array((data, indices, indptr), shape=(2, 2)) + x = maximum_bipartite_matching(graph, perm_type='row') + y = maximum_bipartite_matching(graph, perm_type='column') + expected_matching = np.array([1, 0]) + assert_array_equal(expected_matching, x) + assert_array_equal(expected_matching, y) + + +def test_maximum_bipartite_matching_feasibility_of_result(): + # This is a regression test for GitHub issue #11458 + data = np.ones(50, dtype=int) + indices = [11, 12, 19, 22, 23, 5, 22, 3, 8, 10, 5, 6, 11, 12, 13, 5, 13, + 14, 20, 22, 3, 15, 3, 13, 14, 11, 12, 19, 22, 23, 5, 22, 3, 8, + 10, 5, 6, 11, 12, 13, 5, 13, 14, 20, 22, 3, 15, 3, 13, 14] + indptr = [0, 5, 7, 10, 10, 15, 20, 22, 22, 23, 25, 30, 32, 35, 35, 40, 45, + 47, 47, 48, 50] + graph = csr_array((data, indices, indptr), shape=(20, 25)) + x = maximum_bipartite_matching(graph, perm_type='row') + y = maximum_bipartite_matching(graph, perm_type='column') + assert (x != -1).sum() == 13 + assert (y != -1).sum() == 13 + # Ensure that each element of the matching is in fact an edge in the graph. + for u, v in zip(range(graph.shape[0]), y): + if v != -1: + assert graph[u, v] + for u, v in zip(x, range(graph.shape[1])): + if u != -1: + assert graph[u, v] + + +def test_matching_large_random_graph_with_one_edge_incident_to_each_vertex(): + np.random.seed(42) + A = diags_array(np.ones(25), offsets=0, format='csr') + rand_perm = np.random.permutation(25) + rand_perm2 = np.random.permutation(25) + + Rrow = np.arange(25) + Rcol = rand_perm + Rdata = np.ones(25, dtype=int) + Rmat = csr_array((Rdata, (Rrow, Rcol))) + + Crow = rand_perm2 + Ccol = np.arange(25) + Cdata = np.ones(25, dtype=int) + Cmat = csr_array((Cdata, (Crow, Ccol))) + # Randomly permute identity matrix + B = Rmat @ A @ Cmat + + # Row permute + perm = maximum_bipartite_matching(B, perm_type='row') + Rrow = np.arange(25) + Rcol = perm + Rdata = np.ones(25, dtype=int) + Rmat = csr_array((Rdata, (Rrow, Rcol))) + C1 = Rmat @ B + + # Column permute + perm2 = maximum_bipartite_matching(B, perm_type='column') + Crow = perm2 + Ccol = np.arange(25) + Cdata = np.ones(25, dtype=int) + Cmat = csr_array((Cdata, (Crow, Ccol))) + C2 = B @ Cmat + + # Should get identity matrix back + assert_equal(any(C1.diagonal() == 0), False) + assert_equal(any(C2.diagonal() == 0), False) + + +@pytest.mark.parametrize('num_rows,num_cols', [(0, 0), (2, 0), (0, 3)]) +def test_min_weight_full_matching_trivial_graph(num_rows, num_cols): + biadjacency = csr_array((num_cols, num_rows)) + biadjacency1 = coo_array((num_cols, num_rows)) + + row_ind, col_ind = min_weight_full_bipartite_matching(biadjacency) + assert len(row_ind) == 0 + assert len(col_ind) == 0 + + row_ind1, col_ind1 = min_weight_full_bipartite_matching(biadjacency1) + assert len(row_ind1) == 0 + assert len(col_ind1) == 0 + + +@pytest.mark.parametrize('biadjacency', + [ + [[1, 1, 1], [1, 0, 0], [1, 0, 0]], + [[1, 1, 1], [0, 0, 1], [0, 0, 1]], + [[1, 0, 0, 1], [1, 1, 0, 1], [0, 0, 0, 0]], + [[1, 0, 0], [2, 0, 0]], + [[0, 1, 0], [0, 2, 0]], + [[1, 0], [2, 0], [5, 0]] + ]) +def test_min_weight_full_matching_infeasible_problems(biadjacency): + with pytest.raises(ValueError): + min_weight_full_bipartite_matching(csr_array(biadjacency)) + with pytest.raises(ValueError): + min_weight_full_bipartite_matching(coo_array(biadjacency)) + + +def test_min_weight_full_matching_large_infeasible(): + # Regression test for GitHub issue #17269 + a = np.asarray([ + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.001, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.001, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.001, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.001, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.001, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.001, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.001, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.001, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.001], + [0.0, 0.11687445, 0.0, 0.0, 0.01319788, 0.07509257, 0.0, + 0.0, 0.0, 0.74228317, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.81087935, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.8408466, 0.0, 0.0, 0.0, 0.0, 0.01194389, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.82994211, 0.0, 0.0, 0.0, 0.11468516, 0.0, 0.0, 0.0, + 0.11173505, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0], + [0.18796507, 0.0, 0.04002318, 0.0, 0.0, 0.0, 0.0, 0.0, 0.75883335, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.71545464, 0.0, 0.0, 0.0, 0.0, 0.0, 0.02748488, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.78470564, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.14829198, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.10870609, 0.0, 0.0, 0.0, 0.8918677, 0.0, 0.0, 0.0, 0.06306644, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.63844085, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.7442354, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.09850549, 0.0, 0.0, 0.18638258, + 0.2769244, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.73182464, 0.0, 0.0, 0.46443561, + 0.38589284, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.29510278, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.09666032, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + ]) + with pytest.raises(ValueError, match='no full matching exists'): + min_weight_full_bipartite_matching(csr_array(a)) + with pytest.raises(ValueError, match='no full matching exists'): + min_weight_full_bipartite_matching(coo_array(a)) + + +def test_explicit_zero_causes_warning(): + biadjacency = csr_array(((2, 0, 3), (0, 1, 1), (0, 2, 3))) + with pytest.warns(UserWarning): + min_weight_full_bipartite_matching(biadjacency) + with pytest.warns(UserWarning): + min_weight_full_bipartite_matching(biadjacency.tocoo()) + + +# General test for linear sum assignment solvers to make it possible to rely +# on the same tests for scipy.optimize.linear_sum_assignment. +def linear_sum_assignment_assertions( + solver, array_type, sign, test_case +): + cost_matrix, expected_cost = test_case + maximize = sign == -1 + cost_matrix = sign * array_type(cost_matrix) + expected_cost = sign * np.array(expected_cost) + + row_ind, col_ind = solver(cost_matrix, maximize=maximize) + assert_array_equal(row_ind, np.sort(row_ind)) + assert_array_equal(expected_cost, + np.array(cost_matrix[row_ind, col_ind]).flatten()) + + cost_matrix = cost_matrix.T + row_ind, col_ind = solver(cost_matrix, maximize=maximize) + assert_array_equal(row_ind, np.sort(row_ind)) + assert_array_equal(np.sort(expected_cost), + np.sort(np.array( + cost_matrix[row_ind, col_ind])).flatten()) + + +linear_sum_assignment_test_cases = list(product( + [-1, 1], + [ + # Square + ([[400, 150, 400], + [400, 450, 600], + [300, 225, 300]], + [150, 400, 300]), + + # Rectangular variant + ([[400, 150, 400, 1], + [400, 450, 600, 2], + [300, 225, 300, 3]], + [150, 2, 300]), + + ([[10, 10, 8], + [9, 8, 1], + [9, 7, 4]], + [10, 1, 7]), + + # Square + ([[10, 10, 8, 11], + [9, 8, 1, 1], + [9, 7, 4, 10]], + [10, 1, 4]), + + # Rectangular variant + ([[10, float("inf"), float("inf")], + [float("inf"), float("inf"), 1], + [float("inf"), 7, float("inf")]], + [10, 1, 7]) + ])) + + +@pytest.mark.parametrize('sign,test_case', linear_sum_assignment_test_cases) +def test_min_weight_full_matching_small_inputs(sign, test_case): + linear_sum_assignment_assertions( + min_weight_full_bipartite_matching, csr_array, sign, test_case) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/test_pydata_sparse.py b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/test_pydata_sparse.py new file mode 100644 index 0000000000000000000000000000000000000000..bc751ecfdee8ca325cc7422cd24526470c6f062a --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/test_pydata_sparse.py @@ -0,0 +1,197 @@ +import pytest + +import numpy as np +import scipy.sparse as sp +import scipy.sparse.csgraph as spgraph +from scipy._external.packaging_version import version + +from numpy.testing import assert_equal + +try: + import sparse +except Exception: + sparse = None + +pytestmark = pytest.mark.skipif(sparse is None, + reason="pydata/sparse not installed") + + +msg = "pydata/sparse (0.15.1) does not implement necessary operations" + + +sparse_params = (pytest.param("COO"), + pytest.param("DOK", marks=[pytest.mark.xfail(reason=msg)])) + + +def check_sparse_version(min_ver): + if sparse is None: + return pytest.mark.skip(reason="sparse is not installed") + return pytest.mark.skipif( + version.parse(sparse.__version__) < version.Version(min_ver), + reason=f"sparse version >= {min_ver} required" + ) + + +@pytest.fixture(params=sparse_params) +def sparse_cls(request): + return getattr(sparse, request.param) + + +@pytest.fixture +def graphs(sparse_cls): + graph = [ + [0, 1, 1, 0, 0], + [0, 0, 1, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 1], + [0, 0, 0, 0, 0], + ] + A_dense = np.array(graph) + A_sparse = sparse_cls(A_dense) + return A_dense, A_sparse + + +@pytest.mark.parametrize( + "func", + [ + spgraph.shortest_path, + spgraph.dijkstra, + spgraph.floyd_warshall, + spgraph.bellman_ford, + spgraph.johnson, + spgraph.reverse_cuthill_mckee, + spgraph.maximum_bipartite_matching, + spgraph.structural_rank, + ] +) +def test_csgraph_equiv(func, graphs): + A_dense, A_sparse = graphs + actual = func(A_sparse) + desired = func(sp.csc_array(A_dense)) + assert_equal(actual, desired) + + +def test_connected_components(graphs): + A_dense, A_sparse = graphs + func = spgraph.connected_components + + actual_comp, actual_labels = func(A_sparse) + desired_comp, desired_labels, = func(sp.csc_array(A_dense)) + + assert actual_comp == desired_comp + assert_equal(actual_labels, desired_labels) + + +def test_laplacian(graphs): + A_dense, A_sparse = graphs + sparse_cls = type(A_sparse) + func = spgraph.laplacian + + actual = func(A_sparse) + desired = func(sp.csc_array(A_dense)) + + assert isinstance(actual, sparse_cls) + + assert_equal(actual.todense(), desired.todense()) + + +@pytest.mark.parametrize( + "func", [spgraph.breadth_first_order, spgraph.depth_first_order] +) +def test_order_search(graphs, func): + A_dense, A_sparse = graphs + + actual = func(A_sparse, 0) + desired = func(sp.csc_array(A_dense), 0) + + assert_equal(actual, desired) + + +@pytest.mark.parametrize( + "func", [spgraph.breadth_first_tree, spgraph.depth_first_tree] +) +def test_tree_search(graphs, func): + A_dense, A_sparse = graphs + sparse_cls = type(A_sparse) + + actual = func(A_sparse, 0) + desired = func(sp.csc_array(A_dense), 0) + + assert isinstance(actual, sparse_cls) + + assert_equal(actual.todense(), desired.todense()) + + +def test_minimum_spanning_tree(graphs): + A_dense, A_sparse = graphs + sparse_cls = type(A_sparse) + func = spgraph.minimum_spanning_tree + + actual = func(A_sparse) + desired = func(sp.csc_array(A_dense)) + + assert isinstance(actual, sparse_cls) + + assert_equal(actual.todense(), desired.todense()) + + +def test_maximum_flow(graphs): + A_dense, A_sparse = graphs + sparse_cls = type(A_sparse) + func = spgraph.maximum_flow + + actual = func(A_sparse, 0, 2) + desired = func(sp.csr_array(A_dense), 0, 2) + + assert actual.flow_value == desired.flow_value + assert isinstance(actual.flow, sparse_cls) + + assert_equal(actual.flow.todense(), desired.flow.todense()) + + +def test_min_weight_full_bipartite_matching(graphs): + A_dense, A_sparse = graphs + func = spgraph.min_weight_full_bipartite_matching + + actual = func(A_sparse[0:2, 1:3]) + A_csc = sp.csc_array(A_dense) + desired = func(A_csc[0:2, 1:3]) + desired1 = func(A_csc[0:2, 1:3].tocoo()) + + assert_equal(actual, desired) + assert_equal(actual, desired1) + + +@check_sparse_version("0.15.4") +@pytest.mark.parametrize( + "func", + [ + spgraph.shortest_path, + spgraph.dijkstra, + spgraph.floyd_warshall, + spgraph.bellman_ford, + spgraph.johnson, + spgraph.minimum_spanning_tree, + ] +) +@pytest.mark.parametrize( + "fill_value, comp_func", + [(np.inf, np.isposinf), (np.nan, np.isnan)], +) +def test_nonzero_fill_value(graphs, func, fill_value, comp_func): + A_dense, A_sparse = graphs + A_sparse = A_sparse.astype(float) + A_sparse.fill_value = fill_value + sparse_cls = type(A_sparse) + + actual = func(A_sparse) + desired = func(sp.csc_array(A_dense)) + + if func == spgraph.minimum_spanning_tree: + assert isinstance(actual, sparse_cls) + assert comp_func(actual.fill_value) + actual = actual.todense() + actual[comp_func(actual)] = 0.0 + assert_equal(actual, desired.todense()) + else: + assert_equal(actual, desired) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/test_reordering.py b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/test_reordering.py new file mode 100644 index 0000000000000000000000000000000000000000..add76cdc29c39c079f386a6ca73075bb90b0a6e8 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/test_reordering.py @@ -0,0 +1,70 @@ +import numpy as np +from numpy.testing import assert_equal +from scipy.sparse.csgraph import reverse_cuthill_mckee, structural_rank +from scipy.sparse import csc_array, csr_array, coo_array + + +def test_graph_reverse_cuthill_mckee(): + A = np.array([[1, 0, 0, 0, 1, 0, 0, 0], + [0, 1, 1, 0, 0, 1, 0, 1], + [0, 1, 1, 0, 1, 0, 0, 0], + [0, 0, 0, 1, 0, 0, 1, 0], + [1, 0, 1, 0, 1, 0, 0, 0], + [0, 1, 0, 0, 0, 1, 0, 1], + [0, 0, 0, 1, 0, 0, 1, 0], + [0, 1, 0, 0, 0, 1, 0, 1]], dtype=int) + + graph = csr_array(A) + perm = reverse_cuthill_mckee(graph) + correct_perm = np.array([6, 3, 7, 5, 1, 2, 4, 0]) + assert_equal(perm, correct_perm) + + # Test int64 indices input + graph.indices = graph.indices.astype('int64') + graph.indptr = graph.indptr.astype('int64') + perm = reverse_cuthill_mckee(graph, True) + assert_equal(perm, correct_perm) + + +def test_graph_reverse_cuthill_mckee_ordering(): + data = np.ones(63,dtype=int) + rows = np.array([0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, + 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, + 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 9, 9, + 9, 10, 10, 10, 10, 10, 11, 11, 11, 11, + 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, + 14, 15, 15, 15, 15, 15]) + cols = np.array([0, 2, 5, 8, 10, 1, 3, 9, 11, 0, 2, + 7, 10, 1, 3, 11, 4, 6, 12, 14, 0, 7, 13, + 15, 4, 6, 14, 2, 5, 7, 15, 0, 8, 10, 13, + 1, 9, 11, 0, 2, 8, 10, 15, 1, 3, 9, 11, + 4, 12, 14, 5, 8, 13, 15, 4, 6, 12, 14, + 5, 7, 10, 13, 15]) + graph = csr_array((data, (rows,cols))) + perm = reverse_cuthill_mckee(graph) + correct_perm = np.array([12, 14, 4, 6, 10, 8, 2, 15, + 0, 13, 7, 5, 9, 11, 1, 3]) + assert_equal(perm, correct_perm) + + +def test_graph_structural_rank(): + # Test square matrix #1 + A = csc_array([[1, 1, 0], + [1, 0, 1], + [0, 1, 0]]) + assert_equal(structural_rank(A), 3) + + # Test square matrix #2 + rows = np.array([0,0,0,0,0,1,1,2,2,3,3,3,3,3,3,4,4,5,5,6,6,7,7]) + cols = np.array([0,1,2,3,4,2,5,2,6,0,1,3,5,6,7,4,5,5,6,2,6,2,4]) + data = np.ones_like(rows) + B = coo_array((data,(rows,cols)), shape=(8,8)) + assert_equal(structural_rank(B), 6) + + #Test non-square matrix + C = csc_array([[1, 0, 2, 0], + [2, 0, 4, 0]]) + assert_equal(structural_rank(C), 2) + + #Test tall matrix + assert_equal(structural_rank(C.T), 2) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/test_shortest_path.py b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/test_shortest_path.py new file mode 100644 index 0000000000000000000000000000000000000000..761a35e1a4ed5e12de3950b399a88b63c8fe6359 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/test_shortest_path.py @@ -0,0 +1,547 @@ +from io import StringIO +import warnings +import numpy as np +from numpy.testing import assert_array_almost_equal, assert_array_equal, assert_allclose +from pytest import raises as assert_raises +from scipy.sparse.csgraph import (shortest_path, dijkstra, johnson, + bellman_ford, construct_dist_matrix, yen, + NegativeCycleError) +import scipy.sparse +from scipy.io import mmread +import pytest + +directed_G = np.array([[0, 3, 3, 0, 0], + [0, 0, 0, 2, 4], + [0, 0, 0, 0, 0], + [1, 0, 0, 0, 0], + [2, 0, 0, 2, 0]], dtype=float) + +# Undirected version of directed_G +undirected_G = np.array([[0, 3, 3, 1, 2], + [3, 0, 0, 2, 4], + [3, 0, 0, 0, 0], + [1, 2, 0, 0, 2], + [2, 4, 0, 2, 0]], dtype=float) + +unweighted_G = (directed_G > 0).astype(float) + +# Correct shortest path lengths for directed_G and undirected_G +directed_SP = [[0, 3, 3, 5, 7], + [3, 0, 6, 2, 4], + [np.inf, np.inf, 0, np.inf, np.inf], + [1, 4, 4, 0, 8], + [2, 5, 5, 2, 0]] + +directed_2SP_0_to_3 = [[-9999, 0, -9999, 1, -9999], + [-9999, 0, -9999, 4, 1]] + +undirected_SP = np.array([[0, 3, 3, 1, 2], + [3, 0, 6, 2, 4], + [3, 6, 0, 4, 5], + [1, 2, 4, 0, 2], + [2, 4, 5, 2, 0]], dtype=float) + +undirected_SP_limit_2 = np.array([[0, np.inf, np.inf, 1, 2], + [np.inf, 0, np.inf, 2, np.inf], + [np.inf, np.inf, 0, np.inf, np.inf], + [1, 2, np.inf, 0, 2], + [2, np.inf, np.inf, 2, 0]], dtype=float) + +undirected_SP_limit_0 = np.ones((5, 5), dtype=float) - np.eye(5) +undirected_SP_limit_0[undirected_SP_limit_0 > 0] = np.inf + +# Correct predecessors for directed_G and undirected_G +directed_pred = np.array([[-9999, 0, 0, 1, 1], + [3, -9999, 0, 1, 1], + [-9999, -9999, -9999, -9999, -9999], + [3, 0, 0, -9999, 1], + [4, 0, 0, 4, -9999]], dtype=float) + +undirected_pred = np.array([[-9999, 0, 0, 0, 0], + [1, -9999, 0, 1, 1], + [2, 0, -9999, 0, 0], + [3, 3, 0, -9999, 3], + [4, 4, 0, 4, -9999]], dtype=float) + +# Other graphs +directed_sparse_zero_G = scipy.sparse.csr_array( + ( + [0, 1, 2, 3, 1], + ([0, 1, 2, 3, 4], [1, 2, 0, 4, 3]), + ), + shape=(5, 5), +) + +directed_sparse_zero_SP = [[0, 0, 1, np.inf, np.inf], + [3, 0, 1, np.inf, np.inf], + [2, 2, 0, np.inf, np.inf], + [np.inf, np.inf, np.inf, 0, 3], + [np.inf, np.inf, np.inf, 1, 0]] + +undirected_sparse_zero_G = scipy.sparse.csr_array( + ( + [0, 0, 1, 1, 2, 2, 1, 1], + ([0, 1, 1, 2, 2, 0, 3, 4], [1, 0, 2, 1, 0, 2, 4, 3]) + ), + shape=(5, 5), +) + +undirected_sparse_zero_SP = [[0, 0, 1, np.inf, np.inf], + [0, 0, 1, np.inf, np.inf], + [1, 1, 0, np.inf, np.inf], + [np.inf, np.inf, np.inf, 0, 1], + [np.inf, np.inf, np.inf, 1, 0]] + +directed_negative_weighted_G = np.array([[0, 0, 0], + [-1, 0, 0], + [0, -1, 0]], dtype=float) + +directed_negative_weighted_SP = np.array([[0, np.inf, np.inf], + [-1, 0, np.inf], + [-2, -1, 0]], dtype=float) + +methods = ['auto', 'FW', 'D', 'BF', 'J'] + + +def test_dijkstra_limit(): + limits = [0, 2, np.inf] + results = [undirected_SP_limit_0, + undirected_SP_limit_2, + undirected_SP] + + def check(limit, result): + SP = dijkstra(undirected_G, directed=False, limit=limit) + assert_array_almost_equal(SP, result) + + for limit, result in zip(limits, results): + check(limit, result) + + +def test_directed(): + def check(method): + SP = shortest_path(directed_G, method=method, directed=True, + overwrite=False) + assert_array_almost_equal(SP, directed_SP) + + for method in methods: + check(method) + + +def test_undirected(): + def check(method, directed_in): + if directed_in: + SP1 = shortest_path(directed_G, method=method, directed=False, + overwrite=False) + assert_array_almost_equal(SP1, undirected_SP) + else: + SP2 = shortest_path(undirected_G, method=method, directed=True, + overwrite=False) + assert_array_almost_equal(SP2, undirected_SP) + + for method in methods: + for directed_in in (True, False): + check(method, directed_in) + + +def test_directed_sparse_zero(): + # test directed sparse graph with zero-weight edge and two connected components + def check(method): + SP = shortest_path(directed_sparse_zero_G, method=method, directed=True, + overwrite=False) + assert_array_almost_equal(SP, directed_sparse_zero_SP) + + for method in methods: + check(method) + + +def test_undirected_sparse_zero(): + def check(method, directed_in): + if directed_in: + SP1 = shortest_path(directed_sparse_zero_G, method=method, directed=False, + overwrite=False) + assert_array_almost_equal(SP1, undirected_sparse_zero_SP) + else: + SP2 = shortest_path(undirected_sparse_zero_G, method=method, directed=True, + overwrite=False) + assert_array_almost_equal(SP2, undirected_sparse_zero_SP) + + for method in methods: + for directed_in in (True, False): + check(method, directed_in) + + +@pytest.mark.parametrize('directed, SP_ans', + ((True, directed_SP), + (False, undirected_SP))) +@pytest.mark.parametrize('indices', ([0, 2, 4], [0, 4], [3, 4], [0, 0])) +def test_dijkstra_indices_min_only(directed, SP_ans, indices): + SP_ans = np.array(SP_ans) + indices = np.array(indices, dtype=np.int64) + min_ind_ans = indices[np.argmin(SP_ans[indices, :], axis=0)] + min_d_ans = np.zeros(SP_ans.shape[0], SP_ans.dtype) + for k in range(SP_ans.shape[0]): + min_d_ans[k] = SP_ans[min_ind_ans[k], k] + min_ind_ans[np.isinf(min_d_ans)] = -9999 + + SP, pred, sources = dijkstra(directed_G, + directed=directed, + indices=indices, + min_only=True, + return_predecessors=True) + assert_array_almost_equal(SP, min_d_ans) + assert_array_equal(min_ind_ans, sources) + SP = dijkstra(directed_G, + directed=directed, + indices=indices, + min_only=True, + return_predecessors=False) + assert_array_almost_equal(SP, min_d_ans) + + +@pytest.mark.parametrize('n', (10, 100, 1000)) +def test_dijkstra_min_only_random(n): + rng = np.random.default_rng(7345782358920239234) + data = scipy.sparse.random_array((n, n), density=0.5, format='lil', + rng=rng, dtype=np.float64) + data.setdiag(np.zeros(n, dtype=np.bool_)) + # choose some random vertices + v = np.arange(n) + rng.shuffle(v) + indices = v[:int(n*.1)] + ds, pred, sources = dijkstra(data, + directed=True, + indices=indices, + min_only=True, + return_predecessors=True) + for k in range(n): + p = pred[k] + s = sources[k] + while p != -9999: + assert sources[p] == s + p = pred[p] + + +@pytest.mark.parametrize('n', (10, 100)) +@pytest.mark.parametrize("method", ['FW', 'J', 'BF']) +@pytest.mark.parametrize('directed', (True, False)) +def test_star_graph(n, method, directed): + # Build the star graph + star_arr = np.zeros((n, n), dtype=float) + star_center_idx = 0 + star_arr[star_center_idx, :] = star_arr[:, star_center_idx] = range(n) + G = scipy.sparse.csr_array(star_arr, shape=(n, n)) + # Build the distances matrix + SP_solution = np.zeros((n, n), dtype=float) + SP_solution[:] = star_arr[star_center_idx] + for idx in range(1, n): + SP_solution[idx] += star_arr[idx, star_center_idx] + np.fill_diagonal(SP_solution, 0) + + SP = shortest_path(G, method=method, directed=directed) + assert_allclose( + SP_solution, SP + ) + + +def test_dijkstra_random(): + # reproduces the hang observed in gh-17782 + n = 10 + indices = [0, 4, 4, 5, 7, 9, 0, 6, 2, 3, 7, 9, 1, 2, 9, 2, 5, 6] + indptr = [0, 0, 2, 5, 6, 7, 8, 12, 15, 18, 18] + data = [0.33629, 0.40458, 0.47493, 0.42757, 0.11497, 0.91653, 0.69084, + 0.64979, 0.62555, 0.743, 0.01724, 0.99945, 0.31095, 0.15557, + 0.02439, 0.65814, 0.23478, 0.24072] + graph = scipy.sparse.csr_array((data, indices, indptr), shape=(n, n)) + dijkstra(graph, directed=True, return_predecessors=True) + + +def test_gh_17782_segfault(): + text = """%%MatrixMarket matrix coordinate real general + 84 84 22 + 2 1 4.699999809265137e+00 + 6 14 1.199999973177910e-01 + 9 6 1.199999973177910e-01 + 10 16 2.012000083923340e+01 + 11 10 1.422000026702881e+01 + 12 1 9.645999908447266e+01 + 13 18 2.012000083923340e+01 + 14 13 4.679999828338623e+00 + 15 11 1.199999973177910e-01 + 16 12 1.199999973177910e-01 + 18 15 1.199999973177910e-01 + 32 2 2.299999952316284e+00 + 33 20 6.000000000000000e+00 + 33 32 5.000000000000000e+00 + 36 9 3.720000028610229e+00 + 36 37 3.720000028610229e+00 + 36 38 3.720000028610229e+00 + 37 44 8.159999847412109e+00 + 38 32 7.903999328613281e+01 + 43 20 2.400000000000000e+01 + 43 33 4.000000000000000e+00 + 44 43 6.028000259399414e+01 + """ + data = mmread(StringIO(text), spmatrix=False) + dijkstra(data, directed=True, return_predecessors=True) + + +def test_shortest_path_indices(): + indices = np.arange(4) + + def check(func, indshape): + outshape = indshape + (5,) + SP = func(directed_G, directed=False, + indices=indices.reshape(indshape)) + assert_array_almost_equal(SP, undirected_SP[indices].reshape(outshape)) + + for indshape in [(4,), (4, 1), (2, 2)]: + for func in (dijkstra, bellman_ford, johnson, shortest_path): + check(func, indshape) + + assert_raises(ValueError, shortest_path, directed_G, method='FW', + indices=indices) + + +def test_predecessors(): + SP_res = {True: directed_SP, + False: undirected_SP} + pred_res = {True: directed_pred, + False: undirected_pred} + + def check(method, directed): + SP, pred = shortest_path(directed_G, method, directed=directed, + overwrite=False, + return_predecessors=True) + assert_array_almost_equal(SP, SP_res[directed]) + assert_array_almost_equal(pred, pred_res[directed]) + + for method in methods: + for directed in (True, False): + check(method, directed) + + +def test_construct_shortest_path(): + def check(method, directed): + SP1, pred = shortest_path(directed_G, + directed=directed, + overwrite=False, + return_predecessors=True) + SP2 = construct_dist_matrix(directed_G, pred, directed=directed) + assert_array_almost_equal(SP1, SP2) + + for method in methods: + for directed in (True, False): + check(method, directed) + +@pytest.mark.parametrize("directed", [True, False]) +def test_construct_dist_matrix_predecessors_error(directed): + SP1, pred = shortest_path(directed_G, + directed=directed, + overwrite=False, + return_predecessors=True) + assert_raises(TypeError, construct_dist_matrix, + directed_G, pred.astype(np.int64), directed) + + +def test_unweighted_path(): + def check(method, directed): + SP1 = shortest_path(directed_G, + directed=directed, + overwrite=False, + unweighted=True) + SP2 = shortest_path(unweighted_G, + directed=directed, + overwrite=False, + unweighted=False) + assert_array_almost_equal(SP1, SP2) + + for method in methods: + for directed in (True, False): + check(method, directed) + + +def test_negative_cycles(): + # create a small graph with a negative cycle + graph = np.ones([5, 5]) + graph.flat[::6] = 0 + graph[1, 2] = -2 + + def check(method, directed): + assert_raises(NegativeCycleError, shortest_path, graph, method, + directed) + + for directed in (True, False): + for method in ['FW', 'J', 'BF']: + check(method, directed) + + assert_raises(NegativeCycleError, yen, graph, 0, 1, 1, + directed=directed) + + +@pytest.mark.parametrize("method", ['FW', 'J', 'BF']) +def test_negative_weights(method): + SP = shortest_path(directed_negative_weighted_G, method, directed=True) + assert_allclose(SP, directed_negative_weighted_SP, atol=1e-10) + + +def test_masked_input(): + np.ma.masked_equal(directed_G, 0) + + def check(method): + SP = shortest_path(directed_G, method=method, directed=True, + overwrite=False) + assert_array_almost_equal(SP, directed_SP) + + for method in methods: + check(method) + + +def test_overwrite(): + G = np.array([[0, 3, 3, 1, 2], + [3, 0, 0, 2, 4], + [3, 0, 0, 0, 0], + [1, 2, 0, 0, 2], + [2, 4, 0, 2, 0]], dtype=float) + foo = G.copy() + shortest_path(foo, overwrite=False) + assert_array_equal(foo, G) + + +@pytest.mark.parametrize('method', methods) +def test_buffer(method): + # Smoke test that sparse matrices with read-only buffers (e.g., those from + # joblib workers) do not cause:: + # + # ValueError: buffer source array is read-only + # + G = scipy.sparse.csr_array([[1.]]) + G.data.flags['WRITEABLE'] = False + shortest_path(G, method=method) + + +def test_NaN_warnings(): + with warnings.catch_warnings(record=True) as record: + shortest_path(np.array([[0, 1], [np.nan, 0]])) + for r in record: + assert r.category is not RuntimeWarning + + +def test_sparse_matrices(): + # Test that using lil,csr and csc sparse matrix do not cause error + G_dense = np.array([[0, 3, 0, 0, 0], + [0, 0, -1, 0, 0], + [0, 0, 0, 2, 0], + [0, 0, 0, 0, 4], + [0, 0, 0, 0, 0]], dtype=float) + SP = shortest_path(G_dense) + G_csr = scipy.sparse.csr_array(G_dense) + G_csc = scipy.sparse.csc_array(G_dense) + G_lil = scipy.sparse.lil_array(G_dense) + assert_array_almost_equal(SP, shortest_path(G_csr)) + assert_array_almost_equal(SP, shortest_path(G_csc)) + assert_array_almost_equal(SP, shortest_path(G_lil)) + + +def test_yen_directed(): + distances, predecessors = yen( + directed_G, + source=0, + sink=3, + K=2, + return_predecessors=True + ) + assert_allclose(distances, [5., 9.]) + assert_allclose(predecessors, directed_2SP_0_to_3) + + +def test_yen_dense(): + dense_undirected_G = np.array([ + [0, 3, 3, 1, 2], + [3, 0, 7, 6, 5], + [3, 7, 0, 4, 0], + [1, 6, 4, 0, 2], + [2, 5, 0, 2, 0]], dtype=float) + distances = yen( + dense_undirected_G, + source=0, + sink=4, + K=5, + directed=False, + ) + assert_allclose(distances, [2., 3., 8., 9., 11.]) + + +def test_yen_undirected(): + distances = yen( + undirected_G, + source=0, + sink=3, + K=4, + directed=False, + ) + assert_allclose(distances, [1., 4., 5., 8.]) + + +def test_yen_unweighted(): + # Ask for more paths than there are, verify only the available paths are returned + distances, predecessors = yen( + directed_G, + source=0, + sink=3, + K=4, + unweighted=True, + return_predecessors=True, + ) + assert_allclose(distances, [2., 3.]) + assert_allclose(predecessors, directed_2SP_0_to_3) + + +def test_yen_no_paths(): + distances = yen( + directed_G, + source=2, + sink=3, + K=1, + ) + assert distances.size == 0 + + +def test_yen_negative_weights(): + distances = yen( + directed_negative_weighted_G, + source=2, + sink=0, + K=1, + ) + assert_allclose(distances, [-2.]) + + +@pytest.mark.parametrize('source, sink', [(0, -1), (10000, 1), (2, 6)]) +def test_yen_source_sink_validation(source, sink): + # directed_G has shape (6, 6) + with pytest.raises(ValueError, match="must have 0 <="): + yen(directed_G, source, sink, 2) + + +@pytest.mark.parametrize("min_only", (True, False)) +@pytest.mark.parametrize("directed", (True, False)) +@pytest.mark.parametrize("return_predecessors", (True, False)) +@pytest.mark.parametrize("index_dtype", (np.int32, np.int64)) +@pytest.mark.parametrize("indices", (None, [1])) +def test_20904(min_only, directed, return_predecessors, index_dtype, indices): + """Test two failures from gh-20904: int32 and indices-as-None.""" + adj_mat = scipy.sparse.eye_array(4, format="csr") + adj_mat = scipy.sparse.csr_array( + ( + adj_mat.data, + adj_mat.indices.astype(index_dtype), + adj_mat.indptr.astype(index_dtype), + ), + ) + dijkstra( + adj_mat, + directed, + indices=indices, + min_only=min_only, + return_predecessors=return_predecessors, + ) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/test_spanning_tree.py b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/test_spanning_tree.py new file mode 100644 index 0000000000000000000000000000000000000000..3237a14584d42022184a54f174b809a2b06d16ed --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/test_spanning_tree.py @@ -0,0 +1,66 @@ +"""Test the minimum spanning tree function""" +import numpy as np +from numpy.testing import assert_ +import numpy.testing as npt +from scipy.sparse import csr_array +from scipy.sparse.csgraph import minimum_spanning_tree + + +def test_minimum_spanning_tree(): + + # Create a graph with two connected components. + graph = [[0,1,0,0,0], + [1,0,0,0,0], + [0,0,0,8,5], + [0,0,8,0,1], + [0,0,5,1,0]] + graph = np.asarray(graph) + + # Create the expected spanning tree. + expected = [[0,1,0,0,0], + [0,0,0,0,0], + [0,0,0,0,5], + [0,0,0,0,1], + [0,0,0,0,0]] + expected = np.asarray(expected) + + # Ensure minimum spanning tree code gives this expected output. + csgraph = csr_array(graph) + mintree = minimum_spanning_tree(csgraph) + mintree_array = mintree.toarray() + npt.assert_array_equal(mintree_array, expected, + 'Incorrect spanning tree found.') + + # Ensure that the original graph was not modified. + npt.assert_array_equal(csgraph.toarray(), graph, + 'Original graph was modified.') + + # Now let the algorithm modify the csgraph in place. + mintree = minimum_spanning_tree(csgraph, overwrite=True) + npt.assert_array_equal(mintree.toarray(), expected, + 'Graph was not properly modified to contain MST.') + + np.random.seed(1234) + for N in (5, 10, 15, 20): + + # Create a random graph. + graph = 3 + np.random.random((N, N)) + csgraph = csr_array(graph) + + # The spanning tree has at most N - 1 edges. + mintree = minimum_spanning_tree(csgraph) + assert_(mintree.nnz < N) + + # Set the sub diagonal to 1 to create a known spanning tree. + idx = np.arange(N-1) + graph[idx,idx+1] = 1 + csgraph = csr_array(graph) + mintree = minimum_spanning_tree(csgraph) + + # We expect to see this pattern in the spanning tree and otherwise + # have this zero. + expected = np.zeros((N, N)) + expected[idx, idx+1] = 1 + + npt.assert_array_equal(mintree.toarray(), expected, + 'Incorrect spanning tree found.') diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/test_traversal.py b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/test_traversal.py new file mode 100644 index 0000000000000000000000000000000000000000..1716cf044d5fa27b833225c9a8675f02944f132e --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/csgraph/tests/test_traversal.py @@ -0,0 +1,150 @@ +import warnings +import numpy as np +import pytest +from numpy.testing import assert_array_almost_equal +from scipy.sparse import csr_array, csr_matrix, coo_array, coo_matrix +from scipy.sparse.csgraph import (breadth_first_tree, depth_first_tree, + csgraph_to_dense, csgraph_from_dense, csgraph_masked_from_dense) + + +def test_graph_breadth_first(): + csgraph = np.array([[0, 1, 2, 0, 0], + [1, 0, 0, 0, 3], + [2, 0, 0, 7, 0], + [0, 0, 7, 0, 1], + [0, 3, 0, 1, 0]]) + csgraph = csgraph_from_dense(csgraph, null_value=0) + + bfirst = np.array([[0, 1, 2, 0, 0], + [0, 0, 0, 0, 3], + [0, 0, 0, 7, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0]]) + + for directed in [True, False]: + bfirst_test = breadth_first_tree(csgraph, 0, directed) + assert_array_almost_equal(csgraph_to_dense(bfirst_test), + bfirst) + + +def test_graph_depth_first(): + csgraph = np.array([[0, 1, 2, 0, 0], + [1, 0, 0, 0, 3], + [2, 0, 0, 7, 0], + [0, 0, 7, 0, 1], + [0, 3, 0, 1, 0]]) + csgraph = csgraph_from_dense(csgraph, null_value=0) + + dfirst = np.array([[0, 1, 0, 0, 0], + [0, 0, 0, 0, 3], + [0, 0, 0, 0, 0], + [0, 0, 7, 0, 0], + [0, 0, 0, 1, 0]]) + + for directed in [True, False]: + dfirst_test = depth_first_tree(csgraph, 0, directed) + assert_array_almost_equal(csgraph_to_dense(dfirst_test), dfirst) + + +def test_return_type(): + from .._laplacian import laplacian + from .._min_spanning_tree import minimum_spanning_tree + + np_csgraph = np.array([[0, 1, 2, 0, 0], + [1, 0, 0, 0, 3], + [2, 0, 0, 7, 0], + [0, 0, 7, 0, 1], + [0, 3, 0, 1, 0]]) + csgraph = csr_array(np_csgraph) + assert isinstance(laplacian(csgraph), coo_array) + assert isinstance(minimum_spanning_tree(csgraph), csr_array) + for directed in [True, False]: + assert isinstance(depth_first_tree(csgraph, 0, directed), csr_array) + assert isinstance(breadth_first_tree(csgraph, 0, directed), csr_array) + + csgraph = csgraph_from_dense(np_csgraph, null_value=0) + assert isinstance(csgraph, csr_array) + assert isinstance(laplacian(csgraph), coo_array) + assert isinstance(minimum_spanning_tree(csgraph), csr_array) + for directed in [True, False]: + assert isinstance(depth_first_tree(csgraph, 0, directed), csr_array) + assert isinstance(breadth_first_tree(csgraph, 0, directed), csr_array) + + csgraph = csgraph_masked_from_dense(np_csgraph, null_value=0) + assert isinstance(csgraph, np.ma.MaskedArray) + assert csgraph._baseclass is np.ndarray + # laplacian doesnt work with masked arrays so not here + assert isinstance(minimum_spanning_tree(csgraph), csr_array) + for directed in [True, False]: + assert isinstance(depth_first_tree(csgraph, 0, directed), csr_array) + assert isinstance(breadth_first_tree(csgraph, 0, directed), csr_array) + + # start of testing with matrix/spmatrix types + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", "the matrix subclass.*", DeprecationWarning) + warnings.filterwarnings( + "ignore", "the matrix subclass.*", PendingDeprecationWarning) + + nm_csgraph = np.matrix([[0, 1, 2, 0, 0], + [1, 0, 0, 0, 3], + [2, 0, 0, 7, 0], + [0, 0, 7, 0, 1], + [0, 3, 0, 1, 0]]) + + csgraph = csr_matrix(nm_csgraph) + assert isinstance(laplacian(csgraph), coo_matrix) + assert isinstance(minimum_spanning_tree(csgraph), csr_matrix) + for directed in [True, False]: + assert isinstance(depth_first_tree(csgraph, 0, directed), csr_matrix) + assert isinstance(breadth_first_tree(csgraph, 0, directed), csr_matrix) + + csgraph = csgraph_from_dense(nm_csgraph, null_value=0) + assert isinstance(csgraph, csr_matrix) + assert isinstance(laplacian(csgraph), coo_matrix) + assert isinstance(minimum_spanning_tree(csgraph), csr_matrix) + for directed in [True, False]: + assert isinstance(depth_first_tree(csgraph, 0, directed), csr_matrix) + assert isinstance(breadth_first_tree(csgraph, 0, directed), csr_matrix) + + mm_csgraph = csgraph_masked_from_dense(nm_csgraph, null_value=0) + assert isinstance(mm_csgraph, np.ma.MaskedArray) + # laplacian doesnt work with masked arrays so not here + assert isinstance(minimum_spanning_tree(csgraph), csr_matrix) + for directed in [True, False]: + assert isinstance(depth_first_tree(csgraph, 0, directed), csr_matrix) + assert isinstance(breadth_first_tree(csgraph, 0, directed), csr_matrix) + # end of testing with matrix/spmatrix types + + +def test_graph_breadth_first_trivial_graph(): + csgraph = np.array([[0]]) + csgraph = csgraph_from_dense(csgraph, null_value=0) + + bfirst = np.array([[0]]) + + for directed in [True, False]: + bfirst_test = breadth_first_tree(csgraph, 0, directed) + assert_array_almost_equal(csgraph_to_dense(bfirst_test), bfirst) + + +def test_graph_depth_first_trivial_graph(): + csgraph = np.array([[0]]) + csgraph = csgraph_from_dense(csgraph, null_value=0) + + bfirst = np.array([[0]]) + + for directed in [True, False]: + bfirst_test = depth_first_tree(csgraph, 0, directed) + assert_array_almost_equal(csgraph_to_dense(bfirst_test), + bfirst) + + +@pytest.mark.parametrize('directed', [True, False]) +@pytest.mark.parametrize('tree_func', [breadth_first_tree, depth_first_tree]) +def test_int64_indices(tree_func, directed): + # See https://github.com/scipy/scipy/issues/18716 + g = csr_array(([1], np.array([[0], [1]], dtype=np.int64)), shape=(2, 2)) + assert g.indices.dtype == np.int64 + tree = tree_func(g, 0, directed=directed) + assert_array_almost_equal(csgraph_to_dense(tree), [[0, 1], [0, 0]]) + diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/csr.py b/venv/lib/python3.12/site-packages/scipy/sparse/csr.py new file mode 100644 index 0000000000000000000000000000000000000000..62cb27795f8e70b21b1864d8066aea12f45d0693 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/csr.py @@ -0,0 +1,22 @@ +# This file is not meant for public use and will be removed in SciPy v2.0.0. +# Use the `scipy.sparse` namespace for importing the functions +# included below. + +from scipy._lib.deprecation import _sub_module_deprecation + + +__all__ = [ # noqa: F822 + 'csr_matrix', + 'isspmatrix_csr', + 'spmatrix', +] + + +def __dir__(): + return __all__ + + +def __getattr__(name): + return _sub_module_deprecation(sub_package="sparse", module="csr", + private_modules=["_csr"], all=__all__, + attribute=name) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/data.py b/venv/lib/python3.12/site-packages/scipy/sparse/data.py new file mode 100644 index 0000000000000000000000000000000000000000..b020b3e1b657b8b904ac437d347a062bb355ecf2 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/data.py @@ -0,0 +1,18 @@ +# This file is not meant for public use and will be removed in SciPy v2.0.0. +# Use the `scipy.sparse` namespace for importing the functions +# included below. + +from scipy._lib.deprecation import _sub_module_deprecation + + +__all__ : list[str] = [] + + +def __dir__(): + return __all__ + + +def __getattr__(name): + return _sub_module_deprecation(sub_package="sparse", module="data", + private_modules=["_data"], all=__all__, + attribute=name) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/dia.py b/venv/lib/python3.12/site-packages/scipy/sparse/dia.py new file mode 100644 index 0000000000000000000000000000000000000000..55f4443ad7f7bd0007b25f1223459a3ee5a89748 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/dia.py @@ -0,0 +1,22 @@ +# This file is not meant for public use and will be removed in SciPy v2.0.0. +# Use the `scipy.sparse` namespace for importing the functions +# included below. + +from scipy._lib.deprecation import _sub_module_deprecation + + +__all__ = [ # noqa: F822 + 'dia_matrix', + 'isspmatrix_dia', + 'spmatrix', +] + + +def __dir__(): + return __all__ + + +def __getattr__(name): + return _sub_module_deprecation(sub_package="sparse", module="dia", + private_modules=["_dia"], all=__all__, + attribute=name) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/dok.py b/venv/lib/python3.12/site-packages/scipy/sparse/dok.py new file mode 100644 index 0000000000000000000000000000000000000000..6672823ef28e6a0f9e9c8a481fc851b13aaf4158 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/dok.py @@ -0,0 +1,22 @@ +# This file is not meant for public use and will be removed in SciPy v2.0.0. +# Use the `scipy.sparse` namespace for importing the functions +# included below. + +from scipy._lib.deprecation import _sub_module_deprecation + + +__all__ = [ # noqa: F822 + 'dok_matrix', + 'isspmatrix_dok', + 'spmatrix', +] + + +def __dir__(): + return __all__ + + +def __getattr__(name): + return _sub_module_deprecation(sub_package="sparse", module="dok", + private_modules=["_dok"], all=__all__, + attribute=name) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/extract.py b/venv/lib/python3.12/site-packages/scipy/sparse/extract.py new file mode 100644 index 0000000000000000000000000000000000000000..be5e161b6f99e57e2b2a6b3d4f1ef6427c07658d --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/extract.py @@ -0,0 +1,23 @@ +# This file is not meant for public use and will be removed in SciPy v2.0.0. +# Use the `scipy.sparse` namespace for importing the functions +# included below. + +from scipy._lib.deprecation import _sub_module_deprecation + + +__all__ = [ # noqa: F822 + 'coo_matrix', + 'find', + 'tril', + 'triu', +] + + +def __dir__(): + return __all__ + + +def __getattr__(name): + return _sub_module_deprecation(sub_package="sparse", module="extract", + private_modules=["_extract"], all=__all__, + attribute=name) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/lil.py b/venv/lib/python3.12/site-packages/scipy/sparse/lil.py new file mode 100644 index 0000000000000000000000000000000000000000..5f7bf8eb03bb36a1b2fa77c5fc0840e532ab64fd --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/lil.py @@ -0,0 +1,22 @@ +# This file is not meant for public use and will be removed in SciPy v2.0.0. +# Use the `scipy.sparse` namespace for importing the functions +# included below. + +from scipy._lib.deprecation import _sub_module_deprecation + + +__all__ = [ # noqa: F822 + 'isspmatrix_lil', + 'lil_array', + 'lil_matrix', +] + + +def __dir__(): + return __all__ + + +def __getattr__(name): + return _sub_module_deprecation(sub_package="sparse", module="lil", + private_modules=["_lil"], all=__all__, + attribute=name) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/__init__.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e32dd362f8d45f747642719cb60eab8def9af43b --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/__init__.py @@ -0,0 +1,150 @@ +""" +Sparse linear algebra (:mod:`scipy.sparse.linalg`) +================================================== + +.. currentmodule:: scipy.sparse.linalg + +Abstract linear operators +------------------------- + +.. autosummary:: + :toctree: generated/ + + LinearOperator -- abstract representation of a linear operator + aslinearoperator -- convert an object to an abstract linear operator + +Matrix Operations +----------------- + +.. autosummary:: + :toctree: generated/ + + inv -- compute the sparse matrix inverse + expm -- compute the sparse matrix exponential + expm_multiply -- compute the product of a matrix exponential and a matrix + funm_multiply_krylov -- use a Krylov method to compute f(A)b for a general f + matrix_power -- compute the matrix power by raising a matrix to an exponent + +Matrix norms +------------ + +.. autosummary:: + :toctree: generated/ + + norm -- Norm of a sparse matrix + onenormest -- Estimate the 1-norm of a sparse matrix + +Solving linear problems +----------------------- + +Direct methods for linear equation systems: + +.. autosummary:: + :toctree: generated/ + + spsolve -- Solve the sparse linear system Ax=b + spsolve_triangular -- Solve sparse linear system Ax=b for a triangular A. + is_sptriangular -- Check if sparse A is triangular. + spbandwidth -- Find the bandwidth of a sparse matrix. + factorized -- Pre-factorize matrix to a function solving a linear system + MatrixRankWarning -- Warning on exactly singular matrices + use_solver -- Select direct solver to use + +Iterative methods for linear equation systems: + +.. autosummary:: + :toctree: generated/ + + bicg -- Use BIConjugate Gradient iteration to solve Ax = b + bicgstab -- Use BIConjugate Gradient STABilized iteration to solve Ax = b + cg -- Use Conjugate Gradient iteration to solve Ax = b + cgs -- Use Conjugate Gradient Squared iteration to solve Ax = b + gmres -- Use Generalized Minimal RESidual iteration to solve Ax = b + lgmres -- Solve a matrix equation using the LGMRES algorithm + minres -- Use MINimum RESidual iteration to solve Ax = b + qmr -- Use Quasi-Minimal Residual iteration to solve Ax = b + gcrotmk -- Solve a matrix equation using the GCROT(m,k) algorithm + tfqmr -- Use Transpose-Free Quasi-Minimal Residual iteration to solve Ax = b + +Iterative methods for least-squares problems: + +.. autosummary:: + :toctree: generated/ + + lsqr -- Find the least-squares solution to a sparse linear equation system + lsmr -- Find the least-squares solution to a sparse linear equation system + +Matrix factorizations +--------------------- + +Eigenvalue problems: + +.. autosummary:: + :toctree: generated/ + + eigs -- Find k eigenvalues and eigenvectors of the square matrix A + eigsh -- Find k eigenvalues and eigenvectors of a symmetric matrix + lobpcg -- Solve symmetric partial eigenproblems with optional preconditioning + +Singular values problems: + +.. autosummary:: + :toctree: generated/ + + svds -- Compute k singular values/vectors for a sparse matrix + +The `svds` function supports the following solvers: + +.. toctree:: + + sparse.linalg.svds-arpack + sparse.linalg.svds-lobpcg + sparse.linalg.svds-propack + +Complete or incomplete LU factorizations + +.. autosummary:: + :toctree: generated/ + + splu -- Compute a LU decomposition for a sparse matrix + spilu -- Compute an incomplete LU decomposition for a sparse matrix + SuperLU -- Object representing an LU factorization + +Sparse arrays with structure +---------------------------- + +.. autosummary:: + :toctree: generated/ + + LaplacianNd -- Laplacian on a uniform rectangular grid in ``N`` dimensions + +Exceptions +---------- + +.. autosummary:: + :toctree: generated/ + + ArpackNoConvergence + ArpackError + +""" + +from ._isolve import * +from ._dsolve import * +from ._interface import * +from ._eigen import * +from ._matfuncs import * +from ._onenormest import * +from ._norm import * +from ._expm_multiply import * +from ._funm_multiply_krylov import * +from ._special_sparse_arrays import * + +# Deprecated namespaces, to be removed in v2.0.0 +from . import isolve, dsolve, interface, eigen, matfuncs + +__all__ = [s for s in dir() if not s.startswith('_')] + +from scipy._lib._testutils import PytestTester +test = PytestTester(__name__) +del PytestTester diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5059f62b3bd61a464965402e11c116e8452c2513 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/__pycache__/__init__.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/__pycache__/_expm_multiply.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/__pycache__/_expm_multiply.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b4ed9197c8a589725ac037273e054058cc7b792b Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/__pycache__/_expm_multiply.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/__pycache__/_funm_multiply_krylov.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/__pycache__/_funm_multiply_krylov.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3ec46416dbf5cdb97be6715dae293e810f60e457 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/__pycache__/_funm_multiply_krylov.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/__pycache__/_interface.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/__pycache__/_interface.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a01b611d8c593ee0156294125c7c5962afb53d94 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/__pycache__/_interface.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/__pycache__/_matfuncs.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/__pycache__/_matfuncs.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c65c528320936271dc291b7ce2f0d867b689ef8a Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/__pycache__/_matfuncs.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/__pycache__/_norm.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/__pycache__/_norm.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2a5e51d697919edf408f80d766636da6d21f0de7 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/__pycache__/_norm.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/__pycache__/_onenormest.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/__pycache__/_onenormest.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9a94203a07ce11c246b43c8e1c121101d7507faf Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/__pycache__/_onenormest.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/__pycache__/_special_sparse_arrays.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/__pycache__/_special_sparse_arrays.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..51a381b6675fb338b249c6362496f9f97d69386d Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/__pycache__/_special_sparse_arrays.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/__pycache__/_svdp.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/__pycache__/_svdp.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..be5dd979fdd8fdee984c9dfa88f1692ff594a530 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/__pycache__/_svdp.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/__pycache__/dsolve.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/__pycache__/dsolve.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e97105588eb7170e7b01544175dd770f7c7348a8 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/__pycache__/dsolve.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/__pycache__/eigen.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/__pycache__/eigen.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..98f919ef6e8203e25bebe5c3e9d1414c00aa9439 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/__pycache__/eigen.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/__pycache__/interface.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/__pycache__/interface.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..793ffa35944185e8251eb33991ce2206b96a8b99 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/__pycache__/interface.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/__pycache__/isolve.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/__pycache__/isolve.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7efb27a65e05aa5db6a9ccd51eddda2bb367dbf0 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/__pycache__/isolve.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/__pycache__/matfuncs.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/__pycache__/matfuncs.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7f720e11f2633468faa0cbc8ff49f2a0d320b583 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/__pycache__/matfuncs.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_dsolve/__init__.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_dsolve/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..90005e3af863d2f7913e6fc4ea8de85962a3efdf --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_dsolve/__init__.py @@ -0,0 +1,71 @@ +""" +Linear Solvers +============== + +The default solver is SuperLU (included in the scipy distribution), +which can solve real or complex linear systems in both single and +double precisions. It is automatically replaced by UMFPACK, if +available. Note that UMFPACK works in double precision only, so +switch it off by:: + + >>> from scipy.sparse.linalg import spsolve, use_solver + >>> use_solver(useUmfpack=False) + +to solve in the single precision. See also use_solver documentation. + +Example session:: + + >>> from scipy.sparse import csc_array, dia_array + >>> from numpy import array + >>> + >>> print("Inverting a sparse linear system:") + >>> print("The sparse matrix (constructed from diagonals):") + >>> a = dia_array(([[1, 2, 3, 4, 5], [6, 5, 8, 9, 10]], [0, 1]), shape=(5, 5)) + >>> b = array([1, 2, 3, 4, 5]) + >>> print("Solve: single precision complex:") + >>> use_solver( useUmfpack = False ) + >>> a = a.astype('F') + >>> x = spsolve(a, b) + >>> print(x) + >>> print("Error: ", a@x-b) + >>> + >>> print("Solve: double precision complex:") + >>> use_solver( useUmfpack = True ) + >>> a = a.astype('D') + >>> x = spsolve(a, b) + >>> print(x) + >>> print("Error: ", a@x-b) + >>> + >>> print("Solve: double precision:") + >>> a = a.astype('d') + >>> x = spsolve(a, b) + >>> print(x) + >>> print("Error: ", a@x-b) + >>> + >>> print("Solve: single precision:") + >>> use_solver( useUmfpack = False ) + >>> a = a.astype('f') + >>> x = spsolve(a, b.astype('f')) + >>> print(x) + >>> print("Error: ", a@x-b) + +""" + +#import umfpack +#__doc__ = '\n\n'.join( (__doc__, umfpack.__doc__) ) +#del umfpack + +from .linsolve import * +from ._superlu import SuperLU +from . import _add_newdocs +from . import linsolve + +__all__ = [ + 'MatrixRankWarning', 'SuperLU', 'factorized', + 'spilu', 'splu', 'spsolve', 'is_sptriangular', + 'spsolve_triangular', 'use_solver', 'spbandwidth', +] + +from scipy._lib._testutils import PytestTester +test = PytestTester(__name__) +del PytestTester diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_dsolve/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_dsolve/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..42e8de4dd712067f3190357a630d2eeb491b583f Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_dsolve/__pycache__/__init__.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_dsolve/__pycache__/_add_newdocs.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_dsolve/__pycache__/_add_newdocs.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f07e8eafbe90cb4813f316635ab6f53ddc3cbfff Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_dsolve/__pycache__/_add_newdocs.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_dsolve/__pycache__/linsolve.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_dsolve/__pycache__/linsolve.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a94e8f91a80b5ece204f2d497c78ea2f18a9a353 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_dsolve/__pycache__/linsolve.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_dsolve/_add_newdocs.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_dsolve/_add_newdocs.py new file mode 100644 index 0000000000000000000000000000000000000000..cec34dca456b36a1f77e64f853fc1d821f5215eb --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_dsolve/_add_newdocs.py @@ -0,0 +1,147 @@ +from numpy.lib import add_newdoc + +add_newdoc('scipy.sparse.linalg._dsolve._superlu', 'SuperLU', + """ + LU factorization of a sparse matrix. + + Factorization is represented as:: + + Pr @ A @ Pc = L @ U + + To construct these `SuperLU` objects, call the `splu` and `spilu` + functions. + + Attributes + ---------- + shape + nnz + perm_c + perm_r + L + U + + Methods + ------- + solve + + Notes + ----- + + .. versionadded:: 0.14.0 + + Examples + -------- + The LU decomposition can be used to solve matrix equations. Consider: + + >>> import numpy as np + >>> from scipy.sparse import csc_array + >>> from scipy.sparse.linalg import splu + >>> A = csc_array([[1,2,0,4], [1,0,0,1], [1,0,2,1], [2,2,1,0.]]) + + This can be solved for a given right-hand side: + + >>> lu = splu(A) + >>> b = np.array([1, 2, 3, 4]) + >>> x = lu.solve(b) + >>> A.dot(x) + array([ 1., 2., 3., 4.]) + + The ``lu`` object also contains an explicit representation of the + decomposition. The permutations are represented as mappings of + indices: + + >>> lu.perm_r + array([2, 1, 3, 0], dtype=int32) # may vary + >>> lu.perm_c + array([0, 1, 3, 2], dtype=int32) # may vary + + The L and U factors are sparse matrices in CSC format: + + >>> lu.L.toarray() + array([[ 1. , 0. , 0. , 0. ], # may vary + [ 0.5, 1. , 0. , 0. ], + [ 0.5, -1. , 1. , 0. ], + [ 0.5, 1. , 0. , 1. ]]) + >>> lu.U.toarray() + array([[ 2. , 2. , 0. , 1. ], # may vary + [ 0. , -1. , 1. , -0.5], + [ 0. , 0. , 5. , -1. ], + [ 0. , 0. , 0. , 2. ]]) + + The permutation matrices can be constructed: + + >>> Pr = csc_array((np.ones(4), (lu.perm_r, np.arange(4)))) + >>> Pc = csc_array((np.ones(4), (np.arange(4), lu.perm_c))) + + We can reassemble the original matrix: + + >>> (Pr.T @ (lu.L @ lu.U) @ Pc.T).toarray() + array([[ 1., 2., 0., 4.], + [ 1., 0., 0., 1.], + [ 1., 0., 2., 1.], + [ 2., 2., 1., 0.]]) + """) + +add_newdoc('scipy.sparse.linalg._dsolve._superlu', 'SuperLU', ('solve', + """ + solve(rhs[, trans]) + + Solves linear system of equations with one or several right-hand sides. + + Parameters + ---------- + rhs : ndarray, shape (n,) or (n, k) + Right hand side(s) of equation + trans : {'N', 'T', 'H'}, optional + Type of system to solve:: + + 'N': A @ x == rhs (default) + 'T': A^T @ x == rhs + 'H': A^H @ x == rhs + + i.e., normal, transposed, and hermitian conjugate. + + Returns + ------- + x : ndarray, shape ``rhs.shape`` + Solution vector(s) + """)) + +add_newdoc('scipy.sparse.linalg._dsolve._superlu', 'SuperLU', ('L', + """ + Lower triangular factor with unit diagonal as a + `scipy.sparse.csc_array`. + + .. versionadded:: 0.14.0 + """)) + +add_newdoc('scipy.sparse.linalg._dsolve._superlu', 'SuperLU', ('U', + """ + Upper triangular factor as a `scipy.sparse.csc_array`. + + .. versionadded:: 0.14.0 + """)) + +add_newdoc('scipy.sparse.linalg._dsolve._superlu', 'SuperLU', ('shape', + """ + Shape of the original matrix as a tuple of ints. + """)) + +add_newdoc('scipy.sparse.linalg._dsolve._superlu', 'SuperLU', ('nnz', + """ + Number of nonzero elements in the matrix. + """)) + +add_newdoc('scipy.sparse.linalg._dsolve._superlu', 'SuperLU', ('perm_c', + """ + Permutation Pc represented as an array of indices. + + See the `SuperLU` docstring for details. + """)) + +add_newdoc('scipy.sparse.linalg._dsolve._superlu', 'SuperLU', ('perm_r', + """ + Permutation Pr represented as an array of indices. + + See the `SuperLU` docstring for details. + """)) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_dsolve/_superlu.cpython-312-x86_64-linux-gnu.so b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_dsolve/_superlu.cpython-312-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..110fe94ce6720de405b20cd8d3ea254a4be09600 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_dsolve/_superlu.cpython-312-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b7cc362fe1b3a25a1f1c0fa36b5350869901273c79b8692392cc2f8ef273dc54 +size 436625 diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_dsolve/linsolve.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_dsolve/linsolve.py new file mode 100644 index 0000000000000000000000000000000000000000..f7012ed0f961c3ff520382a9cca211d7610575d9 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_dsolve/linsolve.py @@ -0,0 +1,933 @@ +from warnings import warn, catch_warnings, simplefilter + +import numpy as np +from numpy import asarray +from scipy.sparse import (issparse, SparseEfficiencyWarning, + csr_array, csc_array, eye_array, diags_array) +from scipy.sparse._sputils import (is_pydata_spmatrix, convert_pydata_sparse_to_scipy, + get_index_dtype, safely_cast_index_arrays) +from scipy.linalg import LinAlgError +import copy +import threading + +from . import _superlu + +noScikit = False +try: + import scikits.umfpack as umfpack +except ImportError: + noScikit = True + +useUmfpack = threading.local() + + +__all__ = ['use_solver', 'spsolve', 'splu', 'spilu', 'factorized', + 'MatrixRankWarning', 'spsolve_triangular', 'is_sptriangular', 'spbandwidth'] + + +class MatrixRankWarning(UserWarning): + """Warning for exactly singular matrices.""" + pass + + +def use_solver(**kwargs): + """ + Select default sparse direct solver to be used. + + Parameters + ---------- + **kwargs + The following options may be passed as keyword arguments. + + useUmfpack : bool, optional + Use UMFPACK [1]_, [2]_, [3]_, [4]_. over SuperLU. Has effect only + if ``scikits.umfpack`` is installed. Default: True + assumeSortedIndices : bool, optional + Allow UMFPACK to skip the step of sorting indices for a CSR/CSC matrix. + Has effect only if useUmfpack is True and ``scikits.umfpack`` is + installed. Default: False + + Notes + ----- + The default sparse solver is UMFPACK when available + (``scikits.umfpack`` is installed). This can be changed by passing + useUmfpack = False, which then causes the always present SuperLU + based solver to be used. + + UMFPACK requires a CSR/CSC matrix to have sorted column/row indices. If + sure that the matrix fulfills this, pass ``assumeSortedIndices=True`` + to gain some speed. + + References + ---------- + .. [1] T. A. Davis, Algorithm 832: UMFPACK - an unsymmetric-pattern + multifrontal method with a column pre-ordering strategy, ACM + Trans. on Mathematical Software, 30(2), 2004, pp. 196--199. + :doi:`10.1145/992200.992206`. + + .. [2] T. A. Davis, A column pre-ordering strategy for the + unsymmetric-pattern multifrontal method, ACM Trans. + on Mathematical Software, 30(2), 2004, pp. 165--195. + :doi:`10.1145/992200.992205`. + + .. [3] T. A. Davis and I. S. Duff, A combined unifrontal/multifrontal + method for unsymmetric sparse matrices, ACM Trans. on + Mathematical Software, 25(1), 1999, pp. 1--19. + :doi:`10.1145/305658.287640`. + + .. [4] T. A. Davis and I. S. Duff, An unsymmetric-pattern multifrontal + method for sparse LU factorization, SIAM J. Matrix Analysis and + Computations, 18(1), 1997, pp. 140--158. + :doi:`10.1137/S0895479894246905T`. + + Examples + -------- + >>> import numpy as np + >>> from scipy.sparse.linalg import use_solver, spsolve + >>> from scipy.sparse import csc_array + >>> R = np.random.randn(5, 5) + >>> A = csc_array(R) + >>> b = np.random.randn(5) + >>> use_solver(useUmfpack=False) # enforce superLU over UMFPACK + >>> x = spsolve(A, b) + >>> np.allclose(A.dot(x), b) + True + >>> use_solver(useUmfpack=True) # reset umfPack usage to default + """ + global useUmfpack + if 'useUmfpack' in kwargs: + useUmfpack.u = kwargs['useUmfpack'] + if useUmfpack.u and 'assumeSortedIndices' in kwargs: + umfpack.configure(assumeSortedIndices=kwargs['assumeSortedIndices']) + +def _get_umf_family(A): + """Get umfpack family string given the sparse matrix dtype.""" + _families = { + (np.float64, np.int32): 'di', + (np.complex128, np.int32): 'zi', + (np.float64, np.int64): 'dl', + (np.complex128, np.int64): 'zl' + } + + # A.dtype.name can only be "float64" or + # "complex128" in control flow + f_type = getattr(np, A.dtype.name) + # control flow may allow for more index + # types to get through here + i_type = getattr(np, A.indices.dtype.name) + + try: + family = _families[(f_type, i_type)] + + except KeyError as e: + msg = ('only float64 or complex128 matrices with int32 or int64 ' + f'indices are supported! (got: matrix: {f_type}, indices: {i_type})') + raise ValueError(msg) from e + + # See gh-8278. Considered converting only if + # A.shape[0]*A.shape[1] > np.iinfo(np.int32).max, + # but that didn't always fix the issue. + family = family[0] + "l" + A_new = copy.copy(A) + A_new.indptr = np.asarray(A.indptr, dtype=np.int64) + A_new.indices = np.asarray(A.indices, dtype=np.int64) + + return family, A_new + +def spsolve(A, b, permc_spec=None, use_umfpack=True): + """Solve the sparse linear system Ax=b, where b may be a vector or a matrix. + + Parameters + ---------- + A : ndarray or sparse array or matrix + The square matrix A will be converted into CSC or CSR form + b : ndarray or sparse array or matrix + The matrix or vector representing the right hand side of the equation. + If a vector, b.shape must be (n,) or (n, 1). + permc_spec : str, optional + How to permute the columns of the matrix for sparsity preservation. + (default: 'COLAMD') + + - ``NATURAL``: natural ordering. + - ``MMD_ATA``: minimum degree ordering on the structure of A^T A. + - ``MMD_AT_PLUS_A``: minimum degree ordering on the structure of A^T+A. + - ``COLAMD``: approximate minimum degree column ordering [1]_, [2]_. + + use_umfpack : bool, optional + if True (default) then use UMFPACK for the solution [3]_, [4]_, [5]_, + [6]_ . This is only referenced if b is a vector and + ``scikits.umfpack`` is installed. + + Returns + ------- + x : ndarray or sparse array or matrix + the solution of the sparse linear equation. + If b is a vector, then x is a vector of size A.shape[1] + If b is a matrix, then x is a matrix of size (A.shape[1], b.shape[1]) + + Notes + ----- + For solving the matrix expression AX = B, this solver assumes the resulting + matrix X is sparse, as is often the case for very sparse inputs. If the + resulting X is dense, the construction of this sparse result will be + relatively expensive. In that case, consider converting A to a dense + matrix and using scipy.linalg.solve or its variants. + + References + ---------- + .. [1] T. A. Davis, J. R. Gilbert, S. Larimore, E. Ng, Algorithm 836: + COLAMD, an approximate column minimum degree ordering algorithm, + ACM Trans. on Mathematical Software, 30(3), 2004, pp. 377--380. + :doi:`10.1145/1024074.1024080` + + .. [2] T. A. Davis, J. R. Gilbert, S. Larimore, E. Ng, A column approximate + minimum degree ordering algorithm, ACM Trans. on Mathematical + Software, 30(3), 2004, pp. 353--376. :doi:`10.1145/1024074.1024079` + + .. [3] T. A. Davis, Algorithm 832: UMFPACK - an unsymmetric-pattern + multifrontal method with a column pre-ordering strategy, ACM + Trans. on Mathematical Software, 30(2), 2004, pp. 196--199. + :doi:`10.1145/992200.992206`. + + .. [4] T. A. Davis, A column pre-ordering strategy for the + unsymmetric-pattern multifrontal method, ACM Trans. + on Mathematical Software, 30(2), 2004, pp. 165--195. + :doi:`10.1145/992200.992205`. + + .. [5] T. A. Davis and I. S. Duff, A combined unifrontal/multifrontal + method for unsymmetric sparse matrices, ACM Trans. on + Mathematical Software, 25(1), 1999, pp. 1--19. + :doi:`10.1145/305658.287640`. + + .. [6] T. A. Davis and I. S. Duff, An unsymmetric-pattern multifrontal + method for sparse LU factorization, SIAM J. Matrix Analysis and + Computations, 18(1), 1997, pp. 140--158. + :doi:`10.1137/S0895479894246905T`. + + + Examples + -------- + >>> import numpy as np + >>> from scipy.sparse import csc_array + >>> from scipy.sparse.linalg import spsolve + >>> A = csc_array([[3, 2, 0], [1, -1, 0], [0, 5, 1]], dtype=float) + >>> B = csc_array([[2, 0], [-1, 0], [2, 0]], dtype=float) + >>> x = spsolve(A, B) + >>> np.allclose(A.dot(x).toarray(), B.toarray()) + True + """ + is_pydata_sparse = is_pydata_spmatrix(b) + pydata_sparse_cls = b.__class__ if is_pydata_sparse else None + A = convert_pydata_sparse_to_scipy(A) + b = convert_pydata_sparse_to_scipy(b) + + if not (issparse(A) and A.format in ("csc", "csr")): + A = csc_array(A) + warn('spsolve requires A be CSC or CSR matrix format', + SparseEfficiencyWarning, stacklevel=2) + + # b is a vector only if b have shape (n,) or (n, 1) + b_is_sparse = issparse(b) + if not b_is_sparse: + b = asarray(b) + b_is_vector = ((b.ndim == 1) or (b.ndim == 2 and b.shape[1] == 1)) + + # sum duplicates for non-canonical format + A.sum_duplicates() + A = A._asfptype() # upcast to a floating point format + result_dtype = np.promote_types(A.dtype, b.dtype) + if A.dtype != result_dtype: + A = A.astype(result_dtype) + if b.dtype != result_dtype: + b = b.astype(result_dtype) + + # validate input shapes + M, N = A.shape + if (M != N): + raise ValueError(f"matrix must be square (has shape {(M, N)})") + + if M != b.shape[0]: + raise ValueError(f"matrix - rhs dimension mismatch ({A.shape} - {b.shape[0]})") + + if not hasattr(useUmfpack, 'u'): + useUmfpack.u = not noScikit + + use_umfpack = use_umfpack and useUmfpack.u + + if b_is_vector and use_umfpack: + if b_is_sparse: + b_vec = b.toarray() + else: + b_vec = b + b_vec = asarray(b_vec, dtype=A.dtype).ravel() + + if noScikit: + raise RuntimeError('Scikits.umfpack not installed.') + + if A.dtype.char not in 'dD': + raise ValueError("convert matrix data to double, please, using" + " .astype(), or set linsolve.useUmfpack.u = False") + + umf_family, A = _get_umf_family(A) + umf = umfpack.UmfpackContext(umf_family) + x = umf.linsolve(umfpack.UMFPACK_A, A, b_vec, + autoTranspose=True) + else: + if b_is_vector and b_is_sparse: + b = b.toarray() + b_is_sparse = False + + if not b_is_sparse: + if A.format == "csc": + flag = 1 # CSC format + else: + flag = 0 # CSR format + + indices = A.indices.astype(np.intc, copy=False) + indptr = A.indptr.astype(np.intc, copy=False) + options = dict(ColPerm=permc_spec) + x, info = _superlu.gssv(N, A.nnz, A.data, indices, indptr, + b, flag, options=options) + if info == 0: + pass # Success + elif 0 < info <= N: + warn("Matrix is exactly singular", MatrixRankWarning, stacklevel=2) + x.fill(np.nan) + elif info > N: + raise MemoryError("Out of memory during gssv") + else: + # gssv is not supposed to return any info < 0 + # However, it calls gstrf, which can set info < 0 + raise Exception(f"gssv exited with unknown exit code {info}") + if b_is_vector: + x = x.ravel() + else: + # b is sparse + Afactsolve = factorized(A) + + if not (b.format == "csc" or is_pydata_spmatrix(b)): + warn('spsolve is more efficient when sparse b ' + 'is in the CSC matrix format', + SparseEfficiencyWarning, stacklevel=2) + b = csc_array(b) + + # Create a sparse output matrix by repeatedly applying + # the sparse factorization to solve columns of b. + data_segs = [] + row_segs = [] + col_segs = [] + for j in range(b.shape[1]): + bj = b[:, j].toarray().ravel() + xj = Afactsolve(bj) + w = np.flatnonzero(xj) + segment_length = w.shape[0] + row_segs.append(w) + col_segs.append(np.full(segment_length, j, dtype=int)) + data_segs.append(np.asarray(xj[w], dtype=A.dtype)) + sparse_data = np.concatenate(data_segs) + idx_dtype = get_index_dtype(maxval=max(b.shape)) + sparse_row = np.concatenate(row_segs, dtype=idx_dtype) + sparse_col = np.concatenate(col_segs, dtype=idx_dtype) + x = A.__class__((sparse_data, (sparse_row, sparse_col)), + shape=b.shape, dtype=A.dtype) + + if is_pydata_sparse: + x = pydata_sparse_cls.from_scipy_sparse(x) + + return x + + +def splu(A, permc_spec=None, diag_pivot_thresh=None, + relax=None, panel_size=None, options=None): + """ + Compute the LU decomposition of a sparse, square matrix. + + Parameters + ---------- + A : sparse array or matrix + Sparse array to factorize. Most efficient when provided in CSC + format. Other formats will be converted to CSC before factorization. + permc_spec : str, optional + How to permute the columns of the matrix for sparsity preservation. + (default: 'COLAMD') + + - ``NATURAL``: natural ordering. + - ``MMD_ATA``: minimum degree ordering on the structure of A^T A. + - ``MMD_AT_PLUS_A``: minimum degree ordering on the structure of A^T+A. + - ``COLAMD``: approximate minimum degree column ordering + + diag_pivot_thresh : float, optional + Threshold used for a diagonal entry to be an acceptable pivot. + See SuperLU user's guide for details [1]_ + relax : int, optional + Expert option for customizing the degree of relaxing supernodes. + See SuperLU user's guide for details [1]_ + panel_size : int, optional + Expert option for customizing the panel size. + See SuperLU user's guide for details [1]_ + options : dict, optional + Dictionary containing additional expert options to SuperLU. + See SuperLU user guide [1]_ (section 2.4 on the 'Options' argument) + for more details. For example, you can specify + ``options=dict(Equil=False, IterRefine='SINGLE'))`` + to turn equilibration off and perform a single iterative refinement. + + Returns + ------- + invA : scipy.sparse.linalg.SuperLU + Object, which has a ``solve`` method. + + See Also + -------- + spilu : incomplete LU decomposition + + Notes + ----- + When a real array is factorized and the returned SuperLU object's ``solve()`` + method is used with complex arguments an error is generated. Instead, cast the + initial array to complex and then factorize. + + This function uses the SuperLU library. + + References + ---------- + .. [1] SuperLU https://portal.nersc.gov/project/sparse/superlu/ + + Examples + -------- + >>> import numpy as np + >>> from scipy.sparse import csc_array + >>> from scipy.sparse.linalg import splu + >>> A = csc_array([[1., 0., 0.], [5., 0., 2.], [0., -1., 0.]], dtype=float) + >>> B = splu(A) + >>> x = np.array([1., 2., 3.], dtype=float) + >>> B.solve(x) + array([ 1. , -3. , -1.5]) + >>> A.dot(B.solve(x)) + array([ 1., 2., 3.]) + >>> B.solve(A.dot(x)) + array([ 1., 2., 3.]) + """ + + if is_pydata_spmatrix(A): + A_cls = type(A) + def csc_construct_func(*a, cls=A_cls): + return cls.from_scipy_sparse(csc_array(*a)) + A = A.to_scipy_sparse().tocsc() + else: + csc_construct_func = csc_array + + if not (issparse(A) and A.format == "csc"): + A = csc_array(A) + warn('splu converted its input to CSC format', + SparseEfficiencyWarning, stacklevel=2) + + # sum duplicates for non-canonical format + A.sum_duplicates() + A = A._asfptype() # upcast to a floating point format + + M, N = A.shape + if (M != N): + raise ValueError("can only factor square matrices") # is this true? + + indices, indptr = safely_cast_index_arrays(A, np.intc, "SuperLU") + + _options = dict(DiagPivotThresh=diag_pivot_thresh, ColPerm=permc_spec, + PanelSize=panel_size, Relax=relax) + if options is not None: + _options.update(options) + + # Ensure that no column permutations are applied + if (_options["ColPerm"] == "NATURAL"): + _options["SymmetricMode"] = True + + return _superlu.gstrf(N, A.nnz, A.data, indices, indptr, + csc_construct_func=csc_construct_func, + ilu=False, options=_options) + + +def spilu(A, drop_tol=None, fill_factor=None, drop_rule=None, permc_spec=None, + diag_pivot_thresh=None, relax=None, panel_size=None, options=None): + """ + Compute an incomplete LU decomposition for a sparse, square matrix. + + The resulting object is an approximation to the inverse of `A`. + + Parameters + ---------- + A : (N, N) array_like + Sparse array to factorize. Most efficient when provided in CSC format. + Other formats will be converted to CSC before factorization. + drop_tol : float, optional + Drop tolerance (``0 <= tol <= 1``) for an incomplete LU decomposition. + (default: 1e-4) + + Note that `drop_tol` primarily affects entries generated as fill-in + during the ILU factorization; for matrices that produce little or no + fill-in, changing this parameter may have no visible effect on the + sparsity pattern of the factors. + fill_factor : float, optional + Specifies the fill ratio upper bound (``>= 1.0``) for ILU. (default: 10) + drop_rule : str, optional + Comma-separated string of drop rules to use. + Available rules: ``basic``, ``prows``, ``column``, ``area``, + ``secondary``, ``dynamic``, ``interp``. (Default: ``basic,area``) + + See SuperLU documentation for details. + permc_spec : str, optional + How to permute the columns of the matrix for sparsity preservation. + (default: 'COLAMD') + + - ``NATURAL``: natural ordering. + - ``MMD_ATA``: minimum degree ordering on the structure of A^T A. + - ``MMD_AT_PLUS_A``: minimum degree ordering on the structure of A^T+A. + - ``COLAMD``: approximate minimum degree column ordering + + diag_pivot_thresh : float, optional + Threshold used for a diagonal entry to be an acceptable pivot. + See SuperLU user's guide for details [1]_ + relax : int, optional + Expert option for customizing the degree of relaxing supernodes. + See SuperLU user's guide for details [1]_ + panel_size : int, optional + Expert option for customizing the panel size. + See SuperLU user's guide for details [1]_ + options : dict, optional + Dictionary containing additional expert options to SuperLU. + See SuperLU user guide [1]_ (section 2.4 on the 'Options' argument) + for more details. For example, you can specify + ``options=dict(Equil=False, IterRefine='SINGLE'))`` + to turn equilibration off and perform a single iterative refinement. + + Returns + ------- + invA_approx : scipy.sparse.linalg.SuperLU + Object, which has a ``solve`` method. + + See Also + -------- + splu : complete LU decomposition + + Notes + ----- + When a real array is factorized and the returned SuperLU object's ``solve()`` method + is used with complex arguments an error is generated. Instead, cast the initial + array to complex and then factorize. + + To improve the better approximation to the inverse, you may need to + increase `fill_factor` AND decrease `drop_tol`. + + The effect of `drop_tol` is matrix-dependent. In particular, `drop_tol` + does not guarantee that existing off-diagonal entries will be removed; + it controls dropping of candidate entries during factorization (often + fill-in). For some sparsity patterns, the ILU factors can be identical + to the full LU factors even for large `drop_tol`. + + This function uses the SuperLU library. + + References + ---------- + .. [1] SuperLU https://portal.nersc.gov/project/sparse/superlu/ + + Examples + -------- + >>> import numpy as np + >>> from scipy.sparse import csc_array + >>> from scipy.sparse.linalg import spilu + >>> A = csc_array([[1., 0., 0.], [5., 0., 2.], [0., -1., 0.]], dtype=float) + >>> B = spilu(A) + >>> x = np.array([1., 2., 3.], dtype=float) + >>> B.solve(x) + array([ 1. , -3. , -1.5]) + >>> A.dot(B.solve(x)) + array([ 1., 2., 3.]) + >>> B.solve(A.dot(x)) + array([ 1., 2., 3.]) + """ + + if is_pydata_spmatrix(A): + A_cls = type(A) + def csc_construct_func(*a, cls=A_cls): + return cls.from_scipy_sparse(csc_array(*a)) + A = A.to_scipy_sparse().tocsc() + else: + csc_construct_func = csc_array + + if not (issparse(A) and A.format == "csc"): + A = csc_array(A) + warn('spilu converted its input to CSC format', + SparseEfficiencyWarning, stacklevel=2) + + # sum duplicates for non-canonical format + A.sum_duplicates() + A = A._asfptype() # upcast to a floating point format + + M, N = A.shape + if (M != N): + raise ValueError("can only factor square matrices") # is this true? + + indices, indptr = safely_cast_index_arrays(A, np.intc, "SuperLU") + + _options = dict(ILU_DropRule=drop_rule, ILU_DropTol=drop_tol, + ILU_FillFactor=fill_factor, + DiagPivotThresh=diag_pivot_thresh, ColPerm=permc_spec, + PanelSize=panel_size, Relax=relax) + if options is not None: + _options.update(options) + + # Ensure that no column permutations are applied + if (_options["ColPerm"] == "NATURAL"): + _options["SymmetricMode"] = True + + return _superlu.gstrf(N, A.nnz, A.data, indices, indptr, + csc_construct_func=csc_construct_func, + ilu=True, options=_options) + + +def factorized(A): + """ + Return a function for solving a sparse linear system, with A pre-factorized. + + Parameters + ---------- + A : (N, N) array_like + Input. A in CSC format is most efficient. A CSR format matrix will + be converted to CSC before factorization. + + Returns + ------- + solve : callable + To solve the linear system of equations given in `A`, the `solve` + callable should be passed an ndarray of shape (N,). + + Examples + -------- + >>> import numpy as np + >>> from scipy.sparse.linalg import factorized + >>> from scipy.sparse import csc_array + >>> A = np.array([[ 3. , 2. , -1. ], + ... [ 2. , -2. , 4. ], + ... [-1. , 0.5, -1. ]]) + >>> solve = factorized(csc_array(A)) # Makes LU decomposition. + >>> rhs1 = np.array([1, -2, 0]) + >>> solve(rhs1) # Uses the LU factors. + array([ 1., -2., -2.]) + + """ + if is_pydata_spmatrix(A): + A = A.to_scipy_sparse().tocsc() + + if not hasattr(useUmfpack, 'u'): + useUmfpack.u = not noScikit + + if useUmfpack.u: + if noScikit: + raise RuntimeError('Scikits.umfpack not installed.') + + if not (issparse(A) and A.format == "csc"): + A = csc_array(A) + warn('splu converted its input to CSC format', + SparseEfficiencyWarning, stacklevel=2) + + A = A._asfptype() # upcast to a floating point format + + if A.dtype.char not in 'dD': + raise ValueError("convert matrix data to double, please, using" + " .astype(), or set linsolve.useUmfpack.u = False") + + umf_family, A = _get_umf_family(A) + umf = umfpack.UmfpackContext(umf_family) + + # Make LU decomposition. + umf.numeric(A) + + def solve(b): + with np.errstate(divide="ignore", invalid="ignore"): + # Ignoring warnings with numpy >= 1.23.0, see gh-16523 + result = umf.solve(umfpack.UMFPACK_A, A, b, autoTranspose=True) + + return result + + return solve + else: + return splu(A).solve + + +def spsolve_triangular(A, b, lower=True, overwrite_A=False, overwrite_b=False, + unit_diagonal=False): + """ + Solve the equation ``A x = b`` for `x`, assuming A is a triangular matrix. + + Parameters + ---------- + A : (M, M) sparse array or matrix + A sparse square triangular matrix. Should be in CSR or CSC format. + b : (M,) or (M, N) array_like + Right-hand side matrix in ``A x = b`` + lower : bool, optional + Whether `A` is a lower or upper triangular matrix. + Default is lower triangular matrix. + overwrite_A : bool, optional + Allow changing `A`. + Enabling gives a performance gain. Default is False. + overwrite_b : bool, optional + Allow overwriting data in `b`. + Enabling gives a performance gain. Default is False. + If `overwrite_b` is True, it should be ensured that + `b` has an appropriate dtype to be able to store the result. + unit_diagonal : bool, optional + If True, diagonal elements of `a` are assumed to be 1. + + .. versionadded:: 1.4.0 + + Returns + ------- + x : (M,) or (M, N) ndarray + Solution to the system ``A x = b``. Shape of return matches shape + of `b`. + + Raises + ------ + LinAlgError + If `A` is singular or not triangular. + ValueError + If shape of `A` or shape of `b` do not match the requirements. + + Notes + ----- + .. versionadded:: 0.19.0 + + Examples + -------- + >>> import numpy as np + >>> from scipy.sparse import csc_array + >>> from scipy.sparse.linalg import spsolve_triangular + >>> A = csc_array([[3, 0, 0], [1, -1, 0], [2, 0, 1]], dtype=float) + >>> B = np.array([[2, 0], [-1, 0], [2, 0]], dtype=float) + >>> x = spsolve_triangular(A, B) + >>> np.allclose(A.dot(x), B) + True + """ + + if is_pydata_spmatrix(A): + A = A.to_scipy_sparse().tocsc() + + trans = "N" + if issparse(A) and A.format == "csr": + A = A.T + trans = "T" + lower = not lower + + if not (issparse(A) and A.format == "csc"): + warn('CSC or CSR matrix format is required. Converting to CSC matrix.', + SparseEfficiencyWarning, stacklevel=2) + A = csc_array(A) + elif not overwrite_A: + A = A.copy() + + + M, N = A.shape + if M != N: + raise ValueError( + f'A must be a square matrix but its shape is {A.shape}.') + + if unit_diagonal: + with catch_warnings(): + simplefilter('ignore', SparseEfficiencyWarning) + A.setdiag(1) + else: + diag = A.diagonal() + if np.any(diag == 0): + raise LinAlgError( + 'A is singular: zero entry on diagonal.') + invdiag = 1/diag + if trans == "N": + A = A @ diags_array(invdiag) + else: + A = (A.T @ diags_array(invdiag)).T + + # sum duplicates for non-canonical format + A.sum_duplicates() + + b = np.asanyarray(b) + + if b.ndim not in [1, 2]: + raise ValueError( + f'b must have 1 or 2 dims but its shape is {b.shape}.') + if M != b.shape[0]: + raise ValueError( + 'The size of the dimensions of A must be equal to ' + 'the size of the first dimension of b but the shape of A is ' + f'{A.shape} and the shape of b is {b.shape}.' + ) + + result_dtype = np.promote_types(np.promote_types(A.dtype, np.float32), b.dtype) + if A.dtype != result_dtype: + A = A.astype(result_dtype) + if b.dtype != result_dtype: + b = b.astype(result_dtype) + elif not overwrite_b: + b = b.copy() + + if lower: + L = A + U = csc_array((N, N), dtype=result_dtype) + else: + L = eye_array(N, dtype=result_dtype, format='csc') + U = A + U.setdiag(0) + + L_indices, L_indptr = safely_cast_index_arrays(L, np.intc, "SuperLU") + U_indices, U_indptr = safely_cast_index_arrays(U, np.intc, "SuperLU") + + x, info = _superlu.gstrs(trans, + N, L.nnz, L.data, L_indices, L_indptr, + N, U.nnz, U.data, U_indices, U_indptr, + b) + if info: + raise LinAlgError('A is singular.') + + if not unit_diagonal: + invdiag = invdiag.reshape(-1, *([1] * (len(x.shape) - 1))) + x = x * invdiag + + return x + + +def is_sptriangular(A): + """Returns 2-tuple indicating lower/upper triangular structure for sparse ``A``. + + Checks for triangular structure in ``A``. The result is summarized in + two boolean values ``lower`` and ``upper`` to designate whether ``A`` is + lower triangular or upper triangular respectively. Diagonal ``A`` will + result in both being True. Non-triangular structure results in False for both. + + Only the sparse structure is used here. Values are not checked for zeros. + + This function will convert a copy of ``A`` to CSC format if it is not already + CSR or CSC format. So it may be more efficient to convert it yourself if you + have other uses for the CSR/CSC version. + + If ``A`` is not square, the portions outside the upper left square of the + matrix do not affect its triangular structure. You probably want to work + with the square portion of the matrix, though it is not requred here. + + Parameters + ---------- + A : SciPy sparse array or matrix + A sparse matrix preferrably in CSR or CSC format. + + Returns + ------- + lower, upper : 2-tuple of bool + Whether `A` is lower / upper triangular. + + .. versionadded:: 1.15.0 + + Examples + -------- + >>> import numpy as np + >>> from scipy.sparse import csc_array, eye_array + >>> from scipy.sparse.linalg import is_sptriangular + >>> A = csc_array([[3, 0, 0], [1, -1, 0], [2, 0, 1]], dtype=float) + >>> is_sptriangular(A) + (True, False) + >>> D = eye_array(3, format='csr') + >>> is_sptriangular(D) + (True, True) + """ + if not (issparse(A) and A.format in ("csc", "csr", "coo", "dia", "dok", "lil")): + warn('is_sptriangular needs sparse and not BSR format. Converting to CSR.', + SparseEfficiencyWarning, stacklevel=2) + A = csr_array(A) + + # bsr is better off converting to csr + if A.format == "dia": + return A.offsets.max() <= 0, A.offsets.min() >= 0 + elif A.format == "coo": + rows, cols = A.coords + return (cols <= rows).all(), (cols >= rows).all() + elif A.format == "dok": + return all(c <= r for r, c in A.keys()), all(c >= r for r, c in A.keys()) + elif A.format == "lil": + lower = all(col <= row for row, cols in enumerate(A.rows) for col in cols) + upper = all(col >= row for row, cols in enumerate(A.rows) for col in cols) + return lower, upper + # format in ("csc", "csr") + indptr, indices = A.indptr, A.indices + N = len(indptr) - 1 + + lower, upper = True, True + # check middle, 1st, last col (treat as CSC and switch at end if CSR) + for col in [N // 2, 0, -1]: + rows = indices[indptr[col]:indptr[col + 1]] + upper = upper and (col >= rows).all() + lower = lower and (col <= rows).all() + if not upper and not lower: + return False, False + # check all cols + cols = np.repeat(np.arange(N), np.diff(indptr)) + rows = indices + upper = upper and (cols >= rows).all() + lower = lower and (cols <= rows).all() + if A.format == 'csr': + return upper, lower + return lower, upper + + +def spbandwidth(A): + """Return the lower and upper bandwidth of a 2D numeric array. + + Computes the lower and upper limits on the bandwidth of the + sparse 2D array ``A``. The result is summarized as a 2-tuple + of positive integers ``(lo, hi)``. A zero denotes no sub/super + diagonal entries on that side (triangular). The maximum value + for ``lo`` (``hi``) is one less than the number of rows(cols). + + Only the sparse structure is used here. Values are not checked for zeros. + + Parameters + ---------- + A : SciPy sparse array or matrix + A sparse matrix preferrably in CSR or CSC format. + + Returns + ------- + below, above : 2-tuple of int + The distance to the farthest non-zero diagonal below/above the + main diagonal. + + .. versionadded:: 1.15.0 + + Examples + -------- + >>> import numpy as np + >>> from scipy.sparse.linalg import spbandwidth + >>> from scipy.sparse import csc_array, eye_array + >>> A = csc_array([[3, 0, 0], [1, -1, 0], [2, 0, 1]], dtype=float) + >>> spbandwidth(A) + (2, 0) + >>> D = eye_array(3, format='csr') + >>> spbandwidth(D) + (0, 0) + """ + if not (issparse(A) and A.format in ("csc", "csr", "coo", "dia", "dok")): + warn('spbandwidth needs sparse format not LIL and BSR. Converting to CSR.', + SparseEfficiencyWarning, stacklevel=2) + A = csr_array(A) + + # bsr and lil are better off converting to csr + if A.format == "dia": + return max(0, -A.offsets.min().item()), max(0, A.offsets.max().item()) + if A.format in ("csc", "csr"): + indptr, indices = A.indptr, A.indices + N = len(indptr) - 1 + gap = np.repeat(np.arange(N), np.diff(indptr)) - indices + if A.format == 'csr': + gap = -gap + elif A.format == "coo": + gap = A.coords[1] - A.coords[0] + elif A.format == "dok": + gap = [(c - r) for r, c in A.keys()] + [0] + return -min(gap), max(gap) + return max(-np.min(gap).item(), 0), max(np.max(gap).item(), 0) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_dsolve/tests/__init__.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_dsolve/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_dsolve/tests/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_dsolve/tests/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a4dce91d5384a22ce526be8936d8ba7c8291becb Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_dsolve/tests/__pycache__/__init__.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_dsolve/tests/__pycache__/test_linsolve.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_dsolve/tests/__pycache__/test_linsolve.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aa38af306694b0f2adf85c7fdcdca31f970cb013 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_dsolve/tests/__pycache__/test_linsolve.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_dsolve/tests/test_linsolve.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_dsolve/tests/test_linsolve.py new file mode 100644 index 0000000000000000000000000000000000000000..5cac2f85c4b47e2d581fb1bc88088de72f976d5d --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_dsolve/tests/test_linsolve.py @@ -0,0 +1,930 @@ +import sys +import threading +import warnings + +import numpy as np +from numpy import array, finfo, arange, eye, all, unique, ones, dot +from numpy.exceptions import ComplexWarning +from numpy.testing import ( + assert_array_almost_equal, assert_almost_equal, + assert_equal, assert_array_equal, assert_, assert_allclose) +import pytest +from pytest import raises as assert_raises, warns as assert_warns + +import scipy.linalg +from scipy.linalg import norm, inv +from scipy.sparse import (issparse, SparseEfficiencyWarning, + csc_array, csr_array, coo_array, dia_array, + dok_array, lil_array, bsr_array, + eye_array, random_array, tril, triu, kron) +from scipy.sparse.linalg import SuperLU +from scipy.sparse.linalg._dsolve import (spsolve, use_solver, splu, spilu, + MatrixRankWarning, _superlu, spsolve_triangular, factorized, + is_sptriangular, spbandwidth) +import scipy.sparse + +from scipy._lib._testutils import check_free_memory + + +# scikits.umfpack is not a SciPy dependency but it is optionally used in +# dsolve, so check whether it's available +try: + import scikits.umfpack as umfpack + has_umfpack = True +except ImportError: + has_umfpack = False + +def toarray(a): + if issparse(a): + return a.toarray() + else: + return a + + +def setup_bug_8278(): + N = 2 ** 6 + h = 1/N + Ah1D = dia_array(([-1, 2, -1], [-1, 0, 1]), shape=(N-1, N-1))/(h**2) + eyeN = eye_array(N - 1) + A = (kron(eyeN, kron(eyeN, Ah1D)) + + kron(eyeN, kron(Ah1D, eyeN)) + + kron(Ah1D, kron(eyeN, eyeN))) + b = np.random.rand((N-1)**3) + return A, b + + +class TestFactorized: + def setup_method(self): + n = 5 + d = arange(n) + 1 + self.n = n + self.A = dia_array(((d, 2*d, d[::-1]), (-3, 0, 5)), shape=(n,n)).tocsc() + + def _check_singular(self): + A = csc_array((5,5), dtype='d') + b = ones(5) + assert_array_almost_equal(0. * b, factorized(A)(b)) + + def _check_non_singular(self): + # Make a diagonal dominant, to make sure it is not singular + n = 5 + rng = np.random.default_rng(14332) + a = csc_array(rng.random((n, n))) + b = ones(n) + + expected = splu(a).solve(b) + assert_array_almost_equal(factorized(a)(b), expected) + + def test_singular_without_umfpack(self): + use_solver(useUmfpack=False) + with assert_raises(RuntimeError, match="Factor is exactly singular"): + self._check_singular() + + @pytest.mark.skipif(not has_umfpack, reason="umfpack not available") + def test_singular_with_umfpack(self): + use_solver(useUmfpack=True) + with warnings.catch_warnings(): + msg = "divide by zero encountered in double_scalars" + warnings.filterwarnings("ignore", msg, RuntimeWarning) + assert_warns(umfpack.UmfpackWarning, self._check_singular) + + def test_non_singular_without_umfpack(self): + use_solver(useUmfpack=False) + self._check_non_singular() + + @pytest.mark.skipif(not has_umfpack, reason="umfpack not available") + def test_non_singular_with_umfpack(self): + use_solver(useUmfpack=True) + self._check_non_singular() + + def test_cannot_factorize_nonsquare_matrix_without_umfpack(self): + use_solver(useUmfpack=False) + msg = "can only factor square matrices" + with assert_raises(ValueError, match=msg): + factorized(self.A[:, :4]) + + @pytest.mark.skipif(not has_umfpack, reason="umfpack not available") + def test_factorizes_nonsquare_matrix_with_umfpack(self): + use_solver(useUmfpack=True) + # does not raise + factorized(self.A[:,:4]) + + def test_call_with_incorrectly_sized_matrix_without_umfpack(self): + use_solver(useUmfpack=False) + solve = factorized(self.A) + + rng = np.random.default_rng(230498) + b = rng.random(4) + B = rng.random((4, 3)) + BB = rng.random((self.n, 3, 9)) + + with assert_raises(ValueError, match="is of incompatible size"): + solve(b) + with assert_raises(ValueError, match="is of incompatible size"): + solve(B) + with assert_raises(ValueError, + match="object too deep for desired array"): + solve(BB) + + @pytest.mark.skipif(not has_umfpack, reason="umfpack not available") + def test_call_with_incorrectly_sized_matrix_with_umfpack(self): + use_solver(useUmfpack=True) + solve = factorized(self.A) + + rng = np.random.default_rng(643095823) + b = rng.random(4) + B = rng.random((4, 3)) + BB = rng.random((self.n, 3, 9)) + + # does not raise + solve(b) + msg = "object too deep for desired array" + with assert_raises(ValueError, match=msg): + solve(B) + with assert_raises(ValueError, match=msg): + solve(BB) + + def test_call_with_cast_to_complex_without_umfpack(self): + use_solver(useUmfpack=False) + solve = factorized(self.A) + rng = np.random.default_rng(23454) + b = rng.random(4) + for t in [np.complex64, np.complex128]: + with assert_raises(TypeError, match="Cannot cast array data"): + solve(b.astype(t)) + + @pytest.mark.skipif(not has_umfpack, reason="umfpack not available") + def test_call_with_cast_to_complex_with_umfpack(self): + use_solver(useUmfpack=True) + solve = factorized(self.A) + rng = np.random.default_rng(23454) + b = rng.random(4) + for t in [np.complex64, np.complex128]: + assert_warns(ComplexWarning, solve, b.astype(t)) + + @pytest.mark.skipif(not has_umfpack, reason="umfpack not available") + def test_assume_sorted_indices_flag(self): + # a sparse matrix with unsorted indices + unsorted_inds = np.array([2, 0, 1, 0]) + data = np.array([10, 16, 5, 0.4]) + indptr = np.array([0, 1, 2, 4]) + A = csc_array((data, unsorted_inds, indptr), (3, 3)) + b = ones(3) + + # should raise when incorrectly assuming indices are sorted + use_solver(useUmfpack=True, assumeSortedIndices=True) + with assert_raises(RuntimeError, + match="UMFPACK_ERROR_invalid_matrix"): + factorized(A) + + # should sort indices and succeed when not assuming indices are sorted + use_solver(useUmfpack=True, assumeSortedIndices=False) + expected = splu(A.copy()).solve(b) + + assert_equal(A.has_sorted_indices, 0) + assert_array_almost_equal(factorized(A)(b), expected) + + @pytest.mark.slow + @pytest.mark.skipif(not has_umfpack, reason="umfpack not available") + def test_bug_8278(self): + check_free_memory(8000) + use_solver(useUmfpack=True) + A, b = setup_bug_8278() + A = A.tocsc() + f = factorized(A) + x = f(b) + assert_array_almost_equal(A @ x, b) + + +class TestLinsolve: + def setup_method(self): + use_solver(useUmfpack=False) + + def test_singular(self): + A = csc_array((5,5), dtype='d') + b = array([1, 2, 3, 4, 5],dtype='d') + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", "Matrix is exactly singular", MatrixRankWarning) + x = spsolve(A, b) + assert_(not np.isfinite(x).any()) + + def test_singular_gh_3312(self): + # "Bad" test case that leads SuperLU to call LAPACK with invalid + # arguments. Check that it fails moderately gracefully. + ij = np.array([(17, 0), (17, 6), (17, 12), (10, 13)], dtype=np.int32) + v = np.array([0.284213, 0.94933781, 0.15767017, 0.38797296]) + A = csc_array((v, ij.T), shape=(20, 20)) + b = np.arange(20) + + try: + # should either raise a runtime error or return value + # appropriate for singular input (which yields the warning) + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", "Matrix is exactly singular", MatrixRankWarning) + x = spsolve(A, b) + assert not np.isfinite(x).any() + except RuntimeError: + pass + + @pytest.mark.parametrize('format', ['csc', 'csr']) + @pytest.mark.parametrize('idx_dtype', [np.int32, np.int64]) + def test_twodiags(self, format: str, idx_dtype: np.dtype): + A = dia_array(([[1, 2, 3, 4, 5], [6, 5, 8, 9, 10]], [0, 1]), + shape=(5, 5)).asformat(format) + b = array([1, 2, 3, 4, 5]) + + # condition number of A + cond_A = norm(A.toarray(), 2) * norm(inv(A.toarray()), 2) + + for t in ['f','d','F','D']: + eps = finfo(t).eps # floating point epsilon + b = b.astype(t) + Asp = A.astype(t) + Asp.indices = Asp.indices.astype(idx_dtype, copy=False) + Asp.indptr = Asp.indptr.astype(idx_dtype, copy=False) + + x = spsolve(Asp, b) + assert_(norm(b - Asp@x) < 10 * cond_A * eps) + + def test_bvector_smoketest(self): + Adense = array([[0., 1., 1.], + [1., 0., 1.], + [0., 0., 1.]]) + As = csc_array(Adense) + rng = np.random.default_rng(1234) + x = rng.standard_normal(3) + b = As@x + x2 = spsolve(As, b) + + assert_array_almost_equal(x, x2) + + def test_bmatrix_smoketest(self): + Adense = array([[0., 1., 1.], + [1., 0., 1.], + [0., 0., 1.]]) + As = csc_array(Adense) + rng = np.random.default_rng(1234) + x = rng.standard_normal((3, 4)) + Bdense = As.dot(x) + Bs = csc_array(Bdense) + x2 = spsolve(As, Bs) + assert_array_almost_equal(x, x2.toarray()) + + def test_non_square(self): + with warnings.catch_warnings(): + warnings.simplefilter('ignore', SparseEfficiencyWarning) + # A is not square. + A = ones((3, 4)) + b = ones((4, 1)) + assert_raises(ValueError, spsolve, A, b) + # A2 and b2 have incompatible shapes. + A2 = csc_array(eye(3)) + b2 = array([1.0, 2.0]) + assert_raises(ValueError, spsolve, A2, b2) + + def test_example_comparison(self): + with warnings.catch_warnings(): + warnings.simplefilter('ignore', SparseEfficiencyWarning) + row = array([0,0,1,2,2,2]) + col = array([0,2,2,0,1,2]) + data = array([1,2,3,-4,5,6]) + sM = csr_array((data,(row,col)), shape=(3,3), dtype=float) + M = sM.toarray() + + row = array([0,0,1,1,0,0]) + col = array([0,2,1,1,0,0]) + data = array([1,1,1,1,1,1]) + sN = csr_array((data, (row,col)), shape=(3,3), dtype=float) + N = sN.toarray() + + sX = spsolve(sM, sN) + X = scipy.linalg.solve(M, N) + + assert_array_almost_equal(X, sX.toarray()) + + @pytest.mark.skipif(not has_umfpack, reason="umfpack not available") + def test_shape_compatibility(self): + with warnings.catch_warnings(): + warnings.simplefilter('ignore', SparseEfficiencyWarning) + use_solver(useUmfpack=True) + A = csc_array([[1., 0], [0, 2]]) + bs = [ + [1, 6], + array([1, 6]), + [[1], [6]], + array([[1], [6]]), + csc_array([[1], [6]]), + csr_array([[1], [6]]), + dok_array([[1], [6]]), + bsr_array([[1], [6]]), + array([[1., 2., 3.], [6., 8., 10.]]), + csc_array([[1., 2., 3.], [6., 8., 10.]]), + csr_array([[1., 2., 3.], [6., 8., 10.]]), + dok_array([[1., 2., 3.], [6., 8., 10.]]), + bsr_array([[1., 2., 3.], [6., 8., 10.]]), + ] + + for b in bs: + x = np.linalg.solve(A.toarray(), toarray(b)) + for spmattype in [csc_array, csr_array, dok_array, lil_array]: + x1 = spsolve(spmattype(A), b, use_umfpack=True) + x2 = spsolve(spmattype(A), b, use_umfpack=False) + + # check solution + if x.ndim == 2 and x.shape[1] == 1: + # interprets also these as "vectors" + x = x.ravel() + + assert_array_almost_equal(toarray(x1), x, + err_msg=repr((b, spmattype, 1))) + assert_array_almost_equal(toarray(x2), x, + err_msg=repr((b, spmattype, 2))) + + # dense vs. sparse output ("vectors" are always dense) + if issparse(b) and x.ndim > 1: + assert_(issparse(x1), repr((b, spmattype, 1))) + assert_(issparse(x2), repr((b, spmattype, 2))) + else: + assert_(isinstance(x1, np.ndarray), repr((b, spmattype, 1))) + assert_(isinstance(x2, np.ndarray), repr((b, spmattype, 2))) + + # check output shape + if x.ndim == 1: + # "vector" + assert_equal(x1.shape, (A.shape[1],)) + assert_equal(x2.shape, (A.shape[1],)) + else: + # "matrix" + assert_equal(x1.shape, x.shape) + assert_equal(x2.shape, x.shape) + + A = csc_array((3, 3)) + b = csc_array((1, 3)) + assert_raises(ValueError, spsolve, A, b) + + def test_ndarray_support(self): + with warnings.catch_warnings(): + warnings.simplefilter('ignore', SparseEfficiencyWarning) + A = array([[1., 2.], [2., 0.]]) + x = array([[1., 1.], [0.5, -0.5]]) + b = array([[2., 0.], [2., 2.]]) + + assert_array_almost_equal(x, spsolve(A, b)) + + def test_gssv_badinput(self): + N = 10 + d = arange(N) + 1.0 + A = dia_array(((d, 2*d, d[::-1]), (-3, 0, 5)), shape=(N, N)) + + for container in (csc_array, csr_array): + A = container(A) + b = np.arange(N) + + def not_c_contig(x): + return x.repeat(2)[::2] + + def not_1dim(x): + return x[:,None] + + def bad_type(x): + return x.astype(bool) + + def too_short(x): + return x[:-1] + + badops = [not_c_contig, not_1dim, bad_type, too_short] + + for badop in badops: + msg = f"{container!r} {badop!r}" + # Not C-contiguous + assert_raises((ValueError, TypeError), _superlu.gssv, + N, A.nnz, badop(A.data), A.indices, A.indptr, + b, int(A.format == 'csc'), err_msg=msg) + assert_raises((ValueError, TypeError), _superlu.gssv, + N, A.nnz, A.data, badop(A.indices), A.indptr, + b, int(A.format == 'csc'), err_msg=msg) + assert_raises((ValueError, TypeError), _superlu.gssv, + N, A.nnz, A.data, A.indices, badop(A.indptr), + b, int(A.format == 'csc'), err_msg=msg) + + def test_sparsity_preservation(self): + ident = csc_array([ + [1, 0, 0], + [0, 1, 0], + [0, 0, 1]]) + b = csc_array([ + [0, 1], + [1, 0], + [0, 0]]) + x = spsolve(ident, b) + assert_equal(ident.nnz, 3) + assert_equal(b.nnz, 2) + assert_equal(x.nnz, 2) + assert_allclose(x.toarray(), b.toarray(), atol=1e-12, rtol=1e-12) + + def test_dtype_cast(self): + A_real = csr_array([[1, 2, 0], + [0, 0, 3], + [4, 0, 5]]) + A_complex = csr_array([[1, 2, 0], + [0, 0, 3], + [4, 0, 5 + 1j]]) + b_real = np.array([1,1,1]) + b_complex = np.array([1,1,1]) + 1j*np.array([1,1,1]) + x = spsolve(A_real, b_real) + assert_(np.issubdtype(x.dtype, np.floating)) + x = spsolve(A_real, b_complex) + assert_(np.issubdtype(x.dtype, np.complexfloating)) + x = spsolve(A_complex, b_real) + assert_(np.issubdtype(x.dtype, np.complexfloating)) + x = spsolve(A_complex, b_complex) + assert_(np.issubdtype(x.dtype, np.complexfloating)) + + @pytest.mark.slow + @pytest.mark.skipif(not has_umfpack, reason="umfpack not available") + def test_bug_8278(self): + check_free_memory(8000) + use_solver(useUmfpack=True) + A, b = setup_bug_8278() + x = spsolve(A, b) + assert_array_almost_equal(A @ x, b) + + +class TestSplu: + def setup_method(self): + use_solver(useUmfpack=False) + n = 40 + d = arange(n) + 1 + self.n = n + self.A = dia_array(((d, 2*d, d[::-1]), (-3, 0, 5)), shape=(n, n)).tocsc() + + def _smoketest(self, spxlu, check, dtype, idx_dtype): + if np.issubdtype(dtype, np.complexfloating): + A = self.A + 1j*self.A.T + else: + A = self.A + + A = A.astype(dtype) + A.indices = A.indices.astype(idx_dtype, copy=False) + A.indptr = A.indptr.astype(idx_dtype, copy=False) + lu = spxlu(A) + + rng = np.random.RandomState(1234) + + # Input shapes + for k in [None, 1, 2, self.n, self.n+2]: + msg = f"k={k!r}" + + if k is None: + b = rng.rand(self.n) + else: + b = rng.rand(self.n, k) + + if np.issubdtype(dtype, np.complexfloating): + b = b + 1j*rng.rand(*b.shape) + b = b.astype(dtype) + + x = lu.solve(b) + check(A, b, x, msg) + + x = lu.solve(b, 'T') + check(A.T, b, x, msg) + + x = lu.solve(b, 'H') + check(A.T.conj(), b, x, msg) + + def test_splu_smoketest(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", SparseEfficiencyWarning) + self._internal_test_splu_smoketest() + + def _internal_test_splu_smoketest(self): + # Check that splu works at all + def check(A, b, x, msg=""): + eps = np.finfo(A.dtype).eps + r = A @ x + assert_(abs(r - b).max() < 1e3*eps, msg) + + for dtype in [np.float32, np.float64, np.complex64, np.complex128]: + for idx_dtype in [np.int32, np.int64]: + self._smoketest(splu, check, dtype, idx_dtype) + + def test_spilu_smoketest(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", SparseEfficiencyWarning) + self._internal_test_spilu_smoketest() + + def _internal_test_spilu_smoketest(self): + errors = [] + + def check(A, b, x, msg=""): + r = A @ x + err = abs(r - b).max() + assert_(err < 1e-2, msg) + if b.dtype in (np.float64, np.complex128): + errors.append(err) + + for dtype in [np.float32, np.float64, np.complex64, np.complex128]: + for idx_dtype in [np.int32, np.int64]: + self._smoketest(spilu, check, dtype, idx_dtype) + + assert_(max(errors) > 1e-5) + + def test_spilu_drop_rule(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", SparseEfficiencyWarning) + # Test passing in the drop_rule argument to spilu. + A = eye_array(2) + + rules = [ + b'basic,area'.decode('ascii'), # unicode + b'basic,area', # ascii + [b'basic', b'area'.decode('ascii')] + ] + for rule in rules: + # Argument should be accepted + assert_(isinstance(spilu(A, drop_rule=rule), SuperLU)) + + def test_splu_nnz0(self): + A = csc_array((5,5), dtype='d') + assert_raises(RuntimeError, splu, A) + + def test_spilu_nnz0(self): + A = csc_array((5,5), dtype='d') + assert_raises(RuntimeError, spilu, A) + + def test_splu_basic(self): + # Test basic splu functionality. + n = 30 + rng = np.random.RandomState(12) + a = rng.rand(n, n) + a[a < 0.95] = 0 + # First test with a singular matrix + a[:, 0] = 0 + a_ = csc_array(a) + # Matrix is exactly singular + assert_raises(RuntimeError, splu, a_) + + # Make a diagonal dominant, to make sure it is not singular + a += 4*eye(n) + a_ = csc_array(a) + lu = splu(a_) + b = ones(n) + x = lu.solve(b) + assert_almost_equal(dot(a, x), b) + + def test_splu_perm(self): + # Test the permutation vectors exposed by splu. + n = 30 + rng = np.random.default_rng(1342354) + a = rng.random((n, n)) + a[a < 0.95] = 0 + # Make a diagonal dominant, to make sure it is not singular + a += 4*eye(n) + a_ = csc_array(a) + lu = splu(a_) + # Check that the permutation indices do belong to [0, n-1]. + for perm in (lu.perm_r, lu.perm_c): + assert_(all(perm > -1)) + assert_(all(perm < n)) + assert_equal(len(unique(perm)), len(perm)) + + # Now make a symmetric, and test that the two permutation vectors are + # the same + # Note: a += a.T relies on undefined behavior. + a = a + a.T + a_ = csc_array(a) + lu = splu(a_) + assert_array_equal(lu.perm_r, lu.perm_c) + + @pytest.mark.parametrize("splu_fun, rtol", [(splu, 1e-7), (spilu, 1e-1)]) + def test_natural_permc(self, splu_fun, rtol): + # Test that the "NATURAL" permc_spec does not permute the matrix + rng = np.random.RandomState(42) + n = 500 + p = 0.01 + A = random_array((n, n), density=p, random_state=rng) + x = rng.rand(n) + # Make A diagonal dominant to make sure it is not singular + A += (n + 1) * eye_array(n) + A_ = csc_array(A) + b = A_ @ x + + # without permc_spec, permutation is not identity + lu = splu_fun(A_) + assert_(np.any(lu.perm_c != np.arange(n))) + + # with permc_spec="NATURAL", permutation is identity + lu = splu_fun(A_, permc_spec="NATURAL") + assert_array_equal(lu.perm_c, np.arange(n)) + + # Also, lu decomposition is valid + x2 = lu.solve(b) + assert_allclose(x, x2, rtol=rtol) + + @pytest.mark.skipif(not hasattr(sys, 'getrefcount'), reason="no sys.getrefcount") + def test_lu_refcount(self): + # Test that we are keeping track of the reference count with splu. + n = 30 + rng = np.random.default_rng(1342354) + a = rng.random((n, n)) + a[a < 0.95] = 0 + # Make a diagonal dominant, to make sure it is not singular + a += 4*eye(n) + a_ = csc_array(a) + lu = splu(a_) + + # And now test that we don't have a refcount bug + rc = sys.getrefcount(lu) + for attr in ('perm_r', 'perm_c'): + perm = getattr(lu, attr) + assert_equal(sys.getrefcount(lu), rc + 1) + del perm + assert_equal(sys.getrefcount(lu), rc) + + def test_bad_inputs(self): + A = self.A.tocsc() + rng = np.random.default_rng(235634) + + assert_raises(ValueError, splu, A[:,:4]) + assert_raises(ValueError, spilu, A[:,:4]) + + for lu in [splu(A), spilu(A)]: + b = rng.random(42) + B = rng.random((42, 3)) + BB = rng.random((self.n, 3, 9)) + assert_raises(ValueError, lu.solve, b) + assert_raises(ValueError, lu.solve, B) + assert_raises(ValueError, lu.solve, BB) + assert_raises(TypeError, lu.solve, + b.astype(np.complex64)) + assert_raises(TypeError, lu.solve, + b.astype(np.complex128)) + + def test_superlu_dlamch_i386_nan(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", SparseEfficiencyWarning) + # SuperLU 4.3 calls some functions returning floats without + # declaring them. On i386@linux call convention, this fails to + # clear floating point registers after call. As a result, NaN + # can appear in the next floating point operation made. + # + # Here's a test case that triggered the issue. + n = 8 + d = np.arange(n) + 1 + A = dia_array(((d, 2*d, d[::-1]), (-3, 0, 5)), shape=(n, n)) + A = A.astype(np.float32) + spilu(A) + A = A + 1j*A + B = A.toarray() + assert_(not np.isnan(B).any()) + + def test_lu_attr(self): + def check(dtype, complex_2=False): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", SparseEfficiencyWarning) + + A = self.A.astype(dtype) + + if complex_2: + A = A + 1j*A.T + + n = A.shape[0] + lu = splu(A) + + # Check that the decomposition is as advertised + + Pc = np.zeros((n, n)) + Pc[np.arange(n), lu.perm_c] = 1 + + Pr = np.zeros((n, n)) + Pr[lu.perm_r, np.arange(n)] = 1 + + Ad = A.toarray() + lhs = Pr.dot(Ad).dot(Pc) + rhs = (lu.L @ lu.U).toarray() + + eps = np.finfo(dtype).eps + + assert_allclose(lhs, rhs, atol=100*eps) + + check(np.float32) + check(np.float64) + check(np.complex64) + check(np.complex128) + check(np.complex64, True) + check(np.complex128, True) + + @pytest.mark.slow + def test_threads_parallel(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", SparseEfficiencyWarning) + oks = [] + + def worker(): + try: + self.test_splu_basic() + self._internal_test_splu_smoketest() + self._internal_test_spilu_smoketest() + oks.append(True) + except Exception: + pass + + threads = [threading.Thread(target=worker) + for k in range(20)] + for t in threads: + t.start() + for t in threads: + t.join() + + assert_equal(len(oks), 20) + + def test_singular_matrix(self): + # Test that SuperLU does not print to stdout when a singular matrix is + # passed. See gh-20993. + A = eye_array(10, format='csr') + A[-1, -1] = 0 + b = np.zeros(10) + with pytest.warns(MatrixRankWarning): + res = spsolve(A, b) + assert np.isnan(res).all() + + +class TestGstrsErrors: + def setup_method(self): + self.A = csc_array([[1.0,2.0,3.0],[4.0,5.0,6.0],[7.0,8.0,9.0]], dtype=np.float64) + self.b = np.array([[1.0],[2.0],[3.0]], dtype=np.float64) + + def test_trans(self): + L = tril(self.A, format='csc') + U = triu(self.A, k=1, format='csc') + with assert_raises(ValueError, match="trans must be N, T, or H"): + _superlu.gstrs('X', L.shape[0], L.nnz, L.data, L.indices, L.indptr, + U.shape[0], U.nnz, U.data, U.indices, U.indptr, self.b) + + def test_shape_LU(self): + L = tril(self.A[0:2,0:2], format='csc') + U = triu(self.A, k=1, format='csc') + with assert_raises(ValueError, match="L and U must have the same dimension"): + _superlu.gstrs('N', L.shape[0], L.nnz, L.data, L.indices, L.indptr, + U.shape[0], U.nnz, U.data, U.indices, U.indptr, self.b) + + def test_shape_b(self): + L = tril(self.A, format='csc') + U = triu(self.A, k=1, format='csc') + with assert_raises(ValueError, match="right hand side array has invalid shape"): + _superlu.gstrs('N', L.shape[0], L.nnz, L.data, L.indices, L.indptr, + U.shape[0], U.nnz, U.data, U.indices, U.indptr, + self.b[0:2]) + + def test_types_differ(self): + L = tril(self.A.astype(np.float32), format='csc') + U = triu(self.A, k=1, format='csc') + with assert_raises(TypeError, match="nzvals types of L and U differ"): + _superlu.gstrs('N', L.shape[0], L.nnz, L.data, L.indices, L.indptr, + U.shape[0], U.nnz, U.data, U.indices, U.indptr, self.b) + + def test_types_unsupported(self): + L = tril(self.A.astype(np.uint8), format='csc') + U = triu(self.A.astype(np.uint8), k=1, format='csc') + with assert_raises(TypeError, match="nzvals is not of a type supported"): + _superlu.gstrs('N', L.shape[0], L.nnz, L.data, L.indices, L.indptr, + U.shape[0], U.nnz, U.data, U.indices, U.indptr, + self.b.astype(np.uint8)) + +class TestSpsolveTriangular: + def setup_method(self): + use_solver(useUmfpack=False) + + @pytest.mark.parametrize("fmt",["csr","csc"]) + def test_zero_diagonal(self,fmt): + n = 5 + rng = np.random.default_rng(43876432987) + A = coo_array(rng.standard_normal((n, n))) + b = np.arange(n) + A = tril(A, k=0, format=fmt) + + x = spsolve_triangular(A, b, unit_diagonal=True, lower=True) + + A.setdiag(1) + assert_allclose(A.dot(x), b) + + # Regression test from gh-15199 + A = np.array([[0, 0, 0], [1, 0, 0], [1, 1, 0]], dtype=np.float64) + b = np.array([1., 2., 3.]) + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", "CSC or CSR matrix format is", SparseEfficiencyWarning) + spsolve_triangular(A, b, unit_diagonal=True) + + @pytest.mark.parametrize("fmt",["csr","csc"]) + def test_singular(self,fmt): + n = 5 + if fmt == "csr": + A = csr_array((n, n)) + else: + A = csc_array((n, n)) + b = np.arange(n) + for lower in (True, False): + assert_raises(scipy.linalg.LinAlgError, + spsolve_triangular, A, b, lower=lower) + + def test_bad_shape(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", SparseEfficiencyWarning) + # A is not square. + A = np.zeros((3, 4)) + b = ones((4, 1)) + assert_raises(ValueError, spsolve_triangular, A, b) + # A2 and b2 have incompatible shapes. + A2 = csr_array(eye(3)) + b2 = array([1.0, 2.0]) + assert_raises(ValueError, spsolve_triangular, A2, b2) + + def test_input_types(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", SparseEfficiencyWarning) + A = array([[1., 0.], [1., 2.]]) + b = array([[2., 0.], [2., 2.]]) + for matrix_type in (array, csc_array, csr_array): + x = spsolve_triangular(matrix_type(A), b, lower=True) + assert_array_almost_equal(A.dot(x), b) + + @pytest.mark.slow + @pytest.mark.parametrize("n", [10, 10**2, 10**3]) + @pytest.mark.parametrize("m", [1, 10]) + @pytest.mark.parametrize("lower", [True, False]) + @pytest.mark.parametrize("format", ["csr", "csc"]) + @pytest.mark.parametrize("unit_diagonal", [False, True]) + @pytest.mark.parametrize("choice_of_A", ["real", "complex"]) + @pytest.mark.parametrize("choice_of_b", ["floats", "ints", "complexints"]) + def test_random(self, n, m, lower, format, unit_diagonal, choice_of_A, choice_of_b): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", SparseEfficiencyWarning) + def random_triangle_matrix(n, lower=True, format="csr", choice_of_A="real"): + if choice_of_A == "real": + dtype = np.float64 + elif choice_of_A == "complex": + dtype = np.complex128 + else: + raise ValueError("choice_of_A must be 'real' or 'complex'.") + rng = np.random.default_rng(789002319) + rvs = rng.random + A = random_array((n, n), density=0.1, format='lil', dtype=dtype, + random_state=rng, data_sampler=rvs) + if lower: + A = tril(A, format="lil") + else: + A = triu(A, format="lil") + for i in range(n): + A[i, i] = np.random.rand() + 1 + if format == "csc": + A = A.tocsc(copy=False) + else: + A = A.tocsr(copy=False) + return A + + rng = np.random.default_rng(1234) + A = random_triangle_matrix(n, lower=lower) + if choice_of_b == "floats": + b = rng.random((n, m)) + elif choice_of_b == "ints": + b = rng.integers(-9, 9, (n, m)) + elif choice_of_b == "complexints": + b = rng.integers(-9, 9, (n, m)) + rng.integers(-9, 9, (n, m)) * 1j + else: + raise ValueError( + "choice_of_b must be 'floats', 'ints', or 'complexints'.") + x = spsolve_triangular(A, b, lower=lower, unit_diagonal=unit_diagonal) + if unit_diagonal: + A.setdiag(1) + assert_allclose(A.dot(x), b, atol=1.5e-6) + + +@pytest.mark.parametrize("nnz", [10, 10**2, 10**3]) +@pytest.mark.parametrize("fmt", ["csr", "csc", "coo", "dia", "dok", "lil"]) +def test_is_sptriangular_and_spbandwidth(nnz, fmt): + with warnings.catch_warnings(): + warnings.simplefilter('ignore', SparseEfficiencyWarning) + rng = np.random.default_rng(42) + + N = nnz // 2 + dens = 0.1 + A = random_array((N, N), density=dens, format="csr", rng=rng) + A[1, 3] = A[3, 1] = 22 # ensure not upper or lower + A = A.asformat(fmt) + AU = triu(A, format=fmt) + AL = tril(A, format=fmt) + D = 0.1 * eye_array(N, format=fmt) + + assert is_sptriangular(A) == (False, False) + assert is_sptriangular(AL) == (True, False) + assert is_sptriangular(AU) == (False, True) + assert is_sptriangular(D) == (True, True) + + assert spbandwidth(A) == scipy.linalg.bandwidth(A.toarray()) + assert spbandwidth(AU) == scipy.linalg.bandwidth(AU.toarray()) + assert spbandwidth(AL) == scipy.linalg.bandwidth(AL.toarray()) + assert spbandwidth(D) == scipy.linalg.bandwidth(D.toarray()) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/__init__.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..25278d34ecd3353d409a25f7a94797902fe6ef93 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/__init__.py @@ -0,0 +1,22 @@ +""" +Sparse Eigenvalue Solvers +------------------------- + +The submodules of sparse.linalg._eigen: + 1. lobpcg: Locally Optimal Block Preconditioned Conjugate Gradient Method + +""" +from .arpack import * +from .lobpcg import * +from ._svds import svds + +from . import arpack + +__all__ = [ + 'ArpackError', 'ArpackNoConvergence', + 'eigs', 'eigsh', 'lobpcg', 'svds' +] + +from scipy._lib._testutils import PytestTester +test = PytestTester(__name__) +del PytestTester diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..61185582d1db6cb31061320d04e2284a124193c3 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/__pycache__/__init__.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/__pycache__/_svds.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/__pycache__/_svds.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7c53182cbd12ed7a11e8e764de32fe70124ddef2 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/__pycache__/_svds.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/__pycache__/_svds_doc.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/__pycache__/_svds_doc.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2b9033f75ad9e06dea1261d259ee67be456831ad Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/__pycache__/_svds_doc.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/_svds.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/_svds.py new file mode 100644 index 0000000000000000000000000000000000000000..2400cbbcdf0dcd437df83d66e2367a4e673370e7 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/_svds.py @@ -0,0 +1,535 @@ +import math +import numpy as np +from . import eigsh + +from scipy._lib._util import _transition_to_rng, check_random_state +from scipy.sparse.linalg._interface import LinearOperator, aslinearoperator +from scipy.sparse.linalg._eigen.lobpcg import lobpcg # type: ignore[no-redef] +from scipy.sparse.linalg._svdp import _svdp +from scipy.linalg import svd + +__all__ = ['svds'] + + +def _herm(x): + return x.T.conj() + + +def _iv(A, k, ncv, tol, which, v0, maxiter, + return_singular, solver, rng): + + # input validation/standardization for `solver` + # out of order because it's needed for other parameters + solver = str(solver).lower() + solvers = {"arpack", "lobpcg", "propack"} + if solver not in solvers: + raise ValueError(f"solver must be one of {solvers}.") + + # input validation/standardization for `A` + A = aslinearoperator(A) # this takes care of some input validation + if not np.issubdtype(A.dtype, np.number): + message = "`A` must be of numeric data type." + raise ValueError(message) + if math.prod(A.shape) == 0: + message = "`A` must not be empty." + raise ValueError(message) + if A.ndim != 2: + raise ValueError("Only 2-D input is supported for `A` (a single matrix)") + + # input validation/standardization for `k` + kmax = min(A.shape) if solver == 'propack' else min(A.shape) - 1 + if int(k) != k or not (0 < k <= kmax): + message = "`k` must be an integer satisfying `0 < k < min(A.shape)`." + raise ValueError(message) + k = int(k) + + # input validation/standardization for `ncv` + if solver == "arpack" and ncv is not None: + if int(ncv) != ncv or not (k < ncv < min(A.shape)): + message = ("`ncv` must be an integer satisfying " + "`k < ncv < min(A.shape)`.") + raise ValueError(message) + ncv = int(ncv) + + # input validation/standardization for `tol` + if tol < 0 or not np.isfinite(tol): + message = "`tol` must be a non-negative floating point value." + raise ValueError(message) + tol = float(tol) + + # input validation/standardization for `which` + which = str(which).upper() + whichs = {'LM', 'SM'} + if which not in whichs: + raise ValueError(f"`which` must be in {whichs}.") + + # input validation/standardization for `v0` + if v0 is not None: + v0 = np.atleast_1d(v0) + if not (np.issubdtype(v0.dtype, np.complexfloating) + or np.issubdtype(v0.dtype, np.floating)): + message = ("`v0` must be of floating or complex floating " + "data type.") + raise ValueError(message) + + shape = (A.shape[0],) if solver == 'propack' else (min(A.shape),) + if v0.shape != shape: + message = f"`v0` must have shape {shape}." + raise ValueError(message) + + # input validation/standardization for `maxiter` + if maxiter is not None and (int(maxiter) != maxiter or maxiter <= 0): + message = "`maxiter` must be a positive integer." + raise ValueError(message) + maxiter = int(maxiter) if maxiter is not None else maxiter + + # input validation/standardization for `return_singular_vectors` + # not going to be flexible with this; too complicated for little gain + rs_options = {True, False, "vh", "u"} + if return_singular not in rs_options: + raise ValueError(f"`return_singular_vectors` must be in {rs_options}.") + + rng = check_random_state(rng) + + return (A, k, ncv, tol, which, v0, maxiter, + return_singular, solver, rng) + + +@_transition_to_rng("random_state", position_num=9) +def svds(A, k=6, ncv=None, tol=0, which='LM', v0=None, + maxiter=None, return_singular_vectors=True, + solver='arpack', rng=None, options=None): + """ + Partial singular value decomposition of a sparse matrix. + + Compute the largest or smallest `k` singular values and corresponding + singular vectors of a sparse matrix `A`. The order in which the singular + values are returned is not guaranteed. + + In the descriptions below, let ``M, N = A.shape``. + + Parameters + ---------- + A : ndarray, sparse matrix, or LinearOperator + Matrix to decompose of a floating point numeric dtype. + k : int, default: 6 + Number of singular values and singular vectors to compute. + Must satisfy ``1 <= k <= kmax``, where ``kmax=min(M, N)`` for + ``solver='propack'`` and ``kmax=min(M, N) - 1`` otherwise. + ncv : int, optional + When ``solver='arpack'``, this is the number of Lanczos vectors + generated. See :ref:`'arpack' ` for details. + When ``solver='lobpcg'`` or ``solver='propack'``, this parameter is + ignored. + tol : float, optional + Tolerance for singular values. Zero (default) means machine precision. + which : {'LM', 'SM'} + Which `k` singular values to find: either the largest magnitude ('LM') + or smallest magnitude ('SM') singular values. + v0 : ndarray, optional + The starting vector for iteration; see method-specific + documentation (:ref:`'arpack' `, + :ref:`'lobpcg' `), or + :ref:`'propack' ` for details. + maxiter : int, optional + Maximum number of iterations; see method-specific + documentation (:ref:`'arpack' `, + :ref:`'lobpcg' `), or + :ref:`'propack' ` for details. + return_singular_vectors : {True, False, "u", "vh"} + Singular values are always computed and returned; this parameter + controls the computation and return of singular vectors. + + - ``True``: return singular vectors. + - ``False``: do not return singular vectors. + - ``"u"``: if ``M <= N``, compute only the left singular vectors and + return ``None`` for the right singular vectors. Otherwise, compute + all singular vectors. + - ``"vh"``: if ``M > N``, compute only the right singular vectors and + return ``None`` for the left singular vectors. Otherwise, compute + all singular vectors. + + If ``solver='propack'``, the option is respected regardless of the + matrix shape. + + solver : {'arpack', 'propack', 'lobpcg'}, optional + The solver used. + :ref:`'arpack' `, + :ref:`'lobpcg' `, and + :ref:`'propack' ` are supported. + Default: `'arpack'`. + rng : `numpy.random.Generator`, optional + Pseudorandom number generator state. When `rng` is None, a new + `numpy.random.Generator` is created using entropy from the + operating system. Types other than `numpy.random.Generator` are + passed to `numpy.random.default_rng` to instantiate a ``Generator``. + options : dict, optional + A dictionary of solver-specific options. No solver-specific options + are currently supported; this parameter is reserved for future use. + + Returns + ------- + u : ndarray, shape=(M, k) + Unitary matrix having left singular vectors as columns. + s : ndarray, shape=(k,) + The singular values. + vh : ndarray, shape=(k, N) + Unitary matrix having right singular vectors as rows. + + Notes + ----- + When ARPACK or LOBPCG is selected as the method, the singular values and + singular vectors are computed as the eigenvalues and eigenvectors of the + corresponding Gram matrix, either ``A.conj().T @ A`` or ``A @ A.conj().T``, + depending on which one is computationally cheaper. It is then followed by + the Rayleigh-Ritz method as postprocessing; see Using the normal matrix, + in Rayleigh-Ritz method, (2022, Nov. 19), Wikipedia, https://w.wiki/4zms. + + Alternatively, the PROPACK solver can be called. + + Dtype of `A` is mapped, if possible, to float(32,64) and complex(64,128) + for internal computations. + + Examples + -------- + Construct a matrix `A` from singular values and vectors. + + >>> import numpy as np + >>> from scipy import sparse, linalg, stats + >>> from scipy.sparse.linalg import svds, aslinearoperator, LinearOperator + + Construct a dense matrix `A` from singular values and vectors. + + >>> rng = np.random.default_rng(258265244568965474821194062361901728911) + >>> orthogonal = stats.ortho_group.rvs(10, random_state=rng) + >>> s = [1e-3, 1, 2, 3, 4] # non-zero singular values + >>> u = orthogonal[:, :5] # left singular vectors + >>> vT = orthogonal[:, 5:].T # right singular vectors + >>> A = u @ np.diag(s) @ vT + + With only four singular values/vectors, the SVD approximates the original + matrix. + + >>> u4, s4, vT4 = svds(A, k=4) + >>> A4 = u4 @ np.diag(s4) @ vT4 + >>> np.allclose(A4, A, atol=1e-3) + True + + With all five non-zero singular values/vectors, we can reproduce + the original matrix more accurately. + + >>> u5, s5, vT5 = svds(A, k=5) + >>> A5 = u5 @ np.diag(s5) @ vT5 + >>> np.allclose(A5, A) + True + + The singular values match the expected singular values. + + >>> np.allclose(s5, s) + True + + Since the singular values are not close to each other in this example, + every singular vector matches as expected up to a difference in sign. + + >>> (np.allclose(np.abs(u5), np.abs(u)) and + ... np.allclose(np.abs(vT5), np.abs(vT))) + True + + The singular vectors are also orthogonal. + + >>> (np.allclose(u5.T @ u5, np.eye(5)) and + ... np.allclose(vT5 @ vT5.T, np.eye(5))) + True + + If there are (nearly) multiple singular values, the corresponding + individual singular vectors may be unstable, but the whole invariant + subspace containing all such singular vectors is computed accurately + as can be measured by angles between subspaces via 'subspace_angles'. + + >>> rng = np.random.default_rng(178686584221410808734965903901790843963) + >>> s = [1, 1 + 1e-6] # non-zero singular values + >>> u, _ = np.linalg.qr(rng.standard_normal((99, 2))) + >>> v, _ = np.linalg.qr(rng.standard_normal((99, 2))) + >>> vT = v.T + >>> A = u @ np.diag(s) @ vT + >>> A = A.astype(np.float32) + >>> u2, s2, vT2 = svds(A, k=2, rng=rng) + >>> np.allclose(s2, s) + True + + The angles between the individual exact and computed singular vectors + may not be so small. To check use: + + >>> (linalg.subspace_angles(u2[:, :1], u[:, :1]) + + ... linalg.subspace_angles(u2[:, 1:], u[:, 1:])) + array([0.06562513]) # may vary + >>> (linalg.subspace_angles(vT2[:1, :].T, vT[:1, :].T) + + ... linalg.subspace_angles(vT2[1:, :].T, vT[1:, :].T)) + array([0.06562507]) # may vary + + As opposed to the angles between the 2-dimensional invariant subspaces + that these vectors span, which are small for rights singular vectors + + >>> linalg.subspace_angles(u2, u).sum() < 1e-6 + True + + as well as for left singular vectors. + + >>> linalg.subspace_angles(vT2.T, vT.T).sum() < 1e-6 + True + + The next example follows that of 'sklearn.decomposition.TruncatedSVD'. + + >>> rng = np.random.default_rng(0) + >>> X_dense = rng.random(size=(100, 100)) + >>> X_dense[:, 2 * np.arange(50)] = 0 + >>> X = sparse.csr_array(X_dense) + >>> _, singular_values, _ = svds(X, k=5, rng=rng) + >>> print(singular_values) + [ 4.3221... 4.4043... 4.4907... 4.5858... 35.4549...] + + The function can be called without the transpose of the input matrix + ever explicitly constructed. + + >>> rng = np.random.default_rng(102524723947864966825913730119128190974) + >>> G = sparse.random_array((8, 9), density=0.5, rng=rng) + >>> Glo = aslinearoperator(G) + >>> _, singular_values_svds, _ = svds(Glo, k=5, rng=rng) + >>> _, singular_values_svd, _ = linalg.svd(G.toarray()) + >>> np.allclose(singular_values_svds, singular_values_svd[-4::-1]) + True + + The most memory efficient scenario is where neither + the original matrix, nor its transpose, is explicitly constructed. + Our example computes the smallest singular values and vectors + of 'LinearOperator' constructed from the numpy function 'np.diff' used + column-wise to be consistent with 'LinearOperator' operating on columns. + + >>> diff0 = lambda a: np.diff(a, axis=0) + + Let us create the matrix from 'diff0' to be used for validation only. + + >>> n = 5 # The dimension of the space. + >>> M_from_diff0 = diff0(np.eye(n)) + >>> print(M_from_diff0.astype(int)) + [[-1 1 0 0 0] + [ 0 -1 1 0 0] + [ 0 0 -1 1 0] + [ 0 0 0 -1 1]] + + The matrix 'M_from_diff0' is bi-diagonal and could be alternatively + created directly by + + >>> M = - np.eye(n - 1, n, dtype=int) + >>> np.fill_diagonal(M[:,1:], 1) + >>> np.allclose(M, M_from_diff0) + True + + Its transpose + + >>> print(M.T) + [[-1 0 0 0] + [ 1 -1 0 0] + [ 0 1 -1 0] + [ 0 0 1 -1] + [ 0 0 0 1]] + + can be viewed as the incidence matrix; see + Incidence matrix, (2022, Nov. 19), Wikipedia, https://w.wiki/5YXU, + of a linear graph with 5 vertices and 4 edges. The 5x5 normal matrix + ``M.T @ M`` thus is + + >>> print(M.T @ M) + [[ 1 -1 0 0 0] + [-1 2 -1 0 0] + [ 0 -1 2 -1 0] + [ 0 0 -1 2 -1] + [ 0 0 0 -1 1]] + + the graph Laplacian, while the actually used in 'svds' smaller size + 4x4 normal matrix ``M @ M.T`` + + >>> print(M @ M.T) + [[ 2 -1 0 0] + [-1 2 -1 0] + [ 0 -1 2 -1] + [ 0 0 -1 2]] + + is the so-called edge-based Laplacian; see + Symmetric Laplacian via the incidence matrix, in Laplacian matrix, + (2022, Nov. 19), Wikipedia, https://w.wiki/5YXW. + + The 'LinearOperator' setup needs the options 'rmatvec' and 'rmatmat' + of multiplication by the matrix transpose ``M.T``, but we want to be + matrix-free to save memory, so knowing how ``M.T`` looks like, we + manually construct the following function to be + used in ``rmatmat=diff0t``. + + >>> def diff0t(a): + ... if a.ndim == 1: + ... a = a[:,np.newaxis] # Turn 1D into 2D array + ... d = np.zeros((a.shape[0] + 1, a.shape[1]), dtype=a.dtype) + ... d[0, :] = - a[0, :] + ... d[1:-1, :] = a[0:-1, :] - a[1:, :] + ... d[-1, :] = a[-1, :] + ... return d + + We check that our function 'diff0t' for the matrix transpose is valid. + + >>> np.allclose(M.T, diff0t(np.eye(n-1))) + True + + Now we setup our matrix-free 'LinearOperator' called 'diff0_func_aslo' + and for validation the matrix-based 'diff0_matrix_aslo'. + + >>> def diff0_func_aslo_def(n): + ... return LinearOperator(matvec=diff0, + ... matmat=diff0, + ... rmatvec=diff0t, + ... rmatmat=diff0t, + ... shape=(n - 1, n)) + >>> diff0_func_aslo = diff0_func_aslo_def(n) + >>> diff0_matrix_aslo = aslinearoperator(M_from_diff0) + + And validate both the matrix and its transpose in 'LinearOperator'. + + >>> np.allclose(diff0_func_aslo(np.eye(n)), + ... diff0_matrix_aslo(np.eye(n))) + True + >>> np.allclose(diff0_func_aslo.T(np.eye(n-1)), + ... diff0_matrix_aslo.T(np.eye(n-1))) + True + + Having the 'LinearOperator' setup validated, we run the solver. + + >>> n = 100 + >>> diff0_func_aslo = diff0_func_aslo_def(n) + >>> u, s, vT = svds(diff0_func_aslo, k=3, which='SM') + + The singular values squared and the singular vectors are known + explicitly; see + Pure Dirichlet boundary conditions, in + Eigenvalues and eigenvectors of the second derivative, + (2022, Nov. 19), Wikipedia, https://w.wiki/5YX6, + since 'diff' corresponds to first + derivative, and its smaller size n-1 x n-1 normal matrix + ``M @ M.T`` represent the discrete second derivative with the Dirichlet + boundary conditions. We use these analytic expressions for validation. + + >>> se = 2. * np.sin(np.pi * np.arange(1, 4) / (2. * n)) + >>> ue = np.sqrt(2 / n) * np.sin(np.pi * np.outer(np.arange(1, n), + ... np.arange(1, 4)) / n) + >>> np.allclose(s, se, atol=1e-3) + True + >>> np.allclose(np.abs(u), np.abs(ue), atol=1e-6) + True + + """ + args = _iv(A, k, ncv, tol, which, v0, maxiter, return_singular_vectors, + solver, rng) + (A, k, ncv, tol, which, v0, maxiter, + return_singular_vectors, solver, rng) = args + + largest = (which == 'LM') + n, m = A.shape + + if n >= m: + X_dot = A.matvec + X_matmat = A.matmat + XH_dot = A.rmatvec + XH_mat = A.rmatmat + transpose = False + else: + X_dot = A.rmatvec + X_matmat = A.rmatmat + XH_dot = A.matvec + XH_mat = A.matmat + transpose = True + + dtype = getattr(A, 'dtype', None) + if dtype is None: + dtype = A.dot(np.zeros([m, 1])).dtype + + def matvec_XH_X(x): + return XH_dot(X_dot(x)) + + def matmat_XH_X(x): + return XH_mat(X_matmat(x)) + + XH_X = LinearOperator(matvec=matvec_XH_X, dtype=A.dtype, + matmat=matmat_XH_X, + shape=(min(A.shape), min(A.shape))) + + # Get a low rank approximation of the implicitly defined gramian matrix. + # This is not a stable way to approach the problem. + if solver == 'lobpcg': + + if k == 1 and v0 is not None: + X = np.reshape(v0, (-1, 1)) + else: + X = rng.standard_normal(size=(min(A.shape), k)) + + _, eigvec = lobpcg(XH_X, X, tol=tol ** 2, maxiter=maxiter, + largest=largest) + + elif solver == 'propack': + jobu = return_singular_vectors in {True, 'u'} + jobv = return_singular_vectors in {True, 'vh'} + irl_mode = (which == 'SM') + res = _svdp(A, k=k, tol=tol**2, which=which, maxiter=None, + compute_u=jobu, compute_v=jobv, irl_mode=irl_mode, + kmax=maxiter, v0=v0, rng=rng) + + u, s, vh, _ = res # but we'll ignore bnd, the last output + + # PROPACK order appears to be largest first. `svds` output order is not + # guaranteed, according to documentation, but for ARPACK and LOBPCG + # they actually are ordered smallest to largest, so reverse for + # consistency. + s = s[::-1] + u = u[:, ::-1] + vh = vh[::-1] + + u = u if jobu else None + vh = vh if jobv else None + + if return_singular_vectors: + return u, s, vh + else: + return s + + elif solver == 'arpack' or solver is None: + if v0 is None: + v0 = rng.standard_normal(size=(min(A.shape),)) + _, eigvec = eigsh(XH_X, k=k, tol=tol ** 2, maxiter=maxiter, + ncv=ncv, which=which, v0=v0) + # arpack do not guarantee exactly orthonormal eigenvectors + # for clustered eigenvalues, especially in complex arithmetic + eigvec, _ = np.linalg.qr(eigvec) + + # the eigenvectors eigvec must be orthonomal here; see gh-16712 + Av = X_matmat(eigvec) + if not return_singular_vectors: + s = svd(Av, compute_uv=False, overwrite_a=True) + return s[::-1] + + # compute the left singular vectors of X and update the right ones + # accordingly + u, s, vh = svd(Av, full_matrices=False, overwrite_a=True) + u = u[:, ::-1] + s = s[::-1] + vh = vh[::-1] + + jobu = return_singular_vectors in {True, 'u'} + jobv = return_singular_vectors in {True, 'vh'} + + if transpose: + u_tmp = eigvec @ _herm(vh) if jobu else None + vh = _herm(u) if jobv else None + u = u_tmp + else: + if not jobu: + u = None + vh = vh @ _herm(eigvec) if jobv else None + + return u, s, vh diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/_svds_doc.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/_svds_doc.py new file mode 100644 index 0000000000000000000000000000000000000000..90b85876d2a69dd3c2efa56445e46c93bf0eaa9e --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/_svds_doc.py @@ -0,0 +1,382 @@ +def _svds_arpack_doc(A, k=6, ncv=None, tol=0, which='LM', v0=None, + maxiter=None, return_singular_vectors=True, + solver='arpack', rng=None): + """ + Partial singular value decomposition of a sparse matrix using ARPACK. + + Compute the largest or smallest `k` singular values and corresponding + singular vectors of a sparse matrix `A`. The order in which the singular + values are returned is not guaranteed. + + In the descriptions below, let ``M, N = A.shape``. + + Parameters + ---------- + A : sparse matrix or LinearOperator + Matrix to decompose. + k : int, optional + Number of singular values and singular vectors to compute. + Must satisfy ``1 <= k <= min(M, N) - 1``. + Default is 6. + ncv : int, optional + The number of Lanczos vectors generated. + The default is ``min(n, max(2*k + 1, 20))``. + If specified, must satisfy ``k + 1 < ncv < min(M, N)``; ``ncv > 2*k`` + is recommended. + tol : float, optional + Tolerance for singular values. Zero (default) means machine precision. + which : {'LM', 'SM'} + Which `k` singular values to find: either the largest magnitude ('LM') + or smallest magnitude ('SM') singular values. + v0 : ndarray, optional + The starting vector for iteration: + an (approximate) left singular vector if ``N > M`` and a right singular + vector otherwise. Must be of length ``min(M, N)``. + Default: random + maxiter : int, optional + Maximum number of Arnoldi update iterations allowed; + default is ``min(M, N) * 10``. + return_singular_vectors : {True, False, "u", "vh"} + Singular values are always computed and returned; this parameter + controls the computation and return of singular vectors. + + - ``True``: return singular vectors. + - ``False``: do not return singular vectors. + - ``"u"``: if ``M <= N``, compute only the left singular vectors and + return ``None`` for the right singular vectors. Otherwise, compute + all singular vectors. + - ``"vh"``: if ``M > N``, compute only the right singular vectors and + return ``None`` for the left singular vectors. Otherwise, compute + all singular vectors. + + solver : {'arpack', 'propack', 'lobpcg'}, optional + This is the solver-specific documentation for ``solver='arpack'``. + :ref:`'lobpcg' ` and + :ref:`'propack' ` + are also supported. + rng : `numpy.random.Generator`, optional + Pseudorandom number generator state. When `rng` is None, a new + `numpy.random.Generator` is created using entropy from the + operating system. Types other than `numpy.random.Generator` are + passed to `numpy.random.default_rng` to instantiate a ``Generator``. + options : dict, optional + A dictionary of solver-specific options. No solver-specific options + are currently supported; this parameter is reserved for future use. + + Returns + ------- + u : ndarray, shape=(M, k) + Unitary matrix having left singular vectors as columns. + s : ndarray, shape=(k,) + The singular values. + vh : ndarray, shape=(k, N) + Unitary matrix having right singular vectors as rows. + + Notes + ----- + This is a naive implementation using ARPACK as an eigensolver + on ``A.conj().T @ A`` or ``A @ A.conj().T``, depending on which one is more + efficient. + + Examples + -------- + Construct a matrix ``A`` from singular values and vectors. + + >>> import numpy as np + >>> from scipy.stats import ortho_group + >>> from scipy.sparse import csc_array, diags_array + >>> from scipy.sparse.linalg import svds + >>> rng = np.random.default_rng() + >>> orthogonal = csc_array(ortho_group.rvs(10, random_state=rng)) + >>> s = [0.0001, 0.001, 3, 4, 5] # singular values + >>> u = orthogonal[:, :5] # left singular vectors + >>> vT = orthogonal[:, 5:].T # right singular vectors + >>> A = u @ diags_array(s) @ vT + + With only three singular values/vectors, the SVD approximates the original + matrix. + + >>> u2, s2, vT2 = svds(A, k=3, solver='arpack') + >>> A2 = u2 @ np.diag(s2) @ vT2 + >>> np.allclose(A2, A.toarray(), atol=1e-3) + True + + With all five singular values/vectors, we can reproduce the original + matrix. + + >>> u3, s3, vT3 = svds(A, k=5, solver='arpack') + >>> A3 = u3 @ np.diag(s3) @ vT3 + >>> np.allclose(A3, A.toarray()) + True + + The singular values match the expected singular values, and the singular + vectors are as expected up to a difference in sign. + + >>> (np.allclose(s3, s) and + ... np.allclose(np.abs(u3), np.abs(u.toarray())) and + ... np.allclose(np.abs(vT3), np.abs(vT.toarray()))) + True + + The singular vectors are also orthogonal. + + >>> (np.allclose(u3.T @ u3, np.eye(5)) and + ... np.allclose(vT3 @ vT3.T, np.eye(5))) + True + """ + pass + + +def _svds_lobpcg_doc(A, k=6, ncv=None, tol=0, which='LM', v0=None, + maxiter=None, return_singular_vectors=True, + solver='lobpcg', rng=None): + """ + Partial singular value decomposition of a sparse matrix using LOBPCG. + + Compute the largest or smallest `k` singular values and corresponding + singular vectors of a sparse matrix `A`. The order in which the singular + values are returned is not guaranteed. + + In the descriptions below, let ``M, N = A.shape``. + + Parameters + ---------- + A : sparse matrix or LinearOperator + Matrix to decompose. + k : int, default: 6 + Number of singular values and singular vectors to compute. + Must satisfy ``1 <= k <= min(M, N) - 1``. + ncv : int, optional + Ignored. + tol : float, optional + Tolerance for singular values. Zero (default) means machine precision. + which : {'LM', 'SM'} + Which `k` singular values to find: either the largest magnitude ('LM') + or smallest magnitude ('SM') singular values. + v0 : ndarray, optional + If `k` is 1, the starting vector for iteration: + an (approximate) left singular vector if ``N > M`` and a right singular + vector otherwise. Must be of length ``min(M, N)``. + Ignored otherwise. + Default: random + maxiter : int, default: 20 + Maximum number of iterations. + return_singular_vectors : {True, False, "u", "vh"} + Singular values are always computed and returned; this parameter + controls the computation and return of singular vectors. + + - ``True``: return singular vectors. + - ``False``: do not return singular vectors. + - ``"u"``: if ``M <= N``, compute only the left singular vectors and + return ``None`` for the right singular vectors. Otherwise, compute + all singular vectors. + - ``"vh"``: if ``M > N``, compute only the right singular vectors and + return ``None`` for the left singular vectors. Otherwise, compute + all singular vectors. + + solver : {'arpack', 'propack', 'lobpcg'}, optional + This is the solver-specific documentation for ``solver='lobpcg'``. + :ref:`'arpack' ` and + :ref:`'propack' ` + are also supported. + rng : `numpy.random.Generator`, optional + Pseudorandom number generator state. When `rng` is None, a new + `numpy.random.Generator` is created using entropy from the + operating system. Types other than `numpy.random.Generator` are + passed to `numpy.random.default_rng` to instantiate a ``Generator``. + options : dict, optional + A dictionary of solver-specific options. No solver-specific options + are currently supported; this parameter is reserved for future use. + + Returns + ------- + u : ndarray, shape=(M, k) + Unitary matrix having left singular vectors as columns. + s : ndarray, shape=(k,) + The singular values. + vh : ndarray, shape=(k, N) + Unitary matrix having right singular vectors as rows. + + Notes + ----- + This is a naive implementation using LOBPCG as an eigensolver + on ``A.conj().T @ A`` or ``A @ A.conj().T``, depending on which one is more + efficient. + + Examples + -------- + Construct a matrix ``A`` from singular values and vectors. + + >>> import numpy as np + >>> from scipy.stats import ortho_group + >>> from scipy.sparse import csc_array, diags_array + >>> from scipy.sparse.linalg import svds + >>> rng = np.random.default_rng() + >>> orthogonal = csc_array(ortho_group.rvs(10, random_state=rng)) + >>> s = [0.0001, 0.001, 3, 4, 5] # singular values + >>> u = orthogonal[:, :5] # left singular vectors + >>> vT = orthogonal[:, 5:].T # right singular vectors + >>> A = u @ diags_array(s) @ vT + + With only three singular values/vectors, the SVD approximates the original + matrix. + + >>> u2, s2, vT2 = svds(A, k=3, solver='lobpcg') + >>> A2 = u2 @ np.diag(s2) @ vT2 + >>> np.allclose(A2, A.toarray(), atol=1e-3) + True + + With all five singular values/vectors, we can reproduce the original + matrix. + + >>> u3, s3, vT3 = svds(A, k=5, solver='lobpcg') + >>> A3 = u3 @ np.diag(s3) @ vT3 + >>> np.allclose(A3, A.toarray()) + True + + The singular values match the expected singular values, and the singular + vectors are as expected up to a difference in sign. + + >>> (np.allclose(s3, s) and + ... np.allclose(np.abs(u3), np.abs(u.todense())) and + ... np.allclose(np.abs(vT3), np.abs(vT.todense()))) + True + + The singular vectors are also orthogonal. + + >>> (np.allclose(u3.T @ u3, np.eye(5)) and + ... np.allclose(vT3 @ vT3.T, np.eye(5))) + True + + """ + pass + + +def _svds_propack_doc(A, k=6, ncv=None, tol=0, which='LM', v0=None, + maxiter=None, return_singular_vectors=True, + solver='propack', rng=None): + """ + Partial singular value decomposition of a sparse matrix using PROPACK. + + Compute the largest or smallest `k` singular values and corresponding + singular vectors of a sparse matrix `A`. The order in which the singular + values are returned is not guaranteed. + + In the descriptions below, let ``M, N = A.shape``. + + Parameters + ---------- + A : sparse matrix or LinearOperator + Matrix to decompose. If `A` is a ``LinearOperator`` + object, it must define both ``matvec`` and ``rmatvec`` methods. + k : int, default: 6 + Number of singular values and singular vectors to compute. + Must satisfy ``1 <= k <= min(M, N)``. + ncv : int, optional + Ignored. + tol : float, optional + The desired relative accuracy for computed singular values. + Zero (default) means machine precision. + which : {'LM', 'SM'} + Which `k` singular values to find: either the largest magnitude ('LM') + or smallest magnitude ('SM') singular values. Note that choosing + ``which='SM'`` will force the ``irl`` option to be set ``True``. + v0 : ndarray, optional + Starting vector for iterations: must be of length ``A.shape[0]``. + If not specified, PROPACK will generate a starting vector. + maxiter : int, optional + Maximum number of iterations / maximal dimension of the Krylov + subspace. Default is ``10 * k``. + return_singular_vectors : {True, False, "u", "vh"} + Singular values are always computed and returned; this parameter + controls the computation and return of singular vectors. + + - ``True``: return singular vectors. + - ``False``: do not return singular vectors. + - ``"u"``: compute only the left singular vectors; return ``None`` for + the right singular vectors. + - ``"vh"``: compute only the right singular vectors; return ``None`` + for the left singular vectors. + + solver : {'arpack', 'propack', 'lobpcg'}, optional + This is the solver-specific documentation for ``solver='propack'``. + :ref:`'arpack' ` and + :ref:`'lobpcg' ` + are also supported. + rng : `numpy.random.Generator`, optional + Pseudorandom number generator state. When `rng` is None, a new + `numpy.random.Generator` is created using entropy from the + operating system. Types other than `numpy.random.Generator` are + passed to `numpy.random.default_rng` to instantiate a ``Generator``. + options : dict, optional + A dictionary of solver-specific options. No solver-specific options + are currently supported; this parameter is reserved for future use. + + Returns + ------- + u : ndarray, shape=(M, k) + Unitary matrix having left singular vectors as columns. + s : ndarray, shape=(k,) + The singular values. + vh : ndarray, shape=(k, N) + Unitary matrix having right singular vectors as rows. + + Notes + ----- + This is an interface to the Fortran library PROPACK [1]_. + The current default is to run with IRL mode disabled unless seeking the + smallest singular values/vectors (``which='SM'``). + + References + ---------- + + .. [1] Larsen, Rasmus Munk. "PROPACK-Software for large and sparse SVD + calculations." Available online. URL + http://sun.stanford.edu/~rmunk/PROPACK (2004): 2008-2009. + + Examples + -------- + Construct a matrix ``A`` from singular values and vectors. + + >>> import numpy as np + >>> from scipy.stats import ortho_group + >>> from scipy.sparse import csc_array, diags_array + >>> from scipy.sparse.linalg import svds + >>> rng = np.random.default_rng() + >>> orthogonal = csc_array(ortho_group.rvs(10, random_state=rng)) + >>> s = [0.0001, 0.001, 3, 4, 5] # singular values + >>> u = orthogonal[:, :5] # left singular vectors + >>> vT = orthogonal[:, 5:].T # right singular vectors + >>> A = u @ diags_array(s) @ vT + + With only three singular values/vectors, the SVD approximates the original + matrix. + + >>> u2, s2, vT2 = svds(A, k=3, solver='propack') + >>> A2 = u2 @ np.diag(s2) @ vT2 + >>> np.allclose(A2, A.todense(), atol=1e-3) + True + + With all five singular values/vectors, we can reproduce the original + matrix. + + >>> u3, s3, vT3 = svds(A, k=5, solver='propack') + >>> A3 = u3 @ np.diag(s3) @ vT3 + >>> np.allclose(A3, A.todense()) + True + + The singular values match the expected singular values, and the singular + vectors are as expected up to a difference in sign. + + >>> (np.allclose(s3, s) and + ... np.allclose(np.abs(u3), np.abs(u.toarray())) and + ... np.allclose(np.abs(vT3), np.abs(vT.toarray()))) + True + + The singular vectors are also orthogonal. + + >>> (np.allclose(u3.T @ u3, np.eye(5)) and + ... np.allclose(vT3 @ vT3.T, np.eye(5))) + True + + """ + pass diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/arpack/__init__.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/arpack/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..679b94480d7ff5a11e037ffb758f2214c6e5097f --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/arpack/__init__.py @@ -0,0 +1,20 @@ +""" +Eigenvalue solver using iterative methods. + +Find k eigenvectors and eigenvalues of a matrix A using the +Arnoldi/Lanczos iterative methods from ARPACK [1]_,[2]_. + +These methods are most useful for large sparse matrices. + + - eigs(A,k) + - eigsh(A,k) + +References +---------- +.. [1] ARPACK Software, http://www.caam.rice.edu/software/ARPACK/ +.. [2] R. B. Lehoucq, D. C. Sorensen, and C. Yang, ARPACK USERS GUIDE: + Solution of Large Scale Eigenvalue Problems by Implicitly Restarted + Arnoldi Methods. SIAM, Philadelphia, PA, 1998. + +""" +from .arpack import * diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/arpack/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/arpack/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..244ff88b306e4b2d8c00a568a44244633b04aef5 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/arpack/__pycache__/__init__.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/arpack/__pycache__/arpack.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/arpack/__pycache__/arpack.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8fd98d689eff669ffbeb3b63824ae12c1354e928 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/arpack/__pycache__/arpack.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/arpack/_arpacklib.cpython-312-x86_64-linux-gnu.so b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/arpack/_arpacklib.cpython-312-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..8d183b9951f6794a6a317b4e75e557172a9ac6cc --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/arpack/_arpacklib.cpython-312-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:be1f166a30bc7ab506a81922430d82b16ad2013cb0dfb7917ea15510106b46c3 +size 223721 diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/arpack/arpack.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/arpack/arpack.py new file mode 100644 index 0000000000000000000000000000000000000000..fb706eb66e2a69f55230970becdcdeaab3b45d03 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/arpack/arpack.py @@ -0,0 +1,1789 @@ +""" +Find a few eigenvectors and eigenvalues of a matrix. + + +Uses ARPACK: https://github.com/opencollab/arpack-ng + +""" +# Wrapper implementation notes +# +# ARPACK Entry Points +# ------------------- +# The entry points to ARPACK are +# - (s,d)seupd : single and double precision symmetric matrix +# - (s,d,c,z)neupd: single,double,complex,double complex general matrix +# This wrapper puts the *neupd (general matrix) interfaces in eigs() +# and the *seupd (symmetric matrix) in eigsh(). +# There is no specialized interface for complex Hermitian matrices. +# To find eigenvalues of a complex Hermitian matrix you +# may use eigsh(), but eigsh() will simply call eigs() +# and return the real part of the eigenvalues thus obtained. + +# Number of eigenvalues returned and complex eigenvalues +# ------------------------------------------------------ +# The ARPACK nonsymmetric real and double interface (s,d)naupd return +# eigenvalues and eigenvectors in real (float,double) arrays. +# Since the eigenvalues and eigenvectors are, in general, complex +# ARPACK puts the real and imaginary parts in consecutive entries +# in real-valued arrays. This wrapper puts the real entries +# into complex data types and attempts to return the requested eigenvalues +# and eigenvectors. + + +# Solver modes +# ------------ +# ARPACK and handle shifted and shift-inverse computations +# for eigenvalues by providing a shift (sigma) and a solver. + +import numpy as np +import warnings +from scipy.sparse.linalg._interface import aslinearoperator, LinearOperator +from scipy.sparse import eye_array, issparse +from scipy.linalg import eig, eigh, lu_factor, lu_solve +from scipy.linalg.lapack import HAS_ILP64 +from scipy.sparse._sputils import ( + convert_pydata_sparse_to_scipy, isdense, is_pydata_spmatrix, +) +from scipy.sparse.linalg import gmres, splu + +from . import _arpacklib + +_int_dtype = np.int64 if HAS_ILP64 else np.int32 + +__docformat__ = "restructuredtext en" + +__all__ = ['eigs', 'eigsh', 'ArpackError', 'ArpackNoConvergence'] + + +_type_conv = {'f': 's', 'd': 'd', 'F': 'c', 'D': 'z'} +_ndigits = {'f': 5, 'd': 12, 'F': 5, 'D': 12} + +DNAUPD_ERRORS = { + 0: "Normal exit.", + 1: "Maximum number of iterations taken. " + "All possible eigenvalues of OP has been found. IPARAM(5) " + "returns the number of wanted converged Ritz values.", + 2: "No longer an informational error. Deprecated starting " + "with release 2 of ARPACK.", + 3: "No shifts could be applied during a cycle of the " + "Implicitly restarted Arnoldi iteration. One possibility " + "is to increase the size of NCV relative to NEV. ", + -1: "N must be positive.", + -2: "NEV must be positive.", + -3: "NCV-NEV >= 2 and less than or equal to N.", + -4: "The maximum number of Arnoldi update iterations allowed " + "must be greater than zero.", + -5: " WHICH must be one of 'LM', 'SM', 'LR', 'SR', 'LI', 'SI'", + -6: "BMAT must be one of 'I' or 'G'.", + -7: "Length of private work array WORKL is not sufficient.", + -8: "Error return from LAPACK eigenvalue calculation;", + -9: "Starting vector is zero.", + -10: "IPARAM(7) must be 1,2,3,4.", + -11: "IPARAM(7) = 1 and BMAT = 'G' are incompatible.", + -12: "IPARAM(1) must be equal to 0 or 1.", + -13: "NEV and WHICH = 'BE' are incompatible.", + -9999: "Could not build an Arnoldi factorization. " + "IPARAM(5) returns the size of the current Arnoldi " + "factorization. The user is advised to check that " + "enough workspace and array storage has been allocated." +} + +SNAUPD_ERRORS = DNAUPD_ERRORS + +ZNAUPD_ERRORS = DNAUPD_ERRORS.copy() +ZNAUPD_ERRORS[-10] = "IPARAM(7) must be 1,2,3." + +CNAUPD_ERRORS = ZNAUPD_ERRORS + +DSAUPD_ERRORS = { + 0: "Normal exit.", + 1: "Maximum number of iterations taken. " + "All possible eigenvalues of OP has been found.", + 2: "No longer an informational error. Deprecated starting with " + "release 2 of ARPACK.", + 3: "No shifts could be applied during a cycle of the Implicitly " + "restarted Arnoldi iteration. One possibility is to increase " + "the size of NCV relative to NEV. ", + -1: "N must be positive.", + -2: "NEV must be positive.", + -3: "NCV must be greater than NEV and less than or equal to N.", + -4: "The maximum number of Arnoldi update iterations allowed " + "must be greater than zero.", + -5: "WHICH must be one of 'LM', 'SM', 'LA', 'SA' or 'BE'.", + -6: "BMAT must be one of 'I' or 'G'.", + -7: "Length of private work array WORKL is not sufficient.", + -8: "Error return from trid. eigenvalue calculation; " + "Informational error from LAPACK routine dsteqr .", + -9: "Starting vector is zero.", + -10: "IPARAM(7) must be 1,2,3,4,5.", + -11: "IPARAM(7) = 1 and BMAT = 'G' are incompatible.", + -12: "IPARAM(1) must be equal to 0 or 1.", + -13: "NEV and WHICH = 'BE' are incompatible. ", + -9999: "Could not build an Arnoldi factorization. " + "IPARAM(5) returns the size of the current Arnoldi " + "factorization. The user is advised to check that " + "enough workspace and array storage has been allocated.", +} + +SSAUPD_ERRORS = DSAUPD_ERRORS + +DNEUPD_ERRORS = { + 0: "Normal exit.", + 1: "The Schur form computed by LAPACK routine dlahqr " + "could not be reordered by LAPACK routine dtrsen. " + "Re-enter subroutine dneupd with IPARAM(5)NCV and " + "increase the size of the arrays DR and DI to have " + "dimension at least dimension NCV and allocate at least NCV " + "columns for Z. NOTE: Not necessary if Z and V share " + "the same space. Please notify the authors if this error" + "occurs.", + -1: "N must be positive.", + -2: "NEV must be positive.", + -3: "NCV-NEV >= 2 and less than or equal to N.", + -5: "WHICH must be one of 'LM', 'SM', 'LR', 'SR', 'LI', 'SI'", + -6: "BMAT must be one of 'I' or 'G'.", + -7: "Length of private work WORKL array is not sufficient.", + -8: "Error return from calculation of a real Schur form. " + "Informational error from LAPACK routine dlahqr .", + -9: "Error return from calculation of eigenvectors. " + "Informational error from LAPACK routine dtrevc.", + -10: "IPARAM(7) must be 1,2,3,4.", + -11: "IPARAM(7) = 1 and BMAT = 'G' are incompatible.", + -12: "HOWMNY = 'S' not yet implemented", + -13: "HOWMNY must be one of 'A' or 'P' if RVEC = .true.", + -14: "DNAUPD did not find any eigenvalues to sufficient " + "accuracy.", + -15: "DNEUPD got a different count of the number of converged " + "Ritz values than DNAUPD got. This indicates the user " + "probably made an error in passing data from DNAUPD to " + "DNEUPD or that the data was modified before entering " + "DNEUPD", +} + +SNEUPD_ERRORS = DNEUPD_ERRORS.copy() +SNEUPD_ERRORS[1] = ("The Schur form computed by LAPACK routine slahqr " + "could not be reordered by LAPACK routine strsen . " + "Re-enter subroutine dneupd with IPARAM(5)=NCV and " + "increase the size of the arrays DR and DI to have " + "dimension at least dimension NCV and allocate at least " + "NCV columns for Z. NOTE: Not necessary if Z and V share " + "the same space. Please notify the authors if this error " + "occurs.") +SNEUPD_ERRORS[-14] = ("SNAUPD did not find any eigenvalues to sufficient " + "accuracy.") +SNEUPD_ERRORS[-15] = ("SNEUPD got a different count of the number of " + "converged Ritz values than SNAUPD got. This indicates " + "the user probably made an error in passing data from " + "SNAUPD to SNEUPD or that the data was modified before " + "entering SNEUPD") + +ZNEUPD_ERRORS = {0: "Normal exit.", + 1: "The Schur form computed by LAPACK routine csheqr " + "could not be reordered by LAPACK routine ztrsen. " + "Re-enter subroutine zneupd with IPARAM(5)=NCV and " + "increase the size of the array D to have " + "dimension at least dimension NCV and allocate at least " + "NCV columns for Z. NOTE: Not necessary if Z and V share " + "the same space. Please notify the authors if this error " + "occurs.", + -1: "N must be positive.", + -2: "NEV must be positive.", + -3: "NCV-NEV >= 1 and less than or equal to N.", + -5: "WHICH must be one of 'LM', 'SM', 'LR', 'SR', 'LI', 'SI'", + -6: "BMAT must be one of 'I' or 'G'.", + -7: "Length of private work WORKL array is not sufficient.", + -8: "Error return from LAPACK eigenvalue calculation. " + "This should never happened.", + -9: "Error return from calculation of eigenvectors. " + "Informational error from LAPACK routine ztrevc.", + -10: "IPARAM(7) must be 1,2,3", + -11: "IPARAM(7) = 1 and BMAT = 'G' are incompatible.", + -12: "HOWMNY = 'S' not yet implemented", + -13: "HOWMNY must be one of 'A' or 'P' if RVEC = .true.", + -14: "ZNAUPD did not find any eigenvalues to sufficient " + "accuracy.", + -15: "ZNEUPD got a different count of the number of " + "converged Ritz values than ZNAUPD got. This " + "indicates the user probably made an error in passing " + "data from ZNAUPD to ZNEUPD or that the data was " + "modified before entering ZNEUPD" + } + +CNEUPD_ERRORS = ZNEUPD_ERRORS.copy() +CNEUPD_ERRORS[-14] = ("CNAUPD did not find any eigenvalues to sufficient " + "accuracy.") +CNEUPD_ERRORS[-15] = ("CNEUPD got a different count of the number of " + "converged Ritz values than CNAUPD got. This indicates " + "the user probably made an error in passing data from " + "CNAUPD to CNEUPD or that the data was modified before " + "entering CNEUPD") + +DSEUPD_ERRORS = { + 0: "Normal exit.", + -1: "N must be positive.", + -2: "NEV must be positive.", + -3: "NCV must be greater than NEV and less than or equal to N.", + -5: "WHICH must be one of 'LM', 'SM', 'LA', 'SA' or 'BE'.", + -6: "BMAT must be one of 'I' or 'G'.", + -7: "Length of private work WORKL array is not sufficient.", + -8: ("Error return from trid. eigenvalue calculation; " + "Information error from LAPACK routine dsteqr."), + -9: "Starting vector is zero.", + -10: "IPARAM(7) must be 1,2,3,4,5.", + -11: "IPARAM(7) = 1 and BMAT = 'G' are incompatible.", + -12: "NEV and WHICH = 'BE' are incompatible.", + -14: "DSAUPD did not find any eigenvalues to sufficient accuracy.", + -15: "HOWMNY must be one of 'A' or 'S' if RVEC = .true.", + -16: "HOWMNY = 'S' not yet implemented", + -17: ("DSEUPD got a different count of the number of converged " + "Ritz values than DSAUPD got. This indicates the user " + "probably made an error in passing data from DSAUPD to " + "DSEUPD or that the data was modified before entering " + "DSEUPD.") +} + +SSEUPD_ERRORS = DSEUPD_ERRORS.copy() +SSEUPD_ERRORS[-14] = ("SSAUPD did not find any eigenvalues " + "to sufficient accuracy.") +SSEUPD_ERRORS[-17] = ("SSEUPD got a different count of the number of " + "converged " + "Ritz values than SSAUPD got. This indicates the user " + "probably made an error in passing data from SSAUPD to " + "SSEUPD or that the data was modified before entering " + "SSEUPD.") + +_SAUPD_ERRORS = {'d': DSAUPD_ERRORS, + 's': SSAUPD_ERRORS} +_NAUPD_ERRORS = {'d': DNAUPD_ERRORS, + 's': SNAUPD_ERRORS, + 'z': ZNAUPD_ERRORS, + 'c': CNAUPD_ERRORS} +_SEUPD_ERRORS = {'d': DSEUPD_ERRORS, + 's': SSEUPD_ERRORS} +_NEUPD_ERRORS = {'d': DNEUPD_ERRORS, + 's': SNEUPD_ERRORS, + 'z': ZNEUPD_ERRORS, + 'c': CNEUPD_ERRORS} + +# accepted values of parameter WHICH in _SEUPD +_SEUPD_WHICH = ['LM', 'SM', 'LA', 'SA', 'BE'] + +# accepted values of parameter WHICH in _NAUPD +_NEUPD_WHICH = ['LM', 'SM', 'LR', 'SR', 'LI', 'SI'] + +# The enum values for the parameter WHICH in _NAUPD and _SEUPD +WHICH_DICT = { + 'LM': 0, 'SM': 1, 'LR': 2, 'SR': 3, 'LI': 4, 'SI': 5, 'LA': 6, 'SA': 7, 'BE': 8 +} + +# The enum values for the parameter HOWMNY in _NEUPD and _SEUPD +HOWMNY_DICT = {'A': 0, 'P': 1, 'S': 2} + +class ArpackError(RuntimeError): + """ + ARPACK error. + + Parameters + ---------- + info : int or str + Key for `infodict` to get error message. + infodict : dict, optional + Dictionary mapping `info` keys to error messages. + """ + + def __init__(self, info, infodict=None): + if infodict is None: + infodict = _NAUPD_ERRORS + + msg = infodict.get(info, "Unknown error") + super().__init__(f"ARPACK error {info}: {msg}") + + + +class ArpackNoConvergence(ArpackError): + """ + ARPACK iteration did not converge. + + Parameters + ---------- + msg : str + Error message. + eigenvalues : ndarray + Conveged eigenvalues. + eigenvectors : ndarray + Converged eigenvectors. + + Attributes + ---------- + eigenvalues : ndarray + Partial result. Converged eigenvalues. + eigenvectors : ndarray + Partial result. Converged eigenvectors. + + """ + + def __init__(self, msg, eigenvalues, eigenvectors): + ArpackError.__init__(self, -1, {-1: msg}) + self.eigenvalues = eigenvalues + self.eigenvectors = eigenvectors + + +def choose_ncv(k): + """ + Choose number of lanczos vectors based on target number + of singular/eigen values and vectors to compute, k. + """ + return max(2 * k + 1, 20) + + +class _ArpackParams: + def __init__(self, n, k, tp, rng, mode=1, sigma=None, ncv=None, v0=None, + maxiter=None, which="LM", tol=0): + if k <= 0: + raise ValueError(f"k must be positive, k={k}") + + if maxiter is None: + maxiter = n * 10 + if maxiter <= 0: + raise ValueError(f"maxiter must be positive, maxiter={maxiter}") + + if tp not in 'fdFD': + # Use `float64` libraries from integer dtypes. + if np.can_cast(tp, 'd'): + tp = 'd' + else: + raise ValueError("matrix type must be 'f', 'd', 'F', or 'D'") + + if v0 is not None: + # ARPACK overwrites its initial resid, make a copy + self.resid = np.array(v0, copy=True, dtype=tp) + info = 1 + else: + # ARPACK will use a random initial vector. + self.resid = rng.uniform(low=-1.0, high=1.0, size=[n]).astype(tp) + info = 0 + + if sigma is None: + #sigma not used + self.sigma = 0 + else: + self.sigma = sigma + + if ncv is None: + ncv = choose_ncv(k) + self.ncv = min(ncv, n) + self.n = n + self.v = np.zeros((n, ncv), tp) # holds Ritz vectors + self.which = which + + # set solver mode and parameters + self.mode = mode + self.arpack_dict = { + 'tol': tol, + 'getv0_rnorm0': 0.0, + 'aitr_betaj': 0.0, + 'aitr_rnorm1': 0.0, + 'aitr_wnorm': 0.0, + 'aup2_rnorm': 0.0, + 'ido': 0, + 'which': WHICH_DICT[which], + 'bmat': 0, + 'info': info, + 'iter': 0, + 'maxiter': int(maxiter), + 'mode': mode, + 'n': n, + 'nconv': 0, + 'ncv': self.ncv, + 'nev': k, + 'np': 0, + 'shift': 1, + 'getv0_first': 0, + 'getv0_iter': 0, + 'getv0_itry': 0, + 'getv0_orth': 0, + 'aitr_iter': 0, + 'aitr_j': 0, + 'aitr_orth1': 0, + 'aitr_orth2': 0, + 'aitr_restart': 0, + 'aitr_step3': 0, + 'aitr_step4': 0, + 'aitr_ierr': 0, + 'aup2_initv': 0, + 'aup2_iter': 0, + 'aup2_getv0': 0, + 'aup2_cnorm': 0, + 'aup2_kplusp': 0, + 'aup2_nev': 0, + 'aup2_nev0': 0, + 'aup2_np0': 0, + 'aup2_numcnv': 0, + 'aup2_update': 0, + 'aup2_ushift': 0, + } + + self.k = k + self.tp = tp + self.converged = False + + def _raise_no_convergence(self): + msg = "No convergence (%d iterations, %d/%d eigenvectors converged)" + k_ok = self.arpack_dict['nconv'] + num_iter = self.arpack_dict['iter'] + try: + ev, vec = self.extract(True) + except ArpackError as err: + msg = f"{msg} [{err}]" + ev = np.zeros((0,)) + vec = np.zeros((self.n, 0)) + k_ok = 0 + raise ArpackNoConvergence(f"No convergence ({num_iter} iterations, " + f"{k_ok}/{self.k} eigenvectors converged)", + ev, vec) + + +class _SymmetricArpackParams(_ArpackParams): + def __init__(self, n, k, tp, matvec, mode=1, M_matvec=None, Minv_matvec=None, + sigma=None, ncv=None, v0=None, maxiter=None, which="LM", tol=0, + rng=None): + # The following modes are supported: + # mode = 1: + # Solve the standard eigenvalue problem: + # A*x = lambda*x : + # A - symmetric + # Arguments should be + # matvec = left multiplication by A + # M_matvec = None [not used] + # Minv_matvec = None [not used] + # + # mode = 2: + # Solve the general eigenvalue problem: + # A*x = lambda*M*x + # A - symmetric + # M - symmetric positive definite + # Arguments should be + # matvec = left multiplication by A + # M_matvec = left multiplication by M + # Minv_matvec = left multiplication by M^-1 + # + # mode = 3: + # Solve the general eigenvalue problem in shift-invert mode: + # A*x = lambda*M*x + # A - symmetric + # M - symmetric positive semi-definite + # Arguments should be + # matvec = None [not used] + # M_matvec = left multiplication by M + # or None, if M is the identity + # Minv_matvec = left multiplication by [A-sigma*M]^-1 + # + # mode = 4: + # Solve the general eigenvalue problem in Buckling mode: + # A*x = lambda*AG*x + # A - symmetric positive semi-definite + # AG - symmetric indefinite + # Arguments should be + # matvec = left multiplication by A + # M_matvec = None [not used] + # Minv_matvec = left multiplication by [A-sigma*AG]^-1 + # + # mode = 5: + # Solve the general eigenvalue problem in Cayley-transformed mode: + # A*x = lambda*M*x + # A - symmetric + # M - symmetric positive semi-definite + # Arguments should be + # matvec = left multiplication by A + # M_matvec = left multiplication by M + # or None, if M is the identity + # Minv_matvec = left multiplication by [A-sigma*M]^-1 + if mode == 1: + if matvec is None: + raise ValueError("matvec must be specified for mode=1") + if M_matvec is not None: + raise ValueError("M_matvec cannot be specified for mode=1") + if Minv_matvec is not None: + raise ValueError("Minv_matvec cannot be specified for mode=1") + + self.OP = matvec + self.B = lambda x: x + self.bmat = 'I' + elif mode == 2: + if matvec is None: + raise ValueError("matvec must be specified for mode=2") + if M_matvec is None: + raise ValueError("M_matvec must be specified for mode=2") + if Minv_matvec is None: + raise ValueError("Minv_matvec must be specified for mode=2") + + self.OP = lambda x: Minv_matvec(matvec(x)) + self.OPa = Minv_matvec + self.OPb = matvec + self.B = M_matvec + self.bmat = 'G' + elif mode == 3: + if matvec is not None: + raise ValueError("matvec must not be specified for mode=3") + if Minv_matvec is None: + raise ValueError("Minv_matvec must be specified for mode=3") + + if M_matvec is None: + self.OP = Minv_matvec + self.OPa = Minv_matvec + self.B = lambda x: x + self.bmat = 'I' + else: + self.OP = lambda x: Minv_matvec(M_matvec(x)) + self.OPa = Minv_matvec + self.B = M_matvec + self.bmat = 'G' + elif mode == 4: + if matvec is None: + raise ValueError("matvec must be specified for mode=4") + if M_matvec is not None: + raise ValueError("M_matvec must not be specified for mode=4") + if Minv_matvec is None: + raise ValueError("Minv_matvec must be specified for mode=4") + self.OPa = Minv_matvec + self.OP = lambda x: self.OPa(matvec(x)) + self.B = matvec + self.bmat = 'G' + elif mode == 5: + if matvec is None: + raise ValueError("matvec must be specified for mode=5") + if Minv_matvec is None: + raise ValueError("Minv_matvec must be specified for mode=5") + + self.OPa = Minv_matvec + self.A_matvec = matvec + + if M_matvec is None: + self.OP = lambda x: Minv_matvec(matvec(x) + sigma * x) + self.B = lambda x: x + self.bmat = 'I' + else: + self.OP = lambda x: Minv_matvec(matvec(x) + + sigma * M_matvec(x)) + self.B = M_matvec + self.bmat = 'G' + else: + raise ValueError(f"mode={mode} not implemented") + + if which not in _SEUPD_WHICH: + raise ValueError(f"which must be one of {' '.join(_SEUPD_WHICH)}") + if k >= n: + raise ValueError(f"k must be less than ndim(A), k={k}") + + self.rng = np.random.default_rng(rng) + _ArpackParams.__init__(self, n, k, tp, self.rng, mode, sigma, ncv, v0, + maxiter, which, tol) + + self.arpack_dict['bmat'] = 0 if self.bmat == 'I' else 1 + + if self.ncv > n or self.ncv <= k: + raise ValueError(f"ncv must be k= n - 1: + raise ValueError(f"k must be less than ndim(A)-1, k={k}") + + self.rng = np.random.default_rng(rng) + _ArpackParams.__init__(self, n, k, tp, rng, mode, sigma, ncv, v0, maxiter, + which, tol) + + self.arpack_dict['bmat'] = 0 if self.bmat == 'I' else 1 + + if self.ncv > n or self.ncv <= k + 1: + raise ValueError(f"ncv must be k+1 k, so we'll + # throw out this case. + nreturned -= 1 + i += 1 + + else: + # real matrix, mode 3 or 4, imag(sigma) is nonzero: + # see remark 3 in neupd.f + # Build complex eigenvalues from real and imaginary parts + i = 0 + while i <= k: + if abs(d[i].imag) == 0: + d[i] = np.dot(zr[:, i], self.matvec(zr[:, i])) + else: + if i < k: + z[:, i] = zr[:, i] + 1.0j * zr[:, i + 1] + z[:, i + 1] = z[:, i].conjugate() + d[i] = ((np.dot(zr[:, i], + self.matvec(zr[:, i])) + + np.dot(zr[:, i + 1], + self.matvec(zr[:, i + 1]))) + + 1j * (np.dot(zr[:, i], + self.matvec(zr[:, i + 1])) + - np.dot(zr[:, i + 1], + self.matvec(zr[:, i])))) + d[i + 1] = d[i].conj() + i += 1 + else: + #last eigenvalue is complex: the imaginary part of + # the eigenvector has not been returned + #this can only happen if nreturned > k, so we'll + # throw out this case. + nreturned -= 1 + i += 1 + + # Now we have k+1 possible eigenvalues and eigenvectors + # Return the ones specified by the keyword "which" + + if nreturned <= k: + # we got less or equal as many eigenvalues we wanted + d = d[:nreturned] + z = z[:, :nreturned] + else: + # we got one extra eigenvalue (likely a cc pair, but which?) + if self.mode in (1, 2): + rd = d + elif self.mode in (3, 4): + rd = 1 / (d - self.sigma) + + if self.which in ['LR', 'SR']: + ind = np.argsort(rd.real) + elif self.which in ['LI', 'SI']: + # for LI,SI ARPACK returns largest,smallest + # abs(imaginary) (complex pairs come together) + ind = np.argsort(abs(rd.imag)) + else: + ind = np.argsort(abs(rd)) + + if self.which in ['LR', 'LM', 'LI']: + ind = ind[-k:][::-1] + elif self.which in ['SR', 'SM', 'SI']: + ind = ind[:k] + + d = d[ind] + z = z[:, ind] + else: + d = np.zeros([k], dtype=self.tp) + z = np.zeros([n, k], dtype=self.tp, order='F') + self._arpack_extract( + self.arpack_dict, return_eigenvectors, howmny, sselect, d, z, + self.sigma, workev, self.resid, self.v, self.ipntr, self.workd, + self.workl, self.rwork) + + ierr = self.arpack_dict['info'] + + if ierr != 0: + raise ArpackError(ierr, infodict=self.extract_infodict) + + k_ok = self.arpack_dict['nconv'] + d = d[:k_ok] + z = z[:, :k_ok] + + if return_eigenvectors: + return d, z + else: + return d + +class SpLuInv(LinearOperator): + """ + SpLuInv: + helper class to repeatedly solve M*x=b + using a sparse LU-decomposition of M + """ + + def __init__(self, M): + self.M_lu = splu(M) + super().__init__(dtype=M.dtype, shape=M.shape) + self.isreal = not np.issubdtype(self.dtype, np.complexfloating) + + def _matvec(self, x): + # careful here: splu.solve will throw away imaginary + # part of x if M is real + x = np.asarray(x) + if self.isreal and np.issubdtype(x.dtype, np.complexfloating): + return (self.M_lu.solve(np.real(x).astype(self.dtype)) + + 1j * self.M_lu.solve(np.imag(x).astype(self.dtype))) + else: + return self.M_lu.solve(x.astype(self.dtype)) + + +class LuInv(LinearOperator): + """ + LuInv: + helper class to repeatedly solve M*x=b + using an LU-decomposition of M + """ + + def __init__(self, M): + self.M_lu = lu_factor(M) + super().__init__(dtype=M.dtype, shape=M.shape) + + def _matvec(self, x): + return lu_solve(self.M_lu, x) + + +def gmres_loose(A, b, tol): + """ + gmres with looser termination condition. + """ + b = np.asarray(b) + min_tol = 1000 * np.sqrt(b.size) * np.finfo(b.dtype).eps + return gmres(A, b, rtol=max(tol, min_tol), atol=0) + + +class IterInv(LinearOperator): + """ + IterInv: + helper class to repeatedly solve M*x=b + using an iterative method. + """ + + def __init__(self, M, ifunc=gmres_loose, tol=0): + self.M = M + if hasattr(M, 'dtype'): + dtype = M.dtype + else: + x = np.zeros(M.shape[1]) + dtype = (M * x).dtype + super().__init__(dtype=dtype, shape=M.shape) + + if tol <= 0: + # when tol=0, ARPACK uses machine tolerance as calculated + # by LAPACK's _LAMCH function. We should match this + tol = 2 * np.finfo(self.dtype).eps + self.ifunc = ifunc + self.tol = tol + + def _matvec(self, x): + b, info = self.ifunc(self.M, x, tol=self.tol) + if info != 0: + raise ValueError( + f"Error in inverting M: function {self.ifunc.__name__} " + f"did not converge (info = {info})." + ) + return b + + +class IterOpInv(LinearOperator): + """ + IterOpInv: + helper class to repeatedly solve [A-sigma*M]*x = b + using an iterative method + """ + + def __init__(self, A, M, sigma, ifunc=gmres_loose, tol=0): + self.A = A + self.M = M + self.sigma = sigma + + def mult_func(x): + return A.matvec(x) - sigma * M.matvec(x) + + def mult_func_M_None(x): + return A.matvec(x) - sigma * x + + x = np.zeros(A.shape[1]) + if M is None: + dtype = mult_func_M_None(x).dtype + self.OP = LinearOperator(self.A.shape, + mult_func_M_None, + dtype=dtype) + else: + dtype = mult_func(x).dtype + self.OP = LinearOperator(self.A.shape, + mult_func, + dtype=dtype) + super().__init__(dtype=dtype, shape=A.shape) + + if tol <= 0: + # when tol=0, ARPACK uses machine tolerance as calculated + # by LAPACK's _LAMCH function. We should match this + tol = 2 * np.finfo(self.OP.dtype).eps + self.ifunc = ifunc + self.tol = tol + + def _matvec(self, x): + b, info = self.ifunc(self.OP, x, tol=self.tol) + if info != 0: + raise ValueError( + f"Error in inverting [A-sigma*M]: function {self.ifunc.__name__} " + f"did not converge (info = {info})." + ) + return b + + +def _fast_spmatrix_to_csc(A, hermitian=False): + """Convert sparse matrix to CSC (by transposing, if possible)""" + if (A.format == "csr" and hermitian + and not np.issubdtype(A.dtype, np.complexfloating)): + return A.T + elif is_pydata_spmatrix(A): + # No need to convert + return A + else: + return A.tocsc() + + +def get_inv_matvec(M, hermitian=False, tol=0): + if isdense(M): + return LuInv(M).matvec + elif issparse(M) or is_pydata_spmatrix(M): + M = _fast_spmatrix_to_csc(M, hermitian=hermitian) + return SpLuInv(M).matvec + else: + return IterInv(M, tol=tol).matvec + + +def get_OPinv_matvec(A, M, sigma, hermitian=False, tol=0): + if sigma == 0: + return get_inv_matvec(A, hermitian=hermitian, tol=tol) + + if M is None: + #M is the identity matrix + if isdense(A): + if (np.issubdtype(A.dtype, np.complexfloating) + or np.imag(sigma) == 0): + A = np.copy(A) + else: + A = A + 0j + A.flat[::A.shape[1] + 1] -= sigma + return LuInv(A).matvec + elif issparse(A) or is_pydata_spmatrix(A): + A = A - sigma * eye_array(A.shape[0]) + A = _fast_spmatrix_to_csc(A, hermitian=hermitian) + return SpLuInv(A).matvec + else: + return IterOpInv(aslinearoperator(A), + M, sigma, tol=tol).matvec + else: + if ((not isdense(A) and not issparse(A) and not is_pydata_spmatrix(A)) or + (not isdense(M) and not issparse(M) and not is_pydata_spmatrix(M))): + return IterOpInv(aslinearoperator(A), + aslinearoperator(M), + sigma, tol=tol).matvec + elif isdense(A) or isdense(M): + return LuInv(A - sigma * M).matvec + else: + OP = A - sigma * M + OP = _fast_spmatrix_to_csc(OP, hermitian=hermitian) + return SpLuInv(OP).matvec + + +def eigs(A, k=6, M=None, sigma=None, which='LM', v0=None, + ncv=None, maxiter=None, tol=0, return_eigenvectors=True, + Minv=None, OPinv=None, OPpart=None, rng=None): + """ + Find k eigenvalues and eigenvectors of the square matrix A. + + Solves ``A @ x[i] = w[i] * x[i]``, the standard eigenvalue problem + for w[i] eigenvalues with corresponding eigenvectors x[i]. + + If M is specified, solves ``A @ x[i] = w[i] * M @ x[i]``, the + generalized eigenvalue problem for w[i] eigenvalues + with corresponding eigenvectors x[i] + + Parameters + ---------- + A : ndarray, sparse matrix or LinearOperator + An array, sparse matrix, or LinearOperator representing + the operation ``A @ x``, where A is a real or complex square matrix. + k : int, optional + The number of eigenvalues and eigenvectors desired. + `k` must be smaller than N-1. It is not possible to compute all + eigenvectors of a matrix. + M : ndarray, sparse matrix or LinearOperator, optional + An array, sparse matrix, or LinearOperator representing + the operation M@x for the generalized eigenvalue problem:: + + A @ x = w * M @ x. + + M must represent a real symmetric matrix if A is real, and must + represent a complex Hermitian matrix if A is complex. For best + results, the data type of M should be the same as that of A. + Additionally: + + - If `sigma` is None, M is positive definite + - If `sigma` is specified, M is positive semi-definite + + If sigma is None, eigs requires an operator to compute the solution + of the linear equation ``M @ x = b``. This is done internally via a + (sparse) LU decomposition for an explicit matrix M, or via an + iterative solver for a general linear operator. Alternatively, + the user can supply the matrix or operator Minv, which gives + ``x = Minv @ b = M^-1 @ b``. + sigma : real or complex, optional + Find eigenvalues near sigma using shift-invert mode. This requires + an operator to compute the solution of the linear system + ``[A - sigma * M] @ x = b``, where M is the identity matrix if + unspecified. This is computed internally via a (sparse) LU + decomposition for explicit matrices A & M, or via an iterative + solver if either A or M is a general linear operator. + Alternatively, the user can supply the matrix or operator OPinv, + which gives ``x = OPinv @ b = [A - sigma * M]^-1 @ b``. + For a real matrix A, shift-invert can either be done in imaginary + mode or real mode, specified by the parameter OPpart ('r' or 'i'). + Note that when sigma is specified, the keyword 'which' (below) + refers to the shifted eigenvalues ``w'[i]`` where: + + - If A is real and OPpart == 'r' (default), + ``w'[i] = 1/2 * [1/(w[i]-sigma) + 1/(w[i]-conj(sigma))]``. + - If A is real and OPpart == 'i', + ``w'[i] = 1/2i * [1/(w[i]-sigma) - 1/(w[i]-conj(sigma))]``. + - If A is complex, ``w'[i] = 1/(w[i]-sigma)``. + + which : str, ['LM' | 'SM' | 'LR' | 'SR' | 'LI' | 'SI'], optional + Which `k` eigenvectors and eigenvalues to find: + + - 'LM' : largest magnitude + - 'SM' : smallest magnitude + - 'LR' : largest real part + - 'SR' : smallest real part + - 'LI' : largest imaginary part + - 'SI' : smallest imaginary part + + When sigma != None, 'which' refers to the shifted eigenvalues w'[i] + (see discussion in 'sigma', above). ARPACK is generally better + at finding large values than small values. If small eigenvalues are + desired, consider using shift-invert mode for better performance. + v0 : ndarray, optional + Starting vector for iteration. + Default: random + ncv : int, optional + The number of Lanczos vectors generated + `ncv` must be greater than `k`; it is recommended that ``ncv > 2*k``. + Default: ``min(n, max(2*k + 1, 20))`` + maxiter : int, optional + Maximum number of Arnoldi update iterations allowed + Default: ``n*10`` + tol : float, optional + Relative accuracy for eigenvalues (stopping criterion) + The default value of 0 implies machine precision. + return_eigenvectors : bool, optional + Return eigenvectors (True) in addition to eigenvalues + Minv : ndarray, sparse matrix or LinearOperator, optional + See notes in M, above. + OPinv : ndarray, sparse matrix or LinearOperator, optional + See notes in sigma, above. + OPpart : {'r' or 'i'}, optional + See notes in sigma, above + rng : `numpy.random.Generator`, optional + Pseudorandom number generator state. When `rng` is None, a new + `numpy.random.Generator` is created using entropy from the + operating system. Types other than `numpy.random.Generator` are + passed to `numpy.random.default_rng` to instantiate a ``Generator``. + + .. versionadded:: 1.17.0 + + Returns + ------- + w : ndarray + Array of k eigenvalues. + v : ndarray + An array of `k` eigenvectors. + ``v[:, i]`` is the eigenvector corresponding to the eigenvalue w[i]. + + Raises + ------ + ArpackNoConvergence + When the requested convergence is not obtained. + The currently converged eigenvalues and eigenvectors can be found + as ``eigenvalues`` and ``eigenvectors`` attributes of the exception + object. + + See Also + -------- + eigsh : eigenvalues and eigenvectors for symmetric matrix A + svds : singular value decomposition for a matrix A + + Notes + ----- + This function is a wrapper to the ARPACK [1]_ SNEUPD, DNEUPD, CNEUPD, + ZNEUPD, functions which use the Implicitly Restarted Arnoldi Method to + find the eigenvalues and eigenvectors [2]_. + + References + ---------- + .. [1] ARPACK Software, https://github.com/opencollab/arpack-ng + .. [2] R. B. Lehoucq, D. C. Sorensen, and C. Yang, ARPACK USERS GUIDE: + Solution of Large Scale Eigenvalue Problems by Implicitly Restarted + Arnoldi Methods. SIAM, Philadelphia, PA, 1998. + + Examples + -------- + Find 6 eigenvectors of the identity matrix: + + >>> import numpy as np + >>> from scipy.sparse.linalg import eigs + >>> id = np.eye(13) + >>> vals, vecs = eigs(id, k=6) + >>> vals + array([ 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j]) + >>> vecs.shape + (13, 6) + + """ + A = convert_pydata_sparse_to_scipy(A) + if (A_ndim := len(A.shape)) > 2: + raise ValueError(f"{A_ndim}-dimensional `A` is unsupported, expected 2-D.") + M = convert_pydata_sparse_to_scipy(M) + if A.shape[0] != A.shape[1]: + raise ValueError(f'expected square matrix (shape={A.shape})') + if M is not None: + if M.shape != A.shape: + raise ValueError(f'wrong M dimensions {M.shape}, should be {A.shape}') + if np.dtype(M.dtype).char.lower() != np.dtype(A.dtype).char.lower(): + warnings.warn('M does not have the same type precision as A. ' + 'This may adversely affect ARPACK convergence', + stacklevel=2) + + n = A.shape[0] + + if k <= 0: + raise ValueError(f"k={k} must be greater than 0.") + + if k >= n - 1: + warnings.warn("k >= N - 1 for N * N square matrix. " + "Attempting to use scipy.linalg.eig instead.", + RuntimeWarning, stacklevel=2) + + if issparse(A): + raise TypeError("Cannot use scipy.linalg.eig for sparse A with " + "k >= N - 1. Use scipy.linalg.eig(A.toarray()) or" + " reduce k.") + if isinstance(A, LinearOperator): + raise TypeError("Cannot use scipy.linalg.eig for LinearOperator " + "A with k >= N - 1.") + if isinstance(M, LinearOperator): + raise TypeError("Cannot use scipy.linalg.eig for LinearOperator " + "M with k >= N - 1.") + + return eig(A, b=M, right=return_eigenvectors) + + if sigma is None: + matvec = aslinearoperator(A).matvec + + if OPinv is not None: + raise ValueError("OPinv should not be specified " + "with sigma = None.") + if OPpart is not None: + raise ValueError("OPpart should not be specified with " + "sigma = None or complex A") + + if M is None: + #standard eigenvalue problem + mode = 1 + M_matvec = None + Minv_matvec = None + if Minv is not None: + raise ValueError("Minv should not be " + "specified with M = None.") + else: + #general eigenvalue problem + mode = 2 + if Minv is None: + Minv_matvec = get_inv_matvec(M, hermitian=True, tol=tol) + else: + Minv = aslinearoperator(Minv) + Minv_matvec = Minv.matvec + M_matvec = aslinearoperator(M).matvec + else: + #sigma is not None: shift-invert mode + if np.issubdtype(A.dtype, np.complexfloating): + if OPpart is not None: + raise ValueError("OPpart should not be specified " + "with sigma=None or complex A") + mode = 3 + elif OPpart is None or OPpart.lower() == 'r': + mode = 3 + elif OPpart.lower() == 'i': + if np.imag(sigma) == 0: + raise ValueError("OPpart cannot be 'i' if sigma is real") + mode = 4 + else: + raise ValueError("OPpart must be one of ('r','i')") + + matvec = aslinearoperator(A).matvec + if Minv is not None: + raise ValueError("Minv should not be specified when sigma is" + "specified.") + if OPinv is None: + Minv_matvec = get_OPinv_matvec(A, M, sigma, + hermitian=False, tol=tol) + else: + OPinv = aslinearoperator(OPinv) + Minv_matvec = OPinv.matvec + if M is None: + M_matvec = None + else: + M_matvec = aslinearoperator(M).matvec + + rng = np.random.default_rng(rng) + params = _UnsymmetricArpackParams(n, k, A.dtype.char, matvec, mode, + M_matvec, Minv_matvec, sigma, + ncv, v0, maxiter, which, tol, rng) + + while not params.converged: + params.iterate() + + return params.extract(return_eigenvectors) + + +def eigsh(A, k=6, M=None, sigma=None, which='LM', v0=None, + ncv=None, maxiter=None, tol=0, return_eigenvectors=True, + Minv=None, OPinv=None, mode='normal', rng=None): + """ + Find k eigenvalues and eigenvectors of the real symmetric square matrix + or complex Hermitian matrix A. + + Solves ``A @ x[i] = w[i] * x[i]``, the standard eigenvalue problem for + w[i] eigenvalues with corresponding eigenvectors x[i]. + + If M is specified, solves ``A @ x[i] = w[i] * M @ x[i]``, the + generalized eigenvalue problem for w[i] eigenvalues + with corresponding eigenvectors x[i]. + + Note that there is no specialized routine for the case when A is a complex + Hermitian matrix. In this case, ``eigsh()`` will call ``eigs()`` and return + the real parts of the eigenvalues thus obtained. + + Parameters + ---------- + A : ndarray, sparse matrix or LinearOperator + A square operator representing the operation ``A @ x``, where ``A`` is + real symmetric or complex Hermitian. For buckling mode (see below) + ``A`` must additionally be positive-definite. + k : int, optional + The number of eigenvalues and eigenvectors desired. + `k` must be smaller than N. It is not possible to compute all + eigenvectors of a matrix. + + Returns + ------- + w : array + Array of k eigenvalues. + v : array + An array representing the `k` eigenvectors. The column ``v[:, i]`` is + the eigenvector corresponding to the eigenvalue ``w[i]``. + + Other Parameters + ---------------- + M : An N x N matrix, array, sparse matrix, or linear operator representing + the operation ``M @ x`` for the generalized eigenvalue problem:: + + A @ x = w * M @ x. + + M must represent a real symmetric matrix if A is real, and must + represent a complex Hermitian matrix if A is complex. For best + results, the data type of M should be the same as that of A. + Additionally: + + - If sigma is None, M is symmetric positive definite. + - If sigma is specified, M is symmetric positive semi-definite. + - In buckling mode, M is symmetric indefinite. + + If sigma is None, eigsh requires an operator to compute the solution + of the linear equation ``M @ x = b``. This is done internally via a + (sparse) LU decomposition for an explicit matrix M, or via an + iterative solver for a general linear operator. Alternatively, + the user can supply the matrix or operator Minv, which gives + ``x = Minv @ b = M^-1 @ b``. + sigma : real + Find eigenvalues near sigma using shift-invert mode. This requires + an operator to compute the solution of the linear system + ``[A - sigma * M] x = b``, where M is the identity matrix if + unspecified. This is computed internally via a (sparse) LU + decomposition for explicit matrices A & M, or via an iterative + solver if either A or M is a general linear operator. + Alternatively, the user can supply the matrix or operator `OPinv`, + which gives ``x = OPinv @ b = [A - sigma * M]^-1 @ b``. + Regardless of the selected mode (normal, cayley, or buckling), + `OPinv` should always be supplied as ``OPinv = [A - sigma * M]^-1``. + + Note that when sigma is specified, the keyword 'which' refers to + the shifted eigenvalues ``w'[i]`` where: + + - if ``mode == 'normal'``: ``w'[i] = 1 / (w[i] - sigma)``. + - if ``mode == 'cayley'``: ``w'[i] = (w[i] + sigma) / (w[i] - sigma)``. + - if ``mode == 'buckling'``: ``w'[i] = w[i] / (w[i] - sigma)``. + + (see further discussion in 'mode' below) + which : str ['LM' | 'SM' | 'LA' | 'SA' | 'BE'] + If A is a complex Hermitian matrix, 'BE' is invalid. + Which `k` eigenvectors and eigenvalues to find: + + - 'LM' : Largest (in magnitude) eigenvalues. + - 'SM' : Smallest (in magnitude) eigenvalues. + - 'LA' : Largest (algebraic) eigenvalues. + - 'SA' : Smallest (algebraic) eigenvalues. + - 'BE' : Half (k/2) from each end of the spectrum. + + When k is odd, return one more (k/2+1) from the high end. + When sigma != None, 'which' refers to the shifted eigenvalues ``w'[i]`` + (see discussion in 'sigma', above). ARPACK is generally better + at finding large values than small values. If small eigenvalues are + desired, consider using shift-invert mode for better performance. + v0 : ndarray, optional + Starting vector for iteration. Default: random + ncv : int, optional + The number of Lanczos vectors generated ncv must be greater than k and + smaller than n; it is recommended that ``ncv > 2*k``. + Default: ``min(n, max(2*k + 1, 20))`` + maxiter : int, optional + Maximum number of Arnoldi update iterations allowed. + Default: ``n*10`` + tol : float + Relative accuracy for eigenvalues (stopping criterion). + The default value of 0 implies machine precision. + return_eigenvectors : bool + Return eigenvectors (True) in addition to eigenvalues. + This value determines the order in which eigenvalues are sorted. + The sort order is also dependent on the `which` variable. + + - For which = 'LM' or 'SA': + + - If `return_eigenvectors` is True, eigenvalues are sorted by + algebraic value. + - If `return_eigenvectors` is False, eigenvalues are sorted by + absolute value. + + - For which = 'BE' or 'LA': + + - eigenvalues are always sorted by algebraic value. + + - For which = 'SM': + + - If `return_eigenvectors` is True, eigenvalues are sorted by + algebraic value. + - If `return_eigenvectors` is False, eigenvalues are sorted by + decreasing absolute value. + + Minv : N x N matrix, array, sparse matrix, or LinearOperator + See notes in M, above. + OPinv : N x N matrix, array, sparse matrix, or LinearOperator + See notes in sigma, above. + mode : str ['normal' | 'buckling' | 'cayley'] + Specify strategy to use for shift-invert mode. This argument applies + only for real-valued A and sigma != None. For shift-invert mode, + ARPACK internally solves the eigenvalue problem + ``OP @ x'[i] = w'[i] * B @ x'[i]`` + and transforms the resulting Ritz vectors x'[i] and Ritz values w'[i] + into the desired eigenvectors and eigenvalues of the problem + ``A @ x[i] = w[i] * M @ x[i]``. + The modes are as follows: + + - 'normal':: + + OP = [A - sigma * M]^-1 @ M, + B = M, + w'[i] = 1 / (w[i] - sigma) + + - 'buckling':: + + OP = [A - sigma * M]^-1 @ A, + B = A, + w'[i] = w[i] / (w[i] - sigma) + + - 'cayley':: + + OP = [A - sigma * M]^-1 @ [A + sigma * M], + B = M, + w'[i] = (w[i] + sigma) / (w[i] - sigma) + + The choice of mode will affect which eigenvalues are selected by + the keyword 'which', and can also impact the stability of + convergence (see [2]_ for a discussion). + rng : `numpy.random.Generator`, optional + Pseudorandom number generator state. When `rng` is None, a new + `numpy.random.Generator` is created using entropy from the + operating system. Types other than `numpy.random.Generator` are + passed to `numpy.random.default_rng` to instantiate a ``Generator``. + + .. versionadded:: 1.17.0 + + Raises + ------ + ArpackNoConvergence + When the requested convergence is not obtained. + + The currently converged eigenvalues and eigenvectors can be found + as ``eigenvalues`` and ``eigenvectors`` attributes of the exception + object. + + See Also + -------- + eigs : eigenvalues and eigenvectors for a general (nonsymmetric) matrix A + svds : singular value decomposition for a matrix A + + Notes + ----- + This function is a wrapper to the ARPACK [1]_ SSEUPD and DSEUPD + functions which use the Implicitly Restarted Lanczos Method to + find the eigenvalues and eigenvectors [2]_. + + References + ---------- + .. [1] ARPACK Software, https://github.com/opencollab/arpack-ng + .. [2] R. B. Lehoucq, D. C. Sorensen, and C. Yang, ARPACK USERS GUIDE: + Solution of Large Scale Eigenvalue Problems by Implicitly Restarted + Arnoldi Methods. SIAM, Philadelphia, PA, 1998. + + Examples + -------- + >>> import numpy as np + >>> from scipy.sparse.linalg import eigsh + >>> identity = np.eye(13) + >>> eigenvalues, eigenvectors = eigsh(identity, k=6) + >>> eigenvalues + array([1., 1., 1., 1., 1., 1.]) + >>> eigenvectors.shape + (13, 6) + + """ + # complex Hermitian matrices should be solved with eigs + if np.issubdtype(A.dtype, np.complexfloating): + if mode != 'normal': + raise ValueError(f"mode={mode} cannot be used with complex matrix A") + if which == 'BE': + raise ValueError("which='BE' cannot be used with complex matrix A") + elif which == 'LA': + which = 'LR' + elif which == 'SA': + which = 'SR' + ret = eigs(A, k, M=M, sigma=sigma, which=which, v0=v0, + ncv=ncv, maxiter=maxiter, tol=tol, + return_eigenvectors=return_eigenvectors, Minv=Minv, + OPinv=OPinv) + + if return_eigenvectors: + return ret[0].real, ret[1] + else: + return ret.real + + if (A_ndim := len(A.shape)) > 2: + raise ValueError(f"{A_ndim}-dimensional `A` is unsupported, expected 2-D.") + if A.shape[0] != A.shape[1]: + raise ValueError(f'expected square matrix (shape={A.shape})') + if M is not None: + if M.shape != A.shape: + raise ValueError(f'wrong M dimensions {M.shape}, should be {A.shape}') + if np.dtype(M.dtype).char.lower() != np.dtype(A.dtype).char.lower(): + warnings.warn('M does not have the same type precision as A. ' + 'This may adversely affect ARPACK convergence', + stacklevel=2) + + n = A.shape[0] + + if k <= 0: + raise ValueError("k must be greater than 0.") + + if k >= n: + warnings.warn("k >= N for N * N square matrix. " + "Attempting to use scipy.linalg.eigh instead.", + RuntimeWarning, stacklevel=2) + + if issparse(A): + raise TypeError("Cannot use scipy.linalg.eigh for sparse A with " + "k >= N. Use scipy.linalg.eigh(A.toarray()) or" + " reduce k.") + if isinstance(A, LinearOperator): + raise TypeError("Cannot use scipy.linalg.eigh for LinearOperator " + "A with k >= N.") + if isinstance(M, LinearOperator): + raise TypeError("Cannot use scipy.linalg.eigh for LinearOperator " + "M with k >= N.") + + return eigh(A, b=M, eigvals_only=not return_eigenvectors) + + if sigma is None: + A = aslinearoperator(A) + matvec = A.matvec + + if OPinv is not None: + raise ValueError("OPinv should not be specified with sigma = None.") + if M is None: + #standard eigenvalue problem + mode = 1 + M_matvec = None + Minv_matvec = None + if Minv is not None: + raise ValueError("Minv should not be specified with M = None.") + else: + #general eigenvalue problem + mode = 2 + if Minv is None: + Minv_matvec = get_inv_matvec(M, hermitian=True, tol=tol) + else: + Minv = aslinearoperator(Minv) + Minv_matvec = Minv.matvec + M_matvec = aslinearoperator(M).matvec + else: + # sigma is not None: shift-invert mode + if Minv is not None: + raise ValueError("Minv should not be specified when sigma is " + "specified.") + + # normal mode + if mode == 'normal': + mode = 3 + matvec = None + if OPinv is None: + Minv_matvec = get_OPinv_matvec(A, M, sigma, + hermitian=True, tol=tol) + else: + OPinv = aslinearoperator(OPinv) + Minv_matvec = OPinv.matvec + if M is None: + M_matvec = None + else: + M = aslinearoperator(M) + M_matvec = M.matvec + + # buckling mode + elif mode == 'buckling': + mode = 4 + if OPinv is None: + Minv_matvec = get_OPinv_matvec(A, M, sigma, + hermitian=True, tol=tol) + else: + Minv_matvec = aslinearoperator(OPinv).matvec + matvec = aslinearoperator(A).matvec + M_matvec = None + + # cayley-transform mode + elif mode == 'cayley': + mode = 5 + matvec = aslinearoperator(A).matvec + if OPinv is None: + Minv_matvec = get_OPinv_matvec(A, M, sigma, + hermitian=True, tol=tol) + else: + Minv_matvec = aslinearoperator(OPinv).matvec + if M is None: + M_matvec = None + else: + M_matvec = aslinearoperator(M).matvec + + # unrecognized mode + else: + raise ValueError(f"unrecognized mode '{mode}'") + rng = np.random.default_rng(rng) + params = _SymmetricArpackParams(n, k, A.dtype.char, matvec, mode, + M_matvec, Minv_matvec, sigma, + ncv, v0, maxiter, which, tol, rng) + + while not params.converged: + params.iterate() + + return params.extract(return_eigenvectors) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/arpack/tests/__init__.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/arpack/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/arpack/tests/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/arpack/tests/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..25ec72fc707fb01551591eb83a2cf7bd2a23becc Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/arpack/tests/__pycache__/__init__.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/arpack/tests/__pycache__/test_arpack.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/arpack/tests/__pycache__/test_arpack.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c5a18511fc13150bbe0f65290bc63b032a34a0dd Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/arpack/tests/__pycache__/test_arpack.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/arpack/tests/test_arpack.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/arpack/tests/test_arpack.py new file mode 100644 index 0000000000000000000000000000000000000000..30e2d228623787a2154211301c9d909bb91a31b7 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/arpack/tests/test_arpack.py @@ -0,0 +1,717 @@ +import threading +import itertools +import warnings + +import numpy as np + +from numpy.testing import assert_allclose, assert_equal +from pytest import raises as assert_raises +import pytest + +from numpy import dot, conj +from scipy.linalg import eig, eigh +from scipy.sparse import csc_array, csr_array, diags_array, random_array +from scipy.sparse.linalg import LinearOperator, aslinearoperator +from scipy.sparse.linalg._eigen.arpack import (eigs, eigsh, arpack, + ArpackNoConvergence) + + +from scipy._lib._gcutils import assert_deallocated + + +# precision for tests +_ndigits = {'f': 3, 'd': 11, 'F': 3, 'D': 11} + + +def _get_test_tolerance(type_char, mattype=None, D_type=None, which=None): + """ + Return tolerance values suitable for a given test: + + Parameters + ---------- + type_char : {'f', 'd', 'F', 'D'} + Data type in ARPACK eigenvalue problem + mattype : {csr_array, aslinearoperator, asarray}, optional + Linear operator type + + Returns + ------- + tol + Tolerance to pass to the ARPACK routine + rtol + Relative tolerance for outputs + atol + Absolute tolerance for outputs + + """ + + rtol = {'f': 3000 * np.finfo(np.float32).eps, + 'F': 3000 * np.finfo(np.float32).eps, + 'd': 2000 * np.finfo(np.float64).eps, + 'D': 2000 * np.finfo(np.float64).eps}[type_char] + atol = rtol + tol = 0 + + if mattype is aslinearoperator and type_char in ('f', 'F'): + # iterative methods in single precision: worse errors + # also: bump ARPACK tolerance so that the iterative method converges + tol = 30 * np.finfo(np.float32).eps + rtol *= 5 + + if ( + isinstance(mattype, type) and issubclass(mattype, csr_array) + and type_char in ('f', 'F') + ): + # sparse in single precision: worse errors + rtol *= 5 + + if ( + which in ('LM', 'SM', 'LA') + and D_type.name == "gen-hermitian-Mc" + ): + if type_char == 'F': + # missing case 1, 2, and more, from PR 14798 + rtol *= 5 + + if type_char == 'D': + # missing more cases, from PR 14798 + rtol *= 10 + atol *= 10 + + return tol, rtol, atol + + +def generate_matrix(N, complex_=False, hermitian=False, + pos_definite=False, sparse=False, rng=None): + M = rng.random((N, N)) + if complex_: + M = M + 1j * rng.random((N, N)) + + if hermitian: + if pos_definite: + if sparse: + i = np.arange(N) + j = rng.randint(N, size=N-2) + i, j = np.meshgrid(i, j) + M[i, j] = 0 + M = np.dot(M.conj(), M.T) + else: + M = np.dot(M.conj(), M.T) + if sparse: + i = rng.randint(N, size=N * N // 4) + j = rng.randint(N, size=N * N // 4) + ind = np.nonzero(i == j) + j[ind] = (j[ind] + 1) % N + M[i, j] = 0 + M[j, i] = 0 + else: + if sparse: + i = rng.randint(N, size=N * N // 2) + j = rng.randint(N, size=N * N // 2) + M[i, j] = 0 + return M + + +def generate_matrix_symmetric(N, pos_definite=False, sparse=False, rng=None): + M = rng.random((N, N)) + + M = 0.5 * (M + M.T) # Make M symmetric + + if pos_definite: + Id = N * np.eye(N) + if sparse: + M = csr_array(M) + M += Id + else: + if sparse: + M = csr_array(M) + + return M + + +def assert_allclose_cc(actual, desired, **kw): + """Almost equal or complex conjugates almost equal""" + try: + assert_allclose(actual, desired, **kw) + except AssertionError: + assert_allclose(actual, conj(desired), **kw) + + +def argsort_which(eigenvalues, typ, k, which, + sigma=None, OPpart=None, mode=None): + """Return sorted indices of eigenvalues using the "which" keyword + from eigs and eigsh""" + if sigma is None: + reval = np.round(eigenvalues, decimals=_ndigits[typ]) + else: + if mode is None or mode == 'normal': + if OPpart is None: + reval = 1. / (eigenvalues - sigma) + elif OPpart == 'r': + reval = 0.5 * (1. / (eigenvalues - sigma) + + 1. / (eigenvalues - np.conj(sigma))) + elif OPpart == 'i': + reval = -0.5j * (1. / (eigenvalues - sigma) + - 1. / (eigenvalues - np.conj(sigma))) + elif mode == 'cayley': + reval = (eigenvalues + sigma) / (eigenvalues - sigma) + elif mode == 'buckling': + reval = eigenvalues / (eigenvalues - sigma) + else: + raise ValueError(f"mode='{mode}' not recognized") + + reval = np.round(reval, decimals=_ndigits[typ]) + + if which in ['LM', 'SM']: + ind = np.argsort(abs(reval)) + elif which in ['LR', 'SR', 'LA', 'SA', 'BE']: + ind = np.argsort(np.real(reval)) + elif which in ['LI', 'SI']: + # for LI,SI ARPACK returns largest,smallest abs(imaginary) why? + if typ.islower(): + ind = np.argsort(abs(np.imag(reval))) + else: + ind = np.argsort(np.imag(reval)) + else: + raise ValueError(f"which='{which}' is unrecognized") + + if which in ['LM', 'LA', 'LR', 'LI']: + return ind[-k:] + elif which in ['SM', 'SA', 'SR', 'SI']: + return ind[:k] + elif which == 'BE': + return np.concatenate((ind[:k//2], ind[k//2-k:])) + + +def eval_evec(symmetric, d, typ, k, which, v0=None, sigma=None, + mattype=np.asarray, OPpart=None, mode='normal', rng=None): + general = ('bmat' in d) + + if symmetric: + eigs_func = eigsh + else: + eigs_func = eigs + + if general: + err = (f"error for {eigs_func.__name__}:general, typ={typ}, which={which}, " + f"sigma={sigma}, mattype={mattype.__name__}," + f" OPpart={OPpart}, mode={mode}") + else: + err = (f"error for {eigs_func.__name__}:standard, typ={typ}, which={which}, " + f"sigma={sigma}, mattype={mattype.__name__}, " + f"OPpart={OPpart}, mode={mode}") + + a = d['mat'].astype(typ) + ac = mattype(a) + + if general: + b = d['bmat'].astype(typ) + bc = mattype(b) + + # get exact eigenvalues + exact_eval = d['eval'].astype(typ.upper()) + ind = argsort_which(exact_eval, typ, k, which, + sigma, OPpart, mode) + exact_eval = exact_eval[ind] + + # compute arpack eigenvalues + kwargs = dict(which=which, v0=v0, sigma=sigma, rng=rng) + if eigs_func is eigsh: + kwargs['mode'] = mode + else: + kwargs['OPpart'] = OPpart + + # compute suitable tolerances + kwargs['tol'], rtol, atol = _get_test_tolerance(typ, mattype, d, which) + # on rare occasions, ARPACK routines return results that are proper + # eigenvalues and -vectors, but not necessarily the ones requested in + # the parameter which. This is inherent to the Krylov methods, and + # should not be treated as a failure. If such a rare situation + # occurs, the calculation is tried again (but at most a few times). + ntries = 0 + while ntries < 5: + # solve + if general: + try: + eigenvalues, evec = eigs_func(ac, k, bc, **kwargs) + except ArpackNoConvergence: + kwargs['maxiter'] = 20*a.shape[0] + eigenvalues, evec = eigs_func(ac, k, bc, **kwargs) + else: + try: + eigenvalues, evec = eigs_func(ac, k, **kwargs) + except ArpackNoConvergence: + kwargs['maxiter'] = 20*a.shape[0] + eigenvalues, evec = eigs_func(ac, k, **kwargs) + + ind = argsort_which(eigenvalues, typ, k, which, + sigma, OPpart, mode) + eigenvalues = eigenvalues[ind] + evec = evec[:, ind] + + try: + # check eigenvalues + assert_allclose_cc(eigenvalues, exact_eval, rtol=rtol, atol=atol, + err_msg=err) + check_evecs = True + except AssertionError: + check_evecs = False + ntries += 1 + + if check_evecs: + # check eigenvectors + LHS = np.dot(a, evec) + if general: + RHS = eigenvalues * np.dot(b, evec) + else: + RHS = eigenvalues * evec + + assert_allclose(LHS, RHS, rtol=rtol, atol=atol, err_msg=err) + break + + # check eigenvalues + assert_allclose_cc(eigenvalues, exact_eval, rtol=rtol, atol=atol, err_msg=err) + + +class DictWithRepr(dict): + def __init__(self, name): + self.name = name + + def __repr__(self): + return f"<{self.name}>" + + +class SymmetricParams: + def __init__(self): + self.eigs = eigsh + self.which = ['LM', 'SM', 'LA', 'SA', 'BE'] + self.mattypes = [csr_array, aslinearoperator, np.asarray] + self.sigmas_modes = {None: ['normal'], + 0.5: ['normal', 'buckling', 'cayley']} + + # generate matrices + # these should all be float32 so that the eigenvalues + # are the same in float32 and float64 + N = 6 + rng = np.random.RandomState(2300) + Ar = generate_matrix(N, hermitian=True, + pos_definite=True, + rng=rng).astype('f').astype('d') + M = generate_matrix(N, hermitian=True, + pos_definite=True, + rng=rng).astype('f').astype('d') + Ac = generate_matrix(N, hermitian=True, pos_definite=True, + complex_=True, rng=rng).astype('F').astype('D') + Mc = generate_matrix(N, hermitian=True, pos_definite=True, + complex_=True, rng=rng).astype('F').astype('D') + v0 = rng.random(N) + + # standard symmetric problem + SS = DictWithRepr("std-symmetric") + SS['mat'] = Ar + SS['v0'] = v0 + SS['eval'] = eigh(SS['mat'], eigvals_only=True) + + # general symmetric problem + GS = DictWithRepr("gen-symmetric") + GS['mat'] = Ar + GS['bmat'] = M + GS['v0'] = v0 + GS['eval'] = eigh(GS['mat'], GS['bmat'], eigvals_only=True) + + # standard hermitian problem + SH = DictWithRepr("std-hermitian") + SH['mat'] = Ac + SH['v0'] = v0 + SH['eval'] = eigh(SH['mat'], eigvals_only=True) + + # general hermitian problem + GH = DictWithRepr("gen-hermitian") + GH['mat'] = Ac + GH['bmat'] = M + GH['v0'] = v0 + GH['eval'] = eigh(GH['mat'], GH['bmat'], eigvals_only=True) + + # general hermitian problem with hermitian M + GHc = DictWithRepr("gen-hermitian-Mc") + GHc['mat'] = Ac + GHc['bmat'] = Mc + GHc['v0'] = v0 + GHc['eval'] = eigh(GHc['mat'], GHc['bmat'], eigvals_only=True) + + self.real_test_cases = [SS, GS] + self.complex_test_cases = [SH, GH, GHc] + + +class NonSymmetricParams: + def __init__(self): + self.eigs = eigs + self.which = ['LM', 'LR', 'LI'] # , 'SM', 'LR', 'SR', 'LI', 'SI'] + self.mattypes = [csr_array, aslinearoperator, np.asarray] + self.sigmas_OPparts = {None: [None], + 0.1: ['r'], + 0.1 + 0.1j: ['r', 'i']} + + # generate matrices + # these should all be float32 so that the eigenvalues + # are the same in float32 and float64 + N = 6 + rng = np.random.RandomState(2300) + Ar = generate_matrix(N, rng=rng).astype('f').astype('d') + M = generate_matrix(N, hermitian=True, + pos_definite=True, rng=rng).astype('f').astype('d') + Ac = generate_matrix(N, complex_=True, rng=rng).astype('F').astype('D') + v0 = rng.random(N) + + # standard real nonsymmetric problem + SNR = DictWithRepr("std-real-nonsym") + SNR['mat'] = Ar + SNR['v0'] = v0 + SNR['eval'] = eig(SNR['mat'], left=False, right=False) + + # general real nonsymmetric problem + GNR = DictWithRepr("gen-real-nonsym") + GNR['mat'] = Ar + GNR['bmat'] = M + GNR['v0'] = v0 + GNR['eval'] = eig(GNR['mat'], GNR['bmat'], left=False, right=False) + + # standard complex nonsymmetric problem + SNC = DictWithRepr("std-cmplx-nonsym") + SNC['mat'] = Ac + SNC['v0'] = v0 + SNC['eval'] = eig(SNC['mat'], left=False, right=False) + + # general complex nonsymmetric problem + GNC = DictWithRepr("gen-cmplx-nonsym") + GNC['mat'] = Ac + GNC['bmat'] = M + GNC['v0'] = v0 + GNC['eval'] = eig(GNC['mat'], GNC['bmat'], left=False, right=False) + + self.real_test_cases = [SNR, GNR] + self.complex_test_cases = [SNC, GNC] + + +@pytest.mark.parametrize("sigma, mode", [(None, 'normal'), (0.5, 'normal'), + (0.5, 'buckling'), (0.5, 'cayley')]) +@pytest.mark.parametrize("mattype", [csr_array, aslinearoperator, np.asarray]) +@pytest.mark.parametrize("which", ['LM', 'SM', 'LA', 'SA', 'BE']) +@pytest.mark.parametrize("typ", ['f', 'd']) +@pytest.mark.parametrize("D", SymmetricParams().real_test_cases) +def test_symmetric_modes(D, typ, which, mattype, sigma, mode): + rng = np.random.default_rng(1749531508689996) + k = 2 + eval_evec(True, D, typ, k, which, None, sigma, mattype, None, mode, rng=rng) + + +@pytest.mark.parametrize("sigma", [None, 0.5]) +@pytest.mark.parametrize("mattype", [csr_array, aslinearoperator, np.asarray]) +@pytest.mark.parametrize("which", ['LM', 'SM', 'LA', 'SA']) +@pytest.mark.parametrize("typ", ['F', 'D']) +@pytest.mark.parametrize("D", SymmetricParams().complex_test_cases) +def test_hermitian_modes(D, typ, which, mattype, sigma): + rng = np.random.default_rng(1749531706842957) + k = 2 + eval_evec(True, D, typ, k, which, None, sigma, mattype, rng=rng) + + +@pytest.mark.parametrize("typ", ['f', 'd']) +@pytest.mark.parametrize("D", SymmetricParams().real_test_cases) +@pytest.mark.parametrize("k", [1, 2, 3, 4, 5]) +def test_symmetric_starting_vector(k, D, typ): + rng = np.random.default_rng(1749532110418901) + v0 = rng.uniform(size=len(D['v0'])).astype(typ) + eval_evec(True, D, typ, k, 'LM', v0, rng=rng) + + +def test_symmetric_no_convergence(): + rng = np.random.RandomState(1234) + m = generate_matrix(30, hermitian=True, pos_definite=True, rng=rng) + tol, rtol, atol = _get_test_tolerance('d') + try: + w, v = eigsh(m, 4, which='LM', v0=m[:, 0], maxiter=5, tol=tol, ncv=9) + raise AssertionError("Spurious no-error exit") + except ArpackNoConvergence as err: + k = len(err.eigenvalues) + if k <= 0: + raise AssertionError("Spurious no-eigenvalues-found case") from err + w, v = err.eigenvalues, err.eigenvectors + assert_allclose(dot(m, v), w * v, rtol=rtol, atol=atol) + + +@pytest.mark.parametrize("sigma, OPpart", [(None, None), (0.1, 'r'), + (0.1 + 0.1j, 'r'), (0.1 + 0.1j, 'i')]) +@pytest.mark.parametrize("mattype", [csr_array, aslinearoperator, np.asarray]) +@pytest.mark.parametrize("which", ['LM', 'LR', 'LI']) +@pytest.mark.parametrize("typ", ['f', 'd']) +@pytest.mark.parametrize("D", NonSymmetricParams().real_test_cases) +def test_real_nonsymmetric_modes(D, typ, which, mattype, + sigma, OPpart): + rng = np.random.default_rng(174953334412726) + k = 2 + eval_evec(False, D, typ, k, which, None, sigma, mattype, OPpart, rng=rng) + + +@pytest.mark.parametrize("sigma", [None, 0.1, 0.1 + 0.1j]) +@pytest.mark.parametrize("mattype", [csr_array, aslinearoperator, np.asarray]) +@pytest.mark.parametrize("which", ['LM', 'LR', 'LI']) +@pytest.mark.parametrize("typ", ['F', 'D']) +@pytest.mark.parametrize("D", NonSymmetricParams().complex_test_cases) +def test_complex_nonsymmetric_modes(D, typ, which, mattype, sigma): + rng = np.random.default_rng(1749533536274527) + k = 2 + eval_evec(False, D, typ, k, which, None, sigma, mattype, rng=rng) + + +@pytest.mark.parametrize("typ", ['F', 'D']) +@pytest.mark.parametrize("D", NonSymmetricParams().complex_test_cases) +@pytest.mark.parametrize("k", [1, 2, 3, 4]) +def test_nonsymmetric_starting_vector(k, D, typ): + rng = np.random.default_rng(174953366983161) + A = D['mat'] + n = A.shape[0] + v0 = rng.uniform(size=n).astype(typ) + eval_evec(False, D, typ, k, "LM", v0, sigma=None, rng=rng) + + +def test_standard_nonsymmetric_no_convergence(): + rng = np.random.RandomState(1234) + m = generate_matrix(30, complex_=True, rng=rng) + tol, rtol, atol = _get_test_tolerance('d') + try: + w, v = eigs(m, 4, which='LM', v0=m[:, 0], maxiter=5, tol=tol) + raise AssertionError("Spurious no-error exit") + except ArpackNoConvergence as err: + k = len(err.eigenvalues) + if k <= 0: + raise AssertionError("Spurious no-eigenvalues-found case") from err + w, v = err.eigenvalues, err.eigenvectors + for ww, vv in zip(w, v.T): + assert_allclose(dot(m, vv), ww * vv, rtol=rtol, atol=atol) + + +def test_eigen_bad_shapes(): + # A is not square. + A = csc_array(np.zeros((2, 3))) + assert_raises(ValueError, eigs, A) + + +def test_eigen_bad_kwargs(): + # Test eigen on wrong keyword argument + A = csc_array(np.zeros((8, 8))) + assert_raises(ValueError, eigs, A, which='XX') + + +def test_ticket_1459_arpack_crash(): + for dtype in [np.float32, np.float64]: + # This test does not seem to catch the issue for float32, + # but we made the same fix there, just to be sure + + N = 6 + k = 2 + + np.random.seed(2301) + A = np.random.random((N, N)).astype(dtype) + v0 = np.array([-0.71063568258907849895, -0.83185111795729227424, + -0.34365925382227402451, 0.46122533684552280420, + -0.58001341115969040629, -0.78844877570084292984e-01], + dtype=dtype) + + # Should not crash: + evals, evecs = eigs(A, k, v0=v0) + + +def test_linearoperator_deallocation(): + # Check that the linear operators used by the Arpack wrappers are + # deallocatable by reference counting -- they are big objects, so + # Python's cyclic GC may not collect them fast enough before + # running out of memory if eigs/eigsh are called in a tight loop. + + M_d = np.eye(10) + M_s = csc_array(M_d) + M_o = aslinearoperator(M_d) + + with assert_deallocated(lambda: arpack.SpLuInv(M_s)): + pass + with assert_deallocated(lambda: arpack.LuInv(M_d)): + pass + with assert_deallocated(lambda: arpack.IterInv(M_s)): + pass + with assert_deallocated(lambda: arpack.IterOpInv(M_o, None, 0.3)): + pass + with assert_deallocated(lambda: arpack.IterOpInv(M_o, M_o, 0.3)): + pass + + +def test_parallel_threads(num_parallel_threads): + results = [] + rng = np.random.default_rng(1234) + v0 = rng.random(50) + + def worker(): + x = diags_array([1.0, -2.0, 1.0], offsets=[-1, 0, 1], shape=(50, 50)) + w, v = eigs(x, k=3, v0=v0) + results.append(w) + + w, v = eigsh(x, k=3, v0=v0) + results.append(w) + + nthreads = 9 // num_parallel_threads + 1 + threads = [threading.Thread(target=worker) for _ in range(nthreads)] + for t in threads: + t.start() + for t in threads: + t.join() + + worker() + + for r in results: + assert_allclose(r, results[-1]) + + +def test_reentering(): + # Just some linear operator that calls eigs recursively + def A_matvec(x): + x = diags_array([1.0, -2.0, 1.0], offsets=[-1, 0, 1], shape=(50, 50)) + w, v = eigs(x, k=1) + return v.real / w[0].real + A = LinearOperator(matvec=A_matvec, dtype=float, shape=(50, 50)) + + # ================= Old Fortran tests ================== + # The Fortran code is not reentrant, so this fails (gracefully, not crashing) + # assert_raises(RuntimeError, eigs, A, k=1) + # assert_raises(RuntimeError, eigsh, A, k=1) + # + # These should not crash upon reentrance + eigs(A, k=1) + eigsh(A, k=1) + + +def test_regression_arpackng_1315(): + # Check that issue arpack-ng/#1315 is not present. + # Adapted from arpack-ng/TESTS/bug_1315_single.c + # If this fails, then the installed ARPACK library is faulty. + + for dtype in [np.float32, np.float64]: + np.random.seed(1234) + + w0 = np.arange(1, 1000+1).astype(dtype) + A = diags_array([w0], offsets=[0], shape=(1000, 1000)) + + v0 = np.random.rand(1000).astype(dtype) + w, v = eigs(A, k=9, ncv=2*9+1, which="LM", v0=v0) + + assert_allclose(np.sort(w), np.sort(w0[-9:]), + rtol=1e-4) + + +def test_eigs_for_k_greater(): + # Test eigs() for k beyond limits. + rng = np.random.RandomState(1234) + A_sparse = diags_array([1.0, -2.0, 1.0], offsets=[-1, 0, 1], shape=(4, 4)) + A = generate_matrix(4, sparse=False, rng=rng) + M_dense = rng.random((4, 4)) + M_sparse = generate_matrix(4, sparse=True, rng=rng) + M_linop = aslinearoperator(M_dense) + eig_tuple1 = eig(A, b=M_dense) + eig_tuple2 = eig(A, b=M_sparse) + + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + + assert_equal(eigs(A, M=M_dense, k=3), eig_tuple1) + assert_equal(eigs(A, M=M_dense, k=4), eig_tuple1) + assert_equal(eigs(A, M=M_dense, k=5), eig_tuple1) + assert_equal(eigs(A, M=M_sparse, k=5), eig_tuple2) + + # M as LinearOperator + assert_raises(TypeError, eigs, A, M=M_linop, k=3) + + # Test 'A' for different types + assert_raises(TypeError, eigs, aslinearoperator(A), k=3) + assert_raises(TypeError, eigs, A_sparse, k=3) + + +def test_eigsh_for_k_greater(): + # Test eigsh() for k beyond limits. + rng = np.random.RandomState(1234) + A_sparse = diags_array([1.0, -2.0, 1.0], offsets=[-1, 0, 1], shape=(4, 4)) + A = generate_matrix(4, sparse=False, rng=rng) + M_dense = generate_matrix_symmetric(4, pos_definite=True, rng=rng) + M_sparse = generate_matrix_symmetric( + 4, pos_definite=True, sparse=True, rng=rng) + M_linop = aslinearoperator(M_dense) + eig_tuple1 = eigh(A, b=M_dense) + eig_tuple2 = eigh(A, b=M_sparse) + + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + + assert_equal(eigsh(A, M=M_dense, k=4), eig_tuple1) + assert_equal(eigsh(A, M=M_dense, k=5), eig_tuple1) + assert_equal(eigsh(A, M=M_sparse, k=5), eig_tuple2) + + # M as LinearOperator + assert_raises(TypeError, eigsh, A, M=M_linop, k=4) + + # Test 'A' for different types + assert_raises(TypeError, eigsh, aslinearoperator(A), k=4) + assert_raises(TypeError, eigsh, A_sparse, M=M_dense, k=4) + + +def test_real_eigs_real_k_subset(): + rng = np.random.default_rng(2) + + n = 10 + A = random_array(shape=(n, n), density=0.5, rng=rng) + A.data *= 2 + A.data -= 1 + A += A.T # make symmetric to test real eigenvalues + + v0 = np.ones(n) + + whichs = ['LM', 'SM', 'LR', 'SR', 'LI', 'SI'] + dtypes = [np.float32, np.float64] + + for which, sigma, dtype in itertools.product(whichs, [None, 0, 5], dtypes): + prev_w = np.array([], dtype=dtype) + eps = np.finfo(dtype).eps + for k in range(1, 9): + w, z = eigs(A.astype(dtype), k=k, which=which, sigma=sigma, + v0=v0.astype(dtype), tol=0) + assert_allclose(np.linalg.norm(A.dot(z) - z * w), 0, atol=np.sqrt(eps)) + + # Check that the set of eigenvalues for `k` is a subset of that for `k+1` + dist = abs(prev_w[:,None] - w).min(axis=1) + assert_allclose(dist, 0, atol=np.sqrt(eps)) + + prev_w = w + + +@pytest.mark.parametrize("dtype", [np.float32, np.float64, np.complex64, np.complex128]) +def test_gh24358(dtype): + # gh-24358: eigs with which="SR" returned zeros due to nev variable was modifed + # after naupd calls in the C ARPACK implementation. This was due to hitting a + # complex valued eig but requesting only one of them. + + # Test the specific issue in gh-24358 + A = csr_array(np.array([[-1.3, 2.7, 0.2], + [0.8, 4.1, 2.2], + [2.1, 4.4, -1.9]], dtype=dtype)) + w, z = eigs(A, 1, which="SR") + atol = 1e-4 if dtype in [np.float32, np.complex64] else 1e-6 + assert_allclose(w.real, -2.495689365014214, atol=atol, rtol=0.0) + # ARPACK can sometimes pick up the conjugate + assert_allclose(np.abs(w.imag), 0.5173365219668336, atol=atol, rtol=0.0) + assert_allclose(A @ z, w * z, atol=atol, rtol=0.0) + + +@pytest.mark.parametrize("func", [eigs, eigsh]) +def test_nD(func): + """Check that >2-D operators are rejected cleanly.""" + def id(x): + return x + A = LinearOperator(shape=(2, 2, 2), matvec=id, dtype=np.float64) + with pytest.raises(ValueError, match="expected 2-D"): + func(A) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/lobpcg/__init__.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/lobpcg/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..6ab5330361a6bcc2a8403f9b3788aedae750d57f --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/lobpcg/__init__.py @@ -0,0 +1,16 @@ +""" +Locally Optimal Block Preconditioned Conjugate Gradient Method (LOBPCG) + +LOBPCG is a preconditioned eigensolver for large symmetric positive definite +(SPD) generalized eigenproblems. + +Call the function lobpcg - see help for lobpcg.lobpcg. + +""" +from .lobpcg import * + +__all__ = [s for s in dir() if not s.startswith('_')] + +from scipy._lib._testutils import PytestTester +test = PytestTester(__name__) +del PytestTester diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/lobpcg/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/lobpcg/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a0458bc1bda0efaabd133ee3a33140f452545d7e Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/lobpcg/__pycache__/__init__.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/lobpcg/__pycache__/lobpcg.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/lobpcg/__pycache__/lobpcg.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..68fdd33c29a675383efc6c674bf84a68e217ba7c Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/lobpcg/__pycache__/lobpcg.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/lobpcg/lobpcg.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/lobpcg/lobpcg.py new file mode 100644 index 0000000000000000000000000000000000000000..84b4a5e0ef83bc97b1ce09edd05af573a558f429 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/lobpcg/lobpcg.py @@ -0,0 +1,1109 @@ +""" +Locally Optimal Block Preconditioned Conjugate Gradient Method (LOBPCG). + +References +---------- +.. [1] A. V. Knyazev (2001), + Toward the Optimal Preconditioned Eigensolver: Locally Optimal + Block Preconditioned Conjugate Gradient Method. + SIAM Journal on Scientific Computing 23, no. 2, + pp. 517-541. :doi:`10.1137/S1064827500366124` + +.. [2] A. V. Knyazev, I. Lashuk, M. E. Argentati, and E. Ovchinnikov (2007), + Block Locally Optimal Preconditioned Eigenvalue Xolvers (BLOPEX) + in hypre and PETSc. :arxiv:`0705.2626` + +.. [3] A. V. Knyazev's C and MATLAB implementations: + https://github.com/lobpcg/blopex +""" + +import warnings +import numpy as np +from scipy.linalg import (inv, eigh, cho_factor, cho_solve, + cholesky, LinAlgError) +from scipy.sparse.linalg import LinearOperator +from scipy.sparse import issparse + +__all__ = ["lobpcg"] + + +def _report_nonhermitian(M, name): + """ + Report if `M` is not a Hermitian matrix given its type. + """ + from scipy.linalg import norm + + md = M - M.T.conj() + nmd = norm(md, 1) + tol = 10 * np.finfo(M.dtype).eps + tol = max(tol, tol * norm(M, 1)) + if nmd > tol: + warnings.warn( + f"Matrix {name} of the type {M.dtype} is not Hermitian: " + f"condition: {nmd} < {tol} fails.", + UserWarning, stacklevel=4 + ) + +def _as2d(ar): + """ + If the input array is 2D return it, if it is 1D, append a dimension, + making it a column vector. + """ + if ar.ndim == 2: + return ar + else: # Assume 1! + aux = np.asarray(ar).reshape((ar.shape[0], 1)) + return aux + + +def _makeMatMat(m): + if m is None: + return None + elif callable(m): + return lambda v: m(v) + else: + return lambda v: m @ v + + +def _matmul_inplace(x, y, verbosityLevel=0): + """Perform 'np.matmul' in-place if possible. + + If some sufficient conditions for inplace matmul are met, do so. + Otherwise try inplace update and fall back to overwrite if that fails. + """ + if x.flags["CARRAY"] and x.shape[1] == y.shape[1] and x.dtype == y.dtype: + # conditions where we can guarantee that inplace updates will work; + # i.e. x is not a view/slice, x & y have compatible dtypes, and the + # shape of the result of x @ y matches the shape of x. + np.matmul(x, y, out=x) + else: + # ideally, we'd have an exhaustive list of conditions above when + # inplace updates are possible; since we don't, we opportunistically + # try if it works, and fall back to overwriting if necessary + try: + np.matmul(x, y, out=x) + except Exception: + if verbosityLevel: + warnings.warn( + "Inplace update of x = x @ y failed, " + "x needs to be overwritten.", + UserWarning, stacklevel=3 + ) + x = x @ y + return x + + +def _applyConstraints(blockVectorV, factYBY, blockVectorBY, blockVectorY): + """Changes blockVectorV in-place.""" + YBV = blockVectorBY.T.conj() @ blockVectorV + tmp = cho_solve(factYBY, YBV) + blockVectorV -= blockVectorY @ tmp + + +def _b_orthonormalize(B, blockVectorV, blockVectorBV=None, + verbosityLevel=0): + """in-place B-orthonormalize the given block vector using Cholesky.""" + if blockVectorBV is None: + if B is None: + blockVectorBV = blockVectorV + else: + try: + blockVectorBV = B(blockVectorV) + except Exception as e: + if verbosityLevel: + warnings.warn( + f"Secondary MatMul call failed with error\n" + f"{e}\n", + UserWarning, stacklevel=3 + ) + return None, None, None + if blockVectorBV.shape != blockVectorV.shape: + raise ValueError( + f"The shape {blockVectorV.shape} " + f"of the orthogonalized matrix not preserved\n" + f"and changed to {blockVectorBV.shape} " + f"after multiplying by the secondary matrix.\n" + ) + + VBV = blockVectorV.T.conj() @ blockVectorBV + try: + # VBV is a Cholesky factor from now on... + VBV = cholesky(VBV, overwrite_a=True) + VBV = inv(VBV, overwrite_a=True) + blockVectorV = _matmul_inplace( + blockVectorV, VBV, + verbosityLevel=verbosityLevel + ) + if B is not None: + blockVectorBV = _matmul_inplace( + blockVectorBV, VBV, + verbosityLevel=verbosityLevel + ) + return blockVectorV, blockVectorBV, VBV + except LinAlgError: + if verbosityLevel: + warnings.warn( + "Cholesky has failed.", + UserWarning, stacklevel=3 + ) + return None, None, None + + +def _get_indx(_lambda, num, largest): + """Get `num` indices into `_lambda` depending on `largest` option.""" + ii = np.argsort(_lambda) + if largest: + ii = ii[:-num - 1:-1] + else: + ii = ii[:num] + + return ii + + +def _handle_gramA_gramB_verbosity(gramA, gramB, verbosityLevel): + if verbosityLevel: + _report_nonhermitian(gramA, "gramA") + _report_nonhermitian(gramB, "gramB") + + +def lobpcg( + A, + X, + B=None, + M=None, + Y=None, + tol=None, + maxiter=None, + largest=True, + verbosityLevel=0, + retLambdaHistory=False, + retResidualNormsHistory=False, + restartControl=20, +): + """Locally Optimal Block Preconditioned Conjugate Gradient Method (LOBPCG). + + LOBPCG is a preconditioned eigensolver for large real symmetric and complex + Hermitian definite generalized eigenproblems. + + Parameters + ---------- + A : {sparse matrix, ndarray, LinearOperator, callable object} + The Hermitian linear operator of the problem, usually given by a + sparse matrix. Often called the "stiffness matrix". + X : ndarray, float32 or float64 + Initial approximation to the ``k`` eigenvectors (non-sparse). + If `A` has ``shape=(n,n)`` then `X` must have ``shape=(n,k)``. + B : {sparse matrix, ndarray, LinearOperator, callable object} + Optional. By default ``B = None``, which is equivalent to identity. + The right hand side operator in a generalized eigenproblem if present. + Often called the "mass matrix". Must be Hermitian positive definite. + M : {sparse matrix, ndarray, LinearOperator, callable object} + Optional. By default ``M = None``, which is equivalent to identity. + Preconditioner aiming to accelerate convergence. + Y : ndarray, float32 or float64, default: None + An ``n-by-sizeY`` ndarray of constraints with ``sizeY < n``. + The iterations will be performed in the ``B``-orthogonal complement + of the column-space of `Y`. `Y` must be full rank if present. + tol : scalar, optional + The default is ``tol=n*sqrt(eps)``. + Solver tolerance for the stopping criterion. + maxiter : int, default: 20 + Maximum number of iterations. + largest : bool, default: True + When True, solve for the largest eigenvalues, otherwise the smallest. + verbosityLevel : int, optional + By default ``verbosityLevel=0`` no output. + Controls the solver standard/screen output. + retLambdaHistory : bool, default: False + Whether to return iterative eigenvalue history. + retResidualNormsHistory : bool, default: False + Whether to return iterative history of residual norms. + restartControl : int, optional + Iterations restart if the residuals jump ``2**restartControl`` times + compared to the smallest recorded in ``retResidualNormsHistory``. + The default is ``restartControl=20``, making the restarts rare for + backward compatibility. + + Returns + ------- + lambda : ndarray of the shape ``(k, )``. + Array of ``k`` approximate eigenvalues. + v : ndarray of the same shape as ``X.shape``. + An array of ``k`` approximate eigenvectors. + lambdaHistory : ndarray, optional. + The eigenvalue history, if `retLambdaHistory` is ``True``. + ResidualNormsHistory : ndarray, optional. + The history of residual norms, if `retResidualNormsHistory` + is ``True``. + + Notes + ----- + The iterative loop runs ``maxit=maxiter`` (20 if ``maxit=None``) + iterations at most and finishes earlier if the tolerance is met. + Breaking backward compatibility with the previous version, LOBPCG + now returns the block of iterative vectors with the best accuracy rather + than the last one iterated, as a cure for possible divergence. + + If ``X.dtype == np.float32`` and user-provided operations/multiplications + by `A`, `B`, and `M` all preserve the ``np.float32`` data type, + all the calculations and the output are in ``np.float32``. + + The size of the iteration history output equals to the number of the best + (limited by `maxit`) iterations plus 3: initial, final, and postprocessing. + + If both `retLambdaHistory` and `retResidualNormsHistory` are ``True``, + the return tuple has the following format + ``(lambda, V, lambda history, residual norms history)``. + + In the following ``n`` denotes the matrix size and ``k`` the number + of required eigenvalues (smallest or largest). + + The LOBPCG code internally solves eigenproblems of the size ``3k`` on every + iteration by calling the dense eigensolver `eigh`, so if ``k`` is not + small enough compared to ``n``, it makes no sense to call the LOBPCG code. + Moreover, if one calls the LOBPCG algorithm for ``5k > n``, it would likely + break internally, so the code calls the standard function `eigh` instead. + It is not that ``n`` should be large for the LOBPCG to work, but rather the + ratio ``n / k`` should be large. It you call LOBPCG with ``k=1`` + and ``n=10``, it works though ``n`` is small. The method is intended + for extremely large ``n / k``. + + The convergence speed depends basically on three factors: + + 1. Quality of the initial approximations `X` to the seeking eigenvectors. + Randomly distributed around the origin vectors work well if no better + choice is known. + + 2. Relative separation of the desired eigenvalues from the rest + of the eigenvalues. One can vary ``k`` to improve the separation. + + 3. Proper preconditioning to shrink the spectral spread. + For example, a rod vibration test problem (under tests + directory) is ill-conditioned for large ``n``, so convergence will be + slow, unless efficient preconditioning is used. For this specific + problem, a good simple preconditioner function would be a linear solve + for `A`, which is easy to code since `A` is tridiagonal. + + References + ---------- + .. [1] A. V. Knyazev (2001), + Toward the Optimal Preconditioned Eigensolver: Locally Optimal + Block Preconditioned Conjugate Gradient Method. + SIAM Journal on Scientific Computing 23, no. 2, + pp. 517-541. :doi:`10.1137/S1064827500366124` + + .. [2] A. V. Knyazev, I. Lashuk, M. E. Argentati, and E. Ovchinnikov + (2007), Block Locally Optimal Preconditioned Eigenvalue Xolvers + (BLOPEX) in hypre and PETSc. :arxiv:`0705.2626` + + .. [3] A. V. Knyazev's C and MATLAB implementations: + https://github.com/lobpcg/blopex + + Examples + -------- + Our first example is minimalistic - find the largest eigenvalue of + a diagonal matrix by solving the non-generalized eigenvalue problem + ``A x = lambda x`` without constraints or preconditioning. + + >>> import numpy as np + >>> from scipy.sparse import diags_array + >>> from scipy.sparse.linalg import LinearOperator, aslinearoperator + >>> from scipy.sparse.linalg import lobpcg + + The square matrix size is + + >>> n = 100 + + and its diagonal entries are 1, ..., 100 defined by + + >>> vals = np.arange(1, n + 1).astype(np.int16) + + The first mandatory input parameter in this test is + the sparse diagonal matrix `A` + of the eigenvalue problem ``A x = lambda x`` to solve. + + >>> A = diags_array(vals, offsets=0, shape=(n, n), dtype=None) + >>> A.toarray() + array([[ 1, 0, 0, ..., 0, 0, 0], + [ 0, 2, 0, ..., 0, 0, 0], + [ 0, 0, 3, ..., 0, 0, 0], + ..., + [ 0, 0, 0, ..., 98, 0, 0], + [ 0, 0, 0, ..., 0, 99, 0], + [ 0, 0, 0, ..., 0, 0, 100]], shape=(100, 100), dtype=int16) + + The second mandatory input parameter `X` is a 2D array with the + row dimension determining the number of requested eigenvalues. + `X` is an initial guess for targeted eigenvectors. + `X` must have linearly independent columns. + If no initial approximations available, randomly oriented vectors + commonly work best, e.g., with components normally distributed + around zero or uniformly distributed on the interval [-1 1]. + Setting the initial approximations to dtype ``np.float32`` + forces all iterative values to dtype ``np.float32`` speeding up + the run while still allowing accurate eigenvalue computations. + + >>> k = 1 + >>> rng = np.random.default_rng() + >>> X = rng.normal(size=(n, k)) + >>> X = X.astype(np.float32) + + >>> eigenvalues, _ = lobpcg(A, X, maxiter=60) + >>> eigenvalues + array([100.], dtype=float32) + + `lobpcg` needs only access the matrix product with `A` rather + then the matrix itself. Since the matrix `A` is diagonal in + this example, one can write a function of the matrix product + ``A @ X`` using the diagonal values ``vals`` only, e.g., by + element-wise multiplication with broadcasting in the lambda-function + + >>> A_lambda = lambda X: vals[:, np.newaxis] * X + + or the regular function + + >>> def A_matmat(X): + ... return vals[:, np.newaxis] * X + + and use the handle to one of these callables as an input + + >>> eigenvalues, _ = lobpcg(A_lambda, X, maxiter=60) + >>> eigenvalues + array([100.], dtype=float32) + >>> eigenvalues, _ = lobpcg(A_matmat, X, maxiter=60) + >>> eigenvalues + array([100.], dtype=float32) + + The traditional callable `LinearOperator` is no longer + necessary but still supported as the input to `lobpcg`. + Specifying ``matmat=A_matmat`` explicitly improves performance. + + >>> A_lo = LinearOperator((n, n), matvec=A_matmat, matmat=A_matmat, dtype=np.int16) + >>> eigenvalues, _ = lobpcg(A_lo, X, maxiter=80) + >>> eigenvalues + array([100.], dtype=float32) + + The least efficient callable option is `aslinearoperator`: + + >>> eigenvalues, _ = lobpcg(aslinearoperator(A), X, maxiter=80) + >>> eigenvalues + array([100.], dtype=float32) + + We now switch to computing the three smallest eigenvalues specifying + + >>> k = 3 + >>> X = np.random.default_rng().normal(size=(n, k)) + + and ``largest=False`` parameter + + >>> eigenvalues, _ = lobpcg(A, X, largest=False, maxiter=90) + >>> print(eigenvalues) + [1. 2. 3.] + + The next example illustrates computing 3 smallest eigenvalues of + the same matrix `A` given by the function handle ``A_matmat`` but + with constraints and preconditioning. + + Constraints - an optional input parameter is a 2D array comprising + of column vectors that the eigenvectors must be orthogonal to + + >>> Y = np.eye(n, 3) + + The preconditioner acts as the inverse of `A` in this example, but + in the reduced precision ``np.float32`` even though the initial `X` + and thus all iterates and the output are in full ``np.float64``. + + >>> inv_vals = 1./vals + >>> inv_vals = inv_vals.astype(np.float32) + >>> M = lambda X: inv_vals[:, np.newaxis] * X + + Let us now solve the eigenvalue problem for the matrix `A` first + without preconditioning requesting 80 iterations + + >>> eigenvalues, _ = lobpcg(A_matmat, X, Y=Y, largest=False, maxiter=80) + >>> eigenvalues + array([4., 5., 6.]) + >>> eigenvalues.dtype + dtype('float64') + + With preconditioning we need only 20 iterations from the same `X` + + >>> eigenvalues, _ = lobpcg(A_matmat, X, Y=Y, M=M, largest=False, maxiter=20) + >>> eigenvalues + array([4., 5., 6.]) + + Note that the vectors passed in `Y` are the eigenvectors of the 3 + smallest eigenvalues. The results returned above are orthogonal to those. + + The primary matrix `A` may be indefinite, e.g., after shifting + ``vals`` by 50 from 1, ..., 100 to -49, ..., 50, we still can compute + the 3 smallest or largest eigenvalues. + + >>> vals = vals - 50 + >>> X = rng.normal(size=(n, k)) + >>> eigenvalues, _ = lobpcg(A_matmat, X, largest=False, maxiter=99) + >>> eigenvalues + array([-49., -48., -47.]) + >>> eigenvalues, _ = lobpcg(A_matmat, X, largest=True, maxiter=99) + >>> eigenvalues + array([50., 49., 48.]) + + """ + blockVectorX = X + bestblockVectorX = blockVectorX + blockVectorY = Y + residualTolerance = tol + if maxiter is None: + maxiter = 20 + + bestIterationNumber = maxiter + + sizeY = 0 + if blockVectorY is not None: + if len(blockVectorY.shape) != 2: + warnings.warn( + f"Expected rank-2 array for argument Y, instead got " + f"{len(blockVectorY.shape)}, " + f"so ignore it and use no constraints.", + UserWarning, stacklevel=2 + ) + blockVectorY = None + else: + sizeY = blockVectorY.shape[1] + + # Block size. + if blockVectorX is None: + raise ValueError("The mandatory initial matrix X cannot be None") + if len(blockVectorX.shape) != 2: + raise ValueError("expected rank-2 array for argument X") + + n, sizeX = blockVectorX.shape + + # Data type of iterates, determined by X, must be inexact + if not np.issubdtype(blockVectorX.dtype, np.inexact): + warnings.warn( + f"Data type for argument X is {blockVectorX.dtype}, " + f"which is not inexact, so casted to np.float32.", + UserWarning, stacklevel=2 + ) + blockVectorX = np.asarray(blockVectorX, dtype=np.float32) + + if retLambdaHistory: + lambdaHistory = np.zeros((maxiter + 3, sizeX), + dtype=blockVectorX.dtype) + if retResidualNormsHistory: + residualNormsHistory = np.zeros((maxiter + 3, sizeX), + dtype=blockVectorX.dtype) + + if verbosityLevel: + aux = "Solving " + if B is None: + aux += "standard" + else: + aux += "generalized" + aux += " eigenvalue problem with" + if M is None: + aux += "out" + aux += " preconditioning\n\n" + aux += f"matrix size {n}\n" + aux += f"block size {sizeX}\n\n" + if blockVectorY is None: + aux += "No constraints\n\n" + else: + if sizeY > 1: + aux += f"{sizeY} constraints\n\n" + else: + aux += f"{sizeY} constraint\n\n" + print(aux) + + if (n - sizeY) < (5 * sizeX): + warnings.warn( + f"The problem size {n} minus the constraints size {sizeY} " + f"is too small relative to the block size {sizeX}. " + f"Using a dense eigensolver instead of LOBPCG iterations." + f"No output of the history of the iterations.", + UserWarning, stacklevel=2 + ) + + sizeX = min(sizeX, n) + + if blockVectorY is not None: + raise NotImplementedError( + "The dense eigensolver does not support constraints." + ) + + # Define the closed range of indices of eigenvalues to return. + if largest: + eigvals = (n - sizeX, n - 1) + else: + eigvals = (0, sizeX - 1) + + try: + if isinstance(A, LinearOperator): + A = A(np.eye(n, dtype=int)) + elif callable(A): + A = A(np.eye(n, dtype=int)) + if A.shape != (n, n): + raise ValueError( + f"The shape {A.shape} of the primary matrix\n" + f"defined by a callable object is wrong.\n" + f"Expected {(n, n)}." + ) + elif issparse(A): + A = A.toarray() + else: + A = np.asarray(A) + except Exception as e: + raise Exception( + f"Primary MatMul call failed with error\n" + f"{e}\n") + + if B is not None: + try: + if isinstance(B, LinearOperator): + B = B(np.eye(n, dtype=int)) + elif callable(B): + B = B(np.eye(n, dtype=int)) + if B.shape != (n, n): + raise ValueError( + f"The shape {B.shape} of the secondary matrix\n" + f"defined by a callable object is wrong.\n" + ) + elif issparse(B): + B = B.toarray() + else: + B = np.asarray(B) + except Exception as e: + raise Exception( + f"Secondary MatMul call failed with error\n" + f"{e}\n") + + try: + vals, vecs = eigh(A, + B, + subset_by_index=eigvals, + check_finite=False) + if largest: + # Reverse order to be compatible with eigs() in 'LM' mode. + vals = vals[::-1] + vecs = vecs[:, ::-1] + + return vals, vecs + except Exception as e: + raise Exception( + f"Dense eigensolver failed with error\n" + f"{e}\n" + ) + + if (residualTolerance is None) or (residualTolerance <= 0.0): + residualTolerance = np.sqrt(np.finfo(blockVectorX.dtype).eps) * n + + A = _makeMatMat(A) + B = _makeMatMat(B) + M = _makeMatMat(M) + + # Apply constraints to X. + if blockVectorY is not None: + + if B is not None: + blockVectorBY = B(blockVectorY) + if blockVectorBY.shape != blockVectorY.shape: + raise ValueError( + f"The shape {blockVectorY.shape} " + f"of the constraint not preserved\n" + f"and changed to {blockVectorBY.shape} " + f"after multiplying by the secondary matrix.\n" + ) + else: + blockVectorBY = blockVectorY + + # gramYBY is a dense array. + gramYBY = blockVectorY.T.conj() @ blockVectorBY + try: + # gramYBY is a Cholesky factor from now on... + gramYBY = cho_factor(gramYBY, overwrite_a=True) + except LinAlgError as e: + raise ValueError("Linearly dependent constraints") from e + + _applyConstraints(blockVectorX, gramYBY, blockVectorBY, blockVectorY) + + ## + # B-orthonormalize X. + blockVectorX, blockVectorBX, _ = _b_orthonormalize( + B, blockVectorX, verbosityLevel=verbosityLevel) + if blockVectorX is None: + raise ValueError("Linearly dependent initial approximations") + + ## + # Compute the initial Ritz vectors: solve the eigenproblem. + blockVectorAX = A(blockVectorX) + if blockVectorAX.shape != blockVectorX.shape: + raise ValueError( + f"The shape {blockVectorX.shape} " + f"of the initial approximations not preserved\n" + f"and changed to {blockVectorAX.shape} " + f"after multiplying by the primary matrix.\n" + ) + + gramXAX = blockVectorX.T.conj() @ blockVectorAX + + _lambda, eigBlockVector = eigh(gramXAX, check_finite=False) + ii = _get_indx(_lambda, sizeX, largest) + _lambda = _lambda[ii] + if retLambdaHistory: + lambdaHistory[0, :] = _lambda + + eigBlockVector = np.asarray(eigBlockVector[:, ii]) + blockVectorX = _matmul_inplace( + blockVectorX, eigBlockVector, + verbosityLevel=verbosityLevel + ) + blockVectorAX = _matmul_inplace( + blockVectorAX, eigBlockVector, + verbosityLevel=verbosityLevel + ) + if B is not None: + blockVectorBX = _matmul_inplace( + blockVectorBX, eigBlockVector, + verbosityLevel=verbosityLevel + ) + + ## + # Active index set. + activeMask = np.ones((sizeX,), dtype=bool) + + ## + # Main iteration loop. + + blockVectorP = None # set during iteration + blockVectorAP = None + blockVectorBP = None + + smallestResidualNorm = np.abs(np.finfo(blockVectorX.dtype).max) + + iterationNumber = -1 + restart = True + forcedRestart = False + explicitGramFlag = False + while iterationNumber < maxiter: + iterationNumber += 1 + + if B is not None: + aux = blockVectorBX * _lambda[np.newaxis, :] + else: + aux = blockVectorX * _lambda[np.newaxis, :] + + blockVectorR = blockVectorAX - aux + + aux = np.sum(blockVectorR.conj() * blockVectorR, 0) + residualNorms = np.sqrt(np.abs(aux)) + if retResidualNormsHistory: + residualNormsHistory[iterationNumber, :] = residualNorms + residualNorm = np.sum(np.abs(residualNorms)) / sizeX + + if residualNorm < smallestResidualNorm: + smallestResidualNorm = residualNorm + bestIterationNumber = iterationNumber + bestblockVectorX = blockVectorX + elif residualNorm > 2**restartControl * smallestResidualNorm: + forcedRestart = True + blockVectorAX = A(blockVectorX) + if blockVectorAX.shape != blockVectorX.shape: + raise ValueError( + f"The shape {blockVectorX.shape} " + f"of the restarted iterate not preserved\n" + f"and changed to {blockVectorAX.shape} " + f"after multiplying by the primary matrix.\n" + ) + if B is not None: + blockVectorBX = B(blockVectorX) + if blockVectorBX.shape != blockVectorX.shape: + raise ValueError( + f"The shape {blockVectorX.shape} " + f"of the restarted iterate not preserved\n" + f"and changed to {blockVectorBX.shape} " + f"after multiplying by the secondary matrix.\n" + ) + + ii = np.where(residualNorms > residualTolerance, True, False) + activeMask = activeMask & ii + currentBlockSize = activeMask.sum() + + if verbosityLevel: + print(f"iteration {iterationNumber}") + print(f"current block size: {currentBlockSize}") + print(f"eigenvalue(s):\n{_lambda}") + print(f"residual norm(s):\n{residualNorms}") + + if currentBlockSize == 0: + break + + activeBlockVectorR = _as2d(blockVectorR[:, activeMask]) + + if iterationNumber > 0: + activeBlockVectorP = _as2d(blockVectorP[:, activeMask]) + activeBlockVectorAP = _as2d(blockVectorAP[:, activeMask]) + if B is not None: + activeBlockVectorBP = _as2d(blockVectorBP[:, activeMask]) + + if M is not None: + # Apply preconditioner T to the active residuals. + activeBlockVectorR = M(activeBlockVectorR) + + ## + # Apply constraints to the preconditioned residuals. + if blockVectorY is not None: + _applyConstraints(activeBlockVectorR, + gramYBY, + blockVectorBY, + blockVectorY) + + ## + # B-orthogonalize the preconditioned residuals to X. + if B is not None: + activeBlockVectorR = activeBlockVectorR - ( + blockVectorX @ + (blockVectorBX.T.conj() @ activeBlockVectorR) + ) + else: + activeBlockVectorR = activeBlockVectorR - ( + blockVectorX @ + (blockVectorX.T.conj() @ activeBlockVectorR) + ) + + ## + # B-orthonormalize the preconditioned residuals. + aux = _b_orthonormalize( + B, activeBlockVectorR, verbosityLevel=verbosityLevel) + activeBlockVectorR, activeBlockVectorBR, _ = aux + + if activeBlockVectorR is None: + warnings.warn( + f"Failed at iteration {iterationNumber} with accuracies " + f"{residualNorms}\n not reaching the requested " + f"tolerance {residualTolerance}.", + UserWarning, stacklevel=2 + ) + break + activeBlockVectorAR = A(activeBlockVectorR) + + if iterationNumber > 0: + if B is not None: + aux = _b_orthonormalize( + B, activeBlockVectorP, activeBlockVectorBP, + verbosityLevel=verbosityLevel + ) + activeBlockVectorP, activeBlockVectorBP, invR = aux + else: + aux = _b_orthonormalize(B, activeBlockVectorP, + verbosityLevel=verbosityLevel) + activeBlockVectorP, _, invR = aux + # Function _b_orthonormalize returns None if Cholesky fails + if activeBlockVectorP is not None: + activeBlockVectorAP = _matmul_inplace( + activeBlockVectorAP, invR, + verbosityLevel=verbosityLevel + ) + restart = forcedRestart + else: + restart = True + + ## + # Perform the Rayleigh Ritz Procedure: + # Compute symmetric Gram matrices: + + if activeBlockVectorAR.dtype == "float32": + myeps = 1 + else: + myeps = np.sqrt(np.finfo(activeBlockVectorR.dtype).eps) + + if residualNorms.max() > myeps and not explicitGramFlag: + explicitGramFlag = False + else: + # Once explicitGramFlag, forever explicitGramFlag. + explicitGramFlag = True + + # Shared memory assignments to simplify the code + if B is None: + blockVectorBX = blockVectorX + activeBlockVectorBR = activeBlockVectorR + if not restart: + activeBlockVectorBP = activeBlockVectorP + + # Common submatrices: + gramXAR = np.dot(blockVectorX.T.conj(), activeBlockVectorAR) + gramRAR = np.dot(activeBlockVectorR.T.conj(), activeBlockVectorAR) + + gramDtype = activeBlockVectorAR.dtype + if explicitGramFlag: + gramRAR = (gramRAR + gramRAR.T.conj()) / 2 + gramXAX = np.dot(blockVectorX.T.conj(), blockVectorAX) + gramXAX = (gramXAX + gramXAX.T.conj()) / 2 + gramXBX = np.dot(blockVectorX.T.conj(), blockVectorBX) + gramRBR = np.dot(activeBlockVectorR.T.conj(), activeBlockVectorBR) + gramXBR = np.dot(blockVectorX.T.conj(), activeBlockVectorBR) + else: + gramXAX = np.diag(_lambda).astype(gramDtype) + gramXBX = np.eye(sizeX, dtype=gramDtype) + gramRBR = np.eye(currentBlockSize, dtype=gramDtype) + gramXBR = np.zeros((sizeX, currentBlockSize), dtype=gramDtype) + + if not restart: + gramXAP = np.dot(blockVectorX.T.conj(), activeBlockVectorAP) + gramRAP = np.dot(activeBlockVectorR.T.conj(), activeBlockVectorAP) + gramPAP = np.dot(activeBlockVectorP.T.conj(), activeBlockVectorAP) + gramXBP = np.dot(blockVectorX.T.conj(), activeBlockVectorBP) + gramRBP = np.dot(activeBlockVectorR.T.conj(), activeBlockVectorBP) + if explicitGramFlag: + gramPAP = (gramPAP + gramPAP.T.conj()) / 2 + gramPBP = np.dot(activeBlockVectorP.T.conj(), + activeBlockVectorBP) + else: + gramPBP = np.eye(currentBlockSize, dtype=gramDtype) + + gramA = np.block( + [ + [gramXAX, gramXAR, gramXAP], + [gramXAR.T.conj(), gramRAR, gramRAP], + [gramXAP.T.conj(), gramRAP.T.conj(), gramPAP], + ] + ) + gramB = np.block( + [ + [gramXBX, gramXBR, gramXBP], + [gramXBR.T.conj(), gramRBR, gramRBP], + [gramXBP.T.conj(), gramRBP.T.conj(), gramPBP], + ] + ) + + _handle_gramA_gramB_verbosity(gramA, gramB, verbosityLevel) + + try: + _lambda, eigBlockVector = eigh(gramA, + gramB, + check_finite=False) + except LinAlgError as e: + # raise ValueError("eigh failed in lobpcg iterations") from e + if verbosityLevel: + warnings.warn( + f"eigh failed at iteration {iterationNumber} \n" + f"with error {e} causing a restart.\n", + UserWarning, stacklevel=2 + ) + # try again after dropping the direction vectors P from RR + restart = True + + if restart: + gramA = np.block([[gramXAX, gramXAR], [gramXAR.T.conj(), gramRAR]]) + gramB = np.block([[gramXBX, gramXBR], [gramXBR.T.conj(), gramRBR]]) + + _handle_gramA_gramB_verbosity(gramA, gramB, verbosityLevel) + + try: + _lambda, eigBlockVector = eigh(gramA, + gramB, + check_finite=False) + except LinAlgError as e: + # raise ValueError("eigh failed in lobpcg iterations") from e + warnings.warn( + f"eigh failed at iteration {iterationNumber} with error\n" + f"{e}\n", + UserWarning, stacklevel=2 + ) + break + + ii = _get_indx(_lambda, sizeX, largest) + _lambda = _lambda[ii] + eigBlockVector = eigBlockVector[:, ii] + if retLambdaHistory: + lambdaHistory[iterationNumber + 1, :] = _lambda + + # Compute Ritz vectors. + if B is not None: + if not restart: + eigBlockVectorX = eigBlockVector[:sizeX] + eigBlockVectorR = eigBlockVector[sizeX: + sizeX + currentBlockSize] + eigBlockVectorP = eigBlockVector[sizeX + currentBlockSize:] + + pp = np.dot(activeBlockVectorR, eigBlockVectorR) + pp += np.dot(activeBlockVectorP, eigBlockVectorP) + + app = np.dot(activeBlockVectorAR, eigBlockVectorR) + app += np.dot(activeBlockVectorAP, eigBlockVectorP) + + bpp = np.dot(activeBlockVectorBR, eigBlockVectorR) + bpp += np.dot(activeBlockVectorBP, eigBlockVectorP) + else: + eigBlockVectorX = eigBlockVector[:sizeX] + eigBlockVectorR = eigBlockVector[sizeX:] + + pp = np.dot(activeBlockVectorR, eigBlockVectorR) + app = np.dot(activeBlockVectorAR, eigBlockVectorR) + bpp = np.dot(activeBlockVectorBR, eigBlockVectorR) + + blockVectorX = np.dot(blockVectorX, eigBlockVectorX) + pp + blockVectorAX = np.dot(blockVectorAX, eigBlockVectorX) + app + blockVectorBX = np.dot(blockVectorBX, eigBlockVectorX) + bpp + + blockVectorP, blockVectorAP, blockVectorBP = pp, app, bpp + + else: + if not restart: + eigBlockVectorX = eigBlockVector[:sizeX] + eigBlockVectorR = eigBlockVector[sizeX: + sizeX + currentBlockSize] + eigBlockVectorP = eigBlockVector[sizeX + currentBlockSize:] + + pp = np.dot(activeBlockVectorR, eigBlockVectorR) + pp += np.dot(activeBlockVectorP, eigBlockVectorP) + + app = np.dot(activeBlockVectorAR, eigBlockVectorR) + app += np.dot(activeBlockVectorAP, eigBlockVectorP) + else: + eigBlockVectorX = eigBlockVector[:sizeX] + eigBlockVectorR = eigBlockVector[sizeX:] + + pp = np.dot(activeBlockVectorR, eigBlockVectorR) + app = np.dot(activeBlockVectorAR, eigBlockVectorR) + + blockVectorX = np.dot(blockVectorX, eigBlockVectorX) + pp + blockVectorAX = np.dot(blockVectorAX, eigBlockVectorX) + app + + blockVectorP, blockVectorAP = pp, app + + if B is not None: + aux = blockVectorBX * _lambda[np.newaxis, :] + else: + aux = blockVectorX * _lambda[np.newaxis, :] + + blockVectorR = blockVectorAX - aux + + aux = np.sum(blockVectorR.conj() * blockVectorR, 0) + residualNorms = np.sqrt(np.abs(aux)) + # Use old lambda in case of early loop exit. + if retLambdaHistory: + lambdaHistory[iterationNumber + 1, :] = _lambda + if retResidualNormsHistory: + residualNormsHistory[iterationNumber + 1, :] = residualNorms + residualNorm = np.sum(np.abs(residualNorms)) / sizeX + if residualNorm < smallestResidualNorm: + smallestResidualNorm = residualNorm + bestIterationNumber = iterationNumber + 1 + bestblockVectorX = blockVectorX + + if np.max(np.abs(residualNorms)) > residualTolerance: + warnings.warn( + f"Exited at iteration {iterationNumber} with accuracies \n" + f"{residualNorms}\n" + f"not reaching the requested tolerance {residualTolerance}.\n" + f"Use iteration {bestIterationNumber} instead with accuracy \n" + f"{smallestResidualNorm}.\n", + UserWarning, stacklevel=2 + ) + + if verbosityLevel: + print(f"Final iterative eigenvalue(s):\n{_lambda}") + print(f"Final iterative residual norm(s):\n{residualNorms}") + + blockVectorX = bestblockVectorX + # Making eigenvectors "exactly" satisfy the blockVectorY constrains + if blockVectorY is not None: + _applyConstraints(blockVectorX, + gramYBY, + blockVectorBY, + blockVectorY) + + # Making eigenvectors "exactly" othonormalized by final "exact" RR + blockVectorAX = A(blockVectorX) + if blockVectorAX.shape != blockVectorX.shape: + raise ValueError( + f"The shape {blockVectorX.shape} " + f"of the postprocessing iterate not preserved\n" + f"and changed to {blockVectorAX.shape} " + f"after multiplying by the primary matrix.\n" + ) + gramXAX = np.dot(blockVectorX.T.conj(), blockVectorAX) + + blockVectorBX = blockVectorX + if B is not None: + blockVectorBX = B(blockVectorX) + if blockVectorBX.shape != blockVectorX.shape: + raise ValueError( + f"The shape {blockVectorX.shape} " + f"of the postprocessing iterate not preserved\n" + f"and changed to {blockVectorBX.shape} " + f"after multiplying by the secondary matrix.\n" + ) + + gramXBX = np.dot(blockVectorX.T.conj(), blockVectorBX) + _handle_gramA_gramB_verbosity(gramXAX, gramXBX, verbosityLevel) + gramXAX = (gramXAX + gramXAX.T.conj()) / 2 + gramXBX = (gramXBX + gramXBX.T.conj()) / 2 + try: + _lambda, eigBlockVector = eigh(gramXAX, + gramXBX, + check_finite=False) + except LinAlgError as e: + raise ValueError("eigh has failed in lobpcg postprocessing") from e + + ii = _get_indx(_lambda, sizeX, largest) + _lambda = _lambda[ii] + eigBlockVector = np.asarray(eigBlockVector[:, ii]) + + blockVectorX = np.dot(blockVectorX, eigBlockVector) + blockVectorAX = np.dot(blockVectorAX, eigBlockVector) + + if B is not None: + blockVectorBX = np.dot(blockVectorBX, eigBlockVector) + aux = blockVectorBX * _lambda[np.newaxis, :] + else: + aux = blockVectorX * _lambda[np.newaxis, :] + + blockVectorR = blockVectorAX - aux + + aux = np.sum(blockVectorR.conj() * blockVectorR, 0) + residualNorms = np.sqrt(np.abs(aux)) + + if retLambdaHistory: + lambdaHistory[bestIterationNumber + 1, :] = _lambda + if retResidualNormsHistory: + residualNormsHistory[bestIterationNumber + 1, :] = residualNorms + + if retLambdaHistory: + lambdaHistory = lambdaHistory[ + : bestIterationNumber + 2, :] + if retResidualNormsHistory: + residualNormsHistory = residualNormsHistory[ + : bestIterationNumber + 2, :] + + if np.max(np.abs(residualNorms)) > residualTolerance: + warnings.warn( + f"Exited postprocessing with accuracies \n" + f"{residualNorms}\n" + f"not reaching the requested tolerance {residualTolerance}.", + UserWarning, stacklevel=2 + ) + + if verbosityLevel: + print(f"Final postprocessing eigenvalue(s):\n{_lambda}") + print(f"Final residual norm(s):\n{residualNorms}") + + if retLambdaHistory: + lambdaHistory = np.vsplit(lambdaHistory, np.shape(lambdaHistory)[0]) + lambdaHistory = [np.squeeze(i) for i in lambdaHistory] + if retResidualNormsHistory: + residualNormsHistory = np.vsplit(residualNormsHistory, + np.shape(residualNormsHistory)[0]) + residualNormsHistory = [np.squeeze(i) for i in residualNormsHistory] + + if retLambdaHistory: + if retResidualNormsHistory: + return _lambda, blockVectorX, lambdaHistory, residualNormsHistory + else: + return _lambda, blockVectorX, lambdaHistory + else: + if retResidualNormsHistory: + return _lambda, blockVectorX, residualNormsHistory + else: + return _lambda, blockVectorX diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/lobpcg/tests/__init__.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/lobpcg/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/lobpcg/tests/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/lobpcg/tests/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b06a3cfe5ae03e02ec1894c1e8031c9338cf434f Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/lobpcg/tests/__pycache__/__init__.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/lobpcg/tests/__pycache__/test_lobpcg.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/lobpcg/tests/__pycache__/test_lobpcg.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fc5637760e939291af65e8722b1640603ea8817d Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/lobpcg/tests/__pycache__/test_lobpcg.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/lobpcg/tests/test_lobpcg.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/lobpcg/tests/test_lobpcg.py new file mode 100644 index 0000000000000000000000000000000000000000..ccddf46db3cd69333f91314698fc67435288f376 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/lobpcg/tests/test_lobpcg.py @@ -0,0 +1,722 @@ +""" Test functions for the sparse.linalg._eigen.lobpcg module +""" + +import itertools +import platform +import sys +import pytest +import numpy as np +from numpy import ones, r_, diag +from numpy.testing import (assert_almost_equal, assert_equal, + assert_allclose, assert_array_less) + +from scipy import sparse +from scipy.linalg import (eigh, toeplitz, + cholesky_banded, cho_solve_banded) +from scipy.sparse import dia_array, eye_array, csr_array +from scipy.sparse.linalg import eigsh, LinearOperator +from scipy.sparse.linalg._eigen.lobpcg import lobpcg +from scipy.sparse.linalg._eigen.lobpcg.lobpcg import _b_orthonormalize +from scipy.sparse.linalg._special_sparse_arrays import (Sakurai, + MikotaPair) + +_IS_32BIT = (sys.maxsize < 2**32) + +INT_DTYPES = (np.intc, np.long, np.longlong, np.uintc, np.ulong, np.ulonglong) +# np.half is unsupported on many test systems so excluded +REAL_DTYPES = (np.float32, np.float64, np.longdouble) +COMPLEX_DTYPES = (np.complex64, np.complex128, np.clongdouble) +INEXACTDTYPES = REAL_DTYPES + COMPLEX_DTYPES +ALLDTYPES = INT_DTYPES + INEXACTDTYPES + + +def sign_align(A, B): + """Align signs of columns of A match those of B: column-wise remove + sign of A by multiplying with its sign then multiply in sign of B. + """ + return np.array([col_A * np.sign(col_A[0]) * np.sign(col_B[0]) + for col_A, col_B in zip(A.T, B.T)]).T + +def ElasticRod(n): + """Build the matrices for the generalized eigenvalue problem of the + fixed-free elastic rod vibration model. + """ + L = 1.0 + le = L/n + rho = 7.85e3 + S = 1.e-4 + E = 2.1e11 + mass = rho*S*le/6. + k = E*S/le + A = k*(diag(r_[2.*ones(n-1), 1])-diag(ones(n-1), 1)-diag(ones(n-1), -1)) + B = mass*(diag(r_[4.*ones(n-1), 2])+diag(ones(n-1), 1)+diag(ones(n-1), -1)) + return A, B + + +@pytest.mark.filterwarnings("ignore:The problem size") +@pytest.mark.parametrize("n", [10, 20]) +@pytest.mark.filterwarnings("ignore:Exited at iteration") +@pytest.mark.filterwarnings("ignore:Exited postprocessing") +def test_ElasticRod(n): + """Check eigh vs. lobpcg consistency for elastic rod model. + """ + A, B = ElasticRod(n) + m = 2 + rnd = np.random.RandomState(0) + X = rnd.standard_normal((n, m)) + eigvals, _ = lobpcg(A, X, B=B, tol=1e-2, maxiter=50, largest=False) + eigvals.sort() + w, _ = eigh(A, b=B) + w.sort() + assert_almost_equal(w[:int(m/2)], eigvals[:int(m/2)], decimal=2) + + +@pytest.mark.parametrize("n", [50]) +@pytest.mark.parametrize("m", [1, 2, 10]) +@pytest.mark.filterwarnings("ignore:Casting complex values to real") +@pytest.mark.filterwarnings("ignore::DeprecationWarning") +@pytest.mark.filterwarnings("ignore:An ill-conditioned matrix") +@pytest.mark.parametrize("Vdtype", INEXACTDTYPES) +@pytest.mark.parametrize("Bdtype", ALLDTYPES) +@pytest.mark.parametrize("BVdtype", INEXACTDTYPES) +def test_b_orthonormalize(n, m, Vdtype, Bdtype, BVdtype): + """Test B-orthonormalization by Cholesky with callable 'B'. + The function '_b_orthonormalize' is key in LOBPCG but may + lead to numerical instabilities. The input vectors are often + badly scaled, so the function needs scale-invariant Cholesky; + see https://netlib.org/lapack/lawnspdf/lawn14.pdf. + """ + rnd = np.random.RandomState(0) + X = rnd.standard_normal((n, m)).astype(Vdtype) + Xcopy = np.copy(X) + vals = np.arange(1, n+1, dtype=float) + B = dia_array(([vals], [0]), shape=(n, n)).astype(Bdtype) + BX = B @ X + BX = BX.astype(BVdtype) + is_all_complex = (np.issubdtype(Vdtype, np.complexfloating) and + np.issubdtype(BVdtype, np.complexfloating)) + is_all_notcomplex = (not np.issubdtype(Vdtype, np.complexfloating) and + not np.issubdtype(Bdtype, np.complexfloating) and + not np.issubdtype(BVdtype, np.complexfloating)) + + # All complex or all not complex can calculate in-place + check_inplace = is_all_complex or is_all_notcomplex + # np.longdouble tol cannot be achieved on most systems + atol = m * n * max(np.finfo(Vdtype).eps, + np.finfo(BVdtype).eps, + np.finfo(np.float64).eps) + + Xo, BXo, _ = _b_orthonormalize(lambda v: B @ v, X, BX) + if check_inplace: + # Check in-place + assert_equal(X, Xo) + assert_equal(id(X), id(Xo)) + assert_equal(BX, BXo) + assert_equal(id(BX), id(BXo)) + # Check BXo + assert_allclose(B @ Xo, BXo, atol=atol, rtol=atol) + # Check B-orthonormality + assert_allclose(Xo.T.conj() @ B @ Xo, np.identity(m), + atol=atol, rtol=atol) + # Repeat without BX in outputs + X = np.copy(Xcopy) + Xo1, BXo1, _ = _b_orthonormalize(lambda v: B @ v, X) + assert_allclose(Xo, Xo1, atol=atol, rtol=atol) + assert_allclose(BXo, BXo1, atol=atol, rtol=atol) + if check_inplace: + # Check in-place. + assert_equal(X, Xo1) + assert_equal(id(X), id(Xo1)) + # Check BXo1 + assert_allclose(B @ Xo1, BXo1, atol=atol, rtol=atol) + + # Introduce column-scaling in X + scaling = 1.0 / np.geomspace(10, 1e10, num=m) + X = Xcopy * scaling + X = X.astype(Vdtype) + BX = B @ X + BX = BX.astype(BVdtype) + # Check scaling-invariance of Cholesky-based orthonormalization + # XXX: internally, _b_orthonormalize tries to invert an ill-conditioned matrix + Xo1, BXo1, _ = _b_orthonormalize(lambda v: B @ v, X, BX) + # The output should be the same, up the signs of the columns + Xo1 = sign_align(Xo1, Xo) + assert_allclose(Xo, Xo1, atol=atol, rtol=atol) + BXo1 = sign_align(BXo1, BXo) + assert_allclose(BXo, BXo1, atol=atol, rtol=atol) + + +@pytest.mark.filterwarnings("ignore:Exited at iteration 0") +@pytest.mark.filterwarnings("ignore:Exited postprocessing") +def test_nonhermitian_warning(capsys): + """Check the warning of a Ritz matrix being not Hermitian + by feeding a non-Hermitian input matrix. + Also check stdout since verbosityLevel=1 and lack of stderr. + """ + n = 10 + X = np.arange(n * 2).reshape(n, 2).astype(np.float32) + A = np.arange(n * n).reshape(n, n).astype(np.float32) + with pytest.warns(UserWarning, match="Matrix gramA"): + _, _ = lobpcg(A, X, verbosityLevel=1, maxiter=0) + out, err = capsys.readouterr() # Capture output + assert out.startswith("Solving standard eigenvalue") # Test stdout + assert err == '' # Test empty stderr + # Make the matrix symmetric and the UserWarning disappears. + A += A.T + _, _ = lobpcg(A, X, verbosityLevel=1, maxiter=0) + out, err = capsys.readouterr() # Capture output + assert out.startswith("Solving standard eigenvalue") # Test stdout + assert err == '' # Test empty stderr + + +def test_regression(): + """Check the eigenvalue of the identity matrix is one. + """ + # https://mail.python.org/pipermail/scipy-user/2010-October/026944.html + n = 10 + X = np.ones((n, 1)) + A = np.identity(n) + w, _ = lobpcg(A, X) + assert_allclose(w, [1]) + + +@pytest.mark.filterwarnings("ignore:The problem size") +@pytest.mark.parametrize('n, m, m_excluded', [(30, 4, 3), (4, 2, 0)]) +def test_diagonal(n, m, m_excluded): + """Test ``m - m_excluded`` eigenvalues and eigenvectors of + diagonal matrices of the size ``n`` varying matrix formats: + dense array, spare matrix, and ``LinearOperator`` for both + matrixes in the generalized eigenvalue problem ``Av = cBv`` + and for the preconditioner. + """ + rnd = np.random.RandomState(0) + + # Define the generalized eigenvalue problem Av = cBv + # where (c, v) is a generalized eigenpair, + # A is the diagonal matrix whose entries are 1,...n, + # B is the identity matrix. + vals = np.arange(1, n+1, dtype=float) + A_s = dia_array(([vals], [0]), shape=(n, n)) + A_a = A_s.toarray() + + def A_f(x): + return A_s @ x + + A_lo = LinearOperator(matvec=A_f, + matmat=A_f, + shape=(n, n), dtype=float) + + B_a = eye_array(n) + B_s = csr_array(B_a) + + def B_f(x): + return B_a @ x + + B_lo = LinearOperator(matvec=B_f, + matmat=B_f, + shape=(n, n), dtype=float) + + # Let the preconditioner M be the inverse of A. + M_s = dia_array(([1./vals], [0]), shape=(n, n)) + M_a = M_s.toarray() + + def M_f(x): + return M_s @ x + + M_lo = LinearOperator(matvec=M_f, + matmat=M_f, + shape=(n, n), dtype=float) + + # Pick random initial vectors. + X = rnd.normal(size=(n, m)) + + # Require that the returned eigenvectors be in the orthogonal complement + # of the first few standard basis vectors. + if m_excluded > 0: + Y = np.eye(n, m_excluded) + else: + Y = None + + for A in [A_a, A_s, A_lo]: + for B in [B_a, B_s, B_lo]: + for M in [M_a, M_s, M_lo]: + eigvals, vecs = lobpcg(A, X, B, M=M, Y=Y, + maxiter=40, largest=False) + + assert_allclose(eigvals, np.arange(1+m_excluded, + 1+m_excluded+m)) + _check_eigen(A, eigvals, vecs, rtol=1e-3, atol=1e-3) + + +def _check_eigen(M, w, V, rtol=1e-8, atol=1e-14): + """Check if the eigenvalue residual is small. + """ + mult_wV = np.multiply(w, V) + dot_MV = M.dot(V) + assert_allclose(mult_wV, dot_MV, rtol=rtol, atol=atol) + + +def _check_fiedler(n, p): + """Check the Fiedler vector computation. + """ + # This is not necessarily the recommended way to find the Fiedler vector. + col = np.zeros(n) + col[1] = 1 + A = toeplitz(col) + D = np.diag(A.sum(axis=1)) + L = D - A + # Compute the full eigendecomposition using tricks, e.g. + # http://www.cs.yale.edu/homes/spielman/561/2009/lect02-09.pdf + tmp = np.pi * np.arange(n) / n + analytic_w = 2 * (1 - np.cos(tmp)) + analytic_V = np.cos(np.outer(np.arange(n) + 1/2, tmp)) + _check_eigen(L, analytic_w, analytic_V) + # Compute the full eigendecomposition using eigh. + eigh_w, eigh_V = eigh(L) + _check_eigen(L, eigh_w, eigh_V) + # Check that the first eigenvalue is near zero and that the rest agree. + assert_array_less(np.abs([eigh_w[0], analytic_w[0]]), 1e-14) + assert_allclose(eigh_w[1:], analytic_w[1:]) + + # Check small lobpcg eigenvalues. + X = analytic_V[:, :p] + lobpcg_w, lobpcg_V = lobpcg(L, X, largest=False) + assert_equal(lobpcg_w.shape, (p,)) + assert_equal(lobpcg_V.shape, (n, p)) + _check_eigen(L, lobpcg_w, lobpcg_V) + assert_array_less(np.abs(np.min(lobpcg_w)), 1e-14) + assert_allclose(np.sort(lobpcg_w)[1:], analytic_w[1:p]) + + # Check large lobpcg eigenvalues. + X = analytic_V[:, -p:] + lobpcg_w, lobpcg_V = lobpcg(L, X, largest=True) + assert_equal(lobpcg_w.shape, (p,)) + assert_equal(lobpcg_V.shape, (n, p)) + _check_eigen(L, lobpcg_w, lobpcg_V) + assert_allclose(np.sort(lobpcg_w), analytic_w[-p:]) + + # Look for the Fiedler vector using good but not exactly correct guesses. + fiedler_guess = np.concatenate((np.ones(n//2), -np.ones(n-n//2))) + X = np.vstack((np.ones(n), fiedler_guess)).T + lobpcg_w, _ = lobpcg(L, X, largest=False) + # Mathematically, the smaller eigenvalue should be zero + # and the larger should be the algebraic connectivity. + lobpcg_w = np.sort(lobpcg_w) + assert_allclose(lobpcg_w, analytic_w[:2], atol=1e-14) + + +def test_fiedler_small_8(): + """Check the dense workaround path for small matrices. + """ + # This triggers the dense path because 8 < 2*5. + with pytest.warns(UserWarning, match="The problem size"): + _check_fiedler(8, 2) + + +def test_fiedler_large_12(): + """Check the dense workaround path avoided for non-small matrices. + """ + # This does not trigger the dense path, because 2*5 <= 12. + _check_fiedler(12, 2) + + +@pytest.mark.filterwarnings("ignore:Failed at iteration") +@pytest.mark.filterwarnings("ignore:Exited at iteration") +@pytest.mark.filterwarnings("ignore:Exited postprocessing") +def test_failure_to_run_iterations(): + """Check that the code exits gracefully without breaking. Issue #10974. + The code may or not issue a warning, filtered out. Issue #15935, #17954. + """ + rnd = np.random.RandomState(0) + X = rnd.standard_normal((100, 10)) + A = X @ X.T + Q = rnd.standard_normal((X.shape[0], 4)) + eigenvalues, _ = lobpcg(A, Q, maxiter=40, tol=1e-12) + assert np.max(eigenvalues) > 0 + + +def test_failure_to_run_iterations_nonsymmetric(): + """Check that the code exists gracefully without breaking + if the matrix in not symmetric. + """ + A = np.zeros((10, 10)) + A[0, 1] = 1 + Q = np.ones((10, 1)) + msg = "Exited at iteration 2|Exited postprocessing with accuracies.*" + with pytest.warns(UserWarning, match=msg): + eigenvalues, _ = lobpcg(A, Q, maxiter=20) + assert np.max(eigenvalues) > 0 + + +@pytest.mark.filterwarnings("ignore:The problem size") +def test_hermitian(): + """Check complex-value Hermitian cases. + """ + rnd = np.random.RandomState(0) + + sizes = [3, 12] + ks = [1, 2] + gens = [True, False] + + for s, k, gen, dh, dx, db in ( + itertools.product(sizes, ks, gens, gens, gens, gens) + ): + H = rnd.random((s, s)) + 1.j * rnd.random((s, s)) + H = 10 * np.eye(s) + H + H.T.conj() + H = H.astype(np.complex128) if dh else H.astype(np.complex64) + + X = rnd.standard_normal((s, k)) + X = X + 1.j * rnd.standard_normal((s, k)) + X = X.astype(np.complex128) if dx else X.astype(np.complex64) + + if not gen: + B = np.eye(s) + w, v = lobpcg(H, X, maxiter=99, verbosityLevel=0) + # Also test mixing complex H with real B. + wb, _ = lobpcg(H, X, B, maxiter=99, verbosityLevel=0) + assert_allclose(w, wb, rtol=1e-6) + w0, _ = eigh(H) + else: + B = rnd.random((s, s)) + 1.j * rnd.random((s, s)) + B = 10 * np.eye(s) + B.dot(B.T.conj()) + B = B.astype(np.complex128) if db else B.astype(np.complex64) + w, v = lobpcg(H, X, B, maxiter=99, verbosityLevel=0) + w0, _ = eigh(H, B) + + for wx, vx in zip(w, v.T): + # Check eigenvector + assert_allclose(np.linalg.norm(H.dot(vx) - B.dot(vx) * wx) + / np.linalg.norm(H.dot(vx)), + 0, atol=5e-2, rtol=0) + + # Compare eigenvalues + j = np.argmin(abs(w0 - wx)) + assert_allclose(wx, w0[j], rtol=1e-4) + + +# The n=5 case tests the alternative small matrix code path that uses eigh(). +@pytest.mark.filterwarnings("ignore:The problem size") +@pytest.mark.parametrize('n, atol', [(20, 1e-3), (5, 1e-8)]) +def test_eigsh_consistency(n, atol): + """Check eigsh vs. lobpcg consistency. + """ + vals = np.arange(1, n+1, dtype=np.float64) + A = dia_array((vals, 0), shape=(n, n)) + rnd = np.random.RandomState(0) + X = rnd.standard_normal((n, 2)) + lvals, lvecs = lobpcg(A, X, largest=True, maxiter=100) + vals, _ = eigsh(A, k=2) + + _check_eigen(A, lvals, lvecs, atol=atol, rtol=0) + assert_allclose(np.sort(vals), np.sort(lvals), atol=1e-14) + + +def test_verbosity(): + """Check that nonzero verbosity level code runs. + """ + rnd = np.random.RandomState(0) + X = rnd.standard_normal((10, 10)) + A = X @ X.T + Q = rnd.standard_normal((X.shape[0], 1)) + msg = "Exited at iteration.*|Exited postprocessing with accuracies.*" + with pytest.warns(UserWarning, match=msg): + _, _ = lobpcg(A, Q, maxiter=3, verbosityLevel=9) + + +@pytest.mark.xfail(_IS_32BIT and sys.platform == 'win32', + reason="tolerance violation on windows") +@pytest.mark.xfail(platform.machine() == 'ppc64le', + reason="fails on ppc64le") +@pytest.mark.filterwarnings("ignore:Exited postprocessing") +def test_tolerance_float32(): + """Check lobpcg for attainable tolerance in float32. + """ + rnd = np.random.RandomState(0) + n = 50 + m = 3 + vals = -np.arange(1, n + 1) + A = dia_array(([vals], [0]), shape=(n, n)) + A = A.astype(np.float32) + X = rnd.standard_normal((n, m)) + X = X.astype(np.float32) + eigvals, _ = lobpcg(A, X, tol=1.25e-5, maxiter=50, verbosityLevel=0) + assert_allclose(eigvals, -np.arange(1, 1 + m), atol=2e-5, rtol=1e-5) + + +@pytest.mark.filterwarnings("ignore::DeprecationWarning") +@pytest.mark.parametrize("vdtype", INEXACTDTYPES) +@pytest.mark.parametrize("mdtype", ALLDTYPES) +@pytest.mark.parametrize("arr_type", [np.array, + sparse.csr_array, + sparse.coo_array]) +def test_dtypes(vdtype, mdtype, arr_type): + """Test lobpcg in various dtypes. + """ + rnd = np.random.RandomState(0) + n = 12 + m = 2 + A = arr_type(np.diag(np.arange(1, n + 1)).astype(mdtype)) + X = rnd.random((n, m)) + X = X.astype(vdtype) + eigvals, eigvecs = lobpcg(A, X, tol=1e-2, largest=False) + assert_allclose(eigvals, np.arange(1, 1 + m), atol=1e-1) + # eigenvectors must be nearly real in any case + assert_allclose(np.sum(np.abs(eigvecs - eigvecs.conj())), 0, atol=1e-2) + + +@pytest.mark.filterwarnings("ignore:Exited at iteration") +@pytest.mark.filterwarnings("ignore:Exited postprocessing") +def test_inplace_warning(): + """Check lobpcg gives a warning in '_b_orthonormalize' + that in-place orthogonalization is impossible due to dtype mismatch. + """ + rnd = np.random.RandomState(0) + n = 6 + m = 1 + vals = -np.arange(1, n + 1) + A = dia_array(([vals], [0]), shape=(n, n)) + A = A.astype(np.cdouble) + X = rnd.standard_normal((n, m)) + with pytest.warns(UserWarning, match="Inplace update"): + eigvals, _ = lobpcg(A, X, maxiter=2, verbosityLevel=1) + + +def test_maxit(): + """Check lobpcg if maxit=maxiter runs maxiter iterations and + if maxit=None runs 20 iterations (the default) + by checking the size of the iteration history output, which should + be the number of iterations plus 3 (initial, final, and postprocessing) + typically when maxiter is small and the choice of the best is passive. + """ + rnd = np.random.RandomState(0) + n = 50 + m = 4 + vals = -np.arange(1, n + 1) + A = dia_array(([vals], [0]), shape=(n, n)) + A = A.astype(np.float32) + X = rnd.standard_normal((n, m)) + X = X.astype(np.float64) + msg = "Exited at iteration.*|Exited postprocessing with accuracies.*" + for maxiter in range(1, 4): + with pytest.warns(UserWarning, match=msg): + _, _, l_h, r_h = lobpcg(A, X, tol=1e-8, maxiter=maxiter, + retLambdaHistory=True, + retResidualNormsHistory=True) + assert_allclose(np.shape(l_h)[0], maxiter+3) + assert_allclose(np.shape(r_h)[0], maxiter+3) + with pytest.warns(UserWarning, match=msg): + l, _, l_h, r_h = lobpcg(A, X, tol=1e-8, + retLambdaHistory=True, + retResidualNormsHistory=True) + assert_allclose(np.shape(l_h)[0], 20+3) + assert_allclose(np.shape(r_h)[0], 20+3) + # Check that eigenvalue output is the last one in history + assert_allclose(l, l_h[-1]) + # Make sure that both history outputs are lists + assert isinstance(l_h, list) + assert isinstance(r_h, list) + # Make sure that both history lists are arrays-like + assert_allclose(np.shape(l_h), np.shape(np.asarray(l_h))) + assert_allclose(np.shape(r_h), np.shape(np.asarray(r_h))) + + +@pytest.mark.xslow +@pytest.mark.filterwarnings("ignore:Exited at iteration") +@pytest.mark.filterwarnings("ignore:Exited postprocessing") +def test_sakurai(): + """Check lobpcg and eighs accuracy for the Sakurai example + already used in `benchmarks/benchmarks/sparse_linalg_lobpcg.py`. + """ + n = 50 + tol = 100 * n * n * n* np.finfo(float).eps + sakurai_obj = Sakurai(n, dtype='int') + A = sakurai_obj + m = 3 + ee = sakurai_obj.eigenvalues(3) + rng = np.random.default_rng(0) + X = rng.normal(size=(n, m)) + el, _ = lobpcg(A, X, tol=1e-9, maxiter=5000, largest=False) + accuracy = max(abs(ee - el) / ee) + assert_allclose(accuracy, 0., atol=tol) + a_l = LinearOperator((n, n), matvec=A, matmat=A, dtype='float64') + ea, _ = eigsh(a_l, k=m, which='SA', tol=1e-9, maxiter=15000, + v0 = rng.normal(size=(n, 1))) + accuracy = max(abs(ee - ea) / ee) + assert_allclose(accuracy, 0., atol=tol) + + +@pytest.mark.parametrize("n", [500, 1000]) +@pytest.mark.filterwarnings("ignore:Exited at iteration") +@pytest.mark.filterwarnings("ignore:Exited postprocessing") +def test_sakurai_inverse(n): + """Check lobpcg and eighs accuracy for the sakurai_inverse example + already used in `benchmarks/benchmarks/sparse_linalg_lobpcg.py`. + """ + def a(x): + return cho_solve_banded((c, False), x) + tol = 100 * n * n * n* np.finfo(float).eps + sakurai_obj = Sakurai(n) + A = sakurai_obj.tobanded().astype(np.float64) + m = 3 + ee = sakurai_obj.eigenvalues(3) + rng = np.random.default_rng(0) + X = rng.normal(size=(n, m)) + c = cholesky_banded(A) + el, _ = lobpcg(a, X, tol=1e-9, maxiter=8) + accuracy = max(abs(ee - 1. / el) / ee) + assert_allclose(accuracy, 0., atol=tol) + a_l = LinearOperator((n, n), matvec=a, matmat=a, dtype='float64') + ea, _ = eigsh(a_l, k=m, which='LA', tol=1e-9, maxiter=8, + v0 = rng.normal(size=(n, 1))) + accuracy = max(abs(ee - np.sort(1. / ea)) / ee) + assert_allclose(accuracy, 0., atol=tol) + + +@pytest.mark.filterwarnings("ignore:The problem size") +@pytest.mark.parametrize("n", [10, 20, 128, 256, 512, 1024, 2048]) +@pytest.mark.filterwarnings("ignore:Exited at iteration") +@pytest.mark.filterwarnings("ignore:Exited postprocessing") +def test_MikotaPair(n): + """Check lobpcg and eighs accuracy for the Mikota example + already used in `benchmarks/benchmarks/sparse_linalg_lobpcg.py`. + """ + def a(x): + return cho_solve_banded((c, False), x) + mik = MikotaPair(n) + mik_k = mik.k + mik_m = mik.m + Ac = mik_k + Bc = mik_m + Ab = mik_k.tobanded() + eigenvalues = mik.eigenvalues + if n == 10: + m = 3 # lobpcg calls eigh + elif n == 20: + m = 2 + else: + m = 10 + ee = eigenvalues(m) + tol = 100 * m * n * n * np.finfo(float).eps + rng = np.random.default_rng(0) + X = rng.normal(size=(n, m)) + c = cholesky_banded(Ab.astype(np.float32)) + el, _ = lobpcg(Ac, X, Bc, M=a, tol=1e-4, + maxiter=40, largest=False) + accuracy = max(abs(ee - el) / ee) + assert_allclose(accuracy, 0., atol=tol) + B = LinearOperator((n, n), matvec=Bc, matmat=Bc, dtype='float64') + A = LinearOperator((n, n), matvec=Ac, matmat=Ac, dtype='float64') + c = cholesky_banded(Ab) + a_l = LinearOperator((n, n), matvec=a, matmat=a, dtype='float64') + ea, _ = eigsh(B, k=m, M=A, Minv=a_l, which='LA', tol=1e-4, maxiter=50, + v0 = rng.normal(size=(n, 1))) + accuracy = max(abs(ee - np.sort(1./ea)) / ee) + assert_allclose(accuracy, 0., atol=tol) + + +@pytest.mark.slow +@pytest.mark.parametrize("n", [15]) +@pytest.mark.parametrize("m", [1, 2]) +@pytest.mark.filterwarnings("ignore:Exited at iteration") +@pytest.mark.filterwarnings("ignore:Exited postprocessing") +def test_diagonal_data_types(n, m): + """Check lobpcg for diagonal matrices for all matrix types. + Constraints are imposed, so a dense eigensolver eig cannot run. + """ + rnd = np.random.RandomState(0) + # Define the generalized eigenvalue problem Av = cBv + # where (c, v) is a generalized eigenpair, + # and where we choose A and B to be diagonal. + vals = np.arange(1, n + 1) + + list_sparse_format = ['bsr', 'coo', 'csc', 'csr', 'dia', 'dok', 'lil'] + for s_f_i, s_f in enumerate(list_sparse_format): + + As64 = dia_array(([vals * vals], [0]), shape=(n, n)).asformat(s_f) + As32 = As64.astype(np.float32) + Af64 = As64.toarray() + Af32 = Af64.astype(np.float32) + + def As32f(x): + return As32 @ x + As32LO = LinearOperator(matvec=As32f, + matmat=As32f, + shape=(n, n), + dtype=As32.dtype) + + listA = [Af64, As64, Af32, As32, As32f, As32LO, lambda v: As32 @ v] + + Bs64 = dia_array(([vals], [0]), shape=(n, n)).asformat(s_f) + Bf64 = Bs64.toarray() + Bs32 = Bs64.astype(np.float32) + + def Bs32f(x): + return Bs32 @ x + Bs32LO = LinearOperator(matvec=Bs32f, + matmat=Bs32f, + shape=(n, n), + dtype=Bs32.dtype) + listB = [Bf64, Bs64, Bs32, Bs32f, Bs32LO, lambda v: Bs32 @ v] + + # Define the preconditioner function as LinearOperator. + Ms64 = dia_array(([1./vals], [0]), shape=(n, n)).asformat(s_f) + + def Ms64precond(x): + return Ms64 @ x + Ms64precondLO = LinearOperator(matvec=Ms64precond, + matmat=Ms64precond, + shape=(n, n), + dtype=Ms64.dtype) + Mf64 = Ms64.toarray() + + def Mf64precond(x): + return Mf64 @ x + Mf64precondLO = LinearOperator(matvec=Mf64precond, + matmat=Mf64precond, + shape=(n, n), + dtype=Mf64.dtype) + Ms32 = Ms64.astype(np.float32) + + def Ms32precond(x): + return Ms32 @ x + Ms32precondLO = LinearOperator(matvec=Ms32precond, + matmat=Ms32precond, + shape=(n, n), + dtype=Ms32.dtype) + Mf32 = Ms32.toarray() + + def Mf32precond(x): + return Mf32 @ x + Mf32precondLO = LinearOperator(matvec=Mf32precond, + matmat=Mf32precond, + shape=(n, n), + dtype=Mf32.dtype) + listM = [None, Ms64, Ms64precondLO, Mf64precondLO, Ms64precond, + Ms32, Ms32precondLO, Mf32precondLO, Ms32precond] + + # Setup matrix of the initial approximation to the eigenvectors + # (cannot be sparse array). + Xf64 = rnd.random((n, m)) + Xf32 = Xf64.astype(np.float32) + listX = [Xf64, Xf32] + + # Require that the returned eigenvectors be in the orthogonal complement + # of the first few standard basis vectors (cannot be sparse array). + m_excluded = 3 + Yf64 = np.eye(n, m_excluded, dtype=float) + Yf32 = np.eye(n, m_excluded, dtype=np.float32) + listY = [Yf64, Yf32] + + tests = list(itertools.product(listA, listB, listM, listX, listY)) + + for A, B, M, X, Y in tests: + # This is one of the slower tests because there are >1,000 configs + # to test here. Flip a biased coin to decide whether to run each + # test to get decent coverage in less time. + if rnd.random() < 0.98: + continue # too many tests + eigvals, _ = lobpcg(A, X, B=B, M=M, Y=Y, tol=1e-4, + maxiter=100, largest=False) + assert_allclose(eigvals, + np.arange(1 + m_excluded, 1 + m_excluded + m), + atol=1e-5) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/tests/__init__.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/tests/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/tests/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7c9b9e760197ddaaf32d0db56fa0631e5027d443 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/tests/__pycache__/__init__.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/tests/__pycache__/test_svds.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/tests/__pycache__/test_svds.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..75c7ba26b2638a65888a4fb969930faf2b6359f8 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/tests/__pycache__/test_svds.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/tests/test_svds.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/tests/test_svds.py new file mode 100644 index 0000000000000000000000000000000000000000..209cdcf604a1eebf82d2a85e69945bd0e2dfa4a3 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_eigen/tests/test_svds.py @@ -0,0 +1,893 @@ +import re +import copy +import numpy as np + +from numpy.testing import assert_allclose, assert_equal, assert_array_equal +import pytest + +from scipy._lib._array_api import SCIPY_ARRAY_API +from scipy.linalg import svd, null_space +from scipy.sparse import csc_array, issparse, dia_array, random_array +from scipy.sparse.linalg import LinearOperator, aslinearoperator +from scipy.sparse.linalg import svds +from scipy.sparse.linalg._eigen.arpack import ArpackNoConvergence + + +# --- Helper Functions / Classes --- + + +def sorted_svd(m, k, which='LM'): + # Compute svd of a dense matrix m, and return singular vectors/values + # sorted. + if issparse(m): + m = m.toarray() + u, s, vh = svd(m) + if which == 'LM': + ii = np.argsort(s)[-k:] + elif which == 'SM': + ii = np.argsort(s)[:k] + else: + raise ValueError(f"unknown which={which!r}") + + return u[:, ii], s[ii], vh[ii] + + +def _check_svds(A, k, u, s, vh, which="LM", check_usvh_A=False, + check_svd=True, atol=1e-10, rtol=1e-7): + n, m = A.shape + + # Check shapes. + assert_equal(u.shape, (n, k)) + assert_equal(s.shape, (k,)) + assert_equal(vh.shape, (k, m)) + + # Check that the original matrix can be reconstituted. + A_rebuilt = (u*s).dot(vh) + assert_equal(A_rebuilt.shape, A.shape) + if check_usvh_A: + assert_allclose(A_rebuilt, A, atol=atol, rtol=rtol) + + # Check that u is a semi-orthogonal matrix. + uh_u = np.dot(u.T.conj(), u) + assert_equal(uh_u.shape, (k, k)) + assert_allclose(uh_u, np.identity(k), atol=atol, rtol=rtol) + + # Check that vh is a semi-orthogonal matrix. + vh_v = np.dot(vh, vh.T.conj()) + assert_equal(vh_v.shape, (k, k)) + assert_allclose(vh_v, np.identity(k), atol=atol, rtol=rtol) + + # Check that scipy.sparse.linalg.svds ~ scipy.linalg.svd + if check_svd: + u2, s2, vh2 = sorted_svd(A, k, which) + assert_allclose(np.abs(u), np.abs(u2), atol=atol, rtol=rtol) + assert_allclose(s, s2, atol=atol, rtol=rtol) + assert_allclose(np.abs(vh), np.abs(vh2), atol=atol, rtol=rtol) + + +def _check_svds_n(A, k, u, s, vh, which="LM", check_res=True, + check_svd=True, atol=1e-10, rtol=1e-7): + n, m = A.shape + + # Check shapes. + assert_equal(u.shape, (n, k)) + assert_equal(s.shape, (k,)) + assert_equal(vh.shape, (k, m)) + + # Check that u is a semi-orthogonal matrix. + uh_u = np.dot(u.T.conj(), u) + assert_equal(uh_u.shape, (k, k)) + error = np.sum(np.abs(uh_u - np.identity(k))) / (k * k) + assert_allclose(error, 0.0, atol=atol, rtol=rtol) + + # Check that vh is a semi-orthogonal matrix. + vh_v = np.dot(vh, vh.T.conj()) + assert_equal(vh_v.shape, (k, k)) + error = np.sum(np.abs(vh_v - np.identity(k))) / (k * k) + assert_allclose(error, 0.0, atol=atol, rtol=rtol) + + # Check residuals + if check_res: + ru = A.T.conj() @ u - vh.T.conj() * s + rus = np.sum(np.abs(ru)) / (n * k) + rvh = A @ vh.T.conj() - u * s + rvhs = np.sum(np.abs(rvh)) / (m * k) + assert_allclose(rus, 0.0, atol=atol, rtol=rtol) + assert_allclose(rvhs, 0.0, atol=atol, rtol=rtol) + + # Check that scipy.sparse.linalg.svds ~ scipy.linalg.svd + if check_svd: + u2, s2, vh2 = sorted_svd(A, k, which) + assert_allclose(s, s2, atol=atol, rtol=rtol) + A_rebuilt_svd = (u2*s2).dot(vh2) + A_rebuilt = (u*s).dot(vh) + assert_equal(A_rebuilt.shape, A.shape) + error = np.sum(np.abs(A_rebuilt_svd - A_rebuilt)) / (k * k) + assert_allclose(error, 0.0, atol=atol, rtol=rtol) + + +class CheckingLinearOperator(LinearOperator): + def __init__(self, A): + self.A = A + super().__init__(dtype=A.dtype, shape=A.shape) + + def _matvec(self, x): + assert_equal(max(x.shape), np.size(x)) + return self.A.dot(x) + + def _rmatvec(self, x): + assert_equal(max(x.shape), np.size(x)) + return self.A.T.conjugate().dot(x) + + +# --- Test Input Validation --- +# Tests input validation on parameters `k` and `which`. +# Needs better input validation checks for all other parameters. + +class SVDSCommonTests: + + solver = None + + # some of these IV tests could run only once, say with solver=None + + _A_empty_msg = "`A` must not be empty." + _A_dtype_msg = "`A` must be of numeric data type" + _A_type_msg = "type not understood" + _A_ndim_msg = "Only 2-D input" + _A_validation_inputs = [ + (np.asarray([[]]), ValueError, _A_empty_msg), + ( + np.array([['a', 'b'], ['c', 'd']], dtype='object'), + TypeError if SCIPY_ARRAY_API else ValueError, + "only boolean and numerical dtypes" if SCIPY_ARRAY_API else _A_dtype_msg + ), + ("hi", TypeError, _A_type_msg), + (np.asarray([[[1., 2.], [3., 4.]]]), ValueError, _A_ndim_msg)] + + @pytest.mark.parametrize("args", _A_validation_inputs) + def test_svds_input_validation_A(self, args): + A, error_type, message = args + with pytest.raises(error_type, match=message): + svds(A, k=1, solver=self.solver, rng=0) + + @pytest.mark.parametrize("which", ["LM", "SM"]) + def test_svds_int_A(self, which): + A = np.asarray([[1, 2], [3, 4]]) + if self.solver == 'lobpcg': + with pytest.warns(UserWarning, match="The problem size"): + res = svds(A, k=1, which=which, solver=self.solver, rng=0) + else: + res = svds(A, k=1, which=which, solver=self.solver, rng=0) + _check_svds(A, 1, *res, which=which, atol=8e-10) + + def test_svds_diff0_docstring_example(self): + def diff0(a): + return np.diff(a, axis=0) + def diff0t(a): + if a.ndim == 1: + a = a[:,np.newaxis] # Turn 1D into 2D array + d = np.zeros((a.shape[0] + 1, a.shape[1]), dtype=a.dtype) + d[0, :] = - a[0, :] + d[1:-1, :] = a[0:-1, :] - a[1:, :] + d[-1, :] = a[-1, :] + return d + def diff0_func_aslo_def(n): + return LinearOperator(matvec=diff0, + matmat=diff0, + rmatvec=diff0t, + rmatmat=diff0t, + shape=(n - 1, n)) + n = 100 + diff0_func_aslo = diff0_func_aslo_def(n) + rng = np.random.default_rng(1758043640965324) + u, s, _ = svds(diff0_func_aslo, k=3, which='SM', rng=rng) + se = 2. * np.sin(np.pi * np.arange(1, 4) / (2. * n)) + ue = np.sqrt(2 / n) * np.sin(np.pi * np.outer(np.arange(1, n), + np.arange(1, 4)) / n) + assert_allclose(s, se, atol=1e-3) + assert_allclose(np.abs(u), np.abs(ue), atol=1e-6) + + @pytest.mark.parametrize("k", [-1, 0, 3, 4, 5, 1.5, "1"]) + def test_svds_input_validation_k_1(self, k): + rng = np.random.default_rng(0) + A = rng.random((4, 3)) + + # propack can do complete SVD + if self.solver == 'propack' and k == 3: + res = svds(A, k=k, solver=self.solver, rng=0) + _check_svds(A, k, *res, check_usvh_A=True, check_svd=True) + return + + message = ("`k` must be an integer satisfying") + with pytest.raises(ValueError, match=message): + svds(A, k=k, solver=self.solver, rng=0) + + def test_svds_input_validation_k_2(self): + # I think the stack trace is reasonable when `k` can't be converted + # to an int. + message = "int() argument must be a" + with pytest.raises(TypeError, match=re.escape(message)): + svds(np.eye(10), k=[], solver=self.solver, rng=0) + + message = "invalid literal for int()" + with pytest.raises(ValueError, match=message): + svds(np.eye(10), k="hi", solver=self.solver, rng=0) + + @pytest.mark.parametrize("tol", (-1, np.inf, np.nan)) + def test_svds_input_validation_tol_1(self, tol): + message = "`tol` must be a non-negative floating point value." + with pytest.raises(ValueError, match=message): + svds(np.eye(10), tol=tol, solver=self.solver, rng=0) + + @pytest.mark.parametrize("tol", ([], 'hi')) + def test_svds_input_validation_tol_2(self, tol): + # I think the stack trace is reasonable here + message = "'<' not supported between instances" + with pytest.raises(TypeError, match=message): + svds(np.eye(10), tol=tol, solver=self.solver, rng=0) + + @pytest.mark.parametrize("which", ('LA', 'SA', 'ekki', 0)) + def test_svds_input_validation_which(self, which): + # Regression test for a github issue. + # https://github.com/scipy/scipy/issues/4590 + # Function was not checking for eigenvalue type and unintended + # values could be returned. + with pytest.raises(ValueError, match="`which` must be in"): + svds(np.eye(10), which=which, solver=self.solver, rng=0) + + @pytest.mark.parametrize("transpose", (True, False)) + @pytest.mark.parametrize("n", range(4, 9)) + def test_svds_input_validation_v0_1(self, transpose, n): + rng = np.random.default_rng(0) + A = rng.random((5, 7)) + v0 = rng.random(n) + if transpose: + A = A.T + k = 2 + message = "`v0` must have shape" + + required_length = (A.shape[0] if self.solver == 'propack' + else min(A.shape)) + if n != required_length: + with pytest.raises(ValueError, match=message): + svds(A, k=k, v0=v0, solver=self.solver, rng=0) + + def test_svds_input_validation_v0_2(self): + A = np.ones((10, 10)) + v0 = np.ones((1, 10)) + message = "`v0` must have shape" + with pytest.raises(ValueError, match=message): + svds(A, k=1, v0=v0, solver=self.solver, rng=0) + + @pytest.mark.parametrize("v0", ("hi", 1, np.ones(10, dtype=int))) + def test_svds_input_validation_v0_3(self, v0): + A = np.ones((10, 10)) + message = "`v0` must be of floating or complex floating data type." + with pytest.raises(ValueError, match=message): + svds(A, k=1, v0=v0, solver=self.solver, rng=0) + + @pytest.mark.parametrize("maxiter", (-1, 0, 5.5)) + def test_svds_input_validation_maxiter_1(self, maxiter): + message = ("`maxiter` must be a positive integer.") + with pytest.raises(ValueError, match=message): + svds(np.eye(10), maxiter=maxiter, solver=self.solver, rng=0) + + def test_svds_input_validation_maxiter_2(self): + # I think the stack trace is reasonable when `k` can't be converted + # to an int. + message = "int() argument must be a" + with pytest.raises(TypeError, match=re.escape(message)): + svds(np.eye(10), maxiter=[], solver=self.solver, rng=0) + + message = "invalid literal for int()" + with pytest.raises(ValueError, match=message): + svds(np.eye(10), maxiter="hi", solver=self.solver, rng=0) + + @pytest.mark.parametrize("rsv", ('ekki', 10)) + def test_svds_input_validation_return_singular_vectors(self, rsv): + message = "`return_singular_vectors` must be in" + with pytest.raises(ValueError, match=message): + svds(np.eye(10), return_singular_vectors=rsv, solver=self.solver, rng=0) + + # --- Test Parameters --- + @pytest.mark.parametrize("k", [3, 5]) + @pytest.mark.parametrize("which", ["LM", "SM"]) + def test_svds_parameter_k_which(self, k, which): + # check that the `k` parameter sets the number of eigenvalues/ + # eigenvectors returned. + # Also check that the `which` parameter sets whether the largest or + # smallest eigenvalues are returned + rng = np.random.default_rng(0) + A = rng.random((10, 10)) + if self.solver == 'lobpcg': + with pytest.warns(UserWarning, match="The problem size"): + res = svds(A, k=k, which=which, solver=self.solver, rng=0) + else: + res = svds(A, k=k, which=which, solver=self.solver, rng=0) + _check_svds(A, k, *res, which=which, atol=1e-9, rtol=2e-13) + + @pytest.mark.filterwarnings("ignore:Exited", + reason="Ignore LOBPCG early exit.") + # loop instead of parametrize for simplicity + def test_svds_parameter_tol(self): + # check the effect of the `tol` parameter on solver accuracy by solving + # the same problem with varying `tol` and comparing the eigenvalues + # against ground truth computed + n = 100 # matrix size + k = 3 # number of eigenvalues to check + + # generate a random, sparse-ish matrix + # effect isn't apparent for matrices that are too small + rng = np.random.default_rng(0) + A = rng.random((n, n)) + A[A > .1] = 0 + A = A @ A.T + + _, s, _ = svd(A) # calculate ground truth + + # calculate the error as a function of `tol` + A = csc_array(A) + + def err(tol): + _, s2, _ = svds(A, k=k, v0=np.ones(n), maxiter=1000, + solver=self.solver, tol=tol, rng=0) + return np.linalg.norm((s2 - s[k-1::-1])/s[k-1::-1]) + + tols = [1e-4, 1e-2, 1e0] # tolerance levels to check + # for 'arpack' and 'propack', accuracies make discrete steps + accuracies = {'propack': [1e-12, 1e-6, 1e-4], + 'arpack': [2.5e-15, 1e-10, 1e-10], + 'lobpcg': [2e-12, 4e-2, 2]} + + for tol, accuracy in zip(tols, accuracies[self.solver]): + error = err(tol) + assert error < accuracy + + def test_svd_v0(self): + # check that the `v0` parameter affects the solution + n = 100 + k = 1 + # If k != 1, LOBPCG needs more initial vectors, which are generated + # with rng, so it does not pass w/ k >= 2. + # For some other values of `n`, the AssertionErrors are not raised + # with different v0s, which is reasonable. + + rng = np.random.default_rng(0) + A = rng.random((n, n)) + + # with the same v0, solutions are the same, and they are accurate + # v0 takes precedence over rng + v0a = rng.random(n) + res1a = svds(A, k, v0=v0a, solver=self.solver, rng=0) + res2a = svds(A, k, v0=v0a, solver=self.solver, rng=1) + for idx in range(3): + assert_allclose(res1a[idx], res2a[idx], rtol=1e-15, atol=2e-16) + _check_svds(A, k, *res1a) + + # with the same v0, solutions are the same, and they are accurate + v0b = rng.random(n) + res1b = svds(A, k, v0=v0b, solver=self.solver, rng=2) + res2b = svds(A, k, v0=v0b, solver=self.solver, rng=3) + for idx in range(3): + assert_allclose(res1b[idx], res2b[idx], rtol=1e-15, atol=2e-16) + _check_svds(A, k, *res1b) + + # with different v0, solutions can be numerically different + message = "Arrays are not equal" + with pytest.raises(AssertionError, match=message): + assert_equal(res1a, res1b) + + def test_svd_rng(self): + # check that the `rng` parameter affects the solution + # Admittedly, `n` and `k` are chosen so that all solver pass all + # these checks. That's a tall order, since LOBPCG doesn't want to + # achieve the desired accuracy and ARPACK often returns the same + # singular values/vectors for different v0. + n = 100 + k = 1 + + rng = np.random.default_rng(0) + A = rng.random((n, n)) + + # with the same rng, solutions are the same and accurate + res1a = svds(A, k, solver=self.solver, rng=0) + res2a = svds(A, k, solver=self.solver, rng=0) + for idx in range(3): + assert_allclose(res1a[idx], res2a[idx], rtol=1e-15, atol=2e-16) + _check_svds(A, k, *res1a) + + # with the same rng, solutions are the same and accurate + res1b = svds(A, k, solver=self.solver, rng=1) + res2b = svds(A, k, solver=self.solver, rng=1) + for idx in range(3): + assert_allclose(res1b[idx], res2b[idx], rtol=1e-15, atol=2e-16) + _check_svds(A, k, *res1b) + + # with different rng, solutions can be numerically different + message = "Arrays are not equal" + with pytest.raises(AssertionError, match=message): + assert_equal(res1a, res1b) + + def test_svd_rng_2(self): + n = 100 + k = 1 + + rng = np.random.default_rng(234981) + A = rng.random((n, n)) + rng_2 = copy.deepcopy(rng) + + # with the same rng, solutions are the same and accurate + res1a = svds(A, k, solver=self.solver, rng=rng) + res2a = svds(A, k, solver=self.solver, rng=rng_2) + for idx in range(3): + assert_allclose(res1a[idx], res2a[idx], rtol=1e-15, atol=2e-16) + _check_svds(A, k, *res1a) + + def test_svd_random_state(self): + # the legacy `random_state` argument should work for now, + # even with NumPy <2.2. + # Regression test for gh-25069 + rng = np.random.default_rng(0) + A = rng.random((5, 5)) + res = svds(A, 1, solver=self.solver, random_state=np.random.RandomState(0)) + _check_svds(A, 1, *res) + + @pytest.mark.filterwarnings("ignore:Exited", + reason="Ignore LOBPCG early exit.") + def test_svd_rng_3(self): + n = 100 + k = 5 + + rng1 = np.random.default_rng(0) + rng2 = np.random.default_rng(234832) + A = rng1.random((n, n)) + + # rng in different state produces accurate - but not + # not necessarily identical - results + res1a = svds(A, k, solver=self.solver, rng=rng1, maxiter=1000) + res2a = svds(A, k, solver=self.solver, rng=rng2, maxiter=1000) + _check_svds(A, k, *res1a, atol=2e-7) + _check_svds(A, k, *res2a, atol=2e-7) + + message = "Arrays are not equal" + with pytest.raises(AssertionError, match=message): + assert_equal(res1a, res2a) + + @pytest.mark.filterwarnings("ignore:Exited postprocessing") + def test_svd_maxiter(self): + # check that maxiter works as expected: should not return accurate + # solution after 1 iteration, but should with default `maxiter` + A = np.diag(np.arange(9)).astype(np.float64) + k = 1 + u, s, vh = sorted_svd(A, k) + # Use default maxiter by default + maxiter = None + + if self.solver == 'arpack': + message = "ARPACK error -1: No convergence" + with pytest.raises(ArpackNoConvergence, match=message): + svds(A, k, ncv=3, maxiter=1, solver=self.solver, rng=0) + elif self.solver == 'lobpcg': + # Set maxiter higher so test passes without changing + # default and breaking backward compatibility (gh-20221) + maxiter = 30 + with pytest.warns(UserWarning, match="Exited at iteration"): + svds(A, k, maxiter=1, solver=self.solver, rng=0) + elif self.solver == 'propack': + message = "k=1 singular triplets did not converge within" + with pytest.raises(np.linalg.LinAlgError, match=message): + svds(A, k, maxiter=1, solver=self.solver, rng=0) + + ud, sd, vhd = svds(A, k, solver=self.solver, maxiter=maxiter, rng=0) + _check_svds(A, k, ud, sd, vhd, atol=1e-8) + assert_allclose(np.abs(ud), np.abs(u), atol=1e-8) + assert_allclose(np.abs(vhd), np.abs(vh), atol=1e-8) + assert_allclose(np.abs(sd), np.abs(s), atol=1e-9) + + @pytest.mark.parametrize("rsv", (True, False, 'u', 'vh')) + @pytest.mark.parametrize("shape", ((5, 7), (6, 6), (7, 5))) + def test_svd_return_singular_vectors(self, rsv, shape): + # check that the return_singular_vectors parameter works as expected + rng = np.random.default_rng(0) + A = rng.random(shape) + k = 2 + M, N = shape + u, s, vh = sorted_svd(A, k) + + respect_u = True if self.solver == 'propack' else M <= N + respect_vh = True if self.solver == 'propack' else M > N + + if self.solver == 'lobpcg': + with pytest.warns(UserWarning, match="The problem size"): + if rsv is False: + s2 = svds(A, k, return_singular_vectors=rsv, + solver=self.solver, rng=rng) + assert_allclose(s2, s) + elif rsv == 'u' and respect_u: + u2, s2, vh2 = svds(A, k, return_singular_vectors=rsv, + solver=self.solver, rng=rng) + assert_allclose(np.abs(u2), np.abs(u)) + assert_allclose(s2, s) + assert vh2 is None + elif rsv == 'vh' and respect_vh: + u2, s2, vh2 = svds(A, k, return_singular_vectors=rsv, + solver=self.solver, rng=rng) + assert u2 is None + assert_allclose(s2, s) + assert_allclose(np.abs(vh2), np.abs(vh)) + else: + u2, s2, vh2 = svds(A, k, return_singular_vectors=rsv, + solver=self.solver, rng=rng) + if u2 is not None: + assert_allclose(np.abs(u2), np.abs(u)) + assert_allclose(s2, s) + if vh2 is not None: + assert_allclose(np.abs(vh2), np.abs(vh)) + else: + if rsv is False: + s2 = svds(A, k, return_singular_vectors=rsv, + solver=self.solver, rng=rng) + assert_allclose(s2, s) + elif rsv == 'u' and respect_u: + u2, s2, vh2 = svds(A, k, return_singular_vectors=rsv, + solver=self.solver, rng=rng) + assert_allclose(np.abs(u2), np.abs(u)) + assert_allclose(s2, s) + assert vh2 is None + elif rsv == 'vh' and respect_vh: + u2, s2, vh2 = svds(A, k, return_singular_vectors=rsv, + solver=self.solver, rng=rng) + assert u2 is None + assert_allclose(s2, s) + assert_allclose(np.abs(vh2), np.abs(vh)) + else: + u2, s2, vh2 = svds(A, k, return_singular_vectors=rsv, + solver=self.solver, rng=rng) + if u2 is not None: + assert_allclose(np.abs(u2), np.abs(u)) + assert_allclose(s2, s) + if vh2 is not None: + assert_allclose(np.abs(vh2), np.abs(vh)) + + # --- Test Basic Functionality --- + # Tests the accuracy of each solver for real and complex matrices provided + # as list, dense array, sparse matrix, and LinearOperator. + + A1 = [[1, 2, 3], [3, 4, 3], [1 + 1j, 0, 2], [0, 0, 1]] + A2 = [[1, 2, 3, 8 + 5j], [3 - 2j, 4, 3, 5], [1, 0, 2, 3], [0, 0, 1, 0]] + + @pytest.mark.filterwarnings("ignore:k >= N - 1", + reason="needed to demonstrate #16725") + @pytest.mark.parametrize('A', (A1, A2)) + @pytest.mark.parametrize('k', range(1, 5)) + # PROPACK fails a lot if @pytest.mark.parametrize('which', ("SM", "LM")) + @pytest.mark.parametrize('real', (True, False)) + @pytest.mark.parametrize('transpose', (False, True)) + # In gh-14299, it was suggested the `svds` should _not_ work with lists + @pytest.mark.parametrize('lo_type', (np.asarray, csc_array, + aslinearoperator)) + def test_svd_simple(self, A, k, real, transpose, lo_type): + + A = np.asarray(A) + A = np.real(A) if real else A + A = A.T if transpose else A + A2 = lo_type(A) + + # could check for the appropriate errors, but that is tested above + if k > min(A.shape): + pytest.skip("`k` cannot be greater than `min(A.shape)`") + if self.solver != 'propack' and k >= min(A.shape): + pytest.skip("Only PROPACK supports complete SVD") + if self.solver == 'arpack' and not real and k == min(A.shape) - 1: + pytest.skip("#16725") + + atol = 3e-10 + if self.solver == 'propack': + atol = 3e-9 # otherwise test fails on Linux aarch64 (see gh-19855) + + if self.solver == 'lobpcg': + with pytest.warns(UserWarning, match="The problem size"): + u, s, vh = svds(A2, k, solver=self.solver, rng=0) + else: + u, s, vh = svds(A2, k, solver=self.solver, rng=0) + _check_svds(A, k, u, s, vh, atol=atol) + + def test_svd_linop(self): + solver = self.solver + + nmks = [(6, 7, 3), + (9, 5, 4), + (10, 8, 5)] + + def reorder(args): + U, s, VH = args + j = np.argsort(s) + return U[:, j], s[j], VH[j, :] + + for n, m, k in nmks: + # Test svds on a LinearOperator. + A = np.random.RandomState(52).randn(n, m) + L = CheckingLinearOperator(A) + + if solver == 'propack': + v0 = np.ones(n) + else: + v0 = np.ones(min(A.shape)) + if solver == 'lobpcg': + with pytest.warns(UserWarning, match="The problem size"): + U1, s1, VH1 = reorder(svds(A, k, v0=v0, solver=solver, rng=0)) + U2, s2, VH2 = reorder(svds(L, k, v0=v0, solver=solver, rng=0)) + else: + U1, s1, VH1 = reorder(svds(A, k, v0=v0, solver=solver, rng=0)) + U2, s2, VH2 = reorder(svds(L, k, v0=v0, solver=solver, rng=0)) + + assert_allclose(np.abs(U1), np.abs(U2)) + assert_allclose(s1, s2) + assert_allclose(np.abs(VH1), np.abs(VH2)) + assert_allclose(np.dot(U1, np.dot(np.diag(s1), VH1)), + np.dot(U2, np.dot(np.diag(s2), VH2))) + + # Try again with which="SM". + A = np.random.RandomState(1909).randn(n, m) + L = CheckingLinearOperator(A) + + # TODO: arpack crashes when v0=v0, which="SM" + kwargs = {'v0': v0} if solver not in {None, 'arpack'} else {} + if self.solver == 'lobpcg': + with pytest.warns(UserWarning, match="The problem size"): + U1, s1, VH1 = reorder(svds(A, k, which="SM", solver=solver, + rng=0, **kwargs)) + U2, s2, VH2 = reorder(svds(L, k, which="SM", solver=solver, + rng=0, **kwargs)) + else: + U1, s1, VH1 = reorder(svds(A, k, which="SM", solver=solver, + rng=0, **kwargs)) + U2, s2, VH2 = reorder(svds(L, k, which="SM", solver=solver, + rng=0, **kwargs)) + + assert_allclose(np.abs(U1), np.abs(U2)) + assert_allclose(s1 + 1, s2 + 1) + assert_allclose(np.abs(VH1), np.abs(VH2)) + assert_allclose(np.dot(U1, np.dot(np.diag(s1), VH1)), + np.dot(U2, np.dot(np.diag(s2), VH2))) + + if k < min(n, m) - 1: + # Complex input and explicit which="LM". + for (dt, eps) in [(complex, 1e-7), (np.complex64, 3e-3)]: + rng = np.random.RandomState(1648) + A = (rng.randn(n, m) + 1j * rng.randn(n, m)).astype(dt) + L = CheckingLinearOperator(A) + + if self.solver == 'lobpcg': + with pytest.warns(UserWarning, + match="The problem size"): + U1, s1, VH1 = reorder(svds(A, k, which="LM", + solver=solver, rng=0)) + U2, s2, VH2 = reorder(svds(L, k, which="LM", + solver=solver, rng=0)) + else: + U1, s1, VH1 = reorder(svds(A, k, which="LM", + solver=solver, rng=0)) + U2, s2, VH2 = reorder(svds(L, k, which="LM", + solver=solver, rng=0)) + + assert_allclose(np.abs(U1), np.abs(U2), rtol=eps) + assert_allclose(s1, s2, rtol=eps) + assert_allclose(np.abs(VH1), np.abs(VH2), rtol=eps) + assert_allclose(np.dot(U1, np.dot(np.diag(s1), VH1)), + np.dot(U2, np.dot(np.diag(s2), VH2)), + rtol=eps) + + SHAPES = ((100, 100), (100, 101), (101, 100)) + + @pytest.mark.filterwarnings("ignore:Exited at iteration") + @pytest.mark.filterwarnings("ignore:Exited postprocessing") + @pytest.mark.parallel_threads_limit(4) # Very slow + @pytest.mark.parametrize("shape", SHAPES) + @pytest.mark.parametrize("dtype", (np.float32, np.float64, + np.complex64, np.complex128)) + def test_small_sigma_sparse(self, shape, dtype): + # https://github.com/scipy/scipy/pull/11829 + solver = self.solver + # 2do: PROPACK fails orthogonality of singular vectors + # if dtype == complex and self.solver == 'propack': + # pytest.skip("PROPACK unsupported for complex dtype") + rng = np.random.default_rng(0) + k = 5 + (m, n) = shape + S = random_array(shape=(m, n), density=0.1, rng=rng) + if dtype is complex: + S = + 1j * random_array(shape=(m, n), density=0.1, rng=rng) + e = np.ones(m) + e[0:5] *= 1e1 ** np.arange(-5, 0, 1) + S = dia_array((e, 0), shape=(m, m)) @ S + S = S.astype(dtype) + u, s, vh = svds(S, k, which='SM', solver=solver, maxiter=1000, rng=0) + c_svd = False # partial SVD can be different from full SVD + _check_svds_n(S, k, u, s, vh, which="SM", check_svd=c_svd, atol=2e-1) + + # --- Test Edge Cases --- + # Checks a few edge cases. + @pytest.mark.parametrize("shape", ((6, 5), (5, 5), (5, 6))) + @pytest.mark.parametrize("dtype", (float, complex)) + def test_svd_LM_ones_matrix(self, shape, dtype): + # Check that svds can deal with matrix_rank less than k in LM mode. + k = 3 + n, m = shape + A = np.ones((n, m), dtype=dtype) + + if self.solver == 'lobpcg': + with pytest.warns(UserWarning, match="The problem size"): + U, s, VH = svds(A, k, solver=self.solver, rng=0) + else: + U, s, VH = svds(A, k, solver=self.solver, rng=0) + + _check_svds(A, k, U, s, VH, check_usvh_A=True, check_svd=False) + + # Check that the largest singular value is near sqrt(n*m) + # and the other singular values have been forced to zero. + assert_allclose(np.max(s), np.sqrt(n*m)) + s = np.array(sorted(s)[:-1]) + 1 + z = np.ones_like(s) + assert_allclose(s, z) + + @pytest.mark.filterwarnings("ignore:k >= N - 1", + reason="needed to demonstrate #16725") + @pytest.mark.parametrize("shape", ((3, 4), (4, 4), (4, 3), (4, 2))) + @pytest.mark.parametrize("dtype", (float, complex)) + def test_zero_matrix(self, shape, dtype): + # Check that svds can deal with matrices containing only zeros; + # see https://github.com/scipy/scipy/issues/3452/ + # shape = (4, 2) is included because it is the particular case + # reported in the issue + k = 1 + n, m = shape + A = np.zeros((n, m), dtype=dtype) + + if (self.solver == 'arpack'): + pytest.skip('See gh-21110.') + + if (self.solver == 'arpack' and dtype is complex + and k == min(A.shape) - 1): + pytest.skip("#16725") + + if self.solver == 'propack': + pytest.skip("PROPACK failures unrelated to PR #16712") + + if self.solver == 'lobpcg': + with pytest.warns(UserWarning, match="The problem size"): + U, s, VH = svds(A, k, solver=self.solver, rng=0) + else: + U, s, VH = svds(A, k, solver=self.solver, rng=0) + + # Check some generic properties of svd. + _check_svds(A, k, U, s, VH, check_usvh_A=True, check_svd=False) + + # Check that the singular values are zero. + assert_array_equal(s, 0) + + @pytest.mark.parametrize("shape", ((20, 20), (20, 21), (21, 20))) + @pytest.mark.parametrize("dtype", (np.float32, np.float64, + np.complex64, np.complex128)) + @pytest.mark.filterwarnings("ignore:Exited", + reason="Ignore LOBPCG early exit.") + def test_small_sigma(self, shape, dtype): + rng = np.random.default_rng(179847540) + A = rng.random(shape).astype(dtype) + u, _, vh = svd(A, full_matrices=False) + if dtype in [np.float32, np.complex64]: + e = 10.0 + else: + e = 100.0 + t = e**(-np.arange(len(vh))).astype(dtype) + A = (u*t).dot(vh) + k = 4 + u, s, vh = svds(A, k, solver=self.solver, maxiter=100, rng=0) + t = np.sum(s > 0) + assert_equal(t, k) + # LOBPCG needs larger atol and rtol to pass + _check_svds_n(A, k, u, s, vh, atol=1e-3, rtol=1e0, check_svd=False) + + @pytest.mark.filterwarnings("ignore:The problem size") + @pytest.mark.parametrize("dtype", (np.float32, np.float64, + np.complex64, np.complex128)) + def test_small_sigma2(self, dtype): + rng = np.random.default_rng(179847540) + # create a 10x10 singular matrix with a 4-dim null space + dim = 4 + size = 10 + x = rng.random((size, size-dim)) + y = x[:, :dim] * rng.random(dim) + mat = np.hstack((x, y)) + mat = mat.astype(dtype) + + nz = null_space(mat) + assert_equal(nz.shape[1], dim) + + # Tolerances atol and rtol adjusted to pass np.float32 + # Use non-sparse svd + u, s, vh = svd(mat) + # Singular values are 0: + assert_allclose(s[-dim:], 0, atol=1e-6, rtol=1e0) + # Smallest right singular vectors in null space: + assert_allclose(mat @ vh[-dim:, :].T, 0, atol=1e-6, rtol=1e0) + + # Smallest singular values should be 0 + sp_mat = csc_array(mat) + su, ss, svh = svds(sp_mat, k=dim, which='SM', solver=self.solver, rng=0) + # Smallest dim singular values are 0: + assert_allclose(ss, 0, atol=1e-5, rtol=1e0) + # Smallest singular vectors via svds in null space: + n, m = mat.shape + if n < m: # else the assert fails with some libraries unclear why + assert_allclose(sp_mat.transpose() @ su, 0, atol=1e-5, rtol=1e0) + assert_allclose(sp_mat @ svh.T, 0, atol=1e-5, rtol=1e0) + +# --- Perform tests with each solver --- + + +class Test_SVDS_once: + @pytest.mark.parametrize("solver", ['ekki', object]) + def test_svds_input_validation_solver(self, solver): + message = "solver must be one of" + with pytest.raises(ValueError, match=message): + svds(np.ones((3, 4)), k=2, solver=solver, rng=0) + + +class Test_SVDS_ARPACK(SVDSCommonTests): + + def setup_method(self): + self.solver = 'arpack' + + @pytest.mark.parametrize("ncv", list(range(-1, 8)) + [4.5, "5"]) + def test_svds_input_validation_ncv_1(self, ncv): + rng = np.random.default_rng(0) + A = rng.random((6, 7)) + k = 3 + if ncv in {4, 5}: + u, s, vh = svds(A, k=k, ncv=ncv, solver=self.solver, rng=0) + # partial decomposition, so don't check that u@diag(s)@vh=A; + # do check that scipy.sparse.linalg.svds ~ scipy.linalg.svd + _check_svds(A, k, u, s, vh) + else: + message = ("`ncv` must be an integer satisfying") + with pytest.raises(ValueError, match=message): + svds(A, k=k, ncv=ncv, solver=self.solver, rng=0) + + def test_svds_input_validation_ncv_2(self): + # I think the stack trace is reasonable when `ncv` can't be converted + # to an int. + message = "int() argument must be a" + with pytest.raises(TypeError, match=re.escape(message)): + svds(np.eye(10), ncv=[], solver=self.solver, rng=0) + + message = "invalid literal for int()" + with pytest.raises(ValueError, match=message): + svds(np.eye(10), ncv="hi", solver=self.solver, rng=0) + + # I can't see a robust relationship between `ncv` and relevant outputs + # (e.g. accuracy, time), so no test of the parameter. + + +class Test_SVDS_LOBPCG(SVDSCommonTests): + + def setup_method(self): + self.solver = 'lobpcg' + + +class Test_SVDS_PROPACK(SVDSCommonTests): + + def setup_method(self): + self.solver = 'propack' + + def test_svd_LM_ones_matrix(self): + message = ("PROPACK does not return orthonormal singular vectors " + "associated with zero singular values.") + # There are some other issues with this matrix of all ones, e.g. + # `which='sm'` and `k=1` returns the largest singular value + pytest.xfail(message) + + def test_svd_LM_zeros_matrix(self): + message = ("PROPACK does not return orthonormal singular vectors " + "associated with zero singular values.") + pytest.xfail(message) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_expm_multiply.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_expm_multiply.py new file mode 100644 index 0000000000000000000000000000000000000000..04f88285620624a1f87ac65d5fe6e9c6b1e1b732 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_expm_multiply.py @@ -0,0 +1,816 @@ +"""Compute the action of the matrix exponential.""" +from warnings import warn + +import numpy as np + +import scipy.linalg +import scipy.sparse.linalg +from scipy.linalg._decomp_qr import qr +from scipy.sparse._sputils import is_pydata_spmatrix +from scipy.sparse.linalg import aslinearoperator +from scipy.sparse.linalg._interface import IdentityOperator +from scipy.sparse.linalg._onenormest import onenormest + +__all__ = ['expm_multiply'] + + +def _exact_inf_norm(A): + # A compatibility function which should eventually disappear. + if scipy.sparse.issparse(A): + return max(abs(A).sum(axis=1).flat) + elif is_pydata_spmatrix(A): + return max(abs(A).sum(axis=1)) + else: + return np.linalg.norm(A, np.inf) + + +def _exact_1_norm(A): + # A compatibility function which should eventually disappear. + if scipy.sparse.issparse(A): + return max(abs(A).sum(axis=0).flat) + elif is_pydata_spmatrix(A): + return max(abs(A).sum(axis=0)) + else: + return np.linalg.norm(A, 1) + + +def _trace(A): + # A compatibility function which should eventually disappear. + if is_pydata_spmatrix(A): + return A.to_scipy_sparse().trace() + else: + return A.trace() + + +def traceest(A, m3, seed=None): + """Estimate `np.trace(A)` using `3*m3` matrix-vector products. + + The result is not deterministic. + + Parameters + ---------- + A : LinearOperator + Linear operator whose trace will be estimated. Has to be square. + m3 : int + Number of matrix-vector products divided by 3 used to estimate the + trace. + seed : optional + Seed for `numpy.random.default_rng`. + Can be provided to obtain deterministic results. + + Returns + ------- + trace : LinearOperator.dtype + Estimate of the trace + + Notes + ----- + This is the Hutch++ algorithm given in [1]_. + + References + ---------- + .. [1] Meyer, Raphael A., Cameron Musco, Christopher Musco, and David P. + Woodruff. "Hutch++: Optimal Stochastic Trace Estimation." In Symposium + on Simplicity in Algorithms (SOSA), pp. 142-155. Society for Industrial + and Applied Mathematics, 2021. + :doi:`10.1137/1.9781611976496.16`. + + """ + rng = np.random.default_rng(seed) + if len(A.shape) != 2 or A.shape[-1] != A.shape[-2]: + raise ValueError("Expected A to be like a square matrix.") + n = A.shape[-1] + S = rng.choice([-1.0, +1.0], [n, m3]) + Q, _ = qr(A.matmat(S), overwrite_a=True, mode='economic') + trQAQ = np.trace(Q.conj().T @ A.matmat(Q)) + G = rng.choice([-1, +1], [n, m3]) + right = G - Q@(Q.conj().T @ G) + trGAG = np.trace(right.conj().T @ A.matmat(right)) + return trQAQ + trGAG/m3 + + +def _ident_like(A): + # A compatibility function which should eventually disappear. + if scipy.sparse.issparse(A): + # Creates a sparse matrix in dia format + out = scipy.sparse.eye(A.shape[0], A.shape[1], dtype=A.dtype) + if scipy.sparse.issparse(A): + return out.asformat(A.format) + return scipy.sparse.dia_array(out).asformat(A.format) + elif is_pydata_spmatrix(A): + import sparse + return sparse.eye(A.shape[0], A.shape[1], dtype=A.dtype) + elif isinstance(A, scipy.sparse.linalg.LinearOperator): + return IdentityOperator(A.shape, dtype=A.dtype) + else: + return np.eye(A.shape[0], A.shape[1], dtype=A.dtype) + + +def expm_multiply(A, B, start=None, stop=None, num=None, + endpoint=None, traceA=None): + """ + Compute the action of the matrix exponential of A on B. + + Parameters + ---------- + A : transposable linear operator + The operator whose exponential is of interest. + B : ndarray, sparse array + The matrix or vector to be multiplied by the matrix exponential of A. + start : scalar, optional + The starting time point of the sequence. + stop : scalar, optional + The end time point of the sequence, unless `endpoint` is set to False. + In that case, the sequence consists of all but the last of ``num + 1`` + evenly spaced time points, so that `stop` is excluded. + Note that the step size changes when `endpoint` is False. + num : int, optional + Number of time points to use. + endpoint : bool, optional + If True, `stop` is the last time point. Otherwise, it is not included. + traceA : scalar, optional + Trace of `A`. If not given the trace is estimated for linear operators, + or calculated exactly for sparse matrices. It is used to precondition + `A`, thus an approximate trace is acceptable. + For linear operators, `traceA` should be provided to ensure performance + as the estimation is not guaranteed to be reliable for all cases. + + .. versionadded:: 1.9.0 + + Returns + ------- + expm_A_B : ndarray + The result of the action :math:`e^{t_k A} B`. + + Warns + ----- + UserWarning + If `A` is a linear operator and ``traceA=None`` (default). + + Notes + ----- + The optional arguments defining the sequence of evenly spaced time points + are compatible with the arguments of `numpy.linspace`. + + The output ndarray shape is somewhat complicated so I explain it here. + The ndim of the output could be either 1, 2, or 3. + It would be 1 if you are computing the expm action on a single vector + at a single time point. + It would be 2 if you are computing the expm action on a vector + at multiple time points, or if you are computing the expm action + on a matrix at a single time point. + It would be 3 if you want the action on a matrix with multiple + columns at multiple time points. + If multiple time points are requested, expm_A_B[0] will always + be the action of the expm at the first time point, + regardless of whether the action is on a vector or a matrix. + + References + ---------- + .. [1] Awad H. Al-Mohy and Nicholas J. Higham (2011) + "Computing the Action of the Matrix Exponential, + with an Application to Exponential Integrators." + SIAM Journal on Scientific Computing, + 33 (2). pp. 488-511. ISSN 1064-8275 + http://eprints.ma.man.ac.uk/1591/ + + .. [2] Nicholas J. Higham and Awad H. Al-Mohy (2010) + "Computing Matrix Functions." + Acta Numerica, + 19. 159-208. ISSN 0962-4929 + http://eprints.ma.man.ac.uk/1451/ + + Examples + -------- + >>> import numpy as np + >>> from scipy.sparse import csc_array + >>> from scipy.sparse.linalg import expm, expm_multiply + >>> A = csc_array([[1, 0], [0, 1]]) + >>> A.toarray() + array([[1, 0], + [0, 1]], dtype=int64) + >>> B = np.array([np.exp(-1.), np.exp(-2.)]) + >>> B + array([ 0.36787944, 0.13533528]) + >>> expm_multiply(A, B, start=1, stop=2, num=3, endpoint=True) + array([[ 1. , 0.36787944], + [ 1.64872127, 0.60653066], + [ 2.71828183, 1. ]]) + >>> expm(A).dot(B) # Verify 1st timestep + array([ 1. , 0.36787944]) + >>> expm(1.5*A).dot(B) # Verify 2nd timestep + array([ 1.64872127, 0.60653066]) + >>> expm(2*A).dot(B) # Verify 3rd timestep + array([ 2.71828183, 1. ]) + """ + if all(arg is None for arg in (start, stop, num, endpoint)): + X = _expm_multiply_simple(A, B, traceA=traceA) + else: + X, status = _expm_multiply_interval(A, B, start, stop, num, + endpoint, traceA=traceA) + return X + + +def _expm_multiply_simple(A, B, t=1.0, traceA=None, balance=False): + """ + Compute the action of the matrix exponential at a single time point. + + Parameters + ---------- + A : transposable linear operator + The operator whose exponential is of interest. + B : ndarray + The matrix to be multiplied by the matrix exponential of A. + t : float + A time point. + traceA : scalar, optional + Trace of `A`. If not given the trace is estimated for linear operators, + or calculated exactly for sparse matrices. It is used to precondition + `A`, thus an approximate trace is acceptable + balance : bool + Indicates whether or not to apply balancing. + + Returns + ------- + F : ndarray + :math:`e^{t A} B` + + Notes + ----- + This is algorithm (3.2) in Al-Mohy and Higham (2011). + + """ + if balance: + raise NotImplementedError + if len(A.shape) != 2 or A.shape[0] != A.shape[1]: + raise ValueError('expected A to be like a square matrix') + if A.shape[1] != B.shape[0]: + raise ValueError(f'shapes of matrices A {A.shape} and B {B.shape}' + ' are incompatible') + ident = _ident_like(A) + is_linear_operator = isinstance(A, scipy.sparse.linalg.LinearOperator) + n = A.shape[0] + if len(B.shape) == 1: + n0 = 1 + elif len(B.shape) == 2: + n0 = B.shape[1] + else: + raise ValueError('expected B to be like a matrix or a vector') + u_d = 2**-53 + tol = u_d + if traceA is None: + if is_linear_operator: + warn("Trace of LinearOperator not available, it will be estimated." + " Provide `traceA` to ensure performance.", stacklevel=3) + # m3=1 is bit arbitrary choice, a more accurate trace (larger m3) might + # speed up exponential calculation, but trace estimation is more costly + traceA = traceest(A, m3=1) if is_linear_operator else _trace(A) + mu = traceA / float(n) + A = A - mu * ident + A_1_norm = onenormest(A) if is_linear_operator else _exact_1_norm(A) + if t*A_1_norm == 0: + m_star, s = 0, 1 + else: + ell = 2 + norm_info = LazyOperatorNormInfo(t*A, A_1_norm=t*A_1_norm, ell=ell) + m_star, s = _fragment_3_1(norm_info, n0, tol, ell=ell) + return _expm_multiply_simple_core(A, B, t, mu, m_star, s, tol, balance) + + +def _expm_multiply_simple_core(A, B, t, mu, m_star, s, tol=None, balance=False): + """ + A helper function. + """ + if balance: + raise NotImplementedError + if tol is None: + u_d = 2 ** -53 + tol = u_d + F = B + eta = np.exp(t*mu / float(s)) + for i in range(s): + c1 = _exact_inf_norm(B) + for j in range(m_star): + coeff = t / float(s*(j+1)) + B = coeff * A.dot(B) + c2 = _exact_inf_norm(B) + F = F + B + if c1 + c2 <= tol * _exact_inf_norm(F): + break + c1 = c2 + F = eta * F + B = F + return F + + +# This table helps to compute bounds. +# They seem to have been difficult to calculate, involving symbolic +# manipulation of equations, followed by numerical root finding. +_theta = { + # The first 30 values are from table A.3 of Computing Matrix Functions. + 1: 2.29e-16, + 2: 2.58e-8, + 3: 1.39e-5, + 4: 3.40e-4, + 5: 2.40e-3, + 6: 9.07e-3, + 7: 2.38e-2, + 8: 5.00e-2, + 9: 8.96e-2, + 10: 1.44e-1, + # 11 + 11: 2.14e-1, + 12: 3.00e-1, + 13: 4.00e-1, + 14: 5.14e-1, + 15: 6.41e-1, + 16: 7.81e-1, + 17: 9.31e-1, + 18: 1.09, + 19: 1.26, + 20: 1.44, + # 21 + 21: 1.62, + 22: 1.82, + 23: 2.01, + 24: 2.22, + 25: 2.43, + 26: 2.64, + 27: 2.86, + 28: 3.08, + 29: 3.31, + 30: 3.54, + # The rest are from table 3.1 of + # Computing the Action of the Matrix Exponential. + 35: 4.7, + 40: 6.0, + 45: 7.2, + 50: 8.5, + 55: 9.9, + } + + +def _onenormest_matrix_power(A, p, + t=2, itmax=5, compute_v=False, compute_w=False): + """ + Efficiently estimate the 1-norm of A^p. + + Parameters + ---------- + A : ndarray + Matrix whose 1-norm of a power is to be computed. + p : int + Non-negative integer power. + t : int, optional + A positive parameter controlling the tradeoff between + accuracy versus time and memory usage. + Larger values take longer and use more memory + but give more accurate output. + itmax : int, optional + Use at most this many iterations. + compute_v : bool, optional + Request a norm-maximizing linear operator input vector if True. + compute_w : bool, optional + Request a norm-maximizing linear operator output vector if True. + + Returns + ------- + est : float + An underestimate of the 1-norm of the sparse matrix. + v : ndarray, optional + The vector such that ||Av||_1 == est*||v||_1. + It can be thought of as an input to the linear operator + that gives an output with particularly large norm. + w : ndarray, optional + The vector Av which has relatively large 1-norm. + It can be thought of as an output of the linear operator + that is relatively large in norm compared to the input. + + """ + #XXX Eventually turn this into an API function in the _onenormest module, + #XXX and remove its underscore, + #XXX but wait until expm_multiply goes into scipy. + from scipy.sparse.linalg._onenormest import onenormest + return onenormest(aslinearoperator(A) ** p) + +class LazyOperatorNormInfo: + """ + Information about an operator is lazily computed. + + The information includes the exact 1-norm of the operator, + in addition to estimates of 1-norms of powers of the operator. + This uses the notation of Computing the Action (2011). + This class is specialized enough to probably not be of general interest + outside of this module. + + """ + + def __init__(self, A, A_1_norm=None, ell=2, scale=1): + """ + Provide the operator and some norm-related information. + + Parameters + ---------- + A : linear operator + The operator of interest. + A_1_norm : float, optional + The exact 1-norm of A. + ell : int, optional + A technical parameter controlling norm estimation quality. + scale : int, optional + If specified, return the norms of scale*A instead of A. + + """ + self._A = A + self._A_1_norm = A_1_norm + self._ell = ell + self._d = {} + self._scale = scale + + def set_scale(self,scale): + """ + Set the scale parameter. + """ + self._scale = scale + + def onenorm(self): + """ + Compute the exact 1-norm. + """ + if self._A_1_norm is None: + self._A_1_norm = _exact_1_norm(self._A) + return self._scale*self._A_1_norm + + def d(self, p): + """ + Lazily estimate :math:`d_p(A) ~= || A^p ||^(1/p)` + where :math:`||.||` is the 1-norm. + """ + if p not in self._d: + est = _onenormest_matrix_power(self._A, p, self._ell) + self._d[p] = est ** (1.0 / p) + return self._scale*self._d[p] + + def alpha(self, p): + """ + Lazily compute max(d(p), d(p+1)). + """ + return max(self.d(p), self.d(p+1)) + +def _compute_cost_div_m(m, p, norm_info): + """ + A helper function for computing bounds. + + This is equation (3.10). + It measures cost in terms of the number of required matrix products. + + Parameters + ---------- + m : int + A valid key of _theta. + p : int + A matrix power. + norm_info : LazyOperatorNormInfo + Information about 1-norms of related operators. + + Returns + ------- + cost_div_m : int + Required number of matrix products divided by m. + + """ + return int(np.ceil(norm_info.alpha(p) / _theta[m])) + + +def _compute_p_max(m_max): + """ + Compute the largest positive integer p such that p*(p-1) <= m_max + 1. + + Do this in a slightly dumb way, but safe and not too slow. + + Parameters + ---------- + m_max : int + A count related to bounds. + + """ + sqrt_m_max = np.sqrt(m_max) + p_low = int(np.floor(sqrt_m_max)) + p_high = int(np.ceil(sqrt_m_max + 1)) + return max(p for p in range(p_low, p_high+1) if p*(p-1) <= m_max + 1) + + +def _fragment_3_1(norm_info, n0, tol, m_max=55, ell=2): + """ + A helper function for the _expm_multiply_* functions. + + Parameters + ---------- + norm_info : LazyOperatorNormInfo + Information about norms of certain linear operators of interest. + n0 : int + Number of columns in the _expm_multiply_* B matrix. + tol : float + Expected to be + :math:`2^{-24}` for single precision or + :math:`2^{-53}` for double precision. + m_max : int + A value related to a bound. + ell : int + The number of columns used in the 1-norm approximation. + This is usually taken to be small, maybe between 1 and 5. + + Returns + ------- + best_m : int + Related to bounds for error control. + best_s : int + Amount of scaling. + + Notes + ----- + This is code fragment (3.1) in Al-Mohy and Higham (2011). + The discussion of default values for m_max and ell + is given between the definitions of equation (3.11) + and the definition of equation (3.12). + + """ + if ell < 1: + raise ValueError('expected ell to be a positive integer') + best_m = None + best_s = None + if _condition_3_13(norm_info.onenorm(), n0, m_max, ell): + for m, theta in _theta.items(): + s = int(np.ceil(norm_info.onenorm() / theta)) + if best_m is None or m * s < best_m * best_s: + best_m = m + best_s = s + else: + # Equation (3.11). + for p in range(2, _compute_p_max(m_max) + 1): + for m in range(p*(p-1)-1, m_max+1): + if m in _theta: + s = _compute_cost_div_m(m, p, norm_info) + if best_m is None or m * s < best_m * best_s: + best_m = m + best_s = s + best_s = max(best_s, 1) + return best_m, best_s + + +def _condition_3_13(A_1_norm, n0, m_max, ell): + """ + A helper function for the _expm_multiply_* functions. + + Parameters + ---------- + A_1_norm : float + The precomputed 1-norm of A. + n0 : int + Number of columns in the _expm_multiply_* B matrix. + m_max : int + A value related to a bound. + ell : int + The number of columns used in the 1-norm approximation. + This is usually taken to be small, maybe between 1 and 5. + + Returns + ------- + value : bool + Indicates whether or not the condition has been met. + + Notes + ----- + This is condition (3.13) in Al-Mohy and Higham (2011). + + """ + + # This is the rhs of equation (3.12). + p_max = _compute_p_max(m_max) + a = 2 * ell * p_max * (p_max + 3) + + # Evaluate the condition (3.13). + b = _theta[m_max] / float(n0 * m_max) + return A_1_norm <= a * b + + +def _expm_multiply_interval(A, B, start=None, stop=None, num=None, + endpoint=None, traceA=None, balance=False, + status_only=False): + """ + Compute the action of the matrix exponential at multiple time points. + + Parameters + ---------- + A : transposable linear operator + The operator whose exponential is of interest. + B : ndarray + The matrix to be multiplied by the matrix exponential of A. + start : scalar, optional + The starting time point of the sequence. + stop : scalar, optional + The end time point of the sequence, unless `endpoint` is set to False. + In that case, the sequence consists of all but the last of ``num + 1`` + evenly spaced time points, so that `stop` is excluded. + Note that the step size changes when `endpoint` is False. + num : int, optional + Number of time points to use. + traceA : scalar, optional + Trace of `A`. If not given the trace is estimated for linear operators, + or calculated exactly for sparse matrices. It is used to precondition + `A`, thus an approximate trace is acceptable + endpoint : bool, optional + If True, `stop` is the last time point. Otherwise, it is not included. + balance : bool + Indicates whether or not to apply balancing. + status_only : bool + A flag that is set to True for some debugging and testing operations. + + Returns + ------- + F : ndarray + :math:`e^{t_k A} B` + status : int + An integer status for testing and debugging. + + Notes + ----- + This is algorithm (5.2) in Al-Mohy and Higham (2011). + + There seems to be a typo, where line 15 of the algorithm should be + moved to line 6.5 (between lines 6 and 7). + + """ + if balance: + raise NotImplementedError + if len(A.shape) != 2 or A.shape[0] != A.shape[1]: + raise ValueError('expected A to be like a square matrix') + if A.shape[1] != B.shape[0]: + raise ValueError(f'shapes of matrices A {A.shape} and B {B.shape}' + ' are incompatible') + ident = _ident_like(A) + is_linear_operator = isinstance(A, scipy.sparse.linalg.LinearOperator) + n = A.shape[0] + if len(B.shape) == 1: + n0 = 1 + elif len(B.shape) == 2: + n0 = B.shape[1] + else: + raise ValueError('expected B to be like a matrix or a vector') + u_d = 2**-53 + tol = u_d + if traceA is None: + if is_linear_operator: + warn("Trace of LinearOperator not available, it will be estimated." + " Provide `traceA` to ensure performance.", stacklevel=3) + # m3=5 is bit arbitrary choice, a more accurate trace (larger m3) might + # speed up exponential calculation, but trace estimation is also costly + # an educated guess would need to consider the number of time points + traceA = traceest(A, m3=5) if is_linear_operator else _trace(A) + mu = traceA / float(n) + + # Get the linspace samples, attempting to preserve the linspace defaults. + linspace_kwargs = {'retstep': True} + if num is not None: + linspace_kwargs['num'] = num + if endpoint is not None: + linspace_kwargs['endpoint'] = endpoint + samples, step = np.linspace(start, stop, **linspace_kwargs) + + # Convert the linspace output to the notation used by the publication. + nsamples = len(samples) + if nsamples < 2: + raise ValueError('at least two time points are required') + q = nsamples - 1 + h = step + t_0 = samples[0] + t_q = samples[q] + + # Define the output ndarray. + # Use an ndim=3 shape, such that the last two indices + # are the ones that may be involved in level 3 BLAS operations. + X_shape = (nsamples,) + B.shape + X = np.empty(X_shape, dtype=np.result_type(A.dtype, B.dtype, float)) + t = t_q - t_0 + A = A - mu * ident + A_1_norm = onenormest(A) if is_linear_operator else _exact_1_norm(A) + ell = 2 + norm_info = LazyOperatorNormInfo(t*A, A_1_norm=t*A_1_norm, ell=ell) + if t*A_1_norm == 0: + m_star, s = 0, 1 + else: + m_star, s = _fragment_3_1(norm_info, n0, tol, ell=ell) + + # Compute the expm action up to the initial time point. + action_t0 = _expm_multiply_simple_core(A, B, t_0, mu, m_star, s) + if scipy.sparse.issparse(action_t0): + action_t0 = action_t0.toarray() + elif is_pydata_spmatrix(action_t0): + action_t0 = action_t0.todense() + X[0] = action_t0 + + # Compute the expm action at the rest of the time points. + if q <= s: + if status_only: + return 0 + else: + return _expm_multiply_interval_core_0(A, X, + h, mu, q, norm_info, tol, ell,n0) + elif not (q % s): + if status_only: + return 1 + else: + return _expm_multiply_interval_core_1(A, X, + h, mu, m_star, s, q, tol) + elif (q % s): + if status_only: + return 2 + else: + return _expm_multiply_interval_core_2(A, X, + h, mu, m_star, s, q, tol) + else: + raise Exception('internal error') + + +def _expm_multiply_interval_core_0(A, X, h, mu, q, norm_info, tol, ell, n0): + """ + A helper function, for the case q <= s. + """ + + # Compute the new values of m_star and s which should be applied + # over intervals of size t/q + if norm_info.onenorm() == 0: + m_star, s = 0, 1 + else: + norm_info.set_scale(1./q) + m_star, s = _fragment_3_1(norm_info, n0, tol, ell=ell) + norm_info.set_scale(1) + + for k in range(q): + X[k+1] = _expm_multiply_simple_core(A, X[k], h, mu, m_star, s) + return X, 0 + + +def _expm_multiply_interval_core_1(A, X, h, mu, m_star, s, q, tol): + """ + A helper function, for the case q > s and q % s == 0. + """ + d = q // s + input_shape = X.shape[1:] + K_shape = (m_star + 1, ) + input_shape + K = np.empty(K_shape, dtype=X.dtype) + for i in range(s): + Z = X[i*d] + K[0] = Z + high_p = 0 + for k in range(1, d+1): + F = K[0] + c1 = _exact_inf_norm(F) + for p in range(1, m_star+1): + if p > high_p: + K[p] = h * A.dot(K[p-1]) / float(p) + coeff = float(pow(k, p)) + F = F + coeff * K[p] + inf_norm_K_p_1 = _exact_inf_norm(K[p]) + c2 = coeff * inf_norm_K_p_1 + if c1 + c2 <= tol * _exact_inf_norm(F): + break + c1 = c2 + X[k + i*d] = np.exp(k*h*mu) * F + return X, 1 + + +def _expm_multiply_interval_core_2(A, X, h, mu, m_star, s, q, tol): + """ + A helper function, for the case q > s and q % s > 0. + """ + d = q // s + j = q // d + r = q - d * j + input_shape = X.shape[1:] + K_shape = (m_star + 1, ) + input_shape + K = np.empty(K_shape, dtype=X.dtype) + for i in range(j + 1): + Z = X[i*d] + K[0] = Z + high_p = 0 + if i < j: + effective_d = d + else: + effective_d = r + for k in range(1, effective_d+1): + F = K[0] + c1 = _exact_inf_norm(F) + for p in range(1, m_star+1): + if p == high_p + 1: + K[p] = h * A.dot(K[p-1]) / float(p) + high_p = p + coeff = float(pow(k, p)) + F = F + coeff * K[p] + inf_norm_K_p_1 = _exact_inf_norm(K[p]) + c2 = coeff * inf_norm_K_p_1 + if c1 + c2 <= tol * _exact_inf_norm(F): + break + c1 = c2 + X[k + i*d] = np.exp(k*h*mu) * F + return X, 2 diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_funm_multiply_krylov.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_funm_multiply_krylov.py new file mode 100644 index 0000000000000000000000000000000000000000..eea90f384b08adcb09e5e6d0412b42cb360a7a0b --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_funm_multiply_krylov.py @@ -0,0 +1,340 @@ +""" + Restared Krylov method for evaluating f(A)b + + The original code was written in MATLAB by Stefan Guttel. + It was later adapted to C++ and Python by Nicolas Guidotti. + Both authors agrees to relicense the code under the BSD license. + + Copyright (C) 2025 Nicolas Guidotti and Stefan Guttel. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +""" + +import numpy as np +from scipy.linalg import norm +from ._isolve.iterative import _get_atol_rtol + +__all__ = ['funm_multiply_krylov'] + +def _funm_multiply_krylov_arnoldi(A, b, bnorm, V, H, m): + """ + The Arnoldi iteration for constructing the basis V and the projection H = V * A V + for the Krylov subspace Km(A, b) of order m. + + Parameters + ---------- + A : transposable linear operator + The operator whose matrix function is of interest. + b : ndarray + The vector b to multiply the f(A) with. + V : ndarray + The n x (m + 1) matrix whose columns determines the basis for + Krylov subspace Km(A, b). + H : ndarray + A (m + 1) x m upper Hessenberg matrix representing the projection of A + onto Km(A, b). + m : int + The order of the Krylov subspace. + + Returns + ------- + breakdown : bool + Indicate if the Arnoldi broke down or not + + iter : int + Returns the last valid iteration. + + """ + + dotprod = np.vdot if np.iscomplexobj(b) else np.dot + norm_tol = np.finfo(b.dtype.char).eps ** 2 + V[:, 0] = b / bnorm + + for k in range(0, m): + V[:, k + 1] = A.dot(V[:, k]) + + # Uses the modified Gram-Schmift process to orthogonalize V[:, k + 1] + # against the previous basis vectors + for i in range(0, k + 1): + H[i, k] = dotprod(V[:, i], V[:, k + 1]) + V[:, k + 1] = V[:, k + 1] - H[i, k] * V[:, i] + + H[k + 1, k] = norm(V[:, k + 1]) + if H[k + 1, k] < norm_tol: + return True, k + + V[:, k + 1] = V[:, k + 1] / H[k + 1, k] + + return False, m + +def _funm_multiply_krylov_lanczos(A, b, bnorm, V, H, m): + """ + The Lanczos iteration for constructing the basis V and the projection H = V * A V + for the Krylov subspace Km(A, b) of order m. A must be Hermitian. + + Parameters + ---------- + A : transposable linear operator + The operator whose matrix function is of interest. + b : ndarray + The vector b to multiply the f(A) with. + V : ndarray + The n x (m + 1) matrix whose columns determines the basis for + Krylov subspace Km(A, b). + H : ndarray + A (m + 1) x m upper Hessenberg matrix representing the projection of A + onto Km(A, b). + m : int + The order of the Krylov subspace. + + Returns + ------- + breakdown : bool + Indicate if the Arnoldi broke down or not + + iter : int + Returns the last valid iteration. + + """ + dotprod = np.vdot if np.iscomplexobj(b) else np.dot + norm_tol = np.finfo(b.dtype.char).eps ** 2 + V[:, 0] = b / bnorm + + for k in range(0, m): + if k > 0: + V[:, k + 1] = A.dot(V[:, k]) - H[k, k - 1] * V[:, k - 1] + else: + V[:, k + 1] = A.dot(V[:, k]) + + H[k, k] = dotprod(V[:, k + 1], V[:, k]) + V[:, k + 1] = V[:, k + 1] - H[k, k] * V[:, k] + + H[k + 1, k] = norm(V[:, k + 1]) + + if H[k + 1, k] < norm_tol: + return True, k + + V[:, k + 1] = V[:, k + 1] / H[k + 1, k] + if k < m - 1: + H[k, k + 1] = H[k + 1, k] + + return False, m + + +def funm_multiply_krylov(f, A, b, *, assume_a = "general", t = 1.0, atol = 0.0, + rtol = 1e-6, restart_every_m = None, max_restarts = 20): + """ + A restarted Krylov method for evaluating ``y = f(tA) b`` from [1]_ [2]_. + + Parameters + ---------- + f : callable + Callable object that computes the matrix function ``F = f(X)``. + A : {sparse array, ndarray, LinearOperator} + A real or complex N-by-N matrix. + Alternatively, `A` can be a linear operator which can + produce ``Ax`` using, e.g., ``scipy.sparse.linalg.LinearOperator``. + b : ndarray + A vector to multiply the ``f(tA)`` with. + assume_a : str, optional + Indicate the structure of ``A``. The algorithm will use this information + to select the appropriated code path. The available options are + 'hermitian'/'her' and 'general'/'gen'. If ommited, then it is assumed + that ``A`` has a 'general' structure. + t : float, optional + The value to scale the matrix ``A`` with. The default is ``t = 1.0`` + atol, rtol : float, optional + Parameters for the convergence test. For convergence, + ``norm(||y_k - y_k-1||) <= max(rtol*norm(b), atol)`` should be satisfied. + The default is ``atol=0.`` and ``rtol=1e-6``. + restart_every_m : int + If the iteration number reaches this value a restart is triggered. + Larger values increase iteration cost but may be necessary for convergence. + If omitted, ``min(20, n)`` is used. + max_restarts : int, optional + Maximum number of restart cycles. The algorithm will stop + after max_restarts cycles even if the specified tolerance has not been + achieved. The default is ``max_restarts=20`` + + Returns + ------- + y : ndarray + The result of ``f(tA) b``. + + Notes + ----- + The convergence of the Krylov method heavily depends on the spectrum + of ``A`` and the function ``f``. With restarting, there are only formal + proofs for functions of order 1 (e.g., ``exp``, ``sin``, ``cos``) and + Stieltjes functions [2]_ [3]_, while the general case remains an open problem. + + References + ---------- + .. [1] M. Afanasjew, M. Eiermann, O. G. Ernst, and S. Güttel, + "Implementation of a restarted Krylov subspace method for the + evaluation of matrix functions," Linear Algebra and its Applications, + vol. 429, no. 10, pp. 2293-2314, Nov. 2008, :doi:`10.1016/j.laa.2008.06.029`. + + .. [2] M. Eiermann and O. G. Ernst, "A Restarted Krylov Subspace Method + for the Evaluation of Matrix Functions," SIAM J. Numer. Anal., vol. 44, + no. 6, pp. 2481-2504, Jan. 2006, :doi:`10.1137/050633846`. + + .. [3] A. Frommer, S. Güttel, and M. Schweitzer, "Convergence of Restarted + Krylov Subspace Methods for Stieltjes Functions of Matrices," SIAM J. + Matrix Anal. Appl., vol. 35, no. 4, pp. 1602-1624, + Jan. 2014, :doi:`10.1137/140973463`. + + Examples + -------- + >>> import numpy as np + >>> from scipy.sparse import csr_array + >>> from scipy.sparse.linalg import funm_multiply_krylov + >>> from scipy.linalg import expm, solve + >>> A = csr_array([[3, 2, 0], [1, -1, 0], [0, 5, 1]], dtype=float) + >>> b = np.array([2, 4, -1], dtype=float) + >>> t = 0.1 + + Compute ``y = exp(tA) b``. + + >>> y = funm_multiply_krylov(expm, A, b, t = t) + >>> y + array([3.6164913 , 3.88421511 , 0.96073457]) + + >>> ref = expm(t * A.todense()) @ b + >>> err = y - ref + >>> err + array([4.44089210e-16 , 0.00000000e+00 , 2.22044605e-16]) + + Compute :math:`y = (A^3 - A) b`. + + >>> poly = lambda X : X @ X @ X - X + >>> y = funm_multiply_krylov(poly, A, b) + >>> y + array([132. , 24. , 70.]) + + >>> ref = poly(A.todense()) @ b + >>> err = y - ref + >>> err + array([ 0.00000000e+00 , 7.10542736e-15 , -2.84217094e-14]) + + Compute :math:`y = f(tA) b`, where :math:`f(X) = X^{-1}(e^{X} - I)`. This is + known as the "phi function" from the exponential integrator literature. + + >>> phim_1 = lambda X : solve(X, expm(X) - np.eye(X.shape[0])) + >>> y = funm_multiply_krylov(phim_1, A, b, t = t) + >>> y + array([ 2.76984306 , 3.92769192 , -0.03111392]) + + >>> ref = phim_1(t * A.todense()) @ b + >>> err = y - ref + >>> err + array([ 0.00000000e+00 , 8.88178420e-16 , -4.60742555e-15]) + """ + + if assume_a not in {'hermitian', 'general', 'her', 'gen'}: + raise ValueError(f'scipy.sparse.linalg.funm_multiply_krylov: {assume_a} ' + 'is not a recognized matrix structure') + is_hermitian = (assume_a == 'her') or (assume_a == 'hermitian') + + if len(b.shape) != 1: + raise ValueError("scipy.sparse.linalg.funm_multiply_krylov: " + "argument 'b' must be a 1D array.") + n = b.shape[0] + + if restart_every_m is None: + restart_every_m = min(20, n) + + restart_every_m = int(restart_every_m) + max_restarts = int(max_restarts) + + if restart_every_m <= 0: + raise ValueError("scipy.sparse.linalg.funm_multiply_krylov: " + "argument 'restart_every_m' must be positive.") + + if max_restarts <= 0: + raise ValueError("scipy.sparse.linalg.funm_multiply_krylov: " + "argument 'max_restarts' must be positive.") + + m = restart_every_m + max_restarts = min(max_restarts, int(n / m) + 1) + mmax = m * max_restarts + + bnorm = norm(b) + atol, _ = _get_atol_rtol("funm_multiply_krylov", bnorm, atol, rtol) + + if bnorm == 0: + y = np.array(b) + return y + + # Preallocate the maximum memory space. + # Using the column major order here since we work with + # each individual column separately. + internal_type = np.common_type(A, b) + V = np.zeros((n, m + 1), dtype = internal_type, order = 'F') + H = np.zeros((mmax + 1, mmax), dtype = internal_type, order = 'F') + + restart = 1 + + if is_hermitian: + breakdown, j = _funm_multiply_krylov_lanczos(A, b, bnorm, V, + H[:m + 1, :m], m) + else: + breakdown, j = _funm_multiply_krylov_arnoldi(A, b, bnorm, V, + H[:m + 1, :m], m) + + fH = f(t * H[:j, :j]) + y = bnorm * V[:, :j].dot(fH[:, 0]) + + if breakdown: + return y + + update_norm = norm(bnorm * fH[:, 0]) + + while restart < max_restarts and update_norm > atol: + begin = restart * m + end = (restart + 1) * m + + if is_hermitian: + breakdown, j = _funm_multiply_krylov_lanczos(A, V[:, m], 1, V, + H[begin:end + 1, begin:end], m) + else: + breakdown, j = _funm_multiply_krylov_arnoldi(A, V[:, m], 1, V, + H[begin:end + 1, begin:end], m) + + if breakdown: + end = begin + j + fH = f(t * H[:end, :end]) + y[:end] = y[:end] + bnorm * V[:, :m].dot(fH[begin:end, 0]) + return y + + fH = f(t * H[:end, :end]) + y = y + bnorm * V[:, :m].dot(fH[begin:end, 0]) + update_norm = norm(bnorm * fH[begin:end, 0]) + restart += 1 + + return y diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_interface.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_interface.py new file mode 100644 index 0000000000000000000000000000000000000000..f822e7316a3ac54a053a8ee9c68aa7304245d706 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_interface.py @@ -0,0 +1,1236 @@ +"""Abstract linear algebra library. + +This module defines a class hierarchy that implements a kind of "lazy" +matrix representation, called the ``LinearOperator``. It can be used to do +linear algebra with extremely large sparse or structured matrices, without +representing those explicitly in memory. Such matrices can be added, +multiplied, transposed, etc. + +As a motivating example, suppose you want have a matrix where almost all of +the elements have the value one. The standard sparse matrix representation +skips the storage of zeros, but not ones. By contrast, a LinearOperator is +able to represent such matrices efficiently. First, we need a compact way to +represent an all-ones matrix:: + + >>> import numpy as np + >>> from scipy.sparse.linalg._interface import LinearOperator + >>> class Ones(LinearOperator): + ... def __init__(self, shape): + ... super().__init__(dtype=None, shape=shape) + ... def _matvec(self, x): + ... return np.repeat(x.sum(), self.shape[0]) + +Instances of this class emulate ``np.ones(shape)``, but using a constant +amount of storage, independent of ``shape``. The ``_matvec`` method specifies +how this linear operator multiplies with (operates on) a vector. We can now +add this operator to a sparse matrix that stores only offsets from one:: + + >>> from scipy.sparse.linalg._interface import aslinearoperator + >>> from scipy.sparse import csr_array + >>> offsets = csr_array([[1, 0, 2], [0, -1, 0], [0, 0, 3]]) + >>> A = aslinearoperator(offsets) + Ones(offsets.shape) + >>> A.dot([1, 2, 3]) + array([13, 4, 15]) + +The result is the same as that given by its dense, explicitly-stored +counterpart:: + + >>> (np.ones(A.shape, A.dtype) + offsets.toarray()).dot([1, 2, 3]) + array([13, 4, 15]) + +Several algorithms in the ``scipy.sparse`` library are able to operate on +``LinearOperator`` instances. +""" + +import os +import types +import warnings + +import numpy as np + +from scipy import sparse +from scipy._external import array_api_extra as xpx +from scipy._lib._array_api import ( + SCIPY_ARRAY_API, + _asarray, + array_namespace, + is_array_api_obj, + is_pydata_sparse_array, + np_compat, + xp_capabilities, + xp_copy, + xp_isscalar, + xp_result_type, +) +from scipy.sparse import issparse +from scipy.sparse._sputils import asmatrix, is_pydata_spmatrix, isintlike, isshape + +__all__ = ["LinearOperator", "aslinearoperator"] + + +@xp_capabilities() +class LinearOperator: + """Common interface for performing matrix vector products. + + Many iterative methods (e.g. `cg`, `gmres`) do not need to know the + individual entries of a matrix to solve a linear system ``A@x = b``. + Such solvers only require the computation of matrix vector + products, ``A@v``, where ``v`` is a dense vector. This class serves as + an abstract interface between iterative solvers and matrix-like + objects. + + To construct a concrete `LinearOperator`, either pass appropriate + callables to the constructor of this class, or subclass it. + + A subclass must implement either one of the methods ``_matvec`` + and ``_matmat``, and the attributes/properties ``shape`` (pair of + integers, optionally with additional batch dimensions at the front) + and ``dtype`` (may be None). It may call the ``__init__`` + on this class to have these attributes validated. Implementing + ``_matvec`` automatically implements ``_matmat`` (using a naive + algorithm) and vice-versa. + + Optionally, a subclass may implement ``_rmatvec`` or ``_adjoint`` + to implement the Hermitian adjoint (conjugate transpose). As with + ``_matvec`` and ``_matmat``, implementing either ``_rmatvec`` or + ``_adjoint`` implements the other automatically. Implementing + ``_adjoint`` is preferable; ``_rmatvec`` is mostly there for + backwards compatibility. + + The defined operator may have additional "batch" dimensions + prepended to the core shape, to represent a batch of 2-D operators; + see :ref:`linalg_batch` for details. + + Parameters + ---------- + shape : tuple + Matrix dimensions ``(..., M, N)``, + where ``...`` represents any additional batch dimensions. + matvec : callable f(v) + Applies ``A`` to ``v``, where ``v`` is a dense vector + with shape ``(..., N)``. + rmatvec : callable f(v) + Applies ``A^H`` to ``v``, where ``A^H`` is the conjugate transpose of ``A``, + and ``v`` is a dense vector of shape ``(..., M)``. + matmat : callable f(V) + Returns ``A @ V``, where ``V`` is a dense matrix + with dimensions ``(..., N, K)``. + rmatmat : callable f(V) + Returns ``A^H @ V``, where ``A^H`` is the conjugate transpose of ``A``, + and where ``V`` is a dense matrix with dimensions ``(..., M, K)``. + dtype : dtype + Data type of the matrix or matrices. + xp : array_namespace, optional + A namespace compatible with the array API standard for use in array operations. + Default: ``numpy``. + + Attributes + ---------- + args : tuple + For linear operators describing products etc. of other linear + operators, the operands of the binary operation. + ndim : int + Number of dimensions (greater than 2 in the case of batch dimensions). + T : LinearOperator + Transpose. + H : LinearOperator + Hermitian adjoint. + + Methods + ------- + matvec + matmat + adjoint + transpose + rmatvec + rmatmat + dot + rdot + __mul__ + __matmul__ + __call__ + __add__ + __truediv__ + __rmul__ + __rmatmul__ + + See Also + -------- + aslinearoperator : Construct a `LinearOperator`. + + Notes + ----- + The user-defined `matvec` function must properly handle the case + where ``v`` has shape ``(..., N)``. + + It is highly recommended to explicitly specify the `dtype`, otherwise + it is determined automatically at the cost of a single matvec application + on ``int8`` zero vector using the promoted `dtype` of the output. + It is assumed that `matmat`, `rmatvec`, and `rmatmat` would result in + the same dtype of the output given an ``int8`` input as `matvec`. + + `LinearOperator` instances can also be multiplied, added with each + other, and raised to integral powers, all lazily: the result of these + operations + is always a new, composite `LinearOperator`, that defers linear + operations to the original operators and combines the results. + + More details regarding how to subclass a `LinearOperator` and several + examples of concrete `LinearOperator` instances can be found in the + external project `PyLops `_. + + Examples + -------- + >>> import numpy as np + >>> from scipy.sparse.linalg import LinearOperator + >>> def mv(v): + ... return np.array([2*v[0], 3*v[1]]) + ... + >>> A = LinearOperator((2,2), matvec=mv) + >>> A + <2x2 _CustomLinearOperator with dtype=int8> + >>> A.matvec(np.ones(2)) + array([ 2., 3.]) + >>> A @ np.ones(2) + array([ 2., 3.]) + + """ # numpydoc ignore=PR01,PR02 + + # Necessary for right matmul with numpy arrays. + __array_ufunc__ = None + + # generic type compatibility with scipy-stubs + __class_getitem__: classmethod = classmethod(types.GenericAlias) + + ndim: int + + def __new__(cls, *args, **kwargs): + if cls is LinearOperator: + # Operate as _CustomLinearOperator factory. + return super().__new__(_CustomLinearOperator) + else: + obj = super().__new__(cls) + + if ( + type(obj)._matvec == LinearOperator._matvec + and type(obj)._matmat == LinearOperator._matmat + ): + warnings.warn( + "LinearOperator subclass should implement" + " at least one of _matvec and _matmat.", + category=RuntimeWarning, + stacklevel=2, + ) + + return obj + + def __init__(self, dtype, shape, xp=None): + """Initialize this LinearOperator. + + To be called by subclasses. ``dtype`` may be None; ``shape`` should + be convertible to a length >=2 tuple. + """ + xp = np_compat if xp is None else xp + if dtype is not None: + dtype = xp.empty(0, dtype=dtype).dtype + + shape = tuple(shape) + if len(shape) < 2: + raise ValueError(f"invalid shape {shape!r} (must be at least 2-d)") + if not isshape(shape, check_nd=False): + raise ValueError(f"invalid shape {shape!r}") + + self.dtype = dtype + self.shape = shape + self.ndim = len(shape) + self._xp = xp + + def __getstate__(self): + state = self.__dict__.copy() + state["_xp"] = state["_xp"].empty(0) + return state + + def __setstate__(self, state): + self._xp = array_namespace(state.pop("_xp")) + self.__dict__.update(state) + + def _init_dtype(self): + """Determine the dtype by executing `matvec` on an `int8` test vector. + + In `np.promote_types` hierarchy, the type `int8` is the smallest, + so we call `matvec` on `int8` and use the promoted dtype of the output + to set the default `dtype` of the `LinearOperator`. + We assume that `matmat`, `rmatvec`, and `rmatmat` would result in + the same dtype of the output given an `int8` input as `matvec`. + + Called from subclasses at the end of the __init__ routine. + """ + if self.dtype is None: + xp = self._xp + v = xp.zeros(self.shape[-1], dtype=xp.int8) + try: + matvec_v = xp.asarray(self.matvec(v)) + except (OverflowError, TypeError, RuntimeError): + self.dtype = xpx.default_dtype(xp=xp, kind="integral") + else: + self.dtype = matvec_v.dtype + + def _matmat(self, X, /): + """Default matrix-matrix multiplication handler. + + If ``self`` is a linear operator of shape ``(..., M, N)``, + then this method will be called on a shape ``(..., N, K)`` array, + and should return a shape ``(..., M, K)`` array. + + Falls back to `_matvec`, so defining that will + define matrix multiplication too (though in a very suboptimal way). + """ + xp = self._xp + + # NOTE: we can't use `_matvec` directly (for the unbatched case) + # as we can't assume that user-defined `matvec` functions support batching. + # TODO: determine whether user-defined `_matvec` supports batching, + # and use it directly. + return xp.stack( + [self._matvec(X[..., :, i]) for i in range(X.shape[-1])], axis=-1 + ) + + def _matvec(self, x): + """Default matrix-vector multiplication handler. + + If ``self`` is a linear operator of shape ``(..., M, N)``, + then this method will be called on a shape + ``(..., N)`` array, + and should return a shape ``(..., M)`` array. + + Falls back to `_matmat`, so defining that + will define matrix-vector multiplication as well. + """ + xp = self._xp + return self._matmat(x[..., xp.newaxis])[..., 0] + + def _shared_matvec(self, x, adjoint: bool = False): + xp = self._xp + + x = _asarray(x, subok=True, xp=xp) + + *self_broadcast_dims, M, N = self.shape + inner_dim, outer_dim = (M, N) if adjoint else (N, M) + + # TODO: deprecate `np.matrix` support + if isinstance(x, np.matrix): + y = self._rmatvec(x) if adjoint else self._matvec(x) + if x.shape == (inner_dim, 1): + y = y.reshape(outer_dim, 1) + return asmatrix(y) + + x_broadcast_dims: tuple[int, ...] = () + row_vector: bool = False + if column_vector := x.shape == (inner_dim, 1): + func_name = "`rmatvec`" if adjoint else "`matvec`" + matmat_func_name = "`rmatmat`" if adjoint else "`matmat`" + msg = ( + f"Calling {func_name} on 'column vectors' of shape " + f"`({inner_dim}, 1)` was deprecated in SciPy 1.18.0 and will no " + f"longer be possible in SciPy 1.20.0. " + f"Please call {matmat_func_name} instead for identical behaviour." + ) + warnings.warn( + msg, FutureWarning, skip_file_prefixes=(os.path.dirname(__file__),) + ) + x = xp.reshape(x, (inner_dim,)) + elif x.ndim >= 1 and (row_vector := x.shape[-1] == inner_dim): + x_broadcast_dims = x.shape[:-1] + + if not (row_vector or column_vector): + msg = ( + f"Dimension mismatch: `x` must have a shape ending in " + f"`({inner_dim},)`, or shape `({inner_dim}, 1)`. " + f"Given shape: {x.shape}" + ) + raise ValueError(msg) + + y = self._rmatvec(x) if adjoint else self._matvec(x) + + broadcasted_dims = xpx.broadcast_shapes(self_broadcast_dims, x_broadcast_dims) + if row_vector: + y = xp.reshape(y, (*broadcasted_dims, outer_dim)) + elif column_vector: + y = xp.reshape(y, (*broadcasted_dims, outer_dim, 1)) + + return y + + def matvec(self, x): + """Matrix-vector multiplication. + + Applies ``A`` to `x`, where ``A`` is an ``M`` x ``N`` + linear operator (or batch of linear operators) + and `x` is a row vector (or batch of such vectors). + + Parameters + ---------- + x : {matrix, ndarray} + An array with shape ``(..., N)`` representing a row vector + (or batch of row vectors). + + .. versionadded:: 1.18.0 + A ``FutureWarning`` is emitted for column vector input of shape + ``(N, 1)``, for which an array with shape ``(M, 1)`` is returned. + `matmat` can be called instead for identical behaviour on such input. + + Returns + ------- + y : {matrix, ndarray} + An array with shape ``(..., M)``. + + Notes + ----- + This method wraps the user-specified ``matvec`` routine or overridden + ``_matvec`` method to ensure that `y` has the correct shape and type. + """ + return self._shared_matvec(x) + + def rmatvec(self, x): + """Adjoint matrix-vector multiplication. + + Applies ``A^H`` to `x`, where ``A`` is an + ``M`` x ``N`` linear operator (or batch of linear operators) + and `x` is a row vector (or batch of such vectors). + + Parameters + ---------- + x : {matrix, ndarray} + An array with shape ``(..., M)`` representing a row vector + (or batch of row vectors), + or an array with shape ``(M, 1)`` representing a column vector. + + .. versionadded:: 1.18.0 + A ``FutureWarning`` is now emitted for column vector input of shape + ``(M, 1)``, for which an array with shape ``(N, 1)`` is returned. + `rmatmat` can be called instead for identical behaviour on such input. + + Returns + ------- + y : {matrix, ndarray} + An array with shape ``(..., N)``. + + Notes + ----- + This method wraps the user-specified ``rmatvec`` routine or overridden + ``_rmatvec`` method to ensure that `y` has the correct shape and type. + """ + return self._shared_matvec(x, adjoint=True) + + def _rmatvec(self, x): + """Default implementation of `_rmatvec`. + Defers to `_rmatmat` or `adjoint`.""" + if type(self)._adjoint == LinearOperator._adjoint: + # _adjoint not overridden, prevent infinite recursion + if ( + hasattr(self, "_rmatmat") + and type(self)._rmatmat != LinearOperator._rmatmat + ): + xp = self._xp + # Try to use _rmatmat as a fallback + return self._rmatmat(x[..., xp.newaxis])[..., 0] + raise NotImplementedError + else: + return self.H._matvec(x) + + def _shared_matmat(self, X, adjoint: bool = False): + if not (issparse(X) or is_pydata_spmatrix(X)): + X = _asarray(X, subok=True, xp=self._xp) + + if X.ndim < 2: + raise ValueError(f"Expected at least 2-d ndarray or matrix, not {X.ndim}-d") + + if X.shape[-2] != (self.shape[-2] if adjoint else self.shape[-1]): + raise ValueError(f"Dimension mismatch: {self.shape}, {X.shape}") + + try: + Y = self._rmatmat(X) if adjoint else self._matmat(X) + except Exception as e: + if issparse(X) or is_pydata_spmatrix(X): + raise TypeError( + "Multipliying LinearOperator with a sparse matrix failed." + " Try wrapping the matrix with `aslinearoperator` first," + " or ensuring the operator's `matmat` function" + " supports sparse input." + ) from e + raise + + if isinstance(Y, np.matrix): + Y = asmatrix(Y) + + return Y + + def matmat(self, X): + """Matrix-matrix multiplication. + + Performs the operation ``A @ X`` where ``A`` is an ``M`` x ``N`` + linear operator (or batch of linear operators) + and `X` is a dense ``N`` x ``K`` matrix + (or batch of dense matrices). + + Parameters + ---------- + X : {matrix, ndarray} + An array with shape ``(..., N, K)`` representing the dense matrix + (or batch of dense matrices). + + Returns + ------- + Y : {matrix, ndarray} + An array with shape ``(..., M, K)``. + + Notes + ----- + This method wraps any user-specified ``matmat`` routine or overridden + ``_matmat`` method to ensure that `Y` has the correct type. + """ + return self._shared_matmat(X) + + def rmatmat(self, X): + """Adjoint matrix-matrix multiplication. + + Performs the operation ``A^H @ X`` where ``A`` is an ``M`` x ``N`` + linear operator (or batch of linear operators) + and `X` is a dense ``M`` x ``K`` matrix + (or batch of dense matrices). + The default implementation defers to the adjoint. + + Parameters + ---------- + X : {matrix, ndarray} + An array with shape ``(..., M, K)`` representing the dense matrix + (or batch of dense matrices). + + Returns + ------- + Y : {matrix, ndarray} + An array with shape ``(..., N, K)``. + + Notes + ----- + This method wraps any user-specified ``rmatmat`` routine or overridden + ``_rmatmat`` method to ensure that `Y` has the correct type. + + """ + return self._shared_matmat(X, adjoint=True) + + def _rmatmat(self, X, /): + """Default implementation of `_rmatmat`; defers to `rmatvec` or `adjoint`.""" + if type(self)._adjoint == LinearOperator._adjoint: + xp = self._xp + + # NOTE: we can't use `_rmatvec` directly as we can't assume that + # user-defined `rmatvec` functions support batching. + return xp.stack( + [self._rmatvec(X[..., :, i]) for i in range(X.shape[-1])], axis=-1 + ) + else: + return self.H._matmat(X) + + def __call__(self, x): + """Apply this linear operator. + + Equivalent to `__matmul__`. + """ + return self @ x + + def __mul__(self, x): + """Multiplication. + + Used by the ``*`` operator. Equivalent to `dot`. + """ + return self.dot(x) + + def __truediv__(self, other): + """Scalar Division. + + Returns a lazily scaled linear operator. + """ + if not xp_isscalar(other): + raise ValueError("Can only divide a linear operator by a scalar.") + + return _ScaledLinearOperator(self, 1.0 / other, xp=self._xp) + + def _check_matching_namespace(self, x): + xp_x = getattr(x, "_xp", None) + if xp_x is None: + xp_x = array_namespace(x, self._xp.empty(0), sparse_ok=True) + if xp_x != self._xp: + msg = ( + f"Mismatched array namespaces." + f"Namespace for self is {self._xp}, namespace for x is {xp_x}" + ) + raise TypeError(msg) + + def dot(self, x): + """Multi-purpose multiplication method. + + Parameters + ---------- + x : array_like or `LinearOperator` or scalar + Array-like input will be interpreted as a 1-D row vector or + 2-D matrix (or batch of matrices) + depending on its shape. See the Returns section for details. + + Returns + ------- + Ax : array or `LinearOperator` + - For `LinearOperator` input, operator composition is performed. + + - For scalar input, a lazily scaled operator is returned. + + - Otherwise, the input is expected to take the form of a dense + 1-D vector or 2-D matrix (or batch of matrices), + interpreted as follows + (where ``self`` is an ``M`` by ``N`` linear operator): + + - If `x` has shape ``(N,)`` + it is interpreted as a row vector + and `matvec` is called. + - If `x` has shape ``(..., N, K)`` for some + integer ``K``, it is interpreted as a matrix + (or batch of matrices if there are batch dimensions) + and `matmat` is called. + + See Also + -------- + __mul__ : Equivalent method used by the ``*`` operator. + __matmul__ : + Method used by the ``@`` operator which rejects scalar + input before calling this method. + + Notes + ----- + To perform matrix-vector multiplication on batches of vectors, + use `matvec`. + + For clarity, it is recommended to use the `matvec` or + `matmat` methods directly instead of this method + when interacting with dense vectors and matrices. + + """ + self._check_matching_namespace(x) + if isinstance(x, LinearOperator): + return _ProductLinearOperator(self, x, xp=self._xp) + elif xp_isscalar(x): + return _ScaledLinearOperator(self, x, xp=self._xp) + else: + if not issparse(x) and not is_pydata_spmatrix(x): + # scipy.sparse arrays shouldn't be converted to numpy arrays + # pydata_sparse arrays do not require conversion + # (and should avoid hitting https://github.com/pydata/sparse/pull/918) + x = self._xp.asarray(x) + + N = self.shape[-1] + + # treat 1-D input as a vector and >1-D input as a matrix, if the shape fits + vector = x.shape == (N,) + matrix = x.ndim >= 2 and x.shape[-2] == N + + if not (vector or matrix): + msg = ( + f"Dimension mismatch: array input `x` must have shape `({N},)` " + f"or a shape ending in `({N}, K)` for some integer `K`. " + f"Given shape: {x.shape}" + ) + raise ValueError(msg) + + if vector: + return self.matvec(x) + elif matrix: + return self.matmat(x) + + def __matmul__(self, other): + """Matrix Multiplication. + + Used by the ``@`` operator. + Rejects scalar input. + Otherwise, equivalent to `dot`. + """ + if xp_isscalar(other): + raise ValueError("Scalar operands are not allowed, use '*' instead") + return self.__mul__(other) + + def __rmatmul__(self, other): + """Matrix Multiplication from the right. + + Used by the ``@`` operator from the right. + Rejects scalar input. + Otherwise, equivalent to `rdot`. + """ + if xp_isscalar(other): + raise ValueError("Scalar operands are not allowed, use '*' instead") + return self.__rmul__(other) + + def __rmul__(self, x): + """Multiplication from the right. + + Used by the ``*`` operator from the right. Equivalent to `rdot`. + """ + return self.rdot(x) + + def rdot(self, x): + """Multi-purpose multiplication method from the right. + + .. note :: + + This method returns ``x A``. + To perform adjoint multiplication instead, use one of + `rmatvec` or `rmatmat`, or take the adjoint first, + like ``self.H.rdot(x)`` or ``x * self.H``. + + Parameters + ---------- + x : array_like or `LinearOperator` or scalar + Array-like input will be interpreted as a 1-D row vector or + 2-D matrix (or batch of matrices) + depending on its shape. See the Returns section for details. + + Returns + ------- + xA : array or `LinearOperator` + - For `LinearOperator` input, operator composition is performed. + + - For scalar input, a lazily scaled operator is returned. + + - Otherwise, the input is expected to take the form of a dense + 1-D vector or 2-D matrix (or batch of matrices), + interpreted as follows + (where ``self`` is an ``M`` by ``N`` linear operator): + + - If `x` has shape ``(M,)`` + it is interpreted as a row vector. + - If `x` has shape ``(..., K, M)`` for some + integer ``K``, it is interpreted as a matrix + (or batch of matrices if there are batch dimensions). + + See Also + -------- + dot : Multi-purpose multiplication method from the left. + __rmul__ : + Equivalent method, used by the ``*`` operator from the right. + __rmatmul__ : + Method used by the ``@`` operator from the right + which rejects scalar input before calling this method. + """ + self._check_matching_namespace(x) + if isinstance(x, LinearOperator): + return _ProductLinearOperator(x, self, xp=self._xp) + elif xp_isscalar(x): + return _ScaledLinearOperator(self, x, xp=self._xp) + else: + if not issparse(x) and not is_pydata_spmatrix(x): + # scipy.sparse arrays shouldn't be converted to numpy arrays + # pydata_sparse arrays do not require conversion + # (and should avoid hitting https://github.com/pydata/sparse/pull/918) + x = self._xp.asarray(x) + + M = self.shape[-2] + + # treat 1-D input as a vector and >1-D input as a matrix, if the shape fits + vector = x.shape == (M,) + matrix = x.ndim >= 2 and x.shape[-1] == M + + if not (vector or matrix): + msg = ( + f"Dimension mismatch: `x` must have shape `({M},)` " + f"or a shape ending in `(K, {M})` for some integer `K`. " + f"Given shape: {x.shape}." + ) + raise ValueError(msg) + + # We use transpose instead of rmatvec/rmatmat to avoid + # unnecessary complex conjugation if possible. + # scipy/scipy#24157 + def mT(x): + match x.ndim: + case 0 | 1: + return x + case 2: + return x.T + case _: + return self._xp.moveaxis(x, -2, -1) + if vector: + return self.T.matvec(mT(x)) + elif matrix: + return mT(self.T.matmat(mT(x))) + + def __pow__(self, p): + self._check_matching_namespace(p) + if xp_isscalar(p): + return _PowerLinearOperator(self, p, xp=self._xp) + else: + return NotImplemented + + def __add__(self, x): + """Linear operator addition. + + The input must be a `LinearOperator`. + A lazily summed linear operator is returned. + """ + self._check_matching_namespace(x) + if isinstance(x, LinearOperator): + return _SumLinearOperator(self, x, xp=self._xp) + else: + return NotImplemented + + def __neg__(self): + return _ScaledLinearOperator(self, -1, xp=self._xp) + + def __sub__(self, x): + return self.__add__(-x) + + def __repr__(self): + if self.dtype is None: + dt = "unspecified dtype" + else: + dt = "dtype=" + str(self.dtype) + + shape = "x".join(str(dim) for dim in self.shape) + return f"<{shape} {self.__class__.__name__} with {dt}>" + + def adjoint(self): + """Hermitian adjoint. + + Returns the Hermitian adjoint of this linear operator, + also known as the Hermitian + conjugate or Hermitian transpose. For a complex matrix, the + Hermitian adjoint is equal to the conjugate transpose. + + Returns + ------- + `LinearOperator` + Hermitian adjoint of self. + + See Also + -------- + :attr:`~scipy.sparse.linalg.LinearOperator.H` : Equivalent attribute. + """ + return self._adjoint() + + @property + def H(self): + """Hermitian adjoint. + + See Also + -------- + scipy.sparse.linalg.LinearOperator.adjoint : Equivalent method. + """ + return self.adjoint() + + def transpose(self): + """Transpose. + + Returns + ------- + `LinearOperator` + Transpose of the linear operator. + + See Also + -------- + :attr:`~scipy.sparse.linalg.LinearOperator.T` : Equivalent attribute. + """ + return self._transpose() + + @property + def T(self): + """Transpose. + + See Also + -------- + scipy.sparse.linalg.LinearOperator.transpose : Equivalent method. + """ + return self.transpose() + + def _adjoint(self): + """Default implementation of `_adjoint`. + Defers to adjoint functions, e.g. `_rmatvec` for `_matvec`.""" + return _AdjointLinearOperator(self, xp=self._xp) + + def _transpose(self): + """Default implementation of `_transpose`. + For `_matvec`, defers to `_rmatvec` + `np.conj`.""" + return _TransposedLinearOperator(self, xp=self._xp) + + +class _CustomLinearOperator(LinearOperator): + """Linear operator defined in terms of user-specified operations.""" + + def __init__( + self, + shape, + matvec, + rmatvec=None, + matmat=None, + dtype=None, + rmatmat=None, + xp=None, + ): + super().__init__(dtype, shape, xp) + + self.args = () + + self.__matvec_impl = matvec + self.__rmatvec_impl = rmatvec + self.__rmatmat_impl = rmatmat + self.__matmat_impl = matmat + + self._init_dtype() + + def _matmat(self, X): + if self.__matmat_impl is not None: + return self.__matmat_impl(X) + else: + return super()._matmat(X) + + def _matvec(self, x): + return self.__matvec_impl(x) + + def _rmatvec(self, x): + func = self.__rmatvec_impl + if func is None: + raise NotImplementedError("rmatvec is not defined") + return self.__rmatvec_impl(x) + + def _rmatmat(self, X): + if self.__rmatmat_impl is not None: + return self.__rmatmat_impl(X) + else: + return super()._rmatmat(X) + + def _adjoint(self): + return _CustomLinearOperator( + shape=(*self.shape[:-2], self.shape[-1], self.shape[-2]), + matvec=self.__rmatvec_impl, + rmatvec=self.__matvec_impl, + matmat=self.__rmatmat_impl, + rmatmat=self.__matmat_impl, + dtype=self.dtype, + xp=self._xp, + ) + + +class _AdjointLinearOperator(LinearOperator): + """Adjoint of arbitrary Linear Operator""" + + def __init__(self, A, xp=None): + shape = (*A.shape[:-2], A.shape[-1], A.shape[-2]) + super().__init__(A.dtype, shape, xp) + self.A = A + self.args = (A,) + + def _matvec(self, x): + return self.A._rmatvec(x) + + def _rmatvec(self, x): + return self.A._matvec(x) + + def _matmat(self, x): + return self.A._rmatmat(x) + + def _rmatmat(self, x): + return self.A._matmat(x) + + +class _TransposedLinearOperator(LinearOperator): + """Transposition of arbitrary Linear Operator""" + + def __init__(self, A, xp=None): + shape = (*A.shape[:-2], A.shape[-1], A.shape[-2]) + super().__init__(A.dtype, shape, xp) + self.A = A + self.args = (A,) + + def _matvec(self, x): + return self._xp.conj(self.A._rmatvec(self._xp.conj(x))) + + def _rmatvec(self, x): + return self._xp.conj(self.A._matvec(self._xp.conj(x))) + + def _matmat(self, x): + return self._xp.conj(self.A._rmatmat(self._xp.conj(x))) + + def _rmatmat(self, x): + return self._xp.conj(self.A._matmat(self._xp.conj(x))) + + +def _get_dtype(operators, dtypes=None, xp=None): + """Returns the promoted dtype from input dtypes and operators.""" + xp = np_compat if xp is None else xp + if dtypes is None: + dtypes = [] + for obj in operators: + if obj is not None and hasattr(obj, "dtype"): + dtypes.append(obj.dtype) + return xp_result_type(*dtypes, xp=xp) + + +class _SumLinearOperator(LinearOperator): + """Representing ``A + B``""" + + def __init__(self, A, B, xp=None): + if not isinstance(A, LinearOperator) or not isinstance(B, LinearOperator): + raise ValueError("both operands have to be a LinearOperator") + *A_broadcast_dims, A_M, A_N = A.shape + *B_broadcast_dims, B_M, B_N = B.shape + if (A_M, A_N) != (B_M, B_N): + raise ValueError(f"cannot add {A} and {B}: shape mismatch") + broadcasted_dims = xpx.broadcast_shapes(A_broadcast_dims, B_broadcast_dims) + self.args = (A, B) + super().__init__(_get_dtype([A, B], xp=xp), (*broadcasted_dims, A_M, A_N), xp) + + def _matvec(self, x): + return self.args[0].matvec(x) + self.args[1].matvec(x) + + def _rmatvec(self, x): + return self.args[0].rmatvec(x) + self.args[1].rmatvec(x) + + def _rmatmat(self, x): + return self.args[0].rmatmat(x) + self.args[1].rmatmat(x) + + def _matmat(self, x): + return self.args[0].matmat(x) + self.args[1].matmat(x) + + def _adjoint(self): + A, B = self.args + return A.H + B.H + + +class _ProductLinearOperator(LinearOperator): + """Representing ``A @ B``""" + + def __init__(self, A, B, xp=None): + if not isinstance(A, LinearOperator) or not isinstance(B, LinearOperator): + raise ValueError("both operands have to be a LinearOperator") + *A_broadcast_dims, A_M, A_N = A.shape + *B_broadcast_dims, B_M, B_N = B.shape + if A_N != B_M: + raise ValueError(f"cannot multiply {A} and {B}: shape mismatch") + broadcasted_dims = np.broadcast_shapes(A_broadcast_dims, B_broadcast_dims) + super().__init__(_get_dtype([A, B], xp=xp), (*broadcasted_dims, A_M, B_N), xp) + self.args = (A, B) + + def _matvec(self, x): + return self.args[0].matvec(self.args[1].matvec(x)) + + def _rmatvec(self, x): + return self.args[1].rmatvec(self.args[0].rmatvec(x)) + + def _rmatmat(self, x): + return self.args[1].rmatmat(self.args[0].rmatmat(x)) + + def _matmat(self, x): + return self.args[0].matmat(self.args[1].matmat(x)) + + def _adjoint(self): + A, B = self.args + return B.H @ A.H + + +class _ScaledLinearOperator(LinearOperator): + """Representing ``alpha * A``""" + + def __init__(self, A, alpha, xp=None): + if not isinstance(A, LinearOperator): + raise ValueError("LinearOperator expected as A") + if not np.isscalar(alpha): + raise ValueError("scalar expected as alpha") + if isinstance(A, _ScaledLinearOperator): + A, alpha_original = A.args + # Avoid in-place multiplication so that we don't accidentally mutate + # the original prefactor. + alpha = alpha * alpha_original + + dtype = _get_dtype([A], [alpha], xp=xp) + super().__init__(dtype, A.shape, xp) + self.args = (A, alpha) + # Note: args[1] is alpha (a scalar), so use `*` below, not `@` + + def _matvec(self, x): + return self.args[1] * self.args[0].matvec(x) + + def _rmatvec(self, x): + return self.args[1].conjugate() * self.args[0].rmatvec(x) + + def _rmatmat(self, x): + return self.args[1].conjugate() * self.args[0].rmatmat(x) + + def _matmat(self, x): + return self.args[1] * self.args[0].matmat(x) + + def _adjoint(self): + A, alpha = self.args + return A.H * alpha.conjugate() + + +class _PowerLinearOperator(LinearOperator): + """Representing ``A ** p``""" + + def __init__(self, A, p, xp=None): + if not isinstance(A, LinearOperator): + raise ValueError("LinearOperator expected as A") + if A.shape[-2] != A.shape[-1]: + msg = f"square core-dimensions of LinearOperator expected, got {A!r}" + raise ValueError(msg) + if not isintlike(p) or p < 0: + raise ValueError("non-negative integer expected as p") + + super().__init__(_get_dtype([A], xp=xp), A.shape, xp) + self.args = (A, p) + + def _power(self, fun, x): + res = xp_copy(x) + for i in range(self.args[1]): + res = fun(res) + return res + + def _matvec(self, x): + return self._power(self.args[0].matvec, x) + + def _rmatvec(self, x): + return self._power(self.args[0].rmatvec, x) + + def _rmatmat(self, x): + return self._power(self.args[0].rmatmat, x) + + def _matmat(self, x): + return self._power(self.args[0].matmat, x) + + def _adjoint(self): + A, p = self.args + return A.H**p + + +class MatrixLinearOperator(LinearOperator): + """Operator defined by a matrix `A` which implements ``@``.""" + + def __init__(self, A, xp=None): + super().__init__(A.dtype, A.shape, xp) + self.A = A + self.__adj = None + self.args = (A,) + + def _matmat(self, X): + return self.A @ X + + def _adjoint(self): + if self.__adj is None: + self.__adj = _AdjointMatrixOperator(self.A, xp=self._xp) + return self.__adj + + +class _AdjointMatrixOperator(MatrixLinearOperator): + """Representing ``A.H``, for `MatrixLinearOperator` `A`.""" + + def __init__(self, A, xp=None): + xp = np_compat if xp is None else xp + if A.ndim > 2: + if issparse(A): + A_T = sparse.swapaxes(A, -1, -2) + else: + A_T = A.mT + else: + A_T = A.T + super().__init__(xp.conj(A_T), xp=xp) + self.args = (A,) # override to ensure `self.args[0]` is accurate + + def _adjoint(self): + return MatrixLinearOperator(self.args[0], xp=self._xp) + + +class IdentityOperator(LinearOperator): + def __init__(self, shape, dtype=None, xp=None): + super().__init__(dtype, shape, xp) + + def _matvec(self, x): + return x + + def _rmatvec(self, x): + return x + + def _rmatmat(self, x): + return x + + def _matmat(self, x): + return x + + def _adjoint(self): + return self + + +@xp_capabilities() +def aslinearoperator(A): + """Return `A` as a `LinearOperator`. + + See the `LinearOperator` documentation for additional information. + + Parameters + ---------- + A : object + Object to convert to a `LinearOperator`. May be any one of the following types: + + - `numpy.ndarray` + - `numpy.matrix` + - `scipy.sparse` array + (e.g. `~scipy.sparse.csr_array`, `~scipy.sparse.lil_array`, etc.) + - `LinearOperator` + - An object with ``.shape`` and ``.matvec`` attributes + + Returns + ------- + B : LinearOperator + A `LinearOperator` corresponding with `A` + + Notes + ----- + If `A` has no ``.dtype`` attribute, the data type is determined by calling + :func:`LinearOperator.matvec()` - set the ``.dtype`` attribute to prevent this + call upon the linear operator creation. + + Examples + -------- + >>> import numpy as np + >>> from scipy.sparse.linalg import aslinearoperator + >>> M = np.array([[1,2,3],[4,5,6]], dtype=np.int32) + >>> aslinearoperator(M) + <2x3 MatrixLinearOperator with dtype=int32> + """ + if isinstance(A, LinearOperator): + return A + + # https://github.com/data-apis/array-api-compat/issues/388 + if is_array_api_obj(A) and not isinstance(A, np.matrix): + xp = array_namespace(A) + if is_pydata_sparse_array(A) and not SCIPY_ARRAY_API: + pass # skip `atleast_nd`, since `xp=np` does not work with `sparse` + else: + A = xpx.atleast_nd(A, ndim=2, xp=xp) + return MatrixLinearOperator(A, xp=xp) + + if issparse(A): + return MatrixLinearOperator(A) + + if isinstance(A, np.matrix): + A = np.atleast_2d(np.asarray(A)) + return MatrixLinearOperator(A) + + if hasattr(A, "shape") and hasattr(A, "matvec"): + rmatvec = None + rmatmat = None + dtype = None + + if hasattr(A, "rmatvec"): + rmatvec = A.rmatvec + if hasattr(A, "rmatmat"): + rmatmat = A.rmatmat + if hasattr(A, "dtype"): + dtype = A.dtype + return LinearOperator( + A.shape, A.matvec, rmatvec=rmatvec, rmatmat=rmatmat, dtype=dtype + ) + + raise TypeError("type not understood") diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/__init__.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..3b57274542928e79c234bb6955849a90be21990e --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/__init__.py @@ -0,0 +1,20 @@ +"Iterative Solvers for Sparse Linear Systems" + +#from info import __doc__ +from .iterative import * +from .minres import minres +from .lgmres import lgmres +from .lsqr import lsqr +from .lsmr import lsmr +from ._gcrotmk import gcrotmk +from .tfqmr import tfqmr + +__all__ = [ + 'bicg', 'bicgstab', 'cg', 'cgs', 'gcrotmk', 'gmres', + 'lgmres', 'lsmr', 'lsqr', + 'minres', 'qmr', 'tfqmr' +] + +from scipy._lib._testutils import PytestTester +test = PytestTester(__name__) +del PytestTester diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d2b2709140905b24d58644c68dde3acb11452460 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/__pycache__/__init__.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/__pycache__/_gcrotmk.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/__pycache__/_gcrotmk.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1202c887827c65c000ab404907203a73feb3214e Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/__pycache__/_gcrotmk.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/__pycache__/iterative.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/__pycache__/iterative.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..893960148a9150352dc08ebba28859afe2eab6b5 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/__pycache__/iterative.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/__pycache__/lgmres.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/__pycache__/lgmres.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..46a7d2c49069817f11b8960c50208bfd18bd4ea5 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/__pycache__/lgmres.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/__pycache__/lsmr.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/__pycache__/lsmr.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..541ff8e823928eeb974aad720fbe3adf5c66ad55 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/__pycache__/lsmr.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/__pycache__/lsqr.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/__pycache__/lsqr.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..09d3d92881968ebb6b4131c53b5d8b578e43ad18 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/__pycache__/lsqr.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/__pycache__/minres.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/__pycache__/minres.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a181087869c78d1bee4d86ea929294e5d6863873 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/__pycache__/minres.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/__pycache__/tfqmr.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/__pycache__/tfqmr.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..139de69880f91ed0ab298c7a5651bed8245f0ca5 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/__pycache__/tfqmr.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/__pycache__/utils.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/__pycache__/utils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a4e893b729631d2845da1c91853257ee4535d430 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/__pycache__/utils.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/_gcrotmk.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/_gcrotmk.py new file mode 100644 index 0000000000000000000000000000000000000000..2d70b4225750c91ec9ce862b682a4f4edfb77e24 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/_gcrotmk.py @@ -0,0 +1,506 @@ +# Copyright (C) 2015, Pauli Virtanen +# Distributed under the same license as SciPy. + +import numpy as np +from numpy.linalg import LinAlgError +# pyrefly:ignore[missing-module-attribute] +from scipy.linalg import get_blas_funcs, qr, solve, svd, qr_insert, lstsq +from .iterative import _get_atol_rtol +from scipy.sparse.linalg._isolve.utils import make_system +from scipy._lib._array_api import xp_capabilities + + +__all__ = ['gcrotmk'] + + +def _fgmres(matvec, v0, m, atol, lpsolve=None, rpsolve=None, cs=(), outer_v=(), + prepend_outer_v=False): + """ + FGMRES Arnoldi process, with optional projection or augmentation + + Parameters + ---------- + matvec : callable + Operation A*x + v0 : ndarray + Initial vector, normalized to nrm2(v0) == 1 + m : int + Number of GMRES rounds + atol : float + Absolute tolerance for early exit + lpsolve : callable + Left preconditioner L + rpsolve : callable + Right preconditioner R + cs : list of (ndarray, ndarray) + Columns of matrices C and U in GCROT + outer_v : list of ndarrays + Augmentation vectors in LGMRES + prepend_outer_v : bool, optional + Whether augmentation vectors come before or after + Krylov iterates + + Raises + ------ + LinAlgError + If nans encountered + + Returns + ------- + Q, R : ndarray + QR decomposition of the upper Hessenberg H=QR + B : ndarray + Projections corresponding to matrix C + vs : list of ndarray + Columns of matrix V + zs : list of ndarray + Columns of matrix Z + y : ndarray + Solution to ||H y - e_1||_2 = min! + res : float + The final (preconditioned) residual norm + + """ + + if lpsolve is None: + def lpsolve(x): + return x + if rpsolve is None: + def rpsolve(x): + return x + + axpy, dot, scal, nrm2 = get_blas_funcs(['axpy', 'dot', 'scal', 'nrm2'], (v0,)) + + vs = [v0] + zs = [] + y = None + res = np.nan + + m = m + len(outer_v) + + # Orthogonal projection coefficients + B = np.zeros((len(cs), m), dtype=v0.dtype) + + # H is stored in QR factorized form + Q = np.ones((1, 1), dtype=v0.dtype) + R = np.zeros((1, 0), dtype=v0.dtype) + + eps = np.finfo(v0.dtype).eps + + breakdown = False + + # FGMRES Arnoldi process + for j in range(m): + # L A Z = C B + V H + + if prepend_outer_v and j < len(outer_v): + z, w = outer_v[j] + elif prepend_outer_v and j == len(outer_v): + z = rpsolve(v0) + w = None + elif not prepend_outer_v and j >= m - len(outer_v): + z, w = outer_v[j - (m - len(outer_v))] + else: + z = rpsolve(vs[-1]) + w = None + + if w is None: + w = lpsolve(matvec(z)) + else: + # w is clobbered below + w = w.copy() + + w_norm = nrm2(w) + + # GCROT projection: L A -> (1 - C C^H) L A + # i.e. orthogonalize against C + for i, c in enumerate(cs): + alpha = dot(c, w) + B[i,j] = alpha + w = axpy(c, w, c.shape[0], -alpha) # w -= alpha*c + + # Orthogonalize against V + hcur = np.zeros(j+2, dtype=Q.dtype) + for i, v in enumerate(vs): + alpha = dot(v, w) + hcur[i] = alpha + w = axpy(v, w, v.shape[0], -alpha) # w -= alpha*v + hcur[i+1] = nrm2(w) + + with np.errstate(over='ignore', divide='ignore'): + # Careful with denormals + alpha = 1/hcur[-1] + + if np.isfinite(alpha): + w = scal(alpha, w) + + if not (hcur[-1] > eps * w_norm): + # w essentially in the span of previous vectors, + # or we have nans. Bail out after updating the QR + # solution. + breakdown = True + + vs.append(w) + zs.append(z) + + # Arnoldi LSQ problem + + # Add new column to H=Q@R, padding other columns with zeros + Q2 = np.zeros((j+2, j+2), dtype=Q.dtype, order='F') + Q2[:j+1,:j+1] = Q + Q2[j+1,j+1] = 1 + + R2 = np.zeros((j+2, j), dtype=R.dtype, order='F') + R2[:j+1,:] = R + + Q, R = qr_insert(Q2, R2, hcur, j, which='col', + overwrite_qru=True, check_finite=False) + + # Transformed least squares problem + # || Q R y - inner_res_0 * e_1 ||_2 = min! + # Since R = [R'; 0], solution is y = inner_res_0 (R')^{-1} (Q^H)[:j,0] + + # Residual is immediately known + res = abs(Q[0,-1]) + + # Check for termination + if res < atol or breakdown: + break + + if not np.isfinite(R[j,j]): + # nans encountered, bail out + raise LinAlgError() + + # -- Get the LSQ problem solution + + # The problem is triangular, but the condition number may be + # bad (or in case of breakdown the last diagonal entry may be + # zero), so use lstsq instead of trtrs. + y, _, _, _, = lstsq(R[:j+1,:j+1], Q[0,:j+1].conj()) + + B = B[:,:j+1] + + return Q, R, B, vs, zs, y, res + + +@xp_capabilities(np_only=True) +def gcrotmk(A, b, x0=None, *, rtol=1e-5, atol=0., maxiter=1000, M=None, callback=None, + m=20, k=None, CU=None, discard_C=False, truncate='oldest'): + """ + Solve ``Ax = b`` with the flexible GCROT(m,k) algorithm. + + Parameters + ---------- + A : {sparse array, ndarray, LinearOperator} + The real or complex N-by-N matrix of the linear system. + Alternatively, `A` can be a linear operator which can + produce ``Ax`` using, e.g., + `LinearOperator`. + b : ndarray + Right hand side of the linear system. Has shape (N,) or (N,1). + x0 : ndarray + Starting guess for the solution. + rtol, atol : float, optional + Parameters for the convergence test. For convergence, + ``norm(b - A @ x) <= max(rtol*norm(b), atol)`` should be satisfied. + The default is ``rtol=1e-5`` and ``atol=0.0``. + maxiter : int, optional + Maximum number of iterations. Iteration will stop after maxiter + steps even if the specified tolerance has not been achieved. The + default is ``1000``. + M : {sparse array, ndarray, LinearOperator}, optional + Preconditioner for `A`. The preconditioner should approximate the + inverse of `A`. gcrotmk is a 'flexible' algorithm and the preconditioner + can vary from iteration to iteration. Effective preconditioning + dramatically improves the rate of convergence, which implies that + fewer iterations are needed to reach a given error tolerance. + callback : function, optional + User-supplied function to call after each iteration. It is called + as ``callback(xk)``, where ``xk`` is the current solution vector. + m : int, optional + Number of inner FGMRES iterations per each outer iteration. + Default: 20 + k : int, optional + Number of vectors to carry between inner FGMRES iterations. + According to [2]_, good values are around `m`. + Default: `m` + CU : list of tuples, optional + List of tuples ``(c, u)`` which contain the columns of the matrices + C and U in the GCROT(m,k) algorithm. For details, see [2]_. + The list given and vectors contained in it are modified in-place. + If not given, start from empty matrices. The ``c`` elements in the + tuples can be ``None``, in which case the vectors are recomputed + via ``c = A u`` on start and orthogonalized as described in [3]_. + discard_C : bool, optional + Discard the C-vectors at the end. Useful if recycling Krylov subspaces + for different linear systems. + truncate : {'oldest', 'smallest'}, optional + Truncation scheme to use. Drop: oldest vectors, or vectors with + smallest singular values using the scheme discussed in [1,2]. + See [2]_ for detailed comparison. + Default: 'oldest' + + Returns + ------- + x : ndarray + The solution found. + info : int + Provides convergence information: + + * 0 : successful exit + * >0 : convergence to tolerance not achieved, number of iterations + + References + ---------- + .. [1] E. de Sturler, ''Truncation strategies for optimal Krylov subspace + methods'', SIAM J. Numer. Anal. 36, 864 (1999). + .. [2] J.E. Hicken and D.W. Zingg, ''A simplified and flexible variant + of GCROT for solving nonsymmetric linear systems'', + SIAM J. Sci. Comput. 32, 172 (2010). + .. [3] M.L. Parks, E. de Sturler, G. Mackey, D.D. Johnson, S. Maiti, + ''Recycling Krylov subspaces for sequences of linear systems'', + SIAM J. Sci. Comput. 28, 1651 (2006). + + Examples + -------- + >>> import numpy as np + >>> from scipy.sparse import csc_array + >>> from scipy.sparse.linalg import gcrotmk + >>> R = np.random.randn(5, 5) + >>> A = csc_array(R) + >>> b = np.random.randn(5) + >>> x, exit_code = gcrotmk(A, b, atol=1e-5) + >>> print(exit_code) + 0 + >>> np.allclose(A.dot(x), b) + True + + """ + A,M,x,b = make_system(A,M,x0,b) + + if not np.isfinite(b).all(): + raise ValueError("RHS must contain only finite numbers") + + if truncate not in ('oldest', 'smallest'): + raise ValueError(f"Invalid value for 'truncate': {truncate!r}") + + matvec = A.matvec + psolve = M.matvec + + if CU is None: + CU = [] + + if k is None: + k = m + + axpy, dot, scal = None, None, None + + if x0 is None: + r = b.copy() + else: + r = b - matvec(x) + + axpy, dot, scal, nrm2 = get_blas_funcs(['axpy', 'dot', 'scal', 'nrm2'], (x, r)) + + b_norm = nrm2(b) + + # we call this to get the right atol/rtol and raise errors as necessary + atol, rtol = _get_atol_rtol('gcrotmk', b_norm, atol, rtol) + + if b_norm == 0: + x = b + return (x, 0) + + if discard_C: + CU[:] = [(None, u) for c, u in CU] + + # Reorthogonalize old vectors + if CU: + # Sort already existing vectors to the front + CU.sort(key=lambda cu: cu[0] is not None) + + # Fill-in missing ones + C = np.empty((A.shape[0], len(CU)), dtype=r.dtype, order='F') + us = [] + j = 0 + while CU: + # More memory-efficient: throw away old vectors as we go + c, u = CU.pop(0) + if c is None: + c = matvec(u) + C[:,j] = c + j += 1 + us.append(u) + + # Orthogonalize + Q, R, P = qr(C, overwrite_a=True, mode='economic', pivoting=True) + del C + + # C := Q + cs = list(Q.T) + + # U := U P R^-1, back-substitution + new_us = [] + for j in range(len(cs)): + u = us[P[j]] + for i in range(j): + u = axpy(us[P[i]], u, u.shape[0], -R[i,j]) + if abs(R[j,j]) < 1e-12 * abs(R[0,0]): + # discard rest of the vectors + break + u = scal(1.0/R[j,j], u) + new_us.append(u) + + # Form the new CU lists + CU[:] = list(zip(cs, new_us))[::-1] + + if CU: + axpy, dot = get_blas_funcs(['axpy', 'dot'], (r,)) + + # Solve first the projection operation with respect to the CU + # vectors. This corresponds to modifying the initial guess to + # be + # + # x' = x + U y + # y = argmin_y || b - A (x + U y) ||^2 + # + # The solution is y = C^H (b - A x) + for c, u in CU: + yc = dot(c, r) + x = axpy(u, x, x.shape[0], yc) + r = axpy(c, r, r.shape[0], -yc) + + # GCROT main iteration + for j_outer in range(maxiter): + # -- callback + if callback is not None: + callback(x) + + beta = nrm2(r) + + # -- check stopping condition + beta_tol = max(atol, rtol * b_norm) + + if beta <= beta_tol and (j_outer > 0 or CU): + # recompute residual to avoid rounding error + r = b - matvec(x) + beta = nrm2(r) + + if beta <= beta_tol: + j_outer = -1 + break + + ml = m + max(k - len(CU), 0) + + cs = [c for c, u in CU] + + try: + Q, R, B, vs, zs, y, pres = _fgmres(matvec, + r/beta, + ml, + rpsolve=psolve, + atol=max(atol, rtol*b_norm)/beta, + cs=cs) + y *= beta + except LinAlgError: + # Floating point over/underflow, non-finite result from + # matmul etc. -- report failure. + break + + # + # At this point, + # + # [A U, A Z] = [C, V] G; G = [ I B ] + # [ 0 H ] + # + # where [C, V] has orthonormal columns, and r = beta v_0. Moreover, + # + # || b - A (x + Z y + U q) ||_2 = || r - C B y - V H y - C q ||_2 = min! + # + # from which y = argmin_y || beta e_1 - H y ||_2, and q = -B y + # + + # + # GCROT(m,k) update + # + + # Define new outer vectors + + # ux := (Z - U B) y + ux = zs[0]*y[0] + for z, yc in zip(zs[1:], y[1:]): + ux = axpy(z, ux, ux.shape[0], yc) # ux += z*yc + by = B.dot(y) + for cu, byc in zip(CU, by): + c, u = cu + ux = axpy(u, ux, ux.shape[0], -byc) # ux -= u*byc + + # cx := V H y + with np.errstate(invalid="ignore"): + hy = Q.dot(R.dot(y)) + cx = vs[0] * hy[0] + for v, hyc in zip(vs[1:], hy[1:]): + cx = axpy(v, cx, cx.shape[0], hyc) # cx += v*hyc + + # Normalize cx, maintaining cx = A ux + # This new cx is orthogonal to the previous C, by construction + try: + alpha = 1/nrm2(cx) + if not np.isfinite(alpha): + raise FloatingPointError() + except (FloatingPointError, ZeroDivisionError): + # Cannot update, so skip it + continue + + cx = scal(alpha, cx) + ux = scal(alpha, ux) + + # Update residual and solution + gamma = dot(cx, r) + r = axpy(cx, r, r.shape[0], -gamma) # r -= gamma*cx + x = axpy(ux, x, x.shape[0], gamma) # x += gamma*ux + + # Truncate CU + if truncate == 'oldest': + while len(CU) >= k and CU: + del CU[0] + elif truncate == 'smallest': + if len(CU) >= k and CU: + # cf. [1,2] + D = solve(R[:-1,:].T, B.T).T + W, sigma, V = svd(D) + + # C := C W[:,:k-1], U := U W[:,:k-1] + new_CU = [] + for j, w in enumerate(W[:,:k-1].T): + c, u = CU[0] + c = c * w[0] + u = u * w[0] + for cup, wp in zip(CU[1:], w[1:]): + cp, up = cup + c = axpy(cp, c, c.shape[0], wp) + u = axpy(up, u, u.shape[0], wp) + + # Reorthogonalize at the same time; not necessary + # in exact arithmetic, but floating point error + # tends to accumulate here + for cp, up in new_CU: + alpha = dot(cp, c) + c = axpy(cp, c, c.shape[0], -alpha) + u = axpy(up, u, u.shape[0], -alpha) + alpha = nrm2(c) + c = scal(1.0/alpha, c) + u = scal(1.0/alpha, u) + + new_CU.append((c, u)) + CU[:] = new_CU + + # Add new vector to CU + CU.append((cx, ux)) + + # Include the solution vector to the span + CU.append((None, x.copy())) + if discard_C: + CU[:] = [(None, uz) for cz, uz in CU] + + return x, j_outer + 1 diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/iterative.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/iterative.py new file mode 100644 index 0000000000000000000000000000000000000000..df3fbf52ade58b530d048edb0d854d51d4dca95c --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/iterative.py @@ -0,0 +1,1058 @@ +import warnings +import numpy as np +from scipy.sparse.linalg._interface import LinearOperator +from .utils import make_system +from scipy.linalg import get_lapack_funcs +from scipy._lib._array_api import xp_capabilities + +__all__ = ['bicg', 'bicgstab', 'cg', 'cgs', 'gmres', 'qmr'] + + +def _get_atol_rtol(name, b_norm, atol=0., rtol=1e-5): + """ + A helper function to handle tolerance normalization + """ + if atol == 'legacy' or atol is None or atol < 0: + msg = (f"'scipy.sparse.linalg.{name}' called with invalid `atol`={atol}; " + "if set, `atol` must be a real, non-negative number.") + raise ValueError(msg) + + atol = max(float(atol), float(rtol) * float(b_norm)) + + return atol, rtol + + +@xp_capabilities(np_only=True) +def bicg(A, b, x0=None, *, rtol=1e-5, atol=0., maxiter=None, M=None, callback=None): + """ + Solve ``Ax = b`` with the BIConjugate Gradient method. + + Parameters + ---------- + A : {sparse array, ndarray, LinearOperator} + The real or complex N-by-N matrix of the linear system. + Alternatively, `A` can be a linear operator which can + produce ``Ax`` and ``A^T x`` using, e.g., + ``scipy.sparse.linalg.LinearOperator``. + b : ndarray + Right hand side of the linear system. Has shape (N,) or (N,1). + x0 : ndarray + Starting guess for the solution. + rtol, atol : float, optional + Parameters for the convergence test. For convergence, + ``norm(b - A @ x) <= max(rtol*norm(b), atol)`` should be satisfied. + The default is ``atol=0.`` and ``rtol=1e-5``. + maxiter : int + Maximum number of iterations. Iteration will stop after maxiter + steps even if the specified tolerance has not been achieved. + M : {sparse array, ndarray, LinearOperator} + Preconditioner for `A`. It should approximate the + inverse of `A` (see Notes). Effective preconditioning dramatically improves the + rate of convergence, which implies that fewer iterations are needed + to reach a given error tolerance. + callback : function + User-supplied function to call after each iteration. It is called + as ``callback(xk)``, where ``xk`` is the current solution vector. + + Returns + ------- + x : ndarray + The converged solution. + info : int + Provides convergence information: + 0 : successful exit + >0 : convergence to tolerance not achieved, number of iterations + <0 : parameter breakdown + + Notes + ----- + The preconditioner `M` should be a matrix such that ``M @ A`` has a smaller + condition number than `A`, see [1]_ . + + References + ---------- + .. [1] "Preconditioner", Wikipedia, + https://en.wikipedia.org/wiki/Preconditioner + .. [2] "Biconjugate gradient method", Wikipedia, + https://en.wikipedia.org/wiki/Biconjugate_gradient_method + + Examples + -------- + >>> import numpy as np + >>> from scipy.sparse import csc_array + >>> from scipy.sparse.linalg import bicg + >>> A = csc_array([[3, 2, 0], [1, -1, 0], [0, 5, 1.]]) + >>> b = np.array([2., 4., -1.]) + >>> x, exitCode = bicg(A, b, atol=1e-5) + >>> print(exitCode) # 0 indicates successful convergence + 0 + >>> np.allclose(A.dot(x), b) + True + """ + A, M, x, b = make_system(A, M, x0, b) + bnrm2 = np.linalg.norm(b) + + atol, _ = _get_atol_rtol('bicg', bnrm2, atol, rtol) + + if bnrm2 == 0: + return b, 0 + + n = len(b) + dotprod = np.vdot if np.iscomplexobj(x) else np.dot + + if maxiter is None: + maxiter = n*10 + + matvec, rmatvec = A.matvec, A.rmatvec + psolve, rpsolve = M.matvec, M.rmatvec + + rhotol = np.finfo(x.dtype.char).eps**2 + + # Dummy values to initialize vars, silence linter warnings + rho_prev, p, ptilde = None, None, None + + r = b - matvec(x) if x.any() else b.copy() + rtilde = r.copy() + + for iteration in range(maxiter): + if np.linalg.norm(r) < atol: # Are we done? + return x, 0 + + z = psolve(r) + ztilde = rpsolve(rtilde) + # order matters in this dot product + rho_cur = dotprod(rtilde, z) + + if np.abs(rho_cur) < rhotol: # Breakdown case + return x, -10 + + if iteration > 0: + beta = rho_cur / rho_prev + p *= beta + p += z + ptilde *= beta.conj() + ptilde += ztilde + else: # First spin + p = z.copy() + ptilde = ztilde.copy() + + q = matvec(p) + qtilde = rmatvec(ptilde) + rv = dotprod(ptilde, q) + + if rv == 0: + return x, -11 + + alpha = rho_cur / rv + x += alpha*p + r -= alpha*q + rtilde -= alpha.conj()*qtilde + rho_prev = rho_cur + + if callback: + callback(x) + + else: # for loop exhausted + # Return incomplete progress + return x, maxiter + + +@xp_capabilities(np_only=True) +def bicgstab(A, b, x0=None, *, rtol=1e-5, atol=0., maxiter=None, M=None, + callback=None): + """ + Solve ``Ax = b`` with the BIConjugate Gradient STABilized method. + + Parameters + ---------- + A : {sparse array, ndarray, LinearOperator} + The real or complex N-by-N matrix of the linear system. + Alternatively, `A` can be a linear operator which can + produce ``Ax`` and ``A^T x`` using, e.g., + ``scipy.sparse.linalg.LinearOperator``. + b : ndarray + Right hand side of the linear system. Has shape (N,) or (N,1). + x0 : ndarray + Starting guess for the solution. + rtol, atol : float, optional + Parameters for the convergence test. For convergence, + ``norm(b - A @ x) <= max(rtol*norm(b), atol)`` should be satisfied. + The default is ``atol=0.`` and ``rtol=1e-5``. + maxiter : int + Maximum number of iterations. Iteration will stop after maxiter + steps even if the specified tolerance has not been achieved. + M : {sparse array, ndarray, LinearOperator} + Preconditioner for `A`. It should approximate the + inverse of `A` (see Notes). Effective preconditioning dramatically improves the + rate of convergence, which implies that fewer iterations are needed + to reach a given error tolerance. + callback : function + User-supplied function to call after each iteration. It is called + as ``callback(xk)``, where ``xk`` is the current solution vector. + + Returns + ------- + x : ndarray + The converged solution. + info : int + Provides convergence information: + 0 : successful exit + >0 : convergence to tolerance not achieved, number of iterations + <0 : parameter breakdown + + Notes + ----- + The preconditioner `M` should be a matrix such that ``M @ A`` has a smaller + condition number than `A`, see [1]_ . + + References + ---------- + .. [1] "Preconditioner", Wikipedia, + https://en.wikipedia.org/wiki/Preconditioner + .. [2] "Biconjugate gradient stabilized method", + Wikipedia, https://en.wikipedia.org/wiki/Biconjugate_gradient_stabilized_method + + Examples + -------- + >>> import numpy as np + >>> from scipy.sparse import csc_array + >>> from scipy.sparse.linalg import bicgstab + >>> R = np.array([[4, 2, 0, 1], + ... [3, 0, 0, 2], + ... [0, 1, 1, 1], + ... [0, 2, 1, 0]]) + >>> A = csc_array(R) + >>> b = np.array([-1, -0.5, -1, 2]) + >>> x, exit_code = bicgstab(A, b, atol=1e-5) + >>> print(exit_code) # 0 indicates successful convergence + 0 + >>> np.allclose(A.dot(x), b) + True + """ + A, M, x, b = make_system(A, M, x0, b) + bnrm2 = np.linalg.norm(b) + + atol, _ = _get_atol_rtol('bicgstab', bnrm2, atol, rtol) + + if bnrm2 == 0: + return b, 0 + + n = len(b) + + dotprod = np.vdot if np.iscomplexobj(x) else np.dot + + if maxiter is None: + maxiter = n*10 + + matvec = A.matvec + psolve = M.matvec + + # These values make no sense but coming from original Fortran code + # sqrt might have been meant instead. + rhotol = np.finfo(x.dtype.char).eps**2 + omegatol = rhotol + + # Dummy values to initialize vars, silence linter warnings + rho_prev, omega, alpha, p, v = None, None, None, None, None + + r = b - matvec(x) if x.any() else b.copy() + rtilde = r.copy() + + for iteration in range(maxiter): + if np.linalg.norm(r) < atol: # Are we done? + return x, 0 + + rho = dotprod(rtilde, r) + if np.abs(rho) < rhotol: # rho breakdown + return x, -10 + + if iteration > 0: + if np.abs(omega) < omegatol: # omega breakdown + return x, -11 + + beta = (rho / rho_prev) * (alpha / omega) + p -= omega*v + p *= beta + p += r + else: # First spin + s = np.empty_like(r) + p = r.copy() + + phat = psolve(p) + v = matvec(phat) + rv = dotprod(rtilde, v) + if rv == 0: + return x, -11 + alpha = rho / rv + r -= alpha*v + s[:] = r[:] + + if np.linalg.norm(s) < atol: + x += alpha*phat + return x, 0 + + shat = psolve(s) + t = matvec(shat) + omega = dotprod(t, s) / dotprod(t, t) + x += alpha*phat + x += omega*shat + r -= omega*t + rho_prev = rho + + if callback: + callback(x) + + else: # for loop exhausted + # Return incomplete progress + return x, maxiter + + +@xp_capabilities(np_only=True) +def cg(A, b, x0=None, *, rtol=1e-5, atol=0., maxiter=None, M=None, callback=None): + """ + Solve ``Ax = b`` with the Conjugate Gradient method, for a symmetric, + positive-definite `A`. + + Parameters + ---------- + A : {sparse array, ndarray, LinearOperator} + The real or complex N-by-N matrix of the linear system. + `A` must represent a hermitian, positive definite matrix. + Alternatively, `A` can be a linear operator which can + produce ``Ax`` using, e.g., + ``scipy.sparse.linalg.LinearOperator``. + b : ndarray + Right hand side of the linear system. Has shape (N,) or (N,1). + x0 : ndarray + Starting guess for the solution. + rtol, atol : float, optional + Parameters for the convergence test. For convergence, + ``norm(b - A @ x) <= max(rtol*norm(b), atol)`` should be satisfied. + The default is ``atol=0.`` and ``rtol=1e-5``. + maxiter : int + Maximum number of iterations. Iteration will stop after maxiter + steps even if the specified tolerance has not been achieved. + M : {sparse array, ndarray, LinearOperator} + Preconditioner for `A`. `M` must represent a hermitian, positive definite + matrix. It should approximate the inverse of `A` (see Notes). + Effective preconditioning dramatically improves the + rate of convergence, which implies that fewer iterations are needed + to reach a given error tolerance. + callback : function + User-supplied function to call after each iteration. It is called + as ``callback(xk)``, where ``xk`` is the current solution vector. + + Returns + ------- + x : ndarray + The converged solution. + info : int + Provides convergence information: + 0 : successful exit + >0 : convergence to tolerance not achieved, number of iterations + + Notes + ----- + The preconditioner `M` should be a matrix such that ``M @ A`` has a smaller + condition number than `A`, see [2]_. + + References + ---------- + .. [1] "Conjugate Gradient Method, Wikipedia, + https://en.wikipedia.org/wiki/Conjugate_gradient_method + .. [2] "Preconditioner", + Wikipedia, https://en.wikipedia.org/wiki/Preconditioner + + Examples + -------- + >>> import numpy as np + >>> from scipy.sparse import csc_array + >>> from scipy.sparse.linalg import cg + >>> P = np.array([[4, 0, 1, 0], + ... [0, 5, 0, 0], + ... [1, 0, 3, 2], + ... [0, 0, 2, 4]]) + >>> A = csc_array(P) + >>> b = np.array([-1, -0.5, -1, 2]) + >>> x, exit_code = cg(A, b, atol=1e-5) + >>> print(exit_code) # 0 indicates successful convergence + 0 + >>> np.allclose(A.dot(x), b) + True + """ + A, M, x, b = make_system(A, M, x0, b) + bnrm2 = np.linalg.norm(b) + + atol, _ = _get_atol_rtol('cg', bnrm2, atol, rtol) + + if bnrm2 == 0: + return b, 0 + + n = len(b) + + if maxiter is None: + maxiter = n*10 + + dotprod = np.vdot if np.iscomplexobj(x) else np.dot + + matvec = A.matvec + psolve = M.matvec + r = b - matvec(x) if x.any() else b.copy() + + # Dummy value to initialize var, silences warnings + rho_prev, p = None, None + + for iteration in range(maxiter): + if np.linalg.norm(r) < atol: # Are we done? + return x, 0 + + z = psolve(r) + rho_cur = dotprod(r, z) + if iteration > 0: + beta = rho_cur / rho_prev + p *= beta + p += z + else: # First spin + p = np.empty_like(r) + p[:] = z[:] + + q = matvec(p) + alpha = rho_cur / dotprod(p, q) + x += alpha*p + r -= alpha*q + rho_prev = rho_cur + + if callback: + callback(x) + + else: # for loop exhausted + # Return incomplete progress + return x, maxiter + + +@xp_capabilities(np_only=True) +def cgs(A, b, x0=None, *, rtol=1e-5, atol=0., maxiter=None, M=None, callback=None): + """ + Solve ``Ax = b`` with the Conjugate Gradient Squared method. + + Parameters + ---------- + A : {sparse array, ndarray, LinearOperator} + The real-valued N-by-N matrix of the linear system. + Alternatively, `A` can be a linear operator which can + produce ``Ax`` using, e.g., + ``scipy.sparse.linalg.LinearOperator``. + b : ndarray + Right hand side of the linear system. Has shape (N,) or (N,1). + x0 : ndarray + Starting guess for the solution. + rtol, atol : float, optional + Parameters for the convergence test. For convergence, + ``norm(b - A @ x) <= max(rtol*norm(b), atol)`` should be satisfied. + The default is ``atol=0.`` and ``rtol=1e-5``. + maxiter : int + Maximum number of iterations. Iteration will stop after maxiter + steps even if the specified tolerance has not been achieved. + M : {sparse array, ndarray, LinearOperator} + Preconditioner for ``A``. It should approximate the + inverse of `A` (see Notes). Effective preconditioning dramatically improves the + rate of convergence, which implies that fewer iterations are needed + to reach a given error tolerance. + callback : function + User-supplied function to call after each iteration. It is called + as ``callback(xk)``, where ``xk`` is the current solution vector. + + Returns + ------- + x : ndarray + The converged solution. + info : int + Provides convergence information: + 0 : successful exit + >0 : convergence to tolerance not achieved, number of iterations + <0 : parameter breakdown + + Notes + ----- + The preconditioner `M` should be a matrix such that ``M @ A`` has a smaller + condition number than `A`, see [1]_. + + References + ---------- + .. [1] "Preconditioner", Wikipedia, + https://en.wikipedia.org/wiki/Preconditioner + .. [2] "Conjugate gradient squared", Wikipedia, + https://en.wikipedia.org/wiki/Conjugate_gradient_squared_method + + Examples + -------- + >>> import numpy as np + >>> from scipy.sparse import csc_array + >>> from scipy.sparse.linalg import cgs + >>> R = np.array([[4, 2, 0, 1], + ... [3, 0, 0, 2], + ... [0, 1, 1, 1], + ... [0, 2, 1, 0]]) + >>> A = csc_array(R) + >>> b = np.array([-1, -0.5, -1, 2]) + >>> x, exit_code = cgs(A, b) + >>> print(exit_code) # 0 indicates successful convergence + 0 + >>> np.allclose(A.dot(x), b) + True + """ + A, M, x, b = make_system(A, M, x0, b) + bnrm2 = np.linalg.norm(b) + + atol, _ = _get_atol_rtol('cgs', bnrm2, atol, rtol) + + if bnrm2 == 0: + return b, 0 + + n = len(b) + + dotprod = np.vdot if np.iscomplexobj(x) else np.dot + + if maxiter is None: + maxiter = n*10 + + matvec = A.matvec + psolve = M.matvec + + rhotol = np.finfo(x.dtype.char).eps**2 + + r = b - matvec(x) if x.any() else b.copy() + + rtilde = r.copy() + bnorm = np.linalg.norm(b) + if bnorm == 0: + bnorm = 1 + + # Dummy values to initialize vars, silence linter warnings + rho_prev, p, u, q = None, None, None, None + + for iteration in range(maxiter): + rnorm = np.linalg.norm(r) + if rnorm < atol: # Are we done? + return x, 0 + + rho_cur = dotprod(rtilde, r) + if np.abs(rho_cur) < rhotol: # Breakdown case + return x, -10 + + if iteration > 0: + beta = rho_cur / rho_prev + + # u = r + beta * q + # p = u + beta * (q + beta * p); + u[:] = r[:] + u += beta*q + + p *= beta + p += q + p *= beta + p += u + + else: # First spin + p = r.copy() + u = r.copy() + q = np.empty_like(r) + + phat = psolve(p) + vhat = matvec(phat) + rv = dotprod(rtilde, vhat) + + if rv == 0: # Dot product breakdown + return x, -11 + + alpha = rho_cur / rv + q[:] = u[:] + q -= alpha*vhat + uhat = psolve(u + q) + x += alpha*uhat + + # Due to numerical error build-up the actual residual is computed + # instead of the following two lines that were in the original + # FORTRAN templates, still using a single matvec. + + # qhat = matvec(uhat) + # r -= alpha*qhat + r = b - matvec(x) + + rho_prev = rho_cur + + if callback: + callback(x) + + else: # for loop exhausted + # Return incomplete progress + return x, maxiter + + +@xp_capabilities(np_only=True) +def gmres(A, b, x0=None, *, rtol=1e-5, atol=0., restart=None, maxiter=None, M=None, + callback=None, callback_type=None): + """ + Solve ``Ax = b`` with the Generalized Minimal RESidual method. + + Parameters + ---------- + A : {sparse array, ndarray, LinearOperator} + The real or complex N-by-N matrix of the linear system. + Alternatively, `A` can be a linear operator which can + produce ``Ax`` using, e.g., + ``scipy.sparse.linalg.LinearOperator``. + b : ndarray + Right hand side of the linear system. Has shape (N,) or (N,1). + x0 : ndarray + Starting guess for the solution (a vector of zeros by default). + rtol, atol : float + Parameters for the convergence test. For convergence, + ``norm(b - A @ x) <= max(rtol*norm(b), atol)`` should be satisfied. + The default is ``atol=0.`` and ``rtol=1e-5``. + restart : int, optional + Number of iterations between restarts. Larger values increase + iteration cost, but may be necessary for convergence. + If omitted, ``min(20, n)`` is used. + maxiter : int, optional + Maximum number of iterations (restart cycles). Iteration will stop + after maxiter steps even if the specified tolerance has not been + achieved. See `callback_type`. + M : {sparse array, ndarray, LinearOperator} + Inverse of the preconditioner of `A`. `M` should approximate the + inverse of `A` and be easy to solve for (see Notes). Effective + preconditioning dramatically improves the rate of convergence, + which implies that fewer iterations are needed to reach a given + error tolerance. By default, no preconditioner is used. + In this implementation, left preconditioning is used, + and the preconditioned residual is minimized. However, the final + convergence is tested with respect to the ``b - A @ x`` residual. + callback : function + User-supplied function to call after each iteration. It is called + as ``callback(args)``, where ``args`` are selected by `callback_type`. + callback_type : {'x', 'pr_norm', 'legacy'}, optional + Callback function argument requested: + - ``x``: current iterate (ndarray), called on every restart + - ``pr_norm``: relative (preconditioned) residual norm (float), + called on every inner iteration + - ``legacy`` (default): same as ``pr_norm``, but also changes the + meaning of `maxiter` to count inner iterations instead of restart + cycles. + + This keyword has no effect if `callback` is not set. + + Returns + ------- + x : ndarray + The converged solution. + info : int + Provides convergence information: + 0 : successful exit + >0 : convergence to tolerance not achieved, number of iterations + + See Also + -------- + LinearOperator + + Notes + ----- + A preconditioner, P, is chosen such that P is close to A but easy to solve + for. The preconditioner parameter required by this routine is + ``M = P^-1``. The inverse should preferably not be calculated + explicitly. Rather, use the following template to produce M:: + + # Construct a linear operator that computes P^-1 @ x. + import scipy.sparse.linalg as spla + M_x = lambda x: spla.spsolve(P, x) + M = spla.LinearOperator((n, n), M_x) + + Examples + -------- + >>> import numpy as np + >>> from scipy.sparse import csc_array + >>> from scipy.sparse.linalg import gmres + >>> A = csc_array([[3, 2, 0], [1, -1, 0], [0, 5, 1]], dtype=float) + >>> b = np.array([2, 4, -1], dtype=float) + >>> x, exitCode = gmres(A, b, atol=1e-5) + >>> print(exitCode) # 0 indicates successful convergence + 0 + >>> np.allclose(A.dot(x), b) + True + """ + if callback is not None and callback_type is None: + # Warn about 'callback_type' semantic changes. + # Probably should be removed only in far future, Scipy 2.0 or so. + msg = ("scipy.sparse.linalg.gmres called without specifying " + "`callback_type`. The default value will be changed in" + " a future release. For compatibility, specify a value " + "for `callback_type` explicitly, e.g., " + "``gmres(..., callback_type='pr_norm')``, or to retain the " + "old behavior ``gmres(..., callback_type='legacy')``" + ) + warnings.warn(msg, category=DeprecationWarning, stacklevel=3) + + if callback_type is None: + callback_type = 'legacy' + + if callback_type not in ('x', 'pr_norm', 'legacy'): + raise ValueError(f"Unknown callback_type: {callback_type!r}") + + if callback is None: + callback_type = None + + A, M, x, b = make_system(A, M, x0, b) + matvec = A.matvec + psolve = M.matvec + n = len(b) + bnrm2 = np.linalg.norm(b) + + atol, _ = _get_atol_rtol('gmres', bnrm2, atol, rtol) + + if bnrm2 == 0: + return b, 0 + + eps = np.finfo(x.dtype.char).eps + + dotprod = np.vdot if np.iscomplexobj(x) else np.dot + + if maxiter is None: + maxiter = n*10 + + if restart is None: + restart = 20 + restart = min(restart, n) + + Mb_nrm2 = np.linalg.norm(psolve(b)) + + # ==================================================== + # =========== Tolerance control from gh-8400 ========= + # ==================================================== + # Tolerance passed to GMRESREVCOM applies to the inner + # iteration and deals with the left-preconditioned + # residual. + ptol_max_factor = 1. + ptol = Mb_nrm2 * min(ptol_max_factor, atol / bnrm2) + presid = 0. + # ==================================================== + lartg = get_lapack_funcs('lartg', dtype=x.dtype) + + # allocate internal variables + v = np.empty([restart+1, n], dtype=x.dtype) + h = np.zeros([restart, restart+1], dtype=x.dtype) + givens = np.zeros([restart, 2], dtype=x.dtype) + + # legacy iteration count + inner_iter = 0 + + for iteration in range(maxiter): + if iteration == 0: + r = b - matvec(x) if x.any() else b.copy() + if np.linalg.norm(r) < atol: # Are we done? + return x, 0 + + v[0, :] = psolve(r) + tmp = np.linalg.norm(v[0, :]) + v[0, :] *= (1 / tmp) + # RHS of the Hessenberg problem + S = np.zeros(restart+1, dtype=x.dtype) + S[0] = tmp + + breakdown = False + for col in range(restart): + av = matvec(v[col, :]) + w = psolve(av) + + # Modified Gram-Schmidt + h0 = np.linalg.norm(w) + for k in range(col+1): + tmp = dotprod(v[k, :], w) + h[col, k] = tmp + w -= tmp*v[k, :] + + h1 = np.linalg.norm(w) + h[col, col + 1] = h1 + v[col + 1, :] = w[:] + + # Exact solution indicator + if h1 <= eps*h0: + h[col, col + 1] = 0 + breakdown = True + else: + v[col + 1, :] *= (1 / h1) + + # apply past Givens rotations to current h column + for k in range(col): + c, s = givens[k, 0], givens[k, 1] + n0, n1 = h[col, [k, k+1]] + h[col, [k, k + 1]] = [c*n0 + s*n1, -s.conj()*n0 + c*n1] + + # get and apply current rotation to h and S + c, s, mag = lartg(h[col, col], h[col, col+1]) + givens[col, :] = [c, s] + h[col, [col, col+1]] = mag, 0 + + # S[col+1] component is always 0 + tmp = -np.conjugate(s)*S[col] + S[[col, col + 1]] = [c*S[col], tmp] + presid = np.abs(tmp) + inner_iter += 1 + + if callback_type in ('legacy', 'pr_norm'): + callback(presid / bnrm2) + # Legacy behavior + if callback_type == 'legacy' and inner_iter == maxiter: + break + if presid <= ptol or breakdown: + break + + # Solve h(col, col) upper triangular system and allow pseudo-solve + # singular cases as in (but without the f2py copies): + # y = trsv(h[:col+1, :col+1].T, S[:col+1]) + + if h[col, col] == 0: + S[col] = 0 + + y = np.zeros([col+1], dtype=x.dtype) + y[:] = S[:col+1] + for k in range(col, 0, -1): + if y[k] != 0: + y[k] /= h[k, k] + tmp = y[k] + y[:k] -= tmp*h[k, :k] + if y[0] != 0: + y[0] /= h[0, 0] + + x += y @ v[:col+1, :] + + r = b - matvec(x) + rnorm = np.linalg.norm(r) + + # Legacy exit + if callback_type == 'legacy' and inner_iter == maxiter: + return x, 0 if rnorm <= atol else maxiter + + if callback_type == 'x': + callback(x) + + if rnorm <= atol: + break + elif breakdown: + # Reached breakdown (= exact solution), but the external + # tolerance check failed. Bail out with failure. + break + elif presid <= ptol: + # Inner loop passed but outer didn't + ptol_max_factor = max(eps, 0.25 * ptol_max_factor) + else: + ptol_max_factor = min(1.0, 1.5 * ptol_max_factor) + + ptol = presid * min(ptol_max_factor, atol / rnorm) + + info = 0 if (rnorm <= atol) else maxiter + return x, info + + +@xp_capabilities(np_only=True) +def qmr(A, b, x0=None, *, rtol=1e-5, atol=0., maxiter=None, M1=None, M2=None, + callback=None): + """ + Solve ``Ax = b`` with the Quasi-Minimal Residual method. + + Parameters + ---------- + A : {sparse array, ndarray, LinearOperator} + The real-valued N-by-N matrix of the linear system. + Alternatively, ``A`` can be a linear operator which can + produce ``Ax`` and ``A^T x`` using, e.g., + ``scipy.sparse.linalg.LinearOperator``. + b : ndarray + Right hand side of the linear system. Has shape (N,) or (N,1). + x0 : ndarray + Starting guess for the solution. + rtol, atol : float, optional + Parameters for the convergence test. For convergence, + ``norm(b - A @ x) <= max(rtol*norm(b), atol)`` should be satisfied. + The default is ``atol=0.`` and ``rtol=1e-5``. + maxiter : int + Maximum number of iterations. Iteration will stop after maxiter + steps even if the specified tolerance has not been achieved. + M1 : {sparse array, ndarray, LinearOperator} + Left preconditioner for A. + M2 : {sparse array, ndarray, LinearOperator} + Right preconditioner for A. Used together with the left + preconditioner M1. The matrix M1@A@M2 should have better + conditioned than A alone. + callback : function + User-supplied function to call after each iteration. It is called + as callback(xk), where xk is the current solution vector. + + Returns + ------- + x : ndarray + The converged solution. + info : int + Provides convergence information: + 0 : successful exit + >0 : convergence to tolerance not achieved, number of iterations + <0 : parameter breakdown + + See Also + -------- + LinearOperator + + Examples + -------- + >>> import numpy as np + >>> from scipy.sparse import csc_array + >>> from scipy.sparse.linalg import qmr + >>> A = csc_array([[3., 2., 0.], [1., -1., 0.], [0., 5., 1.]]) + >>> b = np.array([2., 4., -1.]) + >>> x, exitCode = qmr(A, b, atol=1e-5) + >>> print(exitCode) # 0 indicates successful convergence + 0 + >>> np.allclose(A.dot(x), b) + True + """ + A_ = A + A, M, x, b = make_system(A, None, x0, b) + bnrm2 = np.linalg.norm(b) + + atol, _ = _get_atol_rtol('qmr', bnrm2, atol, rtol) + + if bnrm2 == 0: + return b, 0 + + if M1 is None and M2 is None: + if hasattr(A_, 'psolve'): + def left_psolve(b): + return A_.psolve(b, 'left') + + def right_psolve(b): + return A_.psolve(b, 'right') + + def left_rpsolve(b): + return A_.rpsolve(b, 'left') + + def right_rpsolve(b): + return A_.rpsolve(b, 'right') + M1 = LinearOperator(A.shape, + matvec=left_psolve, + rmatvec=left_rpsolve) + M2 = LinearOperator(A.shape, + matvec=right_psolve, + rmatvec=right_rpsolve) + else: + def id(b): + return b + M1 = LinearOperator(A.shape, matvec=id, rmatvec=id) + M2 = LinearOperator(A.shape, matvec=id, rmatvec=id) + + n = len(b) + if maxiter is None: + maxiter = n*10 + + dotprod = np.vdot if np.iscomplexobj(x) else np.dot + + rhotol = np.finfo(x.dtype.char).eps + betatol = rhotol + gammatol = rhotol + deltatol = rhotol + epsilontol = rhotol + xitol = rhotol + + r = b - A.matvec(x) if x.any() else b.copy() + + vtilde = r.copy() + y = M1.matvec(vtilde) + rho = np.linalg.norm(y) + wtilde = r.copy() + z = M2.rmatvec(wtilde) + xi = np.linalg.norm(z) + gamma, eta, theta = 1, -1, 0 + v = np.empty_like(vtilde) + w = np.empty_like(wtilde) + + # Dummy values to initialize vars, silence linter warnings + epsilon, q, d, p, s = None, None, None, None, None + + for iteration in range(maxiter): + if np.linalg.norm(r) < atol: # Are we done? + return x, 0 + if np.abs(rho) < rhotol: # rho breakdown + return x, -10 + if np.abs(xi) < xitol: # xi breakdown + return x, -15 + + v[:] = vtilde[:] + v *= (1 / rho) + y *= (1 / rho) + w[:] = wtilde[:] + w *= (1 / xi) + z *= (1 / xi) + delta = dotprod(z, y) + + if np.abs(delta) < deltatol: # delta breakdown + return x, -13 + + ytilde = M2.matvec(y) + ztilde = M1.rmatvec(z) + + if iteration > 0: + ytilde -= (xi * delta / epsilon) * p + p[:] = ytilde[:] + ztilde -= (rho * (delta / epsilon).conj()) * q + q[:] = ztilde[:] + else: # First spin + p = ytilde.copy() + q = ztilde.copy() + + ptilde = A.matvec(p) + epsilon = dotprod(q, ptilde) + if np.abs(epsilon) < epsilontol: # epsilon breakdown + return x, -14 + + beta = epsilon / delta + if np.abs(beta) < betatol: # beta breakdown + return x, -11 + + vtilde[:] = ptilde[:] + vtilde -= beta*v + y = M1.matvec(vtilde) + + rho_prev = rho + rho = np.linalg.norm(y) + wtilde[:] = w[:] + wtilde *= - beta.conj() + wtilde += A.rmatvec(q) + z = M2.rmatvec(wtilde) + xi = np.linalg.norm(z) + gamma_prev = gamma + theta_prev = theta + theta = rho / (gamma_prev * np.abs(beta)) + gamma = 1 / np.sqrt(1 + theta**2) + + if np.abs(gamma) < gammatol: # gamma breakdown + return x, -12 + + eta *= -(rho_prev / beta) * (gamma / gamma_prev)**2 + + if iteration > 0: + d *= (theta_prev * gamma) ** 2 + d += eta*p + s *= (theta_prev * gamma) ** 2 + s += eta*ptilde + else: + d = p.copy() + d *= eta + s = ptilde.copy() + s *= eta + + x += d + r -= s + + if callback: + callback(x) + + else: # for loop exhausted + # Return incomplete progress + return x, maxiter diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/lgmres.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/lgmres.py new file mode 100644 index 0000000000000000000000000000000000000000..a23ba71ba52d27c0e4b4bea544b1f7541092213e --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/lgmres.py @@ -0,0 +1,232 @@ +# Copyright (C) 2009, Pauli Virtanen +# Distributed under the same license as SciPy. + +import numpy as np +from numpy.linalg import LinAlgError +from scipy._lib._array_api import xp_capabilities +from scipy.linalg import get_blas_funcs +from .iterative import _get_atol_rtol +from .utils import make_system + +from ._gcrotmk import _fgmres + +__all__ = ['lgmres'] + + +@xp_capabilities(np_only=True) +def lgmres(A, b, x0=None, *, rtol=1e-5, atol=0., maxiter=1000, M=None, callback=None, + inner_m=30, outer_k=3, outer_v=None, store_outer_Av=True, + prepend_outer_v=False): + """ + Solve ``Ax = b`` with the LGMRES algorithm. + + The LGMRES algorithm [1]_ [2]_ is designed to avoid some problems + in the convergence in restarted GMRES, and often converges in fewer + iterations. + + Parameters + ---------- + A : {sparse array, ndarray, LinearOperator} + The real or complex N-by-N matrix of the linear system. + Alternatively, ``A`` can be a linear operator which can + produce ``Ax`` using, e.g., + ``scipy.sparse.linalg.LinearOperator``. + b : ndarray + Right hand side of the linear system. Has shape (N,) or (N,1). + x0 : ndarray + Starting guess for the solution. + rtol, atol : float, optional + Parameters for the convergence test. For convergence, + ``norm(b - A @ x) <= max(rtol*norm(b), atol)`` should be satisfied. + The default is ``rtol=1e-5``, the default for ``atol`` is ``0.0``. + maxiter : int, optional + Maximum number of iterations. Iteration will stop after maxiter + steps even if the specified tolerance has not been achieved. + M : {sparse array, ndarray, LinearOperator}, optional + Preconditioner for A. The preconditioner should approximate the + inverse of A. Effective preconditioning dramatically improves the + rate of convergence, which implies that fewer iterations are needed + to reach a given error tolerance. + callback : function, optional + User-supplied function to call after each iteration. It is called + as callback(xk), where xk is the current solution vector. + inner_m : int, optional + Number of inner GMRES iterations per each outer iteration. + outer_k : int, optional + Number of vectors to carry between inner GMRES iterations. + According to [1]_, good values are in the range of 1...3. + However, note that if you want to use the additional vectors to + accelerate solving multiple similar problems, larger values may + be beneficial. + outer_v : list of tuples, optional + List containing tuples ``(v, Av)`` of vectors and corresponding + matrix-vector products, used to augment the Krylov subspace, and + carried between inner GMRES iterations. The element ``Av`` can + be `None` if the matrix-vector product should be re-evaluated. + This parameter is modified in-place by `lgmres`, and can be used + to pass "guess" vectors in and out of the algorithm when solving + similar problems. + store_outer_Av : bool, optional + Whether LGMRES should store also A@v in addition to vectors `v` + in the `outer_v` list. Default is True. + prepend_outer_v : bool, optional + Whether to put outer_v augmentation vectors before Krylov iterates. + In standard LGMRES, prepend_outer_v=False. + + Returns + ------- + x : ndarray + The converged solution. + info : int + Provides convergence information: + + - 0 : successful exit + - >0 : convergence to tolerance not achieved, number of iterations + - <0 : illegal input or breakdown + + Notes + ----- + The LGMRES algorithm [1]_ [2]_ is designed to avoid the + slowing of convergence in restarted GMRES, due to alternating + residual vectors. Typically, it often outperforms GMRES(m) of + comparable memory requirements by some measure, or at least is not + much worse. + + Another advantage in this algorithm is that you can supply it with + 'guess' vectors in the `outer_v` argument that augment the Krylov + subspace. If the solution lies close to the span of these vectors, + the algorithm converges faster. This can be useful if several very + similar matrices need to be inverted one after another, such as in + Newton-Krylov iteration where the Jacobian matrix often changes + little in the nonlinear steps. + + References + ---------- + .. [1] A.H. Baker and E.R. Jessup and T. Manteuffel, "A Technique for + Accelerating the Convergence of Restarted GMRES", SIAM J. Matrix + Anal. Appl. 26, 962 (2005). + .. [2] A.H. Baker, "On Improving the Performance of the Linear Solver + restarted GMRES", PhD thesis, University of Colorado (2003). + + Examples + -------- + >>> import numpy as np + >>> from scipy.sparse import csc_array + >>> from scipy.sparse.linalg import lgmres + >>> A = csc_array([[3, 2, 0], [1, -1, 0], [0, 5, 1]], dtype=float) + >>> b = np.array([2, 4, -1], dtype=float) + >>> x, exitCode = lgmres(A, b, atol=1e-5) + >>> print(exitCode) # 0 indicates successful convergence + 0 + >>> np.allclose(A.dot(x), b) + True + """ + A,M,x,b = make_system(A,M,x0,b) + + if not np.isfinite(b).all(): + raise ValueError("RHS must contain only finite numbers") + + matvec = A.matvec + psolve = M.matvec + + if outer_v is None: + outer_v = [] + + axpy, dot, scal = None, None, None + nrm2 = get_blas_funcs('nrm2', [b]) + + b_norm = nrm2(b) + + # we call this to get the right atol/rtol and raise errors as necessary + atol, rtol = _get_atol_rtol('lgmres', b_norm, atol, rtol) + + if b_norm == 0: + x = b + return (x, 0) + + ptol_max_factor = 1.0 + + for k_outer in range(maxiter): + r_outer = matvec(x) - b + + # -- callback + if callback is not None: + callback(x) + + # -- determine input type routines + if axpy is None: + if np.iscomplexobj(r_outer) and not np.iscomplexobj(x): + x = x.astype(r_outer.dtype) + axpy, dot, scal, nrm2 = get_blas_funcs(['axpy', 'dot', 'scal', 'nrm2'], + (x, r_outer)) + + # -- check stopping condition + r_norm = nrm2(r_outer) + if r_norm <= max(atol, rtol * b_norm): + break + + # -- inner LGMRES iteration + v0 = -psolve(r_outer) + inner_res_0 = nrm2(v0) + + if inner_res_0 == 0: + rnorm = nrm2(r_outer) + raise RuntimeError("Preconditioner returned a zero vector; " + f"|v| ~ {rnorm:.1g}, |M v| = 0") + + v0 = scal(1.0/inner_res_0, v0) + + ptol = min(ptol_max_factor, max(atol, rtol*b_norm)/r_norm) + + try: + Q, R, B, vs, zs, y, pres = _fgmres(matvec, + v0, + inner_m, + lpsolve=psolve, + atol=ptol, + outer_v=outer_v, + prepend_outer_v=prepend_outer_v) + y *= inner_res_0 + if not np.isfinite(y).all(): + # Overflow etc. in computation. There's no way to + # recover from this, so we have to bail out. + raise LinAlgError() + except LinAlgError: + # Floating point over/underflow, non-finite result from + # matmul etc. -- report failure. + return x, k_outer + 1 + + # Inner loop tolerance control + if pres > ptol: + ptol_max_factor = min(1.0, 1.5 * ptol_max_factor) + else: + ptol_max_factor = max(1e-16, 0.25 * ptol_max_factor) + + # -- GMRES terminated: eval solution + dx = zs[0]*y[0] + for w, yc in zip(zs[1:], y[1:]): + dx = axpy(w, dx, dx.shape[0], yc) # dx += w*yc + + # -- Store LGMRES augmentation vectors + nx = nrm2(dx) + if nx > 0: + if store_outer_Av: + q = Q.dot(R.dot(y)) + ax = vs[0]*q[0] + for v, qc in zip(vs[1:], q[1:]): + ax = axpy(v, ax, ax.shape[0], qc) + outer_v.append((dx/nx, ax/nx)) + else: + outer_v.append((dx/nx, None)) + + # -- Retain only a finite number of augmentation vectors + while len(outer_v) > outer_k: + del outer_v[0] + + # -- Apply step + x += dx + else: + # didn't converge ... + return x, maxiter + + return x, 0 diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/lsmr.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/lsmr.py new file mode 100644 index 0000000000000000000000000000000000000000..5081ebee1315024be20bd14da2922555ce846759 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/lsmr.py @@ -0,0 +1,494 @@ +""" +Copyright (C) 2010 David Fong and Michael Saunders + +LSMR uses an iterative method. + +07 Jun 2010: Documentation updated +03 Jun 2010: First release version in Python + +David Chin-lung Fong clfong@stanford.edu +Institute for Computational and Mathematical Engineering +Stanford University + +Michael Saunders saunders@stanford.edu +Systems Optimization Laboratory +Dept of MS&E, Stanford University. + +""" + +__all__ = ['lsmr'] + +from numpy import zeros, inf, atleast_1d, result_type +from numpy.linalg import norm +from math import sqrt +from scipy.sparse.linalg._interface import aslinearoperator + +from scipy.sparse.linalg._isolve.lsqr import _sym_ortho + + +def lsmr(A, b, damp=0.0, atol=1e-6, btol=1e-6, conlim=1e8, + maxiter=None, show=False, x0=None): + """Iterative solver for least-squares problems. + + lsmr solves the system of linear equations ``Ax = b``. If the system + is inconsistent, it solves the least-squares problem ``min ||b - Ax||_2``. + ``A`` is a rectangular matrix of dimension m-by-n, where all cases are + allowed: m = n, m > n, or m < n. ``b`` is a vector of length m. + The matrix A may be dense or sparse (usually sparse). + + Parameters + ---------- + A : {sparse array, ndarray, LinearOperator} + Matrix A in the linear system. + Alternatively, ``A`` can be a linear operator which can + produce ``Ax`` and ``A^H x`` using, e.g., + ``scipy.sparse.linalg.LinearOperator``. + b : array_like, shape (m,) + Vector ``b`` in the linear system. + damp : float + Damping factor for regularized least-squares. `lsmr` solves + the regularized least-squares problem:: + + min ||(b) - ( A )x|| + ||(0) (damp*I) ||_2 + + where damp is a scalar. If damp is None or 0, the system + is solved without regularization. Default is 0. + atol, btol : float, optional + Stopping tolerances. `lsmr` continues iterations until a + certain backward error estimate is smaller than some quantity + depending on atol and btol. Let ``r = b - Ax`` be the + residual vector for the current approximate solution ``x``. + If ``Ax = b`` seems to be consistent, `lsmr` terminates + when ``norm(r) <= atol * norm(A) * norm(x) + btol * norm(b)``. + Otherwise, `lsmr` terminates when ``norm(A^H r) <= + atol * norm(A) * norm(r)``. If both tolerances are 1.0e-6 (default), + the final ``norm(r)`` should be accurate to about 6 + digits. (The final ``x`` will usually have fewer correct digits, + depending on ``cond(A)`` and the size of LAMBDA.) If `atol` + or `btol` is None, a default value of 1.0e-6 will be used. + Ideally, they should be estimates of the relative error in the + entries of ``A`` and ``b`` respectively. For example, if the entries + of ``A`` have 7 correct digits, set ``atol = 1e-7``. This prevents + the algorithm from doing unnecessary work beyond the + uncertainty of the input data. + conlim : float, optional + `lsmr` terminates if an estimate of ``cond(A)`` exceeds + `conlim`. For compatible systems ``Ax = b``, conlim could be + as large as 1.0e+12 (say). For least-squares problems, + `conlim` should be less than 1.0e+8. If `conlim` is None, the + default value is 1e+8. Maximum precision can be obtained by + setting ``atol = btol = conlim = 0``, but the number of + iterations may then be excessive. Default is 1e8. + maxiter : int, optional + `lsmr` terminates if the number of iterations reaches + `maxiter`. The default is ``maxiter = min(m, n)``. For + ill-conditioned systems, a larger value of `maxiter` may be + needed. Default is False. + show : bool, optional + Print iterations logs if ``show=True``. Default is False. + x0 : array_like, shape (n,), optional + Initial guess of ``x``, if None zeros are used. Default is None. + + .. versionadded:: 1.0.0 + + Returns + ------- + x : ndarray of float + Least-square solution returned. + istop : int + istop gives the reason for stopping:: + + istop = 0 means x=0 is a solution. If x0 was given, then x=x0 is a + solution. + = 1 means x is an approximate solution to A@x = B, + according to atol and btol. + = 2 means x approximately solves the least-squares problem + according to atol. + = 3 means COND(A) seems to be greater than CONLIM. + = 4 is the same as 1 with atol = btol = eps (machine + precision) + = 5 is the same as 2 with atol = eps. + = 6 is the same as 3 with CONLIM = 1/eps. + = 7 means ITN reached maxiter before the other stopping + conditions were satisfied. + + itn : int + Number of iterations used. + normr : float + ``norm(b-Ax)`` + normar : float + ``norm(A^H (b - Ax))`` + norma : float + ``norm(A)`` + conda : float + Condition number of A. + normx : float + ``norm(x)`` + + Notes + ----- + + .. versionadded:: 0.11.0 + + References + ---------- + .. [1] D. C.-L. Fong and M. A. Saunders, + "LSMR: An iterative algorithm for sparse least-squares problems", + SIAM J. Sci. Comput., vol. 33, pp. 2950-2971, 2011. + :arxiv:`1006.0758` + .. [2] LSMR Software, https://web.stanford.edu/group/SOL/software/lsmr/ + + Examples + -------- + >>> import numpy as np + >>> from scipy.sparse import csc_array + >>> from scipy.sparse.linalg import lsmr + >>> A = csc_array([[1., 0.], [1., 1.], [0., 1.]], dtype=float) + + The first example has the trivial solution ``[0, 0]`` + + >>> b = np.array([0., 0., 0.], dtype=float) + >>> x, istop, itn, normr = lsmr(A, b)[:4] + >>> istop + 0 + >>> x + array([0., 0.]) + + The stopping code ``istop=0`` returned indicates that a vector of zeros was + found as a solution. The returned solution `x` indeed contains + ``[0., 0.]``. The next example has a non-trivial solution: + + >>> b = np.array([1., 0., -1.], dtype=float) + >>> x, istop, itn, normr = lsmr(A, b)[:4] + >>> istop + 1 + >>> x + array([ 1., -1.]) + >>> itn + 1 + >>> normr + 4.440892098500627e-16 + + As indicated by ``istop=1``, `lsmr` found a solution obeying the tolerance + limits. The given solution ``[1., -1.]`` obviously solves the equation. The + remaining return values include information about the number of iterations + (`itn=1`) and the remaining difference of left and right side of the solved + equation. + The final example demonstrates the behavior in the case where there is no + solution for the equation: + + >>> b = np.array([1., 0.01, -1.], dtype=float) + >>> x, istop, itn, normr = lsmr(A, b)[:4] + >>> istop + 2 + >>> x + array([ 1.00333333, -0.99666667]) + >>> A.dot(x)-b + array([ 0.00333333, -0.00333333, 0.00333333]) + >>> normr + 0.005773502691896255 + + `istop` indicates that the system is inconsistent and thus `x` is rather an + approximate solution to the corresponding least-squares problem. `normr` + contains the minimal distance that was found. + """ + + A = aslinearoperator(A) + if A.ndim > 2: + raise ValueError(f"{A.ndim}-dimensional `A` is unsupported, expected 2-D.") + b = atleast_1d(b) + if b.ndim > 1: + b = b.squeeze() + + msg = ('The exact solution is x = 0, or x = x0, if x0 was given ', + 'Ax - b is small enough, given atol, btol ', + 'The least-squares solution is good enough, given atol ', + 'The estimate of cond(Abar) has exceeded conlim ', + 'Ax - b is small enough for this machine ', + 'The least-squares solution is good enough for this machine', + 'Cond(Abar) seems to be too large for this machine ', + 'The iteration limit has been reached ') + + hdg1 = ' itn x(1) norm r norm Ar' + hdg2 = ' compatible LS norm A cond A' + pfreq = 20 # print frequency (for repeating the heading) + pcount = 0 # print counter + + m, n = A.shape + + # stores the num of singular values + minDim = min([m, n]) + + if maxiter is None: + maxiter = minDim + + if x0 is None: + dtype = result_type(A, b, float) + else: + dtype = result_type(A, b, x0, float) + + if show: + print(' ') + print('LSMR Least-squares solution of Ax = b\n') + print(f'The matrix A has {m} rows and {n} columns') + print(f'damp = {damp:20.14e}\n') + print(f'atol = {atol:8.2e} conlim = {conlim:8.2e}\n') + print(f'btol = {btol:8.2e} maxiter = {maxiter:8g}\n') + + u = b + normb = norm(b) + if x0 is None: + x = zeros(n, dtype) + beta = normb.copy() + else: + x = atleast_1d(x0.copy()) + u = u - A.matvec(x) + beta = norm(u) + + if beta > 0: + u = (1 / beta) * u + v = A.rmatvec(u) + alpha = norm(v) + else: + v = zeros(n, dtype) + alpha = 0 + + if alpha > 0: + v = (1 / alpha) * v + + # Initialize variables for 1st iteration. + + itn = 0 + zetabar = alpha * beta + alphabar = alpha + rho = 1 + rhobar = 1 + cbar = 1 + sbar = 0 + + h = v.copy() + hbar = zeros(n, dtype) + + # Initialize variables for estimation of ||r||. + + betadd = beta + betad = 0 + rhodold = 1 + tautildeold = 0 + thetatilde = 0 + zeta = 0 + d = 0 + + # Initialize variables for estimation of ||A|| and cond(A) + + normA2 = alpha * alpha + maxrbar = 0 + minrbar = 1e+100 + normA = sqrt(normA2) + condA = 1 + normx = 0 + + # Initialize print strings + str1 = '' + str2 = '' + str3 = '' + str4 = '' + + # Items for use in stopping rules, normb set earlier + istop = 0 + ctol = 0 + if conlim > 0: + ctol = 1 / conlim + normr = beta + + # Reverse the order here from the original matlab code because + # there was an error on return when arnorm==0 + normar = alpha * beta + if normar == 0: + if show: + print(msg[0]) + return x, istop, itn, normr, normar, normA, condA, normx + + if normb == 0: + x[()] = 0 + return x, istop, itn, normr, normar, normA, condA, normx + + if show: + print(' ') + print(hdg1, hdg2) + test1 = 1 + test2 = alpha / beta + str1 = f'{itn:6g} {x[0]:12.5e}' + str2 = f' {normr:10.3e} {normar:10.3e}' + str3 = f' {test1:8.1e} {test2:8.1e}' + print(''.join([str1, str2, str3])) + + # Main iteration loop. + while itn < maxiter: + itn = itn + 1 + + # Perform the next step of the bidiagonalization to obtain the + # next beta, u, alpha, v. These satisfy the relations + # beta*u = A@v - alpha*u, + # alpha*v = A'@u - beta*v. + + u *= -alpha + u += A.matvec(v) + beta = norm(u) + + if beta > 0: + u *= (1 / beta) + v *= -beta + v += A.rmatvec(u) + alpha = norm(v) + if alpha > 0: + v *= (1 / alpha) + + # At this point, beta = beta_{k+1}, alpha = alpha_{k+1}. + + # Construct rotation Qhat_{k,2k+1}. + + chat, shat, alphahat = _sym_ortho(alphabar, damp) + + # Use a plane rotation (Q_i) to turn B_i to R_i + + rhoold = rho + c, s, rho = _sym_ortho(alphahat, beta) + thetanew = s*alpha + alphabar = c*alpha + + # Use a plane rotation (Qbar_i) to turn R_i^T to R_i^bar + + rhobarold = rhobar + zetaold = zeta + thetabar = sbar * rho + rhotemp = cbar * rho + cbar, sbar, rhobar = _sym_ortho(cbar * rho, thetanew) + zeta = cbar * zetabar + zetabar = - sbar * zetabar + + # Update h, h_hat, x. + + hbar *= - (thetabar * rho / (rhoold * rhobarold)) + hbar += h + x += (zeta / (rho * rhobar)) * hbar + h *= - (thetanew / rho) + h += v + + # Estimate of ||r||. + + # Apply rotation Qhat_{k,2k+1}. + betaacute = chat * betadd + betacheck = -shat * betadd + + # Apply rotation Q_{k,k+1}. + betahat = c * betaacute + betadd = -s * betaacute + + # Apply rotation Qtilde_{k-1}. + # betad = betad_{k-1} here. + + thetatildeold = thetatilde + ctildeold, stildeold, rhotildeold = _sym_ortho(rhodold, thetabar) + thetatilde = stildeold * rhobar + rhodold = ctildeold * rhobar + betad = - stildeold * betad + ctildeold * betahat + + # betad = betad_k here. + # rhodold = rhod_k here. + + tautildeold = (zetaold - thetatildeold * tautildeold) / rhotildeold + taud = (zeta - thetatilde * tautildeold) / rhodold + d = d + betacheck * betacheck + normr = sqrt(d + (betad - taud)**2 + betadd * betadd) + + # Estimate ||A||. + normA2 = normA2 + beta * beta + normA = sqrt(normA2) + normA2 = normA2 + alpha * alpha + + # Estimate cond(A). + maxrbar = max(maxrbar, rhobarold) + if itn > 1: + minrbar = min(minrbar, rhobarold) + condA = max(maxrbar, rhotemp) / min(minrbar, rhotemp) + + # Test for convergence. + + # Compute norms for convergence testing. + normar = abs(zetabar) + normx = norm(x) + + # Now use these norms to estimate certain other quantities, + # some of which will be small near a solution. + + test1 = normr / normb + if (normA * normr) != 0: + test2 = normar / (normA * normr) + else: + test2 = inf + test3 = 1 / condA + t1 = test1 / (1 + normA * normx / normb) + rtol = btol + atol * normA * normx / normb + + # The following tests guard against extremely small values of + # atol, btol or ctol. (The user may have set any or all of + # the parameters atol, btol, conlim to 0.) + # The effect is equivalent to the normAl tests using + # atol = eps, btol = eps, conlim = 1/eps. + + if itn >= maxiter: + istop = 7 + if 1 + test3 <= 1: + istop = 6 + if 1 + test2 <= 1: + istop = 5 + if 1 + t1 <= 1: + istop = 4 + + # Allow for tolerances set by the user. + + if test3 <= ctol: + istop = 3 + if test2 <= atol: + istop = 2 + if test1 <= rtol: + istop = 1 + + # See if it is time to print something. + + if show: + if (n <= 40) or (itn <= 10) or (itn >= maxiter - 10) or \ + (itn % 10 == 0) or (test3 <= 1.1 * ctol) or \ + (test2 <= 1.1 * atol) or (test1 <= 1.1 * rtol) or \ + (istop != 0): + + if pcount >= pfreq: + pcount = 0 + print(' ') + print(hdg1, hdg2) + pcount = pcount + 1 + str1 = f'{itn:6g} {x[0]:12.5e}' + str2 = f' {normr:10.3e} {normar:10.3e}' + str3 = f' {test1:8.1e} {test2:8.1e}' + str4 = f' {normA:8.1e} {condA:8.1e}' + print(''.join([str1, str2, str3, str4])) + + if istop > 0: + break + + # Print the stopping condition. + + if show: + print(' ') + print('LSMR finished') + print(msg[istop]) + print(f'istop ={istop:8g} normr ={normr:8.1e}') + print(f' normA ={normA:8.1e} normAr ={normar:8.1e}') + print(f'itn ={itn:8g} condA ={condA:8.1e}') + print(f' normx ={normx:8.1e}') + print(str1, str2) + print(str3, str4) + + return x, istop, itn, normr, normar, normA, condA, normx diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/lsqr.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/lsqr.py new file mode 100644 index 0000000000000000000000000000000000000000..abedfb855ffea6e38ff0027c15e54334e7352e73 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/lsqr.py @@ -0,0 +1,591 @@ +"""Sparse Equations and Least Squares. + +The original Fortran code was written by C. C. Paige and M. A. Saunders as +described in + +C. C. Paige and M. A. Saunders, LSQR: An algorithm for sparse linear +equations and sparse least squares, TOMS 8(1), 43--71 (1982). + +C. C. Paige and M. A. Saunders, Algorithm 583; LSQR: Sparse linear +equations and least-squares problems, TOMS 8(2), 195--209 (1982). + +It is licensed under the following BSD license: + +Copyright (c) 2006, Systems Optimization Laboratory +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of Stanford University nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The Fortran code was translated to Python for use in CVXOPT by Jeffery +Kline with contributions by Mridul Aanjaneya and Bob Myhill. + +Adapted for SciPy by Stefan van der Walt. + +""" + +__all__ = ['lsqr'] + +import numpy as np +from math import sqrt +from scipy.sparse.linalg._interface import aslinearoperator +from scipy.sparse._sputils import convert_pydata_sparse_to_scipy + +eps = np.finfo(np.float64).eps + + +def _sym_ortho(a, b): + """ + Stable implementation of Givens rotation. + + Notes + ----- + The routine 'SymOrtho' was added for numerical stability. This is + recommended by S.-C. Choi in [1]_. It removes the unpleasant potential of + ``1/eps`` in some important places (see, for example text following + "Compute the next plane rotation Qk" in minres.py). + + References + ---------- + .. [1] S.-C. Choi, "Iterative Methods for Singular Linear Equations + and Least-Squares Problems", Dissertation, + http://www.stanford.edu/group/SOL/dissertations/sou-cheng-choi-thesis.pdf + + """ + if b == 0: + return np.sign(a), 0, abs(a) + elif a == 0: + return 0, np.sign(b), abs(b) + elif abs(b) > abs(a): + tau = a / b + s = np.sign(b) / sqrt(1 + tau * tau) + c = s * tau + r = b / s + else: + tau = b / a + c = np.sign(a) / sqrt(1+tau*tau) + s = c * tau + r = a / c + return c, s, r + + +def lsqr(A, b, damp=0.0, atol=1e-6, btol=1e-6, conlim=1e8, + iter_lim=None, show=False, calc_var=False, x0=None): + """Find the least-squares solution to a large, sparse, linear system + of equations. + + The function solves ``Ax = b`` or ``min ||Ax - b||^2`` or + ``min ||Ax - b||^2 + d^2 ||x - x0||^2``. + + The matrix A may be square or rectangular (over-determined or + under-determined), and may have any rank. + + :: + + 1. Unsymmetric equations -- solve Ax = b + + 2. Linear least squares -- solve Ax = b + in the least-squares sense + + 3. Damped least squares -- solve ( A )*x = ( b ) + ( damp*I ) ( damp*x0 ) + in the least-squares sense + + Parameters + ---------- + A : {sparse array, ndarray, LinearOperator} + Representation of an m-by-n matrix. + Alternatively, ``A`` can be a linear operator which can + produce ``Ax`` and ``A^T x`` using, e.g., + ``scipy.sparse.linalg.LinearOperator``. + b : array_like, shape (m,) + Right-hand side vector ``b``. + damp : float + Damping coefficient. Default is 0. + atol, btol : float, optional + Stopping tolerances. `lsqr` continues iterations until a + certain backward error estimate is smaller than some quantity + depending on atol and btol. Let ``r = b - Ax`` be the + residual vector for the current approximate solution ``x``. + If ``Ax = b`` seems to be consistent, `lsqr` terminates + when ``norm(r) <= atol * norm(A) * norm(x) + btol * norm(b)``. + Otherwise, `lsqr` terminates when ``norm(A^H r) <= + atol * norm(A) * norm(r)``. If both tolerances are 1.0e-6 (default), + the final ``norm(r)`` should be accurate to about 6 + digits. (The final ``x`` will usually have fewer correct digits, + depending on ``cond(A)`` and the size of LAMBDA.) If `atol` + or `btol` is None, a default value of 1.0e-6 will be used. + Ideally, they should be estimates of the relative error in the + entries of ``A`` and ``b`` respectively. For example, if the entries + of ``A`` have 7 correct digits, set ``atol = 1e-7``. This prevents + the algorithm from doing unnecessary work beyond the + uncertainty of the input data. + conlim : float, optional + Another stopping tolerance. lsqr terminates if an estimate of + ``cond(A)`` exceeds `conlim`. For compatible systems ``Ax = + b``, `conlim` could be as large as 1.0e+12 (say). For + least-squares problems, conlim should be less than 1.0e+8. + Maximum precision can be obtained by setting ``atol = btol = + conlim = zero``, but the number of iterations may then be + excessive. Default is 1e8. + iter_lim : int, optional + Explicit limitation on number of iterations (for safety). + show : bool, optional + Display an iteration log. Default is False. + calc_var : bool, optional + Whether to estimate diagonals of ``(A'A + damp^2*I)^{-1}``. + x0 : array_like, shape (n,), optional + Initial guess of x, if None zeros are used. Default is None. + + .. versionadded:: 1.0.0 + + Returns + ------- + x : ndarray of float + The final solution. + istop : int + Gives the reason for termination. + 1 means x is an approximate solution to Ax = b. + 2 means x approximately solves the least-squares problem. + itn : int + Iteration number upon termination. + r1norm : float + ``norm(r)``, where ``r = b - Ax``. + r2norm : float + ``sqrt( norm(r)^2 + damp^2 * norm(x - x0)^2 )``. Equal to `r1norm` + if ``damp == 0``. + anorm : float + Estimate of Frobenius norm of ``Abar = [[A]; [damp*I]]``. + acond : float + Estimate of ``cond(Abar)``. + arnorm : float + Estimate of ``norm(A'@r - damp^2*(x - x0))``. + xnorm : float + ``norm(x)`` + var : ndarray of float + If ``calc_var`` is True, estimates all diagonals of + ``(A'A)^{-1}`` (if ``damp == 0``) or more generally ``(A'A + + damp^2*I)^{-1}``. This is well defined if A has full column + rank or ``damp > 0``. (Not sure what var means if ``rank(A) + < n`` and ``damp = 0.``) + + Notes + ----- + LSQR uses an iterative method to approximate the solution. The + number of iterations required to reach a certain accuracy depends + strongly on the scaling of the problem. Poor scaling of the rows + or columns of A should therefore be avoided where possible. + + For example, in problem 1 the solution is unaltered by + row-scaling. If a row of A is very small or large compared to + the other rows of A, the corresponding row of ( A b ) should be + scaled up or down. + + In problems 1 and 2, the solution x is easily recovered + following column-scaling. Unless better information is known, + the nonzero columns of A should be scaled so that they all have + the same Euclidean norm (e.g., 1.0). + + In problem 3, there is no freedom to re-scale if damp is + nonzero. However, the value of damp should be assigned only + after attention has been paid to the scaling of A. + + The parameter damp is intended to help regularize + ill-conditioned systems, by preventing the true solution from + being very large. Another aid to regularization is provided by + the parameter acond, which may be used to terminate iterations + before the computed solution becomes very large. + + If some initial estimate ``x0`` is known and if ``damp == 0``, + one could proceed as follows: + + 1. Compute a residual vector ``r0 = b - A@x0``. + 2. Use LSQR to solve the system ``A@dx = r0``. + 3. Add the correction dx to obtain a final solution ``x = x0 + dx``. + + This requires that ``x0`` be available before and after the call + to LSQR. To judge the benefits, suppose LSQR takes k1 iterations + to solve A@x = b and k2 iterations to solve A@dx = r0. + If x0 is "good", norm(r0) will be smaller than norm(b). + If the same stopping tolerances atol and btol are used for each + system, k1 and k2 will be similar, but the final solution x0 + dx + should be more accurate. The only way to reduce the total work + is to use a larger stopping tolerance for the second system. + If some value btol is suitable for A@x = b, the larger value + btol*norm(b)/norm(r0) should be suitable for A@dx = r0. + + Preconditioning is another way to reduce the number of iterations. + If it is possible to solve a related system ``M@x = b`` + efficiently, where M approximates A in some helpful way (e.g. M - + A has low rank or its elements are small relative to those of A), + LSQR may converge more rapidly on the system ``A@M(inverse)@z = + b``, after which x can be recovered by solving M@x = z. + + If A is symmetric, LSQR should not be used! + + Alternatives are the symmetric conjugate-gradient method (cg) + and/or SYMMLQ. SYMMLQ is an implementation of symmetric cg that + applies to any symmetric A and will converge more rapidly than + LSQR. If A is positive definite, there are other implementations + of symmetric cg that require slightly less work per iteration than + SYMMLQ (but will take the same number of iterations). + + References + ---------- + .. [1] C. C. Paige and M. A. Saunders (1982a). + "LSQR: An algorithm for sparse linear equations and + sparse least squares", ACM TOMS 8(1), 43-71. + .. [2] C. C. Paige and M. A. Saunders (1982b). + "Algorithm 583. LSQR: Sparse linear equations and least + squares problems", ACM TOMS 8(2), 195-209. + .. [3] M. A. Saunders (1995). "Solution of sparse rectangular + systems using LSQR and CRAIG", BIT 35, 588-604. + + Examples + -------- + >>> import numpy as np + >>> from scipy.sparse import csc_array + >>> from scipy.sparse.linalg import lsqr + >>> A = csc_array([[1., 0.], [1., 1.], [0., 1.]], dtype=float) + + The first example has the trivial solution ``[0, 0]`` + + >>> b = np.array([0., 0., 0.], dtype=float) + >>> x, istop, itn, normr = lsqr(A, b)[:4] + >>> istop + 0 + >>> x + array([ 0., 0.]) + + The stopping code ``istop=0`` returned indicates that a vector of zeros was + found as a solution. The returned solution `x` indeed contains + ``[0., 0.]``. The next example has a non-trivial solution: + + >>> b = np.array([1., 0., -1.], dtype=float) + >>> x, istop, itn, r1norm = lsqr(A, b)[:4] + >>> istop + 1 + >>> x + array([ 1., -1.]) + >>> itn + 1 + >>> r1norm + 4.440892098500627e-16 + + As indicated by ``istop=1``, `lsqr` found a solution obeying the tolerance + limits. The given solution ``[1., -1.]`` obviously solves the equation. The + remaining return values include information about the number of iterations + (`itn=1`) and the remaining difference of left and right side of the solved + equation. + The final example demonstrates the behavior in the case where there is no + solution for the equation: + + >>> b = np.array([1., 0.01, -1.], dtype=float) + >>> x, istop, itn, r1norm = lsqr(A, b)[:4] + >>> istop + 2 + >>> x + array([ 1.00333333, -0.99666667]) + >>> A.dot(x)-b + array([ 0.00333333, -0.00333333, 0.00333333]) + >>> r1norm + 0.005773502691896255 + + `istop` indicates that the system is inconsistent and thus `x` is rather an + approximate solution to the corresponding least-squares problem. `r1norm` + contains the norm of the minimal residual that was found. + """ + A = convert_pydata_sparse_to_scipy(A) + A = aslinearoperator(A) + if A.ndim > 2: + raise ValueError(f"{A.ndim}-dimensional `A` is unsupported, expected 2-D.") + b = np.atleast_1d(b) + if b.ndim > 1: + b = b.squeeze() + + m, n = A.shape + if iter_lim is None: + iter_lim = 2 * n + var = np.zeros(n) + + msg = ('The exact solution is x = 0 ', + 'Ax - b is small enough, given atol, btol ', + 'The least-squares solution is good enough, given atol ', + 'The estimate of cond(Abar) has exceeded conlim ', + 'Ax - b is small enough for this machine ', + 'The least-squares solution is good enough for this machine', + 'Cond(Abar) seems to be too large for this machine ', + 'The iteration limit has been reached ') + + if show: + print(' ') + print('LSQR Least-squares solution of Ax = b') + str1 = f'The matrix A has {m} rows and {n} columns' + str2 = f'damp = {damp:20.14e} calc_var = {calc_var:8g}' + str3 = f'atol = {atol:8.2e} conlim = {conlim:8.2e}' + str4 = f'btol = {btol:8.2e} iter_lim = {iter_lim:8g}' + print(str1) + print(str2) + print(str3) + print(str4) + + itn = 0 + istop = 0 + ctol = 0 + if conlim > 0: + ctol = 1/conlim + anorm = 0 + acond = 0 + dampsq = damp**2 + ddnorm = 0 + res2 = 0 + xnorm = 0 + xxnorm = 0 + z = 0 + cs2 = -1 + sn2 = 0 + + # Set up the first vectors u and v for the bidiagonalization. + # These satisfy beta*u = b - A@x, alfa*v = A'@u. + u = b + bnorm = np.linalg.norm(b) + + if x0 is None: + x = np.zeros(n) + beta = bnorm.copy() + else: + x = np.asarray(x0) + u = u - A.matvec(x) + beta = np.linalg.norm(u) + + if beta > 0: + u = (1/beta) * u + v = A.rmatvec(u) + alfa = np.linalg.norm(v) + else: + v = x.copy() + alfa = 0 + + if alfa > 0: + v = (1/alfa) * v + w = v.copy() + + rhobar = alfa + phibar = beta + rnorm = beta + r1norm = rnorm + r2norm = rnorm + + # Reverse the order here from the original matlab code because + # there was an error on return when arnorm==0 + arnorm = alfa * beta + if arnorm == 0: + if show: + print(msg[0]) + return x, istop, itn, r1norm, r2norm, anorm, acond, arnorm, xnorm, var + + head1 = ' Itn x[0] r1norm r2norm ' + head2 = ' Compatible LS Norm A Cond A' + + if show: + print(' ') + print(head1, head2) + test1 = 1 + test2 = alfa / beta + str1 = f'{itn:6g} {x[0]:12.5e}' + str2 = f' {r1norm:10.3e} {r2norm:10.3e}' + str3 = f' {test1:8.1e} {test2:8.1e}' + print(str1, str2, str3) + + # Main iteration loop. + while itn < iter_lim: + itn = itn + 1 + # Perform the next step of the bidiagonalization to obtain the + # next beta, u, alfa, v. These satisfy the relations + # beta*u = a@v - alfa*u, + # alfa*v = A'@u - beta*v. + u = A.matvec(v) - alfa * u + beta = np.linalg.norm(u) + + if beta > 0: + u = (1/beta) * u + anorm = sqrt(anorm**2 + alfa**2 + beta**2 + dampsq) + v = A.rmatvec(u) - beta * v + alfa = np.linalg.norm(v) + if alfa > 0: + v = (1 / alfa) * v + + # Use a plane rotation to eliminate the damping parameter. + # This alters the diagonal (rhobar) of the lower-bidiagonal matrix. + if damp > 0: + rhobar1 = sqrt(rhobar**2 + dampsq) + cs1 = rhobar / rhobar1 + sn1 = damp / rhobar1 + psi = sn1 * phibar + phibar = cs1 * phibar + else: + # cs1 = 1 and sn1 = 0 + rhobar1 = rhobar + psi = 0. + + # Use a plane rotation to eliminate the subdiagonal element (beta) + # of the lower-bidiagonal matrix, giving an upper-bidiagonal matrix. + cs, sn, rho = _sym_ortho(rhobar1, beta) + + theta = sn * alfa + rhobar = -cs * alfa + phi = cs * phibar + phibar = sn * phibar + tau = sn * phi + + # Update x and w. + t1 = phi / rho + t2 = -theta / rho + dk = (1 / rho) * w + + x = x + t1 * w + w = v + t2 * w + ddnorm = ddnorm + np.linalg.norm(dk)**2 + + if calc_var: + var = var + dk**2 + + # Use a plane rotation on the right to eliminate the + # super-diagonal element (theta) of the upper-bidiagonal matrix. + # Then use the result to estimate norm(x). + delta = sn2 * rho + gambar = -cs2 * rho + rhs = phi - delta * z + zbar = rhs / gambar + xnorm = sqrt(xxnorm + zbar**2) + gamma = sqrt(gambar**2 + theta**2) + cs2 = gambar / gamma + sn2 = theta / gamma + z = rhs / gamma + xxnorm = xxnorm + z**2 + + # Test for convergence. + # First, estimate the condition of the matrix Abar, + # and the norms of rbar and Abar'rbar. + acond = anorm * sqrt(ddnorm) + res1 = phibar**2 + res2 = res2 + psi**2 + rnorm = sqrt(res1 + res2) + arnorm = alfa * abs(tau) + + # Distinguish between + # r1norm = ||b - Ax|| and + # r2norm = rnorm in current code + # = sqrt(r1norm^2 + damp^2*||x - x0||^2). + # Estimate r1norm from + # r1norm = sqrt(r2norm^2 - damp^2*||x - x0||^2). + # Although there is cancellation, it might be accurate enough. + if damp > 0: + r1sq = rnorm**2 - dampsq * xxnorm + r1norm = sqrt(abs(r1sq)) + if r1sq < 0: + r1norm = -r1norm + else: + r1norm = rnorm + r2norm = rnorm + + # Now use these norms to estimate certain other quantities, + # some of which will be small near a solution. + test1 = rnorm / bnorm + test2 = arnorm / (anorm * rnorm + eps) + test3 = 1 / (acond + eps) + t1 = test1 / (1 + anorm * xnorm / bnorm) + rtol = btol + atol * anorm * xnorm / bnorm + + # The following tests guard against extremely small values of + # atol, btol or ctol. (The user may have set any or all of + # the parameters atol, btol, conlim to 0.) + # The effect is equivalent to the normal tests using + # atol = eps, btol = eps, conlim = 1/eps. + if itn >= iter_lim: + istop = 7 + if 1 + test3 <= 1: + istop = 6 + if 1 + test2 <= 1: + istop = 5 + if 1 + t1 <= 1: + istop = 4 + + # Allow for tolerances set by the user. + if test3 <= ctol: + istop = 3 + if test2 <= atol: + istop = 2 + if test1 <= rtol: + istop = 1 + + if show: + # See if it is time to print something. + prnt = False + if n <= 40: + prnt = True + if itn <= 10: + prnt = True + if itn >= iter_lim-10: + prnt = True + # if itn%10 == 0: prnt = True + if test3 <= 2*ctol: + prnt = True + if test2 <= 10*atol: + prnt = True + if test1 <= 10*rtol: + prnt = True + if istop != 0: + prnt = True + + if prnt: + str1 = f'{itn:6g} {x[0]:12.5e}' + str2 = f' {r1norm:10.3e} {r2norm:10.3e}' + str3 = f' {test1:8.1e} {test2:8.1e}' + str4 = f' {anorm:8.1e} {acond:8.1e}' + print(str1, str2, str3, str4) + + if istop != 0: + break + + # End of iteration loop. + # Print the stopping condition. + if show: + print(' ') + print('LSQR finished') + print(msg[istop]) + print(' ') + str1 = f'istop ={istop:8g} r1norm ={r1norm:8.1e}' + str2 = f'anorm ={anorm:8.1e} arnorm ={arnorm:8.1e}' + str3 = f'itn ={itn:8g} r2norm ={r2norm:8.1e}' + str4 = f'acond ={acond:8.1e} xnorm ={xnorm:8.1e}' + print(str1 + ' ' + str2) + print(str3 + ' ' + str4) + print(' ') + + return x, istop, itn, r1norm, r2norm, anorm, acond, arnorm, xnorm, var diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/minres.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/minres.py new file mode 100644 index 0000000000000000000000000000000000000000..bf6fdd2b333c4bbf38d636243495c1f7559bae5d --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/minres.py @@ -0,0 +1,379 @@ +from numpy import inner, vdot, iscomplexobj, zeros, inf, finfo +from numpy.linalg import norm +from math import sqrt + +from scipy._lib._array_api import xp_capabilities + +from .utils import make_system + +__all__ = ['minres'] + + +@xp_capabilities(np_only=True) +def minres(A, b, x0=None, *, rtol=1e-5, shift=0.0, maxiter=None, + M=None, callback=None, show=False, check=False): + """ + Solve ``Ax = b`` with the MINimum RESidual method, + for a real symmetric or complex hermitian matrix `A`. + + MINRES minimizes ``norm(Ax - b)`` for a real symmetric or complex hermitian + matrix `A`. Unlike the Conjugate Gradient method, `A` can be indefinite + or singular. + + If ``shift != 0`` then the method solves ``(A - shift*I)x = b``. + + Parameters + ---------- + A : {sparse array, ndarray, LinearOperator} + The real symmetric or complex hermitian N-by-N matrix of the + linear system. Alternatively, `A` can be a linear operator which can + produce ``Ax`` using, e.g., + ``scipy.sparse.linalg.LinearOperator``. + b : ndarray + Right hand side of the linear system. Has shape (N,) or (N,1). + + Returns + ------- + x : ndarray + The converged solution. + info : int + Provides convergence information: + 0 : successful exit + >0 : convergence to tolerance not achieved, number of iterations + <0 : illegal input or breakdown + + Other Parameters + ---------------- + x0 : ndarray + Starting guess for the solution. + rtol : float + Tolerance to achieve. The algorithm terminates when the relative + residual is below ``rtol``. + shift : float + Value to apply to the system ``(A - shift * I)x = b``. Default is 0. + maxiter : int + Maximum number of iterations. Iteration will stop after maxiter + steps even if the specified tolerance has not been achieved. + M : {sparse array, ndarray, LinearOperator} + Preconditioner for `A`. The preconditioner should approximate the + inverse of `A`. Effective preconditioning dramatically improves the + rate of convergence, which implies that fewer iterations are needed + to reach a given error tolerance. + callback : function + User-supplied function to call after each iteration. It is called + as ``callback(xk)``, where ``xk`` is the current solution vector. + show : bool + If ``True``, print out a summary and metrics related to the solution + during iterations. Default is ``False``. + check : bool + If ``True``, run additional input validation to check that `A` and + `M` (if specified) are symmetric. Default is ``False``. + + Notes + ----- + This file is a translation of the MATLAB implementation [2]_. + + References + ---------- + .. [1] Solution of sparse indefinite systems of linear equations, + C. C. Paige and M. A. Saunders (1975), + SIAM J. Numer. Anal. 12(4), pp. 617-629. + https://web.stanford.edu/group/SOL/software/minres/ + .. [2] https://web.stanford.edu/group/SOL/software/minres/minres-matlab.zip + + Examples + -------- + >>> import numpy as np + >>> from scipy.sparse import csc_array + >>> from scipy.sparse.linalg import minres + >>> A = csc_array([[3, 2, 0], [1, -1, 0], [0, 5, 1]], dtype=float) + >>> A = A + A.T + >>> b = np.array([2, 4, -1], dtype=float) + >>> x, exitCode = minres(A, b) + >>> print(exitCode) # 0 indicates successful convergence + 0 + >>> np.allclose(A.dot(x), b) + True + """ + A, M, x, b = make_system(A, M, x0, b) + + dotprod = vdot if iscomplexobj(A) else inner + matvec = A.matvec + psolve = M.matvec + + first = 'Enter minres. ' + last = 'Exit minres. ' + + n = A.shape[0] + + if maxiter is None: + maxiter = 5 * n + + msg = [' beta2 = 0. If M = I, b and x are eigenvectors ', # -1 + ' beta1 = 0. The exact solution is x0 ', # 0 + ' A solution to Ax = b was found, given rtol ', # 1 + ' A least-squares solution was found, given rtol ', # 2 + ' Reasonable accuracy achieved, given eps ', # 3 + ' x has converged to an eigenvector ', # 4 + ' acond has exceeded 0.1/eps ', # 5 + ' The iteration limit was reached ', # 6 + ' A does not define a symmetric matrix ', # 7 + ' M does not define a symmetric matrix ', # 8 + ' M does not define a pos-def preconditioner '] # 9 + + if show: + print(first + 'Solution of symmetric Ax = b') + print(first + f'n = {n:3g} shift = {shift:23.14e}') + print(first + f'itnlim = {maxiter:3g} rtol = {rtol:11.2e}') + print() + + istop = 0 + itn = 0 + Anorm = 0 + Acond = 0 + rnorm = 0 + ynorm = 0 + + xtype = x.dtype + + eps = finfo(xtype).eps + + # Set up y and v for the first Lanczos vector v1. + # y = beta1 P' v1, where P = C**(-1). + # v is really P' v1. + + if x0 is None: + r1 = b.copy() + else: + r1 = b - A@x + y = psolve(r1) + + beta1 = dotprod(r1, y) + + if beta1 < 0: + raise ValueError('indefinite preconditioner') + elif beta1 == 0: + return (x, 0) + + bnorm = norm(b) + if bnorm == 0: + x = b + return (x, 0) + + beta1 = sqrt(beta1.real) + + if check: + # are these too strict? + + # see if A is symmetric + w = matvec(y) + r2 = matvec(w) + s = dotprod(w,w) + t = dotprod(y,r2) + z = abs(s - t) + epsa = (s + eps) * eps**(1.0/3.0) + if z > epsa: + raise ValueError('non-symmetric matrix') + + # see if M is symmetric + r2 = psolve(y) + s = dotprod(y,y) + t = dotprod(r1,r2) + z = abs(s - t) + epsa = (s + eps) * eps**(1.0/3.0) + if z > epsa: + raise ValueError('non-symmetric preconditioner') + + # Initialize other quantities + oldb = 0 + beta = beta1 + dbar = 0 + epsln = 0 + qrnorm = beta1 + phibar = beta1 + rhs1 = beta1 + rhs2 = 0 + tnorm2 = 0 + gmax = 0 + gmin = finfo(xtype).max + cs = -1 + sn = 0 + w = zeros(n, dtype=xtype) + w2 = zeros(n, dtype=xtype) + r2 = r1 + + if show: + print() + print() + print(' Itn x(1) Compatible LS norm(A) cond(A) gbar/|A|') + + while itn < maxiter: + itn += 1 + + s = 1.0/beta + v = s*y + + y = matvec(v) + y = y - shift * v + + if itn >= 2: + y = y - (beta/oldb)*r1 + + alfa = dotprod(v,y) + y = y - (alfa/beta)*r2 + r1 = r2 + r2 = y + y = psolve(r2) + oldb = beta + beta = dotprod(r2,y) + if beta < 0: + raise ValueError('non-symmetric matrix') + beta = sqrt(beta.real) + tnorm2 += alfa**2 + oldb**2 + beta**2 + + if itn == 1: + if beta/beta1 <= 10*eps: + istop = -1 # Terminate later + + # Apply previous rotation Qk-1 to get + # [deltak epslnk+1] = [cs sn][dbark 0 ] + # [gbar k dbar k+1] [sn -cs][alfak betak+1]. + + oldeps = epsln + delta = cs * dbar + sn * alfa # delta1 = 0 deltak + gbar = sn * dbar - cs * alfa # gbar 1 = alfa1 gbar k + epsln = sn * beta # epsln2 = 0 epslnk+1 + dbar = - cs * beta # dbar 2 = beta2 dbar k+1 + root = norm([gbar, dbar]) + Arnorm = phibar * root + + # Compute the next plane rotation Qk + + gamma = norm([gbar, beta]).astype(xtype) # gammak + gamma = max(gamma, eps) + cs = gbar / gamma # ck + sn = beta / gamma # sk + phi = cs * phibar # phik + phibar = sn * phibar # phibark+1 + + # Update x. + + denom = 1.0/gamma + w1 = w2 + w2 = w + w = (v - oldeps*w1 - delta*w2) * denom + x = x + phi*w + + # Go round again. + + gmax = max(gmax, gamma) + gmin = min(gmin, gamma) + z = rhs1 / gamma + rhs1 = rhs2 - delta*z + rhs2 = - epsln*z + + # Estimate various norms and test for convergence. + + Anorm = sqrt(tnorm2.real) + ynorm = norm(x) + epsa = Anorm * eps + epsx = Anorm * ynorm * eps + epsr = Anorm * ynorm * rtol + diag = gbar + + if diag == 0: + diag = epsa + + qrnorm = phibar + rnorm = qrnorm + if ynorm == 0 or Anorm == 0: + test1 = inf + else: + test1 = rnorm / (Anorm*ynorm) # ||r|| / (||A|| ||x||) + if Anorm == 0: + test2 = inf + else: + test2 = root / Anorm # ||Ar|| / (||A|| ||r||) + + # Estimate cond(A). + # In this version we look at the diagonals of R in the + # factorization of the lower Hessenberg matrix, Q @ H = R, + # where H is the tridiagonal matrix from Lanczos with one + # extra row, beta(k+1) e_k^T. + + Acond = gmax/gmin + + # See if any of the stopping criteria are satisfied. + # In rare cases, istop is already -1 from above (Abar = const*I). + + if istop == 0: + t1 = 1 + test1 # These tests work if rtol < eps + t2 = 1 + test2 + if t2 <= 1: + istop = 2 + if t1 <= 1: + istop = 1 + + if itn >= maxiter: + istop = 6 + if Acond >= 0.1/eps: + istop = 4 + if epsx >= beta1: + istop = 3 + # if rnorm <= epsx : istop = 2 + # if rnorm <= epsr : istop = 1 + if test2 <= rtol: + istop = 2 + if test1 <= rtol: + istop = 1 + + # See if it is time to print something. + + prnt = False + if n <= 40: + prnt = True + if itn <= 10: + prnt = True + if itn >= maxiter-10: + prnt = True + if itn % 10 == 0: + prnt = True + if qrnorm <= 10*epsx: + prnt = True + if qrnorm <= 10*epsr: + prnt = True + if Acond <= 1e-2/eps: + prnt = True + if istop != 0: + prnt = True + + if show and prnt: + str1 = f'{itn:6g} {x[0]:12.5e} {test1:10.3e}' + str2 = f' {test2:10.3e}' + str3 = f' {Anorm:8.1e} {Acond:8.1e} {gbar/Anorm:8.1e}' + + print(str1 + str2 + str3) + + if itn % 10 == 0: + print() + + if callback is not None: + callback(x) + + if istop != 0: + break # TODO check this + + if show: + print() + print(last + f' istop = {istop:3g} itn ={itn:5g}') + print(last + f' Anorm = {Anorm:12.4e} Acond = {Acond:12.4e}') + print(last + f' rnorm = {rnorm:12.4e} ynorm = {ynorm:12.4e}') + print(last + f' Arnorm = {Arnorm:12.4e}') + print(last + msg[istop+1]) + + if istop == 6: + info = maxiter + else: + info = 0 + + return (x,info) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/tests/__init__.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/tests/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/tests/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7b0a275c5403434c6fc5934e66a74d333eb2458c Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/tests/__pycache__/__init__.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/tests/__pycache__/test_gcrotmk.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/tests/__pycache__/test_gcrotmk.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a6ef554cf265c9802bbc0f15b36de90f3f0a0520 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/tests/__pycache__/test_gcrotmk.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/tests/__pycache__/test_iterative.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/tests/__pycache__/test_iterative.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5dada84eb883baef0e2b35f49d1cf2bbfdcc9d8c Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/tests/__pycache__/test_iterative.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/tests/__pycache__/test_lgmres.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/tests/__pycache__/test_lgmres.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cffe5d46fa736665c58171b518ab38ab1bb153d7 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/tests/__pycache__/test_lgmres.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/tests/__pycache__/test_lsmr.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/tests/__pycache__/test_lsmr.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..19e2b40de5048a0b538277d1c6ca972ebefadb12 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/tests/__pycache__/test_lsmr.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/tests/__pycache__/test_lsqr.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/tests/__pycache__/test_lsqr.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a958f6a40e750ed3c3a03c3ed906a5288694689f Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/tests/__pycache__/test_lsqr.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/tests/__pycache__/test_minres.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/tests/__pycache__/test_minres.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..abb232da000edeccc6b0a10ca29f2318849548cd Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/tests/__pycache__/test_minres.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/tests/__pycache__/test_utils.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/tests/__pycache__/test_utils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..49a1a4fe0b88cb69bfbee7a36a28d37310cca7b3 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/tests/__pycache__/test_utils.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/tests/test_gcrotmk.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/tests/test_gcrotmk.py new file mode 100644 index 0000000000000000000000000000000000000000..25fb61d1c06adc06d93b115d6cbcdb6a986d4459 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/tests/test_gcrotmk.py @@ -0,0 +1,189 @@ +#!/usr/bin/env python +"""Tests for the linalg._isolve.gcrotmk module +""" +import threading +import warnings + +from numpy.testing import (assert_, assert_allclose, assert_equal) + +import numpy as np +from numpy import zeros, array, allclose +from scipy.linalg import norm +from scipy.sparse import csr_array, eye_array, random_array + +from scipy.sparse.linalg._interface import LinearOperator +from scipy.sparse.linalg import splu +from scipy.sparse.linalg._isolve import gcrotmk, gmres + + +Am = csr_array(array([[-2,1,0,0,0,9], + [1,-2,1,0,5,0], + [0,1,-2,1,0,0], + [0,0,1,-2,1,0], + [0,3,0,1,-2,1], + [1,0,0,0,1,-2]])) +b = array([1,2,3,4,5,6]) +count = threading.local() # [0] +niter = threading.local() # [0] + + +def matvec(v): + if not hasattr(count, 'c'): + count.c = [0] + count.c[0] += 1 + return Am@v + + +def cb(v): + if not hasattr(niter, 'n'): + niter.n = [0] + niter.n[0] += 1 + + +A = LinearOperator(matvec=matvec, shape=Am.shape, dtype=Am.dtype) + + +def do_solve(**kw): + if not hasattr(niter, 'n'): + niter.n = [0] + + if not hasattr(count, 'c'): + count.c = [0] + + count.c[0] = 0 + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", ".*called without specifying.*", DeprecationWarning) + x0, flag = gcrotmk(A, b, x0=zeros(A.shape[0]), rtol=1e-14, **kw) + count_0 = count.c[0] + assert_(allclose(A@x0, b, rtol=1e-12, atol=1e-12), norm(A@x0-b)) + return x0, count_0 + + +class TestGCROTMK: + def test_preconditioner(self): + # Check that preconditioning works + pc = splu(Am.tocsc()) + M = LinearOperator(matvec=pc.solve, shape=A.shape, dtype=A.dtype) + + x0, count_0 = do_solve() + niter.n[0] = 0 + x1, count_1 = do_solve(M=M, callback=cb) + + assert_equal(count_1, 3) + assert count_1 < count_0/2 + assert allclose(x1, x0, rtol=1e-14) + assert niter.n[0] < 3 + + def test_arnoldi(self): + rng = np.random.default_rng(1) + + A = eye_array(2000) + random_array((2000, 2000), density=5e-4, rng=rng) + b = rng.random(2000) + + # The inner arnoldi should be equivalent to gmres + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", ".*called without specifying.*", DeprecationWarning) + x0, flag0 = gcrotmk(A, b, x0=zeros(A.shape[0]), m=10, k=0, maxiter=1) + x1, flag1 = gmres(A, b, x0=zeros(A.shape[0]), restart=10, maxiter=1) + + assert_equal(flag0, 1) + assert_equal(flag1, 1) + assert np.linalg.norm(A.dot(x0) - b) > 1e-4 + + assert_allclose(x0, x1) + + def test_cornercase(self): + np.random.seed(1234) + + # Rounding error may prevent convergence with tol=0 --- ensure + # that the return values in this case are correct, and no + # exceptions are raised + + for n in [3, 5, 10, 100]: + A = 2*eye_array(n) + + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", ".*called without specifying.*", DeprecationWarning) + b = np.ones(n) + x, info = gcrotmk(A, b, maxiter=10) + assert_equal(info, 0) + assert_allclose(A.dot(x) - b, 0, atol=1e-14) + + x, info = gcrotmk(A, b, rtol=0, maxiter=10) + if info == 0: + assert_allclose(A.dot(x) - b, 0, atol=1e-14) + + b = np.random.rand(n) + x, info = gcrotmk(A, b, maxiter=10) + assert_equal(info, 0) + assert_allclose(A.dot(x) - b, 0, atol=1e-14) + + x, info = gcrotmk(A, b, rtol=0, maxiter=10) + if info == 0: + assert_allclose(A.dot(x) - b, 0, atol=1e-14) + + def test_nans(self): + A = eye_array(3, format='lil') + A[1,1] = np.nan + b = np.ones(3) + + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", ".*called without specifying.*", DeprecationWarning) + x, info = gcrotmk(A, b, rtol=0, maxiter=10) + assert_equal(info, 1) + + def test_truncate(self): + np.random.seed(1234) + A = np.random.rand(30, 30) + np.eye(30) + b = np.random.rand(30) + + for truncate in ['oldest', 'smallest']: + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", ".*called without specifying.*", DeprecationWarning) + x, info = gcrotmk(A, b, m=10, k=10, truncate=truncate, + rtol=1e-4, maxiter=200) + assert_equal(info, 0) + assert_allclose(A.dot(x) - b, 0, atol=1e-3) + + def test_CU(self): + for discard_C in (True, False): + # Check that C,U behave as expected + CU = [] + x0, count_0 = do_solve(CU=CU, discard_C=discard_C) + assert_(len(CU) > 0) + assert_(len(CU) <= 6) + + if discard_C: + for c, u in CU: + assert_(c is None) + + # should converge immediately + x1, count_1 = do_solve(CU=CU, discard_C=discard_C) + if discard_C: + assert_equal(count_1, 2 + len(CU)) + else: + assert_equal(count_1, 3) + assert_(count_1 <= count_0/2) + assert_allclose(x1, x0, atol=1e-14) + + def test_denormals(self): + # Check that no warnings are emitted if the matrix contains + # numbers for which 1/x has no float representation, and that + # the solver behaves properly. + A = np.array([[1, 2], [3, 4]], dtype=float) + A *= 100 * np.nextafter(0, 1) + + b = np.array([1, 1]) + + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", ".*called without specifying.*", DeprecationWarning) + xp, info = gcrotmk(A, b) + + if info == 0: + assert_allclose(A.dot(xp), b) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/tests/test_iterative.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/tests/test_iterative.py new file mode 100644 index 0000000000000000000000000000000000000000..496aa9d3ff2256766955bb1ca2dcaaca0a5ddc25 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/tests/test_iterative.py @@ -0,0 +1,930 @@ +""" Test functions for the sparse.linalg._isolve module +""" + +from contextlib import nullcontext +import itertools +import platform +import pytest +import types + +import numpy as np +from numpy.testing import assert_allclose +from numpy import zeros, arange, array, ones, eye, iscomplexobj + +from scipy._lib._array_api import ( + is_numpy, make_xp_pytest_param, make_xp_pytest_marks, + xp_assert_close, xp_assert_less, xp_vector_norm, xp_assert_equal, + xp_assert_less_equal, +) +from scipy._external import array_api_extra as xpx +from scipy._lib._sparse import issparse +from scipy.sparse import dia_array, csr_array, kronsum + +from scipy.sparse.linalg import LinearOperator, aslinearoperator +from scipy.sparse.linalg._isolve import (bicg, bicgstab, cg, cgs, + gcrotmk, gmres, lgmres, + minres, qmr, tfqmr) +from scipy.sparse.linalg._interface import IdentityOperator + +# TODO check that method preserve shape and type +# TODO test both preconditioner methods + + +# list of all solvers under test +_SOLVERS = [bicg, bicgstab, cg, cgs, gcrotmk, gmres, lgmres, + minres, qmr, tfqmr] + +CB_TYPE_FILTER = ".*called without specifying `callback_type`.*" + + +def _assert_success(*, A, x, b, xp, rtol=1.0, atol=0.0, less_equal=False): + Ax = xp.squeeze(A @ x[..., xp.newaxis], axis=-1) + residual = Ax - b + err = xp_vector_norm(residual, axis=-1) + assertion = xp_assert_less_equal if less_equal else xp_assert_less + limit = atol + rtol * xp_vector_norm(b, axis=-1) + assertion(err, limit) + + +# create parametrized fixture for easy reuse in tests +@pytest.fixture( + scope="session", + params=[make_xp_pytest_param(func) for func in _SOLVERS], +) +def solver(request): + """ + Fixture for all solvers in scipy.sparse.linalg._isolve + """ + return request.param + + +class Case: + def __init__(self, name, A, b=None, skip=None, nonconvergence=None): + self.name = name + self.A = A + if b is None: + self.b = arange(A.shape[0], dtype=A.dtype) + else: + self.b = b + if skip is None: + self.skip = [] + else: + self.skip = skip + if nonconvergence is None: + self.nonconvergence = [] + else: + self.nonconvergence = nonconvergence + + +class SingleTest: + # case with a specific solver + def __init__(self, A, b, solver, casename, convergence=True): + self.A = A + self.b = b + self.solver = solver + self.casename = casename + self.name = casename + '-' + solver.__name__ + self.convergence = convergence + + def __repr__(self): + return f"<{self.name}>" + + +def xp_case(case, xp, batch_A, batch_b, rng=None): + sparse = issparse(case.A) + + if (not_np := not is_numpy(xp)) and sparse: # TODO: pydata/sparse for sparse tests? + pytest.skip("sparse tests run only with `np` backend") + + A = xp.asarray(case.A) if not_np else case.A + b = xp.asarray(case.b) if not_np else case.b + + rng = np.random.default_rng(rng) + if sparse and (batch_A or batch_b): + A = A.tocoo() + if batch_A: + # apply some random scaling to each system in batch, + # preserving symmetry, (non-)positive definiteness, Hermiticity + scaling = np.abs(rng.standard_normal(batch_A + (1, 1))) + batch_array = xp.asarray(scaling, dtype=A.dtype) + A = batch_array * A + if batch_b: + # apply some random scaling to each RHS in batch + b = b * xp.asarray(rng.random(batch_b + (1,)), dtype=b.dtype) + + return types.SimpleNamespace( + name=case.name, + A=A, + b=b, + convergence=case.convergence, + solver=case.solver, + casename=case.casename, + ) + + +class IterativeParams: + def __init__(self): + sym_solvers = [minres, cg] + posdef_solvers = [cg] + real_solvers = [minres] + + # list of Cases + self.cases = [] + + # Symmetric and Positive Definite + N = 40 + data = ones((3, N)) + data[0, :] = 2 + data[1, :] = -1 + data[2, :] = -1 + Poisson1D = dia_array((data, [0, -1, 1]), shape=(N, N)).tocsr() + self.cases.append(Case("poisson1d", Poisson1D)) + # note: minres fails for single precision + self.cases.append(Case("poisson1d-F", Poisson1D.astype(np.float32), + skip=[minres])) + + # Symmetric and Negative Definite + self.cases.append(Case("neg-poisson1d", -Poisson1D, + skip=posdef_solvers)) + # note: minres fails for single precision + self.cases.append(Case("neg-poisson1d-F", (-Poisson1D).astype('f'), + skip=posdef_solvers + [minres])) + + # 2-dimensional Poisson equations + Poisson2D = kronsum(Poisson1D, Poisson1D) + # note: minres fails for 2-d poisson problem, + # it will be fixed in the future PR + self.cases.append(Case("poisson2d", Poisson2D, skip=[minres])) + # note: minres fails for single precision + self.cases.append(Case("poisson2d-F", Poisson2D.astype('f'), + skip=[minres])) + + # Symmetric and Indefinite + data = array([[6, -5, 2, 7, -1, 10, 4, -3, -8, 9]], dtype='d') + RandDiag = dia_array((data, [0]), shape=(10, 10)).tocsr() + self.cases.append(Case("rand-diag", RandDiag, skip=posdef_solvers)) + self.cases.append(Case("rand-diag-F", RandDiag.astype('f'), + skip=posdef_solvers)) + + # Random real-valued + rng = np.random.RandomState(1234) + data = rng.rand(4, 4) + self.cases.append(Case("rand", data, + skip=posdef_solvers + sym_solvers)) + self.cases.append(Case("rand-F", data.astype('f'), + skip=posdef_solvers + sym_solvers)) + + # Random symmetric real-valued + rng = np.random.RandomState(1234) + data = rng.rand(4, 4) + data = data + data.T + self.cases.append(Case("rand-sym", data, skip=posdef_solvers)) + self.cases.append(Case("rand-sym-F", data.astype('f'), + skip=posdef_solvers)) + + # Random pos-def symmetric real + np.random.seed(1234) + data = np.random.rand(9, 9) + data = np.dot(data.conj(), data.T) + self.cases.append(Case("rand-sym-pd", data)) + # note: minres fails for single precision + self.cases.append(Case("rand-sym-pd-F", data.astype('f'), + skip=[minres])) + + # Random complex-valued + rng = np.random.RandomState(1234) + data = rng.rand(4, 4) + 1j * rng.rand(4, 4) + skip_cmplx = posdef_solvers + sym_solvers + real_solvers + self.cases.append(Case("rand-cmplx", data, skip=skip_cmplx)) + self.cases.append(Case("rand-cmplx-F", data.astype('F'), + skip=skip_cmplx)) + + # Random hermitian complex-valued + rng = np.random.RandomState(1234) + data = rng.rand(4, 4) + 1j * rng.rand(4, 4) + data = data + data.T.conj() + self.cases.append(Case("rand-cmplx-herm", data, + skip=posdef_solvers + real_solvers)) + self.cases.append(Case("rand-cmplx-herm-F", data.astype('F'), + skip=posdef_solvers + real_solvers)) + + # Random pos-def hermitian complex-valued + rng = np.random.RandomState(1234) + data = rng.rand(9, 9) + 1j * rng.rand(9, 9) + data = np.dot(data.conj(), data.T) + self.cases.append(Case( + "rand-cmplx-sym-pd", data, skip=real_solvers) + ) + self.cases.append(Case("rand-cmplx-sym-pd-F", data.astype('F'), + skip=real_solvers)) + + # Non-symmetric and Positive Definite + # + # cgs, qmr, bicg and tfqmr fail to converge on this one + # -- algorithmic limitation apparently + data = ones((2, 10)) + data[0, :] = 2 + data[1, :] = -1 + A = dia_array((data, [0, -1]), shape=(10, 10)).tocsr() + self.cases.append(Case("nonsymposdef", A, + skip=sym_solvers + [cgs, qmr, bicg, tfqmr])) + self.cases.append(Case("nonsymposdef-F", A.astype('F'), + skip=sym_solvers + [cgs, qmr, bicg, tfqmr])) + + # Symmetric, non-pd, hitting cgs/bicg/bicgstab/qmr/tfqmr breakdown + A = np.array([[0, 0, 0, 0, 0, 1, -1, -0, -0, -0, -0], + [0, 0, 0, 0, 0, 2, -0, -1, -0, -0, -0], + [0, 0, 0, 0, 0, 2, -0, -0, -1, -0, -0], + [0, 0, 0, 0, 0, 2, -0, -0, -0, -1, -0], + [0, 0, 0, 0, 0, 1, -0, -0, -0, -0, -1], + [1, 2, 2, 2, 1, 0, -0, -0, -0, -0, -0], + [-1, 0, 0, 0, 0, 0, -1, -0, -0, -0, -0], + [0, -1, 0, 0, 0, 0, -0, -1, -0, -0, -0], + [0, 0, -1, 0, 0, 0, -0, -0, -1, -0, -0], + [0, 0, 0, -1, 0, 0, -0, -0, -0, -1, -0], + [0, 0, 0, 0, -1, 0, -0, -0, -0, -0, -1]], dtype=float) + b = np.array([0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0], dtype=float) + assert (A == A.T).all() + self.cases.append(Case("sym-nonpd", A, b, + skip=posdef_solvers, + nonconvergence=[cgs, bicg, bicgstab, qmr, tfqmr] + ) + ) + + def generate_tests(self): + # generate test cases with skips applied + tests = [] + for case in self.cases: + for solver in _SOLVERS: + if (solver in case.skip): + continue + if solver in case.nonconvergence: + tests += [SingleTest(case.A, case.b, solver, case.name, + convergence=False)] + else: + tests += [ + SingleTest(case.A, case.b, solver, case.name) + ] + return tests + + +cases = IterativeParams().generate_tests() + + +@pytest.fixture( + params=[pytest.param(c, marks=make_xp_pytest_marks(c.solver)) for c in cases], + ids=[x.name for x in cases], + scope="module" +) +def case(request): + """ + Fixture for all cases in IterativeParams + """ + return request.param + + +@pytest.mark.parametrize("batch_A", [()]) +@pytest.mark.parametrize("batch_b", [()]) +def test_maxiter(case, xp, batch_A, batch_b): + case = xp_case(case, xp, batch_A, batch_b, rng=38) + if not case.convergence: + pytest.skip("Solver - Breakdown case, see gh-8829") + A = case.A + rtol = 1e-12 + + b = case.b + x0 = 0 * b + + residuals = [] + + def callback(x): + if x.ndim == 0: + residuals.append(xp_vector_norm(b - case.A * x)) + else: + Ax = xp.squeeze(case.A @ x[..., xp.newaxis], axis=-1) + residuals.append(xp_vector_norm(b - Ax, axis=-1)) + + if case.solver == gmres: + with pytest.warns(DeprecationWarning, match=CB_TYPE_FILTER): + x, info = case.solver(A, b, x0=x0, rtol=rtol, maxiter=1, callback=callback) + else: + x, info = case.solver(A, b, x0=x0, rtol=rtol, maxiter=1, callback=callback) + + assert len(residuals) == 1 + assert info == 1 + + +@pytest.mark.parametrize("batch_A", [()]) +@pytest.mark.parametrize("batch_b", [()]) +def test_convergence(case, xp, batch_A, batch_b): + if (case.solver is tfqmr) and ("poisson2d-F" in case.name): + pytest.skip("Struggles to converge with single precision on some platforms") + case = xp_case(case, xp, batch_A, batch_b, rng=38) + A = case.A + + if A.dtype in (xp.float64, xp.complex128): + rtol = 1e-8 + else: + rtol = 1e-2 + + b = case.b + x0 = 0 * b + + x, info = case.solver(A, b, x0=x0, rtol=rtol) + xp_assert_equal(x0, 0 * b) # ensure that x0 is not overwritten + + if case.convergence: + assert info == 0 + _assert_success(A=A, x=x, b=b, xp=xp, rtol=rtol) + else: + assert info != 0 + _assert_success(A=A, x=x, b=b, xp=xp, rtol=1, less_equal=True) + + +@pytest.mark.parametrize("batch_A", [()]) +@pytest.mark.parametrize("batch_b", [()]) +def test_precond_dummy(case, xp, batch_A, batch_b): + dtype = case.A.dtype + case = xp_case(case, xp, batch_A, batch_b) + if (case.solver is cgs) and ("pd-F" in case.name): + pytest.skip("Struggles to converge with single precision") + if (case.solver is tfqmr) and ("poisson2d-F" in case.name): + pytest.skip("Hits divide-by-zero with single precision") + if not case.convergence: + pytest.skip("Solver - Breakdown case, see gh-8829") + + rtol = 1e-8 if np.finfo(dtype).eps < 1e-8 else 1.2e-3 + + A = case.A + + # NOTE: the following was previously uncommented as dead code -- + # was the intention to set `A = dia_array(...)`? + + # _, M, N = A.shape + # # Ensure the diagonal elements of A are non-zero before calculating + # # 1.0 / xp.linalg.diagonal(A) + # diagOfA = xp.linalg.diagonal(A) if not is_numpy(xp) else A.diagonal() + # if xp.count_nonzero(diagOfA) == diagOfA.shape[0]: + # dia_array(([1.0 / diagOfA], [0]), shape=(M, N)) + + b = case.b + x0 = 0 * b + + precond = IdentityOperator(shape=A.shape) + + if case.solver is qmr: + x, info = case.solver(A, b, M1=precond, M2=precond, x0=x0, rtol=rtol) + else: + x, info = case.solver(A, b, M=precond, x0=x0, rtol=rtol) + + assert info == 0 + _assert_success(A=A, x=x, b=b, xp=np, rtol=rtol) + + A = aslinearoperator(A) + + x, info = case.solver(A, b, x0=x0, rtol=rtol) + assert info == 0 + _assert_success(A=A, x=x, b=b, xp=np, rtol=rtol) + + +@pytest.mark.parametrize("batch_A", [()]) +@pytest.mark.parametrize("batch_b", [()]) +@pytest.mark.fail_slow(10) +def test_precond_inverse(case, xp, batch_A, batch_b): + if case.casename not in ('poisson1d', 'poisson2d'): + pytest.skip("specific to poisson1d and poisson2d cases") + if case.solver is qmr: + pytest.skip("skipped for qmr") + + case = xp_case(case, xp, batch_A, batch_b, rng=38) + rtol = 1e-8 + + def inverse(b, which=None): + """inverse preconditioner""" + A = case.A + if is_numpy(xp) and not isinstance(A, np.ndarray): + A = A.toarray() + return xp.linalg.solve(A, b[..., np.newaxis]) + + def rinverse(b, which=None): + """inverse preconditioner""" + A = case.A + if is_numpy(xp) and not isinstance(A, np.ndarray): + A = A.toarray() + return xp.linalg.solve(A.T, b[..., np.newaxis]) + + matvec_count = [0] + + def matvec(b): + matvec_count[0] += 1 + return case.A @ b[..., np.newaxis] + + def rmatvec(b): + matvec_count[0] += 1 + return case.A.T @ b[..., np.newaxis] + + b = case.b + x0 = 0 * b + + A = LinearOperator(case.A.shape, matvec, rmatvec=rmatvec) + precond = LinearOperator(case.A.shape, inverse, rmatvec=rinverse) + + # Solve with preconditioner + matvec_count = [0] + x, info = case.solver(A, b, M=precond, x0=x0, rtol=rtol) + + assert info == 0 + _assert_success(A=case.A, x=x, b=b, xp=xp, rtol=rtol) + + # Solution should be nearly instant + assert matvec_count[0] <= 3 + + +@pytest.mark.skip_xp_backends("dask.array", reason="https://github.com/dask/dask/issues/11711") +@pytest.mark.parametrize("batch_A", [()]) +@pytest.mark.parametrize("batch_b", [()]) +def test_atol(solver, xp, batch_A, batch_b): + # TODO: minres / tfqmr. It didn't historically use absolute tolerances, so + # fixing it is less urgent. + if solver in (minres, tfqmr): + pytest.skip("TODO: Add atol to minres/tfqmr") + + # Historically this is tested as below, all pass but for some reason + # gcrotmk is over-sensitive to difference between random.seed/rng.random + # Hence tol lower bound is changed from -10 to -9 + # np.random.seed(1234) + # A = np.random.rand(10, 10) + # A = A @ A.T + 10 * np.eye(10) + # b = 1e3*np.random.rand(10) + + rng = np.random.default_rng(168441431005389) + A = rng.uniform(size=(*batch_A, 10, 10)) + A = A @ A.mT + 10*np.eye(10) + b = 1e3 * rng.uniform(size=(*batch_b, 10)) + + dtype = xpx.default_dtype(xp) + A = xp.asarray(A, dtype=dtype) + b = xp.asarray(b, dtype=dtype) + + tols = np.r_[0, np.logspace(-9, 2, 7), np.inf] + + # Check effect of badly scaled preconditioners + M0 = rng.standard_normal(size=(*batch_A, 10, 10)) + M0 = xp.asarray(M0) + M0 = M0 @ M0.mT + Ms = [None, 1e-6 * M0, 1e6 * M0] + + for M, rtol, atol in itertools.product(Ms, tols, tols): + if rtol == 0 and atol == 0: + continue + + if solver is qmr: + if M is not None: + M = aslinearoperator(M) + M2 = IdentityOperator(shape=(*batch_A, 10, 10)) + else: + M2 = None + x, info = solver(A, b, M1=M, M2=M2, rtol=rtol, atol=atol) + else: + x, info = solver(A, b, M=M, rtol=rtol, atol=atol) + + assert info == 0 + _assert_success(A=A, x=x, b=b, xp=xp, rtol=rtol, atol=atol) + + +@pytest.mark.skip_xp_backends("dask.array", reason="https://github.com/dask/dask/issues/11711") +@pytest.mark.parametrize("batch_A", [()]) +@pytest.mark.parametrize("batch_b", [()]) +def test_zero_rhs(solver, xp, batch_A, batch_b): + rng = np.random.default_rng(1684414984100503) + dtype = xpx.default_dtype(xp) + A = xp.asarray(rng.random(size=(*batch_A, 10, 10)), dtype=dtype) + A = A @ A.mT + 10 * xp.eye(10) + + b = xp.zeros((*batch_b, 10), dtype=dtype) + tols = np.r_[np.logspace(-10, 2, 7)] + + expected = xp.broadcast_to(b, (*np.broadcast_shapes(batch_A, batch_b), 10)) + for tol in tols: + tol = float(tol) + x, info = solver(A, b, rtol=tol) + assert info == 0 + xp_assert_close(x, expected, atol=1e-15) + + x, info = solver(A, b, rtol=tol, x0=xp.ones((*batch_b, 10))) + assert info == 0 + xp_assert_close(x, expected, atol=tol) + + if solver is not minres: + x, info = solver(A, b, rtol=tol, atol=0.0, x0=xp.ones((*batch_b, 10))) + if info == 0: + xp_assert_close(x, expected) + + x, info = solver(A, b, rtol=tol, atol=tol) + assert info == 0 + xp_assert_close(x, expected, atol=1e-300) + + x, info = solver(A, b, rtol=tol, atol=0.0) + assert info == 0 + xp_assert_close(x, expected, atol=1e-300) + + +# unbatched test +@pytest.mark.xfail(reason="see gh-18697") +def test_maxiter_worsening(solver, xp): + if solver not in (gmres, lgmres, qmr): + # these were skipped from the very beginning, see gh-9201; gh-14160 + pytest.skip("Solver breakdown case") + # Check error does not grow (boundlessly) with increasing maxiter. + # This can occur due to the solvers hitting close to breakdown, + # which they should detect and halt as necessary. + # cf. gh-9100 + if (solver is lgmres and + platform.machine() not in ['x86_64' 'x86', 'aarch64', 'arm64']): + # see gh-17839 + pytest.xfail(reason="fails on at least ppc64le, ppc64 and riscv64") + + # Singular matrix, rhs numerically not in range + A = np.array([[-0.1112795288033378, 0, 0, 0.16127952880333685], + [0, -0.13627952880333782 + 6.283185307179586j, 0, 0], + [0, 0, -0.13627952880333782 - 6.283185307179586j, 0], + [0.1112795288033368, 0j, 0j, -0.16127952880333785]]) + v = np.ones(4) + dtype = xpx.default_dtype(xp) + A, v = (xp.asarray(arr, dtype=dtype) for arr in [A, v]) + best_error = np.inf + + # Unable to match the Fortran code tolerance levels with this example + # Original tolerance values + + # slack_tol = 7 if platform.machine() == 'aarch64' else 5 + slack_tol = 9 + + rtol = 1e-8 + for maxiter in range(1, 20): + x, info = solver(A, v, maxiter=maxiter, rtol=rtol, atol=0.0) + if info == 0: + _assert_success(A=A, x=x, b=v, xp=xp, rtol=rtol) + + # Check with slack + error = xp_vector_norm(A @ x - v) + best_error = xp.min(best_error, error) + assert error <= slack_tol * best_error + + +def _setup_random_system(xp, batch_A, batch_b): + rng = np.random.default_rng(1685363802304750) + n = 10 + A = rng.random(size=(*batch_A, n, n)) + A = A @ A.mT + b = rng.random((*batch_b, n)) + x0 = rng.random((*batch_b, n)) + dtype = xpx.default_dtype(xp) + A, b, x0 = (xp.asarray(arr, dtype=dtype) for arr in [A, b, x0]) + return A, b, x0 + + +@pytest.mark.parametrize("batch_A", [()]) +@pytest.mark.parametrize("batch_b", [()]) +def test_x0_working(solver, xp, batch_A, batch_b): + # Easy problem + A, b, x0 = _setup_random_system(xp, batch_A, batch_b) + + if solver is minres: + kw = dict(rtol=1e-6) + else: + kw = dict(atol=0.0, rtol=1e-6) + + x, info = solver(A, b, **kw) + assert info == 0 + + _assert_success(A=A, x=x, b=b, xp=xp, rtol=1e-6) + + x, info = solver(A, b, x0=x0, **kw) + assert info == 0 + _assert_success(A=A, x=x, b=b, xp=xp, rtol=1e-5) + + +@pytest.mark.parametrize("batch_A", [()]) +@pytest.mark.parametrize("batch_b", [()]) +def test_x0_equals_Mb(case, xp, batch_A, batch_b): + dtype = case.A.dtype + case = xp_case(case, xp, batch_A, batch_b, rng=38) + if (case.solver is cgs) and ("pd-F" in case.name): + pytest.skip("Struggles to converge with single precision") + if (case.solver is bicgstab) and (case.name == 'nonsymposdef-bicgstab'): + pytest.skip("Solver fails due to numerical noise " + "on some architectures (see gh-15533).") + if case.solver is tfqmr: + pytest.skip("Solver does not support x0='Mb'") + + A = case.A + b = case.b + x0 = 'Mb' + rtol = 1e-8 if np.finfo(dtype).eps < 1e-8 else 1.5e-3 + x, info = case.solver(A, b, x0=x0, rtol=rtol) + + assert x0 == 'Mb' # ensure that x0 is not overwritten + assert info == 0 + _assert_success(A=A, x=x, b=b, xp=xp, rtol=rtol) + + +@pytest.mark.parametrize("batch_A", [()]) +@pytest.mark.parametrize("batch_b", [()]) +def test_x0_solves_problem_exactly(solver, xp, batch_A, batch_b): + # See gh-19948 + dtype = xpx.default_dtype(xp) + mat = xp.tile(xp.eye(2, dtype=dtype), (*batch_A, 1, 1)) + rhs = xp.tile(xp.asarray([-1., -1.], dtype=dtype), (*batch_b, 1)) + + sol, info = solver(mat, rhs, x0=rhs) + expected = xp.broadcast_to(rhs, (*np.broadcast_shapes(batch_A, batch_b), 2)) + xp_assert_close(sol, expected) + assert info == 0 + + +@pytest.mark.parametrize("batch_A", [()]) +@pytest.mark.parametrize("batch_b", [()]) +def test_show(case, capsys, xp, batch_A, batch_b): + if case.solver != tfqmr: + pytest.skip("tfqmr specific test") + if "poisson2d-F" in case.name: + pytest.skip("Hits divide-by-zero with single precision") + if "pd-F" in case.name: + pytest.skip("Struggles to converge with single precision on some platforms") + case = xp_case(case, xp, batch_A, batch_b) + def cb(x): + pass + + ctx = ( + np.errstate(all='ignore') + if case.casename == "nonsymposdef" + else nullcontext() + ) + with ctx: + x, info = tfqmr(case.A, case.b, callback=cb, show=True) + + out, err = capsys.readouterr() + + if case.casename == "sym-nonpd": + # no logs for some reason + exp = "" + elif case.casename in ("nonsymposdef", "nonsymposdef-F"): + # Asymmetric and Positive Definite + exp = "TFQMR: Linear solve not converged due to reach MAXIT iterations" + else: # all other cases + exp = "TFQMR: Linear solve converged due to reach TOL iterations" + + assert out.startswith(exp) + assert err == "" + + +@pytest.mark.parametrize("batch_A", [()]) +@pytest.mark.parametrize("batch_b", [()]) +def test_positional_error(solver, xp, batch_A, batch_b): + A, b, x0 = _setup_random_system(xp, batch_A, batch_b) + with pytest.raises(TypeError): + solver(A, b, x0, 1e-5) + + +@pytest.mark.parametrize("batch_A", [()]) +@pytest.mark.parametrize("batch_b", [()]) +@pytest.mark.parametrize("atol", ["legacy", None, -1]) +def test_invalid_atol(solver, atol, xp, batch_A, batch_b): + if solver == minres: + pytest.skip("minres has no `atol` argument") + A, b, x0 = _setup_random_system(xp, batch_A, batch_b) + with pytest.raises(ValueError): + solver(A, b, x0, atol=atol) + + +class TestQMR: + @pytest.mark.filterwarnings('ignore::scipy.sparse.SparseEfficiencyWarning') + def test_leftright_precond(self): + """Check that QMR works with left and right preconditioners""" + + from scipy.sparse.linalg._dsolve import splu + from scipy.sparse.linalg._interface import LinearOperator + + n = 100 + + dat = ones(n) + A = dia_array(([-2 * dat, 4 * dat, -dat], [-1, 0, 1]), shape=(n, n)) + b = arange(n, dtype='d') + + L = dia_array(([-dat / 2, dat], [-1, 0]), shape=(n, n)) + U = dia_array(([4 * dat, -dat], [0, 1]), shape=(n, n)) + L_solver = splu(L) + U_solver = splu(U) + + def L_solve(b): + return L_solver.solve(b) + + def U_solve(b): + return U_solver.solve(b) + + def LT_solve(b): + return L_solver.solve(b, 'T') + + def UT_solve(b): + return U_solver.solve(b, 'T') + + M1 = LinearOperator((n, n), matvec=L_solve, rmatvec=LT_solve) + M2 = LinearOperator((n, n), matvec=U_solve, rmatvec=UT_solve) + + rtol = 1e-8 + x, info = qmr(A, b, rtol=rtol, maxiter=15, M1=M1, M2=M2) + + assert info == 0 + assert xp_vector_norm(A @ x - b) <= rtol * xp_vector_norm(b) + + +class TestGMRES: + def test_basic(self): + A = np.vander(np.arange(10) + 1)[:, ::-1] + b = np.zeros(10) + b[0] = 1 + + x_gm, err = gmres(A, b, restart=5, maxiter=1) + + assert_allclose(x_gm[0], 0.359, rtol=1e-2) + + @pytest.mark.filterwarnings(f"ignore:{CB_TYPE_FILTER}:DeprecationWarning") + def test_callback(self): + + def store_residual(r, rvec): + rvec[rvec.nonzero()[0].max() + 1] = r + + # Define, A,b + A = csr_array(array([[-2, 1, 0, 0, 0, 0], + [1, -2, 1, 0, 0, 0], + [0, 1, -2, 1, 0, 0], + [0, 0, 1, -2, 1, 0], + [0, 0, 0, 1, -2, 1], + [0, 0, 0, 0, 1, -2]])) + b = ones((A.shape[0],)) + maxiter = 1 + rvec = zeros(maxiter + 1) + rvec[0] = 1.0 + + def callback(r): + return store_residual(r, rvec) + + x, flag = gmres(A, b, x0=zeros(A.shape[0]), rtol=1e-16, + maxiter=maxiter, callback=callback) + + # Expected output from SciPy 1.0.0 + assert_allclose(rvec, array([1.0, 0.81649658092772603]), rtol=1e-10) + + # Test preconditioned callback + M = 1e-3 * np.eye(A.shape[0]) + rvec = zeros(maxiter + 1) + rvec[0] = 1.0 + x, flag = gmres(A, b, M=M, rtol=1e-16, maxiter=maxiter, + callback=callback) + + # Expected output from SciPy 1.0.0 + # (callback has preconditioned residual!) + assert_allclose(rvec, array([1.0, 1e-3 * 0.81649658092772603]), + rtol=1e-10) + + def test_abi(self): + # Check we don't segfault on gmres with complex argument + A = eye(2) + b = ones(2) + r_x, r_info = gmres(A, b) + r_x = r_x.astype(complex) + x, info = gmres(A.astype(complex), b.astype(complex)) + + assert iscomplexobj(x) + assert_allclose(r_x, x) + assert r_info == info + + @pytest.mark.fail_slow(10) + def test_atol_legacy(self): + + A = eye(2) + b = ones(2) + x, info = gmres(A, b, rtol=1e-5) + assert xp_vector_norm(A @ x - b) <= 1e-5 * xp_vector_norm(b) + assert_allclose(x, b, atol=0.0, rtol=1e-8) + + rndm = np.random.RandomState(12345) + A = rndm.rand(30, 30) + b = 1e-6 * ones(30) + x, info = gmres(A, b, rtol=1e-7, restart=20) + assert xp_vector_norm(A @ x - b) > 1e-7 + + A = eye(2) + b = 1e-10 * ones(2) + x, info = gmres(A, b, rtol=1e-8, atol=0.0) + assert xp_vector_norm(A @ x - b) <= 1e-8 * xp_vector_norm(b) + + def test_defective_precond_breakdown(self): + # Breakdown due to defective preconditioner + M = np.eye(3) + M[2, 2] = 0 + + b = np.array([0, 1, 1]) + x = np.array([1, 0, 0]) + A = np.diag([2, 3, 4]) + + x, info = gmres(A, b, x0=x, M=M, rtol=1e-15, atol=0.0) + + # Should not return nans, nor terminate with false success + assert not np.isnan(x).any() + if info == 0: + assert xp_vector_norm(A @ x - b) <= 1e-15 * xp_vector_norm(b) + + # The solution should be OK outside null space of M + assert_allclose(M @ (A @ x), M @ b) + + def test_defective_matrix_breakdown(self): + # Breakdown due to defective matrix + A = np.array([[0, 1, 0], [1, 0, 0], [0, 0, 0]]) + b = np.array([1, 0, 1]) + rtol = 1e-8 + x, info = gmres(A, b, rtol=rtol, atol=0) + + # Should not return nans, nor terminate with false success + assert not np.isnan(x).any() + if info == 0: + assert xp_vector_norm(A @ x - b) <= rtol * xp_vector_norm(b) + + # The solution should be OK outside null space of A + assert_allclose(A @ (A @ x), A @ b) + + @pytest.mark.filterwarnings(f"ignore:{CB_TYPE_FILTER}:DeprecationWarning") + def test_callback_type(self): + # The legacy callback type changes meaning of 'maxiter' + np.random.seed(1) + A = np.random.rand(20, 20) + b = np.random.rand(20) + + cb_count = [0] + + def pr_norm_cb(r): + cb_count[0] += 1 + assert isinstance(r, float) + + def x_cb(x): + cb_count[0] += 1 + assert isinstance(x, np.ndarray) + + # 2 iterations is not enough to solve the problem + cb_count = [0] + x, info = gmres(A, b, rtol=1e-6, atol=0.0, callback=pr_norm_cb, + maxiter=2, restart=50) + assert info == 2 + assert cb_count[0] == 2 + + # With `callback_type` specified, no warning should be raised + cb_count = [0] + x, info = gmres(A, b, rtol=1e-6, atol=0.0, callback=pr_norm_cb, + maxiter=2, restart=50, callback_type='legacy') + assert info == 2 + assert cb_count[0] == 2 + + # 2 restart cycles is enough to solve the problem + cb_count = [0] + x, info = gmres(A, b, rtol=1e-6, atol=0.0, callback=pr_norm_cb, + maxiter=2, restart=50, callback_type='pr_norm') + assert info == 0 + assert cb_count[0] > 2 + + # 2 restart cycles is enough to solve the problem + cb_count = [0] + x, info = gmres(A, b, rtol=1e-6, atol=0.0, callback=x_cb, maxiter=2, + restart=50, callback_type='x') + assert info == 0 + assert cb_count[0] == 1 + + def test_callback_x_monotonic(self): + # Check that callback_type='x' gives monotonic norm decrease + rng = np.random.RandomState(1) + A = rng.rand(20, 20) + np.eye(20) + b = rng.rand(20) + + prev_r = [np.inf] + count = [0] + + def x_cb(x): + r = xp_vector_norm(A @ x - b) + assert r <= prev_r[0] + prev_r[0] = r + count[0] += 1 + + x, info = gmres(A, b, rtol=1e-6, atol=0.0, callback=x_cb, maxiter=20, + restart=10, callback_type='x') + assert info == 20 + assert count[0] == 20 + + +def test_nD(solver, xp): + """Check that >2-D operators are rejected cleanly.""" + def id(x): + return x + A = LinearOperator(shape=(2, 2, 2), matvec=id, dtype=xp.float64, xp=xp) + b = xp.ones((2, 2)) + with pytest.raises(ValueError, match="expected 2-D"): + solver(A, b) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/tests/test_lgmres.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/tests/test_lgmres.py new file mode 100644 index 0000000000000000000000000000000000000000..d66f0b950f3d70d5e36223c767d65cf2718e8287 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/tests/test_lgmres.py @@ -0,0 +1,228 @@ +"""Tests for the linalg._isolve.lgmres module +""" +import threading +import warnings + +from numpy.testing import (assert_, assert_allclose, assert_equal) + + +import numpy as np +from numpy import zeros, array, allclose +from scipy.linalg import norm +from scipy.sparse import csr_array, eye_array, random_array + +from scipy.sparse.linalg._interface import LinearOperator +from scipy.sparse.linalg import splu +from scipy.sparse.linalg._isolve import lgmres, gmres + + +Am = csr_array(array([[-2, 1, 0, 0, 0, 9], + [1, -2, 1, 0, 5, 0], + [0, 1, -2, 1, 0, 0], + [0, 0, 1, -2, 1, 0], + [0, 3, 0, 1, -2, 1], + [1, 0, 0, 0, 1, -2]])) +b = array([1, 2, 3, 4, 5, 6]) +count = threading.local() # [0] +niter = threading.local() # [0] + + +def matvec(v): + if not hasattr(count, 'c'): + count.c = [0] + count.c[0] += 1 + return Am@v + + +def cb(v): + if not hasattr(niter, 'n'): + niter.n = [0] + niter.n[0] += 1 + + +A = LinearOperator(matvec=matvec, shape=Am.shape, dtype=Am.dtype) + + +def do_solve(**kw): + if not hasattr(niter, 'n'): + niter.n = [0] + if not hasattr(count, 'c'): + count.c = [0] + count.c[0] = 0 + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", ".*called without specifying.*", DeprecationWarning) + x0, flag = lgmres(A, b, x0=zeros(A.shape[0]), + inner_m=6, rtol=1e-14, **kw) + count_0 = count.c[0] + assert_(allclose(A@x0, b, rtol=1e-12, atol=1e-12), norm(A@x0-b)) + return x0, count_0 + + +class TestLGMRES: + def test_preconditioner(self): + # Check that preconditioning works + pc = splu(Am.tocsc()) + M = LinearOperator(matvec=pc.solve, shape=A.shape, dtype=A.dtype) + + x0, count_0 = do_solve() + niter.n[0] = 0 + x1, count_1 = do_solve(M=M, callback=cb) + + assert count_1 == 3 + assert count_1 < count_0/2 + assert allclose(x1, x0, rtol=1e-14) + assert niter.n[0] < 3 + + def test_outer_v(self): + # Check that the augmentation vectors behave as expected + + outer_v = [] + x0, count_0 = do_solve(outer_k=6, outer_v=outer_v) + assert_(len(outer_v) > 0) + assert_(len(outer_v) <= 6) + + x1, count_1 = do_solve(outer_k=6, outer_v=outer_v, + prepend_outer_v=True) + assert_(count_1 == 2, count_1) + assert_(count_1 < count_0/2) + assert_(allclose(x1, x0, rtol=1e-14)) + + # --- + + outer_v = [] + x0, count_0 = do_solve(outer_k=6, outer_v=outer_v, + store_outer_Av=False) + assert_(array([v[1] is None for v in outer_v]).all()) + assert_(len(outer_v) > 0) + assert_(len(outer_v) <= 6) + + x1, count_1 = do_solve(outer_k=6, outer_v=outer_v, + prepend_outer_v=True) + assert_(count_1 == 3, count_1) + assert_(count_1 < count_0/2) + assert_(allclose(x1, x0, rtol=1e-14)) + + def test_arnoldi(self): + rng = np.random.default_rng(123) + + A = eye_array(2000) + random_array((2000, 2000), density=5e-4, rng=rng) + b = rng.random(2000) + + # The inner arnoldi should be equivalent to gmres + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", ".*called without specifying.*", DeprecationWarning) + x0, flag0 = lgmres(A, b, x0=zeros(A.shape[0]), inner_m=10, maxiter=1) + x1, flag1 = gmres(A, b, x0=zeros(A.shape[0]), restart=10, maxiter=1) + + assert_equal(flag0, 1) + assert_equal(flag1, 1) + norm = np.linalg.norm(A.dot(x0) - b) + assert_(norm > 1e-4) + assert_allclose(x0, x1) + + def test_cornercase(self): + rng = np.random.RandomState(1234) + + # Rounding error may prevent convergence with tol=0 --- ensure + # that the return values in this case are correct, and no + # exceptions are raised + + for n in [3, 5, 10, 100]: + A = 2*eye_array(n) + + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", ".*called without specifying.*", DeprecationWarning) + + b = np.ones(n) + x, info = lgmres(A, b, maxiter=10) + assert_equal(info, 0) + assert_allclose(A.dot(x) - b, 0, atol=1e-14) + + x, info = lgmres(A, b, rtol=0, maxiter=10) + if info == 0: + assert_allclose(A.dot(x) - b, 0, atol=1e-14) + + b = rng.rand(n) + x, info = lgmres(A, b, maxiter=10) + assert_equal(info, 0) + assert_allclose(A.dot(x) - b, 0, atol=1e-14) + + x, info = lgmres(A, b, rtol=0, maxiter=10) + if info == 0: + assert_allclose(A.dot(x) - b, 0, atol=1e-14) + + def test_nans(self): + A = eye_array(3, format='lil') + A[1, 1] = np.nan + b = np.ones(3) + + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", ".*called without specifying.*", DeprecationWarning) + x, info = lgmres(A, b, rtol=0, maxiter=10) + assert_equal(info, 1) + + def test_breakdown_with_outer_v(self): + A = np.array([[1, 2], [3, 4]], dtype=float) + b = np.array([1, 2]) + + x = np.linalg.solve(A, b) + v0 = np.array([1, 0]) + + # The inner iteration should converge to the correct solution, + # since it's in the outer vector list + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", ".*called without specifying.*", DeprecationWarning) + xp, info = lgmres(A, b, outer_v=[(v0, None), (x, None)], maxiter=1) + + assert_allclose(xp, x, atol=1e-12) + + def test_breakdown_underdetermined(self): + # Should find LSQ solution in the Krylov span in one inner + # iteration, despite solver breakdown from nilpotent A. + A = np.array([[0, 1, 1, 1], + [0, 0, 1, 1], + [0, 0, 0, 1], + [0, 0, 0, 0]], dtype=float) + + bs = [ + np.array([1, 1, 1, 1]), + np.array([1, 1, 1, 0]), + np.array([1, 1, 0, 0]), + np.array([1, 0, 0, 0]), + ] + + for b in bs: + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", ".*called without specifying.*", DeprecationWarning) + xp, info = lgmres(A, b, maxiter=1) + resp = np.linalg.norm(A.dot(xp) - b) + + K = np.c_[b, A.dot(b), A.dot(A.dot(b)), A.dot(A.dot(A.dot(b)))] + y, _, _, _ = np.linalg.lstsq(A.dot(K), b, rcond=-1) + x = K.dot(y) + res = np.linalg.norm(A.dot(x) - b) + + assert_allclose(resp, res, err_msg=repr(b)) + + def test_denormals(self): + # Check that no warnings are emitted if the matrix contains + # numbers for which 1/x has no float representation, and that + # the solver behaves properly. + A = np.array([[1, 2], [3, 4]], dtype=float) + A *= 100 * np.nextafter(0, 1) + + b = np.array([1, 1]) + + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", ".*called without specifying.*", DeprecationWarning) + xp, info = lgmres(A, b) + + if info == 0: + assert_allclose(A.dot(xp), b) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/tests/test_lsmr.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/tests/test_lsmr.py new file mode 100644 index 0000000000000000000000000000000000000000..71a7c9ecb1387f54e3c5857adbe701a738e80f1c --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/tests/test_lsmr.py @@ -0,0 +1,205 @@ +""" +Copyright (C) 2010 David Fong and Michael Saunders +Distributed under the same license as SciPy + +Testing Code for LSMR. + +03 Jun 2010: First version release with lsmr.py + +David Chin-lung Fong clfong@stanford.edu +Institute for Computational and Mathematical Engineering +Stanford University + +Michael Saunders saunders@stanford.edu +Systems Optimization Laboratory +Dept of MS&E, Stanford University. + +""" + +import numpy as np +from numpy import array, arange, eye, zeros, ones, transpose, hstack +from numpy.linalg import norm +from numpy.testing import assert_allclose +import pytest +from scipy.sparse import coo_array +from scipy.sparse.linalg import lsmr, LinearOperator, aslinearoperator +from .test_lsqr import G, b + + +class TestLSMR: + def setup_method(self): + self.n = 10 + self.m = 10 + + def assertCompatibleSystem(self, A, xtrue): + Afun = aslinearoperator(A) + b = Afun.matvec(xtrue) + x = lsmr(A, b)[0] + assert norm(x - xtrue) == pytest.approx(0, abs=1e-5) + + def testIdentityACase1(self): + A = eye(self.n) + xtrue = zeros((self.n,)) + self.assertCompatibleSystem(A, xtrue) + + def testIdentityACase2(self): + A = eye(self.n) + xtrue = ones((self.n,)) + self.assertCompatibleSystem(A, xtrue) + + def testIdentityACase3(self): + A = eye(self.n) + xtrue = transpose(arange(self.n,0,-1)) + self.assertCompatibleSystem(A, xtrue) + + def testBidiagonalA(self): + A = lowerBidiagonalMatrix(20,self.n) + xtrue = transpose(arange(self.n,0,-1)) + self.assertCompatibleSystem(A,xtrue) + + def testScalarB(self): + A = array([[1.0, 2.0]]) + b = 3.0 + x = lsmr(A, b)[0] + assert norm(A.dot(x) - b) == pytest.approx(0) + + def testComplexX(self): + A = eye(self.n) + xtrue = transpose(arange(self.n, 0, -1) * (1 + 1j)) + self.assertCompatibleSystem(A, xtrue) + + def testComplexX0(self): + A = 4 * eye(self.n) + ones((self.n, self.n)) + xtrue = transpose(arange(self.n, 0, -1)) + b = aslinearoperator(A).matvec(xtrue) + x0 = zeros(self.n, dtype=complex) + x = lsmr(A, b, x0=x0)[0] + assert norm(x - xtrue) == pytest.approx(0, abs=1e-5) + + def testComplexA(self): + A = 4 * eye(self.n) + 1j * ones((self.n, self.n)) + xtrue = transpose(arange(self.n, 0, -1).astype(complex)) + self.assertCompatibleSystem(A, xtrue) + + def testComplexB(self): + A = 4 * eye(self.n) + ones((self.n, self.n)) + xtrue = transpose(arange(self.n, 0, -1) * (1 + 1j)) + b = aslinearoperator(A).matvec(xtrue) + x = lsmr(A, b)[0] + assert norm(x - xtrue) == pytest.approx(0, abs=1e-5) + + def testColumnB(self): + A = eye(self.n) + b = ones((self.n, 1)) + x = lsmr(A, b)[0] + assert norm(A.dot(x) - b.ravel()) == pytest.approx(0) + + def testInitialization(self): + # Test that the default setting is not modified + x_ref, _, itn_ref, normr_ref, *_ = lsmr(G, b) + assert_allclose(norm(b - G@x_ref), normr_ref, atol=1e-6) + + # Test passing zeros yields similar result + x0 = zeros(b.shape) + x = lsmr(G, b, x0=x0)[0] + assert_allclose(x, x_ref) + + # Test warm-start with single iteration + x0 = lsmr(G, b, maxiter=1)[0] + + x, _, itn, normr, *_ = lsmr(G, b, x0=x0) + assert_allclose(norm(b - G@x), normr, atol=1e-6) + + # NOTE(gh-12139): This doesn't always converge to the same value as + # ref because error estimates will be slightly different when calculated + # from zeros vs x0 as a result only compare norm and itn (not x). + + # x generally converges 1 iteration faster because it started at x0. + # itn == itn_ref means that lsmr(x0) took an extra iteration see above. + # -1 is technically possible but is rare (1 in 100000) so it's more + # likely to be an error elsewhere. + assert itn - itn_ref in (0, 1) + + # If an extra iteration is performed normr may be 0, while normr_ref + # may be much larger. + assert normr < normr_ref * (1 + 1e-6) + + +class TestLSMRReturns: + def setup_method(self): + self.n = 10 + self.A = lowerBidiagonalMatrix(20, self.n) + self.xtrue = transpose(arange(self.n, 0, -1)) + self.Afun = aslinearoperator(self.A) + self.b = self.Afun.matvec(self.xtrue) + self.x0 = ones(self.n) + self.x00 = self.x0.copy() + self.returnValues = lsmr(self.A, self.b) + self.returnValuesX0 = lsmr(self.A, self.b, x0=self.x0) + + def test_unchanged_x0(self): + x, istop, itn, normr, normar, normA, condA, normx = self.returnValuesX0 + assert_allclose(self.x00, self.x0) + + def testNormr(self): + x, istop, itn, normr, normar, normA, condA, normx = self.returnValues + assert norm(self.b - self.Afun.matvec(x)) == pytest.approx(normr) + + def testNormar(self): + x, istop, itn, normr, normar, normA, condA, normx = self.returnValues + assert (norm(self.Afun.rmatvec(self.b - self.Afun.matvec(x))) + == pytest.approx(normar)) + + def testNormx(self): + x, istop, itn, normr, normar, normA, condA, normx = self.returnValues + assert norm(x) == pytest.approx(normx) + + +def lowerBidiagonalMatrix(m, n): + # This is a simple example for testing LSMR. + # It uses the leading m*n submatrix from + # A = [ 1 + # 1 2 + # 2 3 + # 3 4 + # ... + # n ] + # suitably padded by zeros. + # + # 04 Jun 2010: First version for distribution with lsmr.py + if m <= n: + row = hstack((arange(m, dtype=int), + arange(1, m, dtype=int))) + col = hstack((arange(m, dtype=int), + arange(m-1, dtype=int))) + data = hstack((arange(1, m+1, dtype=float), + arange(1,m, dtype=float))) + return coo_array((data, (row, col)), shape=(m,n)) + else: + row = hstack((arange(n, dtype=int), + arange(1, n+1, dtype=int))) + col = hstack((arange(n, dtype=int), + arange(n, dtype=int))) + data = hstack((arange(1, n+1, dtype=float), + arange(1,n+1, dtype=float))) + return coo_array((data,(row, col)), shape=(m,n)) + +def test_lsmr_maxiter_zero_with_show(): + # Test that lsmr does not crash when maxiter=0 and show=True + # Regression test for gh-23748 (str4 undefined variable bug) + A = eye(3) + b = ones(3) + result = lsmr(A, b, maxiter=0, show=True) + # Check that we get a valid result with zero iterations + assert result[0] is not None # x should exist + assert result[1] == 0 # istop should be 0 + assert result[2] == 0 # itn should be 0 + +def test_nD(): + """Check that >2-D operators are rejected cleanly.""" + def id(x): + return x + A = LinearOperator(shape=(2, 2, 2), matvec=id, dtype=np.float64) + b = np.ones((2, 2)) + with pytest.raises(ValueError, match="expected 2-D"): + lsmr(A, b) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/tests/test_lsqr.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/tests/test_lsqr.py new file mode 100644 index 0000000000000000000000000000000000000000..339704b0a339a52cc970354bddfe62f9ad372b61 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/tests/test_lsqr.py @@ -0,0 +1,128 @@ +import numpy as np +from numpy.testing import assert_allclose, assert_array_equal, assert_equal +import pytest +import scipy.sparse +from scipy.sparse.linalg import lsqr, LinearOperator + +# Set up a test problem +n = 35 +G = np.eye(n) +normal = np.random.normal +norm = np.linalg.norm + +for jj in range(5): + gg = normal(size=n) + hh = gg * gg.T + G += (hh + hh.T) * 0.5 + G += normal(size=n) * normal(size=n) + +b = normal(size=n) + +# tolerance for atol/btol keywords of lsqr() +tol = 2e-10 +# tolerances for testing the results of the lsqr() call with assert_allclose +# These tolerances are a bit fragile - see discussion in gh-15301. +atol_test = 4e-10 +rtol_test = 2e-8 +show = False +maxit = None + + +def test_lsqr_basic(): + b_copy = b.copy() + xo, *_ = lsqr(G, b, show=show, atol=tol, btol=tol, iter_lim=maxit) + assert_array_equal(b_copy, b) + + svx = np.linalg.solve(G, b) + assert_allclose(xo, svx, atol=atol_test, rtol=rtol_test) + + # Now the same but with damp > 0. + # This is equivalent to solving the extended system: + # ( G ) @ x = ( b ) + # ( damp*I ) ( 0 ) + damp = 1.5 + xo, *_ = lsqr( + G, b, damp=damp, show=show, atol=tol, btol=tol, iter_lim=maxit) + + Gext = np.r_[G, damp * np.eye(G.shape[1])] + bext = np.r_[b, np.zeros(G.shape[1])] + svx, *_ = np.linalg.lstsq(Gext, bext, rcond=None) + assert_allclose(xo, svx, atol=atol_test, rtol=rtol_test) + + +def test_gh_2466(): + row = np.array([0, 0]) + col = np.array([0, 1]) + val = np.array([1, -1]) + A = scipy.sparse.coo_array((val, (row, col)), shape=(1, 2)) + b = np.asarray([4]) + lsqr(A, b) + + +def test_well_conditioned_problems(): + # Test that sparse the lsqr solver returns the right solution + # on various problems with different random seeds. + # This is a non-regression test for a potential ZeroDivisionError + # raised when computing the `test2` & `test3` convergence conditions. + n = 10 + A_sparse = scipy.sparse.eye_array(n, n) + A_dense = A_sparse.toarray() + + with np.errstate(invalid='raise'): + for seed in range(30): + rng = np.random.RandomState(seed + 10) + beta = rng.rand(n) + beta[beta == 0] = 0.00001 # ensure that all the betas are not null + b = A_sparse @ beta[:, np.newaxis] + output = lsqr(A_sparse, b, show=show) + + # Check that the termination condition corresponds to an approximate + # solution to Ax = b + assert_equal(output[1], 1) + solution = output[0] + + # Check that we recover the ground truth solution + assert_allclose(solution, beta) + + # Sanity check: compare to the dense array solver + reference_solution = np.linalg.solve(A_dense, b).ravel() + assert_allclose(solution, reference_solution) + + +def test_b_shapes(): + # Test b being a scalar. + A = np.array([[1.0, 2.0]]) + b = 3.0 + x = lsqr(A, b)[0] + assert norm(A.dot(x) - b) == pytest.approx(0) + + # Test b being a column vector. + A = np.eye(10) + b = np.ones((10, 1)) + x = lsqr(A, b)[0] + assert norm(A.dot(x) - b.ravel()) == pytest.approx(0) + + +def test_initialization(): + # Test the default setting is the same as zeros + b_copy = b.copy() + x_ref = lsqr(G, b, show=show, atol=tol, btol=tol, iter_lim=maxit) + x0 = np.zeros(x_ref[0].shape) + x = lsqr(G, b, show=show, atol=tol, btol=tol, iter_lim=maxit, x0=x0) + assert_array_equal(b_copy, b) + assert_allclose(x_ref[0], x[0]) + + # Test warm-start with single iteration + x0 = lsqr(G, b, show=show, atol=tol, btol=tol, iter_lim=1)[0] + x = lsqr(G, b, show=show, atol=tol, btol=tol, iter_lim=maxit, x0=x0) + assert_allclose(x_ref[0], x[0]) + assert_array_equal(b_copy, b) + +def test_nD(): + """Check that >2-D operators are rejected cleanly.""" + def id(x): + return x + A = LinearOperator(shape=(2, 2, 2), matvec=id, dtype=np.float64) + b = np.ones((2, 2)) + with pytest.raises(ValueError, match="expected 2-D"): + lsqr(A, b) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/tests/test_minres.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/tests/test_minres.py new file mode 100644 index 0000000000000000000000000000000000000000..847eb75771b976d7f42dff0975dece49be092a26 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/tests/test_minres.py @@ -0,0 +1,134 @@ +import numpy as np +from numpy.linalg import norm +from numpy.testing import assert_equal, assert_allclose, assert_ +from scipy.sparse.linalg._isolve import minres +import pytest +from functools import lru_cache + +from pytest import raises as assert_raises + +@lru_cache +def _cached_sample_problem(make_complex: bool): + # A random 10 x 10 symmetric matrix + rng = np.random.RandomState(1234) + if make_complex: + matrix = rng.rand(10, 10) + 1j * rng.rand(10, 10) + matrix = matrix + matrix.T.conj() + else: + matrix = rng.rand(10, 10) + matrix = matrix + matrix.T + # A random vector of length 10 + vector = rng.rand(10) + return matrix, vector + +def get_sample_problem(): + A, b = _cached_sample_problem(False) + return A.copy(), b.copy() + +def get_sample_problem_complex(): + A, b = _cached_sample_problem(True) + return A.copy(), b.copy() + +@pytest.mark.parametrize("problem_func", + [get_sample_problem, get_sample_problem_complex]) +def test_singular(problem_func): + A, b = problem_func() + A[0, ] = 0 + b[0] = 0 + xp, info = minres(A, b) + assert_equal(info, 0) + assert norm(A @ xp - b) <= 1e-5 * norm(b) + +@pytest.mark.parametrize("problem_func", + [get_sample_problem, get_sample_problem_complex]) +def test_x0_is_used_by(problem_func): + A, b = problem_func() + # Random x0 to feed minres + rng = np.random.RandomState(12345) + x0 = rng.rand(10) + trace = [] + + def trace_iterates(xk): + trace.append(xk) + minres(A, b, x0=x0, callback=trace_iterates) + trace_with_x0 = trace + + trace = [] + minres(A, b, callback=trace_iterates) + assert_(not np.array_equal(trace_with_x0[0], trace[0])) + +@pytest.mark.parametrize("problem_func", + [get_sample_problem, get_sample_problem_complex]) +def test_shift(problem_func): + A, b = problem_func() + shift = 0.5 + shifted_A = A - shift * np.eye(10) + x1, info1 = minres(A, b, shift=shift) + x2, info2 = minres(shifted_A, b) + assert_equal(info1, 0) + assert_allclose(x1, x2, rtol=1e-5) + +@pytest.mark.parametrize("problem_func", + [get_sample_problem, get_sample_problem_complex]) +def test_asymmetric_fail(problem_func): + """Asymmetric matrix should raise `ValueError` when check=True""" + A, b = problem_func() + A[1, 2] = 1 + A[2, 1] = 2 + with assert_raises(ValueError): + xp, info = minres(A, b, check=True) + +@pytest.mark.parametrize("problem_func", + [get_sample_problem, get_sample_problem_complex]) +def test_asymmetric_preconditioner_fail(problem_func): + """Non-symmetric (non-Hermitian) preconditioner M + should raise ValueError when check=True.""" + A, b = problem_func() + rng = np.random.RandomState(4321) + if np.iscomplexobj(A): + M = rng.rand(10, 10) + 1j * rng.rand(10, 10) + M = M + M.T.conj() + M[1, 2] = 1 + 2j + M[2, 1] = 3 + 0j # break Hermitian symmetry + else: + M = rng.rand(10, 10) + M = M + M.T + M[1, 2] = 1.0 + M[2, 1] = 2.0 # break symmetry + with assert_raises(ValueError): + xp, info = minres(A, b, M=M, check=True) + +@pytest.mark.parametrize("make_complex", [False, True]) +def test_minres_non_default_x0(make_complex): + rng = np.random.RandomState(1234) + rtol = 1e-6 + a = rng.randn(5, 5) if not make_complex else rng.randn(5, 5) + 1j * rng.randn(5, 5) + a = np.dot(a, a.T) if not make_complex else np.dot(a, a.T.conj()) + b = rng.randn(5) + c = rng.randn(5) + x = minres(a, b, x0=c, rtol=rtol)[0] + assert norm(a @ x - b) <= rtol * norm(b) + +@pytest.mark.parametrize("make_complex", [False, True]) +def test_minres_precond_non_default_x0(make_complex): + rng = np.random.RandomState(12345) + rtol = 1e-6 + a = rng.randn(5, 5) if not make_complex else rng.randn(5, 5) + 1j * rng.randn(5, 5) + a = np.dot(a, a.T) if not make_complex else np.dot(a, a.T.conj()) + b = rng.randn(5) + c = rng.randn(5) + m = rng.randn(5, 5) + m = np.dot(m, m.T) + x = minres(a, b, M=m, x0=c, rtol=rtol)[0] + assert norm(a @ x - b) <= rtol * norm(b) + +def test_minres_precond_exact_x0(): + rng = np.random.RandomState(1234) + rtol = 1e-6 + a = np.eye(10) + b = np.ones(10) + c = np.ones(10) + m = rng.randn(10, 10) + m = np.dot(m, m.T) + x = minres(a, b, M=m, x0=c, rtol=rtol)[0] + assert norm(a @ x - b) <= rtol * norm(b) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/tests/test_utils.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/tests/test_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..bb62e2223d515288bd5aa6c5a70028279c4f6d30 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/tests/test_utils.py @@ -0,0 +1,9 @@ +import numpy as np +from pytest import raises as assert_raises + +import scipy.sparse.linalg._isolve.utils as utils + + +def test_make_system_bad_shape(): + assert_raises(ValueError, + utils.make_system, np.zeros((5,3)), None, np.zeros(4), np.zeros(4)) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/tfqmr.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/tfqmr.py new file mode 100644 index 0000000000000000000000000000000000000000..5ed70d39f5660c659855e0a4febf378987ed5625 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/tfqmr.py @@ -0,0 +1,182 @@ +import numpy as np + +from scipy._lib._array_api import xp_capabilities +from .iterative import _get_atol_rtol +from .utils import make_system + + +__all__ = ['tfqmr'] + + +@xp_capabilities(np_only=True) +def tfqmr(A, b, x0=None, *, rtol=1e-5, atol=0., maxiter=None, M=None, + callback=None, show=False): + """ + Solve ``Ax = b`` with the Transpose-Free Quasi-Minimal Residual method. + + Parameters + ---------- + A : {sparse array, ndarray, LinearOperator} + The real or complex N-by-N matrix of the linear system. + Alternatively, `A` can be a linear operator which can + produce ``Ax`` using, e.g., + `scipy.sparse.linalg.LinearOperator`. + b : {ndarray} + Right hand side of the linear system. Has shape (N,) or (N,1). + x0 : {ndarray} + Starting guess for the solution. + rtol, atol : float, optional + Parameters for the convergence test. For convergence, + ``norm(b - A @ x) <= max(rtol*norm(b), atol)`` should be satisfied. + The default is ``rtol=1e-5``, the default for ``atol`` is ``0.0``. + maxiter : int, optional + Maximum number of iterations. Iteration will stop after maxiter + steps even if the specified tolerance has not been achieved. + Default is ``min(10000, ndofs * 10)``, where ``ndofs = A.shape[0]``. + M : {sparse array, ndarray, LinearOperator} + Inverse of the preconditioner of A. M should approximate the + inverse of A and be easy to solve for (see Notes). Effective + preconditioning dramatically improves the rate of convergence, + which implies that fewer iterations are needed to reach a given + error tolerance. By default, no preconditioner is used. + callback : function, optional + User-supplied function to call after each iteration. It is called + as ``callback(xk)``, where ``xk`` is the current solution vector. + show : bool, optional + Specify ``show = True`` to show the convergence, ``show = False`` is + to close the output of the convergence. + Default is `False`. + + Returns + ------- + x : ndarray + The converged solution. + info : int + Provides convergence information: + + - 0 : successful exit + - >0 : convergence to tolerance not achieved, number of iterations + - <0 : illegal input or breakdown + + Notes + ----- + The Transpose-Free QMR algorithm is derived from the CGS algorithm. + However, unlike CGS, the convergence curves for the TFQMR method is + smoothed by computing a quasi minimization of the residual norm. The + implementation supports left preconditioner, and the "residual norm" + to compute in convergence criterion is actually an upper bound on the + actual residual norm ``||b - Axk||``. + + References + ---------- + .. [1] R. W. Freund, A Transpose-Free Quasi-Minimal Residual Algorithm for + Non-Hermitian Linear Systems, SIAM J. Sci. Comput., 14(2), 470-482, + 1993. + .. [2] Y. Saad, Iterative Methods for Sparse Linear Systems, 2nd edition, + SIAM, Philadelphia, 2003. + .. [3] C. T. Kelley, Iterative Methods for Linear and Nonlinear Equations, + number 16 in Frontiers in Applied Mathematics, SIAM, Philadelphia, + 1995. + + Examples + -------- + >>> import numpy as np + >>> from scipy.sparse import csc_array + >>> from scipy.sparse.linalg import tfqmr + >>> A = csc_array([[3, 2, 0], [1, -1, 0], [0, 5, 1]], dtype=float) + >>> b = np.array([2, 4, -1], dtype=float) + >>> x, exitCode = tfqmr(A, b, atol=0.0) + >>> print(exitCode) # 0 indicates successful convergence + 0 + >>> np.allclose(A.dot(x), b) + True + """ + + # Check data type + dtype = A.dtype + if np.issubdtype(dtype, np.int64): + dtype = float + A = A.astype(dtype) + if np.issubdtype(b.dtype, np.int64): + b = b.astype(dtype) + + A, M, x, b = make_system(A, M, x0, b) + + # Check if the R.H.S is a zero vector + if np.linalg.norm(b) == 0.: + x = b.copy() + return (x, 0) + + ndofs = A.shape[0] + if maxiter is None: + maxiter = min(10000, ndofs * 10) + + if x0 is None: + r = b.copy() + else: + r = b - A.matvec(x) + u = r + w = r.copy() + # Take rstar as b - Ax0, that is rstar := r = b - Ax0 mathematically + rstar = r + v = M.matvec(A.matvec(r)) + uhat = v + d = theta = eta = 0. + # at this point we know rstar == r, so rho is always real + rho = np.inner(rstar.conjugate(), r).real + rhoLast = rho + r0norm = np.sqrt(rho) + tau = r0norm + if r0norm == 0: + return (x, 0) + + # we call this to get the right atol and raise errors as necessary + atol, _ = _get_atol_rtol('tfqmr', r0norm, atol, rtol) + + for iter in range(maxiter): + even = iter % 2 == 0 + if (even): + vtrstar = np.inner(rstar.conjugate(), v) + # Check breakdown + if vtrstar == 0.: + return (x, -1) + alpha = rho / vtrstar + uNext = u - alpha * v # [1]-(5.6) + w -= alpha * uhat # [1]-(5.8) + d = u + (theta**2 / alpha) * eta * d # [1]-(5.5) + # [1]-(5.2) + theta = np.linalg.norm(w) / tau + c = np.sqrt(1. / (1 + theta**2)) + tau *= theta * c + # Calculate step and direction [1]-(5.4) + eta = (c**2) * alpha + z = M.matvec(d) + x += eta * z + + if callback is not None: + callback(x) + + # Convergence criterion + if tau * np.sqrt(iter+1) < atol: + if (show): + print("TFQMR: Linear solve converged due to reach TOL " + f"iterations {iter+1}") + return (x, 0) + + if (not even): + # [1]-(5.7) + rho = np.inner(rstar.conjugate(), w) + beta = rho / rhoLast + u = w + beta * u + v = beta * uhat + (beta**2) * v + uhat = M.matvec(A.matvec(u)) + v += uhat + else: + uhat = M.matvec(A.matvec(uNext)) + u = uNext + rhoLast = rho + + if (show): + print("TFQMR: Linear solve not converged due to reach MAXIT " + f"iterations {iter+1}") + return (x, maxiter) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/utils.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..334d9692187d8e775e5c2dc6b8446857a1e25b0b --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_isolve/utils.py @@ -0,0 +1,123 @@ +__docformat__ = "restructuredtext en" + +__all__ = [] + + +from numpy import asanyarray, asarray, array, zeros + +from scipy.sparse.linalg._interface import aslinearoperator, LinearOperator, \ + IdentityOperator + +_coerce_rules = {('f','f'):'f', ('f','d'):'d', ('f','F'):'F', + ('f','D'):'D', ('d','f'):'d', ('d','d'):'d', + ('d','F'):'D', ('d','D'):'D', ('F','f'):'F', + ('F','d'):'D', ('F','F'):'F', ('F','D'):'D', + ('D','f'):'D', ('D','d'):'D', ('D','F'):'D', + ('D','D'):'D'} + + +def coerce(x,y): + if x not in 'fdFD': + x = 'd' + if y not in 'fdFD': + y = 'd' + return _coerce_rules[x,y] + + +def id(x): + return x + + +def make_system(A, M, x0, b): + """Make a linear system Ax=b + + Parameters + ---------- + A : LinearOperator + sparse or dense matrix (or any valid input to aslinearoperator) + M : {LinearOperator, Nones} + preconditioner + sparse or dense matrix (or any valid input to aslinearoperator) + x0 : {array_like, str, None} + initial guess to iterative method. + ``x0 = 'Mb'`` means using the nonzero initial guess ``M @ b``. + Default is `None`, which means using the zero initial guess. + b : array_like + right hand side + + Returns + ------- + (A, M, x, b) + A : LinearOperator + matrix of the linear system + M : LinearOperator + preconditioner + x : rank 1 ndarray + initial guess + b : rank 1 ndarray + right hand side + + """ + A_ = A + A = aslinearoperator(A) + if A.ndim > 2: + raise ValueError(f"{A.ndim}-dimensional `A` is unsupported, expected 2-D.") + + if A.shape[0] != A.shape[1]: + raise ValueError(f'expected square matrix, but got shape={(A.shape,)}') + + N = A.shape[0] + + b = asanyarray(b) + + if not (b.shape == (N,1) or b.shape == (N,)): + raise ValueError(f'shapes of A {A.shape} and b {b.shape} are ' + 'incompatible') + + if b.dtype.char not in 'fdFD': + b = b.astype('d') # upcast non-FP types to double + + if hasattr(A,'dtype'): + xtype = A.dtype.char + else: + xtype = A.matvec(b).dtype.char + xtype = coerce(xtype, b.dtype.char) + + b = asarray(b,dtype=xtype) # make b the same type as x + b = b.ravel() + + # process preconditioner + if M is None: + if hasattr(A_,'psolve'): + psolve = A_.psolve + else: + psolve = id + if hasattr(A_,'rpsolve'): + rpsolve = A_.rpsolve + else: + rpsolve = id + if psolve is id and rpsolve is id: + M = IdentityOperator(shape=A.shape, dtype=A.dtype) + else: + M = LinearOperator(A.shape, matvec=psolve, rmatvec=rpsolve, + dtype=A.dtype) + else: + M = aslinearoperator(M) + if A.shape != M.shape: + raise ValueError('matrix and preconditioner have different shapes') + + # set initial guess + if x0 is None: + x = zeros(N, dtype=xtype) + elif isinstance(x0, str): + if x0 == 'Mb': # use nonzero initial guess ``M @ b`` + bCopy = b.copy() + x = M.matvec(bCopy) + else: + x = array(x0, dtype=xtype) + if not (x.shape == (N, 1) or x.shape == (N,)): + raise ValueError(f'shapes of A {A.shape} and ' + f'x0 {x.shape} are incompatible') + x = x.ravel() + + return A, M, x, b diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_matfuncs.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_matfuncs.py new file mode 100644 index 0000000000000000000000000000000000000000..794738572ac1246380bb850a3e170e00dea4a73d --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_matfuncs.py @@ -0,0 +1,939 @@ +""" +Sparse matrix functions +""" + +# +# Authors: Travis Oliphant, March 2002 +# Anthony Scopatz, August 2012 (Sparse Updates) +# Jake Vanderplas, August 2012 (Sparse Updates) +# + +__all__ = ['expm', 'inv', 'matrix_power'] + +import numpy as np +from scipy.linalg._basic import solve, solve_triangular + +from scipy.sparse._base import issparse +from scipy.sparse.linalg import spsolve +from scipy.sparse._sputils import is_pydata_spmatrix, isintlike + +import scipy.sparse +import scipy.sparse.linalg +from scipy.sparse.linalg._interface import LinearOperator +from scipy.sparse._construct import eye_array + +from ._expm_multiply import _ident_like, _exact_1_norm as _onenorm + + +UPPER_TRIANGULAR = 'upper_triangular' + + +def inv(A): + """ + Compute the inverse of a sparse arrays. + + Parameters + ---------- + A : (M, M) sparse arrays + square matrix to be inverted + + Returns + ------- + Ainv : (M, M) sparse arrays + inverse of `A` + + Notes + ----- + This computes the sparse inverse of `A`. If the inverse of `A` is expected + to be non-sparse, it will likely be faster to convert `A` to dense and use + `scipy.linalg.inv`. + + Examples + -------- + >>> from scipy.sparse import csc_array + >>> from scipy.sparse.linalg import inv + >>> A = csc_array([[1., 0.], [1., 2.]]) + >>> Ainv = inv(A) + >>> Ainv + + >>> A.dot(Ainv) + + >>> A.dot(Ainv).toarray() + array([[ 1., 0.], + [ 0., 1.]]) + + .. versionadded:: 0.12.0 + + """ + # Check input + if not (issparse(A) or is_pydata_spmatrix(A)): + raise TypeError('Input must be a sparse arrays') + + # Use sparse direct solver to solve "AX = I" accurately + I = _ident_like(A) + Ainv = spsolve(A, I) + return Ainv + + +def _onenorm_matrix_power_nnm(A, p): + """ + Compute the 1-norm of a non-negative integer power of a non-negative matrix. + + Parameters + ---------- + A : a square ndarray or matrix or sparse arrays + Input matrix with non-negative entries. + p : non-negative integer + The power to which the matrix is to be raised. + + Returns + ------- + out : float + The 1-norm of the matrix power p of A. + + """ + # Check input + if int(p) != p or p < 0: + raise ValueError('expected non-negative integer p') + p = int(p) + if len(A.shape) != 2 or A.shape[0] != A.shape[1]: + raise ValueError('expected A to be like a square matrix') + + # Explicitly make a column vector so that this works when A is a + # numpy matrix (in addition to ndarray and sparse arrays). + v = np.ones((A.shape[0], 1), dtype=float) + M = A.T + for i in range(p): + v = M.dot(v) + return np.max(v) + + +def _is_upper_triangular(A): + # This function could possibly be of wider interest. + if issparse(A): + lower_part = scipy.sparse.tril(A, -1) + # Check structural upper triangularity, + # then coincidental upper triangularity if needed. + return lower_part.nnz == 0 or lower_part.count_nonzero() == 0 + elif is_pydata_spmatrix(A): + import sparse + lower_part = sparse.tril(A, -1) + return lower_part.nnz == 0 + else: + return not np.tril(A, -1).any() + + +def _smart_matrix_product(A, B, alpha=None, structure=None): + """ + A matrix product that knows about sparse and structured matrices. + + Parameters + ---------- + A : 2d ndarray + First matrix. + B : 2d ndarray + Second matrix. + alpha : float + The matrix product will be scaled by this constant. + structure : str, optional + A string describing the structure of both matrices `A` and `B`. + Only `upper_triangular` is currently supported. + + Returns + ------- + M : 2d ndarray + Matrix product of A and B. + + """ + if len(A.shape) != 2: + raise ValueError('expected A to be a rectangular matrix') + if len(B.shape) != 2: + raise ValueError('expected B to be a rectangular matrix') + f = None + if structure == UPPER_TRIANGULAR: + if (not issparse(A) and not issparse(B) + and not is_pydata_spmatrix(A) and not is_pydata_spmatrix(B)): + f, = scipy.linalg.get_blas_funcs(('trmm',), (A, B)) + if f is not None: + if alpha is None: + alpha = 1. + out = f(alpha, A, B) + else: + if alpha is None: + out = A.dot(B) + else: + out = alpha * A.dot(B) + return out + + +class MatrixPowerOperator(LinearOperator): + + def __init__(self, A, p, structure=None): + if A.ndim != 2 or A.shape[0] != A.shape[1]: + raise ValueError('expected A to be like a square matrix') + if p < 0: + raise ValueError('expected p to be a non-negative integer') + self._A = A + self._p = p + self._structure = structure + super().__init__(dtype = A.dtype, shape=A.shape) + + def _matvec(self, x): + for i in range(self._p): + x = self._A.dot(x) + return x + + def _rmatvec(self, x): + A_T = self._A.T + x = x.ravel() + for i in range(self._p): + x = A_T.dot(x) + return x + + def _matmat(self, X): + for i in range(self._p): + X = _smart_matrix_product(self._A, X, structure=self._structure) + return X + + @property + def T(self): + return MatrixPowerOperator(self._A.T, self._p) + + +class ProductOperator(LinearOperator): + """ + For now, this is limited to products of multiple square matrices. + """ + + def __init__(self, *args, **kwargs): + self._structure = kwargs.get('structure', None) + for A in args: + if len(A.shape) != 2 or A.shape[0] != A.shape[1]: + raise ValueError( + 'For now, the ProductOperator implementation is ' + 'limited to the product of multiple square matrices.') + shape = None + if args: + n = args[0].shape[0] + for A in args: + for d in A.shape: + if d != n: + raise ValueError( + 'The square matrices of the ProductOperator ' + 'must all have the same shape.') + shape = (n, n) + dtype = np.result_type(*[x.dtype for x in args]) + super().__init__(dtype=dtype, shape=shape) + self._operator_sequence = args + + def _matvec(self, x): + for A in reversed(self._operator_sequence): + x = A.dot(x) + return x + + def _rmatvec(self, x): + x = x.ravel() + for A in self._operator_sequence: + x = A.T.dot(x) + return x + + def _matmat(self, X): + for A in reversed(self._operator_sequence): + X = _smart_matrix_product(A, X, structure=self._structure) + return X + + @property + def T(self): + T_args = [A.T for A in reversed(self._operator_sequence)] + return ProductOperator(*T_args) + + +def _onenormest_matrix_power(A, p, + t=2, itmax=5, compute_v=False, compute_w=False, structure=None): + """ + Efficiently estimate the 1-norm of A^p. + + Parameters + ---------- + A : ndarray + Matrix whose 1-norm of a power is to be computed. + p : int + Non-negative integer power. + t : int, optional + A positive parameter controlling the tradeoff between + accuracy versus time and memory usage. + Larger values take longer and use more memory + but give more accurate output. + itmax : int, optional + Use at most this many iterations. + compute_v : bool, optional + Request a norm-maximizing linear operator input vector if True. + compute_w : bool, optional + Request a norm-maximizing linear operator output vector if True. + + Returns + ------- + est : float + An underestimate of the 1-norm of the sparse arrays. + v : ndarray, optional + The vector such that ||Av||_1 == est*||v||_1. + It can be thought of as an input to the linear operator + that gives an output with particularly large norm. + w : ndarray, optional + The vector Av which has relatively large 1-norm. + It can be thought of as an output of the linear operator + that is relatively large in norm compared to the input. + + """ + return scipy.sparse.linalg.onenormest( + MatrixPowerOperator(A, p, structure=structure)) + + +def _onenormest_product(operator_seq, + t=2, itmax=5, compute_v=False, compute_w=False, structure=None): + """ + Efficiently estimate the 1-norm of the matrix product of the args. + + Parameters + ---------- + operator_seq : linear operator sequence + Matrices whose 1-norm of product is to be computed. + t : int, optional + A positive parameter controlling the tradeoff between + accuracy versus time and memory usage. + Larger values take longer and use more memory + but give more accurate output. + itmax : int, optional + Use at most this many iterations. + compute_v : bool, optional + Request a norm-maximizing linear operator input vector if True. + compute_w : bool, optional + Request a norm-maximizing linear operator output vector if True. + structure : str, optional + A string describing the structure of all operators. + Only `upper_triangular` is currently supported. + + Returns + ------- + est : float + An underestimate of the 1-norm of the sparse arrays. + v : ndarray, optional + The vector such that ||Av||_1 == est*||v||_1. + It can be thought of as an input to the linear operator + that gives an output with particularly large norm. + w : ndarray, optional + The vector Av which has relatively large 1-norm. + It can be thought of as an output of the linear operator + that is relatively large in norm compared to the input. + + """ + return scipy.sparse.linalg.onenormest( + ProductOperator(*operator_seq, structure=structure)) + + +class _ExpmPadeHelper: + """ + Help lazily evaluate a matrix exponential. + + The idea is to not do more work than we need for high expm precision, + so we lazily compute matrix powers and store or precompute + other properties of the matrix. + + """ + + def __init__(self, A, structure=None, use_exact_onenorm=False): + """ + Initialize the object. + + Parameters + ---------- + A : a dense or sparse square numpy matrix or ndarray + The matrix to be exponentiated. + structure : str, optional + A string describing the structure of matrix `A`. + Only `upper_triangular` is currently supported. + use_exact_onenorm : bool, optional + If True then only the exact one-norm of matrix powers and products + will be used. Otherwise, the one-norm of powers and products + may initially be estimated. + """ + self.A = A + self._A2 = None + self._A4 = None + self._A6 = None + self._A8 = None + self._A10 = None + self._d4_exact = None + self._d6_exact = None + self._d8_exact = None + self._d10_exact = None + self._d4_approx = None + self._d6_approx = None + self._d8_approx = None + self._d10_approx = None + self.ident = _ident_like(A) + self.structure = structure + self.use_exact_onenorm = use_exact_onenorm + + @property + def A2(self): + if self._A2 is None: + self._A2 = _smart_matrix_product( + self.A, self.A, structure=self.structure) + return self._A2 + + @property + def A4(self): + if self._A4 is None: + self._A4 = _smart_matrix_product( + self.A2, self.A2, structure=self.structure) + return self._A4 + + @property + def A6(self): + if self._A6 is None: + self._A6 = _smart_matrix_product( + self.A4, self.A2, structure=self.structure) + return self._A6 + + @property + def A8(self): + if self._A8 is None: + self._A8 = _smart_matrix_product( + self.A6, self.A2, structure=self.structure) + return self._A8 + + @property + def A10(self): + if self._A10 is None: + self._A10 = _smart_matrix_product( + self.A4, self.A6, structure=self.structure) + return self._A10 + + @property + def d4_tight(self): + if self._d4_exact is None: + self._d4_exact = _onenorm(self.A4)**(1/4.) + return self._d4_exact + + @property + def d6_tight(self): + if self._d6_exact is None: + self._d6_exact = _onenorm(self.A6)**(1/6.) + return self._d6_exact + + @property + def d8_tight(self): + if self._d8_exact is None: + self._d8_exact = _onenorm(self.A8)**(1/8.) + return self._d8_exact + + @property + def d10_tight(self): + if self._d10_exact is None: + self._d10_exact = _onenorm(self.A10)**(1/10.) + return self._d10_exact + + @property + def d4_loose(self): + if self.use_exact_onenorm: + return self.d4_tight + if self._d4_exact is not None: + return self._d4_exact + else: + if self._d4_approx is None: + self._d4_approx = _onenormest_matrix_power(self.A2, 2, + structure=self.structure)**(1/4.) + return self._d4_approx + + @property + def d6_loose(self): + if self.use_exact_onenorm: + return self.d6_tight + if self._d6_exact is not None: + return self._d6_exact + else: + if self._d6_approx is None: + self._d6_approx = _onenormest_matrix_power(self.A2, 3, + structure=self.structure)**(1/6.) + return self._d6_approx + + @property + def d8_loose(self): + if self.use_exact_onenorm: + return self.d8_tight + if self._d8_exact is not None: + return self._d8_exact + else: + if self._d8_approx is None: + self._d8_approx = _onenormest_matrix_power(self.A4, 2, + structure=self.structure)**(1/8.) + return self._d8_approx + + @property + def d10_loose(self): + if self.use_exact_onenorm: + return self.d10_tight + if self._d10_exact is not None: + return self._d10_exact + else: + if self._d10_approx is None: + self._d10_approx = _onenormest_product((self.A4, self.A6), + structure=self.structure)**(1/10.) + return self._d10_approx + + def pade3(self): + b = (120., 60., 12., 1.) + U = _smart_matrix_product(self.A, + b[3]*self.A2 + b[1]*self.ident, + structure=self.structure) + V = b[2]*self.A2 + b[0]*self.ident + return U, V + + def pade5(self): + b = (30240., 15120., 3360., 420., 30., 1.) + U = _smart_matrix_product(self.A, + b[5]*self.A4 + b[3]*self.A2 + b[1]*self.ident, + structure=self.structure) + V = b[4]*self.A4 + b[2]*self.A2 + b[0]*self.ident + return U, V + + def pade7(self): + b = (17297280., 8648640., 1995840., 277200., 25200., 1512., 56., 1.) + U = _smart_matrix_product(self.A, + b[7]*self.A6 + b[5]*self.A4 + b[3]*self.A2 + b[1]*self.ident, + structure=self.structure) + V = b[6]*self.A6 + b[4]*self.A4 + b[2]*self.A2 + b[0]*self.ident + return U, V + + def pade9(self): + b = (17643225600., 8821612800., 2075673600., 302702400., 30270240., + 2162160., 110880., 3960., 90., 1.) + U = _smart_matrix_product(self.A, + (b[9]*self.A8 + b[7]*self.A6 + b[5]*self.A4 + + b[3]*self.A2 + b[1]*self.ident), + structure=self.structure) + V = (b[8]*self.A8 + b[6]*self.A6 + b[4]*self.A4 + + b[2]*self.A2 + b[0]*self.ident) + return U, V + + def pade13_scaled(self, s): + b = (64764752532480000., 32382376266240000., 7771770303897600., + 1187353796428800., 129060195264000., 10559470521600., + 670442572800., 33522128640., 1323241920., 40840800., 960960., + 16380., 182., 1.) + B = self.A * 2**-s + B2 = self.A2 * 2**(-2*s) + B4 = self.A4 * 2**(-4*s) + B6 = self.A6 * 2**(-6*s) + U2 = _smart_matrix_product(B6, + b[13]*B6 + b[11]*B4 + b[9]*B2, + structure=self.structure) + U = _smart_matrix_product(B, + (U2 + b[7]*B6 + b[5]*B4 + + b[3]*B2 + b[1]*self.ident), + structure=self.structure) + V2 = _smart_matrix_product(B6, + b[12]*B6 + b[10]*B4 + b[8]*B2, + structure=self.structure) + V = V2 + b[6]*B6 + b[4]*B4 + b[2]*B2 + b[0]*self.ident + return U, V + + +def expm(A): + """ + Compute the matrix exponential using Pade approximation. + + Parameters + ---------- + A : (M,M) array_like or sparse array + 2D Array or Matrix (sparse or dense) to be exponentiated + + Returns + ------- + expA : (M,M) ndarray + Matrix exponential of `A` + + Notes + ----- + This is algorithm (6.1) which is a simplification of algorithm (5.1). + + .. versionadded:: 0.12.0 + + References + ---------- + .. [1] Awad H. Al-Mohy and Nicholas J. Higham (2009) + "A New Scaling and Squaring Algorithm for the Matrix Exponential." + SIAM Journal on Matrix Analysis and Applications. + 31 (3). pp. 970-989. ISSN 1095-7162 + + Examples + -------- + >>> from scipy.sparse import csc_array + >>> from scipy.sparse.linalg import expm + >>> A = csc_array([[1, 0, 0], [0, 2, 0], [0, 0, 3]]) + >>> A.toarray() + array([[1, 0, 0], + [0, 2, 0], + [0, 0, 3]], dtype=int64) + >>> Aexp = expm(A) + >>> Aexp + + >>> Aexp.toarray() + array([[ 2.71828183, 0. , 0. ], + [ 0. , 7.3890561 , 0. ], + [ 0. , 0. , 20.08553692]]) + """ + return _expm(A, use_exact_onenorm='auto') + + +def _expm(A, use_exact_onenorm): + # Core of expm, separated to allow testing exact and approximate + # algorithms. + + # Avoid indiscriminate asarray() to allow sparse or other strange arrays. + if isinstance(A, list | tuple | np.matrix): + A = np.asarray(A) + if len(A.shape) != 2 or A.shape[0] != A.shape[1]: + raise ValueError('expected a square matrix') + + # gracefully handle size-0 input, + # carefully handling sparse scenario + if A.shape == (0, 0): + out = np.zeros([0, 0], dtype=A.dtype) + if issparse(A) or is_pydata_spmatrix(A): + return A.__class__(out) + return out + + # Trivial case + if A.shape == (1, 1): + out = [[np.exp(A[0, 0])]] + + # Avoid indiscriminate casting to ndarray to + # allow for sparse or other strange arrays + if issparse(A) or is_pydata_spmatrix(A): + return A.__class__(out) + + return np.array(out) + + # Ensure input is of float type, to avoid integer overflows etc. + if ((isinstance(A, np.ndarray) or issparse(A) or is_pydata_spmatrix(A)) + and not np.issubdtype(A.dtype, np.inexact)): + A = A.astype(float) + + # Detect upper triangularity. + structure = UPPER_TRIANGULAR if _is_upper_triangular(A) else None + + if use_exact_onenorm == "auto": + # Hardcode a matrix order threshold for exact vs. estimated one-norms. + use_exact_onenorm = A.shape[0] < 200 + + # Track functions of A to help compute the matrix exponential. + h = _ExpmPadeHelper( + A, structure=structure, use_exact_onenorm=use_exact_onenorm) + + # Try Pade order 3. + eta_1 = max(h.d4_loose, h.d6_loose) + if eta_1 < 1.495585217958292e-002 and _ell(h.A, 3) == 0: + U, V = h.pade3() + return _solve_P_Q(U, V, structure=structure) + + # Try Pade order 5. + eta_2 = max(h.d4_tight, h.d6_loose) + if eta_2 < 2.539398330063230e-001 and _ell(h.A, 5) == 0: + U, V = h.pade5() + return _solve_P_Q(U, V, structure=structure) + + # Try Pade orders 7 and 9. + eta_3 = max(h.d6_tight, h.d8_loose) + if eta_3 < 9.504178996162932e-001 and _ell(h.A, 7) == 0: + U, V = h.pade7() + return _solve_P_Q(U, V, structure=structure) + if eta_3 < 2.097847961257068e+000 and _ell(h.A, 9) == 0: + U, V = h.pade9() + return _solve_P_Q(U, V, structure=structure) + + # Use Pade order 13. + eta_4 = max(h.d8_loose, h.d10_loose) + eta_5 = min(eta_3, eta_4) + theta_13 = 4.25 + + # Choose smallest s>=0 such that 2**(-s) eta_5 <= theta_13 + if eta_5 == 0: + # Nilpotent special case + s = 0 + else: + s = max(int(np.ceil(np.log2(eta_5 / theta_13))), 0) + s = s + _ell(2**-s * h.A, 13) + U, V = h.pade13_scaled(s) + X = _solve_P_Q(U, V, structure=structure) + if structure == UPPER_TRIANGULAR: + # Invoke Code Fragment 2.1. + X = _fragment_2_1(X, h.A, s) + else: + # X = r_13(A)^(2^s) by repeated squaring. + for i in range(s): + X = X.dot(X) + return X + + +def _solve_P_Q(U, V, structure=None): + """ + A helper function for expm_2009. + + Parameters + ---------- + U : ndarray + Pade numerator. + V : ndarray + Pade denominator. + structure : str, optional + A string describing the structure of both matrices `U` and `V`. + Only `upper_triangular` is currently supported. + + Notes + ----- + The `structure` argument is inspired by similar args + for theano and cvxopt functions. + + """ + P = U + V + Q = -U + V + if issparse(U) or is_pydata_spmatrix(U): + return spsolve(Q, P) + elif structure is None: + return solve(Q, P) + elif structure == UPPER_TRIANGULAR: + return solve_triangular(Q, P) + else: + raise ValueError('unsupported matrix structure: ' + str(structure)) + + +def _exp_sinch(a, x): + """ + Stably evaluate exp(a)*sinh(x)/x + + Notes + ----- + The strategy of falling back to a sixth order Taylor expansion + was suggested by the Spallation Neutron Source docs + which was found on the internet by google search. + http://www.ornl.gov/~t6p/resources/xal/javadoc/gov/sns/tools/math/ElementaryFunction.html + The details of the cutoff point and the Horner-like evaluation + was picked without reference to anything in particular. + + Note that sinch is not currently implemented in scipy.special, + whereas the "engineer's" definition of sinc is implemented. + The implementation of sinc involves a scaling factor of pi + that distinguishes it from the "mathematician's" version of sinc. + + """ + + # If x is small then use sixth order Taylor expansion. + # How small is small? I am using the point where the relative error + # of the approximation is less than 1e-14. + # If x is large then directly evaluate sinh(x) / x. + if abs(x) < 0.0135: + x2 = x*x + return np.exp(a) * (1 + (x2/6.)*(1 + (x2/20.)*(1 + (x2/42.)))) + else: + return (np.exp(a + x) - np.exp(a - x)) / (2*x) + + +def _eq_10_42(lam_1, lam_2, t_12): + """ + Equation (10.42) of Functions of Matrices: Theory and Computation. + + Notes + ----- + This is a helper function for _fragment_2_1 of expm_2009. + Equation (10.42) is on page 251 in the section on Schur algorithms. + In particular, section 10.4.3 explains the Schur-Parlett algorithm. + expm([[lam_1, t_12], [0, lam_1]) + = + [[exp(lam_1), t_12*exp((lam_1 + lam_2)/2)*sinch((lam_1 - lam_2)/2)], + [0, exp(lam_2)] + """ + + # The plain formula t_12 * (exp(lam_2) - exp(lam_2)) / (lam_2 - lam_1) + # apparently suffers from cancellation, according to Higham's textbook. + # A nice implementation of sinch, defined as sinh(x)/x, + # will apparently work around the cancellation. + a = 0.5 * (lam_1 + lam_2) + b = 0.5 * (lam_1 - lam_2) + return t_12 * _exp_sinch(a, b) + + +def _fragment_2_1(X, T, s): + """ + A helper function for expm_2009. + + Notes + ----- + The argument X is modified in-place, but this modification is not the same + as the returned value of the function. + This function also takes pains to do things in ways that are compatible + with sparse arrays, for example by avoiding fancy indexing + and by using methods of the matrices whenever possible instead of + using functions of the numpy or scipy libraries themselves. + + """ + # Form X = r_m(2^-s T) + # Replace diag(X) by exp(2^-s diag(T)). + n = X.shape[0] + diag_T = np.ravel(T.diagonal().copy()) + + # Replace diag(X) by exp(2^-s diag(T)). + scale = 2 ** -s + exp_diag = np.exp(scale * diag_T) + for k in range(n): + X[k, k] = exp_diag[k] + + for i in range(s-1, -1, -1): + X = X.dot(X) + + # Replace diag(X) by exp(2^-i diag(T)). + scale = 2 ** -i + exp_diag = np.exp(scale * diag_T) + for k in range(n): + X[k, k] = exp_diag[k] + + # Replace (first) superdiagonal of X by explicit formula + # for superdiagonal of exp(2^-i T) from Eq (10.42) of + # the author's 2008 textbook + # Functions of Matrices: Theory and Computation. + for k in range(n-1): + lam_1 = scale * diag_T[k] + lam_2 = scale * diag_T[k+1] + t_12 = scale * T[k, k+1] + value = _eq_10_42(lam_1, lam_2, t_12) + X[k, k+1] = value + + # Return the updated X matrix. + return X + + +def _ell(A, m): + """ + A helper function for expm_2009. + + Parameters + ---------- + A : linear operator + A linear operator whose norm of power we care about. + m : int + The power of the linear operator + + Returns + ------- + value : int + A value related to a bound. + + """ + if len(A.shape) != 2 or A.shape[0] != A.shape[1]: + raise ValueError('expected A to be like a square matrix') + + # The c_i are explained in (2.2) and (2.6) of the 2005 expm paper. + # They are coefficients of terms of a generating function series expansion. + c_i = {3: 100800., + 5: 10059033600., + 7: 4487938430976000., + 9: 5914384781877411840000., + 13: 113250775606021113483283660800000000. + } + abs_c_recip = c_i[m] + + # This is explained after Eq. (1.2) of the 2009 expm paper. + # It is the "unit roundoff" of IEEE double precision arithmetic. + u = 2**-53 + + # Compute the one-norm of matrix power p of abs(A). + A_abs_onenorm = _onenorm_matrix_power_nnm(abs(A), 2*m + 1) + + # Treat zero norm as a special case. + if not A_abs_onenorm: + return 0 + + alpha = A_abs_onenorm / (_onenorm(A) * abs_c_recip) + log2_alpha_div_u = np.log2(alpha/u) + value = int(np.ceil(log2_alpha_div_u / (2 * m))) + return max(value, 0) + +def matrix_power(A, power): + """ + Raise a square matrix to the integer power, `power`. + + For non-negative integers, ``A**power`` is computed using repeated + matrix multiplications. Negative integers are not supported. + + Parameters + ---------- + A : (M, M) square sparse array or matrix + sparse array that will be raised to power `power` + power : int + Exponent used to raise sparse array `A` + + Returns + ------- + A**power : (M, M) sparse array or matrix + The output matrix will be the same shape as A, and will preserve + the class of A, but the format of the output may be changed. + + Notes + ----- + This uses a recursive implementation of the matrix power. For computing + the matrix power using a reasonably large `power`, this may be less efficient + than computing the product directly, using A @ A @ ... @ A. + This is contingent upon the number of nonzero entries in the matrix. + + .. versionadded:: 1.12.0 + + Examples + -------- + >>> from scipy import sparse + >>> A = sparse.csc_array([[0,1,0],[1,0,1],[0,1,0]]) + >>> A.todense() + array([[0, 1, 0], + [1, 0, 1], + [0, 1, 0]]) + >>> (A @ A).todense() + array([[1, 0, 1], + [0, 2, 0], + [1, 0, 1]]) + >>> A2 = sparse.linalg.matrix_power(A, 2) + >>> A2.todense() + array([[1, 0, 1], + [0, 2, 0], + [1, 0, 1]]) + >>> A4 = sparse.linalg.matrix_power(A, 4) + >>> A4.todense() + array([[2, 0, 2], + [0, 4, 0], + [2, 0, 2]]) + + """ + M, N = A.shape + if M != N: + raise TypeError('sparse matrix is not square') + + if isintlike(power): + power = int(power) + if power < 0: + raise ValueError('exponent must be >= 0') + + if power == 0: + return eye_array(M, dtype=A.dtype) + + if power == 1: + return A.copy() + + tmp = matrix_power(A, power // 2) + if power % 2: + return A @ tmp @ tmp + else: + return tmp @ tmp + else: + raise ValueError("exponent must be an integer") diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_norm.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_norm.py new file mode 100644 index 0000000000000000000000000000000000000000..7ecc009015b32a2a6717ef934ffb4a58f0eca996 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_norm.py @@ -0,0 +1,201 @@ +"""Sparse matrix norms. + +""" +import numpy as np +from scipy.sparse import issparse +from scipy.sparse.linalg import svds +from scipy.sparse._sputils import convert_pydata_sparse_to_scipy +import scipy.sparse as sp + +from numpy import sqrt, abs + +__all__ = ['norm'] + + +def _sparse_frobenius_norm(x): + data = sp._sputils._todata(x) + return np.linalg.norm(data) + + +def norm(x, ord=None, axis=None): + """ + Norm of a sparse matrix. + + This function is able to return one of seven different matrix norms, + depending on the value of the ``ord`` parameter. + + Parameters + ---------- + x : a sparse array + Input sparse array. + ord : {non-zero int, inf, -inf, 'fro'}, optional + Order of the norm (see table under ``Notes``). inf means numpy's + `inf` object. + axis : {int, 2-tuple of ints, None}, optional + If `axis` is an integer, it specifies the axis of `x` along which to + compute the vector norms. If `axis` is a 2-tuple, it specifies the + axes that hold 2-D matrices, and the matrix norms of these matrices + are computed. If `axis` is None then either a vector norm (when `x` + is 1-D) or a matrix norm (when `x` is 2-D) is returned. + + Returns + ------- + n : float or ndarray + The selected norm of `x`. + + Notes + ----- + Some of the ord are not implemented because some associated functions like, + _multi_svd_norm, are not yet available for sparse array. + + This docstring is modified based on numpy.linalg.norm. + https://github.com/numpy/numpy/blob/main/numpy/linalg/linalg.py + + The following norms can be calculated: + + ===== ============================ + ord norm for sparse arrays + ===== ============================ + None Frobenius norm + 'fro' Frobenius norm + inf max(sum(abs(x), axis=1)) + -inf min(sum(abs(x), axis=1)) + 0 abs(x).sum(axis=axis) + 1 max(sum(abs(x), axis=0)) + -1 min(sum(abs(x), axis=0)) + 2 Spectral norm (the largest singular value) + -2 Not implemented + other Not implemented + ===== ============================ + + The Frobenius norm is given by [1]_: + + :math:`||A||_F = [\\sum_{i,j} abs(a_{i,j})^2]^{1/2}` + + References + ---------- + .. [1] G. H. Golub and C. F. Van Loan, *Matrix Computations*, + Baltimore, MD, Johns Hopkins University Press, 1985, pg. 15 + + Examples + -------- + >>> from scipy.sparse import csr_array, diags_array + >>> import numpy as np + >>> from scipy.sparse.linalg import norm + >>> a = np.arange(9) - 4 + >>> a + array([-4, -3, -2, -1, 0, 1, 2, 3, 4]) + >>> b = a.reshape((3, 3)) + >>> b + array([[-4, -3, -2], + [-1, 0, 1], + [ 2, 3, 4]]) + + >>> b = csr_array(b) + >>> norm(b) + 7.745966692414834 + >>> norm(b, 'fro') + 7.745966692414834 + >>> norm(b, np.inf) + 9 + >>> norm(b, -np.inf) + 2 + >>> norm(b, 1) + 7 + >>> norm(b, -1) + 6 + + The matrix 2-norm or the spectral norm is the largest singular + value, computed approximately and with limitations. + + >>> b = diags_array([-1, 1], offsets=[0, 1], shape=(9, 10)) + >>> norm(b, 2) + 1.9753... + """ + x = convert_pydata_sparse_to_scipy(x, target_format="csr") + if not issparse(x): + raise TypeError("input is not sparse. use numpy.linalg.norm") + + # Check the default case first and handle it immediately. + if axis is None and ord in (None, 'fro', 'f'): + return _sparse_frobenius_norm(x) + + # DIA format doesn't support .max() or .min() methods. + if x.format == 'dia': + x = x.tocsr() + + if axis is None: + axis = tuple(range(x.ndim)) + elif not isinstance(axis, tuple): + msg = "'axis' must be None, an integer or a tuple of integers" + try: + int_axis = int(axis) + except TypeError as e: + raise TypeError(msg) from e + if axis != int_axis: + raise TypeError(msg) + axis = (int_axis,) + + nd = x.ndim + if len(axis) == 2: + row_axis, col_axis = axis + if not (-nd <= row_axis < nd and -nd <= col_axis < nd): + message = f'Invalid axis {axis!r} for an array with shape {x.shape!r}' + raise ValueError(message) + if row_axis % nd == col_axis % nd: + raise ValueError('Duplicate axes given.') + if ord == 2: + _, s, _ = svds(x, k=1, solver="arpack", rng=None) + return s[0] + if ord == -2: + raise NotImplementedError + #return _multi_svd_norm(x, row_axis, col_axis, amin) + if ord in (None, 'f', 'fro'): + # The axis order does not matter for this norm. + return _sparse_frobenius_norm(x) + if np.can_cast(x.dtype, float): + x = x.astype(float, copy=False) + if ord == 1: + return abs(x).sum(axis=row_axis).max() + elif ord == np.inf: + return abs(x).sum(axis=col_axis).max() + elif ord == -1: + return abs(x).sum(axis=row_axis).min() + elif ord == -np.inf: + return abs(x).sum(axis=col_axis).min() + else: + raise ValueError("Invalid norm order for matrices.") + elif len(axis) == 1: + a, = axis + if not (-nd <= a < nd): + message = f'Invalid axis {axis!r} for an array with shape {x.shape!r}' + raise ValueError(message) + if ord == 0: + return x.count_nonzero(axis=a) + if np.can_cast(x.dtype, float): + x = x.astype(float, copy=False) + if ord == np.inf: + return _ravel(abs(x).max(axis=a)) + if ord == -np.inf: + return _ravel(abs(x).min(axis=a)) + if ord == 1: + # special case for speedup + return _ravel(abs(x).sum(axis=a)) + if ord in (2, None): + return _ravel(sqrt(abs(x).power(2).sum(axis=a))) + try: + ord + 1 + except TypeError as e: + raise ValueError('Invalid norm order for vectors.') from e + return np.power(abs(x).power(ord).sum(axis=a), 1 / ord) + else: + raise ValueError("Improper number of dimensions to norm.") + + +def _ravel(M): + if hasattr(M, 'toarray'): + return M.toarray().ravel() + elif hasattr(M, 'A'): + return M.A.ravel() + else: + return M.ravel() diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_onenormest.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_onenormest.py new file mode 100644 index 0000000000000000000000000000000000000000..6da6a9faa86294583955fc29549ac8d29819d4b9 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_onenormest.py @@ -0,0 +1,364 @@ +"""Sparse block 1-norm estimator. +""" + +import numpy as np +from scipy.sparse.linalg import aslinearoperator + + +__all__ = ['onenormest'] + + +def onenormest(A, t=2, itmax=5, compute_v=False, compute_w=False): + """ + Compute a lower bound of the 1-norm of a sparse array. + + Parameters + ---------- + A : ndarray or other linear operator + A linear operator that can be transposed and that can + produce matrix products. + t : int, optional + A positive parameter controlling the tradeoff between + accuracy versus time and memory usage. + Larger values take longer and use more memory + but give more accurate output. + itmax : int, optional + Use at most this many iterations. + compute_v : bool, optional + Request a norm-maximizing linear operator input vector if True. + compute_w : bool, optional + Request a norm-maximizing linear operator output vector if True. + + Returns + ------- + est : float + An underestimate of the 1-norm of the sparse array. + v : ndarray, optional + The vector such that ||Av||_1 == est*||v||_1. + It can be thought of as an input to the linear operator + that gives an output with particularly large norm. + w : ndarray, optional + The vector Av which has relatively large 1-norm. + It can be thought of as an output of the linear operator + that is relatively large in norm compared to the input. + + Notes + ----- + This is algorithm 2.4 of [1]_. + + In [2]_ it is described as follows. + "This algorithm typically requires the evaluation of + about 4t matrix-vector products and almost invariably + produces a norm estimate (which is, in fact, a lower + bound on the norm) correct to within a factor 3." + + .. versionadded:: 0.13.0 + + References + ---------- + .. [1] Nicholas J. Higham and Francoise Tisseur (2000), + "A Block Algorithm for Matrix 1-Norm Estimation, + with an Application to 1-Norm Pseudospectra." + SIAM J. Matrix Anal. Appl. Vol. 21, No. 4, pp. 1185-1201. + + .. [2] Awad H. Al-Mohy and Nicholas J. Higham (2009), + "A new scaling and squaring algorithm for the matrix exponential." + SIAM J. Matrix Anal. Appl. Vol. 31, No. 3, pp. 970-989. + + Examples + -------- + >>> import numpy as np + >>> from scipy.sparse import csc_array + >>> from scipy.sparse.linalg import onenormest + >>> A = csc_array([[1., 0., 0.], [5., 8., 2.], [0., -1., 0.]], dtype=float) + >>> A.toarray() + array([[ 1., 0., 0.], + [ 5., 8., 2.], + [ 0., -1., 0.]]) + >>> onenormest(A) + 9.0 + >>> np.linalg.norm(A.toarray(), ord=1) + 9.0 + """ + + # Check the input. + A = aslinearoperator(A) + if A.shape[0] != A.shape[1]: + raise ValueError('expected the operator to act like a square matrix') + + # If the operator size is small compared to t, + # then it is easier to compute the exact norm. + # Otherwise estimate the norm. + n = A.shape[1] + if t >= n: + A_explicit = np.asarray(aslinearoperator(A).matmat(np.identity(n))) + if A_explicit.shape != (n, n): + raise Exception('internal error: ', + 'unexpected shape ' + str(A_explicit.shape)) + col_abs_sums = abs(A_explicit).sum(axis=0) + if col_abs_sums.shape != (n, ): + raise Exception('internal error: ', + 'unexpected shape ' + str(col_abs_sums.shape)) + argmax_j = np.argmax(col_abs_sums) + v = elementary_vector(n, argmax_j) + w = A_explicit[:, argmax_j] + est = col_abs_sums[argmax_j] + else: + est, v, w, nmults, nresamples = _onenormest_core(A, A.H, t, itmax) + + # Report the norm estimate along with some certificates of the estimate. + if compute_v or compute_w: + result = (est,) + if compute_v: + result += (v,) + if compute_w: + result += (w,) + return result + else: + return est + + +def _blocked_elementwise(func): + """ + Decorator for an elementwise function, to apply it blockwise along + first dimension, to avoid excessive memory usage in temporaries. + """ + block_size = 2**20 + + def wrapper(x): + if x.shape[0] < block_size: + return func(x) + else: + y0 = func(x[:block_size]) + y = np.zeros((x.shape[0],) + y0.shape[1:], dtype=y0.dtype) + y[:block_size] = y0 + del y0 + for j in range(block_size, x.shape[0], block_size): + y[j:j+block_size] = func(x[j:j+block_size]) + return y + return wrapper + + +@_blocked_elementwise +def sign_round_up(X): + """ + This should do the right thing for both real and complex matrices. + + From Higham and Tisseur: + "Everything in this section remains valid for complex matrices + provided that sign(A) is redefined as the matrix (aij / |aij|) + (and sign(0) = 1) transposes are replaced by conjugate transposes." + + """ + Y = X.copy() + Y[Y == 0] = 1 + Y /= np.abs(Y) + return Y + + +@_blocked_elementwise +def _max_abs_axis1(X): + return np.max(np.abs(X), axis=1) + + +def _sum_abs_axis0(X): + block_size = 2**20 + r = None + for j in range(0, X.shape[0], block_size): + y = np.sum(np.abs(X[j:j+block_size]), axis=0) + if r is None: + r = y + else: + r += y + return r + + +def elementary_vector(n, i): + v = np.zeros(n, dtype=float) + v[i] = 1 + return v + + +def vectors_are_parallel(v, w): + # Columns are considered parallel when they are equal or negative. + # Entries are required to be in {-1, 1}, + # which guarantees that the magnitudes of the vectors are identical. + if v.ndim != 1 or v.shape != w.shape: + raise ValueError('expected conformant vectors with entries in {-1,1}') + n = v.shape[0] + return np.dot(v, w) == n + + +def every_col_of_X_is_parallel_to_a_col_of_Y(X, Y): + for v in X.T: + if not any(vectors_are_parallel(v, w) for w in Y.T): + return False + return True + + +def column_needs_resampling(i, X, Y=None): + # column i of X needs resampling if either + # it is parallel to a previous column of X or + # it is parallel to a column of Y + n, t = X.shape + v = X[:, i] + if any(vectors_are_parallel(v, X[:, j]) for j in range(i)): + return True + if Y is not None: + if any(vectors_are_parallel(v, w) for w in Y.T): + return True + return False + + +def resample_column(i, X): + X[:, i] = np.random.randint(0, 2, size=X.shape[0])*2 - 1 + + +def less_than_or_close(a, b): + return np.allclose(a, b) or (a < b) + + +def _onenormest_core(A, AT, t, itmax): + """ + Compute a lower bound of the 1-norm of a sparse array. + + Parameters + ---------- + A : ndarray or other linear operator + A linear operator that can produce matrix products. + AT : ndarray or other linear operator + The transpose of A. + t : int, optional + A positive parameter controlling the tradeoff between + accuracy versus time and memory usage. + itmax : int, optional + Use at most this many iterations. + + Returns + ------- + est : float + An underestimate of the 1-norm of the sparse array. + v : ndarray, optional + The vector such that ||Av||_1 == est*||v||_1. + It can be thought of as an input to the linear operator + that gives an output with particularly large norm. + w : ndarray, optional + The vector Av which has relatively large 1-norm. + It can be thought of as an output of the linear operator + that is relatively large in norm compared to the input. + nmults : int, optional + The number of matrix products that were computed. + nresamples : int, optional + The number of times a parallel column was observed, + necessitating a re-randomization of the column. + + Notes + ----- + This is algorithm 2.4. + + """ + # This function is a more or less direct translation + # of Algorithm 2.4 from the Higham and Tisseur (2000) paper. + A_linear_operator = aslinearoperator(A) + AT_linear_operator = aslinearoperator(AT) + if itmax < 2: + raise ValueError('at least two iterations are required') + if t < 1: + raise ValueError('at least one column is required') + n = A.shape[0] + if t >= n: + raise ValueError('t should be smaller than the order of A') + # Track the number of big*small matrix multiplications + # and the number of resamplings. + nmults = 0 + nresamples = 0 + # "We now explain our choice of starting matrix. We take the first + # column of X to be the vector of 1s [...] This has the advantage that + # for a matrix with nonnegative elements the algorithm converges + # with an exact estimate on the second iteration, and such matrices + # arise in applications [...]" + X = np.ones((n, t), dtype=float) + # "The remaining columns are chosen as rand{-1,1}, + # with a check for and correction of parallel columns, + # exactly as for S in the body of the algorithm." + if t > 1: + for i in range(1, t): + # These are technically initial samples, not resamples, + # so the resampling count is not incremented. + resample_column(i, X) + for i in range(t): + while column_needs_resampling(i, X): + resample_column(i, X) + nresamples += 1 + # "Choose starting matrix X with columns of unit 1-norm." + X /= float(n) + # "indices of used unit vectors e_j" + ind_hist = np.zeros(0, dtype=np.intp) + est_old = 0 + S = np.zeros((n, t), dtype=float) + k = 1 + ind = None + while True: + Y = np.asarray(A_linear_operator.matmat(X)) + nmults += 1 + mags = _sum_abs_axis0(Y) + est = np.max(mags) + best_j = np.argmax(mags) + if est > est_old or k == 2: + if k >= 2: + ind_best = ind[best_j] + w = Y[:, best_j] + # (1) + if k >= 2 and est <= est_old: + est = est_old + break + est_old = est + S_old = S + if k > itmax: + break + S = sign_round_up(Y) + del Y + # (2) + if every_col_of_X_is_parallel_to_a_col_of_Y(S, S_old): + break + if t > 1: + # "Ensure that no column of S is parallel to another column of S + # or to a column of S_old by replacing columns of S by rand{-1,1}." + for i in range(t): + while column_needs_resampling(i, S, S_old): + resample_column(i, S) + nresamples += 1 + del S_old + # (3) + Z = np.asarray(AT_linear_operator.matmat(S)) + nmults += 1 + h = _max_abs_axis1(Z) + del Z + # (4) + if k >= 2 and max(h) == h[ind_best]: + break + # "Sort h so that h_first >= ... >= h_last + # and re-order ind correspondingly." + # + # Later on, we will need at most t+len(ind_hist) largest + # entries, so drop the rest + ind = np.argsort(h)[::-1][:t+len(ind_hist)].copy() + del h + if t > 1: + # (5) + # Break if the most promising t vectors have been visited already. + if np.isin(ind[:t], ind_hist).all(): + break + # Put the most promising unvisited vectors at the front of the list + # and put the visited vectors at the end of the list. + # Preserve the order of the indices induced by the ordering of h. + seen = np.isin(ind, ind_hist) + ind = np.concatenate((ind[~seen], ind[seen])) + for j in range(t): + X[:, j] = elementary_vector(n, ind[j]) + + new_ind = ind[:t][~np.isin(ind[:t], ind_hist)] + ind_hist = np.concatenate((ind_hist, new_ind)) + k += 1 + v = elementary_vector(n, ind_best) + return est, v, w, nmults, nresamples diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_propack.cpython-312-x86_64-linux-gnu.so b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_propack.cpython-312-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..08d275924a1835c735f28b19abc7b728e92f02e2 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_propack.cpython-312-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b0fc98a02b3367027b9f22f2afacca2e5ae57b656077e216946398bfc5eb028e +size 149313 diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_special_sparse_arrays.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_special_sparse_arrays.py new file mode 100644 index 0000000000000000000000000000000000000000..ac2014f3d7bce50495a68d2e4861c7bc94139824 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_special_sparse_arrays.py @@ -0,0 +1,952 @@ +import numpy as np +from scipy.sparse.linalg import LinearOperator +from scipy.sparse import kron, eye_array, dia_array + +__all__ = ['LaplacianNd'] +# Sakurai and Mikota classes are intended for tests and benchmarks +# and explicitly not included in the public API of this module. + + +class LaplacianNd(LinearOperator): + """ + The grid Laplacian in ``N`` dimensions and its eigenvalues/eigenvectors. + + Construct Laplacian on a uniform rectangular grid in `N` dimensions + and output its eigenvalues and eigenvectors. + The Laplacian ``L`` is square, negative definite, real symmetric array + with signed integer entries and zeros otherwise. + + Parameters + ---------- + grid_shape : tuple + A tuple of integers of length ``N`` (corresponding to the dimension of + the Lapacian), where each entry gives the size of that dimension. The + Laplacian matrix is square of the size ``np.prod(grid_shape)``. + boundary_conditions : {'neumann', 'dirichlet', 'periodic'}, optional + The type of the boundary conditions on the boundaries of the grid. + Valid values are ``'dirichlet'`` or ``'neumann'``(default) or + ``'periodic'``. + dtype : dtype + Numerical type of the array. Default is ``np.int8``. + + Methods + ------- + toarray() + Construct a dense array from Laplacian data + tosparse() + Construct a sparse array from Laplacian data + eigenvalues(m=None) + Construct a 1D array of `m` largest (smallest in absolute value) + eigenvalues of the Laplacian matrix in ascending order. + eigenvectors(m=None): + Construct the array with columns made of `m` eigenvectors (``float``) + of the ``Nd`` Laplacian corresponding to the `m` ordered eigenvalues. + + .. versionadded:: 1.12.0 + + Notes + ----- + Compared to the MATLAB/Octave implementation [1] of 1-, 2-, and 3-D + Laplacian, this code allows the arbitrary N-D case and the matrix-free + callable option, but is currently limited to pure Dirichlet, Neumann or + Periodic boundary conditions only. + + The Laplacian matrix of a graph (`scipy.sparse.csgraph.laplacian`) of a + rectangular grid corresponds to the negative Laplacian with the Neumann + conditions, i.e., ``boundary_conditions = 'neumann'``. + + All eigenvalues and eigenvectors of the discrete Laplacian operator for + an ``N``-dimensional regular grid of shape `grid_shape` with the grid + step size ``h=1`` are analytically known [2]. + + References + ---------- + .. [1] https://github.com/lobpcg/blopex/blob/master/blopex_\ +tools/matlab/laplacian/laplacian.m + .. [2] "Eigenvalues and eigenvectors of the second derivative", Wikipedia + https://en.wikipedia.org/wiki/Eigenvalues_and_eigenvectors_\ +of_the_second_derivative + + Examples + -------- + >>> import numpy as np + >>> from scipy.sparse.linalg import LaplacianNd + >>> from scipy.sparse import diags_array, csgraph + >>> from scipy.linalg import eigvalsh + + The one-dimensional Laplacian demonstrated below for pure Neumann boundary + conditions on a regular grid with ``n=6`` grid points is exactly the + negative graph Laplacian for the undirected linear graph with ``n`` + vertices using the sparse adjacency matrix ``G`` represented by the + famous tri-diagonal matrix: + + >>> n = 6 + >>> G = diags_array(np.ones(n - 1), offsets=1, format='csr') + >>> Lf = csgraph.laplacian(G, symmetrized=True, form='function') + >>> grid_shape = (n, ) + >>> lap = LaplacianNd(grid_shape, boundary_conditions='neumann') + >>> np.array_equal(lap.matmat(np.eye(n)), -Lf(np.eye(n))) + True + + Since all matrix entries of the Laplacian are integers, ``'int8'`` is + the default dtype for storing matrix representations. + + >>> lap.tosparse() + + >>> lap.toarray() + array([[-1, 1, 0, 0, 0, 0], + [ 1, -2, 1, 0, 0, 0], + [ 0, 1, -2, 1, 0, 0], + [ 0, 0, 1, -2, 1, 0], + [ 0, 0, 0, 1, -2, 1], + [ 0, 0, 0, 0, 1, -1]], dtype=int8) + >>> np.array_equal(lap.matmat(np.eye(n)), lap.toarray()) + True + >>> np.array_equal(lap.tosparse().toarray(), lap.toarray()) + True + + Any number of extreme eigenvalues and/or eigenvectors can be computed. + + >>> lap = LaplacianNd(grid_shape, boundary_conditions='periodic') + >>> lap.eigenvalues() + array([-4., -3., -3., -1., -1., 0.]) + >>> lap.eigenvalues()[-2:] + array([-1., 0.]) + >>> lap.eigenvalues(2) + array([-1., 0.]) + >>> lap.eigenvectors(1) + array([[0.40824829], + [0.40824829], + [0.40824829], + [0.40824829], + [0.40824829], + [0.40824829]]) + >>> lap.eigenvectors(2) + array([[ 0.28867513, 0.40824829], + [ 0.57735027, 0.40824829], + [ 0.28867513, 0.40824829], + [-0.28867513, 0.40824829], + [-0.57735027, 0.40824829], + [-0.28867513, 0.40824829]]) + >>> lap.eigenvectors() + array([[ 0.40824829, 0.5 , 0.28867513, 0.28867513, 0.5 , + 0.40824829], + [-0.40824829, 0. , -0.57735027, 0.57735027, 0. , + 0.40824829], + [ 0.40824829, -0.5 , 0.28867513, 0.28867513, -0.5 , + 0.40824829], + [-0.40824829, 0.5 , 0.28867513, -0.28867513, -0.5 , + 0.40824829], + [ 0.40824829, 0. , -0.57735027, -0.57735027, 0. , + 0.40824829], + [-0.40824829, -0.5 , 0.28867513, -0.28867513, 0.5 , + 0.40824829]]) + + The two-dimensional Laplacian is illustrated on a regular grid with + ``grid_shape = (2, 3)`` points in each dimension. + + >>> grid_shape = (2, 3) + >>> n = np.prod(grid_shape) + + Numeration of grid points is as follows: + + >>> np.arange(n).reshape(grid_shape + (-1,)) + array([[[0], + [1], + [2]], + + [[3], + [4], + [5]]]) + + Each of the boundary conditions ``'dirichlet'``, ``'periodic'``, and + ``'neumann'`` is illustrated separately; with ``'dirichlet'`` + + >>> lap = LaplacianNd(grid_shape, boundary_conditions='dirichlet') + >>> lap.tosparse() + + >>> lap.toarray() + array([[-4, 1, 0, 1, 0, 0], + [ 1, -4, 1, 0, 1, 0], + [ 0, 1, -4, 0, 0, 1], + [ 1, 0, 0, -4, 1, 0], + [ 0, 1, 0, 1, -4, 1], + [ 0, 0, 1, 0, 1, -4]], dtype=int8) + >>> np.array_equal(lap.matmat(np.eye(n)), lap.toarray()) + True + >>> np.array_equal(lap.tosparse().toarray(), lap.toarray()) + True + >>> lap.eigenvalues() + array([-6.41421356, -5. , -4.41421356, -3.58578644, -3. , + -1.58578644]) + >>> eigvals = eigvalsh(lap.toarray().astype(np.float64)) + >>> np.allclose(lap.eigenvalues(), eigvals) + True + >>> np.allclose(lap.toarray() @ lap.eigenvectors(), + ... lap.eigenvectors() @ np.diag(lap.eigenvalues())) + True + + with ``'periodic'`` + + >>> lap = LaplacianNd(grid_shape, boundary_conditions='periodic') + >>> lap.tosparse() + + >>> lap.toarray() + array([[-4, 1, 1, 2, 0, 0], + [ 1, -4, 1, 0, 2, 0], + [ 1, 1, -4, 0, 0, 2], + [ 2, 0, 0, -4, 1, 1], + [ 0, 2, 0, 1, -4, 1], + [ 0, 0, 2, 1, 1, -4]], dtype=int8) + >>> np.array_equal(lap.matmat(np.eye(n)), lap.toarray()) + True + >>> np.array_equal(lap.tosparse().toarray(), lap.toarray()) + True + >>> lap.eigenvalues() + array([-7., -7., -4., -3., -3., 0.]) + >>> eigvals = eigvalsh(lap.toarray().astype(np.float64)) + >>> np.allclose(lap.eigenvalues(), eigvals) + True + >>> np.allclose(lap.toarray() @ lap.eigenvectors(), + ... lap.eigenvectors() @ np.diag(lap.eigenvalues())) + True + + and with ``'neumann'`` + + >>> lap = LaplacianNd(grid_shape, boundary_conditions='neumann') + >>> lap.tosparse() + + >>> lap.toarray() + array([[-2, 1, 0, 1, 0, 0], + [ 1, -3, 1, 0, 1, 0], + [ 0, 1, -2, 0, 0, 1], + [ 1, 0, 0, -2, 1, 0], + [ 0, 1, 0, 1, -3, 1], + [ 0, 0, 1, 0, 1, -2]], dtype=int8) + >>> np.array_equal(lap.matmat(np.eye(n)), lap.toarray()) + True + >>> np.array_equal(lap.tosparse().toarray(), lap.toarray()) + True + >>> lap.eigenvalues() + array([-5., -3., -3., -2., -1., 0.]) + >>> eigvals = eigvalsh(lap.toarray().astype(np.float64)) + >>> np.allclose(lap.eigenvalues(), eigvals) + True + >>> np.allclose(lap.toarray() @ lap.eigenvectors(), + ... lap.eigenvectors() @ np.diag(lap.eigenvalues())) + True + + """ + + def __init__(self, grid_shape, *, + boundary_conditions='neumann', + dtype=np.int8): + + if boundary_conditions not in ('dirichlet', 'neumann', 'periodic'): + raise ValueError( + f"Unknown value {boundary_conditions!r} is given for " + "'boundary_conditions' parameter. The valid options are " + "'dirichlet', 'periodic', and 'neumann' (default)." + ) + + self.grid_shape = grid_shape + self.boundary_conditions = boundary_conditions + # LaplacianNd folds all dimensions in `grid_shape` into a single one + N = np.prod(grid_shape) + super().__init__(dtype=dtype, shape=(N, N)) + + def _eigenvalue_ordering(self, m): + """Compute `m` largest eigenvalues in each of the ``N`` directions, + i.e., up to ``m * N`` total, order them and return `m` largest. + """ + grid_shape = self.grid_shape + if m is None: + indices = np.indices(grid_shape) + Leig = np.zeros(grid_shape) + else: + grid_shape_min = min(grid_shape, + tuple(np.ones_like(grid_shape) * m)) + indices = np.indices(grid_shape_min) + Leig = np.zeros(grid_shape_min) + + for j, n in zip(indices, grid_shape): + if self.boundary_conditions == 'dirichlet': + Leig += -4 * np.sin(np.pi * (j + 1) / (2 * (n + 1))) ** 2 + elif self.boundary_conditions == 'neumann': + Leig += -4 * np.sin(np.pi * j / (2 * n)) ** 2 + else: # boundary_conditions == 'periodic' + Leig += -4 * np.sin(np.pi * np.floor((j + 1) / 2) / n) ** 2 + + Leig_ravel = Leig.ravel() + ind = np.argsort(Leig_ravel) + eigenvalues = Leig_ravel[ind] + if m is not None: + eigenvalues = eigenvalues[-m:] + ind = ind[-m:] + + return eigenvalues, ind + + def eigenvalues(self, m=None): + """Return the requested number of eigenvalues. + + Parameters + ---------- + m : int, optional + The positive number of smallest eigenvalues to return. + If not provided, then all eigenvalues will be returned. + + Returns + ------- + eigenvalues : float array + The requested `m` smallest or all eigenvalues, in ascending order. + """ + eigenvalues, _ = self._eigenvalue_ordering(m) + return eigenvalues + + def _ev1d(self, j, n): + """Return 1 eigenvector in 1d with index `j` + and number of grid points `n` where ``j < n``. + """ + if self.boundary_conditions == 'dirichlet': + i = np.pi * (np.arange(n) + 1) / (n + 1) + ev = np.sqrt(2. / (n + 1.)) * np.sin(i * (j + 1)) + elif self.boundary_conditions == 'neumann': + i = np.pi * (np.arange(n) + 0.5) / n + ev = np.sqrt((1. if j == 0 else 2.) / n) * np.cos(i * j) + else: # boundary_conditions == 'periodic' + if j == 0: + ev = np.sqrt(1. / n) * np.ones(n) + elif j + 1 == n and n % 2 == 0: + ev = np.sqrt(1. / n) * np.tile([1, -1], n//2) + elif (j + 1) % 2 == 0: + i = np.pi * (np.arange(n) + 0.5) / n + ev = np.sqrt(2. / n) * np.sin(i * (j + 1)) + else: + i = np.pi * (np.arange(n) + 0.5) / n + ev = np.sqrt(2. / n) * np.cos(i * j) + # make small values exact zeros correcting round-off errors + # due to symmetry of eigenvectors the exact 0. is correct + ev[np.abs(ev) < np.finfo(np.float64).eps] = 0. + return ev + + def _one_eve(self, k): + """Return 1 eigenvector in Nd with multi-index `j` + as a tensor product of the corresponding 1d eigenvectors. + """ + phi = [self._ev1d(j, n) for j, n in zip(k, self.grid_shape)] + result = phi[0] + for phi in phi[1:]: + result = np.tensordot(result, phi, axes=0) + return np.asarray(result).ravel() + + def eigenvectors(self, m=None): + """Return the requested number of eigenvectors for ordered eigenvalues. + + Parameters + ---------- + m : int, optional + The positive number of eigenvectors to return. If not provided, + then all eigenvectors will be returned. + + Returns + ------- + eigenvectors : float array + An array with columns made of the requested `m` or all eigenvectors. + The columns are ordered according to the `m` ordered eigenvalues. + """ + _, ind = self._eigenvalue_ordering(m) + if m is None: + grid_shape_min = self.grid_shape + else: + grid_shape_min = min(self.grid_shape, + tuple(np.ones_like(self.grid_shape) * m)) + + N_indices = np.unravel_index(ind, grid_shape_min) + N_indices = [tuple(x) for x in zip(*N_indices)] + eigenvectors_list = [self._one_eve(k) for k in N_indices] + return np.column_stack(eigenvectors_list) + + def toarray(self): + """ + Converts the Laplacian data to a dense array. + + Returns + ------- + L : ndarray + The shape is ``(N, N)`` where ``N = np.prod(grid_shape)``. + + """ + grid_shape = self.grid_shape + n = np.prod(grid_shape) + L = np.zeros([n, n], dtype=np.int8) + # Scratch arrays + L_i = np.empty_like(L) + Ltemp = np.empty_like(L) + + for ind, dim in enumerate(grid_shape): + # Start zeroing out L_i + L_i[:] = 0 + # Allocate the top left corner with the kernel of L_i + # Einsum returns writable view of arrays + np.einsum("ii->i", L_i[:dim, :dim])[:] = -2 + np.einsum("ii->i", L_i[: dim - 1, 1:dim])[:] = 1 + np.einsum("ii->i", L_i[1:dim, : dim - 1])[:] = 1 + + if self.boundary_conditions == 'neumann': + L_i[0, 0] = -1 + L_i[dim - 1, dim - 1] = -1 + elif self.boundary_conditions == 'periodic': + if dim > 1: + L_i[0, dim - 1] += 1 + L_i[dim - 1, 0] += 1 + else: + L_i[0, 0] += 1 + + # kron is too slow for large matrices hence the next two tricks + # 1- kron(eye, mat) is block_diag(mat, mat, ...) + # 2- kron(mat, eye) can be performed by 4d stride trick + + # 1- + new_dim = dim + # for block_diag we tile the top left portion on the diagonal + if ind > 0: + tiles = np.prod(grid_shape[:ind]) + for j in range(1, tiles): + L_i[j*dim:(j+1)*dim, j*dim:(j+1)*dim] = L_i[:dim, :dim] + new_dim += dim + # 2- + # we need the keep L_i, but reset the array + Ltemp[:new_dim, :new_dim] = L_i[:new_dim, :new_dim] + tiles = int(np.prod(grid_shape[ind+1:])) + # Zero out the top left, the rest is already 0 + L_i[:new_dim, :new_dim] = 0 + idx = [x for x in range(tiles)] + L_i.reshape( + (new_dim, tiles, + new_dim, tiles) + )[:, idx, :, idx] = Ltemp[:new_dim, :new_dim] + + L += L_i + + return L.astype(self.dtype) + + def tosparse(self): + """ + Constructs a sparse array from the Laplacian data. The returned sparse + array format is dependent on the selected boundary conditions. + + Returns + ------- + L : scipy.sparse.sparray + The shape is ``(N, N)`` where ``N = np.prod(grid_shape)``. + + """ + N = len(self.grid_shape) + p = np.prod(self.grid_shape) + L = dia_array((p, p), dtype=np.int8) + + for i in range(N): + dim = self.grid_shape[i] + data = np.ones([3, dim], dtype=np.int8) + data[1, :] *= -2 + + if self.boundary_conditions == 'neumann': + data[1, 0] = -1 + data[1, -1] = -1 + + L_i = dia_array((data, [-1, 0, 1]), shape=(dim, dim), + dtype=np.int8 + ) + + if self.boundary_conditions == 'periodic': + t = dia_array((dim, dim), dtype=np.int8) + t.setdiag([1], k=-dim+1) + t.setdiag([1], k=dim-1) + L_i += t + + for j in range(i): + L_i = kron(eye_array(self.grid_shape[j], dtype=np.int8), L_i) + for j in range(i + 1, N): + L_i = kron(L_i, eye_array(self.grid_shape[j], dtype=np.int8)) + L += L_i + return L.astype(self.dtype) + + def _matvec(self, x): + grid_shape = self.grid_shape + N = len(grid_shape) + X = x.reshape(grid_shape + (-1,)) + Y = -2 * N * X + for i in range(N): + Y += np.roll(X, 1, axis=i) + Y += np.roll(X, -1, axis=i) + if self.boundary_conditions in ('neumann', 'dirichlet'): + Y[(slice(None),)*i + (0,) + (slice(None),)*(N-i-1) + ] -= np.roll(X, 1, axis=i)[ + (slice(None),) * i + (0,) + (slice(None),) * (N-i-1) + ] + Y[ + (slice(None),) * i + (-1,) + (slice(None),) * (N-i-1) + ] -= np.roll(X, -1, axis=i)[ + (slice(None),) * i + (-1,) + (slice(None),) * (N-i-1) + ] + + if self.boundary_conditions == 'neumann': + Y[ + (slice(None),) * i + (0,) + (slice(None),) * (N-i-1) + ] += np.roll(X, 0, axis=i)[ + (slice(None),) * i + (0,) + (slice(None),) * (N-i-1) + ] + Y[ + (slice(None),) * i + (-1,) + (slice(None),) * (N-i-1) + ] += np.roll(X, 0, axis=i)[ + (slice(None),) * i + (-1,) + (slice(None),) * (N-i-1) + ] + + return Y.reshape(-1, X.shape[-1]) + + def _matmat(self, x): + return self._matvec(x) + + def _adjoint(self): + return self + + def _transpose(self): + return self + + +class Sakurai(LinearOperator): + """ + Construct a Sakurai matrix in various formats and its eigenvalues. + + Constructs the "Sakurai" matrix motivated by reference [1]_: + square real symmetric positive definite and 5-diagonal + with the main diagonal ``[5, 6, 6, ..., 6, 6, 5], the ``+1`` and ``-1`` + diagonals filled with ``-4``, and the ``+2`` and ``-2`` diagonals + made of ``1``. Its eigenvalues are analytically known to be + ``16. * np.power(np.cos(0.5 * k * np.pi / (n + 1)), 4)``. + The matrix gets ill-conditioned with its size growing. + It is useful for testing and benchmarking sparse eigenvalue solvers + especially those taking advantage of its banded 5-diagonal structure. + See the notes below for details. + + Parameters + ---------- + n : int + The size of the matrix. + dtype : dtype + Numerical type of the array. Default is ``np.int8``. + + Methods + ------- + toarray() + Construct a dense array from Laplacian data + tosparse() + Construct a sparse array from Laplacian data + tobanded() + The Sakurai matrix in the format for banded symmetric matrices, + i.e., (3, n) ndarray with 3 upper diagonals + placing the main diagonal at the bottom. + eigenvalues + All eigenvalues of the Sakurai matrix ordered ascending. + + Notes + ----- + Reference [1]_ introduces a generalized eigenproblem for the matrix pair + `A` and `B` where `A` is the identity so we turn it into an eigenproblem + just for the matrix `B` that this function outputs in various formats + together with its eigenvalues. + + .. versionadded:: 1.12.0 + + References + ---------- + .. [1] T. Sakurai, H. Tadano, Y. Inadomi, and U. Nagashima, + "A moment-based method for large-scale generalized + eigenvalue problems", + Appl. Num. Anal. Comp. Math. Vol. 1 No. 2 (2004). + + Examples + -------- + >>> import numpy as np + >>> from scipy.sparse.linalg._special_sparse_arrays import Sakurai + >>> from scipy.linalg import eig_banded + >>> n = 6 + >>> sak = Sakurai(n) + + Since all matrix entries are small integers, ``'int8'`` is + the default dtype for storing matrix representations. + + >>> sak.toarray() + array([[ 5, -4, 1, 0, 0, 0], + [-4, 6, -4, 1, 0, 0], + [ 1, -4, 6, -4, 1, 0], + [ 0, 1, -4, 6, -4, 1], + [ 0, 0, 1, -4, 6, -4], + [ 0, 0, 0, 1, -4, 5]], dtype=int8) + >>> sak.tobanded() + array([[ 1, 1, 1, 1, 1, 1], + [-4, -4, -4, -4, -4, -4], + [ 5, 6, 6, 6, 6, 5]], dtype=int8) + >>> sak.tosparse() + + >>> np.array_equal(sak.dot(np.eye(n)), sak.tosparse().toarray()) + True + >>> sak.eigenvalues() + array([0.03922866, 0.56703972, 2.41789479, 5.97822974, + 10.54287655, 14.45473055]) + >>> sak.eigenvalues(2) + array([0.03922866, 0.56703972]) + + The banded form can be used in scipy functions for banded matrices, e.g., + + >>> e = eig_banded(sak.tobanded(), eigvals_only=True) + >>> np.allclose(sak.eigenvalues(), e, atol= n * n * n * np.finfo(float).eps) + True + + """ + def __init__(self, n, dtype=np.int8): + self.n = n + self.dtype = dtype + shape = (n, n) + super().__init__(dtype, shape) + + def eigenvalues(self, m=None): + """Return the requested number of eigenvalues. + + Parameters + ---------- + m : int, optional + The positive number of smallest eigenvalues to return. + If not provided, then all eigenvalues will be returned. + + Returns + ------- + eigenvalues : `np.float64` array + The requested `m` smallest or all eigenvalues, in ascending order. + """ + if m is None: + m = self.n + k = np.arange(self.n + 1 -m, self.n + 1) + return np.flip(16. * np.power(np.cos(0.5 * k * np.pi / (self.n + 1)), 4)) + + def tobanded(self): + """ + Construct the Sakurai matrix as a banded array. + """ + d0 = np.r_[5, 6 * np.ones(self.n - 2, dtype=self.dtype), 5] + d1 = -4 * np.ones(self.n, dtype=self.dtype) + d2 = np.ones(self.n, dtype=self.dtype) + return np.array([d2, d1, d0]).astype(self.dtype) + + def tosparse(self): + """ + Construct the Sakurai matrix in a sparse format. + """ + from scipy.sparse import diags_array + d = self.tobanded() + # the banded format has the main diagonal at the bottom + # `diags_array` inherits dtype from banded + return diags_array([d[0], d[1], d[2], d[1], d[0]], offsets=[-2, -1, 0, 1, 2], + shape=(self.n, self.n), dtype=d.dtype) + + def toarray(self): + return self.tosparse().toarray() + + def _matvec(self, x): + """ + Construct matrix-free callable banded-matrix-vector multiplication by + the Sakurai matrix without constructing or storing the matrix itself + using the knowledge of its entries and the 5-diagonal format. + """ + x = x.reshape(self.n, -1) + result_dtype = np.promote_types(x.dtype, self.dtype) + sx = np.zeros_like(x, dtype=result_dtype) + sx[0, :] = 5 * x[0, :] - 4 * x[1, :] + x[2, :] + sx[-1, :] = 5 * x[-1, :] - 4 * x[-2, :] + x[-3, :] + sx[1: -1, :] = (6 * x[1: -1, :] - 4 * (x[:-2, :] + x[2:, :]) + + np.pad(x[:-3, :], ((1, 0), (0, 0))) + + np.pad(x[3:, :], ((0, 1), (0, 0)))) + return sx + + def _matmat(self, x): + """ + Construct matrix-free callable matrix-matrix multiplication by + the Sakurai matrix without constructing or storing the matrix itself + by reusing the ``_matvec(x)`` that supports both 1D and 2D arrays ``x``. + """ + return self._matvec(x) + + def _adjoint(self): + return self + + def _transpose(self): + return self + + +class MikotaM(LinearOperator): + """ + Construct a mass matrix in various formats of Mikota pair. + + The mass matrix `M` is square real diagonal + positive definite with entries that are reciprocal to integers. + + Parameters + ---------- + shape : tuple of int + The shape of the matrix. + dtype : dtype + Numerical type of the array. Default is ``np.float64``. + + Methods + ------- + toarray() + Construct a dense array from Mikota data + tosparse() + Construct a sparse array from Mikota data + tobanded() + The format for banded symmetric matrices, + i.e., (1, n) ndarray with the main diagonal. + """ + def __init__(self, shape, dtype=np.float64): + self.shape = shape + self.dtype = dtype + super().__init__(dtype, shape) + + def _diag(self): + # The matrix is constructed from its diagonal 1 / [1, ..., N+1]; + # compute in a function to avoid duplicated code & storage footprint + return (1. / np.arange(1, self.shape[0] + 1)).astype(self.dtype) + + def tobanded(self): + return self._diag() + + def tosparse(self): + from scipy.sparse import diags_array + return diags_array([self._diag()], offsets=[0], + shape=self.shape, dtype=self.dtype) + + def toarray(self): + return np.diag(self._diag()).astype(self.dtype) + + def _matvec(self, x): + """ + Construct matrix-free callable banded-matrix-vector multiplication by + the Mikota mass matrix without constructing or storing the matrix itself + using the knowledge of its entries and the diagonal format. + """ + x = x.reshape(self.shape[0], -1) + return self._diag()[:, np.newaxis] * x + + def _matmat(self, x): + """ + Construct matrix-free callable matrix-matrix multiplication by + the Mikota mass matrix without constructing or storing the matrix itself + by reusing the ``_matvec(x)`` that supports both 1D and 2D arrays ``x``. + """ + return self._matvec(x) + + def _adjoint(self): + return self + + def _transpose(self): + return self + + +class MikotaK(LinearOperator): + """ + Construct a stiffness matrix in various formats of Mikota pair. + + The stiffness matrix `K` is square real tri-diagonal symmetric + positive definite with integer entries. + + Parameters + ---------- + shape : tuple of int + The shape of the matrix. + dtype : dtype + Numerical type of the array. Default is ``np.int32``. + + Methods + ------- + toarray() + Construct a dense array from Mikota data + tosparse() + Construct a sparse array from Mikota data + tobanded() + The format for banded symmetric matrices, + i.e., (2, n) ndarray with 2 upper diagonals + placing the main diagonal at the bottom. + """ + def __init__(self, shape, dtype=np.int32): + self.shape = shape + self.dtype = dtype + super().__init__(dtype, shape) + # The matrix is constructed from its diagonals; + # we precompute these to avoid duplicating the computation + n = shape[0] + self._diag0 = np.arange(2 * n - 1, 0, -2, dtype=self.dtype) + self._diag1 = - np.arange(n - 1, 0, -1, dtype=self.dtype) + + def tobanded(self): + return np.array([np.pad(self._diag1, (1, 0), 'constant'), self._diag0]) + + def tosparse(self): + from scipy.sparse import diags_array + return diags_array([self._diag1, self._diag0, self._diag1], offsets=[-1, 0, 1], + shape=self.shape, dtype=self.dtype) + + def toarray(self): + return self.tosparse().toarray() + + def _matvec(self, x): + """ + Construct matrix-free callable banded-matrix-vector multiplication by + the Mikota stiffness matrix without constructing or storing the matrix + itself using the knowledge of its entries and the 3-diagonal format. + """ + x = x.reshape(self.shape[0], -1) + result_dtype = np.promote_types(x.dtype, self.dtype) + kx = np.zeros_like(x, dtype=result_dtype) + d1 = self._diag1 + d0 = self._diag0 + kx[0, :] = d0[0] * x[0, :] + d1[0] * x[1, :] + kx[-1, :] = d1[-1] * x[-2, :] + d0[-1] * x[-1, :] + kx[1: -1, :] = (d1[:-1, None] * x[: -2, :] + + d0[1: -1, None] * x[1: -1, :] + + d1[1:, None] * x[2:, :]) + return kx + + def _matmat(self, x): + """ + Construct matrix-free callable matrix-matrix multiplication by + the Stiffness mass matrix without constructing or storing the matrix itself + by reusing the ``_matvec(x)`` that supports both 1D and 2D arrays ``x``. + """ + return self._matvec(x) + + def _adjoint(self): + return self + + def _transpose(self): + return self + + +class MikotaPair: + """ + Construct the Mikota pair of matrices in various formats and + eigenvalues of the generalized eigenproblem with them. + + The Mikota pair of matrices [1, 2]_ models a vibration problem + of a linear mass-spring system with the ends attached where + the stiffness of the springs and the masses increase along + the system length such that vibration frequencies are subsequent + integers 1, 2, ..., `n` where `n` is the number of the masses. Thus, + eigenvalues of the generalized eigenvalue problem for + the matrix pair `K` and `M` where `K` is the system stiffness matrix + and `M` is the system mass matrix are the squares of the integers, + i.e., 1, 4, 9, ..., ``n * n``. + + The stiffness matrix `K` is square real tri-diagonal symmetric + positive definite. The mass matrix `M` is diagonal with diagonal + entries 1, 1/2, 1/3, ...., ``1/n``. Both matrices get + ill-conditioned with `n` growing. + + Parameters + ---------- + n : int + The size of the matrices of the Mikota pair. + dtype : dtype + Numerical type of the array. Default is ``np.float64``. + + Attributes + ---------- + eigenvalues : 1D ndarray, ``np.uint64`` + All eigenvalues of the Mikota pair ordered ascending. + + Methods + ------- + MikotaK() + A `LinearOperator` custom object for the stiffness matrix. + MikotaM() + A `LinearOperator` custom object for the mass matrix. + + .. versionadded:: 1.12.0 + + References + ---------- + .. [1] J. Mikota, "Frequency tuning of chain structure multibody oscillators + to place the natural frequencies at omega1 and N-1 integer multiples + omega2,..., omegaN", Z. Angew. Math. Mech. 81 (2001), S2, S201-S202. + Appl. Num. Anal. Comp. Math. Vol. 1 No. 2 (2004). + .. [2] Peter C. Muller and Metin Gurgoze, + "Natural frequencies of a multi-degree-of-freedom vibration system", + Proc. Appl. Math. Mech. 6, 319-320 (2006). + :doi:`10.1002/pamm.200610141`. + + Examples + -------- + >>> import numpy as np + >>> from scipy.sparse.linalg._special_sparse_arrays import MikotaPair + >>> n = 6 + >>> mik = MikotaPair(n) + >>> mik_k = mik.k + >>> mik_m = mik.m + >>> mik_k.toarray() + array([[11., -5., 0., 0., 0., 0.], + [-5., 9., -4., 0., 0., 0.], + [ 0., -4., 7., -3., 0., 0.], + [ 0., 0., -3., 5., -2., 0.], + [ 0., 0., 0., -2., 3., -1.], + [ 0., 0., 0., 0., -1., 1.]]) + >>> mik_k.tobanded() + array([[ 0., -5., -4., -3., -2., -1.], + [11., 9., 7., 5., 3., 1.]]) + >>> mik_m.tobanded() + array([1. , 0.5 , 0.33333333, 0.25 , 0.2 , + 0.16666667]) + >>> mik_k.tosparse() + + >>> mik_m.tosparse() + + >>> np.array_equal(mik_k(np.eye(n)), mik_k.toarray()) + True + >>> np.array_equal(mik_m(np.eye(n)), mik_m.toarray()) + True + >>> mik.eigenvalues() + array([ 1, 4, 9, 16, 25, 36]) + >>> mik.eigenvalues(2) + array([ 1, 4]) + + """ + def __init__(self, n, dtype=np.float64): + self.n = n + self.dtype = dtype + self.shape = (n, n) + self.m = MikotaM(self.shape, self.dtype) + self.k = MikotaK(self.shape, self.dtype) + + def eigenvalues(self, m=None): + """Return the requested number of eigenvalues. + + Parameters + ---------- + m : int, optional + The positive number of smallest eigenvalues to return. + If not provided, then all eigenvalues will be returned. + + Returns + ------- + eigenvalues : `np.uint64` array + The requested `m` smallest or all eigenvalues, in ascending order. + """ + if m is None: + m = self.n + arange_plus1 = np.arange(1, m + 1, dtype=np.uint64) + return arange_plus1 * arange_plus1 diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_svdp.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_svdp.py new file mode 100644 index 0000000000000000000000000000000000000000..17ee447cd58ac96d28180000c645c09889ef8447 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/_svdp.py @@ -0,0 +1,304 @@ +__all__ = ['_svdp'] + +import numpy as np +from scipy.linalg.lapack import HAS_ILP64 + +from scipy.sparse.linalg import aslinearoperator +from scipy.linalg import LinAlgError + +from . import _propack # type: ignore[attr-defined] + + +_lansvd_dict = { + 'f': _propack.slansvd, + 'd': _propack.dlansvd, + 'F': _propack.clansvd, + 'D': _propack.zlansvd, +} + + +_lansvd_irl_dict = { + 'f': _propack.slansvd_irl, + 'd': _propack.dlansvd_irl, + 'F': _propack.clansvd_irl, + 'D': _propack.zlansvd_irl, +} + + +_which_converter = { + 'LM': 1, + 'SM': 0, +} + + +class _AProd: + """ + Wrapper class for linear operator + + The call signature of the __call__ method matches the callback of + the PROPACK routines. + """ + def __init__(self, A): + try: + self.A = aslinearoperator(A) + except TypeError: + self.A = aslinearoperator(np.asarray(A)) + + def __call__(self, transa, m, n, x, y): + if transa == 0: + y[:] = self.A.matvec(x) + else: + y[:] = self.A.rmatvec(x) + + @property + def shape(self): + return self.A.shape + + @property + def dtype(self): + try: + return self.A.dtype + except AttributeError: + return self.A.matvec(np.zeros(self.A.shape[1])).dtype + + +def _svdp(A, k, which='LM', irl_mode=True, kmax=None, + compute_u=True, compute_v=True, v0=None, full_output=False, tol=0, + delta=None, eta=None, anorm=0, cgs=False, elr=True, + min_relgap=0.002, shifts=None, maxiter=None, rng=None): + """ + Compute the singular value decomposition of a linear operator using PROPACK + + Parameters + ---------- + A : array_like, sparse matrix, or LinearOperator + Operator for which SVD will be computed. If `A` is a LinearOperator + object, it must define both ``matvec`` and ``rmatvec`` methods. + k : int + Number of singular values/vectors to compute + which : {"LM", "SM"} + Which singular triplets to compute: + - 'LM': compute triplets corresponding to the `k` largest singular + values + - 'SM': compute triplets corresponding to the `k` smallest singular + values + `which='SM'` requires `irl_mode=True`. Computes largest singular + values by default. + irl_mode : bool, optional + If `True`, then compute SVD using IRL (implicitly restarted Lanczos) + mode. Default is `True`. + kmax : int, optional + Maximal number of iterations / maximal dimension of the Krylov + subspace. Default is ``10 * k``. + compute_u : bool, optional + If `True` (default) then compute left singular vectors, `u`. + compute_v : bool, optional + If `True` (default) then compute right singular vectors, `v`. + tol : float, optional + The desired relative accuracy for computed singular values. + If not specified, it will be set based on machine precision. + v0 : array_like, optional + Starting vector for iterations: must be of length ``A.shape[0]``. + If not specified, PROPACK will generate a starting vector. + full_output : bool, optional + If `True`, then return sigma_bound. Default is `False`. + delta : float, optional + Level of orthogonality to maintain between Lanczos vectors. + Default is set based on machine precision. + eta : float, optional + Orthogonality cutoff. During reorthogonalization, vectors with + component larger than `eta` along the Lanczos vector will be purged. + Default is set based on machine precision. + anorm : float, optional + Estimate of ``||A||``. Default is ``0``. + cgs : bool, optional + If `True`, reorthogonalization is done using classical Gram-Schmidt. + If `False` (default), it is done using modified Gram-Schmidt. + elr : bool, optional + If `True` (default), then extended local orthogonality is enforced + when obtaining singular vectors. + min_relgap : float, optional + The smallest relative gap allowed between any shift in IRL mode. + Default is ``0.001``. Accessed only if ``irl_mode=True``. + shifts : int, optional + Number of shifts per restart in IRL mode. Default is determined + to satisfy ``k <= min(kmax-shifts, m, n)``. Must be + >= 0, but choosing 0 might lead to performance degradation. + Accessed only if ``irl_mode=True``. + maxiter : int, optional + Maximum number of restarts in IRL mode. Default is ``1000``. + Accessed only if ``irl_mode=True``. + rng : `numpy.random.Generator`, optional + Pseudorandom number generator state. When `rng` is None, a new + `numpy.random.Generator` is created using entropy from the + operating system. Types other than `numpy.random.Generator` are + passed to `numpy.random.default_rng` to instantiate a ``Generator``. + + Returns + ------- + u : ndarray + The `k` largest (``which="LM"``) or smallest (``which="SM"``) left + singular vectors, ``shape == (A.shape[0], 3)``, returned only if + ``compute_u=True``. + sigma : ndarray + The top `k` singular values, ``shape == (k,)`` + vt : ndarray + The `k` largest (``which="LM"``) or smallest (``which="SM"``) right + singular vectors, ``shape == (3, A.shape[1])``, returned only if + ``compute_v=True``. + sigma_bound : ndarray + the error bounds on the singular values sigma, returned only if + ``full_output=True``. + + """ + if rng is None: + raise ValueError("`rng` must be a normalized numpy.random.Generator instance") + + which = which.upper() + if which not in {'LM', 'SM'}: + raise ValueError("`which` must be either 'LM' or 'SM'") + if not irl_mode and which == 'SM': + raise ValueError("`which`='SM' requires irl_mode=True") + + aprod = _AProd(A) + typ = aprod.dtype.char + + try: + lansvd_irl = _lansvd_irl_dict[typ] + lansvd = _lansvd_dict[typ] + except KeyError: + # work with non-supported types using native system precision + if np.iscomplexobj(np.empty(0, dtype=typ)): + typ = 'D' + else: + typ = 'd' + lansvd_irl = _lansvd_irl_dict[typ] + lansvd = _lansvd_dict[typ] + + m, n = aprod.shape + if (k < 1) or (k > min(m, n)): + raise ValueError("k must be positive and not greater than m or n") + + if kmax is None: + kmax = 10*k + if maxiter is None: + maxiter = 1000 + + # guard against unnecessarily large kmax + kmax = min(m + 1, n + 1, kmax) + if kmax < k: + raise ValueError( + "kmax must be greater than or equal to k, " + f"but kmax ({kmax}) < k ({k})") + + # convert python args to fortran args + jobu = 1 if compute_u else 0 + jobv = 1 if compute_v else 0 + + # these will be the output arrays + u = np.zeros((m, kmax + 1), order='F', dtype=typ) + v = np.zeros((n, kmax), order='F', dtype=typ) + sigma = np.zeros(k, order='F', dtype=typ.lower()) + bnd = np.zeros(k, order='F', dtype=typ.lower()) + + # Specify the starting vector. if v0 is all zero, PROPACK will generate + # a random starting vector: the random seed cannot be controlled in that + # case, so we'll instead use numpy to generate a random vector + if v0 is None: + u[:, 0] = rng.uniform(size=m) + if np.iscomplexobj(np.empty(0, dtype=typ)): # complex type + u[:, 0] += 1j * rng.uniform(size=m) + else: + try: + u[:, 0] = v0 + except ValueError: + raise ValueError(f"v0 must be of length {m}") + + # process options for the fit + if delta is None: + delta = np.sqrt(np.finfo(typ).eps) + if eta is None: + eta = np.finfo(typ).eps ** 0.75 + + if irl_mode: + doption = np.array((delta, eta, anorm, min_relgap), dtype=typ.lower()) + + # validate or find default shifts + if shifts is None: + shifts = kmax - k + if k > min(kmax - shifts, m, n): + raise ValueError('shifts must satisfy ' + 'k <= min(kmax-shifts, m, n)!') + elif shifts < 0: + raise ValueError('shifts must be >= 0!') + + else: + doption = np.array((delta, eta, anorm), dtype=typ.lower()) + + int_dtype = np.int64 if HAS_ILP64 else np.int32 + ioption = np.array((int(bool(cgs)), int(bool(elr))), dtype=int_dtype) + + # PROPACK uses a few LAPACK functions that require sufficiently large + # work arrays to utilize BLAS level 3 operations. In almost all relevant + # architectures, the blocksize is 32 or 64. We use 32 here to be on + # the conservative side. + NB = 32 + + # Determine lwork & liwork: + # the required lengths are specified in the PROPACK documentation + if compute_u or compute_v: + lwork = m + n + 5*kmax**2 + 9*kmax + 4 + lwork += max(3*kmax**2 + 4*kmax + 4, NB*max(m, n)) + liwork = 8*kmax + else: + lwork = m + n + 9*kmax + 2*kmax**2 + 4 + max(m + n, 4*kmax + 4) + liwork = 2*kmax + 2 + work = np.empty(lwork, dtype=typ.lower()) + iwork = np.empty(liwork, dtype=int_dtype) + + # dummy arguments: these are passed to aprod, and not used in this wrapper + dparm = np.empty(1, dtype=typ.lower()) + iparm = np.empty(1, dtype=int_dtype) + + if typ.isupper(): + zwork = np.empty(m + n + kmax, dtype=typ) + works = work, zwork, iwork + else: + works = work, iwork + + # Generate the seed for the PROPACK random float generator. + # TODO: once `svds` drops legacy positional `random_state` support, + # or once `_lib._util.check_random_state` is adjusted to always + # coerce `RandomState` input to a `Generator` instance + # (possible from NumPy >=2.2), + # the legacy branch can be removed here. + if hasattr(rng, "integers"): + rng_state = rng.integers( + low=0, high=np.iinfo(np.int64).max, size=4, dtype=np.uint64 + ) + else: # legacy np.random.RandomState + rng_state = rng.randint( + low=0, high=np.iinfo(np.int64).max, size=4, dtype=np.uint64 + ) + + if irl_mode: + info = lansvd_irl(_which_converter[which], jobu, + jobv, m, n, shifts, k, maxiter, tol, + aprod, u, sigma, bnd, v, *works, doption, + ioption, dparm, iparm, rng_state) + else: + info = lansvd(jobu, jobv, m, n, k, kmax, tol, aprod, u, sigma, bnd, v, + *works, doption, ioption, dparm, iparm, rng_state) + + if info > 0: + raise LinAlgError( + f"An invariant subspace of dimension {info} was found.") + elif info < 0: + raise LinAlgError( + f"k={k} singular triplets did not converge within " + f"kmax={kmax} iterations") + + # info == 0: The K largest (or smallest) singular triplets were computed + # successfully! + + return u[:, :k], sigma, v[:, :k].conj().T, bnd diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/dsolve.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/dsolve.py new file mode 100644 index 0000000000000000000000000000000000000000..45139f6b280d047386652577d9e1c8d2aaeb7033 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/dsolve.py @@ -0,0 +1,22 @@ +# This file is not meant for public use and will be removed in SciPy v2.0.0. +# Use the `scipy.sparse.linalg` namespace for importing the functions +# included below. + +from scipy._lib.deprecation import _sub_module_deprecation + + +__all__ = [ # noqa: F822 + 'MatrixRankWarning', 'SuperLU', 'factorized', + 'spilu', 'splu', 'spsolve', + 'spsolve_triangular', 'use_solver', 'test' +] + + +def __dir__(): + return __all__ + + +def __getattr__(name): + return _sub_module_deprecation(sub_package="sparse.linalg", module="dsolve", + private_modules=["_dsolve"], all=__all__, + attribute=name) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/eigen.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/eigen.py new file mode 100644 index 0000000000000000000000000000000000000000..588986d6650aad334e6a9a682ed76cef94295298 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/eigen.py @@ -0,0 +1,21 @@ +# This file is not meant for public use and will be removed in SciPy v2.0.0. +# Use the `scipy.sparse.linalg` namespace for importing the functions +# included below. + +from scipy._lib.deprecation import _sub_module_deprecation + + +__all__ = [ # noqa: F822 + 'ArpackError', 'ArpackNoConvergence', 'ArpackError', + 'eigs', 'eigsh', 'lobpcg', 'svds', 'test' +] + + +def __dir__(): + return __all__ + + +def __getattr__(name): + return _sub_module_deprecation(sub_package="sparse.linalg", module="eigen", + private_modules=["_eigen"], all=__all__, + attribute=name) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/interface.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/interface.py new file mode 100644 index 0000000000000000000000000000000000000000..24f40f185b1328b16e7e239e5a165cc6b1ed4317 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/interface.py @@ -0,0 +1,20 @@ +# This file is not meant for public use and will be removed in SciPy v2.0.0. +# Use the `scipy.sparse.linalg` namespace for importing the functions +# included below. + +from scipy._lib.deprecation import _sub_module_deprecation + + +__all__ = [ # noqa: F822 + 'LinearOperator', 'aslinearoperator', +] + + +def __dir__(): + return __all__ + + +def __getattr__(name): + return _sub_module_deprecation(sub_package="sparse.linalg", module="interface", + private_modules=["_interface"], all=__all__, + attribute=name) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/isolve.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/isolve.py new file mode 100644 index 0000000000000000000000000000000000000000..e032ddd9c673be3bc8790adad3bdae1839127050 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/isolve.py @@ -0,0 +1,22 @@ +# This file is not meant for public use and will be removed in SciPy v2.0.0. +# Use the `scipy.sparse.linalg` namespace for importing the functions +# included below. + +from scipy._lib.deprecation import _sub_module_deprecation + + +__all__ = [ # noqa: F822 + 'bicg', 'bicgstab', 'cg', 'cgs', 'gcrotmk', 'gmres', + 'lgmres', 'lsmr', 'lsqr', + 'minres', 'qmr', 'tfqmr', 'test' +] + + +def __dir__(): + return __all__ + + +def __getattr__(name): + return _sub_module_deprecation(sub_package="sparse.linalg", module="isolve", + private_modules=["_isolve"], all=__all__, + attribute=name) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/matfuncs.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/matfuncs.py new file mode 100644 index 0000000000000000000000000000000000000000..8ed877ff1aa6f5a5466ce94729b9225dcce37b36 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/matfuncs.py @@ -0,0 +1,18 @@ +# This file is not meant for public use and will be removed in SciPy v2.0.0. +# Use the `scipy.sparse.linalg` namespace for importing the functions +# included below. + +from scipy._lib.deprecation import _sub_module_deprecation + + +__all__ = ["expm", "inv", "spsolve", "LinearOperator"] # noqa: F822 + + +def __dir__(): + return __all__ + + +def __getattr__(name): + return _sub_module_deprecation(sub_package="sparse.linalg", module="matfuncs", + private_modules=["_matfuncs"], all=__all__, + attribute=name) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/__init__.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dacf8f88cb1c1cca4ae42ad93056c49d20f833c2 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/__pycache__/__init__.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/__pycache__/test_expm_multiply.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/__pycache__/test_expm_multiply.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8c092c6b722aa1cd7050c9ab0faac68cca404e5f Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/__pycache__/test_expm_multiply.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/__pycache__/test_funm_multiply_krylov.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/__pycache__/test_funm_multiply_krylov.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..34a0f284a684333913455f61570a4653630b16ed Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/__pycache__/test_funm_multiply_krylov.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/__pycache__/test_interface.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/__pycache__/test_interface.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4428e20403e5c85f32dea1924be05b50d98c378d Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/__pycache__/test_interface.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/__pycache__/test_matfuncs.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/__pycache__/test_matfuncs.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f410a7ff2631bc19487844a830826d92cac47c27 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/__pycache__/test_matfuncs.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/__pycache__/test_norm.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/__pycache__/test_norm.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fab4a4c0a5b74f7b640561435c4f76e9c5b4f545 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/__pycache__/test_norm.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/__pycache__/test_onenormest.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/__pycache__/test_onenormest.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a69585ed7b0e5afd7d1bc25ecb32d1a38a01fb79 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/__pycache__/test_onenormest.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/__pycache__/test_propack.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/__pycache__/test_propack.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b98ba2ba986ac3160cac729334e54d738de46941 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/__pycache__/test_propack.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/__pycache__/test_pydata_sparse.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/__pycache__/test_pydata_sparse.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3e82ef4ee72ba76a32d89e7616b52316b3296733 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/__pycache__/test_pydata_sparse.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/__pycache__/test_special_sparse_arrays.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/__pycache__/test_special_sparse_arrays.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..82b6179fe4aee63c2bb9c89564983a40c4f848d0 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/__pycache__/test_special_sparse_arrays.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/propack_test_data.npz b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/propack_test_data.npz new file mode 100644 index 0000000000000000000000000000000000000000..9d45bf727f957b84e5f0ad2f2892d66aa4d377da --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/propack_test_data.npz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:25d42e05bb6cb5672db22ebf8df7f113e4cb21b1817491fadc996341a542346d +size 600259 diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/test_expm_multiply.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/test_expm_multiply.py new file mode 100644 index 0000000000000000000000000000000000000000..fa21cd2672727bc49dc7ea270fc00c40d3b0ebc2 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/test_expm_multiply.py @@ -0,0 +1,370 @@ +"""Test functions for the sparse.linalg._expm_multiply module.""" +import warnings + +from functools import partial +from itertools import product + +import numpy as np +import pytest +from numpy.testing import (assert_allclose, assert_, assert_equal) +from scipy.sparse import SparseEfficiencyWarning +import scipy.sparse +from scipy.sparse.linalg import aslinearoperator +import scipy.linalg +from scipy.sparse.linalg import expm as sp_expm +from scipy.sparse.linalg._expm_multiply import (_theta, _compute_p_max, + _onenormest_matrix_power, expm_multiply, _expm_multiply_simple, + _expm_multiply_interval) + + +IMPRECISE = {np.single, np.csingle} +REAL_DTYPES = (np.intc, np.long, np.longlong, + np.float32, np.float64, np.longdouble) +COMPLEX_DTYPES = (np.complex64, np.complex128, np.clongdouble) +DTYPES = REAL_DTYPES + COMPLEX_DTYPES + + +def estimated_warns(): + """If trace is estimated, it should warn. + + We warn that estimation of trace might impact performance. + All result have to be correct nevertheless! + """ + return pytest.warns(UserWarning, match="Trace of LinearOperator not available") + + +def less_than_or_close(a, b): + return np.allclose(a, b) or (a < b) + + +class TestExpmActionSimple: + """ + These tests do not consider the case of multiple time steps in one call. + """ + + def test_theta_monotonicity(self): + pairs = sorted(_theta.items()) + for (m_a, theta_a), (m_b, theta_b) in zip(pairs[:-1], pairs[1:]): + assert_(theta_a < theta_b) + + def test_p_max_default(self): + m_max = 55 + expected_p_max = 8 + observed_p_max = _compute_p_max(m_max) + assert_equal(observed_p_max, expected_p_max) + + def test_p_max_range(self): + for m_max in range(1, 55+1): + p_max = _compute_p_max(m_max) + assert_(p_max*(p_max - 1) <= m_max + 1) + p_too_big = p_max + 1 + assert_(p_too_big*(p_too_big - 1) > m_max + 1) + + def test_onenormest_matrix_power(self): + rng = np.random.RandomState(1234) + n = 40 + nsamples = 10 + for i in range(nsamples): + A = scipy.linalg.inv(rng.randn(n, n)) + for p in range(4): + if not p: + M = np.identity(n) + else: + M = np.dot(M, A) + estimated = _onenormest_matrix_power(A, p) + exact = np.linalg.norm(M, 1) + assert_(less_than_or_close(estimated, exact)) + assert_(less_than_or_close(exact, 3*estimated)) + + def test_expm_multiply(self): + rng = np.random.default_rng(1234) + n = 40 + k = 3 + nsamples = 10 + for i in range(nsamples): + A = scipy.linalg.inv(rng.standard_normal((n, n))) + B = rng.standard_normal((n, k)) + observed = expm_multiply(A, B) + expected = np.dot(sp_expm(A), B) + assert_allclose(observed, expected) + with estimated_warns(): + observed = expm_multiply(aslinearoperator(A), B) + assert_allclose(observed, expected) + traceA = np.trace(A) + observed = expm_multiply(aslinearoperator(A), B, traceA=traceA) + assert_allclose(observed, expected) + + def test_matrix_vector_multiply(self): + rng = np.random.default_rng(1234) + n = 40 + nsamples = 10 + for i in range(nsamples): + A = scipy.linalg.inv(rng.standard_normal((n, n))) + v = rng.standard_normal(n) + observed = expm_multiply(A, v) + expected = np.dot(sp_expm(A), v) + assert_allclose(observed, expected) + with estimated_warns(): + observed = expm_multiply(aslinearoperator(A), v) + assert_allclose(observed, expected) + + def test_scaled_expm_multiply(self): + rng = np.random.default_rng(1234) + n = 40 + k = 3 + nsamples = 10 + for i, t in product(range(nsamples), [0.2, 1.0, 1.5]): + with np.errstate(invalid='ignore'): + A = scipy.linalg.inv(rng.standard_normal((n, n))) + B = rng.standard_normal((n, k)) + observed = _expm_multiply_simple(A, B, t=t) + expected = np.dot(sp_expm(t*A), B) + assert_allclose(observed, expected) + with estimated_warns(): + observed = _expm_multiply_simple(aslinearoperator(A), B, t=t) + assert_allclose(observed, expected) + + def test_scaled_expm_multiply_single_timepoint(self): + rng = np.random.default_rng(1234) + t = 0.1 + n = 5 + k = 2 + A = rng.standard_normal((n, n)) + B = rng.standard_normal((n, k)) + observed = _expm_multiply_simple(A, B, t=t) + expected = sp_expm(t*A).dot(B) + assert_allclose(observed, expected) + with estimated_warns(): + observed = _expm_multiply_simple(aslinearoperator(A), B, t=t) + assert_allclose(observed, expected) + + def test_sparse_expm_multiply(self): + rng = np.random.default_rng(1234) + n = 40 + k = 3 + nsamples = 10 + for i in range(nsamples): + A = scipy.sparse.random_array((n, n), density=0.05, rng=rng) + B = rng.standard_normal((n, k)) + observed = expm_multiply(A, B) + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", + "splu converted its input to CSC format", + SparseEfficiencyWarning, + ) + warnings.filterwarnings( + "ignore", + "spsolve is more efficient when sparse b is in the " + "CSC matrix format", + SparseEfficiencyWarning, + ) + expected = sp_expm(A).dot(B) + assert_allclose(observed, expected) + with estimated_warns(): + observed = expm_multiply(aslinearoperator(A), B) + assert_allclose(observed, expected) + + def test_complex(self): + A = np.array([ + [1j, 1j], + [0, 1j]], dtype=complex) + B = np.array([1j, 1j]) + observed = expm_multiply(A, B) + expected = np.array([ + 1j * np.exp(1j) + 1j * (1j*np.cos(1) - np.sin(1)), + 1j * np.exp(1j)], dtype=complex) + assert_allclose(observed, expected) + with estimated_warns(): + observed = expm_multiply(aslinearoperator(A), B) + assert_allclose(observed, expected) + + +class TestExpmActionInterval: + + @pytest.mark.fail_slow(20) + def test_sparse_expm_multiply_interval(self): + rng = np.random.default_rng(1234) + start = 0.1 + stop = 3.2 + n = 40 + k = 3 + endpoint = True + for num in (14, 13, 2): + A = scipy.sparse.random_array((n, n), density=0.05, rng=rng) + B = rng.standard_normal((n, k)) + v = rng.standard_normal((n,)) + for target in (B, v): + X = expm_multiply(A, target, start=start, stop=stop, + num=num, endpoint=endpoint) + samples = np.linspace(start=start, stop=stop, + num=num, endpoint=endpoint) + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", + "splu converted its input to CSC format", + SparseEfficiencyWarning, + ) + warnings.filterwarnings( + "ignore", + "spsolve is more efficient when sparse b is in" + " the CSC matrix format", + SparseEfficiencyWarning, + ) + for solution, t in zip(X, samples): + assert_allclose(solution, sp_expm(t*A).dot(target)) + + @pytest.mark.fail_slow(20) + def test_expm_multiply_interval_vector(self): + rng = np.random.default_rng(1234) + interval = {'start': 0.1, 'stop': 3.2, 'endpoint': True} + for num, n in product([14, 13, 2], [1, 2, 5, 20, 40]): + A = scipy.linalg.inv(rng.standard_normal((n, n))) + v = rng.standard_normal(n) + samples = np.linspace(num=num, **interval) + X = expm_multiply(A, v, num=num, **interval) + for solution, t in zip(X, samples): + assert_allclose(solution, sp_expm(t*A).dot(v)) + # test for linear operator with unknown trace -> estimate trace + with estimated_warns(): + Xguess = expm_multiply(aslinearoperator(A), v, num=num, **interval) + # test for linear operator with given trace + Xgiven = expm_multiply(aslinearoperator(A), v, num=num, **interval, + traceA=np.trace(A)) + # test robustness for linear operator with wrong trace + Xwrong = expm_multiply(aslinearoperator(A), v, num=num, **interval, + traceA=np.trace(A)*5) + for sol_guess, sol_given, sol_wrong, t in zip(Xguess, Xgiven, + Xwrong, samples): + correct = sp_expm(t*A).dot(v) + assert_allclose(sol_guess, correct) + assert_allclose(sol_given, correct) + assert_allclose(sol_wrong, correct) + + @pytest.mark.fail_slow(20) + def test_expm_multiply_interval_matrix(self): + rng = np.random.default_rng(1234) + interval = {'start': 0.1, 'stop': 3.2, 'endpoint': True} + for num, n, k in product([14, 13, 2], [1, 2, 5, 20, 40], [1, 2]): + A = scipy.linalg.inv(rng.standard_normal((n, n))) + B = rng.standard_normal((n, k)) + samples = np.linspace(num=num, **interval) + X = expm_multiply(A, B, num=num, **interval) + for solution, t in zip(X, samples): + assert_allclose(solution, sp_expm(t*A).dot(B)) + with estimated_warns(): + X = expm_multiply(aslinearoperator(A), B, num=num, **interval) + for solution, t in zip(X, samples): + assert_allclose(solution, sp_expm(t*A).dot(B)) + + def test_sparse_expm_multiply_interval_dtypes(self): + # Test A & B int + A = scipy.sparse.diags_array(np.arange(5),format='csr', dtype=int) + B = np.ones(5, dtype=int) + Aexpm = scipy.sparse.diags_array(np.exp(np.arange(5)),format='csr') + BI = np.identity(5, dtype=int) + BI_sparse = scipy.sparse.csr_array(BI) + assert_allclose(expm_multiply(A,B,0,1)[-1], Aexpm.dot(B)) + assert_allclose(np.diag(expm_multiply(A, BI_sparse, 0, 1)[-1]), Aexpm.dot(B)) + + # Test A complex, B int + A = scipy.sparse.diags_array(-1j*np.arange(5),format='csr', dtype=complex) + B = np.ones(5, dtype=int) + Aexpm = scipy.sparse.diags_array(np.exp(-1j*np.arange(5)),format='csr') + assert_allclose(expm_multiply(A,B,0,1)[-1], Aexpm.dot(B)) + assert_allclose(np.diag(expm_multiply(A, BI_sparse, 0, 1)[-1]), Aexpm.dot(B)) + + # Test A int, B complex + A = scipy.sparse.diags_array(np.arange(5),format='csr', dtype=int) + B = np.full(5, 1j, dtype=complex) + Aexpm = scipy.sparse.diags_array(np.exp(np.arange(5)),format='csr') + assert_allclose(expm_multiply(A,B,0,1)[-1], Aexpm.dot(B)) + BI = np.identity(5, dtype=complex)*1j + assert_allclose( + np.diag(expm_multiply(A, scipy.sparse.csr_array(BI), 0, 1)[-1]), + Aexpm.dot(B) + ) + + def test_expm_multiply_interval_status_0(self): + self._help_test_specific_expm_interval_status(0) + + def test_expm_multiply_interval_status_1(self): + self._help_test_specific_expm_interval_status(1) + + def test_expm_multiply_interval_status_2(self): + self._help_test_specific_expm_interval_status(2) + + def _help_test_specific_expm_interval_status(self, target_status): + rng = np.random.RandomState(1234) + start = 0.1 + stop = 3.2 + num = 13 + endpoint = True + n = 5 + k = 2 + nrepeats = 10 + nsuccesses = 0 + for num in [14, 13, 2] * nrepeats: + A = rng.randn(n, n) + B = rng.randn(n, k) + status = _expm_multiply_interval(A, B, + start=start, stop=stop, num=num, endpoint=endpoint, + status_only=True) + if status == target_status: + X, status = _expm_multiply_interval(A, B, + start=start, stop=stop, num=num, endpoint=endpoint, + status_only=False) + assert_equal(X.shape, (num, n, k)) + samples = np.linspace(start=start, stop=stop, + num=num, endpoint=endpoint) + for solution, t in zip(X, samples): + assert_allclose(solution, sp_expm(t*A).dot(B)) + nsuccesses += 1 + if not nsuccesses: + msg = 'failed to find a status-' + str(target_status) + ' interval' + raise Exception(msg) + + +@pytest.mark.filterwarnings("ignore::DeprecationWarning") +@pytest.mark.parametrize("dtype_a", DTYPES) +@pytest.mark.parametrize("dtype_b", DTYPES) +@pytest.mark.parametrize("b_is_matrix", [False, True]) +def test_expm_multiply_dtype(dtype_a, dtype_b, b_is_matrix): + """Make sure `expm_multiply` handles all numerical dtypes correctly.""" + assert_allclose_ = (partial(assert_allclose, rtol=1.8e-3, atol=1e-5) + if {dtype_a, dtype_b} & IMPRECISE else assert_allclose) + rng = np.random.default_rng(1234) + # test data + n = 7 + b_shape = (n, 3) if b_is_matrix else (n, ) + if dtype_a in REAL_DTYPES: + A = scipy.linalg.inv(rng.random([n, n])).astype(dtype_a) + else: + A = scipy.linalg.inv( + rng.random([n, n]) + 1j*rng.random([n, n]) + ).astype(dtype_a) + if dtype_b in REAL_DTYPES: + B = (2*rng.random(b_shape)).astype(dtype_b) + else: + B = (rng.random(b_shape) + 1j*rng.random(b_shape)).astype(dtype_b) + + # single application + sol_mat = expm_multiply(A, B) + with estimated_warns(): + sol_op = expm_multiply(aslinearoperator(A), B) + direct_sol = np.dot(sp_expm(A), B) + assert_allclose_(sol_mat, direct_sol) + assert_allclose_(sol_op, direct_sol) + sol_op = expm_multiply(aslinearoperator(A), B, traceA=np.trace(A)) + assert_allclose_(sol_op, direct_sol) + + # for time points + interval = {'start': 0.1, 'stop': 3.2, 'num': 13, 'endpoint': True} + samples = np.linspace(**interval) + X_mat = expm_multiply(A, B, **interval) + with estimated_warns(): + X_op = expm_multiply(aslinearoperator(A), B, **interval) + for sol_mat, sol_op, t in zip(X_mat, X_op, samples): + direct_sol = sp_expm(t*A).dot(B) + assert_allclose_(sol_mat, direct_sol) + assert_allclose_(sol_op, direct_sol) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/test_funm_multiply_krylov.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/test_funm_multiply_krylov.py new file mode 100644 index 0000000000000000000000000000000000000000..3fab8a535c402b8b32742f3e28b0d3bff3b3ad2c --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/test_funm_multiply_krylov.py @@ -0,0 +1,185 @@ +"""Test functions for the sparse.linalg._krylov_funm module.""" +from functools import partial + +import numpy as np +import pytest +from numpy.testing import (assert_allclose) +import scipy.sparse +import scipy.linalg +from scipy.sparse.linalg import aslinearoperator +from scipy.linalg import (expm, cosm, coshm, sinm, sinhm) + +from scipy.sparse.linalg._funm_multiply_krylov import funm_multiply_krylov + +REAL_DTYPES = (np.float32, np.float64) +COMPLEX_DTYPES = (np.complex64, np.complex128) +DTYPES = REAL_DTYPES + COMPLEX_DTYPES + +# This the phi_1 function from exponential integrators: phi_1 (z) = (e^z - 1) / z +def custom(X): + return scipy.linalg.solve(X, expm(X) - np.eye(X.shape[0])) + +FUNCS = (expm, cosm, coshm, sinm, sinhm, custom) + +class TestKrylovFunmv: + + def test_krylov_funm_zero_vector(self): + n = 20 + A = np.zeros((n, n)) + b = np.zeros(n) + observed = funm_multiply_krylov(expm, A, b) + expected = np.zeros(n) + assert_allclose(observed, expected) + + @pytest.mark.parametrize("f", FUNCS) + def test_funm_multiply_krylov_nonhermitian_dense(self, f): + rng = np.random.default_rng(1738151906092735) + n = 60 + nsamples = 10 + + for i in range(nsamples): + A = rng.standard_normal((n, n)) + b = rng.standard_normal(n) + + fA = f(A) + expected = fA @ b + observed = funm_multiply_krylov(f, A, b) + assert_allclose(observed, expected, rtol = 1E-6, atol = 1E-8) + observed = funm_multiply_krylov(f, aslinearoperator(A), b) + assert_allclose(observed, expected, rtol = 1E-6, atol = 1E-8) + + @pytest.mark.parametrize("f", FUNCS) + def test_funm_multiply_krylov_nonhermitian_sparse(self, f, num_parallel_threads): + + rng = np.random.default_rng(1738151906092735) + n = 100 + nsamples = 1 + 9 // num_parallel_threads # Very slow otherwise + + for i in range(nsamples): + D = scipy.sparse.diags_array(rng.standard_normal(n)) + A = scipy.sparse.random_array((n, n), density = 0.01, rng = rng) + D + denseA = A.todense() + b = rng.standard_normal(n) + + fA = f(denseA) + expected = fA @ b + observed = funm_multiply_krylov(f, A, b) + assert_allclose(observed, expected, rtol = 1E-6, atol = 1E-8) + observed = funm_multiply_krylov(f, aslinearoperator(A), b) + assert_allclose(observed, expected, rtol = 1E-6, atol = 1E-8) + + @pytest.mark.parametrize("f", FUNCS) + def test_funm_multiply_krylov_hermitian_dense(self, f): + + rng = np.random.default_rng(1738151906092735) + n = 60 + nsamples = 10 + + for i in range(nsamples): + R = np.triu(rng.standard_normal((n, n))) + A = R.T + R + b = rng.standard_normal(n) + + fA = f(A) + expected = fA @ b + observed = funm_multiply_krylov(f, A, b, assume_a = 'her') + assert_allclose(observed, expected, rtol = 1E-6, atol = 1E-8) + observed = funm_multiply_krylov(f, aslinearoperator(A), b, assume_a = 'her') + assert_allclose(observed, expected, rtol = 1E-6, atol = 1E-8) + + @pytest.mark.parametrize("f", FUNCS) + def test_funm_multiply_krylov_hermitian_sparse(self, f, num_parallel_threads): + + rng = np.random.default_rng(1738151906092735) + n = 100 + nsamples = 1 + 9 // num_parallel_threads # Very slow otherwise + + for i in range(nsamples): + D = scipy.sparse.diags_array(rng.standard_normal(n)) + A = scipy.sparse.random_array((n, n), density = 0.01, rng = rng) + R = scipy.sparse.triu(A) + A = R + R.T + D + denseA = A.todense() + b = rng.standard_normal(n) + + fA = f(denseA) + expected = fA @ b + observed = funm_multiply_krylov(f, A, b, assume_a = 'her') + assert_allclose(observed, expected, rtol = 1E-6, atol = 1E-8) + + observed = funm_multiply_krylov(f, aslinearoperator(A), b, assume_a = 'her') + assert_allclose(observed, expected, rtol = 1E-6, atol = 1E-8) + + def test_funm_multiply_krylov_breakdown(self): + rng = np.random.default_rng(1738151906092735) + + # From test_iterative + A = np.array([[0, 0, 0, 0, 0, 1, -1, -0, -0, -0, -0], + [0, 0, 0, 0, 0, 2, -0, -1, -0, -0, -0], + [0, 0, 0, 0, 0, 2, -0, -0, -1, -0, -0], + [0, 0, 0, 0, 0, 2, -0, -0, -0, -1, -0], + [0, 0, 0, 0, 0, 1, -0, -0, -0, -0, -1], + [1, 2, 2, 2, 1, 0, -0, -0, -0, -0, -0], + [-1, 0, 0, 0, 0, 0, -1, -0, -0, -0, -0], + [0, -1, 0, 0, 0, 0, -0, -1, -0, -0, -0], + [0, 0, -1, 0, 0, 0, -0, -0, -1, -0, -0], + [0, 0, 0, -1, 0, 0, -0, -0, -0, -1, -0], + [0, 0, 0, 0, -1, 0, -0, -0, -0, -0, -1]], dtype = float) + b = rng.standard_normal(A.shape[0]) + + fA = expm(A) + expected = fA @ b + observed = funm_multiply_krylov(expm, A, b, restart_every_m = 40) + assert_allclose(observed, expected) + + def test_funm_multiply_krylov_invalid_input(self): + A = np.array([[1, 2], [3, 4]]) # Non-hermitian matrix + b = np.array([1.0, 2.0]) # Ensure 'b' is a 1D array of floats + + # Test for invalid 'b' (not 1D) + b_invalid = np.array([[1.0], [2.0]]) # 2D array + with pytest.raises(ValueError, + match="argument 'b' must be a 1D array."): + funm_multiply_krylov(np.exp, A, b_invalid) + + # Test for invalid restart parameter + with pytest.raises(ValueError, + match="argument 'restart_every_m' must be positive."): + funm_multiply_krylov(np.exp, A, b, restart_every_m=0) + + # Test for invalid max_restarts + with pytest.raises(ValueError, + match="argument 'max_restarts' must be positive."): + funm_multiply_krylov(np.exp, A, b, max_restarts=0) + + # Test for invalid 'assume_a' string + with pytest.raises(ValueError, + match="is not a recognized matrix structure"): + funm_multiply_krylov(np.exp, A, b, assume_a='invalid') + + +@pytest.mark.parametrize("dtype_a", DTYPES) +@pytest.mark.parametrize("dtype_b", DTYPES) +def test_funm_multiply_krylov_types(dtype_a, dtype_b): + assert_allclose_ = (partial(assert_allclose, rtol = 1.8e-3, atol = 1e-5) + if {dtype_a, dtype_b} else assert_allclose) + + rng = np.random.default_rng(1738151906092735) + n = 50 + + if dtype_a in REAL_DTYPES: + A = rng.random([n, n]).astype(dtype_a) + else: + A = (rng.random([n, n]) + 1j * rng.random([n, n])).astype(dtype_a) + + if dtype_b in REAL_DTYPES: + b = (2 * rng.random(n)).astype(dtype_b) + else: + b = (rng.random(n) + 1j * rng.random(n)).astype(dtype_b) + + expA = expm(A) + expected = expA @ b + observed = funm_multiply_krylov(expm, A, b) + assert_allclose_(observed, expected) + observed = funm_multiply_krylov(expm, aslinearoperator(A), b) + assert_allclose_(observed, expected) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/test_interface.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/test_interface.py new file mode 100644 index 0000000000000000000000000000000000000000..2d8ebeb6db20bb63d6f7bb88fe256b2e186d3847 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/test_interface.py @@ -0,0 +1,1297 @@ +"""Test functions for the sparse.linalg._interface module +""" + +import contextlib +from functools import partial +from itertools import product +import operator +from types import ModuleType +from typing import NamedTuple + +import pytest +from pytest import raises as assert_raises, warns +from numpy.testing import assert_, assert_equal + +import numpy as np +from scipy._lib._array_api import ( + SCIPY_ARRAY_API, SCIPY_DEVICE, is_torch, xp_assert_close, is_lazy_array, + xp_assert_equal, xp_ravel, is_numpy, make_xp_pytest_marks, is_jax_array, +) +from scipy._external import array_api_extra as xpx +import scipy.sparse as sparse + +import scipy.sparse.linalg._interface as interface +from scipy.sparse.linalg import LinearOperator, aslinearoperator +from scipy.sparse._sputils import matrix +from scipy._lib._gcutils import assert_deallocated + +pytestmark = make_xp_pytest_marks( + (LinearOperator, "__init__"), + (LinearOperator, "matvec"), + (LinearOperator, "matmat"), + (LinearOperator, "dot"), + (LinearOperator, "rdot"), + (LinearOperator, "rmatvec"), + (LinearOperator, "rmatmat"), + aslinearoperator +) + +def generate_broadcastable_shapes(nshapes, *, ndim=2, min=0, max=10, rng=None): + rng = np.random.default_rng(rng) + min = np.broadcast_to(min, ndim) # so min and max can be scalars or array-like + max = np.broadcast_to(max, ndim) + batch_shape = tuple(rng.integers(min_, max_+1) for min_, max_ in zip(min, max)) + shapes = np.repeat([batch_shape], nshapes, axis=0) + + # make some elements of some shapes 1 (while preserving overall batch shape) + for column in shapes.T: + column[rng.integers(1, nshapes):] = 1 + # permute elements between shapes (while preserving overall batch shape) + shapes = list(rng.permuted(shapes, axis=0)) + # potentially trim preceeding 1s from a shape + for i in range(len(shapes)): + shape = shapes[i] + j = np.where(shape != 1)[0][0] if np.any(shape != 1) else ndim + if rng.random() < 0.25: + shapes[i] = shape[rng.integers(j+1):] + break + + assert np.broadcast_shapes(*shapes) == batch_shape + return [tuple(int(el) for el in shape) for shape in shapes] + + +class TestLinearOperator: + def setup_method(self): + self.A = np.array([[1,2,3], + [4,5,6]]) + self.B = np.array([[1,2], + [3,4], + [5,6]]) + self.C = np.array([[1,2], + [3,4]]) + + def test_matvec(self, xp): + def get_matvecs(A): + return [{ + 'shape': A.shape, + 'dtype': xp.float64, + 'matvec': lambda x: A @ x, + 'rmatvec': lambda x: xp.conj(A.T) @ x, + 'rmatmat': lambda x: xp.conj(A.T) @ x, + 'matmat': lambda x: A @ x + }] + + _asarray = partial(xp.asarray, dtype=xp.complex128) + + for matvecs in get_matvecs(_asarray(self.A)): + A = interface.LinearOperator(**matvecs, xp=xp) + + assert A.args == () + + xp_assert_equal(A.matvec(_asarray([1,2,3])), _asarray([14,32])) + with pytest.warns(FutureWarning, match="column vectors"): + xp_assert_equal( + A.matvec(_asarray([[1],[2],[3]])), + _asarray([[14],[32]]), + ) + xp_assert_equal(A @ _asarray([1,2,3]), _asarray([14,32])) + xp_assert_equal(A @ _asarray([[1],[2],[3]]), _asarray([[14],[32]])) + xp_assert_equal(A.dot(_asarray([1,2,3])), _asarray([14,32])) + xp_assert_equal(A.dot(_asarray([[1],[2],[3]])), _asarray([[14],[32]])) + + if not SCIPY_ARRAY_API: + xp_assert_equal( + A.matvec(matrix([[1],[2],[3]])), + _asarray([[14],[32]]), + ) + xp_assert_equal(A @ matrix([[1],[2],[3]]), _asarray([[14],[32]])) + xp_assert_equal(A.dot(matrix([[1],[2],[3]])), _asarray([[14],[32]])) + + xp_assert_equal((2*A) @ _asarray([1,1,1]), _asarray([12,30])) + xp_assert_equal((2 * A).rmatvec(_asarray([1, 1])), _asarray([10, 14, 18])) + xp_assert_equal((2*A).H.matvec(_asarray([1,1])), _asarray([10, 14, 18])) + xp_assert_equal( + (2*A).adjoint().matvec(_asarray([1,1])), + _asarray([10, 14, 18]), + ) + xp_assert_equal((2*A) @ _asarray([[1],[1],[1]]), _asarray([[12],[30]])) + xp_assert_equal( + (2 * A).matmat(_asarray([[1], [1], [1]])), + _asarray([[12], [30]]), + ) + xp_assert_equal((A*2) @ _asarray([1,1,1]), _asarray([12,30])) + xp_assert_equal((A*2) @ _asarray([[1],[1],[1]]), _asarray([[12],[30]])) + xp_assert_equal((2j*A) @ _asarray([1,1,1]), _asarray([12j,30j])) + xp_assert_equal((A+A) @ _asarray([1,1,1]), _asarray([12, 30])) + xp_assert_equal((A + A).rmatvec(_asarray([1, 1])), _asarray([10, 14, 18])) + xp_assert_equal((A+A).H.matvec(_asarray([1,1])), _asarray([10, 14, 18])) + xp_assert_equal( + (A + A).adjoint().matvec(_asarray([1,1])), + _asarray([10, 14, 18]), + ) + xp_assert_equal((A+A) @ _asarray([[1],[1],[1]]), _asarray([[12], [30]])) + xp_assert_equal( + (A+A).matmat(_asarray([[1],[1],[1]])), + _asarray([[12], [30]]) + ) + xp_assert_equal((-A) @ _asarray([1,1,1]), _asarray([-6,-15])) + xp_assert_equal((-A) @ _asarray([[1],[1],[1]]), _asarray([[-6],[-15]])) + xp_assert_equal((A-A) @ _asarray([1,1,1]), _asarray([0,0])) + xp_assert_equal((A - A) @ _asarray([[1], [1], [1]]), _asarray([[0], [0]])) + + A_ = xp.asarray(self.A, dtype=xp.complex128) + X = xp.asarray([[1, 2], [3, 4]], dtype=xp.complex128) + xp_assert_equal((2 * A).rmatmat(X), (2 * A_).T @ X) + xp_assert_equal((A * 2).rmatmat(X), (A_ * 2).T @ X) + xp_assert_equal((2j * A).rmatmat(X), xp.conj((2j * A_).T) @ X) + xp_assert_equal((A * 2j).rmatmat(X), xp.conj((A_ * 2j).T) @ X) + xp_assert_equal((A + A).rmatmat(X), (A_ + A_).T @ X) + xp_assert_equal((A + 2j * A).rmatmat(X), xp.conj((A_ + 2j * A_).T) @ X) + xp_assert_equal((-A).rmatmat(X), (-A_).T @ X) + xp_assert_equal((A - A).rmatmat(X), (A_ - A_).T @ X) + xp_assert_equal((2j * A).rmatmat(2j * X), xp.conj((2j * A_).T) @ (2j * X)) + + z = A+A + assert len(z.args) == 2 and z.args[0] is A and z.args[1] is A + z = 2*A + assert len(z.args) == 2 + if not is_lazy_array(xp.empty(0)): + # lazy_xp_function breaks object identity + assert z.args[0] is A and z.args[1] == 2 + + array_object = type(xp.empty(0)) + assert isinstance(A.matvec(_asarray([1, 2, 3])), array_object) + ctx = ( + pytest.warns(FutureWarning, match="column vectors") + if not is_jax_array(xp.empty(0)) + else contextlib.nullcontext() + ) + with ctx: + assert isinstance(A.matvec(_asarray([[1],[2],[3]])), array_object) + assert isinstance(A @ _asarray([1,2,3]), array_object) + assert isinstance(A @ _asarray([[1],[2],[3]]), array_object) + assert isinstance(A.dot(_asarray([1,2,3])), array_object) + assert isinstance(A.dot(_asarray([[1],[2],[3]])), array_object) + + if not SCIPY_ARRAY_API: + assert isinstance(A.matvec(matrix([[1],[2],[3]])), array_object) + assert isinstance(A @ matrix([[1],[2],[3]]), array_object) + assert isinstance(A.dot(matrix([[1],[2],[3]])), array_object) + + assert isinstance(2*A, interface._ScaledLinearOperator) + assert isinstance(2j*A, interface._ScaledLinearOperator) + assert isinstance(A+A, interface._SumLinearOperator) + assert isinstance(-A, interface._ScaledLinearOperator) + assert isinstance(A-A, interface._SumLinearOperator) + assert isinstance(A/2, interface._ScaledLinearOperator) + assert isinstance(A/2j, interface._ScaledLinearOperator) + + if not is_lazy_array(xp.empty(0)): + # lazy_xp_function breaks object identity + assert ((A * 3) / 3).args[0] is A # check for simplification + + # Test that prefactor is of _ScaledLinearOperator is not mutated + # when the operator is multiplied by a number + result = A @ _asarray([1, 2, 3]) + B = A * 3 + C = A / 5 + xp_assert_equal(A @ _asarray([1, 2, 3]), result) + + assert (2j * A).dtype == xp.complex128 + + # Test division by non-scalar + msg = "Can only divide a linear operator by a scalar." + with assert_raises(ValueError, match=msg): + _ = A / xp.asarray([1, 2]) + + assert_raises(ValueError, A.matvec, xp.asarray([1,2])) + assert_raises(ValueError, A.matvec, xp.asarray([1,2,3,4])) + assert_raises(ValueError, A.matvec, xp.asarray([[1],[2]])) + assert_raises(ValueError, A.matvec, xp.asarray([[1],[2],[3],[4]])) + + assert_raises(ValueError, lambda: A@A) + assert_raises(ValueError, lambda: A**2) + + for matvecsA, matvecsB in product(get_matvecs(_asarray(self.A)), + get_matvecs(_asarray(self.B))): + A = interface.LinearOperator(**matvecsA, xp=xp) + B = interface.LinearOperator(**matvecsB, xp=xp) + AtimesB = _asarray(self.A @ self.B) + X = _asarray([[1, 2], [3, 4]]) + + xp_assert_equal((A @ B).rmatmat(X), AtimesB.T @ X) + xp_assert_equal((2j * A @ B).rmatmat(X), xp.conj((2j * AtimesB).T) @ X) + + xp_assert_equal((A @ B) @ _asarray([1,1]), _asarray([50,113])) + xp_assert_equal((A @ B) @ _asarray([[1],[1]]), _asarray([[50],[113]])) + xp_assert_equal((A @ B).matmat(_asarray([[1],[1]])), _asarray([[50],[113]])) + + xp_assert_equal((A @ B).rmatvec(_asarray([1, 1])), _asarray([71, 92])) + xp_assert_equal((A @ B).H.matvec(_asarray([1, 1])), _asarray([71, 92])) + xp_assert_equal( + (A @ B).adjoint().matvec(_asarray([1, 1])), + _asarray([71, 92]), + ) + + assert isinstance(A@B, interface._ProductLinearOperator) + + assert_raises(ValueError, lambda: A+B) + assert_raises(ValueError, lambda: A**2) + + z = A@B + assert len(z.args) == 2 + if not is_lazy_array(xp.empty(0)): + # lazy_xp_function breaks object identity + assert z.args[0] is A and z.args[1] is B + + for matvecsC in get_matvecs(_asarray(self.C)): + C = interface.LinearOperator(**matvecsC, xp=xp) + X = _asarray([[1, 2], [3, 4]]) + C_ = _asarray(self.C) + + xp_assert_equal(C.rmatmat(X), (C_).T @ X) + xp_assert_equal((C**2).rmatmat(X), (C_ @ C_).T @ X) + + xp_assert_equal((C**2)@_asarray([1,1]), _asarray([17,37])) + xp_assert_equal((C**2).rmatvec(_asarray([1, 1])), _asarray([22, 32])) + xp_assert_equal((C**2).H.matvec(_asarray([1, 1])), _asarray([22, 32])) + xp_assert_equal( + (C**2).adjoint().matvec(_asarray([1, 1])), + _asarray([22, 32]), + ) + xp_assert_equal((C**2).matmat(_asarray([[1],[1]])), _asarray([[17],[37]])) + + assert isinstance(C**2, interface._PowerLinearOperator) + + @pytest.mark.skip_xp_backends( + "array_api_strict", + reason="https://github.com/data-apis/array-api-strict/issues/188", + ) + def test_matmul(self, xp): + _asarray = partial(xp.asarray, dtype=xp.complex128) + A_ = _asarray(self.A) + D = {'shape': A_.shape, + 'dtype': xp.complex128, + 'matvec': lambda x: xp.reshape(A_ @ x, (A_.shape[0],)), + 'rmatvec': lambda x: xp.reshape(xp.conj(A_.T) @ x, (A_.shape[1],)), + 'rmatmat': lambda x: xp.conj(A_.T) @ x, + 'matmat': lambda x: A_ @ x} + A = interface.LinearOperator(**D, xp=xp) + B = _asarray([[1 + 1j, 2, 3], + [4, 5, 6], + [7, 8, 9]]) + b = B[0, ...] + + xp_assert_equal(operator.matmul(A, b), A * b) + xp_assert_equal( + operator.matmul(A, xp.reshape(b, (-1, 1))), + A * xp.reshape(b, (-1, 1)), + ) + xp_assert_equal(operator.matmul(A, B), A @ B) + xp_assert_equal(operator.matmul(b, A.H), b * A.H) + xp_assert_equal(operator.matmul(b, A.adjoint()), b * A.adjoint()) + xp_assert_equal( + operator.matmul(xp.reshape(b, (1, -1)), A.H), + xp.reshape(b, (1, -1)) * A.H, + ) + xp_assert_equal( + operator.matmul(b.reshape(1, -1), A.adjoint()), + b.reshape(1, -1) * A.adjoint(), + ) + xp_assert_equal(operator.matmul(B, A.H), B @ A.H) + xp_assert_equal(operator.matmul(B, A.adjoint()), B @ A.adjoint()) + assert_raises(ValueError, operator.matmul, A, 2) + assert_raises(ValueError, operator.matmul, 2, A) + + def test_dimension_mismatch(self, xp): + msg = "(D|d)imension mismatch" + A = interface.aslinearoperator(xp.ones((3, 4))) + # matvec + A.matvec(xp.ones(4)) + with pytest.raises(ValueError, match=msg): + A.matvec(xp.ones(3)) + # rmatvec + A.rmatvec(xp.ones(3)) + with pytest.raises(ValueError, match=msg): + A.rmatvec(xp.ones(4)) + # matmat + A.matmat(xp.ones((4, 3))) + with pytest.raises(ValueError, match=msg): + A.matmat(xp.ones((3, 3))) + # rmatmat + A.rmatmat(xp.ones((3, 3))) + with pytest.raises(ValueError, match=msg): + A.rmatmat(xp.ones((4, 4))) + # dot + A.dot(xp.ones((4, 3))) + with pytest.raises(ValueError, match=msg): + A.dot(xp.ones((3, 3))) + # rdot + A.rdot(xp.ones((3, 3))) + with pytest.raises(ValueError, match=msg): + A.rdot(xp.ones((4, 4))) + + +@pytest.mark.skip_xp_backends("dask.array", reason="https://github.com/dask/dask/issues/11711") +class TestDotTests: + """ + This class aims to help ensure correctness of the LinearOperator + interface, by verifying correctness properties based on equivalent + computations using 'forward' and 'adjoint' modes. + """ + class OperatorArgs(NamedTuple): + """ + shape: (core) shape of the operator + op_dtype: dtype of the operator + data_dtype: real dtype corresponding to op_dtype for data generation + complex: the operator has a complex dtype + """ + shape: tuple[int, ...] + op_dtype: str + data_dtype: str + complex: bool + + real_square_args: OperatorArgs = OperatorArgs( + (12, 12), "float64", "float64", False + ) + integer_square_args: OperatorArgs = OperatorArgs( + (9, 9), "int32", "float32", False + ) + complex_square_args: OperatorArgs = OperatorArgs( + (13, 13), "complex64", "float32", True + ) + real_overdetermined_args: OperatorArgs = OperatorArgs( + (17, 11), "float64", "float64", False + ) + complex_overdetermined_args: OperatorArgs = OperatorArgs( + (17, 11), "complex128", "float64", True + ) + real_underdetermined_args: OperatorArgs = OperatorArgs( + (5, 9), "float64", "float64", False + ) + + square_args_list: list[OperatorArgs] = [ + real_square_args, integer_square_args, complex_square_args + ] + all_args_list: list[OperatorArgs] = square_args_list + [ + real_overdetermined_args, complex_overdetermined_args, real_underdetermined_args + ] + + def check_matvec( + self, xp: ModuleType, op: interface.LinearOperator, data_dtype: str, + complex_data: bool = False, + ): + """ + This check verifies the equivalence of the forward and adjoint computation, + using `matvec` and `rmatvec` respectively, on randomised data. + + Data is generated with the real dtype `data_dtype` and operated on by the + linear operator `op`. + + If `complex_data` is set to `True`, complex data is instead generated + by combining randomised real and imaginary components, each generated + with `data_dtype`. + + If `check_operators` is set to `True`, equivalence is checked between + `matvec` and `*` and `@`, + and between `rmatvec` and the composition of `.H` and `*`. + + If `check_dot` is set to `True`, equivalence is checked between + `matvec` and `.dot`, + and between `rmatvec` and the composition of `.H` and `.dot`. + """ + rng = np.random.default_rng(42) + + dtype = np.dtype(data_dtype) + *batch_shape, M, N = op.shape + + u_broadcast, v_broadcast = generate_broadcastable_shapes( + 2, ndim=3, min=0, max=5, rng=63 + ) + + for u_shape, v_shape in ( + # test broadcasting for unbatched RHS + ((N,), (M,)), + # as well as with the batch shape of `op` + 3-D broadcast dims + ((*u_broadcast, *batch_shape, N), (*v_broadcast, *batch_shape, M)) + ): + u = rng.standard_normal(u_shape, dtype=dtype) + v = rng.standard_normal(v_shape, dtype=dtype) + if complex_data: + u = u + (1j * rng.standard_normal(u_shape, dtype=dtype)) + v = v + (1j * rng.standard_normal(v_shape, dtype=dtype)) + u = xp.asarray(u) + v = xp.asarray(v) + + op_u = op.matvec(u) + opH_v = op.rmatvec(v) + + op_u_H_v = xp.vecdot(op_u, v, axis=-1) + uH_opH_v = xp.vecdot(u, opH_v, axis=-1) + + rtol = 1e-12 if np.finfo(data_dtype).eps < 1e-8 else 1e-5 + atol = 2e-15 if np.finfo(data_dtype).eps < 1e-8 else 1e-5 + xp_assert_close(op_u_H_v, uH_opH_v, rtol=rtol, atol=atol) + + def check_matmat( + self, xp, op: interface.LinearOperator, data_dtype: str, + complex_data: bool = False, check_operators: bool = False, + check_dot: bool = False, + ): + """ + This check verifies the equivalence of the forward and adjoint computation, + using `matmat` and `rmatmat` respectively, on randomised data. + + Data is generated with the real dtype `data_dtype` and operated on by the + linear operator `op`. + + If `complex_data` is set to `True`, complex data is instead generated + by combining randomised real and imaginary components, each generated + with `data_dtype`. + + If `check_operators` is set to `True`, equivalence is checked between + `matmat` and `*` and `@`, + and between `rmatvec` and the composition of `.H` and `@`. + + If `check_dot` is set to `True`, equivalence is checked between + `matmat` and `.dot`, + and between `rmatmat` and the composition of `.H` and `.dot`. + """ + rng = np.random.default_rng(42) + k = rng.integers(2, 100) + + dtype = np.dtype(data_dtype) + *batch_shape, M, N = op.shape + + # Test `U` and `V` with the same batch shape as `op` + U = rng.standard_normal(size=(*batch_shape, N, k), dtype=dtype) + V = rng.standard_normal(size=(*batch_shape, M, k), dtype=dtype) + if complex_data: + U = U + (1j * rng.standard_normal(size=(*batch_shape, N, k), dtype=dtype)) + V = V + (1j * rng.standard_normal(size=(*batch_shape, M, k), dtype=dtype)) + U = xp.asarray(U) + V = xp.asarray(V) + + op_U = op.matmat(U) + opH_V = op.rmatmat(V) + + if check_operators: + xp_assert_close(op_U, op * U) + xp_assert_close(op_U, op @ U) + xp_assert_close(opH_V, op.H * V) + xp_assert_close(opH_V, op.H @ V) + + if check_dot: + xp_assert_close(op_U, op.dot(U)) + xp_assert_close(opH_V, op.H.dot(V)) + + op_U_H = xp.conj(op_U).mT + UH = xp.conj(U).mT + + op_U_H_V = xp.matmul(op_U_H, V) + UH_opH_V = xp.matmul(UH, opH_V) + + rtol = 1e-12 if np.finfo(data_dtype).eps < 1e-8 else 1e-5 + atol = 2e-15 if np.finfo(data_dtype).eps < 1e-8 else 1e-5 + xp_assert_close(op_U_H_V, UH_opH_V, rtol=rtol, atol=atol) + + @pytest.mark.parametrize("batch_shape", [(), (3,), (3, 4, 5,), (0,)]) + @pytest.mark.parametrize("args", square_args_list) + def test_identity_square( + self, args: OperatorArgs, batch_shape: tuple[int, ...], xp + ): + """ + Simple identity operator on square matrices. + Tests batches of RHS via `args.batch_shape`. + """ + def identity(x): + shape = (*xpx.broadcast_shapes(batch_shape, x.shape[:-1]), x.shape[-1]) + return xp.broadcast_to(x, shape) + + shape = batch_shape + args.shape + dtype = getattr(xp, args.op_dtype) + op = interface.LinearOperator( # type:ignore[call-arg] + shape=shape, dtype=dtype, + matvec=identity, rmatvec=identity, xp=xp, + ) + + self.check_matvec(xp, op, data_dtype=args.data_dtype, complex_data=args.complex) + self.check_matmat(xp, op, data_dtype=args.data_dtype, complex_data=args.complex) + + @pytest.mark.parametrize("batch_shape", [(), (3,), (3, 4, 5,), (0,)]) + @pytest.mark.parametrize("args", all_args_list) + def test_identity_nonsquare( + self, args: OperatorArgs, batch_shape: tuple[int, ...], xp + ): + """ + Identity operator with zero-padding on non-square matrices. + Tests batches of RHS via `args.batch_shape`. + """ + M, N = args.shape + + def pad_core_dim(x, target_size): + pad_widths = [(0, 0)] * x.ndim # no padding for batch dims + pad_widths[-1] = (0, target_size - x.shape[-1]) # padding for core dim + return xpx.pad(x, pad_widths, mode="constant") + + def mv(x): + match np.sign(x.shape[-1] - M): + case 0: # square + pass + case 1: # crop x to size + x = x[..., :M] + case -1: # pad with zeros + x = pad_core_dim(x, target_size=M) + + shape = (*xpx.broadcast_shapes(batch_shape, x.shape[:-1]), x.shape[-1]) + return xp.broadcast_to(x, shape) + + def rmv(x): + match np.sign(N - x.shape[-1]): + case 0: # square + pass + case 1: # pad with zeros + x = pad_core_dim(x, target_size=N) + case -1: # crop x to size + x = x[..., :N] + + shape = (*xpx.broadcast_shapes(batch_shape, x.shape[:-1]), x.shape[-1]) + return xp.broadcast_to(x, shape) + + shape = batch_shape + args.shape + dtype = getattr(xp, args.op_dtype) + op = interface.LinearOperator( # type:ignore[call-arg] + shape=shape, dtype=dtype, matvec=mv, rmatvec=rmv, xp=xp + ) + + self.check_matvec(xp, op, data_dtype=args.data_dtype, complex_data=args.complex) + self.check_matmat(xp, op, data_dtype=args.data_dtype, complex_data=args.complex) + + @pytest.mark.parametrize("batch_shape", [(), (3,), (3, 4, 5,), (0,)]) + @pytest.mark.parametrize("args", square_args_list) + def test_scaling_square(self, args: OperatorArgs, batch_shape: tuple[int, ...], xp): + """ + Simple (complex) scaling operator on square matrices. + Tests batches of RHS via `args.batch_shape`. + """ + def scale(x): + scale = (3 + 2j) / abs(3 + 2j) + shape = (*xpx.broadcast_shapes(batch_shape, x.shape[:-1]), x.shape[-1]) + return xp.broadcast_to(scale * x, shape) + + def r_scale(x): + scale = (3 - 2j) / abs(3 - 2j) + shape = (*xpx.broadcast_shapes(batch_shape, x.shape[:-1]), x.shape[-1]) + return xp.broadcast_to(scale * x, shape) + + shape = batch_shape + args.shape + dtype = getattr(xp, args.op_dtype) + op = interface.LinearOperator( # type:ignore[call-arg] + shape=shape, dtype=dtype, matvec=scale, rmatvec=r_scale, xp=xp + ) + self.check_matvec(xp, op, data_dtype=args.data_dtype, complex_data=args.complex) + self.check_matmat(xp, op, data_dtype=args.data_dtype, complex_data=args.complex) + + @pytest.mark.parametrize("batch_shape", [(), (3,), (3, 4, 5,), (0,)]) + def test_subclass_matmat(self, batch_shape: tuple[int, ...], xp): + """ + Simple rotation operator defined by `matmat` and `adjoint`, + subclassing `LinearOperator`. + Tests batches of RHS via `batch_shape`. + """ + class RotOp(interface.LinearOperator): + + def __init__(self, dtype, shape, theta): + self._theta = theta + super().__init__(dtype, shape, xp=xp) + + def _matmat(self, X): + theta = self._theta + R = xp.asarray([ + [np.cos(theta), -np.sin(theta)], + [np.sin(theta), np.cos(theta)] + ]) + B = R @ X + shape = xpx.broadcast_shapes(batch_shape, X.shape[:-2]) + X.shape[-2:] + return xp.broadcast_to(B, shape) + + def _adjoint(self): + negative_theta = -self._theta + return RotOp(self.dtype, self.shape, negative_theta) + + theta = xp.pi / 2 + data_dtype = "float64" + op = RotOp(shape=(*batch_shape, 2, 2), dtype=xp.float64, theta=theta) + + self.check_matvec(xp, op, data_dtype=data_dtype, complex_data=False) + self.check_matmat(xp, op, data_dtype=data_dtype, complex_data=False) + + @pytest.mark.parametrize("batch_shape", [(), (3,), (3, 4, 5,), (0,)]) + @pytest.mark.skip_xp_backends("dask.array", reason="https://github.com/dask/dask/issues/11711") + def test_aslinearop_dense( + self, batch_shape: tuple[int, ...], xp + ): + """ + Test operators coming from `aslinearoperator`, + *including batched LHS*. + """ + rng = np.random.default_rng(42) + matrix = xp.asarray( + rng.standard_normal((*batch_shape, 4, 4)), + dtype=xp.complex128, + ) + op = interface.aslinearoperator(matrix) + data_dtype = "float64" + self.check_matvec(xp, op, data_dtype=data_dtype, complex_data=True) + self.check_matmat(xp, op, data_dtype=data_dtype, complex_data=True) + + @pytest.mark.parametrize("batch_shape", [(), (3,), (3, 4, 5,), (0,)]) + @pytest.mark.skip_xp_backends(np_only=True) + def test_aslinearop_sparse(self, batch_shape: tuple[int, ...], xp): + """ + Test operators coming from `aslinearoperator` with sparse arrays, + *including batched LHS* + """ + matrix = sparse.random_array((*batch_shape, 4, 4), dtype=xp.complex128) + op = interface.aslinearoperator(matrix) + data_dtype = "float64" + self.check_matvec(xp, op, data_dtype=data_dtype, complex_data=True) + self.check_matmat(xp, op, data_dtype=data_dtype, complex_data=True) + + +class TestAsLinearOperator: + def make_cases(self, original, dtype, xp=None): + cases = [] + + # Test default implementations of _adjoint and _rmatvec, which + # refer to each other. + _xp = xp if xp is not None else np + def mv(x): + y = original @ x + if len(x.shape) == 2: + y = _xp.reshape(y, (-1, 1)) + return y + + def rmv(x): + return _xp.conj(original.T) @ x + + class BaseMatlike(interface.LinearOperator): + args = () + + def __init__(self, dtype): + super().__init__( + dtype=dtype, + shape=original.shape, + xp=xp, + ) + + def _matvec(self, x): + return mv(x) + + class HasRmatvec(BaseMatlike): + args = () + + def _rmatvec(self,x): + return rmv(x) + + class HasAdjoint(BaseMatlike): + args = () + + def _adjoint(self): + shape = self.shape[1], self.shape[0] + matvec = partial(rmv) + rmatvec = partial(mv) + return interface.LinearOperator(matvec=matvec, + rmatvec=rmatvec, + dtype=dtype, + shape=shape, + xp=xp,) + + class HasRmatmat(HasRmatvec): + def _matmat(self, x): + return original @ x + + def _rmatmat(self, x): + return _xp.conj(original.T) @ x + + if xp: + cases.append((original, original)) + + cases.append((HasRmatvec(dtype), original)) + cases.append((HasAdjoint(dtype), original)) + cases.append((HasRmatmat(dtype), original)) + + cases.append((interface.aslinearoperator(original.T).T, original)) + cases.append(( + interface.aslinearoperator(original.T).H, + xp.conj(original), + )) + cases.append(( + interface.aslinearoperator(original.T).adjoint(), + xp.conj(original)), + ) + + return cases + + cases.append((matrix(original, dtype=dtype), original)) + cases.append((np.array(original, dtype=dtype), original)) + cases.append((sparse.csr_array(original, dtype=dtype), original)) + + cases.append((HasRmatvec(dtype), original)) + cases.append((HasAdjoint(dtype), original)) + cases.append((HasRmatmat(dtype), original)) + return cases + + def setup_method(self): + self.cases = [] + make_cases = self.make_cases + original = np.array([[1,2,3], [4,5,6]]) + self.cases += make_cases(original, np.int32) + self.cases += make_cases(original, np.float32) + self.cases += make_cases(original, np.float64) + self.cases += [(interface.aslinearoperator(M).T, A.T) + for M, A in make_cases(original.T, np.float64)] + self.cases += [(interface.aslinearoperator(M).H, A.T.conj()) + for M, A in make_cases(original.T, np.float64)] + self.cases += [(interface.aslinearoperator(M).adjoint(), A.T.conj()) + for M, A in make_cases(original.T, np.float64)] + + original = np.array([[1, 2j, 3j], [4j, 5j, 6]]) + self.cases += make_cases(original, np.complex128) + self.cases += [(interface.aslinearoperator(M).T, A.T) + for M, A in make_cases(original.T, np.complex128)] + self.cases += [(interface.aslinearoperator(M).H, A.T.conj()) + for M, A in make_cases(original.T, np.complex128)] + self.cases += [(interface.aslinearoperator(M).adjoint(), A.T.conj()) + for M, A in make_cases(original.T, np.complex128)] + + @pytest.mark.skip_xp_backends(np_only=True) + def test_basic(self, xp): + + for M, A_array in self.cases: + A = interface.aslinearoperator(M) + + xs = [np.array([1, 2, 3]), + np.array([[1], [2], [3]])] + ys = [np.array([1, 2]), np.array([[1], [2]])] + + if A.dtype == np.complex128: + xs += [np.array([1, 2j, 3j]), + np.array([[1], [2j], [3j]])] + ys += [np.array([1, 2j]), np.array([[1], [2j]])] + + x2 = np.array([[1, 4], [2, 5], [3, 6]]) + + for x in xs: + ctx = ( + pytest.warns(FutureWarning, match="column vectors") + if x.shape[-1] == 1 + else contextlib.nullcontext() + ) + with ctx: + assert_equal(A.matvec(x), A_array.dot(x)) + + assert_equal(A @ x, A_array.dot(x)) + + assert_equal(A.matmat(x2), A_array.dot(x2)) + assert_equal(A @ x2, A_array.dot(x2)) + + for y in ys: + ctx = ( + pytest.warns(FutureWarning, match="column vectors") + if y.shape[-1] == 1 + else contextlib.nullcontext() + ) + with ctx: + assert_equal(A.rmatvec(y), A_array.T.conj().dot(y)) + assert_equal(A.T.matvec(y), A_array.T.dot(y)) + assert_equal(A.H.matvec(y), A_array.T.conj().dot(y)) + assert_equal(A.adjoint().matvec(y), A_array.T.conj().dot(y)) + + for y in ys: + if y.ndim < 2: + continue + assert_equal(A.rmatmat(y), A_array.T.conj().dot(y)) + assert_equal(A.T.matmat(y), A_array.T.dot(y)) + assert_equal(A.H.matmat(y), A_array.T.conj().dot(y)) + assert_equal(A.adjoint().matmat(y), A_array.T.conj().dot(y)) + + if hasattr(M,'dtype'): + assert_equal(A.dtype, M.dtype) + + assert_(hasattr(A, 'args')) + + @pytest.mark.skip_xp_backends(np_only=True) + def test_dot(self, xp): + + for M, A_array in self.cases: + A = interface.aslinearoperator(M) + + x0 = np.array([1, 2, 3]) + x1 = np.array([[1], [2], [3]]) + x2 = np.array([[1, 4], [2, 5], [3, 6]]) + + assert_equal(A.dot(x0), A_array.dot(x0)) + assert_equal(A.dot(x1), A_array.dot(x1)) + assert_equal(A.dot(x2), A_array.dot(x2)) + + @pytest.mark.parametrize("dtype", ["int64", "float64", "complex128"]) + def test_xp(self, dtype, xp): + if dtype == "int64" and is_torch(xp) and SCIPY_DEVICE != "cpu": + pytest.skip("\"addmm_cuda\" not implemented for 'Long'") + dtype = getattr(xp, dtype) + original = xp.asarray([[1, 2, 3], [4, 5, 6]], dtype=dtype) + for M, A_array in self.make_cases(original, dtype, xp=xp): + A = interface.aslinearoperator(M) + + xs = [xp.asarray([1, 2, 3]), + xp.asarray([[1], [2], [3]])] + ys = [xp.asarray([1, 2]), xp.asarray([[1], [2]])] + + if A.dtype == xp.complex128: + xs += [xp.asarray([1, 2j, 3j]), + xp.asarray([[1], [2j], [3j]])] + ys += [xp.asarray([1, 2j]), xp.asarray([[1], [2j]])] + + x2 = xp.asarray([[1, 4], [2, 5], [3, 6]], dtype=dtype) + + for x in xs: + x = xp.astype(x, dtype) + xp_assert_equal(A @ x, A_array @ x) + + xp_assert_equal(A.matmat(x2), A_array @ x2) + xp_assert_equal(A @ x2, A_array @ x2) + + for y in ys: + if y.ndim < 2: + continue + y = xp.astype(y, dtype) + xp_assert_equal(A.rmatmat(y), xp.conj(A_array.T) @ y) + xp_assert_equal(A.T.matmat(y), xp.conj(A_array.T) @ y) + xp_assert_equal(A.H.matmat(y), xp.conj(A_array.T) @ y) + xp_assert_equal(A.adjoint().matmat(y), xp.conj(A_array.T) @ y) + + if hasattr(M, 'dtype'): + assert_equal(A.dtype, M.dtype) + + assert hasattr(A, 'args') + + +def test_repr(xp): + A = interface.LinearOperator(shape=(1, 1), matvec=lambda x: xp.asarray([1]), xp=xp) + repr_A = repr(A) + assert 'unspecified dtype' not in repr_A, repr_A + + +def test_identity(xp): + ident = interface.IdentityOperator((3, 3), xp=xp) + xp_assert_equal(ident @ xp.asarray([1, 2, 3]), xp.asarray([1, 2, 3])) + xp_assert_equal(xp_ravel(ident.dot(xp.reshape(xp.arange(9), (3, 3)))), xp.arange(9)) + + assert_raises(ValueError, ident.matvec, xp.asarray([1, 2, 3, 4])) + + +def test_attributes(xp): + A = interface.aslinearoperator(xp.reshape(xp.arange(16, dtype=xp.float64), (4, 4))) + + def always_four_ones(x): + x = xp.asarray(x) + assert x.shape == (3,) or x.shape == (3, 1) + return xp.ones(4) + + B = interface.LinearOperator(shape=(4, 3), matvec=always_four_ones, xp=xp) + + ops = [A, B, A * B, A @ B, A.H, A.adjoint(), A + A, B + B, A**4] + for op in ops: + assert hasattr(op, "dtype") + assert hasattr(op, "shape") + assert hasattr(op, "_matvec") + + +def matvec_for_pickle(x): + """ Needed for test_pickle as local functions are not pickleable """ + return x + + +@pytest.mark.skip_xp_backends( + "array_api_strict", + reason="pickle-ability is not guaranteed by the standard" +) +def test_pickle(xp): + import pickle + + protocol_min = 0 if is_numpy(xp) else 2 + for protocol in range(protocol_min, pickle.HIGHEST_PROTOCOL + 1): + A = interface.LinearOperator((3, 3), matvec_for_pickle, xp=xp) + s = pickle.dumps(A, protocol=protocol) + B = pickle.loads(s) + + for k in A.__dict__: + assert getattr(A, k) == getattr(B, k) + + +def test_inheritance(xp): + class Empty(interface.LinearOperator): + pass + + with warns(RuntimeWarning, match="should implement at least"): + assert_raises(TypeError, Empty) + + class Identity(interface.LinearOperator): + def __init__(self, n): + super().__init__(dtype=None, shape=(n, n), xp=xp) + + def _matvec(self, x): + return x + + id3 = Identity(3) + xp_assert_equal(id3.matvec(xp.asarray([1, 2, 3])), xp.asarray([1, 2, 3])) + assert_raises(NotImplementedError, id3.rmatvec, xp.asarray([4, 5, 6])) + + class MatmatOnly(interface.LinearOperator): + def __init__(self, A): + super().__init__(A.dtype, A.shape, xp=xp) + self.A = A + + def _matmat(self, x): + return self.A @ x + + mm = MatmatOnly(xp.asarray(np.random.randn(5, 3))) + assert mm.matvec(xp.asarray(np.random.randn(3))).shape == (5,) + + +def test_dtypes_of_operator_sum(xp): + # gh-6078 + + mat_complex = xp.asarray(np.random.rand(2,2) + 1j * np.random.rand(2,2)) + mat_real = xp.asarray(np.random.rand(2,2)) + + complex_operator = interface.aslinearoperator(mat_complex) + real_operator = interface.aslinearoperator(mat_real) + + sum_complex = complex_operator + complex_operator + sum_real = real_operator + real_operator + + assert sum_real.dtype == xp.float64 + assert sum_complex.dtype == xp.complex128 + + +def test_no_double_init(xp): + call_count = [0] + + def matvec(v): + call_count[0] += 1 + return v + + # It should call matvec exactly once (in order to determine the + # operator dtype) + interface.LinearOperator((2, 2), matvec=matvec, xp=xp) + assert_equal(call_count[0], 1) + + +INT_DTYPES = (np.int8, np.int16, np.int32, np.int64) +REAL_DTYPES = (np.float32, np.float64, np.longdouble) +COMPLEX_DTYPES = (np.complex64, np.complex128, np.clongdouble) +INEXACTDTYPES = REAL_DTYPES + COMPLEX_DTYPES +ALLDTYPES = INT_DTYPES + INEXACTDTYPES + + +@pytest.mark.parametrize("test_dtype", ALLDTYPES) +def test_determine_lo_dtype_from_matvec(test_dtype, xp): + if "longdouble" in test_dtype.__name__ and not is_numpy(xp): + pytest.skip("longdoubles are only tested for `np`") + # gh-19209 + scalar = xp.asarray(np.array(1, dtype=test_dtype)) + def mv(v): + return xp.stack([scalar * v[0], v[1]]) + + lo = interface.LinearOperator((2, 2), matvec=mv, xp=xp) + # expected dtype depends on if mixed exact-inexact promotion is defined + # since dtype determination follows the following procedure: + # - take the dtype from calling `matvec` on an `int8` + # - unless that fails (e.g. via overflow or no mixed exact-inexact promotion), + # in which case use the default integral dtype + expected = scalar.dtype + if xp.isdtype(expected, ("real floating", "complex floating")): + try: + xp.asarray(2) + xp.asarray(2.0) + except TypeError: + expected = xpx.default_dtype(xp, kind="integral") + assert lo.dtype == expected + + +def test_determine_lo_dtype_for_int(xp): + # gh-19209 + # test Python int larger than int8 max cast to some int + def mv(v): + return xp.asarray([128 * v[0], v[1]]) + + lo = interface.LinearOperator((2, 2), matvec=mv, xp=xp) + assert xp.isdtype(lo.dtype, "integral") + + +def test_adjoint_conjugate(xp): + X = xp.asarray([[1j]], dtype=xp.complex128) + A = interface.aslinearoperator(X) + + B = 1j * A + Y = 1j * X + + v = xp.asarray([1], dtype=xp.complex128) + + xp_assert_equal(B.dot(v), xp.vecdot(Y, v)) + xp_assert_equal(B.H.dot(v), xp.vecdot(xp.conj(Y.T), v)) + xp_assert_equal(B.adjoint().dot(v), xp.vecdot(xp.conj(Y.T), v)) + + +def test_ndim(xp): + X = xp.asarray([[1]]) + A = interface.aslinearoperator(X) + assert A.ndim == 2 + + +def test_transpose_noconjugate(xp): + X = xp.asarray([[1j]], dtype=xp.complex128) + A = interface.aslinearoperator(X) + + B = 1j * A + Y = 1j * X + + v = xp.asarray([1], dtype=xp.complex128) + + xp_assert_equal(B.dot(v), xp.vecdot(Y, v)) + xp_assert_equal(B.T.dot(v), xp.vecdot(Y.T, v)) + + +@pytest.mark.skip_xp_backends("dask.array", reason="https://github.com/dask/dask/issues/11711") +@pytest.mark.skip_xp_backends( + "array_api_strict", + reason="https://github.com/data-apis/array-api-strict/issues/188" +) +def test_transpose_multiplication(xp): + _asarray = partial(xp.asarray, dtype=xp.complex128) + class MyMatrix(interface.LinearOperator): + def __init__(self, A): + super().__init__(A.dtype, A.shape, xp=xp) + self.A = A + def _matmat(self, other): return self.A @ other + def _rmatmat(self, other): return self.A.mT @ other + + A = MyMatrix(_asarray([[1, 2], [3, 4]])) + X = _asarray([1, 2]) + X_T = X + B = _asarray([[10, 20], [30, 40]]) + X2 = xp.reshape(X, (-1, 1)) + Y = _asarray([[1, 2], [3, 4]]) + + xp_assert_equal(A @ B, Y @ B) + xp_assert_equal(B.T @ A, B.T @ Y) + xp_assert_equal(A.T @ B, Y.mT @ B) + xp_assert_equal(A @ X, Y @ X) + xp_assert_equal(X_T @ A, X_T @ Y) + xp_assert_equal(A.T @ X, Y.mT @ X) + xp_assert_equal(A @ X2, Y @ X2) + xp_assert_equal(X2.mT @ A, X2.mT @ Y) + xp_assert_equal(A.T @ X2, Y.mT @ X2) + + +@pytest.mark.skip_xp_backends(np_only=True) +def test_sparse_matmat_exception(): + B = sparse.eye_array(2) + # well defined matmat via `aslinearoperator` + A = interface.aslinearoperator(sparse.eye_array(2)) + assert isinstance(A @ B, sparse.sparray) + assert isinstance(B @ A, sparse.sparray) + xp_assert_equal((A @ B).toarray(), np.eye(2)) + xp_assert_equal((B @ A).toarray(), np.eye(2)) + # ill-defined matmat via default fallback to matvec + A = interface.LinearOperator((2, 2), matvec=lambda x: x) + msg = "Try wrapping the matrix with `aslinearoperator` first." + with assert_raises(TypeError, match=msg): + A @ B + with assert_raises(TypeError, match=msg): + B @ A + # after using `aslinearoperator` + B = interface.aslinearoperator(B) + assert isinstance(A @ B, interface.LinearOperator) + assert isinstance(B @ A, interface.LinearOperator) + xp_assert_equal((A @ B).matvec(np.ones(2)), np.ones(2)) + + +@pytest.mark.skip_xp_backends( + "jax.numpy", reason="remaining reference (due to lazy_xp_function?)" +) +def test_MatrixLinearOperator_refcycle(xp): + # gh-10634 + # Test that MatrixLinearOperator can be automatically garbage collected + A = xp.eye(2) + with assert_deallocated(interface.MatrixLinearOperator, A, xp) as op: + op.adjoint() + del op + + +@pytest.mark.parametrize("left", [False, True]) +@pytest.mark.parametrize("operator_definition", ["subclass_matvec", "subclass_matmat", + "__init__matvec", "__init__matmat", + "aslinearoperator"]) +@pytest.mark.parametrize("batch_A", [(), (5,), (0,)]) +@pytest.mark.parametrize("batch_x", [(), (6, 1), (0, 1)]) +@pytest.mark.parametrize("dtype", [np.float64, np.complex128]) +@pytest.mark.skip_xp_backends("dask.array", reason="https://github.com/dask/dask/issues/11711") +@pytest.mark.skip_xp_backends( + "array_api_strict", + reason="https://github.com/data-apis/array-api-strict/issues/188" +) +def test_batch(left, operator_definition, batch_A, batch_x, dtype, xp): + # TODO ideas: + # - test lower-precision types + # - test `transpose`, `adjoint`, `__mul__`, etc. + # - test composite LinearOperators + rng = np.random.default_rng(41981392342349823) + + m, n, k = 4, 3, 2 + A_ = rng.random((*batch_A, m, n)) + x_row = rng.random((*batch_x, n if left else m)) + x_col = rng.random((*batch_x, n if left else m, 1)) + x_mat = rng.random((*batch_x, n if left else m, k)) + + if dtype == np.complex128: + A_ = A_ + 1j * rng.random(A_.shape) + x_row = x_row + 1j * rng.random(x_row.shape) + x_col = x_col + 1j * rng.random(x_col.shape) + x_mat = x_mat + 1j * rng.random(x_mat.shape) + + A_, x_row, x_col, x_mat = (xp.asarray(x) for x in (A_, x_row, x_col, x_mat)) + + def matvec(A, x): + if not batch_x: + assert x.ndim == 1 + return A @ x + else: + # It might make it easier on the author of LinearOperators to + # "ravel" all batch dimensions into one before calling their `matvec` + # implementation. That way, they only have to think about vectorizing + # w.r.t. one batch dimension rather than an arbitrary number of batch + # dimensions. In this case, `x.ndim` would be exactly 2. + assert x.ndim >= 2 + return (A @ (x[..., xp.newaxis]))[..., 0] + + def matmat(A, X): + if not batch_x: + assert X.ndim == 2 + else: + # Similar to above, we could ravel all batch dimensions before calling + # the user's `matmat`. Then `x.ndim` would be exactly 3. + assert X.ndim >= 3 + return A @ X + + if operator_definition == "aslinearoperator": + A = interface.aslinearoperator(A_) + elif operator_definition == "__init__matvec": + A = interface.LinearOperator( + shape=A_.shape, dtype=A_.dtype, + matvec=lambda x: matvec(A_, x), + rmatvec=lambda x: matvec(xp.conj(A_.mT), x), + xp=xp, + ) + elif operator_definition == "__init__matmat": + # A = interface.LinearOperator(shape=A_.shape, dtype=A_.dtype, + # matmat=lambda X: matmat(A_, X)) + pytest.skip("should work but doesn't - see gh-24510") + elif operator_definition == "subclass_matvec": + class MyLinearOperator(interface.LinearOperator): + def _matvec(self, x): + return matvec(A_, x) + def _rmatvec(self, x): + return matvec(xp.conj(A_.mT), x) + A = MyLinearOperator(shape=A_.shape, dtype=A_.dtype, xp=xp) + elif operator_definition == "subclass_matmat": + class MyLinearOperator(interface.LinearOperator): + def _matmat(self, X): + return matmat(A_, X) + def _rmatmat(self, X): + return matmat(xp.conj(A_.mT), X) + A = MyLinearOperator(shape=A_.shape, dtype=A_.dtype, xp=xp) + + # Test matvec + # a. with row vector (or batch of row vectors) + xp_assert_close( + A.matvec(x_row) if left else A.rmatvec(x_row), + matvec(A_, x_row) if left else matvec(xp.conj(A_.mT), x_row) + ) + # b. with column vector (or batch of column vectors) + if batch_x: + message = "Dimension mismatch:..." + with pytest.raises(ValueError, match=message): + A.matvec(x_col) if left else A.rmatvec(x_col) + else: + message = ("Calling `matvec` on 'column vectors'..." if left + else "Calling `rmatvec` on 'column vectors'...") + with pytest.warns(FutureWarning, match=message): + xp_assert_close( + A.matvec(x_col) if left else A.rmatvec(x_col), + A_ @ x_col if left else xp.conj(A_.mT) @ x_col, + ) + # c. with matrix (or batch of matrices) + with pytest.raises(ValueError, match="Dimension mismatch:..."): + A.matvec(x_mat) if left else A.rmatvec(x_mat) + + # Test matmat + # a. with row vector (or batch of row vectors) + message = "Dimension mismatch:..." if batch_x else "Expected at least 2-d..." + with pytest.raises(ValueError, match=message): + A.matmat(x_row) if left else A.rmatmat(x_row) + # b. with column vector (or batch of column vectors) + xp_assert_close( + A.matmat(x_col) if left else A.rmatmat(x_col), + A_ @ x_col if left else xp.conj(A_.mT) @ x_col, + ) + # c. with matrix (or batch of matrices) + xp_assert_close( + A.matmat(x_mat) if left else A.rmatmat(x_mat), + A_ @ x_mat if left else xp.conj(A_.mT) @ x_mat, + ) + + # test __matmul__ (via `@`), `__call__`, and `dot` + if left: + # a. with row vector (or batch of row vectors) + if batch_x: + with pytest.raises(ValueError, match="Dimension mismatch:..."): + A @ x_row + else: + xp_assert_close(A @ x_row, A_ @ x_row) + xp_assert_close(A(x_row), A_ @ x_row) + xp_assert_close(A.dot(x_row), A_ @ x_row) + # b. with column vector (or batch of column vectors) + xp_assert_close(A @ x_col, A_ @ x_col) + xp_assert_close(A(x_col), A_ @ x_col) + xp_assert_close(A.dot(x_col), A_ @ x_col) + # c. with matrix (or batch of matrices) + xp_assert_close(A @ x_mat, A_ @ x_mat) + xp_assert_close(A(x_mat), A_ @ x_mat) + xp_assert_close(A.dot(x_mat), A_ @ x_mat) + else: + # a. with row vector (or batch of row vectors) + broadcastable = True + try: + # `batch_x[-1]` becomes part of the core shape for matrix + # multiplication with 1-D row vector + # success thus depends on whether `batch_A` can broadcast + # with `batch_x[:-1]` + np.broadcast_shapes(batch_A, batch_x[:-1]) + except ValueError: + broadcastable = False + if not broadcastable: + # Maybe should be "Dimension mismatch..."? + msg = "Incompatible shapes|size of tensor|could not be broadcast..." + with pytest.raises((ValueError, RuntimeError), match=msg): + x_row @ A + else: + xp_assert_close(x_row @ A, x_row @ A_) + xp_assert_close(A.rdot(x_row), x_row @ A_) + # b. with column vector (or batch of column vectors) + with pytest.raises(ValueError, match="Dimension mismatch:..."): + x_col @ A + # c. with matrix (or batch of matrices) + xp_assert_close(xp.conj(x_mat.mT) @ A, xp.conj(x_mat.mT) @ A_) + xp_assert_close(A.rdot(xp.conj(x_mat.mT)), xp.conj(x_mat.mT) @ A_) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/test_matfuncs.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/test_matfuncs.py new file mode 100644 index 0000000000000000000000000000000000000000..14bfff88769db7a4d08b4f10e0fb13778c52dd02 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/test_matfuncs.py @@ -0,0 +1,599 @@ +# +# Created by: Pearu Peterson, March 2002 +# +""" Test functions for scipy.linalg._matfuncs module + +""" +import math +import warnings + +import numpy as np +from numpy import array, eye, exp, random +from numpy.testing import ( + assert_allclose, assert_, assert_array_almost_equal, assert_equal, + assert_array_almost_equal_nulp) + +from scipy.sparse import csc_array, SparseEfficiencyWarning +from scipy.sparse._construct import eye_array +from scipy.sparse.linalg._matfuncs import (expm, _expm, + ProductOperator, MatrixPowerOperator, + _onenorm_matrix_power_nnm, matrix_power) +from scipy.sparse._sputils import matrix +from scipy.linalg import logm +from scipy.special import factorial, binom +import scipy.sparse +import scipy.sparse.linalg + + +def _burkardt_13_power(n, p): + """ + A helper function for testing matrix functions. + + Parameters + ---------- + n : int greater than 1 + Order of the square matrix to be returned. + p : non-negative integer + Power of the matrix. + + Returns + ------- + out : ndarray representing a square matrix + A Forsythe matrix of order n, raised to the power p. + + """ + # Input validation. + if n != int(n) or n < 2: + raise ValueError('n must be an integer greater than 1') + n = int(n) + if p != int(p) or p < 0: + raise ValueError('p must be a non-negative integer') + p = int(p) + + # Construct the matrix explicitly. + a, b = divmod(p, n) + large = np.power(10.0, -n*a) + small = large * np.power(10.0, -n) + return np.diag([large]*(n-b), b) + np.diag([small]*b, b-n) + + +def test_onenorm_matrix_power_nnm(): + np.random.seed(1234) + for n in range(1, 5): + for p in range(5): + M = np.random.random((n, n)) + Mp = np.linalg.matrix_power(M, p) + observed = _onenorm_matrix_power_nnm(M, p) + expected = np.linalg.norm(Mp, 1) + assert_allclose(observed, expected) + +def test_matrix_power(): + np.random.seed(1234) + row, col = np.random.randint(0, 4, size=(2, 6)) + data = np.random.random(size=(6,)) + Amat = csc_array((data, (row, col)), shape=(4, 4)) + A = csc_array((data, (row, col)), shape=(4, 4)) + Adense = A.toarray() + for power in (2, 5, 6): + Apow = matrix_power(A, power).toarray() + Amat_pow = matrix_power(Amat, power).toarray() + Adense_pow = np.linalg.matrix_power(Adense, power) + assert_allclose(Apow, Adense_pow) + assert_allclose(Apow, Amat_pow) + + +class TestExpM: + def test_zero_ndarray(self): + a = array([[0.,0],[0,0]]) + assert_array_almost_equal(expm(a),[[1,0],[0,1]]) + + def test_zero_sparse(self): + a = csc_array([[0.,0],[0,0]]) + assert_array_almost_equal(expm(a).toarray(),[[1,0],[0,1]]) + + def test_zero_matrix(self): + a = matrix([[0.,0],[0,0]]) + assert_array_almost_equal(expm(a),[[1,0],[0,1]]) + + def test_misc_types(self): + A = expm(np.array([[1]])) + assert_allclose(expm(((1,),)), A) + assert_allclose(expm([[1]]), A) + assert_allclose(expm(matrix([[1]])), A) + assert_allclose(expm(np.array([[1]])), A) + assert_allclose(expm(csc_array([[1]])).toarray(), A) + B = expm(np.array([[1j]])) + assert_allclose(expm(((1j,),)), B) + assert_allclose(expm([[1j]]), B) + assert_allclose(expm(matrix([[1j]])), B) + assert_allclose(expm(csc_array([[1j]])).toarray(), B) + + def test_bidiagonal_sparse(self): + A = csc_array([ + [1, 3, 0], + [0, 1, 5], + [0, 0, 2]], dtype=float) + e1 = math.exp(1) + e2 = math.exp(2) + expected = np.array([ + [e1, 3*e1, 15*(e2 - 2*e1)], + [0, e1, 5*(e2 - e1)], + [0, 0, e2]], dtype=float) + observed = expm(A).toarray() + assert_array_almost_equal(observed, expected) + + def test_padecases_dtype_float(self): + for dtype in [np.float32, np.float64]: + for scale in [1e-2, 1e-1, 5e-1, 1, 10]: + A = scale * eye(3, dtype=dtype) + observed = expm(A) + expected = exp(scale, dtype=dtype) * eye(3, dtype=dtype) + assert_array_almost_equal_nulp(observed, expected, nulp=100) + + def test_padecases_dtype_complex(self): + for dtype in [np.complex64, np.complex128]: + for scale in [1e-2, 1e-1, 5e-1, 1, 10]: + A = scale * eye(3, dtype=dtype) + observed = expm(A) + expected = exp(scale, dtype=dtype) * eye(3, dtype=dtype) + assert_array_almost_equal_nulp(observed, expected, nulp=100) + + def test_padecases_dtype_sparse_float(self): + # float32 and complex64 lead to errors in spsolve/UMFpack + dtype = np.float64 + for scale in [1e-2, 1e-1, 5e-1, 1, 10]: + a = scale * eye_array(3, 3, dtype=dtype, format='csc') + e = exp(scale, dtype=dtype) * eye(3, dtype=dtype) + with warnings.catch_warnings(): + msg = "Changing the sparsity structure" + warnings.filterwarnings("ignore", msg, SparseEfficiencyWarning) + exact_onenorm = _expm(a, use_exact_onenorm=True).toarray() + inexact_onenorm = _expm(a, use_exact_onenorm=False).toarray() + assert_array_almost_equal_nulp(exact_onenorm, e, nulp=100) + assert_array_almost_equal_nulp(inexact_onenorm, e, nulp=100) + + def test_padecases_dtype_sparse_complex(self): + # float32 and complex64 lead to errors in spsolve/UMFpack + dtype = np.complex128 + for scale in [1e-2, 1e-1, 5e-1, 1, 10]: + a = scale * eye_array(3, 3, dtype=dtype, format='csc') + e = exp(scale) * eye(3, dtype=dtype) + with warnings.catch_warnings(): + msg = "Changing the sparsity structure" + warnings.filterwarnings("ignore", msg, SparseEfficiencyWarning) + assert_array_almost_equal_nulp(expm(a).toarray(), e, nulp=100) + + def test_logm_consistency(self): + random.seed(1234) + for dtype in [np.float64, np.complex128]: + for n in range(1, 10): + for scale in [1e-4, 1e-3, 1e-2, 1e-1, 1, 1e1, 1e2]: + # make logm(A) be of a given scale + A = (eye(n) + random.rand(n, n) * scale).astype(dtype) + if np.iscomplexobj(A): + A = A + 1j * random.rand(n, n) * scale + assert_array_almost_equal(expm(logm(A)), A) + + def test_integer_matrix(self): + Q = np.array([ + [-3, 1, 1, 1], + [1, -3, 1, 1], + [1, 1, -3, 1], + [1, 1, 1, -3]]) + assert_allclose(expm(Q), expm(1.0 * Q)) + + def test_integer_matrix_2(self): + # Check for integer overflows + Q = np.array([[-500, 500, 0, 0], + [0, -550, 360, 190], + [0, 630, -630, 0], + [0, 0, 0, 0]], dtype=np.int16) + assert_allclose(expm(Q), expm(1.0 * Q)) + + Q = csc_array(Q) + assert_allclose(expm(Q).toarray(), expm(1.0 * Q).toarray()) + + def test_triangularity_perturbation(self): + # Experiment (1) of + # Awad H. Al-Mohy and Nicholas J. Higham (2012) + # Improved Inverse Scaling and Squaring Algorithms + # for the Matrix Logarithm. + A = np.array([ + [3.2346e-1, 3e4, 3e4, 3e4], + [0, 3.0089e-1, 3e4, 3e4], + [0, 0, 3.221e-1, 3e4], + [0, 0, 0, 3.0744e-1]], + dtype=float) + A_logm = np.array([ + [-1.12867982029050462e+00, 9.61418377142025565e+04, + -4.52485573953179264e+09, 2.92496941103871812e+14], + [0.00000000000000000e+00, -1.20101052953082288e+00, + 9.63469687211303099e+04, -4.68104828911105442e+09], + [0.00000000000000000e+00, 0.00000000000000000e+00, + -1.13289322264498393e+00, 9.53249183094775653e+04], + [0.00000000000000000e+00, 0.00000000000000000e+00, + 0.00000000000000000e+00, -1.17947533272554850e+00]], + dtype=float) + assert_allclose(expm(A_logm), A, rtol=1e-4) + + # Perturb the upper triangular matrix by tiny amounts, + # so that it becomes technically not upper triangular. + random.seed(1234) + tiny = 1e-17 + A_logm_perturbed = A_logm.copy() + A_logm_perturbed[1, 0] = tiny + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", "Ill-conditioned.*", RuntimeWarning) + warnings.filterwarnings("ignore", "An ill-conditioned.*", RuntimeWarning) + + A_expm_logm_perturbed = expm(A_logm_perturbed) + rtol = 1e-4 + atol = 100 * tiny + assert_(not np.allclose(A_expm_logm_perturbed, A, rtol=rtol, atol=atol)) + + def test_burkardt_1(self): + # This matrix is diagonal. + # The calculation of the matrix exponential is simple. + # + # This is the first of a series of matrix exponential tests + # collected by John Burkardt from the following sources. + # + # Alan Laub, + # Review of "Linear System Theory" by Joao Hespanha, + # SIAM Review, + # Volume 52, Number 4, December 2010, pages 779--781. + # + # Cleve Moler and Charles Van Loan, + # Nineteen Dubious Ways to Compute the Exponential of a Matrix, + # Twenty-Five Years Later, + # SIAM Review, + # Volume 45, Number 1, March 2003, pages 3--49. + # + # Cleve Moler, + # Cleve's Corner: A Balancing Act for the Matrix Exponential, + # 23 July 2012. + # + # Robert Ward, + # Numerical computation of the matrix exponential + # with accuracy estimate, + # SIAM Journal on Numerical Analysis, + # Volume 14, Number 4, September 1977, pages 600--610. + exp1 = np.exp(1) + exp2 = np.exp(2) + A = np.array([ + [1, 0], + [0, 2], + ], dtype=float) + desired = np.array([ + [exp1, 0], + [0, exp2], + ], dtype=float) + actual = expm(A) + assert_allclose(actual, desired) + + def test_burkardt_2(self): + # This matrix is symmetric. + # The calculation of the matrix exponential is straightforward. + A = np.array([ + [1, 3], + [3, 2], + ], dtype=float) + desired = np.array([ + [39.322809708033859, 46.166301438885753], + [46.166301438885768, 54.711576854329110], + ], dtype=float) + actual = expm(A) + assert_allclose(actual, desired) + + def test_burkardt_3(self): + # This example is due to Laub. + # This matrix is ill-suited for the Taylor series approach. + # As powers of A are computed, the entries blow up too quickly. + exp1 = np.exp(1) + exp39 = np.exp(39) + A = np.array([ + [0, 1], + [-39, -40], + ], dtype=float) + desired = np.array([ + [ + 39/(38*exp1) - 1/(38*exp39), + -np.expm1(-38) / (38*exp1)], + [ + 39*np.expm1(-38) / (38*exp1), + -1/(38*exp1) + 39/(38*exp39)], + ], dtype=float) + actual = expm(A) + assert_allclose(actual, desired) + + def test_burkardt_4(self): + # This example is due to Moler and Van Loan. + # The example will cause problems for the series summation approach, + # as well as for diagonal Pade approximations. + A = np.array([ + [-49, 24], + [-64, 31], + ], dtype=float) + U = np.array([[3, 1], [4, 2]], dtype=float) + V = np.array([[1, -1/2], [-2, 3/2]], dtype=float) + w = np.array([-17, -1], dtype=float) + desired = np.dot(U * np.exp(w), V) + actual = expm(A) + assert_allclose(actual, desired) + + def test_burkardt_5(self): + # This example is due to Moler and Van Loan. + # This matrix is strictly upper triangular + # All powers of A are zero beyond some (low) limit. + # This example will cause problems for Pade approximations. + A = np.array([ + [0, 6, 0, 0], + [0, 0, 6, 0], + [0, 0, 0, 6], + [0, 0, 0, 0], + ], dtype=float) + desired = np.array([ + [1, 6, 18, 36], + [0, 1, 6, 18], + [0, 0, 1, 6], + [0, 0, 0, 1], + ], dtype=float) + actual = expm(A) + assert_allclose(actual, desired) + + def test_burkardt_6(self): + # This example is due to Moler and Van Loan. + # This matrix does not have a complete set of eigenvectors. + # That means the eigenvector approach will fail. + exp1 = np.exp(1) + A = np.array([ + [1, 1], + [0, 1], + ], dtype=float) + desired = np.array([ + [exp1, exp1], + [0, exp1], + ], dtype=float) + actual = expm(A) + assert_allclose(actual, desired) + + def test_burkardt_7(self): + # This example is due to Moler and Van Loan. + # This matrix is very close to example 5. + # Mathematically, it has a complete set of eigenvectors. + # Numerically, however, the calculation will be suspect. + exp1 = np.exp(1) + eps = np.spacing(1) + A = np.array([ + [1 + eps, 1], + [0, 1 - eps], + ], dtype=float) + desired = np.array([ + [exp1, exp1], + [0, exp1], + ], dtype=float) + actual = expm(A) + assert_allclose(actual, desired) + + def test_burkardt_8(self): + # This matrix was an example in Wikipedia. + exp4 = np.exp(4) + exp16 = np.exp(16) + A = np.array([ + [21, 17, 6], + [-5, -1, -6], + [4, 4, 16], + ], dtype=float) + desired = np.array([ + [13*exp16 - exp4, 13*exp16 - 5*exp4, 2*exp16 - 2*exp4], + [-9*exp16 + exp4, -9*exp16 + 5*exp4, -2*exp16 + 2*exp4], + [16*exp16, 16*exp16, 4*exp16], + ], dtype=float) * 0.25 + actual = expm(A) + assert_allclose(actual, desired) + + def test_burkardt_9(self): + # This matrix is due to the NAG Library. + # It is an example for function F01ECF. + A = np.array([ + [1, 2, 2, 2], + [3, 1, 1, 2], + [3, 2, 1, 2], + [3, 3, 3, 1], + ], dtype=float) + desired = np.array([ + [740.7038, 610.8500, 542.2743, 549.1753], + [731.2510, 603.5524, 535.0884, 542.2743], + [823.7630, 679.4257, 603.5524, 610.8500], + [998.4355, 823.7630, 731.2510, 740.7038], + ], dtype=float) + actual = expm(A) + assert_allclose(actual, desired) + + def test_burkardt_10(self): + # This is Ward's example #1. + # It is defective and nonderogatory. + A = np.array([ + [4, 2, 0], + [1, 4, 1], + [1, 1, 4], + ], dtype=float) + assert_allclose(sorted(scipy.linalg.eigvals(A)), (3, 3, 6)) + desired = np.array([ + [147.8666224463699, 183.7651386463682, 71.79703239999647], + [127.7810855231823, 183.7651386463682, 91.88256932318415], + [127.7810855231824, 163.6796017231806, 111.9681062463718], + ], dtype=float) + actual = expm(A) + assert_allclose(actual, desired) + + def test_burkardt_11(self): + # This is Ward's example #2. + # It is a symmetric matrix. + A = np.array([ + [29.87942128909879, 0.7815750847907159, -2.289519314033932], + [0.7815750847907159, 25.72656945571064, 8.680737820540137], + [-2.289519314033932, 8.680737820540137, 34.39400925519054], + ], dtype=float) + assert_allclose(scipy.linalg.eigvalsh(A), (20, 30, 40)) + desired = np.array([ + [ + 5.496313853692378E+15, + -1.823188097200898E+16, + -3.047577080858001E+16], + [ + -1.823188097200899E+16, + 6.060522870222108E+16, + 1.012918429302482E+17], + [ + -3.047577080858001E+16, + 1.012918429302482E+17, + 1.692944112408493E+17], + ], dtype=float) + actual = expm(A) + assert_allclose(actual, desired) + + def test_burkardt_12(self): + # This is Ward's example #3. + # Ward's algorithm has difficulty estimating the accuracy + # of its results. + A = np.array([ + [-131, 19, 18], + [-390, 56, 54], + [-387, 57, 52], + ], dtype=float) + assert_allclose(sorted(scipy.linalg.eigvals(A)), (-20, -2, -1)) + desired = np.array([ + [-1.509644158793135, 0.3678794391096522, 0.1353352811751005], + [-5.632570799891469, 1.471517758499875, 0.4060058435250609], + [-4.934938326088363, 1.103638317328798, 0.5413411267617766], + ], dtype=float) + actual = expm(A) + assert_allclose(actual, desired) + + def test_burkardt_13(self): + # This is Ward's example #4. + # This is a version of the Forsythe matrix. + # The eigenvector problem is badly conditioned. + # Ward's algorithm has difficulty estimating the accuracy + # of its results for this problem. + # + # Check the construction of one instance of this family of matrices. + A4_actual = _burkardt_13_power(4, 1) + A4_desired = [[0, 1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1], + [1e-4, 0, 0, 0]] + assert_allclose(A4_actual, A4_desired) + # Check the expm for a few instances. + for n in (2, 3, 4, 10): + # Approximate expm using Taylor series. + # This works well for this matrix family + # because each matrix in the summation, + # even before dividing by the factorial, + # is entrywise positive with max entry 10**(-floor(p/n)*n). + k = max(1, int(np.ceil(16/n))) + desired = np.zeros((n, n), dtype=float) + for p in range(n*k): + Ap = _burkardt_13_power(n, p) + assert_equal(np.min(Ap), 0) + assert_allclose(np.max(Ap), np.power(10, -np.floor(p/n)*n)) + desired += Ap / factorial(p) + actual = expm(_burkardt_13_power(n, 1)) + assert_allclose(actual, desired) + + def test_burkardt_14(self): + # This is Moler's example. + # This badly scaled matrix caused problems for MATLAB's expm(). + A = np.array([ + [0, 1e-8, 0], + [-(2e10 + 4e8/6.), -3, 2e10], + [200./3., 0, -200./3.], + ], dtype=float) + desired = np.array([ + [0.446849468283175, 1.54044157383952e-09, 0.462811453558774], + [-5743067.77947947, -0.0152830038686819, -4526542.71278401], + [0.447722977849494, 1.54270484519591e-09, 0.463480648837651], + ], dtype=float) + actual = expm(A) + assert_allclose(actual, desired) + + def test_pascal(self): + # Test pascal triangle. + # Nilpotent exponential, used to trigger a failure (gh-8029) + + for scale in [1.0, 1e-3, 1e-6]: + for n in range(0, 80, 3): + sc = scale ** np.arange(n, -1, -1) + if np.any(sc < 1e-300): + break + + A = np.diag(np.arange(1, n + 1), -1) * scale + B = expm(A) + + got = B + expected = binom(np.arange(n + 1)[:,None], + np.arange(n + 1)[None,:]) * sc[None,:] / sc[:,None] + atol = 1e-13 * abs(expected).max() + assert_allclose(got, expected, atol=atol) + + def test_matrix_input(self): + # Large np.matrix inputs should work, gh-5546 + A = np.zeros((200, 200)) + A[-1,0] = 1 + B0 = expm(A) + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", "the matrix subclass.*", DeprecationWarning) + warnings.filterwarnings( + "ignore", "the matrix subclass.*", PendingDeprecationWarning) + B = expm(np.matrix(A)) + assert_allclose(B, B0) + + def test_exp_sinch_overflow(self): + # Check overflow in intermediate steps is fixed (gh-11839) + L = np.array([[1.0, -0.5, -0.5, 0.0, 0.0, 0.0, 0.0], + [0.0, 1.0, 0.0, -0.5, -0.5, 0.0, 0.0], + [0.0, 0.0, 1.0, 0.0, 0.0, -0.5, -0.5], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]]) + + E0 = expm(-L) + E1 = expm(-2**11 * L) + E2 = E0 + for j in range(11): + E2 = E2 @ E2 + + assert_allclose(E1, E2) + + +class TestOperators: + + def test_product_operator(self): + random.seed(1234) + n = 5 + k = 2 + nsamples = 10 + for i in range(nsamples): + A = np.random.randn(n, n) + B = np.random.randn(n, n) + C = np.random.randn(n, n) + D = np.random.randn(n, k) + op = ProductOperator(A, B, C) + assert_allclose(op.matmat(D), A.dot(B).dot(C).dot(D)) + assert_allclose(op.T.matmat(D), (A.dot(B).dot(C)).T.dot(D)) + + def test_matrix_power_operator(self): + random.seed(1234) + n = 5 + k = 2 + p = 3 + nsamples = 10 + for i in range(nsamples): + A = np.random.randn(n, n) + B = np.random.randn(n, k) + op = MatrixPowerOperator(A, p) + assert_allclose(op.matmat(B), np.linalg.matrix_power(A, p).dot(B)) + assert_allclose(op.T.matmat(B), np.linalg.matrix_power(A, p).T.dot(B)) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/test_norm.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/test_norm.py new file mode 100644 index 0000000000000000000000000000000000000000..1704e42afc5b8311e688243fb38626d746fa881b --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/test_norm.py @@ -0,0 +1,150 @@ +"""Test functions for the sparse.linalg.norm module +""" + +import numpy as np +from numpy.linalg import norm as npnorm +from numpy.testing import assert_allclose, assert_equal +from pytest import raises as assert_raises + +import scipy.sparse +from scipy.sparse.linalg import norm as spnorm + + +# https://github.com/scipy/scipy/issues/16031 +# https://github.com/scipy/scipy/issues/21690 +def test_sparray_norm(): + row = np.array([0, 0, 1, 1]) + col = np.array([0, 1, 2, 3]) + data = np.array([4, 5, 7, 9]) + test_arr = scipy.sparse.coo_array((data, (row, col)), shape=(2, 4)) + test_mat = scipy.sparse.coo_matrix((data, (row, col)), shape=(2, 4)) + for ord in (1, np.inf, None): + for ax in [0, 1, None, (0, 1), (1, 0)]: + for A in (test_arr, test_mat): + expected = npnorm(A.toarray(), ord=ord, axis=ax) + actual = spnorm(A, ord=ord, axis=ax) + assert hasattr(actual, "dtype") + assert_equal(actual, expected) + # test 1d array and 1d-like (column) matrix + test_arr_1d = scipy.sparse.coo_array((data, (col,)), shape=(4,)) + test_mat_col = scipy.sparse.coo_matrix((data, (col, [0, 0, 0, 0])), shape=(4, 1)) + for ord in (1, np.inf, None): + for ax in [0, None]: + for A in (test_arr_1d, test_mat_col): + expected = npnorm(A.toarray(), ord=ord, axis=ax) + assert_equal(spnorm(A, ord=ord, axis=ax), expected) + + +class TestNorm: + def setup_method(self): + a = np.arange(9) - 4 + b = a.reshape((3, 3)) + self.b = scipy.sparse.csr_array(b) + + def test_matrix_norm(self): + + # Frobenius norm is the default + assert_allclose(spnorm(self.b), 7.745966692414834) + assert_allclose(spnorm(self.b, 'fro'), 7.745966692414834) + + assert_allclose(spnorm(self.b, np.inf), 9) + assert_allclose(spnorm(self.b, -np.inf), 2) + assert_allclose(spnorm(self.b, 1), 7) + assert_allclose(spnorm(self.b, -1), 6) + assert_allclose(spnorm(self.b.astype(np.float64), 2), 7.348469228349534) + + # _multi_svd_norm is not implemented for sparse array + assert_raises(NotImplementedError, spnorm, self.b, -2) + + def test_matrix_norm_axis(self): + for m, axis in ((self.b, None), (self.b, (0, 1)), (self.b.T, (1, 0))): + assert_allclose(spnorm(m, axis=axis), 7.745966692414834) + assert_allclose(spnorm(m, 'fro', axis=axis), 7.745966692414834) + assert_allclose(spnorm(m, np.inf, axis=axis), 9) + assert_allclose(spnorm(m, -np.inf, axis=axis), 2) + assert_allclose(spnorm(m, 1, axis=axis), 7) + assert_allclose(spnorm(m, -1, axis=axis), 6) + + def test_vector_norm(self): + v = [4.5825756949558398, 4.2426406871192848, 4.5825756949558398] + for m, a in (self.b, 0), (self.b.T, 1): + for axis in a, (a, ), a-2, (a-2, ): + assert_allclose(spnorm(m, 1, axis=axis), [7, 6, 7]) + assert_allclose(spnorm(m, np.inf, axis=axis), [4, 3, 4]) + assert_allclose(spnorm(m, axis=axis), v) + assert_allclose(spnorm(m, ord=2, axis=axis), v) + assert_allclose(spnorm(m, ord=None, axis=axis), v) + + def test_norm_exceptions(self): + m = self.b + assert_raises(TypeError, spnorm, m, None, 1.5) + assert_raises(TypeError, spnorm, m, None, [2]) + assert_raises(ValueError, spnorm, m, None, ()) + assert_raises(ValueError, spnorm, m, None, (0, 1, 2)) + assert_raises(ValueError, spnorm, m, None, (0, 0)) + assert_raises(ValueError, spnorm, m, None, (0, 2)) + assert_raises(ValueError, spnorm, m, None, (-3, 0)) + assert_raises(ValueError, spnorm, m, None, 2) + assert_raises(ValueError, spnorm, m, None, -3) + assert_raises(ValueError, spnorm, m, 'plate_of_shrimp', 0) + assert_raises(ValueError, spnorm, m, 'plate_of_shrimp', (0, 1)) + + +class TestVsNumpyNorm: + _sparse_types = ( + scipy.sparse.bsr_array, + scipy.sparse.coo_array, + scipy.sparse.csc_array, + scipy.sparse.csr_array, + scipy.sparse.dia_array, + scipy.sparse.dok_array, + scipy.sparse.lil_array, + ) + _test_matrices = ( + (np.arange(9) - 4).reshape((3, 3)), + [ + [1, 2, 3], + [-1, 1, 4]], + [ + [1, 0, 3], + [-1, 1, 4j]], + np.array([[-128, 127], [127, -128]], dtype=np.int8), + ) + + def test_sparse_matrix_norms(self): + for sparse_type in self._sparse_types: + for M in self._test_matrices: + S = sparse_type(M) + assert_allclose(spnorm(S), npnorm(M)) + assert_allclose(spnorm(S, 'fro'), npnorm(M, 'fro')) + assert_allclose(spnorm(S, np.inf), npnorm(M, np.inf)) + assert_allclose(spnorm(S, -np.inf), npnorm(M, -np.inf)) + assert_allclose(spnorm(S, 1), npnorm(M, 1)) + assert_allclose(spnorm(S, -1), npnorm(M, -1)) + + def test_sparse_matrix_norms_with_axis(self): + for sparse_type in self._sparse_types: + for M in self._test_matrices: + S = sparse_type(M) + for axis in None, (0, 1), (1, 0): + assert_allclose(spnorm(S, axis=axis), npnorm(M, axis=axis)) + for ord in 'fro', np.inf, -np.inf, 1, -1: + assert_allclose(spnorm(S, ord, axis=axis), + npnorm(M, ord, axis=axis)) + # Some numpy matrix norms are allergic to negative axes. + for axis in (-2, -1), (-1, -2), (1, -2): + assert_allclose(spnorm(S, axis=axis), npnorm(M, axis=axis)) + assert_allclose(spnorm(S, 'f', axis=axis), + npnorm(M, 'f', axis=axis)) + assert_allclose(spnorm(S, 'fro', axis=axis), + npnorm(M, 'fro', axis=axis)) + + def test_sparse_vector_norms(self): + for sparse_type in self._sparse_types: + for M in self._test_matrices: + S = sparse_type(M) + for axis in (0, 1, -1, -2, (0, ), (1, ), (-1, ), (-2, )): + assert_allclose(spnorm(S, axis=axis), npnorm(M, axis=axis)) + for ord in None, 2, np.inf, -np.inf, 1, 0.5, 0.42, 0: + assert_allclose(spnorm(S, ord, axis=axis), + npnorm(M, ord, axis=axis)) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/test_onenormest.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/test_onenormest.py new file mode 100644 index 0000000000000000000000000000000000000000..dc55c73884d3fbc6ea07f2fb8d18894752c7402f --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/test_onenormest.py @@ -0,0 +1,229 @@ +"""Test functions for the sparse.linalg._onenormest module +""" + +import numpy as np +from numpy.testing import assert_allclose, assert_equal, assert_ +import pytest +import scipy.linalg +import scipy.sparse.linalg +from scipy.sparse.linalg._onenormest import _onenormest_core + + +class MatrixProductOperator(scipy.sparse.linalg.LinearOperator): + """ + This is purely for onenormest testing. + """ + + def __init__(self, A, B): + if A.ndim != 2 or B.ndim != 2: + raise ValueError('expected ndarrays representing matrices') + if A.shape[1] != B.shape[0]: + raise ValueError('incompatible shapes') + super().__init__(dtype=None, shape=(A.shape[0], B.shape[1])) + self.A = A + self.B = B + + def _matvec(self, x): + return np.dot(self.A, np.dot(self.B, x)) + + def _rmatvec(self, x): + return np.dot(np.dot(x, self.A), self.B) + + def _matmat(self, X): + return np.dot(self.A, np.dot(self.B, X)) + + @property + def T(self): + return MatrixProductOperator(self.B.T, self.A.T) + + +class TestOnenormest: + + @pytest.mark.xslow + def test_onenormest_table_3_t_2(self): + # This will take multiple seconds if your computer is slow like mine. + # It is stochastic, so the tolerance could be too strict. + np.random.seed(1234) + t = 2 + n = 100 + itmax = 5 + nsamples = 5000 + observed = [] + expected = [] + nmult_list = [] + nresample_list = [] + for i in range(nsamples): + A = scipy.linalg.inv(np.random.randn(n, n)) + est, v, w, nmults, nresamples = _onenormest_core(A, A.T, t, itmax) + observed.append(est) + expected.append(scipy.linalg.norm(A, 1)) + nmult_list.append(nmults) + nresample_list.append(nresamples) + observed = np.array(observed, dtype=float) + expected = np.array(expected, dtype=float) + relative_errors = np.abs(observed - expected) / expected + + # check the mean underestimation ratio + underestimation_ratio = observed / expected + assert_(0.99 < np.mean(underestimation_ratio) < 1.0) + + # check the max and mean required column resamples + assert_equal(np.max(nresample_list), 2) + assert_(0.05 < np.mean(nresample_list) < 0.2) + + # check the proportion of norms computed exactly correctly + nexact = np.count_nonzero(relative_errors < 1e-14) + proportion_exact = nexact / float(nsamples) + assert_(0.9 < proportion_exact < 0.95) + + # check the average number of matrix*vector multiplications + assert_(3.5 < np.mean(nmult_list) < 4.5) + + @pytest.mark.xslow + def test_onenormest_table_4_t_7(self): + # This will take multiple seconds if your computer is slow like mine. + # It is stochastic, so the tolerance could be too strict. + np.random.seed(1234) + t = 7 + n = 100 + itmax = 5 + nsamples = 5000 + observed = [] + expected = [] + nmult_list = [] + nresample_list = [] + for i in range(nsamples): + A = np.random.randint(-1, 2, size=(n, n)) + est, v, w, nmults, nresamples = _onenormest_core(A, A.T, t, itmax) + observed.append(est) + expected.append(scipy.linalg.norm(A, 1)) + nmult_list.append(nmults) + nresample_list.append(nresamples) + observed = np.array(observed, dtype=float) + expected = np.array(expected, dtype=float) + relative_errors = np.abs(observed - expected) / expected + + # check the mean underestimation ratio + underestimation_ratio = observed / expected + assert_(0.90 < np.mean(underestimation_ratio) < 0.99) + + # check the required column resamples + assert_equal(np.max(nresample_list), 0) + + # check the proportion of norms computed exactly correctly + nexact = np.count_nonzero(relative_errors < 1e-14) + proportion_exact = nexact / float(nsamples) + assert_(0.15 < proportion_exact < 0.25) + + # check the average number of matrix*vector multiplications + assert_(3.5 < np.mean(nmult_list) < 4.5) + + def test_onenormest_table_5_t_1(self): + # "note that there is no randomness and hence only one estimate for t=1" + t = 1 + n = 100 + itmax = 5 + alpha = 1 - 1e-6 + A = -scipy.linalg.inv(np.identity(n) + alpha*np.eye(n, k=1)) + first_col = np.array([1] + [0]*(n-1)) + first_row = np.array([(-alpha)**i for i in range(n)]) + B = -scipy.linalg.toeplitz(first_col, first_row) + assert_allclose(A, B) + est, v, w, nmults, nresamples = _onenormest_core(B, B.T, t, itmax) + exact_value = scipy.linalg.norm(B, 1) + underest_ratio = est / exact_value + assert_allclose(underest_ratio, 0.05, rtol=1e-4) + assert_equal(nmults, 11) + assert_equal(nresamples, 0) + # check the non-underscored version of onenormest + est_plain = scipy.sparse.linalg.onenormest(B, t=t, itmax=itmax) + assert_allclose(est, est_plain) + + @pytest.mark.xslow + def test_onenormest_table_6_t_2(self): + #TODO this test seems to give estimates that match the table, + #TODO even though no attempt has been made to deal with + #TODO complex numbers in the one-norm estimation. + # This will take multiple seconds if your computer is slow like mine. + # It is stochastic, so the tolerance could be too strict. + np.random.seed(1234) + t = 2 + n = 100 + itmax = 5 + nsamples = 5000 + observed = [] + expected = [] + nmult_list = [] + nresample_list = [] + for i in range(nsamples): + A_inv = np.random.rand(n, n) + 1j * np.random.rand(n, n) + A = scipy.linalg.inv(A_inv) + est, v, w, nmults, nresamples = _onenormest_core(A, A.T, t, itmax) + observed.append(est) + expected.append(scipy.linalg.norm(A, 1)) + nmult_list.append(nmults) + nresample_list.append(nresamples) + observed = np.array(observed, dtype=float) + expected = np.array(expected, dtype=float) + relative_errors = np.abs(observed - expected) / expected + + # check the mean underestimation ratio + underestimation_ratio = observed / expected + underestimation_ratio_mean = np.mean(underestimation_ratio) + assert_(0.90 < underestimation_ratio_mean < 0.99) + + # check the required column resamples + max_nresamples = np.max(nresample_list) + assert_equal(max_nresamples, 0) + + # check the proportion of norms computed exactly correctly + nexact = np.count_nonzero(relative_errors < 1e-14) + proportion_exact = nexact / float(nsamples) + assert_(0.7 < proportion_exact < 0.8) + + # check the average number of matrix*vector multiplications + mean_nmult = np.mean(nmult_list) + assert_(4 < mean_nmult < 5) + + def _help_product_norm_slow(self, A, B): + # for profiling + C = np.dot(A, B) + return scipy.linalg.norm(C, 1) + + def _help_product_norm_fast(self, A, B): + # for profiling + t = 2 + itmax = 5 + D = MatrixProductOperator(A, B) + est, v, w, nmults, nresamples = _onenormest_core(D, D.T, t, itmax) + return est + + @pytest.mark.slow + def test_onenormest_linear_operator(self): + # Define a matrix through its product A B. + # Depending on the shapes of A and B, + # it could be easy to multiply this product by a small matrix, + # but it could be annoying to look at all of + # the entries of the product explicitly. + np.random.seed(1234) + n = 6000 + k = 3 + A = np.random.randn(n, k) + B = np.random.randn(k, n) + fast_estimate = self._help_product_norm_fast(A, B) + exact_value = self._help_product_norm_slow(A, B) + assert_(fast_estimate <= exact_value <= 3*fast_estimate, + f'fast: {fast_estimate:g}\nexact:{exact_value:g}') + + def test_returns(self): + np.random.seed(1234) + A = scipy.sparse.random_array((50, 50), density=0.1) + + s0 = scipy.linalg.norm(A.toarray(), 1) + s1, v = scipy.sparse.linalg.onenormest(A, compute_v=True) + s2, w = scipy.sparse.linalg.onenormest(A, compute_w=True) + s3, v2, w2 = scipy.sparse.linalg.onenormest(A, compute_w=True, compute_v=True) + + assert_allclose(s1, s0, rtol=1e-9) + assert_allclose(np.linalg.norm(A.dot(v), 1), s0*np.linalg.norm(v, 1), rtol=1e-9) + assert_allclose(A.dot(v), w, rtol=1e-9) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/test_propack.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/test_propack.py new file mode 100644 index 0000000000000000000000000000000000000000..4cdb235aef1407b9b5ec2c1c39acddb1f7090278 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/test_propack.py @@ -0,0 +1,203 @@ +import os +import pytest + +import numpy as np +from numpy.testing import assert_allclose +from pytest import raises as assert_raises +from scipy.sparse.linalg._svdp import _svdp +from scipy.sparse import csr_array, csc_array + + +# dtype_flavour to tolerance +TOLS = { + np.float32: 1e-4, + np.float64: 1e-8, + np.complex64: 1e-4, + np.complex128: 1e-8, +} + + +def is_complex_type(dtype): + return np.dtype(dtype).kind == "c" + + +_dtypes = [] +for dtype_flavour in TOLS.keys(): + marks = [] + if is_complex_type(dtype_flavour): + marks = [pytest.mark.slow] + _dtypes.append(pytest.param(dtype_flavour, marks=marks, + id=dtype_flavour.__name__)) +_dtypes = tuple(_dtypes) # type: ignore[assignment] + + +# The test function here is adapted from the original Fortran PROPACK tests. +# It is not very robust to arbitrary seeding since partial reorthogonalization +# does not have a predictable upperbound on the number of iterations. +def check_svdp(n, m, constructor, dtype, k, irl_mode, which, f=0.8, rng=None): + tol = TOLS[dtype] + + if rng is None: + rng = np.random.default_rng(0) + + # Legacy clamp for the generator + rng2 = np.random.default_rng(0) + if is_complex_type(dtype): + M = (- 5 + 10 * rng2.uniform(size=[n, m]) + - 5j + 10j * rng2.uniform(size=[n, m])).astype(dtype) + else: + M = (-5 + 10 * rng2.uniform(size=[n, m])).astype(dtype) + M[M.real > 10 * f - 5] = 0 + Msp = constructor(M) + + u1, sigma1, vt1 = np.linalg.svd(M, full_matrices=False) + u2, sigma2, vt2, _ = _svdp(Msp, k=k,which=which, irl_mode=irl_mode, + tol=tol, rng=rng) + + # check the which + if which.upper() == 'SM': + u1 = np.roll(u1, k, 1) + vt1 = np.roll(vt1, k, 0) + sigma1 = np.roll(sigma1, k) + + # check that singular values agree + assert_allclose(sigma1[:k], sigma2, rtol=tol, atol=tol) + + # check that singular vectors are orthogonal + assert_allclose(np.abs(u1.conj().T @ u2), np.eye(n, k), rtol=tol, atol=tol) + assert_allclose(np.abs(vt1.conj() @ vt2.T), np.eye(n, k), rtol=tol, atol=tol) + + +@pytest.mark.parametrize('ctor', (np.array, csr_array, csc_array)) +@pytest.mark.parametrize('dtype', [np.float32, np.float64, + np.complex64, np.complex128]) +@pytest.mark.parametrize('irl', (True, False)) +@pytest.mark.parametrize('which', ('LM', 'SM')) +def test_svdp(ctor, dtype, irl, which): + rng = np.random.default_rng(1757937293955503) + n, m, k = 10, 20, 3 + if which == 'SM' and not irl: + message = "`which`='SM' requires irl_mode=True" + with assert_raises(ValueError, match=message): + check_svdp(n, m, ctor, dtype, k, irl, which, rng=rng) + else: + check_svdp(n, m, ctor, dtype, k, irl, which, rng=rng) + + +@pytest.mark.xslow +@pytest.mark.parametrize('dtype', _dtypes) +@pytest.mark.parametrize('irl', (False, True)) +def test_examples(dtype, irl): + # Note: atol for complex64 bumped from 1e-4 to 1e-3 due to test failures + # with BLIS, Netlib, and MKL+AVX512 - see + # https://github.com/conda-forge/scipy-feedstock/pull/198#issuecomment-999180432 + atol = { + np.float32: 1.3e-4, + np.float64: 1e-9, + np.complex64: 1e-3, + np.complex128: 1e-9, + }[dtype] + + path_prefix = os.path.dirname(__file__) + # Test matrices from `illc1850.coord` and `mhd1280b.cua` distributed with + # PROPACK 2.1: http://sun.stanford.edu/~rmunk/PROPACK/ + relative_path = "propack_test_data.npz" + filename = os.path.join(path_prefix, relative_path) + with np.load(filename, allow_pickle=True) as data: + if is_complex_type(dtype): + A = data['A_complex'].item().astype(dtype) + else: + A = data['A_real'].item().astype(dtype) + + k = 200 + u, s, vh, _ = _svdp(A, k, irl_mode=irl, rng=np.random.default_rng(0)) + + # complex example matrix has many repeated singular values, so check only + # beginning non-repeated singular vectors to avoid permutations + sv_check = 27 if is_complex_type(dtype) else k + u = u[:, :sv_check] + vh = vh[:sv_check, :] + s = s[:sv_check] + + # Check orthogonality of singular vectors + assert_allclose(np.eye(u.shape[1]), u.conj().T @ u, atol=atol) + assert_allclose(np.eye(vh.shape[0]), vh @ vh.conj().T, atol=atol) + + # Ensure the norm of the difference between the np.linalg.svd and + # PROPACK reconstructed matrices is small + u3, s3, vh3 = np.linalg.svd(A.todense()) + u3 = u3[:, :sv_check] + s3 = s3[:sv_check] + vh3 = vh3[:sv_check, :] + A3 = u3 @ np.diag(s3) @ vh3 + recon = u @ np.diag(s) @ vh + assert_allclose(np.linalg.norm(A3 - recon), 0, atol=atol) + + +@pytest.mark.parametrize('shifts', (None, -10, 0, 1, 10, 70)) +@pytest.mark.parametrize('dtype', _dtypes[:2]) +def test_shifts(shifts, dtype): + rng = np.random.default_rng(0) + n, k = 70, 10 + A = rng.random((n, n)) + if shifts is not None and ((shifts < 0) or (k > min(n-1-shifts, n))): + with pytest.raises(ValueError): + _svdp(A, k, shifts=shifts, kmax=5*k, irl_mode=True, rng=rng) + else: + _svdp(A, k, shifts=shifts, kmax=5*k, irl_mode=True, rng=rng) + + +@pytest.mark.slow +@pytest.mark.xfail() +def test_shifts_accuracy(): + rng = np.random.default_rng(0) + n, k = 70, 10 + A = rng.random((n, n)).astype(np.float64) + u1, s1, vt1, _ = _svdp(A, k, shifts=None, which='SM', irl_mode=True, rng=rng) + u2, s2, vt2, _ = _svdp(A, k, shifts=32, which='SM', irl_mode=True, rng=rng) + # shifts <= 32 doesn't agree with shifts > 32 + # Does agree when which='LM' instead of 'SM' + assert_allclose(s1, s2) + + +@pytest.mark.parametrize('irl_mode', [False, True]) +@pytest.mark.parametrize('dtype', (np.float32, np.float64)) +def test_thin_hilbert(irl_mode, dtype): + rng = np.random.default_rng(1757951587606893) + m, n = 200, 4 + + # Generate a Hilbert matrix of size m x n + A = np.array([[1 / (i + j + 1) for j in range(n)] for i in range(m)], dtype=dtype) + uu, ss, vv = np.linalg.svd(A, full_matrices=False) + u, s, vt, _ = _svdp(A, k=4, which='LM', irl_mode=irl_mode, rng=rng) + assert_allclose(s, ss, atol=TOLS[dtype]) + + # Check orthogonality of singular vectors + assert_allclose(np.eye(u.shape[1]), u.T @ u, atol=TOLS[dtype]) + assert_allclose(np.eye(vt.shape[0]), vt @ vt.T, atol=TOLS[dtype]) + + # Check orthogonality against numpy svd results + assert_allclose(np.abs(uu.T @ u), np.eye(n), atol=TOLS[dtype]) + assert_allclose(np.abs(vv @ vt.T), np.eye(n), atol=TOLS[dtype]) + + +@pytest.mark.parametrize('dtype', (np.float32, np.float64, np.complex64, np.complex128)) +def test_fat_random(dtype): + rng = np.random.default_rng(1758046113948869) + m, n = 3, 100 + + A = rng.uniform(size=(m, n)).astype(dtype) + if dtype in (np.complex64, np.complex128): + A += dtype(1j) * rng.uniform(size=(m, n)).astype(dtype) + + uu, ss, vv = np.linalg.svd(A, full_matrices=False) + u, s, vt, _ = _svdp(A, k=3, which='LM', irl_mode=True, rng=rng) + assert_allclose(s, ss, atol=TOLS[dtype]) + + # Check orthogonality of singular vectors + assert_allclose(np.eye(u.shape[1]), u.conj().T @ u, atol=TOLS[dtype]) + assert_allclose(np.eye(vt.shape[0]), vt @ vt.conj().T, atol=TOLS[dtype]) + + # Check orthogonality against numpy svd results + assert_allclose(np.abs(uu.conj().T @ u), np.eye(m), atol=TOLS[dtype]) + assert_allclose(np.abs(vv @ vt.conj().T), np.eye(m), atol=TOLS[dtype]) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/test_pydata_sparse.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/test_pydata_sparse.py new file mode 100644 index 0000000000000000000000000000000000000000..0b6a304f4b7fce9761f9225ab538db53e72741be --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/test_pydata_sparse.py @@ -0,0 +1,272 @@ +import pytest + +import numpy as np +import scipy.sparse as sp +import scipy.sparse.linalg as splin + +from numpy.testing import assert_allclose, assert_equal + +try: + import sparse +except Exception: + sparse = None + +pytestmark = pytest.mark.skipif(sparse is None, + reason="pydata/sparse not installed") + + +msg = "pydata/sparse (0.15.1) does not implement necessary operations" + + +sparse_params = (pytest.param("COO"), + pytest.param("DOK", marks=[pytest.mark.xfail(reason=msg)])) + +scipy_sparse_classes = [ + sp.bsr_array, + sp.csr_array, + sp.coo_array, + sp.csc_array, + sp.dia_array, + sp.dok_array +] + + +@pytest.fixture(params=sparse_params) +def sparse_cls(request): + return getattr(sparse, request.param) + + +@pytest.fixture(params=scipy_sparse_classes) +def sp_sparse_cls(request): + return request.param + + +@pytest.fixture +def same_matrix(sparse_cls, sp_sparse_cls): + np.random.seed(1234) + A_dense = np.random.rand(9, 9) + return sp_sparse_cls(A_dense), sparse_cls(A_dense) + + +@pytest.fixture +def matrices(sparse_cls): + np.random.seed(1234) + A_dense = np.random.rand(9, 9) + A_dense = A_dense @ A_dense.T + A_sparse = sparse_cls(A_dense) + b = np.random.rand(9) + return A_dense, A_sparse, b + + +def test_isolve_gmres(matrices): + # Several of the iterative solvers use the same + # isolve.utils.make_system wrapper code, so test just one of them. + A_dense, A_sparse, b = matrices + x, info = splin.gmres(A_sparse, b, atol=1e-15) + assert info == 0 + assert isinstance(x, np.ndarray) + assert_allclose(A_sparse @ x, b) + + +def test_lsmr(matrices): + A_dense, A_sparse, b = matrices + res0 = splin.lsmr(A_dense, b) + res = splin.lsmr(A_sparse, b) + assert_allclose(res[0], res0[0], atol=1e-3) + + +# test issue 17012 +def test_lsmr_output_shape(): + x = splin.lsmr(A=np.ones((10, 1)), b=np.zeros(10), x0=np.ones(1))[0] + assert_equal(x.shape, (1,)) + + +def test_lsqr(matrices): + A_dense, A_sparse, b = matrices + res0 = splin.lsqr(A_dense, b) + res = splin.lsqr(A_sparse, b) + assert_allclose(res[0], res0[0], atol=1e-5) + + +def test_eigs(matrices): + A_dense, A_sparse, v0 = matrices + + M_dense = np.diag(v0**2) + M_sparse = A_sparse.__class__(M_dense) + + w_dense, v_dense = splin.eigs(A_dense, k=3, v0=v0) + w, v = splin.eigs(A_sparse, k=3, v0=v0) + + assert_allclose(w, w_dense) + assert_allclose(v, v_dense) + + for M in [M_sparse, M_dense]: + w_dense, v_dense = splin.eigs(A_dense, M=M_dense, k=3, v0=v0) + w, v = splin.eigs(A_sparse, M=M, k=3, v0=v0) + + assert_allclose(w, w_dense) + assert_allclose(v, v_dense) + + w_dense, v_dense = splin.eigsh(A_dense, M=M_dense, k=3, v0=v0) + w, v = splin.eigsh(A_sparse, M=M, k=3, v0=v0) + + assert_allclose(w, w_dense) + assert_allclose(v, v_dense) + + +def test_svds(matrices): + A_dense, A_sparse, v0 = matrices + + u0, s0, vt0 = splin.svds(A_dense, k=2, v0=v0) + u, s, vt = splin.svds(A_sparse, k=2, v0=v0) + + assert_allclose(s, s0) + assert_allclose(np.abs(u), np.abs(u0)) + assert_allclose(np.abs(vt), np.abs(vt0)) + + +def test_lobpcg(matrices): + A_dense, A_sparse, x = matrices + X = x[:,None] + + w_dense, v_dense = splin.lobpcg(A_dense, X) + w, v = splin.lobpcg(A_sparse, X) + + assert_allclose(w, w_dense) + assert_allclose(v, v_dense) + + +def test_spsolve(matrices): + A_dense, A_sparse, b = matrices + b2 = np.random.rand(len(b), 3) + + x0 = splin.spsolve(sp.csc_array(A_dense), b) + x = splin.spsolve(A_sparse, b) + assert isinstance(x, np.ndarray) + assert_allclose(x, x0) + + x0 = splin.spsolve(sp.csc_array(A_dense), b) + x = splin.spsolve(A_sparse, b, use_umfpack=True) + assert isinstance(x, np.ndarray) + assert_allclose(x, x0) + + x0 = splin.spsolve(sp.csc_array(A_dense), b2) + x = splin.spsolve(A_sparse, b2) + assert isinstance(x, np.ndarray) + assert_allclose(x, x0) + + x0 = splin.spsolve(sp.csc_array(A_dense), + sp.csc_array(A_dense)) + x = splin.spsolve(A_sparse, A_sparse) + assert isinstance(x, type(A_sparse)) + assert_allclose(x.todense(), x0.todense()) + + +def test_splu(matrices): + A_dense, A_sparse, b = matrices + n = len(b) + sparse_cls = type(A_sparse) + + lu = splin.splu(A_sparse) + + assert isinstance(lu.L, sparse_cls) + assert isinstance(lu.U, sparse_cls) + + _Pr_scipy = sp.csc_array((np.ones(n), (lu.perm_r, np.arange(n)))) + _Pc_scipy = sp.csc_array((np.ones(n), (np.arange(n), lu.perm_c))) + Pr = sparse_cls.from_scipy_sparse(_Pr_scipy) + Pc = sparse_cls.from_scipy_sparse(_Pc_scipy) + A2 = Pr.T @ lu.L @ lu.U @ Pc.T + + assert_allclose(A2.todense(), A_sparse.todense()) + + z = lu.solve(A_sparse.todense()) + assert_allclose(z, np.eye(n), atol=1e-10) + + +def test_spilu(matrices): + A_dense, A_sparse, b = matrices + sparse_cls = type(A_sparse) + + lu = splin.spilu(A_sparse) + + assert isinstance(lu.L, sparse_cls) + assert isinstance(lu.U, sparse_cls) + + z = lu.solve(A_sparse.todense()) + assert_allclose(z, np.eye(len(b)), atol=1e-3) + + +def test_spsolve_triangular(matrices): + A_dense, A_sparse, b = matrices + A_sparse = sparse.tril(A_sparse) + + x = splin.spsolve_triangular(A_sparse, b) + assert_allclose(A_sparse @ x, b) + + +def test_onenormest(matrices): + A_dense, A_sparse, b = matrices + est0 = splin.onenormest(A_dense) + est = splin.onenormest(A_sparse) + assert_allclose(est, est0) + + +def test_norm(matrices): + A_dense, A_sparse, b = matrices + norm0 = splin.norm(sp.csr_array(A_dense)) + norm = splin.norm(A_sparse) + assert_allclose(norm, norm0) + + +def test_inv(matrices): + A_dense, A_sparse, b = matrices + x0 = splin.inv(sp.csc_array(A_dense)) + x = splin.inv(A_sparse) + assert_allclose(x.todense(), x0.todense()) + + +def test_expm(matrices): + A_dense, A_sparse, b = matrices + x0 = splin.expm(sp.csc_array(A_dense)) + x = splin.expm(A_sparse) + assert_allclose(x.todense(), x0.todense()) + + +def test_expm_multiply(matrices): + A_dense, A_sparse, b = matrices + x0 = splin.expm_multiply(A_dense, b) + x = splin.expm_multiply(A_sparse, b) + assert_allclose(x, x0) + + x0 = splin.expm_multiply(A_dense, A_dense) + x = splin.expm_multiply(A_sparse, A_sparse) + assert_allclose(x.todense(), x0) + + +def test_eq(same_matrix): + sp_sparse, pd_sparse = same_matrix + # temporary splint until pydata sparse support sparray equality + sp_sparse = sp.coo_matrix(sp_sparse).asformat(sp_sparse.format) + assert (sp_sparse == pd_sparse).all() + + +def test_ne(same_matrix): + sp_sparse, pd_sparse = same_matrix + # temporary splint until pydata sparse support sparray equality + sp_sparse = sp.coo_matrix(sp_sparse).asformat(sp_sparse.format) + assert not (sp_sparse != pd_sparse).any() + + +def test_ge(same_matrix): + sp_sparse, pd_sparse = same_matrix + # temporary splint until pydata sparse support sparray equality + sp_sparse = sp.coo_matrix(sp_sparse).asformat(sp_sparse.format) + assert (sp_sparse >= pd_sparse).all() + + +def test_gt(same_matrix): + sp_sparse, pd_sparse = same_matrix + # temporary splint until pydata sparse support sparray equality + sp_sparse = sp.coo_matrix(sp_sparse).asformat(sp_sparse.format) + assert not (sp_sparse > pd_sparse).any() diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/test_special_sparse_arrays.py b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/test_special_sparse_arrays.py new file mode 100644 index 0000000000000000000000000000000000000000..0af5aa2481b15c042e3649925aaed1b10eaa621c --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/linalg/tests/test_special_sparse_arrays.py @@ -0,0 +1,342 @@ +import pytest +import numpy as np +from numpy.testing import assert_array_equal, assert_allclose + +from scipy.sparse import diags_array, csgraph +from scipy.linalg import eigh + +from scipy.sparse.linalg import LaplacianNd +from scipy.sparse.linalg._special_sparse_arrays import Sakurai +from scipy.sparse.linalg._special_sparse_arrays import MikotaPair + +INT_DTYPES = [np.int8, np.int16, np.int32, np.int64] +REAL_DTYPES = [np.float32, np.float64] +COMPLEX_DTYPES = [np.complex64, np.complex128] +ALLDTYPES = INT_DTYPES + REAL_DTYPES + COMPLEX_DTYPES + + +class TestLaplacianNd: + """ + LaplacianNd tests + """ + + @pytest.mark.parametrize('bc', ['neumann', 'dirichlet', 'periodic']) + def test_1d_specific_shape(self, bc): + lap = LaplacianNd(grid_shape=(6, ), boundary_conditions=bc) + lapa = lap.toarray() + if bc == 'neumann': + a = np.array( + [ + [-1, 1, 0, 0, 0, 0], + [1, -2, 1, 0, 0, 0], + [0, 1, -2, 1, 0, 0], + [0, 0, 1, -2, 1, 0], + [0, 0, 0, 1, -2, 1], + [0, 0, 0, 0, 1, -1], + ] + ) + elif bc == 'dirichlet': + a = np.array( + [ + [-2, 1, 0, 0, 0, 0], + [1, -2, 1, 0, 0, 0], + [0, 1, -2, 1, 0, 0], + [0, 0, 1, -2, 1, 0], + [0, 0, 0, 1, -2, 1], + [0, 0, 0, 0, 1, -2], + ] + ) + else: + a = np.array( + [ + [-2, 1, 0, 0, 0, 1], + [1, -2, 1, 0, 0, 0], + [0, 1, -2, 1, 0, 0], + [0, 0, 1, -2, 1, 0], + [0, 0, 0, 1, -2, 1], + [1, 0, 0, 0, 1, -2], + ] + ) + assert_array_equal(a, lapa) + + def test_1d_with_graph_laplacian(self): + n = 6 + G = diags_array(np.ones(n - 1), offsets=1, format='dia') + Lf = csgraph.laplacian(G, symmetrized=True, form='function') + La = csgraph.laplacian(G, symmetrized=True, form='array') + grid_shape = (n,) + bc = 'neumann' + lap = LaplacianNd(grid_shape, boundary_conditions=bc) + assert_array_equal(lap(np.eye(n)), -Lf(np.eye(n))) + assert_array_equal(lap.toarray(), -La.toarray()) + # https://github.com/numpy/numpy/issues/24351 + assert_array_equal(lap.tosparse().toarray(), -La.toarray()) + + @pytest.mark.parametrize('grid_shape', [(6, ), (2, 3), (2, 3, 4)]) + @pytest.mark.parametrize('bc', ['neumann', 'dirichlet', 'periodic']) + def test_eigenvalues(self, grid_shape, bc): + lap = LaplacianNd(grid_shape, boundary_conditions=bc, dtype=np.float64) + L = lap.toarray() + eigvals = eigh(L, eigvals_only=True) + n = np.prod(grid_shape) + eigenvalues = lap.eigenvalues() + dtype = eigenvalues.dtype + atol = n * n * np.finfo(dtype).eps + # test the default ``m = None`` + assert_allclose(eigenvalues, eigvals, atol=atol) + # test every ``m > 0`` + for m in np.arange(1, n + 1): + assert_array_equal(lap.eigenvalues(m), eigenvalues[-m:]) + + @pytest.mark.parametrize('grid_shape', [(6, ), (2, 3), (2, 3, 4)]) + @pytest.mark.parametrize('bc', ['neumann', 'dirichlet', 'periodic']) + def test_eigenvectors(self, grid_shape, bc): + lap = LaplacianNd(grid_shape, boundary_conditions=bc, dtype=np.float64) + n = np.prod(grid_shape) + eigenvalues = lap.eigenvalues() + eigenvectors = lap.eigenvectors() + dtype = eigenvectors.dtype + atol = n * n * max(np.finfo(dtype).eps, np.finfo(np.double).eps) + # test the default ``m = None`` every individual eigenvector + for i in np.arange(n): + r = lap.toarray() @ eigenvectors[:, i] - eigenvectors[:, i] * eigenvalues[i] + assert_allclose(r, np.zeros_like(r), atol=atol) + # test every ``m > 0`` + for m in np.arange(1, n + 1): + e = lap.eigenvalues(m) + ev = lap.eigenvectors(m) + r = lap.toarray() @ ev - ev @ np.diag(e) + assert_allclose(r, np.zeros_like(r), atol=atol) + # test orthogonality + assert_allclose(eigenvectors @ eigenvectors.T, np.eye(n), atol=atol) + # test full eigendecomposition + L = eigenvectors @ np.diag(eigenvalues) @ eigenvectors.T + assert_allclose(L, lap.toarray(), atol=atol) + + @pytest.mark.parametrize('grid_shape', [(6, ), (2, 3), (2, 3, 4)]) + @pytest.mark.parametrize('bc', ['neumann', 'dirichlet', 'periodic']) + def test_toarray_tosparse_consistency(self, grid_shape, bc): + lap = LaplacianNd(grid_shape, boundary_conditions=bc) + n = np.prod(grid_shape) + assert_array_equal(lap.toarray(), lap(np.eye(n))) + assert_array_equal(lap.tosparse().toarray(), lap.toarray()) + + @pytest.mark.parametrize('dtype', ALLDTYPES) + @pytest.mark.parametrize('grid_shape', [(6, ), (2, 3), (2, 3, 4)]) + @pytest.mark.parametrize('bc', ['neumann', 'dirichlet', 'periodic']) + def test_linearoperator_shape_dtype(self, grid_shape, bc, dtype): + lap = LaplacianNd(grid_shape, boundary_conditions=bc, dtype=dtype) + n = np.prod(grid_shape) + assert lap.shape == (n, n) + assert lap.dtype == dtype + assert_array_equal( + LaplacianNd( + grid_shape, boundary_conditions=bc, dtype=dtype + ).toarray(), + LaplacianNd(grid_shape, boundary_conditions=bc) + .toarray() + .astype(dtype), + ) + assert_array_equal( + LaplacianNd(grid_shape, boundary_conditions=bc, dtype=dtype) + .tosparse() + .toarray(), + LaplacianNd(grid_shape, boundary_conditions=bc) + .tosparse() + .toarray() + .astype(dtype), + ) + + @pytest.mark.parametrize('dtype', ALLDTYPES) + @pytest.mark.parametrize('grid_shape', [(6, ), (2, 3), (2, 3, 4)]) + @pytest.mark.parametrize('bc', ['neumann', 'dirichlet', 'periodic']) + def test_dot(self, grid_shape, bc, dtype): + """ Test the dot-product for type preservation and consistency. + """ + lap = LaplacianNd(grid_shape, boundary_conditions=bc) + n = np.prod(grid_shape) + x0 = np.arange(n) + x1 = x0.reshape((-1, 1)) + x2 = np.arange(2 * n).reshape((n, 2)) + input_set = [x0, x1, x2] + for x in input_set: + y = lap.dot(x.astype(dtype)) + assert x.shape == y.shape + assert y.dtype == dtype + if x.ndim == 2: + yy = lap.toarray() @ x.astype(dtype) + assert yy.dtype == dtype + np.array_equal(y, yy) + + def test_boundary_conditions_value_error(self): + with pytest.raises(ValueError, match="Unknown value 'robin'"): + LaplacianNd(grid_shape=(6, ), boundary_conditions='robin') + + +class TestSakurai: + """ + Sakurai tests + """ + + def test_specific_shape(self): + sak = Sakurai(6) + assert_array_equal(sak.toarray(), sak(np.eye(6))) + a = np.array( + [ + [ 5, -4, 1, 0, 0, 0], + [-4, 6, -4, 1, 0, 0], + [ 1, -4, 6, -4, 1, 0], + [ 0, 1, -4, 6, -4, 1], + [ 0, 0, 1, -4, 6, -4], + [ 0, 0, 0, 1, -4, 5] + ] + ) + + np.array_equal(a, sak.toarray()) + np.array_equal(sak.tosparse().toarray(), sak.toarray()) + ab = np.array( + [ + [ 1, 1, 1, 1, 1, 1], + [-4, -4, -4, -4, -4, -4], + [ 5, 6, 6, 6, 6, 5] + ] + ) + np.array_equal(ab, sak.tobanded()) + e = np.array( + [0.03922866, 0.56703972, 2.41789479, 5.97822974, + 10.54287655, 14.45473055] + ) + np.array_equal(e, sak.eigenvalues()) + np.array_equal(e[:2], sak.eigenvalues(2)) + + # `Sakurai` default `dtype` is `np.int8` as its entries are small integers + @pytest.mark.parametrize('dtype', ALLDTYPES) + def test_linearoperator_shape_dtype(self, dtype): + n = 7 + sak = Sakurai(n, dtype=dtype) + assert sak.shape == (n, n) + assert sak.dtype == dtype + assert_array_equal(sak.toarray(), Sakurai(n).toarray().astype(dtype)) + assert_array_equal(sak.tosparse().toarray(), + Sakurai(n).tosparse().toarray().astype(dtype)) + + @pytest.mark.parametrize('dtype', ALLDTYPES) + @pytest.mark.parametrize('argument_dtype', ALLDTYPES) + def test_dot(self, dtype, argument_dtype): + """ Test the dot-product for type preservation and consistency. + """ + result_dtype = np.promote_types(argument_dtype, dtype) + n = 5 + sak = Sakurai(n) + x0 = np.arange(n) + x1 = x0.reshape((-1, 1)) + x2 = np.arange(2 * n).reshape((n, 2)) + input_set = [x0, x1, x2] + for x in input_set: + y = sak.dot(x.astype(argument_dtype)) + assert x.shape == y.shape + assert np.can_cast(y.dtype, result_dtype) + if x.ndim == 2: + ya = sak.toarray() @ x.astype(argument_dtype) + np.array_equal(y, ya) + assert np.can_cast(ya.dtype, result_dtype) + ys = sak.tosparse() @ x.astype(argument_dtype) + np.array_equal(y, ys) + assert np.can_cast(ys.dtype, result_dtype) + +class TestMikotaPair: + """ + MikotaPair tests + """ + # both MikotaPair `LinearOperator`s share the same dtype + # while `MikotaK` `dtype` can be as small as its default `np.int32` + # since its entries are integers, the `MikotaM` involves inverses + # so its smallest still accurate `dtype` is `np.float32` + tested_types = REAL_DTYPES + COMPLEX_DTYPES + + def test_specific_shape(self): + n = 6 + mik = MikotaPair(n) + mik_k = mik.k + mik_m = mik.m + assert_array_equal(mik_k.toarray(), mik_k(np.eye(n))) + assert_array_equal(mik_m.toarray(), mik_m(np.eye(n))) + + k = np.array( + [ + [11, -5, 0, 0, 0, 0], + [-5, 9, -4, 0, 0, 0], + [ 0, -4, 7, -3, 0, 0], + [ 0, 0, -3, 5, -2, 0], + [ 0, 0, 0, -2, 3, -1], + [ 0, 0, 0, 0, -1, 1] + ] + ) + np.array_equal(k, mik_k.toarray()) + np.array_equal(mik_k.tosparse().toarray(), k) + kb = np.array( + [ + [ 0, -5, -4, -3, -2, -1], + [11, 9, 7, 5, 3, 1] + ] + ) + np.array_equal(kb, mik_k.tobanded()) + + minv = np.arange(1, n + 1) + np.array_equal(np.diag(1. / minv), mik_m.toarray()) + np.array_equal(mik_m.tosparse().toarray(), mik_m.toarray()) + np.array_equal(1. / minv, mik_m.tobanded()) + + e = np.array([ 1, 4, 9, 16, 25, 36]) + np.array_equal(e, mik.eigenvalues()) + np.array_equal(e[:2], mik.eigenvalues(2)) + + @pytest.mark.parametrize('dtype', tested_types) + def test_linearoperator_shape_dtype(self, dtype): + n = 7 + mik = MikotaPair(n, dtype=dtype) + mik_k = mik.k + mik_m = mik.m + assert mik_k.shape == (n, n) + assert mik_k.dtype == dtype + assert mik_m.shape == (n, n) + assert mik_m.dtype == dtype + mik_default_dtype = MikotaPair(n) + mikd_k = mik_default_dtype.k + mikd_m = mik_default_dtype.m + assert mikd_k.shape == (n, n) + assert mikd_k.dtype == np.float64 + assert mikd_m.shape == (n, n) + assert mikd_m.dtype == np.float64 + assert_array_equal(mik_k.toarray(), + mikd_k.toarray().astype(dtype)) + assert_array_equal(mik_k.tosparse().toarray(), + mikd_k.tosparse().toarray().astype(dtype)) + + @pytest.mark.parametrize('dtype', tested_types) + @pytest.mark.parametrize('argument_dtype', ALLDTYPES) + def test_dot(self, dtype, argument_dtype): + """ Test the dot-product for type preservation and consistency. + """ + result_dtype = np.promote_types(argument_dtype, dtype) + n = 5 + mik = MikotaPair(n, dtype=dtype) + mik_k = mik.k + mik_m = mik.m + x0 = np.arange(n) + x1 = x0.reshape((-1, 1)) + x2 = np.arange(2 * n).reshape((n, 2)) + lo_set = [mik_k, mik_m] + input_set = [x0, x1, x2] + for lo in lo_set: + for x in input_set: + y = lo.dot(x.astype(argument_dtype)) + assert x.shape == y.shape + assert np.can_cast(y.dtype, result_dtype) + if x.ndim == 2: + ya = lo.toarray() @ x.astype(argument_dtype) + np.array_equal(y, ya) + assert np.can_cast(ya.dtype, result_dtype) + ys = lo.tosparse() @ x.astype(argument_dtype) + np.array_equal(y, ys) + assert np.can_cast(ys.dtype, result_dtype) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/sparsetools.py b/venv/lib/python3.12/site-packages/scipy/sparse/sparsetools.py new file mode 100644 index 0000000000000000000000000000000000000000..404e431d89d479520d2198ae73b9eab7b23a80f7 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/sparsetools.py @@ -0,0 +1,17 @@ +# This file is not meant for public use and will be removed in SciPy v2.0.0. +# Use the `scipy.sparse` namespace for importing the functions +# included below. + +from scipy._lib.deprecation import _sub_module_deprecation + +__all__: list[str] = [] + + +def __dir__(): + return __all__ + + +def __getattr__(name): + return _sub_module_deprecation(sub_package="sparse", module="sparsetools", + private_modules=["_sparsetools"], all=__all__, + attribute=name) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/spfuncs.py b/venv/lib/python3.12/site-packages/scipy/sparse/spfuncs.py new file mode 100644 index 0000000000000000000000000000000000000000..911969e414d4a1d3888900ad8392b5fc2177c850 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/spfuncs.py @@ -0,0 +1,17 @@ +# This file is not meant for public use and will be removed in SciPy v2.0.0. +# Use the `scipy.sparse` namespace for importing the functions +# included below. + +from scipy._lib.deprecation import _sub_module_deprecation + +__all__: list[str] = [] + + +def __dir__(): + return __all__ + + +def __getattr__(name): + return _sub_module_deprecation(sub_package="sparse", module="spfuncs", + private_modules=["_spfuncs"], all=__all__, + attribute=name) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/sputils.py b/venv/lib/python3.12/site-packages/scipy/sparse/sputils.py new file mode 100644 index 0000000000000000000000000000000000000000..4ddd27a43889609b0642bd7579e13c8e3c460a8b --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/sputils.py @@ -0,0 +1,17 @@ +# This file is not meant for public use and will be removed in SciPy v2.0.0. +# Use the `scipy.sparse` namespace for importing the functions +# included below. + +from scipy._lib.deprecation import _sub_module_deprecation + +__all__: list[str] = [] + + +def __dir__(): + return __all__ + + +def __getattr__(name): + return _sub_module_deprecation(sub_package="sparse", module="sputils", + private_modules=["_sputils"], all=__all__, + attribute=name) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/tests/__init__.py b/venv/lib/python3.12/site-packages/scipy/sparse/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4e2de51a1d9dd0dd9e6fbf1c0b03c557602fab7f Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/__init__.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_64bit.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_64bit.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..40897a85e8130a83ffc5d50521914b054831735f Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_64bit.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_arithmetic1d.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_arithmetic1d.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7864142ad2ab9d5dcafb6d041a319395fa423d99 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_arithmetic1d.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_array_api.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_array_api.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ebd32b1a95c064227906372f6c9c23a1671d5fff Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_array_api.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_base.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_base.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b06abb0c0e14cad69429c9751a20c4c002f613be --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_base.cpython-312.pyc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2e8dfb1aff7f9dd0838479b27bd46141ef2fa6f6b757b4b38eb200dfedaa8cea +size 357811 diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_common1d.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_common1d.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..38462852c4bd320aea944c6de5959749f3599da1 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_common1d.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_construct.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_construct.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b012ce63cbaa994827660fd89618dacbe2d5783b Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_construct.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_coo.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_coo.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..90750abd1a6f94aa5e535ff980de43cfdbb06c60 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_coo.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_csc.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_csc.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f25b79e5e632bf5f82bc9b53cb55c5998f0244af Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_csc.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_csr.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_csr.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3529281e9fb5a40340345dc6b66bde7b39fdf2b0 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_csr.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_dok.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_dok.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..94e9a01da157487b225fd3240b88563249752121 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_dok.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_extract.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_extract.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d25ea34b3d44100463a0f0c8fa0d4c15a335525b Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_extract.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_indexing1d.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_indexing1d.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..86796ad1cb8df50b47f1d1352d95ea8bf2d4c7bd Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_indexing1d.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_matrix_io.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_matrix_io.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ef1a7bb1e0e3f7012ace4f6917e9c1597d658540 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_matrix_io.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_minmax1d.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_minmax1d.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ca61b1d785eda7b532ac94682afdfc2123ec3c05 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_minmax1d.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_sparsetools.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_sparsetools.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5c5ac3bbb365f45789345ffcfd9cbd0f0659edac Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_sparsetools.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_spfuncs.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_spfuncs.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8177c7e0d49389aa15875000ca3b973ec551a675 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_spfuncs.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_sputils.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_sputils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6e39ae5a7c1363fa415d04644d079844a9d28223 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/sparse/tests/__pycache__/test_sputils.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_64bit.py b/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_64bit.py new file mode 100644 index 0000000000000000000000000000000000000000..64cc7245a631a25712d89ef6078faec5946c2cbb --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_64bit.py @@ -0,0 +1,304 @@ +""" Test functions involving 64bit or 32bit indexing """ +import pytest +import numpy as np +from scipy.sparse import ( + bsr_array, coo_array, csc_array, csr_array, dia_array, + bsr_matrix, coo_matrix, csc_matrix, csr_matrix, dia_matrix, +) + +# rename to avoid pytest collecting them in this module +from .test_base import ( + TestBSR as _TestBSR, + TestCOO as _TestCOO, + TestCSC as _TestCSC, + TestCSR as _TestCSR, + TestDIA as _TestDIA, + TestDOK as _TestDOK, + TestLIL as _TestLIL, + TestBSRMatrix as _TestBSRMatrix, + TestCOOMatrix as _TestCOOMatrix, + TestCSCMatrix as _TestCSCMatrix, + TestCSRMatrix as _TestCSRMatrix, + TestDIAMatrix as _TestDIAMatrix, + TestDOKMatrix as _TestDOKMatrix, + TestLILMatrix as _TestLILMatrix, + with_64bit_maxval_limit, +) + + +# name : reason not tested here +SKIP_TESTS = { + 'test_expm': 'expm for 64-bit indices not available', + 'test_inv': 'linsolve for 64-bit indices not available', + 'test_solve': 'linsolve for 64-bit indices not available', + 'test_scalar_idx_dtype': 'test implemented in base class', + 'test_large_dimensions_reshape': 'test actually requires 64-bit to work', + 'test_constructor_smallcol': 'test verifies int32 indexes', + 'test_constructor_largecol': 'test verifies int64 indexes', + 'test_sparse_format_conversions': 'test produces deprecation warning', + 'test_tocoo_tocsr_tocsc_gh19245': 'test verifies int32 indexes', +} + + +def cases_64bit(sp_api): + """Yield all tests for all formats + + This is more than testing get_index_dtype. It allows checking whether upcasting + or downcasting the index dtypes affects test results. The approach used here + does not try to figure out which tests might fail due to 32/64-bit issues. + We just run them all. + So, each test method in that uses cases_64bit reruns most of the test suite! + """ + if sp_api == "sparray": + TEST_CLASSES = [_TestBSR, _TestCOO, _TestCSC, _TestCSR, _TestDIA] + elif sp_api == "sparray-extra": + # lil/dok->other conversion operations use get_index_dtype + # so we include lil & dok test suite even though they do not + # use get_index_dtype within the class. That means many of + # these tests are superfluous, but it's hard to pick which + TEST_CLASSES = [_TestDOK, _TestLIL] + elif sp_api == "spmatrix": + TEST_CLASSES = [_TestBSRMatrix, _TestCOOMatrix, _TestCSCMatrix, + _TestCSRMatrix, _TestDIAMatrix] + elif sp_api == "spmatrix-extra": + # lil/dok->other conversion operations use get_index_dtype + TEST_CLASSES = [_TestDOKMatrix, _TestLILMatrix] + else: + raise ValueError(f"parameter {sp_api=} is not valid") + + for cls in TEST_CLASSES: + for method_name in sorted(dir(cls)): + method = getattr(cls, method_name) + if (method_name.startswith('test_') and + not getattr(method, 'slow', False)): + marks = [] + + msg = SKIP_TESTS.get(method_name) + if msg: + marks.append(pytest.mark.skip(reason=msg)) + + markers = getattr(method, 'pytestmark', []) + for mark in markers: + if mark.name in ('skipif', 'skip', 'xfail', 'xslow'): + marks.append(mark) + + yield pytest.param(cls, method_name, marks=marks) + + +@pytest.mark.thread_unsafe(reason="fails in parallel") +class RunAll64Bit: + def _check_resiliency(self, cls, method_name, **kw): + # Resiliency test, to check that sparse matrices deal reasonably + # with varying index data types. + + @with_64bit_maxval_limit(**kw) + def check(cls, method_name): + instance = cls() + if hasattr(instance, 'setup_method'): + instance.setup_method() + try: + getattr(instance, method_name)() + finally: + if hasattr(instance, 'teardown_method'): + instance.teardown_method() + + check(cls, method_name) + + +class Test64BitArray(RunAll64Bit): + # inheritance of pytest test classes does not separate marks for subclasses. + # So we define these functions in both Array and Matrix versions. + @pytest.mark.parametrize('cls,method_name', list(cases_64bit("sparray"))) + def test_resiliency_limit_10(self, cls, method_name): + self._check_resiliency(cls, method_name, maxval_limit=10) + + @pytest.mark.parametrize('cls,method_name', list(cases_64bit("sparray"))) + def test_resiliency_all_32(self, cls, method_name): + self._check_resiliency(cls, method_name, fixed_dtype=np.int32) + + @pytest.mark.parametrize('cls,method_name', list(cases_64bit("sparray"))) + def test_resiliency_all_64(self, cls, method_name): + self._check_resiliency(cls, method_name, fixed_dtype=np.int64) + + @pytest.mark.fail_slow(2) + @pytest.mark.parametrize('cls,method_name', list(cases_64bit("sparray"))) + def test_resiliency_random(self, cls, method_name): + self._check_resiliency(cls, method_name) + + +class Test64BitMatrix(RunAll64Bit): + # assert_32bit=True only for spmatrix cuz sparray does not check index content + @pytest.mark.fail_slow(5) + @pytest.mark.parametrize('cls,method_name', list(cases_64bit("spmatrix"))) + def test_no_64(self, cls, method_name): + self._check_resiliency(cls, method_name, assert_32bit=True) + + +class Test64BitMatrixSameAsArray(RunAll64Bit): + # inheritance of pytest test classes does not separate marks for subclasses. + # So we define these functions in both Array and Matrix versions. + @pytest.mark.parametrize('cls,method_name', list(cases_64bit("spmatrix"))) + def test_resiliency_limit_10(self, cls, method_name): + self._check_resiliency(cls, method_name, maxval_limit=10) + + @pytest.mark.parametrize('cls,method_name', list(cases_64bit("spmatrix"))) + def test_resiliency_all_32(self, cls, method_name): + self._check_resiliency(cls, method_name, fixed_dtype=np.int32) + + @pytest.mark.parametrize('cls,method_name', list(cases_64bit("spmatrix"))) + def test_resiliency_all_64(self, cls, method_name): + self._check_resiliency(cls, method_name, fixed_dtype=np.int64) + + @pytest.mark.fail_slow(2) + @pytest.mark.parametrize('cls,method_name', list(cases_64bit("spmatrix"))) + def test_resiliency_random(self, cls, method_name): + # Resiliency check that sparse deals with varying index data types. + self._check_resiliency(cls, method_name) + +# Extra: LIL and DOK classes. no direct get_index_dtype, but convert to classes that do +@pytest.mark.xslow +class Test64BitArrayExtra(RunAll64Bit): + # inheritance of pytest test classes does not separate marks for subclasses. + # So we define these functions in both Array and Matrix versions. + @pytest.mark.parametrize('cls,method_name', list(cases_64bit("sparray-extra"))) + def test_resiliency_limit_10(self, cls, method_name): + self._check_resiliency(cls, method_name, maxval_limit=10) + + @pytest.mark.parametrize('cls,method_name', list(cases_64bit("sparray-extra"))) + def test_resiliency_all_32(self, cls, method_name): + self._check_resiliency(cls, method_name, fixed_dtype=np.int32) + + @pytest.mark.parametrize('cls,method_name', list(cases_64bit("sparray-extra"))) + def test_resiliency_all_64(self, cls, method_name): + self._check_resiliency(cls, method_name, fixed_dtype=np.int64) + + @pytest.mark.fail_slow(2) + @pytest.mark.parametrize('cls,method_name', list(cases_64bit("sparray-extra"))) + def test_resiliency_random(self, cls, method_name): + # Resiliency check that sparse deals with varying index data types. + self._check_resiliency(cls, method_name) + + +# Extra: LIL and DOK classes. no direct get_index_dtype, but convert to classes that do +@pytest.mark.xslow +class Test64BitMatrixExtra(RunAll64Bit): + # assert_32bit=True only for spmatrix cuz sparray does not check index content + @pytest.mark.fail_slow(5) + @pytest.mark.parametrize('cls,method_name', list(cases_64bit("spmatrix-extra"))) + def test_no_64(self, cls, method_name): + self._check_resiliency(cls, method_name, assert_32bit=True) + + # inheritance of pytest test classes does not separate marks for subclasses. + # So we define these functions in both Array and Matrix versions. + @pytest.mark.parametrize('cls,method_name', list(cases_64bit("spmatrix-extra"))) + def test_resiliency_limit_10(self, cls, method_name): + self._check_resiliency(cls, method_name, maxval_limit=10) + + @pytest.mark.parametrize('cls,method_name', list(cases_64bit("spmatrix-extra"))) + def test_resiliency_all_32(self, cls, method_name): + self._check_resiliency(cls, method_name, fixed_dtype=np.int32) + + @pytest.mark.parametrize('cls,method_name', list(cases_64bit("spmatrix-extra"))) + def test_resiliency_all_64(self, cls, method_name): + self._check_resiliency(cls, method_name, fixed_dtype=np.int64) + + @pytest.mark.fail_slow(2) + @pytest.mark.parametrize('cls,method_name', list(cases_64bit("spmatrix-extra"))) + def test_resiliency_random(self, cls, method_name): + # Resiliency check that sparse deals with varying index data types. + self._check_resiliency(cls, method_name) + + +@pytest.mark.thread_unsafe(reason="Fails in parallel for unknown reasons") +class Test64BitTools: + # classes that use get_index_dtype + MAT_CLASSES = [ + bsr_matrix, coo_matrix, csc_matrix, csr_matrix, dia_matrix, + bsr_array, coo_array, csc_array, csr_array, dia_array, + ] + + def _compare_index_dtype(self, m, dtype): + dtype = np.dtype(dtype) + if m.format in ['csc', 'csr', 'bsr']: + return (m.indices.dtype == dtype) and (m.indptr.dtype == dtype) + elif m.format == 'coo': + return (m.row.dtype == dtype) and (m.col.dtype == dtype) + elif m.format == 'dia': + return (m.offsets.dtype == dtype) + else: + raise ValueError(f"matrix {m!r} has no integer indices") + + def test_decorator_maxval_limit(self): + # Test that the with_64bit_maxval_limit decorator works + + @with_64bit_maxval_limit(maxval_limit=10) + def check(mat_cls): + m = mat_cls(np.random.rand(10, 1)) + assert self._compare_index_dtype(m, np.int32) + m = mat_cls(np.random.rand(11, 1)) + assert self._compare_index_dtype(m, np.int64) + + for mat_cls in self.MAT_CLASSES: + check(mat_cls) + + def test_decorator_maxval_random(self): + # Test that the with_64bit_maxval_limit decorator works (2) + + @with_64bit_maxval_limit(random=True) + def check(mat_cls): + seen_32 = False + seen_64 = False + for k in range(100): + m = mat_cls(np.random.rand(9, 9)) + seen_32 = seen_32 or self._compare_index_dtype(m, np.int32) + seen_64 = seen_64 or self._compare_index_dtype(m, np.int64) + if seen_32 and seen_64: + break + else: + raise AssertionError("both 32 and 64 bit indices not seen") + + for mat_cls in self.MAT_CLASSES: + check(mat_cls) + + def test_downcast_intp(self): + # Check that bincount and ufunc.reduceat intp downcasts are + # dealt with. The point here is to trigger points in the code + # that can fail on 32-bit systems when using 64-bit indices, + # due to use of functions that only work with intp-size indices. + + @with_64bit_maxval_limit(fixed_dtype=np.int64, downcast_maxval=1) + def check_limited(csc_container, csr_container, coo_container): + # These involve indices larger than `downcast_maxval` + a = csc_container([[1, 2], [3, 4], [5, 6]]) + pytest.raises(AssertionError, a.count_nonzero, axis=1) + pytest.raises(AssertionError, a.sum, axis=0) + + a = csr_container([[1, 2, 3], [3, 4, 6]]) + pytest.raises(AssertionError, a.count_nonzero, axis=0) + pytest.raises(AssertionError, a.sum, axis=1) + + a = coo_container([[1, 2, 3], [3, 4, 5]]) + pytest.raises(AssertionError, a.count_nonzero, axis=0) + a.has_canonical_format = False + pytest.raises(AssertionError, a.sum_duplicates) + + @with_64bit_maxval_limit(fixed_dtype=np.int64) + def check_unlimited(csc_container, csr_container, coo_container): + # These involve indices smaller than `downcast_maxval` + a = csc_container([[1, 2], [3, 4], [5, 6]]) + a.count_nonzero(axis=1) + a.sum(axis=0) + + a = csr_container([[1, 2, 3], [3, 4, 6]]) + a.count_nonzero(axis=0) + a.sum(axis=1) + + a = coo_container([[1, 2, 3], [3, 4, 5]]) + a.count_nonzero(axis=0) + a.has_canonical_format = False + a.sum_duplicates() + + check_limited(csc_array, csr_array, coo_array) + check_unlimited(csc_array, csr_array, coo_array) + check_limited(csc_matrix, csr_matrix, coo_matrix) + check_unlimited(csc_matrix, csr_matrix, coo_matrix) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_arithmetic1d.py b/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_arithmetic1d.py new file mode 100644 index 0000000000000000000000000000000000000000..62b56f5fe2f69d9188d1c01cf02521f7dd2f1e9d --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_arithmetic1d.py @@ -0,0 +1,341 @@ +"""Test of 1D arithmetic operations""" + +import pytest + +import numpy as np +from numpy.testing import assert_equal, assert_allclose + +from scipy.sparse import coo_array, csr_array +from scipy.sparse._sputils import isscalarlike + + +spcreators = [coo_array, csr_array] +math_dtypes = [np.int64, np.float64, np.complex128] + + +def toarray(a): + if isinstance(a, np.ndarray) or isscalarlike(a): + return a + return a.toarray() + +@pytest.fixture +def dat1d(): + return np.array([3, 0, 1, 0], 'd') + + +@pytest.fixture +def datsp_math_dtypes(dat1d): + dat_dtypes = {dtype: dat1d.astype(dtype) for dtype in math_dtypes} + return { + sp: [(dtype, dat, sp(dat)) for dtype, dat in dat_dtypes.items()] + for sp in spcreators + } + + +@pytest.mark.parametrize("spcreator", spcreators) +class TestArithmetic1D: + def test_empty_arithmetic(self, spcreator): + shape = (5,) + for mytype in [ + np.dtype('int32'), + np.dtype('float32'), + np.dtype('float64'), + np.dtype('complex64'), + np.dtype('complex128'), + ]: + a = spcreator(shape, dtype=mytype) + b = a + a + c = 2 * a + assert isinstance(a @ a.tocsr(), np.ndarray) + assert isinstance(a @ a.tocoo(), np.ndarray) + for m in [a, b, c]: + assert m @ m == a.toarray() @ a.toarray() + assert m.dtype == mytype + assert toarray(m).dtype == mytype + + def test_abs(self, spcreator): + A = np.array([-1, 0, 17, 0, -5, 0, 1, -4, 0, 0, 0, 0], 'd') + assert_equal(abs(A), abs(spcreator(A)).toarray()) + + def test_round(self, spcreator): + A = np.array([-1.35, 0.56, 17.25, -5.98], 'd') + Asp = spcreator(A) + assert_equal(np.around(A, decimals=1), round(Asp, ndigits=1).toarray()) + + def test_elementwise_power(self, spcreator): + A = np.array([-4, -3, -2, -1, 0, 1, 2, 3, 4], 'd') + Asp = spcreator(A) + assert_equal(np.power(A, 2), Asp.power(2).toarray()) + + # element-wise power function needs a scalar power + with pytest.raises(NotImplementedError, match='input is not scalar'): + spcreator(A).power(A) + + def test_real(self, spcreator): + D = np.array([1 + 3j, 2 - 4j]) + A = spcreator(D) + assert_equal(A.real.toarray(), D.real) + + def test_imag(self, spcreator): + D = np.array([1 + 3j, 2 - 4j]) + A = spcreator(D) + assert_equal(A.imag.toarray(), D.imag) + + def test_mul_scalar(self, spcreator, datsp_math_dtypes): + for dtype, dat, datsp in datsp_math_dtypes[spcreator]: + assert_equal(dat * 2, (datsp * 2).toarray()) + assert_equal(dat * 17.3, (datsp * 17.3).toarray()) + + def test_rmul_scalar(self, spcreator, datsp_math_dtypes): + for dtype, dat, datsp in datsp_math_dtypes[spcreator]: + assert_equal(2 * dat, (2 * datsp).toarray()) + assert_equal(17.3 * dat, (17.3 * datsp).toarray()) + + def test_sub(self, spcreator, datsp_math_dtypes): + for dtype, dat, datsp in datsp_math_dtypes[spcreator]: + if dtype == np.dtype('bool'): + # boolean array subtraction deprecated in 1.9.0 + continue + + assert_equal((datsp - datsp).toarray(), np.zeros(4)) + assert_equal((datsp - 0).toarray(), dat) + + A = spcreator([1, -4, 0, 2], dtype='d') + assert_equal((datsp - A).toarray(), dat - A.toarray()) + assert_equal((A - datsp).toarray(), A.toarray() - dat) + + # test broadcasting + assert_equal(datsp.toarray() - dat[0], dat - dat[0]) + + def test_add0(self, spcreator, datsp_math_dtypes): + for dtype, dat, datsp in datsp_math_dtypes[spcreator]: + # Adding 0 to a sparse matrix + assert_equal((datsp + 0).toarray(), dat) + # use sum (which takes 0 as a starting value) + sumS = sum([k * datsp for k in range(1, 3)]) + sumD = sum([k * dat for k in range(1, 3)]) + assert_allclose(sumS.toarray(), sumD) + + def test_elementwise_multiply(self, spcreator): + # real/real + A = np.array([4, 0, 9]) + B = np.array([0, 7, -1]) + Asp = spcreator(A) + Bsp = spcreator(B) + assert_allclose(Asp.multiply(Bsp).toarray(), A * B) # sparse/sparse + assert_allclose(Asp.multiply(B).toarray(), A * B) # sparse/dense + + # complex/complex + C = np.array([1 - 2j, 0 + 5j, -1 + 0j]) + D = np.array([5 + 2j, 7 - 3j, -2 + 1j]) + Csp = spcreator(C) + Dsp = spcreator(D) + assert_allclose(Csp.multiply(Dsp).toarray(), C * D) # sparse/sparse + assert_allclose(Csp.multiply(D).toarray(), C * D) # sparse/dense + + # real/complex + assert_allclose(Asp.multiply(Dsp).toarray(), A * D) # sparse/sparse + assert_allclose(Asp.multiply(D).toarray(), A * D) # sparse/dense + + def test_elementwise_multiply_broadcast(self, spcreator): + A = np.array([4]) + B = np.array([[-9]]) + C = np.array([1, -1, 0]) + D = np.array([[7, 9, -9]]) + E = np.array([[3], [2], [1]]) + F = np.array([[8, 6, 3], [-4, 3, 2], [6, 6, 6]]) + G = [1, 2, 3] + H = np.ones((3, 4)) + J = H.T + K = np.array([[0]]) + L = np.array([[[1, 2], [0, 1]]]) + + # Some arrays can't be cast as spmatrices (A, C, L) so leave + # them out. + Asp = spcreator(A) + Csp = spcreator(C) + Gsp = spcreator(G) + # 2d arrays + Bsp = spcreator(B) + Dsp = spcreator(D) + Esp = spcreator(E) + Fsp = spcreator(F) + Hsp = spcreator(H) + Hspp = spcreator(H[0, None]) + Jsp = spcreator(J) + Jspp = spcreator(J[:, 0, None]) + Ksp = spcreator(K) + + matrices = [A, B, C, D, E, F, G, H, J, K, L] + spmatrices = [Asp, Bsp, Csp, Dsp, Esp, Fsp, Gsp, Hsp, Hspp, Jsp, Jspp, Ksp] + sp1dmatrices = [Asp, Csp, Gsp] + + # sparse/sparse + for i in sp1dmatrices: + for j in spmatrices: + try: + dense_mult = i.toarray() * j.toarray() + except ValueError: + with pytest.raises(ValueError, match='inconsistent shapes'): + i.multiply(j) + continue + sp_mult = i.multiply(j) + assert_allclose(sp_mult.toarray(), dense_mult) + + # sparse/dense + for i in sp1dmatrices: + for j in matrices: + try: + dense_mult = i.toarray() * j + except TypeError: + continue + except ValueError: + matchme = 'broadcast together|inconsistent shapes' + with pytest.raises(ValueError, match=matchme): + i.multiply(j) + continue + try: + sp_mult = i.multiply(j) + except ValueError: + continue + assert_allclose(toarray(sp_mult), dense_mult) + + def test_elementwise_divide(self, spcreator, dat1d): + datsp = spcreator(dat1d) + expected = np.array([1, np.nan, 1, np.nan]) + actual = datsp / datsp + # need assert_array_equal to handle nan values + np.testing.assert_array_equal(actual, expected) + + denom = spcreator([1, 0, 0, 4], dtype='d') + expected = [3, np.nan, np.inf, 0] + np.testing.assert_array_equal(datsp / denom, expected) + + # complex + A = np.array([1 - 2j, 0 + 5j, -1 + 0j]) + B = np.array([5 + 2j, 7 - 3j, -2 + 1j]) + Asp = spcreator(A) + Bsp = spcreator(B) + assert_allclose(Asp / Bsp, A / B) + + # integer + A = np.array([1, 2, 3]) + B = np.array([0, 1, 2]) + Asp = spcreator(A) + Bsp = spcreator(B) + with np.errstate(divide='ignore'): + assert_equal(Asp / Bsp, A / B) + + # mismatching sparsity patterns + A = np.array([0, 1]) + B = np.array([1, 0]) + Asp = spcreator(A) + Bsp = spcreator(B) + with np.errstate(divide='ignore', invalid='ignore'): + assert_equal(Asp / Bsp, A / B) + + def test_pow(self, spcreator): + A = np.array([1, 0, 2, 0]) + B = spcreator(A) + + # unusual exponents + with pytest.raises(ValueError, match='negative integer powers'): + B**-1 + with pytest.raises(NotImplementedError, match='zero power'): + B**0 + + for exponent in [1, 2, 3, 2.2]: + ret_sp = B**exponent + ret_np = A**exponent + assert_equal(ret_sp.toarray(), ret_np) + assert_equal(ret_sp.dtype, ret_np.dtype) + + def test_dot_scalar(self, spcreator, dat1d): + A = spcreator(dat1d) + scalar = 10 + actual = A.dot(scalar) + expected = A * scalar + + assert_allclose(actual.toarray(), expected.toarray()) + + def test_matmul(self, spcreator): + Msp = spcreator([2, 0, 3.0]) + B = spcreator(np.array([[0, 1], [1, 0], [0, 2]], 'd')) + col = np.array([[1, 2, 3]]).T + + # check sparse @ dense 2d column + assert_allclose(Msp @ col, Msp.toarray() @ col) + + # check sparse1d @ sparse2d, sparse1d @ dense2d, dense1d @ sparse2d + assert_allclose((Msp @ B).toarray(), (Msp @ B).toarray()) + assert_allclose(Msp.toarray() @ B, (Msp @ B).toarray()) + assert_allclose(Msp @ B.toarray(), (Msp @ B).toarray()) + + # check sparse1d @ dense1d, sparse1d @ sparse1d + V = np.array([0, 0, 1]) + assert_allclose(Msp @ V, Msp.toarray() @ V) + + Vsp = spcreator(V) + Msp_Vsp = Msp @ Vsp + assert isinstance(Msp_Vsp, np.ndarray) + assert Msp_Vsp.shape == () + + # output is 0-dim ndarray + assert_allclose(np.array(3), Msp_Vsp) + assert_allclose(np.array(3), Msp.toarray() @ Vsp) + assert_allclose(np.array(3), Msp @ Vsp.toarray()) + assert_allclose(np.array(3), Msp.toarray() @ Vsp.toarray()) + + # check error on matrix-scalar + with pytest.raises(ValueError, match='Scalar operands are not allowed'): + Msp @ 1 + with pytest.raises(ValueError, match='Scalar operands are not allowed'): + 1 @ Msp + + def test_sub_dense(self, spcreator, datsp_math_dtypes): + # subtracting a dense matrix to/from a sparse matrix + for dtype, dat, datsp in datsp_math_dtypes[spcreator]: + if dtype == np.dtype('bool'): + # boolean array subtraction deprecated in 1.9.0 + continue + + # Manually add to avoid upcasting from scalar + # multiplication. + sum1 = (dat + dat + dat) - datsp + assert_equal(sum1, dat + dat) + sum2 = (datsp + datsp + datsp) - dat + assert_equal(sum2, dat + dat) + + def test_size_zero_matrix_arithmetic(self, spcreator): + # Test basic matrix arithmetic with shapes like 0, (1, 0), (0, 3), etc. + mat = np.array([]) + a = mat.reshape(0) + d = mat.reshape((1, 0)) + f = np.ones([5, 5]) + + asp = spcreator(a) + dsp = spcreator(d) + # bad shape for addition + with pytest.raises(ValueError, match='inconsistent shapes'): + asp.__add__(dsp) + + # matrix product. + assert_equal(asp.dot(asp), np.dot(a, a)) + + # bad matrix products + with pytest.raises(ValueError, match='dimension mismatch|shapes.*not aligned'): + asp.dot(f) + + # elemente-wise multiplication + assert_equal(asp.multiply(asp).toarray(), np.multiply(a, a)) + + assert_equal(asp.multiply(a).toarray(), np.multiply(a, a)) + + assert_equal(asp.multiply(6).toarray(), np.multiply(a, 6)) + + # bad element-wise multiplication + with pytest.raises(ValueError, match='inconsistent shapes'): + asp.multiply(f) + + # Addition + assert_equal(asp.__add__(asp).toarray(), a.__add__(a)) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_array_api.py b/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_array_api.py new file mode 100644 index 0000000000000000000000000000000000000000..ee84fb1952d57c3896107e10ab4f31a50cc33a97 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_array_api.py @@ -0,0 +1,576 @@ +import pytest +import numpy as np +import numpy.testing as npt +import scipy.sparse +import scipy.sparse.linalg as spla + + +sparray_types = ('bsr', 'coo', 'csc', 'csr', 'dia', 'dok', 'lil') + +sparray_classes = [ + getattr(scipy.sparse, f'{T}_array') for T in sparray_types +] + +A = np.array([ + [0, 1, 2, 0], + [2, 0, 0, 3], + [1, 4, 0, 0] +]) + +B = np.array([ + [0, 1], + [2, 0] +]) + +X = np.array([ + [1, 0, 0, 1], + [2, 1, 2, 0], + [0, 2, 1, 0], + [0, 0, 1, 2] +], dtype=float) + + +sparrays = [sparray(A) for sparray in sparray_classes] +square_sparrays = [sparray(B) for sparray in sparray_classes] +eig_sparrays = [sparray(X) for sparray in sparray_classes] + +parametrize_sparrays = pytest.mark.parametrize( + "A", sparrays, ids=sparray_types +) +parametrize_square_sparrays = pytest.mark.parametrize( + "B", square_sparrays, ids=sparray_types +) +parametrize_eig_sparrays = pytest.mark.parametrize( + "X", eig_sparrays, ids=sparray_types +) + + +@parametrize_sparrays +def test_sum(A): + assert not isinstance(A.sum(axis=0), np.matrix), \ + "Expected array, got matrix" + assert A.sum(axis=0).shape == (4,) + assert A.sum(axis=1).shape == (3,) + + +@parametrize_sparrays +def test_mean(A): + assert not isinstance(A.mean(axis=1), np.matrix), \ + "Expected array, got matrix" + + +@parametrize_sparrays +def test_min_max(A): + # Some formats don't support min/max operations, so we skip those here. + if hasattr(A, 'min'): + assert not isinstance(A.min(axis=1), np.matrix), \ + "Expected array, got matrix" + if hasattr(A, 'max'): + assert not isinstance(A.max(axis=1), np.matrix), \ + "Expected array, got matrix" + if hasattr(A, 'argmin'): + assert not isinstance(A.argmin(axis=1), np.matrix), \ + "Expected array, got matrix" + if hasattr(A, 'argmax'): + assert not isinstance(A.argmax(axis=1), np.matrix), \ + "Expected array, got matrix" + + +@parametrize_sparrays +def test_todense(A): + assert not isinstance(A.todense(), np.matrix), \ + "Expected array, got matrix" + + +@parametrize_sparrays +def test_indexing(A): + if A.__class__.__name__[:3] in ('dia', 'coo', 'bsr'): + return + + all_res = ( + A[1, :], + A[:, 1], + A[1, [1, 2]], + A[[1, 2], 1], + A[[0]], + A[:, [1, 2]], + A[[1, 2], :], + A[1, [[1, 2]]], + A[[[1, 2]], 1], + ) + + for res in all_res: + assert isinstance(res, scipy.sparse.sparray), \ + f"Expected sparse array, got {res._class__.__name__}" + + +@parametrize_sparrays +def test_dense_addition(A): + X = np.random.random(A.shape) + assert not isinstance(A + X, np.matrix), "Expected array, got matrix" + + +@parametrize_sparrays +def test_sparse_addition(A): + assert isinstance((A + A), scipy.sparse.sparray), "Expected array, got matrix" + + +@parametrize_sparrays +def test_elementwise_mul(A): + assert np.all((A * A).todense() == A.power(2).todense()) + + +@parametrize_sparrays +def test_elementwise_rmul(A): + with pytest.raises(TypeError): + None * A + + with pytest.raises(ValueError): + np.eye(3) * scipy.sparse.csr_array(np.arange(6).reshape(2, 3)) + + assert np.all((2 * A) == (A.todense() * 2)) + + assert np.all((A.todense() * A) == (A.todense() ** 2)) + + +@parametrize_sparrays +def test_matmul(A): + assert np.all((A @ A.T).todense() == A.dot(A.T).todense()) + + +@parametrize_sparrays +def test_power_operator(A): + assert isinstance((A**2), scipy.sparse.sparray), "Expected array, got matrix" + + # https://github.com/scipy/scipy/issues/15948 + npt.assert_equal((A**2).todense(), (A.todense())**2) + + # power of zero is all ones (dense) so helpful msg exception + with pytest.raises(NotImplementedError, match="zero power"): + A**0 + + +@parametrize_sparrays +def test_sparse_divide(A): + assert isinstance(A / A, np.ndarray) + +@parametrize_sparrays +def test_sparse_dense_divide(A): + with pytest.warns(RuntimeWarning): + assert isinstance((A / A.todense()), scipy.sparse.sparray) + +@parametrize_sparrays +def test_dense_divide(A): + assert isinstance((A / 2), scipy.sparse.sparray), "Expected array, got matrix" + + +@parametrize_sparrays +def test_no_A_attr(A): + with pytest.raises(AttributeError): + A.A + + +@parametrize_sparrays +def test_no_H_attr(A): + with pytest.raises(AttributeError): + A.H + + +@parametrize_sparrays +def test_getrow_getcol(A): + assert isinstance(A._getcol(0), scipy.sparse.sparray) + assert isinstance(A._getrow(0), scipy.sparse.sparray) + + +# -- linalg -- + +@parametrize_sparrays +def test_as_linearoperator(A): + L = spla.aslinearoperator(A) + npt.assert_allclose(L * [1, 2, 3, 4], A @ [1, 2, 3, 4]) + + +@parametrize_square_sparrays +def test_inv(B): + if B.__class__.__name__[:3] != 'csc': + return + + C = spla.inv(B) + + assert isinstance(C, scipy.sparse.sparray) + npt.assert_allclose(C.todense(), np.linalg.inv(B.todense())) + + +@parametrize_square_sparrays +def test_expm(B): + if B.__class__.__name__[:3] != 'csc': + return + + Bmat = scipy.sparse.csc_matrix(B) + + C = spla.expm(B) + + assert isinstance(C, scipy.sparse.sparray) + npt.assert_allclose( + C.todense(), + spla.expm(Bmat).todense() + ) + + +@parametrize_square_sparrays +def test_expm_multiply(B): + if B.__class__.__name__[:3] != 'csc': + return + + npt.assert_allclose( + spla.expm_multiply(B, np.array([1, 2])), + spla.expm(B) @ [1, 2] + ) + + +@parametrize_sparrays +def test_norm(A): + C = spla.norm(A) + npt.assert_allclose(C, np.linalg.norm(A.todense())) + + +@parametrize_square_sparrays +def test_onenormest(B): + C = spla.onenormest(B) + npt.assert_allclose(C, np.linalg.norm(B.todense(), 1)) + + +@parametrize_square_sparrays +def test_spsolve(B): + if B.__class__.__name__[:3] not in ('csc', 'csr'): + return + + npt.assert_allclose( + spla.spsolve(B, [1, 2]), + np.linalg.solve(B.todense(), [1, 2]) + ) + + +@pytest.mark.parametrize("fmt",["csr","csc"]) +def test_spsolve_triangular(fmt): + arr = [ + [1, 0, 0, 0], + [2, 1, 0, 0], + [3, 2, 1, 0], + [4, 3, 2, 1], + ] + if fmt == "csr": + X = scipy.sparse.csr_array(arr) + else: + X = scipy.sparse.csc_array(arr) + spla.spsolve_triangular(X, [1, 2, 3, 4]) + + +@parametrize_square_sparrays +def test_factorized(B): + if B.__class__.__name__[:3] != 'csc': + return + + LU = spla.factorized(B) + npt.assert_allclose( + LU(np.array([1, 2])), + np.linalg.solve(B.todense(), [1, 2]) + ) + + +@parametrize_square_sparrays +@pytest.mark.parametrize( + "solver", + ["bicg", "bicgstab", "cg", "cgs", "gmres", "lgmres", "minres", "qmr", + "gcrotmk", "tfqmr"] +) +def test_solvers(B, solver): + if solver == "minres": + kwargs = {} + else: + kwargs = {'atol': 1e-5} + + x, info = getattr(spla, solver)(B, np.array([1, 2]), **kwargs) + assert info >= 0 # no errors, even if perhaps did not converge fully + npt.assert_allclose(x, [1, 1], atol=1e-1) + + +@parametrize_sparrays +@pytest.mark.parametrize( + "solver", + ["lsqr", "lsmr"] +) +def test_lstsqr(A, solver): + x, *_ = getattr(spla, solver)(A, [1, 2, 3]) + npt.assert_allclose(A @ x, [1, 2, 3]) + + +@parametrize_eig_sparrays +def test_eigs(X): + e, v = spla.eigs(X, k=1) + npt.assert_allclose( + X @ v, + e[0] * v + ) + + +@parametrize_eig_sparrays +def test_eigsh(X): + X = X + X.T + e, v = spla.eigsh(X, k=1) + npt.assert_allclose( + X @ v, + e[0] * v + ) + + +@parametrize_eig_sparrays +def test_svds(X): + u, s, vh = spla.svds(X, k=3) + u2, s2, vh2 = np.linalg.svd(X.todense()) + s = np.sort(s) + s2 = np.sort(s2[:3]) + npt.assert_allclose(s, s2, atol=1e-3) + + +def test_splu(): + X = scipy.sparse.csc_array([ + [1, 0, 0, 0], + [2, 1, 0, 0], + [3, 2, 1, 0], + [4, 3, 2, 1], + ]) + LU = spla.splu(X) + npt.assert_allclose( + LU.solve(np.array([1, 2, 3, 4])), + np.asarray([1, 0, 0, 0], dtype=np.float64), + rtol=1e-14, atol=3e-16 + ) + + +def test_spilu(): + X = scipy.sparse.csc_array([ + [1, 0, 0, 0], + [2, 1, 0, 0], + [3, 2, 1, 0], + [4, 3, 2, 1], + ]) + LU = spla.spilu(X) + npt.assert_allclose( + LU.solve(np.array([1, 2, 3, 4])), + np.asarray([1, 0, 0, 0], dtype=np.float64), + rtol=1e-14, atol=3e-16 + ) + + +@pytest.mark.parametrize( + "cls,indices_attrs", + [ + ( + scipy.sparse.csr_array, + ["indices", "indptr"], + ), + ( + scipy.sparse.csc_array, + ["indices", "indptr"], + ), + ( + scipy.sparse.coo_array, + ["row", "col"], + ), + ] +) +@pytest.mark.parametrize("expected_dtype", [np.int64, np.int32]) +def test_index_dtype_compressed(cls, indices_attrs, expected_dtype): + input_array = scipy.sparse.coo_array(np.arange(9).reshape(3, 3)) + coo_tuple = ( + input_array.data, + ( + input_array.row.astype(expected_dtype), + input_array.col.astype(expected_dtype), + ) + ) + + result = cls(coo_tuple) + for attr in indices_attrs: + assert getattr(result, attr).dtype == expected_dtype + + result = cls(coo_tuple, shape=(3, 3)) + for attr in indices_attrs: + assert getattr(result, attr).dtype == expected_dtype + + if issubclass(cls, scipy.sparse._compressed._cs_matrix): + input_array_csr = input_array.tocsr() + csr_tuple = ( + input_array_csr.data, + input_array_csr.indices.astype(expected_dtype), + input_array_csr.indptr.astype(expected_dtype), + ) + + result = cls(csr_tuple) + for attr in indices_attrs: + assert getattr(result, attr).dtype == expected_dtype + + result = cls(csr_tuple, shape=(3, 3)) + for attr in indices_attrs: + assert getattr(result, attr).dtype == expected_dtype + + +def test_default_is_matrix_diags(): + m = scipy.sparse.diags([0.0, 1.0, 2.0]) + assert not isinstance(m, scipy.sparse.sparray) + + +def test_default_is_matrix_eye(): + m = scipy.sparse.eye(3) + assert not isinstance(m, scipy.sparse.sparray) + + +def test_default_is_matrix_spdiags(): + m = scipy.sparse.spdiags([1.0, 2.0, 3.0], 0, 3, 3) + assert not isinstance(m, scipy.sparse.sparray) + + +def test_default_is_matrix_identity(): + m = scipy.sparse.identity(3) + assert not isinstance(m, scipy.sparse.sparray) + + +@pytest.mark.filterwarnings("ignore:.*switching.*sparse array int:DeprecationWarning") +def test_default_is_matrix_kron_dense(): + m = scipy.sparse.kron( + np.array([[1, 2], [3, 4]]), np.array([[4, 3], [2, 1]]) + ) + assert not isinstance(m, scipy.sparse.sparray) + + +@pytest.mark.filterwarnings("ignore:.*switching.*sparse array int:DeprecationWarning") +def test_default_is_matrix_kron_sparse(): + m = scipy.sparse.kron( + np.array([[1, 2], [3, 4]]), np.array([[1, 0], [0, 0]]) + ) + assert not isinstance(m, scipy.sparse.sparray) + + +@pytest.mark.filterwarnings("ignore:.*switching.*sparse array int:DeprecationWarning") +def test_default_is_matrix_kronsum(): + m = scipy.sparse.kronsum( + np.array([[1, 0], [0, 1]]), np.array([[0, 1], [1, 0]]) + ) + assert not isinstance(m, scipy.sparse.sparray) + + +def test_default_is_matrix_random(): + m = scipy.sparse.random(3, 3) + assert not isinstance(m, scipy.sparse.sparray) + + +def test_default_is_matrix_rand(): + m = scipy.sparse.rand(3, 3) + assert not isinstance(m, scipy.sparse.sparray) + + +@pytest.mark.parametrize("fn", (scipy.sparse.hstack, scipy.sparse.vstack)) +def test_default_is_matrix_stacks(fn): + """Same idea as `test_default_construction_fn_matrices`, but for the + stacking creation functions.""" + A = scipy.sparse.coo_matrix(np.eye(2)) + B = scipy.sparse.coo_matrix([[0, 1], [1, 0]]) + m = fn([A, B]) + assert not isinstance(m, scipy.sparse.sparray) + + +@pytest.mark.filterwarnings("ignore:.*switching.*sparse array int:DeprecationWarning") +def test_blocks_default_construction_fn_matrices(): + """Same idea as `test_default_construction_fn_matrices`, but block functions""" + A = scipy.sparse.coo_matrix(np.eye(2)) + B = scipy.sparse.coo_matrix([[2], [0]]) + C = scipy.sparse.coo_matrix([[3]]) + + # block diag + m = scipy.sparse.block_diag((A, B, C)) + assert not isinstance(m, scipy.sparse.sparray) + + # bmat + m = scipy.sparse.bmat([[A, None], [None, C]]) + assert not isinstance(m, scipy.sparse.sparray) + + # ndarray input + A = np.eye(2) + B = [[2], [0]] + C = [[3]] + + # block diag + m = scipy.sparse.block_diag((A, B, C)) + assert not isinstance(m, scipy.sparse.sparray) + + # bmat + m = scipy.sparse.bmat([[A, None], [None, C]]) + assert not isinstance(m, scipy.sparse.sparray) + + +def test_format_property(): + for fmt in sparray_types: + arr_cls = getattr(scipy.sparse, f"{fmt}_array") + M = arr_cls([[1, 2]]) + assert M.format == fmt + assert M._format == fmt + with pytest.raises(AttributeError): + M.format = "qqq" + + +def test_issparse(): + m = scipy.sparse.eye(3) + a = scipy.sparse.csr_array(m) + assert not isinstance(m, scipy.sparse.sparray) + assert isinstance(a, scipy.sparse.sparray) + + # Both sparse arrays and sparse matrices should be sparse + assert scipy.sparse.issparse(a) + assert scipy.sparse.issparse(m) + + # ndarray and array_likes are not sparse + assert not scipy.sparse.issparse(a.todense()) + assert not scipy.sparse.issparse(m.todense()) + + +def test_isspmatrix(): + m = scipy.sparse.eye(3) + a = scipy.sparse.csr_array(m) + assert not isinstance(m, scipy.sparse.sparray) + assert isinstance(a, scipy.sparse.sparray) + + # Should only be true for sparse matrices, not sparse arrays + assert not scipy.sparse.isspmatrix(a) + assert scipy.sparse.isspmatrix(m) + + # ndarray and array_likes are not sparse + assert not scipy.sparse.isspmatrix(a.todense()) + assert not scipy.sparse.isspmatrix(m.todense()) + + +@pytest.mark.parametrize( + ("fmt", "fn"), + ( + ("bsr", scipy.sparse.isspmatrix_bsr), + ("coo", scipy.sparse.isspmatrix_coo), + ("csc", scipy.sparse.isspmatrix_csc), + ("csr", scipy.sparse.isspmatrix_csr), + ("dia", scipy.sparse.isspmatrix_dia), + ("dok", scipy.sparse.isspmatrix_dok), + ("lil", scipy.sparse.isspmatrix_lil), + ), +) +def test_isspmatrix_format(fmt, fn): + m = scipy.sparse.eye(3, format=fmt) + a = scipy.sparse.csr_array(m).asformat(fmt) + assert not isinstance(m, scipy.sparse.sparray) + assert isinstance(a, scipy.sparse.sparray) + + # Should only be true for sparse matrices, not sparse arrays + assert not fn(a) + assert fn(m) + + # ndarray and array_likes are not sparse + assert not fn(a.todense()) + assert not fn(m.todense()) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_base.py b/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_base.py new file mode 100644 index 0000000000000000000000000000000000000000..ebcf4e9af80a12b49efefe23942e45d0f3038f62 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_base.py @@ -0,0 +1,5971 @@ +# +# Authors: Travis Oliphant, Ed Schofield, Robert Cimrman, Nathan Bell, and others + +""" Test functions for sparse matrices. Each class in the "Matrix class +based tests" section become subclasses of the classes in the "Generic +tests" section. This is done by the functions in the "Tailored base +class for generic tests" section. + +""" + + +import contextlib +import functools +import operator +import platform +import itertools +import sys +import warnings + +import pytest +from pytest import raises as assert_raises + +import numpy as np +from numpy import (arange, zeros, array, dot, asarray, + vstack, ndarray, transpose, diag, kron, inf, conjugate, + int8) + +import random +from numpy.testing import (assert_equal, assert_array_equal, + assert_array_almost_equal, assert_almost_equal, assert_, + assert_allclose) +from numpy.exceptions import ComplexWarning + +from types import GenericAlias + +import scipy.linalg + +import scipy.sparse as sparse +from scipy.sparse import (csc_matrix, csr_matrix, dok_matrix, + coo_matrix, lil_matrix, dia_matrix, bsr_matrix, + csc_array, csr_array, dok_array, + coo_array, lil_array, dia_array, bsr_array, + eye, issparse, SparseEfficiencyWarning, sparray, spmatrix, + matrix_transpose,) +from scipy.sparse._base import _formats +from scipy.sparse._sputils import (supported_dtypes, isscalarlike, + get_index_dtype, asmatrix, matrix) +from scipy.sparse.linalg import splu, expm, inv + +IS_COLAB = ('google.colab' in sys.modules) + + +def assert_in(member, collection, msg=None): + message = msg if msg is not None else f"{member!r} not found in {collection!r}" + assert_(member in collection, msg=message) + + +def assert_array_equal_dtype(x, y, **kwargs): + assert_(x.dtype == y.dtype) + assert_array_equal(x, y, **kwargs) + + +NON_ARRAY_BACKED_FORMATS = frozenset(['dok']) + +WMSG = "Changing the sparsity structure" + +def sparse_may_share_memory(A, B): + # Checks if A and B have any numpy array sharing memory. + + def _underlying_arrays(x): + # Given any object (e.g. a sparse array), returns all numpy arrays + # stored in any attribute. + + arrays = [] + for a in x.__dict__.values(): + if isinstance(a, np.ndarray | np.generic): + arrays.append(a) + return arrays + + for a in _underlying_arrays(A): + for b in _underlying_arrays(B): + if np.may_share_memory(a, b): + return True + return False + + +def with_64bit_maxval_limit(maxval_limit=None, random=False, fixed_dtype=None, + downcast_maxval=None, assert_32bit=False): + """ + Monkeypatch the maxval threshold at which scipy.sparse switches to + 64-bit index arrays, or make it (pseudo-)random. + + """ + if maxval_limit is None: + maxval_limit = np.int64(10) + else: + # Ensure we use numpy scalars rather than Python scalars (matters for + # NEP 50 casting rule changes) + maxval_limit = np.int64(maxval_limit) + + if assert_32bit: + def new_get_index_dtype(arrays=(), maxval=None, check_contents=False): + tp = get_index_dtype(arrays, maxval, check_contents) + assert_equal(np.iinfo(tp).max, np.iinfo(np.int32).max) + assert_(tp == np.int32 or tp == np.intc) + return tp + elif fixed_dtype is not None: + def new_get_index_dtype(arrays=(), maxval=None, check_contents=False): + return fixed_dtype + elif random: + rng = np.random.default_rng(1234) + def new_get_index_dtype(arrays=(), maxval=None, check_contents=False): + return (np.int32, np.int64)[rng.integers(2)] + else: + def new_get_index_dtype(arrays=(), maxval=None, check_contents=False): + dtype = np.int32 + if maxval is not None: + if maxval > maxval_limit: + dtype = np.int64 + for arr in arrays: + arr = np.asarray(arr) + if arr.dtype > np.int32: + if check_contents: + if arr.size == 0: + # a bigger type not needed + continue + elif np.issubdtype(arr.dtype, np.integer): + maxval = arr.max() + minval = arr.min() + if minval >= -maxval_limit and maxval <= maxval_limit: + # a bigger type not needed + continue + dtype = np.int64 + return dtype + + if downcast_maxval is not None: + def new_downcast_intp_index(arr): + if arr.max() > downcast_maxval: + raise AssertionError("downcast limited") + return arr.astype(np.intp) + + def decorator(func): + backup = [] + modules = [scipy.sparse._bsr, scipy.sparse._coo, scipy.sparse._csc, + scipy.sparse._csr, scipy.sparse._dia, scipy.sparse._dok, + scipy.sparse._lil, scipy.sparse._sputils, + scipy.sparse._compressed, scipy.sparse._construct] + + @functools.wraps(func) + def wrapper(*a, **kw): + try: + for mod in modules: + backup.append(( + mod, + 'get_index_dtype', + getattr(mod, 'get_index_dtype', None) + )) + setattr(mod, 'get_index_dtype', new_get_index_dtype) + + if downcast_maxval is not None: + backup.append(( + mod, + 'downcast_intp_index', + getattr(mod, 'downcast_intp_index', None) + )) + setattr(mod, 'downcast_intp_index', new_downcast_intp_index) + + return func(*a, **kw) + finally: + for mod, name, oldfunc in backup: + if oldfunc is not None: + setattr(mod, name, oldfunc) + return wrapper + return decorator + + +def toarray(a): + if isinstance(a, np.ndarray) or isscalarlike(a): + return a + return a.toarray() + + +class BinopTester: + # Custom type to test binary operations on sparse matrices. + + def __add__(self, mat): + return "matrix on the right" + + def __mul__(self, mat): + return "matrix on the right" + + def __sub__(self, mat): + return "matrix on the right" + + def __radd__(self, mat): + return "matrix on the left" + + def __rmul__(self, mat): + return "matrix on the left" + + def __rsub__(self, mat): + return "matrix on the left" + + def __matmul__(self, mat): + return "matrix on the right" + + def __rmatmul__(self, mat): + return "matrix on the left" + +class BinopTester_with_shape: + # Custom type to test binary operations on sparse matrices + # with object which has shape attribute. + def __init__(self,shape): + self._shape = shape + + def shape(self): + return self._shape + + def ndim(self): + return len(self._shape) + + def __add__(self, mat): + return "matrix on the right" + + def __mul__(self, mat): + return "matrix on the right" + + def __sub__(self, mat): + return "matrix on the right" + + def __radd__(self, mat): + return "matrix on the left" + + def __rmul__(self, mat): + return "matrix on the left" + + def __rsub__(self, mat): + return "matrix on the left" + + def __matmul__(self, mat): + return "matrix on the right" + + def __rmatmul__(self, mat): + return "matrix on the left" + +class ComparisonTester: + # Custom type to test comparison operations on sparse matrices. + def __eq__(self, other): + return "eq" + + def __ne__(self, other): + return "ne" + + def __lt__(self, other): + return "lt" + + def __le__(self, other): + return "le" + + def __gt__(self, other): + return "gt" + + def __ge__(self, other): + return "ge" + + +#------------------------------------------------------------------------------ +# Generic tests +#------------------------------------------------------------------------------ + + +class _MatrixMixin: + """mixin to easily allow tests of both sparray and spmatrix""" + bsr_container = bsr_matrix + coo_container = coo_matrix + csc_container = csc_matrix + csr_container = csr_matrix + dia_container = dia_matrix + dok_container = dok_matrix + lil_container = lil_matrix + asdense = staticmethod(asmatrix) + + def test_getrow(self): + assert_array_equal(self.datsp.getrow(1).toarray(), self.dat[[1], :]) + assert_array_equal(self.datsp.getrow(-1).toarray(), self.dat[[-1], :]) + + def test_getcol(self): + assert_array_equal(self.datsp.getcol(1).toarray(), self.dat[:, [1]]) + assert_array_equal(self.datsp.getcol(-1).toarray(), self.dat[:, [-1]]) + + def test_asfptype(self): + A = self.spcreator(arange(6,dtype='int32').reshape(2,3)) + + assert_equal(A.asfptype().dtype, np.dtype('float64')) + assert_equal(A.asfptype().format, A.format) + assert_equal(A.astype('int16').asfptype().dtype, np.dtype('float32')) + assert_equal(A.astype('complex128').asfptype().dtype, np.dtype('complex128')) + + B = A.asfptype() + C = B.asfptype() + assert_(B is C) + + +# TODO test prune +# TODO test has_sorted_indices +class _TestCommon: + """test common functionality shared by all sparse formats""" + math_dtypes = supported_dtypes + + bsr_container = bsr_array + coo_container = coo_array + csc_container = csc_array + csr_container = csr_array + dia_container = dia_array + dok_container = dok_array + lil_container = lil_array + asdense = array + + @classmethod + def init_class(cls): + # Canonical data. + cls.dat = array([[1, 0, 0, 2], [3, 0, 1, 0], [0, 2, 0, 0]], 'd') + cls.datsp = cls.spcreator(cls.dat) + + # Some sparse and dense matrices with data for every supported dtype. + # This set union is a workaround for numpy#6295, which means that + # two np.int64 dtypes don't hash to the same value. + cls.checked_dtypes = set(supported_dtypes).union(cls.math_dtypes) + cls.dat_dtypes = {} + cls.datsp_dtypes = {} + for dtype in cls.checked_dtypes: + cls.dat_dtypes[dtype] = cls.dat.astype(dtype) + cls.datsp_dtypes[dtype] = cls.spcreator(cls.dat.astype(dtype)) + + # Check that the original data is equivalent to the + # corresponding dat_dtypes & datsp_dtypes. + assert_equal(cls.dat, cls.dat_dtypes[np.float64]) + assert_equal(cls.datsp.toarray(), + cls.datsp_dtypes[np.float64].toarray()) + + cls.is_array_test = isinstance(cls.datsp, sparray) + + def test_bool(self): + def check(dtype): + datsp = self.datsp_dtypes[dtype] + + assert_raises(ValueError, bool, datsp) + assert_(self.spcreator([[1]])) + assert_(not self.spcreator([[0]])) + + if isinstance(self, TestDOK): + pytest.skip("Cannot create a rank <= 2 DOK matrix.") + for dtype in self.checked_dtypes: + check(dtype) + + def test_bool_rollover(self): + # bool's underlying dtype is 1 byte, check that it does not + # rollover True -> False at 256. + dat = array([[True, False]]) + datsp = self.spcreator(dat) + + for _ in range(10): + datsp = datsp + datsp + dat = dat + dat + assert_array_equal(dat, datsp.toarray()) + + def test_eq(self): + + def check(dtype): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", SparseEfficiencyWarning) + warnings.simplefilter("ignore", ComplexWarning) + dat = self.dat_dtypes[dtype] + datsp = self.datsp_dtypes[dtype] + dat2 = dat.copy() + dat2[:,0] = 0 + datsp2 = self.spcreator(dat2) + datbsr = self.bsr_container(dat) + datcsr = self.csr_container(dat) + datcsc = self.csc_container(dat) + datlil = self.lil_container(dat) + + # sparse/sparse + assert_array_equal_dtype(dat == dat2, (datsp == datsp2).toarray()) + # mix sparse types + assert_array_equal_dtype(dat == dat2, (datbsr == datsp2).toarray()) + assert_array_equal_dtype(dat == dat2, (datcsr == datsp2).toarray()) + assert_array_equal_dtype(dat == dat2, (datcsc == datsp2).toarray()) + assert_array_equal_dtype(dat == dat2, (datlil == datsp2).toarray()) + # sparse/dense + assert_array_equal_dtype(dat == datsp2, datsp2 == dat) + # sparse/scalar + assert_array_equal_dtype(dat == 0, (datsp == 0).toarray()) + assert_array_equal_dtype(dat == 1, (datsp == 1).toarray()) + assert_array_equal_dtype(dat == np.nan, + (datsp == np.nan).toarray()) + + if self.datsp.format not in ['bsr', 'csc', 'csr']: + pytest.skip("Bool comparisons only implemented for BSR, CSC, and CSR.") + for dtype in self.checked_dtypes: + check(dtype) + + def test_ne(self): + def check(dtype): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", SparseEfficiencyWarning) + warnings.simplefilter("ignore", ComplexWarning) + dat = self.dat_dtypes[dtype] + datsp = self.datsp_dtypes[dtype] + dat2 = dat.copy() + dat2[:,0] = 0 + datsp2 = self.spcreator(dat2) + datbsr = self.bsr_container(dat) + datcsc = self.csc_container(dat) + datcsr = self.csr_container(dat) + datlil = self.lil_container(dat) + + # sparse/sparse + assert_array_equal_dtype(dat != dat2, (datsp != datsp2).toarray()) + # mix sparse types + assert_array_equal_dtype(dat != dat2, (datbsr != datsp2).toarray()) + assert_array_equal_dtype(dat != dat2, (datcsc != datsp2).toarray()) + assert_array_equal_dtype(dat != dat2, (datcsr != datsp2).toarray()) + assert_array_equal_dtype(dat != dat2, (datlil != datsp2).toarray()) + # sparse/dense + assert_array_equal_dtype(dat != datsp2, datsp2 != dat) + # sparse/scalar + assert_array_equal_dtype(dat != 0, (datsp != 0).toarray()) + assert_array_equal_dtype(dat != 1, (datsp != 1).toarray()) + assert_array_equal_dtype(0 != dat, (0 != datsp).toarray()) + assert_array_equal_dtype(1 != dat, (1 != datsp).toarray()) + assert_array_equal_dtype(dat != np.nan, + (datsp != np.nan).toarray()) + + if self.datsp.format not in ['bsr', 'csc', 'csr']: + pytest.skip("Bool comparisons only implemented for BSR, CSC, and CSR.") + for dtype in self.checked_dtypes: + check(dtype) + + def test_eq_ne_different_shapes(self): + if self.datsp.format not in ['bsr', 'csc', 'csr']: + pytest.skip("Bool comparisons only implemented for BSR, CSC, and CSR.") + # Is this what we want? numpy raises when shape differs. we return False. + assert (self.datsp == self.datsp.T) is False + assert (self.datsp != self.datsp.T) is True + + def test_lt(self): + def check(dtype): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", SparseEfficiencyWarning) + warnings.simplefilter("ignore", ComplexWarning) + # data + dat = self.dat_dtypes[dtype] + datsp = self.datsp_dtypes[dtype] + dat2 = dat.copy() + dat2[:,0] = 0 + datsp2 = self.spcreator(dat2) + datcomplex = dat.astype(complex) + datcomplex[:,0] = 1 + 1j + datspcomplex = self.spcreator(datcomplex) + datbsr = self.bsr_container(dat) + datcsc = self.csc_container(dat) + datcsr = self.csr_container(dat) + datlil = self.lil_container(dat) + + # sparse/sparse + assert_array_equal_dtype(dat < dat2, (datsp < datsp2).toarray()) + assert_array_equal_dtype(datcomplex < dat2, + (datspcomplex < datsp2).toarray()) + # mix sparse types + assert_array_equal_dtype(dat < dat2, (datbsr < datsp2).toarray()) + assert_array_equal_dtype(dat < dat2, (datcsc < datsp2).toarray()) + assert_array_equal_dtype(dat < dat2, (datcsr < datsp2).toarray()) + assert_array_equal_dtype(dat < dat2, (datlil < datsp2).toarray()) + + assert_array_equal_dtype(dat2 < dat, (datsp2 < datbsr).toarray()) + assert_array_equal_dtype(dat2 < dat, (datsp2 < datcsc).toarray()) + assert_array_equal_dtype(dat2 < dat, (datsp2 < datcsr).toarray()) + assert_array_equal_dtype(dat2 < dat, (datsp2 < datlil).toarray()) + # sparse/dense + assert_array_equal_dtype(dat < dat2, datsp < dat2) + assert_array_equal_dtype(datcomplex < dat2, datspcomplex < dat2) + # sparse/scalar + for val in [2, 1, 0, -1, -2]: + val = np.int64(val) # avoid Python scalar (due to NEP 50 changes) + assert_array_equal_dtype((datsp < val).toarray(), dat < val) + assert_array_equal_dtype((val < datsp).toarray(), val < dat) + + with np.errstate(invalid='ignore'): + assert_array_equal_dtype((datsp < np.nan).toarray(), + dat < np.nan) + + # data + dat = self.dat_dtypes[dtype] + datsp = self.datsp_dtypes[dtype] + dat2 = dat.copy() + dat2[:,0] = 0 + datsp2 = self.spcreator(dat2) + + # dense rhs + assert_array_equal_dtype(dat < datsp2, datsp < dat2) + + if self.datsp.format not in ['bsr', 'csc', 'csr']: + pytest.skip("Bool comparisons only implemented for BSR, CSC, and CSR.") + for dtype in self.checked_dtypes: + check(dtype) + + def test_gt(self): + def check(dtype): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", SparseEfficiencyWarning) + warnings.simplefilter("ignore", ComplexWarning) + dat = self.dat_dtypes[dtype] + datsp = self.datsp_dtypes[dtype] + dat2 = dat.copy() + dat2[:,0] = 0 + datsp2 = self.spcreator(dat2) + datcomplex = dat.astype(complex) + datcomplex[:,0] = 1 + 1j + datspcomplex = self.spcreator(datcomplex) + datbsr = self.bsr_container(dat) + datcsc = self.csc_container(dat) + datcsr = self.csr_container(dat) + datlil = self.lil_container(dat) + + # sparse/sparse + assert_array_equal_dtype(dat > dat2, (datsp > datsp2).toarray()) + assert_array_equal_dtype(datcomplex > dat2, + (datspcomplex > datsp2).toarray()) + # mix sparse types + assert_array_equal_dtype(dat > dat2, (datbsr > datsp2).toarray()) + assert_array_equal_dtype(dat > dat2, (datcsc > datsp2).toarray()) + assert_array_equal_dtype(dat > dat2, (datcsr > datsp2).toarray()) + assert_array_equal_dtype(dat > dat2, (datlil > datsp2).toarray()) + + assert_array_equal_dtype(dat2 > dat, (datsp2 > datbsr).toarray()) + assert_array_equal_dtype(dat2 > dat, (datsp2 > datcsc).toarray()) + assert_array_equal_dtype(dat2 > dat, (datsp2 > datcsr).toarray()) + assert_array_equal_dtype(dat2 > dat, (datsp2 > datlil).toarray()) + # sparse/dense + assert_array_equal_dtype(dat > dat2, datsp > dat2) + assert_array_equal_dtype(datcomplex > dat2, datspcomplex > dat2) + # sparse/scalar + for val in [2, 1, 0, -1, -2]: + val = np.int64(val) # avoid Python scalar (due to NEP 50 changes) + assert_array_equal_dtype((datsp > val).toarray(), dat > val) + assert_array_equal_dtype((val > datsp).toarray(), val > dat) + + with np.errstate(invalid='ignore'): + assert_array_equal_dtype((datsp > np.nan).toarray(), + dat > np.nan) + + # data + dat = self.dat_dtypes[dtype] + datsp = self.datsp_dtypes[dtype] + dat2 = dat.copy() + dat2[:,0] = 0 + datsp2 = self.spcreator(dat2) + + # dense rhs + assert_array_equal_dtype(dat > datsp2, datsp > dat2) + + if self.datsp.format not in ['bsr', 'csc', 'csr']: + pytest.skip("Bool comparisons only implemented for BSR, CSC, and CSR.") + for dtype in self.checked_dtypes: + check(dtype) + + def test_le(self): + def check(dtype): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", SparseEfficiencyWarning) + warnings.simplefilter("ignore", ComplexWarning) + dat = self.dat_dtypes[dtype] + datsp = self.datsp_dtypes[dtype] + dat2 = dat.copy() + dat2[:,0] = 0 + datsp2 = self.spcreator(dat2) + datcomplex = dat.astype(complex) + datcomplex[:,0] = 1 + 1j + datspcomplex = self.spcreator(datcomplex) + datbsr = self.bsr_container(dat) + datcsc = self.csc_container(dat) + datcsr = self.csr_container(dat) + datlil = self.lil_container(dat) + + # sparse/sparse + assert_array_equal_dtype(dat <= dat2, (datsp <= datsp2).toarray()) + assert_array_equal_dtype(datcomplex <= dat2, + (datspcomplex <= datsp2).toarray()) + # mix sparse types + assert_array_equal_dtype((datbsr <= datsp2).toarray(), dat <= dat2) + assert_array_equal_dtype((datcsc <= datsp2).toarray(), dat <= dat2) + assert_array_equal_dtype((datcsr <= datsp2).toarray(), dat <= dat2) + assert_array_equal_dtype((datlil <= datsp2).toarray(), dat <= dat2) + + assert_array_equal_dtype((datsp2 <= datbsr).toarray(), dat2 <= dat) + assert_array_equal_dtype((datsp2 <= datcsc).toarray(), dat2 <= dat) + assert_array_equal_dtype((datsp2 <= datcsr).toarray(), dat2 <= dat) + assert_array_equal_dtype((datsp2 <= datlil).toarray(), dat2 <= dat) + # sparse/dense + assert_array_equal_dtype(datsp <= dat2, dat <= dat2) + assert_array_equal_dtype(datspcomplex <= dat2, datcomplex <= dat2) + # sparse/scalar + for val in [2, 1, -1, -2]: + val = np.int64(val) # avoid Python scalar (due to NEP 50 changes) + assert_array_equal_dtype((datsp <= val).toarray(), dat <= val) + assert_array_equal_dtype((val <= datsp).toarray(), val <= dat) + + # data + dat = self.dat_dtypes[dtype] + datsp = self.datsp_dtypes[dtype] + dat2 = dat.copy() + dat2[:,0] = 0 + datsp2 = self.spcreator(dat2) + + # dense rhs + assert_array_equal_dtype(dat <= datsp2, datsp <= dat2) + + if self.datsp.format not in ['bsr', 'csc', 'csr']: + pytest.skip("Bool comparisons only implemented for BSR, CSC, and CSR.") + for dtype in self.checked_dtypes: + check(dtype) + + def test_ge(self): + def check(dtype): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", SparseEfficiencyWarning) + warnings.simplefilter("ignore", ComplexWarning) + dat = self.dat_dtypes[dtype] + datsp = self.datsp_dtypes[dtype] + dat2 = dat.copy() + dat2[:,0] = 0 + datsp2 = self.spcreator(dat2) + datcomplex = dat.astype(complex) + datcomplex[:,0] = 1 + 1j + datspcomplex = self.spcreator(datcomplex) + datbsr = self.bsr_container(dat) + datcsc = self.csc_container(dat) + datcsr = self.csr_container(dat) + datlil = self.lil_container(dat) + + # sparse/sparse + assert_array_equal_dtype(dat >= dat2, (datsp >= datsp2).toarray()) + assert_array_equal_dtype(datcomplex >= dat2, + (datspcomplex >= datsp2).toarray()) + # mix sparse types + assert_array_equal_dtype((datbsr >= datsp2).toarray(), dat >= dat2) + assert_array_equal_dtype((datcsc >= datsp2).toarray(), dat >= dat2) + assert_array_equal_dtype((datcsr >= datsp2).toarray(), dat >= dat2) + assert_array_equal_dtype((datlil >= datsp2).toarray(), dat >= dat2) + + assert_array_equal_dtype((datsp2 >= datbsr).toarray(), dat2 >= dat) + assert_array_equal_dtype((datsp2 >= datcsc).toarray(), dat2 >= dat) + assert_array_equal_dtype((datsp2 >= datcsr).toarray(), dat2 >= dat) + assert_array_equal_dtype((datsp2 >= datlil).toarray(), dat2 >= dat) + # sparse/dense + assert_array_equal_dtype(datsp >= dat2, dat >= dat2) + assert_array_equal_dtype(datspcomplex >= dat2, datcomplex >= dat2) + # sparse/scalar + for val in [2, 1, -1, -2]: + val = np.int64(val) # avoid Python scalar (due to NEP 50 changes) + assert_array_equal_dtype((datsp >= val).toarray(), dat >= val) + assert_array_equal_dtype((val >= datsp).toarray(), val >= dat) + + # dense data + dat = self.dat_dtypes[dtype] + datsp = self.datsp_dtypes[dtype] + dat2 = dat.copy() + dat2[:,0] = 0 + datsp2 = self.spcreator(dat2) + + # dense rhs + assert_array_equal_dtype(dat >= datsp2, datsp >= dat2) + + if self.datsp.format not in ['bsr', 'csc', 'csr']: + pytest.skip("Bool comparisons only implemented for BSR, CSC, and CSR.") + for dtype in self.checked_dtypes: + check(dtype) + + def test_empty(self): + # create empty matrices + assert_equal(self.spcreator((3, 3)).toarray(), zeros((3, 3))) + assert_equal(self.spcreator((3, 3)).nnz, 0) + assert_equal(self.spcreator((3, 3)).count_nonzero(), 0) + if self.datsp.format != "dia": + assert_equal(self.spcreator((3, 3)).count_nonzero(axis=0), array([0, 0, 0])) + + def test_count_nonzero(self): + axis_support = self.datsp.format != "dia" + axes = [None, 0, 1, -1, -2] if axis_support else [None] + + for A in (self.datsp, self.datsp.T): + for ax in axes: + expected = np.count_nonzero(A.toarray(), axis=ax) + assert_equal(A.count_nonzero(axis=ax), expected) + + if not axis_support: + with assert_raises(NotImplementedError, match="not implemented .* format"): + self.datsp.count_nonzero(axis=0) + + def test_invalid_shapes(self): + assert_raises(ValueError, self.spcreator, (-1,3)) + assert_raises(ValueError, self.spcreator, (3,-1)) + assert_raises(ValueError, self.spcreator, (-1,-1)) + + def test_repr(self): + datsp = self.spcreator([[1, 0, 0], [0, 0, 0], [0, 0, -2]]) + extra = ( + "(1 diagonals) " if datsp.format == "dia" + else "(blocksize=1x1) " if datsp.format == "bsr" + else "" + ) + _, fmt = _formats[datsp.format] + sparse_cls = "array" if self.is_array_test else "matrix" + expected = ( + f"<{fmt} sparse {sparse_cls} of dtype '{datsp.dtype}'\n" + f"\twith {datsp.nnz} stored elements {extra}and shape {datsp.shape}>" + ) + assert repr(datsp) == expected + + def test_str_maxprint(self): + datsp = self.spcreator(np.arange(75).reshape(5, 15)) + assert datsp.maxprint == 50 + assert len(str(datsp).split('\n')) == 51 + 3 + + dat = np.arange(15).reshape(5,3) + datsp = self.spcreator(dat) + # format dia reports nnz=15, but we want 14 + nnz_small = 14 if datsp.format == 'dia' else datsp.nnz + datsp_mp6 = self.spcreator(dat, maxprint=6) + + assert len(str(datsp).split('\n')) == nnz_small + 3 + assert len(str(datsp_mp6).split('\n')) == 6 + 4 + + # Check parameter `maxprint` is keyword only + datsp = self.spcreator(dat, shape=(5, 3), dtype='i', copy=False, maxprint=4) + datsp = self.spcreator(dat, (5, 3), 'i', False, maxprint=4) + with pytest.raises(TypeError, match="positional argument|unpack non-iterable"): + self.spcreator(dat, (5, 3), 'i', False, 4) + + def test_str(self): + datsp = self.spcreator([[1, 0, 0], [0, 0, 0], [0, 0, -2]]) + if datsp.nnz != 2: + return + extra = ( + "(1 diagonals) " if datsp.format == "dia" + else "(blocksize=1x1) " if datsp.format == "bsr" + else "" + ) + _, fmt = _formats[datsp.format] + sparse_cls = "array" if self.is_array_test else "matrix" + expected = ( + f"<{fmt} sparse {sparse_cls} of dtype '{datsp.dtype}'\n" + f"\twith {datsp.nnz} stored elements {extra}and shape {datsp.shape}>" + "\n Coords\tValues" + "\n (0, 0)\t1" + "\n (2, 2)\t-2" + ) + assert str(datsp) == expected + + def test_empty_arithmetic(self): + # Test manipulating empty matrices. Fails in SciPy SVN <= r1768 + shape = (5, 5) + for mytype in [np.dtype('int32'), np.dtype('float32'), + np.dtype('float64'), np.dtype('complex64'), + np.dtype('complex128')]: + a = self.spcreator(shape, dtype=mytype) + b = a + a + c = 2 * a + d = a @ a.tocsc() + e = a @ a.tocsr() + f = a @ a.tocoo() + for m in [a,b,c,d,e,f]: + assert_equal(m.toarray(), a.toarray()@a.toarray()) + # These fail in all revisions <= r1768: + assert_equal(m.dtype,mytype) + assert_equal(m.toarray().dtype,mytype) + + def test_abs(self): + A = array([[-1, 0, 17], [0, -5, 0], [1, -4, 0], [0, 0, 0]], 'd') + assert_equal(abs(A), abs(self.spcreator(A)).toarray()) + + def test_round(self): + decimal = 1 + A = array([[-1.35, 0.56], [17.25, -5.98]], 'd') + assert_equal(np.around(A, decimals=decimal), + round(self.spcreator(A), ndigits=decimal).toarray()) + + def test_elementwise_power(self): + A = array([[-4, -3, -2], [-1, 0, 1], [2, 3, 4]], 'd') + assert_equal(np.power(A, 2), self.spcreator(A).power(2).toarray()) + + #it's element-wise power function, input has to be a scalar + assert_raises(NotImplementedError, self.spcreator(A).power, A) + + def test_neg(self): + A = array([[-1, 0, 17], [0, -5, 0], [1, -4, 0], [0, 0, 0]], 'd') + assert_equal(-A, (-self.spcreator(A)).toarray()) + + # see gh-5843 + A = array([[True, False, False], [False, False, True]]) + assert_raises(NotImplementedError, self.spcreator(A).__neg__) + + def test_real(self): + D = array([[1 + 3j, 2 - 4j]]) + A = self.spcreator(D) + assert_equal(A.real.toarray(), D.real) + + def test_imag(self): + D = array([[1 + 3j, 2 - 4j]]) + A = self.spcreator(D) + assert_equal(A.imag.toarray(), D.imag) + + def test_diagonal(self): + # Does the matrix's .diagonal() method work? + mats = [] + mats.append([[1,0,2]]) + mats.append([[1],[0],[2]]) + mats.append([[0,1],[0,2],[0,3]]) + mats.append([[0,0,1],[0,0,2],[0,3,0]]) + mats.append([[1,0],[0,0]]) + + mats.append(kron(mats[0],[[1,2]])) + mats.append(kron(mats[0],[[1],[2]])) + mats.append(kron(mats[1],[[1,2],[3,4]])) + mats.append(kron(mats[2],[[1,2],[3,4]])) + mats.append(kron(mats[3],[[1,2],[3,4]])) + mats.append(kron(mats[3],[[1,2,3,4]])) + + for m in mats: + rows, cols = array(m).shape + sparse_mat = self.spcreator(m) + for k in range(-rows-1, cols+2): + assert_equal(sparse_mat.diagonal(k=k), diag(m, k=k)) + # Test for k beyond boundaries(issue #11949) + assert_equal(sparse_mat.diagonal(k=10), diag(m, k=10)) + assert_equal(sparse_mat.diagonal(k=-99), diag(m, k=-99)) + + # Test all-zero matrix. + assert_equal(self.spcreator((40, 16130)).diagonal(), np.zeros(40)) + # Test empty matrix + # https://github.com/scipy/scipy/issues/11949 + assert_equal(self.spcreator((0, 0)).diagonal(), np.empty(0)) + assert_equal(self.spcreator((15, 0)).diagonal(), np.empty(0)) + assert_equal(self.spcreator((0, 5)).diagonal(10), np.empty(0)) + + def test_trace(self): + # For square matrix + A = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + B = self.spcreator(A) + for k in range(-2, 3): + assert_equal(A.trace(offset=k), B.trace(offset=k)) + + # For rectangular matrix + A = np.array([[1, 2, 3], [4, 5, 6]]) + B = self.spcreator(A) + for k in range(-1, 3): + assert_equal(A.trace(offset=k), B.trace(offset=k)) + + def test_reshape(self): + x = self.spcreator([[1, 0, 7], [0, 0, 0], [0, 3, 0], [0, 0, 5]]) + for order in ['C', 'F']: + for s in [(12, 1), (1, 12)]: + assert_array_equal(x.reshape(s, order=order).toarray(), + x.toarray().reshape(s, order=order)) + + # This example is taken from the stackoverflow answer at + # https://stackoverflow.com/q/16511879 + x = self.spcreator([[0, 10, 0, 0], [0, 0, 0, 0], [0, 20, 30, 40]]) + y = x.reshape((2, 6)) # Default order is 'C' + desired = [[0, 10, 0, 0, 0, 0], [0, 0, 0, 20, 30, 40]] + assert_array_equal(y.toarray(), desired) + + # Reshape with negative indexes + y = x.reshape((2, -1)) + assert_array_equal(y.toarray(), desired) + y = x.reshape((-1, 6)) + assert_array_equal(y.toarray(), desired) + assert_raises(ValueError, x.reshape, (-1, -1)) + + # Reshape with star args + y = x.reshape(2, 6) + assert_array_equal(y.toarray(), desired) + assert_raises(TypeError, x.reshape, 2, 6, not_an_arg=1) + + # Reshape with same size is noop unless copy=True + y = x.reshape((3, 4)) + assert_(y is x) + y = x.reshape((3, 4), copy=True) + assert_(y is not x) + + # Ensure reshape did not alter original size + assert_array_equal(x.shape, (3, 4)) + + if self.is_array_test: + with assert_raises(AttributeError, match="has no setter|n't set attribute"): + x.shape = (2, 6) + else: # spmatrix test + # Reshape in place + x.shape = (2, 6) + assert_array_equal(x.toarray(), desired) + + # Reshape to bad ndim + assert_raises(ValueError, x.reshape, (x.size,)) + assert_raises(ValueError, x.reshape, (1, x.size, 1)) + + @pytest.mark.slow + def test_setdiag_comprehensive(self): + def dense_setdiag(a, v, k): + v = np.asarray(v) + if k >= 0: + n = min(a.shape[0], a.shape[1] - k) + if v.ndim != 0: + n = min(n, len(v)) + v = v[:n] + i = np.arange(0, n) + j = np.arange(k, k + n) + a[i,j] = v + elif k < 0: + dense_setdiag(a.T, v, -k) + + def check_setdiag(a, b, k): + # Check setting diagonal using a scalar, a vector of + # correct length, and too short or too long vectors + for r in [-1, len(np.diag(a, k)), 2, 30]: + if r < 0: + v = np.random.choice(range(1, 20)) + else: + v = np.random.randint(1, 20, size=r) + + dense_setdiag(a, v, k) + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", WMSG, SparseEfficiencyWarning) + b.setdiag(v, k) + + # check that dense_setdiag worked + d = np.diag(a, k) + if np.asarray(v).ndim == 0: + assert_array_equal(d, v, err_msg="{msg} {r}") + else: + n = min(len(d), len(v)) + assert_array_equal(d[:n], v[:n], err_msg="{msg} {r}") + # check that sparse setdiag worked + assert_array_equal(b.toarray(), a, err_msg="{msg} {r}") + + # comprehensive test + np.random.seed(1234) + shapes = [(0,5), (5,0), (1,5), (5,1), (5,5)] + for dtype in [np.int8, np.float64]: + for m,n in shapes: + ks = np.arange(-m+1, n-1) + for k in ks: + a = np.zeros((m, n), dtype=dtype) + b = self.spcreator((m, n), dtype=dtype) + + check_setdiag(a, b, k) + + # check overwriting etc + for k2 in np.random.choice(ks, size=min(len(ks), 5)): + check_setdiag(a, b, k2) + + def test_setdiag(self): + # simple test cases + m = self.spcreator(np.eye(3)) + m2 = self.spcreator((4, 4)) + values = [3, 2, 1] + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", WMSG, SparseEfficiencyWarning) + assert_raises(ValueError, m.setdiag, values, k=4) + m.setdiag(values) + assert_array_equal(m.diagonal(), values) + m.setdiag(values, k=1) + assert_array_equal(m.toarray(), np.array([[3, 3, 0], + [0, 2, 2], + [0, 0, 1]])) + m.setdiag(values, k=-2) + assert_array_equal(m.toarray(), np.array([[3, 3, 0], + [0, 2, 2], + [3, 0, 1]])) + m.setdiag((9,), k=2) + assert_array_equal(m.toarray()[0,2], 9) + m.setdiag((9,), k=-2) + assert_array_equal(m.toarray()[2,0], 9) + # test short values on an empty matrix + m2.setdiag([1], k=2) + assert_array_equal(m2.toarray()[0], [0, 0, 1, 0]) + # test overwriting that same diagonal + m2.setdiag([1, 1], k=2) + assert_array_equal(m2.toarray()[:2], [[0, 0, 1, 0], + [0, 0, 0, 1]]) + + def test_nonzero(self): + A = array([[1, 0, 1],[0, 1, 1],[0, 0, 1]]) + Asp = self.spcreator(A) + + A_nz = {tuple(ij) for ij in transpose(A.nonzero())} + Asp_nz = {tuple(ij) for ij in transpose(Asp.nonzero())} + + assert_equal(A_nz, Asp_nz) + + def test_numpy_nonzero(self): + # See gh-5987 + A = array([[1, 0, 1], [0, 1, 1], [0, 0, 1]]) + Asp = self.spcreator(A) + + A_nz = {tuple(ij) for ij in transpose(np.nonzero(A))} + Asp_nz = {tuple(ij) for ij in transpose(np.nonzero(Asp))} + + assert_equal(A_nz, Asp_nz) + + def test_sum(self): + np.random.seed(1234) + dat_1 = np.array([[0, 1, 2], + [3, -4, 5], + [-6, 7, 9]]) + dat_2 = np.random.rand(5, 5) + dat_3 = np.array([[]]) + dat_4 = np.zeros((40, 40)) + dat_5 = sparse.rand(5, 5, density=1e-2).toarray() + matrices = [dat_1, dat_2, dat_3, dat_4, dat_5] + + def check(dtype, j): + dat = self.asdense(matrices[j], dtype=dtype) + datsp = self.spcreator(dat, dtype=dtype) + with np.errstate(over='ignore'): + assert_array_almost_equal(dat.sum(), datsp.sum()) + assert_equal(dat.sum().dtype, datsp.sum().dtype) + assert_(np.isscalar(datsp.sum(axis=None))) + assert_array_almost_equal(dat.sum(axis=None), datsp.sum(axis=None)) + assert_equal(dat.sum(axis=None).dtype, datsp.sum(axis=None).dtype) + assert_array_almost_equal(dat.sum(axis=0), datsp.sum(axis=0)) + assert_equal(dat.sum(axis=0).dtype, datsp.sum(axis=0).dtype) + assert_array_almost_equal(dat.sum(axis=1), datsp.sum(axis=1)) + assert_equal(dat.sum(axis=1).dtype, datsp.sum(axis=1).dtype) + assert_array_almost_equal(dat.sum(axis=-2), datsp.sum(axis=-2)) + assert_equal(dat.sum(axis=-2).dtype, datsp.sum(axis=-2).dtype) + assert_array_almost_equal(dat.sum(axis=-1), datsp.sum(axis=-1)) + assert_equal(dat.sum(axis=-1).dtype, datsp.sum(axis=-1).dtype) + assert_array_almost_equal(dat.sum(axis=(0, 1)), datsp.sum(axis=(0, 1))) + assert_equal(dat.sum(axis=(0, 1)).dtype, datsp.sum(axis=(0, 1)).dtype) + + for dtype in self.checked_dtypes: + for j in range(len(matrices)): + check(dtype, j) + + def test_sum_invalid_params(self): + out = np.zeros((1, 3)) + dat = array([[0, 1, 2], + [3, -4, 5], + [-6, 7, 9]]) + datsp = self.spcreator(dat) + + with assert_raises(ValueError, match="axis out of range"): + datsp.sum(axis=3) + with assert_raises(ValueError, match="axis out of range"): + datsp.sum(axis=(0, 3)) + with assert_raises(TypeError, match="axis must be an integer"): + datsp.sum(axis=1.5) + # error msg varies by sparray (1d result) or spmatrix (2d result) + with assert_raises(ValueError, match="do.*n.t match.*shape|wrong.*dimensions"): + datsp.mean(axis=1, out=out) + + def test_sum_dtype(self): + dat = array([[0, 1, 2], + [3, -4, 5], + [-6, 7, 9]]) + datsp = self.spcreator(dat) + + def check(dtype): + dat_sum = dat.sum(dtype=dtype) + datsp_sum = datsp.sum(dtype=dtype) + + assert_array_almost_equal(dat_sum, datsp_sum) + assert_equal(dat_sum.dtype, datsp_sum.dtype) + + for dtype in self.checked_dtypes: + check(dtype) + + def test_sum_dtype_pair_of_dtypes(self): + # Test sum with dtype on data of various input dtypes + # to ensure elements are cast before summing + base_dat = array([[0.6, 0.7], + [0.8, 0.9]]) + + for input_dtype in self.checked_dtypes: + dat = base_dat.astype(input_dtype) + datsp = self.spcreator(dat) + + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=PendingDeprecationWarning) + if not self.is_array_test: + dat = np.matrix(dat) + + for output_dtype in self.checked_dtypes: + # Skip problematic dtype combinations (complex to real, etc.) + # by catching exceptions when numpy itself fails + try: + with np.errstate(all='raise'): + _ = dat.sum(dtype=output_dtype) + except Exception: + # Skip this combination if NumPy can't handle it + continue + + # Check axis=None + with np.errstate(over='ignore'): + dat_sum = dat.sum(dtype=output_dtype) + datsp_sum = datsp.sum(dtype=output_dtype) + assert_allclose(datsp_sum, dat_sum) + assert_equal(datsp_sum.dtype, dat_sum.dtype) + + # Check axis=0 + dat_sum = dat.sum(axis=0, dtype=output_dtype) + datsp_sum = datsp.sum(axis=0, dtype=output_dtype) + assert_allclose(datsp_sum, dat_sum) + assert_equal(datsp_sum.dtype, dat_sum.dtype) + + # Check axis=1 + dat_sum = dat.sum(axis=1, dtype=output_dtype) + datsp_sum = datsp.sum(axis=1, dtype=output_dtype) + assert_allclose(datsp_sum, dat_sum) + assert_equal(datsp_sum.dtype, dat_sum.dtype) + + def test_sum_dtype_fractional_to_int(self): + # Test sum with dtype=int on data of float input dtype + # to ensure elements are cast before summing + datsp = self.spcreator([[0.6, 0.7], + [0.8, 0.9]]) + + correct_res = array([0, 0] if self.is_array_test else [[0, 0]]) + t_correct_res = array([0, 0] if self.is_array_test else [[0], [0]]) + + assert_equal(datsp.sum(dtype=int), 0) # Check axis=None + assert_equal(datsp.sum(axis=0, dtype=int), correct_res) # Check axis=0 + assert_equal(datsp.sum(axis=1, dtype=int), t_correct_res) # Check axis=1 + + def test_sum_out(self): + keep = not self.is_array_test + dat = array([[0, 1, 2], + [3, -4, 5], + [-6, 7, 9]]) + datsp = self.spcreator(dat) + + dat_out = array(0) if self.is_array_test else array([[0]]) + datsp_out = array(0) if self.is_array_test else matrix([[0]]) + + dat.sum(out=dat_out, keepdims=keep) + datsp.sum(out=datsp_out) + assert_array_almost_equal(dat_out, datsp_out) + + dat_out = np.zeros((3,)) if self.is_array_test else np.zeros((3, 1)) + datsp_out = np.zeros((3,)) if self.is_array_test else matrix(np.zeros((3, 1))) + + dat.sum(axis=1, out=dat_out, keepdims=keep) + datsp.sum(axis=1, out=datsp_out) + assert_array_almost_equal(dat_out, datsp_out) + + # check that wrong shape out parameter raises + with assert_raises(ValueError, match="output parameter"): + datsp.sum(out=array([0])) + with assert_raises(ValueError, match="output parameter"): + datsp.sum(out=array([[0]] if self.is_array_test else 0)) + + def test_numpy_sum(self): + # See gh-5987 + dat = array([[0, 1, 2], + [3, -4, 5], + [-6, 7, 9]]) + datsp = self.spcreator(dat) + + dat_sum = np.sum(dat) + datsp_sum = np.sum(datsp) + + assert_array_almost_equal(dat_sum, datsp_sum) + assert_equal(dat_sum.dtype, datsp_sum.dtype) + + def test_sum_mean_container_type(self): + dat = array([[0, 1, 2], + [3, -4, 5], + [-6, 7, 9]]) + datsp = self.spcreator(dat) + + assert isscalarlike(datsp.sum()) + matrix_or_array = ndarray if self.is_array_test else np.matrix + assert isinstance(datsp.sum(axis=0), matrix_or_array) + assert isinstance(datsp.sum(axis=1), matrix_or_array) + + assert isscalarlike(datsp.mean()) + assert isinstance(datsp.mean(axis=0), matrix_or_array) + assert isinstance(datsp.mean(axis=1), matrix_or_array) + + def test_mean(self): + keep = not self.is_array_test + def check(dtype): + dat = array([[0, 1, 2], + [3, 4, 5], + [6, 7, 9]], dtype=dtype) + datsp = self.spcreator(dat, dtype=dtype) + + assert_array_almost_equal(dat.mean(), datsp.mean()) + assert_equal(dat.mean().dtype, datsp.mean().dtype) + assert_(np.isscalar(datsp.mean(axis=None))) + assert_array_almost_equal( + dat.mean(axis=None, keepdims=keep), datsp.mean(axis=None) + ) + assert_equal(dat.mean(axis=None).dtype, datsp.mean(axis=None).dtype) + assert_array_almost_equal( + dat.mean(axis=0, keepdims=keep), datsp.mean(axis=0) + ) + assert_equal(dat.mean(axis=0).dtype, datsp.mean(axis=0).dtype) + assert_array_almost_equal( + dat.mean(axis=1, keepdims=keep), datsp.mean(axis=1) + ) + assert_equal(dat.mean(axis=1).dtype, datsp.mean(axis=1).dtype) + assert_array_almost_equal( + dat.mean(axis=-2, keepdims=keep), datsp.mean(axis=-2) + ) + assert_equal(dat.mean(axis=-2).dtype, datsp.mean(axis=-2).dtype) + assert_array_almost_equal( + dat.mean(axis=-1, keepdims=keep), datsp.mean(axis=-1) + ) + assert_equal(dat.mean(axis=-1).dtype, datsp.mean(axis=-1).dtype) + assert_array_almost_equal( + dat.mean(axis=(0, 1), keepdims=keep), datsp.mean(axis=(0, 1)) + ) + assert_equal(dat.mean(axis=(0, 1)).dtype, datsp.mean(axis=(0, 1)).dtype) + + + for dtype in self.checked_dtypes: + check(dtype) + + def test_mean_invalid_param(self): + out = self.asdense(np.zeros((1, 3))) + dat = array([[0, 1, 2], + [3, -4, 5], + [-6, 7, 9]]) + datsp = self.spcreator(dat) + + with assert_raises(ValueError, match="axis out of range"): + datsp.mean(axis=3) + with assert_raises(ValueError, match="axis out of range"): + datsp.mean(axis=(0, 3)) + with assert_raises(TypeError, match="axis must be an integer"): + datsp.mean(axis=1.5) + # error msg varies by sparray (1d result) or spmatrix (2d result) + with assert_raises(ValueError, match="do.*n.t match.*shape|wrong.*dimensions"): + datsp.mean(axis=1, out=out) + + def test_mean_dtype(self): + dat = array([[0, 1, 2], + [3, -4, 5], + [-6, 7, 9]]) + datsp = self.spcreator(dat) + + def check(dtype): + dat_mean = dat.mean(dtype=dtype) + datsp_mean = datsp.mean(dtype=dtype) + + assert_array_almost_equal(dat_mean, datsp_mean) + assert_equal(dat_mean.dtype, datsp_mean.dtype) + + for dtype in self.checked_dtypes: + check(dtype) + + def test_mean_out(self): + keep = not self.is_array_test + dat = array([[0, 1, 2], + [3, -4, 5], + [-6, 7, 9]]) + datsp = self.spcreator(dat) + + dat_out = array(0) if self.is_array_test else array([[0]]) + datsp_out = array(0) if self.is_array_test else matrix([[0]]) + + dat.mean(out=dat_out, keepdims=keep) + datsp.mean(out=datsp_out) + assert_array_almost_equal(dat_out, datsp_out) + + dat_out = np.zeros((3,)) if self.is_array_test else np.zeros((3, 1)) + datsp_out = np.zeros((3,)) if self.is_array_test else matrix(np.zeros((3, 1))) + + dat.mean(axis=1, out=dat_out, keepdims=keep) + datsp.mean(axis=1, out=datsp_out) + assert_array_almost_equal(dat_out, datsp_out) + + # check that wrong shape out parameter raises + with assert_raises(ValueError, match="output parameter.*wrong.*dimension"): + datsp.mean(out=array([0])) + with assert_raises(ValueError, match="output parameter.*wrong.*dimension"): + datsp.mean(out=array([[0]] if self.is_array_test else 0)) + + def test_numpy_mean(self): + # See gh-5987 + dat = array([[0, 1, 2], + [3, -4, 5], + [-6, 7, 9]]) + datsp = self.spcreator(dat) + + dat_mean = np.mean(dat) + datsp_mean = np.mean(datsp) + + assert_array_almost_equal(dat_mean, datsp_mean) + assert_equal(dat_mean.dtype, datsp_mean.dtype) + + def test_expm(self): + M = array([[1, 0, 2], [0, 0, 3], [-4, 5, 6]], float) + sM = self.spcreator(M, shape=(3,3), dtype=float) + Mexp = scipy.linalg.expm(M) + + N = array([[3., 0., 1.], [0., 2., 0.], [0., 0., 0.]]) + sN = self.spcreator(N, shape=(3,3), dtype=float) + Nexp = scipy.linalg.expm(N) + + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", + "splu converted its input to CSC format", + SparseEfficiencyWarning, + ) + warnings.filterwarnings( + "ignore", + "spsolve is more efficient when sparse b is in the CSC matrix format", + SparseEfficiencyWarning, + ) + warnings.filterwarnings( + "ignore", + "spsolve requires A be CSC or CSR matrix format", + SparseEfficiencyWarning, + ) + sMexp = expm(sM).toarray() + sNexp = expm(sN).toarray() + + assert_array_almost_equal((sMexp - Mexp), zeros((3, 3))) + assert_array_almost_equal((sNexp - Nexp), zeros((3, 3))) + + def test_inv(self): + def check(dtype): + M = array([[1, 0, 2], [0, 0, 3], [-4, 5, 6]], dtype) + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", + "spsolve requires A be CSC or CSR matrix format", + SparseEfficiencyWarning, + ) + warnings.filterwarnings( + "ignore", + "spsolve is more efficient when sparse b " + "is in the CSC matrix format", + SparseEfficiencyWarning, + ) + warnings.filterwarnings( + "ignore", + "splu converted its input to CSC format", + SparseEfficiencyWarning, + ) + sM = self.spcreator(M, shape=(3,3), dtype=dtype) + sMinv = inv(sM) + assert_array_almost_equal(sMinv.dot(sM).toarray(), np.eye(3)) + assert_raises(TypeError, inv, M) + for dtype in [float]: + check(dtype) + + def test_from_array(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", ComplexWarning) + A = array([[1,0,0],[2,3,4],[0,5,0],[0,0,0]]) + assert_array_equal(self.spcreator(A).toarray(), A) + + A = array([[1.0 + 3j, 0, 0], + [0, 2.0 + 5, 0], + [0, 0, 0]]) + assert_array_equal(self.spcreator(A).toarray(), A) + assert_array_equal( + self.spcreator(A, dtype='int16').toarray(),A.astype('int16')) + + + def test_from_matrix(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", ComplexWarning) + A = self.asdense([[1, 0, 0], [2, 3, 4], [0, 5, 0], [0, 0, 0]]) + assert_array_equal(self.spcreator(A).todense(), A) + + A = self.asdense([[1.0 + 3j, 0, 0], + [0, 2.0 + 5, 0], + [0, 0, 0]]) + assert_array_equal(self.spcreator(A).todense(), A) + assert_array_equal( + self.spcreator(A, dtype='int16').todense(), A.astype('int16') + ) + + def test_from_list(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", ComplexWarning) + A = [[1,0,0],[2,3,4],[0,5,0],[0,0,0]] + assert_array_equal(self.spcreator(A).toarray(), A) + + A = [[1.0 + 3j, 0, 0], + [0, 2.0 + 5, 0], + [0, 0, 0]] + assert_array_equal(self.spcreator(A).toarray(), array(A)) + assert_array_equal( + self.spcreator(A, dtype='int16').toarray(), array(A).astype('int16') + ) + + def test_from_sparse(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", ComplexWarning) + D = array([[1,0,0],[2,3,4],[0,5,0],[0,0,0]]) + S = self.csr_container(D) + assert_array_equal(self.spcreator(S).toarray(), D) + S = self.spcreator(D) + assert_array_equal(self.spcreator(S).toarray(), D) + + D = array([[1.0 + 3j, 0, 0], + [0, 2.0 + 5, 0], + [0, 0, 0]]) + S = self.csr_container(D) + assert_array_equal(self.spcreator(S).toarray(), D) + assert_array_equal(self.spcreator(S, dtype='int16').toarray(), + D.astype('int16')) + S = self.spcreator(D) + assert_array_equal(self.spcreator(S).toarray(), D) + assert_array_equal(self.spcreator(S, dtype='int16').toarray(), + D.astype('int16')) + + # def test_array(self): + # """test array(A) where A is in sparse format""" + # assert_equal( array(self.datsp), self.dat ) + + def test_todense(self): + # Check C- or F-contiguous (default). + chk = self.datsp.todense() + assert isinstance(chk, np.ndarray if self.is_array_test else np.matrix) + assert_array_equal(chk, self.dat) + assert_(chk.flags.c_contiguous != chk.flags.f_contiguous) + # Check C-contiguous (with arg). + chk = self.datsp.todense(order='C') + assert_array_equal(chk, self.dat) + assert_(chk.flags.c_contiguous) + assert_(not chk.flags.f_contiguous) + # Check F-contiguous (with arg). + chk = self.datsp.todense(order='F') + assert_array_equal(chk, self.dat) + assert_(not chk.flags.c_contiguous) + assert_(chk.flags.f_contiguous) + # Check with out argument (array). + out = np.zeros(self.datsp.shape, dtype=self.datsp.dtype) + chk = self.datsp.todense(out=out) + assert_array_equal(self.dat, out) + assert_array_equal(self.dat, chk) + assert np.may_share_memory(chk, out) + # Check with out array (matrix). + out = self.asdense(np.zeros(self.datsp.shape, dtype=self.datsp.dtype)) + chk = self.datsp.todense(out=out) + assert_array_equal(self.dat, out) + assert_array_equal(self.dat, chk) + assert np.may_share_memory(chk, out) + a = array([[1.,2.,3.]]) + dense_dot_dense = a @ self.dat + check = a @ self.datsp.todense() + assert_array_equal(dense_dot_dense, check) + b = array([[1.,2.,3.,4.]]).T + dense_dot_dense = self.dat @ b + check2 = self.datsp.todense() @ b + assert_array_equal(dense_dot_dense, check2) + # Check bool data works. + spbool = self.spcreator(self.dat, dtype=bool) + matbool = self.dat.astype(bool) + assert_array_equal(spbool.todense(), matbool) + + def test_toarray(self): + # Check C- or F-contiguous (default). + dat = asarray(self.dat) + chk = self.datsp.toarray() + assert_array_equal(chk, dat) + assert_(chk.flags.c_contiguous != chk.flags.f_contiguous) + # Check C-contiguous (with arg). + chk = self.datsp.toarray(order='C') + assert_array_equal(chk, dat) + assert_(chk.flags.c_contiguous) + assert_(not chk.flags.f_contiguous) + # Check F-contiguous (with arg). + chk = self.datsp.toarray(order='F') + assert_array_equal(chk, dat) + assert_(not chk.flags.c_contiguous) + assert_(chk.flags.f_contiguous) + # Check with output arg. + out = np.zeros(self.datsp.shape, dtype=self.datsp.dtype) + self.datsp.toarray(out=out) + assert_array_equal(chk, dat) + # Check that things are fine when we don't initialize with zeros. + out[...] = 1. + self.datsp.toarray(out=out) + assert_array_equal(chk, dat) + a = array([1.,2.,3.]) + dense_dot_dense = dot(a, dat) + check = dot(a, self.datsp.toarray()) + assert_array_equal(dense_dot_dense, check) + b = array([1.,2.,3.,4.]) + dense_dot_dense = dot(dat, b) + check2 = dot(self.datsp.toarray(), b) + assert_array_equal(dense_dot_dense, check2) + # Check bool data works. + spbool = self.spcreator(self.dat, dtype=bool) + arrbool = dat.astype(bool) + assert_array_equal(spbool.toarray(), arrbool) + + def test_astype(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", ComplexWarning) + D = array([[2.0 + 3j, 0, 0], + [0, 4.0 + 5j, 0], + [0, 0, 0]]) + S = self.spcreator(D) + + for x in supported_dtypes: + # Check correctly casted + D_casted = D.astype(x) + for copy in (True, False): + S_casted = S.astype(x, copy=copy) + assert_equal(S_casted.dtype, D_casted.dtype) # correct type + assert_equal(S_casted.toarray(), D_casted) # correct values + assert_equal(S_casted.format, S.format) # format preserved + # Check correctly copied + assert_(S_casted.astype(x, copy=False) is S_casted) + S_copied = S_casted.astype(x, copy=True) + assert_(S_copied is not S_casted) + + def check_equal_but_not_same_array_attribute(attribute): + a = getattr(S_casted, attribute) + b = getattr(S_copied, attribute) + assert_array_equal(a, b) + assert_(a is not b) + i = (0,) * b.ndim + b_i = b[i] + b[i] = not b[i] + assert_(a[i] != b[i]) + b[i] = b_i + + if S_casted.format in ('csr', 'csc', 'bsr'): + for attribute in ('indices', 'indptr', 'data'): + check_equal_but_not_same_array_attribute(attribute) + elif S_casted.format == 'coo': + for attribute in ('row', 'col', 'data'): + check_equal_but_not_same_array_attribute(attribute) + elif S_casted.format == 'dia': + for attribute in ('offsets', 'data'): + check_equal_but_not_same_array_attribute(attribute) + + def test_astype_immutable(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", ComplexWarning) + D = array([[2.0 + 3j, 0, 0], + [0, 4.0 + 5j, 0], + [0, 0, 0]]) + S = self.spcreator(D) + if hasattr(S, 'data'): + S.data.flags.writeable = False + if S.format in ('csr', 'csc', 'bsr'): + S.indptr.flags.writeable = False + S.indices.flags.writeable = False + for x in supported_dtypes: + D_casted = D.astype(x) + S_casted = S.astype(x) + assert_equal(S_casted.dtype, D_casted.dtype) + + def test_mul_scalar(self): + def check(dtype): + dat = self.dat_dtypes[dtype] + datsp = self.datsp_dtypes[dtype] + + assert_array_equal(dat*2, (datsp*2).toarray()) + assert_array_equal(dat*17.3, (datsp*17.3).toarray()) + + for dtype in self.math_dtypes: + check(dtype) + + def test_rmul_scalar(self): + def check(dtype): + dat = self.dat_dtypes[dtype] + datsp = self.datsp_dtypes[dtype] + + assert_array_equal(2*dat, (2*datsp).toarray()) + assert_array_equal(17.3*dat, (17.3*datsp).toarray()) + + for dtype in self.math_dtypes: + check(dtype) + + # GitHub issue #15210 + def test_rmul_scalar_type_error(self): + datsp = self.datsp_dtypes[np.float64] + with assert_raises(TypeError): + None * datsp + + def test_add(self): + def check(dtype): + dat = self.dat_dtypes[dtype] + datsp = self.datsp_dtypes[dtype] + + a = dat.copy() + a[0,2] = 2.0 + b = datsp + c = b + a + assert_array_equal(c, b.toarray() + a) + + c = b + b.tocsr() + assert_array_equal(c.toarray(), + b.toarray() + b.toarray()) + + # test broadcasting + c = b + a[0] + assert_array_equal(c, b.toarray() + a[0]) + + for dtype in self.math_dtypes: + check(dtype) + + def test_radd(self): + def check(dtype): + dat = self.dat_dtypes[dtype] + datsp = self.datsp_dtypes[dtype] + + a = dat.copy() + a[0,2] = 2.0 + b = datsp + c = a + b + assert_array_equal(c, a + b.toarray()) + + for dtype in self.math_dtypes: + check(dtype) + + def test_sub(self): + def check(dtype): + dat = self.dat_dtypes[dtype] + datsp = self.datsp_dtypes[dtype] + + assert_array_equal((datsp - datsp).toarray(), np.zeros((3, 4))) + assert_array_equal((datsp - 0).toarray(), dat) + + A = self.spcreator( + np.array([[1, 0, 0, 4], [-1, 0, 0, 0], [0, 8, 0, -5]], 'd') + ) + assert_array_equal((datsp - A).toarray(), dat - A.toarray()) + assert_array_equal((A - datsp).toarray(), A.toarray() - dat) + + # test broadcasting + assert_array_equal(datsp - dat[0], dat - dat[0]) + + for dtype in self.math_dtypes: + if dtype == np.dtype('bool'): + # boolean array subtraction deprecated in 1.9.0 + continue + + check(dtype) + + def test_rsub(self): + def check(dtype): + dat = self.dat_dtypes[dtype] + datsp = self.datsp_dtypes[dtype] + + assert_array_equal((dat - datsp),[[0,0,0,0],[0,0,0,0],[0,0,0,0]]) + assert_array_equal((datsp - dat),[[0,0,0,0],[0,0,0,0],[0,0,0,0]]) + assert_array_equal((0 - datsp).toarray(), -dat) + + A = self.spcreator([[1,0,0,4],[-1,0,0,0],[0,8,0,-5]],dtype='d') + assert_array_equal((dat - A), dat - A.toarray()) + assert_array_equal((A - dat), A.toarray() - dat) + assert_array_equal(A.toarray() - datsp, A.toarray() - dat) + assert_array_equal(datsp - A.toarray(), dat - A.toarray()) + + # test broadcasting + assert_array_equal(dat[0] - datsp, dat[0] - dat) + + for dtype in self.math_dtypes: + if dtype == np.dtype('bool'): + # boolean array subtraction deprecated in 1.9.0 + continue + + check(dtype) + + def test_add0(self): + def check(dtype): + dat = self.dat_dtypes[dtype] + datsp = self.datsp_dtypes[dtype] + + # Adding 0 to a sparse matrix + assert_array_equal((datsp + 0).toarray(), dat) + # use sum (which takes 0 as a starting value) + sumS = sum([k * datsp for k in range(1, 3)]) + sumD = sum([k * dat for k in range(1, 3)]) + assert_almost_equal(sumS.toarray(), sumD) + + for dtype in self.math_dtypes: + check(dtype) + + def test_elementwise_multiply(self): + # real/real + A = array([[4,0,9],[2,-3,5]]) + B = array([[0,7,0],[0,-4,0]]) + Asp = self.spcreator(A) + Bsp = self.spcreator(B) + # check output format + out_fmt = Asp.format if Asp.format in ('csc', 'dia', 'bsr') else 'csr' + assert (Asp.multiply(Bsp)).format == out_fmt + assert_almost_equal(Asp.multiply(Bsp).toarray(), A*B) # sparse/sparse + assert_almost_equal(Asp.multiply(B).toarray(), A*B) # sparse/dense + + # complex/complex + C = array([[1-2j,0+5j,-1+0j],[4-3j,-3+6j,5]]) + D = array([[5+2j,7-3j,-2+1j],[0-1j,-4+2j,9]]) + Csp = self.spcreator(C) + Dsp = self.spcreator(D) + assert_almost_equal(Csp.multiply(Dsp).toarray(), C*D) # sparse/sparse + assert_almost_equal(Csp.multiply(D).toarray(), C*D) # sparse/dense + + # real/complex + assert_almost_equal(Asp.multiply(Dsp).toarray(), A*D) # sparse/sparse + assert_almost_equal(Asp.multiply(D).toarray(), A*D) # sparse/dense + + def test_elementwise_multiply_broadcast(self): + A = array([4]) + B = array([[-9]]) + C = array([1,-1,0]) + D = array([[7,9,-9]]) + E = array([[3],[2],[1]]) + F = array([[8,6,3],[-4,3,2],[6,6,6]]) + G = [1, 2, 3] + H = np.ones((3, 4)) + J = H.T + K = array([[0]]) + L = array([[[1,2],[0,1]]]) + + # Some arrays can't be cast as spmatrices (A,C,L) so leave + # them out. + Bsp = self.spcreator(B) + Dsp = self.spcreator(D) + Esp = self.spcreator(E) + Fsp = self.spcreator(F) + Hsp = self.spcreator(H) + Hspp = self.spcreator(H[0,None]) + Jsp = self.spcreator(J) + Jspp = self.spcreator(J[:,0,None]) + Ksp = self.spcreator(K) + + matrices = [A, B, C, D, E, F, G, H, J, K, L] + spmatrices = [Bsp, Dsp, Esp, Fsp, Hsp, Hspp, Jsp, Jspp, Ksp] + + # sparse/sparse + for i in spmatrices: + for j in spmatrices: + try: + dense_mult = i.toarray() * j.toarray() + except ValueError: + assert_raises(ValueError, i.multiply, j) + continue + sp_mult = i.multiply(j) + assert_almost_equal(sp_mult.toarray(), dense_mult) + + # sparse/dense + for i in spmatrices: + for j in matrices: + try: + dense_mult = i.toarray() * j + except TypeError: + continue + except ValueError: + assert_raises(ValueError, i.multiply, j) + continue + try: + sp_mult = i.multiply(j) + except ValueError: + continue + if issparse(sp_mult): + assert_almost_equal(sp_mult.toarray(), dense_mult) + else: + assert_almost_equal(sp_mult, dense_mult) + + def test_elementwise_divide(self): + expected = [[1,np.nan,np.nan,1], + [1,np.nan,1,np.nan], + [np.nan,1,np.nan,np.nan]] + assert_array_equal(toarray(self.datsp / self.datsp), expected) + + denom = self.spcreator([[1,0,0,4],[-1,0,0,0],[0,8,0,-5]],dtype='d') + expected = [[1,np.nan,np.nan,0.5], + [-3,np.nan,inf,np.nan], + [np.nan,0.25,np.nan,0]] + assert_array_equal(toarray(self.datsp / denom), expected) + + # complex + A = array([[1-2j,0+5j,-1+0j],[4-3j,-3+6j,5]]) + B = array([[5+2j,7-3j,-2+1j],[0-1j,-4+2j,9]]) + Asp = self.spcreator(A) + Bsp = self.spcreator(B) + assert_almost_equal(toarray(Asp / Bsp), A/B) + + # integer + A = array([[1,2,3],[-3,2,1]]) + B = array([[0,1,2],[0,-2,3]]) + Asp = self.spcreator(A) + Bsp = self.spcreator(B) + with np.errstate(divide='ignore'): + assert_array_equal(toarray(Asp / Bsp), A / B) + + # mismatching sparsity patterns + A = array([[0,1],[1,0]]) + B = array([[1,0],[1,0]]) + Asp = self.spcreator(A) + Bsp = self.spcreator(B) + with np.errstate(divide='ignore', invalid='ignore'): + assert_array_equal(np.array(toarray(Asp / Bsp)), A / B) + + def test_pow(self): + A = array([[1, 0, 2, 0], [0, 3, 4, 0], [0, 5, 0, 0], [0, 6, 7, 8]]) + B = self.spcreator(A) + + if self.is_array_test: # sparrays use element-wise power + for exponent in [1, 2, 2.2, 3, 1+3j]: + ret_sp = B**exponent + ret_np = A**exponent + assert_array_equal(ret_sp.toarray(), ret_np) + assert_equal(ret_sp.dtype, ret_np.dtype) + + # invalid exponents + assert_raises(NotImplementedError, B.__pow__, 0) + assert_raises(ValueError, B.__pow__, -1) + + # nonsquare matrix + B = self.spcreator(A[:3,:]) + assert_equal((B**1).toarray(), B.toarray()) + else: # test sparse matrix. spmatrices use matrix multiplicative power + for exponent in [0,1,2,3]: + ret_sp = B**exponent + ret_np = np.linalg.matrix_power(A, exponent) + assert_array_equal(ret_sp.toarray(), ret_np) + assert_equal(ret_sp.dtype, ret_np.dtype) + + # invalid exponents + for exponent in [-1, 2.2, 1 + 3j]: + assert_raises(ValueError, B.__pow__, exponent) + + # nonsquare matrix + B = self.spcreator(A[:3,:]) + assert_raises(TypeError, B.__pow__, 1) + + def test_rmatvec(self): + M = self.spcreator([[3,0,0],[0,1,0],[2,0,3.0],[2,3,0]]) + assert_array_almost_equal([1,2,3,4] @ M, dot([1,2,3,4], M.toarray())) + row = array([[1,2,3,4]]) + assert_array_almost_equal(row @ M, row @ M.toarray()) + + def test_small_multiplication(self): + # test that A*x works for x with shape () (1,) (1,1) and (1,0) + A = self.spcreator([[1],[2],[3]]) + + assert_(issparse(A * array(1))) + assert_equal((A * array(1)).toarray(), [[1], [2], [3]]) + + assert_equal(A @ array([1]), array([1, 2, 3])) + assert_equal(A @ array([[1]]), array([[1], [2], [3]])) + assert_equal(A @ np.ones((1, 1)), array([[1], [2], [3]])) + assert_equal(A @ np.ones((1, 0)), np.ones((3, 0))) + + def test_star_vs_at_sign_for_sparray_and_spmatrix(self): + # test that * is matmul for spmatrix and mul for sparray + A = np.array([[1], [2], [3]]) + Asp = self.spcreator(A) + + if self.is_array_test: + assert_array_almost_equal((Asp * np.ones((3, 1))).toarray(), A) + assert_array_almost_equal((Asp * array([[1]])).toarray(), A) + else: + assert_equal(Asp * array([1]), array([1, 2, 3])) + assert_equal(Asp * array([[1]]), array([[1], [2], [3]])) + assert_equal(Asp * np.ones((1, 0)), np.ones((3, 0))) + + def test_binop_custom_type(self): + # Non-regression test: previously, binary operations would raise + # NotImplementedError instead of returning NotImplemented + # (https://docs.python.org/library/constants.html#NotImplemented) + # so overloading Custom + matrix etc. didn't work. + A = self.spcreator([[1], [2], [3]]) + B = BinopTester() + assert_equal(A + B, "matrix on the left") + assert_equal(A - B, "matrix on the left") + assert_equal(A * B, "matrix on the left") + assert_equal(B + A, "matrix on the right") + assert_equal(B - A, "matrix on the right") + assert_equal(B * A, "matrix on the right") + + assert_equal(A @ B, "matrix on the left") + assert_equal(B @ A, "matrix on the right") + + def test_binop_custom_type_with_shape(self): + A = self.spcreator([[1], [2], [3]]) + B = BinopTester_with_shape((3,1)) + assert_equal(A + B, "matrix on the left") + assert_equal(A - B, "matrix on the left") + assert_equal(A * B, "matrix on the left") + assert_equal(B + A, "matrix on the right") + assert_equal(B - A, "matrix on the right") + assert_equal(B * A, "matrix on the right") + + assert_equal(A @ B, "matrix on the left") + assert_equal(B @ A, "matrix on the right") + + def test_mul_custom_type(self): + class Custom: + def __init__(self, scalar): + self.scalar = scalar + + def __rmul__(self, other): + return other * self.scalar + + scalar = 2 + A = self.spcreator([[1],[2],[3]]) + c = Custom(scalar) + A_scalar = A * scalar + A_c = A * c + assert_array_equal_dtype(A_scalar.toarray(), A_c.toarray()) + assert_equal(A_scalar.format, A_c.format) + + def test_comparisons_custom_type(self): + A = self.spcreator([[1], [2], [3]]) + B = ComparisonTester() + assert_equal(A == B, "eq") + assert_equal(A != B, "ne") + assert_equal(A > B, "lt") + assert_equal(A >= B, "le") + assert_equal(A < B, "gt") + assert_equal(A <= B, "ge") + + def test_dot_scalar(self): + M = self.spcreator(array([[3,0,0],[0,1,0],[2,0,3.0],[2,3,0]])) + scalar = 10 + actual = M.dot(scalar) + expected = M * scalar + + assert_allclose(actual.toarray(), expected.toarray()) + + def test_matmul(self): + M = self.spcreator(array([[3,0,0],[0,1,0],[2,0,3.0],[2,3,0]])) + B = self.spcreator(array([[0,1],[1,0],[0,2]],'d')) + col = array([[1,2,3]]).T + + matmul = operator.matmul + # check matrix-vector + assert_array_almost_equal(matmul(M, col), M.toarray() @ col) + + # check matrix-matrix + assert_array_almost_equal(matmul(M, B).toarray(), (M @ B).toarray()) + assert_array_almost_equal(matmul(M.toarray(), B), (M @ B).toarray()) + assert_array_almost_equal(matmul(M, B.toarray()), (M @ B).toarray()) + + # check error on matrix-scalar + assert_raises(ValueError, matmul, M, 1) + assert_raises(ValueError, matmul, 1, M) + + def test_matvec(self): + M = self.spcreator([[3,0,0],[0,1,0],[2,0,3.0],[2,3,0]]) + col = array([[1,2,3]]).T + + assert_array_almost_equal(M @ col, M.toarray() @ col) + + # check result dimensions (ticket #514) + assert_equal((M @ array([1,2,3])).shape,(4,)) + assert_equal((M @ array([[1],[2],[3]])).shape,(4,1)) + assert_equal((M @ matrix([[1],[2],[3]])).shape,(4,1)) + + # check result type + assert_(isinstance(M @ array([1,2,3]), ndarray)) + matrix_or_array = ndarray if self.is_array_test else np.matrix + assert_(isinstance(M @ matrix([1,2,3]).T, matrix_or_array)) + + # ensure exception is raised for improper dimensions + bad_vecs = [array([1,2]), array([1,2,3,4]), array([[1],[2]]), + matrix([1,2,3]), matrix([[1],[2]])] + for x in bad_vecs: + assert_raises(ValueError, M.__matmul__, x) + + # The current relationship between sparse matrix products and array + # products is as follows: + assert_almost_equal(M@array([1,2,3]), dot(M.toarray(),[1,2,3])) + assert_almost_equal(M@[[1],[2],[3]], np.atleast_2d(dot(M.toarray(),[1,2,3])).T) + # Note that the result of M * x is dense if x has a singleton dimension. + + # Currently M.matvec(asarray(col)) is rank-1, whereas M.matvec(col) + # is rank-2. Is this desirable? + + def test_matmat_sparse(self): + a = matrix([[3,0,0],[0,1,0],[2,0,3.0],[2,3,0]]) + a2 = array([[3,0,0],[0,1,0],[2,0,3.0],[2,3,0]]) + b = matrix([[0,1],[1,0],[0,2]],'d') + asp = self.spcreator(a) + bsp = self.spcreator(b) + assert_array_almost_equal((asp @ bsp).toarray(), a @ b) + assert_array_almost_equal(asp @ b, a @ b) + assert_array_almost_equal(a @ bsp, a @ b) + assert_array_almost_equal(a2 @ bsp, a @ b) + + # Now try performing cross-type multiplication: + csp = bsp.tocsc() + c = b + want = a @ c + assert_array_almost_equal((asp @ csp).toarray(), want) + assert_array_almost_equal(asp @ c, want) + + assert_array_almost_equal(a @ csp, want) + assert_array_almost_equal(a2 @ csp, want) + csp = bsp.tocsr() + assert_array_almost_equal((asp @ csp).toarray(), want) + assert_array_almost_equal(asp @ c, want) + + assert_array_almost_equal(a @ csp, want) + assert_array_almost_equal(a2 @ csp, want) + csp = bsp.tocoo() + assert_array_almost_equal((asp @ csp).toarray(), want) + assert_array_almost_equal(asp @ c, want) + + assert_array_almost_equal(a @ csp, want) + assert_array_almost_equal(a2 @ csp, want) + + # Test provided by Andy Fraser, 2006-03-26 + L = 30 + frac = .3 + random.seed(0) # make runs repeatable + A = zeros((L,2)) + for i in range(L): + for j in range(2): + r = random.random() + if r < frac: + A[i,j] = r/frac + + A = self.spcreator(A) + B = A @ A.T + assert_array_almost_equal(B.toarray(), A.toarray() @ A.T.toarray()) + assert_array_almost_equal(B.toarray(), A.toarray() @ A.toarray().T) + + # check dimension mismatch 2x2 times 3x2 + A = self.spcreator([[1,2],[3,4]]) + B = self.spcreator([[1,2],[3,4],[5,6]]) + assert_raises(ValueError, A.__matmul__, B) + if self.is_array_test: + assert_raises(ValueError, A.__mul__, B) + + def test_matmat_dense(self): + a = [[3,0,0],[0,1,0],[2,0,3.0],[2,3,0]] + asp = self.spcreator(a) + + # check both array and matrix types + bs = [array([[1,2],[3,4],[5,6]]), matrix([[1,2],[3,4],[5,6]])] + + for b in bs: + result = asp @ b + assert_(isinstance(result, ndarray if self.is_array_test else type(b))) + assert_equal(result.shape, (4,2)) + assert_equal(result, dot(a,b)) + + @pytest.mark.filterwarnings("ignore:.*switching.*sparse array:DeprecationWarning") + def test_sparse_format_conversions(self): + A = sparse.kron([[1,0,2],[0,3,4],[5,0,0]], [[1,2],[0,3]]) + D = A.toarray() + A = self.spcreator(A) + + for format in ['bsr','coo','csc','csr','dia','dok','lil']: + a = A.asformat(format) + assert_equal(a.format,format) + assert_array_equal(a.toarray(), D) + + b = self.spcreator(D+3j).asformat(format) + assert_equal(b.format,format) + assert_array_equal(b.toarray(), D+3j) + + c = self.spcreator(D).asformat(format) + assert_equal(c.format,format) + assert_array_equal(c.toarray(), D) + + for format in ['array', 'dense']: + a = A.asformat(format) + assert_array_equal(a, D) + + b = self.spcreator(D+3j).asformat(format) + assert_array_equal(b, D+3j) + + def test_tobsr(self): + x = array([[1,0,2,0],[0,0,0,0],[0,0,4,5]]) + y = array([[0,1,2],[3,0,5]]) + A = kron(x,y) + Asp = self.spcreator(A) + for format in ['bsr']: + fn = getattr(Asp, 'to' + format) + + for X in [1, 2, 3, 6]: + for Y in [1, 2, 3, 4, 6, 12]: + assert_equal(fn(blocksize=(X, Y)).toarray(), A) + + def test_transpose(self): + dat_1 = self.dat + dat_2 = np.array([[]]) + matrices = [dat_1, dat_2] + + def check(dtype, j): + dat = array(matrices[j], dtype=dtype) + datsp = self.spcreator(dat) + + a = datsp.transpose() + b = dat.transpose() + + assert_array_equal(a.toarray(), b) + assert_array_equal(a.transpose().toarray(), dat) + assert_array_equal(datsp.transpose(axes=(1, 0)).toarray(), b) + assert_array_equal(datsp.mT.toarray(), b) + assert_array_equal(matrix_transpose(datsp).toarray(), b) + assert_equal(a.dtype, b.dtype) + + # See gh-5987 + empty = self.spcreator((3, 4)) + assert_array_equal(np.transpose(empty).toarray(), + np.transpose(zeros((3, 4)))) + assert_array_equal(empty.T.toarray(), zeros((4, 3))) + assert_array_equal(empty.mT.toarray(), empty.T.toarray()) + assert_array_equal(matrix_transpose(empty).toarray(), empty.T.toarray()) + assert_raises(ValueError, empty.transpose, axes=0) + + for dtype in self.checked_dtypes: + for j in range(len(matrices)): + check(dtype, j) + + def test_add_dense(self): + def check(dtype): + dat = self.dat_dtypes[dtype] + datsp = self.datsp_dtypes[dtype] + + # adding a dense matrix to a sparse matrix + sum1 = dat + datsp + assert_array_equal(sum1, dat + dat) + sum2 = datsp + dat + assert_array_equal(sum2, dat + dat) + + for dtype in self.math_dtypes: + check(dtype) + + def test_sub_dense(self): + # subtracting a dense matrix to/from a sparse matrix + def check(dtype): + dat = self.dat_dtypes[dtype] + datsp = self.datsp_dtypes[dtype] + + # Behavior is different for bool. + if dat.dtype == bool: + sum1 = dat - datsp + assert_array_equal(sum1, dat - dat) + sum2 = datsp - dat + assert_array_equal(sum2, dat - dat) + else: + # Manually add to avoid upcasting from scalar + # multiplication. + sum1 = (dat + dat + dat) - datsp + assert_array_equal(sum1, dat + dat) + sum2 = (datsp + datsp + datsp) - dat + assert_array_equal(sum2, dat + dat) + + for dtype in self.math_dtypes: + if dtype == np.dtype('bool'): + # boolean array subtraction deprecated in 1.9.0 + continue + + check(dtype) + + def test_maximum_minimum(self): + A_dense = np.array([[1, 0, 3], [0, 4, 5], [0, 0, 0]]) + B_dense = np.array([[1, 1, 2], [0, 3, 6], [1, -1, 0]]) + + A_dense_cpx = np.array([[1, 0, 3], [0, 4+2j, 5], [0, 1j, -1j]]) + + def check(dtype, dtype2, btype): + if np.issubdtype(dtype, np.complexfloating): + A = self.spcreator(A_dense_cpx.astype(dtype)) + else: + A = self.spcreator(A_dense.astype(dtype)) + if btype == 'scalar': + B = dtype2.type(1) + elif btype == 'scalar2': + B = dtype2.type(-1) + elif btype == 'dense': + B = B_dense.astype(dtype2) + elif btype == 'sparse': + B = self.spcreator(B_dense.astype(dtype2)) + else: + raise ValueError() + + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", + "Taking (maximum|minimum) with a (positive|negative) number " + "results in a dense matrix", + SparseEfficiencyWarning, + ) + + max_s = A.maximum(B) + min_s = A.minimum(B) + + max_d = np.maximum(toarray(A), toarray(B)) + assert_array_equal(toarray(max_s), max_d) + assert_equal(max_s.dtype, max_d.dtype) + + min_d = np.minimum(toarray(A), toarray(B)) + assert_array_equal(toarray(min_s), min_d) + assert_equal(min_s.dtype, min_d.dtype) + + for dtype in self.math_dtypes: + for dtype2 in [np.int8, np.float64, np.complex128]: + for btype in ['scalar', 'scalar2', 'dense', 'sparse']: + check(np.dtype(dtype), np.dtype(dtype2), btype) + + def test_copy(self): + # Check whether the copy=True and copy=False keywords work + A = self.datsp + + # check that copy preserves format + assert_equal(A.copy().format, A.format) + assert_equal(A.__class__(A,copy=True).format, A.format) + assert_equal(A.__class__(A,copy=False).format, A.format) + + assert_equal(A.copy().toarray(), A.toarray()) + assert_equal(A.__class__(A, copy=True).toarray(), A.toarray()) + assert_equal(A.__class__(A, copy=False).toarray(), A.toarray()) + + # check that XXX_array.toXXX() works + toself = getattr(A,'to' + A.format) + assert_(toself() is A) + assert_(toself(copy=False) is A) + assert_equal(toself(copy=True).format, A.format) + assert_equal(toself(copy=True).toarray(), A.toarray()) + + # check whether the data is copied? + assert_(not sparse_may_share_memory(A.copy(), A)) + + # test that __iter__ is compatible with NumPy matrix + def test_iterator(self): + B = self.asdense(np.arange(50).reshape(5, 10)) + A = self.spcreator(B) + + for x, y in zip(A, B): + assert_equal(x.toarray(), y) + + def test_size_zero_matrix_arithmetic(self): + # Test basic matrix arithmetic with shapes like (0,0), (10,0), + # (0, 3), etc. + mat = array([]) + a = mat.reshape((0, 0)) + b = mat.reshape((0, 1)) + c = mat.reshape((0, 5)) + d = mat.reshape((1, 0)) + e = mat.reshape((5, 0)) + f = np.ones([5, 5]) + + asp = self.spcreator(a) + bsp = self.spcreator(b) + csp = self.spcreator(c) + dsp = self.spcreator(d) + esp = self.spcreator(e) + fsp = self.spcreator(f) + + # matrix product. + assert_array_equal(asp.dot(asp).toarray(), np.dot(a, a)) + assert_array_equal(bsp.dot(dsp).toarray(), np.dot(b, d)) + assert_array_equal(dsp.dot(bsp).toarray(), np.dot(d, b)) + assert_array_equal(csp.dot(esp).toarray(), np.dot(c, e)) + assert_array_equal(csp.dot(fsp).toarray(), np.dot(c, f)) + assert_array_equal(esp.dot(csp).toarray(), np.dot(e, c)) + assert_array_equal(dsp.dot(csp).toarray(), np.dot(d, c)) + assert_array_equal(fsp.dot(esp).toarray(), np.dot(f, e)) + + # bad matrix products + assert_raises(ValueError, dsp.dot, e) + assert_raises(ValueError, asp.dot, d) + + # elemente-wise multiplication + assert_array_equal(asp.multiply(asp).toarray(), np.multiply(a, a)) + assert_array_equal(bsp.multiply(bsp).toarray(), np.multiply(b, b)) + assert_array_equal(dsp.multiply(dsp).toarray(), np.multiply(d, d)) + + assert_array_equal(asp.multiply(a).toarray(), np.multiply(a, a)) + assert_array_equal(bsp.multiply(b).toarray(), np.multiply(b, b)) + assert_array_equal(dsp.multiply(d).toarray(), np.multiply(d, d)) + + assert_array_equal(asp.multiply(6).toarray(), np.multiply(a, 6)) + assert_array_equal(bsp.multiply(6).toarray(), np.multiply(b, 6)) + assert_array_equal(dsp.multiply(6).toarray(), np.multiply(d, 6)) + + # bad element-wise multiplication + assert_raises(ValueError, asp.multiply, c) + assert_raises(ValueError, esp.multiply, c) + + # Addition + assert_array_equal(asp.__add__(asp).toarray(), a.__add__(a)) + assert_array_equal(bsp.__add__(bsp).toarray(), b.__add__(b)) + assert_array_equal(dsp.__add__(dsp).toarray(), d.__add__(d)) + + # bad addition + assert_raises(ValueError, asp.__add__, dsp) + assert_raises(ValueError, bsp.__add__, asp) + + def test_size_zero_conversions(self): + mat = array([]) + a = mat.reshape((0, 0)) + b = mat.reshape((0, 5)) + c = mat.reshape((5, 0)) + + for m in [a, b, c]: + spm = self.spcreator(m) + assert_array_equal(spm.tocoo().toarray(), m) + assert_array_equal(spm.tocsr().toarray(), m) + assert_array_equal(spm.tocsc().toarray(), m) + assert_array_equal(spm.tolil().toarray(), m) + assert_array_equal(spm.todok().toarray(), m) + assert_array_equal(spm.tobsr().toarray(), m) + + def test_dtype_check(self): + a = np.array([[3.5, 0, 1.1], [0, 0, 0]], dtype=np.float16) + with assert_raises(ValueError, match="does not support dtype"): + self.spcreator(a) + + A32 = self.spcreator(a.astype(np.float32)) + with assert_raises(ValueError, match="does not support dtype"): + self.spcreator(A32, dtype=np.float16) + + def test_pickle(self): + import pickle + def check(): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", SparseEfficiencyWarning) + datsp = self.datsp.copy() + for protocol in range(pickle.HIGHEST_PROTOCOL): + sploaded = pickle.loads(pickle.dumps(datsp, protocol=protocol)) + assert_equal(datsp.shape, sploaded.shape) + assert_array_equal(datsp.toarray(), sploaded.toarray()) + assert_equal(datsp.format, sploaded.format) + # Hacky check for class member equality. This assumes that + # all instance variables are one of: + # 1. Plain numpy ndarrays + # 2. Tuples of ndarrays + # 3. Types that support equality comparison with == + for key, val in datsp.__dict__.items(): + if isinstance(val, np.ndarray): + assert_array_equal(val, sploaded.__dict__[key]) + elif (isinstance(val, tuple) and val + and isinstance(val[0], np.ndarray)): + assert_array_equal(val, sploaded.__dict__[key]) + else: + assert_(val == sploaded.__dict__[key]) + check() + + def test_unary_ufunc_overrides(self): + def check(name): + if name == "sign": + pytest.skip("sign conflicts with comparison op " + "support on Numpy") + if self.datsp.format in ["dok", "lil"]: + pytest.skip("Unary ops not implemented for dok/lil") + ufunc = getattr(np, name) + + X = self.spcreator(np.arange(20).reshape(4, 5) / 20.) + X0 = ufunc(X.toarray()) + + X2 = ufunc(X) + assert_array_equal(X2.toarray(), X0) + + for name in ["sin", "tan", "arcsin", "arctan", "sinh", "tanh", + "arcsinh", "arctanh", "rint", "sign", "expm1", "log1p", + "deg2rad", "rad2deg", "floor", "ceil", "trunc", "sqrt", + "abs"]: + check(name) + + def test_resize(self): + # resize(shape) resizes the matrix in-place + D = np.array([[1, 0, 3, 4], + [2, 0, 0, 0], + [3, 0, 0, 0]]) + S = self.spcreator(D) + assert_(S.resize((3, 2)) is None) + assert_array_equal(S.toarray(), [[1, 0], + [2, 0], + [3, 0]]) + S.resize((2, 2)) + assert_array_equal(S.toarray(), [[1, 0], + [2, 0]]) + S.resize((3, 2)) + assert_array_equal(S.toarray(), [[1, 0], + [2, 0], + [0, 0]]) + S.resize((3, 3)) + assert_array_equal(S.toarray(), [[1, 0, 0], + [2, 0, 0], + [0, 0, 0]]) + # test no-op + S.resize((3, 3)) + assert_array_equal(S.toarray(), [[1, 0, 0], + [2, 0, 0], + [0, 0, 0]]) + + # test *args + S.resize(3, 2) + assert_array_equal(S.toarray(), [[1, 0], + [2, 0], + [0, 0]]) + + if self.is_array_test and S.format in ["coo", "csr"]: + S.resize(1) + else: + assert_raises((ValueError, NotImplementedError, IndexError), S.resize, 1) + + for bad_shape in [(-1, 2), (2, -1), (1, 2, 3)]: + assert_raises(ValueError, S.resize, bad_shape) + + def test_constructor1_base(self): + A = self.datsp + + self_format = A.format + + C = A.__class__(A, copy=False) + assert_array_equal_dtype(A.toarray(), C.toarray()) + if self_format not in NON_ARRAY_BACKED_FORMATS: + assert_(sparse_may_share_memory(A, C)) + + C = A.__class__(A, dtype=A.dtype, copy=False) + assert_array_equal_dtype(A.toarray(), C.toarray()) + if self_format not in NON_ARRAY_BACKED_FORMATS: + assert_(sparse_may_share_memory(A, C)) + + C = A.__class__(A, dtype=np.float32, copy=False) + assert_array_equal(A.toarray(), C.toarray()) + + C = A.__class__(A, copy=True) + assert_array_equal_dtype(A.toarray(), C.toarray()) + assert_(not sparse_may_share_memory(A, C)) + + for other_format in ['csr', 'csc', 'coo', 'dia', 'dok', 'lil']: + if other_format == self_format: + continue + B = A.asformat(other_format) + C = A.__class__(B, copy=False) + assert_array_equal_dtype(A.toarray(), C.toarray()) + + C = A.__class__(B, copy=True) + assert_array_equal_dtype(A.toarray(), C.toarray()) + assert_(not sparse_may_share_memory(B, C)) + + +class _TestInplaceArithmetic: + def test_inplace_dense(self): + a = np.ones((3, 4)) + b = self.spcreator(a) + + x = a.copy() + y = a.copy() + x += a + y += b + assert_array_equal(x, y) + + x = a.copy() + y = a.copy() + x -= a + y -= b + assert_array_equal(x, y) + + if self.is_array_test: + # Elementwise multiply from sparray.__rmul__ + x = a.copy() + y = a.copy() + with assert_raises(ValueError, match="inconsistent shapes"): + x *= b.T + x = x * a + y *= b + assert_array_equal(x, y.toarray()) + else: + # Matrix multiply from spmatrix.__rmul__ + x = a.copy() + y = a.copy() + with assert_raises(ValueError, match="dimension mismatch"): + x *= b + x = x.dot(a.T) + y *= b.T + assert_array_equal(x, y) + + # Matrix multiply from __rmatmul__ + y = a.copy() + y @= b.T + x = a.copy() + y = a.copy() + with assert_raises(ValueError, match="dimension mismatch"): + x @= b + x = x.dot(a.T) + y @= b.T + assert_array_equal(x, y) + + # Floor division is not supported + with assert_raises(TypeError, match="unsupported operand"): + x //= b + + def test_imul_scalar(self): + def check(dtype): + dat = self.dat_dtypes[dtype] + datsp = self.datsp_dtypes[dtype] + + # Avoid implicit casting. + if np.can_cast(int, dtype, casting='same_kind'): + a = datsp.copy() + a *= 2 + b = dat.copy() + b *= 2 + assert_array_equal(b, a.toarray()) + + if np.can_cast(float, dtype, casting='same_kind'): + a = datsp.copy() + a *= 17.3 + b = dat.copy() + b *= 17.3 + assert_array_equal(b, a.toarray()) + + for dtype in self.math_dtypes: + check(dtype) + + def test_idiv_scalar(self): + def check(dtype): + dat = self.dat_dtypes[dtype] + datsp = self.datsp_dtypes[dtype] + + if np.can_cast(int, dtype, casting='same_kind'): + a = datsp.copy() + a /= 2 + b = dat.copy() + b /= 2 + assert_array_equal(b, a.toarray()) + + if np.can_cast(float, dtype, casting='same_kind'): + a = datsp.copy() + a /= 17.3 + b = dat.copy() + b /= 17.3 + assert_array_equal(b, a.toarray()) + + for dtype in self.math_dtypes: + # /= should only be used with float dtypes to avoid implicit + # casting. + if not np.can_cast(dtype, np.dtype(int)): + check(dtype) + + def test_inplace_success(self): + # Inplace ops should work even if a specialized version is not + # implemented, falling back to x = x y + a = self.spcreator(np.eye(5)) + b = self.spcreator(np.eye(5)) + bp = self.spcreator(np.eye(5)) + + b += a + bp = bp + a + assert_allclose(b.toarray(), bp.toarray()) + + if self.is_array_test: + b *= a + bp = bp * a + assert_allclose(b.toarray(), bp.toarray()) + + b @= a + bp = bp @ a + assert_allclose(b.toarray(), bp.toarray()) + + b -= a + bp = bp - a + assert_allclose(b.toarray(), bp.toarray()) + + with assert_raises(TypeError, match="unsupported operand"): + a //= b + + +class _TestGetSet: + def test_getelement(self): + def check(dtype): + D = array([[1,0,0], + [4,3,0], + [0,2,0], + [0,0,0]], dtype=dtype) + A = self.spcreator(D) + + M,N = D.shape + + for i in range(-M, M): + for j in range(-N, N): + assert_equal(A[i,j], D[i,j]) + + assert_equal(type(A[1,1]), dtype) + + for ij in [(0,3),(-1,3),(4,0),(4,3),(4,-1), (1, 2, 3)]: + assert_raises((IndexError, TypeError), A.__getitem__, ij) + + for dtype in supported_dtypes: + check(np.dtype(dtype)) + + def test_setelement(self): + def check(dtype, scalar_container): + A = self.spcreator((3, 4), dtype=dtype) + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", WMSG, SparseEfficiencyWarning) + A[0, 0] = scalar_container(dtype.type(0)) # bug 870 + A[1, 2] = scalar_container(dtype.type(4.0)) + A[0, 1] = scalar_container(dtype.type(3)) + A[2, 0] = scalar_container(dtype.type(2.0)) + A[0, -1] = scalar_container(dtype.type(8)) + A[-1, -2] = scalar_container(dtype.type(7)) + A[0, 1] = scalar_container(dtype.type(5)) + + if dtype != np.bool_: + assert_array_equal( + A.toarray(), [[0, 5, 0, 8], [0, 0, 4, 0], [2, 0, 7, 0]] + ) + + for ij in [(0, 4), (-1, 4), (3, 0), (3, 4), (3, -1)]: + assert_raises(IndexError, A.__setitem__, ij, 123.0) + + for v in [[1, 2, 3], array([1, 2, 3])]: + assert_raises(ValueError, A.__setitem__, (0, 0), v) + + if not np.issubdtype(dtype, np.complexfloating) and dtype != np.bool_: + for v in [3j]: + assert_raises(TypeError, A.__setitem__, (0, 0), v) + + scalar_containers = [ + lambda x: csr_array(np.array([[x]])), np.array, lambda x: x + ] + for scalar_container in scalar_containers: + for dtype in supported_dtypes: + check(np.dtype(dtype), scalar_container) + + def test_negative_index_assignment(self): + # Regression test for GitHub issue 4428. + + def check(dtype): + A = self.spcreator((3, 10), dtype=dtype) + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", WMSG, SparseEfficiencyWarning) + A[0, -4] = 1 + assert_equal(A[0, -4], 1) + + for dtype in self.math_dtypes: + check(np.dtype(dtype)) + + def test_scalar_assign_2(self): + n, m = (5, 10) + + def _test_set(i, j, nitems): + msg = f"{i!r} ; {j!r} ; {nitems!r}" + A = self.spcreator((n, m)) + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", WMSG, SparseEfficiencyWarning) + A[i, j] = 1 + assert_almost_equal(A.sum(), nitems, err_msg=msg) + assert_almost_equal(A[i, j], 1, err_msg=msg) + + # [i,j] + for i, j in [(2, 3), (-1, 8), (-1, -2), (array(-1), -2), (-1, array(-2)), + (array(-1), array(-2))]: + _test_set(i, j, 1) + + def test_index_scalar_assign(self): + A = self.spcreator((5, 5)) + B = np.zeros((5, 5)) + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", WMSG, SparseEfficiencyWarning) + for C in [A, B]: + C[0,1] = 1 + C[3,0] = 4 + C[3,0] = 9 + assert_array_equal(A.toarray(), B) + + +@pytest.mark.thread_unsafe(reason="fails in parallel") +class _TestSolve: + def test_solve(self): + # Test whether the lu_solve command segfaults, as reported by Nils + # Wagner for a 64-bit machine, 02 March 2005 (EJS) + n = 20 + np.random.seed(0) # make tests repeatable + A = zeros((n,n), dtype=complex) + x = np.random.rand(n) + y = np.random.rand(n-1)+1j*np.random.rand(n-1) + r = np.random.rand(n) + for i in range(len(x)): + A[i,i] = x[i] + for i in range(len(y)): + A[i,i+1] = y[i] + A[i+1,i] = conjugate(y[i]) + A = self.spcreator(A) + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", "splu converted its input to CSC format", + SparseEfficiencyWarning) + x = splu(A).solve(r) + assert_almost_equal(A @ x,r) + + +class _TestSlicing: + def test_dtype_preservation(self): + assert_equal(self.spcreator((1,10), dtype=np.int16)[0,1:5].dtype, np.int16) + assert_equal(self.spcreator((1,10), dtype=np.int32)[0,1:5].dtype, np.int32) + assert_equal(self.spcreator((1,10), dtype=np.float32)[0,1:5].dtype, np.float32) + assert_equal(self.spcreator((1,10), dtype=np.float64)[0,1:5].dtype, np.float64) + + def test_dtype_preservation_empty_slice(self): + # This should be parametrized with pytest, but something in the parent + # class creation used in this file breaks pytest.mark.parametrize. + for dt in [np.int16, np.int32, np.float32, np.float64]: + A = self.spcreator((3, 2), dtype=dt) + assert_equal(A[:, 0:0:2].dtype, dt) + assert_equal(A[0:0:2, :].dtype, dt) + assert_equal(A[0, 0:0:2].dtype, dt) + assert_equal(A[0:0:2, 0].dtype, dt) + + def test_get_horiz_slice(self): + B = self.asdense(arange(50.).reshape(5,10)) + A = self.spcreator(B) + r0, r1, r2 = (0, 1, 2) if self.is_array_test else ([0], [1], [2]) + assert_array_equal(B[r1, :], A[1, :].toarray()) + assert_array_equal(B[r1, 2:5], A[1, 2:5].toarray()) + + C = self.asdense([[1, 2, 1], [4, 0, 6], [0, 0, 0], [0, 0, 1]]) + D = self.spcreator(C) + assert_array_equal(C[r1, 1:3], D[1, 1:3].toarray()) + + # Now test slicing when a row contains only zeros + E = self.asdense([[1, 2, 1], [4, 0, 0], [0, 0, 0], [0, 0, 1]]) + F = self.spcreator(E) + assert_array_equal(E[r1, 1:3], F[1, 1:3].toarray()) + assert_array_equal(E[r2, -2:], F[2, -2:].toarray()) + + # The following should raise exceptions: + assert_raises(IndexError, A.__getitem__, (slice(None), 11)) + assert_raises(IndexError, A.__getitem__, (6, slice(3, 7))) + + def test_get_vert_slice(self): + B = arange(50.).reshape(5, 10) + A = self.spcreator(B) + c0, c1, c2 = (0, 1, 2) if self.is_array_test else ([0], [1], [2]) + assert_array_equal(B[2:5, c0], A[2:5, 0].toarray()) + assert_array_equal(B[:, c1], A[:, 1].toarray()) + + C = array([[1, 2, 1], [4, 0, 6], [0, 0, 0], [0, 0, 1]]) + D = self.spcreator(C) + assert_array_equal(C[1:3, c1], D[1:3, 1].toarray()) + assert_array_equal(C[:, c2], D[:, 2].toarray()) + + # Now test slicing when a column contains only zeros + E = array([[1, 0, 1], [4, 0, 0], [0, 0, 0], [0, 0, 1]]) + F = self.spcreator(E) + assert_array_equal(E[:, c1], F[:, 1].toarray()) + assert_array_equal(E[-2:, c2], F[-2:, 2].toarray()) + + # The following should raise exceptions: + assert_raises(IndexError, A.__getitem__, (slice(None), 11)) + assert_raises(IndexError, A.__getitem__, (6, slice(3, 7))) + + def test_get_slices(self): + B = arange(50.).reshape(5, 10) + A = self.spcreator(B) + assert_array_equal(A[2:5, 0:3].toarray(), B[2:5, 0:3]) + assert_array_equal(A[1:, :-1].toarray(), B[1:, :-1]) + assert_array_equal(A[:-1, 1:].toarray(), B[:-1, 1:]) + + # Now test slicing when a column contains only zeros + E = array([[1, 0, 1], [4, 0, 0], [0, 0, 0], [0, 0, 1]]) + F = self.spcreator(E) + assert_array_equal(E[1:2, 1:2], F[1:2, 1:2].toarray()) + assert_array_equal(E[:, 1:], F[:, 1:].toarray()) + + def test_non_unit_stride_2d_indexing(self): + # Regression test -- used to silently ignore the stride. + v0 = np.random.rand(50, 50) + try: + v = self.spcreator(v0)[0:25:2, 2:30:3] + except ValueError: + # if unsupported + raise pytest.skip("feature not implemented") + + assert_array_equal(v.toarray(), v0[0:25:2, 2:30:3]) + + def test_slicing_2(self): + B = self.asdense(arange(50).reshape(5,10)) + A = self.spcreator(B) + + # [i,j] + assert_equal(A[2,3], B[2,3]) + assert_equal(A[-1,8], B[-1,8]) + assert_equal(A[-1,-2],B[-1,-2]) + assert_equal(A[array(-1),-2],B[-1,-2]) + assert_equal(A[-1,array(-2)],B[-1,-2]) + assert_equal(A[array(-1),array(-2)],B[-1,-2]) + + # [i,1:2] + assert_equal(A[2, :].toarray(), B[2, :]) + assert_equal(A[2, 5:-2].toarray(), B[2, 5:-2]) + assert_equal(A[array(2), 5:-2].toarray(), B[2, 5:-2]) + + # [1:2,j] + assert_equal(A[:, 2].toarray(), B[:, 2]) + assert_equal(A[3:4, 9].toarray(), B[3:4, 9]) + assert_equal(A[1:4, -5].toarray(), B[1:4, -5]) + assert_equal(A[2:-1, 3].toarray(), B[2:-1, 3]) + assert_equal(A[2:-1, array(3)].toarray(), B[2:-1, 3]) + + # [1:2,1:2] + assert_equal(A[1:2, 1:2].toarray(), B[1:2, 1:2]) + assert_equal(A[4:, 3:].toarray(), B[4:, 3:]) + assert_equal(A[:4, :5].toarray(), B[:4, :5]) + assert_equal(A[2:-1, :5].toarray(), B[2:-1, :5]) + + # [i] + assert_equal(A[1, :].toarray(), B[1, :]) + assert_equal(A[-2, :].toarray(), B[-2, :]) + assert_equal(A[array(-2), :].toarray(), B[-2, :]) + + # [1:2] + assert_equal(A[1:4].toarray(), B[1:4]) + assert_equal(A[1:-2].toarray(), B[1:-2]) + + # Check bug reported by Robert Cimrman: + # http://thread.gmane.org/gmane.comp.python.scientific.devel/7986 (dead link) + s = slice(int8(2),int8(4),None) + assert_equal(A[s, :].toarray(), B[2:4, :]) + assert_equal(A[:, s].toarray(), B[:, 2:4]) + + @pytest.mark.fail_slow(2) + def test_slicing_3(self): + B = self.asdense(arange(50).reshape(5,10)) + A = self.spcreator(B) + + s_ = np.s_ + slices = [s_[:2], s_[1:2], s_[3:], s_[3::2], + s_[15:20], s_[3:2], + s_[8:3:-1], s_[4::-2], s_[:5:-1], + 0, 1, s_[:], s_[1:5], -1, -2, -5, + array(-1), np.int8(-3)] + + def check_1(a): + x = A[a] + y = B[a] + if y.shape == (): + assert_equal(x, y, repr(a)) + else: + if x.size == 0 and y.size == 0: + pass + else: + assert_array_equal(x.toarray(), y, repr(a)) + + for j, a in enumerate(slices): + check_1(a) + + def check_2(a, b): + # Indexing np.matrix with 0-d arrays seems to be broken, + # as they seem not to be treated as scalars. + # https://github.com/numpy/numpy/issues/3110 + if isinstance(a, np.ndarray): + ai = int(a) + else: + ai = a + if isinstance(b, np.ndarray): + bi = int(b) + else: + bi = b + + x = A[a, b] + y = B[ai, bi] + + if y.shape == (): + assert_equal(x, y, repr((a, b))) + else: + if x.size == 0 and y.size == 0: + pass + else: + assert_array_equal(x.toarray(), y, repr((a, b))) + + for i, a in enumerate(slices): + for j, b in enumerate(slices): + check_2(a, b) + + # Check out of bounds etc. systematically + extra_slices = [] + for a, b, c in itertools.product(*([(None, 0, 1, 2, 5, 15, + -1, -2, 5, -15)]*3)): + if c == 0: + continue + extra_slices.append(slice(a, b, c)) + + for a in extra_slices: + check_2(a, a) + check_2(a, -2) + check_2(-2, a) + + def test_None_slicing(self): + B = self.asdense(arange(50).reshape(5,10)) + A = self.spcreator(B) + + assert A[1, 2].ndim == 0 + assert A[None, 1, 2:4].shape == (1, 2) + assert A[None, 1, 2, None].shape == (1, 1) + + # see gh-22458 + assert A[None, 1].shape == (1, 10) + assert A[1, None].shape == (1, 10) + assert A[None, 1, :].shape == (1, 10) + assert A[1, None, :].shape == (1, 10) + assert A[1, :, None].shape == (10, 1) + + assert A[None, 1:3, 2].shape == B[None, 1:3, 2].shape == (1, 2) + assert A[1:3, None, 2].shape == B[1:3, None, 2].shape == (2, 1) + assert A[1:3, 2, None].shape == B[1:3, 2, None].shape == (2, 1) + assert A[None, 1, 2:4].shape == B[None, 1, 2:4].shape == (1, 2) + assert A[1, None, 2:4].shape == B[1, None, 2:4].shape == (1, 2) + assert A[1, 2:4, None].shape == B[1, 2:4, None].shape == (2, 1) + + # different for spmatrix + if self.is_array_test: + assert A[1:3, 2].shape == B[1:3, 2].shape == (2,) + assert A[1, 2:4].shape == B[1, 2:4].shape == (2,) + assert A[None, 1, 2].shape == B[None, 1, 2].shape == (1,) + assert A[1, None, 2].shape == B[1, None, 2].shape == (1,) + assert A[1, 2, None].shape == B[1, 2, None].shape == (1,) + else: + assert A[1, 2:4].shape == B[1, 2:4].shape == (1, 2) + assert A[1:3, 2].shape == B[1:3, 2].shape == (2, 1) + assert A[None, 1, 2].shape == B[None, 1, 2].shape == (1, 1) + assert A[1, None, 2].shape == B[1, None, 2].shape == (1, 1) + assert A[1, 2, None].shape == B[1, 2, None].shape == (1, 1) + + def test_ellipsis_slicing(self): + b = self.asdense(arange(50).reshape(5,10)) + a = self.spcreator(b) + + assert_array_equal(a[...].toarray(), b[...]) + assert_array_equal(a[...,].toarray(), b[...,]) + + assert_array_equal(a[4, ...].toarray(), b[4, ...]) + assert_array_equal(a[..., 4].toarray(), b[..., 4]) + assert_array_equal(a[..., 5].toarray(), b[..., 5]) + with pytest.raises(IndexError, match='index .5. out of range'): + a[5, ...] + with pytest.raises(IndexError, match='index .10. out of range'): + a[..., 10] + with pytest.raises(IndexError, match='index .5. out of range'): + a.T[..., 5] + + assert_array_equal(a[1:, ...].toarray(), b[1:, ...]) + assert_array_equal(a[..., 1:].toarray(), b[..., 1:]) + assert_array_equal(a[:2, ...].toarray(), b[:2, ...]) + assert_array_equal(a[..., :2].toarray(), b[..., :2]) + + # check slice limit outside range + assert_array_equal(a[:5, ...].toarray(), b[:5, ...]) + assert_array_equal(a[..., :5].toarray(), b[..., :5]) + assert_array_equal(a[5:, ...].toarray(), b[5:, ...]) + assert_array_equal(a[..., 5:].toarray(), b[..., 5:]) + assert_array_equal(a[10:, ...].toarray(), b[10:, ...]) + assert_array_equal(a[..., 10:].toarray(), b[..., 10:]) + + # ellipsis should be ignored + assert_array_equal(a[1:, 1, ...].toarray(), b[1:, 1, ...]) + assert_array_equal(a[1, ..., 1:].toarray(), b[1, ..., 1:]) + assert_array_equal(a[..., 1, 1:].toarray(), b[1, ..., 1:]) + assert_array_equal(a[:2, 1, ...].toarray(), b[:2, 1, ...]) + assert_array_equal(a[1, ..., :2].toarray(), b[1, ..., :2]) + assert_array_equal(a[..., 1, :2].toarray(), b[1, ..., :2]) + # These return ints + assert_equal(a[1, 1, ...], b[1, 1, ...]) + assert_equal(a[1, ..., 1], b[1, ..., 1]) + + def test_ellipsis_fancy_bool(self): + numpy_a = self.asdense(arange(50).reshape(5, 10)) + a = self.spcreator(numpy_a) + + ix5 = [True, False, True, False, True] + ix10 = [False] * 5 + ix5 # same number of True values as ix5 + ix10_6True = ix5 + ix5 # not same number of True values as ix5 + full_ix = [ix10] * 5 + + assert_array_equal(toarray(a[full_ix, ...]), numpy_a[full_ix, ...]) + assert_array_equal(toarray(a[..., full_ix]), numpy_a[..., full_ix]) + + assert_array_equal(toarray(a[ix5, ...]), numpy_a[ix5, ...]) + assert_array_equal(toarray(a[..., ix10]), numpy_a[..., ix10]) + + assert_array_equal(toarray(a[ix5, ..., ix10]), numpy_a[ix5, ..., ix10]) + assert_array_equal(toarray(a[..., ix5, ix10]), numpy_a[..., ix5, ix10]) + assert_array_equal(toarray(a[ix5, ix10, ...]), numpy_a[ix5, ix10, ...]) + + with assert_raises(IndexError, match="shape mismatch"): + a[ix5, ix10_6True] + + def test_ellipsis_fancy_slicing(self): + b = self.asdense(arange(50).reshape(5, 10)) + a = self.spcreator(b) + + assert_array_equal(a[[4], ...].toarray(), b[[4], ...]) + assert_array_equal(a[[2, 4], ...].toarray(), b[[2, 4], ...]) + assert_array_equal(a[..., [4]].toarray(), b[..., [4]]) + assert_array_equal(a[..., [2, 4]].toarray(), b[..., [2, 4]]) + + assert_array_equal(a[[4], 1, ...].toarray(), b[[4], 1, ...]) + assert_array_equal(a[[2, 4], 1, ...].toarray(), b[[2, 4], 1, ...]) + assert_array_equal(a[[4], ..., 1].toarray(), b[[4], ..., 1]) + assert_array_equal(a[..., [4], 1].toarray(), b[..., [4], 1]) + # fancy index gives dense + assert_array_equal(toarray(a[[2, 4], ..., [2, 4]]), b[[2, 4], ..., [2, 4]]) + assert_array_equal(toarray(a[..., [2, 4], [2, 4]]), b[..., [2, 4], [2, 4]]) + + def test_multiple_ellipsis_slicing(self): + a = self.spcreator(arange(6).reshape(3, 2)) + + with pytest.raises(IndexError, + match='an index can only have a single ellipsis'): + a[..., ...] + with pytest.raises(IndexError, + match='an index can only have a single ellipsis'): + a[..., 1, ...] + + +class _TestSlicingAssign: + def test_slice_scalar_assign(self): + A = self.spcreator((5, 5)) + B = np.zeros((5, 5)) + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", WMSG, SparseEfficiencyWarning) + for C in [A, B]: + C[0:1,1] = 1 + C[3:0,0] = 4 + C[3:4,0] = 9 + C[0,4:] = 1 + C[3::-1,4:] = 9 + assert_array_equal(A.toarray(), B) + + def test_slice_assign_2(self): + n, m = (5, 10) + + def _test_set(i, j): + msg = f"i={i!r}; j={j!r}" + A = self.spcreator((n, m)) + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", WMSG, SparseEfficiencyWarning) + A[i, j] = 1 + B = np.zeros((n, m)) + B[i, j] = 1 + assert_array_almost_equal(A.toarray(), B, err_msg=msg) + # [i,1:2] + for i, j in [(2, slice(3)), (2, slice(None, 10, 4)), (2, slice(5, -2)), + (array(2), slice(5, -2))]: + _test_set(i, j) + + def test_self_self_assignment(self): + # Tests whether a row of one sparse array can be assigned to another. + B = self.spcreator((4,3)) + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", WMSG, SparseEfficiencyWarning) + B[0,0] = 2 + B[1,2] = 7 + B[2,1] = 3 + B[3,0] = 10 + + A = B / 10 + B[0,:] = A[0,:] + assert_array_equal(A[0,:].toarray(), B[0,:].toarray()) + + A = B / 10 + B[:,:] = A[:1,:1] + assert_array_equal(np.zeros((4,3)) + A[0,0], B.toarray()) + + A = B / 10 + B[:-1,0] = A[None,0,:].T + assert_array_equal(A[0,:].toarray().T, B[:-1,0].toarray()) + + def test_slice_assignment(self): + B = self.spcreator((4,3)) + expected = array([[10,0,0], + [0,0,6], + [0,14,0], + [0,0,0]]) + block = [[1,0],[0,4]] + + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", WMSG, SparseEfficiencyWarning) + B[0,0] = 5 + B[1,2] = 3 + B[2,1] = 7 + B[:,:] = B+B + assert_array_equal(B.toarray(), expected) + + B[:2,:2] = self.csc_container(array(block)) + assert_array_equal(B.toarray()[:2, :2], block) + + def test_sparsity_modifying_assignment(self): + B = self.spcreator((4,3)) + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", WMSG, SparseEfficiencyWarning) + B[0,0] = 5 + B[1,2] = 3 + B[2,1] = 7 + B[3,0] = 10 + B[:3] = self.csr_container(np.eye(3)) + + expected = array([[1,0,0],[0,1,0],[0,0,1],[10,0,0]]) + assert_array_equal(B.toarray(), expected) + + def test_set_slice(self): + A = self.spcreator((5,10)) + B = array(zeros((5, 10), float)) + s_ = np.s_ + slices = [s_[:2], s_[1:2], s_[3:], s_[3::2], + s_[8:3:-1], s_[4::-2], s_[:5:-1], + 0, 1, s_[:], s_[1:5], -1, -2, -5, + array(-1), np.int8(-3)] + + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", WMSG, SparseEfficiencyWarning) + for j, a in enumerate(slices): + A[a] = j + B[a] = j + assert_array_equal(A.toarray(), B, repr(a)) + + for i, a in enumerate(slices): + for j, b in enumerate(slices): + A[a,b] = 10*i + 1000*(j+1) + B[a,b] = 10*i + 1000*(j+1) + assert_array_equal(A.toarray(), B, repr((a, b))) + + A[0, 1:10:2] = range(1, 10, 2) + B[0, 1:10:2] = range(1, 10, 2) + assert_array_equal(A.toarray(), B) + A[1:5:2, 0] = np.arange(1, 5, 2)[:, None] + B[1:5:2, 0] = np.arange(1, 5, 2)[:] + assert_array_equal(A.toarray(), B) + + # The next commands should raise exceptions + assert_raises(ValueError, A.__setitem__, (0, 0), list(range(100))) + assert_raises(ValueError, A.__setitem__, (0, 0), arange(100)) + assert_raises(ValueError, A.__setitem__, (0, slice(None)), + list(range(100))) + assert_raises(ValueError, A.__setitem__, (slice(None), 1), + list(range(100))) + assert_raises(ValueError, A.__setitem__, (slice(None), 1), A.copy()) + assert_raises(ValueError, A.__setitem__, + ([[1, 2, 3], [0, 3, 4]], [1, 2, 3]), [1, 2, 3, 4]) + assert_raises(IndexError, A.__setitem__, + ([[1, 2, 3], [0, 3, 4], [4, 1, 3]], + [[1, 2, 4], [0, 1, 3]]), [2, 3, 4]) + assert_raises(ValueError, A.__setitem__, (slice(4), 0), + [[1, 2], [3, 4]]) + + def test_assign_empty(self): + A = self.spcreator(np.ones((2, 3))) + B = self.spcreator((1, 2)) + # Note: This is not like NumPy!! Incoming shape needs to be (2,) for NumPy + # we are more lenient to accommodate vectors in 2d format as input + A[1, :2] = B + assert_array_equal(A.toarray(), [[1, 1, 1], [0, 0, 1]]) + + def test_assign_1d_slice(self): + A = self.spcreator(np.ones((3, 3))) + x = np.zeros(3) + A[:, 0] = x + A[1, :] = x + assert_array_equal(A.toarray(), [[0, 1, 1], [0, 0, 0], [0, 1, 1]]) + + +class _TestFancyIndexing: + """Tests fancy indexing features. The tests for any matrix formats + that implement these features should derive from this class. + """ + + def test_dtype_preservation_empty_index(self): + # This should be parametrized with pytest, but something in the parent + # class creation used in this file breaks pytest.mark.parametrize. + for dt in [np.int16, np.int32, np.float32, np.float64]: + A = self.spcreator((3, 2), dtype=dt) + assert_equal(A[:, [False, False]].dtype, dt) + assert_equal(A[[False, False, False], :].dtype, dt) + assert_equal(A[:, []].dtype, dt) + assert_equal(A[[], :].dtype, dt) + + def test_bad_index(self): + A = self.spcreator(np.zeros([5, 5])) + assert_raises(IndexError, A.__getitem__, "foo").match('Index dimension') + assert_raises(IndexError, A.__getitem__, (2, "foo")).match('Index dimension') + idx = ([1, 2, 3], [1, 2, 3, 4]) + assert_raises(IndexError, A.__getitem__, idx).match('shape mismatch') + + def test_fancy_indexing(self): + B = self.asdense(arange(50).reshape(5,10)) + A = self.spcreator(B) + + # [i] + assert_equal(A[[3]].toarray(), B[[3]]) + assert_equal(A[[1, 3]].toarray(), B[[1, 3]]) + + # [i,[1,2]] + assert_equal(A[3, [3]].toarray(), B[3, [3]]) + assert_equal(A[3, [1, 3]].toarray(), B[3, [1, 3]]) + assert_equal(A[-1, [2, -5]].toarray(), B[-1, [2, -5]]) + assert_equal(A[array(-1), [2, -5]].toarray(), B[-1, [2, -5]]) + assert_equal(A[-1, array([2, -5])].toarray(), B[-1, [2, -5]]) + assert_equal(A[array(-1), array([2, -5])].toarray(), B[-1, [2, -5]]) + + # [1:2,[1,2]] + assert_equal(A[:, [2, 8, 3, -1]].toarray(), B[:, [2, 8, 3, -1]]) + assert_equal(A[3:4, [9]].toarray(), B[3:4, [9]]) + assert_equal(A[1:4, [-1, -5]].toarray(), B[1:4, [-1, -5]]) + assert_equal(A[1:4, array([-1, -5])].toarray(), B[1:4, [-1, -5]]) + + # [[1,2],j] + assert_equal(A[[3], 3].toarray(), B[[3], 3]) + assert_equal(A[[1, 3], 3].toarray(), B[[1, 3], 3]) + assert_equal(A[[2, -5], -4].toarray(), B[[2, -5], -4]) + assert_equal(A[array([2, -5]), -4].toarray(), B[[2, -5], -4]) + assert_equal(A[[2, -5], array(-4)].toarray(), B[[2, -5], -4]) + assert_equal(A[array([2, -5]), array(-4)].toarray(), B[[2, -5], -4]) + + # [[1,2],1:2] + assert_equal(A[[3], :].toarray(), B[[3], :]) + assert_equal(A[[1, 3], :].toarray(), B[[1, 3], :]) + assert_equal(A[[2, -5], 8:-1].toarray(), B[[2, -5], 8:-1]) + assert_equal(A[array([2, -5]), 8:-1].toarray(), B[[2, -5], 8:-1]) + + # [[1,2],[1,2]] + assert_equal(toarray(A[[3], [4]]), B[[3], [4]]) + assert_equal(toarray(A[[1, 3], [2, 4]]), B[[1, 3], [2, 4]]) + assert_equal(toarray(A[[-1, -3], [2, -4]]), B[[-1, -3], [2, -4]]) + assert_equal( + toarray(A[array([-1, -3]), [2, -4]]), B[[-1, -3], [2, -4]] + ) + assert_equal( + toarray(A[[-1, -3], array([2, -4])]), B[[-1, -3], [2, -4]] + ) + assert_equal( + toarray(A[array([-1, -3]), array([2, -4])]), B[[-1, -3], [2, -4]] + ) + + # [[[1],[2]],[1,2]] + assert_equal(A[[[1], [3]], [2, 4]].toarray(), B[[[1], [3]], [2, 4]]) + assert_equal( + A[[[-1], [-3], [-2]], [2, -4]].toarray(), + B[[[-1], [-3], [-2]], [2, -4]] + ) + assert_equal( + A[array([[-1], [-3], [-2]]), [2, -4]].toarray(), + B[[[-1], [-3], [-2]], [2, -4]] + ) + assert_equal( + A[[[-1], [-3], [-2]], array([2, -4])].toarray(), + B[[[-1], [-3], [-2]], [2, -4]] + ) + assert_equal( + A[array([[-1], [-3], [-2]]), array([2, -4])].toarray(), + B[[[-1], [-3], [-2]], [2, -4]] + ) + + # [[1,2]] + assert_equal(A[[1, 3]].toarray(), B[[1, 3]]) + assert_equal(A[[-1, -3]].toarray(), B[[-1, -3]]) + assert_equal(A[array([-1, -3])].toarray(), B[[-1, -3]]) + + # [[1,2],:][:,[1,2]] + assert_equal(A[[3], :][:, [4]].toarray(), B[[3], :][:, [4]]) + assert_equal( + A[[1, 3], :][:, [2, 4]].toarray(), B[[1, 3], :][:, [2, 4]] + ) + assert_equal( + A[[-1, -3], :][:, [2, -4]].toarray(), B[[-1, -3], :][:, [2, -4]] + ) + assert_equal( + A[array([-1, -3]), :][:, array([2, -4])].toarray(), + B[[-1, -3], :][:, [2, -4]] + ) + + assert_equal( + A[1, [[1, 3]]][[[0, 0]], 1].toarray(), B[1, [[1, 3]]][[[0, 0]], 1] + ) + assert_equal( + A[1, [[-1, -3]]][[[0, -1]], 1].toarray(), B[1, [[-1, -3]]][[[0, -1]], 1] + ) + # leads to 3D result + if A.format == "coo": + assert_equal(A[:1, [[1, 3]]].toarray(), B[:1, [[1, 3]]]) + assert_equal(A[[[0, 0]], :1].toarray(), B[[[0, 0]], :1]) + else: + with pytest.raises(IndexError, match="Only 1D or 2D|>2D is not supported"): + A[:1, [[1, 3]]] + with pytest.raises(IndexError, match="Only 1D or 2D|>2D is not supported"): + A[[[0, 0]], :1] + + + assert_equal( + A[:, [1, 3]][[2, 4], :].toarray(), B[:, [1, 3]][[2, 4], :] + ) + assert_equal( + A[:, [-1, -3]][[2, -4], :].toarray(), B[:, [-1, -3]][[2, -4], :] + ) + assert_equal( + A[:, array([-1, -3])][array([2, -4]), :].toarray(), + B[:, [-1, -3]][[2, -4], :] + ) + + # Check bug reported by Robert Cimrman: + # http://thread.gmane.org/gmane.comp.python.scientific.devel/7986 (dead link) + s = slice(int8(2),int8(4),None) + assert_equal(A[s, :].toarray(), B[2:4, :]) + assert_equal(A[:, s].toarray(), B[:, 2:4]) + + # Regression for gh-4917: index with tuple of 2D arrays + i = np.array([[1]], dtype=int) + assert_equal(A[i, i].toarray(), B[i, i]) + + # Regression for gh-4917: index with tuple of empty nested lists + assert_equal(A[[[]], [[]]].toarray(), B[[[]], [[]]]) + + def test_fancy_indexing_randomized(self): + np.random.seed(1234) # make runs repeatable + + NUM_SAMPLES = 50 + M = 6 + N = 4 + + D = self.asdense(np.random.rand(M,N)) + D = np.multiply(D, D > 0.5) + + I = np.random.randint(-M + 1, M, size=NUM_SAMPLES) + J = np.random.randint(-N + 1, N, size=NUM_SAMPLES) + + S = self.spcreator(D) + + SIJ = S[I,J] + if issparse(SIJ): + SIJ = SIJ.toarray() + assert_equal(SIJ, D[I,J]) + + I_bad = I + M + J_bad = J - N + + assert_raises(IndexError, S.__getitem__, (I_bad,J)) + assert_raises(IndexError, S.__getitem__, (I,J_bad)) + + def test_missized_masking(self): + M, N = 5, 10 + + B = self.asdense(arange(M * N).reshape(M, N)) + A = self.spcreator(B) + + # Content of mask shouldn't matter, only its size + row_long = np.ones(M + 1, dtype=bool) + row_short = np.ones(M - 1, dtype=bool) + col_long = np.ones(N + 2, dtype=bool) + col_short = np.ones(N - 2, dtype=bool) + + match="bool index .* has shape .* instead of .*" + for i, j in itertools.product( + (row_long, row_short, slice(None)), + (col_long, col_short, slice(None)), + ): + if isinstance(i, slice) and isinstance(j, slice): + continue + with pytest.raises(IndexError, match=match): + _ = A[i, j] + + def test_fancy_indexing_boolean(self): + np.random.seed(1234) # make runs repeatable + + B = self.asdense(arange(50).reshape(5,10)) + A = self.spcreator(B) + + I = np.array(np.random.randint(0, 2, size=5), dtype=bool) + J = np.array(np.random.randint(0, 2, size=10), dtype=bool) + X = np.array(np.random.randint(0, 2, size=(5, 10)), dtype=bool) + + assert_equal(toarray(A[I]), B[I]) + assert_equal(toarray(A[:, J]), B[:, J]) + assert_equal(toarray(A[X]), B[X]) + assert_equal(toarray(A[B > 9]), B[B > 9]) + + I = np.array([True, False, True, True, False]) + J = np.array([False, True, True, False, True, + False, False, False, False, False]) + + assert_equal(toarray(A[I, J]), B[I, J]) + + Z1 = np.zeros((6, 11), dtype=bool) + Z2 = np.zeros((6, 11), dtype=bool) + Z2[0,-1] = True + Z3 = np.zeros((6, 11), dtype=bool) + Z3[-1,0] = True + + assert_raises(IndexError, A.__getitem__, Z1) + assert_raises(IndexError, A.__getitem__, Z2) + assert_raises(IndexError, A.__getitem__, Z3) + assert_raises((IndexError, ValueError), A.__getitem__, (X, 1)) + + def test_fancy_indexing_sparse_boolean(self): + np.random.seed(1234) # make runs repeatable + + B = self.asdense(arange(50).reshape(5,10)) + A = self.spcreator(B) + + X = np.array(np.random.randint(0, 2, size=(5, 10)), dtype=bool) + + Xsp = self.csr_container(X) + + assert_equal(toarray(A[Xsp]), B[X]) + assert_equal(toarray(A[A > 9]), B[B > 9]) + + Z = np.array(np.random.randint(0, 2, size=(5, 11)), dtype=bool) + Y = np.array(np.random.randint(0, 2, size=(6, 10)), dtype=bool) + + Zsp = self.csr_container(Z) + Ysp = self.csr_container(Y) + + assert_raises(IndexError, A.__getitem__, Zsp) + assert_raises(IndexError, A.__getitem__, Ysp) + assert_raises((IndexError, ValueError), A.__getitem__, (Xsp, 1)) + + def test_fancy_indexing_regression_3087(self): + mat = self.spcreator(array([[1, 0, 0], [0,1,0], [1,0,0]])) + desired_cols = np.ravel(mat.sum(0)) > 0 + assert_equal(mat[:, desired_cols].toarray(), [[1, 0], [0, 1], [1, 0]]) + + def test_fancy_indexing_seq_assign(self): + mat = self.spcreator(array([[1, 0], [0, 1]])) + assert_raises(ValueError, mat.__setitem__, (0, 0), np.array([1,2])) + + def test_fancy_indexing_2d_assign(self): + # regression test for gh-10695 + mat = self.spcreator(array([[1, 0], [2, 3]])) + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", WMSG, SparseEfficiencyWarning) + mat[[0, 1], [1, 1]] = mat[[1, 0], [0, 0]] + assert_equal(toarray(mat), array([[1, 2], [2, 1]])) + + def test_fancy_indexing_empty(self): + B = self.asdense(arange(50).reshape(5,10)) + B[1,:] = 0 + B[:,2] = 0 + B[3,6] = 0 + A = self.spcreator(B) + + K = np.array([False, False, False, False, False]) + assert_equal(toarray(A[K]), B[K]) + K = np.array([], dtype=int) + assert_equal(toarray(A[K]), B[K]) + assert_equal(toarray(A[K, K]), B[K, K]) + J = np.array([0, 1, 2, 3, 4], dtype=int)[:,None] + assert_equal(toarray(A[K, J]), B[K, J]) + assert_equal(toarray(A[J, K]), B[J, K]) + + +@contextlib.contextmanager +def check_remains_sorted(X): + """Checks that sorted indices property is retained through an operation + """ + if not hasattr(X, 'has_sorted_indices') or not X.has_sorted_indices: + yield + return + yield + indices = X.indices.copy() + X.has_sorted_indices = False + X.sort_indices() + assert_array_equal(indices, X.indices, + 'Expected sorted indices, found unsorted') + + +class _TestFancyIndexingAssign: + def test_bad_index_assign(self): + A = self.spcreator(np.zeros([5, 5])) + assert_raises((IndexError, ValueError, TypeError), A.__setitem__, "foo", 2) + assert_raises((IndexError, ValueError, TypeError), A.__setitem__, (2, "foo"), 5) + + def test_fancy_indexing_set(self): + n, m = (5, 10) + + def _test_set_slice(i, j): + A = self.spcreator((n, m)) + B = self.asdense(np.zeros((n, m))) + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", WMSG, SparseEfficiencyWarning) + B[i, j] = 1 + with check_remains_sorted(A): + A[i, j] = 1 + assert_array_almost_equal(A.toarray(), B) + # [1:2,1:2] + for i, j in [((2, 3, 4), slice(None, 10, 4)), + (np.arange(3), slice(5, -2)), + (slice(2, 5), slice(5, -2))]: + _test_set_slice(i, j) + for i, j in [(np.arange(3), np.arange(3)), ((0, 3, 4), (1, 2, 4))]: + _test_set_slice(i, j) + + def test_fancy_assignment_dtypes(self): + def check(dtype): + A = self.spcreator((5, 5), dtype=dtype) + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", WMSG, SparseEfficiencyWarning) + A[[0,1],[0,1]] = dtype.type(1) + assert_equal(A.sum(), dtype.type(1)*2) + A[0:2,0:2] = dtype.type(1.0) + assert_equal(A.sum(), dtype.type(1)*4) + A[2,2] = dtype.type(1.0) + assert_equal(A.sum(), dtype.type(1)*4 + dtype.type(1)) + + for dtype in supported_dtypes: + check(np.dtype(dtype)) + + def test_sequence_assignment(self): + A = self.spcreator((4,3)) + B = self.spcreator(eye(3,4)) + + i0 = [0,1,2] + i1 = (0,1,2) + i2 = array(i0) + + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", WMSG, SparseEfficiencyWarning) + with check_remains_sorted(A): + A[0,i0] = B[i0,0].T + A[1,i1] = B[i1,1].T + A[2,i2] = B[i2,2].T + assert_array_equal(A.toarray(), B.T.toarray()) + + # column slice + A = self.spcreator((2,3)) + with check_remains_sorted(A): + A[1,1:3] = [10,20] + assert_array_equal(A.toarray(), [[0, 0, 0], [0, 10, 20]]) + + # row slice + A = self.spcreator((3,2)) + with check_remains_sorted(A): + A[1:3,1] = [[10],[20]] + assert_array_equal(A.toarray(), [[0, 0], [0, 10], [0, 20]]) + + # both slices + A = self.spcreator((3,3)) + B = self.asdense(np.zeros((3,3))) + with check_remains_sorted(A): + for C in [A, B]: + C[[0,1,2], [0,1,2]] = [4,5,6] + assert_array_equal(A.toarray(), B) + + # both slices (2) + A = self.spcreator((4, 3)) + with check_remains_sorted(A): + A[(1, 2, 3), (0, 1, 2)] = [1, 2, 3] + assert_almost_equal(A.sum(), 6) + B = self.asdense(np.zeros((4, 3))) + B[(1, 2, 3), (0, 1, 2)] = [1, 2, 3] + assert_array_equal(A.toarray(), B) + + def test_fancy_assign_empty(self): + B = self.asdense(arange(50).reshape(5,10)) + B[1,:] = 0 + B[:,2] = 0 + B[3,6] = 0 + A = self.spcreator(B) + + K = np.array([False, False, False, False, False]) + A[K] = 42 + assert_equal(toarray(A), B) + + K = np.array([], dtype=int) + A[K] = 42 + assert_equal(toarray(A), B) + A[K,K] = 42 + assert_equal(toarray(A), B) + + J = np.array([0, 1, 2, 3, 4], dtype=int)[:,None] + A[K,J] = 42 + assert_equal(toarray(A), B) + A[J,K] = 42 + assert_equal(toarray(A), B) + + +class _TestFancyMultidim: + def test_fancy_indexing_ndarray(self): + IandJ = [ + (np.array([[1], [2], [3]]), np.array([3, 4, 2])), + (np.array([[1], [2], [3]]), np.array([[3, 4, 2]])), + (np.array([[1, 2, 3]]), np.array([[3], [4], [2]])), + (np.array([1, 2, 3]), np.array([[3], [4], [2]])), + (np.array([[1, 2, 3], [3, 4, 2]]), np.array([[5, 6, 3], [2, 3, 1]])), + ] + + np.random.seed(1234) + D = self.asdense(np.random.rand(5, 7)) + S = self.spcreator(D) + + # These inputs generate 3-D outputs + if S.format == 'coo': + IandJ.append( + (np.array([[[1], [2], [3]], [[3], [4], [2]]]), + np.array([[[5], [6], [3]], [[2], [3], [1]]])) + ) + + for I, J in IandJ: + SIJ = S[I, J] + DIJ = D[I, J] + assert_equal(toarray(SIJ), DIJ) + + I_bad = I + 5 + J_bad = J + 7 + + assert_raises(IndexError, S.__getitem__, (I_bad, J)) + assert_raises(IndexError, S.__getitem__, (I, J_bad)) + + if S.format != 'coo': + assert_raises(IndexError, S.__getitem__, ([I, I], slice(None))) + assert_raises(IndexError, S.__getitem__, (slice(None), [J, J])) + else: + assert_equal(S[[I, I], :].toarray(), D[[I, I], :]) + assert_equal(S[:, [J, J]].toarray(), D[:, [J, J]]) + + +class _TestFancyMultidimAssign: + def test_fancy_assign_ndarray(self): + np.random.seed(1234) + + D = self.asdense(np.random.rand(5, 7)) + S = self.spcreator(D) + X = np.random.rand(2, 3) + + I = np.array([[1, 2, 3], [3, 4, 2]]) + J = np.array([[5, 6, 3], [2, 3, 1]]) + + with check_remains_sorted(S): + S[I,J] = X + D[I,J] = X + assert_equal(S.toarray(), D) + + I_bad = I + 5 + J_bad = J + 7 + + C = [1, 2, 3] + + with check_remains_sorted(S): + S[I,J] = C + D[I,J] = C + assert_equal(S.toarray(), D) + + with check_remains_sorted(S): + S[I,J] = 3 + D[I,J] = 3 + assert_equal(S.toarray(), D) + + assert_raises(IndexError, S.__setitem__, (I_bad,J), C) + assert_raises(IndexError, S.__setitem__, (I,J_bad), C) + + def test_fancy_indexing_multidim_set(self): + n, m = (5, 10) + + def _test_set_slice(i, j): + A = self.spcreator((n, m)) + with check_remains_sorted(A), warnings.catch_warnings(): + warnings.filterwarnings("ignore", WMSG, SparseEfficiencyWarning) + A[i, j] = 1 + B = self.asdense(np.zeros((n, m))) + B[i, j] = 1 + assert_array_almost_equal(A.toarray(), B) + # [[[1, 2], [1, 2]], [1, 2]] + for i, j in [(np.array([[1, 2], [1, 3]]), [1, 3]), + (np.array([0, 4]), [[0, 3], [1, 2]]), + ([[1, 2, 3], [0, 2, 4]], [[0, 4, 3], [4, 1, 2]])]: + _test_set_slice(i, j) + + def test_fancy_assign_list(self): + np.random.seed(1234) + + D = self.asdense(np.random.rand(5, 7)) + S = self.spcreator(D) + X = np.random.rand(2, 3) + + I = [[1, 2, 3], [3, 4, 2]] + J = [[5, 6, 3], [2, 3, 1]] + + S[I,J] = X + D[I,J] = X + assert_equal(S.toarray(), D) + + I_bad = [[ii + 5 for ii in i] for i in I] + J_bad = [[jj + 7 for jj in j] for j in J] + C = [1, 2, 3] + + S[I,J] = C + D[I,J] = C + assert_equal(S.toarray(), D) + + S[I,J] = 3 + D[I,J] = 3 + assert_equal(S.toarray(), D) + + assert_raises(IndexError, S.__setitem__, (I_bad,J), C) + assert_raises(IndexError, S.__setitem__, (I,J_bad), C) + + def test_fancy_assign_slice(self): + np.random.seed(1234) + + D = self.asdense(np.random.rand(5, 7)) + S = self.spcreator(D) + + I = [1, 2, 3, 3, 4, 2] + J = [5, 6, 3, 2, 3, 1] + + I_bad = [ii + 5 for ii in I] + J_bad = [jj + 7 for jj in J] + + C1 = [1, 2, 3, 4, 5, 6, 7] + C2 = np.arange(5)[:, None] + assert_raises(IndexError, S.__setitem__, (I_bad, slice(None)), C1) + assert_raises(IndexError, S.__setitem__, (slice(None), J_bad), C2) + + +class _TestArithmetic: + """ + Test real/complex arithmetic + """ + def __arith_init(self): + # these can be represented exactly in FP (so arithmetic should be exact) + __A = array([[-1.5, 6.5, 0, 2.25, 0, 0], + [3.125, -7.875, 0.625, 0, 0, 0], + [0, 0, -0.125, 1.0, 0, 0], + [0, 0, 8.375, 0, 0, 0]], 'float64') + __B = array([[0.375, 0, 0, 0, -5, 2.5], + [14.25, -3.75, 0, 0, -0.125, 0], + [0, 7.25, 0, 0, 0, 0], + [18.5, -0.0625, 0, 0, 0, 0]], 'complex128') + __B.imag = array([[1.25, 0, 0, 0, 6, -3.875], + [2.25, 4.125, 0, 0, 0, 2.75], + [0, 4.125, 0, 0, 0, 0], + [-0.0625, 0, 0, 0, 0, 0]], 'float64') + + # fractions are all x/16ths + assert_array_equal((__A*16).astype('int32'),16*__A) + assert_array_equal((__B.real*16).astype('int32'),16*__B.real) + assert_array_equal((__B.imag*16).astype('int32'),16*__B.imag) + + __Asp = self.spcreator(__A) + __Bsp = self.spcreator(__B) + return __A, __B, __Asp, __Bsp + + @pytest.mark.fail_slow(20) + def test_add_sub(self): + __A, __B, __Asp, __Bsp = self.__arith_init() + + # basic tests + assert_array_equal( + (__Asp + __Bsp).toarray(), __A + __B + ) + + # check conversions + for x in supported_dtypes: + with np.errstate(invalid="ignore"): + A = __A.astype(x) + Asp = self.spcreator(A) + for y in supported_dtypes: + if not np.issubdtype(y, np.complexfloating): + with np.errstate(invalid="ignore"): + B = __B.real.astype(y) + else: + B = __B.astype(y) + Bsp = self.spcreator(B) + + # addition + D1 = A + B + S1 = Asp + Bsp + + assert_equal(S1.dtype,D1.dtype) + assert_array_equal(S1.toarray(), D1) + assert_array_equal(Asp + B,D1) # check sparse + dense + assert_array_equal(A + Bsp,D1) # check dense + sparse + + # subtraction + if np.dtype('bool') in [x, y]: + # boolean array subtraction deprecated in 1.9.0 + continue + + D1 = A - B + S1 = Asp - Bsp + + assert_equal(S1.dtype,D1.dtype) + assert_array_equal(S1.toarray(), D1) + assert_array_equal(Asp - B,D1) # check sparse - dense + assert_array_equal(A - Bsp,D1) # check dense - sparse + + def test_mu(self): + __A, __B, __Asp, __Bsp = self.__arith_init() + + # basic tests + assert_array_equal((__Asp @ __Bsp.T).toarray(), + __A @ __B.T) + + for x in supported_dtypes: + with np.errstate(invalid="ignore"): + A = __A.astype(x) + Asp = self.spcreator(A) + for y in supported_dtypes: + if np.issubdtype(y, np.complexfloating): + B = __B.astype(y) + else: + with np.errstate(invalid="ignore"): + B = __B.real.astype(y) + Bsp = self.spcreator(B) + + D1 = A @ B.T + S1 = Asp @ Bsp.T + + assert_allclose(S1.toarray(), D1, + atol=1e-14*abs(D1).max()) + assert_equal(S1.dtype,D1.dtype) + + +class _TestMinMax: + def test_minmax(self): + for dtype in [np.float32, np.float64, np.int32, np.int64, np.complex128]: + D = np.arange(20, dtype=dtype).reshape(5,4) + + X = self.spcreator(D) + assert_equal(X.min(), 0) + assert_equal(X.max(), 19) + assert_equal(X.min().dtype, dtype) + assert_equal(X.max().dtype, dtype) + + D *= -1 + X = self.spcreator(D) + assert_equal(X.min(), -19) + assert_equal(X.max(), 0) + + D += 5 + X = self.spcreator(D) + assert_equal(X.min(), -14) + assert_equal(X.max(), 5) + + # try a fully dense matrix + X = self.spcreator(np.arange(1, 10).reshape(3, 3)) + assert_equal(X.min(), 1) + assert_equal(X.min().dtype, X.dtype) + assert_equal(X.min(explicit=True), 1) + + X = -X + assert_equal(X.max(), -1) + assert_equal(X.max(explicit=True), -1) + + # and a fully sparse matrix + Z = self.spcreator(np.zeros((1, 1))) + assert_equal(Z.min(), 0) + assert_equal(Z.max(), 0) + assert_equal(Z.max().dtype, Z.dtype) + + # another test + D = np.arange(20, dtype=float).reshape(5,4) + D[0:2, :] = 0 + X = self.spcreator(D) + assert_equal(X.min(), 0) + assert_equal(X.max(), 19) + + # zero-size matrices + for D in [np.zeros((0, 0)), np.zeros((0, 10)), np.zeros((10, 0))]: + X = self.spcreator(D) + assert_raises(ValueError, X.min) + assert_raises(ValueError, X.max) + + def test_minmax_axis(self): + keep = not self.is_array_test + D = np.arange(50).reshape(5, 10) + # completely empty rows, leaving some completely full: + D[1, :] = 0 + # empty at end for reduceat: + D[:, 9] = 0 + # partial rows/cols: + D[3, 3] = 0 + # entries on either side of 0: + D[2, 2] = -1 + X = self.spcreator(D) + + axes_even = [0, -2] + axes_odd = [1, -1] + for axis in axes_odd + axes_even: + assert_array_equal( + X.max(axis=axis).toarray(), D.max(axis=axis, keepdims=keep) + ) + assert_array_equal( + X.min(axis=axis).toarray(), D.min(axis=axis, keepdims=keep) + ) + + for axis in axes_even: + assert_equal( + X.max(axis=axis, explicit=True).toarray(), + self.asdense([40, 41, 42, 43, 44, 45, 46, 47, 48, 0]) + ) + if np.any(X.data == 0): + # Noncanonical case + expected = self.asdense([20, 1, -1, 3, 4, 5, 0, 7, 8, 0]) + else: + expected = self.asdense([20, 1, -1, 3, 4, 5, 6, 7, 8, 0]) + assert_equal(X.min(axis=axis, explicit=True).toarray(), expected) + + for axis in axes_odd: + expected_max = np.array([8, 0, 28, 38, 48]) + expected_min = np.array([1, 0, -1, 30, 40]) + if not self.is_array_test: + expected_max = expected_max.reshape((5, 1)) + expected_min = expected_min.reshape((5, 1)) + assert_equal(X.max(axis=axis, explicit=True).toarray(), expected_max) + assert_equal(X.min(axis=axis, explicit=True).toarray(), expected_min) + + # full matrix + D = np.arange(1, 51).reshape(10, 5) + X = self.spcreator(D) + for axis in axes_odd + axes_even: + assert_array_equal( + X.max(axis=axis).toarray(), D.max(axis=axis, keepdims=keep) + ) + assert_array_equal( + X.min(axis=axis).toarray(), D.min(axis=axis, keepdims=keep) + ) + assert_equal(X.max(axis=(0, 1)), D.max(axis=(0, 1), keepdims=keep)) + + for axis in axes_even: + expected_max = D[-1, :] + expected_min = D[0, :] + if not self.is_array_test: + expected_max = D[None, -1, :] + expected_min = D[None, 0, :] + assert_equal(X.max(axis=axis, explicit=True).toarray(), expected_max) + assert_equal(X.min(axis=axis, explicit=True).toarray(), expected_min) + for axis in axes_odd: + expected_max = D[:, -1] + expected_min = D[:, 0] + if not self.is_array_test: + expected_max = D[:, -1, None] + expected_min = D[:, 0, None] + assert_equal(X.max(axis=axis, explicit=True).toarray(), expected_max) + assert_equal(X.min(axis=axis, explicit=True).toarray(), expected_min) + + # empty matrix + D = self.asdense(np.zeros((10, 5))) + X = self.spcreator(D) + for axis in axes_even + axes_odd: + assert_equal(X.max(axis=axis, explicit=True).toarray(), D.max(axis=axis)) + assert_equal(X.min(axis=axis, explicit=True).toarray(), D.min(axis=axis)) + + # zero-size matrices + D = self.asdense(np.zeros((0, 10))) + X = self.spcreator(D) + explicit_values = [True, False] + even_explicit_pairs = list(itertools.product(axes_even, explicit_values)) + odd_explicit_pairs = list(itertools.product(axes_odd, explicit_values)) + for axis, ex in even_explicit_pairs: + assert_raises(ValueError, X.min, axis=axis, explicit=ex) + assert_raises(ValueError, X.max, axis=axis, explicit=ex) + for axis, ex in odd_explicit_pairs: + assert_equal(X.max(axis=axis, explicit=ex).toarray(), D.max(axis=axis)) + assert_equal(X.min(axis=axis, explicit=ex).toarray(), D.min(axis=axis)) + + D = self.asdense(np.zeros((10, 0))) + X = self.spcreator(D) + for axis, ex in odd_explicit_pairs: + assert_raises(ValueError, X.min, axis=axis, explicit=ex) + assert_raises(ValueError, X.max, axis=axis, explicit=ex) + for axis, ex in even_explicit_pairs: + assert_equal(X.max(axis=axis, explicit=ex).toarray(), D.max(axis=axis)) + assert_equal(X.min(axis=axis, explicit=ex).toarray(), D.min(axis=axis)) + + def test_minmax_container_type(self): + dat = array([[0, 1, 2], + [3, -4, 5], + [-6, 7, 9]]) + datsp = self.spcreator(dat) + matrix_or_array = ndarray if self.is_array_test else np.matrix + spmatrix_or_sparray = sparray if self.is_array_test else spmatrix + + assert isscalarlike(datsp.min()) + assert isinstance(datsp.min(axis=0), spmatrix_or_sparray) + assert isinstance(datsp.min(axis=1), spmatrix_or_sparray) + + assert isscalarlike(datsp.max()) + assert isinstance(datsp.max(axis=0), spmatrix_or_sparray) + assert isinstance(datsp.max(axis=1), spmatrix_or_sparray) + + assert isscalarlike(datsp.nanmin()) + assert isinstance(datsp.nanmin(axis=0), spmatrix_or_sparray) + assert isinstance(datsp.nanmin(axis=1), spmatrix_or_sparray) + + assert isscalarlike(datsp.nanmax()) + assert isinstance(datsp.nanmax(axis=0), spmatrix_or_sparray) + assert isinstance(datsp.nanmax(axis=1), spmatrix_or_sparray) + + assert isscalarlike(datsp.argmin()) + assert isinstance(datsp.argmin(axis=0), matrix_or_array) + assert isinstance(datsp.argmin(axis=1), matrix_or_array) + + assert isscalarlike(datsp.argmax()) + assert isinstance(datsp.argmax(axis=0), matrix_or_array) + assert isinstance(datsp.argmax(axis=1), matrix_or_array) + + def test_nanminmax(self): + D = self.asdense(np.arange(50).reshape(5,10), dtype=float) + D[1, :] = 0 + D[:, 9] = 0 + D[3, 3] = 0 + D[2, 2] = -1 + D[4, 2] = np.nan + D[1, 4] = np.nan + X = self.spcreator(D) + + X_nan_maximum = X.nanmax() + assert np.isscalar(X_nan_maximum) + assert X_nan_maximum == np.nanmax(D) + + X_nan_minimum = X.nanmin() + assert np.isscalar(X_nan_minimum) + assert X_nan_minimum == np.nanmin(D) + + X_nan_minimum = X.nanmin(axis=(0, 1)) + assert np.isscalar(X_nan_minimum) + assert X_nan_minimum == np.nanmin(D, axis=(0, 1)) + + axes = [-2, -1, 0, 1] + for axis in axes: + X_nan_maxima = X.nanmax(axis=axis) + assert_allclose(X_nan_maxima.toarray(), np.nanmax(D, axis=axis)) + assert isinstance(X_nan_maxima, self.coo_container) + + X_nan_minima = X.nanmin(axis=axis) + assert_allclose(X_nan_minima.toarray(), np.nanmin(D, axis=axis)) + assert isinstance(X_nan_minima, self.coo_container) + + def test_minmax_invalid_params(self): + dat = array([[0, 1, 2], + [3, -4, 5], + [-6, 7, 9]]) + datsp = self.spcreator(dat) + + for fname in ('min', 'max'): + datfunc = getattr(dat, fname) + func = getattr(datsp, fname) + assert_raises(ValueError, func, axis=3) + assert_raises(TypeError, func, axis=1.5) + assert_raises(ValueError, func, axis=1, out=1) + assert_equal(func(axis=(0, 1)), datfunc(axis=(0, 1))) + + def test_numpy_minmax(self): + # See gh-5987 + # xref gh-7460 in 'numpy' + from scipy.sparse import _data + + dat = array([[0, 1, 2], + [3, -4, 5], + [-6, 7, 9]]) + datsp = self.spcreator(dat) + + # We are only testing sparse matrices who have + # implemented 'min' and 'max' because they are + # the ones with the compatibility issues with + # the 'numpy' implementation. + if isinstance(datsp, _data._minmax_mixin): + assert_array_equal(np.min(datsp), np.min(dat)) + assert_array_equal(np.max(datsp), np.max(dat)) + + def test_argmax(self): + from scipy.sparse import _data + D1 = np.array([ + [-1, 5, 2, 3], + [0, 0, -1, -2], + [-1, -2, -3, -4], + [1, 2, 3, 4], + [1, 2, 0, 0], + ]) + D2 = D1.transpose() + # Non-regression test cases for gh-16929. + D3 = np.array([[4, 3], [7, 5]]) + D4 = np.array([[4, 3], [7, 0]]) + D5 = np.array([[5, 5, 3], [4, 9, 10], [3, 4, 9]]) + + for D in [D1, D2, D3, D4, D5]: + D = self.asdense(D) + mat = self.spcreator(D) + if not isinstance(mat, _data._minmax_mixin): + continue + + assert_equal(mat.argmax(), np.argmax(D)) + assert_equal(mat.argmin(), np.argmin(D)) + + assert_equal(mat.argmax(axis=0), np.argmax(D, axis=0)) + assert_equal(mat.argmin(axis=0), np.argmin(D, axis=0)) + + assert_equal(mat.argmax(axis=1), np.argmax(D, axis=1)) + assert_equal(mat.argmin(axis=1), np.argmin(D, axis=1)) + + # full matrix with explicit=True + mat = self.spcreator(self.asdense(D5)) + assert_equal(mat.argmax(explicit=True), 5) + assert_equal((-mat).argmax(explicit=True), 2) + assert_equal(mat.argmin(explicit=True), 2) + assert_equal((-mat).argmin(explicit=True), 5) + + # zero-size matrices + D6 = self.spcreator(np.empty((0, 5))) + D7 = self.spcreator(np.empty((5, 0))) + explicits = [True, False] + + for mat, axis, ex in itertools.product([D6, D7], [None, 0, 1], explicits): + if axis is None or mat.shape[axis] == 0: + with pytest.raises(ValueError, match="Cannot apply"): + mat.argmax(axis=axis, explicit=ex) + with pytest.raises(ValueError, match="Cannot apply"): + mat.argmin(axis=axis, explicit=ex) + else: + if self.is_array_test: + expected = np.zeros(0) + else: + expected = np.zeros((0, 1) if axis == 1 else (1, 0)) + assert_equal(mat.argmin(axis=axis, explicit=ex), expected) + assert_equal(mat.argmax(axis=axis, explicit=ex), expected) + + mat = self.spcreator(D1) + assert_equal(mat.argmax(axis=0, explicit=True), self.asdense([3, 0, 3, 3])) + assert_equal(mat.argmin(axis=0, explicit=True), self.asdense([0, 2, 2, 2])) + + expected_max = np.array([1, 2, 0, 3, 1]) + expected_min = np.array([0, 3, 3, 0, 0]) + if mat.nnz != 16: + # Noncanonical case + expected_min[-1] = 2 + if not self.is_array_test: + expected_max = expected_max.reshape((5, 1)) + expected_min = expected_min.reshape((5, 1)) + + assert_equal(mat.argmax(axis=1, explicit=True), expected_max) + assert_equal(asarray(mat.argmin(axis=1, explicit=True)), expected_min) + + # all zeros + D = np.zeros((2, 2)) + mat = self.spcreator(D) + if mat.nnz != 0: + # Noncanonical case + assert_equal(mat.argmin(axis=None, explicit=True), 0) + assert_equal(mat.argmax(axis=None, explicit=True), 0) + else: + # Canonical case + with pytest.raises(ValueError, match="Cannot apply"): + mat.argmin(axis=None, explicit=True) + with pytest.raises(ValueError, match="Cannot apply"): + mat.argmax(axis=None, explicit=True) + + +class _TestGetNnzAxis: + def test_getnnz_axis(self): + dat = array([[0, 2], + [3, 5], + [-6, 9]]) + bool_dat = dat.astype(bool) + datsp = self.spcreator(dat) + + accepted_return_dtypes = (np.int32, np.int64) + + getnnz = datsp.count_nonzero if self.is_array_test else datsp.getnnz + assert_array_equal(bool_dat.sum(axis=None), getnnz(axis=None)) + assert_array_equal(bool_dat.sum(), getnnz()) + assert_array_equal(bool_dat.sum(axis=0), getnnz(axis=0)) + assert_in(getnnz(axis=0).dtype, accepted_return_dtypes) + assert_array_equal(bool_dat.sum(axis=1), getnnz(axis=1)) + assert_in(getnnz(axis=1).dtype, accepted_return_dtypes) + assert_array_equal(bool_dat.sum(axis=-2), getnnz(axis=-2)) + assert_in(getnnz(axis=-2).dtype, accepted_return_dtypes) + assert_array_equal(bool_dat.sum(axis=-1), getnnz(axis=-1)) + assert_in(getnnz(axis=-1).dtype, accepted_return_dtypes) + + assert_raises(ValueError, getnnz, axis=2) + + +#------------------------------------------------------------------------------ +# Tailored base class for generic tests +#------------------------------------------------------------------------------ + +def _possibly_unimplemented(cls, require=True): + """ + Construct a class that either runs tests as usual (require=True), + or each method skips if it encounters a common error. + """ + if require: + return cls + else: + def wrap(fc): + @functools.wraps(fc) + def wrapper(*a, **kw): + try: + return fc(*a, **kw) + except (NotImplementedError, TypeError, ValueError, + IndexError, AttributeError): + raise pytest.skip("feature not implemented") + + return wrapper + + new_dict = dict(cls.__dict__) + for name, func in cls.__dict__.items(): + if name.startswith('test_'): + new_dict[name] = wrap(func) + return type(cls.__name__ + "NotImplemented", + cls.__bases__, + new_dict) + + +def sparse_test_class(getset=True, slicing=True, slicing_assign=True, + fancy_indexing=True, fancy_assign=True, + fancy_multidim_indexing=True, fancy_multidim_assign=True, + minmax=True, nnz_axis=True): + """ + Construct a base class, optionally converting some of the tests in + the suite to check that the feature is not implemented. + """ + bases = (_TestCommon, + _possibly_unimplemented(_TestGetSet, getset), + _TestSolve, + _TestInplaceArithmetic, + _TestArithmetic, + _possibly_unimplemented(_TestSlicing, slicing), + _possibly_unimplemented(_TestSlicingAssign, slicing_assign), + _possibly_unimplemented(_TestFancyIndexing, fancy_indexing), + _possibly_unimplemented(_TestFancyIndexingAssign, + fancy_assign), + _possibly_unimplemented(_TestFancyMultidim, + fancy_indexing and fancy_multidim_indexing), + _possibly_unimplemented(_TestFancyMultidimAssign, + fancy_multidim_assign and fancy_assign), + _possibly_unimplemented(_TestMinMax, minmax), + _possibly_unimplemented(_TestGetNnzAxis, nnz_axis)) + + # check that test names do not clash + names = {} + for cls in bases: + for name in cls.__dict__: + if not name.startswith('test_'): + continue + old_cls = names.get(name) + if old_cls is not None: + raise ValueError(f"Test class {cls.__name__} overloads test " + f"{name} defined in {old_cls.__name__}") + names[name] = cls + + return type("TestBase", bases, {}) + + +#------------------------------------------------------------------------------ +# Matrix class based tests +#------------------------------------------------------------------------------ + +class _CompressedMixin: + def _test_setdiag_sorted(self, D): + A = self.spcreator(D) + # Force sorted indices + A.has_sorted_indices = False + A.sort_indices() + assert A.has_sorted_indices + # Set the diagonal (only 1 new entry / 1002, so _insert_many is used) + with check_remains_sorted(A): + A.setdiag(5) + assert np.all(A.diagonal() == 5) + + def test_setdiag_noconvert(self): + # Test small ratio of new elements + # see gh-21791 setting mixture of existing and not when new_values < 0.001*nnz + # see gh-23644 + # Create off-main-diagonal elements so that we have multiple elements + # per column to see if the indices are sorted or not + N = 1002 + vals = np.arange(1, N + 1) + diags = np.c_[[-1, 2, 1]] * vals # rows are diagonal entries + # Remove a small number of diagonal elements so we have a small ratio + # of new ones to force _cs_matrix._setdiag to remain in CSC/CSR format + N_new = 3 + diags[1, -N_new:] = 0.0 + offsets = [-1, 0, 1] + D = self.dia_container((diags, offsets), shape=(N, N)) + return self._test_setdiag_sorted(D) + + def test_setdiag_cooconvert(self): + # Test large ratio of new elements + # see gh-23644 + # Create off-main-diagonal elements so that we have multiple elements + # per column to see if the indices are sorted or not + N = 1002 + vals = np.arange(1, N + 1) # only a few non-zeros + diags = np.c_[[-1, 2, 1]] * vals + # Remove many entries so we have a large ratio of new entries + diags[1, 5:] = 0.0 + offsets = [-1, 0, 1] + D = self.dia_container((diags, offsets), shape=(N, N)) + return self._test_setdiag_sorted(D) + + +class TestCSR(_CompressedMixin, sparse_test_class()): + @classmethod + def spcreator(cls, *args, **kwargs): + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", WMSG, SparseEfficiencyWarning) + return csr_array(*args, **kwargs) + math_dtypes = [np.bool_, np.int_, np.float64, np.complex128] + + def test_constructor1(self): + b = array([[0, 4, 0], + [3, 0, 0], + [0, 2, 0]], 'd') + bsp = self.csr_container(b) + assert_array_almost_equal(bsp.data,[4,3,2]) + assert_array_equal(bsp.indices,[1,0,1]) + assert_array_equal(bsp.indptr,[0,1,2,3]) + assert_equal(bsp.nnz,3) + assert_equal(bsp.format,'csr') + assert_array_equal(bsp.toarray(), b) + + def test_constructor2(self): + b = zeros((6,6),'d') + b[3,4] = 5 + bsp = self.csr_container(b) + assert_array_almost_equal(bsp.data,[5]) + assert_array_equal(bsp.indices,[4]) + assert_array_equal(bsp.indptr,[0,0,0,0,1,1,1]) + assert_array_almost_equal(bsp.toarray(), b) + + def test_constructor3(self): + b = array([[1, 0], + [0, 2], + [3, 0]], 'd') + bsp = self.csr_container(b) + assert_array_almost_equal(bsp.data,[1,2,3]) + assert_array_equal(bsp.indices,[0,1,0]) + assert_array_equal(bsp.indptr,[0,1,2,3]) + assert_array_almost_equal(bsp.toarray(), b) + + def test_constructor4(self): + # using (data, ij) format + row = array([2, 3, 1, 3, 0, 1, 3, 0, 2, 1, 2]) + col = array([0, 1, 0, 0, 1, 1, 2, 2, 2, 2, 1]) + data = array([6., 10., 3., 9., 1., 4., + 11., 2., 8., 5., 7.]) + + ij = vstack((row,col)) + csr = self.csr_container((data,ij),(4,3)) + assert_array_equal(arange(12).reshape(4, 3), csr.toarray()) + + # using Python lists and a specified dtype + csr = self.csr_container(([2**63 + 1, 1], ([0, 1], [0, 1])), dtype=np.uint64) + dense = array([[2**63 + 1, 0], [0, 1]], dtype=np.uint64) + assert_array_equal(dense, csr.toarray()) + + # with duplicates (should sum the duplicates) + csr = self.csr_container(([1,1,1,1], ([0,2,2,0], [0,1,1,0]))) + assert csr.nnz == 2 + + def test_constructor5(self): + # infer dimensions from arrays + indptr = array([0,1,3,3]) + indices = array([0,5,1,2]) + data = array([1,2,3,4]) + csr = self.csr_container((data, indices, indptr)) + assert_array_equal(csr.shape,(3,6)) + + def test_constructor6(self): + # infer dimensions and dtype from lists + indptr = [0, 1, 3, 3] + indices = [0, 5, 1, 2] + data = [1, 2, 3, 4] + csr = self.csr_container((data, indices, indptr)) + assert_array_equal(csr.shape, (3,6)) + assert_(np.issubdtype(csr.dtype, np.signedinteger)) + + def test_constructor_smallcol(self): + # int64 indices not required + data = arange(6) + 1 + col = array([1, 2, 1, 0, 0, 2], dtype=np.int64) + ptr = array([0, 2, 4, 6], dtype=np.int64) + + a = self.csr_container((data, col, ptr), shape=(3, 3)) + + b = array([[0, 1, 2], + [4, 3, 0], + [5, 0, 6]], 'd') + + # sparray is less aggressive in downcasting indices to int32 than spmatrix + expected_dtype = np.dtype(np.int64 if self.is_array_test else np.int32) + assert_equal(a.indptr.dtype, expected_dtype) + assert_equal(a.indices.dtype, expected_dtype) + assert_array_equal(a.toarray(), b) + + def test_constructor_largecol(self): + # int64 indices required + data = arange(6) + 1 + large = np.iinfo(np.int32).max + 100 + col = array([0, 1, 2, large, large+1, large+2], dtype=np.int64) + ptr = array([0, 2, 4, 6], dtype=np.int64) + + a = self.csr_container((data, col, ptr)) + + assert_equal(a.indptr.dtype, np.dtype(np.int64)) + assert_equal(a.indices.dtype, np.dtype(np.int64)) + assert_array_equal(a.shape, (3, max(col)+1)) + + def test_sort_indices(self): + data = arange(5) + indices = array([7, 2, 1, 5, 4]) + indptr = array([0, 3, 5]) + asp = self.csr_container((data, indices, indptr), shape=(2,10)) + bsp = asp.copy() + asp.sort_indices() + assert_array_equal(asp.indices,[1, 2, 7, 4, 5]) + assert_array_equal(asp.toarray(), bsp.toarray()) + + def test_eliminate_zeros(self): + data = array([1, 0, 0, 0, 2, 0, 3, 0]) + indices = array([1, 2, 3, 4, 5, 6, 7, 8]) + indptr = array([0, 3, 8]) + asp = self.csr_container((data, indices, indptr), shape=(2,10)) + bsp = asp.copy() + asp.eliminate_zeros() + assert_array_equal(asp.nnz, 3) + assert_array_equal(asp.data,[1, 2, 3]) + assert_array_equal(asp.toarray(), bsp.toarray()) + + def test_ufuncs(self): + X = self.csr_container(np.arange(20).reshape(4, 5) / 20.) + for f in ["sin", "tan", "arcsin", "arctan", "sinh", "tanh", + "arcsinh", "arctanh", "rint", "sign", "expm1", "log1p", + "deg2rad", "rad2deg", "floor", "ceil", "trunc", "sqrt"]: + assert_equal(hasattr(self.datsp, f), True) + X2 = getattr(X, f)() + assert_equal(X.shape, X2.shape) + assert_array_equal(X.indices, X2.indices) + assert_array_equal(X.indptr, X2.indptr) + assert_array_equal(X2.toarray(), getattr(np, f)(X.toarray())) + + def test_unsorted_arithmetic(self): + data = arange(5) + indices = array([7, 2, 1, 5, 4]) + indptr = array([0, 3, 5]) + asp = self.csr_container((data, indices, indptr), shape=(2,10)) + data = arange(6) + indices = array([8, 1, 5, 7, 2, 4]) + indptr = array([0, 2, 6]) + bsp = self.csr_container((data, indices, indptr), shape=(2,10)) + assert_equal((asp + bsp).toarray(), asp.toarray() + bsp.toarray()) + + def test_fancy_indexing_broadcast(self): + # broadcasting indexing mode is supported + I = np.array([[1], [2], [3]]) + J = np.array([3, 4, 2]) + + np.random.seed(1234) + D = self.asdense(np.random.rand(5, 7)) + S = self.spcreator(D) + + SIJ = S[I,J] + if issparse(SIJ): + SIJ = SIJ.toarray() + assert_equal(SIJ, D[I,J]) + + def test_has_sorted_indices(self): + "Ensure has_sorted_indices memoizes sorted state for sort_indices" + sorted_inds = np.array([0, 1]) + unsorted_inds = np.array([1, 0]) + data = np.array([1, 1]) + indptr = np.array([0, 2]) + M = self.csr_container((data, sorted_inds, indptr)).copy() + assert_equal(True, M.has_sorted_indices) + assert isinstance(M.has_sorted_indices, bool) + + M = self.csr_container((data, unsorted_inds, indptr)).copy() + assert_equal(False, M.has_sorted_indices) + + # set by sorting + M.sort_indices() + assert_equal(True, M.has_sorted_indices) + assert_array_equal(M.indices, sorted_inds) + + M = self.csr_container((data, unsorted_inds, indptr)).copy() + # set manually (although underlyingly unsorted) + M.has_sorted_indices = True + assert_equal(True, M.has_sorted_indices) + assert_array_equal(M.indices, unsorted_inds) + + # ensure sort bypassed when has_sorted_indices == True + M.sort_indices() + assert_array_equal(M.indices, unsorted_inds) + + def test_has_canonical_format(self): + "Ensure has_canonical_format memoizes state for sum_duplicates" + + info_no_dups = (np.array([2]), np.array([0]), np.array([0, 1])) + info_with_dups = (np.array([1, 1]), np.array([0, 0]), np.array([0, 2])) + + M = self.csr_container(info_no_dups) + assert_equal(True, M.has_canonical_format) + + M = self.csr_container(info_with_dups).copy() + assert_equal(False, M.has_canonical_format) + assert isinstance(M.has_canonical_format, bool) + + # set flag by deduplicating + M.sum_duplicates() + assert_equal(True, M.has_canonical_format) + assert_equal(1, len(M.indices)) + + # manually set flag True (although underlyingly duplicated) + M = self.csr_container(info_with_dups).copy() + M.has_canonical_format = True + assert_equal(True, M.has_canonical_format) + assert_equal(2, len(M.indices)) # unaffected content + # ensure deduplication bypassed when has_canonical_format == True + M.sum_duplicates() + assert_equal(2, len(M.indices)) # still has duplicates!!!! + # ensure deduplication reenabled when has_canonical_format == False + M.has_canonical_format = False + M.sum_duplicates() + assert_equal(1, len(M.indices)) + assert_equal(True, M.has_canonical_format) + + # manually set flag False (although underlyingly canonical) + M.has_canonical_format = False + assert_equal(False, M.has_canonical_format) + Mcheck = self.csr_container((M.data, M.indices, M.indptr)) + assert_equal(True, Mcheck.has_canonical_format) + # sum_duplicates does not complain when no work to do + M.sum_duplicates() + assert_equal(True, M.has_canonical_format) + + # check assignments maintain canonical format + M = self.csr_container((np.array([2]), np.array([2]), np.array([0, 1, 1, 1]))) + assert_equal(M.shape, (3, 3)) + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", WMSG, SparseEfficiencyWarning) + M[0, 1] = 2 + M[1, :] *= 5 + M[0, 2] = 3 + assert_equal(True, M.has_canonical_format) + Mcheck = self.csr_container((M.data, M.indices, M.indptr)) + assert_equal(True, Mcheck.has_canonical_format) + + # resetting index arrays before accessing M.has_canonical_format is OK + M = self.csr_container(info_no_dups) + M.data, M.indices, M.indptr = info_with_dups + assert_equal(False, M.has_canonical_format) + assert_equal(2, len(M.indices)) # dups and has_canonical_format is False + + # but reset after accessing M.has_canonical_format can break flag + M = self.csr_container(info_no_dups) + M.has_canonical_format # underlying attr is set here + M.data, M.indices, M.indptr = info_with_dups + assert_equal(True, M.has_canonical_format) + assert_equal(2, len(M.indices)) # dups but has_canonical_format is True + M.sum_duplicates() + assert_equal(2, len(M.indices)) # still has duplicates!!!! + + def test_scalar_idx_dtype(self): + # Check that index dtype takes into account all parameters + # passed to sparsetools, including the scalar ones + indptr = np.zeros(2, dtype=np.int32) + indices = np.zeros(0, dtype=np.int32) + vals = np.zeros(0) + a = self.csr_container((vals, indices, indptr), shape=(1, 2**31-1)) + b = self.csr_container((vals, indices, indptr), shape=(1, 2**31)) + ij = np.zeros((2, 0), dtype=np.int32) + c = self.csr_container((vals, ij), shape=(1, 2**31-1)) + d = self.csr_container((vals, ij), shape=(1, 2**31)) + e = self.csr_container((1, 2**31-1)) + f = self.csr_container((1, 2**31)) + assert_equal(a.indptr.dtype, np.int32) + assert_equal(b.indptr.dtype, np.int64) + assert_equal(c.indptr.dtype, np.int32) + assert_equal(d.indptr.dtype, np.int64) + assert_equal(e.indptr.dtype, np.int32) + assert_equal(f.indptr.dtype, np.int64) + + # These shouldn't fail + for x in [a, b, c, d, e, f]: + x + x + + def test_binop_explicit_zeros(self): + # Check that binary ops don't introduce spurious explicit zeros. + # See gh-9619 for context. + a = self.csr_container([[0, 1, 0]]) + b = self.csr_container([[1, 1, 0]]) + assert (a + b).nnz == 2 + assert a.multiply(b).nnz == 1 + + +TestCSR.init_class() + + +class TestCSRMatrix(_MatrixMixin, TestCSR): + @classmethod + def spcreator(cls, *args, **kwargs): + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", WMSG, SparseEfficiencyWarning) + return csr_matrix(*args, **kwargs) + +def test_spmatrix_subscriptable(): + result = csr_matrix[np.int8] + assert isinstance(result, GenericAlias) + assert result.__origin__ is csr_matrix + assert result.__args__ == (np.int8,) + + +TestCSRMatrix.init_class() + + +class TestCSC(_CompressedMixin, sparse_test_class()): + @classmethod + def spcreator(cls, *args, **kwargs): + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", WMSG, SparseEfficiencyWarning) + return csc_array(*args, **kwargs) + math_dtypes = [np.bool_, np.int_, np.float64, np.complex128] + + def test_constructor1(self): + b = array([[1, 0, 0, 0], [0, 0, 1, 0], [0, 2, 0, 3]], 'd') + bsp = self.csc_container(b) + assert_array_almost_equal(bsp.data,[1,2,1,3]) + assert_array_equal(bsp.indices,[0,2,1,2]) + assert_array_equal(bsp.indptr,[0,1,2,3,4]) + assert_equal(bsp.nnz,4) + assert_equal(bsp.shape,b.shape) + assert_equal(bsp.format,'csc') + + def test_constructor2(self): + b = zeros((6,6),'d') + b[2,4] = 5 + bsp = self.csc_container(b) + assert_array_almost_equal(bsp.data,[5]) + assert_array_equal(bsp.indices,[2]) + assert_array_equal(bsp.indptr,[0,0,0,0,0,1,1]) + + def test_constructor3(self): + b = array([[1, 0], [0, 0], [0, 2]], 'd') + bsp = self.csc_container(b) + assert_array_almost_equal(bsp.data,[1,2]) + assert_array_equal(bsp.indices,[0,2]) + assert_array_equal(bsp.indptr,[0,1,2]) + + def test_constructor4(self): + # using (data, ij) format + row = array([2, 3, 1, 3, 0, 1, 3, 0, 2, 1, 2]) + col = array([0, 1, 0, 0, 1, 1, 2, 2, 2, 2, 1]) + data = array([6., 10., 3., 9., 1., 4., 11., 2., 8., 5., 7.]) + + ij = vstack((row,col)) + csc = self.csc_container((data,ij),(4,3)) + assert_array_equal(arange(12).reshape(4, 3), csc.toarray()) + + # with duplicates (should sum the duplicates) + csc = self.csc_container(([1,1,1,1], ([0,2,2,0], [0,1,1,0]))) + assert csc.nnz == 2 + + def test_constructor5(self): + # infer dimensions from arrays + indptr = array([0,1,3,3]) + indices = array([0,5,1,2]) + data = array([1,2,3,4]) + csc = self.csc_container((data, indices, indptr)) + assert_array_equal(csc.shape,(6,3)) + + def test_constructor6(self): + # infer dimensions and dtype from lists + indptr = [0, 1, 3, 3] + indices = [0, 5, 1, 2] + data = [1, 2, 3, 4] + csc = self.csc_container((data, indices, indptr)) + assert_array_equal(csc.shape,(6,3)) + assert_(np.issubdtype(csc.dtype, np.signedinteger)) + + def test_eliminate_zeros(self): + data = array([1, 0, 0, 0, 2, 0, 3, 0]) + indices = array([1, 2, 3, 4, 5, 6, 7, 8]) + indptr = array([0, 3, 8]) + asp = self.csc_container((data, indices, indptr), shape=(10,2)) + bsp = asp.copy() + asp.eliminate_zeros() + assert_array_equal(asp.nnz, 3) + assert_array_equal(asp.data,[1, 2, 3]) + assert_array_equal(asp.toarray(), bsp.toarray()) + + def test_sort_indices(self): + data = arange(5) + row = array([7, 2, 1, 5, 4]) + ptr = [0, 3, 5] + asp = self.csc_container((data, row, ptr), shape=(10,2)) + bsp = asp.copy() + asp.sort_indices() + assert_array_equal(asp.indices,[1, 2, 7, 4, 5]) + assert_array_equal(asp.toarray(), bsp.toarray()) + + def test_ufuncs(self): + X = self.csc_container(np.arange(21).reshape(7, 3) / 21.) + for f in ["sin", "tan", "arcsin", "arctan", "sinh", "tanh", + "arcsinh", "arctanh", "rint", "sign", "expm1", "log1p", + "deg2rad", "rad2deg", "floor", "ceil", "trunc", "sqrt"]: + assert_equal(hasattr(self.datsp, f), True) + X2 = getattr(X, f)() + assert_equal(X.shape, X2.shape) + assert_array_equal(X.indices, X2.indices) + assert_array_equal(X.indptr, X2.indptr) + assert_array_equal(X2.toarray(), getattr(np, f)(X.toarray())) + + def test_unsorted_arithmetic(self): + data = arange(5) + indices = array([7, 2, 1, 5, 4]) + indptr = array([0, 3, 5]) + asp = self.csc_container((data, indices, indptr), shape=(10,2)) + data = arange(6) + indices = array([8, 1, 5, 7, 2, 4]) + indptr = array([0, 2, 6]) + bsp = self.csc_container((data, indices, indptr), shape=(10,2)) + assert_equal((asp + bsp).toarray(), asp.toarray() + bsp.toarray()) + + def test_fancy_indexing_broadcast(self): + # broadcasting indexing mode is supported + I = np.array([[1], [2], [3]]) + J = np.array([3, 4, 2]) + + np.random.seed(1234) + D = self.asdense(np.random.rand(5, 7)) + S = self.spcreator(D) + + SIJ = S[I,J] + if issparse(SIJ): + SIJ = SIJ.toarray() + assert_equal(SIJ, D[I,J]) + + def test_scalar_idx_dtype(self): + # Check that index dtype takes into account all parameters + # passed to sparsetools, including the scalar ones + indptr = np.zeros(2, dtype=np.int32) + indices = np.zeros(0, dtype=np.int32) + vals = np.zeros(0) + a = self.csc_container((vals, indices, indptr), shape=(2**31-1, 1)) + b = self.csc_container((vals, indices, indptr), shape=(2**31, 1)) + ij = np.zeros((2, 0), dtype=np.int32) + c = self.csc_container((vals, ij), shape=(2**31-1, 1)) + d = self.csc_container((vals, ij), shape=(2**31, 1)) + e = self.csr_container((1, 2**31-1)) + f = self.csr_container((1, 2**31)) + assert_equal(a.indptr.dtype, np.int32) + assert_equal(b.indptr.dtype, np.int64) + assert_equal(c.indptr.dtype, np.int32) + assert_equal(d.indptr.dtype, np.int64) + assert_equal(e.indptr.dtype, np.int32) + assert_equal(f.indptr.dtype, np.int64) + + # These shouldn't fail + for x in [a, b, c, d, e, f]: + x + x + + +TestCSC.init_class() + + +class TestCSCMatrix(_MatrixMixin, TestCSC): + @classmethod + def spcreator(cls, *args, **kwargs): + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", WMSG, SparseEfficiencyWarning) + return csc_matrix(*args, **kwargs) + + +TestCSCMatrix.init_class() + + +class TestDOK(sparse_test_class(minmax=False, nnz_axis=False)): + spcreator = dok_array + math_dtypes = [np.int_, np.float64, np.complex128] + + def test_mult(self): + A = self.dok_container((10, 12)) + A[0, 3] = 10 + A[5, 6] = 20 + D = A @ A.T + E = A @ A.T.conjugate() + assert_array_equal(D.toarray(), E.toarray()) + + def test_add_nonzero(self): + A = self.spcreator((3,2)) + A[0,1] = -10 + A[2,0] = 20 + A = A + 10 + B = array([[10, 0], [10, 10], [30, 10]]) + assert_array_equal(A.toarray(), B) + + A = A + 1j + B = B + 1j + assert_array_equal(A.toarray(), B) + + def test_dok_divide_scalar(self): + A = self.spcreator((3,2)) + A[0,1] = -10 + A[2,0] = 20 + + assert_array_equal((A/1j).toarray(), A.toarray()/1j) + assert_array_equal((A/9).toarray(), A.toarray()/9) + + def test_convert(self): + # Test provided by Andrew Straw. Fails in SciPy <= r1477. + (m, n) = (6, 7) + a = self.dok_container((m, n)) + + # set a few elements, but none in the last column + a[2,1] = 1 + a[0,2] = 2 + a[3,1] = 3 + a[1,5] = 4 + a[4,3] = 5 + a[4,2] = 6 + + # assert that the last column is all zeros + assert_array_equal(a.toarray()[:,n-1], zeros(m,)) + + # make sure it still works for CSC format + csc = a.tocsc() + assert_array_equal(csc.toarray()[:,n-1], zeros(m,)) + + # now test CSR + (m, n) = (n, m) + b = a.transpose() + assert_equal(b.shape, (m, n)) + # assert that the last row is all zeros + assert_array_equal(b.toarray()[m-1,:], zeros(n,)) + + # make sure it still works for CSR format + csr = b.tocsr() + assert_array_equal(csr.toarray()[m-1,:], zeros(n,)) + + def test_ctor(self): + # Empty ctor + assert_raises(TypeError, self.dok_container) + + # Dense ctor + b = array([[1, 0, 0, 0], [0, 0, 1, 0], [0, 2, 0, 3]], 'd') + A = self.dok_container(b) + assert_equal(b.dtype, A.dtype) + assert_equal(A.toarray(), b) + + # Sparse ctor + c = self.csr_container(b) + assert_equal(A.toarray(), c.toarray()) + + data = [[0, 1, 2], [3, 0, 0]] + d = self.dok_container(data, dtype=np.float32) + assert_equal(d.dtype, np.float32) + da = d.toarray() + assert_equal(da.dtype, np.float32) + assert_array_equal(da, data) + + def test_ticket1160(self): + # Regression test for ticket #1160. + a = self.dok_container((3,3)) + a[0,0] = 0 + # This assert would fail, because the above assignment would + # incorrectly call __set_item__ even though the value was 0. + assert_((0,0) not in a.keys(), "Unexpected entry (0,0) in keys") + + # Slice assignments were also affected. + b = self.dok_container((3,3)) + b[:,0] = 0 + assert_(len(b.keys()) == 0, "Unexpected entries in keys") + + +class TestDOKMatrix(_MatrixMixin, TestDOK): + spcreator = dok_matrix + + +TestDOK.init_class() +TestDOKMatrix.init_class() + + +class TestLIL(sparse_test_class(minmax=False)): + spcreator = lil_array + math_dtypes = [np.int_, np.float64, np.complex128] + + def test_dot(self): + A = zeros((10, 10), np.complex128) + A[0, 3] = 10 + A[5, 6] = 20j + + B = self.lil_container((10, 10), dtype=np.complex128) + B[0, 3] = 10 + B[5, 6] = 20j + + # TODO: properly handle this assertion on ppc64le + if platform.machine() != 'ppc64le': + assert_array_equal(A @ A.T, (B @ B.T).toarray()) + + assert_array_equal(A @ A.conjugate().T, (B @ B.conjugate().T).toarray()) + + def test_scalar_mul(self): + x = self.lil_container((3, 3)) + x[0, 0] = 2 + + x = x*2 + assert_equal(x[0, 0], 4) + + x = x*0 + assert_equal(x[0, 0], 0) + + def test_truediv_scalar(self): + A = self.spcreator((3, 2)) + A[0, 1] = -10 + A[2, 0] = 20 + + assert_array_equal((A / 1j).toarray(), A.toarray() / 1j) + assert_array_equal((A / 9).toarray(), A.toarray() / 9) + + def test_inplace_ops(self): + A = self.lil_container([[0, 2, 3], [4, 0, 6]]) + B = self.lil_container([[0, 1, 0], [0, 2, 3]]) + + data = {'add': (B, A + B), + 'sub': (B, A - B), + 'mul': (3, A * 3)} + + for op, (other, expected) in data.items(): + result = A.copy() + getattr(result, f'__i{op}__')(other) + + assert_array_equal(result.toarray(), expected.toarray()) + + # Ticket 1604. + A = self.lil_container((1, 3), dtype=np.dtype('float64')) + B = self.asdense([0.1, 0.1, 0.1]) + A[0, :] += B + assert_array_equal(A[0, :].toarray(), B) + + def test_lil_iteration(self): + row_data = [[1, 2, 3], [4, 5, 6]] + B = self.lil_container(array(row_data)) + for r, row in enumerate(B): + assert_array_equal(row.toarray(), array(row_data[r], ndmin=row.ndim)) + + def test_lil_from_csr(self): + # Tests whether a LIL can be constructed from a CSR. + B = self.lil_container((10, 10)) + B[0, 3] = 10 + B[5, 6] = 20 + B[8, 3] = 30 + B[3, 8] = 40 + B[8, 9] = 50 + C = B.tocsr() + D = self.lil_container(C) + assert_array_equal(C.toarray(), D.toarray()) + + def test_fancy_indexing_lil(self): + M = self.asdense(arange(25).reshape(5, 5)) + A = self.lil_container(M) + + assert_equal(A[array([1, 2, 3]), 2:3].toarray(), + M[array([1, 2, 3]), 2:3]) + + def test_point_wise_multiply(self): + l = self.lil_container((4, 3)) + l[0, 0] = 1 + l[1, 1] = 2 + l[2, 2] = 3 + l[3, 1] = 4 + + m = self.lil_container((4, 3)) + m[0, 0] = 1 + m[0, 1] = 2 + m[2, 2] = 3 + m[3, 1] = 4 + m[3, 2] = 4 + + assert_array_equal(l.multiply(m).toarray(), + m.multiply(l).toarray()) + + assert_array_equal(l.multiply(m).toarray(), + [[1, 0, 0], + [0, 0, 0], + [0, 0, 9], + [0, 16, 0]]) + + def test_lil_multiply_removal(self): + # Ticket #1427. + a = self.lil_container(np.ones((3, 3))) + a *= 2. + a[0, :] = 0 + + +class TestLILMatrix(_MatrixMixin, TestLIL): + spcreator = lil_matrix + + +TestLIL.init_class() +TestLILMatrix.init_class() + + +class BaseTestCOO: + math_dtypes = [np.int_, np.float64, np.complex128] + + def test_constructor1(self): + # unsorted triplet format + row = array([2, 3, 1, 3, 0, 1, 3, 0, 2, 1, 2]) + col = array([0, 1, 0, 0, 1, 1, 2, 2, 2, 2, 1]) + data = array([6., 10., 3., 9., 1., 4., 11., 2., 8., 5., 7.]) + + coo = self.coo_container((data,(row,col)),(4,3)) + assert_array_equal(arange(12).reshape(4, 3), coo.toarray()) + + # using Python lists and a specified dtype + coo = self.coo_container(([2**63 + 1, 1], ([0, 1], [0, 1])), dtype=np.uint64) + dense = array([[2**63 + 1, 0], [0, 1]], dtype=np.uint64) + assert_array_equal(dense, coo.toarray()) + + def test_constructor2(self): + # unsorted triplet format with duplicates (which are summed) + row = array([0,1,2,2,2,2,0,0,2,2]) + col = array([0,2,0,2,1,1,1,0,0,2]) + data = array([2,9,-4,5,7,0,-1,2,1,-5]) + coo = self.coo_container((data,(row,col)),(3,3)) + + mat = array([[4, -1, 0], [0, 0, 9], [-3, 7, 0]]) + + assert_array_equal(mat, coo.toarray()) + + def test_constructor3(self): + # empty matrix + coo = self.coo_container((4,3)) + + assert_array_equal(coo.shape,(4,3)) + assert_array_equal(coo.row,[]) + assert_array_equal(coo.col,[]) + assert_array_equal(coo.data,[]) + assert_array_equal(coo.toarray(), zeros((4, 3))) + + def test_constructor4(self): + # from dense matrix + mat = array([[0,1,0,0], + [7,0,3,0], + [0,4,0,0]]) + coo = self.coo_container(mat) + assert_array_equal(coo.toarray(), mat) + + # upgrade rank 1 arrays to row matrix + mat = array([0,1,0,0]) + coo = self.coo_container(mat) + expected = mat if self.is_array_test else mat.reshape(1, -1) + assert_array_equal(coo.toarray(), expected) + + # error if second arg interpreted as shape (gh-9919) + with pytest.raises(TypeError, match=r'object cannot be interpreted'): + self.coo_container([0, 11, 22, 33], ([0, 1, 2, 3], [0, 0, 0, 0])) + + # error if explicit shape arg doesn't match the dense matrix + with pytest.raises(ValueError, match=r'inconsistent shapes'): + self.coo_container([0, 11, 22, 33], shape=(4, 4)) + + def test_constructor_data_ij_dtypeNone(self): + data = [1] + coo = self.coo_container((data, ([0], [0])), dtype=None) + assert coo.dtype == np.array(data).dtype + + @pytest.mark.xfail(run=False, reason='COO does not have a __getitem__') + def test_iterator(self): + pass + + def test_todia_all_zeros(self): + zeros = [[0, 0]] + dia = self.coo_container(zeros).todia() + assert_array_equal(dia.toarray(), zeros) + + def test_sum_duplicates(self): + coo = self.coo_container((4,3)) + coo.sum_duplicates() + coo = self.coo_container(([1,2], ([1,0], [1,0]))) + coo.sum_duplicates() + assert_array_equal(coo.toarray(), [[2,0],[0,1]]) + coo = self.coo_container(([1,2], ([1,1], [1,1]))) + coo.sum_duplicates() + assert_array_equal(coo.toarray(), [[0,0],[0,3]]) + assert_array_equal(coo.row, [1]) + assert_array_equal(coo.col, [1]) + assert_array_equal(coo.data, [3]) + + def test_todok_duplicates(self): + coo = self.coo_container(([1,1,1,1], ([0,2,2,0], [0,1,1,0]))) + dok = coo.todok() + assert_array_equal(dok.toarray(), coo.toarray()) + + def test_tocompressed_duplicates(self): + coo = self.coo_container(([1,1,1,1], ([0,2,2,0], [0,1,1,0]))) + csr = coo.tocsr() + assert_equal(csr.nnz + 2, coo.nnz) + csc = coo.tocsc() + assert_equal(csc.nnz + 2, coo.nnz) + + def test_has_canonical_format(self): + "Ensure has_canonical_format memoizes state for sum_duplicates" + + A = self.coo_container((2, 3)) + assert_equal(A.has_canonical_format, True) + + A_array = np.array([[0, 2, 0]]) + A_coords_form = (np.array([2]), (np.array([0]), np.array([1]))) + A_coords_dups = (np.array([1, 1]), (np.array([0, 0]), np.array([1, 1]))) + + A = self.coo_container(A_array) + assert A.has_canonical_format is True + A = self.coo_container(A_coords_form) + assert A.has_canonical_format is False + A.sum_duplicates() + assert A.has_canonical_format is True + + A = self.coo_container(A, copy=True) + assert A.has_canonical_format is True + A = self.coo_container(A, copy=False) + assert A.has_canonical_format is False + A.sum_duplicates() + assert A.has_canonical_format is True + + A = self.coo_container(A_coords_dups) + assert A.has_canonical_format is False + assert_equal(A.nnz, 2) # duplicates + A.sum_duplicates() + assert A.has_canonical_format is True + assert_equal(A.nnz, 1) + + # manually set + A.has_canonical_format = False + assert_equal(A.has_canonical_format, False) + assert_equal(A.nnz, 1) # incorrectly False + A.sum_duplicates() # check flag updated + assert_equal(A.has_canonical_format, True) + + A = self.coo_container(A_coords_dups) + A.has_canonical_format = True + assert_equal(A.has_canonical_format, True) + assert_equal(A.nnz, 2) # incorrectly True + A.sum_duplicates() # check dups not removed due to flag + assert_equal(A.nnz, 2) # still has duplicates!!!! + + def test_eliminate_zeros(self): + data = array([1, 0, 0, 0, 2, 0, 3, 0]) + row = array([0, 0, 0, 1, 1, 1, 1, 1]) + col = array([1, 2, 3, 4, 5, 6, 7, 8]) + asp = self.coo_container((data, (row, col)), shape=(2,10)) + bsp = asp.copy() + asp.eliminate_zeros() + assert_((asp.data != 0).all()) + assert_array_equal(asp.toarray(), bsp.toarray()) + + def test_reshape_copy(self): + arr = [[0, 10, 0, 0], [0, 0, 0, 0], [0, 20, 30, 40]] + new_shape = (2, 6) + x = self.coo_container(arr) + + y = x.reshape(new_shape) + assert_(y.data is x.data) + + y = x.reshape(new_shape, copy=False) + assert_(y.data is x.data) + + y = x.reshape(new_shape, copy=True) + assert_(not np.may_share_memory(y.data, x.data)) + + def test_large_dimensions_reshape(self): + # Test that reshape is immune to integer overflow when number of elements + # exceeds 2^31-1 + mat1 = self.coo_container(([1], ([3000000], [1000])), (3000001, 1001)) + mat2 = self.coo_container(([1], ([1000], [3000000])), (1001, 3000001)) + + # assert_array_equal is slow for big matrices because it expects dense + # Using __ne__ and nnz instead + assert_((mat1.reshape((1001, 3000001), order='C') != mat2).nnz == 0) + assert_((mat2.reshape((3000001, 1001), order='F') != mat1).nnz == 0) + +class TestCOO(BaseTestCOO, + sparse_test_class(getset=True, + slicing=True, slicing_assign=True, + fancy_indexing=True, fancy_assign=True)): + spcreator = coo_array + +class TestCOOMatrix(_MatrixMixin, + BaseTestCOO, + sparse_test_class(getset=False, + slicing=False, slicing_assign=False, + fancy_indexing=False, fancy_assign=False)): + spcreator = coo_matrix + + +TestCOO.init_class() +TestCOOMatrix.init_class() + + +def test_sparray_subscriptable(): + result = coo_array[np.int8, tuple[int]] + assert isinstance(result, GenericAlias) + assert result.__origin__ is coo_array + assert result.__args__ == (np.int8, tuple[int]) + + result = coo_array[np.int8] + assert isinstance(result, GenericAlias) + assert result.__origin__ is coo_array + assert result.__args__ == (np.int8,) + + +class TestDIA(sparse_test_class(getset=False, slicing=False, slicing_assign=False, + fancy_indexing=False, fancy_assign=False, + minmax=False, nnz_axis=False)): + spcreator = dia_array + math_dtypes = [np.int_, np.float64, np.complex128] + + def test_constructor1(self): + D = array([[1, 0, 3, 0], + [1, 2, 0, 4], + [0, 2, 3, 0], + [0, 0, 3, 4]]) + data = np.array([[1,2,3,4]]).repeat(3,axis=0) + offsets = np.array([0,-1,2]) + assert_equal(self.dia_container((data, offsets), shape=(4, 4)).toarray(), D) + + @pytest.mark.xfail(run=False, reason='DIA does not have a __getitem__') + def test_iterator(self): + pass + + @with_64bit_maxval_limit(3) + def test_setdiag_dtype(self): + m = self.dia_container(np.eye(3)) + assert_equal(m.offsets.dtype, np.int32) + m.setdiag((3,), k=2) + assert_equal(m.offsets.dtype, np.int32) + + m = self.dia_container(np.eye(4)) + assert_equal(m.offsets.dtype, np.int64) + m.setdiag((3,), k=3) + assert_equal(m.offsets.dtype, np.int64) + + def ill_cases(self): + # Ill-formed inputs and reference 2 x 2 outputs for testing _getnnz() + # and tocsr(): list of tuples + # (data, offsets, nnz, dense array, case description) + + d1 = [[1]] # diagonal shorter than width + d3 = [[1, 2, 3]] # diagonal longer than width + + return [(d1, [-1], 1, [[0, 0], [1, 0]], + 'shorter diagonal within'), + (d1, [1], 0, [[0, 0], [0, 0]], + 'shorter diagonal above (but within if full)'), + (d1, [3], 0, [[0, 0], [0, 0]], + 'shorter diagonal, all elements above'), + (d1, [-3], 0, [[0, 0], [0, 0]], + 'shorter diagonal, all elements below'), + (d3, [-1], 1, [[0, 0], [1, 0]], + 'longer diagonal within (only head)'), + (d3, [1], 1, [[0, 2], [0, 0]], + 'longer diagonal within (only tail)'), + (d3, [3], 0, [[0, 0], [0, 0]], + 'longer diagonal, all elements above'), + (d3, [-3], 0, [[0, 0], [0, 0]], + 'longer diagonal, all elements below'), + (None, None, 0, [[0, 0], [0, 0]], + 'empty input'), + ([[0, 0]], [0], 2, [[0, 0], [0, 0]], + 'explicit zeros'), + (np.arange(1, 1 + 7).reshape((7, 1)), + [0, 1, 2, 3, -1, -2, -3], + 2, [[1, 0], [5, 0]], + 'overfilled shorter-diagonal, out of order'), + (np.arange(1, 1 + 7 * 3).reshape((7, 3)), + [0, 1, 2, 3, -1, -2, -3], + 4, [[1, 5], [13, 2]], + 'overfilled longer-diagonal, out of order')] + + def test_getnnz(self): + for data, ofsets, nnz, ref, case in self.ill_cases(): + for shape in [(2, 2), (0, 2), (2, 0)]: + if data is None: + A = self.dia_container(shape) + else: + A = self.dia_container((data, ofsets), shape=shape) + if 0 in shape: + nnz = 0 + assert A._getnnz() == nnz, 'case: ' + case + + @pytest.mark.skip(reason='DIA stores extra zeros') + def test_getnnz_axis(self): + pass + + def test_tocsr(self): + # test bound checks (other pathological cases are tested by + # TestConstructUtils::test_spdiags, and normal operation is ensured by + # many other tests here using .toarray()) + for data, ofsets, _, r, case in self.ill_cases(): + for shape in [(2, 2), (0, 2), (2, 0)]: + if data is None: + A = self.dia_container(shape) + else: + A = self.dia_container((data, ofsets), shape=shape) + B = A.tocsr() + ref = np.array(r)[:shape[0], :shape[1]] + nnz = np.count_nonzero(ref) + assert B.nnz == nnz + assert_array_equal(B.toarray(), ref, err_msg='case: ' + case) + + def test_convert_gh14555(self): + # regression test for gh-14555 + m = self.dia_container(([[1, 1, 0]], [-1]), shape=(4, 2)) + expected = m.toarray() + assert_array_equal(m.tocsc().toarray(), expected) + assert_array_equal(m.tocsr().toarray(), expected) + + def test_tocoo_gh10050(self): + # regression test for gh-10050 + m = self.dia_container([[1, 2], [3, 4]]).tocoo() + flat_inds = np.ravel_multi_index((m.row, m.col), m.shape) + inds_are_sorted = np.all(np.diff(flat_inds) > 0) + assert m.has_canonical_format == inds_are_sorted + + def test_tocoo_tocsr_tocsc_gh19245(self): + # test index_dtype with tocoo, tocsr, tocsc + data = np.array([[1, 2, 3, 4]]).repeat(3, axis=0) + offsets = np.array([0, -1, 2], dtype=np.int32) + dia = sparse.dia_array((data, offsets), shape=(4, 4)) + + coo = dia.tocoo() + assert coo.col.dtype == np.int32 + csr = dia.tocsr() + assert csr.indices.dtype == np.int32 + csc = dia.tocsc() + assert csc.indices.dtype == np.int32 + + def test_add_sparse(self): + # test format and cases not covered by common add tests + A = diag([1, 2]) + B = A + diag([3], 1) + Asp = self.dia_container(A) + Bsp = self.dia_container(B) + + Csp = Asp + Bsp + assert isinstance(Csp, self.dia_container) + assert_array_equal(Csp.toarray(), A + B) + + Csp = Bsp + Asp + assert isinstance(Csp, self.dia_container) + assert_array_equal(Csp.toarray(), B + A) + + def test_sub_sparse(self): + # test format and cases not covered by common sub tests + A = diag([1, 2]) + B = A + diag([3], 1) + Asp = self.dia_container(A) + Bsp = self.dia_container(B) + + Csp = Asp - Bsp + assert isinstance(Csp, self.dia_container) + assert_array_equal(Csp.toarray(), A - B) + + Csp = Bsp - Asp + assert isinstance(Csp, self.dia_container) + assert_array_equal(Csp.toarray(), B - A) + + Bsp = Bsp.asformat('csr') + assert_array_equal((Asp - Bsp).toarray(), A - B) + assert_array_equal((Bsp - Asp).toarray(), B - A) + + def test_mul_scalar(self): + # repro for gh-20434 + m = self.dia_container([[1, 2], [0, 4]]) + res = m * 3 + assert isinstance(res, m.__class__) + assert_array_equal(res.toarray(), [[3, 6], [0, 12]]) + + res2 = m.multiply(3) + assert isinstance(res2, m.__class__) + assert_array_equal(res2.toarray(), [[3, 6], [0, 12]]) + + def test_matmul_dia(self): + # test DIA structure of DIA @ DIA: + + # that all and only needed elements are used and produced + A = array([[1, 2, 3], + [4, 5, 6]]) + B = array([[11, 12], + [13, 14], + [15, 16]]) + Asp = self.dia_container(A) + Bsp = self.dia_container(B) + Asp.data[Asp.data == 0] = -1 # poison outside elements + Bsp.data[Bsp.data == 0] = -1 + assert_array_equal(Asp.toarray(), A) + assert_array_equal(Bsp.toarray(), B) + + C = A @ B + Csp = Asp @ Bsp + assert isinstance(Csp, self.dia_container) + assert_array_equal(Csp.toarray(), C) + assert_array_equal(Csp.offsets, [-1, 0, 1]) + assert_array_equal(Csp.data, self.dia_container(C).data) + + C = B @ A + Csp = Bsp @ Asp + assert isinstance(Csp, self.dia_container) + assert_array_equal(Csp.toarray(), C) + assert_array_equal(Csp.offsets, [-2, -1, 0, 1, 2]) + assert_array_equal(Csp.data, self.dia_container(C).data) + + # short data and that order of input offsets doesn't matter + Asp = self.dia_container(([[0., 1., 2.], [3., 4., 5.]], [1, -2]), (5, 5)) + Bsp = self.dia_container(([[6., 7., 8.], [0., 0., 9.]], [-1, 2]), (5, 5)) + + Csp = Asp @ Bsp + assert_array_equal(Csp.offsets, array([-3, 0])) + assert_array_equal(Csp.data, [[24., 35., 0.], + [6., 14., 27.]]) + + Csp = Bsp @ Asp + assert_array_equal(Csp.offsets, array([-3, 0])) + assert_array_equal(Csp.data, [[24., 0., 0.], + [27., 6., 14.]]) + + +class TestDIAMatrix(_MatrixMixin, TestDIA): + spcreator = dia_matrix + + +TestDIA.init_class() +TestDIAMatrix.init_class() + + +class TestBSR(sparse_test_class(getset=False, + slicing=False, slicing_assign=False, + fancy_indexing=False, fancy_assign=False, + nnz_axis=False)): + spcreator = bsr_array + math_dtypes = [np.int_, np.float64, np.complex128] + + def test_constructor1(self): + # check native BSR format constructor + indptr = array([0,2,2,4]) + indices = array([0,2,2,3]) + data = zeros((4,2,3)) + + data[0] = array([[0, 1, 2], + [3, 0, 5]]) + data[1] = array([[0, 2, 4], + [6, 0, 10]]) + data[2] = array([[0, 4, 8], + [12, 0, 20]]) + data[3] = array([[0, 5, 10], + [15, 0, 25]]) + + A = kron([[1,0,2,0],[0,0,0,0],[0,0,4,5]], [[0,1,2],[3,0,5]]) + Asp = self.bsr_container((data,indices,indptr),shape=(6,12)) + assert_equal(Asp.toarray(), A) + + # infer shape from arrays + Asp = self.bsr_container((data,indices,indptr)) + assert_equal(Asp.toarray(), A) + + def test_constructor2(self): + # construct from dense + + # test zero mats + for shape in [(1,1), (5,1), (1,10), (10,4), (3,7), (2,1)]: + A = zeros(shape) + assert_equal(self.bsr_container(A).toarray(), A) + A = zeros((4,6)) + assert_equal(self.bsr_container(A, blocksize=(2, 2)).toarray(), A) + assert_equal(self.bsr_container(A, blocksize=(2, 3)).toarray(), A) + + A = kron([[1,0,2,0],[0,0,0,0],[0,0,4,5]], [[0,1,2],[3,0,5]]) + assert_equal(self.bsr_container(A).toarray(), A) + assert_equal(self.bsr_container(A, shape=(6, 12)).toarray(), A) + assert_equal(self.bsr_container(A, blocksize=(1, 1)).toarray(), A) + assert_equal(self.bsr_container(A, blocksize=(2, 3)).toarray(), A) + assert_equal(self.bsr_container(A, blocksize=(2, 6)).toarray(), A) + assert_equal(self.bsr_container(A, blocksize=(2, 12)).toarray(), A) + assert_equal(self.bsr_container(A, blocksize=(3, 12)).toarray(), A) + assert_equal(self.bsr_container(A, blocksize=(6, 12)).toarray(), A) + + A = kron([[1,0,2,0],[0,1,0,0],[0,0,0,0]], [[0,1,2],[3,0,5]]) + assert_equal(self.bsr_container(A, blocksize=(2, 3)).toarray(), A) + + def test_constructor3(self): + # construct from coo-like (data,(row,col)) format + arg = ([1,2,3], ([0,1,1], [0,0,1])) + A = array([[1,0],[2,3]]) + assert_equal(self.bsr_container(arg, blocksize=(2, 2)).toarray(), A) + + def test_constructor4(self): + # regression test for gh-6292: self.bsr_matrix((data, indices, indptr)) was + # trying to compare an int to a None + n = 8 + data = np.ones((n, n, 1), dtype=np.int8) + indptr = np.array([0, n], dtype=np.int32) + indices = np.arange(n, dtype=np.int32) + self.bsr_container((data, indices, indptr), blocksize=(n, 1), copy=False) + + def test_constructor5(self): + # check for validations introduced in gh-13400 + n = 8 + data_1dim = np.ones(n) + data = np.ones((n, n, n)) + indptr = np.array([0, n]) + indices = np.arange(n) + + with assert_raises(ValueError): + # data ndim check + self.bsr_container((data_1dim, indices, indptr)) + + with assert_raises(ValueError): + # invalid blocksize + self.bsr_container((data, indices, indptr), blocksize=(1, 1, 1)) + + with assert_raises(ValueError): + # mismatching blocksize + self.bsr_container((data, indices, indptr), blocksize=(1, 1)) + + def test_default_dtype(self): + # As a numpy array, `values` has shape (2, 2, 1). + values = [[[1], [1]], [[1], [1]]] + indptr = np.array([0, 2], dtype=np.int32) + indices = np.array([0, 1], dtype=np.int32) + b = self.bsr_container((values, indices, indptr), blocksize=(2, 1)) + assert b.dtype == np.array(values).dtype + + def test_bsr_tocsr(self): + # check native conversion from BSR to CSR + indptr = array([0, 2, 2, 4]) + indices = array([0, 2, 2, 3]) + data = zeros((4, 2, 3)) + + data[0] = array([[0, 1, 2], + [3, 0, 5]]) + data[1] = array([[0, 2, 4], + [6, 0, 10]]) + data[2] = array([[0, 4, 8], + [12, 0, 20]]) + data[3] = array([[0, 5, 10], + [15, 0, 25]]) + + A = kron([[1, 0, 2, 0], [0, 0, 0, 0], [0, 0, 4, 5]], + [[0, 1, 2], [3, 0, 5]]) + Absr = self.bsr_container((data, indices, indptr), shape=(6, 12)) + Acsr = Absr.tocsr() + Acsr_via_coo = Absr.tocoo().tocsr() + assert_equal(Acsr.toarray(), A) + assert_equal(Acsr.toarray(), Acsr_via_coo.toarray()) + + def test_eliminate_zeros(self): + data = kron([1, 0, 0, 0, 2, 0, 3, 0], [[1,1],[1,1]]).T + data = data.reshape(-1,2,2) + indices = array([1, 2, 3, 4, 5, 6, 7, 8]) + indptr = array([0, 3, 8]) + asp = self.bsr_container((data, indices, indptr), shape=(4,20)) + bsp = asp.copy() + asp.eliminate_zeros() + assert_array_equal(asp.nnz, 3*4) + assert_array_equal(asp.toarray(), bsp.toarray()) + + # GitHub issue #9687 + def test_eliminate_zeros_all_zero(self): + np.random.seed(0) + m = self.bsr_container(np.random.random((12, 12)), blocksize=(2, 3)) + + # eliminate some blocks, but not all + m.data[m.data <= 0.9] = 0 + m.eliminate_zeros() + assert_equal(m.nnz, 66) + assert_array_equal(m.data.shape, (11, 2, 3)) + + # eliminate all remaining blocks + m.data[m.data <= 1.0] = 0 + m.eliminate_zeros() + assert_equal(m.nnz, 0) + assert_array_equal(m.data.shape, (0, 2, 3)) + assert_array_equal(m.toarray(), np.zeros((12, 12))) + + # test fast path + m.eliminate_zeros() + assert_equal(m.nnz, 0) + assert_array_equal(m.data.shape, (0, 2, 3)) + assert_array_equal(m.toarray(), np.zeros((12, 12))) + + def test_has_canonical_format(self): + "Ensure has_canonical_format memoizes state for sum_duplicates" + + A = np.array([[2, 3, 2], [0, 2, 1], [-4, 0, 2]]) + M = self.bsr_container(A) + assert_equal(True, M.has_canonical_format) + + indices = np.array([0, 0]) # contains duplicate + data = np.array([A, A*0]) + indptr = np.array([0, 2]) + + M = self.bsr_container((data, indices, indptr)).copy() + assert_equal(False, M.has_canonical_format) + assert isinstance(M.has_canonical_format, bool) + # set flag by deduplicating + M.sum_duplicates() + assert_equal(True, M.has_canonical_format) + assert_equal(1, len(M.indices)) + + # manually set flag True (although underlyingly duplicated) + M = self.bsr_container((data, indices, indptr)).copy() + M.has_canonical_format = True + assert_equal(True, M.has_canonical_format) + assert_equal(2, len(M.indices)) # unaffected content + # ensure deduplication bypassed when has_canonical_format == True + M.sum_duplicates() + assert_equal(2, len(M.indices)) # still has duplicates!!!! + # ensure deduplication reenabled when has_canonical_format == False + M.has_canonical_format = False + M.sum_duplicates() + assert_equal(1, len(M.indices)) + assert_equal(True, M.has_canonical_format) + + # manually set flag False (although underlyingly canonical) + M = self.bsr_container(A) + M.has_canonical_format = False + assert_equal(False, M.has_canonical_format) + assert_equal(1, len(M.indices)) + # sum_duplicates does not complain when no work to do + M.sum_duplicates() + assert_equal(True, M.has_canonical_format) + + # manually reset index arrays before accessing M.has_canonical_format is OK + M = self.bsr_container(A) + M.data, M.indices, M.indptr = data, indices, indptr + assert_equal(False, M.has_canonical_format) + assert_equal(2, len(M.indices)) # dups and has_canonical_format is False + + # but reset after accessing M.has_canonical_format can break flag + M = self.bsr_container(A) + M.has_canonical_format # underlying attr is set here + M.data, M.indices, M.indptr = data, indices, indptr + assert_equal(True, M.has_canonical_format) + assert_equal(2, len(M.indices)) # dups but has_canonical_format is True + M.sum_duplicates() + assert_equal(2, len(M.indices)) # still has duplicates!!!! + + def test_bsr_matvec(self): + A = self.bsr_container(arange(2*3*4*5).reshape(2*4,3*5), blocksize=(4,5)) + x = arange(A.shape[1]).reshape(-1,1) + assert_equal(A @ x, A.toarray() @ x) + + def test_bsr_matvecs(self): + A = self.bsr_container(arange(2*3*4*5).reshape(2*4,3*5), blocksize=(4,5)) + x = arange(A.shape[1]*6).reshape(-1,6) + assert_equal(A @ x, A.toarray() @ x) + + @pytest.mark.xfail(run=False, reason='BSR does not have a __getitem__') + def test_iterator(self): + pass + + @pytest.mark.xfail(run=False, reason='BSR does not have a __setitem__') + def test_setdiag(self): + pass + + def test_resize_blocked(self): + # test resize() with non-(1,1) blocksize + D = np.array([[1, 0, 3, 4], + [2, 0, 0, 0], + [3, 0, 0, 0]]) + S = self.spcreator(D, blocksize=(1, 2)) + assert_(S.resize((3, 2)) is None) + assert_array_equal(S.toarray(), [[1, 0], + [2, 0], + [3, 0]]) + S.resize((2, 2)) + assert_array_equal(S.toarray(), [[1, 0], + [2, 0]]) + S.resize((3, 2)) + assert_array_equal(S.toarray(), [[1, 0], + [2, 0], + [0, 0]]) + S.resize((3, 4)) + assert_array_equal(S.toarray(), [[1, 0, 0, 0], + [2, 0, 0, 0], + [0, 0, 0, 0]]) + assert_raises(ValueError, S.resize, (2, 3)) + + @pytest.mark.xfail(run=False, reason='BSR does not have a __setitem__') + def test_setdiag_comprehensive(self): + pass + + @pytest.mark.skipif(IS_COLAB, reason="exceeds memory limit") + def test_scalar_idx_dtype(self): + # Check that index dtype takes into account all parameters + # passed to sparsetools, including the scalar ones + indptr = np.zeros(2, dtype=np.int32) + indices = np.zeros(0, dtype=np.int32) + vals = np.zeros((0, 1, 1)) + a = self.bsr_container((vals, indices, indptr), shape=(1, 2**31-1)) + b = self.bsr_container((vals, indices, indptr), shape=(1, 2**31)) + c = self.bsr_container((1, 2**31-1)) + d = self.bsr_container((1, 2**31)) + assert_equal(a.indptr.dtype, np.int32) + assert_equal(b.indptr.dtype, np.int64) + assert_equal(c.indptr.dtype, np.int32) + assert_equal(d.indptr.dtype, np.int64) + + try: + vals2 = np.zeros((0, 1, 2**31-1)) + vals3 = np.zeros((0, 1, 2**31)) + e = self.bsr_container((vals2, indices, indptr), shape=(1, 2**31-1)) + f = self.bsr_container((vals3, indices, indptr), shape=(1, 2**31)) + assert_equal(e.indptr.dtype, np.int32) + assert_equal(f.indptr.dtype, np.int64) + except (MemoryError, ValueError): + # May fail on 32-bit Python + e = 0 + f = 0 + + # These shouldn't fail + for x in [a, b, c, d, e, f]: + x + x + + +class TestBSRMatrix(_MatrixMixin, TestBSR): + spcreator = bsr_matrix + + +TestBSR.init_class() +TestBSRMatrix.init_class() + + +#------------------------------------------------------------------------------ +# Tests for non-canonical representations (with duplicates, unsorted indices) +#------------------------------------------------------------------------------ + +def _same_sum_duplicate(data, *inds, **kwargs): + """Duplicates entries to produce the same matrix""" + indptr = kwargs.pop('indptr', None) + if np.issubdtype(data.dtype, np.bool_) or \ + np.issubdtype(data.dtype, np.unsignedinteger): + if indptr is None: + return (data,) + inds + else: + return (data,) + inds + (indptr,) + + zeros_pos = (data == 0).nonzero() + + # duplicate data + data = data.repeat(2, axis=0) + data[::2] -= 1 + data[1::2] = 1 + + # don't spoil all explicit zeros + if zeros_pos[0].size > 0: + pos = tuple(p[0] for p in zeros_pos) + pos1 = (2*pos[0],) + pos[1:] + pos2 = (2*pos[0]+1,) + pos[1:] + data[pos1] = 0 + data[pos2] = 0 + + inds = tuple(indices.repeat(2) for indices in inds) + + if indptr is None: + return (data,) + inds + else: + return (data,) + inds + (indptr * 2,) + + +class _NonCanonicalMixin: + def spcreator(self, D, *args, sorted_indices=False, **kwargs): + """Replace D with a non-canonical equivalent: containing + duplicate elements and explicit zeros""" + construct = super().spcreator + M = construct(D, *args, **kwargs) + + zero_pos = (M.toarray() == 0).nonzero() + has_zeros = (zero_pos[0].size > 0) + if has_zeros: + k = zero_pos[0].size//2 + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", WMSG, SparseEfficiencyWarning) + M = self._insert_explicit_zero(M, zero_pos[0][k], zero_pos[1][k]) + + arg1 = self._arg1_for_noncanonical(M, sorted_indices) + if 'shape' not in kwargs: + kwargs['shape'] = M.shape + NC = construct(arg1, **kwargs) + + # check that result is valid + if NC.dtype in [np.float32, np.complex64]: + # For single-precision floats, the differences between M and NC + # that are introduced by the extra operations involved in the + # construction of NC necessitate a more lenient tolerance level + # than the default. + rtol = 1e-05 + else: + rtol = 1e-07 + assert_allclose(NC.toarray(), M.toarray(), rtol=rtol) + + # check that at least one explicit zero + if has_zeros: + assert_((NC.data == 0).any()) + # TODO check that NC has duplicates (which are not explicit zeros) + + return NC + + @pytest.mark.skip(reason='bool(matrix) counts explicit zeros') + def test_bool(self): + pass + + @pytest.mark.skip(reason='getnnz-axis counts explicit zeros') + def test_getnnz_axis(self): + pass + + @pytest.mark.skip(reason='nnz counts explicit zeros') + def test_empty(self): + pass + + +class _NonCanonicalCompressedMixin(_NonCanonicalMixin): + def _arg1_for_noncanonical(self, M, sorted_indices=False): + """Return non-canonical constructor arg1 equivalent to M""" + data, indices, indptr = _same_sum_duplicate(M.data, M.indices, + indptr=M.indptr) + if not sorted_indices: + for start, stop in zip(indptr, indptr[1:]): + indices[start:stop] = indices[start:stop][::-1].copy() + data[start:stop] = data[start:stop][::-1].copy() + return data, indices, indptr + + def _insert_explicit_zero(self, M, i, j): + M[i,j] = 0 + return M + + +class _NonCanonicalCSMixin(_NonCanonicalCompressedMixin): + def test_getelement(self): + def check(dtype, sorted_indices): + D = array([[1,0,0], + [4,3,0], + [0,2,0], + [0,0,0]], dtype=dtype) + A = self.spcreator(D, sorted_indices=sorted_indices) + + M,N = D.shape + + for i in range(-M, M): + for j in range(-N, N): + assert_equal(A[i,j], D[i,j]) + + for ij in [(0,3),(-1,3),(4,0),(4,3),(4,-1), (1, 2, 3)]: + assert_raises((IndexError, TypeError), A.__getitem__, ij) + + for dtype in supported_dtypes: + for sorted_indices in [False, True]: + check(np.dtype(dtype), sorted_indices) + + def test_setitem_sparse(self): + D = np.eye(3) + A = self.spcreator(D) + B = self.spcreator([[1,2,3]]) + + D[1,:] = B.toarray() + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", WMSG, SparseEfficiencyWarning) + A[1,:] = B + assert_array_equal(A.toarray(), D) + + D[:,2] = B.toarray().ravel() + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", WMSG, SparseEfficiencyWarning) + A[:,2] = B.T + assert_array_equal(A.toarray(), D) + + @pytest.mark.xfail(run=False, reason='inverse broken with non-canonical matrix') + def test_inv(self): + pass + + @pytest.mark.xfail(run=False, reason='solve broken with non-canonical matrix') + def test_solve(self): + pass + + +class TestCSRNonCanonical(_NonCanonicalCSMixin, TestCSR): + pass + + +class TestCSRNonCanonicalMatrix(TestCSRNonCanonical, TestCSRMatrix): + pass + + +class TestCSCNonCanonical(_NonCanonicalCSMixin, TestCSC): + pass + + +class TestCSCNonCanonicalMatrix(TestCSCNonCanonical, TestCSCMatrix): + pass + + +class TestBSRNonCanonical(_NonCanonicalCompressedMixin, TestBSR): + def _insert_explicit_zero(self, M, i, j): + x = M.tocsr() + x[i,j] = 0 + return x.tobsr(blocksize=M.blocksize) + + @pytest.mark.xfail(run=False, reason='diagonal broken with non-canonical BSR') + def test_diagonal(self): + pass + + @pytest.mark.xfail(run=False, reason='expm broken with non-canonical BSR') + def test_expm(self): + pass + + +class TestBSRNonCanonicalMatrix(TestBSRNonCanonical, TestBSRMatrix): + pass + + +class COONonCanonicalMixin(_NonCanonicalMixin): + def _arg1_for_noncanonical(self, M, sorted_indices=None): + """Return non-canonical constructor arg1 equivalent to M""" + data, row, col = _same_sum_duplicate(M.data, M.row, M.col) + return data, (row, col) + + def _insert_explicit_zero(self, M, i, j): + M.data = np.r_[M.data.dtype.type(0), M.data] + M.row = np.r_[M.row.dtype.type(i), M.row] + M.col = np.r_[M.col.dtype.type(j), M.col] + return M + + def test_setdiag_noncanonical(self): + m = self.spcreator(np.eye(3)) + m.sum_duplicates() + m.setdiag([3, 2], k=1) + m.sum_duplicates() + assert_(np.all(np.diff(m.col) >= 0)) + + +class TestCOONonCanonical(COONonCanonicalMixin, TestCOO): + pass + + +class TestCOONonCanonicalMatrix(COONonCanonicalMixin, TestCOOMatrix): + pass + + +def test_broadcast_to(): + a = np.array([[1, 0, 2]]) + b = np.array([[1], [0], [2]]) + c = np.array([[1, 0, 2], [0, 3, 0]]) + d = np.array([[7]]) + e = np.array([[0]]) + f = np.array([[0,0,0,0]]) + for container in (csc_matrix, csc_array, csr_matrix, csr_array): + res_a = container(a)._broadcast_to((2,3)) + res_b = container(b)._broadcast_to((3,4)) + res_c = container(c)._broadcast_to((2,3)) + res_d = container(d)._broadcast_to((4,4)) + res_e = container(e)._broadcast_to((5,6)) + res_f = container(f)._broadcast_to((2,4)) + assert_array_equal(res_a.toarray(), np.broadcast_to(a, (2,3))) + assert_array_equal(res_b.toarray(), np.broadcast_to(b, (3,4))) + assert_array_equal(res_c.toarray(), c) + assert_array_equal(res_d.toarray(), np.broadcast_to(d, (4,4))) + assert_array_equal(res_e.toarray(), np.broadcast_to(e, (5,6))) + assert_array_equal(res_f.toarray(), np.broadcast_to(f, (2,4))) + + with pytest.raises(ValueError, match="cannot be broadcast"): + container([[1, 2, 0], [3, 0, 1]])._broadcast_to(shape=(2, 1)) + + with pytest.raises(ValueError, match="cannot be broadcast"): + container([[0, 1, 2]])._broadcast_to(shape=(3, 2)) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_common1d.py b/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_common1d.py new file mode 100644 index 0000000000000000000000000000000000000000..437af3d839c29d8131672a1e593ddb283608e7e8 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_common1d.py @@ -0,0 +1,444 @@ +"""Test of 1D aspects of sparse array classes""" +import warnings + +import pytest + +import numpy as np +from numpy.testing import assert_equal, assert_allclose +from numpy.exceptions import ComplexWarning + +from scipy.sparse import ( + bsr_array, csc_array, dia_array, lil_array, + coo_array, csr_array, dok_array, + ) +from scipy.sparse._sputils import supported_dtypes, matrix + + +spcreators = [coo_array, csr_array, dok_array] +math_dtypes = [np.int64, np.float64, np.complex128] + + +@pytest.fixture +def dat1d(): + return np.array([3, 0, 1, 0], 'd') + + +@pytest.fixture +def datsp_math_dtypes(dat1d): + dat_dtypes = {dtype: dat1d.astype(dtype) for dtype in math_dtypes} + return { + spcreator: [(dtype, dat, spcreator(dat)) for dtype, dat in dat_dtypes.items()] + for spcreator in spcreators + } + + +# Test init with 1D dense input +# sparrays which do not plan to support 1D +@pytest.mark.parametrize("spcreator", [bsr_array, csc_array, dia_array, lil_array]) +def test_no_1d_support_in_init(spcreator): + with pytest.raises(ValueError, match="arrays don't support 1D input"): + spcreator([0, 1, 2, 3]) + + +# Test init with nD dense input +# sparrays which do not yet support nD +@pytest.mark.parametrize( + "spcreator", [csr_array, dok_array, bsr_array, csc_array, dia_array, lil_array] +) +def test_no_nd_support_in_init(spcreator): + with pytest.raises(ValueError, match="arrays don't.*support 3D"): + spcreator(np.ones((3, 2, 4))) + + +# Main tests class +@pytest.mark.parametrize("spcreator", spcreators) +class TestCommon1D: + """test common functionality shared by 1D sparse formats""" + + def test_create_empty(self, spcreator): + assert_equal(spcreator((3,)).toarray(), np.zeros(3)) + assert_equal(spcreator((3,)).nnz, 0) + assert_equal(spcreator((3,)).count_nonzero(), 0) + + def test_invalid_shapes(self, spcreator): + with pytest.raises(ValueError, match='elements cannot be negative'): + spcreator((-3,)) + + def test_repr(self, spcreator, dat1d): + repr(spcreator(dat1d)) + + def test_str(self, spcreator, dat1d): + str(spcreator(dat1d)) + + def test_neg(self, spcreator): + A = np.array([-1, 0, 17, 0, -5, 0, 1, -4, 0, 0, 0, 0], 'd') + assert_equal(-A, (-spcreator(A)).toarray()) + + def test_1d_supported_init(self, spcreator): + A = spcreator([0, 1, 2, 3]) + assert A.ndim == 1 + + def test_reshape_1d_tofrom_row_or_column(self, spcreator): + # add a dimension 1d->2d + x = spcreator([1, 0, 7, 0, 0, 0, 0, -3, 0, 0, 0, 5]) + y = x.reshape(1, 12) + desired = [[1, 0, 7, 0, 0, 0, 0, -3, 0, 0, 0, 5]] + assert_equal(y.toarray(), desired) + + # remove a size-1 dimension 2d->1d + x = spcreator(desired) + y = x.reshape(12) + assert_equal(y.toarray(), desired[0]) + y2 = x.reshape((12,)) + assert y.shape == y2.shape + + # make a 2d column into 1d. 2d->1d + y = x.T.reshape(12) + assert_equal(y.toarray(), desired[0]) + + def test_reshape(self, spcreator): + x = spcreator([1, 0, 7, 0, 0, 0, 0, -3, 0, 0, 0, 5]) + y = x.reshape((4, 3)) + desired = [[1, 0, 7], [0, 0, 0], [0, -3, 0], [0, 0, 5]] + assert_equal(y.toarray(), desired) + + y = x.reshape((12,)) + assert y is x + + y = x.reshape(12) + assert_equal(y.toarray(), x.toarray()) + + def test_sum(self, spcreator): + np.random.seed(1234) + dat_1 = np.array([0, 1, 2, 3, -4, 5, -6, 7, 9]) + dat_2 = np.random.rand(5) + dat_3 = np.array([]) + dat_4 = np.zeros((40,)) + arrays = [dat_1, dat_2, dat_3, dat_4] + + for dat in arrays: + datsp = spcreator(dat) + with np.errstate(over='ignore'): + assert np.isscalar(datsp.sum()) + assert_allclose(dat.sum(), datsp.sum()) + assert_allclose(dat.sum(axis=None), datsp.sum(axis=None)) + assert_allclose(dat.sum(axis=0), datsp.sum(axis=0)) + assert_allclose(dat.sum(axis=-1), datsp.sum(axis=-1)) + + # test `out` parameter + datsp.sum(axis=0, out=np.zeros(())) + + def test_sum_invalid_params(self, spcreator): + out = np.zeros((3,)) # wrong size for out + dat = np.array([0, 1, 2]) + datsp = spcreator(dat) + + with pytest.raises(ValueError, match='axis out of range'): + datsp.sum(axis=1) + with pytest.raises(ValueError, match='axis out of range'): + datsp.sum(axis=(0, 3)) + with pytest.raises(TypeError, match='axis must be an integer'): + datsp.sum(axis=1.5) + with pytest.raises(ValueError, match='output parameter.*wrong.*dimension'): + datsp.sum(axis=0, out=out) + + def test_numpy_sum(self, spcreator): + dat = np.array([0, 1, 2]) + datsp = spcreator(dat) + + dat_sum = np.sum(dat) + datsp_sum = np.sum(datsp) + + assert_allclose(dat_sum, datsp_sum) + + def test_mean(self, spcreator): + dat = np.array([0, 1, 2]) + datsp = spcreator(dat) + + assert_allclose(dat.mean(), datsp.mean()) + assert np.isscalar(datsp.mean(axis=None)) + assert_allclose(dat.mean(axis=None), datsp.mean(axis=None)) + assert_allclose(dat.mean(axis=0), datsp.mean(axis=0)) + assert_allclose(dat.mean(axis=-1), datsp.mean(axis=-1)) + + with pytest.raises(ValueError, match='axis'): + datsp.mean(axis=1) + with pytest.raises(ValueError, match='axis'): + datsp.mean(axis=-2) + + def test_mean_invalid_params(self, spcreator): + out = np.asarray(np.zeros((1, 3))) + dat = np.array([[0, 1, 2], [3, -4, 5], [-6, 7, 9]]) + + datsp = spcreator(dat) + with pytest.raises(ValueError, match='axis out of range'): + datsp.mean(axis=3) + with pytest.raises(ValueError, match='axis out of range'): + datsp.mean(axis=(0, 3)) + with pytest.raises(TypeError, match='axis must be an integer'): + datsp.mean(axis=1.5) + with pytest.raises(ValueError, match='out.*not match shape'): + datsp.mean(axis=1, out=out) + + def test_sum_dtype(self, spcreator): + dat = np.array([0, 1, 2]) + datsp = spcreator(dat) + + for dtype in supported_dtypes: + dat_sum = dat.sum(dtype=dtype) + datsp_sum = datsp.sum(dtype=dtype) + + assert_allclose(dat_sum, datsp_sum) + assert_equal(dat_sum.dtype, datsp_sum.dtype) + + def test_mean_dtype(self, spcreator): + dat = np.array([0, 1, 2]) + datsp = spcreator(dat) + + for dtype in supported_dtypes: + dat_mean = dat.mean(dtype=dtype) + datsp_mean = datsp.mean(dtype=dtype) + + assert_allclose(dat_mean, datsp_mean) + assert_equal(dat_mean.dtype, datsp_mean.dtype) + + def test_mean_out(self, spcreator): + dat = np.array([0, 1, 2]) + datsp = spcreator(dat) + + dat_out = np.array(0) + datsp_out = np.array(0) + + dat.mean(out=dat_out) + datsp.mean(out=datsp_out) + assert_allclose(dat_out, datsp_out) + + dat.mean(axis=0, out=dat_out) + datsp.mean(axis=0, out=datsp_out) + assert_allclose(dat_out, datsp_out) + + with pytest.raises(ValueError, match="output parameter.*dimension"): + datsp.mean(out=np.array([0])) + with pytest.raises(ValueError, match="output parameter.*dimension"): + datsp.mean(out=np.array([[0]])) + + def test_numpy_mean(self, spcreator): + dat = np.array([0, 1, 2]) + datsp = spcreator(dat) + + dat_mean = np.mean(dat) + datsp_mean = np.mean(datsp) + + assert_allclose(dat_mean, datsp_mean) + assert_equal(dat_mean.dtype, datsp_mean.dtype) + + def test_from_array(self, spcreator): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", ComplexWarning) + A = np.array([2, 3, 4]) + assert_equal(spcreator(A).toarray(), A) + + A = np.array([1.0 + 3j, 0, -1]) + assert_equal(spcreator(A).toarray(), A) + assert_equal(spcreator(A, dtype='int16').toarray(), A.astype('int16')) + + def test_from_list(self, spcreator): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", ComplexWarning) + A = [2, 3, 4] + assert_equal(spcreator(A).toarray(), A) + + A = [1.0 + 3j, 0, -1] + assert_equal(spcreator(A).toarray(), np.array(A)) + assert_equal( + spcreator(A, dtype='int16').toarray(), np.array(A).astype('int16') + ) + + def test_from_sparse(self, spcreator): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", ComplexWarning) + D = np.array([1, 0, 0]) + S = coo_array(D) + assert_equal(spcreator(S).toarray(), D) + S = spcreator(D) + assert_equal(spcreator(S).toarray(), D) + + D = np.array([1.0 + 3j, 0, -1]) + S = coo_array(D) + assert_equal(spcreator(S).toarray(), D) + assert_equal(spcreator(S, dtype='int16').toarray(), D.astype('int16')) + S = spcreator(D) + assert_equal(spcreator(S).toarray(), D) + assert_equal(spcreator(S, dtype='int16').toarray(), D.astype('int16')) + + def test_toarray(self, spcreator, dat1d): + datsp = spcreator(dat1d) + # Check C- or F-contiguous (default). + chk = datsp.toarray() + assert_equal(chk, dat1d) + assert chk.flags.c_contiguous == chk.flags.f_contiguous + + # Check C-contiguous (with arg). + chk = datsp.toarray(order='C') + assert_equal(chk, dat1d) + assert chk.flags.c_contiguous + assert chk.flags.f_contiguous + + # Check F-contiguous (with arg). + chk = datsp.toarray(order='F') + assert_equal(chk, dat1d) + assert chk.flags.c_contiguous + assert chk.flags.f_contiguous + + # Check with output arg. + out = np.zeros(datsp.shape, dtype=datsp.dtype) + datsp.toarray(out=out) + assert_equal(out, dat1d) + + # Check that things are fine when we don't initialize with zeros. + out[...] = 1.0 + datsp.toarray(out=out) + assert_equal(out, dat1d) + + # np.dot does not work with sparse matrices (unless scalars) + # so this is testing whether dat1d matches datsp.toarray() + a = np.array([1.0, 2.0, 3.0, 4.0]) + dense_dot_dense = np.dot(a, dat1d) + check = np.dot(a, datsp.toarray()) + assert_equal(dense_dot_dense, check) + + b = np.array([1.0, 2.0, 3.0, 4.0]) + dense_dot_dense = np.dot(dat1d, b) + check = np.dot(datsp.toarray(), b) + assert_equal(dense_dot_dense, check) + + # Check bool data works. + spbool = spcreator(dat1d, dtype=bool) + arrbool = dat1d.astype(bool) + assert_equal(spbool.toarray(), arrbool) + + def test_add(self, spcreator, datsp_math_dtypes): + for dtype, dat, datsp in datsp_math_dtypes[spcreator]: + a = dat.copy() + a[0] = 2.0 + b = datsp + c = b + a + assert_equal(c, b.toarray() + a) + + # test broadcasting + # Note: cant add nonzero scalar to sparray. Can add len 1 array + c = b + a[0:1] + assert_equal(c, b.toarray() + a[0]) + + def test_radd(self, spcreator, datsp_math_dtypes): + for dtype, dat, datsp in datsp_math_dtypes[spcreator]: + a = dat.copy() + a[0] = 2.0 + b = datsp + c = a + b + assert_equal(c, a + b.toarray()) + + def test_rsub(self, spcreator, datsp_math_dtypes): + for dtype, dat, datsp in datsp_math_dtypes[spcreator]: + if dtype == np.dtype('bool'): + # boolean array subtraction deprecated in 1.9.0 + continue + + assert_equal((dat - datsp), [0, 0, 0, 0]) + assert_equal((datsp - dat), [0, 0, 0, 0]) + assert_equal((0 - datsp).toarray(), -dat) + + A = spcreator([1, -4, 0, 2], dtype='d') + assert_equal((dat - A), dat - A.toarray()) + assert_equal((A - dat), A.toarray() - dat) + assert_equal(A.toarray() - datsp, A.toarray() - dat) + assert_equal(datsp - A.toarray(), dat - A.toarray()) + + # test broadcasting + assert_equal(dat[:1] - datsp, dat[:1] - dat) + + def test_matmul_basic(self, spcreator): + A = np.array([[2, 0, 3.0], [0, 0, 0], [0, 1, 2]]) + v = np.array([1, 0, 3]) + Asp = spcreator(A) + vsp = spcreator(v) + + # sparse result when both args are sparse and result not scalar + assert_equal((Asp @ vsp).toarray(), A @ v) + assert_equal(A @ vsp, A @ v) + assert_equal(Asp @ v, A @ v) + assert_equal((vsp @ Asp).toarray(), v @ A) + assert_equal(vsp @ A, v @ A) + assert_equal(v @ Asp, v @ A) + + assert_equal(vsp @ vsp, v @ v) + assert_equal(v @ vsp, v @ v) + assert_equal(vsp @ v, v @ v) + assert_equal((Asp @ Asp).toarray(), A @ A) + assert_equal(A @ Asp, A @ A) + assert_equal(Asp @ A, A @ A) + + def test_matvec(self, spcreator): + A = np.array([2, 0, 3.0]) + Asp = spcreator(A) + col = np.array([[1, 2, 3]]).T + + assert_allclose(Asp @ col, Asp.toarray() @ col) + + assert (A @ np.array([1, 2, 3])).shape == () + assert Asp @ np.array([1, 2, 3]) == 11 + assert (Asp @ np.array([1, 2, 3])).shape == () + assert (Asp @ np.array([[1], [2], [3]])).shape == (1,) + # check result type + assert isinstance(Asp @ matrix([[1, 2, 3]]).T, np.ndarray) + + # ensure exception is raised for improper dimensions + bad_vecs = [np.array([1, 2]), np.array([1, 2, 3, 4]), np.array([[1], [2]])] + for x in bad_vecs: + with pytest.raises(ValueError, match='dimension mismatch'): + Asp @ x + + # The current relationship between sparse matrix products and array + # products is as follows: + dot_result = np.dot(Asp.toarray(), [1, 2, 3]) + assert_allclose(Asp @ np.array([1, 2, 3]), dot_result) + assert_allclose(Asp @ [[1], [2], [3]], dot_result.T) + # Note that the result of Asp @ x is dense if x has a singleton dimension. + + def test_rmatvec(self, spcreator, dat1d): + M = spcreator(dat1d) + assert_allclose([1, 2, 3, 4] @ M, np.dot([1, 2, 3, 4], M.toarray())) + row = np.array([[1, 2, 3, 4]]) + assert_allclose(row @ M, row @ M.toarray()) + + def test_transpose(self, spcreator, dat1d): + for A in [dat1d, np.array([])]: + B = spcreator(A) + assert_equal(B.toarray(), A) + assert_equal(B.transpose().toarray(), A) + assert_equal(B.dtype, A.dtype) + + def test_add_dense_to_sparse(self, spcreator, datsp_math_dtypes): + for dtype, dat, datsp in datsp_math_dtypes[spcreator]: + sum1 = dat + datsp + assert_equal(sum1, dat + dat) + sum2 = datsp + dat + assert_equal(sum2, dat + dat) + + def test_iterator(self, spcreator): + # test that __iter__ is compatible with NumPy + B = np.arange(5) + A = spcreator(B) + + if A.format not in ['coo', 'dia', 'bsr']: + for x, y in zip(A, B): + assert_equal(x, y) + + def test_resize(self, spcreator): + # resize(shape) resizes the matrix in-place + D = np.array([1, 0, 3, 4]) + S = spcreator(D) + assert S.resize((3,)) is None + assert_equal(S.toarray(), [1, 0, 3]) + S.resize((5,)) + assert_equal(S.toarray(), [1, 0, 3, 0, 0]) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_construct.py b/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_construct.py new file mode 100644 index 0000000000000000000000000000000000000000..8f243ad3935e0c7a334bc019999f3f2f3a8c0ddc --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_construct.py @@ -0,0 +1,1169 @@ +"""test sparse matrix construction functions""" + +import numpy as np +from numpy import array +from numpy.testing import (assert_equal, assert_, + assert_array_equal, assert_array_almost_equal_nulp) +import pytest +from pytest import raises as assert_raises +from scipy._lib._testutils import check_free_memory + +from scipy.sparse import (csr_matrix, coo_matrix, + csr_array, coo_array, + csc_array, bsr_array, + dia_array, dok_array, + lil_array, csc_matrix, + bsr_matrix, dia_matrix, + lil_matrix, sparray, spmatrix, + _construct as construct) +from scipy.sparse._construct import rand as sprand + +sparse_formats = ['csr','csc','coo','bsr','dia','lil','dok'] + +#TODO check whether format=XXX is respected + + +def _sprandn(m, n, density=0.01, format="coo", dtype=None, rng=None): + # Helper function for testing. + rng = np.random.default_rng(rng) + data_rvs = rng.standard_normal + return construct.random(m, n, density, format, dtype, rng, data_rvs) + + +def _sprandn_array(m, n, density=0.01, format="coo", dtype=None, rng=None): + # Helper function for testing. + rng = np.random.default_rng(rng) + data_sampler = rng.standard_normal + return construct.random_array((m, n), density=density, format=format, dtype=dtype, + rng=rng, data_sampler=data_sampler) + + +class TestConstructUtils: + + @pytest.mark.parametrize("cls", [ + csc_array, csr_array, coo_array, bsr_array, + dia_array, dok_array, lil_array + ]) + def test_singleton_array_constructor(self, cls): + with pytest.raises( + ValueError, + match=( + 'scipy sparse array classes do not support ' + 'instantiation from a scalar' + ) + ): + cls(0) + + @pytest.mark.parametrize("cls", [ + csc_matrix, csr_matrix, coo_matrix, + bsr_matrix, dia_matrix, lil_matrix + ]) + def test_singleton_matrix_constructor(self, cls): + """ + This test is for backwards compatibility post scipy 1.13. + The behavior observed here is what is to be expected + with the older matrix classes. This test comes with the + exception of dok_matrix, which was not working pre scipy1.12 + (unlike the rest of these). + """ + assert cls(0).shape == (1, 1) + + def test_spdiags(self): + diags1 = array([[1, 2, 3, 4, 5]]) + diags2 = array([[1, 2, 3, 4, 5], + [6, 7, 8, 9,10]]) + diags3 = array([[1, 2, 3, 4, 5], + [6, 7, 8, 9,10], + [11,12,13,14,15]]) + + cases = [] + cases.append((diags1, 0, 1, 1, [[1]])) + cases.append((diags1, [0], 1, 1, [[1]])) + cases.append((diags1, [0], 2, 1, [[1],[0]])) + cases.append((diags1, [0], 1, 2, [[1,0]])) + cases.append((diags1, [1], 1, 2, [[0,2]])) + cases.append((diags1,[-1], 1, 2, [[0,0]])) + cases.append((diags1, [0], 2, 2, [[1,0],[0,2]])) + cases.append((diags1,[-1], 2, 2, [[0,0],[1,0]])) + cases.append((diags1, [3], 2, 2, [[0,0],[0,0]])) + cases.append((diags1, [0], 3, 4, [[1,0,0,0],[0,2,0,0],[0,0,3,0]])) + cases.append((diags1, [1], 3, 4, [[0,2,0,0],[0,0,3,0],[0,0,0,4]])) + cases.append((diags1, [2], 3, 5, [[0,0,3,0,0],[0,0,0,4,0],[0,0,0,0,5]])) + + cases.append((diags2, [0,2], 3, 3, [[1,0,8],[0,2,0],[0,0,3]])) + cases.append((diags2, [-1,0], 3, 4, [[6,0,0,0],[1,7,0,0],[0,2,8,0]])) + cases.append((diags2, [2,-3], 6, 6, [[0,0,3,0,0,0], + [0,0,0,4,0,0], + [0,0,0,0,5,0], + [6,0,0,0,0,0], + [0,7,0,0,0,0], + [0,0,8,0,0,0]])) + + cases.append((diags3, [-1,0,1], 6, 6, [[6,12, 0, 0, 0, 0], + [1, 7,13, 0, 0, 0], + [0, 2, 8,14, 0, 0], + [0, 0, 3, 9,15, 0], + [0, 0, 0, 4,10, 0], + [0, 0, 0, 0, 5, 0]])) + cases.append((diags3, [-4,2,-1], 6, 5, [[0, 0, 8, 0, 0], + [11, 0, 0, 9, 0], + [0,12, 0, 0,10], + [0, 0,13, 0, 0], + [1, 0, 0,14, 0], + [0, 2, 0, 0,15]])) + cases.append((diags3, [-1, 1, 2], len(diags3[0]), len(diags3[0]), + [[0, 7, 13, 0, 0], + [1, 0, 8, 14, 0], + [0, 2, 0, 9, 15], + [0, 0, 3, 0, 10], + [0, 0, 0, 4, 0]])) + + for d, o, m, n, result in cases: + if len(d[0]) == m and m == n: + assert_equal(construct.spdiags(d, o).toarray(), result) + assert_equal(construct.spdiags(d, o, m, n).toarray(), result) + assert_equal(construct.spdiags(d, o, (m, n)).toarray(), result) + + def test_diags(self): + a = array([1.0, 2.0, 3.0, 4.0, 5.0]) + b = array([6.0, 7.0, 8.0, 9.0, 10.0]) + c = array([11.0, 12.0, 13.0, 14.0, 15.0]) + + cases = [] + cases.append((a[:1], 0, (1, 1), [[1]])) + cases.append(([a[:1]], [0], (1, 1), [[1]])) + cases.append(([a[:1]], [0], (2, 1), [[1],[0]])) + cases.append(([a[:1]], [0], (1, 2), [[1,0]])) + cases.append(([a[:1]], [1], (1, 2), [[0,1]])) + cases.append(([a[:2]], [0], (2, 2), [[1,0],[0,2]])) + cases.append(([a[:1]],[-1], (2, 2), [[0,0],[1,0]])) + cases.append(([a[:3]], [0], (3, 4), [[1,0,0,0],[0,2,0,0],[0,0,3,0]])) + cases.append(([a[:3]], [1], (3, 4), [[0,1,0,0],[0,0,2,0],[0,0,0,3]])) + cases.append(([a[:1]], [-2], (3, 5), [[0,0,0,0,0],[0,0,0,0,0],[1,0,0,0,0]])) + cases.append(([a[:2]], [-1], (3, 5), [[0,0,0,0,0],[1,0,0,0,0],[0,2,0,0,0]])) + cases.append(([a[:3]], [0], (3, 5), [[1,0,0,0,0],[0,2,0,0,0],[0,0,3,0,0]])) + cases.append(([a[:3]], [1], (3, 5), [[0,1,0,0,0],[0,0,2,0,0],[0,0,0,3,0]])) + cases.append(([a[:3]], [2], (3, 5), [[0,0,1,0,0],[0,0,0,2,0],[0,0,0,0,3]])) + cases.append(([a[:2]], [3], (3, 5), [[0,0,0,1,0],[0,0,0,0,2],[0,0,0,0,0]])) + cases.append(([a[:1]], [4], (3, 5), [[0,0,0,0,1],[0,0,0,0,0],[0,0,0,0,0]])) + cases.append(([a[:1]], [-4], (5, 3), [[0,0,0],[0,0,0],[0,0,0],[0,0,0],[1,0,0]])) + cases.append(([a[:2]], [-3], (5, 3), [[0,0,0],[0,0,0],[0,0,0],[1,0,0],[0,2,0]])) + cases.append(([a[:3]], [-2], (5, 3), [[0,0,0],[0,0,0],[1,0,0],[0,2,0],[0,0,3]])) + cases.append(([a[:3]], [-1], (5, 3), [[0,0,0],[1,0,0],[0,2,0],[0,0,3],[0,0,0]])) + cases.append(([a[:3]], [0], (5, 3), [[1,0,0],[0,2,0],[0,0,3],[0,0,0],[0,0,0]])) + cases.append(([a[:2]], [1], (5, 3), [[0,1,0],[0,0,2],[0,0,0],[0,0,0],[0,0,0]])) + cases.append(([a[:1]], [2], (5, 3), [[0,0,1],[0,0,0],[0,0,0],[0,0,0],[0,0,0]])) + + cases.append(([a[:3],b[:1]], [0,2], (3, 3), [[1,0,6],[0,2,0],[0,0,3]])) + cases.append(([a[:2],b[:3]], [-1,0], (3, 4), [[6,0,0,0],[1,7,0,0],[0,2,8,0]])) + cases.append(([a[:4],b[:3]], [2,-3], (6, 6), [[0,0,1,0,0,0], + [0,0,0,2,0,0], + [0,0,0,0,3,0], + [6,0,0,0,0,4], + [0,7,0,0,0,0], + [0,0,8,0,0,0]])) + + cases.append(([a[:4],b,c[:4]], [-1,0,1], (5, 5), [[6,11, 0, 0, 0], + [1, 7,12, 0, 0], + [0, 2, 8,13, 0], + [0, 0, 3, 9,14], + [0, 0, 0, 4,10]])) + cases.append(([a[:2],b[:3],c], [-4,2,-1], (6, 5), [[0, 0, 6, 0, 0], + [11, 0, 0, 7, 0], + [0,12, 0, 0, 8], + [0, 0,13, 0, 0], + [1, 0, 0,14, 0], + [0, 2, 0, 0,15]])) + + # too long arrays are OK + cases.append(([a], [0], (1, 1), [[1]])) + cases.append(([a[:3],b], [0,2], (3, 3), [[1, 0, 6], [0, 2, 0], [0, 0, 3]])) + cases.append(( + np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]), + [0,-1], + (3, 3), + [[1, 0, 0], [4, 2, 0], [0, 5, 3]] + )) + + # scalar case: broadcasting + cases.append(([1.0,-2.0,1.0], [1,0,-1], (3, 3), [[-2, 1, 0], + [1, -2, 1], + [0, 1, -2]])) + + for d, o, shape, result in cases: + err_msg = f"{d!r} {o!r} {shape!r} {result!r}" + assert_equal(construct.diags(d, offsets=o, shape=shape).toarray(), + result, err_msg=err_msg) + + if (shape[0] == shape[1] + and hasattr(d[0], '__len__') + and len(d[0]) <= max(shape)): + # should be able to find the shape automatically + assert_equal(construct.diags(d, offsets=o).toarray(), + result, err_msg=err_msg) + + def test_diags_default(self): + a = array([1.0, 2.0, 3.0, 4.0, 5.0]) + assert_equal(construct.diags(a).toarray(), np.diag(a)) + + def test_diags_default_bad(self): + a = array([[1, 2, 3, 4, 5], [2, 3, 4, 5, 6]]) + assert_raises(ValueError, construct.diags, a) + + def test_diags_bad(self): + a = array([1.0, 2.0, 3.0, 4.0, 5.0]) + b = array([6.0, 7.0, 8.0, 9.0, 10.0]) + c = array([11.0, 12.0, 13.0, 14.0, 15.0]) + + cases = [] + cases.append(([a[:0]], 0, (1, 1))) + cases.append(([a[:4],b,c[:3]], [-1,0,1], (5, 5))) + cases.append(([a[:2],c,b[:3]], [-4,2,-1], (6, 5))) + cases.append(([a[:2],c,b[:3]], [-4,2,-1], None)) + cases.append(([], [-4,2,-1], None)) + cases.append(([1.0], [-5], (4, 4))) + cases.append(([a], 0, None)) + + for d, o, shape in cases: + assert_raises(ValueError, construct.diags, d, offsets=o, shape=shape) + + assert_raises(TypeError, construct.diags, [[None]], offsets=[0]) + + def test_diags_vs_diag(self): + # Check that + # + # diags([a, b, ...], [i, j, ...]) == diag(a, i) + diag(b, j) + ... + # + + rng = np.random.RandomState(1234) + + for n_diags in [1, 2, 3, 4, 5, 10]: + n = 1 + n_diags//2 + rng.randint(0, 10) + + offsets = np.arange(-n+1, n-1) + rng.shuffle(offsets) + offsets = offsets[:n_diags] + + diagonals = [rng.rand(n - abs(q)) for q in offsets] + + mat = construct.diags(diagonals, offsets=offsets) + dense_mat = sum([np.diag(x, j) for x, j in zip(diagonals, offsets)]) + + assert_array_almost_equal_nulp(mat.toarray(), dense_mat) + + if len(offsets) == 1: + mat = construct.diags(diagonals[0], offsets=offsets[0]) + dense_mat = np.diag(diagonals[0], offsets[0]) + assert_array_almost_equal_nulp(mat.toarray(), dense_mat) + + def test_diags_dtype(self): + x = construct.diags([2.2], offsets=[0], shape=(2, 2), dtype=int) + assert_equal(x.dtype, int) + assert_equal(x.toarray(), [[2, 0], [0, 2]]) + + def test_diags_one_diagonal(self): + d = [0.0, 1.0, 2.0, 3.0, 4.0] + for k in range(-5, 6): + assert_equal(construct.diags(d, offsets=k).toarray(), + construct.diags([d], offsets=[k]).toarray()) + + def test_diags_empty(self): + x = construct.diags([]) + assert_equal(x.shape, (0, 0)) + + @pytest.mark.parametrize("identity", [construct.identity, construct.eye_array]) + def test_identity(self, identity): + assert_equal(identity(1).toarray(), [[1]]) + assert_equal(identity(2).toarray(), [[1,0],[0,1]]) + + I = identity(3, dtype='int8', format='dia') + assert_equal(I.dtype, np.dtype('int8')) + assert_equal(I.format, 'dia') + + for fmt in sparse_formats: + I = identity(3, format=fmt) + assert_equal(I.format, fmt) + assert_equal(I.toarray(), [[1,0,0],[0,1,0],[0,0,1]]) + + @pytest.mark.parametrize("eye", [construct.eye, construct.eye_array]) + def test_eye(self, eye): + assert_equal(eye(1,1).toarray(), [[1]]) + assert_equal(eye(2,3).toarray(), [[1,0,0],[0,1,0]]) + assert_equal(eye(3,2).toarray(), [[1,0],[0,1],[0,0]]) + assert_equal(eye(3,3).toarray(), [[1,0,0],[0,1,0],[0,0,1]]) + + assert_equal(eye(3,3,dtype='int16').dtype, np.dtype('int16')) + + for m in [3, 5]: + for n in [3, 5]: + for k in range(-5,6): + # scipy.sparse.eye deviates from np.eye here. np.eye will + # create arrays of all 0's when the diagonal offset is + # greater than the size of the array. For sparse arrays + # this makes less sense, especially as it results in dia + # arrays with negative diagonals. Therefore sp.sparse.eye + # validates that diagonal offsets fall within the shape of + # the array. See gh-18555. + if (k > 0 and k > n) or (k < 0 and abs(k) > m): + with pytest.raises( + ValueError, match="Offset.*out of bounds" + ): + eye(m, n, k=k) + + else: + assert_equal( + eye(m, n, k=k).toarray(), + np.eye(m, n, k=k) + ) + if m == n: + assert_equal( + eye(m, k=k).toarray(), + np.eye(m, n, k=k) + ) + + @pytest.mark.parametrize("eye", [construct.eye, construct.eye_array]) + def test_eye_one(self, eye): + assert_equal(eye(1).toarray(), [[1]]) + assert_equal(eye(2).toarray(), [[1,0],[0,1]]) + + I = eye(3, dtype='int8', format='dia') + assert_equal(I.dtype, np.dtype('int8')) + assert_equal(I.format, 'dia') + + for fmt in sparse_formats: + I = eye(3, format=fmt) + assert_equal(I.format, fmt) + assert_equal(I.toarray(), [[1,0,0],[0,1,0],[0,0,1]]) + + def test_eye_array_vs_matrix(self): + assert isinstance(construct.eye_array(3), sparray) + assert not isinstance(construct.eye(3), sparray) + + @pytest.mark.parametrize("arr,kw_format,out_format", [ + ([[0, 0], [0, 1]], None, 'coo'), # 2D sparse + ([[1, 0], [1, 1]], None, 'bsr'), # 2D dense + ([[[1, 0], [1, 1]]], None, 'coo'), # 3D dense + ]) + def test_kron_output_format(self, arr, kw_format, out_format): + sparr = coo_array(arr) + assert construct.kron(sparr, sparr, format=kw_format).format == out_format + assert construct.kron(sparr, arr, format=kw_format).format == out_format + assert construct.kron(arr, sparr, format=kw_format).format == out_format + + def test_kron(self): + cases = [] + + cases.append(array([[0]])) + cases.append(array([[-1]])) + cases.append(array([[4]])) + cases.append(array([[10]])) + cases.append(array([[0], [0]])) + cases.append(array([[0, 0]])) + cases.append(array([[1, 2], [3, 4]])) + cases.append(array([[0, 2], [5, 0]])) + cases.append(array([[0, 2, -6], [8, 0, 14]])) + cases.append(array([[5, 4], [0, 0], [6, 0]])) + cases.append(array([[5, 4, 4], [1, 0, 0], [6, 0, 8]])) + cases.append(array([[0, 1, 0, 2, 0, 5, 8]])) + cases.append(array([[0.5, 0.125, 0, 3.25], [0, 2.5, 0, 0]])) + + # test all cases with some formats + for a in cases: + ca = csr_array(a) + for b in cases: + cb = csr_array(b) + expected = np.kron(a, b) + for fmt in sparse_formats[1:4]: + result = construct.kron(ca, cb, format=fmt) + assert_equal(result.format, fmt) + assert_array_equal(result.toarray(), expected) + assert isinstance(result, sparray) + + # nD cases + cases.append(array([0, 1, 2])) + cases.append(array([[[0, 1, 2], [0, 1, 0]]])) + cases.append(array([[[0, 1]], [[2, 2]], [[1, 0]], [[2, 0]]])) + + for a in cases: + ca = coo_array(a) + for b in cases: + cb = coo_array(b) + expected = np.kron(a, b) + result = construct.kron(ca, cb, format="coo") + assert_array_equal(result.toarray(), expected) + + # test one case with all formats + a = array([[0.5, 0.125, 0, 3.25], [0, 2.5, 0, 0]]) + b = array([[5, 4, 4], [1, 0, 0], [6, 0, 8]]) + ca = csr_array(a) + cb = csr_array(b) + + expected = np.kron(a, b) + for fmt in sparse_formats: + result = construct.kron(ca, cb, format=fmt) + assert_equal(result.format, fmt) + assert_array_equal(result.toarray(), expected) + assert isinstance(result, sparray) + + # check that spmatrix returned when both inputs are spmatrix + result = construct.kron(csr_matrix(a), csr_matrix(b), format=fmt) + assert_equal(result.format, fmt) + assert_array_equal(result.toarray(), expected) + assert isinstance(result, spmatrix) + + @pytest.mark.filterwarnings("ignore:.*switching.*sparse array:DeprecationWarning") + def test_kron_ndim_exceptions(self): + # spmatrix is default, so exceptions with 3D unless sparse arrays are input + with pytest.raises(TypeError, match='expected 2D array or matrix'): + construct.kron([[0], [1]], [[[0, 1]]]) + with pytest.raises(TypeError, match="expected 2D array or matrix"): + construct.kron([[[1, 1]]], [[1], [1]]) + + # no exception for 3D if any sparse arrays input + construct.kron(coo_array([[[0, 1]]]), [[[0], [1]]]) + construct.kron([[[0, 1]]], coo_array([[[0], [1]]])) + + # no exception for 1D if either sparray or spmatrix + construct.kron([[0], [1]], [0, 1, 0]) # spmatrix b/c lists; 1d-list -> 2d + construct.kron([1, 1], [[1], [1]]) + construct.kron([[0], [1]], coo_array([0, 1, 0])) # sparray 1d-list -> 1d + construct.kron(coo_array([1, 1]), [[1], [1]]) + + def test_kron_large(self): + n = 2**16 + a = construct.diags_array([1], shape=(1, n), offsets=n-1, dtype=None) + b = construct.diags_array([1], shape=(n, 1), offsets=1-n, dtype=None) + + construct.kron(a, a) + construct.kron(b, b) + + def test_kronsum(self): + cases = [] + + cases.append(array([[0]])) + cases.append(array([[-1]])) + cases.append(array([[4]])) + cases.append(array([[10]])) + cases.append(array([[1,2],[3,4]])) + cases.append(array([[0,2],[5,0]])) + cases.append(array([[0,2,-6],[8,0,14],[0,3,0]])) + cases.append(array([[1,0,0],[0,5,-1],[4,-2,8]])) + + # test all cases with default format + for a in cases: + for b in cases: + result = construct.kronsum(csr_array(a), csr_array(b)).toarray() + expected = (np.kron(np.eye(b.shape[0]), a) + + np.kron(b, np.eye(a.shape[0]))) + assert_array_equal(result, expected) + + # check that spmatrix returned when both inputs are spmatrix + result = construct.kronsum(csr_matrix(a), csr_matrix(b)).toarray() + assert_array_equal(result, expected) + + @pytest.mark.filterwarnings("ignore:.*switching.*sparse array:DeprecationWarning") + def test_kronsum_ndim_exceptions(self): + with pytest.raises(ValueError, match='requires 2D input'): + construct.kronsum([[0], [1]], csr_array([0, 1])) + with pytest.raises(ValueError, match='requires 2D input'): + construct.kronsum(csr_array([0, 1]), [[0], [1]]) + # no exception if sparse arrays are not input (spmatrix inferred) + construct.kronsum([[0, 1], [1, 0]], [2]) + + @pytest.mark.parametrize("coo_cls", [coo_matrix, coo_array]) + def test_vstack(self, coo_cls): + A = coo_cls([[1,2],[3,4]]) + B = coo_cls([[5,6]]) + + expected = array([[1, 2], + [3, 4], + [5, 6]]) + assert_equal(construct.vstack([A, B]).toarray(), expected) + assert_equal(construct.vstack([A, B], dtype=np.float32).dtype, + np.float32) + + assert_equal(construct.vstack([A.todok(), B.todok()]).toarray(), expected) + + assert_equal(construct.vstack([A.tocsr(), B.tocsr()]).toarray(), + expected) + result = construct.vstack([A.tocsr(), B.tocsr()], + format="csr", dtype=np.float32) + assert_equal(result.dtype, np.float32) + assert_equal(result.indices.dtype, np.int32) + assert_equal(result.indptr.dtype, np.int32) + + assert_equal(construct.vstack([A.tocsc(), B.tocsc()]).toarray(), + expected) + result = construct.vstack([A.tocsc(), B.tocsc()], + format="csc", dtype=np.float32) + assert_equal(result.dtype, np.float32) + assert_equal(result.indices.dtype, np.int32) + assert_equal(result.indptr.dtype, np.int32) + + def test_vstack_maintain64bit_idx_dtype(self): + # see gh-20389 v/hstack returns int32 idx_dtype with input int64 idx_dtype + X = csr_array([[1, 0, 0], [0, 1, 0], [0, 1, 0]]) + X.indptr = X.indptr.astype(np.int64) + X.indices = X.indices.astype(np.int64) + assert construct.vstack([X, X]).indptr.dtype == np.int64 + assert construct.hstack([X, X]).indptr.dtype == np.int64 + + X = csc_array([[1, 0, 0], [0, 1, 0], [0, 1, 0]]) + X.indptr = X.indptr.astype(np.int64) + X.indices = X.indices.astype(np.int64) + assert construct.vstack([X, X]).indptr.dtype == np.int64 + assert construct.hstack([X, X]).indptr.dtype == np.int64 + + X = coo_array([[1, 0, 0], [0, 1, 0], [0, 1, 0]]) + X.coords = tuple(co.astype(np.int64) for co in X.coords) + assert construct.vstack([X, X]).coords[0].dtype == np.int64 + assert construct.hstack([X, X]).coords[0].dtype == np.int64 + + def test_vstack_matrix_or_array(self): + A = [[1,2],[3,4]] + B = [[5,6]] + assert isinstance(construct.vstack([coo_array(A), coo_array(B)]), sparray) + assert isinstance(construct.vstack([coo_array(A), coo_matrix(B)]), sparray) + assert isinstance(construct.vstack([coo_matrix(A), coo_array(B)]), sparray) + assert isinstance(construct.vstack([coo_matrix(A), coo_matrix(B)]), spmatrix) + + def test_vstack_1d_with_2d(self): + # fixes gh-21064 + arr = csr_array([[1, 0, 0], [0, 1, 0]]) + arr1d = csr_array([1, 0, 0]) + arr1dcoo = coo_array([1, 0, 0]) + assert construct.vstack([arr, np.array([0, 0, 0])]).shape == (3, 3) + assert construct.hstack([arr1d, np.array([[0]])]).shape == (1, 4) + assert construct.hstack([arr1d, arr1d]).shape == (1, 6) + assert construct.vstack([arr1d, arr1d]).shape == (2, 3) + + # check csr specialty stacking code like _stack_along_minor_axis + assert construct.hstack([arr, arr]).shape == (2, 6) + assert construct.hstack([arr1d, arr1d]).shape == (1, 6) + + assert construct.hstack([arr1d, arr1dcoo]).shape == (1, 6) + assert construct.vstack([arr, arr1dcoo]).shape == (3, 3) + assert construct.vstack([arr1d, arr1dcoo]).shape == (2, 3) + + with pytest.raises(ValueError, match="incompatible row dimensions"): + construct.hstack([arr, np.array([0, 0])]) + with pytest.raises(ValueError, match="incompatible column dimensions"): + construct.vstack([arr, np.array([0, 0])]) + + @pytest.mark.parametrize("coo_cls", [coo_matrix, coo_array]) + def test_hstack(self, coo_cls): + A = coo_cls([[1,2],[3,4]]) + B = coo_cls([[5],[6]]) + + expected = array([[1, 2, 5], + [3, 4, 6]]) + assert_equal(construct.hstack([A, B]).toarray(), expected) + assert_equal(construct.hstack([A, B], dtype=np.float32).dtype, + np.float32) + + assert_equal(construct.hstack([A.todok(), B.todok()]).toarray(), expected) + + assert_equal(construct.hstack([A.tocsc(), B.tocsc()]).toarray(), + expected) + assert_equal(construct.hstack([A.tocsc(), B.tocsc()], + dtype=np.float32).dtype, + np.float32) + assert_equal(construct.hstack([A.tocsr(), B.tocsr()]).toarray(), + expected) + assert_equal(construct.hstack([A.tocsr(), B.tocsr()], + dtype=np.float32).dtype, + np.float32) + + def test_hstack_matrix_or_array(self): + A = [[1,2],[3,4]] + B = [[5],[6]] + assert isinstance(construct.hstack([coo_array(A), coo_array(B)]), sparray) + assert isinstance(construct.hstack([coo_array(A), coo_matrix(B)]), sparray) + assert isinstance(construct.hstack([coo_matrix(A), coo_array(B)]), sparray) + assert isinstance(construct.hstack([coo_matrix(A), coo_matrix(B)]), spmatrix) + + @pytest.mark.parametrize("block_array", (construct.bmat, construct.block_array)) + def test_block_creation(self, block_array): + + A = coo_array([[1, 2], [3, 4]]) + B = coo_array([[5],[6]]) + C = coo_array([[7]]) + D = coo_array((0, 0)) + + expected = array([[1, 2, 5], + [3, 4, 6], + [0, 0, 7]]) + assert_equal(block_array([[A, B], [None, C]]).toarray(), expected) + E = csr_array((1, 2), dtype=np.int32) + assert_equal(block_array([[A.tocsr(), B.tocsr()], + [E, C.tocsr()]]).toarray(), + expected) + assert_equal(block_array([[A.tocsc(), B.tocsc()], + [E.tocsc(), C.tocsc()]]).toarray(), + expected) + + expected = array([[1, 2, 0], + [3, 4, 0], + [0, 0, 7]]) + assert_equal(block_array([[A, None], [None, C]]).toarray(), expected) + assert_equal(block_array([[A.tocsr(), E.T.tocsr()], + [E, C.tocsr()]]).toarray(), + expected) + assert_equal(block_array([[A.tocsc(), E.T.tocsc()], + [E.tocsc(), C.tocsc()]]).toarray(), + expected) + + Z = csr_array((1, 1), dtype=np.int32) + expected = array([[0, 5], + [0, 6], + [7, 0]]) + assert_equal(block_array([[None, B], [C, None]]).toarray(), expected) + assert_equal(block_array([[E.T.tocsr(), B.tocsr()], + [C.tocsr(), Z]]).toarray(), + expected) + assert_equal(block_array([[E.T.tocsc(), B.tocsc()], + [C.tocsc(), Z.tocsc()]]).toarray(), + expected) + + expected = np.empty((0, 0)) + assert_equal(block_array([[None, None]]).toarray(), expected) + assert_equal(block_array([[None, D], [D, None]]).toarray(), + expected) + + # test bug reported in gh-5976 + expected = array([[7]]) + assert_equal(block_array([[None, D], [C, None]]).toarray(), + expected) + + # test failure cases + with assert_raises(ValueError) as excinfo: + block_array([[A], [B]]) + excinfo.match(r'Got blocks\[1,0\]\.shape\[1\] == 1, expected 2') + + with assert_raises(ValueError) as excinfo: + block_array([[A.tocsr()], [B.tocsr()]]) + excinfo.match(r'incompatible dimensions for axis 1') + + with assert_raises(ValueError) as excinfo: + block_array([[A.tocsc()], [B.tocsc()]]) + excinfo.match(r'Mismatching dimensions along axis 1: ({1, 2}|{2, 1})') + + with assert_raises(ValueError) as excinfo: + block_array([[A, C]]) + excinfo.match(r'Got blocks\[0,1\]\.shape\[0\] == 1, expected 2') + + with assert_raises(ValueError) as excinfo: + block_array([[A.tocsr(), C.tocsr()]]) + excinfo.match(r'Mismatching dimensions along axis 0: ({1, 2}|{2, 1})') + + with assert_raises(ValueError) as excinfo: + block_array([[A.tocsc(), C.tocsc()]]) + excinfo.match(r'incompatible dimensions for axis 0') + + def test_block_return_type(self): + block = construct.block_array + + # csr format ensures we hit _compressed_sparse_stack + # shape of F,G ensure we hit _stack_along_minor_axis + # list version ensure we hit the path with neither helper function + Fl, Gl = [[1, 2],[3, 4]], [[7], [5]] + Fm, Gm = csr_matrix(Fl), csr_matrix(Gl) + assert isinstance(block([[None, Fl], [Gl, None]], format="csr"), sparray) + assert isinstance(block([[None, Fm], [Gm, None]], format="csr"), sparray) + assert isinstance(block([[Fm, Gm]], format="csr"), sparray) + + def test_bmat_return_type(self): + """This can be removed after sparse matrix is removed""" + bmat = construct.bmat + # check return type. if any input _is_array output array, else matrix + Fl, Gl = [[1, 2],[3, 4]], [[7], [5]] + Fm, Gm = csr_matrix(Fl), csr_matrix(Gl) + Fa, Ga = csr_array(Fl), csr_array(Gl) + assert isinstance(bmat([[Fa, Ga]], format="csr"), sparray) + assert isinstance(bmat([[Fm, Gm]], format="csr"), spmatrix) + assert isinstance(bmat([[None, Fa], [Ga, None]], format="csr"), sparray) + assert isinstance(bmat([[None, Fm], [Ga, None]], format="csr"), sparray) + assert isinstance(bmat([[None, Fm], [Gm, None]], format="csr"), spmatrix) + assert isinstance(bmat([[None, Fl], [Gl, None]], format="csr"), spmatrix) + + # type returned by _compressed_sparse_stack (all csr) + assert isinstance(bmat([[Ga, Ga]], format="csr"), sparray) + assert isinstance(bmat([[Gm, Ga]], format="csr"), sparray) + assert isinstance(bmat([[Ga, Gm]], format="csr"), sparray) + assert isinstance(bmat([[Gm, Gm]], format="csr"), spmatrix) + # shape is 2x2 so no _stack_along_minor_axis + assert isinstance(bmat([[Fa, Fm]], format="csr"), sparray) + assert isinstance(bmat([[Fm, Fm]], format="csr"), spmatrix) + + # type returned by _compressed_sparse_stack (all csc) + assert isinstance(bmat([[Gm.tocsc(), Ga.tocsc()]], format="csc"), sparray) + assert isinstance(bmat([[Gm.tocsc(), Gm.tocsc()]], format="csc"), spmatrix) + # shape is 2x2 so no _stack_along_minor_axis + assert isinstance(bmat([[Fa.tocsc(), Fm.tocsc()]], format="csr"), sparray) + assert isinstance(bmat([[Fm.tocsc(), Fm.tocsc()]], format="csr"), spmatrix) + + # type returned when mixed input + assert isinstance(bmat([[Gl, Ga]], format="csr"), sparray) + assert isinstance(bmat([[Gm.tocsc(), Ga]], format="csr"), sparray) + assert isinstance(bmat([[Gm.tocsc(), Gm]], format="csr"), spmatrix) + assert isinstance(bmat([[Gm, Gm]], format="csc"), spmatrix) + + @pytest.mark.xslow + @pytest.mark.xfail_on_32bit("Can't create large array for test") + def test_concatenate_int32_overflow(self): + """ test for indptr overflow when concatenating matrices """ + check_free_memory(30000) + + n = 33000 + A = csr_array(np.ones((n, n), dtype=bool)) + B = A.copy() + C = construct._compressed_sparse_stack((A, B), axis=0, + return_spmatrix=False) + + assert_(np.all(np.equal(np.diff(C.indptr), n))) + assert_equal(C.indices.dtype, np.int64) + assert_equal(C.indptr.dtype, np.int64) + + def test_block_diag_basic(self): + """ basic test for block_diag """ + A = coo_array([[1,2],[3,4]]) + B = coo_array([[5],[6]]) + C = coo_array([[7]]) + + expected = array([[1, 2, 0, 0], + [3, 4, 0, 0], + [0, 0, 5, 0], + [0, 0, 6, 0], + [0, 0, 0, 7]]) + + ABC = construct.block_diag((A, B, C)) + assert_equal(ABC.toarray(), expected) + assert ABC.coords[0].dtype == np.int32 + + def test_block_diag_idx_dtype(self): + X = coo_array([[1, 0, 0], [0, 1, 0], [0, 1, 0]]) + X.coords = tuple(co.astype(np.int64) for co in X.coords) + assert construct.block_diag([X, X]).coords[0].dtype == np.int64 + + @pytest.mark.filterwarnings("ignore:.*switching.*sparse array:DeprecationWarning") + def test_block_diag_scalar_1d_args(self): + """ block_diag with scalar and 1d arguments """ + # one 1d matrix and a scalar + assert_array_equal(construct.block_diag([[2,3], 4]).toarray(), + [[2, 3, 0], [0, 0, 4]]) + # 1d sparse arrays + A = coo_array([1,0,3]) + B = coo_array([0,4]) + assert_array_equal(construct.block_diag([A, B]).toarray(), + [[1, 0, 3, 0, 0], [0, 0, 0, 0, 4]]) + + @pytest.mark.filterwarnings("ignore:.*switching.*sparse array:DeprecationWarning") + def test_block_diag_1(self): + """ block_diag with one matrix """ + assert_equal(construct.block_diag([[1, 0]]).toarray(), + array([[1, 0]])) + assert_equal(construct.block_diag([[[1, 0]]]).toarray(), + array([[1, 0]])) + assert_equal(construct.block_diag([[[1], [0]]]).toarray(), + array([[1], [0]])) + # just on scalar + assert_equal(construct.block_diag([1]).toarray(), + array([[1]])) + + def test_block_diag_sparse_arrays(self): + """ block_diag with sparse arrays """ + + A = coo_array([[1, 2, 3]], shape=(1, 3)) + B = coo_array([[4, 5]], shape=(1, 2)) + assert_equal(construct.block_diag([A, B]).toarray(), + array([[1, 2, 3, 0, 0], [0, 0, 0, 4, 5]])) + + A = coo_array([[1], [2], [3]], shape=(3, 1)) + B = coo_array([[4], [5]], shape=(2, 1)) + assert_equal(construct.block_diag([A, B]).toarray(), + array([[1, 0], [2, 0], [3, 0], [0, 4], [0, 5]])) + + def test_block_diag_return_type(self): + A, B = coo_array([[1, 2, 3]]), coo_matrix([[2, 3, 4]]) + assert isinstance(construct.block_diag([A, A]), sparray) + assert isinstance(construct.block_diag([A, B]), sparray) + assert isinstance(construct.block_diag([B, A]), sparray) + assert isinstance(construct.block_diag([B, B]), spmatrix) + + def test_random_sampling(self): + # Simple sanity checks for sparse random sampling. + for f in sprand, _sprandn: + for t in [np.float32, np.float64, np.longdouble, + np.int32, np.int64, np.complex64, np.complex128]: + x = f(5, 10, density=0.1, dtype=t) + assert_equal(x.dtype, t) + assert_equal(x.shape, (5, 10)) + assert_equal(x.nnz, 5) + + x1 = f(5, 10, density=0.1, rng=4321) + assert_equal(x1.dtype, np.float64) + + x2 = f(5, 10, density=0.1, rng=np.random.default_rng(4321)) + + assert_array_equal(x1.data, x2.data) + assert_array_equal(x1.row, x2.row) + assert_array_equal(x1.col, x2.col) + + for density in [0.0, 0.1, 0.5, 1.0]: + x = f(5, 10, density=density) + assert_equal(x.nnz, int(density * np.prod(x.shape))) + + for fmt in ['coo', 'csc', 'csr', 'lil']: + x = f(5, 10, format=fmt) + assert_equal(x.format, fmt) + + assert_raises(ValueError, lambda: f(5, 10, 1.1)) + assert_raises(ValueError, lambda: f(5, 10, -0.1)) + + @pytest.mark.parametrize("rng", [None, 4321, np.random.default_rng(4321)]) + def test_rand(self, rng): + # Simple distributional checks for sparse.rand. + x = sprand(10, 20, density=0.5, dtype=np.float64, rng=rng) + assert_(np.all(np.less_equal(0, x.data))) + assert_(np.all(np.less_equal(x.data, 1))) + + @pytest.mark.parametrize("rng", [None, 4321, np.random.default_rng(4321)]) + def test_randn(self, rng): + # Simple distributional checks for sparse.randn. + # Statistically, some of these should be negative + # and some should be greater than 1. + x = _sprandn(10, 20, density=0.5, dtype=np.float64, rng=rng) + assert_(np.any(np.less(x.data, 0))) + assert_(np.any(np.less(1, x.data))) + x = _sprandn_array(10, 20, density=0.5, dtype=np.float64, rng=rng) + assert_(np.any(np.less(x.data, 0))) + assert_(np.any(np.less(1, x.data))) + + def test_random_accept_str_dtype(self): + # anything that np.dtype can convert to a dtype should be accepted + # for the dtype + construct.random(10, 10, dtype='d') + construct.random_array((10, 10), dtype='d') + construct.random_array((10, 10, 10), dtype='d') + construct.random_array((10, 10, 10, 10, 10), dtype='d') + + def test_random_array_maintains_array_shape(self): + # preserve use of old random_state during SPEC 7 transition + arr = construct.random_array((0, 4), density=0.3, dtype=int, random_state=0) + assert arr.shape == (0, 4) + + arr = construct.random_array((10, 10, 10), density=0.3, dtype=int, rng=0) + assert arr.shape == (10, 10, 10) + + arr = construct.random_array((10, 10, 10, 10, 10), density=0.3, dtype=int, + rng=0) + assert arr.shape == (10, 10, 10, 10, 10) + + def test_random_array_idx_dtype(self): + A = construct.random_array((10, 10)) + assert A.coords[0].dtype == np.int32 + + def test_random_sparse_matrix_returns_correct_number_of_non_zero_elements(self): + # A 10 x 10 matrix, with density of 12.65%, should have 13 nonzero elements. + # 10 x 10 x 0.1265 = 12.65, which should be rounded up to 13, not 12. + sparse_matrix = construct.random(10, 10, density=0.1265) + assert_equal(sparse_matrix.count_nonzero(),13) + # check random_array + sparse_array = construct.random_array((10, 10), density=0.1265) + assert_equal(sparse_array.count_nonzero(),13) + assert isinstance(sparse_array, sparray) + # check big size + shape = (2**33, 2**33) + sparse_array = construct.random_array(shape, density=2.7105e-17) + assert_equal(sparse_array.count_nonzero(),2000) + + # for n-D + # check random_array + sparse_array = construct.random_array((10, 10, 10, 10), density=0.12658) + assert_equal(sparse_array.count_nonzero(),1266) + assert isinstance(sparse_array, sparray) + # check big size + shape = (2**33, 2**33, 2**33) + sparse_array = construct.random_array(shape, density=2.7105e-28) + assert_equal(sparse_array.count_nonzero(),172) + + +def test_deprecated_warnings_output_defaults_switch_from_spmatrix(): + A = B = np.array([[1, 0], [1, 0]]) + with pytest.deprecated_call(match=".*switching.*sparse array int"): + construct.kron(A, B) + with pytest.deprecated_call(match=".*switching.*sparse array int"): + construct.kronsum(A, B) + # Note: vstack hstack and bmat do not support all dense input. So no default. + with pytest.deprecated_call(match=".*switching.*sparse array int"): + construct.block_diag([A, B]) + + +def test_diags_array(): + """Tests of diags_array that do not rely on diags wrapper.""" + diag = np.arange(1.0, 5.0) + + assert_array_equal(construct.diags_array(diag, dtype=None).toarray(), np.diag(diag)) + + assert_array_equal( + construct.diags_array(diag, offsets=2, dtype=None).toarray(), np.diag(diag, k=2) + ) + + assert_array_equal( + construct.diags_array(diag, offsets=2, shape=(4, 4), dtype=None).toarray(), + np.diag(diag, k=2)[:4, :4] + ) + + # Offset outside bounds when shape specified + with pytest.raises(ValueError, match=".*out of bounds"): + construct.diags(np.arange(1.0, 5.0), 5, shape=(4, 4)) + + +@pytest.mark.parametrize('func', (construct.diags_array, construct.diags)) +def test_diags_int(func): + d = [[3], [1, 2], [4]] + offsets = [-1, 0, 1] + # Until the deprecation period is over, `dtype=None` must be given + # explicitly to avoid the warning and the cast to an inexact type + # in diags_array() (gh-23102). + arr = func(d, offsets=offsets, dtype=None) + expected = np.array([[1, 4], [3, 2]]) + assert_array_equal(arr.toarray(), expected, strict=True) + + +@pytest.mark.parametrize('func', (construct.diags_array, construct.diags)) +def test_diags_int_to_float64(func): + d = [[3], [1, 2], [4]] + offsets = [-1, 0, 1] + # Until the deprecation period is over, diags and diag_array will cast + # integer inputs to float64 by default. A warning will be generated + # that indicates this behavior is deprecated. + # See gh-23102. + with pytest.warns(FutureWarning, match="output has been cast to"): + arr = func(d, offsets=offsets) + expected = np.array([[1.0, 4.0], [3.0, 2.0]]) + assert_array_equal(arr.toarray(), expected, strict=True) + + +def test_swapaxes(): + # Borrowed from Numpy swapaxes tests + x = np.array([8.375, 7.545, 8.828, 8.5, 1.757, 5.928, + 8.43, 7.78, 9.865, 5.878, 8.979, 4.732, + 3.012, 6.022, 5.095, 3.116, 5.238, 3.957, + 6.04, 9.63, 7.712, 3.382, 4.489, 6.479, + 7.189, 9.645, 5.395, 4.961, 9.894, 2.893, + 7.357, 9.828, 6.272, 3.758, 6.693, 0.993]) + sX = coo_array(x).reshape(6, 6) + sXswapped = construct.swapaxes(sX, 0, 1) + assert_equal(sXswapped[-1].toarray(), sX[:, -1].toarray()) + + sXX = sX.reshape(3, 2, 2, 3) + sXXswapped = construct.swapaxes(sXX, 0, 2) + assert_equal(sXXswapped.shape, (2, 2, 3, 3)) + + +def test_3d_swapaxes(): + tgt = [[[0, 0], [2, 6]], [[1, 5], [0, 7]]] + x = np.array([[[0, 1], [2, 0]], [[0, 5], [6, 7]]]) + A = coo_array(x) #[[[0, 1], [2, 0]], [[0, 5], [6, 7]]]) + out = construct.swapaxes(A, 0, 2) + assert_equal(out.toarray(), tgt) + assert_equal(out.toarray(), np.swapaxes(x, 0, 2)) + + +@pytest.mark.parametrize("format", sparse_formats) +def test_sparse_format_swapaxes(format): + A = np.array([[2, 0, 1], [3, 5, 0]]) + SA = coo_array(A).asformat(format) + + out = construct.swapaxes(SA, 1, 0) + assert out.format == "coo" + assert out.shape == (3, 2) + assert_equal(out.toarray(), np.swapaxes(A, 1, 0)) + assert not out.has_canonical_format + + +def test_axis_swapaxes(): + A = coo_array([[2, 0], [3, 5]]) + with assert_raises(ValueError, match="Invalid axis"): + construct.swapaxes(A, -4, 0) + with assert_raises(ValueError, match="Invalid axis"): + construct.swapaxes(A, 0, -4) + with assert_raises(ValueError, match="Invalid axis"): + construct.swapaxes(A, 3, 0) + with assert_raises(ValueError, match="Invalid axis"): + construct.swapaxes(A, 0, 3) + with assert_raises(ValueError, match="Invalid axis"): + construct.swapaxes(A, 1.2, 1) + with assert_raises(ValueError, match="Invalid axis"): + construct.swapaxes(A, 1, 1.2) + assert_equal(construct.swapaxes(A, 0, 0).toarray(), A.toarray()) + for i in range(2): + assert_equal( + construct.swapaxes(A, i, 1 - i).toarray(), + construct.swapaxes(A, i - 2, -1 - i).toarray() + ) + + +def test_permute_dims(): + # Borrowed from Numpy tests. + x = np.array([8.375, 7.545, 8.828, 8.5, 1.757, 5.928, + 8.43, 7.78, 9.865, 5.878, 8.979, 4.732, + 3.012, 6.022, 5.095, 3.116, 5.238, 3.957, + 6.04, 9.63, 7.712, 3.382, 4.489, 6.479, + 7.189, 9.645, 5.395, 4.961, 9.894, 2.893, + 7.357, 9.828, 6.272, 3.758, 6.693, 0.993]) + npx = x.reshape(6, 6) + sX = coo_array(x).reshape(6, 6) + + sXpermuted = construct.permute_dims(sX, axes=(1, 0), copy=True) + sXtransposed = sX.transpose(axes=(1, 0)) + assert_equal(sXpermuted.toarray(), sXtransposed.toarray()) + assert_equal(sXpermuted[-1].toarray(), sX[:, -1].toarray()) + + npxx = npx.reshape(3, 2, 2, 3) + sXX = sX.reshape(3, 2, 2, 3) + sXXpermuted = construct.permute_dims(sXX, axes=(0, 2, 1, 3), copy=True) + assert_equal(sXXpermuted.shape, (3, 2, 2, 3)) + sXXtransposed = sXX.transpose(axes=(0, 2, 1, 3)) + assert_equal(sXXtransposed.shape, (3, 2, 2, 3)) + assert_equal(sXXpermuted.toarray(), sXXtransposed.toarray()) + # TODO change np.transpose to np.permute_dims when numpy 2 is min supported version + assert_equal(sXXpermuted.toarray(), np.transpose(npxx, axes=(0, 2, 1, 3))) + + +def test_3d_permute_dims(): + tgt = [[[0], [2], [0], [6]], [[1], [0], [5], [7]]] + x = np.array([[[0, 1], [2, 0], [0, 5], [6, 7]]]) + A = coo_array(x) + + out = construct.permute_dims(A, axes=(2, 1, 0)) + assert_equal(out.shape, (2, 4, 1)) + assert_equal(out.toarray(), tgt) + # TODO change np.transpose to np.permute_dims when numpy 2 is min supported version + assert_equal(out.toarray(), np.transpose(x, axes=(2, 1, 0))) + + +def test_canonical_format_permute_dims(): + A = coo_array([[2, 0, 1], [3, 5, 0]]) + # identity axes keep has_canoncial_format True after permute_dims. + assert construct.permute_dims(A, axes=(0, 1)).has_canonical_format is True + assert construct.permute_dims(A, axes=[0, 1]).has_canonical_format is True + # order changes set has_canonical_format to False + assert construct.permute_dims(A, axes=[1, 0]).has_canonical_format is False + + +def test_axis_permute_dims(): + A = coo_array([[2, 0, 1], [3, 5, 0]]) + + with assert_raises(ValueError, match="Incorrect number of axes"): + construct.permute_dims(A, axes=(2, 0, 1)) + with assert_raises(ValueError, match="duplicate value in axis"): + construct.permute_dims(A, axes=(0, 0)) + with assert_raises(TypeError, match="axis must be an integer/tuple"): + construct.permute_dims(A, axes={1, 0}) + + with assert_raises(ValueError, match="axis out of range"): + construct.permute_dims(A, axes=(-3, 0)) + with assert_raises(ValueError, match="axis out of range"): + construct.permute_dims(A, axes=(0, -3)) + with assert_raises(ValueError, match="axis out of range"): + construct.permute_dims(A, axes=(2, 0)) + with assert_raises(ValueError, match="axis out of range"): + construct.permute_dims(A, axes=(0, 2)) + with assert_raises(TypeError, match="axis must be an integer"): + construct.permute_dims(A, axes=(1.2, 0)) + + assert_equal( + construct.permute_dims(A, axes=(1, 0), copy=True).toarray(), + A.transpose(axes=(1, 0), copy=True).toarray() + ) + # use lists for axes + assert_equal( + construct.permute_dims(A, axes=[1, 0], copy=True).toarray(), + A.transpose(axes=[1, 0], copy=True).toarray() + ) + assert_equal( + construct.permute_dims(A, axes=None, copy=True).toarray(), + A.transpose(axes=(1, 0), copy=True).toarray() + ) + assert_equal( + construct.permute_dims(A, axes=(0, 1), copy=True).toarray(), A.toarray() + ) + + +@pytest.mark.parametrize("format", sparse_formats) +def test_sparse_format_permute_dims(format): + A = np.array([[2, 0, 1], [3, 5, 0]]) + SA = coo_array(A).asformat(format) + + out = construct.permute_dims(SA, axes=(1, 0)) + assert out.format == "coo" + assert out.shape == (3, 2) + # TODO change np.transpose to np.permute_dims when numpy 2 is min supported version + assert_equal(out.toarray(), np.transpose(A, axes=(1, 0))) + assert not out.has_canonical_format + + +def test_expand_dims(): + # Borrowed from Numpy tests. + x = np.array([8.375, 7.545, 8.828, 8.5, 1.757, 5.928, + 8.43, 7.78, 9.865, 5.878, 8.979, 4.732, + 3.012, 6.022, 5.095, 3.116, 5.238, 3.957, + 6.04, 9.63, 7.712, 3.382, 4.489, 6.479, + 7.189, 9.645, 5.395, 4.961, 9.894, 2.893, + 7.357, 9.828, 6.272, 3.758, 6.693, 0.993]) + npx = x.reshape(6, 6) + sX = coo_array(npx) + + npx_expanded = np.expand_dims(npx, axis=1) + sXexpanded = construct.expand_dims(sX, axis=1) + assert_equal(sXexpanded[-1].toarray(), sX[-1, np.newaxis, :].toarray()) + assert_equal(sXexpanded.toarray(), npx_expanded) + + npxx = npx.reshape(3, 2, 2, 3) + sXX = sX.reshape(3, 2, 2, 3) + + npxx_expanded = np.expand_dims(npxx, axis=2) + sXXexpanded = construct.expand_dims(sXX, axis=2) + assert_equal(sXXexpanded.shape, (3, 2, 1, 2, 3)) + assert_equal(sXXexpanded.toarray(), npxx_expanded) + + npxx_expanded = np.expand_dims(npxx, axis=-2) + sXXexpanded = construct.expand_dims(sXX, axis=-2) + assert_equal(sXXexpanded.shape, (3, 2, 2, 1, 3)) + assert_equal(sXXexpanded.toarray(), npxx_expanded) + + +def test_3d_expand_dims(): + tgt = [[[[0, 0], [2, 6]]], [[[1, 5], [0, 7]]]] + A = coo_array([[[0, 0], [2, 6]], [[1, 5], [0, 7]]]) + out = construct.expand_dims(A, axis=1) + assert_equal(out.toarray(), tgt) + + +@pytest.mark.parametrize("format", sparse_formats) +def test_sparse_format_expand_dims(format): + A = np.array([[2, 0], [3, 5]]) + SA = coo_array(A).asformat(format) + + out = construct.expand_dims(SA, axis=1) + assert out.format == "coo" + assert out.shape == (2, 1, 2) + assert_equal(out.toarray(), np.expand_dims(A, axis=1)) + assert SA.tocoo().has_canonical_format == out.has_canonical_format + + +def test_axis_expand_dims(): + A = coo_array([[2, 0], [3, 5]]) + with assert_raises(ValueError, match="Invalid axis"): + construct.expand_dims(A, axis=-4) + with assert_raises(ValueError, match="Invalid axis"): + construct.expand_dims(A, axis=3) + with assert_raises(ValueError, match="Invalid axis"): + construct.expand_dims(A, axis=1.2) + for i in range(3): + assert_equal( + construct.expand_dims(A, axis=i).toarray(), + construct.expand_dims(A, axis=i - 3).toarray() + ) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_coo.py b/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_coo.py new file mode 100644 index 0000000000000000000000000000000000000000..d1d7a58e2599b0719a712a89f126c00fb46cf857 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_coo.py @@ -0,0 +1,1407 @@ +import math +import warnings + +import numpy as np +from numpy.testing import assert_equal, assert_allclose +import pytest +from scipy.linalg import block_diag +from scipy.sparse import ( + coo_array, random_array, SparseEfficiencyWarning, matrix_transpose +) +from scipy.sparse._csr import csr_array +from .._coo import _block_diag, _extract_block_diag + + +def test_shape_constructor(): + empty1d = coo_array((3,)) + assert empty1d.shape == (3,) + assert_equal(empty1d.toarray(), np.zeros((3,))) + + empty2d = coo_array((3, 2)) + assert empty2d.shape == (3, 2) + assert_equal(empty2d.toarray(), np.zeros((3, 2))) + + empty_nd = coo_array((2,3,4,6,7)) + assert empty_nd.shape == (2,3,4,6,7) + assert_equal(empty_nd.toarray(), np.zeros((2,3,4,6,7))) + + +def test_dense_constructor(): + # 1d + res1d = coo_array([1, 2, 3]) + assert res1d.shape == (3,) + assert_equal(res1d.toarray(), np.array([1, 2, 3])) + + # 2d + res2d = coo_array([[1, 2, 3], [4, 5, 6]]) + assert res2d.shape == (2, 3) + assert_equal(res2d.toarray(), np.array([[1, 2, 3], [4, 5, 6]])) + + # 4d + arr4d = np.array([[[[3, 7], [1, 0]], [[6, 5], [9, 2]]], + [[[4, 3], [2, 8]], [[7, 5], [1, 6]]], + [[[0, 9], [4, 3]], [[2, 1], [7, 8]]]]) + res4d = coo_array(arr4d) + assert res4d.shape == (3, 2, 2, 2) + assert_equal(res4d.toarray(), arr4d) + + # 9d + np.random.seed(12) + arr9d = np.random.randn(2,3,4,7,6,5,3,2,4) + res9d = coo_array(arr9d) + assert res9d.shape == (2,3,4,7,6,5,3,2,4) + assert_equal(res9d.toarray(), arr9d) + + # storing nan as element of sparse array + nan_3d = coo_array([[[1, np.nan]], [[3, 4]], [[5, 6]]]) + assert nan_3d.shape == (3, 1, 2) + assert_equal(nan_3d.toarray(), np.array([[[1, np.nan]], [[3, 4]], [[5, 6]]])) + + +def test_dense_constructor_with_shape(): + res1d = coo_array([1, 2, 3], shape=(3,)) + assert res1d.shape == (3,) + assert_equal(res1d.toarray(), np.array([1, 2, 3])) + + res2d = coo_array([[1, 2, 3], [4, 5, 6]], shape=(2, 3)) + assert res2d.shape == (2, 3) + assert_equal(res2d.toarray(), np.array([[1, 2, 3], [4, 5, 6]])) + + res3d = coo_array([[[3]], [[4]]], shape=(2, 1, 1)) + assert res3d.shape == (2, 1, 1) + assert_equal(res3d.toarray(), np.array([[[3]], [[4]]])) + + np.random.seed(12) + arr7d = np.random.randn(2,4,1,6,5,3,2) + res7d = coo_array((arr7d), shape=(2,4,1,6,5,3,2)) + assert res7d.shape == (2,4,1,6,5,3,2) + assert_equal(res7d.toarray(), arr7d) + + +def test_dense_constructor_with_inconsistent_shape(): + with pytest.raises(ValueError, match='inconsistent shapes'): + coo_array([1, 2, 3], shape=(4,)) + + with pytest.raises(ValueError, match='inconsistent shapes'): + coo_array([1, 2, 3], shape=(3, 1)) + + with pytest.raises(ValueError, match='inconsistent shapes'): + coo_array([[1, 2, 3]], shape=(3,)) + + with pytest.raises(ValueError, match='inconsistent shapes'): + coo_array([[[3]], [[4]]], shape=(1, 1, 1)) + + with pytest.raises(ValueError, + match='axis 0 index 2 exceeds matrix dimension 2'): + coo_array(([1], ([2],)), shape=(2,)) + + with pytest.raises(ValueError, + match='axis 1 index 3 exceeds matrix dimension 3'): + coo_array(([1,3], ([0, 1], [0, 3], [1, 1])), shape=(2, 3, 2)) + + with pytest.raises(ValueError, match='negative axis 0 index: -1'): + coo_array(([1], ([-1],))) + + with pytest.raises(ValueError, match='negative axis 2 index: -1'): + coo_array(([1], ([0], [2], [-1]))) + + +def test_1d_sparse_constructor(): + empty1d = coo_array((3,)) + res = coo_array(empty1d) + assert res.shape == (3,) + assert_equal(res.toarray(), np.zeros((3,))) + + +def test_1d_tuple_constructor(): + res = coo_array(([9,8], ([1,2],))) + assert res.shape == (3,) + assert_equal(res.toarray(), np.array([0, 9, 8])) + + +def test_1d_tuple_constructor_with_shape(): + res = coo_array(([9,8], ([1,2],)), shape=(4,)) + assert res.shape == (4,) + assert_equal(res.toarray(), np.array([0, 9, 8, 0])) + +def test_reshape_overflow(): + # see gh-22353 : new idx_dtype can need to be int64 instead of int32 + M, N = (1045507, 523266) + coords = (np.array([M - 1], dtype='int32'), np.array([N - 1], dtype='int32')) + A = coo_array(([3.3], coords), shape=(M, N)) + + # need new idx_dtype to not overflow + B = A.reshape((M * N, 1)) + assert B.coords[0].dtype == np.dtype('int64') + assert B.coords[0][0] == (M * N) - 1 + + # need idx_dtype to stay int32 if before and after can be int32 + C = A.reshape(N, M) + assert C.coords[0].dtype == np.dtype('int32') + assert C.coords[0][0] == N - 1 + +def test_reshape(): + arr1d = coo_array([1, 0, 3]) + assert arr1d.shape == (3,) + + col_vec = arr1d.reshape((3, 1)) + assert col_vec.shape == (3, 1) + assert_equal(col_vec.toarray(), np.array([[1], [0], [3]])) + + row_vec = arr1d.reshape((1, 3)) + assert row_vec.shape == (1, 3) + assert_equal(row_vec.toarray(), np.array([[1, 0, 3]])) + + # attempting invalid reshape + with pytest.raises(ValueError, match="cannot reshape array"): + arr1d.reshape((3,3)) + + # attempting reshape with a size 0 dimension + with pytest.raises(ValueError, match="cannot reshape array"): + arr1d.reshape((3,0)) + + arr2d = coo_array([[1, 2, 0], [0, 0, 3]]) + assert arr2d.shape == (2, 3) + + flat = arr2d.reshape((6,)) + assert flat.shape == (6,) + assert_equal(flat.toarray(), np.array([1, 2, 0, 0, 0, 3])) + + # 2d to 3d + to_3d_arr = arr2d.reshape((2, 3, 1)) + assert to_3d_arr.shape == (2, 3, 1) + assert_equal(to_3d_arr.toarray(), np.array([[[1], [2], [0]], [[0], [0], [3]]])) + + # attempting invalid reshape + with pytest.raises(ValueError, match="cannot reshape array"): + arr2d.reshape((1,3)) + + +def test_nnz(): + arr1d = coo_array([1, 0, 3]) + assert arr1d.shape == (3,) + assert arr1d.nnz == 2 + + arr2d = coo_array([[1, 2, 0], [0, 0, 3]]) + assert arr2d.shape == (2, 3) + assert arr2d.nnz == 3 + + +def test_transpose(): + arr1d = coo_array([1, 0, 3]).T + assert arr1d.shape == (3,) + assert_equal(arr1d.toarray(), np.array([1, 0, 3])) + + arr2d = coo_array([[1, 2, 0], [0, 0, 3]]) + assert arr2d.T.shape == (3, 2) + desired = np.array([[1, 0], [2, 0], [0, 3]]) + assert_equal(arr2d.T.toarray(), desired) + assert_equal(arr2d.mT.toarray(), desired) + assert_equal(matrix_transpose(arr2d).toarray(), desired) + + +def test_transpose_with_axis(): + arr1d = coo_array([1, 0, 3]).transpose(axes=(0,)) + assert arr1d.shape == (3,) + assert_equal(arr1d.toarray(), np.array([1, 0, 3])) + + arr2d = coo_array([[1, 2, 0], [0, 0, 3]]).transpose(axes=(0, 1)) + assert arr2d.shape == (2, 3) + assert_equal(arr2d.toarray(), np.array([[1, 2, 0], [0, 0, 3]])) + + with pytest.raises(ValueError, match="axes don't match matrix dimensions"): + coo_array([1, 0, 3]).transpose(axes=(0, 1)) + + with pytest.raises(ValueError, match="repeated axis in transpose"): + coo_array([[1, 2, 0], [0, 0, 3]]).transpose(axes=(1, 1)) + + +def test_1d_row_and_col(): + res = coo_array([1, -2, -3]) + assert_equal(res.col, np.array([0, 1, 2])) + assert_equal(res.row, np.zeros_like(res.col)) + assert res.row.dtype == res.col.dtype + assert res.row.flags.writeable is False + + res.col = [1, 2, 3] + assert len(res.coords) == 1 + assert_equal(res.col, np.array([1, 2, 3])) + assert res.row.dtype == res.col.dtype + + with pytest.raises(ValueError, match="cannot set row attribute"): + res.row = [1, 2, 3] + + +def test_1d_toformats(): + res = coo_array([1, -2, -3]) + for f in [res.tobsr, res.tocsc, res.todia, res.tolil]: + with pytest.raises(ValueError, match='Cannot convert'): + f() + for f in [res.tocoo, res.tocsr, res.todok]: + assert_equal(f().toarray(), res.toarray()) + + +@pytest.mark.parametrize('arg', [1, 2, 4, 5, 8]) +def test_1d_resize(arg: int): + den = np.array([1, -2, -3]) + res = coo_array(den) + den.resize(arg, refcheck=False) + res.resize(arg) + assert res.shape == den.shape + assert_equal(res.toarray(), den) + + +@pytest.mark.parametrize('arg', list(zip([1, 2, 3, 4], [1, 2, 3, 4]))) +def test_1d_to_2d_resize(arg: tuple[int, int]): + den = np.array([1, 0, 3]) + res = coo_array(den) + + den.resize(arg, refcheck=False) + res.resize(arg) + assert res.shape == den.shape + assert_equal(res.toarray(), den) + + +@pytest.mark.parametrize('arg', [1, 4, 6, 8]) +def test_2d_to_1d_resize(arg: int): + den = np.array([[1, 0, 3], [4, 0, 0]]) + res = coo_array(den) + den.resize(arg, refcheck=False) + res.resize(arg) + assert res.shape == den.shape + assert_equal(res.toarray(), den) + + +def test_sum_duplicates(): + # 1d case + arr1d = coo_array(([2, 2, 2], ([1, 0, 1],))) + assert arr1d.nnz == 3 + assert_equal(arr1d.toarray(), np.array([2, 4])) + arr1d.sum_duplicates() + assert arr1d.nnz == 2 + assert_equal(arr1d.toarray(), np.array([2, 4])) + + # 4d case + arr4d = coo_array(([2, 3, 7], ([1, 0, 1], [0, 2, 0], [1, 2, 1], [1, 0, 1]))) + assert arr4d.nnz == 3 + expected = np.array( # noqa: E501 + [[[[0, 0], [0, 0], [0, 0]], [[0, 0], [0, 0], [0, 0]], [[0, 0], [0, 0], [3, 0]]], + [[[0, 0], [0, 9], [0, 0]], [[0, 0], [0, 0], [0, 0]], [[0, 0], [0, 0], [0, 0]]]] + ) + assert_equal(arr4d.toarray(), expected) + arr4d.sum_duplicates() + assert arr4d.nnz == 2 + assert_equal(arr4d.toarray(), expected) + + # when there are no duplicates + arr_nodups = coo_array(([1, 2, 3, 4], ([0, 1, 2, 3],))) + assert arr_nodups.nnz == 4 + arr_nodups.sum_duplicates() + assert arr_nodups.nnz == 4 + + +def test_eliminate_zeros(): + arr1d = coo_array(([0, 0, 1], ([1, 0, 1],))) + assert arr1d.nnz == 3 + assert arr1d.count_nonzero() == 1 + assert_equal(arr1d.toarray(), np.array([0, 1])) + arr1d.eliminate_zeros() + assert arr1d.nnz == 1 + assert arr1d.count_nonzero() == 1 + assert_equal(arr1d.toarray(), np.array([0, 1])) + assert_equal(arr1d.col, np.array([1])) + assert_equal(arr1d.row, np.array([0])) + + +def test_1d_add_dense(): + den_a = np.array([0, -2, -3, 0]) + den_b = np.array([0, 1, 2, 3]) + exp = den_a + den_b + res = coo_array(den_a) + den_b + assert type(res) is type(exp) + assert_equal(res, exp) + + +def test_1d_add_sparse(): + den_a = np.array([0, -2, -3, 0]) + den_b = np.array([0, 1, 2, 3]) + dense_sum = den_a + den_b + # this routes through CSR format + sparse_sum = coo_array(den_a) + coo_array(den_b) + assert_equal(dense_sum, sparse_sum.toarray()) + + +def test_1d_matmul_vector(): + den_a = np.array([0, -2, -3, 0]) + den_b = np.array([0, 1, 2, 3]) + exp = den_a @ den_b + res = coo_array(den_a) @ den_b + assert np.ndim(res) == 0 + assert_equal(res, exp) + + +def test_1d_matmul_multivector(): + den = np.array([0, -2, -3, 0]) + other = np.array([[0, 1, 2, 3], [3, 2, 1, 0]]).T + exp = den @ other + res = coo_array(den) @ other + assert type(res) is type(exp) + assert_equal(res, exp) + + +def test_2d_matmul_multivector(): + # sparse-sparse matmul + den = np.array([[0, 1, 2, 3], [3, 2, 1, 0]]) + arr2d = coo_array(den) + exp = den @ den.T + res = arr2d @ arr2d.T + assert_equal(res.toarray(), exp) + + # sparse-dense matmul for self.ndim = 2 + den = np.array([[0, 4, 3, 0, 5], [1, 0, 7, 3, 4]]) + arr2d = coo_array(den) + exp = den @ den.T + res = arr2d @ den.T + assert_equal(res, exp) + + # sparse-dense matmul for self.ndim = 1 + den_a = np.array([[0, 4, 3, 0, 5], [1, 0, 7, 3, 4]]) + den_b = np.array([0, 1, 6, 0, 4]) + arr1d = coo_array(den_b) + exp = den_b @ den_a.T + res = arr1d @ den_a.T + assert_equal(res, exp) + + # sparse-dense matmul for self.ndim = 1 and other.ndim = 2 + den_a = np.array([1, 0, 2]) + den_b = np.array([[3], [4], [0]]) + exp = den_a @ den_b + res = coo_array(den_a) @ den_b + assert_equal(res, exp) + res = coo_array(den_a) @ list(den_b) + assert_equal(res, exp) + + +def test_1d_diagonal(): + den = np.array([0, -2, -3, 0]) + with pytest.raises(ValueError, match='diagonal requires two dimensions'): + coo_array(den).diagonal() + + +@pytest.mark.parametrize('shape', [(0,), (7,), (4,7), (0,0,0), (3,6,2), + (1,0,3), (7,9,3,2,4,5)]) +def test_nd_todense(shape): + np.random.seed(12) + arr = np.random.randint(low=0, high=5, size=shape) + assert_equal(coo_array(arr).todense(), arr) + + +@pytest.mark.parametrize('shape', [(0,), (7,), (4,7), (0,0,0), (3,6,2), + (1,0,3), (7,9,3,2,4,5)]) +def test_nd_sparse_constructor(shape): + empty_arr = coo_array(shape) + res = coo_array(empty_arr) + assert res.shape == (shape) + assert_equal(res.toarray(), np.zeros(shape)) + + +@pytest.mark.parametrize('shape', [(0,), (7,), (4,7), (0,0,0), (3,6,2), + (1,0,3), (7,9,3,2,4,5)]) +def test_nd_tuple_constructor(shape): + np.random.seed(12) + arr = np.random.randn(*shape) + res = coo_array(arr) + assert res.shape == shape + assert_equal(res.toarray(), arr) + + +@pytest.mark.parametrize('shape', [(0,), (7,), (4,7), (0,0,0), (3,6,2), + (1,0,3), (7,9,3,2,4,5)]) +def test_nd_tuple_constructor_with_shape(shape): + np.random.seed(12) + arr = np.random.randn(*shape) + res = coo_array(arr, shape=shape) + assert res.shape == shape + assert_equal(res.toarray(), arr) + + +def test_tuple_constructor_for_dim_size_zero(): + # arrays with a dimension of size 0 + with pytest.raises(ValueError, match='exceeds matrix dimension'): + coo_array(([9, 8], ([1, 2], [1, 0], [2, 1])), shape=(3,4,0)) + + empty_arr = coo_array(([], ([], [], [], [])), shape=(4,0,2,3)) + assert_equal(empty_arr.toarray(), np.empty((4,0,2,3))) + + +@pytest.mark.parametrize(('shape', 'new_shape'), [((4,9,6,5), (3,6,15,4)), + ((4,9,6,5), (36,30)), + ((4,9,6,5), (1080,)), + ((4,9,6,5), (2,3,2,2,3,5,3)),]) +def test_nd_reshape(shape, new_shape): + # reshaping a 4d sparse array + rng = np.random.default_rng(23409823) + + arr4d = random_array(shape, density=0.6, rng=rng, dtype=int) + assert arr4d.shape == shape + den4d = arr4d.toarray() + + exp_arr = den4d.reshape(new_shape) + res_arr = arr4d.reshape(new_shape) + assert res_arr.shape == new_shape + assert_equal(res_arr.toarray(), exp_arr) + + +@pytest.mark.parametrize('shape', [(0,), (7,), (4,7), (0,0,0), (3,6,2), + (1,0,3), (7,9,3,2,4,5)]) +def test_nd_nnz(shape): + rng = np.random.default_rng(23409823) + + arr = random_array(shape, density=0.6, rng=rng, dtype=int) + assert arr.nnz == np.count_nonzero(arr.toarray()) + + +@pytest.mark.parametrize('shape', [(0,), (7,), (4,7), (0,0,0), (3,6,2), + (1,0,3), (7,9,3,2,4,5)]) +def test_nd_transpose(shape): + rng = np.random.default_rng(23409823) + + arr = random_array(shape, density=0.6, rng=rng, dtype=int) + exp_arr = arr.toarray().T + trans_arr = arr.transpose() + assert trans_arr.shape == shape[::-1] + assert_equal(trans_arr.toarray(), exp_arr) + + if len(shape) >= 2: + exp_arr = arr.toarray().mT + trans_arr = arr.mT + assert trans_arr.shape == exp_arr.shape + assert_equal(trans_arr.toarray(), exp_arr) + + trans_arr = matrix_transpose(arr) + assert trans_arr.shape == exp_arr.shape + assert_equal(trans_arr.toarray(), exp_arr) + else: + with pytest.raises(ValueError, match="2-dimensional"): + arr.mT + with pytest.raises(ValueError, match="2-dimensional"): + matrix_transpose(arr) + + +@pytest.mark.parametrize(('shape', 'axis_perm'), [((3,), (0,)), + ((2,3), (0,1)), + ((2,4,3,6,5,3), (1,2,0,5,3,4)),]) +def test_nd_transpose_with_axis(shape, axis_perm): + rng = np.random.default_rng(23409823) + + arr = random_array(shape, density=0.6, rng=rng, dtype=int) + trans_arr = arr.transpose(axes=axis_perm) + assert_equal(trans_arr.toarray(), np.transpose(arr.toarray(), axes=axis_perm)) + + +def test_transpose_with_inconsistent_axis(): + with pytest.raises(ValueError, match="axes don't match matrix dimensions"): + coo_array([1, 0, 3]).transpose(axes=(0, 1)) + + with pytest.raises(ValueError, match="repeated axis in transpose"): + coo_array([[1, 2, 0], [0, 0, 3]]).transpose(axes=(1, 1)) + + +def test_nd_eliminate_zeros(): + # for 3d sparse arrays + arr3d = coo_array(([1, 0, 0, 4], ([0, 1, 1, 2], [0, 1, 0, 1], [1, 1, 2, 0]))) + assert arr3d.nnz == 4 + assert arr3d.count_nonzero() == 2 + assert_equal(arr3d.toarray(), np.array([[[0, 1, 0], [0, 0, 0]], + [[0, 0, 0], [0, 0, 0]], [[0, 0, 0], [4, 0, 0]]])) + arr3d.eliminate_zeros() + assert arr3d.nnz == 2 + assert arr3d.count_nonzero() == 2 + assert_equal(arr3d.toarray(), np.array([[[0, 1, 0], [0, 0, 0]], + [[0, 0, 0], [0, 0, 0]], [[0, 0, 0], [4, 0, 0]]])) + + # for a 5d sparse array when all elements of data array are 0 + coords = ([0, 1, 1, 2], [0, 1, 0, 1], [1, 1, 2, 0], [0, 0, 2, 3], [1, 0, 0, 2]) + arr5d = coo_array(([0, 0, 0, 0], coords)) + assert arr5d.nnz == 4 + assert arr5d.count_nonzero() == 0 + arr5d.eliminate_zeros() + assert arr5d.nnz == 0 + assert arr5d.count_nonzero() == 0 + assert_equal(arr5d.col, np.array([])) + assert_equal(arr5d.row, np.array([])) + assert_equal(arr5d.coords, ([], [], [], [], [])) + + +@pytest.mark.parametrize('shape', [(0,), (7,), (4,7), (0,0,0), (3,6,2), + (1,0,3), (7,9,3,2,4,5)]) +def test_nd_add_dense(shape): + rng = np.random.default_rng(23409823) + sp_x = random_array(shape, density=0.6, rng=rng, dtype=int) + sp_y = random_array(shape, density=0.6, rng=rng, dtype=int) + den_x, den_y = sp_x.toarray(), sp_y.toarray() + exp = den_x + den_y + res = sp_x + den_y + assert type(res) is type(exp) + assert_equal(res, exp) + + +@pytest.mark.parametrize('shape', [(0,), (7,), (4,7), (0,0,0), (3,6,2), + (1,0,3), (7,9,3,2,4,5)]) +def test_nd_add_sparse(shape): + rng = np.random.default_rng(23409823) + sp_x = random_array((shape), density=0.6, rng=rng, dtype=int) + sp_y = random_array((shape), density=0.6, rng=rng, dtype=int) + den_x, den_y = sp_x.toarray(), sp_y.toarray() + + dense_sum = den_x + den_y + sparse_sum = sp_x + sp_y + assert_equal(dense_sum, sparse_sum.toarray()) + + +def test_add_sparse_with_inf(): + # addition of sparse arrays with an inf element + den_a = np.array([[[0], [np.inf]], [[-3], [0]]]) + den_b = np.array([[[0], [1]], [[2], [3]]]) + dense_sum = den_a + den_b + sparse_sum = coo_array(den_a) + coo_array(den_b) + assert_equal(dense_sum, sparse_sum.toarray()) + + +@pytest.mark.parametrize(('a_shape', 'b_shape'), [((7,), (12,)), + ((6,4), (6,5)), + ((5,9,3,2), (9,5,2,3)),]) +def test_nd_add_sparse_with_inconsistent_shapes(a_shape, b_shape): + rng = np.random.default_rng(23409823) + + arr_a = random_array((a_shape), density=0.6, rng=rng, dtype=int) + arr_b = random_array((b_shape), density=0.6, rng=rng, dtype=int) + with pytest.raises(ValueError, match="inconsistent shapes"): + arr_a + arr_b + + +@pytest.mark.parametrize('shape', [(0,), (7,), (4,7), (0,0,0), (3,6,2), + (1,0,3), (7,9,3,2,4,5)]) +def test_nd_sub_dense(shape): + rng = np.random.default_rng(23409823) + sp_x = random_array(shape, density=0.6, rng=rng, dtype=int) + sp_y = random_array(shape, density=0.6, rng=rng, dtype=int) + den_x, den_y = sp_x.toarray(), sp_y.toarray() + exp = den_x - den_y + res = sp_x - den_y + assert type(res) is type(exp) + assert_equal(res, exp) + + +@pytest.mark.parametrize('shape', [(0,), (7,), (4,7), (0,0,0), (3,6,2), + (1,0,3), (7,9,3,2,4,5)]) +def test_nd_sub_sparse(shape): + rng = np.random.default_rng(23409823) + + sp_x = random_array(shape, density=0.6, rng=rng, dtype=int) + sp_y = random_array(shape, density=0.6, rng=rng, dtype=int) + den_x, den_y = sp_x.toarray(), sp_y.toarray() + + dense_sum = den_x - den_y + sparse_sum = sp_x - sp_y + assert_equal(dense_sum, sparse_sum.toarray()) + + +def test_nd_sub_sparse_with_nan(): + # subtraction of sparse arrays with a nan element + den_a = np.array([[[0], [np.nan]], [[-3], [0]]]) + den_b = np.array([[[0], [1]], [[2], [3]]]) + dense_sum = den_a - den_b + sparse_sum = coo_array(den_a) - coo_array(den_b) + assert_equal(dense_sum, sparse_sum.toarray()) + + +@pytest.mark.parametrize(('a_shape', 'b_shape'), [((7,), (12,)), + ((6,4), (6,5)), + ((5,9,3,2), (9,5,2,3)),]) +def test_nd_sub_sparse_with_inconsistent_shapes(a_shape, b_shape): + rng = np.random.default_rng(23409823) + + arr_a = random_array((a_shape), density=0.6, rng=rng, dtype=int) + arr_b = random_array((b_shape), density=0.6, rng=rng, dtype=int) + with pytest.raises(ValueError, match="inconsistent shapes"): + arr_a - arr_b + + +mat_vec_shapes = [ + ((2, 3, 4, 5), (5,)), + ((0, 0), (0,)), + ((2, 3, 4, 7, 8), (8,)), + ((4, 4, 2, 0), (0,)), + ((6, 5, 3, 2, 4), (4, 1)), + ((2,5), (5,)), + ((2, 5), (5, 1)), + ((3,), (3, 1)), + ((4,), (4,)) +] +@pytest.mark.parametrize(('mat_shape', 'vec_shape'), mat_vec_shapes) +def test_nd_matmul_vector(mat_shape, vec_shape): + rng = np.random.default_rng(23409823) + + sp_x = random_array(mat_shape, density=0.6, rng=rng, dtype=int) + sp_y = random_array(vec_shape, density=0.6, rng=rng, dtype=int) + den_x, den_y = sp_x.toarray(), sp_y.toarray() + exp = den_x @ den_y + res = sp_x @ den_y + assert_equal(res,exp) + res = sp_x @ list(den_y) + assert_equal(res,exp) + + +mat_mat_shapes = [ + ((2, 3, 4, 5), (2, 3, 5, 7)), + ((0, 0), (0,)), + ((4, 4, 2, 0), (0,)), + ((7, 8, 3), (3,)), + ((7, 8, 3), (3, 1)), + ((6, 5, 3, 2, 4), (4, 3)), + ((1, 3, 2, 4), (6, 5, 1, 4, 3)), + ((6, 1, 1, 2, 4), (1, 3, 4, 3)), + ((4,), (2, 4, 3)), + ((3,), (5, 6, 7, 3, 2)), + ((4,), (4, 3)), + ((2, 5), (5, 1)), +] +@pytest.mark.parametrize(('mat_shape1', 'mat_shape2'), mat_mat_shapes) +def test_nd_matmul(mat_shape1, mat_shape2): + rng = np.random.default_rng(23409823) + + sp_x = random_array(mat_shape1, density=0.6, random_state=rng, dtype=int) + sp_y = random_array(mat_shape2, density=0.6, random_state=rng, dtype=int) + den_x, den_y = sp_x.toarray(), sp_y.toarray() + exp = den_x @ den_y + # sparse-sparse + res = sp_x @ sp_y + assert_equal(res.toarray(), exp) + # sparse-dense + res = sp_x @ den_y + assert_equal(res, exp) + res = sp_x @ list(den_y) + assert_equal(res, exp) + + # dense-sparse + res = den_x @ sp_y + assert_equal(res, exp) + + +def test_nd_matmul_sparse_with_inconsistent_arrays(): + rng = np.random.default_rng(23409823) + + sp_x = random_array((4,5,7,6,3), density=0.6, random_state=rng, dtype=int) + sp_y = random_array((1,5,3,2,5), density=0.6, random_state=rng, dtype=int) + with pytest.raises(ValueError, match="matmul: dimension mismatch with signature"): + sp_x @ sp_y + with pytest.raises(ValueError, match="matmul: dimension mismatch with signature"): + sp_x @ (sp_y.toarray()) + + sp_z = random_array((1,5,3,2), density=0.6, random_state=rng, dtype=int) + with pytest.raises(ValueError, match="Batch dimensions are not broadcastable"): + sp_x @ sp_z + with pytest.raises(ValueError, match="Batch dimensions are not broadcastable"): + sp_x @ (sp_z.toarray()) + + +def test_dot_1d_1d(): # 1-D inner product + a = coo_array([1,2,3]) + b = coo_array([4,5,6]) + exp = np.dot(a.toarray(), b.toarray()) + res = a.dot(b) + assert_equal(res, exp) + res = a.dot(b.toarray()) + assert_equal(res, exp) + + +def test_dot_sparse_scalar(): + a = coo_array([[1, 2], [3, 4], [5, 6]]) + b = 3 + res = a.dot(b) + exp = np.dot(a.toarray(), b) + assert_equal(res.toarray(), exp) + + +def test_dot_with_inconsistent_shapes(): + arr_a = coo_array([[[1, 2]], [[3, 4]]]) + arr_b = coo_array([4, 5, 6]) + with pytest.raises(ValueError, match="not aligned for n-D dot"): + arr_a.dot(arr_b) + + +def test_matmul_dot_not_implemented(): + arr_a = coo_array([[1, 2], [3, 4]]) + with pytest.raises(TypeError, match="argument not supported type"): + arr_a.dot(None) + with pytest.raises(TypeError, match="arg not supported type"): + arr_a.tensordot(None) + with pytest.raises(TypeError, match="unsupported operand type"): + arr_a @ None + with pytest.raises(TypeError, match="unsupported operand type"): + None @ arr_a + + +dot_shapes = [ + ((3,3), (3,3)), ((4,6), (6,7)), ((1,4), (4,1)), # matrix multiplication 2-D + ((3,2,4,7), (7,)), ((5,), (6,3,5,2)), # dot of n-D and 1-D arrays + ((3,2,4,7), (7,1)), ((1,5,), (6,3,5,2)), + ((4,6), (3,2,6,4)), ((2,8,7), (4,5,7,7,2)), # dot of n-D and m-D arrays + ((4,5,7,6), (3,2,6,4)), +] +@pytest.mark.parametrize(('a_shape', 'b_shape'), dot_shapes) +def test_dot_nd(a_shape, b_shape): + rng = np.random.default_rng(23409823) + + arr_a = random_array(a_shape, density=0.6, random_state=rng, dtype=int) + arr_b = random_array(b_shape, density=0.6, random_state=rng, dtype=int) + + exp = np.dot(arr_a.toarray(), arr_b.toarray()) + # sparse-dense + res = arr_a.dot(arr_b.toarray()) + assert_equal(res, exp) + res = arr_a.dot(list(arr_b.toarray())) + assert_equal(res, exp) + # sparse-sparse + res = arr_a.dot(arr_b) + assert_equal(res.toarray(), exp) + + +tensordot_shapes_and_axes = [ + ((4,6), (6,7), ([1], [0])), + ((3,2,4,7), (7,), ([3], [0])), + ((5,), (6,3,5,2), ([0], [2])), + ((4,5,7,6), (3,2,6,4), ([0, 3], [3, 2])), + ((2,8,7), (4,5,7,8,2), ([0, 1, 2], [4, 3, 2])), + ((4,5,3,2,6), (3,2,6,7,8), 3), + ((4,5,7), (7,3,7), 1), + ((2,3,4), (2,3,4), ([0, 1, 2], [0, 1, 2])), +] +@pytest.mark.parametrize(('a_shape', 'b_shape', 'axes'), tensordot_shapes_and_axes) +def test_tensordot(a_shape, b_shape, axes): + rng = np.random.default_rng(23409823) + + arr_a = random_array(a_shape, density=0.6, random_state=rng, dtype=int) + arr_b = random_array(b_shape, density=0.6, random_state=rng, dtype=int) + + exp = np.tensordot(arr_a.toarray(), arr_b.toarray(), axes=axes) + + # sparse-dense + res = arr_a.tensordot(arr_b.toarray(), axes=axes) + assert_equal(res, exp) + res = arr_a.tensordot(list(arr_b.toarray()), axes=axes) + assert_equal(res, exp) + + # sparse-sparse + res = arr_a.tensordot(arr_b, axes=axes) + if type(res) is coo_array: + assert_equal(res.toarray(), exp) + else: + assert_equal(res, exp) + + +def test_tensordot_with_invalid_args(): + rng = np.random.default_rng(23409823) + + arr_a = random_array((3,4,5), density=0.6, random_state=rng, dtype=int) + arr_b = random_array((3,4,6), density=0.6, random_state=rng, dtype=int) + + axes = ([2], [2]) # sizes of 2nd axes of both shapes do not match + with pytest.raises(ValueError, match="sizes of the corresponding axes must match"): + arr_a.tensordot(arr_b, axes=axes) + + arr_a = random_array((5,4,2,3,7), density=0.6, random_state=rng, dtype=int) + arr_b = random_array((4,6,3,2), density=0.6, random_state=rng, dtype=int) + + axes = ([2,0,1], [1,3]) # lists have different lengths + with pytest.raises(ValueError, match="axes lists/tuples must be of the" + " same length"): + arr_a.tensordot(arr_b, axes=axes) + + +@pytest.mark.parametrize(('actual_shape', 'broadcast_shape'), + [((1,3,5,4), (2,3,5,4)), ((2,1,5,4), (6,2,3,5,4)), + ((1,1,7,8,9), (4,5,6,7,8,9)), ((1,3), (4,5,3)), + ((7,8,1), (7,8,5)), ((3,1), (3,4)), ((1,), (5,)), + ((1,1,1), (4,5,6)), ((1,3,1,5,4), (8,2,3,9,5,4)),]) +def test_broadcast_to(actual_shape, broadcast_shape): + rng = np.random.default_rng(23409823) + + arr = random_array(actual_shape, density=0.6, random_state=rng, dtype=int) + res = arr._broadcast_to(broadcast_shape) + exp = np.broadcast_to(arr.toarray(), broadcast_shape) + assert_equal(res.toarray(), exp) + + +@pytest.mark.parametrize(('shape'), [(4,5,6,7,8), (6,4), + (5,9,3,2), (9,5,2,3,4),]) +def test_block_diag(shape): + rng = np.random.default_rng(23409823) + sp_x = random_array(shape, density=0.6, random_state=rng, dtype=int) + den_x = sp_x.toarray() + + # converting n-d numpy array to an array of slices of 2-D matrices, + # to pass as argument into scipy.linalg.block_diag + num_slices = int(np.prod(den_x.shape[:-2])) + reshaped_array = den_x.reshape((num_slices,) + den_x.shape[-2:]) + matrices = [reshaped_array[i, :, :] for i in range(num_slices)] + exp = block_diag(*matrices) + + res = _block_diag(sp_x) + + assert_equal(res.toarray(), exp) + + +@pytest.mark.parametrize(('shape'), [(4,5,6,7,8), (6,4), + (5,9,3,2), (9,5,2,3,4),]) +def test_extract_block_diag(shape): + rng = np.random.default_rng(23409823) + sp_x = random_array(shape, density=0.6, random_state=rng, dtype=int) + res = _extract_block_diag(_block_diag(sp_x), shape) + + assert_equal(res.toarray(), sp_x.toarray()) + + +add_sub_shapes = [ + ((3,4), (3,4)), ((3,4,6), (3,4,6)), ((3,7,5), (3,7,5)) +] +@pytest.mark.parametrize(('a_shape', 'b_shape'), add_sub_shapes) +def test_add_no_broadcasting(a_shape, b_shape): + rng = np.random.default_rng(23409823) + a = random_array(a_shape, density=0.6, random_state=rng, dtype=int) + b = random_array(b_shape, density=0.6, random_state=rng, dtype=int) + + res = a + b + exp = np.add(a.toarray(), b.toarray()) + assert_equal(res.toarray(), exp) + res = a + b.toarray() + assert_equal(res, exp) + + +@pytest.mark.parametrize(('a_shape', 'b_shape'), add_sub_shapes) +def test_sub_no_broadcasting(a_shape, b_shape): + rng = np.random.default_rng(23409823) + a = random_array(a_shape, density=0.6, random_state=rng, dtype=int) + b = random_array(b_shape, density=0.6, random_state=rng, dtype=int) + + res = a - b + exp = np.subtract(a.toarray(), b.toarray()) + assert_equal(res.toarray(), exp) + + res = a - b.toarray() + assert_equal(res, exp) + + +argmax_argmin_shapes_axis = [ + ((3,), None), ((3,), 0), + ((4,6), 1), ((7,3), 0), ((3,5), None), + ((2,8,7), 2), ((2,8,7), 0), + ((2,0), 0), ((3,0,0,2), 0), + ((3,2,4,7), None), ((3,2,4,7), 1), ((3,2,4,7), 0), ((3,2,4,7), 2), + ((3,2,4,7), -2), ((4,5,7,8,2), 4), ((4,5,7,8,2), -3), +] + + +@pytest.mark.parametrize(('shape', 'axis'), argmax_argmin_shapes_axis) +def test_argmax_argmin(shape, axis): + rng = np.random.default_rng(23409823) + a = random_array(shape, density=0.6, random_state=rng, dtype=int) + + res = a.argmax(axis=axis) + exp = np.argmax(a.toarray(), axis=axis) + assert_equal(res, exp) + + res = a.argmin(axis=axis) + exp = np.argmin(a.toarray(), axis=axis) + assert_equal(res, exp) + + +max_min_shapes_axis = [ + ((3,), None), ((3,), 0), + ((4,6), 1), ((7,3), 0), ((3,5), None), + ((2,8,7), 2), ((2,8,7), 0), + ((3,2,4,7), None), ((3,2,4,7), 1), ((3,2,4,7), 0), ((3,2,4,7), 2), + ((4,5,7,8,2), 4), ((4,5,8,1), 3), ((4,6), (0,)), ((4,6), (0,1)), + ((3,0,2), 2), ((3,0,2), (0,2)), ((3,0), 0), + ((3,7,8,5), (0,1)), ((3,7,8,5), (2,1)), ((3,7,8,5), (2,0)), + ((3,7,8,5), (0,-2)), ((3,7,8,5), (-1,2)), ((3,7,8,5), (3)), + ((3,7,8,5), (0,1,2)), ((3,7,8,5), (0,1,2,3)), +] +@pytest.mark.parametrize(('shape', 'axis'), max_min_shapes_axis) +def test_min_max(shape, axis): + rng = np.random.default_rng(23409823) + a = random_array(shape, density=0.6, random_state=rng, dtype=int) + + res_min = a.min(axis=axis) + exp_min = np.min(a.toarray(), axis=axis) + res_max = a.max(axis=axis) + exp_max = np.max(a.toarray(), axis=axis) + res_nanmin = a.nanmin(axis=axis) + exp_nanmin = np.nanmin(a.toarray(), axis=axis) + res_nanmax = a.nanmax(axis=axis) + exp_nanmax = np.nanmax(a.toarray(), axis=axis) + + for res, exp in [(res_min, exp_min), (res_max, exp_max), + (res_nanmin, exp_nanmin), (res_nanmax, exp_nanmax)]: + if np.issubdtype(type(res), np.number): + assert_equal(res, exp) + else: + assert_equal(res.toarray(), exp) + + +def test_min_max_full(): + for a in (coo_array([[[1, 2, 3, 4]]]), coo_array([[1, 2, 3, 4]])): + assert a.min() == 1 + assert (-a).max() == -1 + + +sum_mean_params = [ + ((3,), None, None), ((3,), 0, None), + ((4,6), 1, None), ((7,3), 0, None), ((3,5), None, None), + ((2,8,7), 2, None), ((2,8,7), 0, np.zeros((8,7))), + ((3,2,4,7), None, None), ((3,2,4,7), 1, np.zeros((3,4,7))), + ((3,2,4,7), 0, None), ((4,5,7,8,2), 4, None), + ((4,5,8,1), 3, None), ((4,6), (0,), None), ((4,6), (0,1), None), + ((3,0,2), 2, None), ((3,0,2), (0,2), None), ((3,0), 0, None), + ((3,7,8,5), (0,1), np.zeros((8,5))), ((3,7,8,5), (2,1), None), + ((3,7,8,5), (0,-2), None), ((3,7,8,5), (-1,2), np.zeros((3,7))), + ((3,7,8,5), (3), None), ((3,7,8,5), (0,1,2), np.zeros((5,))), + ((3,7,8,5), (0,1,2,3), None), +] +@pytest.mark.parametrize(('shape', 'axis', 'out'), sum_mean_params) +def test_sum(shape, axis, out): + rng = np.random.default_rng(23409823) + a = random_array(shape, density=0.6, random_state=rng, dtype=int) + + res = a.sum(axis=axis, out=out) + exp = np.sum(a.toarray(), axis=axis) + assert_equal(res, exp) + if out is not None: + assert_equal(out, exp) + assert id(res) == id(out) + + +@pytest.mark.parametrize(('shape', 'axis', 'out'), sum_mean_params) +def test_mean(shape, axis, out): + rng = np.random.default_rng(23409823) + a = random_array(shape, density=0.6, random_state=rng, dtype=int) + + res = a.mean(axis=axis, out=out) + exp = np.mean(a.toarray(), axis=axis) + assert_allclose(res, exp) + if out is not None: + assert id(res) == id(out) + assert_allclose(out, exp) + + +def test_pow_abs_round(): + rng = np.random.default_rng(23409823) + a = random_array((3,6,5,2,4), density=0.6, random_state=rng, dtype=int) + assert_allclose((a**3).toarray(), np.power(a.toarray(), 3)) + assert_allclose((a**7).toarray(), np.power(a.toarray(), 7)) + assert_allclose(round(a).toarray(), np.round(a.toarray())) + assert_allclose(abs(a).toarray(), np.abs(a.toarray())) + + +#bitwise_op_and_compare_broadcast_shapes = [ +# ((3,4), (3,4)), ((1,4), (2,1)), ((3,5), (1,)), ((1,), (7,8)), +# ((3,4,6), (3,4,6)), ((4,3), (2,1,3)), ((2,1,3), (4,3)), +# ((3,5,4), (1,)), ((1,), (7,8,4)), ((16,1,6), (2,6)), ((3,7,5), (3,7,5)), +# ((16,2,6), (1,2,6)), ((7,8), (5,7,8)), ((4,5,1), (5,1)), +# ((6,8,3), (4,1,1,3)), ((1,1,1), (3,4,2)), ((3,4,2), (1,1,1,1,1)), +bitwise_op_and_compare_shapes = [ + ((3,4), (3,4)), ((3,4,6), (3,4,6)), ((3,7,5), (3,7,5)), +] +@pytest.mark.parametrize(('a_shape', 'b_shape'), bitwise_op_and_compare_shapes) +def test_boolean_comparisons(a_shape, b_shape): + rng = np.random.default_rng(23409823) + a = random_array(a_shape, density=0.6, random_state=rng, dtype=int) + b = random_array(b_shape, density=0.6, random_state=rng, dtype=int) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", SparseEfficiencyWarning) + assert_equal((a==b).toarray(), a.toarray()==b.toarray()) + assert_equal((a!=b).toarray(), a.toarray()!=b.toarray()) + assert_equal((a>=b).toarray(), a.toarray()>=b.toarray()) + assert_equal((a<=b).toarray(), a.toarray()<=b.toarray()) + assert_equal((a>b).toarray(), a.toarray()>b.toarray()) + assert_equal((a=b).toarray(), np.bitwise_not((ab).toarray())) + + +def test_boolean_comparisons_with_scalar(): + rng = np.random.default_rng(23409823) + a = random_array((5,4,8,7), density=0.6, random_state=rng, dtype=int) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", SparseEfficiencyWarning) + assert_equal((a==0).toarray(), a.toarray()==0) + assert_equal((a!=0).toarray(), a.toarray()!=0) + assert_equal((a>=1).toarray(), a.toarray()>=1) + assert_equal((a<=1).toarray(), a.toarray()<=1) + assert_equal((a>0).toarray(), a.toarray()>0) + assert_equal((a<0).toarray(), a.toarray()<0) + + +@pytest.mark.parametrize(('a_shape', 'b_shape'), bitwise_op_and_compare_shapes) +def test_multiply(a_shape, b_shape): + rng = np.random.default_rng(23409823) + a = random_array(a_shape, density=0.6, random_state=rng, dtype=int) + b = random_array(b_shape, density=0.6, random_state=rng, dtype=int) + res = a * b + exp = np.multiply(a.toarray(), b.toarray()) + assert_equal(res.toarray(), exp) + + +def test_multiply_with_scalar(): + rng = np.random.default_rng(23409823) + a = random_array((3,5,4), density=0.6, random_state=rng, dtype=int) + res = a * 7 + exp = np.multiply(a.toarray(), 7) + assert_equal(res.toarray(), exp) + + +@pytest.mark.parametrize(('a_shape', 'b_shape'), bitwise_op_and_compare_shapes) +def test_divide(a_shape, b_shape): + rng = np.random.default_rng(23409823) + a = random_array(a_shape, density=0.6, random_state=rng, dtype=int) + b = np.arange(1, 1 + math.prod(b_shape)).reshape(b_shape) + res = a / b + exp = a.toarray() / b + assert_allclose(res.toarray(), exp) + + res = a / b + assert_allclose(res.toarray(), exp) + + +def test_divide_with_scalar(): + rng = np.random.default_rng(23409823) + a = random_array((3,5,4), density=0.6, random_state=rng, dtype=int) + res = a / 7 + exp = a.toarray() / 7 + assert_allclose(res.toarray(), exp) + + +@pytest.mark.parametrize(('a_shape', 'b_shape'), bitwise_op_and_compare_shapes) +def test_maximum(a_shape, b_shape): + rng = np.random.default_rng(23409823) + a = random_array(a_shape, density=0.6, random_state=rng, dtype=int) + b = random_array(b_shape, density=0.6, random_state=rng, dtype=int) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", SparseEfficiencyWarning) + res = a.maximum(b) + exp = np.maximum(a.toarray(), b.toarray()) + assert_equal(res.toarray(), exp) + + +@pytest.mark.parametrize(('a_shape', 'b_shape'), bitwise_op_and_compare_shapes) +def test_minimum(a_shape, b_shape): + rng = np.random.default_rng(23409823) + a = random_array(a_shape, density=0.6, random_state=rng, dtype=int) + b = random_array(b_shape, density=0.6, random_state=rng, dtype=int) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", SparseEfficiencyWarning) + res = a.minimum(b) + exp = np.minimum(a.toarray(), b.toarray()) + assert_equal(res.toarray(), exp) + + +def test_maximum_with_scalar(): + a = coo_array([0,1,6]) + b = coo_array([[15, 0], [14, 5], [0, -12]]) + c = coo_array([[[[3,0], [2,4]], [[8,9], [-3,12]]], + [[[5,2], [3,0]], [[0,7], [0,-6]]]]) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", SparseEfficiencyWarning) + assert_equal(a.maximum(5).toarray(), np.maximum(a.toarray(), 5)) + assert_equal(b.maximum(9).toarray(), np.maximum(b.toarray(), 9)) + assert_equal(c.maximum(5).toarray(), np.maximum(c.toarray(), 5)) + + +def test_minimum_with_scalar(): + a = coo_array([0,1,6]) + b = coo_array([[15, 0], [14, 5], [0, -12]]) + c = coo_array([[[[3,0], [2,4]], [[8,9], [-3,12]]], + [[[5,2], [3,0]], [[0,7], [0,-6]]]]) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", SparseEfficiencyWarning) + assert_equal(a.minimum(5).toarray(), np.minimum(a.toarray(), 5)) + assert_equal(b.minimum(9).toarray(), np.minimum(b.toarray(), 9)) + assert_equal(c.minimum(5).toarray(), np.minimum(c.toarray(), 5)) + + +def test_1d_coo_get(): + B = coo_array(np.arange(9)) + + assert B[0] == 0 + assert B[4] == 4 + + assert_equal(B[1:3].toarray(), B.toarray()[1:3]) + assert_equal(B[:3].toarray(), B.toarray()[:3]) + assert_equal(B[1:].toarray(), B.toarray()[1:]) + assert_equal(B[1:5:2].toarray(), B.toarray()[1:5:2]) + + assert_equal(B[[1, 3, 4]].toarray(), B.toarray()[[1, 3, 4]]) + assert_equal(B[[3, 4, 1]].toarray(), B.toarray()[[3, 4, 1]]) + + +def test_2d_coo_get(): + B = coo_array(np.arange(4 * 5).reshape((4, 5))) + + assert B[0, 0] == 0 + assert B[3, 4] == 19 + + assert_equal(B[1:3, 0].toarray(), B.toarray()[1:3, 0]) + assert_equal(B[2, 1:3].toarray(), B.toarray()[2, 1:3]) + assert_equal(B[:2, 1:5:3].toarray(), B.toarray()[:2, 1:5:3]) + + assert_equal(B[[1, 3], 0].toarray(), B.toarray()[[1,3], 0]) + assert_equal(B[2:, [1, 3]].toarray(), B.toarray()[2:, [1,3]]) + assert_equal(B[np.array([1, 3]), 0].toarray(), B.toarray()[[1,3], 0]) + assert_equal(B[2:, np.array([1, 3])].toarray(), B.toarray()[2:, [1,3]]) + + assert_equal(B[:, 0].toarray(), B.toarray()[:, 0]) + assert_equal(B[0, :].toarray(), B.toarray()[0, :]) + + +def test_3d_coo_get(): + A = coo_array(np.arange(4 * 5 * 6).reshape((4, 5, 6))) + assert A[0, 0, 0] == 0 + assert A[3, 4, 5] == 119 + + assert_equal(A[0, 1:3, 0].toarray(), A.toarray()[0, 1:3, 0]) + assert_equal(A[0, 1:3, 3].toarray(), A.toarray()[0, 1:3, 3]) + assert_equal(A[:3, 1:5:2, 2:].toarray(), A.toarray()[:3, 1:5:2, 2:]) + + assert_equal(A[[1, 3], 0, 0].toarray(), A.toarray()[[1, 3], 0, 0]) + assert_equal(A[:2, 1:3, [1, 3]].toarray(), A.toarray()[:2, 1:3, [1, 3]]) + + assert_equal(A[0, :, 0].toarray(), A.toarray()[0, :, 0]) + assert_equal(A[:, :, 0].toarray(), A.toarray()[:, :, 0]) + + +def test_newaxis_get(): + A = coo_array(np.arange(4 * 5 * 6).reshape((4, 5, 6))) + D = A.toarray() + assert_equal(A[:5, 3:, 1].toarray(), D[:5, 3:, 1]) + assert_equal(A[None, :5, 1:3, None, 1].toarray(), D[None, :5, 1:3, None, 1]) + assert_equal(A[None, :5, 3:, 1, None].toarray(), D[None, :5, 3:, 1, None]) + assert_equal(A[None, :5, ..., None].toarray(), D[None, :5, ..., None]) + + +def test_newaxis_set(): + D = np.arange(4 * 5 * 6).reshape((4, 5, 6)) + A = coo_array(D) + + A[None, 3:, 1] = D[None, 3:, 1] = 5 + assert_equal(A.toarray(), D) + A[3:, 1, None] = D[3:, 1, None] = 7 + assert_equal(A.toarray(), D) + A[3:, None, 1] = D[3:, None, 1] = 3 + assert_equal(A.toarray(), D) + + +def test_1d_coo_set(): + D = np.arange(9) + A = coo_array(D) + + A[0] = D[0] = -1 + assert_equal(A.toarray(), D) + A[4] = D[4] = -2 + assert_equal(A.toarray(), D) + + A[1:3] = D[1:3] = [-2, -3] + assert_equal(A.toarray(), D) + A[:3] = D[:3] = [-7, -8, -9] + assert_equal(A.toarray(), D) + A[1:] = D[1:] = -D[1:] + assert_equal(A.toarray(), D) + A[1:5:2] = D[1:5:2] = -D[1:5:2] + assert_equal(A.toarray(), D) + + A[[1, 3, 4]] = D[[1, 3, 4]] = -D[[1, 3, 4]] + assert_equal(A.toarray(), D) + A[[3, 4, 1]] = D[[3, 4, 1]] = -D[[1, 2, 3]] + assert_equal(A.toarray(), D) + + +D = np.arange(4 * 5).reshape((4, 5)) +A = coo_array(D) + +keys = [ + # all int + (1, 3), + (-1, -3), + # slices and ints + (slice(1, 3, None), 0), + (2, slice(1, 3, None)), + (slice(1, 3, None), slice(1, 5, 3)), + (slice(None, None, -1), 2), + (1, slice(None)), + (Ellipsis,), + # array indexing + ([1, 3], 0), + (2, [1, 2]), + (np.array([1, 3]), slice(1, None)), + (slice(2), np.array([1, 2, 3])), + # fancy array indexing + (np.array([1, 3]), np.array([0, 2])), +] + +@pytest.mark.parametrize(["A", "D", "idx"], [(A, D, idx) for idx in keys]) +@pytest.mark.thread_unsafe +def test_2d_coo_set(A, D, idx): + D[idx] = A[idx] = -D[idx] + assert_equal(A.toarray(), D) + + +D = np.arange(4 * 5 * 6).reshape((4, 5, 6)) +A = coo_array(D) + +keys = [ + # all ints + (0, 0, 0), + (3, -4, 5), + # slices and ints + (0, slice(1, 3), 0), + (slice(3, None), 3, slice(2)), + (slice(2), slice(1, 5, 2), slice(1, None)), + (slice(None, None, -1), slice(None), slice(1, 5, 2)), + (Ellipsis), + # array indexing + (2, [1, 2], slice(3)), + (np.array([1, 3]), slice(1, None), 0), + (np.array([1, 3]), slice(1, None), [0]), + # fancy array indexing + (np.array([1, 3]), slice(1, None), np.array([2, 4])), + (2, np.array([1, 3]), np.array([2, 4])), + (np.array([1, 3]), np.array([2, 4]), 1), + (np.array([1, 3]), np.array([2, 4]), [2]), +] + +@pytest.mark.parametrize(["A", "D", "idx"], [(A, D, idx) for idx in keys]) +@pytest.mark.thread_unsafe +def test_3d_coo_set(A, D, idx): + D[idx] = A[idx] = -99 + assert_equal(A.toarray(), D) + + +@pytest.mark.parametrize( + "scalar_container", + [lambda x: csr_array(np.array([[x]])), np.array, lambda x: x], + ids=["sparse", "dense", "scalar"], +) +@pytest.mark.thread_unsafe(reason="fails in parallel") +def test_3d_coo_singleton(scalar_container): + A[(0, 0, 0)] = scalar_container(-99) + D[(0, 0, 0)] = -99 + assert_equal(A.toarray(), D) + + +#D = np.arange(4 * 5 * 6 * 4 * 6).reshape((4, 5, 6, 4, 6)) +D = np.arange(4 * 6 * 6 * 3 * 4).reshape((4, 6, 6, 3, 4)) +A = coo_array(D) + +keys = [ + # multidimensional + ("all-ints", (1, 2, 1)), + ("ellipsis-first", (..., 0)), + ("ellipsis-last", (1, ...)), + ("ellipsis-middle", (1, ..., 0)), + # slices and ints + ("split-ints", (slice(None, 4, None), 2, 1, slice(None, None, None), 1)), + ("contiguous-ints-last", (slice(4, None, None), slice(None, None, None), 2, 1, 1)), + ("contiguous-ints-first", (2, 1, 1, slice(4, None, None), slice(None, None, None))), + ("empty-slice", (slice(4, 4, None), slice(None, None, None), 2, 1, 1)), + # slices and arrays and ints + ("split-2d-arrays-and-ints", + (slice(None, 4, None), [[2, 1, 1], [2, 1, 2]], [[2, 1, 0], [3, 4, 0]], + slice(None, None, None), 0) + ), + ("contiguous-2d-arrays-and-ints", + (slice(None, 4, None), [[2, 1, 0], [2, 1, 2]], [[2, 1, 0], [3, 4, 0]], + 1, slice(None, None, None)) + ), + ("split-1d-arrays-and-ints", + (slice(None, 4, None), [2, 1, 0], [2, 1, 0], slice(None, None, None), 1) + ), + ("split-ints-broadcast-arrays-slice-step-not-1", + (0, slice(1, 4, 2), 1, [[2, 1, 0]], [[2], [1], [0]]) + ), + ("contiguous-broadcast-arrays", + (slice(None, 3, None), slice(1, 4, 2), 1, [[2, 1, 0]], [[2], [1], [0]]) + ), + ("contiguous-arrays-first", + ([[2, 1, 0]], [[2], [1], [0]], 0, slice(1, 4, 2), slice(None, 3, None)) + ), + ("split-arrays-some-first-some-last", + ([[2, 1, 0]], slice(1, 4, 2), slice(None, 3, None), 0, [[2], [1], [0]]) + ), + # test we are not creating duplicate entries for duplicate values in array index + ("duplicate-array-entries-split-arrays", + (slice(None, 4, None), [[2, 1, 0], [2, 1, 1]], [[2, 1, 0], [3, 4, 4]], + slice(None, None, None), 1) + ), + ("duplicate-array-entries-contiguous-broadcast-arrays", + (slice(1, 4, 2), 0, 1, [[2, 1, 1]], [[2], [1], [1]]) + ), + ] + +@pytest.mark.parametrize(["A", "D", "ix", "msg"], [(A, D, ix, msg) for msg, ix in keys]) +@pytest.mark.thread_unsafe +def test_5d_coo_set(A, D, ix, msg): + D[ix] = A[ix] = -99 + assert_equal(A.toarray(), D, err_msg=f"\nTest of: {msg}\n") + + +def test_bool_get(): + D = np.arange(4 * 6 * 6 * 3 * 4).reshape((4, 6, 6, 3, 4)) + A = coo_array(D) + + assert_equal(A[D > 500].toarray(), D[D > 500]) + assert_equal(A[D > 5000].toarray(), D[D > 5000]) + assert_equal(A[D > -1].toarray(), D[D > -1]) + + assert_equal(A[A > 500].toarray(), D[D > 500]) + assert_equal(A[A > 5000].toarray(), D[D > 5000]) + assert_equal(A[A < -1].toarray(), D[D < -1]) + + bi0 = A[:, 0, 0, 0, 0] > 50 + bi1 = A[0, :, 0, 0, 0] > 50 + + idx = (bi0, slice(2, 3, None), [1], slice(None, 2, 2), bi0) + idxnp = (bi0.toarray(), slice(2, 3, 1), [1], slice(0, 2, 2), bi0.toarray()) + result = D[idxnp] + assert_equal(A[idx].toarray(), result) + assert_equal(A[idxnp].toarray(), result) + + idx = (slice(2, 3, None), bi1, bi1, slice(None, 2, 2), [1]) + idxnp = (slice(2, 3, None), bi1.toarray(), bi1.toarray(), slice(None, 2, 2), [1]) + result = D[idxnp] + assert_equal(A[idx].toarray(), result) + + +def test_bool_set(): + D_orig = np.arange(4 * 6 * 6 * 3 * 4).reshape((4, 6, 6, 3, 4)) + A_orig = coo_array(D_orig) + + A, D = A_orig.copy(), D_orig.copy() + D[D > 500] = A[A > 500] = -33 + assert_equal(A.toarray(), D) + + A, D = A_orig.copy(), D_orig.copy() + D[D > 500] = A[A > 500] = -77 + assert_equal(A.toarray(), D) + + A, D = A_orig.copy(), D_orig.copy() + bool0 = A[:, 0, 0, 0, 0] > 50 + idx = (bool0, slice(2, 3, None), [1], slice(None, 2, 2), bool0) + idxnp = (bool0.toarray(), slice(2, 3, 1), [1], slice(0, 2, 2), bool0.toarray()) + + D[idxnp] = A[idx] = -55 + assert_equal(A.toarray(), D) + + A, D = A_orig.copy(), D_orig.copy() + D[idxnp] = A[idxnp] = -88 + assert_equal(A.toarray(), D) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_csc.py b/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_csc.py new file mode 100644 index 0000000000000000000000000000000000000000..7093a8f1b0dd6b00a554d8df94bf40c0c9e77b86 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_csc.py @@ -0,0 +1,131 @@ +import operator +import sys +import platform + +import numpy as np +from numpy.testing import assert_array_almost_equal, assert_ +from scipy.sparse import csr_matrix, csc_matrix, lil_matrix, csr_array, csc_array + +import pytest + + +LINUX_INTEL = (sys.platform == 'linux') and (platform.machine() == 'x86_64') + + + +def test_csc_getrow(): + N = 10 + np.random.seed(0) + X = np.random.random((N, N)) + X[X > 0.7] = 0 + Xcsc = csc_matrix(X) + + for i in range(N): + arr_row = X[i:i + 1, :] + csc_row = Xcsc.getrow(i) + + assert_array_almost_equal(arr_row, csc_row.toarray()) + assert_(type(csc_row) is csr_matrix) + + +def test_csc_getcol(): + N = 10 + np.random.seed(0) + X = np.random.random((N, N)) + X[X > 0.7] = 0 + Xcsc = csc_matrix(X) + + for i in range(N): + arr_col = X[:, i:i + 1] + csc_col = Xcsc.getcol(i) + + assert_array_almost_equal(arr_col, csc_col.toarray()) + assert_(type(csc_col) is csc_matrix) + +@pytest.mark.parametrize("matrix_input, axis, expected_shape", + [(csc_matrix([[1, 0], + [0, 0], + [0, 2]]), + 0, (0, 2)), + (csc_matrix([[1, 0], + [0, 0], + [0, 2]]), + 1, (3, 0)), + (csc_matrix([[1, 0], + [0, 0], + [0, 2]]), + 'both', (0, 0)), + (csc_matrix([[0, 1, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 2, 3, 0, 1]]), + 0, (0, 6))]) +def test_csc_empty_slices(matrix_input, axis, expected_shape): + # see gh-11127 for related discussion + slice_1 = matrix_input.toarray().shape[0] - 1 + slice_2 = slice_1 + slice_3 = slice_2 - 1 + + if axis == 0: + actual_shape_1 = matrix_input[slice_1:slice_2, :].toarray().shape + actual_shape_2 = matrix_input[slice_1:slice_3, :].toarray().shape + elif axis == 1: + actual_shape_1 = matrix_input[:, slice_1:slice_2].toarray().shape + actual_shape_2 = matrix_input[:, slice_1:slice_3].toarray().shape + elif axis == 'both': + actual_shape_1 = matrix_input[slice_1:slice_2, slice_1:slice_2].toarray().shape + actual_shape_2 = matrix_input[slice_1:slice_3, slice_1:slice_3].toarray().shape + + assert actual_shape_1 == expected_shape + assert actual_shape_1 == actual_shape_2 + + +@pytest.mark.parametrize('ax', (-2, -1, 0, 1, None)) +def test_argmax_overflow(ax): + # See gh-13646: Windows integer overflow for large sparse matrices. + dim = (100000, 100000) + A = lil_matrix(dim) + A[-2, -2] = 42 + A[-3, -3] = 0.1234 + A = csc_matrix(A) + idx = A.argmax(axis=ax) + + if ax is None: + # idx is a single flattened index + # that we need to convert to a 2d index pair; + # can't do this with np.unravel_index because + # the dimensions are too large + ii = idx % dim[0] + jj = idx // dim[0] + else: + # idx is an array of size of A.shape[ax]; + # check the max index to make sure no overflows + # we encountered + assert np.count_nonzero(idx) == A.nnz + ii, jj = np.max(idx), np.argmax(idx) + + assert A[ii, jj] == A[-2, -2] + + +@pytest.mark.skipif(not LINUX_INTEL, reason="avoid variations due to OS, see gh-23826") +@pytest.mark.timeout(2) # only slow when broken (when spurious conversion occurs) +@pytest.mark.parametrize("op", (operator.ne, operator.lt, operator.gt, + operator.add, operator.sub, operator.mul, + # truediv, eq, ge, le make dense output so not tested + lambda x, y: x.minimum(y), lambda x, y: x.maximum(y))) +def test_compressed_rc_conversion_mixup(op): + # see gh-23826 for related discussion + num_minor_axis = np.iinfo(np.uint32).max + 1 + minor_axis_index = np.array([num_minor_axis - 1]) + major_axis_index = np.array([10]) + row_cols = (minor_axis_index, major_axis_index) + col_rows = (major_axis_index, minor_axis_index) + + X = csc_array((np.array([10]), row_cols), shape=(num_minor_axis, 20)) + X_2 = X.copy() + # causes timeout error upon large memory alloc only if conversion to CSR occurs + op(X_2, X) + + Z = csr_array((np.array([10]), col_rows), shape=(20, num_minor_axis)) + Z_2 = Z.copy() + # causes timeout error upon large memory alloc only if conversion to CSC occurs + op(Z_2, Z) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_csr.py b/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_csr.py new file mode 100644 index 0000000000000000000000000000000000000000..b7a3f6d980f37870d6683ad465fca9ba9e3c87c0 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_csr.py @@ -0,0 +1,281 @@ +import warnings +import numpy as np +from numpy.testing import assert_array_almost_equal, assert_, assert_array_equal +from scipy._lib._testutils import check_free_memory +from scipy.sparse import csr_matrix, csc_matrix, csr_array, csc_array, hstack +from scipy import sparse +import pytest + + +def _check_csr_rowslice(i, sl, X, Xcsr): + np_slice = X[i, sl] + csr_slice = Xcsr[i, sl] + assert_array_almost_equal(np_slice, csr_slice.toarray()[0]) + assert_(type(csr_slice) is csr_matrix) + + +def test_csr_rowslice(): + N = 10 + np.random.seed(0) + X = np.random.random((N, N)) + X[X > 0.7] = 0 + Xcsr = csr_matrix(X) + + slices = [slice(None, None, None), + slice(None, None, -1), + slice(1, -2, 2), + slice(-2, 1, -2)] + + for i in range(N): + for sl in slices: + _check_csr_rowslice(i, sl, X, Xcsr) + + +def test_csr_getrow(): + N = 10 + np.random.seed(0) + X = np.random.random((N, N)) + X[X > 0.7] = 0 + Xcsr = csr_matrix(X) + + for i in range(N): + arr_row = X[i:i + 1, :] + csr_row = Xcsr.getrow(i) + + assert_array_almost_equal(arr_row, csr_row.toarray()) + assert_(type(csr_row) is csr_matrix) + + +def test_csr_getcol(): + N = 10 + np.random.seed(0) + X = np.random.random((N, N)) + X[X > 0.7] = 0 + Xcsr = csr_matrix(X) + + for i in range(N): + arr_col = X[:, i:i + 1] + csr_col = Xcsr.getcol(i) + + assert_array_almost_equal(arr_col, csr_col.toarray()) + assert_(type(csr_col) is csr_matrix) + +@pytest.mark.parametrize("matrix_input, axis, expected_shape", + [(csr_matrix([[1, 0, 0, 0], + [0, 0, 0, 0], + [0, 2, 3, 0]]), + 0, (0, 4)), + (csr_matrix([[1, 0, 0, 0], + [0, 0, 0, 0], + [0, 2, 3, 0]]), + 1, (3, 0)), + (csr_matrix([[1, 0, 0, 0], + [0, 0, 0, 0], + [0, 2, 3, 0]]), + 'both', (0, 0)), + (csr_matrix([[0, 1, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 2, 3, 0]]), + 0, (0, 5))]) +def test_csr_empty_slices(matrix_input, axis, expected_shape): + # see gh-11127 for related discussion + slice_1 = matrix_input.toarray().shape[0] - 1 + slice_2 = slice_1 + slice_3 = slice_2 - 1 + + if axis == 0: + actual_shape_1 = matrix_input[slice_1:slice_2, :].toarray().shape + actual_shape_2 = matrix_input[slice_1:slice_3, :].toarray().shape + elif axis == 1: + actual_shape_1 = matrix_input[:, slice_1:slice_2].toarray().shape + actual_shape_2 = matrix_input[:, slice_1:slice_3].toarray().shape + elif axis == 'both': + actual_shape_1 = matrix_input[slice_1:slice_2, slice_1:slice_2].toarray().shape + actual_shape_2 = matrix_input[slice_1:slice_3, slice_1:slice_3].toarray().shape + + assert actual_shape_1 == expected_shape + assert actual_shape_1 == actual_shape_2 + + +def test_csr_bool_indexing(): + data = csr_matrix([[0, 1, 2], [3, 4, 5], [6, 7, 8]]) + list_indices1 = [False, True, False] + array_indices1 = np.array(list_indices1) + list_indices2 = [[False, True, False], [False, True, False], [False, True, False]] + array_indices2 = np.array(list_indices2) + list_indices3 = ([False, True, False], [False, True, False]) + array_indices3 = (np.array(list_indices3[0]), np.array(list_indices3[1])) + slice_list1 = data[list_indices1].toarray() + slice_array1 = data[array_indices1].toarray() + slice_list2 = data[list_indices2] + slice_array2 = data[array_indices2] + slice_list3 = data[list_indices3] + slice_array3 = data[array_indices3] + assert (slice_list1 == slice_array1).all() + assert (slice_list2 == slice_array2).all() + assert (slice_list3 == slice_array3).all() + + +@pytest.mark.xfail_on_32bit("Can't create large array for test") +@pytest.mark.timeout(2) # only slow when broken (conversion to 2d index arrays) +@pytest.mark.parametrize("cls", [csr_matrix, csr_array, csc_matrix, csc_array]) +def test_fancy_indexing_broadcasts_without_making_dense_2d(cls): + # Fixes Issue gh-24339 + J = np.arange(100_000) + I = J.reshape((100_000, 1)) + S = cls((100_000, 100_000)) + # checking nnz, but really testing indexing. + assert S[I, J].nnz == 0 # 1D row array for columns -> broadcasts to 2D + assert S[I, J.reshape(1, -1)].nnz == 0 # 2D row array as index for columns + + +def test_csr_hstack_int64(): + """ + Tests if hstack properly promotes to indices and indptr arrays to np.int64 + when using np.int32 during concatenation would result in either array + overflowing. + """ + max_int32 = np.iinfo(np.int32).max + + # First case: indices would overflow with int32 + data = [1.0] + row = [0] + + max_indices_1 = max_int32 - 1 + max_indices_2 = 3 + + # Individual indices arrays are representable with int32 + col_1 = [max_indices_1 - 1] + col_2 = [max_indices_2 - 1] + + X_1 = csr_matrix((data, (row, col_1))) + X_2 = csr_matrix((data, (row, col_2))) + + assert max(max_indices_1 - 1, max_indices_2 - 1) < max_int32 + assert X_1.indices.dtype == X_1.indptr.dtype == np.int32 + assert X_2.indices.dtype == X_2.indptr.dtype == np.int32 + + # ... but when concatenating their CSR matrices, the resulting indices + # array can't be represented with int32 and must be promoted to int64. + X_hs = hstack([X_1, X_2], format="csr") + + assert X_hs.indices.max() == max_indices_1 + max_indices_2 - 1 + assert max_indices_1 + max_indices_2 - 1 > max_int32 + assert X_hs.indices.dtype == X_hs.indptr.dtype == np.int64 + + # Even if the matrices are empty, we must account for their size + # contribution so that we may safely set the final elements. + X_1_empty = csr_matrix(X_1.shape) + X_2_empty = csr_matrix(X_2.shape) + X_hs_empty = hstack([X_1_empty, X_2_empty], format="csr") + + assert X_hs_empty.shape == X_hs.shape + assert X_hs_empty.indices.dtype == np.int64 + + # Should be just small enough to stay in int32 after stack. Note that + # we theoretically could support indices.max() == max_int32, but due to an + # edge-case in the underlying sparsetools code + # (namely the `coo_tocsr` routine), + # we require that max(X_hs_32.shape) < max_int32 as well. + # Hence we can only support max_int32 - 1. + col_3 = [max_int32 - max_indices_1 - 1] + X_3 = csr_matrix((data, (row, col_3))) + X_hs_32 = hstack([X_1, X_3], format="csr") + assert X_hs_32.indices.dtype == np.int32 + assert X_hs_32.indices.max() == max_int32 - 1 + +@pytest.mark.parametrize("cls", [csr_matrix, csr_array, csc_matrix, csc_array]) +def test_mixed_index_dtype_int_indexing(cls): + # https://github.com/scipy/scipy/issues/20182 + rng = np.random.default_rng(0) + base_mtx = cls(sparse.random(50, 50, random_state=rng, density=0.1)) + indptr_64bit = base_mtx.copy() + indices_64bit = base_mtx.copy() + indptr_64bit.indptr = base_mtx.indptr.astype(np.int64) + indices_64bit.indices = base_mtx.indices.astype(np.int64) + + for mtx in [base_mtx, indptr_64bit, indices_64bit]: + np.testing.assert_array_equal( + mtx[[1,2], :].toarray(), + base_mtx[[1, 2], :].toarray() + ) + np.testing.assert_array_equal( + mtx[:, [1, 2]].toarray(), + base_mtx[:, [1, 2]].toarray() + ) + +def test_broadcast_to(): + a = np.array([1, 0, 2]) + b = np.array([3]) + e = np.zeros((0,)) + res_a = csr_array(a)._broadcast_to((2,3)) + res_b = csr_array(b)._broadcast_to((4,)) + res_c = csr_array(b)._broadcast_to((2,4)) + res_d = csr_array(b)._broadcast_to((1,)) + res_e = csr_array(e)._broadcast_to((4,0)) + assert_array_equal(res_a.toarray(), np.broadcast_to(a, (2,3))) + assert_array_equal(res_b.toarray(), np.broadcast_to(b, (4,))) + assert_array_equal(res_c.toarray(), np.broadcast_to(b, (2,4))) + assert_array_equal(res_d.toarray(), np.broadcast_to(b, (1,))) + assert_array_equal(res_e.toarray(), np.broadcast_to(e, (4,0))) + + with pytest.raises(ValueError, match="cannot be broadcast"): + csr_matrix([[1, 2, 0], [3, 0, 1]])._broadcast_to(shape=(2, 1)) + + with pytest.raises(ValueError, match="cannot be broadcast"): + csr_matrix([[0, 1, 2]])._broadcast_to(shape=(3, 2)) + + with pytest.raises(ValueError, match="cannot be broadcast"): + csr_array([0, 1, 2])._broadcast_to(shape=(3, 2)) + +@pytest.mark.xslow +def test_large_assignments(): + # When nnz grows bigger than int32 can hold, shift to int64 index arrays + + # This test requires *a lot* of memory! + check_free_memory(65536) + + # parametrize puts lots of slow stuff into pytest's collection phase. So dont use it + def check(A_info, index, rhs): + if A_info is None: + A = csr_array((d, d), dtype=np.int32) + else: + A = csr_array(A_info, shape=(d, d)) + + # check that we start small + assert A.indices.dtype == np.int32 + assert A.nnz < 2**31 + # do the assignment + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", warn_msg, sparse.SparseEfficiencyWarning) + A[index[:, None], index] = rhs + # check that we end large + assert A.indices.dtype == np.int64 + assert A.nnz > 2**31 + + N = 46340 # 46340**2 fits in int32, 46341**2 does not + d = 47000 # d**2 > 2**31 with some extra room + warn_msg = "Changing the sparsity structure" + + big_data = np.ones((N, N), dtype=np.float32) + big_coords = tuple(co.astype(np.int32) for co in big_data.nonzero()) + big_data = big_data.reshape(-1) + big_info = (big_data, big_coords) + + index_N = np.arange(N + 1, dtype=np.int32) + index_300 = np.arange(N, N + 300, dtype=np.int32) + + rhs_300 = np.arange(300 * 300, dtype=np.float32).reshape((300, 300)) + rhs_300_sparse = csr_array(rhs_300) + + # 1: see gh-24915: From empty array, assign 1 to 46341x46341 region") + check(None, index_N, 1) + + # 2: From 46340x46340 region filled, assign 1 to new 300x300 region") + check(big_info, index_300, 1) + + # 3: From 46340x46340 region filled, assign np.array to new 300x300 region") + check(big_info, index_300, rhs_300) + + # 4: From 46340x46340 region filled, assign sparse to new 300x300 region") + check(big_info, index_300, rhs_300_sparse) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_dok.py b/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_dok.py new file mode 100644 index 0000000000000000000000000000000000000000..9b54d3067e8da75b5c8d520e93d480287e364341 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_dok.py @@ -0,0 +1,225 @@ +import warnings + +import pytest +import numpy as np +from numpy.testing import assert_equal +import scipy as sp +from scipy.sparse import dok_array, dok_matrix + + +pytestmark = pytest.mark.thread_unsafe + + +@pytest.fixture +def d(): + return {(0, 1): 1, (0, 2): 2} + +@pytest.fixture +def A(): + return np.array([[0, 1, 2], [0, 0, 0], [0, 0, 0]]) + +@pytest.fixture(params=[dok_array, dok_matrix]) +def Asp(request): + A = request.param((3, 3)) + A[(0, 1)] = 1 + A[(0, 2)] = 2 + yield A + +# Note: __iter__ and comparison dunders act like ndarrays for DOK, not dict. +# Dunders reversed, or, ror, ior work as dict for dok_matrix, raise for dok_array +# All other dict methods on DOK format act like dict methods (with extra checks). + +# Start of tests +################ +def test_dict_methods_covered(d, Asp): + d_methods = set(dir(d)) - {"__class_getitem__"} + asp_methods = set(dir(Asp)) + assert d_methods < asp_methods + +def test_clear(d, Asp): + assert d.items() == Asp.items() + d.clear() + Asp.clear() + assert d.items() == Asp.items() + +def test_copy(d, Asp): + assert d.items() == Asp.items() + dd = d.copy() + asp = Asp.copy() + assert dd.items() == asp.items() + assert asp.items() == Asp.items() + asp[(0, 1)] = 3 + assert Asp[(0, 1)] == 1 + +def test_fromkeys_default(): + # test with default value + edges = [(0, 2), (1, 0), (2, 1)] + Xdok = dok_array.fromkeys(edges) + X = [[0, 0, 1], [1, 0, 0], [0, 1, 0]] + assert_equal(Xdok.toarray(), X) + +def test_fromkeys_positional(): + # test with positional value + edges = [(0, 2), (1, 0), (2, 1)] + Xdok = dok_array.fromkeys(edges, -1) + X = [[0, 0, -1], [-1, 0, 0], [0, -1, 0]] + assert_equal(Xdok.toarray(), X) + +def test_fromkeys_iterator(): + it = ((a, a % 2) for a in range(4)) + Xdok = dok_array.fromkeys(it) + X = [[1, 0], [0, 1], [1, 0], [0, 1]] + assert_equal(Xdok.toarray(), X) + +def test_get(d, Asp): + assert Asp.get((0, 1)) == d.get((0, 1)) + assert Asp.get((0, 0), 99) == d.get((0, 0), 99) + with pytest.raises(IndexError, match="out of bounds"): + Asp.get((0, 4), 99) + +def test_items(d, Asp): + assert Asp.items() == d.items() + +def test_keys(d, Asp): + assert Asp.keys() == d.keys() + +def test_pop(d, Asp): + assert d.pop((0, 1)) == 1 + assert Asp.pop((0, 1)) == 1 + assert d.items() == Asp.items() + + assert Asp.pop((22, 21), None) is None + assert Asp.pop((22, 21), "other") == "other" + with pytest.raises(KeyError, match="(22, 21)"): + Asp.pop((22, 21)) + msg = "got some positional-only arguments passed as keyword arguments: 'default'" + with pytest.raises(TypeError, match=msg): + Asp.pop((22, 21), default=5) + +def test_popitem(d, Asp): + assert d.popitem() == Asp.popitem() + assert d.items() == Asp.items() + +def test_setdefault(d, Asp): + assert Asp.setdefault((0, 1), 4) == 1 + assert Asp.setdefault((2, 2), 4) == 4 + d.setdefault((0, 1), 4) + d.setdefault((2, 2), 4) + assert d.items() == Asp.items() + +def test_update(d, Asp): + for input in [Asp, Asp._dict, Asp._dict.items()]: + Bsp = dok_array(Asp.shape) + Bsp.update(input) + assert_equal(Bsp.toarray(), Asp.toarray()) + + with pytest.raises(ValueError, match="Inexact indices .* not allowed"): + Asp.update(np.zeros((2,2))) + with pytest.raises(IndexError, match="length needs to match self.shape"): + Asp.update({(3, 2, 1, 0): 1.2}) + with pytest.raises(IndexError, match="integer keys required"): + Asp.update({(0.2, 1): 1.2}) + with pytest.raises(IndexError, match="negative index"): + Asp.update({(0, -1): 1.2}) + with pytest.raises(IndexError, match="index .* is too large"): + Asp.update({(0, 3): 1.2}) + +def test_values(d, Asp): + # Note: dict.values are strange: d={1: 1}; d.values() == d.values() is False + # Using list(d.values()) makes them comparable. + assert list(Asp.values()) == list(d.values()) + +def test_dunder_getitem(d, Asp): + assert Asp[(0, 1)] == d[(0, 1)] + +def test_dunder_setitem(d, Asp): + Asp[(1, 1)] = 5 + d[(1, 1)] = 5 + assert d.items() == Asp.items() + +def test_dunder_delitem(d, Asp): + del Asp[(0, 1)] + del d[(0, 1)] + assert d.items() == Asp.items() + +def test_dunder_contains(d, Asp): + assert ((0, 1) in d) == ((0, 1) in Asp) + assert ((0, 0) in d) == ((0, 0) in Asp) + +def test_dunder_len(d, Asp): + assert len(d) == len(Asp) + +# Note: dunders reversed, or, ror, ior work as dict for dok_matrix, raise for dok_array +def test_dunder_reversed(d, Asp): + if isinstance(Asp, dok_array): + with pytest.raises(TypeError): + list(reversed(Asp)) + else: + assert list(reversed(Asp)) == list(reversed(d)) + +def test_dunder_ior(d, Asp): + if isinstance(Asp, dok_array): + with pytest.raises(TypeError): + Asp |= Asp + else: + dd = {(0, 0): 5} + Asp |= dd + assert Asp[(0, 0)] == 5 + d |= dd + assert d.items() == Asp.items() + dd |= Asp + assert dd.items() == Asp.items() + +def test_dunder_or(d, Asp): + if isinstance(Asp, dok_array): + with pytest.raises(TypeError): + Asp | Asp + else: + assert d | d == Asp | d + assert d | d == Asp | Asp + +def test_dunder_ror(d, Asp): + if isinstance(Asp, dok_array): + with pytest.raises(TypeError): + Asp | Asp + with pytest.raises(TypeError): + d | Asp + else: + assert Asp.__ror__(d) == Asp.__ror__(Asp) + assert d.__ror__(d) == Asp.__ror__(d) + assert d | Asp + +# Note: comparison dunders, e.g. ==, >=, etc follow np.array not dict +def test_dunder_eq(A, Asp): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", sp.sparse.SparseEfficiencyWarning) + assert (Asp == Asp).toarray().all() + assert (A == Asp).all() + +def test_dunder_ne(A, Asp): + assert not (Asp != Asp).toarray().any() + assert not (A != Asp).any() + +def test_dunder_lt(A, Asp): + assert not (Asp < Asp).toarray().any() + assert not (A < Asp).any() + +def test_dunder_gt(A, Asp): + assert not (Asp > Asp).toarray().any() + assert not (A > Asp).any() + +def test_dunder_le(A, Asp): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", sp.sparse.SparseEfficiencyWarning) + assert (Asp <= Asp).toarray().all() + assert (A <= Asp).all() + +def test_dunder_ge(A, Asp): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", sp.sparse.SparseEfficiencyWarning) + assert (Asp >= Asp).toarray().all() + assert (A >= Asp).all() + +# Note: iter dunder follows np.array not dict +def test_dunder_iter(A, Asp): + assert all((a == asp).all() for a, asp in zip(A, Asp)) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_extract.py b/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_extract.py new file mode 100644 index 0000000000000000000000000000000000000000..cfb443890f9cae4955bdec9264d6f1b9afb5007b --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_extract.py @@ -0,0 +1,61 @@ +"""test sparse matrix construction functions""" + +import pytest + +from numpy.testing import assert_equal +from scipy.sparse import csr_matrix, csr_array, sparray + +import numpy as np +from scipy.sparse import _extract + + +class TestExtract: + def setup_method(self): + self.cases = [ + csr_array([[1,2]]), + csr_array([[1,0]]), + csr_array([[0,0]]), + csr_array([[1],[2]]), + csr_array([[1],[0]]), + csr_array([[0],[0]]), + csr_array([[1,2],[3,4]]), + csr_array([[0,1],[0,0]]), + csr_array([[0,0],[1,0]]), + csr_array([[0,0],[0,0]]), + csr_array([[1,2,0,0,3],[4,5,0,6,7],[0,0,8,9,0]]), + csr_array([[1,2,0,0,3],[4,5,0,6,7],[0,0,8,9,0]]).T, + ] + + def test_find(self): + for A in self.cases: + I,J,V = _extract.find(A) + B = csr_array((V,(I,J)), shape=A.shape) + assert_equal(A.toarray(), B.toarray()) + + def test_tril(self): + for A in self.cases: + B = A.toarray() + for k in [-3,-2,-1,0,1,2,3]: + assert_equal(_extract.tril(A,k=k).toarray(), np.tril(B,k=k)) + + def test_triu(self): + for A in self.cases: + B = A.toarray() + for k in [-3,-2,-1,0,1,2,3]: + assert_equal(_extract.triu(A,k=k).toarray(), np.triu(B,k=k)) + + def test_array_vs_matrix(self): + for A in self.cases: + assert isinstance(_extract.tril(A), sparray) + assert isinstance(_extract.triu(A), sparray) + M = csr_matrix(A) + assert not isinstance(_extract.tril(M), sparray) + assert not isinstance(_extract.triu(M), sparray) + + +def test_deprecated_warnings_output_defaults_switch_from_spmatrix(): + A = np.array([[1, 2], [3, 0]]) + with pytest.deprecated_call(match=".*switching.*sparse array int"): + _extract.tril(A) + with pytest.deprecated_call(match=".*switching.*sparse array int"): + _extract.triu(A) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_indexing1d.py b/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_indexing1d.py new file mode 100644 index 0000000000000000000000000000000000000000..6b41adc59df59594587b92179e905ebc5bfd9510 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_indexing1d.py @@ -0,0 +1,611 @@ +import contextlib +import warnings + +import pytest +import numpy as np +from numpy.testing import assert_allclose, assert_equal + +from scipy.sparse import csr_array, dok_array, SparseEfficiencyWarning +from .test_arithmetic1d import toarray + + +formats_for_index1d = [csr_array, dok_array] + + +@contextlib.contextmanager +def check_remains_sorted(X): + """Checks that sorted indices property is retained through an operation""" + yield + if not hasattr(X, 'has_sorted_indices') or not X.has_sorted_indices: + return + indices = X.indices.copy() + X.has_sorted_indices = False + X.sort_indices() + assert_equal(indices, X.indices, 'Expected sorted indices, found unsorted') + + +@pytest.mark.parametrize("spcreator", formats_for_index1d) +class TestGetSet1D: + def test_None_index(self, spcreator): + D = np.array([4, 3, 0]) + A = spcreator(D) + + N = D.shape[0] + for j in range(-N, N): + assert_equal(A[j, None].toarray(), D[j, None]) + assert_equal(A[None, j].toarray(), D[None, j]) + assert_equal(A[None, None, j].toarray(), D[None, None, j]) + + def test_getitem_shape(self, spcreator): + A = spcreator(np.arange(3 * 4).reshape(3, 4)) + assert A[1, 2].ndim == 0 + assert A[1, 2:3].shape == (1,) + assert A[None, 1, 2:3].shape == (1, 1) + assert A[None, 1, 2].shape == (1,) + assert A[None, 1, 2, None].shape == (1, 1) + + # see gh-22458 + assert A[None, 1].shape == (1, 4) + assert A[1, None].shape == (1, 4) + assert A[None, 1, :].shape == (1, 4) + assert A[1, None, :].shape == (1, 4) + assert A[1, :, None].shape == (4, 1) + + # output is >2D + if A.format == "coo": + assert A[None, 2, 1, None, None].shape == (1, 1, 1) + assert A[None, 0:2, None, 1].shape == (1,2,1) + assert A[0:1, 1:, None].shape == (1,3,1) + assert A[1:, 1, None, None].shape == (3,1,1) + + def test_getelement(self, spcreator): + D = np.array([4, 3, 0]) + A = spcreator(D) + + N = D.shape[0] + for j in range(-N, N): + assert_equal(A[j], D[j]) + + for ij in [3, -4]: + with pytest.raises(IndexError, match='index (.*) out of (range|bounds)'): + A.__getitem__(ij) + + # single element tuples unwrapped + assert A[(0,)] == 4 + + with pytest.raises(IndexError, match='index (.*) out of (range|bounds)'): + A.__getitem__((4,)) + + @pytest.mark.parametrize( + "scalar_container", + [lambda x: csr_array(np.array([[x]])), np.array, lambda x: x], + ids=["sparse", "dense", "scalar"] + ) + def test_setelement(self, spcreator, scalar_container): + dtype = np.float64 + A = spcreator((12,), dtype=dtype) + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", + "Changing the sparsity structure of .* is expensive", + SparseEfficiencyWarning, + ) + A[0] = scalar_container(dtype(0)) + A[1] = scalar_container(dtype(3)) + A[8] = scalar_container(dtype(9.0)) + A[-2] = scalar_container(dtype(7)) + A[5] = scalar_container(9) + + A[-9,] = scalar_container(dtype(8)) + A[1,] = scalar_container(dtype(5)) # overwrite using 1-tuple index + + for ij in [13, -14, (13,), (14,)]: + with pytest.raises(IndexError, match='out of (range|bounds)'): + A.__setitem__(ij, 123.0) + + +@pytest.mark.parametrize("spcreator", formats_for_index1d) +class TestSlicingAndFancy1D: + ####################### + # Int-like Array Index + ####################### + def test_get_array_index(self, spcreator): + D = np.array([4, 3, 0]) + A = spcreator(D) + + assert_equal(A[()].toarray(), D[()]) + for ij in [(0, 3), (3,)]: + with pytest.raises(IndexError, match='out of (range|bounds)|many indices'): + A.__getitem__(ij) + + def test_set_array_index(self, spcreator): + dtype = np.float64 + A = spcreator((12,), dtype=dtype) + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", + "Changing the sparsity structure of .* is expensive", + SparseEfficiencyWarning, + ) + A[np.array(6)] = dtype(4.0) # scalar index + A[np.array(6)] = dtype(2.0) # overwrite with scalar index + assert_equal(A.toarray(), [0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0]) + + for ij in [(13,), (-14,)]: + with pytest.raises(IndexError, match='index .* out of (range|bounds)'): + A.__setitem__(ij, 123.0) + + for v in [(), (0, 3), [1, 2, 3], np.array([1, 2, 3])]: + msg = 'Trying to assign a sequence to an item' + with pytest.raises(ValueError, match=msg): + A.__setitem__(0, v) + + #################### + # 1d Slice as index + #################### + def test_dtype_preservation(self, spcreator): + assert_equal(spcreator((10,), dtype=np.int16)[1:5].dtype, np.int16) + assert_equal(spcreator((6,), dtype=np.int32)[0:0:2].dtype, np.int32) + assert_equal(spcreator((6,), dtype=np.int64)[:].dtype, np.int64) + + def test_get_1d_slice(self, spcreator): + B = np.arange(50.) + A = spcreator(B) + assert_equal(B[:], A[:].toarray()) + assert_equal(B[2:5], A[2:5].toarray()) + + C = np.array([4, 0, 6, 0, 0, 0, 0, 0, 1]) + D = spcreator(C) + assert_equal(C[1:3], D[1:3].toarray()) + + # Now test slicing when a row contains only zeros + E = np.array([0, 0, 0, 0, 0]) + F = spcreator(E) + assert_equal(E[1:3], F[1:3].toarray()) + assert_equal(E[-2:], F[-2:].toarray()) + assert_equal(E[:], F[:].toarray()) + assert_equal(E[slice(None)], F[slice(None)].toarray()) + + def test_slicing_idx_slice(self, spcreator): + B = np.arange(50) + A = spcreator(B) + + # [i] + assert_equal(A[2], B[2]) + assert_equal(A[-1], B[-1]) + assert_equal(A[np.array(-2)], B[-2]) + + # [1:2] + assert_equal(A[:].toarray(), B[:]) + assert_equal(A[5:-2].toarray(), B[5:-2]) + assert_equal(A[5:12:3].toarray(), B[5:12:3]) + + # int8 slice + s = slice(np.int8(2), np.int8(4), None) + assert_equal(A[s].toarray(), B[2:4]) + + # np.s_ + s_ = np.s_ + slices = [s_[:2], s_[1:2], s_[3:], s_[3::2], + s_[15:20], s_[3:2], + s_[8:3:-1], s_[4::-2], s_[:5:-1], + 0, 1, s_[:], s_[1:5], -1, -2, -5, + np.array(-1), np.int8(-3)] + + for j, a in enumerate(slices): + x = A[a] + y = B[a] + if y.shape == (): + assert_equal(x, y, repr(a)) + else: + if x.size == 0 and y.size == 0: + pass + else: + assert_equal(x.toarray(), y, repr(a)) + + def test_ellipsis_1d_slicing(self, spcreator): + B = np.arange(50) + A = spcreator(B) + assert_equal(A[...].toarray(), B[...]) + assert_equal(A[...,].toarray(), B[...,]) + + ########################## + # Assignment with Slicing + ########################## + def test_slice_scalar_assign(self, spcreator): + A = spcreator((5,)) + B = np.zeros((5,)) + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", + "Changing the sparsity structure of .* is expensive", + SparseEfficiencyWarning, + ) + for C in [A, B]: + C[0:1] = 1 + C[2:0] = 4 + C[2:3] = 9 + C[3:] = 1 + C[3::-1] = 9 + assert_equal(A.toarray(), B) + + def test_slice_assign_2(self, spcreator): + shape = (10,) + + for idx in [slice(3), slice(None, 10, 4), slice(5, -2)]: + A = spcreator(shape) + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", + "Changing the sparsity structure of .* is expensive", + SparseEfficiencyWarning, + ) + A[idx] = 1 + B = np.zeros(shape) + B[idx] = 1 + msg = f"idx={idx!r}" + assert_allclose(A.toarray(), B, err_msg=msg) + + def test_self_self_assignment(self, spcreator): + # Tests whether a row of one lil_matrix can be assigned to another. + B = spcreator((5,)) + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", + "Changing the sparsity structure of .* is expensive", + SparseEfficiencyWarning, + ) + B[0] = 2 + B[1] = 0 + B[2] = 3 + B[3] = 10 + + A = B / 10 + B[:] = A[:] + assert_equal(A[:].toarray(), B[:].toarray()) + + A = B / 10 + B[:] = A[:1] + assert_equal(np.zeros((5,)) + A[0], B.toarray()) + + A = B / 10 + B[:-1] = A[1:] + assert_equal(A[1:].toarray(), B[:-1].toarray()) + + def test_slice_assignment(self, spcreator): + B = spcreator((4,)) + expected = np.array([10, 0, 14, 0]) + block = [2, 1] + + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", + "Changing the sparsity structure of .* is expensive", + SparseEfficiencyWarning, + ) + B[0] = 5 + B[2] = 7 + B[:] = B + B + assert_equal(B.toarray(), expected) + + B[:2] = csr_array(block) + assert_equal(B.toarray()[:2], block) + + def test_set_slice(self, spcreator): + A = spcreator((5,)) + B = np.zeros(5, float) + s_ = np.s_ + slices = [s_[:2], s_[1:2], s_[3:], s_[3::2], + s_[8:3:-1], s_[4::-2], s_[:5:-1], + 0, 1, s_[:], s_[1:5], -1, -2, -5, + np.array(-1), np.int8(-3)] + + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", + "Changing the sparsity structure of .* is expensive", + SparseEfficiencyWarning, + ) + for j, a in enumerate(slices): + A[a] = j + B[a] = j + assert_equal(A.toarray(), B, repr(a)) + + A[1:10:2] = range(1, 5, 2) + B[1:10:2] = range(1, 5, 2) + assert_equal(A.toarray(), B) + + # The next commands should raise exceptions + toobig = list(range(100)) + with pytest.raises(ValueError, match='Trying to assign a sequence to an item'): + A.__setitem__(0, toobig) + with pytest.raises(ValueError, match='could not be broadcast together'): + A.__setitem__(slice(None), toobig) + + def test_assign_empty(self, spcreator): + A = spcreator(np.ones(3)) + B = spcreator((2,)) + A[:2] = B + assert_equal(A.toarray(), [0, 0, 1]) + + #################### + # 1d Fancy Indexing + #################### + def test_dtype_preservation_empty_index(self, spcreator): + A = spcreator((2,), dtype=np.int16) + assert_equal(A[[False, False]].dtype, np.int16) + assert_equal(A[[]].dtype, np.int16) + + def test_bad_index(self, spcreator): + A = spcreator(np.zeros(5)) + with pytest.raises(IndexError, match='Index dimension must be 1 or 2'): + A.__getitem__("foo") + with pytest.raises(IndexError, match='tuple index out of range'): + A.__getitem__((2, "foo")) + + def test_fancy_indexing_2darray(self, spcreator): + B = np.arange(50).reshape((5, 10)) + A = spcreator(B) + + # [i] + assert_equal(A[[1, 3]].toarray(), B[[1, 3]]) + + # [i,[1,2]] + assert_equal(A[3, [1, 3]].toarray(), B[3, [1, 3]]) + assert_equal(A[-1, [2, -5]].toarray(), B[-1, [2, -5]]) + assert_equal(A[np.array(-1), [2, -5]].toarray(), B[-1, [2, -5]]) + assert_equal(A[-1, np.array([2, -5])].toarray(), B[-1, [2, -5]]) + assert_equal(A[np.array(-1), np.array([2, -5])].toarray(), B[-1, [2, -5]]) + + # [1:2,[1,2]] + assert_equal(A[:, [2, 8, 3, -1]].toarray(), B[:, [2, 8, 3, -1]]) + assert_equal(A[3:4, [9]].toarray(), B[3:4, [9]]) + assert_equal(A[1:4, [-1, -5]].toarray(), B[1:4, [-1, -5]]) + assert_equal(A[1:4, np.array([-1, -5])].toarray(), B[1:4, [-1, -5]]) + + # [[1,2],j] + assert_equal(A[[1, 3], 3].toarray(), B[[1, 3], 3]) + assert_equal(A[[2, -5], -4].toarray(), B[[2, -5], -4]) + assert_equal(A[np.array([2, -5]), -4].toarray(), B[[2, -5], -4]) + assert_equal(A[[2, -5], np.array(-4)].toarray(), B[[2, -5], -4]) + assert_equal(A[np.array([2, -5]), np.array(-4)].toarray(), B[[2, -5], -4]) + + # [[1,2],1:2] + assert_equal(A[[1, 3], :].toarray(), B[[1, 3], :]) + assert_equal(A[[2, -5], 8:-1].toarray(), B[[2, -5], 8:-1]) + assert_equal(A[np.array([2, -5]), 8:-1].toarray(), B[[2, -5], 8:-1]) + + # [[1,2],[1,2]] + assert_equal(toarray(A[[1, 3], [2, 4]]), B[[1, 3], [2, 4]]) + assert_equal(toarray(A[[-1, -3], [2, -4]]), B[[-1, -3], [2, -4]]) + assert_equal( + toarray(A[np.array([-1, -3]), [2, -4]]), B[[-1, -3], [2, -4]] + ) + assert_equal( + toarray(A[[-1, -3], np.array([2, -4])]), B[[-1, -3], [2, -4]] + ) + assert_equal( + toarray(A[np.array([-1, -3]), np.array([2, -4])]), B[[-1, -3], [2, -4]] + ) + + # [[[1],[2]],[1,2]] + assert_equal(A[[[1], [3]], [2, 4]].toarray(), B[[[1], [3]], [2, 4]]) + assert_equal( + A[[[-1], [-3], [-2]], [2, -4]].toarray(), + B[[[-1], [-3], [-2]], [2, -4]] + ) + assert_equal( + A[np.array([[-1], [-3], [-2]]), [2, -4]].toarray(), + B[[[-1], [-3], [-2]], [2, -4]] + ) + assert_equal( + A[[[-1], [-3], [-2]], np.array([2, -4])].toarray(), + B[[[-1], [-3], [-2]], [2, -4]] + ) + assert_equal( + A[np.array([[-1], [-3], [-2]]), np.array([2, -4])].toarray(), + B[[[-1], [-3], [-2]], [2, -4]] + ) + + # [[1,2]] + assert_equal(A[[1, 3]].toarray(), B[[1, 3]]) + assert_equal(A[[-1, -3]].toarray(), B[[-1, -3]]) + assert_equal(A[np.array([-1, -3])].toarray(), B[[-1, -3]]) + + # [[1,2],:][:,[1,2]] + assert_equal( + A[[1, 3], :][:, [2, 4]].toarray(), B[[1, 3], :][:, [2, 4]] + ) + assert_equal( + A[[-1, -3], :][:, [2, -4]].toarray(), B[[-1, -3], :][:, [2, -4]] + ) + assert_equal( + A[np.array([-1, -3]), :][:, np.array([2, -4])].toarray(), + B[[-1, -3], :][:, [2, -4]] + ) + + # [:,[1,2]][[1,2],:] + assert_equal( + A[:, [1, 3]][[2, 4], :].toarray(), B[:, [1, 3]][[2, 4], :] + ) + assert_equal( + A[:, [-1, -3]][[2, -4], :].toarray(), B[:, [-1, -3]][[2, -4], :] + ) + assert_equal( + A[:, np.array([-1, -3])][np.array([2, -4]), :].toarray(), + B[:, [-1, -3]][[2, -4], :] + ) + + def test_fancy_indexing(self, spcreator): + B = np.arange(50) + A = spcreator(B) + + # [i] + assert_equal(A[[3]].toarray(), B[[3]]) + + # [np.array] + assert_equal(A[[1, 3]].toarray(), B[[1, 3]]) + assert_equal(A[[2, -5]].toarray(), B[[2, -5]]) + assert_equal(A[np.array(-1)], B[-1]) + assert_equal(A[np.array([-1, 2])].toarray(), B[[-1, 2]]) + assert_equal(A[np.array(5)], B[np.array(5)]) + + # [[[1],[2]]] + ind = np.array([[1], [3]]) + assert_equal(A[ind].toarray(), B[ind]) + ind = np.array([[-1], [-3], [-2]]) + assert_equal(A[ind].toarray(), B[ind]) + + # [[1, 2]] + assert_equal(A[[1, 3]].toarray(), B[[1, 3]]) + assert_equal(A[[-1, -3]].toarray(), B[[-1, -3]]) + assert_equal(A[np.array([-1, -3])].toarray(), B[[-1, -3]]) + + # [[1, 2]][[1, 2]] + assert_equal(A[[1, 5, 2, 8]][[1, 3]].toarray(), + B[[1, 5, 2, 8]][[1, 3]]) + assert_equal(A[[-1, -5, 2, 8]][[1, -4]].toarray(), + B[[-1, -5, 2, 8]][[1, -4]]) + + def test_fancy_indexing_boolean(self, spcreator): + np.random.seed(1234) # make runs repeatable + + B = np.arange(50) + A = spcreator(B) + + I = np.array(np.random.randint(0, 2, size=50), dtype=bool) + + assert_equal(toarray(A[I]), B[I]) + assert_equal(toarray(A[B > 9]), B[B > 9]) + + Z1 = np.zeros(51, dtype=bool) + Z2 = np.zeros(51, dtype=bool) + Z2[-1] = True + Z3 = np.zeros(51, dtype=bool) + Z3[0] = True + + msg = 'bool index .* has shape|boolean index did not match' + with pytest.raises(IndexError, match=msg): + A.__getitem__(Z1) + with pytest.raises(IndexError, match=msg): + A.__getitem__(Z2) + with pytest.raises(IndexError, match=msg): + A.__getitem__(Z3) + + def test_fancy_indexing_sparse_boolean(self, spcreator): + np.random.seed(1234) # make runs repeatable + + B = np.arange(20) + A = spcreator(B) + + X = np.array(np.random.randint(0, 2, size=20), dtype=bool) + Xsp = csr_array(X) + + assert_equal(toarray(A[Xsp]), B[X]) + assert_equal(toarray(A[A > 9]), B[B > 9]) + + Y = np.array(np.random.randint(0, 2, size=60), dtype=bool) + + Ysp = csr_array(Y) + + with pytest.raises(IndexError, match='bool index .* has shape|only integers'): + A.__getitem__(Ysp) + with pytest.raises(IndexError, match='tuple index out of range|only integers'): + A.__getitem__((Xsp, 1)) + + def test_fancy_indexing_seq_assign(self, spcreator): + mat = spcreator(np.array([1, 0])) + with pytest.raises(ValueError, match='Trying to assign a sequence to an item'): + mat.__setitem__(0, np.array([1, 2])) + + def test_fancy_indexing_empty(self, spcreator): + B = np.arange(50) + B[3:9] = 0 + B[30] = 0 + A = spcreator(B) + + K = np.array([False] * 50) + assert_equal(toarray(A[K]), B[K]) + K = np.array([], dtype=int) + assert_equal(toarray(A[K]), B[K]) + J = np.array([0, 1, 2, 3, 4], dtype=int) + assert_equal(toarray(A[J]), B[J]) + + ############################ + # 1d Fancy Index Assignment + ############################ + def test_bad_index_assign(self, spcreator): + A = spcreator(np.zeros(5)) + msg = 'Index dimension must be 1 or 2|only integers' + with pytest.raises((IndexError, ValueError, TypeError), match=msg): + A.__setitem__("foo", 2) + + def test_fancy_indexing_set(self, spcreator): + M = (5,) + + # [1:2] + for j in [[2, 3, 4], slice(None, 10, 4), np.arange(3), + slice(5, -2), slice(2, 5)]: + A = spcreator(M) + B = np.zeros(M) + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", + "Changing the sparsity structure of .* is expensive", + SparseEfficiencyWarning, + ) + B[j] = 1 + with check_remains_sorted(A): + A[j] = 1 + assert_allclose(A.toarray(), B) + + def test_sequence_assignment(self, spcreator): + A = spcreator((4,)) + B = spcreator((3,)) + + i0 = [0, 1, 2] + i1 = (0, 1, 2) + i2 = np.array(i0) + + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", + "Changing the sparsity structure of .* is expensive", + SparseEfficiencyWarning, + ) + with check_remains_sorted(A): + A[i0] = B[i0] + msg = "Too many indices for array|tuple index out of range" + with pytest.raises(IndexError, match=msg): + B.__getitem__(i1) + A[i2] = B[i2] + assert_equal(A[:3].toarray(), B.toarray()) + assert A.shape == (4,) + + # slice + A = spcreator((4,)) + with check_remains_sorted(A): + A[1:3] = [10, 20] + assert_equal(A.toarray(), [0, 10, 20, 0]) + + # array + A = spcreator((4,)) + B = np.zeros(4) + with check_remains_sorted(A): + for C in [A, B]: + C[[0, 1, 2]] = [4, 5, 6] + assert_equal(A.toarray(), B) + + def test_fancy_assign_empty(self, spcreator): + B = np.arange(50) + B[2] = 0 + B[[3, 6]] = 0 + A = spcreator(B) + + K = np.array([False] * 50) + A[K] = 42 + assert_equal(A.toarray(), B) + + K = np.array([], dtype=int) + A[K] = 42 + assert_equal(A.toarray(), B) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_matrix_io.py b/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_matrix_io.py new file mode 100644 index 0000000000000000000000000000000000000000..50d97a412125dd59c3fc2717037fa22da72505c6 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_matrix_io.py @@ -0,0 +1,116 @@ +import os +import numpy as np +import tempfile + +import pytest +from pytest import raises as assert_raises +from numpy.testing import assert_equal, assert_ + +from scipy.sparse import (sparray, csr_array, coo_array, save_npz, load_npz, + csc_matrix, csr_matrix, bsr_matrix, dia_matrix, + coo_matrix, dok_matrix) + + +DATA_DIR = os.path.join(os.path.dirname(__file__), 'data') + + +def _save_and_load(matrix): + fd, tmpfile = tempfile.mkstemp(suffix='.npz') + os.close(fd) + try: + save_npz(tmpfile, matrix) + loaded_matrix = load_npz(tmpfile) + finally: + os.remove(tmpfile) + return loaded_matrix + +def _check_save_and_load(dense_matrix): + for matrix_class in [csc_matrix, csr_matrix, bsr_matrix, dia_matrix, coo_matrix]: + matrix = matrix_class(dense_matrix) + loaded_matrix = _save_and_load(matrix) + assert_(type(loaded_matrix) is matrix_class) + assert_(loaded_matrix.shape == dense_matrix.shape) + assert_(loaded_matrix.dtype == dense_matrix.dtype) + assert_equal(loaded_matrix.toarray(), dense_matrix) + +def test_save_and_load_random(): + N = 10 + np.random.seed(0) + dense_matrix = np.random.random((N, N)) + dense_matrix[dense_matrix > 0.7] = 0 + _check_save_and_load(dense_matrix) + +def test_save_and_load_empty(): + dense_matrix = np.zeros((4,6)) + _check_save_and_load(dense_matrix) + +def test_save_and_load_one_entry(): + dense_matrix = np.zeros((4,6)) + dense_matrix[1,2] = 1 + _check_save_and_load(dense_matrix) + +def test_sparray_vs_spmatrix(): + #save/load matrix + fd, tmpfile = tempfile.mkstemp(suffix='.npz') + os.close(fd) + try: + save_npz(tmpfile, csr_matrix([[1.2, 0, 0.9], [0, 0.3, 0]])) + loaded_matrix = load_npz(tmpfile) + finally: + os.remove(tmpfile) + + #save/load array + fd, tmpfile = tempfile.mkstemp(suffix='.npz') + os.close(fd) + try: + save_npz(tmpfile, csr_array([[1.2, 0, 0.9], [0, 0.3, 0]])) + loaded_array = load_npz(tmpfile) + finally: + os.remove(tmpfile) + + assert not isinstance(loaded_matrix, sparray) + assert isinstance(loaded_array, sparray) + assert_(loaded_matrix.dtype == loaded_array.dtype) + assert_equal(loaded_matrix.toarray(), loaded_array.toarray()) + +@pytest.mark.parametrize("value", [0, 1.2]) +@pytest.mark.parametrize("ndim", [1, 2, 3]) +def test_nd_coo_format(ndim, value): + A = coo_array([value]).reshape((1,) * ndim) + + #save/load array + fd, tmpfile = tempfile.mkstemp(suffix='.npz') + os.close(fd) + try: + save_npz(tmpfile, A) + loaded_A = load_npz(tmpfile) + finally: + os.remove(tmpfile) + + assert isinstance(loaded_A, coo_array) + assert_(loaded_A.shape == A.shape) + assert_equal(A.toarray(), loaded_A.toarray()) + +def test_malicious_load(): + class Executor: + def __reduce__(self): + return (assert_, (False, 'unexpected code execution')) + + fd, tmpfile = tempfile.mkstemp(suffix='.npz') + os.close(fd) + try: + np.savez(tmpfile, format=Executor()) + + # Should raise a ValueError, not execute code + assert_raises(ValueError, load_npz, tmpfile) + finally: + os.remove(tmpfile) + +def test_implemented_error(): + # Attempts to save an unsupported type and checks that an + # NotImplementedError is raised. + + x = dok_matrix((2,3)) + x[0,1] = 1 + + assert_raises(NotImplementedError, save_npz, 'x.npz', x) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_minmax1d.py b/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_minmax1d.py new file mode 100644 index 0000000000000000000000000000000000000000..dca3f44fa485070805995c2f76c0c511123ce355 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_minmax1d.py @@ -0,0 +1,128 @@ +"""Test of min-max 1D features of sparse array classes""" + +import pytest + +import numpy as np + +from numpy.testing import assert_equal, assert_array_equal + +from scipy.sparse import coo_array, csr_array, csc_array, bsr_array +from scipy.sparse import coo_matrix, csr_matrix, csc_matrix, bsr_matrix +from scipy.sparse._sputils import isscalarlike + + +def toarray(a): + if isinstance(a, np.ndarray) or isscalarlike(a): + return a + return a.toarray() + + +formats_for_minmax = [bsr_array, coo_array, csc_array, csr_array] +formats_for_minmax_supporting_1d = [coo_array, csr_array] + + +@pytest.mark.parametrize("spcreator", formats_for_minmax_supporting_1d) +class Test_MinMaxMixin1D: + def test_minmax(self, spcreator): + D = np.arange(5) + X = spcreator(D) + + assert_equal(X.min(), 0) + assert_equal(X.max(), 4) + assert_equal((-X).min(), -4) + assert_equal((-X).max(), 0) + + def test_minmax_axis(self, spcreator): + D = np.arange(50) + X = spcreator(D) + + for axis in [0, -1]: + assert_array_equal( + toarray(X.max(axis=axis)), D.max(axis=axis, keepdims=True) + ) + assert_array_equal( + toarray(X.min(axis=axis)), D.min(axis=axis, keepdims=True) + ) + for axis in [-2, 1]: + with pytest.raises(ValueError, match="axis out of range"): + X.min(axis=axis) + with pytest.raises(ValueError, match="axis out of range"): + X.max(axis=axis) + + def test_numpy_minmax(self, spcreator): + dat = np.array([0, 1, 2]) + datsp = spcreator(dat) + assert_array_equal(np.min(datsp), np.min(dat)) + assert_array_equal(np.max(datsp), np.max(dat)) + + + def test_argmax(self, spcreator): + D1 = np.array([-1, 5, 2, 3]) + D2 = np.array([0, 0, -1, -2]) + D3 = np.array([-1, -2, -3, -4]) + D4 = np.array([1, 2, 3, 4]) + D5 = np.array([1, 2, 0, 0]) + + for D in [D1, D2, D3, D4, D5]: + mat = spcreator(D) + + assert_equal(mat.argmax(), np.argmax(D)) + assert_equal(mat.argmin(), np.argmin(D)) + + assert_equal(mat.argmax(axis=0), np.argmax(D, axis=0)) + assert_equal(mat.argmin(axis=0), np.argmin(D, axis=0)) + + D6 = np.empty((0,)) + + for axis in [None, 0]: + mat = spcreator(D6) + with pytest.raises(ValueError, match="to an empty matrix"): + mat.argmin(axis=axis) + with pytest.raises(ValueError, match="to an empty matrix"): + mat.argmax(axis=axis) + + +@pytest.mark.parametrize("spcreator", formats_for_minmax) +class Test_ShapeMinMax2DWithAxis: + def test_minmax(self, spcreator): + dat = np.array([[-1, 5, 0, 3], [0, 0, -1, -2], [0, 0, 1, 2]]) + datsp = spcreator(dat) + + for (spminmax, npminmax) in [ + (datsp.min, np.min), + (datsp.max, np.max), + (datsp.nanmin, np.nanmin), + (datsp.nanmax, np.nanmax), + ]: + for ax, result_shape in [(0, (4,)), (1, (3,))]: + assert_equal(toarray(spminmax(axis=ax)), npminmax(dat, axis=ax)) + assert_equal(spminmax(axis=ax).shape, result_shape) + assert spminmax(axis=ax).format == "coo" + + for spminmax in [datsp.argmin, datsp.argmax]: + for ax in [0, 1]: + assert isinstance(spminmax(axis=ax), np.ndarray) + + # verify spmatrix behavior + spmat_form = { + 'coo': coo_matrix, + 'csr': csr_matrix, + 'csc': csc_matrix, + 'bsr': bsr_matrix, + } + datspm = spmat_form[datsp.format](dat) + + for spm, npm in [ + (datspm.min, np.min), + (datspm.max, np.max), + (datspm.nanmin, np.nanmin), + (datspm.nanmax, np.nanmax), + ]: + for ax, result_shape in [(0, (1, 4)), (1, (3, 1))]: + assert_equal(toarray(spm(axis=ax)), npm(dat, axis=ax, keepdims=True)) + assert_equal(spm(axis=ax).shape, result_shape) + assert spm(axis=ax).format == "coo" + + for spminmax in [datspm.argmin, datspm.argmax]: + for ax in [0, 1]: + assert isinstance(spminmax(axis=ax), np.ndarray) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_sparsetools.py b/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_sparsetools.py new file mode 100644 index 0000000000000000000000000000000000000000..6ac2f2b0efd4de1d9b947dc0bc28b14881bc9db2 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_sparsetools.py @@ -0,0 +1,340 @@ +import sys +import os +import gc +import threading + +import numpy as np +from numpy.testing import assert_equal, assert_, assert_allclose +from scipy.sparse import (_sparsetools, coo_matrix, csr_matrix, csc_matrix, + bsr_matrix, dia_matrix) +from scipy.sparse._sputils import supported_dtypes +from scipy._lib._testutils import check_free_memory + +import pytest +from pytest import raises as assert_raises + + +def int_to_int8(n): + """ + Wrap an integer to the interval [-128, 127]. + """ + return (n + 128) % 256 - 128 + + +def test_exception(): + assert_raises(MemoryError, _sparsetools.test_throw_error) + + +def test_threads(): + # Smoke test for parallel threaded execution; doesn't actually + # check that code runs in parallel, but just that it produces + # expected results. + nthreads = 10 + niter = 100 + + n = 20 + a = csr_matrix(np.ones([n, n])) + bres = [] + + class Worker(threading.Thread): + def run(self): + b = a.copy() + for j in range(niter): + _sparsetools.csr_plus_csr(n, n, + a.indptr, a.indices, a.data, + a.indptr, a.indices, a.data, + b.indptr, b.indices, b.data) + bres.append(b) + + threads = [Worker() for _ in range(nthreads)] + for thread in threads: + thread.start() + for thread in threads: + thread.join() + + for b in bres: + assert_(np.all(b.toarray() == 2)) + + +def test_regression_std_vector_dtypes(): + # Regression test for gh-3780, checking the std::vector typemaps + # in sparsetools.cxx are complete. + for dtype in supported_dtypes: + ad = np.array([[1, 2], [3, 4]]).astype(dtype) + a = csr_matrix(ad, dtype=dtype) + + # getcol is one function using std::vector typemaps, and should not fail + assert_equal(a.getcol(0).toarray(), ad[:, :1]) + + +@pytest.mark.slow +@pytest.mark.xfail_on_32bit("Can't create large array for test") +def test_nnz_overflow(): + # Regression test for gh-7230 / gh-7871, checking that coo_toarray + # with nnz > int32max doesn't overflow. + nnz = np.iinfo(np.int32).max + 1 + # Ensure ~20 GB of RAM is free to run this test. + check_free_memory((4 + 4 + 1) * nnz / 1e6 + 0.5) + + # Use nnz duplicate entries to keep the dense version small. + row = np.zeros(nnz, dtype=np.int32) + col = np.zeros(nnz, dtype=np.int32) + data = np.zeros(nnz, dtype=np.int8) + data[-1] = 4 + s = coo_matrix((data, (row, col)), shape=(1, 1), copy=False) + # Sums nnz duplicates to produce a 1x1 array containing 4. + d = s.toarray() + + assert_allclose(d, [[4]]) + + +@pytest.mark.skipif( + not (sys.platform.startswith('linux') and np.dtype(np.intp).itemsize >= 8), + reason="test requires 64-bit Linux" +) +class TestInt32Overflow: + """ + Some of the sparsetools routines use dense 2D matrices whose + total size is not bounded by the nnz of the sparse matrix. These + routines used to suffer from int32 wraparounds; here, we try to + check that the wraparounds don't occur any more. + """ + # choose n large enough + n = 50000 + + def setup_method(self): + assert self.n**2 > np.iinfo(np.int32).max + + # check there's enough memory even if everything is run at the + # same time + try: + parallel_count = int(os.environ.get('PYTEST_XDIST_WORKER_COUNT', '1')) + except ValueError: + parallel_count = np.inf + + check_free_memory(3000 * parallel_count) + + def teardown_method(self): + gc.collect() + + @pytest.mark.fail_slow(2) # keep in fast set, only non-slow test + def test_coo_todense(self): + # Check *_todense routines (cf. gh-2179) + # + # All of them in the end call coo_matrix.todense + + n = self.n + + i = np.array([0, n-1]) + j = np.array([0, n-1]) + data = np.array([1, 2], dtype=np.int8) + m = coo_matrix((data, (i, j))) + + r = m.todense() + assert_equal(r[0,0], 1) + assert_equal(r[-1,-1], 2) + del r + gc.collect() + + @pytest.mark.slow + def test_matvecs(self): + # Check *_matvecs routines + n = self.n + + i = np.array([0, n-1]) + j = np.array([0, n-1]) + data = np.array([1, 2], dtype=np.int8) + m = coo_matrix((data, (i, j))) + + b = np.ones((n, n), dtype=np.int8) + for sptype in (csr_matrix, csc_matrix, bsr_matrix): + m2 = sptype(m) + r = m2.dot(b) + assert_equal(r[0,0], 1) + assert_equal(r[-1,-1], 2) + del r + gc.collect() + + del b + gc.collect() + + @pytest.mark.slow + def test_dia_matvec(self): + # Check: huge dia_matrix _matvec + n = self.n + data = np.ones((n, n), dtype=np.int8) + offsets = np.arange(n) + m = dia_matrix((data, offsets), shape=(n, n)) + v = np.ones(m.shape[1], dtype=np.int8) + r = m.dot(v) + assert_equal(r[0], int_to_int8(n)) + del data, offsets, m, v, r + gc.collect() + + _bsr_ops = [pytest.param("matmat", marks=pytest.mark.xslow), + pytest.param("matvecs", marks=pytest.mark.xslow), + "matvec", + "diagonal", + "sort_indices", + pytest.param("transpose", marks=pytest.mark.xslow)] + + @pytest.mark.slow + @pytest.mark.parametrize("op", _bsr_ops) + def test_bsr_1_block(self, op): + # Check: huge bsr_matrix (1-block) + # + # The point here is that indices inside a block may overflow. + + def get_matrix(): + n = self.n + data = np.ones((1, n, n), dtype=np.int8) + indptr = np.array([0, 1], dtype=np.int32) + indices = np.array([0], dtype=np.int32) + m = bsr_matrix((data, indices, indptr), blocksize=(n, n), copy=False) + del data, indptr, indices + return m + + gc.collect() + try: + getattr(self, "_check_bsr_" + op)(get_matrix) + finally: + gc.collect() + + @pytest.mark.slow + @pytest.mark.parametrize("op", _bsr_ops) + def test_bsr_n_block(self, op): + # Check: huge bsr_matrix (n-block) + # + # The point here is that while indices within a block don't + # overflow, accumulators across many block may. + + def get_matrix(): + n = self.n + data = np.ones((n, n, 1), dtype=np.int8) + indptr = np.array([0, n], dtype=np.int32) + indices = np.arange(n, dtype=np.int32) + m = bsr_matrix((data, indices, indptr), blocksize=(n, 1), copy=False) + del data, indptr, indices + return m + + gc.collect() + try: + getattr(self, "_check_bsr_" + op)(get_matrix) + finally: + gc.collect() + + def _check_bsr_matvecs(self, m): # skip name check + m = m() + n = self.n + + # _matvecs + r = m.dot(np.ones((n, 2), dtype=np.int8)) + assert_equal(r[0, 0], int_to_int8(n)) + + def _check_bsr_matvec(self, m): # skip name check + m = m() + n = self.n + + # _matvec + r = m.dot(np.ones((n,), dtype=np.int8)) + assert_equal(r[0], int_to_int8(n)) + + def _check_bsr_diagonal(self, m): # skip name check + m = m() + n = self.n + + # _diagonal + r = m.diagonal() + assert_equal(r, np.ones(n)) + + def _check_bsr_sort_indices(self, m): # skip name check + # _sort_indices + m = m() + m.sort_indices() + + def _check_bsr_transpose(self, m): # skip name check + # _transpose + m = m() + m.transpose() + + def _check_bsr_matmat(self, m): # skip name check + m = m() + n = self.n + + # _bsr_matmat + m2 = bsr_matrix(np.ones((n, 2), dtype=np.int8), blocksize=(m.blocksize[1], 2)) + m.dot(m2) # shouldn't SIGSEGV + del m2 + + # _bsr_matmat + m2 = bsr_matrix(np.ones((2, n), dtype=np.int8), blocksize=(2, m.blocksize[0])) + m2.dot(m) # shouldn't SIGSEGV + + +@pytest.mark.skip(reason="64-bit indices in sparse matrices not available") +def test_csr_matmat_int64_overflow(): + n = 3037000500 + assert n**2 > np.iinfo(np.int64).max + + # the test would take crazy amounts of memory + check_free_memory(n * (8*2 + 1) * 3 / 1e6) + + # int64 overflow + data = np.ones((n,), dtype=np.int8) + indptr = np.arange(n+1, dtype=np.int64) + indices = np.zeros(n, dtype=np.int64) + a = csr_matrix((data, indices, indptr)) + b = a.T + + assert_raises(RuntimeError, a.dot, b) + + +def test_upcast(): + a0 = csr_matrix([[np.pi, np.pi*1j], [3, 4]], dtype=complex) + b0 = np.array([256+1j, 2**32], dtype=complex) + + for a_dtype in supported_dtypes: + for b_dtype in supported_dtypes: + msg = f"({a_dtype!r}, {b_dtype!r})" + + if np.issubdtype(a_dtype, np.complexfloating): + a = a0.copy().astype(a_dtype) + else: + a = a0.real.copy().astype(a_dtype) + + if np.issubdtype(b_dtype, np.complexfloating): + b = b0.copy().astype(b_dtype) + else: + with np.errstate(invalid="ignore"): + # Casting a large value (2**32) to int8 causes a warning in + # numpy >1.23 + b = b0.real.copy().astype(b_dtype) + + if not (a_dtype == np.bool_ and b_dtype == np.bool_): + c = np.zeros((2,), dtype=np.bool_) + assert_raises(ValueError, _sparsetools.csr_matvec, + 2, 2, a.indptr, a.indices, a.data, b, c) + + if ((np.issubdtype(a_dtype, np.complexfloating) and + not np.issubdtype(b_dtype, np.complexfloating)) or + (not np.issubdtype(a_dtype, np.complexfloating) and + np.issubdtype(b_dtype, np.complexfloating))): + c = np.zeros((2,), dtype=np.float64) + assert_raises(ValueError, _sparsetools.csr_matvec, + 2, 2, a.indptr, a.indices, a.data, b, c) + + c = np.zeros((2,), dtype=np.result_type(a_dtype, b_dtype)) + _sparsetools.csr_matvec(2, 2, a.indptr, a.indices, a.data, b, c) + assert_allclose(c, np.dot(a.toarray(), b), err_msg=msg) + + +def test_endianness(): + d = np.ones((3,4)) + offsets = [-1,0,1] + + a = dia_matrix((d.astype('f8'), offsets), (4, 4)) + v = np.arange(4) + + assert_allclose(a.dot(v), [1, 3, 6, 5]) + assert_allclose(b.dot(v), [1, 3, 6, 5]) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_spfuncs.py b/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_spfuncs.py new file mode 100644 index 0000000000000000000000000000000000000000..75bc2d92c369be5799a904bc0938617f30321f12 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_spfuncs.py @@ -0,0 +1,97 @@ +from numpy import array, kron, diag +from numpy.testing import assert_, assert_equal + +from scipy.sparse import _spfuncs as spfuncs +from scipy.sparse import csr_matrix, csc_matrix, bsr_matrix +from scipy.sparse._sparsetools import (csr_scale_rows, csr_scale_columns, + bsr_scale_rows, bsr_scale_columns) + + +class TestSparseFunctions: + def test_scale_rows_and_cols(self): + D = array([[1, 0, 0, 2, 3], + [0, 4, 0, 5, 0], + [0, 0, 6, 7, 0]]) + + #TODO expose through function + S = csr_matrix(D) + v = array([1,2,3]) + csr_scale_rows(3,5,S.indptr,S.indices,S.data,v) + assert_equal(S.toarray(), diag(v)@D) + + S = csr_matrix(D) + v = array([1,2,3,4,5]) + csr_scale_columns(3,5,S.indptr,S.indices,S.data,v) + assert_equal(S.toarray(), D@diag(v)) + + # blocks + E = kron(D,[[1,2],[3,4]]) + S = bsr_matrix(E,blocksize=(2,2)) + v = array([1,2,3,4,5,6]) + bsr_scale_rows(3,5,2,2,S.indptr,S.indices,S.data,v) + assert_equal(S.toarray(), diag(v)@E) + + S = bsr_matrix(E,blocksize=(2,2)) + v = array([1,2,3,4,5,6,7,8,9,10]) + bsr_scale_columns(3,5,2,2,S.indptr,S.indices,S.data,v) + assert_equal(S.toarray(), E@diag(v)) + + E = kron(D,[[1,2,3],[4,5,6]]) + S = bsr_matrix(E,blocksize=(2,3)) + v = array([1,2,3,4,5,6]) + bsr_scale_rows(3,5,2,3,S.indptr,S.indices,S.data,v) + assert_equal(S.toarray(), diag(v)@E) + + S = bsr_matrix(E,blocksize=(2,3)) + v = array([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]) + bsr_scale_columns(3,5,2,3,S.indptr,S.indices,S.data,v) + assert_equal(S.toarray(), E@diag(v)) + + def test_estimate_blocksize(self): + mats = [] + mats.append([[0,1],[1,0]]) + mats.append([[1,1,0],[0,0,1],[1,0,1]]) + mats.append([[0],[0],[1]]) + mats = [array(x) for x in mats] + + blks = [] + blks.append([[1]]) + blks.append([[1,1],[1,1]]) + blks.append([[1,1],[0,1]]) + blks.append([[1,1,0],[1,0,1],[1,1,1]]) + blks = [array(x) for x in blks] + + for A in mats: + for B in blks: + X = kron(A,B) + r,c = spfuncs.estimate_blocksize(X) + assert_(r >= B.shape[0]) + assert_(c >= B.shape[1]) + + def test_count_blocks(self): + def gold(A,bs): + R,C = bs + I,J = A.nonzero() + return len(set(zip(I//R,J//C))) + + mats = [] + mats.append([[0]]) + mats.append([[1]]) + mats.append([[1,0]]) + mats.append([[1,1]]) + mats.append([[0,1],[1,0]]) + mats.append([[1,1,0],[0,0,1],[1,0,1]]) + mats.append([[0],[0],[1]]) + + for A in mats: + for B in mats: + X = kron(A,B) + Y = csr_matrix(X) + for R in range(1,6): + for C in range(1,6): + assert_equal(spfuncs.count_blocks(Y, (R, C)), gold(X, (R, C))) + + X = kron([[1,1,0],[0,0,1],[1,0,1]],[[1,1]]) + Y = csc_matrix(X) + assert_equal(spfuncs.count_blocks(X, (1, 2)), gold(X, (1, 2))) + assert_equal(spfuncs.count_blocks(Y, (1, 2)), gold(X, (1, 2))) diff --git a/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_sputils.py b/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_sputils.py new file mode 100644 index 0000000000000000000000000000000000000000..345f89f8585cb6906794220ae74e172cf1f45cba --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/sparse/tests/test_sputils.py @@ -0,0 +1,424 @@ +"""unit tests for sparse utility functions""" + +import numpy as np +from numpy.testing import assert_equal +import pytest +from pytest import raises as assert_raises +from scipy.sparse import _sputils as sputils, csr_array, bsr_array, dia_array, coo_array +from scipy.sparse._sputils import matrix + + +class TestSparseUtils: + + def test_upcast(self): + assert_equal(sputils.upcast('intc'), np.intc) + assert_equal(sputils.upcast('int32', 'float32'), np.float64) + assert_equal(sputils.upcast('bool', complex, float), np.complex128) + assert_equal(sputils.upcast('i', 'd'), np.float64) + + def test_getdtype(self): + A = np.array([1], dtype='int8') + + assert_equal(sputils.getdtype(None, default=float), float) + assert_equal(sputils.getdtype(None, a=A), np.int8) + + with assert_raises( + ValueError, + match="scipy.sparse does not support dtype object. .*", + ): + sputils.getdtype("O") + + with assert_raises( + ValueError, + match="scipy.sparse does not support dtype float16. .*", + ): + sputils.getdtype(None, default=np.float16) + + def test_isscalarlike(self): + assert_equal(sputils.isscalarlike(3.0), True) + assert_equal(sputils.isscalarlike(-4), True) + assert_equal(sputils.isscalarlike(2.5), True) + assert_equal(sputils.isscalarlike(1 + 3j), True) + assert_equal(sputils.isscalarlike(np.array(3)), True) + assert_equal(sputils.isscalarlike("16"), True) + + assert_equal(sputils.isscalarlike(np.array([3])), False) + assert_equal(sputils.isscalarlike([[3]]), False) + assert_equal(sputils.isscalarlike((1,)), False) + assert_equal(sputils.isscalarlike((1, 2)), False) + + def test_isintlike(self): + assert_equal(sputils.isintlike(-4), True) + assert_equal(sputils.isintlike(np.array(3)), True) + assert_equal(sputils.isintlike(np.array([3])), False) + with assert_raises( + ValueError, + match="Inexact indices into sparse matrices are not allowed" + ): + sputils.isintlike(3.0) + + assert_equal(sputils.isintlike(2.5), False) + assert_equal(sputils.isintlike(1 + 3j), False) + assert_equal(sputils.isintlike((1,)), False) + assert_equal(sputils.isintlike((1, 2)), False) + + def test_isshape(self): + assert_equal(sputils.isshape((1, 2)), True) + assert_equal(sputils.isshape((5, 2)), True) + + assert_equal(sputils.isshape((1.5, 2)), False) + assert_equal(sputils.isshape((2, 2, 2)), False) + assert_equal(sputils.isshape(([2], 2)), False) + assert_equal(sputils.isshape((-1, 2), nonneg=False),True) + assert_equal(sputils.isshape((2, -1), nonneg=False),True) + assert_equal(sputils.isshape((-1, 2), nonneg=True),False) + assert_equal(sputils.isshape((2, -1), nonneg=True),False) + + assert_equal(sputils.isshape((1.5, 2), allow_nd=(1, 2)), False) + assert_equal(sputils.isshape(([2], 2), allow_nd=(1, 2)), False) + assert_equal(sputils.isshape((2, 2, -2), nonneg=True, allow_nd=(1, 2)), + False) + assert_equal(sputils.isshape((2,), allow_nd=(1, 2)), True) + assert_equal(sputils.isshape((2, 2,), allow_nd=(1, 2)), True) + assert_equal(sputils.isshape((2, 2, 2), allow_nd=(1, 2)), False) + + def test_issequence(self): + assert_equal(sputils.issequence((1,)), True) + assert_equal(sputils.issequence((1, 2, 3)), True) + assert_equal(sputils.issequence([1]), True) + assert_equal(sputils.issequence([1, 2, 3]), True) + assert_equal(sputils.issequence(np.array([1, 2, 3])), True) + + assert_equal(sputils.issequence(np.array([[1], [2], [3]])), False) + assert_equal(sputils.issequence(3), False) + + def test_ismatrix(self): + assert_equal(sputils.ismatrix(((),)), True) + assert_equal(sputils.ismatrix([[1], [2]]), True) + assert_equal(sputils.ismatrix(np.arange(3)[None]), True) + + assert_equal(sputils.ismatrix([1, 2]), False) + assert_equal(sputils.ismatrix(np.arange(3)), False) + assert_equal(sputils.ismatrix([[[1]]]), False) + assert_equal(sputils.ismatrix(3), False) + + def test_isdense(self): + assert_equal(sputils.isdense(np.array([1])), True) + assert_equal(sputils.isdense(matrix([1])), True) + + def test_validateaxis(self): + with assert_raises(ValueError, match="does not accept 0D axis"): + sputils.validateaxis(()) + + for ax in [1.5, (0, 1.5), (1.5, 0)]: + with assert_raises(TypeError, match="must be an integer"): + sputils.validateaxis(ax) + for ax in [(1, 1), (1, -1), (0, -2)]: + with assert_raises(ValueError, match="duplicate value in axis"): + sputils.validateaxis(ax) + + # ndim 1 + for ax in [1, -2, (0, 1), (1, -1)]: + with assert_raises(ValueError, match="out of range"): + sputils.validateaxis(ax, ndim=1) + with assert_raises(ValueError, match="duplicate value in axis"): + sputils.validateaxis((0, -1), ndim=1) + # all valid axis values lead to None when canonical + for axis in (0, -1, None, (0,), (-1,)): + assert sputils.validateaxis(axis, ndim=1) is None + + # ndim 2 + for ax in [5, -5, (0, 5), (-5, 0)]: + with assert_raises(ValueError, match="out of range"): + sputils.validateaxis(ax, ndim=2) + for axis in ((0,), (1,), None): + assert sputils.validateaxis(axis, ndim=2) == axis + axis_2d = {-2: (0,), -1: (1,), 0: (0,), 1: (1,), (0, 1): None, (0, -1): None} + for axis, canonical_axis in axis_2d.items(): + assert sputils.validateaxis(axis, ndim=2) == canonical_axis + + # ndim 4 + for axis in ((2,), (3,), (2, 3), (2, 1), (0, 3)): + assert sputils.validateaxis(axis, ndim=4) == axis + axis_4d = {-4: (0,), -3: (1,), 2: (2,), 3: (3,), (3, -4): (3, 0)} + for axis, canonical_axis in axis_4d.items(): + sputils.validateaxis(axis, ndim=4) == canonical_axis + + @pytest.mark.parametrize("container", [csr_array, bsr_array]) + def test_safely_cast_index_compressed(self, container): + # This is slow to test completely as nnz > imax is big + # and indptr is big for some shapes + # So we don't test large nnz, nor csc_array (same code as csr_array) + imax = np.int64(np.iinfo(np.int32).max) + + # Shape 32bit + A32 = container((1, imax)) + # indices big type, small values + B32 = A32.copy() + B32.indices = B32.indices.astype(np.int64) + B32.indptr = B32.indptr.astype(np.int64) + + # Shape 64bit + # indices big type, small values + A64 = csr_array((1, imax + 1)) + # indices small type, small values + B64 = A64.copy() + B64.indices = B64.indices.astype(np.int32) + B64.indptr = B64.indptr.astype(np.int32) + # indices big type, big values + C64 = A64.copy() + C64.indices = np.array([imax + 1], dtype=np.int64) + C64.indptr = np.array([0, 1], dtype=np.int64) + C64.data = np.array([2.2]) + + assert (A32.indices.dtype, A32.indptr.dtype) == (np.int32, np.int32) + assert (B32.indices.dtype, B32.indptr.dtype) == (np.int64, np.int64) + assert (A64.indices.dtype, A64.indptr.dtype) == (np.int64, np.int64) + assert (B64.indices.dtype, B64.indptr.dtype) == (np.int32, np.int32) + assert (C64.indices.dtype, C64.indptr.dtype) == (np.int64, np.int64) + + for A in [A32, B32, A64, B64]: + indices, indptr = sputils.safely_cast_index_arrays(A, np.int32) + assert (indices.dtype, indptr.dtype) == (np.int32, np.int32) + indices, indptr = sputils.safely_cast_index_arrays(A, np.int64) + assert (indices.dtype, indptr.dtype) == (np.int64, np.int64) + + indices, indptr = sputils.safely_cast_index_arrays(A, A.indices.dtype) + assert indices is A.indices + assert indptr is A.indptr + + with assert_raises(ValueError): + sputils.safely_cast_index_arrays(C64, np.int32) + indices, indptr = sputils.safely_cast_index_arrays(C64, np.int64) + assert indices is C64.indices + assert indptr is C64.indptr + + def test_safely_cast_index_coo(self): + # This is slow to test completely as nnz > imax is big + # So we don't test large nnz + imax = np.int64(np.iinfo(np.int32).max) + + # Shape 32bit + A32 = coo_array((1, imax)) + # coords big type, small values + B32 = A32.copy() + B32.coords = tuple(co.astype(np.int64) for co in B32.coords) + + # Shape 64bit + # coords big type, small values + A64 = coo_array((1, imax + 1)) + # coords small type, small values + B64 = A64.copy() + B64.coords = tuple(co.astype(np.int32) for co in B64.coords) + # coords big type, big values + C64 = A64.copy() + C64.coords = (np.array([imax + 1]), np.array([0])) + C64.data = np.array([2.2]) + + assert A32.coords[0].dtype == np.int32 + assert B32.coords[0].dtype == np.int64 + assert A64.coords[0].dtype == np.int64 + assert B64.coords[0].dtype == np.int32 + assert C64.coords[0].dtype == np.int64 + + for A in [A32, B32, A64, B64]: + coords = sputils.safely_cast_index_arrays(A, np.int32) + assert coords[0].dtype == np.int32 + coords = sputils.safely_cast_index_arrays(A, np.int64) + assert coords[0].dtype == np.int64 + + coords = sputils.safely_cast_index_arrays(A, A.coords[0].dtype) + assert coords[0] is A.coords[0] + + with assert_raises(ValueError): + sputils.safely_cast_index_arrays(C64, np.int32) + coords = sputils.safely_cast_index_arrays(C64, np.int64) + assert coords[0] is C64.coords[0] + + def test_safely_cast_index_dia(self): + # This is slow to test completely as nnz > imax is big + # So we don't test large nnz + imax = np.int64(np.iinfo(np.int32).max) + + # Shape 32bit + A32 = dia_array((1, imax)) + # offsets big type, small values + B32 = A32.copy() + B32.offsets = B32.offsets.astype(np.int64) + + # Shape 64bit + # offsets big type, small values + A64 = dia_array((1, imax + 2)) + # offsets small type, small values + B64 = A64.copy() + B64.offsets = B64.offsets.astype(np.int32) + # offsets big type, big values + C64 = A64.copy() + C64.offsets = np.array([imax + 1]) + C64.data = np.array([2.2]) + + assert A32.offsets.dtype == np.int32 + assert B32.offsets.dtype == np.int64 + assert A64.offsets.dtype == np.int64 + assert B64.offsets.dtype == np.int32 + assert C64.offsets.dtype == np.int64 + + for A in [A32, B32, A64, B64]: + offsets = sputils.safely_cast_index_arrays(A, np.int32) + assert offsets.dtype == np.int32 + offsets = sputils.safely_cast_index_arrays(A, np.int64) + assert offsets.dtype == np.int64 + + offsets = sputils.safely_cast_index_arrays(A, A.offsets.dtype) + assert offsets is A.offsets + + with assert_raises(ValueError): + sputils.safely_cast_index_arrays(C64, np.int32) + offsets = sputils.safely_cast_index_arrays(C64, np.int64) + assert offsets is C64.offsets + + def test_get_index_dtype(self): + imax = np.int64(np.iinfo(np.int32).max) + too_big = imax + 1 + + # Check that uint32's with no values too large doesn't return + # int64 + a1 = np.ones(90, dtype='uint32') + a2 = np.ones(90, dtype='uint32') + assert_equal( + np.dtype(sputils.get_index_dtype((a1, a2), check_contents=True)), + np.dtype('int32') + ) + + # Check that if we can not convert but all values are less than or + # equal to max that we can just convert to int32 + a1[-1] = imax + assert_equal( + np.dtype(sputils.get_index_dtype((a1, a2), check_contents=True)), + np.dtype('int32') + ) + + # Check that if it can not convert directly and the contents are + # too large that we return int64 + a1[-1] = too_big + assert_equal( + np.dtype(sputils.get_index_dtype((a1, a2), check_contents=True)), + np.dtype('int64') + ) + + # test that if can not convert and didn't specify to check_contents + # we return int64 + a1 = np.ones(89, dtype='uint32') + a2 = np.ones(89, dtype='uint32') + assert_equal( + np.dtype(sputils.get_index_dtype((a1, a2))), + np.dtype('int64') + ) + + # Check that even if we have arrays that can be converted directly + # that if we specify a maxval directly it takes precedence + a1 = np.ones(12, dtype='uint32') + a2 = np.ones(12, dtype='uint32') + assert_equal( + np.dtype(sputils.get_index_dtype( + (a1, a2), maxval=too_big, check_contents=True + )), + np.dtype('int64') + ) + + # Check that an array with a too max size and maxval set + # still returns int64 + a1[-1] = too_big + assert_equal( + np.dtype(sputils.get_index_dtype((a1, a2), maxval=too_big)), + np.dtype('int64') + ) + + # tests public broadcast_shapes largely from + # numpy/numpy/lib/tests/test_stride_tricks.py + # first 3 cause np.broadcast to raise index too large, but not sputils + @pytest.mark.parametrize("input_shapes,target_shape", [ + [((6, 5, 1, 4, 1, 1), (1, 2**32), (2**32, 1)), (6, 5, 1, 4, 2**32, 2**32)], + [((6, 5, 1, 4, 1, 1), (1, 2**32)), (6, 5, 1, 4, 1, 2**32)], + [((1, 2**32), (2**32, 1)), (2**32, 2**32)], + [[2, 2, 2], (2,)], + [[], ()], + [[()], ()], + [[(7,)], (7,)], + [[(1, 2), (2,)], (1, 2)], + [[(2,), (1, 2)], (1, 2)], + [[(1, 1)], (1, 1)], + [[(1, 1), (3, 4)], (3, 4)], + [[(6, 7), (5, 6, 1), (7,), (5, 1, 7)], (5, 6, 7)], + [[(5, 6, 1)], (5, 6, 1)], + [[(1, 3), (3, 1)], (3, 3)], + [[(1, 0), (0, 0)], (0, 0)], + [[(0, 1), (0, 0)], (0, 0)], + [[(1, 0), (0, 1)], (0, 0)], + [[(1, 1), (0, 0)], (0, 0)], + [[(1, 1), (1, 0)], (1, 0)], + [[(1, 1), (0, 1)], (0, 1)], + [[(), (0,)], (0,)], + [[(0,), (0, 0)], (0, 0)], + [[(0,), (0, 1)], (0, 0)], + [[(1,), (0, 0)], (0, 0)], + [[(), (0, 0)], (0, 0)], + [[(1, 1), (0,)], (1, 0)], + [[(1,), (0, 1)], (0, 1)], + [[(1,), (1, 0)], (1, 0)], + [[(), (1, 0)], (1, 0)], + [[(), (0, 1)], (0, 1)], + [[(1,), (3,)], (3,)], + [[2, (3, 2)], (3, 2)], + [[(1, 2)] * 32, (1, 2)], + [[(1, 2)] * 100, (1, 2)], + [[(2,)] * 32, (2,)], + ]) + def test_broadcast_shapes_successes(self, input_shapes, target_shape): + assert_equal(sputils.broadcast_shapes(*input_shapes), target_shape) + + # tests public broadcast_shapes failures + @pytest.mark.parametrize("input_shapes", [ + [(3,), (4,)], + [(2, 3), (2,)], + [2, (2, 3)], + [(3,), (3,), (4,)], + [(2, 5), (3, 5)], + [(2, 4), (2, 5)], + [(1, 3, 4), (2, 3, 3)], + [(1, 2), (3, 1), (3, 2), (10, 5)], + [(2,)] * 32 + [(3,)] * 32, + ]) + def test_broadcast_shapes_failures(self, input_shapes): + with assert_raises(ValueError, match="cannot be broadcast"): + sputils.broadcast_shapes(*input_shapes) + + def test_check_shape_overflow(self): + new_shape = sputils.check_shape([(10, -1)], (65535, 131070)) + assert_equal(new_shape, (10, 858967245)) + + def test_matrix(self): + a = [[1, 2, 3]] + b = np.array(a) + + assert isinstance(sputils.matrix(a), np.matrix) + assert isinstance(sputils.matrix(b), np.matrix) + + c = sputils.matrix(b) + c[:, :] = 123 + assert_equal(b, a) + + c = sputils.matrix(b, copy=False) + c[:, :] = 123 + assert_equal(b, [[123, 123, 123]]) + + def test_asmatrix(self): + a = [[1, 2, 3]] + b = np.array(a) + + assert isinstance(sputils.asmatrix(a), np.matrix) + assert isinstance(sputils.asmatrix(b), np.matrix) + + c = sputils.asmatrix(b) + c[:, :] = 123 + assert_equal(b, [[123, 123, 123]]) diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/__init__.py b/venv/lib/python3.12/site-packages/scipy/spatial/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..d92f89e29c0406c6baf08d192ac0d9e49538ed14 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/__init__.py @@ -0,0 +1,130 @@ +""" +============================================================= +Spatial algorithms and data structures (:mod:`scipy.spatial`) +============================================================= + +.. currentmodule:: scipy.spatial + +.. toctree:: + :hidden: + + spatial.distance + spatial.transform + +Spatial transformations +======================= + +These are contained in the `scipy.spatial.transform` submodule. + +Nearest-neighbor queries +======================== +.. autosummary:: + :toctree: generated/ + + KDTree -- class for efficient nearest-neighbor queries + cKDTree -- class for efficient nearest-neighbor queries (faster implementation) + Rectangle + +Distance metrics +================ + +Distance metrics are contained in the :mod:`scipy.spatial.distance` submodule. + +Delaunay triangulation, convex hulls, and Voronoi diagrams +========================================================== + +.. autosummary:: + :toctree: generated/ + + Delaunay -- compute Delaunay triangulation of input points + ConvexHull -- compute a convex hull for input points + Voronoi -- compute a Voronoi diagram hull from input points + SphericalVoronoi -- compute a Voronoi diagram from input points on the surface of a sphere + HalfspaceIntersection -- compute the intersection points of input halfspaces + +Plotting helpers +================ + +.. autosummary:: + :toctree: generated/ + + delaunay_plot_2d -- plot 2-D triangulation + convex_hull_plot_2d -- plot 2-D convex hull + voronoi_plot_2d -- plot 2-D Voronoi diagram + +.. seealso:: :ref:`Tutorial ` + + +Simplex representation +====================== +The simplices (triangles, tetrahedra, etc.) appearing in the Delaunay +tessellation (N-D simplices), convex hull facets, and Voronoi ridges +(N-1-D simplices) are represented in the following scheme:: + + tess = Delaunay(points) + hull = ConvexHull(points) + voro = Voronoi(points) + + # coordinates of the jth vertex of the ith simplex + tess.points[tess.simplices[i, j], :] # tessellation element + hull.points[hull.simplices[i, j], :] # convex hull facet + voro.vertices[voro.ridge_vertices[i, j], :] # ridge between Voronoi cells + +For Delaunay triangulations and convex hulls, the neighborhood +structure of the simplices satisfies the condition: +``tess.neighbors[i,j]`` is the neighboring simplex of the ith +simplex, opposite to the ``j``-vertex. It is -1 in case of no neighbor. + +Convex hull facets also define a hyperplane equation:: + + (hull.equations[i,:-1] * coord).sum() + hull.equations[i,-1] == 0 + +Similar hyperplane equations for the Delaunay triangulation correspond +to the convex hull facets on the corresponding N+1-D +paraboloid. + +The Delaunay triangulation objects offer a method for locating the +simplex containing a given point, and barycentric coordinate +computations. + +Miscellaneous Functions +======================= + +.. autosummary:: + :toctree: generated/ + + tsearch + distance_matrix + minkowski_distance + minkowski_distance_p + procrustes + geometric_slerp + +Warnings / Errors used in :mod:`scipy.spatial` +============================================== +.. autosummary:: + :toctree: generated/ + + QhullError +""" # noqa: E501 + +from ._kdtree import * +from ._ckdtree import * # type: ignore[import-not-found] +from ._qhull import * +from ._spherical_voronoi import SphericalVoronoi +from ._plotutils import * +from ._procrustes import procrustes +from ._geometric_slerp import geometric_slerp + +# Deprecated namespaces, to be removed in v2.0.0 +from . import ckdtree, kdtree, qhull + +__all__ = [s for s in dir() if not s.startswith('_')] + +from . import distance, transform + +__all__ += ['distance', 'transform'] + +from scipy._lib._testutils import PytestTester +test = PytestTester(__name__) +del PytestTester diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/spatial/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..870575d3ceb4e006ac51e26644ce42ec870b32d7 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/spatial/__pycache__/__init__.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/__pycache__/_geometric_slerp.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/spatial/__pycache__/_geometric_slerp.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..75b474c6b49f21b62bcd6eeca646173f56ed780f Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/spatial/__pycache__/_geometric_slerp.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/__pycache__/_kdtree.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/spatial/__pycache__/_kdtree.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..850828aceebac975ffd781cf3458ea6aabe0d598 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/spatial/__pycache__/_kdtree.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/__pycache__/_plotutils.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/spatial/__pycache__/_plotutils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d05c981739a1cb8fb7f3a1071c33e94048039cee Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/spatial/__pycache__/_plotutils.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/__pycache__/_procrustes.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/spatial/__pycache__/_procrustes.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e542bf07e9bb6f70ca5b6abe863cf20fb82e3997 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/spatial/__pycache__/_procrustes.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/__pycache__/_spherical_voronoi.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/spatial/__pycache__/_spherical_voronoi.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1ef03d6f406601b416238ac128b2965d947f0c5a Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/spatial/__pycache__/_spherical_voronoi.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/__pycache__/ckdtree.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/spatial/__pycache__/ckdtree.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..95300243b419ec61464ad1885214c56bb8fcc173 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/spatial/__pycache__/ckdtree.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/__pycache__/distance.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/spatial/__pycache__/distance.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..af833e674dd79a8f7f4020189e7dc8cf37291bd9 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/__pycache__/distance.cpython-312.pyc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9303051209417bf7165f42e7aac8ba2e3257b6e81d802d69171f95f61e7e20e4 +size 100547 diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/__pycache__/kdtree.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/spatial/__pycache__/kdtree.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b6a8f63489e4a7c9bd0a988b7e8773c947d8e2b7 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/spatial/__pycache__/kdtree.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/__pycache__/qhull.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/spatial/__pycache__/qhull.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..69b1eab87c4094e25c1b9d75c10587ae86fb09ba Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/spatial/__pycache__/qhull.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/_ckdtree.cpython-312-x86_64-linux-gnu.so b/venv/lib/python3.12/site-packages/scipy/spatial/_ckdtree.cpython-312-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..f19391c96eb43d8f43a91143c442633a61f369fe --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/_ckdtree.cpython-312-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d2b0626f25c41939ecd57a8f9f99b4ed7f2f074f39e9af41f63c39946244099 +size 777720 diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/_distance_pybind.cpython-312-x86_64-linux-gnu.so b/venv/lib/python3.12/site-packages/scipy/spatial/_distance_pybind.cpython-312-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..b889e120da1fa96f44d9cfc0c2c1761bbd708179 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/_distance_pybind.cpython-312-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5ef008015ebba788b98c647047afa9a46b0cccc4340c8dc4a880e2587e4b5bc9 +size 616280 diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/_distance_wrap.cpython-312-x86_64-linux-gnu.so b/venv/lib/python3.12/site-packages/scipy/spatial/_distance_wrap.cpython-312-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..676cdc8f64a4e3742d34e0ae26d6bf047a65cf65 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/_distance_wrap.cpython-312-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4a25a65e63e8fad64206d75d8f085f1f87489c453dc4065ed93457596f12900c +size 120904 diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/_geometric_slerp.py b/venv/lib/python3.12/site-packages/scipy/spatial/_geometric_slerp.py new file mode 100644 index 0000000000000000000000000000000000000000..6629ac70467ebddfb95cf5e720a4c4930788143d --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/_geometric_slerp.py @@ -0,0 +1,256 @@ +__all__ = ['geometric_slerp'] + +import warnings +from typing import TYPE_CHECKING + +import numpy as np +from scipy.spatial.distance import euclidean + +if TYPE_CHECKING: + import numpy.typing as npt + + +def _geometric_slerp(start, end, t): + # create an orthogonal basis using QR decomposition + basis = np.vstack([start, end]) + Q, R = np.linalg.qr(basis.T) + signs = 2 * (np.diag(R) >= 0) - 1 + Q = Q.T * signs.T[:, np.newaxis] + R = R.T * signs.T[:, np.newaxis] + + # calculate the angle between `start` and `end` + c = np.dot(start, end) + s = np.linalg.det(R) + omega = np.arctan2(s, c) + + # interpolate + start, end = Q + s = np.sin(t * omega) + c = np.cos(t * omega) + return start * c[:, np.newaxis] + end * s[:, np.newaxis] + + +def geometric_slerp( + start: "npt.ArrayLike", + end: "npt.ArrayLike", + t: "npt.ArrayLike", + tol: float = 1e-7, +) -> np.ndarray: + """ + Geometric spherical linear interpolation. + + The interpolation occurs along a unit-radius + great circle arc in arbitrary dimensional space. + + Parameters + ---------- + start : (n_dimensions, ) array-like + Single n-dimensional input coordinate in a 1-D array-like + object. `n` must be greater than 1. + end : (n_dimensions, ) array-like + Single n-dimensional input coordinate in a 1-D array-like + object. `n` must be greater than 1. + t : float or (n_points,) 1D array-like + A float or 1D array-like of doubles representing interpolation + parameters. A common approach is to generate the array + with ``np.linspace(0, 1, n_pts)`` for linearly spaced points. + Ascending, descending, and scrambled orders are permitted. + + .. versionchanged:: 1.17.0 + Extrapolation is permitted, allowing values below `0` + and above `1`. + tol : float + The absolute tolerance for determining if the start and end + coordinates are antipodes. + + Returns + ------- + result : (t.size, D) + An array of doubles containing the interpolated + spherical path and including start and + end when 0 and 1 t are used. The + interpolated values should correspond to the + same sort order provided in the t array. The result + may be 1-dimensional if ``t`` is a float. + + Raises + ------ + ValueError + If ``start`` or ``end`` are not on the + unit n-sphere, or for a variety of degenerate conditions. + + See Also + -------- + scipy.spatial.transform.Slerp : 3-D Slerp that works with quaternions + + Notes + ----- + The implementation is based on the mathematical formula provided in [1]_, + and the first known presentation of this algorithm, derived from study of + 4-D geometry, is credited to Glenn Davis in a footnote of the original + quaternion Slerp publication by Ken Shoemake [2]_. + + .. versionadded:: 1.5.0 + + References + ---------- + .. [1] https://en.wikipedia.org/wiki/Slerp#Geometric_Slerp + .. [2] Ken Shoemake (1985) Animating rotation with quaternion curves. + ACM SIGGRAPH Computer Graphics, 19(3): 245-254. + + Examples + -------- + Interpolate four linearly-spaced values on the circumference of + a circle spanning 90 degrees: + + >>> import numpy as np + >>> from scipy.spatial import geometric_slerp + >>> import matplotlib.pyplot as plt + >>> fig = plt.figure() + >>> ax = fig.add_subplot(111) + >>> start = np.array([1, 0]) + >>> end = np.array([0, 1]) + >>> t_vals = np.linspace(0, 1, 4) + >>> result = geometric_slerp(start, + ... end, + ... t_vals) + + The interpolated results should be at 30 degree intervals + recognizable on the unit circle: + + >>> ax.scatter(result[...,0], result[...,1], c='k') + >>> circle = plt.Circle((0, 0), 1, color='grey') + >>> ax.add_artist(circle) + >>> ax.set_aspect('equal') + >>> plt.show() + + Attempting to interpolate between antipodes on a circle is + ambiguous because there are two possible paths, and on a + sphere there are infinite possible paths on the geodesic surface. + Nonetheless, one of the ambiguous paths is returned along + with a warning: + + >>> import warnings + >>> opposite_pole = np.array([-1, 0]) + >>> with warnings.catch_warnings(): + ... warnings.simplefilter("ignore", UserWarning) + ... geometric_slerp(start, + ... opposite_pole, + ... t_vals) + array([[ 1.00000000e+00, 0.00000000e+00], + [ 5.00000000e-01, 8.66025404e-01], + [-5.00000000e-01, 8.66025404e-01], + [-1.00000000e+00, 1.22464680e-16]]) + + Extend the original example to a sphere and plot interpolation + points in 3D: + + >>> from mpl_toolkits.mplot3d import proj3d + >>> fig = plt.figure() + >>> ax = fig.add_subplot(111, projection='3d') + + Plot the unit sphere for reference (optional): + + >>> u = np.linspace(0, 2 * np.pi, 100) + >>> v = np.linspace(0, np.pi, 100) + >>> x = np.outer(np.cos(u), np.sin(v)) + >>> y = np.outer(np.sin(u), np.sin(v)) + >>> z = np.outer(np.ones(np.size(u)), np.cos(v)) + >>> ax.plot_surface(x, y, z, color='y', alpha=0.1) + + Interpolating over a larger number of points + may provide the appearance of a smooth curve on + the surface of the sphere, which is also useful + for discretized integration calculations on a + sphere surface: + + >>> start = np.array([1, 0, 0]) + >>> end = np.array([0, 0, 1]) + >>> t_vals = np.linspace(0, 1, 200) + >>> result = geometric_slerp(start, + ... end, + ... t_vals) + >>> ax.plot(result[...,0], + ... result[...,1], + ... result[...,2], + ... c='k') + >>> plt.show() + + It is also possible to perform extrapolations outside + the interpolation interval by using interpolation parameter + values below 0 or above 1. For example, the above example + may be adjusted to extrapolate to an antipodal position: + + >>> fig = plt.figure() + >>> ax = fig.add_subplot(111, projection='3d') + >>> ax.plot_surface(x, y, z, color='y', alpha=0.1) + >>> start = np.array([1, 0, 0]) + >>> end = np.array([0, 0, 1]) + >>> t_vals = np.linspace(0, 2, 400) + >>> result = geometric_slerp(start, + ... end, + ... t_vals) + >>> ax.plot(result[...,0], result[...,1], result[...,2], c='k') + >>> plt.show() + """ + + start = np.asarray(start, dtype=np.float64) + end = np.asarray(end, dtype=np.float64) + t = np.asarray(t) + + if t.ndim > 1: + raise ValueError("The interpolation parameter " + "value must be one dimensional.") + + if start.ndim != 1 or end.ndim != 1: + raise ValueError("Start and end coordinates " + "must be one-dimensional") + + if start.size != end.size: + raise ValueError("The dimensions of start and " + "end must match (have same size)") + + if start.size < 2 or end.size < 2: + raise ValueError("The start and end coordinates must " + "both be in at least two-dimensional " + "space") + + if np.array_equal(start, end): + return np.linspace(start, start, t.size) + + # for points that violate equation for n-sphere + for coord in [start, end]: + if not np.allclose(np.linalg.norm(coord), 1.0, + rtol=1e-9, + atol=0): + raise ValueError("start and end are not" + " on a unit n-sphere") + + if not isinstance(tol, float): + raise ValueError("tol must be a float") + else: + tol = np.fabs(tol) + + coord_dist = euclidean(start, end) + + # diameter of 2 within tolerance means antipodes, which is a problem + # for all unit n-spheres (even the 0-sphere would have an ambiguous path) + if np.allclose(coord_dist, 2.0, rtol=0, atol=tol): + warnings.warn("start and end are antipodes " + "using the specified tolerance; " + "this may cause ambiguous slerp paths", + stacklevel=2) + + t = np.asarray(t, dtype=np.float64) + + if t.size == 0: + return np.empty((0, start.size)) + + if t.ndim == 0: + return _geometric_slerp(start, + end, + np.atleast_1d(t)).ravel() + else: + return _geometric_slerp(start, + end, + t) diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/_hausdorff.cpython-312-x86_64-linux-gnu.so b/venv/lib/python3.12/site-packages/scipy/spatial/_hausdorff.cpython-312-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..cb24aceeb1ed2b074755ff7f4ac0c966630a4eab Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/spatial/_hausdorff.cpython-312-x86_64-linux-gnu.so differ diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/_kdtree.py b/venv/lib/python3.12/site-packages/scipy/spatial/_kdtree.py new file mode 100644 index 0000000000000000000000000000000000000000..73e46312ab6d3ffbf251575bbb0d318dcb825dad --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/_kdtree.py @@ -0,0 +1,1021 @@ +# Copyright Anne M. Archibald 2008 +# Released under the scipy license +import warnings +import os + +import numpy as np +from ._ckdtree import cKDTree, cKDTreeNode # type: ignore[import-not-found] +from .distance import minkowski +from scipy._lib._array_api import xp_capabilities + +__all__ = ['minkowski_distance_p', 'minkowski_distance', + 'distance_matrix', + 'Rectangle', 'KDTree'] + + +@xp_capabilities(out_of_scope=True) +def minkowski_distance_p(x, y, p=2.0): + """Compute the pth power of the L**p distance between two arrays. + + For efficiency, this function computes the L**p distance but does + not extract the pth root. If `p` is 1 or infinity, this is equal to + the actual L**p distance. + + The last dimensions of `x` and `y` must be the same length. Any + other dimensions must be compatible for broadcasting. + + .. deprecated:: 1.18.0 + This function is deprecated in favor of `scipy.spatial.distance.minkowski` + and will be removed in SciPy 1.20.0. + + Parameters + ---------- + x : (..., K) array_like + Input array. + y : (..., K) array_like + Input array. + p : float, 1 <= p <= infinity + Which Minkowski p-norm to use. + + Returns + ------- + dist : ndarray + pth power of the distance between the input arrays. + + Examples + -------- + >>> from scipy.spatial import minkowski_distance_p + >>> minkowski_distance_p([[0, 0], [0, 0]], [[1, 1], [0, 1]]) + array([2., 1.]) + + """ + msg = ("`minkowski_distance_p` is deprecated in favor of " + "`scipy.spatial.distance.minkowski` as of SciPy 1.18.0 and will be removed " + "in SciPy 1.20.0.") + warnings.warn(msg, DeprecationWarning, + skip_file_prefixes=(os.path.dirname(__file__),)) + x = np.asarray(x) + y = np.asarray(y) + + # Find smallest common datatype with float64 (return type of this + # function) - addresses #10262. + # Don't just cast to float64 for complex input case. + common_datatype = np.promote_types(np.promote_types(x.dtype, y.dtype), + 'float64') + + # Make sure x and y are NumPy arrays of correct datatype. + x = x.astype(common_datatype) + y = y.astype(common_datatype) + + if p == np.inf: + return np.amax(np.abs(y-x), axis=-1) + elif p == 1: + return np.sum(np.abs(y-x), axis=-1) + else: + return np.sum(np.abs(y-x)**p, axis=-1) + + +@xp_capabilities(out_of_scope=True) +def minkowski_distance(x, y, p=2.0): + """Compute the L**p distance between two arrays. + + The last dimensions of `x` and `y` must be the same length. Any + other dimensions must be compatible for broadcasting. + + .. deprecated:: 1.18.0 + This function is deprecated in favor of `scipy.spatial.distance.minkowski` + and will be removed in SciPy 1.20.0. + + Parameters + ---------- + x : (..., K) array_like + Input array. + y : (..., K) array_like + Input array. + p : float, 1 <= p <= infinity + Which Minkowski p-norm to use. + + Returns + ------- + dist : ndarray + Distance between the input arrays. + + Examples + -------- + >>> from scipy.spatial import minkowski_distance + >>> minkowski_distance([[0, 0], [0, 0]], [[1, 1], [0, 1]]) + array([ 1.41421356, 1. ]) + + """ + msg = ("`minkowski_distance` is deprecated in favor of " + "`scipy.spatial.distance.minkowski` as of SciPy 1.18.0 and will be removed " + "in SciPy 1.20.0.") + warnings.warn(msg, DeprecationWarning, + skip_file_prefixes=(os.path.dirname(__file__),)) + x = np.asarray(x) + y = np.asarray(y) + if p == np.inf or p == 1: + return minkowski_distance_p(x, y, p) + else: + return minkowski_distance_p(x, y, p)**(1./p) + + +class Rectangle: + """Hyperrectangle class. + + Represents a Cartesian product of intervals. + + Parameters + ---------- + maxes : array_like of shape (m,) + Upper bounds of the hyperrectangle along each dimension. + mins : array_like of shape (m,) + Lower bounds of the hyperrectangle along each dimension. + + Attributes + ---------- + maxes : ndarray of shape (m,) + Upper bounds for each dimension. + mins : ndarray of shape (m,) + Lower bounds for each dimension. + m : int + Dimensionality of the hyperrectangle. + + Notes + ----- + If any element of `maxes` is smaller than the corresponding element in `mins`, the + values are swapped automatically. + """ + def __init__(self, maxes, mins): + """Construct a hyperrectangle.""" + self.maxes = np.maximum(maxes,mins).astype(float) + self.mins = np.minimum(maxes,mins).astype(float) + self.m, = self.maxes.shape + + def __repr__(self): + return f"" + + def volume(self): + """Compute the total volume of the hyperrectangle. + + Returns + ------- + vol : float + Volume of the hyperrectangle. + """ + return np.prod(self.maxes-self.mins) + + def split(self, d, split): + """Produce two hyperrectangles by splitting. + + In general, if you need to compute maximum and minimum + distances to the children, it can be done more efficiently + by updating the maximum and minimum distances to the parent. + + Parameters + ---------- + d : int + Axis to split hyperrectangle along. + split : float + Position along axis `d` to split at. + + Returns + ------- + less : Rectangle + Rectangle with `d`-th axis less than `split`. + greater : Rectangle + Rectangle with `d`-th axis greater than `split`. + """ + mid = np.copy(self.maxes) + mid[d] = split + less = Rectangle(self.mins, mid) + mid = np.copy(self.mins) + mid[d] = split + greater = Rectangle(mid, self.maxes) + return less, greater + + def min_distance_point(self, x, p=2.0): + """ + Return the minimum distance between input and points in the + hyperrectangle. + + Parameters + ---------- + x : array_like + Input. + p : float, optional + Input. + + Returns + ------- + dist : ndarray + Minimum distance. + """ + delta = np.maximum(0, np.maximum(self.mins - x, x - self.maxes)) + return minkowski(np.zeros_like(delta), delta, p) + + def max_distance_point(self, x, p=2.0): + """ + Return the maximum distance between input and points in the hyperrectangle. + + Parameters + ---------- + x : array_like + Input array. + p : float, optional + Input. + + Returns + ------- + dist : ndarray + Maximum distance. + """ + delta = np.maximum(self.maxes - x, x - self.mins) + return minkowski(np.zeros_like(delta), delta, p) + + def min_distance_rectangle(self, other, p=2.0): + """ + Compute the minimum distance between points in the two hyperrectangles. + + Parameters + ---------- + other : hyperrectangle + Input. + p : float + Input. + + Returns + ------- + dist : float + Minimum distance between the two hyperrectangles. + """ + delta = np.maximum(0, np.maximum(self.mins - other.maxes, + other.mins - self.maxes)) + return minkowski(np.zeros_like(delta), delta, p) + + def max_distance_rectangle(self, other, p=2.0): + """ + Compute the maximum distance between points in the two hyperrectangles. + + Parameters + ---------- + other : hyperrectangle + Input. + p : float, optional + Input. + + Returns + ------- + dist : float + Maximum distance between the two hyperrectangles. + """ + delta = np.maximum(self.maxes - other.mins, other.maxes - self.mins) + return minkowski(np.zeros_like(delta), delta, p) + + +class KDTree(cKDTree): + """ + kd-tree for quick nearest-neighbor lookup. + + This class provides an index into a set of k-dimensional points + which can be used to rapidly look up the nearest neighbors of any + point. + + Parameters + ---------- + data : array_like, shape (n,m) + The n data points of dimension m to be indexed. This array is + not copied unless this is necessary to produce a contiguous + array of doubles. The data are also copied if the kd-tree is built + with copy_data=True. Concurrently modifying the data while constructing + a KDTree is not well-defined and may lead to crashes or data + corruption. If no copy happens, modifying the data *after* creating + the KDTree may lead to data corruption or incorrect answers when + searching the tree. + leafsize : positive int, optional + The number of points at which the algorithm switches over to + brute-force. Default: 10. + compact_nodes : bool, optional + If True, the kd-tree is built to shrink the hyperrectangles to + the actual data range. This usually gives a more compact tree that + is robust against degenerated input data and gives faster queries + at the expense of longer build time. Default: True. + copy_data : bool, optional + If True the data is always copied to protect the kd-tree against + data corruption. Default: False. + balanced_tree : bool, optional + If True, the median is used to split the hyperrectangles instead of + the midpoint. This usually gives a more compact tree and + faster queries at the expense of longer build time. Default: True. + boxsize : array_like or scalar, optional + Apply an m-d toroidal topology to the KDTree. The topology is generated + by :math:`x_i + n_i L_i` where :math:`n_i` are integers and :math:`L_i` + is the boxsize along i-th dimension. The input data shall be wrapped + into :math:`[0, L_i)`. A ValueError is raised if any of the data is + outside of this bound. + + Attributes + ---------- + data : ndarray, shape (n,m) + The n data points of dimension m to be indexed. This array is + not copied unless this is necessary to produce a contiguous + array of doubles. The data are also copied if the kd-tree is built + with ``copy_data=True``. + leafsize : positive int + The number of points at which the algorithm switches over to + brute-force. + m : int + The dimension of a single data-point. + n : int + The number of data points. + maxes : ndarray, shape (m,) + The maximum value in each dimension of the n data points. + mins : ndarray, shape (m,) + The minimum value in each dimension of the n data points. + size : int + The number of nodes in the tree. + boxsize : None or ndarray, shape (m.) + If boxsize is passed to the initializer, this will be set to the bounds + of the periodic box. None if no boxsize is passed. + + Notes + ----- + The algorithm used is described in [1]_. + The general idea is that the kd-tree is a binary tree, each of whose + nodes represents an axis-aligned hyperrectangle. Each node specifies + an axis and splits the set of points based on whether their coordinate + along that axis is greater than or less than a particular value. + + During construction, the axis and splitting point are chosen by the + "sliding midpoint" rule, which ensures that the cells do not all + become long and thin. + + The tree can be queried for the r closest neighbors of any given point + (optionally returning only those within some maximum distance of the + point). It can also be queried, with a substantial gain in efficiency, + for the r approximate closest neighbors. + + For large dimensions (20 is already large) do not expect this to run + significantly faster than brute force. High-dimensional nearest-neighbor + queries are a substantial open problem in computer science. + + References + ---------- + .. [1] S. Maneewongvatana and D.E. Mount, "Analysis of approximate + nearest neighbor searching with clustered point sets," + Arxiv e-print, 1999, https://arxiv.org/pdf/cs.CG/9901013 + """ # numpydoc ignore=SS02 + + class node: + @staticmethod + def _create(ckdtree_node=None): + """Create either an inner or leaf node, wrapping a cKDTreeNode instance""" + if ckdtree_node is None: + return KDTree.node(ckdtree_node) + elif ckdtree_node.split_dim == -1: + return KDTree.leafnode(ckdtree_node) + else: + return KDTree.innernode(ckdtree_node) + + def __init__(self, ckdtree_node=None): + if ckdtree_node is None: + ckdtree_node = cKDTreeNode() + self._node = ckdtree_node + + def __lt__(self, other): + return id(self) < id(other) + + def __gt__(self, other): + return id(self) > id(other) + + def __le__(self, other): + return id(self) <= id(other) + + def __ge__(self, other): + return id(self) >= id(other) + + def __eq__(self, other): + return id(self) == id(other) + + class leafnode(node): + @property + def idx(self): + return self._node.indices + + @property + def children(self): + return self._node.children + + class innernode(node): + def __init__(self, ckdtreenode): + assert isinstance(ckdtreenode, cKDTreeNode) + super().__init__(ckdtreenode) + self.less = KDTree.node._create(ckdtreenode.lesser) + self.greater = KDTree.node._create(ckdtreenode.greater) + + @property + def split_dim(self): + return self._node.split_dim + + @property + def split(self): + return self._node.split + + @property + def children(self): + return self._node.children + + @property + def tree(self): + if not hasattr(self, "_tree"): + self._tree = KDTree.node._create(super().tree) + + return self._tree + + def __init__(self, data, leafsize=10, compact_nodes=True, copy_data=False, + balanced_tree=True, boxsize=None): + data = np.asarray(data) + if data.dtype.kind == 'c': + raise TypeError("KDTree does not work with complex data") + + # Note KDTree has different default leafsize from cKDTree + super().__init__(data, leafsize, compact_nodes, copy_data, + balanced_tree, boxsize) + + def query(self, x, k=1, eps=0.0, p=2.0, distance_upper_bound=np.inf, + workers=1): + r"""Query the kd-tree for nearest neighbors. + + Parameters + ---------- + x : array_like, last dimension self.m + An array of points to query. + k : int or Sequence[int], optional + Either the number of nearest neighbors to return, or a list of the + k-th nearest neighbors to return, starting from 1. + eps : nonnegative float, optional + Return approximate nearest neighbors; the kth returned value + is guaranteed to be no further than (1+eps) times the + distance to the real kth nearest neighbor. + p : float, 1<=p<=infinity, optional + Which Minkowski p-norm to use. + 1 is the sum-of-absolute-values distance ("Manhattan" distance). + 2 is the usual Euclidean distance. + infinity is the maximum-coordinate-difference distance. + A large, finite p may cause a ValueError if overflow can occur. + distance_upper_bound : nonnegative float, optional + Return only neighbors within this distance. This is used to prune + tree searches, so if you are doing a series of nearest-neighbor + queries, it may help to supply the distance to the nearest neighbor + of the most recent point. + workers : int, optional + Number of workers to use for parallel processing. If -1 is given + all CPU threads are used. Default: 1. + + .. versionadded:: 1.6.0 + + Returns + ------- + d : float or array of floats + The distances to the nearest neighbors. + If ``x`` has shape ``tuple+(self.m,)``, then ``d`` has shape + ``tuple+(k,)``. + When k == 1, the last dimension of the output is squeezed. + Missing neighbors are indicated with infinite distances. + Hits are sorted by distance (nearest first). + + .. versionchanged:: 1.9.0 + Previously if ``k=None``, then `d` was an object array of + shape ``tuple``, containing lists of distances. This behavior + has been removed, use `query_ball_point` instead. + + i : int or array of integers + The index of each neighbor in ``self.data``. + ``i`` is the same shape as d. + Missing neighbors are indicated with ``self.n``. + + Examples + -------- + + >>> import numpy as np + >>> from scipy.spatial import KDTree + >>> x, y = np.mgrid[0:5, 2:8] + >>> tree = KDTree(np.c_[x.ravel(), y.ravel()]) + + To query the nearest neighbours and return squeezed result, use + + >>> dd, ii = tree.query([[0, 0], [2.2, 2.9]], k=1) + >>> print(dd, ii, sep='\n') + [2. 0.2236068] + [ 0 13] + + To query the nearest neighbours and return unsqueezed result, use + + >>> dd, ii = tree.query([[0, 0], [2.2, 2.9]], k=[1]) + >>> print(dd, ii, sep='\n') + [[2. ] + [0.2236068]] + [[ 0] + [13]] + + To query the second nearest neighbours and return unsqueezed result, + use + + >>> dd, ii = tree.query([[0, 0], [2.2, 2.9]], k=[2]) + >>> print(dd, ii, sep='\n') + [[2.23606798] + [0.80622577]] + [[ 6] + [19]] + + To query the first and second nearest neighbours, use + + >>> dd, ii = tree.query([[0, 0], [2.2, 2.9]], k=2) + >>> print(dd, ii, sep='\n') + [[2. 2.23606798] + [0.2236068 0.80622577]] + [[ 0 6] + [13 19]] + + or, be more specific + + >>> dd, ii = tree.query([[0, 0], [2.2, 2.9]], k=[1, 2]) + >>> print(dd, ii, sep='\n') + [[2. 2.23606798] + [0.2236068 0.80622577]] + [[ 0 6] + [13 19]] + + """ + x = np.asarray(x) + if x.dtype.kind == 'c': + raise TypeError("KDTree does not work with complex data") + + if k is None: + raise ValueError("k must be an integer or a sequence of integers") + + d, i = super().query(x, k, eps, p, distance_upper_bound, workers) + if isinstance(i, int): + i = np.intp(i) + return d, i + + def query_ball_point(self, x, r, p=2.0, eps=0.0, workers=1, + return_sorted=None, return_length=False): + """Find all points within distance r of point(s) x. + + Parameters + ---------- + x : array_like, shape tuple + (self.m,) + The point or points to search for neighbors of. + r : array_like, float + The radius of points to return, must broadcast to the length of x. + p : float, optional + Which Minkowski p-norm to use. Should be in the range [1, inf]. + A finite large p may cause a ValueError if overflow can occur. + eps : nonnegative float, optional + Approximate search. Branches of the tree are not explored if their + nearest points are further than ``r / (1 + eps)``, and branches are + added in bulk if their furthest points are nearer than + ``r * (1 + eps)``. + workers : int, optional + Number of jobs to schedule for parallel processing. If -1 is given + all processors are used. Default: 1. + + .. versionadded:: 1.6.0 + return_sorted : bool, optional + Sorts returned indices if True and does not sort them if False. If + None, does not sort single point queries, but does sort + multi-point queries which was the behavior before this option + was added. + + .. versionadded:: 1.6.0 + return_length : bool, optional + Return the number of points inside the radius instead of a list + of the indices. + + .. versionadded:: 1.6.0 + + Returns + ------- + results : list or array of lists + If `x` is a single point, returns a list of the indices of the + neighbors of `x`. If `x` is an array of points, returns an object + array of shape tuple containing lists of neighbors. + + Notes + ----- + If you have many points whose neighbors you want to find, you may save + substantial amounts of time by putting them in a KDTree and using + query_ball_tree. + + Examples + -------- + >>> import numpy as np + >>> from scipy import spatial + >>> x, y = np.mgrid[0:5, 0:5] + >>> points = np.c_[x.ravel(), y.ravel()] + >>> tree = spatial.KDTree(points) + >>> sorted(tree.query_ball_point([2, 0], 1)) + [5, 10, 11, 15] + + Query multiple points and plot the results: + + >>> import matplotlib.pyplot as plt + >>> points = np.asarray(points) + >>> plt.plot(points[:,0], points[:,1], '.') + >>> for results in tree.query_ball_point(([2, 0], [3, 3]), 1): + ... nearby_points = points[results] + ... plt.plot(nearby_points[:,0], nearby_points[:,1], 'o') + >>> plt.margins(0.1, 0.1) + >>> plt.show() + + """ + x = np.asarray(x) + if x.dtype.kind == 'c': + raise TypeError("KDTree does not work with complex data") + return super().query_ball_point( + x, r, p, eps, workers, return_sorted, return_length) + + def query_ball_tree(self, other, r, p=2.0, eps=0.0): + """ + Find all pairs of points between `self` and `other` whose distance is + at most r. + + Parameters + ---------- + other : KDTree instance + The tree containing points to search against. + r : float + The maximum distance, has to be positive. + p : float, optional + Which Minkowski norm to use. `p` has to meet the condition + ``1 <= p <= infinity``. + eps : float, optional + Approximate search. Branches of the tree are not explored + if their nearest points are further than ``r/(1+eps)``, and + branches are added in bulk if their furthest points are nearer + than ``r * (1+eps)``. `eps` has to be non-negative. + + Returns + ------- + results : list of lists + For each element ``self.data[i]`` of this tree, ``results[i]`` is a + list of the indices of its neighbors in ``other.data``. + + Examples + -------- + You can search all pairs of points between two kd-trees within a distance: + + >>> import matplotlib.pyplot as plt + >>> import numpy as np + >>> from scipy.spatial import KDTree + >>> rng = np.random.default_rng() + >>> points1 = rng.random((15, 2)) + >>> points2 = rng.random((15, 2)) + >>> plt.figure(figsize=(6, 6)) + >>> plt.plot(points1[:, 0], points1[:, 1], "xk", markersize=14) + >>> plt.plot(points2[:, 0], points2[:, 1], "og", markersize=14) + >>> kd_tree1 = KDTree(points1) + >>> kd_tree2 = KDTree(points2) + >>> indexes = kd_tree1.query_ball_tree(kd_tree2, r=0.2) + >>> for i in range(len(indexes)): + ... for j in indexes[i]: + ... plt.plot([points1[i, 0], points2[j, 0]], + ... [points1[i, 1], points2[j, 1]], "-r") + >>> plt.show() + + """ + return super().query_ball_tree(other, r, p, eps) + + def query_pairs(self, r, p=2.0, eps=0.0, output_type='set'): + """Find all pairs of points in `self` whose distance is at most r. + + Parameters + ---------- + r : positive float + The maximum distance. + p : float, optional + Which Minkowski norm to use. `p` has to meet the condition + ``1 <= p <= infinity``. + eps : float, optional + Approximate search. Branches of the tree are not explored + if their nearest points are further than ``r/(1+eps)``, and + branches are added in bulk if their furthest points are nearer + than ``r * (1+eps)``. `eps` has to be non-negative. + output_type : str, optional + Choose the output container, 'set' or 'ndarray'. Default: 'set' + + .. versionadded:: 1.6.0 + + Returns + ------- + results : set or ndarray + Set of pairs ``(i,j)``, with ``i < j``, for which the corresponding + positions are close. If output_type is 'ndarray', an ndarray is + returned instead of a set. + + Examples + -------- + You can search all pairs of points in a kd-tree within a distance: + + >>> import matplotlib.pyplot as plt + >>> import numpy as np + >>> from scipy.spatial import KDTree + >>> rng = np.random.default_rng() + >>> points = rng.random((20, 2)) + >>> plt.figure(figsize=(6, 6)) + >>> plt.plot(points[:, 0], points[:, 1], "xk", markersize=14) + >>> kd_tree = KDTree(points) + >>> pairs = kd_tree.query_pairs(r=0.2) + >>> for (i, j) in pairs: + ... plt.plot([points[i, 0], points[j, 0]], + ... [points[i, 1], points[j, 1]], "-r") + >>> plt.show() + + """ + return super().query_pairs(r, p, eps, output_type) + + def count_neighbors(self, other, r, p=2.0, weights=None, cumulative=True): + """Count how many nearby pairs can be formed. + + Count the number of pairs ``(x1,x2)`` can be formed, with ``x1`` drawn + from ``self`` and ``x2`` drawn from ``other``, and where + ``distance(x1, x2, p) <= r``. + + Data points on ``self`` and ``other`` are optionally weighted by the + ``weights`` argument. (See below) + + This is adapted from the "two-point correlation" algorithm described by + Gray and Moore [1]_. See notes for further discussion. + + Parameters + ---------- + other : KDTree + The other tree to draw points from, can be the same tree as self. + r : float or one-dimensional array of floats + The radius to produce a count for. Multiple radii are searched with + a single tree traversal. + If the count is non-cumulative (``cumulative=False``), ``r`` + defines the edges of the bins, and must be non-decreasing. + p : float, optional + 1<=p<=infinity. + Which Minkowski p-norm to use. + Default 2.0. + A finite large p may cause a ValueError if overflow can occur. + weights : tuple, array_like, or None, optional + If None, the pair-counting is unweighted. + If given as a tuple, weights[0] is the weights of points in + ``self``, and weights[1] is the weights of points in ``other``; + either can be None to indicate the points are unweighted. + If given as an array_like, weights is the weights of points in + ``self`` and ``other``. For this to make sense, ``self`` and + ``other`` must be the same tree. If ``self`` and ``other`` are two + different trees, a ``ValueError`` is raised. + Default: None + + .. versionadded:: 1.6.0 + cumulative : bool, optional + Whether the returned counts are cumulative. When cumulative is set + to ``False`` the algorithm is optimized to work with a large number + of bins (>10) specified by ``r``. When ``cumulative`` is set to + True, the algorithm is optimized to work with a small number of + ``r``. Default: True + + .. versionadded:: 1.6.0 + + Returns + ------- + result : scalar or 1-D array + The number of pairs. For unweighted counts, the result is integer. + For weighted counts, the result is float. + If cumulative is False, ``result[i]`` contains the counts with + ``(-inf if i == 0 else r[i-1]) < R <= r[i]`` + + Notes + ----- + Pair-counting is the basic operation used to calculate the two point + correlation functions from a data set composed of position of objects. + + Two point correlation function measures the clustering of objects and + is widely used in cosmology to quantify the large scale structure + in our Universe, but it may be useful for data analysis in other fields + where self-similar assembly of objects also occur. + + The Landy-Szalay estimator for the two point correlation function of + ``D`` measures the clustering signal in ``D``. [2]_ + + For example, given the position of two sets of objects, + + - objects ``D`` (data) contains the clustering signal, and + + - objects ``R`` (random) that contains no signal, + + .. math:: + + \\xi(r) = \\frac{ - 2 f + f^2}{f^2}, + + where the brackets represents counting pairs between two data sets + in a finite bin around ``r`` (distance), corresponding to setting + `cumulative=False`, and ``f = float(len(D)) / float(len(R))`` is the + ratio between number of objects from data and random. + + The algorithm implemented here is loosely based on the dual-tree + algorithm described in [1]_. We switch between two different + pair-cumulation scheme depending on the setting of ``cumulative``. + The computing time of the method we use when for + ``cumulative == False`` does not scale with the total number of bins. + The algorithm for ``cumulative == True`` scales linearly with the + number of bins, though it is slightly faster when only + 1 or 2 bins are used. [5]_. + + As an extension to the naive pair-counting, + weighted pair-counting counts the product of weights instead + of number of pairs. + Weighted pair-counting is used to estimate marked correlation functions + ([3]_, section 2.2), + or to properly calculate the average of data per distance bin + (e.g. [4]_, section 2.1 on redshift). + + .. [1] Gray and Moore, + "N-body problems in statistical learning", + Mining the sky, 2000, + https://arxiv.org/abs/astro-ph/0012333 + + .. [2] Landy and Szalay, + "Bias and variance of angular correlation functions", + The Astrophysical Journal, 1993, + http://adsabs.harvard.edu/abs/1993ApJ...412...64L + + .. [3] Sheth, Connolly and Skibba, + "Marked correlations in galaxy formation models", + Arxiv e-print, 2005, + https://arxiv.org/abs/astro-ph/0511773 + + .. [4] Hawkins, et al., + "The 2dF Galaxy Redshift Survey: correlation functions, + peculiar velocities and the matter density of the Universe", + Monthly Notices of the Royal Astronomical Society, 2002, + http://adsabs.harvard.edu/abs/2003MNRAS.346...78H + + .. [5] https://github.com/scipy/scipy/pull/5647#issuecomment-168474926 + + Examples + -------- + You can count neighbors number between two kd-trees within a distance: + + >>> import numpy as np + >>> from scipy.spatial import KDTree + >>> rng = np.random.default_rng() + >>> points1 = rng.random((5, 2)) + >>> points2 = rng.random((5, 2)) + >>> kd_tree1 = KDTree(points1) + >>> kd_tree2 = KDTree(points2) + >>> kd_tree1.count_neighbors(kd_tree2, 0.2) + 1 + + This number is same as the total pair number calculated by + `query_ball_tree`: + + >>> indexes = kd_tree1.query_ball_tree(kd_tree2, r=0.2) + >>> sum([len(i) for i in indexes]) + 1 + + """ + return super().count_neighbors(other, r, p, weights, cumulative) + + def sparse_distance_matrix( + self, other, max_distance, p=2.0, output_type='dok_matrix'): + """Compute a sparse distance matrix. + + Computes a distance matrix between two KDTrees, leaving as zero + any distance greater than max_distance. + + Parameters + ---------- + other : KDTree + The other `KDTree` to compute distances against. + max_distance : positive float + Maximum distance within which neighbors are returned. Distances above this + value are returned as zero. + p : float, 1<=p<=infinity + Which Minkowski p-norm to use. + A finite large p may cause a ValueError if overflow can occur. + output_type : str, optional + Which container to use for output data. Options: ``'dok_array'``, + ``'coo_array'``, ``'dict'``, or ``'ndarray'``. + Legacy options ``'dok_matrix'`` and ``'coo_matrix'`` are still available. + Default: ``'dok_matrix'``. + + .. warning:: dok_matrix and coo_matrix are being replaced. + + All new code using scipy sparse should use sparse array + types 'dok_array' or 'coo_array'. The default value of + `output_type` will be deprecated at v1.19 and switch from + 'dok_matrix' to 'dok_array' in v1.21. + The values 'dok_matrix' and 'coo_matrix' continue + to work, but will go away eventually. + + .. versionadded:: 1.6.0 + + Returns + ------- + result : dok_array, coo_array, dict or ndarray + Sparse matrix representing the results in "dictionary of keys" + format. If a dict is returned the keys are ``(i,j)`` tuples of indices. + If output_type is ``'ndarray'`` a record array with fields ``'i'``, ``'j'``, + and ``'v'`` is returned, + + Examples + -------- + You can compute a sparse distance matrix between two kd-trees: + + >>> import numpy as np + >>> from scipy.spatial import KDTree + >>> rng = np.random.default_rng() + >>> points1 = rng.random((5, 2)) + >>> points2 = rng.random((5, 2)) + >>> kdtree1 = KDTree(points1) + >>> kdtree2 = KDTree(points2) + >>> sdm = kdtree1.sparse_distance_matrix(kdtree2, 0.3, output_type="dok_array") + >>> sdm.toarray() + array([[0. , 0. , 0.12295571, 0. , 0. ], + [0. , 0. , 0. , 0. , 0. ], + [0.28942611, 0. , 0. , 0.2333084 , 0. ], + [0. , 0. , 0. , 0. , 0. ], + [0.24617575, 0.29571802, 0.26836782, 0. , 0. ]]) + + You can check distances above the `max_distance` are zeros: + + >>> from scipy.spatial.distance import cdist + >>> cdist(points1, points2) + array([[0.56906522, 0.39923701, 0.12295571, 0.8658745 , 0.79428925], + [0.37327919, 0.7225693 , 0.87665969, 0.32580855, 0.75679479], + [0.28942611, 0.30088013, 0.6395831 , 0.2333084 , 0.33630734], + [0.31994999, 0.72658602, 0.71124834, 0.55396483, 0.90785663], + [0.24617575, 0.29571802, 0.26836782, 0.57714465, 0.6473269 ]]) + + """ + return super().sparse_distance_matrix(other, max_distance, p, output_type) + + +@xp_capabilities(out_of_scope=True) +def distance_matrix(x, y, p=2.0, threshold=1000000): + """Compute the distance matrix. + + Returns the matrix of all pair-wise distances. + + .. deprecated:: 1.18.0 + This function is deprecated in favor of `scipy.spatial.distance.cdist` + and will be removed in SciPy 1.20.0. + + Parameters + ---------- + x : (M, K) array_like + Matrix of M vectors in K dimensions. + y : (N, K) array_like + Matrix of N vectors in K dimensions. + p : float, 1 <= p <= infinity + Which Minkowski p-norm to use. + threshold : positive int + If ``M * N * K`` > `threshold`, algorithm uses a Python loop instead + of large temporary arrays. + + Returns + ------- + result : (M, N) ndarray + Matrix containing the distance from every vector in `x` to every vector + in `y`. + + Examples + -------- + >>> from scipy.spatial import distance_matrix + >>> distance_matrix([[0,0],[0,1]], [[1,0],[1,1]]) + array([[ 1. , 1.41421356], + [ 1.41421356, 1. ]]) + + """ + msg = ("`distance_matrix` is deprecated in favor of " + "`scipy.spatial.distance.cdist` as of SciPy 1.18.0 and will be removed " + "in SciPy 1.20.0.") + warnings.warn(msg, DeprecationWarning, + skip_file_prefixes=(os.path.dirname(__file__),)) + x = np.asarray(x) + m, k = x.shape + y = np.asarray(y) + n, kk = y.shape + + if k != kk: + raise ValueError(f"x contains {k}-dimensional vectors but y contains " + f"{kk}-dimensional vectors") + + if m*n*k <= threshold: + return minkowski_distance(x[:,np.newaxis,:],y[np.newaxis,:,:],p) + else: + result = np.empty((m,n),dtype=float) # FIXME: figure out the best dtype + if m < n: + for i in range(m): + result[i,:] = minkowski_distance(x[i],y,p) + else: + for j in range(n): + result[:,j] = minkowski_distance(x,y[j],p) + return result diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/_plotutils.py b/venv/lib/python3.12/site-packages/scipy/spatial/_plotutils.py new file mode 100644 index 0000000000000000000000000000000000000000..a6290cde5dd9e37e01ac7c3944758fe59a0952b0 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/_plotutils.py @@ -0,0 +1,263 @@ +import numpy as np + +__all__ = ['delaunay_plot_2d', 'convex_hull_plot_2d', 'voronoi_plot_2d'] + + +def _get_axes(): + import matplotlib.pyplot as plt + + return plt.figure().gca() + + +def _adjust_bounds(ax, points): + margin = 0.1 * np.ptp(points, axis=0) + xy_min = points.min(axis=0) - margin + xy_max = points.max(axis=0) + margin + ax.set_xlim(xy_min[0], xy_max[0]) + ax.set_ylim(xy_min[1], xy_max[1]) + + +def delaunay_plot_2d(tri, ax=None): + """ + Plot the given Delaunay triangulation in 2-D. + + Parameters + ---------- + tri : scipy.spatial.Delaunay instance + Triangulation to plot + ax : matplotlib.axes.Axes instance, optional + Axes to plot on + + Returns + ------- + fig : matplotlib.figure.Figure instance + Figure for the plot + + See Also + -------- + Delaunay + matplotlib.pyplot.triplot + + Notes + ----- + Requires Matplotlib. + + Examples + -------- + + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from scipy.spatial import Delaunay, delaunay_plot_2d + + The Delaunay triangulation of a set of random points: + + >>> rng = np.random.default_rng() + >>> points = rng.random((30, 2)) + >>> tri = Delaunay(points) + + Plot it: + + >>> _ = delaunay_plot_2d(tri) + >>> plt.show() + + """ + if tri.points.shape[1] != 2: + raise ValueError("Delaunay triangulation is not 2-D") + + x, y = tri.points.T + + ax = ax or _get_axes() + ax.plot(x, y, 'o') + ax.triplot(x, y, tri.simplices.copy()) + + _adjust_bounds(ax, tri.points) + + return ax.figure + + +def convex_hull_plot_2d(hull, ax=None): + """ + Plot the given convex hull diagram in 2-D. + + Parameters + ---------- + hull : scipy.spatial.ConvexHull instance + Convex hull to plot + ax : matplotlib.axes.Axes instance, optional + Axes to plot on + + Returns + ------- + fig : matplotlib.figure.Figure instance + Figure for the plot + + See Also + -------- + ConvexHull + + Notes + ----- + Requires Matplotlib. + + + Examples + -------- + + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from scipy.spatial import ConvexHull, convex_hull_plot_2d + + The convex hull of a random set of points: + + >>> rng = np.random.default_rng() + >>> points = rng.random((30, 2)) + >>> hull = ConvexHull(points) + + Plot it: + + >>> _ = convex_hull_plot_2d(hull) + >>> plt.show() + + """ + from matplotlib.collections import LineCollection + + if hull.points.shape[1] != 2: + raise ValueError("Convex hull is not 2-D") + + ax = ax or _get_axes() + ax.plot(hull.points[:, 0], hull.points[:, 1], 'o') + line_segments = [hull.points[simplex] for simplex in hull.simplices] + ax.add_collection(LineCollection(line_segments, + colors='k', + linestyle='solid')) + _adjust_bounds(ax, hull.points) + + return ax.figure + + +def voronoi_plot_2d(vor, ax=None, **kw): + """ + Plot the given Voronoi diagram in 2-D. + + Parameters + ---------- + vor : scipy.spatial.Voronoi instance + Diagram to plot + ax : matplotlib.axes.Axes instance, optional + Axes to plot on + **kw + The following options may be passed as keyword arguments: + + show_points : bool, optional + Add the Voronoi points to the plot. + show_vertices : bool, optional + Add the Voronoi vertices to the plot. + line_colors : str, optional + Specifies the line color for polygon boundaries + line_width : float, optional + Specifies the line width for polygon boundaries + line_alpha : float, optional + Specifies the line alpha for polygon boundaries + point_size : float, optional + Specifies the size of points + + Returns + ------- + fig : matplotlib.figure.Figure instance + Figure for the plot + + See Also + -------- + Voronoi + + Notes + ----- + Requires Matplotlib. For degenerate input, including collinearity and + other violations of general position, it may be preferable to + calculate the Voronoi diagram with Qhull options ``QJ`` for random + joggling, or ``Qt`` to enforce triangulated output. Otherwise, some + Voronoi regions may not be visible. + + Examples + -------- + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from scipy.spatial import Voronoi, voronoi_plot_2d + + Create a set of points for the example: + + >>> rng = np.random.default_rng() + >>> points = rng.random((10,2)) + + Generate the Voronoi diagram for the points: + + >>> vor = Voronoi(points) + + Use `voronoi_plot_2d` to plot the diagram: + + >>> fig = voronoi_plot_2d(vor) + + Use `voronoi_plot_2d` to plot the diagram again, with some settings + customized: + + >>> fig = voronoi_plot_2d(vor, show_vertices=False, line_colors='orange', + ... line_width=2, line_alpha=0.6, point_size=2) + >>> plt.show() + + """ + from matplotlib.collections import LineCollection + + if vor.points.shape[1] != 2: + raise ValueError("Voronoi diagram is not 2-D") + + ax = ax or _get_axes() + + if kw.get('show_points', True): + point_size = kw.get('point_size', None) + ax.plot(vor.points[:, 0], vor.points[:, 1], '.', markersize=point_size) + if kw.get('show_vertices', True): + ax.plot(vor.vertices[:, 0], vor.vertices[:, 1], 'o') + + line_colors = kw.get('line_colors', 'k') + line_width = kw.get('line_width', 1.0) + line_alpha = kw.get('line_alpha', 1.0) + + center = vor.points.mean(axis=0) + ptp_bound = np.ptp(vor.points, axis=0) + + finite_segments = [] + infinite_segments = [] + for pointidx, simplex in zip(vor.ridge_points, vor.ridge_vertices): + simplex = np.asarray(simplex) + if np.all(simplex >= 0): + finite_segments.append(vor.vertices[simplex]) + else: + i = simplex[simplex >= 0][0] # finite end Voronoi vertex + + t = vor.points[pointidx[1]] - vor.points[pointidx[0]] # tangent + t /= np.linalg.norm(t) + n = np.array([-t[1], t[0]]) # normal + + midpoint = vor.points[pointidx].mean(axis=0) + direction = np.sign(np.dot(midpoint - center, n)) * n + if (vor.furthest_site): + direction = -direction + aspect_factor = abs(ptp_bound.max() / ptp_bound.min()) + far_point = vor.vertices[i] + direction * ptp_bound.max() * aspect_factor + + infinite_segments.append([vor.vertices[i], far_point]) + + ax.add_collection(LineCollection(finite_segments, + colors=line_colors, + lw=line_width, + alpha=line_alpha, + linestyle='solid')) + ax.add_collection(LineCollection(infinite_segments, + colors=line_colors, + lw=line_width, + alpha=line_alpha, + linestyle='dashed')) + + _adjust_bounds(ax, vor.points) + + return ax.figure diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/_procrustes.py b/venv/lib/python3.12/site-packages/scipy/spatial/_procrustes.py new file mode 100644 index 0000000000000000000000000000000000000000..e3814ab5a8404461b446eb473f5bf96deb9f8c1f --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/_procrustes.py @@ -0,0 +1,132 @@ +""" +This module provides functions to perform full Procrustes analysis. + +This code was originally written by Justin Kucynski and ported over from +scikit-bio by Yoshiki Vazquez-Baeza. +""" + +import numpy as np +from scipy.linalg import orthogonal_procrustes + + +__all__ = ['procrustes'] + + +def procrustes(data1, data2): + r"""Procrustes analysis, a similarity test for two data sets. + + Each input matrix is a set of points or vectors (the rows of the matrix). + The dimension of the space is the number of columns of each matrix. Given + two identically sized matrices, procrustes standardizes both such that: + + - :math:`tr(AA^{T}) = 1`. + + - Both sets of points are centered around the origin. + + Procrustes ([1]_, [2]_) then applies the optimal transform to the second + matrix (including scaling/dilation, rotations, and reflections) to minimize + :math:`M^{2}=\sum(data1-data2)^{2}`, or the sum of the squares of the + pointwise differences between the two input datasets. + + This function was not designed to handle datasets with different numbers of + datapoints (rows). If two data sets have different dimensionality + (different number of columns), simply add columns of zeros to the smaller + of the two. + + Parameters + ---------- + data1 : array_like + Matrix, n rows represent points in k (columns) space `data1` is the + reference data, after it is standardised, the data from `data2` will be + transformed to fit the pattern in `data1` (must have >1 unique points). + data2 : array_like + n rows of data in k space to be fit to `data1`. Must be the same + shape ``(numrows, numcols)`` as data1 (must have >1 unique points). + + Returns + ------- + mtx1 : array_like + A standardized version of `data1`. + mtx2 : array_like + The orientation of `data2` that best fits `data1`. Centered, but not + necessarily :math:`tr(AA^{T}) = 1`. + disparity : float + :math:`M^{2}` as defined above. + + Raises + ------ + ValueError + If the input arrays are not two-dimensional. + If the shape of the input arrays is different. + If the input arrays have zero columns or zero rows. + + See Also + -------- + scipy.linalg.orthogonal_procrustes + scipy.spatial.distance.directed_hausdorff : Another similarity test + for two data sets + + Notes + ----- + - The disparity should not depend on the order of the input matrices, but + the output matrices will, as only the first output matrix is guaranteed + to be scaled such that :math:`tr(AA^{T}) = 1`. + + - Duplicate data points are generally ok, duplicating a data point will + increase its effect on the procrustes fit. + + - The disparity scales as the number of points per input matrix. + + References + ---------- + .. [1] Krzanowski, W. J. (2000). "Principles of Multivariate analysis". + .. [2] Gower, J. C. (1975). "Generalized procrustes analysis". + + Examples + -------- + >>> import numpy as np + >>> from scipy.spatial import procrustes + + The matrix ``b`` is a rotated, shifted, scaled and mirrored version of + ``a`` here: + + >>> a = np.array([[1, 3], [1, 2], [1, 1], [2, 1]], 'd') + >>> b = np.array([[4, -2], [4, -4], [4, -6], [2, -6]], 'd') + >>> mtx1, mtx2, disparity = procrustes(a, b) + >>> round(disparity) + 0 + + """ + mtx1 = np.array(data1, dtype=np.float64, copy=True) + mtx2 = np.array(data2, dtype=np.float64, copy=True) + + if mtx1.ndim != 2 or mtx2.ndim != 2: + raise ValueError("Input matrices must be two-dimensional") + if mtx1.shape != mtx2.shape: + raise ValueError("Input matrices must be of same shape") + if mtx1.size == 0: + raise ValueError("Input matrices must be >0 rows and >0 cols") + + # translate all the data to the origin + mtx1 -= np.mean(mtx1, 0) + mtx2 -= np.mean(mtx2, 0) + + norm1 = np.linalg.norm(mtx1) + norm2 = np.linalg.norm(mtx2) + + if norm1 == 0 or norm2 == 0: + raise ValueError("Input matrices must contain >1 unique points") + + # change scaling of data (in rows) such that trace(mtx*mtx') = 1 + mtx1 /= norm1 + mtx2 /= norm2 + + # transform mtx2 to minimize disparity + R, s = orthogonal_procrustes(mtx1, mtx2) + mtx2 = np.dot(mtx2, R.T) * s + + # measure the dissimilarity between the two datasets + disparity = np.sum(np.square(mtx1 - mtx2)) + + return mtx1, mtx2, disparity + diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/_qhull.cpython-312-x86_64-linux-gnu.so b/venv/lib/python3.12/site-packages/scipy/spatial/_qhull.cpython-312-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..d598bfa3b5a9977e993562a7373b2f8dd3d6bfc0 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/_qhull.cpython-312-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f0ece0e7838e9d1ee5c3126292fed225e740734d99280b0faa3ff1cf726b531a +size 935776 diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/_qhull.pyi b/venv/lib/python3.12/site-packages/scipy/spatial/_qhull.pyi new file mode 100644 index 0000000000000000000000000000000000000000..723c28d1ce90ab6cd0657e7587eb1eb6eea2f8d6 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/_qhull.pyi @@ -0,0 +1,213 @@ +''' +Static type checking stub file for scipy/spatial/qhull.pyx +''' + + +import numpy as np +from numpy.typing import ArrayLike, NDArray +from typing import final + +class QhullError(RuntimeError): + ... + +@final +class _Qhull: + # Read-only cython attribute that behaves, more or less, like a property + @property + def ndim(self) -> int: ... + mode_option: bytes + options: bytes + furthest_site: bool + + def __init__( + self, + mode_option: bytes, + points: NDArray[np.float64], + options: None | bytes = ..., + required_options: None | bytes = ..., + furthest_site: bool = ..., + incremental: bool = ..., + interior_point: None | NDArray[np.float64] = ..., + ) -> None: ... + def check_active(self) -> None: ... + def close(self) -> None: ... + def get_points(self) -> NDArray[np.float64]: ... + def add_points( + self, + points: ArrayLike, + interior_point: ArrayLike = ... + ) -> None: ... + def get_paraboloid_shift_scale(self) -> tuple[float, float]: ... + def volume_area(self) -> tuple[float, float]: ... + def triangulate(self) -> None: ... + def get_simplex_facet_array(self) -> tuple[ + NDArray[np.intc], + NDArray[np.intc], + NDArray[np.float64], + NDArray[np.intc], + NDArray[np.intc], + ]: ... + def get_hull_points(self) -> NDArray[np.float64]: ... + def get_hull_facets(self) -> tuple[ + list[list[int]], + NDArray[np.float64], + ]: ... + def get_voronoi_diagram(self) -> tuple[ + NDArray[np.float64], + NDArray[np.intc], + list[list[int]], + list[list[int]], + NDArray[np.intp], + ]: ... + def get_extremes_2d(self) -> NDArray[np.intc]: ... + +def _get_barycentric_transforms( + points: NDArray[np.float64], + simplices: NDArray[np.intc], + eps: float +) -> NDArray[np.float64]: ... + +class _QhullUser: + ndim: int + npoints: int + min_bound: NDArray[np.float64] + max_bound: NDArray[np.float64] + + def __init__(self, qhull: _Qhull, incremental: bool = ...) -> None: ... + def close(self) -> None: ... + def _update(self, qhull: _Qhull) -> None: ... + def _add_points( + self, + points: ArrayLike, + restart: bool = ..., + interior_point: ArrayLike = ... + ) -> None: ... + +class Delaunay(_QhullUser): + furthest_site: bool + paraboloid_scale: float + paraboloid_shift: float + simplices: NDArray[np.intc] + neighbors: NDArray[np.intc] + equations: NDArray[np.float64] + coplanar: NDArray[np.intc] + good: NDArray[np.intc] + nsimplex: int + vertices: NDArray[np.intc] + + def __init__( + self, + points: ArrayLike, + furthest_site: bool = ..., + incremental: bool = ..., + qhull_options: None | str = ... + ) -> None: ... + def _update(self, qhull: _Qhull) -> None: ... + def add_points( + self, + points: ArrayLike, + restart: bool = ... + ) -> None: ... + @property + def points(self) -> NDArray[np.float64]: ... + @property + def transform(self) -> NDArray[np.float64]: ... + @property + def vertex_to_simplex(self) -> NDArray[np.intc]: ... + @property + def vertex_neighbor_vertices(self) -> tuple[ + NDArray[np.intc], + NDArray[np.intc], + ]: ... + @property + def convex_hull(self) -> NDArray[np.intc]: ... + def find_simplex( + self, + xi: ArrayLike, + bruteforce: bool = ..., + tol: float = ... + ) -> NDArray[np.intc]: ... + def plane_distance(self, xi: ArrayLike) -> NDArray[np.float64]: ... + def lift_points(self, x: ArrayLike) -> NDArray[np.float64]: ... + +def tsearch(tri: Delaunay, xi: ArrayLike) -> NDArray[np.intc]: ... +def _copy_docstr(dst: object, src: object) -> None: ... + +class ConvexHull(_QhullUser): + simplices: NDArray[np.intc] + neighbors: NDArray[np.intc] + equations: NDArray[np.float64] + coplanar: NDArray[np.intc] + good: None | NDArray[np.bool_] + volume: float + area: float + nsimplex: int + + def __init__( + self, + points: ArrayLike, + incremental: bool = ..., + qhull_options: None | str = ... + ) -> None: ... + def _update(self, qhull: _Qhull) -> None: ... + def add_points(self, points: ArrayLike, + restart: bool = ...) -> None: ... + @property + def points(self) -> NDArray[np.float64]: ... + @property + def vertices(self) -> NDArray[np.intc]: ... + +class Voronoi(_QhullUser): + vertices: NDArray[np.float64] + ridge_points: NDArray[np.intc] + ridge_vertices: list[list[int]] + regions: list[list[int]] + point_region: NDArray[np.intp] + furthest_site: bool + + def __init__( + self, + points: ArrayLike, + furthest_site: bool = ..., + incremental: bool = ..., + qhull_options: None | str = ... + ) -> None: ... + def _update(self, qhull: _Qhull) -> None: ... + def add_points( + self, + points: ArrayLike, + restart: bool = ... + ) -> None: ... + @property + def points(self) -> NDArray[np.float64]: ... + @property + def ridge_dict(self) -> dict[tuple[int, int], list[int]]: ... + +class HalfspaceIntersection(_QhullUser): + interior_point: NDArray[np.float64] + dual_facets: list[list[int]] + dual_equations: NDArray[np.float64] + dual_points: NDArray[np.float64] + dual_volume: float + dual_area: float + intersections: NDArray[np.float64] + ndim: int + nineq: int + + def __init__( + self, + halfspaces: ArrayLike, + interior_point: ArrayLike, + incremental: bool = ..., + qhull_options: None | str = ... + ) -> None: ... + def _update(self, qhull: _Qhull) -> None: ... + def add_halfspaces( + self, + halfspaces: ArrayLike, + restart: bool = ... + ) -> None: ... + @property + def halfspaces(self) -> NDArray[np.float64]: ... + @property + def dual_vertices(self) -> NDArray[np.integer]: ... diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/_spherical_voronoi.py b/venv/lib/python3.12/site-packages/scipy/spatial/_spherical_voronoi.py new file mode 100644 index 0000000000000000000000000000000000000000..1d4a095ec026fa373759eba3b88b41ec5c896925 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/_spherical_voronoi.py @@ -0,0 +1,339 @@ +""" +Spherical Voronoi Code + +.. versionadded:: 0.18.0 + +""" +# +# Copyright (C) Tyler Reddy, Ross Hemsley, Edd Edmondson, +# Nikolai Nowaczyk, Joe Pitt-Francis, 2015. +# +# Distributed under the same BSD license as SciPy. +# + +import numpy as np +import scipy +from . import _voronoi +from scipy.spatial import cKDTree # type: ignore[attr-defined] + +__all__ = ['SphericalVoronoi'] + + +def calculate_solid_angles(R): + """Calculates the solid angles of plane triangles. Implements the method of + Van Oosterom and Strackee [VanOosterom]_ with some modifications. Assumes + that input points have unit norm.""" + # Original method uses a triple product `R1 . (R2 x R3)` for the numerator. + # This is equal to the determinant of the matrix [R1 R2 R3], which can be + # computed with better stability. + numerator = np.linalg.det(R) + denominator = 1 + (np.einsum('ij,ij->i', R[:, 0], R[:, 1]) + + np.einsum('ij,ij->i', R[:, 1], R[:, 2]) + + np.einsum('ij,ij->i', R[:, 2], R[:, 0])) + return np.abs(2 * np.arctan2(numerator, denominator)) + + +class SphericalVoronoi: + """ + Voronoi diagrams on the surface of a sphere. + + .. versionadded:: 0.18.0 + + Parameters + ---------- + points : ndarray of floats, shape (npoints, ndim) + Coordinates of points from which to construct a spherical + Voronoi diagram. + radius : float, optional + Radius of the sphere (Default: 1) + center : ndarray of floats, shape (ndim,) + Center of sphere (Default: origin) + threshold : float + Threshold for detecting duplicate points and + mismatches between points and sphere parameters. + (Default: 1e-06) + + Attributes + ---------- + points : double array of shape (npoints, ndim) + the points in `ndim` dimensions to generate the Voronoi diagram from + radius : double + radius of the sphere + center : double array of shape (ndim,) + center of the sphere + vertices : double array of shape (nvertices, ndim) + Voronoi vertices corresponding to points + regions : list of list of integers of shape (npoints, _ ) + the n-th entry is a list consisting of the indices + of the vertices belonging to the n-th point in points + + Methods + ------- + calculate_areas + Calculates the areas of the Voronoi regions. For 2D point sets, the + regions are circular arcs. The sum of the areas is ``2 * pi * radius``. + For 3D point sets, the regions are spherical polygons. The sum of the + areas is ``4 * pi * radius**2``. + + Raises + ------ + ValueError + If there are duplicates in `points`. + If the provided `radius` is not consistent with `points`. + + See Also + -------- + Voronoi : Conventional Voronoi diagrams in N dimensions. + + Notes + ----- + The spherical Voronoi diagram algorithm proceeds as follows. The Convex + Hull of the input points (generators) is calculated, and is equivalent to + their Delaunay triangulation on the surface of the sphere [Caroli]_. + The Convex Hull neighbour information is then used to + order the Voronoi region vertices around each generator. The latter + approach is substantially less sensitive to floating point issues than + angle-based methods of Voronoi region vertex sorting. + + Empirical assessment of spherical Voronoi algorithm performance suggests + quadratic time complexity (loglinear is optimal, but algorithms are more + challenging to implement). + + References + ---------- + .. [Caroli] Caroli et al. Robust and Efficient Delaunay triangulations of + points on or close to a sphere. Research Report RR-7004, 2009. + + .. [VanOosterom] Van Oosterom and Strackee. The solid angle of a plane + triangle. IEEE Transactions on Biomedical Engineering, + 2, 1983, pp 125--126. + + Examples + -------- + Do some imports and take some points on a cube: + + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from scipy.spatial import SphericalVoronoi, geometric_slerp + >>> from mpl_toolkits.mplot3d import proj3d + >>> # set input data + >>> points = np.array([[0, 0, 1], [0, 0, -1], [1, 0, 0], + ... [0, 1, 0], [0, -1, 0], [-1, 0, 0], ]) + + Calculate the spherical Voronoi diagram: + + >>> radius = 1 + >>> center = np.array([0, 0, 0]) + >>> sv = SphericalVoronoi(points, radius, center) + + Generate plot: + + >>> # sort vertices (optional, helpful for plotting) + >>> sv.sort_vertices_of_regions() + >>> t_vals = np.linspace(0, 1, 2000) + >>> fig = plt.figure() + >>> ax = fig.add_subplot(111, projection='3d') + >>> # plot the unit sphere for reference (optional) + >>> u = np.linspace(0, 2 * np.pi, 100) + >>> v = np.linspace(0, np.pi, 100) + >>> x = np.outer(np.cos(u), np.sin(v)) + >>> y = np.outer(np.sin(u), np.sin(v)) + >>> z = np.outer(np.ones(np.size(u)), np.cos(v)) + >>> ax.plot_surface(x, y, z, color='y', alpha=0.1) + >>> # plot generator points + >>> ax.scatter(points[:, 0], points[:, 1], points[:, 2], c='b') + >>> # plot Voronoi vertices + >>> ax.scatter(sv.vertices[:, 0], sv.vertices[:, 1], sv.vertices[:, 2], + ... c='g') + >>> # indicate Voronoi regions (as Euclidean polygons) + >>> for region in sv.regions: + ... n = len(region) + ... for i in range(n): + ... start = sv.vertices[region][i] + ... end = sv.vertices[region][(i + 1) % n] + ... result = geometric_slerp(start, end, t_vals) + ... ax.plot(result[..., 0], + ... result[..., 1], + ... result[..., 2], + ... c='k') + >>> ax.azim = 10 + >>> ax.elev = 40 + >>> _ = ax.set_xticks([]) + >>> _ = ax.set_yticks([]) + >>> _ = ax.set_zticks([]) + >>> fig.set_size_inches(4, 4) + >>> plt.show() + """ + def __init__(self, points, radius=1, center=None, threshold=1e-06): + + if radius is None: + raise ValueError('`radius` is `None`. ' + 'Please provide a floating point number ' + '(i.e. `radius=1`).') + + self.radius = float(radius) + self.points = np.array(points).astype(np.float64) + self._dim = self.points.shape[1] + if center is None: + self.center = np.zeros(self._dim) + else: + self.center = np.array(center, dtype=float) + + # test degenerate input + self._rank = np.linalg.matrix_rank(self.points - self.points[0], + tol=threshold * self.radius) + if self._rank < self._dim: + raise ValueError(f"Rank of input points must be at least {self._dim}") + + if cKDTree(self.points).query_pairs(threshold * self.radius): + raise ValueError("Duplicate generators present.") + + radii = np.linalg.norm(self.points - self.center, axis=1) + max_discrepancy = np.abs(radii - self.radius).max() + if max_discrepancy >= threshold * self.radius: + raise ValueError("Radius inconsistent with generators.") + + self._calc_vertices_regions() + + def _calc_vertices_regions(self): + """ + Calculates the Voronoi vertices and regions of the generators stored + in self.points. The vertices will be stored in self.vertices and the + regions in self.regions. + + This algorithm was discussed at PyData London 2015 by + Tyler Reddy, Ross Hemsley and Nikolai Nowaczyk + """ + # get Convex Hull + conv = scipy.spatial.ConvexHull(self.points) + # get circumcenters of Convex Hull triangles from facet equations + # for 3D input circumcenters will have shape: (2N-4, 3) + self.vertices = self.radius * conv.equations[:, :-1] + self.center + self._simplices = conv.simplices + # calculate regions from triangulation + # for 3D input simplex_indices will have shape: (2N-4,) + simplex_indices = np.arange(len(self._simplices)) + # for 3D input tri_indices will have shape: (6N-12,) + tri_indices = np.column_stack([simplex_indices] * self._dim).ravel() + # for 3D input point_indices will have shape: (6N-12,) + self._point_indices = self._simplices.ravel() + # for 3D input indices will have shape: (6N-12,) + self._indices = np.argsort(self._point_indices, kind='mergesort') + # for 3D input flattened_groups will have shape: (6N-12,) + flattened_groups = tri_indices[self._indices].astype(np.intp) + # intervals will have shape: (N+1,) + self._intervals = np.cumsum(np.bincount(self._point_indices + 1)) + # split flattened groups to get nested list of unsorted regions + groups = [list(flattened_groups[self._intervals[i]:self._intervals[i + 1]]) + for i in range(len(self._intervals) - 1)] + self.regions = groups + + def sort_vertices_of_regions(self): + """Sort indices of the vertices to be (counter-)clockwise ordered. + + Raises + ------ + TypeError + If the points are not three-dimensional. + + Notes + ----- + For each region in regions, it sorts the indices of the Voronoi + vertices such that the resulting points are in a clockwise or + counterclockwise order around the generator point. + + This is done as follows: Recall that the n-th region in regions + surrounds the n-th generator in points and that the k-th + Voronoi vertex in vertices is the circumcenter of the k-th triangle + in self._simplices. For each region n, we choose the first triangle + (=Voronoi vertex) in self._simplices and a vertex of that triangle + not equal to the center n. These determine a unique neighbor of that + triangle, which is then chosen as the second triangle. The second + triangle will have a unique vertex not equal to the current vertex or + the center. This determines a unique neighbor of the second triangle, + which is then chosen as the third triangle and so forth. We proceed + through all the triangles (=Voronoi vertices) belonging to the + generator in points and obtain a sorted version of the vertices + of its surrounding region. + """ + if self._dim != 3: + raise TypeError("Only supported for three-dimensional point sets") + _voronoi.sort_vertices_of_regions(self._simplices, self.regions) + + def _calculate_areas_3d(self): + self.sort_vertices_of_regions() + sizes = np.diff(self._intervals) + csizes = np.cumsum(sizes) + num_regions = csizes[-1] + + # We create a set of triangles consisting of one point and two Voronoi + # vertices. The vertices of each triangle are adjacent in the sorted + # regions list. + point_indices = self._point_indices[self._indices] + + nbrs1 = np.array([r for region in self.regions for r in region]) + + # The calculation of nbrs2 is a vectorized version of: + # np.array([r for region in self.regions for r in np.roll(region, 1)]) + nbrs2 = np.roll(nbrs1, 1) + indices = np.roll(csizes, 1) + indices[0] = 0 + nbrs2[indices] = nbrs1[csizes - 1] + + # Normalize points and vertices. + pnormalized = (self.points - self.center) / self.radius + vnormalized = (self.vertices - self.center) / self.radius + + # Create the complete set of triangles and calculate their solid angles + triangles = np.hstack([pnormalized[point_indices], + vnormalized[nbrs1], + vnormalized[nbrs2] + ]).reshape((num_regions, 3, 3)) + triangle_solid_angles = calculate_solid_angles(triangles) + + # Sum the solid angles of the triangles in each region + solid_angles = np.cumsum(triangle_solid_angles)[csizes - 1] + solid_angles[1:] -= solid_angles[:-1] + + # Get polygon areas using A = omega * r**2 + return solid_angles * self.radius**2 + + def _calculate_areas_2d(self): + # Find start and end points of arcs + arcs = self.points[self._simplices] - self.center + + # Calculate the angle subtended by arcs + d = np.sum((arcs[:, 1] - arcs[:, 0]) ** 2, axis=1) + theta = np.arccos(1 - (d / (2 * (self.radius ** 2)))) + + # Get areas using A = r * theta + areas = self.radius * theta + + # Correct arcs which go the wrong way (single-hemisphere inputs) + indices = np.einsum('ij,ij->i', arcs[:, 0], + self.vertices - self.center) < 0 + areas[indices] = 2 * np.pi * self.radius - areas[indices] + return areas + + def calculate_areas(self): + """Calculates the areas of the Voronoi regions. + + For 2D point sets, the regions are circular arcs. The sum of the areas + is ``2 * pi * radius``. + + For 3D point sets, the regions are spherical polygons. The sum of the + areas is ``4 * pi * radius**2``. + + .. versionadded:: 1.5.0 + + Returns + ------- + areas : double array of shape (npoints,) + The areas of the Voronoi regions. + """ + if self._dim == 2: + return self._calculate_areas_2d() + elif self._dim == 3: + return self._calculate_areas_3d() + else: + raise TypeError("Only supported for 2D and 3D point sets") diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/_voronoi.cpython-312-x86_64-linux-gnu.so b/venv/lib/python3.12/site-packages/scipy/spatial/_voronoi.cpython-312-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..4284ee93affc8458be4fbe1c325f99c1888ab89c Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/spatial/_voronoi.cpython-312-x86_64-linux-gnu.so differ diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/_voronoi.pyi b/venv/lib/python3.12/site-packages/scipy/spatial/_voronoi.pyi new file mode 100644 index 0000000000000000000000000000000000000000..c7c361ff69414d50a6ebcfe2c837025b60083940 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/_voronoi.pyi @@ -0,0 +1,4 @@ + +import numpy as np + +def sort_vertices_of_regions(simplices: np.ndarray, regions: list[list[int]]) -> None: ... # noqa: E501 diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/ckdtree.py b/venv/lib/python3.12/site-packages/scipy/spatial/ckdtree.py new file mode 100644 index 0000000000000000000000000000000000000000..40f524c71bf122ac822626596c2991b19ee0d30e --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/ckdtree.py @@ -0,0 +1,18 @@ +# This file is not meant for public use and will be removed in SciPy v2.0.0. +# Use the `scipy.spatial` namespace for importing the functions +# included below. + +from scipy._lib.deprecation import _sub_module_deprecation + + +__all__ = ["cKDTree"] # noqa: F822 + + +def __dir__(): + return __all__ + + +def __getattr__(name): + return _sub_module_deprecation(sub_package="spatial", module="ckdtree", + private_modules=["_ckdtree"], all=__all__, + attribute=name) diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/distance.py b/venv/lib/python3.12/site-packages/scipy/spatial/distance.py new file mode 100644 index 0000000000000000000000000000000000000000..f8602f3faf442881dff5260c4df5652086a7cdb0 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/distance.py @@ -0,0 +1,2942 @@ +""" +Distance computations (:mod:`scipy.spatial.distance`) +===================================================== + +.. sectionauthor:: Damian Eads + +Function reference +------------------ + +Distance matrix computation from a collection of raw observation vectors +stored in a rectangular array. + +.. autosummary:: + :toctree: generated/ + + pdist -- pairwise distances between observation vectors. + cdist -- distances between two collections of observation vectors + squareform -- convert distance matrix to a condensed one and vice versa + directed_hausdorff -- directed Hausdorff distance between arrays + +Predicates for checking the validity of distance matrices, both +condensed and redundant. Also contained in this module are functions +for computing the number of observations in a distance matrix. + +.. autosummary:: + :toctree: generated/ + + is_valid_dm -- checks for a valid distance matrix + is_valid_y -- checks for a valid condensed distance matrix + num_obs_dm -- # of observations in a distance matrix + num_obs_y -- # of observations in a condensed distance matrix + +Distance functions between two numeric vectors ``u`` and ``v``. Computing +distances over a large collection of vectors is inefficient for these +functions. Use ``pdist`` for this purpose. + +.. autosummary:: + :toctree: generated/ + + braycurtis -- the Bray-Curtis distance. + canberra -- the Canberra distance. + chebyshev -- the Chebyshev distance. + cityblock -- the Manhattan distance. + correlation -- the Correlation distance. + cosine -- the Cosine distance. + euclidean -- the Euclidean distance. + jensenshannon -- the Jensen-Shannon distance. + mahalanobis -- the Mahalanobis distance. + minkowski -- the Minkowski distance. + seuclidean -- the normalized Euclidean distance. + sqeuclidean -- the squared Euclidean distance. + +Distance functions between two boolean vectors (representing sets) ``u`` and +``v``. As in the case of numerical vectors, ``pdist`` is more efficient for +computing the distances between all pairs. + +.. autosummary:: + :toctree: generated/ + + dice -- the Dice dissimilarity. + hamming -- the Hamming distance. + jaccard -- the Jaccard distance. + rogerstanimoto -- the Rogers-Tanimoto dissimilarity. + russellrao -- the Russell-Rao dissimilarity. + sokalsneath -- the Sokal-Sneath dissimilarity. + yule -- the Yule dissimilarity. + +:func:`hamming` also operates over discrete numerical vectors. +""" + +# Copyright (C) Damian Eads, 2007-2008. New BSD License. + +__all__ = [ + 'braycurtis', + 'canberra', + 'cdist', + 'chebyshev', + 'cityblock', + 'correlation', + 'cosine', + 'dice', + 'directed_hausdorff', + 'euclidean', + 'hamming', + 'is_valid_dm', + 'is_valid_y', + 'jaccard', + 'jensenshannon', + 'mahalanobis', + 'minkowski', + 'num_obs_dm', + 'num_obs_y', + 'pdist', + 'rogerstanimoto', + 'russellrao', + 'seuclidean', + 'sokalsneath', + 'sqeuclidean', + 'squareform', + 'yule' +] + + +import math +import warnings +import dataclasses +from collections.abc import Callable +from functools import partial + +import numpy as np + +from scipy._lib._array_api import _asarray +from scipy._lib._util import _asarray_validated, _transition_to_rng +from scipy._external import array_api_extra as xpx +from scipy.linalg import norm +from scipy.special import rel_entr +from . import _hausdorff, _distance_pybind, _distance_wrap + + +def _copy_array_if_base_present(a): + """Copy the array if its base points to a parent array.""" + if a.base is not None: + return a.copy() + return a + + +def _correlation_cdist_wrap(XA, XB, dm, **kwargs): + XA = XA - XA.mean(axis=1, keepdims=True) + XB = XB - XB.mean(axis=1, keepdims=True) + _distance_wrap.cdist_cosine_double_wrap(XA, XB, dm, **kwargs) + + +def _correlation_pdist_wrap(X, dm, **kwargs): + X2 = X - X.mean(axis=1, keepdims=True) + _distance_wrap.pdist_cosine_double_wrap(X2, dm, **kwargs) + + +def _convert_to_type(X, out_type): + return np.ascontiguousarray(X, dtype=out_type) + + +def _nbool_correspond_all(u, v, w=None): + if u.dtype == v.dtype == bool and w is None: + not_u = ~u + not_v = ~v + nff = (not_u & not_v).sum() + nft = (not_u & v).sum() + ntf = (u & not_v).sum() + ntt = (u & v).sum() + else: + dtype = np.result_type(int, u.dtype, v.dtype) + u = u.astype(dtype) + v = v.astype(dtype) + not_u = 1.0 - u + not_v = 1.0 - v + if w is not None: + not_u = w * not_u + u = w * u + nff = (not_u * not_v).sum() + nft = (not_u * v).sum() + ntf = (u * not_v).sum() + ntt = (u * v).sum() + return (nff, nft, ntf, ntt) + + +def _nbool_correspond_ft_tf(u, v, w=None): + if u.dtype == v.dtype == bool and w is None: + not_u = ~u + not_v = ~v + nft = (not_u & v).sum() + ntf = (u & not_v).sum() + else: + dtype = np.result_type(int, u.dtype, v.dtype) + u = u.astype(dtype) + v = v.astype(dtype) + not_u = 1.0 - u + not_v = 1.0 - v + if w is not None: + not_u = w * not_u + u = w * u + nft = (not_u * v).sum() + ntf = (u * not_v).sum() + return (nft, ntf) + + +def _validate_cdist_input(XA, XB, mA, mB, n, metric_info, **kwargs): + # get supported types + types = metric_info.types + # choose best type + typ = types[types.index(XA.dtype)] if XA.dtype in types else types[0] + # validate data + XA = _convert_to_type(XA, out_type=typ) + XB = _convert_to_type(XB, out_type=typ) + + # validate kwargs + _validate_kwargs = metric_info.validator + if _validate_kwargs: + kwargs = _validate_kwargs((XA, XB), mA + mB, n, **kwargs) + return XA, XB, typ, kwargs + + +def _validate_weight_with_size(X, m, n, **kwargs): + w = kwargs.pop('w', None) + if w is None: + return kwargs + + if w.ndim != 1 or w.shape[0] != n: + raise ValueError("Weights must have same size as input vector. " + f"{w.shape[0]} vs. {n}") + + kwargs['w'] = _validate_weights(w) + return kwargs + + +def _validate_hamming_kwargs(X, m, n, **kwargs): + w = kwargs.get('w', np.ones((n,), dtype='double')) + + if w.ndim != 1 or w.shape[0] != n: + raise ValueError(f"Weights must have same size as input vector. " + f"{w.shape[0]} vs. {n}") + + kwargs['w'] = _validate_weights(w) + return kwargs + + +def _validate_mahalanobis_kwargs(X, m, n, **kwargs): + VI = kwargs.pop('VI', None) + if VI is None: + if m <= n: + # There are fewer observations than the dimension of + # the observations. + raise ValueError( + f"The number of observations ({m}) is too small; " + f"the covariance matrix is singular. For observations " + f"with {n} dimensions, at least {n + 1} observations are required.") + if isinstance(X, tuple): + X = np.vstack(X) + CV = np.atleast_2d(np.cov(X.astype(np.float64, copy=False).T)) + VI = np.linalg.inv(CV).T.copy() + kwargs["VI"] = _convert_to_double(VI) + return kwargs + + +def _validate_minkowski_kwargs(X, m, n, **kwargs): + kwargs = _validate_weight_with_size(X, m, n, **kwargs) + if 'p' not in kwargs: + kwargs['p'] = 2. + else: + if kwargs['p'] <= 0: + raise ValueError("p must be greater than 0") + + return kwargs + + +def _validate_pdist_input(X, m, n, metric_info, **kwargs): + # get supported types + types = metric_info.types + # choose best type + typ = types[types.index(X.dtype)] if X.dtype in types else types[0] + # validate data + X = _convert_to_type(X, out_type=typ) + + # validate kwargs + _validate_kwargs = metric_info.validator + if _validate_kwargs: + kwargs = _validate_kwargs(X, m, n, **kwargs) + return X, typ, kwargs + + +def _validate_seuclidean_kwargs(X, m, n, **kwargs): + V = kwargs.pop('V', None) + if V is None: + if isinstance(X, tuple): + X = np.vstack(X) + V = np.var(X.astype(np.float64, copy=False), axis=0, ddof=1) + else: + V = np.asarray(V, order='c') + if len(V.shape) != 1: + raise ValueError('Variance vector V must ' + 'be one-dimensional.') + if V.shape[0] != n: + raise ValueError('Variance vector V must be of the same ' + 'dimension as the vectors on which the distances ' + 'are computed.') + kwargs['V'] = _convert_to_double(V) + return kwargs + + +def _validate_vector(u, dtype=None): + # XXX Is order='c' really necessary? + u = np.asarray(u, dtype=dtype, order='c') + if u.ndim == 1: + return u + raise ValueError("Input vector should be 1-D.") + + +def _validate_weights(w, dtype=np.float64): + w = _validate_vector(w, dtype=dtype) + if np.any(w < 0): + raise ValueError("Input weights should be all non-negative") + return w + + +@_transition_to_rng('seed', position_num=2, replace_doc=False) +def directed_hausdorff(u, v, rng=0): + """ + Compute the directed Hausdorff distance between two 2-D arrays. + + Distances between pairs are calculated using a Euclidean metric. + + Parameters + ---------- + u : (M,N) array_like + Input array with M points in N dimensions. + v : (O,N) array_like + Input array with O points in N dimensions. + rng : int or `numpy.random.Generator` or None, optional + Pseudorandom number generator state. Default is 0 so the + shuffling of `u` and `v` is reproducible. + + If `rng` is passed by keyword, types other than `numpy.random.Generator` are + passed to `numpy.random.default_rng` to instantiate a ``Generator``. + If `rng` is already a ``Generator`` instance, then the provided instance is + used. + + If this argument is passed by position or `seed` is passed by keyword, + legacy behavior for the argument `seed` applies: + + - If `seed` is None, a new ``RandomState`` instance is used. The state is + initialized using data from ``/dev/urandom`` (or the Windows analogue) + if available or from the system clock otherwise. + - If `seed` is an int, a new ``RandomState`` instance is used, + seeded with `seed`. + - If `seed` is already a ``Generator`` or ``RandomState`` instance, then + that instance is used. + + .. versionchanged:: 1.15.0 + As part of the `SPEC-007 `_ + transition from use of `numpy.random.RandomState` to + `numpy.random.Generator`, this keyword was changed from `seed` to `rng`. + For an interim period, both keywords will continue to work, although only + one may be specified at a time. After the interim period, function calls + using the `seed` keyword will emit warnings. The behavior of both `seed` + and `rng` are outlined above, but only the `rng` keyword should be used in + new code. + + Returns + ------- + d : double + The directed Hausdorff distance between arrays `u` and `v`, + + index_1 : int + index of point contributing to Hausdorff pair in `u` + + index_2 : int + index of point contributing to Hausdorff pair in `v` + + Raises + ------ + ValueError + An exception is thrown if `u` and `v` do not have + the same number of columns. + + See Also + -------- + scipy.spatial.procrustes : Another similarity test for two data sets + + Notes + ----- + Uses the early break technique and the random sampling approach + described by [1]_. Although worst-case performance is ``O(m * o)`` + (as with the brute force algorithm), this is unlikely in practice + as the input data would have to require the algorithm to explore + every single point interaction, and after the algorithm shuffles + the input points at that. The best case performance is ``O(m)``, + which is satisfied by selecting an inner loop distance that is + less than cmax and leads to an early break as often as possible. + The authors have formally shown that the average runtime is + closer to ``O(m)``. + + .. versionadded:: 0.19.0 + + References + ---------- + .. [1] A. A. Taha and A. Hanbury, "An efficient algorithm for + calculating the exact Hausdorff distance." IEEE Transactions On + Pattern Analysis And Machine Intelligence, vol. 37, no. 11, + pp. 2153-63, 2015. :doi:`10.1109/TPAMI.2015.2408351`. + + Examples + -------- + Find the directed Hausdorff distance between two 2-D arrays of + coordinates: + + >>> from scipy.spatial.distance import directed_hausdorff + >>> import numpy as np + >>> u = np.array([(1.0, 0.0), + ... (0.0, 1.0), + ... (-1.0, 0.0), + ... (0.0, -1.0)]) + >>> v = np.array([(2.0, 0.0), + ... (0.0, 2.0), + ... (-2.0, 0.0), + ... (0.0, -4.0)]) + + >>> directed_hausdorff(u, v)[0] + 2.23606797749979 + >>> directed_hausdorff(v, u)[0] + 3.0 + + Find the general (symmetric) Hausdorff distance between two 2-D + arrays of coordinates: + + >>> max(directed_hausdorff(u, v)[0], directed_hausdorff(v, u)[0]) + 3.0 + + Find the indices of the points that generate the Hausdorff distance + (the Hausdorff pair): + + >>> directed_hausdorff(v, u)[1:] + (3, 3) + + """ + u = np.asarray(u, dtype=np.float64, order='c') + v = np.asarray(v, dtype=np.float64, order='c') + if u.shape[1] != v.shape[1]: + raise ValueError('u and v need to have the same ' + 'number of columns') + result = _hausdorff.directed_hausdorff(u, v, rng) + return result + + +def minkowski(u, v, p=2, w=None): + """ + Compute the Minkowski distance between two arrays. + + The Minkowski distance between arrays `u` and `v`, + is defined as + + .. math:: + + {\\|u-v\\|}_p = (\\sum{|u_i - v_i|^p})^{1/p}. + + + \\left(\\sum{w_i(|(u_i - v_i)|^p)}\\right)^{1/p}. + + Parameters + ---------- + u : (..., N) array_like + Input array. + v : (..., N) array_like + Input array. + p : scalar + The order of the norm of the difference :math:`{\\|u-v\\|}_p`. Note + that for :math:`0 < p < 1`, the triangle inequality only holds with + an additional multiplicative factor, i.e. it is only a quasi-metric. + w : (N,) array_like, optional + The weights for each value in `u` and `v`. Default is None, + which gives each value a weight of 1.0 + + Returns + ------- + minkowski : float or ndarray + The Minkowski distance between vectors `u` and `v`. + + Examples + -------- + >>> from scipy.spatial import distance + >>> distance.minkowski([1, 0, 0], [0, 1, 0], 1) + 2.0 + >>> distance.minkowski([1, 0, 0], [0, 1, 0], 2) + 1.4142135623730951 + >>> distance.minkowski([1, 0, 0], [0, 1, 0], 3) + 1.2599210498948732 + >>> distance.minkowski([1, 1, 0], [0, 1, 0], 1) + 1.0 + >>> distance.minkowski([1, 1, 0], [0, 1, 0], 2) + 1.0 + >>> distance.minkowski([1, 1, 0], [0, 1, 0], 3) + 1.0 + + """ + u = _asarray(u, order='C') + v = _asarray(v, order='C') + if p <= 0: + raise ValueError("p must be greater than 0") + u_v = u - v + if w is not None: + w = _validate_weights(w) + if p == 1: + root_w = w + elif p == 2: + # better precision and speed + root_w = np.sqrt(w) + elif p == np.inf: + root_w = (w != 0) + else: + root_w = np.power(w, 1/p) + u_v = root_w * u_v + dist = norm(u_v, ord=p, axis=-1) + return dist + + +def euclidean(u, v, w=None): + """ + Computes the Euclidean distance between two arrays. + + The Euclidean distance between arrays `u` and `v`, is defined as + + .. math:: + + {\\|u-v\\|}_2 + + \\left(\\sum{(w_i |(u_i - v_i)|^2)}\\right)^{1/2} + + Parameters + ---------- + u : (..., N) array_like + Input array. + v : (..., N) array_like + Input array. + w : (N,) array_like, optional + The weights for each value in `u` and `v`. Default is None, + which gives each value a weight of 1.0 + + Returns + ------- + euclidean : float or ndarray + The Euclidean distance between vectors `u` and `v`. + + Examples + -------- + >>> from scipy.spatial import distance + >>> distance.euclidean([1, 0, 0], [0, 1, 0]) + 1.4142135623730951 + >>> distance.euclidean([1, 1, 0], [0, 1, 0]) + 1.0 + + """ + return minkowski(u, v, p=2, w=w) + + +def sqeuclidean(u, v, w=None): + """ + Compute the squared Euclidean distance between two arrays. + + The squared Euclidean distance between `u` and `v` is defined as + + .. math:: + + \\sum_i{w_i |u_i - v_i|^2} + + Parameters + ---------- + u : (..., N) array_like + Input array. + v : (..., N) array_like + Input array. + w : (N,) array_like, optional + The weights for each value in `u` and `v`. Default is None, + which gives each value a weight of 1.0 + + Returns + ------- + sqeuclidean : float or ndarray + The squared Euclidean distance between vectors `u` and `v`. + + Examples + -------- + >>> from scipy.spatial import distance + >>> distance.sqeuclidean([1, 0, 0], [0, 1, 0]) + 2.0 + >>> distance.sqeuclidean([1, 1, 0], [0, 1, 0]) + 1.0 + + """ + # Preserve float dtypes, but convert everything else to np.float64 + # for stability. + utype, vtype = None, None + if not (hasattr(u, "dtype") and np.issubdtype(u.dtype, np.inexact)): + utype = np.float64 + if not (hasattr(v, "dtype") and np.issubdtype(v.dtype, np.inexact)): + vtype = np.float64 + + u = _asarray(u, dtype=utype, order='C') + v = _asarray(v, dtype=vtype, order='C') + u_v = u - v + u_v_w = u_v # only want weights applied once + if w is not None: + w = _validate_weights(w) + u_v_w = w * u_v + return np.vecdot(u_v, u_v_w) + + +def correlation(u, v, w=None, centered=True): + """ + Compute the correlation distance between two 1-D arrays. + + The correlation distance between `u` and `v`, is + defined as + + .. math:: + + 1 - \\frac{(u - \\bar{u}) \\cdot (v - \\bar{v})} + {{\\|(u - \\bar{u})\\|}_2 {\\|(v - \\bar{v})\\|}_2} + + where :math:`\\bar{u}` is the mean of the elements of `u` + and :math:`x \\cdot y` is the dot product of :math:`x` and :math:`y`. + + Parameters + ---------- + u : (N,) array_like of floats + Input array. + v : (N,) array_like of floats + Input array. + w : (N,) array_like of floats, optional + The weights for each value in `u` and `v`. Default is None, + which gives each value a weight of 1.0 + centered : bool, optional + If True, `u` and `v` will be centered. Default is True. + + Returns + ------- + correlation : double + The correlation distance between 1-D array `u` and `v`. + + Examples + -------- + Find the correlation between two arrays. + + >>> from scipy.spatial.distance import correlation + >>> correlation([1, 0, 1], [1, 1, 0]) + 1.5 + + Using a weighting array, the correlation can be calculated as: + + >>> correlation([1, 0, 1], [1, 1, 0], w=[0.9, 0.1, 0.1]) + 1.1 + + If centering is not needed, the correlation can be calculated as: + + >>> correlation([1, 0, 1], [1, 1, 0], centered=False) + 0.5 + """ + u = _validate_vector(u) + v = _validate_vector(v) + if np.iscomplexobj(u) or np.iscomplexobj(v): + msg = "`u` and `v` must be real." + raise TypeError(msg) + if w is not None: + w = _validate_weights(w) + w = w / w.sum() + if centered: + if w is not None: + umu = np.dot(u, w) + vmu = np.dot(v, w) + else: + umu = np.mean(u) + vmu = np.mean(v) + u = u - umu + v = v - vmu + if w is not None: + vw = v * w + uw = u * w + else: + vw, uw = v, u + uv = np.dot(u, vw) + uu = np.dot(u, uw) + vv = np.dot(v, vw) + dist = 1.0 - uv / math.sqrt(uu * vv) + # Clip the result to avoid rounding error + return np.clip(dist, 0.0, 2.0) + + +def cosine(u, v, w=None): + """ + Compute the Cosine distance between 1-D arrays. + + The Cosine distance between `u` and `v`, is defined as + + .. math:: + + 1 - \\frac{u \\cdot v} + {\\|u\\|_2 \\|v\\|_2}. + + where :math:`u \\cdot v` is the dot product of :math:`u` and + :math:`v`. + + Parameters + ---------- + u : (N,) array_like of floats + Input array. + v : (N,) array_like of floats + Input array. + w : (N,) array_like of floats, optional + The weights for each value in `u` and `v`. Default is None, + which gives each value a weight of 1.0 + + Returns + ------- + cosine : double + The Cosine distance between vectors `u` and `v`. + + Examples + -------- + >>> from scipy.spatial import distance + >>> distance.cosine([1, 0, 0], [0, 1, 0]) + 1.0 + >>> distance.cosine([100, 0, 0], [0, 1, 0]) + 1.0 + >>> distance.cosine([1, 1, 0], [0, 1, 0]) + 0.29289321881345254 + + """ + # cosine distance is also referred to as 'uncentered correlation', + # or 'reflective correlation' + return correlation(u, v, w=w, centered=False) + + +def hamming(u, v, w=None): + """ + Compute the Hamming distance between two 1-D arrays. + + The Hamming distance between 1-D arrays `u` and `v`, is simply the + proportion of disagreeing components in `u` and `v`. If `u` and `v` are + boolean vectors, the Hamming distance is + + .. math:: + + \\frac{c_{01} + c_{10}}{n} + + where :math:`c_{ij}` is the number of occurrences of + :math:`\\mathtt{u[k]} = i` and :math:`\\mathtt{v[k]} = j` for + :math:`k < n`. + + Parameters + ---------- + u : (N,) array_like + Input array. + v : (N,) array_like + Input array. + w : (N,) array_like, optional + The weights for each value in `u` and `v`. Default is None, + which gives each value a weight of 1.0 + + Returns + ------- + hamming : double + The Hamming distance between vectors `u` and `v`. + + Examples + -------- + >>> from scipy.spatial import distance + >>> distance.hamming([1, 0, 0], [0, 1, 0]) + 0.66666666666666663 + >>> distance.hamming([1, 0, 0], [1, 1, 0]) + 0.33333333333333331 + >>> distance.hamming([1, 0, 0], [2, 0, 0]) + 0.33333333333333331 + >>> distance.hamming([1, 0, 0], [3, 0, 0]) + 0.33333333333333331 + + """ + u = _validate_vector(u) + v = _validate_vector(v) + if u.shape != v.shape: + raise ValueError('The 1d arrays must have equal lengths.') + u_ne_v = u != v + if w is not None: + w = _validate_weights(w) + if w.shape != u.shape: + raise ValueError("'w' should have the same length as 'u' and 'v'.") + w = w / w.sum() + return np.dot(u_ne_v, w) + return np.mean(u_ne_v) + + +def jaccard(u, v, w=None): + r""" + Compute the Jaccard dissimilarity between two boolean vectors. + + Given boolean vectors :math:`u \equiv (u_1, \cdots, u_n)` + and :math:`v \equiv (v_1, \cdots, v_n)` that are not both zero, + their *Jaccard dissimilarity* is defined as ([1]_, p. 26) + + .. math:: + + d_\textrm{jaccard}(u, v) := \frac{c_{10} + c_{01}} + {c_{11} + c_{10} + c_{01}} + + where + + .. math:: + + c_{ij} := \sum_{1 \le k \le n, u_k=i, v_k=j} 1 + + for :math:`i, j \in \{ 0, 1\}`. If :math:`u` and :math:`v` are both zero, + their Jaccard dissimilarity is defined to be zero. [2]_ + + If a (non-negative) weight vector :math:`w \equiv (w_1, \cdots, w_n)` + is supplied, the *weighted Jaccard dissimilarity* is defined similarly + but with :math:`c_{ij}` replaced by + + .. math:: + + \tilde{c}_{ij} := \sum_{1 \le k \le n, u_k=i, v_k=j} w_k + + Parameters + ---------- + u : (N,) array_like of bools + Input vector. + v : (N,) array_like of bools + Input vector. + w : (N,) array_like of floats, optional + Weights for each pair of :math:`(u_k, v_k)`. Default is ``None``, + which gives each pair a weight of ``1.0``. + + Returns + ------- + jaccard : float + The Jaccard dissimilarity between vectors `u` and `v`, optionally + weighted by `w` if supplied. + + Notes + ----- + The Jaccard dissimilarity satisfies the triangle inequality and is + qualified as a metric. [2]_ + + The *Jaccard index*, or *Jaccard similarity coefficient*, is equal to + one minus the Jaccard dissimilarity. [3]_ + + The dissimilarity between general (finite) sets may be computed by + encoding them as boolean vectors and computing the dissimilarity + between the encoded vectors. + For example, subsets :math:`A,B` of :math:`\{ 1, 2, ..., n \}` may be + encoded into boolean vectors :math:`u, v` by setting + :math:`u_k := 1_{k \in A}`, :math:`v_k := 1_{k \in B}` + for :math:`k = 1,2,\cdots,n`. + + .. versionchanged:: 1.2.0 + Previously, if all (positively weighted) elements in `u` and `v` are + zero, the function would return ``nan``. This was changed to return + ``0`` instead. + + .. versionchanged:: 1.15.0 + Non-0/1 numeric input used to produce an ad hoc result. Since 1.15.0, + numeric input is converted to Boolean before computation. + + References + ---------- + .. [1] Kaufman, L. and Rousseeuw, P. J. (1990). "Finding Groups in Data: + An Introduction to Cluster Analysis." John Wiley & Sons, Inc. + .. [2] Kosub, S. (2019). "A note on the triangle inequality for the + Jaccard distance." *Pattern Recognition Letters*, 120:36-38. + .. [3] https://en.wikipedia.org/wiki/Jaccard_index + + Examples + -------- + >>> from scipy.spatial import distance + + Non-zero vectors with no matching 1s have dissimilarity of 1.0: + + >>> distance.jaccard([1, 0, 0], [0, 1, 0]) + 1.0 + + Vectors with some matching 1s have dissimilarity less than 1.0: + + >>> distance.jaccard([1, 0, 0, 0], [1, 1, 1, 0]) + 0.6666666666666666 + + Identical vectors, including zero vectors, have dissimilarity of 0.0: + + >>> distance.jaccard([1, 0, 0], [1, 0, 0]) + 0.0 + >>> distance.jaccard([0, 0, 0], [0, 0, 0]) + 0.0 + + The following example computes the dissimilarity from a confusion matrix + directly by setting the weight vector to the frequency of True Positive, + False Negative, False Positive, and True Negative: + + >>> distance.jaccard([1, 1, 0, 0], [1, 0, 1, 0], [31, 41, 59, 26]) + 0.7633587786259542 # (41+59)/(31+41+59) + + """ + u = _validate_vector(u) + v = _validate_vector(v) + + unequal = np.bitwise_xor(u != 0, v != 0) + nonzero = np.bitwise_or(u != 0, v != 0) + if w is not None: + w = _validate_weights(w) + unequal = w * unequal + nonzero = w * nonzero + a = np.float64(unequal.sum()) + b = np.float64(nonzero.sum()) + return (a / b) if b != 0 else np.float64(0) + + +def seuclidean(u, v, V): + """ + Return the standardized Euclidean distance between two 1-D arrays. + + The standardized Euclidean distance between two n-vectors `u` and `v` is + + .. math:: + + \\sqrt{\\sum\\limits_i \\frac{1}{V_i} \\left(u_i-v_i \\right)^2} + + ``V`` is the variance vector; ``V[I]`` is the variance computed over all the i-th + components of the points. If not passed, it is automatically computed. + + Parameters + ---------- + u : (N,) array_like + Input array. + v : (N,) array_like + Input array. + V : (N,) array_like + `V` is a 1-D array of component variances. It is usually computed + among a larger collection of vectors. + + Returns + ------- + seuclidean : double + The standardized Euclidean distance between vectors `u` and `v`. + + Examples + -------- + >>> from scipy.spatial import distance + >>> distance.seuclidean([1, 0, 0], [0, 1, 0], [0.1, 0.1, 0.1]) + 4.4721359549995796 + >>> distance.seuclidean([1, 0, 0], [0, 1, 0], [1, 0.1, 0.1]) + 3.3166247903553998 + >>> distance.seuclidean([1, 0, 0], [0, 1, 0], [10, 0.1, 0.1]) + 3.1780497164141406 + + """ + u = _validate_vector(u) + v = _validate_vector(v) + V = _validate_vector(V, dtype=np.float64) + if V.shape[0] != u.shape[0] or u.shape[0] != v.shape[0]: + raise TypeError('V must be a 1-D array of the same dimension ' + 'as u and v.') + return euclidean(u, v, w=1/V) + + +def cityblock(u, v, w=None): + """ + Compute the City Block (Manhattan) distance. + + Computes the Manhattan distance between two 1-D arrays `u` and `v`, + which is defined as + + .. math:: + + \\sum_i {\\left| u_i - v_i \\right|}. + + Parameters + ---------- + u : (N,) array_like + Input array. + v : (N,) array_like + Input array. + w : (N,) array_like, optional + The weights for each value in `u` and `v`. Default is None, + which gives each value a weight of 1.0 + + Returns + ------- + cityblock : double + The City Block (Manhattan) distance between vectors `u` and `v`. + + Examples + -------- + >>> from scipy.spatial import distance + >>> distance.cityblock([1, 0, 0], [0, 1, 0]) + 2 + >>> distance.cityblock([1, 0, 0], [0, 2, 0]) + 3 + >>> distance.cityblock([1, 0, 0], [1, 1, 0]) + 1 + + """ + u = _validate_vector(u) + v = _validate_vector(v) + l1_diff = abs(u - v) + if w is not None: + w = _validate_weights(w) + l1_diff = w * l1_diff + return l1_diff.sum() + + +def mahalanobis(u, v, VI): + """ + Compute the Mahalanobis distance between two 1-D arrays. + + The Mahalanobis distance between 1-D arrays `u` and `v`, is defined as + + .. math:: + + \\sqrt{ (u-v) V^{-1} (u-v)^T } + + where ``V`` is the covariance matrix. Note that the argument `VI` + is the inverse of ``V``. + + Parameters + ---------- + u : (N,) array_like + Input array. + v : (N,) array_like + Input array. + VI : array_like + The inverse of the covariance matrix. + + Returns + ------- + mahalanobis : double + The Mahalanobis distance between vectors `u` and `v`. + + Examples + -------- + >>> from scipy.spatial import distance + >>> iv = [[1, 0.5, 0.5], [0.5, 1, 0.5], [0.5, 0.5, 1]] + >>> distance.mahalanobis([1, 0, 0], [0, 1, 0], iv) + 1.0 + >>> distance.mahalanobis([0, 2, 0], [0, 1, 0], iv) + 1.0 + >>> distance.mahalanobis([2, 0, 0], [0, 1, 0], iv) + 1.7320508075688772 + + """ + u = _validate_vector(u) + v = _validate_vector(v) + VI = np.atleast_2d(VI) + delta = u - v + m = np.dot(np.dot(delta, VI), delta) + return np.sqrt(m) + + +def chebyshev(u, v, w=None): + r""" + Compute the Chebyshev distance. + + The *Chebyshev distance* between real vectors + :math:`u \equiv (u_1, \cdots, u_n)` and + :math:`v \equiv (v_1, \cdots, v_n)` is defined as [1]_ + + .. math:: + + d_\textrm{chebyshev}(u,v) := \max_{1 \le i \le n} |u_i-v_i| + + If a (non-negative) weight vector :math:`w \equiv (w_1, \cdots, w_n)` + is supplied, the *weighted Chebyshev distance* is defined to be the + weighted Minkowski distance of infinite order; that is, + + .. math:: + + \begin{align} + d_\textrm{chebyshev}(u,v;w) &:= \lim_{p\rightarrow \infty} + \left( \sum_{i=1}^n w_i | u_i-v_i |^p \right)^\frac{1}{p} \\ + &= \max_{1 \le i \le n} 1_{w_i > 0} | u_i - v_i | + \end{align} + + Parameters + ---------- + u : (N,) array_like of floats + Input vector. + v : (N,) array_like of floats + Input vector. + w : (N,) array_like of floats, optional + Weight vector. Default is ``None``, which gives all pairs + :math:`(u_i, v_i)` the same weight ``1.0``. + + Returns + ------- + chebyshev : float + The Chebyshev distance between vectors `u` and `v`, optionally weighted + by `w`. + + References + ---------- + .. [1] https://en.wikipedia.org/wiki/Chebyshev_distance + + Examples + -------- + >>> from scipy.spatial import distance + >>> distance.chebyshev([1, 0, 0], [0, 1, 0]) + 1 + >>> distance.chebyshev([1, 1, 0], [0, 1, 0]) + 1 + + """ + u = _validate_vector(u) + v = _validate_vector(v) + if w is not None: + w = _validate_weights(w) + return max((w > 0) * abs(u - v)) + return max(abs(u - v)) + + +def braycurtis(u, v, w=None): + """ + Compute the Bray-Curtis distance between two 1-D arrays. + + Bray-Curtis distance is defined as + + .. math:: + + \\sum{|u_i-v_i|} / \\sum{|u_i+v_i|} + + The Bray-Curtis distance is in the range [0, 1] if all coordinates are + positive, and is undefined if the inputs are of length zero. + + Parameters + ---------- + u : (N,) array_like + Input array. + v : (N,) array_like + Input array. + w : (N,) array_like, optional + The weights for each value in `u` and `v`. Default is None, + which gives each value a weight of 1.0 + + Returns + ------- + braycurtis : double + The Bray-Curtis distance between 1-D arrays `u` and `v`. + + Examples + -------- + >>> from scipy.spatial import distance + >>> distance.braycurtis([1, 0, 0], [0, 1, 0]) + 1.0 + >>> distance.braycurtis([1, 1, 0], [0, 1, 0]) + 0.33333333333333331 + + """ + u = _validate_vector(u) + v = _validate_vector(v, dtype=np.float64) + l1_diff = abs(u - v) + l1_sum = abs(u + v) + if w is not None: + w = _validate_weights(w) + l1_diff = w * l1_diff + l1_sum = w * l1_sum + return l1_diff.sum() / l1_sum.sum() + + +def canberra(u, v, w=None): + """ + Compute the Canberra distance between two 1-D arrays. + + The Canberra distance is defined as + + .. math:: + + d(u,v) = \\sum_i \\frac{|u_i-v_i|} + {|u_i|+|v_i|}. + + Parameters + ---------- + u : (N,) array_like + Input array. + v : (N,) array_like + Input array. + w : (N,) array_like, optional + The weights for each value in `u` and `v`. Default is None, + which gives each value a weight of 1.0 + + Returns + ------- + canberra : double + The Canberra distance between vectors `u` and `v`. + + Notes + ----- + When ``u[i]`` and ``v[i]`` are 0 for given i, then the fraction 0/0 = 0 is + used in the calculation. + + Examples + -------- + >>> from scipy.spatial import distance + >>> distance.canberra([1, 0, 0], [0, 1, 0]) + 2.0 + >>> distance.canberra([1, 1, 0], [0, 1, 0]) + 1.0 + + """ + u = _validate_vector(u) + v = _validate_vector(v, dtype=np.float64) + if w is not None: + w = _validate_weights(w) + with np.errstate(invalid='ignore'): + abs_uv = abs(u - v) + abs_u = abs(u) + abs_v = abs(v) + d = abs_uv / (abs_u + abs_v) + if w is not None: + d = w * d + d = np.nansum(d) + return d + + +def jensenshannon(p, q, base=None, *, axis=0, keepdims=False): + """ + Compute the Jensen-Shannon distance (metric) between + two probability arrays. This is the square root + of the Jensen-Shannon divergence. + + The Jensen-Shannon distance between two probability + vectors `p` and `q` is defined as, + + .. math:: + + \\sqrt{\\frac{D(p \\parallel m) + D(q \\parallel m)}{2}} + + where :math:`m` is the pointwise mean of :math:`p` and :math:`q` + and :math:`D` is the Kullback-Leibler divergence. + + This routine will normalize `p` and `q` if they don't sum to 1.0. + + Parameters + ---------- + p : (N,) array_like + left probability vector + q : (N,) array_like + right probability vector + base : double, optional + the base of the logarithm used to compute the output + if not given, then the routine uses the default base of + scipy.stats.entropy. + axis : int, optional + Axis along which the Jensen-Shannon distances are computed. The default + is 0. + + .. versionadded:: 1.7.0 + keepdims : bool, optional + If this is set to `True`, the reduced axes are left in the + result as dimensions with size one. With this option, + the result will broadcast correctly against the input array. + Default is False. + + .. versionadded:: 1.7.0 + + Returns + ------- + js : double or ndarray + The Jensen-Shannon distances between `p` and `q` along the `axis`. + + Notes + ----- + + .. versionadded:: 1.2.0 + + Examples + -------- + >>> from scipy.spatial import distance + >>> import numpy as np + >>> distance.jensenshannon([1.0, 0.0, 0.0], [0.0, 1.0, 0.0], 2.0) + 1.0 + >>> distance.jensenshannon([1.0, 0.0], [0.5, 0.5]) + 0.46450140402245893 + >>> distance.jensenshannon([1.0, 0.0, 0.0], [1.0, 0.0, 0.0]) + 0.0 + >>> a = np.array([[1, 2, 3, 4], + ... [5, 6, 7, 8], + ... [9, 10, 11, 12]]) + >>> b = np.array([[13, 14, 15, 16], + ... [17, 18, 19, 20], + ... [21, 22, 23, 24]]) + >>> distance.jensenshannon(a, b, axis=0) + array([0.1954288, 0.1447697, 0.1138377, 0.0927636]) + >>> distance.jensenshannon(a, b, axis=1) + array([0.1402339, 0.0399106, 0.0201815]) + + """ + p = np.asarray(p) + q = np.asarray(q) + p = p / np.sum(p, axis=axis, keepdims=True) + q = q / np.sum(q, axis=axis, keepdims=True) + m = (p + q) / 2.0 + left = rel_entr(p, m) + right = rel_entr(q, m) + left_sum = np.sum(left, axis=axis, keepdims=keepdims) + right_sum = np.sum(right, axis=axis, keepdims=keepdims) + js = left_sum + right_sum + if base is not None: + js /= np.log(base) + return np.sqrt(js / 2.0) + + +def yule(u, v, w=None): + """ + Compute the Yule dissimilarity between two boolean 1-D arrays. + + The Yule dissimilarity is defined as + + .. math:: + + \\frac{R}{c_{TT} * c_{FF} + \\frac{R}{2}} + + where :math:`c_{ij}` is the number of occurrences of + :math:`\\mathtt{u[k]} = i` and :math:`\\mathtt{v[k]} = j` for + :math:`k < n` and :math:`R = 2.0 * c_{TF} * c_{FT}`. + + Parameters + ---------- + u : (N,) array_like, bool + Input array. + v : (N,) array_like, bool + Input array. + w : (N,) array_like, optional + The weights for each value in `u` and `v`. Default is None, + which gives each value a weight of 1.0 + + Returns + ------- + yule : double + The Yule dissimilarity between vectors `u` and `v`. + + Examples + -------- + >>> from scipy.spatial import distance + >>> distance.yule([1, 0, 0], [0, 1, 0]) + 2.0 + >>> distance.yule([1, 1, 0], [0, 1, 0]) + 0.0 + + """ + u = _validate_vector(u) + v = _validate_vector(v) + if w is not None: + w = _validate_weights(w) + (nff, nft, ntf, ntt) = _nbool_correspond_all(u, v, w=w) + half_R = ntf * nft + if half_R == 0: + return 0.0 + else: + return float(2.0 * half_R / (ntt * nff + half_R)) + + +def dice(u, v, w=None): + """ + Compute the Dice dissimilarity between two boolean 1-D arrays. + + The Dice dissimilarity between `u` and `v`, is + + .. math:: + + \\frac{c_{TF} + c_{FT}} + {2c_{TT} + c_{FT} + c_{TF}} + + where :math:`c_{ij}` is the number of occurrences of + :math:`\\mathtt{u[k]} = i` and :math:`\\mathtt{v[k]} = j` for + :math:`k < n`. + + Parameters + ---------- + u : (N,) array_like, bool + Input 1-D array. + v : (N,) array_like, bool + Input 1-D array. + w : (N,) array_like, optional + The weights for each value in `u` and `v`. Default is None, + which gives each value a weight of 1.0 + + Returns + ------- + dice : double + The Dice dissimilarity between 1-D arrays `u` and `v`. + + Notes + ----- + This function computes the Dice dissimilarity index. To compute the + Dice similarity index, convert one to the other with similarity = + 1 - dissimilarity. + + Examples + -------- + >>> from scipy.spatial import distance + >>> distance.dice([1, 0, 0], [0, 1, 0]) + 1.0 + >>> distance.dice([1, 0, 0], [1, 1, 0]) + 0.3333333333333333 + >>> distance.dice([1, 0, 0], [2, 0, 0]) + -0.3333333333333333 + + """ + u = _validate_vector(u) + v = _validate_vector(v) + if w is not None: + w = _validate_weights(w) + if u.dtype == v.dtype == bool and w is None: + ntt = (u & v).sum() + else: + dtype = np.result_type(int, u.dtype, v.dtype) + u = u.astype(dtype) + v = v.astype(dtype) + if w is None: + ntt = (u * v).sum() + else: + ntt = (u * v * w).sum() + (nft, ntf) = _nbool_correspond_ft_tf(u, v, w=w) + return float((ntf + nft) / np.array(2.0 * ntt + ntf + nft)) + + +def rogerstanimoto(u, v, w=None): + """ + Compute the Rogers-Tanimoto dissimilarity between two boolean 1-D arrays. + + The Rogers-Tanimoto dissimilarity between two boolean 1-D arrays + `u` and `v`, is defined as + + .. math:: + \\frac{R} + {c_{TT} + c_{FF} + R} + + where :math:`c_{ij}` is the number of occurrences of + :math:`\\mathtt{u[k]} = i` and :math:`\\mathtt{v[k]} = j` for + :math:`k < n` and :math:`R = 2(c_{TF} + c_{FT})`. + + Parameters + ---------- + u : (N,) array_like, bool + Input array. + v : (N,) array_like, bool + Input array. + w : (N,) array_like, optional + The weights for each value in `u` and `v`. Default is None, + which gives each value a weight of 1.0 + + Returns + ------- + rogerstanimoto : double + The Rogers-Tanimoto dissimilarity between vectors + `u` and `v`. + + Examples + -------- + >>> from scipy.spatial import distance + >>> distance.rogerstanimoto([1, 0, 0], [0, 1, 0]) + 0.8 + >>> distance.rogerstanimoto([1, 0, 0], [1, 1, 0]) + 0.5 + >>> distance.rogerstanimoto([1, 0, 0], [2, 0, 0]) + -1.0 + + """ + u = _validate_vector(u) + v = _validate_vector(v) + if w is not None: + w = _validate_weights(w) + (nff, nft, ntf, ntt) = _nbool_correspond_all(u, v, w=w) + return float(2.0 * (ntf + nft)) / float(ntt + nff + (2.0 * (ntf + nft))) + + +def russellrao(u, v, w=None): + """ + Compute the Russell-Rao dissimilarity between two boolean 1-D arrays. + + The Russell-Rao dissimilarity between two boolean 1-D arrays, `u` and + `v`, is defined as + + .. math:: + + \\frac{n - c_{TT}} + {n} + + where :math:`c_{ij}` is the number of occurrences of + :math:`\\mathtt{u[k]} = i` and :math:`\\mathtt{v[k]} = j` for + :math:`k < n`. + + Parameters + ---------- + u : (N,) array_like, bool + Input array. + v : (N,) array_like, bool + Input array. + w : (N,) array_like, optional + The weights for each value in `u` and `v`. Default is None, + which gives each value a weight of 1.0 + + Returns + ------- + russellrao : double + The Russell-Rao dissimilarity between vectors `u` and `v`. + + Examples + -------- + >>> from scipy.spatial import distance + >>> distance.russellrao([1, 0, 0], [0, 1, 0]) + 1.0 + >>> distance.russellrao([1, 0, 0], [1, 1, 0]) + 0.6666666666666666 + >>> distance.russellrao([1, 0, 0], [2, 0, 0]) + 0.3333333333333333 + + """ + u = _validate_vector(u) + v = _validate_vector(v) + if u.dtype == v.dtype == bool and w is None: + ntt = (u & v).sum() + n = float(len(u)) + elif w is None: + ntt = (u * v).sum() + n = float(len(u)) + else: + w = _validate_weights(w) + ntt = (u * v * w).sum() + n = w.sum() + return float(n - ntt) / n + + +def sokalsneath(u, v, w=None): + """ + Compute the Sokal-Sneath dissimilarity between two boolean 1-D arrays. + + The Sokal-Sneath dissimilarity between `u` and `v`, + + .. math:: + + \\frac{R} + {c_{TT} + R} + + where :math:`c_{ij}` is the number of occurrences of + :math:`\\mathtt{u[k]} = i` and :math:`\\mathtt{v[k]} = j` for + :math:`k < n` and :math:`R = 2(c_{TF} + c_{FT})`. + + Parameters + ---------- + u : (N,) array_like, bool + Input array. + v : (N,) array_like, bool + Input array. + w : (N,) array_like, optional + The weights for each value in `u` and `v`. Default is None, + which gives each value a weight of 1.0 + + Returns + ------- + sokalsneath : double + The Sokal-Sneath dissimilarity between vectors `u` and `v`. + + Examples + -------- + >>> from scipy.spatial import distance + >>> distance.sokalsneath([1, 0, 0], [0, 1, 0]) + 1.0 + >>> distance.sokalsneath([1, 0, 0], [1, 1, 0]) + 0.66666666666666663 + >>> distance.sokalsneath([1, 0, 0], [2, 1, 0]) + 0.0 + >>> distance.sokalsneath([1, 0, 0], [3, 1, 0]) + -2.0 + + """ + u = _validate_vector(u) + v = _validate_vector(v) + if u.dtype == v.dtype == bool and w is None: + ntt = (u & v).sum() + elif w is None: + ntt = (u * v).sum() + else: + w = _validate_weights(w) + ntt = (u * v * w).sum() + (nft, ntf) = _nbool_correspond_ft_tf(u, v, w=w) + denom = np.array(ntt + 2.0 * (ntf + nft)) + if not denom.any(): + raise ValueError('Sokal-Sneath dissimilarity is not defined for ' + 'vectors that are entirely false.') + return float(2.0 * (ntf + nft)) / denom + + +_convert_to_double = partial(_convert_to_type, out_type=np.float64) +_convert_to_bool = partial(_convert_to_type, out_type=bool) + +# adding python-only wrappers to _distance_wrap module +_distance_wrap.pdist_correlation_double_wrap = _correlation_pdist_wrap +_distance_wrap.cdist_correlation_double_wrap = _correlation_cdist_wrap + + +@dataclasses.dataclass(frozen=True) +class CDistMetricWrapper: + metric_name: str + + def __call__(self, XA, XB, *, out=None, **kwargs): + XA = np.ascontiguousarray(XA) + XB = np.ascontiguousarray(XB) + mA, n = XA.shape + mB, _ = XB.shape + metric_name = self.metric_name + metric_info = _METRICS[metric_name] + XA, XB, typ, kwargs = _validate_cdist_input( + XA, XB, mA, mB, n, metric_info, **kwargs) + + w = kwargs.pop('w', None) + if w is not None: + metric = metric_info.dist_func + return _cdist_callable( + XA, XB, metric=metric, out=out, w=w, **kwargs) + + dm = _prepare_out_argument(out, np.float64, (mA, mB)) + # get cdist wrapper + cdist_fn = getattr(_distance_wrap, f'cdist_{metric_name}_{typ}_wrap') + cdist_fn(XA, XB, dm, **kwargs) + return dm + + +@dataclasses.dataclass(frozen=True) +class PDistMetricWrapper: + metric_name: str + + def __call__(self, X, *, out=None, **kwargs): + X = np.ascontiguousarray(X) + m, n = X.shape + metric_name = self.metric_name + metric_info = _METRICS[metric_name] + X, typ, kwargs = _validate_pdist_input( + X, m, n, metric_info, **kwargs) + out_size = (m * (m - 1)) // 2 + w = kwargs.pop('w', None) + if w is not None: + metric = metric_info.dist_func + return _pdist_callable( + X, metric=metric, out=out, w=w, **kwargs) + + dm = _prepare_out_argument(out, np.float64, (out_size,)) + # get pdist wrapper + pdist_fn = getattr(_distance_wrap, f'pdist_{metric_name}_{typ}_wrap') + pdist_fn(X, dm, **kwargs) + return dm + + +@dataclasses.dataclass(frozen=True) +class MetricInfo: + # Name of python distance function + canonical_name: str + # All aliases, including canonical_name + aka: set[str] + # unvectorized distance function + dist_func: Callable + # Optimized cdist function + cdist_func: Callable + # Optimized pdist function + pdist_func: Callable + # function that checks kwargs and computes default values: + # f(X, m, n, **kwargs) + validator: Callable | None = None + # list of supported types: + # X (pdist) and XA (cdist) are used to choose the type. if there is no + # match the first type is used. Default double + types: list[str] = dataclasses.field(default_factory=lambda: ['double']) + # true if out array must be C-contiguous + requires_contiguous_out: bool = True + + +# Registry of implemented metrics: +_METRIC_INFOS = [ + MetricInfo( + canonical_name='braycurtis', + aka={'braycurtis'}, + dist_func=braycurtis, + cdist_func=_distance_pybind.cdist_braycurtis, + pdist_func=_distance_pybind.pdist_braycurtis, + ), + MetricInfo( + canonical_name='canberra', + aka={'canberra'}, + dist_func=canberra, + cdist_func=_distance_pybind.cdist_canberra, + pdist_func=_distance_pybind.pdist_canberra, + ), + MetricInfo( + canonical_name='chebyshev', + aka={'chebychev', 'chebyshev', 'cheby', 'cheb', 'ch'}, + dist_func=chebyshev, + cdist_func=_distance_pybind.cdist_chebyshev, + pdist_func=_distance_pybind.pdist_chebyshev, + ), + MetricInfo( + canonical_name='cityblock', + aka={'cityblock', 'cblock', 'cb', 'c'}, + dist_func=cityblock, + cdist_func=_distance_pybind.cdist_cityblock, + pdist_func=_distance_pybind.pdist_cityblock, + ), + MetricInfo( + canonical_name='correlation', + aka={'correlation', 'co'}, + dist_func=correlation, + cdist_func=CDistMetricWrapper('correlation'), + pdist_func=PDistMetricWrapper('correlation'), + ), + MetricInfo( + canonical_name='cosine', + aka={'cosine', 'cos'}, + dist_func=cosine, + cdist_func=CDistMetricWrapper('cosine'), + pdist_func=PDistMetricWrapper('cosine'), + ), + MetricInfo( + canonical_name='dice', + aka={'dice'}, + types=['bool'], + dist_func=dice, + cdist_func=_distance_pybind.cdist_dice, + pdist_func=_distance_pybind.pdist_dice, + ), + MetricInfo( + canonical_name='euclidean', + aka={'euclidean', 'euclid', 'eu', 'e'}, + dist_func=euclidean, + cdist_func=_distance_pybind.cdist_euclidean, + pdist_func=_distance_pybind.pdist_euclidean, + ), + MetricInfo( + canonical_name='hamming', + aka={'matching', 'hamming', 'hamm', 'ha', 'h'}, + types=['double', 'bool'], + validator=_validate_hamming_kwargs, + dist_func=hamming, + cdist_func=_distance_pybind.cdist_hamming, + pdist_func=_distance_pybind.pdist_hamming, + ), + MetricInfo( + canonical_name='jaccard', + aka={'jaccard', 'jacc', 'ja', 'j'}, + types=['double', 'bool'], + dist_func=jaccard, + cdist_func=_distance_pybind.cdist_jaccard, + pdist_func=_distance_pybind.pdist_jaccard, + ), + MetricInfo( + canonical_name='jensenshannon', + aka={'jensenshannon', 'js'}, + dist_func=jensenshannon, + cdist_func=CDistMetricWrapper('jensenshannon'), + pdist_func=PDistMetricWrapper('jensenshannon'), + ), + MetricInfo( + canonical_name='mahalanobis', + aka={'mahalanobis', 'mahal', 'mah'}, + validator=_validate_mahalanobis_kwargs, + dist_func=mahalanobis, + cdist_func=CDistMetricWrapper('mahalanobis'), + pdist_func=PDistMetricWrapper('mahalanobis'), + ), + MetricInfo( + canonical_name='minkowski', + aka={'minkowski', 'mi', 'm', 'pnorm'}, + validator=_validate_minkowski_kwargs, + dist_func=minkowski, + cdist_func=_distance_pybind.cdist_minkowski, + pdist_func=_distance_pybind.pdist_minkowski, + ), + MetricInfo( + canonical_name='rogerstanimoto', + aka={'rogerstanimoto'}, + types=['bool'], + dist_func=rogerstanimoto, + cdist_func=_distance_pybind.cdist_rogerstanimoto, + pdist_func=_distance_pybind.pdist_rogerstanimoto, + ), + MetricInfo( + canonical_name='russellrao', + aka={'russellrao'}, + types=['bool'], + dist_func=russellrao, + cdist_func=_distance_pybind.cdist_russellrao, + pdist_func=_distance_pybind.pdist_russellrao, + ), + MetricInfo( + canonical_name='seuclidean', + aka={'seuclidean', 'se', 's'}, + validator=_validate_seuclidean_kwargs, + dist_func=seuclidean, + cdist_func=CDistMetricWrapper('seuclidean'), + pdist_func=PDistMetricWrapper('seuclidean'), + ), + MetricInfo( + canonical_name='sokalsneath', + aka={'sokalsneath'}, + types=['bool'], + dist_func=sokalsneath, + cdist_func=_distance_pybind.cdist_sokalsneath, + pdist_func=_distance_pybind.pdist_sokalsneath, + ), + MetricInfo( + canonical_name='sqeuclidean', + aka={'sqeuclidean', 'sqe', 'sqeuclid'}, + dist_func=sqeuclidean, + cdist_func=_distance_pybind.cdist_sqeuclidean, + pdist_func=_distance_pybind.pdist_sqeuclidean, + ), + MetricInfo( + canonical_name='yule', + aka={'yule'}, + types=['bool'], + dist_func=yule, + cdist_func=_distance_pybind.cdist_yule, + pdist_func=_distance_pybind.pdist_yule, + ), +] + +_METRICS = {info.canonical_name: info for info in _METRIC_INFOS} +_METRIC_ALIAS = {alias: info + for info in _METRIC_INFOS + for alias in info.aka} + +_METRICS_NAMES = list(_METRICS.keys()) + +_TEST_METRICS = {'test_' + info.canonical_name: info for info in _METRIC_INFOS} + + +def pdist(X, metric='euclidean', *, out=None, **kwargs): + """ + Pairwise distances between observations in n-dimensional space. + + See Notes for common calling conventions. + + Parameters + ---------- + X : array_like + An m by n array of m original observations in an + n-dimensional space. + metric : str or function, optional + The distance metric to use. The distance function can + be 'braycurtis', 'canberra', 'chebyshev', 'cityblock', + 'correlation', 'cosine', 'dice', 'euclidean', 'hamming', + 'jaccard', 'jensenshannon', + 'mahalanobis', 'matching', 'minkowski', 'rogerstanimoto', + 'russellrao', 'seuclidean', 'sokalsneath', + 'sqeuclidean', 'yule'. + out : ndarray, optional + The output array. + If not None, condensed distance matrix Y is stored in this array. + **kwargs : dict, optional + Extra arguments to `metric`: refer to each metric documentation for a + list of all possible arguments. + + Some possible arguments: + + p : scalar + The p-norm to apply for Minkowski, weighted and unweighted. + Default: 2. + + w : ndarray + The weight vector for metrics that support weights (e.g., Minkowski). + + V : ndarray + The variance vector for standardized Euclidean. + Default: var(X, axis=0, ddof=1) + + VI : ndarray + The inverse of the covariance matrix for Mahalanobis. + Default: inv(cov(X.T)).T + + Returns + ------- + Y : ndarray + Returns a condensed distance matrix Y. For each :math:`i` and :math:`j` + (where :math:`i 0` (note + that this is only a quasi-metric if :math:`0 < p < 1`). + + 3. ``Y = pdist(X, 'cityblock')`` + + Computes the city block or Manhattan distance between the + points. + + 4. ``Y = pdist(X, 'seuclidean', V=None)`` + + Computes the standardized Euclidean distance. The standardized + Euclidean distance between two n-vectors ``u`` and ``v`` is + + .. math:: + + \\sqrt{\\sum {(u_i-v_i)^2 / V[x_i]}} + + + V is the variance vector; V[i] is the variance computed over all + the i'th components of the points. If not passed, it is + automatically computed. + + 5. ``Y = pdist(X, 'sqeuclidean')`` + + Computes the squared Euclidean distance :math:`\\|u-v\\|_2^2` between + the vectors. + + 6. ``Y = pdist(X, 'cosine')`` + + Computes the cosine distance between vectors u and v, + + .. math:: + + 1 - \\frac{u \\cdot v} + {{\\|u\\|}_2 {\\|v\\|}_2} + + where :math:`\\|*\\|_2` is the 2-norm of its argument ``*``, and + :math:`u \\cdot v` is the dot product of ``u`` and ``v``. + + 7. ``Y = pdist(X, 'correlation')`` + + Computes the correlation distance between vectors u and v. This is + + .. math:: + + 1 - \\frac{(u - \\bar{u}) \\cdot (v - \\bar{v})} + {{\\|(u - \\bar{u})\\|}_2 {\\|(v - \\bar{v})\\|}_2} + + where :math:`\\bar{v}` is the mean of the elements of vector v, + and :math:`x \\cdot y` is the dot product of :math:`x` and :math:`y`. + + 8. ``Y = pdist(X, 'hamming')`` + + Computes the normalized Hamming distance, or the proportion of + those vector elements between two n-vectors ``u`` and ``v`` + which disagree. To save memory, the matrix ``X`` can be of type + boolean. + + 9. ``Y = pdist(X, 'jaccard')`` + + Computes the Jaccard distance between the points. Given two + vectors, ``u`` and ``v``, the Jaccard distance is the + proportion of those elements ``u[i]`` and ``v[i]`` that + disagree. + + 10. ``Y = pdist(X, 'jensenshannon')`` + + Computes the Jensen-Shannon distance between two probability arrays. + Given two probability vectors, :math:`p` and :math:`q`, the + Jensen-Shannon distance is + + .. math:: + + \\sqrt{\\frac{D(p \\parallel m) + D(q \\parallel m)}{2}} + + where :math:`m` is the pointwise mean of :math:`p` and :math:`q` + and :math:`D` is the Kullback-Leibler divergence. + + 11. ``Y = pdist(X, 'chebyshev')`` + + Computes the Chebyshev distance between the points. The + Chebyshev distance between two n-vectors ``u`` and ``v`` is the + maximum norm-1 distance between their respective elements. More + precisely, the distance is given by + + .. math:: + + d(u,v) = \\max_i {|u_i-v_i|} + + 12. ``Y = pdist(X, 'canberra')`` + + Computes the Canberra distance between the points. The + Canberra distance between two points ``u`` and ``v`` is + + .. math:: + + d(u,v) = \\sum_i \\frac{|u_i-v_i|} + {|u_i|+|v_i|} + + + 13. ``Y = pdist(X, 'braycurtis')`` + + Computes the Bray-Curtis distance between the points. The + Bray-Curtis distance between two points ``u`` and ``v`` is + + + .. math:: + + d(u,v) = \\frac{\\sum_i {|u_i-v_i|}} + {\\sum_i {|u_i+v_i|}} + + 14. ``Y = pdist(X, 'mahalanobis', VI=None)`` + + Computes the Mahalanobis distance between the points. The + Mahalanobis distance between two points ``u`` and ``v`` is + :math:`\\sqrt{(u-v)(1/V)(u-v)^T}` where :math:`(1/V)` (the ``VI`` + variable) is the inverse covariance. If ``VI`` is not None, + ``VI`` will be used as the inverse covariance matrix. + + 15. ``Y = pdist(X, 'yule')`` + + Computes the Yule distance between each pair of boolean + vectors. (see yule function documentation) + + 16. ``Y = pdist(X, 'matching')`` + + Synonym for 'hamming'. + + 17. ``Y = pdist(X, 'dice')`` + + Computes the Dice distance between each pair of boolean + vectors. (see dice function documentation) + + + 18. ``Y = pdist(X, 'rogerstanimoto')`` + + Computes the Rogers-Tanimoto distance between each pair of + boolean vectors. (see rogerstanimoto function documentation) + + 19. ``Y = pdist(X, 'russellrao')`` + + Computes the Russell-Rao distance between each pair of + boolean vectors. (see russellrao function documentation) + + 20. ``Y = pdist(X, 'sokalsneath')`` + + Computes the Sokal-Sneath distance between each pair of + boolean vectors. (see sokalsneath function documentation) + + 21. ``Y = pdist(X, f)`` + + Computes the distance between all pairs of vectors in X + using the user supplied 2-arity function f. For example, + Euclidean distance between the vectors could be computed + as follows:: + + dm = pdist(X, lambda u, v: np.sqrt(((u-v)**2).sum())) + + Note that you should avoid passing a reference to one of + the distance functions defined in this library. For example,:: + + dm = pdist(X, sokalsneath) + + would calculate the pair-wise distances between the vectors in + X using the Python function sokalsneath. This would result in + sokalsneath being called :math:`{n \\choose 2}` times, which + is inefficient. Instead, the optimized C version is more + efficient, and we call it using the following syntax.:: + + dm = pdist(X, 'sokalsneath') + + Examples + -------- + >>> import numpy as np + >>> from scipy.spatial.distance import pdist + + ``x`` is an array of five points in three-dimensional space. + + >>> x = np.array([[2, 0, 2], [2, 2, 3], [-2, 4, 5], [0, 1, 9], [2, 2, 4]]) + + ``pdist(x)`` with no additional arguments computes the 10 pairwise + Euclidean distances: + + >>> pdist(x) + array([2.23606798, 6.40312424, 7.34846923, 2.82842712, 4.89897949, + 6.40312424, 1. , 5.38516481, 4.58257569, 5.47722558]) + + The following computes the pairwise Minkowski distances with ``p = 3.5``: + + >>> pdist(x, metric='minkowski', p=3.5) + array([2.04898923, 5.1154929 , 7.02700737, 2.43802731, 4.19042714, + 6.03956994, 1. , 4.45128103, 4.10636143, 5.0619695 ]) + + The pairwise city block or Manhattan distances: + + >>> pdist(x, metric='cityblock') + array([ 3., 11., 10., 4., 8., 9., 1., 9., 7., 8.]) + + """ + # You can also call this as: + # Y = pdist(X, 'test_abc') + # where 'abc' is the metric being tested. This computes the distance + # between all pairs of vectors in X using the distance metric 'abc' but + # with a more succinct, verifiable, but less efficient implementation. + + X = _asarray(X) + if X.ndim != 2: + raise ValueError('A 2-dimensional array must be passed. ' + f'(Shape was {X.shape}).') + + n = X.shape[0] + return xpx.lazy_apply(_np_pdist, X, out, + # lazy_apply doesn't support Array kwargs + kwargs.pop('w', None), + kwargs.pop('V', None), + kwargs.pop('VI', None), + # See src/distance_pybind.cpp::pdist + shape=((n * (n - 1)) // 2, ), dtype=X.dtype, + as_numpy=True, metric=metric, **kwargs) + + +def _np_pdist(X, out, w, V, VI, metric='euclidean', **kwargs): + + X = _asarray_validated(X, sparse_ok=False, objects_ok=True, mask_ok=True, + check_finite=False) + m, n = X.shape + + if w is not None: + kwargs["w"] = w + if V is not None: + kwargs["V"] = V + if VI is not None: + kwargs["VI"] = VI + + if callable(metric): + mstr = getattr(metric, '__name__', 'UnknownCustomMetric') + metric_info = _METRIC_ALIAS.get(mstr, None) + + if metric_info is not None: + X, typ, kwargs = _validate_pdist_input( + X, m, n, metric_info, **kwargs) + + return _pdist_callable(X, metric=metric, out=out, **kwargs) + elif isinstance(metric, str): + mstr = metric.lower() + metric_info = _METRIC_ALIAS.get(mstr, None) + + if metric_info is not None: + pdist_fn = metric_info.pdist_func + return pdist_fn(X, out=out, **kwargs) + elif mstr.startswith("test_"): + metric_info = _TEST_METRICS.get(mstr, None) + if metric_info is None: + raise ValueError(f'Unknown "Test" Distance Metric: {mstr[5:]}') + X, typ, kwargs = _validate_pdist_input( + X, m, n, metric_info, **kwargs) + return _pdist_callable( + X, metric=metric_info.dist_func, out=out, **kwargs) + else: + raise ValueError(f'Unknown Distance Metric: {mstr}') + else: + raise TypeError('2nd argument metric must be a string identifier ' + 'or a function.') + + +def squareform(X, force="no", checks=True): + """ + Convert a vector-form distance vector to a square-form distance + matrix, and vice-versa. + + Parameters + ---------- + X : array_like + Either a condensed or redundant distance matrix. + force : str, optional + As with MATLAB(TM), if force is equal to ``'tovector'`` or + ``'tomatrix'``, the input will be treated as a distance matrix or + distance vector respectively. + checks : bool, optional + If set to False, no checks will be made for matrix + symmetry nor zero diagonals. This is useful if it is known that + ``X - X.T1`` is small and ``diag(X)`` is close to zero. + These values are ignored any way so they do not disrupt the + squareform transformation. + + Returns + ------- + Y : ndarray + If a condensed distance matrix is passed, a redundant one is + returned, or if a redundant one is passed, a condensed distance + matrix is returned. + + Notes + ----- + 1. ``v = squareform(X)`` + + Given a square n-by-n symmetric distance matrix ``X``, + ``v = squareform(X)`` returns an ``n * (n-1) / 2`` + (i.e. binomial coefficient n choose 2) sized vector `v` + where :math:`v[{n \\choose 2} - {n-i \\choose 2} + (j-i-1)]` + is the distance between distinct points ``i`` and ``j``. + If ``X`` is non-square or asymmetric, an error is raised. + + 2. ``X = squareform(v)`` + + Given an ``n * (n-1) / 2`` sized vector ``v`` + for some integer ``n >= 1`` encoding distances as described, + ``X = squareform(v)`` returns an n-by-n distance matrix ``X``. + The ``X[i, j]`` and ``X[j, i]`` values are set to + :math:`v[{n \\choose 2} - {n-i \\choose 2} + (j-i-1)]` + and all diagonal elements are zero. + + In SciPy 0.19.0, ``squareform`` stopped casting all input types to + float64, and started returning arrays of the same dtype as the input. + + Examples + -------- + >>> import numpy as np + >>> from scipy.spatial.distance import pdist, squareform + + ``x`` is an array of five points in three-dimensional space. + + >>> x = np.array([[2, 0, 2], [2, 2, 3], [-2, 4, 5], [0, 1, 9], [2, 2, 4]]) + + ``pdist(x)`` computes the Euclidean distances between each pair of + points in ``x``. The distances are returned in a one-dimensional + array with length ``5*(5 - 1)/2 = 10``. + + >>> distvec = pdist(x) + >>> distvec + array([2.23606798, 6.40312424, 7.34846923, 2.82842712, 4.89897949, + 6.40312424, 1. , 5.38516481, 4.58257569, 5.47722558]) + + ``squareform(distvec)`` returns the 5x5 distance matrix. + + >>> m = squareform(distvec) + >>> m + array([[0. , 2.23606798, 6.40312424, 7.34846923, 2.82842712], + [2.23606798, 0. , 4.89897949, 6.40312424, 1. ], + [6.40312424, 4.89897949, 0. , 5.38516481, 4.58257569], + [7.34846923, 6.40312424, 5.38516481, 0. , 5.47722558], + [2.82842712, 1. , 4.58257569, 5.47722558, 0. ]]) + + When given a square distance matrix ``m``, ``squareform(m)`` returns + the one-dimensional condensed distance vector associated with the + matrix. In this case, we recover ``distvec``. + + >>> squareform(m) + array([2.23606798, 6.40312424, 7.34846923, 2.82842712, 4.89897949, + 6.40312424, 1. , 5.38516481, 4.58257569, 5.47722558]) + """ + X = np.ascontiguousarray(X) + + s = X.shape + + if force.lower() == 'tomatrix': + if len(s) != 1: + raise ValueError("Forcing 'tomatrix' but input X is not a " + "distance vector.") + elif force.lower() == 'tovector': + if len(s) != 2: + raise ValueError("Forcing 'tovector' but input X is not a " + "distance matrix.") + + # X = squareform(v) + if len(s) == 1: + if s[0] == 0: + return np.zeros((1, 1), dtype=X.dtype) + + # Grab the closest value to the square root of the number + # of elements times 2 to see if the number of elements + # is indeed a binomial coefficient. + d = int(np.ceil(np.sqrt(s[0] * 2))) + + # Check that v is of valid dimensions. + if d * (d - 1) != s[0] * 2: + raise ValueError('Incompatible vector size. It must be a binomial ' + 'coefficient n choose 2 for some integer n >= 2.') + + # Allocate memory for the distance matrix. + M = np.zeros((d, d), dtype=X.dtype) + + # Since the C code does not support striding using strides. + # The dimensions are used instead. + X = _copy_array_if_base_present(X) + + # Fill in the values of the distance matrix. + _distance_wrap.to_squareform_from_vector_wrap(M, X) + + # Return the distance matrix. + return M + elif len(s) == 2: + if s[0] != s[1]: + raise ValueError('The matrix argument must be square.') + if checks: + is_valid_dm(X, throw=True, name='X') + + # One-side of the dimensions is set here. + d = s[0] + + if d <= 1: + return np.array([], dtype=X.dtype) + + # Create a vector. + v = np.zeros((d * (d - 1)) // 2, dtype=X.dtype) + + # Since the C code does not support striding using strides. + # The dimensions are used instead. + X = _copy_array_if_base_present(X) + + # Convert the vector to squareform. + _distance_wrap.to_vector_from_squareform_wrap(X, v) + return v + else: + raise ValueError("The first argument must be one or two dimensional " + f"array. A {len(s)}-dimensional array is not permitted") + + +def is_valid_dm(D, tol=0.0, throw=False, name="D", warning=False): + """ + Return True if input array satisfies basic distance matrix properties + (symmetry and zero diagonal). + + This function checks whether the input is a 2-dimensional square NumPy array + with a zero diagonal and symmetry within a specified tolerance. These are + necessary properties for a distance matrix but not sufficient -- in particular, + this function does **not** check the triangle inequality, which is required + for a true metric distance matrix. + + The triangle inequality states that for any three points ``i``, ``j``, and ``k``: + ``D[i,k] <= D[i,j] + D[j,k]`` + + Parameters + ---------- + D : array_like + The candidate object to test for basic distance matrix properties. + tol : float, optional + The distance matrix is considered symmetric if the absolute difference + between entries ``ij`` and ``ji`` is less than or equal to `tol`. The same + tolerance is used to determine whether diagonal entries are effectively zero. + throw : bool, optional + If True, raises an exception when the input is invalid. + name : str, optional + The name of the variable to check. This is used in exception messages when + `throw` is True to identify the offending variable. + warning : bool, optional + If True, a warning message is raised instead of throwing an exception. + + Returns + ------- + valid : bool + True if the input satisfies the symmetry and zero-diagonal conditions. + + Raises + ------ + ValueError + If `throw` is True and `D` is not a valid distance matrix. + UserWarning + If `warning` is True and `D` is not a valid distance matrix. + + Notes + ----- + This function does not check the triangle inequality, which is required for + a complete validation of a metric distance matrix. Only structural properties + (symmetry and zero diagonal) are verified. Small numerical deviations from symmetry + or exact zero diagonal are tolerated within the `tol` parameter. + + Examples + -------- + >>> import numpy as np + >>> from scipy.spatial.distance import is_valid_dm + + This matrix is a valid distance matrix. + + >>> d = np.array([[0.0, 1.1, 1.2, 1.3], + ... [1.1, 0.0, 1.0, 1.4], + ... [1.2, 1.0, 0.0, 1.5], + ... [1.3, 1.4, 1.5, 0.0]]) + >>> is_valid_dm(d) + True + + In the following examples, the input is not a valid distance matrix. + + Not square: + + >>> is_valid_dm([[0, 2, 2], [2, 0, 2]]) + False + + Nonzero diagonal element: + + >>> is_valid_dm([[0, 1, 1], [1, 2, 3], [1, 3, 0]]) + False + + Not symmetric: + + >>> is_valid_dm([[0, 1, 3], [2, 0, 1], [3, 1, 0]]) + False + + """ + D = np.asarray(D, order='c') + valid = True + try: + s = D.shape + if len(D.shape) != 2: + if name: + raise ValueError(f"Distance matrix '{name}' must have shape=2 " + "(i.e. be two-dimensional).") + else: + raise ValueError('Distance matrix must have shape=2 (i.e. ' + 'be two-dimensional).') + if tol == 0.0: + if not (D == D.T).all(): + if name: + raise ValueError(f"Distance matrix '{name}' must be symmetric.") + else: + raise ValueError('Distance matrix must be symmetric.') + if not (D[range(0, s[0]), range(0, s[0])] == 0).all(): + if name: + raise ValueError(f"Distance matrix '{name}' diagonal must be zero.") + else: + raise ValueError('Distance matrix diagonal must be zero.') + else: + if not (D - D.T <= tol).all(): + if name: + raise ValueError(f'Distance matrix \'{name}\' must be ' + f'symmetric within tolerance {tol:5.5f}.') + else: + raise ValueError('Distance matrix must be symmetric within ' + f'tolerance {tol:5.5f}.') + if not (D[range(0, s[0]), range(0, s[0])] <= tol).all(): + if name: + raise ValueError(f'Distance matrix \'{name}\' diagonal must be ' + f'close to zero within tolerance {tol:5.5f}.') + else: + raise ValueError(('Distance matrix \'{}\' diagonal must be close ' + 'to zero within tolerance {:5.5f}.').format(*tol)) + except Exception as e: + if throw: + raise + if warning: + warnings.warn(str(e), stacklevel=2) + valid = False + return valid + + +def is_valid_y(y, warning=False, throw=False, name=None): + """ + Return True if the input array is a valid condensed distance matrix. + + Condensed distance matrices must be 1-dimensional numpy arrays. + Their length must be a binomial coefficient :math:`{n \\choose 2}` + for some positive integer n. + + Parameters + ---------- + y : array_like + The condensed distance matrix. + warning : bool, optional + Invokes a warning if the variable passed is not a valid + condensed distance matrix. The warning message explains why + the distance matrix is not valid. `name` is used when + referencing the offending variable. + throw : bool, optional + Throws an exception if the variable passed is not a valid + condensed distance matrix. + name : str, optional + Used when referencing the offending variable in the + warning or exception message. + + Returns + ------- + bool + True if the input array is a valid condensed distance matrix, + False otherwise. + + Examples + -------- + >>> from scipy.spatial.distance import is_valid_y + + This vector is a valid condensed distance matrix. The length is 6, + which corresponds to ``n = 4``, since ``4*(4 - 1)/2`` is 6. + + >>> v = [1.0, 1.2, 1.0, 0.5, 1.3, 0.9] + >>> is_valid_y(v) + True + + An input vector with length, say, 7, is not a valid condensed distance + matrix. + + >>> is_valid_y([1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7]) + False + + """ + y = _asarray(y) + name_str = f"'{name}' " if name else "" + try: + if len(y.shape) != 1: + raise ValueError(f"Condensed distance matrix {name_str}must " + "have shape=1 (i.e. be one-dimensional).") + n = y.shape[0] + d = int(np.ceil(np.sqrt(n * 2))) + if (d * (d - 1) / 2) != n: + raise ValueError(f"Length n of condensed distance matrix {name_str}" + "must be a binomial coefficient, i.e. " + "there must be a k such that (k \\choose 2)=n)!") + except Exception as e: + if throw: + raise + if warning: + warnings.warn(str(e), stacklevel=2) + return False + return True + + +def num_obs_dm(d): + """ + Return the number of original observations that correspond to a + square, redundant distance matrix. + + Parameters + ---------- + d : array_like + The target distance matrix. + + Returns + ------- + num_obs_dm : int + The number of observations in the redundant distance matrix. + + Examples + -------- + Find the number of original observations corresponding + to a square redundant distance matrix d. + + >>> from scipy.spatial.distance import num_obs_dm + >>> d = [[0, 100, 200], [100, 0, 150], [200, 150, 0]] + >>> num_obs_dm(d) + 3 + """ + d = np.asarray(d, order='c') + is_valid_dm(d, tol=np.inf, throw=True, name='d') + return d.shape[0] + + +def num_obs_y(Y): + """ + Return the number of original observations that correspond to a + condensed distance matrix. + + Parameters + ---------- + Y : array_like + Condensed distance matrix. + + Returns + ------- + n : int + The number of observations in the condensed distance matrix `Y`. + + Examples + -------- + Find the number of original observations corresponding to a + condensed distance matrix Y. + + >>> from scipy.spatial.distance import num_obs_y + >>> Y = [1, 2, 3.5, 7, 10, 4] + >>> num_obs_y(Y) + 4 + """ + Y = _asarray(Y) + is_valid_y(Y, throw=True, name='Y') + k = Y.shape[0] + if k == 0: + raise ValueError("The number of observations cannot be determined on " + "an empty distance matrix.") + d = int(np.ceil(np.sqrt(k * 2))) + if (d * (d - 1) / 2) != k: + raise ValueError("Invalid condensed distance matrix passed. Must be " + "some k where k=(n choose 2) for some n >= 2.") + return d + + +def _prepare_out_argument(out, dtype, expected_shape): + if out is None: + return np.empty(expected_shape, dtype=dtype) + + if out.shape != expected_shape: + raise ValueError("Output array has incorrect shape.") + if not out.flags.c_contiguous: + raise ValueError("Output array must be C-contiguous.") + if out.dtype != np.float64: + raise ValueError("Output array must be double type.") + return out + + +def _pdist_callable(X, *, out, metric, **kwargs): + n = X.shape[0] + out_size = (n * (n - 1)) // 2 + dm = _prepare_out_argument(out, np.float64, (out_size,)) + k = 0 + for i in range(X.shape[0] - 1): + for j in range(i + 1, X.shape[0]): + dm[k] = metric(X[i], X[j], **kwargs) + k += 1 + return dm + + +def _cdist_callable(XA, XB, *, out, metric, **kwargs): + mA = XA.shape[0] + mB = XB.shape[0] + dm = _prepare_out_argument(out, np.float64, (mA, mB)) + for i in range(mA): + for j in range(mB): + dm[i, j] = metric(XA[i], XB[j], **kwargs) + return dm + + +def cdist(XA, XB, metric='euclidean', *, out=None, **kwargs): + """ + Compute distance between each pair of the two collections of inputs. + + See Notes for common calling conventions. + + Parameters + ---------- + XA : array_like + An :math:`m_A` by :math:`n` array of :math:`m_A` + original observations in an :math:`n`-dimensional space. + Inputs are converted to float type. + XB : array_like + An :math:`m_B` by :math:`n` array of :math:`m_B` + original observations in an :math:`n`-dimensional space. + Inputs are converted to float type. + metric : str or callable, optional + The distance metric to use. If a string, the distance function can be + 'braycurtis', 'canberra', 'chebyshev', 'cityblock', 'correlation', + 'cosine', 'dice', 'euclidean', 'hamming', 'jaccard', 'jensenshannon', + 'mahalanobis', 'matching', 'minkowski', 'rogerstanimoto', 'russellrao', + 'seuclidean', 'sokalsneath', 'sqeuclidean', 'yule'. + out : ndarray, optional + Output array in which to place the result. It must have the same + shape as the expected output. + **kwargs : dict, optional + Extra arguments to `metric`: refer to each metric documentation for a + list of all possible arguments. + + Some possible arguments: + + p : scalar + The p-norm to apply for Minkowski, weighted and unweighted. + Default: 2. + + w : array_like + The weight vector for metrics that support weights (e.g., Minkowski). + + V : array_like + The variance vector for standardized Euclidean. + Default: var(vstack([XA, XB]), axis=0, ddof=1) + + VI : array_like + The inverse of the covariance matrix for Mahalanobis. + Default: inv(cov(vstack([XA, XB].T))).T + + out : ndarray + The output array + If not None, the distance matrix Y is stored in this array. + + Returns + ------- + Y : ndarray + A :math:`m_A` by :math:`m_B` distance matrix is returned. + For each :math:`i` and :math:`j`, the metric + ``dist(u=XA[i], v=XB[j])`` is computed and stored in the + :math:`ij` th entry. + + Raises + ------ + ValueError + An exception is thrown if `XA` and `XB` do not have + the same number of columns. + + Notes + ----- + The following are common calling conventions: + + 1. ``Y = cdist(XA, XB, 'euclidean')`` + + Computes the distance between :math:`m` points using + Euclidean distance (2-norm) as the distance metric between the + points. The points are arranged as :math:`m` + :math:`n`-dimensional row vectors in the matrix X. + + 2. ``Y = cdist(XA, XB, 'minkowski', p=2.)`` + + Computes the distances using the Minkowski distance + :math:`\\|u-v\\|_p` (:math:`p`-norm) where :math:`p > 0` (note + that this is only a quasi-metric if :math:`0 < p < 1`). + + 3. ``Y = cdist(XA, XB, 'cityblock')`` + + Computes the city block or Manhattan distance between the + points. + + 4. ``Y = cdist(XA, XB, 'seuclidean', V=None)`` + + Computes the standardized Euclidean distance. The standardized + Euclidean distance between two n-vectors ``u`` and ``v`` is + + .. math:: + + \\sqrt{\\sum {(u_i-v_i)^2 / V[x_i]}}. + + V is the variance vector; V[i] is the variance computed over all + the i'th components of the points. If not passed, it is + automatically computed. + + 5. ``Y = cdist(XA, XB, 'sqeuclidean')`` + + Computes the squared Euclidean distance :math:`\\|u-v\\|_2^2` between + the vectors. + + 6. ``Y = cdist(XA, XB, 'cosine')`` + + Computes the cosine distance between vectors u and v, + + .. math:: + + 1 - \\frac{u \\cdot v} + {{\\|u\\|}_2 {\\|v\\|}_2} + + where :math:`\\|*\\|_2` is the 2-norm of its argument ``*``, and + :math:`u \\cdot v` is the dot product of :math:`u` and :math:`v`. + + 7. ``Y = cdist(XA, XB, 'correlation')`` + + Computes the correlation distance between vectors u and v. This is + + .. math:: + + 1 - \\frac{(u - \\bar{u}) \\cdot (v - \\bar{v})} + {{\\|(u - \\bar{u})\\|}_2 {\\|(v - \\bar{v})\\|}_2} + + where :math:`\\bar{v}` is the mean of the elements of vector v, + and :math:`x \\cdot y` is the dot product of :math:`x` and :math:`y`. + + + 8. ``Y = cdist(XA, XB, 'hamming')`` + + Computes the normalized Hamming distance, or the proportion of + those vector elements between two n-vectors ``u`` and ``v`` + which disagree. To save memory, the matrix ``X`` can be of type + boolean. + + 9. ``Y = cdist(XA, XB, 'jaccard')`` + + Computes the Jaccard distance between the points. Given two + vectors, ``u`` and ``v``, the Jaccard distance is the + proportion of those elements ``u[i]`` and ``v[i]`` that + disagree where at least one of them is non-zero. + + 10. ``Y = cdist(XA, XB, 'jensenshannon')`` + + Computes the Jensen-Shannon distance between two probability arrays. + Given two probability vectors, :math:`p` and :math:`q`, the + Jensen-Shannon distance is + + .. math:: + + \\sqrt{\\frac{D(p \\parallel m) + D(q \\parallel m)}{2}} + + where :math:`m` is the pointwise mean of :math:`p` and :math:`q` + and :math:`D` is the Kullback-Leibler divergence. + + 11. ``Y = cdist(XA, XB, 'chebyshev')`` + + Computes the Chebyshev distance between the points. The + Chebyshev distance between two n-vectors ``u`` and ``v`` is the + maximum norm-1 distance between their respective elements. More + precisely, the distance is given by + + .. math:: + + d(u,v) = \\max_i {|u_i-v_i|}. + + 12. ``Y = cdist(XA, XB, 'canberra')`` + + Computes the Canberra distance between the points. The + Canberra distance between two points ``u`` and ``v`` is + + .. math:: + + d(u,v) = \\sum_i \\frac{|u_i-v_i|} + {|u_i|+|v_i|}. + + 13. ``Y = cdist(XA, XB, 'braycurtis')`` + + Computes the Bray-Curtis distance between the points. The + Bray-Curtis distance between two points ``u`` and ``v`` is + + + .. math:: + + d(u,v) = \\frac{\\sum_i (|u_i-v_i|)} + {\\sum_i (|u_i+v_i|)} + + 14. ``Y = cdist(XA, XB, 'mahalanobis', VI=None)`` + + Computes the Mahalanobis distance between the points. The + Mahalanobis distance between two points ``u`` and ``v`` is + :math:`\\sqrt{(u-v)(1/V)(u-v)^T}` where :math:`(1/V)` (the ``VI`` + variable) is the inverse covariance. If ``VI`` is not None, + ``VI`` will be used as the inverse covariance matrix. + + 15. ``Y = cdist(XA, XB, 'yule')`` + + Computes the Yule distance between the boolean + vectors. (see `yule` function documentation) + + 16. ``Y = cdist(XA, XB, 'matching')`` + + Synonym for 'hamming'. + + 17. ``Y = cdist(XA, XB, 'dice')`` + + Computes the Dice distance between the boolean vectors. (see + `dice` function documentation). + + 18. ``Y = cdist(XA, XB, 'rogerstanimoto')`` + + Computes the Rogers-Tanimoto distance between the boolean + vectors. (see `rogerstanimoto` function documentation) + + 19. ``Y = cdist(XA, XB, 'russellrao')`` + + Computes the Russell-Rao distance between the boolean + vectors. (see `russellrao` function documentation) + + 20. ``Y = cdist(XA, XB, 'sokalsneath')`` + + Computes the Sokal-Sneath distance between the vectors. (see + `sokalsneath` function documentation) + + 21. ``Y = cdist(XA, XB, f)`` + + Computes the distance between all pairs of vectors in X + using the user supplied 2-arity function f. For example, + Euclidean distance between the vectors could be computed + as follows:: + + dm = cdist(XA, XB, lambda u, v: np.sqrt(((u-v)**2).sum())) + + Note that you should avoid passing a reference to one of + the distance functions defined in this library. For example,:: + + dm = cdist(XA, XB, sokalsneath) + + would calculate the pair-wise distances between the vectors in + X using the Python function `sokalsneath`. This would result in + sokalsneath being called :math:`{n \\choose 2}` times, which + is inefficient. Instead, the optimized C version is more + efficient, and we call it using the following syntax:: + + dm = cdist(XA, XB, 'sokalsneath') + + Examples + -------- + Find the Euclidean distances between four 2-D coordinates: + + >>> from scipy.spatial import distance + >>> import numpy as np + >>> coords = [(35.0456, -85.2672), + ... (35.1174, -89.9711), + ... (35.9728, -83.9422), + ... (36.1667, -86.7833)] + >>> distance.cdist(coords, coords, 'euclidean') + array([[ 0. , 4.7044, 1.6172, 1.8856], + [ 4.7044, 0. , 6.0893, 3.3561], + [ 1.6172, 6.0893, 0. , 2.8477], + [ 1.8856, 3.3561, 2.8477, 0. ]]) + + + Find the Manhattan distance from a 3-D point to the corners of the unit + cube: + + >>> a = np.array([[0, 0, 0], + ... [0, 0, 1], + ... [0, 1, 0], + ... [0, 1, 1], + ... [1, 0, 0], + ... [1, 0, 1], + ... [1, 1, 0], + ... [1, 1, 1]]) + >>> b = np.array([[ 0.1, 0.2, 0.4]]) + >>> distance.cdist(a, b, 'cityblock') + array([[ 0.7], + [ 0.9], + [ 1.3], + [ 1.5], + [ 1.5], + [ 1.7], + [ 2.1], + [ 2.3]]) + + """ + # You can also call this as: + # Y = cdist(XA, XB, 'test_abc') + # where 'abc' is the metric being tested. This computes the distance + # between all pairs of vectors in XA and XB using the distance metric 'abc' + # but with a more succinct, verifiable, but less efficient implementation. + + XA = np.asarray(XA) + XB = np.asarray(XB) + + s = XA.shape + sB = XB.shape + + if len(s) != 2: + raise ValueError('XA must be a 2-dimensional array.') + if len(sB) != 2: + raise ValueError('XB must be a 2-dimensional array.') + if s[1] != sB[1]: + raise ValueError('XA and XB must have the same number of columns ' + '(i.e. feature dimension.)') + + mA = s[0] + mB = sB[0] + n = s[1] + + if callable(metric): + mstr = getattr(metric, '__name__', 'Unknown') + metric_info = _METRIC_ALIAS.get(mstr, None) + if metric_info is not None: + XA, XB, typ, kwargs = _validate_cdist_input( + XA, XB, mA, mB, n, metric_info, **kwargs) + return _cdist_callable(XA, XB, metric=metric, out=out, **kwargs) + elif isinstance(metric, str): + mstr = metric.lower() + metric_info = _METRIC_ALIAS.get(mstr, None) + if metric_info is not None: + cdist_fn = metric_info.cdist_func + return cdist_fn(XA, XB, out=out, **kwargs) + elif mstr.startswith("test_"): + metric_info = _TEST_METRICS.get(mstr, None) + if metric_info is None: + raise ValueError(f'Unknown "Test" Distance Metric: {mstr[5:]}') + XA, XB, typ, kwargs = _validate_cdist_input( + XA, XB, mA, mB, n, metric_info, **kwargs) + return _cdist_callable( + XA, XB, metric=metric_info.dist_func, out=out, **kwargs) + else: + raise ValueError(f'Unknown Distance Metric: {mstr}') + else: + raise TypeError('2nd argument metric must be a string identifier ' + 'or a function.') diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/distance.pyi b/venv/lib/python3.12/site-packages/scipy/spatial/distance.pyi new file mode 100644 index 0000000000000000000000000000000000000000..4846d37e3add466f738f56ab088e9091118e1ca1 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/distance.pyi @@ -0,0 +1,200 @@ +from typing import (overload, Any, SupportsFloat, Literal, Protocol, SupportsIndex) + +import numpy as np +from numpy.typing import ArrayLike, NDArray + +# Anything that can be parsed by `np.float64.__init__` and is thus +# compatible with `ndarray.__setitem__` (for a float64 array) +_FloatValue = None | str | bytes | SupportsFloat | SupportsIndex + +class _MetricCallback1(Protocol): + def __call__( + self, __XA: NDArray[Any], __XB: NDArray[Any] + ) -> _FloatValue: ... + +class _MetricCallback2(Protocol): + def __call__( + self, __XA: NDArray[Any], __XB: NDArray[Any], **kwargs: Any + ) -> _FloatValue: ... + +# TODO: Use a single protocol with a parameter specification variable +# once available (PEP 612) +_MetricCallback = _MetricCallback1 | _MetricCallback2 + +_MetricKind = Literal[ + 'braycurtis', + 'canberra', + 'chebychev', 'chebyshev', 'cheby', 'cheb', 'ch', + 'cityblock', 'cblock', 'cb', 'c', + 'correlation', 'co', + 'cosine', 'cos', + 'dice', + 'euclidean', 'euclid', 'eu', 'e', + 'hamming', 'hamm', 'ha', 'h', + 'minkowski', 'mi', 'm', 'pnorm', + 'jaccard', 'jacc', 'ja', 'j', + 'jensenshannon', 'js', + 'mahalanobis', 'mahal', 'mah', + 'rogerstanimoto', + 'russellrao', + 'seuclidean', 'se', 's', + 'sokalsneath', + 'sqeuclidean', 'sqe', 'sqeuclid', + 'yule', +] + +# Function annotations + +def braycurtis( + u: ArrayLike, v: ArrayLike, w: ArrayLike | None = ... +) -> np.float64: ... + +def canberra( + u: ArrayLike, v: ArrayLike, w: ArrayLike | None = ... +) -> np.float64: ... + +# TODO: Add `metric`-specific overloads +# Returns a float64 or float128 array, depending on the input dtype +@overload +def cdist( + XA: ArrayLike, + XB: ArrayLike, + metric: _MetricKind = ..., + *, + out: None | NDArray[np.floating[Any]] = ..., + p: float = ..., + w: ArrayLike | None = ..., + V: ArrayLike | None = ..., + VI: ArrayLike | None = ..., +) -> NDArray[np.floating[Any]]: ... +@overload +def cdist( + XA: ArrayLike, + XB: ArrayLike, + metric: _MetricCallback, + *, + out: None | NDArray[np.floating[Any]] = ..., + **kwargs: Any, +) -> NDArray[np.floating[Any]]: ... + +# TODO: Wait for dtype support; the return type is +# dependent on the input arrays dtype +def chebyshev( + u: ArrayLike, v: ArrayLike, w: ArrayLike | None = ... +) -> Any: ... + +# TODO: Wait for dtype support; the return type is +# dependent on the input arrays dtype +def cityblock( + u: ArrayLike, v: ArrayLike, w: ArrayLike | None = ... +) -> Any: ... + +def correlation( + u: ArrayLike, v: ArrayLike, w: ArrayLike | None = ..., centered: bool = ... +) -> np.float64: ... + +def cosine( + u: ArrayLike, v: ArrayLike, w: ArrayLike | None = ... +) -> np.float64: ... + +def dice( + u: ArrayLike, v: ArrayLike, w: ArrayLike | None = ... +) -> float: ... + +def directed_hausdorff( + u: ArrayLike, v: ArrayLike, seed: int | None = ... +) -> tuple[float, int, int]: ... + +def euclidean( + u: ArrayLike, v: ArrayLike, w: ArrayLike | None = ... +) -> float: ... + +def hamming( + u: ArrayLike, v: ArrayLike, w: ArrayLike | None = ... +) -> np.float64: ... + +def is_valid_dm( + D: ArrayLike, + tol: float = ..., + throw: bool = ..., + name: str | None = ..., + warning: bool = ..., +) -> bool: ... + +def is_valid_y( + y: ArrayLike, + warning: bool = ..., + throw: bool = ..., + name: str | None = ..., +) -> bool: ... + +def jaccard( + u: ArrayLike, v: ArrayLike, w: ArrayLike | None = ... +) -> np.float64: ... + +def jensenshannon( + p: ArrayLike, q: ArrayLike, base: float | None = ... +) -> np.float64: ... + +def mahalanobis( + u: ArrayLike, v: ArrayLike, VI: ArrayLike +) -> np.float64: ... + +def minkowski( + u: ArrayLike, v: ArrayLike, p: float = ..., w: ArrayLike | None = ... +) -> float: ... + +def num_obs_dm(d: ArrayLike) -> int: ... + +def num_obs_y(Y: ArrayLike) -> int: ... + +# TODO: Add `metric`-specific overloads +@overload +def pdist( + X: ArrayLike, + metric: _MetricKind = ..., + *, + out: None | NDArray[np.floating[Any]] = ..., + p: float = ..., + w: ArrayLike | None = ..., + V: ArrayLike | None = ..., + VI: ArrayLike | None = ..., +) -> NDArray[np.floating[Any]]: ... +@overload +def pdist( + X: ArrayLike, + metric: _MetricCallback, + *, + out: None | NDArray[np.floating[Any]] = ..., + **kwargs: Any, +) -> NDArray[np.floating[Any]]: ... + +def seuclidean( + u: ArrayLike, v: ArrayLike, V: ArrayLike +) -> float: ... + +def sokalsneath( + u: ArrayLike, v: ArrayLike, w: ArrayLike | None = ... +) -> np.float64: ... + +def sqeuclidean( + u: ArrayLike, v: ArrayLike, w: ArrayLike | None = ... +) -> np.float64: ... + +def squareform( + X: ArrayLike, + force: Literal["no", "tomatrix", "tovector"] = ..., + checks: bool = ..., +) -> NDArray[Any]: ... + +def rogerstanimoto( + u: ArrayLike, v: ArrayLike, w: ArrayLike | None = ... +) -> float: ... + +def russellrao( + u: ArrayLike, v: ArrayLike, w: ArrayLike | None = ... +) -> float: ... + +def yule( + u: ArrayLike, v: ArrayLike, w: ArrayLike | None = ... +) -> float: ... diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/kdtree.py b/venv/lib/python3.12/site-packages/scipy/spatial/kdtree.py new file mode 100644 index 0000000000000000000000000000000000000000..512205ddc15dc7d427f8df908ee346895295b674 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/kdtree.py @@ -0,0 +1,25 @@ +# This file is not meant for public use and will be removed in SciPy v2.0.0. +# Use the `scipy.spatial` namespace for importing the functions +# included below. + +from scipy._lib.deprecation import _sub_module_deprecation + + +__all__ = [ # noqa: F822 + 'KDTree', + 'Rectangle', + 'cKDTree', + 'distance_matrix', + 'minkowski_distance', + 'minkowski_distance_p', +] + + +def __dir__(): + return __all__ + + +def __getattr__(name): + return _sub_module_deprecation(sub_package="spatial", module="kdtree", + private_modules=["_kdtree"], all=__all__, + attribute=name) diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/qhull.py b/venv/lib/python3.12/site-packages/scipy/spatial/qhull.py new file mode 100644 index 0000000000000000000000000000000000000000..a8d51bf239bfe48077e66a36bcbf59f6dbadaf95 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/qhull.py @@ -0,0 +1,25 @@ +# This file is not meant for public use and will be removed in SciPy v2.0.0. +# Use the `scipy.spatial` namespace for importing the functions +# included below. + +from scipy._lib.deprecation import _sub_module_deprecation + + +__all__ = [ # noqa: F822 + 'ConvexHull', + 'Delaunay', + 'HalfspaceIntersection', + 'QhullError', + 'Voronoi', + 'tsearch', +] + + +def __dir__(): + return __all__ + + +def __getattr__(name): + return _sub_module_deprecation(sub_package="spatial", module="qhull", + private_modules=["_qhull"], all=__all__, + attribute=name) diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/qhull_src/COPYING_QHULL.txt b/venv/lib/python3.12/site-packages/scipy/spatial/qhull_src/COPYING_QHULL.txt new file mode 100644 index 0000000000000000000000000000000000000000..122a00a4fa9933794601dca8bff713b4ea1459dc --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/qhull_src/COPYING_QHULL.txt @@ -0,0 +1,39 @@ + Qhull, Copyright (c) 1993-2020 + + C.B. Barber + Arlington, MA + + and + + The National Science and Technology Research Center for + Computation and Visualization of Geometric Structures + (The Geometry Center) + University of Minnesota + + email: qhull@qhull.org + +This software includes Qhull from C.B. Barber and The Geometry Center. +Files derived from Qhull 1.0 are copyrighted by the Geometry Center. The +remaining files are copyrighted by C.B. Barber. Qhull is free software +and may be obtained via http from www.qhull.org. It may be freely copied, +modified, and redistributed under the following conditions: + +1. All copyright notices must remain intact in all files. + +2. A copy of this text file must be distributed along with any copies + of Qhull that you redistribute; this includes copies that you have + modified, or copies of programs or other software products that + include Qhull. + +3. If you modify Qhull, you must include a notice giving the + name of the person performing the modification, the date of + modification, and the reason for such modification. + +4. When distributing modified versions of Qhull, or other software + products that include Qhull, you must provide notice that the original + source code may be obtained as noted above. + +5. There is no warranty or other guarantee of fitness for Qhull, it is + provided solely "as is". Bug reports or fixes may be sent to + qhull_bug@qhull.org; the authors may or may not act on them as + they desire. diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/tests/__init__.py b/venv/lib/python3.12/site-packages/scipy/spatial/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/tests/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/spatial/tests/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4d8cb43b36ded579c011e2a1c7b9770b7896dabc Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/spatial/tests/__pycache__/__init__.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/tests/__pycache__/test__plotutils.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/spatial/tests/__pycache__/test__plotutils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5844562d0646c4221d56f074e02e19ef28f8dedd Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/spatial/tests/__pycache__/test__plotutils.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/tests/__pycache__/test__procrustes.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/spatial/tests/__pycache__/test__procrustes.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d982611cc3d812ba2d5a08f060e2809ed57afae0 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/spatial/tests/__pycache__/test__procrustes.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/tests/__pycache__/test_distance.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/spatial/tests/__pycache__/test_distance.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9a31e1a9cb10889384740770e1d0fdc44ecda793 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/tests/__pycache__/test_distance.cpython-312.pyc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fb08c5ae8e5d0b211100172718ecc91edd63019933b4f6d2e93feeab5fdb3200 +size 129889 diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/tests/__pycache__/test_hausdorff.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/spatial/tests/__pycache__/test_hausdorff.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f16ecd5d58ad182a7189dce74ba6f13b152ade84 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/spatial/tests/__pycache__/test_hausdorff.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/tests/__pycache__/test_kdtree.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/spatial/tests/__pycache__/test_kdtree.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f1876d146d000fef7f69e96a7a0f0ca6c877292c --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/tests/__pycache__/test_kdtree.cpython-312.pyc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:92b69ac224bdb92b6e920c50b018a9675da9abcfab850c475527020b7f069c00 +size 103445 diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/tests/__pycache__/test_qhull.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/spatial/tests/__pycache__/test_qhull.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2ead2b9c8e7656ed8c77339650369c952ab47f0a Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/spatial/tests/__pycache__/test_qhull.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/tests/__pycache__/test_slerp.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/spatial/tests/__pycache__/test_slerp.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ed6b9453c3f1669464f24215dc6a92c1b1c9d523 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/spatial/tests/__pycache__/test_slerp.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/tests/__pycache__/test_spherical_voronoi.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/spatial/tests/__pycache__/test_spherical_voronoi.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0d43d8d6630e300e47b68bf4d03ecc0d4bc58dbc Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/spatial/tests/__pycache__/test_spherical_voronoi.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/cdist-X1.txt b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/cdist-X1.txt new file mode 100644 index 0000000000000000000000000000000000000000..833d5bdf2a344f585c5f34faa3e22716b1aa363c --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/cdist-X1.txt @@ -0,0 +1,10 @@ +1.147593763490969421e-01 8.926156143344999849e-01 1.437758624645746330e-02 1.803435962879929022e-02 5.533046214065578949e-01 5.554315640747428118e-01 4.497546637814608950e-02 4.438089247948049376e-01 7.984582810220538507e-01 2.752880789161644692e-01 1.344667112315823809e-01 9.230479561452992199e-01 6.040471462941819913e-01 3.797251652770228247e-01 4.316042735592399149e-01 5.312356915348823705e-01 4.348143005129563310e-01 3.111531488508799681e-01 9.531194313908697424e-04 8.212995023500069269e-02 6.689953269869852726e-01 9.914864535288493430e-01 8.037556036341153565e-01 +9.608925123801395074e-01 2.974451233678974127e-01 9.001110330654185088e-01 5.824163330415995654e-01 7.308574928293812834e-01 2.276154562412870952e-01 7.306791076039623745e-01 8.677244866905511333e-01 9.160806456176984192e-01 6.157216959991280714e-01 5.149053524695440531e-01 3.056427344890983999e-01 9.790557366933895223e-01 4.484995861076724877e-01 4.776550391081165747e-01 7.210436977670631187e-01 9.136399501661039979e-01 4.260275733550000776e-02 5.943900041968954717e-01 3.864571606342745991e-01 9.442027665110838131e-01 4.779949058608601309e-02 6.107551944250865228e-01 +3.297286578103622023e-01 5.980207401936733502e-01 3.673301293561567205e-01 2.585830520887681949e-01 4.660558746104259686e-01 6.083795956610364986e-01 4.535206368070313632e-01 6.873989778785424276e-01 5.130152688495458468e-01 7.665877846542720198e-01 3.444402973525138023e-01 3.583658123644906102e-02 7.924818220986856732e-01 8.746685720522412444e-01 3.010105569182431884e-01 6.012239357385538163e-01 6.233737362204671006e-01 4.830438698668915176e-01 2.317286885842551047e-02 7.585989958123050547e-01 7.108257632278830451e-01 1.551024884178199281e-01 2.665485998155288083e-01 +2.456278068903017253e-02 4.148739837711815648e-01 1.986372227934196655e-01 6.920408530298168825e-01 1.003067576685774398e-01 7.421560456480125190e-01 1.808453980608998313e-01 4.251297882537475870e-01 6.773002683522370004e-01 4.084108792570182445e-01 7.462888013191590897e-01 8.069930220529277776e-01 9.211110587681808903e-01 4.141491046181076108e-01 7.486318689260342829e-01 9.515405507589296263e-01 4.634288892577109742e-03 8.027593488166355762e-01 3.010346805217798405e-01 8.663248877242523127e-01 2.479968181181605447e-01 5.619851096054278017e-01 3.903886764590250857e-01 +7.122019976035700584e-01 6.188878051047785878e-01 7.290897087051201320e-01 6.334802157757637442e-01 5.523084734954342156e-01 5.614937129563645213e-01 2.496741051791574462e-01 5.972227939599233926e-01 1.786590597761109622e-01 2.609525984850900038e-01 7.210438943286010538e-01 2.211429064605652250e-01 9.140497572472672250e-02 1.430242193668443962e-01 7.856446942916397447e-01 4.635256358156553125e-01 5.278744289813760426e-01 3.702808015407184072e-01 5.527073830480792038e-01 6.370732917599846168e-01 9.953487928925482953e-01 3.021789770611936765e-01 3.354901923998221402e-02 +6.509638560895427695e-01 8.387598220902757751e-01 7.761375971745763103e-01 1.481627639227802717e-01 3.529474982902305324e-01 4.883093646287851586e-01 9.652923033658690199e-01 9.500680513565308294e-01 3.061885005078281985e-01 7.271902818906019750e-01 2.358962978196710303e-03 7.359889703223099211e-01 8.988893768074724955e-01 4.135279653937307121e-02 8.516441856688283796e-01 4.889597623270667270e-01 5.575909822114655245e-01 9.010853652261575641e-01 2.912844516556202246e-01 9.088759383368658629e-01 8.104351227460024898e-01 8.080695436776826890e-01 1.430530913253185155e-01 +8.048001196608134400e-01 3.066089444418462762e-02 9.021887554292090661e-01 6.154331491807940591e-02 1.378912575206647784e-02 5.775720193142440673e-01 1.219298963069791464e-01 1.883270243412101808e-01 5.569262398688379356e-02 8.964817777510125651e-02 7.977092785346929782e-01 4.878149375226197293e-01 4.511973131518809410e-02 1.858690046801604323e-01 6.947686471083162063e-01 5.884058794291086025e-01 8.638884676612634816e-01 3.855470871341656336e-01 3.495049047300468059e-01 2.767740932353948136e-01 4.731087031714035218e-01 6.679001673437914288e-01 7.502944200696660682e-01 +6.527328264244687261e-01 8.289483383553154505e-01 9.179741348282299818e-01 1.065639864466713105e-01 6.253616929058514184e-01 5.927750325266062381e-01 3.039157425463192563e-01 2.452766763359194302e-01 6.514027700704632107e-01 5.529218485487964463e-01 4.941158239308394151e-01 6.605306467722642516e-01 2.273688037050677346e-01 4.282616592244774534e-01 2.956128257930247250e-01 1.154803628237965896e-01 9.228220410235263849e-01 6.663525307676617659e-01 1.908852615936970087e-01 9.921383408926374159e-01 4.988716450388516188e-01 1.014900352736023414e-01 3.363930180244284474e-01 +2.914369076275757919e-01 5.196673601143533272e-01 7.420144907858341465e-01 1.768984185504740569e-01 5.296766993228564369e-01 5.922023566159900776e-01 5.965161262020234334e-01 3.810272333046110793e-01 8.368797246118340194e-01 7.896422363801189892e-01 9.655797561098209414e-01 4.430034032346981121e-01 2.780869795706976122e-01 3.047310845416009162e-01 8.051138863500326703e-01 6.731468634690835895e-01 4.743383036815584930e-01 9.530709614322225853e-01 7.753587619850917934e-01 2.801137109357491051e-01 6.182543660889736614e-01 5.005218857766725593e-01 9.071447804755052857e-01 +2.075071644012620453e-01 4.834950086973934802e-01 3.037011473860764532e-01 6.476084284887700937e-01 8.107195771564194020e-01 7.869075869075803364e-01 6.851234019375299633e-01 3.544187468104398331e-02 4.847673235908021017e-01 5.690262846164507726e-01 1.663354142616256803e-01 9.692796809752548537e-01 4.133441725866372485e-01 6.729167604487583665e-01 3.998813427407297283e-01 8.272617414104491695e-01 2.129248316324727774e-01 6.517004761357130249e-01 7.363013506605019520e-01 4.072375306356985636e-01 4.463336683526665238e-01 5.485059309728204102e-01 1.981745754527846071e-01 diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/cdist-X2.txt b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/cdist-X2.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc3ea19674ee36856446c75df98b8c17c53ca51f --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/cdist-X2.txt @@ -0,0 +1,20 @@ +7.680465556300619667e-02 4.675022344069014180e-01 8.955498989131543963e-01 3.816236071436276411e-01 1.109030077070989329e-01 2.318928815459808668e-02 7.477394240984251983e-01 1.202289789304434864e-01 8.007290497575981769e-01 6.795195698871731027e-01 6.568225762396605605e-01 2.231475263228478445e-01 7.064624077661341151e-02 1.081656666815267176e-02 1.592069359090128033e-01 1.363392203645097389e-01 9.277020735447568667e-01 8.103136564528209407e-01 5.229467676276455812e-02 7.708020259874025504e-01 6.527954747473352359e-02 5.516397414886525796e-01 3.653371861367954443e-01 +8.144399106025798085e-01 7.731852525462976633e-01 6.909477620673205589e-01 9.696063817000286633e-01 4.297887511677249694e-01 6.989600553425188156e-01 7.310201335033380543e-01 3.135256147868910048e-01 5.715578037275241829e-01 3.935000744675094531e-01 2.057715781268398825e-01 5.892508589665171881e-01 8.512951599236765476e-01 9.569808799061578775e-01 6.164885878024699561e-01 4.714185430004367294e-01 6.128831737628155363e-01 6.641799309623502845e-01 6.001985185338730711e-01 4.231922889723856995e-01 7.605249308075449077e-01 1.064530958018087281e-01 6.306470691957204444e-01 +4.265470127256254518e-01 5.933766716280767239e-01 3.698589270536845053e-02 2.173799740537294412e-01 3.032679325475639009e-01 4.271831790058847611e-01 1.828944535901013690e-01 4.772333422710156592e-01 2.564773455194128138e-01 7.120329875362141347e-01 8.952243430110462530e-01 1.808777012183288013e-01 3.612151871458374464e-01 3.960999167923041631e-01 1.821669970670747318e-02 8.835474857189200559e-01 1.353104648821573663e-01 3.457291739160937016e-01 1.126467375304566199e-01 4.107293162402323450e-01 4.051719311053743056e-01 4.007382985250427243e-01 1.286905671428811848e-01 +2.910657003883979632e-01 9.616259180685315933e-03 2.033032441536681834e-01 1.096599110293863255e-01 4.191101704605176836e-01 5.462131536027151624e-01 8.393047907010142694e-01 9.046805198676335369e-01 7.009863472176891541e-01 2.508215985039629059e-01 6.754410796667598138e-01 6.740895474032024826e-01 1.358993708621679675e-01 8.219861775211464439e-01 6.322220445623235596e-01 2.766813559002430090e-01 6.575983861590951607e-01 9.515869708336625044e-01 8.654526462353933081e-01 3.450245117834797037e-01 5.649032890631299209e-01 4.717687914789682191e-01 3.296483580510030098e-01 +9.172477457635394016e-01 3.057396583041891436e-01 7.335332344225760082e-01 8.370236206345178509e-01 3.765464253115927695e-01 5.089680319287778199e-01 1.202325719268168003e-01 9.717771065272349240e-01 5.907820104019682050e-01 9.809211614977710880e-01 9.064285003671219698e-01 8.848841466121748489e-01 2.043407730734815297e-01 9.157600394927275511e-01 4.532260315147775831e-01 4.241077335005828397e-01 1.751730149568804240e-01 4.090412146081819911e-01 3.632197861847064058e-02 5.832539334970230360e-01 4.041848151536805434e-01 3.603643989086504629e-01 1.838411383882069261e-01 +2.508806403290032572e-01 4.381403985282813496e-01 4.694787405018008286e-02 6.353900562024634713e-01 1.200813444244532846e-01 6.072397042913001419e-01 9.937255904754030977e-01 4.916670237677555066e-01 3.473845913923001572e-01 3.526875922864345370e-01 5.448595548197197047e-01 2.245096010156972799e-01 9.003258279804994269e-01 3.534560469735994470e-01 2.989266066346342177e-01 4.621024982808636938e-01 9.626538866576676012e-01 9.791401720716153001e-01 7.138514287330390840e-01 9.832862333928654719e-01 3.233999591031431198e-01 5.406467224926423398e-01 9.581890295057201579e-01 +5.210583601680578436e-01 4.598159993059653949e-01 2.111497132057748027e-01 5.949977700916546652e-01 6.342618461422359077e-01 9.888228769705599275e-01 6.096770711536318998e-01 7.548431368960863974e-01 7.490858664860100546e-01 3.186213496546415058e-01 7.895687083231245351e-01 4.178326793268141159e-01 8.095818334534051752e-01 7.886271673523481684e-01 4.038905626506847923e-01 3.652649247094948981e-01 8.267205959224892542e-01 6.433617243328785262e-01 3.117681563249452559e-01 9.675995575054980868e-01 3.675673836358472890e-01 5.863757289184046151e-01 9.099029857959717305e-02 +4.024573981231733821e-01 3.578997554002771864e-01 3.519299868071553705e-01 7.417747693762357653e-01 2.963713903285800644e-01 9.602967989298948348e-01 3.811392331739601458e-01 5.493237898295448840e-01 6.835113342793640578e-01 2.304506220807415184e-01 3.727299857731285471e-01 5.450263991912108752e-01 6.951521210987908761e-01 6.474582745861203747e-01 6.316089475403589004e-01 5.672043967425510758e-02 9.034937506977609445e-01 2.332567550780038079e-01 1.096955741449157085e-02 8.870663813493575578e-01 4.384385452180562526e-01 7.100898998169548060e-01 3.245358176196319056e-01 +9.162009194452818139e-01 5.572224742426723498e-02 3.445910686865658601e-01 9.683564008127462097e-01 9.375063149031520604e-01 9.128188852869822956e-02 9.613605414326487075e-01 5.298598697556915482e-01 6.724799695520149445e-01 1.269103938571825019e-02 1.008406153387807480e-01 8.951105272379104028e-01 1.585460318853607609e-01 6.739986455059543413e-01 5.345419321702655768e-01 6.248843899572337213e-01 3.050288488994817859e-01 1.423645553465189284e-01 1.802121190541096096e-01 9.474646822694763326e-01 2.345716438587298613e-01 9.688281784764296578e-01 1.845165243240991515e-01 +2.548297646910531178e-01 2.580877375379494465e-01 1.355482532666937301e-01 6.478812986505504412e-01 9.971695982152032345e-01 2.606721082477282403e-01 5.483439686378906996e-01 4.409612606704470528e-01 4.396442074915688503e-01 7.414262832597111608e-01 7.308840725375539416e-01 8.072095530497225280e-02 6.829509968656330976e-01 5.700030854230387911e-01 3.801845336730320657e-01 2.481059916867158766e-01 3.977295094395927322e-03 5.749480512407895150e-01 4.112033136603401307e-01 8.676159710377848722e-01 9.062646588480167686e-01 3.326691167317923359e-01 8.498307982774666591e-01 +4.464338109330643345e-01 8.546516760817471914e-01 7.384800352329814466e-01 3.692485164984804502e-02 2.915662689505471583e-02 9.010049994217171898e-01 8.622900253010918892e-01 9.786230638032608065e-01 6.546824077297251909e-01 6.342297560006789903e-01 2.230339826582647955e-01 7.658846744185553446e-01 4.603043831539479491e-01 2.017100469861691225e-01 4.891590639893540482e-01 1.937140918314912419e-01 8.161582138652878626e-01 5.597293607114051106e-02 8.423261093326828153e-02 5.105392204475533990e-02 8.234193902673621057e-01 1.784268309975372002e-01 9.118997881986501408e-02 +8.588746913421980711e-01 1.479641118621310980e-02 1.375875301146138874e-01 7.533888774725254756e-01 5.782592791549248101e-01 9.128573037619659436e-01 1.831275762880391067e-01 3.471382864827737835e-01 4.859524740929310749e-02 8.955146541561730400e-01 4.787220791101074457e-01 4.222803577759057791e-01 8.469923964908064873e-01 6.300290047587608910e-02 1.020873237837905956e-01 3.585612487182909813e-02 6.320107119904569970e-01 5.891245970008752719e-01 1.104698053665007507e-01 4.233226558073774903e-01 4.432217054386708988e-01 2.864765416628194394e-01 2.489777211814803159e-02 +5.343810659756068615e-01 4.829076396403546578e-01 8.364480888953172988e-01 8.931374995414760321e-01 6.034161442354715188e-01 3.578336000768178593e-03 4.100579775972763574e-01 3.968667908067096128e-01 5.897163653686778861e-01 3.003241263928478899e-01 2.520935203143799264e-01 3.112129371563532310e-02 9.052865295974613646e-01 1.172285124002711010e-01 4.840001666149388315e-01 3.424620676348436588e-01 5.526057133826853818e-01 6.346139530261846184e-01 5.747945930485597321e-01 1.389915612177697879e-01 2.413801217666421417e-01 7.829900796662081497e-01 7.213528084845653998e-01 +9.384509283406079483e-01 6.303019601671526750e-01 1.787921522728125323e-01 1.556003868047917127e-02 5.662397078816850948e-01 3.437473614806091371e-01 8.615844972800188462e-01 7.624380237306396246e-01 1.096468347898514883e-01 1.276566836610887323e-01 8.479188493443535757e-01 3.634713454428405432e-01 7.478112314318967613e-01 9.856395696968375253e-01 6.250293654177319080e-02 1.919327272501809567e-01 1.415594476031050153e-01 7.224057351041784925e-01 8.452145259310355208e-01 5.434318833772002755e-01 5.177620959731277228e-02 3.358977598185840518e-01 2.542654881527960375e-01 +4.800909104006243489e-01 3.651345393613150137e-01 3.657093052788148446e-01 8.579662326651369408e-01 5.787694361240260932e-01 6.491966196891312268e-01 3.252508517294879775e-01 8.639694334693422961e-01 3.028097078756678551e-01 6.295814666338699350e-01 7.305627351548695803e-01 6.975931849120264872e-03 8.321205159004851915e-01 2.681809305821257761e-01 3.628869474597150591e-01 9.598981434716586936e-01 5.947913523332928332e-01 7.794864238003402779e-01 2.819511239444029149e-01 5.134200958476284882e-01 7.284684743064278045e-01 3.099571109539331903e-01 1.502222882866774967e-01 +2.463382654375219083e-01 4.465700737264240994e-01 7.180855317941433613e-01 5.056099420785193921e-01 6.182117344332578313e-01 2.370453793561340117e-01 9.831748018047525850e-01 6.397098184531551102e-01 8.260469782208745837e-02 7.474671691560941245e-01 9.963429983418570224e-02 5.450078811081275898e-01 5.370188678062637333e-02 2.774024442708808991e-01 2.082643088545442778e-01 2.704155352788065736e-01 7.225035580445194894e-01 4.866791976239246420e-01 1.357043111201584606e-01 7.911335827987711067e-01 7.278977102006007893e-01 6.880892094410231419e-01 1.029231496520791600e-01 +6.901796117735281566e-01 1.558248977395644275e-01 4.241818789360329855e-01 5.055658246392458199e-01 1.756288758075611467e-01 4.215083703818177652e-01 7.809231602323289945e-01 1.170053878686481141e-01 6.497026323614403243e-01 5.733120641440232479e-01 4.407703406152092551e-01 5.608677124532297498e-01 7.471045703286000039e-01 3.334604336022076732e-01 8.927208811415126011e-01 9.794565286182396191e-01 9.621542824973521313e-01 3.945825239405253981e-01 8.338963875792834157e-01 9.310552325082104286e-01 7.688283033784242271e-01 3.798823731047119567e-01 1.459993613028365278e-02 +7.848623555505630511e-01 2.681039365355797344e-03 7.833208051794043891e-01 8.184381915171493604e-01 4.682581645582317709e-01 2.391069309436419932e-01 1.765377537168698607e-01 9.863494676539893424e-01 4.378412300863872009e-01 7.494505491149090481e-01 1.942180356195394308e-01 9.981402467222395547e-01 7.992190944052800505e-01 1.350875702852057936e-01 4.950149186748543650e-01 7.243422481248201761e-01 3.544596746353472216e-01 8.320192561472177228e-01 9.776840296475269865e-01 7.733852731914863110e-01 2.305732998099923048e-01 9.746878189802981041e-01 7.747723331200035979e-01 +6.521099013127149568e-01 5.452399443648201505e-01 8.146707517183656710e-01 3.827256063695345656e-01 7.954832091744263867e-01 7.834427643148527132e-01 9.661317930643520402e-02 9.215673965718058636e-01 4.914305728788055383e-01 4.105628408027649501e-01 9.844647830893304974e-02 3.974831165301851987e-01 3.857608898053827007e-01 5.520210781401946321e-01 3.445787541654143915e-03 4.552922057017416702e-01 7.456544561760444223e-01 4.753985092154335845e-01 2.821385239833401615e-01 7.560136035104459973e-01 8.453142510471420845e-01 6.679627143276523071e-01 6.910882868284401459e-01 +8.526493480446283302e-01 1.183917973068240315e-01 6.163988861865119517e-01 5.751899460059114455e-01 1.638797964925038375e-01 8.214597298784013235e-01 5.424670654187370156e-01 1.806631819658732763e-01 9.268107278221827672e-01 4.127397378597359445e-01 7.529877485901653733e-01 1.714251090083847018e-01 2.601487784245806179e-01 2.028326156742237263e-01 5.299879450122358948e-01 7.587877062981395193e-01 4.070738595375062996e-01 3.546903049793261875e-01 8.695365138547607176e-01 1.447085661525142619e-01 3.193366245820845606e-01 8.797841086211429795e-01 2.666562188639977071e-01 diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/degenerate_pointset.npz b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/degenerate_pointset.npz new file mode 100644 index 0000000000000000000000000000000000000000..4f22bd3a3c941a683747944a0f12c7914f4b3f07 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/degenerate_pointset.npz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:048abc1ddd924bf2d4d1f216015552ed9431f9e99546fbf382768eda58788175 +size 22548 diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/iris.txt b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/iris.txt new file mode 100644 index 0000000000000000000000000000000000000000..4d78390c2596beb41b1abff651a729e4e964c36e --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/iris.txt @@ -0,0 +1,150 @@ +5.099999999999999645e+00 3.500000000000000000e+00 1.399999999999999911e+00 2.000000000000000111e-01 +4.900000000000000355e+00 3.000000000000000000e+00 1.399999999999999911e+00 2.000000000000000111e-01 +4.700000000000000178e+00 3.200000000000000178e+00 1.300000000000000044e+00 2.000000000000000111e-01 +4.599999999999999645e+00 3.100000000000000089e+00 1.500000000000000000e+00 2.000000000000000111e-01 +5.000000000000000000e+00 3.600000000000000089e+00 1.399999999999999911e+00 2.000000000000000111e-01 +5.400000000000000355e+00 3.899999999999999911e+00 1.699999999999999956e+00 4.000000000000000222e-01 +4.599999999999999645e+00 3.399999999999999911e+00 1.399999999999999911e+00 2.999999999999999889e-01 +5.000000000000000000e+00 3.399999999999999911e+00 1.500000000000000000e+00 2.000000000000000111e-01 +4.400000000000000355e+00 2.899999999999999911e+00 1.399999999999999911e+00 2.000000000000000111e-01 +4.900000000000000355e+00 3.100000000000000089e+00 1.500000000000000000e+00 1.000000000000000056e-01 +5.400000000000000355e+00 3.700000000000000178e+00 1.500000000000000000e+00 2.000000000000000111e-01 +4.799999999999999822e+00 3.399999999999999911e+00 1.600000000000000089e+00 2.000000000000000111e-01 +4.799999999999999822e+00 3.000000000000000000e+00 1.399999999999999911e+00 1.000000000000000056e-01 +4.299999999999999822e+00 3.000000000000000000e+00 1.100000000000000089e+00 1.000000000000000056e-01 +5.799999999999999822e+00 4.000000000000000000e+00 1.199999999999999956e+00 2.000000000000000111e-01 +5.700000000000000178e+00 4.400000000000000355e+00 1.500000000000000000e+00 4.000000000000000222e-01 +5.400000000000000355e+00 3.899999999999999911e+00 1.300000000000000044e+00 4.000000000000000222e-01 +5.099999999999999645e+00 3.500000000000000000e+00 1.399999999999999911e+00 2.999999999999999889e-01 +5.700000000000000178e+00 3.799999999999999822e+00 1.699999999999999956e+00 2.999999999999999889e-01 +5.099999999999999645e+00 3.799999999999999822e+00 1.500000000000000000e+00 2.999999999999999889e-01 +5.400000000000000355e+00 3.399999999999999911e+00 1.699999999999999956e+00 2.000000000000000111e-01 +5.099999999999999645e+00 3.700000000000000178e+00 1.500000000000000000e+00 4.000000000000000222e-01 +4.599999999999999645e+00 3.600000000000000089e+00 1.000000000000000000e+00 2.000000000000000111e-01 +5.099999999999999645e+00 3.299999999999999822e+00 1.699999999999999956e+00 5.000000000000000000e-01 +4.799999999999999822e+00 3.399999999999999911e+00 1.899999999999999911e+00 2.000000000000000111e-01 +5.000000000000000000e+00 3.000000000000000000e+00 1.600000000000000089e+00 2.000000000000000111e-01 +5.000000000000000000e+00 3.399999999999999911e+00 1.600000000000000089e+00 4.000000000000000222e-01 +5.200000000000000178e+00 3.500000000000000000e+00 1.500000000000000000e+00 2.000000000000000111e-01 +5.200000000000000178e+00 3.399999999999999911e+00 1.399999999999999911e+00 2.000000000000000111e-01 +4.700000000000000178e+00 3.200000000000000178e+00 1.600000000000000089e+00 2.000000000000000111e-01 +4.799999999999999822e+00 3.100000000000000089e+00 1.600000000000000089e+00 2.000000000000000111e-01 +5.400000000000000355e+00 3.399999999999999911e+00 1.500000000000000000e+00 4.000000000000000222e-01 +5.200000000000000178e+00 4.099999999999999645e+00 1.500000000000000000e+00 1.000000000000000056e-01 +5.500000000000000000e+00 4.200000000000000178e+00 1.399999999999999911e+00 2.000000000000000111e-01 +4.900000000000000355e+00 3.100000000000000089e+00 1.500000000000000000e+00 1.000000000000000056e-01 +5.000000000000000000e+00 3.200000000000000178e+00 1.199999999999999956e+00 2.000000000000000111e-01 +5.500000000000000000e+00 3.500000000000000000e+00 1.300000000000000044e+00 2.000000000000000111e-01 +4.900000000000000355e+00 3.100000000000000089e+00 1.500000000000000000e+00 1.000000000000000056e-01 +4.400000000000000355e+00 3.000000000000000000e+00 1.300000000000000044e+00 2.000000000000000111e-01 +5.099999999999999645e+00 3.399999999999999911e+00 1.500000000000000000e+00 2.000000000000000111e-01 +5.000000000000000000e+00 3.500000000000000000e+00 1.300000000000000044e+00 2.999999999999999889e-01 +4.500000000000000000e+00 2.299999999999999822e+00 1.300000000000000044e+00 2.999999999999999889e-01 +4.400000000000000355e+00 3.200000000000000178e+00 1.300000000000000044e+00 2.000000000000000111e-01 +5.000000000000000000e+00 3.500000000000000000e+00 1.600000000000000089e+00 5.999999999999999778e-01 +5.099999999999999645e+00 3.799999999999999822e+00 1.899999999999999911e+00 4.000000000000000222e-01 +4.799999999999999822e+00 3.000000000000000000e+00 1.399999999999999911e+00 2.999999999999999889e-01 +5.099999999999999645e+00 3.799999999999999822e+00 1.600000000000000089e+00 2.000000000000000111e-01 +4.599999999999999645e+00 3.200000000000000178e+00 1.399999999999999911e+00 2.000000000000000111e-01 +5.299999999999999822e+00 3.700000000000000178e+00 1.500000000000000000e+00 2.000000000000000111e-01 +5.000000000000000000e+00 3.299999999999999822e+00 1.399999999999999911e+00 2.000000000000000111e-01 +7.000000000000000000e+00 3.200000000000000178e+00 4.700000000000000178e+00 1.399999999999999911e+00 +6.400000000000000355e+00 3.200000000000000178e+00 4.500000000000000000e+00 1.500000000000000000e+00 +6.900000000000000355e+00 3.100000000000000089e+00 4.900000000000000355e+00 1.500000000000000000e+00 +5.500000000000000000e+00 2.299999999999999822e+00 4.000000000000000000e+00 1.300000000000000044e+00 +6.500000000000000000e+00 2.799999999999999822e+00 4.599999999999999645e+00 1.500000000000000000e+00 +5.700000000000000178e+00 2.799999999999999822e+00 4.500000000000000000e+00 1.300000000000000044e+00 +6.299999999999999822e+00 3.299999999999999822e+00 4.700000000000000178e+00 1.600000000000000089e+00 +4.900000000000000355e+00 2.399999999999999911e+00 3.299999999999999822e+00 1.000000000000000000e+00 +6.599999999999999645e+00 2.899999999999999911e+00 4.599999999999999645e+00 1.300000000000000044e+00 +5.200000000000000178e+00 2.700000000000000178e+00 3.899999999999999911e+00 1.399999999999999911e+00 +5.000000000000000000e+00 2.000000000000000000e+00 3.500000000000000000e+00 1.000000000000000000e+00 +5.900000000000000355e+00 3.000000000000000000e+00 4.200000000000000178e+00 1.500000000000000000e+00 +6.000000000000000000e+00 2.200000000000000178e+00 4.000000000000000000e+00 1.000000000000000000e+00 +6.099999999999999645e+00 2.899999999999999911e+00 4.700000000000000178e+00 1.399999999999999911e+00 +5.599999999999999645e+00 2.899999999999999911e+00 3.600000000000000089e+00 1.300000000000000044e+00 +6.700000000000000178e+00 3.100000000000000089e+00 4.400000000000000355e+00 1.399999999999999911e+00 +5.599999999999999645e+00 3.000000000000000000e+00 4.500000000000000000e+00 1.500000000000000000e+00 +5.799999999999999822e+00 2.700000000000000178e+00 4.099999999999999645e+00 1.000000000000000000e+00 +6.200000000000000178e+00 2.200000000000000178e+00 4.500000000000000000e+00 1.500000000000000000e+00 +5.599999999999999645e+00 2.500000000000000000e+00 3.899999999999999911e+00 1.100000000000000089e+00 +5.900000000000000355e+00 3.200000000000000178e+00 4.799999999999999822e+00 1.800000000000000044e+00 +6.099999999999999645e+00 2.799999999999999822e+00 4.000000000000000000e+00 1.300000000000000044e+00 +6.299999999999999822e+00 2.500000000000000000e+00 4.900000000000000355e+00 1.500000000000000000e+00 +6.099999999999999645e+00 2.799999999999999822e+00 4.700000000000000178e+00 1.199999999999999956e+00 +6.400000000000000355e+00 2.899999999999999911e+00 4.299999999999999822e+00 1.300000000000000044e+00 +6.599999999999999645e+00 3.000000000000000000e+00 4.400000000000000355e+00 1.399999999999999911e+00 +6.799999999999999822e+00 2.799999999999999822e+00 4.799999999999999822e+00 1.399999999999999911e+00 +6.700000000000000178e+00 3.000000000000000000e+00 5.000000000000000000e+00 1.699999999999999956e+00 +6.000000000000000000e+00 2.899999999999999911e+00 4.500000000000000000e+00 1.500000000000000000e+00 +5.700000000000000178e+00 2.600000000000000089e+00 3.500000000000000000e+00 1.000000000000000000e+00 +5.500000000000000000e+00 2.399999999999999911e+00 3.799999999999999822e+00 1.100000000000000089e+00 +5.500000000000000000e+00 2.399999999999999911e+00 3.700000000000000178e+00 1.000000000000000000e+00 +5.799999999999999822e+00 2.700000000000000178e+00 3.899999999999999911e+00 1.199999999999999956e+00 +6.000000000000000000e+00 2.700000000000000178e+00 5.099999999999999645e+00 1.600000000000000089e+00 +5.400000000000000355e+00 3.000000000000000000e+00 4.500000000000000000e+00 1.500000000000000000e+00 +6.000000000000000000e+00 3.399999999999999911e+00 4.500000000000000000e+00 1.600000000000000089e+00 +6.700000000000000178e+00 3.100000000000000089e+00 4.700000000000000178e+00 1.500000000000000000e+00 +6.299999999999999822e+00 2.299999999999999822e+00 4.400000000000000355e+00 1.300000000000000044e+00 +5.599999999999999645e+00 3.000000000000000000e+00 4.099999999999999645e+00 1.300000000000000044e+00 +5.500000000000000000e+00 2.500000000000000000e+00 4.000000000000000000e+00 1.300000000000000044e+00 +5.500000000000000000e+00 2.600000000000000089e+00 4.400000000000000355e+00 1.199999999999999956e+00 +6.099999999999999645e+00 3.000000000000000000e+00 4.599999999999999645e+00 1.399999999999999911e+00 +5.799999999999999822e+00 2.600000000000000089e+00 4.000000000000000000e+00 1.199999999999999956e+00 +5.000000000000000000e+00 2.299999999999999822e+00 3.299999999999999822e+00 1.000000000000000000e+00 +5.599999999999999645e+00 2.700000000000000178e+00 4.200000000000000178e+00 1.300000000000000044e+00 +5.700000000000000178e+00 3.000000000000000000e+00 4.200000000000000178e+00 1.199999999999999956e+00 +5.700000000000000178e+00 2.899999999999999911e+00 4.200000000000000178e+00 1.300000000000000044e+00 +6.200000000000000178e+00 2.899999999999999911e+00 4.299999999999999822e+00 1.300000000000000044e+00 +5.099999999999999645e+00 2.500000000000000000e+00 3.000000000000000000e+00 1.100000000000000089e+00 +5.700000000000000178e+00 2.799999999999999822e+00 4.099999999999999645e+00 1.300000000000000044e+00 +6.299999999999999822e+00 3.299999999999999822e+00 6.000000000000000000e+00 2.500000000000000000e+00 +5.799999999999999822e+00 2.700000000000000178e+00 5.099999999999999645e+00 1.899999999999999911e+00 +7.099999999999999645e+00 3.000000000000000000e+00 5.900000000000000355e+00 2.100000000000000089e+00 +6.299999999999999822e+00 2.899999999999999911e+00 5.599999999999999645e+00 1.800000000000000044e+00 +6.500000000000000000e+00 3.000000000000000000e+00 5.799999999999999822e+00 2.200000000000000178e+00 +7.599999999999999645e+00 3.000000000000000000e+00 6.599999999999999645e+00 2.100000000000000089e+00 +4.900000000000000355e+00 2.500000000000000000e+00 4.500000000000000000e+00 1.699999999999999956e+00 +7.299999999999999822e+00 2.899999999999999911e+00 6.299999999999999822e+00 1.800000000000000044e+00 +6.700000000000000178e+00 2.500000000000000000e+00 5.799999999999999822e+00 1.800000000000000044e+00 +7.200000000000000178e+00 3.600000000000000089e+00 6.099999999999999645e+00 2.500000000000000000e+00 +6.500000000000000000e+00 3.200000000000000178e+00 5.099999999999999645e+00 2.000000000000000000e+00 +6.400000000000000355e+00 2.700000000000000178e+00 5.299999999999999822e+00 1.899999999999999911e+00 +6.799999999999999822e+00 3.000000000000000000e+00 5.500000000000000000e+00 2.100000000000000089e+00 +5.700000000000000178e+00 2.500000000000000000e+00 5.000000000000000000e+00 2.000000000000000000e+00 +5.799999999999999822e+00 2.799999999999999822e+00 5.099999999999999645e+00 2.399999999999999911e+00 +6.400000000000000355e+00 3.200000000000000178e+00 5.299999999999999822e+00 2.299999999999999822e+00 +6.500000000000000000e+00 3.000000000000000000e+00 5.500000000000000000e+00 1.800000000000000044e+00 +7.700000000000000178e+00 3.799999999999999822e+00 6.700000000000000178e+00 2.200000000000000178e+00 +7.700000000000000178e+00 2.600000000000000089e+00 6.900000000000000355e+00 2.299999999999999822e+00 +6.000000000000000000e+00 2.200000000000000178e+00 5.000000000000000000e+00 1.500000000000000000e+00 +6.900000000000000355e+00 3.200000000000000178e+00 5.700000000000000178e+00 2.299999999999999822e+00 +5.599999999999999645e+00 2.799999999999999822e+00 4.900000000000000355e+00 2.000000000000000000e+00 +7.700000000000000178e+00 2.799999999999999822e+00 6.700000000000000178e+00 2.000000000000000000e+00 +6.299999999999999822e+00 2.700000000000000178e+00 4.900000000000000355e+00 1.800000000000000044e+00 +6.700000000000000178e+00 3.299999999999999822e+00 5.700000000000000178e+00 2.100000000000000089e+00 +7.200000000000000178e+00 3.200000000000000178e+00 6.000000000000000000e+00 1.800000000000000044e+00 +6.200000000000000178e+00 2.799999999999999822e+00 4.799999999999999822e+00 1.800000000000000044e+00 +6.099999999999999645e+00 3.000000000000000000e+00 4.900000000000000355e+00 1.800000000000000044e+00 +6.400000000000000355e+00 2.799999999999999822e+00 5.599999999999999645e+00 2.100000000000000089e+00 +7.200000000000000178e+00 3.000000000000000000e+00 5.799999999999999822e+00 1.600000000000000089e+00 +7.400000000000000355e+00 2.799999999999999822e+00 6.099999999999999645e+00 1.899999999999999911e+00 +7.900000000000000355e+00 3.799999999999999822e+00 6.400000000000000355e+00 2.000000000000000000e+00 +6.400000000000000355e+00 2.799999999999999822e+00 5.599999999999999645e+00 2.200000000000000178e+00 +6.299999999999999822e+00 2.799999999999999822e+00 5.099999999999999645e+00 1.500000000000000000e+00 +6.099999999999999645e+00 2.600000000000000089e+00 5.599999999999999645e+00 1.399999999999999911e+00 +7.700000000000000178e+00 3.000000000000000000e+00 6.099999999999999645e+00 2.299999999999999822e+00 +6.299999999999999822e+00 3.399999999999999911e+00 5.599999999999999645e+00 2.399999999999999911e+00 +6.400000000000000355e+00 3.100000000000000089e+00 5.500000000000000000e+00 1.800000000000000044e+00 +6.000000000000000000e+00 3.000000000000000000e+00 4.799999999999999822e+00 1.800000000000000044e+00 +6.900000000000000355e+00 3.100000000000000089e+00 5.400000000000000355e+00 2.100000000000000089e+00 +6.700000000000000178e+00 3.100000000000000089e+00 5.599999999999999645e+00 2.399999999999999911e+00 +6.900000000000000355e+00 3.100000000000000089e+00 5.099999999999999645e+00 2.299999999999999822e+00 +5.799999999999999822e+00 2.700000000000000178e+00 5.099999999999999645e+00 1.899999999999999911e+00 +6.799999999999999822e+00 3.200000000000000178e+00 5.900000000000000355e+00 2.299999999999999822e+00 +6.700000000000000178e+00 3.299999999999999822e+00 5.700000000000000178e+00 2.500000000000000000e+00 +6.700000000000000178e+00 3.000000000000000000e+00 5.200000000000000178e+00 2.299999999999999822e+00 +6.299999999999999822e+00 2.500000000000000000e+00 5.000000000000000000e+00 1.899999999999999911e+00 +6.500000000000000000e+00 3.000000000000000000e+00 5.200000000000000178e+00 2.000000000000000000e+00 +6.200000000000000178e+00 3.399999999999999911e+00 5.400000000000000355e+00 2.299999999999999822e+00 +5.900000000000000355e+00 3.000000000000000000e+00 5.099999999999999645e+00 1.800000000000000044e+00 diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-boolean-inp.txt b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-boolean-inp.txt new file mode 100644 index 0000000000000000000000000000000000000000..0636cc9f4590f2e960f7136d83a5d2cb07c56536 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-boolean-inp.txt @@ -0,0 +1,20 @@ +1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 +1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 +1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 +0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 +1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 +1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 +1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 +1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 +1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 +1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 +0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 +1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 +1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 +1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 +0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 +1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 +1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 +0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 +1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 +1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-chebyshev-ml-iris.txt b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-chebyshev-ml-iris.txt new file mode 100644 index 0000000000000000000000000000000000000000..0aff1267ca7fd6f41d38c2273540bb771e9cbd0c --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-chebyshev-ml-iris.txt @@ -0,0 +1 @@ + 5.0000000e-01 4.0000000e-01 5.0000000e-01 1.0000000e-01 4.0000000e-01 5.0000000e-01 1.0000000e-01 7.0000000e-01 4.0000000e-01 3.0000000e-01 3.0000000e-01 5.0000000e-01 8.0000000e-01 7.0000000e-01 9.0000000e-01 4.0000000e-01 1.0000000e-01 6.0000000e-01 3.0000000e-01 3.0000000e-01 2.0000000e-01 5.0000000e-01 3.0000000e-01 5.0000000e-01 5.0000000e-01 2.0000000e-01 1.0000000e-01 1.0000000e-01 4.0000000e-01 4.0000000e-01 3.0000000e-01 6.0000000e-01 7.0000000e-01 4.0000000e-01 3.0000000e-01 4.0000000e-01 4.0000000e-01 7.0000000e-01 1.0000000e-01 1.0000000e-01 1.2000000e+00 7.0000000e-01 4.0000000e-01 5.0000000e-01 5.0000000e-01 3.0000000e-01 5.0000000e-01 2.0000000e-01 2.0000000e-01 3.3000000e+00 3.1000000e+00 3.5000000e+00 2.6000000e+00 3.2000000e+00 3.1000000e+00 3.3000000e+00 1.9000000e+00 3.2000000e+00 2.5000000e+00 2.1000000e+00 2.8000000e+00 2.6000000e+00 3.3000000e+00 2.2000000e+00 3.0000000e+00 3.1000000e+00 2.7000000e+00 3.1000000e+00 2.5000000e+00 3.4000000e+00 2.6000000e+00 3.5000000e+00 3.3000000e+00 2.9000000e+00 3.0000000e+00 3.4000000e+00 3.6000000e+00 3.1000000e+00 2.1000000e+00 2.4000000e+00 2.3000000e+00 2.5000000e+00 3.7000000e+00 3.1000000e+00 3.1000000e+00 3.3000000e+00 3.0000000e+00 2.7000000e+00 2.6000000e+00 3.0000000e+00 3.2000000e+00 2.6000000e+00 1.9000000e+00 2.8000000e+00 2.8000000e+00 2.8000000e+00 2.9000000e+00 1.6000000e+00 2.7000000e+00 4.6000000e+00 3.7000000e+00 4.5000000e+00 4.2000000e+00 4.4000000e+00 5.2000000e+00 3.1000000e+00 4.9000000e+00 4.4000000e+00 4.7000000e+00 3.7000000e+00 3.9000000e+00 4.1000000e+00 3.6000000e+00 3.7000000e+00 3.9000000e+00 4.1000000e+00 5.3000000e+00 5.5000000e+00 3.6000000e+00 4.3000000e+00 3.5000000e+00 5.3000000e+00 3.5000000e+00 4.3000000e+00 4.6000000e+00 3.4000000e+00 3.5000000e+00 4.2000000e+00 4.4000000e+00 4.7000000e+00 5.0000000e+00 4.2000000e+00 3.7000000e+00 4.2000000e+00 4.7000000e+00 4.2000000e+00 4.1000000e+00 3.4000000e+00 4.0000000e+00 4.2000000e+00 3.7000000e+00 3.7000000e+00 4.5000000e+00 4.3000000e+00 3.8000000e+00 3.6000000e+00 3.8000000e+00 4.0000000e+00 3.7000000e+00 2.0000000e-01 3.0000000e-01 6.0000000e-01 9.0000000e-01 4.0000000e-01 4.0000000e-01 5.0000000e-01 1.0000000e-01 7.0000000e-01 4.0000000e-01 1.0000000e-01 6.0000000e-01 1.0000000e+00 1.4000000e+00 9.0000000e-01 5.0000000e-01 8.0000000e-01 8.0000000e-01 5.0000000e-01 7.0000000e-01 6.0000000e-01 3.0000000e-01 5.0000000e-01 2.0000000e-01 4.0000000e-01 5.0000000e-01 4.0000000e-01 2.0000000e-01 2.0000000e-01 5.0000000e-01 1.1000000e+00 1.2000000e+00 1.0000000e-01 2.0000000e-01 6.0000000e-01 1.0000000e-01 5.0000000e-01 4.0000000e-01 5.0000000e-01 7.0000000e-01 5.0000000e-01 5.0000000e-01 8.0000000e-01 1.0000000e-01 8.0000000e-01 3.0000000e-01 7.0000000e-01 3.0000000e-01 3.3000000e+00 3.1000000e+00 3.5000000e+00 2.6000000e+00 3.2000000e+00 3.1000000e+00 3.3000000e+00 1.9000000e+00 3.2000000e+00 2.5000000e+00 2.1000000e+00 2.8000000e+00 2.6000000e+00 3.3000000e+00 2.2000000e+00 3.0000000e+00 3.1000000e+00 2.7000000e+00 3.1000000e+00 2.5000000e+00 3.4000000e+00 2.6000000e+00 3.5000000e+00 3.3000000e+00 2.9000000e+00 3.0000000e+00 3.4000000e+00 3.6000000e+00 3.1000000e+00 2.1000000e+00 2.4000000e+00 2.3000000e+00 2.5000000e+00 3.7000000e+00 3.1000000e+00 3.1000000e+00 3.3000000e+00 3.0000000e+00 2.7000000e+00 2.6000000e+00 3.0000000e+00 3.2000000e+00 2.6000000e+00 1.9000000e+00 2.8000000e+00 2.8000000e+00 2.8000000e+00 2.9000000e+00 1.6000000e+00 2.7000000e+00 4.6000000e+00 3.7000000e+00 4.5000000e+00 4.2000000e+00 4.4000000e+00 5.2000000e+00 3.1000000e+00 4.9000000e+00 4.4000000e+00 4.7000000e+00 3.7000000e+00 3.9000000e+00 4.1000000e+00 3.6000000e+00 3.7000000e+00 3.9000000e+00 4.1000000e+00 5.3000000e+00 5.5000000e+00 3.6000000e+00 4.3000000e+00 3.5000000e+00 5.3000000e+00 3.5000000e+00 4.3000000e+00 4.6000000e+00 3.4000000e+00 3.5000000e+00 4.2000000e+00 4.4000000e+00 4.7000000e+00 5.0000000e+00 4.2000000e+00 3.7000000e+00 4.2000000e+00 4.7000000e+00 4.2000000e+00 4.1000000e+00 3.4000000e+00 4.0000000e+00 4.2000000e+00 3.7000000e+00 3.7000000e+00 4.5000000e+00 4.3000000e+00 3.8000000e+00 3.6000000e+00 3.8000000e+00 4.0000000e+00 3.7000000e+00 2.0000000e-01 4.0000000e-01 7.0000000e-01 2.0000000e-01 3.0000000e-01 3.0000000e-01 2.0000000e-01 7.0000000e-01 3.0000000e-01 2.0000000e-01 4.0000000e-01 1.1000000e+00 1.2000000e+00 7.0000000e-01 4.0000000e-01 1.0000000e+00 6.0000000e-01 7.0000000e-01 5.0000000e-01 4.0000000e-01 4.0000000e-01 6.0000000e-01 3.0000000e-01 3.0000000e-01 5.0000000e-01 5.0000000e-01 3.0000000e-01 3.0000000e-01 7.0000000e-01 9.0000000e-01 1.0000000e+00 2.0000000e-01 3.0000000e-01 8.0000000e-01 2.0000000e-01 3.0000000e-01 4.0000000e-01 3.0000000e-01 9.0000000e-01 3.0000000e-01 4.0000000e-01 6.0000000e-01 2.0000000e-01 6.0000000e-01 1.0000000e-01 6.0000000e-01 3.0000000e-01 3.4000000e+00 3.2000000e+00 3.6000000e+00 2.7000000e+00 3.3000000e+00 3.2000000e+00 3.4000000e+00 2.0000000e+00 3.3000000e+00 2.6000000e+00 2.2000000e+00 2.9000000e+00 2.7000000e+00 3.4000000e+00 2.3000000e+00 3.1000000e+00 3.2000000e+00 2.8000000e+00 3.2000000e+00 2.6000000e+00 3.5000000e+00 2.7000000e+00 3.6000000e+00 3.4000000e+00 3.0000000e+00 3.1000000e+00 3.5000000e+00 3.7000000e+00 3.2000000e+00 2.2000000e+00 2.5000000e+00 2.4000000e+00 2.6000000e+00 3.8000000e+00 3.2000000e+00 3.2000000e+00 3.4000000e+00 3.1000000e+00 2.8000000e+00 2.7000000e+00 3.1000000e+00 3.3000000e+00 2.7000000e+00 2.0000000e+00 2.9000000e+00 2.9000000e+00 2.9000000e+00 3.0000000e+00 1.7000000e+00 2.8000000e+00 4.7000000e+00 3.8000000e+00 4.6000000e+00 4.3000000e+00 4.5000000e+00 5.3000000e+00 3.2000000e+00 5.0000000e+00 4.5000000e+00 4.8000000e+00 3.8000000e+00 4.0000000e+00 4.2000000e+00 3.7000000e+00 3.8000000e+00 4.0000000e+00 4.2000000e+00 5.4000000e+00 5.6000000e+00 3.7000000e+00 4.4000000e+00 3.6000000e+00 5.4000000e+00 3.6000000e+00 4.4000000e+00 4.7000000e+00 3.5000000e+00 3.6000000e+00 4.3000000e+00 4.5000000e+00 4.8000000e+00 5.1000000e+00 4.3000000e+00 3.8000000e+00 4.3000000e+00 4.8000000e+00 4.3000000e+00 4.2000000e+00 3.5000000e+00 4.1000000e+00 4.3000000e+00 3.8000000e+00 3.8000000e+00 4.6000000e+00 4.4000000e+00 3.9000000e+00 3.7000000e+00 3.9000000e+00 4.1000000e+00 3.8000000e+00 5.0000000e-01 8.0000000e-01 3.0000000e-01 4.0000000e-01 2.0000000e-01 3.0000000e-01 8.0000000e-01 3.0000000e-01 2.0000000e-01 4.0000000e-01 1.2000000e+00 1.3000000e+00 8.0000000e-01 5.0000000e-01 1.1000000e+00 7.0000000e-01 8.0000000e-01 6.0000000e-01 5.0000000e-01 5.0000000e-01 4.0000000e-01 4.0000000e-01 4.0000000e-01 6.0000000e-01 6.0000000e-01 1.0000000e-01 2.0000000e-01 8.0000000e-01 1.0000000e+00 1.1000000e+00 3.0000000e-01 4.0000000e-01 9.0000000e-01 3.0000000e-01 2.0000000e-01 5.0000000e-01 4.0000000e-01 8.0000000e-01 2.0000000e-01 4.0000000e-01 7.0000000e-01 2.0000000e-01 7.0000000e-01 1.0000000e-01 7.0000000e-01 4.0000000e-01 3.2000000e+00 3.0000000e+00 3.4000000e+00 2.5000000e+00 3.1000000e+00 3.0000000e+00 3.2000000e+00 1.8000000e+00 3.1000000e+00 2.4000000e+00 2.0000000e+00 2.7000000e+00 2.5000000e+00 3.2000000e+00 2.1000000e+00 2.9000000e+00 3.0000000e+00 2.6000000e+00 3.0000000e+00 2.4000000e+00 3.3000000e+00 2.5000000e+00 3.4000000e+00 3.2000000e+00 2.8000000e+00 2.9000000e+00 3.3000000e+00 3.5000000e+00 3.0000000e+00 2.0000000e+00 2.3000000e+00 2.2000000e+00 2.4000000e+00 3.6000000e+00 3.0000000e+00 3.0000000e+00 3.2000000e+00 2.9000000e+00 2.6000000e+00 2.5000000e+00 2.9000000e+00 3.1000000e+00 2.5000000e+00 1.8000000e+00 2.7000000e+00 2.7000000e+00 2.7000000e+00 2.8000000e+00 1.5000000e+00 2.6000000e+00 4.5000000e+00 3.6000000e+00 4.4000000e+00 4.1000000e+00 4.3000000e+00 5.1000000e+00 3.0000000e+00 4.8000000e+00 4.3000000e+00 4.6000000e+00 3.6000000e+00 3.8000000e+00 4.0000000e+00 3.5000000e+00 3.6000000e+00 3.8000000e+00 4.0000000e+00 5.2000000e+00 5.4000000e+00 3.5000000e+00 4.2000000e+00 3.4000000e+00 5.2000000e+00 3.4000000e+00 4.2000000e+00 4.5000000e+00 3.3000000e+00 3.4000000e+00 4.1000000e+00 4.3000000e+00 4.6000000e+00 4.9000000e+00 4.1000000e+00 3.6000000e+00 4.1000000e+00 4.6000000e+00 4.1000000e+00 4.0000000e+00 3.3000000e+00 3.9000000e+00 4.1000000e+00 3.6000000e+00 3.6000000e+00 4.4000000e+00 4.2000000e+00 3.7000000e+00 3.5000000e+00 3.7000000e+00 3.9000000e+00 3.6000000e+00 4.0000000e-01 4.0000000e-01 2.0000000e-01 7.0000000e-01 5.0000000e-01 4.0000000e-01 2.0000000e-01 6.0000000e-01 7.0000000e-01 8.0000000e-01 8.0000000e-01 4.0000000e-01 1.0000000e-01 7.0000000e-01 2.0000000e-01 4.0000000e-01 2.0000000e-01 4.0000000e-01 3.0000000e-01 5.0000000e-01 6.0000000e-01 2.0000000e-01 2.0000000e-01 2.0000000e-01 4.0000000e-01 5.0000000e-01 4.0000000e-01 5.0000000e-01 6.0000000e-01 5.0000000e-01 4.0000000e-01 5.0000000e-01 5.0000000e-01 6.0000000e-01 2.0000000e-01 1.0000000e-01 1.3000000e+00 6.0000000e-01 4.0000000e-01 5.0000000e-01 6.0000000e-01 2.0000000e-01 4.0000000e-01 3.0000000e-01 3.0000000e-01 3.3000000e+00 3.1000000e+00 3.5000000e+00 2.6000000e+00 3.2000000e+00 3.1000000e+00 3.3000000e+00 1.9000000e+00 3.2000000e+00 2.5000000e+00 2.1000000e+00 2.8000000e+00 2.6000000e+00 3.3000000e+00 2.2000000e+00 3.0000000e+00 3.1000000e+00 2.7000000e+00 3.1000000e+00 2.5000000e+00 3.4000000e+00 2.6000000e+00 3.5000000e+00 3.3000000e+00 2.9000000e+00 3.0000000e+00 3.4000000e+00 3.6000000e+00 3.1000000e+00 2.1000000e+00 2.4000000e+00 2.3000000e+00 2.5000000e+00 3.7000000e+00 3.1000000e+00 3.1000000e+00 3.3000000e+00 3.0000000e+00 2.7000000e+00 2.6000000e+00 3.0000000e+00 3.2000000e+00 2.6000000e+00 1.9000000e+00 2.8000000e+00 2.8000000e+00 2.8000000e+00 2.9000000e+00 1.6000000e+00 2.7000000e+00 4.6000000e+00 3.7000000e+00 4.5000000e+00 4.2000000e+00 4.4000000e+00 5.2000000e+00 3.1000000e+00 4.9000000e+00 4.4000000e+00 4.7000000e+00 3.7000000e+00 3.9000000e+00 4.1000000e+00 3.6000000e+00 3.7000000e+00 3.9000000e+00 4.1000000e+00 5.3000000e+00 5.5000000e+00 3.6000000e+00 4.3000000e+00 3.5000000e+00 5.3000000e+00 3.5000000e+00 4.3000000e+00 4.6000000e+00 3.4000000e+00 3.5000000e+00 4.2000000e+00 4.4000000e+00 4.7000000e+00 5.0000000e+00 4.2000000e+00 3.7000000e+00 4.2000000e+00 4.7000000e+00 4.2000000e+00 4.1000000e+00 3.4000000e+00 4.0000000e+00 4.2000000e+00 3.7000000e+00 3.7000000e+00 4.5000000e+00 4.3000000e+00 3.8000000e+00 3.6000000e+00 3.8000000e+00 4.0000000e+00 3.7000000e+00 8.0000000e-01 5.0000000e-01 1.0000000e+00 8.0000000e-01 2.0000000e-01 6.0000000e-01 9.0000000e-01 1.1000000e+00 5.0000000e-01 5.0000000e-01 4.0000000e-01 4.0000000e-01 3.0000000e-01 3.0000000e-01 5.0000000e-01 3.0000000e-01 8.0000000e-01 6.0000000e-01 6.0000000e-01 9.0000000e-01 5.0000000e-01 4.0000000e-01 5.0000000e-01 7.0000000e-01 8.0000000e-01 5.0000000e-01 3.0000000e-01 3.0000000e-01 8.0000000e-01 7.0000000e-01 4.0000000e-01 8.0000000e-01 1.0000000e+00 5.0000000e-01 4.0000000e-01 1.6000000e+00 1.0000000e+00 4.0000000e-01 3.0000000e-01 9.0000000e-01 3.0000000e-01 8.0000000e-01 2.0000000e-01 6.0000000e-01 3.0000000e+00 2.8000000e+00 3.2000000e+00 2.3000000e+00 2.9000000e+00 2.8000000e+00 3.0000000e+00 1.6000000e+00 2.9000000e+00 2.2000000e+00 1.9000000e+00 2.5000000e+00 2.3000000e+00 3.0000000e+00 1.9000000e+00 2.7000000e+00 2.8000000e+00 2.4000000e+00 2.8000000e+00 2.2000000e+00 3.1000000e+00 2.3000000e+00 3.2000000e+00 3.0000000e+00 2.6000000e+00 2.7000000e+00 3.1000000e+00 3.3000000e+00 2.8000000e+00 1.8000000e+00 2.1000000e+00 2.0000000e+00 2.2000000e+00 3.4000000e+00 2.8000000e+00 2.8000000e+00 3.0000000e+00 2.7000000e+00 2.4000000e+00 2.3000000e+00 2.7000000e+00 2.9000000e+00 2.3000000e+00 1.6000000e+00 2.5000000e+00 2.5000000e+00 2.5000000e+00 2.6000000e+00 1.4000000e+00 2.4000000e+00 4.3000000e+00 3.4000000e+00 4.2000000e+00 3.9000000e+00 4.1000000e+00 4.9000000e+00 2.8000000e+00 4.6000000e+00 4.1000000e+00 4.4000000e+00 3.4000000e+00 3.6000000e+00 3.8000000e+00 3.3000000e+00 3.4000000e+00 3.6000000e+00 3.8000000e+00 5.0000000e+00 5.2000000e+00 3.3000000e+00 4.0000000e+00 3.2000000e+00 5.0000000e+00 3.2000000e+00 4.0000000e+00 4.3000000e+00 3.1000000e+00 3.2000000e+00 3.9000000e+00 4.1000000e+00 4.4000000e+00 4.7000000e+00 3.9000000e+00 3.4000000e+00 3.9000000e+00 4.4000000e+00 3.9000000e+00 3.8000000e+00 3.1000000e+00 3.7000000e+00 3.9000000e+00 3.4000000e+00 3.4000000e+00 4.2000000e+00 4.0000000e+00 3.5000000e+00 3.3000000e+00 3.5000000e+00 3.7000000e+00 3.4000000e+00 4.0000000e-01 5.0000000e-01 3.0000000e-01 8.0000000e-01 2.0000000e-01 4.0000000e-01 4.0000000e-01 1.2000000e+00 1.1000000e+00 8.0000000e-01 5.0000000e-01 1.1000000e+00 5.0000000e-01 8.0000000e-01 5.0000000e-01 4.0000000e-01 5.0000000e-01 5.0000000e-01 4.0000000e-01 4.0000000e-01 6.0000000e-01 6.0000000e-01 2.0000000e-01 3.0000000e-01 8.0000000e-01 7.0000000e-01 9.0000000e-01 3.0000000e-01 4.0000000e-01 9.0000000e-01 3.0000000e-01 4.0000000e-01 5.0000000e-01 4.0000000e-01 1.1000000e+00 2.0000000e-01 4.0000000e-01 5.0000000e-01 4.0000000e-01 5.0000000e-01 2.0000000e-01 7.0000000e-01 4.0000000e-01 3.3000000e+00 3.1000000e+00 3.5000000e+00 2.6000000e+00 3.2000000e+00 3.1000000e+00 3.3000000e+00 1.9000000e+00 3.2000000e+00 2.5000000e+00 2.1000000e+00 2.8000000e+00 2.6000000e+00 3.3000000e+00 2.2000000e+00 3.0000000e+00 3.1000000e+00 2.7000000e+00 3.1000000e+00 2.5000000e+00 3.4000000e+00 2.6000000e+00 3.5000000e+00 3.3000000e+00 2.9000000e+00 3.0000000e+00 3.4000000e+00 3.6000000e+00 3.1000000e+00 2.1000000e+00 2.4000000e+00 2.3000000e+00 2.5000000e+00 3.7000000e+00 3.1000000e+00 3.1000000e+00 3.3000000e+00 3.0000000e+00 2.7000000e+00 2.6000000e+00 3.0000000e+00 3.2000000e+00 2.6000000e+00 1.9000000e+00 2.8000000e+00 2.8000000e+00 2.8000000e+00 2.9000000e+00 1.6000000e+00 2.7000000e+00 4.6000000e+00 3.7000000e+00 4.5000000e+00 4.2000000e+00 4.4000000e+00 5.2000000e+00 3.1000000e+00 4.9000000e+00 4.4000000e+00 4.7000000e+00 3.7000000e+00 3.9000000e+00 4.1000000e+00 3.6000000e+00 3.7000000e+00 3.9000000e+00 4.1000000e+00 5.3000000e+00 5.5000000e+00 3.6000000e+00 4.3000000e+00 3.5000000e+00 5.3000000e+00 3.5000000e+00 4.3000000e+00 4.6000000e+00 3.4000000e+00 3.5000000e+00 4.2000000e+00 4.4000000e+00 4.7000000e+00 5.0000000e+00 4.2000000e+00 3.7000000e+00 4.2000000e+00 4.7000000e+00 4.2000000e+00 4.1000000e+00 3.4000000e+00 4.0000000e+00 4.2000000e+00 3.7000000e+00 3.7000000e+00 4.5000000e+00 4.3000000e+00 3.8000000e+00 3.6000000e+00 3.8000000e+00 4.0000000e+00 3.7000000e+00 6.0000000e-01 3.0000000e-01 4.0000000e-01 2.0000000e-01 4.0000000e-01 7.0000000e-01 8.0000000e-01 1.0000000e+00 5.0000000e-01 1.0000000e-01 7.0000000e-01 4.0000000e-01 4.0000000e-01 3.0000000e-01 5.0000000e-01 3.0000000e-01 4.0000000e-01 4.0000000e-01 2.0000000e-01 2.0000000e-01 2.0000000e-01 3.0000000e-01 3.0000000e-01 4.0000000e-01 7.0000000e-01 8.0000000e-01 3.0000000e-01 3.0000000e-01 5.0000000e-01 3.0000000e-01 6.0000000e-01 1.0000000e-01 2.0000000e-01 1.1000000e+00 6.0000000e-01 4.0000000e-01 4.0000000e-01 4.0000000e-01 4.0000000e-01 4.0000000e-01 3.0000000e-01 1.0000000e-01 3.2000000e+00 3.0000000e+00 3.4000000e+00 2.5000000e+00 3.1000000e+00 3.0000000e+00 3.2000000e+00 1.8000000e+00 3.1000000e+00 2.4000000e+00 2.0000000e+00 2.7000000e+00 2.5000000e+00 3.2000000e+00 2.1000000e+00 2.9000000e+00 3.0000000e+00 2.6000000e+00 3.0000000e+00 2.4000000e+00 3.3000000e+00 2.5000000e+00 3.4000000e+00 3.2000000e+00 2.8000000e+00 2.9000000e+00 3.3000000e+00 3.5000000e+00 3.0000000e+00 2.0000000e+00 2.3000000e+00 2.2000000e+00 2.4000000e+00 3.6000000e+00 3.0000000e+00 3.0000000e+00 3.2000000e+00 2.9000000e+00 2.6000000e+00 2.5000000e+00 2.9000000e+00 3.1000000e+00 2.5000000e+00 1.8000000e+00 2.7000000e+00 2.7000000e+00 2.7000000e+00 2.8000000e+00 1.5000000e+00 2.6000000e+00 4.5000000e+00 3.6000000e+00 4.4000000e+00 4.1000000e+00 4.3000000e+00 5.1000000e+00 3.0000000e+00 4.8000000e+00 4.3000000e+00 4.6000000e+00 3.6000000e+00 3.8000000e+00 4.0000000e+00 3.5000000e+00 3.6000000e+00 3.8000000e+00 4.0000000e+00 5.2000000e+00 5.4000000e+00 3.5000000e+00 4.2000000e+00 3.4000000e+00 5.2000000e+00 3.4000000e+00 4.2000000e+00 4.5000000e+00 3.3000000e+00 3.4000000e+00 4.1000000e+00 4.3000000e+00 4.6000000e+00 4.9000000e+00 4.1000000e+00 3.6000000e+00 4.1000000e+00 4.6000000e+00 4.1000000e+00 4.0000000e+00 3.3000000e+00 3.9000000e+00 4.1000000e+00 3.6000000e+00 3.6000000e+00 4.4000000e+00 4.2000000e+00 3.7000000e+00 3.5000000e+00 3.7000000e+00 3.9000000e+00 3.6000000e+00 5.0000000e-01 1.0000000e+00 5.0000000e-01 4.0000000e-01 3.0000000e-01 1.4000000e+00 1.5000000e+00 1.0000000e+00 7.0000000e-01 1.3000000e+00 9.0000000e-01 1.0000000e+00 8.0000000e-01 7.0000000e-01 7.0000000e-01 5.0000000e-01 6.0000000e-01 6.0000000e-01 8.0000000e-01 8.0000000e-01 3.0000000e-01 4.0000000e-01 1.0000000e+00 1.2000000e+00 1.3000000e+00 5.0000000e-01 6.0000000e-01 1.1000000e+00 5.0000000e-01 1.0000000e-01 7.0000000e-01 6.0000000e-01 6.0000000e-01 3.0000000e-01 6.0000000e-01 9.0000000e-01 4.0000000e-01 9.0000000e-01 3.0000000e-01 9.0000000e-01 6.0000000e-01 3.3000000e+00 3.1000000e+00 3.5000000e+00 2.6000000e+00 3.2000000e+00 3.1000000e+00 3.3000000e+00 1.9000000e+00 3.2000000e+00 2.5000000e+00 2.1000000e+00 2.8000000e+00 2.6000000e+00 3.3000000e+00 2.2000000e+00 3.0000000e+00 3.1000000e+00 2.7000000e+00 3.1000000e+00 2.5000000e+00 3.4000000e+00 2.6000000e+00 3.5000000e+00 3.3000000e+00 2.9000000e+00 3.0000000e+00 3.4000000e+00 3.6000000e+00 3.1000000e+00 2.1000000e+00 2.4000000e+00 2.3000000e+00 2.5000000e+00 3.7000000e+00 3.1000000e+00 3.1000000e+00 3.3000000e+00 3.0000000e+00 2.7000000e+00 2.6000000e+00 3.0000000e+00 3.2000000e+00 2.6000000e+00 1.9000000e+00 2.8000000e+00 2.8000000e+00 2.8000000e+00 2.9000000e+00 1.6000000e+00 2.7000000e+00 4.6000000e+00 3.7000000e+00 4.5000000e+00 4.2000000e+00 4.4000000e+00 5.2000000e+00 3.1000000e+00 4.9000000e+00 4.4000000e+00 4.7000000e+00 3.7000000e+00 3.9000000e+00 4.1000000e+00 3.6000000e+00 3.7000000e+00 3.9000000e+00 4.1000000e+00 5.3000000e+00 5.5000000e+00 3.6000000e+00 4.3000000e+00 3.5000000e+00 5.3000000e+00 3.5000000e+00 4.3000000e+00 4.6000000e+00 3.4000000e+00 3.5000000e+00 4.2000000e+00 4.4000000e+00 4.7000000e+00 5.0000000e+00 4.2000000e+00 3.7000000e+00 4.2000000e+00 4.7000000e+00 4.2000000e+00 4.1000000e+00 3.4000000e+00 4.0000000e+00 4.2000000e+00 3.7000000e+00 3.7000000e+00 4.5000000e+00 4.3000000e+00 3.8000000e+00 3.6000000e+00 3.8000000e+00 4.0000000e+00 3.7000000e+00 6.0000000e-01 3.0000000e-01 1.0000000e-01 6.0000000e-01 9.0000000e-01 1.3000000e+00 8.0000000e-01 4.0000000e-01 8.0000000e-01 7.0000000e-01 5.0000000e-01 6.0000000e-01 5.0000000e-01 4.0000000e-01 4.0000000e-01 1.0000000e-01 3.0000000e-01 4.0000000e-01 3.0000000e-01 2.0000000e-01 1.0000000e-01 5.0000000e-01 1.0000000e+00 1.1000000e+00 0.0000000e+00 3.0000000e-01 6.0000000e-01 0.0000000e+00 5.0000000e-01 3.0000000e-01 4.0000000e-01 8.0000000e-01 5.0000000e-01 5.0000000e-01 7.0000000e-01 2.0000000e-01 7.0000000e-01 3.0000000e-01 6.0000000e-01 2.0000000e-01 3.2000000e+00 3.0000000e+00 3.4000000e+00 2.5000000e+00 3.1000000e+00 3.0000000e+00 3.2000000e+00 1.8000000e+00 3.1000000e+00 2.4000000e+00 2.0000000e+00 2.7000000e+00 2.5000000e+00 3.2000000e+00 2.1000000e+00 2.9000000e+00 3.0000000e+00 2.6000000e+00 3.0000000e+00 2.4000000e+00 3.3000000e+00 2.5000000e+00 3.4000000e+00 3.2000000e+00 2.8000000e+00 2.9000000e+00 3.3000000e+00 3.5000000e+00 3.0000000e+00 2.0000000e+00 2.3000000e+00 2.2000000e+00 2.4000000e+00 3.6000000e+00 3.0000000e+00 3.0000000e+00 3.2000000e+00 2.9000000e+00 2.6000000e+00 2.5000000e+00 2.9000000e+00 3.1000000e+00 2.5000000e+00 1.8000000e+00 2.7000000e+00 2.7000000e+00 2.7000000e+00 2.8000000e+00 1.5000000e+00 2.6000000e+00 4.5000000e+00 3.6000000e+00 4.4000000e+00 4.1000000e+00 4.3000000e+00 5.1000000e+00 3.0000000e+00 4.8000000e+00 4.3000000e+00 4.6000000e+00 3.6000000e+00 3.8000000e+00 4.0000000e+00 3.5000000e+00 3.6000000e+00 3.8000000e+00 4.0000000e+00 5.2000000e+00 5.4000000e+00 3.5000000e+00 4.2000000e+00 3.4000000e+00 5.2000000e+00 3.4000000e+00 4.2000000e+00 4.5000000e+00 3.3000000e+00 3.4000000e+00 4.1000000e+00 4.3000000e+00 4.6000000e+00 4.9000000e+00 4.1000000e+00 3.6000000e+00 4.1000000e+00 4.6000000e+00 4.1000000e+00 4.0000000e+00 3.3000000e+00 3.9000000e+00 4.1000000e+00 3.6000000e+00 3.6000000e+00 4.4000000e+00 4.2000000e+00 3.7000000e+00 3.5000000e+00 3.7000000e+00 3.9000000e+00 3.6000000e+00 6.0000000e-01 7.0000000e-01 1.1000000e+00 4.0000000e-01 7.0000000e-01 2.0000000e-01 3.0000000e-01 3.0000000e-01 3.0000000e-01 3.0000000e-01 3.0000000e-01 8.0000000e-01 4.0000000e-01 6.0000000e-01 7.0000000e-01 4.0000000e-01 2.0000000e-01 3.0000000e-01 7.0000000e-01 6.0000000e-01 3.0000000e-01 4.0000000e-01 5.0000000e-01 6.0000000e-01 5.0000000e-01 2.0000000e-01 6.0000000e-01 1.0000000e+00 3.0000000e-01 4.0000000e-01 1.4000000e+00 1.0000000e+00 4.0000000e-01 4.0000000e-01 7.0000000e-01 3.0000000e-01 8.0000000e-01 1.0000000e-01 4.0000000e-01 3.2000000e+00 3.0000000e+00 3.4000000e+00 2.5000000e+00 3.1000000e+00 3.0000000e+00 3.2000000e+00 1.8000000e+00 3.1000000e+00 2.4000000e+00 2.0000000e+00 2.7000000e+00 2.5000000e+00 3.2000000e+00 2.1000000e+00 2.9000000e+00 3.0000000e+00 2.6000000e+00 3.0000000e+00 2.4000000e+00 3.3000000e+00 2.5000000e+00 3.4000000e+00 3.2000000e+00 2.8000000e+00 2.9000000e+00 3.3000000e+00 3.5000000e+00 3.0000000e+00 2.0000000e+00 2.3000000e+00 2.2000000e+00 2.4000000e+00 3.6000000e+00 3.0000000e+00 3.0000000e+00 3.2000000e+00 2.9000000e+00 2.6000000e+00 2.5000000e+00 2.9000000e+00 3.1000000e+00 2.5000000e+00 1.8000000e+00 2.7000000e+00 2.7000000e+00 2.7000000e+00 2.8000000e+00 1.5000000e+00 2.6000000e+00 4.5000000e+00 3.6000000e+00 4.4000000e+00 4.1000000e+00 4.3000000e+00 5.1000000e+00 3.0000000e+00 4.8000000e+00 4.3000000e+00 4.6000000e+00 3.6000000e+00 3.8000000e+00 4.0000000e+00 3.5000000e+00 3.6000000e+00 3.8000000e+00 4.0000000e+00 5.2000000e+00 5.4000000e+00 3.5000000e+00 4.2000000e+00 3.4000000e+00 5.2000000e+00 3.4000000e+00 4.2000000e+00 4.5000000e+00 3.3000000e+00 3.4000000e+00 4.1000000e+00 4.3000000e+00 4.6000000e+00 4.9000000e+00 4.1000000e+00 3.6000000e+00 4.1000000e+00 4.6000000e+00 4.1000000e+00 4.0000000e+00 3.3000000e+00 3.9000000e+00 4.1000000e+00 3.6000000e+00 3.6000000e+00 4.4000000e+00 4.2000000e+00 3.7000000e+00 3.5000000e+00 3.7000000e+00 3.9000000e+00 3.6000000e+00 4.0000000e-01 5.0000000e-01 1.0000000e+00 1.0000000e+00 6.0000000e-01 3.0000000e-01 9.0000000e-01 4.0000000e-01 6.0000000e-01 3.0000000e-01 6.0000000e-01 3.0000000e-01 3.0000000e-01 4.0000000e-01 2.0000000e-01 4.0000000e-01 4.0000000e-01 2.0000000e-01 3.0000000e-01 6.0000000e-01 7.0000000e-01 8.0000000e-01 3.0000000e-01 4.0000000e-01 7.0000000e-01 3.0000000e-01 4.0000000e-01 3.0000000e-01 3.0000000e-01 1.1000000e+00 4.0000000e-01 4.0000000e-01 4.0000000e-01 4.0000000e-01 4.0000000e-01 2.0000000e-01 5.0000000e-01 2.0000000e-01 3.1000000e+00 2.9000000e+00 3.3000000e+00 2.4000000e+00 3.0000000e+00 2.9000000e+00 3.1000000e+00 1.7000000e+00 3.0000000e+00 2.3000000e+00 1.9000000e+00 2.6000000e+00 2.4000000e+00 3.1000000e+00 2.0000000e+00 2.8000000e+00 2.9000000e+00 2.5000000e+00 2.9000000e+00 2.3000000e+00 3.2000000e+00 2.4000000e+00 3.3000000e+00 3.1000000e+00 2.7000000e+00 2.8000000e+00 3.2000000e+00 3.4000000e+00 2.9000000e+00 1.9000000e+00 2.2000000e+00 2.1000000e+00 2.3000000e+00 3.5000000e+00 2.9000000e+00 2.9000000e+00 3.1000000e+00 2.8000000e+00 2.5000000e+00 2.4000000e+00 2.8000000e+00 3.0000000e+00 2.4000000e+00 1.7000000e+00 2.6000000e+00 2.6000000e+00 2.6000000e+00 2.7000000e+00 1.4000000e+00 2.5000000e+00 4.4000000e+00 3.5000000e+00 4.3000000e+00 4.0000000e+00 4.2000000e+00 5.0000000e+00 2.9000000e+00 4.7000000e+00 4.2000000e+00 4.5000000e+00 3.5000000e+00 3.7000000e+00 3.9000000e+00 3.4000000e+00 3.5000000e+00 3.7000000e+00 3.9000000e+00 5.1000000e+00 5.3000000e+00 3.4000000e+00 4.1000000e+00 3.3000000e+00 5.1000000e+00 3.3000000e+00 4.1000000e+00 4.4000000e+00 3.2000000e+00 3.3000000e+00 4.0000000e+00 4.2000000e+00 4.5000000e+00 4.8000000e+00 4.0000000e+00 3.5000000e+00 4.0000000e+00 4.5000000e+00 4.0000000e+00 3.9000000e+00 3.2000000e+00 3.8000000e+00 4.0000000e+00 3.5000000e+00 3.5000000e+00 4.3000000e+00 4.1000000e+00 3.6000000e+00 3.4000000e+00 3.6000000e+00 3.8000000e+00 3.5000000e+00 5.0000000e-01 1.0000000e+00 1.4000000e+00 9.0000000e-01 5.0000000e-01 9.0000000e-01 8.0000000e-01 6.0000000e-01 7.0000000e-01 6.0000000e-01 4.0000000e-01 5.0000000e-01 2.0000000e-01 4.0000000e-01 5.0000000e-01 4.0000000e-01 2.0000000e-01 2.0000000e-01 6.0000000e-01 1.1000000e+00 1.2000000e+00 1.0000000e-01 2.0000000e-01 7.0000000e-01 1.0000000e-01 4.0000000e-01 4.0000000e-01 5.0000000e-01 7.0000000e-01 4.0000000e-01 5.0000000e-01 8.0000000e-01 2.0000000e-01 8.0000000e-01 2.0000000e-01 7.0000000e-01 3.0000000e-01 3.3000000e+00 3.1000000e+00 3.5000000e+00 2.6000000e+00 3.2000000e+00 3.1000000e+00 3.3000000e+00 1.9000000e+00 3.2000000e+00 2.5000000e+00 2.1000000e+00 2.8000000e+00 2.6000000e+00 3.3000000e+00 2.2000000e+00 3.0000000e+00 3.1000000e+00 2.7000000e+00 3.1000000e+00 2.5000000e+00 3.4000000e+00 2.6000000e+00 3.5000000e+00 3.3000000e+00 2.9000000e+00 3.0000000e+00 3.4000000e+00 3.6000000e+00 3.1000000e+00 2.1000000e+00 2.4000000e+00 2.3000000e+00 2.5000000e+00 3.7000000e+00 3.1000000e+00 3.1000000e+00 3.3000000e+00 3.0000000e+00 2.7000000e+00 2.6000000e+00 3.0000000e+00 3.2000000e+00 2.6000000e+00 1.9000000e+00 2.8000000e+00 2.8000000e+00 2.8000000e+00 2.9000000e+00 1.6000000e+00 2.7000000e+00 4.6000000e+00 3.7000000e+00 4.5000000e+00 4.2000000e+00 4.4000000e+00 5.2000000e+00 3.1000000e+00 4.9000000e+00 4.4000000e+00 4.7000000e+00 3.7000000e+00 3.9000000e+00 4.1000000e+00 3.6000000e+00 3.7000000e+00 3.9000000e+00 4.1000000e+00 5.3000000e+00 5.5000000e+00 3.6000000e+00 4.3000000e+00 3.5000000e+00 5.3000000e+00 3.5000000e+00 4.3000000e+00 4.6000000e+00 3.4000000e+00 3.5000000e+00 4.2000000e+00 4.4000000e+00 4.7000000e+00 5.0000000e+00 4.2000000e+00 3.7000000e+00 4.2000000e+00 4.7000000e+00 4.2000000e+00 4.1000000e+00 3.4000000e+00 4.0000000e+00 4.2000000e+00 3.7000000e+00 3.7000000e+00 4.5000000e+00 4.3000000e+00 3.8000000e+00 3.6000000e+00 3.8000000e+00 4.0000000e+00 3.7000000e+00 1.5000000e+00 1.4000000e+00 1.1000000e+00 8.0000000e-01 1.4000000e+00 8.0000000e-01 1.1000000e+00 8.0000000e-01 6.0000000e-01 8.0000000e-01 8.0000000e-01 7.0000000e-01 7.0000000e-01 9.0000000e-01 9.0000000e-01 5.0000000e-01 5.0000000e-01 1.1000000e+00 1.1000000e+00 1.2000000e+00 6.0000000e-01 7.0000000e-01 1.2000000e+00 6.0000000e-01 2.0000000e-01 8.0000000e-01 7.0000000e-01 7.0000000e-01 2.0000000e-01 7.0000000e-01 8.0000000e-01 5.0000000e-01 8.0000000e-01 3.0000000e-01 1.0000000e+00 7.0000000e-01 3.6000000e+00 3.4000000e+00 3.8000000e+00 2.9000000e+00 3.5000000e+00 3.4000000e+00 3.6000000e+00 2.2000000e+00 3.5000000e+00 2.8000000e+00 2.4000000e+00 3.1000000e+00 2.9000000e+00 3.6000000e+00 2.5000000e+00 3.3000000e+00 3.4000000e+00 3.0000000e+00 3.4000000e+00 2.8000000e+00 3.7000000e+00 2.9000000e+00 3.8000000e+00 3.6000000e+00 3.2000000e+00 3.3000000e+00 3.7000000e+00 3.9000000e+00 3.4000000e+00 2.4000000e+00 2.7000000e+00 2.6000000e+00 2.8000000e+00 4.0000000e+00 3.4000000e+00 3.4000000e+00 3.6000000e+00 3.3000000e+00 3.0000000e+00 2.9000000e+00 3.3000000e+00 3.5000000e+00 2.9000000e+00 2.2000000e+00 3.1000000e+00 3.1000000e+00 3.1000000e+00 3.2000000e+00 1.9000000e+00 3.0000000e+00 4.9000000e+00 4.0000000e+00 4.8000000e+00 4.5000000e+00 4.7000000e+00 5.5000000e+00 3.4000000e+00 5.2000000e+00 4.7000000e+00 5.0000000e+00 4.0000000e+00 4.2000000e+00 4.4000000e+00 3.9000000e+00 4.0000000e+00 4.2000000e+00 4.4000000e+00 5.6000000e+00 5.8000000e+00 3.9000000e+00 4.6000000e+00 3.8000000e+00 5.6000000e+00 3.8000000e+00 4.6000000e+00 4.9000000e+00 3.7000000e+00 3.8000000e+00 4.5000000e+00 4.7000000e+00 5.0000000e+00 5.3000000e+00 4.5000000e+00 4.0000000e+00 4.5000000e+00 5.0000000e+00 4.5000000e+00 4.4000000e+00 3.7000000e+00 4.3000000e+00 4.5000000e+00 4.0000000e+00 4.0000000e+00 4.8000000e+00 4.6000000e+00 4.1000000e+00 3.9000000e+00 4.1000000e+00 4.3000000e+00 4.0000000e+00 4.0000000e-01 4.0000000e-01 7.0000000e-01 5.0000000e-01 7.0000000e-01 6.0000000e-01 7.0000000e-01 1.2000000e+00 7.0000000e-01 1.0000000e+00 1.0000000e+00 8.0000000e-01 6.0000000e-01 6.0000000e-01 1.1000000e+00 1.0000000e+00 6.0000000e-01 6.0000000e-01 3.0000000e-01 9.0000000e-01 8.0000000e-01 5.0000000e-01 9.0000000e-01 1.4000000e+00 7.0000000e-01 8.0000000e-01 1.7000000e+00 1.4000000e+00 8.0000000e-01 7.0000000e-01 1.0000000e+00 7.0000000e-01 1.2000000e+00 5.0000000e-01 8.0000000e-01 3.5000000e+00 3.3000000e+00 3.7000000e+00 2.8000000e+00 3.4000000e+00 3.3000000e+00 3.5000000e+00 2.1000000e+00 3.4000000e+00 2.7000000e+00 2.3000000e+00 3.0000000e+00 2.8000000e+00 3.5000000e+00 2.4000000e+00 3.2000000e+00 3.3000000e+00 2.9000000e+00 3.3000000e+00 2.7000000e+00 3.6000000e+00 2.8000000e+00 3.7000000e+00 3.5000000e+00 3.1000000e+00 3.2000000e+00 3.6000000e+00 3.8000000e+00 3.3000000e+00 2.3000000e+00 2.6000000e+00 2.5000000e+00 2.7000000e+00 3.9000000e+00 3.3000000e+00 3.3000000e+00 3.5000000e+00 3.2000000e+00 2.9000000e+00 2.8000000e+00 3.2000000e+00 3.4000000e+00 2.8000000e+00 2.1000000e+00 3.0000000e+00 3.0000000e+00 3.0000000e+00 3.1000000e+00 1.8000000e+00 2.9000000e+00 4.8000000e+00 3.9000000e+00 4.7000000e+00 4.4000000e+00 4.6000000e+00 5.4000000e+00 3.3000000e+00 5.1000000e+00 4.6000000e+00 4.9000000e+00 3.9000000e+00 4.1000000e+00 4.3000000e+00 3.8000000e+00 3.9000000e+00 4.1000000e+00 4.3000000e+00 5.5000000e+00 5.7000000e+00 3.8000000e+00 4.5000000e+00 3.7000000e+00 5.5000000e+00 3.7000000e+00 4.5000000e+00 4.8000000e+00 3.6000000e+00 3.7000000e+00 4.4000000e+00 4.6000000e+00 4.9000000e+00 5.2000000e+00 4.4000000e+00 3.9000000e+00 4.4000000e+00 4.9000000e+00 4.4000000e+00 4.3000000e+00 3.6000000e+00 4.2000000e+00 4.4000000e+00 3.9000000e+00 3.9000000e+00 4.7000000e+00 4.5000000e+00 4.0000000e+00 3.8000000e+00 4.0000000e+00 4.2000000e+00 3.9000000e+00 5.0000000e-01 9.0000000e-01 6.0000000e-01 6.0000000e-01 1.0000000e+00 7.0000000e-01 1.1000000e+00 1.1000000e+00 1.0000000e+00 1.4000000e+00 1.0000000e+00 9.0000000e-01 1.0000000e+00 1.2000000e+00 1.3000000e+00 1.0000000e+00 5.0000000e-01 2.0000000e-01 1.3000000e+00 1.2000000e+00 9.0000000e-01 1.3000000e+00 1.4000000e+00 1.0000000e+00 9.0000000e-01 2.1000000e+00 1.3000000e+00 9.0000000e-01 6.0000000e-01 1.4000000e+00 6.0000000e-01 1.2000000e+00 7.0000000e-01 1.1000000e+00 3.2000000e+00 3.0000000e+00 3.4000000e+00 2.5000000e+00 3.1000000e+00 3.0000000e+00 3.2000000e+00 2.0000000e+00 3.1000000e+00 2.4000000e+00 2.4000000e+00 2.7000000e+00 2.5000000e+00 3.2000000e+00 2.1000000e+00 2.9000000e+00 3.0000000e+00 2.6000000e+00 3.0000000e+00 2.4000000e+00 3.3000000e+00 2.5000000e+00 3.4000000e+00 3.2000000e+00 2.8000000e+00 2.9000000e+00 3.3000000e+00 3.5000000e+00 3.0000000e+00 2.0000000e+00 2.3000000e+00 2.2000000e+00 2.4000000e+00 3.6000000e+00 3.0000000e+00 3.0000000e+00 3.2000000e+00 2.9000000e+00 2.6000000e+00 2.5000000e+00 2.9000000e+00 3.1000000e+00 2.5000000e+00 2.1000000e+00 2.7000000e+00 2.7000000e+00 2.7000000e+00 2.8000000e+00 1.9000000e+00 2.6000000e+00 4.5000000e+00 3.6000000e+00 4.4000000e+00 4.1000000e+00 4.3000000e+00 5.1000000e+00 3.0000000e+00 4.8000000e+00 4.3000000e+00 4.6000000e+00 3.6000000e+00 3.8000000e+00 4.0000000e+00 3.5000000e+00 3.6000000e+00 3.8000000e+00 4.0000000e+00 5.2000000e+00 5.4000000e+00 3.5000000e+00 4.2000000e+00 3.4000000e+00 5.2000000e+00 3.4000000e+00 4.2000000e+00 4.5000000e+00 3.3000000e+00 3.4000000e+00 4.1000000e+00 4.3000000e+00 4.6000000e+00 4.9000000e+00 4.1000000e+00 3.6000000e+00 4.1000000e+00 4.6000000e+00 4.1000000e+00 4.0000000e+00 3.3000000e+00 3.9000000e+00 4.1000000e+00 3.6000000e+00 3.6000000e+00 4.4000000e+00 4.2000000e+00 3.7000000e+00 3.5000000e+00 3.7000000e+00 3.9000000e+00 3.6000000e+00 4.0000000e-01 4.0000000e-01 3.0000000e-01 5.0000000e-01 3.0000000e-01 8.0000000e-01 6.0000000e-01 6.0000000e-01 9.0000000e-01 5.0000000e-01 4.0000000e-01 5.0000000e-01 7.0000000e-01 8.0000000e-01 5.0000000e-01 3.0000000e-01 3.0000000e-01 8.0000000e-01 7.0000000e-01 4.0000000e-01 8.0000000e-01 1.0000000e+00 5.0000000e-01 4.0000000e-01 1.6000000e+00 1.0000000e+00 4.0000000e-01 6.0000000e-01 9.0000000e-01 3.0000000e-01 8.0000000e-01 2.0000000e-01 6.0000000e-01 3.4000000e+00 3.2000000e+00 3.6000000e+00 2.7000000e+00 3.3000000e+00 3.2000000e+00 3.4000000e+00 2.0000000e+00 3.3000000e+00 2.6000000e+00 2.2000000e+00 2.9000000e+00 2.7000000e+00 3.4000000e+00 2.3000000e+00 3.1000000e+00 3.2000000e+00 2.8000000e+00 3.2000000e+00 2.6000000e+00 3.5000000e+00 2.7000000e+00 3.6000000e+00 3.4000000e+00 3.0000000e+00 3.1000000e+00 3.5000000e+00 3.7000000e+00 3.2000000e+00 2.2000000e+00 2.5000000e+00 2.4000000e+00 2.6000000e+00 3.8000000e+00 3.2000000e+00 3.2000000e+00 3.4000000e+00 3.1000000e+00 2.8000000e+00 2.7000000e+00 3.1000000e+00 3.3000000e+00 2.7000000e+00 2.0000000e+00 2.9000000e+00 2.9000000e+00 2.9000000e+00 3.0000000e+00 1.7000000e+00 2.8000000e+00 4.7000000e+00 3.8000000e+00 4.6000000e+00 4.3000000e+00 4.5000000e+00 5.3000000e+00 3.2000000e+00 5.0000000e+00 4.5000000e+00 4.8000000e+00 3.8000000e+00 4.0000000e+00 4.2000000e+00 3.7000000e+00 3.8000000e+00 4.0000000e+00 4.2000000e+00 5.4000000e+00 5.6000000e+00 3.7000000e+00 4.4000000e+00 3.6000000e+00 5.4000000e+00 3.6000000e+00 4.4000000e+00 4.7000000e+00 3.5000000e+00 3.6000000e+00 4.3000000e+00 4.5000000e+00 4.8000000e+00 5.1000000e+00 4.3000000e+00 3.8000000e+00 4.3000000e+00 4.8000000e+00 4.3000000e+00 4.2000000e+00 3.5000000e+00 4.1000000e+00 4.3000000e+00 3.8000000e+00 3.8000000e+00 4.6000000e+00 4.4000000e+00 3.9000000e+00 3.7000000e+00 3.9000000e+00 4.1000000e+00 3.8000000e+00 6.0000000e-01 3.0000000e-01 3.0000000e-01 2.0000000e-01 5.0000000e-01 3.0000000e-01 5.0000000e-01 5.0000000e-01 2.0000000e-01 1.0000000e-01 1.0000000e-01 4.0000000e-01 4.0000000e-01 3.0000000e-01 6.0000000e-01 7.0000000e-01 4.0000000e-01 3.0000000e-01 4.0000000e-01 4.0000000e-01 7.0000000e-01 1.0000000e-01 1.0000000e-01 1.2000000e+00 7.0000000e-01 3.0000000e-01 5.0000000e-01 5.0000000e-01 3.0000000e-01 5.0000000e-01 2.0000000e-01 2.0000000e-01 3.3000000e+00 3.1000000e+00 3.5000000e+00 2.6000000e+00 3.2000000e+00 3.1000000e+00 3.3000000e+00 1.9000000e+00 3.2000000e+00 2.5000000e+00 2.1000000e+00 2.8000000e+00 2.6000000e+00 3.3000000e+00 2.2000000e+00 3.0000000e+00 3.1000000e+00 2.7000000e+00 3.1000000e+00 2.5000000e+00 3.4000000e+00 2.6000000e+00 3.5000000e+00 3.3000000e+00 2.9000000e+00 3.0000000e+00 3.4000000e+00 3.6000000e+00 3.1000000e+00 2.1000000e+00 2.4000000e+00 2.3000000e+00 2.5000000e+00 3.7000000e+00 3.1000000e+00 3.1000000e+00 3.3000000e+00 3.0000000e+00 2.7000000e+00 2.6000000e+00 3.0000000e+00 3.2000000e+00 2.6000000e+00 1.9000000e+00 2.8000000e+00 2.8000000e+00 2.8000000e+00 2.9000000e+00 1.6000000e+00 2.7000000e+00 4.6000000e+00 3.7000000e+00 4.5000000e+00 4.2000000e+00 4.4000000e+00 5.2000000e+00 3.1000000e+00 4.9000000e+00 4.4000000e+00 4.7000000e+00 3.7000000e+00 3.9000000e+00 4.1000000e+00 3.6000000e+00 3.7000000e+00 3.9000000e+00 4.1000000e+00 5.3000000e+00 5.5000000e+00 3.6000000e+00 4.3000000e+00 3.5000000e+00 5.3000000e+00 3.5000000e+00 4.3000000e+00 4.6000000e+00 3.4000000e+00 3.5000000e+00 4.2000000e+00 4.4000000e+00 4.7000000e+00 5.0000000e+00 4.2000000e+00 3.7000000e+00 4.2000000e+00 4.7000000e+00 4.2000000e+00 4.1000000e+00 3.4000000e+00 4.0000000e+00 4.2000000e+00 3.7000000e+00 3.7000000e+00 4.5000000e+00 4.3000000e+00 3.8000000e+00 3.6000000e+00 3.8000000e+00 4.0000000e+00 3.7000000e+00 6.0000000e-01 4.0000000e-01 6.0000000e-01 1.1000000e+00 6.0000000e-01 9.0000000e-01 8.0000000e-01 7.0000000e-01 5.0000000e-01 5.0000000e-01 1.0000000e+00 9.0000000e-01 4.0000000e-01 5.0000000e-01 4.0000000e-01 8.0000000e-01 7.0000000e-01 4.0000000e-01 8.0000000e-01 1.3000000e+00 6.0000000e-01 7.0000000e-01 1.5000000e+00 1.3000000e+00 7.0000000e-01 6.0000000e-01 9.0000000e-01 6.0000000e-01 1.1000000e+00 4.0000000e-01 7.0000000e-01 3.0000000e+00 2.8000000e+00 3.2000000e+00 2.3000000e+00 2.9000000e+00 2.8000000e+00 3.0000000e+00 1.6000000e+00 2.9000000e+00 2.2000000e+00 1.8000000e+00 2.5000000e+00 2.3000000e+00 3.0000000e+00 1.9000000e+00 2.7000000e+00 2.8000000e+00 2.4000000e+00 2.8000000e+00 2.2000000e+00 3.1000000e+00 2.3000000e+00 3.2000000e+00 3.0000000e+00 2.6000000e+00 2.7000000e+00 3.1000000e+00 3.3000000e+00 2.8000000e+00 1.8000000e+00 2.1000000e+00 2.0000000e+00 2.2000000e+00 3.4000000e+00 2.8000000e+00 2.8000000e+00 3.0000000e+00 2.7000000e+00 2.4000000e+00 2.3000000e+00 2.7000000e+00 2.9000000e+00 2.3000000e+00 1.6000000e+00 2.5000000e+00 2.5000000e+00 2.5000000e+00 2.6000000e+00 1.3000000e+00 2.4000000e+00 4.3000000e+00 3.4000000e+00 4.2000000e+00 3.9000000e+00 4.1000000e+00 4.9000000e+00 2.8000000e+00 4.6000000e+00 4.1000000e+00 4.4000000e+00 3.4000000e+00 3.6000000e+00 3.8000000e+00 3.3000000e+00 3.4000000e+00 3.6000000e+00 3.8000000e+00 5.0000000e+00 5.2000000e+00 3.3000000e+00 4.0000000e+00 3.2000000e+00 5.0000000e+00 3.2000000e+00 4.0000000e+00 4.3000000e+00 3.1000000e+00 3.2000000e+00 3.9000000e+00 4.1000000e+00 4.4000000e+00 4.7000000e+00 3.9000000e+00 3.4000000e+00 3.9000000e+00 4.4000000e+00 3.9000000e+00 3.8000000e+00 3.1000000e+00 3.7000000e+00 3.9000000e+00 3.4000000e+00 3.4000000e+00 4.2000000e+00 4.0000000e+00 3.5000000e+00 3.3000000e+00 3.5000000e+00 3.7000000e+00 3.4000000e+00 4.0000000e-01 1.0000000e-01 5.0000000e-01 5.0000000e-01 4.0000000e-01 8.0000000e-01 4.0000000e-01 3.0000000e-01 4.0000000e-01 6.0000000e-01 7.0000000e-01 4.0000000e-01 3.0000000e-01 4.0000000e-01 7.0000000e-01 6.0000000e-01 4.0000000e-01 7.0000000e-01 8.0000000e-01 4.0000000e-01 3.0000000e-01 1.5000000e+00 7.0000000e-01 3.0000000e-01 4.0000000e-01 8.0000000e-01 1.0000000e-01 6.0000000e-01 2.0000000e-01 5.0000000e-01 3.2000000e+00 3.0000000e+00 3.4000000e+00 2.5000000e+00 3.1000000e+00 3.0000000e+00 3.2000000e+00 1.8000000e+00 3.1000000e+00 2.4000000e+00 2.0000000e+00 2.7000000e+00 2.5000000e+00 3.2000000e+00 2.1000000e+00 2.9000000e+00 3.0000000e+00 2.6000000e+00 3.0000000e+00 2.4000000e+00 3.3000000e+00 2.5000000e+00 3.4000000e+00 3.2000000e+00 2.8000000e+00 2.9000000e+00 3.3000000e+00 3.5000000e+00 3.0000000e+00 2.0000000e+00 2.3000000e+00 2.2000000e+00 2.4000000e+00 3.6000000e+00 3.0000000e+00 3.0000000e+00 3.2000000e+00 2.9000000e+00 2.6000000e+00 2.5000000e+00 2.9000000e+00 3.1000000e+00 2.5000000e+00 1.8000000e+00 2.7000000e+00 2.7000000e+00 2.7000000e+00 2.8000000e+00 1.5000000e+00 2.6000000e+00 4.5000000e+00 3.6000000e+00 4.4000000e+00 4.1000000e+00 4.3000000e+00 5.1000000e+00 3.0000000e+00 4.8000000e+00 4.3000000e+00 4.6000000e+00 3.6000000e+00 3.8000000e+00 4.0000000e+00 3.5000000e+00 3.6000000e+00 3.8000000e+00 4.0000000e+00 5.2000000e+00 5.4000000e+00 3.5000000e+00 4.2000000e+00 3.4000000e+00 5.2000000e+00 3.4000000e+00 4.2000000e+00 4.5000000e+00 3.3000000e+00 3.4000000e+00 4.1000000e+00 4.3000000e+00 4.6000000e+00 4.9000000e+00 4.1000000e+00 3.6000000e+00 4.1000000e+00 4.6000000e+00 4.1000000e+00 4.0000000e+00 3.3000000e+00 3.9000000e+00 4.1000000e+00 3.6000000e+00 3.6000000e+00 4.4000000e+00 4.2000000e+00 3.7000000e+00 3.5000000e+00 3.7000000e+00 3.9000000e+00 3.6000000e+00 3.0000000e-01 8.0000000e-01 3.0000000e-01 6.0000000e-01 4.0000000e-01 4.0000000e-01 2.0000000e-01 3.0000000e-01 7.0000000e-01 6.0000000e-01 2.0000000e-01 7.0000000e-01 8.0000000e-01 5.0000000e-01 5.0000000e-01 4.0000000e-01 5.0000000e-01 1.0000000e+00 3.0000000e-01 4.0000000e-01 1.1000000e+00 1.0000000e+00 4.0000000e-01 4.0000000e-01 6.0000000e-01 4.0000000e-01 8.0000000e-01 3.0000000e-01 4.0000000e-01 3.0000000e+00 2.8000000e+00 3.2000000e+00 2.3000000e+00 2.9000000e+00 2.8000000e+00 3.0000000e+00 1.6000000e+00 2.9000000e+00 2.2000000e+00 1.8000000e+00 2.5000000e+00 2.3000000e+00 3.0000000e+00 1.9000000e+00 2.7000000e+00 2.8000000e+00 2.4000000e+00 2.8000000e+00 2.2000000e+00 3.1000000e+00 2.3000000e+00 3.2000000e+00 3.0000000e+00 2.6000000e+00 2.7000000e+00 3.1000000e+00 3.3000000e+00 2.8000000e+00 1.8000000e+00 2.1000000e+00 2.0000000e+00 2.2000000e+00 3.4000000e+00 2.8000000e+00 2.8000000e+00 3.0000000e+00 2.7000000e+00 2.4000000e+00 2.3000000e+00 2.7000000e+00 2.9000000e+00 2.3000000e+00 1.6000000e+00 2.5000000e+00 2.5000000e+00 2.5000000e+00 2.6000000e+00 1.3000000e+00 2.4000000e+00 4.3000000e+00 3.4000000e+00 4.2000000e+00 3.9000000e+00 4.1000000e+00 4.9000000e+00 2.8000000e+00 4.6000000e+00 4.1000000e+00 4.4000000e+00 3.4000000e+00 3.6000000e+00 3.8000000e+00 3.3000000e+00 3.4000000e+00 3.6000000e+00 3.8000000e+00 5.0000000e+00 5.2000000e+00 3.3000000e+00 4.0000000e+00 3.2000000e+00 5.0000000e+00 3.2000000e+00 4.0000000e+00 4.3000000e+00 3.1000000e+00 3.2000000e+00 3.9000000e+00 4.1000000e+00 4.4000000e+00 4.7000000e+00 3.9000000e+00 3.4000000e+00 3.9000000e+00 4.4000000e+00 3.9000000e+00 3.8000000e+00 3.1000000e+00 3.7000000e+00 3.9000000e+00 3.4000000e+00 3.4000000e+00 4.2000000e+00 4.0000000e+00 3.5000000e+00 3.3000000e+00 3.5000000e+00 3.7000000e+00 3.4000000e+00 5.0000000e-01 4.0000000e-01 4.0000000e-01 7.0000000e-01 3.0000000e-01 2.0000000e-01 3.0000000e-01 5.0000000e-01 6.0000000e-01 3.0000000e-01 4.0000000e-01 5.0000000e-01 6.0000000e-01 5.0000000e-01 4.0000000e-01 6.0000000e-01 7.0000000e-01 3.0000000e-01 2.0000000e-01 1.4000000e+00 7.0000000e-01 2.0000000e-01 4.0000000e-01 7.0000000e-01 2.0000000e-01 5.0000000e-01 2.0000000e-01 4.0000000e-01 3.2000000e+00 3.0000000e+00 3.4000000e+00 2.5000000e+00 3.1000000e+00 3.0000000e+00 3.2000000e+00 1.8000000e+00 3.1000000e+00 2.4000000e+00 2.0000000e+00 2.7000000e+00 2.5000000e+00 3.2000000e+00 2.1000000e+00 2.9000000e+00 3.0000000e+00 2.6000000e+00 3.0000000e+00 2.4000000e+00 3.3000000e+00 2.5000000e+00 3.4000000e+00 3.2000000e+00 2.8000000e+00 2.9000000e+00 3.3000000e+00 3.5000000e+00 3.0000000e+00 2.0000000e+00 2.3000000e+00 2.2000000e+00 2.4000000e+00 3.6000000e+00 3.0000000e+00 3.0000000e+00 3.2000000e+00 2.9000000e+00 2.6000000e+00 2.5000000e+00 2.9000000e+00 3.1000000e+00 2.5000000e+00 1.8000000e+00 2.7000000e+00 2.7000000e+00 2.7000000e+00 2.8000000e+00 1.5000000e+00 2.6000000e+00 4.5000000e+00 3.6000000e+00 4.4000000e+00 4.1000000e+00 4.3000000e+00 5.1000000e+00 3.0000000e+00 4.8000000e+00 4.3000000e+00 4.6000000e+00 3.6000000e+00 3.8000000e+00 4.0000000e+00 3.5000000e+00 3.6000000e+00 3.8000000e+00 4.0000000e+00 5.2000000e+00 5.4000000e+00 3.5000000e+00 4.2000000e+00 3.4000000e+00 5.2000000e+00 3.4000000e+00 4.2000000e+00 4.5000000e+00 3.3000000e+00 3.4000000e+00 4.1000000e+00 4.3000000e+00 4.6000000e+00 4.9000000e+00 4.1000000e+00 3.6000000e+00 4.1000000e+00 4.6000000e+00 4.1000000e+00 4.0000000e+00 3.3000000e+00 3.9000000e+00 4.1000000e+00 3.6000000e+00 3.6000000e+00 4.4000000e+00 4.2000000e+00 3.7000000e+00 3.5000000e+00 3.7000000e+00 3.9000000e+00 3.6000000e+00 7.0000000e-01 9.0000000e-01 6.0000000e-01 6.0000000e-01 6.0000000e-01 6.0000000e-01 6.0000000e-01 6.0000000e-01 8.0000000e-01 6.0000000e-01 9.0000000e-01 5.0000000e-01 4.0000000e-01 9.0000000e-01 5.0000000e-01 6.0000000e-01 5.0000000e-01 4.0000000e-01 1.3000000e+00 4.0000000e-01 6.0000000e-01 9.0000000e-01 6.0000000e-01 6.0000000e-01 4.0000000e-01 7.0000000e-01 4.0000000e-01 3.7000000e+00 3.5000000e+00 3.9000000e+00 3.0000000e+00 3.6000000e+00 3.5000000e+00 3.7000000e+00 2.3000000e+00 3.6000000e+00 2.9000000e+00 2.5000000e+00 3.2000000e+00 3.0000000e+00 3.7000000e+00 2.6000000e+00 3.4000000e+00 3.5000000e+00 3.1000000e+00 3.5000000e+00 2.9000000e+00 3.8000000e+00 3.0000000e+00 3.9000000e+00 3.7000000e+00 3.3000000e+00 3.4000000e+00 3.8000000e+00 4.0000000e+00 3.5000000e+00 2.5000000e+00 2.8000000e+00 2.7000000e+00 2.9000000e+00 4.1000000e+00 3.5000000e+00 3.5000000e+00 3.7000000e+00 3.4000000e+00 3.1000000e+00 3.0000000e+00 3.4000000e+00 3.6000000e+00 3.0000000e+00 2.3000000e+00 3.2000000e+00 3.2000000e+00 3.2000000e+00 3.3000000e+00 2.0000000e+00 3.1000000e+00 5.0000000e+00 4.1000000e+00 4.9000000e+00 4.6000000e+00 4.8000000e+00 5.6000000e+00 3.5000000e+00 5.3000000e+00 4.8000000e+00 5.1000000e+00 4.1000000e+00 4.3000000e+00 4.5000000e+00 4.0000000e+00 4.1000000e+00 4.3000000e+00 4.5000000e+00 5.7000000e+00 5.9000000e+00 4.0000000e+00 4.7000000e+00 3.9000000e+00 5.7000000e+00 3.9000000e+00 4.7000000e+00 5.0000000e+00 3.8000000e+00 3.9000000e+00 4.6000000e+00 4.8000000e+00 5.1000000e+00 5.4000000e+00 4.6000000e+00 4.1000000e+00 4.6000000e+00 5.1000000e+00 4.6000000e+00 4.5000000e+00 3.8000000e+00 4.4000000e+00 4.6000000e+00 4.1000000e+00 4.1000000e+00 4.9000000e+00 4.7000000e+00 4.2000000e+00 4.0000000e+00 4.2000000e+00 4.4000000e+00 4.1000000e+00 3.0000000e-01 3.0000000e-01 1.0000000e-01 3.0000000e-01 3.0000000e-01 4.0000000e-01 3.0000000e-01 3.0000000e-01 8.0000000e-01 9.0000000e-01 4.0000000e-01 5.0000000e-01 4.0000000e-01 4.0000000e-01 7.0000000e-01 3.0000000e-01 4.0000000e-01 1.0000000e+00 7.0000000e-01 2.0000000e-01 5.0000000e-01 3.0000000e-01 5.0000000e-01 5.0000000e-01 4.0000000e-01 3.0000000e-01 3.0000000e+00 2.8000000e+00 3.2000000e+00 2.3000000e+00 2.9000000e+00 2.8000000e+00 3.0000000e+00 1.6000000e+00 2.9000000e+00 2.2000000e+00 1.8000000e+00 2.5000000e+00 2.3000000e+00 3.0000000e+00 1.9000000e+00 2.7000000e+00 2.8000000e+00 2.4000000e+00 2.8000000e+00 2.2000000e+00 3.1000000e+00 2.3000000e+00 3.2000000e+00 3.0000000e+00 2.6000000e+00 2.7000000e+00 3.1000000e+00 3.3000000e+00 2.8000000e+00 1.8000000e+00 2.1000000e+00 2.0000000e+00 2.2000000e+00 3.4000000e+00 2.8000000e+00 2.8000000e+00 3.0000000e+00 2.7000000e+00 2.4000000e+00 2.3000000e+00 2.7000000e+00 2.9000000e+00 2.3000000e+00 1.6000000e+00 2.5000000e+00 2.5000000e+00 2.5000000e+00 2.6000000e+00 1.3000000e+00 2.4000000e+00 4.3000000e+00 3.4000000e+00 4.2000000e+00 3.9000000e+00 4.1000000e+00 4.9000000e+00 2.8000000e+00 4.6000000e+00 4.1000000e+00 4.4000000e+00 3.4000000e+00 3.6000000e+00 3.8000000e+00 3.3000000e+00 3.4000000e+00 3.6000000e+00 3.8000000e+00 5.0000000e+00 5.2000000e+00 3.3000000e+00 4.0000000e+00 3.2000000e+00 5.0000000e+00 3.2000000e+00 4.0000000e+00 4.3000000e+00 3.1000000e+00 3.2000000e+00 3.9000000e+00 4.1000000e+00 4.4000000e+00 4.7000000e+00 3.9000000e+00 3.4000000e+00 3.9000000e+00 4.4000000e+00 3.9000000e+00 3.8000000e+00 3.1000000e+00 3.7000000e+00 3.9000000e+00 3.4000000e+00 3.4000000e+00 4.2000000e+00 4.0000000e+00 3.5000000e+00 3.3000000e+00 3.5000000e+00 3.7000000e+00 3.4000000e+00 4.0000000e-01 3.0000000e-01 4.0000000e-01 5.0000000e-01 3.0000000e-01 3.0000000e-01 6.0000000e-01 7.0000000e-01 8.0000000e-01 4.0000000e-01 7.0000000e-01 7.0000000e-01 4.0000000e-01 6.0000000e-01 4.0000000e-01 6.0000000e-01 1.1000000e+00 6.0000000e-01 4.0000000e-01 4.0000000e-01 5.0000000e-01 4.0000000e-01 5.0000000e-01 5.0000000e-01 5.0000000e-01 2.8000000e+00 2.6000000e+00 3.0000000e+00 2.1000000e+00 2.7000000e+00 2.6000000e+00 2.8000000e+00 1.4000000e+00 2.7000000e+00 2.0000000e+00 1.6000000e+00 2.3000000e+00 2.1000000e+00 2.8000000e+00 1.7000000e+00 2.5000000e+00 2.6000000e+00 2.2000000e+00 2.6000000e+00 2.0000000e+00 2.9000000e+00 2.1000000e+00 3.0000000e+00 2.8000000e+00 2.4000000e+00 2.5000000e+00 2.9000000e+00 3.1000000e+00 2.6000000e+00 1.6000000e+00 1.9000000e+00 1.8000000e+00 2.0000000e+00 3.2000000e+00 2.6000000e+00 2.6000000e+00 2.8000000e+00 2.5000000e+00 2.2000000e+00 2.1000000e+00 2.5000000e+00 2.7000000e+00 2.1000000e+00 1.4000000e+00 2.3000000e+00 2.3000000e+00 2.3000000e+00 2.4000000e+00 1.1000000e+00 2.2000000e+00 4.1000000e+00 3.2000000e+00 4.0000000e+00 3.7000000e+00 3.9000000e+00 4.7000000e+00 2.6000000e+00 4.4000000e+00 3.9000000e+00 4.2000000e+00 3.2000000e+00 3.4000000e+00 3.6000000e+00 3.1000000e+00 3.2000000e+00 3.4000000e+00 3.6000000e+00 4.8000000e+00 5.0000000e+00 3.1000000e+00 3.8000000e+00 3.0000000e+00 4.8000000e+00 3.0000000e+00 3.8000000e+00 4.1000000e+00 2.9000000e+00 3.0000000e+00 3.7000000e+00 3.9000000e+00 4.2000000e+00 4.5000000e+00 3.7000000e+00 3.2000000e+00 3.7000000e+00 4.2000000e+00 3.7000000e+00 3.6000000e+00 2.9000000e+00 3.5000000e+00 3.7000000e+00 3.2000000e+00 3.2000000e+00 4.0000000e+00 3.8000000e+00 3.3000000e+00 3.1000000e+00 3.3000000e+00 3.5000000e+00 3.2000000e+00 4.0000000e-01 5.0000000e-01 4.0000000e-01 3.0000000e-01 2.0000000e-01 4.0000000e-01 1.1000000e+00 1.2000000e+00 1.0000000e-01 4.0000000e-01 5.0000000e-01 1.0000000e-01 6.0000000e-01 4.0000000e-01 5.0000000e-01 7.0000000e-01 6.0000000e-01 5.0000000e-01 8.0000000e-01 2.0000000e-01 8.0000000e-01 4.0000000e-01 7.0000000e-01 3.0000000e-01 3.1000000e+00 2.9000000e+00 3.3000000e+00 2.4000000e+00 3.0000000e+00 2.9000000e+00 3.1000000e+00 1.7000000e+00 3.0000000e+00 2.3000000e+00 1.9000000e+00 2.6000000e+00 2.4000000e+00 3.1000000e+00 2.0000000e+00 2.8000000e+00 2.9000000e+00 2.5000000e+00 2.9000000e+00 2.3000000e+00 3.2000000e+00 2.4000000e+00 3.3000000e+00 3.1000000e+00 2.7000000e+00 2.8000000e+00 3.2000000e+00 3.4000000e+00 2.9000000e+00 1.9000000e+00 2.2000000e+00 2.1000000e+00 2.3000000e+00 3.5000000e+00 2.9000000e+00 2.9000000e+00 3.1000000e+00 2.8000000e+00 2.5000000e+00 2.4000000e+00 2.8000000e+00 3.0000000e+00 2.4000000e+00 1.7000000e+00 2.6000000e+00 2.6000000e+00 2.6000000e+00 2.7000000e+00 1.4000000e+00 2.5000000e+00 4.4000000e+00 3.5000000e+00 4.3000000e+00 4.0000000e+00 4.2000000e+00 5.0000000e+00 2.9000000e+00 4.7000000e+00 4.2000000e+00 4.5000000e+00 3.5000000e+00 3.7000000e+00 3.9000000e+00 3.4000000e+00 3.5000000e+00 3.7000000e+00 3.9000000e+00 5.1000000e+00 5.3000000e+00 3.4000000e+00 4.1000000e+00 3.3000000e+00 5.1000000e+00 3.3000000e+00 4.1000000e+00 4.4000000e+00 3.2000000e+00 3.3000000e+00 4.0000000e+00 4.2000000e+00 4.5000000e+00 4.8000000e+00 4.0000000e+00 3.5000000e+00 4.0000000e+00 4.5000000e+00 4.0000000e+00 3.9000000e+00 3.2000000e+00 3.8000000e+00 4.0000000e+00 3.5000000e+00 3.5000000e+00 4.3000000e+00 4.1000000e+00 3.6000000e+00 3.4000000e+00 3.6000000e+00 3.8000000e+00 3.5000000e+00 2.0000000e-01 2.0000000e-01 3.0000000e-01 3.0000000e-01 4.0000000e-01 7.0000000e-01 8.0000000e-01 3.0000000e-01 4.0000000e-01 5.0000000e-01 3.0000000e-01 6.0000000e-01 2.0000000e-01 3.0000000e-01 1.1000000e+00 6.0000000e-01 2.0000000e-01 4.0000000e-01 4.0000000e-01 4.0000000e-01 4.0000000e-01 3.0000000e-01 2.0000000e-01 3.1000000e+00 2.9000000e+00 3.3000000e+00 2.4000000e+00 3.0000000e+00 2.9000000e+00 3.1000000e+00 1.7000000e+00 3.0000000e+00 2.3000000e+00 1.9000000e+00 2.6000000e+00 2.4000000e+00 3.1000000e+00 2.0000000e+00 2.8000000e+00 2.9000000e+00 2.5000000e+00 2.9000000e+00 2.3000000e+00 3.2000000e+00 2.4000000e+00 3.3000000e+00 3.1000000e+00 2.7000000e+00 2.8000000e+00 3.2000000e+00 3.4000000e+00 2.9000000e+00 1.9000000e+00 2.2000000e+00 2.1000000e+00 2.3000000e+00 3.5000000e+00 2.9000000e+00 2.9000000e+00 3.1000000e+00 2.8000000e+00 2.5000000e+00 2.4000000e+00 2.8000000e+00 3.0000000e+00 2.4000000e+00 1.7000000e+00 2.6000000e+00 2.6000000e+00 2.6000000e+00 2.7000000e+00 1.4000000e+00 2.5000000e+00 4.4000000e+00 3.5000000e+00 4.3000000e+00 4.0000000e+00 4.2000000e+00 5.0000000e+00 2.9000000e+00 4.7000000e+00 4.2000000e+00 4.5000000e+00 3.5000000e+00 3.7000000e+00 3.9000000e+00 3.4000000e+00 3.5000000e+00 3.7000000e+00 3.9000000e+00 5.1000000e+00 5.3000000e+00 3.4000000e+00 4.1000000e+00 3.3000000e+00 5.1000000e+00 3.3000000e+00 4.1000000e+00 4.4000000e+00 3.2000000e+00 3.3000000e+00 4.0000000e+00 4.2000000e+00 4.5000000e+00 4.8000000e+00 4.0000000e+00 3.5000000e+00 4.0000000e+00 4.5000000e+00 4.0000000e+00 3.9000000e+00 3.2000000e+00 3.8000000e+00 4.0000000e+00 3.5000000e+00 3.5000000e+00 4.3000000e+00 4.1000000e+00 3.6000000e+00 3.4000000e+00 3.6000000e+00 3.8000000e+00 3.5000000e+00 1.0000000e-01 5.0000000e-01 4.0000000e-01 2.0000000e-01 6.0000000e-01 7.0000000e-01 4.0000000e-01 3.0000000e-01 3.0000000e-01 4.0000000e-01 8.0000000e-01 1.0000000e-01 2.0000000e-01 1.2000000e+00 8.0000000e-01 4.0000000e-01 4.0000000e-01 5.0000000e-01 3.0000000e-01 6.0000000e-01 2.0000000e-01 2.0000000e-01 3.2000000e+00 3.0000000e+00 3.4000000e+00 2.5000000e+00 3.1000000e+00 3.0000000e+00 3.2000000e+00 1.8000000e+00 3.1000000e+00 2.4000000e+00 2.0000000e+00 2.7000000e+00 2.5000000e+00 3.2000000e+00 2.1000000e+00 2.9000000e+00 3.0000000e+00 2.6000000e+00 3.0000000e+00 2.4000000e+00 3.3000000e+00 2.5000000e+00 3.4000000e+00 3.2000000e+00 2.8000000e+00 2.9000000e+00 3.3000000e+00 3.5000000e+00 3.0000000e+00 2.0000000e+00 2.3000000e+00 2.2000000e+00 2.4000000e+00 3.6000000e+00 3.0000000e+00 3.0000000e+00 3.2000000e+00 2.9000000e+00 2.6000000e+00 2.5000000e+00 2.9000000e+00 3.1000000e+00 2.5000000e+00 1.8000000e+00 2.7000000e+00 2.7000000e+00 2.7000000e+00 2.8000000e+00 1.5000000e+00 2.6000000e+00 4.5000000e+00 3.6000000e+00 4.4000000e+00 4.1000000e+00 4.3000000e+00 5.1000000e+00 3.0000000e+00 4.8000000e+00 4.3000000e+00 4.6000000e+00 3.6000000e+00 3.8000000e+00 4.0000000e+00 3.5000000e+00 3.6000000e+00 3.8000000e+00 4.0000000e+00 5.2000000e+00 5.4000000e+00 3.5000000e+00 4.2000000e+00 3.4000000e+00 5.2000000e+00 3.4000000e+00 4.2000000e+00 4.5000000e+00 3.3000000e+00 3.4000000e+00 4.1000000e+00 4.3000000e+00 4.6000000e+00 4.9000000e+00 4.1000000e+00 3.6000000e+00 4.1000000e+00 4.6000000e+00 4.1000000e+00 4.0000000e+00 3.3000000e+00 3.9000000e+00 4.1000000e+00 3.6000000e+00 3.6000000e+00 4.4000000e+00 4.2000000e+00 3.7000000e+00 3.5000000e+00 3.7000000e+00 3.9000000e+00 3.6000000e+00 5.0000000e-01 4.0000000e-01 2.0000000e-01 7.0000000e-01 8.0000000e-01 3.0000000e-01 2.0000000e-01 3.0000000e-01 3.0000000e-01 8.0000000e-01 1.0000000e-01 2.0000000e-01 1.1000000e+00 8.0000000e-01 4.0000000e-01 5.0000000e-01 4.0000000e-01 4.0000000e-01 6.0000000e-01 3.0000000e-01 2.0000000e-01 3.3000000e+00 3.1000000e+00 3.5000000e+00 2.6000000e+00 3.2000000e+00 3.1000000e+00 3.3000000e+00 1.9000000e+00 3.2000000e+00 2.5000000e+00 2.1000000e+00 2.8000000e+00 2.6000000e+00 3.3000000e+00 2.2000000e+00 3.0000000e+00 3.1000000e+00 2.7000000e+00 3.1000000e+00 2.5000000e+00 3.4000000e+00 2.6000000e+00 3.5000000e+00 3.3000000e+00 2.9000000e+00 3.0000000e+00 3.4000000e+00 3.6000000e+00 3.1000000e+00 2.1000000e+00 2.4000000e+00 2.3000000e+00 2.5000000e+00 3.7000000e+00 3.1000000e+00 3.1000000e+00 3.3000000e+00 3.0000000e+00 2.7000000e+00 2.6000000e+00 3.0000000e+00 3.2000000e+00 2.6000000e+00 1.9000000e+00 2.8000000e+00 2.8000000e+00 2.8000000e+00 2.9000000e+00 1.6000000e+00 2.7000000e+00 4.6000000e+00 3.7000000e+00 4.5000000e+00 4.2000000e+00 4.4000000e+00 5.2000000e+00 3.1000000e+00 4.9000000e+00 4.4000000e+00 4.7000000e+00 3.7000000e+00 3.9000000e+00 4.1000000e+00 3.6000000e+00 3.7000000e+00 3.9000000e+00 4.1000000e+00 5.3000000e+00 5.5000000e+00 3.6000000e+00 4.3000000e+00 3.5000000e+00 5.3000000e+00 3.5000000e+00 4.3000000e+00 4.6000000e+00 3.4000000e+00 3.5000000e+00 4.2000000e+00 4.4000000e+00 4.7000000e+00 5.0000000e+00 4.2000000e+00 3.7000000e+00 4.2000000e+00 4.7000000e+00 4.2000000e+00 4.1000000e+00 3.4000000e+00 4.0000000e+00 4.2000000e+00 3.7000000e+00 3.7000000e+00 4.5000000e+00 4.3000000e+00 3.8000000e+00 3.6000000e+00 3.8000000e+00 4.0000000e+00 3.7000000e+00 1.0000000e-01 7.0000000e-01 9.0000000e-01 1.0000000e+00 2.0000000e-01 4.0000000e-01 8.0000000e-01 2.0000000e-01 3.0000000e-01 4.0000000e-01 3.0000000e-01 9.0000000e-01 3.0000000e-01 4.0000000e-01 6.0000000e-01 2.0000000e-01 6.0000000e-01 2.0000000e-01 6.0000000e-01 3.0000000e-01 3.1000000e+00 2.9000000e+00 3.3000000e+00 2.4000000e+00 3.0000000e+00 2.9000000e+00 3.1000000e+00 1.7000000e+00 3.0000000e+00 2.3000000e+00 1.9000000e+00 2.6000000e+00 2.4000000e+00 3.1000000e+00 2.0000000e+00 2.8000000e+00 2.9000000e+00 2.5000000e+00 2.9000000e+00 2.3000000e+00 3.2000000e+00 2.4000000e+00 3.3000000e+00 3.1000000e+00 2.7000000e+00 2.8000000e+00 3.2000000e+00 3.4000000e+00 2.9000000e+00 1.9000000e+00 2.2000000e+00 2.1000000e+00 2.3000000e+00 3.5000000e+00 2.9000000e+00 2.9000000e+00 3.1000000e+00 2.8000000e+00 2.5000000e+00 2.4000000e+00 2.8000000e+00 3.0000000e+00 2.4000000e+00 1.7000000e+00 2.6000000e+00 2.6000000e+00 2.6000000e+00 2.7000000e+00 1.4000000e+00 2.5000000e+00 4.4000000e+00 3.5000000e+00 4.3000000e+00 4.0000000e+00 4.2000000e+00 5.0000000e+00 2.9000000e+00 4.7000000e+00 4.2000000e+00 4.5000000e+00 3.5000000e+00 3.7000000e+00 3.9000000e+00 3.4000000e+00 3.5000000e+00 3.7000000e+00 3.9000000e+00 5.1000000e+00 5.3000000e+00 3.4000000e+00 4.1000000e+00 3.3000000e+00 5.1000000e+00 3.3000000e+00 4.1000000e+00 4.4000000e+00 3.2000000e+00 3.3000000e+00 4.0000000e+00 4.2000000e+00 4.5000000e+00 4.8000000e+00 4.0000000e+00 3.5000000e+00 4.0000000e+00 4.5000000e+00 4.0000000e+00 3.9000000e+00 3.2000000e+00 3.8000000e+00 4.0000000e+00 3.5000000e+00 3.5000000e+00 4.3000000e+00 4.1000000e+00 3.6000000e+00 3.4000000e+00 3.6000000e+00 3.8000000e+00 3.5000000e+00 6.0000000e-01 1.0000000e+00 1.1000000e+00 1.0000000e-01 4.0000000e-01 7.0000000e-01 1.0000000e-01 4.0000000e-01 3.0000000e-01 4.0000000e-01 8.0000000e-01 4.0000000e-01 4.0000000e-01 7.0000000e-01 2.0000000e-01 7.0000000e-01 2.0000000e-01 6.0000000e-01 2.0000000e-01 3.1000000e+00 2.9000000e+00 3.3000000e+00 2.4000000e+00 3.0000000e+00 2.9000000e+00 3.1000000e+00 1.7000000e+00 3.0000000e+00 2.3000000e+00 1.9000000e+00 2.6000000e+00 2.4000000e+00 3.1000000e+00 2.0000000e+00 2.8000000e+00 2.9000000e+00 2.5000000e+00 2.9000000e+00 2.3000000e+00 3.2000000e+00 2.4000000e+00 3.3000000e+00 3.1000000e+00 2.7000000e+00 2.8000000e+00 3.2000000e+00 3.4000000e+00 2.9000000e+00 1.9000000e+00 2.2000000e+00 2.1000000e+00 2.3000000e+00 3.5000000e+00 2.9000000e+00 2.9000000e+00 3.1000000e+00 2.8000000e+00 2.5000000e+00 2.4000000e+00 2.8000000e+00 3.0000000e+00 2.4000000e+00 1.7000000e+00 2.6000000e+00 2.6000000e+00 2.6000000e+00 2.7000000e+00 1.4000000e+00 2.5000000e+00 4.4000000e+00 3.5000000e+00 4.3000000e+00 4.0000000e+00 4.2000000e+00 5.0000000e+00 2.9000000e+00 4.7000000e+00 4.2000000e+00 4.5000000e+00 3.5000000e+00 3.7000000e+00 3.9000000e+00 3.4000000e+00 3.5000000e+00 3.7000000e+00 3.9000000e+00 5.1000000e+00 5.3000000e+00 3.4000000e+00 4.1000000e+00 3.3000000e+00 5.1000000e+00 3.3000000e+00 4.1000000e+00 4.4000000e+00 3.2000000e+00 3.3000000e+00 4.0000000e+00 4.2000000e+00 4.5000000e+00 4.8000000e+00 4.0000000e+00 3.5000000e+00 4.0000000e+00 4.5000000e+00 4.0000000e+00 3.9000000e+00 3.2000000e+00 3.8000000e+00 4.0000000e+00 3.5000000e+00 3.5000000e+00 4.3000000e+00 4.1000000e+00 3.6000000e+00 3.4000000e+00 3.6000000e+00 3.8000000e+00 3.5000000e+00 7.0000000e-01 8.0000000e-01 5.0000000e-01 4.0000000e-01 2.0000000e-01 5.0000000e-01 1.0000000e+00 3.0000000e-01 4.0000000e-01 1.1000000e+00 1.0000000e+00 4.0000000e-01 4.0000000e-01 6.0000000e-01 4.0000000e-01 8.0000000e-01 3.0000000e-01 4.0000000e-01 3.2000000e+00 3.0000000e+00 3.4000000e+00 2.5000000e+00 3.1000000e+00 3.0000000e+00 3.2000000e+00 1.8000000e+00 3.1000000e+00 2.4000000e+00 2.0000000e+00 2.7000000e+00 2.5000000e+00 3.2000000e+00 2.1000000e+00 2.9000000e+00 3.0000000e+00 2.6000000e+00 3.0000000e+00 2.4000000e+00 3.3000000e+00 2.5000000e+00 3.4000000e+00 3.2000000e+00 2.8000000e+00 2.9000000e+00 3.3000000e+00 3.5000000e+00 3.0000000e+00 2.0000000e+00 2.3000000e+00 2.2000000e+00 2.4000000e+00 3.6000000e+00 3.0000000e+00 3.0000000e+00 3.2000000e+00 2.9000000e+00 2.6000000e+00 2.5000000e+00 2.9000000e+00 3.1000000e+00 2.5000000e+00 1.8000000e+00 2.7000000e+00 2.7000000e+00 2.7000000e+00 2.8000000e+00 1.5000000e+00 2.6000000e+00 4.5000000e+00 3.6000000e+00 4.4000000e+00 4.1000000e+00 4.3000000e+00 5.1000000e+00 3.0000000e+00 4.8000000e+00 4.3000000e+00 4.6000000e+00 3.6000000e+00 3.8000000e+00 4.0000000e+00 3.5000000e+00 3.6000000e+00 3.8000000e+00 4.0000000e+00 5.2000000e+00 5.4000000e+00 3.5000000e+00 4.2000000e+00 3.4000000e+00 5.2000000e+00 3.4000000e+00 4.2000000e+00 4.5000000e+00 3.3000000e+00 3.4000000e+00 4.1000000e+00 4.3000000e+00 4.6000000e+00 4.9000000e+00 4.1000000e+00 3.6000000e+00 4.1000000e+00 4.6000000e+00 4.1000000e+00 4.0000000e+00 3.3000000e+00 3.9000000e+00 4.1000000e+00 3.6000000e+00 3.6000000e+00 4.4000000e+00 4.2000000e+00 3.7000000e+00 3.5000000e+00 3.7000000e+00 3.9000000e+00 3.6000000e+00 3.0000000e-01 1.0000000e+00 9.0000000e-01 6.0000000e-01 1.0000000e+00 1.1000000e+00 7.0000000e-01 6.0000000e-01 1.8000000e+00 9.0000000e-01 6.0000000e-01 4.0000000e-01 1.1000000e+00 3.0000000e-01 9.0000000e-01 4.0000000e-01 8.0000000e-01 3.2000000e+00 3.0000000e+00 3.4000000e+00 2.5000000e+00 3.1000000e+00 3.0000000e+00 3.2000000e+00 1.8000000e+00 3.1000000e+00 2.4000000e+00 2.1000000e+00 2.7000000e+00 2.5000000e+00 3.2000000e+00 2.1000000e+00 2.9000000e+00 3.0000000e+00 2.6000000e+00 3.0000000e+00 2.4000000e+00 3.3000000e+00 2.5000000e+00 3.4000000e+00 3.2000000e+00 2.8000000e+00 2.9000000e+00 3.3000000e+00 3.5000000e+00 3.0000000e+00 2.0000000e+00 2.3000000e+00 2.2000000e+00 2.4000000e+00 3.6000000e+00 3.0000000e+00 3.0000000e+00 3.2000000e+00 2.9000000e+00 2.6000000e+00 2.5000000e+00 2.9000000e+00 3.1000000e+00 2.5000000e+00 1.8000000e+00 2.7000000e+00 2.7000000e+00 2.7000000e+00 2.8000000e+00 1.6000000e+00 2.6000000e+00 4.5000000e+00 3.6000000e+00 4.4000000e+00 4.1000000e+00 4.3000000e+00 5.1000000e+00 3.0000000e+00 4.8000000e+00 4.3000000e+00 4.6000000e+00 3.6000000e+00 3.8000000e+00 4.0000000e+00 3.5000000e+00 3.6000000e+00 3.8000000e+00 4.0000000e+00 5.2000000e+00 5.4000000e+00 3.5000000e+00 4.2000000e+00 3.4000000e+00 5.2000000e+00 3.4000000e+00 4.2000000e+00 4.5000000e+00 3.3000000e+00 3.4000000e+00 4.1000000e+00 4.3000000e+00 4.6000000e+00 4.9000000e+00 4.1000000e+00 3.6000000e+00 4.1000000e+00 4.6000000e+00 4.1000000e+00 4.0000000e+00 3.3000000e+00 3.9000000e+00 4.1000000e+00 3.6000000e+00 3.6000000e+00 4.4000000e+00 4.2000000e+00 3.7000000e+00 3.5000000e+00 3.7000000e+00 3.9000000e+00 3.6000000e+00 1.1000000e+00 1.0000000e+00 7.0000000e-01 1.1000000e+00 1.2000000e+00 8.0000000e-01 7.0000000e-01 1.9000000e+00 1.1000000e+00 7.0000000e-01 5.0000000e-01 1.2000000e+00 4.0000000e-01 1.0000000e+00 5.0000000e-01 9.0000000e-01 3.3000000e+00 3.1000000e+00 3.5000000e+00 2.6000000e+00 3.2000000e+00 3.1000000e+00 3.3000000e+00 1.9000000e+00 3.2000000e+00 2.5000000e+00 2.2000000e+00 2.8000000e+00 2.6000000e+00 3.3000000e+00 2.2000000e+00 3.0000000e+00 3.1000000e+00 2.7000000e+00 3.1000000e+00 2.5000000e+00 3.4000000e+00 2.6000000e+00 3.5000000e+00 3.3000000e+00 2.9000000e+00 3.0000000e+00 3.4000000e+00 3.6000000e+00 3.1000000e+00 2.1000000e+00 2.4000000e+00 2.3000000e+00 2.5000000e+00 3.7000000e+00 3.1000000e+00 3.1000000e+00 3.3000000e+00 3.0000000e+00 2.7000000e+00 2.6000000e+00 3.0000000e+00 3.2000000e+00 2.6000000e+00 1.9000000e+00 2.8000000e+00 2.8000000e+00 2.8000000e+00 2.9000000e+00 1.7000000e+00 2.7000000e+00 4.6000000e+00 3.7000000e+00 4.5000000e+00 4.2000000e+00 4.4000000e+00 5.2000000e+00 3.1000000e+00 4.9000000e+00 4.4000000e+00 4.7000000e+00 3.7000000e+00 3.9000000e+00 4.1000000e+00 3.6000000e+00 3.7000000e+00 3.9000000e+00 4.1000000e+00 5.3000000e+00 5.5000000e+00 3.6000000e+00 4.3000000e+00 3.5000000e+00 5.3000000e+00 3.5000000e+00 4.3000000e+00 4.6000000e+00 3.4000000e+00 3.5000000e+00 4.2000000e+00 4.4000000e+00 4.7000000e+00 5.0000000e+00 4.2000000e+00 3.7000000e+00 4.2000000e+00 4.7000000e+00 4.2000000e+00 4.1000000e+00 3.4000000e+00 4.0000000e+00 4.2000000e+00 3.7000000e+00 3.7000000e+00 4.5000000e+00 4.3000000e+00 3.8000000e+00 3.6000000e+00 3.8000000e+00 4.0000000e+00 3.7000000e+00 3.0000000e-01 6.0000000e-01 0.0000000e+00 5.0000000e-01 3.0000000e-01 4.0000000e-01 8.0000000e-01 5.0000000e-01 5.0000000e-01 7.0000000e-01 2.0000000e-01 7.0000000e-01 3.0000000e-01 6.0000000e-01 2.0000000e-01 3.2000000e+00 3.0000000e+00 3.4000000e+00 2.5000000e+00 3.1000000e+00 3.0000000e+00 3.2000000e+00 1.8000000e+00 3.1000000e+00 2.4000000e+00 2.0000000e+00 2.7000000e+00 2.5000000e+00 3.2000000e+00 2.1000000e+00 2.9000000e+00 3.0000000e+00 2.6000000e+00 3.0000000e+00 2.4000000e+00 3.3000000e+00 2.5000000e+00 3.4000000e+00 3.2000000e+00 2.8000000e+00 2.9000000e+00 3.3000000e+00 3.5000000e+00 3.0000000e+00 2.0000000e+00 2.3000000e+00 2.2000000e+00 2.4000000e+00 3.6000000e+00 3.0000000e+00 3.0000000e+00 3.2000000e+00 2.9000000e+00 2.6000000e+00 2.5000000e+00 2.9000000e+00 3.1000000e+00 2.5000000e+00 1.8000000e+00 2.7000000e+00 2.7000000e+00 2.7000000e+00 2.8000000e+00 1.5000000e+00 2.6000000e+00 4.5000000e+00 3.6000000e+00 4.4000000e+00 4.1000000e+00 4.3000000e+00 5.1000000e+00 3.0000000e+00 4.8000000e+00 4.3000000e+00 4.6000000e+00 3.6000000e+00 3.8000000e+00 4.0000000e+00 3.5000000e+00 3.6000000e+00 3.8000000e+00 4.0000000e+00 5.2000000e+00 5.4000000e+00 3.5000000e+00 4.2000000e+00 3.4000000e+00 5.2000000e+00 3.4000000e+00 4.2000000e+00 4.5000000e+00 3.3000000e+00 3.4000000e+00 4.1000000e+00 4.3000000e+00 4.6000000e+00 4.9000000e+00 4.1000000e+00 3.6000000e+00 4.1000000e+00 4.6000000e+00 4.1000000e+00 4.0000000e+00 3.3000000e+00 3.9000000e+00 4.1000000e+00 3.6000000e+00 3.6000000e+00 4.4000000e+00 4.2000000e+00 3.7000000e+00 3.5000000e+00 3.7000000e+00 3.9000000e+00 3.6000000e+00 5.0000000e-01 3.0000000e-01 6.0000000e-01 3.0000000e-01 3.0000000e-01 9.0000000e-01 6.0000000e-01 4.0000000e-01 7.0000000e-01 2.0000000e-01 6.0000000e-01 4.0000000e-01 5.0000000e-01 2.0000000e-01 3.5000000e+00 3.3000000e+00 3.7000000e+00 2.8000000e+00 3.4000000e+00 3.3000000e+00 3.5000000e+00 2.1000000e+00 3.4000000e+00 2.7000000e+00 2.3000000e+00 3.0000000e+00 2.8000000e+00 3.5000000e+00 2.4000000e+00 3.2000000e+00 3.3000000e+00 2.9000000e+00 3.3000000e+00 2.7000000e+00 3.6000000e+00 2.8000000e+00 3.7000000e+00 3.5000000e+00 3.1000000e+00 3.2000000e+00 3.6000000e+00 3.8000000e+00 3.3000000e+00 2.3000000e+00 2.6000000e+00 2.5000000e+00 2.7000000e+00 3.9000000e+00 3.3000000e+00 3.3000000e+00 3.5000000e+00 3.2000000e+00 2.9000000e+00 2.8000000e+00 3.2000000e+00 3.4000000e+00 2.8000000e+00 2.1000000e+00 3.0000000e+00 3.0000000e+00 3.0000000e+00 3.1000000e+00 1.8000000e+00 2.9000000e+00 4.8000000e+00 3.9000000e+00 4.7000000e+00 4.4000000e+00 4.6000000e+00 5.4000000e+00 3.3000000e+00 5.1000000e+00 4.6000000e+00 4.9000000e+00 3.9000000e+00 4.1000000e+00 4.3000000e+00 3.8000000e+00 3.9000000e+00 4.1000000e+00 4.3000000e+00 5.5000000e+00 5.7000000e+00 3.8000000e+00 4.5000000e+00 3.7000000e+00 5.5000000e+00 3.7000000e+00 4.5000000e+00 4.8000000e+00 3.6000000e+00 3.7000000e+00 4.4000000e+00 4.6000000e+00 4.9000000e+00 5.2000000e+00 4.4000000e+00 3.9000000e+00 4.4000000e+00 4.9000000e+00 4.4000000e+00 4.3000000e+00 3.6000000e+00 4.2000000e+00 4.4000000e+00 3.9000000e+00 3.9000000e+00 4.7000000e+00 4.5000000e+00 4.0000000e+00 3.8000000e+00 4.0000000e+00 4.2000000e+00 3.9000000e+00 6.0000000e-01 1.1000000e+00 4.0000000e-01 5.0000000e-01 1.2000000e+00 1.1000000e+00 5.0000000e-01 6.0000000e-01 7.0000000e-01 4.0000000e-01 9.0000000e-01 2.0000000e-01 5.0000000e-01 3.4000000e+00 3.2000000e+00 3.6000000e+00 2.7000000e+00 3.3000000e+00 3.2000000e+00 3.4000000e+00 2.0000000e+00 3.3000000e+00 2.6000000e+00 2.2000000e+00 2.9000000e+00 2.7000000e+00 3.4000000e+00 2.3000000e+00 3.1000000e+00 3.2000000e+00 2.8000000e+00 3.2000000e+00 2.6000000e+00 3.5000000e+00 2.7000000e+00 3.6000000e+00 3.4000000e+00 3.0000000e+00 3.1000000e+00 3.5000000e+00 3.7000000e+00 3.2000000e+00 2.2000000e+00 2.5000000e+00 2.4000000e+00 2.6000000e+00 3.8000000e+00 3.2000000e+00 3.2000000e+00 3.4000000e+00 3.1000000e+00 2.8000000e+00 2.7000000e+00 3.1000000e+00 3.3000000e+00 2.7000000e+00 2.0000000e+00 2.9000000e+00 2.9000000e+00 2.9000000e+00 3.0000000e+00 1.7000000e+00 2.8000000e+00 4.7000000e+00 3.8000000e+00 4.6000000e+00 4.3000000e+00 4.5000000e+00 5.3000000e+00 3.2000000e+00 5.0000000e+00 4.5000000e+00 4.8000000e+00 3.8000000e+00 4.0000000e+00 4.2000000e+00 3.7000000e+00 3.8000000e+00 4.0000000e+00 4.2000000e+00 5.4000000e+00 5.6000000e+00 3.7000000e+00 4.4000000e+00 3.6000000e+00 5.4000000e+00 3.6000000e+00 4.4000000e+00 4.7000000e+00 3.5000000e+00 3.6000000e+00 4.3000000e+00 4.5000000e+00 4.8000000e+00 5.1000000e+00 4.3000000e+00 3.8000000e+00 4.3000000e+00 4.8000000e+00 4.3000000e+00 4.2000000e+00 3.5000000e+00 4.1000000e+00 4.3000000e+00 3.8000000e+00 3.8000000e+00 4.6000000e+00 4.4000000e+00 3.9000000e+00 3.7000000e+00 3.9000000e+00 4.1000000e+00 3.8000000e+00 5.0000000e-01 3.0000000e-01 4.0000000e-01 8.0000000e-01 5.0000000e-01 5.0000000e-01 7.0000000e-01 2.0000000e-01 7.0000000e-01 3.0000000e-01 6.0000000e-01 2.0000000e-01 3.2000000e+00 3.0000000e+00 3.4000000e+00 2.5000000e+00 3.1000000e+00 3.0000000e+00 3.2000000e+00 1.8000000e+00 3.1000000e+00 2.4000000e+00 2.0000000e+00 2.7000000e+00 2.5000000e+00 3.2000000e+00 2.1000000e+00 2.9000000e+00 3.0000000e+00 2.6000000e+00 3.0000000e+00 2.4000000e+00 3.3000000e+00 2.5000000e+00 3.4000000e+00 3.2000000e+00 2.8000000e+00 2.9000000e+00 3.3000000e+00 3.5000000e+00 3.0000000e+00 2.0000000e+00 2.3000000e+00 2.2000000e+00 2.4000000e+00 3.6000000e+00 3.0000000e+00 3.0000000e+00 3.2000000e+00 2.9000000e+00 2.6000000e+00 2.5000000e+00 2.9000000e+00 3.1000000e+00 2.5000000e+00 1.8000000e+00 2.7000000e+00 2.7000000e+00 2.7000000e+00 2.8000000e+00 1.5000000e+00 2.6000000e+00 4.5000000e+00 3.6000000e+00 4.4000000e+00 4.1000000e+00 4.3000000e+00 5.1000000e+00 3.0000000e+00 4.8000000e+00 4.3000000e+00 4.6000000e+00 3.6000000e+00 3.8000000e+00 4.0000000e+00 3.5000000e+00 3.6000000e+00 3.8000000e+00 4.0000000e+00 5.2000000e+00 5.4000000e+00 3.5000000e+00 4.2000000e+00 3.4000000e+00 5.2000000e+00 3.4000000e+00 4.2000000e+00 4.5000000e+00 3.3000000e+00 3.4000000e+00 4.1000000e+00 4.3000000e+00 4.6000000e+00 4.9000000e+00 4.1000000e+00 3.6000000e+00 4.1000000e+00 4.6000000e+00 4.1000000e+00 4.0000000e+00 3.3000000e+00 3.9000000e+00 4.1000000e+00 3.6000000e+00 3.6000000e+00 4.4000000e+00 4.2000000e+00 3.7000000e+00 3.5000000e+00 3.7000000e+00 3.9000000e+00 3.6000000e+00 7.0000000e-01 6.0000000e-01 7.0000000e-01 2.0000000e-01 6.0000000e-01 8.0000000e-01 4.0000000e-01 8.0000000e-01 2.0000000e-01 9.0000000e-01 6.0000000e-01 3.4000000e+00 3.2000000e+00 3.6000000e+00 2.7000000e+00 3.3000000e+00 3.2000000e+00 3.4000000e+00 2.0000000e+00 3.3000000e+00 2.6000000e+00 2.2000000e+00 2.9000000e+00 2.7000000e+00 3.4000000e+00 2.3000000e+00 3.1000000e+00 3.2000000e+00 2.8000000e+00 3.2000000e+00 2.6000000e+00 3.5000000e+00 2.7000000e+00 3.6000000e+00 3.4000000e+00 3.0000000e+00 3.1000000e+00 3.5000000e+00 3.7000000e+00 3.2000000e+00 2.2000000e+00 2.5000000e+00 2.4000000e+00 2.6000000e+00 3.8000000e+00 3.2000000e+00 3.2000000e+00 3.4000000e+00 3.1000000e+00 2.8000000e+00 2.7000000e+00 3.1000000e+00 3.3000000e+00 2.7000000e+00 2.0000000e+00 2.9000000e+00 2.9000000e+00 2.9000000e+00 3.0000000e+00 1.7000000e+00 2.8000000e+00 4.7000000e+00 3.8000000e+00 4.6000000e+00 4.3000000e+00 4.5000000e+00 5.3000000e+00 3.2000000e+00 5.0000000e+00 4.5000000e+00 4.8000000e+00 3.8000000e+00 4.0000000e+00 4.2000000e+00 3.7000000e+00 3.8000000e+00 4.0000000e+00 4.2000000e+00 5.4000000e+00 5.6000000e+00 3.7000000e+00 4.4000000e+00 3.6000000e+00 5.4000000e+00 3.6000000e+00 4.4000000e+00 4.7000000e+00 3.5000000e+00 3.6000000e+00 4.3000000e+00 4.5000000e+00 4.8000000e+00 5.1000000e+00 4.3000000e+00 3.8000000e+00 4.3000000e+00 4.8000000e+00 4.3000000e+00 4.2000000e+00 3.5000000e+00 4.1000000e+00 4.3000000e+00 3.8000000e+00 3.8000000e+00 4.6000000e+00 4.4000000e+00 3.9000000e+00 3.7000000e+00 3.9000000e+00 4.1000000e+00 3.8000000e+00 2.0000000e-01 1.1000000e+00 7.0000000e-01 4.0000000e-01 4.0000000e-01 4.0000000e-01 4.0000000e-01 5.0000000e-01 3.0000000e-01 1.0000000e-01 3.2000000e+00 3.0000000e+00 3.4000000e+00 2.5000000e+00 3.1000000e+00 3.0000000e+00 3.2000000e+00 1.8000000e+00 3.1000000e+00 2.4000000e+00 2.0000000e+00 2.7000000e+00 2.5000000e+00 3.2000000e+00 2.1000000e+00 2.9000000e+00 3.0000000e+00 2.6000000e+00 3.0000000e+00 2.4000000e+00 3.3000000e+00 2.5000000e+00 3.4000000e+00 3.2000000e+00 2.8000000e+00 2.9000000e+00 3.3000000e+00 3.5000000e+00 3.0000000e+00 2.0000000e+00 2.3000000e+00 2.2000000e+00 2.4000000e+00 3.6000000e+00 3.0000000e+00 3.0000000e+00 3.2000000e+00 2.9000000e+00 2.6000000e+00 2.5000000e+00 2.9000000e+00 3.1000000e+00 2.5000000e+00 1.8000000e+00 2.7000000e+00 2.7000000e+00 2.7000000e+00 2.8000000e+00 1.5000000e+00 2.6000000e+00 4.5000000e+00 3.6000000e+00 4.4000000e+00 4.1000000e+00 4.3000000e+00 5.1000000e+00 3.0000000e+00 4.8000000e+00 4.3000000e+00 4.6000000e+00 3.6000000e+00 3.8000000e+00 4.0000000e+00 3.5000000e+00 3.6000000e+00 3.8000000e+00 4.0000000e+00 5.2000000e+00 5.4000000e+00 3.5000000e+00 4.2000000e+00 3.4000000e+00 5.2000000e+00 3.4000000e+00 4.2000000e+00 4.5000000e+00 3.3000000e+00 3.4000000e+00 4.1000000e+00 4.3000000e+00 4.6000000e+00 4.9000000e+00 4.1000000e+00 3.6000000e+00 4.1000000e+00 4.6000000e+00 4.1000000e+00 4.0000000e+00 3.3000000e+00 3.9000000e+00 4.1000000e+00 3.6000000e+00 3.6000000e+00 4.4000000e+00 4.2000000e+00 3.7000000e+00 3.5000000e+00 3.7000000e+00 3.9000000e+00 3.6000000e+00 1.2000000e+00 6.0000000e-01 3.0000000e-01 6.0000000e-01 5.0000000e-01 3.0000000e-01 4.0000000e-01 3.0000000e-01 2.0000000e-01 3.4000000e+00 3.2000000e+00 3.6000000e+00 2.7000000e+00 3.3000000e+00 3.2000000e+00 3.4000000e+00 2.0000000e+00 3.3000000e+00 2.6000000e+00 2.2000000e+00 2.9000000e+00 2.7000000e+00 3.4000000e+00 2.3000000e+00 3.1000000e+00 3.2000000e+00 2.8000000e+00 3.2000000e+00 2.6000000e+00 3.5000000e+00 2.7000000e+00 3.6000000e+00 3.4000000e+00 3.0000000e+00 3.1000000e+00 3.5000000e+00 3.7000000e+00 3.2000000e+00 2.2000000e+00 2.5000000e+00 2.4000000e+00 2.6000000e+00 3.8000000e+00 3.2000000e+00 3.2000000e+00 3.4000000e+00 3.1000000e+00 2.8000000e+00 2.7000000e+00 3.1000000e+00 3.3000000e+00 2.7000000e+00 2.0000000e+00 2.9000000e+00 2.9000000e+00 2.9000000e+00 3.0000000e+00 1.7000000e+00 2.8000000e+00 4.7000000e+00 3.8000000e+00 4.6000000e+00 4.3000000e+00 4.5000000e+00 5.3000000e+00 3.2000000e+00 5.0000000e+00 4.5000000e+00 4.8000000e+00 3.8000000e+00 4.0000000e+00 4.2000000e+00 3.7000000e+00 3.8000000e+00 4.0000000e+00 4.2000000e+00 5.4000000e+00 5.6000000e+00 3.7000000e+00 4.4000000e+00 3.6000000e+00 5.4000000e+00 3.6000000e+00 4.4000000e+00 4.7000000e+00 3.5000000e+00 3.6000000e+00 4.3000000e+00 4.5000000e+00 4.8000000e+00 5.1000000e+00 4.3000000e+00 3.8000000e+00 4.3000000e+00 4.8000000e+00 4.3000000e+00 4.2000000e+00 3.5000000e+00 4.1000000e+00 4.3000000e+00 3.8000000e+00 3.8000000e+00 4.6000000e+00 4.4000000e+00 3.9000000e+00 3.7000000e+00 3.9000000e+00 4.1000000e+00 3.8000000e+00 9.0000000e-01 1.2000000e+00 1.5000000e+00 7.0000000e-01 1.5000000e+00 9.0000000e-01 1.4000000e+00 1.0000000e+00 3.4000000e+00 3.2000000e+00 3.6000000e+00 2.7000000e+00 3.3000000e+00 3.2000000e+00 3.4000000e+00 2.0000000e+00 3.3000000e+00 2.6000000e+00 2.2000000e+00 2.9000000e+00 2.7000000e+00 3.4000000e+00 2.3000000e+00 3.1000000e+00 3.2000000e+00 2.8000000e+00 3.2000000e+00 2.6000000e+00 3.5000000e+00 2.7000000e+00 3.6000000e+00 3.4000000e+00 3.0000000e+00 3.1000000e+00 3.5000000e+00 3.7000000e+00 3.2000000e+00 2.2000000e+00 2.5000000e+00 2.4000000e+00 2.6000000e+00 3.8000000e+00 3.2000000e+00 3.2000000e+00 3.4000000e+00 3.1000000e+00 2.8000000e+00 2.7000000e+00 3.1000000e+00 3.3000000e+00 2.7000000e+00 2.0000000e+00 2.9000000e+00 2.9000000e+00 2.9000000e+00 3.0000000e+00 1.7000000e+00 2.8000000e+00 4.7000000e+00 3.8000000e+00 4.6000000e+00 4.3000000e+00 4.5000000e+00 5.3000000e+00 3.2000000e+00 5.0000000e+00 4.5000000e+00 4.8000000e+00 3.8000000e+00 4.0000000e+00 4.2000000e+00 3.7000000e+00 3.8000000e+00 4.0000000e+00 4.2000000e+00 5.4000000e+00 5.6000000e+00 3.7000000e+00 4.4000000e+00 3.6000000e+00 5.4000000e+00 3.6000000e+00 4.4000000e+00 4.7000000e+00 3.5000000e+00 3.6000000e+00 4.3000000e+00 4.5000000e+00 4.8000000e+00 5.1000000e+00 4.3000000e+00 3.8000000e+00 4.3000000e+00 4.8000000e+00 4.3000000e+00 4.2000000e+00 3.5000000e+00 4.1000000e+00 4.3000000e+00 3.8000000e+00 3.8000000e+00 4.6000000e+00 4.4000000e+00 3.9000000e+00 3.7000000e+00 3.9000000e+00 4.1000000e+00 3.8000000e+00 6.0000000e-01 7.0000000e-01 4.0000000e-01 7.0000000e-01 2.0000000e-01 9.0000000e-01 6.0000000e-01 3.4000000e+00 3.2000000e+00 3.6000000e+00 2.7000000e+00 3.3000000e+00 3.2000000e+00 3.4000000e+00 2.0000000e+00 3.3000000e+00 2.6000000e+00 2.2000000e+00 2.9000000e+00 2.7000000e+00 3.4000000e+00 2.3000000e+00 3.1000000e+00 3.2000000e+00 2.8000000e+00 3.2000000e+00 2.6000000e+00 3.5000000e+00 2.7000000e+00 3.6000000e+00 3.4000000e+00 3.0000000e+00 3.1000000e+00 3.5000000e+00 3.7000000e+00 3.2000000e+00 2.2000000e+00 2.5000000e+00 2.4000000e+00 2.6000000e+00 3.8000000e+00 3.2000000e+00 3.2000000e+00 3.4000000e+00 3.1000000e+00 2.8000000e+00 2.7000000e+00 3.1000000e+00 3.3000000e+00 2.7000000e+00 2.0000000e+00 2.9000000e+00 2.9000000e+00 2.9000000e+00 3.0000000e+00 1.7000000e+00 2.8000000e+00 4.7000000e+00 3.8000000e+00 4.6000000e+00 4.3000000e+00 4.5000000e+00 5.3000000e+00 3.2000000e+00 5.0000000e+00 4.5000000e+00 4.8000000e+00 3.8000000e+00 4.0000000e+00 4.2000000e+00 3.7000000e+00 3.8000000e+00 4.0000000e+00 4.2000000e+00 5.4000000e+00 5.6000000e+00 3.7000000e+00 4.4000000e+00 3.6000000e+00 5.4000000e+00 3.6000000e+00 4.4000000e+00 4.7000000e+00 3.5000000e+00 3.6000000e+00 4.3000000e+00 4.5000000e+00 4.8000000e+00 5.1000000e+00 4.3000000e+00 3.8000000e+00 4.3000000e+00 4.8000000e+00 4.3000000e+00 4.2000000e+00 3.5000000e+00 4.1000000e+00 4.3000000e+00 3.8000000e+00 3.8000000e+00 4.6000000e+00 4.4000000e+00 3.9000000e+00 3.7000000e+00 3.9000000e+00 4.1000000e+00 3.8000000e+00 3.0000000e-01 5.0000000e-01 4.0000000e-01 4.0000000e-01 4.0000000e-01 4.0000000e-01 3.1000000e+00 2.9000000e+00 3.3000000e+00 2.4000000e+00 3.0000000e+00 2.9000000e+00 3.1000000e+00 1.7000000e+00 3.0000000e+00 2.3000000e+00 1.9000000e+00 2.6000000e+00 2.4000000e+00 3.1000000e+00 2.0000000e+00 2.8000000e+00 2.9000000e+00 2.5000000e+00 2.9000000e+00 2.3000000e+00 3.2000000e+00 2.4000000e+00 3.3000000e+00 3.1000000e+00 2.7000000e+00 2.8000000e+00 3.2000000e+00 3.4000000e+00 2.9000000e+00 1.9000000e+00 2.2000000e+00 2.1000000e+00 2.3000000e+00 3.5000000e+00 2.9000000e+00 2.9000000e+00 3.1000000e+00 2.8000000e+00 2.5000000e+00 2.4000000e+00 2.8000000e+00 3.0000000e+00 2.4000000e+00 1.7000000e+00 2.6000000e+00 2.6000000e+00 2.6000000e+00 2.7000000e+00 1.4000000e+00 2.5000000e+00 4.4000000e+00 3.5000000e+00 4.3000000e+00 4.0000000e+00 4.2000000e+00 5.0000000e+00 2.9000000e+00 4.7000000e+00 4.2000000e+00 4.5000000e+00 3.5000000e+00 3.7000000e+00 3.9000000e+00 3.4000000e+00 3.5000000e+00 3.7000000e+00 3.9000000e+00 5.1000000e+00 5.3000000e+00 3.4000000e+00 4.1000000e+00 3.3000000e+00 5.1000000e+00 3.3000000e+00 4.1000000e+00 4.4000000e+00 3.2000000e+00 3.3000000e+00 4.0000000e+00 4.2000000e+00 4.5000000e+00 4.8000000e+00 4.0000000e+00 3.5000000e+00 4.0000000e+00 4.5000000e+00 4.0000000e+00 3.9000000e+00 3.2000000e+00 3.8000000e+00 4.0000000e+00 3.5000000e+00 3.5000000e+00 4.3000000e+00 4.1000000e+00 3.6000000e+00 3.4000000e+00 3.6000000e+00 3.8000000e+00 3.5000000e+00 8.0000000e-01 3.0000000e-01 6.0000000e-01 4.0000000e-01 5.0000000e-01 2.8000000e+00 2.6000000e+00 3.0000000e+00 2.1000000e+00 2.7000000e+00 2.6000000e+00 2.8000000e+00 1.4000000e+00 2.7000000e+00 2.0000000e+00 1.8000000e+00 2.3000000e+00 2.1000000e+00 2.8000000e+00 1.7000000e+00 2.5000000e+00 2.6000000e+00 2.2000000e+00 2.6000000e+00 2.0000000e+00 2.9000000e+00 2.1000000e+00 3.0000000e+00 2.8000000e+00 2.4000000e+00 2.5000000e+00 2.9000000e+00 3.1000000e+00 2.6000000e+00 1.6000000e+00 1.9000000e+00 1.8000000e+00 2.0000000e+00 3.2000000e+00 2.6000000e+00 2.6000000e+00 2.8000000e+00 2.5000000e+00 2.2000000e+00 2.1000000e+00 2.5000000e+00 2.7000000e+00 2.1000000e+00 1.5000000e+00 2.3000000e+00 2.3000000e+00 2.3000000e+00 2.4000000e+00 1.3000000e+00 2.2000000e+00 4.1000000e+00 3.2000000e+00 4.0000000e+00 3.7000000e+00 3.9000000e+00 4.7000000e+00 2.6000000e+00 4.4000000e+00 3.9000000e+00 4.2000000e+00 3.2000000e+00 3.4000000e+00 3.6000000e+00 3.1000000e+00 3.2000000e+00 3.4000000e+00 3.6000000e+00 4.8000000e+00 5.0000000e+00 3.1000000e+00 3.8000000e+00 3.0000000e+00 4.8000000e+00 3.0000000e+00 3.8000000e+00 4.1000000e+00 2.9000000e+00 3.0000000e+00 3.7000000e+00 3.9000000e+00 4.2000000e+00 4.5000000e+00 3.7000000e+00 3.2000000e+00 3.7000000e+00 4.2000000e+00 3.7000000e+00 3.6000000e+00 2.9000000e+00 3.5000000e+00 3.7000000e+00 3.2000000e+00 3.2000000e+00 4.0000000e+00 3.8000000e+00 3.3000000e+00 3.1000000e+00 3.3000000e+00 3.5000000e+00 3.2000000e+00 8.0000000e-01 2.0000000e-01 7.0000000e-01 3.0000000e-01 3.3000000e+00 3.1000000e+00 3.5000000e+00 2.6000000e+00 3.2000000e+00 3.1000000e+00 3.3000000e+00 1.9000000e+00 3.2000000e+00 2.5000000e+00 2.1000000e+00 2.8000000e+00 2.6000000e+00 3.3000000e+00 2.2000000e+00 3.0000000e+00 3.1000000e+00 2.7000000e+00 3.1000000e+00 2.5000000e+00 3.4000000e+00 2.6000000e+00 3.5000000e+00 3.3000000e+00 2.9000000e+00 3.0000000e+00 3.4000000e+00 3.6000000e+00 3.1000000e+00 2.1000000e+00 2.4000000e+00 2.3000000e+00 2.5000000e+00 3.7000000e+00 3.1000000e+00 3.1000000e+00 3.3000000e+00 3.0000000e+00 2.7000000e+00 2.6000000e+00 3.0000000e+00 3.2000000e+00 2.6000000e+00 1.9000000e+00 2.8000000e+00 2.8000000e+00 2.8000000e+00 2.9000000e+00 1.6000000e+00 2.7000000e+00 4.6000000e+00 3.7000000e+00 4.5000000e+00 4.2000000e+00 4.4000000e+00 5.2000000e+00 3.1000000e+00 4.9000000e+00 4.4000000e+00 4.7000000e+00 3.7000000e+00 3.9000000e+00 4.1000000e+00 3.6000000e+00 3.7000000e+00 3.9000000e+00 4.1000000e+00 5.3000000e+00 5.5000000e+00 3.6000000e+00 4.3000000e+00 3.5000000e+00 5.3000000e+00 3.5000000e+00 4.3000000e+00 4.6000000e+00 3.4000000e+00 3.5000000e+00 4.2000000e+00 4.4000000e+00 4.7000000e+00 5.0000000e+00 4.2000000e+00 3.7000000e+00 4.2000000e+00 4.7000000e+00 4.2000000e+00 4.1000000e+00 3.4000000e+00 4.0000000e+00 4.2000000e+00 3.7000000e+00 3.7000000e+00 4.5000000e+00 4.3000000e+00 3.8000000e+00 3.6000000e+00 3.8000000e+00 4.0000000e+00 3.7000000e+00 6.0000000e-01 2.0000000e-01 5.0000000e-01 3.1000000e+00 2.9000000e+00 3.3000000e+00 2.4000000e+00 3.0000000e+00 2.9000000e+00 3.1000000e+00 1.7000000e+00 3.0000000e+00 2.3000000e+00 1.9000000e+00 2.6000000e+00 2.4000000e+00 3.1000000e+00 2.0000000e+00 2.8000000e+00 2.9000000e+00 2.5000000e+00 2.9000000e+00 2.3000000e+00 3.2000000e+00 2.4000000e+00 3.3000000e+00 3.1000000e+00 2.7000000e+00 2.8000000e+00 3.2000000e+00 3.4000000e+00 2.9000000e+00 1.9000000e+00 2.2000000e+00 2.1000000e+00 2.3000000e+00 3.5000000e+00 2.9000000e+00 2.9000000e+00 3.1000000e+00 2.8000000e+00 2.5000000e+00 2.4000000e+00 2.8000000e+00 3.0000000e+00 2.4000000e+00 1.7000000e+00 2.6000000e+00 2.6000000e+00 2.6000000e+00 2.7000000e+00 1.4000000e+00 2.5000000e+00 4.4000000e+00 3.5000000e+00 4.3000000e+00 4.0000000e+00 4.2000000e+00 5.0000000e+00 2.9000000e+00 4.7000000e+00 4.2000000e+00 4.5000000e+00 3.5000000e+00 3.7000000e+00 3.9000000e+00 3.4000000e+00 3.5000000e+00 3.7000000e+00 3.9000000e+00 5.1000000e+00 5.3000000e+00 3.4000000e+00 4.1000000e+00 3.3000000e+00 5.1000000e+00 3.3000000e+00 4.1000000e+00 4.4000000e+00 3.2000000e+00 3.3000000e+00 4.0000000e+00 4.2000000e+00 4.5000000e+00 4.8000000e+00 4.0000000e+00 3.5000000e+00 4.0000000e+00 4.5000000e+00 4.0000000e+00 3.9000000e+00 3.2000000e+00 3.8000000e+00 4.0000000e+00 3.5000000e+00 3.5000000e+00 4.3000000e+00 4.1000000e+00 3.6000000e+00 3.4000000e+00 3.6000000e+00 3.8000000e+00 3.5000000e+00 7.0000000e-01 4.0000000e-01 3.3000000e+00 3.1000000e+00 3.5000000e+00 2.6000000e+00 3.2000000e+00 3.1000000e+00 3.3000000e+00 1.9000000e+00 3.2000000e+00 2.5000000e+00 2.1000000e+00 2.8000000e+00 2.6000000e+00 3.3000000e+00 2.2000000e+00 3.0000000e+00 3.1000000e+00 2.7000000e+00 3.1000000e+00 2.5000000e+00 3.4000000e+00 2.6000000e+00 3.5000000e+00 3.3000000e+00 2.9000000e+00 3.0000000e+00 3.4000000e+00 3.6000000e+00 3.1000000e+00 2.1000000e+00 2.4000000e+00 2.3000000e+00 2.5000000e+00 3.7000000e+00 3.1000000e+00 3.1000000e+00 3.3000000e+00 3.0000000e+00 2.7000000e+00 2.6000000e+00 3.0000000e+00 3.2000000e+00 2.6000000e+00 1.9000000e+00 2.8000000e+00 2.8000000e+00 2.8000000e+00 2.9000000e+00 1.6000000e+00 2.7000000e+00 4.6000000e+00 3.7000000e+00 4.5000000e+00 4.2000000e+00 4.4000000e+00 5.2000000e+00 3.1000000e+00 4.9000000e+00 4.4000000e+00 4.7000000e+00 3.7000000e+00 3.9000000e+00 4.1000000e+00 3.6000000e+00 3.7000000e+00 3.9000000e+00 4.1000000e+00 5.3000000e+00 5.5000000e+00 3.6000000e+00 4.3000000e+00 3.5000000e+00 5.3000000e+00 3.5000000e+00 4.3000000e+00 4.6000000e+00 3.4000000e+00 3.5000000e+00 4.2000000e+00 4.4000000e+00 4.7000000e+00 5.0000000e+00 4.2000000e+00 3.7000000e+00 4.2000000e+00 4.7000000e+00 4.2000000e+00 4.1000000e+00 3.4000000e+00 4.0000000e+00 4.2000000e+00 3.7000000e+00 3.7000000e+00 4.5000000e+00 4.3000000e+00 3.8000000e+00 3.6000000e+00 3.8000000e+00 4.0000000e+00 3.7000000e+00 4.0000000e-01 3.2000000e+00 3.0000000e+00 3.4000000e+00 2.5000000e+00 3.1000000e+00 3.0000000e+00 3.2000000e+00 1.8000000e+00 3.1000000e+00 2.4000000e+00 2.0000000e+00 2.7000000e+00 2.5000000e+00 3.2000000e+00 2.1000000e+00 2.9000000e+00 3.0000000e+00 2.6000000e+00 3.0000000e+00 2.4000000e+00 3.3000000e+00 2.5000000e+00 3.4000000e+00 3.2000000e+00 2.8000000e+00 2.9000000e+00 3.3000000e+00 3.5000000e+00 3.0000000e+00 2.0000000e+00 2.3000000e+00 2.2000000e+00 2.4000000e+00 3.6000000e+00 3.0000000e+00 3.0000000e+00 3.2000000e+00 2.9000000e+00 2.6000000e+00 2.5000000e+00 2.9000000e+00 3.1000000e+00 2.5000000e+00 1.8000000e+00 2.7000000e+00 2.7000000e+00 2.7000000e+00 2.8000000e+00 1.5000000e+00 2.6000000e+00 4.5000000e+00 3.6000000e+00 4.4000000e+00 4.1000000e+00 4.3000000e+00 5.1000000e+00 3.0000000e+00 4.8000000e+00 4.3000000e+00 4.6000000e+00 3.6000000e+00 3.8000000e+00 4.0000000e+00 3.5000000e+00 3.6000000e+00 3.8000000e+00 4.0000000e+00 5.2000000e+00 5.4000000e+00 3.5000000e+00 4.2000000e+00 3.4000000e+00 5.2000000e+00 3.4000000e+00 4.2000000e+00 4.5000000e+00 3.3000000e+00 3.4000000e+00 4.1000000e+00 4.3000000e+00 4.6000000e+00 4.9000000e+00 4.1000000e+00 3.6000000e+00 4.1000000e+00 4.6000000e+00 4.1000000e+00 4.0000000e+00 3.3000000e+00 3.9000000e+00 4.1000000e+00 3.6000000e+00 3.6000000e+00 4.4000000e+00 4.2000000e+00 3.7000000e+00 3.5000000e+00 3.7000000e+00 3.9000000e+00 3.6000000e+00 3.3000000e+00 3.1000000e+00 3.5000000e+00 2.6000000e+00 3.2000000e+00 3.1000000e+00 3.3000000e+00 1.9000000e+00 3.2000000e+00 2.5000000e+00 2.1000000e+00 2.8000000e+00 2.6000000e+00 3.3000000e+00 2.2000000e+00 3.0000000e+00 3.1000000e+00 2.7000000e+00 3.1000000e+00 2.5000000e+00 3.4000000e+00 2.6000000e+00 3.5000000e+00 3.3000000e+00 2.9000000e+00 3.0000000e+00 3.4000000e+00 3.6000000e+00 3.1000000e+00 2.1000000e+00 2.4000000e+00 2.3000000e+00 2.5000000e+00 3.7000000e+00 3.1000000e+00 3.1000000e+00 3.3000000e+00 3.0000000e+00 2.7000000e+00 2.6000000e+00 3.0000000e+00 3.2000000e+00 2.6000000e+00 1.9000000e+00 2.8000000e+00 2.8000000e+00 2.8000000e+00 2.9000000e+00 1.6000000e+00 2.7000000e+00 4.6000000e+00 3.7000000e+00 4.5000000e+00 4.2000000e+00 4.4000000e+00 5.2000000e+00 3.1000000e+00 4.9000000e+00 4.4000000e+00 4.7000000e+00 3.7000000e+00 3.9000000e+00 4.1000000e+00 3.6000000e+00 3.7000000e+00 3.9000000e+00 4.1000000e+00 5.3000000e+00 5.5000000e+00 3.6000000e+00 4.3000000e+00 3.5000000e+00 5.3000000e+00 3.5000000e+00 4.3000000e+00 4.6000000e+00 3.4000000e+00 3.5000000e+00 4.2000000e+00 4.4000000e+00 4.7000000e+00 5.0000000e+00 4.2000000e+00 3.7000000e+00 4.2000000e+00 4.7000000e+00 4.2000000e+00 4.1000000e+00 3.4000000e+00 4.0000000e+00 4.2000000e+00 3.7000000e+00 3.7000000e+00 4.5000000e+00 4.3000000e+00 3.8000000e+00 3.6000000e+00 3.8000000e+00 4.0000000e+00 3.7000000e+00 6.0000000e-01 2.0000000e-01 1.5000000e+00 5.0000000e-01 1.3000000e+00 7.0000000e-01 2.1000000e+00 4.0000000e-01 1.8000000e+00 2.0000000e+00 1.1000000e+00 1.0000000e+00 9.0000000e-01 1.4000000e+00 3.0000000e-01 1.4000000e+00 1.2000000e+00 1.0000000e+00 1.4000000e+00 1.1000000e+00 9.0000000e-01 7.0000000e-01 9.0000000e-01 6.0000000e-01 4.0000000e-01 4.0000000e-01 3.0000000e-01 1.0000000e+00 1.3000000e+00 1.5000000e+00 1.5000000e+00 1.2000000e+00 1.0000000e+00 1.6000000e+00 1.0000000e+00 3.0000000e-01 9.0000000e-01 1.4000000e+00 1.5000000e+00 1.5000000e+00 9.0000000e-01 1.2000000e+00 2.0000000e+00 1.4000000e+00 1.3000000e+00 1.3000000e+00 8.0000000e-01 1.9000000e+00 1.3000000e+00 1.3000000e+00 1.2000000e+00 1.2000000e+00 9.0000000e-01 1.1000000e+00 1.9000000e+00 2.1000000e+00 1.6000000e+00 1.1000000e+00 1.4000000e+00 6.0000000e-01 6.0000000e-01 8.0000000e-01 1.3000000e+00 1.2000000e+00 9.0000000e-01 8.0000000e-01 2.0000000e+00 2.2000000e+00 1.0000000e+00 1.0000000e+00 1.4000000e+00 2.0000000e+00 7.0000000e-01 1.0000000e+00 1.3000000e+00 8.0000000e-01 9.0000000e-01 9.0000000e-01 1.1000000e+00 1.4000000e+00 1.7000000e+00 9.0000000e-01 7.0000000e-01 9.0000000e-01 1.4000000e+00 1.0000000e+00 8.0000000e-01 1.0000000e+00 7.0000000e-01 1.0000000e+00 9.0000000e-01 1.2000000e+00 1.2000000e+00 1.1000000e+00 9.0000000e-01 7.0000000e-01 6.0000000e-01 9.0000000e-01 1.1000000e+00 5.0000000e-01 9.0000000e-01 4.0000000e-01 7.0000000e-01 2.0000000e-01 1.5000000e+00 3.0000000e-01 1.2000000e+00 1.4000000e+00 5.0000000e-01 1.0000000e+00 3.0000000e-01 9.0000000e-01 3.0000000e-01 8.0000000e-01 6.0000000e-01 1.0000000e+00 8.0000000e-01 5.0000000e-01 5.0000000e-01 7.0000000e-01 4.0000000e-01 3.0000000e-01 2.0000000e-01 4.0000000e-01 5.0000000e-01 4.0000000e-01 1.0000000e+00 9.0000000e-01 9.0000000e-01 6.0000000e-01 6.0000000e-01 1.0000000e+00 4.0000000e-01 3.0000000e-01 9.0000000e-01 8.0000000e-01 9.0000000e-01 9.0000000e-01 3.0000000e-01 6.0000000e-01 1.4000000e+00 8.0000000e-01 7.0000000e-01 7.0000000e-01 3.0000000e-01 1.5000000e+00 7.0000000e-01 1.5000000e+00 6.0000000e-01 1.4000000e+00 1.1000000e+00 1.3000000e+00 2.1000000e+00 1.5000000e+00 1.8000000e+00 1.3000000e+00 1.6000000e+00 6.0000000e-01 8.0000000e-01 1.0000000e+00 7.0000000e-01 9.0000000e-01 8.0000000e-01 1.0000000e+00 2.2000000e+00 2.4000000e+00 1.0000000e+00 1.2000000e+00 8.0000000e-01 2.2000000e+00 5.0000000e-01 1.2000000e+00 1.5000000e+00 4.0000000e-01 4.0000000e-01 1.1000000e+00 1.3000000e+00 1.6000000e+00 1.9000000e+00 1.1000000e+00 6.0000000e-01 1.1000000e+00 1.6000000e+00 1.1000000e+00 1.0000000e+00 4.0000000e-01 9.0000000e-01 1.1000000e+00 8.0000000e-01 6.0000000e-01 1.4000000e+00 1.2000000e+00 8.0000000e-01 7.0000000e-01 7.0000000e-01 9.0000000e-01 6.0000000e-01 1.4000000e+00 4.0000000e-01 1.2000000e+00 6.0000000e-01 2.0000000e+00 3.0000000e-01 1.7000000e+00 1.9000000e+00 1.0000000e+00 9.0000000e-01 8.0000000e-01 1.3000000e+00 5.0000000e-01 1.3000000e+00 1.1000000e+00 9.0000000e-01 1.3000000e+00 1.0000000e+00 9.0000000e-01 6.0000000e-01 8.0000000e-01 6.0000000e-01 5.0000000e-01 3.0000000e-01 2.0000000e-01 9.0000000e-01 1.4000000e+00 1.4000000e+00 1.4000000e+00 1.1000000e+00 9.0000000e-01 1.5000000e+00 9.0000000e-01 2.0000000e-01 8.0000000e-01 1.3000000e+00 1.4000000e+00 1.4000000e+00 8.0000000e-01 1.1000000e+00 1.9000000e+00 1.3000000e+00 1.2000000e+00 1.2000000e+00 7.0000000e-01 1.9000000e+00 1.2000000e+00 1.1000000e+00 1.1000000e+00 1.0000000e+00 7.0000000e-01 9.0000000e-01 1.7000000e+00 2.0000000e+00 1.4000000e+00 9.0000000e-01 1.2000000e+00 5.0000000e-01 5.0000000e-01 6.0000000e-01 1.2000000e+00 1.1000000e+00 8.0000000e-01 6.0000000e-01 1.8000000e+00 2.0000000e+00 9.0000000e-01 8.0000000e-01 1.3000000e+00 1.8000000e+00 6.0000000e-01 8.0000000e-01 1.1000000e+00 7.0000000e-01 8.0000000e-01 7.0000000e-01 9.0000000e-01 1.2000000e+00 1.5000000e+00 7.0000000e-01 6.0000000e-01 8.0000000e-01 1.2000000e+00 9.0000000e-01 6.0000000e-01 9.0000000e-01 6.0000000e-01 9.0000000e-01 8.0000000e-01 1.1000000e+00 1.0000000e+00 1.0000000e+00 8.0000000e-01 6.0000000e-01 5.0000000e-01 8.0000000e-01 1.0000000e+00 1.0000000e+00 5.0000000e-01 1.0000000e+00 7.0000000e-01 1.1000000e+00 4.0000000e-01 5.0000000e-01 7.0000000e-01 5.0000000e-01 7.0000000e-01 6.0000000e-01 1.2000000e+00 7.0000000e-01 4.0000000e-01 7.0000000e-01 2.0000000e-01 9.0000000e-01 6.0000000e-01 9.0000000e-01 7.0000000e-01 9.0000000e-01 1.1000000e+00 1.3000000e+00 1.2000000e+00 6.0000000e-01 5.0000000e-01 2.0000000e-01 3.0000000e-01 4.0000000e-01 1.1000000e+00 7.0000000e-01 1.1000000e+00 1.2000000e+00 8.0000000e-01 7.0000000e-01 2.0000000e-01 4.0000000e-01 7.0000000e-01 3.0000000e-01 7.0000000e-01 4.0000000e-01 7.0000000e-01 6.0000000e-01 7.0000000e-01 1.0000000e+00 5.0000000e-01 2.0000000e+00 1.1000000e+00 1.9000000e+00 1.6000000e+00 1.8000000e+00 2.6000000e+00 6.0000000e-01 2.3000000e+00 1.8000000e+00 2.1000000e+00 1.1000000e+00 1.3000000e+00 1.5000000e+00 1.0000000e+00 1.1000000e+00 1.3000000e+00 1.5000000e+00 2.7000000e+00 2.9000000e+00 1.0000000e+00 1.7000000e+00 9.0000000e-01 2.7000000e+00 9.0000000e-01 1.7000000e+00 2.0000000e+00 8.0000000e-01 9.0000000e-01 1.6000000e+00 1.8000000e+00 2.1000000e+00 2.4000000e+00 1.6000000e+00 1.1000000e+00 1.6000000e+00 2.2000000e+00 1.6000000e+00 1.5000000e+00 8.0000000e-01 1.4000000e+00 1.6000000e+00 1.4000000e+00 1.1000000e+00 1.9000000e+00 1.7000000e+00 1.2000000e+00 1.0000000e+00 1.2000000e+00 1.4000000e+00 1.1000000e+00 8.0000000e-01 5.0000000e-01 1.6000000e+00 2.0000000e-01 1.3000000e+00 1.5000000e+00 6.0000000e-01 6.0000000e-01 4.0000000e-01 1.0000000e+00 3.0000000e-01 9.0000000e-01 7.0000000e-01 6.0000000e-01 9.0000000e-01 6.0000000e-01 6.0000000e-01 3.0000000e-01 4.0000000e-01 3.0000000e-01 2.0000000e-01 3.0000000e-01 4.0000000e-01 5.0000000e-01 1.1000000e+00 1.0000000e+00 1.0000000e+00 7.0000000e-01 5.0000000e-01 1.1000000e+00 6.0000000e-01 3.0000000e-01 5.0000000e-01 9.0000000e-01 1.0000000e+00 1.0000000e+00 4.0000000e-01 7.0000000e-01 1.5000000e+00 9.0000000e-01 8.0000000e-01 8.0000000e-01 3.0000000e-01 1.6000000e+00 8.0000000e-01 1.4000000e+00 7.0000000e-01 1.3000000e+00 1.0000000e+00 1.2000000e+00 2.0000000e+00 1.6000000e+00 1.7000000e+00 1.2000000e+00 1.5000000e+00 5.0000000e-01 7.0000000e-01 9.0000000e-01 8.0000000e-01 9.0000000e-01 8.0000000e-01 9.0000000e-01 2.1000000e+00 2.3000000e+00 6.0000000e-01 1.1000000e+00 9.0000000e-01 2.1000000e+00 3.0000000e-01 1.1000000e+00 1.4000000e+00 3.0000000e-01 4.0000000e-01 1.0000000e+00 1.2000000e+00 1.5000000e+00 1.8000000e+00 1.0000000e+00 5.0000000e-01 1.0000000e+00 1.5000000e+00 1.0000000e+00 9.0000000e-01 5.0000000e-01 8.0000000e-01 1.0000000e+00 8.0000000e-01 7.0000000e-01 1.3000000e+00 1.1000000e+00 8.0000000e-01 4.0000000e-01 6.0000000e-01 8.0000000e-01 6.0000000e-01 6.0000000e-01 1.2000000e+00 9.0000000e-01 6.0000000e-01 1.0000000e+00 3.0000000e-01 6.0000000e-01 4.0000000e-01 9.0000000e-01 1.0000000e+00 2.0000000e-01 4.0000000e-01 6.0000000e-01 6.0000000e-01 5.0000000e-01 5.0000000e-01 6.0000000e-01 4.0000000e-01 7.0000000e-01 9.0000000e-01 1.1000000e+00 1.0000000e+00 3.0000000e-01 1.0000000e+00 7.0000000e-01 8.0000000e-01 6.0000000e-01 6.0000000e-01 3.0000000e-01 6.0000000e-01 1.0000000e+00 6.0000000e-01 4.0000000e-01 5.0000000e-01 2.0000000e-01 4.0000000e-01 5.0000000e-01 1.2000000e+00 3.0000000e-01 3.0000000e-01 3.0000000e-01 5.0000000e-01 1.5000000e+00 4.0000000e-01 1.5000000e+00 6.0000000e-01 1.4000000e+00 1.1000000e+00 1.3000000e+00 2.1000000e+00 8.0000000e-01 1.8000000e+00 1.3000000e+00 1.6000000e+00 8.0000000e-01 8.0000000e-01 1.1000000e+00 7.0000000e-01 1.1000000e+00 1.0000000e+00 1.0000000e+00 2.2000000e+00 2.4000000e+00 6.0000000e-01 1.2000000e+00 7.0000000e-01 2.2000000e+00 6.0000000e-01 1.2000000e+00 1.5000000e+00 5.0000000e-01 5.0000000e-01 1.1000000e+00 1.5000000e+00 1.7000000e+00 2.2000000e+00 1.1000000e+00 6.0000000e-01 1.1000000e+00 2.0000000e+00 1.1000000e+00 1.0000000e+00 5.0000000e-01 1.2000000e+00 1.1000000e+00 1.2000000e+00 6.0000000e-01 1.4000000e+00 1.2000000e+00 1.0000000e+00 6.0000000e-01 8.0000000e-01 1.0000000e+00 6.0000000e-01 1.4000000e+00 4.0000000e-01 1.1000000e+00 1.3000000e+00 5.0000000e-01 1.1000000e+00 4.0000000e-01 1.1000000e+00 4.0000000e-01 7.0000000e-01 6.0000000e-01 1.1000000e+00 8.0000000e-01 4.0000000e-01 7.0000000e-01 8.0000000e-01 5.0000000e-01 4.0000000e-01 3.0000000e-01 5.0000000e-01 4.0000000e-01 4.0000000e-01 1.2000000e+00 9.0000000e-01 1.0000000e+00 8.0000000e-01 6.0000000e-01 9.0000000e-01 3.0000000e-01 4.0000000e-01 1.0000000e+00 7.0000000e-01 8.0000000e-01 8.0000000e-01 3.0000000e-01 7.0000000e-01 1.4000000e+00 7.0000000e-01 6.0000000e-01 6.0000000e-01 4.0000000e-01 1.7000000e+00 6.0000000e-01 1.3000000e+00 6.0000000e-01 1.2000000e+00 9.0000000e-01 1.1000000e+00 1.9000000e+00 1.4000000e+00 1.6000000e+00 1.1000000e+00 1.4000000e+00 4.0000000e-01 6.0000000e-01 8.0000000e-01 8.0000000e-01 8.0000000e-01 7.0000000e-01 8.0000000e-01 2.0000000e+00 2.2000000e+00 1.1000000e+00 1.0000000e+00 7.0000000e-01 2.0000000e+00 6.0000000e-01 1.0000000e+00 1.3000000e+00 5.0000000e-01 3.0000000e-01 9.0000000e-01 1.1000000e+00 1.4000000e+00 1.7000000e+00 9.0000000e-01 5.0000000e-01 9.0000000e-01 1.4000000e+00 9.0000000e-01 8.0000000e-01 3.0000000e-01 7.0000000e-01 9.0000000e-01 7.0000000e-01 6.0000000e-01 1.2000000e+00 1.0000000e+00 7.0000000e-01 8.0000000e-01 5.0000000e-01 7.0000000e-01 4.0000000e-01 1.7000000e+00 6.0000000e-01 4.0000000e-01 1.0000000e+00 1.1000000e+00 1.4000000e+00 7.0000000e-01 1.8000000e+00 1.2000000e+00 9.0000000e-01 1.3000000e+00 7.0000000e-01 1.5000000e+00 1.2000000e+00 1.6000000e+00 1.4000000e+00 1.5000000e+00 1.7000000e+00 1.9000000e+00 1.8000000e+00 1.2000000e+00 8.0000000e-01 6.0000000e-01 6.0000000e-01 9.0000000e-01 1.8000000e+00 1.2000000e+00 1.2000000e+00 1.8000000e+00 1.4000000e+00 8.0000000e-01 7.0000000e-01 1.1000000e+00 1.3000000e+00 9.0000000e-01 1.0000000e-01 9.0000000e-01 9.0000000e-01 9.0000000e-01 1.3000000e+00 3.0000000e-01 8.0000000e-01 2.7000000e+00 1.8000000e+00 2.6000000e+00 2.3000000e+00 2.5000000e+00 3.3000000e+00 1.2000000e+00 3.0000000e+00 2.5000000e+00 2.8000000e+00 1.8000000e+00 2.0000000e+00 2.2000000e+00 1.7000000e+00 1.8000000e+00 2.0000000e+00 2.2000000e+00 3.4000000e+00 3.6000000e+00 1.7000000e+00 2.4000000e+00 1.6000000e+00 3.4000000e+00 1.6000000e+00 2.4000000e+00 2.7000000e+00 1.5000000e+00 1.6000000e+00 2.3000000e+00 2.5000000e+00 2.8000000e+00 3.1000000e+00 2.3000000e+00 1.8000000e+00 2.3000000e+00 2.8000000e+00 2.3000000e+00 2.2000000e+00 1.5000000e+00 2.1000000e+00 2.3000000e+00 2.0000000e+00 1.8000000e+00 2.6000000e+00 2.4000000e+00 1.9000000e+00 1.7000000e+00 1.9000000e+00 2.1000000e+00 1.8000000e+00 1.4000000e+00 1.6000000e+00 7.0000000e-01 7.0000000e-01 5.0000000e-01 1.0000000e+00 2.0000000e-01 1.0000000e+00 8.0000000e-01 7.0000000e-01 1.0000000e+00 7.0000000e-01 6.0000000e-01 4.0000000e-01 5.0000000e-01 3.0000000e-01 2.0000000e-01 2.0000000e-01 4.0000000e-01 6.0000000e-01 1.1000000e+00 1.1000000e+00 1.1000000e+00 8.0000000e-01 6.0000000e-01 1.2000000e+00 6.0000000e-01 2.0000000e-01 6.0000000e-01 1.0000000e+00 1.1000000e+00 1.1000000e+00 5.0000000e-01 8.0000000e-01 1.6000000e+00 1.0000000e+00 9.0000000e-01 9.0000000e-01 4.0000000e-01 1.6000000e+00 9.0000000e-01 1.4000000e+00 8.0000000e-01 1.3000000e+00 1.0000000e+00 1.2000000e+00 2.0000000e+00 1.7000000e+00 1.7000000e+00 1.2000000e+00 1.5000000e+00 7.0000000e-01 7.0000000e-01 9.0000000e-01 9.0000000e-01 1.1000000e+00 1.0000000e+00 9.0000000e-01 2.1000000e+00 2.3000000e+00 7.0000000e-01 1.1000000e+00 1.0000000e+00 2.1000000e+00 5.0000000e-01 1.1000000e+00 1.4000000e+00 5.0000000e-01 5.0000000e-01 1.0000000e+00 1.2000000e+00 1.5000000e+00 1.8000000e+00 1.0000000e+00 5.0000000e-01 1.0000000e+00 1.5000000e+00 1.1000000e+00 9.0000000e-01 6.0000000e-01 8.0000000e-01 1.1000000e+00 1.0000000e+00 8.0000000e-01 1.3000000e+00 1.2000000e+00 1.0000000e+00 6.0000000e-01 7.0000000e-01 1.0000000e+00 7.0000000e-01 7.0000000e-01 7.0000000e-01 8.0000000e-01 9.0000000e-01 4.0000000e-01 1.5000000e+00 6.0000000e-01 6.0000000e-01 1.0000000e+00 4.0000000e-01 9.0000000e-01 9.0000000e-01 1.1000000e+00 9.0000000e-01 1.2000000e+00 1.4000000e+00 1.6000000e+00 1.5000000e+00 8.0000000e-01 5.0000000e-01 3.0000000e-01 4.0000000e-01 6.0000000e-01 1.2000000e+00 6.0000000e-01 8.0000000e-01 1.5000000e+00 1.1000000e+00 4.0000000e-01 3.0000000e-01 5.0000000e-01 9.0000000e-01 6.0000000e-01 6.0000000e-01 4.0000000e-01 5.0000000e-01 5.0000000e-01 1.0000000e+00 9.0000000e-01 5.0000000e-01 2.1000000e+00 1.2000000e+00 2.0000000e+00 1.7000000e+00 1.9000000e+00 2.7000000e+00 6.0000000e-01 2.4000000e+00 1.9000000e+00 2.2000000e+00 1.3000000e+00 1.4000000e+00 1.6000000e+00 1.1000000e+00 1.2000000e+00 1.4000000e+00 1.6000000e+00 2.8000000e+00 3.0000000e+00 1.1000000e+00 1.8000000e+00 1.0000000e+00 2.8000000e+00 1.1000000e+00 1.8000000e+00 2.1000000e+00 1.0000000e+00 1.0000000e+00 1.7000000e+00 2.0000000e+00 2.2000000e+00 2.7000000e+00 1.7000000e+00 1.2000000e+00 1.7000000e+00 2.5000000e+00 1.7000000e+00 1.6000000e+00 9.0000000e-01 1.7000000e+00 1.7000000e+00 1.7000000e+00 1.2000000e+00 2.0000000e+00 1.8000000e+00 1.5000000e+00 1.1000000e+00 1.3000000e+00 1.5000000e+00 1.2000000e+00 1.0000000e+00 1.0000000e+00 1.2000000e+00 9.0000000e-01 1.7000000e+00 1.0000000e+00 8.0000000e-01 1.2000000e+00 6.0000000e-01 1.3000000e+00 1.1000000e+00 1.4000000e+00 1.2000000e+00 1.4000000e+00 1.6000000e+00 1.8000000e+00 1.7000000e+00 1.0000000e+00 7.0000000e-01 5.0000000e-01 5.0000000e-01 8.0000000e-01 1.6000000e+00 1.0000000e+00 1.4000000e+00 1.7000000e+00 1.3000000e+00 1.0000000e+00 5.0000000e-01 9.0000000e-01 1.1000000e+00 8.0000000e-01 3.0000000e-01 7.0000000e-01 1.0000000e+00 9.0000000e-01 1.2000000e+00 5.0000000e-01 8.0000000e-01 2.5000000e+00 1.6000000e+00 2.4000000e+00 2.1000000e+00 2.3000000e+00 3.1000000e+00 1.0000000e+00 2.8000000e+00 2.3000000e+00 2.6000000e+00 1.6000000e+00 1.8000000e+00 2.0000000e+00 1.5000000e+00 1.6000000e+00 1.8000000e+00 2.0000000e+00 3.2000000e+00 3.4000000e+00 1.5000000e+00 2.2000000e+00 1.4000000e+00 3.2000000e+00 1.4000000e+00 2.2000000e+00 2.5000000e+00 1.3000000e+00 1.4000000e+00 2.1000000e+00 2.3000000e+00 2.6000000e+00 2.9000000e+00 2.1000000e+00 1.6000000e+00 2.1000000e+00 2.7000000e+00 2.1000000e+00 2.0000000e+00 1.3000000e+00 1.9000000e+00 2.1000000e+00 1.9000000e+00 1.6000000e+00 2.4000000e+00 2.2000000e+00 1.7000000e+00 1.5000000e+00 1.7000000e+00 1.9000000e+00 1.6000000e+00 8.0000000e-01 5.0000000e-01 6.0000000e-01 8.0000000e-01 3.0000000e-01 5.0000000e-01 8.0000000e-01 5.0000000e-01 6.0000000e-01 2.0000000e-01 7.0000000e-01 5.0000000e-01 5.0000000e-01 7.0000000e-01 9.0000000e-01 8.0000000e-01 3.0000000e-01 7.0000000e-01 6.0000000e-01 6.0000000e-01 3.0000000e-01 9.0000000e-01 5.0000000e-01 4.0000000e-01 8.0000000e-01 7.0000000e-01 3.0000000e-01 5.0000000e-01 4.0000000e-01 4.0000000e-01 4.0000000e-01 9.0000000e-01 3.0000000e-01 3.0000000e-01 2.0000000e-01 3.0000000e-01 1.2000000e+00 2.0000000e-01 1.8000000e+00 9.0000000e-01 1.7000000e+00 1.4000000e+00 1.6000000e+00 2.4000000e+00 1.0000000e+00 2.1000000e+00 1.6000000e+00 1.9000000e+00 9.0000000e-01 1.1000000e+00 1.3000000e+00 8.0000000e-01 9.0000000e-01 1.1000000e+00 1.3000000e+00 2.5000000e+00 2.7000000e+00 8.0000000e-01 1.5000000e+00 7.0000000e-01 2.5000000e+00 7.0000000e-01 1.5000000e+00 1.8000000e+00 6.0000000e-01 7.0000000e-01 1.4000000e+00 1.6000000e+00 1.9000000e+00 2.2000000e+00 1.4000000e+00 9.0000000e-01 1.4000000e+00 1.9000000e+00 1.4000000e+00 1.3000000e+00 6.0000000e-01 1.2000000e+00 1.4000000e+00 1.0000000e+00 9.0000000e-01 1.7000000e+00 1.5000000e+00 1.0000000e+00 8.0000000e-01 1.0000000e+00 1.2000000e+00 9.0000000e-01 7.0000000e-01 7.0000000e-01 9.0000000e-01 8.0000000e-01 5.0000000e-01 5.0000000e-01 4.0000000e-01 1.0000000e+00 6.0000000e-01 9.0000000e-01 7.0000000e-01 7.0000000e-01 8.0000000e-01 8.0000000e-01 1.0000000e+00 7.0000000e-01 5.0000000e-01 5.0000000e-01 5.0000000e-01 5.0000000e-01 1.1000000e+00 8.0000000e-01 1.2000000e+00 9.0000000e-01 4.0000000e-01 8.0000000e-01 5.0000000e-01 5.0000000e-01 8.0000000e-01 4.0000000e-01 1.0000000e+00 5.0000000e-01 8.0000000e-01 7.0000000e-01 7.0000000e-01 1.0000000e+00 6.0000000e-01 2.0000000e+00 1.1000000e+00 1.9000000e+00 1.6000000e+00 1.8000000e+00 2.6000000e+00 1.1000000e+00 2.3000000e+00 1.8000000e+00 2.1000000e+00 1.1000000e+00 1.3000000e+00 1.5000000e+00 1.0000000e+00 1.4000000e+00 1.3000000e+00 1.5000000e+00 2.7000000e+00 2.9000000e+00 1.0000000e+00 1.7000000e+00 1.0000000e+00 2.7000000e+00 9.0000000e-01 1.7000000e+00 2.0000000e+00 8.0000000e-01 9.0000000e-01 1.6000000e+00 1.8000000e+00 2.1000000e+00 2.4000000e+00 1.6000000e+00 1.1000000e+00 1.6000000e+00 2.1000000e+00 1.6000000e+00 1.5000000e+00 8.0000000e-01 1.4000000e+00 1.6000000e+00 1.3000000e+00 1.1000000e+00 1.9000000e+00 1.7000000e+00 1.3000000e+00 1.0000000e+00 1.2000000e+00 1.4000000e+00 1.1000000e+00 1.1000000e+00 6.0000000e-01 5.0000000e-01 6.0000000e-01 7.0000000e-01 8.0000000e-01 4.0000000e-01 7.0000000e-01 4.0000000e-01 2.0000000e-01 4.0000000e-01 5.0000000e-01 7.0000000e-01 6.0000000e-01 2.0000000e-01 1.2000000e+00 9.0000000e-01 1.0000000e+00 8.0000000e-01 4.0000000e-01 7.0000000e-01 5.0000000e-01 6.0000000e-01 6.0000000e-01 6.0000000e-01 7.0000000e-01 6.0000000e-01 1.0000000e-01 7.0000000e-01 1.4000000e+00 5.0000000e-01 5.0000000e-01 5.0000000e-01 4.0000000e-01 1.7000000e+00 6.0000000e-01 1.3000000e+00 5.0000000e-01 1.2000000e+00 9.0000000e-01 1.1000000e+00 1.9000000e+00 1.2000000e+00 1.6000000e+00 1.1000000e+00 1.4000000e+00 6.0000000e-01 6.0000000e-01 8.0000000e-01 6.0000000e-01 1.0000000e+00 9.0000000e-01 8.0000000e-01 2.0000000e+00 2.2000000e+00 7.0000000e-01 1.0000000e+00 6.0000000e-01 2.0000000e+00 4.0000000e-01 1.0000000e+00 1.3000000e+00 4.0000000e-01 4.0000000e-01 9.0000000e-01 1.1000000e+00 1.4000000e+00 1.8000000e+00 9.0000000e-01 4.0000000e-01 9.0000000e-01 1.6000000e+00 1.0000000e+00 8.0000000e-01 4.0000000e-01 8.0000000e-01 1.0000000e+00 9.0000000e-01 5.0000000e-01 1.2000000e+00 1.1000000e+00 9.0000000e-01 5.0000000e-01 6.0000000e-01 9.0000000e-01 4.0000000e-01 1.1000000e+00 9.0000000e-01 5.0000000e-01 9.0000000e-01 4.0000000e-01 1.2000000e+00 5.0000000e-01 1.3000000e+00 1.1000000e+00 8.0000000e-01 1.0000000e+00 1.2000000e+00 1.4000000e+00 9.0000000e-01 3.0000000e-01 5.0000000e-01 5.0000000e-01 3.0000000e-01 1.5000000e+00 9.0000000e-01 9.0000000e-01 1.1000000e+00 8.0000000e-01 5.0000000e-01 4.0000000e-01 8.0000000e-01 1.0000000e+00 4.0000000e-01 6.0000000e-01 6.0000000e-01 6.0000000e-01 6.0000000e-01 7.0000000e-01 6.0000000e-01 5.0000000e-01 2.4000000e+00 1.5000000e+00 2.3000000e+00 2.0000000e+00 2.2000000e+00 3.0000000e+00 9.0000000e-01 2.7000000e+00 2.2000000e+00 2.5000000e+00 1.5000000e+00 1.7000000e+00 1.9000000e+00 1.4000000e+00 1.5000000e+00 1.7000000e+00 1.9000000e+00 3.1000000e+00 3.3000000e+00 1.4000000e+00 2.1000000e+00 1.3000000e+00 3.1000000e+00 1.3000000e+00 2.1000000e+00 2.4000000e+00 1.2000000e+00 1.3000000e+00 2.0000000e+00 2.2000000e+00 2.5000000e+00 2.8000000e+00 2.0000000e+00 1.5000000e+00 2.0000000e+00 2.5000000e+00 2.0000000e+00 1.9000000e+00 1.2000000e+00 1.8000000e+00 2.0000000e+00 1.5000000e+00 1.5000000e+00 2.3000000e+00 2.1000000e+00 1.6000000e+00 1.4000000e+00 1.6000000e+00 1.8000000e+00 1.5000000e+00 1.1000000e+00 9.0000000e-01 9.0000000e-01 1.1000000e+00 8.0000000e-01 6.0000000e-01 6.0000000e-01 6.0000000e-01 3.0000000e-01 1.0000000e-01 4.0000000e-01 6.0000000e-01 7.0000000e-01 1.0000000e+00 1.2000000e+00 1.2000000e+00 9.0000000e-01 7.0000000e-01 1.3000000e+00 7.0000000e-01 3.0000000e-01 8.0000000e-01 1.1000000e+00 1.2000000e+00 1.2000000e+00 6.0000000e-01 9.0000000e-01 1.7000000e+00 1.1000000e+00 1.0000000e+00 1.0000000e+00 5.0000000e-01 1.6000000e+00 1.0000000e+00 1.6000000e+00 9.0000000e-01 1.5000000e+00 1.2000000e+00 1.4000000e+00 2.2000000e+00 1.8000000e+00 1.9000000e+00 1.4000000e+00 1.7000000e+00 7.0000000e-01 9.0000000e-01 1.1000000e+00 1.0000000e+00 1.0000000e+00 9.0000000e-01 1.1000000e+00 2.3000000e+00 2.5000000e+00 9.0000000e-01 1.3000000e+00 1.1000000e+00 2.3000000e+00 5.0000000e-01 1.3000000e+00 1.6000000e+00 5.0000000e-01 6.0000000e-01 1.2000000e+00 1.4000000e+00 1.7000000e+00 2.0000000e+00 1.2000000e+00 7.0000000e-01 1.2000000e+00 1.7000000e+00 1.2000000e+00 1.1000000e+00 7.0000000e-01 1.0000000e+00 1.2000000e+00 9.0000000e-01 9.0000000e-01 1.5000000e+00 1.3000000e+00 9.0000000e-01 6.0000000e-01 8.0000000e-01 1.0000000e+00 8.0000000e-01 5.0000000e-01 8.0000000e-01 6.0000000e-01 3.0000000e-01 5.0000000e-01 7.0000000e-01 5.0000000e-01 8.0000000e-01 1.0000000e+00 1.2000000e+00 1.1000000e+00 4.0000000e-01 1.0000000e+00 7.0000000e-01 8.0000000e-01 6.0000000e-01 6.0000000e-01 2.0000000e-01 4.0000000e-01 1.1000000e+00 7.0000000e-01 4.0000000e-01 5.0000000e-01 4.0000000e-01 5.0000000e-01 5.0000000e-01 1.2000000e+00 3.0000000e-01 3.0000000e-01 3.0000000e-01 6.0000000e-01 1.5000000e+00 4.0000000e-01 1.5000000e+00 6.0000000e-01 1.5000000e+00 1.1000000e+00 1.3000000e+00 2.1000000e+00 7.0000000e-01 1.8000000e+00 1.3000000e+00 1.6000000e+00 9.0000000e-01 8.0000000e-01 1.2000000e+00 5.0000000e-01 9.0000000e-01 8.0000000e-01 1.0000000e+00 2.2000000e+00 2.4000000e+00 8.0000000e-01 1.3000000e+00 5.0000000e-01 2.2000000e+00 7.0000000e-01 1.2000000e+00 1.6000000e+00 6.0000000e-01 5.0000000e-01 1.1000000e+00 1.6000000e+00 1.8000000e+00 2.3000000e+00 1.1000000e+00 7.0000000e-01 1.1000000e+00 2.1000000e+00 1.1000000e+00 1.0000000e+00 4.0000000e-01 1.3000000e+00 1.1000000e+00 1.3000000e+00 6.0000000e-01 1.4000000e+00 1.2000000e+00 1.1000000e+00 7.0000000e-01 9.0000000e-01 9.0000000e-01 6.0000000e-01 5.0000000e-01 2.0000000e-01 8.0000000e-01 3.0000000e-01 8.0000000e-01 6.0000000e-01 6.0000000e-01 8.0000000e-01 1.0000000e+00 9.0000000e-01 5.0000000e-01 6.0000000e-01 3.0000000e-01 4.0000000e-01 2.0000000e-01 1.0000000e+00 5.0000000e-01 7.0000000e-01 9.0000000e-01 5.0000000e-01 3.0000000e-01 3.0000000e-01 3.0000000e-01 5.0000000e-01 2.0000000e-01 8.0000000e-01 3.0000000e-01 3.0000000e-01 3.0000000e-01 4.0000000e-01 1.1000000e+00 3.0000000e-01 1.9000000e+00 1.0000000e+00 1.8000000e+00 1.5000000e+00 1.7000000e+00 2.5000000e+00 9.0000000e-01 2.2000000e+00 1.7000000e+00 2.0000000e+00 1.0000000e+00 1.2000000e+00 1.4000000e+00 1.0000000e+00 1.4000000e+00 1.3000000e+00 1.4000000e+00 2.6000000e+00 2.8000000e+00 9.0000000e-01 1.6000000e+00 1.0000000e+00 2.6000000e+00 8.0000000e-01 1.6000000e+00 1.9000000e+00 8.0000000e-01 8.0000000e-01 1.5000000e+00 1.7000000e+00 2.0000000e+00 2.3000000e+00 1.5000000e+00 1.0000000e+00 1.5000000e+00 2.0000000e+00 1.5000000e+00 1.4000000e+00 8.0000000e-01 1.3000000e+00 1.5000000e+00 1.3000000e+00 1.0000000e+00 1.8000000e+00 1.6000000e+00 1.3000000e+00 9.0000000e-01 1.1000000e+00 1.3000000e+00 1.0000000e+00 6.0000000e-01 1.0000000e+00 6.0000000e-01 4.0000000e-01 6.0000000e-01 7.0000000e-01 8.0000000e-01 6.0000000e-01 8.0000000e-01 7.0000000e-01 1.0000000e+00 7.0000000e-01 8.0000000e-01 6.0000000e-01 6.0000000e-01 8.0000000e-01 1.2000000e+00 9.0000000e-01 2.0000000e-01 8.0000000e-01 7.0000000e-01 7.0000000e-01 8.0000000e-01 5.0000000e-01 1.2000000e+00 6.0000000e-01 8.0000000e-01 7.0000000e-01 7.0000000e-01 1.5000000e+00 6.0000000e-01 1.5000000e+00 6.0000000e-01 1.4000000e+00 1.1000000e+00 1.3000000e+00 2.1000000e+00 1.3000000e+00 1.8000000e+00 1.3000000e+00 1.6000000e+00 1.0000000e+00 8.0000000e-01 1.0000000e+00 5.0000000e-01 9.0000000e-01 1.0000000e+00 1.0000000e+00 2.2000000e+00 2.4000000e+00 5.0000000e-01 1.2000000e+00 6.0000000e-01 2.2000000e+00 5.0000000e-01 1.2000000e+00 1.5000000e+00 6.0000000e-01 8.0000000e-01 1.1000000e+00 1.3000000e+00 1.6000000e+00 1.9000000e+00 1.1000000e+00 6.0000000e-01 1.1000000e+00 1.6000000e+00 1.2000000e+00 1.0000000e+00 8.0000000e-01 9.0000000e-01 1.1000000e+00 9.0000000e-01 6.0000000e-01 1.4000000e+00 1.2000000e+00 8.0000000e-01 5.0000000e-01 8.0000000e-01 1.2000000e+00 8.0000000e-01 9.0000000e-01 5.0000000e-01 1.0000000e+00 8.0000000e-01 8.0000000e-01 1.0000000e+00 1.2000000e+00 1.1000000e+00 6.0000000e-01 4.0000000e-01 1.0000000e-01 2.0000000e-01 2.0000000e-01 1.2000000e+00 6.0000000e-01 9.0000000e-01 1.1000000e+00 7.0000000e-01 5.0000000e-01 2.0000000e-01 5.0000000e-01 7.0000000e-01 2.0000000e-01 6.0000000e-01 3.0000000e-01 5.0000000e-01 4.0000000e-01 6.0000000e-01 9.0000000e-01 3.0000000e-01 2.1000000e+00 1.2000000e+00 2.0000000e+00 1.7000000e+00 1.9000000e+00 2.7000000e+00 7.0000000e-01 2.4000000e+00 1.9000000e+00 2.2000000e+00 1.2000000e+00 1.4000000e+00 1.6000000e+00 1.1000000e+00 1.3000000e+00 1.4000000e+00 1.6000000e+00 2.8000000e+00 3.0000000e+00 1.1000000e+00 1.8000000e+00 1.0000000e+00 2.8000000e+00 1.0000000e+00 1.8000000e+00 2.1000000e+00 9.0000000e-01 1.0000000e+00 1.7000000e+00 1.9000000e+00 2.2000000e+00 2.5000000e+00 1.7000000e+00 1.2000000e+00 1.7000000e+00 2.2000000e+00 1.7000000e+00 1.6000000e+00 9.0000000e-01 1.5000000e+00 1.7000000e+00 1.3000000e+00 1.2000000e+00 2.0000000e+00 1.8000000e+00 1.3000000e+00 1.1000000e+00 1.3000000e+00 1.5000000e+00 1.2000000e+00 8.0000000e-01 7.0000000e-01 6.0000000e-01 5.0000000e-01 7.0000000e-01 9.0000000e-01 8.0000000e-01 3.0000000e-01 1.3000000e+00 1.0000000e+00 1.1000000e+00 9.0000000e-01 5.0000000e-01 5.0000000e-01 3.0000000e-01 8.0000000e-01 9.0000000e-01 7.0000000e-01 8.0000000e-01 6.0000000e-01 4.0000000e-01 8.0000000e-01 1.5000000e+00 6.0000000e-01 6.0000000e-01 6.0000000e-01 5.0000000e-01 1.8000000e+00 7.0000000e-01 1.2000000e+00 5.0000000e-01 1.2000000e+00 8.0000000e-01 1.0000000e+00 1.8000000e+00 1.0000000e+00 1.5000000e+00 1.0000000e+00 1.3000000e+00 6.0000000e-01 5.0000000e-01 9.0000000e-01 7.0000000e-01 6.0000000e-01 5.0000000e-01 7.0000000e-01 1.9000000e+00 2.1000000e+00 1.0000000e+00 1.0000000e+00 4.0000000e-01 1.9000000e+00 5.0000000e-01 9.0000000e-01 1.3000000e+00 4.0000000e-01 2.0000000e-01 8.0000000e-01 1.3000000e+00 1.5000000e+00 2.0000000e+00 8.0000000e-01 4.0000000e-01 8.0000000e-01 1.8000000e+00 8.0000000e-01 7.0000000e-01 2.0000000e-01 1.0000000e+00 8.0000000e-01 1.0000000e+00 5.0000000e-01 1.1000000e+00 9.0000000e-01 8.0000000e-01 7.0000000e-01 6.0000000e-01 6.0000000e-01 3.0000000e-01 9.0000000e-01 7.0000000e-01 3.0000000e-01 5.0000000e-01 8.0000000e-01 1.0000000e+00 5.0000000e-01 5.0000000e-01 6.0000000e-01 6.0000000e-01 3.0000000e-01 1.1000000e+00 7.0000000e-01 6.0000000e-01 7.0000000e-01 5.0000000e-01 5.0000000e-01 6.0000000e-01 6.0000000e-01 6.0000000e-01 3.0000000e-01 1.1000000e+00 5.0000000e-01 4.0000000e-01 4.0000000e-01 3.0000000e-01 1.0000000e+00 4.0000000e-01 2.0000000e+00 1.1000000e+00 1.9000000e+00 1.6000000e+00 1.8000000e+00 2.6000000e+00 1.2000000e+00 2.3000000e+00 1.8000000e+00 2.1000000e+00 1.1000000e+00 1.3000000e+00 1.5000000e+00 1.0000000e+00 1.1000000e+00 1.3000000e+00 1.5000000e+00 2.7000000e+00 2.9000000e+00 1.0000000e+00 1.7000000e+00 9.0000000e-01 2.7000000e+00 9.0000000e-01 1.7000000e+00 2.0000000e+00 8.0000000e-01 9.0000000e-01 1.6000000e+00 1.8000000e+00 2.1000000e+00 2.4000000e+00 1.6000000e+00 1.1000000e+00 1.6000000e+00 2.1000000e+00 1.6000000e+00 1.5000000e+00 8.0000000e-01 1.4000000e+00 1.6000000e+00 1.1000000e+00 1.1000000e+00 1.9000000e+00 1.7000000e+00 1.2000000e+00 1.0000000e+00 1.2000000e+00 1.4000000e+00 1.1000000e+00 3.0000000e-01 6.0000000e-01 5.0000000e-01 5.0000000e-01 5.0000000e-01 4.0000000e-01 1.4000000e+00 1.1000000e+00 1.2000000e+00 1.0000000e+00 3.0000000e-01 9.0000000e-01 9.0000000e-01 6.0000000e-01 5.0000000e-01 8.0000000e-01 9.0000000e-01 8.0000000e-01 5.0000000e-01 9.0000000e-01 1.6000000e+00 7.0000000e-01 7.0000000e-01 7.0000000e-01 6.0000000e-01 1.9000000e+00 8.0000000e-01 1.1000000e+00 5.0000000e-01 1.0000000e+00 7.0000000e-01 9.0000000e-01 1.7000000e+00 1.4000000e+00 1.4000000e+00 9.0000000e-01 1.2000000e+00 7.0000000e-01 4.0000000e-01 6.0000000e-01 6.0000000e-01 9.0000000e-01 8.0000000e-01 6.0000000e-01 1.8000000e+00 2.0000000e+00 3.0000000e-01 8.0000000e-01 7.0000000e-01 1.8000000e+00 3.0000000e-01 8.0000000e-01 1.1000000e+00 3.0000000e-01 5.0000000e-01 7.0000000e-01 9.0000000e-01 1.2000000e+00 1.6000000e+00 7.0000000e-01 3.0000000e-01 7.0000000e-01 1.4000000e+00 9.0000000e-01 6.0000000e-01 5.0000000e-01 6.0000000e-01 9.0000000e-01 8.0000000e-01 5.0000000e-01 1.0000000e+00 1.0000000e+00 8.0000000e-01 4.0000000e-01 5.0000000e-01 9.0000000e-01 5.0000000e-01 4.0000000e-01 5.0000000e-01 7.0000000e-01 6.0000000e-01 3.0000000e-01 1.2000000e+00 9.0000000e-01 1.0000000e+00 8.0000000e-01 4.0000000e-01 7.0000000e-01 6.0000000e-01 6.0000000e-01 5.0000000e-01 6.0000000e-01 7.0000000e-01 6.0000000e-01 2.0000000e-01 7.0000000e-01 1.4000000e+00 5.0000000e-01 5.0000000e-01 5.0000000e-01 4.0000000e-01 1.7000000e+00 6.0000000e-01 1.3000000e+00 7.0000000e-01 1.2000000e+00 9.0000000e-01 1.1000000e+00 1.9000000e+00 1.2000000e+00 1.6000000e+00 1.1000000e+00 1.4000000e+00 8.0000000e-01 7.0000000e-01 9.0000000e-01 8.0000000e-01 1.2000000e+00 1.1000000e+00 8.0000000e-01 2.0000000e+00 2.2000000e+00 6.0000000e-01 1.1000000e+00 8.0000000e-01 2.0000000e+00 6.0000000e-01 1.0000000e+00 1.3000000e+00 6.0000000e-01 6.0000000e-01 9.0000000e-01 1.1000000e+00 1.4000000e+00 1.8000000e+00 1.0000000e+00 4.0000000e-01 9.0000000e-01 1.6000000e+00 1.2000000e+00 8.0000000e-01 6.0000000e-01 9.0000000e-01 1.2000000e+00 1.1000000e+00 7.0000000e-01 1.2000000e+00 1.3000000e+00 1.1000000e+00 7.0000000e-01 8.0000000e-01 1.1000000e+00 6.0000000e-01 2.0000000e-01 5.0000000e-01 7.0000000e-01 4.0000000e-01 8.0000000e-01 9.0000000e-01 9.0000000e-01 6.0000000e-01 8.0000000e-01 1.0000000e+00 5.0000000e-01 4.0000000e-01 6.0000000e-01 8.0000000e-01 9.0000000e-01 9.0000000e-01 3.0000000e-01 6.0000000e-01 1.4000000e+00 8.0000000e-01 7.0000000e-01 7.0000000e-01 2.0000000e-01 1.3000000e+00 7.0000000e-01 1.7000000e+00 8.0000000e-01 1.6000000e+00 1.3000000e+00 1.5000000e+00 2.3000000e+00 1.5000000e+00 2.0000000e+00 1.5000000e+00 1.8000000e+00 8.0000000e-01 1.0000000e+00 1.2000000e+00 7.0000000e-01 1.1000000e+00 1.0000000e+00 1.2000000e+00 2.4000000e+00 2.6000000e+00 7.0000000e-01 1.4000000e+00 8.0000000e-01 2.4000000e+00 6.0000000e-01 1.4000000e+00 1.7000000e+00 5.0000000e-01 6.0000000e-01 1.3000000e+00 1.5000000e+00 1.8000000e+00 2.1000000e+00 1.3000000e+00 8.0000000e-01 1.3000000e+00 1.8000000e+00 1.3000000e+00 1.2000000e+00 5.0000000e-01 1.1000000e+00 1.3000000e+00 1.0000000e+00 8.0000000e-01 1.6000000e+00 1.4000000e+00 1.0000000e+00 7.0000000e-01 9.0000000e-01 1.1000000e+00 8.0000000e-01 4.0000000e-01 6.0000000e-01 6.0000000e-01 9.0000000e-01 1.1000000e+00 1.1000000e+00 8.0000000e-01 7.0000000e-01 1.2000000e+00 6.0000000e-01 3.0000000e-01 7.0000000e-01 1.0000000e+00 1.1000000e+00 1.1000000e+00 5.0000000e-01 8.0000000e-01 1.6000000e+00 1.0000000e+00 9.0000000e-01 9.0000000e-01 4.0000000e-01 1.5000000e+00 9.0000000e-01 1.6000000e+00 8.0000000e-01 1.5000000e+00 1.2000000e+00 1.4000000e+00 2.2000000e+00 1.7000000e+00 1.9000000e+00 1.4000000e+00 1.7000000e+00 7.0000000e-01 9.0000000e-01 1.1000000e+00 9.0000000e-01 1.0000000e+00 9.0000000e-01 1.1000000e+00 2.3000000e+00 2.5000000e+00 8.0000000e-01 1.3000000e+00 1.0000000e+00 2.3000000e+00 5.0000000e-01 1.3000000e+00 1.6000000e+00 4.0000000e-01 5.0000000e-01 1.2000000e+00 1.4000000e+00 1.7000000e+00 2.0000000e+00 1.2000000e+00 7.0000000e-01 1.2000000e+00 1.7000000e+00 1.2000000e+00 1.1000000e+00 6.0000000e-01 1.0000000e+00 1.2000000e+00 9.0000000e-01 8.0000000e-01 1.5000000e+00 1.3000000e+00 9.0000000e-01 6.0000000e-01 8.0000000e-01 1.0000000e+00 7.0000000e-01 3.0000000e-01 8.0000000e-01 1.3000000e+00 1.3000000e+00 1.3000000e+00 1.0000000e+00 8.0000000e-01 1.4000000e+00 8.0000000e-01 3.0000000e-01 5.0000000e-01 1.2000000e+00 1.3000000e+00 1.3000000e+00 7.0000000e-01 1.0000000e+00 1.8000000e+00 1.2000000e+00 1.1000000e+00 1.1000000e+00 6.0000000e-01 1.8000000e+00 1.1000000e+00 1.2000000e+00 1.0000000e+00 1.1000000e+00 8.0000000e-01 1.0000000e+00 1.8000000e+00 1.9000000e+00 1.5000000e+00 1.0000000e+00 1.3000000e+00 6.0000000e-01 5.0000000e-01 7.0000000e-01 1.1000000e+00 1.0000000e+00 9.0000000e-01 7.0000000e-01 1.9000000e+00 2.1000000e+00 8.0000000e-01 9.0000000e-01 1.2000000e+00 1.9000000e+00 5.0000000e-01 9.0000000e-01 1.2000000e+00 6.0000000e-01 7.0000000e-01 8.0000000e-01 1.0000000e+00 1.3000000e+00 1.6000000e+00 8.0000000e-01 5.0000000e-01 8.0000000e-01 1.3000000e+00 1.0000000e+00 7.0000000e-01 8.0000000e-01 7.0000000e-01 1.0000000e+00 9.0000000e-01 1.0000000e+00 1.1000000e+00 1.1000000e+00 9.0000000e-01 5.0000000e-01 6.0000000e-01 9.0000000e-01 9.0000000e-01 7.0000000e-01 1.5000000e+00 1.2000000e+00 1.3000000e+00 1.1000000e+00 7.0000000e-01 1.3000000e+00 7.0000000e-01 3.0000000e-01 7.0000000e-01 1.1000000e+00 1.2000000e+00 1.2000000e+00 6.0000000e-01 1.0000000e+00 1.7000000e+00 1.1000000e+00 1.0000000e+00 1.0000000e+00 7.0000000e-01 2.0000000e+00 1.0000000e+00 1.0000000e+00 9.0000000e-01 9.0000000e-01 6.0000000e-01 8.0000000e-01 1.6000000e+00 1.8000000e+00 1.3000000e+00 8.0000000e-01 1.1000000e+00 3.0000000e-01 3.0000000e-01 5.0000000e-01 1.0000000e+00 9.0000000e-01 6.0000000e-01 5.0000000e-01 1.7000000e+00 1.9000000e+00 8.0000000e-01 7.0000000e-01 1.1000000e+00 1.7000000e+00 4.0000000e-01 7.0000000e-01 1.0000000e+00 5.0000000e-01 6.0000000e-01 6.0000000e-01 8.0000000e-01 1.1000000e+00 1.4000000e+00 6.0000000e-01 4.0000000e-01 6.0000000e-01 1.1000000e+00 7.0000000e-01 5.0000000e-01 7.0000000e-01 4.0000000e-01 7.0000000e-01 6.0000000e-01 9.0000000e-01 9.0000000e-01 8.0000000e-01 6.0000000e-01 5.0000000e-01 3.0000000e-01 6.0000000e-01 8.0000000e-01 1.0000000e+00 7.0000000e-01 8.0000000e-01 6.0000000e-01 6.0000000e-01 6.0000000e-01 5.0000000e-01 7.0000000e-01 6.0000000e-01 4.0000000e-01 5.0000000e-01 5.0000000e-01 1.0000000e-01 5.0000000e-01 1.2000000e+00 4.0000000e-01 3.0000000e-01 3.0000000e-01 2.0000000e-01 1.5000000e+00 4.0000000e-01 1.5000000e+00 6.0000000e-01 1.4000000e+00 1.1000000e+00 1.3000000e+00 2.1000000e+00 1.1000000e+00 1.8000000e+00 1.3000000e+00 1.6000000e+00 6.0000000e-01 8.0000000e-01 1.0000000e+00 5.0000000e-01 9.0000000e-01 8.0000000e-01 1.0000000e+00 2.2000000e+00 2.4000000e+00 7.0000000e-01 1.2000000e+00 5.0000000e-01 2.2000000e+00 4.0000000e-01 1.2000000e+00 1.5000000e+00 3.0000000e-01 4.0000000e-01 1.1000000e+00 1.3000000e+00 1.6000000e+00 1.9000000e+00 1.1000000e+00 6.0000000e-01 1.1000000e+00 1.7000000e+00 1.1000000e+00 1.0000000e+00 3.0000000e-01 9.0000000e-01 1.1000000e+00 9.0000000e-01 6.0000000e-01 1.4000000e+00 1.2000000e+00 8.0000000e-01 5.0000000e-01 7.0000000e-01 9.0000000e-01 6.0000000e-01 3.0000000e-01 2.0000000e-01 4.0000000e-01 1.6000000e+00 1.0000000e+00 1.0000000e+00 1.2000000e+00 9.0000000e-01 6.0000000e-01 5.0000000e-01 9.0000000e-01 1.1000000e+00 5.0000000e-01 7.0000000e-01 7.0000000e-01 7.0000000e-01 7.0000000e-01 8.0000000e-01 6.0000000e-01 6.0000000e-01 2.5000000e+00 1.6000000e+00 2.4000000e+00 2.1000000e+00 2.3000000e+00 3.1000000e+00 1.0000000e+00 2.8000000e+00 2.3000000e+00 2.6000000e+00 1.6000000e+00 1.8000000e+00 2.0000000e+00 1.5000000e+00 1.6000000e+00 1.8000000e+00 2.0000000e+00 3.2000000e+00 3.4000000e+00 1.5000000e+00 2.2000000e+00 1.4000000e+00 3.2000000e+00 1.4000000e+00 2.2000000e+00 2.5000000e+00 1.3000000e+00 1.4000000e+00 2.1000000e+00 2.3000000e+00 2.6000000e+00 2.9000000e+00 2.1000000e+00 1.6000000e+00 2.1000000e+00 2.6000000e+00 2.1000000e+00 2.0000000e+00 1.3000000e+00 1.9000000e+00 2.1000000e+00 1.6000000e+00 1.6000000e+00 2.4000000e+00 2.2000000e+00 1.7000000e+00 1.5000000e+00 1.7000000e+00 1.9000000e+00 1.6000000e+00 1.0000000e-01 3.0000000e-01 1.3000000e+00 7.0000000e-01 1.0000000e+00 1.2000000e+00 8.0000000e-01 6.0000000e-01 2.0000000e-01 6.0000000e-01 8.0000000e-01 3.0000000e-01 5.0000000e-01 4.0000000e-01 6.0000000e-01 5.0000000e-01 7.0000000e-01 8.0000000e-01 4.0000000e-01 2.2000000e+00 1.3000000e+00 2.1000000e+00 1.8000000e+00 2.0000000e+00 2.8000000e+00 7.0000000e-01 2.5000000e+00 2.0000000e+00 2.3000000e+00 1.3000000e+00 1.5000000e+00 1.7000000e+00 1.2000000e+00 1.3000000e+00 1.5000000e+00 1.7000000e+00 2.9000000e+00 3.1000000e+00 1.2000000e+00 1.9000000e+00 1.1000000e+00 2.9000000e+00 1.1000000e+00 1.9000000e+00 2.2000000e+00 1.0000000e+00 1.1000000e+00 1.8000000e+00 2.0000000e+00 2.3000000e+00 2.6000000e+00 1.8000000e+00 1.3000000e+00 1.8000000e+00 2.3000000e+00 1.8000000e+00 1.7000000e+00 1.0000000e+00 1.6000000e+00 1.8000000e+00 1.4000000e+00 1.3000000e+00 2.1000000e+00 1.9000000e+00 1.4000000e+00 1.2000000e+00 1.4000000e+00 1.6000000e+00 1.3000000e+00 3.0000000e-01 1.4000000e+00 8.0000000e-01 1.0000000e+00 1.2000000e+00 8.0000000e-01 6.0000000e-01 3.0000000e-01 7.0000000e-01 9.0000000e-01 3.0000000e-01 5.0000000e-01 5.0000000e-01 6.0000000e-01 5.0000000e-01 7.0000000e-01 7.0000000e-01 4.0000000e-01 2.3000000e+00 1.4000000e+00 2.2000000e+00 1.9000000e+00 2.1000000e+00 2.9000000e+00 8.0000000e-01 2.6000000e+00 2.1000000e+00 2.4000000e+00 1.4000000e+00 1.6000000e+00 1.8000000e+00 1.3000000e+00 1.4000000e+00 1.6000000e+00 1.8000000e+00 3.0000000e+00 3.2000000e+00 1.3000000e+00 2.0000000e+00 1.2000000e+00 3.0000000e+00 1.2000000e+00 2.0000000e+00 2.3000000e+00 1.1000000e+00 1.2000000e+00 1.9000000e+00 2.1000000e+00 2.4000000e+00 2.7000000e+00 1.9000000e+00 1.4000000e+00 1.9000000e+00 2.4000000e+00 1.9000000e+00 1.8000000e+00 1.1000000e+00 1.7000000e+00 1.9000000e+00 1.4000000e+00 1.4000000e+00 2.2000000e+00 2.0000000e+00 1.5000000e+00 1.3000000e+00 1.5000000e+00 1.7000000e+00 1.4000000e+00 1.2000000e+00 6.0000000e-01 7.0000000e-01 9.0000000e-01 5.0000000e-01 3.0000000e-01 3.0000000e-01 5.0000000e-01 7.0000000e-01 1.0000000e-01 8.0000000e-01 3.0000000e-01 3.0000000e-01 3.0000000e-01 4.0000000e-01 9.0000000e-01 2.0000000e-01 2.1000000e+00 1.2000000e+00 2.0000000e+00 1.7000000e+00 1.9000000e+00 2.7000000e+00 9.0000000e-01 2.4000000e+00 1.9000000e+00 2.2000000e+00 1.2000000e+00 1.4000000e+00 1.6000000e+00 1.1000000e+00 1.2000000e+00 1.4000000e+00 1.6000000e+00 2.8000000e+00 3.0000000e+00 1.1000000e+00 1.8000000e+00 1.0000000e+00 2.8000000e+00 1.0000000e+00 1.8000000e+00 2.1000000e+00 9.0000000e-01 1.0000000e+00 1.7000000e+00 1.9000000e+00 2.2000000e+00 2.5000000e+00 1.7000000e+00 1.2000000e+00 1.7000000e+00 2.2000000e+00 1.7000000e+00 1.6000000e+00 9.0000000e-01 1.5000000e+00 1.7000000e+00 1.2000000e+00 1.2000000e+00 2.0000000e+00 1.8000000e+00 1.3000000e+00 1.1000000e+00 1.3000000e+00 1.5000000e+00 1.2000000e+00 6.0000000e-01 7.0000000e-01 7.0000000e-01 7.0000000e-01 1.0000000e+00 1.1000000e+00 7.0000000e-01 5.0000000e-01 1.1000000e+00 1.8000000e+00 9.0000000e-01 9.0000000e-01 9.0000000e-01 8.0000000e-01 2.1000000e+00 1.0000000e+00 9.0000000e-01 3.0000000e-01 1.1000000e+00 5.0000000e-01 7.0000000e-01 1.6000000e+00 1.1000000e+00 1.3000000e+00 7.0000000e-01 1.2000000e+00 5.0000000e-01 4.0000000e-01 8.0000000e-01 4.0000000e-01 8.0000000e-01 7.0000000e-01 5.0000000e-01 1.7000000e+00 1.8000000e+00 5.0000000e-01 9.0000000e-01 4.0000000e-01 1.7000000e+00 3.0000000e-01 7.0000000e-01 1.2000000e+00 3.0000000e-01 3.0000000e-01 5.0000000e-01 1.2000000e+00 1.4000000e+00 1.9000000e+00 6.0000000e-01 3.0000000e-01 5.0000000e-01 1.7000000e+00 8.0000000e-01 4.0000000e-01 3.0000000e-01 9.0000000e-01 8.0000000e-01 9.0000000e-01 3.0000000e-01 8.0000000e-01 9.0000000e-01 7.0000000e-01 3.0000000e-01 5.0000000e-01 7.0000000e-01 3.0000000e-01 6.0000000e-01 1.3000000e+00 9.0000000e-01 4.0000000e-01 5.0000000e-01 4.0000000e-01 7.0000000e-01 5.0000000e-01 1.2000000e+00 3.0000000e-01 3.0000000e-01 3.0000000e-01 8.0000000e-01 1.5000000e+00 4.0000000e-01 1.5000000e+00 6.0000000e-01 1.7000000e+00 1.1000000e+00 1.3000000e+00 2.2000000e+00 5.0000000e-01 1.9000000e+00 1.3000000e+00 1.8000000e+00 1.1000000e+00 1.0000000e+00 1.4000000e+00 5.0000000e-01 9.0000000e-01 1.0000000e+00 1.1000000e+00 2.3000000e+00 2.4000000e+00 8.0000000e-01 1.5000000e+00 5.0000000e-01 2.3000000e+00 9.0000000e-01 1.3000000e+00 1.8000000e+00 8.0000000e-01 7.0000000e-01 1.1000000e+00 1.8000000e+00 2.0000000e+00 2.5000000e+00 1.1000000e+00 9.0000000e-01 1.1000000e+00 2.3000000e+00 1.1000000e+00 1.0000000e+00 6.0000000e-01 1.5000000e+00 1.3000000e+00 1.5000000e+00 6.0000000e-01 1.4000000e+00 1.3000000e+00 1.3000000e+00 9.0000000e-01 1.1000000e+00 9.0000000e-01 6.0000000e-01 7.0000000e-01 1.1000000e+00 4.0000000e-01 9.0000000e-01 8.0000000e-01 4.0000000e-01 8.0000000e-01 1.2000000e+00 7.0000000e-01 4.0000000e-01 5.0000000e-01 5.0000000e-01 1.5000000e+00 6.0000000e-01 1.5000000e+00 7.0000000e-01 1.4000000e+00 1.1000000e+00 1.3000000e+00 2.1000000e+00 1.1000000e+00 1.8000000e+00 1.3000000e+00 1.6000000e+00 6.0000000e-01 8.0000000e-01 1.0000000e+00 9.0000000e-01 8.0000000e-01 8.0000000e-01 1.0000000e+00 2.2000000e+00 2.4000000e+00 1.2000000e+00 1.2000000e+00 6.0000000e-01 2.2000000e+00 7.0000000e-01 1.2000000e+00 1.5000000e+00 6.0000000e-01 4.0000000e-01 1.1000000e+00 1.3000000e+00 1.6000000e+00 1.9000000e+00 1.1000000e+00 6.0000000e-01 1.1000000e+00 1.7000000e+00 1.1000000e+00 1.0000000e+00 4.0000000e-01 9.0000000e-01 1.1000000e+00 9.0000000e-01 7.0000000e-01 1.4000000e+00 1.2000000e+00 7.0000000e-01 9.0000000e-01 7.0000000e-01 9.0000000e-01 6.0000000e-01 8.0000000e-01 1.1000000e+00 1.2000000e+00 1.2000000e+00 6.0000000e-01 9.0000000e-01 1.7000000e+00 1.1000000e+00 1.0000000e+00 1.0000000e+00 5.0000000e-01 1.7000000e+00 1.0000000e+00 1.3000000e+00 9.0000000e-01 1.2000000e+00 9.0000000e-01 1.1000000e+00 1.9000000e+00 1.8000000e+00 1.6000000e+00 1.1000000e+00 1.4000000e+00 5.0000000e-01 6.0000000e-01 8.0000000e-01 1.0000000e+00 9.0000000e-01 8.0000000e-01 8.0000000e-01 2.0000000e+00 2.2000000e+00 9.0000000e-01 1.0000000e+00 1.1000000e+00 2.0000000e+00 4.0000000e-01 1.0000000e+00 1.3000000e+00 5.0000000e-01 6.0000000e-01 9.0000000e-01 1.1000000e+00 1.4000000e+00 1.7000000e+00 9.0000000e-01 4.0000000e-01 9.0000000e-01 1.4000000e+00 9.0000000e-01 8.0000000e-01 7.0000000e-01 7.0000000e-01 9.0000000e-01 8.0000000e-01 9.0000000e-01 1.2000000e+00 1.0000000e+00 8.0000000e-01 6.0000000e-01 5.0000000e-01 8.0000000e-01 8.0000000e-01 7.0000000e-01 8.0000000e-01 8.0000000e-01 7.0000000e-01 5.0000000e-01 1.3000000e+00 7.0000000e-01 7.0000000e-01 6.0000000e-01 6.0000000e-01 1.4000000e+00 6.0000000e-01 1.6000000e+00 7.0000000e-01 1.5000000e+00 1.2000000e+00 1.4000000e+00 2.2000000e+00 1.4000000e+00 1.9000000e+00 1.4000000e+00 1.7000000e+00 9.0000000e-01 9.0000000e-01 1.1000000e+00 7.0000000e-01 1.1000000e+00 1.0000000e+00 1.1000000e+00 2.3000000e+00 2.5000000e+00 6.0000000e-01 1.3000000e+00 7.0000000e-01 2.3000000e+00 5.0000000e-01 1.3000000e+00 1.6000000e+00 5.0000000e-01 7.0000000e-01 1.2000000e+00 1.4000000e+00 1.7000000e+00 2.0000000e+00 1.2000000e+00 7.0000000e-01 1.2000000e+00 1.7000000e+00 1.2000000e+00 1.1000000e+00 7.0000000e-01 1.0000000e+00 1.2000000e+00 1.0000000e+00 7.0000000e-01 1.5000000e+00 1.3000000e+00 1.0000000e+00 6.0000000e-01 8.0000000e-01 1.1000000e+00 7.0000000e-01 5.0000000e-01 4.0000000e-01 5.0000000e-01 4.0000000e-01 8.0000000e-01 3.0000000e-01 1.0000000e-01 1.0000000e-01 6.0000000e-01 1.1000000e+00 2.0000000e-01 1.9000000e+00 1.0000000e+00 1.8000000e+00 1.5000000e+00 1.7000000e+00 2.5000000e+00 7.0000000e-01 2.2000000e+00 1.7000000e+00 2.0000000e+00 1.0000000e+00 1.2000000e+00 1.4000000e+00 9.0000000e-01 1.1000000e+00 1.2000000e+00 1.4000000e+00 2.6000000e+00 2.8000000e+00 9.0000000e-01 1.6000000e+00 8.0000000e-01 2.6000000e+00 8.0000000e-01 1.6000000e+00 1.9000000e+00 7.0000000e-01 8.0000000e-01 1.5000000e+00 1.7000000e+00 2.0000000e+00 2.3000000e+00 1.5000000e+00 1.0000000e+00 1.5000000e+00 2.1000000e+00 1.5000000e+00 1.4000000e+00 7.0000000e-01 1.3000000e+00 1.5000000e+00 1.3000000e+00 1.0000000e+00 1.8000000e+00 1.6000000e+00 1.1000000e+00 9.0000000e-01 1.1000000e+00 1.3000000e+00 1.0000000e+00 4.0000000e-01 6.0000000e-01 3.0000000e-01 7.0000000e-01 2.0000000e-01 5.0000000e-01 4.0000000e-01 7.0000000e-01 1.0000000e+00 3.0000000e-01 2.0000000e+00 1.1000000e+00 1.9000000e+00 1.6000000e+00 1.8000000e+00 2.6000000e+00 6.0000000e-01 2.3000000e+00 1.8000000e+00 2.1000000e+00 1.1000000e+00 1.3000000e+00 1.5000000e+00 1.0000000e+00 1.1000000e+00 1.3000000e+00 1.5000000e+00 2.7000000e+00 2.9000000e+00 1.0000000e+00 1.7000000e+00 9.0000000e-01 2.7000000e+00 9.0000000e-01 1.7000000e+00 2.0000000e+00 8.0000000e-01 9.0000000e-01 1.6000000e+00 1.8000000e+00 2.1000000e+00 2.4000000e+00 1.6000000e+00 1.1000000e+00 1.6000000e+00 2.2000000e+00 1.6000000e+00 1.5000000e+00 8.0000000e-01 1.4000000e+00 1.6000000e+00 1.4000000e+00 1.1000000e+00 1.9000000e+00 1.7000000e+00 1.2000000e+00 1.0000000e+00 1.2000000e+00 1.4000000e+00 1.1000000e+00 6.0000000e-01 4.0000000e-01 1.1000000e+00 2.0000000e-01 4.0000000e-01 3.0000000e-01 7.0000000e-01 1.4000000e+00 3.0000000e-01 1.6000000e+00 7.0000000e-01 1.6000000e+00 1.2000000e+00 1.4000000e+00 2.2000000e+00 6.0000000e-01 1.9000000e+00 1.4000000e+00 1.7000000e+00 1.0000000e+00 9.0000000e-01 1.3000000e+00 8.0000000e-01 1.2000000e+00 1.1000000e+00 1.1000000e+00 2.3000000e+00 2.5000000e+00 6.0000000e-01 1.4000000e+00 8.0000000e-01 2.3000000e+00 8.0000000e-01 1.3000000e+00 1.7000000e+00 7.0000000e-01 6.0000000e-01 1.2000000e+00 1.7000000e+00 1.9000000e+00 2.4000000e+00 1.2000000e+00 8.0000000e-01 1.2000000e+00 2.2000000e+00 1.2000000e+00 1.1000000e+00 6.0000000e-01 1.4000000e+00 1.2000000e+00 1.4000000e+00 7.0000000e-01 1.5000000e+00 1.3000000e+00 1.2000000e+00 8.0000000e-01 1.0000000e+00 1.1000000e+00 7.0000000e-01 6.0000000e-01 1.3000000e+00 5.0000000e-01 4.0000000e-01 4.0000000e-01 3.0000000e-01 1.6000000e+00 5.0000000e-01 1.4000000e+00 5.0000000e-01 1.3000000e+00 1.0000000e+00 1.2000000e+00 2.0000000e+00 1.2000000e+00 1.7000000e+00 1.2000000e+00 1.5000000e+00 6.0000000e-01 7.0000000e-01 9.0000000e-01 6.0000000e-01 1.0000000e+00 9.0000000e-01 9.0000000e-01 2.1000000e+00 2.3000000e+00 8.0000000e-01 1.1000000e+00 6.0000000e-01 2.1000000e+00 4.0000000e-01 1.1000000e+00 1.4000000e+00 4.0000000e-01 4.0000000e-01 1.0000000e+00 1.2000000e+00 1.5000000e+00 1.8000000e+00 1.0000000e+00 5.0000000e-01 1.0000000e+00 1.6000000e+00 1.0000000e+00 9.0000000e-01 4.0000000e-01 8.0000000e-01 1.0000000e+00 9.0000000e-01 5.0000000e-01 1.3000000e+00 1.1000000e+00 9.0000000e-01 5.0000000e-01 6.0000000e-01 9.0000000e-01 5.0000000e-01 8.0000000e-01 2.0000000e-01 4.0000000e-01 3.0000000e-01 4.0000000e-01 1.0000000e+00 2.0000000e-01 2.0000000e+00 1.1000000e+00 1.9000000e+00 1.6000000e+00 1.8000000e+00 2.6000000e+00 9.0000000e-01 2.3000000e+00 1.8000000e+00 2.1000000e+00 1.1000000e+00 1.3000000e+00 1.5000000e+00 1.0000000e+00 1.2000000e+00 1.3000000e+00 1.5000000e+00 2.7000000e+00 2.9000000e+00 1.0000000e+00 1.7000000e+00 9.0000000e-01 2.7000000e+00 9.0000000e-01 1.7000000e+00 2.0000000e+00 8.0000000e-01 9.0000000e-01 1.6000000e+00 1.8000000e+00 2.1000000e+00 2.4000000e+00 1.6000000e+00 1.1000000e+00 1.6000000e+00 2.1000000e+00 1.6000000e+00 1.5000000e+00 8.0000000e-01 1.4000000e+00 1.6000000e+00 1.1000000e+00 1.1000000e+00 1.9000000e+00 1.7000000e+00 1.2000000e+00 1.0000000e+00 1.2000000e+00 1.4000000e+00 1.1000000e+00 9.0000000e-01 9.0000000e-01 9.0000000e-01 1.2000000e+00 3.0000000e-01 8.0000000e-01 2.7000000e+00 1.8000000e+00 2.6000000e+00 2.3000000e+00 2.5000000e+00 3.3000000e+00 1.2000000e+00 3.0000000e+00 2.5000000e+00 2.8000000e+00 1.8000000e+00 2.0000000e+00 2.2000000e+00 1.7000000e+00 1.8000000e+00 2.0000000e+00 2.2000000e+00 3.4000000e+00 3.6000000e+00 1.7000000e+00 2.4000000e+00 1.6000000e+00 3.4000000e+00 1.6000000e+00 2.4000000e+00 2.7000000e+00 1.5000000e+00 1.6000000e+00 2.3000000e+00 2.5000000e+00 2.8000000e+00 3.1000000e+00 2.3000000e+00 1.8000000e+00 2.3000000e+00 2.8000000e+00 2.3000000e+00 2.2000000e+00 1.5000000e+00 2.1000000e+00 2.3000000e+00 1.9000000e+00 1.8000000e+00 2.6000000e+00 2.4000000e+00 1.9000000e+00 1.7000000e+00 1.9000000e+00 2.1000000e+00 1.8000000e+00 3.0000000e-01 2.0000000e-01 6.0000000e-01 1.2000000e+00 1.0000000e-01 1.8000000e+00 9.0000000e-01 1.7000000e+00 1.4000000e+00 1.6000000e+00 2.4000000e+00 7.0000000e-01 2.1000000e+00 1.6000000e+00 1.9000000e+00 9.0000000e-01 1.1000000e+00 1.3000000e+00 8.0000000e-01 1.1000000e+00 1.1000000e+00 1.3000000e+00 2.5000000e+00 2.7000000e+00 8.0000000e-01 1.5000000e+00 7.0000000e-01 2.5000000e+00 7.0000000e-01 1.5000000e+00 1.8000000e+00 6.0000000e-01 7.0000000e-01 1.4000000e+00 1.6000000e+00 1.9000000e+00 2.3000000e+00 1.4000000e+00 9.0000000e-01 1.4000000e+00 2.1000000e+00 1.4000000e+00 1.3000000e+00 6.0000000e-01 1.3000000e+00 1.4000000e+00 1.3000000e+00 9.0000000e-01 1.7000000e+00 1.5000000e+00 1.1000000e+00 8.0000000e-01 1.0000000e+00 1.2000000e+00 9.0000000e-01 1.0000000e-01 5.0000000e-01 1.2000000e+00 2.0000000e-01 1.8000000e+00 9.0000000e-01 1.7000000e+00 1.4000000e+00 1.6000000e+00 2.4000000e+00 8.0000000e-01 2.1000000e+00 1.6000000e+00 1.9000000e+00 9.0000000e-01 1.1000000e+00 1.3000000e+00 8.0000000e-01 1.2000000e+00 1.1000000e+00 1.3000000e+00 2.5000000e+00 2.7000000e+00 8.0000000e-01 1.5000000e+00 8.0000000e-01 2.5000000e+00 7.0000000e-01 1.5000000e+00 1.8000000e+00 6.0000000e-01 7.0000000e-01 1.4000000e+00 1.6000000e+00 1.9000000e+00 2.2000000e+00 1.4000000e+00 9.0000000e-01 1.4000000e+00 2.0000000e+00 1.4000000e+00 1.3000000e+00 6.0000000e-01 1.2000000e+00 1.4000000e+00 1.2000000e+00 9.0000000e-01 1.7000000e+00 1.5000000e+00 1.1000000e+00 8.0000000e-01 1.0000000e+00 1.2000000e+00 9.0000000e-01 5.0000000e-01 1.2000000e+00 1.0000000e-01 1.8000000e+00 9.0000000e-01 1.7000000e+00 1.4000000e+00 1.6000000e+00 2.4000000e+00 8.0000000e-01 2.1000000e+00 1.6000000e+00 1.9000000e+00 9.0000000e-01 1.1000000e+00 1.3000000e+00 8.0000000e-01 1.1000000e+00 1.1000000e+00 1.3000000e+00 2.5000000e+00 2.7000000e+00 8.0000000e-01 1.5000000e+00 7.0000000e-01 2.5000000e+00 7.0000000e-01 1.5000000e+00 1.8000000e+00 6.0000000e-01 7.0000000e-01 1.4000000e+00 1.6000000e+00 1.9000000e+00 2.2000000e+00 1.4000000e+00 9.0000000e-01 1.4000000e+00 2.0000000e+00 1.4000000e+00 1.3000000e+00 6.0000000e-01 1.2000000e+00 1.4000000e+00 1.2000000e+00 9.0000000e-01 1.7000000e+00 1.5000000e+00 1.0000000e+00 8.0000000e-01 1.0000000e+00 1.2000000e+00 9.0000000e-01 1.3000000e+00 5.0000000e-01 1.7000000e+00 8.0000000e-01 1.6000000e+00 1.3000000e+00 1.5000000e+00 2.3000000e+00 1.3000000e+00 2.0000000e+00 1.5000000e+00 1.8000000e+00 8.0000000e-01 1.0000000e+00 1.2000000e+00 7.0000000e-01 1.1000000e+00 1.0000000e+00 1.2000000e+00 2.4000000e+00 2.6000000e+00 7.0000000e-01 1.4000000e+00 7.0000000e-01 2.4000000e+00 6.0000000e-01 1.4000000e+00 1.7000000e+00 5.0000000e-01 6.0000000e-01 1.3000000e+00 1.5000000e+00 1.8000000e+00 2.1000000e+00 1.3000000e+00 8.0000000e-01 1.3000000e+00 1.8000000e+00 1.3000000e+00 1.2000000e+00 5.0000000e-01 1.1000000e+00 1.3000000e+00 1.0000000e+00 8.0000000e-01 1.6000000e+00 1.4000000e+00 1.0000000e+00 7.0000000e-01 9.0000000e-01 1.1000000e+00 8.0000000e-01 1.1000000e+00 3.0000000e+00 2.1000000e+00 2.9000000e+00 2.6000000e+00 2.8000000e+00 3.6000000e+00 1.5000000e+00 3.3000000e+00 2.8000000e+00 3.1000000e+00 2.1000000e+00 2.3000000e+00 2.5000000e+00 2.0000000e+00 2.1000000e+00 2.3000000e+00 2.5000000e+00 3.7000000e+00 3.9000000e+00 2.0000000e+00 2.7000000e+00 1.9000000e+00 3.7000000e+00 1.9000000e+00 2.7000000e+00 3.0000000e+00 1.8000000e+00 1.9000000e+00 2.6000000e+00 2.8000000e+00 3.1000000e+00 3.4000000e+00 2.6000000e+00 2.1000000e+00 2.6000000e+00 3.1000000e+00 2.6000000e+00 2.5000000e+00 1.8000000e+00 2.4000000e+00 2.6000000e+00 2.1000000e+00 2.1000000e+00 2.9000000e+00 2.7000000e+00 2.2000000e+00 2.0000000e+00 2.2000000e+00 2.4000000e+00 2.1000000e+00 1.9000000e+00 1.0000000e+00 1.8000000e+00 1.5000000e+00 1.7000000e+00 2.5000000e+00 8.0000000e-01 2.2000000e+00 1.7000000e+00 2.0000000e+00 1.0000000e+00 1.2000000e+00 1.4000000e+00 9.0000000e-01 1.1000000e+00 1.2000000e+00 1.4000000e+00 2.6000000e+00 2.8000000e+00 9.0000000e-01 1.6000000e+00 8.0000000e-01 2.6000000e+00 8.0000000e-01 1.6000000e+00 1.9000000e+00 7.0000000e-01 8.0000000e-01 1.5000000e+00 1.7000000e+00 2.0000000e+00 2.3000000e+00 1.5000000e+00 1.0000000e+00 1.5000000e+00 2.0000000e+00 1.5000000e+00 1.4000000e+00 7.0000000e-01 1.3000000e+00 1.5000000e+00 1.2000000e+00 1.0000000e+00 1.8000000e+00 1.6000000e+00 1.1000000e+00 9.0000000e-01 1.1000000e+00 1.3000000e+00 1.0000000e+00 9.0000000e-01 8.0000000e-01 7.0000000e-01 3.0000000e-01 1.3000000e+00 1.5000000e+00 1.0000000e+00 8.0000000e-01 9.0000000e-01 9.0000000e-01 7.0000000e-01 5.0000000e-01 1.0000000e+00 9.0000000e-01 7.0000000e-01 7.0000000e-01 1.4000000e+00 1.4000000e+00 1.1000000e+00 6.0000000e-01 1.1000000e+00 1.4000000e+00 1.1000000e+00 4.0000000e-01 9.0000000e-01 1.2000000e+00 1.1000000e+00 5.0000000e-01 9.0000000e-01 1.1000000e+00 1.6000000e+00 5.0000000e-01 1.0000000e+00 1.1000000e+00 1.4000000e+00 4.0000000e-01 7.0000000e-01 1.2000000e+00 6.0000000e-01 4.0000000e-01 9.0000000e-01 9.0000000e-01 5.0000000e-01 4.0000000e-01 8.0000000e-01 1.0000000e+00 8.0000000e-01 6.0000000e-01 9.0000000e-01 1.3000000e+00 5.0000000e-01 7.0000000e-01 1.8000000e+00 9.0000000e-01 1.5000000e+00 9.0000000e-01 1.4000000e+00 7.0000000e-01 6.0000000e-01 1.0000000e+00 2.0000000e-01 5.0000000e-01 6.0000000e-01 7.0000000e-01 1.9000000e+00 1.9000000e+00 5.0000000e-01 1.1000000e+00 2.0000000e-01 1.9000000e+00 5.0000000e-01 9.0000000e-01 1.4000000e+00 4.0000000e-01 3.0000000e-01 6.0000000e-01 1.4000000e+00 1.6000000e+00 2.1000000e+00 6.0000000e-01 5.0000000e-01 5.0000000e-01 1.9000000e+00 7.0000000e-01 6.0000000e-01 3.0000000e-01 1.1000000e+00 9.0000000e-01 1.1000000e+00 0.0000000e+00 1.0000000e+00 9.0000000e-01 9.0000000e-01 5.0000000e-01 7.0000000e-01 7.0000000e-01 3.0000000e-01 8.0000000e-01 6.0000000e-01 7.0000000e-01 2.2000000e+00 4.0000000e-01 5.0000000e-01 6.0000000e-01 8.0000000e-01 7.0000000e-01 4.0000000e-01 1.4000000e+00 1.3000000e+00 7.0000000e-01 6.0000000e-01 8.0000000e-01 1.0000000e+00 1.1000000e+00 2.0000000e-01 1.5000000e+00 8.0000000e-01 1.0000000e+00 4.0000000e-01 3.0000000e-01 1.1000000e+00 1.0000000e+00 7.0000000e-01 5.0000000e-01 3.0000000e-01 8.0000000e-01 7.0000000e-01 8.0000000e-01 1.0000000e+00 6.0000000e-01 8.0000000e-01 7.0000000e-01 1.1000000e+00 5.0000000e-01 4.0000000e-01 8.0000000e-01 1.3000000e+00 3.0000000e-01 4.0000000e-01 7.0000000e-01 9.0000000e-01 7.0000000e-01 9.0000000e-01 1.2000000e+00 4.0000000e-01 1.3000000e+00 1.4000000e+00 1.0000000e+00 4.0000000e-01 9.0000000e-01 5.0000000e-01 3.0000000e-01 5.0000000e-01 6.0000000e-01 6.0000000e-01 5.0000000e-01 2.0000000e-01 1.4000000e+00 1.4000000e+00 7.0000000e-01 6.0000000e-01 7.0000000e-01 1.4000000e+00 7.0000000e-01 4.0000000e-01 9.0000000e-01 8.0000000e-01 7.0000000e-01 3.0000000e-01 9.0000000e-01 1.1000000e+00 1.6000000e+00 4.0000000e-01 5.0000000e-01 4.0000000e-01 1.4000000e+00 6.0000000e-01 2.0000000e-01 8.0000000e-01 6.0000000e-01 6.0000000e-01 6.0000000e-01 5.0000000e-01 5.0000000e-01 7.0000000e-01 5.0000000e-01 6.0000000e-01 4.0000000e-01 5.0000000e-01 5.0000000e-01 1.1000000e+00 1.6000000e+00 8.0000000e-01 5.0000000e-01 7.0000000e-01 7.0000000e-01 5.0000000e-01 3.0000000e-01 8.0000000e-01 7.0000000e-01 5.0000000e-01 4.0000000e-01 1.2000000e+00 1.2000000e+00 8.0000000e-01 4.0000000e-01 9.0000000e-01 1.2000000e+00 9.0000000e-01 3.0000000e-01 7.0000000e-01 1.0000000e+00 9.0000000e-01 2.0000000e-01 7.0000000e-01 9.0000000e-01 1.4000000e+00 2.0000000e-01 7.0000000e-01 8.0000000e-01 1.2000000e+00 4.0000000e-01 4.0000000e-01 1.0000000e+00 4.0000000e-01 2.0000000e-01 7.0000000e-01 7.0000000e-01 3.0000000e-01 3.0000000e-01 6.0000000e-01 8.0000000e-01 6.0000000e-01 4.0000000e-01 7.0000000e-01 2.7000000e+00 3.0000000e-01 9.0000000e-01 6.0000000e-01 1.5000000e+00 1.3000000e+00 1.1000000e+00 1.9000000e+00 1.8000000e+00 1.3000000e+00 1.1000000e+00 8.0000000e-01 4.0000000e-01 1.6000000e+00 9.0000000e-01 2.0000000e+00 2.0000000e-01 1.7000000e+00 9.0000000e-01 6.0000000e-01 1.8000000e+00 1.7000000e+00 1.2000000e+00 8.0000000e-01 5.0000000e-01 8.0000000e-01 1.2000000e+00 1.5000000e+00 1.5000000e+00 5.0000000e-01 1.3000000e+00 1.2000000e+00 1.8000000e+00 1.2000000e+00 1.0000000e+00 1.5000000e+00 1.8000000e+00 8.0000000e-01 9.0000000e-01 1.4000000e+00 1.6000000e+00 1.4000000e+00 1.4000000e+00 1.7000000e+00 2.4000000e+00 1.8000000e+00 2.3000000e+00 1.6000000e+00 1.5000000e+00 1.9000000e+00 8.0000000e-01 9.0000000e-01 1.5000000e+00 1.6000000e+00 2.8000000e+00 2.8000000e+00 1.1000000e+00 2.0000000e+00 7.0000000e-01 2.8000000e+00 1.4000000e+00 1.8000000e+00 2.3000000e+00 1.3000000e+00 1.2000000e+00 1.5000000e+00 2.3000000e+00 2.5000000e+00 3.0000000e+00 1.5000000e+00 1.4000000e+00 1.2000000e+00 2.8000000e+00 1.4000000e+00 1.5000000e+00 1.1000000e+00 2.0000000e+00 1.8000000e+00 2.0000000e+00 9.0000000e-01 1.9000000e+00 1.8000000e+00 1.8000000e+00 1.4000000e+00 1.6000000e+00 1.3000000e+00 1.0000000e+00 6.0000000e-01 7.0000000e-01 1.2000000e+00 1.0000000e+00 8.0000000e-01 1.6000000e+00 1.5000000e+00 1.0000000e+00 8.0000000e-01 9.0000000e-01 6.0000000e-01 1.3000000e+00 6.0000000e-01 1.7000000e+00 4.0000000e-01 1.4000000e+00 6.0000000e-01 3.0000000e-01 1.5000000e+00 1.4000000e+00 9.0000000e-01 5.0000000e-01 2.0000000e-01 9.0000000e-01 9.0000000e-01 1.2000000e+00 1.2000000e+00 5.0000000e-01 1.0000000e+00 9.0000000e-01 1.5000000e+00 9.0000000e-01 7.0000000e-01 1.2000000e+00 1.5000000e+00 5.0000000e-01 7.0000000e-01 1.1000000e+00 1.3000000e+00 1.1000000e+00 1.1000000e+00 1.4000000e+00 1.1000000e+00 7.0000000e-01 5.0000000e-01 5.0000000e-01 1.0000000e+00 9.0000000e-01 7.0000000e-01 5.0000000e-01 1.3000000e+00 1.1000000e+00 8.0000000e-01 7.0000000e-01 1.1000000e+00 1.0000000e+00 9.0000000e-01 8.0000000e-01 7.0000000e-01 1.0000000e+00 9.0000000e-01 3.0000000e-01 5.0000000e-01 7.0000000e-01 1.3000000e+00 4.0000000e-01 7.0000000e-01 6.0000000e-01 1.0000000e+00 9.0000000e-01 6.0000000e-01 1.0000000e+00 6.0000000e-01 6.0000000e-01 7.0000000e-01 9.0000000e-01 7.0000000e-01 8.0000000e-01 6.0000000e-01 8.0000000e-01 6.0000000e-01 9.0000000e-01 8.0000000e-01 1.0000000e+00 9.0000000e-01 6.0000000e-01 1.5000000e+00 1.4000000e+00 8.0000000e-01 7.0000000e-01 6.0000000e-01 1.0000000e+00 1.4000000e+00 4.0000000e-01 1.6000000e+00 8.0000000e-01 1.2000000e+00 5.0000000e-01 7.0000000e-01 1.3000000e+00 1.2000000e+00 8.0000000e-01 9.0000000e-01 8.0000000e-01 7.0000000e-01 8.0000000e-01 1.0000000e+00 1.1000000e+00 6.0000000e-01 9.0000000e-01 8.0000000e-01 1.3000000e+00 7.0000000e-01 5.0000000e-01 1.0000000e+00 1.4000000e+00 4.0000000e-01 5.0000000e-01 9.0000000e-01 1.1000000e+00 9.0000000e-01 1.0000000e+00 1.3000000e+00 5.0000000e-01 4.0000000e-01 8.0000000e-01 7.0000000e-01 3.0000000e-01 4.0000000e-01 1.6000000e+00 1.8000000e+00 1.0000000e+00 6.0000000e-01 9.0000000e-01 1.6000000e+00 5.0000000e-01 6.0000000e-01 9.0000000e-01 4.0000000e-01 4.0000000e-01 5.0000000e-01 7.0000000e-01 1.0000000e+00 1.4000000e+00 5.0000000e-01 5.0000000e-01 6.0000000e-01 1.2000000e+00 5.0000000e-01 4.0000000e-01 5.0000000e-01 4.0000000e-01 5.0000000e-01 4.0000000e-01 7.0000000e-01 8.0000000e-01 6.0000000e-01 3.0000000e-01 7.0000000e-01 2.0000000e-01 3.0000000e-01 6.0000000e-01 4.0000000e-01 7.0000000e-01 6.0000000e-01 5.0000000e-01 3.0000000e-01 1.4000000e+00 1.6000000e+00 5.0000000e-01 5.0000000e-01 8.0000000e-01 1.4000000e+00 4.0000000e-01 6.0000000e-01 8.0000000e-01 5.0000000e-01 4.0000000e-01 3.0000000e-01 8.0000000e-01 1.0000000e+00 1.5000000e+00 3.0000000e-01 4.0000000e-01 5.0000000e-01 1.3000000e+00 7.0000000e-01 4.0000000e-01 5.0000000e-01 5.0000000e-01 5.0000000e-01 5.0000000e-01 6.0000000e-01 6.0000000e-01 6.0000000e-01 4.0000000e-01 3.0000000e-01 3.0000000e-01 7.0000000e-01 5.0000000e-01 1.1000000e+00 1.0000000e+00 4.0000000e-01 3.0000000e-01 1.2000000e+00 1.4000000e+00 8.0000000e-01 2.0000000e-01 1.2000000e+00 1.2000000e+00 6.0000000e-01 3.0000000e-01 5.0000000e-01 7.0000000e-01 7.0000000e-01 4.0000000e-01 5.0000000e-01 6.0000000e-01 1.1000000e+00 4.0000000e-01 6.0000000e-01 7.0000000e-01 9.0000000e-01 5.0000000e-01 4.0000000e-01 8.0000000e-01 1.0000000e-01 3.0000000e-01 4.0000000e-01 1.0000000e+00 4.0000000e-01 4.0000000e-01 3.0000000e-01 5.0000000e-01 3.0000000e-01 6.0000000e-01 9.0000000e-01 4.0000000e-01 7.0000000e-01 8.0000000e-01 2.0000000e+00 2.0000000e+00 5.0000000e-01 1.2000000e+00 3.0000000e-01 2.0000000e+00 6.0000000e-01 1.0000000e+00 1.5000000e+00 5.0000000e-01 5.0000000e-01 7.0000000e-01 1.5000000e+00 1.7000000e+00 2.2000000e+00 7.0000000e-01 6.0000000e-01 6.0000000e-01 2.0000000e+00 9.0000000e-01 7.0000000e-01 5.0000000e-01 1.2000000e+00 1.0000000e+00 1.2000000e+00 2.0000000e-01 1.1000000e+00 1.0000000e+00 1.0000000e+00 6.0000000e-01 8.0000000e-01 9.0000000e-01 5.0000000e-01 6.0000000e-01 7.0000000e-01 1.9000000e+00 1.9000000e+00 9.0000000e-01 1.1000000e+00 4.0000000e-01 1.9000000e+00 6.0000000e-01 9.0000000e-01 1.4000000e+00 6.0000000e-01 6.0000000e-01 6.0000000e-01 1.4000000e+00 1.6000000e+00 2.1000000e+00 6.0000000e-01 9.0000000e-01 1.0000000e+00 1.9000000e+00 6.0000000e-01 6.0000000e-01 6.0000000e-01 1.1000000e+00 9.0000000e-01 1.1000000e+00 5.0000000e-01 1.0000000e+00 9.0000000e-01 9.0000000e-01 5.0000000e-01 7.0000000e-01 6.0000000e-01 6.0000000e-01 5.0000000e-01 1.4000000e+00 1.6000000e+00 1.0000000e+00 5.0000000e-01 8.0000000e-01 1.4000000e+00 5.0000000e-01 4.0000000e-01 8.0000000e-01 5.0000000e-01 5.0000000e-01 4.0000000e-01 8.0000000e-01 1.0000000e+00 1.5000000e+00 4.0000000e-01 8.0000000e-01 9.0000000e-01 1.3000000e+00 3.0000000e-01 5.0000000e-01 5.0000000e-01 5.0000000e-01 3.0000000e-01 5.0000000e-01 6.0000000e-01 6.0000000e-01 4.0000000e-01 3.0000000e-01 7.0000000e-01 3.0000000e-01 2.0000000e-01 5.0000000e-01 1.2000000e+00 1.4000000e+00 8.0000000e-01 5.0000000e-01 9.0000000e-01 1.2000000e+00 6.0000000e-01 3.0000000e-01 7.0000000e-01 7.0000000e-01 6.0000000e-01 3.0000000e-01 7.0000000e-01 9.0000000e-01 1.4000000e+00 4.0000000e-01 4.0000000e-01 4.0000000e-01 1.2000000e+00 6.0000000e-01 1.0000000e-01 7.0000000e-01 4.0000000e-01 6.0000000e-01 5.0000000e-01 7.0000000e-01 5.0000000e-01 7.0000000e-01 5.0000000e-01 5.0000000e-01 3.0000000e-01 5.0000000e-01 6.0000000e-01 1.2000000e+00 1.7000000e+00 1.0000000e+00 2.1000000e+00 1.0000000e+00 1.8000000e+00 1.0000000e+00 7.0000000e-01 1.9000000e+00 1.8000000e+00 1.3000000e+00 9.0000000e-01 1.0000000e+00 3.0000000e-01 1.3000000e+00 1.6000000e+00 1.6000000e+00 8.0000000e-01 1.4000000e+00 1.3000000e+00 1.9000000e+00 1.3000000e+00 1.1000000e+00 1.6000000e+00 1.9000000e+00 9.0000000e-01 1.0000000e+00 1.5000000e+00 1.7000000e+00 1.5000000e+00 1.5000000e+00 1.8000000e+00 1.9000000e+00 1.2000000e+00 2.1000000e+00 3.0000000e-01 2.0000000e+00 1.2000000e+00 9.0000000e-01 2.1000000e+00 2.0000000e+00 1.3000000e+00 1.1000000e+00 8.0000000e-01 1.2000000e+00 1.3000000e+00 1.8000000e+00 1.6000000e+00 8.0000000e-01 1.4000000e+00 1.4000000e+00 2.1000000e+00 1.5000000e+00 1.3000000e+00 1.8000000e+00 1.9000000e+00 1.0000000e+00 1.2000000e+00 1.7000000e+00 1.9000000e+00 1.7000000e+00 1.5000000e+00 1.8000000e+00 1.0000000e+00 6.0000000e-01 1.7000000e+00 5.0000000e-01 1.1000000e+00 1.2000000e+00 6.0000000e-01 8.0000000e-01 6.0000000e-01 1.2000000e+00 1.4000000e+00 1.9000000e+00 7.0000000e-01 6.0000000e-01 6.0000000e-01 1.7000000e+00 1.2000000e+00 9.0000000e-01 8.0000000e-01 9.0000000e-01 9.0000000e-01 9.0000000e-01 5.0000000e-01 1.0000000e+00 1.1000000e+00 8.0000000e-01 4.0000000e-01 8.0000000e-01 1.2000000e+00 8.0000000e-01 1.3000000e+00 1.0000000e+00 8.0000000e-01 2.0000000e-01 5.0000000e-01 9.0000000e-01 8.0000000e-01 5.0000000e-01 7.0000000e-01 5.0000000e-01 1.0000000e+00 5.0000000e-01 8.0000000e-01 9.0000000e-01 8.0000000e-01 6.0000000e-01 5.0000000e-01 9.0000000e-01 3.0000000e-01 2.0000000e-01 6.0000000e-01 1.1000000e+00 2.0000000e-01 2.0000000e-01 5.0000000e-01 7.0000000e-01 5.0000000e-01 7.0000000e-01 1.0000000e+00 2.1000000e+00 7.0000000e-01 1.1000000e+00 1.6000000e+00 6.0000000e-01 5.0000000e-01 8.0000000e-01 1.6000000e+00 1.8000000e+00 2.3000000e+00 8.0000000e-01 7.0000000e-01 7.0000000e-01 2.1000000e+00 7.0000000e-01 8.0000000e-01 4.0000000e-01 1.3000000e+00 1.1000000e+00 1.3000000e+00 2.0000000e-01 1.2000000e+00 1.1000000e+00 1.1000000e+00 7.0000000e-01 9.0000000e-01 6.0000000e-01 3.0000000e-01 1.8000000e+00 1.0000000e+00 7.0000000e-01 1.9000000e+00 1.8000000e+00 1.3000000e+00 9.0000000e-01 6.0000000e-01 1.0000000e+00 1.3000000e+00 1.6000000e+00 1.6000000e+00 6.0000000e-01 1.4000000e+00 1.3000000e+00 1.9000000e+00 1.3000000e+00 1.1000000e+00 1.6000000e+00 1.9000000e+00 9.0000000e-01 1.0000000e+00 1.5000000e+00 1.7000000e+00 1.5000000e+00 1.5000000e+00 1.8000000e+00 8.0000000e-01 1.1000000e+00 1.0000000e-01 3.0000000e-01 7.0000000e-01 9.0000000e-01 1.2000000e+00 1.6000000e+00 7.0000000e-01 3.0000000e-01 7.0000000e-01 1.4000000e+00 7.0000000e-01 6.0000000e-01 3.0000000e-01 6.0000000e-01 7.0000000e-01 6.0000000e-01 5.0000000e-01 1.0000000e+00 8.0000000e-01 5.0000000e-01 2.0000000e-01 3.0000000e-01 7.0000000e-01 4.0000000e-01 5.0000000e-01 9.0000000e-01 8.0000000e-01 5.0000000e-01 5.0000000e-01 7.0000000e-01 1.2000000e+00 5.0000000e-01 6.0000000e-01 7.0000000e-01 1.0000000e+00 4.0000000e-01 3.0000000e-01 9.0000000e-01 3.0000000e-01 3.0000000e-01 6.0000000e-01 9.0000000e-01 2.0000000e-01 4.0000000e-01 5.0000000e-01 8.0000000e-01 5.0000000e-01 5.0000000e-01 8.0000000e-01 1.2000000e+00 1.1000000e+00 8.0000000e-01 2.0000000e-01 4.0000000e-01 7.0000000e-01 8.0000000e-01 9.0000000e-01 1.1000000e+00 5.0000000e-01 9.0000000e-01 8.0000000e-01 1.2000000e+00 6.0000000e-01 6.0000000e-01 9.0000000e-01 1.4000000e+00 5.0000000e-01 7.0000000e-01 8.0000000e-01 1.0000000e+00 8.0000000e-01 1.0000000e+00 1.3000000e+00 2.0000000e-01 8.0000000e-01 1.0000000e+00 1.3000000e+00 1.7000000e+00 8.0000000e-01 3.0000000e-01 8.0000000e-01 1.5000000e+00 8.0000000e-01 7.0000000e-01 2.0000000e-01 7.0000000e-01 8.0000000e-01 7.0000000e-01 4.0000000e-01 1.1000000e+00 9.0000000e-01 5.0000000e-01 3.0000000e-01 4.0000000e-01 6.0000000e-01 3.0000000e-01 7.0000000e-01 1.1000000e+00 1.3000000e+00 1.8000000e+00 7.0000000e-01 3.0000000e-01 7.0000000e-01 1.6000000e+00 7.0000000e-01 6.0000000e-01 1.0000000e-01 8.0000000e-01 7.0000000e-01 8.0000000e-01 3.0000000e-01 1.0000000e+00 8.0000000e-01 6.0000000e-01 5.0000000e-01 4.0000000e-01 5.0000000e-01 2.0000000e-01 8.0000000e-01 1.0000000e+00 1.5000000e+00 1.0000000e-01 6.0000000e-01 7.0000000e-01 1.3000000e+00 6.0000000e-01 3.0000000e-01 8.0000000e-01 5.0000000e-01 3.0000000e-01 5.0000000e-01 6.0000000e-01 4.0000000e-01 5.0000000e-01 4.0000000e-01 6.0000000e-01 4.0000000e-01 6.0000000e-01 5.0000000e-01 3.0000000e-01 8.0000000e-01 8.0000000e-01 9.0000000e-01 1.1000000e+00 7.0000000e-01 9.0000000e-01 8.0000000e-01 1.2000000e+00 5.0000000e-01 8.0000000e-01 7.0000000e-01 1.4000000e+00 7.0000000e-01 9.0000000e-01 7.0000000e-01 9.0000000e-01 7.0000000e-01 1.0000000e+00 1.3000000e+00 1.0000000e+00 1.0000000e+00 1.1000000e+00 1.3000000e+00 4.0000000e-01 1.1000000e+00 1.0000000e+00 1.4000000e+00 7.0000000e-01 7.0000000e-01 1.0000000e+00 1.6000000e+00 6.0000000e-01 7.0000000e-01 9.0000000e-01 1.1000000e+00 9.0000000e-01 1.2000000e+00 1.5000000e+00 1.5000000e+00 1.6000000e+00 1.8000000e+00 8.0000000e-01 1.6000000e+00 1.5000000e+00 1.9000000e+00 1.0000000e+00 1.2000000e+00 1.3000000e+00 2.1000000e+00 1.1000000e+00 1.2000000e+00 1.2000000e+00 1.6000000e+00 1.4000000e+00 1.7000000e+00 2.0000000e+00 7.0000000e-01 8.0000000e-01 1.3000000e+00 6.0000000e-01 4.0000000e-01 8.0000000e-01 5.0000000e-01 3.0000000e-01 5.0000000e-01 6.0000000e-01 4.0000000e-01 5.0000000e-01 4.0000000e-01 6.0000000e-01 4.0000000e-01 6.0000000e-01 5.0000000e-01 5.0000000e-01 1.4000000e+00 9.0000000e-01 4.0000000e-01 3.0000000e-01 6.0000000e-01 9.0000000e-01 8.0000000e-01 5.0000000e-01 8.0000000e-01 1.0000000e+00 8.0000000e-01 4.0000000e-01 5.0000000e-01 8.0000000e-01 4.0000000e-01 1.6000000e+00 1.0000000e+00 5.0000000e-01 8.0000000e-01 8.0000000e-01 1.0000000e+00 9.0000000e-01 5.0000000e-01 9.0000000e-01 1.1000000e+00 9.0000000e-01 6.0000000e-01 6.0000000e-01 9.0000000e-01 5.0000000e-01 1.4000000e+00 1.3000000e+00 1.7000000e+00 8.0000000e-01 1.0000000e+00 1.0000000e+00 1.9000000e+00 9.0000000e-01 1.0000000e+00 1.0000000e+00 1.4000000e+00 1.2000000e+00 1.5000000e+00 1.8000000e+00 6.0000000e-01 8.0000000e-01 6.0000000e-01 4.0000000e-01 6.0000000e-01 7.0000000e-01 5.0000000e-01 4.0000000e-01 4.0000000e-01 9.0000000e-01 4.0000000e-01 2.0000000e-01 6.0000000e-01 7.0000000e-01 5.0000000e-01 6.0000000e-01 5.0000000e-01 6.0000000e-01 5.0000000e-01 7.0000000e-01 5.0000000e-01 6.0000000e-01 3.0000000e-01 5.0000000e-01 5.0000000e-01 9.0000000e-01 8.0000000e-01 9.0000000e-01 3.0000000e-01 1.1000000e+00 9.0000000e-01 7.0000000e-01 5.0000000e-01 5.0000000e-01 6.0000000e-01 3.0000000e-01 3.0000000e-01 3.0000000e-01 1.1000000e+00 5.0000000e-01 4.0000000e-01 2.0000000e-01 6.0000000e-01 4.0000000e-01 7.0000000e-01 1.0000000e+00 5.0000000e-01 9.0000000e-01 3.0000000e-01 2.0000000e-01 4.0000000e-01 6.0000000e-01 4.0000000e-01 5.0000000e-01 8.0000000e-01 1.1000000e+00 8.0000000e-01 6.0000000e-01 2.0000000e-01 6.0000000e-01 4.0000000e-01 7.0000000e-01 1.0000000e+00 1.0000000e+00 9.0000000e-01 9.0000000e-01 5.0000000e-01 7.0000000e-01 7.0000000e-01 3.0000000e-01 2.0000000e-01 7.0000000e-01 9.0000000e-01 7.0000000e-01 6.0000000e-01 9.0000000e-01 5.0000000e-01 8.0000000e-01 5.0000000e-01 5.0000000e-01 8.0000000e-01 5.0000000e-01 3.0000000e-01 5.0000000e-01 8.0000000e-01 5.0000000e-01 9.0000000e-01 5.0000000e-01 4.0000000e-01 6.0000000e-01 5.0000000e-01 diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-chebyshev-ml.txt b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-chebyshev-ml.txt new file mode 100644 index 0000000000000000000000000000000000000000..786486295935319c03a60a349f03328c127935b9 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-chebyshev-ml.txt @@ -0,0 +1 @@ + 8.9084734e-01 9.3573853e-01 9.3507398e-01 9.6040691e-01 9.2918157e-01 9.6617342e-01 9.0430930e-01 9.5753424e-01 8.7106898e-01 9.2169905e-01 9.7401159e-01 8.9013416e-01 9.3956689e-01 9.0041896e-01 9.2588355e-01 9.3849417e-01 8.9713468e-01 9.1481804e-01 9.7500539e-01 9.0012586e-01 9.0962559e-01 8.5860091e-01 8.6981095e-01 8.9995771e-01 8.8070172e-01 9.1456657e-01 8.6711474e-01 9.2593917e-01 8.7560376e-01 8.5193121e-01 9.0898542e-01 8.7765302e-01 8.6555584e-01 8.6093485e-01 9.0447028e-01 8.7614405e-01 9.4803522e-01 8.4998062e-01 7.8398996e-01 8.9538612e-01 8.3902291e-01 9.9039470e-01 9.5480519e-01 8.9152195e-01 9.1623329e-01 7.9094921e-01 9.1777100e-01 9.8972335e-01 9.0429093e-01 8.7646362e-01 9.2136649e-01 9.7178177e-01 8.9610979e-01 9.4710327e-01 9.3612450e-01 9.0241499e-01 7.7992538e-01 8.7262126e-01 9.3325183e-01 8.5796531e-01 9.4267977e-01 6.7224167e-01 7.9568368e-01 8.6411267e-01 9.3311642e-01 9.0160114e-01 9.0698887e-01 8.5833256e-01 9.6902830e-01 9.5072298e-01 8.6808495e-01 9.7879599e-01 8.8060729e-01 8.2818573e-01 8.4366706e-01 8.4506700e-01 9.4532981e-01 9.1792306e-01 7.8917825e-01 9.8337805e-01 8.1751613e-01 9.3037855e-01 9.1618832e-01 8.6568874e-01 8.9751397e-01 8.7923710e-01 8.6814329e-01 9.0330164e-01 8.2426213e-01 9.4644643e-01 8.8431293e-01 8.8497426e-01 9.0633818e-01 9.5537161e-01 8.2167575e-01 8.7771053e-01 9.0681167e-01 8.7626143e-01 8.7463464e-01 9.8033940e-01 9.2920881e-01 9.5108549e-01 9.1287466e-01 8.0052218e-01 9.2409517e-01 8.8252650e-01 8.7873923e-01 9.2989402e-01 9.1985043e-01 9.6172646e-01 8.8223856e-01 9.4477822e-01 8.8310948e-01 9.4461306e-01 9.1875210e-01 9.1233363e-01 9.2124013e-01 9.5460897e-01 8.4640982e-01 9.0882657e-01 9.8169468e-01 9.7828355e-01 8.4150533e-01 8.6888923e-01 9.7138825e-01 8.7988144e-01 9.6720910e-01 8.9450147e-01 9.5331584e-01 8.8871809e-01 8.9736685e-01 8.6258146e-01 9.1331565e-01 9.0968870e-01 9.4833654e-01 9.0536967e-01 9.5099871e-01 8.0251958e-01 9.2526150e-01 9.8971957e-01 9.0340947e-01 9.4955892e-01 9.6838162e-01 8.7534901e-01 9.1178797e-01 9.2649154e-01 9.5260993e-01 9.3178143e-01 9.4943000e-01 8.7816171e-01 9.6506542e-01 8.3422958e-01 9.3443585e-01 9.3220084e-01 8.5706573e-01 8.4666325e-01 9.0474744e-01 9.1080644e-01 9.2406899e-01 8.7901768e-01 9.3265263e-01 9.5992829e-01 9.5696271e-01 9.1932272e-01 8.0937044e-01 9.0904917e-01 8.9516756e-01 9.4797906e-01 8.4159421e-01 9.6773901e-01 9.7099825e-01 9.6941820e-01 9.8174088e-01 9.7569951e-01 9.3655362e-01 8.4130333e-01 9.5994549e-01 8.4235414e-01 9.1429418e-01 9.3418117e-01 8.4600977e-01 8.8166496e-01 8.7594776e-01 8.8571112e-01 9.6308174e-01 9.5315927e-01 8.6997519e-01 8.9383032e-01 9.4686804e-01 9.4399596e-01 diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-cityblock-ml-iris.txt b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-cityblock-ml-iris.txt new file mode 100644 index 0000000000000000000000000000000000000000..6722928a4a4491c74d3fef5205276b532b15dcbf --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-cityblock-ml-iris.txt @@ -0,0 +1 @@ + 7.0000000e-01 8.0000000e-01 1.0000000e+00 2.0000000e-01 1.2000000e+00 7.0000000e-01 3.0000000e-01 1.3000000e+00 8.0000000e-01 6.0000000e-01 6.0000000e-01 9.0000000e-01 1.7000000e+00 1.4000000e+00 1.8000000e+00 1.0000000e+00 1.0000000e-01 1.3000000e+00 5.0000000e-01 7.0000000e-01 5.0000000e-01 1.0000000e+00 8.0000000e-01 9.0000000e-01 8.0000000e-01 6.0000000e-01 2.0000000e-01 2.0000000e-01 9.0000000e-01 9.0000000e-01 7.0000000e-01 9.0000000e-01 1.1000000e+00 8.0000000e-01 6.0000000e-01 5.0000000e-01 8.0000000e-01 1.3000000e+00 2.0000000e-01 3.0000000e-01 2.0000000e+00 1.1000000e+00 7.0000000e-01 1.0000000e+00 9.0000000e-01 5.0000000e-01 8.0000000e-01 5.0000000e-01 3.0000000e-01 6.7000000e+00 6.0000000e+00 7.0000000e+00 5.3000000e+00 6.6000000e+00 5.5000000e+00 6.1000000e+00 4.0000000e+00 6.4000000e+00 4.6000000e+00 4.5000000e+00 5.4000000e+00 5.6000000e+00 6.1000000e+00 4.4000000e+00 6.2000000e+00 5.4000000e+00 5.0000000e+00 6.8000000e+00 4.9000000e+00 6.1000000e+00 5.4000000e+00 7.0000000e+00 6.0000000e+00 5.9000000e+00 6.2000000e+00 7.0000000e+00 7.2000000e+00 5.9000000e+00 4.4000000e+00 4.8000000e+00 4.6000000e+00 5.0000000e+00 6.8000000e+00 5.2000000e+00 5.5000000e+00 6.6000000e+00 6.5000000e+00 4.8000000e+00 5.1000000e+00 5.3000000e+00 5.9000000e+00 5.2000000e+00 4.0000000e+00 5.2000000e+00 4.9000000e+00 5.1000000e+00 5.7000000e+00 3.5000000e+00 5.1000000e+00 8.3000000e+00 6.9000000e+00 8.9000000e+00 7.6000000e+00 8.3000000e+00 1.0100000e+01 5.8000000e+00 9.3000000e+00 8.6000000e+00 9.2000000e+00 7.2000000e+00 7.7000000e+00 8.2000000e+00 7.0000000e+00 7.3000000e+00 7.6000000e+00 7.6000000e+00 1.0200000e+01 1.1100000e+01 7.1000000e+00 8.5000000e+00 6.5000000e+00 1.0400000e+01 7.1000000e+00 8.0000000e+00 8.6000000e+00 6.8000000e+00 6.6000000e+00 8.1000000e+00 8.4000000e+00 9.4000000e+00 9.9000000e+00 8.2000000e+00 6.9000000e+00 7.3000000e+00 9.9000000e+00 7.7000000e+00 7.4000000e+00 6.4000000e+00 8.1000000e+00 8.4000000e+00 8.0000000e+00 6.9000000e+00 8.6000000e+00 8.4000000e+00 8.0000000e+00 7.5000000e+00 7.5000000e+00 7.3000000e+00 6.6000000e+00 5.0000000e-01 5.0000000e-01 7.0000000e-01 1.9000000e+00 8.0000000e-01 6.0000000e-01 6.0000000e-01 3.0000000e-01 1.3000000e+00 7.0000000e-01 2.0000000e-01 1.0000000e+00 2.1000000e+00 2.5000000e+00 1.7000000e+00 8.0000000e-01 2.0000000e+00 1.2000000e+00 1.2000000e+00 1.2000000e+00 1.3000000e+00 1.1000000e+00 1.0000000e+00 3.0000000e-01 9.0000000e-01 9.0000000e-01 7.0000000e-01 6.0000000e-01 4.0000000e-01 1.2000000e+00 1.6000000e+00 1.8000000e+00 3.0000000e-01 5.0000000e-01 1.2000000e+00 3.0000000e-01 6.0000000e-01 7.0000000e-01 8.0000000e-01 1.3000000e+00 8.0000000e-01 1.2000000e+00 1.7000000e+00 2.0000000e-01 1.2000000e+00 5.0000000e-01 1.2000000e+00 4.0000000e-01 6.8000000e+00 6.1000000e+00 6.9000000e+00 5.0000000e+00 6.3000000e+00 5.2000000e+00 6.4000000e+00 3.3000000e+00 6.1000000e+00 4.3000000e+00 4.0000000e+00 5.1000000e+00 5.3000000e+00 5.8000000e+00 4.1000000e+00 6.1000000e+00 5.1000000e+00 4.7000000e+00 6.5000000e+00 4.6000000e+00 6.2000000e+00 5.1000000e+00 6.7000000e+00 5.7000000e+00 5.6000000e+00 5.9000000e+00 6.7000000e+00 6.9000000e+00 5.6000000e+00 4.1000000e+00 4.5000000e+00 4.3000000e+00 4.7000000e+00 6.5000000e+00 4.9000000e+00 6.0000000e+00 6.5000000e+00 6.2000000e+00 4.5000000e+00 4.8000000e+00 5.0000000e+00 5.6000000e+00 4.9000000e+00 3.5000000e+00 4.9000000e+00 4.6000000e+00 4.8000000e+00 5.4000000e+00 3.2000000e+00 4.8000000e+00 8.6000000e+00 6.6000000e+00 8.6000000e+00 7.3000000e+00 8.0000000e+00 9.8000000e+00 5.1000000e+00 9.0000000e+00 8.3000000e+00 9.9000000e+00 7.3000000e+00 7.4000000e+00 7.9000000e+00 6.7000000e+00 7.0000000e+00 7.7000000e+00 7.3000000e+00 1.0900000e+01 1.0800000e+01 6.8000000e+00 8.6000000e+00 6.2000000e+00 1.0100000e+01 6.8000000e+00 8.3000000e+00 8.7000000e+00 6.5000000e+00 6.3000000e+00 7.8000000e+00 8.1000000e+00 9.1000000e+00 1.0600000e+01 7.9000000e+00 6.6000000e+00 7.0000000e+00 9.6000000e+00 8.2000000e+00 7.3000000e+00 6.1000000e+00 8.0000000e+00 8.3000000e+00 7.9000000e+00 6.6000000e+00 8.7000000e+00 8.7000000e+00 7.7000000e+00 7.2000000e+00 7.2000000e+00 7.8000000e+00 6.3000000e+00 4.0000000e-01 8.0000000e-01 2.0000000e+00 5.0000000e-01 7.0000000e-01 7.0000000e-01 6.0000000e-01 1.4000000e+00 6.0000000e-01 5.0000000e-01 9.0000000e-01 2.0000000e+00 2.6000000e+00 1.6000000e+00 9.0000000e-01 2.1000000e+00 1.3000000e+00 1.3000000e+00 1.3000000e+00 8.0000000e-01 1.2000000e+00 9.0000000e-01 8.0000000e-01 1.0000000e+00 1.0000000e+00 8.0000000e-01 3.0000000e-01 5.0000000e-01 1.3000000e+00 1.7000000e+00 1.9000000e+00 6.0000000e-01 4.0000000e-01 1.1000000e+00 6.0000000e-01 5.0000000e-01 8.0000000e-01 7.0000000e-01 1.2000000e+00 3.0000000e-01 1.3000000e+00 1.8000000e+00 5.0000000e-01 1.3000000e+00 2.0000000e-01 1.3000000e+00 5.0000000e-01 6.9000000e+00 6.2000000e+00 7.2000000e+00 5.5000000e+00 6.8000000e+00 5.7000000e+00 6.5000000e+00 3.8000000e+00 6.6000000e+00 4.8000000e+00 4.5000000e+00 5.6000000e+00 5.8000000e+00 6.3000000e+00 4.6000000e+00 6.4000000e+00 5.6000000e+00 5.2000000e+00 7.0000000e+00 5.1000000e+00 6.3000000e+00 5.6000000e+00 7.2000000e+00 6.2000000e+00 6.1000000e+00 6.4000000e+00 7.2000000e+00 7.4000000e+00 6.1000000e+00 4.6000000e+00 5.0000000e+00 4.8000000e+00 5.2000000e+00 7.0000000e+00 5.4000000e+00 6.1000000e+00 6.8000000e+00 6.7000000e+00 5.0000000e+00 5.3000000e+00 5.5000000e+00 6.1000000e+00 5.4000000e+00 4.0000000e+00 5.4000000e+00 5.1000000e+00 5.3000000e+00 5.9000000e+00 3.7000000e+00 5.3000000e+00 8.7000000e+00 7.1000000e+00 9.1000000e+00 7.8000000e+00 8.5000000e+00 1.0300000e+01 5.6000000e+00 9.5000000e+00 8.8000000e+00 1.0000000e+01 7.4000000e+00 7.9000000e+00 8.4000000e+00 7.2000000e+00 7.5000000e+00 7.8000000e+00 7.8000000e+00 1.1000000e+01 1.1300000e+01 7.3000000e+00 8.7000000e+00 6.7000000e+00 1.0600000e+01 7.3000000e+00 8.4000000e+00 8.8000000e+00 7.0000000e+00 6.8000000e+00 8.3000000e+00 8.6000000e+00 9.6000000e+00 1.0700000e+01 8.4000000e+00 7.1000000e+00 7.5000000e+00 1.0100000e+01 8.3000000e+00 7.6000000e+00 6.6000000e+00 8.3000000e+00 8.6000000e+00 8.2000000e+00 7.1000000e+00 8.8000000e+00 8.8000000e+00 8.2000000e+00 7.7000000e+00 7.7000000e+00 7.9000000e+00 6.8000000e+00 1.0000000e+00 2.0000000e+00 5.0000000e-01 7.0000000e-01 5.0000000e-01 4.0000000e-01 1.4000000e+00 6.0000000e-01 5.0000000e-01 9.0000000e-01 2.4000000e+00 2.6000000e+00 2.0000000e+00 1.1000000e+00 2.1000000e+00 1.3000000e+00 1.3000000e+00 1.3000000e+00 1.0000000e+00 1.2000000e+00 9.0000000e-01 6.0000000e-01 1.0000000e+00 1.0000000e+00 1.0000000e+00 3.0000000e-01 3.0000000e-01 1.3000000e+00 1.7000000e+00 2.1000000e+00 4.0000000e-01 8.0000000e-01 1.5000000e+00 4.0000000e-01 5.0000000e-01 8.0000000e-01 1.1000000e+00 1.2000000e+00 5.0000000e-01 1.3000000e+00 1.8000000e+00 5.0000000e-01 1.3000000e+00 2.0000000e-01 1.3000000e+00 7.0000000e-01 6.9000000e+00 6.2000000e+00 7.0000000e+00 5.3000000e+00 6.6000000e+00 5.5000000e+00 6.5000000e+00 3.6000000e+00 6.4000000e+00 4.6000000e+00 4.3000000e+00 5.4000000e+00 5.6000000e+00 6.1000000e+00 4.4000000e+00 6.2000000e+00 5.4000000e+00 5.0000000e+00 6.8000000e+00 4.9000000e+00 6.3000000e+00 5.4000000e+00 7.0000000e+00 6.0000000e+00 5.9000000e+00 6.2000000e+00 7.0000000e+00 7.2000000e+00 5.9000000e+00 4.4000000e+00 4.8000000e+00 4.6000000e+00 5.0000000e+00 6.8000000e+00 5.2000000e+00 6.1000000e+00 6.6000000e+00 6.5000000e+00 4.8000000e+00 5.1000000e+00 5.3000000e+00 5.9000000e+00 5.2000000e+00 3.8000000e+00 5.2000000e+00 4.9000000e+00 5.1000000e+00 5.7000000e+00 3.5000000e+00 5.1000000e+00 8.7000000e+00 6.9000000e+00 8.9000000e+00 7.6000000e+00 8.3000000e+00 1.0100000e+01 5.4000000e+00 9.3000000e+00 8.6000000e+00 1.0000000e+01 7.4000000e+00 7.7000000e+00 8.2000000e+00 7.0000000e+00 7.3000000e+00 7.8000000e+00 7.6000000e+00 1.1000000e+01 1.1100000e+01 7.1000000e+00 8.7000000e+00 6.5000000e+00 1.0400000e+01 7.1000000e+00 8.4000000e+00 8.8000000e+00 6.8000000e+00 6.6000000e+00 8.1000000e+00 8.4000000e+00 9.4000000e+00 1.0700000e+01 8.2000000e+00 6.9000000e+00 7.3000000e+00 9.9000000e+00 8.3000000e+00 7.4000000e+00 6.4000000e+00 8.1000000e+00 8.4000000e+00 8.0000000e+00 6.9000000e+00 8.8000000e+00 8.8000000e+00 8.0000000e+00 7.5000000e+00 7.5000000e+00 7.9000000e+00 6.6000000e+00 1.2000000e+00 7.0000000e-01 3.0000000e-01 1.3000000e+00 8.0000000e-01 6.0000000e-01 6.0000000e-01 9.0000000e-01 1.7000000e+00 1.4000000e+00 1.8000000e+00 1.0000000e+00 3.0000000e-01 1.3000000e+00 5.0000000e-01 9.0000000e-01 5.0000000e-01 8.0000000e-01 1.0000000e+00 9.0000000e-01 8.0000000e-01 6.0000000e-01 4.0000000e-01 4.0000000e-01 9.0000000e-01 9.0000000e-01 9.0000000e-01 9.0000000e-01 1.1000000e+00 8.0000000e-01 6.0000000e-01 7.0000000e-01 8.0000000e-01 1.3000000e+00 4.0000000e-01 3.0000000e-01 2.0000000e+00 1.1000000e+00 7.0000000e-01 1.0000000e+00 9.0000000e-01 5.0000000e-01 8.0000000e-01 5.0000000e-01 3.0000000e-01 6.9000000e+00 6.2000000e+00 7.2000000e+00 5.5000000e+00 6.8000000e+00 5.7000000e+00 6.3000000e+00 4.0000000e+00 6.6000000e+00 4.8000000e+00 4.5000000e+00 5.6000000e+00 5.8000000e+00 6.3000000e+00 4.6000000e+00 6.4000000e+00 5.6000000e+00 5.2000000e+00 7.0000000e+00 5.1000000e+00 6.3000000e+00 5.6000000e+00 7.2000000e+00 6.2000000e+00 6.1000000e+00 6.4000000e+00 7.2000000e+00 7.4000000e+00 6.1000000e+00 4.6000000e+00 5.0000000e+00 4.8000000e+00 5.2000000e+00 7.0000000e+00 5.4000000e+00 5.7000000e+00 6.8000000e+00 6.7000000e+00 5.0000000e+00 5.3000000e+00 5.5000000e+00 6.1000000e+00 5.4000000e+00 4.0000000e+00 5.4000000e+00 5.1000000e+00 5.3000000e+00 5.9000000e+00 3.7000000e+00 5.3000000e+00 8.5000000e+00 7.1000000e+00 9.1000000e+00 7.8000000e+00 8.5000000e+00 1.0300000e+01 5.8000000e+00 9.5000000e+00 8.8000000e+00 9.2000000e+00 7.4000000e+00 7.9000000e+00 8.4000000e+00 7.2000000e+00 7.5000000e+00 7.8000000e+00 7.8000000e+00 1.0200000e+01 1.1300000e+01 7.3000000e+00 8.7000000e+00 6.7000000e+00 1.0600000e+01 7.3000000e+00 8.2000000e+00 8.8000000e+00 7.0000000e+00 6.8000000e+00 8.3000000e+00 8.6000000e+00 9.6000000e+00 9.9000000e+00 8.4000000e+00 7.1000000e+00 7.5000000e+00 1.0100000e+01 7.9000000e+00 7.6000000e+00 6.6000000e+00 8.3000000e+00 8.6000000e+00 8.2000000e+00 7.1000000e+00 8.8000000e+00 8.6000000e+00 8.2000000e+00 7.7000000e+00 7.7000000e+00 7.5000000e+00 6.8000000e+00 1.7000000e+00 1.3000000e+00 2.5000000e+00 1.8000000e+00 6.0000000e-01 1.4000000e+00 2.1000000e+00 2.9000000e+00 1.2000000e+00 1.0000000e+00 4.0000000e-01 1.1000000e+00 5.0000000e-01 7.0000000e-01 7.0000000e-01 7.0000000e-01 2.0000000e+00 1.0000000e+00 1.5000000e+00 1.6000000e+00 1.0000000e+00 1.0000000e+00 1.2000000e+00 1.7000000e+00 1.7000000e+00 7.0000000e-01 9.0000000e-01 9.0000000e-01 1.8000000e+00 1.8000000e+00 1.1000000e+00 1.8000000e+00 2.5000000e+00 1.2000000e+00 1.3000000e+00 3.0000000e+00 2.3000000e+00 1.1000000e+00 6.0000000e-01 1.9000000e+00 7.0000000e-01 2.0000000e+00 7.0000000e-01 1.5000000e+00 6.3000000e+00 5.6000000e+00 6.6000000e+00 4.9000000e+00 6.2000000e+00 5.1000000e+00 5.7000000e+00 4.2000000e+00 6.0000000e+00 4.6000000e+00 4.7000000e+00 5.0000000e+00 5.2000000e+00 5.7000000e+00 4.0000000e+00 5.8000000e+00 5.0000000e+00 4.6000000e+00 6.4000000e+00 4.5000000e+00 5.7000000e+00 5.0000000e+00 6.6000000e+00 5.6000000e+00 5.5000000e+00 5.8000000e+00 6.6000000e+00 6.8000000e+00 5.5000000e+00 4.0000000e+00 4.4000000e+00 4.2000000e+00 4.6000000e+00 6.4000000e+00 4.8000000e+00 5.1000000e+00 6.2000000e+00 6.1000000e+00 4.4000000e+00 4.7000000e+00 4.9000000e+00 5.5000000e+00 4.8000000e+00 4.2000000e+00 4.8000000e+00 4.5000000e+00 4.7000000e+00 5.3000000e+00 3.7000000e+00 4.7000000e+00 7.9000000e+00 6.5000000e+00 8.5000000e+00 7.2000000e+00 7.9000000e+00 9.7000000e+00 6.0000000e+00 8.9000000e+00 8.2000000e+00 8.6000000e+00 6.8000000e+00 7.3000000e+00 7.8000000e+00 6.6000000e+00 6.9000000e+00 7.2000000e+00 7.2000000e+00 9.2000000e+00 1.0700000e+01 6.7000000e+00 8.1000000e+00 6.1000000e+00 1.0000000e+01 6.7000000e+00 7.6000000e+00 8.2000000e+00 6.4000000e+00 6.2000000e+00 7.7000000e+00 8.0000000e+00 9.0000000e+00 8.9000000e+00 7.8000000e+00 6.5000000e+00 6.9000000e+00 9.5000000e+00 7.3000000e+00 7.0000000e+00 6.0000000e+00 7.7000000e+00 8.0000000e+00 7.6000000e+00 6.5000000e+00 8.2000000e+00 8.0000000e+00 7.6000000e+00 7.1000000e+00 7.1000000e+00 6.9000000e+00 6.2000000e+00 6.0000000e-01 8.0000000e-01 9.0000000e-01 1.3000000e+00 5.0000000e-01 8.0000000e-01 1.2000000e+00 2.1000000e+00 2.3000000e+00 1.5000000e+00 6.0000000e-01 1.8000000e+00 1.0000000e+00 1.2000000e+00 1.0000000e+00 7.0000000e-01 1.1000000e+00 8.0000000e-01 1.1000000e+00 7.0000000e-01 9.0000000e-01 7.0000000e-01 6.0000000e-01 8.0000000e-01 1.0000000e+00 1.6000000e+00 1.8000000e+00 9.0000000e-01 9.0000000e-01 1.2000000e+00 9.0000000e-01 8.0000000e-01 7.0000000e-01 6.0000000e-01 1.3000000e+00 6.0000000e-01 1.0000000e+00 1.5000000e+00 6.0000000e-01 1.2000000e+00 3.0000000e-01 1.2000000e+00 6.0000000e-01 7.0000000e+00 6.3000000e+00 7.3000000e+00 5.6000000e+00 6.9000000e+00 5.8000000e+00 6.4000000e+00 3.9000000e+00 6.7000000e+00 4.9000000e+00 4.6000000e+00 5.7000000e+00 5.9000000e+00 6.4000000e+00 4.7000000e+00 6.5000000e+00 5.7000000e+00 5.3000000e+00 7.1000000e+00 5.2000000e+00 6.4000000e+00 5.7000000e+00 7.3000000e+00 6.3000000e+00 6.2000000e+00 6.5000000e+00 7.3000000e+00 7.5000000e+00 6.2000000e+00 4.7000000e+00 5.1000000e+00 4.9000000e+00 5.3000000e+00 7.1000000e+00 5.5000000e+00 5.8000000e+00 6.9000000e+00 6.8000000e+00 5.1000000e+00 5.4000000e+00 5.6000000e+00 6.2000000e+00 5.5000000e+00 4.1000000e+00 5.5000000e+00 5.2000000e+00 5.4000000e+00 6.0000000e+00 3.8000000e+00 5.4000000e+00 8.6000000e+00 7.2000000e+00 9.2000000e+00 7.9000000e+00 8.6000000e+00 1.0400000e+01 5.7000000e+00 9.6000000e+00 8.9000000e+00 9.7000000e+00 7.5000000e+00 8.0000000e+00 8.5000000e+00 7.3000000e+00 7.6000000e+00 7.9000000e+00 7.9000000e+00 1.0700000e+01 1.1400000e+01 7.4000000e+00 8.8000000e+00 6.8000000e+00 1.0700000e+01 7.4000000e+00 8.3000000e+00 8.9000000e+00 7.1000000e+00 6.9000000e+00 8.4000000e+00 8.7000000e+00 9.7000000e+00 1.0400000e+01 8.5000000e+00 7.2000000e+00 7.6000000e+00 1.0200000e+01 8.0000000e+00 7.7000000e+00 6.7000000e+00 8.4000000e+00 8.7000000e+00 8.3000000e+00 7.2000000e+00 8.9000000e+00 8.7000000e+00 8.3000000e+00 7.8000000e+00 7.8000000e+00 7.6000000e+00 6.9000000e+00 1.2000000e+00 5.0000000e-01 7.0000000e-01 3.0000000e-01 8.0000000e-01 1.6000000e+00 1.7000000e+00 1.9000000e+00 1.3000000e+00 4.0000000e-01 1.4000000e+00 6.0000000e-01 6.0000000e-01 6.0000000e-01 1.1000000e+00 7.0000000e-01 6.0000000e-01 5.0000000e-01 3.0000000e-01 3.0000000e-01 3.0000000e-01 6.0000000e-01 6.0000000e-01 6.0000000e-01 1.0000000e+00 1.4000000e+00 5.0000000e-01 5.0000000e-01 8.0000000e-01 5.0000000e-01 1.2000000e+00 1.0000000e-01 4.0000000e-01 1.9000000e+00 1.0000000e+00 6.0000000e-01 1.1000000e+00 8.0000000e-01 6.0000000e-01 7.0000000e-01 6.0000000e-01 2.0000000e-01 6.6000000e+00 5.9000000e+00 6.9000000e+00 5.2000000e+00 6.5000000e+00 5.4000000e+00 6.0000000e+00 3.7000000e+00 6.3000000e+00 4.5000000e+00 4.2000000e+00 5.3000000e+00 5.5000000e+00 6.0000000e+00 4.3000000e+00 6.1000000e+00 5.3000000e+00 4.9000000e+00 6.7000000e+00 4.8000000e+00 6.0000000e+00 5.3000000e+00 6.9000000e+00 5.9000000e+00 5.8000000e+00 6.1000000e+00 6.9000000e+00 7.1000000e+00 5.8000000e+00 4.3000000e+00 4.7000000e+00 4.5000000e+00 4.9000000e+00 6.7000000e+00 5.1000000e+00 5.4000000e+00 6.5000000e+00 6.4000000e+00 4.7000000e+00 5.0000000e+00 5.2000000e+00 5.8000000e+00 5.1000000e+00 3.7000000e+00 5.1000000e+00 4.8000000e+00 5.0000000e+00 5.6000000e+00 3.4000000e+00 5.0000000e+00 8.2000000e+00 6.8000000e+00 8.8000000e+00 7.5000000e+00 8.2000000e+00 1.0000000e+01 5.5000000e+00 9.2000000e+00 8.5000000e+00 9.3000000e+00 7.1000000e+00 7.6000000e+00 8.1000000e+00 6.9000000e+00 7.2000000e+00 7.5000000e+00 7.5000000e+00 1.0300000e+01 1.1000000e+01 7.0000000e+00 8.4000000e+00 6.4000000e+00 1.0300000e+01 7.0000000e+00 7.9000000e+00 8.5000000e+00 6.7000000e+00 6.5000000e+00 8.0000000e+00 8.3000000e+00 9.3000000e+00 1.0000000e+01 8.1000000e+00 6.8000000e+00 7.2000000e+00 9.8000000e+00 7.6000000e+00 7.3000000e+00 6.3000000e+00 8.0000000e+00 8.3000000e+00 7.9000000e+00 6.8000000e+00 8.5000000e+00 8.3000000e+00 7.9000000e+00 7.4000000e+00 7.4000000e+00 7.2000000e+00 6.5000000e+00 9.0000000e-01 1.9000000e+00 1.1000000e+00 6.0000000e-01 6.0000000e-01 2.7000000e+00 3.1000000e+00 2.3000000e+00 1.4000000e+00 2.6000000e+00 1.8000000e+00 1.8000000e+00 1.8000000e+00 1.3000000e+00 1.7000000e+00 1.4000000e+00 9.0000000e-01 1.5000000e+00 1.5000000e+00 1.3000000e+00 8.0000000e-01 8.0000000e-01 1.8000000e+00 2.2000000e+00 2.4000000e+00 9.0000000e-01 1.1000000e+00 1.8000000e+00 9.0000000e-01 2.0000000e-01 1.3000000e+00 1.4000000e+00 9.0000000e-01 4.0000000e-01 1.8000000e+00 2.3000000e+00 6.0000000e-01 1.8000000e+00 5.0000000e-01 1.8000000e+00 1.0000000e+00 7.4000000e+00 6.7000000e+00 7.5000000e+00 5.4000000e+00 6.7000000e+00 5.6000000e+00 7.0000000e+00 3.7000000e+00 6.5000000e+00 4.7000000e+00 4.4000000e+00 5.7000000e+00 5.7000000e+00 6.2000000e+00 4.5000000e+00 6.7000000e+00 5.7000000e+00 5.1000000e+00 6.9000000e+00 5.0000000e+00 6.8000000e+00 5.5000000e+00 7.1000000e+00 6.1000000e+00 6.0000000e+00 6.5000000e+00 7.1000000e+00 7.5000000e+00 6.0000000e+00 4.5000000e+00 4.9000000e+00 4.7000000e+00 5.1000000e+00 6.9000000e+00 5.5000000e+00 6.6000000e+00 7.1000000e+00 6.6000000e+00 5.1000000e+00 5.2000000e+00 5.4000000e+00 6.2000000e+00 5.3000000e+00 3.9000000e+00 5.3000000e+00 5.2000000e+00 5.2000000e+00 5.8000000e+00 3.6000000e+00 5.2000000e+00 9.2000000e+00 7.0000000e+00 9.2000000e+00 7.7000000e+00 8.6000000e+00 1.0400000e+01 5.5000000e+00 9.4000000e+00 8.7000000e+00 1.0500000e+01 7.9000000e+00 7.8000000e+00 8.5000000e+00 7.1000000e+00 7.4000000e+00 8.3000000e+00 7.9000000e+00 1.1500000e+01 1.1200000e+01 7.2000000e+00 9.2000000e+00 6.6000000e+00 1.0500000e+01 7.2000000e+00 8.9000000e+00 9.3000000e+00 6.9000000e+00 6.9000000e+00 8.2000000e+00 8.7000000e+00 9.5000000e+00 1.1200000e+01 8.3000000e+00 7.0000000e+00 7.4000000e+00 1.0200000e+01 8.8000000e+00 7.9000000e+00 6.7000000e+00 8.6000000e+00 8.9000000e+00 8.5000000e+00 7.0000000e+00 9.3000000e+00 9.3000000e+00 8.3000000e+00 7.6000000e+00 7.8000000e+00 8.4000000e+00 6.9000000e+00 1.2000000e+00 6.0000000e-01 3.0000000e-01 1.1000000e+00 2.2000000e+00 2.4000000e+00 1.8000000e+00 9.0000000e-01 1.9000000e+00 1.1000000e+00 1.1000000e+00 1.1000000e+00 1.4000000e+00 1.0000000e+00 9.0000000e-01 4.0000000e-01 8.0000000e-01 8.0000000e-01 8.0000000e-01 5.0000000e-01 3.0000000e-01 1.1000000e+00 1.3000000e+00 1.9000000e+00 0.0000000e+00 6.0000000e-01 1.3000000e+00 0.0000000e+00 9.0000000e-01 6.0000000e-01 9.0000000e-01 1.6000000e+00 9.0000000e-01 1.1000000e+00 1.6000000e+00 5.0000000e-01 1.1000000e+00 6.0000000e-01 1.1000000e+00 5.0000000e-01 6.7000000e+00 6.0000000e+00 6.8000000e+00 5.1000000e+00 6.4000000e+00 5.3000000e+00 6.3000000e+00 3.4000000e+00 6.2000000e+00 4.4000000e+00 4.1000000e+00 5.2000000e+00 5.4000000e+00 5.9000000e+00 4.2000000e+00 6.0000000e+00 5.2000000e+00 4.8000000e+00 6.6000000e+00 4.7000000e+00 6.1000000e+00 5.2000000e+00 6.8000000e+00 5.8000000e+00 5.7000000e+00 6.0000000e+00 6.8000000e+00 7.0000000e+00 5.7000000e+00 4.2000000e+00 4.6000000e+00 4.4000000e+00 4.8000000e+00 6.6000000e+00 5.0000000e+00 5.9000000e+00 6.4000000e+00 6.3000000e+00 4.6000000e+00 4.9000000e+00 5.1000000e+00 5.7000000e+00 5.0000000e+00 3.6000000e+00 5.0000000e+00 4.7000000e+00 4.9000000e+00 5.5000000e+00 3.3000000e+00 4.9000000e+00 8.5000000e+00 6.7000000e+00 8.7000000e+00 7.4000000e+00 8.1000000e+00 9.9000000e+00 5.2000000e+00 9.1000000e+00 8.4000000e+00 9.8000000e+00 7.2000000e+00 7.5000000e+00 8.0000000e+00 6.8000000e+00 7.1000000e+00 7.6000000e+00 7.4000000e+00 1.0800000e+01 1.0900000e+01 6.9000000e+00 8.5000000e+00 6.3000000e+00 1.0200000e+01 6.9000000e+00 8.2000000e+00 8.6000000e+00 6.6000000e+00 6.4000000e+00 7.9000000e+00 8.2000000e+00 9.2000000e+00 1.0500000e+01 8.0000000e+00 6.7000000e+00 7.1000000e+00 9.7000000e+00 8.1000000e+00 7.2000000e+00 6.2000000e+00 7.9000000e+00 8.2000000e+00 7.8000000e+00 6.7000000e+00 8.6000000e+00 8.6000000e+00 7.8000000e+00 7.3000000e+00 7.3000000e+00 7.7000000e+00 6.4000000e+00 1.0000000e+00 1.5000000e+00 2.3000000e+00 1.0000000e+00 1.2000000e+00 6.0000000e-01 7.0000000e-01 7.0000000e-01 5.0000000e-01 5.0000000e-01 5.0000000e-01 1.4000000e+00 1.2000000e+00 1.3000000e+00 1.2000000e+00 1.0000000e+00 4.0000000e-01 6.0000000e-01 1.3000000e+00 1.3000000e+00 5.0000000e-01 7.0000000e-01 7.0000000e-01 1.2000000e+00 1.2000000e+00 5.0000000e-01 1.2000000e+00 1.9000000e+00 6.0000000e-01 9.0000000e-01 2.6000000e+00 1.7000000e+00 1.1000000e+00 1.0000000e+00 1.5000000e+00 5.0000000e-01 1.4000000e+00 1.0000000e-01 9.0000000e-01 6.5000000e+00 5.8000000e+00 6.8000000e+00 5.1000000e+00 6.4000000e+00 5.3000000e+00 5.9000000e+00 4.4000000e+00 6.2000000e+00 4.8000000e+00 4.9000000e+00 5.2000000e+00 5.4000000e+00 5.9000000e+00 4.2000000e+00 6.0000000e+00 5.2000000e+00 4.8000000e+00 6.6000000e+00 4.7000000e+00 5.9000000e+00 5.2000000e+00 6.8000000e+00 5.8000000e+00 5.7000000e+00 6.0000000e+00 6.8000000e+00 7.0000000e+00 5.7000000e+00 4.2000000e+00 4.6000000e+00 4.4000000e+00 4.8000000e+00 6.6000000e+00 5.0000000e+00 5.3000000e+00 6.4000000e+00 6.3000000e+00 4.6000000e+00 4.9000000e+00 5.1000000e+00 5.7000000e+00 5.0000000e+00 4.4000000e+00 5.0000000e+00 4.7000000e+00 4.9000000e+00 5.5000000e+00 3.9000000e+00 4.9000000e+00 8.1000000e+00 6.7000000e+00 8.7000000e+00 7.4000000e+00 8.1000000e+00 9.9000000e+00 6.2000000e+00 9.1000000e+00 8.4000000e+00 8.8000000e+00 7.0000000e+00 7.5000000e+00 8.0000000e+00 6.8000000e+00 7.1000000e+00 7.4000000e+00 7.4000000e+00 9.6000000e+00 1.0900000e+01 6.9000000e+00 8.3000000e+00 6.3000000e+00 1.0200000e+01 6.9000000e+00 7.8000000e+00 8.4000000e+00 6.6000000e+00 6.4000000e+00 7.9000000e+00 8.2000000e+00 9.2000000e+00 9.3000000e+00 8.0000000e+00 6.7000000e+00 7.1000000e+00 9.7000000e+00 7.5000000e+00 7.2000000e+00 6.2000000e+00 7.9000000e+00 8.2000000e+00 7.8000000e+00 6.7000000e+00 8.4000000e+00 8.2000000e+00 7.8000000e+00 7.3000000e+00 7.3000000e+00 7.1000000e+00 6.4000000e+00 7.0000000e-01 1.5000000e+00 2.0000000e+00 2.2000000e+00 1.6000000e+00 7.0000000e-01 1.5000000e+00 9.0000000e-01 7.0000000e-01 9.0000000e-01 1.0000000e+00 8.0000000e-01 3.0000000e-01 6.0000000e-01 4.0000000e-01 6.0000000e-01 6.0000000e-01 3.0000000e-01 3.0000000e-01 9.0000000e-01 1.3000000e+00 1.7000000e+00 6.0000000e-01 8.0000000e-01 1.1000000e+00 6.0000000e-01 1.1000000e+00 4.0000000e-01 7.0000000e-01 1.8000000e+00 9.0000000e-01 7.0000000e-01 1.2000000e+00 7.0000000e-01 7.0000000e-01 6.0000000e-01 9.0000000e-01 5.0000000e-01 6.7000000e+00 6.0000000e+00 7.0000000e+00 5.3000000e+00 6.6000000e+00 5.5000000e+00 6.1000000e+00 3.6000000e+00 6.4000000e+00 4.6000000e+00 4.3000000e+00 5.4000000e+00 5.6000000e+00 6.1000000e+00 4.4000000e+00 6.2000000e+00 5.4000000e+00 5.0000000e+00 6.8000000e+00 4.9000000e+00 6.1000000e+00 5.4000000e+00 7.0000000e+00 6.0000000e+00 5.9000000e+00 6.2000000e+00 7.0000000e+00 7.2000000e+00 5.9000000e+00 4.4000000e+00 4.8000000e+00 4.6000000e+00 5.0000000e+00 6.8000000e+00 5.2000000e+00 5.5000000e+00 6.6000000e+00 6.5000000e+00 4.8000000e+00 5.1000000e+00 5.3000000e+00 5.9000000e+00 5.2000000e+00 3.8000000e+00 5.2000000e+00 4.9000000e+00 5.1000000e+00 5.7000000e+00 3.5000000e+00 5.1000000e+00 8.3000000e+00 6.9000000e+00 8.9000000e+00 7.6000000e+00 8.3000000e+00 1.0100000e+01 5.4000000e+00 9.3000000e+00 8.6000000e+00 9.4000000e+00 7.2000000e+00 7.7000000e+00 8.2000000e+00 7.0000000e+00 7.3000000e+00 7.6000000e+00 7.6000000e+00 1.0400000e+01 1.1100000e+01 7.1000000e+00 8.5000000e+00 6.5000000e+00 1.0400000e+01 7.1000000e+00 8.0000000e+00 8.6000000e+00 6.8000000e+00 6.6000000e+00 8.1000000e+00 8.4000000e+00 9.4000000e+00 1.0100000e+01 8.2000000e+00 6.9000000e+00 7.3000000e+00 9.9000000e+00 7.7000000e+00 7.4000000e+00 6.4000000e+00 8.1000000e+00 8.4000000e+00 8.0000000e+00 6.9000000e+00 8.6000000e+00 8.4000000e+00 8.0000000e+00 7.5000000e+00 7.5000000e+00 7.3000000e+00 6.6000000e+00 8.0000000e-01 2.3000000e+00 2.7000000e+00 1.9000000e+00 1.0000000e+00 2.2000000e+00 1.4000000e+00 1.4000000e+00 1.4000000e+00 1.3000000e+00 1.3000000e+00 1.0000000e+00 5.0000000e-01 1.1000000e+00 1.1000000e+00 9.0000000e-01 6.0000000e-01 4.0000000e-01 1.4000000e+00 1.6000000e+00 2.0000000e+00 3.0000000e-01 7.0000000e-01 1.4000000e+00 3.0000000e-01 6.0000000e-01 9.0000000e-01 1.0000000e+00 1.3000000e+00 8.0000000e-01 1.4000000e+00 1.9000000e+00 2.0000000e-01 1.4000000e+00 5.0000000e-01 1.4000000e+00 6.0000000e-01 7.0000000e+00 6.3000000e+00 7.1000000e+00 5.2000000e+00 6.5000000e+00 5.4000000e+00 6.6000000e+00 3.5000000e+00 6.3000000e+00 4.5000000e+00 4.2000000e+00 5.3000000e+00 5.5000000e+00 6.0000000e+00 4.3000000e+00 6.3000000e+00 5.3000000e+00 4.9000000e+00 6.7000000e+00 4.8000000e+00 6.4000000e+00 5.3000000e+00 6.9000000e+00 5.9000000e+00 5.8000000e+00 6.1000000e+00 6.9000000e+00 7.1000000e+00 5.8000000e+00 4.3000000e+00 4.7000000e+00 4.5000000e+00 4.9000000e+00 6.7000000e+00 5.1000000e+00 6.2000000e+00 6.7000000e+00 6.4000000e+00 4.7000000e+00 5.0000000e+00 5.2000000e+00 5.8000000e+00 5.1000000e+00 3.7000000e+00 5.1000000e+00 4.8000000e+00 5.0000000e+00 5.6000000e+00 3.4000000e+00 5.0000000e+00 8.8000000e+00 6.8000000e+00 8.8000000e+00 7.5000000e+00 8.2000000e+00 1.0000000e+01 5.3000000e+00 9.2000000e+00 8.5000000e+00 1.0100000e+01 7.5000000e+00 7.6000000e+00 8.1000000e+00 6.9000000e+00 7.2000000e+00 7.9000000e+00 7.5000000e+00 1.1100000e+01 1.1000000e+01 7.0000000e+00 8.8000000e+00 6.4000000e+00 1.0300000e+01 7.0000000e+00 8.5000000e+00 8.9000000e+00 6.7000000e+00 6.5000000e+00 8.0000000e+00 8.3000000e+00 9.3000000e+00 1.0800000e+01 8.1000000e+00 6.8000000e+00 7.2000000e+00 9.8000000e+00 8.4000000e+00 7.5000000e+00 6.3000000e+00 8.2000000e+00 8.5000000e+00 8.1000000e+00 6.8000000e+00 8.9000000e+00 8.9000000e+00 7.9000000e+00 7.4000000e+00 7.4000000e+00 8.0000000e+00 6.5000000e+00 2.7000000e+00 3.5000000e+00 2.5000000e+00 1.8000000e+00 3.0000000e+00 2.2000000e+00 2.2000000e+00 2.2000000e+00 1.1000000e+00 2.1000000e+00 1.8000000e+00 1.3000000e+00 1.9000000e+00 1.9000000e+00 1.7000000e+00 1.2000000e+00 1.2000000e+00 2.2000000e+00 2.4000000e+00 2.8000000e+00 1.1000000e+00 1.1000000e+00 2.0000000e+00 1.1000000e+00 4.0000000e-01 1.7000000e+00 1.6000000e+00 1.3000000e+00 6.0000000e-01 2.2000000e+00 2.7000000e+00 1.0000000e+00 2.2000000e+00 9.0000000e-01 2.2000000e+00 1.4000000e+00 7.8000000e+00 7.1000000e+00 7.9000000e+00 6.0000000e+00 7.3000000e+00 6.2000000e+00 7.4000000e+00 4.3000000e+00 7.1000000e+00 5.3000000e+00 5.0000000e+00 6.1000000e+00 6.3000000e+00 6.8000000e+00 5.1000000e+00 7.1000000e+00 6.1000000e+00 5.7000000e+00 7.5000000e+00 5.6000000e+00 7.2000000e+00 6.1000000e+00 7.7000000e+00 6.7000000e+00 6.6000000e+00 6.9000000e+00 7.7000000e+00 7.9000000e+00 6.6000000e+00 5.1000000e+00 5.5000000e+00 5.3000000e+00 5.7000000e+00 7.5000000e+00 5.9000000e+00 7.0000000e+00 7.5000000e+00 7.2000000e+00 5.5000000e+00 5.8000000e+00 6.0000000e+00 6.6000000e+00 5.9000000e+00 4.5000000e+00 5.9000000e+00 5.6000000e+00 5.8000000e+00 6.4000000e+00 4.2000000e+00 5.8000000e+00 9.6000000e+00 7.6000000e+00 9.6000000e+00 8.3000000e+00 9.0000000e+00 1.0800000e+01 6.1000000e+00 1.0000000e+01 9.3000000e+00 1.0900000e+01 8.3000000e+00 8.4000000e+00 8.9000000e+00 7.7000000e+00 8.0000000e+00 8.7000000e+00 8.3000000e+00 1.1900000e+01 1.1800000e+01 7.8000000e+00 9.6000000e+00 7.2000000e+00 1.1100000e+01 7.8000000e+00 9.3000000e+00 9.7000000e+00 7.5000000e+00 7.3000000e+00 8.8000000e+00 9.1000000e+00 1.0100000e+01 1.1600000e+01 8.9000000e+00 7.6000000e+00 8.0000000e+00 1.0600000e+01 9.2000000e+00 8.3000000e+00 7.1000000e+00 9.0000000e+00 9.3000000e+00 8.9000000e+00 7.6000000e+00 9.7000000e+00 9.7000000e+00 8.7000000e+00 8.2000000e+00 8.2000000e+00 8.8000000e+00 7.3000000e+00 1.0000000e+00 8.0000000e-01 1.5000000e+00 9.0000000e-01 1.3000000e+00 1.5000000e+00 1.5000000e+00 1.8000000e+00 2.2000000e+00 2.3000000e+00 2.2000000e+00 2.0000000e+00 1.4000000e+00 1.4000000e+00 2.3000000e+00 2.3000000e+00 1.5000000e+00 1.1000000e+00 7.0000000e-01 2.2000000e+00 1.6000000e+00 9.0000000e-01 2.2000000e+00 2.5000000e+00 1.6000000e+00 1.5000000e+00 3.2000000e+00 2.3000000e+00 2.1000000e+00 1.8000000e+00 2.3000000e+00 1.3000000e+00 2.2000000e+00 1.1000000e+00 1.7000000e+00 6.7000000e+00 6.0000000e+00 7.0000000e+00 5.9000000e+00 6.6000000e+00 5.7000000e+00 6.1000000e+00 5.4000000e+00 6.4000000e+00 5.8000000e+00 5.9000000e+00 5.4000000e+00 5.6000000e+00 6.1000000e+00 4.8000000e+00 6.2000000e+00 5.8000000e+00 5.0000000e+00 6.8000000e+00 5.3000000e+00 6.1000000e+00 5.4000000e+00 7.0000000e+00 6.0000000e+00 5.9000000e+00 6.2000000e+00 7.0000000e+00 7.2000000e+00 5.9000000e+00 4.6000000e+00 5.4000000e+00 5.2000000e+00 5.0000000e+00 6.8000000e+00 6.0000000e+00 5.5000000e+00 6.6000000e+00 6.5000000e+00 5.2000000e+00 5.7000000e+00 5.9000000e+00 5.9000000e+00 5.2000000e+00 5.4000000e+00 5.6000000e+00 5.1000000e+00 5.3000000e+00 5.7000000e+00 4.9000000e+00 5.3000000e+00 8.3000000e+00 6.9000000e+00 8.9000000e+00 7.6000000e+00 8.3000000e+00 1.0100000e+01 7.2000000e+00 9.3000000e+00 8.6000000e+00 9.0000000e+00 7.2000000e+00 7.7000000e+00 8.2000000e+00 7.2000000e+00 7.3000000e+00 7.6000000e+00 7.6000000e+00 9.6000000e+00 1.1100000e+01 7.1000000e+00 8.5000000e+00 6.9000000e+00 1.0400000e+01 7.1000000e+00 8.0000000e+00 8.6000000e+00 6.8000000e+00 6.6000000e+00 8.1000000e+00 8.4000000e+00 9.4000000e+00 9.3000000e+00 8.2000000e+00 6.9000000e+00 7.3000000e+00 9.9000000e+00 7.7000000e+00 7.4000000e+00 6.4000000e+00 8.1000000e+00 8.4000000e+00 8.0000000e+00 6.9000000e+00 8.6000000e+00 8.4000000e+00 8.0000000e+00 7.5000000e+00 7.5000000e+00 7.3000000e+00 6.6000000e+00 1.0000000e+00 1.7000000e+00 9.0000000e-01 1.3000000e+00 1.7000000e+00 1.3000000e+00 2.6000000e+00 2.0000000e+00 2.5000000e+00 2.4000000e+00 1.8000000e+00 1.6000000e+00 1.8000000e+00 2.5000000e+00 2.5000000e+00 1.3000000e+00 1.1000000e+00 7.0000000e-01 2.4000000e+00 2.4000000e+00 1.5000000e+00 2.4000000e+00 3.1000000e+00 1.8000000e+00 1.9000000e+00 3.6000000e+00 2.9000000e+00 1.9000000e+00 1.6000000e+00 2.5000000e+00 1.5000000e+00 2.6000000e+00 1.3000000e+00 2.1000000e+00 6.7000000e+00 6.0000000e+00 7.0000000e+00 5.7000000e+00 6.6000000e+00 5.5000000e+00 6.1000000e+00 5.2000000e+00 6.4000000e+00 5.6000000e+00 5.7000000e+00 5.4000000e+00 5.6000000e+00 6.1000000e+00 4.6000000e+00 6.2000000e+00 5.6000000e+00 5.0000000e+00 6.8000000e+00 5.1000000e+00 6.1000000e+00 5.4000000e+00 7.0000000e+00 6.0000000e+00 5.9000000e+00 6.2000000e+00 7.0000000e+00 7.2000000e+00 5.9000000e+00 4.4000000e+00 5.2000000e+00 5.0000000e+00 5.0000000e+00 6.8000000e+00 5.8000000e+00 5.5000000e+00 6.6000000e+00 6.5000000e+00 5.0000000e+00 5.5000000e+00 5.7000000e+00 5.9000000e+00 5.2000000e+00 5.2000000e+00 5.4000000e+00 4.9000000e+00 5.1000000e+00 5.7000000e+00 4.7000000e+00 5.1000000e+00 8.3000000e+00 6.9000000e+00 8.9000000e+00 7.6000000e+00 8.3000000e+00 1.0100000e+01 7.0000000e+00 9.3000000e+00 8.6000000e+00 9.0000000e+00 7.2000000e+00 7.7000000e+00 8.2000000e+00 7.0000000e+00 7.3000000e+00 7.6000000e+00 7.6000000e+00 9.6000000e+00 1.1100000e+01 7.1000000e+00 8.5000000e+00 6.7000000e+00 1.0400000e+01 7.1000000e+00 8.0000000e+00 8.6000000e+00 6.8000000e+00 6.6000000e+00 8.1000000e+00 8.4000000e+00 9.4000000e+00 9.3000000e+00 8.2000000e+00 6.9000000e+00 7.3000000e+00 9.9000000e+00 7.7000000e+00 7.4000000e+00 6.4000000e+00 8.1000000e+00 8.4000000e+00 8.0000000e+00 6.9000000e+00 8.6000000e+00 8.4000000e+00 8.0000000e+00 7.5000000e+00 7.5000000e+00 7.3000000e+00 6.6000000e+00 9.0000000e-01 9.0000000e-01 7.0000000e-01 1.1000000e+00 7.0000000e-01 1.6000000e+00 1.4000000e+00 1.9000000e+00 1.8000000e+00 1.2000000e+00 1.0000000e+00 1.0000000e+00 1.9000000e+00 1.9000000e+00 7.0000000e-01 9.0000000e-01 7.0000000e-01 1.8000000e+00 1.4000000e+00 7.0000000e-01 1.8000000e+00 2.1000000e+00 1.2000000e+00 9.0000000e-01 2.6000000e+00 1.9000000e+00 1.3000000e+00 1.0000000e+00 1.7000000e+00 9.0000000e-01 1.8000000e+00 7.0000000e-01 1.3000000e+00 6.7000000e+00 6.0000000e+00 7.0000000e+00 5.3000000e+00 6.6000000e+00 5.5000000e+00 6.1000000e+00 4.6000000e+00 6.4000000e+00 5.0000000e+00 5.1000000e+00 5.4000000e+00 5.6000000e+00 6.1000000e+00 4.4000000e+00 6.2000000e+00 5.4000000e+00 5.0000000e+00 6.8000000e+00 4.9000000e+00 6.1000000e+00 5.4000000e+00 7.0000000e+00 6.0000000e+00 5.9000000e+00 6.2000000e+00 7.0000000e+00 7.2000000e+00 5.9000000e+00 4.4000000e+00 4.8000000e+00 4.6000000e+00 5.0000000e+00 6.8000000e+00 5.2000000e+00 5.5000000e+00 6.6000000e+00 6.5000000e+00 4.8000000e+00 5.1000000e+00 5.3000000e+00 5.9000000e+00 5.2000000e+00 4.6000000e+00 5.2000000e+00 4.9000000e+00 5.1000000e+00 5.7000000e+00 4.1000000e+00 5.1000000e+00 8.3000000e+00 6.9000000e+00 8.9000000e+00 7.6000000e+00 8.3000000e+00 1.0100000e+01 6.4000000e+00 9.3000000e+00 8.6000000e+00 9.0000000e+00 7.2000000e+00 7.7000000e+00 8.2000000e+00 7.0000000e+00 7.3000000e+00 7.6000000e+00 7.6000000e+00 9.6000000e+00 1.1100000e+01 7.1000000e+00 8.5000000e+00 6.5000000e+00 1.0400000e+01 7.1000000e+00 8.0000000e+00 8.6000000e+00 6.8000000e+00 6.6000000e+00 8.1000000e+00 8.4000000e+00 9.4000000e+00 9.3000000e+00 8.2000000e+00 6.9000000e+00 7.3000000e+00 9.9000000e+00 7.7000000e+00 7.4000000e+00 6.4000000e+00 8.1000000e+00 8.4000000e+00 8.0000000e+00 6.9000000e+00 8.6000000e+00 8.4000000e+00 8.0000000e+00 7.5000000e+00 7.5000000e+00 7.3000000e+00 6.6000000e+00 1.2000000e+00 4.0000000e-01 8.0000000e-01 4.0000000e-01 1.1000000e+00 7.0000000e-01 1.0000000e+00 9.0000000e-01 5.0000000e-01 3.0000000e-01 3.0000000e-01 1.0000000e+00 1.0000000e+00 6.0000000e-01 1.0000000e+00 1.2000000e+00 9.0000000e-01 7.0000000e-01 6.0000000e-01 9.0000000e-01 1.4000000e+00 3.0000000e-01 2.0000000e-01 1.9000000e+00 1.2000000e+00 6.0000000e-01 9.0000000e-01 8.0000000e-01 6.0000000e-01 9.0000000e-01 6.0000000e-01 4.0000000e-01 6.6000000e+00 5.9000000e+00 6.9000000e+00 5.2000000e+00 6.5000000e+00 5.4000000e+00 6.0000000e+00 3.9000000e+00 6.3000000e+00 4.5000000e+00 4.4000000e+00 5.3000000e+00 5.5000000e+00 6.0000000e+00 4.3000000e+00 6.1000000e+00 5.3000000e+00 4.9000000e+00 6.7000000e+00 4.8000000e+00 6.0000000e+00 5.3000000e+00 6.9000000e+00 5.9000000e+00 5.8000000e+00 6.1000000e+00 6.9000000e+00 7.1000000e+00 5.8000000e+00 4.3000000e+00 4.7000000e+00 4.5000000e+00 4.9000000e+00 6.7000000e+00 5.1000000e+00 5.4000000e+00 6.5000000e+00 6.4000000e+00 4.7000000e+00 5.0000000e+00 5.2000000e+00 5.8000000e+00 5.1000000e+00 3.9000000e+00 5.1000000e+00 4.8000000e+00 5.0000000e+00 5.6000000e+00 3.4000000e+00 5.0000000e+00 8.2000000e+00 6.8000000e+00 8.8000000e+00 7.5000000e+00 8.2000000e+00 1.0000000e+01 5.7000000e+00 9.2000000e+00 8.5000000e+00 9.1000000e+00 7.1000000e+00 7.6000000e+00 8.1000000e+00 6.9000000e+00 7.2000000e+00 7.5000000e+00 7.5000000e+00 1.0100000e+01 1.1000000e+01 7.0000000e+00 8.4000000e+00 6.4000000e+00 1.0300000e+01 7.0000000e+00 7.9000000e+00 8.5000000e+00 6.7000000e+00 6.5000000e+00 8.0000000e+00 8.3000000e+00 9.3000000e+00 9.8000000e+00 8.1000000e+00 6.8000000e+00 7.2000000e+00 9.8000000e+00 7.6000000e+00 7.3000000e+00 6.3000000e+00 8.0000000e+00 8.3000000e+00 7.9000000e+00 6.8000000e+00 8.5000000e+00 8.3000000e+00 7.9000000e+00 7.4000000e+00 7.4000000e+00 7.2000000e+00 6.5000000e+00 8.0000000e-01 8.0000000e-01 1.0000000e+00 2.1000000e+00 1.3000000e+00 1.6000000e+00 1.7000000e+00 1.3000000e+00 1.1000000e+00 1.3000000e+00 1.8000000e+00 1.8000000e+00 1.0000000e+00 1.2000000e+00 1.0000000e+00 1.9000000e+00 1.9000000e+00 1.0000000e+00 1.9000000e+00 2.6000000e+00 1.3000000e+00 1.4000000e+00 3.1000000e+00 2.4000000e+00 1.4000000e+00 9.0000000e-01 2.0000000e+00 8.0000000e-01 2.1000000e+00 8.0000000e-01 1.6000000e+00 6.0000000e+00 5.3000000e+00 6.3000000e+00 5.0000000e+00 5.9000000e+00 4.8000000e+00 5.4000000e+00 4.5000000e+00 5.7000000e+00 4.9000000e+00 5.0000000e+00 4.7000000e+00 4.9000000e+00 5.4000000e+00 3.9000000e+00 5.5000000e+00 4.9000000e+00 4.3000000e+00 6.1000000e+00 4.4000000e+00 5.4000000e+00 4.7000000e+00 6.3000000e+00 5.3000000e+00 5.2000000e+00 5.5000000e+00 6.3000000e+00 6.5000000e+00 5.2000000e+00 3.7000000e+00 4.5000000e+00 4.3000000e+00 4.3000000e+00 6.1000000e+00 5.1000000e+00 4.8000000e+00 5.9000000e+00 5.8000000e+00 4.3000000e+00 4.8000000e+00 5.0000000e+00 5.2000000e+00 4.5000000e+00 4.5000000e+00 4.7000000e+00 4.2000000e+00 4.4000000e+00 5.0000000e+00 4.0000000e+00 4.4000000e+00 7.6000000e+00 6.2000000e+00 8.2000000e+00 6.9000000e+00 7.6000000e+00 9.4000000e+00 6.3000000e+00 8.6000000e+00 7.9000000e+00 8.3000000e+00 6.5000000e+00 7.0000000e+00 7.5000000e+00 6.3000000e+00 6.6000000e+00 6.9000000e+00 6.9000000e+00 8.9000000e+00 1.0400000e+01 6.4000000e+00 7.8000000e+00 6.0000000e+00 9.7000000e+00 6.4000000e+00 7.3000000e+00 7.9000000e+00 6.1000000e+00 5.9000000e+00 7.4000000e+00 7.7000000e+00 8.7000000e+00 8.6000000e+00 7.5000000e+00 6.2000000e+00 6.6000000e+00 9.2000000e+00 7.0000000e+00 6.7000000e+00 5.7000000e+00 7.4000000e+00 7.7000000e+00 7.3000000e+00 6.2000000e+00 7.9000000e+00 7.7000000e+00 7.3000000e+00 6.8000000e+00 6.8000000e+00 6.6000000e+00 5.9000000e+00 1.0000000e+00 2.0000000e-01 1.3000000e+00 9.0000000e-01 1.2000000e+00 1.1000000e+00 7.0000000e-01 5.0000000e-01 7.0000000e-01 1.2000000e+00 1.2000000e+00 8.0000000e-01 6.0000000e-01 1.0000000e+00 1.1000000e+00 1.1000000e+00 1.0000000e+00 1.1000000e+00 1.8000000e+00 5.0000000e-01 6.0000000e-01 2.3000000e+00 1.6000000e+00 8.0000000e-01 5.0000000e-01 1.2000000e+00 2.0000000e-01 1.3000000e+00 4.0000000e-01 8.0000000e-01 6.8000000e+00 6.1000000e+00 7.1000000e+00 5.4000000e+00 6.7000000e+00 5.6000000e+00 6.2000000e+00 4.1000000e+00 6.5000000e+00 4.7000000e+00 4.6000000e+00 5.5000000e+00 5.7000000e+00 6.2000000e+00 4.5000000e+00 6.3000000e+00 5.5000000e+00 5.1000000e+00 6.9000000e+00 5.0000000e+00 6.2000000e+00 5.5000000e+00 7.1000000e+00 6.1000000e+00 6.0000000e+00 6.3000000e+00 7.1000000e+00 7.3000000e+00 6.0000000e+00 4.5000000e+00 4.9000000e+00 4.7000000e+00 5.1000000e+00 6.9000000e+00 5.3000000e+00 5.6000000e+00 6.7000000e+00 6.6000000e+00 4.9000000e+00 5.2000000e+00 5.4000000e+00 6.0000000e+00 5.3000000e+00 4.1000000e+00 5.3000000e+00 5.0000000e+00 5.2000000e+00 5.8000000e+00 3.6000000e+00 5.2000000e+00 8.4000000e+00 7.0000000e+00 9.0000000e+00 7.7000000e+00 8.4000000e+00 1.0200000e+01 5.9000000e+00 9.4000000e+00 8.7000000e+00 9.1000000e+00 7.3000000e+00 7.8000000e+00 8.3000000e+00 7.1000000e+00 7.4000000e+00 7.7000000e+00 7.7000000e+00 9.7000000e+00 1.1200000e+01 7.2000000e+00 8.6000000e+00 6.6000000e+00 1.0500000e+01 7.2000000e+00 8.1000000e+00 8.7000000e+00 6.9000000e+00 6.7000000e+00 8.2000000e+00 8.5000000e+00 9.5000000e+00 9.4000000e+00 8.3000000e+00 7.0000000e+00 7.4000000e+00 1.0000000e+01 7.8000000e+00 7.5000000e+00 6.5000000e+00 8.2000000e+00 8.5000000e+00 8.1000000e+00 7.0000000e+00 8.7000000e+00 8.5000000e+00 8.1000000e+00 7.6000000e+00 7.6000000e+00 7.4000000e+00 6.7000000e+00 1.0000000e+00 1.7000000e+00 7.0000000e-01 8.0000000e-01 9.0000000e-01 7.0000000e-01 5.0000000e-01 5.0000000e-01 1.0000000e+00 1.0000000e+00 4.0000000e-01 1.2000000e+00 1.2000000e+00 1.1000000e+00 1.1000000e+00 6.0000000e-01 1.1000000e+00 1.8000000e+00 5.0000000e-01 1.0000000e+00 2.5000000e+00 1.6000000e+00 1.0000000e+00 1.1000000e+00 1.4000000e+00 8.0000000e-01 1.3000000e+00 6.0000000e-01 8.0000000e-01 6.0000000e+00 5.3000000e+00 6.3000000e+00 4.6000000e+00 5.9000000e+00 4.8000000e+00 5.4000000e+00 3.9000000e+00 5.7000000e+00 4.3000000e+00 4.4000000e+00 4.7000000e+00 4.9000000e+00 5.4000000e+00 3.7000000e+00 5.5000000e+00 4.7000000e+00 4.3000000e+00 6.1000000e+00 4.2000000e+00 5.4000000e+00 4.7000000e+00 6.3000000e+00 5.3000000e+00 5.2000000e+00 5.5000000e+00 6.3000000e+00 6.5000000e+00 5.2000000e+00 3.7000000e+00 4.1000000e+00 3.9000000e+00 4.3000000e+00 6.1000000e+00 4.5000000e+00 4.8000000e+00 5.9000000e+00 5.8000000e+00 4.1000000e+00 4.4000000e+00 4.6000000e+00 5.2000000e+00 4.5000000e+00 3.9000000e+00 4.5000000e+00 4.2000000e+00 4.4000000e+00 5.0000000e+00 3.4000000e+00 4.4000000e+00 7.6000000e+00 6.2000000e+00 8.2000000e+00 6.9000000e+00 7.6000000e+00 9.4000000e+00 5.7000000e+00 8.6000000e+00 7.9000000e+00 8.7000000e+00 6.5000000e+00 7.0000000e+00 7.5000000e+00 6.3000000e+00 6.6000000e+00 6.9000000e+00 6.9000000e+00 9.7000000e+00 1.0400000e+01 6.4000000e+00 7.8000000e+00 5.8000000e+00 9.7000000e+00 6.4000000e+00 7.3000000e+00 7.9000000e+00 6.1000000e+00 5.9000000e+00 7.4000000e+00 7.7000000e+00 8.7000000e+00 9.4000000e+00 7.5000000e+00 6.2000000e+00 6.6000000e+00 9.2000000e+00 7.0000000e+00 6.7000000e+00 5.7000000e+00 7.4000000e+00 7.7000000e+00 7.3000000e+00 6.2000000e+00 7.9000000e+00 7.7000000e+00 7.3000000e+00 6.8000000e+00 6.8000000e+00 6.6000000e+00 5.9000000e+00 1.3000000e+00 7.0000000e-01 1.2000000e+00 1.1000000e+00 5.0000000e-01 5.0000000e-01 7.0000000e-01 1.2000000e+00 1.2000000e+00 6.0000000e-01 8.0000000e-01 1.2000000e+00 1.1000000e+00 1.1000000e+00 1.0000000e+00 1.1000000e+00 1.8000000e+00 5.0000000e-01 6.0000000e-01 2.3000000e+00 1.6000000e+00 6.0000000e-01 5.0000000e-01 1.2000000e+00 4.0000000e-01 1.3000000e+00 4.0000000e-01 8.0000000e-01 6.6000000e+00 5.9000000e+00 6.9000000e+00 5.2000000e+00 6.5000000e+00 5.4000000e+00 6.0000000e+00 3.9000000e+00 6.3000000e+00 4.5000000e+00 4.4000000e+00 5.3000000e+00 5.5000000e+00 6.0000000e+00 4.3000000e+00 6.1000000e+00 5.3000000e+00 4.9000000e+00 6.7000000e+00 4.8000000e+00 6.0000000e+00 5.3000000e+00 6.9000000e+00 5.9000000e+00 5.8000000e+00 6.1000000e+00 6.9000000e+00 7.1000000e+00 5.8000000e+00 4.3000000e+00 4.7000000e+00 4.5000000e+00 4.9000000e+00 6.7000000e+00 5.1000000e+00 5.4000000e+00 6.5000000e+00 6.4000000e+00 4.7000000e+00 5.0000000e+00 5.2000000e+00 5.8000000e+00 5.1000000e+00 3.9000000e+00 5.1000000e+00 4.8000000e+00 5.0000000e+00 5.6000000e+00 3.4000000e+00 5.0000000e+00 8.2000000e+00 6.8000000e+00 8.8000000e+00 7.5000000e+00 8.2000000e+00 1.0000000e+01 5.7000000e+00 9.2000000e+00 8.5000000e+00 8.9000000e+00 7.1000000e+00 7.6000000e+00 8.1000000e+00 6.9000000e+00 7.2000000e+00 7.5000000e+00 7.5000000e+00 9.7000000e+00 1.1000000e+01 7.0000000e+00 8.4000000e+00 6.4000000e+00 1.0300000e+01 7.0000000e+00 7.9000000e+00 8.5000000e+00 6.7000000e+00 6.5000000e+00 8.0000000e+00 8.3000000e+00 9.3000000e+00 9.4000000e+00 8.1000000e+00 6.8000000e+00 7.2000000e+00 9.8000000e+00 7.6000000e+00 7.3000000e+00 6.3000000e+00 8.0000000e+00 8.3000000e+00 7.9000000e+00 6.8000000e+00 8.5000000e+00 8.3000000e+00 7.9000000e+00 7.4000000e+00 7.4000000e+00 7.2000000e+00 6.5000000e+00 1.8000000e+00 1.3000000e+00 1.6000000e+00 1.4000000e+00 1.2000000e+00 1.2000000e+00 1.1000000e+00 1.3000000e+00 1.7000000e+00 1.7000000e+00 1.9000000e+00 1.4000000e+00 1.0000000e+00 1.3000000e+00 1.4000000e+00 1.1000000e+00 1.2000000e+00 9.0000000e-01 1.8000000e+00 9.0000000e-01 1.5000000e+00 1.8000000e+00 1.3000000e+00 1.3000000e+00 8.0000000e-01 1.3000000e+00 1.1000000e+00 7.7000000e+00 7.0000000e+00 8.0000000e+00 6.3000000e+00 7.6000000e+00 6.5000000e+00 7.1000000e+00 4.6000000e+00 7.4000000e+00 5.6000000e+00 5.3000000e+00 6.4000000e+00 6.6000000e+00 7.1000000e+00 5.4000000e+00 7.2000000e+00 6.4000000e+00 6.0000000e+00 7.8000000e+00 5.9000000e+00 7.1000000e+00 6.4000000e+00 8.0000000e+00 7.0000000e+00 6.9000000e+00 7.2000000e+00 8.0000000e+00 8.2000000e+00 6.9000000e+00 5.4000000e+00 5.8000000e+00 5.6000000e+00 6.0000000e+00 7.8000000e+00 6.2000000e+00 6.5000000e+00 7.6000000e+00 7.5000000e+00 5.8000000e+00 6.1000000e+00 6.3000000e+00 6.9000000e+00 6.2000000e+00 4.8000000e+00 6.2000000e+00 5.9000000e+00 6.1000000e+00 6.7000000e+00 4.5000000e+00 6.1000000e+00 9.3000000e+00 7.9000000e+00 9.9000000e+00 8.6000000e+00 9.3000000e+00 1.1100000e+01 6.4000000e+00 1.0300000e+01 9.6000000e+00 1.0000000e+01 8.2000000e+00 8.7000000e+00 9.2000000e+00 8.0000000e+00 8.3000000e+00 8.6000000e+00 8.6000000e+00 1.1000000e+01 1.2100000e+01 8.1000000e+00 9.5000000e+00 7.5000000e+00 1.1400000e+01 8.1000000e+00 9.0000000e+00 9.6000000e+00 7.8000000e+00 7.6000000e+00 9.1000000e+00 9.4000000e+00 1.0400000e+01 1.0700000e+01 9.2000000e+00 7.9000000e+00 8.3000000e+00 1.0900000e+01 8.7000000e+00 8.4000000e+00 7.4000000e+00 9.1000000e+00 9.4000000e+00 9.0000000e+00 7.9000000e+00 9.6000000e+00 9.4000000e+00 9.0000000e+00 8.5000000e+00 8.5000000e+00 8.3000000e+00 7.6000000e+00 9.0000000e-01 8.0000000e-01 4.0000000e-01 8.0000000e-01 8.0000000e-01 9.0000000e-01 9.0000000e-01 7.0000000e-01 1.5000000e+00 1.9000000e+00 1.0000000e+00 1.0000000e+00 1.3000000e+00 1.0000000e+00 1.7000000e+00 6.0000000e-01 9.0000000e-01 2.2000000e+00 1.5000000e+00 5.0000000e-01 8.0000000e-01 1.1000000e+00 9.0000000e-01 1.2000000e+00 1.1000000e+00 7.0000000e-01 5.9000000e+00 5.2000000e+00 6.2000000e+00 4.5000000e+00 5.8000000e+00 4.7000000e+00 5.3000000e+00 3.2000000e+00 5.6000000e+00 3.8000000e+00 3.7000000e+00 4.6000000e+00 4.8000000e+00 5.3000000e+00 3.6000000e+00 5.4000000e+00 4.6000000e+00 4.2000000e+00 6.0000000e+00 4.1000000e+00 5.3000000e+00 4.6000000e+00 6.2000000e+00 5.2000000e+00 5.1000000e+00 5.4000000e+00 6.2000000e+00 6.4000000e+00 5.1000000e+00 3.6000000e+00 4.0000000e+00 3.8000000e+00 4.2000000e+00 6.0000000e+00 4.4000000e+00 4.9000000e+00 5.8000000e+00 5.7000000e+00 4.0000000e+00 4.3000000e+00 4.5000000e+00 5.1000000e+00 4.4000000e+00 3.2000000e+00 4.4000000e+00 4.1000000e+00 4.3000000e+00 4.9000000e+00 2.7000000e+00 4.3000000e+00 7.5000000e+00 6.1000000e+00 8.1000000e+00 6.8000000e+00 7.5000000e+00 9.3000000e+00 5.0000000e+00 8.5000000e+00 7.8000000e+00 8.8000000e+00 6.4000000e+00 6.9000000e+00 7.4000000e+00 6.2000000e+00 6.5000000e+00 6.8000000e+00 6.8000000e+00 9.8000000e+00 1.0300000e+01 6.3000000e+00 7.7000000e+00 5.7000000e+00 9.6000000e+00 6.3000000e+00 7.2000000e+00 7.8000000e+00 6.0000000e+00 5.8000000e+00 7.3000000e+00 7.6000000e+00 8.6000000e+00 9.5000000e+00 7.4000000e+00 6.1000000e+00 6.5000000e+00 9.1000000e+00 7.1000000e+00 6.6000000e+00 5.6000000e+00 7.3000000e+00 7.6000000e+00 7.2000000e+00 6.1000000e+00 7.8000000e+00 7.6000000e+00 7.2000000e+00 6.7000000e+00 6.7000000e+00 6.7000000e+00 5.8000000e+00 9.0000000e-01 7.0000000e-01 9.0000000e-01 9.0000000e-01 6.0000000e-01 6.0000000e-01 1.2000000e+00 1.6000000e+00 2.0000000e+00 9.0000000e-01 1.1000000e+00 1.4000000e+00 9.0000000e-01 1.4000000e+00 7.0000000e-01 1.0000000e+00 2.1000000e+00 1.2000000e+00 1.0000000e+00 9.0000000e-01 1.0000000e+00 1.0000000e+00 9.0000000e-01 1.2000000e+00 8.0000000e-01 6.4000000e+00 5.7000000e+00 6.7000000e+00 5.0000000e+00 6.3000000e+00 5.2000000e+00 5.8000000e+00 3.3000000e+00 6.1000000e+00 4.3000000e+00 4.0000000e+00 5.1000000e+00 5.3000000e+00 5.8000000e+00 4.1000000e+00 5.9000000e+00 5.1000000e+00 4.7000000e+00 6.5000000e+00 4.6000000e+00 5.8000000e+00 5.1000000e+00 6.7000000e+00 5.7000000e+00 5.6000000e+00 5.9000000e+00 6.7000000e+00 6.9000000e+00 5.6000000e+00 4.1000000e+00 4.5000000e+00 4.3000000e+00 4.7000000e+00 6.5000000e+00 4.9000000e+00 5.2000000e+00 6.3000000e+00 6.2000000e+00 4.5000000e+00 4.8000000e+00 5.0000000e+00 5.6000000e+00 4.9000000e+00 3.5000000e+00 4.9000000e+00 4.6000000e+00 4.8000000e+00 5.4000000e+00 3.2000000e+00 4.8000000e+00 8.0000000e+00 6.6000000e+00 8.6000000e+00 7.3000000e+00 8.0000000e+00 9.8000000e+00 5.1000000e+00 9.0000000e+00 8.3000000e+00 9.1000000e+00 6.9000000e+00 7.4000000e+00 7.9000000e+00 6.7000000e+00 7.0000000e+00 7.3000000e+00 7.3000000e+00 1.0100000e+01 1.0800000e+01 6.8000000e+00 8.2000000e+00 6.2000000e+00 1.0100000e+01 6.8000000e+00 7.7000000e+00 8.3000000e+00 6.5000000e+00 6.3000000e+00 7.8000000e+00 8.1000000e+00 9.1000000e+00 9.8000000e+00 7.9000000e+00 6.6000000e+00 7.0000000e+00 9.6000000e+00 7.4000000e+00 7.1000000e+00 6.1000000e+00 7.8000000e+00 8.1000000e+00 7.7000000e+00 6.6000000e+00 8.3000000e+00 8.1000000e+00 7.7000000e+00 7.2000000e+00 7.2000000e+00 7.0000000e+00 6.3000000e+00 6.0000000e-01 8.0000000e-01 8.0000000e-01 5.0000000e-01 3.0000000e-01 1.1000000e+00 1.5000000e+00 1.9000000e+00 4.0000000e-01 6.0000000e-01 1.3000000e+00 4.0000000e-01 9.0000000e-01 6.0000000e-01 9.0000000e-01 1.6000000e+00 1.1000000e+00 9.0000000e-01 1.4000000e+00 5.0000000e-01 9.0000000e-01 8.0000000e-01 1.1000000e+00 5.0000000e-01 6.5000000e+00 5.8000000e+00 6.6000000e+00 4.7000000e+00 6.0000000e+00 4.9000000e+00 6.1000000e+00 3.2000000e+00 5.8000000e+00 4.0000000e+00 3.7000000e+00 4.8000000e+00 5.0000000e+00 5.5000000e+00 3.8000000e+00 5.8000000e+00 4.8000000e+00 4.4000000e+00 6.2000000e+00 4.3000000e+00 5.9000000e+00 4.8000000e+00 6.4000000e+00 5.4000000e+00 5.3000000e+00 5.6000000e+00 6.4000000e+00 6.6000000e+00 5.3000000e+00 3.8000000e+00 4.2000000e+00 4.0000000e+00 4.4000000e+00 6.2000000e+00 4.6000000e+00 5.7000000e+00 6.2000000e+00 5.9000000e+00 4.2000000e+00 4.5000000e+00 4.7000000e+00 5.3000000e+00 4.6000000e+00 3.2000000e+00 4.6000000e+00 4.3000000e+00 4.5000000e+00 5.1000000e+00 2.9000000e+00 4.5000000e+00 8.3000000e+00 6.3000000e+00 8.3000000e+00 7.0000000e+00 7.7000000e+00 9.5000000e+00 5.0000000e+00 8.7000000e+00 8.0000000e+00 9.6000000e+00 7.0000000e+00 7.1000000e+00 7.6000000e+00 6.4000000e+00 6.7000000e+00 7.4000000e+00 7.0000000e+00 1.0600000e+01 1.0500000e+01 6.5000000e+00 8.3000000e+00 5.9000000e+00 9.8000000e+00 6.5000000e+00 8.0000000e+00 8.4000000e+00 6.2000000e+00 6.0000000e+00 7.5000000e+00 7.8000000e+00 8.8000000e+00 1.0300000e+01 7.6000000e+00 6.3000000e+00 6.7000000e+00 9.3000000e+00 7.9000000e+00 7.0000000e+00 5.8000000e+00 7.7000000e+00 8.0000000e+00 7.6000000e+00 6.3000000e+00 8.4000000e+00 8.4000000e+00 7.4000000e+00 6.9000000e+00 6.9000000e+00 7.5000000e+00 6.0000000e+00 6.0000000e-01 6.0000000e-01 7.0000000e-01 7.0000000e-01 5.0000000e-01 1.3000000e+00 1.7000000e+00 8.0000000e-01 8.0000000e-01 1.1000000e+00 8.0000000e-01 1.5000000e+00 4.0000000e-01 5.0000000e-01 2.0000000e+00 1.3000000e+00 3.0000000e-01 8.0000000e-01 9.0000000e-01 7.0000000e-01 1.0000000e+00 9.0000000e-01 5.0000000e-01 6.3000000e+00 5.6000000e+00 6.6000000e+00 4.9000000e+00 6.2000000e+00 5.1000000e+00 5.7000000e+00 3.4000000e+00 6.0000000e+00 4.2000000e+00 3.9000000e+00 5.0000000e+00 5.2000000e+00 5.7000000e+00 4.0000000e+00 5.8000000e+00 5.0000000e+00 4.6000000e+00 6.4000000e+00 4.5000000e+00 5.7000000e+00 5.0000000e+00 6.6000000e+00 5.6000000e+00 5.5000000e+00 5.8000000e+00 6.6000000e+00 6.8000000e+00 5.5000000e+00 4.0000000e+00 4.4000000e+00 4.2000000e+00 4.6000000e+00 6.4000000e+00 4.8000000e+00 5.1000000e+00 6.2000000e+00 6.1000000e+00 4.4000000e+00 4.7000000e+00 4.9000000e+00 5.5000000e+00 4.8000000e+00 3.4000000e+00 4.8000000e+00 4.5000000e+00 4.7000000e+00 5.3000000e+00 3.1000000e+00 4.7000000e+00 7.9000000e+00 6.5000000e+00 8.5000000e+00 7.2000000e+00 7.9000000e+00 9.7000000e+00 5.2000000e+00 8.9000000e+00 8.2000000e+00 9.0000000e+00 6.8000000e+00 7.3000000e+00 7.8000000e+00 6.6000000e+00 6.9000000e+00 7.2000000e+00 7.2000000e+00 1.0000000e+01 1.0700000e+01 6.7000000e+00 8.1000000e+00 6.1000000e+00 1.0000000e+01 6.7000000e+00 7.6000000e+00 8.2000000e+00 6.4000000e+00 6.2000000e+00 7.7000000e+00 8.0000000e+00 9.0000000e+00 9.7000000e+00 7.8000000e+00 6.5000000e+00 6.9000000e+00 9.5000000e+00 7.3000000e+00 7.0000000e+00 6.0000000e+00 7.7000000e+00 8.0000000e+00 7.6000000e+00 6.5000000e+00 8.2000000e+00 8.0000000e+00 7.6000000e+00 7.1000000e+00 7.1000000e+00 6.9000000e+00 6.2000000e+00 2.0000000e-01 9.0000000e-01 9.0000000e-01 5.0000000e-01 7.0000000e-01 1.1000000e+00 8.0000000e-01 8.0000000e-01 5.0000000e-01 8.0000000e-01 1.5000000e+00 2.0000000e-01 5.0000000e-01 2.2000000e+00 1.3000000e+00 7.0000000e-01 1.0000000e+00 1.1000000e+00 5.0000000e-01 1.0000000e+00 3.0000000e-01 5.0000000e-01 6.5000000e+00 5.8000000e+00 6.8000000e+00 5.1000000e+00 6.4000000e+00 5.3000000e+00 5.9000000e+00 4.0000000e+00 6.2000000e+00 4.4000000e+00 4.5000000e+00 5.2000000e+00 5.4000000e+00 5.9000000e+00 4.2000000e+00 6.0000000e+00 5.2000000e+00 4.8000000e+00 6.6000000e+00 4.7000000e+00 5.9000000e+00 5.2000000e+00 6.8000000e+00 5.8000000e+00 5.7000000e+00 6.0000000e+00 6.8000000e+00 7.0000000e+00 5.7000000e+00 4.2000000e+00 4.6000000e+00 4.4000000e+00 4.8000000e+00 6.6000000e+00 5.0000000e+00 5.3000000e+00 6.4000000e+00 6.3000000e+00 4.6000000e+00 4.9000000e+00 5.1000000e+00 5.7000000e+00 5.0000000e+00 4.0000000e+00 5.0000000e+00 4.7000000e+00 4.9000000e+00 5.5000000e+00 3.5000000e+00 4.9000000e+00 8.1000000e+00 6.7000000e+00 8.7000000e+00 7.4000000e+00 8.1000000e+00 9.9000000e+00 5.8000000e+00 9.1000000e+00 8.4000000e+00 9.0000000e+00 7.0000000e+00 7.5000000e+00 8.0000000e+00 6.8000000e+00 7.1000000e+00 7.4000000e+00 7.4000000e+00 1.0000000e+01 1.0900000e+01 6.9000000e+00 8.3000000e+00 6.3000000e+00 1.0200000e+01 6.9000000e+00 7.8000000e+00 8.4000000e+00 6.6000000e+00 6.4000000e+00 7.9000000e+00 8.2000000e+00 9.2000000e+00 9.7000000e+00 8.0000000e+00 6.7000000e+00 7.1000000e+00 9.7000000e+00 7.5000000e+00 7.2000000e+00 6.2000000e+00 7.9000000e+00 8.2000000e+00 7.8000000e+00 6.7000000e+00 8.4000000e+00 8.2000000e+00 7.8000000e+00 7.3000000e+00 7.3000000e+00 7.1000000e+00 6.4000000e+00 9.0000000e-01 9.0000000e-01 5.0000000e-01 9.0000000e-01 1.1000000e+00 8.0000000e-01 6.0000000e-01 5.0000000e-01 8.0000000e-01 1.3000000e+00 2.0000000e-01 5.0000000e-01 2.0000000e+00 1.1000000e+00 9.0000000e-01 1.2000000e+00 9.0000000e-01 7.0000000e-01 8.0000000e-01 5.0000000e-01 3.0000000e-01 6.5000000e+00 5.8000000e+00 6.8000000e+00 5.1000000e+00 6.4000000e+00 5.3000000e+00 5.9000000e+00 4.0000000e+00 6.2000000e+00 4.4000000e+00 4.5000000e+00 5.2000000e+00 5.4000000e+00 5.9000000e+00 4.2000000e+00 6.0000000e+00 5.2000000e+00 4.8000000e+00 6.6000000e+00 4.7000000e+00 5.9000000e+00 5.2000000e+00 6.8000000e+00 5.8000000e+00 5.7000000e+00 6.0000000e+00 6.8000000e+00 7.0000000e+00 5.7000000e+00 4.2000000e+00 4.6000000e+00 4.4000000e+00 4.8000000e+00 6.6000000e+00 5.0000000e+00 5.3000000e+00 6.4000000e+00 6.3000000e+00 4.6000000e+00 4.9000000e+00 5.1000000e+00 5.7000000e+00 5.0000000e+00 4.0000000e+00 5.0000000e+00 4.7000000e+00 4.9000000e+00 5.5000000e+00 3.5000000e+00 4.9000000e+00 8.1000000e+00 6.7000000e+00 8.7000000e+00 7.4000000e+00 8.1000000e+00 9.9000000e+00 5.8000000e+00 9.1000000e+00 8.4000000e+00 9.2000000e+00 7.0000000e+00 7.5000000e+00 8.0000000e+00 6.8000000e+00 7.1000000e+00 7.4000000e+00 7.4000000e+00 1.0200000e+01 1.0900000e+01 6.9000000e+00 8.3000000e+00 6.3000000e+00 1.0200000e+01 6.9000000e+00 7.8000000e+00 8.4000000e+00 6.6000000e+00 6.4000000e+00 7.9000000e+00 8.2000000e+00 9.2000000e+00 9.9000000e+00 8.0000000e+00 6.7000000e+00 7.1000000e+00 9.7000000e+00 7.5000000e+00 7.2000000e+00 6.2000000e+00 7.9000000e+00 8.2000000e+00 7.8000000e+00 6.7000000e+00 8.4000000e+00 8.2000000e+00 7.8000000e+00 7.3000000e+00 7.3000000e+00 7.1000000e+00 6.4000000e+00 2.0000000e-01 1.2000000e+00 1.6000000e+00 2.0000000e+00 5.0000000e-01 7.0000000e-01 1.4000000e+00 5.0000000e-01 8.0000000e-01 7.0000000e-01 1.0000000e+00 1.5000000e+00 6.0000000e-01 1.0000000e+00 1.5000000e+00 6.0000000e-01 1.0000000e+00 3.0000000e-01 1.2000000e+00 6.0000000e-01 6.6000000e+00 5.9000000e+00 6.9000000e+00 5.2000000e+00 6.5000000e+00 5.4000000e+00 6.2000000e+00 3.5000000e+00 6.3000000e+00 4.5000000e+00 4.2000000e+00 5.3000000e+00 5.5000000e+00 6.0000000e+00 4.3000000e+00 6.1000000e+00 5.3000000e+00 4.9000000e+00 6.7000000e+00 4.8000000e+00 6.0000000e+00 5.3000000e+00 6.9000000e+00 5.9000000e+00 5.8000000e+00 6.1000000e+00 6.9000000e+00 7.1000000e+00 5.8000000e+00 4.3000000e+00 4.7000000e+00 4.5000000e+00 4.9000000e+00 6.7000000e+00 5.1000000e+00 5.8000000e+00 6.5000000e+00 6.4000000e+00 4.7000000e+00 5.0000000e+00 5.2000000e+00 5.8000000e+00 5.1000000e+00 3.7000000e+00 5.1000000e+00 4.8000000e+00 5.0000000e+00 5.6000000e+00 3.4000000e+00 5.0000000e+00 8.4000000e+00 6.8000000e+00 8.8000000e+00 7.5000000e+00 8.2000000e+00 1.0000000e+01 5.3000000e+00 9.2000000e+00 8.5000000e+00 9.7000000e+00 7.1000000e+00 7.6000000e+00 8.1000000e+00 6.9000000e+00 7.2000000e+00 7.5000000e+00 7.5000000e+00 1.0700000e+01 1.1000000e+01 7.0000000e+00 8.4000000e+00 6.4000000e+00 1.0300000e+01 7.0000000e+00 8.1000000e+00 8.5000000e+00 6.7000000e+00 6.5000000e+00 8.0000000e+00 8.3000000e+00 9.3000000e+00 1.0400000e+01 8.1000000e+00 6.8000000e+00 7.2000000e+00 9.8000000e+00 8.0000000e+00 7.3000000e+00 6.3000000e+00 8.0000000e+00 8.3000000e+00 7.9000000e+00 6.8000000e+00 8.5000000e+00 8.5000000e+00 7.9000000e+00 7.4000000e+00 7.4000000e+00 7.6000000e+00 6.5000000e+00 1.2000000e+00 1.6000000e+00 2.0000000e+00 3.0000000e-01 7.0000000e-01 1.4000000e+00 3.0000000e-01 8.0000000e-01 7.0000000e-01 1.0000000e+00 1.5000000e+00 8.0000000e-01 1.0000000e+00 1.5000000e+00 4.0000000e-01 1.0000000e+00 5.0000000e-01 1.2000000e+00 6.0000000e-01 6.6000000e+00 5.9000000e+00 6.7000000e+00 5.0000000e+00 6.3000000e+00 5.2000000e+00 6.2000000e+00 3.3000000e+00 6.1000000e+00 4.3000000e+00 4.0000000e+00 5.1000000e+00 5.3000000e+00 5.8000000e+00 4.1000000e+00 5.9000000e+00 5.1000000e+00 4.7000000e+00 6.5000000e+00 4.6000000e+00 6.0000000e+00 5.1000000e+00 6.7000000e+00 5.7000000e+00 5.6000000e+00 5.9000000e+00 6.7000000e+00 6.9000000e+00 5.6000000e+00 4.1000000e+00 4.5000000e+00 4.3000000e+00 4.7000000e+00 6.5000000e+00 4.9000000e+00 5.8000000e+00 6.3000000e+00 6.2000000e+00 4.5000000e+00 4.8000000e+00 5.0000000e+00 5.6000000e+00 4.9000000e+00 3.5000000e+00 4.9000000e+00 4.6000000e+00 4.8000000e+00 5.4000000e+00 3.2000000e+00 4.8000000e+00 8.4000000e+00 6.6000000e+00 8.6000000e+00 7.3000000e+00 8.0000000e+00 9.8000000e+00 5.1000000e+00 9.0000000e+00 8.3000000e+00 9.7000000e+00 7.1000000e+00 7.4000000e+00 7.9000000e+00 6.7000000e+00 7.0000000e+00 7.5000000e+00 7.3000000e+00 1.0700000e+01 1.0800000e+01 6.8000000e+00 8.4000000e+00 6.2000000e+00 1.0100000e+01 6.8000000e+00 8.1000000e+00 8.5000000e+00 6.5000000e+00 6.3000000e+00 7.8000000e+00 8.1000000e+00 9.1000000e+00 1.0400000e+01 7.9000000e+00 6.6000000e+00 7.0000000e+00 9.6000000e+00 8.0000000e+00 7.1000000e+00 6.1000000e+00 7.8000000e+00 8.1000000e+00 7.7000000e+00 6.6000000e+00 8.5000000e+00 8.5000000e+00 7.7000000e+00 7.2000000e+00 7.2000000e+00 7.6000000e+00 6.3000000e+00 1.2000000e+00 1.2000000e+00 1.1000000e+00 1.1000000e+00 6.0000000e-01 1.1000000e+00 1.8000000e+00 5.0000000e-01 8.0000000e-01 2.3000000e+00 1.6000000e+00 8.0000000e-01 1.1000000e+00 1.2000000e+00 1.0000000e+00 1.3000000e+00 6.0000000e-01 8.0000000e-01 6.0000000e+00 5.3000000e+00 6.3000000e+00 4.6000000e+00 5.9000000e+00 4.8000000e+00 5.4000000e+00 3.9000000e+00 5.7000000e+00 4.3000000e+00 4.4000000e+00 4.7000000e+00 4.9000000e+00 5.4000000e+00 3.7000000e+00 5.5000000e+00 4.7000000e+00 4.3000000e+00 6.1000000e+00 4.2000000e+00 5.4000000e+00 4.7000000e+00 6.3000000e+00 5.3000000e+00 5.2000000e+00 5.5000000e+00 6.3000000e+00 6.5000000e+00 5.2000000e+00 3.7000000e+00 4.1000000e+00 3.9000000e+00 4.3000000e+00 6.1000000e+00 4.5000000e+00 4.8000000e+00 5.9000000e+00 5.8000000e+00 4.1000000e+00 4.4000000e+00 4.6000000e+00 5.2000000e+00 4.5000000e+00 3.9000000e+00 4.5000000e+00 4.2000000e+00 4.4000000e+00 5.0000000e+00 3.4000000e+00 4.4000000e+00 7.6000000e+00 6.2000000e+00 8.2000000e+00 6.9000000e+00 7.6000000e+00 9.4000000e+00 5.7000000e+00 8.6000000e+00 7.9000000e+00 8.7000000e+00 6.5000000e+00 7.0000000e+00 7.5000000e+00 6.3000000e+00 6.6000000e+00 6.9000000e+00 6.9000000e+00 9.7000000e+00 1.0400000e+01 6.4000000e+00 7.8000000e+00 5.8000000e+00 9.7000000e+00 6.4000000e+00 7.3000000e+00 7.9000000e+00 6.1000000e+00 5.9000000e+00 7.4000000e+00 7.7000000e+00 8.7000000e+00 9.4000000e+00 7.5000000e+00 6.2000000e+00 6.6000000e+00 9.2000000e+00 7.0000000e+00 6.7000000e+00 5.7000000e+00 7.4000000e+00 7.7000000e+00 7.3000000e+00 6.2000000e+00 7.9000000e+00 7.7000000e+00 7.3000000e+00 6.8000000e+00 6.8000000e+00 6.6000000e+00 5.9000000e+00 6.0000000e-01 1.3000000e+00 1.5000000e+00 1.2000000e+00 1.3000000e+00 2.2000000e+00 9.0000000e-01 1.2000000e+00 2.9000000e+00 2.0000000e+00 1.4000000e+00 1.1000000e+00 1.8000000e+00 6.0000000e-01 1.7000000e+00 6.0000000e-01 1.2000000e+00 7.2000000e+00 6.5000000e+00 7.5000000e+00 5.8000000e+00 7.1000000e+00 6.0000000e+00 6.6000000e+00 4.7000000e+00 6.9000000e+00 5.1000000e+00 5.2000000e+00 5.9000000e+00 6.1000000e+00 6.6000000e+00 4.9000000e+00 6.7000000e+00 5.9000000e+00 5.5000000e+00 7.3000000e+00 5.4000000e+00 6.6000000e+00 5.9000000e+00 7.5000000e+00 6.5000000e+00 6.4000000e+00 6.7000000e+00 7.5000000e+00 7.7000000e+00 6.4000000e+00 4.9000000e+00 5.3000000e+00 5.1000000e+00 5.5000000e+00 7.3000000e+00 5.7000000e+00 6.0000000e+00 7.1000000e+00 7.0000000e+00 5.3000000e+00 5.6000000e+00 5.8000000e+00 6.4000000e+00 5.7000000e+00 4.7000000e+00 5.7000000e+00 5.4000000e+00 5.6000000e+00 6.2000000e+00 4.2000000e+00 5.6000000e+00 8.8000000e+00 7.4000000e+00 9.4000000e+00 8.1000000e+00 8.8000000e+00 1.0600000e+01 6.5000000e+00 9.8000000e+00 9.1000000e+00 9.5000000e+00 7.7000000e+00 8.2000000e+00 8.7000000e+00 7.5000000e+00 7.8000000e+00 8.1000000e+00 8.1000000e+00 1.0100000e+01 1.1600000e+01 7.6000000e+00 9.0000000e+00 7.0000000e+00 1.0900000e+01 7.6000000e+00 8.5000000e+00 9.1000000e+00 7.3000000e+00 7.1000000e+00 8.6000000e+00 8.9000000e+00 9.9000000e+00 9.8000000e+00 8.7000000e+00 7.4000000e+00 7.8000000e+00 1.0400000e+01 8.2000000e+00 7.9000000e+00 6.9000000e+00 8.6000000e+00 8.9000000e+00 8.5000000e+00 7.4000000e+00 9.1000000e+00 8.9000000e+00 8.5000000e+00 8.0000000e+00 8.0000000e+00 7.8000000e+00 7.1000000e+00 1.9000000e+00 1.7000000e+00 8.0000000e-01 1.9000000e+00 2.4000000e+00 1.3000000e+00 1.4000000e+00 3.1000000e+00 2.2000000e+00 1.8000000e+00 1.5000000e+00 2.0000000e+00 1.0000000e+00 1.9000000e+00 8.0000000e-01 1.4000000e+00 7.0000000e+00 6.3000000e+00 7.3000000e+00 5.6000000e+00 6.9000000e+00 5.8000000e+00 6.4000000e+00 5.1000000e+00 6.7000000e+00 5.5000000e+00 5.6000000e+00 5.7000000e+00 5.9000000e+00 6.4000000e+00 4.7000000e+00 6.5000000e+00 5.7000000e+00 5.3000000e+00 7.1000000e+00 5.2000000e+00 6.4000000e+00 5.7000000e+00 7.3000000e+00 6.3000000e+00 6.2000000e+00 6.5000000e+00 7.3000000e+00 7.5000000e+00 6.2000000e+00 4.7000000e+00 5.1000000e+00 4.9000000e+00 5.3000000e+00 7.1000000e+00 5.7000000e+00 5.8000000e+00 6.9000000e+00 6.8000000e+00 5.1000000e+00 5.4000000e+00 5.6000000e+00 6.2000000e+00 5.5000000e+00 5.1000000e+00 5.5000000e+00 5.2000000e+00 5.4000000e+00 6.0000000e+00 4.6000000e+00 5.4000000e+00 8.6000000e+00 7.2000000e+00 9.2000000e+00 7.9000000e+00 8.6000000e+00 1.0400000e+01 6.9000000e+00 9.6000000e+00 8.9000000e+00 9.3000000e+00 7.5000000e+00 8.0000000e+00 8.5000000e+00 7.3000000e+00 7.6000000e+00 7.9000000e+00 7.9000000e+00 9.9000000e+00 1.1400000e+01 7.4000000e+00 8.8000000e+00 6.8000000e+00 1.0700000e+01 7.4000000e+00 8.3000000e+00 8.9000000e+00 7.1000000e+00 6.9000000e+00 8.4000000e+00 8.7000000e+00 9.7000000e+00 9.6000000e+00 8.5000000e+00 7.2000000e+00 7.6000000e+00 1.0200000e+01 8.0000000e+00 7.7000000e+00 6.7000000e+00 8.4000000e+00 8.7000000e+00 8.3000000e+00 7.2000000e+00 8.9000000e+00 8.7000000e+00 8.3000000e+00 7.8000000e+00 7.8000000e+00 7.6000000e+00 6.9000000e+00 6.0000000e-01 1.3000000e+00 0.0000000e+00 9.0000000e-01 6.0000000e-01 9.0000000e-01 1.6000000e+00 9.0000000e-01 1.1000000e+00 1.6000000e+00 5.0000000e-01 1.1000000e+00 6.0000000e-01 1.1000000e+00 5.0000000e-01 6.7000000e+00 6.0000000e+00 6.8000000e+00 5.1000000e+00 6.4000000e+00 5.3000000e+00 6.3000000e+00 3.4000000e+00 6.2000000e+00 4.4000000e+00 4.1000000e+00 5.2000000e+00 5.4000000e+00 5.9000000e+00 4.2000000e+00 6.0000000e+00 5.2000000e+00 4.8000000e+00 6.6000000e+00 4.7000000e+00 6.1000000e+00 5.2000000e+00 6.8000000e+00 5.8000000e+00 5.7000000e+00 6.0000000e+00 6.8000000e+00 7.0000000e+00 5.7000000e+00 4.2000000e+00 4.6000000e+00 4.4000000e+00 4.8000000e+00 6.6000000e+00 5.0000000e+00 5.9000000e+00 6.4000000e+00 6.3000000e+00 4.6000000e+00 4.9000000e+00 5.1000000e+00 5.7000000e+00 5.0000000e+00 3.6000000e+00 5.0000000e+00 4.7000000e+00 4.9000000e+00 5.5000000e+00 3.3000000e+00 4.9000000e+00 8.5000000e+00 6.7000000e+00 8.7000000e+00 7.4000000e+00 8.1000000e+00 9.9000000e+00 5.2000000e+00 9.1000000e+00 8.4000000e+00 9.8000000e+00 7.2000000e+00 7.5000000e+00 8.0000000e+00 6.8000000e+00 7.1000000e+00 7.6000000e+00 7.4000000e+00 1.0800000e+01 1.0900000e+01 6.9000000e+00 8.5000000e+00 6.3000000e+00 1.0200000e+01 6.9000000e+00 8.2000000e+00 8.6000000e+00 6.6000000e+00 6.4000000e+00 7.9000000e+00 8.2000000e+00 9.2000000e+00 1.0500000e+01 8.0000000e+00 6.7000000e+00 7.1000000e+00 9.7000000e+00 8.1000000e+00 7.2000000e+00 6.2000000e+00 7.9000000e+00 8.2000000e+00 7.8000000e+00 6.7000000e+00 8.6000000e+00 8.6000000e+00 7.8000000e+00 7.3000000e+00 7.3000000e+00 7.7000000e+00 6.4000000e+00 9.0000000e-01 6.0000000e-01 9.0000000e-01 6.0000000e-01 5.0000000e-01 1.6000000e+00 7.0000000e-01 1.1000000e+00 1.6000000e+00 7.0000000e-01 1.1000000e+00 6.0000000e-01 1.1000000e+00 3.0000000e-01 6.7000000e+00 6.0000000e+00 7.0000000e+00 5.3000000e+00 6.6000000e+00 5.5000000e+00 6.3000000e+00 3.8000000e+00 6.4000000e+00 4.6000000e+00 4.3000000e+00 5.4000000e+00 5.6000000e+00 6.1000000e+00 4.4000000e+00 6.2000000e+00 5.4000000e+00 5.0000000e+00 6.8000000e+00 4.9000000e+00 6.1000000e+00 5.4000000e+00 7.0000000e+00 6.0000000e+00 5.9000000e+00 6.2000000e+00 7.0000000e+00 7.2000000e+00 5.9000000e+00 4.4000000e+00 4.8000000e+00 4.6000000e+00 5.0000000e+00 6.8000000e+00 5.2000000e+00 5.9000000e+00 6.6000000e+00 6.5000000e+00 4.8000000e+00 5.1000000e+00 5.3000000e+00 5.9000000e+00 5.2000000e+00 3.8000000e+00 5.2000000e+00 4.9000000e+00 5.1000000e+00 5.7000000e+00 3.5000000e+00 5.1000000e+00 8.5000000e+00 6.9000000e+00 8.9000000e+00 7.6000000e+00 8.3000000e+00 1.0100000e+01 5.6000000e+00 9.3000000e+00 8.6000000e+00 9.8000000e+00 7.2000000e+00 7.7000000e+00 8.2000000e+00 7.0000000e+00 7.3000000e+00 7.6000000e+00 7.6000000e+00 1.0800000e+01 1.1100000e+01 7.1000000e+00 8.5000000e+00 6.5000000e+00 1.0400000e+01 7.1000000e+00 8.2000000e+00 8.6000000e+00 6.8000000e+00 6.6000000e+00 8.1000000e+00 8.4000000e+00 9.4000000e+00 1.0500000e+01 8.2000000e+00 6.9000000e+00 7.3000000e+00 9.9000000e+00 8.1000000e+00 7.4000000e+00 6.4000000e+00 8.1000000e+00 8.4000000e+00 8.0000000e+00 6.9000000e+00 8.6000000e+00 8.6000000e+00 8.0000000e+00 7.5000000e+00 7.5000000e+00 7.7000000e+00 6.6000000e+00 1.3000000e+00 1.6000000e+00 7.0000000e-01 6.0000000e-01 2.3000000e+00 1.4000000e+00 1.2000000e+00 1.5000000e+00 1.4000000e+00 1.0000000e+00 1.3000000e+00 6.0000000e-01 8.0000000e-01 6.4000000e+00 5.7000000e+00 6.7000000e+00 5.0000000e+00 6.3000000e+00 5.2000000e+00 5.8000000e+00 4.5000000e+00 6.1000000e+00 4.9000000e+00 5.0000000e+00 5.1000000e+00 5.3000000e+00 5.8000000e+00 4.1000000e+00 5.9000000e+00 5.1000000e+00 4.7000000e+00 6.5000000e+00 4.6000000e+00 5.8000000e+00 5.1000000e+00 6.7000000e+00 5.7000000e+00 5.6000000e+00 5.9000000e+00 6.7000000e+00 6.9000000e+00 5.6000000e+00 4.1000000e+00 4.5000000e+00 4.3000000e+00 4.7000000e+00 6.5000000e+00 5.1000000e+00 5.2000000e+00 6.3000000e+00 6.2000000e+00 4.5000000e+00 4.8000000e+00 5.0000000e+00 5.6000000e+00 4.9000000e+00 4.5000000e+00 4.9000000e+00 4.6000000e+00 4.8000000e+00 5.4000000e+00 4.0000000e+00 4.8000000e+00 8.0000000e+00 6.6000000e+00 8.6000000e+00 7.3000000e+00 8.0000000e+00 9.8000000e+00 6.3000000e+00 9.0000000e+00 8.3000000e+00 8.9000000e+00 6.9000000e+00 7.4000000e+00 7.9000000e+00 6.7000000e+00 7.0000000e+00 7.3000000e+00 7.3000000e+00 9.9000000e+00 1.0800000e+01 6.8000000e+00 8.2000000e+00 6.2000000e+00 1.0100000e+01 6.8000000e+00 7.7000000e+00 8.3000000e+00 6.5000000e+00 6.3000000e+00 7.8000000e+00 8.1000000e+00 9.1000000e+00 9.6000000e+00 7.9000000e+00 6.6000000e+00 7.0000000e+00 9.6000000e+00 7.4000000e+00 7.1000000e+00 6.1000000e+00 7.8000000e+00 8.1000000e+00 7.7000000e+00 6.6000000e+00 8.3000000e+00 8.1000000e+00 7.7000000e+00 7.2000000e+00 7.2000000e+00 7.0000000e+00 6.3000000e+00 9.0000000e-01 6.0000000e-01 9.0000000e-01 1.6000000e+00 9.0000000e-01 1.1000000e+00 1.6000000e+00 5.0000000e-01 1.1000000e+00 6.0000000e-01 1.1000000e+00 5.0000000e-01 6.7000000e+00 6.0000000e+00 6.8000000e+00 5.1000000e+00 6.4000000e+00 5.3000000e+00 6.3000000e+00 3.4000000e+00 6.2000000e+00 4.4000000e+00 4.1000000e+00 5.2000000e+00 5.4000000e+00 5.9000000e+00 4.2000000e+00 6.0000000e+00 5.2000000e+00 4.8000000e+00 6.6000000e+00 4.7000000e+00 6.1000000e+00 5.2000000e+00 6.8000000e+00 5.8000000e+00 5.7000000e+00 6.0000000e+00 6.8000000e+00 7.0000000e+00 5.7000000e+00 4.2000000e+00 4.6000000e+00 4.4000000e+00 4.8000000e+00 6.6000000e+00 5.0000000e+00 5.9000000e+00 6.4000000e+00 6.3000000e+00 4.6000000e+00 4.9000000e+00 5.1000000e+00 5.7000000e+00 5.0000000e+00 3.6000000e+00 5.0000000e+00 4.7000000e+00 4.9000000e+00 5.5000000e+00 3.3000000e+00 4.9000000e+00 8.5000000e+00 6.7000000e+00 8.7000000e+00 7.4000000e+00 8.1000000e+00 9.9000000e+00 5.2000000e+00 9.1000000e+00 8.4000000e+00 9.8000000e+00 7.2000000e+00 7.5000000e+00 8.0000000e+00 6.8000000e+00 7.1000000e+00 7.6000000e+00 7.4000000e+00 1.0800000e+01 1.0900000e+01 6.9000000e+00 8.5000000e+00 6.3000000e+00 1.0200000e+01 6.9000000e+00 8.2000000e+00 8.6000000e+00 6.6000000e+00 6.4000000e+00 7.9000000e+00 8.2000000e+00 9.2000000e+00 1.0500000e+01 8.0000000e+00 6.7000000e+00 7.1000000e+00 9.7000000e+00 8.1000000e+00 7.2000000e+00 6.2000000e+00 7.9000000e+00 8.2000000e+00 7.8000000e+00 6.7000000e+00 8.6000000e+00 8.6000000e+00 7.8000000e+00 7.3000000e+00 7.3000000e+00 7.7000000e+00 6.4000000e+00 1.3000000e+00 1.2000000e+00 9.0000000e-01 2.0000000e-01 1.8000000e+00 2.3000000e+00 6.0000000e-01 1.8000000e+00 5.0000000e-01 1.8000000e+00 1.0000000e+00 7.4000000e+00 6.7000000e+00 7.5000000e+00 5.6000000e+00 6.9000000e+00 5.8000000e+00 7.0000000e+00 3.9000000e+00 6.7000000e+00 4.9000000e+00 4.6000000e+00 5.7000000e+00 5.9000000e+00 6.4000000e+00 4.7000000e+00 6.7000000e+00 5.7000000e+00 5.3000000e+00 7.1000000e+00 5.2000000e+00 6.8000000e+00 5.7000000e+00 7.3000000e+00 6.3000000e+00 6.2000000e+00 6.5000000e+00 7.3000000e+00 7.5000000e+00 6.2000000e+00 4.7000000e+00 5.1000000e+00 4.9000000e+00 5.3000000e+00 7.1000000e+00 5.5000000e+00 6.6000000e+00 7.1000000e+00 6.8000000e+00 5.1000000e+00 5.4000000e+00 5.6000000e+00 6.2000000e+00 5.5000000e+00 4.1000000e+00 5.5000000e+00 5.2000000e+00 5.4000000e+00 6.0000000e+00 3.8000000e+00 5.4000000e+00 9.2000000e+00 7.2000000e+00 9.2000000e+00 7.9000000e+00 8.6000000e+00 1.0400000e+01 5.7000000e+00 9.6000000e+00 8.9000000e+00 1.0500000e+01 7.9000000e+00 8.0000000e+00 8.5000000e+00 7.3000000e+00 7.6000000e+00 8.3000000e+00 7.9000000e+00 1.1500000e+01 1.1400000e+01 7.4000000e+00 9.2000000e+00 6.8000000e+00 1.0700000e+01 7.4000000e+00 8.9000000e+00 9.3000000e+00 7.1000000e+00 6.9000000e+00 8.4000000e+00 8.7000000e+00 9.7000000e+00 1.1200000e+01 8.5000000e+00 7.2000000e+00 7.6000000e+00 1.0200000e+01 8.8000000e+00 7.9000000e+00 6.7000000e+00 8.6000000e+00 8.9000000e+00 8.5000000e+00 7.2000000e+00 9.3000000e+00 9.3000000e+00 8.3000000e+00 7.8000000e+00 7.8000000e+00 8.4000000e+00 6.9000000e+00 5.0000000e-01 2.0000000e+00 1.1000000e+00 7.0000000e-01 1.0000000e+00 9.0000000e-01 5.0000000e-01 8.0000000e-01 5.0000000e-01 3.0000000e-01 6.5000000e+00 5.8000000e+00 6.8000000e+00 5.1000000e+00 6.4000000e+00 5.3000000e+00 5.9000000e+00 3.8000000e+00 6.2000000e+00 4.4000000e+00 4.3000000e+00 5.2000000e+00 5.4000000e+00 5.9000000e+00 4.2000000e+00 6.0000000e+00 5.2000000e+00 4.8000000e+00 6.6000000e+00 4.7000000e+00 5.9000000e+00 5.2000000e+00 6.8000000e+00 5.8000000e+00 5.7000000e+00 6.0000000e+00 6.8000000e+00 7.0000000e+00 5.7000000e+00 4.2000000e+00 4.6000000e+00 4.4000000e+00 4.8000000e+00 6.6000000e+00 5.0000000e+00 5.3000000e+00 6.4000000e+00 6.3000000e+00 4.6000000e+00 4.9000000e+00 5.1000000e+00 5.7000000e+00 5.0000000e+00 3.8000000e+00 5.0000000e+00 4.7000000e+00 4.9000000e+00 5.5000000e+00 3.3000000e+00 4.9000000e+00 8.1000000e+00 6.7000000e+00 8.7000000e+00 7.4000000e+00 8.1000000e+00 9.9000000e+00 5.6000000e+00 9.1000000e+00 8.4000000e+00 9.2000000e+00 7.0000000e+00 7.5000000e+00 8.0000000e+00 6.8000000e+00 7.1000000e+00 7.4000000e+00 7.4000000e+00 1.0200000e+01 1.0900000e+01 6.9000000e+00 8.3000000e+00 6.3000000e+00 1.0200000e+01 6.9000000e+00 7.8000000e+00 8.4000000e+00 6.6000000e+00 6.4000000e+00 7.9000000e+00 8.2000000e+00 9.2000000e+00 9.9000000e+00 8.0000000e+00 6.7000000e+00 7.1000000e+00 9.7000000e+00 7.5000000e+00 7.2000000e+00 6.2000000e+00 7.9000000e+00 8.2000000e+00 7.8000000e+00 6.7000000e+00 8.4000000e+00 8.2000000e+00 7.8000000e+00 7.3000000e+00 7.3000000e+00 7.1000000e+00 6.4000000e+00 1.7000000e+00 1.0000000e+00 6.0000000e-01 1.1000000e+00 8.0000000e-01 8.0000000e-01 9.0000000e-01 8.0000000e-01 4.0000000e-01 6.8000000e+00 6.1000000e+00 7.1000000e+00 5.4000000e+00 6.7000000e+00 5.6000000e+00 6.2000000e+00 3.9000000e+00 6.5000000e+00 4.7000000e+00 4.4000000e+00 5.5000000e+00 5.7000000e+00 6.2000000e+00 4.5000000e+00 6.3000000e+00 5.5000000e+00 5.1000000e+00 6.9000000e+00 5.0000000e+00 6.2000000e+00 5.5000000e+00 7.1000000e+00 6.1000000e+00 6.0000000e+00 6.3000000e+00 7.1000000e+00 7.3000000e+00 6.0000000e+00 4.5000000e+00 4.9000000e+00 4.7000000e+00 5.1000000e+00 6.9000000e+00 5.3000000e+00 5.6000000e+00 6.7000000e+00 6.6000000e+00 4.9000000e+00 5.2000000e+00 5.4000000e+00 6.0000000e+00 5.3000000e+00 3.9000000e+00 5.3000000e+00 5.0000000e+00 5.2000000e+00 5.8000000e+00 3.6000000e+00 5.2000000e+00 8.4000000e+00 7.0000000e+00 9.0000000e+00 7.7000000e+00 8.4000000e+00 1.0200000e+01 5.7000000e+00 9.4000000e+00 8.7000000e+00 9.3000000e+00 7.3000000e+00 7.8000000e+00 8.3000000e+00 7.1000000e+00 7.4000000e+00 7.7000000e+00 7.7000000e+00 1.0300000e+01 1.1200000e+01 7.2000000e+00 8.6000000e+00 6.6000000e+00 1.0500000e+01 7.2000000e+00 8.1000000e+00 8.7000000e+00 6.9000000e+00 6.7000000e+00 8.2000000e+00 8.5000000e+00 9.5000000e+00 1.0000000e+01 8.3000000e+00 7.0000000e+00 7.4000000e+00 1.0000000e+01 7.8000000e+00 7.5000000e+00 6.5000000e+00 8.2000000e+00 8.5000000e+00 8.1000000e+00 7.0000000e+00 8.7000000e+00 8.5000000e+00 8.1000000e+00 7.6000000e+00 7.6000000e+00 7.4000000e+00 6.7000000e+00 1.1000000e+00 2.3000000e+00 2.8000000e+00 1.1000000e+00 2.5000000e+00 1.2000000e+00 2.5000000e+00 1.7000000e+00 7.9000000e+00 7.2000000e+00 8.0000000e+00 4.7000000e+00 7.0000000e+00 5.9000000e+00 7.5000000e+00 3.2000000e+00 7.0000000e+00 4.8000000e+00 3.7000000e+00 6.2000000e+00 5.0000000e+00 6.7000000e+00 5.0000000e+00 7.2000000e+00 6.2000000e+00 5.2000000e+00 6.2000000e+00 4.7000000e+00 7.3000000e+00 5.8000000e+00 6.8000000e+00 6.4000000e+00 6.5000000e+00 7.0000000e+00 7.4000000e+00 8.0000000e+00 6.5000000e+00 4.4000000e+00 4.4000000e+00 4.2000000e+00 5.2000000e+00 7.0000000e+00 6.0000000e+00 7.1000000e+00 7.6000000e+00 5.9000000e+00 5.6000000e+00 4.9000000e+00 5.3000000e+00 6.7000000e+00 5.2000000e+00 3.2000000e+00 5.4000000e+00 5.7000000e+00 5.7000000e+00 6.3000000e+00 3.3000000e+00 5.5000000e+00 9.7000000e+00 7.1000000e+00 9.7000000e+00 8.2000000e+00 9.1000000e+00 1.0900000e+01 5.2000000e+00 9.9000000e+00 8.4000000e+00 1.1000000e+01 8.4000000e+00 7.9000000e+00 9.0000000e+00 6.8000000e+00 7.7000000e+00 8.8000000e+00 8.4000000e+00 1.2000000e+01 1.1100000e+01 6.5000000e+00 9.7000000e+00 6.9000000e+00 1.0800000e+01 7.3000000e+00 9.4000000e+00 9.8000000e+00 7.2000000e+00 7.4000000e+00 8.5000000e+00 9.2000000e+00 9.8000000e+00 1.1700000e+01 8.6000000e+00 7.3000000e+00 7.3000000e+00 1.0700000e+01 9.3000000e+00 8.4000000e+00 7.2000000e+00 9.1000000e+00 9.4000000e+00 9.0000000e+00 7.1000000e+00 9.8000000e+00 9.8000000e+00 8.8000000e+00 7.3000000e+00 8.3000000e+00 8.9000000e+00 7.4000000e+00 1.6000000e+00 2.1000000e+00 8.0000000e-01 1.6000000e+00 3.0000000e-01 1.6000000e+00 8.0000000e-01 7.2000000e+00 6.5000000e+00 7.5000000e+00 5.8000000e+00 7.1000000e+00 6.0000000e+00 6.8000000e+00 4.1000000e+00 6.9000000e+00 5.1000000e+00 4.8000000e+00 5.9000000e+00 6.1000000e+00 6.6000000e+00 4.9000000e+00 6.7000000e+00 5.9000000e+00 5.5000000e+00 7.3000000e+00 5.4000000e+00 6.6000000e+00 5.9000000e+00 7.5000000e+00 6.5000000e+00 6.4000000e+00 6.7000000e+00 7.5000000e+00 7.7000000e+00 6.4000000e+00 4.9000000e+00 5.3000000e+00 5.1000000e+00 5.5000000e+00 7.3000000e+00 5.7000000e+00 6.4000000e+00 7.1000000e+00 7.0000000e+00 5.3000000e+00 5.6000000e+00 5.8000000e+00 6.4000000e+00 5.7000000e+00 4.3000000e+00 5.7000000e+00 5.4000000e+00 5.6000000e+00 6.2000000e+00 4.0000000e+00 5.6000000e+00 9.0000000e+00 7.4000000e+00 9.4000000e+00 8.1000000e+00 8.8000000e+00 1.0600000e+01 5.9000000e+00 9.8000000e+00 9.1000000e+00 1.0300000e+01 7.7000000e+00 8.2000000e+00 8.7000000e+00 7.5000000e+00 7.8000000e+00 8.1000000e+00 8.1000000e+00 1.1300000e+01 1.1600000e+01 7.6000000e+00 9.0000000e+00 7.0000000e+00 1.0900000e+01 7.6000000e+00 8.7000000e+00 9.1000000e+00 7.3000000e+00 7.1000000e+00 8.6000000e+00 8.9000000e+00 9.9000000e+00 1.1000000e+01 8.7000000e+00 7.4000000e+00 7.8000000e+00 1.0400000e+01 8.6000000e+00 7.9000000e+00 6.9000000e+00 8.6000000e+00 8.9000000e+00 8.5000000e+00 7.4000000e+00 9.1000000e+00 9.1000000e+00 8.5000000e+00 8.0000000e+00 8.0000000e+00 8.2000000e+00 7.1000000e+00 9.0000000e-01 1.2000000e+00 8.0000000e-01 1.3000000e+00 1.0000000e+00 8.0000000e-01 6.2000000e+00 5.5000000e+00 6.5000000e+00 4.8000000e+00 6.1000000e+00 5.0000000e+00 5.6000000e+00 3.3000000e+00 5.9000000e+00 4.1000000e+00 3.8000000e+00 4.9000000e+00 5.1000000e+00 5.6000000e+00 3.9000000e+00 5.7000000e+00 4.9000000e+00 4.5000000e+00 6.3000000e+00 4.4000000e+00 5.6000000e+00 4.9000000e+00 6.5000000e+00 5.5000000e+00 5.4000000e+00 5.7000000e+00 6.5000000e+00 6.7000000e+00 5.4000000e+00 3.9000000e+00 4.3000000e+00 4.1000000e+00 4.5000000e+00 6.3000000e+00 4.7000000e+00 5.0000000e+00 6.1000000e+00 6.0000000e+00 4.3000000e+00 4.6000000e+00 4.8000000e+00 5.4000000e+00 4.7000000e+00 3.3000000e+00 4.7000000e+00 4.4000000e+00 4.6000000e+00 5.2000000e+00 3.0000000e+00 4.6000000e+00 7.8000000e+00 6.4000000e+00 8.4000000e+00 7.1000000e+00 7.8000000e+00 9.6000000e+00 5.1000000e+00 8.8000000e+00 8.1000000e+00 8.7000000e+00 6.7000000e+00 7.2000000e+00 7.7000000e+00 6.5000000e+00 6.8000000e+00 7.1000000e+00 7.1000000e+00 9.7000000e+00 1.0600000e+01 6.6000000e+00 8.0000000e+00 6.0000000e+00 9.9000000e+00 6.6000000e+00 7.5000000e+00 8.1000000e+00 6.3000000e+00 6.1000000e+00 7.6000000e+00 7.9000000e+00 8.9000000e+00 9.4000000e+00 7.7000000e+00 6.4000000e+00 6.8000000e+00 9.4000000e+00 7.2000000e+00 6.9000000e+00 5.9000000e+00 7.6000000e+00 7.9000000e+00 7.5000000e+00 6.4000000e+00 8.1000000e+00 7.9000000e+00 7.5000000e+00 7.0000000e+00 7.0000000e+00 6.8000000e+00 6.1000000e+00 1.7000000e+00 5.0000000e-01 1.8000000e+00 9.0000000e-01 1.3000000e+00 6.3000000e+00 5.6000000e+00 6.6000000e+00 4.9000000e+00 6.2000000e+00 5.1000000e+00 5.7000000e+00 3.6000000e+00 6.0000000e+00 4.2000000e+00 4.1000000e+00 5.0000000e+00 5.2000000e+00 5.7000000e+00 4.0000000e+00 5.8000000e+00 5.0000000e+00 4.6000000e+00 6.4000000e+00 4.5000000e+00 5.7000000e+00 5.0000000e+00 6.6000000e+00 5.6000000e+00 5.5000000e+00 5.8000000e+00 6.6000000e+00 6.8000000e+00 5.5000000e+00 4.0000000e+00 4.4000000e+00 4.2000000e+00 4.6000000e+00 6.4000000e+00 4.8000000e+00 5.1000000e+00 6.2000000e+00 6.1000000e+00 4.4000000e+00 4.7000000e+00 4.9000000e+00 5.5000000e+00 4.8000000e+00 3.6000000e+00 4.8000000e+00 4.5000000e+00 4.7000000e+00 5.3000000e+00 3.1000000e+00 4.7000000e+00 7.9000000e+00 6.5000000e+00 8.5000000e+00 7.2000000e+00 7.9000000e+00 9.7000000e+00 5.4000000e+00 8.9000000e+00 8.2000000e+00 8.6000000e+00 6.8000000e+00 7.3000000e+00 7.8000000e+00 6.6000000e+00 6.9000000e+00 7.2000000e+00 7.2000000e+00 9.2000000e+00 1.0700000e+01 6.7000000e+00 8.1000000e+00 6.1000000e+00 1.0000000e+01 6.7000000e+00 7.6000000e+00 8.2000000e+00 6.4000000e+00 6.2000000e+00 7.7000000e+00 8.0000000e+00 9.0000000e+00 8.9000000e+00 7.8000000e+00 6.5000000e+00 6.9000000e+00 9.5000000e+00 7.3000000e+00 7.0000000e+00 6.0000000e+00 7.7000000e+00 8.0000000e+00 7.6000000e+00 6.5000000e+00 8.2000000e+00 8.0000000e+00 7.6000000e+00 7.1000000e+00 7.1000000e+00 6.9000000e+00 6.2000000e+00 1.4000000e+00 5.0000000e-01 1.4000000e+00 6.0000000e-01 6.8000000e+00 6.1000000e+00 6.9000000e+00 5.0000000e+00 6.3000000e+00 5.2000000e+00 6.4000000e+00 3.3000000e+00 6.1000000e+00 4.3000000e+00 4.0000000e+00 5.1000000e+00 5.3000000e+00 5.8000000e+00 4.1000000e+00 6.1000000e+00 5.1000000e+00 4.7000000e+00 6.5000000e+00 4.6000000e+00 6.2000000e+00 5.1000000e+00 6.7000000e+00 5.7000000e+00 5.6000000e+00 5.9000000e+00 6.7000000e+00 6.9000000e+00 5.6000000e+00 4.1000000e+00 4.5000000e+00 4.3000000e+00 4.7000000e+00 6.5000000e+00 4.9000000e+00 6.0000000e+00 6.5000000e+00 6.2000000e+00 4.5000000e+00 4.8000000e+00 5.0000000e+00 5.6000000e+00 4.9000000e+00 3.5000000e+00 4.9000000e+00 4.6000000e+00 4.8000000e+00 5.4000000e+00 3.2000000e+00 4.8000000e+00 8.6000000e+00 6.6000000e+00 8.6000000e+00 7.3000000e+00 8.0000000e+00 9.8000000e+00 5.1000000e+00 9.0000000e+00 8.3000000e+00 9.9000000e+00 7.3000000e+00 7.4000000e+00 7.9000000e+00 6.7000000e+00 7.0000000e+00 7.7000000e+00 7.3000000e+00 1.0900000e+01 1.0800000e+01 6.8000000e+00 8.6000000e+00 6.2000000e+00 1.0100000e+01 6.8000000e+00 8.3000000e+00 8.7000000e+00 6.5000000e+00 6.3000000e+00 7.8000000e+00 8.1000000e+00 9.1000000e+00 1.0600000e+01 7.9000000e+00 6.6000000e+00 7.0000000e+00 9.6000000e+00 8.2000000e+00 7.3000000e+00 6.1000000e+00 8.0000000e+00 8.3000000e+00 7.9000000e+00 6.6000000e+00 8.7000000e+00 8.7000000e+00 7.7000000e+00 7.2000000e+00 7.2000000e+00 7.8000000e+00 6.3000000e+00 1.3000000e+00 4.0000000e-01 8.0000000e-01 6.8000000e+00 6.1000000e+00 7.1000000e+00 5.4000000e+00 6.7000000e+00 5.6000000e+00 6.2000000e+00 4.1000000e+00 6.5000000e+00 4.7000000e+00 4.6000000e+00 5.5000000e+00 5.7000000e+00 6.2000000e+00 4.5000000e+00 6.3000000e+00 5.5000000e+00 5.1000000e+00 6.9000000e+00 5.0000000e+00 6.2000000e+00 5.5000000e+00 7.1000000e+00 6.1000000e+00 6.0000000e+00 6.3000000e+00 7.1000000e+00 7.3000000e+00 6.0000000e+00 4.5000000e+00 4.9000000e+00 4.7000000e+00 5.1000000e+00 6.9000000e+00 5.3000000e+00 5.6000000e+00 6.7000000e+00 6.6000000e+00 4.9000000e+00 5.2000000e+00 5.4000000e+00 6.0000000e+00 5.3000000e+00 4.1000000e+00 5.3000000e+00 5.0000000e+00 5.2000000e+00 5.8000000e+00 3.6000000e+00 5.2000000e+00 8.4000000e+00 7.0000000e+00 9.0000000e+00 7.7000000e+00 8.4000000e+00 1.0200000e+01 5.9000000e+00 9.4000000e+00 8.7000000e+00 9.1000000e+00 7.3000000e+00 7.8000000e+00 8.3000000e+00 7.1000000e+00 7.4000000e+00 7.7000000e+00 7.7000000e+00 9.7000000e+00 1.1200000e+01 7.2000000e+00 8.6000000e+00 6.6000000e+00 1.0500000e+01 7.2000000e+00 8.1000000e+00 8.7000000e+00 6.9000000e+00 6.7000000e+00 8.2000000e+00 8.5000000e+00 9.5000000e+00 9.4000000e+00 8.3000000e+00 7.0000000e+00 7.4000000e+00 1.0000000e+01 7.8000000e+00 7.5000000e+00 6.5000000e+00 8.2000000e+00 8.5000000e+00 8.1000000e+00 7.0000000e+00 8.7000000e+00 8.5000000e+00 8.1000000e+00 7.6000000e+00 7.6000000e+00 7.4000000e+00 6.7000000e+00 1.3000000e+00 5.0000000e-01 6.9000000e+00 6.2000000e+00 7.2000000e+00 5.5000000e+00 6.8000000e+00 5.7000000e+00 6.5000000e+00 3.8000000e+00 6.6000000e+00 4.8000000e+00 4.5000000e+00 5.6000000e+00 5.8000000e+00 6.3000000e+00 4.6000000e+00 6.4000000e+00 5.6000000e+00 5.2000000e+00 7.0000000e+00 5.1000000e+00 6.3000000e+00 5.6000000e+00 7.2000000e+00 6.2000000e+00 6.1000000e+00 6.4000000e+00 7.2000000e+00 7.4000000e+00 6.1000000e+00 4.6000000e+00 5.0000000e+00 4.8000000e+00 5.2000000e+00 7.0000000e+00 5.4000000e+00 6.1000000e+00 6.8000000e+00 6.7000000e+00 5.0000000e+00 5.3000000e+00 5.5000000e+00 6.1000000e+00 5.4000000e+00 4.0000000e+00 5.4000000e+00 5.1000000e+00 5.3000000e+00 5.9000000e+00 3.7000000e+00 5.3000000e+00 8.7000000e+00 7.1000000e+00 9.1000000e+00 7.8000000e+00 8.5000000e+00 1.0300000e+01 5.6000000e+00 9.5000000e+00 8.8000000e+00 1.0000000e+01 7.4000000e+00 7.9000000e+00 8.4000000e+00 7.2000000e+00 7.5000000e+00 7.8000000e+00 7.8000000e+00 1.1000000e+01 1.1300000e+01 7.3000000e+00 8.7000000e+00 6.7000000e+00 1.0600000e+01 7.3000000e+00 8.4000000e+00 8.8000000e+00 7.0000000e+00 6.8000000e+00 8.3000000e+00 8.6000000e+00 9.6000000e+00 1.0700000e+01 8.4000000e+00 7.1000000e+00 7.5000000e+00 1.0100000e+01 8.3000000e+00 7.6000000e+00 6.6000000e+00 8.3000000e+00 8.6000000e+00 8.2000000e+00 7.1000000e+00 8.8000000e+00 8.8000000e+00 8.2000000e+00 7.7000000e+00 7.7000000e+00 7.9000000e+00 6.8000000e+00 8.0000000e-01 6.6000000e+00 5.9000000e+00 6.9000000e+00 5.2000000e+00 6.5000000e+00 5.4000000e+00 6.0000000e+00 4.3000000e+00 6.3000000e+00 4.7000000e+00 4.8000000e+00 5.3000000e+00 5.5000000e+00 6.0000000e+00 4.3000000e+00 6.1000000e+00 5.3000000e+00 4.9000000e+00 6.7000000e+00 4.8000000e+00 6.0000000e+00 5.3000000e+00 6.9000000e+00 5.9000000e+00 5.8000000e+00 6.1000000e+00 6.9000000e+00 7.1000000e+00 5.8000000e+00 4.3000000e+00 4.7000000e+00 4.5000000e+00 4.9000000e+00 6.7000000e+00 5.1000000e+00 5.4000000e+00 6.5000000e+00 6.4000000e+00 4.7000000e+00 5.0000000e+00 5.2000000e+00 5.8000000e+00 5.1000000e+00 4.3000000e+00 5.1000000e+00 4.8000000e+00 5.0000000e+00 5.6000000e+00 3.8000000e+00 5.0000000e+00 8.2000000e+00 6.8000000e+00 8.8000000e+00 7.5000000e+00 8.2000000e+00 1.0000000e+01 6.1000000e+00 9.2000000e+00 8.5000000e+00 8.9000000e+00 7.1000000e+00 7.6000000e+00 8.1000000e+00 6.9000000e+00 7.2000000e+00 7.5000000e+00 7.5000000e+00 9.7000000e+00 1.1000000e+01 7.0000000e+00 8.4000000e+00 6.4000000e+00 1.0300000e+01 7.0000000e+00 7.9000000e+00 8.5000000e+00 6.7000000e+00 6.5000000e+00 8.0000000e+00 8.3000000e+00 9.3000000e+00 9.4000000e+00 8.1000000e+00 6.8000000e+00 7.2000000e+00 9.8000000e+00 7.6000000e+00 7.3000000e+00 6.3000000e+00 8.0000000e+00 8.3000000e+00 7.9000000e+00 6.8000000e+00 8.5000000e+00 8.3000000e+00 7.9000000e+00 7.4000000e+00 7.4000000e+00 7.2000000e+00 6.5000000e+00 6.6000000e+00 5.9000000e+00 6.9000000e+00 5.2000000e+00 6.5000000e+00 5.4000000e+00 6.0000000e+00 3.7000000e+00 6.3000000e+00 4.5000000e+00 4.2000000e+00 5.3000000e+00 5.5000000e+00 6.0000000e+00 4.3000000e+00 6.1000000e+00 5.3000000e+00 4.9000000e+00 6.7000000e+00 4.8000000e+00 6.0000000e+00 5.3000000e+00 6.9000000e+00 5.9000000e+00 5.8000000e+00 6.1000000e+00 6.9000000e+00 7.1000000e+00 5.8000000e+00 4.3000000e+00 4.7000000e+00 4.5000000e+00 4.9000000e+00 6.7000000e+00 5.1000000e+00 5.6000000e+00 6.5000000e+00 6.4000000e+00 4.7000000e+00 5.0000000e+00 5.2000000e+00 5.8000000e+00 5.1000000e+00 3.7000000e+00 5.1000000e+00 4.8000000e+00 5.0000000e+00 5.6000000e+00 3.4000000e+00 5.0000000e+00 8.2000000e+00 6.8000000e+00 8.8000000e+00 7.5000000e+00 8.2000000e+00 1.0000000e+01 5.5000000e+00 9.2000000e+00 8.5000000e+00 9.5000000e+00 7.1000000e+00 7.6000000e+00 8.1000000e+00 6.9000000e+00 7.2000000e+00 7.5000000e+00 7.5000000e+00 1.0500000e+01 1.1000000e+01 7.0000000e+00 8.4000000e+00 6.4000000e+00 1.0300000e+01 7.0000000e+00 7.9000000e+00 8.5000000e+00 6.7000000e+00 6.5000000e+00 8.0000000e+00 8.3000000e+00 9.3000000e+00 1.0200000e+01 8.1000000e+00 6.8000000e+00 7.2000000e+00 9.8000000e+00 7.8000000e+00 7.3000000e+00 6.3000000e+00 8.0000000e+00 8.3000000e+00 7.9000000e+00 6.8000000e+00 8.5000000e+00 8.3000000e+00 7.9000000e+00 7.4000000e+00 7.4000000e+00 7.4000000e+00 6.5000000e+00 9.0000000e-01 5.0000000e-01 3.2000000e+00 1.1000000e+00 2.0000000e+00 1.0000000e+00 4.7000000e+00 9.0000000e-01 3.1000000e+00 4.8000000e+00 1.9000000e+00 3.1000000e+00 1.2000000e+00 2.9000000e+00 7.0000000e-01 1.9000000e+00 2.7000000e+00 2.1000000e+00 3.2000000e+00 1.6000000e+00 2.1000000e+00 1.7000000e+00 1.5000000e+00 1.4000000e+00 9.0000000e-01 7.0000000e-01 1.1000000e+00 1.6000000e+00 3.5000000e+00 3.5000000e+00 3.7000000e+00 2.7000000e+00 2.1000000e+00 2.1000000e+00 1.6000000e+00 5.0000000e-01 2.0000000e+00 2.3000000e+00 3.0000000e+00 2.6000000e+00 1.2000000e+00 2.7000000e+00 4.7000000e+00 2.5000000e+00 2.2000000e+00 2.2000000e+00 1.6000000e+00 4.6000000e+00 2.4000000e+00 3.2000000e+00 2.6000000e+00 2.2000000e+00 2.3000000e+00 2.6000000e+00 3.4000000e+00 3.3000000e+00 2.6000000e+00 2.5000000e+00 3.1000000e+00 1.5000000e+00 2.2000000e+00 1.9000000e+00 2.9000000e+00 3.0000000e+00 2.1000000e+00 1.9000000e+00 4.1000000e+00 4.4000000e+00 2.4000000e+00 2.0000000e+00 2.6000000e+00 3.7000000e+00 1.8000000e+00 2.1000000e+00 1.9000000e+00 1.7000000e+00 1.7000000e+00 2.6000000e+00 1.7000000e+00 2.7000000e+00 3.8000000e+00 2.7000000e+00 1.6000000e+00 2.4000000e+00 3.2000000e+00 2.8000000e+00 1.9000000e+00 1.7000000e+00 1.6000000e+00 2.3000000e+00 1.5000000e+00 2.6000000e+00 2.3000000e+00 2.5000000e+00 1.9000000e+00 2.2000000e+00 1.8000000e+00 2.6000000e+00 2.1000000e+00 1.0000000e+00 2.5000000e+00 6.0000000e-01 1.3000000e+00 5.0000000e-01 4.0000000e+00 8.0000000e-01 2.4000000e+00 4.1000000e+00 1.0000000e+00 2.4000000e+00 9.0000000e-01 2.2000000e+00 6.0000000e-01 1.0000000e+00 2.0000000e+00 1.2000000e+00 2.5000000e+00 1.1000000e+00 1.4000000e+00 1.2000000e+00 1.2000000e+00 7.0000000e-01 6.0000000e-01 1.2000000e+00 1.2000000e+00 7.0000000e-01 2.8000000e+00 2.8000000e+00 3.0000000e+00 2.0000000e+00 1.6000000e+00 1.2000000e+00 7.0000000e-01 6.0000000e-01 1.3000000e+00 1.6000000e+00 2.3000000e+00 1.9000000e+00 7.0000000e-01 2.0000000e+00 4.0000000e+00 1.8000000e+00 1.5000000e+00 1.5000000e+00 9.0000000e-01 3.9000000e+00 1.7000000e+00 2.7000000e+00 2.1000000e+00 2.9000000e+00 1.8000000e+00 2.3000000e+00 4.1000000e+00 2.4000000e+00 3.3000000e+00 2.6000000e+00 3.8000000e+00 1.2000000e+00 1.7000000e+00 2.2000000e+00 2.4000000e+00 2.5000000e+00 1.6000000e+00 1.6000000e+00 4.8000000e+00 5.1000000e+00 1.9000000e+00 2.5000000e+00 2.1000000e+00 4.4000000e+00 1.3000000e+00 2.2000000e+00 2.6000000e+00 1.2000000e+00 1.2000000e+00 2.1000000e+00 2.4000000e+00 3.4000000e+00 4.5000000e+00 2.2000000e+00 1.1000000e+00 2.1000000e+00 3.9000000e+00 2.3000000e+00 1.4000000e+00 1.2000000e+00 2.1000000e+00 2.4000000e+00 2.0000000e+00 2.1000000e+00 2.6000000e+00 2.6000000e+00 2.0000000e+00 1.7000000e+00 1.5000000e+00 2.1000000e+00 1.6000000e+00 3.3000000e+00 1.0000000e+00 2.1000000e+00 1.1000000e+00 4.8000000e+00 1.0000000e+00 3.2000000e+00 4.9000000e+00 1.8000000e+00 3.2000000e+00 1.3000000e+00 3.0000000e+00 8.0000000e-01 1.8000000e+00 2.8000000e+00 2.0000000e+00 3.3000000e+00 1.5000000e+00 2.2000000e+00 1.2000000e+00 1.6000000e+00 1.5000000e+00 1.0000000e+00 6.0000000e-01 6.0000000e-01 1.5000000e+00 3.6000000e+00 3.6000000e+00 3.8000000e+00 2.8000000e+00 1.6000000e+00 2.0000000e+00 1.7000000e+00 4.0000000e-01 2.1000000e+00 2.4000000e+00 3.1000000e+00 2.7000000e+00 1.3000000e+00 2.8000000e+00 4.8000000e+00 2.6000000e+00 2.3000000e+00 2.3000000e+00 1.7000000e+00 4.7000000e+00 2.5000000e+00 2.9000000e+00 2.1000000e+00 1.9000000e+00 1.8000000e+00 2.1000000e+00 3.1000000e+00 3.2000000e+00 2.3000000e+00 2.0000000e+00 3.0000000e+00 1.2000000e+00 1.7000000e+00 1.4000000e+00 2.4000000e+00 2.5000000e+00 1.8000000e+00 1.4000000e+00 4.0000000e+00 4.1000000e+00 1.9000000e+00 1.7000000e+00 2.1000000e+00 3.4000000e+00 1.3000000e+00 1.8000000e+00 1.8000000e+00 1.4000000e+00 1.2000000e+00 2.1000000e+00 1.4000000e+00 2.4000000e+00 3.7000000e+00 2.2000000e+00 1.1000000e+00 2.1000000e+00 2.9000000e+00 2.5000000e+00 1.4000000e+00 1.4000000e+00 1.1000000e+00 1.8000000e+00 1.0000000e+00 2.1000000e+00 2.0000000e+00 2.2000000e+00 1.4000000e+00 1.7000000e+00 1.3000000e+00 2.3000000e+00 1.6000000e+00 2.3000000e+00 1.2000000e+00 2.8000000e+00 1.7000000e+00 2.3000000e+00 9.0000000e-01 1.6000000e+00 1.5000000e+00 9.0000000e-01 2.0000000e+00 1.1000000e+00 2.5000000e+00 1.5000000e+00 1.1000000e+00 1.5000000e+00 6.0000000e-01 2.6000000e+00 1.1000000e+00 2.1000000e+00 1.9000000e+00 1.8000000e+00 2.3000000e+00 2.7000000e+00 3.3000000e+00 1.8000000e+00 1.3000000e+00 5.0000000e-01 7.0000000e-01 9.0000000e-01 2.3000000e+00 1.5000000e+00 2.4000000e+00 2.9000000e+00 1.2000000e+00 9.0000000e-01 2.0000000e-01 8.0000000e-01 2.0000000e+00 7.0000000e-01 1.5000000e+00 7.0000000e-01 1.2000000e+00 1.0000000e+00 1.6000000e+00 1.8000000e+00 8.0000000e-01 5.0000000e+00 2.4000000e+00 5.0000000e+00 3.5000000e+00 4.4000000e+00 6.2000000e+00 1.7000000e+00 5.2000000e+00 3.7000000e+00 6.3000000e+00 3.7000000e+00 3.2000000e+00 4.3000000e+00 2.1000000e+00 3.0000000e+00 4.1000000e+00 3.7000000e+00 7.3000000e+00 6.4000000e+00 1.8000000e+00 5.0000000e+00 2.2000000e+00 6.1000000e+00 2.6000000e+00 4.7000000e+00 5.1000000e+00 2.5000000e+00 2.7000000e+00 3.8000000e+00 4.5000000e+00 5.1000000e+00 7.0000000e+00 3.9000000e+00 2.6000000e+00 2.6000000e+00 6.0000000e+00 4.6000000e+00 3.7000000e+00 2.5000000e+00 4.4000000e+00 4.7000000e+00 4.3000000e+00 2.4000000e+00 5.1000000e+00 5.1000000e+00 4.1000000e+00 2.6000000e+00 3.6000000e+00 4.2000000e+00 2.7000000e+00 1.1000000e+00 9.0000000e-01 3.8000000e+00 4.0000000e-01 2.2000000e+00 3.9000000e+00 1.2000000e+00 2.2000000e+00 7.0000000e-01 2.2000000e+00 8.0000000e-01 1.2000000e+00 1.8000000e+00 1.0000000e+00 2.3000000e+00 1.5000000e+00 1.2000000e+00 8.0000000e-01 8.0000000e-01 7.0000000e-01 6.0000000e-01 6.0000000e-01 1.0000000e+00 7.0000000e-01 2.6000000e+00 2.6000000e+00 2.8000000e+00 1.8000000e+00 1.2000000e+00 1.4000000e+00 1.3000000e+00 6.0000000e-01 1.1000000e+00 1.8000000e+00 2.1000000e+00 1.7000000e+00 7.0000000e-01 1.8000000e+00 3.8000000e+00 1.6000000e+00 1.7000000e+00 1.5000000e+00 9.0000000e-01 3.7000000e+00 1.5000000e+00 3.1000000e+00 1.7000000e+00 2.7000000e+00 1.6000000e+00 2.1000000e+00 3.9000000e+00 2.2000000e+00 2.9000000e+00 2.0000000e+00 4.0000000e+00 1.4000000e+00 1.3000000e+00 2.0000000e+00 2.0000000e+00 2.1000000e+00 2.0000000e+00 1.4000000e+00 5.0000000e+00 4.5000000e+00 1.5000000e+00 2.7000000e+00 1.7000000e+00 3.8000000e+00 9.0000000e-01 2.4000000e+00 2.8000000e+00 8.0000000e-01 1.2000000e+00 1.7000000e+00 2.2000000e+00 2.8000000e+00 4.7000000e+00 1.8000000e+00 7.0000000e-01 1.7000000e+00 3.7000000e+00 2.7000000e+00 1.6000000e+00 1.2000000e+00 2.1000000e+00 2.4000000e+00 2.0000000e+00 1.7000000e+00 2.8000000e+00 2.8000000e+00 1.8000000e+00 1.3000000e+00 1.3000000e+00 2.5000000e+00 1.6000000e+00 1.6000000e+00 2.7000000e+00 1.1000000e+00 1.3000000e+00 2.8000000e+00 9.0000000e-01 1.7000000e+00 8.0000000e-01 1.1000000e+00 1.5000000e+00 5.0000000e-01 9.0000000e-01 1.3000000e+00 1.2000000e+00 1.4000000e+00 9.0000000e-01 1.5000000e+00 7.0000000e-01 1.0000000e+00 1.3000000e+00 1.5000000e+00 2.1000000e+00 6.0000000e-01 1.5000000e+00 1.5000000e+00 1.7000000e+00 9.0000000e-01 1.3000000e+00 7.0000000e-01 1.2000000e+00 1.7000000e+00 1.2000000e+00 7.0000000e-01 1.0000000e+00 6.0000000e-01 8.0000000e-01 9.0000000e-01 2.7000000e+00 5.0000000e-01 6.0000000e-01 4.0000000e-01 8.0000000e-01 2.6000000e+00 4.0000000e-01 3.8000000e+00 1.4000000e+00 3.8000000e+00 2.3000000e+00 3.2000000e+00 5.0000000e+00 1.5000000e+00 4.0000000e+00 3.1000000e+00 5.1000000e+00 2.5000000e+00 2.2000000e+00 3.1000000e+00 1.5000000e+00 1.8000000e+00 2.9000000e+00 2.5000000e+00 6.1000000e+00 5.6000000e+00 1.6000000e+00 3.8000000e+00 1.2000000e+00 4.9000000e+00 1.6000000e+00 3.5000000e+00 3.9000000e+00 1.3000000e+00 1.5000000e+00 2.6000000e+00 3.3000000e+00 3.9000000e+00 5.8000000e+00 2.7000000e+00 1.4000000e+00 1.8000000e+00 4.8000000e+00 3.4000000e+00 2.5000000e+00 1.3000000e+00 3.2000000e+00 3.5000000e+00 3.1000000e+00 1.4000000e+00 3.9000000e+00 3.9000000e+00 2.9000000e+00 2.0000000e+00 2.4000000e+00 3.0000000e+00 1.5000000e+00 4.3000000e+00 1.1000000e+00 2.7000000e+00 4.4000000e+00 1.3000000e+00 2.7000000e+00 8.0000000e-01 2.5000000e+00 1.1000000e+00 1.3000000e+00 2.3000000e+00 1.5000000e+00 2.8000000e+00 8.0000000e-01 1.7000000e+00 1.1000000e+00 1.1000000e+00 1.2000000e+00 1.1000000e+00 1.3000000e+00 1.1000000e+00 1.0000000e+00 3.1000000e+00 3.1000000e+00 3.3000000e+00 2.3000000e+00 1.3000000e+00 1.5000000e+00 6.0000000e-01 7.0000000e-01 1.6000000e+00 1.9000000e+00 2.6000000e+00 2.2000000e+00 8.0000000e-01 2.3000000e+00 4.3000000e+00 2.1000000e+00 1.8000000e+00 1.8000000e+00 1.2000000e+00 4.2000000e+00 2.0000000e+00 2.2000000e+00 1.8000000e+00 2.8000000e+00 1.5000000e+00 2.2000000e+00 4.0000000e+00 2.5000000e+00 3.2000000e+00 2.5000000e+00 3.5000000e+00 1.1000000e+00 1.6000000e+00 2.1000000e+00 2.1000000e+00 2.2000000e+00 1.5000000e+00 1.5000000e+00 4.5000000e+00 5.0000000e+00 1.8000000e+00 2.4000000e+00 1.8000000e+00 4.3000000e+00 1.0000000e+00 1.9000000e+00 2.5000000e+00 9.0000000e-01 9.0000000e-01 2.0000000e+00 2.3000000e+00 3.3000000e+00 4.2000000e+00 2.1000000e+00 1.0000000e+00 2.0000000e+00 3.8000000e+00 1.8000000e+00 1.3000000e+00 9.0000000e-01 2.0000000e+00 2.3000000e+00 1.9000000e+00 1.8000000e+00 2.5000000e+00 2.3000000e+00 1.9000000e+00 1.4000000e+00 1.4000000e+00 1.6000000e+00 1.3000000e+00 3.8000000e+00 1.6000000e+00 7.0000000e-01 3.0000000e+00 2.0000000e+00 3.5000000e+00 1.8000000e+00 4.0000000e+00 3.0000000e+00 2.0000000e+00 3.2000000e+00 1.5000000e+00 4.1000000e+00 2.6000000e+00 3.6000000e+00 3.2000000e+00 3.3000000e+00 3.8000000e+00 4.2000000e+00 4.8000000e+00 3.3000000e+00 1.2000000e+00 1.2000000e+00 1.0000000e+00 2.0000000e+00 3.8000000e+00 2.8000000e+00 3.9000000e+00 4.4000000e+00 2.9000000e+00 2.4000000e+00 1.7000000e+00 2.1000000e+00 3.5000000e+00 2.0000000e+00 2.0000000e-01 2.2000000e+00 2.5000000e+00 2.5000000e+00 3.1000000e+00 7.0000000e-01 2.3000000e+00 6.5000000e+00 3.9000000e+00 6.5000000e+00 5.0000000e+00 5.9000000e+00 7.7000000e+00 2.0000000e+00 6.7000000e+00 5.2000000e+00 7.8000000e+00 5.2000000e+00 4.7000000e+00 5.8000000e+00 3.6000000e+00 4.5000000e+00 5.6000000e+00 5.2000000e+00 8.8000000e+00 7.9000000e+00 3.5000000e+00 6.5000000e+00 3.7000000e+00 7.6000000e+00 4.1000000e+00 6.2000000e+00 6.6000000e+00 4.0000000e+00 4.2000000e+00 5.3000000e+00 6.0000000e+00 6.6000000e+00 8.5000000e+00 5.4000000e+00 4.1000000e+00 4.1000000e+00 7.5000000e+00 6.1000000e+00 5.2000000e+00 4.0000000e+00 5.9000000e+00 6.2000000e+00 5.8000000e+00 3.9000000e+00 6.6000000e+00 6.6000000e+00 5.6000000e+00 4.1000000e+00 5.1000000e+00 5.7000000e+00 4.2000000e+00 2.4000000e+00 3.9000000e+00 1.4000000e+00 2.2000000e+00 7.0000000e-01 2.0000000e+00 6.0000000e-01 1.4000000e+00 1.8000000e+00 1.4000000e+00 2.3000000e+00 1.7000000e+00 1.2000000e+00 1.2000000e+00 8.0000000e-01 5.0000000e-01 4.0000000e-01 6.0000000e-01 1.0000000e+00 9.0000000e-01 2.6000000e+00 2.6000000e+00 2.8000000e+00 1.8000000e+00 1.6000000e+00 1.6000000e+00 1.5000000e+00 6.0000000e-01 1.1000000e+00 1.6000000e+00 2.1000000e+00 1.7000000e+00 7.0000000e-01 1.8000000e+00 3.8000000e+00 1.6000000e+00 1.5000000e+00 1.3000000e+00 7.0000000e-01 3.7000000e+00 1.5000000e+00 3.3000000e+00 2.1000000e+00 2.7000000e+00 1.8000000e+00 2.3000000e+00 3.9000000e+00 2.6000000e+00 2.9000000e+00 2.2000000e+00 4.0000000e+00 1.6000000e+00 1.7000000e+00 2.0000000e+00 2.4000000e+00 2.5000000e+00 2.2000000e+00 1.6000000e+00 5.0000000e+00 4.7000000e+00 1.9000000e+00 2.7000000e+00 2.1000000e+00 4.0000000e+00 1.3000000e+00 2.4000000e+00 2.8000000e+00 1.2000000e+00 1.4000000e+00 2.1000000e+00 2.2000000e+00 3.0000000e+00 4.7000000e+00 2.2000000e+00 1.1000000e+00 1.9000000e+00 3.7000000e+00 2.9000000e+00 1.8000000e+00 1.4000000e+00 2.1000000e+00 2.4000000e+00 2.0000000e+00 2.1000000e+00 2.8000000e+00 2.8000000e+00 1.8000000e+00 1.7000000e+00 1.5000000e+00 2.7000000e+00 1.8000000e+00 1.7000000e+00 1.4000000e+00 1.8000000e+00 1.9000000e+00 1.0000000e+00 2.4000000e+00 1.4000000e+00 1.2000000e+00 2.2000000e+00 9.0000000e-01 2.5000000e+00 1.2000000e+00 2.4000000e+00 2.0000000e+00 1.9000000e+00 2.2000000e+00 2.6000000e+00 3.2000000e+00 1.7000000e+00 1.4000000e+00 1.0000000e+00 1.2000000e+00 8.0000000e-01 2.2000000e+00 1.2000000e+00 2.3000000e+00 2.8000000e+00 2.1000000e+00 1.0000000e+00 7.0000000e-01 1.1000000e+00 1.9000000e+00 1.0000000e+00 1.6000000e+00 8.0000000e-01 1.3000000e+00 1.1000000e+00 1.7000000e+00 1.5000000e+00 9.0000000e-01 4.9000000e+00 2.3000000e+00 4.9000000e+00 3.4000000e+00 4.3000000e+00 6.1000000e+00 1.4000000e+00 5.1000000e+00 4.0000000e+00 6.2000000e+00 3.6000000e+00 3.1000000e+00 4.2000000e+00 2.4000000e+00 2.9000000e+00 4.0000000e+00 3.6000000e+00 7.2000000e+00 6.5000000e+00 2.5000000e+00 4.9000000e+00 2.1000000e+00 6.0000000e+00 2.5000000e+00 4.6000000e+00 5.0000000e+00 2.4000000e+00 2.6000000e+00 3.7000000e+00 4.4000000e+00 5.0000000e+00 6.9000000e+00 3.8000000e+00 2.5000000e+00 2.7000000e+00 5.9000000e+00 4.5000000e+00 3.6000000e+00 2.4000000e+00 4.3000000e+00 4.6000000e+00 4.2000000e+00 2.3000000e+00 5.0000000e+00 5.0000000e+00 4.0000000e+00 2.9000000e+00 3.5000000e+00 4.1000000e+00 2.6000000e+00 3.1000000e+00 1.7000000e+00 3.6000000e+00 1.9000000e+00 4.1000000e+00 3.1000000e+00 2.1000000e+00 2.9000000e+00 1.6000000e+00 4.2000000e+00 2.7000000e+00 3.7000000e+00 3.3000000e+00 3.4000000e+00 3.9000000e+00 4.3000000e+00 4.9000000e+00 3.4000000e+00 1.3000000e+00 1.3000000e+00 1.1000000e+00 2.1000000e+00 3.9000000e+00 2.9000000e+00 4.0000000e+00 4.5000000e+00 2.8000000e+00 2.5000000e+00 1.8000000e+00 2.2000000e+00 3.6000000e+00 2.1000000e+00 5.0000000e-01 2.3000000e+00 2.6000000e+00 2.6000000e+00 3.2000000e+00 1.2000000e+00 2.4000000e+00 6.6000000e+00 4.0000000e+00 6.6000000e+00 5.1000000e+00 6.0000000e+00 7.8000000e+00 2.3000000e+00 6.8000000e+00 5.3000000e+00 7.9000000e+00 5.3000000e+00 4.8000000e+00 5.9000000e+00 3.7000000e+00 4.6000000e+00 5.7000000e+00 5.3000000e+00 8.9000000e+00 8.0000000e+00 3.2000000e+00 6.6000000e+00 3.8000000e+00 7.7000000e+00 4.2000000e+00 6.3000000e+00 6.7000000e+00 4.1000000e+00 4.3000000e+00 5.4000000e+00 6.1000000e+00 6.7000000e+00 8.6000000e+00 5.5000000e+00 4.2000000e+00 4.2000000e+00 7.6000000e+00 6.2000000e+00 5.3000000e+00 4.1000000e+00 6.0000000e+00 6.3000000e+00 5.9000000e+00 4.0000000e+00 6.7000000e+00 6.7000000e+00 5.7000000e+00 4.2000000e+00 5.2000000e+00 5.8000000e+00 4.3000000e+00 1.6000000e+00 9.0000000e-01 1.2000000e+00 1.2000000e+00 6.0000000e-01 1.0000000e+00 1.4000000e+00 1.5000000e+00 1.1000000e+00 8.0000000e-01 1.6000000e+00 1.2000000e+00 9.0000000e-01 1.0000000e+00 1.8000000e+00 1.8000000e+00 5.0000000e-01 1.8000000e+00 1.8000000e+00 2.0000000e+00 1.0000000e+00 1.4000000e+00 8.0000000e-01 9.0000000e-01 1.4000000e+00 1.5000000e+00 6.0000000e-01 1.3000000e+00 1.3000000e+00 7.0000000e-01 1.0000000e+00 3.0000000e+00 8.0000000e-01 5.0000000e-01 5.0000000e-01 7.0000000e-01 2.9000000e+00 7.0000000e-01 3.5000000e+00 1.7000000e+00 3.5000000e+00 2.2000000e+00 2.9000000e+00 4.7000000e+00 2.0000000e+00 3.9000000e+00 3.2000000e+00 4.8000000e+00 2.2000000e+00 2.3000000e+00 2.8000000e+00 2.0000000e+00 2.1000000e+00 2.6000000e+00 2.2000000e+00 5.8000000e+00 5.7000000e+00 1.7000000e+00 3.5000000e+00 1.7000000e+00 5.0000000e+00 1.7000000e+00 3.2000000e+00 3.6000000e+00 1.4000000e+00 1.2000000e+00 2.7000000e+00 3.0000000e+00 4.0000000e+00 5.5000000e+00 2.8000000e+00 1.5000000e+00 2.1000000e+00 4.5000000e+00 3.1000000e+00 2.2000000e+00 1.0000000e+00 2.9000000e+00 3.2000000e+00 2.8000000e+00 1.7000000e+00 3.6000000e+00 3.6000000e+00 2.6000000e+00 2.1000000e+00 2.1000000e+00 2.7000000e+00 1.2000000e+00 1.9000000e+00 1.8000000e+00 2.4000000e+00 2.2000000e+00 8.0000000e-01 1.2000000e+00 9.0000000e-01 2.7000000e+00 1.0000000e+00 2.0000000e+00 1.6000000e+00 1.7000000e+00 2.2000000e+00 2.6000000e+00 3.2000000e+00 1.7000000e+00 1.2000000e+00 1.0000000e+00 1.0000000e+00 1.0000000e+00 2.2000000e+00 2.4000000e+00 2.3000000e+00 2.8000000e+00 1.1000000e+00 1.6000000e+00 1.1000000e+00 1.5000000e+00 1.9000000e+00 8.0000000e-01 1.8000000e+00 1.4000000e+00 1.5000000e+00 1.5000000e+00 1.5000000e+00 2.3000000e+00 1.3000000e+00 4.9000000e+00 2.7000000e+00 4.9000000e+00 3.4000000e+00 4.3000000e+00 6.1000000e+00 2.6000000e+00 5.1000000e+00 3.6000000e+00 6.2000000e+00 3.6000000e+00 3.1000000e+00 4.2000000e+00 2.6000000e+00 3.3000000e+00 4.0000000e+00 3.6000000e+00 7.2000000e+00 6.3000000e+00 1.5000000e+00 4.9000000e+00 2.9000000e+00 6.0000000e+00 2.5000000e+00 4.6000000e+00 5.0000000e+00 2.4000000e+00 2.6000000e+00 3.7000000e+00 4.4000000e+00 5.0000000e+00 6.9000000e+00 3.8000000e+00 2.5000000e+00 2.5000000e+00 5.9000000e+00 4.5000000e+00 3.6000000e+00 2.4000000e+00 4.3000000e+00 4.6000000e+00 4.2000000e+00 2.7000000e+00 5.0000000e+00 5.0000000e+00 4.0000000e+00 2.5000000e+00 3.5000000e+00 4.1000000e+00 2.8000000e+00 1.7000000e+00 1.1000000e+00 9.0000000e-01 1.5000000e+00 1.1000000e+00 2.0000000e+00 1.0000000e+00 9.0000000e-01 9.0000000e-01 3.0000000e-01 8.0000000e-01 9.0000000e-01 9.0000000e-01 1.3000000e+00 4.0000000e-01 2.3000000e+00 2.3000000e+00 2.5000000e+00 1.5000000e+00 9.0000000e-01 1.1000000e+00 1.0000000e+00 9.0000000e-01 1.2000000e+00 1.3000000e+00 1.8000000e+00 1.4000000e+00 2.0000000e-01 1.5000000e+00 3.5000000e+00 1.3000000e+00 1.2000000e+00 1.0000000e+00 6.0000000e-01 3.4000000e+00 1.2000000e+00 3.0000000e+00 1.4000000e+00 3.0000000e+00 1.5000000e+00 2.4000000e+00 4.2000000e+00 2.1000000e+00 3.2000000e+00 2.5000000e+00 4.3000000e+00 1.7000000e+00 1.6000000e+00 2.3000000e+00 1.7000000e+00 1.8000000e+00 2.1000000e+00 1.7000000e+00 5.3000000e+00 5.0000000e+00 1.2000000e+00 3.0000000e+00 1.4000000e+00 4.3000000e+00 1.0000000e+00 2.7000000e+00 3.1000000e+00 7.0000000e-01 7.0000000e-01 2.0000000e+00 2.5000000e+00 3.3000000e+00 5.0000000e+00 2.1000000e+00 8.0000000e-01 1.2000000e+00 4.0000000e+00 2.6000000e+00 1.7000000e+00 7.0000000e-01 2.4000000e+00 2.7000000e+00 2.3000000e+00 1.4000000e+00 3.1000000e+00 3.1000000e+00 2.1000000e+00 1.4000000e+00 1.6000000e+00 2.2000000e+00 1.1000000e+00 2.2000000e+00 1.2000000e+00 1.2000000e+00 2.4000000e+00 9.0000000e-01 2.3000000e+00 1.0000000e+00 2.6000000e+00 1.8000000e+00 1.5000000e+00 2.0000000e+00 2.6000000e+00 3.0000000e+00 1.5000000e+00 8.0000000e-01 1.0000000e+00 1.0000000e+00 8.0000000e-01 2.4000000e+00 1.4000000e+00 2.1000000e+00 2.6000000e+00 2.1000000e+00 6.0000000e-01 9.0000000e-01 1.3000000e+00 1.7000000e+00 1.0000000e+00 1.8000000e+00 8.0000000e-01 9.0000000e-01 7.0000000e-01 1.3000000e+00 1.7000000e+00 7.0000000e-01 4.7000000e+00 2.5000000e+00 4.7000000e+00 3.2000000e+00 4.1000000e+00 5.9000000e+00 2.4000000e+00 4.9000000e+00 4.2000000e+00 6.0000000e+00 3.4000000e+00 3.3000000e+00 4.0000000e+00 2.6000000e+00 2.9000000e+00 3.8000000e+00 3.4000000e+00 7.0000000e+00 6.7000000e+00 2.7000000e+00 4.7000000e+00 2.1000000e+00 6.0000000e+00 2.7000000e+00 4.4000000e+00 4.8000000e+00 2.4000000e+00 2.4000000e+00 3.7000000e+00 4.2000000e+00 5.0000000e+00 6.7000000e+00 3.8000000e+00 2.5000000e+00 2.9000000e+00 5.7000000e+00 4.3000000e+00 3.4000000e+00 2.2000000e+00 4.1000000e+00 4.4000000e+00 4.0000000e+00 2.5000000e+00 4.8000000e+00 4.8000000e+00 3.8000000e+00 3.1000000e+00 3.3000000e+00 3.9000000e+00 2.4000000e+00 1.4000000e+00 2.0000000e+00 1.6000000e+00 2.5000000e+00 1.7000000e+00 1.4000000e+00 1.6000000e+00 1.4000000e+00 7.0000000e-01 2.0000000e-01 8.0000000e-01 1.0000000e+00 1.1000000e+00 2.8000000e+00 2.8000000e+00 3.0000000e+00 2.0000000e+00 2.0000000e+00 1.6000000e+00 1.3000000e+00 4.0000000e-01 1.3000000e+00 1.6000000e+00 2.3000000e+00 1.9000000e+00 9.0000000e-01 2.0000000e+00 4.0000000e+00 1.8000000e+00 1.5000000e+00 1.5000000e+00 9.0000000e-01 3.9000000e+00 1.7000000e+00 3.3000000e+00 2.5000000e+00 2.7000000e+00 2.2000000e+00 2.5000000e+00 3.9000000e+00 2.8000000e+00 3.1000000e+00 2.4000000e+00 3.8000000e+00 1.6000000e+00 2.1000000e+00 2.0000000e+00 2.8000000e+00 2.9000000e+00 2.2000000e+00 1.8000000e+00 4.8000000e+00 4.9000000e+00 2.3000000e+00 2.5000000e+00 2.5000000e+00 4.2000000e+00 1.7000000e+00 2.2000000e+00 2.6000000e+00 1.6000000e+00 1.6000000e+00 2.5000000e+00 2.2000000e+00 3.2000000e+00 4.5000000e+00 2.6000000e+00 1.5000000e+00 2.3000000e+00 3.7000000e+00 2.9000000e+00 1.8000000e+00 1.6000000e+00 1.9000000e+00 2.2000000e+00 1.8000000e+00 2.5000000e+00 2.6000000e+00 2.6000000e+00 1.8000000e+00 2.1000000e+00 1.7000000e+00 2.7000000e+00 2.0000000e+00 1.4000000e+00 1.4000000e+00 1.5000000e+00 1.1000000e+00 1.4000000e+00 1.6000000e+00 1.2000000e+00 1.3000000e+00 1.2000000e+00 1.8000000e+00 1.8000000e+00 5.0000000e-01 2.0000000e+00 1.8000000e+00 2.0000000e+00 1.4000000e+00 1.4000000e+00 2.0000000e-01 9.0000000e-01 1.4000000e+00 1.7000000e+00 6.0000000e-01 1.3000000e+00 9.0000000e-01 7.0000000e-01 1.4000000e+00 3.0000000e+00 8.0000000e-01 7.0000000e-01 7.0000000e-01 1.1000000e+00 2.9000000e+00 9.0000000e-01 3.5000000e+00 1.5000000e+00 3.5000000e+00 2.2000000e+00 2.9000000e+00 4.7000000e+00 1.4000000e+00 3.9000000e+00 3.2000000e+00 4.8000000e+00 2.2000000e+00 2.3000000e+00 2.8000000e+00 1.6000000e+00 1.9000000e+00 2.6000000e+00 2.2000000e+00 5.8000000e+00 5.7000000e+00 1.7000000e+00 3.5000000e+00 1.1000000e+00 5.0000000e+00 1.7000000e+00 3.2000000e+00 3.6000000e+00 1.4000000e+00 1.2000000e+00 2.7000000e+00 3.0000000e+00 4.0000000e+00 5.5000000e+00 2.8000000e+00 1.5000000e+00 2.1000000e+00 4.5000000e+00 3.1000000e+00 2.2000000e+00 1.0000000e+00 2.9000000e+00 3.2000000e+00 2.8000000e+00 1.5000000e+00 3.6000000e+00 3.6000000e+00 2.6000000e+00 2.1000000e+00 2.1000000e+00 2.7000000e+00 1.2000000e+00 1.8000000e+00 7.0000000e-01 2.1000000e+00 8.0000000e-01 2.0000000e+00 1.2000000e+00 1.3000000e+00 1.8000000e+00 2.2000000e+00 2.8000000e+00 1.3000000e+00 8.0000000e-01 1.0000000e+00 1.0000000e+00 4.0000000e-01 1.8000000e+00 1.6000000e+00 1.9000000e+00 2.4000000e+00 1.5000000e+00 8.0000000e-01 9.0000000e-01 9.0000000e-01 1.5000000e+00 4.0000000e-01 2.0000000e+00 6.0000000e-01 7.0000000e-01 7.0000000e-01 1.1000000e+00 2.1000000e+00 5.0000000e-01 4.5000000e+00 1.9000000e+00 4.5000000e+00 3.0000000e+00 3.9000000e+00 5.7000000e+00 2.2000000e+00 4.7000000e+00 3.6000000e+00 5.8000000e+00 3.2000000e+00 2.7000000e+00 3.8000000e+00 2.2000000e+00 2.5000000e+00 3.6000000e+00 3.2000000e+00 6.8000000e+00 6.1000000e+00 2.1000000e+00 4.5000000e+00 2.1000000e+00 5.6000000e+00 2.1000000e+00 4.2000000e+00 4.6000000e+00 2.0000000e+00 2.2000000e+00 3.3000000e+00 4.0000000e+00 4.6000000e+00 6.5000000e+00 3.4000000e+00 2.1000000e+00 2.3000000e+00 5.5000000e+00 4.1000000e+00 3.2000000e+00 2.0000000e+00 3.9000000e+00 4.2000000e+00 3.8000000e+00 1.9000000e+00 4.6000000e+00 4.6000000e+00 3.6000000e+00 2.5000000e+00 3.1000000e+00 3.7000000e+00 2.2000000e+00 1.9000000e+00 1.9000000e+00 1.4000000e+00 8.0000000e-01 1.2000000e+00 1.3000000e+00 1.4000000e+00 1.6000000e+00 2.0000000e+00 9.0000000e-01 2.4000000e+00 2.0000000e+00 2.2000000e+00 1.8000000e+00 1.4000000e+00 1.6000000e+00 1.5000000e+00 1.6000000e+00 5.0000000e-01 2.0000000e+00 1.7000000e+00 1.5000000e+00 1.1000000e+00 1.6000000e+00 3.0000000e+00 1.6000000e+00 1.9000000e+00 1.7000000e+00 1.1000000e+00 3.3000000e+00 1.7000000e+00 3.7000000e+00 1.9000000e+00 3.7000000e+00 2.2000000e+00 3.1000000e+00 4.9000000e+00 1.8000000e+00 3.9000000e+00 2.4000000e+00 5.0000000e+00 2.4000000e+00 1.9000000e+00 3.0000000e+00 1.8000000e+00 2.5000000e+00 2.8000000e+00 2.4000000e+00 6.0000000e+00 5.1000000e+00 7.0000000e-01 3.7000000e+00 2.1000000e+00 4.8000000e+00 1.3000000e+00 3.4000000e+00 3.8000000e+00 1.2000000e+00 1.6000000e+00 2.5000000e+00 3.2000000e+00 3.8000000e+00 5.7000000e+00 2.6000000e+00 1.3000000e+00 1.7000000e+00 4.7000000e+00 3.3000000e+00 2.4000000e+00 1.6000000e+00 3.1000000e+00 3.4000000e+00 3.0000000e+00 1.9000000e+00 3.8000000e+00 3.8000000e+00 2.8000000e+00 1.3000000e+00 2.3000000e+00 2.9000000e+00 2.0000000e+00 2.6000000e+00 1.1000000e+00 2.1000000e+00 1.7000000e+00 1.8000000e+00 2.3000000e+00 2.7000000e+00 3.3000000e+00 1.8000000e+00 7.0000000e-01 3.0000000e-01 5.0000000e-01 5.0000000e-01 2.3000000e+00 1.7000000e+00 2.4000000e+00 2.9000000e+00 1.6000000e+00 9.0000000e-01 4.0000000e-01 8.0000000e-01 2.0000000e+00 5.0000000e-01 1.5000000e+00 7.0000000e-01 1.0000000e+00 1.0000000e+00 1.6000000e+00 1.4000000e+00 8.0000000e-01 5.0000000e+00 2.4000000e+00 5.0000000e+00 3.5000000e+00 4.4000000e+00 6.2000000e+00 1.9000000e+00 5.2000000e+00 3.7000000e+00 6.3000000e+00 3.7000000e+00 3.2000000e+00 4.3000000e+00 2.1000000e+00 3.0000000e+00 4.1000000e+00 3.7000000e+00 7.3000000e+00 6.4000000e+00 2.2000000e+00 5.0000000e+00 2.2000000e+00 6.1000000e+00 2.6000000e+00 4.7000000e+00 5.1000000e+00 2.5000000e+00 2.7000000e+00 3.8000000e+00 4.5000000e+00 5.1000000e+00 7.0000000e+00 3.9000000e+00 2.6000000e+00 2.6000000e+00 6.0000000e+00 4.6000000e+00 3.7000000e+00 2.5000000e+00 4.4000000e+00 4.7000000e+00 4.3000000e+00 2.4000000e+00 5.1000000e+00 5.1000000e+00 4.1000000e+00 2.6000000e+00 3.6000000e+00 4.2000000e+00 2.7000000e+00 1.9000000e+00 1.5000000e+00 1.3000000e+00 1.8000000e+00 1.7000000e+00 1.7000000e+00 1.3000000e+00 1.0000000e+00 2.9000000e+00 2.9000000e+00 3.1000000e+00 2.1000000e+00 1.1000000e+00 1.3000000e+00 8.0000000e-01 1.3000000e+00 2.2000000e+00 1.7000000e+00 2.4000000e+00 2.0000000e+00 1.0000000e+00 2.1000000e+00 4.1000000e+00 1.9000000e+00 1.6000000e+00 1.6000000e+00 1.6000000e+00 4.0000000e+00 1.8000000e+00 2.4000000e+00 1.0000000e+00 2.8000000e+00 1.5000000e+00 2.2000000e+00 4.0000000e+00 2.1000000e+00 3.2000000e+00 2.5000000e+00 3.7000000e+00 1.1000000e+00 1.6000000e+00 2.1000000e+00 1.3000000e+00 1.4000000e+00 1.5000000e+00 1.5000000e+00 4.7000000e+00 5.0000000e+00 1.6000000e+00 2.4000000e+00 1.0000000e+00 4.3000000e+00 1.0000000e+00 2.1000000e+00 2.5000000e+00 7.0000000e-01 5.0000000e-01 2.0000000e+00 2.7000000e+00 3.3000000e+00 4.4000000e+00 2.1000000e+00 1.4000000e+00 2.0000000e+00 3.8000000e+00 2.0000000e+00 1.3000000e+00 3.0000000e-01 2.0000000e+00 2.3000000e+00 1.9000000e+00 1.0000000e+00 2.5000000e+00 2.5000000e+00 1.9000000e+00 1.4000000e+00 1.4000000e+00 1.6000000e+00 5.0000000e-01 1.6000000e+00 8.0000000e-01 7.0000000e-01 1.2000000e+00 1.6000000e+00 2.2000000e+00 9.0000000e-01 1.4000000e+00 1.4000000e+00 1.6000000e+00 6.0000000e-01 1.6000000e+00 1.6000000e+00 1.5000000e+00 1.8000000e+00 1.1000000e+00 8.0000000e-01 9.0000000e-01 1.3000000e+00 9.0000000e-01 6.0000000e-01 2.6000000e+00 8.0000000e-01 9.0000000e-01 7.0000000e-01 5.0000000e-01 2.5000000e+00 5.0000000e-01 3.9000000e+00 2.1000000e+00 3.9000000e+00 2.4000000e+00 3.3000000e+00 5.1000000e+00 2.4000000e+00 4.1000000e+00 3.2000000e+00 5.2000000e+00 2.6000000e+00 2.3000000e+00 3.2000000e+00 2.4000000e+00 2.5000000e+00 3.0000000e+00 2.6000000e+00 6.2000000e+00 5.7000000e+00 1.9000000e+00 3.9000000e+00 2.1000000e+00 5.0000000e+00 1.7000000e+00 3.6000000e+00 4.0000000e+00 1.4000000e+00 1.6000000e+00 2.7000000e+00 3.4000000e+00 4.0000000e+00 5.9000000e+00 2.8000000e+00 1.5000000e+00 1.9000000e+00 4.9000000e+00 3.5000000e+00 2.6000000e+00 1.6000000e+00 3.3000000e+00 3.6000000e+00 3.2000000e+00 2.1000000e+00 4.0000000e+00 4.0000000e+00 3.0000000e+00 2.1000000e+00 2.5000000e+00 3.1000000e+00 2.0000000e+00 1.0000000e+00 1.3000000e+00 1.4000000e+00 1.0000000e+00 1.2000000e+00 1.1000000e+00 2.6000000e+00 2.4000000e+00 2.6000000e+00 2.0000000e+00 8.0000000e-01 1.8000000e+00 1.7000000e+00 1.2000000e+00 9.0000000e-01 2.2000000e+00 1.9000000e+00 1.7000000e+00 1.1000000e+00 1.8000000e+00 3.6000000e+00 1.8000000e+00 2.1000000e+00 1.9000000e+00 1.3000000e+00 3.5000000e+00 1.9000000e+00 2.9000000e+00 1.3000000e+00 2.9000000e+00 1.4000000e+00 2.3000000e+00 4.1000000e+00 2.0000000e+00 3.1000000e+00 1.6000000e+00 4.2000000e+00 1.6000000e+00 1.1000000e+00 2.2000000e+00 1.2000000e+00 1.9000000e+00 2.0000000e+00 1.6000000e+00 5.2000000e+00 4.3000000e+00 7.0000000e-01 2.9000000e+00 1.5000000e+00 4.0000000e+00 5.0000000e-01 2.6000000e+00 3.0000000e+00 8.0000000e-01 1.0000000e+00 1.7000000e+00 2.4000000e+00 3.0000000e+00 4.9000000e+00 1.8000000e+00 5.0000000e-01 1.1000000e+00 3.9000000e+00 2.5000000e+00 1.6000000e+00 1.2000000e+00 2.3000000e+00 2.6000000e+00 2.2000000e+00 1.3000000e+00 3.0000000e+00 3.0000000e+00 2.0000000e+00 5.0000000e-01 1.5000000e+00 2.3000000e+00 1.4000000e+00 9.0000000e-01 1.2000000e+00 1.0000000e+00 1.6000000e+00 7.0000000e-01 2.0000000e+00 2.0000000e+00 2.2000000e+00 1.2000000e+00 1.0000000e+00 1.4000000e+00 1.3000000e+00 1.2000000e+00 1.1000000e+00 1.4000000e+00 1.7000000e+00 1.1000000e+00 5.0000000e-01 1.2000000e+00 3.2000000e+00 1.2000000e+00 1.1000000e+00 1.1000000e+00 7.0000000e-01 3.1000000e+00 1.1000000e+00 3.3000000e+00 1.5000000e+00 3.3000000e+00 1.8000000e+00 2.7000000e+00 4.5000000e+00 2.2000000e+00 3.5000000e+00 2.6000000e+00 4.6000000e+00 2.0000000e+00 1.7000000e+00 2.6000000e+00 1.8000000e+00 1.9000000e+00 2.4000000e+00 2.0000000e+00 5.6000000e+00 5.1000000e+00 1.3000000e+00 3.3000000e+00 1.5000000e+00 4.4000000e+00 1.1000000e+00 3.0000000e+00 3.4000000e+00 8.0000000e-01 1.0000000e+00 2.1000000e+00 2.8000000e+00 3.4000000e+00 5.3000000e+00 2.2000000e+00 9.0000000e-01 1.3000000e+00 4.3000000e+00 2.9000000e+00 2.0000000e+00 1.0000000e+00 2.7000000e+00 3.0000000e+00 2.6000000e+00 1.5000000e+00 3.4000000e+00 3.4000000e+00 2.4000000e+00 1.5000000e+00 1.9000000e+00 2.5000000e+00 1.4000000e+00 5.0000000e-01 1.1000000e+00 1.5000000e+00 8.0000000e-01 2.1000000e+00 2.1000000e+00 2.3000000e+00 1.3000000e+00 1.7000000e+00 1.5000000e+00 1.4000000e+00 1.1000000e+00 8.0000000e-01 1.1000000e+00 1.6000000e+00 1.4000000e+00 8.0000000e-01 1.3000000e+00 3.3000000e+00 1.1000000e+00 1.0000000e+00 8.0000000e-01 2.0000000e-01 3.2000000e+00 1.0000000e+00 3.4000000e+00 2.2000000e+00 3.2000000e+00 1.9000000e+00 2.6000000e+00 4.4000000e+00 2.5000000e+00 3.4000000e+00 2.7000000e+00 4.5000000e+00 1.9000000e+00 1.8000000e+00 2.5000000e+00 2.5000000e+00 2.6000000e+00 2.3000000e+00 1.9000000e+00 5.5000000e+00 5.2000000e+00 2.0000000e+00 3.2000000e+00 2.2000000e+00 4.5000000e+00 1.4000000e+00 2.9000000e+00 3.3000000e+00 1.3000000e+00 1.5000000e+00 2.2000000e+00 2.7000000e+00 3.5000000e+00 5.2000000e+00 2.3000000e+00 1.2000000e+00 2.0000000e+00 4.2000000e+00 3.0000000e+00 1.9000000e+00 1.5000000e+00 2.6000000e+00 2.9000000e+00 2.5000000e+00 2.2000000e+00 3.3000000e+00 3.3000000e+00 2.3000000e+00 1.8000000e+00 1.8000000e+00 2.8000000e+00 1.9000000e+00 8.0000000e-01 1.0000000e+00 9.0000000e-01 2.6000000e+00 2.6000000e+00 2.8000000e+00 1.8000000e+00 1.8000000e+00 1.4000000e+00 1.3000000e+00 6.0000000e-01 1.1000000e+00 1.4000000e+00 2.1000000e+00 1.7000000e+00 7.0000000e-01 1.8000000e+00 3.8000000e+00 1.6000000e+00 1.3000000e+00 1.3000000e+00 7.0000000e-01 3.7000000e+00 1.5000000e+00 3.3000000e+00 2.3000000e+00 2.7000000e+00 2.0000000e+00 2.3000000e+00 3.9000000e+00 2.6000000e+00 3.1000000e+00 2.4000000e+00 4.0000000e+00 1.6000000e+00 1.9000000e+00 2.0000000e+00 2.6000000e+00 2.7000000e+00 2.2000000e+00 1.6000000e+00 5.0000000e+00 4.9000000e+00 2.1000000e+00 2.7000000e+00 2.3000000e+00 4.2000000e+00 1.5000000e+00 2.4000000e+00 2.8000000e+00 1.4000000e+00 1.4000000e+00 2.3000000e+00 2.2000000e+00 3.2000000e+00 4.7000000e+00 2.4000000e+00 1.3000000e+00 2.1000000e+00 3.7000000e+00 2.9000000e+00 1.8000000e+00 1.4000000e+00 2.1000000e+00 2.4000000e+00 2.0000000e+00 2.3000000e+00 2.8000000e+00 2.8000000e+00 1.8000000e+00 1.9000000e+00 1.5000000e+00 2.7000000e+00 1.8000000e+00 8.0000000e-01 1.3000000e+00 3.0000000e+00 3.0000000e+00 3.2000000e+00 2.2000000e+00 1.4000000e+00 2.0000000e+00 1.9000000e+00 6.0000000e-01 1.5000000e+00 2.2000000e+00 2.5000000e+00 2.1000000e+00 1.1000000e+00 2.2000000e+00 4.2000000e+00 2.0000000e+00 2.1000000e+00 1.9000000e+00 1.3000000e+00 4.1000000e+00 1.9000000e+00 3.3000000e+00 1.9000000e+00 2.3000000e+00 1.8000000e+00 2.3000000e+00 3.5000000e+00 2.8000000e+00 2.5000000e+00 1.8000000e+00 3.6000000e+00 1.6000000e+00 1.5000000e+00 1.6000000e+00 2.2000000e+00 2.3000000e+00 2.2000000e+00 1.6000000e+00 4.6000000e+00 4.1000000e+00 1.7000000e+00 2.3000000e+00 1.9000000e+00 3.4000000e+00 1.1000000e+00 2.2000000e+00 2.4000000e+00 1.0000000e+00 1.4000000e+00 1.9000000e+00 1.8000000e+00 2.4000000e+00 4.3000000e+00 2.0000000e+00 9.0000000e-01 1.7000000e+00 3.3000000e+00 2.9000000e+00 1.8000000e+00 1.4000000e+00 1.7000000e+00 2.2000000e+00 1.6000000e+00 1.9000000e+00 2.4000000e+00 2.6000000e+00 1.6000000e+00 1.5000000e+00 1.5000000e+00 2.7000000e+00 1.8000000e+00 1.5000000e+00 3.6000000e+00 3.6000000e+00 3.8000000e+00 2.8000000e+00 1.2000000e+00 2.0000000e+00 1.7000000e+00 6.0000000e-01 2.1000000e+00 2.4000000e+00 3.1000000e+00 2.7000000e+00 1.3000000e+00 2.8000000e+00 4.8000000e+00 2.6000000e+00 2.3000000e+00 2.3000000e+00 1.7000000e+00 4.7000000e+00 2.5000000e+00 2.5000000e+00 1.5000000e+00 1.7000000e+00 1.2000000e+00 1.5000000e+00 2.9000000e+00 2.8000000e+00 2.1000000e+00 1.4000000e+00 3.0000000e+00 8.0000000e-01 1.1000000e+00 1.0000000e+00 1.8000000e+00 1.9000000e+00 1.4000000e+00 8.0000000e-01 4.0000000e+00 3.9000000e+00 1.7000000e+00 1.7000000e+00 1.7000000e+00 3.2000000e+00 9.0000000e-01 1.4000000e+00 1.8000000e+00 1.0000000e+00 8.0000000e-01 1.5000000e+00 1.4000000e+00 2.2000000e+00 3.7000000e+00 1.6000000e+00 9.0000000e-01 1.9000000e+00 2.7000000e+00 2.1000000e+00 1.0000000e+00 1.0000000e+00 1.1000000e+00 1.4000000e+00 1.0000000e+00 1.5000000e+00 1.8000000e+00 1.8000000e+00 8.0000000e-01 1.1000000e+00 7.0000000e-01 1.9000000e+00 1.0000000e+00 2.1000000e+00 2.1000000e+00 2.3000000e+00 1.3000000e+00 9.0000000e-01 7.0000000e-01 6.0000000e-01 1.1000000e+00 1.2000000e+00 1.1000000e+00 1.6000000e+00 1.2000000e+00 4.0000000e-01 1.3000000e+00 3.3000000e+00 1.1000000e+00 1.0000000e+00 8.0000000e-01 6.0000000e-01 3.2000000e+00 1.0000000e+00 3.2000000e+00 1.4000000e+00 3.2000000e+00 1.7000000e+00 2.6000000e+00 4.4000000e+00 1.7000000e+00 3.4000000e+00 2.7000000e+00 4.5000000e+00 1.9000000e+00 1.8000000e+00 2.5000000e+00 1.7000000e+00 1.8000000e+00 2.3000000e+00 1.9000000e+00 5.5000000e+00 5.2000000e+00 1.2000000e+00 3.2000000e+00 1.4000000e+00 4.5000000e+00 1.2000000e+00 2.9000000e+00 3.3000000e+00 9.0000000e-01 9.0000000e-01 2.2000000e+00 2.7000000e+00 3.5000000e+00 5.2000000e+00 2.3000000e+00 1.0000000e+00 1.6000000e+00 4.2000000e+00 2.8000000e+00 1.9000000e+00 7.0000000e-01 2.6000000e+00 2.9000000e+00 2.5000000e+00 1.4000000e+00 3.3000000e+00 3.3000000e+00 2.3000000e+00 1.6000000e+00 1.8000000e+00 2.4000000e+00 1.1000000e+00 8.0000000e-01 6.0000000e-01 8.0000000e-01 2.6000000e+00 2.2000000e+00 2.7000000e+00 3.2000000e+00 2.1000000e+00 1.4000000e+00 1.1000000e+00 1.3000000e+00 2.3000000e+00 8.0000000e-01 1.2000000e+00 1.2000000e+00 1.3000000e+00 1.3000000e+00 1.9000000e+00 1.3000000e+00 1.1000000e+00 5.3000000e+00 2.7000000e+00 5.3000000e+00 3.8000000e+00 4.7000000e+00 6.5000000e+00 2.6000000e+00 5.5000000e+00 4.2000000e+00 6.6000000e+00 4.0000000e+00 3.5000000e+00 4.6000000e+00 2.6000000e+00 3.3000000e+00 4.4000000e+00 4.0000000e+00 7.6000000e+00 6.7000000e+00 2.7000000e+00 5.3000000e+00 2.7000000e+00 6.4000000e+00 2.9000000e+00 5.0000000e+00 5.4000000e+00 2.8000000e+00 3.0000000e+00 4.1000000e+00 4.8000000e+00 5.4000000e+00 7.3000000e+00 4.2000000e+00 2.9000000e+00 2.9000000e+00 6.3000000e+00 4.9000000e+00 4.0000000e+00 2.8000000e+00 4.7000000e+00 5.0000000e+00 4.6000000e+00 2.7000000e+00 5.4000000e+00 5.4000000e+00 4.4000000e+00 3.1000000e+00 3.9000000e+00 4.5000000e+00 3.0000000e+00 2.0000000e-01 8.0000000e-01 2.6000000e+00 1.8000000e+00 2.7000000e+00 3.2000000e+00 1.7000000e+00 1.2000000e+00 5.0000000e-01 9.0000000e-01 2.3000000e+00 8.0000000e-01 1.2000000e+00 1.0000000e+00 1.3000000e+00 1.3000000e+00 1.9000000e+00 1.3000000e+00 1.1000000e+00 5.3000000e+00 2.7000000e+00 5.3000000e+00 3.8000000e+00 4.7000000e+00 6.5000000e+00 2.0000000e+00 5.5000000e+00 4.0000000e+00 6.6000000e+00 4.0000000e+00 3.5000000e+00 4.6000000e+00 2.4000000e+00 3.3000000e+00 4.4000000e+00 4.0000000e+00 7.6000000e+00 6.7000000e+00 2.3000000e+00 5.3000000e+00 2.5000000e+00 6.4000000e+00 2.9000000e+00 5.0000000e+00 5.4000000e+00 2.8000000e+00 3.0000000e+00 4.1000000e+00 4.8000000e+00 5.4000000e+00 7.3000000e+00 4.2000000e+00 2.9000000e+00 2.9000000e+00 6.3000000e+00 4.9000000e+00 4.0000000e+00 2.8000000e+00 4.7000000e+00 5.0000000e+00 4.6000000e+00 2.7000000e+00 5.4000000e+00 5.4000000e+00 4.4000000e+00 2.9000000e+00 3.9000000e+00 4.5000000e+00 3.0000000e+00 1.0000000e+00 2.8000000e+00 2.0000000e+00 2.9000000e+00 3.4000000e+00 1.9000000e+00 1.4000000e+00 7.0000000e-01 1.1000000e+00 2.5000000e+00 1.0000000e+00 1.0000000e+00 1.2000000e+00 1.5000000e+00 1.5000000e+00 2.1000000e+00 1.3000000e+00 1.3000000e+00 5.5000000e+00 2.9000000e+00 5.5000000e+00 4.0000000e+00 4.9000000e+00 6.7000000e+00 2.2000000e+00 5.7000000e+00 4.2000000e+00 6.8000000e+00 4.2000000e+00 3.7000000e+00 4.8000000e+00 2.6000000e+00 3.5000000e+00 4.6000000e+00 4.2000000e+00 7.8000000e+00 6.9000000e+00 2.5000000e+00 5.5000000e+00 2.7000000e+00 6.6000000e+00 3.1000000e+00 5.2000000e+00 5.6000000e+00 3.0000000e+00 3.2000000e+00 4.3000000e+00 5.0000000e+00 5.6000000e+00 7.5000000e+00 4.4000000e+00 3.1000000e+00 3.1000000e+00 6.5000000e+00 5.1000000e+00 4.2000000e+00 3.0000000e+00 4.9000000e+00 5.2000000e+00 4.8000000e+00 2.9000000e+00 5.6000000e+00 5.6000000e+00 4.6000000e+00 3.1000000e+00 4.1000000e+00 4.7000000e+00 3.2000000e+00 1.8000000e+00 1.6000000e+00 1.9000000e+00 2.4000000e+00 1.5000000e+00 8.0000000e-01 7.0000000e-01 9.0000000e-01 1.5000000e+00 2.0000000e-01 2.0000000e+00 6.0000000e-01 7.0000000e-01 7.0000000e-01 1.1000000e+00 1.9000000e+00 5.0000000e-01 4.5000000e+00 1.9000000e+00 4.5000000e+00 3.0000000e+00 3.9000000e+00 5.7000000e+00 2.2000000e+00 4.7000000e+00 3.6000000e+00 5.8000000e+00 3.2000000e+00 2.7000000e+00 3.8000000e+00 2.2000000e+00 2.5000000e+00 3.6000000e+00 3.2000000e+00 6.8000000e+00 6.1000000e+00 2.1000000e+00 4.5000000e+00 2.1000000e+00 5.6000000e+00 2.1000000e+00 4.2000000e+00 4.6000000e+00 2.0000000e+00 2.2000000e+00 3.3000000e+00 4.0000000e+00 4.6000000e+00 6.5000000e+00 3.4000000e+00 2.1000000e+00 2.3000000e+00 5.5000000e+00 4.1000000e+00 3.2000000e+00 2.0000000e+00 3.9000000e+00 4.2000000e+00 3.8000000e+00 1.9000000e+00 4.6000000e+00 4.6000000e+00 3.6000000e+00 2.5000000e+00 3.1000000e+00 3.7000000e+00 2.2000000e+00 1.6000000e+00 1.3000000e+00 1.6000000e+00 1.7000000e+00 2.0000000e+00 2.1000000e+00 1.7000000e+00 1.1000000e+00 1.8000000e+00 3.8000000e+00 1.6000000e+00 1.9000000e+00 1.7000000e+00 1.5000000e+00 3.7000000e+00 1.7000000e+00 2.7000000e+00 5.0000000e-01 2.7000000e+00 1.2000000e+00 2.1000000e+00 3.9000000e+00 2.0000000e+00 2.9000000e+00 1.8000000e+00 4.0000000e+00 1.4000000e+00 9.0000000e-01 2.0000000e+00 1.0000000e+00 1.1000000e+00 1.8000000e+00 1.4000000e+00 5.0000000e+00 4.3000000e+00 7.0000000e-01 2.7000000e+00 1.1000000e+00 3.8000000e+00 7.0000000e-01 2.4000000e+00 2.8000000e+00 8.0000000e-01 8.0000000e-01 1.5000000e+00 2.2000000e+00 2.8000000e+00 4.7000000e+00 1.6000000e+00 5.0000000e-01 9.0000000e-01 3.7000000e+00 2.3000000e+00 1.4000000e+00 8.0000000e-01 2.1000000e+00 2.4000000e+00 2.0000000e+00 5.0000000e-01 2.8000000e+00 2.8000000e+00 1.8000000e+00 9.0000000e-01 1.3000000e+00 1.9000000e+00 6.0000000e-01 1.1000000e+00 1.6000000e+00 1.9000000e+00 8.0000000e-01 1.3000000e+00 9.0000000e-01 9.0000000e-01 1.6000000e+00 2.8000000e+00 1.0000000e+00 9.0000000e-01 9.0000000e-01 1.3000000e+00 2.7000000e+00 1.1000000e+00 3.7000000e+00 1.7000000e+00 3.7000000e+00 2.4000000e+00 3.1000000e+00 4.9000000e+00 1.2000000e+00 4.1000000e+00 3.4000000e+00 5.0000000e+00 2.4000000e+00 2.5000000e+00 3.0000000e+00 1.8000000e+00 2.1000000e+00 2.8000000e+00 2.4000000e+00 6.0000000e+00 5.9000000e+00 1.9000000e+00 3.7000000e+00 1.3000000e+00 5.2000000e+00 1.9000000e+00 3.4000000e+00 3.8000000e+00 1.6000000e+00 1.4000000e+00 2.9000000e+00 3.2000000e+00 4.2000000e+00 5.7000000e+00 3.0000000e+00 1.7000000e+00 2.3000000e+00 4.7000000e+00 3.3000000e+00 2.4000000e+00 1.2000000e+00 3.1000000e+00 3.4000000e+00 3.0000000e+00 1.7000000e+00 3.8000000e+00 3.8000000e+00 2.8000000e+00 2.3000000e+00 2.3000000e+00 2.9000000e+00 1.4000000e+00 1.3000000e+00 1.8000000e+00 1.5000000e+00 2.2000000e+00 1.8000000e+00 8.0000000e-01 1.9000000e+00 3.9000000e+00 1.7000000e+00 1.4000000e+00 1.4000000e+00 1.2000000e+00 3.8000000e+00 1.6000000e+00 2.8000000e+00 1.8000000e+00 3.4000000e+00 2.1000000e+00 2.8000000e+00 4.6000000e+00 2.1000000e+00 3.8000000e+00 3.1000000e+00 3.9000000e+00 1.7000000e+00 2.2000000e+00 2.7000000e+00 2.1000000e+00 2.2000000e+00 2.1000000e+00 2.1000000e+00 4.9000000e+00 5.6000000e+00 1.8000000e+00 3.0000000e+00 1.8000000e+00 4.9000000e+00 1.6000000e+00 2.5000000e+00 3.1000000e+00 1.3000000e+00 1.1000000e+00 2.6000000e+00 2.9000000e+00 3.9000000e+00 4.6000000e+00 2.7000000e+00 1.6000000e+00 2.2000000e+00 4.4000000e+00 2.2000000e+00 1.9000000e+00 9.0000000e-01 2.6000000e+00 2.9000000e+00 2.5000000e+00 1.8000000e+00 3.1000000e+00 2.9000000e+00 2.5000000e+00 2.0000000e+00 2.0000000e+00 1.8000000e+00 1.3000000e+00 1.7000000e+00 2.0000000e+00 2.7000000e+00 2.3000000e+00 9.0000000e-01 2.4000000e+00 4.4000000e+00 2.2000000e+00 1.9000000e+00 1.9000000e+00 1.3000000e+00 4.3000000e+00 2.1000000e+00 2.9000000e+00 2.1000000e+00 2.3000000e+00 1.8000000e+00 2.1000000e+00 3.5000000e+00 2.8000000e+00 2.7000000e+00 2.0000000e+00 3.4000000e+00 1.2000000e+00 1.7000000e+00 1.6000000e+00 2.4000000e+00 2.5000000e+00 1.8000000e+00 1.4000000e+00 4.4000000e+00 4.5000000e+00 1.9000000e+00 2.1000000e+00 2.1000000e+00 3.8000000e+00 1.3000000e+00 1.8000000e+00 2.2000000e+00 1.2000000e+00 1.2000000e+00 2.1000000e+00 1.8000000e+00 2.8000000e+00 4.1000000e+00 2.2000000e+00 1.1000000e+00 2.1000000e+00 3.3000000e+00 2.5000000e+00 1.4000000e+00 1.2000000e+00 1.5000000e+00 1.8000000e+00 1.4000000e+00 2.1000000e+00 2.2000000e+00 2.2000000e+00 1.4000000e+00 1.7000000e+00 1.3000000e+00 2.3000000e+00 1.6000000e+00 1.7000000e+00 1.4000000e+00 1.2000000e+00 1.2000000e+00 1.3000000e+00 2.7000000e+00 1.3000000e+00 1.6000000e+00 1.4000000e+00 8.0000000e-01 3.0000000e+00 1.4000000e+00 3.8000000e+00 2.2000000e+00 3.8000000e+00 2.3000000e+00 3.2000000e+00 5.0000000e+00 2.1000000e+00 4.0000000e+00 2.5000000e+00 5.1000000e+00 2.5000000e+00 2.0000000e+00 3.1000000e+00 2.1000000e+00 2.8000000e+00 2.9000000e+00 2.5000000e+00 6.1000000e+00 5.2000000e+00 1.2000000e+00 3.8000000e+00 2.4000000e+00 4.9000000e+00 1.4000000e+00 3.5000000e+00 3.9000000e+00 1.5000000e+00 1.9000000e+00 2.6000000e+00 3.3000000e+00 3.9000000e+00 5.8000000e+00 2.7000000e+00 1.4000000e+00 1.8000000e+00 4.8000000e+00 3.4000000e+00 2.5000000e+00 1.9000000e+00 3.2000000e+00 3.5000000e+00 3.1000000e+00 2.2000000e+00 3.9000000e+00 3.9000000e+00 2.9000000e+00 1.4000000e+00 2.4000000e+00 3.2000000e+00 2.3000000e+00 7.0000000e-01 9.0000000e-01 1.1000000e+00 8.0000000e-01 2.4000000e+00 4.0000000e-01 3.0000000e-01 3.0000000e-01 9.0000000e-01 2.3000000e+00 3.0000000e-01 4.1000000e+00 2.1000000e+00 4.1000000e+00 2.8000000e+00 3.5000000e+00 5.3000000e+00 2.0000000e+00 4.5000000e+00 3.8000000e+00 5.4000000e+00 2.8000000e+00 2.9000000e+00 3.4000000e+00 2.2000000e+00 2.5000000e+00 3.2000000e+00 2.8000000e+00 6.4000000e+00 6.3000000e+00 2.3000000e+00 4.1000000e+00 1.7000000e+00 5.6000000e+00 2.3000000e+00 3.8000000e+00 4.2000000e+00 2.0000000e+00 1.8000000e+00 3.3000000e+00 3.6000000e+00 4.6000000e+00 6.1000000e+00 3.4000000e+00 2.1000000e+00 2.5000000e+00 5.1000000e+00 3.7000000e+00 2.8000000e+00 1.6000000e+00 3.5000000e+00 3.8000000e+00 3.4000000e+00 2.1000000e+00 4.2000000e+00 4.2000000e+00 3.2000000e+00 2.7000000e+00 2.7000000e+00 3.3000000e+00 1.8000000e+00 6.0000000e-01 1.8000000e+00 5.0000000e-01 1.7000000e+00 5.0000000e-01 1.0000000e+00 8.0000000e-01 1.4000000e+00 1.6000000e+00 6.0000000e-01 4.8000000e+00 2.2000000e+00 4.8000000e+00 3.3000000e+00 4.2000000e+00 6.0000000e+00 1.5000000e+00 5.0000000e+00 3.5000000e+00 6.1000000e+00 3.5000000e+00 3.0000000e+00 4.1000000e+00 1.9000000e+00 2.8000000e+00 3.9000000e+00 3.5000000e+00 7.1000000e+00 6.2000000e+00 2.0000000e+00 4.8000000e+00 2.0000000e+00 5.9000000e+00 2.4000000e+00 4.5000000e+00 4.9000000e+00 2.3000000e+00 2.5000000e+00 3.6000000e+00 4.3000000e+00 4.9000000e+00 6.8000000e+00 3.7000000e+00 2.4000000e+00 2.4000000e+00 5.8000000e+00 4.4000000e+00 3.5000000e+00 2.3000000e+00 4.2000000e+00 4.5000000e+00 4.1000000e+00 2.2000000e+00 4.9000000e+00 4.9000000e+00 3.9000000e+00 2.4000000e+00 3.4000000e+00 4.0000000e+00 2.5000000e+00 1.4000000e+00 7.0000000e-01 2.1000000e+00 5.0000000e-01 8.0000000e-01 8.0000000e-01 1.2000000e+00 2.0000000e+00 8.0000000e-01 4.4000000e+00 1.8000000e+00 4.4000000e+00 2.9000000e+00 3.8000000e+00 5.6000000e+00 1.3000000e+00 4.6000000e+00 3.3000000e+00 5.7000000e+00 3.1000000e+00 2.6000000e+00 3.7000000e+00 1.7000000e+00 2.4000000e+00 3.5000000e+00 3.1000000e+00 6.7000000e+00 5.8000000e+00 1.8000000e+00 4.4000000e+00 1.6000000e+00 5.5000000e+00 2.0000000e+00 4.1000000e+00 4.5000000e+00 1.9000000e+00 2.1000000e+00 3.2000000e+00 3.9000000e+00 4.5000000e+00 6.4000000e+00 3.3000000e+00 2.0000000e+00 2.0000000e+00 5.4000000e+00 4.0000000e+00 3.1000000e+00 1.9000000e+00 3.8000000e+00 4.1000000e+00 3.7000000e+00 1.8000000e+00 4.5000000e+00 4.5000000e+00 3.5000000e+00 2.2000000e+00 3.0000000e+00 3.6000000e+00 2.1000000e+00 1.5000000e+00 3.5000000e+00 1.3000000e+00 1.0000000e+00 1.0000000e+00 6.0000000e-01 3.4000000e+00 1.2000000e+00 3.0000000e+00 1.6000000e+00 3.0000000e+00 1.7000000e+00 2.4000000e+00 4.2000000e+00 2.1000000e+00 3.4000000e+00 2.7000000e+00 4.3000000e+00 1.7000000e+00 1.8000000e+00 2.3000000e+00 1.9000000e+00 2.0000000e+00 2.1000000e+00 1.7000000e+00 5.3000000e+00 5.2000000e+00 1.4000000e+00 3.0000000e+00 1.6000000e+00 4.5000000e+00 1.2000000e+00 2.7000000e+00 3.1000000e+00 9.0000000e-01 7.0000000e-01 2.2000000e+00 2.5000000e+00 3.5000000e+00 5.0000000e+00 2.3000000e+00 1.0000000e+00 1.4000000e+00 4.0000000e+00 2.6000000e+00 1.7000000e+00 7.0000000e-01 2.4000000e+00 2.7000000e+00 2.3000000e+00 1.6000000e+00 3.1000000e+00 3.1000000e+00 2.1000000e+00 1.6000000e+00 1.6000000e+00 2.2000000e+00 1.1000000e+00 2.0000000e+00 6.0000000e-01 7.0000000e-01 7.0000000e-01 1.1000000e+00 1.9000000e+00 5.0000000e-01 4.5000000e+00 1.9000000e+00 4.5000000e+00 3.0000000e+00 3.9000000e+00 5.7000000e+00 2.0000000e+00 4.7000000e+00 3.4000000e+00 5.8000000e+00 3.2000000e+00 2.7000000e+00 3.8000000e+00 2.0000000e+00 2.5000000e+00 3.6000000e+00 3.2000000e+00 6.8000000e+00 5.9000000e+00 1.9000000e+00 4.5000000e+00 2.1000000e+00 5.6000000e+00 2.1000000e+00 4.2000000e+00 4.6000000e+00 2.0000000e+00 2.2000000e+00 3.3000000e+00 4.0000000e+00 4.6000000e+00 6.5000000e+00 3.4000000e+00 2.1000000e+00 2.1000000e+00 5.5000000e+00 4.1000000e+00 3.2000000e+00 2.0000000e+00 3.9000000e+00 4.2000000e+00 3.8000000e+00 1.9000000e+00 4.6000000e+00 4.6000000e+00 3.6000000e+00 2.3000000e+00 3.1000000e+00 3.7000000e+00 2.2000000e+00 2.2000000e+00 2.5000000e+00 2.5000000e+00 3.1000000e+00 7.0000000e-01 2.3000000e+00 6.5000000e+00 3.9000000e+00 6.5000000e+00 5.0000000e+00 5.9000000e+00 7.7000000e+00 2.2000000e+00 6.7000000e+00 5.2000000e+00 7.8000000e+00 5.2000000e+00 4.7000000e+00 5.8000000e+00 3.6000000e+00 4.5000000e+00 5.6000000e+00 5.2000000e+00 8.8000000e+00 7.9000000e+00 3.3000000e+00 6.5000000e+00 3.7000000e+00 7.6000000e+00 4.1000000e+00 6.2000000e+00 6.6000000e+00 4.0000000e+00 4.2000000e+00 5.3000000e+00 6.0000000e+00 6.6000000e+00 8.5000000e+00 5.4000000e+00 4.1000000e+00 4.1000000e+00 7.5000000e+00 6.1000000e+00 5.2000000e+00 4.0000000e+00 5.9000000e+00 6.2000000e+00 5.8000000e+00 3.9000000e+00 6.6000000e+00 6.6000000e+00 5.6000000e+00 4.1000000e+00 5.1000000e+00 5.7000000e+00 4.2000000e+00 5.0000000e-01 3.0000000e-01 9.0000000e-01 2.1000000e+00 3.0000000e-01 4.3000000e+00 1.7000000e+00 4.3000000e+00 2.8000000e+00 3.7000000e+00 5.5000000e+00 1.6000000e+00 4.5000000e+00 3.4000000e+00 5.6000000e+00 3.0000000e+00 2.5000000e+00 3.6000000e+00 1.8000000e+00 2.3000000e+00 3.4000000e+00 3.0000000e+00 6.6000000e+00 5.9000000e+00 1.9000000e+00 4.3000000e+00 1.5000000e+00 5.4000000e+00 1.9000000e+00 4.0000000e+00 4.4000000e+00 1.8000000e+00 2.0000000e+00 3.1000000e+00 3.8000000e+00 4.4000000e+00 6.3000000e+00 3.2000000e+00 1.9000000e+00 2.1000000e+00 5.3000000e+00 3.9000000e+00 3.0000000e+00 1.8000000e+00 3.7000000e+00 4.0000000e+00 3.6000000e+00 1.7000000e+00 4.4000000e+00 4.4000000e+00 3.4000000e+00 2.3000000e+00 2.9000000e+00 3.5000000e+00 2.0000000e+00 2.0000000e-01 8.0000000e-01 2.4000000e+00 4.0000000e-01 4.0000000e+00 2.0000000e+00 4.0000000e+00 2.7000000e+00 3.4000000e+00 5.2000000e+00 2.1000000e+00 4.4000000e+00 3.7000000e+00 5.3000000e+00 2.7000000e+00 2.8000000e+00 3.3000000e+00 2.1000000e+00 2.4000000e+00 3.1000000e+00 2.7000000e+00 6.3000000e+00 6.2000000e+00 2.2000000e+00 4.0000000e+00 1.8000000e+00 5.5000000e+00 2.2000000e+00 3.7000000e+00 4.1000000e+00 1.9000000e+00 1.7000000e+00 3.2000000e+00 3.5000000e+00 4.5000000e+00 6.0000000e+00 3.3000000e+00 2.0000000e+00 2.4000000e+00 5.0000000e+00 3.6000000e+00 2.7000000e+00 1.5000000e+00 3.4000000e+00 3.7000000e+00 3.3000000e+00 2.0000000e+00 4.1000000e+00 4.1000000e+00 3.1000000e+00 2.6000000e+00 2.6000000e+00 3.2000000e+00 1.7000000e+00 6.0000000e-01 2.4000000e+00 2.0000000e-01 4.0000000e+00 1.8000000e+00 4.0000000e+00 2.5000000e+00 3.4000000e+00 5.2000000e+00 1.9000000e+00 4.2000000e+00 3.5000000e+00 5.3000000e+00 2.7000000e+00 2.6000000e+00 3.3000000e+00 1.9000000e+00 2.2000000e+00 3.1000000e+00 2.7000000e+00 6.3000000e+00 6.0000000e+00 2.0000000e+00 4.0000000e+00 1.6000000e+00 5.3000000e+00 2.0000000e+00 3.7000000e+00 4.1000000e+00 1.7000000e+00 1.7000000e+00 3.0000000e+00 3.5000000e+00 4.3000000e+00 6.0000000e+00 3.1000000e+00 1.8000000e+00 2.2000000e+00 5.0000000e+00 3.6000000e+00 2.7000000e+00 1.5000000e+00 3.4000000e+00 3.7000000e+00 3.3000000e+00 1.8000000e+00 4.1000000e+00 4.1000000e+00 3.1000000e+00 2.4000000e+00 2.6000000e+00 3.2000000e+00 1.7000000e+00 3.0000000e+00 8.0000000e-01 3.4000000e+00 2.0000000e+00 3.4000000e+00 1.9000000e+00 2.8000000e+00 4.6000000e+00 2.3000000e+00 3.6000000e+00 2.9000000e+00 4.7000000e+00 2.1000000e+00 2.0000000e+00 2.7000000e+00 2.3000000e+00 2.4000000e+00 2.5000000e+00 2.1000000e+00 5.7000000e+00 5.4000000e+00 1.8000000e+00 3.4000000e+00 2.0000000e+00 4.7000000e+00 1.4000000e+00 3.1000000e+00 3.5000000e+00 1.1000000e+00 1.3000000e+00 2.4000000e+00 2.9000000e+00 3.7000000e+00 5.4000000e+00 2.5000000e+00 1.2000000e+00 1.8000000e+00 4.4000000e+00 3.0000000e+00 2.1000000e+00 1.3000000e+00 2.8000000e+00 3.1000000e+00 2.7000000e+00 2.0000000e+00 3.5000000e+00 3.5000000e+00 2.5000000e+00 1.8000000e+00 2.0000000e+00 2.6000000e+00 1.7000000e+00 2.2000000e+00 6.4000000e+00 3.8000000e+00 6.4000000e+00 4.9000000e+00 5.8000000e+00 7.6000000e+00 2.3000000e+00 6.6000000e+00 5.1000000e+00 7.7000000e+00 5.1000000e+00 4.6000000e+00 5.7000000e+00 3.5000000e+00 4.4000000e+00 5.5000000e+00 5.1000000e+00 8.7000000e+00 7.8000000e+00 3.6000000e+00 6.4000000e+00 3.6000000e+00 7.5000000e+00 4.0000000e+00 6.1000000e+00 6.5000000e+00 3.9000000e+00 4.1000000e+00 5.2000000e+00 5.9000000e+00 6.5000000e+00 8.4000000e+00 5.3000000e+00 4.0000000e+00 4.0000000e+00 7.4000000e+00 6.0000000e+00 5.1000000e+00 3.9000000e+00 5.8000000e+00 6.1000000e+00 5.7000000e+00 3.8000000e+00 6.5000000e+00 6.5000000e+00 5.5000000e+00 4.0000000e+00 5.0000000e+00 5.6000000e+00 4.1000000e+00 4.2000000e+00 1.8000000e+00 4.2000000e+00 2.7000000e+00 3.6000000e+00 5.4000000e+00 1.9000000e+00 4.4000000e+00 3.5000000e+00 5.5000000e+00 2.9000000e+00 2.6000000e+00 3.5000000e+00 1.9000000e+00 2.2000000e+00 3.3000000e+00 2.9000000e+00 6.5000000e+00 6.0000000e+00 2.0000000e+00 4.2000000e+00 1.6000000e+00 5.3000000e+00 2.0000000e+00 3.9000000e+00 4.3000000e+00 1.7000000e+00 1.9000000e+00 3.0000000e+00 3.7000000e+00 4.3000000e+00 6.2000000e+00 3.1000000e+00 1.8000000e+00 2.2000000e+00 5.2000000e+00 3.8000000e+00 2.9000000e+00 1.7000000e+00 3.6000000e+00 3.9000000e+00 3.5000000e+00 1.8000000e+00 4.3000000e+00 4.3000000e+00 3.3000000e+00 2.4000000e+00 2.8000000e+00 3.4000000e+00 1.9000000e+00 2.6000000e+00 1.6000000e+00 1.5000000e+00 1.0000000e+00 2.6000000e+00 4.5000000e+00 2.4000000e+00 2.1000000e+00 1.3000000e+00 1.7000000e+00 2.0000000e+00 1.7000000e+00 2.9000000e+00 2.0000000e+00 1.1000000e+00 1.7000000e+00 2.9000000e+00 3.2000000e+00 3.4000000e+00 1.2000000e+00 2.8000000e+00 3.1000000e+00 2.4000000e+00 1.1000000e+00 1.7000000e+00 2.5000000e+00 2.3000000e+00 1.4000000e+00 2.3000000e+00 2.3000000e+00 3.0000000e+00 1.3000000e+00 2.4000000e+00 2.4000000e+00 2.0000000e+00 6.0000000e-01 1.5000000e+00 2.5000000e+00 1.8000000e+00 1.1000000e+00 1.9000000e+00 2.6000000e+00 9.0000000e-01 7.0000000e-01 1.7000000e+00 2.4000000e+00 1.8000000e+00 1.0000000e+00 2.3000000e+00 2.6000000e+00 1.3000000e+00 2.0000000e+00 3.8000000e+00 1.9000000e+00 3.0000000e+00 1.9000000e+00 3.9000000e+00 1.3000000e+00 8.0000000e-01 1.9000000e+00 5.0000000e-01 6.0000000e-01 1.7000000e+00 1.5000000e+00 4.9000000e+00 4.2000000e+00 1.2000000e+00 2.6000000e+00 6.0000000e-01 3.7000000e+00 8.0000000e-01 2.3000000e+00 2.9000000e+00 9.0000000e-01 9.0000000e-01 1.4000000e+00 2.7000000e+00 2.7000000e+00 4.6000000e+00 1.5000000e+00 1.0000000e+00 1.4000000e+00 3.6000000e+00 2.2000000e+00 1.5000000e+00 9.0000000e-01 2.0000000e+00 2.3000000e+00 1.9000000e+00 0.0000000e+00 2.7000000e+00 2.7000000e+00 1.7000000e+00 8.0000000e-01 1.2000000e+00 1.8000000e+00 5.0000000e-01 1.5000000e+00 8.0000000e-01 1.2000000e+00 4.5000000e+00 1.0000000e+00 1.3000000e+00 1.3000000e+00 1.7000000e+00 1.8000000e+00 7.0000000e-01 2.9000000e+00 2.6000000e+00 1.7000000e+00 1.3000000e+00 2.3000000e+00 2.2000000e+00 3.4000000e+00 8.0000000e-01 2.8000000e+00 1.7000000e+00 2.4000000e+00 9.0000000e-01 7.0000000e-01 2.5000000e+00 2.3000000e+00 1.2000000e+00 7.0000000e-01 9.0000000e-01 2.2000000e+00 1.3000000e+00 2.4000000e+00 2.4000000e+00 1.0000000e+00 1.8000000e+00 1.5000000e+00 2.5000000e+00 8.0000000e-01 1.1000000e+00 1.3000000e+00 2.6000000e+00 7.0000000e-01 1.3000000e+00 1.3000000e+00 2.4000000e+00 1.4000000e+00 2.0000000e+00 2.3000000e+00 9.0000000e-01 2.7000000e+00 3.0000000e+00 1.7000000e+00 1.0000000e+00 2.8000000e+00 1.2000000e+00 7.0000000e-01 1.0000000e+00 1.8000000e+00 1.7000000e+00 1.2000000e+00 4.0000000e-01 3.8000000e+00 3.5000000e+00 1.9000000e+00 1.5000000e+00 1.7000000e+00 2.8000000e+00 9.0000000e-01 1.2000000e+00 1.6000000e+00 1.0000000e+00 1.0000000e+00 5.0000000e-01 1.4000000e+00 1.8000000e+00 3.5000000e+00 6.0000000e-01 9.0000000e-01 9.0000000e-01 2.5000000e+00 1.1000000e+00 4.0000000e-01 1.2000000e+00 1.3000000e+00 1.2000000e+00 1.8000000e+00 1.3000000e+00 1.6000000e+00 1.6000000e+00 1.4000000e+00 1.1000000e+00 9.0000000e-01 1.3000000e+00 1.0000000e+00 2.0000000e+00 3.9000000e+00 1.8000000e+00 1.1000000e+00 1.9000000e+00 1.1000000e+00 1.2000000e+00 7.0000000e-01 2.3000000e+00 1.8000000e+00 9.0000000e-01 7.0000000e-01 2.9000000e+00 2.8000000e+00 2.8000000e+00 8.0000000e-01 2.2000000e+00 2.5000000e+00 1.8000000e+00 7.0000000e-01 1.5000000e+00 1.9000000e+00 1.7000000e+00 6.0000000e-01 1.3000000e+00 1.7000000e+00 3.0000000e+00 5.0000000e-01 1.8000000e+00 1.8000000e+00 1.6000000e+00 1.0000000e+00 9.0000000e-01 1.9000000e+00 1.0000000e+00 7.0000000e-01 1.3000000e+00 2.0000000e+00 7.0000000e-01 9.0000000e-01 9.0000000e-01 1.8000000e+00 8.0000000e-01 1.2000000e+00 1.7000000e+00 5.7000000e+00 1.0000000e+00 2.5000000e+00 1.9000000e+00 2.9000000e+00 3.0000000e+00 1.9000000e+00 4.1000000e+00 3.8000000e+00 2.9000000e+00 2.5000000e+00 1.1000000e+00 1.0000000e+00 4.6000000e+00 2.0000000e+00 4.0000000e+00 5.0000000e-01 3.6000000e+00 2.1000000e+00 1.5000000e+00 3.7000000e+00 3.5000000e+00 2.4000000e+00 1.7000000e+00 1.1000000e+00 1.4000000e+00 2.5000000e+00 3.6000000e+00 3.6000000e+00 8.0000000e-01 3.0000000e+00 2.7000000e+00 3.7000000e+00 2.0000000e+00 2.3000000e+00 2.5000000e+00 3.8000000e+00 1.9000000e+00 2.5000000e+00 2.5000000e+00 3.6000000e+00 2.6000000e+00 3.2000000e+00 3.5000000e+00 4.7000000e+00 3.2000000e+00 5.8000000e+00 3.2000000e+00 2.7000000e+00 3.8000000e+00 1.6000000e+00 2.5000000e+00 3.6000000e+00 3.2000000e+00 6.8000000e+00 5.9000000e+00 2.1000000e+00 4.5000000e+00 1.7000000e+00 5.6000000e+00 2.1000000e+00 4.2000000e+00 4.6000000e+00 2.0000000e+00 2.2000000e+00 3.3000000e+00 4.2000000e+00 4.6000000e+00 6.5000000e+00 3.4000000e+00 2.5000000e+00 2.7000000e+00 5.5000000e+00 4.1000000e+00 3.2000000e+00 2.0000000e+00 3.9000000e+00 4.2000000e+00 3.8000000e+00 1.9000000e+00 4.6000000e+00 4.6000000e+00 3.6000000e+00 2.1000000e+00 3.1000000e+00 3.7000000e+00 2.2000000e+00 1.5000000e+00 1.7000000e+00 2.5000000e+00 2.2000000e+00 1.7000000e+00 3.5000000e+00 3.4000000e+00 2.7000000e+00 1.7000000e+00 2.1000000e+00 1.8000000e+00 3.6000000e+00 1.8000000e+00 3.4000000e+00 1.1000000e+00 2.6000000e+00 1.9000000e+00 7.0000000e-01 2.7000000e+00 2.7000000e+00 2.0000000e+00 9.0000000e-01 5.0000000e-01 1.8000000e+00 2.1000000e+00 2.6000000e+00 2.6000000e+00 1.2000000e+00 2.8000000e+00 1.9000000e+00 2.9000000e+00 1.8000000e+00 2.1000000e+00 2.3000000e+00 3.0000000e+00 1.7000000e+00 2.3000000e+00 2.3000000e+00 2.8000000e+00 2.2000000e+00 3.0000000e+00 2.7000000e+00 2.6000000e+00 1.8000000e+00 1.1000000e+00 1.2000000e+00 2.0000000e+00 2.5000000e+00 2.0000000e+00 1.0000000e+00 3.6000000e+00 2.7000000e+00 2.1000000e+00 1.5000000e+00 2.5000000e+00 2.4000000e+00 1.5000000e+00 1.2000000e+00 1.4000000e+00 1.8000000e+00 2.0000000e+00 1.1000000e+00 1.2000000e+00 1.4000000e+00 3.3000000e+00 1.2000000e+00 1.7000000e+00 1.3000000e+00 2.3000000e+00 2.1000000e+00 1.2000000e+00 2.2000000e+00 1.5000000e+00 1.4000000e+00 2.0000000e+00 1.9000000e+00 1.4000000e+00 1.6000000e+00 1.6000000e+00 1.3000000e+00 1.5000000e+00 2.3000000e+00 2.0000000e+00 2.6000000e+00 3.1000000e+00 2.0000000e+00 4.2000000e+00 3.3000000e+00 2.2000000e+00 2.6000000e+00 1.6000000e+00 2.5000000e+00 4.7000000e+00 1.3000000e+00 4.1000000e+00 2.4000000e+00 3.7000000e+00 1.6000000e+00 1.2000000e+00 3.8000000e+00 3.6000000e+00 2.5000000e+00 1.8000000e+00 1.6000000e+00 1.7000000e+00 2.4000000e+00 3.7000000e+00 3.7000000e+00 1.3000000e+00 1.7000000e+00 2.6000000e+00 3.8000000e+00 1.9000000e+00 1.6000000e+00 2.0000000e+00 3.9000000e+00 1.2000000e+00 1.2000000e+00 2.2000000e+00 3.7000000e+00 2.7000000e+00 2.1000000e+00 3.6000000e+00 9.0000000e-01 1.0000000e+00 1.6000000e+00 1.5000000e+00 6.0000000e-01 8.0000000e-01 3.6000000e+00 3.9000000e+00 2.1000000e+00 1.3000000e+00 1.5000000e+00 3.2000000e+00 1.1000000e+00 1.0000000e+00 1.8000000e+00 1.2000000e+00 1.0000000e+00 1.1000000e+00 2.0000000e+00 2.4000000e+00 3.3000000e+00 1.2000000e+00 1.1000000e+00 2.1000000e+00 2.7000000e+00 1.3000000e+00 8.0000000e-01 1.2000000e+00 9.0000000e-01 1.2000000e+00 8.0000000e-01 1.3000000e+00 1.4000000e+00 1.4000000e+00 8.0000000e-01 1.1000000e+00 3.0000000e-01 1.1000000e+00 1.0000000e+00 1.1000000e+00 1.3000000e+00 1.4000000e+00 9.0000000e-01 7.0000000e-01 4.1000000e+00 3.4000000e+00 1.6000000e+00 1.8000000e+00 1.4000000e+00 2.9000000e+00 6.0000000e-01 1.5000000e+00 2.1000000e+00 9.0000000e-01 1.1000000e+00 6.0000000e-01 1.9000000e+00 1.9000000e+00 3.8000000e+00 7.0000000e-01 8.0000000e-01 1.2000000e+00 2.8000000e+00 1.6000000e+00 7.0000000e-01 1.3000000e+00 1.2000000e+00 1.5000000e+00 1.5000000e+00 8.0000000e-01 1.9000000e+00 1.9000000e+00 1.1000000e+00 6.0000000e-01 6.0000000e-01 1.4000000e+00 1.1000000e+00 2.2000000e+00 1.9000000e+00 1.0000000e+00 6.0000000e-01 3.0000000e+00 2.9000000e+00 2.7000000e+00 7.0000000e-01 2.1000000e+00 2.4000000e+00 1.7000000e+00 6.0000000e-01 1.4000000e+00 1.8000000e+00 1.6000000e+00 7.0000000e-01 1.2000000e+00 1.6000000e+00 2.9000000e+00 8.0000000e-01 1.7000000e+00 1.9000000e+00 1.7000000e+00 1.3000000e+00 8.0000000e-01 1.8000000e+00 3.0000000e-01 6.0000000e-01 8.0000000e-01 1.9000000e+00 8.0000000e-01 1.0000000e+00 6.0000000e-01 1.7000000e+00 7.0000000e-01 1.3000000e+00 1.6000000e+00 9.0000000e-01 2.0000000e+00 2.0000000e+00 5.2000000e+00 4.3000000e+00 1.1000000e+00 2.9000000e+00 5.0000000e-01 4.0000000e+00 1.1000000e+00 2.6000000e+00 3.4000000e+00 1.2000000e+00 1.2000000e+00 1.7000000e+00 3.2000000e+00 3.2000000e+00 4.9000000e+00 1.8000000e+00 1.5000000e+00 1.7000000e+00 3.9000000e+00 2.5000000e+00 2.0000000e+00 1.2000000e+00 2.3000000e+00 2.6000000e+00 2.2000000e+00 5.0000000e-01 3.0000000e+00 3.0000000e+00 2.0000000e+00 7.0000000e-01 1.5000000e+00 2.1000000e+00 1.0000000e+00 1.3000000e+00 1.9000000e+00 4.7000000e+00 4.0000000e+00 1.8000000e+00 2.2000000e+00 8.0000000e-01 3.9000000e+00 1.4000000e+00 2.3000000e+00 3.3000000e+00 1.3000000e+00 1.3000000e+00 1.4000000e+00 3.1000000e+00 3.1000000e+00 4.8000000e+00 1.3000000e+00 1.4000000e+00 2.0000000e+00 3.2000000e+00 1.6000000e+00 1.9000000e+00 1.3000000e+00 2.0000000e+00 1.7000000e+00 1.5000000e+00 6.0000000e-01 2.3000000e+00 2.1000000e+00 1.3000000e+00 1.4000000e+00 1.4000000e+00 1.4000000e+00 9.0000000e-01 1.0000000e+00 3.4000000e+00 3.5000000e+00 2.5000000e+00 9.0000000e-01 1.9000000e+00 3.4000000e+00 1.5000000e+00 1.0000000e+00 2.0000000e+00 1.6000000e+00 1.4000000e+00 9.0000000e-01 2.2000000e+00 2.6000000e+00 3.5000000e+00 8.0000000e-01 1.5000000e+00 2.1000000e+00 2.3000000e+00 7.0000000e-01 8.0000000e-01 1.6000000e+00 9.0000000e-01 8.0000000e-01 8.0000000e-01 1.7000000e+00 1.0000000e+00 1.0000000e+00 6.0000000e-01 1.5000000e+00 7.0000000e-01 5.0000000e-01 1.4000000e+00 3.6000000e+00 3.5000000e+00 2.1000000e+00 1.3000000e+00 1.9000000e+00 2.8000000e+00 1.1000000e+00 1.0000000e+00 1.4000000e+00 1.2000000e+00 1.0000000e+00 7.0000000e-01 1.2000000e+00 1.8000000e+00 3.3000000e+00 8.0000000e-01 1.1000000e+00 1.3000000e+00 2.3000000e+00 1.3000000e+00 2.0000000e-01 1.2000000e+00 9.0000000e-01 1.0000000e+00 1.4000000e+00 1.5000000e+00 1.4000000e+00 1.4000000e+00 1.0000000e+00 1.3000000e+00 5.0000000e-01 1.3000000e+00 1.0000000e+00 1.5000000e+00 5.7000000e+00 2.5000000e+00 5.1000000e+00 1.2000000e+00 4.7000000e+00 2.6000000e+00 2.2000000e+00 4.8000000e+00 4.6000000e+00 3.5000000e+00 2.8000000e+00 2.2000000e+00 7.0000000e-01 3.4000000e+00 4.7000000e+00 4.7000000e+00 1.5000000e+00 3.1000000e+00 3.6000000e+00 4.8000000e+00 2.9000000e+00 3.0000000e+00 3.2000000e+00 4.9000000e+00 2.4000000e+00 2.8000000e+00 3.4000000e+00 4.7000000e+00 3.7000000e+00 3.3000000e+00 4.6000000e+00 4.8000000e+00 2.6000000e+00 4.6000000e+00 7.0000000e-01 4.0000000e+00 3.1000000e+00 2.5000000e+00 4.3000000e+00 4.5000000e+00 3.0000000e+00 2.7000000e+00 1.7000000e+00 2.2000000e+00 2.9000000e+00 4.2000000e+00 3.8000000e+00 1.2000000e+00 3.6000000e+00 3.7000000e+00 4.7000000e+00 3.0000000e+00 2.9000000e+00 3.1000000e+00 4.2000000e+00 2.5000000e+00 3.1000000e+00 3.1000000e+00 3.8000000e+00 3.6000000e+00 3.8000000e+00 4.5000000e+00 3.4000000e+00 1.6000000e+00 4.5000000e+00 1.2000000e+00 3.1000000e+00 3.5000000e+00 1.3000000e+00 1.3000000e+00 2.2000000e+00 2.9000000e+00 3.5000000e+00 5.4000000e+00 2.3000000e+00 1.0000000e+00 1.2000000e+00 4.4000000e+00 3.0000000e+00 2.1000000e+00 1.3000000e+00 2.8000000e+00 3.1000000e+00 2.7000000e+00 1.2000000e+00 3.5000000e+00 3.5000000e+00 2.5000000e+00 1.0000000e+00 2.0000000e+00 2.6000000e+00 1.3000000e+00 2.8000000e+00 2.5000000e+00 2.4000000e+00 5.0000000e-01 1.1000000e+00 2.5000000e+00 2.3000000e+00 1.2000000e+00 1.3000000e+00 1.7000000e+00 2.6000000e+00 1.1000000e+00 2.4000000e+00 2.4000000e+00 1.4000000e+00 1.0000000e+00 1.3000000e+00 2.5000000e+00 6.0000000e-01 5.0000000e-01 7.0000000e-01 2.6000000e+00 3.0000000e-01 5.0000000e-01 9.0000000e-01 2.4000000e+00 1.4000000e+00 1.2000000e+00 2.3000000e+00 3.9000000e+00 1.0000000e+00 2.5000000e+00 3.3000000e+00 9.0000000e-01 9.0000000e-01 1.6000000e+00 3.1000000e+00 3.1000000e+00 4.8000000e+00 1.7000000e+00 1.4000000e+00 2.0000000e+00 3.8000000e+00 2.4000000e+00 1.9000000e+00 9.0000000e-01 2.2000000e+00 2.5000000e+00 2.1000000e+00 6.0000000e-01 2.9000000e+00 2.9000000e+00 1.9000000e+00 1.2000000e+00 1.4000000e+00 2.0000000e+00 9.0000000e-01 3.5000000e+00 2.6000000e+00 1.8000000e+00 3.6000000e+00 3.8000000e+00 2.5000000e+00 2.0000000e+00 1.0000000e+00 1.5000000e+00 2.6000000e+00 3.5000000e+00 3.5000000e+00 1.1000000e+00 3.5000000e+00 3.0000000e+00 4.0000000e+00 2.5000000e+00 2.8000000e+00 3.0000000e+00 3.7000000e+00 2.4000000e+00 3.0000000e+00 3.0000000e+00 3.5000000e+00 2.9000000e+00 3.7000000e+00 3.8000000e+00 2.1000000e+00 2.5000000e+00 3.0000000e-01 5.0000000e-01 1.2000000e+00 2.3000000e+00 2.5000000e+00 4.4000000e+00 1.3000000e+00 6.0000000e-01 1.4000000e+00 3.4000000e+00 2.0000000e+00 1.1000000e+00 7.0000000e-01 1.8000000e+00 2.1000000e+00 1.7000000e+00 8.0000000e-01 2.5000000e+00 2.5000000e+00 1.5000000e+00 4.0000000e-01 1.0000000e+00 1.8000000e+00 9.0000000e-01 1.2000000e+00 2.2000000e+00 2.0000000e+00 9.0000000e-01 1.4000000e+00 1.8000000e+00 2.5000000e+00 1.0000000e+00 2.1000000e+00 2.1000000e+00 1.9000000e+00 9.0000000e-01 1.0000000e+00 2.2000000e+00 7.0000000e-01 6.0000000e-01 1.2000000e+00 2.3000000e+00 6.0000000e-01 4.0000000e-01 1.0000000e+00 2.1000000e+00 1.1000000e+00 1.1000000e+00 2.0000000e+00 2.6000000e+00 2.4000000e+00 1.9000000e+00 6.0000000e-01 8.0000000e-01 1.9000000e+00 2.0000000e+00 2.5000000e+00 2.5000000e+00 1.3000000e+00 2.1000000e+00 1.4000000e+00 2.6000000e+00 1.3000000e+00 1.6000000e+00 1.8000000e+00 2.9000000e+00 1.0000000e+00 1.6000000e+00 2.0000000e+00 2.7000000e+00 1.9000000e+00 2.3000000e+00 2.4000000e+00 4.0000000e-01 1.3000000e+00 2.4000000e+00 2.6000000e+00 4.5000000e+00 1.4000000e+00 7.0000000e-01 1.5000000e+00 3.5000000e+00 2.1000000e+00 1.2000000e+00 4.0000000e-01 1.9000000e+00 2.2000000e+00 1.8000000e+00 9.0000000e-01 2.6000000e+00 2.6000000e+00 1.6000000e+00 7.0000000e-01 1.1000000e+00 1.7000000e+00 8.0000000e-01 1.5000000e+00 2.2000000e+00 2.8000000e+00 4.3000000e+00 1.6000000e+00 9.0000000e-01 1.5000000e+00 3.3000000e+00 1.9000000e+00 1.0000000e+00 2.0000000e-01 1.7000000e+00 2.0000000e+00 1.6000000e+00 9.0000000e-01 2.4000000e+00 2.4000000e+00 1.4000000e+00 9.0000000e-01 9.0000000e-01 1.5000000e+00 4.0000000e-01 1.7000000e+00 1.7000000e+00 3.4000000e+00 1.0000000e-01 1.2000000e+00 1.2000000e+00 2.2000000e+00 1.0000000e+00 7.0000000e-01 1.7000000e+00 1.0000000e+00 9.0000000e-01 1.5000000e+00 1.4000000e+00 1.3000000e+00 1.3000000e+00 1.1000000e+00 1.2000000e+00 8.0000000e-01 1.2000000e+00 1.5000000e+00 1.0000000e+00 2.5000000e+00 1.8000000e+00 1.9000000e+00 1.9000000e+00 1.5000000e+00 2.3000000e+00 1.4000000e+00 2.4000000e+00 1.3000000e+00 1.6000000e+00 1.8000000e+00 2.7000000e+00 1.4000000e+00 1.8000000e+00 1.8000000e+00 2.5000000e+00 1.7000000e+00 2.5000000e+00 2.2000000e+00 1.9000000e+00 1.8000000e+00 2.5000000e+00 2.5000000e+00 9.0000000e-01 2.7000000e+00 2.0000000e+00 3.0000000e+00 1.7000000e+00 2.0000000e+00 2.2000000e+00 2.7000000e+00 1.6000000e+00 2.2000000e+00 2.2000000e+00 2.5000000e+00 2.1000000e+00 2.9000000e+00 2.8000000e+00 3.5000000e+00 4.4000000e+00 4.4000000e+00 1.6000000e+00 3.2000000e+00 3.3000000e+00 4.5000000e+00 2.8000000e+00 3.1000000e+00 3.3000000e+00 4.6000000e+00 2.5000000e+00 2.9000000e+00 3.5000000e+00 4.4000000e+00 3.4000000e+00 3.4000000e+00 4.3000000e+00 1.3000000e+00 1.3000000e+00 2.1000000e+00 9.0000000e-01 8.0000000e-01 1.8000000e+00 1.1000000e+00 8.0000000e-01 1.4000000e+00 1.5000000e+00 1.2000000e+00 1.2000000e+00 1.0000000e+00 1.3000000e+00 9.0000000e-01 1.1000000e+00 1.6000000e+00 1.0000000e+00 3.4000000e+00 2.0000000e+00 1.1000000e+00 1.1000000e+00 1.8000000e+00 2.1000000e+00 1.7000000e+00 1.0000000e+00 2.5000000e+00 2.5000000e+00 1.5000000e+00 8.0000000e-01 1.0000000e+00 1.8000000e+00 9.0000000e-01 3.4000000e+00 2.0000000e+00 1.3000000e+00 1.7000000e+00 2.2000000e+00 2.1000000e+00 2.7000000e+00 1.4000000e+00 2.5000000e+00 2.5000000e+00 2.3000000e+00 1.4000000e+00 1.8000000e+00 2.0000000e+00 1.5000000e+00 2.4000000e+00 2.5000000e+00 3.5000000e+00 1.8000000e+00 1.7000000e+00 1.9000000e+00 3.6000000e+00 1.3000000e+00 1.9000000e+00 1.9000000e+00 3.4000000e+00 2.4000000e+00 2.6000000e+00 3.3000000e+00 1.1000000e+00 2.1000000e+00 1.4000000e+00 7.0000000e-01 1.5000000e+00 2.2000000e+00 1.1000000e+00 7.0000000e-01 1.3000000e+00 2.0000000e+00 1.4000000e+00 4.0000000e-01 1.9000000e+00 1.2000000e+00 9.0000000e-01 1.0000000e+00 1.4000000e+00 1.5000000e+00 1.4000000e+00 1.4000000e+00 1.2000000e+00 1.3000000e+00 7.0000000e-01 1.1000000e+00 1.0000000e+00 1.9000000e+00 2.2000000e+00 1.8000000e+00 9.0000000e-01 2.6000000e+00 2.6000000e+00 1.6000000e+00 1.1000000e+00 1.1000000e+00 1.7000000e+00 4.0000000e-01 7.0000000e-01 5.0000000e-01 2.0000000e+00 9.0000000e-01 1.1000000e+00 7.0000000e-01 1.8000000e+00 8.0000000e-01 1.2000000e+00 1.7000000e+00 8.0000000e-01 2.3000000e+00 6.0000000e-01 4.0000000e-01 6.0000000e-01 2.1000000e+00 1.1000000e+00 1.1000000e+00 2.0000000e+00 1.9000000e+00 1.0000000e+00 1.2000000e+00 4.0000000e-01 1.7000000e+00 9.0000000e-01 1.3000000e+00 1.6000000e+00 2.7000000e+00 2.7000000e+00 1.7000000e+00 8.0000000e-01 1.2000000e+00 1.8000000e+00 5.0000000e-01 6.0000000e-01 1.0000000e+00 2.5000000e+00 1.5000000e+00 1.3000000e+00 2.4000000e+00 1.0000000e+00 2.5000000e+00 1.5000000e+00 1.1000000e+00 2.4000000e+00 1.5000000e+00 5.0000000e-01 1.1000000e+00 1.4000000e+00 1.0000000e+00 1.8000000e+00 1.1000000e+00 1.2000000e+00 9.0000000e-01 1.5000000e+00 diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-cityblock-ml.txt b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-cityblock-ml.txt new file mode 100644 index 0000000000000000000000000000000000000000..8fb22e62200894f9693010ebf45fb23751f4e3a6 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-cityblock-ml.txt @@ -0,0 +1 @@ + 3.2420590e+01 3.3246607e+01 3.0526910e+01 3.5166573e+01 3.1868301e+01 3.6025002e+01 3.2513623e+01 3.6557796e+01 3.3752212e+01 3.4422130e+01 3.2526018e+01 3.2581161e+01 3.3743555e+01 3.6960777e+01 3.4225270e+01 3.2965308e+01 3.4591031e+01 3.4204203e+01 3.4678123e+01 3.5728720e+01 3.0830047e+01 3.1550681e+01 3.3304790e+01 3.2676753e+01 3.2742330e+01 3.1684556e+01 3.2830915e+01 3.2956614e+01 2.7365639e+01 3.3207307e+01 3.3420925e+01 3.4357941e+01 2.8280126e+01 3.4523458e+01 3.2705274e+01 3.2455891e+01 3.1636060e+01 3.1594957e+01 3.1805202e+01 3.3886574e+01 3.3438829e+01 3.3330030e+01 3.4168514e+01 3.0637353e+01 4.2149167e+01 3.6340559e+01 2.9315308e+01 3.5778314e+01 3.7693050e+01 3.2598714e+01 3.2990836e+01 3.4967659e+01 3.9748920e+01 3.6745043e+01 2.7117550e+01 3.6014760e+01 2.9367558e+01 3.3845350e+01 3.5477339e+01 3.1513372e+01 3.2517953e+01 2.4755097e+01 3.0229897e+01 3.4799343e+01 3.3371710e+01 2.9600910e+01 3.3275088e+01 3.3567110e+01 3.4527016e+01 3.4942320e+01 3.2359383e+01 3.2607100e+01 3.1467914e+01 2.9032039e+01 3.3122878e+01 2.8496709e+01 2.9908448e+01 2.9962886e+01 3.0345299e+01 3.1737613e+01 2.8551485e+01 3.2610551e+01 3.3082660e+01 3.3719298e+01 3.6434018e+01 3.6589278e+01 3.3889586e+01 3.8036774e+01 3.1483497e+01 3.4196794e+01 3.5154035e+01 3.5488608e+01 3.6143183e+01 3.3473491e+01 3.4686446e+01 2.8687495e+01 3.5725742e+01 3.0188298e+01 3.3084534e+01 3.3538519e+01 3.6226849e+01 2.9052099e+01 3.6032733e+01 3.0811503e+01 3.2616190e+01 3.3888566e+01 3.3074570e+01 2.9683515e+01 3.0600771e+01 3.4345247e+01 3.6983843e+01 3.3692824e+01 3.3762461e+01 3.4024582e+01 3.3698854e+01 3.1238613e+01 3.4978833e+01 3.4991078e+01 3.4577741e+01 3.3749227e+01 3.4982272e+01 3.0487868e+01 3.2317632e+01 3.1125588e+01 3.4413791e+01 3.1881871e+01 3.1373821e+01 3.0416864e+01 3.2066187e+01 3.1128313e+01 3.0240249e+01 3.0125198e+01 3.1343454e+01 3.5479092e+01 3.4450767e+01 3.2953507e+01 3.4456795e+01 3.0136375e+01 3.3462150e+01 2.9894274e+01 3.1367432e+01 3.2839320e+01 3.1440398e+01 2.9400374e+01 3.1106338e+01 3.1242624e+01 3.5537892e+01 3.3056459e+01 2.8610281e+01 3.4296217e+01 3.5819772e+01 3.2503922e+01 3.0963029e+01 3.4762112e+01 3.4796284e+01 2.9645345e+01 3.4468088e+01 2.6975590e+01 3.3738555e+01 2.8825009e+01 3.2663999e+01 3.2547878e+01 3.2308091e+01 3.2489966e+01 3.0868597e+01 3.2974220e+01 3.0866111e+01 3.8197342e+01 3.0609568e+01 3.5478978e+01 2.9249184e+01 3.6185622e+01 3.1948258e+01 3.2649719e+01 3.3305650e+01 3.4643955e+01 3.6566241e+01 3.4968484e+01 3.2632218e+01 3.6741383e+01 3.5700008e+01 3.1962468e+01 3.1410623e+01 3.0412061e+01 3.3749077e+01 3.5649661e+01 3.7649263e+01 3.2832574e+01 3.1783914e+01 2.8264292e+01 diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-correlation-ml-iris.txt b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-correlation-ml-iris.txt new file mode 100644 index 0000000000000000000000000000000000000000..f297500381fe33428e6725ea3b10ab6191a46fa3 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-correlation-ml-iris.txt @@ -0,0 +1 @@ + 4.0013388e-03 2.6088954e-05 1.8315482e-03 6.5266850e-04 4.1394685e-04 1.1888069e-03 4.6185289e-04 1.9233577e-03 3.4480388e-03 1.5150632e-05 1.9126718e-03 3.0974734e-03 2.2295833e-04 2.4043394e-03 5.0134320e-03 3.0165570e-03 1.3145239e-04 6.0759419e-04 1.6672981e-03 4.0036132e-03 6.1375191e-04 8.5916540e-03 3.0212269e-03 8.6923503e-03 7.7875235e-03 5.1612907e-04 2.9662451e-04 6.2402983e-04 2.7278440e-03 4.0510347e-03 3.0027154e-03 6.2616145e-03 4.1342211e-03 3.4480388e-03 1.5822510e-03 1.7143312e-03 3.4480388e-03 2.2462074e-04 6.1048465e-04 6.5190641e-04 2.4247873e-02 9.0785596e-04 2.1652052e-04 3.4845573e-03 3.2507646e-03 2.3346511e-03 4.0773355e-04 1.1278223e-04 5.0819669e-04 2.1340893e-01 2.1253858e-01 2.5193073e-01 2.9479565e-01 2.6774348e-01 2.8869785e-01 2.3348217e-01 1.9273490e-01 2.4443270e-01 2.4320510e-01 2.7679421e-01 2.1672263e-01 2.6813840e-01 2.8435705e-01 1.5561363e-01 2.0057173e-01 2.7812139e-01 2.2900256e-01 3.4724680e-01 2.3882260e-01 2.9132931e-01 2.0333645e-01 3.5307051e-01 2.8812452e-01 2.1722530e-01 2.1423111e-01 2.7396952e-01 2.9207940e-01 2.6626182e-01 1.7106032e-01 2.4279706e-01 2.2559055e-01 2.0940857e-01 3.8432412e-01 2.9354670e-01 2.0829958e-01 2.3669414e-01 3.0463326e-01 2.1035851e-01 2.6623117e-01 3.0835417e-01 2.5871089e-01 2.3465249e-01 2.0319416e-01 2.6292582e-01 2.1771735e-01 2.3212816e-01 2.2399387e-01 1.3799316e-01 2.3049526e-01 4.8512087e-01 4.2535066e-01 4.0184471e-01 4.1903049e-01 4.4627199e-01 4.4692268e-01 4.3569888e-01 4.2673251e-01 4.5731950e-01 3.7438176e-01 3.0619251e-01 4.0039114e-01 3.7245195e-01 4.5829878e-01 4.5814844e-01 3.6107062e-01 3.7600936e-01 3.7662883e-01 5.2492832e-01 4.2684428e-01 3.7975064e-01 4.0636707e-01 4.6364339e-01 3.4607190e-01 3.6988036e-01 3.6764668e-01 3.2524634e-01 3.1943549e-01 4.4481193e-01 3.5496498e-01 4.1356534e-01 3.2082320e-01 4.5322964e-01 3.4300770e-01 4.4485158e-01 3.9755578e-01 3.9702418e-01 3.7202285e-01 3.1131344e-01 3.4018064e-01 4.0217537e-01 3.1441868e-01 4.2535066e-01 4.1533176e-01 3.9695242e-01 3.5313531e-01 3.9400199e-01 3.4652657e-01 3.6608320e-01 3.6684161e-01 3.3929143e-03 2.6033698e-03 7.7673212e-03 6.4081099e-03 9.2794464e-03 2.8819447e-03 1.4536586e-03 9.6714455e-04 3.7992387e-03 5.9342609e-03 3.9974031e-04 6.0694735e-03 9.1304628e-03 1.7655983e-02 1.1643899e-02 4.0363794e-03 1.6463709e-03 1.0706739e-02 6.7984475e-04 7.6845878e-03 2.3516587e-02 9.9502337e-05 1.0315881e-02 1.0821735e-03 1.8887942e-03 2.4624674e-03 1.5760536e-03 3.6638868e-03 1.6253664e-03 7.8762517e-04 1.9487010e-02 1.6211862e-02 9.6714455e-04 2.2382105e-03 2.1712385e-03 9.6714455e-04 2.9674185e-03 1.9068589e-03 6.4555509e-03 8.8254342e-03 8.1777355e-03 3.4663084e-03 9.6481454e-03 9.7747764e-05 1.0706793e-02 4.2246850e-03 4.9836128e-03 1.6613867e-03 1.6856078e-01 1.6930583e-01 2.0381801e-01 2.4171317e-01 2.1689289e-01 2.4212069e-01 1.9027913e-01 1.5127382e-01 1.9696970e-01 1.9830901e-01 2.2503195e-01 1.7290786e-01 2.1618942e-01 2.3648275e-01 1.1720113e-01 1.5636322e-01 2.3357633e-01 1.8548772e-01 2.8791738e-01 1.9215793e-01 2.4470933e-01 1.5850128e-01 2.9662484e-01 2.4061109e-01 1.7172858e-01 1.6853658e-01 2.2283143e-01 2.4032537e-01 2.1849821e-01 1.2975740e-01 1.9502432e-01 1.7953012e-01 1.6504306e-01 3.2966120e-01 2.4985160e-01 1.6946778e-01 1.8991741e-01 2.4919698e-01 1.7046257e-01 2.1691882e-01 2.6018338e-01 2.1310883e-01 1.8776864e-01 1.5909082e-01 2.1620321e-01 1.7782256e-01 1.8911127e-01 1.7894094e-01 9.9649433e-02 1.8605378e-01 4.2702260e-01 3.6742642e-01 3.4284735e-01 3.6351462e-01 3.8703088e-01 3.8643219e-01 3.8033312e-01 3.6849688e-01 3.9561383e-01 3.1894438e-01 2.5437467e-01 3.4129756e-01 3.1464030e-01 3.9617829e-01 3.9513061e-01 3.0506617e-01 3.2181902e-01 3.2465822e-01 4.5849285e-01 3.6615509e-01 3.2201598e-01 3.4969244e-01 4.0175045e-01 2.8934891e-01 3.1614551e-01 3.1385643e-01 2.7059114e-01 2.6798818e-01 3.8429703e-01 3.0087709e-01 3.5349775e-01 2.7097983e-01 3.9156246e-01 2.9045741e-01 3.8972428e-01 3.3603762e-01 3.4254663e-01 3.1960575e-01 2.6050327e-01 2.8406500e-01 3.4225008e-01 2.5735736e-01 3.6742642e-01 3.5724408e-01 3.3861338e-01 2.9412113e-01 3.3288556e-01 2.9101723e-01 3.1374321e-01 3.1516519e-01 1.6665208e-03 9.3886805e-04 6.2270349e-04 1.5623211e-03 3.9549141e-04 1.6439076e-03 3.0144044e-03 3.0583810e-05 2.0234943e-03 2.6246966e-03 3.8983492e-04 2.5645160e-03 5.6944285e-03 3.3339055e-03 1.2831328e-04 4.1302346e-04 2.1101667e-03 3.4972086e-03 8.7482704e-04 9.4271115e-03 2.5080125e-03 8.7042936e-03 7.0125369e-03 3.5088415e-04 1.9451019e-04 3.9574419e-04 2.5986219e-03 3.6402032e-03 2.4900748e-03 7.0784673e-03 4.7935149e-03 3.0144044e-03 1.2869381e-03 1.4017897e-03 3.0144044e-03 1.7197161e-04 4.4525534e-04 7.8138074e-04 2.2693053e-02 1.2229281e-03 1.5754704e-04 3.7899670e-03 2.6957902e-03 2.7721274e-03 4.5733371e-04 2.2324575e-04 3.1003560e-04 2.1002397e-01 2.0931874e-01 2.4834042e-01 2.9081912e-01 2.6391644e-01 2.8536997e-01 2.3033428e-01 1.8962461e-01 2.4088505e-01 2.3991724e-01 2.7290008e-01 2.1345771e-01 2.6419304e-01 2.8088883e-01 1.5266518e-01 1.9720348e-01 2.7496549e-01 2.2580939e-01 3.4275207e-01 2.3533918e-01 2.8800391e-01 1.9991219e-01 3.4890690e-01 2.8470246e-01 2.1378625e-01 2.1075909e-01 2.7013290e-01 2.8823552e-01 2.6275308e-01 1.6787442e-01 2.3921107e-01 2.2212337e-01 2.0605918e-01 3.8041788e-01 2.9051019e-01 2.0550537e-01 2.3319159e-01 3.0043218e-01 2.0746652e-01 2.6256228e-01 3.0491843e-01 2.5539836e-01 2.3113139e-01 1.9984797e-01 2.5951290e-01 2.1484809e-01 2.2899468e-01 2.2062653e-01 1.3495710e-01 2.2721304e-01 4.8106467e-01 4.2120214e-01 3.9753504e-01 4.1511036e-01 4.4203186e-01 4.4255637e-01 4.3182495e-01 4.2255527e-01 4.5284888e-01 3.7037516e-01 3.0238339e-01 3.9606802e-01 3.6819494e-01 4.5378706e-01 4.5354234e-01 3.5697379e-01 3.7213234e-01 3.7297305e-01 5.2009409e-01 4.2241474e-01 3.7552000e-01 4.0230555e-01 4.5916611e-01 3.4185974e-01 3.6603540e-01 3.6379117e-01 3.2119479e-01 3.1570003e-01 4.4043880e-01 3.5104995e-01 4.0917086e-01 3.1725229e-01 4.4875464e-01 3.3921954e-01 4.4101743e-01 3.9296628e-01 3.9316311e-01 3.6831351e-01 3.0762140e-01 3.3601664e-01 3.9776902e-01 3.1006892e-01 4.2120214e-01 4.1114584e-01 3.9269988e-01 3.4869458e-01 3.8944692e-01 3.4244384e-01 3.6236872e-01 3.6319420e-01 3.2811792e-03 2.1674206e-03 3.8606330e-03 4.5444049e-04 1.6669051e-04 6.9315236e-04 1.5191179e-03 7.4896915e-04 1.0486334e-03 3.0115188e-03 8.3553530e-03 1.1528814e-02 9.5172421e-03 2.7099731e-03 7.1677618e-04 4.9455548e-03 1.1260396e-03 4.0113810e-03 1.7041109e-02 1.7048436e-03 3.2998306e-03 3.5839458e-03 6.5708756e-04 7.5073414e-04 1.6739794e-03 1.4404874e-04 6.5489426e-04 3.9918560e-03 9.7678136e-03 9.5698494e-03 6.9315236e-04 4.1051921e-03 4.2098821e-03 6.9315236e-04 7.7852178e-04 5.0066998e-04 4.6641147e-03 1.9877450e-02 2.9999880e-03 2.6696154e-03 2.3124511e-03 2.6940762e-03 3.6188953e-03 7.8131154e-04 1.7395433e-03 1.1236329e-03 1.8068407e-01 1.7911784e-01 2.1632614e-01 2.5732132e-01 2.3189913e-01 2.4898678e-01 1.9775994e-01 1.6095697e-01 2.0933838e-01 2.0710048e-01 2.4048237e-01 1.8306865e-01 2.3298507e-01 2.4543409e-01 1.2760114e-01 1.6921167e-01 2.3873595e-01 1.9385405e-01 3.0859675e-01 2.0396213e-01 2.5141439e-01 1.7197884e-01 3.1193260e-01 2.4875015e-01 1.8437187e-01 1.8189435e-01 2.3759181e-01 2.5412614e-01 2.2898904e-01 1.4232827e-01 2.0806035e-01 1.9199138e-01 1.7693745e-01 3.4002004e-01 2.5277786e-01 1.7384453e-01 2.0213849e-01 2.6767804e-01 1.7597754e-01 2.2968366e-01 2.6752309e-01 2.2134041e-01 2.0039734e-01 1.7140536e-01 2.2556075e-01 1.8258974e-01 1.9648053e-01 1.9006393e-01 1.1321036e-01 1.9554999e-01 4.3600761e-01 3.7954719e-01 3.5812457e-01 3.7278397e-01 3.9968201e-01 4.0081518e-01 3.8843945e-01 3.8096257e-01 4.1111272e-01 3.3107441e-01 2.6691137e-01 3.5682832e-01 3.3041468e-01 4.1223120e-01 4.1255773e-01 3.1903974e-01 3.3211211e-01 3.3199965e-01 4.7707633e-01 3.8217339e-01 3.3708398e-01 3.6131644e-01 4.1712805e-01 3.0571403e-01 3.2625308e-01 3.2419755e-01 2.8563873e-01 2.7883266e-01 3.9884997e-01 3.1256889e-01 3.6952768e-01 2.7953122e-01 4.0726631e-01 3.0093515e-01 3.9702583e-01 3.5566883e-01 3.5181973e-01 3.2782384e-01 2.7114516e-01 3.0000941e-01 3.5891976e-01 2.7762255e-01 3.7954719e-01 3.7024637e-01 3.5327118e-01 3.1362016e-01 3.5214869e-01 3.0546169e-01 3.2226324e-01 3.2277503e-01 1.1668032e-04 8.6044327e-05 1.4968429e-03 3.9691382e-03 6.2388252e-03 7.0588028e-04 1.9691519e-03 6.1279520e-03 1.9660913e-04 2.5761274e-03 2.5168387e-03 2.4029967e-03 9.7429318e-04 2.3122381e-03 2.3682626e-04 7.1643085e-03 1.3128642e-04 5.3939078e-03 6.2992904e-03 9.0353935e-03 1.2266741e-02 2.0706893e-03 1.5408774e-03 2.5522607e-03 3.9522692e-03 6.6899152e-03 6.3980861e-03 2.9039306e-03 1.6942568e-03 6.2388252e-03 3.9336207e-03 4.1533642e-03 6.2388252e-03 1.2180733e-03 2.1518176e-03 8.8270219e-04 3.2743785e-02 7.7572782e-05 1.3417853e-03 2.5322339e-03 6.7844678e-03 8.2761566e-04 8.6236157e-04 3.1792685e-04 2.2579028e-03 2.2976852e-01 2.2800773e-01 2.6917639e-01 3.1391346e-01 2.8619117e-01 3.0434544e-01 2.4844431e-01 2.0773635e-01 2.6148944e-01 2.5886513e-01 2.9555853e-01 2.3241008e-01 2.8723433e-01 3.0077518e-01 1.6999944e-01 2.1692490e-01 2.9290302e-01 2.4423212e-01 3.6894940e-01 2.5556289e-01 3.0695160e-01 2.1997775e-01 3.7292526e-01 3.0427758e-01 2.3385552e-01 2.3106102e-01 2.9243468e-01 3.1048744e-01 2.8298832e-01 1.8662712e-01 2.6007204e-01 2.4232174e-01 2.2560003e-01 4.0265980e-01 3.0762748e-01 2.2151447e-01 2.5355085e-01 3.2493368e-01 2.2408199e-01 2.8382455e-01 3.2448802e-01 2.7442187e-01 2.5162228e-01 2.1940897e-01 2.7915380e-01 2.3127910e-01 2.4702051e-01 2.4019206e-01 1.5301315e-01 2.4619533e-01 5.0391216e-01 4.4483392e-01 4.2228707e-01 4.3731273e-01 4.6617394e-01 4.6750253e-01 4.5367757e-01 4.4636521e-01 4.7842690e-01 3.9329580e-01 3.2434154e-01 4.2091234e-01 3.9272980e-01 4.7962551e-01 4.7998875e-01 3.8052757e-01 3.9422077e-01 3.9365636e-01 5.4778535e-01 4.4784024e-01 3.9985415e-01 4.2545240e-01 4.8476479e-01 3.6622351e-01 3.8794414e-01 3.8577592e-01 3.4462011e-01 3.3712507e-01 4.6543611e-01 3.7346604e-01 4.3442200e-01 3.3762412e-01 4.7437523e-01 3.6087712e-01 4.6258851e-01 4.1954525e-01 4.1507031e-01 3.8935328e-01 3.2880662e-01 3.6009673e-01 4.2314218e-01 3.3549029e-01 4.4483392e-01 4.3505302e-01 4.1710447e-01 3.7450914e-01 4.1581746e-01 3.6597096e-01 3.8346411e-01 3.8386195e-01 2.7739415e-04 8.2117467e-04 2.7843462e-03 4.7394226e-03 3.9365385e-04 1.1964598e-03 4.7400628e-03 2.5527396e-04 3.2634446e-03 3.7103657e-03 3.3188195e-03 8.6302611e-04 1.5635411e-03 6.0189508e-04 5.5859876e-03 3.8282951e-04 7.0925635e-03 5.0273924e-03 7.3470160e-03 1.0223636e-02 1.3503463e-03 9.3049535e-04 1.9663208e-03 2.7155903e-03 5.0798223e-03 5.5875952e-03 3.5987384e-03 2.6550151e-03 4.7394226e-03 3.5923878e-03 3.7937786e-03 4.7394226e-03 6.6480476e-04 1.3814035e-03 1.1581699e-03 3.0091048e-02 1.0888067e-04 1.1634967e-03 1.9052023e-03 5.6332034e-03 7.9034466e-04 3.5005887e-04 1.0179107e-04 1.6076683e-03 2.2018795e-01 2.1841167e-01 2.5888963e-01 3.0298911e-01 2.7568827e-01 2.9345793e-01 2.3845526e-01 1.9853879e-01 2.5133209e-01 2.4870042e-01 2.8491736e-01 2.2273608e-01 2.7678086e-01 2.8993699e-01 1.6165333e-01 2.0762176e-01 2.8220337e-01 2.3432093e-01 3.5743296e-01 2.4549712e-01 2.9602684e-01 2.1063470e-01 3.6116406e-01 2.9338620e-01 2.2421094e-01 2.2149308e-01 2.8182182e-01 2.9956540e-01 2.7243830e-01 1.7796817e-01 2.4995634e-01 2.3251210e-01 2.1609445e-01 3.9047864e-01 2.9675065e-01 2.1202908e-01 2.4353030e-01 3.1395037e-01 2.1453916e-01 2.7329907e-01 3.1330772e-01 2.6399518e-01 2.4164659e-01 2.1003834e-01 2.6865531e-01 2.2160867e-01 2.3705638e-01 2.3039039e-01 1.4523591e-01 2.3625874e-01 4.9071957e-01 4.3219633e-01 4.0993016e-01 4.2475440e-01 4.5332327e-01 4.5465382e-01 4.4096188e-01 4.3371359e-01 4.6548638e-01 3.8123224e-01 3.1318896e-01 4.0857587e-01 3.8073064e-01 4.6668253e-01 4.6707002e-01 3.6864305e-01 3.8213773e-01 3.8159377e-01 5.3428461e-01 4.3521869e-01 3.8775353e-01 4.1301893e-01 4.7176152e-01 3.5457739e-01 3.7593537e-01 3.7379344e-01 3.3323181e-01 3.2577190e-01 4.5261005e-01 3.6164142e-01 4.2194514e-01 3.2625778e-01 4.6147754e-01 3.4920516e-01 4.4979521e-01 4.0734174e-01 4.0275102e-01 3.7733353e-01 3.1756791e-01 3.4852048e-01 4.1080565e-01 3.2443308e-01 4.3219633e-01 4.2252463e-01 4.0479526e-01 3.6286403e-01 4.0364435e-01 3.5428112e-01 3.7151258e-01 3.7191203e-01 2.0478784e-03 4.7860280e-03 7.2727783e-03 1.2329906e-03 2.0550268e-03 7.3066158e-03 5.3810576e-04 3.2245377e-03 2.1674888e-03 2.7856851e-03 1.6387773e-03 3.1323423e-03 6.7307381e-05 8.3332066e-03 3.3954200e-04 4.9119026e-03 7.6276175e-03 8.9240504e-03 1.3851706e-02 2.8347122e-03 2.2069601e-03 3.5278340e-03 4.3892066e-03 7.6179982e-03 7.9398397e-03 2.0003115e-03 1.2885024e-03 7.2727783e-03 5.1809110e-03 5.4327573e-03 7.2727783e-03 1.7938257e-03 2.8924302e-03 1.4132511e-03 3.5877316e-02 5.5425651e-05 2.1070391e-03 2.2246722e-03 8.2675293e-03 4.8309816e-04 1.2152730e-03 6.6498554e-04 3.1323983e-03 2.3387083e-01 2.3171300e-01 2.7340823e-01 3.1873143e-01 2.9087099e-01 3.0765106e-01 2.5178166e-01 2.1138210e-01 2.6568600e-01 2.6244481e-01 3.0032621e-01 2.3618270e-01 2.9221666e-01 3.0443867e-01 1.7368514e-01 2.2112593e-01 2.9589691e-01 2.4771596e-01 3.7467992e-01 2.5965416e-01 3.1023215e-01 2.2429067e-01 3.7775475e-01 3.0780450e-01 2.3805322e-01 2.3537447e-01 2.9708156e-01 3.1499475e-01 2.8689460e-01 1.9071775e-01 2.6437980e-01 2.4650321e-01 2.2965596e-01 4.0666307e-01 3.1024566e-01 2.2426781e-01 2.5770992e-01 3.3024819e-01 2.2703835e-01 2.8812097e-01 3.2789845e-01 2.7792695e-01 2.5584753e-01 2.2352457e-01 2.8285984e-01 2.3411709e-01 2.5033634e-01 2.4414228e-01 1.5718330e-01 2.4987685e-01 5.0772777e-01 4.4916856e-01 4.2715006e-01 4.4114868e-01 4.7061118e-01 4.7223859e-01 4.5731921e-01 4.5076024e-01 4.8335950e-01 3.9759816e-01 3.2864932e-01 4.2581769e-01 3.9765561e-01 4.8465345e-01 4.8525127e-01 3.8513607e-01 3.9820652e-01 3.9712803e-01 5.5326906e-01 4.5284577e-01 4.0466523e-01 4.2968979e-01 4.8967945e-01 3.7122633e-01 3.9189300e-01 3.8976354e-01 3.4937792e-01 3.4115590e-01 4.7020033e-01 3.7767688e-01 4.3942138e-01 3.4125876e-01 4.7934102e-01 3.6486747e-01 4.6609349e-01 4.2514437e-01 4.1889348e-01 3.9297481e-01 3.3279384e-01 3.6502235e-01 4.2824304e-01 3.4111505e-01 4.4916856e-01 4.3953404e-01 4.2185806e-01 3.8004789e-01 4.2135185e-01 3.7064704e-01 3.8713375e-01 3.8737322e-01 5.9378937e-04 1.6263483e-03 3.1194349e-04 8.5089275e-04 1.6365846e-03 1.1579874e-03 4.9430863e-03 7.7957878e-03 5.8209267e-03 9.7423596e-04 2.1559031e-04 2.8280232e-03 2.1261057e-03 1.8496545e-03 1.2342594e-02 1.9347552e-03 5.4995961e-03 5.2624400e-03 1.3773080e-04 7.1496401e-05 7.1145768e-04 9.6706058e-04 1.9028496e-03 3.0842001e-03 7.5087003e-03 6.3709632e-03 1.6263483e-03 2.4219636e-03 2.5416684e-03 1.6263483e-03 4.5881830e-05 1.0508341e-04 2.2101780e-03 2.1711060e-02 1.4779987e-03 1.0004664e-03 2.4029906e-03 2.5527616e-03 2.4859397e-03 1.2918144e-04 4.6388898e-04 3.7292268e-04 1.9674800e-01 1.9551090e-01 2.3384591e-01 2.7581575e-01 2.4956432e-01 2.6852776e-01 2.1529223e-01 1.7652116e-01 2.2659936e-01 2.2483764e-01 2.5838801e-01 1.9958250e-01 2.5031938e-01 2.6459593e-01 1.4127677e-01 1.8459488e-01 2.5809699e-01 2.1110498e-01 3.2773890e-01 2.2109976e-01 2.7105695e-01 1.8736680e-01 3.3227725e-01 2.6813261e-01 2.0050552e-01 1.9777445e-01 2.5552383e-01 2.7284453e-01 2.4733170e-01 1.5638593e-01 2.2514733e-01 2.0849921e-01 1.9287136e-01 3.6191982e-01 2.7281863e-01 1.9071038e-01 2.1912633e-01 2.8593968e-01 1.9281596e-01 2.4768186e-01 2.8763479e-01 2.3971138e-01 2.1723753e-01 1.8699948e-01 2.4393940e-01 1.9979806e-01 2.1397418e-01 2.0672587e-01 1.2529166e-01 2.1270878e-01 4.6033946e-01 4.0222602e-01 3.7977808e-01 3.9565939e-01 4.2276626e-01 4.2367223e-01 4.1181849e-01 4.0362790e-01 4.3403229e-01 3.5248619e-01 2.8628855e-01 3.7840466e-01 3.5121985e-01 4.3508597e-01 4.3518504e-01 3.3982310e-01 3.5380487e-01 3.5403534e-01 5.0086936e-01 4.0431760e-01 3.5820087e-01 3.8360776e-01 4.4020334e-01 3.2567463e-01 3.4780712e-01 3.4566354e-01 3.0520609e-01 2.9886193e-01 4.2163480e-01 3.3351442e-01 3.9135037e-01 2.9988740e-01 4.3006423e-01 3.2170490e-01 4.2068520e-01 3.7643926e-01 3.7416586e-01 3.4964963e-01 2.9095264e-01 3.1987117e-01 3.8035231e-01 2.9582266e-01 4.0222602e-01 3.9256940e-01 3.7489846e-01 3.3318735e-01 3.7289799e-01 3.2576051e-01 3.4389968e-01 3.4452790e-01 2.6022528e-04 1.6357171e-03 1.5776794e-03 3.8821876e-04 3.3383101e-03 8.1348232e-03 1.2673676e-02 9.6454978e-03 2.5951087e-03 4.7710550e-04 5.9640558e-03 5.0935416e-04 4.5005321e-03 1.8295131e-02 8.0881241e-04 4.5548809e-03 2.4342161e-03 4.9437559e-04 7.1285200e-04 1.1827453e-03 5.3640347e-04 3.9274104e-04 2.7126384e-03 1.1795471e-02 1.0834355e-02 2.6022528e-04 3.1798475e-03 3.2407554e-03 2.6022528e-04 8.6271302e-04 3.7982619e-04 4.6816553e-03 1.6563494e-02 3.8583498e-03 2.4190920e-03 3.6876972e-03 1.5540748e-03 4.9548680e-03 1.1822578e-03 2.1020911e-03 7.8943086e-04 1.7690932e-01 1.7592254e-01 2.1244374e-01 2.5265403e-01 2.2738778e-01 2.4651370e-01 1.9514374e-01 1.5780110e-01 2.0549282e-01 2.0415245e-01 2.3585798e-01 1.7978451e-01 2.2802648e-01 2.4243736e-01 1.2428265e-01 1.6526016e-01 2.3669421e-01 1.9101658e-01 3.0265561e-01 2.0025949e-01 2.4898144e-01 1.6786898e-01 3.0733331e-01 2.4595698e-01 1.8046568e-01 1.7781045e-01 2.3314059e-01 2.4991164e-01 2.2560913e-01 1.3845901e-01 2.0404813e-01 1.8812805e-01 1.7322140e-01 3.3666075e-01 2.5129679e-01 1.7201674e-01 1.9833201e-01 2.6229128e-01 1.7386253e-01 2.2573363e-01 2.6492814e-01 2.1852944e-01 1.9648937e-01 1.6758641e-01 2.2246603e-01 1.8066071e-01 1.9389274e-01 1.8653629e-01 1.0911288e-01 1.9242842e-01 4.3299781e-01 3.7574545e-01 3.5353088e-01 3.6969868e-01 3.9574789e-01 3.9644649e-01 3.8564737e-01 3.7707427e-01 4.0646475e-01 3.2727058e-01 2.6301141e-01 3.5217112e-01 3.2569733e-01 4.0744443e-01 4.0742674e-01 3.1477415e-01 3.2876954e-01 3.2939944e-01 4.7166141e-01 3.7739415e-01 3.3254323e-01 3.5763917e-01 4.1251084e-01 3.0085067e-01 3.2295735e-01 3.2084322e-01 2.8110727e-01 2.7535557e-01 3.9443856e-01 3.0887601e-01 3.6474538e-01 2.7663010e-01 4.0256676e-01 2.9754793e-01 3.9443683e-01 3.4998590e-01 3.4873324e-01 3.2500314e-01 2.6771990e-01 2.9525187e-01 3.5397733e-01 2.7178914e-01 3.7574545e-01 3.6622316e-01 3.4883278e-01 3.0797294e-01 3.4655783e-01 3.0107914e-01 3.1936679e-01 3.2010756e-01 3.0868881e-03 2.8691382e-03 1.3643967e-04 5.3429196e-03 1.0581034e-02 1.6459895e-02 1.2551534e-02 4.1576220e-03 1.2160579e-03 8.7064401e-03 5.4418849e-05 6.8214146e-03 2.2732626e-02 5.6586090e-04 4.9831593e-03 1.1314391e-03 1.3090644e-03 1.7262411e-03 1.9770194e-03 1.0389613e-03 9.3641634e-05 2.8067647e-03 1.5471006e-02 1.4411876e-02 0.0000000e+00 4.0251529e-03 4.0402030e-03 0.0000000e+00 2.0104773e-03 1.1579294e-03 6.7733512e-03 1.3328703e-02 6.1195429e-03 3.8280621e-03 5.4471697e-03 1.3203495e-03 7.3930866e-03 2.5511055e-03 3.8011014e-03 1.5935161e-03 1.6488937e-01 1.6420208e-01 1.9946134e-01 2.3841307e-01 2.1377841e-01 2.3352484e-01 1.8323739e-01 1.4660873e-01 1.9269692e-01 1.9183992e-01 2.2200730e-01 1.6791595e-01 2.1423078e-01 2.2922867e-01 1.1407468e-01 1.5349471e-01 2.2418179e-01 1.7908948e-01 2.8692621e-01 1.8765985e-01 2.3596576e-01 1.5596498e-01 2.9203641e-01 2.3278981e-01 1.6829150e-01 1.6563525e-01 2.1942293e-01 2.3592536e-01 2.1256421e-01 1.2755291e-01 1.9121342e-01 1.7576718e-01 1.6132929e-01 3.2148865e-01 2.3885357e-01 1.6118125e-01 1.8573297e-01 2.4757051e-01 1.6279862e-01 2.1240660e-01 2.5149161e-01 2.0595435e-01 1.8389163e-01 1.5580881e-01 2.0964365e-01 1.6953436e-01 1.8203376e-01 1.7437090e-01 9.9184065e-02 1.8031354e-01 4.1664204e-01 3.5971944e-01 3.3744612e-01 3.5416868e-01 3.7936058e-01 3.7982315e-01 3.7006184e-01 3.6098196e-01 3.8956200e-01 3.1201251e-01 2.4889912e-01 3.3607844e-01 3.1002022e-01 3.9046127e-01 3.9028467e-01 2.9949936e-01 3.1373736e-01 3.1479561e-01 4.5355800e-01 3.6085089e-01 3.1682959e-01 3.4195608e-01 3.9553949e-01 2.8555816e-01 3.0804941e-01 3.0593834e-01 2.6633772e-01 2.6121338e-01 3.7782244e-01 2.9399549e-01 3.4839508e-01 2.6278429e-01 3.8569374e-01 2.8303600e-01 3.7885421e-01 3.3349568e-01 3.3352424e-01 3.1033775e-01 2.5375578e-01 2.8011018e-01 3.3772574e-01 2.5671985e-01 3.5971944e-01 3.5022311e-01 3.3289784e-01 2.9224130e-01 3.3016010e-01 2.8599652e-01 3.0475125e-01 3.0561759e-01 1.6232219e-03 2.8110674e-03 3.0992704e-04 2.8009412e-03 5.3859157e-03 3.4428307e-03 2.2389965e-04 4.8865483e-04 1.7600776e-03 3.6417064e-03 7.4576509e-04 9.1296419e-03 2.8123816e-03 8.0068765e-03 7.3379880e-03 3.9579356e-04 1.9713653e-04 6.0194434e-04 2.3367622e-03 3.6239908e-03 3.0185118e-03 6.3202257e-03 4.4046298e-03 3.0868881e-03 1.7141363e-03 1.8461990e-03 3.0868881e-03 1.2729358e-04 4.6571753e-04 8.6393185e-04 2.3903803e-02 9.0441478e-04 3.0528309e-04 3.1648190e-03 3.1223857e-03 2.2339929e-03 2.7724170e-04 9.5102356e-05 4.3913729e-04 2.1061066e-01 2.0964712e-01 2.4888926e-01 2.9163776e-01 2.6471732e-01 2.8522189e-01 2.3035873e-01 1.8998369e-01 2.4143327e-01 2.4006731e-01 2.7373417e-01 2.1381600e-01 2.6519773e-01 2.8097864e-01 1.5319882e-01 1.9790190e-01 2.7464921e-01 2.2594124e-01 3.4406181e-01 2.3583526e-01 2.8783351e-01 2.0067586e-01 3.4959337e-01 2.8469567e-01 2.1442337e-01 2.1148410e-01 2.7089383e-01 2.8885413e-01 2.6304757e-01 1.6861411e-01 2.3983808e-01 2.2272157e-01 2.0662897e-01 3.8050402e-01 2.8992390e-01 2.0524163e-01 2.3373909e-01 3.0156183e-01 2.0732304e-01 2.6311208e-01 3.0478863e-01 2.5545595e-01 2.3172924e-01 2.0047959e-01 2.5968741e-01 2.1460680e-01 2.2900957e-01 2.2107714e-01 1.3590375e-01 2.2746759e-01 4.8087096e-01 4.2142796e-01 3.9814594e-01 4.1502871e-01 4.4228995e-01 4.4300705e-01 4.3159396e-01 4.2281768e-01 4.5341178e-01 3.7067256e-01 3.0283474e-01 3.9670954e-01 3.6890449e-01 4.5441105e-01 4.5432159e-01 3.5749751e-01 3.7222314e-01 3.7273757e-01 5.2092654e-01 4.2307513e-01 3.7613897e-01 4.0250144e-01 4.5970761e-01 3.4267709e-01 3.6611458e-01 3.6389952e-01 3.2189682e-01 3.1593975e-01 4.4091119e-01 3.5132753e-01 4.0985044e-01 3.1723537e-01 4.4934566e-01 3.3938050e-01 4.4068685e-01 3.9407776e-01 3.9311145e-01 3.6818097e-01 3.0785214e-01 3.3679573e-01 3.9853670e-01 3.1138703e-01 4.2142796e-01 4.1148317e-01 3.9324794e-01 3.4985868e-01 3.9052142e-01 3.4304315e-01 3.6227810e-01 3.6300235e-01 3.5297445e-03 2.3993465e-03 8.1469449e-03 8.4116551e-03 8.4748907e-03 3.0443320e-03 1.8587915e-03 2.8140158e-03 3.7033592e-03 2.9317197e-03 1.3265454e-02 4.5015236e-03 2.6265400e-03 7.5415562e-03 1.6353241e-03 1.4046892e-03 3.1172532e-03 6.0159720e-04 2.6265400e-03 7.0517547e-03 5.5270478e-03 6.4518235e-03 2.8691382e-03 6.1014438e-03 6.3013427e-03 2.8691382e-03 1.1615614e-03 1.4498289e-03 4.4069463e-03 2.8268404e-02 1.4610903e-03 3.3082950e-03 4.5136771e-04 5.8170191e-03 1.2876475e-03 5.5964987e-04 1.3152056e-03 2.3446691e-03 1.9624436e-01 1.9365486e-01 2.3272539e-01 2.7570910e-01 2.4962218e-01 2.6341222e-01 2.1161968e-01 1.7505868e-01 2.2555325e-01 2.2171510e-01 2.5853430e-01 1.9783603e-01 2.5146360e-01 2.6075203e-01 1.4121466e-01 1.8483453e-01 2.5221719e-01 2.0803875e-01 3.2980663e-01 2.1983733e-01 2.6580206e-01 1.8792174e-01 3.3096405e-01 2.6375229e-01 2.0022607e-01 1.9799659e-01 2.5530781e-01 2.7170116e-01 2.4472945e-01 1.5721426e-01 2.2453551e-01 2.0791989e-01 1.9232403e-01 3.5721533e-01 2.6541719e-01 1.8583170e-01 2.1815822e-01 2.8740780e-01 1.8853156e-01 2.4642239e-01 2.8243457e-01 2.3594931e-01 2.1655773e-01 1.8685267e-01 2.4074340e-01 1.9492750e-01 2.1026662e-01 2.0538411e-01 1.2769778e-01 2.1026662e-01 4.5350142e-01 3.9795187e-01 3.7769065e-01 3.8983341e-01 4.1851849e-01 4.2042793e-01 4.0510768e-01 3.9953508e-01 4.3130939e-01 3.4895812e-01 2.8413412e-01 3.7648942e-01 3.4987508e-01 4.3267862e-01 4.3359924e-01 3.3758650e-01 3.4918773e-01 3.4772733e-01 4.9915788e-01 4.0231358e-01 3.5632097e-01 3.7931174e-01 4.3732697e-01 3.2511301e-01 3.4317696e-01 3.4120231e-01 3.0420772e-01 2.9548175e-01 4.1851849e-01 3.3003548e-01 3.8954108e-01 2.9516106e-01 4.2751620e-01 3.1771485e-01 4.1340131e-01 3.7704473e-01 3.6865281e-01 3.4390717e-01 2.8759676e-01 3.1915503e-01 3.7909158e-01 2.9821954e-01 3.9795187e-01 3.8894782e-01 3.7251565e-01 3.3442155e-01 3.7333196e-01 3.2403985e-01 3.3841983e-01 3.3852014e-01 4.9713816e-03 9.2903476e-03 1.5944722e-02 1.1386125e-02 3.5402300e-03 9.6201776e-04 8.6911918e-03 9.4953207e-05 6.4447745e-03 2.1940505e-02 1.4660203e-04 6.6774582e-03 1.0681613e-03 1.0987220e-03 1.5397922e-03 1.3925202e-03 1.6744918e-03 4.5493239e-04 1.7274513e-03 1.6063124e-02 1.4167352e-02 1.3643967e-04 2.8974888e-03 2.8893579e-03 1.3643967e-04 1.8844794e-03 1.0195101e-03 5.9835494e-03 1.1907169e-02 6.2144210e-03 3.1461009e-03 6.4603159e-03 6.0804116e-04 7.9398115e-03 2.6608779e-03 3.6611489e-03 1.1878605e-03 1.6827914e-01 1.6808878e-01 2.0330224e-01 2.4208927e-01 2.1725982e-01 2.3901101e-01 1.8791090e-01 1.5022413e-01 1.9646923e-01 1.9636982e-01 2.2550082e-01 1.7178784e-01 2.1730098e-01 2.3423675e-01 1.1690649e-01 1.5652486e-01 2.2988556e-01 1.8351752e-01 2.8999988e-01 1.9148148e-01 2.4151451e-01 1.5889324e-01 2.9641885e-01 2.3800995e-01 1.7162027e-01 1.6875690e-01 2.2303923e-01 2.3997805e-01 2.1702847e-01 1.3016155e-01 1.9481438e-01 1.7925806e-01 1.6471086e-01 3.2723835e-01 2.4516700e-01 1.6613136e-01 1.8943302e-01 2.5069233e-01 1.6755119e-01 2.1637456e-01 2.5710224e-01 2.1080083e-01 1.8747239e-01 1.5900127e-01 2.1430731e-01 1.7454101e-01 1.8671199e-01 1.7813660e-01 1.0093430e-01 1.8452279e-01 4.2348788e-01 3.6545764e-01 3.4229982e-01 3.6044592e-01 3.8515670e-01 3.8525367e-01 3.7671058e-01 3.6665866e-01 3.9483186e-01 3.1729700e-01 2.5339335e-01 3.4086300e-01 3.1448994e-01 3.9561710e-01 3.9513385e-01 3.0425707e-01 3.1942407e-01 3.2109044e-01 4.5863634e-01 3.6575820e-01 3.2152631e-01 3.4763745e-01 4.0088503e-01 2.8963041e-01 3.1371721e-01 3.1153634e-01 2.7048666e-01 2.6621754e-01 3.8319922e-01 2.9918600e-01 3.5318564e-01 2.6828212e-01 3.9088644e-01 2.8836369e-01 3.8573578e-01 3.3732047e-01 3.3961180e-01 3.1641339e-01 2.5871452e-01 2.8421639e-01 3.4227214e-01 2.5952711e-01 3.6545764e-01 3.5568934e-01 3.3784386e-01 2.9565951e-01 3.3403769e-01 2.9050448e-01 3.1070970e-01 3.1176741e-01 1.7225353e-03 3.1252650e-03 1.9141697e-03 3.0572974e-04 1.5621195e-03 7.7657730e-04 6.0730841e-03 9.6969946e-05 6.0804497e-03 4.8728559e-03 1.0019276e-02 1.0630759e-02 1.4028596e-03 1.0008907e-03 1.5106647e-03 3.9425625e-03 5.9922357e-03 4.5089760e-03 4.5296071e-03 2.4544132e-03 5.3429196e-03 2.3927105e-03 2.5658780e-03 5.3429196e-03 8.0759380e-04 1.5345623e-03 3.2653860e-04 2.8784931e-02 4.6959359e-04 5.2961514e-04 3.5529133e-03 5.0787251e-03 1.7528094e-03 7.9750642e-04 1.8469304e-04 1.3949343e-03 2.2601194e-01 2.2489065e-01 2.6538416e-01 3.0933559e-01 2.8173337e-01 3.0217927e-01 2.4601138e-01 2.0461695e-01 2.5772236e-01 2.5608721e-01 2.9099324e-01 2.2920421e-01 2.8227951e-01 2.9802519e-01 1.6660589e-01 2.1294215e-01 2.9118999e-01 2.4154604e-01 3.6304829e-01 2.5194222e-01 3.0483437e-01 2.1582549e-01 3.6852765e-01 3.0175865e-01 2.2996178e-01 2.2696306e-01 2.8805620e-01 3.0640689e-01 2.7978083e-01 1.8266242e-01 2.5611673e-01 2.3849427e-01 2.2189938e-01 3.9968280e-01 3.0655638e-01 2.1989254e-01 2.4981061e-01 3.1957344e-01 2.2214967e-01 2.7998655e-01 3.2222399e-01 2.7182647e-01 2.4776514e-01 2.1557967e-01 2.7625427e-01 2.2956697e-01 2.4461597e-01 2.3673300e-01 1.4870158e-01 2.4319918e-01 5.0146439e-01 4.4143183e-01 4.1797463e-01 4.3468983e-01 4.6265612e-01 4.6350594e-01 4.5140079e-01 4.4286970e-01 4.7413628e-01 3.8981309e-01 3.2063561e-01 4.1652733e-01 3.8823357e-01 4.7518267e-01 4.7516433e-01 3.7651294e-01 3.9124904e-01 3.9150135e-01 5.4273573e-01 4.4336023e-01 3.9556546e-01 4.2215915e-01 4.8051706e-01 3.6152094e-01 3.8501414e-01 3.8277781e-01 3.4024930e-01 3.3391004e-01 4.6138930e-01 3.7007400e-01 4.2991865e-01 3.3504580e-01 4.7002175e-01 3.5780202e-01 4.6054739e-01 4.1401655e-01 4.1241306e-01 3.8694896e-01 3.2563422e-01 3.5550143e-01 4.1844379e-01 3.2964964e-01 4.4143183e-01 4.3139176e-01 4.1295631e-01 3.6894646e-01 4.1038562e-01 3.6180237e-01 3.8096707e-01 3.8161756e-01 2.9059655e-03 2.3706161e-04 1.5622923e-03 4.6985391e-03 3.0899699e-03 1.1144685e-02 1.5452086e-03 4.3912180e-03 8.2378168e-03 1.9894938e-02 1.6022015e-02 4.6248887e-03 4.1434850e-03 3.4955495e-03 1.0239539e-02 1.2012056e-02 5.3911313e-03 8.1025404e-03 3.3141536e-03 1.0581034e-02 2.5128050e-03 2.6447621e-03 1.0581034e-02 4.0444636e-03 5.0265914e-03 5.7591609e-04 3.1111838e-02 3.5439088e-03 1.6935296e-03 1.0096079e-02 7.4536930e-03 6.1909555e-03 4.6446420e-03 2.9301624e-03 3.9714148e-03 2.5068010e-01 2.5098525e-01 2.9216380e-01 3.3638336e-01 3.0774280e-01 3.3430586e-01 2.7479523e-01 2.2946729e-01 2.8415608e-01 2.8467354e-01 3.1719659e-01 2.5534962e-01 3.0706036e-01 3.2874800e-01 1.8820706e-01 2.3625921e-01 3.2370915e-01 2.6954509e-01 3.8903955e-01 2.7840834e-01 3.3718014e-01 2.3890051e-01 3.9852630e-01 3.3314087e-01 2.5453269e-01 2.5085481e-01 3.1457631e-01 3.3452242e-01 3.0863300e-01 2.0400912e-01 2.8201538e-01 2.6372052e-01 2.4645516e-01 4.3401653e-01 3.4099078e-01 2.4885351e-01 2.7587880e-01 3.4510687e-01 2.5062404e-01 3.0740449e-01 3.5503881e-01 3.0161400e-01 2.7344211e-01 2.3943815e-01 3.0560987e-01 2.5890901e-01 2.7338361e-01 2.6272911e-01 1.6653571e-01 2.7061027e-01 5.3996294e-01 4.7622847e-01 4.4996413e-01 4.7092027e-01 4.9784258e-01 4.9765553e-01 4.8884384e-01 4.7750758e-01 5.0792579e-01 4.2270376e-01 3.5026703e-01 4.4829833e-01 4.1874266e-01 5.0865069e-01 5.0773433e-01 4.0771387e-01 4.2529697e-01 4.2723724e-01 5.7654330e-01 4.7578493e-01 4.2683086e-01 4.5657562e-01 5.1458642e-01 3.9050926e-01 4.1892650e-01 4.1646419e-01 3.6916765e-01 3.6521270e-01 4.9536279e-01 4.0243409e-01 4.6185742e-01 3.6775616e-01 5.0354755e-01 3.9037876e-01 4.9872590e-01 4.4290716e-01 4.4784995e-01 4.2202156e-01 3.5667822e-01 3.8450868e-01 4.4953729e-01 3.5436611e-01 4.7622847e-01 4.6530251e-01 4.4515647e-01 3.9606646e-01 4.3939461e-01 3.9207838e-01 4.1563418e-01 4.1682072e-01 1.5609953e-03 4.8490070e-03 9.0958370e-03 1.4989091e-03 1.7813868e-02 2.1261488e-03 5.5450301e-04 1.5694148e-02 1.9384410e-02 2.5209737e-02 8.6954304e-03 7.6213095e-03 8.7105486e-03 1.2654300e-02 1.7334342e-02 1.3883194e-02 2.2522789e-03 1.7443565e-04 1.6459895e-02 9.1718824e-03 9.4916987e-03 1.6459895e-02 6.9918464e-03 8.9833757e-03 2.9662983e-03 4.9266036e-02 2.9055422e-03 5.5731976e-03 8.0889759e-03 1.5739049e-02 3.7597322e-03 6.3200695e-03 4.4644236e-03 8.6395939e-03 2.7692079e-01 2.7499135e-01 3.1941212e-01 3.6719225e-01 3.3760508e-01 3.5656667e-01 2.9687968e-01 2.5298236e-01 3.1115442e-01 3.0819394e-01 3.4760947e-01 2.7976620e-01 3.3861131e-01 3.5300749e-01 2.1161836e-01 2.6294922e-01 3.4413759e-01 2.9242527e-01 4.2523864e-01 3.0477646e-01 3.5931443e-01 2.6623770e-01 4.2973462e-01 3.5665121e-01 2.8133678e-01 2.7828246e-01 3.4429599e-01 3.6357656e-01 3.3414792e-01 2.2982660e-01 3.0962503e-01 2.9049749e-01 2.7240490e-01 4.6073277e-01 3.5937207e-01 2.6744708e-01 3.0261187e-01 3.7874332e-01 2.7038484e-01 3.3511133e-01 3.7800754e-01 3.2481692e-01 3.0053112e-01 2.6567213e-01 3.2997477e-01 2.7805229e-01 2.9533686e-01 2.8819780e-01 1.9246360e-01 2.9461561e-01 5.6604002e-01 5.0500114e-01 4.8160322e-01 4.9684759e-01 5.2728223e-01 5.2878056e-01 5.1374073e-01 5.0662675e-01 5.4019822e-01 4.5105935e-01 3.7828674e-01 4.8016813e-01 4.5059090e-01 5.4146236e-01 5.4186046e-01 4.3772456e-01 4.5187440e-01 4.5090747e-01 6.1216296e-01 5.0833718e-01 4.5807252e-01 4.8471571e-01 5.4678369e-01 4.2264992e-01 4.4526564e-01 4.4301133e-01 3.9982165e-01 3.9174986e-01 5.2663733e-01 4.3017652e-01 4.9431552e-01 3.9206379e-01 5.3598953e-01 4.1681568e-01 5.2288475e-01 4.7864082e-01 4.7360696e-01 4.4651927e-01 3.8292357e-01 4.1618542e-01 4.8251093e-01 3.8978460e-01 5.0500114e-01 4.9485605e-01 4.7615846e-01 4.3123359e-01 4.7475023e-01 4.2239197e-01 4.4037545e-01 4.4066833e-01 2.2696323e-03 5.9674345e-03 2.4448133e-03 1.3335241e-02 1.4550127e-03 2.5899751e-03 1.0462849e-02 2.0483763e-02 1.9016265e-02 5.8048658e-03 5.1341121e-03 4.8749246e-03 1.1285550e-02 1.3907377e-02 7.6337585e-03 6.3180508e-03 2.0267734e-03 1.2551534e-02 4.1078635e-03 4.2919330e-03 1.2551534e-02 4.8854397e-03 6.2043549e-03 8.9134425e-04 3.6466805e-02 3.2782414e-03 2.5696799e-03 9.8307089e-03 9.8120264e-03 5.5450409e-03 5.1728098e-03 3.2838118e-03 5.2555481e-03 2.6123905e-01 2.6103645e-01 3.0329747e-01 3.4864780e-01 3.1958423e-01 3.4458162e-01 2.8459400e-01 2.3921225e-01 2.9516933e-01 2.9488851e-01 3.2923215e-01 2.6553322e-01 3.1926739e-01 3.3946038e-01 1.9751618e-01 2.4678743e-01 3.3347141e-01 2.7948950e-01 4.0283567e-01 2.8923185e-01 3.4744486e-01 2.4959732e-01 4.1128993e-01 3.4370645e-01 2.6525258e-01 2.6168140e-01 3.2643822e-01 3.4638099e-01 3.1949478e-01 2.1402441e-01 2.9314941e-01 2.7451414e-01 2.5691069e-01 4.4594923e-01 3.5036767e-01 2.5760360e-01 2.8676453e-01 3.5805235e-01 2.5967094e-01 3.1875975e-01 3.6562012e-01 3.1188296e-01 2.8438818e-01 2.4989387e-01 3.1618152e-01 2.6787548e-01 2.8314006e-01 2.7321717e-01 1.7615044e-01 2.8082637e-01 5.5224352e-01 4.8885819e-01 4.6311762e-01 4.8285549e-01 5.1073035e-01 5.1093091e-01 5.0061869e-01 4.9022404e-01 5.2151073e-01 4.3495694e-01 3.6199911e-01 4.6149506e-01 4.3176986e-01 5.2236122e-01 5.2173621e-01 4.2026138e-01 4.3714978e-01 4.3841019e-01 5.9117531e-01 4.8927834e-01 4.3976762e-01 4.6895974e-01 5.2818477e-01 4.0343191e-01 4.3068816e-01 4.2826096e-01 3.8162190e-01 3.7670056e-01 5.0866155e-01 4.1442940e-01 4.7525840e-01 3.7873655e-01 5.1715063e-01 4.0199874e-01 5.1036877e-01 4.5693051e-01 4.5962906e-01 4.3336423e-01 3.6804311e-01 3.9729031e-01 4.6298867e-01 3.6775880e-01 4.8885819e-01 4.7805886e-01 4.5813974e-01 4.0968699e-01 4.5331588e-01 4.0460089e-01 4.2699969e-01 4.2797954e-01 8.7894099e-04 2.0541035e-03 4.6305984e-03 6.7282118e-04 8.1166178e-03 3.1872717e-03 1.0880321e-02 8.3605717e-03 8.2577841e-04 6.1811314e-04 5.6936180e-04 3.8806955e-03 4.9821714e-03 2.5043588e-03 7.1844876e-03 4.2468498e-03 4.1576220e-03 9.9353221e-04 1.1055786e-03 4.1576220e-03 5.9733473e-04 9.9247040e-04 3.2081104e-04 2.3598005e-02 1.4307976e-03 3.0934240e-05 4.9322953e-03 3.1299098e-03 3.2877683e-03 9.9769768e-04 4.3968696e-04 6.2770216e-04 2.1786575e-01 2.1746374e-01 2.5686278e-01 2.9961632e-01 2.7235218e-01 2.9521657e-01 2.3917491e-01 1.9737534e-01 2.4929109e-01 2.4877587e-01 2.8142376e-01 2.2163964e-01 2.7235613e-01 2.9042411e-01 1.5945203e-01 2.0466964e-01 2.8483074e-01 2.3445929e-01 3.5153649e-01 2.4372083e-01 2.9790924e-01 2.0734139e-01 3.5860995e-01 2.9439415e-01 2.2162942e-01 2.1843466e-01 2.7871280e-01 2.9725443e-01 2.7179832e-01 1.7471289e-01 2.4749127e-01 2.3015922e-01 2.1385216e-01 3.9117409e-01 3.0083879e-01 2.1421404e-01 2.4147478e-01 3.0893498e-01 2.1609222e-01 2.7129944e-01 3.1500957e-01 2.6459749e-01 2.3931769e-01 2.0744985e-01 2.6864595e-01 2.2369874e-01 2.3782351e-01 2.2882042e-01 1.4076875e-01 2.3574843e-01 4.9305396e-01 4.3221625e-01 4.0786305e-01 4.2639713e-01 4.5320351e-01 4.5351054e-01 4.4342223e-01 4.3354113e-01 4.6376094e-01 3.8078797e-01 3.1179824e-01 4.0634323e-01 3.7808795e-01 4.6463298e-01 4.6419251e-01 3.6697079e-01 3.8279432e-01 3.8398235e-01 5.3121485e-01 4.3292773e-01 3.8560639e-01 4.1316779e-01 4.7015919e-01 3.5131205e-01 3.7664303e-01 3.7434225e-01 3.3054253e-01 3.2553357e-01 4.5134808e-01 3.6126826e-01 4.1953095e-01 3.2738535e-01 4.5959627e-01 3.4943014e-01 4.5279702e-01 4.0259529e-01 4.0419917e-01 3.7916847e-01 3.1736132e-01 3.4544845e-01 4.0790313e-01 3.1842804e-01 4.3221625e-01 4.2193584e-01 4.0305492e-01 3.5775833e-01 3.9908964e-01 3.5217975e-01 3.7311479e-01 3.7405234e-01 4.0128787e-03 1.5082918e-03 2.4327589e-03 1.3749582e-02 9.8644164e-04 7.0275865e-03 4.0509182e-03 9.2137987e-06 8.1954627e-05 2.0221322e-04 1.4919720e-03 1.6918938e-03 1.6810350e-03 9.8366461e-03 7.8161988e-03 1.2160579e-03 1.4072237e-03 1.4774681e-03 1.2160579e-03 1.9402564e-04 2.6249834e-05 2.2527118e-03 1.8043780e-02 2.4914194e-03 7.5228825e-04 4.0311549e-03 1.3209671e-03 4.0483306e-03 6.3837247e-04 9.1866746e-04 4.5668021e-05 1.9279238e-01 1.9221899e-01 2.2979083e-01 2.7088361e-01 2.4479658e-01 2.6610561e-01 2.1267629e-01 1.7326091e-01 2.2258210e-01 2.2184784e-01 2.5349552e-01 1.9619155e-01 2.4503806e-01 2.6157291e-01 1.3780150e-01 1.8042756e-01 2.5616217e-01 2.0823320e-01 3.2136818e-01 2.1724300e-01 2.6868196e-01 1.8301959e-01 3.2745065e-01 2.6534429e-01 1.9640162e-01 1.9346456e-01 2.5082861e-01 2.6843057e-01 2.4386055e-01 1.5229091e-01 2.2093793e-01 2.0444992e-01 1.8898039e-01 3.5852616e-01 2.7153123e-01 1.8897970e-01 2.1514921e-01 2.8018592e-01 1.9075855e-01 2.4355737e-01 2.8508289e-01 2.3688721e-01 2.1314274e-01 1.8298617e-01 2.4079289e-01 1.9795777e-01 2.1138990e-01 2.0306053e-01 1.2090533e-01 2.0951271e-01 4.5737829e-01 3.9834085e-01 3.7497739e-01 3.9259152e-01 4.1873643e-01 4.1914498e-01 4.0909121e-01 3.9964410e-01 4.2918834e-01 3.4857995e-01 2.8224226e-01 3.7353062e-01 3.4626594e-01 4.3008323e-01 4.2978752e-01 3.3538250e-01 3.5042715e-01 3.5150644e-01 4.9516533e-01 3.9931312e-01 3.5345241e-01 3.7985840e-01 4.3539662e-01 3.2054198e-01 3.4448056e-01 3.4226906e-01 3.0044482e-01 2.9530625e-01 4.1705793e-01 3.2972732e-01 3.8633770e-01 2.9699130e-01 4.2515986e-01 3.1826259e-01 4.1819279e-01 3.7038673e-01 3.7108888e-01 3.4686702e-01 2.8745249e-01 3.1485734e-01 3.7515178e-01 2.8956004e-01 3.9834085e-01 3.8842721e-01 3.7027395e-01 3.2715736e-01 3.6694987e-01 3.2117964e-01 3.4102816e-01 3.4191835e-01 9.8460504e-03 4.2097646e-04 3.8720695e-03 8.9609139e-03 1.0271114e-02 1.5759765e-02 3.6853519e-03 2.9640455e-03 4.3261623e-03 5.5313131e-03 9.1074119e-03 8.9974672e-03 1.5687499e-03 7.6879483e-04 8.7064401e-03 5.8625582e-03 6.1329529e-03 8.7064401e-03 2.4949105e-03 3.7729184e-03 1.5387455e-03 3.8489345e-02 2.4427633e-04 2.5870999e-03 2.8098846e-03 9.5472550e-03 6.5811256e-04 1.8465019e-03 1.0925425e-03 3.9557876e-03 2.4136396e-01 2.3914246e-01 2.8140056e-01 3.2726072e-01 2.9909180e-01 3.1587284e-01 2.5940651e-01 2.1853199e-01 2.7358477e-01 2.7023320e-01 3.0865618e-01 2.4367721e-01 3.0046030e-01 3.1269475e-01 1.8026794e-01 2.2845050e-01 3.0393397e-01 2.5531598e-01 3.8377418e-01 2.6747174e-01 3.1847415e-01 2.3166294e-01 3.8684625e-01 3.1607055e-01 2.4560721e-01 2.4289838e-01 3.0537010e-01 3.2346477e-01 2.9500292e-01 1.9758720e-01 2.7227129e-01 2.5416529e-01 2.3708995e-01 4.1588028e-01 3.1833018e-01 2.3144395e-01 2.6550973e-01 3.3890880e-01 2.3429142e-01 2.9628566e-01 3.3633600e-01 2.8587728e-01 2.6362949e-01 2.3087874e-01 2.9089607e-01 2.4143368e-01 2.5794041e-01 2.5175590e-01 1.6347149e-01 2.5753099e-01 5.1758017e-01 4.5875138e-01 4.3664497e-01 4.5058511e-01 4.8035455e-01 4.8203651e-01 4.6682330e-01 4.6036369e-01 4.9325486e-01 4.0679312e-01 3.3723664e-01 4.3530687e-01 4.0692383e-01 4.9456817e-01 4.9519341e-01 3.9426880e-01 4.0735509e-01 4.0616607e-01 5.6363656e-01 4.6254311e-01 4.1397979e-01 4.3911880e-01 4.9961302e-01 3.8027310e-01 4.0098514e-01 3.9884545e-01 3.5820683e-01 3.4982569e-01 4.7998848e-01 3.8669536e-01 4.4902142e-01 3.4986296e-01 4.8921417e-01 3.7374272e-01 4.7562308e-01 4.3467328e-01 4.2816870e-01 4.0201263e-01 3.4137888e-01 3.7400811e-01 4.3776854e-01 3.4988231e-01 4.5875138e-01 4.4907175e-01 4.3130086e-01 3.8919296e-01 4.3084858e-01 3.7966236e-01 3.9613519e-01 3.9634370e-01 7.6697767e-03 2.4232895e-02 4.2497383e-04 5.8645021e-03 7.1697323e-04 1.6459430e-03 2.1472725e-03 2.1701877e-03 1.5644328e-03 2.1493500e-04 2.5540450e-03 1.7185148e-02 1.5785034e-02 5.4418849e-05 4.0402433e-03 4.0294443e-03 5.4418849e-05 2.5045043e-03 1.5070390e-03 7.3845654e-03 1.1685406e-02 7.1165327e-03 4.2192668e-03 6.5870297e-03 1.0764642e-03 8.6213220e-03 3.2147266e-03 4.4993834e-03 1.8554035e-03 1.6163874e-01 1.6124582e-01 1.9600517e-01 2.3441471e-01 2.0995191e-01 2.3066019e-01 1.8049424e-01 1.4375647e-01 1.8928742e-01 1.8888935e-01 2.1809263e-01 1.6489764e-01 2.1018407e-01 2.2610729e-01 1.1131536e-01 1.5021138e-01 2.2157821e-01 1.7624974e-01 2.8213828e-01 1.8434338e-01 2.3311339e-01 1.5259174e-01 2.8788764e-01 2.2976085e-01 1.6495975e-01 1.6222817e-01 2.1560367e-01 2.3216078e-01 2.0930930e-01 1.2444580e-01 1.8772709e-01 1.7242257e-01 1.5812464e-01 3.1788081e-01 2.3649618e-01 1.5894784e-01 1.8237277e-01 2.4318751e-01 1.6040650e-01 2.0886424e-01 2.4850015e-01 2.0301750e-01 1.8048879e-01 1.5257957e-01 2.0654604e-01 1.6721007e-01 1.7931046e-01 1.7120054e-01 9.6139593e-02 1.7732284e-01 4.1297254e-01 3.5577891e-01 3.3316436e-01 3.5061301e-01 3.7530173e-01 3.7554177e-01 3.6661833e-01 3.5699377e-01 3.8511507e-01 3.0820403e-01 2.4524565e-01 3.3176869e-01 3.0575678e-01 3.8594227e-01 3.8559393e-01 2.9549451e-01 3.1016061e-01 3.1160241e-01 4.4857854e-01 3.5641190e-01 3.1263400e-01 3.3812708e-01 3.9109335e-01 2.8129908e-01 3.0451362e-01 3.0237858e-01 2.6230725e-01 2.5773246e-01 3.7352449e-01 2.9029824e-01 3.4398832e-01 2.5959514e-01 3.8123049e-01 2.7952979e-01 3.7549545e-01 3.2868655e-01 3.3002382e-01 3.0704202e-01 2.5032682e-01 2.7592132e-01 3.3326893e-01 2.5208920e-01 3.5577891e-01 3.4619466e-01 3.2870702e-01 2.8757710e-01 3.2540565e-01 2.8197541e-01 3.0143248e-01 3.0241595e-01 4.6779054e-03 6.3405358e-03 1.1072067e-02 1.2740784e-02 2.2273681e-03 1.7042107e-03 2.3469652e-03 4.9562680e-03 7.4940361e-03 5.7695726e-03 3.5981535e-03 1.5844867e-03 6.8214146e-03 3.1957366e-03 3.3967393e-03 6.8214146e-03 1.4289215e-03 2.3814252e-03 3.6906895e-04 3.2052685e-02 4.0942979e-04 9.9039775e-04 3.7975123e-03 6.5400285e-03 1.5831617e-03 1.2837357e-03 4.6903663e-04 2.2256509e-03 2.3440889e-01 2.3310632e-01 2.7431935e-01 3.1898347e-01 2.9103053e-01 3.1107217e-01 2.5431417e-01 2.1253549e-01 2.6655333e-01 2.6462531e-01 3.0042601e-01 2.3750489e-01 2.9168949e-01 3.0705757e-01 1.7397041e-01 2.2119992e-01 2.9980584e-01 2.4986709e-01 3.7353610e-01 2.6066067e-01 3.1374027e-01 2.2416418e-01 3.7874518e-01 3.1076095e-01 2.3844790e-01 2.3545346e-01 2.9741154e-01 3.1590317e-01 2.8873621e-01 1.9043311e-01 2.6497335e-01 2.4708373e-01 2.3022342e-01 4.0978464e-01 3.1511039e-01 2.2757419e-01 2.5853132e-01 3.2950090e-01 2.2996799e-01 2.8911377e-01 3.3136481e-01 2.8050509e-01 2.5648758e-01 2.2384164e-01 2.8507826e-01 2.3741411e-01 2.5289158e-01 2.4520543e-01 1.5591712e-01 2.5163277e-01 5.1215829e-01 4.5200156e-01 4.2861009e-01 4.4496538e-01 4.7341919e-01 4.7441196e-01 4.6168878e-01 4.5347805e-01 4.8519916e-01 3.9997999e-01 3.3019092e-01 4.2716946e-01 3.9865809e-01 4.8629326e-01 4.8637186e-01 3.8670691e-01 4.0127624e-01 4.0126667e-01 5.5444310e-01 4.5424309e-01 4.0600299e-01 4.3254422e-01 4.9161364e-01 3.7174460e-01 3.9497344e-01 3.9273809e-01 3.5018083e-01 3.4346685e-01 4.7229362e-01 3.8003558e-01 4.4070091e-01 3.4442025e-01 4.8107176e-01 3.6755422e-01 4.7082182e-01 4.2490414e-01 4.2252116e-01 3.9675769e-01 3.3509282e-01 3.6564070e-01 4.2918040e-01 3.3977682e-01 4.5200156e-01 4.4195847e-01 4.2350677e-01 3.7942836e-01 4.2122143e-01 3.7189771e-01 3.9075300e-01 3.9132598e-01 2.1401129e-02 2.6296569e-02 3.2566498e-02 1.3297832e-02 1.1999623e-02 1.2969856e-02 1.8466773e-02 2.3873417e-02 1.8518842e-02 4.0273028e-03 1.2616800e-03 2.2732626e-02 1.2827409e-02 1.3181090e-02 2.2732626e-02 1.1261737e-02 1.3701796e-02 5.3984869e-03 5.7610599e-02 5.9941455e-03 8.9691119e-03 1.2694193e-02 2.1144882e-02 7.0627421e-03 1.0543898e-02 8.0308076e-03 1.3063205e-02 2.9989796e-01 2.9808728e-01 3.4381381e-01 3.9275825e-01 3.6234621e-01 3.8238034e-01 3.2086021e-01 2.7526551e-01 3.3529881e-01 3.3248525e-01 3.7261196e-01 3.0300865e-01 3.6317903e-01 3.7865155e-01 2.3211430e-01 2.8532877e-01 3.6963026e-01 3.1621648e-01 4.5177033e-01 3.2875625e-01 3.8520789e-01 2.8867443e-01 4.5697798e-01 3.8242988e-01 3.0442487e-01 3.0118555e-01 3.6926913e-01 3.8920975e-01 3.5915749e-01 2.5088735e-01 3.3365900e-01 3.1393007e-01 2.9523721e-01 4.8898239e-01 3.8528778e-01 2.9050533e-01 3.2648373e-01 4.0430761e-01 2.9352285e-01 3.5998625e-01 4.0438969e-01 3.4965804e-01 3.2429593e-01 2.8821126e-01 3.5492121e-01 3.0147553e-01 3.1927061e-01 3.1166517e-01 2.1164881e-01 3.1841810e-01 5.9626248e-01 5.3406968e-01 5.1001241e-01 5.2586196e-01 5.5673824e-01 5.5816878e-01 5.4309284e-01 5.3570978e-01 5.6971774e-01 4.7901704e-01 4.0442977e-01 5.0852772e-01 4.7828326e-01 5.7096471e-01 5.7126109e-01 4.6526268e-01 4.7993533e-01 4.7902644e-01 6.4260357e-01 5.3722757e-01 4.8599135e-01 5.1340958e-01 5.7642669e-01 4.4962350e-01 4.7318809e-01 4.7087433e-01 4.2633712e-01 4.1835467e-01 5.5597597e-01 4.5768215e-01 5.2292685e-01 4.1877489e-01 5.6541976e-01 4.4406435e-01 5.5240885e-01 5.0657081e-01 5.0216108e-01 4.7452659e-01 4.0930806e-01 4.4303784e-01 5.1082488e-01 4.1541892e-01 5.3406968e-01 5.2368299e-01 5.0449934e-01 4.5807263e-01 5.0263701e-01 4.4952858e-01 4.6823972e-01 4.6855914e-01 8.7198254e-03 1.2902931e-03 1.1684021e-03 1.6341127e-03 1.0742763e-03 2.6208124e-03 1.1173357e-03 8.9620141e-04 1.6960301e-02 1.4197837e-02 5.6586090e-04 2.0157829e-03 1.9836256e-03 5.6586090e-04 2.0413623e-03 1.1637665e-03 5.4511725e-03 1.0718252e-02 6.5990100e-03 2.7263257e-03 7.7966832e-03 1.5754002e-04 8.7922744e-03 3.0619296e-03 3.8030551e-03 1.0550593e-03 1.7203244e-01 1.7235723e-01 2.0751307e-01 2.4611820e-01 2.2109400e-01 2.4491699e-01 1.9298922e-01 1.5421652e-01 2.0061094e-01 2.0129686e-01 2.2934517e-01 1.7603988e-01 2.2070889e-01 2.3964854e-01 1.2009621e-01 1.5990877e-01 2.3601974e-01 1.8834267e-01 2.9339046e-01 1.9567575e-01 2.4748464e-01 1.6217086e-01 3.0116422e-01 2.4364046e-01 1.7530938e-01 1.7223213e-01 2.2701156e-01 2.4439785e-01 2.2188150e-01 1.3311613e-01 1.9877894e-01 1.8311301e-01 1.6845668e-01 3.3339209e-01 2.5192873e-01 1.7150268e-01 1.9350184e-01 2.5414415e-01 1.7271733e-01 2.2071334e-01 2.6313203e-01 2.1605186e-01 1.9141813e-01 1.6255272e-01 2.1936783e-01 1.7996851e-01 1.9179556e-01 1.8227672e-01 1.0300751e-01 1.8912086e-01 4.3075013e-01 3.7158844e-01 3.4752045e-01 3.6713693e-01 3.9134187e-01 3.9105984e-01 3.8378198e-01 3.7272538e-01 4.0046876e-01 3.2297138e-01 2.5826699e-01 3.4601238e-01 3.1932057e-01 4.0113556e-01 4.0033459e-01 3.0938917e-01 3.2551476e-01 3.2781102e-01 4.6405632e-01 3.7102765e-01 3.2659021e-01 3.5371449e-01 4.0659862e-01 2.9405620e-01 3.1978995e-01 3.1753736e-01 2.7499746e-01 2.7161605e-01 3.8895029e-01 3.0476818e-01 3.5833770e-01 2.7419176e-01 3.9644448e-01 2.9409120e-01 3.9304592e-01 3.4147602e-01 3.4611220e-01 3.2290811e-01 2.6406812e-01 2.8867894e-01 3.4717424e-01 2.6265032e-01 3.7158844e-01 3.6154105e-01 3.4316147e-01 2.9940560e-01 3.3824889e-01 2.9538166e-01 3.1708459e-01 3.1834036e-01 8.7316989e-03 6.7615313e-03 6.7580543e-03 9.6081666e-03 2.0657605e-03 3.8371349e-03 1.4271025e-02 1.2184393e-02 1.6084765e-02 4.9831593e-03 1.4699244e-02 1.4912454e-02 4.9831593e-03 6.5028816e-03 6.2489819e-03 1.3726162e-02 3.1411524e-02 7.7445772e-03 1.1141212e-02 2.5000477e-03 1.1143820e-02 6.1604304e-03 5.3711085e-03 7.5849080e-03 8.1835455e-03 1.6688130e-01 1.6291433e-01 2.0003066e-01 2.4149909e-01 2.1722570e-01 2.2445012e-01 1.7758122e-01 1.4623090e-01 1.9343386e-01 1.8753794e-01 2.2568158e-01 1.6690274e-01 2.2030573e-01 2.2323949e-01 1.1699096e-01 1.5725818e-01 2.1323159e-01 1.7489777e-01 2.9532205e-01 1.8786273e-01 2.2655558e-01 1.6049736e-01 2.9226070e-01 2.2550223e-01 1.7083413e-01 1.6938578e-01 2.2221647e-01 2.3649790e-01 2.0956346e-01 1.3266766e-01 1.9299947e-01 1.7760033e-01 1.6320695e-01 3.1350837e-01 2.2421748e-01 1.5253844e-01 1.8664517e-01 2.5447595e-01 1.5560691e-01 2.1266187e-01 2.4223633e-01 2.0012528e-01 1.8549850e-01 1.5864276e-01 2.0519641e-01 1.6093217e-01 1.7628990e-01 1.7435363e-01 1.0801101e-01 1.7763987e-01 4.0351234e-01 3.5280598e-01 3.3582666e-01 3.4348161e-01 3.7251839e-01 3.7541634e-01 3.5722931e-01 3.5450932e-01 3.8642215e-01 3.0688243e-01 2.4703841e-01 3.3487550e-01 3.1018257e-01 3.8808655e-01 3.8990045e-01 2.9749231e-01 3.0596368e-01 3.0291072e-01 4.5287211e-01 3.5944458e-01 3.1570031e-01 3.3498082e-01 3.9202879e-01 2.8759651e-01 3.0023966e-01 2.9853735e-01 2.6729925e-01 2.5639363e-01 3.7372548e-01 2.8896987e-01 3.4745520e-01 2.5469579e-01 3.8297689e-01 2.7675896e-01 3.6463006e-01 3.3839246e-01 3.2358656e-01 2.9982548e-01 2.4898596e-01 2.8176933e-01 3.3810925e-01 2.6587897e-01 3.5280598e-01 3.4488951e-01 3.3055981e-01 2.9862862e-01 3.3464036e-01 2.8522813e-01 2.9487313e-01 2.9445589e-01 4.3312037e-03 5.1674385e-03 4.6600432e-03 4.1032398e-03 1.3249619e-03 3.6837745e-03 2.4877830e-02 2.2958753e-02 1.1314391e-03 6.3769043e-03 6.2810489e-03 1.1314391e-03 5.7702159e-03 4.1635898e-03 1.1902706e-02 7.2040189e-03 1.2293784e-02 7.6706139e-03 1.1339253e-02 1.8296747e-03 1.4280945e-02 6.9429868e-03 8.6587607e-03 4.4388406e-03 1.4462991e-01 1.4494206e-01 1.7756526e-01 2.1378771e-01 1.9031042e-01 2.1286860e-01 1.6419734e-01 1.2822571e-01 1.7113301e-01 1.7185341e-01 1.9804950e-01 1.4833973e-01 1.9005998e-01 2.0775634e-01 9.7105565e-02 1.3348080e-01 2.0467358e-01 1.5981923e-01 2.5874568e-01 1.6653873e-01 2.1530133e-01 1.3558698e-01 2.6575883e-01 2.1156399e-01 1.4766732e-01 1.4484877e-01 1.9583483e-01 2.1211626e-01 1.9101641e-01 1.0897783e-01 1.6943757e-01 1.5488355e-01 1.4132668e-01 2.9650216e-01 2.1994601e-01 1.4455458e-01 1.6451956e-01 2.2152336e-01 1.4555202e-01 1.8989624e-01 2.3000345e-01 1.8566766e-01 1.6258902e-01 1.3589561e-01 1.8870744e-01 1.5235300e-01 1.6309487e-01 1.5410176e-01 8.1942750e-02 1.6048904e-01 3.8999083e-01 3.3294245e-01 3.0988325e-01 3.2882841e-01 3.5187047e-01 3.5156617e-01 3.4487977e-01 3.3402051e-01 3.6059894e-01 2.8649814e-01 2.2516617e-01 3.0844683e-01 2.8301726e-01 3.6124416e-01 3.6050400e-01 2.7355262e-01 2.8899505e-01 2.9139815e-01 4.2192237e-01 3.3236960e-01 2.8992253e-01 3.1585310e-01 3.6648886e-01 2.5905330e-01 2.8355149e-01 2.8139447e-01 2.4097820e-01 2.3780572e-01 3.4954046e-01 2.6919588e-01 3.2022959e-01 2.4036369e-01 3.5673540e-01 2.5910558e-01 3.5384915e-01 3.0430469e-01 3.0871278e-01 2.8664912e-01 2.3068275e-01 2.5394489e-01 3.0958210e-01 2.2970603e-01 3.3294245e-01 3.2329736e-01 3.0571743e-01 2.6432145e-01 3.0120271e-01 2.6026006e-01 2.8107880e-01 2.8234890e-01 3.9333334e-05 2.5761851e-04 1.3896575e-03 1.7536063e-03 1.9216331e-03 9.2702772e-03 7.4022776e-03 1.3090644e-03 1.5444025e-03 1.6252591e-03 1.3090644e-03 1.2151407e-04 1.0618022e-05 2.1493524e-03 1.8833153e-02 2.2172388e-03 7.3253783e-04 3.6800655e-03 1.5497854e-03 3.6733784e-03 4.9421897e-04 7.6664670e-04 7.0183041e-05 1.9407897e-01 1.9337938e-01 2.3114302e-01 2.7244640e-01 2.4629979e-01 2.6718506e-01 2.1373312e-01 1.7438911e-01 2.2391848e-01 2.2298785e-01 2.5503185e-01 1.9737603e-01 2.4663659e-01 2.6276403e-01 1.3891547e-01 1.8173656e-01 2.5713590e-01 2.0933386e-01 3.2324703e-01 2.1854256e-01 2.6975489e-01 1.8436571e-01 3.2904939e-01 2.6649404e-01 1.9772084e-01 1.9481826e-01 2.5232520e-01 2.6989379e-01 2.4511828e-01 1.5354404e-01 2.2230840e-01 2.0576951e-01 1.9024966e-01 3.5987888e-01 2.7239312e-01 1.8983400e-01 2.1646886e-01 2.8191043e-01 1.9167912e-01 2.4493869e-01 2.8620678e-01 2.3801287e-01 2.1448199e-01 1.8427005e-01 2.4198503e-01 1.9884520e-01 2.1243903e-01 2.0430599e-01 1.2215728e-01 2.1067715e-01 4.5871846e-01 3.9981904e-01 3.7661173e-01 3.9390784e-01 4.2025710e-01 4.2076082e-01 4.1035332e-01 4.0114215e-01 4.3087129e-01 3.5002279e-01 2.8365040e-01 3.7517763e-01 3.4790525e-01 4.3179681e-01 4.3157518e-01 3.3691557e-01 3.5177012e-01 3.5268567e-01 4.9705280e-01 4.0100475e-01 3.5505921e-01 3.8129652e-01 4.3707681e-01 3.2219168e-01 3.4580868e-01 3.4360838e-01 3.0200588e-01 2.9663386e-01 4.1868171e-01 3.3113109e-01 3.8802094e-01 2.9819354e-01 4.2685142e-01 3.1959000e-01 4.1941543e-01 3.7225097e-01 3.7239048e-01 3.4809183e-01 2.8876177e-01 3.1647952e-01 3.7686145e-01 2.9138730e-01 3.9981904e-01 3.8994709e-01 3.7187123e-01 3.2897937e-01 3.6879196e-01 3.2272643e-01 3.4226532e-01 3.4310531e-01 3.4181612e-04 1.4644608e-03 2.1632573e-03 2.3231555e-03 8.1286280e-03 6.3818616e-03 1.7262411e-03 1.6624580e-03 1.7631926e-03 1.7262411e-03 2.4569053e-05 5.6859581e-05 1.7551896e-03 2.0541034e-02 1.6702311e-03 5.9025530e-04 3.2341087e-03 2.0408948e-03 3.0131078e-03 2.8888789e-04 4.5924146e-04 1.3293684e-04 1.9862431e-01 1.9776069e-01 2.3600084e-01 2.7779005e-01 2.5142895e-01 2.7187023e-01 2.1810101e-01 1.7860088e-01 2.2871390e-01 2.2752286e-01 2.6024955e-01 2.0181434e-01 2.5187795e-01 2.6758248e-01 1.4283878e-01 1.8622396e-01 2.6161990e-01 2.1373552e-01 3.2920505e-01 2.2325952e-01 2.7444329e-01 1.8891553e-01 3.3470963e-01 2.7127428e-01 2.0233024e-01 1.9944956e-01 2.5748056e-01 2.7510316e-01 2.4993365e-01 1.5775267e-01 2.2713624e-01 2.1043184e-01 1.9474801e-01 3.6534538e-01 2.7678503e-01 1.9377126e-01 2.2119834e-01 2.8749014e-01 1.9572304e-01 2.4991164e-01 2.9104517e-01 2.4261230e-01 2.1922346e-01 1.8874518e-01 2.4669621e-01 2.0288553e-01 2.1678869e-01 2.0886650e-01 1.2608625e-01 2.1517216e-01 4.6449792e-01 4.0560127e-01 3.8251194e-01 3.9944814e-01 4.2616553e-01 4.2679596e-01 4.1587264e-01 4.0695550e-01 4.3702628e-01 3.5557170e-01 2.8885981e-01 3.8108822e-01 3.5369655e-01 4.3799369e-01 4.3786355e-01 3.4252750e-01 3.5719184e-01 3.5788232e-01 5.0366583e-01 4.0706759e-01 3.6083966e-01 3.8695887e-01 4.4324858e-01 3.2788328e-01 3.5118655e-01 3.4899273e-01 3.0749578e-01 3.0179987e-01 4.2472010e-01 3.3655063e-01 3.9402558e-01 3.0319627e-01 4.3300399e-01 3.2485825e-01 4.2490754e-01 3.7841034e-01 3.7783349e-01 3.5333537e-01 2.9386653e-01 3.2211174e-01 3.8285611e-01 2.9712888e-01 4.0560127e-01 3.9574961e-01 3.7770634e-01 3.3490218e-01 3.7491174e-01 3.2829405e-01 3.4750328e-01 3.4827580e-01 2.7893549e-03 2.7277911e-03 9.4108270e-04 1.0799149e-02 7.7770747e-03 1.9770194e-03 5.4325768e-04 5.8945201e-04 1.9770194e-03 5.1514113e-04 3.6478892e-04 1.6876295e-03 1.7172006e-02 2.9808015e-03 3.7816236e-04 5.6735840e-03 1.0535544e-03 5.0675384e-03 1.2248969e-03 1.1742680e-03 5.8878380e-05 1.9840502e-01 1.9840646e-01 2.3601178e-01 2.7699198e-01 2.5062921e-01 2.7429513e-01 2.1981939e-01 1.7908511e-01 2.2870797e-01 2.2884763e-01 2.5935890e-01 2.0237179e-01 2.5038976e-01 2.6920634e-01 1.4261267e-01 1.8559031e-01 2.6457877e-01 2.1508394e-01 3.2684946e-01 2.2341437e-01 2.7694800e-01 1.8806980e-01 3.3447269e-01 2.7322826e-01 2.0195195e-01 1.9876628e-01 2.5683031e-01 2.7497319e-01 2.5083242e-01 1.5688509e-01 2.2686098e-01 2.1020792e-01 1.9457412e-01 3.6717222e-01 2.8066992e-01 1.9637709e-01 2.2117475e-01 2.8566080e-01 1.9793610e-01 2.4995417e-01 2.9345115e-01 2.4428403e-01 2.1902512e-01 1.8834472e-01 2.4798811e-01 2.0544380e-01 2.1853532e-01 2.0913443e-01 1.2439434e-01 2.1611468e-01 4.6739623e-01 4.0702486e-01 3.8261075e-01 4.0188705e-01 4.2751037e-01 4.2749738e-01 4.1883120e-01 4.0825884e-01 4.3736523e-01 3.5667688e-01 2.8932174e-01 3.8108192e-01 3.5341594e-01 4.3812879e-01 4.3747647e-01 3.4285168e-01 3.5898948e-01 3.6076578e-01 5.0317788e-01 4.0703744e-01 3.6087589e-01 3.8845434e-01 4.4366743e-01 3.2719263e-01 3.5301257e-01 3.5071770e-01 3.0715395e-01 3.0299726e-01 4.2534595e-01 3.3768843e-01 3.9391146e-01 3.0525217e-01 4.3324318e-01 3.2636388e-01 4.2820876e-01 3.7681443e-01 3.8013938e-01 3.5586945e-01 2.9507753e-01 3.2153910e-01 3.8242816e-01 2.9467977e-01 4.0702486e-01 3.9678588e-01 3.7800850e-01 3.3305200e-01 3.7343525e-01 3.2833826e-01 3.4988792e-01 3.5099795e-01 7.6889988e-04 5.5936308e-03 9.7548717e-03 1.0412603e-02 1.0389613e-03 5.7781385e-03 5.9052415e-03 1.0389613e-03 1.4340775e-03 1.1584691e-03 6.0259991e-03 2.1726707e-02 3.4598658e-03 3.9021886e-03 1.8160607e-03 3.9104990e-03 3.6199548e-03 1.2013216e-03 2.4248068e-03 2.0560641e-03 1.7726458e-01 1.7519789e-01 2.1239850e-01 2.5350621e-01 2.2831460e-01 2.4335474e-01 1.9301815e-01 1.5733953e-01 2.0548992e-01 2.0248973e-01 2.3687424e-01 1.7915768e-01 2.2981656e-01 2.4028800e-01 1.2483249e-01 1.6617415e-01 2.3289941e-01 1.8936545e-01 3.0533170e-01 2.0007138e-01 2.4571617e-01 1.6904051e-01 3.0733551e-01 2.4338605e-01 1.8100871e-01 1.7874491e-01 2.3386295e-01 2.4993365e-01 2.2441376e-01 1.3976123e-01 2.0438723e-01 1.8845276e-01 1.7353191e-01 3.3400984e-01 2.4631085e-01 1.6886156e-01 1.9837340e-01 2.6441281e-01 1.7118963e-01 2.2560913e-01 2.6174131e-01 2.1639026e-01 1.9675554e-01 1.6819987e-01 2.2078897e-01 1.7753606e-01 1.9173589e-01 1.8624992e-01 1.1156657e-01 1.9127963e-01 4.2879129e-01 3.7350470e-01 3.5299849e-01 3.6620603e-01 3.9355975e-01 3.9506070e-01 3.8147101e-01 3.7498057e-01 4.0550993e-01 3.2554347e-01 2.6225589e-01 3.5177359e-01 3.2570798e-01 4.0674255e-01 4.0737898e-01 3.1405475e-01 3.2617446e-01 3.2545044e-01 4.7160294e-01 3.7696723e-01 3.3214002e-01 3.5535112e-01 4.1144267e-01 3.0143840e-01 3.2034131e-01 3.1835849e-01 2.8130938e-01 2.7364725e-01 3.9315179e-01 3.0715437e-01 3.6445309e-01 2.7384746e-01 4.0174546e-01 2.9539622e-01 3.8981435e-01 3.5158797e-01 3.4545598e-01 3.2149982e-01 2.6601526e-01 2.9570609e-01 3.5410378e-01 2.7466513e-01 3.7350470e-01 3.6448827e-01 3.4805708e-01 3.0999906e-01 3.4801807e-01 3.0074518e-01 3.1606363e-01 3.1638243e-01 3.9211584e-03 1.5428451e-02 1.5051422e-02 9.3641634e-05 5.2251987e-03 5.2564865e-03 9.3641634e-05 2.4059863e-03 1.5411207e-03 7.7716003e-03 1.4517538e-02 6.4009679e-03 4.6963365e-03 4.9429047e-03 2.1131701e-03 7.3135995e-03 2.7662314e-03 4.2415669e-03 2.1979302e-03 1.6154877e-01 1.6047147e-01 1.9568300e-01 2.3468417e-01 2.1025394e-01 2.2838581e-01 1.7884422e-01 1.4313754e-01 1.8898793e-01 1.8753794e-01 2.1845622e-01 1.6418759e-01 2.1103286e-01 2.2447017e-01 1.1131548e-01 1.5046796e-01 2.1889519e-01 1.7489777e-01 2.8359045e-01 1.8392350e-01 2.3077309e-01 1.5301294e-01 2.8765002e-01 2.2785871e-01 1.6499082e-01 1.6250540e-01 2.1577964e-01 2.3190311e-01 2.0826660e-01 1.2493129e-01 1.8764327e-01 1.7232008e-01 1.5800409e-01 3.1597925e-01 2.3306671e-01 1.5663136e-01 1.8209705e-01 2.4426589e-01 1.5839676e-01 2.0849973e-01 2.4621874e-01 2.0137281e-01 1.8035428e-01 1.5264734e-01 2.0519641e-01 1.6491824e-01 1.7763987e-01 1.7071018e-01 9.7334595e-02 1.7628990e-01 4.1013753e-01 3.5415853e-01 3.3261378e-01 3.4819629e-01 3.7372548e-01 3.7447405e-01 3.6377706e-01 3.5546726e-01 3.8432564e-01 3.0688243e-01 2.4450319e-01 3.3130276e-01 3.0553473e-01 3.8531353e-01 3.8537934e-01 2.9480414e-01 3.0829006e-01 3.0887441e-01 4.4839112e-01 3.5594109e-01 3.1215369e-01 3.3646690e-01 3.9023537e-01 2.8142851e-01 3.0262656e-01 3.0057353e-01 2.6218143e-01 2.5639363e-01 3.7251839e-01 2.8896987e-01 3.4359589e-01 2.5757650e-01 3.8052352e-01 2.7792266e-01 3.7237623e-01 3.2948525e-01 3.2773210e-01 3.0459398e-01 2.4898596e-01 2.7596310e-01 3.3313575e-01 2.5365041e-01 3.5415853e-01 3.4488951e-01 3.2799981e-01 2.8862094e-01 3.2611271e-01 2.8152144e-01 2.9910812e-01 2.9982464e-01 1.7874931e-02 1.3166667e-02 2.8067647e-03 5.4483543e-04 4.8491153e-04 2.8067647e-03 2.7932574e-03 2.1120566e-03 4.0283644e-03 1.1006031e-02 7.1968081e-03 1.9824114e-03 1.0903705e-02 3.4373671e-04 1.0364284e-02 4.2441401e-03 4.1832512e-03 1.3456492e-03 1.8893734e-01 1.9027350e-01 2.2602463e-01 2.6493124e-01 2.3909907e-01 2.6731994e-01 2.1287273e-01 1.7119179e-01 2.1885648e-01 2.2106766e-01 2.4751153e-01 1.9400411e-01 2.3783986e-01 2.6104566e-01 1.3465024e-01 1.7581619e-01 2.5867158e-01 2.0765719e-01 3.1157756e-01 2.1392913e-01 2.7004976e-01 1.7791371e-01 3.2221942e-01 2.6551484e-01 1.9215572e-01 1.8858682e-01 2.4539617e-01 2.6396374e-01 2.4190361e-01 1.4753547e-01 2.1663955e-01 2.0044864e-01 1.8527044e-01 3.5759997e-01 2.7597540e-01 1.9143761e-01 2.1146655e-01 2.7190276e-01 1.9231433e-01 2.3971651e-01 2.8607417e-01 2.3671699e-01 2.0909909e-01 1.7885289e-01 2.3973819e-01 2.0020124e-01 2.1166375e-01 2.0015528e-01 1.1482314e-01 2.0802247e-01 4.5814031e-01 3.9622068e-01 3.7010544e-01 3.9277065e-01 4.1629822e-01 4.1528170e-01 4.1031284e-01 3.9724817e-01 4.2445794e-01 3.4613203e-01 2.7894605e-01 3.6843870e-01 3.4078468e-01 4.2489846e-01 4.2345869e-01 3.3131814e-01 3.4950009e-01 3.5299308e-01 4.8820349e-01 3.9397890e-01 3.4860920e-01 3.7803314e-01 4.3080058e-01 3.1437592e-01 3.4366436e-01 3.4124618e-01 2.9521701e-01 2.9351505e-01 4.1304236e-01 3.2750551e-01 3.8091343e-01 2.9709224e-01 4.2023724e-01 3.1695286e-01 4.2011817e-01 3.6184591e-01 3.7113199e-01 3.4760954e-01 2.8576301e-01 3.0899799e-01 3.6912287e-01 2.7985004e-01 3.9622068e-01 3.8552168e-01 3.6588486e-01 3.1841134e-01 3.5869504e-01 3.1661834e-01 3.4148439e-01 3.4312143e-01 1.2983249e-03 1.5471006e-02 1.3470267e-02 1.3877317e-02 1.5471006e-02 7.2739782e-03 9.2612860e-03 6.0792360e-03 5.4160667e-02 2.4574168e-03 8.1477262e-03 3.8451807e-03 1.8194605e-02 1.5067164e-03 5.6792549e-03 4.8846913e-03 1.0004616e-02 2.6108642e-01 2.5718656e-01 3.0162187e-01 3.4990044e-01 3.2118558e-01 3.3212064e-01 2.7586622e-01 2.3634761e-01 2.9366681e-01 2.8770633e-01 3.3110877e-01 2.6200119e-01 3.2383134e-01 3.3046521e-01 1.9829106e-01 2.4860521e-01 3.1887574e-01 2.7240198e-01 4.1018982e-01 2.8711577e-01 3.3461178e-01 2.5228731e-01 4.0944673e-01 3.3325697e-01 2.6572836e-01 2.6351234e-01 3.2731613e-01 3.4479515e-01 3.1382719e-01 2.1730674e-01 2.9282566e-01 2.7420761e-01 2.5662111e-01 4.3493335e-01 3.3165524e-01 2.4546083e-01 2.8544328e-01 3.6363165e-01 2.4916639e-01 3.1676067e-01 3.5299851e-01 3.0301330e-01 2.8384547e-01 2.5066938e-01 3.0887732e-01 2.5579752e-01 2.7431070e-01 2.7082223e-01 1.8355500e-01 2.7545202e-01 5.3566205e-01 4.7913827e-01 4.5930782e-01 4.6887050e-01 5.0113940e-01 5.0408511e-01 4.8425611e-01 4.8100390e-01 5.1611676e-01 4.2712908e-01 3.5770237e-01 4.5815125e-01 4.2990067e-01 5.1783166e-01 5.1944925e-01 4.1592191e-01 4.2634771e-01 4.2297928e-01 5.8870710e-01 4.8576650e-01 4.3645958e-01 4.5912638e-01 5.2238783e-01 4.0361731e-01 4.1983156e-01 4.1785974e-01 3.8054730e-01 3.6909926e-01 5.0215992e-01 4.0667536e-01 4.7224138e-01 3.6745640e-01 5.1222367e-01 3.9280819e-01 4.9246039e-01 4.6045456e-01 4.4643751e-01 4.1946868e-01 3.6048019e-01 3.9703515e-01 4.6143668e-01 3.7590038e-01 4.7913827e-01 4.7009550e-01 4.5350904e-01 4.1479012e-01 4.5636227e-01 4.0162438e-01 4.1380491e-01 4.1334093e-01 1.4411876e-02 8.7935053e-03 9.1184252e-03 1.4411876e-02 5.7453578e-03 7.6054776e-03 2.7409931e-03 4.7624677e-02 1.8780439e-03 4.9746662e-03 5.9684857e-03 1.4534556e-02 2.3601867e-03 4.9286760e-03 3.4466344e-03 7.5341006e-03 2.6746877e-01 2.6516664e-01 3.0921139e-01 3.5673992e-01 3.2754140e-01 3.4481450e-01 2.8624779e-01 2.4359422e-01 3.0108022e-01 2.9756219e-01 3.3745616e-01 2.6990583e-01 3.2887946e-01 3.4163113e-01 2.0331657e-01 2.5392638e-01 3.3233847e-01 2.8201820e-01 4.1489235e-01 2.9472025e-01 3.4749512e-01 2.5726587e-01 4.1828245e-01 3.4508966e-01 2.7189140e-01 2.6903465e-01 3.3406888e-01 3.5284896e-01 3.2333666e-01 2.2149953e-01 2.9970099e-01 2.8083524e-01 2.6300357e-01 4.4804341e-01 3.4702929e-01 2.5691578e-01 2.9267142e-01 3.6866600e-01 2.5996213e-01 3.2467728e-01 3.6599412e-01 3.1379996e-01 2.9070461e-01 2.5648991e-01 3.1904849e-01 2.6736905e-01 2.8471636e-01 2.7833907e-01 1.8530093e-01 2.8435281e-01 5.5199539e-01 4.9206433e-01 4.6946939e-01 4.8356355e-01 5.1418554e-01 5.1595196e-01 5.0010048e-01 4.9372697e-01 5.2743722e-01 4.3877522e-01 3.6710197e-01 4.6809646e-01 4.3893984e-01 5.2878375e-01 5.2942069e-01 4.2592703e-01 4.3928640e-01 4.3789059e-01 5.9924430e-01 4.9601715e-01 4.4619754e-01 4.7193405e-01 5.3393309e-01 4.1149136e-01 4.3273356e-01 4.3054493e-01 3.8875406e-01 3.8007672e-01 5.1386048e-01 4.1809676e-01 4.8216133e-01 3.8002405e-01 5.2330849e-01 4.0472333e-01 5.0903363e-01 4.6735136e-01 4.6059448e-01 4.3368525e-01 3.7134977e-01 4.0504167e-01 4.7061436e-01 3.7989795e-01 4.9206433e-01 4.8217825e-01 4.6398354e-01 4.2055878e-01 4.6343857e-01 4.1088828e-01 4.2766517e-01 4.2782051e-01 4.0251529e-03 4.0402030e-03 0.0000000e+00 2.0104773e-03 1.1579294e-03 6.7733512e-03 1.3328703e-02 6.1195429e-03 3.8280621e-03 5.4471697e-03 1.3203495e-03 7.3930866e-03 2.5511055e-03 3.8011014e-03 1.5935161e-03 1.6488937e-01 1.6420208e-01 1.9946134e-01 2.3841307e-01 2.1377841e-01 2.3352484e-01 1.8323739e-01 1.4660873e-01 1.9269692e-01 1.9183992e-01 2.2200730e-01 1.6791595e-01 2.1423078e-01 2.2922867e-01 1.1407468e-01 1.5349471e-01 2.2418179e-01 1.7908948e-01 2.8692621e-01 1.8765985e-01 2.3596576e-01 1.5596498e-01 2.9203641e-01 2.3278981e-01 1.6829150e-01 1.6563525e-01 2.1942293e-01 2.3592536e-01 2.1256421e-01 1.2755291e-01 1.9121342e-01 1.7576718e-01 1.6132929e-01 3.2148865e-01 2.3885357e-01 1.6118125e-01 1.8573297e-01 2.4757051e-01 1.6279862e-01 2.1240660e-01 2.5149161e-01 2.0595435e-01 1.8389163e-01 1.5580881e-01 2.0964365e-01 1.6953436e-01 1.8203376e-01 1.7437090e-01 9.9184065e-02 1.8031354e-01 4.1664204e-01 3.5971944e-01 3.3744612e-01 3.5416868e-01 3.7936058e-01 3.7982315e-01 3.7006184e-01 3.6098196e-01 3.8956200e-01 3.1201251e-01 2.4889912e-01 3.3607844e-01 3.1002022e-01 3.9046127e-01 3.9028467e-01 2.9949936e-01 3.1373736e-01 3.1479561e-01 4.5355800e-01 3.6085089e-01 3.1682959e-01 3.4195608e-01 3.9553949e-01 2.8555816e-01 3.0804941e-01 3.0593834e-01 2.6633772e-01 2.6121338e-01 3.7782244e-01 2.9399549e-01 3.4839508e-01 2.6278429e-01 3.8569374e-01 2.8303600e-01 3.7885421e-01 3.3349568e-01 3.3352424e-01 3.1033775e-01 2.5375578e-01 2.8011018e-01 3.3772574e-01 2.5671985e-01 3.5971944e-01 3.5022311e-01 3.3289784e-01 2.9224130e-01 3.3016010e-01 2.8599652e-01 3.0475125e-01 3.0561759e-01 3.0700267e-06 4.0251529e-03 1.9467123e-03 1.7971688e-03 1.7301608e-03 1.6293141e-02 4.7718752e-03 6.8454231e-04 9.3235659e-03 1.4106675e-03 7.7429721e-03 3.1097831e-03 2.5391665e-03 9.5764453e-04 2.0739268e-01 2.0836358e-01 2.4589773e-01 2.8656370e-01 2.5981614e-01 2.8739169e-01 2.3131893e-01 1.8849968e-01 2.3845740e-01 2.4007299e-01 2.6857108e-01 2.1230255e-01 2.5877066e-01 2.8137449e-01 1.5046312e-01 1.9386671e-01 2.7809337e-01 2.2610426e-01 3.3523485e-01 2.3325794e-01 2.9016640e-01 1.9614834e-01 3.4542234e-01 2.8580893e-01 2.1082239e-01 2.0722978e-01 2.6628244e-01 2.8528108e-01 2.6193836e-01 1.6431358e-01 2.3627495e-01 2.1939954e-01 2.0354344e-01 3.8077948e-01 2.9536596e-01 2.0839572e-01 2.3078002e-01 2.9414568e-01 2.0957408e-01 2.6009226e-01 3.0679254e-01 2.5613081e-01 2.2839644e-01 1.9694047e-01 2.5948108e-01 2.1757899e-01 2.3004331e-01 2.1886202e-01 1.3010315e-01 2.2671016e-01 4.8309156e-01 4.2059741e-01 3.9446827e-01 4.1650857e-01 4.4118694e-01 4.4046177e-01 4.3416675e-01 4.2171124e-01 4.5001131e-01 3.6938174e-01 3.0049261e-01 3.9280494e-01 3.6453208e-01 4.5055186e-01 4.4930066e-01 3.5453263e-01 3.7247610e-01 3.7543144e-01 5.1540859e-01 4.1899010e-01 3.7243361e-01 4.0192295e-01 4.5645786e-01 3.3753686e-01 3.6646355e-01 3.6403348e-01 3.1765048e-01 3.1516192e-01 4.3820660e-01 3.5021371e-01 4.0564147e-01 3.1837605e-01 4.4574076e-01 3.3915719e-01 4.4399141e-01 3.8666018e-01 3.9439941e-01 3.7011128e-01 3.0715348e-01 3.3195062e-01 3.9368522e-01 3.0254596e-01 4.2059741e-01 4.0983289e-01 3.9004780e-01 3.4211299e-01 3.8338579e-01 3.3953411e-01 3.6390344e-01 3.6538415e-01 4.0402030e-03 2.0647746e-03 1.8810615e-03 1.8744038e-03 1.5904825e-02 5.0089073e-03 7.7718458e-04 9.5962272e-03 1.3536136e-03 8.0354537e-03 3.2683077e-03 2.7053207e-03 1.0204136e-03 2.0663566e-01 2.0767698e-01 2.4509318e-01 2.8563097e-01 2.5892354e-01 2.8672996e-01 2.3068420e-01 1.8783705e-01 2.3766371e-01 2.3938906e-01 2.6765771e-01 2.1160129e-01 2.5782529e-01 2.8065126e-01 1.4982077e-01 1.9310124e-01 2.7749364e-01 2.2544624e-01 3.3411471e-01 2.3248629e-01 2.8950769e-01 1.9536146e-01 3.4445558e-01 2.8510792e-01 2.1004612e-01 2.0643532e-01 2.6539182e-01 2.8440424e-01 2.6118254e-01 1.6358877e-01 2.3546280e-01 2.1862058e-01 2.0279724e-01 3.7994304e-01 2.9482551e-01 2.0788172e-01 2.2999781e-01 2.9312113e-01 2.0902225e-01 2.5926765e-01 3.0610117e-01 2.5545076e-01 2.2760393e-01 1.9618808e-01 2.5876275e-01 2.1704368e-01 2.2941326e-01 2.1812470e-01 1.2939132e-01 2.2601611e-01 4.8224276e-01 4.1968249e-01 3.9347110e-01 4.1568549e-01 4.4024422e-01 4.3946585e-01 4.3337086e-01 4.2078495e-01 4.4897602e-01 3.6849705e-01 2.9964288e-01 3.9180103e-01 3.6353845e-01 4.4949934e-01 4.4820701e-01 3.5360058e-01 3.7164688e-01 3.7469388e-01 5.1424729e-01 4.1795596e-01 3.7145645e-01 4.0103422e-01 4.5542290e-01 3.3654347e-01 3.6564394e-01 3.6320808e-01 3.1671115e-01 3.1435391e-01 4.3720666e-01 3.4935506e-01 4.0461470e-01 3.1763784e-01 4.4470146e-01 3.3834387e-01 4.4321597e-01 3.8553656e-01 3.9358898e-01 3.6934910e-01 3.0635763e-01 3.3097385e-01 3.9264615e-01 3.0146236e-01 4.1968249e-01 4.0889669e-01 3.8907226e-01 3.4102273e-01 3.8227517e-01 3.3859771e-01 3.6313559e-01 3.6464431e-01 2.0104773e-03 1.1579294e-03 6.7733512e-03 1.3328703e-02 6.1195429e-03 3.8280621e-03 5.4471697e-03 1.3203495e-03 7.3930866e-03 2.5511055e-03 3.8011014e-03 1.5935161e-03 1.6488937e-01 1.6420208e-01 1.9946134e-01 2.3841307e-01 2.1377841e-01 2.3352484e-01 1.8323739e-01 1.4660873e-01 1.9269692e-01 1.9183992e-01 2.2200730e-01 1.6791595e-01 2.1423078e-01 2.2922867e-01 1.1407468e-01 1.5349471e-01 2.2418179e-01 1.7908948e-01 2.8692621e-01 1.8765985e-01 2.3596576e-01 1.5596498e-01 2.9203641e-01 2.3278981e-01 1.6829150e-01 1.6563525e-01 2.1942293e-01 2.3592536e-01 2.1256421e-01 1.2755291e-01 1.9121342e-01 1.7576718e-01 1.6132929e-01 3.2148865e-01 2.3885357e-01 1.6118125e-01 1.8573297e-01 2.4757051e-01 1.6279862e-01 2.1240660e-01 2.5149161e-01 2.0595435e-01 1.8389163e-01 1.5580881e-01 2.0964365e-01 1.6953436e-01 1.8203376e-01 1.7437090e-01 9.9184065e-02 1.8031354e-01 4.1664204e-01 3.5971944e-01 3.3744612e-01 3.5416868e-01 3.7936058e-01 3.7982315e-01 3.7006184e-01 3.6098196e-01 3.8956200e-01 3.1201251e-01 2.4889912e-01 3.3607844e-01 3.1002022e-01 3.9046127e-01 3.9028467e-01 2.9949936e-01 3.1373736e-01 3.1479561e-01 4.5355800e-01 3.6085089e-01 3.1682959e-01 3.4195608e-01 3.9553949e-01 2.8555816e-01 3.0804941e-01 3.0593834e-01 2.6633772e-01 2.6121338e-01 3.7782244e-01 2.9399549e-01 3.4839508e-01 2.6278429e-01 3.8569374e-01 2.8303600e-01 3.7885421e-01 3.3349568e-01 3.3352424e-01 3.1033775e-01 2.5375578e-01 2.8011018e-01 3.3772574e-01 2.5671985e-01 3.5971944e-01 3.5022311e-01 3.3289784e-01 2.9224130e-01 3.3016010e-01 2.8599652e-01 3.0475125e-01 3.0561759e-01 1.3213080e-04 1.6348389e-03 2.1958653e-02 1.3024074e-03 6.2459052e-04 2.7868209e-03 2.5128724e-03 2.4948096e-03 1.5213665e-04 2.9312203e-04 2.6269769e-04 2.0119891e-01 2.0014287e-01 2.3871519e-01 2.8086210e-01 2.5438649e-01 2.7419983e-01 2.2034638e-01 1.8091150e-01 2.3139686e-01 2.2990542e-01 2.6326545e-01 2.0423778e-01 2.5497864e-01 2.7008054e-01 1.4507978e-01 1.8881738e-01 2.6377709e-01 2.1604374e-01 3.3279319e-01 2.2588101e-01 2.7676493e-01 1.9156801e-01 3.3788258e-01 2.7371187e-01 2.0495838e-01 2.0212622e-01 2.6043406e-01 2.7802264e-01 2.5251791e-01 1.6022437e-01 2.2986910e-01 2.1306976e-01 1.9729190e-01 3.6816423e-01 2.7878040e-01 1.9567818e-01 2.2384809e-01 2.9081290e-01 1.9773475e-01 2.5268382e-01 2.9345893e-01 2.4498738e-01 2.2190073e-01 1.9130390e-01 2.4917712e-01 2.0485354e-01 2.1902087e-01 2.1139045e-01 1.2850635e-01 2.1757950e-01 4.6735126e-01 4.0863686e-01 3.8577163e-01 4.0223139e-01 4.2927752e-01 4.3005363e-01 4.1858115e-01 4.1002225e-01 4.4039161e-01 3.5852229e-01 2.9170762e-01 3.8436621e-01 3.5694453e-01 4.4140604e-01 4.4138801e-01 3.4560659e-01 3.5999045e-01 3.6042881e-01 5.0737364e-01 4.1042921e-01 3.6404336e-01 3.8992213e-01 4.4661300e-01 3.3112795e-01 3.5395838e-01 3.5178032e-01 3.1059068e-01 3.0453530e-01 4.2798873e-01 3.3942843e-01 3.9736659e-01 3.0574030e-01 4.3638021e-01 3.2761075e-01 4.2755962e-01 3.8201356e-01 3.8058140e-01 3.5594943e-01 2.9656762e-01 3.2531047e-01 3.8623001e-01 3.0061288e-01 4.0863686e-01 3.9884323e-01 3.8090672e-01 3.3841042e-01 3.7847943e-01 3.3138355e-01 3.5013260e-01 3.5082807e-01 2.3963109e-03 1.8894374e-02 2.2439950e-03 9.0594652e-04 3.4335324e-03 1.6224592e-03 3.5937956e-03 4.5783206e-04 8.1747242e-04 1.3119947e-04 1.9231717e-01 1.9150735e-01 2.2920314e-01 2.7047035e-01 2.4441394e-01 2.6481790e-01 2.1164496e-01 1.7262115e-01 2.2200818e-01 2.2091128e-01 2.5312771e-01 1.9549847e-01 2.4484600e-01 2.6050695e-01 1.3741417e-01 1.8008449e-01 2.5474237e-01 2.0730811e-01 3.2133505e-01 2.1663183e-01 2.6736983e-01 1.8273137e-01 3.2682425e-01 2.6418461e-01 1.9596515e-01 1.9311641e-01 2.5039748e-01 2.6783585e-01 2.4301257e-01 1.5205635e-01 2.2044020e-01 2.0395985e-01 1.8849810e-01 3.5730736e-01 2.6984619e-01 1.8774203e-01 2.1458977e-01 2.8004857e-01 1.8962388e-01 2.4294411e-01 2.8377929e-01 2.3583758e-01 2.1263399e-01 1.8257496e-01 2.3984158e-01 1.9672007e-01 2.1035264e-01 2.0243724e-01 1.2095575e-01 2.0869633e-01 4.5579154e-01 3.9719792e-01 3.7421582e-01 3.9117796e-01 4.1759891e-01 4.1818270e-01 4.0752420e-01 3.9853286e-01 4.2831769e-01 3.4756630e-01 2.8146997e-01 3.7279915e-01 3.4563396e-01 4.2926782e-01 4.2911586e-01 3.3459449e-01 3.4922354e-01 3.5000846e-01 4.9447633e-01 3.9856686e-01 3.5272682e-01 3.7871114e-01 4.3449948e-01 3.2004729e-01 3.4327468e-01 3.4109263e-01 2.9987407e-01 2.9431934e-01 4.1611792e-01 3.2872232e-01 3.8562509e-01 2.9576964e-01 4.2431958e-01 3.1716861e-01 4.1652649e-01 3.7009695e-01 3.6972936e-01 3.4546743e-01 2.8647014e-01 3.1433567e-01 3.7453491e-01 2.8958559e-01 3.9719792e-01 3.8739786e-01 3.6946063e-01 3.2697767e-01 3.6662992e-01 3.2048201e-01 3.3967430e-01 3.4047263e-01 2.8241524e-02 1.4713791e-03 4.6823304e-04 6.0296352e-03 5.2071190e-03 3.4182869e-03 1.9529381e-03 9.1025104e-04 1.8441969e-03 2.3388660e-01 2.3348281e-01 2.7404368e-01 3.1787596e-01 2.8991570e-01 3.1331298e-01 2.5580945e-01 2.1274052e-01 2.6625865e-01 2.6571335e-01 2.9921857e-01 2.3779036e-01 2.8985951e-01 3.0846478e-01 1.7343111e-01 2.2024476e-01 3.0259960e-01 2.5097696e-01 3.7082192e-01 2.6053197e-01 3.1606460e-01 2.2298918e-01 3.7823337e-01 3.1251210e-01 2.3776110e-01 2.3444923e-01 2.9645330e-01 3.1548927e-01 2.8938607e-01 1.8922982e-01 2.6439849e-01 2.4655891e-01 2.2974757e-01 4.1142478e-01 3.1887022e-01 2.2998734e-01 2.5821680e-01 3.2732410e-01 2.3197317e-01 2.8887378e-01 3.3359010e-01 2.8195681e-01 2.5599107e-01 2.2312925e-01 2.8613603e-01 2.3977955e-01 2.5441596e-01 2.4519140e-01 1.5385241e-01 2.5232104e-01 5.1493631e-01 4.5323214e-01 4.2845834e-01 4.4724986e-01 4.7457294e-01 4.7489714e-01 4.6451744e-01 4.5458397e-01 4.8531016e-01 4.0086775e-01 3.3039510e-01 4.2690815e-01 3.9810528e-01 4.8619268e-01 4.8572861e-01 3.8678000e-01 4.0288412e-01 4.0400345e-01 5.5371159e-01 4.5396321e-01 4.0578110e-01 4.3384503e-01 4.9180921e-01 3.7075981e-01 3.9660943e-01 3.9426853e-01 3.4955388e-01 3.4443823e-01 4.7269976e-01 3.8095285e-01 4.4033063e-01 3.4628429e-01 4.8107810e-01 3.6885113e-01 4.7400892e-01 4.2299055e-01 4.2466221e-01 3.9913020e-01 3.3607659e-01 3.6477722e-01 4.2848136e-01 3.3695835e-01 4.5323214e-01 4.4278413e-01 4.2356519e-01 3.7724050e-01 4.1943108e-01 3.7167660e-01 3.9296882e-01 3.9389283e-01 3.3787081e-02 2.1999955e-02 3.5606869e-02 9.7919779e-03 3.8730614e-02 2.5164827e-02 2.6849118e-02 1.7903003e-02 1.2662420e-01 1.3061214e-01 1.5776511e-01 1.8787898e-01 1.6611889e-01 2.0099100e-01 1.5292701e-01 1.1474225e-01 1.5177722e-01 1.5820095e-01 1.7289957e-01 1.3332512e-01 1.6306194e-01 1.9266388e-01 8.3782583e-02 1.1475472e-01 1.9583103e-01 1.4727273e-01 2.2350713e-01 1.4824286e-01 2.0360977e-01 1.1578494e-01 2.3876064e-01 1.9766401e-01 1.2872248e-01 1.2487918e-01 1.7184761e-01 1.8912261e-01 1.7398714e-01 9.1697206e-02 1.4908080e-01 1.3596066e-01 1.2380963e-01 2.7656824e-01 2.1428571e-01 1.3903777e-01 1.4563130e-01 1.9085533e-01 1.3814153e-01 1.6927103e-01 2.1683317e-01 1.7245190e-01 1.4312164e-01 1.1792045e-01 1.7357532e-01 1.4591400e-01 1.5204372e-01 1.3733781e-01 6.4362895e-02 1.4610010e-01 3.6994724e-01 3.0922665e-01 2.8172128e-01 3.0981046e-01 3.2683078e-01 3.2376302e-01 3.2734273e-01 3.0972403e-01 3.3079457e-01 2.6398840e-01 2.0377846e-01 2.7991998e-01 2.5479336e-01 3.3054260e-01 3.2764011e-01 2.4845492e-01 2.6936921e-01 2.7648193e-01 3.8578015e-01 3.0244531e-01 2.6262990e-01 2.9337350e-01 3.3673416e-01 2.3057418e-01 2.6437275e-01 2.6189855e-01 2.1511723e-01 2.1874548e-01 3.2151629e-01 2.4786804e-01 2.9060022e-01 2.2492263e-01 3.2669967e-01 2.4001378e-01 3.3739817e-01 2.6941492e-01 2.9020759e-01 2.7043921e-01 2.1216140e-01 2.2627991e-01 2.7921868e-01 1.9599823e-01 3.0922665e-01 2.9841488e-01 2.7865157e-01 2.3076134e-01 2.6697481e-01 2.3479056e-01 2.6453179e-01 2.6724220e-01 1.8463349e-03 1.7603525e-03 7.3051568e-03 4.1015204e-04 7.6986614e-04 4.1505661e-04 2.5516535e-03 2.2715123e-01 2.2501703e-01 2.6621562e-01 3.1107232e-01 2.8349906e-01 3.0014485e-01 2.4486000e-01 2.0495206e-01 2.5858102e-01 2.5538755e-01 2.9285644e-01 2.2943000e-01 2.8485147e-01 2.9693404e-01 1.6780939e-01 2.1457958e-01 2.8854018e-01 2.4083271e-01 3.6655716e-01 2.5261802e-01 3.0270375e-01 2.1770871e-01 3.6954821e-01 3.0027680e-01 2.3128364e-01 2.2864524e-01 2.8964053e-01 3.0736347e-01 2.7955757e-01 1.8460266e-01 2.5729263e-01 2.3962699e-01 2.2298939e-01 3.9825299e-01 3.0280555e-01 2.1772566e-01 2.5069761e-01 3.2250864e-01 2.2044120e-01 2.8076678e-01 3.2019205e-01 2.7070478e-01 2.4885895e-01 2.1694127e-01 2.7557358e-01 2.2744661e-01 2.4343281e-01 2.3729093e-01 1.5163937e-01 2.4296011e-01 4.9867564e-01 4.4043288e-01 4.1855224e-01 4.3250726e-01 4.6172807e-01 4.6333132e-01 4.4859207e-01 4.4201028e-01 4.7437581e-01 3.8923920e-01 3.2088439e-01 4.1722959e-01 3.8928568e-01 4.7566007e-01 4.7625383e-01 3.7686783e-01 3.8986190e-01 3.8884081e-01 5.4387473e-01 4.4406349e-01 3.9623886e-01 4.2109532e-01 4.8065646e-01 3.6308022e-01 3.8359994e-01 3.8148432e-01 3.4142085e-01 3.3328357e-01 4.6130540e-01 3.6948155e-01 4.3073385e-01 3.3341169e-01 4.7038237e-01 3.5679054e-01 4.5732763e-01 4.1658437e-01 4.1040883e-01 3.8470265e-01 3.2499997e-01 3.5692838e-01 4.1963835e-01 3.3330111e-01 4.4043288e-01 4.3085698e-01 4.1330060e-01 3.7185723e-01 4.1281711e-01 3.6250007e-01 3.7890193e-01 3.7915604e-01 5.4235993e-03 2.5998420e-03 3.8677378e-03 1.1491859e-03 6.2877149e-04 4.8332372e-04 2.1486898e-01 2.1467854e-01 2.5369922e-01 2.9604720e-01 2.6891688e-01 2.9246555e-01 2.3653315e-01 1.9467620e-01 2.4616446e-01 2.4597983e-01 2.7792166e-01 2.1880759e-01 2.6876410e-01 2.8748855e-01 1.5684504e-01 2.0165721e-01 2.8227299e-01 2.3174975e-01 3.4738814e-01 2.4066356e-01 2.9516630e-01 2.0426320e-01 3.5491429e-01 2.9152419e-01 2.1857252e-01 2.1532575e-01 2.7527993e-01 2.9385335e-01 2.6877316e-01 1.7184619e-01 2.4431046e-01 2.2708854e-01 2.1089056e-01 3.8786051e-01 2.9845589e-01 2.1195408e-01 2.3838722e-01 3.0508869e-01 2.1371586e-01 2.6806783e-01 3.1216051e-01 2.6180490e-01 2.3620065e-01 2.0447438e-01 2.6573552e-01 2.2136814e-01 2.3519672e-01 2.2587537e-01 1.3797486e-01 2.3292930e-01 4.8968343e-01 4.2865553e-01 4.0406082e-01 4.2311568e-01 4.4955467e-01 4.4970134e-01 4.4022012e-01 4.2994584e-01 4.5983149e-01 3.7733154e-01 3.0847182e-01 4.0252124e-01 3.7430526e-01 4.6065171e-01 4.6008825e-01 3.6337620e-01 3.7950484e-01 3.8097080e-01 5.2689771e-01 4.2900834e-01 3.8187058e-01 4.0969052e-01 4.6622956e-01 3.4753954e-01 3.7338441e-01 3.7106677e-01 3.2693971e-01 3.2232718e-01 4.4752724e-01 3.5789640e-01 4.1563695e-01 3.2438944e-01 4.5565562e-01 3.4619907e-01 4.4965557e-01 3.9841763e-01 4.0096111e-01 3.7608333e-01 3.1419485e-01 3.4172782e-01 4.0397551e-01 3.1440281e-01 4.2865553e-01 4.1831326e-01 3.9931889e-01 3.5369559e-01 3.9495166e-01 3.4857729e-01 3.7001412e-01 3.7103653e-01 9.4891023e-03 8.2375707e-04 1.6686708e-03 2.4475618e-03 4.6565778e-03 2.0491959e-01 2.0139969e-01 2.4160221e-01 2.8586265e-01 2.5951451e-01 2.7003471e-01 2.1844761e-01 1.8272084e-01 2.3436629e-01 2.2908460e-01 2.6861177e-01 2.0572725e-01 2.6206958e-01 2.6821933e-01 1.4908933e-01 1.9377976e-01 2.5813756e-01 2.1522061e-01 3.4200281e-01 2.2841742e-01 2.7236034e-01 1.9712279e-01 3.4101997e-01 2.7089218e-01 2.0911514e-01 2.0716704e-01 2.6510721e-01 2.8112922e-01 2.5280689e-01 1.6599180e-01 2.3361443e-01 2.1674754e-01 2.0090153e-01 3.6527528e-01 2.7043122e-01 1.9136502e-01 2.2690330e-01 2.9871211e-01 1.9453190e-01 2.5541554e-01 2.8925890e-01 2.4310626e-01 2.2546391e-01 1.9558871e-01 2.4836004e-01 2.0063248e-01 2.1704777e-01 2.1367234e-01 1.3678274e-01 2.1790721e-01 4.6096128e-01 4.0671584e-01 3.8773373e-01 3.9744288e-01 4.2748862e-01 4.3009711e-01 4.1223718e-01 4.0843810e-01 4.4142325e-01 3.5773584e-01 2.9306179e-01 3.8663461e-01 3.6011993e-01 4.4301487e-01 4.4448732e-01 3.4710733e-01 3.5722182e-01 3.5456119e-01 5.1046759e-01 4.1264841e-01 3.6628224e-01 3.8788026e-01 4.4738884e-01 3.3559118e-01 3.5113614e-01 3.4925623e-01 3.1415212e-01 3.0373195e-01 4.2825709e-01 3.3863565e-01 3.9988417e-01 3.0248234e-01 4.3771445e-01 3.2582243e-01 4.2019828e-01 3.8883040e-01 3.7626984e-01 3.5109855e-01 2.9576205e-01 3.2946337e-01 3.8969349e-01 3.1023698e-01 4.0671584e-01 3.9807129e-01 3.8231042e-01 3.4615104e-01 3.8497123e-01 3.3374807e-01 3.4573433e-01 3.4546409e-01 9.9827550e-03 3.7862368e-03 4.2584946e-03 1.2265791e-03 1.7617949e-01 1.7703821e-01 2.1212343e-01 2.5052805e-01 2.2530971e-01 2.5127302e-01 1.9850334e-01 1.5861692e-01 2.0515179e-01 2.0665158e-01 2.3356888e-01 1.8070269e-01 2.2448284e-01 2.4549391e-01 1.2367511e-01 1.6367665e-01 2.4261509e-01 1.9359579e-01 2.9712394e-01 2.0027260e-01 2.5390638e-01 1.6582785e-01 3.0629971e-01 2.4971132e-01 1.7938888e-01 1.7609087e-01 2.3136866e-01 2.4921343e-01 2.2715314e-01 1.3644726e-01 2.0313672e-01 1.8736201e-01 1.7259709e-01 3.3997757e-01 2.5916969e-01 1.7732718e-01 1.9796934e-01 2.5795325e-01 1.7832877e-01 2.2545225e-01 2.6961076e-01 2.2173788e-01 1.9575867e-01 1.6649347e-01 2.2485533e-01 1.8584864e-01 1.9731550e-01 1.8682160e-01 1.0543428e-01 1.9413835e-01 4.3845459e-01 3.7813833e-01 3.5313427e-01 3.7426892e-01 3.9794201e-01 3.9726718e-01 3.9130315e-01 3.7920848e-01 4.0649770e-01 3.2906316e-01 2.6354873e-01 3.5155280e-01 3.2453882e-01 4.0704153e-01 4.0591144e-01 3.1492301e-01 3.3203733e-01 3.3498583e-01 4.6984073e-01 3.7668481e-01 3.3204804e-01 3.6021418e-01 4.1270518e-01 2.9886261e-01 3.2629567e-01 3.2396944e-01 2.7989788e-01 2.7743776e-01 3.9510115e-01 3.1077003e-01 3.6387709e-01 2.8054253e-01 4.0239292e-01 3.0024699e-01 4.0081171e-01 3.4598760e-01 3.5305310e-01 3.2985030e-01 2.6984561e-01 2.9352511e-01 3.5245796e-01 2.6611615e-01 3.7813833e-01 3.6780473e-01 3.4887718e-01 3.0350575e-01 3.4281917e-01 3.0065558e-01 3.2390437e-01 3.2536506e-01 1.4872402e-03 1.4261828e-03 4.3370414e-03 2.2707675e-01 2.2400493e-01 2.6569285e-01 3.1123078e-01 2.8378554e-01 2.9683242e-01 2.4257696e-01 2.0422363e-01 2.5811380e-01 2.5348079e-01 2.9319960e-01 2.2849166e-01 2.8589147e-01 2.9450563e-01 1.6807288e-01 2.1502765e-01 2.8470332e-01 2.3897250e-01 3.6819788e-01 2.5200370e-01 2.9929335e-01 2.1837402e-01 3.6884033e-01 2.9747447e-01 2.3136263e-01 2.2907381e-01 2.8973378e-01 3.0680413e-01 2.7799960e-01 1.8547528e-01 2.5712752e-01 2.3949352e-01 2.2288675e-01 3.9517754e-01 2.9790586e-01 2.1461980e-01 2.5028856e-01 3.2375470e-01 2.1774627e-01 2.8014020e-01 3.1681085e-01 2.6834640e-01 2.4865097e-01 2.1711982e-01 2.7360556e-01 2.2434130e-01 2.4112622e-01 2.3662943e-01 1.5362542e-01 2.4153192e-01 4.9407199e-01 4.3763583e-01 4.1728329e-01 4.2863716e-01 4.5893922e-01 4.6123811e-01 4.4410036e-01 4.3933832e-01 4.7263926e-01 3.8698450e-01 3.1961653e-01 4.1608078e-01 3.8854631e-01 4.7414193e-01 4.7530563e-01 3.7551546e-01 3.8685112e-01 3.8465728e-01 5.4280771e-01 4.4284025e-01 3.9512469e-01 4.1829234e-01 4.7881043e-01 3.6289720e-01 3.8058317e-01 3.7858579e-01 3.4094528e-01 3.3115887e-01 4.5930198e-01 3.6727055e-01 4.2965213e-01 3.3035276e-01 4.6876907e-01 3.5423104e-01 4.5243416e-01 4.1718566e-01 4.0676646e-01 3.8091352e-01 3.2289623e-01 3.5664111e-01 4.1894270e-01 3.3518670e-01 4.3763583e-01 4.2851737e-01 4.1182914e-01 3.7291664e-01 4.1330116e-01 3.6151280e-01 3.7529785e-01 3.7518553e-01 2.1466294e-04 8.1135795e-04 2.0395325e-01 2.0232995e-01 2.4147692e-01 2.8429935e-01 2.5773107e-01 2.7550146e-01 2.2191392e-01 1.8311464e-01 2.3414066e-01 2.3177254e-01 2.6670079e-01 2.0650082e-01 2.5876572e-01 2.7190067e-01 1.4755667e-01 1.9178198e-01 2.6468375e-01 2.1783813e-01 3.3738596e-01 2.2849816e-01 2.7802206e-01 1.9468150e-01 3.4113386e-01 2.7532568e-01 2.0783209e-01 2.0518365e-01 2.6370466e-01 2.8101900e-01 2.5475268e-01 1.6319612e-01 2.3278117e-01 2.1587899e-01 1.9999930e-01 3.7014440e-01 2.7911349e-01 1.9654509e-01 2.2657275e-01 2.9494234e-01 1.9887441e-01 2.5547815e-01 2.9483790e-01 2.4668408e-01 2.2473055e-01 1.9413008e-01 2.5114079e-01 2.0579387e-01 2.2056413e-01 2.1387343e-01 1.3181835e-01 2.1964768e-01 4.6883654e-01 4.1098091e-01 3.8893009e-01 4.0388763e-01 4.3171852e-01 4.3291890e-01 4.1992695e-01 4.1244869e-01 4.4351517e-01 3.6095375e-01 2.9436456e-01 3.8758959e-01 3.6027619e-01 4.4466450e-01 4.4498549e-01 3.4851465e-01 3.6196876e-01 3.6168475e-01 5.1115719e-01 4.1372900e-01 3.6718805e-01 3.9217531e-01 4.4969696e-01 3.3465314e-01 3.5590188e-01 3.5378490e-01 3.1382959e-01 3.0675099e-01 4.3089908e-01 3.4178657e-01 4.0069101e-01 3.0739171e-01 4.3956059e-01 3.2969272e-01 4.2869495e-01 3.8625531e-01 3.8227459e-01 3.5742723e-01 2.9874912e-01 3.2874265e-01 3.8973129e-01 3.0516637e-01 4.1098091e-01 4.0141982e-01 3.8392089e-01 3.4269880e-01 3.8263609e-01 3.3443392e-01 3.5169446e-01 3.5216636e-01 9.1517643e-04 2.1524074e-01 2.1387759e-01 2.5373107e-01 2.9714785e-01 2.7003778e-01 2.8920001e-01 2.3426287e-01 1.9410928e-01 2.4622443e-01 2.4423617e-01 2.7915984e-01 2.1812334e-01 2.7081916e-01 2.8531140e-01 1.5727134e-01 2.0260081e-01 2.7829111e-01 2.2998414e-01 3.5056990e-01 2.4050939e-01 2.9179089e-01 2.0549251e-01 3.5521699e-01 2.8889714e-01 2.1915718e-01 2.1632646e-01 2.7619378e-01 2.9404932e-01 2.6759110e-01 1.7313032e-01 2.4473695e-01 2.2745766e-01 2.1120369e-01 3.8534612e-01 2.9320974e-01 2.0849982e-01 2.3847660e-01 3.0758274e-01 2.1079215e-01 2.6804702e-01 3.0890781e-01 2.5958017e-01 2.3652855e-01 2.0509955e-01 2.6402277e-01 2.1797131e-01 2.3288914e-01 2.2557846e-01 1.4040598e-01 2.3171289e-01 4.8566100e-01 4.2666426e-01 4.0387929e-01 4.1975185e-01 4.4765854e-01 4.4867725e-01 4.3614287e-01 4.2811681e-01 4.5929331e-01 3.7580128e-01 3.0785579e-01 4.0248311e-01 3.7465557e-01 4.6038945e-01 4.6053443e-01 3.6291292e-01 3.7703492e-01 3.7702914e-01 5.2744561e-01 4.2898757e-01 3.8178676e-01 4.0761255e-01 4.6557959e-01 3.4846205e-01 3.7087965e-01 3.6870032e-01 3.2740185e-01 3.2070546e-01 4.4660679e-01 3.5633353e-01 4.1573756e-01 3.2160319e-01 4.5525495e-01 3.4414484e-01 4.4510906e-01 4.0053809e-01 3.9779009e-01 3.7261777e-01 3.1255985e-01 3.4249381e-01 4.0450836e-01 3.1773210e-01 4.2666426e-01 4.1685273e-01 3.9886436e-01 3.5618753e-01 3.9691207e-01 3.4850257e-01 3.6675459e-01 3.6731925e-01 1.9628524e-01 1.9595152e-01 2.3363495e-01 2.7477230e-01 2.4851118e-01 2.7084921e-01 2.1685944e-01 1.7678722e-01 2.2636857e-01 2.2599655e-01 2.5724256e-01 1.9993208e-01 2.4854721e-01 2.6607935e-01 1.4078995e-01 1.8369551e-01 2.6097312e-01 2.1228442e-01 3.2508389e-01 2.2103528e-01 2.7346255e-01 1.8624671e-01 3.3183707e-01 2.6996197e-01 1.9987779e-01 1.9682726e-01 2.5462868e-01 2.7249816e-01 2.4805238e-01 1.5523356e-01 2.2463473e-01 2.0803519e-01 1.9245485e-01 3.6364465e-01 2.7666661e-01 1.9319723e-01 2.1887484e-01 2.8382186e-01 1.9489313e-01 2.4750809e-01 2.8994226e-01 2.4123726e-01 2.1680288e-01 1.8634448e-01 2.4506974e-01 2.0223799e-01 2.1557050e-01 2.0677515e-01 1.2326853e-01 2.1346465e-01 4.6321322e-01 4.0354170e-01 3.7970298e-01 3.9803381e-01 4.2400584e-01 4.2423988e-01 4.1474064e-01 4.0481837e-01 4.3422436e-01 3.5345426e-01 2.8656464e-01 3.7821934e-01 3.5073976e-01 4.3506541e-01 4.3461895e-01 3.3997143e-01 3.5549675e-01 3.5686166e-01 5.0021759e-01 4.0411514e-01 3.5805390e-01 3.8499293e-01 4.4048074e-01 3.2475847e-01 3.4952686e-01 3.4727706e-01 3.0464816e-01 2.9991017e-01 4.2212289e-01 3.3451084e-01 3.9105629e-01 3.0183284e-01 4.3015090e-01 3.2308204e-01 4.2396798e-01 3.7459262e-01 3.7639485e-01 3.5210962e-01 2.9201270e-01 3.1907654e-01 3.7972706e-01 2.9306033e-01 4.0354170e-01 3.9347753e-01 3.7503434e-01 3.3106203e-01 3.7117506e-01 3.2561211e-01 3.4620183e-01 3.4718282e-01 5.4794269e-04 1.8595150e-03 7.6486933e-03 3.6709934e-03 1.1297771e-02 3.2211024e-03 9.3536855e-04 1.2319946e-03 2.8277178e-03 4.9162548e-03 4.5756064e-04 4.5051431e-03 8.2041162e-03 5.0174936e-03 3.6228961e-04 1.1821527e-02 2.0977093e-03 2.1132418e-02 9.2067605e-04 1.2006919e-02 4.4257629e-04 2.0287770e-02 9.6871594e-03 3.6190304e-05 1.5767395e-04 4.3807979e-03 7.0604016e-03 4.2468409e-03 2.9914879e-03 1.0695136e-03 1.8996353e-04 2.2349983e-05 3.2007300e-02 1.8626112e-02 5.9280137e-03 7.1455899e-04 1.0704644e-02 4.3625889e-03 3.3835317e-03 1.4689427e-02 5.0222514e-03 5.6899707e-04 1.6818673e-04 4.5805421e-03 5.6585758e-03 3.2371570e-03 3.2354233e-04 1.1207565e-02 1.3610741e-03 7.1737216e-02 4.4570224e-02 3.4818230e-02 4.5037397e-02 5.2043753e-02 5.1252208e-02 5.2740741e-02 4.4819381e-02 5.4844272e-02 2.7492078e-02 9.9689819e-03 3.4289978e-02 2.5632216e-02 5.5069559e-02 5.4817042e-02 2.2717491e-02 2.9451557e-02 3.3085492e-02 8.2968826e-02 4.3268668e-02 2.7878625e-02 3.8225012e-02 5.7392372e-02 1.8533113e-02 2.7752880e-02 2.6863838e-02 1.3522246e-02 1.3720834e-02 5.0366251e-02 2.2136217e-02 3.8620607e-02 1.6104597e-02 5.3184340e-02 1.9805362e-02 5.7449954e-02 3.4065200e-02 3.7316903e-02 3.0490470e-02 1.2067932e-02 1.7032969e-02 3.4772493e-02 1.3995163e-02 4.4570224e-02 4.0370599e-02 3.3379869e-02 2.1584097e-02 3.2788169e-02 1.8689520e-02 2.8313400e-02 2.9690702e-02 2.1810130e-03 9.0584977e-03 5.0419708e-03 8.6169391e-03 1.3497185e-03 5.5621011e-04 1.5626898e-03 1.4715788e-03 6.4159866e-03 2.8759099e-05 6.7456588e-03 6.5333041e-03 5.4642837e-03 1.4108585e-03 8.4463171e-03 6.8795048e-04 2.4580787e-02 1.0388021e-03 9.2286561e-03 1.7639378e-03 2.1069750e-02 7.6032385e-03 7.9175727e-04 1.2932915e-03 5.5725803e-03 7.6219263e-03 3.4673161e-03 4.2795834e-03 1.7380404e-03 7.6483144e-04 5.0584546e-04 3.0216137e-02 1.4096242e-02 2.8861882e-03 1.0548294e-03 1.3430225e-02 1.8234348e-03 3.6876972e-03 1.2061426e-02 3.2511669e-03 1.1268214e-03 9.2690858e-04 3.2976984e-03 2.6938518e-03 1.3276664e-03 2.7368625e-04 1.3465991e-02 4.6822375e-04 6.8703048e-02 4.3340061e-02 3.5237451e-02 4.2511976e-02 5.0937508e-02 5.0961712e-02 4.9575019e-02 4.3743233e-02 5.5024281e-02 2.6603107e-02 9.8022959e-03 3.4840370e-02 2.6494597e-02 5.5508568e-02 5.5916268e-02 2.2800218e-02 2.7686924e-02 2.9943438e-02 8.4266289e-02 4.3880838e-02 2.8349604e-02 3.6881146e-02 5.7478275e-02 1.9879185e-02 2.5944410e-02 2.5180647e-02 1.4399125e-02 1.2641568e-02 5.0169037e-02 2.1180767e-02 3.9323276e-02 1.3938661e-02 5.3485998e-02 1.8367828e-02 5.3859405e-02 3.6620288e-02 3.4933732e-02 2.7786245e-02 1.0959937e-02 1.8222006e-02 3.5858197e-02 1.7515874e-02 4.3340061e-02 3.9619354e-02 3.3535832e-02 2.4403195e-02 3.5188163e-02 1.9107255e-02 2.5791231e-02 2.6740983e-02 2.3644893e-03 7.3259797e-04 5.5696730e-03 2.5707992e-03 4.5988577e-03 6.5028014e-05 1.3580837e-03 1.2238983e-03 1.7109321e-03 1.8714646e-03 2.9944669e-03 1.2905109e-02 3.6725511e-03 6.9487479e-03 1.9516582e-03 1.2369689e-02 2.1307052e-04 6.0423668e-03 3.5751225e-03 1.0028727e-02 4.0861108e-03 1.5902610e-03 2.1378325e-03 8.2628456e-04 1.7251178e-03 8.0406810e-04 9.4788056e-03 1.5999623e-04 8.7509151e-04 2.2560004e-03 1.8732619e-02 1.2379328e-02 7.5803848e-03 2.7223442e-04 5.1089250e-03 5.8562851e-03 2.2798360e-04 7.3027135e-03 2.0605789e-03 3.8651941e-04 3.1125030e-03 1.3286042e-03 6.3896783e-03 2.7088758e-03 9.7424018e-04 2.1879745e-02 1.1792881e-03 5.1158538e-02 2.8393852e-02 2.0746802e-02 2.9099880e-02 3.4416089e-02 3.3763806e-02 3.5540899e-02 2.8575694e-02 3.6757319e-02 1.5144171e-02 3.2315336e-03 2.0363783e-02 1.3898176e-02 3.6986917e-02 3.6943012e-02 1.1630303e-02 1.6809451e-02 2.0213112e-02 6.0723696e-02 2.7441217e-02 1.5470812e-02 2.3398093e-02 3.8846172e-02 9.0112370e-03 1.5570624e-02 1.4874310e-02 5.5171614e-03 5.6710746e-03 3.3048406e-02 1.1270408e-02 2.3776077e-02 7.7482583e-03 3.5413096e-02 9.7831561e-03 3.9569593e-02 2.1047269e-02 2.3004495e-02 1.8015778e-02 4.6822715e-03 7.9260822e-03 2.0880056e-02 7.9428824e-03 2.8393852e-02 2.5015882e-02 1.9595645e-02 1.2073748e-02 1.9972960e-02 8.8353740e-03 1.6316616e-02 1.7585650e-02 7.5921246e-04 7.6581519e-03 8.7602132e-03 1.3285408e-02 3.1081233e-03 6.1183864e-03 3.4258252e-04 8.0693053e-03 1.1527108e-03 4.3989827e-03 2.4770294e-02 1.0297780e-02 1.0716436e-02 8.0921947e-03 4.0468338e-03 3.9682629e-03 8.0100500e-03 9.6963228e-03 3.4263963e-03 5.5915243e-03 6.8271736e-03 7.3405400e-03 4.5375223e-04 3.3086446e-04 2.9090971e-03 1.8954919e-02 3.0536401e-03 5.4665114e-03 8.4934379e-03 1.2174442e-02 1.6139108e-02 1.7445625e-02 4.0248005e-03 8.4214697e-04 1.4968032e-02 1.3210674e-03 7.6674238e-03 5.8292743e-03 4.1363018e-03 9.7236406e-03 4.2254644e-03 1.5250051e-02 9.0649565e-03 6.2807525e-03 3.4303230e-02 6.6673397e-03 3.8666536e-02 1.8227060e-02 1.0732501e-02 2.0722480e-02 2.2627004e-02 2.1137328e-02 2.6644819e-02 1.8156140e-02 2.2956019e-02 8.4429075e-03 1.3075172e-03 1.0338488e-02 5.7120788e-03 2.2877982e-02 2.2249653e-02 5.0348740e-03 1.0858478e-02 1.5670368e-02 4.1328272e-02 1.5415087e-02 7.0760309e-03 1.4735153e-02 2.4690692e-02 2.4709117e-03 1.0136092e-02 9.4846153e-03 1.0671740e-03 3.5238125e-03 2.0483172e-02 6.1432951e-03 1.2637418e-02 6.5903261e-03 2.1786492e-02 6.0819601e-03 3.0486925e-02 9.5174641e-03 1.6103409e-02 1.3353819e-02 3.2356456e-03 1.9926682e-03 1.0243487e-02 2.2538296e-03 1.8227060e-02 1.5108364e-02 1.0188754e-02 3.7627514e-03 8.8362152e-03 3.0791096e-03 1.1920070e-02 1.3588585e-02 8.0203401e-03 5.9881494e-03 7.9998519e-03 1.0330180e-03 3.9563353e-03 9.0962406e-05 4.3155161e-03 3.4798297e-04 4.5913809e-03 1.6934319e-02 5.4778348e-03 1.0415690e-02 5.0724002e-03 7.4143773e-03 1.5625174e-03 8.5090504e-03 5.0363885e-03 7.2978538e-03 5.9687085e-03 3.0800386e-03 3.3854665e-03 6.0369763e-05 9.7766526e-04 2.1106407e-03 1.2150960e-02 8.7050457e-04 2.2450596e-03 4.2656078e-03 1.7573546e-02 1.6541615e-02 1.3010703e-02 1.4863348e-03 2.0318224e-03 1.0715058e-02 5.1959240e-04 9.1091800e-03 4.5577281e-03 1.4679901e-03 5.0882044e-03 3.2208315e-03 1.1416637e-02 6.2084512e-03 2.9761078e-03 2.5010280e-02 3.7556543e-03 4.8641699e-02 2.5613136e-02 1.7022941e-02 2.7738185e-02 3.0983818e-02 2.9540677e-02 3.4381880e-02 2.5612023e-02 3.1832087e-02 1.3388731e-02 2.7364434e-03 1.6556686e-02 1.0555947e-02 3.1795721e-02 3.1144834e-02 9.3248236e-03 1.5850102e-02 2.0586917e-02 5.3133864e-02 2.2885657e-02 1.2291162e-02 2.1220606e-02 3.3851933e-02 5.9611654e-03 1.4821288e-02 1.4065156e-02 3.4935277e-03 5.6486577e-03 2.8790647e-02 1.0114487e-02 1.9481208e-02 8.7344459e-03 3.0476411e-02 9.4253713e-03 3.8601009e-02 1.5492941e-02 2.2081481e-02 1.8082534e-02 4.9261084e-03 5.1866248e-03 1.6531969e-02 3.8794099e-03 2.5613136e-02 2.2037756e-02 1.6246851e-02 7.5034139e-03 1.4652134e-02 6.6254167e-03 1.6367612e-02 1.8037074e-02 3.5963937e-03 1.3253217e-02 6.2726815e-03 2.9800542e-03 8.1679414e-03 8.1170586e-03 1.1643531e-02 4.8148791e-04 2.7366574e-02 1.5666050e-02 3.5602925e-04 4.6080605e-03 1.9717886e-02 6.2846724e-03 1.0858138e-05 1.6108248e-02 9.2526017e-03 1.6342536e-04 1.1289374e-02 1.3182521e-02 7.1086671e-03 4.8107615e-03 2.1505038e-03 2.4867835e-02 7.4585494e-03 9.3457001e-03 1.1859292e-02 8.6506765e-03 1.5825531e-03 7.6287797e-03 7.1464196e-03 1.3469318e-02 7.0587993e-03 4.6011946e-03 3.8423475e-04 1.2933991e-03 8.0463849e-03 1.4044313e-02 1.5554975e-03 5.8246512e-03 3.7445923e-03 7.8179143e-03 4.3383609e-02 5.1292419e-03 3.1208966e-02 1.7106674e-02 1.5590525e-02 1.4711786e-02 2.2118312e-02 2.3677515e-02 1.8355265e-02 1.7620984e-02 2.7322237e-02 8.2125300e-03 3.2025084e-03 1.5673958e-02 1.1993026e-02 2.8215754e-02 3.0043744e-02 8.3225318e-03 7.3692394e-03 7.0283195e-03 5.0297559e-02 2.1175797e-02 1.1957606e-02 1.3050611e-02 2.8749586e-02 1.0337000e-02 6.4346633e-03 6.2339245e-03 7.0135662e-03 2.1302546e-03 2.3356399e-02 5.5096838e-03 1.8637999e-02 1.1462623e-03 2.6577654e-02 3.5495923e-03 2.0668821e-02 2.2280093e-02 1.0461671e-02 6.3164945e-03 1.6640428e-03 9.2261377e-03 1.7584245e-02 1.7910761e-02 1.7106674e-02 1.5703153e-02 1.4029303e-02 1.7152691e-02 2.0996496e-02 7.4959989e-03 5.5175599e-03 5.5902559e-03 3.2004692e-03 2.3419030e-03 2.3799055e-04 7.0894399e-03 1.3013007e-03 8.8089668e-03 2.9046172e-03 1.1317768e-02 5.4246845e-03 3.1331888e-03 1.2023171e-04 2.4596565e-02 1.8369557e-03 3.9714617e-03 5.9789699e-03 1.7350836e-02 3.3069650e-03 3.5250221e-03 4.6752456e-03 5.9850395e-03 6.3682068e-03 1.7841601e-03 1.0289989e-02 3.0278347e-03 2.8250687e-03 3.3130538e-03 2.2020473e-02 6.7612416e-03 1.5121522e-03 2.2413165e-03 1.4397994e-02 9.1427197e-04 3.3482085e-03 6.1881321e-03 7.4537620e-04 2.6935839e-03 4.4237033e-03 1.1678481e-03 9.0068892e-04 2.9616334e-06 1.6314282e-03 2.2817755e-02 4.4730602e-04 5.5111404e-02 3.4158218e-02 2.9021577e-02 3.2043073e-02 4.1089749e-02 4.2083473e-02 3.7707137e-02 3.4692829e-02 4.6329738e-02 1.9906109e-02 6.9820129e-03 2.8847990e-02 2.2051442e-02 4.7099521e-02 4.8343924e-02 1.7860817e-02 1.9868896e-02 2.0488578e-02 7.4490873e-02 3.6906910e-02 2.3107397e-02 2.8319511e-02 4.8426947e-02 1.7182411e-02 1.8331671e-02 1.7811306e-02 1.2023707e-02 8.1012404e-03 4.1487060e-02 1.5227136e-02 3.2990763e-02 8.0392167e-03 4.5098943e-02 1.2350504e-02 4.1149609e-02 3.3318195e-02 2.5517275e-02 1.9030633e-02 6.7530681e-03 1.5581926e-02 3.0469465e-02 1.9100479e-02 3.4158218e-02 3.1415243e-02 2.7190491e-02 2.3321177e-02 3.1828581e-02 1.5212907e-02 1.7498443e-02 1.7919743e-02 3.6042298e-03 3.7468828e-03 9.7768723e-03 7.8664808e-04 9.4997776e-03 1.0849175e-02 2.5690262e-03 9.8725606e-04 1.2645853e-02 2.2464135e-03 3.0594913e-02 2.8398433e-03 1.3996699e-02 1.4253747e-03 2.8011999e-02 1.2147431e-02 1.3365379e-03 1.5899927e-03 8.8722552e-03 1.1913349e-02 6.7897739e-03 2.1135064e-03 3.6254453e-03 1.7898196e-03 6.9240260e-04 3.8891340e-02 1.9082524e-02 3.5297395e-03 2.7024302e-03 1.7766622e-02 2.5342822e-03 6.8092652e-03 1.7593080e-02 6.3527044e-03 2.6240465e-03 7.1495011e-04 6.5371413e-03 3.8357773e-03 3.1136154e-03 1.3436442e-03 8.9702416e-03 2.0253115e-03 8.1348350e-02 5.3552444e-02 4.4248656e-02 5.2648527e-02 6.1923652e-02 6.1838703e-02 6.0414517e-02 5.3985372e-02 6.6201925e-02 3.4742321e-02 1.4883289e-02 4.3767758e-02 3.4228373e-02 6.6672589e-02 6.6939218e-02 3.0219175e-02 3.6020463e-02 3.8455365e-02 9.7619080e-02 5.3827294e-02 3.6463335e-02 4.6373140e-02 6.8906148e-02 2.6398982e-02 3.4032020e-02 3.3159361e-02 2.0120628e-02 1.8469842e-02 6.0950543e-02 2.8524017e-02 4.8737718e-02 1.9976319e-02 6.4489083e-02 2.5275791e-02 6.5080438e-02 4.4969016e-02 4.4191042e-02 3.6066964e-02 1.6430448e-02 2.4524789e-02 4.4743887e-02 2.2062365e-02 5.3552444e-02 4.9370398e-02 4.2403953e-02 3.0850071e-02 4.3434971e-02 2.5846885e-02 3.3807321e-02 3.4832107e-02 1.2988107e-03 1.6703175e-03 1.1675843e-03 2.1414615e-03 3.6300242e-03 1.1143098e-02 2.7878082e-03 7.4699291e-03 1.6417856e-03 1.3833381e-02 5.5119833e-05 6.7870415e-03 2.7321459e-03 1.1691818e-02 4.7830259e-03 1.0265050e-03 1.5056614e-03 1.2372212e-03 2.4522309e-03 1.1336677e-03 8.0050923e-03 5.6870746e-05 4.6915622e-04 1.5562032e-03 2.0887194e-02 1.3125417e-02 6.8837382e-03 7.1162950e-05 5.9522504e-03 5.2043728e-03 5.3649750e-04 8.3200456e-03 2.2696632e-03 1.4322584e-04 2.2876100e-03 1.6070427e-03 5.8684470e-03 2.4558869e-03 5.5570590e-04 1.9651663e-02 8.9172519e-04 5.4659464e-02 3.1114613e-02 2.3119551e-02 3.1736271e-02 3.7415588e-02 3.6761852e-02 3.8397207e-02 3.1313890e-02 3.9883303e-02 1.7150840e-02 4.2007421e-03 2.2713095e-02 1.5842502e-02 4.0120080e-02 4.0057089e-02 1.3428356e-02 1.8845203e-02 2.2251079e-02 6.4673609e-02 3.0152373e-02 1.7530451e-02 2.5859899e-02 4.2056336e-02 1.0552527e-02 1.7518114e-02 1.6789150e-02 6.7575680e-03 6.8601014e-03 3.6016991e-02 1.2994930e-02 2.6303908e-02 8.9623653e-03 3.8482980e-02 1.1333705e-02 4.2542187e-02 2.3287141e-02 2.5337550e-02 1.9991634e-02 5.7451264e-03 9.3841364e-03 2.3239540e-02 8.8973019e-03 3.1114613e-02 2.7595638e-02 2.1905943e-02 1.3658827e-02 2.2169613e-02 1.0410758e-02 1.8207584e-02 1.9484294e-02 4.8008453e-03 1.2650854e-03 6.4006179e-03 1.8900850e-03 1.2501460e-02 5.1363105e-03 3.0955001e-03 2.4385845e-04 2.0020418e-02 1.0205842e-03 3.3467158e-03 5.5033857e-03 1.3788482e-02 2.4101593e-03 2.9461849e-03 4.0056193e-03 3.8916824e-03 4.1559722e-03 7.4799809e-04 1.0679921e-02 1.8732675e-03 2.1213449e-03 3.0477499e-03 1.9047032e-02 6.9834653e-03 2.9438547e-03 1.3758132e-03 1.0992441e-02 2.0426183e-03 1.8183256e-03 5.1275661e-03 3.4870074e-04 1.7800086e-03 4.1941671e-03 4.4278656e-04 2.0594764e-03 2.9165015e-04 1.2452133e-03 2.3762069e-02 2.9165015e-04 5.0966362e-02 3.0166832e-02 2.4694798e-02 2.8788397e-02 3.6671707e-02 3.7274280e-02 3.4472569e-02 3.0604199e-02 4.1125938e-02 1.6692997e-02 4.7977197e-03 2.4489085e-02 1.8077937e-02 4.1761735e-02 4.2713992e-02 1.4455884e-02 1.7039782e-02 1.8322490e-02 6.7670508e-02 3.2041093e-02 1.9169462e-02 2.4724400e-02 4.3157837e-02 1.3510623e-02 1.5640518e-02 1.5102883e-02 8.9767687e-03 6.0642978e-03 3.6671707e-02 1.2419694e-02 2.8323093e-02 6.4935421e-03 3.9910361e-02 1.0006286e-02 3.7967777e-02 2.8141888e-02 2.2594035e-02 1.6734859e-02 4.8977754e-03 1.2095984e-02 2.5839912e-02 1.5166518e-02 3.0166832e-02 2.7378800e-02 2.3072794e-02 1.8885101e-02 2.6783663e-02 1.1941964e-02 1.5226991e-02 1.5844953e-02 5.5898027e-03 3.6211515e-04 4.6884489e-03 1.9469863e-02 6.9494812e-03 1.0856564e-02 6.2070186e-03 5.9174289e-03 2.3319364e-03 8.6215938e-03 6.4261295e-03 5.9157019e-03 6.0481987e-03 4.2268867e-03 4.5610911e-03 4.8970997e-05 6.8799458e-04 2.4420024e-03 1.4255275e-02 1.5079078e-03 3.2366850e-03 5.6006336e-03 1.5957034e-02 1.6871464e-02 1.4787237e-02 2.2762765e-03 1.3333689e-03 1.2378081e-02 7.3736127e-04 8.8932355e-03 5.1508306e-03 2.2779875e-03 6.5293055e-03 3.6737889e-03 1.2985539e-02 7.3417742e-03 4.0613774e-03 2.7837547e-02 4.7997557e-03 4.5639701e-02 2.3278576e-02 1.4877176e-02 2.5663407e-02 2.8314352e-02 2.6769516e-02 3.2135703e-02 2.3238033e-02 2.8854470e-02 1.1827077e-02 2.2644401e-03 1.4421001e-02 8.8429015e-03 2.8776903e-02 2.8063687e-02 7.8778602e-03 1.4365876e-02 1.9273282e-02 4.9067035e-02 2.0328263e-02 1.0496416e-02 1.9196637e-02 3.0789735e-02 4.6457607e-03 1.3443289e-02 1.2708604e-02 2.5804695e-03 5.0424172e-03 2.6041403e-02 8.8829912e-03 1.7122028e-02 8.2367466e-03 2.7547637e-02 8.4529986e-03 3.6274794e-02 1.3225786e-02 2.0335647e-02 1.6784857e-02 4.4669319e-03 3.9860599e-03 1.4316384e-02 2.9523808e-03 2.3278576e-02 1.9803941e-02 1.4205949e-02 5.9492702e-03 1.2454271e-02 5.3895801e-03 1.5143895e-02 1.6858271e-02 5.9387347e-03 5.9571758e-03 6.0526515e-03 1.4146691e-03 8.1320415e-03 6.3420687e-04 2.2943426e-02 7.2217865e-04 8.7156753e-03 1.7123420e-03 1.9617600e-02 7.0385795e-03 6.4044399e-04 1.1390332e-03 4.8019256e-03 6.7384180e-03 2.9679315e-03 4.6026472e-03 1.3268878e-03 5.4152853e-04 4.6516773e-04 2.8772311e-02 1.3783757e-02 3.2459159e-03 7.3821107e-04 1.2234521e-02 2.0992860e-03 3.0774015e-03 1.1372571e-02 2.9352447e-03 8.1022588e-04 9.2842537e-04 2.8855444e-03 2.9418595e-03 1.2977062e-03 1.2869337e-04 1.4124612e-02 3.4246729e-04 6.6626445e-02 4.1505551e-02 3.3416051e-02 4.0874061e-02 4.8928444e-02 4.8860648e-02 4.7883828e-02 4.1881126e-02 5.2796241e-02 2.5141882e-02 8.8605790e-03 3.3017625e-02 2.4870522e-02 5.3247026e-02 5.3590923e-02 2.1349209e-02 2.6308756e-02 2.8721666e-02 8.1439460e-02 4.1842449e-02 2.6703197e-02 3.5208876e-02 5.5212555e-02 1.8433774e-02 2.4622301e-02 2.3863121e-02 1.3181398e-02 1.1675955e-02 4.8074316e-02 1.9890418e-02 3.7380285e-02 1.3082557e-02 5.1276398e-02 1.7233265e-02 5.2150081e-02 3.4634218e-02 3.3454504e-02 2.6550201e-02 1.0070918e-02 1.6842626e-02 3.3968952e-02 1.6166125e-02 4.1505551e-02 3.7811539e-02 3.1780349e-02 2.2766214e-02 3.3244526e-02 1.7747224e-02 2.4583452e-02 2.5577750e-02 7.4058632e-03 1.7632325e-02 5.8672157e-03 1.4554963e-02 7.5139440e-03 6.3063306e-03 2.8227254e-03 1.2216172e-02 5.2171506e-03 8.3411571e-03 9.1198919e-03 3.7613430e-03 3.7527482e-03 5.6979631e-04 2.0412760e-03 4.1677011e-03 1.2238157e-02 1.7043766e-03 3.0984992e-03 5.1374687e-03 2.0730806e-02 2.1628161e-02 1.6669142e-02 2.5627983e-03 1.3966031e-03 1.3985502e-02 1.7150229e-03 1.2743810e-02 7.3773848e-03 2.3644296e-03 5.7186323e-03 5.6669191e-03 1.5046711e-02 9.0550035e-03 4.3221940e-03 2.4177800e-02 5.7926331e-03 5.2977640e-02 2.8412846e-02 1.8464622e-02 3.1548301e-02 3.3726407e-02 3.1637531e-02 3.8729422e-02 2.8292534e-02 3.3574657e-02 1.5809867e-02 4.4178873e-03 1.7890807e-02 1.1580028e-02 3.3334020e-02 3.2156672e-02 1.0943899e-02 1.8967943e-02 2.4791881e-02 5.4103864e-02 2.4225163e-02 1.3643641e-02 2.4071431e-02 3.5681100e-02 6.5373199e-03 1.7959936e-02 1.7099713e-02 4.3929724e-03 8.0887873e-03 3.0809355e-02 1.2543658e-02 2.0721585e-02 1.2037199e-02 3.2114506e-02 1.2207760e-02 4.3300211e-02 1.5218252e-02 2.5733556e-02 2.1932604e-02 7.3680112e-03 5.8739609e-03 1.7443010e-02 2.6299180e-03 2.8412846e-02 2.4437667e-02 1.7887656e-02 6.9159049e-03 1.4494165e-02 7.9434468e-03 2.0060731e-02 2.2068579e-02 2.3875094e-02 1.2000415e-02 1.3948342e-03 3.4546351e-03 1.4939730e-02 3.7950422e-03 6.0175173e-04 1.2211196e-02 7.0052335e-03 9.1256063e-05 8.0336825e-03 9.5484059e-03 3.8945506e-03 2.3206010e-03 7.0732153e-04 2.0727990e-02 4.4889370e-03 6.3258821e-03 8.7927798e-03 9.0069682e-03 3.7952179e-03 7.8092546e-03 4.4335690e-03 9.0550736e-03 6.7946167e-03 2.1531805e-03 9.6426638e-04 7.0770423e-04 5.1150602e-03 1.0670446e-02 5.5187206e-04 6.0114634e-03 3.0771240e-03 5.3655604e-03 3.8039285e-02 3.5100982e-03 3.3392676e-02 1.7228333e-02 1.4023464e-02 1.5984642e-02 2.2267037e-02 2.3075907e-02 2.0330300e-02 1.7609739e-02 2.6354220e-02 7.6657981e-03 1.4938436e-03 1.3975046e-02 9.8087979e-03 2.7013992e-02 2.8227679e-02 6.7815824e-03 7.6426994e-03 8.5739542e-03 4.8734857e-02 1.9601188e-02 1.0188446e-02 1.3147209e-02 2.7911001e-02 7.5145131e-03 6.7053137e-03 6.3732135e-03 4.4764490e-03 1.3642012e-03 2.2654181e-02 4.8866188e-03 1.6888522e-02 1.3930577e-03 2.5469441e-02 3.2860894e-03 2.3095694e-02 1.8694362e-02 1.1451986e-02 7.4272413e-03 8.6465452e-04 6.5073035e-03 1.5400191e-02 1.2824348e-02 1.7228333e-02 1.5313622e-02 1.2672619e-02 1.3006211e-02 1.7530867e-02 5.5400420e-03 6.4187909e-03 6.8900377e-03 3.1968437e-03 2.6175875e-02 9.5689725e-03 4.4766639e-02 9.9845360e-03 2.8418681e-02 3.6870693e-03 4.5253718e-02 2.5815239e-02 5.6468536e-03 5.2271680e-03 1.8578768e-02 2.3906603e-02 1.7414675e-02 6.6309054e-04 1.0701959e-02 7.1563943e-03 4.3943049e-03 6.0754711e-02 3.4699507e-02 9.6990814e-03 9.4374842e-03 2.8881695e-02 8.6238198e-03 1.6549411e-02 3.3492594e-02 1.6963829e-02 8.9575119e-03 3.4839312e-03 1.7214511e-02 1.0985852e-02 1.1115876e-02 7.0774378e-03 2.3223923e-03 9.1191389e-03 1.1169736e-01 7.8250518e-02 6.5808362e-02 7.7763348e-02 8.8114611e-02 8.7449743e-02 8.7215232e-02 7.8675005e-02 9.2203874e-02 5.5132383e-02 2.8794481e-02 6.5100824e-02 5.3025891e-02 9.2531519e-02 9.2220151e-02 4.8731252e-02 5.7186075e-02 6.0580024e-02 1.2745253e-01 7.7174441e-02 5.6198920e-02 6.9678402e-02 9.5449745e-02 4.2538093e-02 5.4722713e-02 5.3576276e-02 3.4860888e-02 3.4291475e-02 8.6329226e-02 4.7343142e-02 7.0978985e-02 3.6695928e-02 9.0088005e-02 4.3430772e-02 9.2828505e-02 6.4239353e-02 6.7481588e-02 5.7536867e-02 3.1542812e-02 4.0297150e-02 6.5753742e-02 3.3241661e-02 7.8250518e-02 7.2933055e-02 6.3794057e-02 4.6178781e-02 6.2571051e-02 4.2843100e-02 5.4666081e-02 5.6027673e-02 1.6139749e-02 3.9412782e-03 2.4265145e-02 2.3891707e-03 1.6498373e-02 4.1418612e-05 2.4988702e-02 1.3783039e-02 4.3146851e-04 2.5122323e-04 6.4815288e-03 1.0052630e-02 7.0587637e-03 1.3669961e-03 2.4020339e-03 9.7331181e-04 2.7171014e-04 3.8783427e-02 2.3881244e-02 7.7285447e-03 2.0010737e-03 1.2962382e-02 6.0316172e-03 5.6775409e-03 1.9642810e-02 8.0347010e-03 1.6772041e-03 5.1201506e-05 7.5139419e-03 7.7685816e-03 5.4104161e-03 1.3577183e-03 7.6646931e-03 3.0214900e-03 8.1621829e-02 5.2182884e-02 4.1040324e-02 5.3055858e-02 6.0133087e-02 5.8980137e-02 6.1457708e-02 5.2397706e-02 6.2608146e-02 3.3589093e-02 1.3750660e-02 4.0409320e-02 3.0837807e-02 6.2732754e-02 6.2152008e-02 2.7986481e-02 3.5988306e-02 4.0227814e-02 9.1799153e-02 5.0051858e-02 3.3478326e-02 4.5393111e-02 6.5357720e-02 2.2750138e-02 3.4139497e-02 3.3132950e-02 1.7367166e-02 1.8328224e-02 5.7997026e-02 2.7720666e-02 4.5013892e-02 2.1236242e-02 6.0787869e-02 2.5266646e-02 6.6573172e-02 3.9086224e-02 4.4693577e-02 3.7322864e-02 1.6444309e-02 2.1159286e-02 4.0674593e-02 1.6113197e-02 5.2182884e-02 4.7503868e-02 3.9598661e-02 2.5231075e-02 3.7795470e-02 2.3382629e-02 3.4903444e-02 3.6481922e-02 4.3266011e-03 2.5120283e-02 7.2104610e-03 3.6362605e-04 1.6818393e-02 1.3224351e-02 8.9751703e-04 1.2039448e-02 1.4078561e-02 9.5477546e-03 7.2983976e-03 3.1999143e-03 2.4639885e-02 8.8187561e-03 1.0207680e-02 1.2236717e-02 1.1661588e-02 8.0001293e-04 5.7297070e-03 8.1532898e-03 1.7517743e-02 5.5505771e-03 6.3202290e-03 1.2261175e-03 1.5275601e-03 9.1326114e-03 1.4417917e-02 2.2056571e-03 4.2297788e-03 3.2218738e-03 8.2583968e-03 4.2679879e-02 5.2144338e-03 3.5325690e-02 2.1286357e-02 2.0340751e-02 1.7932964e-02 2.6773752e-02 2.8858059e-02 2.1473619e-02 2.1925037e-02 3.2987864e-02 1.1596147e-02 5.5679058e-03 2.0475433e-02 1.6418368e-02 3.4058657e-02 3.6285268e-02 1.2036020e-02 1.0227865e-02 8.9974606e-03 5.7847652e-02 2.6531721e-02 1.6295053e-02 1.6788576e-02 3.4473710e-02 1.4498141e-02 9.1389529e-03 8.9727022e-03 1.0441422e-02 4.2212624e-03 2.8555402e-02 8.4473824e-03 2.3791713e-02 2.5698702e-03 3.2242281e-02 5.9266749e-03 2.3676462e-02 2.8216759e-02 1.3343464e-02 8.4809495e-03 3.5554866e-03 1.3166972e-02 2.2745344e-02 2.2629651e-02 2.1286357e-02 1.9992085e-02 1.8520730e-02 2.2324446e-02 2.6771848e-02 1.1107369e-02 7.6788410e-03 7.4852977e-03 2.3512869e-02 1.1702196e-03 5.0486445e-03 4.4067902e-03 1.7493736e-02 4.0658633e-03 2.3529372e-03 3.3047066e-03 5.2094902e-03 6.0430183e-03 1.7805903e-03 8.3604250e-03 2.1645857e-03 1.8214590e-03 2.1737913e-03 2.3530298e-02 8.5714286e-03 1.8444368e-03 1.4565864e-03 1.3243081e-02 1.0630397e-03 2.8808992e-03 7.3652636e-03 1.0835317e-03 1.7933322e-03 3.0961279e-03 1.3436220e-03 1.3043704e-03 1.2482375e-04 8.8034137e-04 2.0043242e-02 1.2482375e-04 5.7954445e-02 3.5775683e-02 2.9742194e-02 3.4149795e-02 4.2824229e-02 4.3477315e-02 4.0214177e-02 3.6256651e-02 4.7605297e-02 2.0950874e-02 7.1042975e-03 2.9499916e-02 2.2329817e-02 4.8271691e-02 4.9230028e-02 1.8375611e-02 2.1296157e-02 2.2492082e-02 7.5802989e-02 3.7751582e-02 2.3617894e-02 2.9827991e-02 4.9792717e-02 1.7017775e-02 1.9724142e-02 1.9131907e-02 1.1873908e-02 8.7349825e-03 4.2824229e-02 1.6130090e-02 3.3687060e-02 9.1410950e-03 4.6289887e-02 1.3350026e-02 4.3904542e-02 3.3071376e-02 2.7381594e-02 2.0813943e-02 7.3233676e-03 1.5432666e-02 3.0904955e-02 1.7682128e-02 3.5775683e-02 3.2747980e-02 2.7982984e-02 2.2571206e-02 3.1620085e-02 1.5454693e-02 1.9154732e-02 1.9759156e-02 1.5625666e-02 2.0014241e-02 2.2906319e-02 3.3689213e-03 1.6561417e-02 1.9555072e-02 1.9724522e-02 6.8229205e-03 6.0996359e-03 1.3507575e-02 3.5744409e-02 1.3348521e-02 1.7651623e-02 2.2506296e-02 1.4148535e-02 3.1413076e-02 3.8119216e-02 1.5501824e-02 1.7783197e-03 3.4445966e-02 9.9745771e-03 1.7822684e-02 1.8993068e-02 1.5436010e-02 2.3911299e-02 1.6070505e-02 3.4793519e-02 2.5109394e-02 1.9730191e-02 5.3384757e-02 2.0981999e-02 3.5016323e-02 1.5808551e-02 7.2216559e-03 2.1250521e-02 1.8323057e-02 1.5289849e-02 2.7077232e-02 1.5342317e-02 1.5450153e-02 9.5719689e-03 7.0769801e-03 6.7142336e-03 3.8618160e-03 1.4833874e-02 1.3052654e-02 5.5299114e-03 1.3477582e-02 2.0871841e-02 2.6745939e-02 9.5407500e-03 5.3018257e-03 1.4058965e-02 1.6863810e-02 2.0380823e-03 1.3353665e-02 1.2655783e-02 3.2775616e-03 9.7625272e-03 1.4632542e-02 9.0437643e-03 7.6711708e-03 1.4740530e-02 1.4381411e-02 1.0907741e-02 3.1026769e-02 2.6758954e-03 1.8148668e-02 1.8095332e-02 1.0256426e-02 2.3444102e-03 5.5327937e-03 1.0086149e-03 1.5808551e-02 1.2612237e-02 7.5482105e-03 2.3261323e-04 2.5482034e-03 4.2287654e-03 1.6814254e-02 1.9242327e-02 6.8098804e-03 2.4185222e-03 1.3095995e-02 4.9100577e-03 8.0319599e-04 1.3085229e-03 1.8113887e-03 3.1306373e-03 1.2541646e-03 7.2250359e-03 1.5182032e-04 3.2471009e-04 1.1793141e-03 2.2134336e-02 1.2798604e-02 5.8036245e-03 2.9068060e-05 7.1481800e-03 4.2574873e-03 8.5847073e-04 8.5715637e-03 2.1039195e-03 1.1389023e-04 1.8746094e-03 1.5895426e-03 4.9316123e-03 1.9257560e-03 2.7708506e-04 1.8515651e-02 5.3986259e-04 5.6637702e-02 3.2886175e-02 2.4967826e-02 3.3187187e-02 3.9414817e-02 3.8923256e-02 3.9880016e-02 3.3128964e-02 4.2231952e-02 1.8470852e-02 4.9097912e-03 2.4570551e-02 1.7467535e-02 4.2525770e-02 4.2580968e-02 1.4790908e-02 2.0011146e-02 2.3142926e-02 6.7845285e-02 3.2285576e-02 1.9161137e-02 2.7420216e-02 4.4447416e-02 1.1980855e-02 1.8611023e-02 1.7883360e-02 7.8729229e-03 7.5466400e-03 3.8175619e-02 1.4096206e-02 2.8321778e-02 9.4496873e-03 4.0815141e-02 1.2211823e-02 4.4024579e-02 2.5465922e-02 2.6596134e-02 2.0925904e-02 6.3340227e-03 1.0720439e-02 2.5211490e-02 1.0333158e-02 3.2886175e-02 2.9360405e-02 2.3656085e-02 1.5413665e-02 2.4285913e-02 1.1678199e-02 1.9116491e-02 2.0313205e-02 1.6950975e-02 9.2576768e-03 2.2830227e-04 1.1996475e-02 1.3943618e-02 7.5471408e-03 5.0950912e-03 2.4447813e-03 2.5903324e-02 8.0139448e-03 9.9879335e-03 1.2585436e-02 8.2754975e-03 1.4174352e-03 8.0478199e-03 7.7042954e-03 1.3901912e-02 7.5078674e-03 4.9957099e-03 2.9997761e-04 1.5395926e-03 8.6362248e-03 1.4833108e-02 1.8222710e-03 6.1957159e-03 4.1238914e-03 8.4102568e-03 4.4734160e-02 5.6076899e-03 3.0288189e-02 1.6597244e-02 1.5336169e-02 1.4117002e-02 2.1529085e-02 2.3159309e-02 1.7633168e-02 1.7119251e-02 2.6802924e-02 7.9632454e-03 3.3472740e-03 1.5438450e-02 1.1914773e-02 2.7716384e-02 2.9607261e-02 8.2241873e-03 7.0346123e-03 6.5727745e-03 4.9604200e-02 2.0828511e-02 1.1808642e-02 1.2615158e-02 2.8194719e-02 1.0429441e-02 6.1245969e-03 5.9452412e-03 7.1618546e-03 2.1261487e-03 2.2855826e-02 5.3452154e-03 1.8357871e-02 1.0397070e-03 2.6087088e-02 3.3990115e-03 1.9871685e-02 2.2251351e-02 9.9773797e-03 5.9192173e-03 1.6976055e-03 9.3317377e-03 1.7391404e-02 1.8375579e-02 1.6597244e-02 1.5280892e-02 1.3772451e-02 1.7356806e-02 2.0968489e-02 7.4978418e-03 5.1634523e-03 5.1963190e-03 2.4233623e-02 1.4077025e-02 4.3787020e-04 1.8512542e-04 6.0477386e-03 9.6703484e-03 7.1488601e-03 1.5477068e-03 2.2742281e-03 9.7171923e-04 3.9271863e-04 3.8509554e-02 2.4774335e-02 8.7360281e-03 1.9891111e-03 1.1995846e-02 6.8973897e-03 5.5019459e-03 1.9965157e-02 8.4152844e-03 1.6247783e-03 1.4531219e-04 7.7480972e-03 8.6972032e-03 5.9824611e-03 1.5176827e-03 7.8567259e-03 3.3493021e-03 8.1204385e-02 5.1575806e-02 4.0135980e-02 5.2803481e-02 5.9400299e-02 5.8042463e-02 6.1291964e-02 5.1745941e-02 6.1508632e-02 3.3138226e-02 1.3469654e-02 3.9479896e-02 2.9967140e-02 6.1565597e-02 6.0825097e-02 2.7361698e-02 3.5748038e-02 4.0333571e-02 9.0138895e-02 4.8984196e-02 3.2667108e-02 4.4902460e-02 6.4253355e-02 2.1894327e-02 3.3940189e-02 3.2913172e-02 1.6731486e-02 1.8202458e-02 5.7045856e-02 2.7382191e-02 4.3985602e-02 2.1375556e-02 5.9676750e-02 2.5108522e-02 6.6474949e-02 3.7689168e-02 4.4508235e-02 3.7339453e-02 1.6368548e-02 2.0371230e-02 3.9606943e-02 1.4908799e-02 5.1575806e-02 4.6824025e-02 3.8781771e-02 2.3989539e-02 3.6452117e-02 2.2748990e-02 3.4902979e-02 3.6586492e-02 7.5459794e-03 1.9125850e-02 2.0310725e-02 6.1222293e-03 3.4447226e-03 8.1418452e-03 3.7867035e-02 1.2077489e-02 1.6567789e-02 2.1605044e-02 3.9038067e-03 1.6430417e-02 2.8689503e-02 1.3569150e-02 4.2181405e-03 2.6167927e-02 7.2610935e-03 7.0630535e-03 1.1392972e-02 1.4088158e-02 2.3849569e-02 9.5566395e-03 2.5308356e-02 1.7804542e-02 1.7150362e-02 5.8921861e-02 1.6182419e-02 2.0027139e-02 6.1819655e-03 2.0410396e-03 8.6030014e-03 8.6357480e-03 7.5787415e-03 1.2598904e-02 6.0656768e-03 8.7139108e-03 1.7031846e-03 2.3331644e-03 1.8821103e-03 3.1682605e-04 8.7160052e-03 8.5600328e-03 2.8826583e-04 3.4529372e-03 7.5176091e-03 2.1668627e-02 4.3875084e-03 6.5654007e-04 4.4940457e-03 9.7914071e-03 1.9831433e-04 3.3391345e-03 2.9870521e-03 6.9985633e-04 2.5162956e-03 7.1853248e-03 1.3860021e-03 2.9754449e-03 4.8339512e-03 8.0121089e-03 2.3142577e-03 1.5373296e-02 2.8512223e-03 6.2399212e-03 5.8796015e-03 3.1092372e-03 2.3415322e-04 1.9852251e-03 5.2321529e-03 6.1819655e-03 4.3274341e-03 1.8113019e-03 1.9110595e-03 2.4033410e-03 9.3130265e-05 5.1498910e-03 6.5356041e-03 9.5659910e-03 1.1252405e-02 5.1556774e-03 3.2048021e-03 1.2695843e-03 2.2856756e-02 5.7848184e-03 7.7211451e-03 1.0283621e-02 8.3602499e-03 2.7615733e-03 7.9786726e-03 5.6505970e-03 1.0691105e-02 7.1332727e-03 3.1295602e-03 5.2401241e-04 9.5266815e-04 6.4319329e-03 1.2318850e-02 9.4872145e-04 6.1326072e-03 3.4758657e-03 6.5333653e-03 4.0853883e-02 4.3049208e-03 3.1649609e-02 1.6528693e-02 1.4101758e-02 1.4838879e-02 2.1483291e-02 2.2597676e-02 1.8841242e-02 1.6959579e-02 2.5991410e-02 7.4250762e-03 1.9970150e-03 1.4113092e-02 1.0250250e-02 2.6744340e-02 2.8212793e-02 6.9997737e-03 7.0607843e-03 7.4739664e-03 4.8397396e-02 1.9593840e-02 1.0430940e-02 1.2525634e-02 2.7473469e-02 8.3209543e-03 6.1476473e-03 5.8775226e-03 5.2455782e-03 1.4265861e-03 2.2224591e-02 4.7507930e-03 1.7000338e-02 1.0369313e-03 2.5178358e-02 3.0495461e-03 2.1392322e-02 1.9628131e-02 1.0500631e-02 6.5204272e-03 9.7357638e-04 7.2933833e-03 1.5732817e-02 1.4704532e-02 1.6528693e-02 1.4858839e-02 1.2681737e-02 1.4339393e-02 1.8427144e-02 5.9852538e-03 5.6194230e-03 5.9265324e-03 8.2461319e-05 3.7688699e-03 6.4113525e-03 4.0576095e-03 3.3120738e-03 8.1782115e-04 1.0911416e-04 1.0750826e-04 3.1097496e-02 1.8955233e-02 6.6951403e-03 5.7580225e-04 9.5785875e-03 5.0095389e-03 2.9949836e-03 1.4512717e-02 5.0859668e-03 4.0940931e-04 2.6323499e-04 4.5146422e-03 6.3221754e-03 3.5606197e-03 3.7002765e-04 1.1722539e-02 1.5048339e-03 7.0395338e-02 4.3268177e-02 3.3370831e-02 4.4028795e-02 5.0578307e-02 4.9629423e-02 5.1746432e-02 4.3478758e-02 5.3066435e-02 2.6481479e-02 9.3392135e-03 3.2828923e-02 2.4313691e-02 5.3237993e-02 5.2867243e-02 2.1623243e-02 2.8596153e-02 3.2496284e-02 8.0563616e-02 4.1614554e-02 2.6578058e-02 3.7074597e-02 5.5590704e-02 1.7323162e-02 2.6950223e-02 2.6053254e-02 1.2554151e-02 1.3157434e-02 4.8740123e-02 2.1279987e-02 3.7040705e-02 1.5739340e-02 5.1410982e-02 1.9129885e-02 5.6480014e-02 3.2271228e-02 3.6432542e-02 2.9847396e-02 1.1573499e-02 1.5895178e-02 3.3202746e-02 1.2659655e-02 4.3268177e-02 3.9046279e-02 3.2014714e-02 2.0067384e-02 3.1045819e-02 1.7642117e-02 2.7677552e-02 2.9133996e-02 4.1910853e-03 7.2076003e-03 5.1147587e-03 2.7507325e-03 1.1620134e-03 3.3310681e-04 2.1458858e-04 3.3405406e-02 2.1499526e-02 8.0088547e-03 9.8332257e-04 9.6892205e-03 6.1744631e-03 3.6704596e-03 1.6544301e-02 6.4217725e-03 7.2111274e-04 2.1270559e-04 5.7183304e-03 7.6981750e-03 4.7117366e-03 8.0177817e-04 1.0442534e-02 2.2910923e-03 7.3784227e-02 4.5661689e-02 3.5029236e-02 4.6823533e-02 5.3065363e-02 5.1836541e-02 5.4866908e-02 4.5828107e-02 5.5174565e-02 2.8408213e-02 1.0505539e-02 3.4430852e-02 2.5609623e-02 5.5260802e-02 5.4654205e-02 2.3114484e-02 3.0836583e-02 3.5207839e-02 8.2686924e-02 4.3372344e-02 2.8064998e-02 3.9380344e-02 5.7772002e-02 1.8254235e-02 2.9162191e-02 2.8206341e-02 1.3497345e-02 1.4745279e-02 5.0900941e-02 2.3090436e-02 3.8677830e-02 1.7710943e-02 5.3450122e-02 2.1019262e-02 5.9801906e-02 3.3130692e-02 3.9030212e-02 3.2379101e-02 1.3108535e-02 1.6839016e-02 3.4625964e-02 1.2551681e-02 4.5661689e-02 4.1207031e-02 3.3732757e-02 2.0513590e-02 3.1937443e-02 1.8908607e-02 3.0105345e-02 3.1711397e-02 5.5341249e-04 1.8100865e-03 1.3685556e-02 1.1595059e-03 2.7733190e-03 5.0259130e-03 1.5582273e-02 1.5303863e-02 1.3216313e-02 1.8061219e-03 1.8483286e-03 1.0961959e-02 4.0632184e-04 7.9419645e-03 4.2030049e-03 1.8572156e-03 5.9964081e-03 2.8843938e-03 1.1487297e-02 6.2195738e-03 3.4171878e-03 2.7328194e-02 3.9549773e-03 4.5323537e-02 2.3233798e-02 1.5194289e-02 2.5226902e-02 2.8389486e-02 2.7075250e-02 3.1585337e-02 2.3242139e-02 2.9332670e-02 1.1663012e-02 1.9950589e-03 1.4768251e-02 9.1566367e-03 2.9330780e-02 2.8799195e-02 7.9264454e-03 1.3962831e-02 1.8481959e-02 5.0115855e-02 2.0802698e-02 1.0733745e-02 1.9041166e-02 3.1266982e-02 4.9760517e-03 1.3001310e-02 1.2292231e-02 2.6877167e-03 4.5798906e-03 2.6364594e-02 8.6153969e-03 1.7566530e-02 7.4744318e-03 2.8043210e-02 7.9969256e-03 3.5640074e-02 1.4109863e-02 1.9847472e-02 1.6098385e-02 3.9587699e-03 4.2468630e-03 1.4818700e-02 3.7268555e-03 2.3233798e-02 1.9851942e-02 1.4429507e-02 6.7117241e-03 1.3281147e-02 5.4664867e-03 1.4483311e-02 1.6079322e-02 1.4644608e-03 1.8793440e-02 2.6529941e-03 4.9386280e-03 7.8423223e-03 1.0293845e-02 1.1871124e-02 1.4064748e-02 3.3517168e-03 2.2145112e-03 1.1983758e-02 7.1285200e-04 4.9198989e-03 3.5944894e-03 3.6244119e-03 9.2640212e-03 2.3814700e-03 1.1959906e-02 6.6391160e-03 5.2659669e-03 3.4768199e-02 5.0225808e-03 3.6096256e-02 1.6893661e-02 1.0608435e-02 1.8369401e-02 2.1445998e-02 2.0588370e-02 2.3840862e-02 1.6945577e-02 2.2798129e-02 7.2320779e-03 4.5716975e-04 1.0311574e-02 5.8475513e-03 2.2922553e-02 2.2802937e-02 4.5367353e-03 8.9750660e-03 1.2721749e-02 4.2209187e-02 1.5508457e-02 6.9302761e-03 1.3261817e-02 2.4477020e-02 2.9037640e-03 8.2014592e-03 7.6405998e-03 1.0779902e-03 2.0077167e-03 2.0000571e-02 4.8259503e-03 1.2764273e-02 4.2375783e-03 2.1711543e-02 4.3575270e-03 2.7378197e-02 1.1026559e-02 1.3799894e-02 1.0731586e-02 1.6733633e-03 2.2875292e-03 1.0634216e-02 4.2659077e-03 1.6893661e-02 1.4117780e-02 9.8480141e-03 5.3491991e-03 1.0216132e-02 2.7977678e-03 9.4220767e-03 1.0761419e-02 1.4255275e-02 1.6372127e-03 2.8542706e-03 4.7214122e-03 1.3332443e-02 7.0444510e-03 6.5532524e-03 1.6174955e-03 6.7559932e-03 5.2389698e-03 5.3640347e-04 3.3171183e-03 5.1283913e-04 2.0296126e-03 6.0950963e-03 1.2744293e-04 5.0948120e-03 1.9318373e-03 2.3608728e-03 2.9089432e-02 1.4755776e-03 4.1865124e-02 2.2429622e-02 1.7100637e-02 2.2021240e-02 2.8022948e-02 2.8179896e-02 2.7393595e-02 2.2729266e-02 3.1385076e-02 1.0928177e-02 1.7748140e-03 1.6893502e-02 1.1535806e-02 3.1856326e-02 3.2502251e-02 8.7863970e-03 1.1670124e-02 1.3706425e-02 5.4822004e-02 2.3316700e-02 1.2494202e-02 1.7833364e-02 3.3214276e-02 7.9216177e-03 1.0562159e-02 1.0054198e-02 4.5572702e-03 2.8793276e-03 2.7614983e-02 7.5454514e-03 2.0103411e-02 3.8323098e-03 3.0272091e-02 5.9246169e-03 3.0771609e-02 1.9772265e-02 1.6666117e-02 1.2065056e-02 2.1106407e-03 6.8455106e-03 1.7918371e-02 1.0155544e-02 2.2429622e-02 1.9803941e-02 1.5812785e-02 1.2351590e-02 1.8630292e-02 6.7616623e-03 1.0713593e-02 1.1544341e-02 7.3852619e-03 4.6030790e-03 2.5842708e-03 5.4300453e-02 3.3576641e-02 1.0668428e-02 6.6015056e-03 2.1814225e-02 9.0913366e-03 1.2584515e-02 3.0243821e-02 1.4861127e-02 6.0467884e-03 1.7442631e-03 1.4568415e-02 1.1492675e-02 1.0173171e-02 5.0439803e-03 2.6472861e-03 7.4513739e-03 1.0326079e-01 7.0098941e-02 5.7127711e-02 7.0823391e-02 7.9250920e-02 7.7946311e-02 8.0298570e-02 7.0363352e-02 8.2049228e-02 4.8294128e-02 2.3708406e-02 5.6369153e-02 4.4949110e-02 8.2157728e-02 8.1370723e-02 4.1592461e-02 5.0974970e-02 5.5456655e-02 1.1462128e-01 6.7593968e-02 4.8172633e-02 6.2189094e-02 8.5184220e-02 3.4981615e-02 4.8742735e-02 4.7569879e-02 2.8345798e-02 2.9430788e-02 7.6817222e-02 4.1186102e-02 6.1732592e-02 3.2638549e-02 7.9961084e-02 3.8043139e-02 8.5993831e-02 5.4182844e-02 6.1100206e-02 5.2203816e-02 2.6988218e-02 3.3049806e-02 5.6579852e-02 2.5305632e-02 7.0098941e-02 6.4715083e-02 5.5458622e-02 3.7374533e-02 5.2731313e-02 3.5938214e-02 4.9377578e-02 5.1061578e-02 3.5870177e-04 1.3937749e-03 2.2192133e-02 1.4887667e-02 7.8001469e-03 8.8171759e-05 5.5216302e-03 5.9793748e-03 7.0276376e-04 9.5746848e-03 3.0437686e-03 8.2181914e-05 2.0068727e-03 2.2506604e-03 6.8090409e-03 3.1469229e-03 6.3301284e-04 1.8528100e-02 1.2625125e-03 5.6704255e-02 3.2399710e-02 2.3806821e-02 3.3394775e-02 3.8737297e-02 3.7833623e-02 4.0310184e-02 3.2556548e-02 4.0845247e-02 1.8140183e-02 4.6897646e-03 2.3353931e-02 1.6279158e-02 4.1004664e-02 4.0734447e-02 1.4064354e-02 2.0110135e-02 2.3937572e-02 6.5512797e-02 3.0871191e-02 1.8117892e-02 2.7110445e-02 4.3069544e-02 1.0732705e-02 1.8772743e-02 1.7996705e-02 7.0003565e-03 7.6876035e-03 3.7052349e-02 1.3928781e-02 2.6945955e-02 1.0128877e-02 3.9392715e-02 1.2368176e-02 4.4616010e-02 2.3284644e-02 2.6871325e-02 2.1527670e-02 6.5473767e-03 9.5899926e-03 2.3719212e-02 8.2529644e-03 3.2399710e-02 2.8696517e-02 2.2657048e-02 1.3399527e-02 2.2203050e-02 1.0884330e-02 1.9665180e-02 2.1074599e-02 3.3845415e-04 2.7555720e-02 1.6677843e-02 6.3795274e-03 1.8362843e-04 8.3039101e-03 4.7168779e-03 1.9845708e-03 1.2200549e-02 3.8746792e-03 1.0179352e-04 6.8781671e-04 3.2951534e-03 5.8068152e-03 2.8838183e-03 1.7929502e-04 1.4079350e-02 1.0261263e-03 6.5064614e-02 3.9139337e-02 2.9910766e-02 3.9801101e-02 4.6137717e-02 4.5316574e-02 4.7159535e-02 3.9352955e-02 4.8674767e-02 2.3254889e-02 7.4636086e-03 2.9417103e-02 2.1432655e-02 4.8877998e-02 4.8632496e-02 1.8795647e-02 2.5200016e-02 2.8886320e-02 7.5371106e-02 3.7782169e-02 2.3498806e-02 3.3236011e-02 5.1084684e-02 1.4998262e-02 2.3652548e-02 2.2813769e-02 1.0515841e-02 1.0877794e-02 4.4476659e-02 1.8379623e-02 3.3440220e-02 1.3262540e-02 4.7104689e-02 1.6362923e-02 5.1690239e-02 2.9302173e-02 3.2587374e-02 2.6378456e-02 9.4404077e-03 1.3645256e-02 2.9862026e-02 1.1376615e-02 3.9139337e-02 3.5158865e-02 2.8588131e-02 1.7920264e-02 2.8103943e-02 1.5132216e-02 2.4337714e-02 2.5719532e-02 3.3433714e-02 1.9092201e-02 5.6820829e-03 9.6311300e-04 1.1687721e-02 4.1741711e-03 3.9152052e-03 1.5429746e-02 5.3660274e-03 8.0766059e-04 9.2937667e-05 4.9943675e-03 5.5161799e-03 3.3131521e-03 4.2533013e-04 1.0435178e-02 1.4826896e-03 7.3827823e-02 4.6346021e-02 3.6506307e-02 4.6683572e-02 5.3981470e-02 5.3241058e-02 5.4467293e-02 4.6614647e-02 5.6933591e-02 2.8894083e-02 1.0839802e-02 3.5973376e-02 2.7110780e-02 5.7179243e-02 5.6957917e-02 2.4065468e-02 3.0813759e-02 3.4360438e-02 8.5582303e-02 4.5156230e-02 2.9396418e-02 3.9850269e-02 5.9522160e-02 1.9818286e-02 2.9063291e-02 2.8164144e-02 1.4610346e-02 1.4656069e-02 5.2344759e-02 2.3377306e-02 4.0412481e-02 1.6984268e-02 5.5250411e-02 2.0918953e-02 5.9213325e-02 3.5814189e-02 3.8804285e-02 3.1759139e-02 1.2931018e-02 1.8261938e-02 3.6495356e-02 1.5089913e-02 4.6346021e-02 4.2098942e-02 3.5016667e-02 2.2986158e-02 3.4503485e-02 1.9934983e-02 2.9546649e-02 3.0900707e-02 1.1335824e-02 3.2301226e-02 2.3277392e-02 1.5629192e-02 3.0795664e-02 1.5041716e-02 5.4315810e-03 1.4713782e-02 2.4451968e-02 3.6761202e-02 1.3762814e-02 2.8473446e-02 2.2472023e-02 2.6736705e-02 8.0230549e-02 2.3341323e-02 8.1067950e-03 1.4485807e-03 2.4486060e-03 1.1703837e-03 3.1310445e-03 3.9732842e-03 2.8350922e-03 1.6242765e-03 5.7101906e-03 4.5289508e-04 6.6077306e-03 2.6813364e-03 3.3571693e-03 6.2840633e-03 7.7260784e-03 2.0715363e-03 5.6385417e-05 8.9610646e-04 1.8073537e-02 3.9621048e-03 2.3604444e-03 4.7560860e-04 6.2480089e-03 5.8573026e-03 1.7069942e-04 2.3254450e-04 6.3902073e-03 3.8624324e-03 3.9083302e-03 1.0347438e-03 3.4531305e-03 3.5564063e-03 5.5040957e-03 1.4887365e-03 4.1720167e-03 8.6822743e-03 2.8448886e-04 5.1458480e-04 4.8536300e-03 5.8397245e-03 3.9827712e-03 1.8058791e-02 1.4485807e-03 1.2408841e-03 1.8448387e-03 1.0796845e-02 8.0405443e-03 3.4808504e-03 5.2754145e-04 8.8520211e-04 8.8923137e-03 1.4043268e-02 2.4126814e-02 9.1698320e-03 1.1327807e-02 1.9771649e-03 4.5360079e-03 1.5317882e-02 2.1762150e-02 5.6097757e-03 7.1877025e-03 6.8496039e-03 1.4100359e-02 5.3810596e-02 9.9629280e-03 3.1307769e-02 2.0553715e-02 2.1937453e-02 1.5991855e-02 2.5676129e-02 2.8613140e-02 1.8469501e-02 2.1322428e-02 3.3003652e-02 1.2471005e-02 8.9752587e-03 2.2249180e-02 1.9221338e-02 3.4330234e-02 3.7274155e-02 1.4283960e-02 1.0136722e-02 7.4549090e-03 5.7532344e-02 2.7732649e-02 1.8492242e-02 1.6393364e-02 3.4241314e-02 1.8497141e-02 9.1551303e-03 9.1767161e-03 1.4470512e-02 6.3039218e-03 2.8459911e-02 9.7413243e-03 2.5409718e-02 3.4719577e-03 3.2484401e-02 7.0164005e-03 2.0017467e-02 3.2249701e-02 1.2084843e-02 7.5073944e-03 5.8227226e-03 1.7146634e-02 2.5055287e-02 2.9865216e-02 2.0553715e-02 2.0008085e-02 1.9953689e-02 2.7764157e-02 3.0721044e-02 1.4086953e-02 7.0439235e-03 6.3871700e-03 6.2932024e-03 2.4881477e-02 1.2570656e-04 9.2318283e-03 1.1425023e-02 3.9781027e-03 6.8287051e-03 6.6316807e-03 5.1943433e-03 1.2253382e-04 1.3842125e-03 4.4535183e-03 2.1436401e-02 2.8241776e-03 6.8828353e-02 4.7091014e-02 4.2520568e-02 4.3241334e-02 5.5155458e-02 5.7042053e-02 4.8981701e-02 4.7857882e-02 6.2261278e-02 3.0649818e-02 1.4761608e-02 4.2406580e-02 3.4493935e-02 6.3340671e-02 6.5239814e-02 2.8920518e-02 2.9750953e-02 2.8851937e-02 9.4568410e-02 5.1830706e-02 3.5536784e-02 4.0198717e-02 6.4555077e-02 2.8697450e-02 2.7846993e-02 2.7347316e-02 2.1934395e-02 1.5675611e-02 5.6443004e-02 2.4878970e-02 4.7356921e-02 1.4503825e-02 6.0963940e-02 2.0840976e-02 5.2367097e-02 4.8672170e-02 3.5784762e-02 2.7656172e-02 1.3836981e-02 2.6623769e-02 4.4650658e-02 3.0852069e-02 4.7091014e-02 4.4370840e-02 4.0176310e-02 3.6600204e-02 4.6858384e-02 2.5835018e-02 2.6019603e-02 2.5994222e-02 6.9728196e-03 4.6608676e-03 9.9835054e-04 9.5223148e-03 2.6272609e-03 2.7884021e-05 1.5635547e-03 2.0372846e-03 5.4615556e-03 2.3300808e-03 2.5698617e-04 1.7457304e-02 7.2996870e-04 5.8453388e-02 3.4095678e-02 2.5737672e-02 3.4628989e-02 4.0687367e-02 4.0032201e-02 4.1519056e-02 3.4313166e-02 4.3287054e-02 1.9386832e-02 5.3532424e-03 2.5306754e-02 1.8013458e-02 4.3531307e-02 4.3448361e-02 1.5446633e-02 2.1111383e-02 2.4519102e-02 6.8935827e-02 3.3123525e-02 1.9821803e-02 2.8570801e-02 4.5547846e-02 1.2302601e-02 1.9692146e-02 1.8929129e-02 8.1934144e-03 8.2421848e-03 3.9256624e-02 1.4936345e-02 2.9083818e-02 1.0370393e-02 4.1828239e-02 1.3093356e-02 4.5785106e-02 2.5766235e-02 2.7913838e-02 2.2194996e-02 6.9953757e-03 1.1047300e-02 2.5843766e-02 1.0033677e-02 3.4095678e-02 3.0429687e-02 2.4458901e-02 1.5454278e-02 2.4603652e-02 1.2196519e-02 2.0322394e-02 2.1606954e-02 2.1763592e-02 3.8778874e-03 1.3140898e-02 1.0973585e-02 6.8570288e-03 1.2689294e-02 8.7227464e-03 2.2446768e-02 1.4768924e-02 9.8837253e-03 3.6594036e-02 1.1176832e-02 4.2371132e-02 2.0489598e-02 1.1322791e-02 2.4620431e-02 2.4482623e-02 2.2010230e-02 3.1108475e-02 2.0217050e-02 2.3141445e-02 1.0944826e-02 3.8599335e-03 1.0791452e-02 6.1542383e-03 2.2743482e-02 2.1333545e-02 6.5515291e-03 1.4388718e-02 2.0852825e-02 3.9383251e-02 1.5461941e-02 7.8878514e-03 1.7372104e-02 2.4911352e-02 2.6880230e-03 1.3798611e-02 1.3025794e-02 2.1193973e-03 6.9838956e-03 2.1270749e-02 8.9787629e-03 1.2743052e-02 1.1335890e-02 2.1877049e-02 9.6533919e-03 3.5351983e-02 7.6384883e-03 2.0107777e-02 1.8067074e-02 6.8413076e-03 2.4741061e-03 1.0039063e-02 3.4670280e-04 2.0489598e-02 1.6923143e-02 1.1154236e-02 2.1521967e-03 7.1850256e-03 4.3898059e-03 1.6491142e-02 1.8665860e-02 7.4245740e-03 1.0715228e-02 3.1685550e-03 5.1120383e-03 5.0451240e-03 4.1323270e-03 1.4489604e-04 8.1318290e-04 3.0839979e-03 1.9714350e-02 1.7947024e-03 6.7440625e-02 4.5128820e-02 3.9916292e-02 4.1909560e-02 5.3040678e-02 5.4550431e-02 4.7872699e-02 4.5818357e-02 5.9509133e-02 2.8778013e-02 1.2927960e-02 3.9749997e-02 3.1835574e-02 6.0466685e-02 6.2068316e-02 2.6673332e-02 2.8274822e-02 2.8015780e-02 9.1028220e-02 4.9020613e-02 3.3018114e-02 3.8378554e-02 6.1817956e-02 2.5959804e-02 2.6422336e-02 2.5876047e-02 1.9520884e-02 1.4184191e-02 5.3918305e-02 2.3137863e-02 4.4568283e-02 1.3507580e-02 5.8174666e-02 1.9385371e-02 5.1416653e-02 4.5132833e-02 3.4491897e-02 2.6637939e-02 1.2404699e-02 2.3988175e-02 4.1744067e-02 2.7329966e-02 4.5128820e-02 4.2235431e-02 3.7715182e-02 3.3171215e-02 4.3405180e-02 2.3512609e-02 2.4946638e-02 2.5117607e-02 5.7451096e-03 2.0181202e-03 1.1916495e-03 5.0056293e-03 1.1565469e-03 7.7131020e-03 3.5313465e-03 2.1072743e-03 2.6393551e-02 2.0670282e-03 4.4935473e-02 2.3639481e-02 1.6646699e-02 2.4503230e-02 2.9137719e-02 2.8488385e-02 3.0530237e-02 2.3788614e-02 3.1241308e-02 1.1731452e-02 1.7654569e-03 1.6307697e-02 1.0602623e-02 3.1457386e-02 3.1449229e-02 8.6094127e-03 1.3341698e-02 1.6740022e-02 5.3656632e-02 2.2705161e-02 1.1958802e-02 1.9130319e-02 3.3171817e-02 6.4720106e-03 1.2268562e-02 1.1633623e-02 3.5425115e-03 3.7925712e-03 2.7828397e-02 8.3873180e-03 1.9384549e-02 5.8220699e-03 3.0002594e-02 7.2263237e-03 3.4339797e-02 1.7195852e-02 1.8977924e-02 1.4659749e-02 3.0403500e-03 5.5430802e-03 1.6804744e-02 6.5028482e-03 2.3639481e-02 2.0526700e-02 1.5612876e-02 9.4514606e-03 1.6202837e-02 6.2323724e-03 1.3120120e-02 1.4374340e-02 2.8180986e-03 1.0509318e-02 1.7897176e-02 2.8798358e-03 9.1928702e-03 6.3970688e-03 1.0746267e-02 5.0496414e-02 7.7981540e-03 2.4751970e-02 1.2458649e-02 1.1686269e-02 1.0356316e-02 1.6774912e-02 1.8282513e-02 1.3498932e-02 1.2919728e-02 2.1579044e-02 5.2962835e-03 2.7725666e-03 1.1818152e-02 9.1091800e-03 2.2436834e-02 2.4265521e-02 5.8512682e-03 4.4398598e-03 4.1822455e-03 4.2435194e-02 1.6425553e-02 8.8184300e-03 9.0473617e-03 2.2804959e-02 8.4055012e-03 3.7245140e-03 3.5955501e-03 5.8501155e-03 1.2261597e-03 1.8027694e-02 3.2889908e-03 1.4332649e-02 2.5252408e-04 2.0964539e-02 1.7865385e-03 1.5552416e-02 1.8549588e-02 6.8429145e-03 3.5948443e-03 1.0694978e-03 7.5092190e-03 1.3665818e-02 1.7252830e-02 1.2458649e-02 1.1370103e-02 1.0296741e-02 1.5007456e-02 1.7383467e-02 5.5463863e-03 2.9922345e-03 3.0734531e-03 3.1960387e-03 6.8662451e-03 1.2966654e-04 2.7649067e-03 8.3427366e-04 2.7984284e-03 2.9822521e-02 1.2731780e-03 4.3416537e-02 2.4875338e-02 2.0714079e-02 2.3200351e-02 3.0852531e-02 3.1753284e-02 2.8200300e-02 2.5332686e-02 3.5512031e-02 1.2989773e-02 3.4785945e-03 2.0603808e-02 1.5135483e-02 3.6226615e-02 3.7456354e-02 1.1531484e-02 1.2959687e-02 1.3737739e-02 6.0763799e-02 2.7435536e-02 1.5849420e-02 1.9923216e-02 3.7336849e-02 1.1567919e-02 1.1725372e-02 1.1300051e-02 7.4803077e-03 3.9656480e-03 3.1244978e-02 9.2703065e-03 2.4126524e-02 3.9496417e-03 3.4455719e-02 7.0383738e-03 3.1302098e-02 2.5203523e-02 1.7680030e-02 1.2436482e-02 3.0466203e-03 1.0266971e-02 2.2126952e-02 1.5206492e-02 2.4875338e-02 2.2550174e-02 1.9125072e-02 1.7383181e-02 2.3878119e-02 9.5944653e-03 1.1168635e-02 1.1615557e-02 1.3151667e-03 2.5320521e-03 6.0366399e-03 2.7823080e-03 2.9364031e-04 1.6476106e-02 9.7247557e-04 6.0284770e-02 3.5334723e-02 2.6546579e-02 3.6095524e-02 4.1987790e-02 4.1172424e-02 4.3178382e-02 3.5527420e-02 4.4374359e-02 2.0339225e-02 5.8436205e-03 2.6082735e-02 1.8603485e-02 4.4570054e-02 4.4351810e-02 1.6144347e-02 2.2244142e-02 2.5921932e-02 7.0056396e-02 3.3998717e-02 2.0524111e-02 2.9752453e-02 4.6679420e-02 1.2673386e-02 2.0806267e-02 2.0008665e-02 8.5632030e-03 8.9793060e-03 4.0369526e-02 1.5814708e-02 2.9884891e-02 1.1327921e-02 4.2874486e-02 1.4012365e-02 4.7563178e-02 2.6115270e-02 2.9259206e-02 2.3493076e-02 7.6990791e-03 1.1423166e-02 2.6517929e-02 9.7961479e-03 3.5334723e-02 3.1531604e-02 2.5300172e-02 1.5549538e-02 2.4969819e-02 1.2759840e-02 2.1558690e-02 2.2929217e-02 6.4418336e-03 6.6136731e-03 4.4102106e-03 9.1481615e-04 8.6358065e-03 2.3009102e-03 7.8700864e-02 5.0063075e-02 3.9499858e-02 5.0635713e-02 5.7926279e-02 5.6986449e-02 5.8777354e-02 5.0310505e-02 6.0682610e-02 3.1865387e-02 1.2666075e-02 3.8913195e-02 2.9597893e-02 6.0871779e-02 6.0471723e-02 2.6606279e-02 3.4024273e-02 3.7905442e-02 8.9816272e-02 4.8416482e-02 3.2086335e-02 4.3354864e-02 6.3371751e-02 2.1811352e-02 3.2202223e-02 3.1242353e-02 1.6436605e-02 1.6909958e-02 5.6040246e-02 2.6101775e-02 4.3480802e-02 1.9524603e-02 5.8918015e-02 2.3597831e-02 6.3733101e-02 3.8161980e-02 4.2440703e-02 3.5140814e-02 1.5073633e-02 2.0215594e-02 3.9313992e-02 1.6000311e-02 5.0063075e-02 4.5569203e-02 3.8016354e-02 2.4659247e-02 3.6849168e-02 2.2186423e-02 3.2805330e-02 3.4269626e-02 3.8467105e-03 1.2875815e-03 2.5072619e-03 2.9525973e-02 1.2875815e-03 4.2282453e-02 2.3357390e-02 1.8646096e-02 2.2313442e-02 2.9124686e-02 2.9658732e-02 2.7480923e-02 2.3735668e-02 3.3135090e-02 1.1714292e-02 2.4574323e-03 1.8489812e-02 1.3106245e-02 3.3729170e-02 3.4672500e-02 9.9345371e-03 1.2062833e-02 1.3461486e-02 5.7431716e-02 2.5093254e-02 1.3932087e-02 1.8599958e-02 3.4955869e-02 9.5459229e-03 1.0897419e-02 1.0433767e-02 5.8375168e-03 3.2324242e-03 2.9124686e-02 8.1819777e-03 2.1845036e-02 3.6925062e-03 3.2054971e-02 6.2644754e-03 3.0710474e-02 2.2249402e-02 1.6897068e-02 1.1997033e-02 2.3970471e-03 8.3620725e-03 1.9766550e-02 1.2536577e-02 2.3357390e-02 2.0894448e-02 1.7210222e-02 1.4675128e-02 2.1017625e-02 7.9709601e-03 1.0694253e-02 1.1329979e-02 8.0862109e-04 3.9488260e-03 2.3216413e-02 2.2109950e-03 6.3313725e-02 4.2461361e-02 3.8219621e-02 3.8832114e-02 5.0144792e-02 5.1966249e-02 4.4333772e-02 4.3192281e-02 5.6977170e-02 2.6944997e-02 1.2389873e-02 3.8126276e-02 3.0729621e-02 5.8026007e-02 5.9897200e-02 2.5415278e-02 2.6079828e-02 2.5293603e-02 8.8103550e-02 4.7063061e-02 3.1649890e-02 3.5923563e-02 5.9166899e-02 2.5434205e-02 2.4297609e-02 2.3831192e-02 1.9112973e-02 1.3083158e-02 5.1398726e-02 2.1557413e-02 4.2826183e-02 1.1968109e-02 5.5744441e-02 1.7792619e-02 4.7600638e-02 4.4377594e-02 3.1768113e-02 2.4143930e-02 1.1416637e-02 2.3484839e-02 4.0313562e-02 2.8205265e-02 4.2461361e-02 3.9889484e-02 3.5980112e-02 3.3161057e-02 4.2633793e-02 2.2600989e-02 2.2605649e-02 2.2606045e-02 1.6651142e-03 2.2593118e-02 4.7494657e-04 5.5766903e-02 3.4735344e-02 2.9594132e-02 3.2557240e-02 4.1723024e-02 4.2746173e-02 3.8238608e-02 3.5278801e-02 4.7033204e-02 2.0363061e-02 7.2717611e-03 2.9420951e-02 2.2560431e-02 4.7813714e-02 4.9078515e-02 1.8314288e-02 2.0299319e-02 2.0869007e-02 7.5386883e-02 3.7549246e-02 2.3623644e-02 2.8844458e-02 4.9142563e-02 1.7633849e-02 1.8744071e-02 1.8222205e-02 1.2402012e-02 8.3988430e-03 4.2147698e-02 1.5629140e-02 3.3602383e-02 8.3018288e-03 4.5796447e-02 1.2702505e-02 4.1687026e-02 3.3943329e-02 2.5980350e-02 1.9415550e-02 7.0260502e-03 1.6012157e-02 3.1063717e-02 1.9520820e-02 3.4735344e-02 3.1983672e-02 2.7741891e-02 2.3833980e-02 3.2440091e-02 1.5637054e-02 1.7874227e-02 1.8281297e-02 1.4972117e-02 3.8328039e-04 6.3723448e-02 3.8719440e-02 3.0384566e-02 3.8629777e-02 4.5827446e-02 4.5472032e-02 4.5653244e-02 3.9023449e-02 4.9116846e-02 2.2930601e-02 7.4083466e-03 2.9962579e-02 2.2100137e-02 4.9469036e-02 4.9598054e-02 1.8997460e-02 2.4379661e-02 2.7279745e-02 7.6566902e-02 3.8413283e-02 2.3958684e-02 3.2715136e-02 5.1484761e-02 1.5886249e-02 2.2794916e-02 2.2023153e-02 1.1094162e-02 1.0331639e-02 4.4681292e-02 1.7978253e-02 3.4097516e-02 1.2099271e-02 4.7608429e-02 1.5662138e-02 4.9957613e-02 3.0977854e-02 3.1455087e-02 2.5013254e-02 8.8604244e-03 1.4432007e-02 3.0714741e-02 1.3426163e-02 3.8719440e-02 3.4992868e-02 2.8902863e-02 1.9656006e-02 2.9684992e-02 1.5494562e-02 2.3064694e-02 2.4203450e-02 1.8836956e-02 1.3755831e-01 9.8685385e-02 8.2425470e-02 1.0000651e-01 1.0925759e-01 1.0726669e-01 1.1120208e-01 9.8918033e-02 1.1167083e-01 7.2661493e-02 4.1665349e-02 8.1426911e-02 6.7501542e-02 1.1159988e-01 1.1014878e-01 6.3989575e-02 7.6224987e-02 8.1835804e-02 1.4773346e-01 9.4587815e-02 7.1696153e-02 8.9447560e-02 1.1534202e-01 5.4876111e-02 7.3538690e-02 7.2081812e-02 4.6952332e-02 4.9385723e-02 1.0589901e-01 6.4038977e-02 8.7644093e-02 5.3625558e-02 1.0917520e-01 6.0322002e-02 1.1787190e-01 7.6918821e-02 8.8499122e-02 7.7885622e-02 4.6255911e-02 5.2612368e-02 8.1239445e-02 4.0019052e-02 9.8685385e-02 9.2131722e-02 8.0633770e-02 5.6243805e-02 7.5363077e-02 5.6811044e-02 7.4440381e-02 7.6512320e-02 5.8097767e-02 3.5211195e-02 2.8463781e-02 3.4215950e-02 4.2147698e-02 4.2414656e-02 4.0542228e-02 3.5615822e-02 4.6295613e-02 2.0363061e-02 6.3798220e-03 2.8163995e-02 2.0925233e-02 4.6838114e-02 4.7487902e-02 1.7368541e-02 2.1117782e-02 2.2967145e-02 7.3810390e-02 3.6316652e-02 2.2375879e-02 2.9367287e-02 4.8511603e-02 1.5463833e-02 1.9583828e-02 1.8938570e-02 1.0601468e-02 8.3988430e-03 4.1723024e-02 1.5629140e-02 3.2244606e-02 9.3153170e-03 4.4933305e-02 1.3111918e-02 4.4412271e-02 3.0809608e-02 2.7438827e-02 2.1093197e-02 7.0260502e-03 1.3969417e-02 2.9313944e-02 1.5218585e-02 3.5211195e-02 3.1983672e-02 2.6841235e-02 2.0313092e-02 2.9439905e-02 1.4332974e-02 1.9364178e-02 2.0170126e-02 3.9924060e-03 1.0538198e-02 3.2148883e-03 2.7366019e-03 4.4621109e-03 1.7397347e-03 4.1425525e-03 5.2723146e-03 1.1136640e-02 2.9112354e-02 1.1160954e-02 1.6541883e-02 6.0336652e-03 8.3590571e-03 1.6026960e-02 9.4770245e-03 8.7523451e-03 9.5282841e-03 8.5487892e-03 1.3900105e-02 5.7309995e-03 4.7773544e-03 2.3853571e-02 1.0495078e-02 1.1045936e-02 2.7111420e-02 2.3076623e-02 4.8207470e-03 1.4681091e-02 1.0141190e-02 2.1316125e-02 5.8141126e-03 1.6474359e-02 1.3064397e-03 2.0347529e-02 5.7992324e-03 9.5368199e-03 2.5369333e-02 2.4565418e-02 1.2896533e-02 4.4389699e-02 3.9924060e-03 5.8442541e-03 1.0341765e-02 2.9771308e-02 2.0024906e-02 2.0168043e-02 1.0668382e-02 1.0461554e-02 1.6977050e-03 8.0038314e-04 3.2736649e-04 7.3247771e-04 1.7861084e-03 1.2199790e-05 1.6168506e-03 2.0903986e-03 1.2529506e-02 1.9650001e-03 4.3150624e-03 2.0139182e-03 3.1759995e-03 4.1258242e-03 2.0323215e-03 3.5878910e-03 9.4977361e-03 1.5488203e-03 3.0112961e-03 2.7909675e-04 1.8152664e-03 8.3701176e-03 2.5714996e-03 2.7148441e-03 1.0520423e-02 9.2964166e-03 7.5284963e-04 3.9888853e-03 1.8331853e-03 9.4586856e-03 1.6039412e-03 5.4889906e-03 2.8052973e-03 7.3341015e-03 1.2316222e-03 3.2601592e-03 1.0868316e-02 8.8162037e-03 2.9210959e-03 2.1963225e-02 0.0000000e+00 1.8230646e-04 1.5254865e-03 1.2273284e-02 6.9788794e-03 6.3577637e-03 3.5631537e-03 4.1101326e-03 4.2047124e-03 2.6033919e-03 1.7916004e-03 6.7524244e-03 1.5279872e-03 2.3303877e-03 1.3468412e-03 7.9573793e-03 9.8329441e-06 8.0754860e-04 2.3639655e-03 2.4986002e-03 1.3799583e-03 2.6729899e-03 6.2996248e-03 1.0853281e-02 4.8574547e-04 3.9201723e-04 1.4418237e-03 2.9002781e-03 3.0725233e-03 3.0407572e-03 2.9093497e-03 5.1175009e-03 6.6599859e-03 1.5926877e-03 2.5889139e-03 1.3713626e-04 8.5117654e-03 1.9829707e-03 4.3442759e-03 8.7298845e-03 2.1325966e-03 3.5994846e-03 5.1472858e-03 7.9423211e-03 3.5289513e-03 2.0294631e-04 1.1925423e-02 1.6977050e-03 7.8274456e-04 4.8500499e-05 4.9289073e-03 1.8879551e-03 2.5218165e-03 4.9495642e-03 6.1589498e-03 1.4775011e-03 2.8508338e-03 3.8437964e-04 1.0082748e-03 4.3870644e-03 2.7766403e-03 1.3317076e-02 4.6107743e-03 7.0365754e-03 5.0970174e-03 7.0294656e-03 5.8145812e-03 1.6991258e-03 1.6190058e-03 1.4192459e-02 4.5311136e-03 5.3518685e-03 7.5607221e-04 4.5347742e-03 1.1372343e-02 2.1219078e-03 2.3903563e-03 1.2745790e-02 9.1602079e-03 2.9606388e-03 4.3905218e-03 4.7897027e-03 8.0493172e-03 4.4779450e-03 5.1908739e-03 9.8295198e-04 1.2281083e-02 3.7969538e-04 1.7665186e-03 1.0605818e-02 1.1571194e-02 6.2101277e-03 2.7119271e-02 8.0038314e-04 1.4492761e-03 3.6743113e-03 1.7056692e-02 1.1710246e-02 8.2821554e-03 2.2240766e-03 2.2684005e-03 3.0380682e-04 2.0343287e-03 2.8037036e-04 8.3123377e-04 3.9939935e-03 1.6647447e-02 2.9061444e-03 6.1278767e-03 1.1769582e-03 2.2926522e-03 6.3717175e-03 3.9793414e-03 5.6921961e-03 6.6386310e-03 1.6577942e-03 4.6178179e-03 1.2108195e-03 8.4773320e-04 1.1017765e-02 4.7223063e-03 4.9236953e-03 1.3900771e-02 1.3046714e-02 3.7972280e-04 6.5335900e-03 2.3480655e-03 1.3286542e-02 9.3142474e-04 8.4820747e-03 2.8343361e-03 8.2403259e-03 2.5815546e-03 5.4537681e-03 1.4900562e-02 1.1670902e-02 3.7659184e-03 2.5593433e-02 3.2736649e-04 7.7091107e-04 2.6079554e-03 1.4611634e-02 8.0055625e-03 9.0564615e-03 5.9260895e-03 6.5055071e-03 3.8779943e-03 5.7015696e-04 1.7303569e-04 4.1690936e-03 1.6322670e-02 1.9974930e-03 5.0009825e-03 3.2570724e-04 9.5057293e-04 5.8304604e-03 4.8059418e-03 7.5457291e-03 5.1241997e-03 6.9111123e-04 3.7782470e-03 1.7092344e-03 2.5698914e-04 9.5453465e-03 5.5846827e-03 5.6938512e-03 1.2788699e-02 1.3393372e-02 7.3809633e-06 6.7682476e-03 1.3200598e-03 1.4442150e-02 1.8441195e-04 9.0822620e-03 4.9911804e-03 5.8437761e-03 3.8589879e-03 6.9829557e-03 1.5273413e-02 1.0318402e-02 2.4584659e-03 2.2525564e-02 7.3247771e-04 8.2385517e-04 1.9942911e-03 1.2041849e-02 5.7258386e-03 8.2738022e-03 7.3225251e-03 8.2204997e-03 2.0515682e-03 5.4510656e-03 5.2217407e-03 1.7964531e-02 7.2742632e-03 1.0588415e-02 6.2949332e-03 8.6345788e-03 9.1795241e-03 3.5876856e-03 2.6991302e-03 1.4376485e-02 6.5862079e-03 8.4804528e-03 2.1372463e-03 5.3927480e-03 1.5896874e-02 4.1394106e-03 4.5375167e-03 1.7523452e-02 1.2909742e-02 4.0941134e-03 7.2891356e-03 7.2227860e-03 1.1126344e-02 5.7124492e-03 8.1126160e-03 1.3907276e-04 1.6260954e-02 1.3854449e-03 3.2122771e-03 1.4553993e-02 1.6151019e-02 9.1578470e-03 3.3857588e-02 1.7861084e-03 2.9619943e-03 6.1829720e-03 2.2326762e-02 1.5680260e-02 1.2223630e-02 3.9245535e-03 3.7050303e-03 1.3686449e-03 2.1603250e-03 1.2649428e-02 1.7801502e-03 4.1458992e-03 1.7278919e-03 2.7991042e-03 4.0888611e-03 2.2218452e-03 3.9716339e-03 8.9665631e-03 1.3013529e-03 2.8806398e-03 3.4594790e-04 1.5677728e-03 8.1916354e-03 2.7802616e-03 2.9107124e-03 1.0452698e-02 9.5134829e-03 5.8171764e-04 4.1098444e-03 1.6001879e-03 9.8257537e-03 1.3468918e-03 5.7037585e-03 3.1132824e-03 6.8805675e-03 1.4645264e-03 3.5912866e-03 1.1107885e-02 8.6722521e-03 2.6587395e-03 2.1561220e-02 1.2199790e-05 1.4772036e-04 1.4023821e-03 1.1878859e-02 6.5538274e-03 6.3060622e-03 3.8811059e-03 4.4873121e-03 5.6719205e-03 1.8601099e-02 2.5037679e-03 5.8026549e-03 3.0990295e-05 3.8568498e-04 7.1301713e-03 6.6581271e-03 9.9891912e-03 3.5467136e-03 7.7986655e-04 4.6294369e-03 2.9226786e-03 3.3006174e-05 1.0560037e-02 7.5497742e-03 7.6393818e-03 1.4326258e-02 1.5841028e-02 1.7171643e-04 8.5994033e-03 1.5671556e-03 1.7334506e-02 1.9316046e-05 1.1306536e-02 6.6195881e-03 5.5794173e-03 5.6606012e-03 9.3044548e-03 1.7865075e-02 1.1490070e-02 2.7178516e-03 2.3185889e-02 1.6168506e-03 1.6521970e-03 2.7157547e-03 1.2355858e-02 5.5768992e-03 9.6617637e-03 9.6558503e-03 1.0729574e-02 4.4050618e-03 1.4462579e-03 1.4084514e-03 6.0437228e-03 6.9280724e-03 5.9092958e-04 3.5267150e-04 2.3264461e-03 1.8099026e-02 3.0921484e-03 8.6005594e-04 9.1287231e-04 6.3843974e-03 3.0627943e-03 4.0297343e-04 3.2526954e-04 3.6399798e-03 2.6478661e-03 3.9939935e-03 3.1762278e-04 2.2988741e-03 3.2596799e-03 5.3016678e-03 9.7044163e-04 7.0435695e-03 5.7009823e-03 1.4263218e-03 1.5142545e-03 3.5226184e-03 3.0792135e-03 2.3251410e-03 1.2857609e-02 2.0903986e-03 1.3292907e-03 8.8570158e-04 6.8622586e-03 5.1349839e-03 1.4775225e-03 1.2815928e-03 1.9995407e-03 7.7887386e-03 4.2718762e-03 1.8880208e-02 1.9223691e-02 2.7240761e-03 5.5247078e-03 8.3688264e-03 3.7206770e-02 1.2358468e-02 4.8889738e-03 9.3012979e-03 2.0063076e-02 2.3808756e-03 4.8847228e-03 4.4654367e-03 8.0211221e-04 5.5572260e-04 1.5849918e-02 2.4737465e-03 1.0011166e-02 2.0320189e-03 1.7697241e-02 2.0106232e-03 2.1009310e-02 1.0002251e-02 9.3994441e-03 6.7786672e-03 4.3329436e-04 1.8154672e-03 8.4395042e-03 6.2214471e-03 1.2529506e-02 1.0325253e-02 7.1412079e-03 5.6981926e-03 9.1712120e-03 1.6108637e-03 5.7438691e-03 6.7900644e-03 6.8888062e-04 2.5080626e-03 2.5613963e-03 1.3274318e-03 2.8814590e-03 6.6724246e-03 1.1044919e-02 5.3338455e-04 3.3999007e-04 1.6762239e-03 3.1000026e-03 2.8256325e-03 3.2387801e-03 3.0867064e-03 4.8730365e-03 6.6378483e-03 1.7822362e-03 2.6462661e-03 1.4123935e-04 8.6230682e-03 2.1318487e-03 4.4281251e-03 9.3233552e-03 1.8673327e-03 3.9343847e-03 5.4568897e-03 7.9015195e-03 3.2859077e-03 1.3180217e-04 1.1320265e-02 1.9650001e-03 9.6799653e-04 7.8282421e-05 4.5166083e-03 1.6331972e-03 2.3904078e-03 5.2224500e-03 6.4841213e-03 5.7642096e-03 5.5885978e-03 3.7048759e-04 3.1695738e-03 7.3434699e-03 1.6768125e-02 2.3743894e-03 1.1932462e-04 3.2200650e-03 6.6976868e-03 7.3134819e-04 3.2564607e-03 2.9664521e-03 1.9437509e-03 4.0525881e-03 4.6657625e-03 1.7256268e-03 1.3667365e-03 6.4082141e-03 5.2167922e-03 3.0823107e-03 1.3133486e-02 1.7068449e-03 5.3434665e-03 5.8085657e-03 4.9261084e-03 9.6684380e-04 7.1816432e-04 6.8418503e-03 4.3150624e-03 2.7358698e-03 7.4246714e-04 2.1999970e-03 1.3715192e-03 6.3897855e-04 5.2531215e-03 6.6567087e-03 1.9959041e-04 7.2877827e-03 7.2348769e-03 1.0891220e-02 3.2459725e-03 7.4122702e-04 4.6749634e-03 3.3669230e-03 7.9583955e-05 1.0437521e-02 8.1436642e-03 8.2061472e-03 1.4337740e-02 1.6319841e-02 3.0462366e-04 9.0085125e-03 1.5214225e-03 1.8068443e-02 2.5934558e-05 1.1842923e-02 7.5545176e-03 5.1162105e-03 6.3606028e-03 1.0107865e-02 1.8360102e-02 1.1411116e-02 2.5943653e-03 2.2590562e-02 2.0139182e-03 1.9469619e-03 2.8165944e-03 1.1880076e-02 5.1539049e-03 9.7568917e-03 1.0422756e-02 1.1596876e-02 7.5663186e-03 8.6488322e-03 1.3133604e-02 3.0107568e-03 7.9406152e-04 4.7466095e-03 4.5711602e-03 4.9936224e-04 9.9296431e-03 9.5749791e-03 9.5587917e-03 1.4081306e-02 1.7234890e-02 8.7040364e-04 9.9040316e-03 1.5012448e-03 1.9639569e-02 3.0179394e-04 1.3010570e-02 1.0161281e-02 3.9519870e-03 8.1767554e-03 1.2080688e-02 1.9279439e-02 1.0989202e-02 2.3266303e-03 2.0713423e-02 3.1759995e-03 2.8067172e-03 3.1032715e-03 1.0490119e-02 4.0786420e-03 9.8110939e-03 1.2277454e-02 1.3709414e-02 1.7582839e-03 4.9489607e-03 1.9934064e-02 3.5016865e-03 3.4697267e-04 2.6310541e-03 8.0617166e-03 9.6411709e-04 1.7064375e-03 1.4666650e-03 1.4745772e-03 2.1108592e-03 5.5260764e-03 5.1802495e-04 2.3341578e-03 3.7167048e-03 6.5626516e-03 1.3180061e-03 1.1565092e-02 3.6658759e-03 3.8514458e-03 3.6514318e-03 2.8125792e-03 9.8545506e-04 1.7565517e-03 7.9513446e-03 4.1258242e-03 2.7177731e-03 1.0483123e-03 3.5558284e-03 3.1649242e-03 2.4110016e-04 3.1231514e-03 4.2303212e-03 8.7046320e-04 1.9803680e-02 4.5057787e-03 2.2875453e-03 8.1473256e-04 7.2784764e-03 5.2841523e-03 3.1936914e-05 6.0579771e-05 5.5427829e-03 2.9996038e-03 4.7111715e-03 6.7018226e-04 3.8206460e-03 2.7379425e-03 6.3961850e-03 9.7000606e-04 5.0455840e-03 8.7517746e-03 5.2181192e-04 4.2086901e-04 3.8740476e-03 5.1909001e-03 4.1846614e-03 1.6912712e-02 2.0323215e-03 1.6814934e-03 2.0140995e-03 1.0246238e-02 8.0709165e-03 2.9533168e-03 3.5001524e-04 7.2402168e-04 2.4525121e-02 8.3786268e-03 5.9730003e-03 2.2241688e-03 1.0490949e-02 1.0052150e-02 8.4481649e-04 1.0306755e-03 9.7104895e-03 4.7051149e-03 7.5682486e-03 2.5521754e-03 7.8053009e-03 3.0734717e-03 9.8801938e-03 2.2384251e-03 3.6192976e-03 1.5052125e-02 6.1736712e-04 1.0280118e-04 5.5375454e-03 9.7589612e-03 8.6474768e-03 2.4375316e-02 3.5878910e-03 3.8390770e-03 5.3089764e-03 1.6859170e-02 1.4176552e-02 6.5576789e-03 2.5984028e-04 9.3320862e-05 6.7547274e-03 1.5238284e-02 1.2713534e-02 3.0890016e-03 2.3712664e-02 2.1340343e-02 2.1521507e-02 3.0005294e-02 3.3960619e-02 5.2290080e-03 2.3006673e-02 8.7125215e-03 3.6416136e-02 3.8281789e-03 2.7389250e-02 1.5157530e-02 1.2639929e-02 1.7411617e-02 2.3820503e-02 3.6850697e-02 2.5373970e-02 1.0602298e-02 3.7784525e-02 9.4977361e-03 1.0004799e-02 1.1952848e-02 2.3559319e-02 1.3109247e-02 2.3603108e-02 2.4562463e-02 2.6017612e-02 1.7241949e-03 2.0286689e-03 1.1335916e-03 5.6322483e-03 5.1221157e-03 5.0559971e-03 8.5835151e-03 1.0650923e-02 5.5566853e-04 5.1134826e-03 1.3892193e-04 1.2640034e-02 5.6438567e-04 7.4313522e-03 8.3161300e-03 2.5369950e-03 4.7342848e-03 7.3166190e-03 1.2276381e-02 6.3456614e-03 5.8916640e-04 1.5594819e-02 1.5488203e-03 9.3082136e-04 7.5849611e-04 7.0579696e-03 2.4440449e-03 5.1791902e-03 7.3166180e-03 8.5826500e-03 2.1075364e-03 5.4157920e-03 1.3748154e-03 2.4371345e-03 2.2201300e-03 2.6881175e-03 4.1574302e-03 3.5021271e-03 1.4032793e-03 9.1188034e-04 6.1085103e-03 4.1365224e-03 2.7438304e-03 1.0770631e-02 2.1519140e-03 3.9794011e-03 4.6480198e-03 5.1312578e-03 1.6288841e-03 5.4679156e-04 8.7532006e-03 3.0112961e-03 1.7262374e-03 2.9055719e-04 3.3158230e-03 1.8013008e-03 9.2887256e-04 4.2320076e-03 5.4776329e-03 3.2889511e-03 6.5019207e-03 1.1654311e-03 1.2550970e-03 7.9744383e-03 6.4095512e-03 1.6783080e-03 2.2182877e-03 1.9380758e-03 6.5001122e-03 2.8053456e-03 3.3035106e-03 3.3582234e-03 7.0744470e-03 5.6581852e-04 1.8141689e-03 7.7202001e-03 6.7565678e-03 2.7207024e-03 1.9179880e-02 2.7909675e-04 2.2008347e-04 1.1064738e-03 1.0688407e-02 6.6082062e-03 4.4435997e-03 1.9606397e-03 2.4773484e-03 1.1763001e-02 8.2259536e-03 8.3480149e-03 1.5692475e-02 1.7083720e-02 2.8355914e-04 9.4960515e-03 2.0536401e-03 1.8469924e-02 1.0092056e-04 1.2279622e-02 6.4441301e-03 6.4046448e-03 6.0274458e-03 9.8857207e-03 1.9188640e-02 1.2736270e-02 3.3492132e-03 2.4941935e-02 1.8152664e-03 1.9865894e-03 3.3071796e-03 1.3641872e-02 6.4197769e-03 1.0769048e-02 1.0309787e-02 1.1341762e-02 5.1119032e-03 4.6662587e-03 4.6505160e-04 3.2269116e-03 9.0774431e-03 2.5215213e-03 4.0057628e-03 6.1199030e-03 9.7447895e-03 3.5360157e-03 1.8995080e-02 2.6805580e-03 8.6582433e-03 8.1342278e-03 3.7198152e-03 3.9443681e-05 2.6670326e-03 3.4016785e-03 8.3701176e-03 6.1426721e-03 2.9189037e-03 1.0083175e-03 2.2713681e-03 3.9595770e-04 7.2341855e-03 8.8447009e-03 1.1230173e-05 5.1389929e-03 2.4707449e-03 5.4731760e-03 5.1980845e-04 4.3112681e-03 2.1787917e-03 7.2534046e-03 6.7672528e-04 5.6623211e-03 9.1277214e-03 7.3619628e-04 3.7016392e-04 3.2564607e-03 4.9531882e-03 4.5742792e-03 1.6438776e-02 2.5714996e-03 2.1421258e-03 2.3256067e-03 1.0175775e-02 8.4088183e-03 2.7613050e-03 2.4755840e-04 6.0873636e-04 4.6703189e-03 2.2089026e-03 5.5642336e-03 3.7824549e-04 4.1878969e-03 2.0462491e-03 7.3148116e-03 5.4583143e-04 6.1390745e-03 8.7247882e-03 9.1073145e-04 4.9034572e-04 2.9664521e-03 4.5041455e-03 4.3650355e-03 1.5605807e-02 2.7148441e-03 2.1951828e-03 2.2077213e-03 9.5777719e-03 8.0120192e-03 2.4278861e-03 3.2552080e-04 7.3734328e-04 1.7636357e-03 1.2290813e-02 2.3919392e-03 6.5534766e-03 4.2355076e-03 1.3428854e-02 2.7760250e-03 2.0719142e-02 5.3736931e-03 9.3604766e-03 7.8350168e-03 1.9437509e-03 2.3451546e-04 4.9805974e-03 3.4615227e-03 1.0520423e-02 8.1642247e-03 4.6947450e-03 2.2490758e-03 4.7893620e-03 5.2359029e-04 6.8110246e-03 8.2524171e-03 1.3046714e-02 1.1318623e-03 8.6855001e-03 5.4130623e-04 1.5131426e-02 5.6179632e-04 1.5407719e-02 1.0669118e-02 5.8683852e-03 3.5797271e-03 6.2414181e-05 2.6714308e-03 7.7088950e-03 9.8081718e-03 9.2964166e-03 7.7221233e-03 5.7330158e-03 7.6711675e-03 9.7842038e-03 1.6419007e-03 2.8364539e-03 3.5173281e-03 6.5335900e-03 1.1340078e-03 1.4192754e-02 1.5964523e-04 8.8481511e-03 5.2711173e-03 5.4383051e-03 3.8856537e-03 6.9538378e-03 1.4900562e-02 9.8443835e-03 2.2013577e-03 2.1746585e-02 7.5284963e-04 7.7091107e-04 1.8026328e-03 1.1463429e-02 5.3229428e-03 7.8904169e-03 7.2583306e-03 8.1944055e-03 3.9135288e-03 1.7196090e-03 8.1113282e-03 2.2942459e-04 9.3463740e-03 6.8261058e-03 2.3665724e-03 1.6309596e-03 1.7256268e-03 2.3397606e-03 3.5922701e-03 1.1208568e-02 3.9888853e-03 2.9455145e-03 1.9660659e-03 6.5467152e-03 6.1492489e-03 9.3538874e-04 1.2212079e-03 1.9267388e-03 1.0793365e-02 1.2592720e-03 6.0147390e-03 9.1609131e-03 1.8537817e-03 4.5335508e-03 6.6172179e-03 1.0131803e-02 4.6089934e-03 1.7595473e-04 1.2987364e-02 1.8331853e-03 9.7087402e-04 3.3609260e-04 5.4018686e-03 1.7068395e-03 3.6646912e-03 6.4787894e-03 7.7901480e-03 1.6754655e-02 6.9641544e-04 1.3182224e-02 1.4651385e-02 4.9466239e-03 2.3931163e-03 5.9263439e-04 5.4211666e-03 1.0221921e-02 1.4928620e-02 9.4586856e-03 8.3819492e-03 7.3339215e-03 1.2011676e-02 1.3620169e-02 3.6119007e-03 1.8435886e-03 2.0979826e-03 1.0794235e-02 6.9811950e-03 4.9473584e-03 5.5993500e-03 9.1192858e-03 1.7103671e-02 1.0654852e-02 2.2936354e-03 2.1887320e-02 1.6039412e-03 1.5236521e-03 2.3684718e-03 1.1405606e-02 4.9416620e-03 8.9601309e-03 9.4128890e-03 1.0537337e-02 1.0144176e-02 9.3650956e-03 2.8049928e-03 1.4261619e-03 9.6795720e-04 3.1673467e-03 5.6308712e-03 1.2399657e-02 5.4889906e-03 4.4629844e-03 3.5148873e-03 8.2921709e-03 8.5570702e-03 1.5767832e-03 9.7941910e-04 1.5044401e-03 1.9162648e-02 2.3258011e-03 4.3611697e-03 1.7154674e-02 1.9266334e-02 1.1399040e-02 3.8294804e-02 2.8052973e-03 4.2987486e-03 8.1194560e-03 2.5933333e-02 1.8564970e-02 1.4940871e-02 5.2230072e-03 4.8296564e-03 1.1136786e-02 1.2996602e-02 1.1896568e-02 3.3692870e-03 1.0295085e-03 6.7928621e-03 7.3341015e-03 5.3674586e-03 2.6380919e-03 1.7126363e-03 1.9252416e-05 3.9628066e-03 1.2371798e-02 1.4417093e-02 5.5916770e-04 7.0172680e-03 8.6736288e-03 5.5025710e-03 2.2796596e-02 1.2316222e-03 1.4793666e-03 2.9451077e-03 1.4303806e-02 1.0475492e-02 5.7470026e-03 7.8882742e-04 9.0526661e-04 4.3645694e-03 7.8632956e-03 7.2260963e-03 2.1331409e-02 3.2601592e-03 3.2514734e-03 4.2300937e-03 1.4385386e-02 1.2160577e-02 5.0215597e-03 4.3279739e-05 5.1818418e-05 3.0767343e-03 9.0015857e-03 9.8042907e-03 1.0868316e-02 9.1698320e-03 6.9442206e-03 8.2610449e-03 1.0963075e-02 2.1355881e-03 3.5431999e-03 4.2180590e-03 3.2143555e-03 3.3819870e-03 8.8162037e-03 6.5643841e-03 3.2940615e-03 1.2995092e-03 2.9075684e-03 3.1897532e-04 6.9331287e-03 8.4863836e-03 1.0164058e-02 2.9210959e-03 1.7078622e-03 4.0814516e-04 3.6297378e-03 8.7693352e-04 2.6826046e-03 6.9132822e-03 8.3825591e-03 2.1963225e-02 1.8183756e-02 1.2010413e-02 1.7351817e-03 6.4958425e-03 5.7545512e-03 1.9704198e-02 2.2181841e-02 1.8230646e-04 1.5254865e-03 1.2273284e-02 6.9788794e-03 6.3577637e-03 3.5631537e-03 4.1101326e-03 6.5324999e-04 9.4729463e-03 5.0293123e-03 4.5622977e-03 3.3814637e-03 4.1233865e-03 5.1615257e-03 2.3326030e-03 2.1577255e-03 4.0325048e-03 5.1426622e-03 1.5351757e-03 2.6422594e-03 1.3282600e-02 1.5461999e-02 3.4307479e-03 1.1531055e-02 1.3518551e-02 4.2918744e-03 5.5398788e-03 8.4122900e-05 diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-correlation-ml.txt b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-correlation-ml.txt new file mode 100644 index 0000000000000000000000000000000000000000..2a17a2a8fb002493fff38c7ed059668867768a7e --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-correlation-ml.txt @@ -0,0 +1 @@ + 9.2507465e-01 9.6528566e-01 8.7255441e-01 1.1287379e+00 8.7318727e-01 1.0767102e+00 9.1419676e-01 1.1503304e+00 9.8074509e-01 1.0135025e+00 1.0495025e+00 9.4794536e-01 9.6829273e-01 1.1345767e+00 1.1048008e+00 9.2407796e-01 1.0228634e+00 9.3853195e-01 9.9377619e-01 1.0407662e+00 9.5048989e-01 9.0465688e-01 9.8056930e-01 8.9777156e-01 9.6357127e-01 9.3864452e-01 9.9754613e-01 9.7271356e-01 8.4383151e-01 9.6981983e-01 9.7510267e-01 1.0112663e+00 7.8730400e-01 1.0299498e+00 9.9307979e-01 9.0239520e-01 8.5428231e-01 8.8972742e-01 8.5933162e-01 9.6625934e-01 9.4175449e-01 9.9120729e-01 1.0503963e+00 8.8223053e-01 1.3261434e+00 1.1063209e+00 8.4058398e-01 1.0844267e+00 1.1153093e+00 1.0092643e+00 8.9585237e-01 1.0599818e+00 1.2321707e+00 1.1359624e+00 8.3503556e-01 1.1792243e+00 7.9159781e-01 1.0830419e+00 1.2181870e+00 9.9888500e-01 1.0227144e+00 6.8557277e-01 9.6836193e-01 1.1061227e+00 1.0883453e+00 9.5681974e-01 9.9436299e-01 1.0304323e+00 1.1273949e+00 1.0735563e+00 1.0582583e+00 9.6040272e-01 1.0032137e+00 8.4900547e-01 1.1035351e+00 8.7867480e-01 9.6433176e-01 9.1850122e-01 8.9337435e-01 1.0449390e+00 8.9639384e-01 9.6704971e-01 1.0084258e+00 1.0528587e+00 1.1764481e+00 1.0913280e+00 1.0136672e+00 1.2737156e+00 9.5130359e-01 1.0367909e+00 1.1983402e+00 1.1319901e+00 1.1117462e+00 1.0343695e+00 1.0838628e+00 7.5266057e-01 1.0763316e+00 8.8067924e-01 9.6734383e-01 9.8800551e-01 1.2265742e+00 7.8833055e-01 1.0338670e+00 8.6666625e-01 9.9039950e-01 9.7142684e-01 9.3138616e-01 8.5849977e-01 8.5486301e-01 1.0516028e+00 1.1105313e+00 9.5943505e-01 9.8845171e-01 1.0566288e+00 9.9712198e-01 9.5545756e-01 1.1817974e+00 9.9128482e-01 1.0117892e+00 1.0979115e+00 1.0493943e+00 9.1318848e-01 9.3157311e-01 8.7073304e-01 1.2459441e+00 9.3412689e-01 1.0482297e+00 9.4224032e-01 9.5134153e-01 9.0857493e-01 9.7264161e-01 8.2900820e-01 9.3140549e-01 1.1330242e+00 1.0333002e+00 1.0117861e+00 1.2053255e+00 8.5291396e-01 1.0148928e+00 8.6641379e-01 9.7080819e-01 9.5457159e-01 9.5207457e-01 9.3539674e-01 9.0769069e-01 9.5322590e-01 1.1181803e+00 9.9765614e-01 7.5370610e-01 1.0807114e+00 1.0804601e+00 9.0214124e-01 8.7101998e-01 1.0167435e+00 1.2045936e+00 8.7300539e-01 1.1054300e+00 7.9145574e-01 1.0279340e+00 8.7623462e-01 1.0034756e+00 1.0386933e+00 9.3910970e-01 1.0028455e+00 9.9868824e-01 9.8752945e-01 9.8319327e-01 1.3110209e+00 8.6180633e-01 1.0993856e+00 8.5912563e-01 1.1303979e+00 9.8690459e-01 9.6910090e-01 9.1456819e-01 1.1525339e+00 1.1064552e+00 1.1062255e+00 9.7226683e-01 1.1091447e+00 1.1072238e+00 9.6544444e-01 9.6681036e-01 9.3247685e-01 9.6854634e-01 1.1035119e+00 1.1317148e+00 9.5557793e-01 9.8908485e-01 7.4873648e-01 diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-cosine-ml-iris.txt b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-cosine-ml-iris.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b705b348fc3de5041ad9e3d6a1686af61046b2a --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-cosine-ml-iris.txt @@ -0,0 +1 @@ + 1.4208365e-03 1.2652718e-05 8.9939315e-04 2.4232332e-04 9.9747033e-04 9.2045721e-04 2.2040648e-04 8.6480051e-04 1.2354911e-03 5.3650090e-06 1.0275886e-03 1.1695784e-03 2.3556571e-04 1.4590172e-03 1.8981327e-03 1.0939621e-03 1.2392314e-04 3.5850877e-04 8.6078038e-04 1.4490833e-03 8.4059347e-04 3.2873982e-03 2.7359832e-03 4.1316044e-03 2.7719149e-03 1.1814143e-03 1.1431285e-04 2.3850299e-04 1.3446247e-03 1.6406549e-03 1.2070654e-03 2.2241257e-03 1.4969348e-03 1.2354911e-03 7.6154552e-04 9.0853884e-04 1.2354911e-03 1.5825612e-04 2.3716586e-04 2.5806020e-04 8.5870759e-03 4.3447170e-04 2.6103416e-03 3.4026094e-03 1.2625429e-03 1.0000714e-03 2.7088099e-04 4.6161202e-05 1.7993015e-04 7.1619641e-02 7.4013940e-02 8.2336355e-02 9.3599031e-02 8.6542298e-02 9.2667602e-02 8.0934616e-02 6.7002415e-02 7.9695318e-02 8.3991107e-02 8.8330128e-02 7.6449243e-02 8.6123390e-02 9.1414445e-02 5.9767596e-02 6.8589764e-02 9.2363748e-02 7.5261304e-02 1.0768528e-01 7.8250149e-02 9.7383870e-02 6.9410330e-02 1.0895936e-01 9.1644587e-02 7.2677910e-02 7.2208930e-02 8.7635618e-02 9.3586395e-02 8.7700193e-02 5.8825053e-02 7.9271072e-02 7.4136423e-02 7.0977606e-02 1.1670751e-01 9.6691498e-02 7.7157266e-02 7.8793137e-02 9.6187418e-02 7.4355610e-02 8.6677009e-02 9.7286808e-02 8.5214421e-02 7.7419803e-02 6.8888638e-02 8.6192502e-02 7.4757686e-02 7.8851331e-02 7.5042247e-02 5.2484298e-02 7.8023694e-02 1.3991867e-01 1.2655756e-01 1.2099780e-01 1.2515784e-01 1.3134370e-01 1.3306336e-01 1.2911903e-01 1.2854613e-01 1.3655327e-01 1.1601604e-01 9.9632498e-02 1.2063863e-01 1.1404742e-01 1.3409335e-01 1.3451976e-01 1.1368563e-01 1.1469397e-01 1.1505768e-01 1.5479411e-01 1.2906390e-01 1.1634186e-01 1.2299625e-01 1.3892070e-01 1.0732534e-01 1.1401190e-01 1.1254699e-01 1.0266168e-01 1.0210743e-01 1.3111378e-01 1.0950615e-01 1.2501276e-01 1.0108759e-01 1.3297245e-01 1.0624129e-01 1.3360037e-01 1.2002867e-01 1.2233784e-01 1.1387071e-01 1.0061412e-01 1.0649150e-01 1.2174429e-01 1.0147290e-01 1.2655756e-01 1.2438709e-01 1.2138109e-01 1.1044406e-01 1.1910000e-01 1.0821359e-01 1.1609070e-01 1.1329724e-01 1.2085473e-03 1.2060695e-03 2.7592041e-03 3.0736184e-03 3.7201033e-03 1.0861043e-03 7.3910902e-04 3.4790667e-04 1.3491546e-03 2.4493052e-03 1.8482587e-04 2.3308566e-03 3.8997403e-03 6.3069928e-03 4.1362617e-03 1.5079538e-03 7.4890015e-04 4.0049414e-03 3.0763412e-04 3.2877725e-03 8.6909088e-03 1.8863199e-03 4.7592122e-03 4.5180751e-04 1.7148301e-03 8.8703626e-04 5.7128783e-04 1.7151033e-03 8.4814176e-04 4.7551630e-04 6.9313334e-03 5.8126778e-03 3.4790667e-04 9.7078221e-04 1.0390338e-03 3.4790667e-04 1.1371495e-03 7.0598263e-04 2.3100870e-03 3.1332241e-03 2.9870115e-03 3.7693564e-03 5.5008337e-03 2.0081767e-04 3.9261497e-03 1.6237803e-03 1.7731168e-03 5.9153033e-04 5.9997244e-02 6.3706418e-02 7.0131342e-02 8.0131815e-02 7.3670020e-02 8.1412444e-02 7.1132932e-02 5.6572408e-02 6.7223691e-02 7.3993918e-02 7.4363256e-02 6.6371013e-02 7.1106157e-02 7.9730716e-02 5.0610503e-02 5.7285563e-02 8.2536028e-02 6.3695818e-02 9.1877918e-02 6.6044079e-02 8.7700525e-02 5.7975072e-02 9.4407127e-02 7.9385033e-02 6.0900938e-02 6.0521931e-02 7.4070557e-02 8.1073873e-02 7.6438218e-02 4.7634460e-02 6.6728846e-02 6.1732271e-02 5.9656897e-02 1.0363139e-01 8.7312695e-02 6.8806126e-02 6.7142432e-02 8.0911573e-02 6.5091322e-02 7.4541034e-02 8.5313436e-02 7.4229332e-02 6.5328348e-02 5.7461491e-02 7.4891760e-02 6.5136264e-02 6.8598864e-02 6.3641018e-02 4.2790811e-02 6.7276779e-02 1.2872765e-01 1.1385917e-01 1.0708423e-01 1.1221780e-01 1.1844388e-01 1.1798239e-01 1.1767648e-01 1.1356773e-01 1.2073038e-01 1.0467824e-01 8.8441784e-02 1.0671832e-01 1.0091826e-01 1.2051300e-01 1.2244533e-01 1.0247664e-01 1.0203920e-01 1.0334656e-01 1.3764340e-01 1.1314999e-01 1.0390175e-01 1.1148602e-01 1.2274267e-01 9.3929112e-02 1.0239198e-01 9.9372667e-02 9.0109024e-02 9.0770318e-02 1.1749345e-01 9.5509620e-02 1.0956056e-01 8.9331297e-02 1.1936188e-01 9.3207628e-02 1.1935153e-01 1.0516553e-01 1.1204585e-01 1.0191688e-01 8.9582588e-02 9.3806716e-02 1.0922100e-01 8.9087100e-02 1.1385917e-01 1.1193127e-01 1.0978099e-01 9.7766696e-02 1.0448839e-01 9.5849546e-02 1.0619992e-01 1.0212555e-01 7.8301662e-04 3.3186074e-04 9.6097551e-04 9.6384587e-04 1.7160230e-04 7.1714495e-04 1.0915291e-03 1.4406904e-05 9.9431295e-04 1.0280837e-03 3.4520010e-04 1.6070142e-03 2.0814960e-03 1.1810349e-03 9.3270090e-05 2.4892291e-04 9.5000112e-04 1.2447556e-03 8.3736374e-04 3.6303226e-03 2.4141846e-03 3.9965261e-03 2.4688022e-03 1.0115165e-03 6.9871786e-05 1.7487334e-04 1.2251185e-03 1.4398826e-03 9.8199498e-04 2.5137187e-03 1.7466742e-03 1.0915291e-03 7.0690363e-04 8.5846505e-04 1.0915291e-03 1.0992291e-04 1.6427013e-04 2.8562896e-04 8.0123750e-03 5.0490687e-04 2.4076078e-03 3.3222239e-03 1.0270492e-03 1.0987887e-03 2.4862356e-04 7.8815959e-05 1.1120052e-04 7.0071463e-02 7.2494258e-02 8.0694698e-02 9.1816479e-02 8.4823937e-02 9.1055284e-02 7.9406161e-02 6.5540015e-02 7.8075821e-02 8.2418924e-02 8.6586217e-02 7.4908999e-02 8.4375857e-02 8.9771433e-02 5.8365951e-02 6.7055640e-02 9.0792516e-02 7.3755504e-02 1.0570869e-01 7.6652799e-02 9.5758989e-02 6.7858347e-02 1.0707149e-01 9.0015148e-02 7.1111432e-02 7.0634591e-02 8.5909852e-02 9.1841705e-02 8.6060650e-02 5.7382885e-02 7.7642663e-02 7.2560884e-02 6.9439824e-02 1.1486601e-01 9.5132094e-02 7.5722276e-02 7.7186494e-02 9.4329550e-02 7.2913445e-02 8.4999890e-02 9.5631654e-02 8.3632299e-02 7.5814411e-02 6.7360493e-02 8.4581854e-02 7.3324210e-02 7.7335911e-02 7.3484711e-02 5.1093482e-02 7.6474851e-02 1.3800148e-01 1.2463801e-01 1.1904450e-01 1.2328593e-01 1.2938789e-01 1.3104169e-01 1.2726294e-01 1.2658511e-01 1.3448678e-01 1.1418055e-01 9.7888383e-02 1.1868360e-01 1.1213978e-01 1.3206545e-01 1.3251384e-01 1.1184454e-01 1.1286955e-01 1.1328841e-01 1.5256500e-01 1.2703121e-01 1.1444439e-01 1.2112577e-01 1.3684054e-01 1.0544428e-01 1.1220824e-01 1.1073079e-01 1.0084086e-01 1.0036834e-01 1.2912019e-01 1.0768201e-01 1.2300696e-01 9.9385216e-02 1.3095409e-01 1.0446385e-01 1.3171213e-01 1.1800444e-01 1.2052688e-01 1.1209190e-01 9.8892088e-02 1.0463359e-01 1.1979721e-01 9.9600101e-02 1.2463801e-01 1.2247195e-01 1.1948197e-01 1.0852184e-01 1.1709036e-01 1.0637133e-01 1.1433097e-01 1.1154058e-01 1.2829581e-03 8.6520525e-04 1.3042912e-03 2.3052671e-04 6.0609671e-05 6.1408538e-04 7.9384016e-04 2.5551469e-04 9.4346154e-04 1.8930050e-03 4.6203036e-03 3.8649853e-03 3.3273220e-03 9.7135787e-04 2.5836286e-04 1.6395377e-03 4.6720392e-04 1.3833444e-03 6.8585778e-03 1.1817616e-03 1.4184724e-03 1.2935682e-03 4.4534899e-04 4.3337262e-04 9.9734142e-04 6.2957380e-05 2.1802414e-04 1.3452346e-03 3.6759458e-03 3.7514511e-03 6.1408538e-04 2.3527566e-03 2.5967147e-03 6.1408538e-04 3.1896708e-04 3.0643540e-04 1.7034162e-03 7.0964884e-03 1.0371098e-03 1.9760564e-03 1.6993217e-03 9.2490489e-04 1.2129757e-03 2.8785057e-04 7.8777499e-04 6.4144968e-04 5.7636535e-02 5.9786679e-02 6.7275391e-02 7.7706661e-02 7.1288776e-02 7.6308806e-02 6.5987844e-02 5.3398709e-02 6.4839697e-02 6.8887148e-02 7.2874646e-02 6.2111692e-02 7.1088473e-02 7.5274214e-02 4.7295630e-02 5.5048251e-02 7.6266639e-02 6.0532100e-02 9.0997542e-02 6.3501941e-02 8.1155480e-02 5.5841790e-02 9.1620605e-02 7.5304976e-02 5.8627379e-02 5.8302297e-02 7.2188128e-02 7.7632065e-02 7.2128571e-02 4.6353347e-02 6.4522763e-02 5.9860052e-02 5.7075256e-02 9.8501473e-02 8.0208982e-02 6.2676929e-02 6.4117314e-02 8.0306154e-02 5.9903400e-02 7.1264506e-02 8.0454669e-02 6.9667510e-02 6.2855874e-02 5.5234852e-02 7.0611788e-02 6.0083969e-02 6.3933681e-02 6.0638614e-02 4.1119113e-02 6.3291748e-02 1.2072945e-01 1.0797760e-01 1.0284307e-01 1.0630032e-01 1.1246316e-01 1.1377579e-01 1.1035397e-01 1.0939330e-01 1.1704519e-01 9.8543065e-02 8.3389076e-02 1.0253622e-01 9.6610654e-02 1.1523295e-01 1.1624035e-01 9.6621030e-02 9.6718555e-02 9.7003685e-02 1.3426257e-01 1.1013293e-01 9.8838972e-02 1.0496266e-01 1.1920082e-01 9.0400878e-02 9.6352086e-02 9.4617133e-02 8.6118226e-02 8.5443225e-02 1.1226469e-01 9.1815383e-02 1.0642172e-01 8.4132371e-02 1.1413570e-01 8.8823115e-02 1.1373227e-01 1.0228600e-01 1.0454965e-01 9.5917796e-02 8.4129252e-02 8.9732713e-02 1.0404039e-01 8.5714179e-02 1.0797760e-01 1.0611357e-01 1.0375975e-01 9.3828435e-02 1.0141953e-01 9.1231247e-02 9.8764813e-02 9.5558448e-02 7.0033377e-04 3.9650610e-04 5.3529876e-04 1.4703029e-03 2.2471049e-03 2.6137215e-04 9.1585095e-04 2.3098853e-03 3.2779352e-04 1.7003275e-03 9.5035099e-04 8.4163249e-04 3.6423601e-04 8.6760304e-04 2.6110376e-04 2.4965606e-03 5.0990123e-04 2.2208392e-03 3.4995017e-03 3.9813106e-03 4.2652650e-03 1.4776191e-03 5.3856223e-04 9.6152184e-04 1.6178695e-03 2.4296336e-03 2.2824176e-03 1.0483334e-03 6.6735604e-04 2.2471049e-03 1.7166964e-03 1.9224889e-03 2.2471049e-03 4.4953685e-04 7.5090712e-04 3.1050470e-04 1.1530910e-02 8.0837373e-05 2.6173161e-03 2.7612054e-03 2.3974656e-03 3.9140870e-04 3.5730731e-04 1.1232648e-04 8.0278741e-04 7.4728046e-02 7.6441141e-02 8.5477412e-02 9.7141382e-02 8.9947057e-02 9.5081677e-02 8.2962705e-02 6.9633999e-02 8.3013931e-02 8.6069979e-02 9.2215558e-02 7.8736928e-02 9.0603515e-02 9.4074986e-02 6.2034704e-02 7.1640320e-02 9.4150759e-02 7.8195110e-02 1.1214391e-01 8.1468219e-02 9.9059263e-02 7.2514318e-02 1.1269547e-01 9.4545020e-02 7.5842542e-02 7.5358360e-02 9.1332869e-02 9.6662705e-02 9.0277244e-02 6.2066860e-02 8.2644288e-02 7.7554694e-02 7.3959493e-02 1.1955630e-01 9.8181734e-02 7.8602674e-02 8.1755435e-02 1.0058819e-01 7.6248524e-02 8.9701900e-02 9.9938282e-02 8.7676596e-02 8.0619290e-02 7.1976555e-02 8.8793557e-02 7.6779152e-02 8.1107438e-02 7.7952944e-02 5.5245517e-02 8.0550459e-02 1.4162183e-01 1.2912349e-01 1.2423521e-01 1.2779447e-01 1.3393410e-01 1.3660889e-01 1.3105158e-01 1.3208577e-01 1.4040000e-01 1.1817736e-01 1.0200650e-01 1.2388995e-01 1.1706801e-01 1.3699958e-01 1.3682207e-01 1.1586916e-01 1.1739162e-01 1.1729454e-01 1.5902469e-01 1.3308573e-01 1.1901641e-01 1.2511327e-01 1.4289089e-01 1.1059070e-01 1.1627926e-01 1.1550831e-01 1.0561378e-01 1.0446495e-01 1.3405102e-01 1.1291439e-01 1.2888996e-01 1.0359625e-01 1.3590097e-01 1.0925250e-01 1.3665207e-01 1.2379539e-01 1.2392962e-01 1.1624448e-01 1.0286550e-01 1.0945264e-01 1.2440339e-01 1.0449561e-01 1.2912349e-01 1.2690130e-01 1.2362142e-01 1.1341467e-01 1.2276171e-01 1.1097585e-01 1.1759891e-01 1.1534218e-01 1.3143808e-04 7.3710840e-04 1.1313742e-03 2.6277162e-03 9.9332749e-04 4.8298989e-04 2.9659782e-03 1.8303797e-03 3.9657692e-03 1.4753738e-03 1.6266891e-03 7.0233916e-04 8.0313831e-04 3.4526160e-04 2.3291483e-03 1.3867759e-04 4.2228272e-03 1.6991343e-03 2.3223655e-03 3.8453210e-03 4.2904903e-04 9.9302567e-04 1.7706867e-03 9.4981017e-04 1.8259864e-03 2.0820613e-03 2.1473879e-03 2.0420431e-03 2.6277162e-03 3.0779094e-03 3.4332541e-03 2.6277162e-03 6.3280964e-04 1.0576914e-03 9.5198627e-04 1.0925795e-02 3.7286463e-04 7.9546610e-04 9.1841431e-04 2.1468126e-03 4.9129575e-04 4.3562197e-04 7.5083238e-04 1.3686608e-03 6.3901299e-02 6.4740623e-02 7.3708779e-02 8.4613714e-02 7.7866771e-02 8.2261058e-02 7.0449151e-02 5.8874682e-02 7.1767088e-02 7.3210535e-02 8.0660949e-02 6.6601983e-02 8.0033785e-02 8.1391959e-02 5.1369939e-02 6.0897790e-02 8.0716992e-02 6.7403323e-02 9.9203670e-02 7.0276809e-02 8.4922276e-02 6.1688045e-02 9.9339240e-02 8.2362360e-02 6.4928234e-02 6.4360101e-02 7.9641814e-02 8.3721620e-02 7.7549963e-02 5.2617898e-02 7.1414187e-02 6.6946935e-02 6.3031902e-02 1.0509118e-01 8.4332170e-02 6.6064468e-02 7.0064616e-02 8.8758294e-02 6.4379548e-02 7.7371173e-02 8.7052850e-02 7.5305342e-02 6.9340944e-02 6.1339869e-02 7.6377320e-02 6.5179636e-02 6.9093895e-02 6.6669498e-02 4.5609365e-02 6.8684945e-02 1.2445912e-01 1.1341836e-01 1.0935772e-01 1.1262566e-01 1.1789507e-01 1.2147174e-01 1.1488682e-01 1.1752559e-01 1.2531063e-01 1.0271865e-01 8.7888567e-02 1.0902443e-01 1.0234160e-01 1.2080033e-01 1.1990073e-01 1.0043696e-01 1.0286413e-01 1.0252340e-01 1.4292168e-01 1.1866325e-01 1.0381139e-01 1.0919240e-01 1.2785249e-01 9.6570465e-02 1.0127523e-01 1.0149554e-01 9.1688518e-02 9.0323099e-02 1.1822766e-01 9.9584713e-02 1.1452014e-01 9.0018133e-02 1.1983081e-01 9.5741335e-02 1.2190290e-01 1.0915996e-01 1.0773474e-01 1.0161859e-01 8.8729453e-02 9.5169428e-02 1.0868349e-01 9.0278091e-02 1.1341836e-01 1.1118524e-01 1.0767597e-01 9.8555096e-02 1.0809822e-01 9.6490550e-02 1.0179914e-01 1.0040847e-01 9.0953179e-04 1.6478123e-03 3.1324421e-03 9.3747882e-04 6.8074049e-04 3.4285457e-03 1.4256139e-03 3.3141786e-03 8.1135619e-04 1.2040955e-03 7.3894006e-04 1.1469835e-03 5.4914496e-05 3.0238895e-03 1.1512346e-04 2.9874978e-03 2.7356591e-03 2.9755481e-03 4.8570629e-03 9.8132331e-04 1.1267736e-03 1.9187302e-03 1.4320892e-03 2.5472569e-03 2.7129147e-03 1.2621760e-03 1.1868918e-03 3.1324421e-03 3.1260816e-03 3.4622842e-03 3.1324421e-03 7.8737454e-04 1.2923124e-03 7.7291736e-04 1.2676988e-02 1.5795155e-04 1.4073300e-03 1.3093851e-03 2.8558230e-03 2.3589004e-04 5.3160641e-04 6.3306680e-04 1.5563919e-03 6.9394652e-02 7.0160248e-02 7.9549278e-02 9.0909253e-02 8.3929778e-02 8.8133516e-02 7.5949213e-02 6.4094635e-02 7.7538115e-02 7.8838295e-02 8.6828513e-02 7.2078729e-02 8.6190925e-02 8.7328483e-02 5.6305232e-02 6.6307663e-02 8.6433769e-02 7.2861306e-02 1.0610432e-01 7.5977192e-02 9.0782134e-02 6.7147548e-02 1.0605640e-01 8.8295560e-02 7.0476640e-02 6.9912539e-02 8.5755505e-02 8.9909894e-02 8.3415192e-02 5.7694397e-02 7.7198547e-02 7.2551886e-02 6.8485682e-02 1.1174631e-01 9.0047290e-02 7.1258462e-02 7.5770197e-02 9.5276007e-02 6.9606963e-02 8.3332111e-02 9.3091350e-02 8.1019819e-02 7.5041473e-02 6.6748030e-02 8.2172293e-02 7.0413691e-02 7.4567733e-02 7.2221920e-02 5.0422561e-02 7.4234075e-02 1.3135838e-01 1.2029572e-01 1.1630277e-01 1.1941581e-01 1.2489530e-01 1.2871814e-01 1.2159882e-01 1.2460620e-01 1.3270425e-01 1.0925415e-01 9.4076611e-02 1.1596894e-01 1.0908894e-01 1.2799150e-01 1.2695158e-01 1.0694484e-01 1.0944639e-01 1.0895711e-01 1.5084375e-01 1.2591962e-01 1.1052596e-01 1.1587184e-01 1.3530738e-01 1.0320818e-01 1.0775506e-01 1.0806337e-01 9.8123191e-02 9.6541726e-02 1.2533326e-01 1.0616585e-01 1.2166800e-01 9.6181548e-02 1.2699662e-01 1.0216112e-01 1.2885603e-01 1.1626103e-01 1.1421827e-01 1.0807124e-01 9.4882428e-02 1.0171954e-01 1.1554226e-01 9.6763759e-02 1.2029572e-01 1.1801757e-01 1.1438908e-01 1.0525128e-01 1.1515210e-01 1.0301668e-01 1.0810316e-01 1.0676998e-01 2.4407151e-04 6.8243680e-04 1.6882982e-04 4.2217018e-04 8.1245396e-04 8.1915702e-04 2.7980568e-03 2.6783721e-03 2.0076713e-03 3.3526400e-04 9.3506008e-05 1.0407900e-03 7.3148476e-04 9.1895790e-04 4.8425923e-03 1.7878106e-03 2.5638304e-03 1.8092053e-03 6.2482332e-04 4.5470127e-05 3.8680919e-04 4.8577398e-04 7.0932539e-04 1.0773286e-03 2.7081281e-03 2.3916675e-03 6.8243680e-04 1.3234869e-03 1.5152295e-03 6.8243680e-04 1.7279927e-05 4.4719936e-05 7.6774714e-04 7.6386402e-03 5.1509749e-04 2.1386706e-03 2.3673979e-03 8.8641907e-04 8.8317423e-04 5.7646989e-05 1.8767975e-04 1.8238427e-04 6.4591491e-02 6.6891146e-02 7.4787553e-02 8.5653640e-02 7.8909235e-02 8.4481757e-02 7.3468926e-02 6.0165176e-02 7.2232139e-02 7.6459237e-02 8.0572670e-02 6.9287036e-02 7.8547451e-02 8.3338681e-02 5.3514192e-02 6.1787978e-02 8.4336540e-02 6.7840538e-02 9.9351761e-02 7.0839680e-02 8.9318727e-02 6.2598635e-02 1.0029777e-01 8.3444651e-02 6.5618944e-02 6.5228710e-02 7.9886645e-02 8.5622882e-02 7.9922508e-02 5.2526388e-02 7.1863670e-02 6.6948234e-02 6.3994975e-02 1.0763490e-01 8.8479248e-02 6.9931400e-02 7.1440370e-02 8.8224815e-02 6.7118281e-02 7.8968665e-02 8.8858891e-02 7.7432758e-02 7.0109240e-02 6.2023845e-02 7.8396402e-02 6.7393801e-02 7.1380489e-02 6.7813026e-02 4.6767795e-02 7.0645561e-02 1.3044475e-01 1.1734304e-01 1.1197394e-01 1.1577499e-01 1.2198760e-01 1.2346289e-01 1.1982320e-01 1.1899008e-01 1.2683842e-01 1.0736476e-01 9.1564623e-02 1.1164167e-01 1.0538958e-01 1.2475783e-01 1.2551509e-01 1.0524662e-01 1.0574315e-01 1.0607279e-01 1.4459461e-01 1.1962188e-01 1.0766800e-01 1.1407280e-01 1.2909426e-01 9.8905414e-02 1.0524346e-01 1.0359925e-01 9.4433579e-02 9.3820759e-02 1.2176744e-01 1.0065671e-01 1.1574436e-01 9.2625059e-02 1.2364363e-01 9.7538593e-02 1.2367543e-01 1.1121391e-01 1.1355049e-01 1.0493323e-01 9.2419908e-02 9.8167154e-02 1.1298864e-01 9.3668541e-02 1.1734304e-01 1.1533257e-01 1.1267510e-01 1.0222063e-01 1.1031800e-01 9.9779829e-02 1.0752614e-01 1.0448251e-01 3.8330702e-04 7.6710204e-04 5.4934344e-04 6.1141025e-04 1.8880070e-03 4.3782366e-03 4.2558302e-03 3.3445116e-03 9.0730658e-04 1.6460272e-04 1.9935351e-03 2.2277110e-04 1.5935452e-03 7.2001884e-03 1.0201171e-03 1.9163397e-03 8.7300929e-04 4.6754224e-04 3.6671499e-04 7.4258415e-04 2.1567602e-04 1.3361003e-04 9.1168360e-04 4.3156597e-03 4.1158943e-03 3.8330702e-04 1.9019978e-03 2.1146706e-03 3.8330702e-04 3.1982857e-04 2.1854146e-04 1.6719903e-03 5.9155088e-03 1.3110961e-03 2.0595508e-03 2.2774590e-03 5.2912957e-04 1.6598142e-03 4.0619000e-04 8.5702191e-04 4.6128261e-04 5.7335316e-02 5.9791552e-02 6.7034247e-02 7.7315388e-02 7.0912461e-02 7.6541197e-02 6.6231857e-02 5.3290914e-02 6.4524455e-02 6.9096848e-02 7.2330829e-02 6.2164647e-02 7.0266106e-02 7.5359485e-02 4.7211115e-02 5.4717217e-02 7.6724195e-02 6.0437574e-02 9.0217835e-02 6.3227153e-02 8.1624838e-02 5.5479636e-02 9.1272977e-02 7.5343817e-02 5.8299412e-02 5.7952393e-02 7.1727180e-02 7.7451339e-02 7.2165967e-02 4.5880845e-02 6.4164650e-02 5.9464600e-02 5.6817377e-02 9.8638775e-02 8.0838937e-02 6.3146715e-02 6.3916551e-02 7.9536591e-02 6.0201366e-02 7.1084400e-02 8.0631146e-02 6.9793274e-02 6.2554472e-02 5.4911579e-02 7.0667171e-02 6.0374722e-02 6.4102637e-02 6.0460719e-02 4.0707229e-02 6.3307106e-02 1.2135312e-01 1.0820155e-01 1.0273439e-01 1.0658023e-01 1.1268914e-01 1.1365695e-01 1.1088857e-01 1.0931070e-01 1.1680946e-01 9.8829208e-02 8.3503653e-02 1.0241354e-01 9.6518124e-02 1.1527856e-01 1.1644011e-01 9.6835948e-02 9.6892604e-02 9.7403729e-02 1.3389056e-01 1.0978147e-01 9.8889679e-02 1.0531384e-01 1.1893855e-01 9.0170315e-02 9.6654705e-02 9.4696682e-02 8.5999457e-02 8.5628213e-02 1.1232778e-01 9.1692367e-02 1.0609783e-01 8.4336563e-02 1.1417833e-01 8.8845800e-02 1.1399156e-01 1.0186496e-01 1.0510756e-01 9.6245658e-02 8.4341961e-02 8.9608741e-02 1.0408154e-01 8.5412047e-02 1.0820155e-01 1.0631639e-01 1.0398240e-01 9.3623884e-02 1.0104040e-01 9.1224725e-02 9.9332091e-02 9.5993921e-02 1.1067957e-03 1.4791390e-03 7.1256747e-05 2.0377231e-03 4.3755431e-03 5.9630791e-03 4.4970379e-03 1.5921641e-03 6.4984761e-04 3.3935862e-03 1.2039709e-04 3.0970780e-03 8.3950153e-03 2.1890332e-03 3.1326528e-03 5.0256002e-04 1.6389584e-03 6.4717383e-04 7.1019942e-04 8.9864077e-04 3.8255378e-04 1.2286350e-03 5.5229901e-03 5.1766813e-03 0.0000000e+00 1.5860612e-03 1.6773969e-03 0.0000000e+00 8.4337656e-04 4.6746407e-04 2.4549978e-03 4.7529836e-03 2.3235808e-03 4.0683267e-03 4.3260986e-03 6.7336618e-04 2.8454658e-03 1.0918918e-03 1.3756658e-03 5.7784546e-04 5.9573290e-02 6.3070670e-02 6.9597309e-02 7.9911457e-02 7.3480528e-02 7.9923883e-02 7.0144874e-02 5.5923876e-02 6.6635620e-02 7.3192589e-02 7.4096565e-02 6.5836734e-02 7.1022277e-02 7.8555696e-02 5.0423423e-02 5.7089619e-02 8.1093473e-02 6.2483167e-02 9.2251714e-02 6.5399221e-02 8.6573432e-02 5.7873871e-02 9.3861710e-02 7.7914479e-02 6.0545459e-02 6.0328179e-02 7.3725736e-02 8.0652769e-02 7.5662466e-02 4.7500835e-02 6.6267157e-02 6.1219907e-02 5.9246920e-02 1.0235525e-01 8.5584812e-02 6.7627185e-02 6.6676399e-02 8.1028522e-02 6.3874534e-02 7.4037098e-02 8.3735875e-02 7.3091049e-02 6.4875922e-02 5.7134332e-02 7.3898502e-02 6.3669341e-02 6.7483654e-02 6.3032151e-02 4.3195391e-02 6.6465430e-02 1.2757303e-01 1.1294171e-01 1.0654531e-01 1.1074736e-01 1.1756538e-01 1.1705185e-01 1.1633100e-01 1.1230305e-01 1.1988948e-01 1.0404710e-01 8.7981667e-02 1.0622547e-01 1.0061669e-01 1.2008497e-01 1.2242153e-01 1.0217012e-01 1.0084187e-01 1.0181343e-01 1.3714147e-01 1.1243585e-01 1.0356517e-01 1.1071692e-01 1.2181923e-01 9.3742899e-02 1.0137566e-01 9.8085445e-02 8.9840814e-02 8.9979544e-02 1.1682155e-01 9.4340727e-02 1.0893096e-01 8.8036209e-02 1.1887723e-01 9.1995894e-02 1.1718099e-01 1.0529554e-01 1.1115622e-01 1.0048991e-01 8.8823715e-02 9.3647258e-02 1.0909883e-01 8.9729548e-02 1.1294171e-01 1.1121254e-01 1.0947844e-01 9.8164553e-02 1.0458423e-01 9.5468337e-02 1.0529433e-01 1.0077315e-01 9.3075268e-04 1.0661545e-03 2.6597529e-04 1.6036733e-03 2.0280056e-03 1.2436972e-03 1.5688801e-04 3.1850165e-04 8.9411637e-04 1.3235015e-03 8.8731482e-04 3.4816593e-03 2.6719247e-03 3.9091992e-03 2.6159373e-03 1.1443019e-03 7.9601608e-05 2.3028989e-04 1.2135551e-03 1.4956336e-03 1.2137749e-03 2.2449952e-03 1.5932074e-03 1.1067957e-03 8.0827056e-04 9.5525701e-04 1.1067957e-03 1.2520454e-04 1.8684214e-04 3.3283736e-04 8.4659292e-03 4.3428627e-04 2.6431662e-03 3.3043825e-03 1.2192723e-03 9.6672170e-04 2.2673224e-04 4.0165289e-05 1.5556464e-04 7.0885985e-02 7.3312749e-02 8.1555170e-02 9.2789394e-02 8.5768022e-02 9.1811327e-02 8.0210428e-02 6.6299983e-02 7.8898236e-02 8.3277528e-02 8.7497229e-02 7.5768419e-02 8.5268176e-02 9.0575828e-02 5.9182538e-02 6.7899308e-02 9.1577973e-02 7.4436351e-02 1.0683101e-01 7.7459472e-02 9.6638219e-02 6.8724317e-02 1.0805306e-01 9.0746123e-02 7.1944393e-02 7.1498544e-02 8.6811878e-02 9.2796163e-02 8.6929388e-02 5.8156140e-02 7.8485798e-02 7.3355880e-02 7.0259533e-02 1.1577796e-01 9.5890635e-02 7.6480264e-02 7.8047377e-02 9.5335392e-02 7.3634293e-02 8.5899063e-02 9.6384778e-02 8.4415996e-02 7.6657094e-02 6.8177250e-02 8.5395861e-02 7.3990972e-02 7.8093423e-02 7.4294151e-02 5.1950910e-02 7.7279181e-02 1.3907744e-01 1.2568112e-01 1.2011323e-01 1.2420710e-01 1.3046004e-01 1.3207399e-01 1.2824918e-01 1.2752552e-01 1.3553911e-01 1.1524115e-01 9.8893820e-02 1.1975932e-01 1.1323008e-01 1.3322963e-01 1.3377253e-01 1.1295596e-01 1.1379378e-01 1.1416138e-01 1.5374990e-01 1.2806482e-01 1.1555054e-01 1.2219358e-01 1.3787989e-01 1.0651347e-01 1.1318026e-01 1.1161431e-01 1.0188137e-01 1.0132199e-01 1.3022140e-01 1.0855236e-01 1.2404638e-01 1.0022528e-01 1.3210134e-01 1.0532767e-01 1.3250558e-01 1.1917979e-01 1.2157791e-01 1.1297631e-01 9.9847302e-02 1.0571550e-01 1.2097128e-01 1.0080768e-01 1.2568112e-01 1.2354605e-01 1.2062969e-01 1.0973133e-01 1.1825900e-01 1.0742526e-01 1.1535080e-01 1.1244756e-01 1.9470856e-03 1.8498175e-03 4.7714250e-03 2.8358661e-03 3.0255426e-03 1.1308587e-03 6.7035566e-04 9.3284570e-04 1.3935241e-03 9.8369983e-04 5.6854836e-03 1.9144361e-03 1.0961099e-03 2.6770659e-03 6.7637792e-04 7.3922961e-04 1.6168588e-03 1.9795771e-04 8.8027763e-04 2.3819907e-03 2.3199642e-03 2.7913184e-03 1.4791390e-03 3.2257382e-03 3.5250868e-03 1.4791390e-03 4.9791374e-04 7.0216560e-04 1.6800207e-03 1.0022835e-02 5.5855445e-04 1.9786373e-03 9.4684044e-04 1.9956071e-03 4.5593799e-04 2.5049818e-04 7.2992180e-04 1.1563910e-03 6.0779252e-02 6.2273308e-02 7.0462169e-02 8.1326510e-02 7.4767830e-02 7.8734546e-02 6.8077240e-02 5.6059538e-02 6.8181020e-02 7.1050105e-02 7.6799016e-02 6.4482663e-02 7.5580358e-02 7.7962233e-02 4.9642606e-02 5.8153068e-02 7.8105114e-02 6.3436311e-02 9.5564553e-02 6.6739850e-02 8.2930379e-02 5.9008492e-02 9.5418848e-02 7.8187143e-02 6.1832470e-02 6.1508833e-02 7.5927040e-02 8.0793818e-02 7.4771898e-02 4.9618073e-02 6.7927507e-02 6.3289271e-02 6.0099335e-02 1.0140473e-01 8.1745663e-02 6.4187383e-02 6.7135505e-02 8.4768567e-02 6.1827019e-02 7.4354928e-02 8.3103529e-02 7.2159743e-02 6.6094709e-02 5.8361788e-02 7.3251937e-02 6.2103535e-02 6.6220430e-02 6.3584868e-02 4.3971154e-02 6.5863068e-02 1.2260074e-01 1.1066837e-01 1.0619606e-01 1.0899833e-01 1.1518894e-01 1.1739848e-01 1.1240635e-01 1.1296742e-01 1.2096568e-01 1.0086214e-01 8.5896751e-02 1.0590639e-01 9.9771313e-02 1.1830710e-01 1.1878365e-01 9.8989344e-02 9.9484141e-02 9.9300506e-02 1.3860781e-01 1.1421784e-01 1.0167360e-01 1.0723785e-01 1.2323222e-01 9.3791376e-02 9.8729091e-02 9.7617417e-02 8.9196630e-02 8.7906597e-02 1.1533851e-01 9.5241683e-02 1.1037299e-01 8.6684313e-02 1.1722334e-01 9.1866320e-02 1.1676032e-01 1.0620321e-01 1.0631345e-01 9.8352717e-02 8.6492752e-02 9.2837846e-02 1.0689111e-01 8.8947496e-02 1.1066837e-01 1.0877202e-01 1.0619549e-01 9.7005210e-02 1.0523294e-01 9.4129616e-02 1.0043708e-01 9.7689504e-02 1.8508011e-03 3.7513322e-03 6.0039368e-03 4.2304138e-03 1.5191600e-03 7.2789043e-04 3.6236504e-03 2.5132214e-04 3.2740913e-03 8.1034702e-03 2.4941139e-03 4.0964229e-03 6.0206143e-04 1.9190323e-03 6.6472571e-04 5.1664338e-04 1.3616103e-03 7.0613265e-04 1.0312088e-03 5.8211090e-03 5.1401914e-03 7.1256747e-05 1.1045080e-03 1.1556192e-03 7.1256747e-05 9.3818356e-04 5.1597856e-04 2.2957469e-03 4.3308939e-03 2.5276111e-03 4.3800580e-03 5.1684770e-03 5.8668191e-04 3.2395561e-03 1.2942225e-03 1.4104695e-03 4.9075437e-04 6.2060492e-02 6.5820549e-02 7.2324527e-02 8.2688153e-02 7.6151035e-02 8.3216525e-02 7.3194172e-02 5.8477367e-02 6.9270609e-02 7.6249005e-02 7.6681852e-02 6.8643243e-02 7.3331578e-02 8.1706941e-02 5.2792718e-02 5.9477668e-02 8.4501232e-02 6.5244243e-02 9.4903309e-02 6.8042766e-02 9.0018791e-02 6.0245936e-02 9.6930604e-02 8.1064051e-02 6.3026851e-02 6.2769846e-02 7.6368221e-02 8.3589775e-02 7.8680956e-02 4.9590571e-02 6.8853459e-02 6.3691512e-02 6.1750809e-02 1.0592213e-01 8.9183264e-02 7.0750492e-02 6.9363027e-02 8.3535040e-02 6.6868103e-02 7.6873580e-02 8.7073732e-02 7.6162997e-02 6.7469442e-02 5.9546534e-02 7.6928171e-02 6.6694450e-02 7.0471384e-02 6.5682900e-02 4.5133798e-02 6.9312951e-02 1.3167296e-01 1.1664541e-01 1.0993682e-01 1.1453374e-01 1.2132631e-01 1.2063781e-01 1.2028602e-01 1.1588513e-01 1.2343288e-01 1.0759677e-01 9.1184353e-02 1.0959799e-01 1.0389171e-01 1.2371908e-01 1.2606621e-01 1.0559841e-01 1.0439417e-01 1.0553794e-01 1.4077951e-01 1.1579171e-01 1.0695513e-01 1.1441431e-01 1.2538100e-01 9.6825962e-02 1.0496558e-01 1.0155971e-01 9.2933277e-02 9.3305204e-02 1.2046243e-01 9.7626604e-02 1.1224568e-01 9.1421248e-02 1.2250398e-01 9.5336276e-02 1.2112681e-01 1.0839632e-01 1.1495562e-01 1.0414545e-01 9.2136906e-02 9.6777242e-02 1.1252211e-01 9.2559606e-02 1.1664541e-01 1.1484781e-01 1.1301462e-01 1.0121871e-01 1.0770332e-01 9.8720694e-02 1.0901533e-01 1.0446815e-01 7.8277898e-04 1.7490530e-03 1.0345024e-03 5.6185312e-04 1.1591486e-03 1.1405764e-03 2.5549089e-03 1.4484284e-03 2.2580494e-03 4.5713265e-03 5.6870335e-03 4.1902203e-03 2.4320876e-03 6.0369458e-04 6.2286369e-04 2.4521502e-03 2.9038905e-03 2.2436415e-03 1.7675525e-03 9.5896000e-04 2.0377231e-03 8.9090360e-04 9.7827632e-04 2.0377231e-03 7.4516940e-04 8.4824201e-04 4.3724648e-04 1.0582513e-02 7.1366344e-04 4.1221085e-03 4.7945036e-03 2.3833891e-03 1.3170043e-03 8.5049004e-04 2.9093352e-04 6.7142903e-04 7.9558936e-02 8.2158081e-02 9.0820201e-02 1.0264403e-01 9.5277746e-02 1.0150997e-01 8.9387659e-02 7.4718776e-02 8.7974881e-02 9.2637642e-02 9.6993873e-02 8.4761019e-02 9.4485044e-02 1.0025701e-01 6.7224195e-02 7.6433848e-02 1.0126400e-01 8.3185627e-02 1.1729605e-01 8.6461029e-02 1.0658954e-01 7.7314215e-02 1.1857784e-01 1.0034666e-01 8.0683042e-02 8.0237049e-02 9.6300382e-02 1.0267579e-01 9.6489769e-02 6.6032956e-02 8.7552344e-02 8.2099586e-02 7.8915667e-02 1.2662250e-01 1.0571935e-01 8.5387558e-02 8.7148938e-02 1.0518820e-01 8.2417845e-02 9.5416475e-02 1.0627769e-01 9.3794810e-02 8.5650752e-02 7.6704239e-02 9.4843643e-02 8.2753514e-02 8.7142105e-02 8.3165683e-02 5.9528971e-02 8.6320416e-02 1.5080041e-01 1.3699340e-01 1.3123407e-01 1.3538631e-01 1.4196949e-01 1.4361623e-01 1.3957348e-01 1.3881335e-01 1.4720194e-01 1.2611020e-01 1.0906565e-01 1.3086970e-01 1.2408176e-01 1.4489915e-01 1.4541581e-01 1.2374159e-01 1.2456922e-01 1.2489938e-01 1.6613145e-01 1.3940980e-01 1.2649129e-01 1.3333884e-01 1.4960338e-01 1.1706680e-01 1.2394226e-01 1.2226369e-01 1.1222236e-01 1.1158045e-01 1.4175176e-01 1.1903032e-01 1.3525827e-01 1.1036833e-01 1.4372294e-01 1.1569638e-01 1.4384978e-01 1.3029466e-01 1.3261278e-01 1.2368173e-01 1.1003418e-01 1.1624174e-01 1.3214763e-01 1.1113124e-01 1.3699340e-01 1.3478604e-01 1.3174332e-01 1.2045775e-01 1.2933910e-01 1.1801062e-01 1.2611372e-01 1.2312584e-01 2.4038804e-03 9.9356679e-04 1.6379146e-03 2.9968879e-03 2.7777132e-03 5.0292552e-03 2.9485139e-03 1.7810659e-03 7.1187929e-03 1.0385224e-02 6.8192179e-03 4.7007047e-03 2.2536066e-03 1.6978265e-03 5.5995030e-03 5.8830752e-03 3.3086218e-03 3.5101479e-03 1.6089784e-03 4.3755431e-03 1.0574938e-03 1.0592785e-03 4.3755431e-03 2.5472554e-03 2.6641717e-03 1.0704333e-03 1.2070572e-02 2.4953874e-03 6.0785955e-03 8.5181088e-03 3.9627930e-03 3.6619699e-03 2.9233174e-03 1.7757672e-03 2.0595965e-03 9.0904959e-02 9.3846404e-02 1.0296173e-01 1.1510418e-01 1.0729700e-01 1.1521099e-01 1.0181822e-01 8.5988795e-02 1.0000742e-01 1.0504161e-01 1.0917671e-01 9.6459196e-02 1.0622808e-01 1.1358449e-01 7.7434068e-02 8.7329703e-02 1.1478646e-01 9.5582611e-02 1.2982843e-01 9.8465917e-02 1.1998512e-01 8.8160180e-02 1.3221652e-01 1.1399918e-01 9.2024225e-02 9.1371105e-02 1.0854159e-01 1.1533865e-01 1.0916600e-01 7.6158175e-02 9.9423258e-02 9.3687045e-02 9.0204262e-02 1.4138542e-01 1.1970815e-01 9.7663366e-02 9.8994798e-02 1.1736337e-01 9.4681982e-02 1.0777767e-01 1.2034441e-01 1.0671033e-01 9.7400706e-02 8.7763928e-02 1.0767516e-01 9.5332473e-02 9.9630913e-02 9.4930517e-02 6.8563663e-02 9.8462875e-02 1.6617515e-01 1.5176730e-01 1.4543395e-01 1.5072522e-01 1.5691405e-01 1.5882414e-01 1.5479314e-01 1.5416358e-01 1.6247914e-01 1.3996762e-01 1.2197918e-01 1.4500225e-01 1.3765005e-01 1.5950314e-01 1.5937612e-01 1.3710080e-01 1.3913130e-01 1.3976637e-01 1.8183098e-01 1.5420823e-01 1.4014252e-01 1.4766552e-01 1.6507182e-01 1.3020370e-01 1.3818766e-01 1.3684571e-01 1.2517707e-01 1.2500429e-01 1.5651711e-01 1.3335007e-01 1.4978001e-01 1.2432862e-01 1.5834992e-01 1.2988375e-01 1.6032290e-01 1.4373178e-01 1.4688056e-01 1.3840222e-01 1.2332107e-01 1.2926358e-01 1.4578293e-01 1.2292671e-01 1.5176730e-01 1.4922075e-01 1.4546636e-01 1.3299550e-01 1.4276363e-01 1.3134387e-01 1.4008961e-01 1.3766630e-01 5.7728358e-04 1.6620505e-03 3.0662753e-03 5.1693417e-04 6.0968463e-03 8.4744633e-04 8.7364721e-04 5.8106642e-03 6.7399476e-03 8.6083103e-03 3.1702748e-03 2.7104978e-03 3.3164143e-03 4.2509190e-03 5.8084215e-03 4.6709776e-03 9.8526568e-04 3.6786909e-04 5.9630791e-03 3.9566796e-03 4.2657824e-03 5.9630791e-03 2.3876668e-03 3.1383576e-03 1.0693622e-03 1.7187016e-02 9.8571152e-04 3.1367899e-03 3.7448988e-03 5.3108404e-03 1.2637202e-03 2.1359423e-03 1.6418787e-03 3.1352541e-03 8.3834616e-02 8.4243676e-02 9.4832859e-02 1.0703873e-01 9.9466934e-02 1.0403376e-01 9.0316788e-02 7.7914871e-02 9.2850027e-02 9.3299365e-02 1.0296391e-01 8.6082003e-02 1.0248600e-01 1.0318273e-01 6.8824902e-02 8.0299100e-02 1.0157055e-01 8.7955506e-02 1.2341155e-01 9.1142100e-02 1.0580332e-01 8.1173197e-02 1.2350931e-01 1.0460831e-01 8.4991511e-02 8.4254332e-02 1.0174542e-01 1.0573929e-01 9.8648070e-02 7.1061471e-02 9.2438015e-02 8.7508516e-02 8.2750270e-02 1.2928539e-01 1.0529438e-01 8.4835976e-02 9.0594121e-02 1.1204138e-01 8.3577196e-02 9.8754999e-02 1.0957368e-01 9.6258171e-02 8.9990776e-02 8.0898906e-02 9.7508458e-02 8.4744426e-02 8.9161769e-02 8.6853262e-02 6.2377571e-02 8.8830405e-02 1.4853162e-01 1.3772455e-01 1.3389458e-01 1.3729951e-01 1.4254710e-01 1.4753124e-01 1.3874272e-01 1.4343376e-01 1.5191156e-01 1.2542851e-01 1.0950060e-01 1.3351960e-01 1.2589032e-01 1.4575308e-01 1.4361388e-01 1.2273259e-01 1.2665814e-01 1.2593062e-01 1.7100462e-01 1.4482556e-01 1.2707776e-01 1.3245591e-01 1.5480441e-01 1.1982038e-01 1.2429326e-01 1.2550864e-01 1.1421155e-01 1.1236837e-01 1.4320023e-01 1.2379599e-01 1.4017288e-01 1.1252737e-01 1.4478118e-01 1.1925773e-01 1.4807486e-01 1.3379535e-01 1.3019772e-01 1.2505772e-01 1.1047444e-01 1.1793234e-01 1.3214851e-01 1.1203286e-01 1.3772455e-01 1.3511133e-01 1.3062456e-01 1.2117415e-01 1.3256036e-01 1.1928977e-01 1.2368263e-01 1.2328316e-01 7.8697050e-04 2.0732289e-03 9.4315564e-04 4.6001401e-03 8.4240364e-04 1.3015708e-03 4.6297460e-03 7.5292997e-03 6.5572401e-03 2.5566943e-03 1.7941741e-03 1.8226799e-03 3.9920133e-03 4.8070278e-03 2.6490734e-03 2.2737423e-03 8.3198965e-04 4.4970379e-03 1.8707122e-03 2.0801746e-03 4.4970379e-03 1.6864362e-03 2.1518496e-03 3.0908897e-04 1.2802000e-02 1.1444166e-03 2.7605116e-03 4.8428825e-03 3.3840997e-03 1.9469936e-03 1.7958172e-03 1.1565833e-03 1.8697862e-03 8.2127567e-02 8.3518119e-02 9.3314949e-02 1.0506408e-01 9.7541782e-02 1.0397584e-01 9.0341739e-02 7.6817337e-02 9.1083710e-02 9.3231274e-02 1.0048418e-01 8.5524503e-02 9.9112893e-02 1.0267345e-01 6.7846626e-02 7.8515797e-02 1.0225403e-01 8.6849217e-02 1.2022955e-01 8.9502113e-02 1.0655817e-01 7.9297559e-02 1.2165144e-01 1.0391861e-01 8.3203942e-02 8.2410584e-02 9.9528824e-02 1.0443382e-01 9.8019721e-02 6.8818975e-02 9.0543610e-02 8.5485551e-02 8.1186134e-02 1.2894588e-01 1.0651601e-01 8.5580315e-02 8.9213768e-02 1.0886353e-01 8.3754339e-02 9.7441118e-02 1.0932583e-01 9.5882843e-02 8.8282134e-02 7.9128322e-02 9.6917266e-02 8.4873719e-02 8.8927957e-02 8.5532654e-02 6.0384203e-02 8.8123284e-02 1.4980593e-01 1.3770759e-01 1.3282368e-01 1.3741111e-01 1.4254251e-01 1.4639384e-01 1.3970199e-01 1.4238069e-01 1.5040197e-01 1.2563911e-01 1.0916002e-01 1.3240724e-01 1.2489275e-01 1.4520334e-01 1.4360899e-01 1.2274102e-01 1.2644577e-01 1.2642535e-01 1.6908994e-01 1.4294672e-01 1.2654652e-01 1.3286867e-01 1.5320101e-01 1.1837980e-01 1.2451917e-01 1.2497748e-01 1.1312705e-01 1.1222677e-01 1.4268257e-01 1.2261254e-01 1.3839073e-01 1.1239899e-01 1.4421566e-01 1.1854547e-01 1.4805458e-01 1.3176737e-01 1.3127552e-01 1.2532607e-01 1.1042618e-01 1.1684289e-01 1.3160924e-01 1.1043724e-01 1.3770759e-01 1.3504379e-01 1.3066174e-01 1.1987723e-01 1.3066578e-01 1.1856367e-01 1.2478710e-01 1.2391087e-01 3.0983409e-04 7.5828890e-04 1.5917755e-03 4.8958816e-04 3.3744944e-03 2.1101097e-03 4.2400870e-03 2.8698866e-03 7.9583168e-04 2.4610899e-04 3.6436132e-04 1.4311801e-03 1.7294514e-03 8.6738167e-04 2.6111809e-03 1.6704106e-03 1.5921641e-03 8.6161593e-04 1.0547029e-03 1.5921641e-03 2.0427868e-04 3.5845705e-04 1.2194863e-04 8.2981219e-03 4.9180195e-04 1.7380522e-03 3.0734607e-03 1.0728608e-03 1.1397310e-03 3.4603128e-04 1.9200118e-04 2.8845040e-04 6.9779438e-02 7.1836392e-02 8.0319868e-02 9.1354890e-02 8.4353236e-02 9.0635736e-02 7.8599311e-02 6.5140986e-02 7.7899100e-02 8.1486701e-02 8.6472565e-02 7.4062042e-02 8.4619349e-02 8.9336326e-02 5.7575298e-02 6.6635212e-02 8.9946961e-02 7.3779929e-02 1.0532587e-01 7.6464940e-02 9.4587006e-02 6.7402630e-02 1.0673169e-01 8.9925800e-02 7.0797999e-02 7.0219100e-02 8.5721997e-02 9.1187854e-02 8.5377890e-02 5.7235173e-02 7.7431768e-02 7.2498793e-02 6.9063158e-02 1.1428266e-01 9.4218471e-02 7.4725647e-02 7.6703845e-02 9.4228329e-02 7.2261001e-02 8.4462132e-02 9.5360997e-02 8.3133678e-02 7.5506963e-02 6.7041127e-02 8.4070372e-02 7.2903149e-02 7.6784460e-02 7.3115143e-02 5.0413571e-02 7.5926129e-02 1.3636539e-01 1.2353657e-01 1.1821263e-01 1.2258303e-01 1.2822457e-01 1.3051322e-01 1.2599517e-01 1.2631512e-01 1.3406707e-01 1.1278003e-01 9.6722933e-02 1.1783731e-01 1.1110860e-01 1.3080317e-01 1.3063996e-01 1.1029647e-01 1.1216341e-01 1.1248813e-01 1.5199693e-01 1.2674108e-01 1.1318561e-01 1.1969790e-01 1.3653036e-01 1.0458853e-01 1.1112582e-01 1.1028100e-01 9.9890110e-02 9.9356661e-02 1.2805612e-01 1.0750038e-01 1.2261289e-01 9.8791075e-02 1.2975191e-01 1.0408196e-01 1.3162969e-01 1.1713290e-01 1.1886116e-01 1.1132823e-01 9.7814075e-02 1.0357451e-01 1.1833994e-01 9.8179977e-02 1.2353657e-01 1.2124412e-01 1.1787602e-01 1.0709455e-01 1.1618054e-01 1.0529428e-01 1.1269702e-01 1.1053049e-01 1.3650135e-03 5.3926014e-04 9.6875216e-04 5.5085642e-03 1.1951469e-03 2.8096772e-03 1.4033998e-03 3.8702395e-04 1.0970323e-04 3.3218009e-04 5.6326785e-04 5.8024795e-04 5.6723773e-04 3.5935032e-03 3.0059920e-03 6.4984761e-04 1.1677062e-03 1.3673782e-03 6.4984761e-04 7.6378345e-05 6.3488092e-05 8.1586688e-04 6.3954323e-03 8.4458294e-04 1.6959745e-03 2.5316364e-03 4.4648839e-04 1.3649198e-03 2.1646092e-04 4.0910219e-04 1.5323026e-04 6.2114649e-02 6.4461203e-02 7.2168083e-02 8.2712554e-02 7.6067484e-02 8.2127836e-02 7.1085856e-02 5.7869953e-02 6.9689694e-02 7.3941980e-02 7.7755077e-02 6.6772898e-02 7.5730146e-02 8.0858032e-02 5.1159438e-02 5.9263906e-02 8.1982206e-02 6.5662191e-02 9.5957099e-02 6.8346124e-02 8.6755825e-02 6.0017643e-02 9.7272303e-02 8.1103209e-02 6.3092478e-02 6.2637310e-02 7.7107544e-02 8.2765719e-02 7.7320565e-02 5.0179546e-02 6.9272103e-02 6.4477670e-02 6.1520691e-02 1.0482403e-01 8.6201836e-02 6.7723532e-02 6.8849001e-02 8.5128110e-02 6.4954612e-02 7.6260237e-02 8.6474261e-02 7.5037042e-02 6.7540885e-02 5.9554295e-02 7.5917230e-02 6.5334858e-02 6.9084098e-02 6.5352935e-02 4.4308417e-02 6.8222624e-02 1.2733314e-01 1.1424612e-01 1.0876932e-01 1.1294180e-01 1.1881213e-01 1.2027633e-01 1.1690957e-01 1.1601884e-01 1.2357066e-01 1.0430113e-01 8.8647048e-02 1.0842142e-01 1.0217893e-01 1.2134270e-01 1.2195833e-01 1.0207761e-01 1.0292571e-01 1.0341320e-01 1.4095409e-01 1.1639921e-01 1.0445121e-01 1.1097871e-01 1.2583780e-01 9.5736690e-02 1.0236688e-01 1.0085185e-01 9.1372067e-02 9.1009848e-02 1.1849408e-01 9.7905297e-02 1.1253291e-01 9.0050809e-02 1.2026601e-01 9.4848068e-02 1.2106318e-01 1.0772883e-01 1.1055160e-01 1.0223795e-01 8.9621067e-02 9.5003079e-02 1.0961118e-01 9.0242843e-02 1.1424612e-01 1.1217935e-01 1.0939970e-01 9.8767943e-02 1.0686010e-01 9.6691216e-02 1.0462140e-01 1.0177309e-01 3.4212913e-03 2.0350185e-04 2.2645835e-03 3.4346676e-03 3.6178579e-03 5.4115677e-03 1.4013939e-03 1.2010586e-03 1.9342083e-03 1.8303919e-03 3.0241355e-03 3.0182648e-03 8.7783530e-04 7.3200159e-04 3.3935862e-03 3.0016113e-03 3.3110105e-03 3.3935862e-03 9.0468402e-04 1.4291912e-03 6.5529771e-04 1.3482371e-02 1.1883350e-04 1.9129351e-03 1.8189821e-03 3.2224845e-03 2.2840556e-04 6.5009173e-04 5.8224397e-04 1.6275063e-03 7.3184911e-02 7.4026716e-02 8.3609924e-02 9.5240271e-02 8.8102740e-02 9.2390529e-02 7.9966102e-02 6.7767341e-02 8.1513618e-02 8.2938634e-02 9.0997666e-02 7.6011769e-02 9.0234918e-02 9.1578032e-02 5.9804069e-02 7.0034281e-02 9.0682677e-02 7.6686753e-02 1.1071727e-01 7.9918092e-02 9.5151478e-02 7.0899518e-02 1.1069472e-01 9.2509702e-02 7.4297007e-02 7.3732579e-02 8.9920224e-02 9.4250688e-02 8.7608739e-02 6.1121885e-02 8.1169501e-02 7.6375843e-02 7.2267735e-02 1.1652820e-01 9.4360734e-02 7.5150369e-02 7.9755441e-02 9.9609504e-02 7.3443983e-02 8.7507436e-02 9.7438140e-02 8.5130511e-02 7.8978434e-02 7.0471516e-02 8.6314807e-02 7.4241923e-02 7.8526940e-02 7.6102189e-02 5.3711374e-02 7.8190521e-02 1.3653802e-01 1.2528995e-01 1.2121146e-01 1.2435019e-01 1.2997976e-01 1.3382130e-01 1.2659694e-01 1.2959353e-01 1.3786369e-01 1.1404399e-01 9.8548323e-02 1.2087284e-01 1.1387348e-01 1.3314978e-01 1.3209550e-01 1.1169631e-01 1.1419600e-01 1.1368838e-01 1.5633317e-01 1.3093427e-01 1.1535002e-01 1.2078800e-01 1.4049523e-01 1.0785716e-01 1.1249675e-01 1.1275652e-01 1.0267406e-01 1.0105332e-01 1.3042827e-01 1.1078233e-01 1.2662105e-01 1.0064162e-01 1.3213313e-01 1.0672611e-01 1.3386868e-01 1.2116827e-01 1.1908244e-01 1.1278797e-01 9.9360911e-02 1.0635497e-01 1.2047378e-01 1.0130606e-01 1.2528995e-01 1.2297832e-01 1.1929072e-01 1.0997770e-01 1.2004306e-01 1.0767864e-01 1.1284273e-01 1.1147304e-01 2.8709378e-03 9.0791333e-03 1.3407674e-03 2.7138923e-03 2.4677711e-04 1.1444972e-03 7.6121265e-04 8.8810957e-04 7.0009018e-04 1.4757411e-04 9.0393445e-04 6.0883361e-03 5.6961877e-03 1.2039709e-04 1.8816976e-03 2.0265121e-03 1.2039709e-04 8.6239061e-04 5.2686780e-04 2.5538294e-03 4.1367540e-03 2.4461852e-03 3.1968429e-03 3.7345867e-03 3.8813913e-04 2.9749715e-03 1.1153685e-03 1.5845430e-03 6.9221070e-04 5.5493017e-02 5.8687008e-02 6.5182775e-02 7.5153394e-02 6.8882837e-02 7.5289957e-02 6.5516107e-02 5.1904720e-02 6.2434251e-02 6.8395322e-02 6.9725101e-02 6.1264247e-02 6.7000659e-02 7.3915045e-02 4.6337454e-02 5.2993228e-02 7.6203506e-02 5.8574285e-02 8.7235787e-02 6.1228310e-02 8.1349394e-02 5.3727422e-02 8.8860415e-02 7.3529860e-02 5.6419133e-02 5.6136434e-02 6.9314277e-02 7.5768587e-02 7.0921131e-02 4.3887190e-02 6.2047408e-02 5.7247874e-02 5.5122668e-02 9.7063471e-02 8.0589157e-02 6.3015465e-02 6.2273448e-02 7.6485434e-02 5.9534434e-02 6.9400424e-02 7.9100961e-02 6.8556756e-02 6.0632063e-02 5.3105900e-02 6.9320431e-02 5.9485747e-02 6.3075829e-02 5.8812039e-02 3.9389619e-02 6.2057390e-02 1.2120786e-01 1.0709958e-01 1.0095433e-01 1.0522389e-01 1.1158919e-01 1.1144992e-01 1.1038844e-01 1.0698853e-01 1.1429410e-01 9.8230586e-02 8.2643326e-02 1.0062987e-01 9.5029365e-02 1.1396256e-01 1.1592889e-01 9.6297509e-02 9.5506831e-02 9.6444702e-02 1.3110088e-01 1.0707131e-01 9.7795782e-02 1.0475251e-01 1.1626300e-01 8.8405248e-02 9.5813058e-02 9.2970070e-02 8.4549021e-02 8.4699662e-02 1.1089247e-01 8.9470273e-02 1.0356569e-01 8.3077190e-02 1.1281589e-01 8.7056416e-02 1.1197016e-01 9.9670877e-02 1.0510107e-01 9.5157512e-02 8.3537128e-02 8.8197538e-02 1.0308982e-01 8.4141549e-02 1.0709958e-01 1.0532428e-01 1.0341164e-01 9.2382015e-02 9.8953568e-02 8.9983057e-02 9.9390435e-02 9.5301345e-02 3.0771633e-03 2.2394953e-03 3.5785600e-03 4.5455517e-03 7.4728021e-04 1.0553655e-03 1.6471751e-03 1.6255623e-03 2.5337657e-03 2.0402330e-03 1.9207121e-03 1.4230439e-03 3.0970780e-03 2.6131629e-03 2.9423829e-03 3.0970780e-03 7.3620931e-04 1.2096052e-03 5.1210113e-04 1.1445573e-02 3.3743120e-04 9.3762253e-04 1.6709589e-03 2.3328362e-03 6.4367426e-04 6.1292579e-04 6.6980522e-04 1.3596425e-03 6.8771058e-02 6.9631829e-02 7.8939123e-02 9.0076585e-02 8.3106206e-02 8.8021023e-02 7.5587788e-02 6.3613086e-02 7.6977857e-02 7.8355410e-02 8.6058485e-02 7.1477483e-02 8.5350309e-02 8.7042060e-02 5.5585563e-02 6.5564323e-02 8.6285657e-02 7.2679829e-02 1.0489589e-01 7.5454083e-02 9.0429367e-02 6.6346194e-02 1.0535674e-01 8.8189227e-02 6.9809235e-02 6.9152322e-02 8.5027647e-02 8.9182145e-02 8.2908995e-02 5.6974544e-02 7.6567724e-02 7.1978204e-02 6.7853368e-02 1.1142320e-01 9.0045691e-02 7.1019587e-02 7.5131918e-02 9.4265212e-02 6.9407495e-02 8.2681448e-02 9.3017444e-02 8.0734547e-02 7.4408554e-02 6.6081064e-02 8.1800498e-02 7.0361312e-02 7.4293705e-02 7.1683996e-02 4.9412056e-02 7.3791248e-02 1.3087381e-01 1.1972349e-01 1.1554097e-01 1.1917243e-01 1.2428472e-01 1.2815053e-01 1.2126029e-01 1.2425112e-01 1.3208038e-01 1.0854852e-01 9.3334993e-02 1.1518188e-01 1.0821021e-01 1.2711496e-01 1.2583624e-01 1.0606084e-01 1.0908141e-01 1.0877992e-01 1.4997249e-01 1.2525272e-01 1.0965413e-01 1.1522136e-01 1.3472784e-01 1.0229252e-01 1.0728052e-01 1.0776484e-01 9.7267064e-02 9.5981774e-02 1.2460839e-01 1.0582154e-01 1.2096134e-01 9.5922459e-02 1.2615683e-01 1.0184377e-01 1.2900688e-01 1.1512576e-01 1.1363761e-01 1.0783825e-01 9.4308496e-02 1.0078579e-01 1.1452781e-01 9.5389301e-02 1.1972349e-01 1.1733679e-01 1.1347632e-01 1.0398208e-01 1.1403752e-01 1.0220157e-01 1.0755302e-01 1.0649118e-01 1.0147620e-02 1.1247381e-02 1.2064168e-02 6.5374061e-03 4.5897505e-03 4.8228518e-03 7.5563629e-03 9.2308338e-03 7.2321889e-03 1.6011806e-03 5.3396772e-04 8.3950153e-03 4.7739373e-03 4.9472694e-03 8.3950153e-03 4.4998786e-03 5.2439437e-03 2.2809748e-03 2.1071898e-02 2.7056396e-03 6.9610435e-03 7.8780829e-03 8.1126375e-03 3.2257880e-03 4.3393216e-03 3.1425928e-03 4.9174647e-03 1.0007131e-01 1.0106593e-01 1.1217080e-01 1.2532148e-01 1.1715442e-01 1.2248393e-01 1.0792073e-01 9.3860583e-02 1.0976919e-01 1.1122163e-01 1.2044721e-01 1.0322968e-01 1.1921347e-01 1.2149913e-01 8.4186074e-02 9.6291408e-02 1.2023049e-01 1.0442285e-01 1.4247402e-01 1.0795998e-01 1.2499239e-01 9.7246530e-02 1.4298267e-01 1.2269113e-01 1.0132833e-01 1.0059203e-01 1.1928692e-01 1.2425009e-01 1.1675840e-01 8.5744895e-02 1.0931412e-01 1.0376332e-01 9.9002718e-02 1.4971116e-01 1.2434000e-01 1.0215677e-01 1.0769100e-01 1.2998610e-01 1.0050022e-01 1.1660891e-01 1.2830083e-01 1.1408155e-01 1.0679553e-01 9.6866698e-02 1.1540492e-01 1.0158690e-01 1.0644304e-01 1.0354221e-01 7.6665035e-02 1.0598765e-01 1.7102185e-01 1.5912057e-01 1.5467066e-01 1.5843570e-01 1.6430112e-01 1.6898961e-01 1.6040927e-01 1.6442765e-01 1.7347908e-01 1.4613872e-01 1.2882261e-01 1.5426961e-01 1.4623773e-01 1.6767510e-01 1.6569930e-01 1.4326884e-01 1.4700928e-01 1.4639344e-01 1.9375995e-01 1.6572948e-01 1.4772209e-01 1.5370280e-01 1.7644069e-01 1.3951457e-01 1.4477511e-01 1.4552738e-01 1.3362833e-01 1.3186831e-01 1.6485780e-01 1.4332228e-01 1.6088049e-01 1.3176893e-01 1.6660706e-01 1.3873049e-01 1.6938110e-01 1.5434218e-01 1.5143663e-01 1.4540553e-01 1.2987912e-01 1.3768917e-01 1.5323248e-01 1.3136310e-01 1.5912057e-01 1.5638588e-01 1.5175254e-01 1.4128505e-01 1.5308268e-01 1.3923812e-01 1.4444485e-01 1.4370095e-01 2.6770864e-03 1.6033718e-03 4.5840441e-04 2.0276877e-03 2.4561050e-03 1.2787767e-03 1.0310968e-03 1.1138996e-03 7.3996134e-03 6.8122401e-03 2.1890332e-03 3.7576667e-03 4.1081994e-03 2.1890332e-03 1.7366871e-03 1.7496571e-03 3.0804223e-03 5.2624414e-03 3.0566387e-03 8.7139687e-04 2.3320271e-03 9.5854356e-04 3.5582665e-03 1.9033529e-03 2.7781156e-03 2.0582238e-03 4.8466175e-02 5.0119607e-02 5.7333122e-02 6.6635612e-02 6.0625611e-02 6.6546613e-02 5.6002103e-02 4.4610205e-02 5.5428894e-02 5.8355341e-02 6.2731565e-02 5.1936578e-02 6.1589720e-02 6.5254921e-02 3.8121898e-02 4.5707178e-02 6.5954438e-02 5.2310619e-02 7.8737277e-02 5.4221486e-02 6.9833666e-02 4.6306978e-02 8.0104533e-02 6.6061691e-02 4.9289042e-02 4.8703396e-02 6.2029842e-02 6.6459215e-02 6.1626890e-02 3.8139914e-02 5.4977441e-02 5.0955269e-02 4.7810568e-02 8.6879401e-02 6.9864176e-02 5.2960539e-02 5.4195866e-02 6.9397405e-02 5.0805294e-02 6.0771100e-02 7.0699543e-02 5.9928918e-02 5.3277849e-02 4.6137567e-02 6.0647902e-02 5.1507184e-02 5.4535520e-02 5.1271651e-02 3.2182295e-02 5.3653922e-02 1.0655841e-01 9.4809862e-02 8.9943103e-02 9.4257768e-02 9.8930460e-02 1.0102907e-01 9.7318001e-02 9.7573045e-02 1.0420091e-01 8.5339961e-02 7.1237738e-02 8.9593643e-02 8.3615246e-02 1.0099568e-01 1.0093948e-01 8.3072610e-02 8.4968711e-02 8.5464008e-02 1.2001752e-01 9.7755169e-02 8.5475001e-02 9.1482164e-02 1.0648093e-01 7.7912530e-02 8.4002788e-02 8.3421922e-02 7.3855173e-02 7.3645673e-02 9.8656580e-02 8.1073912e-02 9.4018658e-02 7.3420060e-02 1.0008227e-01 7.8012093e-02 1.0282145e-01 8.8787179e-02 9.1014600e-02 8.4377747e-02 7.2315249e-02 7.7005472e-02 8.9946277e-02 7.2132565e-02 9.4809862e-02 9.2717682e-02 8.9711332e-02 7.9921256e-02 8.7947099e-02 7.8589830e-02 8.5634568e-02 8.3685774e-02 3.6322977e-03 2.1046334e-03 3.2662345e-03 4.7576391e-03 8.8436254e-04 1.6169936e-03 5.0909036e-03 5.3937261e-03 6.9592372e-03 3.1326528e-03 7.3963134e-03 7.8155750e-03 3.1326528e-03 2.8162695e-03 2.9888005e-03 5.3841195e-03 1.1640832e-02 3.1058623e-03 3.5268449e-03 8.9518404e-04 4.1579453e-03 2.4418843e-03 2.3174283e-03 3.5804624e-03 3.9289766e-03 4.8779386e-02 4.9807262e-02 5.7295117e-02 6.7429274e-02 6.1528729e-02 6.3805672e-02 5.4672352e-02 4.4305273e-02 5.5263563e-02 5.7527840e-02 6.3439356e-02 5.1886311e-02 6.2844358e-02 6.3392021e-02 3.9105833e-02 4.6659036e-02 6.3315061e-02 5.0431450e-02 8.1151197e-02 5.3901750e-02 6.8036856e-02 4.7518043e-02 7.9948453e-02 6.3397617e-02 4.9788208e-02 4.9648688e-02 6.2516012e-02 6.6678586e-02 6.0874148e-02 3.9322135e-02 5.5164136e-02 5.1029588e-02 4.8159012e-02 8.4641068e-02 6.6410487e-02 5.1100232e-02 5.4352465e-02 7.1152366e-02 4.8870866e-02 6.0790663e-02 6.7705030e-02 5.8185400e-02 5.3489810e-02 4.6729049e-02 5.9304859e-02 4.8888831e-02 5.2875044e-02 5.1050585e-02 3.4854587e-02 5.2844524e-02 1.0451993e-01 9.3506986e-02 8.9732041e-02 9.1442484e-02 9.7717353e-02 9.9707944e-02 9.4802947e-02 9.5351404e-02 1.0312713e-01 8.4850971e-02 7.1294232e-02 8.9511382e-02 8.4075688e-02 1.0102482e-01 1.0205801e-01 8.3487981e-02 8.2948462e-02 8.2499658e-02 1.1982484e-01 9.7067919e-02 8.5815629e-02 9.0584285e-02 1.0517734e-01 7.8728615e-02 8.2465362e-02 8.1175179e-02 7.4451161e-02 7.2780961e-02 9.8027119e-02 7.9186352e-02 9.3572539e-02 7.1178913e-02 9.9960717e-02 7.6001190e-02 9.8069469e-02 9.0444802e-02 8.9768862e-02 8.1715688e-02 7.1540326e-02 7.7885383e-02 9.0855680e-02 7.5245252e-02 9.3506986e-02 9.1968167e-02 9.0109483e-02 8.2320804e-02 8.9509517e-02 7.8843854e-02 8.4366358e-02 8.1217849e-02 2.0130888e-03 1.8101212e-03 1.7679630e-03 1.5452657e-03 5.1945438e-04 1.2854163e-03 8.7808919e-03 8.2238558e-03 5.0256002e-04 2.7264330e-03 2.8467434e-03 5.0256002e-04 1.9812707e-03 1.4461308e-03 4.1136119e-03 2.5737383e-03 4.2121435e-03 4.2634118e-03 5.2197085e-03 6.4299253e-04 4.8925560e-03 2.3856210e-03 3.0302048e-03 1.5954763e-03 5.0938593e-02 5.4618269e-02 6.0341206e-02 6.9748949e-02 6.3744319e-02 7.0794949e-02 6.1613026e-02 4.7841585e-02 5.7516679e-02 6.4388046e-02 6.4176559e-02 5.7259869e-02 6.1078980e-02 6.9247074e-02 4.2780731e-02 4.8566082e-02 7.2247187e-02 5.4065395e-02 8.0844343e-02 5.6422768e-02 7.7401119e-02 4.9239970e-02 8.2976560e-02 6.8666014e-02 5.1795393e-02 5.1539865e-02 6.3922410e-02 7.0731007e-02 6.6410587e-02 3.9578438e-02 5.7098964e-02 5.2392525e-02 5.0683886e-02 9.1729236e-02 7.6796353e-02 5.9700691e-02 5.7658559e-02 7.0380963e-02 5.5891948e-02 6.4553084e-02 7.4314140e-02 6.4188376e-02 5.5862752e-02 4.8638731e-02 6.4818200e-02 5.5721441e-02 5.9023587e-02 5.4325379e-02 3.5659427e-02 5.7806321e-02 1.1646754e-01 1.0183406e-01 9.5237923e-02 9.9907095e-02 1.0622089e-01 1.0525522e-01 1.0561297e-01 1.0087833e-01 1.0779948e-01 9.3522115e-02 7.8070592e-02 9.4910280e-02 8.9633058e-02 1.0829781e-01 1.1081740e-01 9.1634033e-02 9.0337246e-02 9.1650208e-02 1.2399805e-01 1.0057671e-01 9.2649910e-02 9.9949877e-02 1.0962952e-01 8.2940269e-02 9.1028259e-02 8.7623472e-02 7.9432792e-02 8.0075100e-02 1.0524078e-01 8.3828651e-02 9.7259211e-02 7.8329945e-02 1.0714828e-01 8.1794698e-02 1.0616577e-01 9.3567438e-02 1.0077628e-01 9.0271060e-02 7.9035426e-02 8.3003648e-02 9.7873414e-02 7.9050596e-02 1.0183406e-01 1.0015100e-01 9.8558964e-02 8.7141498e-02 9.2951123e-02 8.4912476e-02 9.5252034e-02 9.0710349e-02 8.3242342e-04 1.3658963e-03 5.7230018e-04 8.1918836e-04 9.7205318e-04 4.1873206e-03 3.8010080e-03 1.6389584e-03 2.5918915e-03 2.9169588e-03 1.6389584e-03 5.6093257e-04 7.3131385e-04 1.3995850e-03 7.2853070e-03 1.1548608e-03 5.6661765e-04 1.3645766e-03 9.0462881e-04 1.5033145e-03 5.7640672e-04 1.1086000e-03 1.0041867e-03 5.6613254e-02 5.8073267e-02 6.6069758e-02 7.6238332e-02 6.9820331e-02 7.5150667e-02 6.4021588e-02 5.2221754e-02 6.4043055e-02 6.6631628e-02 7.2118099e-02 6.0001482e-02 7.1017077e-02 7.4033042e-02 4.5299125e-02 5.3744092e-02 7.4268794e-02 6.0257552e-02 8.9540407e-02 6.2698279e-02 7.8447791e-02 5.4448716e-02 9.0401373e-02 7.4795629e-02 5.7546133e-02 5.6993841e-02 7.1300530e-02 7.5820775e-02 7.0342915e-02 4.5615196e-02 6.3638314e-02 5.9296597e-02 5.5885275e-02 9.6910282e-02 7.8117499e-02 6.0390425e-02 6.2700176e-02 7.9484269e-02 5.8304382e-02 6.9718471e-02 7.9589842e-02 6.8316048e-02 6.1785991e-02 5.4151233e-02 6.9205713e-02 5.8981505e-02 6.2496988e-02 5.9482310e-02 3.9268283e-02 6.1808750e-02 1.1700097e-01 1.0528100e-01 1.0062122e-01 1.0448643e-01 1.0962576e-01 1.1218593e-01 1.0740486e-01 1.0838294e-01 1.1564442e-01 9.5242357e-02 8.0565058e-02 1.0027898e-01 9.3976493e-02 1.1211413e-01 1.1185843e-01 9.2981806e-02 9.4878465e-02 9.5039709e-02 1.3246555e-01 1.0898490e-01 9.5760022e-02 1.0161372e-01 1.1802422e-01 8.8109745e-02 9.3746381e-02 9.3301915e-02 8.3669649e-02 8.2970713e-02 1.0958444e-01 9.1015393e-02 1.0506496e-01 8.2570171e-02 1.1114727e-01 8.7646381e-02 1.1324005e-01 9.9872205e-02 1.0076379e-01 9.4009317e-02 8.1526386e-02 8.7041367e-02 1.0052094e-01 8.2193042e-02 1.0528100e-01 1.0314067e-01 9.9984788e-02 9.0308392e-02 9.8939123e-02 8.8538901e-02 9.5061969e-02 9.3157351e-02 1.7227462e-04 7.9314599e-04 8.9844173e-04 8.9518090e-04 2.8880348e-03 2.3244520e-03 6.4717383e-04 8.8327223e-04 1.0385020e-03 6.4717383e-04 4.2082433e-05 2.2535838e-05 6.1632160e-04 7.2421116e-03 6.3599645e-04 2.4204146e-03 3.0244507e-03 7.7555952e-04 1.1490838e-03 1.6721205e-04 1.6116507e-04 5.3985478e-05 6.6593275e-02 6.9140456e-02 7.6991415e-02 8.7909276e-02 8.1079796e-02 8.7140283e-02 7.5972851e-02 6.2229264e-02 7.4340995e-02 7.8983123e-02 8.2638003e-02 7.1602020e-02 8.0355120e-02 8.5886853e-02 5.5471847e-02 6.3724100e-02 8.7131187e-02 7.0026001e-02 1.0150375e-01 7.2956045e-02 9.2179245e-02 6.4526163e-02 1.0277569e-01 8.5954479e-02 6.7618774e-02 6.7207904e-02 8.2005017e-02 8.8025831e-02 8.2391127e-02 5.4193907e-02 7.3937560e-02 6.8913785e-02 6.6018815e-02 1.1053450e-01 9.1432865e-02 7.2512462e-02 7.3622571e-02 9.0228640e-02 6.9559505e-02 8.1277678e-02 9.1538587e-02 7.9923144e-02 7.2198625e-02 6.3968343e-02 8.0855092e-02 6.9835377e-02 7.3807917e-02 6.9953549e-02 4.8370205e-02 7.2961708e-02 1.3388298e-01 1.2040855e-01 1.1476271e-01 1.1886295e-01 1.2510613e-01 1.2637598e-01 1.2310531e-01 1.2187125e-01 1.2970708e-01 1.1033742e-01 9.4233672e-02 1.1441687e-01 1.0810566e-01 1.2778743e-01 1.2861773e-01 1.0813836e-01 1.0864380e-01 1.0911947e-01 1.4755823e-01 1.2232653e-01 1.1049966e-01 1.1716689e-01 1.3196625e-01 1.0144926e-01 1.0821364e-01 1.0641029e-01 9.6993665e-02 9.6571345e-02 1.2478057e-01 1.0328932e-01 1.1842664e-01 9.5377765e-02 1.2666075e-01 1.0023484e-01 1.2682498e-01 1.1377681e-01 1.1674990e-01 1.0792126e-01 9.5167233e-02 1.0076949e-01 1.1586910e-01 9.6070623e-02 1.2040855e-01 1.1835687e-01 1.1566030e-01 1.0480317e-01 1.1289922e-01 1.0248125e-01 1.1065853e-01 1.0752770e-01 1.5518070e-03 1.3360426e-03 6.1855325e-04 3.8684575e-03 2.7981409e-03 7.1019942e-04 2.9249692e-04 3.8108588e-04 7.1019942e-04 3.4811331e-04 2.0628507e-04 6.8481588e-04 6.1413327e-03 1.2640084e-03 3.0810810e-03 4.5162171e-03 6.1490495e-04 2.0823475e-03 6.6391763e-04 4.5940617e-04 4.0824218e-05 6.8980444e-02 7.2049409e-02 7.9659630e-02 9.0518795e-02 8.3601945e-02 9.0707158e-02 7.9362725e-02 6.4838475e-02 7.6843708e-02 8.2367370e-02 8.4912050e-02 7.4621958e-02 8.2116380e-02 8.9210086e-02 5.7968714e-02 6.6009408e-02 9.1025554e-02 7.2793579e-02 1.0368926e-01 7.5498978e-02 9.6154006e-02 6.6776572e-02 1.0567953e-01 8.9202124e-02 6.9982422e-02 6.9528749e-02 8.4404562e-02 9.0968518e-02 8.5580442e-02 5.6059636e-02 7.6365493e-02 7.1188605e-02 6.8464233e-02 1.1430428e-01 9.5645657e-02 7.6165252e-02 7.6295454e-02 9.2254316e-02 7.2920975e-02 8.4113452e-02 9.5088632e-02 8.3209827e-02 7.4687466e-02 6.6271074e-02 8.4049660e-02 7.3195324e-02 7.7054222e-02 7.2597553e-02 5.0198701e-02 7.5958466e-02 1.3864088e-01 1.2443023e-01 1.1820696e-01 1.2296103e-01 1.2918980e-01 1.2996108e-01 1.2761885e-01 1.2545185e-01 1.3315049e-01 1.1429516e-01 9.7708479e-02 1.1783417e-01 1.1147502e-01 1.3162480e-01 1.3265015e-01 1.1194384e-01 1.1244164e-01 1.1326233e-01 1.5100180e-01 1.2549100e-01 1.1411167e-01 1.2131599e-01 1.3539430e-01 1.0451333e-01 1.1218678e-01 1.1003412e-01 1.0016268e-01 1.0019858e-01 1.2861591e-01 1.0655136e-01 1.2158527e-01 9.9029274e-02 1.3048234e-01 1.0368193e-01 1.3098775e-01 1.1671318e-01 1.2117898e-01 1.1194010e-01 9.8811276e-02 1.0398171e-01 1.1952683e-01 9.8904418e-02 1.2443023e-01 1.2231211e-01 1.1957927e-01 1.0792296e-01 1.1588914e-01 1.0590028e-01 1.1501774e-01 1.1169315e-01 2.7458883e-04 1.9079543e-03 3.8013798e-03 4.1957403e-03 8.9864077e-04 3.1780627e-03 3.4575366e-03 8.9864077e-04 6.1086528e-04 6.3429136e-04 2.2515700e-03 7.8191545e-03 1.2413621e-03 2.0952535e-03 1.3093150e-03 1.3748953e-03 1.2388833e-03 4.8114625e-04 1.1404059e-03 1.0969768e-03 5.5584260e-02 5.7510469e-02 6.4993010e-02 7.5368447e-02 6.9057379e-02 7.3499750e-02 6.3437246e-02 5.1298347e-02 6.2635149e-02 6.6332421e-02 7.0700793e-02 5.9790543e-02 6.9153134e-02 7.2596138e-02 4.5395419e-02 5.3097576e-02 7.3376441e-02 5.8208889e-02 8.8752390e-02 6.1292160e-02 7.8242185e-02 5.3906344e-02 8.8989869e-02 7.2610553e-02 5.6579969e-02 5.6297092e-02 6.9966539e-02 7.5157531e-02 6.9589413e-02 4.4676337e-02 6.2364995e-02 5.7806901e-02 5.5013010e-02 9.5419957e-02 7.7143847e-02 6.0075763e-02 6.1882505e-02 7.8193894e-02 5.7405077e-02 6.8884889e-02 7.7590521e-02 6.7073564e-02 6.0698980e-02 5.3256477e-02 6.8055547e-02 5.7544133e-02 6.1428351e-02 5.8436070e-02 3.9618229e-02 6.0914936e-02 1.1719371e-01 1.0478727e-01 9.9928933e-02 1.0301520e-01 1.0921839e-01 1.1065431e-01 1.0694189e-01 1.0626748e-01 1.1395294e-01 9.5522682e-02 8.0692317e-02 9.9640584e-02 9.3821909e-02 1.1210560e-01 1.1313776e-01 9.3722341e-02 9.3659306e-02 9.3792773e-02 1.3107147e-01 1.0721381e-01 9.5955259e-02 1.0180084e-01 1.1608360e-01 8.7786080e-02 9.3279943e-02 9.1618132e-02 8.3505928e-02 8.2622852e-02 1.0912386e-01 8.8978076e-02 1.0355009e-01 8.1236966e-02 1.1101306e-01 8.5950464e-02 1.1026035e-01 9.9640402e-02 1.0131013e-01 9.2768985e-02 8.1325904e-02 8.7087186e-02 1.0113076e-01 8.3368795e-02 1.0478727e-01 1.0299507e-01 1.0075649e-01 9.1267488e-02 9.8760345e-02 8.8473047e-02 9.5602739e-02 9.2388034e-02 1.3192990e-03 5.6049942e-03 5.6260410e-03 3.8255378e-04 2.7098321e-03 2.9260165e-03 3.8255378e-04 8.5873923e-04 6.4551657e-04 2.7466704e-03 5.2436473e-03 2.1741200e-03 2.6488612e-03 2.5599967e-03 7.2546074e-04 2.4454411e-03 9.4817109e-04 1.6251073e-03 9.8969141e-04 5.2867682e-02 5.5519966e-02 6.2241614e-02 7.2187515e-02 6.6023504e-02 7.1524895e-02 6.1872329e-02 4.9090525e-02 5.9694897e-02 6.4712456e-02 6.7154795e-02 5.7947928e-02 6.4972194e-02 7.0357979e-02 4.3573654e-02 5.0441816e-02 7.2068579e-02 5.5680796e-02 8.4591184e-02 5.8459303e-02 7.7051055e-02 5.1193288e-02 8.5601942e-02 7.0117829e-02 5.3803216e-02 5.3535941e-02 6.6621467e-02 7.2473708e-02 6.7439009e-02 4.1816308e-02 5.9366143e-02 5.4760331e-02 5.2431175e-02 9.2978431e-02 7.6152091e-02 5.9131458e-02 5.9321453e-02 7.4096463e-02 5.5985281e-02 6.6256786e-02 7.5362720e-02 6.5046998e-02 5.7885295e-02 5.0561408e-02 6.5880549e-02 5.5988437e-02 5.9621488e-02 5.5930673e-02 3.7267604e-02 5.8818934e-02 1.1596549e-01 1.0264359e-01 9.7070235e-02 1.0079872e-01 1.0704759e-01 1.0746690e-01 1.0547300e-01 1.0308827e-01 1.1044375e-01 9.3809882e-02 7.8755194e-02 9.6766957e-02 9.1194118e-02 1.0959636e-01 1.1126853e-01 9.1984794e-02 9.1378676e-02 9.2003603e-02 1.2714849e-01 1.0351897e-01 9.3695356e-02 1.0013450e-01 1.1244271e-01 8.4898325e-02 9.1456450e-02 8.9057498e-02 8.0952577e-02 8.0704372e-02 1.0658329e-01 8.5941483e-02 1.0000975e-01 7.9159793e-02 1.0848060e-01 8.3337337e-02 1.0760265e-01 9.6215589e-02 1.0020263e-01 9.0833970e-02 7.9520366e-02 8.4523201e-02 9.8885306e-02 8.0736144e-02 1.0264359e-01 1.0090595e-01 9.8957167e-02 8.8690158e-02 9.5447639e-02 8.6121379e-02 9.4585258e-02 9.0802578e-02 6.3934178e-03 4.8940589e-03 1.2286350e-03 9.0839358e-04 1.0654961e-03 1.2286350e-03 9.6426336e-04 7.9049403e-04 1.4335758e-03 3.9777866e-03 2.4355051e-03 2.0107867e-03 4.6162602e-03 1.1770386e-04 3.4806567e-03 1.4352123e-03 1.5474906e-03 6.2303871e-04 6.0828103e-02 6.3671616e-02 7.0896871e-02 8.0864907e-02 7.4298413e-02 8.2081782e-02 7.0784097e-02 5.7058421e-02 6.8417644e-02 7.3405222e-02 7.5846906e-02 6.5924537e-02 7.3406010e-02 8.0389200e-02 5.0100593e-02 5.7793778e-02 8.2174514e-02 6.5233957e-02 9.3014438e-02 6.7186690e-02 8.6645560e-02 5.8420934e-02 9.5569964e-02 8.0832485e-02 6.1703014e-02 6.1092740e-02 7.5364208e-02 8.1326732e-02 7.6506202e-02 4.8648267e-02 6.7850123e-02 6.3129543e-02 6.0279722e-02 1.0421037e-01 8.6793624e-02 6.7911943e-02 6.7612814e-02 8.2552902e-02 6.4996479e-02 7.4977087e-02 8.6383221e-02 7.4662447e-02 6.6198278e-02 5.8199721e-02 7.5325266e-02 6.5572089e-02 6.8824430e-02 6.4314329e-02 4.2494903e-02 6.7537432e-02 1.2698890e-01 1.1333529e-01 1.0720337e-01 1.1257147e-01 1.1782339e-01 1.1890376e-01 1.1671052e-01 1.1501886e-01 1.2195484e-01 1.0334755e-01 8.7523621e-02 1.0680449e-01 1.0051071e-01 1.1973768e-01 1.2022270e-01 1.0080013e-01 1.0231108e-01 1.0334772e-01 1.3872306e-01 1.1462175e-01 1.0296153e-01 1.1014449e-01 1.2423991e-01 9.3883877e-02 1.0176675e-01 1.0022558e-01 8.9772340e-02 9.0213298e-02 1.1713918e-01 9.6981953e-02 1.1075521e-01 8.9707194e-02 1.1871449e-01 9.4162640e-02 1.2118172e-01 1.0525415e-01 1.1008916e-01 1.0200908e-01 8.8850235e-02 9.3264552e-02 1.0788029e-01 8.7700151e-02 1.1333529e-01 1.1110284e-01 1.0804665e-01 9.6438203e-02 1.0447205e-01 9.5256431e-02 1.0425068e-01 1.0162139e-01 4.7487225e-04 5.5229901e-03 5.0553045e-03 5.3192416e-03 5.5229901e-03 2.6440955e-03 3.2948907e-03 2.1817756e-03 1.9232390e-02 1.0060949e-03 5.2893888e-03 3.6565664e-03 6.4838776e-03 7.5722530e-04 2.1347736e-03 1.7405562e-03 3.5508478e-03 8.4906111e-02 8.5684875e-02 9.5941240e-02 1.0863973e-01 1.0110388e-01 1.0421406e-01 9.1634377e-02 7.8911086e-02 9.3575504e-02 9.5023267e-02 1.0394314e-01 8.7940160e-02 1.0307352e-01 1.0372095e-01 7.0927620e-02 8.1814584e-02 1.0248405e-01 8.7682138e-02 1.2553590e-01 9.1822634e-02 1.0758552e-01 8.2848043e-02 1.2459059e-01 1.0427303e-01 8.6172494e-02 8.5771372e-02 1.0276107e-01 1.0743739e-01 1.0000507e-01 7.2180344e-02 9.3352407e-02 8.8118179e-02 8.3972299e-02 1.2999219e-01 1.0601815e-01 8.6245777e-02 9.1942699e-02 1.1341937e-01 8.4389659e-02 1.0016617e-01 1.0942619e-01 9.6927434e-02 9.1068024e-02 8.2112078e-02 9.8354950e-02 8.4918144e-02 8.9929197e-02 8.7860129e-02 6.4913483e-02 8.9916257e-02 1.5108513e-01 1.3966025e-01 1.3579363e-01 1.3800221e-01 1.4462965e-01 1.4853849e-01 1.4049017e-01 1.4366186e-01 1.5284247e-01 1.2813521e-01 1.1198929e-01 1.3549048e-01 1.2835377e-01 1.4847864e-01 1.4775011e-01 1.2601872e-01 1.2764409e-01 1.2670935e-01 1.7255531e-01 1.4567287e-01 1.2987249e-01 1.3506836e-01 1.5547286e-01 1.2211481e-01 1.2607603e-01 1.2598262e-01 1.1656479e-01 1.1426125e-01 1.4534374e-01 1.2395854e-01 1.4127350e-01 1.1320894e-01 1.4734853e-01 1.1969377e-01 1.4703899e-01 1.3646872e-01 1.3305975e-01 1.2588961e-01 1.1250545e-01 1.2058130e-01 1.3549735e-01 1.1610735e-01 1.3966025e-01 1.3745925e-01 1.3401818e-01 1.2501338e-01 1.3525796e-01 1.2173379e-01 1.2646805e-01 1.2458940e-01 5.1766813e-03 3.3038909e-03 3.5089109e-03 5.1766813e-03 2.1988415e-03 2.7782772e-03 1.0688451e-03 1.7030893e-02 8.9726872e-04 4.6231995e-03 4.6736672e-03 5.3382818e-03 1.1647725e-03 1.9758755e-03 1.2750935e-03 2.7128598e-03 8.7833448e-02 8.9010914e-02 9.9253982e-02 1.1183035e-01 1.0412107e-01 1.0885582e-01 9.5547284e-02 8.2028755e-02 9.6837094e-02 9.8811318e-02 1.0697473e-01 9.1242752e-02 1.0570232e-01 1.0798221e-01 7.3423587e-02 8.4439842e-02 1.0715501e-01 9.1521191e-02 1.2824419e-01 9.5124610e-02 1.1205287e-01 8.5389026e-02 1.2841576e-01 1.0878552e-01 8.9048303e-02 8.8474213e-02 1.0589810e-01 1.1091852e-01 1.0379001e-01 7.4413565e-02 9.6465402e-02 9.1132676e-02 8.6893608e-02 1.3487393e-01 1.1110410e-01 9.0320856e-02 9.5130800e-02 1.1613190e-01 8.8387927e-02 1.0357744e-01 1.1422195e-01 1.0103671e-01 9.4160592e-02 8.4870839e-02 1.0232030e-01 8.9160088e-02 9.3889889e-02 9.1104413e-02 6.6493231e-02 9.3508454e-02 1.5639991e-01 1.4440871e-01 1.3995325e-01 1.4327783e-01 1.4942424e-01 1.5327636e-01 1.4579380e-01 1.4864466e-01 1.5749945e-01 1.3242397e-01 1.1574188e-01 1.3959357e-01 1.3216131e-01 1.5281767e-01 1.5172347e-01 1.2991524e-01 1.3242882e-01 1.3190251e-01 1.7712896e-01 1.5002220e-01 1.3380600e-01 1.3964037e-01 1.6023126e-01 1.2562692e-01 1.3071698e-01 1.3077115e-01 1.2010966e-01 1.1841338e-01 1.4987755e-01 1.2848062e-01 1.4548859e-01 1.1783301e-01 1.5172346e-01 1.2426441e-01 1.5309487e-01 1.3983154e-01 1.3778354e-01 1.3093423e-01 1.1660453e-01 1.2409289e-01 1.3931059e-01 1.1865130e-01 1.4440871e-01 1.4196632e-01 1.3805465e-01 1.2801436e-01 1.3865562e-01 1.2553921e-01 1.3109454e-01 1.2958547e-01 1.5860612e-03 1.6773969e-03 0.0000000e+00 8.4337656e-04 4.6746407e-04 2.4549978e-03 4.7529836e-03 2.3235808e-03 4.0683267e-03 4.3260986e-03 6.7336618e-04 2.8454658e-03 1.0918918e-03 1.3756658e-03 5.7784546e-04 5.9573290e-02 6.3070670e-02 6.9597309e-02 7.9911457e-02 7.3480528e-02 7.9923883e-02 7.0144874e-02 5.5923876e-02 6.6635620e-02 7.3192589e-02 7.4096565e-02 6.5836734e-02 7.1022277e-02 7.8555696e-02 5.0423423e-02 5.7089619e-02 8.1093473e-02 6.2483167e-02 9.2251714e-02 6.5399221e-02 8.6573432e-02 5.7873871e-02 9.3861710e-02 7.7914479e-02 6.0545459e-02 6.0328179e-02 7.3725736e-02 8.0652769e-02 7.5662466e-02 4.7500835e-02 6.6267157e-02 6.1219907e-02 5.9246920e-02 1.0235525e-01 8.5584812e-02 6.7627185e-02 6.6676399e-02 8.1028522e-02 6.3874534e-02 7.4037098e-02 8.3735875e-02 7.3091049e-02 6.4875922e-02 5.7134332e-02 7.3898502e-02 6.3669341e-02 6.7483654e-02 6.3032151e-02 4.3195391e-02 6.6465430e-02 1.2757303e-01 1.1294171e-01 1.0654531e-01 1.1074736e-01 1.1756538e-01 1.1705185e-01 1.1633100e-01 1.1230305e-01 1.1988948e-01 1.0404710e-01 8.7981667e-02 1.0622547e-01 1.0061669e-01 1.2008497e-01 1.2242153e-01 1.0217012e-01 1.0084187e-01 1.0181343e-01 1.3714147e-01 1.1243585e-01 1.0356517e-01 1.1071692e-01 1.2181923e-01 9.3742899e-02 1.0137566e-01 9.8085445e-02 8.9840814e-02 8.9979544e-02 1.1682155e-01 9.4340727e-02 1.0893096e-01 8.8036209e-02 1.1887723e-01 9.1995894e-02 1.1718099e-01 1.0529554e-01 1.1115622e-01 1.0048991e-01 8.8823715e-02 9.3647258e-02 1.0909883e-01 8.9729548e-02 1.1294171e-01 1.1121254e-01 1.0947844e-01 9.8164553e-02 1.0458423e-01 9.5468337e-02 1.0529433e-01 1.0077315e-01 1.0959804e-05 1.5860612e-03 1.2066237e-03 9.8801305e-04 9.8752232e-04 6.0992006e-03 2.3026455e-03 4.3295194e-03 6.8464966e-03 1.1467011e-03 3.5017117e-03 1.7326793e-03 1.1803657e-03 5.4725577e-04 7.4912594e-02 7.8462634e-02 8.6080465e-02 9.7055695e-02 8.9913940e-02 9.8246196e-02 8.6359614e-02 7.0875411e-02 8.3096610e-02 8.9373603e-02 9.1095332e-02 8.1132274e-02 8.7784959e-02 9.6471287e-02 6.3584445e-02 7.1727351e-02 9.8741504e-02 7.9297358e-02 1.1001947e-01 8.1763635e-02 1.0392038e-01 7.2462318e-02 1.1283140e-01 9.6493172e-02 7.5904241e-02 7.5362083e-02 9.0691159e-02 9.7798442e-02 9.2550220e-02 6.1191529e-02 8.2519289e-02 7.7118501e-02 7.4418954e-02 1.2241546e-01 1.0373213e-01 8.3271349e-02 8.2617382e-02 9.8302252e-02 7.9806174e-02 9.0742202e-02 1.0274136e-01 9.0294649e-02 8.0841362e-02 7.2046648e-02 9.1054206e-02 8.0166072e-02 8.3952641e-02 7.8850217e-02 5.4962225e-02 8.2583735e-02 1.4771497e-01 1.3277361e-01 1.2596285e-01 1.3150244e-01 1.3764710e-01 1.3814813e-01 1.3643324e-01 1.3364709e-01 1.4127174e-01 1.2228442e-01 1.0501430e-01 1.2555100e-01 1.1896947e-01 1.3983185e-01 1.4080838e-01 1.1967635e-01 1.2050783e-01 1.2165153e-01 1.5932394e-01 1.3324590e-01 1.2180922e-01 1.2960297e-01 1.4356572e-01 1.1163860e-01 1.2028439e-01 1.1797002e-01 1.0728569e-01 1.0776505e-01 1.3685139e-01 1.1414077e-01 1.2924282e-01 1.0675126e-01 1.3867943e-01 1.1135088e-01 1.3991357e-01 1.2389778e-01 1.2963231e-01 1.2019755e-01 1.0634263e-01 1.1117491e-01 1.2727853e-01 1.0546335e-01 1.3277361e-01 1.3050496e-01 1.2753116e-01 1.1493738e-01 1.2310368e-01 1.1333277e-01 1.2330887e-01 1.1999903e-01 1.6773969e-03 1.4044930e-03 1.1476188e-03 1.1728210e-03 6.0850239e-03 2.5611449e-03 4.7720148e-03 7.3487783e-03 1.2981958e-03 3.8044610e-03 1.9624179e-03 1.3573639e-03 6.7050163e-04 7.5875383e-02 7.9591742e-02 8.7130965e-02 9.8136915e-02 9.0968214e-02 9.9474916e-02 8.7612410e-02 7.1886206e-02 8.4072967e-02 9.0657173e-02 9.2037661e-02 8.2322796e-02 8.8559519e-02 9.7661149e-02 6.4636714e-02 7.2692172e-02 1.0010765e-01 8.0266168e-02 1.1103798e-01 8.2745924e-02 1.0537406e-01 7.3430328e-02 1.1396572e-01 9.7599809e-02 7.6869874e-02 7.6340270e-02 9.1668858e-02 9.8974404e-02 9.3760611e-02 6.2005453e-02 8.3489209e-02 7.8020463e-02 7.5407355e-02 1.2375637e-01 1.0517097e-01 8.4594299e-02 8.3682974e-02 9.9214366e-02 8.1008204e-02 9.1862911e-02 1.0394615e-01 9.1478420e-02 8.1837398e-02 7.2994499e-02 9.2227696e-02 8.1322109e-02 8.5126430e-02 7.9879628e-02 5.5860810e-02 8.3714500e-02 1.4946173e-01 1.3427629e-01 1.2730802e-01 1.3293539e-01 1.3918009e-01 1.3947497e-01 1.3805161e-01 1.3491280e-01 1.4255824e-01 1.2381647e-01 1.0639120e-01 1.2689399e-01 1.2032986e-01 1.4134878e-01 1.4247553e-01 1.2120787e-01 1.2187456e-01 1.2309328e-01 1.6066767e-01 1.3444738e-01 1.2325830e-01 1.3118369e-01 1.4483072e-01 1.1290137e-01 1.2175315e-01 1.1925251e-01 1.0857610e-01 1.0914142e-01 1.3832403e-01 1.1530308e-01 1.3045824e-01 1.0804612e-01 1.4018015e-01 1.1257905e-01 1.4124338e-01 1.2516443e-01 1.3130183e-01 1.2160995e-01 1.0773181e-01 1.1250108e-01 1.2878318e-01 1.0678720e-01 1.3427629e-01 1.3201801e-01 1.2910620e-01 1.1632723e-01 1.2438544e-01 1.1469977e-01 1.2494953e-01 1.2148287e-01 8.4337656e-04 4.6746407e-04 2.4549978e-03 4.7529836e-03 2.3235808e-03 4.0683267e-03 4.3260986e-03 6.7336618e-04 2.8454658e-03 1.0918918e-03 1.3756658e-03 5.7784546e-04 5.9573290e-02 6.3070670e-02 6.9597309e-02 7.9911457e-02 7.3480528e-02 7.9923883e-02 7.0144874e-02 5.5923876e-02 6.6635620e-02 7.3192589e-02 7.4096565e-02 6.5836734e-02 7.1022277e-02 7.8555696e-02 5.0423423e-02 5.7089619e-02 8.1093473e-02 6.2483167e-02 9.2251714e-02 6.5399221e-02 8.6573432e-02 5.7873871e-02 9.3861710e-02 7.7914479e-02 6.0545459e-02 6.0328179e-02 7.3725736e-02 8.0652769e-02 7.5662466e-02 4.7500835e-02 6.6267157e-02 6.1219907e-02 5.9246920e-02 1.0235525e-01 8.5584812e-02 6.7627185e-02 6.6676399e-02 8.1028522e-02 6.3874534e-02 7.4037098e-02 8.3735875e-02 7.3091049e-02 6.4875922e-02 5.7134332e-02 7.3898502e-02 6.3669341e-02 6.7483654e-02 6.3032151e-02 4.3195391e-02 6.6465430e-02 1.2757303e-01 1.1294171e-01 1.0654531e-01 1.1074736e-01 1.1756538e-01 1.1705185e-01 1.1633100e-01 1.1230305e-01 1.1988948e-01 1.0404710e-01 8.7981667e-02 1.0622547e-01 1.0061669e-01 1.2008497e-01 1.2242153e-01 1.0217012e-01 1.0084187e-01 1.0181343e-01 1.3714147e-01 1.1243585e-01 1.0356517e-01 1.1071692e-01 1.2181923e-01 9.3742899e-02 1.0137566e-01 9.8085445e-02 8.9840814e-02 8.9979544e-02 1.1682155e-01 9.4340727e-02 1.0893096e-01 8.8036209e-02 1.1887723e-01 9.1995894e-02 1.1718099e-01 1.0529554e-01 1.1115622e-01 1.0048991e-01 8.8823715e-02 9.3647258e-02 1.0909883e-01 8.9729548e-02 1.1294171e-01 1.1121254e-01 1.0947844e-01 9.8164553e-02 1.0458423e-01 9.5468337e-02 1.0529433e-01 1.0077315e-01 6.2742331e-05 5.7500583e-04 7.7277880e-03 4.4752900e-04 1.9151463e-03 2.3881652e-03 8.6221368e-04 8.6997251e-04 5.7712764e-05 1.4261239e-04 1.6337476e-04 6.5357511e-02 6.7564930e-02 7.5599895e-02 8.6482524e-02 7.9693502e-02 8.5392044e-02 7.4152863e-02 6.0880360e-02 7.3094884e-02 7.7108408e-02 8.1483729e-02 6.9905750e-02 7.9537736e-02 8.4220614e-02 5.4020845e-02 6.2478782e-02 8.5095725e-02 6.8776984e-02 1.0023818e-01 7.1693803e-02 8.9974847e-02 6.3277271e-02 1.0126704e-01 8.4456284e-02 6.6380508e-02 6.5944304e-02 8.0776371e-02 8.6401760e-02 8.0678654e-02 5.3232214e-02 7.2705055e-02 6.7807498e-02 6.4728976e-02 1.0860574e-01 8.9250516e-02 7.0532132e-02 7.2192370e-02 8.9155393e-02 6.7825786e-02 7.9751674e-02 8.9848634e-02 7.8255825e-02 7.0908403e-02 6.2757363e-02 7.9213209e-02 6.8195297e-02 7.2146158e-02 6.8587288e-02 4.7220467e-02 7.1390990e-02 1.3114639e-01 1.1816523e-01 1.1284157e-01 1.1675611e-01 1.2280854e-01 1.2450964e-01 1.2061835e-01 1.2011904e-01 1.2793055e-01 1.0801409e-01 9.2203467e-02 1.1250005e-01 1.0614185e-01 1.2553294e-01 1.2604996e-01 1.0581394e-01 1.0665851e-01 1.0697400e-01 1.4569576e-01 1.2071369e-01 1.0835453e-01 1.1475367e-01 1.3023667e-01 9.9676180e-02 1.0601570e-01 1.0459446e-01 9.5152798e-02 9.4544535e-02 1.2261107e-01 1.0171679e-01 1.1677938e-01 9.3513090e-02 1.2443770e-01 9.8519974e-02 1.2493623e-01 1.1202254e-01 1.1414514e-01 1.0583616e-01 9.3110547e-02 9.8863035e-02 1.1361961e-01 9.4163495e-02 1.1816523e-01 1.1608967e-01 1.1325987e-01 1.0277556e-01 1.1111172e-01 1.0049112e-01 1.0810161e-01 1.0529258e-01 8.3201047e-04 6.6553558e-03 8.0817319e-04 2.3666253e-03 2.9401972e-03 6.0430641e-04 1.3109323e-03 2.0042209e-04 2.9042771e-04 6.4823857e-05 6.4369752e-02 6.6980839e-02 7.4622998e-02 8.5371818e-02 7.8644884e-02 8.4714120e-02 7.3778054e-02 6.0124143e-02 7.1976215e-02 7.6757894e-02 8.0112003e-02 6.9445381e-02 7.7800626e-02 8.3451231e-02 5.3558729e-02 6.1563285e-02 8.4824350e-02 6.7739660e-02 9.8727492e-02 7.0619990e-02 8.9865595e-02 6.2353086e-02 1.0002589e-01 8.3464224e-02 6.5377675e-02 6.4985443e-02 7.9506921e-02 8.5546781e-02 8.0035925e-02 5.2147031e-02 7.1577598e-02 6.6610625e-02 6.3822994e-02 1.0780225e-01 8.9119904e-02 7.0461434e-02 7.1328305e-02 8.7570443e-02 6.7451743e-02 7.8878318e-02 8.9018945e-02 7.7592264e-02 6.9886540e-02 6.1789581e-02 7.8498655e-02 6.7684600e-02 7.1587759e-02 6.7704325e-02 4.6526712e-02 7.0724596e-02 1.3117528e-01 1.1766016e-01 1.1197322e-01 1.1607657e-01 1.2231416e-01 1.2340084e-01 1.2042522e-01 1.1891967e-01 1.2666041e-01 1.0778594e-01 9.1812847e-02 1.1163169e-01 1.0543690e-01 1.2494830e-01 1.2593147e-01 1.0563286e-01 1.0596141e-01 1.0649455e-01 1.4431904e-01 1.1933022e-01 1.0786987e-01 1.1454955e-01 1.2887601e-01 9.8812977e-02 1.0562884e-01 1.0369928e-01 9.4451580e-02 9.4102215e-02 1.2194237e-01 1.0054711e-01 1.1549365e-01 9.2857172e-02 1.2382256e-01 9.7583400e-02 1.2385907e-01 1.1095947e-01 1.1423829e-01 1.0528942e-01 9.2735747e-02 9.8196145e-02 1.1321168e-01 9.3608756e-02 1.1766016e-01 1.1565299e-01 1.1307354e-01 1.0223904e-01 1.1010492e-01 9.9908851e-02 1.0821954e-01 1.0496782e-01 9.9295890e-03 5.3473138e-04 2.1693539e-03 3.7076821e-03 1.8222092e-03 1.2380588e-03 7.0429726e-04 3.2576994e-04 6.7027380e-04 7.5169669e-02 7.7062740e-02 8.6033367e-02 9.7417771e-02 9.0184218e-02 9.6534643e-02 8.3912481e-02 7.0270670e-02 8.3626528e-02 8.6844061e-02 9.2545497e-02 7.9257265e-02 9.0777176e-02 9.5235076e-02 6.2208659e-02 7.1858472e-02 9.5535449e-02 7.9390740e-02 1.1189093e-01 8.2130752e-02 1.0013922e-01 7.2643917e-02 1.1330079e-01 9.5998078e-02 7.6221141e-02 7.5580594e-02 9.1730689e-02 9.7114882e-02 9.1048046e-02 6.2224749e-02 8.3137120e-02 7.8094271e-02 7.4382594e-02 1.2083561e-01 9.9831872e-02 7.9711043e-02 8.2237066e-02 1.0057287e-01 7.7408745e-02 9.0227459e-02 1.0148848e-01 8.8790024e-02 8.1095637e-02 7.2322440e-02 8.9773416e-02 7.8184960e-02 8.2186791e-02 7.8567440e-02 5.4863235e-02 8.1347337e-02 1.4275245e-01 1.3005259e-01 1.2482271e-01 1.2925722e-01 1.3482920e-01 1.3759096e-01 1.3236924e-01 1.3338627e-01 1.4130888e-01 1.1880967e-01 1.0247320e-01 1.2443347e-01 1.1741517e-01 1.3747488e-01 1.3688510e-01 1.1618864e-01 1.1858835e-01 1.1879679e-01 1.5963135e-01 1.3387511e-01 1.1938532e-01 1.2588037e-01 1.4388488e-01 1.1083228e-01 1.1728514e-01 1.1680010e-01 1.0591970e-01 1.0525056e-01 1.3476053e-01 1.1410719e-01 1.2959057e-01 1.0487194e-01 1.3643100e-01 1.1046993e-01 1.3880755e-01 1.2375874e-01 1.2479172e-01 1.1764923e-01 1.0361548e-01 1.0965651e-01 1.2456830e-01 1.0392711e-01 1.3005259e-01 1.2763609e-01 1.2394348e-01 1.1308481e-01 1.2275352e-01 1.1138648e-01 1.1846972e-01 1.1666230e-01 1.1839370e-02 9.5901133e-03 1.3740734e-02 3.5338001e-03 1.3550011e-02 8.8526707e-03 9.4699409e-03 6.3350154e-03 4.8467987e-02 5.3749987e-02 5.7717889e-02 6.5676566e-02 6.0050880e-02 7.0693363e-02 6.1877604e-02 4.6729706e-02 5.4665912e-02 6.4242849e-02 5.9590174e-02 5.6493218e-02 5.5132102e-02 6.8254515e-02 4.1974046e-02 4.5979775e-02 7.3375693e-02 5.2912711e-02 7.3889063e-02 5.3879214e-02 7.8254066e-02 4.6403740e-02 7.8645675e-02 6.7578949e-02 4.9098308e-02 4.8671019e-02 5.9827034e-02 6.7861186e-02 6.5136324e-02 3.6759287e-02 5.3980965e-02 4.9365653e-02 4.8462917e-02 9.0010105e-02 7.8830145e-02 6.1496889e-02 5.5380536e-02 6.4107134e-02 5.6871111e-02 6.2034296e-02 7.3759034e-02 6.3674900e-02 5.3119604e-02 4.6143136e-02 6.3811294e-02 5.6759011e-02 5.9030859e-02 5.2419966e-02 3.3258744e-02 5.6880106e-02 1.1655832e-01 1.0005043e-01 9.1684275e-02 9.8727002e-02 1.0425490e-01 1.0138126e-01 1.0565129e-01 9.7538138e-02 1.0314095e-01 9.2386824e-02 7.6571607e-02 9.1274575e-02 8.6336607e-02 1.0505203e-01 1.0829773e-01 9.0077109e-02 8.8858206e-02 9.1549427e-02 1.1781623e-01 9.5530481e-02 9.0068051e-02 9.8963246e-02 1.0479240e-01 7.9118056e-02 9.0207341e-02 8.5766485e-02 7.6442113e-02 7.8991975e-02 1.0229780e-01 8.0971468e-02 9.2462279e-02 7.7616682e-02 1.0394880e-01 7.9854175e-02 1.0493060e-01 8.8086863e-02 1.0105189e-01 8.9771099e-02 7.8157448e-02 7.9782471e-02 9.4953430e-02 7.4768636e-02 1.0005043e-01 9.8253215e-02 9.6744777e-02 8.3113340e-02 8.7744069e-02 8.2353939e-02 9.5830913e-02 9.0799350e-02 2.1341759e-03 1.9110407e-03 2.4747400e-03 1.4759127e-04 2.6111423e-04 2.1389787e-04 9.9338048e-04 7.1511570e-02 7.2884660e-02 8.1954557e-02 9.3478761e-02 8.6419800e-02 9.0995738e-02 7.9065079e-02 6.6366318e-02 7.9643396e-02 8.2112775e-02 8.8849792e-02 7.5066735e-02 8.7600830e-02 9.0111864e-02 5.8883625e-02 6.8498056e-02 8.9863635e-02 7.4803608e-02 1.0853136e-01 7.8097142e-02 9.4627165e-02 6.9370599e-02 1.0871906e-01 9.0676026e-02 7.2617660e-02 7.2144076e-02 8.7900274e-02 9.2810262e-02 8.6387249e-02 5.9335633e-02 7.9309675e-02 7.4397587e-02 7.0710353e-02 1.1504320e-01 9.3692385e-02 7.4629165e-02 7.8263498e-02 9.7248749e-02 7.2487054e-02 8.6011897e-02 9.5823155e-02 8.3806772e-02 7.7264691e-02 6.8838121e-02 8.4948991e-02 7.3047820e-02 7.7341294e-02 7.4548923e-02 5.2558496e-02 7.6909140e-02 1.3627575e-01 1.2430643e-01 1.1978148e-01 1.2301688e-01 1.2902659e-01 1.3200226e-01 1.2597225e-01 1.2757685e-01 1.3584109e-01 1.1348127e-01 9.7761057e-02 1.1945082e-01 1.1270375e-01 1.3215934e-01 1.3182973e-01 1.1125522e-01 1.1287096e-01 1.1260483e-01 1.5425448e-01 1.2876341e-01 1.1448536e-01 1.2024528e-01 1.3833407e-01 1.0647529e-01 1.1163750e-01 1.1113351e-01 1.0149473e-01 1.0013701e-01 1.2927004e-01 1.0879101e-01 1.2459621e-01 9.9330508e-02 1.3108770e-01 1.0504760e-01 1.3186075e-01 1.1958837e-01 1.1892921e-01 1.1162833e-01 9.8541878e-02 1.0525098e-01 1.1976536e-01 1.0049727e-01 1.2430643e-01 1.2212353e-01 1.1885667e-01 1.0916600e-01 1.1853397e-01 1.0665478e-01 1.1270995e-01 1.1063603e-01 1.5750019e-03 2.3061943e-03 2.5267586e-03 1.8816551e-03 2.4916769e-03 2.6490348e-03 5.6749564e-02 5.7171493e-02 6.5939847e-02 7.5932398e-02 6.9508351e-02 7.4656451e-02 6.2624290e-02 5.2024188e-02 6.4437230e-02 6.4937043e-02 7.2745199e-02 5.8611781e-02 7.2652586e-02 7.3573688e-02 4.4171072e-02 5.3594578e-02 7.2641168e-02 6.1050608e-02 8.9566976e-02 6.3051224e-02 7.6010398e-02 5.4225638e-02 9.0321512e-02 7.5195114e-02 5.7639533e-02 5.6852233e-02 7.1710391e-02 7.4949900e-02 6.9318916e-02 4.6230253e-02 6.3971485e-02 6.0026157e-02 5.5798348e-02 9.5989279e-02 7.6232404e-02 5.8444003e-02 6.2302257e-02 8.0224536e-02 5.7278169e-02 6.9150783e-02 7.9467477e-02 6.7704503e-02 6.1864652e-02 5.4241616e-02 6.8592620e-02 5.8516944e-02 6.1759106e-02 5.9360348e-02 3.8591759e-02 6.1151978e-02 1.1324556e-01 1.0303819e-01 9.9201558e-02 1.0318500e-01 1.0721879e-01 1.1147036e-01 1.0460948e-01 1.0828938e-01 1.1523751e-01 9.2280249e-02 7.8300492e-02 9.8832112e-02 9.2088327e-02 1.0954453e-01 1.0774163e-01 8.9704304e-02 9.3649245e-02 9.3477002e-02 1.3171389e-01 1.0896793e-01 9.3248418e-02 9.8534682e-02 1.1788866e-01 8.6730988e-02 9.1550165e-02 9.2743651e-02 8.2023117e-02 8.1036634e-02 1.0749375e-01 9.1211084e-02 1.0479473e-01 8.1639608e-02 1.0872731e-01 8.7298316e-02 1.1342725e-01 9.8507274e-02 9.7018335e-02 9.2572028e-02 7.9424005e-02 8.5123740e-02 9.7519007e-02 7.9504622e-02 1.0303819e-01 1.0060889e-01 9.6540999e-02 8.7528391e-02 9.7464514e-02 8.6517419e-02 9.1407615e-02 9.1076579e-02 4.2759888e-03 1.3998823e-03 1.8495751e-03 2.8301386e-03 3.7334484e-03 5.5009109e-02 5.4964037e-02 6.3820777e-02 7.4331569e-02 6.8062522e-02 7.0406681e-02 5.9595322e-02 4.9880526e-02 6.2237804e-02 6.2265151e-02 7.1113130e-02 5.6594278e-02 7.1451534e-02 6.9994053e-02 4.3126676e-02 5.2381128e-02 6.8520339e-02 5.7597897e-02 8.9058557e-02 6.0751652e-02 7.2510768e-02 5.3201106e-02 8.7849614e-02 7.0985642e-02 5.6028788e-02 5.5591108e-02 6.9920676e-02 7.2931234e-02 6.6633349e-02 4.5295849e-02 6.2044642e-02 5.8063752e-02 5.4097801e-02 9.1903182e-02 7.1473709e-02 5.5091248e-02 6.0376704e-02 7.9310761e-02 5.3854777e-02 6.7042056e-02 7.4968513e-02 6.4284932e-02 5.9983090e-02 5.2745182e-02 6.5463515e-02 5.4553279e-02 5.8476643e-02 5.7183259e-02 3.8909009e-02 5.8515093e-02 1.0925308e-01 9.9624317e-02 9.6622305e-02 9.8607255e-02 1.0383961e-01 1.0792916e-01 1.0029496e-01 1.0408880e-01 1.1184636e-01 8.9608429e-02 7.6157674e-02 9.6354194e-02 9.0095873e-02 1.0708448e-01 1.0618312e-01 8.7751513e-02 8.9756663e-02 8.8888346e-02 1.2885985e-01 1.0591974e-01 9.1173391e-02 9.5499762e-02 1.1429851e-01 8.5048829e-02 8.8069568e-02 8.8675858e-02 8.0228990e-02 7.8160661e-02 1.0454059e-01 8.7419578e-02 1.0196835e-01 7.7696889e-02 1.0615601e-01 8.3458946e-02 1.0727792e-01 9.7348785e-02 9.3770662e-02 8.8214646e-02 7.6638467e-02 8.3519036e-02 9.5849316e-02 7.9674217e-02 9.9624317e-02 9.7643485e-02 9.4514516e-02 8.7075933e-02 9.6245490e-02 8.4428758e-02 8.8195121e-02 8.6900395e-02 3.3656636e-03 1.2813341e-03 1.5508786e-03 5.5126016e-04 5.7906981e-02 6.0932190e-02 6.7790041e-02 7.7661589e-02 7.1242410e-02 7.8649743e-02 6.7963400e-02 5.4275670e-02 6.5215187e-02 7.0651008e-02 7.2495968e-02 6.3298898e-02 6.9915496e-02 7.7039648e-02 4.7868354e-02 5.5074415e-02 7.9095397e-02 6.1872293e-02 8.9630734e-02 6.4010779e-02 8.3786653e-02 5.5727300e-02 9.1943867e-02 7.7179306e-02 5.8787276e-02 5.8290887e-02 7.2059853e-02 7.8226343e-02 7.3481150e-02 4.5978845e-02 6.4704253e-02 5.9980041e-02 5.7435326e-02 1.0050646e-01 8.3655459e-02 6.5308029e-02 6.4668898e-02 7.9130910e-02 6.2159352e-02 7.1908147e-02 8.2739305e-02 7.1488143e-02 6.3159434e-02 5.5376396e-02 7.2163727e-02 6.2508559e-02 6.5824794e-02 6.1340761e-02 4.0472995e-02 6.4599849e-02 1.2380425e-01 1.0993395e-01 1.0373859e-01 1.0879322e-01 1.1440237e-01 1.1495444e-01 1.1335001e-01 1.1089530e-01 1.1789001e-01 1.0041407e-01 8.4708778e-02 1.0336455e-01 9.7357032e-02 1.1642404e-01 1.1748667e-01 9.8081847e-02 9.8747663e-02 9.9805801e-02 1.3456454e-01 1.1060300e-01 9.9947092e-02 1.0709744e-01 1.2004751e-01 9.0729860e-02 9.8544296e-02 9.6489030e-02 8.6759859e-02 8.7175826e-02 1.1367288e-01 9.3122372e-02 1.0688813e-01 8.6280635e-02 1.1536030e-01 9.0496365e-02 1.1670380e-01 1.0195807e-01 1.0724940e-01 9.8472199e-02 8.5899722e-02 9.0288199e-02 1.0497525e-01 8.5251170e-02 1.0993395e-01 1.0787024e-01 1.0524763e-01 9.3789157e-02 1.0121309e-01 9.2226653e-02 1.0148816e-01 9.8306001e-02 5.0782435e-04 6.2020689e-04 1.6756986e-03 7.0476887e-02 7.1501157e-02 8.0709837e-02 9.2336294e-02 8.5349594e-02 8.8954923e-02 7.7301298e-02 6.5155686e-02 7.8476830e-02 8.0402665e-02 8.7884855e-02 7.3650267e-02 8.6974315e-02 8.8299731e-02 5.7895678e-02 6.7595783e-02 8.7665774e-02 7.3314500e-02 1.0776923e-01 7.6895020e-02 9.2474273e-02 6.8514181e-02 1.0728576e-01 8.8812805e-02 7.1614033e-02 7.1214401e-02 8.6845407e-02 9.1428310e-02 8.4778392e-02 5.8702706e-02 7.8223013e-02 7.3389903e-02 6.9650470e-02 1.1290739e-01 9.1229062e-02 7.2678536e-02 7.7045799e-02 9.6518176e-02 7.0683794e-02 8.4677029e-02 9.3753287e-02 8.2039996e-02 7.6152676e-02 6.7885263e-02 8.3273481e-02 7.1166737e-02 7.5618620e-02 7.3311647e-02 5.2129744e-02 7.5414559e-02 1.3361288e-01 1.2212885e-01 1.1803753e-01 1.2062983e-01 1.2681873e-01 1.3003785e-01 1.2339855e-01 1.2552093e-01 1.3397302e-01 1.1145011e-01 9.6072866e-02 1.1773621e-01 1.1108813e-01 1.3021606e-01 1.2991222e-01 1.0941117e-01 1.1074904e-01 1.1019635e-01 1.5245649e-01 1.2709803e-01 1.1272646e-01 1.1805571e-01 1.3644429e-01 1.0507143e-01 1.0948152e-01 1.0908044e-01 1.0002018e-01 9.8259510e-02 1.2725753e-01 1.0697349e-01 1.2296793e-01 9.7285465e-02 1.2913723e-01 1.0312519e-01 1.2920697e-01 1.1832381e-01 1.1655849e-01 1.0932054e-01 9.6669076e-02 1.0377916e-01 1.1803847e-01 9.9488230e-02 1.2212885e-01 1.2004765e-01 1.1693776e-01 1.0790940e-01 1.1723226e-01 1.0500092e-01 1.1038445e-01 1.0827603e-01 1.5232780e-04 4.0007181e-04 6.5289213e-02 6.7204649e-02 7.5443787e-02 8.6444762e-02 7.9660759e-02 8.4749914e-02 7.3546883e-02 6.0641799e-02 7.3012359e-02 7.6531840e-02 8.1598668e-02 6.9499172e-02 7.9923854e-02 8.3724381e-02 5.3799024e-02 6.2453508e-02 8.4253942e-02 6.8486788e-02 1.0054294e-01 7.1576362e-02 8.9122210e-02 6.3281093e-02 1.0114322e-01 8.3993466e-02 6.6335786e-02 6.5925957e-02 8.0814161e-02 8.6165539e-02 8.0247902e-02 5.3368917e-02 7.2666724e-02 6.7816556e-02 6.4623203e-02 1.0800053e-01 8.8235002e-02 6.9728892e-02 7.2010954e-02 8.9470933e-02 6.7181463e-02 7.9529500e-02 8.9243318e-02 7.7739806e-02 7.0823200e-02 6.2720078e-02 7.8762930e-02 6.7548169e-02 7.1608713e-02 6.8390856e-02 4.7339693e-02 7.1004341e-02 1.3006516e-01 1.1747357e-01 1.1247378e-01 1.1599806e-01 1.2210677e-01 1.2411360e-01 1.1962941e-01 1.1968938e-01 1.2763702e-01 1.0730056e-01 9.1685935e-02 1.1214725e-01 1.0578284e-01 1.2500364e-01 1.2540001e-01 1.0518188e-01 1.0602704e-01 1.0612761e-01 1.4550231e-01 1.2054520e-01 1.0786099e-01 1.1396219e-01 1.2996046e-01 9.9460763e-02 1.0527691e-01 1.0405039e-01 9.4844256e-02 9.3945852e-02 1.2206303e-01 1.0136794e-01 1.1659636e-01 9.2882126e-02 1.2391115e-01 9.8044098e-02 1.2415930e-01 1.1196299e-01 1.1316845e-01 1.0506061e-01 9.2491851e-02 9.8554383e-02 1.1313163e-01 9.4057839e-02 1.1747357e-01 1.1542880e-01 1.1260458e-01 1.0255212e-01 1.1101490e-01 1.0006494e-01 1.0712947e-01 1.0442482e-01 3.2637736e-04 7.1228332e-02 7.3360309e-02 8.1848237e-02 9.3197783e-02 8.6156854e-02 9.1733116e-02 8.0052769e-02 6.6468809e-02 7.9277125e-02 8.3130576e-02 8.8073461e-02 7.5751270e-02 8.6114214e-02 9.0610516e-02 5.9264749e-02 6.8242422e-02 9.1255768e-02 7.4667198e-02 1.0756842e-01 7.7803425e-02 9.6260495e-02 6.9087389e-02 1.0845861e-01 9.0870006e-02 7.2305626e-02 7.1861030e-02 8.7311178e-02 9.3009359e-02 8.6960677e-02 5.8641082e-02 7.8892851e-02 7.3809649e-02 7.0554233e-02 1.1577001e-01 9.5424873e-02 7.6110400e-02 7.8288785e-02 9.6097634e-02 7.3451146e-02 8.6123272e-02 9.6377446e-02 8.4403314e-02 7.7003085e-02 6.8529959e-02 8.5437768e-02 7.3849362e-02 7.8032414e-02 7.4531055e-02 5.2299301e-02 7.7339214e-02 1.3853845e-01 1.2553252e-01 1.2026484e-01 1.2407451e-01 1.3030382e-01 1.3228892e-01 1.2783055e-01 1.2774776e-01 1.3586522e-01 1.1498861e-01 9.8800101e-02 1.1991948e-01 1.1333278e-01 1.3320266e-01 1.3352807e-01 1.1273476e-01 1.1373642e-01 1.1391093e-01 1.5417322e-01 1.2849633e-01 1.1550662e-01 1.2189074e-01 1.3824515e-01 1.0674666e-01 1.1296262e-01 1.1166908e-01 1.0200786e-01 1.0119861e-01 1.3020916e-01 1.0880524e-01 1.2443747e-01 1.0015009e-01 1.3208523e-01 1.0543261e-01 1.3248948e-01 1.1957063e-01 1.2107326e-01 1.1278773e-01 9.9690412e-02 1.0583253e-01 1.2090813e-01 1.0100447e-01 1.2553252e-01 1.2339293e-01 1.2039941e-01 1.0985773e-01 1.1861026e-01 1.0744824e-01 1.1483872e-01 1.1213527e-01 6.6878313e-02 6.9715409e-02 7.7364528e-02 8.8171063e-02 8.1336125e-02 8.7984980e-02 7.6788970e-02 6.2680317e-02 7.4638540e-02 7.9773718e-02 8.2744330e-02 7.2225777e-02 8.0191157e-02 8.6590874e-02 5.5913229e-02 6.3969698e-02 8.8177048e-02 7.0519412e-02 1.0143390e-01 7.3287383e-02 9.3242485e-02 6.4744474e-02 1.0311960e-01 8.6619094e-02 6.7881619e-02 6.7447267e-02 8.2187438e-02 8.8483348e-02 8.3036483e-02 5.4275541e-02 7.4193034e-02 6.9119612e-02 6.6341718e-02 1.1134910e-01 9.2648103e-02 7.3522039e-02 7.4021876e-02 9.0141442e-02 7.0408618e-02 8.1718494e-02 9.2345175e-02 8.0646828e-02 7.2501162e-02 6.4223536e-02 8.1515678e-02 7.0682605e-02 7.4552513e-02 7.0364536e-02 4.8478235e-02 7.3560525e-02 1.3517901e-01 1.2131760e-01 1.1535330e-01 1.1982809e-01 1.2602646e-01 1.2698846e-01 1.2430464e-01 1.2251062e-01 1.3021845e-01 1.1127258e-01 9.4973617e-02 1.1499323e-01 1.0869119e-01 1.2854331e-01 1.2950169e-01 1.0899747e-01 1.0948608e-01 1.1017110e-01 1.4797664e-01 1.2271397e-01 1.1121974e-01 1.1817834e-01 1.3245873e-01 1.0189203e-01 1.0916690e-01 1.0716280e-01 9.7527876e-02 9.7386912e-02 1.2555051e-01 1.0384747e-01 1.1883003e-01 9.6216108e-02 1.2741273e-01 1.0091958e-01 1.2779606e-01 1.1407020e-01 1.1794445e-01 1.0890370e-01 9.6003260e-02 1.0130628e-01 1.1658801e-01 9.6417670e-02 1.2131760e-01 1.1923852e-01 1.1654354e-01 1.0526503e-01 1.1322934e-01 1.0313209e-01 1.1184758e-01 1.0860395e-01 7.4548764e-04 4.1909104e-04 1.5729317e-03 7.9277531e-04 2.5985333e-03 2.1157871e-03 2.6363318e-04 2.7214324e-04 2.5006701e-03 1.1999234e-03 1.3641574e-03 2.4228657e-03 1.9193080e-03 1.5580763e-03 9.1547441e-05 4.2498838e-03 5.1587623e-04 4.4451535e-03 1.9710136e-04 5.9468529e-03 1.1637586e-04 4.0879090e-03 1.9577278e-03 7.5438506e-06 5.1321994e-05 9.5746588e-04 1.8362275e-03 1.6353958e-03 8.5567943e-04 2.3534169e-04 2.0155706e-04 2.8744085e-05 6.6464816e-03 6.0500135e-03 3.7609984e-03 3.0183871e-04 2.6908058e-03 1.8208811e-03 9.2994693e-04 3.0287331e-03 1.4489077e-03 1.1766146e-04 3.4058119e-05 1.3255619e-03 1.5104843e-03 1.2375803e-03 1.2042792e-04 2.2770079e-03 7.0925866e-04 1.7863102e-02 1.0250835e-02 7.3485693e-03 9.3316539e-03 1.1683662e-02 1.0169272e-02 1.2909901e-02 9.0217168e-03 1.1005819e-02 9.1383359e-03 4.6331293e-03 7.2658380e-03 6.2625155e-03 1.2163934e-02 1.5604510e-02 9.0849374e-03 6.4499430e-03 7.6430735e-03 1.6734199e-02 8.8633570e-03 7.8095179e-03 1.1083442e-02 1.1720144e-02 4.3192019e-03 7.6217573e-03 5.4833250e-03 3.8602370e-03 4.7396014e-03 1.0813609e-02 4.4772013e-03 7.7667524e-03 3.8237256e-03 1.1654961e-02 4.0471444e-03 1.1640132e-02 6.9842505e-03 1.3199398e-02 6.9808241e-03 4.8195688e-03 4.8204301e-03 9.7963275e-03 5.3012133e-03 1.0250835e-02 1.0025463e-02 1.1016974e-02 6.8718627e-03 6.8391566e-03 5.4173007e-03 1.1780352e-02 7.9156890e-03 7.3587704e-04 1.9839586e-03 1.2219042e-03 1.5405721e-03 4.1726993e-04 3.7297590e-04 1.1835574e-03 6.1737386e-04 2.7177989e-03 1.1395438e-04 5.3634867e-03 1.2095802e-03 9.5552631e-04 7.0211590e-04 1.8297765e-03 1.3098829e-03 5.8522900e-03 1.0011765e-03 2.8317516e-03 7.2859483e-04 4.4710316e-03 2.0244176e-03 7.6733040e-04 6.7685529e-04 2.1301657e-03 1.3518842e-03 6.1205846e-04 2.4644310e-03 1.1938153e-03 1.5975309e-03 5.1076446e-04 5.2791382e-03 3.0339660e-03 1.1907842e-03 3.5011674e-04 4.7257393e-03 3.5465173e-04 7.3647426e-04 2.3478030e-03 5.8214999e-04 7.6861523e-04 8.1449121e-04 5.9893306e-04 4.9750178e-04 2.3193980e-04 3.6882491e-04 2.8017975e-03 9.9802686e-05 1.3102890e-02 7.5969761e-03 6.0828162e-03 7.3064415e-03 8.8657957e-03 9.3996859e-03 9.1529212e-03 8.6958624e-03 1.0707084e-02 5.8653516e-03 2.3848099e-03 6.0110419e-03 4.6435179e-03 9.5841132e-03 1.1495876e-02 5.7351815e-03 4.7762002e-03 5.2279685e-03 1.6343054e-02 9.1684398e-03 5.3835136e-03 7.5338144e-03 1.1677909e-02 3.4411805e-03 4.9237859e-03 4.5530879e-03 2.5678146e-03 2.5808970e-03 8.5897045e-03 4.5950787e-03 7.8389611e-03 2.4182455e-03 9.2053995e-03 3.4479875e-03 1.0780684e-02 6.4366206e-03 8.7029459e-03 4.8244677e-03 2.4820766e-03 3.2994674e-03 6.9725103e-03 3.6004536e-03 7.5969761e-03 7.2055876e-03 7.4680636e-03 4.8244418e-03 6.1219436e-03 3.5524443e-03 7.3785563e-03 5.0327851e-03 4.5958248e-04 1.4323573e-04 1.2376608e-03 1.4854110e-03 8.9326822e-04 1.4516004e-04 1.6538068e-03 6.5338340e-04 1.1299287e-03 2.4074626e-03 6.9551596e-04 2.6949476e-03 7.2730987e-04 2.7608296e-03 6.5430290e-04 2.7448595e-03 1.4433165e-04 4.0981488e-03 7.0217976e-04 2.0017194e-03 8.4737221e-04 3.5875154e-04 4.2590326e-04 3.7194881e-04 5.5222558e-04 6.3134375e-04 2.4465093e-03 1.5551077e-04 5.7141821e-04 4.4617793e-04 3.7751002e-03 4.2307180e-03 3.3297057e-03 8.4238856e-05 1.8060244e-03 1.6735237e-03 1.3470834e-04 1.4289074e-03 6.1420964e-04 1.0488316e-04 6.6710002e-04 4.5037482e-04 1.3817339e-03 8.2351922e-04 1.8947713e-04 4.2527867e-03 4.2014447e-04 1.3415184e-02 6.6868372e-03 4.2910287e-03 5.8862692e-03 7.8406972e-03 6.4938295e-03 9.0912854e-03 5.6561218e-03 7.2322567e-03 6.1641630e-03 2.7540356e-03 4.2344365e-03 3.5847503e-03 8.2354910e-03 1.1610160e-02 6.2859853e-03 3.6577005e-03 4.7344286e-03 1.2033367e-02 5.6148647e-03 4.9101139e-03 7.6703569e-03 7.8664866e-03 2.1500523e-03 4.7726337e-03 2.9188064e-03 1.9107921e-03 2.6804057e-03 7.0603390e-03 2.2776004e-03 4.6992435e-03 1.8838392e-03 7.7943774e-03 1.9124732e-03 7.9102721e-03 4.1636243e-03 9.7823337e-03 4.1721395e-03 2.8414673e-03 2.6224860e-03 6.5787438e-03 3.5255969e-03 6.6868372e-03 6.5865547e-03 7.7526002e-03 4.4665713e-03 4.0404299e-03 3.0508638e-03 8.7371106e-03 5.0758684e-03 1.4741381e-04 1.6125952e-03 2.5451963e-03 2.5693849e-03 7.6673984e-04 2.4419486e-03 4.9090126e-04 2.2351893e-03 2.2914219e-03 9.4806103e-04 4.8625587e-03 2.0262116e-03 3.3701508e-03 1.9166326e-03 1.0683081e-03 9.0452408e-04 4.3648468e-03 1.8983778e-03 7.0324108e-04 1.1499379e-03 1.4013722e-03 1.4463883e-03 3.0992383e-04 2.7889447e-04 9.9370768e-04 4.4215550e-03 7.4609763e-04 1.5322654e-03 1.6670592e-03 2.5458750e-03 4.8164855e-03 4.9491294e-03 7.9622502e-04 9.3705610e-04 3.2918730e-03 3.3244867e-04 1.4917733e-03 1.2997427e-03 8.4964648e-04 1.9833350e-03 9.7663737e-04 3.0207145e-03 1.9767871e-03 1.2186915e-03 6.6584952e-03 1.4294810e-03 1.1447125e-02 4.9138585e-03 2.4483433e-03 4.3479747e-03 5.8020082e-03 4.0665925e-03 7.5951000e-03 3.6015682e-03 4.5184530e-03 5.0362911e-03 2.3873447e-03 2.3916463e-03 2.1166084e-03 5.8005881e-03 9.3326086e-03 5.1921662e-03 2.5583136e-03 3.9040792e-03 8.1942534e-03 3.2098756e-03 3.4610228e-03 6.2351763e-03 5.0378736e-03 9.3974884e-04 3.8135545e-03 1.8905878e-03 1.0935023e-03 2.2739982e-03 4.8352649e-03 1.2660181e-03 2.5068904e-03 1.6511455e-03 5.4229770e-03 1.1989559e-03 6.1054655e-03 2.0081796e-03 8.6788304e-03 3.3159020e-03 2.5546078e-03 1.5534900e-03 4.7975700e-03 2.5264412e-03 4.9138585e-03 4.8875866e-03 6.2264786e-03 3.0375207e-03 1.9750388e-03 2.0217929e-03 8.0063599e-03 4.3571190e-03 1.6611913e-03 2.0167984e-03 1.5438876e-03 3.6494229e-04 2.0217253e-03 4.6462511e-04 1.5312613e-03 2.1707116e-03 9.6955753e-04 3.3920004e-03 1.0848877e-03 3.2679078e-03 1.3217642e-03 1.7681092e-03 4.3634818e-04 4.3762590e-03 9.8982281e-04 1.4702047e-03 1.2309511e-03 6.6753502e-04 6.7519636e-04 2.4877587e-04 3.8105160e-04 8.2183923e-04 3.0399429e-03 3.2267433e-04 8.9534104e-04 8.4272052e-04 3.5139014e-03 4.8234985e-03 4.1432495e-03 3.0463297e-04 1.2193814e-03 2.4923401e-03 1.7011388e-04 1.7584273e-03 1.0444437e-03 3.3026408e-04 1.0723664e-03 7.7198092e-04 2.2859213e-03 1.4276818e-03 5.7687029e-04 4.8466907e-03 8.6936394e-04 1.2850654e-02 6.1088154e-03 3.5569698e-03 5.5719646e-03 7.1488198e-03 5.6571014e-03 8.7634622e-03 5.0674178e-03 6.2538450e-03 5.7468537e-03 2.5694606e-03 3.4855366e-03 2.9211508e-03 7.2454781e-03 1.0560745e-02 5.7784909e-03 3.4230741e-03 4.7152707e-03 1.0513823e-02 4.7114505e-03 4.2697686e-03 7.1803873e-03 6.8688743e-03 1.5447042e-03 4.5401495e-03 2.7361382e-03 1.4855517e-03 2.5857007e-03 6.2270622e-03 2.0576267e-03 3.8519560e-03 2.0130817e-03 6.8461641e-03 1.8250891e-03 7.7109246e-03 3.0910476e-03 9.4645289e-03 4.1075164e-03 2.7837137e-03 2.0667492e-03 5.7442304e-03 2.7416306e-03 6.1088154e-03 5.9744542e-03 7.0784056e-03 3.5969994e-03 3.0144395e-03 2.5834027e-03 8.5675229e-03 5.0446428e-03 9.3410747e-04 2.6108336e-03 1.8631164e-03 1.0514258e-03 2.7757095e-03 1.6027319e-03 5.9009151e-03 9.3507643e-05 4.8585250e-03 3.2279691e-03 7.0814372e-04 1.8064210e-03 4.7021523e-03 1.7906187e-03 1.7438865e-03 3.2726931e-03 2.1092470e-03 4.4199215e-04 2.5742583e-03 2.7576288e-03 2.1865740e-03 8.5954487e-04 4.3875130e-04 6.2385517e-03 2.0706463e-03 2.9895922e-03 2.4900150e-03 1.5452115e-03 1.2972442e-03 2.2089282e-03 1.3825902e-03 4.4047937e-03 1.3378707e-03 8.5528525e-04 1.4287812e-04 2.3200710e-04 1.9018574e-03 3.1193134e-03 2.7835858e-04 1.0440284e-03 6.7774868e-04 1.6434489e-03 8.2787646e-03 9.1805293e-04 8.2830461e-03 3.6334874e-03 2.7936555e-03 2.6701226e-03 4.5417785e-03 4.4199379e-03 4.7638022e-03 3.6853341e-03 5.4544340e-03 3.4799734e-03 1.6014887e-03 2.8117239e-03 2.4093914e-03 5.5035080e-03 8.3987820e-03 4.0331795e-03 1.3946498e-03 1.6013776e-03 9.9231424e-03 4.6841468e-03 3.0824955e-03 4.3724199e-03 6.0877536e-03 1.8755546e-03 2.0838499e-03 1.1387331e-03 1.4636530e-03 1.0937142e-03 4.3877001e-03 1.4753015e-03 3.8552640e-03 2.5868499e-04 5.1223304e-03 7.0168484e-04 4.3920187e-03 3.9679568e-03 5.8082286e-03 1.3720301e-03 1.2437314e-03 1.9911684e-03 4.5361159e-03 3.9571028e-03 3.6334874e-03 3.7423473e-03 5.0666856e-03 3.9975266e-03 3.7265040e-03 1.8650355e-03 5.0595778e-03 1.9211780e-03 1.4802848e-03 2.3288314e-03 6.8065434e-05 4.0423471e-03 2.2193959e-04 7.6466779e-03 9.2054349e-04 2.1021154e-03 2.1770952e-03 5.4412625e-04 2.3412508e-03 6.7735470e-03 2.1146424e-03 1.1473823e-03 2.2087962e-03 4.4699141e-03 2.0616206e-03 2.1327263e-03 2.0380999e-03 3.2539726e-03 1.3598045e-03 3.9670027e-04 4.8634117e-03 2.4197901e-03 3.2204932e-03 1.7622064e-03 3.8856461e-03 1.2334789e-03 4.0452577e-04 1.0887743e-03 6.2435560e-03 2.0292084e-04 1.0829901e-03 1.7786841e-03 4.1228310e-04 1.8859956e-03 2.3286589e-03 5.1772577e-04 4.9172870e-04 2.0007065e-04 1.2918676e-03 5.0330599e-03 3.9898095e-04 9.4287570e-03 5.3866372e-03 4.8647393e-03 5.2819054e-03 6.4598280e-03 8.0073865e-03 6.1852254e-03 7.5566712e-03 9.5128884e-03 3.7267827e-03 1.2843714e-03 4.8237356e-03 3.5085400e-03 7.3800011e-03 8.6672418e-03 3.7373665e-03 3.3006522e-03 3.2553459e-03 1.4768796e-02 8.5372346e-03 3.7597131e-03 5.0208833e-03 1.0540608e-02 2.9216135e-03 2.9987594e-03 3.5026889e-03 1.9346800e-03 1.3415748e-03 6.5619943e-03 4.2356898e-03 7.2096387e-03 1.4370251e-03 7.0771420e-03 2.7742326e-03 8.9575949e-03 5.8574207e-03 5.7119619e-03 3.0499081e-03 1.2067939e-03 2.4757916e-03 5.0772246e-03 3.1235792e-03 5.3866372e-03 5.0347068e-03 5.1609892e-03 3.8212989e-03 5.4644467e-03 2.4256658e-03 4.6018293e-03 2.9278326e-03 8.9754714e-04 1.9308173e-03 2.4780225e-03 8.6351250e-04 4.2337045e-03 2.1236680e-03 7.2381764e-04 2.1294516e-04 3.5897400e-03 7.7335429e-04 6.4715989e-03 7.0701488e-04 5.1814848e-03 2.9010443e-04 5.5409554e-03 2.4566735e-03 3.3429771e-04 3.2970103e-04 2.0470962e-03 2.3835444e-03 1.6383998e-03 1.0044594e-03 8.9092263e-04 8.3257442e-04 1.5048347e-04 7.3947599e-03 5.1765389e-03 2.4819362e-03 5.2929628e-04 4.5409213e-03 9.8467896e-04 1.3414871e-03 3.3640577e-03 1.3544701e-03 5.6680732e-04 2.1931945e-04 1.3672243e-03 8.5440953e-04 8.3314842e-04 2.6161270e-04 1.7366856e-03 5.2799539e-04 1.7649847e-02 1.0764587e-02 8.4383096e-03 9.9924928e-03 1.2273825e-02 1.1788899e-02 1.2850999e-02 1.0630699e-02 1.2964118e-02 9.0438994e-03 4.5288546e-03 8.3580766e-03 6.9988358e-03 1.3044370e-02 1.5771615e-02 8.9276151e-03 7.0025139e-03 7.7681345e-03 1.9248623e-02 1.0890288e-02 8.2148016e-03 1.1050411e-02 1.3845989e-02 5.2067991e-03 7.6819868e-03 6.2976924e-03 4.3733262e-03 4.7066273e-03 1.1733076e-02 5.6953630e-03 9.5825887e-03 4.0184455e-03 1.2555508e-02 4.8201445e-03 1.2938333e-02 8.5012649e-03 1.2613331e-02 7.2187981e-03 4.6491251e-03 5.3739269e-03 1.0213138e-02 5.7106829e-03 1.0764587e-02 1.0424589e-02 1.1013685e-02 7.3861586e-03 8.2413252e-03 5.8213434e-03 1.1031181e-02 7.7860554e-03 2.6482878e-03 4.2794287e-04 1.8319104e-03 1.5871773e-03 1.2340260e-03 2.9761573e-03 6.4181441e-04 3.9337040e-03 3.6570436e-04 2.9155752e-03 1.3382816e-05 5.6692471e-03 6.4631376e-04 2.4262663e-03 1.0063227e-03 2.3414789e-04 3.8352406e-04 2.6670153e-04 1.1841642e-03 1.3362953e-03 1.8214422e-03 1.1939941e-05 1.6841308e-04 4.0205285e-04 4.8423454e-03 5.6256276e-03 4.4224155e-03 3.0861840e-04 1.5326607e-03 2.2893524e-03 5.4845532e-04 1.9363566e-03 1.1483728e-03 6.7060376e-05 4.8890125e-04 9.7136579e-04 1.7676633e-03 1.3375465e-03 2.5372175e-04 4.0846507e-03 8.2917869e-04 1.6077651e-02 8.4460632e-03 5.5177458e-03 7.2638972e-03 9.7215743e-03 7.5597148e-03 1.1218034e-02 6.4234899e-03 8.1582316e-03 8.1119193e-03 4.1520915e-03 5.4624397e-03 4.9276011e-03 1.0142169e-02 1.4194600e-02 8.3023965e-03 4.8434975e-03 6.1466052e-03 1.3239048e-02 6.2447391e-03 6.5902144e-03 9.7724034e-03 8.6919298e-03 3.1454944e-03 6.3952659e-03 3.7622934e-03 3.0039560e-03 4.0045050e-03 8.7466217e-03 2.6847018e-03 5.4026497e-03 2.7900002e-03 9.6269482e-03 2.5687827e-03 8.7444600e-03 5.2241473e-03 1.2215015e-02 5.4919656e-03 4.2251052e-03 3.8716574e-03 8.5147448e-03 4.9424074e-03 8.4460632e-03 8.4294916e-03 9.9304629e-03 6.0757927e-03 5.1533590e-03 4.4115457e-03 1.1061881e-02 6.6918878e-03 4.2077380e-03 2.8084601e-04 8.0255307e-03 1.0009734e-03 2.4531898e-03 2.5259126e-03 4.7784400e-04 2.9272193e-03 6.4204523e-03 2.4613181e-03 8.1446100e-04 2.5131313e-03 4.2288269e-03 2.3479589e-03 2.4787654e-03 2.3219118e-03 3.4016339e-03 1.1923522e-03 3.6609993e-04 5.5309499e-03 2.7130308e-03 3.6876750e-03 2.1157012e-03 3.5197143e-03 1.1211667e-03 5.0040735e-04 1.2581494e-03 6.2453424e-03 4.9316252e-04 1.0956505e-03 1.9034882e-03 5.6262931e-04 2.1623642e-03 2.7381233e-03 6.2275542e-04 9.1647472e-04 4.3469066e-04 1.6016964e-03 5.5073116e-03 6.0170029e-04 8.2861929e-03 4.5965840e-03 4.2124912e-03 4.7715416e-03 5.5699315e-03 7.3813594e-03 5.3659110e-03 7.1660104e-03 8.8786575e-03 2.9267242e-03 8.0445420e-04 4.1635703e-03 2.8364579e-03 6.3397911e-03 7.3211567e-03 2.8639086e-03 2.8974680e-03 2.8626073e-03 1.3776826e-02 8.0525608e-03 2.9632152e-03 4.1293440e-03 9.9396499e-03 2.4124505e-03 2.4179141e-03 3.2613675e-03 1.4673422e-03 9.3714862e-04 5.6839580e-03 4.1506352e-03 6.7187933e-03 1.3083992e-03 6.0861850e-03 2.6486423e-03 8.7285257e-03 5.1099460e-03 4.7657924e-03 2.6722144e-03 7.9417630e-04 1.8862622e-03 4.0883053e-03 2.3510235e-03 4.5965840e-03 4.1923613e-03 4.1490574e-03 2.9383782e-03 4.7171745e-03 1.8344857e-03 3.7646957e-03 2.4446440e-03 3.4307781e-03 6.9180223e-04 1.9272484e-03 5.2229584e-03 1.7410764e-03 5.5262484e-03 1.3512624e-03 1.2930220e-03 5.9265821e-04 7.2336834e-03 1.6622228e-03 1.4643809e-03 1.4579196e-03 1.0665934e-03 1.2486050e-03 4.3399433e-05 1.3778574e-03 2.2390179e-03 3.1206766e-03 3.9982611e-04 7.0342073e-04 1.4793565e-03 4.5495005e-03 7.4063181e-03 6.9037801e-03 1.1215419e-03 3.4551859e-04 4.3750592e-03 1.0242073e-03 2.4559606e-03 2.2544417e-03 6.9180223e-04 1.5393939e-03 1.8990091e-03 3.7039856e-03 2.8782332e-03 1.2534261e-03 6.1345149e-03 2.1154370e-03 1.6389445e-02 8.1714731e-03 4.6971245e-03 6.9480013e-03 9.2874957e-03 6.0735661e-03 1.1557043e-02 5.0795005e-03 6.2695782e-03 8.5942939e-03 4.8981354e-03 4.6386510e-03 4.5456138e-03 9.3051239e-03 1.4039626e-02 8.8459552e-03 4.7683489e-03 6.5513763e-03 1.0462868e-02 4.3969158e-03 6.5113261e-03 1.0098096e-02 6.6155737e-03 2.7131547e-03 6.8044529e-03 3.4785563e-03 3.0414918e-03 4.6799978e-03 7.9508909e-03 2.0423594e-03 3.8146379e-03 3.3303959e-03 8.7946774e-03 2.4271150e-03 7.7798359e-03 3.9016462e-03 1.3149131e-02 5.7850137e-03 5.0633237e-03 3.7856360e-03 8.3083336e-03 4.9921128e-03 8.1714731e-03 8.2786520e-03 1.0189399e-02 5.8912773e-03 3.9649361e-03 4.4771942e-03 1.2268673e-02 7.3587328e-03 6.5787312e-03 1.3233357e-03 1.1455828e-03 1.2416155e-03 1.4002549e-03 2.1211045e-03 6.2047866e-03 1.6380421e-03 2.0343353e-03 1.2366343e-03 4.6737502e-03 2.4960613e-03 1.3635919e-03 1.1869805e-03 2.7420392e-03 1.3311387e-03 5.4030547e-04 3.4496369e-03 1.8301986e-03 2.4543913e-03 1.0360218e-03 4.9823495e-03 2.4358984e-03 7.7653049e-04 6.6085729e-04 5.4666528e-03 3.6251211e-04 9.0034369e-04 2.5308489e-03 6.8325669e-04 1.3068439e-03 1.4466712e-03 7.0328570e-04 7.2909544e-04 3.3427965e-04 8.2319726e-04 3.3236658e-03 2.7101948e-04 1.1415097e-02 6.6763180e-03 5.5975222e-03 6.7637660e-03 7.8425097e-03 9.1076654e-03 7.9396677e-03 8.6849227e-03 1.0537825e-02 4.7340864e-03 1.6981356e-03 5.5204544e-03 4.0238168e-03 8.5090240e-03 9.7837063e-03 4.5130967e-03 4.3644743e-03 4.6397935e-03 1.5929686e-02 9.2384755e-03 4.4702915e-03 6.2885333e-03 1.1607431e-02 3.1082253e-03 4.1080254e-03 4.4476701e-03 2.1448891e-03 1.9903866e-03 7.7241343e-03 4.8610346e-03 7.8308568e-03 2.2349743e-03 8.1986845e-03 3.4906235e-03 1.0785042e-02 6.0455598e-03 7.1678534e-03 4.3085958e-03 1.8327850e-03 2.7372439e-03 5.8248161e-03 2.8157351e-03 6.6763180e-03 6.1912806e-03 6.1165743e-03 3.9060199e-03 5.6854050e-03 2.9112999e-03 5.9286549e-03 4.2459890e-03 4.7261943e-03 7.5564450e-03 3.0589295e-03 9.9138151e-03 2.5436162e-03 2.3081715e-03 1.8344588e-03 1.2327595e-02 2.9913447e-03 3.4407458e-03 3.5166367e-03 2.2912956e-03 2.5985157e-03 1.0536407e-03 4.0148201e-03 5.3033668e-03 3.3636231e-03 1.5135209e-03 1.3379167e-03 2.9432336e-03 8.2210569e-03 1.2378534e-02 1.1256214e-02 3.0883819e-03 6.3877121e-04 7.6741777e-03 3.3091271e-03 5.2745018e-03 5.1312091e-03 2.0746888e-03 2.6973394e-03 4.6616800e-03 6.5765736e-03 5.8096774e-03 2.9820979e-03 7.3187243e-03 4.6783172e-03 2.3410581e-02 1.3144529e-02 8.3391705e-03 1.1305315e-02 1.4457465e-02 9.3485129e-03 1.7555848e-02 7.8838497e-03 9.1347689e-03 1.4043414e-02 9.2390419e-03 8.2683227e-03 8.4558564e-03 1.4291672e-02 2.0441558e-02 1.4348706e-02 8.7091477e-03 1.1171095e-02 1.3577182e-02 6.6301175e-03 1.1191012e-02 1.5865943e-02 9.2559664e-03 5.8683930e-03 1.1694065e-02 6.7001372e-03 6.5334334e-03 8.9560518e-03 1.2574664e-02 4.2523471e-03 6.2343724e-03 6.8791672e-03 1.3649249e-02 5.2782119e-03 1.1089361e-02 6.8777652e-03 1.9771053e-02 1.0157447e-02 9.4893989e-03 7.5319640e-03 1.3411350e-02 8.8625592e-03 1.3144529e-02 1.3390278e-02 1.5951074e-02 1.0173619e-02 7.0905829e-03 8.5554531e-03 1.8741319e-02 1.2400067e-02 4.2985441e-03 2.4763265e-03 1.0193789e-03 1.4380091e-03 3.6495663e-03 1.2003820e-03 2.0584975e-03 2.4845352e-03 1.6034070e-03 3.3059261e-04 1.8671770e-03 2.0088877e-03 1.4284810e-03 4.3098388e-04 2.3514979e-04 5.2698749e-03 1.3824059e-03 2.2360501e-03 1.8501267e-03 1.6468815e-03 1.8319470e-03 2.4154150e-03 8.5423988e-04 3.3259702e-03 1.3633177e-03 3.9750527e-04 2.1357310e-04 1.4089375e-04 1.2605369e-03 2.3906980e-03 1.0696291e-04 1.0906412e-03 5.9076498e-04 1.1304618e-03 7.2732511e-03 6.4012947e-04 8.9598694e-03 3.8314732e-03 2.5915433e-03 2.9763427e-03 4.7600376e-03 4.3053173e-03 5.3266981e-03 3.6190516e-03 5.2272723e-03 3.6549051e-03 1.5047575e-03 2.5888356e-03 2.1634980e-03 5.5055672e-03 8.5050147e-03 4.0885074e-03 1.5151975e-03 1.9844585e-03 9.6042351e-03 4.2899092e-03 2.9941597e-03 4.6609860e-03 5.8520656e-03 1.4443089e-03 2.3092039e-03 1.1621771e-03 1.1296414e-03 1.1169479e-03 4.4204603e-03 1.2641450e-03 3.4620250e-03 3.4971048e-04 5.1226200e-03 6.2752047e-04 4.7853928e-03 3.3731886e-03 6.3070664e-03 1.6662334e-03 1.2828407e-03 1.6597311e-03 4.4397739e-03 3.3292201e-03 3.8314732e-03 3.8867782e-03 5.1669823e-03 3.5417199e-03 3.1734550e-03 1.6842820e-03 5.5385508e-03 2.3108610e-03 1.0105990e-03 4.7523926e-03 2.9847642e-03 9.7105522e-03 2.6675945e-03 5.8410645e-03 1.0628436e-03 8.9773521e-03 5.4199214e-03 1.6519449e-03 1.3847497e-03 4.5932482e-03 4.2245140e-03 3.0278103e-03 1.7095869e-03 2.8675709e-03 2.8000554e-03 1.2095222e-03 1.0633409e-02 6.3588443e-03 2.3146826e-03 1.8579783e-03 7.7501487e-03 1.5279559e-03 3.0361588e-03 6.2292475e-03 2.9996444e-03 2.2092276e-03 1.2670329e-03 3.0585508e-03 1.9631739e-03 1.9448597e-03 1.6026176e-03 7.3057463e-04 1.6268750e-03 1.9360609e-02 1.3271536e-02 1.1367917e-02 1.3356276e-02 1.4871782e-02 1.6028245e-02 1.4980963e-02 1.5218503e-02 1.7630002e-02 1.0242398e-02 5.4451367e-03 1.1236806e-02 9.0841292e-03 1.5577818e-02 1.6739427e-02 9.6435556e-03 9.8426714e-03 1.0341095e-02 2.4442578e-02 1.5482723e-02 9.8424496e-03 1.2530745e-02 1.8870958e-02 7.3605563e-03 9.5556413e-03 9.6316193e-03 6.0281749e-03 6.1257342e-03 1.4596779e-02 9.4938425e-03 1.3739836e-02 6.3629397e-03 1.5181016e-02 7.9755348e-03 1.8111593e-02 1.1305790e-02 1.3376686e-02 9.8378125e-03 5.8135644e-03 6.9819428e-03 1.1631750e-02 6.1445613e-03 1.3271536e-02 1.2517306e-02 1.1992997e-02 8.3045136e-03 1.0906252e-02 7.4644491e-03 1.1536762e-02 9.7524154e-03 4.5852486e-03 9.8595493e-04 5.1231602e-03 5.3641159e-04 6.1291344e-03 9.0289414e-06 4.9719488e-03 2.7623691e-03 1.0135570e-04 4.9630072e-05 1.4573369e-03 2.2050307e-03 1.9067713e-03 6.5935996e-04 5.5655703e-04 4.9349156e-04 5.3910141e-05 7.6334144e-03 6.4541501e-03 3.6168243e-03 4.5600228e-04 3.3337204e-03 1.8551457e-03 1.2329546e-03 3.8576017e-03 1.8263928e-03 3.4082293e-04 2.8939589e-05 1.6992773e-03 1.7320592e-03 1.4340517e-03 2.7567571e-04 1.5103910e-03 8.5202938e-04 1.8535044e-02 1.1041503e-02 8.1659733e-03 1.0422568e-02 1.2515324e-02 1.1445689e-02 1.3674148e-02 1.0415137e-02 1.2394915e-02 9.4870366e-03 4.7984401e-03 8.0611256e-03 6.7876377e-03 1.2924053e-02 1.5886474e-02 9.2445598e-03 7.3109344e-03 8.4847047e-03 1.8293716e-02 1.0175519e-02 8.2306422e-03 1.1561315e-02 1.3225081e-02 4.8091709e-03 8.1976852e-03 6.4746796e-03 4.2076714e-03 5.1007444e-03 1.1683626e-02 5.5527590e-03 8.9342726e-03 4.4973686e-03 1.2442734e-02 4.9468838e-03 1.3343592e-02 7.6599210e-03 1.3497447e-02 7.8132331e-03 5.1004820e-03 5.1592495e-03 1.0155636e-02 5.1788357e-03 1.1041503e-02 1.0666367e-02 1.1283660e-02 6.9627068e-03 7.4793969e-03 5.8082013e-03 1.1981463e-02 8.5614846e-03 3.8688652e-03 7.5179133e-03 3.7407669e-03 3.1676786e-04 4.6173801e-03 4.2799166e-03 2.2501511e-03 4.2413983e-03 4.2204030e-03 4.6109813e-03 1.7937707e-03 8.2328963e-04 8.3021324e-03 4.1326900e-03 5.3767829e-03 3.8579826e-03 2.3734756e-03 1.6523409e-04 8.8835369e-04 2.5288831e-03 7.6911436e-03 1.1035999e-03 1.9572002e-03 1.3626197e-03 8.4501724e-04 3.6117590e-03 4.7013989e-03 1.0056440e-03 1.3674472e-03 9.9055737e-04 2.9505217e-03 8.8795046e-03 1.5067108e-03 6.0099517e-03 3.2148697e-03 3.5475051e-03 3.0620571e-03 4.0315627e-03 6.0415844e-03 3.4044063e-03 5.7826017e-03 7.5784861e-03 2.1065729e-03 8.8233953e-04 3.5528527e-03 2.5792431e-03 5.1245213e-03 6.2842876e-03 2.4095844e-03 1.8446522e-03 1.4167723e-03 1.2151930e-02 7.2262167e-03 2.4839286e-03 2.9019075e-03 8.5383029e-03 2.6181282e-03 1.4064800e-03 2.3288946e-03 1.7187301e-03 6.4203056e-04 4.4078979e-03 3.6116427e-03 6.0606537e-03 7.3312980e-04 4.8772717e-03 2.0701887e-03 6.3495233e-03 5.1782781e-03 3.3738477e-03 1.3922890e-03 5.6378115e-04 2.0528392e-03 3.5526857e-03 3.4142665e-03 3.2148697e-03 3.0384066e-03 3.3899600e-03 3.3852248e-03 4.7610217e-03 1.6939002e-03 2.6027995e-03 1.1563987e-03 4.9937739e-03 2.8043563e-04 6.0192505e-03 1.1047123e-03 3.7340869e-03 9.3891763e-04 5.7693223e-04 8.5742923e-04 1.1063653e-03 2.1518410e-03 1.7873418e-03 1.8443813e-03 4.7874237e-04 4.0562362e-04 6.3806313e-04 5.6896950e-03 5.3990383e-03 4.0838955e-03 7.6270784e-04 3.0255235e-03 1.8702287e-03 1.2256654e-03 1.9416278e-03 1.1863062e-03 4.8488269e-04 7.2384993e-04 1.1782779e-03 1.1397261e-03 1.1824844e-03 4.5012569e-04 4.3416102e-03 9.3532010e-04 1.7514227e-02 9.8600607e-03 7.1630436e-03 8.1480968e-03 1.1292909e-02 9.1710206e-03 1.2255610e-02 7.6051240e-03 9.9403988e-03 9.4620221e-03 5.2227781e-03 7.1398046e-03 6.5861173e-03 1.2201906e-02 1.6541006e-02 9.8696183e-03 5.7022093e-03 6.6806000e-03 1.5779735e-02 7.9447125e-03 8.2548534e-03 1.1179561e-02 1.0455124e-02 4.7301378e-03 7.3678164e-03 4.4876356e-03 4.3705173e-03 4.8557182e-03 1.0520487e-02 3.4331839e-03 7.0769130e-03 3.1675776e-03 1.1611243e-02 3.1862426e-03 9.1961094e-03 7.4495828e-03 1.3429738e-02 6.1002834e-03 5.0525215e-03 5.4421147e-03 1.0502545e-02 7.0546448e-03 9.8600607e-03 9.9581080e-03 1.1708746e-02 8.2306703e-03 7.3218404e-03 5.8241089e-03 1.2089443e-02 7.3253099e-03 3.2913459e-03 8.4499731e-03 4.8569537e-03 8.0216351e-04 3.5049401e-03 4.1209949e-03 4.1951632e-03 1.4430192e-03 2.1132422e-03 3.9581533e-03 7.7606295e-03 2.8139659e-03 3.8070595e-03 4.7702994e-03 3.8227367e-03 9.3108167e-03 1.0381275e-02 3.5346820e-03 5.2926972e-04 8.0977990e-03 2.5864687e-03 3.9952884e-03 4.6152713e-03 3.2808967e-03 5.0209478e-03 3.9992007e-03 7.6245140e-03 5.9395645e-03 4.2477403e-03 1.1072543e-02 4.9514063e-03 1.3733574e-02 6.2131974e-03 2.8230097e-03 5.6455623e-03 6.8638978e-03 3.4892544e-03 9.9148605e-03 3.2690483e-03 3.3086076e-03 7.4423917e-03 5.1197423e-03 2.7541244e-03 3.1219477e-03 6.1841097e-03 1.0726676e-02 7.6421585e-03 4.1594455e-03 6.2994749e-03 5.6115691e-03 2.0176931e-03 4.9542156e-03 8.4395555e-03 3.5502322e-03 1.8230265e-03 6.1478670e-03 3.1652703e-03 2.7049802e-03 4.9489115e-03 5.3042297e-03 1.9463142e-03 1.6986432e-03 4.1722516e-03 5.8173968e-03 2.6602408e-03 6.4952123e-03 1.5042394e-03 1.1842312e-02 5.5188608e-03 5.4735640e-03 2.9735027e-03 6.0829501e-03 4.0306925e-03 6.2131974e-03 6.3472325e-03 8.2596987e-03 4.2451973e-03 1.6828229e-03 3.7151898e-03 1.1574575e-02 7.1331719e-03 5.4811265e-03 5.5565221e-04 2.6852359e-03 1.0154324e-03 1.7597013e-04 3.1956114e-04 3.9523878e-04 1.2388706e-03 1.2655903e-03 1.6805675e-03 3.2406061e-05 1.6284558e-04 3.0055489e-04 4.9827743e-03 5.3992645e-03 4.0722394e-03 2.5388663e-04 1.8315484e-03 2.0093416e-03 5.5333384e-04 1.9333559e-03 1.0417210e-03 4.5631651e-05 3.9213732e-04 8.9628584e-04 1.5141190e-03 1.1540120e-03 1.6596111e-04 3.8108192e-03 6.9068728e-04 1.6110144e-02 8.5829426e-03 5.7514563e-03 7.4081873e-03 9.8865471e-03 7.9109476e-03 1.1246146e-02 6.7496818e-03 8.5805974e-03 8.1149529e-03 4.1088457e-03 5.6967153e-03 5.0837880e-03 1.0378372e-02 1.4309813e-02 8.2945922e-03 4.9454249e-03 6.1635386e-03 1.3817928e-02 6.6600542e-03 6.6926476e-03 9.8029996e-03 9.1474450e-03 3.3107453e-03 6.4113962e-03 3.9012077e-03 3.0861181e-03 3.9737498e-03 8.9756426e-03 2.8871345e-03 5.7720900e-03 2.7832463e-03 9.8623531e-03 2.6826772e-03 9.0033513e-03 5.5466456e-03 1.2137789e-02 5.5287165e-03 4.1655510e-03 3.9755943e-03 8.6395601e-03 5.0222445e-03 8.5829426e-03 8.5444862e-03 9.9728516e-03 6.1969528e-03 5.4521508e-03 4.4886104e-03 1.0936532e-02 6.6624227e-03 6.0879321e-03 5.2071133e-03 3.8816096e-03 5.8831678e-03 5.7002681e-03 6.1926865e-03 2.4416494e-03 1.5149583e-03 1.0539677e-02 5.8224280e-03 7.4292172e-03 5.4190952e-03 2.7168224e-03 2.8895677e-04 1.2473682e-03 3.7477469e-03 9.3088565e-03 2.0944009e-03 2.9293124e-03 2.5720420e-03 1.8897161e-03 5.1660356e-03 6.4102501e-03 2.0138936e-03 2.7413840e-03 2.0664916e-03 4.4338085e-03 1.0353410e-02 2.6118482e-03 4.2150650e-03 2.5222817e-03 3.4164904e-03 3.0582580e-03 3.1593359e-03 6.2318287e-03 2.3862805e-03 6.4839490e-03 7.9101598e-03 1.0920878e-03 5.4699759e-04 3.4046237e-03 2.2291279e-03 4.0543858e-03 4.2004727e-03 1.1970351e-03 2.0117915e-03 1.4112028e-03 1.1968526e-02 7.9088029e-03 1.7082776e-03 1.7239090e-03 9.0238817e-03 2.6874577e-03 9.4975956e-04 2.9722514e-03 1.7009640e-03 5.1343341e-04 3.7130353e-03 4.7806054e-03 6.6267337e-03 1.3220831e-03 3.9245747e-03 2.9403206e-03 7.2757164e-03 5.0266602e-03 1.8157078e-03 1.4701768e-03 3.5054511e-04 1.7883207e-03 2.3616946e-03 2.6617941e-03 2.5222817e-03 2.1723972e-03 1.9530095e-03 2.4533627e-03 4.5559375e-03 1.3609168e-03 1.2016297e-03 8.2861882e-04 4.8139327e-03 2.8209366e-03 1.1052349e-04 3.7194090e-05 1.3880093e-03 2.0960687e-03 1.8746430e-03 7.3307377e-04 5.4600497e-04 5.2531656e-04 7.7399437e-05 7.5260309e-03 6.5039124e-03 3.6958100e-03 4.3794778e-04 3.1691974e-03 1.9589162e-03 1.1747018e-03 3.8950000e-03 1.8610625e-03 3.3531102e-04 5.6607375e-05 1.7087501e-03 1.8728452e-03 1.4997100e-03 3.0123700e-04 1.5391826e-03 8.8147902e-04 1.8294246e-02 1.0829571e-02 7.9341800e-03 1.0299926e-02 1.2275737e-02 1.1220530e-02 1.3508784e-02 1.0262914e-02 1.2147087e-02 9.2823036e-03 4.6531594e-03 7.8245831e-03 6.5524640e-03 1.2610156e-02 1.5512672e-02 9.0061844e-03 7.1992659e-03 8.4158356e-03 1.7921554e-02 9.9492079e-03 7.9854978e-03 1.1342596e-02 1.2982212e-02 4.5954373e-03 8.0580657e-03 6.3922041e-03 4.0251341e-03 4.9895677e-03 1.1418706e-02 5.4787432e-03 8.7107499e-03 4.4679304e-03 1.2142235e-02 4.8867636e-03 1.3297934e-02 7.3458933e-03 1.3299484e-02 7.7358341e-03 4.9917221e-03 4.9413674e-03 9.8545070e-03 4.8770063e-03 1.0829571e-02 1.0435652e-02 1.1007902e-02 6.6474718e-03 7.1727397e-03 5.6106947e-03 1.1815640e-02 8.4729744e-03 1.5166983e-03 3.8470717e-03 4.0414722e-03 1.3464004e-03 1.0521554e-03 2.2256122e-03 8.2355212e-03 2.4972654e-03 3.6758320e-03 4.2952040e-03 1.2466978e-03 5.4237685e-03 7.4233145e-03 2.7434894e-03 1.3920410e-03 5.6705580e-03 1.6006987e-03 1.4795623e-03 2.5504964e-03 2.8217649e-03 4.8039535e-03 2.1716992e-03 5.1387834e-03 3.8511647e-03 3.4066344e-03 1.1638730e-02 3.4223746e-03 9.4998939e-03 3.3659956e-03 1.1438717e-03 2.4828929e-03 3.9653407e-03 1.5796312e-03 5.9993003e-03 1.2126234e-03 1.7377370e-03 4.6402796e-03 3.0884931e-03 1.1351744e-03 1.5562166e-03 3.9244608e-03 8.1556610e-03 5.1855221e-03 1.5226783e-03 2.8914073e-03 4.3431210e-03 9.2550137e-04 2.9272754e-03 5.3162687e-03 2.0104865e-03 7.9177243e-04 3.1975522e-03 8.1317449e-04 1.3785091e-03 2.5714969e-03 2.9511423e-03 2.8161529e-04 5.9442405e-04 1.5991249e-03 3.5615321e-03 5.8433150e-04 3.1011353e-03 9.7656296e-04 8.0983113e-03 2.3700003e-03 3.0461348e-03 1.6450583e-03 4.0872867e-03 3.5603497e-03 3.3659956e-03 3.6518311e-03 5.6844228e-03 3.2852236e-03 1.0303385e-03 1.9470044e-03 7.8778855e-03 3.7000111e-03 1.9300708e-03 2.2574717e-03 1.1284358e-03 1.0646979e-03 1.0312076e-03 5.0621817e-03 1.2060164e-03 1.8000639e-03 2.0650564e-03 2.1487796e-03 3.2333878e-03 4.0195479e-03 1.2411974e-03 2.7183002e-03 2.2726383e-03 8.5094120e-04 2.4056963e-04 6.3782113e-04 1.2937611e-03 2.4889893e-03 5.9722053e-04 1.5621785e-03 1.2375553e-03 1.3358412e-03 8.1240216e-03 1.2311022e-03 1.1517636e-02 5.2909227e-03 3.4509195e-03 3.7153220e-03 6.3154320e-03 4.5407961e-03 7.2027738e-03 3.4054800e-03 5.2043334e-03 5.7490488e-03 3.1731637e-03 3.4719309e-03 3.4347027e-03 7.1701181e-03 1.1379612e-02 6.4467389e-03 2.2692649e-03 2.9960956e-03 9.7363741e-03 4.0178908e-03 4.7338729e-03 6.7982603e-03 5.6061060e-03 2.3699712e-03 3.8192855e-03 1.4133848e-03 2.2956056e-03 2.5088406e-03 5.7314562e-03 9.5862581e-04 3.4107092e-03 9.7091810e-04 6.6778472e-03 7.6184177e-04 4.3247639e-03 4.2032521e-03 8.9746527e-03 2.5881501e-03 2.8171688e-03 3.0086239e-03 6.5256052e-03 5.3225794e-03 5.2909227e-03 5.5860698e-03 7.6013097e-03 5.5376614e-03 4.0938335e-03 3.1000848e-03 8.1789436e-03 3.7376243e-03 3.2267663e-05 8.3707549e-04 1.6905622e-03 1.5718936e-03 9.3681209e-04 1.8762095e-04 1.9684682e-04 4.1277465e-05 6.4440482e-03 6.0518000e-03 3.8422414e-03 2.6274935e-04 2.4665214e-03 1.9088192e-03 8.3864343e-04 2.9786928e-03 1.4388393e-03 8.3975474e-05 5.3416679e-05 1.2919166e-03 1.6159093e-03 1.2740163e-03 1.2266042e-04 2.3692761e-03 7.1636545e-04 1.7563748e-02 9.9676420e-03 7.0439691e-03 9.1049407e-03 1.1370346e-02 9.8276989e-03 1.2673450e-02 8.7383729e-03 1.0632094e-02 8.9036592e-03 4.4721835e-03 6.9588127e-03 5.9822651e-03 1.1786793e-02 1.5215597e-02 8.8361414e-03 6.2553172e-03 7.4909328e-03 1.6226352e-02 8.5173085e-03 7.5296182e-03 1.0823312e-02 1.1340207e-02 4.0626163e-03 7.4274805e-03 5.3041231e-03 3.6467217e-03 4.5925766e-03 1.0472609e-02 4.2980846e-03 7.4369871e-03 3.7248522e-03 1.1287932e-02 3.8978058e-03 1.1428778e-02 6.6149115e-03 1.2975959e-02 6.8214468e-03 4.6824073e-03 4.5741646e-03 9.4691004e-03 5.0187206e-03 9.9676420e-03 9.7385546e-03 1.0722589e-02 6.5565379e-03 6.4801586e-03 5.1854796e-03 1.1596572e-02 7.7633670e-03 9.9158601e-04 1.6686143e-03 1.5457871e-03 9.5071878e-04 3.0701864e-04 3.6362811e-04 4.2548991e-05 6.6288818e-03 6.0417331e-03 3.6564704e-03 2.5834714e-04 2.6458983e-03 1.8764428e-03 8.4820574e-04 3.2578450e-03 1.5209238e-03 1.5090465e-04 6.1935165e-05 1.3587649e-03 1.7158299e-03 1.3006818e-03 1.6628415e-04 2.0535582e-03 7.1188329e-04 1.7324639e-02 9.9147481e-03 7.0600417e-03 9.2840701e-03 1.1302120e-02 1.0064078e-02 1.2589616e-02 9.1080261e-03 1.0920912e-02 8.6425556e-03 4.2412792e-03 6.9624194e-03 5.8567924e-03 1.1639501e-02 1.4739829e-02 8.4651679e-03 6.3684651e-03 7.5923014e-03 1.6475338e-02 8.8252851e-03 7.3097093e-03 1.0588424e-02 1.1694482e-02 3.9807447e-03 7.3452100e-03 5.5353092e-03 3.5104587e-03 4.4753907e-03 1.0430724e-02 4.6273816e-03 7.6755144e-03 3.8450588e-03 1.1170257e-02 4.1281664e-03 1.1986478e-02 6.5423939e-03 1.2634016e-02 6.9257811e-03 4.5247923e-03 4.3922796e-03 9.1553155e-03 4.5676871e-03 9.9147481e-03 9.5995789e-03 1.0353234e-02 6.1627903e-03 6.3902007e-03 5.0235520e-03 1.1247115e-02 7.7428181e-03 9.7824889e-04 1.6624661e-03 3.0059873e-03 2.5249972e-04 6.2343657e-04 1.1719366e-03 4.0275459e-03 6.3575546e-03 5.8712921e-03 7.5872446e-04 5.6741436e-04 3.5908637e-03 6.4996051e-04 1.9776689e-03 1.6840088e-03 4.6219818e-04 1.2917587e-03 1.3743184e-03 3.0187893e-03 2.2373182e-03 9.0828703e-04 5.7426890e-03 1.5775049e-03 1.5168309e-02 7.4080646e-03 4.2538993e-03 6.3033105e-03 8.5100385e-03 5.7899432e-03 1.0520014e-02 4.8624768e-03 6.1169570e-03 7.6384090e-03 4.1091153e-03 4.1981402e-03 3.9923041e-03 8.6172736e-03 1.2997087e-02 7.8717119e-03 4.1667776e-03 5.7584992e-03 1.0387455e-02 4.3546484e-03 5.7746712e-03 9.1054055e-03 6.5301937e-03 2.3054088e-03 5.9614728e-03 3.0379183e-03 2.5067124e-03 3.9126904e-03 7.3239330e-03 1.8370981e-03 3.7021481e-03 2.7152943e-03 8.1304066e-03 2.0347479e-03 7.4172182e-03 3.6502880e-03 1.1915038e-02 5.0530642e-03 4.2458688e-03 3.2213237e-03 7.5116191e-03 4.3860606e-03 7.4080646e-03 7.4726409e-03 9.2071058e-03 5.2421868e-03 3.6675370e-03 3.8308993e-03 1.1024145e-02 6.4520833e-03 3.0133930e-04 5.1037905e-03 1.2017859e-03 2.1951761e-03 1.7686888e-03 1.8224411e-03 2.8788865e-03 3.1740291e-03 7.0420689e-04 2.2350252e-03 2.1605967e-03 1.6074282e-04 9.9312132e-04 6.3910600e-04 1.1068342e-03 2.2522084e-03 4.2563220e-04 2.1383582e-03 1.1781524e-03 1.1965920e-03 6.6826197e-03 8.9701315e-04 8.8374021e-03 3.5249181e-03 1.9006375e-03 3.2674819e-03 4.3522253e-03 3.8602822e-03 5.5032387e-03 3.5903574e-03 4.6280025e-03 3.2207897e-03 1.0860964e-03 1.8528751e-03 1.3287556e-03 4.5637650e-03 7.2040689e-03 3.3452381e-03 1.6438459e-03 2.5042800e-03 8.4078534e-03 3.6412189e-03 2.1968551e-03 4.2884610e-03 5.3072049e-03 5.5962180e-04 2.2924485e-03 1.3972917e-03 4.1775124e-04 1.0120628e-03 3.7672964e-03 1.3901921e-03 2.7802443e-03 7.8074497e-04 4.2513803e-03 8.6439557e-04 5.7017672e-03 1.9558095e-03 6.1305769e-03 2.0764524e-03 1.1750038e-03 7.8121442e-04 3.3536027e-03 1.6840705e-03 3.5249181e-03 3.4031938e-03 4.3169071e-03 2.0323666e-03 1.8104373e-03 1.0313169e-03 5.4663596e-03 2.6797009e-03 4.7017751e-03 1.4050521e-03 2.2967640e-03 1.4490557e-03 2.3196093e-03 1.7015479e-03 1.5767700e-03 5.5713099e-04 3.7392678e-03 8.9324098e-04 2.5002761e-04 8.6482380e-04 1.3585049e-04 1.1239692e-03 1.9761479e-03 8.2927740e-05 9.6014531e-04 3.4965263e-04 8.9552257e-04 5.7645956e-03 3.1975366e-04 8.7743211e-03 4.0156462e-03 2.9085611e-03 3.7265804e-03 4.9611690e-03 5.3200137e-03 5.4291747e-03 4.9090444e-03 6.4181467e-03 3.1699225e-03 9.0457257e-04 2.8707306e-03 2.0343345e-03 5.5511033e-03 7.6886258e-03 3.2962764e-03 1.9800100e-03 2.4033904e-03 1.0905558e-02 5.4434884e-03 2.6411651e-03 4.3219947e-03 7.2333465e-03 1.3483652e-03 2.2580657e-03 1.9023537e-03 8.2889320e-04 8.3511491e-04 4.6996589e-03 2.2192064e-03 4.3941392e-03 6.5409726e-04 5.2313685e-03 1.2879740e-03 6.5768476e-03 3.4674432e-03 5.6567012e-03 2.0849532e-03 8.7522015e-04 1.2925162e-03 3.9107304e-03 2.2116014e-03 4.0156462e-03 3.8265815e-03 4.4724719e-03 2.6832503e-03 3.2108081e-03 1.3825243e-03 4.7909587e-03 2.3944351e-03 1.6956470e-03 1.0108236e-03 9.3414345e-04 1.2241588e-02 1.0680036e-02 6.4457112e-03 2.1113831e-03 4.8851719e-03 3.9417362e-03 3.5304281e-03 6.9169294e-03 4.3255103e-03 1.5489174e-03 5.9380000e-04 4.2050996e-03 3.5612837e-03 3.5981180e-03 1.5473563e-03 1.0469113e-03 2.7881158e-03 2.6084642e-02 1.6915515e-02 1.3046936e-02 1.5813114e-02 1.8728660e-02 1.6622622e-02 2.0172358e-02 1.5047933e-02 1.7480656e-02 1.5121187e-02 8.9988271e-03 1.2921567e-02 1.1505555e-02 1.9202447e-02 2.2948431e-02 1.4807179e-02 1.1989561e-02 1.3522703e-02 2.4353472e-02 1.4534512e-02 1.3492376e-02 1.7699736e-02 1.8260951e-02 8.7551977e-03 1.3397972e-02 1.0605977e-02 8.1065177e-03 9.3705995e-03 1.7600384e-02 8.9170386e-03 1.3238432e-02 8.2394556e-03 1.8591466e-02 8.5466794e-03 1.8371580e-02 1.2139358e-02 2.0015536e-02 1.2667833e-02 9.3753580e-03 9.4099897e-03 1.5925767e-02 9.2577085e-03 1.6915515e-02 1.6530549e-02 1.7369847e-02 1.1736690e-02 1.2003814e-02 1.0321540e-02 1.8104120e-02 1.3766972e-02 1.5141485e-04 3.6186411e-04 5.0640473e-03 5.8846843e-03 4.5372473e-03 2.9889995e-04 1.4503455e-03 2.4048820e-03 5.6473356e-04 2.1694139e-03 1.2760726e-03 5.0001009e-05 4.2762331e-04 1.0752642e-03 1.9285330e-03 1.4459840e-03 2.6007132e-04 3.8461838e-03 8.8079628e-04 1.6321310e-02 8.6239000e-03 5.6156570e-03 7.5378743e-03 9.9017513e-03 7.7506746e-03 1.1476827e-02 6.6644304e-03 8.3385285e-03 8.2160708e-03 4.1911289e-03 5.5513041e-03 4.9688023e-03 1.0246372e-02 1.4224144e-02 8.3426053e-03 5.0473242e-03 6.4114140e-03 1.3391748e-02 6.3862951e-03 6.6425216e-03 9.9154668e-03 8.8906649e-03 3.1510729e-03 6.5653256e-03 3.9767727e-03 3.0148559e-03 4.1087786e-03 8.8891438e-03 2.8692574e-03 5.5219515e-03 2.9822205e-03 9.7392578e-03 2.7559926e-03 9.1569917e-03 5.1978118e-03 1.2376315e-02 5.7353587e-03 4.3202502e-03 3.8711781e-03 8.5388416e-03 4.7844327e-03 8.6239000e-03 8.5671038e-03 9.9856257e-03 5.9819812e-03 5.1316831e-03 4.4550423e-03 1.1213210e-02 6.9205612e-03 3.8131057e-04 6.7888158e-03 7.3363003e-03 5.3694436e-03 6.8516623e-04 1.8924051e-03 2.8895525e-03 1.2584203e-03 3.1106561e-03 1.9851820e-03 2.2993360e-04 3.0705767e-04 1.8065799e-03 2.2681792e-03 1.9895114e-03 4.4561501e-04 3.2950230e-03 1.3536253e-03 1.9442033e-02 1.0986408e-02 7.5586116e-03 9.6139706e-03 1.2432885e-02 9.8077912e-03 1.4077548e-02 8.4254038e-03 1.0370813e-02 1.0478515e-02 5.7856689e-03 7.4894276e-03 6.8497491e-03 1.2864462e-02 1.7242156e-02 1.0603978e-02 6.8089917e-03 8.2980029e-03 1.5941696e-02 8.0862824e-03 8.7712331e-03 1.2406133e-02 1.0894930e-02 4.6791769e-03 8.5829625e-03 5.4740472e-03 4.5017704e-03 5.7029968e-03 1.1309070e-02 4.0232333e-03 7.1989285e-03 4.2761837e-03 1.2289743e-02 4.0060693e-03 1.1002553e-02 7.0316526e-03 1.5021687e-02 7.5484658e-03 5.9167328e-03 5.5390768e-03 1.0934157e-02 6.4525090e-03 1.0986408e-02 1.0943118e-02 1.2502015e-02 7.9589531e-03 6.9802991e-03 6.2208864e-03 1.3663387e-02 8.8999016e-03 6.6062542e-03 5.5827314e-03 3.2137911e-03 2.4648342e-04 3.0882054e-03 1.4855278e-03 8.8238305e-04 3.0312113e-03 1.3048774e-03 1.7074342e-04 4.2409993e-05 1.2055084e-03 1.2882692e-03 1.0204762e-03 8.9780466e-05 2.0467514e-03 5.4370125e-04 1.7241595e-02 9.9776023e-03 7.2970604e-03 9.2205394e-03 1.1400153e-02 1.0313298e-02 1.2454804e-02 9.2538420e-03 1.1254653e-02 8.6534668e-03 4.2479840e-03 7.2108683e-03 6.0807502e-03 1.1908599e-02 1.4998922e-02 8.5435101e-03 6.3304436e-03 7.3992448e-03 1.7031719e-02 9.1810782e-03 7.4786833e-03 1.0592315e-02 1.2037933e-02 4.2382770e-03 7.2750145e-03 5.5074473e-03 3.6741461e-03 4.4146887e-03 1.0634443e-02 4.6823366e-03 8.0104480e-03 3.6842552e-03 1.1424845e-02 4.0945647e-03 1.1857190e-02 7.0169427e-03 1.2513676e-02 6.7752703e-03 4.4459599e-03 4.6022279e-03 9.3965771e-03 4.9465269e-03 9.9776023e-03 9.6902181e-03 1.0478640e-02 6.5221232e-03 6.8355801e-03 5.1573625e-03 1.1079009e-02 7.5518079e-03 2.6192775e-03 5.9216859e-03 4.3727502e-03 5.1296754e-03 5.3199310e-03 2.7670517e-03 1.1371035e-03 2.6020928e-03 5.1188394e-03 7.5382072e-03 2.4476107e-03 5.0616317e-03 3.9293810e-03 5.2302586e-03 1.5095801e-02 4.1435464e-03 4.4658108e-03 8.7058429e-04 4.4660793e-04 2.5555941e-04 1.2381894e-03 8.7244097e-04 2.0362511e-03 8.1311609e-04 1.5164656e-03 1.9481959e-03 1.9017569e-03 4.9004150e-04 8.1438359e-04 1.7141435e-03 4.7093504e-03 2.7065864e-03 6.0393489e-05 4.2915211e-04 3.9373776e-03 1.5669301e-03 1.2950596e-03 2.1025536e-03 1.9522193e-03 1.0322026e-03 8.4218839e-04 9.2905490e-05 1.2346326e-03 1.1781032e-03 9.8002670e-04 7.9339756e-04 1.1328834e-03 6.2948320e-04 1.4629322e-03 3.9084846e-04 1.4164544e-03 1.5298834e-03 3.8721999e-03 2.7001180e-04 1.5287601e-03 1.2669024e-03 2.1037534e-03 3.7134859e-03 8.7058429e-04 1.1775044e-03 2.8495857e-03 2.7086756e-03 1.4023687e-03 1.0191451e-03 3.8647977e-03 9.3179883e-04 1.2988786e-03 3.9786961e-03 9.7659791e-03 1.8948352e-03 3.2059058e-03 1.9902291e-03 1.7004118e-03 5.2879179e-03 6.5830280e-03 1.9368261e-03 2.2268965e-03 1.8962421e-03 4.4774419e-03 1.1162650e-02 2.6447647e-03 4.9365528e-03 3.0323131e-03 3.9972109e-03 2.9343480e-03 3.7523678e-03 6.3980393e-03 2.7120874e-03 6.2404178e-03 8.0807935e-03 1.9416596e-03 1.3024398e-03 4.0245181e-03 3.0697725e-03 5.0283287e-03 5.8270256e-03 2.3505477e-03 2.0375391e-03 1.2652208e-03 1.2527532e-02 8.0556250e-03 2.6750700e-03 2.5225228e-03 9.0764566e-03 3.4699713e-03 1.3308823e-03 2.7664696e-03 2.4678015e-03 9.5729618e-04 4.3858320e-03 4.4729680e-03 6.8828714e-03 1.1547480e-03 4.8183810e-03 2.7179325e-03 6.3087897e-03 6.0843336e-03 2.6715726e-03 1.3701564e-03 8.5085504e-04 2.7129190e-03 3.6187454e-03 4.3357364e-03 3.0323131e-03 2.8888833e-03 3.1752051e-03 4.0026050e-03 5.6110347e-03 2.1520192e-03 2.0021167e-03 9.3001678e-04 2.6139835e-03 9.7625461e-03 4.3820793e-04 2.7906691e-03 3.4740135e-03 1.5471352e-03 3.7324822e-03 3.8774805e-03 1.8049144e-03 1.0166909e-03 9.6586952e-04 2.7620874e-03 5.6379477e-03 1.4267399e-03 9.9420936e-03 6.9534902e-03 7.2046628e-03 7.1611325e-03 8.0864542e-03 1.1050679e-02 7.0236664e-03 1.0684883e-02 1.2982713e-02 4.4982901e-03 2.1523458e-03 7.1621069e-03 5.3786825e-03 9.3055499e-03 9.5487861e-03 4.3879387e-03 5.0693092e-03 4.4931267e-03 1.8805226e-02 1.2143973e-02 5.1616364e-03 5.8432704e-03 1.4239876e-02 5.0396297e-03 4.0643450e-03 5.6688069e-03 3.5911389e-03 2.3422208e-03 8.6067938e-03 6.9700590e-03 1.0540875e-02 2.8498735e-03 9.0488233e-03 4.9258801e-03 1.1781603e-02 8.6822745e-03 5.8089260e-03 4.4337196e-03 2.0267056e-03 4.1354243e-03 6.4148025e-03 4.4976834e-03 6.9534902e-03 6.4275292e-03 5.9584428e-03 5.3006657e-03 8.1437729e-03 3.9023880e-03 4.5299634e-03 3.7988456e-03 2.5123066e-03 1.1946309e-03 2.0097566e-04 1.7928786e-03 5.7143349e-04 1.2667176e-04 4.6644872e-04 4.4712161e-04 1.0683378e-03 5.7468992e-04 7.6554900e-05 3.3664265e-03 2.0582490e-04 1.3553574e-02 7.1051776e-03 4.8941009e-03 6.5257957e-03 8.3098959e-03 7.5373275e-03 9.3233389e-03 6.7621473e-03 8.4467156e-03 6.1291129e-03 2.5944515e-03 4.8248569e-03 3.9028342e-03 8.7515124e-03 1.1627531e-02 6.1178515e-03 4.1203288e-03 5.0572182e-03 1.3529172e-02 6.7984240e-03 5.0687125e-03 7.7357162e-03 9.2015610e-03 2.4873418e-03 4.9175701e-03 3.5355888e-03 2.0473249e-03 2.6605314e-03 7.6511613e-03 3.0628424e-03 5.7307078e-03 2.1169019e-03 8.3320524e-03 2.4574218e-03 9.1102030e-03 4.8098810e-03 9.5736130e-03 4.5225384e-03 2.7314130e-03 2.7690856e-03 6.7080946e-03 3.3506310e-03 7.1051776e-03 6.8798895e-03 7.7001955e-03 4.4569824e-03 4.6305164e-03 3.1848752e-03 8.4170139e-03 5.2045050e-03 6.9296246e-03 2.1360002e-03 3.7784686e-03 3.9888330e-03 1.9332806e-03 3.1165972e-03 3.4622240e-03 6.1843155e-03 4.9689115e-03 2.8276933e-03 8.4143362e-03 3.9488633e-03 1.7390613e-02 8.6823566e-03 4.7125346e-03 7.5297389e-03 9.6551734e-03 5.6073015e-03 1.2632980e-02 4.7967985e-03 5.4595549e-03 9.6779905e-03 6.1919843e-03 4.6437798e-03 4.8811137e-03 9.2634464e-03 1.4470481e-02 9.9239882e-03 5.5116951e-03 7.7393895e-03 8.8498403e-03 3.6104684e-03 7.0913900e-03 1.1068993e-02 5.6668826e-03 3.0002631e-03 7.9032607e-03 4.0760726e-03 3.7279027e-03 5.9729084e-03 7.9919733e-03 2.3383845e-03 3.2272638e-03 4.6096764e-03 8.7690968e-03 3.1364844e-03 7.9578373e-03 3.3962632e-03 1.4631125e-02 6.8663759e-03 6.4834920e-03 4.3461235e-03 8.7377082e-03 5.5255283e-03 8.6823566e-03 8.8561968e-03 1.1024987e-02 6.2645608e-03 3.5745084e-03 5.1960404e-03 1.3983177e-02 8.7265781e-03 1.5360474e-03 2.2738132e-03 6.3957013e-04 1.8470485e-03 1.9436948e-03 8.2753134e-04 1.4870044e-04 1.8575051e-04 1.1511457e-03 4.1570185e-03 4.2481246e-04 1.2022631e-02 7.4558885e-03 6.7319225e-03 7.0097246e-03 8.7236579e-03 1.0059866e-02 8.2686681e-03 9.2543911e-03 1.1644804e-02 5.5866311e-03 2.5062451e-03 6.6946690e-03 5.2622870e-03 9.8836223e-03 1.1452219e-02 5.6355000e-03 4.7339621e-03 4.6436729e-03 1.7620106e-02 1.0378141e-02 5.6675320e-03 7.1003305e-03 1.2674455e-02 4.3836233e-03 4.5891805e-03 4.7259960e-03 3.2437023e-03 2.5249532e-03 8.8366518e-03 5.2434703e-03 8.9867759e-03 2.2946416e-03 9.5089828e-03 3.7537168e-03 1.0503141e-02 7.8427036e-03 7.7804598e-03 4.4127994e-03 2.3620086e-03 3.9941388e-03 7.2903223e-03 4.7424240e-03 7.4558885e-03 7.1215300e-03 7.3539364e-03 5.7328206e-03 7.4284923e-03 3.9643246e-03 6.4306545e-03 4.3729078e-03 1.0852360e-03 3.9664584e-04 4.5123208e-04 1.2414442e-03 2.3150826e-04 1.4183402e-03 7.0426023e-04 4.8163546e-04 5.0741189e-03 3.9595383e-04 1.0937070e-02 5.0339955e-03 3.1168119e-03 4.5426488e-03 6.0432452e-03 5.3182662e-03 7.1300333e-03 4.7641753e-03 6.1358685e-03 4.4885462e-03 1.6916118e-03 3.0636842e-03 2.3987749e-03 6.3937387e-03 9.2584560e-03 4.5899059e-03 2.5786937e-03 3.4750392e-03 1.0541835e-02 4.8350707e-03 3.4370915e-03 5.7977832e-03 6.8277961e-03 1.3144796e-03 3.3638906e-03 2.1338739e-03 1.0504939e-03 1.6413828e-03 5.4173620e-03 1.8780432e-03 3.9014661e-03 1.1794316e-03 6.0207953e-03 1.3473818e-03 6.9135169e-03 3.1440431e-03 7.6724602e-03 2.9974710e-03 1.7751706e-03 1.5977690e-03 4.8531144e-03 2.4494451e-03 5.0339955e-03 4.8947958e-03 5.8464391e-03 3.1299587e-03 2.9881785e-03 1.9161069e-03 6.7804975e-03 3.6799724e-03 6.0827379e-04 2.1616612e-03 3.6536587e-03 6.0346559e-04 1.7560211e-03 1.3150048e-03 2.0808595e-03 9.6848049e-03 1.5050304e-03 8.5509882e-03 3.4934050e-03 2.3873070e-03 2.2266374e-03 4.3437966e-03 3.4432000e-03 4.8822411e-03 2.6118729e-03 4.2515298e-03 3.9280244e-03 2.1763551e-03 2.4215318e-03 2.3726469e-03 5.2469603e-03 8.8156975e-03 4.6543689e-03 1.1653780e-03 1.5634616e-03 8.3770205e-03 3.5034084e-03 3.2683461e-03 4.7014835e-03 4.7252691e-03 1.7941232e-03 2.2843067e-03 6.9328893e-04 1.6436639e-03 1.4866860e-03 4.0234517e-03 7.8970448e-04 2.8726545e-03 3.0564882e-04 4.8340126e-03 3.3681970e-04 3.2031086e-03 3.4962764e-03 6.5051951e-03 1.2935806e-03 1.7549327e-03 2.1673002e-03 4.7736712e-03 4.5514956e-03 3.4934050e-03 3.7661841e-03 5.5497035e-03 4.3927525e-03 3.3294822e-03 2.0638533e-03 5.8922322e-03 2.1421877e-03 1.0254014e-03 1.7967204e-03 2.4056491e-05 4.9606070e-04 1.5493043e-04 7.3786851e-04 5.7634641e-03 2.2821239e-04 9.8983432e-03 4.8234579e-03 3.6558248e-03 4.1003359e-03 5.8726550e-03 5.9033047e-03 6.1776943e-03 5.1708698e-03 7.0154090e-03 4.1004756e-03 1.5716690e-03 3.6383691e-03 2.8864008e-03 6.7180252e-03 9.3042807e-03 4.3918407e-03 2.3314998e-03 2.6748196e-03 1.1913776e-02 5.9266374e-03 3.6227612e-03 5.2997514e-03 7.7658365e-03 2.0655115e-03 2.8598574e-03 2.0567764e-03 1.5031818e-03 1.3330059e-03 5.6389436e-03 2.2318728e-03 4.9186471e-03 7.3077456e-04 6.3335619e-03 1.3529510e-03 6.4463256e-03 4.4695359e-03 6.6841393e-03 2.3650796e-03 1.3974597e-03 2.1137539e-03 5.1383865e-03 3.4413190e-03 4.8234579e-03 4.7468419e-03 5.6953492e-03 3.9468482e-03 4.2095924e-03 2.1520165e-03 5.7168561e-03 2.8181362e-03 2.6785665e-04 8.6267880e-04 1.5080867e-03 1.0657562e-03 9.1259932e-05 3.2834346e-03 5.6400090e-04 1.5768182e-02 8.4481987e-03 5.6912110e-03 7.5460195e-03 9.7379656e-03 8.1209399e-03 1.1078194e-02 7.1125751e-03 8.8544675e-03 7.7247994e-03 3.7380313e-03 5.6213520e-03 4.8502413e-03 1.0137958e-02 1.3681745e-02 7.7719782e-03 4.9953780e-03 6.2009166e-03 1.4059422e-02 6.9488117e-03 6.3504999e-03 9.4504672e-03 9.5001122e-03 3.1213299e-03 6.2307927e-03 4.0875652e-03 2.8287725e-03 3.7400310e-03 8.8571631e-03 3.1845212e-03 5.9757813e-03 2.8285304e-03 9.6559261e-03 2.8616391e-03 9.5937311e-03 5.3787128e-03 1.1658427e-02 5.5682032e-03 3.8827352e-03 3.6680728e-03 8.1913042e-03 4.3904219e-03 8.4481987e-03 8.3060404e-03 9.4592722e-03 5.6422254e-03 5.2631423e-03 4.2070121e-03 1.0442544e-02 6.5544595e-03 1.6777879e-03 1.6905742e-03 1.4559893e-03 2.3696578e-04 1.7661444e-03 8.8626613e-04 1.8929605e-02 1.1198434e-02 8.2126916e-03 1.0357192e-02 1.2691977e-02 1.1286676e-02 1.3897779e-02 1.0124890e-02 1.2174180e-02 9.8437553e-03 5.0898821e-03 8.1181082e-03 6.9678076e-03 1.3160374e-02 1.6460126e-02 9.6980805e-03 7.2881507e-03 8.4988687e-03 1.8112744e-02 9.9154333e-03 8.5214392e-03 1.1906445e-02 1.2939064e-02 4.9240987e-03 8.3797448e-03 6.3232280e-03 4.3836801e-03 5.2916266e-03 1.1816149e-02 5.2785357e-03 8.7418147e-03 4.4568647e-03 1.2648763e-02 4.7832566e-03 1.2911612e-02 7.7565330e-03 1.3968530e-02 7.8153448e-03 5.3323341e-03 5.3832666e-03 1.0542156e-02 5.6397042e-03 1.1198434e-02 1.0902962e-02 1.1744032e-02 7.3882415e-03 7.5970260e-03 6.0277743e-03 1.2454722e-02 8.6970669e-03 6.8936185e-04 2.4185836e-04 6.5755985e-04 5.6966351e-03 2.3035735e-04 9.8692354e-03 4.6426684e-03 3.3219514e-03 3.9792968e-03 5.6658817e-03 5.5182925e-03 6.1624599e-03 4.8454827e-03 6.5522795e-03 4.0009447e-03 1.4714328e-03 3.2975731e-03 2.5896752e-03 6.3872309e-03 9.0422907e-03 4.2582444e-03 2.2027730e-03 2.6736829e-03 1.1276803e-02 5.4428314e-03 3.3876877e-03 5.2005260e-03 7.2827649e-03 1.7342875e-03 2.7931367e-03 1.8984292e-03 1.2582586e-03 1.2661063e-03 5.3418398e-03 1.9890109e-03 4.4646783e-03 7.0136727e-04 6.0091268e-03 1.2000111e-03 6.3087140e-03 3.9600850e-03 6.7054154e-03 2.3290598e-03 1.3528267e-03 1.8394373e-03 4.8617299e-03 3.0806223e-03 4.6426684e-03 4.5595872e-03 5.5227532e-03 3.5904469e-03 3.7269772e-03 1.9354221e-03 5.7786823e-03 2.8379142e-03 1.6160265e-04 9.1316164e-04 4.5133200e-03 3.9636483e-04 1.2999342e-02 7.7990273e-03 6.7507696e-03 6.8565065e-03 9.1164934e-03 9.6187705e-03 8.8289255e-03 8.5171572e-03 1.1041936e-02 6.3870800e-03 3.1070828e-03 6.7336821e-03 5.5817760e-03 1.0377938e-02 1.2745315e-02 6.6393015e-03 4.6463353e-03 4.6865341e-03 1.7095703e-02 9.6546615e-03 6.2703784e-03 7.8942299e-03 1.1919237e-02 4.5195099e-03 4.9936591e-03 4.3235727e-03 3.5370732e-03 2.9138150e-03 9.0955590e-03 4.5162367e-03 8.4195838e-03 2.1592479e-03 9.9327314e-03 3.3056202e-03 9.5169563e-03 7.9097267e-03 8.9017922e-03 4.4124132e-03 2.8482486e-03 4.3995745e-03 8.1140558e-03 5.6448175e-03 7.7990273e-03 7.6396844e-03 8.3822247e-03 6.5839845e-03 7.5512097e-03 4.3854717e-03 7.5528676e-03 4.6977929e-03 6.1532842e-04 4.4755911e-03 9.8852555e-05 1.1287330e-02 6.2297232e-03 5.1189561e-03 5.6158860e-03 7.4118621e-03 7.9100135e-03 7.4458980e-03 7.1082862e-03 9.2159095e-03 4.9487763e-03 1.9996447e-03 5.0869737e-03 4.0020507e-03 8.3714289e-03 1.0564909e-02 5.1004357e-03 3.5205852e-03 3.7366566e-03 1.4679778e-02 7.9564069e-03 4.6553854e-03 6.3553187e-03 1.0091302e-02 3.0597107e-03 3.7953264e-03 3.3093658e-03 2.2411736e-03 1.9075393e-03 7.2834604e-03 3.5416682e-03 6.7651117e-03 1.4482690e-03 7.9807479e-03 2.4205104e-03 8.4840253e-03 5.9704010e-03 7.4790367e-03 3.4333236e-03 1.8691676e-03 2.9457380e-03 6.2606998e-03 3.9378134e-03 6.2297232e-03 6.0227040e-03 6.6475915e-03 4.7553767e-03 5.6493596e-03 2.9997214e-03 6.2979764e-03 3.7014773e-03 2.9064840e-03 2.5951953e-04 1.5239927e-02 8.3975944e-03 6.0152535e-03 7.5736149e-03 9.7205279e-03 8.7194769e-03 1.0665619e-02 7.7107157e-03 9.6513260e-03 7.3495285e-03 3.4129734e-03 5.9493098e-03 4.9891659e-03 1.0300134e-02 1.3429483e-02 7.3762928e-03 4.9967161e-03 5.9349958e-03 1.5151045e-02 7.8143476e-03 6.2821596e-03 9.0873094e-03 1.0390318e-02 3.3768176e-03 5.9427925e-03 4.2522990e-03 2.8707879e-03 3.4399883e-03 9.0442431e-03 3.6087055e-03 6.7269934e-03 2.6557188e-03 9.8316941e-03 3.0241465e-03 9.9672648e-03 5.9844482e-03 1.0964814e-02 5.3765757e-03 3.5060112e-03 3.7251338e-03 8.1205003e-03 4.4033527e-03 8.3975944e-03 8.1986819e-03 9.1329776e-03 5.6810271e-03 5.8010063e-03 4.1618459e-03 9.6695634e-03 6.1457695e-03 3.7030792e-03 2.6425725e-02 1.8599298e-02 1.5467357e-02 1.8635118e-02 2.0442342e-02 2.0541696e-02 2.1259986e-02 1.9510831e-02 2.1931276e-02 1.5294793e-02 9.1682566e-03 1.5285848e-02 1.2953400e-02 2.0823480e-02 2.2513198e-02 1.4419732e-02 1.4350471e-02 1.5480442e-02 2.9209010e-02 1.9066274e-02 1.4266029e-02 1.8093335e-02 2.3152907e-02 1.0464507e-02 1.4480769e-02 1.3685178e-02 9.2298310e-03 1.0125817e-02 1.9700965e-02 1.2730523e-02 1.7262155e-02 1.0225203e-02 2.0352884e-02 1.1535854e-02 2.3443654e-02 1.4503051e-02 1.9462802e-02 1.4723300e-02 9.8246219e-03 1.0395301e-02 1.6330792e-02 8.8773452e-03 1.8599298e-02 1.7720786e-02 1.7175716e-02 1.1774527e-02 1.4190018e-02 1.1287889e-02 1.7334951e-02 1.4959707e-02 1.2176711e-02 6.5819636e-03 5.0346277e-03 6.0397395e-03 7.7827629e-03 7.8599708e-03 8.1942510e-03 7.0899174e-03 9.0304038e-03 5.3331159e-03 2.1084043e-03 4.9833227e-03 3.9086435e-03 8.5223444e-03 1.0895531e-02 5.3819973e-03 3.7720778e-03 4.2725796e-03 1.4381123e-02 7.5978371e-03 4.7474248e-03 6.8472012e-03 9.8808760e-03 2.7678758e-03 4.1999833e-03 3.4360629e-03 2.0712504e-03 2.1185414e-03 7.4497841e-03 3.4016631e-03 6.4264729e-03 1.6858980e-03 8.1251590e-03 2.4514173e-03 8.9161864e-03 5.4840504e-03 8.2209587e-03 3.8745808e-03 2.1076714e-03 2.7824071e-03 6.3561971e-03 3.5186798e-03 6.5819636e-03 6.3377454e-03 6.9667686e-03 4.4964760e-03 5.2124475e-03 2.9917795e-03 7.0285944e-03 4.2716151e-03 1.5675754e-03 4.2437140e-03 2.9528525e-03 1.2801911e-03 5.5300675e-03 5.2313183e-04 7.0373258e-03 7.0028853e-03 1.5817688e-03 4.6017698e-03 4.2905415e-03 3.9763653e-03 1.7507004e-03 9.8088526e-04 2.0205251e-03 3.8651167e-03 2.8498196e-03 8.0083780e-03 8.7219602e-03 2.5429883e-03 8.6090275e-04 7.9906965e-03 6.1518285e-03 2.2097973e-03 5.7963314e-03 5.7460009e-03 4.2262969e-03 2.1872477e-03 8.9731860e-03 7.8793225e-03 5.8991938e-03 1.8999235e-03 7.1341168e-03 7.0170212e-03 6.4409068e-03 6.9062404e-04 3.2402506e-03 4.2346770e-03 4.9489360e-03 1.9844891e-03 6.6121605e-03 1.5675754e-03 1.4409377e-03 1.2937675e-03 4.5154908e-03 6.0031850e-03 4.0189646e-03 1.2526895e-03 2.2326743e-03 6.7976748e-04 4.4251056e-04 5.1964468e-05 1.5314410e-03 4.7093005e-04 2.2872076e-03 2.4409680e-03 6.1996952e-04 1.8531872e-03 7.0675084e-04 7.4672595e-04 2.9977980e-04 1.6320684e-03 1.1279305e-03 6.4871929e-04 6.2074215e-04 4.0109945e-03 3.1610099e-03 4.6600326e-04 4.3207236e-04 3.1187047e-03 1.7495883e-03 3.8892953e-04 1.4972893e-03 1.7881991e-03 1.3932149e-03 1.6264024e-04 3.1616233e-03 2.5553184e-03 1.9652949e-03 2.4129313e-04 2.2301817e-03 3.0218268e-03 1.9168834e-03 1.5054952e-03 6.4024254e-04 1.6084351e-03 1.4082859e-03 5.9336508e-04 3.2376306e-03 1.1102230e-16 6.3653022e-05 8.5949029e-04 1.8263557e-03 1.6881339e-03 9.7411877e-04 1.7845049e-03 6.1330704e-04 7.1368649e-04 9.0151789e-04 6.1896245e-04 2.2461726e-03 1.0406196e-03 1.1172454e-03 1.6730261e-03 1.8311894e-03 1.8071232e-06 2.5364008e-04 8.3907159e-04 3.3874526e-03 2.1375798e-03 4.5487046e-04 1.1672957e-03 2.8115805e-03 1.2138761e-03 7.0446879e-04 1.8201572e-03 1.6098088e-03 5.2152529e-04 1.0325997e-03 6.5974667e-04 9.0603694e-04 1.4060118e-03 4.3618665e-04 1.3469029e-03 7.5850938e-04 1.4500880e-03 6.7416609e-04 1.0299923e-03 2.5247687e-03 4.1453726e-04 3.7399848e-03 9.2294426e-04 1.7551096e-03 7.2749277e-04 1.1614195e-03 2.4439916e-03 6.7976748e-04 8.1575962e-04 2.1038526e-03 1.4799615e-03 3.3653084e-04 6.6936205e-04 3.8899674e-03 1.5033836e-03 6.1892938e-04 8.9565267e-04 1.1187414e-03 1.0854623e-03 1.6238473e-03 1.6997610e-03 2.5416419e-03 7.7939556e-04 1.2163589e-03 1.1645635e-03 3.7479516e-03 2.5673270e-03 2.8413719e-04 3.4882680e-04 3.5538869e-03 2.1358644e-03 1.3529179e-03 1.5262264e-03 2.0757398e-03 1.9224937e-03 7.7565401e-04 6.3114517e-04 2.1204969e-03 1.7089017e-03 5.9516371e-04 1.8253945e-03 1.7466546e-03 1.3725400e-03 9.7938279e-04 1.2586511e-03 1.2414457e-03 2.1946939e-03 3.0180258e-03 3.2655328e-04 2.0574339e-03 1.9692598e-03 1.9049224e-03 4.6812273e-03 4.4251056e-04 7.9383810e-04 2.4056659e-03 3.2171363e-03 2.0291670e-03 1.5121604e-03 3.2358269e-03 7.8092419e-04 1.6056499e-03 4.5150817e-04 2.5183573e-03 2.4854849e-03 8.1663583e-04 2.4301739e-03 9.3222022e-04 1.0572824e-03 1.7339381e-04 1.3855383e-03 1.3424761e-03 1.0242405e-03 9.7281350e-04 3.6981982e-03 3.3860618e-03 6.8052310e-04 4.8835842e-04 3.1532670e-03 2.2405021e-03 6.9163264e-04 1.9828394e-03 2.3495346e-03 1.9517373e-03 1.3548451e-04 3.7877283e-03 2.8247707e-03 2.6466102e-03 1.4866617e-04 2.8614588e-03 3.2185979e-03 2.1613637e-03 1.5430396e-03 1.0217871e-03 2.1845781e-03 1.8662401e-03 6.6257305e-04 3.7358116e-03 5.1964468e-05 1.2907411e-04 9.1062515e-04 2.1316335e-03 1.9388777e-03 1.3908595e-03 1.9526372e-03 9.5243046e-04 3.3534848e-03 2.1612689e-04 1.1409237e-04 3.7511533e-03 4.4135123e-03 6.6848080e-04 1.6638889e-03 1.5639090e-03 5.3797969e-03 4.6512834e-03 1.2304041e-03 2.1424312e-03 1.1209148e-03 3.9967200e-04 2.4397061e-03 3.5566606e-03 2.8773633e-04 1.9980195e-03 2.5921392e-03 1.0825130e-03 2.8538916e-03 3.5346603e-03 9.3770297e-04 1.4682668e-03 3.5169417e-04 2.8507687e-03 1.3414178e-03 1.7057263e-03 1.1395352e-03 1.1778138e-03 6.0686882e-03 1.8828714e-03 4.1140616e-03 2.6499598e-03 2.9837515e-03 5.4227271e-03 1.5314410e-03 2.0381041e-03 4.2816261e-03 3.9075302e-03 1.2227927e-03 2.5264748e-03 6.5268770e-03 2.9950818e-03 4.2523782e-03 4.6456758e-03 7.0284879e-04 2.6206169e-03 2.2973667e-03 2.1186617e-03 1.0662204e-03 1.4771683e-03 1.2714179e-03 1.6200996e-03 9.3400517e-04 6.3864823e-03 5.7928278e-03 1.2702417e-03 3.0680262e-04 5.4794582e-03 3.6431092e-03 7.0967201e-04 2.9368020e-03 3.3273895e-03 2.1071133e-03 1.0635659e-03 5.3575283e-03 5.0378701e-03 2.9935636e-03 1.0701915e-03 3.8904013e-03 4.2643219e-03 4.2480632e-03 6.5030200e-04 1.1722241e-03 2.1985353e-03 2.8568074e-03 1.2361632e-03 4.8426872e-03 4.7093005e-04 4.8520973e-04 9.1253531e-04 3.1519557e-03 3.8819044e-03 2.1030647e-03 9.4974174e-04 6.5992898e-04 2.7415573e-04 4.6934818e-03 4.8265016e-03 1.1075187e-03 2.2246224e-03 2.7094103e-03 7.2547386e-03 5.7651516e-03 1.2879775e-03 2.2498412e-03 1.7969640e-03 2.9988677e-04 3.2873557e-03 4.6281528e-03 3.4411066e-04 2.2557578e-03 3.0860670e-03 7.9349579e-04 3.1195585e-03 3.7700064e-03 1.7729958e-03 8.2927671e-04 3.1955044e-04 2.5127017e-03 2.3924363e-03 1.2355886e-03 6.3306711e-04 1.7271405e-03 7.3517242e-03 1.9482896e-03 4.3986367e-03 3.1257329e-03 4.1788193e-03 6.3227646e-03 2.2872076e-03 2.9291555e-03 5.5970414e-03 4.9483109e-03 1.7952206e-03 3.0186434e-03 7.6597710e-03 3.3340783e-03 5.0895780e-03 5.6951678e-03 1.1683986e-03 2.4173290e-03 2.2930538e-03 6.6766405e-03 6.0598142e-03 2.0196993e-03 3.2103351e-03 7.1260012e-04 2.1040770e-04 3.4457175e-03 4.8676879e-03 4.7880714e-05 2.6160358e-03 3.7638559e-03 1.6534967e-03 3.7166920e-03 4.7397184e-03 1.5966719e-03 1.7314841e-03 3.1225254e-04 3.8216967e-03 2.0442457e-03 2.2822879e-03 1.3421268e-03 1.4265946e-03 7.7649269e-03 2.8740126e-03 5.4160448e-03 3.5155679e-03 4.0306705e-03 6.4534752e-03 2.4409680e-03 3.0445932e-03 5.6145787e-03 4.8826195e-03 1.5492752e-03 3.4872724e-03 8.3107496e-03 4.2635519e-03 8.0787158e-04 1.6588755e-03 9.5088926e-04 1.1215622e-03 1.1319394e-03 1.0749073e-04 1.3651148e-03 1.0419898e-03 7.3951172e-03 5.6721517e-03 2.8717807e-04 1.2167194e-04 6.0929710e-03 1.9467902e-03 2.7497575e-04 2.6021687e-03 1.4711384e-03 7.6653532e-04 1.1979861e-03 4.6497866e-03 4.6528613e-03 2.0491908e-03 1.1214382e-03 3.1087470e-03 5.8331337e-03 2.9008505e-03 5.1851355e-04 1.1077981e-03 7.4180273e-04 1.1189833e-03 3.3213225e-04 1.9786092e-03 6.1996952e-04 3.2630432e-04 1.6788710e-04 1.0717507e-03 2.5327575e-03 6.9916673e-04 4.8860962e-04 5.0860173e-04 1.7852918e-03 8.1925898e-04 2.7637031e-03 3.4415701e-03 7.6048751e-04 1.3454814e-03 1.4168273e-03 9.1102129e-03 5.4634531e-03 7.0608839e-04 1.5253323e-03 6.6930140e-03 9.6900708e-04 7.0882160e-04 2.0455668e-03 3.9468549e-04 1.1422246e-04 2.4972040e-03 3.2985277e-03 4.3265068e-03 9.6084950e-04 2.6351726e-03 1.9457490e-03 6.5435669e-03 2.5549327e-03 2.2787630e-03 1.2770395e-03 6.6117888e-05 4.1362061e-04 1.2709485e-03 9.0111668e-04 1.8531872e-03 1.4879007e-03 1.4236785e-03 8.2396190e-04 2.2228636e-03 3.0260473e-04 1.7887502e-03 9.8733924e-04 2.2593145e-04 8.4529966e-04 3.3545284e-03 2.0967474e-03 4.8795197e-04 1.2181976e-03 2.8660735e-03 1.2480152e-03 6.7735579e-04 1.8245752e-03 1.6711447e-03 4.7937341e-04 1.0455121e-03 6.9960603e-04 8.5898853e-04 1.3874556e-03 4.5677202e-04 1.3792343e-03 7.7973864e-04 1.4683104e-03 6.8342524e-04 1.0561802e-03 2.6555506e-03 3.7342452e-04 3.7432085e-03 9.6667604e-04 1.7304819e-03 6.7810292e-04 1.1254700e-03 2.3291425e-03 7.0675084e-04 8.2471895e-04 2.0738735e-03 1.3951480e-03 2.9534051e-04 6.3569311e-04 3.8846725e-03 1.5360581e-03 9.9102840e-04 2.6998600e-03 1.1403522e-03 5.9435819e-04 1.1584769e-03 4.5439398e-03 2.4115910e-03 2.2376231e-04 1.3051908e-03 3.1170665e-03 2.4639357e-04 6.5093729e-04 1.0408914e-03 3.2170646e-04 6.8086675e-04 7.3723950e-04 1.9464154e-03 1.6938542e-03 1.2010351e-03 8.6567601e-04 1.2605984e-03 4.0415296e-03 6.0024917e-04 2.8241029e-03 9.3145876e-04 8.7987159e-04 1.5390142e-04 6.1621791e-04 1.2031695e-03 7.4672595e-04 6.4561848e-04 1.3132954e-03 5.7671738e-04 4.3996999e-04 1.2646330e-04 2.7819647e-03 1.1571313e-03 1.1519638e-03 1.4945239e-03 1.5137092e-03 1.7542391e-03 3.0584682e-03 3.1624125e-03 6.9423087e-04 8.0741075e-04 2.9564986e-03 2.1264808e-03 1.1956222e-03 2.4834226e-03 2.4046990e-03 2.4389289e-03 1.1428932e-04 4.1572751e-03 2.6270682e-03 3.3826290e-03 1.0906814e-05 3.3779688e-03 3.9749243e-03 1.6265579e-03 2.0735613e-03 1.7238890e-03 2.6977636e-03 1.8191981e-03 5.1180139e-04 3.3068455e-03 2.9977980e-04 3.0495320e-04 9.6732841e-04 1.7461140e-03 1.4560452e-03 1.4920651e-03 2.5772801e-03 1.7019143e-03 1.0341988e-03 4.0085546e-03 3.6492138e-03 7.4340196e-03 7.9703529e-03 1.4966880e-03 7.8440192e-04 7.7789554e-03 4.4532033e-03 2.2360209e-03 5.9110335e-03 4.1723764e-03 3.6115483e-03 1.8645164e-03 8.6587941e-03 6.9790025e-03 5.9223579e-03 1.3508421e-03 6.9679643e-03 8.8100022e-03 4.3884451e-03 9.3956602e-04 3.8113149e-03 3.5744927e-03 3.3082295e-03 7.5316334e-04 3.6491972e-03 1.6320684e-03 1.1884181e-03 4.5803930e-04 2.1497659e-03 4.0210128e-03 2.8148066e-03 1.4099016e-03 2.8014283e-03 2.0075866e-03 1.7351697e-03 8.4094224e-03 6.5677927e-03 3.9418601e-04 3.5055970e-04 7.1718250e-03 2.0973679e-03 6.4851765e-04 3.3744442e-03 1.5292791e-03 9.4071576e-04 1.7144529e-03 5.4753391e-03 5.4188405e-03 2.5828658e-03 1.5301373e-03 3.8027206e-03 7.3328244e-03 3.1192913e-03 6.2731613e-04 1.7741918e-03 8.4678687e-04 1.1412463e-03 3.3683903e-04 1.4744098e-03 1.1279305e-03 6.7560808e-04 1.3395103e-04 7.7360874e-04 2.7301747e-03 8.0481079e-04 5.3215427e-04 1.0082007e-03 2.3386408e-04 4.5650335e-03 2.1085336e-03 8.9398060e-04 1.5597528e-03 2.5626398e-03 8.9643929e-04 4.6401620e-04 2.1474828e-04 9.4200330e-04 7.4363388e-04 8.8544521e-04 1.1214375e-03 1.5513223e-03 4.7369920e-04 1.2949283e-03 5.0107186e-04 1.9686134e-03 1.5839083e-03 3.0734994e-03 1.1037013e-04 1.0184869e-03 9.4459481e-04 1.6215035e-03 3.1102930e-03 6.4871929e-04 8.4571319e-04 2.1956968e-03 2.1853516e-03 1.4074387e-03 6.7079422e-04 3.0128980e-03 5.5375407e-04 5.9745601e-03 3.5596915e-03 1.0825427e-03 1.1193941e-03 3.8619013e-03 1.7868782e-03 2.6699310e-04 7.4652731e-04 1.5644519e-03 7.6914730e-04 1.1868249e-03 2.1537215e-03 2.8904145e-03 6.7050048e-04 1.5688544e-03 1.1553603e-03 2.3985919e-03 2.8520067e-03 2.1226295e-03 2.6532317e-05 9.5443067e-04 1.5325109e-03 1.7403036e-03 3.8293675e-03 6.2074215e-04 7.8759073e-04 1.8993585e-03 2.7454611e-03 2.5765044e-03 1.0363780e-03 2.0443972e-03 1.4335859e-04 1.4016332e-03 5.4805806e-03 6.7405649e-03 6.4579738e-04 5.1748633e-03 6.3487765e-03 4.3874825e-03 6.7084849e-03 8.1049162e-03 2.6677096e-03 4.6345484e-03 1.7447861e-03 7.4893653e-03 2.9062036e-03 5.4610122e-03 3.0610024e-03 2.8530196e-03 9.9111331e-03 5.6471487e-03 8.9070297e-03 6.1633735e-03 5.5983777e-03 9.1911029e-03 4.0109945e-03 4.6927139e-03 7.3914201e-03 7.0233589e-03 3.0645626e-03 6.1283907e-03 1.0939049e-02 7.1096522e-03 3.7615860e-03 5.7439366e-03 2.3434979e-04 2.1625792e-03 4.1436319e-03 1.4386017e-03 3.2647240e-03 4.5819077e-03 2.2920937e-03 1.0610016e-03 6.9564930e-05 3.4218787e-03 2.8472107e-03 1.7665885e-03 1.7134963e-03 1.2022963e-03 8.8958832e-03 3.1032776e-03 5.2642601e-03 3.2432815e-03 4.6032265e-03 6.0005810e-03 3.1610099e-03 3.7408101e-03 6.4052558e-03 4.8371122e-03 1.3589516e-03 3.3936977e-03 9.2321743e-03 4.6963171e-03 5.0386940e-04 4.2984797e-03 8.6973796e-04 3.5526497e-04 1.7666127e-03 7.2192984e-04 6.5802622e-04 6.6868074e-04 3.2431480e-03 2.9035420e-03 1.6594087e-03 6.4854242e-04 2.1612810e-03 4.9124735e-03 1.3725331e-03 1.5050690e-03 9.8777331e-04 7.5260407e-04 4.1403958e-04 1.4453279e-04 1.2645371e-03 4.6600326e-04 2.4792089e-04 4.5627999e-04 4.7560079e-04 1.1214322e-03 2.2091820e-04 1.5167980e-03 7.9735767e-04 5.8276719e-03 2.5886189e-03 4.3280198e-04 2.9205975e-03 2.2021650e-03 1.3597486e-03 9.5466012e-04 5.1915037e-03 4.8194588e-03 2.6659023e-03 8.3877743e-04 3.6595156e-03 5.4665486e-03 3.2603866e-03 3.5246614e-04 1.2546385e-03 1.3738455e-03 1.7317120e-03 4.0173517e-04 2.8852033e-03 4.3207236e-04 2.1872967e-04 1.7262684e-04 1.6274416e-03 2.8950267e-03 1.1933707e-03 5.1057942e-04 6.2761497e-04 3.2515433e-03 4.5844712e-03 2.0397580e-03 4.4892831e-03 5.6236454e-03 2.1663608e-03 1.9457792e-03 4.4715761e-04 4.4562273e-03 2.6795180e-03 2.6855442e-03 1.3224094e-03 1.8950095e-03 8.9432615e-03 3.4981521e-03 6.3671383e-03 4.3127165e-03 4.9533188e-03 7.5137514e-03 3.1187047e-03 3.8229023e-03 6.6814123e-03 5.8612561e-03 2.0597987e-03 4.3006086e-03 9.5397630e-03 5.0775992e-03 1.3692874e-03 9.6403546e-04 1.3966479e-04 8.6664284e-04 1.6597338e-03 1.3212159e-03 1.4578093e-03 1.0432500e-03 1.9220792e-03 8.7918940e-04 4.4168060e-03 5.0670419e-04 4.4696559e-03 1.4208846e-03 1.1004494e-03 1.6436932e-04 1.5507648e-03 1.1191058e-03 1.7495883e-03 1.6621469e-03 2.5414268e-03 8.8363295e-04 4.0096791e-04 3.3482530e-04 4.2345389e-03 1.9374158e-03 1.2770848e-03 1.0395507e-03 3.9095383e-04 9.2829494e-04 2.8992176e-03 3.2975300e-03 9.5393320e-04 1.0925514e-03 1.6776296e-03 3.8507253e-03 2.3805613e-03 1.2082502e-03 2.8135004e-04 4.7639114e-04 8.7356741e-04 7.4310336e-04 2.3860646e-03 3.8892953e-04 3.1610282e-04 8.0643344e-04 1.4728895e-03 2.0712058e-03 4.6512964e-04 1.1229948e-03 8.8791739e-05 1.1956364e-03 1.3055469e-03 1.5870433e-03 3.7223827e-04 1.0219838e-03 4.8252621e-04 2.1760282e-03 1.1580239e-04 1.5036929e-03 1.5974284e-03 4.9006747e-03 5.1331960e-04 1.6812595e-03 1.3616634e-03 2.7816530e-03 3.8434871e-03 1.4972893e-03 1.8359326e-03 3.6884416e-03 3.0552141e-03 1.4996272e-03 1.2110783e-03 4.7697841e-03 1.3648602e-03 3.9191527e-04 1.9808749e-03 1.8113262e-03 2.3841094e-03 7.8154360e-04 2.2164780e-03 1.0439462e-03 5.1766229e-03 1.1157470e-03 3.6934914e-03 1.2626931e-03 5.1659909e-04 5.8959899e-05 1.4097594e-03 7.9470954e-04 1.7881991e-03 1.5872251e-03 2.1165612e-03 7.0368642e-04 9.2823209e-04 1.5533041e-04 3.3160103e-03 1.5048956e-03 2.0235000e-03 2.4966081e-03 3.5807846e-03 4.7284012e-04 2.2730788e-03 1.2853266e-03 5.0519729e-03 2.3762808e-03 2.2444800e-03 6.5500299e-04 2.4750944e-05 4.4815197e-04 1.3292061e-03 1.4780836e-03 1.3932149e-03 1.1924273e-03 1.5277304e-03 1.1896691e-03 2.0673918e-03 2.5400150e-04 1.8225589e-03 5.4227610e-04 2.9880363e-03 1.8314313e-03 2.5238977e-03 5.5757663e-05 2.3698503e-03 2.8213423e-03 1.3040881e-03 2.4190319e-03 1.1221589e-03 2.3332880e-03 1.5386845e-03 7.3215728e-04 3.3801838e-03 1.6264024e-04 2.8457958e-04 1.3051710e-03 1.8856170e-03 1.1599202e-03 1.2252261e-03 2.8396493e-03 1.3307965e-03 7.9081662e-04 1.1208231e-03 3.7513747e-03 2.2532467e-04 1.8915999e-03 1.8066444e-03 7.8138393e-03 1.7396624e-03 2.9887611e-03 2.1626603e-03 4.5716343e-03 4.7705983e-03 3.1616233e-03 3.6125170e-03 5.9695278e-03 4.2653532e-03 1.8208998e-03 2.2513824e-03 7.5822521e-03 3.0950854e-03 2.6638425e-03 2.3329339e-03 1.2833498e-03 1.8503904e-03 7.6221882e-04 7.7332789e-03 2.4545666e-03 4.1819304e-03 2.3678329e-03 3.7122984e-03 4.8118232e-03 2.5553184e-03 3.0167737e-03 5.3596333e-03 3.8038292e-03 8.6990936e-04 2.5148680e-03 7.9707274e-03 3.8479364e-03 3.0845972e-03 3.4545687e-04 3.2912795e-03 2.5772857e-03 4.0614792e-03 4.9082550e-04 6.4992316e-04 1.0688017e-03 2.7640538e-03 2.9796108e-03 1.9652949e-03 2.0639503e-03 3.2701588e-03 2.6492756e-03 2.3500195e-03 8.8549039e-04 3.5901884e-03 9.2048730e-04 3.0276112e-03 3.6195589e-03 1.4632965e-03 2.1763560e-03 1.5224813e-03 2.5475111e-03 1.6768950e-03 5.4331181e-04 3.2527051e-03 2.4129313e-04 2.7799169e-04 1.0453548e-03 1.7267901e-03 1.3027778e-03 1.3633707e-03 2.6503970e-03 1.5759024e-03 2.1731558e-03 1.7887862e-03 5.7263386e-03 8.5148931e-04 1.6383081e-03 1.3458097e-03 3.3594814e-03 3.6389958e-03 2.2301817e-03 2.5225544e-03 4.3534713e-03 3.1787209e-03 1.6954559e-03 1.3061945e-03 5.4111697e-03 1.7803664e-03 4.1741292e-03 7.9197469e-03 2.2821957e-03 5.7150571e-03 5.2344879e-03 5.9478982e-03 9.4860449e-03 3.0218268e-03 3.9157169e-03 7.0424278e-03 7.6315025e-03 4.1845322e-03 4.7751866e-03 8.3019483e-03 3.6628005e-03 5.6294536e-03 2.4239872e-03 2.7647556e-03 9.2421646e-04 1.7874511e-03 2.0292522e-03 1.9168834e-03 1.9458286e-03 3.1481713e-03 1.3908818e-03 1.3324310e-05 1.1782972e-03 5.7639460e-03 3.2058410e-03 2.4140579e-03 2.0956665e-03 3.1463866e-03 1.2261330e-03 4.0134358e-03 1.5054952e-03 1.1240475e-03 4.7360391e-04 2.7309553e-03 5.1301270e-03 2.4137049e-03 8.9922816e-05 1.2592279e-03 8.6164876e-04 1.2616309e-03 1.6936265e-03 3.4914911e-03 6.4024254e-04 8.0798030e-04 1.9823430e-03 2.5037231e-03 2.1768005e-03 8.4515239e-04 2.3117307e-03 2.1993248e-04 5.6812764e-04 1.3931392e-03 1.4308209e-03 1.6084351e-03 1.3403960e-03 1.4811963e-03 1.2106398e-03 2.4235208e-03 3.5787719e-04 1.6247178e-03 6.1681362e-04 9.2162383e-04 6.4491663e-04 1.4082859e-03 1.1774797e-03 1.5785221e-03 4.0122185e-04 7.3237681e-04 6.4530654e-05 2.8852212e-03 1.3991525e-03 1.5175131e-03 5.9336508e-04 2.9036861e-04 2.0099198e-04 5.5129609e-04 1.5179733e-03 6.8841417e-04 1.3866758e-03 1.2880812e-03 3.2376306e-03 2.6196338e-03 2.0860993e-03 2.5676122e-04 1.7880288e-03 9.1594192e-04 3.5976292e-03 3.2249197e-03 6.3653022e-05 8.5949029e-04 1.8263557e-03 1.6881339e-03 9.7411877e-04 1.7845049e-03 6.1330704e-04 4.6521065e-04 1.3489557e-03 1.6857799e-03 7.7549379e-04 1.3461987e-03 6.0478443e-04 1.0485877e-03 2.7784351e-03 1.1708179e-03 5.9587066e-04 1.2049026e-03 1.1592978e-03 4.9960727e-04 2.5830270e-03 2.2390567e-03 9.5053595e-04 5.2396832e-03 2.8622802e-03 2.1952710e-03 8.7416055e-04 1.1307517e-03 diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-cosine-ml.txt b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-cosine-ml.txt new file mode 100644 index 0000000000000000000000000000000000000000..7c6b67fa43c5fef11101d28dd46f4c1b325b65ee --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-cosine-ml.txt @@ -0,0 +1 @@ + 2.5695885e-01 2.6882042e-01 2.3470353e-01 2.9299329e-01 2.2742702e-01 3.1253572e-01 2.4986352e-01 3.0770122e-01 2.5191977e-01 2.7931567e-01 2.8133743e-01 2.6316239e-01 2.6067201e-01 3.2982339e-01 2.8993002e-01 2.5506356e-01 2.8728051e-01 2.4952121e-01 2.8613379e-01 2.6894157e-01 2.3606353e-01 2.1670935e-01 2.3470242e-01 2.4294172e-01 2.4376454e-01 2.3228195e-01 2.3554918e-01 2.4851241e-01 2.0917546e-01 2.4971488e-01 2.4264224e-01 2.7405461e-01 1.9086415e-01 2.6346574e-01 2.5908801e-01 2.2138495e-01 2.2910721e-01 2.2169919e-01 2.0660065e-01 2.3207102e-01 2.5554688e-01 2.5153751e-01 2.6073682e-01 2.0919640e-01 3.3984433e-01 2.7503792e-01 2.1709889e-01 2.7068095e-01 3.0307041e-01 2.4529612e-01 2.2987015e-01 2.7736967e-01 3.0310708e-01 3.0544316e-01 1.9205388e-01 2.7098021e-01 2.0722466e-01 2.6387343e-01 2.8998308e-01 2.2633010e-01 2.5177075e-01 1.6347011e-01 2.4036389e-01 2.6485871e-01 2.8491965e-01 2.2273619e-01 2.4511873e-01 2.5930533e-01 2.6589995e-01 2.7797191e-01 2.3357373e-01 2.4279909e-01 2.3544532e-01 1.9447286e-01 2.3993534e-01 2.0856243e-01 2.2125251e-01 2.1988206e-01 2.0590152e-01 2.6441952e-01 2.0052739e-01 2.2978496e-01 2.4483670e-01 2.3879510e-01 2.9398425e-01 2.7541852e-01 2.3777469e-01 2.9151131e-01 2.0672752e-01 2.4584031e-01 2.7475025e-01 2.7064343e-01 2.5603684e-01 2.6165327e-01 2.4233155e-01 1.7892657e-01 2.6111203e-01 1.9965682e-01 2.4201634e-01 2.6281353e-01 3.1928221e-01 1.9731963e-01 2.7752862e-01 2.2633080e-01 2.6783167e-01 2.5447186e-01 2.6424243e-01 2.1960672e-01 2.2984242e-01 2.8788736e-01 2.8681630e-01 2.6949787e-01 2.3993685e-01 2.4440073e-01 2.5010397e-01 2.3230769e-01 2.9879682e-01 2.4200592e-01 2.6957748e-01 2.6073240e-01 2.6355347e-01 2.3403674e-01 2.2411413e-01 2.2956729e-01 2.8105976e-01 2.2913304e-01 2.4898608e-01 2.3304000e-01 2.2692988e-01 2.3728251e-01 2.2552243e-01 2.0364084e-01 2.3359511e-01 2.6619167e-01 2.6666588e-01 2.3666880e-01 2.7239113e-01 2.0146697e-01 2.3045559e-01 2.1695523e-01 2.1387991e-01 2.2366404e-01 2.2809635e-01 2.0901297e-01 2.2441100e-01 2.3418882e-01 2.8552218e-01 2.4609015e-01 2.0282492e-01 2.5940295e-01 2.7407006e-01 2.3344890e-01 2.1179142e-01 2.7047821e-01 2.9832768e-01 2.0859082e-01 2.8881331e-01 1.8384598e-01 2.5286491e-01 2.2012615e-01 2.3615775e-01 2.6845565e-01 2.3356355e-01 2.7164193e-01 2.4179380e-01 2.5247973e-01 2.5637548e-01 3.2126483e-01 2.3100774e-01 2.8832546e-01 2.0043257e-01 2.7918333e-01 2.4884522e-01 2.2904723e-01 2.3738940e-01 2.9461278e-01 2.9782005e-01 3.0332073e-01 2.5175971e-01 3.1203784e-01 2.6611535e-01 2.3713507e-01 2.2203585e-01 2.3602325e-01 2.5093670e-01 2.6860434e-01 3.0137874e-01 2.3759606e-01 2.6840346e-01 1.9200556e-01 diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-double-inp.txt b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-double-inp.txt new file mode 100644 index 0000000000000000000000000000000000000000..7a77021775ddb61d226aa8c4ba60f0af013e4a6c --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-double-inp.txt @@ -0,0 +1,20 @@ +8.278938049410748956e-01 9.035293984476246987e-01 1.862188994679486731e-01 8.921151312310462433e-01 2.061859119379583216e-02 3.440636727385729676e-01 1.533779912830328662e-01 5.701372300009802663e-01 5.510020730211558915e-01 1.792362258426003496e-01 8.086175120876580857e-01 6.115487184317183189e-01 1.233471787164852618e-02 1.441643531871039663e-03 4.044309209045688913e-01 3.561398959499905148e-01 1.281985712929750720e-01 8.663300833847481508e-01 8.696027786291581352e-01 3.611727370363766454e-01 5.283537658772616830e-01 1.440241088090119526e-01 3.112457227138950566e-01 6.031280796897889873e-01 9.230324792742518047e-01 2.332121881136874908e-01 3.192652267403439659e-02 3.466206294995559656e-01 2.988687728046366399e-01 5.116749542048093513e-02 2.584975830914494344e-01 4.302023478042227289e-01 8.003972751713522849e-01 9.364931911368097328e-01 9.737098649964673891e-01 4.718038453972229762e-01 4.526591686607864817e-01 1.056485678520797666e-01 5.883019714285405710e-01 3.846092237676981274e-01 6.461500053435473845e-01 1.013239729848824933e-01 1.216151561651189761e-01 5.159668929484659827e-01 8.452074473510227115e-01 9.885170962247968873e-01 7.623883073490128615e-01 2.291163243615434997e-02 5.775530980802381364e-01 7.820699896828091635e-01 8.239186345842965942e-01 3.391800105260227571e-01 9.546318451614538292e-01 3.789677917867695367e-01 4.526533399649290690e-02 8.366786473238587707e-01 3.082636811049858094e-01 1.173936820793450853e-01 7.631994969169442200e-02 2.997416650722183329e-01 5.795208655160232203e-01 3.942350892542011431e-01 1.175126383297261379e-01 4.928232513950027149e-01 9.421293996225950096e-01 8.365391053841342295e-02 6.868059693571844093e-01 3.589527962429440722e-01 7.592939427166059962e-01 5.623849466131448649e-01 2.110746828032050715e-01 9.824683704668600859e-01 2.661230142246236996e-01 6.162272315007123469e-01 5.023254536607497656e-01 5.202854476669782624e-02 5.835090668842095596e-01 7.864642118889143552e-01 2.504012386867506823e-01 6.728308641135989365e-01 4.610793534576096420e-01 4.820508770515909980e-01 9.720403251022265989e-01 3.100069285263498120e-01 7.681017126461753275e-01 7.956539306007082146e-02 2.593389637887737464e-01 1.137852590403054531e-01 3.885303073284454012e-01 8.599094660075957686e-01 5.215167875918280682e-02 1.620908248572288102e-01 1.859236090457663249e-01 6.247716512610480555e-01 3.415128495520775020e-01 7.034903368378029320e-01 6.037564640019568163e-01 2.338969434423310290e-01 1.002104885609900187e-02 7.866058403969036217e-01 +8.033694116033356369e-01 8.653264545544031572e-01 7.468340410754038539e-01 6.362430919910603278e-01 5.120006306625468628e-02 9.503348372633585450e-01 4.697732609626817935e-01 4.221305288459429317e-01 3.153452119838391354e-01 2.991014843442657556e-01 1.190667967280257811e-01 3.486567714509342109e-01 8.289493649885054660e-01 8.454811050800014049e-01 9.149673018211901265e-01 7.708707837193897738e-01 2.640157732122547785e-01 2.107897022189605396e-01 4.207633055054439408e-01 6.719500284654699174e-01 1.458031684893063007e-01 1.800412735886125493e-02 8.402733435220011149e-02 4.206760156883160295e-02 1.376933515041314227e-01 1.716717341022133692e-01 1.788220727652158892e-01 8.224310433402118869e-01 7.729093666867475898e-01 2.064223621025984556e-01 9.592092036227207741e-01 8.312490243754996344e-01 6.673289360369902834e-01 4.632847903690773261e-02 7.643954098358983762e-01 9.359341525615098023e-01 1.914966319163026176e-01 4.536590469402868031e-01 8.640836016538007147e-01 3.941529178175462444e-02 5.602101995205478469e-01 9.263806161941660067e-01 1.555995325944817820e-01 6.172208102950116348e-01 6.335576752812099866e-01 9.766975460368043649e-02 4.475795689539874278e-02 3.248842796104995934e-01 5.700377122149502540e-01 9.066962967256807504e-01 5.458460621505676347e-01 6.833401285581487405e-01 2.887244409544044155e-01 1.316338647016834784e-01 2.325673305245992140e-01 4.150121963188406760e-01 3.834845466366055833e-01 8.149365773968725302e-01 1.867003849450201702e-01 3.170322173543018707e-01 6.832093662682684476e-01 1.729728518929105618e-01 9.236557359702636250e-01 9.152941252150086360e-01 7.224879983096620384e-01 8.557920626598064517e-01 5.344883059251644974e-01 4.876873274449112783e-01 8.308277804506420949e-01 3.916624489322212410e-01 3.459695122273966916e-01 4.033512499027409604e-01 6.555726444913008155e-01 7.138452409380238173e-01 1.683937314599968094e-01 1.769382143486440961e-01 7.588683655178136700e-01 3.750589892880819010e-01 7.525176245126207197e-01 6.083961152538303052e-01 1.145972309907993258e-01 6.239614485809552580e-01 1.307655482065895880e-01 8.530458750670916190e-01 4.801602070124768584e-01 8.168122189863546989e-02 3.793139622744635675e-01 1.496986997776840189e-01 7.129023878302899186e-01 6.830979237438047358e-01 7.635375943876505644e-01 1.824004963251233402e-01 5.764695848992339444e-01 8.865113248731604223e-01 5.784337085544002388e-01 9.700026628755119562e-01 7.318207347905059112e-01 3.851401393936705331e-01 1.774291851193399161e-01 9.763423229242296220e-01 +9.287178470949695175e-01 1.748282433617460718e-01 9.238531711586964734e-01 8.291274445125006443e-01 9.513259272578692416e-01 7.486316801165745494e-01 6.257378457524477300e-01 2.062711693536473101e-01 3.970721244184766130e-01 2.738325225026445597e-01 8.735038948299954642e-01 5.415282140033768066e-01 5.176317904298315398e-01 5.347036264518250093e-01 7.482056965410627258e-01 4.140672582824351800e-01 8.709067272363142376e-01 9.499605569181273079e-01 5.380266748336398619e-01 4.369252161707162241e-01 8.235722216228258397e-03 4.308187193646527691e-01 6.030581482859224129e-01 7.316831195156517920e-01 5.540499846834291420e-01 2.044203040111662872e-01 8.645251782981867583e-01 1.816095717570278545e-01 9.639119168018674966e-01 3.572031072322333634e-01 5.580226816834680248e-01 5.586629875016585478e-01 7.213854320902782780e-01 8.513998260042524580e-01 6.308764347277173723e-02 4.299855362100638567e-01 8.789303907444128150e-01 9.178850359236285783e-01 2.275205845091231582e-01 1.899395443939643213e-01 7.103070862773533944e-01 9.450015289553428399e-01 1.691856364522159595e-01 7.368719616877857925e-01 9.600189536623833231e-01 5.128846522932454244e-01 6.209162727118655578e-02 7.992250598838029907e-01 9.141050280518014937e-01 1.471297785256820978e-01 7.466162372930541524e-01 4.656107650642931084e-01 6.399324135161845728e-01 2.023617619481610230e-01 1.019104648900100996e-01 4.390693688536728700e-02 9.822620353006089600e-01 2.881951852926285529e-01 6.191575015960482098e-02 8.989580763251467932e-01 4.635958631890454429e-01 1.781973138114967270e-02 7.906911683818984571e-02 6.525270776225711167e-02 3.620583622807886925e-01 2.651673718940715796e-01 5.829372395929610651e-01 2.118159824373908595e-01 5.900287159143694504e-01 9.405929925178391215e-01 9.262415619063500971e-01 5.639581506302312475e-01 4.529556154689695635e-02 2.873819210518682166e-01 5.718545934306838996e-01 9.877670791317306742e-01 4.120364488714320927e-01 9.896078045634184583e-01 3.796586997026456523e-01 1.178183652203194098e-01 6.641068305236120795e-01 4.045960610587706618e-03 2.262690437428437340e-01 7.839938005832693957e-01 7.695391333937223743e-01 3.713918392552509884e-01 4.245533341514018399e-01 1.475072494020331915e-01 6.011975181419888514e-01 5.158174017998343741e-01 1.788706151398071764e-01 8.880707130134481986e-01 6.463351030474082659e-01 6.499920635615744624e-01 8.570273676455353318e-01 6.055019270899113515e-01 2.123561211054603159e-02 2.027688787664126968e-01 1.930834215328548487e-01 5.131906052747271518e-01 +2.599990881903107010e-01 6.767857524909899336e-01 7.188217446352963558e-01 3.037178903357997672e-01 4.252381412838680541e-01 4.070924411439535984e-02 8.426710493038247485e-02 8.301517457289483426e-01 8.254603255702420705e-01 7.258533909453509514e-01 9.958706809470796451e-01 1.323408451651194584e-01 8.523995455245143571e-01 2.572405385832454705e-02 4.715363690065482727e-01 7.920130365690022378e-01 7.613745641534582775e-01 5.108305991695683002e-01 7.908714335912382376e-01 4.641131983754837043e-01 3.112627109831845873e-01 4.218013908715474436e-01 3.291577909008427394e-01 2.538715054071232213e-01 1.362470842487485401e-01 2.716429790290709745e-01 1.485325814161112534e-01 4.514539027544387517e-01 6.900835128673067365e-01 7.793407072946112457e-02 5.938024345270752624e-01 1.497853829906865553e-01 5.399567982652856424e-01 1.419209916759478496e-03 7.719776132867679497e-01 3.130795105576239523e-01 6.670071611167494030e-01 8.900596881158256979e-01 8.011158503301568645e-01 7.089295605187424520e-01 4.671116382997058114e-01 6.682965170673403899e-01 6.524835265739736823e-02 5.454288420771494783e-01 7.751910790556310049e-01 8.192595541387335256e-01 3.098855848167891835e-01 3.689971355659119601e-01 8.666507475054133769e-01 2.749042684253171220e-01 3.566565602478318775e-01 4.838173174723044978e-01 1.032975933616413489e-01 5.063065339610417492e-02 5.791168455729079900e-01 3.573337411289496668e-01 6.714098909652352898e-01 2.917057662433912846e-01 2.654964332620638467e-01 7.171804039048814694e-01 3.314488637898249657e-01 5.230399837442840649e-01 6.866534136026025692e-02 1.252966394621071178e-01 5.349397882659551184e-01 2.841423847455760709e-01 4.158473635710734362e-01 7.197062989831272128e-01 5.123869045047864113e-01 8.675622821594339840e-01 8.097441845042540054e-01 7.317178252133832439e-01 3.300847596465853462e-01 5.922311859141077273e-01 8.852619511417836318e-02 2.673412917259408994e-01 6.878259052441990651e-01 3.223000927116328462e-01 8.859387123976615319e-01 5.722722388300067742e-01 8.254877606669521750e-01 5.705299682290687624e-01 7.046478734972855262e-01 1.316324413616759559e-01 3.056358395675535800e-01 2.396516834600909140e-01 2.041201422493257311e-01 1.610755140653103989e-01 1.617012564641111538e-01 4.449920510036902144e-01 2.731012972755201274e-01 7.826874666257994662e-01 5.193612375350010746e-01 8.688804522977213729e-01 3.742157602758655610e-02 6.649628920608219307e-01 5.978149424619171315e-01 5.345645500553952711e-01 9.443202650415919441e-01 6.105837075491723498e-01 +6.387761328141735584e-01 4.210087412162694109e-01 3.777306694964789324e-01 3.576349403292201634e-01 7.272699618880260619e-01 9.173392803607671731e-02 1.212535698300880593e-01 3.871229381194544183e-01 7.735150198351389284e-01 4.687200483013695962e-01 5.161778571874678923e-01 9.839646447226980674e-01 8.626932748911960713e-01 9.618485576577924245e-01 2.997996427525421170e-01 3.955404657388794654e-01 8.480126027102616870e-01 8.194992325050480808e-01 2.800213436873294492e-01 7.188391466620779324e-01 2.289766105875049584e-01 3.838547514028287644e-01 1.363553401061209369e-01 2.131328253542326134e-01 2.666779468144075960e-02 3.252883844200405994e-01 4.207860197469600605e-01 2.991365385037647595e-01 9.180779845534067229e-01 8.787338732192649937e-01 5.404510999105649471e-01 1.735493827761729335e-01 7.405224640747264386e-01 3.927355563629583157e-01 3.957109873399460298e-01 1.313029813325972128e-01 6.434498219738993274e-01 7.162213694578050127e-01 6.454998257494671821e-01 3.808124530008022424e-01 2.027201015737234435e-01 6.667632842770417900e-01 1.609491052365198405e-01 1.192413785409307536e-02 4.546773323526854815e-01 7.733541911050207940e-01 3.902525737195561284e-01 4.006023779897505133e-01 5.156517815815246930e-01 6.135685498584592112e-01 7.062153114980724844e-01 5.505858882117883324e-01 3.541308807182554919e-01 5.237151122342533771e-01 5.230649229131387745e-01 1.973541027697351957e-01 7.940327858595511712e-01 9.998588700623055603e-01 3.878271015153827994e-01 4.455006584967207139e-01 8.376414508056347907e-01 3.310833863524501597e-01 8.020469097392601832e-01 1.890327633084128989e-01 3.830289472395409511e-01 8.605040171046141051e-02 9.978185524023941433e-01 8.333890591892906263e-01 4.509013468741837061e-01 6.355778557686052599e-01 1.422515991097305088e-01 9.549891485963732940e-01 7.535776302868563148e-01 9.306005301880662106e-01 2.444330347211679522e-01 5.828218427569508142e-01 1.261938242968304591e-01 2.829188731405173352e-01 8.100246952078660190e-01 2.032739130996042975e-01 3.997268448390065565e-01 3.882777703107541667e-01 1.102505652624736765e-01 5.826634725328041498e-01 6.508734477956333864e-01 1.777287661702166011e-01 4.857051012052149286e-02 6.850537712379254351e-01 5.012281307761055071e-01 3.329154880061502286e-01 5.006261767216675374e-01 4.542081454976160115e-01 6.777801995399822532e-01 4.271303586474960445e-01 7.820470659692947413e-01 5.143462618485082904e-01 4.071273891563575997e-02 8.503383643856671226e-01 6.877485768345151795e-01 6.498843855014626580e-01 +5.539512747016193117e-01 6.329206647391879548e-01 2.798533500321682688e-01 4.825977295850051307e-01 7.625297023172977751e-01 9.081309101427640362e-01 4.124792086535029600e-01 3.647019658319609059e-01 7.529595202332928228e-02 3.072404010876803593e-01 7.890673660964639957e-01 4.079781478915127657e-01 1.440519120695739064e-01 2.538968953804546791e-01 1.595028243568367143e-01 9.066545851872198636e-02 6.367601114674349416e-01 7.622263643880089479e-02 3.015728236404162654e-01 2.424070469873378375e-01 5.711440390241000475e-01 5.717001375511508998e-01 2.237853674032181939e-01 7.112101625753678436e-01 4.321054197012103026e-01 2.505322169010260058e-02 5.877307077139551916e-01 4.415771174397812304e-01 3.766022855145171322e-01 9.803490652619811785e-01 1.229258314111529860e-01 8.108351868714478439e-01 8.558595456964329662e-01 2.168217533833206589e-01 2.034022719386595623e-01 8.687457137579783772e-01 9.013327195854559104e-01 8.156766512673154779e-01 2.717576187546973943e-01 1.756417893371479133e-01 7.555856977566548505e-01 6.708809351312817748e-01 8.998789237886926085e-01 1.936367585946979775e-01 7.949724635465026390e-01 3.164799312763589834e-01 5.493048513173155456e-01 1.608917269168268493e-01 3.048667492191803330e-01 5.599401537727016764e-01 5.779501360842279611e-01 1.296714605309662316e-01 9.160752328055997706e-01 8.058674476110374574e-01 4.385508937505578908e-01 9.212419718012100356e-01 2.249887451242467140e-01 6.283927745352599903e-01 3.778992451536005159e-01 3.571958698867505611e-03 7.276526470528231760e-01 9.051678673805297892e-01 8.465837072484881931e-01 4.548317505393462135e-02 3.189318261926020748e-01 4.446388607398673587e-01 4.292356336344156365e-01 4.203980977718795309e-01 4.698059253071955599e-01 6.151991200848159203e-01 8.479986139404802614e-01 9.870993262459623052e-01 3.164206525899861955e-01 6.464672171639846976e-01 8.508781429592480183e-01 4.733667503354813677e-01 8.076014176740163863e-01 6.671443255679101458e-01 6.639213267047979761e-01 3.681688930741919830e-01 4.679870252651611162e-01 1.790041740686979521e-01 8.446070273663058847e-01 3.350737544979878191e-01 6.600272349677447359e-01 4.356083218487936115e-01 7.995134167346013010e-01 9.083660261041469619e-01 9.743975306734570241e-01 8.144839650654719376e-01 6.865011984586443239e-01 1.709747281999153268e-01 8.534933687161740945e-01 9.494753729726415070e-01 8.140124992294850426e-01 8.936241255316055287e-01 9.087976860818796077e-01 9.030687493451383663e-02 4.025785149840914734e-01 9.592005611533803711e-01 +5.714058727476275523e-01 7.913573761505965365e-02 9.301773447377043036e-01 4.302822433307075256e-01 4.618892554175407783e-01 1.882471300213742760e-01 6.231472878215863487e-01 2.350437450940777717e-01 8.483410480771292894e-01 8.580803842040533036e-01 4.246398783388435350e-01 5.667321565946502604e-01 7.247417018955526480e-02 5.373984417482219333e-01 8.794242091541510931e-01 9.699025554453030162e-01 8.254197752548814160e-01 7.739723972867470492e-01 6.365819416181199841e-01 3.451230687021222820e-02 1.829102490094791644e-02 9.179618383026147965e-01 4.481667270072077214e-01 4.771270250445739380e-01 1.588469404953456454e-01 3.766332499200618633e-01 5.057026248713025751e-02 9.125900914275182352e-01 8.438133644246305076e-01 3.282972411719701222e-01 6.042003956122835584e-01 7.423456085393266290e-01 1.389012737541106546e-02 3.674754266702850991e-02 2.126646727703802586e-01 3.085666164246750887e-01 4.303440338750976757e-01 1.749037978865556342e-01 2.177699993322510519e-01 6.675614739991906355e-01 1.926533336347433512e-01 8.032010572660308600e-01 4.611412981769049679e-01 9.907201268457492827e-01 8.973785930837320235e-01 6.286342392657409128e-01 8.111266245859546364e-01 1.154230969025437092e-01 8.382880466301794176e-01 1.053753927827069115e-01 9.921712862234919328e-01 9.041662667920956631e-01 3.626267376021269362e-01 2.262225368932846425e-02 8.669003741626111204e-01 7.597054897704164089e-01 4.700318514995387442e-01 4.338185014241978665e-01 1.205425463362067573e-01 2.413879270602589111e-01 5.483334840461459025e-01 2.042653841254596925e-01 5.452588940366013270e-01 3.164646091706100339e-01 1.878958248945691301e-01 2.188622304737641855e-01 2.970982599823450698e-01 5.952148400199362976e-01 9.614251220149501176e-01 5.446813400697393392e-01 5.900748097930779146e-01 2.653062526715309621e-01 5.459933097767216692e-01 3.174185404661935550e-01 1.412133354129242457e-01 1.487441669790685594e-01 3.953776242211952674e-01 5.274261039692862418e-01 1.756132307607755072e-01 4.481942852746899630e-01 6.390660088765629521e-01 2.860380430081067571e-01 5.866902519902850166e-03 3.026687645174785946e-02 1.952533570196290924e-01 2.154769096186736066e-01 8.920573593276575064e-01 5.644513191915436767e-01 5.551464696654353492e-01 4.378199413349500579e-01 8.685737643974280608e-01 7.493934764293597173e-02 9.556749726352036234e-01 6.386433482536227890e-01 8.714694524097754691e-02 1.722786161701279628e-01 6.526867532768643176e-01 8.950304705281527662e-01 6.158198776753203152e-01 9.587176904005377809e-01 +7.705718397401561948e-01 3.165816092999733655e-01 4.334200859975760878e-01 8.639807015515663657e-01 5.576514209532534849e-03 2.456745447057938625e-01 1.664686313299922338e-01 9.637084729617834133e-01 1.083448720752323569e-01 1.865218070380464388e-01 3.730358890475884426e-01 5.015351872138350542e-01 7.420710795841709562e-01 4.919420674769692248e-01 3.426558201886464872e-02 8.669984854934246199e-01 2.204243734202966376e-01 4.109792246853891662e-01 4.361732572946559472e-01 6.819306998053020763e-02 9.986304248057148447e-01 4.119289455392274313e-01 8.533050041845835487e-01 3.416914861912183632e-01 6.522191951039880697e-01 4.162803668786793088e-01 9.051674379917418189e-02 4.552378661306888397e-02 2.122677193466918633e-01 7.461518531655018105e-01 4.654688019259497489e-01 7.877564083548750373e-01 4.518328005682387127e-01 7.173857464237374248e-01 6.940056370290903498e-02 2.804574410412373764e-01 6.095681113112718652e-01 3.680596478602831123e-01 1.814569150719304025e-01 6.505055517979729807e-01 2.759585245701871026e-01 1.429501104786028431e-01 7.813891153083207808e-02 8.925314279991185540e-01 6.692056941902108091e-01 1.915141341107173822e-01 5.750233129581091562e-01 2.051961006251528108e-01 3.849013692629975614e-01 9.503788222043518807e-01 7.690419386411734282e-01 9.978147530014782607e-01 1.719584162437415298e-01 4.890758882401113894e-01 7.195660736040896399e-01 2.485818040997200828e-01 9.706486601870933928e-01 5.182604282071262558e-01 8.082072245463804983e-01 4.889961284821118248e-01 8.042893959057633158e-01 3.200685313413229593e-01 8.983245016887355661e-01 2.811495336955205371e-01 3.986095833814048417e-01 8.607229214132059436e-01 4.827620119717191960e-01 6.715610252037491623e-01 9.330824374137768329e-01 7.537710530085762750e-01 9.840804224010484269e-01 2.319352541177217564e-01 9.569114943157627229e-01 5.821928104654411351e-01 6.700479524814679788e-01 5.663434680086896211e-01 8.851091082101365526e-01 6.800562815862243315e-01 3.578475213752868589e-01 2.900164669281133367e-01 8.379170683569914235e-02 9.929972839740475177e-02 5.946248553621906741e-01 1.991332889320840405e-01 8.115065723822508792e-01 2.023388190440008616e-01 4.056545651129230823e-01 2.966825350250481552e-01 7.457176343507545546e-01 9.856015771246517954e-01 2.264338016147812160e-01 8.366528670045663141e-01 6.116829813603242849e-01 2.605933184296719274e-01 5.765962146558850643e-01 5.064075092266390188e-01 5.499615769589756287e-01 9.240234698632640020e-01 7.169900155229913530e-02 3.544181364560751168e-01 +8.154844535553099627e-01 4.797965609394789777e-01 7.476703385713100447e-01 9.086708404761600910e-01 3.191752505450355937e-01 7.611128630021511965e-01 6.246790343299296611e-01 1.942001426217137006e-01 2.789860414631386565e-01 3.236359785042408621e-02 3.178191288741717413e-01 8.372264298357038337e-01 8.872692914664047636e-01 9.589758852077276963e-01 3.123722260380168425e-01 8.980164015338999439e-01 7.260784140459818348e-01 6.567013512265649222e-01 1.028743505926521529e-01 6.821705410750319443e-01 6.889838995316139858e-01 5.587525493094736007e-02 6.921487028366646310e-01 3.616312929861494885e-01 1.673758008792780583e-01 6.626504595920326146e-01 9.125680913222075086e-01 1.424077784972291871e-01 6.508496429060767197e-01 6.615417385775157477e-01 9.654167310675311198e-01 5.536662974550183858e-01 7.092622144968085962e-03 6.694595400455760625e-01 1.828533619119211417e-01 3.421514408394116247e-01 1.242580151818144518e-01 9.888774797458224075e-01 9.777955172739735135e-01 4.271370765628749178e-01 1.211608384809655936e-01 1.580132417172936954e-01 3.242705395708289640e-01 3.268994391754735940e-01 5.213767653645562383e-03 4.475169480357120699e-01 9.593245219293577986e-01 6.994304536782350867e-01 7.063863152769014331e-01 8.381620829497931080e-01 2.760441799736219615e-01 3.755200946645842475e-01 3.627729621737311172e-01 9.518310606719182498e-01 3.577273025276901386e-01 3.991159901003488164e-01 4.187060513068554535e-01 7.422605403637314581e-01 6.697944269780702342e-01 6.020599837037767799e-01 1.571185850817550245e-01 7.519860911185742847e-01 6.635775704496444938e-01 9.487848173531471252e-01 7.900030232338028924e-01 4.143783957270819052e-01 5.618429740858444932e-01 3.737804619062014000e-01 6.179941187802344693e-01 6.553638605616040058e-01 1.009709416658691739e-01 4.935037098582963910e-01 5.485489972455533936e-01 1.024147956480448984e-01 1.195764707555347917e-01 4.910516327810896531e-01 3.551185778630389089e-01 3.857601645798814927e-01 2.074975219600547760e-01 2.084038664460790002e-01 5.268616653491025037e-01 6.948014877618717833e-01 6.179744044618615817e-01 7.063658085955483168e-01 7.925757227686872630e-01 6.199016959584816577e-01 1.163676037434490107e-01 7.425752264755586252e-01 5.403115665133301215e-01 2.546191951391015840e-01 6.961300925345208501e-01 4.003013072125547467e-01 5.906120962720950995e-02 5.879915846330325824e-01 1.213602408288709800e-01 3.801780679842765576e-01 1.731477742402802722e-01 4.624568816669496485e-01 3.304453744619206823e-01 8.810445876116090869e-02 +5.140190515373614932e-01 1.419225260054487459e-01 7.777845802285945354e-01 3.327562899409282071e-01 8.916875699762913943e-01 7.212852862736146564e-01 5.727327199433507321e-01 5.897820225918504189e-01 7.318614954542906892e-01 7.393985144455500480e-01 4.531340740296823100e-01 9.903061584426188224e-01 4.213350938331624773e-01 4.542342471963995987e-01 9.788786426453045530e-01 1.881707000343846303e-02 8.005433413647761176e-01 1.523502822273363755e-01 5.630164732287495921e-01 5.946603842470724599e-01 1.225547698678740582e-01 1.531136594724622491e-01 8.157973612638946825e-02 2.752046015644330490e-01 6.809045821946161370e-01 6.455289724528190387e-01 3.830356726830793646e-01 4.446144649678575034e-01 4.969038423960672191e-01 5.497873820641221432e-01 9.471879627821714331e-01 5.933046675329255448e-01 4.099233758501530378e-02 5.790409810134594659e-01 9.546095885251496549e-01 2.608616052375664074e-01 6.910160339170060562e-01 1.293709850476291168e-01 6.407264616302255078e-03 6.186037089828009261e-01 5.537861302543241049e-01 3.527421038298221845e-01 8.033232052121624944e-01 8.128114152830284711e-01 8.319982582278713235e-01 5.939566376046836460e-01 2.291090283499520597e-01 5.438101817725821130e-01 6.881146379117278888e-01 2.421968586304659166e-01 5.874047918543783275e-01 6.210102709484541794e-01 7.041387566450251212e-01 6.959223476278774134e-01 9.133877300988062498e-01 9.230647706207778525e-01 6.856884219815310155e-01 6.997988808693775820e-01 6.177944932528769417e-01 5.512902545683161515e-01 5.818280341729102911e-01 6.538267999985679646e-01 6.946673485935980219e-01 4.817938258357623571e-02 9.352008817207906333e-01 4.774162142215661042e-01 5.768063588692976529e-01 4.589648891483899540e-02 7.998946815651652997e-01 4.434260476954369201e-01 9.850053510925722566e-01 6.648626681529369309e-01 4.606293826856903140e-01 3.309042418210563774e-01 1.438901922508034614e-01 7.986559119276418484e-01 7.037818421334554042e-01 3.605119534240813772e-01 3.785959549258922641e-01 9.562491516841659100e-01 4.997955143590974147e-01 1.029540300938682762e-01 1.819017177001992502e-01 3.665425750262368831e-01 1.688063588370778412e-01 7.030735208313992901e-01 8.922375654244527610e-01 1.055706412056253152e-01 2.664739907746691561e-01 9.906029568647586325e-01 6.043845090140997911e-03 3.495786295043534775e-01 5.989441999519146131e-01 6.776147193866479679e-01 7.012991789852640601e-01 1.825838783477321536e-01 7.612293578749116385e-01 1.564769891240175292e-01 2.762157292905387251e-01 7.641900040015234818e-01 +4.746013333880729768e-01 7.609202966712714788e-01 2.537820854162747830e-01 1.709362234877408460e-01 1.886635378734374813e-01 2.439567014093724229e-02 7.640304718272151741e-01 3.483216170435471382e-01 7.744289278738043514e-01 4.190437573644867353e-01 5.319091476394965934e-02 8.580130976087452233e-01 6.259446446786639529e-01 8.793213970773006150e-01 2.441023074890465994e-01 7.753405549489799098e-01 8.760187573193888300e-01 5.946480724009295393e-02 2.873093046571124631e-01 8.710837851946537924e-01 9.103181731924696596e-01 6.534637257615111272e-01 4.128420398577182793e-01 4.905858108576378607e-01 6.178275806701372108e-02 6.368043900016381320e-01 2.865296941219959148e-01 6.371773028539067241e-01 4.924322796636745325e-01 1.709313290387282080e-01 1.856892551689268700e-01 9.592782603102242289e-01 5.402593764193130976e-02 7.287312244390512506e-01 5.679467572000697073e-01 6.255587794305905724e-02 3.069660218141317953e-01 1.089960430557104232e-01 5.550748245336984965e-01 2.555948886689661803e-01 4.140925514039996980e-01 1.180376445052062628e-01 8.832322629884041820e-01 7.784546946701487169e-02 3.177678935473182698e-01 6.681804863429485764e-02 7.047099396645268854e-01 4.133897376851528582e-01 5.600656990480865627e-01 3.883995683475501837e-01 4.459430113152932362e-01 4.214077227574740681e-01 4.763369230200156235e-01 2.701480661168440545e-01 4.296286564389811824e-01 9.601402258758658936e-01 6.326999441846863359e-01 2.442086919688498670e-01 8.407708423957936938e-01 3.626867985638081437e-01 3.641441713291436733e-01 7.932397565989488530e-01 8.902073520619256941e-01 1.929173010337000838e-01 7.309376779324568973e-01 7.305852858337777977e-01 6.510197444582447313e-01 9.512661608643838695e-01 8.461467164366111016e-01 9.245490147941206605e-01 2.658844813385705663e-01 9.538758859344749208e-01 8.215517204998477041e-01 8.217795540390903097e-01 7.569662091300560780e-01 6.262685322871274218e-01 5.597770510574888725e-01 8.155720175123675197e-01 8.545688745180864965e-01 8.986051518529034610e-01 2.477911506572628708e-01 8.462580108996445860e-01 6.065941220995090255e-01 6.500490804973033665e-01 1.120463882674053169e-01 9.299049132942927010e-02 1.388364074229719858e-02 5.901199124540731367e-01 2.795110110544174464e-02 1.644097083463245124e-01 5.341029647603202646e-01 5.276816677181681570e-01 5.439849107754858304e-01 5.371677986392331405e-02 4.515163125788429488e-01 5.036243367087100964e-01 5.721818679625961801e-01 5.271368612400184617e-03 7.720961020546839304e-01 9.015383457479009266e-01 +8.301526916287945701e-01 8.704609696144033348e-01 2.955689129581380303e-01 1.762209253489944727e-01 2.698172933050072553e-01 1.138095349991521399e-01 4.092588531860634760e-01 8.202978121681584467e-01 2.822241377079557356e-01 6.117376205659387223e-01 7.169923068016897938e-01 9.310256256264415331e-02 3.989664052931106708e-01 1.651874953308862803e-02 7.890202597932294282e-02 9.068686774810821305e-01 5.203866694486933842e-01 4.297748572844445336e-01 5.208786995443430712e-01 2.163224881365530816e-01 7.274307306357226111e-01 1.675784956180090823e-01 5.969822786565782691e-01 8.959750832846602453e-02 1.253794151891943764e-01 5.352628522116801291e-01 2.562706125890066300e-01 6.030433202137867044e-01 8.330717547440393833e-01 9.603613683422040914e-02 7.569714244468559450e-01 3.184801677796517128e-01 1.667069341164499896e-01 3.132470247801235619e-01 6.417752836394801097e-01 6.433909425912354152e-02 4.056860213146201710e-01 3.166772891331335327e-01 9.574059746098845247e-01 1.492907964460536974e-01 8.311513764927496162e-01 6.652928354977717396e-01 2.396804722185036374e-01 5.812361618600220270e-01 9.724228681350225445e-01 2.853983236378453414e-01 5.337719354896472979e-01 6.779446197712412081e-01 5.485102006140557540e-01 9.010109155962182648e-01 5.724439967467525037e-01 5.965540527411405947e-01 1.598667990086183321e-01 1.363934512727023041e-01 5.327536522697270405e-01 4.123866715061276222e-01 4.617251396918636841e-01 6.935944951381239898e-01 4.300337419593377453e-01 1.892407993760835128e-01 1.666936825594794724e-01 4.625634184864588772e-01 4.805197636774838355e-02 7.003542850133466224e-01 2.130226006716084974e-03 8.678863343041013367e-01 4.874478520451258623e-01 7.043560228741558848e-01 6.317719270475393722e-01 5.372392256296196766e-01 2.982649812986511995e-01 1.272558612133412037e-01 2.467337555730741983e-01 6.546893200021091097e-01 6.291921159383098150e-01 8.505920470407707379e-01 4.046520490181828578e-01 3.875732096593392795e-01 8.551517214319142024e-01 4.152602284179877090e-01 9.587779137989138611e-01 6.977437468944928112e-01 3.240620775541913634e-02 4.025873770391376061e-01 5.485549335619134270e-01 7.146066156157020455e-01 3.012702534568838519e-01 3.526414480395153594e-01 3.309707144485515284e-01 4.315687014460974913e-01 6.641934530697197747e-01 2.172886798352815507e-01 4.807480925564590057e-01 5.006795397998469177e-01 5.818100901154411586e-01 2.107716091585690732e-01 6.606606051140029301e-01 9.317629042790995797e-01 9.840326342340242061e-01 5.752000964817773898e-01 +9.843444595454536872e-01 1.339523968066913540e-02 6.082172659959028671e-03 7.828244785439336662e-01 5.069653703872761819e-01 2.804896494365415327e-01 2.112385836660957139e-02 6.016479440778699228e-02 7.457477935084961818e-01 3.445503949245375397e-01 4.063494277166557200e-01 8.630275274433116817e-01 5.948396018456146850e-01 1.400867933474212457e-01 6.997522422654076646e-01 5.766519767930851081e-01 5.419976500582250889e-01 7.474121304089603735e-01 2.951600193008566686e-01 7.980170422334191827e-01 1.829036799578199757e-01 6.317636496261300749e-01 2.812612231140887431e-02 5.464747656105657381e-01 3.909873503320924204e-01 4.940850205957293406e-01 8.157850130814222611e-01 5.111092739445756150e-01 9.336823640685747439e-01 7.157105167170837445e-01 7.778989455994214097e-01 1.398722535910470466e-01 5.642653936300449091e-01 3.218717164845980028e-01 9.717427501967056402e-01 3.665791984428700134e-01 3.874321311211759156e-02 9.437600858738082188e-02 5.679526822961932231e-01 5.141385991358327079e-01 7.497840799582222715e-02 5.736515309094968318e-01 1.928132849879083954e-01 6.924244068001785823e-01 1.748389677952593146e-01 4.469577663506929532e-01 1.738527450963387455e-01 7.195287763517190793e-01 8.861150811892871682e-01 1.058443750714600506e-01 1.941789362229970894e-01 9.188374820700584422e-02 7.706736301449305104e-01 6.718642548609364828e-01 5.981029087121966237e-01 4.832880127232569434e-01 3.073688779938709148e-01 5.156312334804930009e-01 1.777418420119527553e-01 8.885462205165685079e-01 4.486254681289014723e-02 1.345398129556140132e-01 7.467627984379916484e-01 4.384565546058830643e-01 7.217750080760946263e-01 3.949550352625393890e-01 4.307950907642028593e-01 6.087680934849041270e-01 3.294516167246774874e-01 1.316682090209408962e-01 1.824857738754404046e-01 5.332379826483617524e-01 3.567136182864261151e-02 1.976220743086236631e-01 5.849349042822560296e-01 1.133174406357483344e-01 7.711522754393199675e-01 8.557306786807005183e-01 3.038353471344266143e-01 4.422747047768413875e-01 2.537160404215925702e-01 2.372714099723788328e-01 5.906462765375103396e-01 4.849909323133470007e-01 2.692576210504484813e-01 4.540849506602829821e-01 9.664935719107857759e-01 2.044371576459835804e-01 4.505417469690352616e-01 7.110722322201217249e-01 3.051357995214963870e-01 8.978937034341526457e-01 6.090501112506481185e-01 6.595415779178889215e-01 6.565426836745864581e-01 6.565608489824376059e-01 2.679102664248229626e-01 3.819533138204529443e-01 6.609794961162380744e-01 2.289558446859882856e-01 +9.274935298374649140e-01 1.174096651033715855e-01 3.030761852629033637e-01 1.605508209527917174e-01 9.601854834873225775e-01 4.341959513718630648e-01 6.320768160802121560e-01 4.213429090614078110e-01 3.695553969042019160e-01 5.965457437116089556e-01 3.520335041155040479e-01 7.702703502247409961e-01 8.571112772962534709e-01 7.904077282532658844e-01 2.247339318352784554e-01 6.823720204503556097e-01 5.883435710582129996e-02 6.786037033312407596e-01 9.721137137641507886e-01 2.042576970668320557e-01 8.394085754806240862e-01 7.433082729552867862e-01 4.072614159870893147e-01 7.451483066617257123e-01 1.699472962789440045e-01 1.753052015584344314e-01 2.255269204788400428e-01 7.794755643807432799e-01 8.407732260470973662e-01 9.301182862857163558e-01 3.701995309382508648e-01 4.481909027604019657e-01 1.261889085033987001e-01 5.600591735875248833e-01 8.244692493969552061e-01 8.969188061645969601e-01 4.802217973423368313e-01 3.556164122713412201e-02 3.393317823164623270e-01 2.491242957582864292e-01 9.863253789366602797e-01 5.585415885291432625e-01 3.702350606362231344e-01 6.766101432620400535e-01 6.999259389475386284e-01 6.676108316872160220e-01 7.870681827507105544e-01 8.746765411259082024e-01 9.125268371282718727e-01 6.638849997061806452e-01 3.253268113800632522e-01 7.968625619248901337e-01 7.584122525443606211e-01 9.028886850768532701e-01 5.381622293189292083e-02 8.097562873320752752e-01 7.092942088208666895e-01 9.915538877968065323e-01 4.319294903327922652e-01 4.307127933969153721e-01 2.768507739641907772e-01 8.076253078288621046e-01 2.569233696442670967e-01 7.595893829724666979e-01 5.768081727897018673e-01 2.537536777625452045e-01 8.874419624636734616e-01 5.091705681832693342e-01 4.811826624992353585e-01 2.794462461940371290e-01 3.846927898276129021e-01 5.129562951959991679e-01 8.515004062224775794e-01 7.103144978683579858e-01 9.526388607201888847e-01 2.367905569592337889e-01 9.137336039323161740e-01 5.722969943101696710e-02 2.019723935481291255e-01 3.098764675203513619e-02 1.121146613918624357e-01 9.937693067724532314e-01 8.476717958861412772e-02 2.059652110343795917e-01 2.139791918759540446e-01 9.137860316709250919e-01 9.530862653366889425e-03 2.027843281683039400e-03 2.506229951837134484e-01 6.244523528392044165e-01 5.523937894075592325e-01 3.712168074031840792e-01 4.218847794299319665e-01 4.827576239387890711e-01 5.244634168840578425e-01 5.182241092381567604e-01 3.308639956263292881e-03 9.370528021570383448e-01 4.694554875029453012e-01 4.950447554541728135e-01 +1.525818111800841814e-01 4.708012184002630107e-02 3.899035965341954846e-01 3.928304521031263929e-01 5.602286661727436945e-01 9.738256658043862313e-01 9.404465779766183475e-01 5.750862754958349088e-01 9.547546956257608741e-01 2.750275291553152535e-01 1.682773435862793265e-01 5.865928471016079726e-04 8.543378154943809255e-01 3.547649971465383079e-01 5.058056647397523031e-01 9.116332486700751137e-02 7.534666421106954726e-01 3.082429494433007733e-01 4.527145111847344916e-01 5.456680635225539255e-01 2.504131242494785914e-01 2.509240770568589296e-01 3.949236999582302898e-01 8.782959620323271821e-03 2.474641132111736752e-01 8.229417958971670943e-01 3.444225768479134420e-01 4.000027489436257522e-01 4.247741954177396417e-01 2.497745404169693373e-02 4.325768602588443423e-01 7.336592463477830117e-01 7.667663267650381975e-02 4.179022553581047683e-01 8.745172741480690126e-01 9.417705509525042817e-02 2.807522782799587446e-01 8.212710101351362590e-01 2.211181944001613386e-01 4.319929503523877168e-01 1.858636923768219873e-02 6.737037795085246694e-01 7.997187114913413275e-01 2.976552505976116647e-01 3.272347030789168887e-01 5.550935453236346406e-01 9.224109746648162522e-01 3.192827922106745708e-01 3.500098324549234530e-01 7.821988386980260888e-01 4.478417135239194380e-01 1.580956175222456572e-01 5.300807813550156844e-01 5.806154798468634581e-01 9.456842911054151868e-01 7.688127895655872956e-01 8.456527833650537840e-01 1.784229089865225770e-01 8.114517450321339087e-01 8.062506298824222428e-01 2.113482500442499523e-01 2.629226789210241666e-01 6.478686221690072022e-01 6.006672861605766300e-02 7.013679843242253131e-01 8.784753961212666828e-01 3.487138165323044880e-02 4.928426758517070461e-01 5.976224683315064512e-01 7.629063997052759616e-01 2.761721278953045422e-01 7.240740503283805696e-01 6.131065729985127888e-01 1.630885615792579957e-01 8.473783868551159060e-01 8.347614542399306448e-02 8.137265626844719657e-01 8.512508664918938539e-01 2.777097816703766320e-01 1.729154355214796990e-01 2.203382750835449766e-01 6.134780912629795857e-01 3.524352564238901753e-01 5.370314860129862256e-01 8.013986113284543578e-02 2.555842138998117852e-01 6.553915758947851389e-01 9.679125599178584061e-01 2.549566319678178150e-01 4.008180804370896633e-01 9.145789951670967310e-01 2.787926039163850511e-01 8.599455912576436933e-02 9.637558000691170967e-02 8.274101203974880692e-01 1.803747268179315411e-01 2.175735407836230095e-01 7.825994939720237742e-01 7.928519890958951599e-02 8.707949373106749213e-01 +6.398420210047787160e-01 5.739624494012524059e-01 3.359672805578653998e-01 1.130399363175038641e-02 3.349439685346782269e-01 2.315484030880912147e-01 4.575228302577399875e-01 1.149494135594463229e-01 2.888244352925943836e-01 3.625470995156252485e-01 3.795973190611611203e-01 6.567047810450010736e-01 1.484039742710284715e-01 9.273251916560719676e-01 4.334256728976307871e-01 6.734771102219323513e-01 9.125080197222198430e-01 4.974393931097168542e-01 8.301481563280355136e-01 4.526450714147856047e-01 2.414236092573898151e-01 8.070129698367667359e-02 7.260400697427102923e-01 1.396509691839398215e-02 2.496450588391967429e-01 4.335741205447194435e-01 3.089314419194891803e-01 9.543503534526003307e-01 5.457977547458532364e-01 3.139663643587058406e-01 5.034762326753475792e-01 4.756788330475764104e-01 6.849334942793482428e-01 3.880666613022351052e-01 6.483446580176778218e-01 5.217503801099343530e-01 5.371145824070304720e-01 3.121260159429154468e-01 8.314121854062171968e-01 4.538695969561833410e-01 8.598896961203845724e-01 9.961993522734106099e-01 8.865717795946430613e-01 7.828987966783660379e-01 3.412415531643435695e-01 7.421170530151157685e-01 4.484104178639959359e-01 6.793217012099640462e-01 3.756179958191659951e-01 7.821287098222597933e-01 6.227726265188193722e-02 8.552983413221663112e-01 4.824668768009222619e-01 2.241531065858231031e-01 4.939536577599041856e-01 5.129566641128722182e-01 1.057984177672518511e-01 9.541452507300716146e-01 3.396646181755047511e-01 7.452588103611947901e-01 5.315559265659929311e-01 5.493475179850665358e-01 5.214824278139198466e-01 5.150075718147916204e-01 1.196075368500321146e-01 9.035665331176232495e-01 7.522653903639873185e-01 6.638708679914825384e-01 5.584174553800479446e-01 5.015819402508836511e-01 5.507698483308445248e-01 5.978677577011723976e-01 8.450418028759657529e-01 3.266677322748618995e-01 1.321610045897971819e-01 2.394354042746985600e-01 2.723972163557076831e-01 5.523301747352814539e-01 5.518043850608547185e-01 5.283968096837132755e-02 8.192733312104071297e-01 2.277106024970321219e-02 1.414998099027269252e-01 6.517281615256080851e-01 1.811694734825117781e-01 9.472370614713256920e-01 5.454497319021770485e-01 1.364119913158231556e-01 8.446142008509562871e-01 7.671725984742419069e-01 2.461161648406858804e-01 1.421724627107351369e-01 6.290652581179481118e-01 7.094144689448004248e-01 4.419656923472803367e-02 6.614741876652251440e-01 8.712193265403500586e-02 4.734931280852430202e-01 5.382037050480286133e-01 1.396459758005891283e-01 +9.709329844415439670e-01 8.998575745276288229e-01 9.151313462895852568e-01 6.920489275523904471e-01 2.892231405199537919e-01 6.750679746268205550e-01 5.515642485826798280e-01 1.065253097812824956e-01 2.957026803465776510e-01 8.937347659632134400e-01 9.800016515925590310e-01 7.745900896182087436e-01 1.570977683146633774e-01 1.482028765821026273e-01 2.111147779712029271e-01 9.683759902485811200e-01 6.550951580826911425e-01 8.728324682592377703e-01 5.044803166579884257e-01 8.285704754811143991e-01 1.693574499337324735e-02 6.032669995180495182e-02 1.687026879086964692e-01 7.701554026145973619e-01 1.429888016593102718e-01 5.881172815379975827e-02 9.704206919487038396e-01 4.450807650730836951e-01 1.597445784258376689e-01 9.849229394397314152e-01 4.220083573536804744e-01 9.357693600374825671e-01 2.313199262338369033e-01 4.556443403861323294e-01 2.590791012828855822e-01 8.438664994487065085e-01 5.519045677502344427e-01 4.702170125676508050e-01 6.814723205638187897e-01 7.418295483665861001e-01 3.684921032028853904e-01 1.501895844844561845e-01 4.214513377519605308e-01 8.600279963652578408e-01 6.625616611189292238e-01 5.200151456470966105e-01 7.881072743086801058e-01 2.771703241081423519e-01 9.034135930616548071e-01 5.848441705791300738e-01 8.341698181274771473e-01 1.966638677318299777e-01 7.059747894371543042e-01 7.013854316067694716e-01 1.828430942760242983e-01 4.745548949934464966e-01 6.306422394641082452e-01 7.760751707194470939e-01 9.813187212598396547e-01 2.293595795266353266e-01 7.749261876107090830e-01 2.384106107787011819e-01 9.721209688979495223e-01 2.715569353686980714e-01 2.915573577694993146e-01 3.579601509630966349e-01 3.085697512342830962e-01 4.070219981627976047e-01 1.989632411372218579e-01 7.330003339460906542e-01 5.397259604481572381e-01 6.931009942216573849e-01 1.385457419653816080e-01 1.140339999976658358e-01 3.980752590866034613e-01 9.471822621683767540e-01 5.476643721405823895e-01 6.824131903515884279e-02 5.844099130744569992e-01 2.346881692012994236e-01 9.436439228519653000e-01 4.855518260479008141e-02 8.157036123302675579e-01 1.169761256455048581e-01 5.532962903488753970e-01 1.100965596251435308e-01 9.789490602992410029e-01 8.433487462016989733e-01 1.272410782852178013e-01 2.885715258680641160e-01 7.990943955388217779e-01 1.565305358979097727e-01 9.160846960406943129e-02 8.521842244411678147e-01 4.474243106736998099e-01 3.843945818845087015e-01 4.710645906071458944e-01 2.398348154123419729e-01 6.435351435258193087e-01 7.656897921129046658e-01 +4.894328120406804539e-01 7.881019629214267574e-01 6.974585354155089512e-01 2.023858939857701156e-01 1.660984914264745926e-01 4.854517801734643534e-01 2.789848572630315715e-01 2.311636522410289718e-01 9.821076233980715608e-01 1.220641257408076052e-01 2.614036146663852866e-01 7.657560715165320220e-01 3.968360577545695378e-01 4.566023622802184434e-02 1.049701948619241598e-02 9.281162949127452766e-01 4.490137965769909201e-01 2.095846458383606725e-01 9.195504656719085679e-01 9.683515436855471004e-01 9.800174878114910060e-01 5.517610861380117804e-01 6.711570559348770670e-01 5.125258050287277989e-01 2.105581493613526423e-01 8.281813206544574868e-01 4.964783994807770995e-01 7.284974208756571645e-01 1.320629592816270348e-01 6.652194518096135045e-01 9.430156297917950958e-01 7.477263137894260003e-01 2.054087806450300979e-01 4.248209124837907247e-01 7.657518666018259257e-02 1.031614100713345028e-01 4.122242287567021712e-01 4.919658859336810686e-01 3.752650167259050651e-01 4.175771429986683270e-01 6.131376293448997927e-01 5.463797405837259591e-01 3.119918548921774004e-01 6.331762507678504459e-01 5.484632429281035559e-01 6.815448032785871302e-01 8.065695507425107991e-02 8.720129122297424207e-01 8.318188557125294480e-03 2.199323537180564170e-02 8.933872719887463454e-01 1.953120287872067706e-02 2.478721941404590234e-01 5.994061179859005994e-01 6.588362611693047155e-01 6.332808851020984564e-01 3.823849348043323326e-01 5.111091324899629251e-01 7.034808459110406531e-01 4.347681568463539481e-01 4.316973576672314961e-01 9.620411080123215664e-01 6.247837467655984467e-01 8.196961678222113301e-01 5.574601810887074294e-01 8.800635018469276094e-01 8.772255241161972528e-01 5.075275933138404527e-01 8.022583187266906224e-01 2.320670802521890286e-01 1.165626629103270195e-01 4.623759662685936744e-01 7.938327000737943617e-02 7.986374689793115378e-01 6.728842751465858862e-01 8.133909095059230765e-01 1.202639390769081329e-01 1.052937257108800262e-01 8.717600467040409473e-02 2.163819956545051104e-01 6.596483385763984852e-01 1.202843170392309258e-02 1.538789195854695091e-01 3.120247727263308901e-01 3.408168327248596308e-01 3.241861797851740556e-01 3.637074533655986208e-01 1.533669345890729119e-01 4.455921334699539660e-01 5.619140093874478437e-01 1.881731359879111887e-01 9.416670800570559052e-01 1.740018593664415247e-01 7.030242331869680505e-01 5.922055553954849172e-01 9.326211623391688077e-01 6.608322881013140027e-01 7.009721551241574478e-01 1.079126054675583202e-01 6.158176671761947940e-01 +5.185079639625639336e-01 9.613742991518259284e-01 5.555312825626229634e-01 2.647628827924735084e-01 6.003697207460141350e-01 5.392112376769145898e-01 6.781186965667050925e-01 9.908971748181496508e-01 4.124155872095397468e-01 9.814941401724619485e-02 2.684237785531295994e-02 1.774652505962848181e-01 1.707589529595294753e-01 4.640932098465534450e-01 2.882179883914587348e-01 7.276822905806898945e-01 6.145789546745269449e-01 1.100959863917608805e-01 6.798859723042820491e-01 9.096984032948918220e-01 3.971368455178179158e-01 2.959494950971321980e-01 3.742088799298171065e-02 1.960739526210202310e-01 7.536102695342027369e-01 6.680915510628401277e-01 4.136507204312135366e-01 3.613996339406737590e-01 3.605422038261204554e-01 7.098503555159476619e-01 8.093719147087541366e-01 6.344097009128880638e-01 3.990082448083617228e-01 2.805918009906902544e-01 7.078488167363675698e-01 9.969917259866583059e-01 9.442054998992396309e-01 1.329075240769165278e-01 6.810681350588387861e-02 8.503491437913293094e-01 8.347117439165431252e-01 2.381858201903953587e-01 7.884260706938626129e-01 7.109907917419661105e-01 6.390916681983604963e-02 6.174365227062991179e-01 5.085733343630816083e-01 1.716846139694149231e-01 9.065664924270055991e-02 5.625330757196970177e-01 3.539663480209681579e-01 8.937139525947165319e-01 3.981380511900556307e-02 7.403597927449541150e-01 3.803872284089604427e-02 6.729519695709765825e-01 5.306080908840085097e-01 2.091237680402112664e-01 5.902903662907804661e-01 2.094778612095482551e-01 7.323447855684165342e-01 3.644574495843493356e-01 2.006215478057034041e-01 3.737617545555030896e-01 5.253471759602216240e-01 4.287889547869583318e-01 7.086098806190446187e-01 4.510792335515292351e-01 6.383187180169215269e-01 8.779355722397681472e-01 4.221338898667141848e-01 6.375840144651815367e-01 8.683057298299173832e-01 6.093730356952498095e-01 9.297141161056151626e-01 7.770838342807246946e-01 6.549661287008456956e-02 2.835060738158660110e-01 4.474138867374952699e-01 8.530336387421445510e-01 3.160209657891883683e-01 8.301538680518486535e-01 6.646903097549101691e-01 7.187130118106234145e-01 1.651862041735395747e-01 9.578252676762609719e-01 6.490273812885494209e-02 9.777273484666341163e-01 8.930729829254262508e-01 9.851054752118463265e-01 4.094323402286751401e-01 1.139176240124337713e-01 7.612865863899589414e-01 2.266379302491570158e-01 6.998882496157835531e-01 9.945043379099228753e-01 7.111578056749194854e-01 7.806190603886183910e-01 3.410170920712443099e-01 9.446084168886822452e-01 +5.015172758330755931e-01 5.569527971282052237e-01 1.122406928736449094e-01 8.960352822124777461e-01 6.049568585854003810e-02 1.202196001338627918e-01 1.870314295763603196e-01 9.017590029396971296e-01 3.597904628087450485e-01 2.130941062746317671e-01 2.556281834629479111e-01 5.123669364829196438e-01 4.754061129282013409e-01 9.764470380372083369e-01 8.038663983900646848e-01 6.960491266420890666e-01 2.940135977911654264e-01 2.857282759910040326e-03 4.599343225832352999e-02 5.597554495210212977e-01 7.445266674304001908e-01 3.387528030535971180e-01 6.429542922125383031e-01 2.123331785532429627e-01 5.302332654117811739e-01 7.262555377662539557e-01 3.982425859900724507e-01 3.243388301740235402e-01 6.191064123738921898e-01 8.988047781373914580e-01 7.819700328765150088e-01 7.664269102804815992e-01 6.734095355422575757e-03 2.904762329148526945e-01 5.097537644843168625e-01 9.524734606001823423e-01 4.812869576591960463e-01 6.236868013640477493e-01 1.459170943214320726e-01 9.874505139403206844e-01 7.561708982837871407e-01 3.798591332432484924e-01 6.056633451375117438e-01 7.935708170258731764e-01 1.458141583518740569e-01 7.082511296391911237e-01 1.098798009731616343e-02 3.655618484905173160e-01 9.551862303858617009e-01 8.148959351152762487e-02 4.739306219219985294e-02 7.963357515359494876e-01 6.208332695202813944e-01 3.884182264923189409e-01 4.589167647950288531e-01 6.496652974138312775e-01 2.467528128074852889e-01 5.309593064844935206e-01 5.364606369543487574e-01 2.421352989851309756e-01 3.776834556696828660e-02 1.564861233558080267e-01 5.197231021782636740e-01 8.725375120634637494e-01 2.441225493455024820e-01 2.320363366041028330e-01 5.026358683423555185e-01 7.035766000474735771e-01 8.347805591467084563e-01 2.303229841813967393e-01 6.908373419683054850e-01 2.646662377366995056e-01 1.259467197942290007e-01 9.372770922994989595e-01 6.674216272867254940e-01 1.027944489143072238e-01 5.686267290346079806e-01 3.948222804451942958e-01 4.689706944496729868e-01 4.446117700449114807e-02 6.817992275557515081e-01 9.084821829413957106e-01 9.184021015315092518e-01 3.045815734169987632e-01 2.204958624923980537e-03 7.542672057172502553e-01 9.460844786545006269e-01 3.373139094575949848e-02 9.059565314915285494e-01 9.938525461318854504e-01 2.542072661725306437e-01 9.685734112479216229e-02 8.223629541824816203e-01 1.057429056898460118e-01 8.080679390260248063e-01 5.823014244609205914e-01 6.413551528031806725e-01 1.787341975438894170e-01 1.250471413912357388e-01 8.390281297596062782e-01 diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-euclidean-ml-iris.txt b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-euclidean-ml-iris.txt new file mode 100644 index 0000000000000000000000000000000000000000..86de3c7592893bdb4438c9a0e7e60b2e7e5e1727 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-euclidean-ml-iris.txt @@ -0,0 +1 @@ + 5.3851648e-01 5.0990195e-01 6.4807407e-01 1.4142136e-01 6.1644140e-01 5.1961524e-01 1.7320508e-01 9.2195445e-01 4.6904158e-01 3.7416574e-01 3.7416574e-01 5.9160798e-01 9.9498744e-01 8.8317609e-01 1.1045361e+00 5.4772256e-01 1.0000000e-01 7.4161985e-01 3.3166248e-01 4.3588989e-01 3.0000000e-01 6.4807407e-01 4.6904158e-01 5.9160798e-01 5.4772256e-01 3.1622777e-01 1.4142136e-01 1.4142136e-01 5.3851648e-01 5.3851648e-01 3.8729833e-01 6.2449980e-01 8.0622577e-01 4.6904158e-01 3.7416574e-01 4.1231056e-01 4.6904158e-01 8.6602540e-01 1.4142136e-01 1.7320508e-01 1.3490738e+00 7.6811457e-01 4.5825757e-01 6.1644140e-01 5.9160798e-01 3.6055513e-01 5.8309519e-01 3.0000000e-01 2.2360680e-01 4.0037482e+00 3.6166283e+00 4.1641326e+00 3.0935417e+00 3.7920970e+00 3.4161382e+00 3.7854986e+00 2.3452079e+00 3.7496667e+00 2.8879058e+00 2.7037012e+00 3.2280025e+00 3.1464265e+00 3.7000000e+00 2.5806976e+00 3.6276714e+00 3.4351128e+00 3.0099834e+00 3.7682887e+00 2.8827071e+00 3.8535698e+00 3.0757113e+00 4.0472213e+00 3.6578682e+00 3.4161382e+00 3.5972211e+00 4.0472213e+00 4.2449971e+00 3.5312887e+00 2.4939928e+00 2.8178006e+00 2.7018512e+00 2.8948230e+00 4.1352146e+00 3.4117444e+00 3.5199432e+00 3.9115214e+00 3.6180105e+00 3.0000000e+00 3.0215890e+00 3.3120990e+00 3.5958309e+00 3.0099834e+00 2.3874673e+00 3.1527766e+00 3.0740852e+00 3.1256999e+00 3.3451457e+00 2.0904545e+00 3.0577770e+00 5.2848841e+00 4.2083251e+00 5.3018865e+00 4.6904158e+00 5.0566788e+00 6.0950800e+00 3.5916570e+00 5.6364883e+00 5.0477718e+00 5.6391489e+00 4.3566042e+00 4.5199558e+00 4.8538644e+00 4.1904654e+00 4.4170126e+00 4.6260134e+00 4.6454279e+00 6.2401923e+00 6.4984614e+00 4.1412558e+00 5.1215232e+00 4.0286474e+00 6.2112801e+00 4.1097445e+00 4.9699095e+00 5.3122500e+00 3.9774364e+00 4.0074930e+00 4.8404545e+00 5.0970580e+00 5.5461698e+00 6.0141500e+00 4.8805737e+00 4.1605288e+00 4.5705580e+00 5.7887823e+00 4.8918299e+00 4.6065171e+00 3.8961519e+00 4.7968740e+00 5.0199602e+00 4.6368092e+00 4.2083251e+00 5.2573758e+00 5.1361464e+00 4.6540305e+00 4.2766810e+00 4.4598206e+00 4.6508064e+00 4.1400483e+00 3.0000000e-01 3.3166248e-01 6.0827625e-01 1.0908712e+00 5.0990195e-01 4.2426407e-01 5.0990195e-01 1.7320508e-01 8.6602540e-01 4.5825757e-01 1.4142136e-01 6.7823300e-01 1.3601471e+00 1.6278821e+00 1.0535654e+00 5.4772256e-01 1.1747340e+00 8.3666003e-01 7.0710678e-01 7.6157731e-01 7.8102497e-01 5.5677644e-01 6.4807407e-01 2.2360680e-01 5.0000000e-01 5.9160798e-01 5.0000000e-01 3.4641016e-01 2.4494897e-01 6.7823300e-01 1.1489125e+00 1.3416408e+00 1.7320508e-01 3.0000000e-01 7.8740079e-01 1.7320508e-01 5.0990195e-01 4.5825757e-01 5.2915026e-01 8.1853528e-01 5.4772256e-01 6.7823300e-01 9.8488578e-01 1.4142136e-01 8.4852814e-01 3.6055513e-01 8.1240384e-01 3.1622777e-01 4.0963398e+00 3.6864617e+00 4.2367440e+00 2.9698485e+00 3.8118237e+00 3.3911650e+00 3.8600518e+00 2.1470911e+00 3.7881394e+00 2.8053520e+00 2.4617067e+00 3.2449961e+00 3.0413813e+00 3.7121422e+00 2.5592968e+00 3.7000000e+00 3.4336569e+00 2.9715316e+00 3.6918830e+00 2.7928480e+00 3.8935845e+00 3.0740852e+00 4.0187063e+00 3.6565011e+00 3.4467376e+00 3.6510273e+00 4.0804412e+00 4.2953463e+00 3.5383612e+00 2.4186773e+00 2.7000000e+00 2.5787594e+00 2.8548205e+00 4.1170378e+00 3.3985291e+00 3.5972211e+00 3.9786933e+00 3.5580894e+00 2.9983329e+00 2.9291637e+00 3.2434549e+00 3.6221541e+00 2.9546573e+00 2.1794495e+00 3.1032241e+00 3.0789609e+00 3.1144823e+00 3.3645208e+00 1.9131126e+00 3.0298515e+00 5.3385391e+00 4.1809090e+00 5.3572381e+00 4.7085029e+00 5.0911688e+00 6.1595454e+00 3.4799425e+00 5.6868269e+00 5.0408333e+00 5.7471732e+00 4.4192760e+00 4.5210618e+00 4.9020404e+00 4.1340053e+00 4.4022721e+00 4.6808119e+00 4.6829478e+00 6.3694584e+00 6.5314623e+00 4.0620192e+00 5.1903757e+00 4.0024992e+00 6.2617889e+00 4.1060930e+00 5.0428167e+00 5.3898052e+00 3.9812058e+00 4.0311289e+00 4.8518038e+00 5.1584882e+00 5.5919585e+00 6.1546730e+00 4.8918299e+00 4.1689327e+00 4.5475268e+00 5.8600341e+00 4.9598387e+00 4.6508064e+00 3.9153544e+00 4.8600412e+00 5.0724747e+00 4.7021272e+00 4.1809090e+00 5.3207142e+00 5.2067264e+00 4.7000000e+00 4.2497059e+00 4.4988888e+00 4.7180504e+00 4.1533119e+00 2.4494897e-01 5.0990195e-01 1.0862780e+00 2.6457513e-01 4.1231056e-01 4.3588989e-01 3.1622777e-01 8.8317609e-01 3.7416574e-01 2.6457513e-01 5.0000000e-01 1.3638182e+00 1.5874508e+00 1.0099505e+00 5.1961524e-01 1.2369317e+00 7.5498344e-01 8.3066239e-01 7.0000000e-01 5.0990195e-01 6.4807407e-01 6.4031242e-01 4.6904158e-01 5.0990195e-01 6.1644140e-01 5.4772256e-01 3.0000000e-01 3.3166248e-01 7.8102497e-01 1.0535654e+00 1.2845233e+00 3.1622777e-01 3.1622777e-01 8.5440037e-01 3.1622777e-01 3.6055513e-01 4.8989795e-01 4.3588989e-01 9.2736185e-01 3.0000000e-01 6.5574385e-01 9.5916630e-01 2.6457513e-01 7.8102497e-01 1.4142136e-01 8.0622577e-01 3.3166248e-01 4.2766810e+00 3.8496753e+00 4.4158804e+00 3.1543621e+00 3.9974992e+00 3.5510562e+00 4.0112342e+00 2.3065125e+00 3.9749214e+00 2.9495762e+00 2.6476405e+00 3.4029399e+00 3.2588341e+00 3.8794329e+00 2.7202941e+00 3.8807216e+00 3.5749126e+00 3.1527766e+00 3.8961519e+00 2.9782545e+00 4.0311289e+00 3.2588341e+00 4.2071368e+00 3.8314488e+00 3.6318040e+00 3.8340579e+00 4.2731721e+00 4.4698993e+00 3.7027017e+00 2.6153394e+00 2.8879058e+00 2.7712813e+00 3.0364453e+00 4.2825226e+00 3.5298725e+00 3.7322915e+00 4.1545156e+00 3.7669616e+00 3.1464265e+00 3.1032241e+00 3.4073450e+00 3.7854986e+00 3.1400637e+00 2.3537205e+00 3.2680269e+00 3.2326460e+00 3.2726136e+00 3.5425979e+00 2.0856654e+00 3.1953091e+00 5.4726593e+00 4.3347434e+00 5.5290144e+00 4.8682646e+00 5.2469038e+00 6.3364028e+00 3.6083237e+00 5.8660038e+00 5.2249402e+00 5.8940648e+00 4.5738387e+00 4.6936127e+00 5.0695167e+00 4.2918527e+00 4.5442271e+00 4.8270074e+00 4.8456166e+00 6.5207362e+00 6.7178866e+00 4.2508823e+00 5.3488316e+00 4.1436699e+00 6.4467046e+00 4.2813549e+00 5.1942276e+00 5.5587768e+00 4.1496988e+00 4.1856899e+00 5.0149776e+00 5.3385391e+00 5.7775427e+00 6.3126856e+00 5.0537115e+00 4.3416587e+00 4.7169906e+00 6.0406953e+00 5.0921508e+00 4.8062459e+00 4.0669399e+00 5.0269275e+00 5.2287666e+00 4.8682646e+00 4.3347434e+00 5.4753995e+00 5.3535035e+00 4.8641546e+00 4.4305756e+00 4.6615448e+00 4.8487112e+00 4.2988371e+00 6.4807407e-01 1.1661904e+00 3.3166248e-01 5.0000000e-01 3.0000000e-01 3.1622777e-01 1.0000000e+00 3.7416574e-01 2.6457513e-01 5.1961524e-01 1.5297059e+00 1.7146428e+00 1.1661904e+00 6.5574385e-01 1.3228757e+00 8.6602540e-01 8.7749644e-01 8.0622577e-01 7.0710678e-01 6.4807407e-01 5.3851648e-01 4.2426407e-01 5.4772256e-01 7.2111026e-01 6.7823300e-01 1.7320508e-01 2.2360680e-01 8.7749644e-01 1.1704700e+00 1.4247807e+00 3.1622777e-01 5.0990195e-01 1.0049876e+00 3.1622777e-01 3.0000000e-01 5.8309519e-01 6.0827625e-01 8.3666003e-01 3.0000000e-01 7.0000000e-01 9.6953597e-01 2.6457513e-01 8.6602540e-01 1.4142136e-01 9.2195445e-01 4.5825757e-01 4.1773197e+00 3.7336309e+00 4.3058100e+00 2.9849623e+00 3.8729833e+00 3.3926391e+00 3.8897301e+00 2.1118712e+00 3.8548671e+00 2.7784888e+00 2.4515301e+00 3.2680269e+00 3.1080541e+00 3.7376463e+00 2.5806976e+00 3.7762415e+00 3.4205263e+00 3.0000000e+00 3.7496667e+00 2.8160256e+00 3.8923001e+00 3.1304952e+00 4.0620192e+00 3.6851052e+00 3.5114100e+00 3.7229021e+00 4.1545156e+00 4.3497126e+00 3.5623026e+00 2.4698178e+00 2.7202941e+00 2.6038433e+00 2.8913665e+00 4.1279535e+00 3.3674916e+00 3.6069378e+00 4.0422766e+00 3.6262929e+00 2.9966648e+00 2.9376862e+00 3.2357379e+00 3.6482873e+00 2.9899833e+00 2.1633308e+00 3.1080541e+00 3.0838288e+00 3.1224990e+00 3.4132096e+00 1.9157244e+00 3.0446675e+00 5.3357286e+00 4.1773197e+00 5.4064776e+00 4.7222876e+00 5.1097945e+00 6.2153037e+00 3.4205263e+00 5.7384667e+00 5.0813384e+00 5.7844619e+00 4.4519659e+00 4.5530210e+00 4.9457052e+00 4.1303753e+00 4.3965896e+00 4.7010637e+00 4.7095647e+00 6.4140471e+00 6.5901442e+00 4.0877867e+00 5.2297227e+00 3.9862263e+00 6.3229740e+00 4.1436699e+00 5.0695167e+00 5.4387499e+00 4.0124805e+00 4.0472213e+00 4.8733972e+00 5.2172790e+00 5.6550862e+00 6.2153037e+00 4.9132474e+00 4.1988094e+00 4.5552168e+00 5.9321160e+00 4.9628621e+00 4.6690470e+00 3.9268308e+00 4.9101935e+00 5.1048996e+00 4.7602521e+00 4.1773197e+00 5.3497664e+00 5.2325902e+00 4.7455242e+00 4.2883563e+00 4.5332108e+00 4.7191101e+00 4.1496988e+00 6.1644140e-01 4.5825757e-01 2.2360680e-01 9.2195445e-01 5.2915026e-01 4.2426407e-01 3.4641016e-01 6.4031242e-01 9.7467943e-01 9.1651514e-01 1.0862780e+00 5.4772256e-01 1.7320508e-01 7.9372539e-01 2.6457513e-01 5.3851648e-01 2.6457513e-01 5.6568542e-01 5.2915026e-01 5.7445626e-01 6.3245553e-01 3.4641016e-01 2.4494897e-01 2.8284271e-01 5.3851648e-01 5.7445626e-01 5.0000000e-01 5.5677644e-01 7.8102497e-01 5.2915026e-01 4.4721360e-01 5.1961524e-01 5.2915026e-01 8.5440037e-01 2.4494897e-01 1.7320508e-01 1.4000000e+00 7.2801099e-01 4.5825757e-01 5.8309519e-01 6.4031242e-01 3.0000000e-01 5.6568542e-01 3.3166248e-01 3.0000000e-01 4.0607881e+00 3.6633318e+00 4.2190046e+00 3.1480152e+00 3.8496753e+00 3.4568772e+00 3.8249183e+00 2.3874673e+00 3.8078866e+00 2.9223278e+00 2.7586228e+00 3.2710854e+00 3.2186954e+00 3.7456642e+00 2.6267851e+00 3.6851052e+00 3.4669872e+00 3.0626786e+00 3.8340579e+00 2.9376862e+00 3.8845849e+00 3.1336879e+00 4.1036569e+00 3.7067506e+00 3.4741906e+00 3.6551334e+00 4.1085277e+00 4.2965102e+00 3.5763109e+00 2.5573424e+00 2.8740216e+00 2.7604347e+00 2.9495762e+00 4.1785165e+00 3.4380227e+00 3.5510562e+00 3.9648455e+00 3.6864617e+00 3.0364453e+00 3.0708305e+00 3.3541020e+00 3.6400549e+00 3.0659419e+00 2.4372115e+00 3.1968735e+00 3.1128765e+00 3.1670175e+00 3.3985291e+00 2.1424285e+00 3.1032241e+00 5.3131911e+00 4.2461747e+00 5.3507009e+00 4.7307505e+00 5.0960769e+00 6.1457302e+00 3.6166283e+00 5.6877060e+00 5.1009803e+00 5.6762664e+00 4.3977267e+00 4.5683695e+00 4.9010203e+00 4.2308392e+00 4.4508426e+00 4.6626173e+00 4.6882833e+00 6.2785349e+00 6.5536250e+00 4.1964271e+00 5.1643005e+00 4.0607881e+00 6.2657801e+00 4.1605288e+00 5.0079936e+00 5.3591044e+00 4.0249224e+00 4.0472213e+00 4.8836462e+00 5.1497573e+00 5.6017854e+00 6.0572271e+00 4.9234135e+00 4.2083251e+00 4.6141088e+00 5.8438001e+00 4.9203658e+00 4.6454279e+00 3.9344631e+00 4.8445846e+00 5.0616203e+00 4.6861498e+00 4.2461747e+00 5.2971691e+00 5.1730069e+00 4.7010637e+00 4.3301270e+00 4.5044423e+00 4.6786750e+00 4.1737274e+00 9.9498744e-01 7.0000000e-01 1.4594520e+00 1.0099505e+00 3.4641016e-01 8.1240384e-01 1.1618950e+00 1.5716234e+00 6.7823300e-01 6.1644140e-01 4.0000000e-01 5.9160798e-01 3.3166248e-01 3.8729833e-01 5.3851648e-01 4.1231056e-01 1.1224972e+00 6.7823300e-01 8.3066239e-01 1.0099505e+00 6.4807407e-01 5.2915026e-01 6.4807407e-01 1.0148892e+00 1.0246951e+00 5.3851648e-01 4.5825757e-01 4.7958315e-01 1.0099505e+00 9.6953597e-01 6.0827625e-01 1.0099505e+00 1.4177447e+00 6.4807407e-01 7.0000000e-01 1.8814888e+00 1.3000000e+00 6.0827625e-01 3.7416574e-01 1.1269428e+00 3.8729833e-01 1.1224972e+00 3.6055513e-01 8.0622577e-01 3.6124784e+00 3.2465366e+00 3.7868192e+00 2.9444864e+00 3.4698703e+00 3.1543621e+00 3.4073450e+00 2.3280893e+00 3.4146742e+00 2.7055499e+00 2.7147744e+00 2.9189039e+00 2.9832868e+00 3.3896903e+00 2.3366643e+00 3.2588341e+00 3.1464265e+00 2.7784888e+00 3.5468296e+00 2.7073973e+00 3.5085610e+00 2.7928480e+00 3.7709415e+00 3.3674916e+00 3.0935417e+00 3.2465366e+00 3.7121422e+00 3.8832976e+00 3.2264532e+00 2.3194827e+00 2.6758176e+00 2.5729361e+00 2.6608269e+00 3.8470768e+00 3.1400637e+00 3.1448370e+00 3.5411862e+00 3.3867388e+00 2.7239677e+00 2.8407745e+00 3.1032241e+00 3.2726136e+00 2.7892651e+00 2.3748684e+00 2.9223278e+00 2.7910571e+00 2.8548205e+00 3.0347982e+00 2.0566964e+00 2.8053520e+00 4.9061186e+00 3.9255573e+00 4.9223978e+00 4.3566042e+00 4.6978719e+00 5.7052607e+00 3.4263683e+00 5.2659282e+00 4.7349762e+00 5.2057660e+00 3.9774364e+00 4.2011903e+00 4.4833024e+00 3.9370039e+00 4.1146081e+00 4.2497059e+00 4.2918527e+00 5.7913729e+00 6.1343296e+00 3.9179076e+00 4.7275787e+00 3.7483330e+00 5.8360946e+00 3.8013156e+00 4.5760245e+00 4.9173163e+00 3.6633318e+00 3.6742346e+00 4.5066617e+00 4.7222876e+00 5.1788030e+00 5.5596762e+00 4.5453273e+00 3.8457769e+00 4.2883563e+00 5.3916602e+00 4.5022217e+00 4.2473521e+00 3.5693137e+00 4.4124823e+00 4.6411206e+00 4.2497059e+00 3.9255573e+00 4.8682646e+00 4.7391982e+00 4.2848571e+00 3.9887341e+00 4.1024383e+00 4.2649736e+00 3.8183766e+00 4.2426407e-01 5.4772256e-01 4.7958315e-01 8.6602540e-01 3.0000000e-01 4.8989795e-01 6.1644140e-01 1.3601471e+00 1.4933185e+00 9.5393920e-01 5.0990195e-01 1.2083046e+00 6.4807407e-01 8.6023253e-01 6.0000000e-01 4.5825757e-01 6.2449980e-01 5.4772256e-01 6.0827625e-01 4.5825757e-01 6.2449980e-01 6.0827625e-01 3.1622777e-01 4.2426407e-01 8.1240384e-01 9.4868330e-01 1.2083046e+00 4.7958315e-01 5.0000000e-01 9.1651514e-01 4.7958315e-01 4.6904158e-01 5.1961524e-01 4.2426407e-01 1.1090537e+00 3.1622777e-01 5.4772256e-01 8.1853528e-01 4.4721360e-01 6.7823300e-01 2.2360680e-01 7.7459667e-01 4.2426407e-01 4.2308392e+00 3.7854986e+00 4.3669211e+00 3.1272992e+00 3.9560081e+00 3.4899857e+00 3.9344631e+00 2.2781571e+00 3.9357337e+00 2.8827071e+00 2.6495283e+00 3.3361655e+00 3.2634338e+00 3.8209946e+00 2.6627054e+00 3.8353618e+00 3.4942810e+00 3.1160873e+00 3.8794329e+00 2.9495762e+00 3.9420807e+00 3.2202484e+00 4.1701319e+00 3.7828561e+00 3.5916570e+00 3.7907783e+00 4.2391037e+00 4.4147480e+00 3.6414283e+00 2.5980762e+00 2.8653098e+00 2.7549955e+00 2.9983329e+00 4.2225585e+00 3.4423829e+00 3.6414283e+00 4.1024383e+00 3.7549967e+00 3.0740852e+00 3.0626786e+00 3.3555923e+00 3.7229021e+00 3.1064449e+00 2.3388031e+00 3.2140317e+00 3.1654384e+00 3.2093613e+00 3.4957117e+00 2.0639767e+00 3.1400637e+00 5.3758720e+00 4.2638011e+00 5.4680892e+00 4.7989582e+00 5.1710734e+00 6.2801274e+00 3.5312887e+00 5.8137767e+00 5.1797683e+00 5.8077534e+00 4.4977772e+00 4.6368092e+00 5.0049975e+00 4.2272923e+00 4.4609416e+00 4.7423623e+00 4.7780749e+00 6.4397205e+00 6.6708320e+00 4.2190046e+00 5.2744668e+00 4.0620192e+00 6.3992187e+00 4.2284749e+00 5.1137071e+00 5.4963624e+00 4.0902323e+00 4.1121770e+00 4.9477268e+00 5.2886671e+00 5.7314920e+00 6.2401923e+00 4.9849774e+00 4.2871902e+00 4.6626173e+00 5.9883220e+00 4.9939964e+00 4.7318073e+00 3.9912404e+00 4.9618545e+00 5.1526692e+00 4.8031240e+00 4.2638011e+00 5.3972215e+00 5.2678269e+00 4.7968740e+00 4.3840620e+00 4.5934736e+00 4.7497368e+00 4.2178193e+00 7.8740079e-01 3.3166248e-01 5.0000000e-01 2.2360680e-01 4.6904158e-01 9.0553851e-01 1.0440307e+00 1.2369317e+00 7.0000000e-01 2.0000000e-01 8.3666003e-01 4.2426407e-01 4.4721360e-01 3.7416574e-01 6.7082039e-01 3.8729833e-01 4.4721360e-01 4.1231056e-01 2.2360680e-01 2.2360680e-01 2.2360680e-01 3.7416574e-01 3.7416574e-01 4.4721360e-01 7.3484692e-01 9.4868330e-01 3.3166248e-01 3.6055513e-01 5.4772256e-01 3.3166248e-01 7.4833148e-01 1.0000000e-01 2.4494897e-01 1.2288206e+00 6.6332496e-01 4.2426407e-01 6.0827625e-01 4.6904158e-01 4.2426407e-01 4.5825757e-01 4.2426407e-01 1.4142136e-01 3.9648455e+00 3.5623026e+00 4.1170378e+00 2.9866369e+00 3.7296112e+00 3.3256578e+00 3.7282704e+00 2.2113344e+00 3.6918830e+00 2.7802878e+00 2.5690465e+00 3.1543621e+00 3.0545049e+00 3.6249138e+00 2.4959968e+00 3.5818989e+00 3.3481338e+00 2.9206164e+00 3.6837481e+00 2.7820855e+00 3.7815341e+00 3.0049958e+00 3.9686270e+00 3.5791060e+00 3.3555923e+00 3.5454196e+00 3.9912404e+00 4.1892720e+00 3.4554305e+00 2.4020824e+00 2.7110883e+00 2.5942244e+00 2.8089144e+00 4.0509258e+00 3.3181320e+00 3.4583233e+00 3.8613469e+00 3.5383612e+00 2.9137605e+00 2.9189039e+00 3.2093613e+00 3.5242020e+00 2.9206164e+00 2.2561028e+00 3.0577770e+00 2.9899833e+00 3.0397368e+00 3.2771939e+00 1.9697716e+00 2.9698485e+00 5.2191953e+00 4.1206796e+00 5.2478567e+00 4.6162756e+00 4.9899900e+00 6.0448325e+00 3.4741906e+00 5.5803226e+00 4.9749372e+00 5.5973208e+00 4.3000000e+00 4.4474712e+00 4.7968740e+00 4.0975602e+00 4.3358967e+00 4.5661800e+00 4.5793013e+00 6.2040309e+00 6.4420494e+00 4.0472213e+00 5.0695167e+00 3.9395431e+00 6.1587336e+00 4.0373258e+00 4.9142650e+00 5.2621288e+00 3.9051248e+00 3.9357337e+00 4.7686476e+00 5.0447993e+00 5.4927225e+00 5.9849812e+00 4.8093659e+00 4.0865633e+00 4.4833024e+00 5.7463032e+00 4.8311489e+00 4.5398238e+00 3.8223030e+00 4.7455242e+00 4.9628621e+00 4.5902070e+00 4.1206796e+00 5.2009614e+00 5.0823223e+00 4.5989129e+00 4.2000000e+00 4.3977267e+00 4.5891176e+00 4.0607881e+00 5.5677644e-01 1.2845233e+00 6.7082039e-01 4.2426407e-01 3.4641016e-01 1.7916473e+00 1.9974984e+00 1.4317821e+00 9.2736185e-01 1.6124515e+00 1.1489125e+00 1.1575837e+00 1.0862780e+00 8.3066239e-01 9.1104336e-01 8.1240384e-01 6.4031242e-01 8.3066239e-01 1.0049876e+00 9.4339811e-01 4.6904158e-01 4.8989795e-01 1.1401754e+00 1.4491377e+00 1.7029386e+00 5.5677644e-01 7.0000000e-01 1.2569805e+00 5.5677644e-01 1.4142136e-01 8.6602540e-01 8.6023253e-01 6.2449980e-01 3.1622777e-01 9.5916630e-01 1.2609520e+00 4.2426407e-01 1.1575837e+00 3.6055513e-01 1.2083046e+00 7.2111026e-01 4.3794977e+00 3.9230090e+00 4.4977772e+00 3.0886890e+00 4.0435133e+00 3.5383612e+00 4.0767634e+00 2.1794495e+00 4.0360872e+00 2.8930952e+00 2.4939928e+00 3.4336569e+00 3.2326460e+00 3.9012818e+00 2.7367864e+00 3.9711459e+00 3.5707142e+00 3.1511903e+00 3.8768544e+00 2.9427878e+00 4.0570926e+00 3.2969683e+00 4.2083251e+00 3.8457769e+00 3.6905284e+00 3.9102430e+00 4.3324358e+00 4.5287967e+00 3.7229021e+00 2.6134269e+00 2.8337255e+00 2.7184554e+00 3.0413813e+00 4.2720019e+00 3.5085610e+00 3.7920970e+00 4.2320208e+00 3.7656341e+00 3.1543621e+00 3.0561414e+00 3.3615473e+00 3.8183766e+00 3.1320920e+00 2.2293497e+00 3.2449961e+00 3.2465366e+00 3.2771939e+00 3.5860842e+00 2.0049938e+00 3.1937439e+00 5.4972721e+00 4.3104524e+00 5.5821143e+00 4.8795492e+00 5.2706736e+00 6.3953108e+00 3.5028560e+00 5.9143892e+00 5.2316345e+00 5.9757845e+00 4.6292548e+00 4.7053161e+00 5.1176166e+00 4.2485292e+00 4.5276926e+00 4.8692915e+00 4.8774994e+00 6.6174013e+00 6.7557383e+00 4.2071368e+00 5.4074023e+00 4.1158231e+00 6.4984614e+00 4.2965102e+00 5.2488094e+00 5.6258333e+00 4.1677332e+00 4.2083251e+00 5.0259327e+00 5.4009258e+00 5.8300943e+00 6.4265076e+00 5.0645829e+00 4.3588989e+00 4.6968074e+00 6.1155539e+00 5.1322510e+00 4.8383882e+00 4.0853396e+00 5.0892043e+00 5.2735187e+00 4.9386233e+00 4.3104524e+00 5.5235858e+00 5.4064776e+00 4.9142650e+00 4.4294469e+00 4.7010637e+00 4.8887626e+00 4.3023250e+00 7.8740079e-01 3.4641016e-01 1.7320508e-01 7.2801099e-01 1.3114877e+00 1.5556349e+00 1.0099505e+00 5.0000000e-01 1.1000000e+00 7.5498344e-01 6.2449980e-01 7.0000000e-01 7.7459667e-01 5.2915026e-01 5.1961524e-01 2.0000000e-01 4.4721360e-01 5.0990195e-01 4.4721360e-01 2.6457513e-01 1.7320508e-01 6.5574385e-01 1.0440307e+00 1.2609520e+00 0.0000000e+00 3.4641016e-01 7.5498344e-01 0.0000000e+00 5.5677644e-01 3.7416574e-01 5.0000000e-01 9.3808315e-01 5.5677644e-01 6.5574385e-01 8.8317609e-01 2.6457513e-01 7.4161985e-01 3.4641016e-01 7.2801099e-01 2.6457513e-01 4.0435133e+00 3.6359318e+00 4.1856899e+00 2.9478806e+00 3.7709415e+00 3.3421550e+00 3.8065733e+00 2.1307276e+00 3.7389838e+00 2.7748874e+00 2.4556058e+00 3.2031235e+00 3.0133038e+00 3.6619667e+00 2.5258662e+00 3.6523965e+00 3.3852622e+00 2.9223278e+00 3.6687873e+00 2.7586228e+00 3.8457769e+00 3.0364453e+00 3.9799497e+00 3.6027767e+00 3.4014703e+00 3.6055513e+00 4.0348482e+00 4.2497059e+00 3.4942810e+00 2.3874673e+00 2.6720778e+00 2.5495098e+00 2.8178006e+00 4.0718546e+00 3.3496268e+00 3.5425979e+00 3.9293765e+00 3.5284558e+00 2.9495762e+00 2.9000000e+00 3.1984371e+00 3.5707142e+00 2.9189039e+00 2.1679483e+00 3.0626786e+00 3.0248967e+00 3.0675723e+00 3.3181320e+00 1.9104973e+00 2.9883106e+00 5.2924474e+00 4.1436699e+00 5.3113087e+00 4.6583259e+00 5.0467812e+00 6.1081912e+00 3.4525353e+00 5.6329388e+00 4.9979996e+00 5.6973678e+00 4.3749286e+00 4.4821870e+00 4.8600412e+00 4.1060930e+00 4.3760713e+00 4.6411206e+00 4.6324939e+00 6.3071388e+00 6.4876806e+00 4.0286474e+00 5.1468437e+00 3.9686270e+00 6.2112801e+00 4.0706265e+00 4.9919936e+00 5.3329167e+00 3.9446166e+00 3.9874804e+00 4.8114447e+00 5.1029403e+00 5.5443665e+00 6.0917978e+00 4.8538644e+00 4.1194660e+00 4.4933284e+00 5.8180753e+00 4.9142650e+00 4.5978256e+00 3.8729833e+00 4.8176758e+00 5.0338852e+00 4.6690470e+00 4.1436699e+00 5.2744668e+00 5.1652686e+00 4.6669048e+00 4.2201896e+00 4.4575778e+00 4.6722586e+00 4.1060930e+00 6.7823300e-01 9.3273791e-01 1.3674794e+00 5.8309519e-01 7.8740079e-01 3.4641016e-01 3.8729833e-01 3.8729833e-01 3.3166248e-01 3.6055513e-01 3.6055513e-01 9.4868330e-01 6.1644140e-01 7.8102497e-01 8.1240384e-01 5.4772256e-01 2.8284271e-01 3.7416574e-01 8.6602540e-01 8.5440037e-01 3.6055513e-01 4.5825757e-01 5.1961524e-01 7.8740079e-01 7.0710678e-01 3.0000000e-01 7.8740079e-01 1.2369317e+00 4.2426407e-01 5.0000000e-01 1.6792856e+00 1.1357817e+00 6.0827625e-01 5.4772256e-01 9.3273791e-01 3.3166248e-01 9.4868330e-01 1.0000000e-01 5.7445626e-01 3.8065733e+00 3.4554305e+00 3.9824616e+00 3.0708305e+00 3.6496575e+00 3.3331667e+00 3.6290495e+00 2.4124676e+00 3.5916570e+00 2.8705400e+00 2.7730849e+00 3.1176915e+00 3.0822070e+00 3.5791060e+00 2.5099801e+00 3.4496377e+00 3.3496268e+00 2.9257478e+00 3.6851052e+00 2.8372522e+00 3.7349699e+00 2.9597297e+00 3.9370039e+00 3.5411862e+00 3.2695565e+00 3.4322005e+00 3.8858718e+00 4.0841156e+00 3.4190642e+00 2.4372115e+00 2.7928480e+00 2.6795522e+00 2.8142495e+00 4.0348482e+00 3.3436507e+00 3.3778692e+00 3.7389838e+00 3.5199432e+00 2.9154759e+00 2.9849623e+00 3.2603681e+00 3.4684290e+00 2.9359837e+00 2.4494897e+00 3.0886890e+00 2.9782545e+00 3.0380915e+00 3.2140317e+00 2.1424285e+00 2.9782545e+00 5.1487863e+00 4.1243181e+00 5.1332251e+00 4.5628938e+00 4.9183331e+00 5.9118525e+00 3.5972211e+00 5.4635154e+00 4.9173163e+00 5.4497706e+00 4.2023803e+00 4.3965896e+00 4.6968074e+00 4.1255303e+00 4.3324358e+00 4.4833024e+00 4.5011110e+00 6.0282667e+00 6.3300869e+00 4.0681691e+00 4.9547957e+00 3.9560081e+00 6.0315835e+00 3.9912404e+00 4.8062459e+00 5.1283526e+00 3.8600518e+00 3.8858718e+00 4.7148701e+00 4.9173163e+00 5.3721504e+00 5.7887823e+00 4.7560488e+00 4.0336088e+00 4.4665423e+00 5.5991071e+00 4.7486840e+00 4.4631827e+00 3.7815341e+00 4.6292548e+00 4.8682646e+00 4.4698993e+00 4.1243181e+00 5.0970580e+00 4.9779514e+00 4.5033321e+00 4.1701319e+00 4.3162484e+00 4.5110974e+00 4.0323690e+00 4.5825757e-01 8.1853528e-01 1.2328828e+00 1.3638182e+00 8.6023253e-01 3.8729833e-01 9.9498744e-01 5.1961524e-01 6.0827625e-01 4.7958315e-01 6.6332496e-01 4.4721360e-01 3.0000000e-01 4.4721360e-01 2.8284271e-01 4.2426407e-01 4.4721360e-01 2.2360680e-01 3.0000000e-01 6.4031242e-01 8.1853528e-01 1.0816654e+00 3.4641016e-01 4.8989795e-01 7.6811457e-01 3.4641016e-01 6.4031242e-01 3.1622777e-01 3.8729833e-01 1.1832160e+00 5.3851648e-01 4.5825757e-01 6.1644140e-01 4.5825757e-01 5.0000000e-01 3.4641016e-01 5.9160798e-01 3.0000000e-01 3.9912404e+00 3.5637059e+00 4.1327957e+00 2.9444864e+00 3.7336309e+00 3.2848135e+00 3.7188708e+00 2.1307276e+00 3.7013511e+00 2.7166155e+00 2.5000000e+00 3.1336879e+00 3.0463092e+00 3.6041643e+00 2.4698178e+00 3.6027767e+00 3.3015148e+00 2.8948230e+00 3.6742346e+00 2.7477263e+00 3.7483330e+00 3.0033315e+00 3.9547440e+00 3.5580894e+00 3.3630343e+00 3.5608988e+00 4.0049969e+00 4.1928511e+00 3.4336569e+00 2.3874673e+00 2.6720778e+00 2.5573424e+00 2.7892651e+00 4.0174619e+00 3.2588341e+00 3.4365681e+00 3.8729833e+00 3.5369478e+00 2.8740216e+00 2.8757608e+00 3.1575307e+00 3.5057096e+00 2.8982753e+00 2.1863211e+00 3.0166206e+00 2.9546573e+00 3.0049958e+00 3.2726136e+00 1.9157244e+00 2.9376862e+00 5.1874849e+00 4.0779897e+00 5.2488094e+00 4.5891176e+00 4.9689033e+00 6.0506198e+00 3.3882149e+00 5.5812185e+00 4.9618545e+00 5.5982140e+00 4.2918527e+00 4.4305756e+00 4.7937459e+00 4.0521599e+00 4.2953463e+00 4.5497253e+00 4.5628938e+00 6.2112801e+00 6.4459289e+00 4.0162171e+00 5.0665570e+00 3.8897301e+00 6.1660360e+00 4.0236799e+00 4.9030603e+00 5.2649786e+00 3.8884444e+00 3.9115214e+00 4.7465777e+00 5.0517324e+00 5.5009090e+00 6.0041652e+00 4.7874837e+00 4.0681691e+00 4.4463468e+00 5.7645468e+00 4.8052055e+00 4.5188494e+00 3.7947332e+00 4.7486840e+00 4.9537864e+00 4.6000000e+00 4.0779897e+00 5.1903757e+00 5.0714889e+00 4.5978256e+00 4.1844952e+00 4.3874822e+00 4.5617979e+00 4.0224371e+00 5.8309519e-01 1.4317821e+00 1.6941074e+00 1.1269428e+00 6.1644140e-01 1.2569805e+00 8.8317609e-01 7.8740079e-01 8.2462113e-01 7.5498344e-01 6.5574385e-01 6.4807407e-01 3.0000000e-01 5.7445626e-01 6.5574385e-01 5.7445626e-01 3.1622777e-01 2.4494897e-01 7.8740079e-01 1.1747340e+00 1.3928388e+00 1.7320508e-01 3.6055513e-01 8.7177979e-01 1.7320508e-01 4.2426407e-01 5.1961524e-01 5.8309519e-01 7.9372539e-01 4.6904158e-01 7.6157731e-01 1.0344080e+00 2.0000000e-01 8.8317609e-01 3.0000000e-01 8.7177979e-01 3.7416574e-01 4.1785165e+00 3.7643060e+00 4.3162484e+00 3.0298515e+00 3.8897301e+00 3.4496377e+00 3.9344631e+00 2.1886069e+00 3.8639358e+00 2.8618176e+00 2.5019992e+00 3.3181320e+00 3.1064449e+00 3.7788887e+00 2.6324893e+00 3.7828561e+00 3.4942810e+00 3.0315013e+00 3.7643060e+00 2.8530685e+00 3.9623226e+00 3.1511903e+00 4.0877867e+00 3.7188708e+00 3.5242020e+00 3.7322915e+00 4.1581246e+00 4.3737855e+00 3.6083237e+00 2.4879711e+00 2.7586228e+00 2.6362853e+00 2.9240383e+00 4.1797129e+00 3.4539832e+00 3.6687873e+00 4.0583248e+00 3.6304270e+00 3.0610456e+00 2.9899833e+00 3.2954514e+00 3.6905284e+00 3.0215890e+00 2.2248595e+00 3.1638584e+00 3.1400637e+00 3.1780497e+00 3.4380227e+00 1.9748418e+00 3.0951575e+00 5.4092513e+00 4.2449971e+00 5.4350713e+00 4.7738873e+00 5.1633323e+00 6.2353829e+00 3.5256205e+00 5.7584720e+00 5.1097945e+00 5.8283788e+00 4.4977772e+00 4.5934736e+00 4.9809638e+00 4.1988094e+00 4.4743715e+00 4.7592016e+00 4.7528939e+00 6.4459289e+00 6.6075714e+00 4.1231056e+00 5.2706736e+00 4.0669399e+00 6.3364028e+00 4.1809090e+00 5.1176166e+00 5.4635154e+00 4.0558600e+00 4.1024383e+00 4.9234135e+00 5.2316345e+00 5.6683331e+00 6.2337790e+00 4.9648766e+00 4.2355637e+00 4.6021734e+00 5.9447456e+00 5.0338852e+00 4.7191101e+00 3.9862263e+00 4.9416596e+00 5.1526692e+00 4.7906158e+00 4.2449971e+00 5.3972215e+00 5.2867760e+00 4.7843495e+00 4.3243497e+00 4.5760245e+00 4.7916594e+00 4.2178193e+00 1.8083141e+00 2.0420578e+00 1.4662878e+00 1.0099505e+00 1.7320508e+00 1.2165525e+00 1.3190906e+00 1.1747340e+00 6.8556546e-01 1.1180340e+00 1.0295630e+00 8.6602540e-01 9.9498744e-01 1.1090537e+00 1.0344080e+00 6.7823300e-01 7.2111026e-01 1.2727922e+00 1.4764823e+00 1.7262677e+00 7.2801099e-01 7.4161985e-01 1.3190906e+00 7.2801099e-01 2.4494897e-01 9.8488578e-01 9.0553851e-01 7.8102497e-01 3.1622777e-01 1.1135529e+00 1.4177447e+00 6.1644140e-01 1.2409674e+00 4.7958315e-01 1.2884099e+00 8.2462113e-01 4.6882833e+00 4.2391037e+00 4.8135226e+00 3.4322005e+00 4.3692105e+00 3.8729833e+00 4.3931765e+00 2.5238859e+00 4.3577517e+00 3.2295511e+00 2.8390139e+00 3.7589892e+00 3.5707142e+00 4.2308392e+00 3.0643107e+00 4.2836900e+00 3.9000000e+00 3.4856850e+00 4.2154478e+00 3.2832910e+00 4.3794977e+00 3.6235342e+00 4.5442271e+00 4.1773197e+00 4.0124805e+00 4.2272923e+00 4.6551047e+00 4.8507731e+00 4.0521599e+00 2.9478806e+00 3.1764760e+00 3.0610456e+00 3.3749074e+00 4.6076024e+00 3.8379682e+00 4.1060930e+00 4.5486262e+00 4.1012193e+00 3.4828150e+00 3.3970576e+00 3.7013511e+00 4.1448764e+00 3.4684290e+00 2.5748786e+00 3.5818989e+00 3.5749126e+00 3.6083237e+00 3.9115214e+00 2.3452079e+00 3.5270384e+00 5.8189346e+00 4.6454279e+00 5.9059292e+00 5.2105662e+00 5.5982140e+00 6.7186308e+00 3.8379682e+00 6.2401923e+00 5.5668663e+00 6.2872888e+00 4.9487372e+00 5.0378567e+00 5.4415071e+00 4.5858478e+00 4.8559242e+00 5.1894123e+00 5.2048055e+00 6.9260378e+00 7.0851958e+00 4.5497253e+00 5.7271284e+00 4.4474712e+00 6.8242216e+00 4.6281746e+00 5.5686623e+00 5.9455866e+00 4.4977772e+00 4.5354162e+00 5.3572381e+00 5.7227616e+00 6.1554854e+00 6.7305275e+00 5.3953684e+00 4.6904158e+00 5.0338852e+00 6.4342832e+00 5.4497706e+00 5.1643005e+00 4.4124823e+00 5.4092513e+00 5.5955339e+00 5.2545219e+00 4.6454279e+00 5.8455111e+00 5.7245087e+00 5.2354560e+00 4.7644517e+00 5.0259327e+00 5.2057660e+00 4.6314145e+00 5.4772256e-01 4.6904158e-01 8.8881944e-01 5.5677644e-01 7.9372539e-01 8.7749644e-01 8.4261498e-01 1.2806248e+00 1.1489125e+00 1.3601471e+00 1.3416408e+00 1.0954451e+00 8.3666003e-01 8.7177979e-01 1.4177447e+00 1.4035669e+00 8.0622577e-01 6.8556546e-01 4.1231056e-01 1.3114877e+00 1.1313708e+00 5.9160798e-01 1.3114877e+00 1.7233688e+00 9.6953597e-01 9.5393920e-01 2.1447611e+00 1.6155494e+00 1.1000000e+00 1.0295630e+00 1.4317821e+00 8.3066239e-01 1.4560220e+00 6.5574385e-01 1.0816654e+00 3.9711459e+00 3.6851052e+00 4.1713307e+00 3.4684290e+00 3.8961519e+00 3.6810325e+00 3.8665230e+00 2.9017236e+00 3.8236109e+00 3.2832910e+00 3.2511536e+00 3.4205263e+00 3.4292856e+00 3.8716921e+00 2.8670542e+00 3.6469165e+00 3.6905284e+00 3.2771939e+00 3.9974992e+00 3.2233523e+00 4.0211939e+00 3.2526912e+00 4.2284749e+00 3.8444766e+00 3.5199432e+00 3.6496575e+00 4.1036569e+00 4.3011626e+00 3.7188708e+00 2.8106939e+00 3.1968735e+00 3.0886890e+00 3.1591138e+00 4.3474130e+00 3.7067506e+00 3.6400549e+00 3.9446166e+00 3.8196859e+00 3.2649655e+00 3.3749074e+00 3.6455452e+00 3.7536649e+00 3.2863353e+00 2.9291637e+00 3.4554305e+00 3.3181320e+00 3.3808283e+00 3.4914181e+00 2.6057628e+00 3.3271610e+00 5.3916602e+00 4.4485953e+00 5.3282267e+00 4.8352870e+00 5.1623638e+00 6.0835845e+00 4.0249224e+00 5.6595053e+00 5.1749396e+00 5.6053546e+00 4.4249294e+00 4.6636895e+00 4.9091751e+00 4.4654227e+00 4.6357308e+00 4.7138095e+00 4.7476310e+00 6.1562976e+00 6.5169011e+00 4.4056782e+00 5.1487863e+00 4.2906876e+00 6.2080593e+00 4.2649736e+00 5.0159745e+00 5.3103672e+00 4.1376322e+00 4.1641326e+00 4.9769469e+00 5.1068581e+00 5.5587768e+00 5.8932164e+00 5.0159745e+00 4.3116122e+00 4.7801674e+00 5.7471732e+00 4.9809638e+00 4.7138095e+00 4.0693980e+00 4.8238988e+00 5.0813384e+00 4.6518813e+00 4.4485953e+00 5.3047149e+00 5.1807335e+00 4.7138095e+00 4.4530888e+00 4.5530210e+00 4.7507894e+00 4.3335897e+00 6.1644140e-01 1.0908712e+00 6.4031242e-01 8.5440037e-01 1.0816654e+00 9.2195445e-01 1.4628739e+00 1.2727922e+00 1.4177447e+00 1.5811388e+00 1.2247449e+00 1.0488088e+00 1.1401754e+00 1.5779734e+00 1.5968719e+00 1.0440307e+00 6.5574385e-01 3.6055513e-01 1.5556349e+00 1.4352700e+00 9.6436508e-01 1.5556349e+00 1.9313208e+00 1.1832160e+00 1.1618950e+00 2.4289916e+00 1.7916473e+00 1.1618950e+00 9.3808315e-01 1.6703293e+00 8.7749644e-01 1.6431677e+00 8.3066239e-01 1.3228757e+00 3.7907783e+00 3.4842503e+00 3.9874804e+00 3.3926391e+00 3.7443290e+00 3.5171011e+00 3.6400549e+00 2.8705400e+00 3.6715120e+00 3.1464265e+00 3.2572995e+00 3.2403703e+00 3.3970576e+00 3.6945906e+00 2.7349589e+00 3.4785054e+00 3.4899857e+00 3.1654384e+00 3.9115214e+00 3.1416556e+00 3.7854986e+00 3.1272992e+00 4.0914545e+00 3.6878178e+00 3.3749074e+00 3.4899857e+00 3.9572718e+00 4.1109610e+00 3.5425979e+00 2.7568098e+00 3.1336879e+00 3.0397368e+00 3.0495901e+00 4.1689327e+00 3.5014283e+00 3.3955854e+00 3.7603191e+00 3.7403208e+00 3.0886890e+00 3.2726136e+00 3.5114100e+00 3.5679126e+00 3.1843367e+00 2.9154759e+00 3.3166248e+00 3.1448370e+00 3.2171416e+00 3.3391616e+00 2.5903668e+00 3.1827661e+00 5.1215232e+00 4.2555846e+00 5.1156622e+00 4.6238512e+00 4.9325450e+00 5.8711157e+00 3.8652296e+00 5.4598535e+00 5.0059964e+00 5.3347915e+00 4.1952354e+00 4.4799554e+00 4.6968074e+00 4.2918527e+00 4.4192760e+00 4.4698993e+00 4.5343136e+00 5.8855756e+00 6.3253458e+00 4.2883563e+00 4.9122296e+00 4.0853396e+00 6.0133186e+00 4.0951190e+00 4.7686476e+00 5.0892043e+00 3.9572718e+00 3.9547440e+00 4.7696960e+00 4.9132474e+00 5.3721504e+00 5.6364883e+00 4.8062459e+00 4.1340053e+00 4.6054316e+00 5.5434646e+00 4.7085029e+00 4.4877611e+00 3.8600518e+00 4.6076024e+00 4.8476799e+00 4.4384682e+00 4.2555846e+00 5.0616203e+00 4.9254441e+00 4.5011110e+00 4.2976738e+00 4.3416587e+00 4.4799554e+00 4.1133928e+00 5.1961524e-01 5.1961524e-01 3.8729833e-01 6.7082039e-01 4.1231056e-01 9.2736185e-01 7.8740079e-01 1.0049876e+00 1.0488088e+00 7.0710678e-01 5.2915026e-01 5.8309519e-01 1.0535654e+00 1.0630146e+00 5.3851648e-01 4.5825757e-01 3.8729833e-01 1.0099505e+00 8.3666003e-01 4.5825757e-01 1.0099505e+00 1.3601471e+00 6.4807407e-01 5.7445626e-01 1.8384776e+00 1.2369317e+00 6.7082039e-01 6.7823300e-01 1.0908712e+00 4.7958315e-01 1.0862780e+00 3.6055513e-01 7.5498344e-01 3.9509493e+00 3.5972211e+00 4.1303753e+00 3.2664966e+00 3.8105118e+00 3.5142567e+00 3.7643060e+00 2.6191602e+00 3.7603191e+00 3.0397368e+00 2.9949958e+00 3.2680269e+00 3.3015148e+00 3.7483330e+00 2.6720778e+00 3.5972211e+00 3.5071356e+00 3.1304952e+00 3.8704005e+00 3.0413813e+00 3.8665230e+00 3.1304952e+00 4.1158231e+00 3.7282704e+00 3.4365681e+00 3.5860842e+00 4.0521599e+00 4.2284749e+00 3.5791060e+00 2.6419690e+00 3.0000000e+00 2.8948230e+00 3.0000000e+00 4.2047592e+00 3.5014283e+00 3.5057096e+00 3.8858718e+00 3.7134889e+00 3.0822070e+00 3.1733263e+00 3.4568772e+00 3.6318040e+00 3.1272992e+00 2.6608269e+00 3.2710854e+00 3.1543621e+00 3.2109189e+00 3.3837849e+00 2.3302360e+00 3.1543621e+00 5.2602281e+00 4.2766810e+00 5.2678269e+00 4.7180504e+00 5.0507425e+00 6.0522723e+00 3.7603191e+00 5.6187187e+00 5.0852729e+00 5.5479726e+00 4.3243497e+00 4.5486262e+00 4.8270074e+00 4.2778499e+00 4.4508426e+00 4.5934736e+00 4.6497312e+00 6.1400326e+00 6.4768820e+00 4.2602817e+00 5.0705029e+00 4.0951190e+00 6.1822326e+00 4.1436699e+00 4.9295030e+00 5.2706736e+00 4.0074930e+00 4.0274061e+00 4.8569538e+00 5.0734604e+00 5.5226805e+00 5.9016947e+00 4.8928519e+00 4.2035699e+00 4.6551047e+00 5.7227616e+00 4.8528342e+00 4.6086874e+00 3.9217343e+00 4.7528939e+00 4.9819675e+00 4.5760245e+00 4.2766810e+00 5.2172790e+00 5.0813384e+00 4.6173586e+00 4.3255058e+00 4.4485953e+00 4.6162756e+00 4.1785165e+00 7.3484692e-01 3.1622777e-01 4.4721360e-01 2.4494897e-01 6.5574385e-01 4.1231056e-01 6.0000000e-01 5.5677644e-01 2.6457513e-01 1.7320508e-01 1.7320508e-01 5.4772256e-01 5.4772256e-01 3.4641016e-01 6.4807407e-01 8.1240384e-01 5.0000000e-01 3.8729833e-01 4.2426407e-01 5.0000000e-01 8.7177979e-01 1.7320508e-01 1.4142136e-01 1.3453624e+00 7.7459667e-01 3.7416574e-01 5.9160798e-01 5.8309519e-01 3.7416574e-01 5.9160798e-01 3.1622777e-01 2.4494897e-01 3.9749214e+00 3.5818989e+00 4.1340053e+00 3.0594117e+00 3.7589892e+00 3.3852622e+00 3.7496667e+00 2.3130067e+00 3.7215588e+00 2.8478062e+00 2.6758176e+00 3.1890437e+00 3.1224990e+00 3.6687873e+00 2.5396850e+00 3.5958309e+00 3.3985291e+00 2.9849623e+00 3.7349699e+00 2.8530685e+00 3.8131352e+00 3.0413813e+00 4.0162171e+00 3.6318040e+00 3.3852622e+00 3.5651087e+00 4.0187063e+00 4.2107007e+00 3.4957117e+00 2.4637370e+00 2.7874720e+00 2.6739484e+00 2.8618176e+00 4.1024383e+00 3.3749074e+00 3.4813790e+00 3.8794329e+00 3.5888717e+00 2.9647934e+00 2.9866369e+00 3.2832910e+00 3.5637059e+00 2.9782545e+00 2.3558438e+00 3.1192948e+00 3.0430248e+00 3.0919250e+00 3.3136083e+00 2.0493902e+00 3.0232433e+00 5.2421370e+00 4.1689327e+00 5.2668776e+00 4.6572524e+00 5.0179677e+00 6.0646517e+00 3.5510562e+00 5.6089215e+00 5.0169712e+00 5.5991071e+00 4.3162484e+00 4.4833024e+00 4.8155997e+00 4.1484937e+00 4.3680659e+00 4.5814845e+00 4.6119410e+00 6.2088646e+00 6.4668385e+00 4.1109610e+00 5.0813384e+00 3.9849718e+00 6.1830413e+00 4.0718546e+00 4.9325450e+00 5.2829916e+00 3.9382737e+00 3.9686270e+00 4.8020829e+00 5.0705029e+00 5.5163394e+00 5.9849812e+00 4.8404545e+00 4.1303753e+00 4.5453273e+00 5.7532599e+00 4.8476799e+00 4.5727453e+00 3.8561639e+00 4.7581509e+00 4.9769469e+00 4.5923850e+00 4.1689327e+00 5.2182373e+00 5.0921508e+00 4.6097722e+00 4.2379240e+00 4.4204072e+00 4.6065171e+00 4.1024383e+00 6.3245553e-01 5.0990195e-01 6.4807407e-01 1.3228757e+00 8.0622577e-01 1.0099505e+00 1.0723805e+00 8.1853528e-01 6.2449980e-01 7.1414284e-01 1.1747340e+00 1.1489125e+00 5.4772256e-01 6.4807407e-01 5.4772256e-01 1.1000000e+00 1.0535654e+00 5.4772256e-01 1.1000000e+00 1.5811388e+00 7.5498344e-01 8.6023253e-01 1.9621417e+00 1.4899664e+00 8.2462113e-01 6.4031242e-01 1.2409674e+00 6.1644140e-01 1.2922848e+00 4.6904158e-01 9.1651514e-01 3.5014283e+00 3.1827661e+00 3.6891733e+00 2.9291637e+00 3.3896903e+00 3.1368774e+00 3.3615473e+00 2.3769729e+00 3.3211444e+00 2.7404379e+00 2.7313001e+00 2.8930952e+00 2.9034462e+00 3.3436507e+00 2.3302360e+00 3.1606961e+00 3.1511903e+00 2.7331301e+00 3.4770677e+00 2.6795522e+00 3.5014283e+00 2.7294688e+00 3.7054015e+00 3.3120990e+00 3.0099834e+00 3.1543621e+00 3.6097091e+00 3.8065733e+00 3.1906112e+00 2.2737634e+00 2.6551836e+00 2.5475478e+00 2.6210685e+00 3.8144462e+00 3.1638584e+00 3.1272992e+00 3.4539832e+00 3.3015148e+00 2.7221315e+00 2.8319605e+00 3.0951575e+00 3.2280025e+00 2.7477263e+00 2.4062419e+00 2.9103264e+00 2.7748874e+00 2.8390139e+00 2.9698485e+00 2.0928450e+00 2.7856777e+00 4.8928519e+00 3.9166312e+00 4.8456166e+00 4.3162484e+00 4.6583259e+00 5.6124861e+00 3.4828150e+00 5.1749396e+00 4.6636895e+00 5.1468437e+00 3.9306488e+00 4.1496988e+00 4.4192760e+00 3.9331921e+00 4.1206796e+00 4.2201896e+00 4.2391037e+00 5.7105166e+00 6.0398675e+00 3.8704005e+00 4.6690470e+00 3.7603191e+00 5.7349804e+00 3.7496667e+00 4.5265881e+00 4.8321838e+00 3.6207734e+00 3.6455452e+00 4.4654227e+00 4.6249324e+00 5.0803543e+00 5.4607692e+00 4.5066617e+00 3.7894591e+00 4.2449971e+00 5.2915026e+00 4.4877611e+00 4.2035699e+00 3.5482390e+00 4.3428102e+00 4.5945620e+00 4.1821047e+00 3.9166312e+00 4.8176758e+00 4.7000000e+00 4.2296572e+00 3.9370039e+00 4.0521599e+00 4.2544095e+00 3.8065733e+00 5.4772256e-01 1.4142136e-01 7.4161985e-01 5.7445626e-01 6.4807407e-01 8.1853528e-01 4.3588989e-01 3.3166248e-01 4.3588989e-01 7.3484692e-01 7.7459667e-01 5.0990195e-01 3.7416574e-01 5.8309519e-01 7.5498344e-01 6.8556546e-01 5.4772256e-01 7.5498344e-01 1.0862780e+00 4.1231056e-01 3.7416574e-01 1.6278821e+00 9.4868330e-01 4.4721360e-01 4.1231056e-01 8.6023253e-01 1.4142136e-01 7.9372539e-01 2.4494897e-01 5.2915026e-01 3.9268308e+00 3.5341194e+00 4.0902323e+00 3.1080541e+00 3.7429935e+00 3.3704599e+00 3.6905284e+00 2.3937418e+00 3.6972963e+00 2.8618176e+00 2.7820855e+00 3.1638584e+00 3.1796226e+00 3.6414283e+00 2.5436195e+00 3.5594943e+00 3.3660065e+00 2.9916551e+00 3.7696154e+00 2.8879058e+00 3.7603191e+00 3.0413813e+00 4.0162171e+00 3.6124784e+00 3.3674916e+00 3.5369478e+00 3.9987498e+00 4.1725292e+00 3.4727511e+00 2.5079872e+00 2.8372522e+00 2.7294688e+00 2.8757608e+00 4.0828911e+00 3.3421550e+00 3.4146742e+00 3.8379682e+00 3.6193922e+00 2.9410882e+00 3.0166206e+00 3.2893768e+00 3.5298725e+00 2.9983329e+00 2.4474477e+00 3.1224990e+00 3.0166206e+00 3.0757113e+00 3.2954514e+00 2.1400935e+00 3.0199338e+00 5.1749396e+00 4.1496988e+00 5.2191953e+00 4.6162756e+00 4.9699095e+00 6.0116553e+00 3.5623026e+00 5.5623736e+00 4.9989999e+00 5.5181519e+00 4.2626283e+00 4.4609416e+00 4.7717921e+00 4.1460825e+00 4.3428102e+00 4.5265881e+00 4.5661800e+00 6.1163715e+00 6.4311741e+00 4.1303753e+00 5.0239427e+00 3.9623226e+00 6.1392182e+00 4.0570926e+00 4.8672374e+00 5.2220686e+00 3.9179076e+00 3.9306488e+00 4.7686476e+00 5.0229473e+00 5.4781384e+00 5.8940648e+00 4.8072861e+00 4.1036569e+00 4.5232732e+00 5.7061370e+00 4.7770284e+00 4.5199558e+00 3.8196859e+00 4.7095647e+00 4.9264592e+00 4.5486262e+00 4.1496988e+00 5.1584882e+00 5.0289164e+00 4.5705580e+00 4.2355637e+00 4.3794977e+00 4.5365185e+00 4.0607881e+00 5.0990195e-01 1.0816654e+00 4.3588989e-01 6.3245553e-01 5.7445626e-01 4.5825757e-01 3.0000000e-01 3.6055513e-01 7.3484692e-01 6.7823300e-01 2.8284271e-01 7.6157731e-01 8.6023253e-01 6.2449980e-01 6.7082039e-01 4.2426407e-01 6.2449980e-01 1.1489125e+00 3.6055513e-01 5.8309519e-01 1.4798649e+00 1.0954451e+00 5.8309519e-01 5.7445626e-01 7.8740079e-01 5.0990195e-01 8.7749644e-01 3.7416574e-01 5.0990195e-01 3.6110940e+00 3.2511536e+00 3.7775654e+00 2.7784888e+00 3.4161382e+00 3.0822070e+00 3.4322005e+00 2.1095023e+00 3.3630343e+00 2.6095977e+00 2.4494897e+00 2.8896367e+00 2.7802878e+00 3.3436507e+00 2.2605309e+00 3.2419130e+00 3.1192948e+00 2.6551836e+00 3.4073450e+00 2.5495098e+00 3.5298725e+00 2.7110883e+00 3.6810325e+00 3.2939338e+00 3.0364453e+00 3.2140317e+00 3.6565011e+00 3.8716921e+00 3.1843367e+00 2.1470911e+00 2.4959968e+00 2.3769729e+00 2.5475478e+00 3.7907783e+00 3.1128765e+00 3.1874755e+00 3.5312887e+00 3.2434549e+00 2.6776856e+00 2.7055499e+00 2.9899833e+00 3.2403703e+00 2.6627054e+00 2.1377558e+00 2.8266588e+00 2.7386128e+00 2.7928480e+00 2.9765752e+00 1.8439089e+00 2.7239677e+00 4.9598387e+00 3.8858718e+00 4.9295030e+00 4.3393548e+00 4.7095647e+00 5.7113921e+00 3.3391616e+00 5.2516664e+00 4.6765372e+00 5.2848841e+00 4.0062451e+00 4.1641326e+00 4.4911023e+00 3.8768544e+00 4.1133928e+00 4.2906876e+00 4.2860238e+00 5.8694122e+00 6.1139185e+00 3.7920970e+00 4.7644517e+00 3.7255872e+00 5.8215118e+00 3.7549967e+00 4.6162756e+00 4.9325450e+00 3.6290495e+00 3.6674242e+00 4.4922155e+00 4.7085029e+00 5.1584882e+00 5.6338264e+00 4.5354162e+00 3.7973675e+00 4.2166337e+00 5.4055527e+00 4.5672749e+00 4.2532341e+00 3.5623026e+00 4.4317040e+00 4.6722586e+00 4.2790186e+00 3.8858718e+00 4.9040799e+00 4.7947888e+00 4.3023250e+00 3.9242834e+00 4.1060930e+00 4.3289722e+00 3.8118237e+00 7.4161985e-01 4.5825757e-01 6.1644140e-01 7.4161985e-01 3.3166248e-01 3.0000000e-01 3.8729833e-01 6.7823300e-01 7.0710678e-01 4.2426407e-01 5.0990195e-01 6.7823300e-01 7.0000000e-01 6.2449980e-01 5.2915026e-01 7.0000000e-01 1.0295630e+00 3.6055513e-01 3.1622777e-01 1.5394804e+00 9.0553851e-01 3.1622777e-01 4.1231056e-01 7.7459667e-01 2.4494897e-01 7.4161985e-01 2.8284271e-01 4.6904158e-01 3.8858718e+00 3.4856850e+00 4.0459857e+00 3.0298515e+00 3.6864617e+00 3.3136083e+00 3.6441734e+00 2.3086793e+00 3.6482873e+00 2.7874720e+00 2.6944387e+00 3.1032241e+00 3.1096624e+00 3.5888717e+00 2.4718414e+00 3.5114100e+00 3.3090784e+00 2.9342802e+00 3.6972963e+00 2.8178006e+00 3.7067506e+00 2.9782545e+00 3.9560081e+00 3.5623026e+00 3.3136083e+00 3.4856850e+00 3.9484174e+00 4.1218928e+00 3.4146742e+00 2.4351591e+00 2.7622455e+00 2.6551836e+00 2.8089144e+00 4.0261644e+00 3.2848135e+00 3.3674916e+00 3.7907783e+00 3.5524639e+00 2.8827071e+00 2.9427878e+00 3.2280025e+00 3.4785054e+00 2.9308702e+00 2.3600847e+00 3.0577770e+00 2.9631065e+00 3.0166206e+00 3.2403703e+00 2.0445048e+00 2.9563491e+00 5.1244512e+00 4.0865633e+00 5.1710734e+00 4.5661800e+00 4.9173163e+00 5.9699246e+00 3.4885527e+00 5.5208695e+00 4.9446941e+00 5.4763126e+00 4.2107007e+00 4.4022721e+00 4.7191101e+00 4.0755368e+00 4.2731721e+00 4.4710178e+00 4.5177428e+00 6.0868711e+00 6.3827894e+00 4.0644803e+00 4.9739320e+00 3.8961519e+00 6.0967204e+00 3.9949969e+00 4.8218254e+00 5.1836281e+00 3.8561639e+00 3.8742741e+00 4.7116876e+00 4.9829710e+00 5.4323107e+00 5.8668561e+00 4.7486840e+00 4.0521599e+00 4.4743715e+00 5.6586217e+00 4.7265209e+00 4.4732538e+00 3.7616486e+00 4.6583259e+00 4.8713448e+00 4.4911023e+00 4.0865633e+00 5.1097945e+00 4.9769469e+00 4.5110974e+00 4.1689327e+00 4.3243497e+00 4.4855323e+00 4.0062451e+00 9.5916630e-01 9.4339811e-01 9.3808315e-01 7.7459667e-01 7.8740079e-01 7.4833148e-01 7.2801099e-01 8.0622577e-01 9.8488578e-01 9.3273791e-01 1.1532563e+00 7.7459667e-01 6.0000000e-01 9.5393920e-01 7.7459667e-01 7.0000000e-01 7.3484692e-01 5.1961524e-01 1.3416408e+00 5.3851648e-01 8.3066239e-01 1.0677078e+00 7.5498344e-01 8.0622577e-01 5.6568542e-01 8.6602540e-01 6.4031242e-01 4.5880279e+00 4.1641326e+00 4.7370877e+00 3.5651087e+00 4.3474130e+00 3.9127995e+00 4.3162484e+00 2.7313001e+00 4.3197222e+00 3.3196385e+00 3.1000000e+00 3.7389838e+00 3.6823905e+00 4.2272923e+00 3.0757113e+00 4.2023803e+00 3.9115214e+00 3.5355339e+00 4.2965102e+00 3.3808283e+00 4.3416587e+00 3.6193922e+00 4.5825757e+00 4.1928511e+00 3.9786933e+00 4.1665333e+00 4.6216880e+00 4.7979162e+00 4.0484565e+00 3.0166206e+00 3.3015148e+00 3.1906112e+00 3.4146742e+00 4.6411206e+00 3.8652296e+00 4.0261644e+00 4.4766059e+00 4.1653331e+00 3.4899857e+00 3.4971417e+00 3.7907783e+00 4.1243181e+00 3.5270384e+00 2.7892651e+00 3.6414283e+00 3.5791060e+00 3.6262929e+00 3.8923001e+00 2.5039968e+00 3.5594943e+00 5.7680153e+00 4.6850827e+00 5.8506410e+00 5.2057660e+00 5.5686623e+00 6.6580778e+00 3.9749214e+00 6.1991935e+00 5.5874860e+00 6.1692787e+00 4.8805737e+00 5.0428167e+00 5.3907328e+00 4.6540305e+00 4.8713448e+00 5.1283526e+00 5.1749396e+00 6.7926431e+00 7.0590368e+00 4.6486557e+00 5.6524331e+00 4.4821870e+00 6.7808554e+00 4.6335731e+00 5.4954527e+00 5.8719673e+00 4.4944410e+00 4.5144213e+00 5.3525695e+00 5.6674509e+00 6.1139185e+00 6.5825527e+00 5.3888774e+00 4.6936127e+00 5.0842895e+00 6.3553127e+00 5.3786615e+00 5.1283526e+00 4.3954522e+00 5.3394756e+00 5.5371473e+00 5.1730069e+00 4.6850827e+00 5.7810034e+00 5.6462377e+00 5.1788030e+00 4.7947888e+00 4.9849774e+00 5.1351728e+00 4.6281746e+00 4.7958315e-01 4.4721360e-01 2.0000000e-01 4.2426407e-01 4.4721360e-01 5.1961524e-01 4.7958315e-01 3.8729833e-01 9.2195445e-01 1.0723805e+00 5.2915026e-01 6.0000000e-01 6.7082039e-01 5.2915026e-01 9.1104336e-01 3.7416574e-01 5.0000000e-01 1.2489996e+00 8.6602540e-01 2.6457513e-01 5.4772256e-01 5.5677644e-01 5.9160798e-01 6.6332496e-01 5.7445626e-01 4.3588989e-01 3.6646964e+00 3.2465366e+00 3.8105118e+00 2.6627054e+00 3.4088121e+00 3.0149627e+00 3.4132096e+00 1.9131126e+00 3.3852622e+00 2.4535688e+00 2.2781571e+00 2.8248894e+00 2.7495454e+00 3.3120990e+00 2.1587033e+00 3.2710854e+00 3.0298515e+00 2.6191602e+00 3.3555923e+00 2.4677925e+00 3.4568772e+00 2.6795522e+00 3.6496575e+00 3.2771939e+00 3.0413813e+00 3.2310989e+00 3.6823905e+00 3.8704005e+00 3.1320920e+00 2.0832667e+00 2.3958297e+00 2.2847319e+00 2.4859606e+00 3.7336309e+00 3.0033315e+00 3.1416556e+00 3.5496479e+00 3.2202484e+00 2.5961510e+00 2.5942244e+00 2.9034462e+00 3.2109189e+00 2.6000000e+00 1.9544820e+00 2.7386128e+00 2.6814175e+00 2.7221315e+00 2.9614186e+00 1.6401219e+00 2.6476405e+00 4.8918299e+00 3.7907783e+00 4.9284886e+00 4.3011626e+00 4.6636895e+00 5.7367238e+00 3.1559468e+00 5.2773099e+00 4.6583259e+00 5.2782573e+00 3.9724048e+00 4.1194660e+00 4.4698993e+00 3.7603191e+00 3.9887341e+00 4.2308392e+00 4.2638011e+00 5.9076222e+00 6.1261734e+00 3.7296112e+00 4.7423623e+00 3.6041643e+00 5.8532043e+00 3.7054015e+00 4.5956501e+00 4.9598387e+00 3.5721142e+00 3.6083237e+00 4.4395946e+00 4.7455242e+00 5.1826634e+00 5.6947344e+00 4.4766059e+00 3.7749172e+00 4.1844952e+00 5.4267854e+00 4.5022217e+00 4.2261093e+00 3.4928498e+00 4.4192760e+00 4.6281746e+00 4.2520583e+00 3.7907783e+00 4.8764741e+00 4.7497368e+00 4.2591079e+00 3.8639358e+00 4.0681691e+00 4.2602817e+00 3.7389838e+00 5.3851648e-01 4.1231056e-01 5.7445626e-01 6.4031242e-01 3.7416574e-01 4.2426407e-01 7.4833148e-01 9.0553851e-01 1.1747340e+00 5.1961524e-01 7.5498344e-01 9.2736185e-01 5.1961524e-01 8.2462113e-01 5.0000000e-01 6.4807407e-01 1.2922848e+00 7.4833148e-01 5.4772256e-01 5.3851648e-01 6.4807407e-01 5.8309519e-01 5.7445626e-01 7.0710678e-01 5.4772256e-01 3.7629775e+00 3.3241540e+00 3.8974351e+00 2.7055499e+00 3.4971417e+00 3.0232433e+00 3.4727511e+00 1.9000000e+00 3.4626579e+00 2.4677925e+00 2.2803509e+00 2.8896367e+00 2.8160256e+00 3.3496268e+00 2.2338308e+00 3.3749074e+00 3.0413813e+00 2.6400758e+00 3.4423829e+00 2.5019992e+00 3.4957117e+00 2.7694765e+00 3.7080992e+00 3.3000000e+00 3.1272992e+00 3.3301652e+00 3.7696154e+00 3.9534795e+00 3.1843367e+00 2.1563859e+00 2.4310492e+00 2.3173260e+00 2.5475478e+00 3.7589892e+00 2.9949958e+00 3.1874755e+00 3.6373067e+00 3.3045423e+00 2.6172505e+00 2.6305893e+00 2.8948230e+00 3.2526912e+00 2.6551836e+00 1.9621417e+00 2.7622455e+00 2.6944387e+00 2.7495454e+00 3.0298515e+00 1.7088007e+00 2.6870058e+00 4.9355851e+00 3.8236109e+00 5.0059964e+00 4.3301270e+00 4.7180504e+00 5.8051701e+00 3.1352831e+00 5.3310412e+00 4.7106263e+00 5.3600373e+00 4.0509258e+00 4.1833001e+00 4.5530210e+00 3.8039453e+00 4.0546270e+00 4.3092923e+00 4.3092923e+00 5.9674115e+00 6.2016127e+00 3.7656341e+00 4.8270074e+00 3.6386811e+00 5.9203040e+00 3.7815341e+00 4.6551047e+00 5.0169712e+00 3.6455452e+00 3.6619667e+00 4.4966654e+00 4.8052055e+00 5.2583267e+00 5.7671483e+00 4.5398238e+00 3.8131352e+00 4.1785165e+00 5.5335341e+00 4.5585085e+00 4.2626283e+00 3.5454196e+00 4.5122057e+00 4.7148701e+00 4.3760713e+00 3.8236109e+00 4.9446941e+00 4.8321838e+00 4.3669211e+00 3.9446166e+00 4.1448764e+00 4.3150898e+00 3.7643060e+00 4.4721360e-01 5.4772256e-01 4.8989795e-01 3.6055513e-01 2.2360680e-01 6.0827625e-01 1.1269428e+00 1.3152946e+00 2.0000000e-01 4.4721360e-01 7.6811457e-01 2.0000000e-01 6.7082039e-01 4.2426407e-01 5.9160798e-01 9.1651514e-01 7.0000000e-01 6.4031242e-01 8.8317609e-01 3.0000000e-01 8.0622577e-01 4.8989795e-01 7.6811457e-01 3.6055513e-01 3.8845849e+00 3.4785054e+00 4.0249224e+00 2.7766887e+00 3.6027767e+00 3.1859065e+00 3.6537652e+00 1.9748418e+00 3.5749126e+00 2.6191602e+00 2.2912878e+00 3.0430248e+00 2.8354894e+00 3.5028560e+00 2.3622024e+00 3.4899857e+00 3.2341923e+00 2.7604347e+00 3.4899857e+00 2.5903668e+00 3.6945906e+00 2.8670542e+00 3.8105118e+00 3.4438351e+00 3.2357379e+00 3.4409301e+00 3.8678159e+00 4.0865633e+00 3.3331667e+00 2.2135944e+00 2.5019992e+00 2.3790755e+00 2.6495283e+00 3.9115214e+00 3.2031235e+00 3.3955854e+00 3.7682887e+00 3.3511192e+00 2.7964263e+00 2.7331301e+00 3.0413813e+00 3.4132096e+00 2.7495454e+00 2.0049938e+00 2.9017236e+00 2.8722813e+00 2.9103264e+00 3.1543621e+00 1.7406895e+00 2.8266588e+00 5.1410116e+00 3.9837169e+00 5.1487863e+00 4.5011110e+00 4.8877398e+00 5.9472683e+00 3.3045423e+00 5.4726593e+00 4.8311489e+00 5.5443665e+00 4.2166337e+00 4.3162484e+00 4.6968074e+00 3.9420807e+00 4.2154478e+00 4.4833024e+00 4.4743715e+00 6.1595454e+00 6.3206012e+00 3.8587563e+00 4.9869831e+00 3.8118237e+00 6.0481402e+00 3.9025633e+00 4.8373546e+00 5.1768716e+00 3.7788887e+00 3.8288379e+00 4.6486557e+00 4.9436828e+00 5.3795911e+00 5.9439044e+00 4.6904158e+00 3.9585351e+00 4.3370497e+00 5.6524331e+00 4.7634021e+00 4.4429720e+00 3.7148351e+00 4.6551047e+00 4.8723711e+00 4.5033321e+00 3.9837169e+00 5.1166395e+00 5.0079936e+00 4.5011110e+00 4.0484565e+00 4.2953463e+00 4.5221676e+00 3.9522146e+00 3.1622777e-01 3.4641016e-01 4.1231056e-01 4.1231056e-01 4.1231056e-01 7.9372539e-01 9.8488578e-01 4.4721360e-01 4.8989795e-01 6.2449980e-01 4.4721360e-01 8.0622577e-01 2.4494897e-01 3.3166248e-01 1.2489996e+00 7.2801099e-01 2.2360680e-01 5.0990195e-01 5.0000000e-01 4.5825757e-01 5.2915026e-01 4.7958315e-01 3.0000000e-01 3.8275318e+00 3.4088121e+00 3.9749214e+00 2.8337255e+00 3.5805028e+00 3.1733263e+00 3.5707142e+00 2.0639767e+00 3.5524639e+00 2.6115130e+00 2.4351591e+00 2.9899833e+00 2.9257478e+00 3.4741906e+00 2.3280893e+00 3.4380227e+00 3.1843367e+00 2.7820855e+00 3.5355339e+00 2.6362853e+00 3.6124784e+00 2.8530685e+00 3.8209946e+00 3.4380227e+00 3.2109189e+00 3.4000000e+00 3.8522721e+00 4.0373258e+00 3.2969683e+00 2.2583180e+00 2.5651511e+00 2.4535688e+00 2.6570661e+00 3.8961519e+00 3.1527766e+00 3.2939338e+00 3.7148351e+00 3.3985291e+00 2.7531800e+00 2.7622455e+00 3.0610456e+00 3.3719431e+00 2.7712813e+00 2.1118712e+00 2.9017236e+00 2.8372522e+00 2.8827071e+00 3.1288976e+00 1.8083141e+00 2.8124722e+00 5.0467812e+00 3.9534795e+00 5.0941143e+00 4.4609416e+00 4.8259714e+00 5.9000000e+00 3.3045423e+00 5.4396691e+00 4.8270074e+00 5.4350713e+00 4.1352146e+00 4.2883563e+00 4.6368092e+00 3.9268308e+00 4.1533119e+00 4.3931765e+00 4.4249294e+00 6.0580525e+00 6.2952363e+00 3.9000000e+00 4.9061186e+00 3.7643060e+00 6.0183054e+00 3.8768544e+00 4.7539457e+00 5.1185936e+00 3.7416574e+00 3.7709415e+00 4.6054316e+00 4.9071377e+00 5.3497664e+00 5.8455111e+00 4.6432747e+00 3.9382737e+00 4.3416587e+00 5.5955339e+00 4.6572524e+00 4.3840620e+00 3.6551334e+00 4.5858478e+00 4.7937459e+00 4.4226689e+00 3.9534795e+00 5.0378567e+00 4.9112117e+00 4.4294469e+00 4.0385641e+00 4.2343831e+00 4.4147480e+00 3.8961519e+00 1.4142136e-01 5.9160798e-01 5.7445626e-01 3.0000000e-01 6.0827625e-01 7.6811457e-01 5.0990195e-01 4.6904158e-01 3.6055513e-01 5.0990195e-01 9.6436508e-01 1.4142136e-01 3.0000000e-01 1.4071247e+00 8.7749644e-01 4.5825757e-01 5.4772256e-01 6.5574385e-01 3.3166248e-01 6.7823300e-01 2.2360680e-01 3.0000000e-01 3.8742741e+00 3.4957117e+00 4.0373258e+00 2.9983329e+00 3.6715120e+00 3.3090784e+00 3.6674242e+00 2.2759613e+00 3.6249138e+00 2.8000000e+00 2.6324893e+00 3.1176915e+00 3.0364453e+00 3.5846897e+00 2.4779023e+00 3.5014283e+00 3.3316662e+00 2.8982753e+00 3.6578682e+00 2.7802878e+00 3.7456642e+00 2.9597297e+00 3.9319207e+00 3.5411862e+00 3.2939338e+00 3.4727511e+00 3.9217343e+00 4.1231056e+00 3.4190642e+00 2.3874673e+00 2.7202941e+00 2.6038433e+00 2.7856777e+00 4.0249224e+00 3.3136083e+00 3.4073450e+00 3.7868192e+00 3.5028560e+00 2.8948230e+00 2.9240383e+00 3.2109189e+00 3.4799425e+00 2.9017236e+00 2.3151674e+00 3.0495901e+00 2.9647934e+00 3.0182777e+00 3.2264532e+00 2.0174241e+00 2.9512709e+00 5.1759057e+00 4.1048752e+00 5.1797683e+00 4.5760245e+00 4.9426713e+00 5.9690870e+00 3.5128336e+00 5.5108983e+00 4.9295030e+00 5.5190579e+00 4.2402830e+00 4.4056782e+00 4.7349762e+00 4.0914545e+00 4.3185646e+00 4.5144213e+00 4.5276926e+00 6.1139185e+00 6.3741666e+00 4.0336088e+00 5.0029991e+00 3.9306488e+00 6.0844063e+00 3.9962482e+00 4.8518038e+00 5.1865210e+00 3.8652296e+00 3.8961519e+00 4.7275787e+00 4.9699095e+00 5.4203321e+00 5.8847260e+00 4.7686476e+00 4.0435133e+00 4.4575778e+00 5.6630381e+00 4.7822589e+00 4.4899889e+00 3.7868192e+00 4.6765372e+00 4.9050994e+00 4.5188494e+00 4.1048752e+00 5.1400389e+00 5.0219518e+00 4.5387223e+00 4.1653331e+00 4.3439613e+00 4.5420260e+00 4.0323690e+00 5.7445626e-01 5.3851648e-01 3.0000000e-01 7.1414284e-01 8.5440037e-01 4.4721360e-01 3.4641016e-01 3.3166248e-01 4.4721360e-01 9.0000000e-01 1.4142136e-01 2.6457513e-01 1.3114877e+00 8.3066239e-01 5.0000000e-01 6.7823300e-01 5.7445626e-01 4.5825757e-01 6.3245553e-01 3.3166248e-01 2.2360680e-01 3.9509493e+00 3.5749126e+00 4.1133928e+00 3.0446675e+00 3.7389838e+00 3.3808283e+00 3.7509999e+00 2.3108440e+00 3.6959437e+00 2.8600699e+00 2.6551836e+00 3.1906112e+00 3.0789609e+00 3.6592349e+00 2.5416530e+00 3.5749126e+00 3.4088121e+00 2.9631065e+00 3.7067506e+00 2.8337255e+00 3.8275318e+00 3.0232433e+00 3.9949969e+00 3.6138622e+00 3.3630343e+00 3.5440090e+00 3.9899875e+00 4.1976184e+00 3.4914181e+00 2.4372115e+00 2.7676705e+00 2.6495283e+00 2.8460499e+00 4.0963398e+00 3.3911650e+00 3.4942810e+00 3.8626416e+00 3.5538711e+00 2.9698485e+00 2.9782545e+00 3.2756679e+00 3.5566838e+00 2.9597297e+00 2.3452079e+00 3.1144823e+00 3.0413813e+00 3.0903074e+00 3.2969683e+00 2.0469489e+00 3.0182777e+00 5.2602281e+00 4.1749251e+00 5.2564246e+00 4.6540305e+00 5.0209561e+00 6.0473135e+00 3.5721142e+00 5.5883808e+00 4.9979996e+00 5.6053546e+00 4.3197222e+00 4.4754888e+00 4.8104054e+00 4.1545156e+00 4.3874822e+00 4.5934736e+00 4.6065171e+00 6.2048368e+00 6.4459289e+00 4.0902323e+00 5.0823223e+00 4.0012498e+00 6.1595454e+00 4.0632499e+00 4.9355851e+00 5.2687759e+00 3.9344631e+00 3.9724048e+00 4.8010416e+00 5.0477718e+00 5.4936327e+00 5.9741108e+00 4.8414874e+00 4.1170378e+00 4.5310043e+00 5.7367238e+00 4.8672374e+00 4.5716518e+00 3.8626416e+00 4.7528939e+00 4.9819675e+00 4.5912961e+00 4.1749251e+00 5.2211110e+00 5.1029403e+00 4.6108568e+00 4.2272923e+00 4.4192760e+00 4.6270941e+00 4.1109610e+00 1.4142136e-01 7.6157731e-01 1.0392305e+00 1.2961481e+00 2.6457513e-01 5.0000000e-01 9.0553851e-01 2.6457513e-01 4.6904158e-01 4.5825757e-01 5.2915026e-01 9.7467943e-01 4.2426407e-01 5.8309519e-01 8.0622577e-01 3.1622777e-01 7.2111026e-01 2.2360680e-01 7.8740079e-01 3.7416574e-01 4.0422766e+00 3.6041643e+00 4.1749251e+00 2.9017236e+00 3.7536649e+00 3.2832910e+00 3.7603191e+00 2.0518285e+00 3.7296112e+00 2.6888659e+00 2.4041631e+00 3.1511903e+00 3.0149627e+00 3.6193922e+00 2.4718414e+00 3.6455452e+00 3.3090784e+00 2.8896367e+00 3.6537652e+00 2.7202941e+00 3.7735925e+00 3.0149627e+00 3.9534795e+00 3.5679126e+00 3.3882149e+00 3.5958309e+00 4.0311289e+00 4.2249260e+00 3.4467376e+00 2.3685439e+00 2.6324893e+00 2.5159491e+00 2.7838822e+00 4.0187063e+00 3.2603681e+00 3.4785054e+00 3.9127995e+00 3.5242020e+00 2.8827071e+00 2.8460499e+00 3.1368774e+00 3.5270384e+00 2.8861739e+00 2.1047565e+00 3.0049958e+00 2.9664794e+00 3.0099834e+00 3.2924155e+00 1.8493242e+00 2.9359837e+00 5.2172790e+00 4.0743098e+00 5.2820451e+00 4.6054316e+00 4.9919936e+00 6.0876925e+00 3.3451457e+00 5.6124861e+00 4.9689033e+00 5.6524331e+00 4.3278170e+00 4.4407207e+00 4.8238988e+00 4.0360872e+00 4.2965102e+00 4.5814845e+00 4.5880279e+00 6.2745518e+00 6.4699304e+00 3.9924930e+00 5.1048996e+00 3.8858718e+00 6.1975802e+00 4.0323690e+00 4.9426713e+00 5.3075418e+00 3.9000000e+00 3.9306488e+00 4.7602521e+00 5.0882217e+00 5.5308227e+00 6.0728906e+00 4.8010416e+00 4.0816663e+00 4.4452222e+00 5.8051701e+00 4.8414874e+00 4.5464272e+00 3.8118237e+00 4.7853944e+00 4.9849774e+00 4.6378875e+00 4.0743098e+00 5.2258971e+00 5.1097945e+00 4.6270941e+00 4.1833001e+00 4.4136153e+00 4.5978256e+00 4.0360872e+00 7.0710678e-01 1.0862780e+00 1.3190906e+00 1.7320508e-01 4.5825757e-01 8.6023253e-01 1.7320508e-01 5.0990195e-01 4.3588989e-01 5.4772256e-01 9.1104336e-01 5.0990195e-01 6.0000000e-01 8.4261498e-01 2.4494897e-01 7.6157731e-01 3.0000000e-01 7.8740079e-01 3.4641016e-01 3.9874804e+00 3.5594943e+00 4.1218928e+00 2.8460499e+00 3.6972963e+00 3.2434549e+00 3.7229021e+00 2.0074860e+00 3.6728735e+00 2.6551836e+00 2.3452079e+00 3.1096624e+00 2.9410882e+00 3.5749126e+00 2.4269322e+00 3.5902646e+00 3.2787193e+00 2.8372522e+00 3.5874782e+00 2.6645825e+00 3.7443290e+00 2.9580399e+00 3.8974351e+00 3.5199432e+00 3.3316662e+00 3.5397740e+00 3.9711459e+00 4.1749251e+00 3.4029399e+00 2.3043437e+00 2.5748786e+00 2.4556058e+00 2.7294688e+00 3.9761791e+00 3.2357379e+00 3.4496377e+00 3.8613469e+00 3.4554305e+00 2.8478062e+00 2.7964263e+00 3.0951575e+00 3.4842503e+00 2.8301943e+00 2.0518285e+00 2.9614186e+00 2.9291637e+00 2.9698485e+00 3.2403703e+00 1.7944358e+00 2.8913665e+00 5.1903757e+00 4.0373258e+00 5.2345009e+00 4.5661800e+00 4.9537864e+00 6.0382117e+00 3.3211444e+00 5.5623736e+00 4.9162994e+00 5.6169387e+00 4.2883563e+00 4.3931765e+00 4.7780749e+00 3.9962482e+00 4.2638011e+00 4.5464272e+00 4.5464272e+00 6.2377881e+00 6.4156060e+00 3.9370039e+00 5.0635956e+00 3.8548671e+00 6.1441029e+00 3.9824616e+00 4.9061186e+00 5.2621288e+00 3.8535698e+00 3.8923001e+00 4.7180504e+00 5.0368641e+00 5.4763126e+00 6.0315835e+00 4.7592016e+00 4.0348482e+00 4.4022721e+00 5.7515215e+00 4.8145612e+00 4.5088801e+00 3.7749172e+00 4.7391982e+00 4.9446941e+00 4.5902070e+00 4.0373258e+00 5.1874849e+00 5.0744458e+00 4.5814845e+00 4.1303753e+00 4.3703547e+00 4.5716518e+00 4.0037482e+00 7.8740079e-01 8.3666003e-01 6.5574385e-01 5.7445626e-01 3.1622777e-01 6.5574385e-01 1.1135529e+00 3.6055513e-01 4.6904158e-01 1.4387495e+00 1.0583005e+00 4.6904158e-01 6.4031242e-01 7.3484692e-01 5.4772256e-01 8.5440037e-01 3.7416574e-01 4.6904158e-01 3.7202150e+00 3.3541020e+00 3.8871583e+00 2.8774989e+00 3.5199432e+00 3.2031235e+00 3.5355339e+00 2.2022716e+00 3.4799425e+00 2.7000000e+00 2.5455844e+00 2.9849623e+00 2.9000000e+00 3.4612137e+00 2.3473389e+00 3.3451457e+00 3.2264532e+00 2.7874720e+00 3.5057096e+00 2.6645825e+00 3.6249138e+00 2.8124722e+00 3.7934153e+00 3.4249088e+00 3.1464265e+00 3.3181320e+00 3.7696154e+00 3.9736633e+00 3.2893768e+00 2.2561028e+00 2.6057628e+00 2.4919872e+00 2.6551836e+00 3.9051248e+00 3.2202484e+00 3.2863353e+00 3.6373067e+00 3.3526109e+00 2.7874720e+00 2.8071338e+00 3.1144823e+00 3.3555923e+00 2.7730849e+00 2.2293497e+00 2.9376862e+00 2.8600699e+00 2.9051678e+00 3.0886890e+00 1.9078784e+00 2.8319605e+00 5.0477718e+00 3.9824616e+00 5.0299105e+00 4.4530888e+00 4.8062459e+00 5.8223707e+00 3.4278273e+00 5.3721504e+00 4.7906158e+00 5.3712196e+00 4.0951190e+00 4.2638011e+00 4.5836667e+00 3.9635842e+00 4.1809090e+00 4.3692105e+00 4.3965896e+00 5.9774577e+00 6.2209324e+00 3.9064050e+00 4.8518038e+00 3.8105118e+00 5.9371710e+00 3.8496753e+00 4.7148701e+00 5.0487622e+00 3.7215588e+00 3.7643060e+00 4.5891176e+00 4.8301139e+00 5.2697249e+00 5.7428216e+00 4.6270941e+00 3.9166312e+00 4.3520110e+00 5.4972721e+00 4.6497312e+00 4.3646306e+00 3.6565011e+00 4.5210618e+00 4.7528939e+00 4.3485630e+00 3.9824616e+00 4.9969991e+00 4.8733972e+00 4.3760713e+00 4.0149720e+00 4.1976184e+00 4.4113490e+00 3.9153544e+00 3.4641016e-01 1.0440307e+00 9.7467943e-01 7.0710678e-01 1.0440307e+00 1.3784049e+00 7.1414284e-01 6.9282032e-01 1.9519221e+00 1.2247449e+00 8.1240384e-01 5.9160798e-01 1.1916375e+00 3.4641016e-01 1.0908712e+00 4.2426407e-01 8.3666003e-01 3.9974992e+00 3.6345564e+00 4.1725292e+00 3.3196385e+00 3.8665230e+00 3.5185224e+00 3.7868192e+00 2.6514147e+00 3.8013156e+00 3.0675723e+00 3.0430248e+00 3.3090784e+00 3.3630343e+00 3.7656341e+00 2.7294688e+00 3.6537652e+00 3.5114100e+00 3.1448370e+00 3.9458839e+00 3.0789609e+00 3.8832976e+00 3.1921779e+00 4.1581246e+00 3.7349699e+00 3.4871192e+00 3.6428011e+00 4.1024383e+00 4.2743421e+00 3.6110940e+00 2.7037012e+00 3.0446675e+00 2.9376862e+00 3.0479501e+00 4.2201896e+00 3.4942810e+00 3.5185224e+00 3.9306488e+00 3.7815341e+00 3.0935417e+00 3.2155870e+00 3.4583233e+00 3.6496575e+00 3.1733263e+00 2.7073973e+00 3.2939338e+00 3.1559468e+00 3.2280025e+00 3.4234486e+00 2.4124676e+00 3.1843367e+00 5.2782573e+00 4.3034870e+00 5.3084838e+00 4.7275787e+00 5.0793700e+00 6.0811183e+00 3.7696154e+00 5.6373753e+00 5.1176166e+00 5.5830099e+00 4.3669211e+00 4.5912961e+00 4.8754487e+00 4.3208795e+00 4.5055521e+00 4.6400431e+00 4.6679760e+00 6.1473572e+00 6.5192024e+00 4.2965102e+00 5.1166395e+00 4.1255303e+00 6.2120850e+00 4.1976184e+00 4.9527770e+00 5.2867760e+00 4.0583248e+00 4.0583248e+00 4.8928519e+00 5.0941143e+00 5.5614746e+00 5.9160798e+00 4.9345719e+00 4.2213742e+00 4.6432747e+00 5.7844619e+00 4.8785244e+00 4.6184413e+00 3.9534795e+00 4.8062459e+00 5.0348784e+00 4.6572524e+00 4.3034870e+00 5.2507142e+00 5.1273775e+00 4.6893496e+00 4.3886217e+00 4.4944410e+00 4.6411206e+00 4.1892720e+00 1.2609520e+00 1.1357817e+00 7.0710678e-01 1.2609520e+00 1.6309506e+00 9.0000000e-01 8.7177979e-01 2.1517435e+00 1.4899664e+00 9.6953597e-01 7.8102497e-01 1.3928388e+00 6.0000000e-01 1.3453624e+00 5.4772256e-01 1.0295630e+00 3.9471509e+00 3.6207734e+00 4.1364236e+00 3.4029399e+00 3.8587563e+00 3.5805028e+00 3.7815341e+00 2.8017851e+00 3.7881394e+00 3.1670175e+00 3.1843367e+00 3.3361655e+00 3.4132096e+00 3.7920970e+00 2.7838822e+00 3.6180105e+00 3.5707142e+00 3.2046841e+00 3.9736633e+00 3.1559468e+00 3.9089641e+00 3.2078030e+00 4.1797129e+00 3.7696154e+00 3.4813790e+00 3.6180105e+00 4.0804412e+00 4.2532341e+00 3.6386811e+00 2.7658633e+00 3.1320920e+00 3.0282008e+00 3.0967725e+00 4.2602817e+00 3.5707142e+00 3.5298725e+00 3.9025633e+00 3.8026307e+00 3.1543621e+00 3.2954514e+00 3.5440090e+00 3.6715120e+00 3.2264532e+00 2.8478062e+00 3.3630343e+00 3.2124757e+00 3.2832910e+00 3.4351128e+00 2.5337719e+00 3.2403703e+00 5.2820451e+00 4.3497126e+00 5.2782573e+00 4.7465777e+00 5.0793700e+00 6.0415230e+00 3.8871583e+00 5.6124861e+00 5.1234754e+00 5.5344376e+00 4.3508620e+00 4.6000000e+00 4.8528342e+00 4.3737855e+00 4.5365185e+00 4.6292548e+00 4.6701178e+00 6.0901560e+00 6.4853681e+00 4.3474130e+00 5.0852729e+00 4.1785165e+00 6.1749494e+00 4.2071368e+00 4.9345719e+00 5.2545219e+00 4.0706265e+00 4.0755368e+00 4.9010203e+00 5.0645829e+00 5.5272054e+00 5.8446557e+00 4.9406477e+00 4.2402830e+00 4.6904158e+00 5.7253821e+00 4.8744230e+00 4.6249324e+00 3.9761791e+00 4.7728398e+00 5.0129831e+00 4.6119410e+00 4.3497126e+00 5.2297227e+00 5.1019604e+00 4.6615448e+00 4.4022721e+00 4.4855323e+00 4.6411206e+00 4.2249260e+00 3.4641016e-01 7.5498344e-01 0.0000000e+00 5.5677644e-01 3.7416574e-01 5.0000000e-01 9.3808315e-01 5.5677644e-01 6.5574385e-01 8.8317609e-01 2.6457513e-01 7.4161985e-01 3.4641016e-01 7.2801099e-01 2.6457513e-01 4.0435133e+00 3.6359318e+00 4.1856899e+00 2.9478806e+00 3.7709415e+00 3.3421550e+00 3.8065733e+00 2.1307276e+00 3.7389838e+00 2.7748874e+00 2.4556058e+00 3.2031235e+00 3.0133038e+00 3.6619667e+00 2.5258662e+00 3.6523965e+00 3.3852622e+00 2.9223278e+00 3.6687873e+00 2.7586228e+00 3.8457769e+00 3.0364453e+00 3.9799497e+00 3.6027767e+00 3.4014703e+00 3.6055513e+00 4.0348482e+00 4.2497059e+00 3.4942810e+00 2.3874673e+00 2.6720778e+00 2.5495098e+00 2.8178006e+00 4.0718546e+00 3.3496268e+00 3.5425979e+00 3.9293765e+00 3.5284558e+00 2.9495762e+00 2.9000000e+00 3.1984371e+00 3.5707142e+00 2.9189039e+00 2.1679483e+00 3.0626786e+00 3.0248967e+00 3.0675723e+00 3.3181320e+00 1.9104973e+00 2.9883106e+00 5.2924474e+00 4.1436699e+00 5.3113087e+00 4.6583259e+00 5.0467812e+00 6.1081912e+00 3.4525353e+00 5.6329388e+00 4.9979996e+00 5.6973678e+00 4.3749286e+00 4.4821870e+00 4.8600412e+00 4.1060930e+00 4.3760713e+00 4.6411206e+00 4.6324939e+00 6.3071388e+00 6.4876806e+00 4.0286474e+00 5.1468437e+00 3.9686270e+00 6.2112801e+00 4.0706265e+00 4.9919936e+00 5.3329167e+00 3.9446166e+00 3.9874804e+00 4.8114447e+00 5.1029403e+00 5.5443665e+00 6.0917978e+00 4.8538644e+00 4.1194660e+00 4.4933284e+00 5.8180753e+00 4.9142650e+00 4.5978256e+00 3.8729833e+00 4.8176758e+00 5.0338852e+00 4.6690470e+00 4.1436699e+00 5.2744668e+00 5.1652686e+00 4.6669048e+00 4.2201896e+00 4.4575778e+00 4.6722586e+00 4.1060930e+00 5.9160798e-01 3.4641016e-01 6.4031242e-01 3.7416574e-01 3.3166248e-01 1.0392305e+00 6.0827625e-01 6.4031242e-01 9.4868330e-01 3.6055513e-01 7.2801099e-01 4.4721360e-01 6.5574385e-01 2.2360680e-01 4.2059482e+00 3.8131352e+00 4.3588989e+00 3.1796226e+00 3.9572718e+00 3.5707142e+00 3.9887341e+00 2.3874673e+00 3.9268308e+00 3.0033315e+00 2.7147744e+00 3.3970576e+00 3.2372828e+00 3.8716921e+00 2.7239677e+00 3.8183766e+00 3.6027767e+00 3.1527766e+00 3.8755645e+00 2.9916551e+00 4.0410395e+00 3.2280025e+00 4.1904654e+00 3.8236109e+00 3.5874782e+00 3.7788887e+00 4.2190046e+00 4.4294469e+00 3.6972963e+00 2.6038433e+00 2.9086079e+00 2.7892651e+00 3.0298515e+00 4.2918527e+00 3.5749126e+00 3.7269290e+00 4.1036569e+00 3.7349699e+00 3.1654384e+00 3.1288976e+00 3.4423829e+00 3.7749172e+00 3.1368774e+00 2.4207437e+00 3.2893768e+00 3.2449961e+00 3.2848135e+00 3.5142567e+00 2.1330729e+00 3.2046841e+00 5.4799635e+00 4.3577517e+00 5.4909016e+00 4.8682646e+00 5.2392748e+00 6.2904690e+00 3.6932371e+00 5.8266629e+00 5.2057660e+00 5.8566202e+00 4.5497253e+00 4.6808119e+00 5.0378567e+00 4.3197222e+00 4.5661800e+00 4.8145612e+00 4.8311489e+00 6.4730209e+00 6.6745786e+00 4.2579338e+00 5.3169540e+00 4.1773197e+00 6.3984373e+00 4.2649736e+00 5.1730069e+00 5.5172457e+00 4.1376322e+00 4.1833001e+00 5.0089919e+00 5.2915026e+00 5.7288742e+00 6.2489999e+00 5.0477718e+00 4.3301270e+00 4.7296934e+00 5.9791304e+00 5.0921508e+00 4.7979162e+00 4.0693980e+00 4.9869831e+00 5.2057660e+00 4.8207883e+00 4.3577517e+00 5.4534393e+00 5.3329167e+00 4.8311489e+00 4.4170126e+00 4.6400431e+00 4.8507731e+00 4.3150898e+00 7.5498344e-01 1.2083046e+00 4.5825757e-01 5.0990195e-01 1.5652476e+00 1.1401754e+00 7.0710678e-01 8.0622577e-01 8.7177979e-01 5.8309519e-01 9.5393920e-01 3.4641016e-01 5.4772256e-01 3.9166312e+00 3.5818989e+00 4.0951190e+00 3.1527766e+00 3.7509999e+00 3.4612137e+00 3.7682887e+00 2.4919872e+00 3.6972963e+00 2.9883106e+00 2.8248894e+00 3.2419130e+00 3.1416556e+00 3.7040518e+00 2.6210685e+00 3.5566838e+00 3.4914181e+00 3.0347982e+00 3.7563280e+00 2.9291637e+00 3.8807216e+00 3.0577770e+00 4.0360872e+00 3.6619667e+00 3.3734256e+00 3.5369478e+00 3.9837169e+00 4.1988094e+00 3.5411862e+00 2.5159491e+00 2.8757608e+00 2.7586228e+00 2.9137605e+00 4.1581246e+00 3.4914181e+00 3.5298725e+00 3.8535698e+00 3.5916570e+00 3.0512293e+00 3.0822070e+00 3.3793490e+00 3.5972211e+00 3.0315013e+00 2.5159491e+00 3.2046841e+00 3.1144823e+00 3.1654384e+00 3.3256578e+00 2.2045408e+00 3.0951575e+00 5.2971691e+00 4.2497059e+00 5.2516664e+00 4.6957428e+00 5.0497525e+00 6.0299254e+00 3.7215588e+00 5.5821143e+00 5.0249378e+00 5.5883808e+00 4.3324358e+00 4.5099889e+00 4.8155997e+00 4.2391037e+00 4.4564560e+00 4.6162756e+00 4.6314145e+00 6.1717096e+00 6.4358372e+00 4.1617304e+00 5.0813384e+00 4.0865633e+00 6.1424751e+00 4.0987803e+00 4.9446941e+00 5.2564246e+00 3.9736633e+00 4.0162171e+00 4.8373546e+00 5.0348784e+00 5.4799635e+00 5.9245253e+00 4.8774994e+00 4.1545156e+00 4.5934736e+00 5.7043843e+00 4.8969378e+00 4.6010868e+00 3.9127995e+00 4.7476310e+00 4.9929951e+00 4.5793013e+00 4.2497059e+00 5.2297227e+00 5.1117512e+00 4.6162756e+00 4.2684892e+00 4.4384682e+00 4.6604721e+00 4.1725292e+00 5.5677644e-01 3.7416574e-01 5.0000000e-01 9.3808315e-01 5.5677644e-01 6.5574385e-01 8.8317609e-01 2.6457513e-01 7.4161985e-01 3.4641016e-01 7.2801099e-01 2.6457513e-01 4.0435133e+00 3.6359318e+00 4.1856899e+00 2.9478806e+00 3.7709415e+00 3.3421550e+00 3.8065733e+00 2.1307276e+00 3.7389838e+00 2.7748874e+00 2.4556058e+00 3.2031235e+00 3.0133038e+00 3.6619667e+00 2.5258662e+00 3.6523965e+00 3.3852622e+00 2.9223278e+00 3.6687873e+00 2.7586228e+00 3.8457769e+00 3.0364453e+00 3.9799497e+00 3.6027767e+00 3.4014703e+00 3.6055513e+00 4.0348482e+00 4.2497059e+00 3.4942810e+00 2.3874673e+00 2.6720778e+00 2.5495098e+00 2.8178006e+00 4.0718546e+00 3.3496268e+00 3.5425979e+00 3.9293765e+00 3.5284558e+00 2.9495762e+00 2.9000000e+00 3.1984371e+00 3.5707142e+00 2.9189039e+00 2.1679483e+00 3.0626786e+00 3.0248967e+00 3.0675723e+00 3.3181320e+00 1.9104973e+00 2.9883106e+00 5.2924474e+00 4.1436699e+00 5.3113087e+00 4.6583259e+00 5.0467812e+00 6.1081912e+00 3.4525353e+00 5.6329388e+00 4.9979996e+00 5.6973678e+00 4.3749286e+00 4.4821870e+00 4.8600412e+00 4.1060930e+00 4.3760713e+00 4.6411206e+00 4.6324939e+00 6.3071388e+00 6.4876806e+00 4.0286474e+00 5.1468437e+00 3.9686270e+00 6.2112801e+00 4.0706265e+00 4.9919936e+00 5.3329167e+00 3.9446166e+00 3.9874804e+00 4.8114447e+00 5.1029403e+00 5.5443665e+00 6.0917978e+00 4.8538644e+00 4.1194660e+00 4.4933284e+00 5.8180753e+00 4.9142650e+00 4.5978256e+00 3.8729833e+00 4.8176758e+00 5.0338852e+00 4.6690470e+00 4.1436699e+00 5.2744668e+00 5.1652686e+00 4.6669048e+00 4.2201896e+00 4.4575778e+00 4.6722586e+00 4.1060930e+00 8.3066239e-01 7.8740079e-01 7.1414284e-01 2.0000000e-01 9.2736185e-01 1.2369317e+00 4.2426407e-01 1.1045361e+00 3.0000000e-01 1.1575837e+00 6.7823300e-01 4.4497191e+00 3.9962482e+00 4.5727453e+00 3.1937439e+00 4.1267421e+00 3.6304270e+00 4.1496988e+00 2.2912878e+00 4.1170378e+00 2.9883106e+00 2.6153394e+00 3.5142567e+00 3.3361655e+00 3.9874804e+00 2.8195744e+00 4.0435133e+00 3.6565011e+00 3.2449961e+00 3.9761791e+00 3.0430248e+00 4.1352146e+00 3.3808283e+00 4.3023250e+00 3.9357337e+00 3.7709415e+00 3.9862263e+00 4.4147480e+00 4.6076024e+00 3.8078866e+00 2.7073973e+00 2.9376862e+00 2.8231188e+00 3.1320920e+00 4.3646306e+00 3.5958309e+00 3.8626416e+00 4.3069711e+00 3.8626416e+00 3.2388269e+00 3.1559468e+00 3.4612137e+00 3.9012818e+00 3.2264532e+00 2.3430749e+00 3.3391616e+00 3.3316662e+00 3.3645208e+00 3.6687873e+00 2.1071308e+00 3.2832910e+00 5.5749439e+00 4.4022721e+00 5.6621551e+00 4.9668904e+00 5.3535035e+00 6.4761099e+00 3.6041643e+00 5.9983331e+00 5.3244718e+00 6.0440053e+00 4.7042534e+00 4.7937459e+00 5.1971146e+00 4.3439613e+00 4.6130250e+00 4.9446941e+00 4.9608467e+00 6.6850580e+00 6.8425142e+00 4.3104524e+00 5.4827001e+00 4.2047592e+00 6.5825527e+00 4.3840620e+00 5.3244718e+00 5.7035077e+00 4.2532341e+00 4.2906876e+00 5.1127292e+00 5.4817880e+00 5.9135438e+00 6.4915329e+00 5.1507281e+00 4.4474712e+00 4.7937459e+00 6.1919302e+00 5.2057660e+00 4.9203658e+00 4.1677332e+00 5.1652686e+00 5.3507009e+00 5.0109879e+00 4.4022721e+00 5.6008928e+00 5.4799635e+00 4.9909919e+00 4.5210618e+00 4.7812132e+00 4.9618545e+00 4.3874822e+00 2.6457513e-01 1.2727922e+00 7.5498344e-01 4.3588989e-01 6.0000000e-01 5.1961524e-01 4.1231056e-01 5.4772256e-01 3.6055513e-01 1.7320508e-01 3.9153544e+00 3.5242020e+00 4.0718546e+00 2.9715316e+00 3.6905284e+00 3.3060551e+00 3.6945906e+00 2.2181073e+00 3.6496575e+00 2.7748874e+00 2.5709920e+00 3.1272992e+00 3.0232433e+00 3.5958309e+00 2.4738634e+00 3.5355339e+00 3.3316662e+00 2.8948230e+00 3.6523965e+00 2.7622455e+00 3.7589892e+00 2.9698485e+00 3.9370039e+00 3.5496479e+00 3.3151169e+00 3.5014283e+00 3.9471509e+00 4.1496988e+00 3.4278273e+00 2.3748684e+00 2.6944387e+00 2.5768197e+00 2.7820855e+00 4.0274061e+00 3.3075671e+00 3.4307434e+00 3.8183766e+00 3.5028560e+00 2.8948230e+00 2.9034462e+00 3.1953091e+00 3.4942810e+00 2.8948230e+00 2.2583180e+00 3.0397368e+00 2.9681644e+00 3.0182777e+00 3.2419130e+00 1.9672316e+00 2.9478806e+00 5.1951901e+00 4.1024383e+00 5.2086467e+00 4.5891176e+00 4.9608467e+00 6.0024995e+00 3.4785054e+00 5.5398556e+00 4.9416596e+00 5.5587768e+00 4.2661458e+00 4.4170126e+00 4.7602521e+00 4.0816663e+00 4.3185646e+00 4.5365185e+00 4.5475268e+00 6.1611687e+00 6.4007812e+00 4.0236799e+00 5.0328918e+00 3.9255573e+00 6.1155539e+00 4.0062451e+00 4.8805737e+00 5.2211110e+00 3.8755645e+00 3.9089641e+00 4.7402532e+00 5.0019996e+00 5.4497706e+00 5.9371710e+00 4.7812132e+00 4.0558600e+00 4.4598206e+00 5.7000000e+00 4.8052055e+00 4.5099889e+00 3.7973675e+00 4.7063787e+00 4.9295030e+00 4.5497253e+00 4.1024383e+00 5.1672043e+00 5.0497525e+00 4.5628938e+00 4.1701319e+00 4.3646306e+00 4.5639895e+00 4.0398020e+00 1.3000000e+00 6.7823300e-01 4.2426407e-01 6.8556546e-01 5.4772256e-01 4.4721360e-01 5.1961524e-01 4.2426407e-01 2.4494897e-01 4.1060930e+00 3.7054015e+00 4.2626283e+00 3.1591138e+00 3.8820098e+00 3.4957117e+00 3.8704005e+00 2.3895606e+00 3.8483763e+00 2.9410882e+00 2.7531800e+00 3.3030289e+00 3.2357379e+00 3.7868192e+00 2.6476405e+00 3.7242449e+00 3.5057096e+00 3.1000000e+00 3.8483763e+00 2.9597297e+00 3.9242834e+00 3.1606961e+00 4.1340053e+00 3.7509999e+00 3.5099858e+00 3.6918830e+00 4.1460825e+00 4.3347434e+00 3.6110940e+00 2.5748786e+00 2.8896367e+00 2.7766887e+00 2.9748950e+00 4.2154478e+00 3.4770677e+00 3.5972211e+00 4.0062451e+00 3.7067506e+00 3.0740852e+00 3.0886890e+00 3.3882149e+00 3.6823905e+00 3.0903074e+00 2.4351591e+00 3.2264532e+00 3.1559468e+00 3.2031235e+00 3.4351128e+00 2.1307276e+00 3.1336879e+00 5.3535035e+00 4.2755117e+00 5.3907328e+00 4.7738873e+00 5.1341991e+00 6.1919302e+00 3.6345564e+00 5.7358522e+00 5.1371198e+00 5.7210139e+00 4.4350874e+00 4.6000000e+00 4.9365980e+00 4.2508823e+00 4.4698993e+00 4.6957428e+00 4.7318073e+00 6.3364028e+00 6.5924199e+00 4.2213742e+00 5.2019227e+00 4.0865633e+00 6.3111013e+00 4.1880783e+00 5.0527220e+00 5.4101756e+00 4.0533936e+00 4.0828911e+00 4.9173163e+00 5.1990384e+00 5.6435804e+00 6.1155539e+00 4.9547957e+00 4.2497059e+00 4.6604721e+00 5.8804762e+00 4.9598387e+00 4.6914816e+00 3.9686270e+00 4.8805737e+00 5.0941143e+00 4.7127487e+00 4.2755117e+00 5.3376025e+00 5.2086467e+00 4.7275787e+00 4.3520110e+00 4.5387223e+00 4.7180504e+00 4.2130749e+00 9.1104336e-01 1.3674794e+00 1.7262677e+00 7.6811457e-01 1.6462078e+00 9.1651514e-01 1.6278821e+00 1.1269428e+00 4.4530888e+00 4.0124805e+00 4.5607017e+00 3.0479501e+00 4.0718546e+00 3.5958309e+00 4.1821047e+00 2.1587033e+00 4.0816663e+00 2.9359837e+00 2.3811762e+00 3.5071356e+00 3.1685959e+00 3.9610605e+00 2.8035692e+00 4.0373258e+00 3.6578682e+00 3.1906112e+00 3.8183766e+00 2.9410882e+00 4.1557190e+00 3.3316662e+00 4.2047592e+00 3.8961519e+00 3.7376463e+00 3.9648455e+00 4.3588989e+00 4.5803930e+00 3.7802116e+00 2.6191602e+00 2.8106939e+00 2.6944387e+00 3.0692019e+00 4.3058100e+00 3.6027767e+00 3.9230090e+00 4.2988371e+00 3.7215588e+00 3.2465366e+00 3.0545049e+00 3.3926391e+00 3.8923001e+00 3.1432467e+00 2.1771541e+00 3.2832910e+00 3.3391616e+00 3.3481338e+00 3.6400549e+00 1.9824228e+00 3.2449961e+00 5.5830099e+00 4.3416587e+00 5.6258333e+00 4.9335586e+00 5.3244718e+00 6.4366140e+00 3.5213634e+00 5.9539903e+00 5.2325902e+00 6.0712437e+00 4.7053161e+00 4.7254629e+00 5.1633323e+00 4.2497059e+00 4.5596052e+00 4.9416596e+00 4.9376108e+00 6.7275553e+00 6.7594378e+00 4.1701319e+00 5.4708317e+00 4.1605288e+00 6.5222695e+00 4.3139309e+00 5.3329167e+00 5.6956123e+00 4.2000000e+00 4.2731721e+00 5.0586559e+00 5.4516053e+00 5.8532043e+00 6.5352888e+00 5.0950957e+00 4.4011362e+00 4.7275787e+00 6.1457302e+00 5.2297227e+00 4.9132474e+00 4.1521079e+00 5.1429563e+00 5.3272882e+00 4.9839743e+00 4.3416587e+00 5.5910643e+00 5.4808758e+00 4.9537864e+00 4.4192760e+00 4.7528939e+00 4.9909919e+00 4.3749286e+00 8.3666003e-01 1.1180340e+00 4.6904158e-01 9.6953597e-01 2.2360680e-01 1.0488088e+00 6.1644140e-01 4.4452222e+00 3.9912404e+00 4.5727453e+00 3.2434549e+00 4.1412558e+00 3.6469165e+00 4.1400483e+00 2.3515952e+00 4.1267421e+00 3.0149627e+00 2.6981475e+00 3.5199432e+00 3.3896903e+00 3.9974992e+00 2.8337255e+00 4.0435133e+00 3.6619667e+00 3.2695565e+00 4.0211939e+00 3.0822070e+00 4.1303753e+00 3.3985291e+00 4.3301270e+00 3.9509493e+00 3.7815341e+00 3.9912404e+00 4.4283180e+00 4.6119410e+00 3.8183766e+00 2.7440845e+00 2.9849623e+00 2.8722813e+00 3.1575307e+00 4.3829214e+00 3.6013886e+00 3.8470768e+00 4.3069711e+00 3.9038443e+00 3.2449961e+00 3.1937439e+00 3.4899857e+00 3.9064050e+00 3.2572995e+00 2.4103942e+00 3.3630343e+00 3.3376639e+00 3.3763886e+00 3.6796739e+00 2.1633308e+00 3.3015148e+00 5.5677644e+00 4.4204072e+00 5.6656862e+00 4.9749372e+00 5.3572381e+00 6.4791975e+00 3.6373067e+00 6.0049979e+00 5.3469618e+00 6.0274373e+00 4.7000000e+00 4.8104054e+00 5.2009614e+00 4.3714986e+00 4.6260134e+00 4.9406477e+00 4.9648766e+00 6.6640828e+00 6.8571131e+00 4.3520110e+00 5.4790510e+00 4.2190046e+00 6.5916614e+00 4.4022721e+00 5.3169540e+00 5.7000000e+00 4.2673177e+00 4.2953463e+00 5.1244512e+00 5.4854353e+00 5.9236813e+00 6.4699304e+00 5.1623638e+00 4.4609416e+00 4.8145612e+00 6.1951594e+00 5.1942276e+00 4.9203658e+00 4.1725292e+00 5.1652686e+00 5.3507009e+00 5.0109879e+00 4.4204072e+00 5.5973208e+00 5.4726593e+00 4.9949975e+00 4.5475268e+00 4.7853944e+00 4.9497475e+00 4.3920383e+00 4.7958315e-01 6.4807407e-01 5.0990195e-01 6.7082039e-01 5.4772256e-01 4.8989795e-01 3.7868192e+00 3.3570821e+00 3.9331921e+00 2.8178006e+00 3.5425979e+00 3.1432467e+00 3.5128336e+00 2.0663978e+00 3.5227830e+00 2.5709920e+00 2.4535688e+00 2.9376862e+00 2.9342802e+00 3.4380227e+00 2.2825424e+00 3.3955854e+00 3.1352831e+00 2.7730849e+00 3.5142567e+00 2.6267851e+00 3.5468296e+00 2.8195744e+00 3.7934153e+00 3.4161382e+00 3.1780497e+00 3.3600595e+00 3.8223030e+00 3.9887341e+00 3.2526912e+00 2.2516660e+00 2.5592968e+00 2.4556058e+00 2.6324893e+00 3.8587563e+00 3.1032241e+00 3.2280025e+00 3.6701499e+00 3.3852622e+00 2.7110883e+00 2.7386128e+00 3.0430248e+00 3.3316662e+00 2.7513633e+00 2.1189620e+00 2.8722813e+00 2.8035692e+00 2.8460499e+00 3.0951575e+00 1.7944358e+00 2.7784888e+00 4.9699095e+00 3.9012818e+00 5.0398413e+00 4.4147480e+00 4.7644517e+00 5.8532043e+00 3.2603681e+00 5.4018515e+00 4.7927028e+00 5.3581713e+00 4.0681691e+00 4.2402830e+00 4.5771170e+00 3.8742741e+00 4.0767634e+00 4.3162484e+00 4.3760713e+00 5.9958319e+00 6.2513998e+00 3.8807216e+00 4.8373546e+00 3.7013511e+00 5.9791304e+00 3.8288379e+00 4.6893496e+00 5.0724747e+00 3.6891733e+00 3.7134889e+00 4.5497253e+00 4.8713448e+00 5.3094256e+00 5.7879185e+00 4.5836667e+00 3.9038443e+00 4.3197222e+00 5.5389530e+00 4.5760245e+00 4.3324358e+00 3.5958309e+00 4.5232732e+00 4.7212287e+00 4.3485630e+00 3.9012818e+00 4.9709154e+00 4.8321838e+00 4.3577517e+00 3.9924930e+00 4.1737274e+00 4.3335897e+00 3.8405729e+00 9.9498744e-01 3.6055513e-01 9.4868330e-01 5.0000000e-01 7.4161985e-01 3.5791060e+00 3.1654384e+00 3.7336309e+00 2.7622455e+00 3.3852622e+00 2.9883106e+00 3.3120990e+00 2.0784610e+00 3.3406586e+00 2.4939928e+00 2.4839485e+00 2.7892651e+00 2.8530685e+00 3.2634338e+00 2.1817424e+00 3.2093613e+00 2.9765752e+00 2.6267851e+00 3.4263683e+00 2.5357445e+00 3.3719431e+00 2.6870058e+00 3.6523965e+00 3.2372828e+00 3.0116441e+00 3.1843367e+00 3.6469165e+00 3.8078866e+00 3.0967725e+00 2.1725561e+00 2.4939928e+00 2.3916521e+00 2.5179357e+00 3.7013511e+00 2.9495762e+00 3.0282008e+00 3.4785054e+00 3.2787193e+00 2.5573424e+00 2.6589472e+00 2.9137605e+00 3.1511903e+00 2.6419690e+00 2.1400935e+00 2.7495454e+00 2.6324893e+00 2.6962938e+00 2.9308702e+00 1.8411953e+00 2.6476405e+00 4.7864392e+00 3.7669616e+00 4.8507731e+00 4.2308392e+00 4.5880279e+00 5.6453521e+00 3.1906112e+00 5.1932649e+00 4.6281746e+00 5.1478151e+00 3.8884444e+00 4.0877867e+00 4.4022721e+00 3.7709415e+00 3.9661064e+00 4.1496988e+00 4.1856899e+00 5.7480431e+00 6.0671245e+00 3.7669616e+00 4.6529560e+00 3.5791060e+00 5.7758116e+00 3.6891733e+00 4.4877611e+00 4.8518038e+00 3.5468296e+00 3.5496479e+00 4.3897608e+00 4.6583259e+00 5.1166395e+00 5.5362442e+00 4.4294469e+00 3.7269290e+00 4.1388404e+00 5.3525695e+00 4.3920383e+00 4.1352146e+00 3.4380227e+00 4.3439613e+00 4.5541190e+00 4.1928511e+00 3.7669616e+00 4.7812132e+00 4.6540305e+00 4.2071368e+00 3.8716921e+00 4.0062451e+00 4.1509035e+00 3.6715120e+00 8.8317609e-01 3.0000000e-01 8.7177979e-01 3.7416574e-01 4.1206796e+00 3.6945906e+00 4.2555846e+00 2.9563491e+00 3.8223030e+00 3.3852622e+00 3.8626416e+00 2.1142375e+00 3.8065733e+00 2.7766887e+00 2.4372115e+00 3.2388269e+00 3.0545049e+00 3.7148351e+00 2.5475478e+00 3.7188708e+00 3.4190642e+00 2.9782545e+00 3.6945906e+00 2.7892651e+00 3.8807216e+00 3.0805844e+00 4.0236799e+00 3.6646964e+00 3.4612137e+00 3.6674242e+00 4.1000000e+00 4.3046487e+00 3.5355339e+00 2.4228083e+00 2.6925824e+00 2.5748786e+00 2.8548205e+00 4.1121770e+00 3.3778692e+00 3.5916570e+00 3.9937451e+00 3.5693137e+00 2.9883106e+00 2.9154759e+00 3.2341923e+00 3.6249138e+00 2.9546573e+00 2.1517435e+00 3.0935417e+00 3.0757113e+00 3.1080541e+00 3.3734256e+00 1.8814888e+00 3.0232433e+00 5.3235327e+00 4.1641326e+00 5.3646994e+00 4.7063787e+00 5.0852729e+00 6.1741396e+00 3.4394767e+00 5.7026310e+00 5.0467812e+00 5.7489129e+00 4.4170126e+00 4.5188494e+00 4.9040799e+00 4.1121770e+00 4.3749286e+00 4.6701178e+00 4.6850827e+00 6.3835727e+00 6.5436993e+00 4.0595566e+00 5.1903757e+00 3.9774364e+00 6.2793312e+00 4.1036569e+00 5.0428167e+00 5.4046276e+00 3.9761791e+00 4.0236799e+00 4.8456166e+00 5.1778374e+00 5.6080300e+00 6.1757591e+00 4.8836462e+00 4.1737274e+00 4.5497253e+00 5.8736701e+00 4.9457052e+00 4.6508064e+00 3.9051248e+00 4.8641546e+00 5.0665570e+00 4.7021272e+00 4.1641326e+00 5.3188345e+00 5.1990384e+00 4.6957428e+00 4.2449971e+00 4.4966654e+00 4.7031904e+00 4.1412558e+00 8.0622577e-01 2.4494897e-01 5.4772256e-01 3.8755645e+00 3.4856850e+00 4.0385641e+00 3.0626786e+00 3.6945906e+00 3.3136083e+00 3.6414283e+00 2.3515952e+00 3.6428011e+00 2.8195744e+00 2.7386128e+00 3.1192948e+00 3.1256999e+00 3.5860842e+00 2.5039968e+00 3.5114100e+00 3.3151169e+00 2.9308702e+00 3.7242449e+00 2.8354894e+00 3.7148351e+00 2.9949958e+00 3.9635842e+00 3.5510562e+00 3.3166248e+00 3.4885527e+00 3.9458839e+00 4.1243181e+00 3.4234486e+00 2.4596748e+00 2.7874720e+00 2.6776856e+00 2.8266588e+00 4.0286474e+00 3.2908965e+00 3.3674916e+00 3.7881394e+00 3.5693137e+00 2.8896367e+00 2.9698485e+00 3.2310989e+00 3.4756294e+00 2.9478806e+00 2.4062419e+00 3.0708305e+00 2.9597297e+00 3.0232433e+00 3.2434549e+00 2.1118712e+00 2.9698485e+00 5.1322510e+00 4.1036569e+00 5.1710734e+00 4.5617979e+00 4.9234135e+00 5.9581876e+00 3.5199432e+00 5.5045436e+00 4.9446941e+00 5.4763126e+00 4.2201896e+00 4.4136153e+00 4.7275787e+00 4.1048752e+00 4.3104524e+00 4.4888751e+00 4.5133136e+00 6.0638272e+00 6.3796552e+00 4.0767634e+00 4.9819675e+00 3.9217343e+00 6.0835845e+00 4.0124805e+00 4.8197510e+00 5.1662365e+00 3.8742741e+00 3.8845849e+00 4.7222876e+00 4.9648766e+00 5.4249424e+00 5.8412327e+00 4.7634021e+00 4.0472213e+00 4.4586994e+00 5.6621551e+00 4.7370877e+00 4.4665423e+00 3.7749172e+00 4.6669048e+00 4.8877398e+00 4.5155288e+00 4.1036569e+00 5.1137071e+00 4.9909919e+00 4.5354162e+00 4.1928511e+00 4.3358967e+00 4.4966654e+00 4.0112342e+00 8.6602540e-01 4.1231056e-01 4.2532341e+00 3.8131352e+00 4.3863424e+00 3.0967725e+00 3.9623226e+00 3.4914181e+00 3.9686270e+00 2.2315914e+00 3.9420807e+00 2.8809721e+00 2.5787594e+00 3.3555923e+00 3.2186954e+00 3.8301436e+00 2.6720778e+00 3.8548671e+00 3.5128336e+00 3.1016125e+00 3.8548671e+00 2.9240383e+00 3.9761791e+00 3.2218007e+00 4.1617304e+00 3.7815341e+00 3.5986108e+00 3.8052595e+00 4.2426407e+00 4.4339599e+00 3.6537652e+00 2.5729361e+00 2.8319605e+00 2.7166155e+00 2.9899833e+00 4.2261093e+00 3.4612137e+00 3.6837481e+00 4.1231056e+00 3.7296112e+00 3.0886890e+00 3.0446675e+00 3.3421550e+00 3.7376463e+00 3.0919250e+00 2.2847319e+00 3.2093613e+00 3.1764760e+00 3.2171416e+00 3.5028560e+00 2.0273135e+00 3.1416556e+00 5.4175640e+00 4.2743421e+00 5.4909016e+00 4.8145612e+00 5.1971146e+00 6.3000000e+00 3.5270384e+00 5.8266629e+00 5.1788030e+00 5.8566202e+00 4.5321077e+00 4.6465041e+00 5.0299105e+00 4.2308392e+00 4.4866469e+00 4.7812132e+00 4.7979162e+00 6.4853681e+00 6.6805688e+00 4.1964271e+00 5.3094256e+00 4.0804412e+00 6.4109282e+00 4.2367440e+00 5.1497573e+00 5.5208695e+00 4.1036569e+00 4.1352146e+00 4.9648766e+00 5.3028294e+00 5.7428216e+00 6.2841069e+00 5.0039984e+00 4.2930176e+00 4.6572524e+00 6.0124870e+00 5.0408333e+00 4.7560488e+00 4.0149720e+00 4.9909919e+00 5.1865210e+00 4.8373546e+00 4.2743421e+00 5.4313902e+00 5.3103672e+00 4.8270074e+00 4.3852024e+00 4.6184413e+00 4.7968740e+00 4.2402830e+00 5.0990195e-01 3.8496753e+00 3.4856850e+00 4.0211939e+00 3.0757113e+00 3.6810325e+00 3.3436507e+00 3.6551334e+00 2.3937418e+00 3.6262929e+00 2.8653098e+00 2.7604347e+00 3.1352831e+00 3.1032241e+00 3.6000000e+00 2.5199206e+00 3.4885527e+00 3.3570821e+00 2.9410882e+00 3.7080992e+00 2.8460499e+00 3.7496667e+00 2.9849623e+00 3.9610605e+00 3.5623026e+00 3.3015148e+00 3.4684290e+00 3.9230090e+00 4.1170378e+00 3.4380227e+00 2.4515301e+00 2.7982137e+00 2.6851443e+00 2.8301943e+00 4.0509258e+00 3.3451457e+00 3.3970576e+00 3.7749172e+00 3.5468296e+00 2.9240383e+00 2.9899833e+00 3.2649655e+00 3.4899857e+00 2.9512709e+00 2.4351591e+00 3.0967725e+00 2.9899833e+00 3.0495901e+00 3.2403703e+00 2.1307276e+00 2.9899833e+00 5.1672043e+00 4.1352146e+00 5.1672043e+00 4.5836667e+00 4.9416596e+00 5.9497899e+00 3.5846897e+00 5.4990908e+00 4.9446941e+00 5.4836119e+00 4.2296572e+00 4.4204072e+00 4.7275787e+00 4.1340053e+00 4.3428102e+00 4.5066617e+00 4.5265881e+00 6.0671245e+00 6.3671030e+00 4.0841156e+00 4.9859803e+00 3.9623226e+00 6.0704201e+00 4.0149720e+00 4.8342528e+00 5.1643005e+00 3.8820098e+00 3.9051248e+00 4.7370877e+00 4.9547957e+00 5.4101756e+00 5.8326666e+00 4.7780749e+00 4.0570926e+00 4.4833024e+00 5.6409219e+00 4.7686476e+00 4.4866469e+00 3.7986840e+00 4.6626173e+00 4.8959167e+00 4.5044423e+00 4.1352146e+00 5.1254268e+00 5.0049975e+00 4.5332108e+00 4.1928511e+00 4.3428102e+00 4.5299007e+00 4.0459857e+00 4.0422766e+00 3.6428011e+00 4.1940434e+00 3.0364453e+00 3.7986840e+00 3.4000000e+00 3.8131352e+00 2.2516660e+00 3.7643060e+00 2.8442925e+00 2.5961510e+00 3.2295511e+00 3.1000000e+00 3.7013511e+00 2.5632011e+00 3.6565011e+00 3.4278273e+00 2.9883106e+00 3.7349699e+00 2.8390139e+00 3.8652296e+00 3.0708305e+00 4.0336088e+00 3.6537652e+00 3.4263683e+00 3.6180105e+00 4.0607881e+00 4.2649736e+00 3.5298725e+00 2.4556058e+00 2.7622455e+00 2.6438608e+00 2.8722813e+00 4.1243181e+00 3.3985291e+00 3.5468296e+00 3.9382737e+00 3.5916570e+00 2.9916551e+00 2.9765752e+00 3.2771939e+00 3.6027767e+00 2.9816103e+00 2.2912878e+00 3.1256999e+00 3.0692019e+00 3.1144823e+00 3.3496268e+00 2.0049938e+00 3.0397368e+00 5.3047149e+00 4.1928511e+00 5.3254108e+00 4.6957428e+00 5.0695167e+00 6.1237244e+00 3.5369478e+00 5.6586217e+00 5.0447993e+00 5.6841886e+00 4.3806392e+00 4.5188494e+00 4.8733972e+00 4.1629317e+00 4.4068129e+00 4.6465041e+00 4.6593991e+00 6.2952363e+00 6.5145990e+00 4.1060930e+00 5.1497573e+00 4.0124805e+00 6.2345810e+00 4.1060930e+00 4.9989999e+00 5.3450912e+00 3.9761791e+00 4.0137264e+00 4.8435524e+00 5.1234754e+00 5.5668663e+00 6.0745370e+00 4.8836462e+00 4.1617304e+00 4.5585085e+00 5.8206529e+00 4.9173163e+00 4.6227697e+00 3.9000000e+00 4.8228622e+00 5.0408333e+00 4.6636895e+00 4.1928511e+00 5.2829916e+00 5.1643005e+00 4.6722586e+00 4.2638011e+00 4.4743715e+00 4.6754679e+00 4.1412558e+00 6.4031242e-01 2.6457513e-01 1.8867962e+00 6.5574385e-01 1.3784049e+00 7.3484692e-01 2.6776856e+00 5.1961524e-01 2.0322401e+00 2.6532998e+00 1.2288206e+00 1.6278821e+00 9.4868330e-01 1.8083141e+00 4.3588989e-01 1.4317821e+00 1.4866069e+00 1.3000000e+00 1.7832555e+00 1.1747340e+00 1.2124356e+00 1.0148892e+00 1.0049876e+00 7.8740079e-01 5.3851648e-01 4.5825757e-01 5.5677644e-01 1.0677078e+00 1.9104973e+00 1.9467922e+00 2.0124612e+00 1.5394804e+00 1.2041595e+00 1.6278821e+00 1.0583005e+00 3.3166248e-01 1.1832160e+00 1.5394804e+00 1.8000000e+00 1.6552945e+00 9.2736185e-01 1.5264338e+00 2.6324893e+00 1.5716234e+00 1.4212670e+00 1.4282857e+00 9.4868330e-01 2.6608269e+00 1.4899664e+00 1.8439089e+00 1.4491377e+00 1.4071247e+00 1.2449900e+00 1.4628739e+00 2.1213203e+00 2.2427661e+00 1.7029386e+00 1.3964240e+00 1.8357560e+00 8.7749644e-01 1.1045361e+00 1.1000000e+00 1.6217275e+00 1.6613248e+00 1.2369317e+00 1.0440307e+00 2.3430749e+00 2.5495098e+00 1.4491377e+00 1.3490738e+00 1.5874508e+00 2.2383029e+00 9.6953597e-01 1.2609520e+00 1.3747727e+00 9.8488578e-01 1.0246951e+00 1.3490738e+00 1.1532563e+00 1.5905974e+00 2.1023796e+00 1.4035669e+00 9.0553851e-01 1.4071247e+00 1.8165902e+00 1.5297059e+00 1.0816654e+00 1.1000000e+00 1.0000000e+00 1.3820275e+00 9.9498744e-01 1.4491377e+00 1.5132746e+00 1.5198684e+00 1.0908712e+00 1.1489125e+00 9.4868330e-01 1.4071247e+00 1.2529964e+00 6.4807407e-01 1.3820275e+00 4.2426407e-01 8.3066239e-01 2.6457513e-01 2.1400935e+00 4.2426407e-01 1.4352700e+00 2.1563859e+00 6.1644140e-01 1.2884099e+00 4.7958315e-01 1.2569805e+00 3.4641016e-01 8.2462113e-01 1.0099505e+00 1.0198039e+00 1.2845233e+00 6.5574385e-01 7.3484692e-01 8.1240384e-01 6.1644140e-01 4.1231056e-01 3.1622777e-01 6.4807407e-01 6.4807407e-01 5.0000000e-01 1.4491377e+00 1.4491377e+00 1.5297059e+00 1.0295630e+00 8.8317609e-01 1.0198039e+00 4.5825757e-01 3.7416574e-01 9.3273791e-01 9.3808315e-01 1.2609520e+00 1.1269428e+00 3.8729833e-01 1.0295630e+00 2.1118712e+00 1.0099505e+00 8.4261498e-01 8.4261498e-01 4.5825757e-01 2.1424285e+00 9.2195445e-01 1.8083141e+00 1.0630146e+00 1.6881943e+00 1.1832160e+00 1.4933185e+00 2.5000000e+00 1.6673332e+00 2.0566964e+00 1.5362291e+00 2.0880613e+00 7.8740079e-01 1.0246951e+00 1.2489996e+00 1.2165525e+00 1.3000000e+00 1.1313708e+00 1.0677078e+00 2.7166155e+00 2.9068884e+00 1.1874342e+00 1.5264338e+00 1.1000000e+00 2.6343880e+00 7.1414284e-01 1.3784049e+00 1.7262677e+00 6.1644140e-01 6.1644140e-01 1.3152946e+00 1.5427249e+00 1.9697716e+00 2.5436195e+00 1.3638182e+00 7.2801099e-01 1.2922848e+00 2.2203603e+00 1.4387495e+00 1.0488088e+00 6.1644140e-01 1.1958261e+00 1.4560220e+00 1.1224972e+00 1.0630146e+00 1.6613248e+00 1.5937377e+00 1.1224972e+00 9.5393920e-01 8.8881944e-01 1.2369317e+00 8.6023253e-01 1.8574176e+00 5.8309519e-01 1.3152946e+00 6.7082039e-01 2.7018512e+00 5.0990195e-01 2.0149442e+00 2.6514147e+00 1.2247449e+00 1.6370706e+00 8.5440037e-01 1.8601075e+00 5.4772256e-01 1.3638182e+00 1.5033296e+00 1.2083046e+00 1.7916473e+00 1.0535654e+00 1.2569805e+00 8.4852814e-01 9.2736185e-01 8.3066239e-01 6.0000000e-01 3.4641016e-01 3.1622777e-01 1.0049876e+00 1.9748418e+00 1.9544820e+00 2.0346990e+00 1.5684387e+00 1.0099505e+00 1.5556349e+00 1.0344080e+00 2.8284271e-01 1.1357817e+00 1.5427249e+00 1.7804494e+00 1.5968719e+00 8.6602540e-01 1.5362291e+00 2.6570661e+00 1.5427249e+00 1.4247807e+00 1.4177447e+00 9.6436508e-01 2.7147744e+00 1.4866069e+00 1.6155494e+00 1.2529964e+00 1.1874342e+00 9.8994949e-01 1.2124356e+00 1.9364917e+00 2.1354157e+00 1.5000000e+00 1.1401754e+00 1.6673332e+00 6.7823300e-01 8.5440037e-01 8.6023253e-01 1.4352700e+00 1.4662878e+00 1.0295630e+00 7.8740079e-01 2.2045408e+00 2.3515952e+00 1.2767145e+00 1.1357817e+00 1.4247807e+00 2.0542639e+00 7.8102497e-01 1.0392305e+00 1.1832160e+00 8.2462113e-01 8.6023253e-01 1.0908712e+00 9.5916630e-01 1.3928388e+00 1.9974984e+00 1.1489125e+00 7.0000000e-01 1.1789826e+00 1.6522712e+00 1.3228757e+00 8.3666003e-01 9.5916630e-01 7.8102497e-01 1.1575837e+00 8.2462113e-01 1.2529964e+00 1.2884099e+00 1.3114877e+00 8.8317609e-01 9.4339811e-01 7.1414284e-01 1.2124356e+00 1.0677078e+00 1.2845233e+00 7.3484692e-01 1.4899664e+00 9.7467943e-01 1.3892444e+00 5.1961524e-01 8.2462113e-01 8.5440037e-01 5.9160798e-01 1.1045361e+00 7.2801099e-01 1.5000000e+00 8.8881944e-01 5.9160798e-01 8.8881944e-01 3.1622777e-01 1.3638182e+00 7.8102497e-01 1.2369317e+00 1.0535654e+00 1.1224972e+00 1.3674794e+00 1.6093477e+00 1.7578396e+00 9.4868330e-01 6.8556546e-01 3.0000000e-01 4.3588989e-01 5.1961524e-01 1.3076697e+00 8.8881944e-01 1.3416408e+00 1.6155494e+00 8.9442719e-01 7.1414284e-01 2.0000000e-01 5.0990195e-01 1.1045361e+00 4.3588989e-01 9.1104336e-01 4.5825757e-01 7.6157731e-01 6.6332496e-01 9.6953597e-01 1.1135529e+00 5.4772256e-01 2.6608269e+00 1.3490738e+00 2.7018512e+00 1.9519221e+00 2.3537205e+00 3.5071356e+00 9.0000000e-01 3.0232433e+00 2.2293497e+00 3.2295511e+00 1.8734994e+00 1.7378147e+00 2.2516660e+00 1.2529964e+00 1.6613248e+00 2.0760539e+00 1.9974984e+00 3.8974351e+00 3.7868192e+00 1.1401754e+00 2.5806976e+00 1.2489996e+00 3.5874782e+00 1.3638182e+00 2.4433583e+00 2.8195744e+00 1.2767145e+00 1.3820275e+00 2.0639767e+00 2.5903668e+00 2.9376862e+00 3.7762415e+00 2.1047565e+00 1.4628739e+00 1.7378147e+00 3.2771939e+00 2.3706539e+00 1.9874607e+00 1.2767145e+00 2.2803509e+00 2.4186773e+00 2.1931712e+00 1.3490738e+00 2.6664583e+00 2.6019224e+00 2.0904545e+00 1.4282857e+00 1.8493242e+00 2.1587033e+00 1.4525839e+00 8.3066239e-01 5.5677644e-01 2.1587033e+00 2.4494897e-01 1.4832397e+00 2.0856654e+00 7.4833148e-01 1.1045361e+00 4.3588989e-01 1.3638182e+00 4.2426407e-01 9.2736185e-01 1.0000000e+00 6.7823300e-01 1.2449900e+00 8.0622577e-01 7.4833148e-01 4.6904158e-01 5.0990195e-01 3.8729833e-01 3.1622777e-01 3.7416574e-01 5.2915026e-01 5.1961524e-01 1.4628739e+00 1.4000000e+00 1.4899664e+00 1.0392305e+00 7.2111026e-01 1.1224972e+00 7.9372539e-01 3.7416574e-01 6.0827625e-01 1.0677078e+00 1.2206556e+00 1.0816654e+00 4.5825757e-01 9.8994949e-01 2.1071308e+00 1.0099505e+00 9.6436508e-01 9.2195445e-01 4.7958315e-01 2.1840330e+00 9.6436508e-01 1.8027756e+00 9.5393920e-01 1.5652476e+00 1.0677078e+00 1.4035669e+00 2.3685439e+00 1.6431677e+00 1.9052559e+00 1.2884099e+00 2.0928450e+00 8.1240384e-01 8.1853528e-01 1.1401754e+00 1.0677078e+00 1.2449900e+00 1.1401754e+00 9.6953597e-01 2.7092434e+00 2.7221315e+00 8.7749644e-01 1.4730920e+00 1.0723805e+00 2.4698178e+00 4.7958315e-01 1.3638182e+00 1.6431677e+00 4.6904158e-01 6.1644140e-01 1.1704700e+00 1.4071247e+00 1.7944358e+00 2.5396850e+00 1.2247449e+00 5.3851648e-01 1.1000000e+00 2.0904545e+00 1.4866069e+00 1.0000000e+00 6.4807407e-01 1.1180340e+00 1.3928388e+00 1.0677078e+00 9.5393920e-01 1.6062378e+00 1.5811388e+00 1.0392305e+00 6.7082039e-01 8.0622577e-01 1.3152946e+00 8.6023253e-01 8.6023253e-01 1.5264338e+00 9.1104336e-01 7.9372539e-01 1.4899664e+00 4.5825757e-01 8.8881944e-01 4.6904158e-01 9.1104336e-01 1.0535654e+00 3.0000000e-01 5.1961524e-01 8.0622577e-01 7.0710678e-01 7.3484692e-01 6.4031242e-01 8.0622577e-01 4.5825757e-01 7.3484692e-01 9.3273791e-01 1.1445523e+00 1.2041595e+00 3.7416574e-01 1.0630146e+00 8.5440037e-01 9.6436508e-01 6.2449980e-01 7.4161985e-01 4.1231056e-01 7.3484692e-01 1.0816654e+00 7.8740079e-01 4.5825757e-01 6.1644140e-01 3.1622777e-01 4.6904158e-01 5.5677644e-01 1.5066519e+00 3.3166248e-01 3.7416574e-01 3.1622777e-01 5.4772256e-01 1.6552945e+00 4.0000000e-01 2.0736441e+00 8.6023253e-01 2.1447611e+00 1.3527749e+00 1.7832555e+00 2.9495762e+00 9.4339811e-01 2.4617067e+00 1.7406895e+00 2.6248809e+00 1.2845233e+00 1.2247449e+00 1.7000000e+00 9.1104336e-01 1.2569805e+00 1.5132746e+00 1.3892444e+00 3.2634338e+00 3.2863353e+00 8.6023253e-01 2.0099751e+00 8.1240384e-01 3.0545049e+00 8.8317609e-01 1.8248288e+00 2.2158520e+00 7.6811457e-01 7.8102497e-01 1.5297059e+00 2.0174241e+00 2.4103942e+00 3.1527766e+00 1.5842980e+00 8.7177979e-01 1.1916375e+00 2.7568098e+00 1.7720045e+00 1.3527749e+00 6.8556546e-01 1.7262677e+00 1.8734994e+00 1.7000000e+00 8.6023253e-01 2.0808652e+00 2.0322401e+00 1.5905974e+00 1.0295630e+00 1.2884099e+00 1.5556349e+00 8.3066239e-01 2.2561028e+00 5.9160798e-01 1.5000000e+00 2.2759613e+00 7.1414284e-01 1.4662878e+00 4.8989795e-01 1.3964240e+00 5.7445626e-01 7.9372539e-01 1.1532563e+00 1.1269428e+00 1.4212670e+00 4.6904158e-01 9.3273791e-01 8.3066239e-01 6.7082039e-01 6.4807407e-01 5.5677644e-01 7.4161985e-01 5.9160798e-01 5.4772256e-01 1.6278821e+00 1.5842980e+00 1.6763055e+00 1.1874342e+00 7.8102497e-01 9.7467943e-01 3.7416574e-01 4.5825757e-01 1.0862780e+00 1.0148892e+00 1.3638182e+00 1.1747340e+00 4.2426407e-01 1.1789826e+00 2.2383029e+00 1.0908712e+00 9.2736185e-01 9.2736185e-01 6.4807407e-01 2.2847319e+00 1.0295630e+00 1.5811388e+00 9.2736185e-01 1.5556349e+00 1.0049876e+00 1.3038405e+00 2.3748684e+00 1.6278821e+00 1.9390719e+00 1.4317821e+00 1.9157244e+00 6.0827625e-01 9.0553851e-01 1.1090537e+00 1.1180340e+00 1.1401754e+00 9.3273791e-01 9.0000000e-01 2.5632011e+00 2.7892651e+00 1.1832160e+00 1.3638182e+00 9.6953597e-01 2.5238859e+00 6.6332496e-01 1.1874342e+00 1.5968719e+00 5.5677644e-01 4.5825757e-01 1.1489125e+00 1.4525839e+00 1.8734994e+00 2.4207437e+00 1.1958261e+00 6.4807407e-01 1.1747340e+00 2.1213203e+00 1.2083046e+00 8.5440037e-01 4.7958315e-01 1.0677078e+00 1.2845233e+00 1.0246951e+00 9.2736185e-01 1.4798649e+00 1.4035669e+00 9.9498744e-01 9.0553851e-01 7.3484692e-01 1.0000000e+00 6.7082039e-01 2.2181073e+00 8.3666003e-01 4.5825757e-01 1.5556349e+00 1.3190906e+00 1.9519221e+00 9.5916630e-01 2.2583180e+00 1.5937377e+00 1.2409674e+00 1.8493242e+00 9.3273791e-01 2.1283797e+00 1.4764823e+00 2.1863211e+00 1.8973666e+00 1.8947295e+00 2.1494185e+00 2.4859606e+00 2.6419690e+00 1.7748239e+00 8.4852814e-01 7.8740079e-01 7.2111026e-01 1.1401754e+00 2.2135944e+00 1.5165751e+00 2.0024984e+00 2.4372115e+00 1.8083141e+00 1.2569805e+00 9.7467943e-01 1.2845233e+00 1.9104973e+00 1.1747340e+00 1.4142136e-01 1.2165525e+00 1.3601471e+00 1.3379088e+00 1.7406895e+00 3.8729833e-01 1.2369317e+00 3.5085610e+00 2.2248595e+00 3.6290495e+00 2.8530685e+00 3.2572995e+00 4.4440972e+00 1.3928388e+00 3.9560081e+00 3.1843367e+00 4.1012193e+00 2.7276363e+00 2.6739484e+00 3.1654384e+00 2.1307276e+00 2.4839485e+00 2.9291637e+00 2.8982753e+00 4.7749346e+00 4.7465777e+00 2.0952327e+00 3.4770677e+00 2.0518285e+00 4.5343136e+00 2.2912878e+00 3.3196385e+00 3.7229021e+00 2.1771541e+00 2.2360680e+00 2.9849623e+00 3.5014283e+00 3.8807216e+00 4.6443514e+00 3.0232433e+00 2.3685439e+00 2.6324893e+00 4.2107007e+00 3.1953091e+00 2.8670542e+00 2.1118712e+00 3.1796226e+00 3.3136083e+00 3.0692019e+00 2.2248595e+00 3.5637059e+00 3.4727511e+00 2.9832868e+00 2.3811762e+00 2.7440845e+00 2.9647934e+00 2.2891046e+00 1.5811388e+00 2.1610183e+00 8.3666003e-01 1.1401754e+00 5.1961524e-01 1.4142136e+00 3.1622777e-01 1.0295630e+00 1.0099505e+00 8.3666003e-01 1.3000000e+00 9.3273791e-01 7.8740079e-01 6.1644140e-01 5.2915026e-01 3.6055513e-01 2.4494897e-01 3.1622777e-01 5.8309519e-01 6.4031242e-01 1.4832397e+00 1.4628739e+00 1.5362291e+00 1.0862780e+00 8.6023253e-01 1.2247449e+00 8.4261498e-01 3.1622777e-01 7.0000000e-01 1.1224972e+00 1.3152946e+00 1.1618950e+00 5.1961524e-01 1.0488088e+00 2.1679483e+00 1.0954451e+00 9.9498744e-01 9.8488578e-01 5.0000000e-01 2.2383029e+00 1.0344080e+00 1.9104973e+00 1.1357817e+00 1.6093477e+00 1.1575837e+00 1.5066519e+00 2.3769729e+00 1.7944358e+00 1.9052559e+00 1.3638182e+00 2.1307276e+00 9.1651514e-01 9.6436508e-01 1.2247449e+00 1.2727922e+00 1.4525839e+00 1.2727922e+00 1.0392305e+00 2.6907248e+00 2.7549955e+00 1.0246951e+00 1.5459625e+00 1.2609520e+00 2.4738634e+00 6.8556546e-01 1.4212670e+00 1.6309506e+00 6.7823300e-01 7.7459667e-01 1.3000000e+00 1.3784049e+00 1.8055470e+00 2.4959968e+00 1.3638182e+00 6.2449980e-01 1.1618950e+00 2.1142375e+00 1.5968719e+00 1.0677078e+00 8.1240384e-01 1.1874342e+00 1.5033296e+00 1.1747340e+00 1.1357817e+00 1.6792856e+00 1.6792856e+00 1.1747340e+00 8.7749644e-01 9.3273791e-01 1.4317821e+00 1.0000000e+00 9.2195445e-01 8.2462113e-01 1.0295630e+00 1.2206556e+00 5.4772256e-01 1.6309506e+00 7.8740079e-01 7.4833148e-01 1.2727922e+00 5.3851648e-01 1.3076697e+00 9.1651514e-01 1.5033296e+00 1.2247449e+00 1.2845233e+00 1.5165751e+00 1.8384776e+00 1.9078784e+00 1.0246951e+00 7.6157731e-01 5.2915026e-01 6.1644140e-01 6.3245553e-01 1.4560220e+00 7.0710678e-01 1.2369317e+00 1.7492856e+00 1.2767145e+00 5.4772256e-01 3.8729833e-01 6.2449980e-01 1.1789826e+00 6.4807407e-01 8.4852814e-01 5.0990195e-01 6.8556546e-01 6.2449980e-01 1.1000000e+00 9.7467943e-01 5.5677644e-01 2.6814175e+00 1.4317821e+00 2.8618176e+00 2.0736441e+00 2.4556058e+00 3.6918830e+00 7.6157731e-01 3.2202484e+00 2.4617067e+00 3.2954514e+00 1.9339080e+00 1.9104973e+00 2.3874673e+00 1.3638182e+00 1.6763055e+00 2.1118712e+00 2.1213203e+00 3.9924930e+00 4.0087405e+00 1.4525839e+00 2.6814175e+00 1.2369317e+00 3.8026307e+00 1.5394804e+00 2.5179357e+00 2.9698485e+00 1.4071247e+00 1.4352700e+00 2.1977261e+00 2.7820855e+00 3.1527766e+00 3.8871583e+00 2.2315914e+00 1.6340135e+00 1.9261360e+00 3.4626579e+00 2.3643181e+00 2.0784610e+00 1.3038405e+00 2.4062419e+00 2.5099801e+00 2.3021729e+00 1.4317821e+00 2.7604347e+00 2.6570661e+00 2.2000000e+00 1.6462078e+00 1.9570386e+00 2.1330729e+00 1.4764823e+00 1.5968719e+00 1.1357817e+00 1.9026298e+00 1.1269428e+00 2.2516660e+00 1.6155494e+00 1.2206556e+00 1.6522712e+00 8.8317609e-01 2.1400935e+00 1.4798649e+00 2.0371549e+00 1.8248288e+00 1.8708287e+00 2.1283797e+00 2.3937418e+00 2.5748786e+00 1.7492856e+00 9.2195445e-01 7.1414284e-01 6.7082039e-01 1.1532563e+00 2.1000000e+00 1.5524175e+00 2.0784610e+00 2.4062419e+00 1.6370706e+00 1.3453624e+00 9.1651514e-01 1.2083046e+00 1.8920888e+00 1.1357817e+00 3.6055513e-01 1.1958261e+00 1.4212670e+00 1.3711309e+00 1.7262677e+00 7.2111026e-01 1.2569805e+00 3.4467376e+00 2.1213203e+00 3.5185224e+00 2.7477263e+00 3.1591138e+00 4.3104524e+00 1.3228757e+00 3.8183766e+00 3.0116441e+00 4.0509258e+00 2.6925824e+00 2.5495098e+00 3.0740852e+00 1.9974984e+00 2.4083189e+00 2.8861739e+00 2.8089144e+00 4.7127487e+00 4.5716518e+00 1.8814888e+00 3.4029399e+00 1.9899749e+00 4.3783559e+00 2.1863211e+00 3.2603681e+00 3.6290495e+00 2.1000000e+00 2.1931712e+00 2.8670542e+00 3.3896903e+00 3.7376463e+00 4.5891176e+00 2.9068884e+00 2.2671568e+00 2.4779023e+00 4.0914545e+00 3.1654384e+00 2.7946377e+00 2.0808652e+00 3.1048349e+00 3.2357379e+00 3.0116441e+00 2.1213203e+00 3.4828150e+00 3.4161382e+00 2.9103264e+00 2.2360680e+00 2.6720778e+00 2.9495762e+00 2.2383029e+00 9.6953597e-01 5.5677644e-01 7.0710678e-01 8.3666003e-01 4.2426407e-01 6.0000000e-01 9.0553851e-01 7.6811457e-01 7.0000000e-01 4.0000000e-01 9.4868330e-01 6.4807407e-01 5.5677644e-01 7.3484692e-01 1.1045361e+00 1.1489125e+00 3.3166248e-01 9.6953597e-01 9.1651514e-01 1.0099505e+00 5.2915026e-01 9.5916630e-01 5.8309519e-01 5.1961524e-01 9.4868330e-01 8.5440037e-01 3.7416574e-01 7.0000000e-01 6.7082039e-01 4.5825757e-01 5.4772256e-01 1.5362291e+00 4.6904158e-01 3.6055513e-01 3.0000000e-01 3.8729833e-01 1.5779734e+00 3.6055513e-01 2.1189620e+00 1.0344080e+00 2.1656408e+00 1.4899664e+00 1.8466185e+00 3.0016662e+00 1.1747340e+00 2.5436195e+00 1.8814888e+00 2.5806976e+00 1.2083046e+00 1.3076697e+00 1.6911535e+00 1.0862780e+00 1.2922848e+00 1.4628739e+00 1.4628739e+00 3.2588341e+00 3.3660065e+00 1.1357817e+00 1.9824228e+00 9.3273791e-01 3.1272992e+00 9.1104336e-01 1.8275667e+00 2.2494444e+00 7.6157731e-01 7.8740079e-01 1.6155494e+00 2.0639767e+00 2.4617067e+00 3.1192948e+00 1.6552945e+00 1.0049876e+00 1.4730920e+00 2.7367864e+00 1.7578396e+00 1.4282857e+00 6.7823300e-01 1.6763055e+00 1.8493242e+00 1.5684387e+00 1.0344080e+00 2.0928450e+00 1.9949937e+00 1.5099669e+00 1.1000000e+00 1.2688578e+00 1.5264338e+00 9.4868330e-01 1.0723805e+00 9.4868330e-01 1.2727922e+00 1.1401754e+00 5.4772256e-01 7.3484692e-01 5.1961524e-01 1.5132746e+00 6.7823300e-01 1.1135529e+00 9.4868330e-01 9.1104336e-01 1.1489125e+00 1.3416408e+00 1.6186414e+00 9.9498744e-01 7.0710678e-01 5.8309519e-01 6.1644140e-01 5.8309519e-01 1.3490738e+00 1.2247449e+00 1.4317821e+00 1.4282857e+00 5.9160798e-01 9.4868330e-01 6.5574385e-01 7.8102497e-01 1.0816654e+00 4.8989795e-01 1.2247449e+00 7.3484692e-01 9.0000000e-01 8.4261498e-01 8.4261498e-01 1.3820275e+00 7.4161985e-01 2.7477263e+00 1.5198684e+00 2.5826343e+00 1.9442222e+00 2.3600847e+00 3.3421550e+00 1.4282857e+00 2.8478062e+00 2.1118712e+00 3.1717503e+00 1.8601075e+00 1.7058722e+00 2.1771541e+00 1.4764823e+00 1.8894444e+00 2.1307276e+00 1.9442222e+00 3.7656341e+00 3.6262929e+00 1.1180340e+00 2.5278449e+00 1.5264338e+00 3.3970576e+00 1.3379088e+00 2.4083189e+00 2.6608269e+00 1.2961481e+00 1.4491377e+00 2.0712315e+00 2.3832751e+00 2.7459060e+00 3.5958309e+00 2.1260292e+00 1.3820275e+00 1.7000000e+00 3.1032241e+00 2.4596748e+00 1.9646883e+00 1.3856406e+00 2.1886069e+00 2.4124676e+00 2.1260292e+00 1.5198684e+00 2.6343880e+00 2.6153394e+00 2.0639767e+00 1.4106736e+00 1.8248288e+00 2.2649503e+00 1.5811388e+00 1.2124356e+00 7.0000000e-01 5.5677644e-01 8.0622577e-01 7.4161985e-01 1.0677078e+00 5.4772256e-01 7.1414284e-01 5.0000000e-01 2.2360680e-01 5.0990195e-01 5.9160798e-01 7.1414284e-01 7.4161985e-01 2.4494897e-01 1.3601471e+00 1.2288206e+00 1.3304135e+00 9.0000000e-01 5.0000000e-01 7.4161985e-01 5.8309519e-01 6.4031242e-01 7.0710678e-01 7.9372539e-01 1.0099505e+00 7.6157731e-01 1.4142136e-01 8.4261498e-01 1.9209373e+00 7.4161985e-01 6.7823300e-01 6.4807407e-01 4.2426407e-01 2.0346990e+00 7.3484692e-01 1.7606817e+00 7.3484692e-01 1.7146428e+00 1.0049876e+00 1.4212670e+00 2.5219040e+00 1.3152946e+00 2.0396078e+00 1.3747727e+00 2.2068076e+00 8.7749644e-01 8.6023253e-01 1.2767145e+00 8.7749644e-01 1.1224972e+00 1.1618950e+00 9.8488578e-01 2.8301943e+00 2.8809721e+00 7.7459667e-01 1.5937377e+00 8.1240384e-01 2.6324893e+00 5.2915026e-01 1.4177447e+00 1.7748239e+00 4.3588989e-01 4.5825757e-01 1.1832160e+00 1.5716234e+00 1.9773720e+00 2.7018512e+00 1.2449900e+00 4.6904158e-01 9.4868330e-01 2.3108440e+00 1.4491377e+00 9.6436508e-01 4.3588989e-01 1.2884099e+00 1.4866069e+00 1.2845233e+00 7.3484692e-01 1.6822604e+00 1.6522712e+00 1.1958261e+00 7.3484692e-01 8.8317609e-01 1.2489996e+00 6.0827625e-01 1.3784049e+00 9.2736185e-01 6.4807407e-01 1.3038405e+00 5.3851648e-01 1.3674794e+00 6.4807407e-01 1.5427249e+00 1.2165525e+00 1.0630146e+00 1.2884099e+00 1.7029386e+00 1.8275667e+00 1.0049876e+00 4.4721360e-01 5.8309519e-01 6.0000000e-01 4.2426407e-01 1.5937377e+00 9.4868330e-01 1.1445523e+00 1.5811388e+00 1.2206556e+00 5.0990195e-01 5.7445626e-01 8.6602540e-01 1.1269428e+00 5.4772256e-01 9.4868330e-01 6.3245553e-01 6.2449980e-01 6.0827625e-01 9.2195445e-01 9.0000000e-01 5.1961524e-01 2.8017851e+00 1.6401219e+00 2.8618176e+00 2.1771541e+00 2.5436195e+00 3.6945906e+00 1.2727922e+00 3.2295511e+00 2.5416530e+00 3.2771939e+00 1.9078784e+00 1.9824228e+00 2.3874673e+00 1.6186414e+00 1.8734994e+00 2.1494185e+00 2.1633308e+00 3.9547440e+00 4.0484565e+00 1.6278821e+00 2.6814175e+00 1.4798649e+00 3.8105118e+00 1.5716234e+00 2.5337719e+00 2.9427878e+00 1.4352700e+00 1.4832397e+00 2.3000000e+00 2.7386128e+00 3.1400637e+00 3.7986840e+00 2.3366643e+00 1.6703293e+00 2.0856654e+00 3.4161382e+00 2.4392622e+00 2.1307276e+00 1.3638182e+00 2.3685439e+00 2.5416530e+00 2.2315914e+00 1.6401219e+00 2.7964263e+00 2.6870058e+00 2.1863211e+00 1.7233688e+00 1.9672316e+00 2.2022716e+00 1.6124515e+00 1.1135529e+00 1.1045361e+00 1.0392305e+00 1.3820275e+00 9.8488578e-01 7.8740079e-01 8.8317609e-01 7.6157731e-01 3.8729833e-01 1.4142136e-01 5.0990195e-01 6.7823300e-01 7.4161985e-01 1.4899664e+00 1.5427249e+00 1.6062378e+00 1.1224972e+00 1.0862780e+00 1.3114877e+00 7.9372539e-01 3.1622777e-01 9.0000000e-01 1.1489125e+00 1.4035669e+00 1.3152946e+00 6.4031242e-01 1.1224972e+00 2.2135944e+00 1.1916375e+00 1.0440307e+00 1.0440307e+00 5.5677644e-01 2.2293497e+00 1.0908712e+00 1.9924859e+00 1.3076697e+00 1.7058722e+00 1.3416408e+00 1.6278821e+00 2.4799194e+00 1.9235384e+00 2.0420578e+00 1.5748016e+00 2.1447611e+00 9.4868330e-01 1.1445523e+00 1.3114877e+00 1.4422205e+00 1.5459625e+00 1.3114877e+00 1.1916375e+00 2.7239677e+00 2.8827071e+00 1.2922848e+00 1.5968719e+00 1.3820275e+00 2.5961510e+00 8.5440037e-01 1.4899664e+00 1.7262677e+00 8.1240384e-01 8.8317609e-01 1.4525839e+00 1.5033296e+00 1.9287302e+00 2.5079872e+00 1.5033296e+00 8.6602540e-01 1.4317821e+00 2.1702534e+00 1.6401219e+00 1.2083046e+00 9.0553851e-01 1.2369317e+00 1.5620499e+00 1.1575837e+00 1.3076697e+00 1.7549929e+00 1.7146428e+00 1.2083046e+00 1.0630146e+00 1.0246951e+00 1.4662878e+00 1.1401754e+00 7.3484692e-01 1.0000000e+00 8.7749644e-01 5.5677644e-01 7.6157731e-01 9.4868330e-01 6.4807407e-01 8.5440037e-01 1.0099505e+00 1.2569805e+00 1.2247449e+00 4.1231056e-01 1.1916375e+00 1.0099505e+00 1.1224972e+00 7.6157731e-01 7.8740079e-01 2.0000000e-01 5.7445626e-01 1.1224972e+00 1.0148892e+00 4.4721360e-01 7.4161985e-01 5.1961524e-01 5.1961524e-01 7.3484692e-01 1.5937377e+00 4.6904158e-01 4.3588989e-01 3.8729833e-01 6.7082039e-01 1.7058722e+00 5.0000000e-01 1.9570386e+00 8.0622577e-01 2.1377558e+00 1.3416408e+00 1.7291616e+00 2.9614186e+00 8.8317609e-01 2.4959968e+00 1.8000000e+00 2.5455844e+00 1.2083046e+00 1.2369317e+00 1.6733201e+00 8.7177979e-01 1.1180340e+00 1.4000000e+00 1.3784049e+00 3.2218007e+00 3.3120990e+00 1.0246951e+00 1.9519221e+00 6.7082039e-01 3.0886890e+00 9.1104336e-01 1.7606817e+00 2.2226111e+00 7.6157731e-01 7.0710678e-01 1.5000000e+00 2.0639767e+00 2.4494897e+00 3.1288976e+00 1.5427249e+00 9.4339811e-01 1.2767145e+00 2.7586228e+00 1.6340135e+00 1.3190906e+00 5.8309519e-01 1.6941074e+00 1.8000000e+00 1.6431677e+00 8.0622577e-01 2.0199010e+00 1.9339080e+00 1.5297059e+00 1.0723805e+00 1.2449900e+00 1.4035669e+00 7.3484692e-01 9.0553851e-01 3.6055513e-01 1.1789826e+00 4.4721360e-01 1.0862780e+00 7.0710678e-01 7.2801099e-01 9.8994949e-01 1.2884099e+00 1.4832397e+00 7.0000000e-01 6.1644140e-01 5.2915026e-01 5.8309519e-01 2.8284271e-01 1.1832160e+00 8.1240384e-01 1.0246951e+00 1.2569805e+00 7.6811457e-01 4.6904158e-01 4.7958315e-01 4.7958315e-01 7.6811457e-01 2.4494897e-01 1.2000000e+00 3.7416574e-01 3.8729833e-01 3.8729833e-01 5.7445626e-01 1.3228757e+00 3.3166248e-01 2.5436195e+00 1.3453624e+00 2.4959968e+00 1.7832555e+00 2.2158520e+00 3.2848135e+00 1.2247449e+00 2.7874720e+00 2.0928450e+00 3.0033315e+00 1.6552945e+00 1.6155494e+00 2.0639767e+00 1.3638182e+00 1.7233688e+00 1.9339080e+00 1.7832555e+00 3.6083237e+00 3.6262929e+00 1.1618950e+00 2.3895606e+00 1.3000000e+00 3.3734256e+00 1.2369317e+00 2.2226111e+00 2.5416530e+00 1.1401754e+00 1.2083046e+00 1.9570386e+00 2.3021729e+00 2.7166155e+00 3.4510868e+00 2.0149442e+00 1.2288206e+00 1.5842980e+00 3.0643107e+00 2.2248595e+00 1.7663522e+00 1.1224972e+00 2.0663978e+00 2.2759613e+00 2.0149442e+00 1.3453624e+00 2.4859606e+00 2.4454039e+00 1.9493589e+00 1.3820275e+00 1.6703293e+00 2.0074860e+00 1.3190906e+00 9.8488578e-01 1.1269428e+00 8.1240384e-01 5.0990195e-01 7.0710678e-01 7.8102497e-01 9.0553851e-01 9.0553851e-01 1.0862780e+00 7.2801099e-01 1.2884099e+00 1.0862780e+00 1.1916375e+00 9.2736185e-01 8.1240384e-01 1.1313708e+00 1.2206556e+00 1.0488088e+00 2.6457513e-01 1.0954451e+00 9.3273791e-01 8.6602540e-01 8.1853528e-01 8.1240384e-01 1.7720045e+00 8.6023253e-01 1.0344080e+00 9.3273791e-01 7.5498344e-01 1.9261360e+00 9.0000000e-01 2.1142375e+00 9.6436508e-01 1.9416488e+00 1.3416408e+00 1.7058722e+00 2.7147744e+00 1.3490738e+00 2.2427661e+00 1.4560220e+00 2.5534291e+00 1.3038405e+00 1.0440307e+00 1.5362291e+00 9.1651514e-01 1.3000000e+00 1.5231546e+00 1.3490738e+00 3.1843367e+00 2.9681644e+00 5.3851648e-01 1.8894444e+00 1.0630146e+00 2.7748874e+00 7.1414284e-01 1.8055470e+00 2.0832667e+00 7.3484692e-01 9.4868330e-01 1.4035669e+00 1.8275667e+00 2.1260292e+00 3.0512293e+00 1.4491377e+00 8.5440037e-01 1.1789826e+00 2.4677925e+00 1.8627936e+00 1.3928388e+00 9.2736185e-01 1.5716234e+00 1.7549929e+00 1.5165751e+00 9.6436508e-01 1.9899749e+00 1.9748418e+00 1.4212670e+00 7.1414284e-01 1.2124356e+00 1.7000000e+00 1.0862780e+00 1.3711309e+00 6.2449980e-01 1.2845233e+00 9.9498744e-01 1.0000000e+00 1.2609520e+00 1.5588457e+00 1.7406895e+00 9.1651514e-01 4.3588989e-01 1.7320508e-01 2.6457513e-01 3.0000000e-01 1.3747727e+00 9.0000000e-01 1.2569805e+00 1.5394804e+00 9.0553851e-01 5.7445626e-01 2.4494897e-01 5.2915026e-01 1.0392305e+00 2.6457513e-01 8.7749644e-01 4.1231056e-01 6.0000000e-01 5.4772256e-01 8.4852814e-01 1.0295630e+00 4.2426407e-01 2.7386128e+00 1.4696938e+00 2.7386128e+00 2.0074860e+00 2.4248711e+00 3.5411862e+00 1.1000000e+00 3.0495901e+00 2.3043437e+00 3.2511536e+00 1.8841444e+00 1.8110770e+00 2.2912878e+00 1.4247807e+00 1.8055470e+00 2.1283797e+00 2.0273135e+00 3.8923001e+00 3.8548671e+00 1.2727922e+00 2.6191602e+00 1.3784049e+00 3.6262929e+00 1.4212670e+00 2.4677925e+00 2.8195744e+00 1.3228757e+00 1.4106736e+00 2.1494185e+00 2.5826343e+00 2.9681644e+00 3.7469988e+00 2.1977261e+00 1.4764823e+00 1.8000000e+00 3.3075671e+00 2.4248711e+00 2.0124612e+00 1.3076697e+00 2.3021729e+00 2.4799194e+00 2.2203603e+00 1.4696938e+00 2.7147744e+00 2.6551836e+00 2.1424285e+00 1.5297059e+00 1.8867962e+00 2.2045408e+00 1.5066519e+00 1.0440307e+00 8.6602540e-01 7.5498344e-01 9.1651514e-01 9.2195445e-01 1.0630146e+00 8.5440037e-01 5.2915026e-01 1.6522712e+00 1.5132746e+00 1.6278821e+00 1.1958261e+00 6.2449980e-01 6.8556546e-01 4.2426407e-01 8.6602540e-01 1.1747340e+00 9.3273791e-01 1.2409674e+00 1.0198039e+00 5.2915026e-01 1.1704700e+00 2.1236761e+00 9.7467943e-01 8.9442719e-01 8.6023253e-01 8.2462113e-01 2.2045408e+00 9.6953597e-01 1.4491377e+00 6.0000000e-01 1.6673332e+00 9.4339811e-01 1.2489996e+00 2.5019992e+00 1.2609520e+00 2.0736441e+00 1.4594520e+00 2.0074860e+00 7.0000000e-01 8.7177979e-01 1.1958261e+00 7.8102497e-01 7.8740079e-01 8.6602540e-01 9.4339811e-01 2.7147744e+00 2.8740216e+00 1.0677078e+00 1.4352700e+00 5.4772256e-01 2.6551836e+00 6.4807407e-01 1.2449900e+00 1.7691806e+00 5.0000000e-01 3.0000000e-01 1.0677078e+00 1.6643317e+00 2.0273135e+00 2.6381812e+00 1.1000000e+00 7.0710678e-01 1.0954451e+00 2.2847319e+00 1.0954451e+00 8.6602540e-01 2.2360680e-01 1.2083046e+00 1.2845233e+00 1.1618950e+00 6.0000000e-01 1.5066519e+00 1.3964240e+00 1.0440307e+00 8.3666003e-01 7.7459667e-01 8.6023253e-01 3.6055513e-01 9.8994949e-01 7.0710678e-01 4.3588989e-01 6.7823300e-01 1.0677078e+00 1.2489996e+00 5.5677644e-01 7.3484692e-01 7.7459667e-01 8.3666003e-01 3.4641016e-01 1.1489125e+00 9.0553851e-01 8.4261498e-01 9.8994949e-01 6.7082039e-01 5.4772256e-01 6.7082039e-01 7.5498344e-01 6.4031242e-01 3.7416574e-01 1.4282857e+00 5.4772256e-01 5.0000000e-01 4.5825757e-01 3.3166248e-01 1.4594520e+00 4.1231056e-01 2.3937418e+00 1.2922848e+00 2.3000000e+00 1.6911535e+00 2.0615528e+00 3.1128765e+00 1.3928388e+00 2.6438608e+00 1.9849433e+00 2.7748874e+00 1.4212670e+00 1.4662878e+00 1.8493242e+00 1.3190906e+00 1.5842980e+00 1.7146428e+00 1.6431677e+00 3.4146742e+00 3.4655447e+00 1.1874342e+00 2.1656408e+00 1.2449900e+00 3.2155870e+00 1.0535654e+00 2.0346990e+00 2.3706539e+00 9.4868330e-01 1.0488088e+00 1.8138357e+00 2.1400935e+00 2.5416530e+00 3.2388269e+00 1.8601075e+00 1.1357817e+00 1.6155494e+00 2.8301943e+00 2.0420578e+00 1.6370706e+00 9.6953597e-01 1.8248288e+00 2.0542639e+00 1.7146428e+00 1.2922848e+00 2.2934690e+00 2.2226111e+00 1.6852300e+00 1.2206556e+00 1.4594520e+00 1.8248288e+00 1.2409674e+00 5.0990195e-01 7.5498344e-01 7.7459667e-01 6.0000000e-01 6.7823300e-01 6.4031242e-01 1.6062378e+00 1.4212670e+00 1.5297059e+00 1.1747340e+00 4.2426407e-01 1.1045361e+00 1.0344080e+00 7.4833148e-01 5.7445626e-01 1.1916375e+00 1.2206556e+00 9.9498744e-01 6.2449980e-01 1.0770330e+00 2.1307276e+00 1.0295630e+00 1.0908712e+00 1.0246951e+00 7.5498344e-01 2.2825424e+00 1.0630146e+00 1.6881943e+00 7.0000000e-01 1.5000000e+00 8.6023253e-01 1.2609520e+00 2.2781571e+00 1.4696938e+00 1.7916473e+00 1.0295630e+00 2.1118712e+00 9.0553851e-01 6.0827625e-01 1.1045361e+00 7.8740079e-01 1.0908712e+00 1.1401754e+00 8.6023253e-01 2.7166155e+00 2.5709920e+00 4.3588989e-01 1.4594520e+00 9.1104336e-01 2.3537205e+00 3.6055513e-01 1.3416408e+00 1.6124515e+00 4.4721360e-01 6.1644140e-01 9.7467943e-01 1.3711309e+00 1.7029386e+00 2.5980762e+00 1.0392305e+00 3.6055513e-01 7.4161985e-01 2.0712315e+00 1.4525839e+00 9.0553851e-01 6.6332496e-01 1.1532563e+00 1.3490738e+00 1.1832160e+00 7.0000000e-01 1.5427249e+00 1.5620499e+00 1.0677078e+00 4.1231056e-01 7.9372539e-01 1.3076697e+00 7.3484692e-01 5.1961524e-01 6.4807407e-01 7.3484692e-01 8.6023253e-01 3.8729833e-01 1.2961481e+00 1.1575837e+00 1.2489996e+00 8.6023253e-01 5.8309519e-01 8.1240384e-01 7.5498344e-01 7.3484692e-01 6.2449980e-01 8.1240384e-01 9.7467943e-01 7.0000000e-01 3.0000000e-01 7.8740079e-01 1.8601075e+00 7.2111026e-01 6.7082039e-01 6.5574385e-01 4.3588989e-01 1.9974984e+00 7.2801099e-01 1.9157244e+00 8.6602540e-01 1.8138357e+00 1.1045361e+00 1.5524175e+00 2.5903668e+00 1.3490738e+00 2.0904545e+00 1.4212670e+00 2.3452079e+00 1.0583005e+00 9.7467943e-01 1.4071247e+00 9.8994949e-01 1.3000000e+00 1.3490738e+00 1.0954451e+00 2.9257478e+00 2.9410882e+00 7.4161985e-01 1.7349352e+00 9.6436508e-01 2.6832816e+00 6.7082039e-01 1.5556349e+00 1.8493242e+00 6.1644140e-01 6.6332496e-01 1.3076697e+00 1.6186414e+00 2.0346990e+00 2.7874720e+00 1.3784049e+00 5.3851648e-01 9.4339811e-01 2.4020824e+00 1.6278821e+00 1.0862780e+00 6.4807407e-01 1.4247807e+00 1.6431677e+00 1.4491377e+00 8.6602540e-01 1.8165902e+00 1.8165902e+00 1.3638182e+00 8.4261498e-01 1.0440307e+00 1.4387495e+00 7.7459667e-01 2.6457513e-01 6.5574385e-01 8.6602540e-01 4.8989795e-01 1.1445523e+00 1.1618950e+00 1.2288206e+00 7.5498344e-01 9.6436508e-01 1.0440307e+00 7.3484692e-01 5.7445626e-01 6.1644140e-01 8.3066239e-01 1.0295630e+00 9.5916630e-01 4.4721360e-01 7.4161985e-01 1.8466185e+00 8.3066239e-01 7.2111026e-01 7.0710678e-01 2.0000000e-01 1.8920888e+00 7.3484692e-01 2.1213203e+00 1.1832160e+00 1.9235384e+00 1.3964240e+00 1.7549929e+00 2.7166155e+00 1.6155494e+00 2.2494444e+00 1.6583124e+00 2.4103942e+00 1.1090537e+00 1.1832160e+00 1.5000000e+00 1.2767145e+00 1.4899664e+00 1.4456832e+00 1.3076697e+00 3.0116441e+00 3.0886890e+00 1.0862780e+00 1.8165902e+00 1.2247449e+00 2.8195744e+00 8.1240384e-01 1.6881943e+00 1.9672316e+00 7.4161985e-01 8.4261498e-01 1.5297059e+00 1.7291616e+00 2.1470911e+00 2.8213472e+00 1.5842980e+00 8.3666003e-01 1.3711309e+00 2.4372115e+00 1.7776389e+00 1.3152946e+00 8.1853528e-01 1.4628739e+00 1.7406895e+00 1.3892444e+00 1.1832160e+00 1.9519221e+00 1.9104973e+00 1.3820275e+00 1.0099505e+00 1.1489125e+00 1.5811388e+00 1.0723805e+00 4.8989795e-01 6.7823300e-01 6.2449980e-01 1.3928388e+00 1.4212670e+00 1.4899664e+00 1.0099505e+00 9.8994949e-01 1.2083046e+00 7.5498344e-01 3.4641016e-01 7.6811457e-01 1.0488088e+00 1.2767145e+00 1.1874342e+00 5.3851648e-01 1.0000000e+00 2.1023796e+00 1.0677078e+00 9.4339811e-01 9.3273791e-01 4.3588989e-01 2.1330729e+00 9.7467943e-01 1.9874607e+00 1.2124356e+00 1.7291616e+00 1.3038405e+00 1.6155494e+00 2.5159491e+00 1.8000000e+00 2.0663978e+00 1.5427249e+00 2.1954498e+00 9.4868330e-01 1.0908712e+00 1.3190906e+00 1.3341664e+00 1.4730920e+00 1.3038405e+00 1.1747340e+00 2.7892651e+00 2.9034462e+00 1.1704700e+00 1.6217275e+00 1.2845233e+00 2.6267851e+00 7.6811457e-01 1.5099669e+00 1.7663522e+00 7.2111026e-01 8.1240384e-01 1.4177447e+00 1.5362291e+00 1.9544820e+00 2.5865034e+00 1.4696938e+00 7.9372539e-01 1.3601471e+00 2.2158520e+00 1.6401219e+00 1.1916375e+00 8.2462113e-01 1.2609520e+00 1.5684387e+00 1.1832160e+00 1.2124356e+00 1.7720045e+00 1.7320508e+00 1.2083046e+00 9.7467943e-01 1.0049876e+00 1.4594520e+00 1.0677078e+00 4.2426407e-01 8.6602540e-01 1.7606817e+00 1.7146428e+00 1.7944358e+00 1.3638182e+00 8.8317609e-01 1.4491377e+00 1.0630146e+00 3.4641016e-01 8.1853528e-01 1.4071247e+00 1.5588457e+00 1.3892444e+00 7.5498344e-01 1.3114877e+00 2.4289916e+00 1.3490738e+00 1.2845233e+00 1.2609520e+00 7.9372539e-01 2.5119713e+00 1.3076697e+00 1.7748239e+00 1.1618950e+00 1.3527749e+00 1.0295630e+00 1.3304135e+00 2.1000000e+00 1.9697716e+00 1.6340135e+00 1.1224972e+00 1.9235384e+00 8.3666003e-01 8.1853528e-01 1.0099505e+00 1.3038405e+00 1.4456832e+00 1.1747340e+00 8.8317609e-01 2.4617067e+00 2.4637370e+00 1.0246951e+00 1.3379088e+00 1.3453624e+00 2.1863211e+00 6.5574385e-01 1.2489996e+00 1.3856406e+00 7.2111026e-01 8.3666003e-01 1.1357817e+00 1.1135529e+00 1.5165751e+00 2.2649503e+00 1.2000000e+00 5.9160798e-01 1.0816654e+00 1.8303005e+00 1.5000000e+00 9.4868330e-01 9.1651514e-01 9.7467943e-01 1.3190906e+00 1.0000000e+00 1.1618950e+00 1.4764823e+00 1.5099669e+00 1.0099505e+00 7.9372539e-01 8.0622577e-01 1.3747727e+00 1.0488088e+00 8.8881944e-01 1.9748418e+00 1.8973666e+00 1.9949937e+00 1.5362291e+00 7.7459667e-01 1.4071247e+00 9.5393920e-01 3.7416574e-01 1.0816654e+00 1.4764823e+00 1.6881943e+00 1.4866069e+00 7.8102497e-01 1.4899664e+00 2.6000000e+00 1.4491377e+00 1.3747727e+00 1.3453624e+00 9.5393920e-01 2.6776856e+00 1.4177447e+00 1.3747727e+00 9.7467943e-01 1.0630146e+00 7.3484692e-01 9.6436508e-01 1.8788294e+00 1.9339080e+00 1.4387495e+00 9.4868330e-01 1.5684387e+00 4.2426407e-01 5.5677644e-01 6.4807407e-01 1.1575837e+00 1.1618950e+00 7.6157731e-01 5.4772256e-01 2.1863211e+00 2.2649503e+00 1.0816654e+00 9.6436508e-01 1.1618950e+00 2.0049938e+00 5.1961524e-01 8.6023253e-01 1.1401754e+00 5.8309519e-01 6.1644140e-01 8.0622577e-01 9.4868330e-01 1.3341664e+00 2.0322401e+00 8.6023253e-01 5.0000000e-01 9.8488578e-01 1.6031220e+00 1.0816654e+00 6.0000000e-01 7.3484692e-01 6.0827625e-01 9.2736185e-01 6.4807407e-01 9.7467943e-01 1.1045361e+00 1.1045361e+00 6.3245553e-01 6.7082039e-01 4.1231056e-01 9.6436508e-01 8.1240384e-01 1.1958261e+00 1.0723805e+00 1.1789826e+00 7.2801099e-01 6.4031242e-01 6.0827625e-01 5.0990195e-01 7.5498344e-01 7.0710678e-01 6.0827625e-01 8.3666003e-01 6.6332496e-01 2.0000000e-01 6.8556546e-01 1.7464249e+00 5.7445626e-01 5.2915026e-01 4.6904158e-01 3.4641016e-01 1.8384776e+00 5.4772256e-01 1.8708287e+00 7.7459667e-01 1.8814888e+00 1.1789826e+00 1.5620499e+00 2.7092434e+00 1.1874342e+00 2.2405357e+00 1.5588457e+00 2.3430749e+00 9.7467943e-01 1.0000000e+00 1.4177447e+00 8.6602540e-01 1.1045361e+00 1.2369317e+00 1.1618950e+00 3.0049958e+00 3.0626786e+00 8.6023253e-01 1.7262677e+00 7.6157731e-01 2.8266588e+00 6.1644140e-01 1.5652476e+00 1.9672316e+00 4.7958315e-01 5.1961524e-01 1.3190906e+00 1.7748239e+00 2.1656408e+00 2.8774989e+00 1.3674794e+00 6.7823300e-01 1.1489125e+00 2.4698178e+00 1.5362291e+00 1.1357817e+00 4.3588989e-01 1.4212670e+00 1.5968719e+00 1.3601471e+00 7.7459667e-01 1.8248288e+00 1.7578396e+00 1.2767145e+00 8.1240384e-01 1.0000000e+00 1.3190906e+00 6.8556546e-01 4.2426407e-01 3.4641016e-01 4.6904158e-01 1.7378147e+00 1.2247449e+00 1.4456832e+00 1.7146428e+00 1.1618950e+00 7.8740079e-01 6.2449980e-01 9.4339811e-01 1.3000000e+00 5.4772256e-01 7.8740079e-01 7.7459667e-01 8.3066239e-01 8.1853528e-01 1.0344080e+00 7.9372539e-01 7.0000000e-01 3.0577770e+00 1.8411953e+00 3.0149627e+00 2.3452079e+00 2.7440845e+00 3.8196859e+00 1.4628739e+00 3.3361655e+00 2.6343880e+00 3.5014283e+00 2.1354157e+00 2.1330729e+00 2.5651511e+00 1.8055470e+00 2.1377558e+00 2.4041631e+00 2.3323808e+00 4.1376322e+00 4.1533119e+00 1.6583124e+00 2.8861739e+00 1.7349352e+00 3.9089641e+00 1.7233688e+00 2.7459060e+00 3.0822070e+00 1.6186414e+00 1.7088007e+00 2.4799194e+00 2.8390139e+00 3.2403703e+00 3.9610605e+00 2.5258662e+00 1.7916473e+00 2.1748563e+00 3.5510562e+00 2.7147744e+00 2.3194827e+00 1.6062378e+00 2.5514702e+00 2.7604347e+00 2.4372115e+00 1.8411953e+00 3.0033315e+00 2.9291637e+00 2.3958297e+00 1.8520259e+00 2.1656408e+00 2.4879711e+00 1.8439089e+00 1.4142136e-01 4.4721360e-01 1.5099669e+00 1.0099505e+00 1.4106736e+00 1.7029386e+00 1.0246951e+00 7.0710678e-01 3.0000000e-01 6.4031242e-01 1.2041595e+00 4.2426407e-01 7.2111026e-01 5.4772256e-01 7.5498344e-01 7.0000000e-01 1.0148892e+00 9.0000000e-01 5.7445626e-01 2.8722813e+00 1.5842980e+00 2.8861739e+00 2.1494185e+00 2.5632011e+00 3.6891733e+00 1.1045361e+00 3.1984371e+00 2.4372115e+00 3.4029399e+00 2.0346990e+00 1.9467922e+00 2.4372115e+00 1.5165751e+00 1.9052559e+00 2.2671568e+00 2.1771541e+00 4.0521599e+00 3.9912404e+00 1.3747727e+00 2.7658633e+00 1.4798649e+00 3.7709415e+00 1.5588457e+00 2.6191602e+00 2.9765752e+00 1.4628739e+00 1.5556349e+00 2.2825424e+00 2.7386128e+00 3.1144823e+00 3.9102430e+00 2.3280893e+00 1.6278821e+00 1.9313208e+00 3.4539832e+00 2.5632011e+00 2.1633308e+00 1.4491377e+00 2.4515301e+00 2.6191602e+00 2.3622024e+00 1.5842980e+00 2.8600699e+00 2.7964263e+00 2.2803509e+00 1.6522712e+00 2.0322401e+00 2.3430749e+00 1.6431677e+00 5.0990195e-01 1.6309506e+00 1.1224972e+00 1.5000000e+00 1.7832555e+00 1.1090537e+00 7.8740079e-01 4.3588989e-01 7.5498344e-01 1.3000000e+00 5.0990195e-01 6.4807407e-01 6.6332496e-01 8.3066239e-01 7.9372539e-01 1.0908712e+00 8.1853528e-01 6.7082039e-01 2.9983329e+00 1.7175564e+00 2.9949958e+00 2.2671568e+00 2.6851443e+00 3.7934153e+00 1.2247449e+00 3.3000000e+00 2.5495098e+00 3.5128336e+00 2.1447611e+00 2.0663978e+00 2.5495098e+00 1.6552945e+00 2.0420578e+00 2.3874673e+00 2.2891046e+00 4.1521079e+00 4.1000000e+00 1.4933185e+00 2.8792360e+00 1.6155494e+00 3.8729833e+00 1.6763055e+00 2.7313001e+00 3.0757113e+00 1.5811388e+00 1.6733201e+00 2.4062419e+00 2.8319605e+00 3.2155870e+00 4.0012498e+00 2.4535688e+00 1.7349352e+00 2.0420578e+00 3.5566838e+00 2.6851443e+00 2.2759613e+00 1.5684387e+00 2.5592968e+00 2.7386128e+00 2.4698178e+00 1.7175564e+00 2.9765752e+00 2.9154759e+00 2.3958297e+00 1.7748239e+00 2.1470911e+00 2.4637370e+00 1.7663522e+00 1.2806248e+00 8.3666003e-01 1.0246951e+00 1.3038405e+00 8.1853528e-01 4.2426407e-01 3.8729833e-01 5.9160798e-01 8.4261498e-01 1.4142136e-01 1.0954451e+00 3.7416574e-01 4.3588989e-01 3.8729833e-01 6.0827625e-01 1.1618950e+00 2.6457513e-01 2.5903668e+00 1.3892444e+00 2.5670995e+00 1.8814888e+00 2.2781571e+00 3.3808283e+00 1.2083046e+00 2.9000000e+00 2.1954498e+00 3.0495901e+00 1.6792856e+00 1.6763055e+00 2.1118712e+00 1.3784049e+00 1.7000000e+00 1.9442222e+00 1.8708287e+00 3.6959437e+00 3.7188708e+00 1.2609520e+00 2.4310492e+00 1.3000000e+00 3.4785054e+00 1.2688578e+00 2.2847319e+00 2.6419690e+00 1.1575837e+00 1.2409674e+00 2.0174241e+00 2.4124676e+00 2.8106939e+00 3.5369478e+00 2.0639767e+00 1.3379088e+00 1.7406895e+00 3.1224990e+00 2.2516660e+00 1.8547237e+00 1.1401754e+00 2.1047565e+00 2.3021729e+00 2.0049938e+00 1.3892444e+00 2.5416530e+00 2.4698178e+00 1.9493589e+00 1.4106736e+00 1.7058722e+00 2.0273135e+00 1.3784049e+00 9.0553851e-01 9.2195445e-01 9.0553851e-01 9.1104336e-01 1.1575837e+00 1.2609520e+00 9.5393920e-01 6.2449980e-01 1.1916375e+00 2.1817424e+00 1.0295630e+00 1.0723805e+00 1.0148892e+00 9.0000000e-01 2.3473389e+00 1.0908712e+00 1.4387495e+00 3.6055513e-01 1.4798649e+00 6.4807407e-01 1.0908712e+00 2.2693611e+00 1.2727922e+00 1.7916473e+00 1.0295630e+00 2.0149442e+00 8.1240384e-01 5.3851648e-01 1.0677078e+00 5.4772256e-01 8.3066239e-01 9.6953597e-01 7.3484692e-01 2.6495283e+00 2.5748786e+00 5.1961524e-01 1.3820275e+00 6.0827625e-01 2.3706539e+00 4.1231056e-01 1.2083046e+00 1.5937377e+00 4.2426407e-01 4.2426407e-01 8.1853528e-01 1.4212670e+00 1.7492856e+00 2.5826343e+00 8.8317609e-01 3.3166248e-01 5.5677644e-01 2.1142375e+00 1.2124356e+00 7.2111026e-01 4.6904158e-01 1.1445523e+00 1.2409674e+00 1.2083046e+00 3.6055513e-01 1.4212670e+00 1.4212670e+00 1.0392305e+00 4.7958315e-01 7.1414284e-01 1.0535654e+00 3.7416574e-01 7.2801099e-01 1.3190906e+00 1.1618950e+00 4.8989795e-01 7.4161985e-01 5.1961524e-01 7.1414284e-01 8.1240384e-01 1.5297059e+00 5.0990195e-01 5.1961524e-01 4.7958315e-01 8.5440037e-01 1.6583124e+00 5.7445626e-01 2.0371549e+00 8.7749644e-01 2.2825424e+00 1.4560220e+00 1.8411953e+00 3.1000000e+00 7.3484692e-01 2.6362853e+00 1.9287302e+00 2.6758176e+00 1.3638182e+00 1.3747727e+00 1.8220867e+00 9.1651514e-01 1.1704700e+00 1.5231546e+00 1.5165751e+00 3.3555923e+00 3.4423829e+00 1.1180340e+00 2.0904545e+00 7.0000000e-01 3.2280025e+00 1.0723805e+00 1.8920888e+00 2.3706539e+00 9.2736185e-01 8.6023253e-01 1.6155494e+00 2.2226111e+00 2.6000000e+00 3.2787193e+00 1.6552945e+00 1.1000000e+00 1.3674794e+00 2.9137605e+00 1.7291616e+00 1.4491377e+00 7.3484692e-01 1.8520259e+00 1.9287302e+00 1.8055470e+00 8.7749644e-01 2.1447611e+00 2.0542639e+00 1.6792856e+00 1.2124356e+00 1.3964240e+00 1.5000000e+00 8.3666003e-01 7.9372539e-01 1.1832160e+00 7.5498344e-01 1.1832160e+00 1.0295630e+00 4.6904158e-01 1.0440307e+00 2.0024984e+00 9.1104336e-01 7.0710678e-01 7.2111026e-01 6.4807407e-01 2.0297783e+00 8.3666003e-01 1.7776389e+00 9.8994949e-01 1.8920888e+00 1.2609520e+00 1.5684387e+00 2.7166155e+00 1.4247807e+00 2.2847319e+00 1.7406895e+00 2.2022716e+00 9.0000000e-01 1.1747340e+00 1.4317821e+00 1.1445523e+00 1.1832160e+00 1.1532563e+00 1.2041595e+00 2.8722813e+00 3.1272992e+00 1.3038405e+00 1.6673332e+00 9.1651514e-01 2.8722813e+00 8.8317609e-01 1.4798649e+00 1.9416488e+00 7.2801099e-01 6.0827625e-01 1.4071247e+00 1.8138357e+00 2.2293497e+00 2.7459060e+00 1.4456832e+00 9.0553851e-01 1.3784049e+00 2.4698178e+00 1.3928388e+00 1.1357817e+00 5.3851648e-01 1.4000000e+00 1.5588457e+00 1.3228757e+00 9.8994949e-01 1.7691806e+00 1.6583124e+00 1.2767145e+00 1.1135529e+00 1.0295630e+00 1.1575837e+00 7.5498344e-01 9.6436508e-01 1.2727922e+00 1.5264338e+00 1.3674794e+00 6.2449980e-01 1.2806248e+00 2.3958297e+00 1.2884099e+00 1.1618950e+00 1.1532563e+00 7.0000000e-01 2.4433583e+00 1.2206556e+00 1.7000000e+00 1.1357817e+00 1.4035669e+00 1.0488088e+00 1.3228757e+00 2.1886069e+00 1.9183326e+00 1.7464249e+00 1.2884099e+00 1.8601075e+00 6.7823300e-01 8.7749644e-01 1.0099505e+00 1.3038405e+00 1.3674794e+00 1.0488088e+00 8.8317609e-01 2.4454039e+00 2.5942244e+00 1.1789826e+00 1.3000000e+00 1.2609520e+00 2.3108440e+00 6.7082039e-01 1.1832160e+00 1.4282857e+00 6.6332496e-01 7.0710678e-01 1.1618950e+00 1.2165525e+00 1.6431677e+00 2.2516660e+00 1.2165525e+00 6.4031242e-01 1.1958261e+00 1.9000000e+00 1.3674794e+00 9.0553851e-01 7.7459667e-01 9.4339811e-01 1.2727922e+00 9.1651514e-01 1.1357817e+00 1.4491377e+00 1.4282857e+00 9.4868330e-01 8.7749644e-01 7.4161985e-01 1.2124356e+00 9.4868330e-01 1.0344080e+00 9.1651514e-01 8.6023253e-01 7.6157731e-01 7.1414284e-01 1.7291616e+00 8.3066239e-01 9.4868330e-01 8.7177979e-01 6.1644140e-01 1.8654758e+00 8.3666003e-01 2.2360680e+00 1.1224972e+00 2.0049938e+00 1.4317821e+00 1.8165902e+00 2.7676705e+00 1.4730920e+00 2.2847319e+00 1.5524175e+00 2.6134269e+00 1.3527749e+00 1.1575837e+00 1.6093477e+00 1.1180340e+00 1.4832397e+00 1.6217275e+00 1.4106736e+00 3.2109189e+00 3.0495901e+00 7.0710678e-01 1.9646883e+00 1.2165525e+00 2.8266588e+00 8.1240384e-01 1.8681542e+00 2.1047565e+00 8.1853528e-01 1.0148892e+00 1.5297059e+00 1.8303005e+00 2.1702534e+00 3.0495901e+00 1.5842980e+00 8.8317609e-01 1.2569805e+00 2.5179357e+00 1.9646883e+00 1.4525839e+00 9.9498744e-01 1.6248077e+00 1.8574176e+00 1.5779734e+00 1.1224972e+00 2.0760539e+00 2.0712315e+00 1.5132746e+00 8.7177979e-01 1.2884099e+00 1.7944358e+00 1.1789826e+00 5.1961524e-01 5.1961524e-01 7.1414284e-01 4.6904158e-01 1.2569805e+00 3.1622777e-01 1.7320508e-01 1.7320508e-01 6.4031242e-01 1.3228757e+00 2.2360680e-01 2.3727621e+00 1.2206556e+00 2.4758837e+00 1.7320508e+00 2.1236761e+00 3.3000000e+00 1.0295630e+00 2.8266588e+00 2.1447611e+00 2.8913665e+00 1.5297059e+00 1.5905974e+00 2.0099751e+00 1.2489996e+00 1.5132746e+00 1.7663522e+00 1.7378147e+00 3.5524639e+00 3.6619667e+00 1.2845233e+00 2.3000000e+00 1.0816654e+00 3.4205263e+00 1.2124356e+00 2.1213203e+00 2.5416530e+00 1.0677078e+00 1.0677078e+00 1.8894444e+00 2.3537205e+00 2.7640550e+00 3.4219877e+00 1.9339080e+00 1.2529964e+00 1.6340135e+00 3.0675723e+00 2.0273135e+00 1.6911535e+00 9.4868330e-01 2.0074860e+00 2.1633308e+00 1.9235384e+00 1.2206556e+00 2.3916521e+00 2.3021729e+00 1.8493242e+00 1.3820275e+00 1.5842980e+00 1.7916473e+00 1.1575837e+00 4.2426407e-01 9.8994949e-01 3.3166248e-01 9.3273791e-01 3.0000000e-01 5.8309519e-01 4.8989795e-01 8.6023253e-01 1.0954451e+00 3.7416574e-01 2.5922963e+00 1.3038405e+00 2.6570661e+00 1.9000000e+00 2.3021729e+00 3.4727511e+00 8.7749644e-01 2.9899833e+00 2.2203603e+00 3.1543621e+00 1.7860571e+00 1.7029386e+00 2.1977261e+00 1.2369317e+00 1.6124515e+00 1.9974984e+00 1.9364917e+00 3.8249183e+00 3.7762415e+00 1.1747340e+00 2.5179357e+00 1.1832160e+00 3.5651087e+00 1.3190906e+00 2.3685439e+00 2.7622455e+00 1.2124356e+00 1.2922848e+00 2.0248457e+00 2.5436195e+00 2.9103264e+00 3.7013511e+00 2.0663978e+00 1.4071247e+00 1.7146428e+00 3.2403703e+00 2.2847319e+00 1.9157244e+00 1.1789826e+00 2.2181073e+00 2.3600847e+00 2.1283797e+00 1.3038405e+00 2.6057628e+00 2.5317978e+00 2.0322401e+00 1.4142136e+00 1.7832555e+00 2.0639767e+00 1.3674794e+00 7.7459667e-01 5.0000000e-01 1.2609520e+00 2.6457513e-01 4.8989795e-01 4.2426407e-01 7.7459667e-01 1.4628739e+00 4.2426407e-01 2.3194827e+00 1.0392305e+00 2.4041631e+00 1.5905974e+00 2.0297783e+00 3.1968735e+00 7.9372539e-01 2.7018512e+00 1.9416488e+00 2.9103264e+00 1.5779734e+00 1.4560220e+00 1.9672316e+00 1.0246951e+00 1.4352700e+00 1.7860571e+00 1.6522712e+00 3.5454196e+00 3.5071356e+00 9.2736185e-01 2.2847319e+00 9.6953597e-01 3.2878564e+00 1.1224972e+00 2.1047565e+00 2.4839485e+00 1.0246951e+00 1.0630146e+00 1.7606817e+00 2.2737634e+00 2.6514147e+00 3.4409301e+00 1.8138357e+00 1.1224972e+00 1.3564660e+00 3.0166206e+00 2.0396078e+00 1.6217275e+00 9.6436508e-01 2.0049938e+00 2.1377558e+00 1.9773720e+00 1.0392305e+00 2.3473389e+00 2.3043437e+00 1.8574176e+00 1.2247449e+00 1.5620499e+00 1.8275667e+00 1.0816654e+00 8.0622577e-01 1.8841444e+00 7.1414284e-01 6.0000000e-01 5.8309519e-01 3.4641016e-01 1.9748418e+00 6.7823300e-01 1.8165902e+00 8.2462113e-01 1.7832555e+00 1.1000000e+00 1.4966630e+00 2.5961510e+00 1.3379088e+00 2.1213203e+00 1.4866069e+00 2.2427661e+00 9.0000000e-01 9.5916630e-01 1.3379088e+00 9.6436508e-01 1.1747340e+00 1.1958261e+00 1.0630146e+00 2.8722813e+00 2.9698485e+00 9.0553851e-01 1.6431677e+00 8.6023253e-01 2.7147744e+00 6.1644140e-01 1.4662878e+00 1.8357560e+00 5.0000000e-01 5.0000000e-01 1.2727922e+00 1.6401219e+00 2.0566964e+00 2.7349589e+00 1.3304135e+00 5.8309519e-01 1.0770330e+00 2.3706539e+00 1.4832397e+00 1.0344080e+00 4.5825757e-01 1.3341664e+00 1.5394804e+00 1.3076697e+00 8.2462113e-01 1.7406895e+00 1.6941074e+00 1.2369317e+00 8.3666003e-01 9.3808315e-01 1.2727922e+00 6.7082039e-01 1.1224972e+00 3.1622777e-01 4.5825757e-01 3.8729833e-01 5.9160798e-01 1.2288206e+00 2.6457513e-01 2.5357445e+00 1.3076697e+00 2.5039968e+00 1.8055470e+00 2.2113344e+00 3.3120990e+00 1.1489125e+00 2.8266588e+00 2.1023796e+00 3.0099834e+00 1.6431677e+00 1.5968719e+00 2.0542639e+00 1.2884099e+00 1.6401219e+00 1.9026298e+00 1.8055470e+00 3.6523965e+00 3.6373067e+00 1.1357817e+00 2.3811762e+00 1.2369317e+00 3.4029399e+00 1.1958261e+00 2.2360680e+00 2.5845696e+00 1.0954451e+00 1.1916375e+00 1.9416488e+00 2.3494680e+00 2.7386128e+00 3.5000000e+00 1.9899749e+00 1.2609520e+00 1.6401219e+00 3.0643107e+00 2.2113344e+00 1.7944358e+00 1.0954451e+00 2.0566964e+00 2.2494444e+00 1.9697716e+00 1.3076697e+00 2.4859606e+00 2.4248711e+00 1.9026298e+00 1.3228757e+00 1.6522712e+00 1.9924859e+00 1.3190906e+00 1.1916375e+00 1.3527749e+00 1.3228757e+00 1.7000000e+00 3.8729833e-01 1.2124356e+00 3.4971417e+00 2.2022716e+00 3.5874782e+00 2.8248894e+00 3.2295511e+00 4.3988635e+00 1.4071247e+00 3.9102430e+00 3.1336879e+00 4.0767634e+00 2.7018512e+00 2.6324893e+00 3.1272992e+00 2.1023796e+00 2.4677925e+00 2.9086079e+00 2.8670542e+00 4.7476310e+00 4.6936127e+00 2.0371549e+00 3.4452866e+00 2.0420578e+00 4.4833024e+00 2.2472205e+00 3.2954514e+00 3.6851052e+00 2.1400935e+00 2.2135944e+00 2.9512709e+00 3.4554305e+00 3.8288379e+00 4.6119410e+00 2.9899833e+00 2.3302360e+00 2.5980762e+00 4.1605288e+00 3.1859065e+00 2.8425341e+00 2.0928450e+00 3.1416556e+00 3.2832910e+00 3.0298515e+00 2.2022716e+00 3.5355339e+00 3.4496377e+00 2.9461840e+00 2.3302360e+00 2.7110883e+00 2.9580399e+00 2.2759613e+00 3.3166248e-01 2.2360680e-01 6.4031242e-01 1.3304135e+00 1.7320508e-01 2.3515952e+00 1.1000000e+00 2.4228083e+00 1.6552945e+00 2.0663978e+00 3.2388269e+00 8.8317609e-01 2.7549955e+00 2.0149442e+00 2.9017236e+00 1.5362291e+00 1.4866069e+00 1.9646883e+00 1.0862780e+00 1.4387495e+00 1.7606817e+00 1.6852300e+00 3.5608988e+00 3.5651087e+00 1.0440307e+00 2.2781571e+00 9.9498744e-01 3.3406586e+00 1.1090537e+00 2.1118712e+00 2.5099801e+00 9.8994949e-01 1.0392305e+00 1.8027756e+00 2.3021729e+00 2.6870058e+00 3.4394767e+00 1.8493242e+00 1.1618950e+00 1.4933185e+00 3.0182777e+00 2.0371549e+00 1.6552945e+00 9.2736185e-01 1.9824228e+00 2.1307276e+00 1.9131126e+00 1.1000000e+00 2.3622024e+00 2.2934690e+00 1.8165902e+00 1.2369317e+00 1.5459625e+00 1.8138357e+00 1.1135529e+00 1.4142136e-01 5.2915026e-01 1.4352700e+00 2.4494897e-01 2.3194827e+00 1.1832160e+00 2.3790755e+00 1.6401219e+00 2.0493902e+00 3.1906112e+00 1.1090537e+00 2.7092434e+00 2.0420578e+00 2.8124722e+00 1.4594520e+00 1.5099669e+00 1.9261360e+00 1.2369317e+00 1.5165751e+00 1.7175564e+00 1.6401219e+00 3.4481879e+00 3.5580894e+00 1.2083046e+00 2.2226111e+00 1.0862780e+00 3.3060551e+00 1.1401754e+00 2.0371549e+00 2.4269322e+00 1.0049876e+00 1.0049876e+00 1.8165902e+00 2.2293497e+00 2.6514147e+00 3.3105891e+00 1.8681542e+00 1.1401754e+00 1.5231546e+00 2.9698485e+00 1.9798990e+00 1.5968719e+00 9.0000000e-01 1.9235384e+00 2.1000000e+00 1.8627936e+00 1.1832160e+00 2.3130067e+00 2.2427661e+00 1.7916473e+00 1.3190906e+00 1.5099669e+00 1.7492856e+00 1.1000000e+00 5.0990195e-01 1.4142136e+00 1.4142136e-01 2.2803509e+00 1.1045361e+00 2.3452079e+00 1.6031220e+00 2.0049938e+00 3.1654384e+00 1.0246951e+00 2.6870058e+00 1.9924859e+00 2.7910571e+00 1.4247807e+00 1.4491377e+00 1.8841444e+00 1.1357817e+00 1.4282857e+00 1.6703293e+00 1.6093477e+00 3.4452866e+00 3.5185224e+00 1.1224972e+00 2.1863211e+00 1.0000000e+00 3.2787193e+00 1.0677078e+00 2.0124612e+00 2.4145393e+00 9.3273791e-01 9.5393920e-01 1.7606817e+00 2.2158520e+00 2.6210685e+00 3.3136083e+00 1.8083141e+00 1.1045361e+00 1.4899664e+00 2.9359837e+00 1.9442222e+00 1.5716234e+00 8.4261498e-01 1.8867962e+00 2.0518285e+00 1.8138357e+00 1.1045361e+00 2.2781571e+00 2.2022716e+00 1.7349352e+00 1.2328828e+00 1.4628739e+00 1.7146428e+00 1.0535654e+00 1.7606817e+00 5.4772256e-01 2.1213203e+00 1.0954451e+00 2.0049938e+00 1.3964240e+00 1.7776389e+00 2.8106939e+00 1.4317821e+00 2.3366643e+00 1.7058722e+00 2.4839485e+00 1.1445523e+00 1.2000000e+00 1.5652476e+00 1.1789826e+00 1.4212670e+00 1.4594520e+00 1.3379088e+00 3.1032241e+00 3.1780497e+00 1.0295630e+00 1.8814888e+00 1.1045361e+00 2.9171904e+00 8.1240384e-01 1.7349352e+00 2.0566964e+00 7.1414284e-01 7.9372539e-01 1.5427249e+00 1.8303005e+00 2.2472205e+00 2.9325757e+00 1.5968719e+00 8.3666003e-01 1.3416408e+00 2.5495098e+00 1.7776389e+00 1.3304135e+00 7.4161985e-01 1.5427249e+00 1.7860571e+00 1.4730920e+00 1.0954451e+00 2.0024984e+00 1.9519221e+00 1.4387495e+00 1.0099505e+00 1.1832160e+00 1.5684387e+00 9.9498744e-01 1.3038405e+00 3.6110940e+00 2.3622024e+00 3.6959437e+00 2.9748950e+00 3.3555923e+00 4.5232732e+00 1.6278821e+00 4.0472213e+00 3.3000000e+00 4.1460825e+00 2.7694765e+00 2.7676705e+00 3.2233523e+00 2.2737634e+00 2.5845696e+00 2.9849623e+00 2.9916551e+00 4.8321838e+00 4.8394215e+00 2.2494444e+00 3.5298725e+00 2.1817424e+00 4.6206060e+00 2.3622024e+00 3.3896903e+00 3.7934153e+00 2.2427661e+00 2.3130067e+00 3.0886890e+00 3.5707142e+00 3.9534795e+00 4.6797436e+00 3.1224990e+00 2.4698178e+00 2.8035692e+00 4.2497059e+00 3.2710854e+00 2.9647934e+00 2.1886069e+00 3.2186954e+00 3.3719431e+00 3.0740852e+00 2.3622024e+00 3.6373067e+00 3.5284558e+00 3.0149627e+00 2.4657656e+00 2.8035692e+00 3.0364453e+00 2.4062419e+00 2.3790755e+00 1.1747340e+00 2.4248711e+00 1.6941074e+00 2.0928450e+00 3.2465366e+00 1.0246951e+00 2.7676705e+00 2.0566964e+00 2.8861739e+00 1.5132746e+00 1.5165751e+00 1.9621417e+00 1.1789826e+00 1.4899664e+00 1.7578396e+00 1.7000000e+00 3.5454196e+00 3.5888717e+00 1.1401754e+00 2.2715633e+00 1.0677078e+00 3.3541020e+00 1.1224972e+00 2.1095023e+00 2.5039968e+00 9.9498744e-01 1.0440307e+00 1.8384776e+00 2.2956481e+00 2.6925824e+00 3.4088121e+00 1.8841444e+00 1.1832160e+00 1.5684387e+00 3.0066593e+00 2.0445048e+00 1.6703293e+00 9.3273791e-01 1.9646883e+00 2.1330729e+00 1.8788294e+00 1.1747340e+00 2.3685439e+00 2.2912878e+00 1.8027756e+00 1.2727922e+00 1.5427249e+00 1.8165902e+00 1.1532563e+00 1.3341664e+00 9.4868330e-01 9.0000000e-01 5.0990195e-01 1.5165751e+00 2.3430749e+00 1.3190906e+00 1.1532563e+00 9.5393920e-01 1.0535654e+00 1.1045361e+00 8.6602540e-01 1.5000000e+00 1.1489125e+00 7.4161985e-01 9.3273791e-01 1.6703293e+00 1.8165902e+00 1.8165902e+00 7.0710678e-01 1.4832397e+00 1.7175564e+00 1.4352700e+00 6.4031242e-01 1.1445523e+00 1.4798649e+00 1.3527749e+00 7.6157731e-01 1.3228757e+00 1.3527749e+00 1.7944358e+00 7.1414284e-01 1.4352700e+00 1.3784049e+00 1.4491377e+00 4.2426407e-01 8.8881944e-01 1.4525839e+00 9.5916630e-01 6.0827625e-01 1.1180340e+00 1.3341664e+00 5.5677644e-01 5.0000000e-01 9.6436508e-01 1.4142136e+00 1.0099505e+00 6.4807407e-01 1.2449900e+00 1.5684387e+00 7.4161985e-01 1.0770330e+00 2.3706539e+00 1.1180340e+00 1.9339080e+00 1.1618950e+00 2.0322401e+00 8.6602540e-01 6.3245553e-01 1.1357817e+00 2.6457513e-01 5.0990195e-01 9.0000000e-01 8.6602540e-01 2.7331301e+00 2.6495283e+00 6.7823300e-01 1.4071247e+00 3.1622777e-01 2.4879711e+00 5.4772256e-01 1.2529964e+00 1.7406895e+00 5.1961524e-01 4.7958315e-01 8.1240384e-01 1.6217275e+00 1.8894444e+00 2.7055499e+00 8.4261498e-01 6.4807407e-01 7.7459667e-01 2.2045408e+00 1.1135529e+00 8.3066239e-01 4.7958315e-01 1.2247449e+00 1.2124356e+00 1.2369317e+00 0.0000000e+00 1.4317821e+00 1.3747727e+00 1.0344080e+00 5.4772256e-01 7.7459667e-01 9.4868330e-01 3.3166248e-01 9.1104336e-01 6.1644140e-01 8.6023253e-01 2.6851443e+00 5.4772256e-01 7.1414284e-01 7.5498344e-01 1.0246951e+00 9.8994949e-01 5.0000000e-01 1.7406895e+00 1.5684387e+00 9.6436508e-01 7.8102497e-01 1.2845233e+00 1.2489996e+00 1.7378147e+00 4.0000000e-01 1.8165902e+00 1.0246951e+00 1.3490738e+00 5.3851648e-01 3.8729833e-01 1.4662878e+00 1.4456832e+00 7.8740079e-01 5.1961524e-01 4.5825757e-01 1.2409674e+00 7.9372539e-01 1.2961481e+00 1.3190906e+00 6.6332496e-01 9.8994949e-01 8.6602540e-01 1.5842980e+00 5.4772256e-01 5.9160798e-01 8.5440037e-01 1.5684387e+00 4.1231056e-01 6.7082039e-01 8.3066239e-01 1.3190906e+00 9.2736185e-01 1.1224972e+00 1.4730920e+00 5.0000000e-01 1.6703293e+00 1.8275667e+00 1.2206556e+00 6.0000000e-01 1.4282857e+00 6.4807407e-01 3.8729833e-01 6.0000000e-01 9.5916630e-01 9.3273791e-01 6.6332496e-01 2.4494897e-01 2.0346990e+00 1.9974984e+00 1.0148892e+00 8.4261498e-01 1.0148892e+00 1.7944358e+00 7.2801099e-01 6.4807407e-01 1.0295630e+00 8.1240384e-01 7.3484692e-01 3.3166248e-01 9.4868330e-01 1.2165525e+00 2.0124612e+00 4.2426407e-01 5.9160798e-01 5.3851648e-01 1.5716234e+00 7.8102497e-01 2.4494897e-01 8.6023253e-01 7.2801099e-01 7.4833148e-01 9.4868330e-01 7.4161985e-01 8.2462113e-01 9.0553851e-01 7.6157731e-01 7.2801099e-01 5.0000000e-01 7.4161985e-01 6.4807407e-01 1.3638182e+00 2.1794495e+00 1.0295630e+00 6.7082039e-01 1.0148892e+00 7.5498344e-01 6.6332496e-01 4.3588989e-01 1.2529964e+00 1.0295630e+00 5.5677644e-01 5.0000000e-01 1.7000000e+00 1.6792856e+00 1.4212670e+00 4.6904158e-01 1.3038405e+00 1.5264338e+00 1.0488088e+00 3.8729833e-01 8.5440037e-01 1.1357817e+00 1.0630146e+00 3.1622777e-01 9.2195445e-01 1.0148892e+00 1.7320508e+00 3.0000000e-01 1.0295630e+00 1.0000000e+00 1.2409674e+00 5.2915026e-01 5.1961524e-01 1.1874342e+00 5.8309519e-01 3.6055513e-01 8.1853528e-01 1.0770330e+00 3.8729833e-01 4.7958315e-01 6.4031242e-01 1.0099505e+00 6.3245553e-01 6.4807407e-01 1.0049876e+00 3.4799425e+00 5.2915026e-01 1.3379088e+00 9.6436508e-01 1.8734994e+00 1.8055470e+00 1.3601471e+00 2.5357445e+00 2.3706539e+00 1.7916473e+00 1.5842980e+00 8.1853528e-01 5.4772256e-01 2.4738634e+00 1.1747340e+00 2.6343880e+00 2.6457513e-01 2.1817424e+00 1.3076697e+00 8.0622577e-01 2.3086793e+00 2.2869193e+00 1.5748016e+00 1.0246951e+00 6.0827625e-01 8.8317609e-01 1.5779734e+00 2.0832667e+00 1.9748418e+00 5.4772256e-01 1.7146428e+00 1.6583124e+00 2.4269322e+00 1.3928388e+00 1.3820275e+00 1.6703293e+00 2.3706539e+00 1.1000000e+00 1.3674794e+00 1.6763055e+00 2.1307276e+00 1.7832555e+00 1.8973666e+00 2.2869193e+00 3.0282008e+00 2.2226111e+00 3.1144823e+00 1.8708287e+00 1.7233688e+00 2.2405357e+00 9.8994949e-01 1.3228757e+00 1.9339080e+00 1.9544820e+00 3.8236109e+00 3.7376463e+00 1.2609520e+00 2.5079872e+00 9.1104336e-01 3.5860842e+00 1.4730920e+00 2.3409400e+00 2.8354894e+00 1.3711309e+00 1.3638182e+00 1.9261360e+00 2.6907248e+00 2.9899833e+00 3.7934153e+00 1.9493589e+00 1.5652476e+00 1.6583124e+00 3.3181320e+00 2.1142375e+00 1.9026298e+00 1.2489996e+00 2.3086793e+00 2.3021729e+00 2.2538855e+00 1.1180340e+00 2.5337719e+00 2.4413111e+00 2.0832667e+00 1.5000000e+00 1.8411953e+00 1.9157244e+00 1.2727922e+00 8.7749644e-01 1.0148892e+00 1.4866069e+00 1.3638182e+00 9.9498744e-01 2.1095023e+00 2.0149442e+00 1.4662878e+00 1.1357817e+00 1.1357817e+00 9.2736185e-01 1.9899749e+00 9.2736185e-01 2.2135944e+00 6.0827625e-01 1.7320508e+00 9.8488578e-01 4.3588989e-01 1.8627936e+00 1.8466185e+00 1.1832160e+00 5.5677644e-01 2.6457513e-01 1.1045361e+00 1.2124356e+00 1.5937377e+00 1.4764823e+00 6.7823300e-01 1.4491377e+00 1.2206556e+00 1.9874607e+00 1.0488088e+00 1.1180340e+00 1.3747727e+00 1.9339080e+00 8.6602540e-01 1.1704700e+00 1.3527749e+00 1.6911535e+00 1.3784049e+00 1.5874508e+00 1.8466185e+00 1.4282857e+00 1.0295630e+00 6.2449980e-01 6.6332496e-01 1.2961481e+00 1.3228757e+00 1.0392305e+00 6.1644140e-01 1.9131126e+00 1.5716234e+00 1.1445523e+00 8.8881944e-01 1.4662878e+00 1.3928388e+00 1.0049876e+00 8.6023253e-01 8.8317609e-01 1.1575837e+00 1.1916375e+00 5.5677644e-01 7.3484692e-01 8.2462113e-01 1.8788294e+00 6.1644140e-01 9.1104336e-01 7.5498344e-01 1.2609520e+00 1.1704700e+00 7.3484692e-01 1.3190906e+00 8.0622577e-01 8.7177979e-01 1.0677078e+00 1.1618950e+00 8.7177979e-01 1.0677078e+00 9.2736185e-01 9.0000000e-01 8.3066239e-01 1.2124356e+00 1.1747340e+00 1.3784049e+00 1.5652476e+00 1.0198039e+00 2.2181073e+00 1.9000000e+00 1.2165525e+00 1.3038405e+00 8.6023253e-01 1.3892444e+00 2.3685439e+00 6.7082039e-01 2.2113344e+00 1.2247449e+00 1.8841444e+00 8.1240384e-01 8.1240384e-01 1.9544820e+00 1.8708287e+00 1.3000000e+00 1.1224972e+00 1.0198039e+00 9.3273791e-01 1.2727922e+00 1.8574176e+00 1.9157244e+00 8.0622577e-01 1.0535654e+00 1.3190906e+00 1.9949937e+00 9.9498744e-01 8.7177979e-01 1.1747340e+00 2.0322401e+00 6.3245553e-01 7.0710678e-01 1.2083046e+00 1.8947295e+00 1.3820275e+00 1.2529964e+00 1.8814888e+00 5.5677644e-01 5.4772256e-01 1.0677078e+00 9.0000000e-01 3.7416574e-01 4.8989795e-01 2.0976177e+00 2.2649503e+00 1.2288206e+00 7.8102497e-01 1.0049876e+00 2.0396078e+00 6.0827625e-01 6.4807407e-01 1.1575837e+00 6.1644140e-01 5.2915026e-01 6.5574385e-01 1.0862780e+00 1.4071247e+00 2.0024984e+00 6.7823300e-01 6.7082039e-01 1.0630146e+00 1.6031220e+00 7.0000000e-01 4.6904158e-01 6.4807407e-01 5.1961524e-01 6.7823300e-01 5.0990195e-01 8.6602540e-01 9.0553851e-01 8.1240384e-01 4.2426407e-01 7.4161985e-01 2.2360680e-01 5.5677644e-01 6.6332496e-01 5.7445626e-01 7.9372539e-01 8.1240384e-01 6.4031242e-01 3.8729833e-01 2.2248595e+00 2.1023796e+00 8.1240384e-01 9.0553851e-01 9.0553851e-01 1.9157244e+00 4.2426407e-01 8.0622577e-01 1.1789826e+00 5.5677644e-01 5.9160798e-01 3.7416574e-01 1.0344080e+00 1.2845233e+00 2.1633308e+00 4.3588989e-01 4.6904158e-01 6.6332496e-01 1.6062378e+00 9.1651514e-01 4.5825757e-01 7.1414284e-01 6.7823300e-01 7.6811457e-01 7.8102497e-01 6.3245553e-01 9.6436508e-01 9.8488578e-01 5.9160798e-01 3.7416574e-01 3.4641016e-01 8.3666003e-01 6.2449980e-01 1.3114877e+00 1.1357817e+00 5.2915026e-01 4.2426407e-01 1.7029386e+00 1.7233688e+00 1.3747727e+00 3.6055513e-01 1.3601471e+00 1.5165751e+00 8.8881944e-01 3.7416574e-01 7.3484692e-01 9.8994949e-01 9.6953597e-01 4.5825757e-01 7.0710678e-01 8.9442719e-01 1.6340135e+00 4.6904158e-01 9.0000000e-01 1.0723805e+00 1.1000000e+00 7.1414284e-01 5.0990195e-01 1.1045361e+00 1.7320508e-01 3.4641016e-01 4.6904158e-01 1.1357817e+00 4.8989795e-01 5.4772256e-01 3.7416574e-01 8.8881944e-01 4.3588989e-01 7.5498344e-01 1.0295630e+00 5.1961524e-01 1.0770330e+00 1.0862780e+00 2.9359837e+00 2.7766887e+00 6.5574385e-01 1.5842980e+00 3.3166248e-01 2.6419690e+00 6.7082039e-01 1.4628739e+00 1.9442222e+00 6.4807407e-01 6.7823300e-01 9.7467943e-01 1.8165902e+00 2.0493902e+00 2.9137605e+00 9.8994949e-01 8.4261498e-01 9.4339811e-01 2.3558438e+00 1.3000000e+00 1.0677078e+00 6.4807407e-01 1.4035669e+00 1.3711309e+00 1.3784049e+00 2.6457513e-01 1.6124515e+00 1.5427249e+00 1.1747340e+00 6.0827625e-01 9.6436508e-01 1.1445523e+00 5.8309519e-01 7.5498344e-01 1.0246951e+00 2.6851443e+00 2.6267851e+00 1.1045361e+00 1.3190906e+00 4.8989795e-01 2.5159491e+00 8.1240384e-01 1.2288206e+00 1.8138357e+00 7.8102497e-01 7.2801099e-01 8.3666003e-01 1.7691806e+00 1.9519221e+00 2.6944387e+00 8.0622577e-01 1.0295630e+00 1.1747340e+00 2.1587033e+00 9.2736185e-01 9.8488578e-01 7.2801099e-01 1.2165525e+00 1.0723805e+00 1.1445523e+00 5.0990195e-01 1.3453624e+00 1.1958261e+00 9.3273791e-01 7.7459667e-01 8.3666003e-01 7.8740079e-01 6.4031242e-01 5.8309519e-01 2.0049938e+00 2.1470911e+00 1.3747727e+00 6.4031242e-01 1.0246951e+00 1.9748418e+00 8.1853528e-01 5.4772256e-01 1.1747340e+00 8.3666003e-01 7.3484692e-01 5.3851648e-01 1.1916375e+00 1.4000000e+00 1.9773720e+00 5.0990195e-01 9.2195445e-01 1.1618950e+00 1.5394804e+00 3.8729833e-01 5.4772256e-01 8.3666003e-01 5.5677644e-01 4.4721360e-01 5.4772256e-01 9.0000000e-01 7.2111026e-01 5.4772256e-01 3.7416574e-01 8.6602540e-01 3.8729833e-01 3.0000000e-01 7.6157731e-01 1.9183326e+00 1.9519221e+00 1.1090537e+00 7.0000000e-01 1.1180340e+00 1.7204651e+00 7.0000000e-01 5.0990195e-01 8.8317609e-01 7.8740079e-01 7.2111026e-01 3.8729833e-01 7.8740079e-01 1.1045361e+00 1.8574176e+00 4.6904158e-01 5.7445626e-01 7.0000000e-01 1.4317821e+00 7.5498344e-01 1.4142136e-01 8.6023253e-01 5.1961524e-01 6.4807407e-01 7.6157731e-01 8.6602540e-01 7.3484692e-01 8.1240384e-01 6.1644140e-01 7.4161985e-01 3.6055513e-01 7.1414284e-01 7.2111026e-01 1.2206556e+00 2.9715316e+00 1.4177447e+00 2.9478806e+00 1.0198039e+00 2.5632011e+00 1.5033296e+00 1.1224972e+00 2.6495283e+00 2.5690465e+00 1.9773720e+00 1.4352700e+00 1.2409674e+00 4.1231056e-01 1.9748418e+00 2.4515301e+00 2.4186773e+00 1.0049876e+00 1.8357560e+00 1.9442222e+00 2.7018512e+00 1.6822604e+00 1.6552945e+00 1.9235384e+00 2.7331301e+00 1.3490738e+00 1.5297059e+00 1.9748418e+00 2.5748786e+00 2.0904545e+00 2.0273135e+00 2.5690465e+00 2.7018512e+00 1.5620499e+00 2.9223278e+00 4.1231056e-01 2.4939928e+00 1.7233688e+00 1.2922848e+00 2.6362853e+00 2.6400758e+00 1.8601075e+00 1.4525839e+00 9.6436508e-01 1.3490738e+00 1.8520259e+00 2.4248711e+00 2.2494444e+00 8.9442719e-01 2.0736441e+00 2.0371549e+00 2.7766887e+00 1.7832555e+00 1.7175564e+00 2.0322401e+00 2.6495283e+00 1.4730920e+00 1.7233688e+00 2.0124612e+00 2.3958297e+00 2.1400935e+00 2.2671568e+00 2.6248809e+00 1.7146428e+00 8.8317609e-01 2.5278449e+00 6.6332496e-01 1.5968719e+00 1.8788294e+00 7.2801099e-01 8.6602540e-01 1.1135529e+00 1.6522712e+00 1.9209373e+00 2.8948230e+00 1.1704700e+00 6.7823300e-01 7.3484692e-01 2.3194827e+00 1.6431677e+00 1.1445523e+00 8.7749644e-01 1.4628739e+00 1.5716234e+00 1.5066519e+00 6.7823300e-01 1.7578396e+00 1.7860571e+00 1.3453624e+00 5.8309519e-01 1.0862780e+00 1.5099669e+00 8.6602540e-01 1.6062378e+00 1.3747727e+00 1.2247449e+00 3.0000000e-01 6.5574385e-01 1.3076697e+00 1.2529964e+00 6.7823300e-01 7.9372539e-01 8.5440037e-01 1.3928388e+00 6.5574385e-01 1.2328828e+00 1.3490738e+00 9.1651514e-01 6.4807407e-01 7.4161985e-01 1.3820275e+00 3.7416574e-01 2.6457513e-01 6.0827625e-01 1.4071247e+00 2.2360680e-01 3.0000000e-01 5.7445626e-01 1.2247449e+00 7.3484692e-01 7.8740079e-01 1.2845233e+00 2.7658633e+00 7.3484692e-01 1.4525839e+00 1.9924859e+00 6.4031242e-01 5.7445626e-01 1.0677078e+00 1.8894444e+00 2.1656408e+00 2.9223278e+00 1.0816654e+00 8.8317609e-01 1.0677078e+00 2.4454039e+00 1.2247449e+00 1.0630146e+00 5.0000000e-01 1.4282857e+00 1.3964240e+00 1.3820275e+00 3.1622777e-01 1.6401219e+00 1.5329710e+00 1.1958261e+00 7.7459667e-01 9.6953597e-01 1.0295630e+00 4.5825757e-01 2.2912878e+00 1.5033296e+00 9.6953597e-01 2.4289916e+00 2.4248711e+00 1.7058722e+00 1.1224972e+00 6.7823300e-01 1.0630146e+00 1.7146428e+00 2.1840330e+00 2.0420578e+00 7.0000000e-01 1.9209373e+00 1.8055470e+00 2.5651511e+00 1.5588457e+00 1.5684387e+00 1.8384776e+00 2.4879711e+00 1.3038405e+00 1.5811388e+00 1.8384776e+00 2.2248595e+00 1.9313208e+00 2.0952327e+00 2.4248711e+00 1.1180340e+00 1.5066519e+00 1.7320508e-01 3.6055513e-01 7.7459667e-01 1.3228757e+00 1.6340135e+00 2.4617067e+00 8.1853528e-01 3.7416574e-01 8.3666003e-01 1.9339080e+00 1.1575837e+00 7.2801099e-01 4.3588989e-01 9.2736185e-01 1.0816654e+00 9.0000000e-01 5.4772256e-01 1.3228757e+00 1.2845233e+00 7.6811457e-01 2.4494897e-01 5.0990195e-01 1.0000000e+00 5.3851648e-01 6.6332496e-01 1.1832160e+00 1.0862780e+00 5.9160798e-01 7.7459667e-01 9.6953597e-01 1.4798649e+00 6.0000000e-01 1.0630146e+00 1.1618950e+00 1.1357817e+00 5.1961524e-01 5.0990195e-01 1.2165525e+00 4.1231056e-01 3.7416574e-01 6.9282032e-01 1.2529964e+00 3.1622777e-01 4.0000000e-01 6.1644140e-01 1.1532563e+00 6.2449980e-01 6.2449980e-01 1.0862780e+00 1.6124515e+00 1.5684387e+00 1.0246951e+00 3.4641016e-01 4.6904158e-01 1.0246951e+00 1.0583005e+00 1.3674794e+00 1.3747727e+00 7.4161985e-01 1.1704700e+00 9.4868330e-01 1.7088007e+00 7.4161985e-01 8.8317609e-01 1.0770330e+00 1.7406895e+00 6.4807407e-01 9.1651514e-01 1.0862780e+00 1.5198684e+00 1.1000000e+00 1.2845233e+00 1.5937377e+00 2.4494897e-01 8.7749644e-01 1.4422205e+00 1.7720045e+00 2.5475478e+00 9.1651514e-01 4.3588989e-01 9.2195445e-01 2.0566964e+00 1.1704700e+00 7.8740079e-01 2.8284271e-01 1.0148892e+00 1.1575837e+00 9.5916630e-01 5.1961524e-01 1.4071247e+00 1.3416408e+00 8.3666003e-01 3.8729833e-01 5.7445626e-01 9.8488578e-01 4.6904158e-01 8.4261498e-01 1.4352700e+00 1.7832555e+00 2.4839485e+00 8.8317609e-01 4.5825757e-01 9.0000000e-01 2.0615528e+00 1.0246951e+00 6.7823300e-01 1.4142136e-01 9.9498744e-01 1.1045361e+00 9.6953597e-01 4.7958315e-01 1.3341664e+00 1.2569805e+00 8.3666003e-01 5.5677644e-01 5.3851648e-01 8.1853528e-01 2.8284271e-01 9.8488578e-01 1.1357817e+00 1.9748418e+00 1.0000000e-01 7.8740079e-01 7.8740079e-01 1.4212670e+00 6.7823300e-01 4.3588989e-01 9.6436508e-01 6.1644140e-01 5.1961524e-01 7.9372539e-01 8.1240384e-01 6.7082039e-01 7.1414284e-01 5.7445626e-01 7.0710678e-01 4.6904158e-01 6.9282032e-01 7.9372539e-01 5.0990195e-01 1.2845233e+00 1.0392305e+00 1.1618950e+00 1.2041595e+00 9.1104336e-01 1.2845233e+00 8.8317609e-01 1.5748016e+00 7.1414284e-01 9.6953597e-01 1.0392305e+00 1.6217275e+00 8.3666003e-01 1.0770330e+00 1.0488088e+00 1.3379088e+00 1.0049876e+00 1.3453624e+00 1.4899664e+00 1.1618950e+00 1.1575837e+00 1.5394804e+00 1.4933185e+00 5.3851648e-01 1.4387495e+00 1.2083046e+00 1.9235384e+00 9.3273791e-01 1.0392305e+00 1.2247449e+00 1.8894444e+00 8.4852814e-01 1.1224972e+00 1.2247449e+00 1.5842980e+00 1.2922848e+00 1.5652476e+00 1.8165902e+00 1.9824228e+00 2.3452079e+00 2.3832751e+00 9.2736185e-01 1.8761663e+00 1.8947295e+00 2.6172505e+00 1.5811388e+00 1.6522712e+00 1.8083141e+00 2.7055499e+00 1.3820275e+00 1.5588457e+00 1.9000000e+00 2.4939928e+00 2.0099751e+00 2.0346990e+00 2.5238859e+00 8.6602540e-01 8.7749644e-01 1.4106736e+00 6.4031242e-01 5.0990195e-01 1.0000000e+00 6.2449980e-01 4.6904158e-01 7.7459667e-01 8.4261498e-01 6.4807407e-01 6.6332496e-01 5.4772256e-01 7.4161985e-01 5.0000000e-01 6.7082039e-01 8.3666003e-01 5.8309519e-01 1.9078784e+00 1.1916375e+00 5.9160798e-01 5.5677644e-01 9.4868330e-01 1.1445523e+00 1.0440307e+00 6.4807407e-01 1.3000000e+00 1.3304135e+00 9.2195445e-01 5.0990195e-01 5.8309519e-01 1.0488088e+00 5.3851648e-01 1.9442222e+00 1.2961481e+00 7.1414284e-01 9.8488578e-01 1.1916375e+00 1.2688578e+00 1.3964240e+00 7.7459667e-01 1.3228757e+00 1.4387495e+00 1.2206556e+00 8.1240384e-01 9.1651514e-01 1.2247449e+00 7.8102497e-01 1.5427249e+00 1.5198684e+00 2.1977261e+00 1.0862780e+00 1.1269428e+00 1.2845233e+00 2.2045408e+00 9.4339811e-01 1.1357817e+00 1.3453624e+00 1.8920888e+00 1.5297059e+00 1.7029386e+00 2.1189620e+00 6.8556546e-01 1.1180340e+00 7.6157731e-01 5.0000000e-01 8.4261498e-01 1.1135529e+00 6.2449980e-01 4.3588989e-01 7.0000000e-01 1.1916375e+00 7.2111026e-01 2.4494897e-01 9.6436508e-01 8.1240384e-01 5.9160798e-01 6.7823300e-01 8.1240384e-01 8.3066239e-01 7.6157731e-01 8.1240384e-01 6.6332496e-01 7.9372539e-01 3.8729833e-01 6.2449980e-01 6.4807407e-01 1.1269428e+00 1.2247449e+00 1.0770330e+00 4.7958315e-01 1.4628739e+00 1.3711309e+00 9.4868330e-01 6.2449980e-01 6.7082039e-01 9.0000000e-01 3.1622777e-01 4.1231056e-01 3.6055513e-01 1.2247449e+00 5.5677644e-01 5.7445626e-01 3.6055513e-01 9.5916630e-01 4.6904158e-01 7.8740079e-01 1.0908712e+00 5.4772256e-01 1.2124356e+00 3.4641016e-01 2.4494897e-01 4.2426407e-01 1.0630146e+00 6.0827625e-01 6.2449980e-01 1.1224972e+00 1.2369317e+00 8.1240384e-01 6.9282032e-01 2.4494897e-01 9.4339811e-01 5.1961524e-01 8.1853528e-01 1.1224972e+00 1.4317821e+00 1.3747727e+00 1.0344080e+00 5.4772256e-01 7.7459667e-01 9.4868330e-01 3.3166248e-01 3.1622777e-01 7.3484692e-01 1.3076697e+00 8.4261498e-01 8.0622577e-01 1.3190906e+00 6.1644140e-01 1.2845233e+00 7.9372539e-01 6.2449980e-01 1.2569805e+00 7.8102497e-01 3.6055513e-01 6.7082039e-01 9.4868330e-01 5.8309519e-01 1.0677078e+00 6.5574385e-01 6.1644140e-01 6.4031242e-01 7.6811457e-01 diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-euclidean-ml.txt b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-euclidean-ml.txt new file mode 100644 index 0000000000000000000000000000000000000000..1b7552021bf9a0606c36628f9432e8f0afe8d765 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-euclidean-ml.txt @@ -0,0 +1 @@ + 4.0515260e+00 4.2121458e+00 3.7357405e+00 4.2313317e+00 3.9136009e+00 4.3843298e+00 3.9811426e+00 4.3624182e+00 4.0642508e+00 4.2105933e+00 4.0747226e+00 3.9068586e+00 4.1637004e+00 4.4303203e+00 4.1841564e+00 4.1063279e+00 4.1862390e+00 4.0719925e+00 4.2227579e+00 4.3173531e+00 3.8811067e+00 3.7577567e+00 4.0623722e+00 3.9882453e+00 4.0432671e+00 3.9085109e+00 4.0283414e+00 4.0846110e+00 3.6459235e+00 3.9544001e+00 4.1134244e+00 4.1805752e+00 3.5121011e+00 4.2747789e+00 4.1048323e+00 3.9269426e+00 3.8932032e+00 3.8281172e+00 3.7288430e+00 4.0863477e+00 4.1527428e+00 4.1646409e+00 4.2027433e+00 3.8441594e+00 4.8419117e+00 4.2455384e+00 3.7622220e+00 4.3967923e+00 4.4663183e+00 4.0435853e+00 4.0421692e+00 4.3124625e+00 4.6499961e+00 4.5595743e+00 3.4230430e+00 4.2612266e+00 3.5676603e+00 4.0866580e+00 4.2307103e+00 3.8521940e+00 3.9951183e+00 3.1022409e+00 3.7290193e+00 4.1931517e+00 4.1127027e+00 3.6633651e+00 4.0235815e+00 3.9729858e+00 4.1980132e+00 4.1579993e+00 3.9948955e+00 3.9081966e+00 3.9031152e+00 3.5069036e+00 4.0015727e+00 3.6763496e+00 3.6614339e+00 3.6227109e+00 3.7357992e+00 4.0170026e+00 3.5216829e+00 3.9322227e+00 3.9094621e+00 4.0170286e+00 4.3264246e+00 4.3435483e+00 4.0788635e+00 4.4761765e+00 3.8468186e+00 4.1490333e+00 4.2800007e+00 4.2260191e+00 4.3031858e+00 4.1897413e+00 4.0530244e+00 3.5893641e+00 4.2186615e+00 3.7979503e+00 4.0915473e+00 4.1343073e+00 4.5063851e+00 3.6394889e+00 4.2508448e+00 3.7160826e+00 4.0105262e+00 4.1578269e+00 4.0290590e+00 3.6971819e+00 3.9414087e+00 4.2522313e+00 4.4091714e+00 4.1542292e+00 3.9594691e+00 4.0923600e+00 4.0855497e+00 3.8253075e+00 4.3034717e+00 4.0976731e+00 4.1316523e+00 4.0872717e+00 4.2643353e+00 3.8887280e+00 3.9411273e+00 3.8848001e+00 4.3481996e+00 3.8716733e+00 3.9084684e+00 3.7546361e+00 3.9354816e+00 3.8293694e+00 3.7568515e+00 3.7184961e+00 3.8404278e+00 4.2570811e+00 4.1423777e+00 4.0291411e+00 4.2094682e+00 3.6127418e+00 4.0459839e+00 3.7737985e+00 3.7647653e+00 3.9762006e+00 3.8999512e+00 3.8509090e+00 3.8975941e+00 3.8432839e+00 4.2109046e+00 4.1339124e+00 3.5898873e+00 4.0794519e+00 4.3504966e+00 3.8862612e+00 3.8332931e+00 4.2190310e+00 4.1366595e+00 3.7220268e+00 4.1250795e+00 3.3169452e+00 4.0757181e+00 3.6487114e+00 3.9513724e+00 4.0735549e+00 3.9137880e+00 3.9656942e+00 3.7724953e+00 4.0505153e+00 3.9062302e+00 4.5671852e+00 3.7542175e+00 4.3731708e+00 3.6733907e+00 4.4667545e+00 4.1004635e+00 4.0530038e+00 4.0346958e+00 4.2145752e+00 4.4298637e+00 4.2982360e+00 4.0878239e+00 4.4061563e+00 4.2115971e+00 3.8263277e+00 3.8603258e+00 3.8572375e+00 4.1051910e+00 4.3787786e+00 4.5309659e+00 4.0047055e+00 4.1308854e+00 3.6283561e+00 diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-hamming-ml.txt b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-hamming-ml.txt new file mode 100644 index 0000000000000000000000000000000000000000..bc4e1ddcb6e1e8699570ecc410e2cb0cfdba2507 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-hamming-ml.txt @@ -0,0 +1 @@ + 4.6000000e-01 4.3000000e-01 4.3000000e-01 5.4000000e-01 4.1000000e-01 5.3000000e-01 4.3000000e-01 5.9000000e-01 4.8000000e-01 4.7000000e-01 4.6000000e-01 4.9000000e-01 4.5000000e-01 5.5000000e-01 5.3000000e-01 4.5000000e-01 4.8000000e-01 4.7000000e-01 4.8000000e-01 5.1000000e-01 4.9000000e-01 4.4000000e-01 4.9000000e-01 4.7000000e-01 4.9000000e-01 4.7000000e-01 5.2000000e-01 4.7000000e-01 4.2000000e-01 4.9000000e-01 4.7000000e-01 5.5000000e-01 3.9000000e-01 5.5000000e-01 4.6000000e-01 4.5000000e-01 4.0000000e-01 4.8000000e-01 4.5000000e-01 4.8000000e-01 4.8000000e-01 5.0000000e-01 4.8000000e-01 4.5000000e-01 6.4000000e-01 5.7000000e-01 4.6000000e-01 5.4000000e-01 5.6000000e-01 4.8000000e-01 4.8000000e-01 5.3000000e-01 5.4000000e-01 5.3000000e-01 4.5000000e-01 5.8000000e-01 4.2000000e-01 5.4000000e-01 6.0000000e-01 5.1000000e-01 4.6000000e-01 4.1000000e-01 4.4000000e-01 5.6000000e-01 5.4000000e-01 4.8000000e-01 4.8000000e-01 5.1000000e-01 5.2000000e-01 5.5000000e-01 4.5000000e-01 4.3000000e-01 4.7000000e-01 4.7000000e-01 5.6000000e-01 4.9000000e-01 4.8000000e-01 4.5000000e-01 4.9000000e-01 4.7000000e-01 4.5000000e-01 4.5000000e-01 5.6000000e-01 4.9000000e-01 5.8000000e-01 5.4000000e-01 4.6000000e-01 5.8000000e-01 5.3000000e-01 5.4000000e-01 5.5000000e-01 5.0000000e-01 5.2000000e-01 4.8000000e-01 5.0000000e-01 3.8000000e-01 5.3000000e-01 4.8000000e-01 5.1000000e-01 4.8000000e-01 5.2000000e-01 4.7000000e-01 5.0000000e-01 4.3000000e-01 4.8000000e-01 5.2000000e-01 5.0000000e-01 4.2000000e-01 4.2000000e-01 4.7000000e-01 5.4000000e-01 5.1000000e-01 5.4000000e-01 5.1000000e-01 4.8000000e-01 4.7000000e-01 5.2000000e-01 5.2000000e-01 5.4000000e-01 5.4000000e-01 5.0000000e-01 4.5000000e-01 4.4000000e-01 4.1000000e-01 5.7000000e-01 4.6000000e-01 5.1000000e-01 5.2000000e-01 5.0000000e-01 4.8000000e-01 5.0000000e-01 4.4000000e-01 5.3000000e-01 5.2000000e-01 4.9000000e-01 5.7000000e-01 5.8000000e-01 4.9000000e-01 5.1000000e-01 4.5000000e-01 5.3000000e-01 4.5000000e-01 4.4000000e-01 3.5000000e-01 4.2000000e-01 5.3000000e-01 5.2000000e-01 5.0000000e-01 3.8000000e-01 5.2000000e-01 5.6000000e-01 4.7000000e-01 4.4000000e-01 5.1000000e-01 5.7000000e-01 4.5000000e-01 5.7000000e-01 4.3000000e-01 5.1000000e-01 3.8000000e-01 5.3000000e-01 4.8000000e-01 4.4000000e-01 5.0000000e-01 4.8000000e-01 5.0000000e-01 4.7000000e-01 6.4000000e-01 4.9000000e-01 5.2000000e-01 4.8000000e-01 5.6000000e-01 4.3000000e-01 4.8000000e-01 4.7000000e-01 6.0000000e-01 5.4000000e-01 5.5000000e-01 4.0000000e-01 5.5000000e-01 5.6000000e-01 4.9000000e-01 5.0000000e-01 4.3000000e-01 5.7000000e-01 5.0000000e-01 5.7000000e-01 4.9000000e-01 4.2000000e-01 3.9000000e-01 diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-jaccard-ml.txt b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-jaccard-ml.txt new file mode 100644 index 0000000000000000000000000000000000000000..a7570d8c3fbdf63bb2240c964941d9e48bc2ad3c --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-jaccard-ml.txt @@ -0,0 +1 @@ + 6.5714286e-01 6.0563380e-01 6.3235294e-01 7.3972603e-01 6.0294118e-01 7.3611111e-01 6.4179104e-01 7.7631579e-01 6.4000000e-01 6.6197183e-01 6.6666667e-01 7.0000000e-01 6.4285714e-01 7.7464789e-01 7.1621622e-01 6.4285714e-01 6.8571429e-01 6.4383562e-01 6.6666667e-01 6.5384615e-01 6.6216216e-01 6.1971831e-01 6.5333333e-01 6.5277778e-01 6.7123288e-01 6.4383562e-01 6.5000000e-01 6.3513514e-01 6.0000000e-01 6.7123288e-01 6.3513514e-01 7.4324324e-01 5.5714286e-01 7.0512821e-01 6.3888889e-01 6.0000000e-01 5.6338028e-01 6.3157895e-01 6.0810811e-01 6.2337662e-01 6.4000000e-01 6.5789474e-01 6.3157895e-01 5.6962025e-01 7.5294118e-01 7.1250000e-01 6.2162162e-01 6.7500000e-01 7.2727273e-01 6.2337662e-01 6.2337662e-01 6.7948718e-01 6.5853659e-01 6.6250000e-01 6.3380282e-01 7.3417722e-01 6.0869565e-01 7.2000000e-01 7.5949367e-01 6.4556962e-01 6.3013699e-01 5.9420290e-01 6.2857143e-01 7.1794872e-01 7.3972603e-01 6.4864865e-01 6.4864865e-01 6.8918919e-01 6.6666667e-01 7.0512821e-01 6.2500000e-01 6.2318841e-01 6.6197183e-01 6.5277778e-01 6.9135802e-01 6.6216216e-01 6.6666667e-01 6.4285714e-01 6.6216216e-01 6.8115942e-01 6.2500000e-01 6.2500000e-01 7.3684211e-01 6.4473684e-01 7.3417722e-01 7.1052632e-01 6.3888889e-01 7.3417722e-01 6.5432099e-01 6.9230769e-01 7.1428571e-01 6.7567568e-01 6.7532468e-01 6.7605634e-01 6.5789474e-01 5.4285714e-01 6.9736842e-01 6.2337662e-01 6.6233766e-01 6.7605634e-01 7.0270270e-01 6.1842105e-01 6.7567568e-01 6.2318841e-01 6.7605634e-01 6.9333333e-01 7.1428571e-01 6.0000000e-01 6.0000000e-01 6.6197183e-01 6.9230769e-01 6.8000000e-01 7.2000000e-01 6.5384615e-01 6.5753425e-01 6.6197183e-01 7.1232877e-01 6.9333333e-01 7.5000000e-01 7.1052632e-01 6.7567568e-01 6.4285714e-01 6.0273973e-01 5.8571429e-01 6.9512195e-01 6.3013699e-01 6.8918919e-01 7.0270270e-01 6.6666667e-01 6.8571429e-01 6.6666667e-01 6.1111111e-01 7.0666667e-01 6.6666667e-01 6.5333333e-01 6.8674699e-01 7.0731707e-01 6.3636364e-01 6.3750000e-01 6.1643836e-01 6.5432099e-01 5.8441558e-01 5.8666667e-01 4.7297297e-01 5.5263158e-01 6.9736842e-01 6.9333333e-01 6.5789474e-01 5.7575758e-01 6.7532468e-01 7.0886076e-01 6.4383562e-01 5.8666667e-01 6.6233766e-01 7.5000000e-01 6.2500000e-01 7.7027027e-01 6.0563380e-01 6.8000000e-01 5.6716418e-01 6.7948718e-01 6.4864865e-01 6.1971831e-01 7.1428571e-01 6.5753425e-01 6.7567568e-01 6.6197183e-01 7.7108434e-01 6.6216216e-01 7.1232877e-01 6.4000000e-01 7.0886076e-01 6.0563380e-01 6.2337662e-01 6.2666667e-01 7.7922078e-01 7.2972973e-01 7.5342466e-01 5.7971014e-01 7.3333333e-01 7.0886076e-01 6.6216216e-01 6.4102564e-01 5.8904110e-01 7.3076923e-01 6.4102564e-01 7.1250000e-01 6.4473684e-01 5.9154930e-01 5.3424658e-01 diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-jensenshannon-ml-iris.txt b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-jensenshannon-ml-iris.txt new file mode 100644 index 0000000000000000000000000000000000000000..da698cf511e6983e1db1bbed6e2974f3480a6903 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-jensenshannon-ml-iris.txt @@ -0,0 +1 @@ +0.0211635609063 0.00455072769716 0.0230610904531 0.0076674324982 0.037571216894 0.029561354778 0.0115281186735 0.0225809070507 0.0346801442638 0.00321049176948 0.0232839774828 0.0321124517082 0.0244179197971 0.0331466156799 0.0373949302575 0.0411984503375 0.0218945865519 0.0198268474453 0.02395616278 0.0254898420418 0.0394901943037 0.0396613298853 0.0621120626322 0.0458316817045 0.0334832834948 0.0445794256135 0.00775602650151 0.00770188279153 0.0277044809709 0.0292851269343 0.0408206135002 0.0384837880405 0.0229145225178 0.0346801442638 0.017155900548 0.0186865079856 0.0346801442638 0.011778601551 0.0109570078484 0.0244803928224 0.0609237090857 0.0125274661674 0.0713079215249 0.05072181848 0.0329702193648 0.018118079192 0.0129441131729 0.00461235131171 0.0077204189151 0.190781073622 0.196235539354 0.202381067744 0.215567934651 0.208951210729 0.208395222197 0.202977772245 0.185995150024 0.197488445796 0.207754344906 0.207231776416 0.200895721783 0.202370257926 0.208839694602 0.184151316466 0.18979501874 0.211864590783 0.187854399445 0.231134943838 0.195836809821 0.220082308081 0.191451897661 0.227329222402 0.204586412278 0.192443163994 0.193684928543 0.206841791869 0.21612354251 0.209268704489 0.175345617131 0.197941218478 0.190625337098 0.191065854858 0.2317867516 0.214902945463 0.200203629275 0.200116177839 0.216845735492 0.194043462589 0.208271280018 0.210985756639 0.203659220099 0.197020978632 0.188475437149 0.205376756706 0.190989482965 0.1980172695 0.194499957306 0.176357663314 0.198492265793 0.255232551639 0.244270007892 0.240469208946 0.23837051036 0.248414598574 0.246817944636 0.244919131711 0.239985639856 0.249741998486 0.238825041235 0.224881988223 0.240479022193 0.236919216915 0.253134884385 0.257281336145 0.239143010746 0.231252341207 0.230464321684 0.26688757037 0.243702807017 0.239741931201 0.243722504108 0.250777226737 0.230838149152 0.233779458319 0.227365052742 0.226996712309 0.224529623997 0.248876761269 0.223504306637 0.241222705588 0.218359591571 0.251783757155 0.22178058433 0.237922879169 0.243259140467 0.243757719218 0.229784431814 0.223834512884 0.231417699032 0.245855002542 0.23260880661 0.244270007892 0.24412721812 0.245364556958 0.239069534798 0.242476824971 0.231950344971 0.238821966724 0.23119389821 0.0191037071088 0.0192689125135 0.0279599225292 0.0400056720024 0.0369973592153 0.0168401225107 0.0156647141811 0.0317569365649 0.0209196472407 0.0261095572505 0.0295911292198 0.0387034711929 0.047943015361 0.0502801086558 0.0505076233319 0.0276985510653 0.0178344200842 0.0354647529806 0.0132311775272 0.0439058315568 0.0587698655317 0.0548749800113 0.0420211683289 0.0158475875817 0.0410205075223 0.0159821398213 0.0151652303532 0.0238719428054 0.0190485214781 0.0351129659726 0.0545720530766 0.0439213935019 0.0317569365649 0.0241815879409 0.0259117209292 0.0317569365649 0.0173211662353 0.0138864344566 0.0338981856531 0.0429537854169 0.027658485714 0.0691172321094 0.0500059403041 0.0236011037029 0.0316644090037 0.0201837094834 0.0228863104939 0.0134861623597 0.176517909274 0.183281450302 0.188265462996 0.20119658342 0.194697326208 0.195209942657 0.190674274015 0.172425697578 0.182894065786 0.195529592383 0.192026007893 0.188346396823 0.186051479032 0.19538200246 0.171605310091 0.175766658214 0.199864266754 0.173408334646 0.215875435175 0.181358449055 0.208526316653 0.177419239992 0.212710197663 0.190468934035 0.178153770213 0.179556589614 0.19192211318 0.202468559318 0.196181327665 0.160572766746 0.183327527516 0.175701303909 0.177066663037 0.218355819513 0.203310616701 0.188843124957 0.186323930321 0.201194011774 0.18173319992 0.194510318604 0.197386404553 0.190483994401 0.182718294182 0.174255518011 0.19205782762 0.178239088983 0.18513760318 0.180597338326 0.163063102971 0.185284857996 0.243887274146 0.231624187925 0.22689829944 0.225289762668 0.235796354542 0.23269059518 0.23306609907 0.225602345189 0.235244584021 0.226892475392 0.212543999067 0.226908361887 0.223757309255 0.240296170501 0.245569279789 0.227339668618 0.2180947426 0.217889650333 0.252259964829 0.228879839341 0.227150610245 0.231836962279 0.236076260811 0.217244760533 0.221438124324 0.213671912157 0.213819376581 0.212040793299 0.235827974309 0.209053467969 0.226615210725 0.205271111175 0.238869689413 0.207939309627 0.223898822147 0.229226063436 0.232630777303 0.217026651272 0.211532427176 0.218347693285 0.233460132247 0.219891922362 0.231624187925 0.231652249658 0.233525349836 0.226377769419 0.228579062405 0.219098380821 0.227789794229 0.219023772057 0.0212386043587 0.00993825296406 0.0336976816686 0.0262325070397 0.0107568964379 0.0200399473123 0.0369530877134 0.00706121377315 0.0225180018442 0.0345620434634 0.0288649599558 0.0352792061476 0.0352526366714 0.0385518621347 0.0178346265809 0.0156087458393 0.0212893461987 0.0244762891905 0.0356848493955 0.0412118431681 0.0577499958146 0.0449060313177 0.0316682156452 0.0402876009485 0.00791017688182 0.00777689270989 0.0262859463939 0.0275691765497 0.0363841779992 0.0426229028778 0.0258430808451 0.0369530877134 0.0171494695134 0.0197038085728 0.0369530877134 0.00856221073911 0.010146504576 0.0213442662268 0.0572142712511 0.0116574543859 0.0671597340405 0.047388301579 0.0285557929122 0.0192111185981 0.0112749020632 0.00781127840012 0.00637566729419 0.187210918471 0.192623672543 0.198833727144 0.212008654042 0.205367416257 0.204958566039 0.199402528264 0.182409867249 0.193987091024 0.204155295526 0.20374075502 0.197257634781 0.198980554413 0.205364330922 0.180426094029 0.186163209648 0.208359044162 0.184479344493 0.227592539467 0.192334488284 0.216529219774 0.187806950452 0.223855994439 0.201226354807 0.188864300604 0.190066520684 0.203335651256 0.212548662023 0.205705049083 0.171742464799 0.194417377469 0.187136601809 0.187472552212 0.228344421279 0.211429948522 0.196622478767 0.196530972542 0.21335054643 0.190497911091 0.204702393397 0.207598118603 0.200155023454 0.193467129006 0.184880183147 0.201857382862 0.187517863683 0.194486736804 0.1909406597 0.172553592377 0.194927231832 0.251743344729 0.240762357546 0.236947962879 0.234942968898 0.244909063494 0.243388177909 0.24143957892 0.236617606758 0.24633077256 0.235257992461 0.221275459366 0.23695229759 0.23334624948 0.249598367646 0.253700089415 0.235542233543 0.227782636512 0.227013807453 0.263469639225 0.240293112196 0.236160757703 0.240179126335 0.247395319298 0.227257451928 0.23025606552 0.223937789122 0.223399862869 0.220967129276 0.245363860699 0.220116506797 0.237779687596 0.214888164759 0.248252329967 0.218347705592 0.234681014401 0.239690494574 0.240217402996 0.22632629355 0.220260809644 0.227813787503 0.242262375922 0.228918020841 0.240762357546 0.240592325032 0.241778783496 0.235418026861 0.23890352284 0.228365058816 0.235271978928 0.227703766766 0.0251699105319 0.0326109277538 0.029278801732 0.0117605763436 0.00495713045566 0.032971458625 0.022273270218 0.00845866705418 0.0338325850006 0.0418060793613 0.0560571569439 0.0475989809058 0.0526432986362 0.0287512950603 0.0156470261333 0.0286833299321 0.0125318521449 0.039628171731 0.0605879245284 0.051592881751 0.0247889558 0.0185457610439 0.0360813596602 0.0162897964903 0.0233731835598 0.00555113851114 0.00851608036789 0.039350861172 0.0494281040416 0.0428445793936 0.032971458625 0.0361084743768 0.0382083791018 0.032971458625 0.0135564720949 0.0133460812565 0.0359059029584 0.0525998249753 0.020448013216 0.0653401649712 0.0366222774812 0.0276779812669 0.0199994530533 0.0115734561992 0.0214333278479 0.0186461579961 0.171344593808 0.177166238774 0.182950771401 0.19643196185 0.18986301898 0.188443393542 0.18388535541 0.166562175459 0.177783941311 0.188923487579 0.187724110496 0.182156573883 0.18272910062 0.189078094755 0.165787292858 0.170722554665 0.192539814536 0.167433166664 0.21234151671 0.17610118578 0.201339136255 0.172496446246 0.207869320447 0.184234771078 0.173084677744 0.174586008872 0.187343267555 0.197067369338 0.190065322687 0.155992913972 0.178370192697 0.170834599332 0.171756811444 0.21215391799 0.195543956872 0.181382419503 0.180878423055 0.197648250997 0.174630568935 0.189049602981 0.190809942592 0.184007315234 0.177572802284 0.169158986096 0.1858235382 0.171054750208 0.178472858167 0.175024341592 0.15843173338 0.179125844986 0.236753289776 0.225336913605 0.221457751336 0.21877528886 0.229546376269 0.227338714151 0.226000082599 0.220106179064 0.230241030133 0.220420771483 0.206398010187 0.221506177578 0.218254053746 0.234550382603 0.23950257292 0.221077875617 0.211789394262 0.210958467875 0.247726054061 0.224160699687 0.221293898641 0.225242596712 0.231164838975 0.212084684776 0.21483758197 0.207582855086 0.208324546741 0.205668770285 0.22999948579 0.203519866038 0.221791807814 0.198729512666 0.233119883684 0.201955632148 0.21744774227 0.224658382044 0.22554531899 0.210282327898 0.205086194314 0.212901784527 0.227685054369 0.214992030153 0.225336913605 0.225426421062 0.227294020009 0.22122180599 0.223891772502 0.213328572484 0.220627825622 0.212027719306 0.036692370716 0.0274221847228 0.014614486903 0.0257905543192 0.038185072385 0.00854173398239 0.0228846598272 0.0362880440778 0.0247966202157 0.0338575762449 0.0341810302892 0.0403839419732 0.023192138006 0.0228332335778 0.0207802721216 0.030246984472 0.0385095591836 0.036116458124 0.063670015249 0.0456331337141 0.0387265099713 0.0454622939774 0.0128829753328 0.0153687384621 0.0290580106985 0.032571197138 0.0439484983687 0.0345946330706 0.0178789804494 0.038185072385 0.0229066226947 0.0243703411158 0.038185072385 0.0145921399018 0.0157758022112 0.0246899780508 0.0667942861058 0.00859371091119 0.0713780561444 0.0493343894065 0.0370078971529 0.0136450272229 0.0138896306008 0.00617904291603 0.0146027872105 0.19304474601 0.19797909852 0.204564547012 0.217893788171 0.211240589753 0.210085035577 0.204424468033 0.187966151726 0.199846283114 0.209201380502 0.209876418912 0.202509063074 0.205495616388 0.210676243708 0.185852822947 0.192014733821 0.213129345848 0.190059083655 0.233894763867 0.198141527364 0.221234996275 0.193689434624 0.229701750232 0.206631480423 0.194727732257 0.195938025333 0.209362001884 0.218147207364 0.211029564766 0.177869147322 0.200327033561 0.193124484677 0.193233901741 0.233604044389 0.215980508747 0.201289146742 0.20219168761 0.219726845187 0.195458087393 0.2103246028 0.212820091698 0.205396680761 0.199290845186 0.190742394929 0.207189399227 0.192521418757 0.199651552643 0.196594932336 0.178468769167 0.200295584792 0.256297768876 0.245841451519 0.242438509968 0.240033073248 0.249980802879 0.248953385462 0.246144223104 0.242178797272 0.252036489845 0.240176361939 0.226412773569 0.242454491215 0.238768549206 0.254841315698 0.258625650226 0.240494667361 0.232976534038 0.231927166971 0.269283834821 0.246140263748 0.241374087503 0.245035624054 0.253144665918 0.232868387014 0.235226383286 0.229279397026 0.228861516729 0.226064745168 0.250630665997 0.225726655668 0.243583203961 0.220042394187 0.253511231548 0.2237596548 0.239872842457 0.245497904196 0.24478307529 0.231328796826 0.225307213023 0.233256809435 0.247442229489 0.234444929147 0.245841451519 0.245661961251 0.246724980668 0.240848644418 0.244659004118 0.233675626419 0.239814102397 0.232532592403 0.010524058973 0.0347497758249 0.0310026365608 0.0636683508445 0.039628310023 0.0339732471818 0.0632599742009 0.0607622080336 0.0610552648732 0.0274939888458 0.0321794296029 0.0203038890155 0.0225566538528 0.0181521200419 0.0426282898435 0.00971744861179 0.0573638408809 0.0304112802741 0.0463628234513 0.044509344874 0.0125060810098 0.0372012029006 0.0399195029174 0.0351572922113 0.0381714857339 0.0236066809352 0.0675880085279 0.0489234884353 0.0636683508445 0.0451372647506 0.0495218266562 0.0636683508445 0.028718795999 0.0363358460459 0.0247409309981 0.0536494939568 0.0291783067693 0.0362475310016 0.0206193569389 0.0238060051702 0.0365127412376 0.0303247636874 0.0382706081182 0.036679138207 0.165252454208 0.168745392776 0.176697416201 0.190075579294 0.183193768644 0.182210296577 0.174895117306 0.159516185843 0.172780618769 0.179424300801 0.183362052205 0.17275366841 0.180884475296 0.18272692365 0.155645508044 0.163596126812 0.183814684533 0.163944686708 0.206939183794 0.170981324059 0.191238016882 0.165171506195 0.202635613449 0.180157192696 0.16688029242 0.167662828735 0.182473473654 0.189606814944 0.182144568907 0.150494922446 0.17311022882 0.166528964674 0.165043072843 0.205731384902 0.18659197245 0.17105591364 0.173799739615 0.193545885155 0.166211621507 0.181959703917 0.185636136233 0.177047042786 0.171588556623 0.162758165885 0.178831153511 0.164197394115 0.170920859204 0.168528635782 0.148460744764 0.171573948268 0.226522867074 0.216861787556 0.214094925757 0.211961277469 0.220987866614 0.22168305754 0.216782699249 0.215609692292 0.225208444508 0.210312198283 0.19661028383 0.214074476024 0.209773195915 0.225810880474 0.228551359916 0.210341321513 0.204691741777 0.203336697222 0.242485382573 0.21963599647 0.211892193047 0.215282949931 0.226705435666 0.20418234927 0.205942096193 0.201754356567 0.199749686219 0.196656401255 0.221913123953 0.199165134473 0.216656797893 0.191759529503 0.224569670715 0.196349964299 0.213992874847 0.217230566883 0.214548114667 0.2028246956 0.195690210734 0.20400781198 0.217750478797 0.204445613083 0.216861787556 0.216379424168 0.21668692278 0.211041069144 0.216249554038 0.204373397921 0.209459569208 0.203359671893 0.0284940982012 0.0283835072119 0.0582564069663 0.0317209110303 0.0290062929149 0.0575581968066 0.0518675712793 0.0525497818792 0.0214648817816 0.0289820919427 0.0151981567712 0.0194868108708 0.00772151270155 0.0396885548083 0.0133170749495 0.0480498930165 0.0406834920646 0.0454173876936 0.04374952779 0.0223043370554 0.0304932603619 0.0333669476098 0.0323541790178 0.036462611527 0.0283937271683 0.0581656862224 0.0386849486073 0.0582564069663 0.0386824403573 0.0427767064205 0.0582564069663 0.0225670168648 0.0303996112373 0.0186317385432 0.0589532282724 0.020129496567 0.0456219811369 0.0271705146906 0.0266114081755 0.0281840659315 0.0237276312035 0.0300017258072 0.0304084467621 0.174689674341 0.17830904459 0.186122916272 0.199541551099 0.192698821274 0.191439303409 0.184399484383 0.168993025372 0.182083678728 0.188980455315 0.192641208359 0.182376656778 0.189869605957 0.192034662906 0.165406381187 0.173160863593 0.193190156667 0.172952807322 0.216356837462 0.18028635421 0.200707489616 0.174765006311 0.211946253402 0.189197004074 0.176338357547 0.17720731475 0.191796191467 0.199121585044 0.191634817003 0.159923924784 0.182459504488 0.175769947329 0.174536678956 0.215011640541 0.195898920801 0.180555890973 0.183304141459 0.202828623711 0.175640593326 0.191448592698 0.194762075203 0.186406863824 0.181008054094 0.172240816201 0.188223844291 0.17346231425 0.180325575914 0.177965844821 0.158332658267 0.181046361448 0.235882031296 0.226280867303 0.2235366831 0.221221709251 0.230404936567 0.23094802366 0.226136073658 0.224736021115 0.234431734165 0.219824894986 0.20619515856 0.223526372128 0.219308937798 0.235287233784 0.23807753807 0.219908336162 0.214027462834 0.212631745032 0.251732773763 0.228838671706 0.221440978506 0.224753704594 0.235866097652 0.213724598783 0.215380278296 0.210994508636 0.209321687173 0.206162323946 0.231346876087 0.208300913946 0.225928825898 0.201080283138 0.234037288778 0.20558958438 0.222856381147 0.226751911512 0.223989137906 0.212135251626 0.205214537461 0.213595258296 0.2273163389 0.21417146415 0.226280867303 0.225846388312 0.226229092302 0.220705781832 0.225781259333 0.213928177909 0.218913775078 0.212729498479 0.0122225873329 0.0303322491715 0.0105162282326 0.0130161869455 0.0294681125528 0.031169289575 0.0445674903412 0.0424428041907 0.0471592401326 0.0242919299586 0.0151911030837 0.0250458377851 0.0159588466859 0.0393890228389 0.0501712969027 0.0573610210315 0.0347635318983 0.0244162409612 0.040396894165 0.00489128055225 0.0130158047868 0.0162739668734 0.0181749821427 0.0399100872217 0.0416234365466 0.032217222914 0.0303322491715 0.0260818708259 0.0277812891175 0.0303322491715 0.0068887146948 0.00350057866506 0.0298024545939 0.056647752597 0.0131029257559 0.0689399130874 0.0438694576111 0.0295733202179 0.0148293487362 0.005187559828 0.00994170320116 0.00857601759458 0.181761248694 0.187453410696 0.193365623784 0.206702523841 0.20011934013 0.199105672278 0.194191029325 0.176999836949 0.188302627791 0.199111466491 0.198126208998 0.192301121465 0.193121251528 0.199652739338 0.175765384319 0.180985589904 0.202947496477 0.178262931918 0.22241465847 0.18663850283 0.211489015658 0.182706386145 0.218266373994 0.195039865626 0.18346554188 0.184856636434 0.197755074291 0.207324505424 0.200404409787 0.166355990223 0.188830148216 0.181378679541 0.182125858248 0.222660838403 0.205968099039 0.191576935017 0.19121911341 0.207887116649 0.185081160877 0.199380829938 0.201554837448 0.194543615891 0.187991163757 0.179524780118 0.19631300534 0.181726234556 0.188970696545 0.185468734774 0.168191837054 0.18953883133 0.246767069406 0.235545780286 0.231684267956 0.229269594484 0.239724337717 0.237749571543 0.236212316891 0.230682790909 0.240645700931 0.230405224861 0.216420479968 0.231715542723 0.228337424754 0.244594612682 0.249193956399 0.230909382279 0.222229420235 0.221427946934 0.257965809127 0.234573740528 0.231288571496 0.235259005866 0.241607850531 0.22220070955 0.225060949621 0.218149643482 0.218415896436 0.21585883122 0.24017332069 0.214149237729 0.232167950695 0.20925074028 0.243199182069 0.21253980165 0.228281003551 0.23468620257 0.235446691383 0.22074773222 0.215228869367 0.222922571895 0.237553097068 0.224606387466 0.235545780286 0.235534393045 0.23712429287 0.230940484692 0.233917063813 0.223401305554 0.230524046304 0.222353817706 0.0346270873711 0.0222428303765 0.0130402644662 0.0348466257078 0.042829178942 0.0549568638218 0.0464424131108 0.050271964255 0.0260503884653 0.0122224116333 0.0282180922789 0.011864843965 0.0376053109772 0.060340947297 0.0487164281844 0.0285013587126 0.0163929694658 0.0334500762426 0.015996373074 0.0216948948211 0.00981357617646 0.00897169179454 0.0351677992971 0.0524329239391 0.0436506734112 0.0346270873711 0.0336931600411 0.03612022228 0.0346270873711 0.0123155461924 0.0128144174355 0.033561339537 0.0480641708164 0.0212422700005 0.0628734973132 0.0366806543871 0.0231396795589 0.0230741684915 0.0123098386274 0.0219470818919 0.0170687771133 0.170097980345 0.176050748737 0.181762587161 0.195138740624 0.188553715994 0.187634353283 0.182931238354 0.165421712546 0.176600578903 0.187912731345 0.186408453678 0.181027641681 0.181322068987 0.188146844457 0.16448627936 0.169388121003 0.191757508168 0.166524283083 0.21084441712 0.17494262847 0.200480456222 0.171127562134 0.206682857942 0.183402734812 0.171811556423 0.173255302855 0.186064001811 0.195873759085 0.189008503285 0.154586199139 0.177136590503 0.16961214015 0.170511848112 0.211247624618 0.194873193684 0.18053463387 0.179673493778 0.196193226513 0.173758955601 0.187869379999 0.19001738823 0.183074417621 0.176342969787 0.167870586049 0.184832516957 0.170273119593 0.177542554096 0.173865162824 0.156824879402 0.178069468543 0.235952767235 0.224377142544 0.220334312937 0.217941307072 0.228582134377 0.226295522057 0.225204079848 0.219149120214 0.229153457047 0.219423743811 0.20530168342 0.220370010221 0.217084867796 0.233463162138 0.238411309618 0.220004052761 0.210872881275 0.210171938201 0.24654768975 0.223020715486 0.220176358035 0.224293060856 0.230089603615 0.210845620347 0.213914309618 0.206670271948 0.207126565579 0.204661896322 0.228953899388 0.202543633745 0.220637247329 0.197849243585 0.232039444595 0.201016080923 0.216786754649 0.223327252818 0.224678101179 0.209454984614 0.204079198003 0.211687536154 0.226538202286 0.213579234366 0.224377142544 0.224425108032 0.226231350025 0.219899730556 0.222577135927 0.212192019374 0.21976273048 0.211209202307 0.0316629149872 0.033505533415 0.00599304401229 0.03147088677 0.0569379974042 0.0709823822809 0.0748230813085 0.0531369685873 0.043263968218 0.0546416139704 0.0249055105449 0.0688386171987 0.0687094353901 0.0814189949505 0.0424145094995 0.0312264925083 0.0671690041442 0.0297074253675 0.0326159908833 0.0331671559449 0.0312681130066 0.0653641254742 0.040141041955 0.0490986378456 0.0 0.0426893927085 0.0408370899258 0.0 0.0368318621027 0.029200211213 0.0578784327643 0.069522041979 0.0411420259607 0.0955605905857 0.0684916485028 0.0536985636371 0.0361283833003 0.0349236407736 0.0326544016244 0.0320790517512 0.190734279342 0.198011093099 0.202165335147 0.215193803756 0.208989236133 0.207943677959 0.205067684162 0.186855165408 0.196324830352 0.210203923195 0.205342231011 0.203427359944 0.198526873832 0.208498767129 0.187766259392 0.1906844311 0.213518747065 0.185598548292 0.229746939288 0.194805777978 0.222821572969 0.192481412892 0.225723793976 0.202375384198 0.192457483521 0.194300696695 0.205376522136 0.216660688755 0.21030774241 0.175307222553 0.197000980201 0.189066511629 0.191539232379 0.231058906578 0.21669613367 0.203523564636 0.200666530244 0.214595210264 0.195827171333 0.208641958794 0.209561099462 0.203960485003 0.196717491572 0.188766309975 0.205678014795 0.191492051166 0.198961164508 0.194672996958 0.180152450124 0.19944864987 0.257583228235 0.245178524187 0.240481186058 0.23786630337 0.249341975217 0.245210894146 0.246468005973 0.237456371003 0.247563146561 0.241300303041 0.22729752986 0.240555698528 0.237993038095 0.254193700204 0.26033321069 0.24220764546 0.231128442611 0.230799434891 0.264694157473 0.241194058993 0.241588310222 0.245985985156 0.248083056968 0.231516408669 0.235215479132 0.226198260689 0.228320459149 0.22623565919 0.249391170367 0.221152204365 0.239284874476 0.218351963925 0.252686589685 0.220530747351 0.234477098822 0.243363395844 0.246969168578 0.229990071021 0.225895881072 0.232959826282 0.248113114083 0.23570223574 0.245178524187 0.245562733947 0.248226439579 0.241702239505 0.242775171886 0.233515238499 0.242256602421 0.232453320509 0.0221776604768 0.0292076409984 0.0225953199419 0.0344078822964 0.0402556862186 0.0443490345462 0.0247422055695 0.0212582761966 0.0262237412261 0.0239779350196 0.0420497572884 0.0414593565817 0.0638943455501 0.0444256278823 0.0324613404347 0.0464717210574 0.0064847144584 0.00810593532016 0.026613709743 0.0281809240896 0.0431707040779 0.0364933096346 0.0236549105265 0.0316629149872 0.0188389154871 0.0197111271958 0.0316629149872 0.0126413659503 0.00989228745945 0.0276229976961 0.0619054176788 0.0136582341261 0.0735855911944 0.0517713161376 0.0346942423053 0.0171636471405 0.0128780143687 0.00330427938484 0.00787705209324 0.191124461218 0.196709270957 0.202716444922 0.215924187936 0.209335641351 0.208652638742 0.203456445375 0.186385899088 0.197740844714 0.208277146555 0.207460837635 0.201437849638 0.202451222155 0.209128071387 0.184787202976 0.190226937612 0.212288039878 0.187958804478 0.231460569504 0.196093364986 0.220613648389 0.191903262961 0.227580912528 0.204703238288 0.192798319471 0.194099784682 0.207098250381 0.21653588119 0.209689452484 0.175692486622 0.198220686642 0.190843207535 0.19145100341 0.232063526554 0.215320322312 0.200757345911 0.200512766736 0.217080327154 0.194471953875 0.2086592247 0.211165769014 0.203992872465 0.197349559187 0.188849659131 0.205722712353 0.191296031353 0.198394567468 0.194851113014 0.177048060287 0.198893335106 0.255751598489 0.244696550693 0.240850735029 0.238657915403 0.248846650839 0.247061872969 0.245367004129 0.240135191988 0.249950401792 0.239377278919 0.225435027987 0.240867925442 0.23738991955 0.253602763959 0.257909304325 0.239755581779 0.231578379023 0.230802663983 0.267123052444 0.243889033036 0.240266927764 0.244253437447 0.250943098487 0.231284290644 0.234232349856 0.227603903464 0.227483301065 0.225010355342 0.249291083828 0.223656027478 0.241455021635 0.21867583866 0.252237135554 0.222012069338 0.237944922778 0.2436864771 0.244348021388 0.230119265967 0.224342678419 0.231928535106 0.246422479994 0.233277995952 0.244696550693 0.244602643666 0.245964063641 0.239692017082 0.242916090829 0.232449793011 0.23942553099 0.231611684555 0.0349328180738 0.0397488289561 0.055917859907 0.0466657680267 0.0537525131124 0.0312322620487 0.0204661899168 0.0273925121636 0.0188335643607 0.0409848131484 0.0582072876054 0.056071962696 0.0229170555223 0.0261517042618 0.0397135231185 0.0177392332242 0.0260266328079 0.00803879586998 0.0153514357558 0.0448019039458 0.0436873202641 0.0393784019825 0.033505533415 0.038918396453 0.040710313775 0.033505533415 0.0158373818638 0.0159973219202 0.0374102115892 0.0608056403948 0.0178404535904 0.0681680501025 0.0368277650919 0.0342153028534 0.0136005609896 0.0114725513671 0.0202654226306 0.0215111320227 0.175502940168 0.180846739943 0.186993890383 0.200622200655 0.194047326964 0.191871659177 0.187230724864 0.170446969951 0.181958118895 0.192296146604 0.192168805538 0.185737701873 0.187567790504 0.1926934047 0.169562979332 0.174903941965 0.195627236518 0.171332536122 0.216941277953 0.180224282629 0.204375817171 0.176708588212 0.212007481629 0.187940100384 0.177271729963 0.178782860675 0.191678059195 0.200974345147 0.193702992688 0.160469688943 0.182595805427 0.175146841545 0.17584337797 0.215706304999 0.198413689274 0.184386485081 0.184856323095 0.20232915317 0.177913816132 0.192982627694 0.194328346433 0.187558388958 0.181714052911 0.173349805963 0.189464804214 0.174374123803 0.181953747446 0.178997842818 0.162709307879 0.18281187754 0.239644460978 0.228724428122 0.225246554022 0.222159074766 0.232927392687 0.231188577573 0.229024014707 0.22394884582 0.234228075319 0.223668927512 0.209861178703 0.225307285274 0.221985913786 0.238104571085 0.242774657148 0.224369518131 0.215280858443 0.214174032708 0.25182022511 0.228290074367 0.224826938646 0.228427350702 0.235193041024 0.216001866886 0.218122104545 0.211215702011 0.212099059638 0.209083204086 0.233569980649 0.207419375116 0.225880589953 0.202184861075 0.236687676647 0.205660738951 0.22091809464 0.228766193737 0.228457617824 0.2135856342 0.208453819381 0.216659972673 0.231192094033 0.218856194539 0.228724428122 0.228811270004 0.230578167797 0.224983578773 0.22794936407 0.216954366886 0.223518672063 0.215168993038 0.0278789967436 0.0521696459286 0.0689795584164 0.0720246274977 0.0510464689946 0.0421745394626 0.053582971262 0.0252834975098 0.0677158937278 0.0652657188093 0.081318384881 0.0463957610979 0.0317140261778 0.0668416555065 0.0279143378417 0.0293650428541 0.0349789935753 0.032697569141 0.0634994948437 0.0398508689254 0.0464805914619 0.00599304401229 0.0382168939676 0.0359888514026 0.00599304401229 0.0356302780251 0.0278103138868 0.0553135387293 0.0681148770417 0.0401494228711 0.0950529579724 0.0698373790182 0.0522664102967 0.0364245082526 0.034367526255 0.0307005764611 0.0296340954647 0.193320448722 0.200616479264 0.204805927895 0.217738543992 0.211505662739 0.210916031972 0.207785188245 0.189492055249 0.199011305592 0.212849559963 0.207930120156 0.205973985295 0.201080290256 0.211364107387 0.190111543422 0.193149888648 0.216396870129 0.188564248953 0.232124397575 0.197509520178 0.225563508571 0.194908404083 0.228408782968 0.205404773753 0.195016829794 0.196780548006 0.207983584103 0.219251719916 0.21299620972 0.177753842585 0.19963357851 0.19173429638 0.194103048534 0.233951642516 0.219652294406 0.206249008964 0.203257559758 0.217055047586 0.19863330849 0.211258733702 0.212590735452 0.206794766477 0.199327419787 0.191301214309 0.208462930841 0.194441169877 0.201765843334 0.197324504463 0.182225897174 0.202147597456 0.260363424846 0.247928312505 0.243139798114 0.240805098305 0.252082485905 0.248010938223 0.249316339351 0.240374954583 0.250342905364 0.243926059402 0.229857687769 0.243199557315 0.240557269117 0.256813113392 0.262812568913 0.244726906585 0.233983032632 0.233739246967 0.267372287242 0.243942332388 0.244149194945 0.248660562989 0.250891907974 0.234045856002 0.23797517084 0.229108542601 0.230855414047 0.228916694642 0.252083674667 0.224056856634 0.242001030284 0.221239323161 0.255322806495 0.223424493307 0.237685672547 0.245823282675 0.249646734812 0.232903558602 0.228555612396 0.235462898266 0.250614129287 0.237926457957 0.247928312505 0.248245352669 0.250761236826 0.244030479155 0.2452417146 0.236085335476 0.244925556651 0.235313123462 0.0296919493074 0.0549103775111 0.0590022859872 0.0450958795243 0.0436647014416 0.0451669494409 0.0401697758406 0.0620690815544 0.0404063812339 0.0856236153109 0.0591687282212 0.0490473517415 0.0683619493373 0.0275851844543 0.0264002504943 0.0446945676427 0.0461763497794 0.0638640733472 0.0232220049486 0.0233353107646 0.03147088677 0.029201781247 0.02614478673 0.03147088677 0.0350355717384 0.030428654072 0.0452764113597 0.0802967236314 0.0328354116031 0.0941098407344 0.0724718577687 0.0562014612922 0.0313029110017 0.0340889421853 0.0234757814211 0.028925036543 0.210407140423 0.216202187825 0.221880775368 0.234954371989 0.228487320903 0.227668142527 0.22288748901 0.205844703323 0.216768667446 0.227711464843 0.226263348504 0.220970493431 0.220784685064 0.228189601628 0.204608420295 0.209669658318 0.231537789973 0.206793805641 0.250139733355 0.215157813925 0.239903556316 0.211352210266 0.246303099122 0.223431243026 0.212079033799 0.213468334673 0.225996065584 0.235701634712 0.228967589943 0.195079844754 0.217288259849 0.209835756349 0.210818201018 0.25087781482 0.234509225035 0.220284749566 0.219817558162 0.235704379779 0.213924917847 0.22786813663 0.229992970272 0.223192642095 0.216540668547 0.208207378637 0.224921456698 0.210576921517 0.217728441709 0.214138769918 0.196963968339 0.21825274764 0.274633184392 0.263620373951 0.259724001387 0.257434131046 0.267733388911 0.265627371082 0.264299751314 0.258574475793 0.268388644672 0.258492710734 0.244710620006 0.259750243754 0.256426981572 0.272472244255 0.27686351493 0.258914849135 0.250487159092 0.249742739323 0.285410306304 0.262307563551 0.259345413586 0.26329314732 0.269271128247 0.250334873532 0.253300294048 0.246383898221 0.246645581278 0.244211963707 0.268141898451 0.242252606427 0.260005586654 0.237699783889 0.271104326635 0.240825446457 0.256147548483 0.26253800174 0.263413026859 0.249061635853 0.243583443282 0.251070491042 0.265469741093 0.25252673858 0.263620373951 0.263585528386 0.265041995624 0.258842865142 0.261802853748 0.251583944502 0.25855292944 0.250657339544 0.0450102465608 0.0439198537957 0.0420878714528 0.0489743500788 0.0458798154287 0.0561577170329 0.0578224624858 0.0224176386409 0.0849412968827 0.0784422731853 0.0630254855321 0.0686145922194 0.0402128784166 0.0342259139129 0.0607491308771 0.0616063147121 0.059057582132 0.0469260494382 0.0248556117621 0.0569379974042 0.0246670677105 0.0228986845775 0.0569379974042 0.0437638412118 0.0433305015411 0.0375189879884 0.0808012493057 0.0411311531308 0.089015155435 0.0787045494767 0.0568803303557 0.0468151856286 0.0457099341419 0.0358166687321 0.0382966404493 0.218181156993 0.223322073096 0.229705702275 0.242392790988 0.23577882008 0.236467641295 0.230139466345 0.213525276303 0.225126391191 0.234563959305 0.23447409507 0.227593305984 0.22968375989 0.236629811874 0.210530415308 0.216784944512 0.23930372472 0.216469414915 0.257297585894 0.22354090813 0.246798396633 0.218282233397 0.254457430072 0.233053108231 0.219729861221 0.220661425884 0.234112165808 0.242944509178 0.236406584132 0.202708452811 0.225406734885 0.218378296681 0.218342011887 0.259279663879 0.242430236301 0.227193609953 0.227261836804 0.24357213448 0.22167035443 0.235341335514 0.239265397892 0.231386700827 0.224381906232 0.215751708357 0.232947742221 0.219218926008 0.225693206986 0.221969156729 0.202171293512 0.225894654473 0.281426284779 0.270908280604 0.267090828143 0.26583132699 0.274949583689 0.273930107263 0.271660675229 0.267616788632 0.276815360824 0.264923167376 0.251110814116 0.267050687079 0.263175899022 0.279239724537 0.282486166675 0.264820551157 0.258570659963 0.257930961428 0.293412787858 0.270816835713 0.265787467516 0.269896000193 0.277962792777 0.257183140616 0.260538043514 0.255036127386 0.253324298915 0.251219664681 0.275354649609 0.251349757403 0.268235077166 0.245978035775 0.277988121804 0.249516964722 0.266387651101 0.269264891567 0.269706692396 0.257221982881 0.25041421232 0.257531332229 0.271532129624 0.257608408712 0.270908280604 0.270491687514 0.271003440471 0.264326332823 0.268486578685 0.258229704408 0.264793848318 0.258283260113 0.0140423845301 0.0233496953457 0.0357817640834 0.0194594148752 0.0561842963236 0.0207161808072 0.0344686045315 0.0532665757163 0.0656105748711 0.0611304155975 0.0376479555399 0.0422417749013 0.0418045042693 0.0514369987371 0.0551924216967 0.0370785412495 0.0618826263881 0.03552992519 0.0709823822809 0.0406476055096 0.0445130802003 0.0709823822809 0.0367370595656 0.0436433053629 0.0176515558758 0.0714383400943 0.0313405988749 0.050902359898 0.0456873150128 0.0406736463161 0.0404939048403 0.0386201712918 0.0390017962403 0.0411563720519 0.190383467974 0.193150151335 0.201647820189 0.214732592431 0.207848794505 0.207203203998 0.199017728171 0.184517441065 0.198117570692 0.203251095462 0.208611510757 0.196706601418 0.206520396425 0.207659337014 0.179547279859 0.188402996857 0.207901541524 0.18984126915 0.231436366396 0.196328732424 0.214604717337 0.189868309751 0.227504737805 0.2057488472 0.191940214233 0.192467873609 0.207646738607 0.21397919575 0.206530920079 0.175883310987 0.198349858418 0.192082967323 0.190004696209 0.230325216181 0.21052421867 0.194679583814 0.198537353627 0.218586319181 0.19070413186 0.206580728089 0.210890308648 0.201869808993 0.196665077614 0.187817228574 0.203604184545 0.189253631177 0.195640755776 0.193545341254 0.172360847921 0.196255157011 0.249220192749 0.240515221189 0.238164043403 0.236348677214 0.244540315717 0.246252369823 0.240132469075 0.240621741867 0.249888930273 0.233326376677 0.219986372028 0.238112203328 0.233455949013 0.249164983161 0.25077275089 0.233019708703 0.229068571188 0.227556818352 0.266813634827 0.244493984417 0.235169888951 0.238293435462 0.251527743788 0.228140449415 0.22959779378 0.226587756924 0.223543468869 0.220396683922 0.245620426642 0.22441324809 0.241383050303 0.216373265669 0.24802824928 0.221314439433 0.239346728325 0.241091845042 0.237050180919 0.227111645095 0.219282181885 0.227591404442 0.240665010845 0.227138628142 0.240515221189 0.239782829874 0.239319392462 0.23385508337 0.24006053231 0.227964392353 0.231962468731 0.227162089828 0.0243523838323 0.0383436294161 0.0281380877138 0.0591143980174 0.023507742642 0.0377968944731 0.0517794728194 0.0731209286425 0.0626942772583 0.0386453724111 0.0461236944312 0.0433886463071 0.057222198299 0.0591551673076 0.0319956615202 0.0701506857494 0.0422353525103 0.0748230813085 0.0387325777977 0.0428806382505 0.0748230813085 0.0409657262208 0.047385502674 0.0175421295668 0.0657295550805 0.0386908764688 0.0489968109439 0.0522686619491 0.0387311691946 0.0491554938498 0.0442532671202 0.0441014308007 0.0436449779054 0.189947462454 0.193087358051 0.201270443197 0.213957860003 0.207091706444 0.207812220084 0.199357780455 0.184445706942 0.197758177741 0.203406879958 0.207785745488 0.196588475912 0.205402521621 0.207935882191 0.179126107639 0.187763747972 0.208638518258 0.190249811145 0.22994679767 0.196055336563 0.215105618909 0.189122223025 0.226906503622 0.206245399814 0.191418910324 0.191795589288 0.206939834769 0.213506326546 0.206505332082 0.175157210084 0.197857598212 0.191658112797 0.189588115864 0.230508642351 0.211553886467 0.195374420383 0.198156887541 0.217288762795 0.191332101287 0.206198782834 0.211486527696 0.202202827305 0.19621322526 0.187298385594 0.203764973183 0.190101114406 0.196046751208 0.193325009164 0.171164264517 0.19631141813 0.249660253112 0.240527632421 0.23771162293 0.236702658605 0.244516745342 0.245937622906 0.240621381598 0.240549902948 0.249413960629 0.233305723303 0.219774348622 0.237625603888 0.232924324305 0.248768260048 0.250380844287 0.232789805762 0.22924781342 0.228117670835 0.26599273559 0.24389404773 0.234785533178 0.238373023947 0.251062908639 0.227440481837 0.229791016779 0.226764157548 0.223002288166 0.220422035353 0.245352612988 0.224391473735 0.240777024278 0.216738025139 0.247654798968 0.221447853711 0.240056634641 0.240035093698 0.237386138185 0.227558813593 0.219321439562 0.226978987712 0.240167124817 0.22597544824 0.240527632421 0.2396924683 0.239078218682 0.232902821621 0.239060496039 0.227569835839 0.232339810664 0.227659905228 0.0141262101806 0.0139426926457 0.0350538423884 0.0193552608508 0.0431621025158 0.0440916637552 0.050896637678 0.0388189182549 0.0267566278127 0.024110231253 0.0232035130335 0.0336464874245 0.0349533341335 0.0219308383764 0.0572621322316 0.0349099564127 0.0531369685873 0.0252590392656 0.0298245046475 0.0531369685873 0.0175869571119 0.0244711956848 0.00818635303924 0.0514811245358 0.0194723442375 0.0509148981601 0.038941584783 0.0196470806889 0.0301479553419 0.021545096414 0.0245644202822 0.0217515624084 0.179463055971 0.184039565703 0.191058390156 0.204133065196 0.197353411589 0.197439799103 0.1907070948 0.174350095292 0.18671934138 0.195225461177 0.19666314681 0.188285080293 0.192912863971 0.197716084636 0.17106321505 0.177948538218 0.199885832792 0.178001143095 0.220003811923 0.185034059424 0.207478284578 0.179492397587 0.21650741229 0.194560853934 0.181055185718 0.181934852801 0.196069170377 0.204292703623 0.197354097622 0.164139489747 0.187014857055 0.180129327357 0.179477085752 0.220692949894 0.202951258529 0.187497059139 0.188415486818 0.206311756974 0.182082560815 0.196599300923 0.200515380101 0.192263706104 0.185773424375 0.176983033355 0.193914579783 0.179773695055 0.186365398569 0.183085748173 0.163066216827 0.18672092116 0.242702110891 0.232274391832 0.228788679171 0.227187602483 0.236388804345 0.235969651611 0.232778101812 0.229721372697 0.23914299349 0.226115326683 0.212181006801 0.228757804803 0.224715842477 0.240932159296 0.244193462731 0.226111600887 0.219855639469 0.218968698082 0.256162056313 0.233278273993 0.227208648321 0.231128251362 0.240452766308 0.218806638317 0.221623839858 0.216518120679 0.214708469871 0.212229381986 0.23697508889 0.213254322456 0.230501952576 0.207030794754 0.239662752997 0.210997516317 0.22840539478 0.231383198724 0.230834439598 0.218316993527 0.211375460714 0.218990694003 0.233092437868 0.219339375552 0.232274391832 0.231855607362 0.232408829329 0.226038981221 0.230519762371 0.219567778072 0.225824086027 0.219230412501 0.0195919993919 0.0221059172753 0.0271326704392 0.0528905379198 0.0431586822848 0.0385447030867 0.0255010287112 0.0262337606525 0.0170321830128 0.0195302099147 0.0206521036595 0.0209833716129 0.0252645250533 0.0550811186683 0.0394746523074 0.043263968218 0.0280088992059 0.0316978756724 0.043263968218 0.00932281475944 0.0154549635915 0.0220221996956 0.04619442006 0.0176558634743 0.0549187053198 0.0347531723104 0.0154010655624 0.0249961358537 0.0130624709681 0.021086975526 0.0157582275316 0.172494143831 0.17784850126 0.184169405165 0.197413015598 0.190717679771 0.190383314088 0.184688114733 0.167622718241 0.179385018024 0.189454833136 0.189254205537 0.182497405034 0.184783379021 0.190756581821 0.165553322788 0.171383967321 0.193742345805 0.169993474917 0.213189630746 0.17771520654 0.201947261672 0.173029090008 0.209395719039 0.186777092552 0.174149245532 0.175318744446 0.188797436083 0.197902799323 0.191019058185 0.156992121883 0.179797341784 0.172560525004 0.172720884671 0.213854773981 0.196871318612 0.181938976962 0.181811193063 0.198969640765 0.17576606357 0.19002752539 0.193102069722 0.185493626505 0.178790364297 0.170132074883 0.187191891105 0.172843021691 0.179777298661 0.176222723299 0.157641466463 0.180195116525 0.237394679003 0.226280310947 0.222447934209 0.220486140395 0.230450693082 0.229006895923 0.227001818993 0.222289337207 0.232008422548 0.22075701885 0.20666547313 0.222449960987 0.218801277251 0.235156691974 0.239347508119 0.221054539901 0.213261323555 0.212504634094 0.249237472588 0.225979854618 0.221636903122 0.225713392695 0.233125083698 0.212685206963 0.215719410439 0.209455227207 0.208792490533 0.206361366722 0.230902205737 0.20571314342 0.223403407555 0.200311655198 0.233802587518 0.20384593219 0.22048704521 0.225216247618 0.225812871456 0.211806271505 0.205652934938 0.213231447371 0.227786843309 0.214377387143 0.226280310947 0.226104307704 0.22732908173 0.220904746241 0.224415757753 0.21378854989 0.220845147853 0.213178189936 0.0385029210536 0.0191331286834 0.0412210559221 0.0479132957057 0.0465498029846 0.0439952648454 0.0294155998226 0.0263022819525 0.0288814535105 0.0321813612841 0.0366154046916 0.0324760763417 0.0515944791638 0.0310049526439 0.0546416139704 0.0338597056505 0.0376153728239 0.0546416139704 0.019563854062 0.0269885058553 0.0157505744896 0.0625368339241 0.0143844790412 0.0526157254036 0.0338348827289 0.0297234721938 0.0233631827985 0.0204547357021 0.0243203298193 0.026443786847 0.18161798851 0.18540629351 0.193056292253 0.206460086953 0.199649422865 0.198358533748 0.191516052491 0.176005251549 0.188914967022 0.196119500233 0.199392563722 0.189524653623 0.196344377596 0.19896940691 0.172616127427 0.180167039066 0.200264745011 0.179650753185 0.223145799502 0.187130729579 0.207841346365 0.181781654149 0.218763807382 0.195938920487 0.183272877978 0.184194077304 0.198599402775 0.206127033545 0.198682577368 0.166796826532 0.189310437328 0.182538488189 0.181515358431 0.221921151125 0.202966250725 0.187732725258 0.190303161279 0.209524247949 0.182729260129 0.198439370358 0.201593038612 0.193392410161 0.187922687561 0.179193994752 0.195211914017 0.180441261226 0.187358375156 0.184927566912 0.165517695891 0.188083050798 0.242939707589 0.233299699182 0.23049760933 0.228144979761 0.237419943564 0.237777043946 0.233178399857 0.23147925362 0.241203272786 0.226929567318 0.213318762235 0.230491320835 0.22634238089 0.242302330141 0.245163744774 0.227035678455 0.220985219764 0.21961554026 0.258484704802 0.235572463293 0.228516087928 0.231839786454 0.242586485114 0.220732170317 0.222439032331 0.217865646283 0.216378419732 0.213247698263 0.238336887242 0.215052759841 0.232719204269 0.208046041848 0.241045913549 0.212450914632 0.229495311164 0.23369764439 0.231101958719 0.219113353825 0.212320151032 0.220660185426 0.234399345217 0.221293359798 0.233299699182 0.23289508713 0.233336932661 0.227805970609 0.232744111879 0.220997609023 0.226042688917 0.219773770977 0.0487507947542 0.0650775470184 0.057802762893 0.0304021403296 0.00977564963899 0.0440829229362 0.0178656109143 0.0223657443481 0.0145412613726 0.00880579384939 0.0433599813023 0.0513293545711 0.0472054655688 0.0249055105449 0.0346274013058 0.0356855998153 0.0249055105449 0.0197624673252 0.0147740519949 0.0419438715965 0.0491548979957 0.0286642180871 0.0731736765881 0.0474587573553 0.0307413987988 0.0281173399333 0.0193986394444 0.0247050709585 0.0190246608336 0.173083486457 0.179910481276 0.184747521108 0.19795391577 0.191518465927 0.190900207622 0.18706914222 0.168885152715 0.179192090216 0.192134059909 0.188561922702 0.185169221179 0.182516578227 0.191341288573 0.168884190059 0.172666616329 0.195883736852 0.16891577804 0.213018522015 0.177613800133 0.204946600226 0.174427531668 0.209108112252 0.18591519295 0.174796071764 0.17642769541 0.188436163643 0.199161967465 0.19263437332 0.157387090389 0.179769711315 0.171986018804 0.173714059835 0.214316704261 0.199152104404 0.185242989169 0.182953007753 0.198045578814 0.177854485907 0.191103831493 0.192902410163 0.18652468984 0.179244739552 0.170950373519 0.188227253613 0.173938849831 0.181251003885 0.177034455953 0.160977434421 0.181661357468 0.240293898195 0.228034626835 0.223480275138 0.221169492968 0.232239647508 0.228872448909 0.229260803782 0.221433214134 0.231450162781 0.223643381382 0.209415344527 0.223528489107 0.220601809879 0.237045012584 0.242726403737 0.224366911478 0.21417342735 0.213777217999 0.24873924373 0.225124576112 0.224039797111 0.228474279829 0.232185938187 0.214138987375 0.217816173262 0.209554716543 0.210717178803 0.208592377005 0.232371287988 0.204873194809 0.222962175481 0.201230387345 0.23556954796 0.203837920113 0.218977104053 0.226289614933 0.229286064361 0.212971252918 0.208146125074 0.215343203422 0.230520772996 0.217614435398 0.228034626835 0.228243837858 0.230522424537 0.223850190554 0.225634530741 0.215935214332 0.224460188266 0.215143400901 0.0529022439667 0.0328752197893 0.0555054115849 0.0509289255649 0.0173017354019 0.040824127775 0.0418711547682 0.0429127577978 0.0455543968368 0.0222834470095 0.0700549868865 0.0480571802481 0.0688386171987 0.0442386440219 0.048830469586 0.0688386171987 0.0328274300443 0.0405886029945 0.0207406620607 0.0561033512724 0.0322786585229 0.0341942563123 0.0291137581017 0.0267606033099 0.0411628160219 0.0351933008888 0.0409598634365 0.0395653722748 0.170381496838 0.173513369262 0.181763628401 0.194913312213 0.188005094963 0.187597093173 0.17963993246 0.164619720579 0.178109956535 0.183964962678 0.188586214824 0.177259498897 0.186421186364 0.18797705999 0.159970598511 0.168451386355 0.188698976448 0.169860506279 0.211615177079 0.176329007245 0.195682012245 0.169937526308 0.20770892717 0.185927394166 0.171947667368 0.172526710735 0.187654893417 0.194331077481 0.186965121806 0.155693053512 0.178340485408 0.171986662107 0.170058435784 0.210831670623 0.191498228781 0.175622787806 0.178712133706 0.198589692402 0.171227708945 0.186830515333 0.191211132234 0.182219297429 0.176703062328 0.167807776518 0.183929379926 0.16962686739 0.17602523241 0.173642929989 0.152568053554 0.176555503125 0.230732284086 0.221405569072 0.21873883425 0.217008773205 0.22548132942 0.226701619289 0.221306518291 0.22096326048 0.23027897199 0.21448417882 0.200865540743 0.218691208961 0.214158679542 0.230111756153 0.232289297369 0.214283536851 0.209653108975 0.208315017711 0.24732302577 0.224778673487 0.216104443077 0.219500448511 0.231884032027 0.208661452368 0.210499772687 0.206990362059 0.20415679031 0.201176497026 0.226427503413 0.204626996142 0.221691815993 0.19686908479 0.228928610762 0.20163871009 0.219806895143 0.22162291443 0.218587303851 0.207804967644 0.200129766869 0.208295752101 0.221764477978 0.208127749312 0.221405569072 0.220756657587 0.220625985458 0.214861357139 0.220620889076 0.208723632287 0.213494112425 0.208081504586 0.0844496882978 0.080315980567 0.0726849367116 0.0675030089859 0.0473153931529 0.0443066374773 0.0646752376508 0.0680246382237 0.0621370759065 0.0478849585017 0.0214355468841 0.0687094353901 0.0384979333887 0.0389190825118 0.0687094353901 0.0480894783324 0.0504544201599 0.0366660205236 0.0908834253131 0.0410486802894 0.0843383154277 0.0744888741312 0.0620377042787 0.0466247584157 0.0490662247741 0.0410809318858 0.0466044522535 0.220241217585 0.22387872958 0.231567441105 0.244549509651 0.237801597966 0.237271702597 0.229957304486 0.214803305719 0.2275731622 0.234289966045 0.237659204867 0.227701989162 0.234344570768 0.237727323575 0.210730852647 0.218580436475 0.238765139748 0.218842181584 0.260585713889 0.225852980679 0.24576980875 0.220083510611 0.25691340974 0.234991633089 0.221816502308 0.222561867727 0.236939418607 0.244255467135 0.237073335368 0.205416544089 0.227870869354 0.221241762596 0.220094090971 0.260317043933 0.241447551617 0.226019474356 0.228752673734 0.247333244315 0.221529692284 0.236780633698 0.24056209021 0.232170242771 0.226474325634 0.217767662464 0.233885787279 0.219595234935 0.226160651319 0.223600212873 0.203287025744 0.22672247966 0.280139937217 0.271046573965 0.268296536378 0.26646171863 0.275059767728 0.275799812567 0.270911764394 0.269815722172 0.27914751079 0.264301608642 0.250949666543 0.26825814336 0.263926860434 0.279625941989 0.2816892989 0.264092216423 0.259287884013 0.257990452056 0.295945646215 0.273550874886 0.265902495939 0.269211685027 0.28055869892 0.258431707188 0.260338655828 0.256388822593 0.254096852299 0.25120005211 0.275959599762 0.25362897924 0.27069009133 0.246633770083 0.278453866291 0.251048870754 0.268244717954 0.271056483898 0.268212813274 0.257487053623 0.250194192056 0.258191856723 0.27145388403 0.257986657626 0.271046573965 0.2704506544 0.270310564507 0.264635332559 0.270115829289 0.258629632749 0.263204545545 0.257890609764 0.0600044713459 0.054351041098 0.0187381084361 0.0598557442646 0.0614469307754 0.0531453686192 0.0525559397425 0.0279315026787 0.094761292833 0.0773971393828 0.0814189949505 0.0655620569523 0.0700255275487 0.0814189949505 0.0520201647503 0.0579117136152 0.0488956649362 0.0419109171187 0.0567137285928 0.0208614229037 0.0306610868801 0.0327090237565 0.063272034339 0.0543984191577 0.0634480421324 0.0583685669148 0.139330779418 0.142986837848 0.150850053523 0.163810831232 0.156868051014 0.157669598862 0.149679456655 0.133871337528 0.147188409072 0.153968295801 0.157417010427 0.14689076955 0.155379198936 0.157713790383 0.129254210759 0.137253284329 0.159227064857 0.139693977567 0.180206197156 0.145463442392 0.166351059616 0.138682374547 0.176816693259 0.155870230339 0.140840856643 0.141328264666 0.156536274226 0.163539791349 0.156545980147 0.124345584519 0.147307265103 0.141019689746 0.139034333305 0.180682036747 0.1624502749 0.146314826392 0.147811298228 0.167221256708 0.141400345074 0.156004354912 0.161263479124 0.152006654871 0.145684418167 0.136675034398 0.153561404536 0.139874017863 0.145889334783 0.142806737922 0.121109447078 0.146069682444 0.201853472765 0.191504661606 0.188213817168 0.18712354961 0.1956147304 0.196211110416 0.19205227926 0.19059963575 0.199675257584 0.184878502918 0.170804202645 0.188147863911 0.18370042204 0.200019806255 0.20290821197 0.184684396544 0.179556147674 0.178663646288 0.216648538359 0.194032671841 0.185998827234 0.190015746161 0.201299319844 0.177907547001 0.180731098286 0.176760615884 0.17358596254 0.171156072474 0.196260390732 0.174185108777 0.190914661822 0.166819613367 0.198798087344 0.171315193398 0.190200635461 0.190703604042 0.189694426068 0.178004531696 0.170196606131 0.177783458136 0.19179082566 0.177644952 0.191504661606 0.190881968726 0.191068709685 0.184514101166 0.189756786146 0.178407147108 0.184631515769 0.178548671216 0.0324892056253 0.0484887435869 0.0393579503362 0.0472627188893 0.0192847169709 0.0232472318507 0.0583393494412 0.0585791036352 0.0611786752117 0.0424145094995 0.0604358052165 0.0620651155443 0.0424145094995 0.037432002363 0.036885144463 0.057932237922 0.0663347563067 0.0402054510934 0.0745837939532 0.0379481013657 0.0474009418109 0.0343119409392 0.0339409730754 0.0428079642087 0.0427340086613 0.16139435599 0.167055199688 0.172679145019 0.186588672878 0.18018023169 0.176484226078 0.173177363378 0.15635152751 0.167324305619 0.178559723881 0.177731707828 0.172313382954 0.172981201837 0.177656055027 0.157022451257 0.161367937512 0.181005231952 0.155624650937 0.203264920982 0.165557822324 0.190457367134 0.163325089652 0.197312648791 0.172046353812 0.163261037864 0.165144860989 0.177247951111 0.186978615056 0.179504291425 0.146841839918 0.168174392768 0.160512412099 0.161906209121 0.200546381048 0.18360708151 0.170619997941 0.170849280459 0.188248015891 0.163499893332 0.178863271671 0.178580924982 0.172741578769 0.16748001244 0.159472149167 0.174802823761 0.159227769495 0.167358477359 0.164720514491 0.151099028747 0.168548224145 0.225620984689 0.214383995774 0.210968404368 0.206923044041 0.218629792329 0.216162853062 0.214556733684 0.208383921822 0.219163632298 0.210075568948 0.196412311391 0.211088115641 0.208225582965 0.224211015109 0.229824216763 0.21125046128 0.200361910494 0.199102744226 0.237092122358 0.213257676298 0.21126821314 0.214648561609 0.219950980033 0.202248505777 0.203853239286 0.195906673599 0.198467217831 0.195092454721 0.219319110187 0.191906900165 0.211043854216 0.187166023617 0.222703227226 0.190377176965 0.20432458647 0.215124925944 0.214931156714 0.198560742198 0.194613360236 0.203185148475 0.217932610212 0.206623397839 0.214383995774 0.214797954846 0.217376030313 0.212326968407 0.214327312553 0.203281369316 0.21006441216 0.200554878155 0.0431984510448 0.0262542814372 0.0292765976694 0.0197548242369 0.011556553192 0.0420248516295 0.0609848282508 0.0559281666693 0.0312264925083 0.0398958448421 0.0413044920489 0.0312264925083 0.0264369738429 0.0230439739916 0.0462734733613 0.0414691510708 0.0363662025945 0.071550533242 0.0478849716213 0.0291862789915 0.0369373377392 0.0270714127462 0.0334218260536 0.0262390277171 0.165544038214 0.172891912863 0.17727312869 0.190354892388 0.183958646909 0.183879480011 0.180336742003 0.161635841791 0.171548979047 0.185413091762 0.180663093483 0.178298498301 0.174219977581 0.184181187608 0.161990271375 0.165191063063 0.189309277337 0.161423020955 0.205054842396 0.170020243784 0.198514733988 0.166940816324 0.201451973118 0.178556962169 0.167238778058 0.168913749918 0.180647428918 0.191850429195 0.185573186785 0.149626920851 0.172104358108 0.164216849771 0.166272040954 0.207167511585 0.192763981398 0.178906141081 0.175593499508 0.189955336349 0.171151190119 0.183759195847 0.185737635433 0.179470325207 0.171692649892 0.163416602952 0.18110112813 0.167100843514 0.174311352225 0.169649991905 0.153736698586 0.174561747041 0.233945473753 0.221154951746 0.216216020369 0.214168470951 0.225368727897 0.22143399553 0.222723035108 0.213934293134 0.223870685298 0.217024022208 0.202620827758 0.216260896263 0.213476245827 0.230060267172 0.236168299839 0.217779153489 0.207119858677 0.206977473585 0.241093686538 0.217416199365 0.217140656671 0.221883614074 0.224541883596 0.206836488872 0.211062122527 0.202310625247 0.203579796415 0.201763166599 0.225319290313 0.197343747333 0.215321920645 0.194216980733 0.228558415828 0.196533366942 0.211711953148 0.218804815465 0.22300715182 0.206091661798 0.201387938181 0.208241563837 0.223688965104 0.210605474313 0.221154951746 0.221416959944 0.223922313478 0.216868041009 0.218202757079 0.208934179216 0.218220121204 0.208484033719 0.0428988991781 0.0449811252912 0.038369173012 0.0392012066073 0.0184411809504 0.0770158611592 0.0591685049971 0.0671690041442 0.0499388239897 0.0544215157144 0.0671690041442 0.0346140690794 0.0413101969584 0.0321994698404 0.0440967531043 0.0383047062906 0.0298070621094 0.0211708137004 0.0201129458309 0.0453565917011 0.0368886973605 0.0457257341 0.0417220412167 0.154888546678 0.158813196403 0.166441835812 0.179672088583 0.172784191158 0.17260621351 0.165322327284 0.149403283235 0.162445964052 0.169819706795 0.172790429714 0.162919256631 0.17010598628 0.17291506116 0.145591206314 0.153172124666 0.174534430185 0.15398983773 0.196191934512 0.160693736573 0.182015889553 0.154710886954 0.192325902157 0.170351903358 0.156482289316 0.157229639215 0.171983255038 0.179460677481 0.172268582678 0.139849276668 0.162711676043 0.156097286205 0.154728919391 0.195984213978 0.177559464338 0.161865850494 0.163584898864 0.182768842876 0.156718657607 0.171791912863 0.175973376944 0.167287390852 0.161244255475 0.152349968293 0.168972557751 0.154707872737 0.161224201041 0.158337859598 0.137882544324 0.161653779265 0.217498297387 0.207268525118 0.204089016051 0.202387117484 0.211405750921 0.211656794766 0.207586435206 0.20562940887 0.215067539532 0.200874708618 0.186922162094 0.204055283767 0.199813802793 0.216054855672 0.219141272905 0.20087129472 0.194993585948 0.193928687775 0.232251037143 0.209370889696 0.202145172225 0.205917531589 0.216546254935 0.194031863501 0.196450064794 0.191939021682 0.189735506736 0.187016932169 0.212133736422 0.189132050882 0.20640648386 0.182100826622 0.214796842045 0.186466235268 0.20445164291 0.206912827034 0.205504201388 0.193322781156 0.186101115818 0.194015369065 0.208047565025 0.194373832921 0.207268525118 0.20679029341 0.207231461284 0.201073842632 0.205977385627 0.194517602543 0.200437951551 0.194023674059 0.00847168263945 0.0208969725179 0.0218078109788 0.0404437599311 0.0404194345362 0.0297098891496 0.0297074253675 0.0216763215811 0.0230614305273 0.0297074253675 0.00860159575866 0.00340785264521 0.0287758992091 0.0571909561512 0.0141236699669 0.0709946391549 0.0479055513747 0.0306293087394 0.0170492128659 0.00888265836541 0.00729150357409 0.00480661141663 0.185289458233 0.191085055337 0.196910034804 0.210145347321 0.203571296435 0.202882056675 0.197912476038 0.180625340213 0.191835734795 0.202787038295 0.201526915714 0.195916323017 0.19637797116 0.203353873438 0.179306027922 0.184472082084 0.206754233077 0.181958697279 0.225649748274 0.19019500565 0.21522963857 0.186167360837 0.221735787446 0.198765098442 0.186973637291 0.18833349586 0.201196357535 0.21085114286 0.204041973491 0.169797982896 0.192333522046 0.184882922721 0.185665053896 0.226331622925 0.209833971543 0.195365654989 0.194769866304 0.211153682947 0.188870228152 0.202929999244 0.20531727443 0.198262875463 0.19151438955 0.183035158868 0.19999216658 0.185562442931 0.19270971507 0.189054601453 0.171539131405 0.193197031986 0.250465285504 0.239173402368 0.23520366736 0.232978158323 0.24334022521 0.241285239552 0.239942028126 0.234265108703 0.244131225098 0.234017030201 0.220001112132 0.235226825533 0.231841004595 0.248118070359 0.252690451382 0.234464488853 0.225903274535 0.225189555796 0.261354129729 0.238022768459 0.234821661628 0.238891339667 0.245085625014 0.225666229038 0.228735290931 0.22181584168 0.221922833386 0.219499858076 0.243732621444 0.217749348385 0.235627115362 0.212972717548 0.246730042299 0.216195919385 0.232029501648 0.238052252824 0.239120375862 0.224484292562 0.218871170879 0.226408868705 0.241049615623 0.227943328268 0.239173402368 0.239135618661 0.240673256099 0.234329166611 0.237299131316 0.226939754937 0.234208016817 0.226098822397 0.028402190164 0.0277439229346 0.038946334352 0.0434213038922 0.0291676419928 0.0326159908833 0.0134911389606 0.0149220924049 0.0326159908833 0.0134816006656 0.0104081517294 0.0265932070523 0.0554420028459 0.0189257472352 0.0720390188254 0.0531694571518 0.0303305078503 0.0242591305562 0.0161087615284 0.011068875208 0.00482735633945 0.188743108882 0.194722342773 0.200407653053 0.213430694909 0.206860229667 0.206917746824 0.201757002065 0.18426566592 0.195342886433 0.206526515083 0.204777293747 0.199508643938 0.199425600774 0.207211939812 0.182702196026 0.187804249003 0.210815779569 0.185880854144 0.228533589019 0.193748347964 0.21913688541 0.189440254064 0.225129338462 0.202750726696 0.19038113591 0.191653574921 0.204515978518 0.214295374936 0.207718295645 0.173066791358 0.195766348142 0.188344856347 0.189125824359 0.230149564934 0.214039936134 0.199354571186 0.198257058594 0.214135463178 0.192868986061 0.206423136152 0.209356843732 0.20214026995 0.194966833942 0.186438002469 0.20377859508 0.189699928978 0.196612341236 0.192629244536 0.174503529883 0.196914127595 0.254336052592 0.242870282486 0.238667846113 0.23688365824 0.247015930548 0.244839615824 0.243870689439 0.237954961473 0.247598002179 0.237655720218 0.223546452001 0.238671394435 0.23524462783 0.251589019272 0.256100611398 0.237973407633 0.229710932979 0.229189966161 0.264623863258 0.241418386796 0.238285878732 0.24258651618 0.248557401899 0.228985563027 0.232518055884 0.22563381547 0.2253177885 0.223189662674 0.247286475663 0.221462369267 0.239019854286 0.21687609732 0.250217711381 0.219989961903 0.23614243802 0.241179806044 0.242913875985 0.228427928816 0.222559090866 0.229759973658 0.244438457877 0.230953863516 0.242870282486 0.24276526561 0.244179265793 0.237465205634 0.24045622955 0.230407994296 0.238017253402 0.230045288889 0.0082487413397 0.0434871339043 0.050183840328 0.0462946366306 0.0331671559449 0.0413860283481 0.0432754400997 0.0331671559449 0.018728526127 0.0181135727037 0.0407852007406 0.0552412031248 0.0241124765906 0.0672915663182 0.036051090003 0.0318240646756 0.0214815502931 0.0159437959725 0.0254901889174 0.0237539942631 0.169264556488 0.175091115152 0.180818741393 0.194404360875 0.187867354301 0.185963216048 0.181699481724 0.164435072081 0.175589139186 0.186821392192 0.185627969522 0.180155862933 0.18064011609 0.186712516664 0.164002900607 0.168780956251 0.190185205445 0.164923288811 0.210475012613 0.173888729012 0.199146471591 0.170598213889 0.205685349652 0.18167215365 0.171034804287 0.172628140609 0.185232711805 0.19500403819 0.187902476327 0.154050585176 0.176234935944 0.168650273349 0.169706859713 0.209769138775 0.193110247226 0.179199013537 0.178806517751 0.195681872133 0.172338947581 0.186953230165 0.188260629529 0.181677967227 0.175468629894 0.167135725316 0.183547713996 0.168593151982 0.176177479228 0.172880341359 0.156935998004 0.176943539051 0.234533528357 0.223126396017 0.219327759561 0.216348929156 0.227347260416 0.225044163552 0.22369577874 0.21767535265 0.227960059154 0.218355967949 0.204394726122 0.219392188471 0.216234012192 0.232478694207 0.237601395838 0.219130755018 0.209451421522 0.208536786671 0.245551542374 0.221905162641 0.219288471778 0.22312655653 0.228847315313 0.210092965448 0.212618708514 0.205172062073 0.20633435877 0.203530506813 0.227844403478 0.201098042061 0.219576265526 0.196351165226 0.231028528894 0.199557870197 0.214705957114 0.222733230705 0.223437599857 0.207886214359 0.202974139337 0.210949037245 0.225748458168 0.213347187038 0.223126396017 0.223291835541 0.22533034675 0.219465519892 0.221962474599 0.211307137623 0.21852957733 0.209700938041 0.0419192621384 0.0545713872547 0.0501857282151 0.0312681130066 0.0402402021553 0.0420008929211 0.0312681130066 0.0205720172252 0.0184919891428 0.0424913715031 0.0489285308529 0.0286939286865 0.0683097144827 0.0399734095489 0.0293256914093 0.0276858239832 0.0194684720042 0.0280123772406 0.0234918354003 0.166859723597 0.173331371512 0.178503598003 0.191909917223 0.185423378094 0.184241312774 0.180294315558 0.162400014304 0.173055799755 0.185412361634 0.18273757472 0.178554599617 0.177148633502 0.184812731656 0.162323255063 0.166439616176 0.188986532191 0.162572794611 0.207449709654 0.171422042516 0.198077549025 0.168235674576 0.203119984389 0.179508471538 0.16860424661 0.170241216483 0.182496759111 0.192879913246 0.186103766816 0.151333877107 0.173669495408 0.165942821486 0.167423536544 0.20787297933 0.192134128169 0.178241744028 0.176630454655 0.192503529595 0.170982423968 0.184797056786 0.186354645979 0.179907483835 0.173050611212 0.164731478826 0.181684378357 0.167089130276 0.174542724285 0.170681059367 0.154769008136 0.175102602238 0.233527638993 0.221524217045 0.217249882254 0.214634557666 0.225748190551 0.222754126699 0.222507410575 0.215313006261 0.225475910208 0.217028556203 0.202867049651 0.217307455338 0.214308520933 0.230711314018 0.236268919515 0.217806003587 0.207670960165 0.207072717123 0.242941912447 0.219250459655 0.217623775648 0.221838573376 0.226272530404 0.207953270239 0.211176611808 0.20315950921 0.204399143496 0.201993427277 0.226020220858 0.198708591851 0.217018221505 0.194631582156 0.229236199557 0.197470332905 0.212630809879 0.22033552246 0.22249437564 0.206324343096 0.201514537553 0.209042945777 0.224134795171 0.211456208554 0.221524217045 0.221736417649 0.223988271389 0.217633390228 0.219633609699 0.209536083539 0.217632171671 0.20838956304 0.0775399671204 0.0564468400456 0.0653641254742 0.0395849667657 0.0441943006701 0.0653641254742 0.03373111328 0.0393588608039 0.0260318537498 0.035188052496 0.0394930245033 0.0375459624937 0.0389958606199 0.0129896263362 0.048815943545 0.0379517413039 0.0436570210079 0.0373605585088 0.163502150984 0.168337389523 0.175155703097 0.187828039408 0.181028588214 0.182647655516 0.175454197959 0.158708602469 0.170954310136 0.179762949884 0.180496225683 0.172501221065 0.176828601599 0.182533215894 0.154901122018 0.161684635697 0.185118234991 0.163277975005 0.203127118484 0.169348081929 0.192443927773 0.163103682661 0.200509349698 0.179853786658 0.164993784705 0.165655282459 0.179962811264 0.188228038198 0.18173609183 0.147930089008 0.171080786567 0.164375611634 0.163483874158 0.205438086531 0.18853460221 0.172643498492 0.17244334411 0.189729944562 0.167174105351 0.180637454384 0.185804185303 0.177096116234 0.169809585441 0.16090449309 0.178551065139 0.165213876 0.171240797871 0.16732581913 0.146067131815 0.17119760345 0.227732558213 0.216775565291 0.212818856201 0.212113111804 0.220864451794 0.220275362854 0.217805693503 0.214378859193 0.223348154292 0.21056133718 0.196361313338 0.212749259626 0.208612136858 0.225027947273 0.228363013794 0.210350767964 0.204552905568 0.204069917831 0.240058060786 0.217392662406 0.211260278871 0.215701291359 0.224730833936 0.202526361799 0.206282759299 0.201292044309 0.198558189974 0.196671901675 0.221200953228 0.197951565037 0.214539723027 0.191917757906 0.223779418483 0.195733699704 0.214045901526 0.214810423689 0.215727246675 0.203293848315 0.195827563807 0.20277537878 0.217057373264 0.202583470922 0.216775565291 0.216238379997 0.216660429864 0.209514540125 0.213990052328 0.203581443905 0.210749726957 0.20422943306 0.0299803388219 0.040141041955 0.0494042382248 0.0475047073328 0.040141041955 0.0460070623429 0.0427394298505 0.0575631845334 0.0966299998606 0.0399614977189 0.101837465377 0.0750240618009 0.0697188576907 0.0332240827502 0.042809511279 0.0352142478257 0.0438103448171 0.215592317841 0.220449330264 0.226776190885 0.240218246846 0.233773884944 0.231038348034 0.2263684672 0.210465175429 0.221881119192 0.231308080404 0.231995396207 0.22508238649 0.227350588366 0.232024663305 0.209334152567 0.215020396004 0.234337406165 0.211132081266 0.256379513335 0.220156177626 0.242726282533 0.216799299323 0.251315628789 0.227298889535 0.217343977664 0.218830240402 0.231491464759 0.240364579333 0.233021642066 0.20102218505 0.222551706707 0.215242680103 0.215887525017 0.254517907032 0.236803444285 0.223122921939 0.224639449766 0.242029857582 0.217221787473 0.232566133597 0.233478733428 0.226945320336 0.221662735537 0.213507733767 0.228892766884 0.213808892567 0.221389781107 0.218899946715 0.202924474402 0.222409930936 0.277044937084 0.267058523642 0.264053120566 0.260683081125 0.271161003688 0.269928478944 0.266921380471 0.262757571495 0.272983826376 0.261803183737 0.248535164351 0.264118904646 0.260747941814 0.276361888176 0.280348586054 0.262426412526 0.254046125774 0.252677731845 0.290308221249 0.267228122274 0.26327783075 0.266417210913 0.273921992864 0.255063180539 0.256547903477 0.250174111671 0.251094888905 0.247813627121 0.2719924919 0.246585755223 0.264876118769 0.241182097008 0.275004799417 0.244784301617 0.259319666846 0.267703173929 0.266005774781 0.252196034878 0.247119094967 0.255516011217 0.269407340011 0.257485027934 0.267058523642 0.267089999571 0.268509786469 0.263477813268 0.266862086523 0.25569939087 0.261126117571 0.253527997613 0.0490986378456 0.0295882981502 0.0295703235206 0.0490986378456 0.0321772405698 0.0329624694956 0.0320791928311 0.0819581869701 0.0243777845713 0.0815880274595 0.0630031242432 0.0521832400773 0.0270041961787 0.0316626367995 0.0225970983534 0.0306135777155 0.209138400452 0.213486068388 0.2205444529 0.233810061497 0.227135531522 0.225853564299 0.21966357841 0.203852341272 0.216088869802 0.224306040557 0.226217923772 0.217746213648 0.22224285579 0.226486749722 0.201086071725 0.207931744175 0.228270944293 0.206490835508 0.249940445551 0.214362660634 0.235990805565 0.209564571075 0.245741550277 0.222850058752 0.210799114332 0.211874421056 0.225608964554 0.23378761328 0.226547651045 0.194192088088 0.216536885951 0.209533041634 0.209201443093 0.249253656365 0.230961033564 0.21611849601 0.218017866257 0.236081990375 0.21084689547 0.226091432124 0.228781524913 0.221104911259 0.215367366824 0.206800674523 0.222911320613 0.208230596605 0.21525709201 0.2125575014 0.193883560936 0.215963986139 0.270701972881 0.26093677072 0.257916700448 0.255512820108 0.265026258573 0.264740784654 0.260931231555 0.258198696768 0.267948592812 0.254868213876 0.241356571396 0.257920982238 0.254009770883 0.269860597717 0.272941762013 0.255029971614 0.248480879151 0.247232962401 0.285073136127 0.262202525959 0.256323297606 0.259707446043 0.269155247223 0.248322830219 0.250262594392 0.245086205405 0.244160084795 0.241180503071 0.265837705385 0.241871800867 0.259549733542 0.235634358004 0.268589453275 0.239657575868 0.255932774798 0.261005427367 0.2590787222 0.246704864465 0.240323606031 0.248446059436 0.262200385961 0.249216212931 0.26093677072 0.260623330072 0.261243034875 0.255650557301 0.260116706756 0.248814449864 0.254094104786 0.247584466928 0.0426893927085 0.0408370899258 0.0 0.0368318621027 0.029200211213 0.0578784327643 0.069522041979 0.0411420259607 0.0955605905857 0.0684916485028 0.0536985636371 0.0361283833003 0.0349236407736 0.0326544016244 0.0320790517512 0.190734279342 0.198011093099 0.202165335147 0.215193803756 0.208989236133 0.207943677959 0.205067684162 0.186855165408 0.196324830352 0.210203923195 0.205342231011 0.203427359944 0.198526873832 0.208498767129 0.187766259392 0.1906844311 0.213518747065 0.185598548292 0.229746939288 0.194805777978 0.222821572969 0.192481412892 0.225723793976 0.202375384198 0.192457483521 0.194300696695 0.205376522136 0.216660688755 0.21030774241 0.175307222553 0.197000980201 0.189066511629 0.191539232379 0.231058906578 0.21669613367 0.203523564636 0.200666530244 0.214595210264 0.195827171333 0.208641958794 0.209561099462 0.203960485003 0.196717491572 0.188766309975 0.205678014795 0.191492051166 0.198961164508 0.194672996958 0.180152450124 0.19944864987 0.257583228235 0.245178524187 0.240481186058 0.23786630337 0.249341975217 0.245210894146 0.246468005973 0.237456371003 0.247563146561 0.241300303041 0.22729752986 0.240555698528 0.237993038095 0.254193700204 0.26033321069 0.24220764546 0.231128442611 0.230799434891 0.264694157473 0.241194058993 0.241588310222 0.245985985156 0.248083056968 0.231516408669 0.235215479132 0.226198260689 0.228320459149 0.22623565919 0.249391170367 0.221152204365 0.239284874476 0.218351963925 0.252686589685 0.220530747351 0.234477098822 0.243363395844 0.246969168578 0.229990071021 0.225895881072 0.232959826282 0.248113114083 0.23570223574 0.245178524187 0.245562733947 0.248226439579 0.241702239505 0.242775171886 0.233515238499 0.242256602421 0.232453320509 0.00485178735022 0.0426893927085 0.0245520805084 0.0237321483943 0.0251494297711 0.057422445578 0.0278324002426 0.0740340192529 0.0616449531864 0.0345266403523 0.0352178925007 0.0280808122885 0.0215364121522 0.0177797202497 0.195939824645 0.201850357058 0.207610278022 0.22033675889 0.213750209485 0.214735564684 0.209038553623 0.191585516698 0.202707820911 0.213610139259 0.211883129405 0.206450810174 0.206559465565 0.214810867748 0.189369070647 0.194741318593 0.218356798216 0.193913039155 0.235021949805 0.201158850164 0.226304640977 0.196279111135 0.23226696484 0.210758787397 0.197506357802 0.198589557713 0.211650666116 0.221261000242 0.214919567324 0.180148757389 0.203013849771 0.195747568982 0.196259855385 0.237643665501 0.221708411792 0.206639148011 0.205357745264 0.220920753341 0.200419916968 0.213509698507 0.217293606067 0.209702604063 0.202159105945 0.193545439121 0.211227865592 0.19759997516 0.20414742205 0.199914048013 0.180698578828 0.204233409175 0.261355583351 0.249944219156 0.245612323364 0.244419283687 0.254044690674 0.252080319195 0.251117898529 0.245501499007 0.254799073865 0.244461494615 0.230320341653 0.245583982907 0.241986600586 0.258340089378 0.262471019952 0.244539880639 0.237114058797 0.236754636273 0.271527597514 0.248603215251 0.244975611097 0.24946627049 0.255826675762 0.23571262824 0.239663820665 0.233206292475 0.232061847402 0.230234276049 0.254229520181 0.229085095534 0.246138012907 0.224434087127 0.257016267918 0.227571722833 0.244299258458 0.247702557023 0.249766808341 0.235948695396 0.229554249737 0.236400217176 0.250952182681 0.236961759284 0.249944219156 0.249686907558 0.250743450736 0.243657848264 0.246990711027 0.237179418187 0.244877764453 0.237415960787 0.0408370899258 0.0273499265107 0.025297636378 0.0296282714156 0.0603715739764 0.030303105355 0.0787085816014 0.0655434565706 0.0387042108075 0.0365503327535 0.0303622028421 0.0226096284705 0.0196557817863 0.19925839426 0.205348957591 0.210921560248 0.223617852816 0.217069506306 0.218059817218 0.212575335145 0.194999027921 0.205926088655 0.217171129009 0.215009852018 0.21001079042 0.209452929226 0.218136520475 0.193003747556 0.198137341753 0.221866239829 0.197043564846 0.238162085485 0.204392911714 0.229893095054 0.199683809546 0.235440202166 0.213909407049 0.200827966888 0.201961005661 0.214826820158 0.224639586072 0.218358620882 0.183440825957 0.206248208954 0.198918059014 0.19962810203 0.240932955881 0.225232933618 0.210279009964 0.208738673233 0.223984027141 0.203935797463 0.216876482587 0.220527121607 0.21308659534 0.205456328664 0.196890655988 0.214607347176 0.201007750183 0.207588221909 0.203264328774 0.184313736826 0.207665740899 0.26489357675 0.253366014308 0.248941435732 0.247733075772 0.257464720601 0.255265649267 0.254603866324 0.248608337617 0.25792057227 0.248007634376 0.233863042555 0.24891723403 0.245404277418 0.261753780229 0.266030956859 0.248122124098 0.240457509587 0.240148573616 0.27462290691 0.251686093628 0.248460344614 0.252996524531 0.258897040073 0.239091218679 0.243138472416 0.236454818573 0.235503469176 0.233728803563 0.257606144443 0.232210377675 0.249277823428 0.227793973728 0.260419107201 0.230810446264 0.247340816609 0.251008018719 0.253372216044 0.239328849004 0.233077723245 0.239855175697 0.254460096493 0.240510971982 0.253366014308 0.253147704183 0.254305992093 0.247178452498 0.250316773817 0.240645130337 0.248503359474 0.240885126472 0.0368318621027 0.029200211213 0.0578784327643 0.069522041979 0.0411420259607 0.0955605905857 0.0684916485028 0.0536985636371 0.0361283833003 0.0349236407736 0.0326544016244 0.0320790517512 0.190734279342 0.198011093099 0.202165335147 0.215193803756 0.208989236133 0.207943677959 0.205067684162 0.186855165408 0.196324830352 0.210203923195 0.205342231011 0.203427359944 0.198526873832 0.208498767129 0.187766259392 0.1906844311 0.213518747065 0.185598548292 0.229746939288 0.194805777978 0.222821572969 0.192481412892 0.225723793976 0.202375384198 0.192457483521 0.194300696695 0.205376522136 0.216660688755 0.21030774241 0.175307222553 0.197000980201 0.189066511629 0.191539232379 0.231058906578 0.21669613367 0.203523564636 0.200666530244 0.214595210264 0.195827171333 0.208641958794 0.209561099462 0.203960485003 0.196717491572 0.188766309975 0.205678014795 0.191492051166 0.198961164508 0.194672996958 0.180152450124 0.19944864987 0.257583228235 0.245178524187 0.240481186058 0.23786630337 0.249341975217 0.245210894146 0.246468005973 0.237456371003 0.247563146561 0.241300303041 0.22729752986 0.240555698528 0.237993038095 0.254193700204 0.26033321069 0.24220764546 0.231128442611 0.230799434891 0.264694157473 0.241194058993 0.241588310222 0.245985985156 0.248083056968 0.231516408669 0.235215479132 0.226198260689 0.228320459149 0.22623565919 0.249391170367 0.221152204365 0.239284874476 0.218351963925 0.252686589685 0.220530747351 0.234477098822 0.243363395844 0.246969168578 0.229990071021 0.225895881072 0.232959826282 0.248113114083 0.23570223574 0.245178524187 0.245562733947 0.248226439579 0.241702239505 0.242775171886 0.233515238499 0.242256602421 0.232453320509 0.00792313459309 0.0236297776546 0.0536541076705 0.0108412074571 0.0628560371629 0.0400426717019 0.0242916508179 0.0172043768705 0.0050674059167 0.0120480352419 0.00909567999576 0.179733982337 0.185130641595 0.191359647755 0.204666509458 0.198016972069 0.197255576955 0.19185518721 0.174855164332 0.186481505005 0.196685913747 0.196375987191 0.189833405887 0.19172374648 0.197739787295 0.173088361774 0.178764981726 0.200732295255 0.17675687262 0.220477540317 0.184804954654 0.209049846749 0.180446207623 0.21646451476 0.193507677512 0.181414841832 0.18267689502 0.195938395299 0.20515640915 0.198204629073 0.164323672477 0.186951627799 0.179636188812 0.180004434454 0.220785829324 0.203765417111 0.189073881786 0.189075562692 0.206144383335 0.182845346787 0.197260508087 0.199879524686 0.192526504243 0.186000212457 0.177429453542 0.194272636643 0.179755047797 0.186852872988 0.183420017951 0.165408476828 0.187374971211 0.244379342624 0.233352874625 0.229596132577 0.227371143644 0.2375226222 0.23596534417 0.233967797192 0.229101525181 0.238946455726 0.22794079194 0.213947298319 0.229612039594 0.226054866131 0.242325328031 0.24658946328 0.228320886035 0.220245475488 0.219410638455 0.256223296024 0.232925784221 0.228888220536 0.232841814871 0.240004491346 0.219971839687 0.222802209556 0.216357175156 0.216093165101 0.213536808192 0.238014479196 0.212556057937 0.230415334062 0.207278762186 0.240959781593 0.210762423837 0.226970181506 0.23251461018 0.23290234378 0.218739342276 0.212847200349 0.220551656017 0.235068599786 0.221910374672 0.233352874625 0.233237981586 0.234568034713 0.228343429058 0.231717374059 0.221050886534 0.227947933137 0.220165226053 0.0299322879907 0.05484354778 0.015508827535 0.0698377856312 0.0461226756997 0.0288439268589 0.0179749812614 0.00820193608475 0.0104514607363 0.00601471721254 0.182232775094 0.188144985482 0.193867682363 0.20711590793 0.200550766912 0.199860135361 0.195016228938 0.177611309591 0.188739284934 0.199919451707 0.198412537874 0.193032154463 0.193186123937 0.200329430243 0.176446801251 0.181459762374 0.203862717343 0.178811701467 0.222599641879 0.187102653143 0.212418919228 0.183165138771 0.218668582765 0.195650642874 0.183922319896 0.18531418569 0.198099986213 0.207874048966 0.201087073424 0.166709980365 0.189246782627 0.18175655241 0.182635613511 0.223327464443 0.206968516624 0.19255440527 0.191763016548 0.208041469681 0.185943960269 0.199929552497 0.20225187193 0.195263692383 0.188456709089 0.17999008547 0.196992188976 0.182563902533 0.189736470349 0.186019084029 0.168664903455 0.190216873808 0.247702840069 0.236282170601 0.232244216334 0.230002139075 0.240457584895 0.238254033101 0.237105182737 0.231183514354 0.241076068443 0.231215767875 0.217161214732 0.232270602033 0.228935795245 0.245246208475 0.249963377759 0.231700745278 0.222930449716 0.222251471523 0.258324331925 0.234942120984 0.231973156153 0.236088516952 0.242009296663 0.222723564262 0.225860019318 0.218781249758 0.219012595092 0.216618271437 0.240821050718 0.214649402949 0.232567858569 0.209986385677 0.243846113816 0.213146433073 0.228924161819 0.235098611969 0.236391467186 0.22153411273 0.216011138881 0.223520339681 0.238239770023 0.225155295827 0.236282170601 0.236275025927 0.237908750738 0.231525069379 0.234355153325 0.224056763193 0.231485450395 0.223215654925 0.0572284762647 0.0227453897084 0.0528238726337 0.0444991634471 0.0266973769114 0.0336948312224 0.0272062333147 0.0274688740262 0.0263328670099 0.185716682176 0.189934662812 0.1972487923 0.210230033124 0.203429572326 0.203652674494 0.196491149306 0.180504651022 0.193104816748 0.200892549998 0.203064104164 0.193989394378 0.199601579765 0.203900303764 0.17670118478 0.184039790634 0.205684310622 0.1846555251 0.226113704537 0.191415178407 0.212986837934 0.185538541574 0.222740004307 0.201086740863 0.187280443615 0.188038576609 0.202414184433 0.210238732317 0.203278048935 0.170511460418 0.193354539773 0.186626010428 0.185640639557 0.226777734968 0.208697723096 0.193060716385 0.194490643156 0.212667601723 0.188009393157 0.2026405567 0.206872630148 0.198376957217 0.192017886181 0.18319542151 0.200010008529 0.185964041385 0.192410951726 0.189280207199 0.168712331151 0.192752182621 0.248024768226 0.23796255467 0.234657432476 0.23319215601 0.242043627351 0.242092609424 0.238343194449 0.236048752979 0.245336704526 0.231521041315 0.21770196943 0.234612870361 0.230403022013 0.246515101463 0.249324779094 0.231381623939 0.225835327084 0.224876205525 0.262239370649 0.239554652873 0.232726560614 0.236544998844 0.246724791051 0.22460575232 0.227286958934 0.222708966341 0.220419587826 0.21790450046 0.242699459353 0.219658545965 0.236700307439 0.213086341068 0.245286981729 0.217239309033 0.234909245332 0.237182720463 0.236050820735 0.224251890124 0.21698485009 0.224622112179 0.238475066919 0.224611061526 0.23796255467 0.237436165661 0.237675287343 0.231370754771 0.236291823604 0.225200181658 0.231029643716 0.224953381888 0.0635710838178 0.0592624543316 0.0596058092469 0.0328544820604 0.0698260168465 0.0573261822931 0.0634460229613 0.0541080958794 0.147303482686 0.155161448288 0.159136079545 0.171063643845 0.16463221595 0.168584971583 0.163624194761 0.144235266953 0.153827968777 0.168063795028 0.16179307599 0.160206770568 0.155398151635 0.167887347752 0.142965424509 0.146100735749 0.17375554158 0.146517107633 0.184102282814 0.152518687773 0.182006446517 0.147493939017 0.182975421708 0.163616999091 0.148716120222 0.149770660992 0.161958247648 0.173135111896 0.168022513735 0.130738265008 0.153920373487 0.146537313722 0.147925357539 0.190546691654 0.177990062346 0.162980599048 0.157241520972 0.169863812022 0.155438284157 0.165389918434 0.170707644688 0.163197102596 0.153422018191 0.144858486622 0.164315178145 0.152440808389 0.158128021971 0.151920639439 0.132537826475 0.157359920068 0.217237278816 0.203578319465 0.19760287759 0.197919551969 0.207681519923 0.203644803794 0.206313583309 0.1971606419 0.205828867849 0.199064489236 0.184178187026 0.197538822775 0.194404518771 0.211337912042 0.217143803133 0.199166671372 0.190313841849 0.191119829867 0.222105237734 0.199176357415 0.198307304 0.204224647581 0.206706114847 0.187412194782 0.19389709622 0.185837912363 0.184448440446 0.184112718669 0.207064291919 0.180760878547 0.196874753064 0.177987251583 0.20993106085 0.180009687643 0.197504007472 0.198552660709 0.205899002329 0.189953313758 0.183706289376 0.188863124638 0.204480568311 0.189510910124 0.203578319465 0.203449868624 0.205307715981 0.196398052452 0.198053210544 0.190139115088 0.201200460168 0.192215464603 0.0646648846724 0.0408252785942 0.0324283536149 0.0110096321254 0.0094227049932 0.0109851717283 0.0161323040352 0.185995265043 0.190666336997 0.197500031875 0.21094278484 0.204249573992 0.202798103731 0.196994111724 0.180742040387 0.192881751338 0.201790619427 0.203110749465 0.195155158145 0.19908437034 0.203454592708 0.178505994536 0.184932353957 0.205646229373 0.183039730424 0.227253690432 0.191142107437 0.213742423108 0.186621634482 0.222820224457 0.199555206103 0.187692696694 0.188884746385 0.202513734055 0.21103182776 0.203761324581 0.170936996707 0.193374027451 0.18623653615 0.18612791719 0.226436319176 0.208431967216 0.193707164322 0.195058461584 0.2131353075 0.187987297837 0.203202157658 0.20562939159 0.19810682266 0.192261721083 0.183685994256 0.199939039445 0.185109577754 0.192293556305 0.189460942551 0.171326016098 0.193018202765 0.248893705524 0.238594153414 0.235368547023 0.232809197602 0.242745772985 0.241996085058 0.238752248121 0.235253956997 0.245180784501 0.232837883436 0.219103668122 0.235388183401 0.231636120508 0.247694764978 0.251385734184 0.233178069062 0.225757404345 0.224585782777 0.262534762143 0.239353867417 0.234157942476 0.23769286224 0.246342255252 0.225805082888 0.227891511242 0.222153883963 0.221706146684 0.218742321816 0.243483255972 0.218775046696 0.236736505477 0.212768083313 0.246367071263 0.216653745648 0.232866084107 0.238586937872 0.237342638049 0.224021718097 0.217958679267 0.226108360277 0.24024082191 0.227355271774 0.238594153414 0.238405022146 0.239431466759 0.233734880392 0.237715653864 0.226470518154 0.232346541994 0.225139690924 0.0393671458325 0.0464638919444 0.0721210062546 0.0652058473437 0.0727980489441 0.069475743339 0.146377403101 0.147841270546 0.157364583964 0.169967544666 0.162961576643 0.163688135507 0.153695971045 0.140223917866 0.154815274304 0.157460458795 0.165356253736 0.150768971529 0.165320051187 0.163744020488 0.133177827162 0.143578449734 0.163063976092 0.148488500757 0.186880468442 0.153029883361 0.168852310214 0.144817839236 0.183493098196 0.16367577587 0.147767272784 0.147687188567 0.164075138054 0.168705452589 0.161392920433 0.132534765042 0.154766041608 0.149414499828 0.145576557217 0.186084208163 0.165901662347 0.149128920765 0.153742006284 0.175201329509 0.146090630422 0.161672202019 0.168031911823 0.15768748388 0.152605178523 0.14358929792 0.159222856143 0.14586039418 0.151244472229 0.149339889326 0.125727972716 0.151529925609 0.203450036676 0.194986217471 0.192843589773 0.192004248179 0.198946853705 0.202034655336 0.194735870429 0.197390062155 0.205967080811 0.187074482741 0.173679535364 0.192730113791 0.187498820793 0.203203985607 0.204052975425 0.186370806955 0.184428712838 0.182999565721 0.222535792393 0.200873648 0.188899014225 0.192203777807 0.208005111614 0.182384645276 0.184037705225 0.182740791406 0.177549631149 0.174643342307 0.20005255491 0.181451046073 0.197344936488 0.172060390986 0.202165775813 0.177622921879 0.197550181919 0.195373313831 0.190875275066 0.18253037262 0.173366080571 0.181382900577 0.194116360457 0.17989694523 0.194986217471 0.19390780748 0.192735187036 0.186889353803 0.194252690611 0.181881177771 0.185754237755 0.182012475847 0.0362542669235 0.0433946419111 0.0395938166279 0.0499100911085 0.0488639489089 0.15152574063 0.154669324023 0.162831337388 0.176665753207 0.169806006203 0.167122046596 0.160378494232 0.145396880889 0.158882941041 0.165181343889 0.170013544811 0.158841213295 0.168049497633 0.168033584387 0.142281111905 0.150191536414 0.168801703096 0.149137142763 0.194382185315 0.156984666205 0.176687245859 0.151908690894 0.188914920448 0.165149458367 0.153257832095 0.154263633953 0.168972071115 0.175883337588 0.16793063653 0.137286345822 0.159386690667 0.152763325671 0.151315442368 0.191108253021 0.171290308569 0.156330262179 0.159994057675 0.180780718652 0.151317533557 0.168120728135 0.170508979962 0.162341150711 0.157837504705 0.149168793021 0.164327797527 0.148926027403 0.156192253462 0.154516403486 0.136169004568 0.157264478091 0.212122717934 0.202635989676 0.200296956824 0.197168424809 0.206818081536 0.207593997357 0.202129844535 0.201194661862 0.211287068634 0.196367072918 0.182828097116 0.200324517328 0.196191886272 0.212106652571 0.215234691898 0.196754062665 0.19012565359 0.188386972878 0.22899541165 0.20586463613 0.198258080181 0.201204812212 0.21275581912 0.190751178898 0.191559171703 0.187109767024 0.186199376174 0.182482910799 0.207969239625 0.184720534783 0.20291190811 0.176982703379 0.210810911009 0.181756418439 0.198660937707 0.204191111601 0.200384835859 0.187985877445 0.181554202617 0.190580156178 0.204342491841 0.191957847677 0.202635989676 0.20235052312 0.203098119616 0.198224746232 0.20315819974 0.190687755597 0.195275148422 0.188602851388 0.0403137962831 0.0283224608052 0.0352856337592 0.0279159520938 0.163417587591 0.169024399252 0.175174902733 0.188097949492 0.181377843525 0.182339093803 0.176249889638 0.158817342465 0.170474967966 0.180845899763 0.180001842158 0.173609983768 0.175496658011 0.182387588854 0.15630806646 0.162058645803 0.185720747493 0.161911269413 0.203372519665 0.168871283524 0.193698104443 0.163602773962 0.200347540332 0.178768462478 0.164992621499 0.165987648079 0.179610648238 0.188805808549 0.182291142535 0.147635227715 0.17074779736 0.163623388142 0.163626611748 0.205461058801 0.189137857415 0.173807850394 0.172761367844 0.189359771077 0.167597338583 0.181003226921 0.18512092453 0.177130623568 0.169726367846 0.160952549622 0.178668367454 0.164953979441 0.171439694143 0.167334380068 0.147661742228 0.171525073941 0.229212799489 0.217687295638 0.213458314816 0.212251579761 0.221837277994 0.220248120289 0.218836902814 0.21380890761 0.22315649106 0.21209021333 0.197773787684 0.213427195478 0.209691510886 0.226221325989 0.230427027381 0.212200169545 0.204828617154 0.204406576244 0.240127225685 0.217034360511 0.212651201199 0.2171572787 0.224326708779 0.203421180278 0.207247009432 0.201076078606 0.199634752126 0.197699005741 0.222082174457 0.197241869749 0.214400679015 0.192020712279 0.224886788727 0.195424460906 0.212817607264 0.215705919361 0.217495203752 0.20360497892 0.196993475509 0.204019508797 0.218720046506 0.204677118987 0.217687295638 0.217405004007 0.218489214457 0.2114130463 0.214943199157 0.204771656731 0.212548494217 0.204988233676 0.0127227257693 0.0140144534262 0.021205315625 0.187358882688 0.192125766059 0.198772205734 0.212374766999 0.205757524042 0.203466313878 0.198269360384 0.182075536022 0.193992765379 0.203217006858 0.204331991647 0.196763084721 0.20014614007 0.2043270841 0.180514052654 0.186571402116 0.206603961592 0.183553088594 0.228871000555 0.192232411854 0.214999691886 0.188338267885 0.22392031363 0.199986970587 0.189109313009 0.19048409561 0.20374267521 0.212452607057 0.205039311477 0.17250608603 0.194596025922 0.187333202847 0.187569910566 0.227244357321 0.209244321292 0.19501056691 0.196469921009 0.214533216851 0.189073691369 0.204563213894 0.206128514596 0.199072106837 0.193573357583 0.185159768848 0.200995780718 0.185854759801 0.193344854477 0.190733738309 0.173803583444 0.194263918799 0.250041858008 0.239752334137 0.236636793113 0.233547933384 0.243918830588 0.242901503454 0.239756635176 0.235879076786 0.246072511959 0.234292093183 0.220682667006 0.236686092505 0.233134556981 0.249085035733 0.25310029637 0.234845006722 0.226672837999 0.225370202913 0.263585873652 0.240269679341 0.235701118126 0.239046163418 0.247138992266 0.227341018945 0.229062028698 0.222899030248 0.223273394137 0.220071420598 0.244719204688 0.219434280561 0.237757183282 0.213627898609 0.247720909818 0.217420020786 0.232895251914 0.240194731922 0.23871780496 0.22484832656 0.219342733041 0.227741527766 0.241902288188 0.229541957119 0.239752334137 0.239709455557 0.241056941465 0.235717844261 0.239326740269 0.227988125157 0.23374803561 0.226117504317 0.0112944223112 0.0116717916683 0.180315550232 0.185624806253 0.191893759643 0.205310880338 0.198677901623 0.19742928403 0.192207723586 0.175346071899 0.186991091141 0.197103740487 0.197010559796 0.19035952375 0.192415460068 0.198031907427 0.173783350974 0.179445795208 0.200920544981 0.176980189616 0.221316628359 0.185291341794 0.209338461592 0.181164395028 0.216995536327 0.193672787851 0.182024521085 0.183353435463 0.196543976916 0.205721030365 0.198641096205 0.165041173069 0.187513275437 0.180170480519 0.180593580969 0.221064954649 0.203852710847 0.189347093282 0.189636739108 0.206917162302 0.183105626465 0.197802407018 0.200023916345 0.192826802002 0.18656768388 0.178054301819 0.194629767109 0.179904508376 0.187157083025 0.183925453374 0.166391248738 0.187799823192 0.244629672801 0.233720966397 0.230103074977 0.227589285523 0.237898238239 0.236372028201 0.234191133105 0.229406521443 0.239389014631 0.228371263352 0.214461216321 0.2301324354 0.226622699222 0.242825477857 0.247136546528 0.228837795311 0.220544275821 0.219592617307 0.256764787312 0.233410014962 0.229423132068 0.233225877209 0.240428033421 0.220595144087 0.223137647662 0.216631996383 0.216685610877 0.213950565713 0.238461242313 0.212870718316 0.230920602382 0.207535413146 0.241449474994 0.211057657029 0.226977435726 0.233226691247 0.233226664174 0.218957536154 0.213267398065 0.221167604245 0.235644719391 0.222751284499 0.233720966397 0.233655964463 0.235074683268 0.229091941731 0.232416283617 0.221592631388 0.228271321964 0.220395124759 0.00983853465038 0.190743576675 0.196127634014 0.202306832097 0.215593264431 0.208988714901 0.208014681254 0.202752493595 0.185879141861 0.197391448173 0.207587390017 0.207246286244 0.200818808941 0.202436920976 0.208560198594 0.184214467342 0.189842910423 0.211501430369 0.187510793393 0.23133568248 0.195719341744 0.219810583637 0.19153274103 0.227260064133 0.204197085901 0.192430421495 0.193730481177 0.206830708645 0.216081015857 0.209114627207 0.175418662775 0.197889059256 0.19054759391 0.191035254803 0.231501988345 0.214455580052 0.199914298524 0.200065006525 0.216984298716 0.193736240961 0.208206242937 0.210580217034 0.203385972256 0.196973524191 0.188474338185 0.205152454389 0.190585220715 0.197746685322 0.194398694242 0.176663593729 0.198324488838 0.254937931723 0.244068220209 0.240384243666 0.238035456407 0.248220627185 0.246646653405 0.244595211757 0.23972473422 0.249602645305 0.238680104062 0.224803483418 0.240405804306 0.236887764418 0.253050369586 0.257246240499 0.239074958955 0.230985078994 0.230097737916 0.266839316033 0.24359819756 0.239685124735 0.243538571704 0.250621888643 0.230851769093 0.233547789712 0.227075078845 0.226983547477 0.224362834405 0.248743336169 0.223247552937 0.241135379781 0.21805286342 0.251689329115 0.22150661959 0.237406179341 0.243353541542 0.243526559835 0.229448331316 0.22367406721 0.231427017129 0.245837459247 0.232818717405 0.244068220209 0.243969807123 0.245289155672 0.239200193755 0.242559723366 0.231895813526 0.238589343178 0.230870674099 0.185074856292 0.190966638679 0.196734232482 0.209858428647 0.203274864625 0.203041999473 0.197926990268 0.180506604609 0.191676192961 0.202741147438 0.201237653931 0.195771907735 0.19601488206 0.203403274574 0.179018659177 0.184172394714 0.206916175569 0.182062920029 0.225161143908 0.190059350101 0.215302655172 0.18583273214 0.22154130677 0.198918819466 0.186732254392 0.188033674574 0.200939126942 0.210648020541 0.203966819203 0.169461460201 0.19212652304 0.184698761261 0.185447917615 0.226382707341 0.210090272929 0.195464472523 0.194576182922 0.210724866996 0.188972223666 0.202749851185 0.205495551665 0.198311837225 0.191308228071 0.1827836447 0.199987032182 0.185758986102 0.192762378199 0.188912535548 0.170985151469 0.193136210612 0.250560354729 0.239150130043 0.235042618318 0.233086785485 0.243308731763 0.241203268173 0.2400598963 0.234273096316 0.244008938502 0.233948328457 0.219858019146 0.235053540533 0.231632132621 0.247968147104 0.252509320758 0.234318201257 0.225941055535 0.225341346975 0.261132458323 0.237859251815 0.23464919225 0.238863315744 0.244976238699 0.225403382703 0.228750181247 0.221868483944 0.221696049716 0.219446034661 0.243630311449 0.217754591911 0.235449762585 0.213055210612 0.246589228494 0.216230229788 0.232311179578 0.237695560547 0.239155145074 0.22460119101 0.218814227997 0.226159982187 0.240839111604 0.227492031494 0.239150130043 0.239069291581 0.240533107829 0.233959974366 0.236956995194 0.226761267667 0.234246750663 0.226212511867 0.0174000566092 0.0121264323023 0.0260599074483 0.0195345881682 0.024716388707 0.0269505563041 0.00951579401108 0.00952789620934 0.0312682326376 0.021380704298 0.0249359987416 0.0321633377276 0.0221499569656 0.0250465767598 0.00649156808823 0.035247677191 0.0178478945632 0.0452164673822 0.00796037757126 0.0457327534983 0.00806434689496 0.0382898633561 0.0225581753549 0.00212523780258 0.00635071079008 0.0188691425113 0.0279463141708 0.0242753243704 0.0170805625395 0.00904309726647 0.0106287996797 0.00382969063087 0.0441552932431 0.0411601284763 0.0356126616505 0.0116846154421 0.0333280322148 0.0231237862627 0.0194233884188 0.0266405905658 0.0193741592831 0.00647009737397 0.00313195595137 0.0192809490983 0.0198825976901 0.0184223649953 0.00589033700008 0.0274923472821 0.0151107135826 0.0780209291989 0.0597893086125 0.0526478480904 0.0519801851129 0.0640907142958 0.0582643284706 0.0646025548306 0.0523589334483 0.0615531482061 0.0598796956679 0.0449628875056 0.0527423072945 0.051324556652 0.0686409960047 0.0805660524071 0.0623345264313 0.0443847427682 0.0466830846559 0.0795542446923 0.0559362746494 0.057180756356 0.0643259703348 0.063211257271 0.0437185245372 0.0513548290636 0.0393714351609 0.0417127661704 0.0422926634719 0.0628873923745 0.0360959787877 0.0527047257983 0.0325588237609 0.0667976247829 0.0335624409497 0.0547114607912 0.0561052885987 0.069799650238 0.0449738444715 0.0432370258673 0.0470356297838 0.065014861143 0.055075271212 0.0597893086125 0.0609892947965 0.0676680770887 0.0593149961508 0.0554983088895 0.0479751568133 0.0662596895747 0.0497728280187 0.01649842894 0.0270940163297 0.0209738779605 0.0204474607028 0.0105739124512 0.0137437284214 0.0226997190651 0.0150100972387 0.0323011347894 0.008153166122 0.0475382748664 0.0187641297777 0.0164404692163 0.0144508320868 0.0210394349685 0.0305556358824 0.0486242638836 0.0214513889049 0.030175982789 0.0140964441541 0.0400575333251 0.0288561044949 0.0169609734513 0.0143755139823 0.0284025548343 0.0224203703593 0.013792641556 0.0304826758057 0.0219984307058 0.027438003111 0.0138498079587 0.0391014082904 0.0268826742795 0.0187520448645 0.0110691342578 0.0424853466078 0.0102534616754 0.0160596134656 0.0266767705353 0.0127998581546 0.0173245890268 0.0173211449901 0.0131739480428 0.0158792885006 0.00835247262611 0.012879803971 0.0267489620569 0.00596710410688 0.0651865992814 0.0501303259948 0.0467631748034 0.0452702235248 0.0544981935174 0.0559804285713 0.0526556031199 0.0527154428326 0.0609295585885 0.0469864956417 0.0318305696362 0.0467870360621 0.0428839020559 0.0596952123639 0.0682904935263 0.0490145651142 0.0373470202066 0.0372051258398 0.0783094346225 0.0573164765228 0.0464381891239 0.0518006344835 0.0637213875725 0.037328539174 0.0398159521041 0.0364375689163 0.0327311081772 0.0300536558581 0.0548465086523 0.039011355091 0.052932116237 0.0253006236285 0.0581288867478 0.0321437027692 0.0554616174667 0.0518535767409 0.0554912699649 0.0361633495108 0.0300500951908 0.0376868875379 0.053920841562 0.0443787428504 0.0501303259948 0.0504194914628 0.0549441033997 0.0487769599128 0.0505784847321 0.0377653392745 0.0513716565226 0.0379306609676 0.0148794236891 0.00906915259144 0.016817766784 0.022666965263 0.0181414830259 0.00943690730089 0.0254552863313 0.0160108606893 0.0217205062473 0.032481574081 0.0125336713193 0.0301048876528 0.0144114156753 0.0283145714635 0.0236499286545 0.0356994826174 0.00980454037789 0.0380382046842 0.0138535372004 0.0270349861178 0.0179310476065 0.0105970849446 0.0106446588122 0.0120133548138 0.0166102052911 0.0152998463146 0.0290596443937 0.00834773888805 0.0173051001714 0.012006228531 0.0322817993752 0.0342847484172 0.0334965405575 0.00578703582339 0.0267498608594 0.0233284219943 0.00836772170811 0.018341518653 0.0127097608473 0.00603575216863 0.0145919151911 0.0109073587446 0.0221533308504 0.0160929276419 0.00858765501382 0.0355326614102 0.0120318414802 0.067539995406 0.0482957753815 0.0406895564252 0.0402187129364 0.0525505335689 0.046337446086 0.0538706159523 0.0410532995186 0.049945680623 0.0498002070389 0.0356573113194 0.0408170339224 0.03998131445 0.0570983300794 0.0703114871106 0.0527515586 0.0326360388464 0.0357703215059 0.068007735392 0.0448145788863 0.0463929144792 0.0539072356938 0.0519007533329 0.0322898342044 0.0406511091494 0.027662402635 0.0308780067072 0.0322628111538 0.0510866561777 0.025649802677 0.0412344607513 0.0218269690426 0.0551778627391 0.0220763570226 0.0446670171533 0.0446820028935 0.060359178521 0.0338187065026 0.0337343667693 0.0362783001067 0.0544202026485 0.046225200341 0.0482957753815 0.049817335882 0.0576169439653 0.0494636606912 0.0440581542748 0.0371733017103 0.0573289834367 0.0394045266195 0.00716257307188 0.0232372547893 0.0296783885761 0.0325204811189 0.0212497773179 0.0292620976856 0.016439517739 0.0284506794787 0.0345599570635 0.018188478274 0.0407426749726 0.0271225123173 0.0318719827355 0.0364884226289 0.021914442716 0.0227786086069 0.0376078412936 0.0256329664506 0.0146652900265 0.0257412263731 0.0241649286356 0.0229439407062 0.0136364254073 0.0100256292567 0.0184950349026 0.0419365600148 0.0198805169881 0.0290470401931 0.0259349477967 0.0239003990661 0.037192775807 0.0407761513521 0.0178664543062 0.0194803036782 0.0346542430628 0.0113663417752 0.0228609184952 0.0221099611853 0.0198250112005 0.0281996969801 0.0190943935979 0.0354944682629 0.027502181016 0.0232700909093 0.0459502516894 0.0236639254111 0.0598327139142 0.0386582188727 0.0283018389706 0.0317793366202 0.0424462733589 0.0338166029949 0.0467225177273 0.0306128740578 0.0370882424143 0.0426365040044 0.0306518342203 0.028353389025 0.0289452185335 0.045605512774 0.0609187137277 0.0456096520871 0.0247062397964 0.0307047013286 0.0541889879817 0.0321986089116 0.0369056583991 0.0461635888574 0.0394191509814 0.0202672234717 0.0339543216254 0.0203331670034 0.0216962768111 0.0275014797571 0.0396207876643 0.019383687668 0.028134515225 0.0203542968498 0.0436399460151 0.0166979961449 0.0388212853573 0.0305366541923 0.0544557347145 0.0282093601187 0.0297076171269 0.0264388860156 0.0445799816631 0.0375526043804 0.0386582188727 0.0404030430489 0.0493940445937 0.0396161416365 0.0302144214958 0.0282464575757 0.0525063270042 0.0345543356833 0.0213910735206 0.0254312508458 0.0257342205184 0.0165221855109 0.0259539784544 0.0158835031429 0.0233831935041 0.0339323139257 0.0162106434865 0.03399674531 0.0200386549433 0.0299171917849 0.0320747556874 0.028046974659 0.0176794481153 0.0368388291758 0.0185008751091 0.0214981879903 0.0241691252685 0.0175651807134 0.0158907875652 0.0122369735355 0.0109776452166 0.015527622801 0.0354957768624 0.014819189185 0.0241388300803 0.019062303667 0.0287269405771 0.0357916006232 0.0364633531028 0.011219279972 0.0225748368915 0.0292557862917 0.00702356235419 0.0224830610185 0.0181511844266 0.0136555064616 0.0214280218685 0.0152776051594 0.0301845131917 0.0223495731118 0.0167956602264 0.0389756608983 0.0178914144765 0.0630604023291 0.0430080788574 0.0341031378994 0.0365519407683 0.0470199755056 0.0406269394348 0.0499352678431 0.0370580183618 0.0441324571638 0.0450148609009 0.0313792244576 0.0341228348643 0.0333031398497 0.0505703458181 0.0642434790604 0.0476293944045 0.0288843329998 0.0334940865056 0.0612862785232 0.0392274534904 0.040346557275 0.0490663667656 0.0464449738085 0.0249899515531 0.0366261362911 0.0249803957153 0.0245501998624 0.0286632518672 0.0448796558538 0.024023128213 0.0352285170514 0.0213779538928 0.0486983458625 0.0204530056806 0.043719713027 0.0366461987398 0.0563678077617 0.0312461294161 0.0303695774583 0.0296679302141 0.0480887950749 0.0392929320337 0.0430080788574 0.0443169978812 0.0521158245642 0.0424602914349 0.0361040078467 0.0312787145737 0.0538034695951 0.0366723303295 0.0189709615579 0.0257111384326 0.0217335533305 0.0223113040739 0.027967478664 0.0234852927562 0.0436322798908 0.00521461133699 0.0364760096592 0.0272343470762 0.018116669378 0.0277674536165 0.043201573857 0.0213285903305 0.0304538340544 0.0273512817022 0.0293661558134 0.0151289313008 0.0243064809516 0.0249356313294 0.024322988602 0.0196946095768 0.0139855052687 0.0405716245072 0.0223909261135 0.0287463352875 0.0239629151594 0.02510185342 0.0224608355139 0.0285849443101 0.0178309380123 0.0374526670612 0.0202528662781 0.0157931802813 0.00761690092841 0.00770880409418 0.0208073209186 0.0270724449013 0.00859765119326 0.0186885305588 0.0137210864523 0.0193875507074 0.0459800217652 0.015147695259 0.0594631949225 0.0420406539118 0.0381002519704 0.0316288068581 0.0464453358384 0.0430647918893 0.0450678160713 0.0372936406921 0.0478953066773 0.0442824164473 0.0325191406563 0.038458833697 0.0379877190085 0.0530105166244 0.0661049064219 0.0485790002934 0.0252530768184 0.0252616659632 0.0666194586026 0.0445179442461 0.0429682351205 0.0475934680999 0.0500384221554 0.032835448251 0.0334890161181 0.0210425012042 0.0304165560005 0.0269992218058 0.0464858418191 0.0236376116141 0.0409389296383 0.0119097107153 0.0510342004912 0.0165862044563 0.0364051705359 0.0459765436849 0.0532158463913 0.0240697227805 0.0285944307415 0.0355069812685 0.0512442268801 0.0481793211849 0.0420406539118 0.0442662890139 0.05312949223 0.0494910921385 0.04499219501 0.0346009573898 0.0502278687979 0.0300299308142 0.0232712606741 0.0301762988443 0.00654363258115 0.0383536762695 0.0073760670349 0.0549696818035 0.0187358469678 0.0230006125585 0.0248620926321 0.0112006081505 0.0374302122761 0.0507791829944 0.0291552453818 0.0203658790931 0.0243768337268 0.0405838171216 0.0318523232399 0.0264398107213 0.0241549255092 0.0342108755533 0.021966370396 0.0115756992435 0.0406700397742 0.029726336077 0.0360763557135 0.0237708872008 0.0347952708329 0.0167090325333 0.0113314779127 0.0183460384347 0.0474972085699 0.0106130759732 0.0188803713912 0.0264255690706 0.0134988723184 0.025504997589 0.027405254367 0.0141844800377 0.0190580694839 0.0107985492476 0.0215712414492 0.0353201768965 0.0121413245456 0.0563908913649 0.043304104353 0.0426898087169 0.0394771848203 0.0476531796683 0.0529352282385 0.044079995111 0.0508430194559 0.0587105683651 0.0390993709526 0.0249779507582 0.042747127932 0.0380731496814 0.053623295371 0.0609715530482 0.0414798961609 0.0320700568798 0.0299307088952 0.0756552859532 0.0564187262812 0.0401315540974 0.0437426630802 0.0619393205223 0.0343990170803 0.0321000148697 0.0335451545545 0.0286757976711 0.0228629245983 0.0490706959168 0.039335399235 0.0517108395757 0.0207931533413 0.0521719534959 0.0306385429933 0.0529657791748 0.0494073245643 0.0465244230409 0.0294935309982 0.022387741137 0.033135656028 0.0474637239198 0.0404408008061 0.043304104353 0.0434045699957 0.0474449484031 0.0438694244062 0.0478136370998 0.032205230585 0.0422362620829 0.0295056722481 0.0179188644218 0.028584000913 0.0304649482776 0.0216593575612 0.0411663680687 0.024518088592 0.0182283990095 0.00873472135302 0.0327024039756 0.0205661418233 0.0530659348987 0.0158790060301 0.0434559310925 0.0108179884547 0.0451149656888 0.0268976993783 0.0108384529361 0.0115766662184 0.0275567255166 0.0316971869351 0.0249562865626 0.0177154908046 0.0178955252093 0.0182906823384 0.00749636648732 0.0477714342488 0.038203551745 0.0295629098692 0.0151014852938 0.0424264199032 0.016651504005 0.0233361298106 0.0296064297332 0.0193940010564 0.0143559792056 0.00822362335298 0.0204472045407 0.0141062231631 0.0150969431722 0.00961358010122 0.0230069897016 0.0136344497691 0.0781873312389 0.061922358127 0.0567947708263 0.0549074799203 0.0663268940474 0.0637272258979 0.0651276548535 0.0583171690756 0.0677292446726 0.060191106251 0.0450946120505 0.0568830058441 0.0543288790766 0.0714589426004 0.081454303397 0.0624243269582 0.0472314410611 0.0479124253475 0.0858307227677 0.0627914211132 0.0589518720089 0.0648453559762 0.0697319385158 0.0476614784634 0.0522209851297 0.0435983831723 0.0443141443319 0.0427834724857 0.0660408790701 0.0423473900154 0.0592034189384 0.034371623803 0.0697342027164 0.0381294864628 0.0595840773258 0.0612390496755 0.0689539919437 0.0466415215244 0.0431628346368 0.0495434525796 0.0666477691564 0.0566659496435 0.061922358127 0.062735676927 0.0682003243846 0.0612178236271 0.0603270178223 0.0499653486319 0.0649071391116 0.0500436367076 0.0339571590708 0.013301666182 0.0294613107742 0.0254168369791 0.0187830848109 0.0337456927151 0.0151760493477 0.0360233288761 0.0161149469416 0.039042024755 0.00229536147376 0.0468249873675 0.0157583667263 0.0310761119725 0.0155497505138 0.0090301180649 0.0126416375855 0.0110159110748 0.0254616235228 0.0242814349621 0.0236690592627 0.00236651040215 0.00842631126501 0.0122777482071 0.0387720000407 0.041740632348 0.0402615455592 0.013434165421 0.0259957062278 0.0279382933191 0.0176919201528 0.0210030594474 0.0190515742522 0.00605687883869 0.0125440326934 0.0183812478057 0.0234838433483 0.0213287295776 0.0102611349583 0.0367608397052 0.018305882063 0.0762459812633 0.0565232340764 0.0480647092458 0.0470080033305 0.0607210126742 0.0515562009886 0.0623134210102 0.0445281266442 0.0542656274802 0.0589172543408 0.0450145677229 0.0482495838943 0.0484339717661 0.0652417317109 0.0793204584838 0.0620264746962 0.0400173133601 0.0433798691859 0.0725797896424 0.0481813389954 0.055284998644 0.0628522019597 0.0554630904838 0.0404443368976 0.0493602212467 0.0332419109523 0.0398075629785 0.0413677301877 0.0589057964055 0.0278286603835 0.0454781228443 0.029186418008 0.0632373035893 0.027173160002 0.0466041993476 0.0516248397682 0.0694549695494 0.0414073101396 0.0429302830553 0.0451579600752 0.0633364240737 0.0552241510377 0.0565232340764 0.0584051969715 0.0667610558404 0.0584355181495 0.0512925402879 0.046128531904 0.0664605075068 0.0478081299752 0.0403784266437 0.00849189377523 0.0578244327435 0.0213782060591 0.0260735654855 0.0286868543518 0.0107633182991 0.0429130184438 0.049112317265 0.0332704837119 0.015321761445 0.0277153223765 0.0397499694056 0.0358925712782 0.0304111104426 0.027505083584 0.0362377250231 0.020296537393 0.0117771620381 0.0454349567531 0.033203078919 0.0404550655622 0.028050968973 0.0329121522515 0.0157822820327 0.0128529655968 0.0212960295437 0.0481179953609 0.0170390332384 0.0197624290813 0.029414246654 0.0176689832365 0.0290804953813 0.0317312604538 0.0173770193477 0.0255273516204 0.0167054579942 0.0259572392414 0.0381174742782 0.0168503391466 0.0510138836142 0.0385473731826 0.0388016187658 0.0368178557203 0.04276997017 0.0503778656709 0.0392641023806 0.0497809957465 0.0564266766196 0.0332095636962 0.0188695135337 0.0387787813789 0.0331502464834 0.0483979502244 0.0548055143427 0.035228159998 0.0294466884888 0.0272290139414 0.0724784935896 0.0548011964122 0.0344489111929 0.0380672364964 0.060026483594 0.0303284751736 0.0272545178904 0.0327801045139 0.0239410636139 0.0177611799925 0.0444007657806 0.0400513163792 0.0497786040376 0.0204852863736 0.0470867569115 0.0308376722615 0.0532970554221 0.0452748414675 0.0407821021663 0.0268849282918 0.0168086928609 0.0279020988959 0.0414910752652 0.0343097482389 0.0385473731826 0.0381367753126 0.0413101510581 0.0377465227401 0.0435171810289 0.0268955341281 0.0365057689544 0.0254190380854 0.0372013435687 0.018490846954 0.023945912793 0.0440067159299 0.0253810829497 0.0423576975539 0.0266775910883 0.0276067076791 0.0155964706338 0.0512008225103 0.0249293141549 0.0224091031216 0.0208182862063 0.0201027144741 0.0218258381078 0.00416795120123 0.0250523436876 0.0291897433081 0.0336104473732 0.0126474260294 0.0179897427224 0.0236687976248 0.0357767564718 0.0479080621505 0.0493839683144 0.0213027411218 0.0127962715487 0.0389577422313 0.0207982711984 0.0248766740175 0.0272195414617 0.0164396805465 0.0239043019084 0.0253433507519 0.0357454256498 0.0316012688091 0.0218891275488 0.046006369731 0.0279699723289 0.0753592714385 0.0538300830176 0.0427234551893 0.0442120918411 0.0575724115573 0.0438794711912 0.0617203772126 0.0368175055007 0.0452394206226 0.0586608041232 0.0462735571883 0.0428932945735 0.0449361857949 0.0608531076963 0.0771129194067 0.0618336393757 0.0380658785095 0.0436894906412 0.0627047512052 0.0382553066725 0.0531191059331 0.0620656917263 0.0459127944105 0.0362899248016 0.0492167826913 0.0301637294202 0.0380460619309 0.0426550687382 0.0544072702012 0.0218332584664 0.0362322493618 0.031226533309 0.0587827461321 0.024852970378 0.0417700516252 0.0442659791406 0.0702283886386 0.0412678777108 0.0447863140602 0.0428258891548 0.0608093994885 0.0534400200237 0.0538300830176 0.0561072465285 0.0656581075266 0.0558523559264 0.0444818994194 0.044500683531 0.0680831562837 0.0488303143353 0.0536500814259 0.0217777943298 0.0182308184172 0.0213271967089 0.017640751735 0.0384313065823 0.0491331799987 0.0285490579199 0.0236508921207 0.0203518801552 0.0410544418794 0.0345949638149 0.024163451076 0.0208164937907 0.0331698057229 0.0214370208347 0.0129093004816 0.0378630437774 0.0285080143021 0.0349561724295 0.0213643314537 0.0378207671811 0.023156173716 0.0144257922467 0.0164328495191 0.0458898771597 0.0137078716838 0.0180512548912 0.0303444429836 0.0166949845236 0.0239375979831 0.0248141920509 0.0164642247089 0.0221495310709 0.0136759772653 0.0203492925476 0.0297801495496 0.012327304817 0.0589145352863 0.0454329273476 0.0436909431618 0.0429329583507 0.0496681092117 0.0546717056664 0.0472190839202 0.0531335572006 0.0601545762173 0.0404993410456 0.025351107679 0.0436326686228 0.0384109589864 0.0546942484238 0.0614849395874 0.0420121432296 0.0350678306139 0.034181943564 0.076607832627 0.0574938051354 0.0407284888751 0.0455392391661 0.0634559846389 0.0340449392101 0.0346886110004 0.0365094436091 0.0283668149646 0.0247441178114 0.0505723350307 0.0414268809452 0.0526572068528 0.0247442404176 0.0533283030157 0.0333480302269 0.0569330422551 0.0488445949836 0.0485166318417 0.0334202567015 0.0241088713298 0.0327605074387 0.0477602460683 0.037977730422 0.0454329273476 0.0450703466816 0.0481686005759 0.0425586735361 0.0473020173719 0.0326189496433 0.0442268465034 0.033265894657 0.0406164099076 0.0563656658624 0.0369501005762 0.0596090654503 0.0304778147307 0.0384204581231 0.0270915625525 0.0693260105947 0.0372136698768 0.037023479673 0.0317062741776 0.0317712182279 0.034804368211 0.0223060266021 0.0435100417127 0.047004084469 0.0362329082931 0.0256397756303 0.0233719185841 0.035627421568 0.0521858971919 0.0650552610656 0.0654396577163 0.0372909018916 0.0208395075735 0.0532980934411 0.0387978654626 0.0393043110859 0.0433091405548 0.0302415541747 0.0340064333643 0.0421117035102 0.0478257550421 0.0466283489206 0.035247716101 0.054937797688 0.0434605477125 0.0930034854114 0.0710598303107 0.0590352084097 0.0604460830393 0.0745555173324 0.0571183440581 0.0793617023165 0.0484952151811 0.0566053521726 0.0768259736839 0.0646780127707 0.0592344115045 0.0623544010763 0.0773513341111 0.0945306112415 0.0799933849368 0.0552426925494 0.0610368934489 0.0726391747026 0.0486086004123 0.0708765879781 0.0800292786375 0.0558578611463 0.0537011940437 0.0672777153371 0.0461852109805 0.0561622363977 0.0610513630375 0.0708198771797 0.0350965268909 0.0484124521051 0.0486964562823 0.075256407672 0.04111323881 0.0515712020719 0.0592740141065 0.0884497661149 0.0586350703024 0.0632023881374 0.0606979638812 0.078320587954 0.0707259147977 0.0710598303107 0.0736338137855 0.0835800603427 0.0732101393079 0.0599496433571 0.0625510181289 0.0864139348042 0.0667637091982 0.0350728110176 0.0244007367668 0.0194418255357 0.0278046770383 0.0387260088041 0.0187288873365 0.0305873095592 0.0241694671072 0.0257451620145 0.0153815378858 0.021353060898 0.0215496497445 0.0200984831417 0.0151985245998 0.0110228413253 0.0387908454863 0.0190085239065 0.026530841158 0.0213969993651 0.0238500818663 0.0245406806879 0.0294364678495 0.0140120927131 0.0331219747793 0.0211978333105 0.0108265764026 0.00897607251111 0.00677241917437 0.0173770286262 0.0245814114194 0.00563326172159 0.0204952689406 0.0138047466575 0.0168569359655 0.0438273371107 0.0133334601758 0.0592456323654 0.0408071286879 0.0355509485261 0.031081872872 0.0452073820968 0.0410054777303 0.04495248578 0.0356498623047 0.0456510923283 0.0431383529174 0.0306345228853 0.0358468154266 0.0353265194622 0.0511623196634 0.0646276367384 0.0471382277373 0.0240229200194 0.0254553208119 0.0642596486532 0.0418982548195 0.0409421092482 0.0466753277343 0.0478887558589 0.0293373407147 0.0326161841904 0.0195735225778 0.0272641873276 0.0254981680323 0.0447040552558 0.0215611560931 0.0381253844376 0.0111199895155 0.0491745036263 0.0145691145727 0.0368088577033 0.0425091408325 0.0528722213324 0.0238501784613 0.0272505069941 0.0325614342759 0.0492627638247 0.0449950723976 0.0408071286879 0.042880253884 0.0517160325388 0.0466599102029 0.0415966376912 0.0321745745193 0.0499813496989 0.030016506883 0.0195067443548 0.034042657808 0.0387050626348 0.0607833234632 0.0322025711177 0.0402462250605 0.019510297358 0.0548235012014 0.0429595771314 0.0252279466187 0.0223273919125 0.0407231129516 0.0368354815518 0.0293953937656 0.0290463736365 0.0328461116776 0.035143771121 0.0215923461726 0.0549385181088 0.0388660143681 0.023623079783 0.0246479181765 0.0539903453268 0.0196033552608 0.0309786977565 0.0425462811063 0.0289476729073 0.0283702078661 0.0228452205014 0.0295421719187 0.0252210967901 0.0230772437036 0.0240590413191 0.0135013916275 0.0218148808282 0.0760114892469 0.0634029220478 0.0607879784498 0.0607163777171 0.0675569624906 0.0711706523576 0.0650651993965 0.0685468614076 0.0760185520266 0.0575507664821 0.0425210776149 0.0606855343213 0.0555229333703 0.0720565340813 0.0774071503121 0.0581416488422 0.0527363337967 0.0521637490438 0.0926239124524 0.0722776295267 0.0578783465588 0.0627903946651 0.0789206671037 0.0503951188312 0.0527646858335 0.0526096570134 0.04527903594 0.0427992547678 0.0682205850017 0.0547141937568 0.067807383273 0.0414244261778 0.0707718291801 0.0483573376556 0.0717861683592 0.0644987372167 0.0647006499463 0.0513517515126 0.0419564687506 0.0495341433982 0.064364362053 0.0518587226049 0.0634029220478 0.0627776825589 0.0645131806296 0.0578143875392 0.0631581345522 0.0499505499876 0.0600491597535 0.0513712453732 0.034450057717 0.0238219725869 0.0465207238958 0.0139550218762 0.0437148052069 0.00245848843359 0.0406705408218 0.027996084796 0.00617634742576 0.0042140797155 0.0226171443798 0.0276920922451 0.023535776763 0.0178395839466 0.0140356715542 0.0166022608919 0.00395476408572 0.0457707350729 0.0404789526881 0.0327219703849 0.0114454148489 0.0362680793242 0.0217463268752 0.0196365744138 0.030477863462 0.0206103562172 0.0102714385383 0.00460445870022 0.0203218526125 0.0212018784388 0.0183586998171 0.00829854248447 0.0218572079865 0.0144443422105 0.0767877803745 0.0594466868469 0.0529468255635 0.0532520366965 0.0637146030163 0.0600960587419 0.0639510014921 0.0553587219012 0.0637257572972 0.0581054381403 0.0427611569751 0.0529559974475 0.050488809742 0.0679805704334 0.0784814462329 0.0599903091584 0.0453120703317 0.0472956572051 0.081207762841 0.0585100350392 0.0557351056658 0.0628545029747 0.0657746195642 0.0431635890019 0.0505766161122 0.0416755044323 0.0404647152471 0.0409948112567 0.0627553433594 0.0398795409571 0.0548556307871 0.0337749376373 0.0662751646447 0.0362650420971 0.058753352182 0.0559719307576 0.0677796526261 0.0456851568449 0.0415580901724 0.0455930571155 0.0632638085815 0.0519953365762 0.0594466868469 0.0601182129397 0.0655928087235 0.0568949371894 0.0552051980285 0.0466604473635 0.0640269526764 0.0493532376221 0.0428114080859 0.0513187599237 0.0353300715155 0.0136841990953 0.0339659047401 0.0392151216672 0.0331598693815 0.0346578423583 0.0330024342773 0.038276947888 0.0230224626975 0.0145213724276 0.0501490398304 0.0358866478447 0.0428680377048 0.0326728263648 0.0285835471973 0.0062386073831 0.0158573158736 0.0257280664001 0.0503050577684 0.0188536314021 0.0230369129683 0.0250256284162 0.0171680840121 0.0324912467925 0.0363869094834 0.0177612063031 0.0250245943265 0.0179627656197 0.0294197347692 0.0465091219099 0.0203095862842 0.0478795030574 0.0357982238192 0.0376241763265 0.0315623347361 0.0401002610089 0.0474897864671 0.0350582543035 0.0460362585126 0.0538318698513 0.0325365856585 0.0213227378076 0.0377990397126 0.0335693240845 0.0471274543161 0.054885621667 0.0360897261843 0.0253548565171 0.0209548211511 0.0704293001167 0.0527939534511 0.0346935966786 0.0364130601336 0.0572327947958 0.0318390512051 0.0244040617426 0.0286768378156 0.0261566126157 0.0173773142125 0.0423900706098 0.037171182215 0.0481110975007 0.0159454061909 0.0456669379702 0.0274665151098 0.0469607798152 0.0462908705234 0.039158298024 0.0212460794033 0.0171141394847 0.0299475002492 0.0420369583583 0.0395076063195 0.0357982238192 0.0363456286371 0.0414629362584 0.0409765644808 0.0445581081298 0.027819794971 0.035217696192 0.0204081888775 0.0536229831874 0.0147412411033 0.0555896434003 0.0257773616784 0.0440411962897 0.0177255540439 0.0193829905731 0.0239978426404 0.0256940264499 0.0393410530504 0.0352411367288 0.0221731284836 0.0182277280594 0.0123391761488 0.020654487001 0.0493059373458 0.0475681206642 0.0451873269558 0.025880190654 0.0390288508413 0.0309214453223 0.0313196281309 0.0264802426158 0.0266703711652 0.0198006836859 0.019520741807 0.0278514155812 0.0221034761072 0.026690732588 0.0196134707881 0.0412756734602 0.0265178671443 0.0867397306197 0.0682110021203 0.0611304199736 0.0570363597121 0.0725160802101 0.0632079822129 0.072465417084 0.0544582268199 0.0658094706322 0.0704919404627 0.0569226677141 0.0614294730407 0.0618668588016 0.0780403185985 0.0918397178914 0.0740264863472 0.0508436174709 0.0525989509117 0.0847012354501 0.0597190404435 0.0681089165817 0.0742388101657 0.0664380426823 0.0545769816583 0.0603124830305 0.0434291774759 0.0533790593767 0.0526730162139 0.07136820435 0.037381607524 0.0577096830472 0.0386410478706 0.0759869972218 0.0373998132392 0.052181569916 0.0660810261842 0.0798969360666 0.0510826914494 0.0540234946407 0.0588039304452 0.0763291167757 0.0694007087698 0.0682110021203 0.0704124305268 0.0789271737863 0.0724038719993 0.0657040131714 0.0590995697111 0.0765722724394 0.0574793382897 0.0411299322154 0.0539204969946 0.0448971315706 0.0161163886115 0.0424587591617 0.0433134103038 0.0424269061508 0.0282464178608 0.029088245744 0.0394507490162 0.0585960215177 0.0377541724349 0.0451043153526 0.0458829699472 0.0321616028135 0.0557124503126 0.0614547711288 0.0392057260171 0.0177559051904 0.0564954090624 0.0332181102648 0.0405454141429 0.0436595776199 0.0393478274847 0.0472383546112 0.0407140770645 0.0569892712332 0.049354627832 0.0439022116296 0.0637553975882 0.0455099918129 0.0646100490562 0.0426539647843 0.028431151743 0.0378227253234 0.0447657124399 0.0278050988657 0.0537117960824 0.0274550112394 0.027109787916 0.0505464931112 0.0435971104256 0.0283504838988 0.0329648689469 0.0443909167392 0.0625701957077 0.0530229796398 0.0343871989004 0.0426240079793 0.0396498771349 0.0213457948378 0.0419360502864 0.0525916757896 0.0283713421426 0.0258314536376 0.0439902997437 0.0303827915475 0.0319578433558 0.0415135400801 0.039276736137 0.0267873256159 0.0192565547583 0.03784586336 0.0425882783228 0.0300770949457 0.0411024644378 0.0235206108502 0.0627241786637 0.0400442811976 0.0439864971889 0.0336917004451 0.0474026229045 0.0427127785344 0.0426539647843 0.0447428873557 0.0545498150062 0.042821329639 0.024917555179 0.03656154349 0.0624685036836 0.0466134489709 0.0464211898017 0.0148261628982 0.0328892390973 0.0156382282878 0.00785541359441 0.0118772597644 0.013233811524 0.0262299374219 0.024092235964 0.0223052815816 0.00384479195153 0.00794260597132 0.0107672255318 0.0397240757159 0.0410257993957 0.038935751255 0.0130110302225 0.0282819504394 0.0262547979726 0.0181226466418 0.0211289335782 0.0182692961983 0.00567846812613 0.0110316054291 0.0179271098732 0.0214946645806 0.0199268176637 0.00875561948865 0.0354271933282 0.0171443662393 0.0766374045646 0.0572939392451 0.0492966948245 0.0478544225205 0.061544844174 0.0531089657897 0.0627070970919 0.0461191081801 0.0559975469422 0.0592703107177 0.045198111152 0.0494826907565 0.0493817865713 0.0662451441692 0.0799247658409 0.0623534855885 0.040791427856 0.0437433118438 0.0743922416686 0.0500539193261 0.0559768175635 0.0632764008918 0.057263588801 0.0415573436738 0.0497522962913 0.0342757495228 0.040553840562 0.0415809282216 0.0599519771943 0.0293556022635 0.0472716928172 0.029440558171 0.0642586667075 0.0282006776448 0.047766217977 0.0531327322745 0.069584244341 0.041867047458 0.0430265316703 0.045945636772 0.0640480645613 0.0558532996544 0.0572939392451 0.0591020248311 0.0672221887664 0.0591786574244 0.0527229760336 0.0467939943149 0.066452337221 0.048010057481 0.0426662887331 0.0439949614302 0.0453715955589 0.0447907897725 0.0421151539334 0.0471958607455 0.0276193449237 0.02275846799 0.0605343055308 0.046215555164 0.0540603153716 0.0428037480825 0.0313795261955 0.0125172459763 0.0208290281635 0.0350621941996 0.0569552641601 0.0299458792012 0.0309610087229 0.0366049420462 0.0292005950568 0.0426658539169 0.046550389751 0.0288402250941 0.037809939324 0.0299619998536 0.0401350153602 0.0525912191682 0.0310232912468 0.0370292782789 0.029362531619 0.0351405044644 0.0315813022426 0.0330583005053 0.0477931213785 0.0264557013575 0.0497838425895 0.0546676669155 0.0214340498054 0.0132666230167 0.0351245595911 0.0284887636401 0.0394555006358 0.0430107543153 0.024001610215 0.0265746605534 0.0212961513987 0.0686985381679 0.055308045978 0.0261438582005 0.0256904480148 0.0588231449389 0.030013743601 0.0177237336601 0.0340579413817 0.023239083403 0.0129096702298 0.0366510262704 0.0448114548715 0.0501265325324 0.0238298010541 0.0385248366721 0.0348349791307 0.0531258509004 0.0432244806886 0.0265717263897 0.0222990802833 0.0105547424712 0.0247433535936 0.0319856839059 0.0308056274733 0.029362531619 0.0282110860995 0.0297242114734 0.0317000113527 0.0411003820059 0.0219347750579 0.022168792283 0.0155469808263 0.0395024062804 0.0287670845389 0.00713435108734 0.00340555052012 0.0220748170877 0.0261325674803 0.0225086838709 0.0197301659136 0.0143163055062 0.0179547860371 0.00552830002832 0.044759241534 0.0400746442935 0.0324889323762 0.0105741309237 0.0352991764051 0.0223168808509 0.0183878728912 0.0306904112299 0.0206021475442 0.0104104939928 0.00670538845305 0.0199820984844 0.0226302237702 0.0187233601872 0.00907856174289 0.0219615297998 0.0143637742128 0.0753337380263 0.0579434032885 0.0513377916805 0.0521965044751 0.0621695045027 0.0588078748161 0.0626419440897 0.0544695113035 0.0624620281749 0.0565017669041 0.0411290585403 0.0513195597187 0.0487054775012 0.0662358469866 0.0766400648909 0.0582604755345 0.0441853596565 0.0463597810918 0.0797172873623 0.0573362873289 0.0539615186802 0.0612879452506 0.064613107429 0.0413483548752 0.0492060616579 0.0408536005927 0.0386658835072 0.0395835329499 0.0611338973657 0.0393645393569 0.053572584104 0.0330686361703 0.0645571164168 0.0355933171917 0.0584689678276 0.0540828717859 0.066313152361 0.0447064134304 0.0401381607957 0.0437269026711 0.0614075519202 0.0498319714223 0.0579434032885 0.0585128670581 0.0638402708268 0.0548281142484 0.0533071471302 0.0449195692472 0.0626153729552 0.0482343239424 0.0295703841711 0.0366619395227 0.036500768924 0.0216100790796 0.0202086133386 0.0291081498137 0.0538445159183 0.0304641791012 0.0384602037434 0.0388505887816 0.0184939827468 0.0431509831641 0.0514497599393 0.0311190761962 0.0193480902941 0.0458820296239 0.0240997287473 0.0258856149523 0.0318135695083 0.0318488388141 0.0408906139312 0.0292269134036 0.0455476970026 0.0384416242221 0.0355459730354 0.0604270677753 0.0357756191383 0.0579552498509 0.0353240752803 0.0225716123758 0.0257247517645 0.0383804782921 0.0215488097029 0.044948702922 0.0168022708851 0.0235401078242 0.0440963094143 0.0364932029322 0.0228797040221 0.0280879909327 0.0410984380147 0.0600604954457 0.0480533666552 0.0218775898923 0.029791578282 0.04158778439 0.0178882014032 0.0370867411707 0.0460836281119 0.0252155578724 0.0208740877353 0.0346851323104 0.0150019768994 0.0257835645737 0.0322493089661 0.0342156649906 0.0116183721538 0.0144931943243 0.0240188836176 0.0389068297986 0.0144502424291 0.0276541092905 0.0250061652639 0.0558771163058 0.0271661677907 0.0351271284352 0.0289684152137 0.0442230773604 0.0424650907505 0.0353240752803 0.0384614305736 0.0501641618566 0.0418657156844 0.0254781137454 0.0303709238998 0.055175185082 0.0355533707256 0.0226236551837 0.0257243733887 0.0188708962976 0.0280458447088 0.0259495603965 0.0358126700891 0.0173972925493 0.0203550537734 0.0241984077655 0.0326658712857 0.0374251933899 0.0415458358848 0.0220997016335 0.0314739029711 0.0297599572502 0.0224642058136 0.0101652715813 0.0184552115135 0.0190795074292 0.0255522841638 0.0188568594492 0.0233889500734 0.0229626929389 0.0201746110209 0.0490887418108 0.0229752614583 0.0721999639917 0.0528489482775 0.0457665690796 0.0402580156331 0.0570067559398 0.0466251225309 0.0575932728503 0.0376765605581 0.0495760090258 0.0573857799099 0.0457294834507 0.0461836275015 0.0478961825174 0.0628300939828 0.0782978077723 0.0617431168606 0.0349090738753 0.0372003973766 0.0687801137491 0.0442160827041 0.0545072221083 0.0604041835816 0.0503536267186 0.0412302683921 0.0462335606677 0.0265634879228 0.0409252862943 0.0403112510912 0.055713517152 0.0210444978159 0.042126696634 0.0241765196843 0.060672423249 0.020816933598 0.0350778856202 0.052005350197 0.067095717986 0.0355534496705 0.0422916670752 0.0461036815572 0.0628103650139 0.0591144676161 0.0528489482775 0.055715116829 0.0658580956838 0.0605104799512 0.0516473901424 0.0459913703308 0.0644951804376 0.0433395766961 0.00476396676703 0.0174479971383 0.0262117873028 0.0230803954362 0.0185627883328 0.00806620839217 0.0113814933623 0.00390319674762 0.0428177321428 0.0406615445683 0.0354624532828 0.0102263088249 0.0317971018832 0.0234656694869 0.0177926588758 0.0262276626577 0.0188776295133 0.00499606040594 0.00424685036617 0.0184558479833 0.02090066744 0.0184013332257 0.00554993882929 0.0277916235291 0.0146255258466 0.0766584518429 0.0582536091399 0.0509151171955 0.0506702158494 0.0625265075224 0.0566765790267 0.063296553303 0.0510181236531 0.0599592936046 0.0584198089247 0.0435074153396 0.0509940717271 0.0495661398107 0.0669266401341 0.0789376730305 0.0608280979649 0.0430167703447 0.0455496571569 0.0778333721022 0.054368608181 0.0555188693765 0.0628704456678 0.0616793859568 0.0418722056261 0.0499801315934 0.0381178178645 0.0399704213931 0.0409195428074 0.0612188756554 0.0349680364553 0.0510653686933 0.0315069690942 0.065089713199 0.0323745393338 0.0538917621299 0.0541667478451 0.0685063182149 0.0437802642661 0.0419063387522 0.0452698567868 0.063322234724 0.0532615551019 0.0582536091399 0.0594215850469 0.0661130238288 0.0575059193505 0.0535703049783 0.046311202915 0.0650382074097 0.0485914933238 0.018842155525 0.0240370427448 0.0209528738511 0.0208089534878 0.0111203801188 0.015917150662 0.00453488271341 0.0421322819802 0.0391458330884 0.0331083714381 0.00824403626512 0.0325132311882 0.0224847085725 0.015999516676 0.0277730715816 0.0186363801442 0.00708833477439 0.00642676690132 0.0178233234532 0.0220593051536 0.0178006297585 0.00678573265748 0.0253531080362 0.0132629896024 0.0741187095157 0.0561450172019 0.0491350925935 0.0497514221438 0.060382347286 0.0560370322524 0.0611402268324 0.051356658754 0.059586423134 0.0554899035837 0.04031824984 0.0491508161478 0.0470289167752 0.0645318276086 0.0757421493114 0.057544117742 0.0418388253062 0.0443100927117 0.0770487593095 0.0543425693403 0.0526585588221 0.0601234502371 0.0616298008584 0.039475560154 0.0476974484773 0.0379820027229 0.0371681460661 0.0383173623723 0.0591794106435 0.0360653613059 0.0506856040282 0.0307481929538 0.0627839311984 0.0325910275487 0.055237903563 0.0520576344331 0.0655568890209 0.042566036842 0.0391094232808 0.0423459347432 0.0602723002528 0.0493981873195 0.0561450172019 0.0569656980973 0.0629553672806 0.0540102942581 0.0513493159685 0.0435185067862 0.0620208318713 0.0466647868495 0.0214518595921 0.0250574648372 0.0327035392179 0.0101396500324 0.0173409936704 0.0207342919251 0.0332931627481 0.0438902497615 0.0452760516117 0.0173962021214 0.0155474932413 0.035097178823 0.0167075078514 0.0219356609327 0.0232161362242 0.0132718217012 0.0215035441799 0.0212520758854 0.0323731403418 0.0276797560909 0.0185474301296 0.0436301136913 0.0240030156204 0.0722858985812 0.0511085237891 0.0406650357841 0.0417405611462 0.0549851372416 0.0429543701734 0.0585838550979 0.0363293620395 0.0449605458738 0.0553495140321 0.0426335340052 0.0408304262584 0.0422933209142 0.0585800490211 0.0742997883793 0.058525226517 0.035206446299 0.0404435198283 0.0627381786217 0.038449263747 0.0501920171677 0.058888502058 0.0460241869732 0.0337463818569 0.0458812434451 0.0278046680998 0.0349131438742 0.0389869853015 0.0521722266345 0.0209043609517 0.0359181281747 0.0276204608178 0.056526179998 0.0222941226577 0.041152359144 0.0428797366789 0.0667722352073 0.0380782189511 0.0410414680557 0.0398735425148 0.0580112990116 0.0505852294836 0.0511085237891 0.0532571789631 0.0625496034306 0.0531021220011 0.042888351609 0.0413906647006 0.0644729984699 0.0453457651066 0.0107504885543 0.0445819304752 0.0242663168605 0.0336280858548 0.0265479358211 0.0206001571473 0.0282052030653 0.0325495864256 0.0168933846577 0.0294835383052 0.0291726320507 0.0086711740192 0.0221542204142 0.0179304565478 0.022368606911 0.0297212987281 0.0149126627655 0.0323363820236 0.0229519593576 0.0235384954017 0.0445361789109 0.0198724736323 0.0521573964273 0.0325036248406 0.025391360945 0.0274230805005 0.0366454630316 0.0344942582431 0.0390192491133 0.0331598239249 0.0393605141281 0.0342759827638 0.021260474927 0.0254073365966 0.0234580906639 0.0407813070352 0.054069225305 0.037293221414 0.0193616327469 0.0235535155332 0.0561408138747 0.0363119747552 0.0299684113953 0.0382284111131 0.0425406057591 0.0161350959097 0.0256650379468 0.0189027762255 0.0143744986934 0.0179840539103 0.0351859317755 0.0236434364907 0.0314452790447 0.0139598707264 0.0389431870684 0.016485382228 0.040355084472 0.0299825744277 0.0456481354326 0.0213868007101 0.0199817088813 0.0197684998025 0.0379808456048 0.0313521152981 0.0325036248406 0.0336827412775 0.0416843797906 0.0334784161986 0.0289224474022 0.0207816321212 0.0432746333929 0.0259935830061 0.0406061367838 0.0235468720047 0.0318323205013 0.0219961670771 0.0255808203638 0.0205571609912 0.0226267329863 0.013012280745 0.0369778751619 0.0187789289183 0.00874641080149 0.0197571101094 0.00977736627462 0.0202312308031 0.0257037864622 0.00771970434192 0.0231557657352 0.0133099999073 0.0187324692629 0.0392432332024 0.0115155558073 0.0539655210169 0.0370438046358 0.0333786958054 0.0316409822149 0.0414621149977 0.0426991016561 0.0406517401284 0.0403678402162 0.0480644681966 0.0361424947464 0.0218113942904 0.0334641673727 0.0302365918541 0.0468956611505 0.0577004528593 0.0391757625612 0.0236163775334 0.0242576354959 0.0653230280715 0.0452817250312 0.0345762397733 0.0404850805623 0.0511603238121 0.024886725311 0.0274569982987 0.0236904007661 0.0206352481789 0.0182697542476 0.041544522984 0.0289437440201 0.0406091261712 0.0126309522282 0.0451825068417 0.020490830418 0.0442568377481 0.0395946754396 0.0458654268305 0.0228937718071 0.0192636370802 0.0258703030154 0.0425840964507 0.0360074541747 0.0370438046358 0.0378930233244 0.0444471390747 0.0387351356827 0.0382610925586 0.0256300024801 0.0425154846229 0.0258478343265 0.0236633695184 0.0174990292841 0.0186530571476 0.0611247172591 0.0557656094279 0.0466088592976 0.0282320418304 0.0445464770565 0.0341524834412 0.0362512556468 0.0421364706395 0.0353354631726 0.0231187500774 0.0150626098763 0.0358189243099 0.0298015850374 0.0324439491366 0.0223980477721 0.0244141427415 0.0302418773665 0.0942328467355 0.076594072426 0.0693014297828 0.0689934026371 0.0808708035071 0.0745545238332 0.0811424703773 0.0680895982601 0.0772898278285 0.0757784502044 0.0604959493699 0.069358828284 0.0677179794145 0.0851546659241 0.0961465377952 0.0777040409423 0.0614342341624 0.0635105029946 0.0949910709403 0.0710640834394 0.0733442971825 0.0804630717269 0.0785569975811 0.0599979412685 0.0679000809486 0.0561320615274 0.0579270123928 0.0585056430456 0.0796324819571 0.0514612322157 0.068283919556 0.0494122791964 0.083373938553 0.0501855689957 0.06973964597 0.0718399524308 0.0852557355766 0.0618964253913 0.0591154896454 0.0630863991576 0.0809010142666 0.069276699042 0.076594072426 0.0775503896407 0.0833112406891 0.0743754315619 0.0713482849075 0.0642332894623 0.0813844341388 0.0662752735857 0.00943572253542 0.0115403490897 0.0385513754293 0.041736873589 0.0399182937318 0.0122452242861 0.025058464158 0.0280391379811 0.0165428935252 0.0220426648475 0.0191966494038 0.00476868066193 0.0119136882752 0.0182274158907 0.024378991238 0.0214350053307 0.00987681971674 0.0356319079498 0.0179231346829 0.0755450486118 0.0557883449648 0.0471764532756 0.0467952668235 0.0599617395979 0.0511011457957 0.0617632501287 0.0445144536833 0.0538325742859 0.0579584947727 0.0439095831377 0.0473266714409 0.047305404175 0.0642647315012 0.0781620294368 0.0608980895696 0.0396397814207 0.0431908772896 0.0719383279912 0.0477762451878 0.0541744509705 0.0619776318533 0.0551486398926 0.0392023772277 0.0486708484659 0.0332061436773 0.0385651962783 0.0405238673503 0.0580675371179 0.0281169946403 0.0449308552075 0.0290979509914 0.0622896414172 0.0272386507233 0.0473564201885 0.0503681043652 0.0686223309305 0.0411717919112 0.0420509879955 0.0438795889294 0.0621492769732 0.0535535915584 0.0557883449648 0.0575324110618 0.0656578520536 0.0569417414102 0.0500298962763 0.0450037013757 0.0656341911967 0.0473715492692 0.0144468889514 0.0470085725986 0.0485138320241 0.0451352115307 0.0199583230651 0.0301372822106 0.0318368515531 0.0256571286264 0.0278614622113 0.0258405224944 0.0121251490044 0.0124421067497 0.0257425497103 0.0259848999587 0.0265128839547 0.0147071440313 0.0356921243154 0.0239599037157 0.0844599977072 0.0649098127865 0.0563152793656 0.0552526867175 0.0691043038035 0.0592804769565 0.0705635348052 0.0516880380121 0.0615515055365 0.0669939251635 0.0527850317966 0.0564919445965 0.0566781222463 0.0735436571696 0.0873943668972 0.0699246931044 0.0483722118201 0.0515510652048 0.079719756552 0.0550353446586 0.0634817595673 0.0710367571435 0.0623974106591 0.0485852577555 0.0576004808055 0.0413084590066 0.0479219849808 0.0493882028299 0.0672450344951 0.0348287223508 0.0527462211198 0.037276801135 0.071549415301 0.0351894067592 0.0529630080357 0.0594111357934 0.0773657257414 0.0496448829109 0.0507905960575 0.0532464315334 0.0714643731585 0.0625646663061 0.0649098127865 0.0667401518449 0.0748051995393 0.0661923309263 0.0591726016457 0.0543093310288 0.074196762744 0.0559244370052 0.043526425057 0.0386335179446 0.0321768451862 0.00974615024856 0.0352347635745 0.0201959159339 0.0180776754132 0.0269400303578 0.0177526168011 0.0076128290146 0.00378003726334 0.0176983136317 0.0184021944475 0.015957334363 0.00467338620778 0.0252008449064 0.0123712673884 0.075769333126 0.0581585948517 0.051685391535 0.0510927438975 0.0624824329901 0.058237684953 0.0625760535583 0.0529652916573 0.0618856654756 0.0574514448673 0.0423317456956 0.0517508019275 0.0497149058646 0.0670867680773 0.0782168275888 0.0597219475669 0.0433096265158 0.0451925797267 0.0797528207091 0.0566461202307 0.0551309318768 0.0620350591031 0.0638200944876 0.0424059873805 0.0493045876457 0.0391604065434 0.0398454975082 0.039967724192 0.0615633801452 0.0371201263022 0.0531118088353 0.0313373664803 0.0653089870936 0.0335830893361 0.0556009421638 0.055317918307 0.0671296861942 0.0435900410315 0.0407047621897 0.0451219309698 0.062876852421 0.0526327547566 0.0581585948517 0.0591116735406 0.0652217833265 0.0570769673892 0.0545681643866 0.0459758175248 0.0634453480612 0.0477419996054 0.0302678050916 0.043643410877 0.0344015081898 0.0370220423378 0.041870658286 0.0264826423168 0.0239447061259 0.0294219290852 0.0381639993795 0.046622906961 0.0276160450358 0.0433656713185 0.0358877725536 0.0394027280044 0.0640596529703 0.0352660320463 0.0428200645336 0.0215929365207 0.0157178006612 0.00848045847957 0.0252717757761 0.0193974269851 0.0287059984697 0.018482386953 0.0255997786212 0.0310533244304 0.0267766622161 0.0163561213819 0.0202134806234 0.03120585866 0.04895305148 0.036414957876 0.00526673250197 0.012342309222 0.0430330254803 0.0254800237585 0.0266262246906 0.032239986602 0.0289090953163 0.0180202124552 0.0199181330431 0.00657470209208 0.019948180111 0.0209059906389 0.0237059104211 0.0188029876251 0.0211536929916 0.0148162018356 0.0289733910382 0.0123381921604 0.0242362829274 0.0258181102869 0.0413288399769 0.0100525363381 0.0238857865216 0.0223322555234 0.0341921837249 0.0380927560603 0.0215929365207 0.0255329039423 0.0383043472154 0.0356000313746 0.0249953874006 0.0211460552822 0.0407734262534 0.0193178864072 0.0180029141367 0.0318989553395 0.055546862882 0.0233316606029 0.0289827460324 0.0288090712213 0.0227692137274 0.0384790428766 0.0423072029606 0.0235668927239 0.02923399269 0.0234314824709 0.0353361736228 0.0517306479462 0.0262806947106 0.0449202216804 0.0352666691701 0.0392752718621 0.0317034826189 0.0393329164331 0.0490036652351 0.0325915526781 0.0480657848217 0.0556144972514 0.0314856186317 0.0229568301755 0.0394863344353 0.0351981784458 0.0468732286627 0.0534614036052 0.0352961935447 0.0267660200432 0.0206247795688 0.0716485928619 0.0553558677376 0.0350658679333 0.0348297205048 0.0591015265332 0.0349050611526 0.0239450314084 0.0312292238968 0.0291976883203 0.0191245738332 0.0424715605102 0.0407160306554 0.0507428891398 0.0195019979736 0.0455419926359 0.03094049167 0.0480027022309 0.0486981223658 0.0364405293098 0.0216724201002 0.0185311393183 0.0322264249099 0.0418711782883 0.0415641388928 0.0352666691701 0.0357541599092 0.040359691621 0.0422344237589 0.0468672273606 0.0294846892119 0.0324923805449 0.019122055634 0.028756827183 0.0587035417373 0.0144079071022 0.0301136242831 0.0361947314748 0.0239369814187 0.0355016994114 0.0354103580658 0.0251937444079 0.0240593800606 0.0195703439645 0.0308533101673 0.0370972551577 0.0220259748313 0.0576270076145 0.0487199724465 0.0508215730415 0.0469075647824 0.0527931212734 0.0620087731623 0.046977227863 0.0607033064427 0.0681905343994 0.0417090362137 0.0294084394516 0.0508408090071 0.0450400867986 0.0589677823649 0.0627613332544 0.0433082894199 0.0402642195426 0.0363046359117 0.084449006206 0.0666097386489 0.0449981598795 0.0463289168121 0.0716590868723 0.0429897098207 0.0369198960753 0.0432687121664 0.0364322199569 0.0287560795721 0.0553568599314 0.0500592177291 0.0617693212505 0.0302484927047 0.057817398636 0.0410079662048 0.0620054930234 0.0578523465697 0.0464641684152 0.0365865801942 0.0272374209649 0.0399576916682 0.0513227964118 0.0446516670985 0.0487199724465 0.0480806958126 0.0495453699635 0.0482845333883 0.0560302897599 0.0385013240981 0.0415333749992 0.0339802993211 0.0315416974034 0.0191639032899 0.00845591360235 0.0214341775937 0.0115218935447 0.00798589270321 0.013178045677 0.0100207917484 0.0199483776485 0.012776598265 0.00707708079363 0.0310463483447 0.00779811122226 0.0665120827305 0.0484731432285 0.0421114253952 0.0418216626354 0.0527853739739 0.0493966141428 0.0532439640509 0.0450980317427 0.0534929652418 0.0482563107324 0.0334028573772 0.0421738253769 0.0400650763191 0.0574010770653 0.0690115183659 0.0507807835016 0.033887277172 0.0360916940356 0.0712074753299 0.0488889567075 0.0456112729946 0.052712754383 0.0558191941722 0.0328832629679 0.0398769847778 0.0305626616722 0.03027845702 0.0307126203306 0.051853149134 0.0304315085387 0.0449208583046 0.0225669791691 0.0556104755493 0.0254498384131 0.0488668504899 0.0462046921677 0.0583599392562 0.0343786096194 0.0317076229625 0.0356254179774 0.0534761774586 0.0442401179536 0.0484731432285 0.0494784680372 0.0560638562362 0.0481022882275 0.0453399462887 0.0364100969213 0.0549637988767 0.0386182470618 0.0499647144567 0.0286991839096 0.0337581082348 0.0374427591873 0.0282526339036 0.0355267981937 0.0350362485208 0.0478325170984 0.0425063748067 0.0336958297594 0.0552821351039 0.0385952701325 0.0756759815417 0.0533281139759 0.0401812481707 0.0447968354881 0.0564105044414 0.0391525826608 0.0629279047421 0.0332827070938 0.0388499692511 0.059944716429 0.0494848197832 0.0402828974015 0.0438747097228 0.0580998054312 0.0757410641548 0.0628856838636 0.039846534973 0.047063496141 0.0543336344875 0.0312551117596 0.052807685198 0.0627916823231 0.0390112066642 0.0353058185357 0.0513654740192 0.0323261929775 0.0392211101217 0.0463708307148 0.0519963158873 0.0234224809867 0.0301000678074 0.0372829629985 0.056085164213 0.0288004220361 0.0417416575715 0.0389119081863 0.0720657500645 0.0444323135884 0.0487706488559 0.0429521653792 0.0596874437499 0.052997284605 0.0533281139759 0.055723410021 0.0657462146584 0.0546473494099 0.0397492903292 0.0452841873006 0.0707319601823 0.0522103619845 0.0233361726138 0.027152976334 0.0145101369991 0.0239084572879 0.0231796409398 0.016731423623 0.010101659051 0.00781108168527 0.0186261838058 0.0315054768721 0.011535154356 0.0661583467836 0.0529737197777 0.0515726376237 0.0472342284059 0.0573866872941 0.0602508051558 0.0535888133028 0.0563499220883 0.0655740867856 0.0494619373879 0.0355566546043 0.0516986354459 0.0478914383755 0.0636133913461 0.0714227137745 0.0519652065027 0.040040966538 0.0379888138015 0.0833863881089 0.0624232672218 0.0504889561622 0.0539483262888 0.0682863371673 0.0434492287718 0.0419221148541 0.0393420375143 0.0383919560321 0.0330409365388 0.0586345177546 0.0426935993753 0.0582011555148 0.0271038932712 0.0620596992058 0.0353264927481 0.0565229896235 0.0582751327845 0.0564224406826 0.0375222198637 0.032781658946 0.0431629716317 0.0579468986629 0.0506284295544 0.0529737197777 0.0534601982596 0.057907553496 0.0543043024926 0.0568716124285 0.0423649825393 0.0520169525452 0.0387128090446 0.0188041113809 0.0118952838684 0.0140246174547 0.0212962400935 0.00885470160798 0.025142494638 0.0164130694781 0.0148836892075 0.0382106616301 0.012548123562 0.0595024177002 0.0405144987261 0.0336829979036 0.0339109092342 0.0447786612887 0.0412528753098 0.0460796810618 0.0378073897699 0.0456289595962 0.0415318567716 0.0274434917532 0.0337586538145 0.0320559171837 0.0493065770936 0.0620181140557 0.044443934423 0.0259210454652 0.0290199212383 0.0631931226882 0.0415500215307 0.0381432011516 0.0457001329437 0.0482419653282 0.0247624956339 0.0326912275918 0.0231122641396 0.0226732232029 0.0241582823353 0.0435758556102 0.024717419625 0.037259551901 0.0161507334564 0.0474557435456 0.0186437176733 0.0428749718429 0.0381563349544 0.0522572394535 0.0270598018866 0.0256825354187 0.028101885414 0.0461630275758 0.0383904744963 0.0405144987261 0.041758354552 0.0492909187408 0.0413606774947 0.0372601462253 0.0289047461458 0.049343864353 0.0319066136889 0.0140884736377 0.0220949123029 0.0294588701998 0.0141915109934 0.0236402055251 0.0202701530019 0.0222961106051 0.0508328337578 0.0209048958304 0.0623063919994 0.0435137888223 0.0379179155361 0.0310595169631 0.0477275300573 0.0401401417297 0.0476510837343 0.0326069267264 0.0442002914704 0.048031576146 0.0373260310255 0.0383688546713 0.0396828967833 0.0540671732006 0.0691161945626 0.052697233895 0.0256698443155 0.0271866198766 0.063343063446 0.0401717345523 0.0456835007162 0.0508390224232 0.0457136947621 0.0340506342939 0.0366331778515 0.0185292352758 0.0332180639746 0.031402826069 0.0470047522912 0.0178796862446 0.0373117201537 0.0146755143259 0.0519441438124 0.0134308953908 0.0305516636587 0.0455547559496 0.0574314455133 0.025677142017 0.0334820400224 0.038185075996 0.053981408775 0.0519396327628 0.0435137888223 0.0464090712113 0.0566586435278 0.0526391071675 0.0449104072539 0.0375180555166 0.0549667946861 0.0334290223836 0.0161979126548 0.0212360948185 0.00313302296585 0.0148689509772 0.00704616989296 0.0135603719097 0.0384674432624 0.00748847746612 0.0612097252086 0.0442840466006 0.040253243803 0.0360606427738 0.0487530949526 0.0470778914902 0.0473392259982 0.0422963534873 0.051949068182 0.0444929580638 0.0309270188278 0.040473799777 0.0386243677637 0.0547837147046 0.066413370926 0.0479885282668 0.0287291459439 0.0288822795748 0.0702829288423 0.0483322868723 0.0432975336344 0.0484818516534 0.0543652596123 0.0329738645592 0.0348525988976 0.0258401695463 0.029652318833 0.0266204166929 0.0488217477328 0.0282325775461 0.0443695558327 0.0152728890442 0.0529346931915 0.0211960241124 0.0432358961175 0.0470042201915 0.0535566647123 0.027654993588 0.0276975587847 0.0349524016669 0.051450438001 0.0457471816551 0.0442840466006 0.0457673454519 0.0531390087147 0.0482613634351 0.0459084957324 0.0344895940573 0.0501063288839 0.0320407745768 0.00918896575074 0.0152928703979 0.0213827580606 0.0175694634428 0.00562421953864 0.0321021314886 0.0136468651148 0.0732793088732 0.0542749513511 0.0465599433205 0.0461499050441 0.0585282720374 0.0518386762248 0.0596896497046 0.0460357977086 0.0551020502351 0.055347044484 0.0408456006547 0.0466734650365 0.0457685420886 0.0629790052567 0.0758414718388 0.0580725583953 0.0386295601195 0.0415657641172 0.0731303485703 0.0495368191967 0.0520897347287 0.0595893389246 0.0567976430466 0.0379647331116 0.046441942626 0.0333063038869 0.036500108881 0.0377849182347 0.0570420811998 0.030012797008 0.0462545479693 0.027461418846 0.0610786707463 0.0275181743774 0.0490679602747 0.0500650525841 0.0657243645716 0.0396831631678 0.0390548397455 0.0418579268325 0.0600259300869 0.0509169565607 0.0542749513511 0.0557017357111 0.0631094362549 0.0546387182457 0.049518990394 0.0428637051261 0.0625010516077 0.0450876329039 0.0212339329669 0.0205215987455 0.0193863377449 0.00780222226267 0.0244911121423 0.0160871853235 0.0794295187203 0.0616053956912 0.0547043851777 0.0543405312398 0.065903416382 0.0608093966938 0.066232208671 0.0551996275352 0.0641635601661 0.0610614868167 0.0459223913813 0.0547672174246 0.0529488520021 0.0703578675954 0.081658744309 0.063265127216 0.046621662518 0.0487339053303 0.0819943915105 0.0586031447424 0.058550915637 0.0656481691263 0.065908662167 0.0454276311919 0.0529363521846 0.0420305180364 0.0431442291715 0.0436296366503 0.0648039964872 0.0390809908814 0.0552779379516 0.0347675148936 0.0685677387884 0.0363124697872 0.0577513229251 0.0579440183076 0.0708163109663 0.0470812852612 0.0443909494169 0.0483964045026 0.0662606542355 0.055662814312 0.0616053956912 0.0625961650479 0.0687432253304 0.0602433308395 0.0572909174214 0.0493875071422 0.0671416550914 0.051430941132 0.0176886906331 0.00927892520724 0.0135248076976 0.0386449359276 0.00800171044068 0.0600153322548 0.0424684845259 0.0378068586886 0.0345103555887 0.0469162632965 0.044830307306 0.0461600332306 0.040366688607 0.0496446456688 0.0429723253644 0.0292480429291 0.0380026884561 0.0362133523808 0.0526530252718 0.0646507321938 0.046401756397 0.0269368048123 0.0278152961494 0.0678508831968 0.0459682894465 0.0412431425852 0.0469952418705 0.0521326959742 0.0302237793371 0.0333696851677 0.0240543524356 0.0271406206916 0.0250182744726 0.0466959485275 0.0265020349694 0.0419000008026 0.0139599809083 0.0507909794153 0.0193885955935 0.0423195771966 0.0441873849773 0.0525088605043 0.0263754966172 0.0262639235251 0.0325133748369 0.0494223408625 0.0434143928247 0.0424684845259 0.0439360734198 0.0514706200386 0.0459476778298 0.0431249041549 0.0322850478478 0.0492075262735 0.0310084454102 0.0100495790147 0.0163467110294 0.0344303173981 0.0138069903297 0.0725561849391 0.0577945355153 0.0549392051976 0.0494983401957 0.0622636335656 0.0613281176125 0.0591564733558 0.055493289997 0.0660163516694 0.0564238014063 0.0428263617367 0.0551692935595 0.0528843814502 0.0687090576791 0.0786138592889 0.0595588928727 0.0427108568825 0.041401774003 0.084635719422 0.0620469795992 0.0566375025559 0.0605458826833 0.0680826326606 0.0475957607451 0.0475121922821 0.0395532575369 0.0436869591597 0.0392259763169 0.0629613789201 0.0403138291944 0.0584854937624 0.0290090498587 0.0669476358426 0.0346983343078 0.0538333844175 0.0617823249939 0.0639146571596 0.0406990904981 0.0395983575205 0.0488304003282 0.0645174644364 0.0580105192199 0.0577945355153 0.0590354282227 0.0651460923012 0.0612394393036 0.0606595939265 0.0481508588685 0.059835663962 0.0439114917806 0.0129734005291 0.03341718436 0.00522839839082 0.0644629130549 0.0490781090479 0.0460393397147 0.042060104717 0.0535473784021 0.0537667869164 0.051142158888 0.0493088817338 0.0587609555936 0.047452874403 0.0333145084294 0.0462018313471 0.0433659094036 0.0595851004773 0.0694769331379 0.0504124171924 0.034671039268 0.0338977868767 0.0768809318577 0.0551957563061 0.0471765984426 0.0517762151648 0.0612752535588 0.0380650031758 0.0388085089346 0.0327052071067 0.0338902731124 0.0299610722521 0.054082097785 0.0351427472718 0.0511360689963 0.0213341577068 0.0578680354373 0.0282202368919 0.0499673113862 0.0525352197686 0.0557073834724 0.0329893658613 0.0303826410135 0.0390336792828 0.055051644837 0.0480885204528 0.0490781090479 0.0500556489468 0.0560168186449 0.0513666437929 0.0513150683738 0.038531174408 0.0517653307967 0.0358776775011 0.0293027668161 0.00949293329263 0.0725916210374 0.0547194436592 0.0483620571735 0.0469816749901 0.0590799074957 0.0545464429945 0.0590974756513 0.0490311503881 0.0583273766433 0.0546626805606 0.0398779297173 0.0484777367952 0.0468025950712 0.0639880374892 0.0757199402524 0.0573199482995 0.0393352512936 0.0411552751652 0.0764775433632 0.0532445829203 0.052349146311 0.0590579753428 0.0602574471507 0.039602885829 0.0459481099263 0.0349175810863 0.037150941259 0.03692168916 0.0582151479375 0.0331340211018 0.0497159727507 0.0271028830882 0.0621502493207 0.0292960379946 0.0512053897158 0.0526539904442 0.0643939446385 0.0395489605444 0.0378591872997 0.0425337328718 0.0602822057384 0.0511975943751 0.0547194436592 0.0559279154126 0.0626694858218 0.0551041885347 0.0518894661445 0.0431638732605 0.0608387750183 0.0441361499894 0.0310183369902 0.0871660444405 0.0733823196322 0.0689340787316 0.0705597518969 0.0774366556509 0.07861295118 0.076322371958 0.075585060054 0.0826717778434 0.0681219966699 0.0528565530205 0.0687776982924 0.0640973745197 0.0810751053908 0.0869333145725 0.0682988428223 0.0623918658396 0.0630905228705 0.0989058396189 0.0780506774957 0.0672923155095 0.0734447484338 0.0852286487915 0.0580045491026 0.0635565107431 0.0611199283903 0.0538632888957 0.0534691027903 0.0772908944799 0.0610905623861 0.073927736475 0.0514266311669 0.0797776498939 0.0563654161709 0.0797411714169 0.0709112220147 0.075991910188 0.061929266059 0.0528794637266 0.0580761513446 0.0735774043698 0.0591743277341 0.0733823196322 0.0727567267189 0.0745131906994 0.065799040936 0.0698581828462 0.0592195179672 0.0715468662712 0.0627789092372 0.0648080233561 0.0484745494015 0.0442686485072 0.0419227062634 0.0529072690322 0.05220865754 0.0515658331236 0.0480206916873 0.056947174509 0.0470499747083 0.0323100366631 0.0443750009709 0.0415450201967 0.0583606796016 0.0686531977281 0.0497286282257 0.0341488382347 0.0345013563823 0.0748626932863 0.0530450676854 0.0459172064129 0.0515384326883 0.0594738121093 0.0355453778886 0.038659716641 0.0319368788596 0.0317165879728 0.0293867131129 0.0529269518286 0.0336846365247 0.0489212862277 0.021422361173 0.0566364033836 0.0272163294484 0.0501093106191 0.0498104699827 0.056059086848 0.0332712049783 0.0299486671345 0.0369730112559 0.0537844555125 0.0455932312627 0.0484745494015 0.0493458255088 0.0553162838774 0.0492693292197 0.0486803630647 0.0369835817896 0.0522579331221 0.0364683447074 0.022730708667 0.036743783492 0.0359076620395 0.0204805867516 0.0463990865864 0.0147686048767 0.0549817963395 0.0525592871928 0.0196223614467 0.0349129145261 0.0366443799136 0.0327544499352 0.0228629827418 0.017026958725 0.0209966358066 0.0388626349727 0.035298115438 0.0567980979705 0.0582058906835 0.0248891314561 0.0145753449597 0.0570907866116 0.0413833987479 0.0269969183741 0.0492024913366 0.039440986454 0.0359176091053 0.0265520996515 0.0615044403055 0.0540461620767 0.0485288037826 0.0239710979892 0.0540724881457 0.0603640513646 0.0428978851084 0.0134027017241 0.0368991356344 0.0354211161184 0.0355257446359 0.0210753154172 0.0377446845484 0.022730708667 0.0201673641607 0.0164841811953 0.0313109932769 0.041027522952 0.0326196770584 0.0184701671719 0.0295244972329 0.0144112105736 0.0158826147242 0.00448936000965 0.0256749628733 0.0117360219999 0.0336843126425 0.0324372887253 0.0146677582401 0.0224513170291 0.0144322007261 0.0126899376427 0.0122323800999 0.0277700509569 0.0197834641463 0.0180901487399 0.0184584697146 0.0418096588931 0.0367452397055 0.0108947935426 0.0131136689852 0.0371795334759 0.0204476192567 0.0118634455346 0.0280193213775 0.0206906520081 0.0208413846069 0.00805992628737 0.0397245591306 0.032104964073 0.0301985227624 0.0107001908888 0.0330988349255 0.0415195660786 0.0230606485279 0.0236410923325 0.0187109927435 0.0222023475244 0.0175662614613 0.0151089228932 0.0279798538754 0.0 0.00504787707871 0.0188703006517 0.0219245561497 0.0212095981034 0.0149942492526 0.0252687339312 0.016225955903 0.014982874816 0.0165713440517 0.0153992347708 0.0258128425881 0.0233194300829 0.0216404069707 0.0254243329386 0.0254506863873 0.000788850240312 0.00972288497974 0.0186083951584 0.0381473811219 0.0294157845108 0.0147578085456 0.0211609731312 0.0342335739858 0.0241609615185 0.0173732430671 0.0259324451415 0.0264222187829 0.0114741578552 0.0193298399608 0.0206022299244 0.0166568971164 0.0229188388297 0.0118905814083 0.0295550989457 0.0189848877848 0.0268759492012 0.0164070102936 0.0252554335694 0.0350869023834 0.0115237799469 0.0366507700029 0.0195520445986 0.0253794363549 0.0152320827299 0.0231766024476 0.028592244401 0.0144112105736 0.0173074040909 0.0296651493536 0.0243710502336 0.0103105434217 0.0155263675354 0.0374184940592 0.0231136537684 0.0190194510466 0.0188364876977 0.0221985054079 0.0214172478398 0.0257446692843 0.0273354856863 0.0275203255805 0.0156705691302 0.0195445751869 0.0258686776409 0.0435616108671 0.0331590462603 0.00822908256455 0.0111578554098 0.0408350390305 0.0282065307084 0.0237933699021 0.0272090918986 0.029489839001 0.0211107451245 0.0171676820259 0.0146997004559 0.0226821224304 0.022183640748 0.0185118691459 0.0264356522916 0.0241910879615 0.0208215741795 0.0237366538469 0.0205889692169 0.0263075431581 0.0263185412116 0.036342074994 0.0103523538982 0.0247947799759 0.0232770954007 0.0303205821473 0.0382875192266 0.0158826147242 0.0205185838207 0.033936913628 0.0342591359421 0.0252819239657 0.0211775313941 0.0366164619483 0.0168683582115 0.0265527528137 0.0122930639405 0.0356399190513 0.0329283261088 0.0161748547301 0.0260907486468 0.0165355786656 0.0155013603686 0.00871939649167 0.0250375042482 0.0205784254424 0.0221880849182 0.0226228234946 0.0402265547715 0.0380348851944 0.0126404460089 0.0132955588731 0.037673161045 0.023685130291 0.0159536871846 0.0317445670434 0.0244407629294 0.0250650773838 0.00665080674841 0.0431354773139 0.0336884150562 0.0346100496205 0.00755310734613 0.037025240188 0.043753914844 0.0237910793513 0.023601769275 0.0229675330633 0.0262374537149 0.0207694947791 0.0142462256068 0.0294419901123 0.00448936000965 0.00600179744469 0.0181950411243 0.0225874743282 0.0220819669003 0.0185630904749 0.0261141108628 0.0202600233978 0.0359510556228 0.0122181959079 0.00715556619596 0.0393252590156 0.0398773260728 0.015944130912 0.025117898688 0.0279596047113 0.0495072405762 0.0438343802676 0.0223301740975 0.0291958566192 0.0238057945893 0.0121408308676 0.0320439133203 0.0386704894061 0.0116681269474 0.0246115626131 0.0320654848123 0.02212022544 0.0306066666749 0.0360151785428 0.0212925403107 0.0265520435189 0.0096564754612 0.0336990775918 0.0258669646258 0.0271355620099 0.0255831511409 0.0192288539502 0.0492946436623 0.0275425360484 0.0388140188539 0.0303333899385 0.0366778253264 0.0435686919256 0.0256749628733 0.0298872725089 0.043124071299 0.0391147976424 0.020010154166 0.0305524053833 0.0506217525372 0.0340993001213 0.0427032875962 0.0428000340065 0.0127191401929 0.0244302586973 0.0258873530415 0.0224665589838 0.0196473040025 0.0259734946343 0.0180766430627 0.0245847379502 0.0205530555257 0.0515531804399 0.047284161103 0.0168752313645 0.0093274994833 0.0472940785154 0.0299859804517 0.013568861355 0.0349261393238 0.0278394065303 0.0233159306106 0.018729490125 0.0474594716238 0.0428441003445 0.0340535271365 0.0192623858156 0.0396592952666 0.0474504224755 0.0346317498496 0.0155232846365 0.0221965807346 0.023519540409 0.0251795457747 0.0186807942123 0.0328466600284 0.0117360219999 0.0111390258469 0.0171063233969 0.027248122956 0.0326787410905 0.0216187767654 0.0174724607588 0.0153838329478 0.013043913361 0.0463610530412 0.0437380208469 0.0239671160531 0.0322222363953 0.0387763357271 0.0597848214293 0.0513617728094 0.0233595904168 0.0301944308891 0.0320013483014 0.010613155035 0.0399275999718 0.0464560281456 0.0134988892532 0.0290788786594 0.0367911394695 0.0175534572334 0.0343523037935 0.0386269072909 0.0314326304477 0.0171757591643 0.0105270444576 0.0306360966619 0.0365489530298 0.0211256372062 0.0155275027866 0.0282406353658 0.0565789653078 0.0282583663219 0.0416523037157 0.0359394330813 0.0459783038684 0.0506220203623 0.0336843126425 0.0381881672141 0.0517807652888 0.0474506190651 0.0289707556044 0.0360504295798 0.0570578425896 0.0371145976129 0.0460576692202 0.0464080290911 0.0220712004031 0.0312574889523 0.0332251791601 0.054841891687 0.0503036463483 0.028999517533 0.0360798173432 0.0193249539307 0.00826557354917 0.0383933246971 0.0453200679143 0.00495350657916 0.0300295268949 0.0391229254289 0.0271682846269 0.036533385441 0.0426841669003 0.0272844902637 0.0287077471132 0.00927183865749 0.0394174170837 0.0313084618098 0.0314758507766 0.0267668687054 0.022518491913 0.05600898249 0.0343402195218 0.045509854552 0.0363226467926 0.0425348706975 0.0488168350062 0.0324372887253 0.0364696219213 0.0493441352532 0.0445105561012 0.0238995483109 0.0369592605884 0.0574925847452 0.0411978099793 0.0155890245102 0.0251635522565 0.0177733966699 0.020295344741 0.0224627747939 0.00622985531203 0.0261058920982 0.0236064364637 0.0550881017687 0.0493624804622 0.00949195374034 0.00547982782741 0.0508785947136 0.0251610996387 0.0119430843146 0.0365988456521 0.0213251727582 0.0179868212343 0.0207990673061 0.0485594746656 0.044214664825 0.0334335462828 0.0201922684831 0.0401754502972 0.0535325351547 0.0317074448278 0.0123261383208 0.0244676333996 0.0171629247157 0.0177983318365 0.0115555789303 0.021116940732 0.0146677582401 0.0105358367361 0.00915254671069 0.0167177891777 0.0294167459865 0.0147897849251 0.0123457776793 0.0164773765795 0.0252103273152 0.0171988861049 0.0305685435552 0.0365843208441 0.0175183238357 0.0218496748957 0.0205878945245 0.0593121863314 0.0470793832619 0.0159816216856 0.020744505382 0.0508661739809 0.018760262956 0.0128710885205 0.0300992820345 0.0117566522917 0.00679248320348 0.0280357325772 0.040552704264 0.0415859654681 0.0232256372519 0.0295874345059 0.0314690999231 0.0507965688951 0.0314591977122 0.0256867489287 0.0204093797109 0.00501434925534 0.0122277574576 0.0226603353781 0.0187158051887 0.0224513170291 0.020695499337 0.0231928239165 0.0198154038419 0.0292814261752 0.0103125125409 0.0227651885673 0.0153489133169 0.00921470910295 0.0183064776985 0.0377227049932 0.0290274791546 0.0152667053733 0.0215933253328 0.0343731361054 0.0245700487863 0.0169471424987 0.0257260078813 0.0268671065769 0.0111334970897 0.0193681705086 0.0211865368155 0.0163734248392 0.0228777878657 0.0117963493804 0.030068869871 0.0193762721713 0.027229886316 0.0161367068103 0.025740037188 0.0358416431275 0.0109673463454 0.0364509080414 0.0199965660095 0.0252914478428 0.014773865842 0.0226846344514 0.0279231861054 0.0144322007261 0.0171240275822 0.0292730788831 0.0236979129564 0.00966001570023 0.0151974508598 0.0372251985508 0.023311727536 0.0176051360114 0.032564167101 0.0208965631668 0.0168197313548 0.0207764664023 0.0423976172234 0.033280884693 0.00922898632458 0.0197200489933 0.0360385863631 0.00893473638299 0.014132357355 0.0250685285695 0.0103719683171 0.0167517993882 0.0135716425012 0.0350532380107 0.027879799306 0.0265658749664 0.015993421345 0.0286771298489 0.0428550440411 0.0151980773887 0.0298292088786 0.0196261415194 0.0184891599574 0.00655621912452 0.016322105066 0.0195353045374 0.0126899376427 0.0127701290018 0.0223217848231 0.0157154460048 0.0129568955631 0.00690277207156 0.0299954575682 0.0193817024498 0.0216771607077 0.0225658582257 0.0286138040807 0.0305011727447 0.0368382394098 0.0388439321615 0.0148355509953 0.0174423915339 0.0380129595202 0.0258082909988 0.0229842208153 0.0373470514825 0.0277476701907 0.0308564780706 0.00775830938446 0.0476084837369 0.0347450599297 0.0411478727988 0.00231111578025 0.0424821924039 0.0488975441679 0.0213563138315 0.0271270574956 0.0304501786922 0.0318897260431 0.0231190177025 0.0126379860304 0.0283589858381 0.0122323800999 0.0114136835682 0.0186791916183 0.0206672138954 0.0199079464104 0.0222007080182 0.0303110417953 0.0280827256688 0.0197052367937 0.0449418080277 0.0438182248518 0.0562345701188 0.0602081091792 0.0243728506009 0.019370550695 0.0596171194363 0.0411146925258 0.0330451422406 0.0550957050319 0.039836592047 0.0399228025142 0.0284759591968 0.0663825437409 0.0558252777705 0.0548034680532 0.0236830779253 0.0595333433641 0.0686676458986 0.0397842689041 0.0197958382609 0.0446061287137 0.0392287195509 0.0347118369491 0.0164026290561 0.0305214779366 0.0277700509569 0.0235155079857 0.0135888018548 0.0240589544851 0.0380160024194 0.0333951182547 0.0242014401746 0.0377621051763 0.0314790157987 0.029423203836 0.0582327389784 0.0535477924002 0.0121411421616 0.00926081168701 0.0551978876015 0.028043323191 0.0176337854836 0.041735927557 0.023998617859 0.0217227463265 0.0246395690903 0.0532994548372 0.0483234850835 0.0380931811444 0.0229416680066 0.0449797181572 0.059280772983 0.0338814552602 0.0120128740103 0.0301325195322 0.0203844916972 0.0200232099081 0.0110576598728 0.0182627991321 0.0197834641463 0.0150522414851 0.006556855609 0.0144048387324 0.0315801243453 0.0179150623078 0.0119668288225 0.022167313587 0.00841936493113 0.0453128226707 0.029523676844 0.0223084484022 0.0276272783534 0.0327625901905 0.0158536182647 0.0147737513682 0.0106741877313 0.0162753926733 0.0159900539559 0.0215767754577 0.023165172617 0.0247814128516 0.0137331692872 0.0264980088885 0.0153005942866 0.0291348842471 0.0254286093168 0.0364522562324 0.00599431682769 0.0189173783639 0.0184393411187 0.0301213577678 0.0340540018144 0.0180901487399 0.0215314467503 0.0337154513722 0.0315978293722 0.024169684578 0.0166816873996 0.0356875981931 0.0143747226708 0.0514837021891 0.0372994668391 0.0230531280792 0.0250236859971 0.0398047566678 0.021934356719 0.0118568359564 0.016712157857 0.0198275637831 0.0143476538676 0.0244078681037 0.0292462046489 0.0328036706334 0.014376575397 0.0286902071785 0.0204063466033 0.0328369650405 0.0322219054124 0.0320787692664 0.00266433410273 0.0165423262191 0.0215926529236 0.03047036768 0.0358252300263 0.0184584697146 0.0213100670766 0.032046547675 0.0333108429689 0.0306570115485 0.018573514939 0.030845411182 0.00824349209933 0.0254197321026 0.047324533609 0.0530498030585 0.0193482837408 0.0436475526218 0.0516271254066 0.0457976820229 0.0502335440929 0.0569904536613 0.0344485460271 0.0478896186827 0.0272539439051 0.0572698534085 0.0358919394369 0.050424643016 0.0434868500285 0.0309009333379 0.0632686594285 0.0501928388473 0.0594250482186 0.0482632028717 0.0482478127894 0.0570074387196 0.0418096588931 0.0448198518438 0.0553205633247 0.0514202209421 0.0325934353049 0.0491031454545 0.0660730408504 0.0548573842682 0.041501827818 0.0494594306994 0.00785967991178 0.0297803384368 0.0414124759052 0.0250716250932 0.0363814861914 0.0430927179021 0.0324719682551 0.0232534554573 0.00557282966442 0.0375065288364 0.0368003051347 0.0279998159154 0.0252506405295 0.0244125277036 0.0601405054627 0.0351799654171 0.0460355764213 0.0372310571496 0.0466115069454 0.0500875517655 0.0367452397055 0.0406580228721 0.0534802351733 0.0469702184582 0.0259083864948 0.0383091416944 0.0609916135179 0.0430915691708 0.0113258711014 0.0432937210726 0.0175511998728 0.0124157338497 0.03212454916 0.0158561794962 0.0176896387613 0.0143093876737 0.043181186929 0.0362893217044 0.0315177649353 0.0141883840024 0.0359022033885 0.0492746305333 0.0223018781484 0.0211045193185 0.0229404408443 0.0181288833884 0.0110415500412 0.00837364985146 0.0172572597582 0.0108947935426 0.00733209366776 0.0131427681285 0.0115359384743 0.0199994152339 0.0092535804352 0.0217603350968 0.0184515208125 0.0501255669434 0.0279388443296 0.0142651752221 0.0382528821443 0.0251771073359 0.0222405400165 0.018907996804 0.050407441851 0.0445874581383 0.03644061978 0.0176520694999 0.0424414179548 0.05338489352 0.0323923780445 0.0108812710336 0.0260958051981 0.0217902651406 0.0213471493056 0.0109405604951 0.0249968133743 0.0131136689852 0.00893536297262 0.00802513881767 0.0192978256402 0.0302167707341 0.018365059649 0.0130926236805 0.0184689475202 0.0343186729394 0.0436186327673 0.0296745080858 0.0408423177906 0.0469164023153 0.0321355072481 0.0293927572919 0.0115197032526 0.0423686398976 0.036129628995 0.0335484782832 0.0259949361948 0.0269704035297 0.0607621523939 0.0380404513983 0.049798653181 0.0409231346593 0.0474755392541 0.0535032395417 0.0371795334759 0.0413092694389 0.054257484038 0.0493594143559 0.0284802931744 0.0415978746211 0.0622229836323 0.0453773607077 0.0190484002067 0.0207749590973 0.00729318592678 0.0172314224853 0.0208211767145 0.0287420582694 0.0242194382748 0.0225231335589 0.0240241380028 0.0229680498334 0.0402905798782 0.0150429891355 0.0374592753112 0.0199314954492 0.0195088829088 0.00836639606005 0.0247669201274 0.0222507048525 0.0204476192567 0.021382719754 0.0305241597933 0.0212891172726 0.0134776031268 0.0107610654817 0.0367912026131 0.0227467739819 0.0252412358335 0.0153803531506 0.0101093922522 0.0191555088988 0.0375470144869 0.0363071087647 0.0222024666973 0.021689493319 0.0288400411309 0.0426974513075 0.0286740878663 0.0220177373135 0.0125507954062 0.0111145943691 0.0144555489867 0.0195277194831 0.0258649050915 0.0118634455346 0.0120062047089 0.0205457113931 0.0226933950128 0.0265812358015 0.010518368073 0.0210005232169 0.00605049088221 0.0227443548737 0.0240543202468 0.0298445720969 0.0127076622649 0.0212764877573 0.0130936058244 0.0350960563748 0.00616350137707 0.0220053566886 0.029182207808 0.0470447673611 0.0143549158293 0.0270200109504 0.0261798064669 0.0399469917707 0.0417589617676 0.0280193213775 0.0317524179632 0.0441157592928 0.0402109444472 0.0286518076954 0.0255338040061 0.0460635600013 0.0240522825965 0.0109052701432 0.0235077295524 0.0318665250498 0.0308330797717 0.0201941994246 0.026209699134 0.024272445381 0.0437154276666 0.0215651164399 0.0334110813047 0.0182660118628 0.0127706309369 0.00547043300996 0.0236736081076 0.0193715054877 0.0206906520081 0.0206326054685 0.0278093020752 0.0196854448654 0.0196839348285 0.00687772387853 0.0318861086105 0.0187829482374 0.026787715043 0.0349795665731 0.0377560197701 0.0169430774361 0.0294826475268 0.02554791035 0.044564230482 0.0307872765104 0.0282156909894 0.0140157312571 0.00304825224888 0.013131876158 0.0254598849649 0.0242359050423 0.0208413846069 0.0206178921833 0.0265921223132 0.024263714379 0.0287692622655 0.0105411880456 0.0255889461915 0.0107722057824 0.040112260752 0.0281920691871 0.0346799127455 0.00547299312148 0.0351698801996 0.0413927767406 0.0178561632488 0.0295704831304 0.0240442618733 0.028433519767 0.0198981190422 0.0164655446546 0.0293872770207 0.00805992628737 0.0103936152637 0.0224915115769 0.0226675446032 0.0164308167554 0.0187959028502 0.0318543120786 0.0236941138036 0.0214366809685 0.02158460699 0.0453234846239 0.0100348820296 0.0205571695519 0.0349449705288 0.0594353543617 0.0269232145172 0.0378563722335 0.0357646095878 0.0508614713562 0.0504482642845 0.0397245591306 0.0434432071263 0.055747628056 0.0497669350857 0.035138779746 0.0361053959246 0.0583555321158 0.0365217793435 0.0331608473129 0.0326376645366 0.0244797200401 0.0260351758605 0.0198255233977 0.055257342683 0.030607412773 0.0406724640229 0.0316826493706 0.0417015250124 0.044714144963 0.032104964073 0.0358439539437 0.0485265392209 0.0417397589401 0.0211023329973 0.0328093106913 0.055937896155 0.0382216948189 0.039178382683 0.0116462448464 0.0328449320294 0.0356452166019 0.0431040247851 0.0127555033307 0.0192172916283 0.0248029464024 0.0398126039657 0.0387676066679 0.0301985227624 0.0325088097853 0.0421224110282 0.0389248491362 0.0344395222458 0.0235149407659 0.0407276725105 0.0194230824244 0.0402634745964 0.0467109968717 0.0198309327046 0.0277799641062 0.0285372209353 0.0306922978338 0.0217975015009 0.0133715864954 0.0282760151712 0.0107001908888 0.0106288988311 0.0196086000788 0.0208164998238 0.0183683750797 0.0208677693371 0.0306825719595 0.0267116346537 0.0244223076771 0.0326854873354 0.0507787766717 0.0180800830921 0.0283453513368 0.0285692206738 0.0439964220236 0.0435330479692 0.0330988349255 0.0364104267178 0.047999984958 0.0430530022579 0.0322085934623 0.0282999450485 0.049271623839 0.0273166054796 0.0425206430225 0.0621747559979 0.0316259435967 0.0474306721161 0.0460270405303 0.055722110458 0.0616108763567 0.0415195660786 0.0463779068274 0.0600405303926 0.058487923658 0.0428330727641 0.0451145046508 0.0622811849491 0.040848815271 0.0429266615699 0.0304603009077 0.0328680598837 0.0193092495687 0.0257358921646 0.0276472175985 0.0230606485279 0.0242829569209 0.0335562675322 0.0240112096757 0.0023427193432 0.0215758461563 0.0440130376785 0.0333268924093 0.0336594835101 0.0267459293135 0.0300685155468 0.0190916149163 0.0302108808069 0.0236410923325 0.0197983357166 0.0116623663768 0.025981149508 0.0407083432987 0.0270506742353 0.00533601555818 0.0243947872794 0.0165388277645 0.0203993556991 0.0306449706863 0.0351570407904 0.0187109927435 0.0216936002715 0.0328125704384 0.0328483395342 0.0289711567156 0.0177290185247 0.0324417191457 0.00990245486371 0.0144435719572 0.0253854499058 0.0235470613048 0.0222023475244 0.0213735089495 0.0257216217897 0.0239916366178 0.0307770932602 0.0118559544137 0.0237036848381 0.0117425310805 0.0184555467145 0.0157796168272 0.0175662614613 0.0166625646078 0.0231975140243 0.0146299258577 0.0171445810537 0.00397360286698 0.029169512627 0.0192796522734 0.0184254094366 0.0151089228932 0.0104342480435 0.00810789324855 0.0108902089943 0.0236280097789 0.017384613998 0.0213096795304 0.0252727380791 0.0279798538754 0.0244605767611 0.021500625639 0.00796997392989 0.0256665045822 0.0176058458155 0.0292950203769 0.0310442784549 0.00504787707871 0.0188703006517 0.0219245561497 0.0212095981034 0.0149942492526 0.0252687339312 0.016225955903 0.0138989004364 0.0180767927652 0.0222193332382 0.0141002986592 0.0215877207325 0.0172352062998 0.0156454524782 0.031383424491 0.0212505739868 0.0142951058056 0.0253431933269 0.0219030212003 0.0156518397703 0.0263438271284 0.0285058962486 0.0193626993311 0.0417261059969 0.0313743437253 0.0260801912789 0.0155074292908 0.0227467684355 diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-jensenshannon-ml.txt b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-jensenshannon-ml.txt new file mode 100644 index 0000000000000000000000000000000000000000..8ed5b9653f4ed88e146d2d5dc5b032537b426f8b --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-jensenshannon-ml.txt @@ -0,0 +1 @@ +0.320369972991 0.338972466 0.308199372323 0.3452431902 0.310024768313 0.357115225615 0.311131096357 0.357391534414 0.329718053755 0.347365921475 0.335272625287 0.336451560653 0.33015370606 0.369628769749 0.344499490029 0.321622508707 0.345377707016 0.321007207534 0.350728979121 0.32809430086 0.30207071308 0.291663252492 0.30760470102 0.315976639534 0.308132467187 0.313014586878 0.310463895925 0.321091616502 0.290044394125 0.322213459935 0.315509196522 0.3331114403 0.281071919202 0.320854431887 0.332190658438 0.299342730178 0.313528775154 0.310049073937 0.288821516545 0.307662081954 0.328387688508 0.317185603454 0.332046170365 0.291912213887 0.37870970117 0.336080073379 0.304593343921 0.330138983604 0.355071759299 0.311946140607 0.302025400768 0.330940761586 0.351140062502 0.354772884287 0.272605322053 0.327957349848 0.28871110366 0.320821172951 0.340976919806 0.30757488831 0.320975346884 0.252776262329 0.314549731907 0.326876483 0.337684418756 0.296520013735 0.31493077245 0.327721982167 0.325802862624 0.341908184107 0.300481749419 0.312499767894 0.301061762121 0.27665157989 0.3082566692 0.287466396145 0.288313694552 0.296629698731 0.283556095025 0.322489360684 0.280765581604 0.297958166613 0.313189657041 0.303470399659 0.348652898212 0.331594734387 0.299446687464 0.339047458559 0.286979246044 0.316326095312 0.321618884109 0.330065896317 0.324500638067 0.328300795872 0.309002568222 0.262587468469 0.31974123777 0.286316182293 0.321162329165 0.328160620315 0.356618051635 0.289733970648 0.344507756538 0.301485561986 0.335785898715 0.322635066518 0.331480718646 0.297897604494 0.306942928189 0.350843442517 0.342585296966 0.341311053315 0.306780105123 0.313401804298 0.319978145568 0.302460397612 0.346105758567 0.312802351189 0.331552275517 0.321624157344 0.318798118247 0.301906095501 0.301585920138 0.314556178985 0.333215221158 0.306929663844 0.317083256901 0.309667679181 0.306529028004 0.30865993751 0.296031907986 0.28742420979 0.311584483038 0.319043629504 0.330278008622 0.314466433681 0.327937382021 0.296448162218 0.307033121385 0.296391953011 0.292691206116 0.297146209653 0.307929858983 0.291863681454 0.307300188104 0.306597817799 0.34718100163 0.317436210259 0.29952626739 0.330762834707 0.334951064852 0.323806678898 0.296203706701 0.33398466797 0.344931265559 0.293948734727 0.332764639313 0.272651853935 0.317324315923 0.300493570867 0.307008231016 0.333263322802 0.31390648462 0.332416491248 0.314766869708 0.321015549211 0.322909289307 0.356882966656 0.310596945263 0.343939748528 0.286269629586 0.33173459898 0.323848483719 0.305841388975 0.319266258167 0.34012363898 0.3443280395 0.353885654057 0.320544729867 0.353280499623 0.315621795536 0.312176062734 0.301562130879 0.312061680573 0.312642847966 0.326222109701 0.357417912858 0.313083593142 0.334033412713 0.295630506074 diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-minkowski-3.2-ml-iris.txt b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-minkowski-3.2-ml-iris.txt new file mode 100644 index 0000000000000000000000000000000000000000..dc396c8c16032b9657101532f7e08e6aa04b2aea --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-minkowski-3.2-ml-iris.txt @@ -0,0 +1 @@ + 5.0817745e-01 4.4535192e-01 5.6700421e-01 1.2418578e-01 4.8927739e-01 5.0180477e-01 1.4096146e-01 8.1242502e-01 4.1586001e-01 3.2586371e-01 3.2586371e-01 5.2942799e-01 8.6137722e-01 7.7039952e-01 9.7270522e-01 4.5581864e-01 1.0000000e-01 6.3861009e-01 3.0546431e-01 3.7427929e-01 2.5251796e-01 5.6700421e-01 3.8776762e-01 5.2942799e-01 5.0905001e-01 2.5651975e-01 1.2418578e-01 1.2418578e-01 4.5470518e-01 4.5470518e-01 3.2816937e-01 6.0181382e-01 7.3457830e-01 4.1586001e-01 3.2586371e-01 4.0147421e-01 4.1586001e-01 7.6752131e-01 1.2418578e-01 1.4096146e-01 1.2396136e+00 7.1462831e-01 4.1449626e-01 5.3588338e-01 5.2942799e-01 3.2352160e-01 5.2862779e-01 2.5251796e-01 2.0656129e-01 3.5031395e+00 3.2158090e+00 3.6682165e+00 2.7164367e+00 3.3288934e+00 3.1477087e+00 3.4033622e+00 2.0308266e+00 3.3209346e+00 2.5912926e+00 2.3257069e+00 2.8912179e+00 2.7273721e+00 3.3660466e+00 2.2876649e+00 3.1664710e+00 3.1642132e+00 2.7448172e+00 3.2474124e+00 2.5734684e+00 3.5025969e+00 2.6980573e+00 3.5983434e+00 3.3515288e+00 3.0113552e+00 3.1469325e+00 3.5526357e+00 3.7475562e+00 3.1812462e+00 2.1818668e+00 2.4927109e+00 2.3909738e+00 2.5729378e+00 3.7711998e+00 3.1620401e+00 3.1916270e+00 3.4478147e+00 3.1312883e+00 2.7541224e+00 2.6886547e+00 3.0483897e+00 3.2685282e+00 2.6752185e+00 2.0587064e+00 2.8619072e+00 2.8416143e+00 2.8554471e+00 2.9845926e+00 1.7697734e+00 2.7640668e+00 4.7690606e+00 3.8067806e+00 4.6866422e+00 4.2843668e+00 4.5417384e+00 5.4120246e+00 3.2161426e+00 5.0569442e+00 4.5165793e+00 4.9462324e+00 3.8595100e+00 4.0249346e+00 4.2787236e+00 3.7387507e+00 3.9160762e+00 4.0938708e+00 4.2028863e+00 5.5316487e+00 5.7297286e+00 3.6968486e+00 4.5074741e+00 3.6330985e+00 5.5146761e+00 3.6293227e+00 4.4495340e+00 4.7599229e+00 3.5255287e+00 3.6076762e+00 4.3339547e+00 4.5590471e+00 4.8997298e+00 5.2856169e+00 4.3511402e+00 3.7760534e+00 4.2460554e+00 5.0103780e+00 4.3808704e+00 4.1939019e+00 3.5087649e+00 4.2018804e+00 4.4140402e+00 3.9807996e+00 3.8067806e+00 4.6775324e+00 4.5250934e+00 4.0376133e+00 3.7473276e+00 3.9523060e+00 4.1709262e+00 3.7872951e+00 2.5251796e-01 3.0546431e-01 6.0060595e-01 9.5035453e-01 4.4535192e-01 4.0293660e-01 5.0090417e-01 1.4096146e-01 7.6752131e-01 4.1449626e-01 1.2418578e-01 6.2024833e-01 1.1845977e+00 1.4700179e+00 9.4309624e-01 5.0905001e-01 1.0003617e+00 8.0358695e-01 5.8851328e-01 7.0826681e-01 6.6384020e-01 4.3456114e-01 5.6700421e-01 2.0656129e-01 4.2667565e-01 5.2942799e-01 4.4417983e-01 2.8192292e-01 2.1269358e-01 5.7324170e-01 1.1056650e+00 1.2393677e+00 1.4096146e-01 2.5251796e-01 6.8961791e-01 1.4096146e-01 5.0090417e-01 4.1449626e-01 5.0270183e-01 7.3535471e-01 5.0905001e-01 5.7324170e-01 8.5690100e-01 1.2418578e-01 8.0587320e-01 3.2352160e-01 7.3496673e-01 3.0275928e-01 3.5601468e+00 3.2472699e+00 3.7137483e+00 2.6693888e+00 3.3563815e+00 3.1472333e+00 3.4276314e+00 1.9506288e+00 3.3563695e+00 2.5739370e+00 2.1870094e+00 2.9033014e+00 2.6860278e+00 3.3789262e+00 2.2884830e+00 3.2153154e+00 3.1667333e+00 2.7423060e+00 3.2269725e+00 2.5465772e+00 3.5123782e+00 2.7147889e+00 3.6030381e+00 3.3619470e+00 3.0427908e+00 3.1888219e+00 3.5910272e+00 3.7805671e+00 3.1921903e+00 2.1611020e+00 2.4491518e+00 2.3430978e+00 2.5700421e+00 3.7741357e+00 3.1615131e+00 3.2084454e+00 3.4884789e+00 3.1228939e+00 2.7575407e+00 2.6617768e+00 3.0343591e+00 3.2842184e+00 2.6656374e+00 1.9595652e+00 2.8539100e+00 2.8474367e+00 2.8585579e+00 3.0059712e+00 1.6867642e+00 2.7634340e+00 4.7806735e+00 3.8055585e+00 4.7194850e+00 4.2963997e+00 4.5579706e+00 5.4507801e+00 3.1945300e+00 5.0903533e+00 4.5297786e+00 4.9814379e+00 3.8841455e+00 4.0376849e+00 4.3069372e+00 3.7284750e+00 3.9173293e+00 4.1124749e+00 4.2221165e+00 5.5759608e+00 5.7633066e+00 3.6758942e+00 4.5370189e+00 3.6312130e+00 5.5536680e+00 3.6416405e+00 4.4736906e+00 4.7961103e+00 3.5380868e+00 3.6203213e+00 4.3467079e+00 4.5977693e+00 4.9380624e+00 5.3421274e+00 4.3637834e+00 3.7899304e+00 4.2477635e+00 5.0602038e+00 4.3953045e+00 4.2110583e+00 3.5192753e+00 4.2358121e+00 4.4378207e+00 4.0189525e+00 3.8055585e+00 4.7017335e+00 4.5483787e+00 4.0656879e+00 3.7516222e+00 3.9742971e+00 4.1845313e+00 3.7939847e+00 2.1269358e-01 4.4535192e-01 8.9366705e-01 2.1845981e-01 3.4378533e-01 3.7427929e-01 2.5651975e-01 7.7039952e-01 3.2586371e-01 2.1845981e-01 4.2667565e-01 1.2113327e+00 1.3801284e+00 8.7175869e-01 4.4651726e-01 1.0719360e+00 6.5223271e-01 7.3813096e-01 5.7867728e-01 4.4535192e-01 5.2655962e-01 6.0611244e-01 3.8776762e-01 4.0176783e-01 5.3588338e-01 5.0905001e-01 3.0000000e-01 3.0546431e-01 7.1169738e-01 9.4309624e-01 1.1327825e+00 2.5651975e-01 3.0275928e-01 8.1067767e-01 2.5651975e-01 3.2352160e-01 4.2538717e-01 3.7427929e-01 9.0252542e-01 3.0000000e-01 5.1138698e-01 7.7869083e-01 2.1845981e-01 6.6384020e-01 1.2418578e-01 6.9325418e-01 3.0546431e-01 3.7098973e+00 3.3770904e+00 3.8553941e+00 2.7868575e+00 3.4895316e+00 3.2571492e+00 3.5499573e+00 2.0646687e+00 3.4944845e+00 2.6743800e+00 2.3196869e+00 3.0181476e+00 2.8270253e+00 3.4973911e+00 2.3997585e+00 3.3600102e+00 3.2716172e+00 2.8619072e+00 3.3597438e+00 2.6649106e+00 3.6203213e+00 2.8440609e+00 3.7280682e+00 3.4822008e+00 3.1786890e+00 3.3296038e+00 3.7325066e+00 3.9121945e+00 3.3084060e+00 2.2888897e+00 2.5683989e+00 2.4649412e+00 2.6906230e+00 3.8866112e+00 3.2625043e+00 3.3219248e+00 3.6264668e+00 3.2609948e+00 2.8656468e+00 2.7738624e+00 3.1430282e+00 3.4033622e+00 2.7865812e+00 2.0797392e+00 2.9638836e+00 2.9589097e+00 2.9695568e+00 3.1337459e+00 1.7991433e+00 2.8758936e+00 4.8875515e+00 3.9111857e+00 4.8490379e+00 4.4107143e+00 4.6725771e+00 5.5854254e+00 3.2933477e+00 5.2226262e+00 4.6541348e+00 5.1068487e+00 4.0049607e+00 4.1564977e+00 4.4321573e+00 3.8331006e+00 4.0161098e+00 4.2255639e+00 4.3417782e+00 5.7091264e+00 5.8970064e+00 3.7961619e+00 4.6611065e+00 3.7313856e+00 5.6903014e+00 3.7618406e+00 4.5942943e+00 4.9290197e+00 3.6553612e+00 3.7333492e+00 4.4613366e+00 4.7342792e+00 5.0749049e+00 5.4844039e+00 4.4774673e+00 3.9102500e+00 4.3611782e+00 5.2016658e+00 4.5034762e+00 4.3281161e+00 3.6300436e+00 4.3648112e+00 4.5562166e+00 4.1482002e+00 3.9111857e+00 4.8218416e+00 4.6648403e+00 4.1879434e+00 3.8717400e+00 4.0945154e+00 4.2919258e+00 3.9013483e+00 5.6700421e-01 9.9714776e-01 3.0546431e-01 4.4417983e-01 2.5251796e-01 3.0275928e-01 8.8835966e-01 3.2586371e-01 2.1845981e-01 4.4651726e-01 1.3360558e+00 1.5022608e+00 9.9714776e-01 5.6769031e-01 1.1765359e+00 7.6752131e-01 8.1354181e-01 6.9325418e-01 6.2092891e-01 5.4292906e-01 4.5470518e-01 4.0293660e-01 4.5581864e-01 6.4704320e-01 6.2024833e-01 1.4096146e-01 2.0656129e-01 8.1354181e-01 1.0574300e+00 1.2554784e+00 3.0275928e-01 4.4535192e-01 9.2264612e-01 3.0275928e-01 2.5251796e-01 5.2862779e-01 5.0592043e-01 8.0358695e-01 2.5251796e-01 5.6454040e-01 7.9878917e-01 2.1845981e-01 7.6752131e-01 1.2418578e-01 8.1242502e-01 4.1449626e-01 3.5875094e+00 3.2277825e+00 3.7190120e+00 2.6019240e+00 3.3414931e+00 3.0741797e+00 3.3904673e+00 1.8683030e+00 3.3506325e+00 2.4892190e+00 2.1209506e+00 2.8530088e+00 2.6606291e+00 3.3264150e+00 2.2345869e+00 3.2325480e+00 3.0894572e+00 2.6859989e+00 3.1954750e+00 2.4836725e+00 3.4467337e+00 2.6928468e+00 3.5602810e+00 3.3090659e+00 3.0346426e+00 3.1953687e+00 3.5930845e+00 3.7635112e+00 3.1392617e+00 2.1242643e+00 2.3839455e+00 2.2806773e+00 2.5225548e+00 3.7070070e+00 3.0760590e+00 3.1551922e+00 3.4865435e+00 3.1033781e+00 2.6867856e+00 2.5906376e+00 2.9536363e+00 3.2348458e+00 2.6148507e+00 1.8841403e+00 2.7819255e+00 2.7801917e+00 2.7920574e+00 2.9774862e+00 1.6195190e+00 2.7001131e+00 4.7151191e+00 3.7310738e+00 4.6963107e+00 4.2348119e+00 4.5036643e+00 5.4345951e+00 3.1040223e+00 5.0660245e+00 4.4858951e+00 4.9576471e+00 3.8485743e+00 3.9894963e+00 4.2781102e+00 3.6535208e+00 3.8473084e+00 4.0656969e+00 4.1736133e+00 5.5611269e+00 5.7439963e+00 3.6142694e+00 4.5082936e+00 3.5527533e+00 5.5400450e+00 3.5988819e+00 4.4321573e+00 4.7754556e+00 3.4913787e+00 3.5638529e+00 4.2915574e+00 4.5844335e+00 4.9269527e+00 5.3501611e+00 4.3091163e+00 3.7395252e+00 4.1763853e+00 5.0687940e+00 4.3363292e+00 4.1568278e+00 3.4594086e+00 4.2175903e+00 4.4004449e+00 4.0139427e+00 3.7310738e+00 4.6611132e+00 4.5083524e+00 4.0415593e+00 3.7070350e+00 3.9354060e+00 4.1243443e+00 3.7225506e+00 4.8927739e-01 4.1449626e-01 2.0656129e-01 8.1242502e-01 5.0270183e-01 4.0293660e-01 2.8192292e-01 6.0611244e-01 8.2305664e-01 8.2899253e-01 9.3824087e-01 4.5581864e-01 1.4096146e-01 7.1840099e-01 2.1845981e-01 4.5470518e-01 2.1845981e-01 4.9674312e-01 4.2418962e-01 5.1607523e-01 6.0551856e-01 2.8192292e-01 2.1269358e-01 2.4837156e-01 4.5470518e-01 5.1607523e-01 4.2667565e-01 5.0991930e-01 6.8917100e-01 5.0270183e-01 4.1312257e-01 5.0180477e-01 5.0270183e-01 7.4549115e-01 2.1269358e-01 1.4096146e-01 1.3190071e+00 6.4755655e-01 4.1449626e-01 5.1691876e-01 6.0611244e-01 2.5251796e-01 4.9674312e-01 3.0546431e-01 3.0000000e-01 3.5310961e+00 3.2313174e+00 3.6912396e+00 2.7363446e+00 3.3486156e+00 3.1550780e+00 3.4146950e+00 2.0587064e+00 3.3422688e+00 2.6000813e+00 2.3658814e+00 2.9005672e+00 2.7581254e+00 3.3764180e+00 2.2982662e+00 3.1914715e+00 3.1684808e+00 2.7581145e+00 3.2719146e+00 2.5906376e+00 3.5076679e+00 2.7164648e+00 3.6146980e+00 3.3629944e+00 3.0317688e+00 3.1699749e+00 3.5767944e+00 3.7653940e+00 3.1912695e+00 2.2046610e+00 2.5131017e+00 2.4132939e+00 2.5882494e+00 3.7797776e+00 3.1649733e+00 3.1986968e+00 3.4685882e+00 3.1575873e+00 2.7599092e+00 2.7031874e+00 3.0575551e+00 3.2787144e+00 2.6914804e+00 2.0914773e+00 2.8714673e+00 2.8482104e+00 2.8631525e+00 3.0002861e+00 1.8009624e+00 2.7738624e+00 4.7744685e+00 3.8132783e+00 4.7036953e+00 4.2925903e+00 4.5507995e+00 5.4317036e+00 3.2245243e+00 5.0748136e+00 4.5314818e+00 4.9621679e+00 3.8715927e+00 4.0372136e+00 4.2937599e+00 3.7469906e+00 3.9213497e+00 4.1030149e+00 4.2136261e+00 5.5512721e+00 5.7499082e+00 3.7127205e+00 4.5218897e+00 3.6377830e+00 5.5357771e+00 3.6429670e+00 4.4609633e+00 4.7775824e+00 3.5373240e+00 3.6158814e+00 4.3437318e+00 4.5790474e+00 4.9211035e+00 5.3110568e+00 4.3608329e+00 3.7876656e+00 4.2543813e+00 5.0356467e+00 4.3872625e+00 4.2028863e+00 3.5161021e+00 4.2189979e+00 4.4261470e+00 4.0000622e+00 3.8132783e+00 4.6893387e+00 4.5361087e+00 4.0527696e+00 3.7622948e+00 3.9645936e+00 4.1768667e+00 3.7924679e+00 8.6137722e-01 5.7867728e-01 1.2470767e+00 8.6361309e-01 2.8192292e-01 6.9369532e-01 9.8450810e-01 1.2949422e+00 5.7324170e-01 5.3588338e-01 4.0000000e-01 4.8135521e-01 3.0546431e-01 3.2816937e-01 5.0817745e-01 3.4378533e-01 9.4558103e-01 6.2024833e-01 6.9728513e-01 9.2288144e-01 5.6700421e-01 4.3691963e-01 5.4292906e-01 8.7202528e-01 8.9095811e-01 5.0817745e-01 3.6171588e-01 3.8934542e-01 8.6361309e-01 7.9878917e-01 5.0592043e-01 8.6361309e-01 1.1959482e+00 5.4292906e-01 5.6454040e-01 1.6807352e+00 1.1055064e+00 5.0592043e-01 3.2586371e-01 9.7779835e-01 3.2816937e-01 9.4558103e-01 2.8507955e-01 6.6827038e-01 3.1533911e+00 2.8840079e+00 3.3274872e+00 2.5335921e+00 3.0169509e+00 2.8661222e+00 3.0732956e+00 1.9492232e+00 3.0013391e+00 2.3437032e+00 2.3116343e+00 2.5873149e+00 2.5591371e+00 3.0631725e+00 2.0220740e+00 2.8270253e+00 2.8656468e+00 2.4892190e+00 3.0178921e+00 2.3656538e+00 3.1846482e+00 2.4132559e+00 3.3163294e+00 3.0590735e+00 2.6993871e+00 2.8174914e+00 3.2310326e+00 3.4162231e+00 2.8802219e+00 1.9932786e+00 2.3173648e+00 2.2314118e+00 2.3212593e+00 3.4779999e+00 2.8654680e+00 2.8662571e+00 3.1113805e+00 2.8927401e+00 2.4634131e+00 2.4685230e+00 2.7948819e+00 2.9596963e+00 2.4341346e+00 2.0039447e+00 2.6000813e+00 2.5498770e+00 2.5700421e+00 2.6813098e+00 1.7123398e+00 2.4913669e+00 4.4418755e+00 3.5123791e+00 4.3488707e+00 3.9713081e+00 4.2172545e+00 5.0700045e+00 2.9631582e+00 4.7239900e+00 4.2113881e+00 4.5979409e+00 3.5255287e+00 3.7162377e+00 3.9448212e+00 3.4598280e+00 3.6097419e+00 3.7620043e+00 3.8810240e+00 5.1822310e+00 5.3953096e+00 3.4508156e+00 4.1665786e+00 3.3353616e+00 5.1763300e+00 3.3260356e+00 4.1143832e+00 4.4201622e+00 3.2188998e+00 3.2929599e+00 4.0183758e+00 4.2229849e+00 4.5637045e+00 4.9290256e+00 4.0343724e+00 3.4708900e+00 3.9559935e+00 4.6576736e+00 4.0502252e+00 3.8718131e+00 3.1963475e+00 3.8610636e+00 4.0785553e+00 3.6345765e+00 3.5123791e+00 4.3416284e+00 4.1864302e+00 3.7018916e+00 3.4568305e+00 3.6254423e+00 3.8415026e+00 3.4775621e+00 4.0293660e-01 5.0905001e-01 3.8934542e-01 8.1130291e-01 2.5251796e-01 4.2538717e-01 4.8927739e-01 1.2406194e+00 1.3074132e+00 8.5233811e-01 5.0090417e-01 1.1185330e+00 5.6700421e-01 8.1099042e-01 5.3022554e-01 4.1449626e-01 5.3665999e-01 5.0905001e-01 5.0592043e-01 4.1449626e-01 6.0181382e-01 6.0060595e-01 2.5651975e-01 3.4583729e-01 8.0064372e-01 8.1558458e-01 1.0597541e+00 3.8934542e-01 4.2667565e-01 9.0074515e-01 3.8934542e-01 4.1586001e-01 5.0180477e-01 4.0293660e-01 1.1003197e+00 2.5651975e-01 4.5581864e-01 6.6539428e-01 4.1312257e-01 5.7324170e-01 2.0656129e-01 7.1504098e-01 4.0293660e-01 3.6583368e+00 3.3018939e+00 3.7934214e+00 2.7118627e+00 3.4196168e+00 3.1646752e+00 3.4663954e+00 1.9965608e+00 3.4302944e+00 2.5753574e+00 2.2837561e+00 2.9283888e+00 2.7788099e+00 3.4116298e+00 2.3101107e+00 3.3028359e+00 3.1719381e+00 2.7826178e+00 3.2957091e+00 2.5882494e+00 3.5217244e+00 2.7724782e+00 3.6509512e+00 3.3998935e+00 3.1126354e+00 3.2681313e+00 3.6719821e+00 3.8384263e+00 3.2202056e+00 2.2240476e+00 2.4960474e+00 2.3970928e+00 2.6119950e+00 3.7951491e+00 3.1592993e+00 3.2300555e+00 3.5604418e+00 3.2025128e+00 2.7701355e+00 2.6892823e+00 3.0524247e+00 3.3177721e+00 2.7092568e+00 2.0227167e+00 2.8731220e+00 2.8671099e+00 2.8775912e+00 3.0586720e+00 1.7332099e+00 2.7865812e+00 4.7866828e+00 3.8123695e+00 4.7714708e+00 4.3189924e+00 4.5796358e+00 5.5132325e+00 3.1921277e+00 5.1489022e+00 4.5737586e+00 5.0249531e+00 3.9185849e+00 4.0697987e+00 4.3502657e+00 3.7349501e+00 3.9102370e+00 4.1311343e+00 4.2548570e+00 5.6362307e+00 5.8240252e+00 3.7174048e+00 4.5773480e+00 3.6270581e+00 5.6209145e+00 3.6774027e+00 4.5072397e+00 4.8555167e+00 3.5675580e+00 3.6401392e+00 4.3693804e+00 4.6657186e+00 5.0062061e+00 5.4227201e+00 4.3844239e+00 3.8261182e+00 4.2718480e+00 5.1373047e+00 4.4043123e+00 4.2383633e+00 3.5347392e+00 4.2868650e+00 4.4668117e+00 4.0716645e+00 3.8123695e+00 4.7338066e+00 4.5734052e+00 4.1036179e+00 3.7882079e+00 4.0078491e+00 4.1922661e+00 3.8027591e+00 6.8961791e-01 3.0546431e-01 4.4417983e-01 2.0656129e-01 4.1586001e-01 7.6625946e-01 8.9687438e-01 1.0919712e+00 5.7867728e-01 1.5422108e-01 7.3851529e-01 4.0293660e-01 4.1312257e-01 3.2586371e-01 5.7257017e-01 3.2816937e-01 4.1312257e-01 4.0147421e-01 2.0656129e-01 2.0656129e-01 2.0656129e-01 3.2586371e-01 3.2586371e-01 4.1312257e-01 7.0437330e-01 8.5205778e-01 3.0546431e-01 3.2352160e-01 5.0905001e-01 3.0546431e-01 6.5172743e-01 1.0000000e-01 2.1269358e-01 1.1283882e+00 6.1092863e-01 4.0293660e-01 5.0592043e-01 4.1586001e-01 4.0293660e-01 4.1449626e-01 3.7255734e-01 1.2418578e-01 3.4445326e+00 3.1392617e+00 3.6011035e+00 2.6118700e+00 3.2516941e+00 3.0511838e+00 3.3218097e+00 1.9189245e+00 3.2468925e+00 2.4924452e+00 2.2081024e+00 2.8038661e+00 2.6291264e+00 3.2767369e+00 2.1964719e+00 3.1025274e+00 3.0696611e+00 2.6485861e+00 3.1554034e+00 2.4715204e+00 3.4135983e+00 2.6151245e+00 3.5092032e+00 3.2604423e+00 2.9354140e+00 3.0782101e+00 3.4818889e+00 3.6726568e+00 3.0922811e+00 2.0843471e+00 2.3874354e+00 2.2845234e+00 2.4794505e+00 3.6775470e+00 3.0659000e+00 3.1055388e+00 3.3775462e+00 3.0430948e+00 2.6597612e+00 2.5873149e+00 2.9471553e+00 3.1807044e+00 2.5795723e+00 1.9450499e+00 2.7640668e+00 2.7473221e+00 2.7611864e+00 2.9015702e+00 1.6626642e+00 2.6693888e+00 4.6823704e+00 3.7130994e+00 4.6117428e+00 4.1946425e+00 4.4565357e+00 5.3399939e+00 3.1168466e+00 4.9805386e+00 4.4303862e+00 4.8738189e+00 3.7806643e+00 3.9387918e+00 4.2018804e+00 3.6441274e+00 3.8290120e+00 4.0132700e+00 4.1177139e+00 5.4615788e+00 5.6559440e+00 3.5983434e+00 4.4321573e+00 3.5405803e+00 5.4429455e+00 3.5441556e+00 4.3687483e+00 4.6853394e+00 3.4399664e+00 3.5203203e+00 4.2473048e+00 4.4861009e+00 4.8281381e+00 5.2242271e+00 4.2652659e+00 3.6876909e+00 4.1503255e+00 4.9488209e+00 4.2966585e+00 4.1071698e+00 3.4205830e+00 4.1292490e+00 4.3363292e+00 3.9150359e+00 3.7130994e+00 4.5977729e+00 4.4473292e+00 3.9643224e+00 3.6603913e+00 3.8715927e+00 4.0861975e+00 3.6954796e+00 5.0991930e-01 1.1327825e+00 5.7257017e-01 4.0293660e-01 3.0811765e-01 1.5771666e+00 1.7488874e+00 1.2431040e+00 8.1273630e-01 1.4170618e+00 1.0106392e+00 1.0389435e+00 9.3824087e-01 7.3813096e-01 7.5976039e-01 6.6491075e-01 6.0611244e-01 6.9728513e-01 8.8861541e-01 8.5177726e-01 3.8776762e-01 4.2538717e-01 1.0346741e+00 1.2943100e+00 1.5015203e+00 5.0991930e-01 6.2482915e-01 1.1473003e+00 5.0991930e-01 1.2418578e-01 7.6752131e-01 7.4586719e-01 6.0181382e-01 3.0275928e-01 7.7869083e-01 1.0440187e+00 4.0293660e-01 1.0120221e+00 3.2352160e-01 1.0597541e+00 6.4704320e-01 3.7504939e+00 3.3717768e+00 3.8731169e+00 2.7062054e+00 3.4865562e+00 3.1921903e+00 3.5262546e+00 1.9522524e+00 3.5018009e+00 2.5914913e+00 2.1818668e+00 2.9807120e+00 2.7874290e+00 3.4557351e+00 2.3604042e+00 3.3915488e+00 3.2027420e+00 2.8150728e+00 3.3206640e+00 2.6018930e+00 3.5642457e+00 2.8360166e+00 3.6902583e+00 3.4394878e+00 3.1847477e+00 3.3503379e+00 3.7461474e+00 3.9068076e+00 3.2666666e+00 2.2590074e+00 2.4950353e+00 2.3935209e+00 2.6534332e+00 3.8259590e+00 3.1834936e+00 3.2834077e+00 3.6377049e+00 3.2390016e+00 2.8060305e+00 2.7012392e+00 3.0647279e+00 3.3658240e+00 2.7423171e+00 1.9645331e+00 2.8984764e+00 2.9033203e+00 2.9139413e+00 3.1189900e+00 1.7118795e+00 2.8228127e+00 4.8290847e+00 3.8416142e+00 4.8350745e+00 4.3569606e+00 4.6261788e+00 5.5774554e+00 3.1958228e+00 5.2067803e+00 4.6153241e+00 5.0947934e+00 3.9803199e+00 4.1159553e+00 4.4131159e+00 3.7587872e+00 3.9513472e+00 4.1881498e+00 4.3024754e+00 5.7071195e+00 5.8839539e+00 3.7280682e+00 4.6419531e+00 3.6578722e+00 5.6843788e+00 3.7276068e+00 4.5625120e+00 4.9179194e+00 3.6182608e+00 3.6866535e+00 4.4136707e+00 4.7307689e+00 5.0723886e+00 5.5062533e+00 4.4301849e+00 3.8690719e+00 4.2943891e+00 5.2192815e+00 4.4536259e+00 4.2828634e+00 3.5797958e+00 4.3570079e+00 4.5278761e+00 4.1542558e+00 3.8416142e+00 4.7899951e+00 4.6341170e+00 4.1740602e+00 3.8316735e+00 4.0656969e+00 4.2413113e+00 3.8376713e+00 6.8961791e-01 3.0811765e-01 1.4096146e-01 6.4755655e-01 1.1229906e+00 1.3835747e+00 8.6361309e-01 4.2667565e-01 9.4009473e-01 7.0784540e-01 5.3665999e-01 6.2482915e-01 6.3977563e-01 4.3691963e-01 4.4651726e-01 1.5422108e-01 3.7598397e-01 4.4535192e-01 3.7598397e-01 2.1845981e-01 1.4096146e-01 5.5419992e-01 1.0065841e+00 1.1474460e+00 0.0000000e+00 3.0811765e-01 6.5223271e-01 0.0000000e+00 5.0991930e-01 3.2586371e-01 4.2667565e-01 8.3172002e-01 5.0991930e-01 5.6769031e-01 7.5082357e-01 2.1845981e-01 7.0479928e-01 3.0811765e-01 6.4755655e-01 2.1845981e-01 3.4865562e+00 3.1726595e+00 3.6377960e+00 2.5987470e+00 3.2814045e+00 3.0627375e+00 3.3515846e+00 1.8841865e+00 3.2769379e+00 2.5038079e+00 2.1311468e+00 2.8311678e+00 2.6104387e+00 3.2962520e+00 2.2214438e+00 3.1433122e+00 3.0878634e+00 2.6552472e+00 3.1570103e+00 2.4668912e+00 3.4394878e+00 2.6411293e+00 3.5233648e+00 3.2747247e+00 2.9659871e+00 3.1154783e+00 3.5134741e+00 3.7059620e+00 3.1148696e+00 2.0851901e+00 2.3731428e+00 2.2655571e+00 2.4927109e+00 3.6920087e+00 3.0823446e+00 3.1337459e+00 3.4135200e+00 3.0481703e+00 2.6780487e+00 2.5874301e+00 2.9489507e+00 3.2027420e+00 2.5873149e+00 1.8973383e+00 2.7738355e+00 2.7632614e+00 2.7778954e+00 2.9269923e+00 1.6390769e+00 2.6848587e+00 4.7106706e+00 3.7313856e+00 4.6446321e+00 4.2142736e+00 4.4836580e+00 5.3716885e+00 3.1250284e+00 5.0074019e+00 4.4485220e+00 4.9128219e+00 3.8150636e+00 3.9624529e+00 4.2358121e+00 3.6602286e+00 3.8605980e+00 4.0488387e+00 4.1418643e+00 5.4970002e+00 5.6855224e+00 3.5964347e+00 4.4685630e+00 3.5634461e+00 5.4730406e+00 3.5693950e+00 4.3989089e+00 4.7150659e+00 3.4668130e+00 3.5464993e+00 4.2723380e+00 4.5155386e+00 4.8594290e+00 5.2647079e+00 4.2921213e+00 3.7064459e+00 4.1581964e+00 4.9913682e+00 4.3286007e+00 4.1303097e+00 3.4468286e+00 4.1669742e+00 4.3729308e+00 3.9624170e+00 3.7313856e+00 4.6297577e+00 4.4844827e+00 4.0056359e+00 3.6817961e+00 3.9035218e+00 4.1179678e+00 3.7164366e+00 6.2024833e-01 8.1304731e-01 1.1868139e+00 4.8036801e-01 7.1799256e-01 2.8192292e-01 3.2816937e-01 3.2816937e-01 3.0546431e-01 3.2352160e-01 3.2352160e-01 8.5205778e-01 4.8927739e-01 6.6384020e-01 7.3496673e-01 4.5581864e-01 2.4837156e-01 3.2586371e-01 7.6752131e-01 7.4549115e-01 3.2352160e-01 4.1449626e-01 5.0180477e-01 6.8961791e-01 5.8851328e-01 2.5251796e-01 6.8961791e-01 1.0919712e+00 3.7255734e-01 4.2667565e-01 1.4993782e+00 1.0344911e+00 5.0592043e-01 4.5581864e-01 8.1304731e-01 3.0546431e-01 8.5205778e-01 1.0000000e-01 4.9766035e-01 3.3472053e+00 3.0922811e+00 3.5254266e+00 2.6661987e+00 3.2094276e+00 3.0570957e+00 3.2869053e+00 2.0190980e+00 3.1913594e+00 2.5206151e+00 2.3403819e+00 2.7928582e+00 2.6680945e+00 3.2615924e+00 2.2070201e+00 3.0233425e+00 3.0716969e+00 2.6575076e+00 3.1694367e+00 2.5088543e+00 3.4030318e+00 2.5954147e+00 3.4988409e+00 3.2483608e+00 2.8891737e+00 3.0123702e+00 3.4182420e+00 3.6203759e+00 3.0811775e+00 2.1190324e+00 2.4416796e+00 2.3440712e+00 2.4897570e+00 3.6753309e+00 3.0715435e+00 3.0851463e+00 3.3123070e+00 3.0424689e+00 2.6625505e+00 2.6241824e+00 2.9689697e+00 3.1616811e+00 2.5961850e+00 2.0559262e+00 2.7803619e+00 2.7462372e+00 2.7639489e+00 2.8736288e+00 1.7674365e+00 2.6773131e+00 4.6660957e+00 3.7173526e+00 4.5567672e+00 4.1782968e+00 4.4326194e+00 5.2720689e+00 3.1469325e+00 4.9232255e+00 4.4057732e+00 4.8164157e+00 3.7433882e+00 3.9194796e+00 4.1567419e+00 3.6582432e+00 3.8303544e+00 3.9861488e+00 4.0892044e+00 5.3882212e+00 5.5946413e+00 3.6180819e+00 4.3839191e+00 3.5469476e+00 5.3734444e+00 3.5262672e+00 4.3306501e+00 4.6237863e+00 3.4237160e+00 3.5051302e+00 4.2288456e+00 4.4201622e+00 4.7609637e+00 5.1280035e+00 4.2469785e+00 3.6684143e+00 4.1480002e+00 4.8602572e+00 4.2765700e+00 4.0824098e+00 3.4092877e+00 4.0737132e+00 4.2991233e+00 3.8524190e+00 3.7173526e+00 4.5590471e+00 4.4107160e+00 3.9202843e+00 3.6509512e+00 3.8388884e+00 4.0680120e+00 3.6894983e+00 4.1449626e-01 6.6539428e-01 1.0717668e+00 1.1847335e+00 7.0776547e-01 3.2816937e-01 9.2095040e-01 4.4651726e-01 6.0060595e-01 3.8934542e-01 6.1092863e-01 3.7598397e-01 3.0000000e-01 4.1312257e-01 2.4837156e-01 4.0293660e-01 4.1312257e-01 2.0656129e-01 3.0000000e-01 6.0611244e-01 7.3535471e-01 9.3801395e-01 3.0811765e-01 4.2538717e-01 7.1462831e-01 3.0811765e-01 5.2574978e-01 3.0275928e-01 3.2816937e-01 1.1107977e+00 4.5470518e-01 4.1449626e-01 4.8927739e-01 4.1449626e-01 4.4417983e-01 2.8192292e-01 5.2942799e-01 2.5251796e-01 3.4297053e+00 3.0906838e+00 3.5704156e+00 2.5301680e+00 3.2062204e+00 2.9663489e+00 3.2615889e+00 1.8330979e+00 3.2074600e+00 2.4030878e+00 2.1292724e+00 2.7344480e+00 2.5716369e+00 3.2053511e+00 2.1242643e+00 3.0798277e+00 2.9831836e+00 2.5729378e+00 3.0964590e+00 2.3917863e+00 3.3353616e+00 2.5635110e+00 3.4441347e+00 3.1882407e+00 2.8938821e+00 3.0477086e+00 3.4484194e+00 3.6265826e+00 3.0209783e+00 2.0203134e+00 2.3063579e+00 2.2046610e+00 2.4100833e+00 3.5972040e+00 2.9748436e+00 3.0349291e+00 3.3414931e+00 2.9918962e+00 2.5764694e+00 2.5038051e+00 2.8573838e+00 3.1113597e+00 2.5079404e+00 1.8623849e+00 2.6799601e+00 2.6656374e+00 2.6804452e+00 2.8458006e+00 1.5870088e+00 2.5906376e+00 4.6056614e+00 3.6293396e+00 4.5625120e+00 4.1184849e+00 4.3862724e+00 5.2957861e+00 3.0253131e+00 4.9300368e+00 4.3656957e+00 4.8256905e+00 3.7228356e+00 3.8717400e+00 4.1487889e+00 3.5605424e+00 3.7509165e+00 3.9489970e+00 4.0502806e+00 5.4193574e+00 5.6096505e+00 3.5201263e+00 4.3797165e+00 3.4555095e+00 5.4004015e+00 3.4805320e+00 4.3069452e+00 4.6373516e+00 3.3738930e+00 3.4478147e+00 4.1762321e+00 4.4428877e+00 4.7870294e+00 5.1982218e+00 4.1948678e+00 3.6180819e+00 4.0668114e+00 4.9227056e+00 4.2245318e+00 4.0358897e+00 3.3459883e+00 4.0835979e+00 4.2783731e+00 3.8797354e+00 3.6293396e+00 4.5370189e+00 4.3879553e+00 3.9155334e+00 3.5955337e+00 3.8113970e+00 4.0131848e+00 3.6132595e+00 5.2862779e-01 1.2431040e+00 1.5013525e+00 9.7779835e-01 5.3588338e-01 1.0669582e+00 8.1385214e-01 6.6432544e-01 7.2823007e-01 6.5223271e-01 5.1138698e-01 5.6700421e-01 2.5251796e-01 4.6472023e-01 5.6769031e-01 4.9766035e-01 2.5651975e-01 2.1269358e-01 6.6432544e-01 1.1134787e+00 1.2632199e+00 1.4096146e-01 2.8507955e-01 7.6787403e-01 1.4096146e-01 4.0293660e-01 4.4651726e-01 5.1691876e-01 7.1840099e-01 4.1586001e-01 6.3108414e-01 8.7021234e-01 2.0000000e-01 8.1385214e-01 2.5251796e-01 7.6787403e-01 3.2586371e-01 3.6025735e+00 3.2810515e+00 3.7511944e+00 2.6894009e+00 3.3904673e+00 3.1636869e+00 3.4574937e+00 1.9666356e+00 3.3893691e+00 2.5954173e+00 2.1997395e+00 2.9322283e+00 2.7092568e+00 3.4012145e+00 2.3186758e+00 3.2568914e+00 3.1861493e+00 2.7595194e+00 3.2561045e+00 2.5646808e+00 3.5381764e+00 2.7476411e+00 3.6278993e+00 3.3809159e+00 3.0768226e+00 3.2277675e+00 3.6265617e+00 3.8150532e+00 3.2176230e+00 2.1864840e+00 2.4668912e+00 2.3596992e+00 2.5949561e+00 3.7935487e+00 3.1789378e+00 3.2360886e+00 3.5252258e+00 3.1522058e+00 2.7777040e+00 2.6819136e+00 3.0473722e+00 3.3079290e+00 2.6886547e+00 1.9757309e+00 2.8726212e+00 2.8654680e+00 2.8788483e+00 3.0349462e+00 1.7160413e+00 2.7852734e+00 4.8087107e+00 3.8282466e+00 4.7531334e+00 4.3176393e+00 4.5857287e+00 5.4831923e+00 3.2147850e+00 5.1185883e+00 4.5544260e+00 5.0194259e+00 3.9185849e+00 4.0655452e+00 4.3416283e+00 3.7535680e+00 3.9509795e+00 4.1478442e+00 4.2472736e+00 5.6096505e+00 5.7957776e+00 3.6945993e+00 4.5734622e+00 3.6568202e+00 5.5854254e+00 3.6720840e+00 4.5038991e+00 4.8262859e+00 3.5684917e+00 3.6474985e+00 4.3740189e+00 4.6282931e+00 4.9713928e+00 5.3806679e+00 4.3928114e+00 3.8121990e+00 4.2612863e+00 5.1032991e+00 4.4267055e+00 4.2347444e+00 3.5464871e+00 4.2738510e+00 4.4745238e+00 4.0663411e+00 3.8282466e+00 4.7338066e+00 4.5852690e+00 4.1075310e+00 3.7823897e+00 4.0070636e+00 4.2156933e+00 3.8157950e+00 1.6177449e+00 1.7454671e+00 1.2604558e+00 8.6361309e-01 1.4955532e+00 1.0118409e+00 1.1594648e+00 9.6204649e-01 6.2081167e-01 9.1750357e-01 8.7504951e-01 7.6752131e-01 8.0660588e-01 9.5965467e-01 9.2859317e-01 5.7324170e-01 6.2205176e-01 1.1313840e+00 1.2653669e+00 1.4930627e+00 6.4755655e-01 7.0479928e-01 1.2236003e+00 6.4755655e-01 2.1269358e-01 8.5105559e-01 7.7360126e-01 7.1169738e-01 2.5651975e-01 8.7229670e-01 1.1327578e+00 5.3588338e-01 1.0269295e+00 3.8934542e-01 1.1042097e+00 7.2823007e-01 4.0317004e+00 3.6659830e+00 4.1618561e+00 3.0123702e+00 3.7804276e+00 3.4970843e+00 3.8244351e+00 2.2591077e+00 3.7930789e+00 2.8953397e+00 2.4889124e+00 3.2809188e+00 3.0866488e+00 3.7578933e+00 2.6595288e+00 3.6754272e+00 3.5073435e+00 3.1173742e+00 3.6212723e+00 2.9065572e+00 3.8667462e+00 3.1302383e+00 3.9918403e+00 3.7416229e+00 3.4760444e+00 3.6375992e+00 4.0358101e+00 4.2016915e+00 3.5683934e+00 2.5569968e+00 2.8007817e+00 2.6989368e+00 2.9539253e+00 4.1306024e+00 3.4882801e+00 3.5831257e+00 3.9280671e+00 3.5362697e+00 3.1099883e+00 3.0065416e+00 3.3706887e+00 3.6672620e+00 3.0442126e+00 2.2719663e+00 3.2032390e+00 3.2071637e+00 3.2176230e+00 3.4155491e+00 2.0139971e+00 3.1260028e+00 5.1310217e+00 4.1456639e+00 5.1323742e+00 4.6609614e+00 4.9284761e+00 5.8739676e+00 3.4984873e+00 5.5048216e+00 4.9175276e+00 5.3898806e+00 4.2781762e+00 4.4176533e+00 4.7107211e+00 4.0621414e+00 4.2494597e+00 4.4865569e+00 4.6045396e+00 6.0012333e+00 6.1816086e+00 4.0339598e+00 4.9390284e+00 3.9601358e+00 5.9803696e+00 4.0277694e+00 4.8626276e+00 5.2147981e+00 3.9185849e+00 3.9887029e+00 4.7161140e+00 5.0257341e+00 5.3673499e+00 5.7939320e+00 4.7320534e+00 4.1713411e+00 4.5995433e+00 5.5085511e+00 4.7536729e+00 4.5857356e+00 3.8819510e+00 4.6519178e+00 4.8256399e+00 4.4430890e+00 4.1456639e+00 5.0898961e+00 4.9316646e+00 4.4680158e+00 4.1325542e+00 4.3648035e+00 4.5412859e+00 4.1418557e+00 4.5581864e-01 4.1586001e-01 7.7074935e-01 5.0991930e-01 7.1840099e-01 7.2486328e-01 7.3145860e-01 1.2122249e+00 9.2112464e-01 1.1384810e+00 1.1451403e+00 9.1163729e-01 7.0386584e-01 7.4855857e-01 1.2220203e+00 1.1947245e+00 6.6827038e-01 6.2081167e-01 3.4378533e-01 1.1229906e+00 9.9348625e-01 5.2942799e-01 1.1229906e+00 1.5344133e+00 8.2275389e-01 8.5233811e-01 1.8985661e+00 1.4692412e+00 8.9653332e-01 8.7420176e-01 1.2431040e+00 7.3813096e-01 1.2951131e+00 5.5419992e-01 9.3801395e-01 3.5789198e+00 3.3663244e+00 3.7753619e+00 3.0049442e+00 3.4909841e+00 3.3695525e+00 3.5654259e+00 2.3989172e+00 3.4663502e+00 2.8427326e+00 2.7185849e+00 3.0894572e+00 3.0108764e+00 3.5617386e+00 2.5173832e+00 3.2758681e+00 3.3732554e+00 2.9816791e+00 3.4895316e+00 2.8451507e+00 3.6905956e+00 2.8989400e+00 3.8036776e+00 3.5549103e+00 3.1734856e+00 3.2772927e+00 3.6834126e+00 3.8868430e+00 3.3809159e+00 2.4588872e+00 2.7850016e+00 2.6918796e+00 2.8113773e+00 3.9804187e+00 3.3742776e+00 3.3692592e+00 3.5726491e+00 3.3587234e+00 2.9691171e+00 2.9539253e+00 3.2926883e+00 3.4584304e+00 2.9217347e+00 2.4321061e+00 3.0988783e+00 3.0546600e+00 3.0736357e+00 3.1699903e+00 2.1306832e+00 2.9913743e+00 4.9420128e+00 4.0177712e+00 4.8123874e+00 4.4703485e+00 4.7113827e+00 5.5147622e+00 3.4715574e+00 5.1811127e+00 4.6944291e+00 5.0587041e+00 4.0117533e+00 4.2085851e+00 4.4199792e+00 3.9616570e+00 4.1103933e+00 4.2537610e+00 4.3721243e+00 5.6218420e+00 5.8419148e+00 3.9412893e+00 4.6390186e+00 3.8408636e+00 5.6159291e+00 3.8175051e+00 4.5984929e+00 4.8771654e+00 3.7143727e+00 3.7943375e+00 4.5141564e+00 4.6734732e+00 5.0086627e+00 5.3396700e+00 4.5298770e+00 3.9647930e+00 4.4561969e+00 5.0778756e+00 4.5477422e+00 4.3671210e+00 3.6996802e+00 4.3269400e+00 4.5602465e+00 4.0901232e+00 4.0177712e+00 4.8233796e+00 4.6689006e+00 4.1757336e+00 3.9466531e+00 4.1130674e+00 4.3408596e+00 3.9840684e+00 5.3588338e-01 9.7098574e-01 6.0611244e-01 7.4549115e-01 1.0101422e+00 8.1242502e-01 1.2342162e+00 1.1486378e+00 1.1959482e+00 1.4468211e+00 1.0906388e+00 9.4287188e-01 1.0346741e+00 1.3793330e+00 1.4148192e+00 1.0065841e+00 5.5419992e-01 2.8507955e-01 1.3835747e+00 1.2681309e+00 9.0679720e-01 1.3835747e+00 1.6801917e+00 1.0588560e+00 1.0122141e+00 2.2040881e+00 1.5564198e+00 1.0122141e+00 7.7553525e-01 1.4987155e+00 7.4893123e-01 1.4320120e+00 7.3813096e-01 1.1765359e+00 3.3186105e+00 3.0934278e+00 3.5115632e+00 2.9015832e+00 3.2557855e+00 3.1381850e+00 3.2787144e+00 2.3983798e+00 3.2261964e+00 2.6655261e+00 2.7738368e+00 2.8425716e+00 2.9377092e+00 3.3097860e+00 2.3365894e+00 3.0236933e+00 3.1147370e+00 2.7988444e+00 3.3431646e+00 2.7201960e+00 3.4033622e+00 2.7009102e+00 3.5863979e+00 3.3171611e+00 2.9439491e+00 3.0327979e+00 3.4475678e+00 3.6195561e+00 3.1337459e+00 2.3758157e+00 2.6957302e+00 2.6219409e+00 2.6429556e+00 3.7305206e+00 3.1152653e+00 3.0762634e+00 3.3088561e+00 3.2115055e+00 2.7318540e+00 2.8101506e+00 3.0967820e+00 3.1998457e+00 2.7619926e+00 2.4596921e+00 2.9002737e+00 2.8148869e+00 2.8449691e+00 2.9378173e+00 2.1723936e+00 2.7843048e+00 4.6358686e+00 3.7621042e+00 4.5316360e+00 4.1928583e+00 4.4221843e+00 5.2364242e+00 3.2682245e+00 4.9072991e+00 4.4428425e+00 4.7561724e+00 3.7219581e+00 3.9498605e+00 4.1390009e+00 3.7275066e+00 3.8377652e+00 3.9569614e+00 4.0917968e+00 5.3289557e+00 5.5738695e+00 3.7540308e+00 4.3457024e+00 3.5797958e+00 5.3465693e+00 3.5720882e+00 4.3012547e+00 4.5936468e+00 3.4616111e+00 3.5205889e+00 4.2389017e+00 4.4031390e+00 4.7432976e+00 5.0569442e+00 4.2531342e+00 3.7092459e+00 4.2038056e+00 4.8064634e+00 4.2402361e+00 4.0806404e+00 3.4276314e+00 4.0438546e+00 4.2676463e+00 3.8085992e+00 3.7621042e+00 4.5272919e+00 4.3667579e+00 3.8946701e+00 3.7163265e+00 3.8338395e+00 4.0342445e+00 3.7061759e+00 4.4651726e-01 4.4651726e-01 3.2816937e-01 5.7257017e-01 3.4378533e-01 8.2384013e-01 6.6432544e-01 8.0758367e-01 9.3048953e-01 5.8851328e-01 4.3691963e-01 5.1691876e-01 8.8062848e-01 8.9917007e-01 5.0817745e-01 3.6171588e-01 3.2816937e-01 8.6361309e-01 7.3851529e-01 4.1449626e-01 8.6361309e-01 1.1845977e+00 5.4292906e-01 4.9766035e-01 1.6754036e+00 1.0919712e+00 5.3309112e-01 6.2024833e-01 9.7098574e-01 3.8934542e-01 9.3824087e-01 2.8507955e-01 6.5223271e-01 3.5185448e+00 3.2633258e+00 3.6996953e+00 2.8710255e+00 3.3892942e+00 3.2497279e+00 3.4561374e+00 2.2371784e+00 3.3772302e+00 2.7023432e+00 2.5704711e+00 2.9638836e+00 2.8904978e+00 3.4483274e+00 2.3826791e+00 3.1954061e+00 3.2493673e+00 2.8645447e+00 3.3669805e+00 2.7184506e+00 3.5654259e+00 2.7812639e+00 3.6908684e+00 3.4451701e+00 3.0736340e+00 3.1881331e+00 3.6017790e+00 3.7914024e+00 3.2604423e+00 2.3308454e+00 2.6548674e+00 2.5630676e+00 2.6859989e+00 3.8615219e+00 3.2492317e+00 3.2498302e+00 3.4856775e+00 3.2460061e+00 2.8456767e+00 2.8220742e+00 3.1709561e+00 3.3452644e+00 2.7965957e+00 2.2780262e+00 2.9733693e+00 2.9362769e+00 2.9511072e+00 3.0600825e+00 1.9688013e+00 2.8661222e+00 4.8176767e+00 3.8889176e+00 4.7230291e+00 4.3578295e+00 4.5962942e+00 5.4442203e+00 3.3242177e+00 5.1039076e+00 4.5914416e+00 4.9653393e+00 3.8994399e+00 4.0931001e+00 4.3174903e+00 3.8262108e+00 3.9673816e+00 4.1302370e+00 4.2654212e+00 5.5551457e+00 5.7673205e+00 3.8189943e+00 4.5366342e+00 3.7059360e+00 5.5500825e+00 3.6985459e+00 4.4934936e+00 4.7995387e+00 3.5922369e+00 3.6724425e+00 4.3963259e+00 4.6010378e+00 4.9364818e+00 5.2936070e+00 4.4094664e+00 3.8558772e+00 4.3453589e+00 5.0159331e+00 4.4225169e+00 4.2579435e+00 3.5745624e+00 4.2301614e+00 4.4459076e+00 3.9875954e+00 3.8889176e+00 4.7169919e+00 4.5531173e+00 4.0619315e+00 3.8238093e+00 3.9999729e+00 4.2141826e+00 3.8611742e+00 6.3808075e-01 3.0275928e-01 3.7598397e-01 2.1269358e-01 5.6769031e-01 3.4378533e-01 5.3022554e-01 5.0991930e-01 2.1845981e-01 1.4096146e-01 1.4096146e-01 4.5581864e-01 4.5581864e-01 3.0811765e-01 6.0670504e-01 7.3496673e-01 4.2667565e-01 3.2816937e-01 4.0293660e-01 4.2667565e-01 7.6787403e-01 1.4096146e-01 1.2418578e-01 1.2394907e+00 7.1504098e-01 3.2586371e-01 5.2942799e-01 5.2862779e-01 3.2586371e-01 5.2942799e-01 2.5651975e-01 2.1269358e-01 3.4944845e+00 3.2032390e+00 3.6588207e+00 2.7040077e+00 3.3172489e+00 3.1387381e+00 3.3902207e+00 2.0195610e+00 3.3129652e+00 2.5744164e+00 2.3173648e+00 2.8753045e+00 2.7215057e+00 3.3565935e+00 2.2694598e+00 3.1556501e+00 3.1511848e+00 2.7390328e+00 3.2351115e+00 2.5646808e+00 3.4858646e+00 2.6854398e+00 3.5885398e+00 3.3452644e+00 3.0014619e+00 3.1359624e+00 3.5442447e+00 3.7351231e+00 3.1683717e+00 2.1722580e+00 2.4832809e+00 2.3831271e+00 2.5617005e+00 3.7607269e+00 3.1489919e+00 3.1764760e+00 3.4370400e+00 3.1222134e+00 2.7420671e+00 2.6759392e+00 3.0406669e+00 3.2584404e+00 2.6649106e+00 2.0477765e+00 2.8508344e+00 2.8325946e+00 2.8443188e+00 2.9745020e+00 1.7495699e+00 2.7521074e+00 4.7498176e+00 3.7908064e+00 4.6736601e+00 4.2736523e+00 4.5261084e+00 5.4025762e+00 3.1986968e+00 5.0495127e+00 4.5070435e+00 4.9284820e+00 3.8418637e+00 4.0108142e+00 4.2628455e+00 3.7198158e+00 3.8891307e+00 4.0719001e+00 4.1917075e+00 5.5215376e+00 5.7192826e+00 3.6876129e+00 4.4897240e+00 3.6129201e+00 5.5066558e+00 3.6138577e+00 4.4349731e+00 4.7514299e+00 3.5090368e+00 3.5920050e+00 4.3185209e+00 4.5521574e+00 4.8905855e+00 5.2768100e+00 4.3339547e+00 3.7672401e+00 4.2403934e+00 4.9963306e+00 4.3598652e+00 4.1826701e+00 3.4920978e+00 4.1853524e+00 4.3933832e+00 3.9574195e+00 3.7908064e+00 4.6611791e+00 4.5034762e+00 4.0149574e+00 3.7307866e+00 3.9355645e+00 4.1498459e+00 3.7732223e+00 6.0551856e-01 4.4535192e-01 6.0670504e-01 1.1765359e+00 6.9325418e-01 9.2288144e-01 9.3637892e-01 7.3535471e-01 5.3665999e-01 5.8914551e-01 1.0576043e+00 1.0106392e+00 4.5581864e-01 5.4292906e-01 4.5581864e-01 9.4009473e-01 8.6290690e-01 4.5581864e-01 9.4009473e-01 1.3885563e+00 6.5223271e-01 7.4740267e-01 1.7041201e+00 1.3421549e+00 7.2823007e-01 6.0611244e-01 1.0653845e+00 6.0121055e-01 1.1521791e+00 4.1586001e-01 7.7919451e-01 3.1037808e+00 2.8727295e+00 3.2914954e+00 2.5112138e+00 2.9950832e+00 2.8632951e+00 3.0711321e+00 1.9332545e+00 2.9709745e+00 2.3448578e+00 2.2688022e+00 2.5914913e+00 2.5183808e+00 3.0579528e+00 2.0217308e+00 2.7906520e+00 2.8719896e+00 2.4738237e+00 2.9926636e+00 2.3440712e+00 3.1967616e+00 2.3980102e+00 3.3005331e+00 3.0483897e+00 2.6752185e+00 2.7868575e+00 3.1931777e+00 3.3968373e+00 2.8794765e+00 1.9640287e+00 2.2899742e+00 2.1990648e+00 2.3082381e+00 3.4762640e+00 2.8726212e+00 2.8752807e+00 3.0841055e+00 2.8599559e+00 2.4658566e+00 2.4543822e+00 2.7852734e+00 2.9558536e+00 2.4184985e+00 1.9730073e+00 2.5941661e+00 2.5490308e+00 2.5692104e+00 2.6676432e+00 1.6855044e+00 2.4875166e+00 4.4549901e+00 3.5192877e+00 4.3283747e+00 3.9701062e+00 4.2192020e+00 5.0364062e+00 2.9740218e+00 4.6944291e+00 4.1953299e+00 4.5849301e+00 3.5249823e+00 3.7114178e+00 3.9340128e+00 3.4663826e+00 3.6308220e+00 3.7719045e+00 3.8752244e+00 5.1489675e+00 5.3620178e+00 3.4363773e+00 4.1584261e+00 3.3485848e+00 5.1375979e+00 3.3209346e+00 4.1101337e+00 4.3926615e+00 3.2186045e+00 3.2982828e+00 4.0192434e+00 4.1885158e+00 4.5274663e+00 4.8785522e+00 4.0373008e+00 3.4619693e+00 3.9494193e+00 4.6147130e+00 4.0642632e+00 3.8698115e+00 3.2042572e+00 3.8459316e+00 4.0795080e+00 3.6227082e+00 3.5192877e+00 4.3377723e+00 4.1907472e+00 3.6992844e+00 3.4507210e+00 3.6230931e+00 3.8568809e+00 3.4855556e+00 4.5581864e-01 1.2418578e-01 6.2660376e-01 5.1607523e-01 5.2655962e-01 8.0096515e-01 4.0438741e-01 3.0546431e-01 4.0438741e-01 6.4806901e-01 7.1504098e-01 4.4535192e-01 3.2586371e-01 4.9857388e-01 7.0784540e-01 6.2081167e-01 4.5581864e-01 7.0784540e-01 9.3824087e-01 4.0147421e-01 3.2586371e-01 1.5252485e+00 8.1558458e-01 3.7598397e-01 4.0147421e-01 8.1099042e-01 1.2418578e-01 6.9006418e-01 2.1269358e-01 5.0270183e-01 3.4104878e+00 3.1150013e+00 3.5735680e+00 2.6813198e+00 3.2413086e+00 3.0598576e+00 3.2985843e+00 2.0418831e+00 3.2329790e+00 2.5171713e+00 2.3816204e+00 2.7937685e+00 2.7102198e+00 3.2724336e+00 2.2054062e+00 3.0737397e+00 3.0650685e+00 2.6738621e+00 3.1983741e+00 2.5253420e+00 3.3951420e+00 2.6185785e+00 3.5201263e+00 3.2642011e+00 2.9245132e+00 3.0559405e+00 3.4672191e+00 3.6501426e+00 3.0869409e+00 2.1449779e+00 2.4611582e+00 2.3678836e+00 2.5038051e+00 3.6798850e+00 3.0627375e+00 3.0833417e+00 3.3518108e+00 3.0810282e+00 2.6595270e+00 2.6323570e+00 2.9747184e+00 3.1719581e+00 2.6119950e+00 2.0875308e+00 2.7837517e+00 2.7481947e+00 2.7653278e+00 2.8959432e+00 1.7918633e+00 2.6810089e+00 4.6579399e+00 3.7114178e+00 4.5860934e+00 4.1845117e+00 4.4368376e+00 5.3138890e+00 3.1392617e+00 4.9608959e+00 4.4280037e+00 4.8387532e+00 3.7530908e+00 3.9304148e+00 4.1764880e+00 3.6510310e+00 3.8111653e+00 3.9838913e+00 4.1019789e+00 5.4302306e+00 5.6352651e+00 3.6334268e+00 4.4011799e+00 3.5332521e+00 5.4201202e+00 3.5378545e+00 4.3430510e+00 4.6603717e+00 3.4302376e+00 3.5053533e+00 4.2335883e+00 4.4640423e+00 4.8059592e+00 5.1881280e+00 4.2497073e+00 3.6834126e+00 4.1572779e+00 4.9128775e+00 4.2687104e+00 4.0908836e+00 3.4061169e+00 4.0989195e+00 4.3064904e+00 3.8759115e+00 3.7114178e+00 4.5707958e+00 4.4147019e+00 3.9326468e+00 3.6624594e+00 3.8494244e+00 4.0586633e+00 3.6843892e+00 4.0176783e-01 9.3801395e-01 3.7427929e-01 6.0551856e-01 4.9766035e-01 4.1449626e-01 2.5251796e-01 3.2352160e-01 7.0437330e-01 6.2024833e-01 2.4837156e-01 7.0826681e-01 8.1099042e-01 5.3665999e-01 5.7257017e-01 4.0293660e-01 5.3665999e-01 1.0321505e+00 3.2352160e-01 4.9857388e-01 1.2654843e+00 1.0181000e+00 4.9857388e-01 4.6472023e-01 6.6432544e-01 4.4535192e-01 8.1354181e-01 3.2586371e-01 4.4535192e-01 3.1652953e+00 2.9034751e+00 3.3383293e+00 2.4277398e+00 3.0113552e+00 2.8500355e+00 3.0981427e+00 1.7594421e+00 2.9944056e+00 2.3105335e+00 2.0559262e+00 2.5987706e+00 2.4171653e+00 3.0605340e+00 2.0054351e+00 2.8372675e+00 2.8748086e+00 2.4385827e+00 2.9411544e+00 2.2761106e+00 3.2149087e+00 2.3896630e+00 3.2878368e+00 3.0415738e+00 2.6906230e+00 2.8216976e+00 3.2222602e+00 3.4304873e+00 2.8822802e+00 1.8816502e+00 2.1994544e+00 2.0961718e+00 2.2729984e+00 3.4713427e+00 2.8746311e+00 2.8977380e+00 3.1239380e+00 2.8149627e+00 2.4626518e+00 2.3988202e+00 2.7512943e+00 2.9634715e+00 2.3744738e+00 1.7861398e+00 2.5679553e+00 2.5438761e+00 2.5602722e+00 2.6724144e+00 1.5132025e+00 2.4693940e+00 4.4818617e+00 3.5188715e+00 4.3693901e+00 3.9814082e+00 4.2430316e+00 5.0846750e+00 2.9378173e+00 4.7308926e+00 4.2026915e+00 4.6369546e+00 3.5592955e+00 3.7219741e+00 3.9702025e+00 3.4565374e+00 3.6485303e+00 3.8059948e+00 3.8952692e+00 5.2045888e+00 5.4038637e+00 3.3942456e+00 4.2018404e+00 3.3550631e+00 5.1838582e+00 3.3280757e+00 4.1439346e+00 4.4349731e+00 3.2284912e+00 3.3133518e+00 4.0354963e+00 4.2293106e+00 4.5707958e+00 4.9486891e+00 4.0555804e+00 3.4667821e+00 3.9402495e+00 4.6817169e+00 4.0952984e+00 3.8891597e+00 3.2181054e+00 3.8906833e+00 4.1179678e+00 3.6792093e+00 3.5188715e+00 4.3738746e+00 4.2318888e+00 3.7415007e+00 3.4482727e+00 3.6509580e+00 3.8867812e+00 3.4956677e+00 6.2660376e-01 4.1449626e-01 4.8927739e-01 7.0479928e-01 3.0546431e-01 2.5251796e-01 3.2816937e-01 5.7324170e-01 6.2538346e-01 3.7255734e-01 4.4535192e-01 5.7324170e-01 6.2482915e-01 5.3665999e-01 4.3691963e-01 6.2482915e-01 8.7420176e-01 3.2352160e-01 2.5651975e-01 1.4293465e+00 7.7360126e-01 2.5651975e-01 4.0147421e-01 7.1504098e-01 2.1269358e-01 6.2660376e-01 2.4837156e-01 4.1586001e-01 3.4011512e+00 3.1015495e+00 3.5629124e+00 2.6446848e+00 3.2242409e+00 3.0444970e+00 3.2854349e+00 1.9922212e+00 3.2208624e+00 2.4875432e+00 2.3235341e+00 2.7738624e+00 2.6761538e+00 3.2590031e+00 2.1770137e+00 3.0609726e+00 3.0488669e+00 2.6564689e+00 3.1671679e+00 2.4967542e+00 3.3778209e+00 2.5968629e+00 3.5012162e+00 3.2523394e+00 2.9093820e+00 3.0417674e+00 3.4541339e+00 3.6357801e+00 3.0695657e+00 2.1117686e+00 2.4265922e+00 2.3324013e+00 2.4794505e+00 3.6641654e+00 3.0465086e+00 3.0686943e+00 3.3395333e+00 3.0541897e+00 2.6428278e+00 2.6018930e+00 2.9558536e+00 3.1589081e+00 2.5867906e+00 2.0334278e+00 2.7624503e+00 2.7347909e+00 2.7481947e+00 2.8804789e+00 1.7294430e+00 2.6604040e+00 4.6390099e+00 3.6904099e+00 4.5721680e+00 4.1717456e+00 4.4201622e+00 5.3038306e+00 3.1101376e+00 4.9521292e+00 4.4131751e+00 4.8218478e+00 3.7351231e+00 3.9119157e+00 4.1593600e+00 3.6239123e+00 3.7806200e+00 3.9616940e+00 4.0893976e+00 5.4208566e+00 5.6225133e+00 3.6099406e+00 4.3833809e+00 3.5087649e+00 5.4106224e+00 3.5167400e+00 4.3287713e+00 4.6517741e+00 3.4091049e+00 3.4875352e+00 4.2154392e+00 4.4559638e+00 4.7948032e+00 5.1800729e+00 4.2298456e+00 3.6705550e+00 4.1464752e+00 4.8981257e+00 4.2482702e+00 4.0788778e+00 3.3871264e+00 4.0817153e+00 4.2852718e+00 3.8517038e+00 3.6904099e+00 4.5544260e+00 4.3933832e+00 3.9084933e+00 3.6377874e+00 3.8310704e+00 4.0381483e+00 3.6684338e+00 7.9016429e-01 9.0454394e-01 7.7553525e-01 6.5633874e-01 6.8961791e-01 6.5172743e-01 6.4755655e-01 6.9325418e-01 8.5690100e-01 7.5871717e-01 9.8800009e-01 6.3977563e-01 5.0503591e-01 9.0852141e-01 6.3977563e-01 6.2482915e-01 6.2605182e-01 4.4651726e-01 1.3039319e+00 4.5470518e-01 6.8801986e-01 9.4492923e-01 6.5223271e-01 6.9325418e-01 4.9674312e-01 7.6752131e-01 5.2574978e-01 3.9950977e+00 3.6682165e+00 4.1454421e+00 3.1171350e+00 3.7869887e+00 3.5623665e+00 3.8418637e+00 2.4093459e+00 3.7913585e+00 2.9787373e+00 2.6930133e+00 3.3123070e+00 3.1668302e+00 3.7985007e+00 2.6980573e+00 3.6472316e+00 3.5682291e+00 3.1756360e+00 3.6828708e+00 2.9891564e+00 3.9102500e+00 3.1455588e+00 4.0375495e+00 3.7881557e+00 3.4756264e+00 3.6204175e+00 4.0288578e+00 4.2043522e+00 3.6069560e+00 2.6127852e+00 2.9004348e+00 2.8010550e+00 3.0013391e+00 4.1901031e+00 3.5584876e+00 3.6126340e+00 3.9170234e+00 3.5823448e+00 3.1646752e+00 3.0923554e+00 3.4563987e+00 3.7021702e+00 3.1018442e+00 2.4341346e+00 3.2724336e+00 3.2604423e+00 3.2715632e+00 3.4335342e+00 2.1375243e+00 3.1807243e+00 5.1732049e+00 4.2085267e+00 5.1402548e+00 4.7090394e+00 4.9640507e+00 5.8783901e+00 3.5970425e+00 5.5195747e+00 4.9586651e+00 5.3905797e+00 4.2919639e+00 4.4545908e+00 4.7214902e+00 4.1323720e+00 4.2962344e+00 4.5078390e+00 4.6379987e+00 5.9985593e+00 6.1927610e+00 4.1173292e+00 4.9467209e+00 4.0217355e+00 5.9855018e+00 4.0597680e+00 4.8845913e+00 5.2228901e+00 3.9505682e+00 4.0262006e+00 4.7557226e+00 5.0295898e+00 5.3695643e+00 5.7704875e+00 4.7697480e+00 4.2124808e+00 4.6690776e+00 5.4857949e+00 4.7867344e+00 4.6237863e+00 3.9220577e+00 4.6512574e+00 4.8397561e+00 4.4244727e+00 4.2085267e+00 5.1102370e+00 4.9464234e+00 4.4688286e+00 4.1733964e+00 4.3844239e+00 4.5752087e+00 4.1957914e+00 3.8934542e-01 3.7598397e-01 1.5422108e-01 3.4583729e-01 3.7598397e-01 4.4651726e-01 3.8934542e-01 3.2816937e-01 8.2929029e-01 9.3610001e-01 4.3691963e-01 5.3022554e-01 5.3309112e-01 4.3691963e-01 7.5976039e-01 3.2586371e-01 4.2667565e-01 1.0733200e+00 7.4777660e-01 2.1845981e-01 5.0905001e-01 4.3456114e-01 5.2942799e-01 5.5492130e-01 4.6472023e-01 3.7427929e-01 3.2191540e+00 2.9033203e+00 3.3725057e+00 2.3744738e+00 3.0162346e+00 2.8254861e+00 3.0850817e+00 1.6867642e+00 3.0206125e+00 2.2489449e+00 1.9859316e+00 2.5612285e+00 2.4037412e+00 3.0483897e+00 1.9482601e+00 2.8711841e+00 2.8361445e+00 2.4280197e+00 2.9151666e+00 2.2428341e+00 3.1709561e+00 2.3770599e+00 3.2772927e+00 3.0392407e+00 2.7044574e+00 2.8456337e+00 3.2543813e+00 3.4363773e+00 2.8560815e+00 1.8517858e+00 2.1570512e+00 2.0577182e+00 2.2449618e+00 3.4472201e+00 2.8333788e+00 2.8654874e+00 3.1455372e+00 2.8102985e+00 2.4278629e+00 2.3504126e+00 2.7240842e+00 2.9511072e+00 2.3468577e+00 1.7140774e+00 2.5325623e+00 2.5220817e+00 2.5303132e+00 2.6704164e+00 1.4096199e+00 2.4355523e+00 4.4339908e+00 3.4713427e+00 4.3742064e+00 3.9639546e+00 4.2144772e+00 5.1104621e+00 2.8721481e+00 4.7555981e+00 4.1997133e+00 4.6264002e+00 3.5337158e+00 3.6986235e+00 3.9580816e+00 3.3951420e+00 3.5651172e+00 3.7578933e+00 3.8852294e+00 5.2312618e+00 5.4232729e+00 3.3678461e+00 4.1846468e+00 3.2909043e+00 5.2164277e+00 3.3005331e+00 4.1286019e+00 4.4583488e+00 3.1948184e+00 3.2785487e+00 4.0052019e+00 4.2627019e+00 4.5989546e+00 4.9978258e+00 4.0193656e+00 3.4602150e+00 3.9315374e+00 4.7096962e+00 4.0441539e+00 3.8751788e+00 3.1769821e+00 3.8841455e+00 4.0829381e+00 3.6559398e+00 3.4713427e+00 4.3535851e+00 4.1922661e+00 3.7063225e+00 3.4135466e+00 3.6262912e+00 3.8337160e+00 3.4586921e+00 4.5470518e-01 3.4378533e-01 4.9766035e-01 5.6631629e-01 3.2586371e-01 3.7255734e-01 6.5172743e-01 7.6625946e-01 9.7356960e-01 4.4651726e-01 7.0784540e-01 8.1273630e-01 4.4651726e-01 6.8757066e-01 4.4417983e-01 6.0670504e-01 1.1521791e+00 6.5172743e-01 4.5581864e-01 4.5470518e-01 5.6700421e-01 4.8036801e-01 5.1607523e-01 5.8851328e-01 5.0905001e-01 3.1972361e+00 2.8360340e+00 3.3243092e+00 2.2696891e+00 2.9531300e+00 2.6835197e+00 2.9981183e+00 1.5916843e+00 2.9546153e+00 2.1366783e+00 1.9099663e+00 2.4717637e+00 2.3219731e+00 2.9300203e+00 1.8705419e+00 2.8448793e+00 2.7044574e+00 2.2951567e+00 2.8430073e+00 2.1220080e+00 3.0654291e+00 2.3117864e+00 3.1749624e+00 2.9091307e+00 2.6433897e+00 2.8065044e+00 3.2000771e+00 3.3714688e+00 2.7511201e+00 1.7679293e+00 2.0426611e+00 1.9423536e+00 2.1457242e+00 3.3172489e+00 2.6940968e+00 2.7682296e+00 3.0935247e+00 2.7391698e+00 2.2996943e+00 2.2360451e+00 2.5729378e+00 2.8382774e+00 2.2413445e+00 1.6312555e+00 2.4031247e+00 2.3848740e+00 2.4037412e+00 2.5843380e+00 1.3803845e+00 2.3178393e+00 4.3373464e+00 3.3555449e+00 4.3029534e+00 3.8394246e+00 4.1166152e+00 5.0345534e+00 2.7564428e+00 4.6628709e+00 4.0929284e+00 4.5724955e+00 3.4657942e+00 3.6038441e+00 3.8912788e+00 3.2935105e+00 3.4985926e+00 3.6938082e+00 3.7771525e+00 5.1600819e+00 5.3477989e+00 3.2453592e+00 4.1245629e+00 3.1885527e+00 5.1389533e+00 3.2183480e+00 4.0411872e+00 4.3734530e+00 3.1116430e+00 3.1793624e+00 3.9065553e+00 4.1815080e+00 4.5288943e+00 4.9506208e+00 3.9281139e+00 3.3421539e+00 3.7789119e+00 4.6812838e+00 3.9623295e+00 3.7603559e+00 3.0782101e+00 3.8325036e+00 4.0241284e+00 3.6474081e+00 3.3555449e+00 4.2739171e+00 4.1339606e+00 3.6720159e+00 3.3349237e+00 3.5512382e+00 3.7511837e+00 3.3364095e+00 4.1312257e-01 5.0905001e-01 4.2538717e-01 3.2352160e-01 2.0656129e-01 5.0592043e-01 1.1017858e+00 1.2234738e+00 1.5422108e-01 4.1312257e-01 6.3924842e-01 1.5422108e-01 6.1968386e-01 4.0293660e-01 5.2942799e-01 7.7919451e-01 6.2482915e-01 5.6631629e-01 8.1385214e-01 2.5251796e-01 8.0032200e-01 4.2538717e-01 7.1462831e-01 3.2352160e-01 3.3601225e+00 3.0492285e+00 3.5130686e+00 2.4784234e+00 3.1574358e+00 2.9495683e+00 3.2305582e+00 1.7639552e+00 3.1543365e+00 2.3872777e+00 2.0066796e+00 2.7104259e+00 2.4866453e+00 3.1794134e+00 2.0999661e+00 3.0164205e+00 2.9733298e+00 2.5409084e+00 3.0313923e+00 2.3496997e+00 3.3211033e+00 2.5173832e+00 3.4035007e+00 3.1599783e+00 2.8424094e+00 2.9896107e+00 3.3894792e+00 3.5821206e+00 2.9960387e+00 1.9633030e+00 2.2546134e+00 2.1472921e+00 2.3729779e+00 3.5766918e+00 2.9692947e+00 3.0147040e+00 3.2887803e+00 2.9233984e+00 2.5628362e+00 2.4694536e+00 2.8371552e+00 3.0850817e+00 2.4681135e+00 1.7749726e+00 2.6585961e+00 2.6493446e+00 2.6623632e+00 2.8060305e+00 1.5124582e+00 2.5679553e+00 4.5912390e+00 3.6144502e+00 4.5212874e+00 4.0982070e+00 4.3637125e+00 5.2494547e+00 3.0086587e+00 4.8875515e+00 4.3294620e+00 4.7880147e+00 3.6912876e+00 3.8418637e+00 4.1117747e+00 3.5413188e+00 3.7389462e+00 3.9250135e+00 4.0233529e+00 5.3754694e+00 5.5627643e+00 3.4785161e+00 4.3436980e+00 3.4455964e+00 5.3512930e+00 3.4471566e+00 4.2776053e+00 4.5941143e+00 3.3449470e+00 3.4269051e+00 4.1524717e+00 4.3946634e+00 4.7366258e+00 5.1414650e+00 4.1713411e+00 3.5895201e+00 4.0467919e+00 4.8635964e+00 4.2075047e+00 4.0127353e+00 3.3273395e+00 4.0411872e+00 4.2480617e+00 3.8321687e+00 3.6144502e+00 4.5072985e+00 4.3598062e+00 3.8780834e+00 3.5586316e+00 3.7805671e+00 3.9971028e+00 3.6003219e+00 2.5651975e-01 2.8192292e-01 3.4378533e-01 3.4378533e-01 4.0147421e-01 7.1840099e-01 8.5690100e-01 3.7598397e-01 4.2538717e-01 5.3665999e-01 3.7598397e-01 6.6827038e-01 2.1269358e-01 3.0546431e-01 1.1320702e+00 6.2988288e-01 2.0656129e-01 4.4535192e-01 4.2667565e-01 4.1449626e-01 4.3691963e-01 3.8934542e-01 2.5251796e-01 3.3428183e+00 3.0232018e+00 3.4944845e+00 2.4950353e+00 3.1381402e+00 2.9363801e+00 3.2027420e+00 1.8084630e+00 3.1409294e+00 2.3642733e+00 2.1113036e+00 2.6784604e+00 2.5283251e+00 3.1625374e+00 2.0667297e+00 2.9950041e+00 2.9473422e+00 2.5410503e+00 3.0407257e+00 2.3596992e+00 3.2858223e+00 2.4986337e+00 3.3960124e+00 3.1519721e+00 2.8255193e+00 2.9686973e+00 3.3765772e+00 3.5575681e+00 2.9720515e+00 1.9732878e+00 2.2758449e+00 2.1765379e+00 2.3630256e+00 3.5606213e+00 2.9432282e+00 2.9813163e+00 3.2672636e+00 2.9349850e+00 2.5393641e+00 2.4678343e+00 2.8348218e+00 3.0655803e+00 2.4649412e+00 1.8381372e+00 2.6459992e+00 2.6324803e+00 2.6428278e+00 2.7886501e+00 1.5384446e+00 2.5500177e+00 4.5509522e+00 3.5863729e+00 4.4953238e+00 4.0776551e+00 4.3319640e+00 5.2308062e+00 2.9880146e+00 4.8736353e+00 4.3174903e+00 4.7496159e+00 3.6545040e+00 3.8173510e+00 4.0797075e+00 3.5129177e+00 3.6850739e+00 3.8789949e+00 4.0011311e+00 5.3516635e+00 5.5447338e+00 3.4854203e+00 4.3070102e+00 3.4066692e+00 5.3366535e+00 3.4209331e+00 4.2472818e+00 4.5769661e+00 3.3144678e+00 3.3951450e+00 4.1229838e+00 4.3815083e+00 4.7200939e+00 5.1200990e+00 4.1381062e+00 3.5750015e+00 4.0416512e+00 4.8355880e+00 4.1628805e+00 3.9898962e+00 3.2934163e+00 4.0073765e+00 4.2053647e+00 3.7839552e+00 3.5863729e+00 4.4735121e+00 4.3145846e+00 3.8316735e+00 3.5355330e+00 3.7466071e+00 3.9521135e+00 3.5718733e+00 1.2418578e-01 5.2942799e-01 4.9766035e-01 2.5251796e-01 6.0060595e-01 7.1462831e-01 4.4535192e-01 3.8776762e-01 3.2352160e-01 4.4535192e-01 8.5434758e-01 1.2418578e-01 2.5251796e-01 1.2643026e+00 8.1354181e-01 4.1449626e-01 4.5581864e-01 5.6769031e-01 3.0546431e-01 6.2024833e-01 2.0656129e-01 2.5251796e-01 3.3898078e+00 3.1105347e+00 3.5575681e+00 2.6249526e+00 3.2229246e+00 3.0488669e+00 3.3004172e+00 1.9465831e+00 3.2118493e+00 2.4993166e+00 2.2473120e+00 2.7928582e+00 2.6296047e+00 3.2639046e+00 2.1933937e+00 3.0559188e+00 3.0673749e+00 2.6440180e+00 3.1486786e+00 2.4776707e+00 3.4056271e+00 2.5954147e+00 3.4958702e+00 3.2483608e+00 2.9041534e+00 3.0378828e+00 3.4425295e+00 3.6411503e+00 3.0811775e+00 2.0851901e+00 2.3997585e+00 2.2980893e+00 2.4741664e+00 3.6714798e+00 3.0661139e+00 3.0923102e+00 3.3390355e+00 3.0288896e+00 2.6566259e+00 2.5949561e+00 2.9511072e+00 3.1662394e+00 2.5768305e+00 1.9764051e+00 2.7650187e+00 2.7420671e+00 2.7570191e+00 2.8802219e+00 1.6913411e+00 2.6662783e+00 4.6723657e+00 3.7109297e+00 4.5802461e+00 4.1829734e+00 4.4414351e+00 5.3024507e+00 3.1246867e+00 4.9479218e+00 4.4123750e+00 4.8421359e+00 3.7582429e+00 3.9238367e+00 4.1750713e+00 3.6455011e+00 3.8262353e+00 3.9966275e+00 4.0997246e+00 5.4219259e+00 5.6210533e+00 3.5985833e+00 4.4045665e+00 3.5402439e+00 5.4041845e+00 3.5288526e+00 4.3467079e+00 4.6509698e+00 3.4261305e+00 3.5087649e+00 4.2340479e+00 4.4487072e+00 4.7898591e+00 5.1726632e+00 4.2521321e+00 3.6728562e+00 4.1446028e+00 4.9002452e+00 4.2845042e+00 4.0915924e+00 3.4110551e+00 4.0971854e+00 4.3142588e+00 3.8789274e+00 3.7109297e+00 4.5753785e+00 4.4261431e+00 3.9377466e+00 3.6482464e+00 3.8509694e+00 4.0749843e+00 3.6894983e+00 5.1607523e-01 4.5470518e-01 2.5251796e-01 7.0086313e-01 8.1067767e-01 3.7598397e-01 2.8192292e-01 3.0546431e-01 3.7598397e-01 8.2654509e-01 1.2418578e-01 2.1845981e-01 1.1754055e+00 8.0326782e-01 4.2667565e-01 5.7324170e-01 4.9766035e-01 4.1449626e-01 6.0551856e-01 3.0546431e-01 2.0656129e-01 3.4780839e+00 3.2028668e+00 3.6478832e+00 2.7001131e+00 3.3123070e+00 3.1424188e+00 3.3940268e+00 2.0081113e+00 3.3027430e+00 2.5846998e+00 2.2899742e+00 2.8843735e+00 2.7014135e+00 3.3579678e+00 2.2804674e+00 3.1447330e+00 3.1614572e+00 2.7347909e+00 3.2266676e+00 2.5600441e+00 3.4989115e+00 2.6835197e+00 3.5848035e+00 3.3425343e+00 2.9944056e+00 3.1272113e+00 3.5315985e+00 3.7321938e+00 3.1736230e+00 2.1642821e+00 2.4763086e+00 2.3729779e+00 2.5613679e+00 3.7645411e+00 3.1602773e+00 3.1861493e+00 3.4298218e+00 3.1090174e+00 2.7503801e+00 2.6773131e+00 3.0414782e+00 3.2606191e+00 2.6626548e+00 2.0308266e+00 2.8549070e+00 2.8371552e+00 2.8500790e+00 2.9720515e+00 1.7439430e+00 2.7570191e+00 4.7646254e+00 3.8019108e+00 4.6714768e+00 4.2777154e+00 4.5341669e+00 5.3940437e+00 3.2096520e+00 5.0408889e+00 4.5037829e+00 4.9318274e+00 3.8493108e+00 4.0147814e+00 4.2656755e+00 3.7323432e+00 3.9122146e+00 4.0862794e+00 4.1939019e+00 5.5136120e+00 5.7113100e+00 3.6836404e+00 4.4947801e+00 3.6298047e+00 5.4953655e+00 3.6181784e+00 4.4396174e+00 4.7440323e+00 3.5161021e+00 3.6013159e+00 4.3259034e+00 4.5411167e+00 4.8804165e+00 5.2620015e+00 4.3431589e+00 3.7666196e+00 4.2394195e+00 4.9871255e+00 4.3755778e+00 4.1864815e+00 3.5032323e+00 4.1868319e+00 4.4036244e+00 3.9638383e+00 3.8019108e+00 4.6672392e+00 4.5155386e+00 4.0245873e+00 3.7349501e+00 3.9420153e+00 4.1660972e+00 3.7835217e+00 1.2418578e-01 7.0826681e-01 9.4125538e-01 1.1340084e+00 2.1845981e-01 4.4417983e-01 8.2105460e-01 2.1845981e-01 3.8776762e-01 4.1449626e-01 4.2418962e-01 9.1075311e-01 3.7255734e-01 4.8036801e-01 6.6827038e-01 2.5651975e-01 6.4704320e-01 2.0656129e-01 6.8961791e-01 3.2586371e-01 3.4686627e+00 3.1154621e+00 3.6024772e+00 2.5108475e+00 3.2290313e+00 2.9704704e+00 3.2809332e+00 1.7903952e+00 3.2349033e+00 2.3960250e+00 2.0600195e+00 2.7476411e+00 2.5610667e+00 3.2181149e+00 2.1323638e+00 3.1154281e+00 2.9881598e+00 2.5786309e+00 3.0947136e+00 2.3839455e+00 3.3448197e+00 2.5821869e+00 3.4532666e+00 3.1998457e+00 2.9200439e+00 3.0794219e+00 3.4771054e+00 3.6510733e+00 3.0328831e+00 2.0197525e+00 2.2893157e+00 2.1858172e+00 2.4166535e+00 3.6030381e+00 2.9770247e+00 3.0492285e+00 3.3713329e+00 2.9974031e+00 2.5833316e+00 2.4944673e+00 2.8535197e+00 3.1260028e+00 2.5104998e+00 1.8109223e+00 2.6804452e+00 2.6742360e+00 2.6875169e+00 2.8656044e+00 1.5447938e+00 2.5961850e+00 4.6147552e+00 3.6320149e+00 4.5850999e+00 4.1288948e+00 4.3989089e+00 5.3208661e+00 3.0146208e+00 4.9525575e+00 4.3778283e+00 4.8485483e+00 3.7416375e+00 3.8836706e+00 4.1692394e+00 3.5584719e+00 3.7545764e+00 3.9635178e+00 4.0653249e+00 5.4465238e+00 5.6318041e+00 3.5148434e+00 4.4004449e+00 3.4571917e+00 5.4256316e+00 3.4931266e+00 4.3243671e+00 4.6617210e+00 3.3863989e+00 3.4595139e+00 4.1872611e+00 4.4691596e+00 4.8126955e+00 5.2323771e+00 4.2057897e+00 3.6309135e+00 4.0714444e+00 4.9544547e+00 4.2355968e+00 4.0495222e+00 3.3563815e+00 4.1075398e+00 4.2957899e+00 3.9065020e+00 3.6320149e+00 4.5543025e+00 4.4046818e+00 3.9362563e+00 3.6038441e+00 3.8285790e+00 4.0238937e+00 3.6204282e+00 6.2538346e-01 1.0167353e+00 1.1763980e+00 1.4096146e-01 4.1449626e-01 7.4740267e-01 1.4096146e-01 4.4535192e-01 3.7427929e-01 4.5581864e-01 8.2135873e-01 4.4535192e-01 5.0503591e-01 7.3145860e-01 2.1269358e-01 7.1421512e-01 2.5251796e-01 6.8961791e-01 2.8192292e-01 3.4295980e+00 3.0905489e+00 3.5700123e+00 2.4944673e+00 3.2020292e+00 2.9613737e+00 3.2617087e+00 1.7750284e+00 3.2049793e+00 2.3909366e+00 2.0308266e+00 2.7326472e+00 2.5286673e+00 3.2028668e+00 2.1181082e+00 3.0792693e+00 2.9816970e+00 2.5624932e+00 3.0681391e+00 2.3677174e+00 3.3352475e+00 2.5566444e+00 3.4334259e+00 3.1839972e+00 2.8907702e+00 3.0462904e+00 3.4448498e+00 3.6256155e+00 3.0181476e+00 1.9946242e+00 2.2719663e+00 2.1665831e+00 2.3980102e+00 3.5922217e+00 2.9733478e+00 3.0355056e+00 3.3410264e+00 2.9673667e+00 2.5744164e+00 2.4820750e+00 2.8455141e+00 3.1100045e+00 2.4920874e+00 1.7903952e+00 2.6704164e+00 2.6637326e+00 2.6767607e+00 2.8425716e+00 1.5257596e+00 2.5839288e+00 4.6057175e+00 3.6244539e+00 4.5619285e+00 4.1170543e+00 4.3856360e+00 5.2953657e+00 3.0110441e+00 4.9290739e+00 4.3593510e+00 4.8266994e+00 3.7227460e+00 3.8675033e+00 4.1480696e+00 3.5505922e+00 3.7479505e+00 3.9489183e+00 4.0495222e+00 5.4213759e+00 5.6069704e+00 3.4988409e+00 4.3796539e+00 3.4519560e+00 5.3990720e+00 3.4751739e+00 4.3070102e+00 4.6372963e+00 3.3701472e+00 3.4467337e+00 4.1738909e+00 4.4422690e+00 4.7852959e+00 5.2004339e+00 4.1925494e+00 3.6148707e+00 4.0613681e+00 4.9222119e+00 4.2248103e+00 4.0355816e+00 3.3448336e+00 4.0832977e+00 4.2781022e+00 3.8793995e+00 3.6244539e+00 4.5369609e+00 4.3880177e+00 3.9147164e+00 3.5857963e+00 3.8105301e+00 4.0134966e+00 3.6122845e+00 7.1799256e-01 8.0358695e-01 5.5419992e-01 4.6472023e-01 2.5651975e-01 5.5419992e-01 1.0198386e+00 3.2352160e-01 4.1586001e-01 1.2565757e+00 1.0054037e+00 4.1586001e-01 5.2574978e-01 6.4806901e-01 4.5581864e-01 8.0619006e-01 3.2586371e-01 4.1586001e-01 3.3274681e+00 3.0643176e+00 3.5031390e+00 2.5831315e+00 3.1734856e+00 3.0256804e+00 3.2593968e+00 1.9049236e+00 3.1662394e+00 2.4587503e+00 2.1948123e+00 2.7522526e+00 2.5874301e+00 3.2341367e+00 2.1493214e+00 2.9966141e+00 3.0389019e+00 2.6214821e+00 3.0978571e+00 2.4463233e+00 3.3673968e+00 2.5500177e+00 3.4578354e+00 3.2240818e+00 2.8578051e+00 2.9828212e+00 3.3905763e+00 3.5904118e+00 3.0455175e+00 2.0467937e+00 2.3640301e+00 2.2638728e+00 2.4385827e+00 3.6424937e+00 3.0387448e+00 3.0543001e+00 3.2867025e+00 2.9810914e+00 2.6291524e+00 2.5579619e+00 2.9291061e+00 3.1351569e+00 2.5421955e+00 1.9274228e+00 2.7359835e+00 2.7196686e+00 2.7293044e+00 2.8418790e+00 1.6234861e+00 2.6349941e+00 4.6268918e+00 3.6735954e+00 4.5276539e+00 4.1519514e+00 4.3981255e+00 5.2510005e+00 3.0847983e+00 4.9049143e+00 4.3738893e+00 4.7804645e+00 3.7059360e+00 3.8806135e+00 4.1210200e+00 3.6007179e+00 3.7674167e+00 3.9401211e+00 4.0632074e+00 5.3683319e+00 5.5675337e+00 3.5650560e+00 4.3467079e+00 3.4964385e+00 5.3534247e+00 3.4817971e+00 4.3007421e+00 4.6057628e+00 3.3796707e+00 3.4684743e+00 4.1910954e+00 4.4033511e+00 4.7374076e+00 5.1107389e+00 4.2056854e+00 3.6417452e+00 4.1251907e+00 4.8290847e+00 4.2339606e+00 4.0576409e+00 3.3702841e+00 4.0376849e+00 4.2550416e+00 3.8015574e+00 3.6735954e+00 4.5249462e+00 4.3662620e+00 3.8699102e+00 3.5979726e+00 3.8007870e+00 4.0252278e+00 3.6568095e+00 3.0811765e-01 1.0065841e+00 9.1075311e-01 6.2538346e-01 1.0065841e+00 1.2125198e+00 7.0086313e-01 6.1623531e-01 1.8279039e+00 1.0613462e+00 6.9369532e-01 4.8135521e-01 1.1149070e+00 3.0811765e-01 9.7098574e-01 4.0293660e-01 8.0358695e-01 3.4154940e+00 3.1439160e+00 3.5872997e+00 2.8054691e+00 3.2839149e+00 3.1127876e+00 3.3274872e+00 2.2159139e+00 3.2598167e+00 2.6167778e+00 2.5758644e+00 2.8523754e+00 2.8256291e+00 3.3122081e+00 2.3003824e+00 3.0947136e+00 3.1160876e+00 2.7384939e+00 3.2940867e+00 2.6281170e+00 3.4401593e+00 2.6851662e+00 3.5758474e+00 3.3024121e+00 2.9636544e+00 3.0850893e+00 3.4935692e+00 3.6787177e+00 3.1382691e+00 2.2643860e+00 2.5838312e+00 2.4957147e+00 2.5876691e+00 3.7272092e+00 3.1148696e+00 3.1192689e+00 3.3731473e+00 3.1641238e+00 2.7164367e+00 2.7371177e+00 3.0433470e+00 3.2094276e+00 2.6989752e+00 2.2729593e+00 2.8576425e+00 2.7954161e+00 2.8234677e+00 2.9407805e+00 1.9980146e+00 2.7511201e+00 4.6993349e+00 3.7716613e+00 4.6090409e+00 4.2170163e+00 4.4740971e+00 5.3233695e+00 3.2312218e+00 4.9715358e+00 4.4649192e+00 4.8634712e+00 3.7907269e+00 3.9777091e+00 4.2102908e+00 3.7293867e+00 3.8887827e+00 4.0321207e+00 4.1304736e+00 5.4336694e+00 5.6535520e+00 3.7088191e+00 4.4333012e+00 3.6017014e+00 5.4287623e+00 3.5939935e+00 4.3696884e+00 4.6684884e+00 3.4865359e+00 3.5518450e+00 4.2777882e+00 4.4716883e+00 4.8204359e+00 5.1830573e+00 4.2975164e+00 3.7189281e+00 4.1915116e+00 4.9282164e+00 4.3126073e+00 4.1182993e+00 3.4568393e+00 4.1297239e+00 4.3496867e+00 3.9207320e+00 3.7716613e+00 4.6018276e+00 4.4564872e+00 3.9827168e+00 3.7295388e+00 3.8905364e+00 4.1037903e+00 3.7281480e+00 1.1474460e+00 1.0344911e+00 7.0043186e-01 1.1474460e+00 1.4311891e+00 8.2654509e-01 7.6787403e-01 1.9730918e+00 1.3073038e+00 7.9878917e-01 6.2407309e-01 1.2632199e+00 5.0503591e-01 1.1833480e+00 5.0905001e-01 9.4080461e-01 3.4392518e+00 3.2008338e+00 3.6261197e+00 2.9076510e+00 3.3439089e+00 3.2073032e+00 3.3907558e+00 2.3329978e+00 3.3168472e+00 2.7081478e+00 2.6929215e+00 2.9283888e+00 2.9264154e+00 3.3942456e+00 2.3848677e+00 3.1312883e+00 3.2027420e+00 2.8385969e+00 3.3781905e+00 2.7324845e+00 3.5133135e+00 2.7602023e+00 3.6563535e+00 3.3905763e+00 3.0256209e+00 3.1312883e+00 3.5423793e+00 3.7299679e+00 3.2179031e+00 2.3661539e+00 2.6906231e+00 2.6054995e+00 2.6801752e+00 3.8138933e+00 3.2027420e+00 3.1888037e+00 3.4187265e+00 3.2459231e+00 2.8060305e+00 2.8359967e+00 3.1453916e+00 3.2886661e+00 2.7943622e+00 2.3874574e+00 2.9533314e+00 2.8877105e+00 2.9141136e+00 3.0147886e+00 2.0992326e+00 2.8425716e+00 4.7630756e+00 3.8535531e+00 4.6550014e+00 4.2947468e+00 4.5389196e+00 5.3632740e+00 3.3234239e+00 5.0232338e+00 4.5363757e+00 4.8985515e+00 3.8441304e+00 4.0476997e+00 4.2599433e+00 3.8106152e+00 3.9516603e+00 4.0850903e+00 4.1995425e+00 5.4671477e+00 5.6957748e+00 3.8037734e+00 4.4771379e+00 3.6783395e+00 5.4690202e+00 3.6632177e+00 4.4259826e+00 4.7160634e+00 3.5560806e+00 3.6239123e+00 4.3464973e+00 4.5187406e+00 4.8619146e+00 5.2001625e+00 4.3635746e+00 3.7979761e+00 4.2843668e+00 4.9451734e+00 4.3709882e+00 4.1899199e+00 3.5299194e+00 4.1707976e+00 4.3972344e+00 3.9457376e+00 3.8535531e+00 4.6546556e+00 4.5024055e+00 4.0227020e+00 3.8004969e+00 3.9484773e+00 4.1635108e+00 3.8079860e+00 3.0811765e-01 6.5223271e-01 0.0000000e+00 5.0991930e-01 3.2586371e-01 4.2667565e-01 8.3172002e-01 5.0991930e-01 5.6769031e-01 7.5082357e-01 2.1845981e-01 7.0479928e-01 3.0811765e-01 6.4755655e-01 2.1845981e-01 3.4865562e+00 3.1726595e+00 3.6377960e+00 2.5987470e+00 3.2814045e+00 3.0627375e+00 3.3515846e+00 1.8841865e+00 3.2769379e+00 2.5038079e+00 2.1311468e+00 2.8311678e+00 2.6104387e+00 3.2962520e+00 2.2214438e+00 3.1433122e+00 3.0878634e+00 2.6552472e+00 3.1570103e+00 2.4668912e+00 3.4394878e+00 2.6411293e+00 3.5233648e+00 3.2747247e+00 2.9659871e+00 3.1154783e+00 3.5134741e+00 3.7059620e+00 3.1148696e+00 2.0851901e+00 2.3731428e+00 2.2655571e+00 2.4927109e+00 3.6920087e+00 3.0823446e+00 3.1337459e+00 3.4135200e+00 3.0481703e+00 2.6780487e+00 2.5874301e+00 2.9489507e+00 3.2027420e+00 2.5873149e+00 1.8973383e+00 2.7738355e+00 2.7632614e+00 2.7778954e+00 2.9269923e+00 1.6390769e+00 2.6848587e+00 4.7106706e+00 3.7313856e+00 4.6446321e+00 4.2142736e+00 4.4836580e+00 5.3716885e+00 3.1250284e+00 5.0074019e+00 4.4485220e+00 4.9128219e+00 3.8150636e+00 3.9624529e+00 4.2358121e+00 3.6602286e+00 3.8605980e+00 4.0488387e+00 4.1418643e+00 5.4970002e+00 5.6855224e+00 3.5964347e+00 4.4685630e+00 3.5634461e+00 5.4730406e+00 3.5693950e+00 4.3989089e+00 4.7150659e+00 3.4668130e+00 3.5464993e+00 4.2723380e+00 4.5155386e+00 4.8594290e+00 5.2647079e+00 4.2921213e+00 3.7064459e+00 4.1581964e+00 4.9913682e+00 4.3286007e+00 4.1303097e+00 3.4468286e+00 4.1669742e+00 4.3729308e+00 3.9624170e+00 3.7313856e+00 4.6297577e+00 4.4844827e+00 4.0056359e+00 3.6817961e+00 3.9035218e+00 4.1179678e+00 3.7164366e+00 5.2942799e-01 3.0811765e-01 6.0611244e-01 3.2586371e-01 3.0546431e-01 9.4125538e-01 6.0060595e-01 5.2574978e-01 8.1558458e-01 2.8507955e-01 6.4755655e-01 4.1312257e-01 5.5419992e-01 2.0656129e-01 3.7045940e+00 3.4142500e+00 3.8690719e+00 2.8688189e+00 3.5226542e+00 3.3385842e+00 3.6010215e+00 2.1580776e+00 3.5196916e+00 2.7652601e+00 2.4083873e+00 3.0820950e+00 2.8783309e+00 3.5617386e+00 2.4693940e+00 3.3658240e+00 3.3558214e+00 2.9322808e+00 3.4112518e+00 2.7424260e+00 3.6945405e+00 2.8867565e+00 3.7848217e+00 3.5471494e+00 3.2076743e+00 3.3449470e+00 3.7498842e+00 3.9450818e+00 3.3735875e+00 2.3490515e+00 2.6490839e+00 2.5444216e+00 2.7549369e+00 3.9621873e+00 3.3527310e+00 3.3865418e+00 3.6475099e+00 3.3024121e+00 2.9459653e+00 2.8566322e+00 3.2311957e+00 3.4653703e+00 2.8535197e+00 2.1708533e+00 3.0455175e+00 3.0364473e+00 3.0465086e+00 3.1799184e+00 1.8842354e+00 2.9509414e+00 4.9594922e+00 3.9924292e+00 4.8844442e+00 4.4804205e+00 4.7354764e+00 5.6130421e+00 3.3873806e+00 5.2583931e+00 4.7090394e+00 5.1413739e+00 4.0532097e+00 4.2156327e+00 4.4735121e+00 3.9153764e+00 4.0928954e+00 4.2826383e+00 4.4004808e+00 5.7341534e+00 5.9271927e+00 3.8700742e+00 4.7016733e+00 3.8156003e+00 5.7155096e+00 3.8175051e+00 4.6462020e+00 4.9620553e+00 3.7143727e+00 3.8000004e+00 4.5254820e+00 4.7613782e+00 5.0991856e+00 5.4888822e+00 4.5411167e+00 3.9718373e+00 4.4397363e+00 5.2074442e+00 4.5701358e+00 4.3916992e+00 3.6996802e+00 4.3970196e+00 4.6045465e+00 4.1691696e+00 3.9924292e+00 4.8725396e+00 4.7150659e+00 4.2256400e+00 3.9292780e+00 4.1454529e+00 4.3599285e+00 3.9798670e+00 6.5223271e-01 1.1268457e+00 4.1449626e-01 5.0090417e-01 1.3784393e+00 1.1053488e+00 5.8851328e-01 6.6827038e-01 7.6787403e-01 4.8036801e-01 9.0852141e-01 2.8192292e-01 5.0905001e-01 3.5117473e+00 3.2719724e+00 3.6961290e+00 2.8060101e+00 3.3799936e+00 3.2401159e+00 3.4709594e+00 2.1293854e+00 3.3643376e+00 2.6848587e+00 2.4115946e+00 2.9723937e+00 2.7984009e+00 3.4455106e+00 2.3749088e+00 3.1913397e+00 3.2574960e+00 2.8320532e+00 3.3150921e+00 2.6637326e+00 3.5883493e+00 2.7640668e+00 3.6695251e+00 3.4317240e+00 3.0617355e+00 3.1820648e+00 3.5855784e+00 3.7950050e+00 3.2620096e+00 2.2645766e+00 2.5831070e+00 2.4810276e+00 2.6563403e+00 3.8575846e+00 3.2574960e+00 3.2718360e+00 3.4856613e+00 3.1913594e+00 2.8466990e+00 2.7801709e+00 3.1437608e+00 3.3466419e+00 2.7595194e+00 2.1498672e+00 2.9543365e+00 2.9330570e+00 2.9459653e+00 3.0511838e+00 1.8555964e+00 2.8534301e+00 4.8490742e+00 3.8962785e+00 4.7308926e+00 4.3643842e+00 4.6144072e+00 5.4442665e+00 3.3129652e+00 5.0997539e+00 4.5816539e+00 4.9883395e+00 3.9213002e+00 4.0961125e+00 4.3313403e+00 3.8279951e+00 4.0003537e+00 4.1625074e+00 4.2731561e+00 5.5604940e+00 5.7635900e+00 3.7812981e+00 4.5580745e+00 3.7236571e+00 5.5437155e+00 3.6992145e+00 4.5120086e+00 4.8012240e+00 3.5989213e+00 3.6872980e+00 4.4084575e+00 4.5949523e+00 4.9306472e+00 5.2918657e+00 4.4250143e+00 3.8510143e+00 4.3338058e+00 5.0198276e+00 4.4571663e+00 4.2687771e+00 3.5910375e+00 4.2454653e+00 4.4729144e+00 4.0139676e+00 3.8962785e+00 4.7377574e+00 4.5853438e+00 4.0877338e+00 3.8179781e+00 4.0161568e+00 4.2490407e+00 3.8755800e+00 5.0991930e-01 3.2586371e-01 4.2667565e-01 8.3172002e-01 5.0991930e-01 5.6769031e-01 7.5082357e-01 2.1845981e-01 7.0479928e-01 3.0811765e-01 6.4755655e-01 2.1845981e-01 3.4865562e+00 3.1726595e+00 3.6377960e+00 2.5987470e+00 3.2814045e+00 3.0627375e+00 3.3515846e+00 1.8841865e+00 3.2769379e+00 2.5038079e+00 2.1311468e+00 2.8311678e+00 2.6104387e+00 3.2962520e+00 2.2214438e+00 3.1433122e+00 3.0878634e+00 2.6552472e+00 3.1570103e+00 2.4668912e+00 3.4394878e+00 2.6411293e+00 3.5233648e+00 3.2747247e+00 2.9659871e+00 3.1154783e+00 3.5134741e+00 3.7059620e+00 3.1148696e+00 2.0851901e+00 2.3731428e+00 2.2655571e+00 2.4927109e+00 3.6920087e+00 3.0823446e+00 3.1337459e+00 3.4135200e+00 3.0481703e+00 2.6780487e+00 2.5874301e+00 2.9489507e+00 3.2027420e+00 2.5873149e+00 1.8973383e+00 2.7738355e+00 2.7632614e+00 2.7778954e+00 2.9269923e+00 1.6390769e+00 2.6848587e+00 4.7106706e+00 3.7313856e+00 4.6446321e+00 4.2142736e+00 4.4836580e+00 5.3716885e+00 3.1250284e+00 5.0074019e+00 4.4485220e+00 4.9128219e+00 3.8150636e+00 3.9624529e+00 4.2358121e+00 3.6602286e+00 3.8605980e+00 4.0488387e+00 4.1418643e+00 5.4970002e+00 5.6855224e+00 3.5964347e+00 4.4685630e+00 3.5634461e+00 5.4730406e+00 3.5693950e+00 4.3989089e+00 4.7150659e+00 3.4668130e+00 3.5464993e+00 4.2723380e+00 4.5155386e+00 4.8594290e+00 5.2647079e+00 4.2921213e+00 3.7064459e+00 4.1581964e+00 4.9913682e+00 4.3286007e+00 4.1303097e+00 3.4468286e+00 4.1669742e+00 4.3729308e+00 3.9624170e+00 3.7313856e+00 4.6297577e+00 4.4844827e+00 4.0056359e+00 3.6817961e+00 3.9035218e+00 4.1179678e+00 3.7164366e+00 7.3813096e-01 6.8961791e-01 7.0086313e-01 2.0000000e-01 7.3805807e-01 1.0030700e+00 4.0293660e-01 9.4352681e-01 2.5251796e-01 1.0120221e+00 6.2024833e-01 3.8265307e+00 3.4552560e+00 3.9537404e+00 2.8022534e+00 3.5701225e+00 3.2863508e+00 3.6126198e+00 2.0524386e+00 3.5845127e+00 2.6848587e+00 2.2888897e+00 3.0684384e+00 2.8791325e+00 3.5464993e+00 2.4469125e+00 3.4686755e+00 3.2961206e+00 2.9072057e+00 3.4107902e+00 2.6959009e+00 3.6545040e+00 2.9195876e+00 3.7806187e+00 3.5312474e+00 3.2669151e+00 3.4295849e+00 3.8277021e+00 3.9909030e+00 3.3562690e+00 2.3465937e+00 2.5906376e+00 2.4892531e+00 2.7423171e+00 3.9193693e+00 3.2780620e+00 3.3708389e+00 3.7190229e+00 3.3268984e+00 2.8983020e+00 2.7954161e+00 3.1611584e+00 3.4557351e+00 2.8328337e+00 2.0658700e+00 2.9919517e+00 2.9960210e+00 3.0059712e+00 3.2048547e+00 1.8038968e+00 2.9141136e+00 4.9189452e+00 3.9342203e+00 4.9208723e+00 4.4494735e+00 4.7160542e+00 5.6635213e+00 3.2909043e+00 5.2946188e+00 4.7062325e+00 5.1779277e+00 4.0657707e+00 4.2053647e+00 4.4986279e+00 3.8509694e+00 4.0384339e+00 4.2739171e+00 4.3927415e+00 5.7909894e+00 5.9706827e+00 3.8237409e+00 4.7267599e+00 3.7490739e+00 5.7704875e+00 3.8154018e+00 4.6503268e+00 5.0044958e+00 3.7060524e+00 3.7762575e+00 4.5037232e+00 4.8164539e+00 5.1574084e+00 5.5860081e+00 4.5195237e+00 3.9601358e+00 4.3901702e+00 5.2992055e+00 4.5412859e+00 4.3739561e+00 3.6694918e+00 4.4403343e+00 4.6130415e+00 4.2323959e+00 3.9342203e+00 4.8773871e+00 4.7190595e+00 4.2559866e+00 3.9201797e+00 4.1523445e+00 4.3289315e+00 3.9302319e+00 2.1845981e-01 1.1486378e+00 7.0784540e-01 4.0438741e-01 5.0503591e-01 4.4651726e-01 4.0147421e-01 5.0905001e-01 3.2352160e-01 1.4096146e-01 3.4156574e+00 3.1235447e+00 3.5778307e+00 2.6097685e+00 3.2346686e+00 3.0478400e+00 3.3101102e+00 1.9193093e+00 3.2270948e+00 2.4922287e+00 2.2081369e+00 2.7965957e+00 2.6184141e+00 3.2685282e+00 2.1916898e+00 3.0773654e+00 3.0673749e+00 2.6423274e+00 3.1444983e+00 2.4678343e+00 3.4088888e+00 2.6016025e+00 3.4988409e+00 3.2521427e+00 2.9171710e+00 3.0559188e+00 3.4597125e+00 3.6553612e+00 3.0847983e+00 2.0765921e+00 2.3848740e+00 2.2817008e+00 2.4722095e+00 3.6724425e+00 3.0650478e+00 3.0981264e+00 3.3567173e+00 3.0288896e+00 2.6566259e+00 2.5851693e+00 2.9455446e+00 3.1719381e+00 2.5729378e+00 1.9450955e+00 2.7611864e+00 2.7431084e+00 2.7570191e+00 2.8884401e+00 1.6625998e+00 2.6648989e+00 4.6768871e+00 3.7101281e+00 4.5948688e+00 4.1876541e+00 4.4480565e+00 5.3202405e+00 3.1169790e+00 4.9630576e+00 4.4189653e+00 4.8572313e+00 3.7684708e+00 3.9292780e+00 4.1872611e+00 3.6418662e+00 3.8262353e+00 4.0041417e+00 4.1076175e+00 5.4411191e+00 5.6370076e+00 3.5929878e+00 4.4174698e+00 3.5389106e+00 5.4223305e+00 3.5340177e+00 4.3569684e+00 4.6672392e+00 3.4309563e+00 3.5133135e+00 4.2392500e+00 4.4661721e+00 4.8075242e+00 5.1977119e+00 4.2572858e+00 3.6784045e+00 4.1454521e+00 4.9233665e+00 4.2900309e+00 4.0984960e+00 3.4145942e+00 4.1120703e+00 4.3243538e+00 3.8957047e+00 3.7101281e+00 4.5857922e+00 4.4360042e+00 3.9497198e+00 3.6509512e+00 3.8600234e+00 4.0800357e+00 3.6915258e+00 1.2223099e+00 6.2024833e-01 3.7255734e-01 6.2081167e-01 5.0905001e-01 3.7598397e-01 4.4651726e-01 3.4583729e-01 2.1269358e-01 3.6091470e+00 3.3105724e+00 3.7708932e+00 2.7979838e+00 3.4251430e+00 3.2391031e+00 3.4951642e+00 2.1075335e+00 3.4235403e+00 2.6687055e+00 2.3989464e+00 2.9762945e+00 2.8216056e+00 3.4603628e+00 2.3673218e+00 3.2680041e+00 3.2498302e+00 2.8414525e+00 3.3361912e+00 2.6626548e+00 3.5849794e+00 2.7906520e+00 3.6926714e+00 3.4497714e+00 3.1105675e+00 3.2468925e+00 3.6557661e+00 3.8432801e+00 3.2705166e+00 2.2719663e+00 2.5786309e+00 2.4784234e+00 2.6629602e+00 3.8619321e+00 3.2465133e+00 3.2780765e+00 3.5475145e+00 3.2266676e+00 2.8416143e+00 2.7719788e+00 3.1392934e+00 3.3624692e+00 2.7656089e+00 2.1335398e+00 2.9496515e+00 2.9338590e+00 2.9447165e+00 3.0808399e+00 1.8330979e+00 2.8520904e+00 4.8482992e+00 3.8884996e+00 4.7814945e+00 4.3763964e+00 4.6280797e+00 5.5131379e+00 3.2922206e+00 5.1594703e+00 4.6125121e+00 5.0342965e+00 3.9453171e+00 4.1137002e+00 4.3683295e+00 3.8151836e+00 3.9816719e+00 4.1715493e+00 4.2963195e+00 5.6322635e+00 5.8289682e+00 3.7874513e+00 4.5945190e+00 3.7079546e+00 5.6180203e+00 3.7164611e+00 4.5394421e+00 4.8614468e+00 3.6107037e+00 3.6929889e+00 4.4201622e+00 4.6634723e+00 5.0015266e+00 5.3906681e+00 4.4348302e+00 3.8718490e+00 4.3427947e+00 5.1078309e+00 4.4583488e+00 4.2864208e+00 3.5920050e+00 4.2919639e+00 4.4953238e+00 4.0619633e+00 3.8884996e+00 4.7650352e+00 4.6046026e+00 4.1173867e+00 3.8320624e+00 4.0389546e+00 4.2480032e+00 3.8727359e+00 9.0049692e-01 1.2307737e+00 1.5483011e+00 7.1462831e-01 1.5272277e+00 9.0074515e-01 1.4700179e+00 1.0331736e+00 3.7896333e+00 3.4304205e+00 3.9179666e+00 2.7683644e+00 3.5321772e+00 3.2685282e+00 3.5962433e+00 2.0250421e+00 3.5485736e+00 2.6642702e+00 2.2244833e+00 3.0435803e+00 2.8325748e+00 3.5230967e+00 2.4205131e+00 3.4307077e+00 3.2815626e+00 2.8843735e+00 3.3658240e+00 2.6687055e+00 3.6389628e+00 2.8830783e+00 3.7490739e+00 3.5087649e+00 3.2305801e+00 3.3908902e+00 3.7876239e+00 3.9561876e+00 3.3306114e+00 2.3112968e+00 2.5604155e+00 2.4585271e+00 2.7122813e+00 3.8971376e+00 3.2667813e+00 3.3674720e+00 3.6849783e+00 3.2837729e+00 2.8840079e+00 2.7685572e+00 3.1442943e+00 3.4335342e+00 2.8028287e+00 2.0286682e+00 2.9704704e+00 2.9822537e+00 2.9868163e+00 3.1741954e+00 1.7644184e+00 2.8908296e+00 4.8985114e+00 3.9102500e+00 4.8863496e+00 4.4272873e+00 4.6888407e+00 5.6294495e+00 3.2706756e+00 5.2636641e+00 4.6765452e+00 5.1547576e+00 4.0378383e+00 4.1743230e+00 4.4638518e+00 3.8230269e+00 4.0056625e+00 4.2450961e+00 4.3676124e+00 5.7745449e+00 5.9344852e+00 3.7932062e+00 4.6942925e+00 3.7245053e+00 5.7354045e+00 3.7814598e+00 4.6271849e+00 4.9763401e+00 3.6736483e+00 3.7511498e+00 4.4747039e+00 4.7841150e+00 5.1205765e+00 5.5660104e+00 4.4889834e+00 3.9348528e+00 4.3728356e+00 5.2548517e+00 4.5226167e+00 4.3526639e+00 3.6449787e+00 4.4041536e+00 4.5787890e+00 4.1879339e+00 3.9102500e+00 4.8490587e+00 4.6897155e+00 4.2150171e+00 3.8841455e+00 4.1203091e+00 4.3121165e+00 3.9109678e+00 6.7975091e-01 9.0056222e-01 4.1586001e-01 8.2275389e-01 2.0656129e-01 9.4287188e-01 6.0121055e-01 3.8264361e+00 3.4551376e+00 3.9537404e+00 2.8149627e+00 3.5710248e+00 3.2874334e+00 3.6122384e+00 2.0711789e+00 3.5849006e+00 2.6879715e+00 2.3281827e+00 3.0685922e+00 2.8946126e+00 3.5468964e+00 2.4478108e+00 3.4686755e+00 3.2962520e+00 2.9098194e+00 3.4214793e+00 2.7033034e+00 3.6543993e+00 2.9209919e+00 3.7841436e+00 3.5321717e+00 3.2673909e+00 3.4297053e+00 3.8284763e+00 3.9909892e+00 3.3567173e+00 2.3533545e+00 2.6019240e+00 2.5015675e+00 2.7452885e+00 3.9207248e+00 3.2781950e+00 3.3698144e+00 3.7190229e+00 3.3356294e+00 2.8984764e+00 2.8022534e+00 3.1646752e+00 3.4558535e+00 2.8373077e+00 2.0905239e+00 2.9944056e+00 2.9961831e+00 3.0065426e+00 3.2053509e+00 1.8216743e+00 2.9155237e+00 4.9187518e+00 3.9355645e+00 4.9209267e+00 4.4497146e+00 4.7161140e+00 5.6635613e+00 3.2956846e+00 5.2947833e+00 4.7084108e+00 5.1767384e+00 4.0656879e+00 4.2065257e+00 4.4986941e+00 3.8543544e+00 4.0391220e+00 4.2738429e+00 4.3928114e+00 5.7890564e+00 5.9715517e+00 3.8320624e+00 4.7267005e+00 3.7498842e+00 5.7708014e+00 3.8168398e+00 4.6501080e+00 5.0044433e+00 3.7068836e+00 3.7763550e+00 4.5042646e+00 4.8165110e+00 5.1578102e+00 5.5839155e+00 4.5200609e+00 3.9608542e+00 4.3918790e+00 5.2992517e+00 4.5407542e+00 4.3739561e+00 3.6695956e+00 4.4403343e+00 4.6130415e+00 4.2323959e+00 3.9355645e+00 4.8773316e+00 4.7188476e+00 4.2560614e+00 3.9234348e+00 4.1524235e+00 4.3283407e+00 3.9303212e+00 3.8934542e-01 5.4292906e-01 4.4535192e-01 5.3309112e-01 4.5581864e-01 4.2538717e-01 3.3319064e+00 3.0058998e+00 3.4822680e+00 2.4967542e+00 3.1249915e+00 2.9284660e+00 3.1836200e+00 1.8265014e+00 3.1331426e+00 2.3481462e+00 2.1458939e+00 2.6572703e+00 2.5437119e+00 3.1519721e+00 2.0470220e+00 2.9815576e+00 2.9302134e+00 2.5421955e+00 3.0374850e+00 2.3632684e+00 3.2598750e+00 2.4873223e+00 3.3884269e+00 3.1477087e+00 2.8156804e+00 2.9556616e+00 3.3682605e+00 3.5401725e+00 2.9561205e+00 1.9790422e+00 2.2832934e+00 2.1885968e+00 2.3571494e+00 3.5486864e+00 2.9260462e+00 2.9587612e+00 3.2530866e+00 2.9361879e+00 2.5256527e+00 2.4631898e+00 2.8325946e+00 3.0534395e+00 2.4619105e+00 1.8618589e+00 2.6377354e+00 2.6235630e+00 2.6314198e+00 2.7785210e+00 1.5475473e+00 2.5392051e+00 4.5179415e+00 3.5641186e+00 4.4752183e+00 4.0626016e+00 4.3069105e+00 5.2164277e+00 2.9689697e+00 4.8634846e+00 4.3067423e+00 4.7194915e+00 3.6262912e+00 3.7979761e+00 4.0547736e+00 3.4875352e+00 3.6400497e+00 3.8418637e+00 3.9849753e+00 5.3352888e+00 5.5294519e+00 3.4830211e+00 4.2776053e+00 3.3760149e+00 5.3253207e+00 3.4004918e+00 4.2238184e+00 4.5645220e+00 3.2914954e+00 3.3718862e+00 4.0995928e+00 4.3725648e+00 4.7075069e+00 5.1063282e+00 4.1113292e+00 3.5651462e+00 4.0375056e+00 4.8132427e+00 4.1268905e+00 3.9732869e+00 3.2685282e+00 3.9810803e+00 4.1706050e+00 3.7449914e+00 3.5641186e+00 4.4464848e+00 4.2774083e+00 3.7941767e+00 3.5148434e+00 3.7206115e+00 3.9162695e+00 3.5510950e+00 8.6137722e-01 3.2352160e-01 7.6166891e-01 4.2667565e-01 6.2660376e-01 3.0634640e+00 2.7392828e+00 3.2125175e+00 2.3391296e+00 2.8734025e+00 2.6707502e+00 2.9145708e+00 1.7569738e+00 2.8671376e+00 2.1479276e+00 2.1308063e+00 2.4108292e+00 2.3854031e+00 2.8851564e+00 1.8368900e+00 2.7201546e+00 2.6724144e+00 2.2982662e+00 2.8483417e+00 2.1701312e+00 3.0045018e+00 2.2531409e+00 3.1444983e+00 2.8783309e+00 2.5586145e+00 2.6967931e+00 3.1073497e+00 3.2779401e+00 2.7018605e+00 1.8104298e+00 2.1219691e+00 2.0368741e+00 2.1367260e+00 3.2905600e+00 2.6692615e+00 2.6939411e+00 2.9863438e+00 2.7320931e+00 2.2698938e+00 2.2722516e+00 2.5933163e+00 2.7845473e+00 2.2472326e+00 1.8195937e+00 2.4037412e+00 2.3571494e+00 2.3781826e+00 2.5200525e+00 1.5411691e+00 2.3001580e+00 4.2694227e+00 3.3237039e+00 4.2115652e+00 3.7930080e+00 4.0498216e+00 4.9425633e+00 2.7652100e+00 4.5847429e+00 4.0464464e+00 4.4666326e+00 3.3738930e+00 3.5479683e+00 3.8004969e+00 3.2711669e+00 3.4346585e+00 3.6043418e+00 3.7146255e+00 5.0600559e+00 5.2639977e+00 3.2609948e+00 4.0260071e+00 3.1481222e+00 5.0506841e+00 3.1599876e+00 3.9594081e+00 4.2851208e+00 3.0501817e+00 3.1181310e+00 3.8476631e+00 4.0931004e+00 4.4378867e+00 4.8317916e+00 3.8652515e+00 3.2970551e+00 3.7653550e+00 4.5583070e+00 3.8836720e+00 3.7008091e+00 3.0188386e+00 3.7282629e+00 3.9300286e+00 3.5186510e+00 3.3237039e+00 4.1891573e+00 4.0376133e+00 3.5648733e+00 3.2885200e+00 3.4693398e+00 3.6732275e+00 3.2917360e+00 8.1385214e-01 2.5251796e-01 7.6787403e-01 3.2586371e-01 3.5846101e+00 3.2546607e+00 3.7315988e+00 2.6609901e+00 3.3659358e+00 3.1439065e+00 3.4298218e+00 1.9383545e+00 3.3723944e+00 2.5580763e+00 2.1777421e+00 2.8983020e+00 2.6954219e+00 3.3808052e+00 2.2790175e+00 3.2344170e+00 3.1579733e+00 2.7462372e+00 3.2292608e+00 2.5444216e+00 3.5028333e+00 2.7205595e+00 3.6067948e+00 3.3670797e+00 3.0557792e+00 3.2048395e+00 3.6088607e+00 3.7891577e+00 3.1900581e+00 2.1641873e+00 2.4447974e+00 2.3408917e+00 2.5700421e+00 3.7710363e+00 3.1506190e+00 3.2040124e+00 3.5027314e+00 3.1322650e+00 2.7512730e+00 2.6533250e+00 3.0299528e+00 3.2862185e+00 2.6656374e+00 1.9477421e+00 2.8481000e+00 2.8454952e+00 2.8544453e+00 3.0132514e+00 1.6658308e+00 2.7590026e+00 4.7688362e+00 3.7943375e+00 4.7263320e+00 4.2949506e+00 4.5532150e+00 5.4636648e+00 3.1768366e+00 5.1030228e+00 4.5342701e+00 4.9831843e+00 3.8820426e+00 4.0358897e+00 4.3088496e+00 3.7133362e+00 3.8949006e+00 4.1025163e+00 4.2237423e+00 5.5888590e+00 5.7742361e+00 3.6743314e+00 4.5370189e+00 3.6141649e+00 5.5687868e+00 3.6395551e+00 4.4736906e+00 4.8085584e+00 3.5338163e+00 3.6144782e+00 4.3417782e+00 4.6138035e+00 4.9524148e+00 5.3625968e+00 4.3570317e+00 3.7932922e+00 4.2488997e+00 5.0747397e+00 4.3832471e+00 4.2110583e+00 3.5113289e+00 4.2399031e+00 4.4320962e+00 4.0189525e+00 3.7943375e+00 4.7000551e+00 4.5409269e+00 4.0612006e+00 3.7475562e+00 3.9722979e+00 4.1719819e+00 3.7859347e+00 6.9325418e-01 2.1269358e-01 5.0905001e-01 3.3337914e+00 3.0376046e+00 3.4948415e+00 2.6099670e+00 3.1641230e+00 2.9745020e+00 3.2202056e+00 1.9796375e+00 3.1511769e+00 2.4469125e+00 2.3235342e+00 2.7196435e+00 2.6337042e+00 3.1881331e+00 2.1375243e+00 2.9985516e+00 2.9847499e+00 2.5867906e+00 3.1255136e+00 2.4464131e+00 3.3203249e+00 2.5432298e+00 3.4386456e+00 3.1757436e+00 2.8453413e+00 2.9797458e+00 3.3872988e+00 3.5731577e+00 3.0079222e+00 2.0710306e+00 2.3862284e+00 2.2923690e+00 2.4260574e+00 3.5964347e+00 2.9822786e+00 3.0067893e+00 3.2740296e+00 3.0040546e+00 2.5786309e+00 2.5579141e+00 2.8891491e+00 3.0885642e+00 2.5333642e+00 2.0283816e+00 2.7031874e+00 2.6626548e+00 2.6835197e+00 2.8149627e+00 1.7472675e+00 2.6016025e+00 4.5864727e+00 3.6358644e+00 4.5092329e+00 4.1008711e+00 4.3608329e+00 5.2329560e+00 3.0685922e+00 4.8761643e+00 4.3448624e+00 4.7688607e+00 3.6817961e+00 3.8534030e+00 4.1033021e+00 3.5811154e+00 3.7530357e+00 3.9183591e+00 4.0199092e+00 5.3504583e+00 5.5556442e+00 3.5517562e+00 4.3306939e+00 3.4641481e+00 5.3377017e+00 3.4637450e+00 4.2663890e+00 4.5772917e+00 3.3571533e+00 3.4296698e+00 4.1575709e+00 4.3797497e+00 4.7253427e+00 5.1097682e+00 4.1763898e+00 3.5983434e+00 4.0666282e+00 4.8419079e+00 4.2005634e+00 4.0083071e+00 3.3318481e+00 4.0278210e+00 4.2396851e+00 3.8171357e+00 3.6358644e+00 4.4969460e+00 4.3490518e+00 3.8705614e+00 3.5905415e+00 3.7766172e+00 3.9906340e+00 3.6052686e+00 7.6752131e-01 4.0147421e-01 3.6660608e+00 3.3135273e+00 3.8017611e+00 2.7018605e+00 3.4275679e+00 3.1699903e+00 3.4789136e+00 1.9730403e+00 3.4358679e+00 2.5840983e+00 2.2331309e+00 2.9422991e+00 2.7581254e+00 3.4189217e+00 2.3233512e+00 3.3121677e+00 3.1836200e+00 2.7812918e+00 3.2896000e+00 2.5816639e+00 3.5379744e+00 2.7795823e+00 3.6532797e+00 3.4029480e+00 3.1195329e+00 3.2770643e+00 3.6772238e+00 3.8493740e+00 3.2305582e+00 2.2173292e+00 2.4840285e+00 2.3814525e+00 2.6148507e+00 3.8019334e+00 3.1710836e+00 3.2447990e+00 3.5700242e+00 3.1955870e+00 2.7803619e+00 2.6879415e+00 3.0521985e+00 3.3264150e+00 2.7089627e+00 1.9908167e+00 2.8775912e+00 2.8744403e+00 2.8857836e+00 3.0658390e+00 1.7171798e+00 2.7936066e+00 4.8056033e+00 3.8247106e+00 4.7834029e+00 4.3283723e+00 4.5943507e+00 5.5219241e+00 3.2001457e+00 5.1552806e+00 4.5786596e+00 5.0423887e+00 3.9353963e+00 4.0804944e+00 4.3648743e+00 3.7469906e+00 3.9346929e+00 4.1523445e+00 4.2650653e+00 5.6468786e+00 5.8321617e+00 3.7127205e+00 4.5943003e+00 3.6444925e+00 5.6275915e+00 3.6885686e+00 4.5212945e+00 4.8635596e+00 3.5807904e+00 3.6545040e+00 4.3827087e+00 4.6717461e+00 5.0136174e+00 5.4320857e+00 4.3994790e+00 3.8323332e+00 4.2736523e+00 5.1501184e+00 4.4249262e+00 4.2490074e+00 3.5500567e+00 4.3022895e+00 4.4864974e+00 4.0933482e+00 3.8247106e+00 4.7495571e+00 4.5943072e+00 4.1245629e+00 3.7976741e+00 4.0232438e+00 4.2129603e+00 3.8158144e+00 4.4535192e-01 3.3681634e+00 3.1015495e+00 3.5417515e+00 2.6663854e+00 3.2198557e+00 3.0579528e+00 3.2934163e+00 2.0153916e+00 3.2040320e+00 2.5204039e+00 2.3388377e+00 2.7956674e+00 2.6725726e+00 3.2655357e+00 2.2078644e+00 3.0402181e+00 3.0721050e+00 2.6595270e+00 3.1749624e+00 2.5094912e+00 3.4048516e+00 2.6019240e+00 3.5045178e+00 3.2523394e+00 2.8999277e+00 3.0267460e+00 3.4333346e+00 3.6317584e+00 3.0844423e+00 2.1209506e+00 2.4419061e+00 2.3443191e+00 2.4920874e+00 3.6775470e+00 3.0715602e+00 3.0884019e+00 3.3261336e+00 3.0501817e+00 2.6631094e+00 2.6243758e+00 2.9691171e+00 3.1659032e+00 2.5983106e+00 2.0538718e+00 2.7808700e+00 2.7473221e+00 2.7650187e+00 2.8804789e+00 1.7660585e+00 2.6784604e+00 4.6691123e+00 3.7183181e+00 4.5689156e+00 4.1821417e+00 4.4377562e+00 5.2873851e+00 3.1455384e+00 4.9362269e+00 4.4131751e+00 4.8285697e+00 3.7508315e+00 3.9249912e+00 4.1665786e+00 3.6588207e+00 3.8312584e+00 3.9914600e+00 4.0953360e+00 5.4042844e+00 5.6094349e+00 3.6203759e+00 4.3940519e+00 3.5472449e+00 5.3896044e+00 3.5318477e+00 4.3383367e+00 4.6370771e+00 3.4283809e+00 3.5084967e+00 4.2335104e+00 4.4348302e+00 4.7765764e+00 5.1494118e+00 4.2515997e+00 3.6735311e+00 4.1503255e+00 4.8803856e+00 4.2802235e+00 4.0874500e+00 3.4119017e+00 4.0856134e+00 4.3069340e+00 3.8658667e+00 3.7183181e+00 4.5670800e+00 4.4180998e+00 3.9298460e+00 3.6561219e+00 3.8459316e+00 4.0712063e+00 3.6910219e+00 3.5300704e+00 3.2300705e+00 3.6894189e+00 2.6906230e+00 3.3402581e+00 3.1455455e+00 3.4142500e+00 1.9871921e+00 3.3364095e+00 2.5801041e+00 2.2579027e+00 2.8953397e+00 2.7040361e+00 3.3706887e+00 2.2848507e+00 3.1889632e+00 3.1641787e+00 2.7405950e+00 3.2351115e+00 2.5567836e+00 3.5066271e+00 2.7031874e+00 3.5985833e+00 3.3547156e+00 3.0245025e+00 3.1656773e+00 3.5695690e+00 3.7624530e+00 3.1847809e+00 2.1665831e+00 2.4678343e+00 2.3636654e+00 2.5680682e+00 3.7710578e+00 3.1606607e+00 3.1985717e+00 3.4665002e+00 3.1244487e+00 2.7540755e+00 2.6724144e+00 3.0392407e+00 3.2747247e+00 2.6671530e+00 2.0066796e+00 2.8554471e+00 2.8427684e+00 2.8549070e+00 2.9928504e+00 1.7230625e+00 2.7611864e+00 4.7742557e+00 3.8047268e+00 4.7018935e+00 4.2892152e+00 4.5488617e+00 5.4303909e+00 3.2037606e+00 5.0724791e+00 4.5217060e+00 4.9623634e+00 3.8707551e+00 4.0296740e+00 4.2915574e+00 3.7321091e+00 3.9154512e+00 4.1022778e+00 4.2113304e+00 5.5520135e+00 5.7453700e+00 3.6849669e+00 4.5212945e+00 3.6308222e+00 5.5330176e+00 3.6335075e+00 4.4607162e+00 4.7770551e+00 3.5299194e+00 3.6126659e+00 4.3390241e+00 4.5771358e+00 4.9175276e+00 5.3118739e+00 4.3561658e+00 3.7812981e+00 4.2455646e+00 5.0340960e+00 4.3872001e+00 4.2015182e+00 3.5126820e+00 4.2176412e+00 4.4249262e+00 3.9985367e+00 3.8047268e+00 4.6887893e+00 4.5358705e+00 4.0502685e+00 3.7475470e+00 3.9619683e+00 4.1767972e+00 3.7895730e+00 6.0611244e-01 2.1845981e-01 1.6212669e+00 5.6769031e-01 1.3103855e+00 7.0437330e-01 2.2923690e+00 4.4651726e-01 1.8497891e+00 2.2196852e+00 1.1283882e+00 1.3099706e+00 9.0827783e-01 1.5790055e+00 3.7427929e-01 1.4018200e+00 1.2701139e+00 1.1341579e+00 1.5133392e+00 1.1134787e+00 1.0264409e+00 8.7202528e-01 9.2264612e-01 6.6432544e-01 4.5470518e-01 4.1449626e-01 4.3456114e-01 1.0085601e+00 1.5838351e+00 1.6415861e+00 1.6742876e+00 1.3140585e+00 1.0496979e+00 1.6013574e+00 1.0054037e+00 3.0546431e-01 1.0168833e+00 1.4293465e+00 1.5774037e+00 1.5278635e+00 9.0252542e-01 1.2994764e+00 2.2231652e+00 1.4317371e+00 1.3207609e+00 1.3224963e+00 8.3649708e-01 2.2607507e+00 1.3421549e+00 1.5412452e+00 1.2539702e+00 1.2643026e+00 1.0324775e+00 1.2342162e+00 1.9387309e+00 2.1209313e+00 1.6105602e+00 1.1912106e+00 1.5832517e+00 7.2486328e-01 8.5585239e-01 9.4009473e-01 1.3873503e+00 1.3945703e+00 1.0313560e+00 8.7720955e-01 2.0658700e+00 2.2655571e+00 1.2460824e+00 1.1834841e+00 1.4368020e+00 2.0378171e+00 7.9878917e-01 1.0960883e+00 1.3102767e+00 8.5105559e-01 9.2480363e-01 1.0805899e+00 1.1043883e+00 1.4313279e+00 1.8006336e+00 1.1235486e+00 7.6625946e-01 1.1633029e+00 1.5390703e+00 1.2493717e+00 9.0965328e-01 1.0182895e+00 8.6983677e-01 1.1880428e+00 9.2095040e-01 1.2539702e+00 1.3335022e+00 1.3109705e+00 9.5035453e-01 9.2112464e-01 7.6166891e-01 1.1418127e+00 1.1276971e+00 5.6700421e-01 1.1449732e+00 4.0293660e-01 7.3813096e-01 2.1845981e-01 1.7551534e+00 3.4583729e-01 1.2603076e+00 1.7354460e+00 5.3588338e-01 1.0777972e+00 3.8934542e-01 1.0669582e+00 3.0811765e-01 8.0294841e-01 7.8768770e-01 1.0018083e+00 1.0175773e+00 5.5419992e-01 5.9426792e-01 7.3496673e-01 4.8927739e-01 3.4378533e-01 2.5651975e-01 5.2655962e-01 5.4292906e-01 4.4417983e-01 1.1634384e+00 1.1527805e+00 1.2020363e+00 8.1521713e-01 7.2526325e-01 1.0018083e+00 4.1449626e-01 3.2586371e-01 9.0277242e-01 8.3172002e-01 1.0440187e+00 9.7779835e-01 3.2816937e-01 8.1521713e-01 1.7083888e+00 8.6361309e-01 7.3145860e-01 7.3145860e-01 3.6171588e-01 1.7816674e+00 7.6914805e-01 1.6177449e+00 8.3060013e-01 1.4732400e+00 1.1107977e+00 1.3546017e+00 2.2147080e+00 1.5404344e+00 1.8624350e+00 1.3603471e+00 1.7544191e+00 6.8961791e-01 8.7478495e-01 1.0733200e+00 9.5271386e-01 1.0466623e+00 9.9348625e-01 1.0085601e+00 2.3452277e+00 2.5288464e+00 1.0480665e+00 1.3130641e+00 8.9653332e-01 2.3282127e+00 5.8914551e-01 1.2436109e+00 1.5625142e+00 4.8927739e-01 4.8927739e-01 1.1593224e+00 1.3813076e+00 1.7138020e+00 2.1603815e+00 1.1866786e+00 6.4755655e-01 1.1521791e+00 1.8620175e+00 1.2565757e+00 1.0067784e+00 4.8927739e-01 1.0056742e+00 1.2594846e+00 9.3049742e-01 8.3060013e-01 1.4762619e+00 1.3817041e+00 9.4558103e-01 7.9613242e-01 7.7074935e-01 1.0627606e+00 7.0776547e-01 1.5593809e+00 4.8036801e-01 1.2165505e+00 6.1151102e-01 2.2871743e+00 4.0176783e-01 1.7963441e+00 2.1851225e+00 1.0906388e+00 1.2884575e+00 8.0619006e-01 1.6156775e+00 5.0905001e-01 1.3093850e+00 1.2434795e+00 1.0262547e+00 1.4875372e+00 1.0069726e+00 1.0669582e+00 7.4511469e-01 8.2384013e-01 6.9728513e-01 5.3022554e-01 3.0811765e-01 2.5651975e-01 9.2264612e-01 1.6478667e+00 1.6180636e+00 1.6694817e+00 1.3199714e+00 9.2288144e-01 1.5068702e+00 9.2859317e-01 2.4837156e-01 9.3238528e-01 1.3813076e+00 1.5238543e+00 1.4346522e+00 8.1130291e-01 1.2794849e+00 2.2234347e+00 1.3629833e+00 1.2671726e+00 1.2652657e+00 8.1810461e-01 2.3116343e+00 1.2988558e+00 1.3410314e+00 1.1276971e+00 1.0590298e+00 8.2552685e-01 1.0264409e+00 1.7485421e+00 2.0171203e+00 1.4118594e+00 9.7949166e-01 1.3980896e+00 5.7324170e-01 6.6317860e-01 7.4586719e-01 1.2603076e+00 1.2604558e+00 8.7504951e-01 6.6432544e-01 1.8915404e+00 2.0711789e+00 1.1178264e+00 9.9368623e-01 1.3223897e+00 1.8515012e+00 6.6384020e-01 8.9303452e-01 1.1107977e+00 7.2823007e-01 8.1099042e-01 8.7170815e-01 9.0876485e-01 1.2370832e+00 1.6626615e+00 9.2112464e-01 6.2482915e-01 9.7377870e-01 1.3752391e+00 1.0720678e+00 7.0386584e-01 9.0876485e-01 6.8917100e-01 1.0120221e+00 8.0294841e-01 1.1276971e+00 1.1329323e+00 1.1353806e+00 8.1385214e-01 7.7588000e-01 5.8914551e-01 9.8054887e-01 1.0085601e+00 1.0879524e+00 6.2605182e-01 1.2079117e+00 8.2305664e-01 1.1903922e+00 4.4651726e-01 6.5648056e-01 7.4164639e-01 5.2942799e-01 8.9852394e-01 6.4755655e-01 1.3035649e+00 7.7074935e-01 4.8135521e-01 7.7074935e-01 2.5651975e-01 1.1022599e+00 6.8917100e-01 1.0627606e+00 8.6290690e-01 9.7759114e-01 1.1868139e+00 1.3969297e+00 1.4333755e+00 7.6166891e-01 5.6075294e-01 2.5251796e-01 3.7427929e-01 4.4651726e-01 1.1444449e+00 7.7074935e-01 1.1571858e+00 1.3491011e+00 8.2624515e-01 7.0086313e-01 2.0000000e-01 4.4535192e-01 8.9852394e-01 3.7427929e-01 7.7885297e-01 4.1449626e-01 7.0826681e-01 6.1092863e-01 8.2275389e-01 1.0198386e+00 5.0905001e-01 2.2002582e+00 1.1640914e+00 2.2347161e+00 1.6833015e+00 1.9584639e+00 2.9773446e+00 7.2852070e-01 2.5984158e+00 1.9494155e+00 2.5625921e+00 1.4644662e+00 1.4491244e+00 1.8190688e+00 1.0934620e+00 1.3861754e+00 1.6265426e+00 1.6626615e+00 3.1878246e+00 3.2549253e+00 1.0346741e+00 2.0606771e+00 1.0425476e+00 3.0882196e+00 1.1022599e+00 1.9692383e+00 2.3537589e+00 1.0082605e+00 1.0950112e+00 1.7332099e+00 2.1942739e+00 2.5032087e+00 3.0886055e+00 1.7538274e+00 1.2342162e+00 1.6237100e+00 2.7181432e+00 1.8926658e+00 1.6507294e+00 1.0082605e+00 1.8244836e+00 1.9254808e+00 1.7303440e+00 1.1640914e+00 2.1641182e+00 2.0565627e+00 1.6435752e+00 1.1782910e+00 1.4699978e+00 1.7142546e+00 1.2095267e+00 8.0326782e-01 5.0991930e-01 1.8350577e+00 2.1269358e-01 1.3537729e+00 1.7146525e+00 6.5172743e-01 8.5585239e-01 4.0438741e-01 1.1847335e+00 3.4583729e-01 9.0252542e-01 8.2372435e-01 6.2024833e-01 1.0324775e+00 6.6827038e-01 6.5172743e-01 3.8776762e-01 4.4535192e-01 3.2816937e-01 2.5651975e-01 3.2586371e-01 4.3691963e-01 5.0180477e-01 1.2342162e+00 1.1573546e+00 1.2172454e+00 8.7848692e-01 6.2205176e-01 1.1016264e+00 6.9006418e-01 3.2586371e-01 5.2371571e-01 9.4492923e-01 1.0646687e+00 1.0101422e+00 4.1449626e-01 8.2552685e-01 1.7679545e+00 9.2288144e-01 8.3888121e-01 8.2929029e-01 3.8934542e-01 1.8776878e+00 8.5434758e-01 1.5481649e+00 7.9613242e-01 1.3657247e+00 1.0085601e+00 1.2641849e+00 2.1002817e+00 1.6030661e+00 1.7482192e+00 1.2100024e+00 1.7005893e+00 6.6491075e-01 7.3535471e-01 9.7949166e-01 8.8358844e-01 1.0423677e+00 9.5498315e-01 9.1051084e-01 2.2737459e+00 2.4086493e+00 7.2486328e-01 1.2326306e+00 9.4832302e-01 2.2096958e+00 3.8934542e-01 1.1729895e+00 1.4561933e+00 3.8776762e-01 4.8927739e-01 1.0574300e+00 1.2643026e+00 1.5918956e+00 2.0914667e+00 1.0906388e+00 5.0817745e-01 1.0182895e+00 1.7457596e+00 1.2250414e+00 9.1663180e-01 5.4292906e-01 9.1750357e-01 1.1891470e+00 8.8358844e-01 7.9613242e-01 1.3916739e+00 1.3267389e+00 8.9303452e-01 5.3309112e-01 6.9325418e-01 1.0574013e+00 7.0776547e-01 7.0776547e-01 1.3071453e+00 9.0049692e-01 6.9006418e-01 1.2079117e+00 3.6171588e-01 7.1791510e-01 4.1586001e-01 9.0049692e-01 1.0069726e+00 2.5251796e-01 4.4651726e-01 6.9325418e-01 6.2538346e-01 5.9426792e-01 5.6631629e-01 6.6827038e-01 4.1449626e-01 7.0437330e-01 9.0277242e-01 1.1055069e+00 1.0496979e+00 3.2586371e-01 1.0083666e+00 7.4164639e-01 8.3888121e-01 6.0181382e-01 6.3861009e-01 3.4378533e-01 6.3808075e-01 1.0101422e+00 6.8961791e-01 4.1449626e-01 5.3588338e-01 2.5651975e-01 4.1586001e-01 5.0991930e-01 1.2869134e+00 3.0546431e-01 3.2586371e-01 3.0275928e-01 5.0905001e-01 1.5278635e+00 4.0000000e-01 1.7279861e+00 7.4586719e-01 1.7831878e+00 1.1718516e+00 1.4824233e+00 2.5111349e+00 8.3620494e-01 2.1256928e+00 1.4719311e+00 2.0814452e+00 1.0175773e+00 1.0014633e+00 1.3875139e+00 7.7885297e-01 1.1473003e+00 1.2144845e+00 1.1591754e+00 2.6773585e+00 2.7900071e+00 7.0776547e-01 1.6151673e+00 7.3496673e-01 2.6263773e+00 7.2526325e-01 1.4645804e+00 1.8755806e+00 6.3924842e-01 6.2407309e-01 1.2731262e+00 1.7507664e+00 2.0635966e+00 2.6116811e+00 1.3129189e+00 7.4855857e-01 1.1149070e+00 2.3160147e+00 1.4246028e+00 1.1229843e+00 5.6075294e-01 1.4120836e+00 1.5094575e+00 1.4108494e+00 7.4586719e-01 1.6884234e+00 1.6211869e+00 1.3017208e+00 8.1521713e-01 1.0401425e+00 1.2452704e+00 6.9728513e-01 1.8185955e+00 4.8135521e-01 1.2509218e+00 1.8049926e+00 5.8914551e-01 1.2205493e+00 4.2538717e-01 1.1912106e+00 4.6472023e-01 7.1840099e-01 8.9207714e-01 1.1017858e+00 1.1127329e+00 4.1586001e-01 7.8197925e-01 8.0326782e-01 5.7257017e-01 5.2655962e-01 4.3456114e-01 6.2660376e-01 4.8135521e-01 4.5581864e-01 1.3318128e+00 1.2468939e+00 1.3144065e+00 9.4935318e-01 6.6384020e-01 9.1075311e-01 3.2586371e-01 4.1449626e-01 1.0130748e+00 8.3280511e-01 1.0906119e+00 9.6204649e-01 3.4583729e-01 9.3296062e-01 1.7901543e+00 8.7170815e-01 7.3805807e-01 7.3805807e-01 5.2655962e-01 1.9041928e+00 8.1521713e-01 1.4138821e+00 7.3805807e-01 1.3166957e+00 9.2264612e-01 1.1533602e+00 2.0690479e+00 1.4700179e+00 1.7092525e+00 1.2231847e+00 1.5870088e+00 5.0592043e-01 7.5791688e-01 9.0575661e-01 9.1750357e-01 9.1802948e-01 8.1304731e-01 8.1638392e-01 2.1978861e+00 2.3802944e+00 1.1107977e+00 1.1386292e+00 7.9878917e-01 2.1900222e+00 6.1092863e-01 1.0480665e+00 1.4148192e+00 5.0991930e-01 3.6171588e-01 9.7825559e-01 1.2593659e+00 1.5912764e+00 2.0615043e+00 1.0056742e+00 5.6700421e-01 1.0137836e+00 1.7695175e+00 1.0597541e+00 8.0619006e-01 3.8934542e-01 8.6513410e-01 1.0755693e+00 8.4050231e-01 7.3805807e-01 1.2832075e+00 1.1947245e+00 8.0660588e-01 8.2105460e-01 5.9426792e-01 8.6983677e-01 5.3309112e-01 1.9083318e+00 6.7975091e-01 4.1449626e-01 1.2452704e+00 1.1763980e+00 1.6420607e+00 7.9016429e-01 1.9365498e+00 1.3172979e+00 1.0653845e+00 1.5684812e+00 8.1304731e-01 1.7169601e+00 1.2768639e+00 1.8804140e+00 1.6311692e+00 1.6315809e+00 1.8424891e+00 2.1489929e+00 2.2038673e+00 1.4613032e+00 8.0587320e-01 6.8961791e-01 6.4704320e-01 9.7949166e-01 1.9250543e+00 1.2802798e+00 1.5824669e+00 2.0485534e+00 1.5790055e+00 1.0078327e+00 8.2305664e-01 1.1498269e+00 1.5838351e+00 1.0137836e+00 1.2418578e-01 1.0230441e+00 1.1119327e+00 1.0941064e+00 1.4719311e+00 3.2816937e-01 1.0165138e+00 2.9338155e+00 1.9158303e+00 3.0455280e+00 2.4635485e+00 2.7430309e+00 3.7921012e+00 1.2632199e+00 3.4105293e+00 2.7619926e+00 3.3261421e+00 2.2045198e+00 2.2598424e+00 2.6204307e+00 1.8330979e+00 2.0701646e+00 2.3622531e+00 2.4525409e+00 3.9619101e+00 4.0743074e+00 1.8315269e+00 2.8475224e+00 1.7388184e+00 3.9054939e+00 1.9111264e+00 2.7377517e+00 3.1510494e+00 1.7964653e+00 1.8350071e+00 2.5277506e+00 2.9970778e+00 3.3196868e+00 3.8532018e+00 2.5453122e+00 2.0312250e+00 2.3887539e+00 3.5269824e+00 2.5986705e+00 2.4210417e+00 1.7228354e+00 2.6125646e+00 2.7062349e+00 2.4839132e+00 1.9158303e+00 2.9502077e+00 2.8139128e+00 2.4180244e+00 1.9947426e+00 2.2550764e+00 2.3956104e+00 1.9332869e+00 1.4468211e+00 1.8027242e+00 7.3851529e-01 9.0658670e-01 5.0180477e-01 1.2418578e+00 2.5651975e-01 1.0022010e+00 8.6361309e-01 7.3851529e-01 1.1055064e+00 7.8197925e-01 6.8961791e-01 4.8927739e-01 5.0270183e-01 3.2352160e-01 2.1269358e-01 2.5651975e-01 4.9857388e-01 6.0611244e-01 1.2633451e+00 1.2342162e+00 1.2794849e+00 9.3824087e-01 7.0776547e-01 1.2014753e+00 7.0429250e-01 2.5651975e-01 6.2482915e-01 1.0329901e+00 1.1593224e+00 1.1069580e+00 5.0180477e-01 8.9712482e-01 1.8394959e+00 1.0181000e+00 9.2095040e-01 9.2047746e-01 4.4417983e-01 1.9314297e+00 9.4103005e-01 1.6328100e+00 9.3238528e-01 1.3969297e+00 1.0389435e+00 1.3327491e+00 2.0961718e+00 1.7103548e+00 1.7405652e+00 1.2330392e+00 1.7474965e+00 7.7919451e-01 8.1810461e-01 1.0613462e+00 1.0417249e+00 1.2331989e+00 1.0974061e+00 9.4125538e-01 2.2568188e+00 2.4127176e+00 8.4050231e-01 1.3145067e+00 1.0960883e+00 2.1973666e+00 5.6075294e-01 1.2221471e+00 1.4467170e+00 5.7324170e-01 6.3977563e-01 1.1341579e+00 1.2436109e+00 1.5826476e+00 2.0476065e+00 1.1847335e+00 5.3665999e-01 1.0391247e+00 1.7521201e+00 1.3293211e+00 9.4492923e-01 6.9369532e-01 1.0019724e+00 1.3083079e+00 1.0406064e+00 9.3238528e-01 1.4580335e+00 1.4387122e+00 1.0576043e+00 7.0233835e-01 8.1304731e-01 1.1697902e+00 8.2372435e-01 7.6914805e-01 7.2823007e-01 8.7504951e-01 1.0611732e+00 4.5581864e-01 1.5204521e+00 6.6432544e-01 6.5172743e-01 1.0866092e+00 4.5470518e-01 1.0573285e+00 9.0074515e-01 1.3083079e+00 1.0613462e+00 1.2123540e+00 1.4190961e+00 1.6754036e+00 1.6596797e+00 8.9095811e-01 6.1947990e-01 4.2418962e-01 4.8927739e-01 6.0551856e-01 1.2951131e+00 6.2538346e-01 1.0030700e+00 1.5663312e+00 1.1396406e+00 4.5581864e-01 3.2816937e-01 5.3665999e-01 1.0166932e+00 6.0670504e-01 6.9167458e-01 4.4535192e-01 5.6075294e-01 5.3665999e-01 1.0182895e+00 9.1075311e-01 5.0991930e-01 2.2632657e+00 1.2601890e+00 2.4384530e+00 1.8269304e+00 2.0927845e+00 3.1870761e+00 6.4290921e-01 2.8096725e+00 2.1462316e+00 2.6900593e+00 1.5901181e+00 1.6364474e+00 2.0101738e+00 1.1729895e+00 1.4078246e+00 1.7083888e+00 1.8278268e+00 3.3435703e+00 3.4604677e+00 1.2331989e+00 2.2206574e+00 1.0719360e+00 3.3068858e+00 1.3163598e+00 2.0994872e+00 2.5539296e+00 1.1948578e+00 1.1991899e+00 1.8828324e+00 2.4245766e+00 2.7358293e+00 3.2702869e+00 1.8959565e+00 1.4312787e+00 1.7665622e+00 2.9527671e+00 1.9255490e+00 1.7860690e+00 1.0796583e+00 2.0205937e+00 2.0647798e+00 1.9168750e+00 1.2601890e+00 2.3069539e+00 2.1608869e+00 1.8128438e+00 1.3838212e+00 1.6376058e+00 1.7220696e+00 1.2768639e+00 1.2687651e+00 1.0344911e+00 1.5320003e+00 9.7779835e-01 1.8837258e+00 1.2979752e+00 1.0012667e+00 1.3957794e+00 7.2526325e-01 1.6850672e+00 1.2372418e+00 1.7004805e+00 1.4979666e+00 1.5611922e+00 1.7745022e+00 2.0153916e+00 2.0815027e+00 1.3830210e+00 8.1242502e-01 5.8914551e-01 5.7257017e-01 9.5676647e-01 1.7518264e+00 1.2724737e+00 1.6669115e+00 1.9675324e+00 1.4200435e+00 1.1136605e+00 7.1881659e-01 1.0072663e+00 1.5134954e+00 9.3238528e-01 3.2352160e-01 9.5222919e-01 1.1681971e+00 1.1043332e+00 1.4120836e+00 6.2205176e-01 1.0078327e+00 2.8028143e+00 1.7525933e+00 2.8815987e+00 2.2944257e+00 2.5825907e+00 3.6132031e+00 1.1179743e+00 3.2286633e+00 2.5673494e+00 3.2133201e+00 2.1127170e+00 2.0867931e+00 2.4721080e+00 1.6626615e+00 1.9456450e+00 2.2607446e+00 2.2983453e+00 3.8335668e+00 3.8818411e+00 1.6299374e+00 2.7127377e+00 1.6132118e+00 3.7182722e+00 1.7559391e+00 2.6123294e+00 2.9966900e+00 1.6625128e+00 1.7303440e+00 2.3560577e+00 2.8340159e+00 3.1407514e+00 3.7366777e+00 2.3765195e+00 1.8701780e+00 2.1933937e+00 3.3657099e+00 2.5066503e+00 2.2797241e+00 1.6331631e+00 2.4808010e+00 2.5698271e+00 2.3770285e+00 1.7525933e+00 2.8053367e+00 2.6963680e+00 2.2934334e+00 1.8202060e+00 2.1229819e+00 2.3231793e+00 1.8122257e+00 8.5462626e-01 5.0991930e-01 6.2538346e-01 8.0358695e-01 3.7255734e-01 5.3022554e-01 8.2105460e-01 6.0900723e-01 6.2482915e-01 3.0844217e-01 7.9580667e-01 5.4292906e-01 5.0991930e-01 7.0437330e-01 9.7270522e-01 9.9532071e-01 3.0546431e-01 7.9878917e-01 7.2343175e-01 7.8768770e-01 4.2418962e-01 9.0876485e-01 5.2862779e-01 4.4651726e-01 8.5205778e-01 7.4164639e-01 3.2586371e-01 5.7867728e-01 5.3309112e-01 4.1449626e-01 4.5581864e-01 1.2131545e+00 3.8776762e-01 3.2352160e-01 2.5251796e-01 3.2816937e-01 1.3221405e+00 2.8507955e-01 1.8873850e+00 9.2859317e-01 1.8730683e+00 1.4111029e+00 1.6550480e+00 2.6320302e+00 1.0406064e+00 2.2657813e+00 1.6658308e+00 2.1339968e+00 1.0072663e+00 1.1444449e+00 1.4417207e+00 8.9971984e-01 1.1192426e+00 1.2342162e+00 1.3367840e+00 2.7726042e+00 2.9277580e+00 9.9368623e-01 1.6694974e+00 7.8197925e-01 2.7493700e+00 7.5976039e-01 1.5849874e+00 1.9802196e+00 6.4290921e-01 7.1799256e-01 1.4445746e+00 1.8216794e+00 2.1462316e+00 2.6367554e+00 1.4616539e+00 9.2264612e-01 1.4088394e+00 2.3235034e+00 1.5120955e+00 1.3224963e+00 6.2024833e-01 1.4078246e+00 1.5587730e+00 1.2801437e+00 9.2859317e-01 1.8096161e+00 1.6710566e+00 1.2378278e+00 8.9653332e-01 1.0864449e+00 1.3071453e+00 9.0827783e-01 8.9159388e-01 7.7763126e-01 1.0417249e+00 9.1802948e-01 5.0905001e-01 6.2605182e-01 4.4651726e-01 1.2379511e+00 6.2024833e-01 9.5571254e-01 8.1558458e-01 7.5976039e-01 9.2944046e-01 1.0661822e+00 1.2662457e+00 8.2342214e-01 5.8851328e-01 5.1691876e-01 5.3588338e-01 5.1691876e-01 1.1717125e+00 9.6838716e-01 1.2601890e+00 1.1258723e+00 4.8135521e-01 8.3649708e-01 5.5419992e-01 6.2407309e-01 9.0965328e-01 4.2538717e-01 1.0906388e+00 5.9426792e-01 8.1638392e-01 7.3145860e-01 7.3145860e-01 1.1880428e+00 6.3861009e-01 2.2927296e+00 1.2766755e+00 2.1156916e+00 1.6869465e+00 1.9835684e+00 2.8209672e+00 1.2028939e+00 2.4458200e+00 1.8683030e+00 2.5149752e+00 1.4746001e+00 1.4371043e+00 1.7501772e+00 1.2500343e+00 1.5991931e+00 1.7211928e+00 1.6271057e+00 3.0580852e+00 3.1168283e+00 1.0328064e+00 2.0203543e+00 1.2344562e+00 2.9146252e+00 1.0941064e+00 1.9515265e+00 2.2002582e+00 1.0531192e+00 1.1790011e+00 1.7600233e+00 1.9895190e+00 2.3106402e+00 2.8876509e+00 1.7983401e+00 1.1763719e+00 1.6118154e+00 2.5100676e+00 2.0039716e+00 1.6449456e+00 1.1276917e+00 1.7245185e+00 1.9495298e+00 1.6638124e+00 1.2766755e+00 2.1512175e+00 2.1034605e+00 1.6449189e+00 1.1924295e+00 1.4645804e+00 1.8408873e+00 1.3037063e+00 1.1269972e+00 6.2482915e-01 5.0991930e-01 6.6827038e-01 7.0479928e-01 8.8358844e-01 4.5581864e-01 7.0086313e-01 4.2667565e-01 2.0656129e-01 4.4535192e-01 5.2942799e-01 7.0086313e-01 6.3861009e-01 2.1269358e-01 1.2261087e+00 1.0119857e+00 1.1001291e+00 8.1638392e-01 4.2667565e-01 7.0479928e-01 5.1691876e-01 6.0611244e-01 6.2538346e-01 6.9006418e-01 8.3812833e-01 6.4290921e-01 1.2418578e-01 7.3145860e-01 1.6044563e+00 6.2660376e-01 5.7324170e-01 5.6700421e-01 4.0293660e-01 1.7981158e+00 6.4806901e-01 1.5090287e+00 5.9426792e-01 1.4258804e+00 9.2264612e-01 1.2221471e+00 2.1613095e+00 1.2165505e+00 1.7814077e+00 1.1712156e+00 1.7475837e+00 7.0233835e-01 7.0776547e-01 1.0386594e+00 7.0233835e-01 1.0228981e+00 9.8450810e-01 8.5105559e-01 2.3257048e+00 2.4547248e+00 7.1504098e-01 1.2838690e+00 6.9369532e-01 2.2753334e+00 4.3691963e-01 1.1508502e+00 1.5109753e+00 4.0438741e-01 4.1449626e-01 1.0168833e+00 1.3670543e+00 1.6898941e+00 2.2253038e+00 1.0655560e+00 4.1586001e-01 9.0827783e-01 1.9262937e+00 1.2075315e+00 8.3888121e-01 4.0438741e-01 1.0401425e+00 1.2250414e+00 1.0755693e+00 5.9426792e-01 1.3866792e+00 1.3487634e+00 1.0056742e+00 5.9426792e-01 7.2526325e-01 1.0425476e+00 5.0592043e-01 1.2125198e+00 9.0252542e-01 5.4292906e-01 1.0679144e+00 4.5470518e-01 1.2307737e+00 5.6700421e-01 1.3629833e+00 1.1271488e+00 9.3592296e-01 1.1329323e+00 1.4903933e+00 1.5826638e+00 9.2264612e-01 3.7598397e-01 5.1691876e-01 5.3022554e-01 3.4583729e-01 1.5102079e+00 9.0478973e-01 9.6664346e-01 1.3678655e+00 1.0012667e+00 5.0090417e-01 4.9766035e-01 8.1130291e-01 1.0331736e+00 4.5581864e-01 7.6955924e-01 6.0551856e-01 6.0181382e-01 6.0060595e-01 8.1242502e-01 7.2852070e-01 5.0180477e-01 2.4944334e+00 1.5259640e+00 2.4897635e+00 2.0286682e+00 2.2758462e+00 3.2467454e+00 1.0417249e+00 2.8819633e+00 2.2804674e+00 2.7472449e+00 1.6234861e+00 1.7644184e+00 2.0587064e+00 1.4533724e+00 1.6559784e+00 1.8347926e+00 1.9605308e+00 3.3855928e+00 3.5452222e+00 1.4540815e+00 2.2852232e+00 1.3536716e+00 3.3617477e+00 1.3717027e+00 2.2096171e+00 2.5931780e+00 1.2603076e+00 1.3371180e+00 2.0643410e+00 2.4233813e+00 2.7521037e+00 3.2246201e+00 2.0784533e+00 1.5405106e+00 2.0088441e+00 2.9099860e+00 2.1140685e+00 1.9448322e+00 1.2330392e+00 2.0122105e+00 2.1687358e+00 1.8353933e+00 1.5259640e+00 2.4321505e+00 2.2783778e+00 1.8251179e+00 1.4795374e+00 1.7068208e+00 1.9049236e+00 1.5165339e+00 1.1004794e+00 9.4753140e-01 9.4125538e-01 1.1763719e+00 8.5105559e-01 6.6432544e-01 7.2526325e-01 6.4290921e-01 3.2816937e-01 1.2418578e-01 4.4535192e-01 6.2024833e-01 7.0479928e-01 1.2172454e+00 1.3021788e+00 1.3289150e+00 9.6141901e-01 8.9366705e-01 1.3003320e+00 7.1840099e-01 3.0275928e-01 8.2654509e-01 1.1056650e+00 1.2497790e+00 1.2234738e+00 6.0611244e-01 9.6141901e-01 1.8661545e+00 1.1149070e+00 1.0038051e+00 1.0038051e+00 5.0991930e-01 1.8886923e+00 1.0132664e+00 1.7427900e+00 1.0573285e+00 1.5462225e+00 1.2230220e+00 1.4700179e+00 2.2554582e+00 1.8183902e+00 1.9191337e+00 1.4359851e+00 1.8399871e+00 8.1558458e-01 9.6664346e-01 1.1754055e+00 1.1548215e+00 1.2523175e+00 1.1229906e+00 1.1149070e+00 2.3868096e+00 2.5734684e+00 1.0665149e+00 1.4148192e+00 1.1763719e+00 2.3591336e+00 6.6317860e-01 1.3545005e+00 1.6178623e+00 6.3735887e-01 7.2526325e-01 1.2709820e+00 1.4169523e+00 1.7425222e+00 2.1449779e+00 1.3015611e+00 7.4777660e-01 1.2601890e+00 1.8513630e+00 1.3897316e+00 1.1185330e+00 7.6625946e-01 1.0919712e+00 1.3783420e+00 1.0120221e+00 1.0573285e+00 1.5860263e+00 1.5022608e+00 1.0597541e+00 8.3060013e-01 8.9095811e-01 1.2107055e+00 9.5498315e-01 5.9426792e-01 8.8835966e-01 7.2486328e-01 4.3456114e-01 6.3108414e-01 7.9580667e-01 5.4292906e-01 8.0619006e-01 1.0003942e+00 1.2057554e+00 1.1282371e+00 4.0147421e-01 1.0482443e+00 8.3812833e-01 9.3049742e-01 6.4290921e-01 6.6432544e-01 2.0000000e-01 4.9766035e-01 1.1016264e+00 8.7202528e-01 4.1312257e-01 6.2660376e-01 4.4651726e-01 5.0180477e-01 5.9426792e-01 1.3172979e+00 3.8776762e-01 3.7427929e-01 3.2816937e-01 6.1151102e-01 1.5338492e+00 4.2667565e-01 1.6536633e+00 6.6827038e-01 1.8195408e+00 1.1798960e+00 1.4588731e+00 2.5552364e+00 7.7039952e-01 2.1764356e+00 1.5179392e+00 2.0659196e+00 1.0072663e+00 1.0165138e+00 1.4077317e+00 7.0523271e-01 9.7441804e-01 1.1290808e+00 1.1879078e+00 2.7003420e+00 2.8251568e+00 8.7478495e-01 1.6113870e+00 5.7257017e-01 2.6756977e+00 7.5976039e-01 1.4611141e+00 1.9290721e+00 6.4290921e-01 5.8851328e-01 1.2509218e+00 1.8216794e+00 2.1226924e+00 2.6556584e+00 1.2741904e+00 8.1527569e-01 1.1396406e+00 2.3658814e+00 1.3219975e+00 1.1377990e+00 4.8036801e-01 1.4418088e+00 1.4695582e+00 1.4097125e+00 6.6827038e-01 1.6762567e+00 1.5624022e+00 1.2731262e+00 8.4812820e-01 1.0423677e+00 1.1235486e+00 6.3808075e-01 7.0328431e-01 2.8507955e-01 9.7377870e-01 3.7598397e-01 8.9971984e-01 6.2538346e-01 6.2988288e-01 8.4591037e-01 1.1042097e+00 1.1949615e+00 5.7867728e-01 6.0121055e-01 4.2418962e-01 4.8036801e-01 2.4837156e-01 1.0588560e+00 6.3735887e-01 8.4050231e-01 1.0216438e+00 6.0900723e-01 3.8776762e-01 3.8934542e-01 3.8934542e-01 6.0900723e-01 2.1269358e-01 1.0100718e+00 3.2586371e-01 3.2816937e-01 3.2816937e-01 4.6472023e-01 1.1765359e+00 3.0546431e-01 2.1603815e+00 1.1833480e+00 2.0696037e+00 1.5733646e+00 1.8844302e+00 2.7913211e+00 1.0279631e+00 2.4069427e+00 1.8096161e+00 2.4013270e+00 1.3194762e+00 1.3641156e+00 1.6852518e+00 1.1847335e+00 1.5344133e+00 1.5901181e+00 1.5133392e+00 2.9601125e+00 3.0929882e+00 9.7994716e-01 1.9359434e+00 1.1341579e+00 2.8969791e+00 1.0267435e+00 1.8174459e+00 2.1353579e+00 9.5498315e-01 1.0067464e+00 1.6752254e+00 1.9600024e+00 2.3015655e+00 2.8161147e+00 1.7177705e+00 1.0636401e+00 1.5095556e+00 2.5229584e+00 1.8388413e+00 1.5016471e+00 9.4558103e-01 1.6620056e+00 1.8661202e+00 1.6246433e+00 1.1833480e+00 2.0524784e+00 1.9917352e+00 1.5896248e+00 1.1449732e+00 1.3635198e+00 1.6539414e+00 1.1377990e+00 7.8695083e-01 1.0194752e+00 6.9369532e-01 4.4535192e-01 6.2538346e-01 7.1169738e-01 8.2684479e-01 7.5791688e-01 8.9971984e-01 7.0394675e-01 1.0777972e+00 8.9366705e-01 9.7548738e-01 7.3805807e-01 6.9369532e-01 9.9348625e-01 1.2013436e+00 9.4287188e-01 2.1845981e-01 9.1163729e-01 7.8197925e-01 7.4777660e-01 8.0096515e-01 6.3735887e-01 1.5043029e+00 7.0776547e-01 8.7021234e-01 7.8197925e-01 7.0784540e-01 1.6629594e+00 7.2852070e-01 1.7521201e+00 7.5705927e-01 1.5812904e+00 1.1798960e+00 1.4306494e+00 2.2994849e+00 1.3047221e+00 1.9342059e+00 1.3259654e+00 2.0165210e+00 1.0919404e+00 8.7720955e-01 1.2180145e+00 7.1881659e-01 1.0466623e+00 1.2389598e+00 1.1426203e+00 2.5872805e+00 2.5766735e+00 5.0817745e-01 1.4924169e+00 8.3060013e-01 2.3982377e+00 5.8914551e-01 1.4728952e+00 1.7209381e+00 6.3808075e-01 8.3649708e-01 1.1916257e+00 1.5183917e+00 1.7983401e+00 2.4620092e+00 1.2174316e+00 7.4549115e-01 1.1136343e+00 1.9965599e+00 1.5255331e+00 1.1891470e+00 8.2384013e-01 1.2304904e+00 1.3937115e+00 1.1842231e+00 7.5705927e-01 1.6132118e+00 1.5725854e+00 1.1127329e+00 5.8914551e-01 9.8054887e-01 1.4089719e+00 9.0521488e-01 1.1043332e+00 5.3665999e-01 1.1040512e+00 8.6137722e-01 8.5335130e-01 1.0692258e+00 1.3395518e+00 1.4121163e+00 7.2343175e-01 4.0438741e-01 1.4096146e-01 2.1845981e-01 2.5251796e-01 1.2340567e+00 7.2852070e-01 1.0216438e+00 1.2599182e+00 7.7360126e-01 5.1607523e-01 2.1269358e-01 5.0270183e-01 8.3345577e-01 2.1845981e-01 7.4893123e-01 3.4378533e-01 5.3022554e-01 4.5581864e-01 6.9167458e-01 9.4080461e-01 3.4583729e-01 2.3056888e+00 1.2961380e+00 2.2790932e+00 1.7645599e+00 2.0520955e+00 3.0186066e+00 8.9827435e-01 2.6386155e+00 2.0191749e+00 2.5992685e+00 1.4843324e+00 1.5325189e+00 1.8691652e+00 1.2554784e+00 1.5582387e+00 1.7070813e+00 1.7171798e+00 3.2008583e+00 3.3121677e+00 1.1313840e+00 2.1147926e+00 1.1879078e+00 3.1280700e+00 1.1681971e+00 2.0145868e+00 2.3728666e+00 1.0720678e+00 1.1437669e+00 1.8347926e+00 2.2027051e+00 2.5315934e+00 3.0688850e+00 1.8636112e+00 1.2768639e+00 1.7126039e+00 2.7381221e+00 1.9739212e+00 1.7039473e+00 1.0573285e+00 1.8507968e+00 2.0095044e+00 1.7591313e+00 1.2961380e+00 2.2339736e+00 2.1358764e+00 1.7106141e+00 1.2731262e+00 1.5241199e+00 1.7828037e+00 1.2869134e+00 8.7720955e-01 7.4777660e-01 6.5223271e-01 7.1881659e-01 7.6914805e-01 9.3999899e-01 8.0619006e-01 4.2418962e-01 1.4104707e+00 1.2144845e+00 1.3128167e+00 1.0056742e+00 5.3665999e-01 5.6075294e-01 3.4583729e-01 8.1130291e-01 9.7730901e-01 7.8197925e-01 9.9089002e-01 8.0353565e-01 4.3691963e-01 9.6095130e-01 1.7110336e+00 7.7033318e-01 7.5196795e-01 7.0776547e-01 6.5648056e-01 1.8915404e+00 7.9878917e-01 1.2730931e+00 5.3022554e-01 1.4349259e+00 8.3620494e-01 1.0733200e+00 2.1767273e+00 1.0960883e+00 1.8048569e+00 1.2035173e+00 1.6539414e+00 6.2482915e-01 7.0523271e-01 1.0184370e+00 7.1169738e-01 6.6432544e-01 7.0480730e-01 8.1527569e-01 2.3116343e+00 2.4505705e+00 1.0085601e+00 1.2063335e+00 4.5581864e-01 2.3022338e+00 5.6700421e-01 1.0655560e+00 1.5550492e+00 4.4417983e-01 2.5251796e-01 8.8358844e-01 1.4559276e+00 1.7532140e+00 2.2755980e+00 8.9653332e-01 5.5160819e-01 9.1163729e-01 1.9862884e+00 9.1163729e-01 7.6752131e-01 2.0656129e-01 1.0632598e+00 1.0516761e+00 1.0391247e+00 5.3022554e-01 1.2756158e+00 1.1406052e+00 8.7720955e-01 7.3851529e-01 6.5633874e-01 7.0776547e-01 3.2352160e-01 9.1273187e-01 7.0043186e-01 3.7427929e-01 5.7324170e-01 9.3615100e-01 1.0733200e+00 5.0991930e-01 5.9426792e-01 6.5633874e-01 6.7975091e-01 3.0811765e-01 1.1056650e+00 7.7360126e-01 7.0429250e-01 8.2552685e-01 5.7257017e-01 5.0905001e-01 6.1968386e-01 6.5223271e-01 6.0611244e-01 3.2586371e-01 1.2028939e+00 5.0905001e-01 4.2667565e-01 4.1449626e-01 3.0546431e-01 1.2470767e+00 4.0147421e-01 2.1213832e+00 1.1521791e+00 2.0070710e+00 1.6126950e+00 1.8637576e+00 2.7490677e+00 1.2370832e+00 2.3910690e+00 1.8274132e+00 2.3004229e+00 1.1979861e+00 1.3368881e+00 1.5972416e+00 1.1093572e+00 1.3693737e+00 1.4644753e+00 1.5211725e+00 2.9013543e+00 3.0559175e+00 1.0590298e+00 1.8374244e+00 1.0423677e+00 2.8588399e+00 9.4309624e-01 1.7735968e+00 2.0979142e+00 8.5205778e-01 9.4287188e-01 1.6546836e+00 1.9109434e+00 2.2424413e+00 2.7118839e+00 1.6774684e+00 1.1029298e+00 1.6007141e+00 2.3898698e+00 1.7557336e+00 1.5191033e+00 8.5462626e-01 1.5344007e+00 1.7571295e+00 1.3898545e+00 1.1521791e+00 1.9987470e+00 1.8815752e+00 1.4085850e+00 1.0646687e+00 1.2740417e+00 1.5577803e+00 1.1296247e+00 4.0176783e-01 6.5223271e-01 6.3977563e-01 5.3022554e-01 5.7324170e-01 5.2574978e-01 1.4438552e+00 1.2221471e+00 1.3131724e+00 1.0406064e+00 3.4583729e-01 9.5943875e-01 9.2859317e-01 6.5172743e-01 5.1607523e-01 9.7548738e-01 1.0611732e+00 8.6137722e-01 5.3665999e-01 9.4854455e-01 1.8311457e+00 8.7420176e-01 8.7170815e-01 8.4050231e-01 6.5223271e-01 2.0303919e+00 8.9917007e-01 1.3866318e+00 5.7867728e-01 1.2002762e+00 7.4740267e-01 1.0440187e+00 1.9213397e+00 1.4087466e+00 1.5433565e+00 9.2836103e-01 1.6392533e+00 7.7360126e-01 5.0592043e-01 8.5585239e-01 6.8961791e-01 9.5035453e-01 9.5498315e-01 7.0776547e-01 2.1812146e+00 2.2081369e+00 3.7427929e-01 1.1335961e+00 7.7885297e-01 2.0291151e+00 3.2352160e-01 1.0661822e+00 1.3165513e+00 3.7598397e-01 5.3588338e-01 8.2305664e-01 1.1437730e+00 1.4415965e+00 2.0902718e+00 8.7848692e-01 3.2352160e-01 7.0479928e-01 1.6865203e+00 1.1904611e+00 7.5791688e-01 5.5492130e-01 8.9207714e-01 1.0805899e+00 9.6271042e-01 5.7867728e-01 1.2256881e+00 1.2481462e+00 8.8358844e-01 4.0147421e-01 6.4405773e-01 1.0887986e+00 5.9426792e-01 4.4651726e-01 5.4292906e-01 7.0437330e-01 7.0776547e-01 3.2816937e-01 1.2134101e+00 9.8820253e-01 1.0733200e+00 8.1099042e-01 4.9857388e-01 7.2172678e-01 6.5223271e-01 6.3808075e-01 5.3665999e-01 6.9369532e-01 8.2305664e-01 6.2482915e-01 2.5251796e-01 7.1799256e-01 1.5895397e+00 6.2205176e-01 5.7257017e-01 5.6769031e-01 4.0438741e-01 1.7935777e+00 6.4755655e-01 1.6267976e+00 7.4777660e-01 1.4807336e+00 9.7270522e-01 1.3173487e+00 2.1839601e+00 1.2277129e+00 1.7938033e+00 1.1948932e+00 1.8448199e+00 8.7383925e-01 8.2305664e-01 1.1418127e+00 8.4591037e-01 1.2153720e+00 1.1640914e+00 9.1163729e-01 2.3638833e+00 2.4815883e+00 6.3861009e-01 1.3946921e+00 8.5434758e-01 2.2902807e+00 6.1151102e-01 1.2452704e+00 1.5325394e+00 6.0121055e-01 6.1092863e-01 1.1228379e+00 1.3752705e+00 1.7103060e+00 2.2560685e+00 1.1879078e+00 4.5470518e-01 9.0454394e-01 1.9729066e+00 1.3650300e+00 9.0521488e-01 6.0670504e-01 1.1454006e+00 1.3674559e+00 1.2262704e+00 7.4777660e-01 1.4820085e+00 1.4947429e+00 1.1729895e+00 7.3145860e-01 8.7720955e-01 1.2163831e+00 6.5633874e-01 2.1845981e-01 5.6769031e-01 7.4777660e-01 4.2538717e-01 9.5099818e-01 9.7994716e-01 1.0119857e+00 6.5223271e-01 8.3888121e-01 1.0038051e+00 5.9426792e-01 4.6472023e-01 6.0121055e-01 8.0326782e-01 9.2836103e-01 9.0876485e-01 3.7598397e-01 6.3861009e-01 1.5602029e+00 8.0326782e-01 7.0129382e-01 7.0043186e-01 2.0000000e-01 1.6208239e+00 7.0437330e-01 1.8619092e+00 9.6271042e-01 1.6849072e+00 1.3188999e+00 1.5860263e+00 2.4084158e+00 1.5142414e+00 2.0543079e+00 1.5230852e+00 1.9980352e+00 9.4375082e-01 1.0588560e+00 1.3035649e+00 1.0035600e+00 1.2499342e+00 1.2459608e+00 1.2225634e+00 2.5586145e+00 2.7210925e+00 8.9366705e-01 1.5500052e+00 1.0014633e+00 2.5139485e+00 6.9369532e-01 1.4790710e+00 1.7580510e+00 6.2660376e-01 7.0429250e-01 1.3804167e+00 1.5625881e+00 1.8966943e+00 2.3518757e+00 1.4139741e+00 8.0358695e-01 1.3075101e+00 2.0455018e+00 1.5153654e+00 1.2234738e+00 6.6539428e-01 1.2342162e+00 1.5049644e+00 1.1591754e+00 9.6271042e-01 1.7107332e+00 1.6328100e+00 1.1880428e+00 8.3812833e-01 1.0106392e+00 1.3267389e+00 8.9767734e-01 4.2538717e-01 6.2024833e-01 6.0181382e-01 1.1431021e+00 1.1948932e+00 1.2269747e+00 8.6361309e-01 8.2552685e-01 1.2002640e+00 6.5223271e-01 3.0811765e-01 7.1462831e-01 1.0067784e+00 1.1396406e+00 1.1147518e+00 5.0817745e-01 8.5335130e-01 1.7711504e+00 1.0085601e+00 9.0454394e-01 9.0277242e-01 4.0438741e-01 1.8140813e+00 9.1075311e-01 1.7412567e+00 9.8054887e-01 1.5527694e+00 1.2155004e+00 1.4692412e+00 2.2702600e+00 1.7126039e+00 1.9279661e+00 1.4238090e+00 1.8539569e+00 8.1558458e-01 9.5035453e-01 1.1763980e+00 1.0621081e+00 1.2047214e+00 1.1205013e+00 1.1134787e+00 2.4108292e+00 2.5851693e+00 9.6095130e-01 1.4178113e+00 1.0879524e+00 2.3751496e+00 6.0900723e-01 1.3570688e+00 1.6277043e+00 5.7015910e-01 6.6491075e-01 1.2652657e+00 1.4292566e+00 1.7566567e+00 2.1846001e+00 1.2961380e+00 7.1840099e-01 1.2329148e+00 1.8795815e+00 1.3897316e+00 1.1149070e+00 6.8757066e-01 1.0960883e+00 1.3785366e+00 1.0168833e+00 9.8054887e-01 1.5871961e+00 1.5043071e+00 1.0597541e+00 7.7033318e-01 8.8861541e-01 1.2058675e+00 8.9134001e-01 3.4583729e-01 8.1130291e-01 1.5090287e+00 1.4644753e+00 1.5150043e+00 1.1847335e+00 8.1385214e-01 1.4041085e+00 8.9917007e-01 3.0811765e-01 6.6539428e-01 1.2643026e+00 1.3836712e+00 1.3112758e+00 7.0784540e-01 1.1353806e+00 2.0777059e+00 1.2396136e+00 1.1498269e+00 1.1474460e+00 6.9006418e-01 2.1775976e+00 1.1752673e+00 1.4613032e+00 1.0391247e+00 1.1810170e+00 8.7504951e-01 1.1390131e+00 1.8670836e+00 1.9048338e+00 1.5205305e+00 1.0228981e+00 1.5676403e+00 6.7975091e-01 6.6539428e-01 8.7175869e-01 1.1533602e+00 1.2459608e+00 9.7730901e-01 7.5082357e-01 2.0536508e+00 2.1838261e+00 8.9095811e-01 1.1306949e+00 1.2394907e+00 1.9665910e+00 5.6769031e-01 1.0425476e+00 1.2324706e+00 6.4704320e-01 7.3851529e-01 9.5476489e-01 1.0198386e+00 1.3510699e+00 1.8411319e+00 1.0100718e+00 5.2942799e-01 9.3801395e-01 1.5112621e+00 1.2002762e+00 7.7763126e-01 8.2899253e-01 8.2305664e-01 1.1377990e+00 9.1663180e-01 1.0391247e+00 1.2653669e+00 1.2757312e+00 9.2288144e-01 6.4405773e-01 6.6827038e-01 1.0851476e+00 9.3048953e-01 7.7074935e-01 1.6569692e+00 1.5391185e+00 1.6134578e+00 1.2794849e+00 7.1504098e-01 1.3197776e+00 7.9613242e-01 3.2586371e-01 8.6165877e-01 1.2653669e+00 1.4028652e+00 1.2701139e+00 6.6384020e-01 1.2172454e+00 2.1489775e+00 1.2262704e+00 1.1578646e+00 1.1452867e+00 7.9613242e-01 2.2808589e+00 1.1959482e+00 1.1500393e+00 9.1075311e-01 9.3999899e-01 6.4806901e-01 8.5434758e-01 1.6806723e+00 1.8184542e+00 1.3334814e+00 8.5205778e-01 1.2702636e+00 3.4583729e-01 4.3456114e-01 5.6700421e-01 1.0389435e+00 1.0122141e+00 6.4290921e-01 5.0905001e-01 1.8419636e+00 1.9902374e+00 9.3801395e-01 8.1810461e-01 1.1069580e+00 1.7940242e+00 4.4651726e-01 7.4740267e-01 1.0346741e+00 5.1691876e-01 6.0121055e-01 6.6827038e-01 8.5205778e-01 1.1776640e+00 1.6778021e+00 7.0776547e-01 4.2667565e-01 7.8695083e-01 1.3400806e+00 8.6165877e-01 5.3022554e-01 7.0437330e-01 5.0592043e-01 8.1273630e-01 6.0670504e-01 9.1075311e-01 9.7270522e-01 9.4352681e-01 6.0551856e-01 5.7257017e-01 3.4378533e-01 7.5705927e-01 8.0064372e-01 1.0450018e+00 8.4812820e-01 9.3847194e-01 6.2988288e-01 6.0611244e-01 6.0060595e-01 5.0090417e-01 7.0784540e-01 6.2538346e-01 5.0592043e-01 6.6932542e-01 5.5492130e-01 1.5422108e-01 5.6075294e-01 1.4235605e+00 4.6472023e-01 4.2418962e-01 3.8776762e-01 2.8192292e-01 1.5978583e+00 4.5581864e-01 1.6256459e+00 6.5633874e-01 1.5986180e+00 1.1106412e+00 1.3708966e+00 2.3519748e+00 1.1147518e+00 1.9798165e+00 1.3654173e+00 1.8856245e+00 7.7033318e-01 8.5335130e-01 1.1771643e+00 6.8076724e-01 9.7270522e-01 1.0165138e+00 1.0391247e+00 2.5081056e+00 2.6437138e+00 7.6716823e-01 1.4120836e+00 6.1947990e-01 2.4692682e+00 4.8927739e-01 1.3077572e+00 1.7030709e+00 3.8934542e-01 4.4651726e-01 1.1594648e+00 1.5551984e+00 1.8760773e+00 2.3977345e+00 1.1868139e+00 6.2024833e-01 1.1056650e+00 2.0821572e+00 1.2794849e+00 1.0244319e+00 3.7427929e-01 1.1646003e+00 1.3139135e+00 1.1119327e+00 6.5633874e-01 1.5344007e+00 1.4333755e+00 1.0386594e+00 6.3735887e-01 8.2372435e-01 1.0901359e+00 6.2081167e-01 3.4583729e-01 2.8192292e-01 4.1586001e-01 1.6237100e+00 1.0540105e+00 1.1816401e+00 1.4110536e+00 9.8450810e-01 6.6432544e-01 5.3665999e-01 9.0454394e-01 1.1389644e+00 5.0905001e-01 7.1799256e-01 7.1504098e-01 7.3813096e-01 7.2783368e-01 8.7021234e-01 6.9006418e-01 6.2482915e-01 2.6619364e+00 1.6754669e+00 2.5821869e+00 2.1421834e+00 2.4107926e+00 3.3209792e+00 1.2036484e+00 2.9531363e+00 2.3720162e+00 2.8824828e+00 1.7671769e+00 1.8842354e+00 2.1714345e+00 1.6176764e+00 1.8723516e+00 2.0134817e+00 2.0676751e+00 3.4808493e+00 3.6264553e+00 1.5230852e+00 2.4135751e+00 1.5351194e+00 3.4281547e+00 1.4948868e+00 2.3378347e+00 2.6680945e+00 1.3977032e+00 1.4832928e+00 2.1976523e+00 2.4795532e+00 2.8157449e+00 3.2956170e+00 2.2214438e+00 1.6336229e+00 2.1064881e+00 2.9775369e+00 2.2994849e+00 2.0603946e+00 1.3916739e+00 2.1189748e+00 2.3204945e+00 1.9672068e+00 1.6754669e+00 2.5635110e+00 2.4436082e+00 1.9753271e+00 1.6077195e+00 1.8374244e+00 2.0981613e+00 1.6585806e+00 1.2418578e-01 3.7598397e-01 1.3405045e+00 8.3812833e-01 1.1437669e+00 1.3915412e+00 8.9095811e-01 6.2538346e-01 2.5251796e-01 6.0611244e-01 9.6791960e-01 3.4583729e-01 6.2205176e-01 4.5581864e-01 6.5223271e-01 5.7867728e-01 8.2619017e-01 8.2654509e-01 4.6472023e-01 2.4061696e+00 1.3868130e+00 2.3985329e+00 1.8751958e+00 2.1591630e+00 3.1397173e+00 8.9852394e-01 2.7599154e+00 2.1335771e+00 2.7193241e+00 1.6021202e+00 1.6415861e+00 1.9851797e+00 1.3336069e+00 1.6224878e+00 1.8082080e+00 1.8350829e+00 3.3292261e+00 3.4297053e+00 1.2340567e+00 2.2298076e+00 1.2654843e+00 3.2489933e+00 1.2770118e+00 2.1337366e+00 2.4988032e+00 1.1786349e+00 1.2545301e+00 1.9393053e+00 2.3288114e+00 2.6536325e+00 3.2010862e+00 1.9648876e+00 1.3964978e+00 1.8188234e+00 2.8588023e+00 2.0766308e+00 1.8216743e+00 1.1634384e+00 1.9698860e+00 2.1147926e+00 1.8660999e+00 1.3868130e+00 2.3472906e+00 2.2417376e+00 1.8131734e+00 1.3752391e+00 1.6372749e+00 1.8856245e+00 1.3922071e+00 4.0176783e-01 1.4467170e+00 9.3049742e-01 1.2002762e+00 1.4411886e+00 9.4375082e-01 6.6432544e-01 3.7427929e-01 7.0784540e-01 1.0466623e+00 4.0176783e-01 5.6700421e-01 5.5492130e-01 6.9728513e-01 6.4405773e-01 8.7170815e-01 7.3535471e-01 5.3309112e-01 2.5193321e+00 1.5039793e+00 2.4895662e+00 1.9791576e+00 2.2673478e+00 3.2268480e+00 1.0014633e+00 2.8466240e+00 2.2303173e+00 2.8133325e+00 1.6962564e+00 1.7458338e+00 2.0805039e+00 1.4552205e+00 1.7454671e+00 1.9165907e+00 1.9332869e+00 3.4131779e+00 3.5208177e+00 1.3379696e+00 2.3275025e+00 1.3866044e+00 3.3340853e+00 1.3784233e+00 2.2317775e+00 2.5822073e+00 1.2828332e+00 1.3595018e+00 2.0485193e+00 2.4057315e+00 2.7355566e+00 3.2722484e+00 2.0762069e+00 1.4905436e+00 1.9191337e+00 2.9375895e+00 2.1864773e+00 1.9213461e+00 1.2702636e+00 2.0582667e+00 2.2206505e+00 1.9521784e+00 1.5039793e+00 2.4497583e+00 2.3480580e+00 1.9124077e+00 1.4817438e+00 1.7372199e+00 1.9937367e+00 1.5016471e+00 1.2122249e+00 6.7975091e-01 8.4050231e-01 1.0796583e+00 6.6539428e-01 3.4583729e-01 3.2816937e-01 5.2942799e-01 7.3145860e-01 1.2418578e-01 9.1163729e-01 3.2586371e-01 3.7427929e-01 3.2816937e-01 5.0592043e-01 1.0122141e+00 2.1845981e-01 2.2481791e+00 1.2631020e+00 2.1874158e+00 1.7295419e+00 1.9965608e+00 2.9333950e+00 1.0072663e+00 2.5632712e+00 1.9670002e+00 2.4858980e+00 1.3655398e+00 1.4724669e+00 1.7716601e+00 1.2125198e+00 1.4903113e+00 1.6105641e+00 1.6572339e+00 3.0940465e+00 3.2344170e+00 1.1332978e+00 2.0129643e+00 1.1341579e+00 3.0445772e+00 1.0864449e+00 1.9287276e+00 2.2802541e+00 9.8820253e-01 1.0688498e+00 1.7838044e+00 2.1039999e+00 2.4365934e+00 2.9349538e+00 1.8084630e+00 1.2266837e+00 1.7026843e+00 2.6138892e+00 1.8911946e+00 1.6476803e+00 9.7949166e-01 1.7305789e+00 1.9168750e+00 1.6065247e+00 1.2631020e+00 2.1541966e+00 2.0395401e+00 1.5896248e+00 1.1996741e+00 1.4306494e+00 1.6928004e+00 1.2436109e+00 7.5791688e-01 8.1242502e-01 7.6625946e-01 7.5976039e-01 1.0289803e+00 1.1332978e+00 7.9613242e-01 5.3665999e-01 1.1149070e+00 1.9007091e+00 9.2836103e-01 9.3610001e-01 9.1858284e-01 8.1638392e-01 2.1493214e+00 1.0132664e+00 1.1680362e+00 3.2352160e-01 1.2372418e+00 5.4292906e-01 8.7170815e-01 1.9366254e+00 1.1486378e+00 1.5564198e+00 8.7420176e-01 1.5682049e+00 6.6491075e-01 4.5470518e-01 8.8358844e-01 4.5581864e-01 8.0326782e-01 7.9878917e-01 5.9426792e-01 2.1460410e+00 2.1931311e+00 5.0180477e-01 1.0950112e+00 5.0592043e-01 2.0545952e+00 3.4378533e-01 9.3923979e-01 1.3512935e+00 3.4583729e-01 3.4583729e-01 6.6539428e-01 1.2670555e+00 1.5369942e+00 2.1465859e+00 7.2526325e-01 3.0546431e-01 5.0991930e-01 1.8206746e+00 9.8054887e-01 5.7015910e-01 3.8776762e-01 9.6664346e-01 9.9089002e-01 1.0262547e+00 3.2352160e-01 1.1127329e+00 1.1166017e+00 8.7848692e-01 3.8934542e-01 5.8914551e-01 8.8062848e-01 3.2586371e-01 6.4755655e-01 1.3011270e+00 1.0122141e+00 4.2538717e-01 6.2660376e-01 4.4651726e-01 7.0086313e-01 6.3735887e-01 1.2926374e+00 4.0176783e-01 4.2288438e-01 3.8934542e-01 8.0619006e-01 1.5230852e+00 4.6472023e-01 1.6933635e+00 7.0233835e-01 1.9584922e+00 1.2594846e+00 1.5411691e+00 2.6785768e+00 6.2605182e-01 2.3003744e+00 1.6284481e+00 2.1882128e+00 1.1729895e+00 1.1500393e+00 1.5577059e+00 7.1881659e-01 9.8985697e-01 1.2389598e+00 1.3108618e+00 2.8217641e+00 2.9357847e+00 9.3026633e-01 1.7457596e+00 5.7867728e-01 2.7994225e+00 9.3610001e-01 1.5801828e+00 2.0692197e+00 8.2384013e-01 7.4740267e-01 1.3410314e+00 1.9783833e+00 2.2683159e+00 2.8062463e+00 1.3610783e+00 9.7249562e-01 1.1868139e+00 2.5244698e+00 1.3852951e+00 1.2460824e+00 6.3808075e-01 1.6077195e+00 1.5873000e+00 1.5826476e+00 7.0233835e-01 1.7831878e+00 1.6667819e+00 1.4276261e+00 9.9519977e-01 1.1984588e+00 1.1903343e+00 7.0386584e-01 7.1840099e-01 1.1107977e+00 5.8624446e-01 9.8495853e-01 8.7504951e-01 4.1586001e-01 8.7720955e-01 1.5824669e+00 7.5976039e-01 5.5160819e-01 5.7741073e-01 5.4292906e-01 1.6736318e+00 6.7975091e-01 1.5883552e+00 8.2552685e-01 1.5948732e+00 1.1332978e+00 1.3595997e+00 2.3503762e+00 1.2554784e+00 1.9862884e+00 1.4596621e+00 1.8378727e+00 7.2852070e-01 9.6204649e-01 1.1697902e+00 9.6664346e-01 9.6271042e-01 9.5676647e-01 1.0496979e+00 2.4751922e+00 2.6546397e+00 1.2224367e+00 1.3843268e+00 7.2343175e-01 2.4751922e+00 7.5082357e-01 1.2832075e+00 1.7000773e+00 6.2988288e-01 5.0592043e-01 1.1833351e+00 1.5613251e+00 1.8886923e+00 2.3645560e+00 1.2016233e+00 7.5791688e-01 1.2125198e+00 2.0748074e+00 1.2155370e+00 1.0244319e+00 4.5470518e-01 1.1485394e+00 1.2770118e+00 1.0720678e+00 8.2552685e-01 1.5113992e+00 1.3835368e+00 1.0035600e+00 9.5571254e-01 8.2234151e-01 1.0120221e+00 6.5223271e-01 8.3888121e-01 1.1486378e+00 1.2994764e+00 1.2307737e+00 6.0181382e-01 1.0483827e+00 1.9867752e+00 1.1408504e+00 1.0391247e+00 1.0361698e+00 5.7867728e-01 2.0669733e+00 1.0646687e+00 1.4623898e+00 9.5866719e-01 1.2497790e+00 9.3048953e-01 1.1765359e+00 1.9666356e+00 1.8175297e+00 1.6242657e+00 1.1520347e+00 1.5603665e+00 5.7324170e-01 7.0233835e-01 8.8887100e-01 1.0919404e+00 1.1355826e+00 8.9712482e-01 8.1385214e-01 2.1052360e+00 2.2845234e+00 1.0166932e+00 1.1341579e+00 1.1332978e+00 2.0738150e+00 5.3309112e-01 1.0588560e+00 1.3224963e+00 5.5492130e-01 6.2538346e-01 9.8450810e-01 1.1271488e+00 1.4561933e+00 1.8911946e+00 1.0230441e+00 5.2574978e-01 1.0056742e+00 1.5916843e+00 1.1355826e+00 8.2105460e-01 7.1504098e-01 8.1527569e-01 1.1176720e+00 8.2899253e-01 9.5866719e-01 1.2943100e+00 1.2429818e+00 8.5205778e-01 7.0233835e-01 6.2660376e-01 9.8054887e-01 8.3649708e-01 8.7822463e-01 8.2899253e-01 8.1099042e-01 7.0826681e-01 5.8914551e-01 1.5042268e+00 7.3813096e-01 8.1558458e-01 7.4855857e-01 6.0121055e-01 1.6260946e+00 7.0386584e-01 1.8605327e+00 8.8503502e-01 1.6493191e+00 1.2601890e+00 1.5390703e+00 2.3592515e+00 1.4088394e+00 1.9940473e+00 1.4245508e+00 2.0716002e+00 1.1004436e+00 9.8820253e-01 1.2927814e+00 9.0056222e-01 1.2208301e+00 1.3194807e+00 1.1996741e+00 2.6153308e+00 2.6539963e+00 6.2538346e-01 1.5687169e+00 9.5271386e-01 2.4562038e+00 6.6491075e-01 1.5249255e+00 1.7538274e+00 6.6539428e-01 8.2619017e-01 1.3131724e+00 1.5409345e+00 1.8470010e+00 2.4533073e+00 1.3504603e+00 7.7039952e-01 1.2057554e+00 2.0352149e+00 1.6006330e+00 1.2331989e+00 8.0660588e-01 1.2747177e+00 1.5040391e+00 1.2426449e+00 8.8503502e-01 1.7019078e+00 1.6700310e+00 1.2144845e+00 7.4855857e-01 1.0401425e+00 1.4600567e+00 9.3296062e-01 5.0180477e-01 4.4651726e-01 6.2149089e-01 4.1586001e-01 1.0078327e+00 3.0275928e-01 1.4096146e-01 1.4096146e-01 6.0611244e-01 1.1536782e+00 2.0656129e-01 2.0491051e+00 1.0646687e+00 2.0979729e+00 1.5528443e+00 1.8279176e+00 2.8469870e+00 8.2234151e-01 2.4692682e+00 1.8399871e+00 2.3632803e+00 1.2493717e+00 1.3312249e+00 1.6756749e+00 1.0425476e+00 1.3092012e+00 1.4505265e+00 1.5123788e+00 2.9884772e+00 3.1361386e+00 1.0755693e+00 1.9017004e+00 9.3801395e-01 2.9635613e+00 9.8054887e-01 1.7833384e+00 2.1970231e+00 8.6513410e-01 8.9742724e-01 1.6159903e+00 2.0524973e+00 2.3760856e+00 2.8811560e+00 1.6399646e+00 1.0934620e+00 1.5205305e+00 2.5867433e+00 1.6928004e+00 1.4836711e+00 7.9580667e-01 1.6659943e+00 1.7839298e+00 1.5792930e+00 1.0646687e+00 2.0113485e+00 1.8901379e+00 1.5067717e+00 1.0950112e+00 1.3129189e+00 1.4875372e+00 1.0389435e+00 4.0293660e-01 8.0499049e-01 3.0546431e-01 7.8197925e-01 2.5251796e-01 5.1691876e-01 4.2538717e-01 7.4740267e-01 1.0181000e+00 3.2586371e-01 2.1717162e+00 1.1533602e+00 2.2234347e+00 1.6690840e+00 1.9433381e+00 2.9713636e+00 7.2486328e-01 2.5929835e+00 1.9489982e+00 2.5241649e+00 1.4089364e+00 1.4425304e+00 1.8012344e+00 1.0919712e+00 1.3726860e+00 1.5830057e+00 1.6408468e+00 3.1546522e+00 3.2544456e+00 1.0406064e+00 2.0352149e+00 1.0168833e+00 3.0859269e+00 1.0901359e+00 1.9325796e+00 2.3348454e+00 9.8054887e-01 1.0379132e+00 1.7250039e+00 2.1825260e+00 2.4995667e+00 3.0529994e+00 1.7458338e+00 1.2167151e+00 1.6214915e+00 2.7108297e+00 1.8418195e+00 1.6195190e+00 9.3847194e-01 1.7995863e+00 1.9034198e+00 1.7022897e+00 1.1533602e+00 2.1413027e+00 2.0233319e+00 1.6211869e+00 1.1770266e+00 1.4411886e+00 1.6502968e+00 1.1644030e+00 6.5633874e-01 4.4417983e-01 1.1332978e+00 2.1845981e-01 4.2538717e-01 3.4583729e-01 7.1504098e-01 1.4080793e+00 3.4583729e-01 1.8866180e+00 8.7848692e-01 1.9819543e+00 1.3312249e+00 1.6523803e+00 2.6988671e+00 6.9006418e-01 2.3100474e+00 1.6455737e+00 2.2934334e+00 1.2426449e+00 1.1905954e+00 1.5932297e+00 8.9095811e-01 1.2681309e+00 1.4065584e+00 1.3487634e+00 2.8835141e+00 2.9705897e+00 7.3805807e-01 1.8205354e+00 8.5462626e-01 2.8117234e+00 9.3049742e-01 1.6679957e+00 2.0758969e+00 8.4050231e-01 8.3060013e-01 1.4419145e+00 1.9521697e+00 2.2599493e+00 2.8310619e+00 1.4807336e+00 9.4558103e-01 1.2404967e+00 2.5235709e+00 1.6070713e+00 1.3102444e+00 7.5705927e-01 1.6281130e+00 1.7021627e+00 1.6240596e+00 8.7848692e-01 1.8790831e+00 1.8155245e+00 1.5040391e+00 1.0014633e+00 1.2481462e+00 1.4334902e+00 8.6165877e-01 6.6827038e-01 1.5475692e+00 5.8914551e-01 5.0503591e-01 4.9857388e-01 3.0811765e-01 1.7160413e+00 5.7324170e-01 1.5795964e+00 6.5648056e-01 1.4967461e+00 1.0182895e+00 1.3034549e+00 2.2380042e+00 1.2266837e+00 1.8619092e+00 1.2701139e+00 1.8007564e+00 7.2852070e-01 7.9016429e-01 1.0989735e+00 7.5705927e-01 1.0406064e+00 1.0184370e+00 9.3999899e-01 2.3886514e+00 2.5357185e+00 8.2684479e-01 1.3424112e+00 7.0776547e-01 2.3522207e+00 4.8927739e-01 1.2205493e+00 1.5832517e+00 4.2667565e-01 4.4417983e-01 1.0974061e+00 1.4319225e+00 1.7587110e+00 2.2711652e+00 1.1390131e+00 5.1691876e-01 1.0163549e+00 1.9782498e+00 1.2532075e+00 9.2859317e-01 4.1449626e-01 1.0852663e+00 1.2786117e+00 1.0887986e+00 6.5648056e-01 1.4596621e+00 1.3991741e+00 1.0313560e+00 6.6932542e-01 7.7553525e-01 1.0741917e+00 5.7257017e-01 9.4558103e-01 2.5651975e-01 4.1449626e-01 3.2816937e-01 4.8135521e-01 1.0908017e+00 2.1845981e-01 2.1701312e+00 1.1752673e+00 2.1084262e+00 1.6352583e+00 1.9101184e+00 2.8518881e+00 9.7825559e-01 2.4781934e+00 1.8745369e+00 2.4235816e+00 1.3078976e+00 1.3842113e+00 1.6965018e+00 1.1329323e+00 1.4319225e+00 1.5496439e+00 1.5691346e+00 3.0266197e+00 3.1503439e+00 1.0244319e+00 1.9425540e+00 1.0627606e+00 2.9623467e+00 1.0056742e+00 1.8539828e+00 2.2026387e+00 9.1163729e-01 9.9475949e-01 1.6952454e+00 2.0275673e+00 2.3581240e+00 2.8793190e+00 1.7227544e+00 1.1332978e+00 1.6029963e+00 2.5482247e+00 1.8278913e+00 1.5611067e+00 9.1163729e-01 1.6653066e+00 1.8462692e+00 1.5634147e+00 1.1752673e+00 2.0757295e+00 1.9739212e+00 1.5320003e+00 1.1179743e+00 1.3567326e+00 1.6362950e+00 1.1594648e+00 9.9475949e-01 1.1004436e+00 1.0720678e+00 1.4108494e+00 3.2816937e-01 9.8054887e-01 2.9240179e+00 1.9013501e+00 3.0016960e+00 2.4403742e+00 2.7185134e+00 3.7481490e+00 1.2643026e+00 3.3676733e+00 2.7249658e+00 3.2951180e+00 2.1701459e+00 2.2231652e+00 2.5778372e+00 1.8193838e+00 2.0594742e+00 2.3383666e+00 2.4210417e+00 3.9277558e+00 4.0319248e+00 1.8011138e+00 2.8105150e+00 1.7324239e+00 3.8595400e+00 1.8657887e+00 2.7098209e+00 3.1083486e+00 1.7551534e+00 1.8090259e+00 2.5002193e+00 2.9463843e+00 3.2688068e+00 3.8087204e+00 2.5182043e+00 1.9933741e+00 2.3692479e+00 3.4705738e+00 2.5885717e+00 2.3959721e+00 1.7005893e+00 2.5655126e+00 2.6734142e+00 2.4311441e+00 1.9013501e+00 2.9205331e+00 2.7876433e+00 2.3735872e+00 1.9516947e+00 2.2170194e+00 2.3879674e+00 1.9213461e+00 3.0546431e-01 2.0656129e-01 6.0611244e-01 1.2246352e+00 1.4096146e-01 1.9777274e+00 9.7249562e-01 2.0297383e+00 1.4616539e+00 1.7458338e+00 2.7737198e+00 7.5082357e-01 2.3931826e+00 1.7478866e+00 2.3213742e+00 1.2131545e+00 1.2498134e+00 1.6130724e+00 9.3824087e-01 1.2565757e+00 1.4029855e+00 1.4325768e+00 2.9414941e+00 3.0577671e+00 8.7720955e-01 1.8438146e+00 8.6956871e-01 2.8895427e+00 9.1310225e-01 1.7228354e+00 2.1321061e+00 8.0499049e-01 8.3345577e-01 1.5318874e+00 1.9898963e+00 2.3090270e+00 2.8491218e+00 1.5587730e+00 1.0122141e+00 1.4162017e+00 2.5326059e+00 1.6463627e+00 1.4047678e+00 7.3805807e-01 1.6165635e+00 1.7228488e+00 1.5520745e+00 9.7249562e-01 1.9420274e+00 1.8372522e+00 1.4628493e+00 1.0030700e+00 1.2523175e+00 1.4531349e+00 9.5571254e-01 1.2418578e-01 5.0270183e-01 1.2603076e+00 2.1269358e-01 1.9932786e+00 1.0168833e+00 1.9946994e+00 1.4557537e+00 1.7495699e+00 2.7337358e+00 9.0575661e-01 2.3519748e+00 1.7324239e+00 2.2796281e+00 1.1801240e+00 1.2450709e+00 1.5872286e+00 1.0267435e+00 1.3336069e+00 1.4152303e+00 1.4096199e+00 2.8778917e+00 3.0268604e+00 1.0067464e+00 1.8215944e+00 9.3824087e-01 2.8471683e+00 9.0658670e-01 1.6933635e+00 2.0801243e+00 8.0758367e-01 8.3783744e-01 1.5390703e+00 1.9310038e+00 2.2599493e+00 2.7651778e+00 1.5728839e+00 9.7949166e-01 1.4165336e+00 2.4822593e+00 1.6510537e+00 1.3842113e+00 7.5755387e-01 1.5773217e+00 1.7253276e+00 1.5255331e+00 1.0168833e+00 1.9287244e+00 1.8366596e+00 1.4599710e+00 1.0339865e+00 1.2378278e+00 1.4537266e+00 9.7249562e-01 5.0090417e-01 1.2507669e+00 1.2418578e-01 1.9589833e+00 9.7270522e-01 1.9792779e+00 1.4437673e+00 1.7230625e+00 2.7260686e+00 8.6012420e-01 2.3478326e+00 1.7190893e+00 2.2590861e+00 1.1454006e+00 1.2174316e+00 1.5614941e+00 9.5476489e-01 1.2555979e+00 1.3635198e+00 1.3969297e+00 2.8759951e+00 3.0161959e+00 9.4558103e-01 1.7925890e+00 8.6983677e-01 2.8416706e+00 8.6513410e-01 1.6742876e+00 2.0756986e+00 7.5871717e-01 7.9613242e-01 1.5107481e+00 1.9286915e+00 2.2531942e+00 2.7669732e+00 1.5384446e+00 9.7270522e-01 1.4111029e+00 2.4671050e+00 1.6105641e+00 1.3717027e+00 7.0429250e-01 1.5517600e+00 1.6837214e+00 1.4807336e+00 9.7270522e-01 1.9032219e+00 1.7953587e+00 1.4097072e+00 9.7855477e-01 1.2036484e+00 1.4110536e+00 9.4309624e-01 1.5090287e+00 5.0905001e-01 1.8619092e+00 9.1163729e-01 1.7230625e+00 1.3188999e+00 1.5883552e+00 2.4588872e+00 1.3193952e+00 2.0946464e+00 1.5338492e+00 2.0321740e+00 9.5099818e-01 1.0604511e+00 1.3277861e+00 9.3296062e-01 1.2221471e+00 1.2470767e+00 1.2266837e+00 2.6106370e+00 2.7667028e+00 8.7420176e-01 1.5746612e+00 8.9852394e-01 2.5679581e+00 6.9369532e-01 1.4905436e+00 1.8028753e+00 6.2149089e-01 6.9006418e-01 1.3813076e+00 1.6199747e+00 1.9552274e+00 2.4344864e+00 1.4148192e+00 8.0358695e-01 1.3039319e+00 2.1287551e+00 1.5153654e+00 1.2246352e+00 6.2660376e-01 1.2741904e+00 1.5160122e+00 1.2047214e+00 9.1163729e-01 1.7242097e+00 1.6420607e+00 1.2064640e+00 8.3812833e-01 1.0168833e+00 1.3257654e+00 8.6137722e-01 1.1533602e+00 3.1382691e+00 2.1485328e+00 3.1786790e+00 2.6799312e+00 2.9354140e+00 3.9353144e+00 1.5252485e+00 3.5658557e+00 2.9475994e+00 3.4455976e+00 2.3167786e+00 2.4320363e+00 2.7459122e+00 2.0598189e+00 2.2501104e+00 2.5013206e+00 2.6321552e+00 4.0910078e+00 4.2293986e+00 2.0521052e+00 2.9731112e+00 1.9619929e+00 4.0491656e+00 2.0481101e+00 2.8946126e+00 3.2860707e+00 1.9342059e+00 2.0025214e+00 2.7210925e+00 3.1148548e+00 3.4424959e+00 3.9360563e+00 2.7333517e+00 2.2078200e+00 2.6383936e+00 3.6047462e+00 2.7713578e+00 2.6121617e+00 1.8925840e+00 2.7075477e+00 2.8419886e+00 2.5215069e+00 2.1485328e+00 3.1102248e+00 2.9517129e+00 2.5041493e+00 2.1435335e+00 2.3887866e+00 2.5633372e+00 2.1544995e+00 2.0467316e+00 1.0576043e+00 2.0528819e+00 1.5379283e+00 1.8096161e+00 2.8029161e+00 8.6012420e-01 2.4272793e+00 1.8028753e+00 2.3372930e+00 1.2144845e+00 1.2985682e+00 1.6312555e+00 1.0166932e+00 1.3073038e+00 1.4333755e+00 1.4843487e+00 2.9588514e+00 3.0950947e+00 9.7949166e-01 1.8647706e+00 9.3615100e-01 2.9181741e+00 9.3049742e-01 1.7594421e+00 2.1522124e+00 8.2342214e-01 8.7720955e-01 1.5965946e+00 1.9973159e+00 2.3235032e+00 2.8379387e+00 1.6211988e+00 1.0588560e+00 1.5076049e+00 2.5254157e+00 1.6945041e+00 1.4637418e+00 7.8197925e-01 1.6130724e+00 1.7539916e+00 1.5193574e+00 1.0576043e+00 1.9849009e+00 1.8691652e+00 1.4607586e+00 1.0375119e+00 1.2741904e+00 1.4947429e+00 1.0361698e+00 1.0621081e+00 8.3649708e-01 7.6590510e-01 4.0176783e-01 1.3455136e+00 1.8827665e+00 1.1093572e+00 9.5676647e-01 9.0852141e-01 9.4309624e-01 8.9852394e-01 6.8076724e-01 1.2002762e+00 9.7825559e-01 7.0479928e-01 7.8197925e-01 1.4637418e+00 1.5390703e+00 1.4628493e+00 6.2538346e-01 1.2208301e+00 1.4754770e+00 1.2162549e+00 5.2574978e-01 1.0104465e+00 1.2832075e+00 1.1810170e+00 6.1947990e-01 1.1242402e+00 1.1718516e+00 1.6295015e+00 5.8914551e-01 1.2063335e+00 1.1879206e+00 1.4041085e+00 4.0293660e-01 7.7074935e-01 1.2709820e+00 7.7869083e-01 5.0592043e-01 9.7441804e-01 1.0621081e+00 5.0991930e-01 4.4417983e-01 8.3888121e-01 1.1770266e+00 8.6361309e-01 6.0670504e-01 1.0324775e+00 1.3844611e+00 6.2660376e-01 8.8695363e-01 2.0692197e+00 9.7441804e-01 1.6995747e+00 1.0122141e+00 1.6372749e+00 7.6752131e-01 6.0551856e-01 1.0244319e+00 2.1845981e-01 5.0090417e-01 7.2852070e-01 7.4777660e-01 2.2645802e+00 2.3019759e+00 5.7324170e-01 1.1833351e+00 2.5651975e-01 2.1907335e+00 5.0905001e-01 1.0330459e+00 1.5124582e+00 4.4651726e-01 3.8934542e-01 6.9369532e-01 1.4517959e+00 1.7036156e+00 2.3021295e+00 7.0429250e-01 5.6700421e-01 6.3977563e-01 1.9782093e+00 8.7229670e-01 6.8801986e-01 3.8934542e-01 1.1199472e+00 9.9519977e-01 1.1263042e+00 0.0000000e+00 1.1697902e+00 1.0851476e+00 9.2859317e-01 5.0905001e-01 7.1504098e-01 7.7763126e-01 3.0546431e-01 8.2135873e-01 6.0121055e-01 7.6716823e-01 2.3579605e+00 4.5581864e-01 5.8914551e-01 6.5223271e-01 8.9095811e-01 8.2552685e-01 4.4417983e-01 1.5124582e+00 1.3844611e+00 8.1810461e-01 6.6384020e-01 1.0516761e+00 1.0733200e+00 1.3725949e+00 3.0844217e-01 1.6183051e+00 8.9095811e-01 1.1426203e+00 4.5470518e-01 3.2816937e-01 1.2604558e+00 1.2459608e+00 7.1799256e-01 5.0180477e-01 3.6171588e-01 1.0269295e+00 7.1840099e-01 1.0531192e+00 1.1093572e+00 6.1092863e-01 8.4591037e-01 7.4777660e-01 1.3693737e+00 5.0905001e-01 4.8135521e-01 8.0619006e-01 1.3844611e+00 3.4378533e-01 5.3309112e-01 7.3813096e-01 1.0901359e+00 8.1273630e-01 9.6141901e-01 1.2978356e+00 4.2667565e-01 1.4573287e+00 1.5826638e+00 1.0904758e+00 5.0503591e-01 1.1258723e+00 5.4292906e-01 3.2816937e-01 5.3022554e-01 7.7869083e-01 7.5871717e-01 5.5492130e-01 2.1269358e-01 1.6596342e+00 1.6919202e+00 8.3280511e-01 7.0429250e-01 8.7202528e-01 1.5772389e+00 7.0394675e-01 5.2655962e-01 9.2836103e-01 8.0064372e-01 7.0437330e-01 3.0546431e-01 9.0478973e-01 1.1271488e+00 1.7235501e+00 4.0293660e-01 5.2942799e-01 4.5470518e-01 1.4317371e+00 6.8917100e-01 2.1269358e-01 8.1099042e-01 6.2988288e-01 6.5172743e-01 7.6166891e-01 6.2660376e-01 6.5648056e-01 7.6625946e-01 6.1947990e-01 6.4755655e-01 4.2667565e-01 6.2660376e-01 5.6700421e-01 1.2113327e+00 1.8396098e+00 8.7504951e-01 5.7257017e-01 8.3280511e-01 7.0784540e-01 5.5492130e-01 3.7427929e-01 1.0284501e+00 8.7420176e-01 5.0991930e-01 4.4417983e-01 1.4089719e+00 1.4387122e+00 1.1127329e+00 4.1586001e-01 1.1205013e+00 1.3344634e+00 9.3048953e-01 3.2816937e-01 7.4164639e-01 1.0244319e+00 9.3999899e-01 2.5651975e-01 8.1242502e-01 9.1858284e-01 1.4955532e+00 2.5251796e-01 8.7420176e-01 8.5335130e-01 1.2045536e+00 4.3691963e-01 4.4651726e-01 1.0480665e+00 4.9857388e-01 2.8507955e-01 7.3535471e-01 8.8695363e-01 3.2816937e-01 3.8934542e-01 6.0611244e-01 8.6361309e-01 6.0551856e-01 5.2655962e-01 8.3783744e-01 3.0351721e+00 4.2418962e-01 1.0941064e+00 7.5705927e-01 1.6559784e+00 1.5582387e+00 1.2112034e+00 2.1967372e+00 2.0692197e+00 1.5564198e+00 1.3693737e+00 8.0096515e-01 4.5581864e-01 2.0330276e+00 1.0137836e+00 2.3142399e+00 2.1845981e-01 1.9017011e+00 1.1228379e+00 6.6827038e-01 2.0223026e+00 1.9969203e+00 1.3792358e+00 8.7478495e-01 5.2371571e-01 8.1385214e-01 1.3793330e+00 1.7664528e+00 1.6569692e+00 5.0905001e-01 1.4644753e+00 1.4341959e+00 2.1204309e+00 1.2632199e+00 1.1880428e+00 1.5405106e+00 2.0692197e+00 9.4009473e-01 1.1355826e+00 1.4992973e+00 1.8311457e+00 1.5765737e+00 1.6311692e+00 1.9969203e+00 2.6670272e+00 1.9783833e+00 2.5784641e+00 1.6572339e+00 1.5613865e+00 1.9842916e+00 8.6110333e-01 1.0720678e+00 1.6180482e+00 1.7140774e+00 3.2123303e+00 3.2542669e+00 1.1332978e+00 2.1449779e+00 7.5976039e-01 3.1540626e+00 1.4088394e+00 1.9797139e+00 2.4825886e+00 1.3075101e+00 1.2330392e+00 1.6629594e+00 2.4148300e+00 2.6746409e+00 3.2573703e+00 1.6686069e+00 1.4322723e+00 1.4341959e+00 2.9471490e+00 1.6864366e+00 1.6385322e+00 1.1320702e+00 2.0632091e+00 1.9468380e+00 2.0389505e+00 9.7441804e-01 2.1303950e+00 2.0095672e+00 1.8517858e+00 1.4168607e+00 1.6483152e+00 1.5346983e+00 1.0866092e+00 7.2486328e-01 8.7202528e-01 1.2988558e+00 1.1847335e+00 8.6137722e-01 1.8265471e+00 1.7177705e+00 1.2107055e+00 9.9368623e-01 9.5866719e-01 7.3805807e-01 1.6506221e+00 7.3805807e-01 1.9449573e+00 5.0592043e-01 1.5350426e+00 7.8695083e-01 3.7427929e-01 1.6553809e+00 1.6249178e+00 1.0168833e+00 5.0991930e-01 2.1845981e-01 9.7270522e-01 1.0264409e+00 1.3817041e+00 1.2768639e+00 5.7324170e-01 1.1634384e+00 1.0611732e+00 1.7483574e+00 9.3048953e-01 9.0056222e-01 1.2340567e+00 1.6995747e+00 6.8076724e-01 9.1883539e-01 1.1718516e+00 1.4616896e+00 1.2125198e+00 1.2951888e+00 1.6249178e+00 1.2028939e+00 8.7420176e-01 5.3665999e-01 5.5492130e-01 1.1340084e+00 1.0720678e+00 8.3345577e-01 5.3588338e-01 1.5520745e+00 1.3258714e+00 9.5099818e-01 7.7074935e-01 1.2604558e+00 1.1891470e+00 9.2264612e-01 8.1099042e-01 7.7039952e-01 1.0389435e+00 1.0054794e+00 4.3456114e-01 6.2605182e-01 7.2823007e-01 1.5784191e+00 4.8927739e-01 7.5976039e-01 6.5223271e-01 1.0692258e+00 9.8985697e-01 6.3808075e-01 1.1178200e+00 6.6827038e-01 7.4855857e-01 8.6513410e-01 1.0122141e+00 7.6787403e-01 9.3615100e-01 7.5835500e-01 8.2654509e-01 6.9728513e-01 9.9519977e-01 9.7356960e-01 1.1306887e+00 1.2197188e+00 8.0353565e-01 1.7933375e+00 1.5916843e+00 1.0118409e+00 1.0089164e+00 7.0776547e-01 1.1591754e+00 1.8437762e+00 5.3309112e-01 1.8278913e+00 9.6838716e-01 1.4843324e+00 6.3735887e-01 7.3496673e-01 1.5570415e+00 1.5003972e+00 1.0421979e+00 9.7759114e-01 8.9070384e-01 7.8197925e-01 1.0329598e+00 1.4388174e+00 1.5204340e+00 6.9325418e-01 9.4309624e-01 1.0339865e+00 1.6134578e+00 8.0660588e-01 7.0523271e-01 1.0406064e+00 1.6372749e+00 5.1303949e-01 5.8851328e-01 1.0072663e+00 1.4951106e+00 1.0950112e+00 1.0934620e+00 1.5213929e+00 5.0991930e-01 4.5581864e-01 9.3615100e-01 7.6590510e-01 3.2586371e-01 4.2538717e-01 1.7942496e+00 1.9566981e+00 1.0636401e+00 6.6384020e-01 9.2264612e-01 1.7814077e+00 5.2371571e-01 6.0670504e-01 1.0120221e+00 4.8927739e-01 4.3691963e-01 5.6769031e-01 8.9366705e-01 1.1948578e+00 1.6982795e+00 5.7324170e-01 5.7257017e-01 8.3060013e-01 1.3824965e+00 5.7867728e-01 4.1586001e-01 5.4292906e-01 4.4651726e-01 5.7324170e-01 4.4535192e-01 7.6752131e-01 8.2105460e-01 6.9369532e-01 3.4583729e-01 7.0479928e-01 2.0656129e-01 4.3456114e-01 6.1092863e-01 4.6472023e-01 7.1840099e-01 6.9369532e-01 5.6631629e-01 3.2816937e-01 1.8058693e+00 1.8261179e+00 6.3735887e-01 7.0328431e-01 8.2684479e-01 1.6791597e+00 4.0293660e-01 6.6827038e-01 9.7377870e-01 5.0991930e-01 4.8135521e-01 3.2586371e-01 8.7021234e-01 1.1327825e+00 1.7839298e+00 3.7427929e-01 4.1586001e-01 5.5492130e-01 1.3916739e+00 7.7919451e-01 4.1449626e-01 5.8914551e-01 5.7324170e-01 6.0900723e-01 6.2407309e-01 6.0551856e-01 7.5705927e-01 7.8695083e-01 4.8135521e-01 3.2586371e-01 3.0811765e-01 7.3851529e-01 5.3665999e-01 1.1524979e+00 1.0244319e+00 4.3691963e-01 3.7255734e-01 1.4090646e+00 1.5060944e+00 1.0810263e+00 2.8507955e-01 1.2406194e+00 1.3336069e+00 7.1791510e-01 3.2586371e-01 5.9426792e-01 8.2552685e-01 8.2275389e-01 4.1449626e-01 5.8851328e-01 7.5196795e-01 1.3415658e+00 4.1586001e-01 7.2852070e-01 8.9159388e-01 9.7249562e-01 5.8914551e-01 4.4535192e-01 9.4352681e-01 1.4096146e-01 3.0811765e-01 4.1586001e-01 1.0244319e+00 4.2538717e-01 4.5581864e-01 3.2586371e-01 7.0869559e-01 3.7427929e-01 6.5223271e-01 9.2836103e-01 4.4651726e-01 8.8695363e-01 8.9971984e-01 2.4227359e+00 2.4243464e+00 5.5419992e-01 1.3235313e+00 3.0546431e-01 2.3149695e+00 6.1151102e-01 1.2036484e+00 1.6520677e+00 5.4292906e-01 5.7324170e-01 8.2305664e-01 1.5788188e+00 1.8238348e+00 2.4554026e+00 8.2552685e-01 7.0429250e-01 7.7588000e-01 2.0959492e+00 1.0466623e+00 8.6513410e-01 5.4292906e-01 1.2497790e+00 1.1215059e+00 1.2436109e+00 2.1845981e-01 1.3165513e+00 1.2256881e+00 1.0406064e+00 6.0060595e-01 8.5434758e-01 9.6664346e-01 5.1691876e-01 6.5223271e-01 8.4050231e-01 2.2451458e+00 2.2996030e+00 9.7270522e-01 1.1594648e+00 4.2538717e-01 2.1936248e+00 6.9369532e-01 1.0119857e+00 1.5297036e+00 6.6384020e-01 6.2988288e-01 7.0386584e-01 1.5113992e+00 1.7140171e+00 2.2868482e+00 6.9325418e-01 9.4080461e-01 1.0406064e+00 1.9734538e+00 7.5835500e-01 7.8695083e-01 6.2988288e-01 1.1158787e+00 9.4832302e-01 1.1055069e+00 5.0090417e-01 1.1452867e+00 1.0056742e+00 9.0277242e-01 6.3977563e-01 7.3851529e-01 6.6432544e-01 6.0611244e-01 5.1691876e-01 1.6983410e+00 1.8377590e+00 1.1500393e+00 5.6631629e-01 8.6012420e-01 1.6864433e+00 6.6539428e-01 4.5581864e-01 9.7356960e-01 6.6932542e-01 5.9426792e-01 4.5470518e-01 9.7548738e-01 1.1573546e+00 1.6772907e+00 4.4535192e-01 8.2929029e-01 9.8450810e-01 1.3812107e+00 3.2816937e-01 5.0905001e-01 6.6932542e-01 5.0991930e-01 3.7598397e-01 5.0905001e-01 7.2852070e-01 6.4704320e-01 4.5581864e-01 3.2586371e-01 7.4777660e-01 3.2816937e-01 2.5251796e-01 6.3108414e-01 1.5573817e+00 1.6420607e+00 9.0575661e-01 5.7867728e-01 9.7441804e-01 1.4917344e+00 6.2482915e-01 4.0176783e-01 7.7039952e-01 7.1799256e-01 6.4704320e-01 3.2816937e-01 7.1799256e-01 9.7270522e-01 1.5593809e+00 4.1586001e-01 4.6472023e-01 5.6454040e-01 1.2601890e+00 6.5223271e-01 1.2418578e-01 7.6716823e-01 4.4651726e-01 6.0670504e-01 6.1947990e-01 7.4777660e-01 5.9426792e-01 7.2172678e-01 5.3588338e-01 6.2660376e-01 3.2352160e-01 5.8914551e-01 6.4704320e-01 1.2013436e+00 2.3665136e+00 1.1771643e+00 2.4806944e+00 1.0018083e+00 2.1098467e+00 1.2627078e+00 8.8503502e-01 2.2024869e+00 2.1511385e+00 1.6189643e+00 1.1368070e+00 1.0688498e+00 3.4378533e-01 1.6188960e+00 1.9698860e+00 1.9254808e+00 8.8861541e-01 1.5832517e+00 1.5978297e+00 2.2712062e+00 1.4277162e+00 1.3610783e+00 1.6849072e+00 2.2645802e+00 1.1106525e+00 1.2665468e+00 1.6689743e+00 2.0995265e+00 1.7457596e+00 1.7532140e+00 2.1511385e+00 2.2712062e+00 1.3276804e+00 2.5485519e+00 3.4378533e-01 2.1870851e+00 1.4266198e+00 1.0379132e+00 2.3072128e+00 2.2736138e+00 1.6156775e+00 1.2095267e+00 8.3888121e-01 1.2277129e+00 1.6151153e+00 2.0528819e+00 1.8792214e+00 8.2624515e-01 1.7265353e+00 1.7004805e+00 2.3953564e+00 1.5733646e+00 1.4691764e+00 1.8497891e+00 2.3019759e+00 1.2238809e+00 1.4266198e+00 1.7962897e+00 2.1027465e+00 1.8635467e+00 1.9008621e+00 2.2439391e+00 1.3353353e+00 7.2526325e-01 2.1293320e+00 5.5492130e-01 1.2776560e+00 1.5193574e+00 6.2988288e-01 8.1130291e-01 8.6912228e-01 1.3752391e+00 1.6044563e+00 2.3474075e+00 9.1883539e-01 6.2024833e-01 6.4806901e-01 1.9000365e+00 1.3674559e+00 9.6664346e-01 8.1354181e-01 1.1751082e+00 1.2304904e+00 1.2256933e+00 5.7324170e-01 1.3628690e+00 1.4089364e+00 1.0866132e+00 4.8036801e-01 8.9971984e-01 1.3044654e+00 8.1130291e-01 1.3916739e+00 1.1500393e+00 9.6838716e-01 2.5251796e-01 5.5419992e-01 1.0573285e+00 1.0284501e+00 5.7324170e-01 7.1840099e-01 6.6317860e-01 1.1434428e+00 5.6769031e-01 9.7855477e-01 1.1106525e+00 8.2899253e-01 6.0670504e-01 6.2660376e-01 1.1449732e+00 3.2586371e-01 2.1845981e-01 6.0060595e-01 1.1833351e+00 2.0656129e-01 2.5251796e-01 5.1607523e-01 9.6324667e-01 5.9426792e-01 7.1799256e-01 1.0879524e+00 2.4372751e+00 7.0437330e-01 1.2331989e+00 1.7427900e+00 6.0611244e-01 5.1607523e-01 9.3615100e-01 1.6812503e+00 1.9411754e+00 2.5109747e+00 9.3801395e-01 7.7039952e-01 8.6513410e-01 2.2052183e+00 9.6324667e-01 8.9917007e-01 4.2667565e-01 1.3224963e+00 1.1912106e+00 1.3084046e+00 2.5651975e-01 1.3897316e+00 1.2541242e+00 1.1120775e+00 7.1504098e-01 9.1051084e-01 8.1521713e-01 3.6171588e-01 2.0209349e+00 1.2627078e+00 7.9878917e-01 2.1431239e+00 2.1198551e+00 1.5016009e+00 9.6141901e-01 6.2024833e-01 1.0083666e+00 1.5022608e+00 1.8803649e+00 1.7557336e+00 6.2482915e-01 1.6044563e+00 1.5582387e+00 2.2435182e+00 1.3836712e+00 1.3199714e+00 1.6568705e+00 2.1907335e+00 1.0796583e+00 1.2825987e+00 1.6205332e+00 1.9460721e+00 1.6995133e+00 1.7678302e+00 2.1198551e+00 9.1750357e-01 1.2756158e+00 1.4096146e-01 3.2352160e-01 7.1504098e-01 1.1242402e+00 1.4312787e+00 2.0223464e+00 7.3535471e-01 3.2586371e-01 7.3851529e-01 1.6386882e+00 9.4477932e-01 6.4755655e-01 3.7427929e-01 7.3805807e-01 8.6165877e-01 7.2852070e-01 5.0905001e-01 1.0922991e+00 1.0175773e+00 6.0900723e-01 2.1269358e-01 4.0176783e-01 8.2372435e-01 4.5470518e-01 5.5492130e-01 9.8495853e-01 9.0521488e-01 5.2942799e-01 6.3977563e-01 7.9878917e-01 1.2832075e+00 5.3022554e-01 8.3060013e-01 9.4500268e-01 1.0244319e+00 4.4651726e-01 4.0176783e-01 1.0230441e+00 3.4378533e-01 3.2586371e-01 6.1623531e-01 1.0330459e+00 2.5651975e-01 4.0000000e-01 5.3588338e-01 9.5676647e-01 5.3665999e-01 5.3665999e-01 9.0521488e-01 1.3865084e+00 1.3669552e+00 8.6012420e-01 2.8192292e-01 4.1586001e-01 8.4050231e-01 8.7383925e-01 1.1355826e+00 1.1712156e+00 6.2660376e-01 9.8985697e-01 8.5205778e-01 1.4909823e+00 6.3861009e-01 7.2526325e-01 9.4854455e-01 1.5124582e+00 5.6700421e-01 7.7919451e-01 8.9971984e-01 1.2483814e+00 9.4009473e-01 1.0879524e+00 1.4147273e+00 2.1269358e-01 8.1354181e-01 1.2441035e+00 1.5551238e+00 2.1137172e+00 8.2899253e-01 3.7427929e-01 8.2929029e-01 1.7587110e+00 9.6095130e-01 7.1799256e-01 2.4837156e-01 8.3280511e-01 9.3797093e-01 7.9016429e-01 4.4651726e-01 1.1833351e+00 1.0724413e+00 6.6932542e-01 3.2816937e-01 4.6472023e-01 8.0467258e-01 3.8776762e-01 7.3145860e-01 1.2564564e+00 1.5558094e+00 2.0983278e+00 7.5082357e-01 3.6171588e-01 7.6590510e-01 1.7862655e+00 8.4050231e-01 6.2024833e-01 1.2418578e-01 8.6137722e-01 8.9852394e-01 8.5462626e-01 3.8934542e-01 1.1192362e+00 1.0078327e+00 7.0386584e-01 5.0991930e-01 4.5470518e-01 6.6539428e-01 2.4837156e-01 8.5690100e-01 1.0344911e+00 1.6689743e+00 1.0000000e-01 6.8961791e-01 7.1799256e-01 1.3207609e+00 6.2024833e-01 3.7427929e-01 8.3888121e-01 5.3588338e-01 4.2288438e-01 6.4405773e-01 6.9369532e-01 5.3309112e-01 5.8914551e-01 4.6472023e-01 6.2538346e-01 4.1586001e-01 6.1623531e-01 6.4405773e-01 4.0176783e-01 1.0175773e+00 8.9303452e-01 1.0122141e+00 1.1161766e+00 7.7885297e-01 1.0755693e+00 8.1385214e-01 1.3792358e+00 5.8914551e-01 8.5462626e-01 8.7848692e-01 1.4517959e+00 7.3851529e-01 9.4854455e-01 8.6263408e-01 1.0941064e+00 8.3783744e-01 1.1172689e+00 1.3545005e+00 1.0391247e+00 1.0389435e+00 1.3163598e+00 1.3379696e+00 4.5470518e-01 1.1951875e+00 1.0632598e+00 1.6796759e+00 7.8197925e-01 8.3345577e-01 1.0540105e+00 1.7036156e+00 6.9167458e-01 8.8503502e-01 1.0279631e+00 1.3693737e+00 1.1192426e+00 1.3077572e+00 1.6183051e+00 1.6694974e+00 1.9094934e+00 1.9895190e+00 8.2384013e-01 1.6634400e+00 1.6218244e+00 2.2178691e+00 1.3008161e+00 1.3567326e+00 1.4994715e+00 2.3021295e+00 1.1763719e+00 1.3024224e+00 1.5535909e+00 2.0373882e+00 1.6756749e+00 1.7981158e+00 2.1739455e+00 7.6752131e-01 8.1354181e-01 1.3198846e+00 6.0611244e-01 4.4535192e-01 8.5335130e-01 5.3665999e-01 3.8776762e-01 6.3977563e-01 7.0429250e-01 5.2655962e-01 5.5492130e-01 4.5581864e-01 6.3861009e-01 4.2667565e-01 6.1151102e-01 6.6932542e-01 5.1691876e-01 1.5922648e+00 1.0054794e+00 4.8135521e-01 4.3456114e-01 7.6955924e-01 9.6664346e-01 8.9687438e-01 5.6700421e-01 1.0421979e+00 1.1001291e+00 8.2929029e-01 4.4535192e-01 5.1691876e-01 8.9712482e-01 4.5470518e-01 1.6914476e+00 1.1340084e+00 5.8914551e-01 8.5105559e-01 9.7548738e-01 1.0864449e+00 1.1160770e+00 6.3977563e-01 1.0720678e+00 1.2163831e+00 1.0047836e+00 6.9369532e-01 7.2343175e-01 1.0613462e+00 6.2407309e-01 1.4238090e+00 1.3511716e+00 1.9067300e+00 9.3824087e-01 1.0331736e+00 1.1327825e+00 1.9782093e+00 9.0454394e-01 1.0244319e+00 1.1833480e+00 1.5948732e+00 1.3360558e+00 1.5461469e+00 1.8900319e+00 6.2081167e-01 9.1750357e-01 6.4290921e-01 4.4417983e-01 7.0429250e-01 8.7229670e-01 5.3665999e-01 4.0438741e-01 5.6454040e-01 1.0054794e+00 5.7015910e-01 2.1269358e-01 7.5705927e-01 7.3496673e-01 5.2942799e-01 6.2024833e-01 6.6491075e-01 6.8801986e-01 6.1947990e-01 7.2172678e-01 5.5492130e-01 6.9006418e-01 3.2816937e-01 5.3665999e-01 5.6700421e-01 9.7779835e-01 1.0014633e+00 9.4854455e-01 3.8934542e-01 1.2342162e+00 1.1043332e+00 7.9580667e-01 5.3665999e-01 5.7257017e-01 7.2852070e-01 3.0275928e-01 3.4378533e-01 3.2352160e-01 1.1199472e+00 5.0991930e-01 4.6472023e-01 2.8507955e-01 7.7869083e-01 4.1586001e-01 7.1799256e-01 1.0132664e+00 5.0905001e-01 9.9519977e-01 3.0811765e-01 2.1269358e-01 4.0293660e-01 8.3060013e-01 5.0592043e-01 5.3665999e-01 9.3049742e-01 1.1263042e+00 8.0064372e-01 6.1623531e-01 2.1269358e-01 7.7588000e-01 4.4651726e-01 7.2783368e-01 1.0329901e+00 1.1697902e+00 1.0851476e+00 9.2859317e-01 5.0905001e-01 7.1504098e-01 7.7763126e-01 3.0546431e-01 2.5651975e-01 7.0437330e-01 1.0573285e+00 7.3145860e-01 6.9325418e-01 1.0901359e+00 5.3588338e-01 1.0175773e+00 6.4405773e-01 5.3665999e-01 1.0078327e+00 6.2407309e-01 3.2352160e-01 5.7257017e-01 8.5205778e-01 5.1691876e-01 9.4022486e-01 5.6769031e-01 4.8927739e-01 6.0611244e-01 6.0900723e-01 diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-minkowski-3.2-ml.txt b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-minkowski-3.2-ml.txt new file mode 100644 index 0000000000000000000000000000000000000000..daa81110a2be1a670f6163a8b255b2c6ecd5ccaa --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-minkowski-3.2-ml.txt @@ -0,0 +1 @@ + 2.0215050e+00 2.0988154e+00 1.8614681e+00 2.0510161e+00 1.9210911e+00 2.1323516e+00 1.9565454e+00 2.1029889e+00 1.9617871e+00 2.0544792e+00 2.0357408e+00 1.8811414e+00 2.0694693e+00 2.1245977e+00 2.0632165e+00 2.0452823e+00 2.0249330e+00 1.9635489e+00 2.0508580e+00 2.0838578e+00 1.9324052e+00 1.8224609e+00 1.9795343e+00 1.9536534e+00 1.9694910e+00 1.9075569e+00 1.9590397e+00 2.0022087e+00 1.8814000e+00 1.8884208e+00 1.9961121e+00 2.0215351e+00 1.7515769e+00 2.0756437e+00 2.0109476e+00 1.9234849e+00 1.9160076e+00 1.8550862e+00 1.7733640e+00 2.0071906e+00 2.0209542e+00 2.0616569e+00 2.0565503e+00 1.9083573e+00 2.2732431e+00 1.9975503e+00 1.9080072e+00 2.1437809e+00 2.1296295e+00 1.9739085e+00 1.9834166e+00 2.1078664e+00 2.2016840e+00 2.2080962e+00 1.7340579e+00 2.0549287e+00 1.7331748e+00 1.9559688e+00 2.0343364e+00 1.8736929e+00 1.9730416e+00 1.5308944e+00 1.8421831e+00 2.0174240e+00 2.0137378e+00 1.7956151e+00 1.9606596e+00 1.9074857e+00 2.0413879e+00 2.0070305e+00 1.9584677e+00 1.8977851e+00 1.9176239e+00 1.7067419e+00 1.9461927e+00 1.8431700e+00 1.8284576e+00 1.7778704e+00 1.8350329e+00 2.0175415e+00 1.7459063e+00 1.9242505e+00 1.8757370e+00 1.9312506e+00 2.0574808e+00 2.0894636e+00 1.9780203e+00 2.1374036e+00 1.8900436e+00 2.0273032e+00 2.0681953e+00 2.0234699e+00 2.0666449e+00 2.0663485e+00 1.9281402e+00 1.7846314e+00 2.0372479e+00 1.8831230e+00 2.0186015e+00 2.0193231e+00 2.2022665e+00 1.8145737e+00 2.0466545e+00 1.8092421e+00 1.9600687e+00 2.0322961e+00 1.9556364e+00 1.8266422e+00 1.9950345e+00 2.1038429e+00 2.1164145e+00 2.0188062e+00 1.8863331e+00 2.0006971e+00 1.9971068e+00 1.8771862e+00 2.1148855e+00 1.9570638e+00 1.9859615e+00 2.0030854e+00 2.0737344e+00 1.9739259e+00 1.9266524e+00 1.9200535e+00 2.1376689e+00 1.8944425e+00 1.9330553e+00 1.8561590e+00 1.9422954e+00 1.8874178e+00 1.8624808e+00 1.8265563e+00 1.8840519e+00 2.0515092e+00 2.0174226e+00 1.9771196e+00 2.0635988e+00 1.7334466e+00 1.9912604e+00 1.8915711e+00 1.8262636e+00 1.9369173e+00 1.9560446e+00 1.9549934e+00 1.9279230e+00 1.9021073e+00 2.0113391e+00 2.0305786e+00 1.8066806e+00 1.9656739e+00 2.1219217e+00 1.8820250e+00 1.8936826e+00 2.0565131e+00 1.9839441e+00 1.8553479e+00 1.9923760e+00 1.6393276e+00 1.9786440e+00 1.8274394e+00 1.9322611e+00 2.0404318e+00 1.9216532e+00 1.9361171e+00 1.8401373e+00 1.9908059e+00 1.9495117e+00 2.1975655e+00 1.8413913e+00 2.1528773e+00 1.8434374e+00 2.1668863e+00 2.0429273e+00 1.9980016e+00 1.9790129e+00 2.0264829e+00 2.1478843e+00 2.0899600e+00 2.0280670e+00 2.1210881e+00 1.9993891e+00 1.8646871e+00 1.9099983e+00 1.9263353e+00 2.0042495e+00 2.1365919e+00 2.1830279e+00 1.9631961e+00 2.0880004e+00 1.8348369e+00 diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-minkowski-5.8-ml-iris.txt b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-minkowski-5.8-ml-iris.txt new file mode 100644 index 0000000000000000000000000000000000000000..aa26b0439f568f97c95bee1b04204f24c9a1f3e0 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-minkowski-5.8-ml-iris.txt @@ -0,0 +1 @@ + 5.0042326e-01 4.1210927e-01 5.2133179e-01 1.1269424e-01 4.2362917e-01 5.0001522e-01 1.2085435e-01 7.4262850e-01 4.0127250e-01 3.0482299e-01 3.0482299e-01 5.0436965e-01 8.0923926e-01 7.1629168e-01 9.1424701e-01 4.1317535e-01 1.0000000e-01 6.0366256e-01 3.0017653e-01 3.3813251e-01 2.2573593e-01 5.2133179e-01 3.4080442e-01 5.0436965e-01 5.0043084e-01 2.2608083e-01 1.1269424e-01 1.1269424e-01 4.1315633e-01 4.1315633e-01 3.0490481e-01 6.0000952e-01 7.0462550e-01 4.0127250e-01 3.0482299e-01 4.0002221e-01 4.0127250e-01 7.1621748e-01 1.1269424e-01 1.2085435e-01 1.2036864e+00 7.0088477e-01 4.0125062e-01 5.0476836e-01 5.0436965e-01 3.0474106e-01 5.0436235e-01 2.2573593e-01 2.0061436e-01 3.3243227e+00 3.1068812e+00 3.5145413e+00 2.6080595e+00 3.2075731e+00 3.1014454e+00 3.3055260e+00 1.9156198e+00 3.2079238e+00 2.5066441e+00 2.1498493e+00 2.8059664e+00 2.6093989e+00 3.3021953e+00 2.2070266e+00 3.0158454e+00 3.1034764e+00 2.7009878e+00 3.1081779e+00 2.5032992e+00 3.4074959e+00 2.6050088e+00 3.5035589e+00 3.3011884e+00 2.9065890e+00 3.0117336e+00 3.4118782e+00 3.6094426e+00 3.1038958e+00 2.1042326e+00 2.4058620e+00 2.3063407e+00 2.5029614e+00 3.7025335e+00 3.1034636e+00 3.1057006e+00 3.3110189e+00 3.0065909e+00 2.7025941e+00 2.6047974e+00 3.0013665e+00 3.2025221e+00 2.6029242e+00 1.9242109e+00 2.8024935e+00 2.8013151e+00 2.8022622e+00 2.9036582e+00 1.6267693e+00 2.7028014e+00 4.6144526e+00 3.7071079e+00 4.5121787e+00 4.2031939e+00 4.4087839e+00 5.2153194e+00 3.1086291e+00 4.9093646e+00 4.4044245e+00 4.7202040e+00 3.7119486e+00 3.9066365e+00 4.1123628e+00 3.6114402e+00 3.7307413e+00 3.9194642e+00 4.1043951e+00 5.3177489e+00 5.5157728e+00 3.6035661e+00 4.3162097e+00 3.5127031e+00 5.3163123e+00 3.5077296e+00 4.3088507e+00 4.6100803e+00 3.4082578e+00 3.5068380e+00 4.2080636e+00 4.4113183e+00 4.7149608e+00 5.0316727e+00 4.2105572e+00 3.7024462e+00 4.2007769e+00 4.7331529e+00 4.2173557e+00 4.1039096e+00 3.4076329e+00 4.0157626e+00 4.2194897e+00 3.7329396e+00 3.7071079e+00 4.5119962e+00 4.3218071e+00 3.8249612e+00 3.6093673e+00 3.8105293e+00 4.0166459e+00 3.7050109e+00 2.2573593e-01 3.0017653e-01 6.0000317e-01 9.0534502e-01 4.1210927e-01 4.0004442e-01 5.0000761e-01 1.2085435e-01 7.1621748e-01 4.0125062e-01 1.1269424e-01 6.0184622e-01 1.0776294e+00 1.4092540e+00 9.0508756e-01 5.0043084e-01 9.0181717e-01 8.0004602e-01 5.2491131e-01 7.0017011e-01 6.1119267e-01 3.6452132e-01 5.2133179e-01 2.0061436e-01 4.0246123e-01 5.0436965e-01 4.1209001e-01 2.4170870e-01 2.0121983e-01 5.2167829e-01 1.1001015e+00 1.2036862e+00 1.2085435e-01 2.2573593e-01 6.3164977e-01 1.2085435e-01 5.0000761e-01 4.0125062e-01 5.0002283e-01 7.0462844e-01 5.0043084e-01 5.2167829e-01 8.0888055e-01 1.1269424e-01 8.0008884e-01 3.0474106e-01 7.0462697e-01 3.0008832e-01 3.3416860e+00 3.1112912e+00 3.5249966e+00 2.6033557e+00 3.2127499e+00 3.1015178e+00 3.3078313e+00 1.9025708e+00 3.2150318e+00 2.5060738e+00 2.1061951e+00 2.8068283e+00 2.6040016e+00 3.3032134e+00 2.2072454e+00 3.0286102e+00 3.1035443e+00 2.7011973e+00 3.1070853e+00 2.5014549e+00 3.4078435e+00 2.6080511e+00 3.5048916e+00 3.3021665e+00 2.9125999e+00 3.0213627e+00 3.4211337e+00 3.6148618e+00 3.1047537e+00 2.1027003e+00 2.4016639e+00 2.3011929e+00 2.5032633e+00 3.7028303e+00 3.1034629e+00 3.1065984e+00 3.3192072e+00 3.0078209e+00 2.7027260e+00 2.6031664e+00 3.0009332e+00 3.2037232e+00 2.6027120e+00 1.9031578e+00 2.8022915e+00 2.8015662e+00 2.8024715e+00 2.9065359e+00 1.6099792e+00 2.7029416e+00 4.6149181e+00 3.7071538e+00 4.5172866e+00 4.2039132e+00 4.4099272e+00 5.2224057e+00 3.1078968e+00 4.9146298e+00 4.4063795e+00 4.7253524e+00 3.7145622e+00 3.9080413e+00 4.1161770e+00 3.6111646e+00 3.7308314e+00 3.9209137e+00 4.1060063e+00 5.3254977e+00 5.5222404e+00 3.6024247e+00 4.3201293e+00 3.5126957e+00 5.3240486e+00 3.5093499e+00 4.3111749e+00 4.6158382e+00 3.4095576e+00 3.5076152e+00 4.2090727e+00 4.4184242e+00 4.7227808e+00 5.0458491e+00 4.2115634e+00 3.7037441e+00 4.2010125e+00 4.7466313e+00 4.2180733e+00 4.1050714e+00 3.4081972e+00 4.0212972e+00 4.2220584e+00 3.7407842e+00 3.7071538e+00 4.5144444e+00 4.3240980e+00 3.8290678e+00 3.6105228e+00 3.8128297e+00 4.0172657e+00 3.7052380e+00 2.0121983e-01 4.1210927e-01 7.9153339e-01 2.0181667e-01 3.0915245e-01 3.3813251e-01 2.2608083e-01 7.1629168e-01 3.0482299e-01 2.0181667e-01 4.0246123e-01 1.1281267e+00 1.2633045e+00 7.8890721e-01 4.1212852e-01 1.0095370e+00 6.0964891e-01 7.0470720e-01 5.2201750e-01 4.1210927e-01 4.5784410e-01 6.0017982e-01 3.4080442e-01 3.4342562e-01 5.0476836e-01 5.0043084e-01 3.0000000e-01 3.0017653e-01 7.0025283e-01 9.0508756e-01 1.0426513e+00 2.2608083e-01 3.0008832e-01 8.0046605e-01 2.2608083e-01 3.0474106e-01 4.0243965e-01 3.3813251e-01 9.0002570e-01 3.0000000e-01 4.3213914e-01 6.8170466e-01 2.0181667e-01 6.1119267e-01 1.1269424e-01 6.3178534e-01 3.0017653e-01 3.4595765e+00 3.2168311e+00 3.6364650e+00 2.7037323e+00 3.3192099e+00 3.2017763e+00 3.4107328e+00 2.0033798e+00 3.3237063e+00 2.6050967e+00 2.2121910e+00 2.9077087e+00 2.7085154e+00 3.4047917e+00 2.3071665e+00 3.1428042e+00 3.2033135e+00 2.8024935e+00 3.2103481e+00 2.6021247e+00 3.5076152e+00 2.7127272e+00 3.6073242e+00 3.4038884e+00 3.0203881e+00 3.1325879e+00 3.5317021e+00 3.7210979e+00 3.2059139e+00 2.2051638e+00 2.5023084e+00 2.4021168e+00 2.6048201e+00 3.8033004e+00 3.2030448e+00 3.2074921e+00 3.4286399e+00 3.1131211e+00 2.8028008e+00 2.7031257e+00 3.1010004e+00 3.3055260e+00 2.7040740e+00 2.0050309e+00 2.9023862e+00 2.9020767e+00 2.9028421e+00 3.0107283e+00 1.7089863e+00 2.8033666e+00 4.7142986e+00 3.8066401e+00 4.6226512e+00 4.3047830e+00 4.5107876e+00 5.3296471e+00 3.2068572e+00 5.0203871e+00 4.5089338e+00 4.8299744e+00 3.8170042e+00 4.0095939e+00 4.2200398e+00 3.7100654e+00 3.8275330e+00 4.0209836e+00 4.2079639e+00 5.4332277e+00 5.6287689e+00 3.7032748e+00 4.4237036e+00 3.6112573e+00 5.4319232e+00 3.6111754e+00 4.4135512e+00 4.7221364e+00 3.5107924e+00 3.6081749e+00 4.3098514e+00 4.5261773e+00 4.8309399e+00 5.1593152e+00 4.3120751e+00 3.8056232e+00 4.3015640e+00 4.8592534e+00 4.3174320e+00 4.2064763e+00 3.5083248e+00 4.1268500e+00 4.3236383e+00 3.8471097e+00 3.8066401e+00 4.6166518e+00 4.4251081e+00 3.9318948e+00 3.7118930e+00 3.9150333e+00 4.1165034e+00 3.8051417e+00 5.2133179e-01 9.0160400e-01 3.0017653e-01 4.1209001e-01 2.2573593e-01 3.0008832e-01 8.2418002e-01 3.0482299e-01 2.0181667e-01 4.1212852e-01 1.2363278e+00 1.3741498e+00 9.0160400e-01 5.2133802e-01 1.1133986e+00 7.1621748e-01 8.0051036e-01 6.3178534e-01 5.6347121e-01 5.0517282e-01 4.1315633e-01 4.0004442e-01 4.1317535e-01 6.0948212e-01 6.0184622e-01 1.2085435e-01 2.0061436e-01 8.0051036e-01 1.0087250e+00 1.1527669e+00 3.0008832e-01 4.1210927e-01 9.0142636e-01 3.0008832e-01 2.2573593e-01 5.0436235e-01 4.5148429e-01 8.0004602e-01 2.2573593e-01 4.8342635e-01 7.2044167e-01 2.0181667e-01 7.1621748e-01 1.1269424e-01 7.4262850e-01 4.0125062e-01 3.2983364e+00 3.0300451e+00 3.4603347e+00 2.5053901e+00 3.1338090e+00 3.0030658e+00 3.2183845e+00 1.8040969e+00 3.1419971e+00 2.4075162e+00 2.0123013e+00 2.7132680e+00 2.5163999e+00 3.2086215e+00 2.1132077e+00 2.9750754e+00 3.0049127e+00 2.6055197e+00 3.0177719e+00 2.4040962e+00 3.3110162e+00 2.5253371e+00 3.4126529e+00 3.2074182e+00 2.8380954e+00 2.9580787e+00 3.3536443e+00 3.5347730e+00 3.0101869e+00 2.0123796e+00 2.3038195e+00 2.2036797e+00 2.4099203e+00 3.6051707e+00 3.0042758e+00 3.0123228e+00 3.2490712e+00 2.9241808e+00 2.6047889e+00 2.5049231e+00 2.9016211e+00 3.1100277e+00 2.5081992e+00 1.8056342e+00 2.7040060e+00 2.7039988e+00 2.7050721e+00 2.8205713e+00 1.5147271e+00 2.6060742e+00 4.5183778e+00 3.6090052e+00 4.4337691e+00 4.1072664e+00 4.3151164e+00 5.1425125e+00 3.0092613e+00 4.8303615e+00 4.3139066e+00 4.6422789e+00 3.6259317e+00 3.8146285e+00 4.0301568e+00 3.5133848e+00 3.6358680e+00 3.8290678e+00 4.0124919e+00 5.2471177e+00 5.4403962e+00 3.5051114e+00 4.2343452e+00 3.4149831e+00 5.2455706e+00 3.4177035e+00 4.2200398e+00 4.5335328e+00 3.3168776e+00 3.4123846e+00 4.1140176e+00 4.3402553e+00 4.6459028e+00 4.9843016e+00 4.1167964e+00 3.6096226e+00 4.1026403e+00 4.6849407e+00 4.1230798e+00 4.0100505e+00 3.3123688e+00 3.9407837e+00 4.1330547e+00 3.6700537e+00 3.6090052e+00 4.4237036e+00 4.2343452e+00 3.7463488e+00 3.5181052e+00 3.7227931e+00 3.9220791e+00 3.6072781e+00 4.2362917e-01 4.0125062e-01 2.0061436e-01 7.4262850e-01 5.0002283e-01 4.0004442e-01 2.4170870e-01 6.0017982e-01 7.4329527e-01 8.0250123e-01 8.5406674e-01 4.1317535e-01 1.2085435e-01 7.0096858e-01 2.0181667e-01 4.1315633e-01 2.0181667e-01 4.5077696e-01 3.6259865e-01 5.0084481e-01 6.0017665e-01 2.4170870e-01 2.0121983e-01 2.2538848e-01 4.1315633e-01 5.0084481e-01 4.0246123e-01 5.0043842e-01 6.3164729e-01 5.0002283e-01 4.0122873e-01 5.0001522e-01 5.0002283e-01 6.7616723e-01 2.0121983e-01 1.2085435e-01 1.3008771e+00 6.0948506e-01 4.0125062e-01 5.0085236e-01 6.0017982e-01 2.2573593e-01 4.5077696e-01 3.0017653e-01 3.0000000e-01 3.3320240e+00 3.1087192e+00 3.5191371e+00 2.6110181e+00 3.2098845e+00 3.1016129e+00 3.3064697e+00 1.9242109e+00 3.2110200e+00 2.5072065e+00 2.1702438e+00 2.8063347e+00 2.6144115e+00 3.3026483e+00 2.2074446e+00 3.0213781e+00 3.1035271e+00 2.7015967e+00 3.1108570e+00 2.5049231e+00 3.4076266e+00 2.6065485e+00 3.5045818e+00 3.3016829e+00 2.9091905e+00 3.0158857e+00 3.4160038e+00 3.6117923e+00 3.1042949e+00 2.1068047e+00 2.4087956e+00 2.3099309e+00 2.5038387e+00 3.7027671e+00 3.1034919e+00 3.1060428e+00 3.3145595e+00 3.0095593e+00 2.7026925e+00 2.6061038e+00 3.0017811e+00 3.2030205e+00 2.6039803e+00 1.9366876e+00 2.8028640e+00 2.8014482e+00 2.8024453e+00 2.9049136e+00 1.6388635e+00 2.7031257e+00 4.6146430e+00 3.7072412e+00 4.5144508e+00 4.2035048e+00 4.4092709e+00 5.2185448e+00 3.1091788e+00 4.9117351e+00 4.4054277e+00 4.7224997e+00 3.7130507e+00 3.9073151e+00 4.1140274e+00 3.6117351e+00 3.7308330e+00 3.9200674e+00 4.1050815e+00 5.3212796e+00 5.5187578e+00 3.6046347e+00 4.3179262e+00 3.5127783e+00 5.3198559e+00 3.5085510e+00 4.3098508e+00 4.6126513e+00 3.4088749e+00 3.5071604e+00 4.2085176e+00 4.4144980e+00 4.7185095e+00 5.0381903e+00 4.2110099e+00 3.7030413e+00 4.2009868e+00 4.7393218e+00 4.2176488e+00 4.1043951e+00 3.4078683e+00 4.0181902e+00 4.2205976e+00 3.7363838e+00 3.7072412e+00 4.5130595e+00 4.3227928e+00 3.8267408e+00 3.6102542e+00 3.8115096e+00 4.0168944e+00 3.7051079e+00 8.0923926e-01 5.2201750e-01 1.1270411e+00 8.0928056e-01 2.4170870e-01 6.3178782e-01 9.1471442e-01 1.1573074e+00 5.2167829e-01 5.0476836e-01 4.0000000e-01 4.2270142e-01 3.0017653e-01 3.0490481e-01 5.0042326e-01 3.0915245e-01 8.5440680e-01 6.0184622e-01 6.3192325e-01 9.0142681e-01 5.2133179e-01 4.0363334e-01 5.0517282e-01 7.8890806e-01 8.2421923e-01 5.0042326e-01 3.1328089e-01 3.4085233e-01 8.0928056e-01 7.2044167e-01 4.5148429e-01 8.0928056e-01 1.0782211e+00 5.0517282e-01 4.8342635e-01 1.6097492e+00 1.0215068e+00 4.5148429e-01 3.0482299e-01 9.1446938e-01 3.0490481e-01 8.5440680e-01 2.4195741e-01 6.1135434e-01 3.0143288e+00 2.8035152e+00 3.2080663e+00 2.3476141e+00 2.9053991e+00 2.8028019e+00 3.0030626e+00 1.7519158e+00 2.9045816e+00 2.2149484e+00 2.0887699e+00 2.5048522e+00 2.3645147e+00 3.0018766e+00 1.9120303e+00 2.7085154e+00 2.8028008e+00 2.4075162e+00 2.8284908e+00 2.2272457e+00 3.1054022e+00 2.3075573e+00 3.2060163e+00 3.0018874e+00 2.6044486e+00 2.7064438e+00 3.1073418e+00 3.3054063e+00 2.8034238e+00 1.8447840e+00 2.1492024e+00 2.0607272e+00 2.2122063e+00 3.4028104e+00 2.8028007e+00 2.8036182e+00 3.0057998e+00 2.7234787e+00 2.4027927e+00 2.3234132e+00 2.7070699e+00 2.9017335e+00 2.3151346e+00 1.8036834e+00 2.5072065e+00 2.5017313e+00 2.5032633e+00 2.6031823e+00 1.5292174e+00 2.4058519e+00 4.3116266e+00 3.4064593e+00 4.2076930e+00 3.9021503e+00 4.1063936e+00 4.9099401e+00 2.8141516e+00 4.6055969e+00 4.1036742e+00 4.4145324e+00 3.4082578e+00 3.6052799e+00 3.8082804e+00 3.3123693e+00 3.4273179e+00 3.6154977e+00 3.8026444e+00 5.0117750e+00 5.2107474e+00 3.3130198e+00 4.0114753e+00 3.2109395e+00 5.0107787e+00 3.2067490e+00 4.0058313e+00 4.3058539e+00 3.1067996e+00 3.2049797e+00 3.9061098e+00 4.1066170e+00 4.4095056e+00 4.7221364e+00 3.9082316e+00 3.4019453e+00 3.9014304e+00 4.4232188e+00 3.9139973e+00 3.8023591e+00 3.1057392e+00 3.7104219e+00 3.9150553e+00 3.4248402e+00 3.4064593e+00 4.2084919e+00 4.0172759e+00 3.5193527e+00 3.3100431e+00 3.5073655e+00 3.7133435e+00 3.4036743e+00 4.0004442e-01 5.0043084e-01 3.4085233e-01 8.0046764e-01 2.2573593e-01 4.0243965e-01 4.2362917e-01 1.2036925e+00 1.1896595e+00 8.0879776e-01 5.0000761e-01 1.1006371e+00 5.2133179e-01 8.0046685e-01 5.0437695e-01 4.0125062e-01 5.0477564e-01 5.0043084e-01 4.5148429e-01 4.0125062e-01 6.0000952e-01 6.0000317e-01 2.2608083e-01 3.0922892e-01 8.0000160e-01 7.4269314e-01 9.6572569e-01 3.4085233e-01 4.0246123e-01 9.0000136e-01 3.4085233e-01 4.0127250e-01 5.0001522e-01 4.0004442e-01 1.1000003e+00 2.2608083e-01 4.1317535e-01 5.7609230e-01 4.0122873e-01 5.2167829e-01 2.0061436e-01 7.0088627e-01 4.0004442e-01 3.3852404e+00 3.1245391e+00 3.5521657e+00 2.6057331e+00 3.2281303e+00 3.1021033e+00 3.3145497e+00 1.9088256e+00 3.2358110e+00 2.5040476e+00 2.1337832e+00 2.8091158e+00 2.6173653e+00 3.3068237e+00 2.2078368e+00 3.0635687e+00 3.1029264e+00 2.7045714e+00 3.1156892e+00 2.5038387e+00 3.4072735e+00 2.6199287e+00 3.5105217e+00 3.3061800e+00 2.9316687e+00 3.0488379e+00 3.4462681e+00 3.6292576e+00 3.1074604e+00 2.1103491e+00 2.4046650e+00 2.3052527e+00 2.5074705e+00 3.7037846e+00 3.1023805e+00 3.1087156e+00 3.3416864e+00 3.0212423e+00 2.7029308e+00 2.6036513e+00 3.0012006e+00 3.2078939e+00 2.6064541e+00 1.9145304e+00 2.8026114e+00 2.8028068e+00 2.8033825e+00 2.9167099e+00 1.6147493e+00 2.7040740e+00 4.6133719e+00 3.7058811e+00 4.5290217e+00 4.2056470e+00 4.4115634e+00 5.2381327e+00 3.1057013e+00 4.9271590e+00 4.4118721e+00 4.7354168e+00 3.7201124e+00 3.9113698e+00 4.1247181e+00 3.6087856e+00 3.7244383e+00 3.9212835e+00 4.1101783e+00 5.3422962e+00 5.5362181e+00 3.6046999e+00 4.3279835e+00 3.5095358e+00 5.3412086e+00 3.5135120e+00 4.3162096e+00 4.6297141e+00 3.4124092e+00 3.5088081e+00 4.2105763e+00 4.4358170e+00 4.7408876e+00 5.0762364e+00 4.2125085e+00 3.7079173e+00 4.2021973e+00 4.7752666e+00 4.2166536e+00 4.1080028e+00 3.4084548e+00 4.0338654e+00 4.2256165e+00 3.7563734e+00 3.7058811e+00 4.5190617e+00 4.3264209e+00 3.8360186e+00 3.6136974e+00 3.8177300e+00 4.0156240e+00 3.7048582e+00 6.3164977e-01 3.0017653e-01 4.1209001e-01 2.0061436e-01 4.0127250e-01 7.0911112e-01 8.2458409e-01 1.0207396e+00 5.2201750e-01 1.2699992e-01 7.0470867e-01 4.0004442e-01 4.0122873e-01 3.0482299e-01 5.2167208e-01 3.0490481e-01 4.0122873e-01 4.0002221e-01 2.0061436e-01 2.0061436e-01 2.0061436e-01 3.0482299e-01 3.0482299e-01 4.0122873e-01 7.0008584e-01 8.0879701e-01 3.0017653e-01 3.0474106e-01 5.0043084e-01 3.0017653e-01 6.0964597e-01 1.0000000e-01 2.0121983e-01 1.1019599e+00 6.0035305e-01 4.0004442e-01 4.5148429e-01 4.0127250e-01 4.0004442e-01 4.0125062e-01 3.3808272e-01 1.1269424e-01 3.2369541e+00 3.0101869e+00 3.4219340e+00 2.5073576e+00 3.1113295e+00 3.0016913e+00 3.2074921e+00 1.8128536e+00 3.1127326e+00 2.4076937e+00 2.0429861e+00 2.7074657e+00 2.5087337e+00 3.2029987e+00 2.1087640e+00 2.9250474e+00 3.0040848e+00 2.6011837e+00 3.0090716e+00 2.4029250e+00 3.3087901e+00 2.5074281e+00 3.4046875e+00 3.2018065e+00 2.8107271e+00 2.9185950e+00 3.3183094e+00 3.5134617e+00 3.0049285e+00 2.0041542e+00 2.3049133e+00 2.2050331e+00 2.4035997e+00 3.6030023e+00 3.0040438e+00 3.0070658e+00 3.2168317e+00 2.9083216e+00 2.6031436e+00 2.5048522e+00 2.9013423e+00 3.1034810e+00 2.5032729e+00 1.8201043e+00 2.7028014e+00 2.7016556e+00 2.7027522e+00 2.8056775e+00 1.5256523e+00 2.6033557e+00 4.5162553e+00 3.6081006e+00 4.4160732e+00 4.1039121e+00 4.3103378e+00 5.1203327e+00 3.0096880e+00 4.8129366e+00 4.3058720e+00 4.6249088e+00 3.6148619e+00 3.8081633e+00 4.0157626e+00 3.5129206e+00 3.6349703e+00 3.8226858e+00 4.0057080e+00 5.2232912e+00 5.4204287e+00 3.5035589e+00 4.2200398e+00 3.4145570e+00 5.2217206e+00 3.4096180e+00 4.2110197e+00 4.5140458e+00 3.3101076e+00 3.4081996e+00 4.1095117e+00 4.3161641e+00 4.6204721e+00 4.9419857e+00 4.1123051e+00 3.6033860e+00 4.1009647e+00 4.6434791e+00 4.1197833e+00 4.0049425e+00 3.3090452e+00 3.9205015e+00 4.1230798e+00 3.6413278e+00 3.6081006e+00 4.4145323e+00 4.2254713e+00 3.7302938e+00 3.5112285e+00 3.7130507e+00 3.9190472e+00 3.6058055e+00 5.0043842e-01 1.0426513e+00 5.2167208e-01 4.0004442e-01 3.0026460e-01 1.4542931e+00 1.5965783e+00 1.1269511e+00 7.4262964e-01 1.3253871e+00 9.3306807e-01 1.0032293e+00 8.5406674e-01 7.0470720e-01 7.0633229e-01 5.7608844e-01 6.0017982e-01 6.3192325e-01 8.2418071e-01 8.0879625e-01 3.4080442e-01 4.0243965e-01 1.0030871e+00 1.2189645e+00 1.3741465e+00 5.0043842e-01 6.0201716e-01 1.1055705e+00 5.0043842e-01 1.1269424e-01 7.1621748e-01 6.7616902e-01 6.0000952e-01 3.0008832e-01 6.8170466e-01 9.3735629e-01 4.0004442e-01 9.3308853e-01 3.0474106e-01 9.6572569e-01 6.0948212e-01 3.4311880e+00 3.1440065e+00 3.5828092e+00 2.6061623e+00 3.2490712e+00 3.1047537e+00 3.3265679e+00 1.9024467e+00 3.2610547e+00 2.5066443e+00 2.1042326e+00 2.8182771e+00 2.6268573e+00 3.3136174e+00 2.2177383e+00 3.1042292e+00 3.1056084e+00 2.7106185e+00 3.1258664e+00 2.5072166e+00 3.4123850e+00 2.6397031e+00 3.5191318e+00 3.3125861e+00 2.9569988e+00 3.0825000e+00 3.4750557e+00 3.6484459e+00 3.1148203e+00 2.1231535e+00 2.4058952e+00 2.3063814e+00 2.5167763e+00 3.7071732e+00 3.1042001e+00 3.1166462e+00 3.3690976e+00 3.0370401e+00 2.7067267e+00 2.6060811e+00 3.0024163e+00 3.2157547e+00 2.6139440e+00 1.9029771e+00 2.8056558e+00 2.8068283e+00 2.8077255e+00 2.9323793e+00 1.6119586e+00 2.7091848e+00 4.6187512e+00 3.7092298e+00 4.5442576e+00 4.2099019e+00 4.4180513e+00 5.2548586e+00 3.1079005e+00 4.9407795e+00 4.4195588e+00 4.7516511e+00 3.7329384e+00 3.9191954e+00 4.1389224e+00 3.6127211e+00 3.7328453e+00 3.9318950e+00 4.1174259e+00 5.3601143e+00 5.5513974e+00 3.6073242e+00 4.3425018e+00 3.5138357e+00 5.3586950e+00 3.5235095e+00 4.3257995e+00 4.6452056e+00 3.4217238e+00 3.5154314e+00 4.2169032e+00 4.4544908e+00 4.7602896e+00 5.1057502e+00 4.2193718e+00 3.7147036e+00 4.2043114e+00 4.8058872e+00 4.2239687e+00 4.1138950e+00 3.4146523e+00 4.0526593e+00 4.2382079e+00 3.7847403e+00 3.7092298e+00 4.5291248e+00 4.3385161e+00 3.8547029e+00 3.6228903e+00 3.8290678e+00 4.0228342e+00 3.7082809e+00 6.3164977e-01 3.0026460e-01 1.2085435e-01 6.0948506e-01 1.0143978e+00 1.3131369e+00 8.0928056e-01 4.0246123e-01 8.5409862e-01 7.0016860e-01 5.0477564e-01 6.0201716e-01 5.6595908e-01 4.0363334e-01 4.1212852e-01 1.2699992e-01 3.3818226e-01 4.1210927e-01 3.3818226e-01 2.0181667e-01 1.2085435e-01 5.0855077e-01 1.0001598e+00 1.1055707e+00 0.0000000e+00 3.0026460e-01 6.0964891e-01 0.0000000e+00 5.0043842e-01 3.0482299e-01 4.0246123e-01 8.0254500e-01 5.0043842e-01 5.2133802e-01 7.0556260e-01 2.0181667e-01 7.0008735e-01 3.0026460e-01 6.0948506e-01 2.0181667e-01 3.2490712e+00 3.0153168e+00 3.4297841e+00 2.5067523e+00 3.1166337e+00 3.0027816e+00 3.2112793e+00 1.8068048e+00 3.1183051e+00 2.4116924e+00 2.0138832e+00 2.7116615e+00 2.5059537e+00 3.2048192e+00 2.1144760e+00 2.9351753e+00 3.0063019e+00 2.6019122e+00 3.0106587e+00 2.4030297e+00 3.3125861e+00 2.5120719e+00 3.4068163e+00 3.2029877e+00 2.8162444e+00 2.9267417e+00 3.3252407e+00 3.5189464e+00 3.0077107e+00 2.0051350e+00 2.3037132e+00 2.2028146e+00 2.4058620e+00 3.6044981e+00 3.0062070e+00 3.0107283e+00 3.2237456e+00 2.9105093e+00 2.6052541e+00 2.5062865e+00 2.9018772e+00 3.1056084e+00 2.5048522e+00 1.8082911e+00 2.7043948e+00 2.7029415e+00 2.7046027e+00 2.8091099e+00 1.5248852e+00 2.6055127e+00 4.5209020e+00 3.6112573e+00 4.4212031e+00 4.1056541e+00 4.3138986e+00 5.1255338e+00 3.0133997e+00 4.8167235e+00 4.3081273e+00 4.6319211e+00 3.6205854e+00 3.8114965e+00 4.0212972e+00 3.5173798e+00 3.6449970e+00 3.8299342e+00 4.0081754e+00 5.2290121e+00 5.4254411e+00 3.5039202e+00 4.2264145e+00 3.4198378e+00 5.2270034e+00 3.4138008e+00 4.2149806e+00 4.5183778e+00 3.3145502e+00 3.4118179e+00 4.1129687e+00 4.3210760e+00 4.6261633e+00 4.9512603e+00 4.1165035e+00 3.6051692e+00 4.1014742e+00 4.6540056e+00 4.1257291e+00 4.0071257e+00 3.3129914e+00 3.9274863e+00 4.1301604e+00 3.6542046e+00 3.6112573e+00 4.4192311e+00 4.2328883e+00 3.7399948e+00 3.5155767e+00 3.7180846e+00 3.9250546e+00 3.6083191e+00 6.0184622e-01 7.4263078e-01 1.1138955e+00 4.2268438e-01 7.0096708e-01 2.4170870e-01 3.0490481e-01 3.0490481e-01 3.0017653e-01 3.0474106e-01 3.0474106e-01 8.0879701e-01 4.2362917e-01 6.1119267e-01 7.0462697e-01 4.1317535e-01 2.2538848e-01 3.0482299e-01 7.1621748e-01 6.7616723e-01 3.0474106e-01 4.0125062e-01 5.0001522e-01 6.3164977e-01 5.2491131e-01 2.2573593e-01 6.3164977e-01 1.0207396e+00 3.3808272e-01 4.0246123e-01 1.4180463e+00 1.0030868e+00 4.5148429e-01 4.1317535e-01 7.4263078e-01 3.0017653e-01 8.0879701e-01 1.0000000e-01 4.5078948e-01 3.2116783e+00 3.0049285e+00 3.4072983e+00 2.5182898e+00 3.1051604e+00 3.0020136e+00 3.2049016e+00 1.8469618e+00 3.1036832e+00 2.4099081e+00 2.1180493e+00 2.7068820e+00 2.5224740e+00 3.2021231e+00 2.1097449e+00 2.9077617e+00 3.0041462e+00 2.6022422e+00 3.0134290e+00 2.4087504e+00 3.3085101e+00 2.5050799e+00 3.4038679e+00 3.2010814e+00 2.8036959e+00 2.9060895e+00 3.3058271e+00 3.5063866e+00 3.0043212e+00 2.0122773e+00 2.3159426e+00 2.2186306e+00 2.4051454e+00 3.6029749e+00 3.0041461e+00 3.0062373e+00 3.2059465e+00 2.9096170e+00 2.6032656e+00 2.5097004e+00 2.9028411e+00 3.1023606e+00 2.5057847e+00 1.8685354e+00 2.7039990e+00 2.7016498e+00 2.7029428e+00 2.8028074e+00 1.5747520e+00 2.6039937e+00 4.5157550e+00 3.6083209e+00 4.4088451e+00 4.1031691e+00 4.3089952e+00 5.1095334e+00 3.0117336e+00 4.8052574e+00 4.3035619e+00 4.6175091e+00 3.6116958e+00 3.8067089e+00 4.0107159e+00 3.5138361e+00 3.6350483e+00 3.8210210e+00 4.0037985e+00 5.2113565e+00 5.4105254e+00 3.5063553e+00 4.2147222e+00 3.4147657e+00 5.2097995e+00 3.4081036e+00 4.2080425e+00 4.5057296e+00 3.3089414e+00 3.4074852e+00 4.1084282e+00 4.3058539e+00 4.6088153e+00 4.9193995e+00 4.1112251e+00 3.6020843e+00 4.1009356e+00 4.6223848e+00 4.1190046e+00 4.0036188e+00 3.3085886e+00 3.9129256e+00 4.1197933e+00 3.6305006e+00 3.6083209e+00 4.4113183e+00 4.2225427e+00 3.7249938e+00 3.5105217e+00 3.7103007e+00 3.9184088e+00 3.6056580e+00 4.0125062e-01 5.7609230e-01 1.0095367e+00 1.0776296e+00 6.3322667e-01 3.0490481e-01 9.0140221e-01 4.1212852e-01 6.0000317e-01 3.4085233e-01 6.0035305e-01 3.3818226e-01 3.0000000e-01 4.0122873e-01 2.2538848e-01 4.0004442e-01 4.0122873e-01 2.0061436e-01 3.0000000e-01 6.0017982e-01 7.0462844e-01 8.5406616e-01 3.0026460e-01 4.0243965e-01 7.0088477e-01 3.0026460e-01 4.5783248e-01 3.0008832e-01 3.0490481e-01 1.1002025e+00 4.1315633e-01 4.0125062e-01 4.2362917e-01 4.0125062e-01 4.1209001e-01 2.4170870e-01 5.0436965e-01 2.2573593e-01 3.1712557e+00 2.9203034e+00 3.3425817e+00 2.4092081e+00 3.0228582e+00 2.9024211e+00 3.1131137e+00 1.7168003e+00 3.0276611e+00 2.3094323e+00 1.9540727e+00 2.6109956e+00 2.4153242e+00 3.1056218e+00 2.0123796e+00 2.8520945e+00 2.9050328e+00 2.5029614e+00 2.9148948e+00 2.3042831e+00 3.2109395e+00 2.4161682e+00 3.3086859e+00 3.1042389e+00 2.7244207e+00 2.8394157e+00 3.2369857e+00 3.4247142e+00 2.9077271e+00 1.9085444e+00 2.2064916e+00 2.1068047e+00 2.3066817e+00 3.5042241e+00 2.9048033e+00 2.9102290e+00 3.1338090e+00 2.8169587e+00 2.5042601e+00 2.4061715e+00 2.8017212e+00 3.0065627e+00 2.4058322e+00 1.7261843e+00 2.6037439e+00 2.6027120e+00 2.6040234e+00 2.7127458e+00 1.4350761e+00 2.5049231e+00 4.4189015e+00 3.5095669e+00 4.3257995e+00 4.0057109e+00 4.2135057e+00 5.0324952e+00 2.9113810e+00 4.7221382e+00 4.2099962e+00 4.5353918e+00 3.5215862e+00 3.7118930e+00 3.9240025e+00 3.4150232e+00 3.5401623e+00 3.7282910e+00 3.9092259e+00 5.1365012e+00 5.3314853e+00 3.4049933e+00 4.1286955e+00 3.3168890e+00 5.1347989e+00 3.3143385e+00 4.1161770e+00 4.4243750e+00 3.2143454e+00 3.3110189e+00 4.0125032e+00 4.2289520e+00 4.5343227e+00 4.8661173e+00 4.0156353e+00 3.5063553e+00 4.0017163e+00 4.5675364e+00 4.0235140e+00 3.9076272e+00 3.2116700e+00 3.8321139e+00 4.0301570e+00 3.5598557e+00 3.5095669e+00 4.3201293e+00 4.1322798e+00 3.6413292e+00 3.4157005e+00 3.6188994e+00 3.8226858e+00 3.5071409e+00 5.0436235e-01 1.1269511e+00 1.4180734e+00 9.1446938e-01 5.0476836e-01 9.6593231e-01 8.0051115e-01 6.1119558e-01 7.0176271e-01 6.0964891e-01 4.3213914e-01 5.2133179e-01 2.2573593e-01 4.1420960e-01 5.2133802e-01 4.5078948e-01 2.2608083e-01 2.0121983e-01 6.1119558e-01 1.1005364e+00 1.2089192e+00 1.2085435e-01 2.4195741e-01 7.1621884e-01 1.2085435e-01 4.0004442e-01 4.1212852e-01 5.0085236e-01 7.0096858e-01 4.0127250e-01 5.6394820e-01 8.0967961e-01 2.0000000e-01 8.0051115e-01 2.2573593e-01 7.1621884e-01 3.0482299e-01 3.3545239e+00 3.1166331e+00 3.5333785e+00 2.6054739e+00 3.2183845e+00 3.1025789e+00 3.3116521e+00 1.9046783e+00 3.2211369e+00 2.5096353e+00 2.1074907e+00 2.8107054e+00 2.6064541e+00 3.3051050e+00 2.2121875e+00 3.0393610e+00 3.1054994e+00 2.7022579e+00 3.1107490e+00 2.5027328e+00 3.4112739e+00 2.6129479e+00 3.5073688e+00 3.3035252e+00 2.9185900e+00 3.0300451e+00 3.4286400e+00 3.6205854e+00 3.1074470e+00 2.1053074e+00 2.4030297e+00 2.3022754e+00 2.5057763e+00 3.7043108e+00 3.1053329e+00 3.1100313e+00 3.3265652e+00 3.0118276e+00 2.7046025e+00 2.6052853e+00 3.0016501e+00 3.2059133e+00 2.6047974e+00 1.9052628e+00 2.8038694e+00 2.8028007e+00 2.8041967e+00 2.9102290e+00 1.6179159e+00 2.7049931e+00 4.6192199e+00 3.7100254e+00 4.5225779e+00 4.2056438e+00 4.4133506e+00 5.2278849e+00 3.1114444e+00 4.9186970e+00 4.4088300e+00 4.7323336e+00 3.7201124e+00 3.9113387e+00 4.1217116e+00 3.6152935e+00 3.7397620e+00 3.9276515e+00 4.1085246e+00 5.3314853e+00 5.5274937e+00 3.6037456e+00 4.3264210e+00 3.5173586e+00 5.3296471e+00 3.5134601e+00 4.3151165e+00 4.6204664e+00 3.4137985e+00 3.5110031e+00 4.2123903e+00 4.4237218e+00 4.7288133e+00 5.0555470e+00 4.2155430e+00 3.7056457e+00 4.2016096e+00 4.7574592e+00 4.2235569e+00 4.1072664e+00 3.4118179e+00 4.0283196e+00 4.2288238e+00 3.7532858e+00 3.7100254e+00 4.5190617e+00 4.3311362e+00 3.8383398e+00 3.6148683e+00 3.8177286e+00 4.0227665e+00 3.7075359e+00 1.5237054e+00 1.5778323e+00 1.1528553e+00 8.0928056e-01 1.4109657e+00 9.0296858e-01 1.1060939e+00 8.5617086e-01 6.0184934e-01 8.2671175e-01 8.1112984e-01 7.1621748e-01 7.2113820e-01 9.0642722e-01 9.0166476e-01 5.2167829e-01 5.6347978e-01 1.1011719e+00 1.1531951e+00 1.3523685e+00 6.0948506e-01 7.0008735e-01 1.2012929e+00 6.0948506e-01 2.0121983e-01 8.0488008e-01 7.1636719e-01 7.0025283e-01 2.2608083e-01 7.4418186e-01 9.6702272e-01 5.0476836e-01 9.0657583e-01 3.4085233e-01 1.0214933e+00 7.0176271e-01 3.7102713e+00 3.4382051e+00 3.8712380e+00 2.9060895e+00 3.5425492e+00 3.4047913e+00 3.6240078e+00 2.2025238e+00 3.5521653e+00 2.8062729e+00 2.4042873e+00 3.1166330e+00 2.9229849e+00 3.6127194e+00 2.5155829e+00 3.3866455e+00 3.4056098e+00 3.0096888e+00 3.4232171e+00 2.8068501e+00 3.7118528e+00 2.9335034e+00 3.8176417e+00 3.6116893e+00 3.2480452e+00 3.3690976e+00 3.7643838e+00 3.9429243e+00 3.4137984e+00 2.4191998e+00 2.7057317e+00 2.6060678e+00 2.8148779e+00 4.0071259e+00 3.4042418e+00 3.4154455e+00 3.6592943e+00 3.3320889e+00 3.0065584e+00 2.9059779e+00 3.3025806e+00 3.5145393e+00 2.9126049e+00 2.2031052e+00 3.1056091e+00 3.1065947e+00 3.1074470e+00 3.2280982e+00 1.9100994e+00 3.0087060e+00 4.9179684e+00 4.0090033e+00 4.8406432e+00 4.5097222e+00 4.7173415e+00 5.5505575e+00 3.4073974e+00 5.2376127e+00 4.7184838e+00 5.0477042e+00 4.0301568e+00 4.2181242e+00 4.4357103e+00 3.9120615e+00 4.0296437e+00 4.2295175e+00 4.4165186e+00 5.6553854e+00 5.8478137e+00 3.9072483e+00 4.6391758e+00 3.8129545e+00 5.6540008e+00 3.8217034e+00 4.6242406e+00 4.9412981e+00 3.7201124e+00 3.8146271e+00 4.5162248e+00 4.7490801e+00 5.0547228e+00 5.3951639e+00 4.5184830e+00 4.0138270e+00 4.5043856e+00 5.0949990e+00 4.5225786e+00 4.4133506e+00 3.7138970e+00 4.3475342e+00 4.5353918e+00 4.0747727e+00 4.0090033e+00 4.8274110e+00 4.6357670e+00 4.1493188e+00 3.9212879e+00 4.1268500e+00 4.3214438e+00 4.0081754e+00 4.1317535e-01 4.0127250e-01 7.1629303e-01 5.0043842e-01 7.0096858e-01 6.3912709e-01 7.0184453e-01 1.2003596e+00 7.9871893e-01 1.0286506e+00 1.0433442e+00 8.2635069e-01 6.3309012e-01 6.7626502e-01 1.1286016e+00 1.0782105e+00 6.1135434e-01 6.0184934e-01 3.0915245e-01 1.0143978e+00 9.0155393e-01 5.0436965e-01 1.0143978e+00 1.4324323e+00 7.4329414e-01 8.0879776e-01 1.7570482e+00 1.4092511e+00 8.1343016e-01 7.8895472e-01 1.1269511e+00 7.0470720e-01 1.2189701e+00 5.0855077e-01 8.5406616e-01 3.5025396e+00 3.3027388e+00 3.7022129e+00 2.8281704e+00 3.4036672e+00 3.3025779e+00 3.5030234e+00 2.1725076e+00 3.4018155e+00 2.7109019e+00 2.4518621e+00 3.0049127e+00 2.8364042e+00 3.5019450e+00 2.4088882e+00 3.2025657e+00 3.3031143e+00 2.9050277e+00 3.3192099e+00 2.7159616e+00 3.6057054e+00 2.8056568e+00 3.7048624e+00 3.5016345e+00 3.1026586e+00 3.2026875e+00 3.6024856e+00 3.8034160e+00 3.3035252e+00 2.3226028e+00 2.6270968e+00 2.5319718e+00 2.7081195e+00 3.9029099e+00 3.3031170e+00 3.3039553e+00 3.5023855e+00 3.2150432e+00 2.9028412e+00 2.8148779e+00 3.2051933e+00 3.4018781e+00 2.8098127e+00 2.1974660e+00 3.0055598e+00 3.0017653e+00 3.0030650e+00 3.1026235e+00 1.9002712e+00 2.9047832e+00 4.8115512e+00 3.9065683e+00 4.7047990e+00 4.4023911e+00 4.6064349e+00 5.4038141e+00 3.3119500e+00 5.1019028e+00 4.6029848e+00 4.9110422e+00 3.9076509e+00 4.1051819e+00 4.3067850e+00 3.8114951e+00 3.9246405e+00 4.1145310e+00 4.3025710e+00 5.5046681e+00 5.7049556e+00 3.8098343e+00 4.5095384e+00 3.7106240e+00 5.5035834e+00 3.7063915e+00 4.5052925e+00 4.8020893e+00 3.6066590e+00 3.7052383e+00 4.4062090e+00 4.6017113e+00 4.9033376e+00 5.2065497e+00 4.4082090e+00 3.9018737e+00 4.4013937e+00 4.9097097e+00 4.4135256e+00 4.3024877e+00 3.6059708e+00 4.2076410e+00 4.4136664e+00 3.9189006e+00 3.9065683e+00 4.7076752e+00 4.5157700e+00 4.0166034e+00 3.8091065e+00 4.0069397e+00 4.2129114e+00 3.9040721e+00 5.0476836e-01 9.1422402e-01 6.0017982e-01 6.7616723e-01 1.0001903e+00 7.4262850e-01 1.1298636e+00 1.1055799e+00 1.0782211e+00 1.4043036e+00 1.0207260e+00 9.0508712e-01 1.0030871e+00 1.2632996e+00 1.3253497e+00 1.0001598e+00 5.0855077e-01 2.4195741e-01 1.3131369e+00 1.2089895e+00 9.0007572e-01 1.3131369e+00 1.5263518e+00 1.0087393e+00 9.3308891e-01 2.1138769e+00 1.4140515e+00 9.3308891e-01 6.8160885e-01 1.4180436e+00 6.7626681e-01 1.3018145e+00 7.0470720e-01 1.1133986e+00 3.2054626e+00 3.0041787e+00 3.4044439e+00 2.6382589e+00 3.1129223e+00 3.0138245e+00 3.2030205e+00 2.1566438e+00 3.1086927e+00 2.4554557e+00 2.5269479e+00 2.7127202e+00 2.6737930e+00 3.2074207e+00 2.1510232e+00 2.9068053e+00 3.0077106e+00 2.6369414e+00 3.0816280e+00 2.4971338e+00 3.3055260e+00 2.5325155e+00 3.4206210e+00 3.2100081e+00 2.8135926e+00 2.9088609e+00 3.3100014e+00 3.5053029e+00 3.0107283e+00 2.1554609e+00 2.4508792e+00 2.3794578e+00 2.4537333e+00 3.6090037e+00 3.0077114e+00 3.0034200e+00 3.2047284e+00 2.9729700e+00 2.6131590e+00 2.5821442e+00 2.9309340e+00 3.1060464e+00 2.5610212e+00 2.2285414e+00 2.7317035e+00 2.7106184e+00 2.7159615e+00 2.8134622e+00 1.9767345e+00 2.6270952e+00 4.5095106e+00 3.6117736e+00 4.4050179e+00 4.1034650e+00 4.3058769e+00 5.1048430e+00 3.0395885e+00 4.8030341e+00 4.3077256e+00 4.6095748e+00 3.6067573e+00 3.8091370e+00 4.0067454e+00 3.5235094e+00 3.6257071e+00 3.8125141e+00 4.0031827e+00 5.2054101e+00 5.4066794e+00 3.5404367e+00 4.2082468e+00 3.4146523e+00 5.2054259e+00 3.4138230e+00 4.2042865e+00 4.5025743e+00 3.3123786e+00 3.4067938e+00 4.1072911e+00 4.3032007e+00 4.6053784e+00 4.9093646e+00 4.1089590e+00 3.6062594e+00 4.1061436e+00 4.6117560e+00 4.1111295e+00 4.0026055e+00 3.3078313e+00 3.9072844e+00 4.1120111e+00 3.6177796e+00 3.6117736e+00 4.4064445e+00 4.2133758e+00 3.7157992e+00 3.5215805e+00 3.7072609e+00 3.9105673e+00 3.6051689e+00 4.1212852e-01 4.1212852e-01 3.0490481e-01 5.2167208e-01 3.0915245e-01 8.0097499e-01 6.1119558e-01 6.9518117e-01 9.0168933e-01 5.2491131e-01 4.0363334e-01 5.0085236e-01 7.8940551e-01 8.2462252e-01 5.0042326e-01 3.1328089e-01 3.0490481e-01 8.0928056e-01 7.0470867e-01 4.0125062e-01 8.0928056e-01 1.0776294e+00 5.0517282e-01 4.5078948e-01 1.6096629e+00 1.0207396e+00 4.5847767e-01 6.0184622e-01 9.1422402e-01 3.4085233e-01 8.5406674e-01 2.4195741e-01 6.0964891e-01 3.4079041e+00 3.2018548e+00 3.6045966e+00 2.7227151e+00 3.3029106e+00 3.2014779e+00 3.4016816e+00 2.0608234e+00 3.3024690e+00 2.6067721e+00 2.3393392e+00 2.9023862e+00 2.7310942e+00 3.4010299e+00 2.3048574e+00 3.1044057e+00 3.2014774e+00 2.8036023e+00 3.2152055e+00 2.6124447e+00 3.5030234e+00 2.7035171e+00 3.6034258e+00 3.4010358e+00 3.0022434e+00 3.1033306e+00 3.5041120e+00 3.7031277e+00 3.2018065e+00 2.2177966e+00 2.5220687e+00 2.4265152e+00 2.6055197e+00 3.8016494e+00 3.2014773e+00 3.2019092e+00 3.4031881e+00 3.1122360e+00 2.8013347e+00 2.7110046e+00 3.1036553e+00 3.3009330e+00 2.7070770e+00 2.0855888e+00 2.9035486e+00 2.9008500e+00 2.9016034e+00 3.0016038e+00 1.7857124e+00 2.8028019e+00 4.7076061e+00 3.8037955e+00 4.6049801e+00 4.3013465e+00 4.5040960e+00 5.3068325e+00 3.2075036e+00 5.0037550e+00 4.5023523e+00 4.8096009e+00 3.8048551e+00 4.0031892e+00 4.2051335e+00 3.7071735e+00 3.8161632e+00 4.0093901e+00 4.2016368e+00 5.4081547e+00 5.6075434e+00 3.7075525e+00 4.4072835e+00 3.6062405e+00 5.4074634e+00 3.6038441e+00 4.4036959e+00 4.7038247e+00 3.5038075e+00 3.6028345e+00 4.3038299e+00 4.5042394e+00 4.8062731e+00 5.1150203e+00 4.3051629e+00 3.8011413e+00 4.3008955e+00 4.8153681e+00 4.3087925e+00 4.2014602e+00 3.5032125e+00 4.1063865e+00 4.3094598e+00 3.8146853e+00 3.8037955e+00 4.6054982e+00 4.4109814e+00 3.9115832e+00 3.7058197e+00 3.9043917e+00 4.1081838e+00 3.8021570e+00 6.0365948e-01 3.0008832e-01 3.3818226e-01 2.0121983e-01 5.2133802e-01 3.0915245e-01 5.0437695e-01 5.0043842e-01 2.0181667e-01 1.2085435e-01 1.2085435e-01 4.1317535e-01 4.1317535e-01 3.0026460e-01 6.0018299e-01 7.0462697e-01 4.0246123e-01 3.0490481e-01 4.0004442e-01 4.0246123e-01 7.1621884e-01 1.2085435e-01 1.1269424e-01 1.2036863e+00 7.0088627e-01 3.0482299e-01 5.0436965e-01 5.0436235e-01 3.0482299e-01 5.0436965e-01 2.2608083e-01 2.0121983e-01 3.3237063e+00 3.1056091e+00 3.5138377e+00 2.6067805e+00 3.2064817e+00 3.1008890e+00 3.3041599e+00 1.9144935e+00 3.2074507e+00 2.5042498e+00 2.1492024e+00 2.8038903e+00 2.6091437e+00 3.3015588e+00 2.2041706e+00 3.0148613e+00 3.1021975e+00 2.7007716e+00 3.1069083e+00 2.5027328e+00 3.4052051e+00 2.6037226e+00 3.5028446e+00 3.3009330e+00 2.9058292e+00 3.0107430e+00 3.4113341e+00 3.6081814e+00 3.1026177e+00 2.1035154e+00 2.4051766e+00 2.3058791e+00 2.5019964e+00 3.7017412e+00 3.1021847e+00 3.1038570e+00 3.3100818e+00 3.0059450e+00 2.7015162e+00 2.6035107e+00 3.0009631e+00 3.2017847e+00 2.6021247e+00 1.9231085e+00 2.8015882e+00 2.8007533e+00 2.8013565e+00 2.9028946e+00 1.6222582e+00 2.7017239e+00 4.6112605e+00 3.7050457e+00 4.5107898e+00 4.2023583e+00 4.4067851e+00 5.2146266e+00 3.1060428e+00 4.9089682e+00 4.4037570e+00 4.7173415e+00 3.7092301e+00 3.9050336e+00 4.1101936e+00 3.6083379e+00 3.7235997e+00 3.9149881e+00 4.1034584e+00 5.3169366e+00 5.5149065e+00 3.6029421e+00 4.3133953e+00 3.5091580e+00 5.3158283e+00 3.5057369e+00 4.3071174e+00 4.6095441e+00 3.4059695e+00 3.5048429e+00 4.2061215e+00 4.4109767e+00 4.7143113e+00 5.0310424e+00 4.2080636e+00 3.7018984e+00 4.2005765e+00 4.7313463e+00 4.2134002e+00 4.1029723e+00 3.4053426e+00 4.0133308e+00 4.2155438e+00 3.7272780e+00 3.7050457e+00 4.5097224e+00 4.3174320e+00 3.8199266e+00 3.6070223e+00 3.8081328e+00 4.0126677e+00 3.7034791e+00 6.0017665e-01 4.1210927e-01 6.0018299e-01 1.1133986e+00 6.3178534e-01 9.0142681e-01 8.5403486e-01 7.0462844e-01 5.0477564e-01 5.2491734e-01 1.0087252e+00 9.3306807e-01 4.1317535e-01 5.0517282e-01 4.1317535e-01 8.5409862e-01 7.5503094e-01 4.1317535e-01 8.5409862e-01 1.3133231e+00 6.0964891e-01 7.0548138e-01 1.5640758e+00 1.3027556e+00 7.0176271e-01 6.0017982e-01 9.6591433e-01 6.0000635e-01 1.1056693e+00 4.0127250e-01 7.1700909e-01 3.0056049e+00 2.8037508e+00 3.2038047e+00 2.3350905e+00 2.9043042e+00 2.8024566e+00 3.0040963e+00 1.7133143e+00 2.9021652e+00 2.2134864e+00 2.0299551e+00 2.5066443e+00 2.3464211e+00 3.0020171e+00 1.9120296e+00 2.7041827e+00 2.8038683e+00 2.4047867e+00 2.8219468e+00 2.2186306e+00 3.1079220e+00 2.3063026e+00 3.2048524e+00 3.0013665e+00 2.6029242e+00 2.7037323e+00 3.1033714e+00 3.3046348e+00 2.8041978e+00 1.8296471e+00 2.1344310e+00 2.0421601e+00 2.2088481e+00 3.4030563e+00 2.8038694e+00 2.8056176e+00 3.0035303e+00 2.7166861e+00 2.4032760e+00 2.3173405e+00 2.7049931e+00 2.9020943e+00 2.3107074e+00 1.7540491e+00 2.5057744e+00 2.5017294e+00 2.5032614e+00 2.6027348e+00 1.4738792e+00 2.4051328e+00 4.3150879e+00 3.4081972e+00 4.2065765e+00 3.9027794e+00 4.1082339e+00 4.9060122e+00 2.8144592e+00 4.6029848e+00 4.1031680e+00 4.4149701e+00 3.4105998e+00 3.6062867e+00 3.8091132e+00 3.3145497e+00 3.4354252e+00 3.6203140e+00 3.8031346e+00 5.0073650e+00 5.2071845e+00 3.3100796e+00 4.0129286e+00 3.2145637e+00 5.0059527e+00 3.2079238e+00 4.0069158e+00 4.3033003e+00 3.1086424e+00 3.2069551e+00 3.9078313e+00 4.1030253e+00 4.4053246e+00 4.7120702e+00 3.9105941e+00 3.4019027e+00 3.9011588e+00 4.4155733e+00 3.9183552e+00 3.8030509e+00 3.1080886e+00 3.7106642e+00 3.9186175e+00 3.4279064e+00 3.4081972e+00 4.2100505e+00 4.0214626e+00 3.5236480e+00 3.3110446e+00 3.5093262e+00 3.7177968e+00 3.4052047e+00 4.1317535e-01 1.1269424e-01 5.6371422e-01 5.0084481e-01 4.5784410e-01 8.0000239e-01 4.0006662e-01 3.0017653e-01 4.0006662e-01 6.0948800e-01 7.0088627e-01 4.1210927e-01 3.0482299e-01 4.5080200e-01 7.0016860e-01 6.0184934e-01 4.1317535e-01 7.0016860e-01 8.5406674e-01 4.0002221e-01 3.0482299e-01 1.5012719e+00 7.4269314e-01 3.3818226e-01 4.0002221e-01 8.0046685e-01 1.1269424e-01 6.3165225e-01 2.0121983e-01 5.0002283e-01 3.2274206e+00 3.0066036e+00 3.4159337e+00 2.5238518e+00 3.1081931e+00 3.0018108e+00 3.2048307e+00 1.8672243e+00 3.1090333e+00 2.4088880e+00 2.1557835e+00 2.7050009e+00 2.5327574e+00 3.2021240e+00 2.1075767e+00 2.9175658e+00 3.0027966e+00 2.6034860e+00 3.0173373e+00 2.4124132e+00 3.3060311e+00 2.5063233e+00 3.4049933e+00 3.2016467e+00 2.8074882e+00 2.9128790e+00 3.3135401e+00 3.5094636e+00 3.0034944e+00 2.0185049e+00 2.3226176e+00 2.2272636e+00 2.4061715e+00 3.6025233e+00 3.0027816e+00 3.0045159e+00 3.2117473e+00 2.9147768e+00 2.6022650e+00 2.5117111e+00 2.9035536e+00 3.1022707e+00 2.5074705e+00 1.8959538e+00 2.7040250e+00 2.7012715e+00 2.7023320e+00 2.8040244e+00 1.6015419e+00 2.6035923e+00 4.5125057e+00 3.6062867e+00 4.4120448e+00 4.1027436e+00 4.3076126e+00 5.1160601e+00 3.0101869e+00 4.8099407e+00 4.3047534e+00 4.6192050e+00 3.6105351e+00 3.8061121e+00 4.0115220e+00 3.5110245e+00 3.6271576e+00 3.8169672e+00 4.0039490e+00 5.2185416e+00 5.4163883e+00 3.5078417e+00 4.2149895e+00 3.4109302e+00 5.2173835e+00 3.4072913e+00 4.2079670e+00 4.5106052e+00 3.3073674e+00 3.4056857e+00 4.1070396e+00 4.3122866e+00 4.6159499e+00 4.9341410e+00 4.1092167e+00 3.6024856e+00 4.1011078e+00 4.6347105e+00 4.1150272e+00 4.0033723e+00 3.3063033e+00 3.9150644e+00 4.1174503e+00 3.6310621e+00 3.6062867e+00 4.4108290e+00 4.2194920e+00 3.7226812e+00 3.5095242e+00 3.7093173e+00 3.9142888e+00 3.6040604e+00 3.4342562e-01 8.5406616e-01 3.3813251e-01 6.0017665e-01 4.5078948e-01 4.0125062e-01 2.2573593e-01 3.0474106e-01 7.0008584e-01 6.0184622e-01 2.2538848e-01 7.0017011e-01 8.0046685e-01 5.0477564e-01 5.2167208e-01 4.0004442e-01 5.0477564e-01 1.0016896e+00 3.0474106e-01 4.5080200e-01 1.1531953e+00 1.0008617e+00 4.5080200e-01 4.1420960e-01 6.1119558e-01 4.1210927e-01 8.0051036e-01 3.0482299e-01 4.1210927e-01 3.0158412e+00 2.8068284e+00 3.2097099e+00 2.3108757e+00 2.9065890e+00 2.8022000e+00 3.0066661e+00 1.6225614e+00 2.9048017e+00 2.2116239e+00 1.8685354e+00 2.5096708e+00 2.3100354e+00 3.0026666e+00 1.9136652e+00 2.7108290e+00 2.8056165e+00 2.4010446e+00 2.8094419e+00 2.2042326e+00 3.1114445e+00 2.3060252e+00 3.2036636e+00 3.0010404e+00 2.6048201e+00 2.7083838e+00 3.1074856e+00 3.3083888e+00 2.8056953e+00 1.8055938e+00 2.1074903e+00 2.0078120e+00 2.2044098e+00 3.4034897e+00 2.8056164e+00 2.8086638e+00 3.0080453e+00 2.7058598e+00 2.4044765e+00 2.3071636e+00 2.7018643e+00 2.9031229e+00 2.3040302e+00 1.6345914e+00 2.5039390e+00 2.5021287e+00 2.5037126e+00 2.6035547e+00 1.3485963e+00 2.4045982e+00 4.3195471e+00 3.4105069e+00 4.2110205e+00 3.9039624e+00 4.1112641e+00 4.9115281e+00 2.8134622e+00 4.6064153e+00 4.1040152e+00 4.4216160e+00 3.4153329e+00 3.6083651e+00 3.8136432e+00 3.3170079e+00 3.4454804e+00 3.6271127e+00 3.8048208e+00 5.0136911e+00 5.2125102e+00 3.3041886e+00 4.0185527e+00 3.2193583e+00 5.0117789e+00 3.2102572e+00 4.0101485e+00 4.3071174e+00 3.1116727e+00 3.2099137e+00 3.9105756e+00 4.1073261e+00 4.4108290e+00 4.7236350e+00 3.9141183e+00 3.4025037e+00 3.9008223e+00 4.4275986e+00 3.9240716e+00 3.8046112e+00 3.1114731e+00 3.7165788e+00 3.9250546e+00 3.4397950e+00 3.4105069e+00 4.2141201e+00 4.0283725e+00 3.5323888e+00 3.3126331e+00 3.5133654e+00 3.7236064e+00 3.4073749e+00 5.6371422e-01 4.0125062e-01 4.2362917e-01 7.0008735e-01 3.0017653e-01 2.2573593e-01 3.0490481e-01 5.2167829e-01 6.0202028e-01 3.3808272e-01 4.1210927e-01 5.2167829e-01 6.0201716e-01 5.0477564e-01 4.0363334e-01 6.0201716e-01 7.8895472e-01 3.0474106e-01 2.2608083e-01 1.4017696e+00 7.1636719e-01 2.2608083e-01 4.0002221e-01 7.0088627e-01 2.0121983e-01 5.6371422e-01 2.2538848e-01 4.0127250e-01 3.2269400e+00 3.0055754e+00 3.4153574e+00 2.5158464e+00 3.1070003e+00 3.0010043e+00 3.2037264e+00 1.8447764e+00 3.1084925e+00 2.4051329e+00 2.1169795e+00 2.7031257e+00 2.5230194e+00 3.2014729e+00 2.1040690e+00 2.9167440e+00 3.0016616e+00 2.6020655e+00 3.0122358e+00 2.4077390e+00 3.3040889e+00 2.5044039e+00 3.4036241e+00 3.2011770e+00 2.8066054e+00 2.9119764e+00 3.3128842e+00 3.5083774e+00 3.0022542e+00 2.0112442e+00 2.3146813e+00 2.2178117e+00 2.4035997e+00 3.6016272e+00 3.0016466e+00 3.0030180e+00 3.2109724e+00 2.9107838e+00 2.6012056e+00 2.5072166e+00 2.9020943e+00 3.1016037e+00 2.5045154e+00 1.8665804e+00 2.7022828e+00 2.7006623e+00 2.7012715e+00 2.8031364e+00 1.5665127e+00 2.6019940e+00 4.5096473e+00 3.6042722e+00 4.4108394e+00 4.1020090e+00 4.3058539e+00 5.1154681e+00 3.0065585e+00 4.8095984e+00 4.3039464e+00 4.6166518e+00 3.6081814e+00 3.8045576e+00 4.0096177e+00 3.5076367e+00 3.6204901e+00 3.8129595e+00 4.0031500e+00 5.2178509e+00 5.4155855e+00 3.5053718e+00 4.2125027e+00 3.4076329e+00 5.2169557e+00 3.4052726e+00 4.2064771e+00 4.5101686e+00 3.3051940e+00 3.4039470e+00 4.1052782e+00 4.3120002e+00 4.6153660e+00 4.9336188e+00 4.1069501e+00 3.6018985e+00 4.1007374e+00 4.6331222e+00 4.1114855e+00 4.0025890e+00 3.3042986e+00 3.9129418e+00 4.1139050e+00 3.6259501e+00 3.6042722e+00 4.4088300e+00 4.2155438e+00 3.7181244e+00 3.5068261e+00 3.7072136e+00 3.9107458e+00 3.6027359e+00 7.1779518e-01 9.0005048e-01 6.8160885e-01 6.0980961e-01 6.3164977e-01 6.0964597e-01 6.0948506e-01 6.3178534e-01 8.0888055e-01 6.5712813e-01 9.1552331e-01 5.6595908e-01 4.5147187e-01 9.0026543e-01 5.6595908e-01 6.0201716e-01 5.6370994e-01 4.1212852e-01 1.3000455e+00 4.1315633e-01 6.1830764e-01 9.0511169e-01 6.0964891e-01 6.3178534e-01 4.5077696e-01 7.1621748e-01 4.5783248e-01 3.7510248e+00 3.5145413e+00 3.9319585e+00 3.0060350e+00 3.6168418e+00 3.5015800e+00 3.7092301e+00 2.3098782e+00 3.6209185e+00 2.9036019e+00 2.5319798e+00 3.2059465e+00 3.0125595e+00 3.7043511e+00 2.6050088e+00 3.4362995e+00 3.5023713e+00 3.1027850e+00 3.5112541e+00 2.9034027e+00 3.8056232e+00 3.0109656e+00 3.9070007e+00 3.7037948e+00 3.3177283e+00 3.4278716e+00 3.8279200e+00 4.0185639e+00 3.5049370e+00 2.5063489e+00 2.8048596e+00 2.7053910e+00 2.9045816e+00 4.1028806e+00 3.5020661e+00 3.5059137e+00 3.7249599e+00 3.4134555e+00 3.1021033e+00 3.0035422e+00 3.4012314e+00 3.6049329e+00 3.0042977e+00 2.3151346e+00 3.2021240e+00 3.2018065e+00 3.2023319e+00 3.3095196e+00 2.0139907e+00 3.1028259e+00 5.0111326e+00 4.1049446e+00 4.9203200e+00 4.6042055e+00 4.8089555e+00 5.6273643e+00 3.5051449e+00 5.3190012e+00 4.8083867e+00 5.1260178e+00 4.1140184e+00 4.3082037e+00 4.5172930e+00 4.0074572e+00 4.1195081e+00 4.3162103e+00 4.5071301e+00 5.7305902e+00 5.9266306e+00 4.0041352e+00 4.7202045e+00 3.9078680e+00 5.7295930e+00 3.9093593e+00 4.7117434e+00 5.0203874e+00 3.8087102e+00 3.9064537e+00 4.6081315e+00 4.8239967e+00 5.1282911e+00 5.4538016e+00 4.6097450e+00 4.1052255e+00 4.6016323e+00 5.1527860e+00 4.6133719e+00 4.5057296e+00 3.8063301e+00 4.4231469e+00 4.6192070e+00 4.1384491e+00 4.1049446e+00 4.9142249e+00 4.7202041e+00 4.2256252e+00 4.0099717e+00 4.2125085e+00 4.4124585e+00 4.1039188e+00 3.4085233e-01 3.3818226e-01 1.2699992e-01 3.0922892e-01 3.3818226e-01 4.1212852e-01 3.4085233e-01 3.0490481e-01 8.0250202e-01 9.0192695e-01 4.0363334e-01 5.0437695e-01 4.5847767e-01 4.0363334e-01 7.0633229e-01 3.0482299e-01 4.0246123e-01 1.0095513e+00 7.0548283e-01 2.0181667e-01 5.0043084e-01 3.6452132e-01 5.0436965e-01 5.0855778e-01 4.1420960e-01 3.3813251e-01 3.0360001e+00 2.8068283e+00 3.2199554e+00 2.3040302e+00 2.9083228e+00 2.8004229e+00 3.0040677e+00 1.6099792e+00 2.9111118e+00 2.2023225e+00 1.8444678e+00 2.5026969e+00 2.3072196e+00 3.0013665e+00 1.9023442e+00 2.7227152e+00 2.8012528e+00 2.4005053e+00 2.8054838e+00 2.2013444e+00 3.1036553e+00 2.3040711e+00 3.2026875e+00 3.0010106e+00 2.6084695e+00 2.7159628e+00 3.1166009e+00 3.3100796e+00 2.8019019e+00 1.8020058e+00 2.1029252e+00 2.0034861e+00 2.2011906e+00 3.4011297e+00 2.8012319e+00 2.8028007e+00 3.0142197e+00 2.7060544e+00 2.4007552e+00 2.3017471e+00 2.7003774e+00 2.9016034e+00 2.3011979e+00 1.6179000e+00 2.5007284e+00 2.5003793e+00 2.5007008e+00 2.6035320e+00 1.3154932e+00 2.4008859e+00 4.3091529e+00 3.4034897e+00 4.2123904e+00 3.9018704e+00 4.1056542e+00 4.9181670e+00 2.8038684e+00 4.6114520e+00 4.1039624e+00 4.4180514e+00 3.4084525e+00 3.6042874e+00 3.8104403e+00 3.3060311e+00 3.4198458e+00 3.6127194e+00 3.8032956e+00 5.0208687e+00 5.2178587e+00 3.3018328e+00 4.0133297e+00 3.2067991e+00 5.0200323e+00 3.2048524e+00 4.0067585e+00 4.3122441e+00 3.1047671e+00 3.2036090e+00 3.9049697e+00 4.1148052e+00 4.4184267e+00 4.7404155e+00 3.9065727e+00 3.4018864e+00 3.9004186e+00 4.4392802e+00 3.9110280e+00 3.8025990e+00 3.1038577e+00 3.7145622e+00 3.9140895e+00 3.4287259e+00 3.4034897e+00 4.2090841e+00 4.0156240e+00 3.5189468e+00 3.3056781e+00 3.5073617e+00 3.7102604e+00 3.4023494e+00 4.1315633e-01 3.0915245e-01 4.5078948e-01 5.2132556e-01 3.0482299e-01 3.3808272e-01 6.0964597e-01 7.0911112e-01 8.6051414e-01 4.1212852e-01 7.0016860e-01 7.4262964e-01 4.1212852e-01 6.1830489e-01 4.1209001e-01 6.0018299e-01 1.1056693e+00 6.0964597e-01 4.1317535e-01 4.1315633e-01 5.2133179e-01 4.2268438e-01 5.0084481e-01 5.2491131e-01 5.0043084e-01 2.9115264e+00 2.6338022e+00 3.0658361e+00 2.1172959e+00 2.7373419e+00 2.6040822e+00 2.8212040e+00 1.4407364e+00 2.7450517e+00 2.0182279e+00 1.7116683e+00 2.3196029e+00 2.1285888e+00 2.8091316e+00 1.7264084e+00 2.5863714e+00 2.6084695e+00 2.2054529e+00 2.6248910e+00 2.0083311e+00 2.9174382e+00 2.1301354e+00 3.0136614e+00 2.8068911e+00 2.4421128e+00 2.5659281e+00 2.9581275e+00 3.1380442e+00 2.6129786e+00 1.6191482e+00 1.9129988e+00 1.8141046e+00 2.0129571e+00 3.2064817e+00 2.6080847e+00 2.6171503e+00 2.8540078e+00 2.5288353e+00 2.2078337e+00 2.1116321e+00 2.5029614e+00 2.7108347e+00 2.1109969e+00 1.4620239e+00 2.3067198e+00 2.3048725e+00 2.3072196e+00 2.4221911e+00 1.1960519e+00 2.2090466e+00 4.1263920e+00 3.2146438e+00 4.0362393e+00 3.7082866e+00 3.9191966e+00 4.7434452e+00 2.6190662e+00 4.4302264e+00 3.9142233e+00 4.2488425e+00 3.2328627e+00 3.4177609e+00 3.6349468e+00 3.1232336e+00 3.2606481e+00 3.4419771e+00 3.6135028e+00 4.8484858e+00 5.0414126e+00 3.1077602e+00 3.8409517e+00 3.0264502e+00 4.8462395e+00 3.0224855e+00 3.8231767e+00 4.1339850e+00 2.9228238e+00 3.0173096e+00 3.7181009e+00 3.9409635e+00 4.2473796e+00 4.5888422e+00 3.7226114e+00 3.2097424e+00 3.7024938e+00 4.2924779e+00 3.7339169e+00 3.6111693e+00 2.9185950e+00 3.5470876e+00 3.7434043e+00 3.2896416e+00 3.2146438e+00 4.0283196e+00 3.8460162e+00 3.3616756e+00 3.1242731e+00 3.3284650e+00 3.5333785e+00 3.2109426e+00 4.0122873e-01 5.0043084e-01 4.0243965e-01 3.0474106e-01 2.0061436e-01 4.5148429e-01 1.1000100e+00 1.2012928e+00 1.2699992e-01 4.0122873e-01 5.6595488e-01 1.2699992e-01 6.0184309e-01 4.0004442e-01 5.0436965e-01 7.1700909e-01 6.0201716e-01 5.2132556e-01 8.0051115e-01 2.2573593e-01 8.0000080e-01 4.0243965e-01 7.0088477e-01 3.0474106e-01 3.1428043e+00 2.9119661e+00 3.3252402e+00 2.4048325e+00 3.0131919e+00 2.9019362e+00 3.1087162e+00 1.7043719e+00 3.0148571e+00 2.3090280e+00 1.9099608e+00 2.6089255e+00 2.4039780e+00 3.1034773e+00 2.0109328e+00 2.8295063e+00 2.9047982e+00 2.5011632e+00 2.9079901e+00 2.3019339e+00 3.2101766e+00 2.4088882e+00 3.3051149e+00 3.1020645e+00 2.7127201e+00 2.8219255e+00 3.2211370e+00 3.4154432e+00 2.9057760e+00 1.9031964e+00 2.2023924e+00 2.1016800e+00 2.3040177e+00 3.5033831e+00 2.9047500e+00 2.9083094e+00 3.1195526e+00 2.8078790e+00 2.5037817e+00 2.4045555e+00 2.8012577e+00 3.0040677e+00 2.4032886e+00 1.7053664e+00 2.6031367e+00 2.6019751e+00 2.6032655e+00 2.7067267e+00 1.4186217e+00 2.5039390e+00 4.4180854e+00 3.5092124e+00 4.3179254e+00 4.0043989e+00 4.2115634e+00 5.0223341e+00 2.9108451e+00 4.7142986e+00 4.2064794e+00 4.5276385e+00 3.5169841e+00 3.7092301e+00 3.9177719e+00 3.4145764e+00 3.5398518e+00 3.7257231e+00 3.9064409e+00 5.1255523e+00 5.3223081e+00 3.4028319e+00 4.1224590e+00 3.3167394e+00 5.1238108e+00 3.3110167e+00 4.1123595e+00 4.4156295e+00 3.2116669e+00 3.3094499e+00 4.0106882e+00 4.2180724e+00 4.5227110e+00 4.8462592e+00 4.0138270e+00 3.5038530e+00 4.0010263e+00 4.5481710e+00 4.0222346e+00 3.9055783e+00 3.2104685e+00 3.8231767e+00 4.0259306e+00 3.5470873e+00 3.5092124e+00 4.3162096e+00 4.1285334e+00 3.6344358e+00 3.4126369e+00 3.6148618e+00 3.8215373e+00 3.5066394e+00 2.2608083e-01 2.4170870e-01 3.0915245e-01 3.0915245e-01 4.0002221e-01 7.0096858e-01 8.0888055e-01 3.3818226e-01 4.0243965e-01 5.0477564e-01 3.3818226e-01 6.1135434e-01 2.0121983e-01 3.0017653e-01 1.1020506e+00 6.0219099e-01 2.0061436e-01 4.1210927e-01 4.0246123e-01 4.0125062e-01 4.0363334e-01 3.4085233e-01 2.2573593e-01 3.1414744e+00 2.9090612e+00 3.3237063e+00 2.4058952e+00 3.0107723e+00 2.9007492e+00 3.1056084e+00 1.7139238e+00 3.0138400e+00 2.3035512e+00 1.9525301e+00 2.6040007e+00 2.4100386e+00 3.1020779e+00 2.0037730e+00 2.8273039e+00 2.9018637e+00 2.5009579e+00 2.9077468e+00 2.3022754e+00 3.2048985e+00 2.4059812e+00 3.3038272e+00 3.1015567e+00 2.7110304e+00 2.8196992e+00 3.2199879e+00 3.4126307e+00 2.9028597e+00 1.9035634e+00 2.2044603e+00 2.1052067e+00 2.3021298e+00 3.5016873e+00 2.9018153e+00 2.9040211e+00 3.1174676e+00 2.8083845e+00 2.5012694e+00 2.4028385e+00 2.8006965e+00 3.0024198e+00 2.4021168e+00 1.7233814e+00 2.6012647e+00 2.6007117e+00 2.6012056e+00 2.7050190e+00 1.4220898e+00 2.5015263e+00 4.4109742e+00 3.5045842e+00 4.3148937e+00 4.0025815e+00 4.2071337e+00 5.0208677e+00 2.9053047e+00 4.7134688e+00 4.2051335e+00 4.5213131e+00 3.5107904e+00 3.7056862e+00 3.9129303e+00 3.4076849e+00 3.5232606e+00 3.7154827e+00 3.9043905e+00 5.1238111e+00 5.3204854e+00 3.4027174e+00 4.1161770e+00 3.3085473e+00 5.1228006e+00 3.3065392e+00 4.1085246e+00 4.4144908e+00 3.2064337e+00 3.3048953e+00 4.0063737e+00 4.2173565e+00 4.5213359e+00 4.8449108e+00 4.0082529e+00 3.5026815e+00 4.0006690e+00 4.5442582e+00 4.0132894e+00 3.9035247e+00 3.2051958e+00 3.8177289e+00 4.0170262e+00 3.5340950e+00 3.5045842e+00 4.3111747e+00 4.1186693e+00 3.6228903e+00 3.4075338e+00 3.6094379e+00 3.8124787e+00 3.5031928e+00 1.1269424e-01 5.0436965e-01 4.5078948e-01 2.2573593e-01 6.0000317e-01 7.0088477e-01 4.1210927e-01 3.4080442e-01 3.0474106e-01 4.1210927e-01 8.0883841e-01 1.1269424e-01 2.2573593e-01 1.2089253e+00 8.0051036e-01 4.0125062e-01 4.1317535e-01 5.2133802e-01 3.0017653e-01 6.0184622e-01 2.0061436e-01 2.2573593e-01 3.2211375e+00 3.0065592e+00 3.4126307e+00 2.5097024e+00 3.1069750e+00 3.0016616e+00 3.2056674e+00 1.8201133e+00 3.1066333e+00 2.4080686e+00 2.0619186e+00 2.7068820e+00 2.5107632e+00 3.2022485e+00 2.1087015e+00 2.9137660e+00 3.0040552e+00 2.6010528e+00 3.0089164e+00 2.4039766e+00 3.3085605e+00 2.5050799e+00 3.4035383e+00 3.2010814e+00 2.8057187e+00 2.9102474e+00 3.3101488e+00 3.5088135e+00 3.0043212e+00 2.0051350e+00 2.3071665e+00 2.2078183e+00 2.4034084e+00 3.6027901e+00 3.0040510e+00 3.0064311e+00 3.2097125e+00 2.9065741e+00 2.6030847e+00 2.5057763e+00 2.9016034e+00 3.1025924e+00 2.5033702e+00 1.8310475e+00 2.7029487e+00 2.7015162e+00 2.7026433e+00 2.8034238e+00 1.5358653e+00 2.6032968e+00 4.5159027e+00 3.6080734e+00 4.4115652e+00 4.1033603e+00 4.3094200e+00 5.1138780e+00 3.0100866e+00 4.8082318e+00 4.3041941e+00 4.6203464e+00 3.6127197e+00 3.8070338e+00 4.0124958e+00 3.5130651e+00 3.6349183e+00 3.8215362e+00 4.0044029e+00 5.2162094e+00 5.4145184e+00 3.5039680e+00 4.2166537e+00 3.4145702e+00 5.2146334e+00 3.4083285e+00 4.2090727e+00 4.5089181e+00 3.3091123e+00 3.4076329e+00 4.1087140e+00 4.3098021e+00 4.6133860e+00 4.9287684e+00 4.1115100e+00 3.6023704e+00 4.1007820e+00 4.6309835e+00 4.1192351e+00 4.0040240e+00 3.3086516e+00 3.9156759e+00 4.1209257e+00 3.6344375e+00 3.6080734e+00 4.4124586e+00 4.2235561e+00 3.7268101e+00 3.5102374e+00 3.7111714e+00 3.9185866e+00 3.6056580e+00 5.0084481e-01 4.1315633e-01 2.2573593e-01 7.0000303e-01 8.0046605e-01 3.3818226e-01 2.4170870e-01 3.0017653e-01 3.3818226e-01 8.0245824e-01 1.1269424e-01 2.0181667e-01 1.1133897e+00 8.0004523e-01 4.0246123e-01 5.2167829e-01 4.5078948e-01 4.0125062e-01 6.0017665e-01 3.0017653e-01 2.0061436e-01 3.3182813e+00 3.1056085e+00 3.5110034e+00 2.6060742e+00 3.2059465e+00 3.1013637e+00 3.3048926e+00 1.9099680e+00 3.2056789e+00 2.5063346e+00 2.1344310e+00 2.8057704e+00 2.6059875e+00 3.3019214e+00 2.2068463e+00 3.0117188e+00 3.1034568e+00 2.7006623e+00 3.1063566e+00 2.5023073e+00 3.4074246e+00 2.6040822e+00 3.5028878e+00 3.3008913e+00 2.9048017e+00 3.0087102e+00 3.4087684e+00 3.6077011e+00 3.1036688e+00 2.1027637e+00 2.4039664e+00 2.3040177e+00 2.5024922e+00 3.7023994e+00 3.1034532e+00 3.1054994e+00 3.3083865e+00 3.0045919e+00 2.7025560e+00 2.6039937e+00 3.0011260e+00 3.2022183e+00 2.6023240e+00 1.9156198e+00 2.8022964e+00 2.8012577e+00 2.8021795e+00 2.9028597e+00 1.6190551e+00 2.7026433e+00 4.6143249e+00 3.7070367e+00 4.5103890e+00 4.2029881e+00 4.4084395e+00 5.2126509e+00 3.1082889e+00 4.9074567e+00 4.4036930e+00 4.7183734e+00 3.7111657e+00 3.9061763e+00 4.1111077e+00 3.6112621e+00 3.7306950e+00 3.9190472e+00 4.1039096e+00 5.3148048e+00 5.5132902e+00 3.6028435e+00 4.3148929e+00 3.5126667e+00 5.3133599e+00 3.5071916e+00 4.3081091e+00 4.6080296e+00 3.4078683e+00 3.5066415e+00 4.2077543e+00 4.4087821e+00 4.7120754e+00 5.0261502e+00 4.2102487e+00 3.7020547e+00 4.2006494e+00 4.7279956e+00 4.2171591e+00 4.1035746e+00 3.4074978e+00 4.0138994e+00 4.2186688e+00 3.7302926e+00 3.7070367e+00 4.5111938e+00 4.3210760e+00 3.8236433e+00 3.6087856e+00 3.8098356e+00 4.0164852e+00 3.7049593e+00 1.1269424e-01 7.0017011e-01 9.0506343e-01 1.0426636e+00 2.0181667e-01 4.1209001e-01 8.0093081e-01 2.0181667e-01 3.4080442e-01 4.0125062e-01 3.6259865e-01 9.0029064e-01 3.3808272e-01 4.2268438e-01 6.1135434e-01 2.2608083e-01 6.0948212e-01 2.0061436e-01 6.3164977e-01 3.0482299e-01 3.1902650e+00 2.9267417e+00 3.3545239e+00 2.4065479e+00 3.0300492e+00 2.9028462e+00 3.1166330e+00 1.7073273e+00 3.0369978e+00 2.3091363e+00 1.9242178e+00 2.6129479e+00 2.4148785e+00 3.1074477e+00 2.0138888e+00 2.8680310e+00 2.9053048e+00 2.5042875e+00 2.9165009e+00 2.3038195e+00 3.2116668e+00 2.4221589e+00 3.3110857e+00 3.1060464e+00 2.7333589e+00 2.8520935e+00 3.2480481e+00 3.4313924e+00 2.9094538e+00 1.9103596e+00 2.2042535e+00 2.1040084e+00 2.3086427e+00 3.5048916e+00 2.9048754e+00 2.9119661e+00 3.1440058e+00 2.8212158e+00 2.5048147e+00 2.4054865e+00 2.8016296e+00 3.0087060e+00 2.4071454e+00 1.7108740e+00 2.6040234e+00 2.6035022e+00 2.6047905e+00 2.7176633e+00 1.4222462e+00 2.5057847e+00 4.4195581e+00 3.5098289e+00 4.3311360e+00 4.0067587e+00 4.2149806e+00 5.0390074e+00 2.9109559e+00 4.7273232e+00 4.2124122e+00 4.5405876e+00 3.5250718e+00 3.7139027e+00 3.9284285e+00 3.4150426e+00 3.5404383e+00 3.7302923e+00 3.9113385e+00 5.1434691e+00 5.3373009e+00 3.4049076e+00 4.1330547e+00 3.3170101e+00 5.1417717e+00 3.3168869e+00 4.1189487e+00 4.4302240e+00 3.2165105e+00 3.3123688e+00 4.0139003e+00 4.2362095e+00 4.5418862e+00 4.8784553e+00 4.0170271e+00 3.5083268e+00 4.0022121e+00 4.5797332e+00 4.0245435e+00 3.9092247e+00 3.2127499e+00 3.8383398e+00 4.0332236e+00 3.5687055e+00 3.5098289e+00 4.3229228e+00 4.1350002e+00 3.6463113e+00 3.4177609e+00 3.6219569e+00 3.8236419e+00 3.5076152e+00 6.0202028e-01 1.0008471e+00 1.1133984e+00 1.2085435e-01 4.0125062e-01 7.0548138e-01 1.2085435e-01 4.1210927e-01 3.3813251e-01 4.1317535e-01 8.0093160e-01 4.1210927e-01 4.5147187e-01 7.0184453e-01 2.0121983e-01 7.0088326e-01 2.2573593e-01 6.3164977e-01 2.4170870e-01 3.1712556e+00 2.9203033e+00 3.3425812e+00 2.4054865e+00 3.0228150e+00 2.9023686e+00 3.1131138e+00 1.7053664e+00 3.0276460e+00 2.3090554e+00 1.9156198e+00 2.6109872e+00 2.4094445e+00 3.1056085e+00 2.0122722e+00 2.8520934e+00 2.9050277e+00 2.5027053e+00 2.9125192e+00 2.3027405e+00 3.2109394e+00 2.4160415e+00 3.3084146e+00 3.1042008e+00 2.7243956e+00 2.8394100e+00 3.2369546e+00 3.4247119e+00 2.9077087e+00 1.9065546e+00 2.2031052e+00 2.1025721e+00 2.3063026e+00 3.5041732e+00 2.9047982e+00 2.9102300e+00 3.1338083e+00 2.8152056e+00 2.5042498e+00 2.4049187e+00 2.8014068e+00 3.0065584e+00 2.4051787e+00 1.7073273e+00 2.6035320e+00 2.6027034e+00 2.6039922e+00 2.7127202e+00 1.4197348e+00 2.5048165e+00 4.4189015e+00 3.5095163e+00 4.3257988e+00 4.0057069e+00 4.2135049e+00 5.0324949e+00 2.9108796e+00 4.7221364e+00 4.2099109e+00 4.5353940e+00 3.5215862e+00 3.7118544e+00 3.9240013e+00 3.4147901e+00 3.5401421e+00 3.7282909e+00 3.9092247e+00 5.1365094e+00 5.3314710e+00 3.4038679e+00 4.1286955e+00 3.3168613e+00 5.1347955e+00 3.3142720e+00 4.1161770e+00 4.4243750e+00 3.2143132e+00 3.3110162e+00 4.0124921e+00 4.2289511e+00 4.5343165e+00 4.8661278e+00 4.0156242e+00 3.5063341e+00 4.0016595e+00 4.5675358e+00 4.0235142e+00 3.9076269e+00 3.2116668e+00 3.8321137e+00 4.0301568e+00 3.5598554e+00 3.5095163e+00 4.3201293e+00 4.1322798e+00 3.6413274e+00 3.4154677e+00 3.6188977e+00 3.8226861e+00 3.5071388e+00 7.0096708e-01 8.0004602e-01 5.0855077e-01 4.1420960e-01 2.2608083e-01 5.0855077e-01 1.0008768e+00 3.0474106e-01 4.0127250e-01 1.1527746e+00 1.0000457e+00 4.0127250e-01 4.5783248e-01 6.0948800e-01 4.1317535e-01 8.0008964e-01 3.0482299e-01 4.0127250e-01 3.2104685e+00 3.0024155e+00 3.4059092e+00 2.5048145e+00 3.1026586e+00 3.0005260e+00 3.2022151e+00 1.8108200e+00 3.1025924e+00 2.4028974e+00 2.0417688e+00 2.7025751e+00 2.5062865e+00 3.2007416e+00 2.1027376e+00 2.9057769e+00 3.0015388e+00 2.6003213e+00 3.0043084e+00 2.4017233e+00 3.3039365e+00 2.5015263e+00 3.4013673e+00 3.2002931e+00 2.8019179e+00 2.9040262e+00 3.3045112e+00 3.5038551e+00 3.0015958e+00 2.0020172e+00 2.3035509e+00 2.2041009e+00 2.4010446e+00 3.6011254e+00 3.0015387e+00 3.0025850e+00 3.2040849e+00 2.9029297e+00 2.6009614e+00 2.5022969e+00 2.9005699e+00 3.1008537e+00 2.5011717e+00 1.8179820e+00 2.7009799e+00 2.7004102e+00 2.7008225e+00 2.8010266e+00 1.5160787e+00 2.6010449e+00 4.5093548e+00 3.6039076e+00 4.4060847e+00 4.1014985e+00 4.3050076e+00 5.1081747e+00 3.0045274e+00 4.8044751e+00 4.3019076e+00 4.6117611e+00 3.6062405e+00 3.8032982e+00 4.0063634e+00 3.5066398e+00 3.6202705e+00 3.8119529e+00 4.0019489e+00 5.2097659e+00 5.4087506e+00 3.5019664e+00 4.2090727e+00 3.4073888e+00 5.2088330e+00 3.4037266e+00 4.2046087e+00 4.5046939e+00 3.3041077e+00 3.4034672e+00 4.1044794e+00 4.3051857e+00 4.6074982e+00 4.9181673e+00 4.1061528e+00 3.6008588e+00 4.1002763e+00 4.6187512e+00 4.1110303e+00 4.0017844e+00 3.3039580e+00 3.9080413e+00 4.1118167e+00 3.6188744e+00 3.6039076e+00 4.4067826e+00 4.2136946e+00 3.7147052e+00 3.5048712e+00 3.7054764e+00 3.9103830e+00 3.6025973e+00 3.0026460e-01 1.0001598e+00 9.0029064e-01 6.0202028e-01 1.0001598e+00 1.1281352e+00 7.0000303e-01 6.0052920e-01 1.8012962e+00 9.6574369e-01 6.3178782e-01 4.2270142e-01 1.1005460e+00 3.0026460e-01 9.1422402e-01 4.0004442e-01 8.0004602e-01 3.2225450e+00 3.0091788e+00 3.4142737e+00 2.5659206e+00 3.1121190e+00 3.0065741e+00 3.2080663e+00 1.9795481e+00 3.1095927e+00 2.4291135e+00 2.3151880e+00 2.7129011e+00 2.5826416e+00 3.2051685e+00 2.1273517e+00 2.9165009e+00 3.0077149e+00 2.6132476e+00 3.0422268e+00 2.4403272e+00 3.3124044e+00 2.5166989e+00 3.4115621e+00 3.2044340e+00 2.8105357e+00 2.9137358e+00 3.3135329e+00 3.5115123e+00 3.0089448e+00 2.0634477e+00 2.3669955e+00 2.2798676e+00 2.4222743e+00 3.6065353e+00 3.0077107e+00 3.0095641e+00 3.2119165e+00 2.9352117e+00 2.6080595e+00 2.5371436e+00 2.9126008e+00 3.1051604e+00 2.5254396e+00 2.0316239e+00 2.7143597e+00 2.7050980e+00 2.7084026e+00 2.8082597e+00 1.7626307e+00 2.6129786e+00 4.5202811e+00 3.6136284e+00 4.4137920e+00 4.1051791e+00 4.3125133e+00 5.1149745e+00 3.0264326e+00 4.8090821e+00 4.3074243e+00 4.6242424e+00 3.6169385e+00 3.8113313e+00 4.0160029e+00 3.5235168e+00 3.6464145e+00 3.8279938e+00 4.0062032e+00 5.2173409e+00 5.4162239e+00 3.5202520e+00 4.2206877e+00 3.4219359e+00 5.2156030e+00 3.4146242e+00 4.2116109e+00 4.5097900e+00 3.3150972e+00 3.4115327e+00 4.1123767e+00 4.3106068e+00 4.6148396e+00 4.9296764e+00 4.1159139e+00 3.6049049e+00 4.1030781e+00 4.6336876e+00 4.1247374e+00 4.0056652e+00 3.3131419e+00 3.9194404e+00 4.1265834e+00 3.6428011e+00 3.6136284e+00 4.4157045e+00 4.2295866e+00 3.7344540e+00 3.5196603e+00 3.7152542e+00 3.9242137e+00 3.6086340e+00 1.1055707e+00 1.0030868e+00 7.0000151e-01 1.1055707e+00 1.3018102e+00 8.0245824e-01 7.1621884e-01 1.9078389e+00 1.1896594e+00 7.2044167e-01 5.3943256e-01 1.2089192e+00 4.5147187e-01 1.0776188e+00 5.0043084e-01 9.0506254e-01 3.3079985e+00 3.1046072e+00 3.5056118e+00 2.6709345e+00 3.2081329e+00 3.1065948e+00 3.3043807e+00 2.0901204e+00 3.2052035e+00 2.5276373e+00 2.4267777e+00 2.8091158e+00 2.6904888e+00 3.3041886e+00 2.2241050e+00 3.0065909e+00 3.1056084e+00 2.7155799e+00 3.1440950e+00 2.5451785e+00 3.4078458e+00 2.6152930e+00 3.5111169e+00 3.3045112e+00 2.9070941e+00 3.0065909e+00 3.4069929e+00 3.6059670e+00 3.1068940e+00 2.1702441e+00 2.4737523e+00 2.3880607e+00 2.5238437e+00 3.7056514e+00 3.1056084e+00 3.1055129e+00 3.3051256e+00 3.0372254e+00 2.7067267e+00 2.6397031e+00 3.0142197e+00 3.2037566e+00 2.6278605e+00 2.1424915e+00 2.8148768e+00 2.8047553e+00 2.8077256e+00 2.9066659e+00 1.8682644e+00 2.7127202e+00 4.6142218e+00 3.7103348e+00 4.5074842e+00 4.2035307e+00 4.4083412e+00 5.2074280e+00 3.1239158e+00 4.9041928e+00 4.4055872e+00 4.7149533e+00 3.7103439e+00 3.9081758e+00 4.1095835e+00 3.6188977e+00 3.7328455e+00 3.9187196e+00 4.1037709e+00 5.3087451e+00 5.5089283e+00 3.6218973e+00 4.3127798e+00 3.5155554e+00 5.3076917e+00 3.5109043e+00 4.3070065e+00 4.6043043e+00 3.4107931e+00 3.5076367e+00 4.2085644e+00 4.4044360e+00 4.7071579e+00 5.0144130e+00 4.2110565e+00 3.7038321e+00 4.2031939e+00 4.7176313e+00 4.2169539e+00 4.1034568e+00 3.4087523e+00 4.0110693e+00 4.2176552e+00 3.7262659e+00 3.7103348e+00 4.5099841e+00 4.3199893e+00 3.8223318e+00 3.6159248e+00 3.8096375e+00 4.0163546e+00 3.7058422e+00 3.0026460e-01 6.0964891e-01 0.0000000e+00 5.0043842e-01 3.0482299e-01 4.0246123e-01 8.0254500e-01 5.0043842e-01 5.2133802e-01 7.0556260e-01 2.0181667e-01 7.0008735e-01 3.0026460e-01 6.0948506e-01 2.0181667e-01 3.2490712e+00 3.0153168e+00 3.4297841e+00 2.5067523e+00 3.1166337e+00 3.0027816e+00 3.2112793e+00 1.8068048e+00 3.1183051e+00 2.4116924e+00 2.0138832e+00 2.7116615e+00 2.5059537e+00 3.2048192e+00 2.1144760e+00 2.9351753e+00 3.0063019e+00 2.6019122e+00 3.0106587e+00 2.4030297e+00 3.3125861e+00 2.5120719e+00 3.4068163e+00 3.2029877e+00 2.8162444e+00 2.9267417e+00 3.3252407e+00 3.5189464e+00 3.0077107e+00 2.0051350e+00 2.3037132e+00 2.2028146e+00 2.4058620e+00 3.6044981e+00 3.0062070e+00 3.0107283e+00 3.2237456e+00 2.9105093e+00 2.6052541e+00 2.5062865e+00 2.9018772e+00 3.1056084e+00 2.5048522e+00 1.8082911e+00 2.7043948e+00 2.7029415e+00 2.7046027e+00 2.8091099e+00 1.5248852e+00 2.6055127e+00 4.5209020e+00 3.6112573e+00 4.4212031e+00 4.1056541e+00 4.3138986e+00 5.1255338e+00 3.0133997e+00 4.8167235e+00 4.3081273e+00 4.6319211e+00 3.6205854e+00 3.8114965e+00 4.0212972e+00 3.5173798e+00 3.6449970e+00 3.8299342e+00 4.0081754e+00 5.2290121e+00 5.4254411e+00 3.5039202e+00 4.2264145e+00 3.4198378e+00 5.2270034e+00 3.4138008e+00 4.2149806e+00 4.5183778e+00 3.3145502e+00 3.4118179e+00 4.1129687e+00 4.3210760e+00 4.6261633e+00 4.9512603e+00 4.1165035e+00 3.6051692e+00 4.1014742e+00 4.6540056e+00 4.1257291e+00 4.0071257e+00 3.3129914e+00 3.9274863e+00 4.1301604e+00 3.6542046e+00 3.6112573e+00 4.4192311e+00 4.2328883e+00 3.7399948e+00 3.5155767e+00 3.7180846e+00 3.9250546e+00 3.6083191e+00 5.0436965e-01 3.0026460e-01 6.0017982e-01 3.0482299e-01 3.0017653e-01 9.0506343e-01 6.0000317e-01 4.5783248e-01 7.4269314e-01 2.4195741e-01 6.0948506e-01 4.0122873e-01 5.0855077e-01 2.0061436e-01 3.5243030e+00 3.3064692e+00 3.7147036e+00 2.8028227e+00 3.4072759e+00 3.3010449e+00 3.5048841e+00 2.1026764e+00 3.4081977e+00 2.7042302e+00 2.3098753e+00 3.0045117e+00 2.8027924e+00 3.5019450e+00 2.4045982e+00 3.2157547e+00 3.3025861e+00 2.9005886e+00 3.3047156e+00 2.7010554e+00 3.6058037e+00 2.8042692e+00 3.7029937e+00 3.5011559e+00 3.1065954e+00 3.2116669e+00 3.6121048e+00 3.8091015e+00 3.3031148e+00 2.3014285e+00 2.6014645e+00 2.5011992e+00 2.7018905e+00 3.9020189e+00 3.3025600e+00 3.3044825e+00 3.5110031e+00 3.2044340e+00 2.9018587e+00 2.8023124e+00 3.2006932e+00 3.4022345e+00 2.8016296e+00 2.1039819e+00 3.0015958e+00 3.0009948e+00 3.0016466e+00 3.1034780e+00 1.8068049e+00 2.9019412e+00 4.8119571e+00 3.9055005e+00 4.7117433e+00 4.4027872e+00 4.6074923e+00 5.4154953e+00 3.3059205e+00 5.1096877e+00 4.6042055e+00 4.9184662e+00 3.9101579e+00 4.1056576e+00 4.3111747e+00 3.8086187e+00 3.9240074e+00 4.1158326e+00 4.3040379e+00 5.5178488e+00 5.7157885e+00 3.8018678e+00 4.5144444e+00 3.7097243e+00 5.5166376e+00 3.7063915e+00 4.5079295e+00 4.8103281e+00 3.6066590e+00 3.7054748e+00 4.4067833e+00 4.6117275e+00 4.9151622e+00 5.2317566e+00 4.4087821e+00 3.9022961e+00 4.4006562e+00 4.9323259e+00 4.4141504e+00 4.3034963e+00 3.6059708e+00 4.2144276e+00 4.4165186e+00 3.9284285e+00 3.9055005e+00 4.7106152e+00 4.5183778e+00 4.0209837e+00 3.8074712e+00 4.0090032e+00 4.2134002e+00 3.9039579e+00 6.0964891e-01 1.1019501e+00 4.0125062e-01 5.0000761e-01 1.2632947e+00 1.1001012e+00 5.2491131e-01 6.1135434e-01 7.1621884e-01 4.2268438e-01 9.0026543e-01 2.4170870e-01 5.0043084e-01 3.4064574e+00 3.2033141e+00 3.6042703e+00 2.7067267e+00 3.3031847e+00 3.2012079e+00 3.4035361e+00 2.0125822e+00 3.3019706e+00 2.6055127e+00 2.2404570e+00 2.9047685e+00 2.7070958e+00 3.4014441e+00 2.3056303e+00 3.1043374e+00 3.2029748e+00 2.8006755e+00 3.2059945e+00 2.6027034e+00 3.5064150e+00 2.7028014e+00 3.6021536e+00 3.4005708e+00 3.0020583e+00 3.1034908e+00 3.5031921e+00 3.7043162e+00 3.2030081e+00 2.2031888e+00 2.5048145e+00 2.4051640e+00 2.6022353e+00 3.8020808e+00 3.2029748e+00 3.2045605e+00 3.4036086e+00 3.1036832e+00 2.8021575e+00 2.7039988e+00 3.1011640e+00 3.3016475e+00 2.7022579e+00 2.0191796e+00 2.9020892e+00 2.9010579e+00 2.9018587e+00 3.0016913e+00 1.7203066e+00 2.8022905e+00 4.7127831e+00 3.8062227e+00 4.6064153e+00 4.3024456e+00 4.5071341e+00 5.3066177e+00 3.2074507e+00 5.0034627e+00 4.5024143e+00 4.8135231e+00 3.8088290e+00 4.0049892e+00 4.2080447e+00 3.7100251e+00 3.8270894e+00 4.0163858e+00 4.2028588e+00 5.4079977e+00 5.6075453e+00 3.7029588e+00 4.4113160e+00 3.6111044e+00 5.4066727e+00 3.6058057e+00 4.4062018e+00 4.7037812e+00 3.5065182e+00 3.6056307e+00 4.3065776e+00 4.5036239e+00 4.8058364e+00 5.1131156e+00 4.3088094e+00 3.8014142e+00 4.3005452e+00 4.8156922e+00 4.3151195e+00 4.2027764e+00 3.5064276e+00 4.1095026e+00 4.3155223e+00 3.8226872e+00 3.8062227e+00 4.6088777e+00 4.4178507e+00 3.9190516e+00 3.7073875e+00 3.9078043e+00 4.1144923e+00 3.8043348e+00 5.0043842e-01 3.0482299e-01 4.0246123e-01 8.0254500e-01 5.0043842e-01 5.2133802e-01 7.0556260e-01 2.0181667e-01 7.0008735e-01 3.0026460e-01 6.0948506e-01 2.0181667e-01 3.2490712e+00 3.0153168e+00 3.4297841e+00 2.5067523e+00 3.1166337e+00 3.0027816e+00 3.2112793e+00 1.8068048e+00 3.1183051e+00 2.4116924e+00 2.0138832e+00 2.7116615e+00 2.5059537e+00 3.2048192e+00 2.1144760e+00 2.9351753e+00 3.0063019e+00 2.6019122e+00 3.0106587e+00 2.4030297e+00 3.3125861e+00 2.5120719e+00 3.4068163e+00 3.2029877e+00 2.8162444e+00 2.9267417e+00 3.3252407e+00 3.5189464e+00 3.0077107e+00 2.0051350e+00 2.3037132e+00 2.2028146e+00 2.4058620e+00 3.6044981e+00 3.0062070e+00 3.0107283e+00 3.2237456e+00 2.9105093e+00 2.6052541e+00 2.5062865e+00 2.9018772e+00 3.1056084e+00 2.5048522e+00 1.8082911e+00 2.7043948e+00 2.7029415e+00 2.7046027e+00 2.8091099e+00 1.5248852e+00 2.6055127e+00 4.5209020e+00 3.6112573e+00 4.4212031e+00 4.1056541e+00 4.3138986e+00 5.1255338e+00 3.0133997e+00 4.8167235e+00 4.3081273e+00 4.6319211e+00 3.6205854e+00 3.8114965e+00 4.0212972e+00 3.5173798e+00 3.6449970e+00 3.8299342e+00 4.0081754e+00 5.2290121e+00 5.4254411e+00 3.5039202e+00 4.2264145e+00 3.4198378e+00 5.2270034e+00 3.4138008e+00 4.2149806e+00 4.5183778e+00 3.3145502e+00 3.4118179e+00 4.1129687e+00 4.3210760e+00 4.6261633e+00 4.9512603e+00 4.1165035e+00 3.6051692e+00 4.1014742e+00 4.6540056e+00 4.1257291e+00 4.0071257e+00 3.3129914e+00 3.9274863e+00 4.1301604e+00 3.6542046e+00 3.6112573e+00 4.4192311e+00 4.2328883e+00 3.7399948e+00 3.5155767e+00 3.7180846e+00 3.9250546e+00 3.6083191e+00 7.0470720e-01 6.3164977e-01 7.0000303e-01 2.0000000e-01 6.4049114e-01 8.7212232e-01 4.0004442e-01 8.5437440e-01 2.2573593e-01 9.3308853e-01 6.0184622e-01 3.5152865e+00 3.2379971e+00 3.6729302e+00 2.7052554e+00 3.3425813e+00 3.2040843e+00 3.4230889e+00 2.0021220e+00 3.3530528e+00 2.6055127e+00 2.2051638e+00 2.9154879e+00 2.7227228e+00 3.4118179e+00 2.3143923e+00 3.1902650e+00 3.2048191e+00 2.8089346e+00 3.2223766e+00 2.6060092e+00 3.5107904e+00 2.7333577e+00 3.6167495e+00 3.4109217e+00 3.0488339e+00 3.1712556e+00 3.5658221e+00 3.7426726e+00 3.2127498e+00 2.2186491e+00 2.5049231e+00 2.4052960e+00 2.6139440e+00 3.8063158e+00 3.2036084e+00 3.2143157e+00 3.4603347e+00 3.1318587e+00 2.8056557e+00 2.7050980e+00 3.1020681e+00 3.3136174e+00 2.7116685e+00 2.0027889e+00 2.9047842e+00 2.9057760e+00 2.9065359e+00 3.0276459e+00 1.7091578e+00 2.8077256e+00 4.7169309e+00 3.8081280e+00 4.6399369e+00 4.3088507e+00 4.5162248e+00 5.3501952e+00 3.2067991e+00 5.0370912e+00 4.5175831e+00 4.8468186e+00 3.8290678e+00 4.0170262e+00 4.2347722e+00 3.7111714e+00 3.8289857e+00 4.0283196e+00 4.2155430e+00 5.4550926e+00 5.6472422e+00 3.7064735e+00 4.4381718e+00 3.6121030e+00 5.4538016e+00 3.6205857e+00 4.4231445e+00 4.7408825e+00 3.5189464e+00 3.6135011e+00 4.3151164e+00 4.5490925e+00 4.8546830e+00 5.1966534e+00 4.3173271e+00 3.8129545e+00 4.3038527e+00 4.8962803e+00 4.3214438e+00 4.2123903e+00 3.5127693e+00 4.1469662e+00 4.3342207e+00 3.8751227e+00 3.8081280e+00 4.6262568e+00 4.4345825e+00 3.9485180e+00 3.7201181e+00 3.9257254e+00 4.1203162e+00 3.8072915e+00 2.0181667e-01 1.1055799e+00 7.0016860e-01 4.0006662e-01 4.5147187e-01 4.1212852e-01 4.0002221e-01 5.0043084e-01 3.0474106e-01 1.2085435e-01 3.2280983e+00 3.0080445e+00 3.4166801e+00 2.5073304e+00 3.1087541e+00 3.0016255e+00 3.2064005e+00 1.8128544e+00 3.1091921e+00 2.4076934e+00 2.0429861e+00 2.7070770e+00 2.5077793e+00 3.2025221e+00 2.1086021e+00 2.9185909e+00 3.0040552e+00 2.6009247e+00 3.0080768e+00 2.4028385e+00 3.3086417e+00 2.5058827e+00 3.4038679e+00 3.2013290e+00 2.8077473e+00 2.9137660e+00 3.3136458e+00 3.5107924e+00 3.0045274e+00 2.0036964e+00 2.3048725e+00 2.2049827e+00 2.4032211e+00 3.6028345e+00 3.0040403e+00 3.0066661e+00 3.2127504e+00 2.9065741e+00 2.6030847e+00 2.5048249e+00 2.9013288e+00 3.1029264e+00 2.5029614e+00 1.8201043e+00 2.7027522e+00 2.7015465e+00 2.7026433e+00 2.8042851e+00 1.5256523e+00 2.6032253e+00 4.5160443e+00 3.6080467e+00 4.4135519e+00 4.1035779e+00 4.3098000e+00 5.1168009e+00 3.0096881e+00 4.8103297e+00 4.3048676e+00 4.6223708e+00 3.6136097e+00 3.8074712e+00 4.0139003e+00 3.5128896e+00 3.6349183e+00 3.8220063e+00 4.0049433e+00 5.2194303e+00 5.4171979e+00 3.5033669e+00 4.2181241e+00 3.4145410e+00 5.2178541e+00 3.4088042e+00 4.2099019e+00 4.5111938e+00 3.3094784e+00 3.4078458e+00 4.1090316e+00 4.3126257e+00 4.6165627e+00 4.9348334e+00 4.1118265e+00 3.6027619e+00 4.1008192e+00 4.6366757e+00 4.1194553e+00 4.0043991e+00 3.3087928e+00 3.9177721e+00 4.1218428e+00 3.6374332e+00 3.6080467e+00 4.4133507e+00 4.2243717e+00 3.7282925e+00 3.5105217e+00 3.7119499e+00 3.9187675e+00 3.6057072e+00 1.2012865e+00 6.0184622e-01 3.3808272e-01 6.0184934e-01 5.0043084e-01 3.3818226e-01 4.1212852e-01 3.0922892e-01 2.0121983e-01 3.4273160e+00 3.2064011e+00 3.6161317e+00 2.7056828e+00 3.3075153e+00 3.2008118e+00 3.4044261e+00 2.0113827e+00 3.3090710e+00 2.6035236e+00 2.2398619e+00 2.9035670e+00 2.7083020e+00 3.4017079e+00 2.3034787e+00 3.1174706e+00 3.2019092e+00 2.8008297e+00 3.2066700e+00 2.6023240e+00 3.5046442e+00 2.7041827e+00 3.6031099e+00 3.4011658e+00 3.0071110e+00 3.1127326e+00 3.5134157e+00 3.7092355e+00 3.2025439e+00 2.2031052e+00 2.5042875e+00 2.4048325e+00 2.6019131e+00 3.8016620e+00 3.2018790e+00 3.2036084e+00 3.4118203e+00 3.1063566e+00 2.8013151e+00 2.7029498e+00 3.1008327e+00 3.3019518e+00 2.7019892e+00 2.0181988e+00 2.9013773e+00 2.9007142e+00 2.9012240e+00 3.0034647e+00 1.7168003e+00 2.8015399e+00 4.7103355e+00 3.8044833e+00 4.6117631e+00 4.3023732e+00 4.5065283e+00 5.3163061e+00 3.2051927e+00 5.0102923e+00 4.5041827e+00 4.8177760e+00 3.8091017e+00 4.0050028e+00 4.2105704e+00 3.7073402e+00 3.8208501e+00 4.0138272e+00 4.2036880e+00 5.4187310e+00 5.6164039e+00 3.7027275e+00 4.4135513e+00 3.6080195e+00 5.4177192e+00 3.6056365e+00 4.4072751e+00 4.7109359e+00 3.5056751e+00 3.6045028e+00 4.3058539e+00 4.5127175e+00 4.8161488e+00 5.1342237e+00 4.3075896e+00 3.8021528e+00 4.3006308e+00 4.8339900e+00 4.3122441e+00 4.2030792e+00 3.5048429e+00 4.1140184e+00 4.3148937e+00 3.8271256e+00 3.8044833e+00 4.6097143e+00 4.4165186e+00 3.9191998e+00 3.7067060e+00 3.9080455e+00 4.1114854e+00 3.8031381e+00 9.0000091e-01 1.2014191e+00 1.5025345e+00 7.0088477e-01 1.5012926e+00 9.0000136e-01 1.4092540e+00 1.0030724e+00 3.4932678e+00 3.2284356e+00 3.6579694e+00 2.7029237e+00 3.3320310e+00 3.2025221e+00 3.4171529e+00 2.0008116e+00 3.3407229e+00 2.6032740e+00 2.2005685e+00 2.9103582e+00 2.7153679e+00 3.4082220e+00 2.3087475e+00 3.1706684e+00 3.2030687e+00 2.8057704e+00 3.2157547e+00 2.6035236e+00 3.5075879e+00 2.7233837e+00 3.6121030e+00 3.4076329e+00 3.0364247e+00 3.1548602e+00 3.5517240e+00 3.7328844e+00 3.2086545e+00 2.2116271e+00 2.5026949e+00 2.4028972e+00 2.6089340e+00 3.8042764e+00 3.2022967e+00 3.2108192e+00 3.4468872e+00 3.1231713e+00 2.8035152e+00 2.7029238e+00 3.1011647e+00 3.3095196e+00 2.7074599e+00 2.0008921e+00 2.9028462e+00 2.9036791e+00 2.9040745e+00 3.0197997e+00 1.7043730e+00 2.8047771e+00 4.7130344e+00 3.8056232e+00 4.6318991e+00 4.3063824e+00 4.5121922e+00 5.3416858e+00 3.2045522e+00 5.0302179e+00 4.5134527e+00 4.8380714e+00 3.8218532e+00 4.0124930e+00 4.2269486e+00 3.7079008e+00 3.8220112e+00 4.0214147e+00 4.2115853e+00 5.4465234e+00 5.6393868e+00 3.7043105e+00 4.4299700e+00 3.6085945e+00 5.4449998e+00 3.6148636e+00 4.4178341e+00 4.7331065e+00 3.5134671e+00 3.6094821e+00 4.3111775e+00 4.5398308e+00 4.8449119e+00 5.1826640e+00 4.3129030e+00 3.8093617e+00 4.3026669e+00 4.8806417e+00 4.3164776e+00 4.2091204e+00 3.5088587e+00 4.1368711e+00 4.3264627e+00 3.8592376e+00 3.8056232e+00 4.6204067e+00 4.4269727e+00 3.9374314e+00 3.7145622e+00 3.9192263e+00 4.1154804e+00 3.8050056e+00 6.1288055e-01 7.7603846e-01 4.0127250e-01 7.4329414e-01 2.0061436e-01 9.0508712e-01 6.0000635e-01 3.5152864e+00 3.2379970e+00 3.6729302e+00 2.7058598e+00 3.3425838e+00 3.2040874e+00 3.4230885e+00 2.0034894e+00 3.3530533e+00 2.6055423e+00 2.2123846e+00 2.9154880e+00 2.7237438e+00 3.4118184e+00 2.3143951e+00 3.1902650e+00 3.2048192e+00 2.8089552e+00 3.2228316e+00 2.6061975e+00 3.5107904e+00 2.7333644e+00 3.6167885e+00 3.4109240e+00 3.0488346e+00 3.1712557e+00 3.5658240e+00 3.7426726e+00 3.2127504e+00 2.2188249e+00 2.5053901e+00 2.4058634e+00 2.6139732e+00 3.8063206e+00 3.2036085e+00 3.2143126e+00 3.4603347e+00 3.1321581e+00 2.8056558e+00 2.7052554e+00 3.1021033e+00 3.3136175e+00 2.7117356e+00 2.0053411e+00 2.9048017e+00 2.9057761e+00 2.9065368e+00 3.0276467e+00 1.7105814e+00 2.8077315e+00 4.7169308e+00 3.8081328e+00 4.6399369e+00 4.3088509e+00 4.5162248e+00 5.3501952e+00 3.2068686e+00 5.0370912e+00 4.5175965e+00 4.8468145e+00 3.8290678e+00 4.0170299e+00 4.2347722e+00 3.7112059e+00 3.8289870e+00 4.0283196e+00 4.2155430e+00 5.4550814e+00 5.6472442e+00 3.7067060e+00 4.4381718e+00 3.6121048e+00 5.4538018e+00 3.6205918e+00 4.4231444e+00 4.7408825e+00 3.5189484e+00 3.6135011e+00 4.3151171e+00 4.5490925e+00 4.8546834e+00 5.1966394e+00 4.3173279e+00 3.8129558e+00 4.3038600e+00 4.8962803e+00 4.3214431e+00 4.2123903e+00 3.5127694e+00 4.1469662e+00 4.3342207e+00 3.8751227e+00 3.8081328e+00 4.6262568e+00 4.4345823e+00 3.9485180e+00 3.7201522e+00 3.9257254e+00 4.1203153e+00 3.8072915e+00 3.4085233e-01 5.0517282e-01 4.1210927e-01 4.5847767e-01 4.1317535e-01 4.0243965e-01 3.1409605e+00 2.9078360e+00 3.3230621e+00 2.4077390e+00 3.0097979e+00 2.9003941e+00 3.1042002e+00 1.7227897e+00 3.0135090e+00 2.3017319e+00 1.9755996e+00 2.6019350e+00 2.4142039e+00 3.1015567e+00 2.0014192e+00 2.8264551e+00 2.9006366e+00 2.5011717e+00 2.9082658e+00 2.3033727e+00 3.2022157e+00 2.4051094e+00 3.3034165e+00 3.1014454e+00 2.7104802e+00 2.8188502e+00 3.2195779e+00 3.4112824e+00 2.9016560e+00 1.9053006e+00 2.2068965e+00 2.1085395e+00 2.3018945e+00 3.5009585e+00 2.9005880e+00 2.9020766e+00 3.1165913e+00 2.8092629e+00 2.5004154e+00 2.4029432e+00 2.8007533e+00 3.0017918e+00 2.4022355e+00 1.7369589e+00 2.6007937e+00 2.6003442e+00 2.6005344e+00 2.7044628e+00 1.4329832e+00 2.5008032e+00 4.4064393e+00 3.5021596e+00 4.3131639e+00 4.0016670e+00 4.2045223e+00 5.0200323e+00 2.9028411e+00 4.7130517e+00 4.2044861e+00 4.5172866e+00 3.5073617e+00 3.7038321e+00 3.9101623e+00 3.4039470e+00 3.5128093e+00 3.7092301e+00 3.9033549e+00 5.1227972e+00 5.3193887e+00 3.4029615e+00 4.1123595e+00 3.3040255e+00 5.1222477e+00 3.3043123e+00 4.1063328e+00 4.4139149e+00 3.2038047e+00 3.3025879e+00 4.0039164e+00 4.2170349e+00 4.5206139e+00 4.8441812e+00 4.0049702e+00 3.5022104e+00 4.0005674e+00 4.5418878e+00 4.0077003e+00 3.9024858e+00 3.2025221e+00 3.8146102e+00 4.0114630e+00 3.5261349e+00 3.5021596e+00 4.3081194e+00 4.1123594e+00 3.6158324e+00 3.4049076e+00 3.6064417e+00 3.8069566e+00 3.5014492e+00 8.0923926e-01 3.0474106e-01 6.5724028e-01 4.0246123e-01 5.6371422e-01 2.8500310e+00 2.6110761e+00 3.0277528e+00 2.1510447e+00 2.7141512e+00 2.6027937e+00 2.8070667e+00 1.5787180e+00 2.7167342e+00 2.0166118e+00 1.9318287e+00 2.3071806e+00 2.1715812e+00 2.8031215e+00 1.7145956e+00 2.5336675e+00 2.6035547e+00 2.2074446e+00 2.6319765e+00 2.0282636e+00 2.9076123e+00 2.1122780e+00 3.0080768e+00 2.8027924e+00 2.4144060e+00 2.5243969e+00 2.9241685e+00 3.1150226e+00 2.6049377e+00 1.6499744e+00 1.9530826e+00 1.8666228e+00 2.0130304e+00 3.2033374e+00 2.6035250e+00 2.6059866e+00 2.8207213e+00 2.5287261e+00 2.2032583e+00 2.1244184e+00 2.5066544e+00 2.7033232e+00 2.1157724e+00 1.6395342e+00 2.3072196e+00 2.3018945e+00 2.3035850e+00 2.4072314e+00 1.3788456e+00 2.2062031e+00 4.1150297e+00 3.2079717e+00 4.0170857e+00 3.7033716e+00 3.9093672e+00 4.7228082e+00 2.6158579e+00 4.4145659e+00 3.9067187e+00 4.2256164e+00 3.2143454e+00 3.4081069e+00 3.6159248e+00 3.1148584e+00 3.2358776e+00 3.4219579e+00 3.6052692e+00 4.8260874e+00 5.0225486e+00 3.1131211e+00 3.8201108e+00 3.0142352e+00 4.8248264e+00 3.0102191e+00 3.8104451e+00 4.1158426e+00 2.9100849e+00 3.0073054e+00 3.7087638e+00 3.9191144e+00 4.2237272e+00 4.5500784e+00 3.7114840e+00 3.2036323e+00 3.7015743e+00 4.2506870e+00 3.7186993e+00 3.6043148e+00 2.9081165e+00 3.5216379e+00 3.7226348e+00 3.2449757e+00 3.2079717e+00 4.0139105e+00 3.8249612e+00 3.3311289e+00 3.1134242e+00 3.3125194e+00 3.5179632e+00 3.2049019e+00 8.0051115e-01 2.2573593e-01 7.1621884e-01 3.0482299e-01 3.3530529e+00 3.1135637e+00 3.5317001e+00 2.6021962e+00 3.2157548e+00 3.1011640e+00 3.3083865e+00 1.9014069e+00 3.2199554e+00 2.5036852e+00 2.1054816e+00 2.8056557e+00 2.6057308e+00 3.3035252e+00 2.2049636e+00 3.0369970e+00 3.1023768e+00 2.7016498e+00 3.1076516e+00 2.5011992e+00 3.4059088e+00 2.6097152e+00 3.5056297e+00 3.3028597e+00 2.9166917e+00 3.0276459e+00 3.4273156e+00 3.6176286e+00 3.1043337e+00 2.1032882e+00 2.4011650e+00 2.3009622e+00 2.5032633e+00 3.7024058e+00 3.1022094e+00 3.1056121e+00 3.3243222e+00 3.0101957e+00 2.7018643e+00 2.6020065e+00 3.0005955e+00 3.2040843e+00 2.6027120e+00 1.9019962e+00 2.8015673e+00 2.8013346e+00 2.8018959e+00 2.9083044e+00 1.6052507e+00 2.7022567e+00 4.6121162e+00 3.7052383e+00 4.5194334e+00 4.2036849e+00 4.4088275e+00 5.2263180e+00 3.1053076e+00 4.9177739e+00 4.4072684e+00 4.7260117e+00 3.7138970e+00 3.9076272e+00 4.1167962e+00 3.6081590e+00 3.7238338e+00 3.9176170e+00 4.1063328e+00 5.3296625e+00 5.5255577e+00 3.6022190e+00 4.3201293e+00 3.5092121e+00 5.3285444e+00 3.5088064e+00 4.3111749e+00 4.6192198e+00 3.4084525e+00 3.5063337e+00 4.2079639e+00 4.4229098e+00 4.7273231e+00 5.0541259e+00 4.2099019e+00 3.7043105e+00 4.2011108e+00 4.7534769e+00 4.2147199e+00 4.1050714e+00 3.4064570e+00 4.0228303e+00 4.2200398e+00 3.7407842e+00 3.7052383e+00 4.5139612e+00 4.3214432e+00 3.8271242e+00 3.6094426e+00 3.8122429e+00 4.0138280e+00 3.7039439e+00 6.3178534e-01 2.0121983e-01 5.0043084e-01 3.1326249e+00 2.9095058e+00 3.3192760e+00 2.4306373e+00 3.0110559e+00 2.9028946e+00 3.1074604e+00 1.7872757e+00 3.0111989e+00 2.3143923e+00 2.0898615e+00 2.6089349e+00 2.4398792e+00 3.1033306e+00 2.0139907e+00 2.8220753e+00 2.9050462e+00 2.5045154e+00 2.9220490e+00 2.3159976e+00 3.2100379e+00 2.4095513e+00 3.3067017e+00 3.1022615e+00 2.7099669e+00 2.8165723e+00 3.2163877e+00 3.4125151e+00 2.9058641e+00 1.9245956e+00 2.2287984e+00 2.1344529e+00 2.3089795e+00 3.5039202e+00 2.9050287e+00 2.9078401e+00 3.1149135e+00 2.8183214e+00 2.5042875e+00 2.4160514e+00 2.8047612e+00 3.0036601e+00 2.4102273e+00 1.8223604e+00 2.6061038e+00 2.6023240e+00 2.6040822e+00 2.7058598e+00 1.5384093e+00 2.5058827e+00 4.4178532e+00 3.5098740e+00 4.3151587e+00 4.0041429e+00 4.2110099e+00 5.0184788e+00 2.9154880e+00 4.7114737e+00 4.2061525e+00 4.5248210e+00 3.5155767e+00 3.7089995e+00 3.9157422e+00 3.4167041e+00 3.5401921e+00 3.7249704e+00 3.9056466e+00 5.1213055e+00 5.3189433e+00 3.4098171e+00 4.1203252e+00 3.3172667e+00 5.1196436e+00 3.3110366e+00 4.1111102e+00 4.4124656e+00 3.2115770e+00 3.3091938e+00 4.0103680e+00 4.2141675e+00 4.5185015e+00 4.8383751e+00 4.0135080e+00 3.5035589e+00 4.0015001e+00 4.5406852e+00 4.0218666e+00 3.9049967e+00 3.2103515e+00 3.8201314e+00 4.0245707e+00 3.5427188e+00 3.5098740e+00 4.3149009e+00 4.1273075e+00 3.6322643e+00 3.4139979e+00 3.6137088e+00 3.8212216e+00 3.5066417e+00 7.1621748e-01 4.0002221e-01 3.3858048e+00 3.1257747e+00 3.5528332e+00 2.6049377e+00 3.2291581e+00 3.1026235e+00 3.3158954e+00 1.9043238e+00 3.2362541e+00 2.5062158e+00 2.1151984e+00 2.8111676e+00 2.6144115e+00 3.3074459e+00 2.2106051e+00 3.0644792e+00 3.1042002e+00 2.7046286e+00 3.1155579e+00 2.5035275e+00 3.4095576e+00 2.6210983e+00 3.5110555e+00 3.3064076e+00 2.9323802e+00 3.0497665e+00 3.4467650e+00 3.6304824e+00 3.1087162e+00 2.1099919e+00 2.4034951e+00 2.3034398e+00 2.5081992e+00 3.7045399e+00 3.1036554e+00 3.1105454e+00 3.3425812e+00 3.0208515e+00 2.7039990e+00 2.6042124e+00 3.0014077e+00 3.2086215e+00 2.6068616e+00 1.9064532e+00 2.8033825e+00 2.8033607e+00 2.8042643e+00 2.9174390e+00 1.6121856e+00 2.7050791e+00 4.6165570e+00 3.7079065e+00 4.5303834e+00 4.2064764e+00 4.4135512e+00 5.2388103e+00 3.1079784e+00 4.9275471e+00 4.4124760e+00 4.7382279e+00 3.7227931e+00 3.9129335e+00 4.1268500e+00 3.6117351e+00 3.7315577e+00 3.9257254e+00 4.1111068e+00 5.3430927e+00 5.5370582e+00 3.6046347e+00 4.3307527e+00 3.5130597e+00 5.3416790e+00 3.5154388e+00 4.3179254e+00 4.6302391e+00 3.4146546e+00 3.5107904e+00 4.2125004e+00 4.4361489e+00 4.7415152e+00 5.0768435e+00 4.2149814e+00 3.7084460e+00 4.2023583e+00 4.7769945e+00 4.2205945e+00 4.1089343e+00 3.4107328e+00 4.0362383e+00 4.2295174e+00 3.7618281e+00 3.7079065e+00 4.5213131e+00 4.3307527e+00 3.8409517e+00 3.6158715e+00 3.8200965e+00 4.0195882e+00 3.7063858e+00 4.1210927e-01 3.2157661e+00 3.0055754e+00 3.4095823e+00 2.5182899e+00 3.1060146e+00 3.0020171e+00 3.2051958e+00 1.8468437e+00 3.1049591e+00 2.4099079e+00 2.1180305e+00 2.7069308e+00 2.5226256e+00 3.2022186e+00 2.1097489e+00 2.9102819e+00 3.0041469e+00 2.6022650e+00 3.0136614e+00 2.4087525e+00 3.3085287e+00 2.5053901e+00 3.4040883e+00 3.2011770e+00 2.8045980e+00 2.9078384e+00 3.3077457e+00 3.5074140e+00 3.0043867e+00 2.0123013e+00 2.3159429e+00 2.2186309e+00 2.4051787e+00 3.6030023e+00 3.0041461e+00 3.0063027e+00 3.2075252e+00 2.9100849e+00 2.6032671e+00 2.5097006e+00 2.9028412e+00 3.1024718e+00 2.5058120e+00 1.8685011e+00 2.7040002e+00 2.7016556e+00 2.7029487e+00 2.8031364e+00 1.5747356e+00 2.6040007e+00 4.5158117e+00 3.6083256e+00 4.4100325e+00 4.1032589e+00 4.3091726e+00 5.1114855e+00 3.0117223e+00 4.8065772e+00 4.3039464e+00 4.6187506e+00 3.6121095e+00 3.8069169e+00 4.0114753e+00 3.5138377e+00 3.6350529e+00 3.8212252e+00 4.0040508e+00 5.2135438e+00 5.4123528e+00 3.5063866e+00 4.2155461e+00 3.4147661e+00 5.2119900e+00 3.4083227e+00 4.2084707e+00 4.5071259e+00 3.3090897e+00 3.4075560e+00 4.1085724e+00 4.3075896e+00 4.6108642e+00 4.9236635e+00 4.1113689e+00 3.6022523e+00 4.1009647e+00 4.6262707e+00 4.1190929e+00 4.0037820e+00 3.3086298e+00 3.9141023e+00 4.1202674e+00 3.6321858e+00 3.6083256e+00 4.4117993e+00 4.2229640e+00 3.7257625e+00 3.5107118e+00 3.7106642e+00 3.9184747e+00 3.6056703e+00 3.3320214e+00 3.1087156e+00 3.5191298e+00 2.6048201e+00 3.2097208e+00 3.1014199e+00 3.3064692e+00 1.9064149e+00 3.2109426e+00 2.5061784e+00 2.1231492e+00 2.8062729e+00 2.6052659e+00 3.3025806e+00 2.2069764e+00 3.0213628e+00 3.1034889e+00 2.7008785e+00 3.1069083e+00 2.5018386e+00 3.4076243e+00 2.6061038e+00 3.5039680e+00 3.3015400e+00 2.9090661e+00 3.0158419e+00 3.4158824e+00 3.6117739e+00 3.1042038e+00 2.1025721e+00 2.4028385e+00 2.3026344e+00 2.5028039e+00 3.7026090e+00 3.1034538e+00 3.1060427e+00 3.3145497e+00 3.0064351e+00 2.7026184e+00 2.6035547e+00 3.0010106e+00 3.2029877e+00 2.6024546e+00 1.9099608e+00 2.8022622e+00 2.8013859e+00 2.8022964e+00 2.9047883e+00 1.6144390e+00 2.7027522e+00 4.6146429e+00 3.7070840e+00 4.5144445e+00 4.2034836e+00 4.4092638e+00 5.2185417e+00 3.1080879e+00 4.9117250e+00 4.4052231e+00 4.7224998e+00 3.7130492e+00 3.9071930e+00 4.1140176e+00 3.6112039e+00 3.7307535e+00 3.9200662e+00 4.1050716e+00 5.3212806e+00 5.5187164e+00 3.6026912e+00 4.3179254e+00 3.5126721e+00 5.3198415e+00 3.5083463e+00 4.3098506e+00 4.6126508e+00 3.4087523e+00 3.5071392e+00 4.2084730e+00 4.4144909e+00 4.7184838e+00 5.0381916e+00 4.2109654e+00 3.7029588e+00 4.2008334e+00 4.7393168e+00 4.2176488e+00 4.1043916e+00 3.4078439e+00 4.0181863e+00 4.2205945e+00 3.7363783e+00 3.7070840e+00 4.5130589e+00 4.3227927e+00 3.8267268e+00 3.6097220e+00 3.8114954e+00 4.0168944e+00 3.7050916e+00 6.0017982e-01 2.0181667e-01 1.5160570e+00 5.2133802e-01 1.3002451e+00 7.0008584e-01 2.1344529e+00 4.1212852e-01 1.8029854e+00 2.0342311e+00 1.1019599e+00 1.1393620e+00 9.0026497e-01 1.4543172e+00 3.3813251e-01 1.4000061e+00 1.2053003e+00 1.0426638e+00 1.4134492e+00 1.1005364e+00 9.3424697e-01 7.8890806e-01 9.0142636e-01 6.1119558e-01 4.1315633e-01 4.0125062e-01 3.6452132e-01 1.0001753e+00 1.4158897e+00 1.5195166e+00 1.5300146e+00 1.2201636e+00 1.0039209e+00 1.6000032e+00 1.0000457e+00 3.0017653e-01 9.3329055e-01 1.4017696e+00 1.5061610e+00 1.5012947e+00 9.0002570e-01 1.2124837e+00 2.0445123e+00 1.4012283e+00 1.3008855e+00 1.3009222e+00 8.0291749e-01 2.0440118e+00 1.3027556e+00 1.3788457e+00 1.2029161e+00 1.2089253e+00 9.3446811e-01 1.1298636e+00 1.9014076e+00 2.1006232e+00 1.6001224e+00 1.1139906e+00 1.4544336e+00 6.3912709e-01 7.1183012e-01 8.5409862e-01 1.3086191e+00 1.2638465e+00 9.2745734e-01 8.1117067e-01 2.0027889e+00 2.2028146e+00 1.1270327e+00 1.0776190e+00 1.4019372e+00 2.0011307e+00 7.2044167e-01 1.0208709e+00 1.3002450e+00 8.0488008e-01 9.0145141e-01 9.4622126e-01 1.1000289e+00 1.4009513e+00 1.7086186e+00 9.7694377e-01 7.0911112e-01 1.0224133e+00 1.4220925e+00 1.0923537e+00 8.2631334e-01 1.0008620e+00 7.8886139e-01 1.0777307e+00 9.0140221e-01 1.2029161e+00 1.2362755e+00 1.1897289e+00 9.0534502e-01 7.9871893e-01 6.5724028e-01 9.8998705e-01 1.1010807e+00 5.2133179e-01 1.0171340e+00 4.0004442e-01 7.0470720e-01 2.0181667e-01 1.5698091e+00 3.0922892e-01 1.2049541e+00 1.5104875e+00 5.0476836e-01 1.0069214e+00 3.4085233e-01 9.6593231e-01 3.0026460e-01 8.0004443e-01 6.6334810e-01 1.0000152e+00 8.7372177e-01 5.0855077e-01 5.2524663e-01 7.0462697e-01 4.2362917e-01 3.0915245e-01 2.2608083e-01 4.5784410e-01 5.0517282e-01 4.1209001e-01 1.0313359e+00 9.9085945e-01 1.0179856e+00 6.9600743e-01 6.3912943e-01 1.0000152e+00 4.0125062e-01 3.0482299e-01 9.0002615e-01 8.0254500e-01 9.3735629e-01 9.1446938e-01 3.0490481e-01 6.9600743e-01 1.4994060e+00 8.0928056e-01 7.0184453e-01 7.0184453e-01 3.1328089e-01 1.5989637e+00 7.0918894e-01 1.5237054e+00 6.9987517e-01 1.4060443e+00 1.1002025e+00 1.3061181e+00 2.1141220e+00 1.5030978e+00 1.8055480e+00 1.3062025e+00 1.6223413e+00 6.3164977e-01 8.1112909e-01 1.0095513e+00 8.0713433e-01 9.2867113e-01 9.0155393e-01 1.0001753e+00 2.2182690e+00 2.4124980e+00 1.0039060e+00 1.2201577e+00 8.1343016e-01 2.2176846e+00 5.2491734e-01 1.2037520e+00 1.5066999e+00 4.2362917e-01 4.2362917e-01 1.1060937e+00 1.3130978e+00 1.6177611e+00 1.9760242e+00 1.1138953e+00 6.0948506e-01 1.1056693e+00 1.6779798e+00 1.1527746e+00 1.0001601e+00 4.2362917e-01 9.1892454e-01 1.1528477e+00 8.3183672e-01 6.9987517e-01 1.4094144e+00 1.2633467e+00 8.5440680e-01 7.2036951e-01 7.1629303e-01 9.6576136e-01 6.3322667e-01 1.4267554e+00 4.2268438e-01 1.2004262e+00 6.0035621e-01 2.0860325e+00 3.4342562e-01 1.7133162e+00 1.9641993e+00 1.0207260e+00 1.0897469e+00 8.0008964e-01 1.4650300e+00 5.0043084e-01 1.3002407e+00 1.1303267e+00 9.3424659e-01 1.3473688e+00 1.0001604e+00 9.6593231e-01 6.7616545e-01 8.0097499e-01 6.3192325e-01 5.0437695e-01 3.0026460e-01 2.2608083e-01 9.0142636e-01 1.4861824e+00 1.4580174e+00 1.4889602e+00 1.1900969e+00 9.0142681e-01 1.5001212e+00 9.0166476e-01 2.2538848e-01 8.3187290e-01 1.3130978e+00 1.4197078e+00 1.4012600e+00 8.0046764e-01 1.1544060e+00 2.0075255e+00 1.3063533e+00 1.2089835e+00 1.2089313e+00 7.4275547e-01 2.0887699e+00 1.2190319e+00 1.1935069e+00 1.1010807e+00 1.0087396e+00 7.4335736e-01 9.3424697e-01 1.7023957e+00 2.0003507e+00 1.4002035e+00 9.1449234e-01 1.2643523e+00 5.2167829e-01 5.5450500e-01 6.7616902e-01 1.2049541e+00 1.1528553e+00 8.1112984e-01 6.1119558e-01 1.8053679e+00 2.0034894e+00 1.0142484e+00 9.0155438e-01 1.3009222e+00 1.8029948e+00 6.1119267e-01 8.2425704e-01 1.1002025e+00 7.0176271e-01 8.0046685e-01 7.5564478e-01 9.0026588e-01 1.2017042e+00 1.5269837e+00 7.9871893e-01 6.0201716e-01 8.6051471e-01 1.2366099e+00 9.4532171e-01 6.3309012e-01 9.0026588e-01 6.3164729e-01 9.3308853e-01 8.0004443e-01 1.1010807e+00 1.0426516e+00 1.0426760e+00 8.0051115e-01 6.8161057e-01 5.2491734e-01 8.6084272e-01 1.0001753e+00 1.0116865e+00 5.6370994e-01 1.0599087e+00 7.4329527e-01 1.1110092e+00 4.1212852e-01 5.6838732e-01 7.0478886e-01 5.0436965e-01 7.7598796e-01 6.0948506e-01 1.2192920e+00 7.1629303e-01 4.2270142e-01 7.1629303e-01 2.2608083e-01 9.7033357e-01 6.3164729e-01 9.6576136e-01 7.5503094e-01 9.1446896e-01 1.1138955e+00 1.3139296e+00 1.2705641e+00 6.5724028e-01 5.0894102e-01 2.2573593e-01 3.3813251e-01 4.1212852e-01 1.1025819e+00 7.1629303e-01 1.1039833e+00 1.2272550e+00 8.0245746e-01 7.0000303e-01 2.0000000e-01 4.1210927e-01 7.7598796e-01 3.3813251e-01 7.1700774e-01 4.0125062e-01 7.0017011e-01 6.0035305e-01 7.4329414e-01 1.0008768e+00 5.0043084e-01 2.0249458e+00 1.1061923e+00 2.0081838e+00 1.6061519e+00 1.8167511e+00 2.7171724e+00 6.3925756e-01 2.3875202e+00 1.8286180e+00 2.2239101e+00 1.2353587e+00 1.3278587e+00 1.6038059e+00 1.0207533e+00 1.2407946e+00 1.3868868e+00 1.5269837e+00 2.8396169e+00 2.9941208e+00 1.0030871e+00 1.8005082e+00 9.3733589e-01 2.8269381e+00 9.7033357e-01 1.7520952e+00 2.1193712e+00 8.6676847e-01 9.4912864e-01 1.6147493e+00 1.9768316e+00 2.2674825e+00 2.7199050e+00 1.6193612e+00 1.1298636e+00 1.6009488e+00 2.4288142e+00 1.6617386e+00 1.5199103e+00 8.6676847e-01 1.5881447e+00 1.6785116e+00 1.4886759e+00 1.1061923e+00 1.9457481e+00 1.7813291e+00 1.3946348e+00 1.0498347e+00 1.2771155e+00 1.4848797e+00 1.1157320e+00 8.0004523e-01 5.0043842e-01 1.6743483e+00 2.0121983e-01 1.3061139e+00 1.5464046e+00 6.0964597e-01 7.1183012e-01 4.0006662e-01 1.0776296e+00 3.0922892e-01 9.0002570e-01 7.3084171e-01 6.0184622e-01 9.3446811e-01 6.1135434e-01 6.0964597e-01 3.4080442e-01 4.1210927e-01 3.0490481e-01 2.2608083e-01 3.0482299e-01 4.0363334e-01 5.0001522e-01 1.1298636e+00 1.0440350e+00 1.0803561e+00 7.8935898e-01 5.6347978e-01 1.1000098e+00 6.3165225e-01 3.0482299e-01 5.0126466e-01 9.0511169e-01 1.0088926e+00 1.0001903e+00 4.0125062e-01 7.4335736e-01 1.5972311e+00 9.0142681e-01 8.0296037e-01 8.0250202e-01 3.4085233e-01 1.7081446e+00 8.0883841e-01 1.4329858e+00 7.2036951e-01 1.3050153e+00 1.0001753e+00 1.2089252e+00 2.0109333e+00 1.6000184e+00 1.7036944e+00 1.2001396e+00 1.5327217e+00 5.7608844e-01 7.0462844e-01 9.1449234e-01 8.1156529e-01 9.3733552e-01 8.5583415e-01 9.0029018e-01 2.1191883e+00 2.3098756e+00 6.3912709e-01 1.1290757e+00 9.0532049e-01 2.1139617e+00 3.4085233e-01 1.1074834e+00 1.4044980e+00 3.4080442e-01 4.2362917e-01 1.0087250e+00 1.2089253e+00 1.5132032e+00 1.8748226e+00 1.0207260e+00 5.0042326e-01 1.0008620e+00 1.5694554e+00 1.0837679e+00 9.0053003e-01 5.0517282e-01 8.2671175e-01 1.0777411e+00 8.1156529e-01 7.2036951e-01 1.3133662e+00 1.1910068e+00 8.2425704e-01 4.5847767e-01 6.3178534e-01 9.1590889e-01 6.3322667e-01 6.3322667e-01 1.2193537e+00 9.0000091e-01 6.3165225e-01 1.0599087e+00 3.1328089e-01 6.3451734e-01 4.0127250e-01 9.0000091e-01 1.0001604e+00 2.2573593e-01 4.1212852e-01 6.3178534e-01 6.0202028e-01 5.2524663e-01 5.2132556e-01 6.1135434e-01 4.0125062e-01 7.0008584e-01 9.0002615e-01 1.1001014e+00 1.0039209e+00 3.0482299e-01 1.0001751e+00 7.0478886e-01 8.0296037e-01 6.0000952e-01 6.0366256e-01 3.0915245e-01 6.0365948e-01 1.0001903e+00 6.3164977e-01 4.0125062e-01 5.0476836e-01 2.2608083e-01 4.0127250e-01 5.0043842e-01 1.2102248e+00 3.0017653e-01 3.0482299e-01 3.0008832e-01 5.0043084e-01 1.5012947e+00 4.0000000e-01 1.5653766e+00 6.7616902e-01 1.5829749e+00 1.1074742e+00 1.3373141e+00 2.2681751e+00 8.0291671e-01 1.9315820e+00 1.3458100e+00 1.7862938e+00 8.7372177e-01 8.7209348e-01 1.2093969e+00 7.1700774e-01 1.1055705e+00 1.0604287e+00 1.0451812e+00 2.3834499e+00 2.5286011e+00 6.3322667e-01 1.3903623e+00 7.0462697e-01 2.3796582e+00 6.3912943e-01 1.2792049e+00 1.6907308e+00 5.6595488e-01 5.3943256e-01 1.1400339e+00 1.5965952e+00 1.8639835e+00 2.3424496e+00 1.1635325e+00 6.7626502e-01 1.1005460e+00 2.0903382e+00 1.2459141e+00 1.0236548e+00 5.0894102e-01 1.2528590e+00 1.2949162e+00 1.2662318e+00 6.7616902e-01 1.4817248e+00 1.3908238e+00 1.1389163e+00 6.9600743e-01 8.9540816e-01 1.0858512e+00 6.3192325e-01 1.5890088e+00 4.2270142e-01 1.1330776e+00 1.5368468e+00 5.2491734e-01 1.1187430e+00 4.0243965e-01 1.1139906e+00 4.1420960e-01 7.0096858e-01 7.3895268e-01 1.1000100e+00 9.3861512e-01 4.0127250e-01 7.1708289e-01 8.0004523e-01 5.2167208e-01 4.5784410e-01 3.6452132e-01 5.6371422e-01 4.2270142e-01 4.1317535e-01 1.2159868e+00 1.0567817e+00 1.1138092e+00 8.3387677e-01 6.1119267e-01 9.0029064e-01 3.0482299e-01 4.0125062e-01 1.0003196e+00 7.4395693e-01 9.3459651e-01 8.5617086e-01 3.0922892e-01 8.0073117e-01 1.5493206e+00 7.5564478e-01 6.4049114e-01 6.4049114e-01 4.5784410e-01 1.7404389e+00 6.9600743e-01 1.3253457e+00 6.4049114e-01 1.2202193e+00 9.0142636e-01 1.1056785e+00 1.9348400e+00 1.4092540e+00 1.6176783e+00 1.1286101e+00 1.4350761e+00 4.5148429e-01 6.7720957e-01 8.1757693e-01 8.2671175e-01 8.1937731e-01 7.4263078e-01 8.0055465e-01 2.0418418e+00 2.2277117e+00 1.1002025e+00 1.0286508e+00 7.2044167e-01 2.0415798e+00 6.0035305e-01 1.0039060e+00 1.3253497e+00 5.0043842e-01 3.1328089e-01 9.0999313e-01 1.1528476e+00 1.4548293e+00 1.8637334e+00 9.1892454e-01 5.2133179e-01 9.3310976e-01 1.5801693e+00 9.6572569e-01 8.0008964e-01 3.4085233e-01 7.5508853e-01 9.6674360e-01 7.4618926e-01 6.4049114e-01 1.2101609e+00 1.0782105e+00 7.2113820e-01 8.0093081e-01 5.2524663e-01 7.8886139e-01 4.5847767e-01 1.7572657e+00 6.1288055e-01 4.0125062e-01 1.0858512e+00 1.1133984e+00 1.4858469e+00 7.1779518e-01 1.8187119e+00 1.2137020e+00 9.6591433e-01 1.4146346e+00 7.4263078e-01 1.5359852e+00 1.2093243e+00 1.7083042e+00 1.4853863e+00 1.5241361e+00 1.7234436e+00 1.9756319e+00 1.9771636e+00 1.3035495e+00 8.0008884e-01 6.3164977e-01 6.0948212e-01 9.1449234e-01 1.8179429e+00 1.2062153e+00 1.3486924e+00 1.8673780e+00 1.4543172e+00 8.7240114e-01 7.4329527e-01 1.1055892e+00 1.4158897e+00 9.3310976e-01 1.1269424e-01 9.3351278e-01 9.7600992e-01 9.6953662e-01 1.3458100e+00 3.0490481e-01 9.0320459e-01 2.7259033e+00 1.8109877e+00 2.7506971e+00 2.3226569e+00 2.5372438e+00 3.4590995e+00 1.2089192e+00 3.1281646e+00 2.5610212e+00 2.9500632e+00 1.9406671e+00 2.0633570e+00 2.3443688e+00 1.7168003e+00 1.8708330e+00 2.0857354e+00 2.2573821e+00 3.5725062e+00 3.7336869e+00 1.7229558e+00 2.5362836e+00 1.6198349e+00 3.5690915e+00 1.7116793e+00 2.4776152e+00 2.8599555e+00 1.6016303e+00 1.6534235e+00 2.3372717e+00 2.7159844e+00 3.0094888e+00 3.4430661e+00 2.3406025e+00 1.8663319e+00 2.3090404e+00 3.1586433e+00 2.3454995e+00 2.2408459e+00 1.5471213e+00 2.3192230e+00 2.4059451e+00 2.1751377e+00 1.8109877e+00 2.6757243e+00 2.4966678e+00 2.1111734e+00 1.7901165e+00 2.0121175e+00 2.1471399e+00 1.8133657e+00 1.4043036e+00 1.6388784e+00 7.0470867e-01 7.7652636e-01 5.0001522e-01 1.1269424e+00 2.2608083e-01 1.0000158e+00 8.0928056e-01 7.0470867e-01 1.0215068e+00 7.1708289e-01 6.3164977e-01 4.2362917e-01 5.0002283e-01 3.0474106e-01 2.0121983e-01 2.2608083e-01 4.5080200e-01 6.0017982e-01 1.1529284e+00 1.1298636e+00 1.1544060e+00 8.5406674e-01 6.3322667e-01 1.2000066e+00 6.3309258e-01 2.2608083e-01 6.0201716e-01 1.0030721e+00 1.1060937e+00 1.1001110e+00 5.0001522e-01 8.2458478e-01 1.6747799e+00 1.0008617e+00 9.0140221e-01 9.0140131e-01 4.1209001e-01 1.7511598e+00 9.0506299e-01 1.4854079e+00 8.3187290e-01 1.3139296e+00 1.0032293e+00 1.2362702e+00 2.0078120e+00 1.7001329e+00 1.7019430e+00 1.2016381e+00 1.5675442e+00 7.1700909e-01 7.4275547e-01 9.6574369e-01 9.3541878e-01 1.1298552e+00 1.0208844e+00 9.0506343e-01 2.1136134e+00 2.3085898e+00 7.4618926e-01 1.1897982e+00 1.0208709e+00 2.1090362e+00 5.0894102e-01 1.1286018e+00 1.4024091e+00 5.2167829e-01 5.6595908e-01 1.0426638e+00 1.2037520e+00 1.5079206e+00 1.8503663e+00 1.0776296e+00 5.0477564e-01 1.0032296e+00 1.5611241e+00 1.1910693e+00 9.0511169e-01 6.3178782e-01 9.0184172e-01 1.1896660e+00 1.0032443e+00 8.3187290e-01 1.3450688e+00 1.3020492e+00 1.0087252e+00 6.1990228e-01 7.4263078e-01 1.0458540e+00 7.3084171e-01 7.0918894e-01 7.0176271e-01 8.1112984e-01 9.6574336e-01 4.1317535e-01 1.5005626e+00 6.1119558e-01 6.0964597e-01 1.0116724e+00 4.1315633e-01 9.3848935e-01 9.0000136e-01 1.1896660e+00 9.6574369e-01 1.2003597e+00 1.4006465e+00 1.6096629e+00 1.5401713e+00 8.2421923e-01 5.3914287e-01 3.6259865e-01 4.2362917e-01 6.0017665e-01 1.2189701e+00 6.0202028e-01 8.7212232e-01 1.5067961e+00 1.1024820e+00 4.1317535e-01 3.0490481e-01 5.0477564e-01 9.3329017e-01 6.0018299e-01 6.1845783e-01 4.1210927e-01 5.0894102e-01 5.0477564e-01 1.0008620e+00 9.0029064e-01 5.0043842e-01 2.1169442e+00 1.2049539e+00 2.2014913e+00 1.7227908e+00 1.9366943e+00 2.8973091e+00 6.0383105e-01 2.5621105e+00 1.9756002e+00 2.3854144e+00 1.4163126e+00 1.4857201e+00 1.8044011e+00 1.1074834e+00 1.2661803e+00 1.4994060e+00 1.6741010e+00 3.0107625e+00 3.1586112e+00 1.1298552e+00 1.9796588e+00 1.0095370e+00 3.0090568e+00 1.1900276e+00 1.8963668e+00 2.3135911e+00 1.0782107e+00 1.0783219e+00 1.7384347e+00 2.2009981e+00 2.4793129e+00 2.9421182e+00 1.7402224e+00 1.3018103e+00 1.7072540e+00 2.6745468e+00 1.7367211e+00 1.6485141e+00 9.6691372e-01 1.8209763e+00 1.8293641e+00 1.7435092e+00 1.2049539e+00 2.0881329e+00 1.9090398e+00 1.6063540e+00 1.2407432e+00 1.4664722e+00 1.5386307e+00 1.2093243e+00 1.0943600e+00 1.0030868e+00 1.3272142e+00 9.1446938e-01 1.7295996e+00 1.1336109e+00 8.7209296e-01 1.2643054e+00 6.3912943e-01 1.4396100e+00 1.1299441e+00 1.5271597e+00 1.3148232e+00 1.4267817e+00 1.6268514e+00 1.8468437e+00 1.8305154e+00 1.1759988e+00 7.4262850e-01 5.2491734e-01 5.2167208e-01 8.5586571e-01 1.6206300e+00 1.1291536e+00 1.4631182e+00 1.7576822e+00 1.3254284e+00 1.0172489e+00 6.0605366e-01 9.1894698e-01 1.2951152e+00 8.3187290e-01 3.0474106e-01 8.1500329e-01 1.0396215e+00 9.6150595e-01 1.2528590e+00 5.6347978e-01 8.7240114e-01 2.5401214e+00 1.6166178e+00 2.5672376e+00 2.1256636e+00 2.3434890e+00 3.2706021e+00 1.0235120e+00 2.9379053e+00 2.3650373e+00 2.7819820e+00 1.7939428e+00 1.8718670e+00 2.1669217e+00 1.5269837e+00 1.7152309e+00 1.9257519e+00 2.0672316e+00 3.3962101e+00 3.5413320e+00 1.5241169e+00 2.3604685e+00 1.4422764e+00 3.3805191e+00 1.5352907e+00 2.2985560e+00 2.6785349e+00 1.4314424e+00 1.4886759e+00 2.1422340e+00 2.5406863e+00 2.8290490e+00 3.2861997e+00 2.1472832e+00 1.6782365e+00 2.1087015e+00 2.9939447e+00 2.1830772e+00 2.0525906e+00 1.3935744e+00 2.1564350e+00 2.2287876e+00 2.0426476e+00 1.6166178e+00 2.4889554e+00 2.3256006e+00 1.9561612e+00 1.6066555e+00 1.8386981e+00 2.0009986e+00 1.6313110e+00 8.0883916e-01 5.0043842e-01 6.0202028e-01 8.0004602e-01 3.3808272e-01 5.0437695e-01 8.0093081e-01 5.2838320e-01 6.0201716e-01 2.5399984e-01 7.2036819e-01 5.0517282e-01 5.0043842e-01 7.0008584e-01 9.1424701e-01 9.0157896e-01 3.0017653e-01 7.2044167e-01 6.2656178e-01 6.6334810e-01 3.6259865e-01 9.0026588e-01 5.0436235e-01 4.1212852e-01 8.0879701e-01 7.0478886e-01 3.0482299e-01 5.2201750e-01 4.5847767e-01 4.0125062e-01 4.1317535e-01 1.0363096e+00 3.4080442e-01 3.0474106e-01 2.2573593e-01 3.0490481e-01 1.2204839e+00 2.4195741e-01 1.8101835e+00 9.0166476e-01 1.7375279e+00 1.4002005e+00 1.6032003e+00 2.4532171e+00 1.0032443e+00 2.1331916e+00 1.6052507e+00 1.9422649e+00 9.1894698e-01 1.1025819e+00 1.3276411e+00 8.1719606e-01 1.0142626e+00 1.1298636e+00 1.3025621e+00 2.5612597e+00 2.7430487e+00 9.0155438e-01 1.5299064e+00 7.1708289e-01 2.5605373e+00 7.0633229e-01 1.5079428e+00 1.8443132e+00 6.0383105e-01 7.0096708e-01 1.4023806e+00 1.6740160e+00 1.9756002e+00 2.3801033e+00 1.4049093e+00 9.0142636e-01 1.4001717e+00 2.0898615e+00 1.4183606e+00 1.3009222e+00 6.0184622e-01 1.2661803e+00 1.4267527e+00 1.1084368e+00 9.0166476e-01 1.7108631e+00 1.5299252e+00 1.0782751e+00 8.1343016e-01 1.0116721e+00 1.2193537e+00 9.0026497e-01 7.9148746e-01 7.0993998e-01 9.3541878e-01 8.1937731e-01 5.0043084e-01 5.6370994e-01 4.1212852e-01 1.0782753e+00 6.0184622e-01 9.0557807e-01 7.4269314e-01 7.0633229e-01 8.2841920e-01 9.1695534e-01 1.0756891e+00 7.3084048e-01 5.2491131e-01 5.0085236e-01 5.0476836e-01 5.0085236e-01 1.1074740e+00 8.3916809e-01 1.2049539e+00 9.6501813e-01 4.2270142e-01 8.0291749e-01 5.0855077e-01 5.3943256e-01 8.2631334e-01 4.0243965e-01 1.0207260e+00 5.2524663e-01 8.0055465e-01 7.0184453e-01 7.0184453e-01 1.0777307e+00 6.0366256e-01 2.0696799e+00 1.1543334e+00 1.9286352e+00 1.6071727e+00 1.8312163e+00 2.6295459e+00 1.1153247e+00 2.3155068e+00 1.8040969e+00 2.1901871e+00 1.2562955e+00 1.3263639e+00 1.5518083e+00 1.1271226e+00 1.4557657e+00 1.4915559e+00 1.5136393e+00 2.7555974e+00 2.9267466e+00 1.0030718e+00 1.7744103e+00 1.0843333e+00 2.7324083e+00 9.6953662e-01 1.7456060e+00 2.0249458e+00 9.1568820e-01 1.0151258e+00 1.6309461e+00 1.8315348e+00 2.1358791e+00 2.5304748e+00 1.6492450e+00 1.1075720e+00 1.6001777e+00 2.2141198e+00 1.7441970e+00 1.5196090e+00 9.6683480e-01 1.4838225e+00 1.7167914e+00 1.4122282e+00 1.1543334e+00 1.9438463e+00 1.8374400e+00 1.4268530e+00 1.0778421e+00 1.2792049e+00 1.5857302e+00 1.1532421e+00 1.1019503e+00 6.0201716e-01 5.0043842e-01 6.1135434e-01 7.0008735e-01 8.1156529e-01 4.1317535e-01 7.0000303e-01 4.0246123e-01 2.0061436e-01 4.1210927e-01 5.0436965e-01 7.0000303e-01 6.0366256e-01 2.0121983e-01 1.2007726e+00 9.1916394e-01 1.0124729e+00 8.0055465e-01 4.0246123e-01 7.0008735e-01 5.0085236e-01 6.0017982e-01 6.0202028e-01 6.3165225e-01 7.4612830e-01 6.0383105e-01 1.1269424e-01 7.0184453e-01 1.4559030e+00 5.6371422e-01 5.2167829e-01 5.2133179e-01 4.0004442e-01 1.7133283e+00 6.0948800e-01 1.3743342e+00 5.2524663e-01 1.2702954e+00 9.0142636e-01 1.1286018e+00 1.9763960e+00 1.2004262e+00 1.6484371e+00 1.1066159e+00 1.5033966e+00 6.1990228e-01 6.3322667e-01 8.9538275e-01 6.1990228e-01 1.0010060e+00 9.1471442e-01 8.0488008e-01 2.0894199e+00 2.2581358e+00 7.0088627e-01 1.1085342e+00 6.3178782e-01 2.0855639e+00 4.0363334e-01 1.0293900e+00 1.3743657e+00 4.0006662e-01 4.0125062e-01 9.3329055e-01 1.2396422e+00 1.5267540e+00 1.9798779e+00 9.6591465e-01 4.0127250e-01 9.0026497e-01 1.7151603e+00 1.0797805e+00 8.0296037e-01 4.0006662e-01 8.9540816e-01 1.0837679e+00 9.6674360e-01 5.2524663e-01 1.2440789e+00 1.1938630e+00 9.1892454e-01 5.2524663e-01 6.3912943e-01 9.3733589e-01 4.5148429e-01 1.1281352e+00 9.0002570e-01 5.0517282e-01 9.4513210e-01 4.1315633e-01 1.2014191e+00 5.2133179e-01 1.3063533e+00 1.1019505e+00 8.5403370e-01 1.0426516e+00 1.3523310e+00 1.4544312e+00 9.0142636e-01 3.3818226e-01 5.0085236e-01 5.0437695e-01 3.0922892e-01 1.5001461e+00 9.0005094e-01 9.0668287e-01 1.2396475e+00 8.7209296e-01 5.0000761e-01 4.5078948e-01 8.0046764e-01 1.0030724e+00 4.1317535e-01 6.7824250e-01 6.0017665e-01 6.0000952e-01 6.0000317e-01 7.4262850e-01 6.3925756e-01 5.0001522e-01 2.4077059e+00 1.5012741e+00 2.3329496e+00 2.0008921e+00 2.2042323e+00 3.0476353e+00 9.3541878e-01 2.7309758e+00 2.2068463e+00 2.5373913e+00 1.5160787e+00 1.7043730e+00 1.9242109e+00 1.4044668e+00 1.5401330e+00 1.7168122e+00 1.9044144e+00 3.1543192e+00 3.3406961e+00 1.4044697e+00 2.1265131e+00 1.3061138e+00 3.1536525e+00 1.3069754e+00 2.1097680e+00 2.4379736e+00 1.2049541e+00 1.3017511e+00 2.0033792e+00 2.2562563e+00 2.5606009e+00 2.9377608e+00 2.0050253e+00 1.5030978e+00 2.0001168e+00 2.6390071e+00 2.0114908e+00 1.9023062e+00 1.2016381e+00 1.8467998e+00 2.0209800e+00 1.6143467e+00 1.5012741e+00 2.3121231e+00 2.1220697e+00 1.6461460e+00 1.4062065e+00 1.6118728e+00 1.8108200e+00 1.5004645e+00 1.1000005e+00 9.0305330e-01 9.0506343e-01 1.1075720e+00 8.0488008e-01 6.1119558e-01 6.3912943e-01 6.0383105e-01 3.0490481e-01 1.1269424e-01 4.1210927e-01 6.0184622e-01 7.0008735e-01 1.0803561e+00 1.2125410e+00 1.2178626e+00 9.0645118e-01 7.9153339e-01 1.3000002e+00 7.0096858e-01 3.0008832e-01 8.0245824e-01 1.1001015e+00 1.2040344e+00 1.2012928e+00 6.0017982e-01 9.0645118e-01 1.7262450e+00 1.1005460e+00 1.0000307e+00 1.0000307e+00 5.0043842e-01 1.7087610e+00 1.0003198e+00 1.6300950e+00 9.3848935e-01 1.5032156e+00 1.2007124e+00 1.4092540e+00 2.2026134e+00 1.8005395e+00 1.9004485e+00 1.4019342e+00 1.7231818e+00 7.4269314e-01 9.0668287e-01 1.1133897e+00 1.0251597e+00 1.0924484e+00 1.0143978e+00 1.1005460e+00 2.3044111e+00 2.5032992e+00 9.4511250e-01 1.3253497e+00 1.1075720e+00 2.3033192e+00 5.5450500e-01 1.3061180e+00 1.6004128e+00 5.4219811e-01 6.3912943e-01 1.2090477e+00 1.4006179e+00 1.7019555e+00 2.0185049e+00 1.2190878e+00 7.0548283e-01 1.2049539e+00 1.7202439e+00 1.2636227e+00 1.1006371e+00 7.0911112e-01 1.0207396e+00 1.2632946e+00 9.3308853e-01 9.3848935e-01 1.5130871e+00 1.3741498e+00 9.6572569e-01 6.9987517e-01 8.2421923e-01 1.0798806e+00 8.5583415e-01 5.2524663e-01 8.2418002e-01 6.3912709e-01 3.6452132e-01 5.6394820e-01 7.2036819e-01 5.0517282e-01 8.0008964e-01 1.0000005e+00 1.2000731e+00 1.1019597e+00 4.0002221e-01 1.0039063e+00 7.4612830e-01 8.3183672e-01 6.0383105e-01 6.1119558e-01 2.0000000e-01 4.5078948e-01 1.1000098e+00 7.8890806e-01 4.0122873e-01 5.6371422e-01 4.1212852e-01 5.0001522e-01 5.2524663e-01 1.2137020e+00 3.4080442e-01 3.3813251e-01 3.0490481e-01 6.0035621e-01 1.5010034e+00 4.0246123e-01 1.5265987e+00 6.1135434e-01 1.6395342e+00 1.1134850e+00 1.3309249e+00 2.3136797e+00 7.1629168e-01 1.9760038e+00 1.3748534e+00 1.8136626e+00 9.1894698e-01 9.0320459e-01 1.2661802e+00 6.0427481e-01 9.1427000e-01 9.6685270e-01 1.0777305e+00 2.4270428e+00 2.5626268e+00 8.1112909e-01 1.4228744e+00 5.2167208e-01 2.4261071e+00 7.0633229e-01 1.3043552e+00 1.7511131e+00 6.0383105e-01 5.2491131e-01 1.1330776e+00 1.6740160e+00 1.9314874e+00 2.4166999e+00 1.1400420e+00 7.4269200e-01 1.1024820e+00 2.1702438e+00 1.1639421e+00 1.0427822e+00 4.2268438e-01 1.3276412e+00 1.2710363e+00 1.3154933e+00 6.1135434e-01 1.4922566e+00 1.3466030e+00 1.1400339e+00 7.3461436e-01 9.3733552e-01 9.7694377e-01 6.0365948e-01 5.8750389e-01 2.4195741e-01 8.6051471e-01 3.3818226e-01 8.1719606e-01 6.0202028e-01 6.0219099e-01 8.0337471e-01 1.0214933e+00 1.0338224e+00 5.2201750e-01 6.0000635e-01 3.6259865e-01 4.2268438e-01 2.2538848e-01 1.0087393e+00 5.4219811e-01 7.4618926e-01 9.2019277e-01 5.2838320e-01 3.4080442e-01 3.4085233e-01 3.4085233e-01 5.2838320e-01 2.0121983e-01 9.0294373e-01 3.0482299e-01 3.0490481e-01 3.0490481e-01 4.1420960e-01 1.1133986e+00 3.0017653e-01 1.9760242e+00 1.0776188e+00 1.8598666e+00 1.5071120e+00 1.7384459e+00 2.5637810e+00 9.3426769e-01 2.2403929e+00 1.7108631e+00 2.0993246e+00 1.1405598e+00 1.2394690e+00 1.4816205e+00 1.0776296e+00 1.4324323e+00 1.4163126e+00 1.4134492e+00 2.6753615e+00 2.8540068e+00 9.1001664e-01 1.6986597e+00 1.0426638e+00 2.6697938e+00 9.0657539e-01 1.6396943e+00 1.9541963e+00 8.5583415e-01 9.0207914e-01 1.5412500e+00 1.7849153e+00 2.0880425e+00 2.4973149e+00 1.5650163e+00 1.0060994e+00 1.5001440e+00 2.2185588e+00 1.6410190e+00 1.4111252e+00 8.5440680e-01 1.4330979e+00 1.6474117e+00 1.4095656e+00 1.0776188e+00 1.8534896e+00 1.7579984e+00 1.3938438e+00 1.0171340e+00 1.1990152e+00 1.4686236e+00 1.0427822e+00 6.8261201e-01 1.0004792e+00 6.3178782e-01 4.1210927e-01 6.0202028e-01 7.0025283e-01 8.0245903e-01 6.7720957e-01 8.1719606e-01 7.0008432e-01 1.0069214e+00 7.9153339e-01 8.6054545e-01 6.4049114e-01 6.3178782e-01 9.0155393e-01 1.2000065e+00 9.0508712e-01 2.0181667e-01 8.2635069e-01 7.1708289e-01 7.0548283e-01 8.0000239e-01 5.4219811e-01 1.3530568e+00 6.3322667e-01 8.0967961e-01 7.1708289e-01 7.0016860e-01 1.5402579e+00 6.3925756e-01 1.5611241e+00 6.4620889e-01 1.4283663e+00 1.1134850e+00 1.3189663e+00 2.1346646e+00 1.3000497e+00 1.8186729e+00 1.3009674e+00 1.7335369e+00 1.0118233e+00 8.1117067e-01 1.0567664e+00 6.0605366e-01 9.2867113e-01 1.0782857e+00 1.0429127e+00 2.2917679e+00 2.4270713e+00 5.0042326e-01 1.2848760e+00 6.9987517e-01 2.2396581e+00 5.2491734e-01 1.3051737e+00 1.5457820e+00 6.0365948e-01 8.0291749e-01 1.1110184e+00 1.3561934e+00 1.6492450e+00 2.1212048e+00 1.1186586e+00 6.7616723e-01 1.1005365e+00 1.7574668e+00 1.3269962e+00 1.0777411e+00 8.0097499e-01 1.0411548e+00 1.1972915e+00 9.9911696e-01 6.4620889e-01 1.4422764e+00 1.3473056e+00 9.3861512e-01 5.2491734e-01 8.6084272e-01 1.2528048e+00 8.2498722e-01 9.6150595e-01 5.0477564e-01 1.0214931e+00 8.0923926e-01 8.0492246e-01 1.0062544e+00 1.2363856e+00 1.2438823e+00 6.2656178e-01 4.0006662e-01 1.2085435e-01 2.0181667e-01 2.2573593e-01 1.2016443e+00 6.3925756e-01 9.2019277e-01 1.1335345e+00 7.1636719e-01 5.0084481e-01 2.0121983e-01 5.0002283e-01 7.3155911e-01 2.0181667e-01 6.7626681e-01 3.0915245e-01 5.0437695e-01 4.1317535e-01 6.1845783e-01 9.0506254e-01 3.0922892e-01 2.1350025e+00 1.2189760e+00 2.0658767e+00 1.7034615e+00 1.9177947e+00 2.7775988e+00 7.7598704e-01 2.4534018e+00 1.9144928e+00 2.2857680e+00 1.2749306e+00 1.4182222e+00 1.6639408e+00 1.1527669e+00 1.4140789e+00 1.4954274e+00 1.6121856e+00 2.8913337e+00 3.0644792e+00 1.1011719e+00 1.8708183e+00 1.0777305e+00 2.8852099e+00 1.0396215e+00 1.8297117e+00 2.1701542e+00 9.4532171e-01 1.0262619e+00 1.7168122e+00 2.0059655e+00 2.3063931e+00 2.7230908e+00 1.7261949e+00 1.2093243e+00 1.7002548e+00 2.4331092e+00 1.7646791e+00 1.6080687e+00 9.3848935e-01 1.6152383e+00 1.7771159e+00 1.4971922e+00 1.2189760e+00 2.0349233e+00 1.8831259e+00 1.4665397e+00 1.1400339e+00 1.3492939e+00 1.5757399e+00 1.2102248e+00 8.1117067e-01 7.0548283e-01 6.0964891e-01 6.0605366e-01 7.0918894e-01 9.0279223e-01 8.0008964e-01 3.6259865e-01 1.3154973e+00 1.0604287e+00 1.1536694e+00 9.1892454e-01 5.0477564e-01 5.0894102e-01 3.0922892e-01 8.0046764e-01 9.0778124e-01 7.1708289e-01 8.6225026e-01 6.8685125e-01 4.0363334e-01 8.4536936e-01 1.5318139e+00 6.5832080e-01 6.7636452e-01 6.3322667e-01 5.6838732e-01 1.8053679e+00 7.2044167e-01 1.2092602e+00 5.0437695e-01 1.3018595e+00 8.0291671e-01 1.0095513e+00 1.9760044e+00 1.0208709e+00 1.6387179e+00 1.0597877e+00 1.4686236e+00 6.0201716e-01 6.0427481e-01 9.3331138e-01 7.0025283e-01 6.1119558e-01 6.0427175e-01 7.4269200e-01 2.0887699e+00 2.2281421e+00 1.0001753e+00 1.0797700e+00 4.1317535e-01 2.0885107e+00 5.2133179e-01 9.6591465e-01 1.4140457e+00 4.1209001e-01 2.2573593e-01 8.1156529e-01 1.3450340e+00 1.5966664e+00 2.0855643e+00 8.1343016e-01 4.6440171e-01 8.2635069e-01 1.8444686e+00 8.2635069e-01 7.1621748e-01 2.0061436e-01 1.0088783e+00 9.1566538e-01 1.0032296e+00 5.0437695e-01 1.1543257e+00 9.8997136e-01 8.1117067e-01 7.0470867e-01 6.0980961e-01 6.3322667e-01 3.0474106e-01 9.0031539e-01 7.0000151e-01 3.3813251e-01 5.2167829e-01 8.5403428e-01 1.0095513e+00 5.0043842e-01 5.2524663e-01 6.0980961e-01 6.1288055e-01 3.0026460e-01 1.1001015e+00 7.1636719e-01 6.3309258e-01 7.4335736e-01 5.2167208e-01 5.0043084e-01 6.0184309e-01 6.0964891e-01 6.0017982e-01 3.0482299e-01 1.1153247e+00 5.0043084e-01 4.0246123e-01 4.0125062e-01 3.0017653e-01 1.1270411e+00 4.0002221e-01 2.0175565e+00 1.1056693e+00 1.9099615e+00 1.6003257e+00 1.8055799e+00 2.6186105e+00 1.2017042e+00 2.3090806e+00 1.8007233e+00 2.1233216e+00 1.1144002e+00 1.3025622e+00 1.5097103e+00 1.0216374e+00 1.2396937e+00 1.3452695e+00 1.5005647e+00 2.7241212e+00 2.9166918e+00 1.0087396e+00 1.7168636e+00 9.3733552e-01 2.7221286e+00 9.0508756e-01 1.7046111e+00 2.0107590e+00 8.0879701e-01 9.0508712e-01 1.6049314e+00 1.8174378e+00 2.1221146e+00 2.4750525e+00 1.6096791e+00 1.1000193e+00 1.6000016e+00 2.1732693e+00 1.6308665e+00 1.5004872e+00 8.0883916e-01 1.4182493e+00 1.6308803e+00 1.2094550e+00 1.1056693e+00 1.9088565e+00 1.7377460e+00 1.2661852e+00 1.0088926e+00 1.2092662e+00 1.4340155e+00 1.1019692e+00 3.4342562e-01 6.0964891e-01 5.6595908e-01 5.0437695e-01 5.2167829e-01 4.5783248e-01 1.4023777e+00 1.1286018e+00 1.2201578e+00 1.0032443e+00 3.0922892e-01 9.0642679e-01 9.0166476e-01 6.0964597e-01 5.0084481e-01 8.6054545e-01 9.6574336e-01 8.0923926e-01 5.0477564e-01 9.0532093e-01 1.6742781e+00 7.8895472e-01 7.5564478e-01 7.4618926e-01 6.0964891e-01 1.9222003e+00 8.2462252e-01 1.2093908e+00 5.2201750e-01 1.0522594e+00 7.0548138e-01 9.3735629e-01 1.7578497e+00 1.4001717e+00 1.4326118e+00 9.0166431e-01 1.3681502e+00 7.1636719e-01 4.5148429e-01 7.1183012e-01 6.3164977e-01 9.0534502e-01 8.5583415e-01 6.3322667e-01 1.9047821e+00 2.0429861e+00 3.3813251e-01 9.4634218e-01 7.1700774e-01 1.8662975e+00 3.0474106e-01 9.1695534e-01 1.1636098e+00 3.3818226e-01 5.0476836e-01 7.4329527e-01 1.0171202e+00 1.3020942e+00 1.8013674e+00 7.8935898e-01 3.0474106e-01 7.0008735e-01 1.4927071e+00 1.0336860e+00 6.7720957e-01 5.0855778e-01 7.3895268e-01 9.4622126e-01 8.4540285e-01 5.2201750e-01 1.0621172e+00 1.0788651e+00 8.1156529e-01 4.0002221e-01 5.6618864e-01 9.6935134e-01 5.2524663e-01 4.1212852e-01 5.0517282e-01 7.0008584e-01 6.3322667e-01 3.0490481e-01 1.2003660e+00 9.1552373e-01 1.0095513e+00 8.0046685e-01 4.5080200e-01 7.0105084e-01 6.0964891e-01 6.0365948e-01 5.0477564e-01 6.3178782e-01 7.4329527e-01 6.0201716e-01 2.2573593e-01 7.0096708e-01 1.4548054e+00 5.6347978e-01 5.2167208e-01 5.2133802e-01 4.0006662e-01 1.7132643e+00 6.0948506e-01 1.4655221e+00 7.0548283e-01 1.2921474e+00 9.1424701e-01 1.1900342e+00 1.9791159e+00 1.2013591e+00 1.6491682e+00 1.1111057e+00 1.5689626e+00 8.0726668e-01 7.4329527e-01 9.8998705e-01 8.0337471e-01 1.2004198e+00 1.1061923e+00 8.2635069e-01 2.0953157e+00 2.2622460e+00 6.0366256e-01 1.2097311e+00 8.0883841e-01 2.0866883e+00 6.0035621e-01 1.0858512e+00 1.3762609e+00 6.0000635e-01 6.0035305e-01 1.0143975e+00 1.2399444e+00 1.5291965e+00 1.9842703e+00 1.0777305e+00 4.1315633e-01 9.0005048e-01 1.7303039e+00 1.2394744e+00 8.2498722e-01 6.0018299e-01 9.9013884e-01 1.2395260e+00 1.1286911e+00 7.0548283e-01 1.3081175e+00 1.3479052e+00 1.1074834e+00 7.0184453e-01 8.1117067e-01 1.1186499e+00 6.0980961e-01 2.0181667e-01 5.2133802e-01 7.0548283e-01 4.0243965e-01 8.5471446e-01 9.1001664e-01 9.1916394e-01 6.0964891e-01 8.0296037e-01 1.0000307e+00 5.2524663e-01 4.1420960e-01 6.0000635e-01 8.0004523e-01 9.0166431e-01 9.0026588e-01 3.3818226e-01 6.0366256e-01 1.4340438e+00 8.0004523e-01 7.0000454e-01 7.0000151e-01 2.0000000e-01 1.4651632e+00 7.0008584e-01 1.7369589e+00 8.4540285e-01 1.6071563e+00 1.3008770e+00 1.5130871e+00 2.3098753e+00 1.5002444e+00 2.0034559e+00 1.5005854e+00 1.8322392e+00 8.5437498e-01 1.0087393e+00 1.2192920e+00 8.4786353e-01 1.1330694e+00 1.1270325e+00 1.2012867e+00 2.4144060e+00 2.6097166e+00 7.9153339e-01 1.4330116e+00 8.7209348e-01 2.4119960e+00 6.3178782e-01 1.4094452e+00 1.7039341e+00 5.6371422e-01 6.3309258e-01 1.3130937e+00 1.5066999e+00 1.8106410e+00 2.1515742e+00 1.3253458e+00 8.0004602e-01 1.3000908e+00 1.8533295e+00 1.3748188e+00 1.2012928e+00 5.7609230e-01 1.1298636e+00 1.3741846e+00 1.0451812e+00 8.4540285e-01 1.6176927e+00 1.4854079e+00 1.0777307e+00 7.4612830e-01 9.3306807e-01 1.1910068e+00 8.1715665e-01 4.0243965e-01 6.0184622e-01 6.0000952e-01 1.0158274e+00 1.1111057e+00 1.1191444e+00 8.0928056e-01 7.4335736e-01 1.2000002e+00 6.0964891e-01 3.0026460e-01 7.0088477e-01 1.0001601e+00 1.1024820e+00 1.1005458e+00 5.0042326e-01 8.0492246e-01 1.6321742e+00 1.0001753e+00 9.0005048e-01 9.0002615e-01 4.0006662e-01 1.6390068e+00 9.0029064e-01 1.6300430e+00 8.6084272e-01 1.5035329e+00 1.2004200e+00 1.4092511e+00 2.2043907e+00 1.7002548e+00 1.9010379e+00 1.4007831e+00 1.7240342e+00 7.4269314e-01 9.0534502e-01 1.1133984e+00 9.3184922e-01 1.0597992e+00 1.0142766e+00 1.1005364e+00 2.3071806e+00 2.5048249e+00 8.4536936e-01 1.3253910e+00 1.0116865e+00 2.3056305e+00 5.2838320e-01 1.3061582e+00 1.6010223e+00 4.8391482e-01 5.7608844e-01 1.2089313e+00 1.4017695e+00 1.7039229e+00 2.0293124e+00 1.2189760e+00 7.0096858e-01 1.2016380e+00 1.7295384e+00 1.2636227e+00 1.1005460e+00 6.1830489e-01 1.0208709e+00 1.2632948e+00 9.3329055e-01 8.6084272e-01 1.5130912e+00 1.3741813e+00 9.6572569e-01 6.5832080e-01 8.2418071e-01 1.0788007e+00 7.9148662e-01 3.0922892e-01 8.0046764e-01 1.3743342e+00 1.3452695e+00 1.3745152e+00 1.0776296e+00 8.0051115e-01 1.4000349e+00 8.2462252e-01 3.0026460e-01 5.7609230e-01 1.2089253e+00 1.3131370e+00 1.3002493e+00 7.0016860e-01 1.0426760e+00 1.8951252e+00 1.2036864e+00 1.1055892e+00 1.1055707e+00 6.3165225e-01 1.9760099e+00 1.1133895e+00 1.3035495e+00 1.0032296e+00 1.1134939e+00 8.1112984e-01 1.0427944e+00 1.8040883e+00 1.9000220e+00 1.5005626e+00 1.0010060e+00 1.3844234e+00 6.1288055e-01 5.7609230e-01 7.8890721e-01 1.1056785e+00 1.1270325e+00 9.0778124e-01 7.0556260e-01 1.9141294e+00 2.1052841e+00 8.2421923e-01 1.0150395e+00 1.2036863e+00 1.9046783e+00 5.2133802e-01 9.3733589e-01 1.2010584e+00 6.0948212e-01 7.0470867e-01 8.5583357e-01 1.0008768e+00 1.3033860e+00 1.6469593e+00 9.0294373e-01 5.0436965e-01 8.5406616e-01 1.3485619e+00 1.0522594e+00 7.0993998e-01 8.0250123e-01 7.4329527e-01 1.0427822e+00 9.0053003e-01 1.0032296e+00 1.1531951e+00 1.1543259e+00 9.0142681e-01 5.6618864e-01 6.1135434e-01 9.3984267e-01 9.0168933e-01 7.1629303e-01 1.5266891e+00 1.3564850e+00 1.4198077e+00 1.1544060e+00 7.0088627e-01 1.3008812e+00 7.2036951e-01 3.0482299e-01 7.4954884e-01 1.1531951e+00 1.2645755e+00 1.2053003e+00 6.1119267e-01 1.0803561e+00 1.9177201e+00 1.1286911e+00 1.0451689e+00 1.0433444e+00 7.2036951e-01 2.0856547e+00 1.0782211e+00 1.0434746e+00 9.0029064e-01 9.0279223e-01 6.0948800e-01 8.0883841e-01 1.6097492e+00 1.8003682e+00 1.3025173e+00 8.0879701e-01 1.1347620e+00 3.0922892e-01 3.6452132e-01 5.2133179e-01 1.0032293e+00 9.3308891e-01 6.0383105e-01 5.0043084e-01 1.7170314e+00 1.9082779e+00 8.5406616e-01 7.4275547e-01 1.1001110e+00 1.7132654e+00 4.1212852e-01 7.0548138e-01 1.0030871e+00 5.0085236e-01 6.0000635e-01 6.1135434e-01 8.0879701e-01 1.1134075e+00 1.4922778e+00 6.3322667e-01 4.0246123e-01 6.8261201e-01 1.1935004e+00 7.4954884e-01 5.0437695e-01 7.0008584e-01 4.5148429e-01 7.4262964e-01 6.0018299e-01 9.0029064e-01 9.1424701e-01 8.5437440e-01 6.0017665e-01 5.2167208e-01 3.0915245e-01 6.4620889e-01 8.0000160e-01 1.0033867e+00 7.3461436e-01 8.2512420e-01 6.0219099e-01 6.0017982e-01 6.0000317e-01 5.0000761e-01 7.0016860e-01 6.0202028e-01 4.5148429e-01 5.7630313e-01 5.0855778e-01 1.2699992e-01 5.0894102e-01 1.2671752e+00 4.1420960e-01 3.6259865e-01 3.4080442e-01 2.4170870e-01 1.5133193e+00 4.1317535e-01 1.5238388e+00 6.0980961e-01 1.4557632e+00 1.1002023e+00 1.3069713e+00 2.1693127e+00 1.1005458e+00 1.8443124e+00 1.3063934e+00 1.6655594e+00 6.5832080e-01 8.0492246e-01 1.0498228e+00 5.7832449e-01 9.1424701e-01 9.0320459e-01 1.0032296e+00 2.2802814e+00 2.4537354e+00 7.1621613e-01 1.2528590e+00 5.3914287e-01 2.2781292e+00 4.2362917e-01 1.2128138e+00 1.5640141e+00 3.4085233e-01 4.1212852e-01 1.1060939e+00 1.4140458e+00 1.7081323e+00 2.1436849e+00 1.1138955e+00 6.0184622e-01 1.1001015e+00 1.8659091e+00 1.1544060e+00 1.0010209e+00 3.3813251e-01 1.0224270e+00 1.1635398e+00 9.7600992e-01 6.0980961e-01 1.4182493e+00 1.2705641e+00 8.9538275e-01 5.4219811e-01 7.3084171e-01 9.6936870e-01 6.0184934e-01 3.0922892e-01 2.4170870e-01 4.0127250e-01 1.6009488e+00 1.0040629e+00 1.0499492e+00 1.2653025e+00 9.1471442e-01 6.1119558e-01 5.0477564e-01 9.0005048e-01 1.1016049e+00 5.0043084e-01 7.0096708e-01 7.0088627e-01 7.0470720e-01 7.0176121e-01 8.0967961e-01 6.3165225e-01 6.0201716e-01 2.5221737e+00 1.6096629e+00 2.4221589e+00 2.1015969e+00 2.3098905e+00 3.1317714e+00 1.0597879e+00 2.8188299e+00 2.3040148e+00 2.6373482e+00 1.6231306e+00 1.8068049e+00 2.0210084e+00 1.5237053e+00 1.7080686e+00 1.8459262e+00 2.0034094e+00 3.2387184e+00 3.4286399e+00 1.5005854e+00 2.2285172e+00 1.4324350e+00 3.2358000e+00 1.4109628e+00 2.2110849e+00 2.5224740e+00 1.3139336e+00 1.4095777e+00 2.1090366e+00 2.3323064e+00 2.6377472e+00 2.9966835e+00 2.1144760e+00 1.6012568e+00 2.1000482e+00 2.6968519e+00 2.1346646e+00 2.0025815e+00 1.3133662e+00 1.9351024e+00 2.1377869e+00 1.7137965e+00 1.6096629e+00 2.4161682e+00 2.2434416e+00 1.7684500e+00 1.5143051e+00 1.7168636e+00 1.9368172e+00 1.6050040e+00 1.1269424e-01 3.3818226e-01 1.3017961e+00 7.4612830e-01 1.0262619e+00 1.2443200e+00 8.2421923e-01 6.0202028e-01 2.2573593e-01 6.0017982e-01 8.4572653e-01 3.0922892e-01 5.6347978e-01 4.1317535e-01 6.0964891e-01 5.2201750e-01 7.3090905e-01 8.0245824e-01 4.1420960e-01 2.2297880e+00 1.3131802e+00 2.1734835e+00 1.8042696e+00 2.0169191e+00 2.8857511e+00 7.7598796e-01 2.5607759e+00 2.0181988e+00 2.3909895e+00 1.3770846e+00 1.5195166e+00 1.7689720e+00 1.2362756e+00 1.4651863e+00 1.5800353e+00 1.7155605e+00 3.0010211e+00 3.1712557e+00 1.2016443e+00 1.9735224e+00 1.1531953e+00 2.9937162e+00 1.1401191e+00 1.9335528e+00 2.2793947e+00 1.0403116e+00 1.1237940e+00 1.8155572e+00 2.1170640e+00 2.4166673e+00 2.8369211e+00 1.8227568e+00 1.3135522e+00 1.8005404e+00 2.5443612e+00 1.8557670e+00 1.7105814e+00 1.0313359e+00 1.7226330e+00 1.8708183e+00 1.5881025e+00 1.3131802e+00 2.1363271e+00 1.9752912e+00 1.5530527e+00 1.2366099e+00 1.4501583e+00 1.6655594e+00 1.3088083e+00 3.4342562e-01 1.4024091e+00 8.3183672e-01 1.0522594e+00 1.2712749e+00 8.5437498e-01 6.1119558e-01 3.3813251e-01 7.0016860e-01 9.2867113e-01 3.4342562e-01 5.2133179e-01 5.0855778e-01 6.3192325e-01 5.6618864e-01 7.5564478e-01 7.0462844e-01 4.5847767e-01 2.3345511e+00 1.4181033e+00 2.2624227e+00 1.9044571e+00 2.1188381e+00 2.9740725e+00 8.7209348e-01 2.6511588e+00 2.1151751e+00 2.4832720e+00 1.4692287e+00 1.6190709e+00 1.8603115e+00 1.3450304e+00 1.5778323e+00 1.6856949e+00 1.8133657e+00 3.0879393e+00 3.2627356e+00 1.3017553e+00 2.0678448e+00 1.2635708e+00 3.0810441e+00 1.2366675e+00 2.0307764e+00 2.3657459e+00 1.1404856e+00 1.2257611e+00 1.9176961e+00 2.1957105e+00 2.4980314e+00 2.9055191e+00 1.9262438e+00 1.4100098e+00 1.9004485e+00 2.6116431e+00 1.9609333e+00 1.8095574e+00 1.1347620e+00 1.8037909e+00 1.9725464e+00 1.6581521e+00 1.4181033e+00 2.2355461e+00 2.0784269e+00 1.6462102e+00 1.3373104e+00 1.5468618e+00 1.7698041e+00 1.4111252e+00 1.2003596e+00 6.1288055e-01 7.4618926e-01 9.6691372e-01 5.7609230e-01 3.0922892e-01 3.0490481e-01 5.0436965e-01 7.0184453e-01 1.1269424e-01 8.2635069e-01 3.0482299e-01 3.3813251e-01 3.0490481e-01 4.5148429e-01 9.3308891e-01 2.0181667e-01 2.1221982e+00 1.2089191e+00 2.0305682e+00 1.7009400e+00 1.9088256e+00 2.7434081e+00 9.1894698e-01 2.4265154e+00 1.9046790e+00 2.2453370e+00 1.2284047e+00 1.4060413e+00 1.6267848e+00 1.1281352e+00 1.3523310e+00 1.4562730e+00 1.6032169e+00 2.8519346e+00 3.0369970e+00 1.1020600e+00 1.8342569e+00 1.0426638e+00 2.8491513e+00 1.0116721e+00 1.8114932e+00 2.1335035e+00 9.1552373e-01 1.0090312e+00 1.7079369e+00 1.9522117e+00 2.2566913e+00 2.6406601e+00 1.7139238e+00 1.2013529e+00 1.7000137e+00 2.3442222e+00 1.7386523e+00 1.6019500e+00 9.1449234e-01 1.5517970e+00 1.7435092e+00 1.3757183e+00 1.2089191e+00 2.0167186e+00 1.8496945e+00 1.3938438e+00 1.1152390e+00 1.3189663e+00 1.5429659e+00 1.2037520e+00 6.7720957e-01 7.4262850e-01 7.0911112e-01 7.0633229e-01 1.0011648e+00 1.1020600e+00 7.2036951e-01 5.0477564e-01 1.1005460e+00 1.8106900e+00 9.0166431e-01 9.0192695e-01 9.0055475e-01 8.0055465e-01 2.1027376e+00 1.0003198e+00 1.0225570e+00 3.0474106e-01 1.1299441e+00 5.0517282e-01 7.5564478e-01 1.7513222e+00 1.1055799e+00 1.4140515e+00 7.8895472e-01 1.3181953e+00 5.7608844e-01 4.1315633e-01 8.1156529e-01 4.1317535e-01 8.0004523e-01 7.2044167e-01 5.2524663e-01 1.8787830e+00 1.9768256e+00 5.0001522e-01 9.4912864e-01 4.5148429e-01 1.8635775e+00 3.0915245e-01 7.8611860e-01 1.2373911e+00 3.0922892e-01 3.0922892e-01 5.7609230e-01 1.2089834e+00 1.4324608e+00 1.9471600e+00 6.3912943e-01 3.0017653e-01 5.0043842e-01 1.7149040e+00 8.6084272e-01 4.8391482e-01 3.4080442e-01 9.0668287e-01 8.6225026e-01 9.3424659e-01 3.0474106e-01 9.3861512e-01 9.5646231e-01 7.8935898e-01 3.4085233e-01 5.2491734e-01 7.8940551e-01 3.0482299e-01 6.0948506e-01 1.3000044e+00 9.3308891e-01 4.0243965e-01 5.6371422e-01 4.1212852e-01 7.0000303e-01 5.4219811e-01 1.2105001e+00 3.4342562e-01 3.6256305e-01 3.4085233e-01 8.0008964e-01 1.5005854e+00 4.1420960e-01 1.5358856e+00 6.1990228e-01 1.7849054e+00 1.1528477e+00 1.3788456e+00 2.4261894e+00 5.6370994e-01 2.0884901e+00 1.4655452e+00 1.9390732e+00 1.1074834e+00 1.0434746e+00 1.4340155e+00 6.0605366e-01 9.1554656e-01 1.0782857e+00 1.1897288e+00 2.5394068e+00 2.6516318e+00 8.3183606e-01 1.5694554e+00 5.2201750e-01 2.5386539e+00 9.0192695e-01 1.4157600e+00 1.8949500e+00 8.0097499e-01 7.0548138e-01 1.1935069e+00 1.8443040e+00 2.0853276e+00 2.5816887e+00 1.1989547e+00 9.1424659e-01 1.1138955e+00 2.3468430e+00 1.1963432e+00 1.1270327e+00 6.0365948e-01 1.5143051e+00 1.3938114e+00 1.5079206e+00 6.1990228e-01 1.5829749e+00 1.4450801e+00 1.3189240e+00 9.1132198e-01 1.1152300e+00 1.0159134e+00 6.3309012e-01 7.0096858e-01 1.1002025e+00 4.8852375e-01 9.1024401e-01 8.1112984e-01 4.0127250e-01 8.1117067e-01 1.3486924e+00 7.0633229e-01 4.6440171e-01 5.1257987e-01 5.0517282e-01 1.5260594e+00 6.1288055e-01 1.5131090e+00 7.4335736e-01 1.4549432e+00 1.1020600e+00 1.3036236e+00 2.1691920e+00 1.1527669e+00 1.8444686e+00 1.3309288e+00 1.6567564e+00 6.3925756e-01 8.5617086e-01 1.0458540e+00 9.0668287e-01 8.4540285e-01 8.5586571e-01 1.0039209e+00 2.2782572e+00 2.4540263e+00 1.2012866e+00 1.2440282e+00 6.2656178e-01 2.2782572e+00 7.0556260e-01 1.2101609e+00 1.5639802e+00 6.0219099e-01 4.5148429e-01 1.1079931e+00 1.4142064e+00 1.7087610e+00 2.1412345e+00 1.1115204e+00 6.7720957e-01 1.1281352e+00 1.8646736e+00 1.1282162e+00 1.0010209e+00 4.1315633e-01 1.0172673e+00 1.1401191e+00 9.4532171e-01 7.4335736e-01 1.4134218e+00 1.2440229e+00 8.4786353e-01 9.0557807e-01 7.2440846e-01 9.3308853e-01 6.0964891e-01 8.0296037e-01 1.1055799e+00 1.2124837e+00 1.2014191e+00 6.0000952e-01 9.3755356e-01 1.7874653e+00 1.1024913e+00 1.0032296e+00 1.0031018e+00 5.2201750e-01 1.8640262e+00 1.0088926e+00 1.3452347e+00 9.0417295e-01 1.2040344e+00 9.0168933e-01 1.1133986e+00 1.9046783e+00 1.8005318e+00 1.6009504e+00 1.1056691e+00 1.4335330e+00 5.2167829e-01 6.1990228e-01 8.2418141e-01 1.0118233e+00 1.0151880e+00 8.2458478e-01 8.0051115e-01 2.0076819e+00 2.2050331e+00 9.3329017e-01 1.0426638e+00 1.1020600e+00 2.0062587e+00 4.5847767e-01 1.0087393e+00 1.3009222e+00 5.0855778e-01 6.0202028e-01 9.1471442e-01 1.1019505e+00 1.4044980e+00 1.7386523e+00 9.3351278e-01 4.5783248e-01 9.1892454e-01 1.4407364e+00 1.0151880e+00 8.0093081e-01 7.0088627e-01 7.4269200e-01 1.0142482e+00 8.0250123e-01 9.0417295e-01 1.2189645e+00 1.1269510e+00 8.0879701e-01 6.1990228e-01 5.6371422e-01 8.6084272e-01 8.0291749e-01 7.8935813e-01 8.0250123e-01 8.0046685e-01 7.0017011e-01 5.2491734e-01 1.3741813e+00 7.0470720e-01 7.4269314e-01 6.7626502e-01 6.0000635e-01 1.4852616e+00 6.3309012e-01 1.6636721e+00 7.5826453e-01 1.5161847e+00 1.2049539e+00 1.4220925e+00 2.2191056e+00 1.4001717e+00 1.9083789e+00 1.4007861e+00 1.7945122e+00 9.6133119e-01 9.1552373e-01 1.1416778e+00 7.7603846e-01 1.1170561e+00 1.1351073e+00 1.1152390e+00 2.3540839e+00 2.5167781e+00 6.0202028e-01 1.3687074e+00 8.0713433e-01 2.3222107e+00 5.7608844e-01 1.3563898e+00 1.6193612e+00 5.7609230e-01 7.3090905e-01 1.2201578e+00 1.4221192e+00 1.7236083e+00 2.1360791e+00 1.2373857e+00 7.1629168e-01 1.2000731e+00 1.7962160e+00 1.3755348e+00 1.1298552e+00 7.2113820e-01 1.0843962e+00 1.3150470e+00 1.0664292e+00 7.5826453e-01 1.5362595e+00 1.4451976e+00 1.0604287e+00 6.7626502e-01 8.9540816e-01 1.2552585e+00 8.0073117e-01 5.0001522e-01 4.1212852e-01 5.6347549e-01 4.0127250e-01 8.7240114e-01 3.0008832e-01 1.2085435e-01 1.2085435e-01 6.0017982e-01 1.1038933e+00 2.0061436e-01 1.9231154e+00 1.0088926e+00 1.8971338e+00 1.5035330e+00 1.7143629e+00 2.6071033e+00 7.2440846e-01 2.2781292e+00 1.7231818e+00 2.0998984e+00 1.0923537e+00 1.2224463e+00 1.4922544e+00 9.3733589e-01 1.1896725e+00 1.2782589e+00 1.4186216e+00 2.7177641e+00 2.8857013e+00 9.6674360e-01 1.6882618e+00 8.5406616e-01 2.7167827e+00 8.6084272e-01 1.6345263e+00 2.0058565e+00 7.5508853e-01 8.1715593e-01 1.5132180e+00 1.8635428e+00 2.1554613e+00 2.5926811e+00 1.5194972e+00 1.0207533e+00 1.5005626e+00 2.3165875e+00 1.5429659e+00 1.4098467e+00 7.2036819e-01 1.4724918e+00 1.5757929e+00 1.3838027e+00 1.0088926e+00 1.8378491e+00 1.6745686e+00 1.2948699e+00 9.4912864e-01 1.1635325e+00 1.3473688e+00 1.0032293e+00 4.0004442e-01 6.9509552e-01 3.0017653e-01 7.1708289e-01 2.2573593e-01 5.0085236e-01 4.0243965e-01 7.0548138e-01 1.0008617e+00 3.0482299e-01 2.0206913e+00 1.1056785e+00 2.0075255e+00 1.6053217e+00 1.8156855e+00 2.7170183e+00 6.3912709e-01 2.3873965e+00 1.8286172e+00 2.2132187e+00 1.2079042e+00 1.3276450e+00 1.6018644e+00 1.0207396e+00 1.2397507e+00 1.3715471e+00 1.5245240e+00 2.8327619e+00 2.9941199e+00 1.0032443e+00 1.7962160e+00 9.3329055e-01 2.8269181e+00 9.6936870e-01 1.7435156e+00 2.1174156e+00 8.6084272e-01 9.2351241e-01 1.6144550e+00 1.9761215e+00 2.2674248e+00 2.7114616e+00 1.6190709e+00 1.1282247e+00 1.6009322e+00 2.4285500e+00 1.6432478e+00 1.5147271e+00 8.2512420e-01 1.5839544e+00 1.6753044e+00 1.4829434e+00 1.1056785e+00 1.9427965e+00 1.7734131e+00 1.3908238e+00 1.0498226e+00 1.2712749e+00 1.4523130e+00 1.1044111e+00 6.0980961e-01 4.1209001e-01 1.1020600e+00 2.0181667e-01 4.0243965e-01 3.0922892e-01 7.0088627e-01 1.4001688e+00 3.0922892e-01 1.6797901e+00 7.8935898e-01 1.7574606e+00 1.2224463e+00 1.4618181e+00 2.4274025e+00 6.3165225e-01 2.0887499e+00 1.4865883e+00 1.9561612e+00 1.0664292e+00 1.0336863e+00 1.3939836e+00 8.2421923e-01 1.2089895e+00 1.1997296e+00 1.1938630e+00 2.5462062e+00 2.6763758e+00 6.4049114e-01 1.5645185e+00 8.0883916e-01 2.5391583e+00 8.3183672e-01 1.4351453e+00 1.8644317e+00 7.4618926e-01 6.9987517e-01 1.2680580e+00 1.7844580e+00 2.0440071e+00 2.5329067e+00 1.2921474e+00 8.5440680e-01 1.2036924e+00 2.2838099e+00 1.3737025e+00 1.1587585e+00 6.4620889e-01 1.4491800e+00 1.4507713e+00 1.4583848e+00 7.8935898e-01 1.6277433e+00 1.5384791e+00 1.3150470e+00 8.7209348e-01 1.0788651e+00 1.2179890e+00 7.4954884e-01 6.1135434e-01 1.3790270e+00 5.2491734e-01 4.5147187e-01 4.5080200e-01 3.0026460e-01 1.6179159e+00 5.2167829e-01 1.4543196e+00 5.6838732e-01 1.3502290e+00 1.0008620e+00 1.2192919e+00 2.0611274e+00 1.2013529e+00 1.7369589e+00 1.2053003e+00 1.5767956e+00 6.3925756e-01 7.1779518e-01 9.6131279e-01 6.4620889e-01 1.0032443e+00 9.3331138e-01 9.0279223e-01 2.1713885e+00 2.3476282e+00 8.0245903e-01 1.1755517e+00 6.3322667e-01 2.1693131e+00 4.2362917e-01 1.1187430e+00 1.4544336e+00 4.0246123e-01 4.1209001e-01 1.0208844e+00 1.3018144e+00 1.5969056e+00 2.0303761e+00 1.0427944e+00 5.0085236e-01 1.0008465e+00 1.7574039e+00 1.1274284e+00 9.0166476e-01 4.0125062e-01 9.3437551e-01 1.1319099e+00 9.6935134e-01 5.6838732e-01 1.3309288e+00 1.2428507e+00 9.2745734e-01 5.7630313e-01 6.8160885e-01 9.6672602e-01 5.2167208e-01 8.5440680e-01 2.2608083e-01 4.0125062e-01 3.0490481e-01 4.2270142e-01 1.0207262e+00 2.0181667e-01 2.0282636e+00 1.1133895e+00 1.9386586e+00 1.6012719e+00 1.8114342e+00 2.6515677e+00 9.0999313e-01 2.3322945e+00 1.8060515e+00 2.1578375e+00 1.1447365e+00 1.3085752e+00 1.5359734e+00 1.0426516e+00 1.3018144e+00 1.3779960e+00 1.5044724e+00 2.7627283e+00 2.9432651e+00 1.0010209e+00 1.7447170e+00 9.6576136e-01 2.7579768e+00 9.1892454e-01 1.7159977e+00 2.0420308e+00 8.2635069e-01 9.1576742e-01 1.6105700e+00 1.8662195e+00 2.1696258e+00 2.5680120e+00 1.6184785e+00 1.1020600e+00 1.6000183e+00 2.2731185e+00 1.6529028e+00 1.5029725e+00 8.2635069e-01 1.4699000e+00 1.6570292e+00 1.3301857e+00 1.1133895e+00 1.9214944e+00 1.7646791e+00 1.3272142e+00 1.0235120e+00 1.2275665e+00 1.4621584e+00 1.1060939e+00 9.1576742e-01 9.6133119e-01 9.4532171e-01 1.2662318e+00 3.0490481e-01 8.6084272e-01 2.7230933e+00 1.8083405e+00 2.7192500e+00 2.3152780e+00 2.5278895e+00 3.4307167e+00 1.2089253e+00 3.1023107e+00 2.5446557e+00 2.9238544e+00 1.9071235e+00 2.0445123e+00 2.3113306e+00 1.7148932e+00 1.8686471e+00 2.0692591e+00 2.2408459e+00 3.5448121e+00 3.7102716e+00 1.7134856e+00 2.5076804e+00 1.6187837e+00 3.5398901e+00 1.6780493e+00 2.4594169e+00 2.8277283e+00 1.5698091e+00 1.6365650e+00 2.3270565e+00 2.6739856e+00 2.9710335e+00 3.3954286e+00 2.3304578e+00 1.8446316e+00 2.3054869e+00 3.1050823e+00 2.3405162e+00 2.2288004e+00 1.5327217e+00 2.2740189e+00 2.3840998e+00 2.1122339e+00 1.8083405e+00 2.6588338e+00 2.4791402e+00 2.0688078e+00 1.7632513e+00 1.9828965e+00 2.1428811e+00 1.8095574e+00 3.0017653e-01 2.0061436e-01 6.0017982e-01 1.2012991e+00 1.2085435e-01 1.8301371e+00 9.1424659e-01 1.8223693e+00 1.4049093e+00 1.6190709e+00 2.5271432e+00 7.0556260e-01 2.1953731e+00 1.6303102e+00 2.0261311e+00 1.0363096e+00 1.1330692e+00 1.4229011e+00 8.5406674e-01 1.1527746e+00 1.2106302e+00 1.3261864e+00 2.6410980e+00 2.8004332e+00 8.1117067e-01 1.6146557e+00 7.8886054e-01 2.6375763e+00 7.9824795e-01 1.5471213e+00 1.9317133e+00 6.9509552e-01 7.3155911e-01 1.4182194e+00 1.8031267e+00 2.0887452e+00 2.5422790e+00 1.4267527e+00 9.3308891e-01 1.4006149e+00 2.2706274e+00 1.4614246e+00 1.3141581e+00 6.4049114e-01 1.4230277e+00 1.5004249e+00 1.3669148e+00 9.1424659e-01 1.7490906e+00 1.5981930e+00 1.2553121e+00 8.7212232e-01 1.0924484e+00 1.2731059e+00 9.0557807e-01 1.1269424e-01 5.0002283e-01 1.2049541e+00 2.0121983e-01 1.8447840e+00 9.3329055e-01 1.7901165e+00 1.4035225e+00 1.6222582e+00 2.4980210e+00 8.1757693e-01 2.1693127e+00 1.6187837e+00 2.0049100e+00 1.0151397e+00 1.1261381e+00 1.3938113e+00 9.0657539e-01 1.2362756e+00 1.2472959e+00 1.3154932e+00 2.6088344e+00 2.7785257e+00 9.0207914e-01 1.5972548e+00 8.5406674e-01 2.6071034e+00 7.7652636e-01 1.5358856e+00 1.8953567e+00 6.9518117e-01 7.4612718e-01 1.4220925e+00 1.7511588e+00 2.0440071e+00 2.4804818e+00 1.4362913e+00 9.1449234e-01 1.4003402e+00 2.2077377e+00 1.4867147e+00 1.3085752e+00 6.7720780e-01 1.3734975e+00 1.5100598e+00 1.3269962e+00 9.3329055e-01 1.7441015e+00 1.6143613e+00 1.2552584e+00 8.7796615e-01 1.0782751e+00 1.3029195e+00 9.1424659e-01 5.0000761e-01 1.2040406e+00 1.1269424e-01 1.8289847e+00 9.1424701e-01 1.7872748e+00 1.4023776e+00 1.6144390e+00 2.4974488e+00 8.0533198e-01 2.1691714e+00 1.6179842e+00 1.9948417e+00 9.9013884e-01 1.1186586e+00 1.3842454e+00 8.5583357e-01 1.1527671e+00 1.1990152e+00 1.3139296e+00 2.6085083e+00 2.7775771e+00 8.5440680e-01 1.5835478e+00 7.8886139e-01 2.6068470e+00 7.5508853e-01 1.5300146e+00 1.8950932e+00 6.5712813e-01 7.2036951e-01 1.4134189e+00 1.7511120e+00 2.0435901e+00 2.4807480e+00 1.4220898e+00 9.1424701e-01 1.4002005e+00 2.2048860e+00 1.4562730e+00 1.3069754e+00 6.3309258e-01 1.3632211e+00 1.4815986e+00 1.2921474e+00 9.1424701e-01 1.7351894e+00 1.5836236e+00 1.2085436e+00 8.4725834e-01 1.0597879e+00 1.2653025e+00 9.0508756e-01 1.3743342e+00 5.0043084e-01 1.7369589e+00 8.2635069e-01 1.6144390e+00 1.3008770e+00 1.5131090e+00 2.3226028e+00 1.3004854e+00 2.0107294e+00 1.5010034e+00 1.8390199e+00 8.5471446e-01 1.0087539e+00 1.2223855e+00 8.0073117e-01 1.1286018e+00 1.1270411e+00 1.2013529e+00 2.4290388e+00 2.6198428e+00 7.8895472e-01 1.4363167e+00 7.7598796e-01 2.4266979e+00 6.3178782e-01 1.4100098e+00 1.7134977e+00 5.6347549e-01 6.3165225e-01 1.3130978e+00 1.5237265e+00 1.8289379e+00 2.1979419e+00 1.3253497e+00 8.0004602e-01 1.3000455e+00 1.9028805e+00 1.3748188e+00 1.2012991e+00 5.6371422e-01 1.1400420e+00 1.3748220e+00 1.0597992e+00 8.2635069e-01 1.6184929e+00 1.4858469e+00 1.0797702e+00 7.4612830e-01 9.3329055e-01 1.1910002e+00 8.0923926e-01 1.1056785e+00 3.0089448e+00 2.1019570e+00 2.9563162e+00 2.6052626e+00 2.8107271e+00 3.6717374e+00 1.5012719e+00 3.3522198e+00 2.8186527e+00 3.1596417e+00 2.1362161e+00 2.3151198e+00 2.5461024e+00 2.0036629e+00 2.1224665e+00 2.3234228e+00 2.5150159e+00 3.7801779e+00 3.9623034e+00 2.0033816e+00 2.7467405e+00 1.9044216e+00 3.7784933e+00 1.9231092e+00 2.7237438e+00 3.0623796e+00 1.8186729e+00 1.9089573e+00 2.6097166e+00 2.8846684e+00 3.1885494e+00 3.5706709e+00 2.6109888e+00 2.1139043e+00 2.6017555e+00 3.2706949e+00 2.6138780e+00 2.5099937e+00 1.8069857e+00 2.4748863e+00 2.6338874e+00 2.2385678e+00 2.1019570e+00 2.9251726e+00 2.7321685e+00 2.2661994e+00 2.0190735e+00 2.2288464e+00 2.4131370e+00 2.1020441e+00 1.9226845e+00 1.0087252e+00 1.8684939e+00 1.5017097e+00 1.7108631e+00 2.5816562e+00 8.0533198e-01 2.2563154e+00 1.7134977e+00 2.0770423e+00 1.0604287e+00 1.2124777e+00 1.4620239e+00 9.3329017e-01 1.1896594e+00 1.2705641e+00 1.4098496e+00 2.6923501e+00 2.8659663e+00 9.1449234e-01 1.6637458e+00 8.5403428e-01 2.6902417e+00 8.3183672e-01 1.6225614e+00 1.9757175e+00 7.3084048e-01 8.1117067e-01 1.5097082e+00 1.8197097e+00 2.1169795e+00 2.5408329e+00 1.5160570e+00 1.0087393e+00 1.5001233e+00 2.2573596e+00 1.5423651e+00 1.4049376e+00 7.1708289e-01 1.4229011e+00 1.5611429e+00 1.3142952e+00 1.0087252e+00 1.8271493e+00 1.6639408e+00 1.2552635e+00 9.2768675e-01 1.1400420e+00 1.3479052e+00 1.0031018e+00 9.3184922e-01 8.0291749e-01 7.0910969e-01 3.4342562e-01 1.3028005e+00 1.6474201e+00 1.0216374e+00 8.5586571e-01 9.0026543e-01 9.0508756e-01 7.7598796e-01 5.7832449e-01 1.0522594e+00 9.0999313e-01 7.0008735e-01 7.1708289e-01 1.4049376e+00 1.4220925e+00 1.2553121e+00 6.0202028e-01 1.1170561e+00 1.4055109e+00 1.1186497e+00 4.5783248e-01 9.3306769e-01 1.2101609e+00 1.1134939e+00 5.3914287e-01 1.0144117e+00 1.1074742e+00 1.6007365e+00 5.2491734e-01 1.0797700e+00 1.1139044e+00 1.4000349e+00 4.0004442e-01 7.1629303e-01 1.2090477e+00 6.8170466e-01 4.5148429e-01 9.1427000e-01 9.3184922e-01 5.0043842e-01 4.1209001e-01 8.0296037e-01 1.0498226e+00 8.0928056e-01 6.0018299e-01 9.3446811e-01 1.3131410e+00 5.6371422e-01 7.8985507e-01 1.8949500e+00 9.1427000e-01 1.5639785e+00 9.3308891e-01 1.4501583e+00 7.1621748e-01 6.0017665e-01 1.0010209e+00 2.0181667e-01 5.0000761e-01 6.3925756e-01 7.0548283e-01 2.0162299e+00 2.0885102e+00 5.2167829e-01 1.1079931e+00 2.2608083e-01 2.0057464e+00 5.0043084e-01 9.2747919e-01 1.4186217e+00 4.1212852e-01 3.4085233e-01 6.3178782e-01 1.4043632e+00 1.6175925e+00 2.1298991e+00 6.3309258e-01 5.2133179e-01 5.6595908e-01 1.9078843e+00 7.4418186e-01 6.1830764e-01 3.4085233e-01 1.1006468e+00 9.1132198e-01 1.1010711e+00 0.0000000e+00 1.0458540e+00 9.3984267e-01 9.0166476e-01 5.0043084e-01 7.0088627e-01 7.0993998e-01 3.0017653e-01 8.0093160e-01 6.0000635e-01 7.1621613e-01 2.2268632e+00 4.1317535e-01 5.2491734e-01 6.0964891e-01 8.2421923e-01 7.4335736e-01 4.1209001e-01 1.4186217e+00 1.3131410e+00 7.4275547e-01 6.1119267e-01 9.1566538e-01 1.0095513e+00 1.1796101e+00 2.5399984e-01 1.5237074e+00 8.2421923e-01 1.0429127e+00 4.1315633e-01 3.0490481e-01 1.1528553e+00 1.1270325e+00 7.0096708e-01 5.0001522e-01 3.1328089e-01 9.0657583e-01 7.0096858e-01 9.1568820e-01 1.0216374e+00 6.0035305e-01 8.0337471e-01 7.0548283e-01 1.2396937e+00 5.0043084e-01 4.2270142e-01 8.0008964e-01 1.3131410e+00 3.0915245e-01 4.5847767e-01 7.0470720e-01 9.6936870e-01 7.4262964e-01 9.0645118e-01 1.2190260e+00 4.0246123e-01 1.3450652e+00 1.4544312e+00 1.0207258e+00 4.5147187e-01 9.6501813e-01 5.0517282e-01 3.0490481e-01 5.0437695e-01 6.8170466e-01 6.5712813e-01 5.0855778e-01 2.0121983e-01 1.4695463e+00 1.5267750e+00 7.4395693e-01 6.3309258e-01 7.8890806e-01 1.4542932e+00 7.0008432e-01 4.5784410e-01 9.0166431e-01 8.0000160e-01 7.0008584e-01 3.0017653e-01 9.0005094e-01 1.1019505e+00 1.6144405e+00 4.0004442e-01 5.0436965e-01 4.1315633e-01 1.4012283e+00 6.3164729e-01 2.0121983e-01 8.0046685e-01 6.0219099e-01 6.0964597e-01 6.5724028e-01 5.6371422e-01 5.6838732e-01 7.0911112e-01 5.3914287e-01 6.0948506e-01 4.0246123e-01 5.6371422e-01 5.2133179e-01 1.1281267e+00 1.6745375e+00 8.1112984e-01 5.2167208e-01 7.4395693e-01 7.0016860e-01 5.0855778e-01 3.3813251e-01 9.0659977e-01 7.8895472e-01 5.0043842e-01 4.1209001e-01 1.2528048e+00 1.3020492e+00 9.3861512e-01 4.0127250e-01 1.0142766e+00 1.2362810e+00 9.0168933e-01 3.0490481e-01 7.0478886e-01 1.0010209e+00 9.0279223e-01 2.2608083e-01 7.4262850e-01 9.0055475e-01 1.4109657e+00 2.2573593e-01 7.8895472e-01 8.0492246e-01 1.2000668e+00 4.0363334e-01 4.1212852e-01 1.0039060e+00 4.5080200e-01 2.4195741e-01 7.0462844e-01 7.8985507e-01 3.0490481e-01 3.4085233e-01 6.0017982e-01 8.0928056e-01 6.0017665e-01 4.5784410e-01 7.4612718e-01 2.7992301e+00 3.6259865e-01 9.6953662e-01 6.4620889e-01 1.5401330e+00 1.4140789e+00 1.1281265e+00 2.0058560e+00 1.8949500e+00 1.4140515e+00 1.2396937e+00 8.0000239e-01 4.1317535e-01 1.8064092e+00 9.3310976e-01 2.1167364e+00 2.0181667e-01 1.7570696e+00 1.0143975e+00 6.1135434e-01 1.8661434e+00 1.8197089e+00 1.2632995e+00 8.1112909e-01 5.0126466e-01 8.0051115e-01 1.2632996e+00 1.5975200e+00 1.5266891e+00 5.0043084e-01 1.3452695e+00 1.3018553e+00 1.9314575e+00 1.2089192e+00 1.0777307e+00 1.5030978e+00 1.8949500e+00 8.5409862e-01 1.0151880e+00 1.4180463e+00 1.6742781e+00 1.4542907e+00 1.4853863e+00 1.8197089e+00 2.4725511e+00 1.8443040e+00 2.3518103e+00 1.6032169e+00 1.5066818e+00 1.9080163e+00 8.0923851e-01 9.4532171e-01 1.5109394e+00 1.6179000e+00 2.9132779e+00 2.9705619e+00 1.1020600e+00 2.0185049e+00 7.0633229e-01 2.9085831e+00 1.4001717e+00 1.8310931e+00 2.3325127e+00 1.3000908e+00 1.2016381e+00 1.5402579e+00 2.3143334e+00 2.5314248e+00 3.0393618e+00 1.5405402e+00 1.4018011e+00 1.3018553e+00 2.8185850e+00 1.4728279e+00 1.5248833e+00 1.1020506e+00 2.0036931e+00 1.8191683e+00 2.0009584e+00 9.1427000e-01 1.9534067e+00 1.8336293e+00 1.8020058e+00 1.4006178e+00 1.6026130e+00 1.3506710e+00 1.0116724e+00 6.3912709e-01 7.8890806e-01 1.2190319e+00 1.0776296e+00 8.0923926e-01 1.6740888e+00 1.5650163e+00 1.0798806e+00 9.0155438e-01 9.0417295e-01 6.4049114e-01 1.4685147e+00 6.4049114e-01 1.7843537e+00 4.5148429e-01 1.4324350e+00 6.8261201e-01 3.3813251e-01 1.5401311e+00 1.4852570e+00 9.3329055e-01 5.0043842e-01 2.0181667e-01 9.1424701e-01 9.3424697e-01 1.2633467e+00 1.2093243e+00 5.2167829e-01 1.0313359e+00 9.6574336e-01 1.5965767e+00 9.0168933e-01 7.7603846e-01 1.2016443e+00 1.5639785e+00 5.7832449e-01 7.7882758e-01 1.1074742e+00 1.3452311e+00 1.1281352e+00 1.1558746e+00 1.4852570e+00 1.1153247e+00 7.8895472e-01 5.0477564e-01 5.0855778e-01 1.0426636e+00 9.4532171e-01 7.3155911e-01 5.0476836e-01 1.3669148e+00 1.1910003e+00 8.5471446e-01 7.1629303e-01 1.1528553e+00 1.0777411e+00 9.0142636e-01 8.0046685e-01 7.1629168e-01 1.0032293e+00 9.1892413e-01 3.6452132e-01 5.6370994e-01 7.0176271e-01 1.4157327e+00 4.2362917e-01 7.0633229e-01 6.0964891e-01 1.0062544e+00 9.1554656e-01 6.0365948e-01 1.0235118e+00 6.1135434e-01 6.7626502e-01 7.5508853e-01 9.3308891e-01 7.1621884e-01 8.5403428e-01 6.5712608e-01 8.0245824e-01 6.3192325e-01 9.1132198e-01 8.6051414e-01 1.0242692e+00 1.0232576e+00 6.8685125e-01 1.5761415e+00 1.4407364e+00 9.0296858e-01 8.3689956e-01 6.3322667e-01 1.0451812e+00 1.5490134e+00 4.5847767e-01 1.6529028e+00 8.3916809e-01 1.2749306e+00 5.4219811e-01 7.0462697e-01 1.3611955e+00 1.3103292e+00 9.0792879e-01 9.1446896e-01 8.2421853e-01 7.1708289e-01 9.0683128e-01 1.1954899e+00 1.2957636e+00 6.3178534e-01 9.0508756e-01 8.7796615e-01 1.4198077e+00 7.2113820e-01 6.0427481e-01 1.0032443e+00 1.4501583e+00 4.5216167e-01 5.2491131e-01 9.1894698e-01 1.2738390e+00 9.4912864e-01 1.0207533e+00 1.3523292e+00 5.0043842e-01 4.1317535e-01 8.5403428e-01 7.0910969e-01 3.0482299e-01 4.0243965e-01 1.6491696e+00 1.8289467e+00 1.0060994e+00 6.1119267e-01 9.0142636e-01 1.6484371e+00 5.0126466e-01 6.0018299e-01 9.3308853e-01 4.2362917e-01 4.0363334e-01 5.2133802e-01 7.9153339e-01 1.0782107e+00 1.5275160e+00 5.2167829e-01 5.2167208e-01 6.9987517e-01 1.2633516e+00 5.2201750e-01 4.0127250e-01 5.0517282e-01 4.1212852e-01 5.2167829e-01 4.1210927e-01 7.1621748e-01 8.0093081e-01 6.3178782e-01 3.0922892e-01 7.0008735e-01 2.0061436e-01 3.6452132e-01 6.0035305e-01 4.1420960e-01 7.0096858e-01 6.3178782e-01 5.2132556e-01 3.0490481e-01 1.5634961e+00 1.6740875e+00 5.4219811e-01 5.8750389e-01 8.0245903e-01 1.5263478e+00 4.0004442e-01 6.1135434e-01 8.6051471e-01 5.0043842e-01 4.2270142e-01 3.0482299e-01 8.0967961e-01 1.0426513e+00 1.5757929e+00 3.3813251e-01 4.0127250e-01 5.0855778e-01 1.3133662e+00 7.1700909e-01 4.0125062e-01 5.2491734e-01 5.2167829e-01 5.2838320e-01 5.3943256e-01 6.0017665e-01 6.4620889e-01 6.8261201e-01 4.2270142e-01 3.0482299e-01 3.0026460e-01 7.0470867e-01 5.0477564e-01 1.1038840e+00 1.0010209e+00 4.0363334e-01 3.3808272e-01 1.2528049e+00 1.4182049e+00 9.2033101e-01 2.4195741e-01 1.2036925e+00 1.2362756e+00 6.3451734e-01 3.0482299e-01 5.2524663e-01 7.4335736e-01 7.4329414e-01 4.0125062e-01 5.2491131e-01 6.7636452e-01 1.1755449e+00 4.0127250e-01 6.3925756e-01 7.9148746e-01 9.1424659e-01 5.2491734e-01 4.1210927e-01 8.5437440e-01 1.2085435e-01 3.0026460e-01 4.0127250e-01 1.0010209e+00 4.0243965e-01 4.1317535e-01 3.0482299e-01 6.0444249e-01 3.3813251e-01 6.0964891e-01 9.0166431e-01 4.1212852e-01 7.8985507e-01 8.1719606e-01 2.1378157e+00 2.2009978e+00 5.0855077e-01 1.2175952e+00 3.0017653e-01 2.1167404e+00 6.0035621e-01 1.0597879e+00 1.5265797e+00 5.0517282e-01 5.2167829e-01 7.4329527e-01 1.5072282e+00 1.7227391e+00 2.2434054e+00 7.4335736e-01 6.3309258e-01 6.8161057e-01 2.0107350e+00 9.2867113e-01 7.5508853e-01 5.0517282e-01 1.2040344e+00 1.0178820e+00 1.2037520e+00 2.0181667e-01 1.1636098e+00 1.0621172e+00 1.0032443e+00 6.0000317e-01 8.0883841e-01 9.0668287e-01 5.0085236e-01 6.0964891e-01 7.4618926e-01 2.0118073e+00 2.0884859e+00 9.1424701e-01 1.1060939e+00 4.0243965e-01 2.0057764e+00 6.3178782e-01 9.1916394e-01 1.4198627e+00 6.1119267e-01 6.0219099e-01 6.3309012e-01 1.4134218e+00 1.6179000e+00 2.1265315e+00 6.3178534e-01 9.0506254e-01 1.0032443e+00 1.9078396e+00 6.5712608e-01 6.8261201e-01 6.0219099e-01 1.1003034e+00 9.0532049e-01 1.1001014e+00 5.0000761e-01 1.0433444e+00 9.1892454e-01 9.0002615e-01 5.6595908e-01 7.0470867e-01 6.1119558e-01 6.0017982e-01 5.0085236e-01 1.5275160e+00 1.6747664e+00 1.0434746e+00 5.2132556e-01 8.0533198e-01 1.5264802e+00 5.7609230e-01 4.1317535e-01 8.6051414e-01 5.7630313e-01 5.2524663e-01 4.1315633e-01 8.6054545e-01 1.0440350e+00 1.5412701e+00 4.1210927e-01 8.0250202e-01 9.1471442e-01 1.3130978e+00 3.0490481e-01 5.0043084e-01 5.7630313e-01 5.0043842e-01 3.3818226e-01 5.0043084e-01 6.3925756e-01 6.0948212e-01 4.1317535e-01 3.0482299e-01 7.0548283e-01 3.0490481e-01 2.2573593e-01 5.6394820e-01 1.3634093e+00 1.4858469e+00 8.1757693e-01 5.2201750e-01 9.1427000e-01 1.3523380e+00 6.0201716e-01 3.4342562e-01 7.1629168e-01 7.0096708e-01 6.0948212e-01 3.0490481e-01 7.0096708e-01 9.1424701e-01 1.4267554e+00 4.0127250e-01 4.1420960e-01 4.8342635e-01 1.2049539e+00 6.0964891e-01 1.1269424e-01 7.1621613e-01 4.1212852e-01 6.0018299e-01 5.3914287e-01 7.0548283e-01 5.2524663e-01 7.0105084e-01 5.0476836e-01 5.6371422e-01 3.0474106e-01 5.2491734e-01 6.0948212e-01 1.2000065e+00 2.0187441e+00 1.0498228e+00 2.2315584e+00 1.0000152e+00 1.8808952e+00 1.1286798e+00 7.5826453e-01 1.9821126e+00 1.9334872e+00 1.4094023e+00 9.7944085e-01 1.0090312e+00 3.0915245e-01 1.4094022e+00 1.7226330e+00 1.6785116e+00 8.2418071e-01 1.4544336e+00 1.4183053e+00 2.0448570e+00 1.3189240e+00 1.1989547e+00 1.6071563e+00 2.0162299e+00 9.7599312e-01 1.1287691e+00 1.5299044e+00 1.8304280e+00 1.5694554e+00 1.5966664e+00 1.9334872e+00 2.0448570e+00 1.2223853e+00 2.3135239e+00 3.0915245e-01 2.0415522e+00 1.2703001e+00 9.2351241e-01 2.1487275e+00 2.0854181e+00 1.4650300e+00 1.1157320e+00 8.0296037e-01 1.2013591e+00 1.4650276e+00 1.8684939e+00 1.6818191e+00 8.0245746e-01 1.5324961e+00 1.5271597e+00 2.1953922e+00 1.5071120e+00 1.3457716e+00 1.8029854e+00 2.0885102e+00 1.0837575e+00 1.2703001e+00 1.7133162e+00 1.9522053e+00 1.7369702e+00 1.6941963e+00 2.0286286e+00 1.1213597e+00 6.3912943e-01 1.9163315e+00 5.0855778e-01 1.1310337e+00 1.3142952e+00 6.0219099e-01 8.0046764e-01 7.2904264e-01 1.2366099e+00 1.4559030e+00 2.0467625e+00 7.7882758e-01 6.0184622e-01 6.0948800e-01 1.7296063e+00 1.2395260e+00 9.0668287e-01 8.0051036e-01 1.0231745e+00 1.0411548e+00 1.0543951e+00 5.2167829e-01 1.1356187e+00 1.2079042e+00 9.3439622e-01 4.2268438e-01 8.1719606e-01 1.2192978e+00 8.0046764e-01 1.3133662e+00 1.0434746e+00 8.3916809e-01 2.2573593e-01 5.0855077e-01 9.3848935e-01 9.0659977e-01 5.2167829e-01 7.0096858e-01 5.5450500e-01 1.0287902e+00 5.2133802e-01 8.4725834e-01 9.7599312e-01 8.0250123e-01 6.0018299e-01 5.6371422e-01 1.0171340e+00 3.0482299e-01 2.0181667e-01 6.0000317e-01 1.1079931e+00 2.0061436e-01 2.2573593e-01 5.0084481e-01 8.1683095e-01 5.2524663e-01 7.0096708e-01 1.0116865e+00 2.2278855e+00 7.0008584e-01 1.1298552e+00 1.6300950e+00 6.0017982e-01 5.0084481e-01 8.5403428e-01 1.6097507e+00 1.8284464e+00 2.3350903e+00 8.5406616e-01 7.1629168e-01 7.5508853e-01 2.1138813e+00 8.1683095e-01 8.2462252e-01 4.0246123e-01 1.3009222e+00 1.1139906e+00 1.3000951e+00 2.2608083e-01 1.2636227e+00 1.1315710e+00 1.1002119e+00 7.0088627e-01 9.0029018e-01 6.9600743e-01 3.1328089e-01 1.8661354e+00 1.1286798e+00 7.2044167e-01 1.9755679e+00 1.9314520e+00 1.3741466e+00 9.0645118e-01 6.0184622e-01 1.0001751e+00 1.3741498e+00 1.7083042e+00 1.6308665e+00 6.0201716e-01 1.4559030e+00 1.4140789e+00 2.0433026e+00 1.3131370e+00 1.1900969e+00 1.6049479e+00 2.0057464e+00 9.6691372e-01 1.1304042e+00 1.5237285e+00 1.7843627e+00 1.5639785e+00 1.5975352e+00 1.9314520e+00 8.2671175e-01 1.1543257e+00 1.2085435e-01 3.0474106e-01 7.0088627e-01 1.0144117e+00 1.3018103e+00 1.7709153e+00 7.0462844e-01 3.0482299e-01 7.0470867e-01 1.4857440e+00 8.1457587e-01 6.0948506e-01 3.3813251e-01 6.4049114e-01 7.4954884e-01 6.3925756e-01 5.0043084e-01 1.0090834e+00 8.7372177e-01 5.2838320e-01 2.0121983e-01 3.4342562e-01 7.3084171e-01 4.1315633e-01 5.0855778e-01 9.1024401e-01 8.2498722e-01 5.0436965e-01 5.6595908e-01 7.2044167e-01 1.2101609e+00 5.0437695e-01 6.9987517e-01 8.1457660e-01 1.0010209e+00 4.1212852e-01 3.4342562e-01 9.3351278e-01 3.0915245e-01 3.0482299e-01 6.0052920e-01 9.2747919e-01 2.2608083e-01 4.0000000e-01 5.0476836e-01 8.5586571e-01 5.0477564e-01 5.0477564e-01 8.2498722e-01 1.2635707e+00 1.2396421e+00 8.0533198e-01 2.4170870e-01 4.0127250e-01 7.4618926e-01 8.0726668e-01 1.0151880e+00 1.1066159e+00 5.6371422e-01 9.1554656e-01 8.0879701e-01 1.3523345e+00 6.0366256e-01 6.3912943e-01 9.0532093e-01 1.4186217e+00 5.2133179e-01 7.1700909e-01 8.1719606e-01 1.0923439e+00 8.5409862e-01 1.0116865e+00 1.3253496e+00 2.0121983e-01 8.0051036e-01 1.1269596e+00 1.4140457e+00 1.8721285e+00 8.0250123e-01 3.3813251e-01 8.0250202e-01 1.5969056e+00 8.4536936e-01 7.0096708e-01 2.2538848e-01 7.4395693e-01 8.3222261e-01 7.1779518e-01 4.1212852e-01 1.1079931e+00 9.4151244e-01 5.7630313e-01 3.0490481e-01 4.1420960e-01 6.9509395e-01 3.4080442e-01 7.0184453e-01 1.1527745e+00 1.4140486e+00 1.8971345e+00 7.0556260e-01 3.1328089e-01 7.0910969e-01 1.6486410e+00 7.4618926e-01 6.0184622e-01 1.1269424e-01 8.0923926e-01 7.7598796e-01 8.0883916e-01 3.4085233e-01 1.0235254e+00 8.7240114e-01 6.3309012e-01 5.0043842e-01 4.1315633e-01 5.7609230e-01 2.2538848e-01 8.0888055e-01 1.0030868e+00 1.5299044e+00 1.0000000e-01 6.3164977e-01 7.0096708e-01 1.3008855e+00 6.0184622e-01 3.3813251e-01 8.0296037e-01 5.0476836e-01 3.6256305e-01 5.6618864e-01 6.3178782e-01 4.5847767e-01 5.2491734e-01 4.1420960e-01 6.0202028e-01 4.0127250e-01 6.0052920e-01 5.6618864e-01 3.4342562e-01 8.7372177e-01 8.2425704e-01 9.3308891e-01 1.1005554e+00 7.1700774e-01 9.6674360e-01 8.0051115e-01 1.2632995e+00 5.2491734e-01 8.0883916e-01 7.8935898e-01 1.4043632e+00 7.0470867e-01 9.0532093e-01 7.5502989e-01 9.6953662e-01 7.4612718e-01 1.0222576e+00 1.3061180e+00 1.0032296e+00 1.0032293e+00 1.1900276e+00 1.3017553e+00 4.1315633e-01 1.1093621e+00 1.0088783e+00 1.5263498e+00 7.1708289e-01 7.3155911e-01 1.0040629e+00 1.6175925e+00 6.1845783e-01 7.5826453e-01 9.3426769e-01 1.2396937e+00 1.0142626e+00 1.2128138e+00 1.5237074e+00 1.5299064e+00 1.6885111e+00 1.8315348e+00 8.0097499e-01 1.6050900e+00 1.5160591e+00 2.0074169e+00 1.1389082e+00 1.2275665e+00 1.3502668e+00 2.1298991e+00 1.1075720e+00 1.2113964e+00 1.3632538e+00 1.7639471e+00 1.4922544e+00 1.7133283e+00 2.0290146e+00 7.1621748e-01 8.0051036e-01 1.3008813e+00 6.0017982e-01 4.1210927e-01 8.0492246e-01 5.0477564e-01 3.4080442e-01 5.6595908e-01 6.3309258e-01 4.5784410e-01 5.0855778e-01 4.1317535e-01 6.0366256e-01 4.0246123e-01 6.0035621e-01 5.7630313e-01 5.0085236e-01 1.4407390e+00 9.1892413e-01 4.2270142e-01 3.6452132e-01 6.7824250e-01 9.0668287e-01 8.2458409e-01 5.2133179e-01 9.0792879e-01 1.0124729e+00 8.0250202e-01 4.1210927e-01 5.0085236e-01 8.2458478e-01 4.1315633e-01 1.6100639e+00 1.0426636e+00 5.2491734e-01 8.0488008e-01 8.6054545e-01 1.0116721e+00 9.7291273e-01 5.6595908e-01 9.4532171e-01 1.1186499e+00 9.1681464e-01 6.3178782e-01 6.2656178e-01 9.6574369e-01 5.3943256e-01 1.4007831e+00 1.3033860e+00 1.7572550e+00 8.5406674e-01 1.0030724e+00 1.0426513e+00 1.9078843e+00 9.0005048e-01 1.0010209e+00 1.0776188e+00 1.4549432e+00 1.2363278e+00 1.5032156e+00 1.8103044e+00 6.0184934e-01 8.2671175e-01 6.0383105e-01 4.1209001e-01 6.3309258e-01 7.4418186e-01 5.0477564e-01 4.0006662e-01 4.8342635e-01 9.1892413e-01 4.8391482e-01 2.0121983e-01 6.4620889e-01 7.0462697e-01 5.0436965e-01 6.0184622e-01 5.7608844e-01 6.1830764e-01 5.3914287e-01 7.0105084e-01 5.0855778e-01 6.3165225e-01 3.0490481e-01 5.0477564e-01 5.2133179e-01 9.1446938e-01 8.7209348e-01 9.0532093e-01 3.4085233e-01 1.1298636e+00 9.6150595e-01 7.2036819e-01 5.0477564e-01 5.2167208e-01 6.3925756e-01 3.0008832e-01 3.0915245e-01 3.0474106e-01 1.1006468e+00 5.0043842e-01 4.1420960e-01 2.4195741e-01 6.8170466e-01 4.0127250e-01 7.0096708e-01 1.0003198e+00 5.0043084e-01 9.1132198e-01 3.0026460e-01 2.0121983e-01 4.0004442e-01 6.9987517e-01 4.5148429e-01 5.0477564e-01 8.3183672e-01 1.1010711e+00 8.0000160e-01 6.0052920e-01 2.0121983e-01 6.8161057e-01 4.1212852e-01 7.0176121e-01 1.0030721e+00 1.0458540e+00 9.3984267e-01 9.0166476e-01 5.0043084e-01 7.0088627e-01 7.0993998e-01 3.0017653e-01 2.2608083e-01 7.0008584e-01 9.3848935e-01 7.0184453e-01 6.3178534e-01 9.6936870e-01 5.0476836e-01 8.7372177e-01 5.6618864e-01 5.0477564e-01 8.7240114e-01 5.3943256e-01 3.0474106e-01 5.2167208e-01 8.0879701e-01 5.0085236e-01 9.0279268e-01 5.2133802e-01 4.2362917e-01 6.0017982e-01 5.2838320e-01 diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-seuclidean-ml-iris.txt b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-seuclidean-ml-iris.txt new file mode 100644 index 0000000000000000000000000000000000000000..3e2759df30c14c1503818cc6a400a370e8fd8e89 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-seuclidean-ml-iris.txt @@ -0,0 +1 @@ + 1.1781739e+00 8.4573383e-01 1.1040164e+00 2.6033464e-01 1.0391769e+00 6.5951091e-01 2.6643250e-01 1.6215602e+00 9.6424206e-01 5.8926015e-01 4.4417668e-01 1.2158053e+00 1.5196051e+00 1.4342986e+00 2.2147971e+00 1.0267382e+00 1.3103399e-01 1.0246015e+00 7.0646671e-01 4.6190224e-01 5.3352899e-01 6.8496652e-01 6.2944414e-01 5.1453676e-01 1.1649855e+00 3.8639663e-01 1.3340137e-01 2.6033464e-01 8.5141186e-01 9.9757114e-01 5.0629646e-01 1.3963590e+00 1.6851313e+00 9.6424206e-01 7.1143905e-01 4.8636669e-01 9.6424206e-01 1.4309353e+00 2.3749211e-01 1.8699153e-01 2.8644037e+00 1.0938603e+00 5.4968254e-01 7.9227302e-01 1.2158053e+00 7.0111465e-01 9.1831777e-01 5.2374483e-01 4.7680727e-01 3.4225655e+00 2.9886388e+00 3.5231786e+00 3.4844976e+00 3.4140450e+00 2.8802393e+00 3.0292175e+00 2.9585178e+00 3.2500773e+00 2.8104851e+00 3.8076036e+00 2.7718515e+00 3.6661618e+00 3.0567513e+00 2.4313960e+00 3.1540282e+00 2.7718124e+00 2.7494216e+00 4.0917474e+00 3.0136333e+00 3.0855821e+00 2.8833448e+00 3.7756717e+00 3.0462644e+00 3.0262994e+00 3.1582448e+00 3.6064873e+00 3.6179250e+00 3.0140884e+00 2.7108793e+00 3.1480683e+00 3.0769261e+00 2.8006021e+00 3.5140011e+00 2.7293962e+00 2.7724816e+00 3.3142477e+00 3.8377035e+00 2.4725633e+00 3.1307104e+00 3.0248446e+00 2.9240118e+00 2.9852014e+00 3.1515797e+00 2.8921724e+00 2.4678110e+00 2.6524994e+00 2.9083443e+00 2.7444686e+00 2.7478281e+00 4.2652803e+00 3.6712837e+00 4.4571526e+00 3.7518856e+00 4.1563042e+00 5.0327536e+00 3.5110505e+00 4.5914343e+00 4.4347154e+00 4.7605836e+00 3.6465897e+00 3.9644198e+00 4.1403419e+00 3.9458900e+00 4.0035735e+00 3.9244083e+00 3.7394250e+00 5.1213512e+00 5.6085412e+00 4.1515197e+00 4.3260896e+00 3.5311267e+00 5.2010517e+00 3.7194928e+00 4.0104626e+00 4.2547108e+00 3.5326627e+00 3.3344440e+00 4.1152831e+00 4.1647603e+00 4.7306329e+00 5.0503286e+00 4.1958529e+00 3.4649010e+00 3.7290073e+00 5.0848756e+00 4.0161825e+00 3.6208873e+00 3.2588014e+00 4.1126592e+00 4.3082433e+00 4.1887411e+00 3.6712837e+00 4.3324309e+00 4.3552675e+00 4.1561376e+00 4.0674499e+00 3.7933592e+00 3.8117183e+00 3.3250640e+00 5.2374483e-01 4.3319335e-01 1.3890415e+00 2.1841707e+00 9.9973471e-01 9.3211669e-01 6.4636269e-01 2.7124234e-01 1.7245677e+00 9.3727156e-01 1.7819563e-01 7.5570839e-01 2.5520912e+00 3.3809114e+00 2.1782802e+00 1.1854382e+00 2.0937104e+00 1.8662528e+00 1.1155937e+00 1.6542533e+00 1.4482752e+00 8.4881492e-01 9.7259094e-01 1.6562722e-01 9.7322023e-01 1.2100516e+00 9.9111027e-01 5.3286499e-01 2.8394141e-01 1.1346946e+00 2.5666454e+00 2.8608436e+00 2.7124234e-01 4.9009568e-01 1.3630799e+00 2.7124234e-01 6.0647055e-01 9.5529726e-01 1.1682143e+00 1.6911681e+00 7.6195008e-01 1.2774622e+00 1.9003949e+00 1.7819563e-01 1.8642334e+00 5.8652824e-01 1.6860841e+00 7.0235100e-01 3.5517185e+00 3.0793995e+00 3.5669733e+00 2.7166736e+00 3.1838913e+00 2.5120824e+00 3.1938169e+00 2.0428688e+00 3.1039816e+00 2.2561090e+00 2.8016158e+00 2.6226738e+00 2.9050141e+00 2.8502197e+00 2.0976260e+00 3.1846091e+00 2.5890531e+00 2.2584354e+00 3.4434621e+00 2.3329638e+00 3.1272801e+00 2.5616006e+00 3.3203580e+00 2.7436923e+00 2.8484236e+00 3.0948526e+00 3.4151452e+00 3.5708989e+00 2.7939968e+00 2.0736054e+00 2.3834491e+00 2.2886612e+00 2.3204706e+00 3.1632397e+00 2.5205525e+00 3.0112889e+00 3.3433635e+00 3.2300528e+00 2.2657938e+00 2.4705763e+00 2.4462190e+00 2.8038852e+00 2.4332562e+00 2.2089299e+00 2.4060762e+00 2.2734727e+00 2.3627180e+00 2.7012637e+00 1.8976741e+00 2.3590971e+00 4.3837112e+00 3.3195686e+00 4.4453895e+00 3.6018522e+00 4.1012355e+00 5.0512932e+00 2.8774753e+00 4.5344585e+00 4.0827835e+00 5.0801762e+00 3.7291677e+00 3.6888814e+00 4.1064223e+00 3.4625304e+00 3.7552252e+00 3.9939605e+00 3.6781201e+00 5.5433523e+00 5.4381439e+00 3.4976392e+00 4.4223824e+00 3.2288234e+00 5.1217596e+00 3.4157742e+00 4.1643077e+00 4.3726405e+00 3.2842292e+00 3.2296198e+00 3.9190152e+00 4.1591876e+00 4.6244312e+00 5.4884429e+00 4.0035368e+00 3.2202996e+00 3.3301365e+00 5.1089381e+00 4.2054648e+00 3.6234874e+00 3.1421933e+00 4.1502382e+00 4.3306814e+00 4.2256435e+00 3.3195686e+00 4.4219948e+00 4.4973329e+00 4.1152664e+00 3.6487297e+00 3.7329401e+00 4.0033827e+00 3.2017663e+00 2.8394141e-01 9.9272943e-01 1.8549949e+00 4.9772204e-01 5.9738093e-01 7.8305765e-01 3.7622328e-01 1.4342986e+00 5.0621589e-01 4.9772204e-01 6.9001472e-01 2.2742100e+00 3.0330374e+00 1.8410898e+00 8.5582452e-01 1.8552074e+00 1.4758765e+00 9.8932340e-01 1.2824303e+00 9.4580058e-01 7.0175090e-01 5.8564715e-01 6.1067563e-01 6.6453319e-01 9.2528705e-01 7.6195008e-01 1.7002750e-01 3.1093967e-01 1.0044375e+00 2.1686473e+00 2.5011215e+00 3.7622328e-01 3.6669623e-01 1.1883075e+00 3.7622328e-01 5.8652824e-01 6.7745878e-01 7.9191984e-01 2.0937821e+00 3.6228991e-01 9.5582164e-01 1.5272557e+00 4.9772204e-01 1.4755007e+00 1.3340137e-01 1.3666101e+00 4.3319335e-01 3.7283414e+00 3.2257816e+00 3.7651559e+00 3.1082143e+00 3.4606263e+00 2.7705999e+00 3.2962379e+00 2.4179023e+00 3.3643791e+00 2.5175848e+00 3.2317517e+00 2.8135312e+00 3.3502574e+00 3.0859108e+00 2.3316915e+00 3.3832002e+00 2.7540885e+00 2.5906747e+00 3.8459512e+00 2.7110494e+00 3.2296198e+00 2.8510843e+00 3.6612067e+00 3.0231940e+00 3.1083626e+00 3.3221751e+00 3.6999785e+00 3.7824508e+00 3.0223050e+00 2.4549511e+00 2.7813487e+00 2.6993734e+00 2.6424988e+00 3.4348309e+00 2.6680185e+00 3.0548262e+00 3.5357687e+00 3.6340473e+00 2.4474337e+00 2.8211532e+00 2.7662396e+00 3.0069386e+00 2.7817508e+00 2.6121651e+00 2.7000040e+00 2.4677010e+00 2.5915377e+00 2.9544131e+00 2.2712863e+00 2.6277952e+00 4.4682388e+00 3.5629824e+00 4.6484688e+00 3.8140427e+00 4.2790737e+00 5.2629823e+00 3.1332305e+00 4.7710813e+00 4.3977196e+00 5.1429155e+00 3.8634878e+00 3.9555042e+00 4.3021827e+00 3.7450218e+00 3.9451569e+00 4.1141318e+00 3.8729361e+00 5.5923916e+00 5.7171219e+00 3.8836634e+00 4.5660923e+00 3.4290419e+00 5.3764415e+00 3.6907517e+00 4.2782894e+00 4.5393827e+00 3.5302656e+00 3.4102235e+00 4.1476932e+00 4.3814983e+00 4.8831870e+00 5.5467550e+00 4.2276454e+00 3.4820326e+00 3.6311162e+00 5.3207972e+00 4.2656428e+00 3.7854499e+00 3.3178004e+00 4.3254709e+00 4.4873379e+00 4.3956810e+00 3.5629824e+00 4.5607328e+00 4.6030755e+00 4.3016138e+00 3.9622355e+00 3.9225802e+00 4.0577906e+00 3.3684813e+00 1.2515236e+00 2.1021589e+00 7.0646671e-01 8.4383266e-01 5.2374483e-01 3.8525820e-01 1.6876653e+00 7.3502408e-01 3.6319073e-01 5.0299964e-01 2.5372015e+00 3.2897546e+00 2.1021589e+00 1.1117653e+00 2.0978519e+00 1.7286097e+00 1.1937015e+00 1.5323598e+00 1.1874605e+00 8.6297946e-01 7.6710016e-01 5.3827772e-01 8.8540687e-01 1.1730565e+00 1.0034646e+00 2.6643250e-01 2.4808718e-01 1.2168625e+00 2.4209958e+00 2.7605308e+00 3.8525820e-01 5.6164055e-01 1.4300979e+00 3.8525820e-01 3.5266705e-01 9.1831777e-01 1.0556536e+00 1.8570904e+00 3.5266705e-01 1.1671832e+00 1.7581227e+00 3.6319073e-01 1.7245677e+00 2.3749211e-01 1.6215602e+00 6.7030885e-01 3.7702988e+00 3.2513048e+00 3.7854692e+00 2.9445918e+00 3.4252075e+00 2.6854877e+00 3.3289662e+00 2.2084365e+00 3.3482393e+00 2.3872006e+00 3.0088381e+00 2.7858967e+00 3.2051999e+00 3.0423378e+00 2.2727200e+00 3.4066596e+00 2.7026316e+00 2.4942728e+00 3.7194777e+00 2.5718070e+00 3.2266664e+00 2.8009311e+00 3.5699473e+00 2.9607930e+00 3.0876835e+00 3.3257458e+00 3.6752903e+00 3.7792524e+00 2.9772187e+00 2.3405405e+00 2.6225184e+00 2.5379456e+00 2.5530959e+00 3.3522700e+00 2.6036900e+00 3.0973166e+00 3.5528019e+00 3.5210610e+00 2.4001125e+00 2.6797931e+00 2.6323855e+00 2.9822614e+00 2.6747723e+00 2.4035668e+00 2.5939621e+00 2.4241443e+00 2.5291529e+00 2.9226858e+00 2.0959349e+00 2.5480036e+00 4.4738080e+00 3.4750769e+00 4.6459778e+00 3.7712854e+00 4.2573656e+00 5.2660922e+00 2.9665251e+00 4.7582165e+00 4.3221591e+00 5.2027091e+00 3.8786505e+00 3.8957203e+00 4.2952898e+00 3.6300721e+00 3.8796854e+00 4.1217239e+00 3.8539421e+00 5.6722969e+00 5.6818418e+00 3.7421166e+00 4.5832488e+00 3.3486395e+00 5.3611963e+00 3.6296692e+00 4.3021827e+00 4.5620086e+00 3.4793229e+00 3.3827920e+00 4.0990020e+00 4.3836503e+00 4.8653269e+00 5.6359100e+00 4.1798856e+00 3.4290065e+00 3.5331571e+00 5.3326389e+00 4.2899049e+00 3.7762521e+00 3.2871170e+00 4.3357622e+00 4.4879065e+00 4.4101806e+00 3.4750769e+00 4.5719131e+00 4.6252914e+00 4.2958117e+00 3.8764095e+00 3.9087616e+00 4.0828629e+00 3.3281063e+00 8.9980139e-01 6.8064066e-01 4.6472955e-01 1.7695601e+00 1.1682143e+00 5.3827772e-01 5.3286499e-01 1.4108003e+00 1.6357068e+00 1.3406177e+00 2.0471148e+00 8.8540687e-01 2.9145160e-01 9.8663349e-01 4.9772204e-01 6.8921053e-01 3.7371902e-01 5.3360548e-01 8.2263932e-01 5.9279023e-01 1.3884168e+00 5.4248468e-01 3.3872939e-01 5.2066928e-01 9.9757114e-01 1.1836141e+00 7.1971771e-01 1.1867923e+00 1.5097838e+00 1.1682143e+00 9.2945909e-01 6.4884272e-01 1.1682143e+00 1.5630357e+00 4.8016385e-01 2.7124234e-01 3.0617227e+00 1.1744248e+00 5.8374436e-01 6.1345624e-01 1.4108003e+00 4.9009568e-01 1.0413386e+00 4.3319335e-01 6.9189100e-01 3.5573943e+00 3.1141702e+00 3.6648465e+00 3.6881887e+00 3.5883824e+00 3.0468381e+00 3.1315658e+00 3.1515797e+00 3.4214871e+00 2.9743594e+00 4.0164863e+00 2.9182492e+00 3.8928105e+00 3.2158144e+00 2.6006889e+00 3.3027067e+00 2.9031809e+00 2.9465762e+00 4.3027855e+00 3.2186029e+00 3.1845052e+00 3.0688421e+00 3.9670252e+00 3.2223969e+00 3.2005819e+00 3.3183883e+00 3.7835219e+00 3.7624114e+00 3.1706932e+00 2.9238797e+00 3.3563322e+00 3.2896970e+00 2.9943889e+00 3.6782511e+00 2.8525048e+00 2.8501434e+00 3.4560405e+00 4.0524463e+00 2.6189855e+00 3.3240937e+00 3.2080454e+00 3.0726532e+00 3.1844624e+00 3.3537486e+00 3.0707196e+00 2.6200714e+00 2.8136838e+00 3.0798325e+00 2.9434145e+00 2.9219863e+00 4.3385668e+00 3.8211670e+00 4.5879449e+00 3.8900790e+00 4.2758494e+00 5.1630878e+00 3.6606996e+00 4.7359279e+00 4.6113949e+00 4.8204842e+00 3.7540483e+00 4.1248784e+00 4.2705921e+00 4.1081181e+00 4.1285849e+00 4.0208302e+00 3.8718630e+00 5.1706118e+00 5.7653526e+00 4.3529699e+00 4.4302351e+00 3.6643052e+00 5.3499285e+00 3.8863166e+00 4.1025634e+00 4.3705834e+00 3.6895783e+00 3.4655259e+00 4.2576017e+00 4.3078329e+00 4.8848930e+00 5.1059908e+00 4.3355275e+00 3.6287723e+00 3.9015858e+00 5.2167033e+00 4.0809175e+00 3.7394250e+00 3.3885059e+00 4.2346521e+00 4.4182506e+00 4.3085803e+00 3.8211670e+00 4.4331391e+00 4.4402220e+00 4.2825037e+00 4.2456731e+00 3.9239772e+00 3.8761056e+00 3.4480528e+00 1.5196051e+00 1.2824303e+00 2.6220224e+00 1.9839744e+00 5.4248468e-01 1.3880441e+00 2.2398377e+00 2.5185753e+00 6.5993495e-01 1.2140269e+00 2.2670334e-01 1.0140902e+00 4.4901474e-01 4.6310132e-01 1.1825559e+00 5.9738093e-01 1.2799022e+00 1.4364110e+00 1.3915110e+00 2.1479410e+00 1.2515236e+00 9.9544409e-01 1.2188859e+00 1.8419620e+00 2.0002725e+00 1.1587093e+00 6.6217390e-01 7.6869104e-01 1.9839744e+00 1.7287715e+00 9.9282597e-01 1.9839744e+00 2.4262873e+00 1.2419907e+00 1.0737552e+00 3.8557204e+00 2.0456732e+00 1.0753036e+00 4.4417668e-01 2.2089621e+00 5.0629646e-01 1.9071648e+00 5.5576380e-01 1.4985933e+00 3.3087308e+00 2.9428879e+00 3.4716469e+00 4.0891690e+00 3.6027276e+00 3.2367228e+00 2.9085289e+00 3.7111791e+00 3.3928277e+00 3.3150290e+00 4.5928107e+00 2.9594198e+00 4.2678298e+00 3.2621413e+00 2.8156205e+00 3.1507918e+00 2.9937665e+00 3.2188610e+00 4.5717893e+00 3.5888229e+00 3.0697068e+00 3.2000980e+00 4.1198780e+00 3.3377308e+00 3.2155231e+00 3.2352945e+00 3.7547729e+00 3.6294383e+00 3.2310889e+00 3.2831808e+00 3.7736317e+00 3.7263104e+00 3.2475076e+00 3.7907966e+00 2.9840079e+00 2.6164035e+00 3.2920107e+00 4.3046992e+00 2.7582086e+00 3.6782988e+00 3.5276458e+00 3.0726914e+00 3.4670753e+00 3.9103065e+00 3.3340819e+00 2.7470239e+00 2.9746673e+00 3.1328218e+00 3.4555378e+00 3.1318122e+00 4.0752096e+00 3.9330937e+00 4.3762388e+00 3.8407425e+00 4.1274362e+00 4.9032073e+00 4.0261574e+00 4.5547766e+00 4.6534818e+00 4.3582690e+00 3.5326627e+00 4.1405270e+00 4.0947877e+00 4.2953569e+00 4.1533819e+00 3.7981559e+00 3.7518931e+00 4.6218430e+00 5.6203182e+00 4.6338556e+00 4.1503558e+00 3.7655153e+00 5.1552615e+00 3.9363994e+00 3.8053980e+00 4.0787299e+00 3.7177375e+00 3.4172455e+00 4.2121483e+00 4.1116484e+00 4.7277367e+00 4.5452377e+00 4.2828893e+00 3.6617103e+00 4.0381240e+00 4.9437127e+00 3.7768622e+00 3.5869495e+00 3.3594066e+00 4.0056296e+00 4.1979142e+00 4.0739556e+00 3.9330937e+00 4.1628496e+00 4.1341118e+00 4.1117268e+00 4.3552101e+00 3.7951858e+00 3.5859295e+00 3.4280549e+00 5.0370871e-01 1.1854382e+00 8.2574748e-01 1.1968529e+00 2.9724335e-01 9.8896933e-01 1.0391769e+00 2.0112023e+00 2.6653431e+00 1.5111262e+00 6.4636269e-01 1.6262201e+00 1.1040164e+00 9.8966705e-01 9.2934901e-01 5.3040146e-01 7.1789533e-01 3.9472619e-01 1.0556536e+00 5.1318506e-01 7.7368489e-01 7.3633268e-01 5.0731024e-01 7.5303835e-01 9.7659801e-01 1.7897583e+00 2.1453760e+00 8.2574748e-01 6.9001472e-01 1.1202045e+00 8.2574748e-01 9.6424206e-01 6.2046469e-01 5.3827772e-01 2.5404386e+00 5.3988754e-01 6.7372733e-01 1.1459117e+00 9.5361455e-01 1.1160907e+00 4.7951153e-01 1.1016806e+00 5.5109043e-01 3.7667766e+00 3.2399456e+00 3.8211099e+00 3.3920086e+00 3.5974024e+00 2.9126202e+00 3.2661365e+00 2.7296888e+00 3.4884812e+00 2.6863536e+00 3.5939578e+00 2.8820992e+00 3.6783922e+00 3.1916608e+00 2.4616677e+00 3.4465420e+00 2.8051321e+00 2.8088029e+00 4.1173049e+00 2.9788024e+00 3.2021702e+00 3.0140681e+00 3.8639979e+00 3.1756883e+00 3.2362495e+00 3.4136564e+00 3.8424217e+00 3.8484723e+00 3.1221021e+00 2.7251978e+00 3.0739866e+00 3.0068046e+00 2.8468838e+00 3.5726594e+00 2.7099355e+00 2.9743925e+00 3.5889631e+00 3.9062347e+00 2.5235038e+00 3.0623697e+00 2.9777787e+00 3.0820765e+00 3.0110500e+00 2.9445347e+00 2.8809757e+00 2.5543631e+00 2.7073441e+00 3.0792230e+00 2.5679169e+00 2.7817508e+00 4.4017095e+00 3.6741422e+00 4.6939918e+00 3.8825162e+00 4.3049794e+00 5.3127345e+00 3.3002804e+00 4.8514877e+00 4.5630927e+00 5.0475012e+00 3.8518880e+00 4.0758615e+00 4.3442223e+00 3.8984747e+00 3.9980344e+00 4.0855291e+00 3.9215612e+00 5.4851910e+00 5.8312870e+00 4.1416475e+00 4.5535489e+00 3.5028870e+00 5.4694510e+00 3.8234999e+00 4.2411026e+00 4.5531893e+00 3.6365882e+00 3.4540568e+00 4.2272070e+00 4.4531017e+00 4.9839411e+00 5.4520873e+00 4.3016933e+00 3.6054769e+00 3.7985950e+00 5.3693255e+00 4.1776636e+00 3.8035129e+00 3.3594554e+00 4.3469553e+00 4.4886880e+00 4.4112274e+00 3.6741422e+00 4.5435532e+00 4.5534752e+00 4.3346048e+00 4.1329859e+00 3.9643709e+00 3.9674740e+00 3.4024060e+00 1.3630799e+00 7.1446962e-01 8.4383266e-01 2.4808718e-01 9.6424206e-01 1.2783641e+00 1.6962086e+00 2.4702874e+00 1.2824303e+00 2.9691107e-01 1.2631980e+00 9.3957399e-01 4.9617437e-01 7.4965096e-01 7.2553812e-01 4.8492463e-01 3.3125444e-01 9.2426065e-01 2.6812643e-01 3.3395426e-01 2.4808718e-01 5.8926015e-01 7.3502408e-01 5.4956349e-01 1.6376300e+00 1.9421609e+00 7.1446962e-01 4.9160020e-01 6.5622658e-01 7.1446962e-01 1.1785203e+00 1.2076330e-01 2.8845946e-01 2.6135503e+00 8.6638670e-01 5.7543116e-01 9.9282597e-01 9.6424206e-01 9.3211669e-01 6.7030885e-01 7.8100392e-01 2.3749211e-01 3.4362741e+00 2.9772187e+00 3.5154539e+00 3.2993605e+00 3.3443673e+00 2.7564382e+00 3.0285957e+00 2.7337208e+00 3.1980682e+00 2.6433553e+00 3.5789723e+00 2.6973512e+00 3.4963203e+00 2.9759207e+00 2.3127671e+00 3.1412273e+00 2.6774449e+00 2.6095931e+00 3.9436181e+00 2.8415480e+00 3.0475523e+00 2.7865107e+00 3.6589666e+00 2.9471549e+00 2.9637920e+00 3.1238401e+00 3.5511257e+00 3.5866237e+00 2.9292978e+00 2.5500042e+00 2.9620296e+00 2.8874183e+00 2.6658729e+00 3.4048426e+00 2.6224103e+00 2.7775195e+00 3.2991484e+00 3.6986035e+00 2.3717154e+00 2.9594198e+00 2.8613259e+00 2.8592001e+00 2.8393888e+00 2.9284199e+00 2.7478281e+00 2.3715604e+00 2.5423572e+00 2.8329679e+00 2.5370255e+00 2.6226761e+00 4.2550363e+00 3.5587552e+00 4.4384175e+00 3.6863990e+00 4.1157774e+00 5.0262130e+00 3.3282378e+00 4.5651798e+00 4.3425674e+00 4.8115590e+00 3.6359466e+00 3.8813909e+00 4.1126592e+00 3.8106374e+00 3.9142555e+00 3.9091502e+00 3.6969354e+00 5.1996380e+00 5.5654572e+00 3.9942942e+00 4.3261607e+00 3.4228883e+00 5.1764012e+00 3.6303894e+00 4.0165247e+00 4.2627936e+00 3.4508614e+00 3.2748167e+00 4.0461381e+00 4.1489951e+00 4.6983104e+00 5.1372583e+00 4.1280577e+00 3.3829235e+00 3.6112201e+00 5.0844328e+00 4.0217556e+00 3.5877667e+00 3.1942059e+00 4.1021303e+00 4.2899049e+00 4.1807095e+00 3.5587552e+00 4.3276502e+00 4.3608503e+00 4.1273623e+00 3.9585431e+00 3.7540483e+00 3.8154519e+00 3.2543470e+00 7.7313507e-01 2.2058495e+00 1.2553676e+00 5.5109043e-01 3.3742167e-01 3.0507869e+00 3.8084614e+00 2.6171176e+00 1.6268459e+00 2.6113513e+00 2.2457527e+00 1.6784057e+00 2.0471148e+00 1.6480463e+00 1.3225313e+00 1.2819528e+00 7.6880092e-01 1.3915110e+00 1.6886167e+00 1.5043671e+00 7.8918675e-01 6.7745878e-01 1.6911617e+00 2.9348176e+00 3.2792996e+00 7.7313507e-01 1.0082548e+00 1.9190366e+00 7.7313507e-01 2.3749211e-01 1.4309353e+00 1.5685186e+00 1.3963590e+00 6.9420840e-01 1.6514950e+00 2.2742046e+00 5.5109043e-01 2.2440749e+00 7.3283576e-01 2.1421205e+00 1.1730565e+00 4.0382971e+00 3.5072516e+00 4.0204750e+00 2.8157524e+00 3.5602797e+00 2.7716933e+00 3.6026548e+00 1.9881684e+00 3.5249607e+00 2.3719578e+00 2.7108793e+00 2.9588139e+00 3.1000099e+00 3.1914275e+00 2.3942229e+00 3.6456797e+00 2.8533918e+00 2.5518058e+00 3.6496659e+00 2.5198146e+00 3.4451131e+00 2.9183683e+00 3.5989425e+00 3.0794356e+00 3.2576813e+00 3.5320163e+00 3.8261152e+00 3.9741896e+00 3.1180182e+00 2.3364082e+00 2.5170134e+00 2.4274466e+00 2.6068690e+00 3.4218333e+00 2.7386417e+00 3.3934324e+00 3.7851452e+00 3.4854109e+00 2.5636830e+00 2.6200486e+00 2.6174942e+00 3.1669559e+00 2.6880360e+00 2.1675629e+00 2.6284424e+00 2.5986852e+00 2.6571682e+00 3.0828753e+00 1.9438939e+00 2.6338308e+00 4.6899445e+00 3.5257224e+00 4.8360835e+00 3.9149039e+00 4.4239486e+00 5.4654338e+00 2.8575769e+00 4.9368840e+00 4.3795070e+00 5.4971381e+00 4.1073887e+00 3.9867349e+00 4.4778796e+00 3.6113294e+00 3.9521234e+00 4.3324165e+00 4.0348180e+00 6.0067497e+00 5.8007868e+00 3.6612067e+00 4.8067419e+00 3.4133837e+00 5.5245725e+00 3.7158962e+00 4.5501061e+00 4.8067452e+00 3.5898573e+00 3.5494058e+00 4.2132256e+00 4.5903044e+00 5.0235775e+00 5.9805492e+00 4.2919572e+00 3.5520531e+00 3.5821954e+00 5.5319518e+00 4.5355231e+00 3.9801247e+00 3.4489678e+00 4.5459210e+00 4.6801759e+00 4.6148655e+00 3.5257224e+00 4.7911899e+00 4.8567489e+00 4.4697071e+00 3.9039516e+00 4.0848538e+00 4.3320055e+00 3.4824517e+00 1.5154593e+00 7.1671402e-01 2.6643250e-01 7.9347379e-01 2.3528246e+00 3.1744385e+00 1.9839744e+00 9.9059199e-01 1.9029496e+00 1.6532822e+00 9.3451915e-01 1.4586696e+00 1.2483935e+00 7.4743804e-01 7.4957404e-01 2.9691107e-01 8.0686941e-01 9.9973471e-01 7.9394533e-01 3.6319073e-01 1.8699153e-01 9.9891776e-01 2.3345854e+00 2.6422396e+00 0.0000000e+00 3.3742167e-01 1.1857824e+00 0.0000000e+00 6.6918102e-01 7.4445830e-01 9.7322023e-01 1.9284841e+00 6.6918102e-01 1.1393372e+00 1.6942803e+00 3.7371902e-01 1.6386105e+00 4.5257749e-01 1.4715172e+00 4.9772204e-01 3.5602797e+00 3.0968979e+00 3.5933352e+00 2.8998722e+00 3.2656298e+00 2.6029746e+00 3.1974447e+00 2.2445103e+00 3.1601923e+00 2.3946216e+00 3.0209665e+00 2.6867317e+00 3.0775658e+00 2.9161244e+00 2.1946278e+00 3.2137635e+00 2.6502891e+00 2.3652711e+00 3.6096140e+00 2.4893065e+00 3.1578003e+00 2.6568473e+00 3.4426472e+00 2.8187901e+00 2.9128857e+00 3.1418202e+00 3.4847097e+00 3.6205958e+00 2.8694312e+00 2.2223283e+00 2.5588203e+00 2.4651138e+00 2.4413293e+00 3.2621861e+00 2.5834128e+00 2.9995857e+00 3.3733791e+00 3.3817876e+00 2.3263008e+00 2.6305758e+00 2.5756071e+00 2.8533918e+00 2.5683063e+00 2.4187322e+00 2.5258216e+00 2.3250308e+00 2.4413618e+00 2.7691536e+00 2.1006933e+00 2.4608850e+00 4.4119896e+00 3.4290419e+00 4.4942656e+00 3.6650933e+00 4.1590662e+00 5.0899438e+00 3.0333619e+00 4.5799469e+00 4.1882413e+00 5.0726081e+00 3.7613721e+00 3.7859992e+00 4.1623715e+00 3.6029758e+00 3.8608065e+00 4.0352348e+00 3.7266849e+00 5.5043247e+00 5.5172732e+00 3.6569442e+00 4.4568115e+00 3.3324016e+00 5.1765224e+00 3.5192065e+00 4.1799642e+00 4.3857400e+00 3.3769078e+00 3.2906843e+00 4.0034548e+00 4.1917183e+00 4.6854597e+00 5.4444866e+00 4.0904298e+00 3.2962678e+00 3.4250783e+00 5.1569386e+00 4.2213314e+00 3.6582637e+00 3.2059261e+00 4.1937040e+00 4.3826532e+00 4.2786320e+00 3.4290419e+00 4.4549850e+00 4.5270304e+00 4.1816268e+00 3.7777251e+00 3.7924144e+00 4.0173731e+00 3.2613828e+00 1.0034646e+00 1.7753099e+00 2.1070188e+00 8.6079202e-01 1.6751898e+00 5.4248468e-01 6.0365341e-01 4.6310132e-01 4.4901474e-01 7.0111465e-01 4.4713936e-01 1.0328871e+00 1.0722301e+00 1.0271920e+00 1.6860841e+00 8.8540687e-01 5.2066928e-01 7.3502408e-01 1.4309353e+00 1.5630357e+00 7.3985997e-01 9.6257499e-01 1.1608422e+00 1.5154593e+00 1.2617482e+00 4.9009568e-01 1.5154593e+00 2.0192952e+00 7.8100392e-01 6.9001472e-01 3.4112480e+00 1.6736143e+00 8.5090098e-01 5.5183182e-01 1.7753099e+00 4.3319335e-01 1.5054343e+00 1.2076330e-01 1.0428797e+00 3.2901237e+00 2.9292978e+00 3.4367371e+00 3.8111737e+00 3.4729880e+00 3.0672734e+00 2.9473505e+00 3.3901879e+00 3.2662947e+00 3.1144880e+00 4.2413542e+00 2.8660589e+00 3.9495965e+00 3.1433256e+00 2.6375433e+00 3.0908568e+00 2.9081458e+00 2.9702968e+00 4.3236374e+00 3.3103937e+00 3.0964304e+00 3.0179755e+00 3.9313682e+00 3.1669001e+00 3.0754584e+00 3.1432906e+00 3.6245464e+00 3.5873526e+00 3.1179878e+00 2.9918256e+00 3.4776059e+00 3.4142800e+00 3.0198617e+00 3.6568155e+00 2.8980988e+00 2.6944324e+00 3.2512254e+00 4.0479095e+00 2.6293791e+00 3.4291613e+00 3.2968205e+00 2.9799791e+00 3.2239661e+00 3.5774656e+00 3.1299499e+00 2.6069579e+00 2.8203994e+00 2.9888842e+00 3.1470877e+00 2.9476507e+00 4.1975965e+00 3.8311128e+00 4.3861256e+00 3.7921747e+00 4.1446583e+00 4.9211801e+00 3.8442384e+00 4.5238497e+00 4.5231439e+00 4.5451210e+00 3.5805575e+00 4.0469570e+00 4.0990882e+00 4.1579560e+00 4.1249171e+00 3.8727781e+00 3.7290616e+00 4.8292468e+00 5.5757877e+00 4.3965263e+00 4.2248397e+00 3.6936498e+00 5.1256160e+00 3.8221804e+00 3.8961870e+00 4.1176453e+00 3.6242665e+00 3.3807801e+00 4.1671042e+00 4.0787299e+00 4.6798442e+00 4.7374542e+00 4.2466909e+00 3.5432140e+00 3.8759165e+00 4.9689016e+00 3.9204411e+00 3.5927937e+00 3.3203721e+00 4.0348754e+00 4.2531600e+00 4.1147391e+00 3.8311128e+00 4.2401451e+00 4.2502727e+00 4.1279956e+00 4.2116129e+00 3.7856899e+00 3.7242025e+00 3.3954918e+00 9.3865015e-01 1.1459117e+00 1.8505741e+00 2.5636327e+00 1.3972701e+00 4.6310132e-01 1.4327294e+00 1.0013399e+00 7.2679299e-01 8.2574748e-01 6.2187934e-01 5.8496636e-01 1.7002750e-01 9.5361455e-01 3.5639126e-01 5.3827772e-01 4.9617437e-01 4.7680727e-01 6.9189100e-01 7.7259801e-01 1.6911681e+00 2.0326426e+00 7.1671402e-01 5.6788283e-01 8.9258315e-01 7.1671402e-01 1.0551281e+00 3.6669623e-01 3.9699460e-01 2.5716465e+00 6.8921053e-01 6.2148529e-01 1.0391769e+00 9.3865015e-01 9.9111027e-01 5.3286499e-01 9.2006504e-01 3.5266705e-01 3.5819899e+00 3.0902007e+00 3.6482741e+00 3.3284223e+00 3.4528558e+00 2.8062636e+00 3.1283731e+00 2.7130802e+00 3.3201500e+00 2.6478976e+00 3.5696084e+00 2.7728704e+00 3.5649049e+00 3.0583917e+00 2.3718219e+00 3.2763163e+00 2.7180029e+00 2.6779045e+00 4.0150919e+00 2.8864805e+00 3.1083977e+00 2.8822332e+00 3.7402558e+00 3.0304088e+00 3.0793541e+00 3.2506893e+00 3.6756049e+00 3.7003058e+00 3.0054875e+00 2.6160903e+00 2.9965118e+00 2.9238797e+00 2.7351276e+00 3.4650507e+00 2.6418165e+00 2.8577585e+00 3.4252075e+00 3.7832878e+00 2.4227175e+00 2.9917855e+00 2.8903467e+00 2.9460322e+00 2.9034030e+00 2.9191699e+00 2.7908173e+00 2.4332562e+00 2.6000033e+00 2.9338362e+00 2.5416562e+00 2.6797931e+00 4.3169600e+00 3.6002350e+00 4.5501061e+00 3.7611228e+00 4.1952298e+00 5.1491201e+00 3.2996439e+00 4.6835622e+00 4.4311275e+00 4.9194005e+00 3.7316761e+00 3.9622355e+00 4.2152785e+00 3.8426553e+00 3.9520057e+00 3.9894324e+00 3.7877346e+00 5.3234168e+00 5.6801405e+00 4.0465336e+00 4.4289683e+00 3.4509848e+00 5.3007226e+00 3.7123045e+00 4.1128937e+00 4.3848871e+00 3.5295907e+00 3.3480190e+00 4.1214168e+00 4.2758432e+00 4.8208082e+00 5.2754050e+00 4.2018689e+00 3.4688327e+00 3.6716137e+00 5.2146461e+00 4.0903576e+00 3.6733277e+00 3.2612647e+00 4.2126998e+00 4.3809966e+00 4.2914999e+00 3.6002350e+00 4.4223824e+00 4.4497684e+00 4.2250047e+00 4.0330034e+00 3.8460049e+00 3.8818416e+00 3.3084835e+00 6.2729876e-01 2.6091054e+00 3.4299177e+00 2.2340939e+00 1.2368073e+00 2.1640372e+00 1.8992968e+00 1.1925354e+00 1.7015647e+00 1.4288989e+00 9.5582164e-01 9.7391954e-01 2.9724335e-01 1.0376697e+00 1.2583645e+00 1.0495503e+00 5.0731024e-01 2.8845946e-01 1.2384679e+00 2.5831347e+00 2.8967543e+00 2.6643250e-01 5.4873947e-01 1.4369223e+00 2.6643250e-01 5.0370871e-01 1.0013399e+00 1.2082987e+00 1.6761482e+00 6.8299624e-01 1.3528452e+00 1.9417181e+00 2.6206799e-01 1.8882412e+00 5.3690447e-01 1.7295385e+00 7.4445830e-01 3.6974389e+00 3.2246529e+00 3.7127916e+00 2.8221999e+00 3.3289662e+00 2.6369282e+00 3.3348648e+00 2.1165503e+00 3.2465431e+00 2.3709411e+00 2.8608899e+00 2.7655496e+00 3.0110500e+00 2.9862341e+00 2.2391292e+00 3.3332541e+00 2.7176350e+00 2.3810734e+00 3.5657791e+00 2.4469788e+00 3.2638546e+00 2.7057900e+00 3.4512743e+00 2.8728052e+00 2.9934131e+00 3.2431147e+00 3.5582624e+00 3.7179545e+00 2.9335017e+00 2.1999209e+00 2.4893065e+00 2.3915367e+00 2.4540260e+00 3.2923304e+00 2.6414379e+00 3.1466196e+00 3.4901671e+00 3.3542627e+00 2.3973915e+00 2.5861640e+00 2.5561973e+00 2.9420425e+00 2.5609364e+00 2.2836399e+00 2.5303888e+00 2.4035745e+00 2.4950488e+00 2.8435005e+00 2.0000785e+00 2.4916202e+00 4.5218181e+00 3.4492861e+00 4.5921002e+00 3.7366932e+00 4.2432727e+00 5.1949299e+00 2.9709788e+00 4.6735988e+00 4.2160797e+00 5.2249989e+00 3.8759828e+00 3.8289542e+00 4.2545385e+00 3.5878027e+00 3.8924868e+00 4.1403048e+00 3.8179103e+00 5.6801405e+00 5.5805905e+00 3.6100547e+00 4.5709635e+00 3.3584734e+00 5.2629823e+00 3.5576748e+00 4.3070506e+00 4.5135384e+00 3.4273212e+00 3.3707040e+00 4.0596064e+00 4.2990937e+00 4.7676077e+00 5.6256469e+00 4.1454035e+00 3.3551224e+00 3.4472672e+00 5.2603070e+00 4.3452859e+00 3.7614312e+00 3.2825924e+00 4.3002370e+00 4.4796258e+00 4.3809022e+00 3.4492861e+00 4.5673965e+00 4.6446301e+00 4.2677070e+00 3.7864370e+00 3.8796124e+00 4.1423594e+00 3.3352922e+00 2.9361142e+00 3.6728262e+00 2.4980859e+00 1.5364600e+00 2.5390784e+00 2.1113072e+00 1.6578570e+00 1.9353585e+00 1.4375287e+00 1.3425463e+00 1.1993279e+00 9.0115406e-01 1.3418210e+00 1.6061161e+00 1.4416694e+00 7.3727571e-01 7.1781501e-01 1.6797637e+00 2.7692440e+00 3.1313820e+00 7.9347379e-01 9.7352372e-01 1.8600648e+00 7.9347379e-01 2.1119253e-01 1.3612390e+00 1.4580439e+00 1.6571634e+00 5.0731024e-01 1.5980974e+00 2.1674065e+00 6.7984069e-01 2.1059482e+00 6.2457556e-01 2.0330443e+00 1.1132823e+00 4.2319020e+00 3.7044235e+00 4.2326669e+00 3.1432906e+00 3.8172627e+00 3.0425144e+00 3.7866079e+00 2.3206274e+00 3.7650177e+00 2.6608343e+00 3.0454230e+00 3.1914925e+00 3.4221447e+00 3.4413652e+00 2.6453561e+00 3.8539838e+00 3.0892079e+00 2.8357998e+00 3.9683086e+00 2.8336784e+00 3.6477040e+00 3.1799040e+00 3.8944724e+00 3.3434129e+00 3.4994776e+00 3.7569353e+00 4.0775935e+00 4.2049294e+00 3.3684490e+00 2.6363662e+00 2.8414019e+00 2.7526519e+00 2.8906655e+00 3.7008234e+00 2.9737492e+00 3.5555916e+00 3.9977110e+00 3.7960948e+00 2.7978670e+00 2.9332077e+00 2.9200513e+00 3.4002561e+00 2.9851921e+00 2.5032729e+00 2.9159414e+00 2.8324645e+00 2.9104902e+00 3.3286096e+00 2.2670901e+00 2.9042354e+00 4.8902416e+00 3.8029663e+00 5.0697571e+00 4.1657422e+00 4.6611282e+00 5.6979337e+00 3.1565039e+00 5.1794156e+00 4.6677357e+00 5.6656906e+00 4.3138249e+00 4.2590423e+00 4.7118516e+00 3.9079657e+00 4.2090892e+00 4.5409993e+00 4.2707579e+00 6.1569683e+00 6.0684263e+00 3.9837013e+00 5.0178221e+00 3.6761530e+00 5.7743610e+00 3.9890690e+00 4.7480354e+00 5.0151961e+00 3.8518880e+00 3.7849163e+00 4.4740109e+00 4.8191103e+00 5.2745801e+00 6.1258486e+00 4.5520039e+00 3.8145791e+00 3.8707244e+00 5.7618968e+00 4.7193263e+00 4.2030300e+00 3.6843246e+00 4.7664506e+00 4.9031551e+00 4.8333734e+00 3.8029663e+00 5.0038635e+00 5.0562579e+00 4.7021393e+00 4.1966653e+00 4.3193180e+00 4.5127918e+00 3.7195417e+00 9.8143688e-01 5.9868400e-01 1.4402716e+00 5.6992880e-01 9.8663349e-01 1.4928150e+00 1.1361809e+00 1.7216149e+00 1.8856736e+00 1.8789959e+00 2.5107352e+00 1.7228721e+00 1.3724737e+00 1.5661153e+00 2.2847787e+00 2.4120925e+00 1.4985933e+00 7.9011741e-01 5.9738093e-01 2.3528246e+00 2.0826771e+00 1.2100516e+00 2.3528246e+00 2.8601865e+00 1.6304499e+00 1.5111262e+00 4.2257604e+00 2.5031609e+00 1.6091095e+00 1.0739839e+00 2.6091054e+00 9.8932340e-01 2.3488496e+00 9.3392552e-01 1.8848176e+00 3.4513181e+00 3.2138675e+00 3.6568023e+00 4.4832075e+00 3.8715598e+00 3.6399979e+00 3.2048569e+00 4.1609431e+00 3.6276988e+00 3.7852753e+00 5.0007603e+00 3.3356061e+00 4.5726588e+00 3.6020324e+00 3.2283316e+00 3.3543125e+00 3.4317803e+00 3.5762357e+00 4.8853618e+00 3.9697083e+00 3.4608105e+00 3.5194529e+00 4.4307529e+00 3.6664068e+00 3.4821665e+00 3.4661369e+00 3.9690303e+00 3.8732280e+00 3.5908374e+00 3.6384054e+00 4.1605480e+00 4.1054173e+00 3.6121763e+00 4.1591449e+00 3.4571840e+00 2.9726287e+00 3.5108834e+00 4.5938444e+00 3.1869025e+00 4.0859475e+00 3.9449709e+00 3.4111584e+00 3.8289196e+00 4.3382952e+00 3.7437946e+00 3.1530215e+00 3.3792174e+00 3.4400302e+00 3.8876642e+00 3.5288768e+00 4.4107365e+00 4.3401559e+00 4.5910423e+00 4.1731099e+00 4.4383007e+00 5.0605479e+00 4.5288382e+00 4.7400085e+00 4.9337127e+00 4.5282137e+00 3.8167418e+00 4.4582410e+00 4.3491396e+00 4.7099690e+00 4.5667632e+00 4.1110524e+00 4.0457882e+00 4.6970439e+00 5.8050200e+00 4.9831785e+00 4.3869525e+00 4.2045486e+00 5.3107398e+00 4.2598936e+00 4.0608563e+00 4.2495756e+00 4.0560071e+00 3.7740189e+00 4.5388860e+00 4.2824837e+00 4.9058468e+00 4.5708763e+00 4.6120616e+00 3.9763551e+00 4.3872260e+00 5.0860672e+00 4.0998055e+00 3.8946369e+00 3.7330702e+00 4.2352833e+00 4.4742220e+00 4.3047259e+00 4.3401559e+00 4.4192906e+00 4.4017154e+00 4.3831142e+00 4.6832544e+00 4.0909816e+00 3.9225445e+00 3.8229302e+00 1.2140269e+00 2.2031378e+00 1.3945864e+00 1.5674943e+00 2.3519816e+00 1.7695601e+00 2.3060361e+00 2.6440626e+00 2.5730128e+00 3.3484034e+00 2.4570006e+00 2.1775427e+00 2.3990667e+00 3.0314484e+00 3.2003667e+00 2.3345854e+00 9.9891776e-01 5.8565201e-01 3.1744385e+00 2.9106021e+00 2.1090950e+00 3.1744385e+00 3.6015962e+00 2.4316107e+00 2.2478972e+00 5.0583620e+00 3.1946200e+00 2.2571919e+00 1.5783735e+00 3.4098353e+00 1.5848534e+00 3.0815481e+00 1.7053877e+00 2.6874763e+00 3.8897688e+00 3.6527400e+00 4.1085323e+00 5.1878354e+00 4.4401043e+00 4.2306533e+00 3.5668974e+00 4.8855250e+00 4.1984227e+00 4.3936084e+00 5.7667342e+00 3.8604223e+00 5.3386416e+00 4.1481805e+00 3.8457422e+00 3.8556395e+00 3.9253518e+00 4.2633467e+00 5.5746944e+00 4.6805795e+00 3.8185179e+00 4.1531225e+00 5.0514886e+00 4.2706189e+00 4.0732692e+00 4.0031242e+00 4.5383244e+00 4.3266944e+00 4.1312924e+00 4.3745457e+00 4.8862079e+00 4.8484299e+00 4.2820168e+00 4.7051757e+00 3.9401848e+00 3.2884177e+00 3.9767257e+00 5.2985037e+00 3.7419338e+00 4.7600849e+00 4.5926355e+00 3.9322406e+00 4.5116211e+00 5.0823619e+00 4.3725296e+00 3.7236864e+00 3.9623539e+00 4.0300759e+00 4.6141946e+00 4.1447443e+00 4.5866744e+00 4.8386745e+00 4.9461657e+00 4.6106150e+00 4.7813164e+00 5.3858108e+00 5.0919277e+00 5.1446449e+00 5.4739987e+00 4.5885042e+00 4.1414025e+00 4.9586480e+00 4.7213864e+00 5.2471037e+00 4.9661074e+00 4.3830009e+00 4.4568388e+00 4.6901031e+00 6.2154767e+00 5.6469307e+00 4.6501660e+00 4.6626232e+00 5.7036296e+00 4.7932843e+00 4.3038060e+00 4.5618709e+00 4.5655950e+00 4.2115551e+00 4.9692114e+00 4.7030193e+00 5.3377504e+00 4.5914343e+00 5.0293150e+00 4.5146706e+00 4.9581881e+00 5.4087027e+00 4.2557771e+00 4.2671438e+00 4.1737149e+00 4.5756985e+00 4.7660397e+00 4.6314702e+00 4.8386745e+00 4.6734470e+00 4.5970177e+00 4.7412505e+00 5.2464126e+00 4.4890534e+00 4.0948317e+00 4.2440420e+00 1.0013399e+00 5.0299964e-01 4.6310132e-01 1.2040900e+00 5.9738093e-01 1.2286837e+00 1.4541908e+00 1.4279677e+00 2.1539145e+00 1.2617482e+00 9.9544409e-01 1.2082987e+00 1.8489243e+00 2.0066856e+00 1.1587093e+00 6.6217390e-01 7.5179033e-01 1.9839744e+00 1.7063292e+00 9.6659661e-01 1.9839744e+00 2.4156729e+00 1.2419907e+00 1.0495503e+00 3.8490499e+00 2.0330726e+00 1.0871867e+00 5.4779717e-01 2.2031378e+00 5.3106808e-01 1.9004159e+00 5.5576380e-01 1.4899949e+00 3.4307448e+00 3.0710756e+00 3.5952799e+00 4.1669813e+00 3.7116384e+00 3.3536981e+00 3.0466130e+00 3.7729829e+00 3.5082606e+00 3.4067800e+00 4.6484249e+00 3.0744089e+00 4.3424419e+00 3.3858347e+00 2.9098729e+00 3.2669110e+00 3.1198644e+00 3.3210229e+00 4.6553382e+00 3.6737423e+00 3.2048569e+00 3.2989479e+00 4.2245828e+00 3.4587219e+00 3.3255241e+00 3.3484846e+00 3.8660480e+00 3.7512964e+00 3.3482610e+00 3.3605387e+00 3.8511468e+00 3.8014113e+00 3.3411134e+00 3.9109127e+00 3.1105014e+00 2.7597977e+00 3.4146222e+00 4.3904048e+00 2.8767763e+00 3.7646132e+00 3.6317356e+00 3.1996946e+00 3.5585167e+00 3.9690108e+00 3.4365573e+00 2.8705339e+00 3.0890888e+00 3.2456270e+00 3.5108688e+00 3.2367228e+00 4.2147014e+00 4.0489906e+00 4.5035700e+00 3.9755362e+00 4.2591912e+00 5.0350768e+00 4.1207838e+00 4.6882252e+00 4.7707308e+00 4.4918348e+00 3.6612573e+00 4.2568131e+00 4.2184327e+00 4.3988058e+00 4.2632946e+00 3.9245996e+00 3.8864624e+00 4.7642090e+00 5.7424408e+00 4.7299069e+00 4.2784034e+00 3.8797952e+00 5.2832732e+00 4.0458553e+00 3.9446593e+00 4.2181053e+00 3.8300888e+00 3.5427774e+00 4.3354099e+00 4.2438935e+00 4.8511407e+00 4.6817036e+00 4.4041714e+00 3.7859242e+00 4.1665370e+00 5.0618540e+00 3.9138566e+00 3.7274783e+00 3.4833346e+00 4.1288327e+00 4.3215818e+00 4.1859543e+00 4.0489906e+00 4.2965095e+00 4.2626474e+00 4.2257654e+00 4.4572702e+00 3.9184477e+00 3.7230473e+00 3.5604297e+00 1.0161882e+00 6.9420840e-01 4.8012872e-01 4.8284931e-01 6.9738730e-01 5.5709100e-01 5.3095950e-01 1.1723315e+00 3.1271814e-01 1.8699153e-01 2.9145160e-01 8.6143605e-01 1.0061402e+00 4.5257749e-01 1.4146831e+00 1.6902182e+00 9.9059199e-01 7.2340544e-01 5.0370871e-01 9.9059199e-01 1.4369223e+00 2.7124234e-01 1.3340137e-01 2.8614050e+00 1.1016806e+00 4.2656951e-01 7.5906970e-01 1.2087236e+00 7.1325427e-01 9.2761923e-01 5.3988754e-01 4.9448466e-01 3.3643791e+00 2.9159414e+00 3.4617249e+00 3.4323687e+00 3.3505903e+00 2.8169505e+00 2.9517065e+00 2.9146662e+00 3.1941250e+00 2.7393282e+00 3.7736317e+00 2.6933089e+00 3.6308668e+00 2.9914579e+00 2.3560812e+00 3.0907905e+00 2.6932687e+00 2.7021788e+00 4.0389540e+00 2.9648098e+00 2.9980910e+00 2.8201257e+00 3.7183934e+00 2.9922398e+00 2.9661287e+00 3.0950932e+00 3.5513156e+00 3.5484439e+00 2.9420200e+00 2.6629529e+00 3.1013619e+00 3.0347859e+00 2.7417410e+00 3.4474072e+00 2.6495954e+00 2.6875764e+00 3.2488445e+00 3.7904353e+00 2.3985415e+00 3.0725852e+00 2.9704304e+00 2.8556850e+00 2.9300511e+00 3.1104511e+00 2.8291506e+00 2.4008046e+00 2.5836379e+00 2.8456807e+00 2.6907656e+00 2.6814159e+00 4.1737238e+00 3.5932878e+00 4.3853076e+00 3.6802688e+00 4.0749525e+00 4.9692376e+00 3.4394110e+00 4.5331007e+00 4.3742923e+00 4.6787296e+00 3.5632387e+00 3.8923023e+00 4.0628985e+00 3.8689922e+00 3.9102807e+00 3.8336686e+00 3.6675649e+00 5.0555526e+00 5.5454277e+00 4.0994961e+00 4.2439469e+00 3.4449831e+00 5.1429556e+00 3.6472400e+00 3.9304610e+00 4.1916938e+00 3.4565067e+00 3.2536517e+00 4.0373591e+00 4.1087274e+00 4.6703619e+00 4.9904781e+00 4.1152831e+00 3.4023949e+00 3.6756751e+00 5.0151763e+00 3.9231895e+00 3.5466262e+00 3.1760855e+00 4.0346845e+00 4.2216886e+00 4.1038501e+00 3.5932878e+00 4.2504107e+00 4.2656428e+00 4.0705667e+00 3.9971917e+00 3.7133040e+00 3.7182295e+00 3.2440381e+00 7.3339246e-01 9.9973471e-01 7.7988766e-01 1.4669572e+00 1.3868865e+00 1.4360884e+00 2.0344949e+00 1.2593779e+00 9.3451915e-01 1.1232628e+00 1.8421759e+00 1.9514085e+00 1.0061402e+00 9.6168382e-01 9.7747632e-01 1.9029496e+00 1.6513423e+00 7.7821113e-01 1.9029496e+00 2.4366790e+00 1.1857824e+00 1.1156669e+00 3.7575639e+00 2.1090460e+00 1.1623508e+00 7.4500632e-01 2.1481102e+00 7.3851064e-01 1.9301732e+00 5.6262711e-01 1.4458364e+00 3.0574507e+00 2.7604800e+00 3.2354442e+00 3.9296796e+00 3.3802783e+00 3.0910114e+00 2.7653977e+00 3.6086433e+00 3.1477156e+00 3.2299948e+00 4.4531271e+00 2.8182580e+00 4.0359059e+00 3.0838698e+00 2.6832031e+00 2.9127171e+00 2.8999239e+00 3.0235972e+00 4.3556993e+00 3.4142800e+00 2.9871882e+00 2.9947610e+00 3.9084388e+00 3.1359326e+00 2.9852014e+00 3.0007807e+00 3.4997296e+00 3.4243092e+00 3.0709062e+00 3.0889274e+00 3.6054231e+00 3.5510321e+00 3.0652992e+00 3.6307363e+00 2.9199707e+00 2.5302845e+00 3.0705222e+00 4.0683526e+00 2.6430958e+00 3.5303998e+00 3.3838093e+00 2.9011205e+00 3.2808511e+00 3.7876206e+00 3.1898591e+00 2.6081677e+00 2.8342553e+00 2.9259899e+00 3.3400468e+00 2.9809771e+00 4.0130133e+00 3.8156727e+00 4.1823301e+00 3.6854232e+00 3.9919300e+00 4.6844795e+00 3.9756960e+00 4.3245814e+00 4.4396397e+00 4.2453585e+00 3.3946374e+00 3.9634682e+00 3.9204865e+00 4.1772364e+00 4.0766155e+00 3.6959934e+00 3.5831715e+00 4.4790872e+00 5.3894840e+00 4.4405497e+00 4.0027890e+00 3.6857786e+00 4.9137586e+00 3.7567964e+00 3.6729588e+00 3.8728151e+00 3.5543966e+00 3.2848126e+00 4.0598486e+00 3.8712880e+00 4.4886485e+00 4.3722180e+00 4.1373490e+00 3.4683949e+00 3.8543469e+00 4.7248681e+00 3.7193644e+00 3.4383872e+00 3.2381387e+00 3.8297356e+00 4.0647650e+00 3.9099360e+00 3.8156727e+00 4.0266221e+00 4.0296168e+00 3.9579549e+00 4.1722551e+00 3.6379295e+00 3.5328511e+00 3.3224979e+00 1.0061402e+00 2.6525508e-01 8.2148003e-01 1.1879760e+00 1.0251165e+00 1.8544941e+00 9.4128180e-01 7.1446962e-01 9.4128180e-01 1.4726083e+00 1.6607117e+00 9.9973471e-01 7.4965096e-01 1.0510795e+00 1.6532822e+00 1.4055304e+00 8.6143605e-01 1.6532822e+00 2.0368618e+00 9.3178083e-01 7.1143905e-01 3.5363390e+00 1.6307900e+00 8.0686941e-01 2.6184788e-01 1.8811296e+00 1.4276574e-01 1.5165187e+00 3.5874135e-01 1.1682143e+00 3.5420892e+00 3.1213639e+00 3.6765721e+00 3.9907084e+00 3.7063187e+00 3.2329517e+00 3.1017380e+00 3.5164906e+00 3.5204595e+00 3.2215483e+00 4.4016409e+00 3.0251724e+00 4.2008255e+00 3.3367044e+00 2.7940225e+00 3.3344791e+00 3.0219495e+00 3.1880051e+00 4.5546425e+00 3.5075399e+00 3.1952269e+00 3.2406785e+00 4.1563139e+00 3.3848806e+00 3.3178898e+00 3.3859281e+00 3.8870730e+00 3.7997125e+00 3.2944053e+00 3.2110142e+00 3.6683444e+00 3.6131226e+00 3.2236002e+00 3.8317071e+00 2.9830921e+00 2.7973167e+00 3.4787185e+00 4.2995699e+00 2.7671612e+00 3.5982071e+00 3.4619260e+00 3.1665431e+00 3.4310890e+00 3.7235030e+00 3.2953414e+00 2.7679629e+00 2.9819595e+00 3.2106653e+00 3.2879746e+00 3.1196883e+00 4.2713770e+00 3.9634682e+00 4.5846990e+00 3.9587038e+00 4.2895460e+00 5.1416821e+00 3.9119997e+00 4.7572039e+00 4.7460626e+00 4.6638344e+00 3.7280454e+00 4.2349181e+00 4.2803486e+00 4.2908242e+00 4.2152718e+00 3.9857018e+00 3.9070797e+00 4.9741957e+00 5.8097018e+00 4.6049285e+00 4.3788345e+00 3.7893243e+00 5.3689314e+00 4.0140467e+00 4.0363955e+00 4.3259832e+00 3.8006490e+00 3.5269016e+00 4.3297076e+00 4.3216441e+00 4.9220177e+00 4.9100057e+00 4.4024600e+00 3.7489348e+00 4.0736926e+00 5.1891895e+00 3.9903212e+00 3.7514870e+00 3.4564041e+00 4.2166575e+00 4.3944655e+00 4.2851348e+00 3.9634682e+00 4.3836343e+00 4.3634474e+00 4.2898748e+00 4.4067690e+00 3.9524852e+00 3.7906894e+00 3.5162082e+00 8.3156200e-01 1.1417173e+00 5.8221430e-01 7.3339246e-01 1.0428797e+00 5.5247822e-01 3.5266705e-01 2.9537172e-01 9.6466498e-01 1.0034646e+00 2.8553149e-01 1.6415483e+00 1.8567917e+00 9.3451915e-01 7.2553812e-01 3.4520795e-01 9.3451915e-01 1.5364952e+00 3.7960845e-01 5.9589853e-01 2.7723424e+00 1.3124532e+00 7.5130648e-01 1.0314203e+00 1.1925354e+00 9.9272943e-01 1.0839891e+00 7.1143905e-01 5.6164055e-01 3.0511653e+00 2.6629268e+00 3.1545235e+00 3.1980310e+00 3.0467396e+00 2.5772061e+00 2.7369167e+00 2.7576827e+00 2.8651003e+00 2.5868532e+00 3.5774656e+00 2.4748633e+00 3.3139897e+00 2.7217211e+00 2.1506369e+00 2.7852281e+00 2.5158340e+00 2.4059801e+00 3.7433691e+00 2.7041074e+00 2.8389661e+00 2.5310559e+00 3.4176981e+00 2.6902386e+00 2.6527549e+00 2.7866168e+00 3.2144386e+00 3.2675625e+00 2.6971865e+00 2.3822357e+00 2.8532332e+00 2.7780124e+00 2.4721123e+00 3.1952919e+00 2.5042136e+00 2.5315299e+00 2.9556760e+00 3.4693709e+00 2.1993495e+00 2.8460127e+00 2.7344861e+00 2.5960616e+00 2.6558880e+00 2.9309653e+00 2.5980406e+00 2.1695355e+00 2.3550298e+00 2.5518802e+00 2.5245372e+00 2.4441489e+00 4.0319503e+00 3.3933783e+00 4.1146478e+00 3.4339804e+00 3.8578841e+00 4.6712206e+00 3.3248410e+00 4.2174528e+00 4.0704050e+00 4.4988341e+00 3.3546526e+00 3.6317702e+00 3.8139412e+00 3.6743372e+00 3.7645284e+00 3.6614225e+00 3.4131399e+00 4.8439861e+00 5.2322637e+00 3.8189229e+00 4.0256031e+00 3.2902097e+00 4.8190347e+00 3.3870859e+00 3.7223145e+00 3.9080259e+00 3.2141230e+00 3.0414452e+00 3.8022687e+00 3.7869665e+00 4.3507688e+00 4.7565318e+00 3.8893282e+00 3.1162635e+00 3.3877625e+00 4.7282676e+00 3.7917280e+00 3.3122397e+00 2.9763122e+00 3.7889092e+00 4.0173731e+00 3.8788191e+00 3.3933783e+00 4.0384828e+00 4.0914753e+00 3.8500022e+00 3.7349483e+00 3.4804621e+00 3.5920363e+00 3.0535851e+00 7.5284003e-01 9.3865015e-01 8.5442446e-01 1.6409761e+00 7.0463400e-01 5.4408162e-01 7.5179033e-01 1.2786676e+00 1.4553344e+00 7.8100392e-01 1.0100290e+00 1.2786676e+00 1.4586696e+00 1.2008045e+00 7.2638147e-01 1.4586696e+00 1.8445759e+00 7.3985997e-01 5.0731024e-01 3.3136601e+00 1.4580439e+00 5.4702555e-01 3.2339566e-01 1.6607117e+00 3.5366952e-01 1.3290015e+00 3.5639126e-01 9.6825676e-01 3.4059851e+00 2.9602214e+00 3.5257340e+00 3.7492673e+00 3.5115913e+00 3.0191277e+00 2.9517483e+00 3.2720571e+00 3.3411331e+00 2.9834222e+00 4.1580731e+00 2.8211532e+00 3.9717534e+00 3.1414634e+00 2.5643903e+00 3.1728076e+00 2.8176969e+00 2.9703822e+00 4.3244606e+00 3.2734582e+00 3.0210021e+00 3.0274003e+00 3.9433841e+00 3.1866772e+00 3.1269680e+00 3.2103093e+00 3.7065013e+00 3.6299273e+00 3.0909484e+00 2.9772513e+00 3.4297321e+00 3.3756936e+00 2.9971174e+00 3.6243253e+00 2.7759818e+00 2.6501700e+00 3.3189004e+00 4.0764633e+00 2.5559925e+00 3.3602243e+00 3.2356862e+00 2.9780147e+00 3.2026716e+00 3.4783251e+00 3.0685582e+00 2.5635668e+00 2.7679629e+00 3.0129566e+00 3.0370166e+00 2.8975180e+00 4.1264564e+00 3.7496421e+00 4.4295218e+00 3.7774558e+00 4.1191095e+00 5.0038078e+00 3.6756489e+00 4.6074750e+00 4.5494422e+00 4.5665604e+00 3.5702411e+00 4.0355009e+00 4.1137066e+00 4.0638414e+00 4.0067360e+00 3.8250619e+00 3.7375780e+00 4.9153359e+00 5.6444336e+00 4.3773916e+00 4.2331397e+00 3.5751580e+00 5.2199809e+00 3.8075775e+00 3.9003626e+00 4.1989415e+00 3.5967191e+00 3.3381510e+00 4.1394208e+00 4.1772604e+00 4.7627066e+00 4.8574416e+00 4.2113835e+00 3.5565415e+00 3.8744064e+00 5.0458107e+00 3.8530980e+00 3.5894641e+00 3.2635788e+00 4.0605147e+00 4.2327162e+00 4.1232607e+00 3.7496421e+00 4.2381044e+00 4.2216886e+00 4.1152818e+00 4.1901775e+00 3.7759339e+00 3.6506667e+00 3.3268510e+00 1.0748172e+00 7.2889003e-01 1.5046031e+00 7.9398919e-01 8.1148630e-01 8.8835337e-01 9.9058911e-01 1.2262672e+00 1.1380274e+00 1.3972288e+00 1.7741287e+00 1.2483935e+00 1.0474897e+00 1.1240042e+00 1.2483935e+00 1.4149548e+00 8.1096210e-01 5.7672351e-01 3.0082939e+00 9.6865373e-01 8.2273123e-01 9.5195566e-01 1.4288989e+00 8.3246212e-01 9.4996842e-01 9.2092295e-01 8.7375509e-01 4.0151215e+00 3.5231786e+00 4.1026785e+00 3.8908802e+00 3.9665585e+00 3.3438394e+00 3.5293285e+00 3.2540384e+00 3.8314935e+00 3.1634347e+00 4.1178317e+00 3.2512254e+00 4.1561446e+00 3.5717750e+00 2.8833448e+00 3.7345570e+00 3.1952822e+00 3.2548783e+00 4.5820687e+00 3.4621656e+00 3.5141919e+00 3.4137993e+00 4.2939653e+00 3.5777026e+00 3.5926398e+00 3.7328374e+00 4.1920808e+00 4.1652091e+00 3.5073274e+00 3.1921998e+00 3.5706840e+00 3.5044583e+00 3.2904241e+00 3.9914615e+00 3.1120432e+00 3.2204607e+00 3.8807669e+00 4.3582892e+00 2.9219284e+00 3.5476488e+00 3.4540639e+00 3.4397115e+00 3.4680001e+00 3.4670753e+00 3.3367044e+00 2.9471549e+00 3.1205392e+00 3.4518638e+00 3.0783517e+00 3.2145380e+00 4.6697624e+00 4.0951447e+00 4.9940386e+00 4.2442248e+00 4.6312366e+00 5.5957028e+00 3.7901714e+00 5.1629764e+00 4.9662629e+00 5.2245876e+00 4.1326097e+00 4.4648550e+00 4.6557857e+00 4.3477764e+00 4.3833898e+00 4.3689111e+00 4.2520226e+00 5.6153369e+00 6.1715044e+00 4.6178873e+00 4.8201070e+00 3.9129644e+00 5.7808751e+00 4.2195149e+00 4.4949009e+00 4.8099428e+00 4.0213766e+00 3.8049151e+00 4.5961475e+00 4.7475868e+00 5.3061067e+00 5.5699347e+00 4.6684257e+00 3.9900166e+00 4.2272640e+00 5.6441645e+00 4.4197984e+00 4.1176453e+00 3.7157925e+00 4.6326704e+00 4.7820862e+00 4.6921349e+00 4.0951447e+00 4.8160040e+00 4.8050680e+00 4.6459078e+00 4.5554678e+00 4.2905569e+00 4.2115152e+00 3.7649212e+00 5.9314593e-01 8.0686941e-01 2.9691107e-01 6.2826980e-01 5.0121118e-01 6.6653737e-01 7.0834786e-01 4.6310132e-01 1.9251840e+00 2.1737519e+00 7.4743804e-01 5.5009731e-01 8.0748088e-01 7.4743804e-01 1.1828955e+00 4.6964680e-01 5.8942278e-01 2.4421558e+00 9.8677196e-01 4.9772204e-01 1.1660949e+00 8.4116354e-01 1.2196311e+00 7.7538587e-01 1.0376697e+00 4.4499696e-01 3.0983271e+00 2.5986852e+00 3.1534326e+00 2.8897192e+00 2.9336987e+00 2.3392252e+00 2.6586759e+00 2.3702978e+00 2.8165027e+00 2.2079130e+00 3.2363154e+00 2.2664200e+00 3.1218253e+00 2.5673178e+00 1.8638939e+00 2.7710337e+00 2.2535803e+00 2.2156046e+00 3.5264693e+00 2.4375344e+00 2.6410495e+00 2.3635223e+00 3.2419868e+00 2.5535068e+00 2.5663186e+00 2.7372400e+00 3.1657714e+00 3.1910277e+00 2.5035271e+00 2.1450705e+00 2.5644558e+00 2.5011730e+00 2.2417546e+00 2.9810976e+00 2.2012005e+00 2.4146140e+00 2.9247451e+00 3.2953953e+00 1.9474034e+00 2.5368532e+00 2.4541089e+00 2.4554575e+00 2.4210504e+00 2.5661600e+00 2.3207574e+00 1.9628165e+00 2.1171985e+00 2.4261018e+00 2.1366217e+00 2.1917681e+00 3.8609963e+00 3.1157672e+00 4.0464741e+00 3.2769657e+00 3.7011972e+00 4.6584806e+00 2.9074576e+00 4.1962146e+00 3.9292452e+00 4.4717831e+00 3.2385313e+00 3.4507621e+00 3.7050324e+00 3.3601278e+00 3.4577371e+00 3.4991206e+00 3.2980589e+00 4.9174048e+00 5.1685262e+00 3.5822256e+00 3.9345678e+00 2.9743611e+00 4.8043725e+00 3.1946631e+00 3.6425792e+00 3.9147944e+00 3.0137993e+00 2.8509730e+00 3.6160190e+00 3.7930649e+00 4.3160863e+00 4.8705552e+00 3.6935350e+00 2.9765851e+00 3.2157655e+00 4.7030966e+00 3.6383061e+00 3.1964791e+00 2.7656084e+00 3.7055141e+00 3.8768834e+00 3.7701724e+00 3.1157672e+00 3.9366463e+00 3.9674740e+00 3.7027144e+00 3.5167570e+00 3.3369517e+00 3.4319544e+00 2.8332022e+00 9.6865373e-01 3.9487224e-01 5.8131330e-01 5.6003943e-01 5.0621589e-01 7.1247632e-01 8.0317491e-01 1.7053539e+00 2.0491684e+00 7.4957404e-01 6.5459290e-01 9.3991103e-01 7.4957404e-01 1.0954558e+00 4.2737382e-01 4.9430028e-01 2.5884539e+00 7.4949264e-01 6.4432393e-01 1.0251728e+00 9.7391954e-01 1.0055888e+00 5.9279023e-01 9.4588685e-01 4.3798311e-01 3.5017283e+00 3.0032209e+00 3.5640998e+00 3.2626300e+00 3.3723783e+00 2.7101865e+00 3.0361435e+00 2.6574564e+00 3.2363742e+00 2.5684615e+00 3.5220489e+00 2.6863775e+00 3.5035562e+00 2.9639854e+00 2.2954282e+00 3.1974234e+00 2.6186896e+00 2.5919605e+00 3.9485388e+00 2.8137879e+00 3.0123600e+00 2.8059985e+00 3.6581986e+00 2.9351026e+00 2.9984934e+00 3.1711590e+00 3.5947528e+00 3.6146776e+00 2.9159819e+00 2.5508141e+00 2.9298445e+00 2.8588898e+00 2.6582994e+00 3.3705985e+00 2.5395254e+00 2.7634723e+00 3.3411818e+00 3.7151762e+00 2.3273691e+00 2.9184140e+00 2.8006021e+00 2.8512853e+00 2.8277392e+00 2.8675465e+00 2.7048983e+00 2.3342128e+00 2.5075548e+00 2.8488482e+00 2.4938133e+00 2.5939117e+00 4.2210242e+00 3.5094230e+00 4.4613495e+00 3.6611526e+00 4.1011462e+00 5.0575391e+00 3.2183295e+00 4.5889909e+00 4.3421582e+00 4.8334387e+00 3.6441411e+00 3.8749352e+00 4.1286607e+00 3.7602700e+00 3.8694583e+00 3.9027404e+00 3.6910974e+00 5.2330448e+00 5.5920875e+00 3.9683831e+00 4.3421746e+00 3.3618744e+00 5.2099570e+00 3.6296154e+00 4.0192804e+00 4.2904705e+00 3.4453138e+00 3.2560920e+00 4.0303932e+00 4.1835729e+00 4.7330562e+00 5.1897695e+00 4.1126264e+00 3.3744864e+00 3.5691373e+00 5.1336306e+00 3.9986271e+00 3.5735980e+00 3.1698618e+00 4.1283627e+00 4.2954773e+00 4.2156054e+00 3.5094230e+00 4.3310092e+00 4.3633885e+00 4.1455698e+00 3.9545856e+00 3.7585686e+00 3.7901495e+00 3.2094268e+00 9.5902306e-01 1.1795364e+00 9.6032771e-01 5.8652824e-01 3.3395426e-01 1.0753036e+00 2.5524007e+00 2.8349345e+00 2.9691107e-01 5.1396090e-01 1.3127309e+00 2.9691107e-01 7.4426155e-01 9.3211669e-01 1.1729612e+00 1.7369516e+00 8.7560645e-01 1.2666796e+00 1.8751947e+00 2.9724335e-01 1.8489906e+00 6.7745878e-01 1.6555341e+00 7.0111465e-01 3.4067014e+00 2.9452188e+00 3.4231096e+00 2.6265336e+00 3.0474186e+00 2.3887954e+00 3.0652160e+00 1.9891259e+00 2.9589070e+00 2.1699637e+00 2.7527251e+00 2.5008826e+00 2.7949298e+00 2.7160947e+00 1.9851008e+00 3.0428102e+00 2.4755098e+00 2.1256864e+00 3.3327734e+00 2.2236827e+00 3.0131023e+00 2.4300526e+00 3.1928299e+00 2.6040863e+00 2.7075499e+00 2.9536823e+00 3.2710263e+00 3.4338296e+00 2.6673396e+00 1.9555334e+00 2.2858019e+00 2.1897213e+00 2.1973377e+00 3.0392888e+00 2.4158794e+00 2.8941568e+00 3.2025759e+00 3.1091590e+00 2.1471303e+00 2.3710990e+00 2.3347284e+00 2.6698387e+00 2.3133518e+00 2.1525596e+00 2.2918773e+00 2.1454625e+00 2.2398142e+00 2.5636830e+00 1.8343082e+00 2.2388656e+00 4.2714137e+00 3.2107728e+00 4.3091817e+00 3.4717121e+00 3.9768763e+00 4.9078859e+00 2.8122927e+00 4.3885241e+00 3.9504682e+00 4.9558940e+00 3.6044479e+00 3.5632387e+00 3.9760735e+00 3.3646188e+00 3.6594046e+00 3.8782141e+00 3.5443654e+00 5.4091146e+00 5.2988184e+00 3.3878489e+00 4.2952367e+00 3.1303129e+00 4.9761618e+00 3.2919446e+00 4.0362588e+00 4.2291285e+00 3.1618923e+00 3.1077588e+00 3.7959134e+00 4.0112445e+00 4.4810404e+00 5.3509795e+00 3.8831153e+00 3.0844802e+00 3.1980603e+00 4.9707249e+00 4.0945548e+00 3.4918172e+00 3.0237585e+00 4.0192804e+00 4.2092252e+00 4.1017980e+00 3.2107728e+00 4.2952415e+00 4.3790330e+00 3.9936934e+00 3.5312555e+00 3.6065699e+00 3.8937621e+00 3.0840989e+00 4.2827238e-01 3.7398306e-01 6.4241342e-01 7.7828522e-01 4.8636669e-01 1.6800011e+00 1.9622194e+00 8.0686941e-01 5.7691891e-01 7.1789533e-01 8.0686941e-01 1.2139401e+00 2.9406726e-01 3.1507080e-01 2.6166211e+00 9.1398375e-01 3.4909881e-01 9.4580058e-01 9.6922609e-01 9.6659661e-01 7.2638147e-01 8.2574748e-01 3.6704030e-01 3.2939549e+00 2.8018134e+00 3.3643791e+00 3.1688464e+00 3.1882120e+00 2.5926123e+00 2.8420400e+00 2.6229843e+00 3.0569434e+00 2.4659441e+00 3.4932809e+00 2.5062529e+00 3.4038365e+00 2.8103848e+00 2.1284730e+00 2.9880998e+00 2.4809350e+00 2.4830222e+00 3.8129321e+00 2.7155086e+00 2.8370039e+00 2.6306749e+00 3.5140670e+00 2.8045035e+00 2.8143558e+00 2.9698163e+00 3.4126571e+00 3.4177063e+00 2.7508387e+00 2.4282690e+00 2.8424700e+00 2.7781836e+00 2.5174968e+00 3.2360555e+00 2.4214382e+00 2.5753180e+00 3.1397228e+00 3.5790751e+00 2.1850441e+00 2.8131786e+00 2.7177153e+00 2.6876771e+00 2.6993734e+00 2.8253248e+00 2.5871836e+00 2.1990767e+00 2.3678133e+00 2.6762366e+00 2.4070540e+00 2.4551607e+00 4.0383784e+00 3.3671653e+00 4.2642537e+00 3.5070157e+00 3.9193977e+00 4.8684861e+00 3.1505769e+00 4.4165051e+00 4.1898287e+00 4.6203725e+00 3.4386758e+00 3.7047820e+00 3.9273366e+00 3.6237778e+00 3.6947343e+00 3.6968840e+00 3.5190023e+00 5.0398879e+00 5.4089760e+00 3.8611646e+00 4.1322470e+00 3.2145601e+00 5.0295849e+00 3.4546083e+00 3.8248698e+00 4.1055247e+00 3.2664139e+00 3.0788010e+00 3.8567883e+00 4.0060320e+00 4.5478501e+00 4.9912214e+00 3.9339246e+00 3.2236553e+00 3.4677443e+00 4.9178815e+00 3.8042163e+00 3.4041321e+00 2.9939885e+00 3.9171296e+00 4.0866646e+00 3.9845548e+00 3.3671653e+00 4.1322520e+00 4.1520423e+00 3.9277271e+00 3.7880802e+00 3.5624203e+00 3.5967687e+00 3.0549169e+00 2.3749211e-01 9.2006504e-01 1.0428797e+00 4.2450569e-01 1.3899721e+00 1.6555341e+00 9.9973471e-01 7.5230154e-01 3.7960845e-01 9.9973471e-01 1.5086315e+00 2.6033464e-01 2.9724335e-01 2.8989712e+00 1.1937015e+00 5.7988427e-01 7.8318003e-01 1.2583645e+00 7.0463400e-01 1.0034646e+00 4.7680727e-01 5.2374483e-01 3.3114294e+00 2.8933417e+00 3.4177063e+00 3.4461307e+00 3.3255941e+00 2.8176969e+00 2.9380167e+00 2.9507452e+00 3.1524130e+00 2.7797208e+00 3.7960371e+00 2.6995806e+00 3.6095704e+00 2.9762135e+00 2.3753547e+00 3.0506196e+00 2.7121484e+00 2.6831858e+00 4.0299127e+00 2.9653560e+00 3.0144396e+00 2.8058449e+00 3.7011663e+00 2.9654419e+00 2.9344166e+00 3.0597490e+00 3.5085997e+00 3.5226725e+00 2.9395346e+00 2.6564538e+00 3.1076159e+00 3.0365838e+00 2.7379532e+00 3.4446760e+00 2.6796909e+00 2.6912430e+00 3.2129972e+00 3.7686900e+00 2.4108238e+00 3.0879511e+00 2.9762529e+00 2.8408428e+00 2.9254092e+00 3.1396427e+00 2.8384396e+00 2.3985415e+00 2.5881776e+00 2.8229620e+00 2.7289403e+00 2.6869860e+00 4.1910480e+00 3.6130663e+00 4.3602249e+00 3.6707779e+00 4.0745117e+00 4.9277939e+00 3.4934873e+00 4.4880495e+00 4.3514534e+00 4.6654573e+00 3.5594056e+00 3.8864758e+00 4.0498126e+00 3.8963526e+00 3.9502563e+00 3.8456535e+00 3.6509387e+00 5.0146974e+00 5.5101576e+00 4.0937915e+00 4.2345704e+00 3.4807992e+00 5.0960662e+00 3.6438388e+00 3.9190152e+00 4.1487738e+00 3.4580678e+00 3.2588014e+00 4.0378648e+00 4.0580582e+00 4.6285945e+00 4.9381887e+00 4.1199488e+00 3.3816601e+00 3.6553790e+00 4.9813108e+00 3.9405186e+00 3.5335600e+00 3.1869497e+00 4.0186781e+00 4.2240093e+00 4.0988575e+00 3.6130663e+00 4.2429720e+00 4.2712088e+00 4.0719125e+00 3.9975817e+00 3.7087600e+00 3.7375363e+00 3.2561951e+00 7.6824760e-01 8.5141186e-01 3.6086962e-01 1.6207126e+00 1.8802756e+00 7.9394533e-01 5.3286499e-01 4.3319335e-01 7.9394533e-01 1.3370188e+00 1.3340137e-01 3.6319073e-01 2.6778759e+00 1.0720705e+00 6.3173774e-01 1.0072799e+00 1.0495503e+00 9.3727156e-01 8.5893964e-01 7.0463400e-01 3.3395426e-01 3.3027871e+00 2.8812178e+00 3.3955887e+00 3.2888081e+00 3.2512254e+00 2.7283479e+00 2.9463809e+00 2.7764635e+00 3.0911130e+00 2.6620270e+00 3.6054231e+00 2.6430457e+00 3.4442793e+00 2.9123088e+00 2.2793286e+00 3.0205073e+00 2.6595069e+00 2.5635668e+00 3.8866925e+00 2.8178210e+00 3.0060120e+00 2.7101865e+00 3.5930006e+00 2.8829084e+00 2.8651003e+00 3.0121201e+00 3.4400598e+00 3.4869142e+00 2.8725792e+00 2.5068325e+00 2.9480926e+00 2.8720011e+00 2.6183827e+00 3.3619078e+00 2.6263990e+00 2.7176350e+00 3.1874455e+00 3.6289342e+00 2.3459758e+00 2.9476507e+00 2.8536577e+00 2.7917808e+00 2.7959976e+00 2.9585178e+00 2.7268209e+00 2.3347284e+00 2.5080346e+00 2.7508387e+00 2.5565749e+00 2.5881776e+00 4.2068537e+00 3.5342439e+00 4.3380559e+00 3.6271373e+00 4.0499864e+00 4.9127682e+00 3.3748745e+00 4.4574738e+00 4.2666131e+00 4.7143178e+00 3.5549829e+00 3.8149934e+00 4.0227420e+00 3.7946027e+00 3.8919837e+00 3.8432323e+00 3.6208873e+00 5.0849580e+00 5.4596452e+00 3.9569474e+00 4.2354064e+00 3.4126422e+00 5.0611947e+00 3.5638945e+00 3.9334644e+00 4.1519487e+00 3.3885059e+00 3.2191166e+00 3.9849073e+00 4.0334328e+00 4.5859724e+00 5.0075986e+00 4.0680600e+00 3.3134027e+00 3.5670952e+00 4.9632121e+00 3.9675062e+00 3.5176551e+00 3.1453377e+00 4.0038982e+00 4.2114761e+00 4.0820077e+00 3.5342439e+00 4.2453199e+00 4.2844704e+00 4.0426067e+00 3.8984747e+00 3.6765607e+00 3.7642725e+00 3.2184749e+00 2.6033464e-01 9.9962901e-01 2.1664244e+00 2.5030472e+00 3.6319073e-01 4.2737382e-01 1.2004100e+00 3.6319073e-01 6.1067563e-01 6.7030885e-01 8.0996690e-01 2.1006743e+00 4.0020411e-01 9.4057729e-01 1.4985933e+00 5.0731024e-01 1.4656715e+00 1.6562722e-01 1.3630799e+00 4.4417668e-01 3.6433721e+00 3.1333439e+00 3.6757970e+00 3.0281244e+00 3.3717708e+00 2.6624050e+00 3.1998148e+00 2.3430115e+00 3.2729115e+00 2.4219924e+00 3.1700354e+00 2.7177110e+00 3.2760900e+00 2.9826961e+00 2.2410752e+00 3.2981044e+00 2.6452183e+00 2.4901533e+00 3.7687554e+00 2.6225184e+00 3.1280668e+00 2.7635526e+00 3.5692464e+00 2.9177616e+00 3.0187150e+00 3.2354747e+00 3.6116754e+00 3.6909005e+00 2.9234404e+00 2.3731183e+00 2.6987010e+00 2.6178190e+00 2.5515904e+00 3.3308561e+00 2.5554841e+00 2.9570491e+00 3.4460544e+00 3.5549612e+00 2.3407691e+00 2.7326628e+00 2.6614903e+00 2.9042354e+00 2.6919656e+00 2.5430017e+00 2.6000033e+00 2.3578684e+00 2.4871797e+00 2.8599439e+00 2.2045434e+00 2.5287499e+00 4.3690091e+00 3.4628576e+00 4.5552847e+00 3.7077077e+00 4.1799642e+00 5.1678263e+00 3.0379779e+00 4.6720960e+00 4.3013447e+00 5.0550361e+00 3.7713495e+00 3.8605708e+00 4.2104897e+00 3.6525334e+00 3.8549710e+00 4.0229434e+00 3.7708197e+00 5.5011831e+00 5.6245097e+00 3.7945557e+00 4.4755001e+00 3.3306776e+00 5.2815051e+00 3.5995462e+00 4.1814664e+00 4.4417427e+00 3.4376057e+00 3.3113002e+00 4.0501276e+00 4.2847585e+00 4.7905454e+00 5.4600808e+00 4.1319681e+00 3.3795104e+00 3.5192584e+00 5.2359042e+00 4.1708373e+00 3.6809073e+00 3.2190305e+00 4.2365575e+00 4.3973146e+00 4.3149219e+00 3.4628576e+00 4.4657183e+00 4.5132257e+00 4.2167698e+00 3.8749352e+00 3.8293474e+00 3.9628758e+00 3.2623926e+00 1.0371214e+00 2.3606689e+00 2.6764689e+00 1.8699153e-01 4.0363332e-01 1.2627589e+00 1.8699153e-01 5.6164055e-01 7.8305765e-01 9.7747632e-01 1.8924893e+00 5.6164055e-01 1.0881632e+00 1.6837963e+00 2.8845946e-01 1.6545637e+00 3.5266705e-01 1.5108472e+00 5.3286499e-01 3.5596461e+00 3.0642731e+00 3.5820652e+00 2.8366432e+00 3.2382208e+00 2.5375138e+00 3.1537738e+00 2.1559444e+00 3.1474432e+00 2.2926143e+00 2.9585178e+00 2.6250629e+00 3.0590120e+00 2.8699760e+00 2.1233327e+00 3.2024265e+00 2.5670381e+00 2.3272067e+00 3.5735096e+00 2.4368431e+00 3.0826231e+00 2.6212838e+00 3.4052824e+00 2.7833861e+00 2.8923043e+00 3.1255601e+00 3.4747564e+00 3.5908785e+00 2.8135312e+00 2.1839196e+00 2.5032729e+00 2.4158569e+00 2.3928312e+00 3.2017644e+00 2.4862299e+00 2.9403226e+00 3.3545989e+00 3.3587820e+00 2.2520446e+00 2.5607059e+00 2.5059290e+00 2.8073565e+00 2.5209771e+00 2.3430115e+00 2.4562939e+00 2.2633781e+00 2.3755041e+00 2.7368591e+00 2.0165442e+00 2.3969046e+00 4.3354025e+00 3.3475977e+00 4.4615703e+00 3.6095772e+00 4.0990362e+00 5.0710534e+00 2.9144612e+00 4.5627576e+00 4.1522674e+00 5.0316497e+00 3.7102339e+00 3.7341705e+00 4.1195531e+00 3.5174471e+00 3.7659275e+00 3.9693828e+00 3.6809073e+00 5.4858042e+00 5.4945039e+00 3.6088006e+00 4.4109170e+00 3.2362256e+00 5.1634795e+00 3.4678413e+00 4.1322470e+00 4.3666536e+00 3.3199204e+00 3.2266664e+00 3.9433408e+00 4.1815045e+00 4.6694810e+00 5.4392260e+00 4.0273519e+00 3.2552513e+00 3.3773249e+00 5.1375752e+00 4.1484621e+00 3.6075786e+00 3.1365574e+00 4.1554935e+00 4.3260165e+00 4.2353581e+00 3.3475977e+00 4.4043042e+00 4.4676627e+00 4.1295047e+00 3.7244531e+00 3.7408419e+00 3.9430201e+00 3.1856251e+00 1.6790448e+00 1.8683303e+00 9.9891776e-01 7.3735391e-01 3.8639663e-01 9.9891776e-01 1.5462701e+00 4.4713936e-01 5.6262711e-01 2.7653818e+00 1.3238834e+00 5.9868400e-01 1.0167074e+00 1.1817121e+00 1.0267382e+00 1.1036371e+00 7.4965096e-01 5.9868400e-01 2.9920629e+00 2.5767485e+00 3.0904477e+00 3.1383073e+00 2.9738737e+00 2.5155127e+00 2.6450302e+00 2.7097016e+00 2.8120208e+00 2.4963677e+00 3.5442384e+00 2.3737852e+00 3.2878773e+00 2.6552959e+00 2.0482711e+00 2.7132601e+00 2.4244329e+00 2.3725931e+00 3.6825624e+00 2.6567419e+00 2.7277627e+00 2.4551607e+00 3.3586468e+00 2.6490703e+00 2.5878996e+00 2.7146857e+00 3.1604264e+00 3.1862677e+00 2.6121387e+00 2.3320406e+00 2.8060955e+00 2.7397840e+00 2.4059801e+00 3.1251810e+00 2.4123722e+00 2.4266062e+00 2.8827369e+00 3.4219145e+00 2.1146056e+00 2.7787333e+00 2.6868306e+00 2.5237903e+00 2.5969195e+00 2.8858666e+00 2.5292454e+00 2.1030528e+00 2.2789104e+00 2.4843930e+00 2.4502513e+00 2.3681813e+00 3.9129285e+00 3.2963379e+00 4.0307050e+00 3.3579713e+00 3.7573925e+00 4.6072225e+00 3.2350676e+00 4.1666050e+00 4.0096938e+00 4.3939440e+00 3.2458961e+00 3.5448948e+00 3.7163166e+00 3.5735211e+00 3.6303034e+00 3.5366397e+00 3.3347301e+00 4.7764597e+00 5.1656525e+00 3.7678733e+00 3.9190152e+00 3.1752055e+00 4.7655871e+00 3.2963860e+00 3.6257668e+00 3.8480918e+00 3.1163356e+00 2.9401017e+00 3.7060704e+00 3.7400429e+00 4.2905130e+00 4.6982735e+00 3.7862785e+00 3.0555922e+00 3.3519252e+00 4.6433941e+00 3.6672706e+00 3.2313825e+00 2.8704346e+00 3.6888814e+00 3.9001229e+00 3.7578379e+00 3.2963379e+00 3.9355102e+00 3.9693842e+00 3.7298088e+00 3.6452459e+00 3.3776637e+00 3.4666091e+00 2.9570067e+00 4.5257749e-01 2.3345854e+00 2.1006743e+00 1.4408765e+00 2.3345854e+00 2.7201861e+00 1.6242170e+00 1.4334280e+00 4.2461520e+00 2.2960397e+00 1.5510150e+00 8.3619405e-01 2.5963945e+00 7.1671402e-01 2.2031378e+00 9.3957399e-01 1.8662528e+00 3.9018608e+00 3.5587525e+00 4.0758181e+00 4.6738622e+00 4.2315488e+00 3.8362958e+00 3.5101695e+00 4.2349456e+00 4.0096351e+00 3.8957954e+00 5.1177048e+00 3.5857491e+00 4.8511271e+00 3.8770675e+00 3.4324591e+00 3.7687554e+00 3.5952204e+00 3.8095276e+00 5.1880951e+00 4.1733990e+00 3.6719420e+00 3.8275992e+00 4.7391892e+00 3.9417326e+00 3.8406076e+00 3.8597390e+00 4.3729123e+00 4.2482658e+00 3.8534412e+00 3.8740219e+00 4.3496532e+00 4.2951960e+00 3.8572117e+00 4.4028225e+00 3.5707989e+00 3.2084035e+00 3.9057558e+00 4.9165227e+00 3.3635180e+00 4.2694314e+00 4.1082916e+00 3.6886188e+00 4.0716087e+00 4.4411156e+00 3.9335446e+00 3.3496034e+00 3.5830335e+00 3.7561390e+00 4.0088699e+00 3.7413425e+00 4.6436290e+00 4.5471223e+00 4.9787007e+00 4.4481204e+00 4.7341193e+00 5.4826137e+00 4.5863292e+00 5.1433212e+00 5.2725182e+00 4.8836508e+00 4.1393671e+00 4.7672599e+00 4.7092337e+00 4.9106562e+00 4.7707455e+00 4.3996661e+00 4.3591552e+00 5.0844032e+00 6.2257171e+00 5.2378683e+00 4.7433741e+00 4.3742533e+00 5.7435198e+00 4.5678545e+00 4.3840310e+00 4.6485090e+00 4.3482964e+00 4.0364176e+00 4.8328894e+00 4.6980887e+00 5.3298852e+00 5.0020990e+00 4.9051796e+00 4.2757519e+00 4.6314634e+00 5.5369700e+00 4.3420582e+00 4.1857666e+00 3.9786340e+00 4.6138259e+00 4.8044656e+00 4.6911542e+00 4.5471223e+00 4.7508760e+00 4.7161034e+00 4.7355095e+00 4.9879154e+00 4.4154797e+00 4.1545903e+00 4.0343137e+00 2.6422396e+00 2.3867296e+00 1.6154069e+00 2.6422396e+00 3.0703842e+00 1.9080710e+00 1.7295385e+00 4.5475791e+00 2.6621202e+00 1.8051284e+00 1.1105716e+00 2.8967543e+00 1.0474897e+00 2.5495727e+00 1.1795364e+00 2.1617152e+00 3.8171826e+00 3.5339654e+00 4.0163479e+00 4.8425911e+00 4.2514283e+00 3.9557653e+00 3.4792404e+00 4.4740529e+00 4.0150475e+00 4.0717494e+00 5.3501548e+00 3.6486698e+00 4.9910943e+00 3.9350582e+00 3.5547141e+00 3.7282030e+00 3.6962934e+00 3.9420318e+00 5.2895497e+00 4.3341610e+00 3.6960949e+00 3.8986276e+00 4.8106103e+00 4.0206152e+00 3.8664495e+00 3.8454452e+00 4.3675713e+00 4.2173036e+00 3.9169317e+00 4.0237417e+00 4.5248905e+00 4.4756871e+00 3.9778974e+00 4.4827624e+00 3.6962934e+00 3.1970228e+00 3.8646917e+00 5.0103465e+00 3.4775294e+00 4.4295579e+00 4.2690345e+00 3.7344524e+00 4.1995700e+00 4.6716989e+00 4.0716455e+00 3.4573201e+00 3.6936958e+00 3.8056210e+00 4.2211876e+00 3.8604223e+00 4.5958210e+00 4.6323449e+00 4.9087471e+00 4.4703758e+00 4.7121623e+00 5.3828288e+00 4.7798674e+00 5.0815530e+00 5.2996504e+00 4.7231435e+00 4.0911974e+00 4.7955081e+00 4.6606898e+00 5.0156099e+00 4.8233009e+00 4.3540706e+00 4.3488974e+00 4.8785742e+00 6.1614901e+00 5.3577416e+00 4.6571075e+00 4.4651793e+00 5.6630236e+00 4.6077729e+00 4.3065165e+00 4.5525984e+00 4.3873290e+00 4.0638414e+00 4.8447047e+00 4.6322938e+00 5.2676175e+00 4.7796156e+00 4.9133278e+00 4.3194697e+00 4.7202167e+00 5.4208419e+00 4.2794874e+00 4.1728215e+00 4.0165591e+00 4.5422714e+00 4.7447407e+00 4.6112705e+00 4.6323449e+00 4.6754921e+00 4.6293227e+00 4.6871898e+00 5.0428586e+00 4.3953592e+00 4.1024574e+00 4.0848110e+00 3.3742167e-01 1.1857824e+00 0.0000000e+00 6.6918102e-01 7.4445830e-01 9.7322023e-01 1.9284841e+00 6.6918102e-01 1.1393372e+00 1.6942803e+00 3.7371902e-01 1.6386105e+00 4.5257749e-01 1.4715172e+00 4.9772204e-01 3.5602797e+00 3.0968979e+00 3.5933352e+00 2.8998722e+00 3.2656298e+00 2.6029746e+00 3.1974447e+00 2.2445103e+00 3.1601923e+00 2.3946216e+00 3.0209665e+00 2.6867317e+00 3.0775658e+00 2.9161244e+00 2.1946278e+00 3.2137635e+00 2.6502891e+00 2.3652711e+00 3.6096140e+00 2.4893065e+00 3.1578003e+00 2.6568473e+00 3.4426472e+00 2.8187901e+00 2.9128857e+00 3.1418202e+00 3.4847097e+00 3.6205958e+00 2.8694312e+00 2.2223283e+00 2.5588203e+00 2.4651138e+00 2.4413293e+00 3.2621861e+00 2.5834128e+00 2.9995857e+00 3.3733791e+00 3.3817876e+00 2.3263008e+00 2.6305758e+00 2.5756071e+00 2.8533918e+00 2.5683063e+00 2.4187322e+00 2.5258216e+00 2.3250308e+00 2.4413618e+00 2.7691536e+00 2.1006933e+00 2.4608850e+00 4.4119896e+00 3.4290419e+00 4.4942656e+00 3.6650933e+00 4.1590662e+00 5.0899438e+00 3.0333619e+00 4.5799469e+00 4.1882413e+00 5.0726081e+00 3.7613721e+00 3.7859992e+00 4.1623715e+00 3.6029758e+00 3.8608065e+00 4.0352348e+00 3.7266849e+00 5.5043247e+00 5.5172732e+00 3.6569442e+00 4.4568115e+00 3.3324016e+00 5.1765224e+00 3.5192065e+00 4.1799642e+00 4.3857400e+00 3.3769078e+00 3.2906843e+00 4.0034548e+00 4.1917183e+00 4.6854597e+00 5.4444866e+00 4.0904298e+00 3.2962678e+00 3.4250783e+00 5.1569386e+00 4.2213314e+00 3.6582637e+00 3.2059261e+00 4.1937040e+00 4.3826532e+00 4.2786320e+00 3.4290419e+00 4.4549850e+00 4.5270304e+00 4.1816268e+00 3.7777251e+00 3.7924144e+00 4.0173731e+00 3.2613828e+00 9.2006504e-01 3.3742167e-01 8.6080744e-01 5.0621589e-01 7.0646671e-01 2.1664244e+00 7.2679299e-01 8.9712099e-01 1.4681660e+00 5.4873947e-01 1.4074199e+00 4.9617437e-01 1.2206236e+00 2.5698045e-01 3.4986942e+00 3.0427234e+00 3.5520531e+00 3.0444864e+00 3.2783159e+00 2.6723101e+00 3.1333743e+00 2.4360210e+00 3.1627463e+00 2.4904253e+00 3.2338077e+00 2.6808015e+00 3.2240677e+00 2.9412073e+00 2.2206950e+00 3.1669559e+00 2.6716144e+00 2.4623998e+00 3.7173707e+00 2.6198784e+00 3.1208539e+00 2.6854361e+00 3.5171200e+00 2.8753360e+00 2.9157449e+00 3.1157529e+00 3.4945103e+00 3.5956983e+00 2.8873581e+00 2.3297122e+00 2.7075732e+00 2.6220688e+00 2.5143128e+00 3.3225169e+00 2.6164571e+00 2.9213819e+00 3.3323415e+00 3.4842326e+00 2.3487772e+00 2.7507828e+00 2.6991998e+00 2.8571158e+00 2.6614903e+00 2.6122501e+00 2.6121387e+00 2.3527202e+00 2.4822998e+00 2.7826627e+00 2.2477567e+00 2.5188545e+00 4.3590737e+00 3.4800724e+00 4.4652192e+00 3.6820633e+00 4.1423404e+00 5.0632361e+00 3.1594575e+00 4.5764429e+00 4.2442248e+00 4.9703970e+00 3.7054123e+00 3.8144340e+00 4.1322520e+00 3.6772717e+00 3.8704422e+00 3.9786899e+00 3.7187193e+00 5.3973273e+00 5.5276243e+00 3.7838435e+00 4.3978718e+00 3.3669786e+00 5.1732410e+00 3.5478651e+00 4.1195682e+00 4.3422158e+00 3.3925731e+00 3.2818178e+00 4.0157845e+00 4.1753467e+00 4.6824968e+00 5.3318394e+00 4.0983108e+00 3.3321312e+00 3.5171976e+00 5.1116177e+00 4.1480571e+00 3.6395566e+00 3.1983718e+00 4.1451783e+00 4.3355345e+00 4.2161052e+00 3.4800724e+00 4.4037157e+00 4.4559384e+00 4.1399085e+00 3.8303307e+00 3.7678377e+00 3.9434740e+00 3.2672961e+00 1.1857824e+00 1.7590894e+00 5.4715569e-01 6.1787077e-01 3.0224093e+00 1.4977817e+00 8.1744862e-01 9.4676850e-01 1.4369223e+00 8.6079202e-01 1.2896554e+00 5.3286499e-01 7.6195008e-01 3.1536923e+00 2.8019556e+00 3.2823965e+00 3.4754319e+00 3.2348803e+00 2.8339836e+00 2.8678686e+00 3.0569237e+00 3.0422163e+00 2.8599505e+00 3.8711727e+00 2.6769819e+00 3.5769114e+00 2.9369339e+00 2.3887701e+00 2.9172679e+00 2.7450499e+00 2.6744413e+00 3.9868196e+00 2.9825819e+00 3.0070640e+00 2.7478281e+00 3.6492544e+00 2.9260177e+00 2.8398296e+00 2.9417237e+00 3.3879693e+00 3.4191352e+00 2.9103957e+00 2.6495864e+00 3.1359829e+00 3.0635119e+00 2.7246726e+00 3.4310966e+00 2.7450499e+00 2.6593850e+00 3.0929063e+00 3.7090709e+00 2.4372581e+00 3.1206171e+00 3.0186562e+00 2.7973689e+00 2.9151879e+00 3.2261028e+00 2.8631702e+00 2.4096686e+00 2.5984928e+00 2.7564382e+00 2.8056103e+00 2.6945402e+00 4.1622883e+00 3.6243461e+00 4.2495237e+00 3.6308369e+00 4.0200378e+00 4.7940036e+00 3.6050689e+00 4.3664479e+00 4.2800934e+00 4.5553898e+00 3.4840330e+00 3.8323637e+00 3.9571437e+00 3.9163572e+00 3.9605759e+00 3.7909588e+00 3.5846709e+00 4.8756388e+00 5.3862972e+00 4.0807979e+00 4.1385728e+00 3.5138167e+00 4.9592897e+00 3.5910983e+00 3.8379532e+00 4.0230039e+00 3.4134019e+00 3.2269518e+00 3.9906410e+00 3.9261146e+00 4.4982182e+00 4.7746016e+00 4.0736767e+00 3.3286256e+00 3.6393910e+00 4.8333249e+00 3.9033387e+00 3.4776516e+00 3.1661860e+00 3.9124707e+00 4.1473618e+00 3.9899548e+00 3.6243461e+00 4.1607944e+00 4.1969547e+00 3.9859042e+00 3.9511939e+00 3.6382505e+00 3.7066628e+00 3.2552942e+00 6.6918102e-01 7.4445830e-01 9.7322023e-01 1.9284841e+00 6.6918102e-01 1.1393372e+00 1.6942803e+00 3.7371902e-01 1.6386105e+00 4.5257749e-01 1.4715172e+00 4.9772204e-01 3.5602797e+00 3.0968979e+00 3.5933352e+00 2.8998722e+00 3.2656298e+00 2.6029746e+00 3.1974447e+00 2.2445103e+00 3.1601923e+00 2.3946216e+00 3.0209665e+00 2.6867317e+00 3.0775658e+00 2.9161244e+00 2.1946278e+00 3.2137635e+00 2.6502891e+00 2.3652711e+00 3.6096140e+00 2.4893065e+00 3.1578003e+00 2.6568473e+00 3.4426472e+00 2.8187901e+00 2.9128857e+00 3.1418202e+00 3.4847097e+00 3.6205958e+00 2.8694312e+00 2.2223283e+00 2.5588203e+00 2.4651138e+00 2.4413293e+00 3.2621861e+00 2.5834128e+00 2.9995857e+00 3.3733791e+00 3.3817876e+00 2.3263008e+00 2.6305758e+00 2.5756071e+00 2.8533918e+00 2.5683063e+00 2.4187322e+00 2.5258216e+00 2.3250308e+00 2.4413618e+00 2.7691536e+00 2.1006933e+00 2.4608850e+00 4.4119896e+00 3.4290419e+00 4.4942656e+00 3.6650933e+00 4.1590662e+00 5.0899438e+00 3.0333619e+00 4.5799469e+00 4.1882413e+00 5.0726081e+00 3.7613721e+00 3.7859992e+00 4.1623715e+00 3.6029758e+00 3.8608065e+00 4.0352348e+00 3.7266849e+00 5.5043247e+00 5.5172732e+00 3.6569442e+00 4.4568115e+00 3.3324016e+00 5.1765224e+00 3.5192065e+00 4.1799642e+00 4.3857400e+00 3.3769078e+00 3.2906843e+00 4.0034548e+00 4.1917183e+00 4.6854597e+00 5.4444866e+00 4.0904298e+00 3.2962678e+00 3.4250783e+00 5.1569386e+00 4.2213314e+00 3.6582637e+00 3.2059261e+00 4.1937040e+00 4.3826532e+00 4.2786320e+00 3.4290419e+00 4.4549850e+00 4.5270304e+00 4.1816268e+00 3.7777251e+00 3.7924144e+00 4.0173731e+00 3.2613828e+00 1.2563834e+00 1.3681903e+00 1.6242170e+00 4.6126066e-01 1.4691503e+00 2.0743925e+00 5.0370871e-01 2.0365895e+00 5.2374483e-01 1.9494772e+00 1.0034646e+00 4.0320101e+00 3.4981749e+00 4.0289839e+00 2.9648238e+00 3.6116412e+00 2.8362334e+00 3.5807825e+00 2.1594400e+00 3.5619276e+00 2.4608850e+00 2.9150653e+00 2.9806848e+00 3.2524084e+00 3.2332049e+00 2.4351674e+00 3.6506644e+00 2.8794131e+00 2.6371069e+00 3.7842148e+00 2.6442387e+00 3.4386758e+00 2.9743384e+00 3.6958304e+00 3.1396988e+00 3.2947222e+00 3.5521670e+00 3.8756118e+00 3.9969338e+00 3.1587328e+00 2.4432066e+00 2.6604221e+00 2.5745994e+00 2.6880360e+00 3.4951118e+00 2.7657429e+00 3.3524671e+00 3.7924883e+00 3.6104716e+00 2.5876530e+00 2.7410968e+00 2.7238850e+00 3.1914275e+00 2.7871336e+00 2.3484202e+00 2.7125180e+00 2.6235600e+00 2.7012637e+00 3.1219910e+00 2.0888843e+00 2.6969064e+00 4.6820911e+00 3.5968849e+00 4.8607426e+00 3.9563487e+00 4.4501699e+00 5.4913616e+00 2.9743611e+00 4.9743359e+00 4.4659463e+00 5.4618868e+00 4.1043393e+00 4.0513907e+00 4.5016468e+00 3.7087600e+00 4.0024694e+00 4.3310092e+00 4.0611789e+00 5.9599083e+00 5.8632763e+00 3.7995759e+00 4.8081465e+00 3.4697005e+00 5.5699347e+00 3.7817852e+00 4.5398887e+00 4.8101536e+00 3.6425657e+00 3.5739550e+00 4.2642554e+00 4.6155808e+00 5.0696209e+00 5.9318766e+00 4.3420618e+00 3.6079861e+00 3.6711700e+00 5.5546786e+00 4.5127918e+00 3.9935485e+00 3.4733020e+00 4.5569739e+00 4.6922818e+00 4.6236700e+00 3.5968849e+00 4.7939394e+00 4.8471780e+00 4.4913725e+00 3.9942507e+00 4.1085491e+00 4.3067090e+00 3.5093006e+00 3.1271814e-01 2.6440626e+00 9.6964683e-01 5.8796666e-01 9.8545402e-01 1.0013399e+00 9.2426065e-01 7.6195008e-01 7.3283576e-01 2.6643250e-01 3.3524935e+00 2.9103383e+00 3.4378505e+00 3.2794093e+00 3.2805279e+00 2.7218307e+00 2.9677934e+00 2.7417113e+00 3.1265865e+00 2.6350666e+00 3.5810092e+00 2.6509959e+00 3.4564670e+00 2.9240118e+00 2.2778214e+00 3.0636652e+00 2.6473174e+00 2.5673371e+00 3.9008585e+00 2.8131786e+00 3.0066012e+00 2.7310040e+00 3.6088006e+00 2.8947302e+00 2.8966017e+00 3.0506196e+00 3.4785143e+00 3.5188926e+00 2.8816131e+00 2.5125550e+00 2.9397900e+00 2.8645995e+00 2.6245231e+00 3.3639057e+00 2.6028733e+00 2.7271822e+00 3.2253861e+00 3.6489825e+00 2.3376510e+00 2.9371604e+00 2.8382974e+00 2.8051321e+00 2.8006021e+00 2.9309089e+00 2.7184808e+00 2.3312464e+00 2.5047936e+00 2.7731354e+00 2.5341497e+00 2.5862794e+00 4.2119757e+00 3.5278864e+00 4.3705395e+00 3.6366115e+00 4.0640736e+00 4.9516709e+00 3.3348040e+00 4.4927271e+00 4.2867968e+00 4.7459453e+00 3.5773144e+00 3.8303307e+00 4.0501276e+00 3.7856794e+00 3.8862115e+00 3.8584574e+00 3.6392860e+00 5.1247727e+00 5.4955777e+00 3.9594564e+00 4.2633399e+00 3.3993739e+00 5.1011948e+00 3.5798230e+00 3.9561606e+00 4.1885925e+00 3.4019138e+00 3.2277183e+00 3.9971830e+00 4.0727212e+00 4.6247900e+00 5.0557045e+00 4.0800856e+00 3.3285999e+00 3.5685643e+00 5.0078455e+00 3.9761694e+00 3.5324648e+00 3.1505332e+00 4.0358238e+00 4.2334406e+00 4.1156691e+00 3.5278864e+00 4.2682695e+00 4.3053166e+00 4.0686429e+00 3.9122205e+00 3.6972894e+00 3.7712394e+00 3.2160302e+00 2.8326674e+00 1.0103954e+00 4.2829723e-01 7.9126749e-01 1.1795364e+00 7.3442235e-01 8.5582452e-01 6.1158310e-01 4.8284931e-01 3.4789406e+00 3.0164286e+00 3.5708825e+00 3.4760111e+00 3.4435701e+00 2.8856827e+00 3.0483404e+00 2.9286177e+00 3.2959553e+00 2.7769569e+00 3.7899650e+00 2.7721706e+00 3.6919547e+00 3.0773843e+00 2.4199353e+00 3.1984671e+00 2.7597977e+00 2.7743820e+00 4.1049898e+00 3.0189963e+00 3.0754044e+00 2.9033794e+00 3.7972498e+00 3.0781443e+00 3.0628742e+00 3.1980682e+00 3.6529321e+00 3.6479042e+00 3.0224061e+00 2.7237896e+00 3.1475538e+00 3.0809334e+00 2.8106441e+00 3.5217353e+00 2.7064382e+00 2.7753422e+00 3.3543209e+00 3.8636687e+00 2.4678110e+00 3.1212622e+00 3.0250044e+00 2.9444840e+00 2.9956969e+00 3.1281937e+00 2.8892226e+00 2.4772050e+00 2.6547819e+00 2.9364676e+00 2.7130802e+00 2.7488632e+00 4.2524457e+00 3.6566905e+00 4.4856620e+00 3.7659004e+00 4.1610154e+00 5.0768465e+00 3.4623926e+00 4.6393183e+00 4.4611186e+00 4.7773180e+00 3.6552032e+00 3.9746118e+00 4.1574253e+00 3.9234139e+00 3.9686223e+00 3.9172103e+00 3.7603950e+00 5.1648090e+00 5.6463490e+00 4.1614237e+00 4.3393711e+00 3.5009132e+00 5.2503936e+00 3.7276020e+00 4.0260707e+00 4.3007127e+00 3.5361708e+00 3.3347521e+00 4.1191095e+00 4.2183675e+00 4.7752353e+00 5.1049558e+00 4.1955154e+00 3.4902431e+00 3.7536489e+00 5.1215318e+00 4.0036288e+00 3.6385337e+00 3.2536517e+00 4.1326097e+00 4.3100988e+00 4.1978681e+00 3.6566905e+00 4.3438151e+00 4.3538983e+00 4.1591001e+00 4.0714399e+00 3.8024850e+00 3.7974783e+00 3.3185266e+00 2.0833080e+00 2.8648636e+00 3.5532593e+00 1.6555341e+00 3.5410343e+00 2.0840787e+00 3.3747131e+00 2.3883072e+00 4.3833871e+00 3.9159762e+00 4.2941647e+00 2.3488350e+00 3.6240540e+00 2.9044888e+00 4.0815608e+00 1.5532921e+00 3.6825697e+00 2.4113529e+00 1.7998094e+00 3.2616916e+00 2.5529439e+00 3.3821744e+00 2.6637769e+00 3.9531209e+00 3.1831859e+00 2.5836708e+00 3.1669559e+00 2.2907828e+00 3.8684560e+00 3.0202406e+00 3.4019580e+00 3.1886068e+00 3.4332960e+00 3.7685816e+00 3.8803374e+00 4.1746389e+00 3.3102735e+00 2.2304221e+00 2.1489616e+00 2.0501444e+00 2.6225712e+00 3.4164974e+00 3.0901976e+00 3.9885258e+00 4.0802502e+00 3.0869095e+00 2.9336463e+00 2.3936974e+00 2.5327316e+00 3.4518638e+00 2.5837552e+00 1.5782205e+00 2.6521862e+00 2.9662386e+00 2.9040188e+00 3.2768109e+00 1.6628177e+00 2.7685987e+00 5.0448046e+00 3.5141919e+00 4.9824611e+00 4.0549341e+00 4.5981277e+00 5.5863508e+00 2.6647036e+00 5.0241554e+00 4.1998979e+00 5.9440534e+00 4.4432394e+00 3.9560997e+00 4.6422438e+00 3.4164839e+00 4.0005863e+00 4.6454886e+00 4.2390210e+00 6.5166388e+00 5.6880370e+00 3.1944389e+00 5.0789131e+00 3.4956324e+00 5.5310286e+00 3.6881384e+00 4.9152167e+00 5.0890922e+00 3.6527501e+00 3.7902440e+00 4.2540354e+00 4.7585941e+00 5.0389487e+00 6.4918086e+00 4.3280601e+00 3.6284588e+00 3.4969965e+00 5.6399353e+00 4.9671290e+00 4.2659568e+00 3.6994310e+00 4.7714894e+00 4.8963175e+00 4.8281202e+00 3.5141919e+00 5.0683437e+00 5.1871515e+00 4.6280145e+00 3.7055141e+00 4.2764028e+00 4.7873094e+00 3.7371542e+00 1.1433971e+00 1.6774310e+00 6.8299624e-01 1.6304499e+00 2.4808718e-01 1.5886765e+00 7.6250797e-01 4.0055392e+00 3.4676312e+00 4.0289839e+00 3.2391776e+00 3.6989507e+00 2.9466089e+00 3.5208636e+00 2.4804256e+00 3.6211670e+00 2.6281173e+00 3.2921089e+00 3.0161637e+00 3.5345457e+00 3.2983536e+00 2.5210242e+00 3.6506644e+00 2.9161244e+00 2.7938108e+00 4.0292846e+00 2.8755116e+00 3.4075988e+00 3.0797683e+00 3.8646774e+00 3.2397520e+00 3.3586779e+00 3.5819899e+00 3.9571013e+00 4.0234614e+00 3.2253861e+00 2.6519927e+00 2.9269738e+00 2.8491914e+00 2.8419330e+00 3.6148101e+00 2.8039428e+00 3.2558795e+00 3.7924883e+00 3.8389577e+00 2.6284424e+00 2.9648238e+00 2.9126202e+00 3.2245885e+00 2.9718548e+00 2.6864788e+00 2.8651003e+00 2.6637996e+00 2.7789114e+00 3.1894122e+00 2.3748697e+00 2.8127546e+00 4.6364269e+00 3.7133040e+00 4.8825792e+00 4.0097653e+00 4.4740109e+00 5.5106999e+00 3.1817279e+00 5.0169254e+00 4.6066522e+00 5.3636182e+00 4.0783379e+00 4.1550948e+00 4.5252166e+00 3.8770438e+00 4.0814269e+00 4.3063766e+00 4.0872895e+00 5.8336247e+00 5.9533030e+00 4.0437148e+00 4.7859703e+00 3.5604924e+00 5.6269403e+00 3.8926783e+00 4.4927794e+00 4.7879866e+00 3.7291513e+00 3.6035976e+00 4.3384511e+00 4.6385717e+00 5.1321867e+00 5.8049832e+00 4.4149501e+00 3.6953819e+00 3.8133051e+00 5.5737973e+00 4.4415093e+00 3.9935485e+00 3.5037963e+00 4.5569739e+00 4.6922818e+00 4.6236700e+00 3.7133040e+00 4.7716971e+00 4.8030835e+00 4.5149959e+00 4.1509766e+00 4.1343605e+00 4.2319568e+00 3.5394847e+00 7.6869104e-01 1.2471855e+00 8.7636491e-01 9.9981032e-01 7.8863556e-01 7.0733904e-01 3.2400577e+00 2.7256768e+00 3.3173156e+00 3.2734582e+00 3.1889456e+00 2.6198618e+00 2.7351941e+00 2.7665224e+00 3.0627699e+00 2.5021229e+00 3.6608924e+00 2.4643905e+00 3.5457670e+00 2.8045035e+00 2.1368327e+00 2.9466857e+00 2.4386380e+00 2.5729082e+00 3.8963334e+00 2.8235662e+00 2.7242811e+00 2.6575342e+00 3.5598437e+00 2.8418228e+00 2.8206835e+00 2.9462527e+00 3.4233881e+00 3.3667899e+00 2.7322904e+00 2.5411273e+00 2.9638750e+00 2.9140871e+00 2.5797070e+00 3.2425969e+00 2.3780833e+00 2.4351544e+00 3.0892387e+00 3.6720185e+00 2.1688001e+00 2.8939857e+00 2.7945402e+00 2.6616170e+00 2.7767058e+00 2.9769851e+00 2.6347557e+00 2.1986118e+00 2.3753308e+00 2.6828901e+00 2.5283291e+00 2.4839186e+00 3.8851614e+00 3.3427747e+00 4.1909067e+00 3.4628626e+00 3.8305140e+00 4.8043725e+00 3.1800308e+00 4.3815267e+00 4.2038608e+00 4.4513681e+00 3.3256952e+00 3.6826282e+00 3.8475722e+00 3.6210755e+00 3.6107649e+00 3.5632387e+00 3.4596612e+00 4.8847297e+00 5.3781989e+00 3.9435460e+00 4.0131264e+00 3.1614358e+00 4.9957986e+00 3.4408340e+00 3.7000441e+00 4.0284550e+00 3.2354442e+00 3.0107962e+00 3.8036057e+00 3.9713385e+00 4.5180638e+00 4.8486868e+00 3.8729425e+00 3.2243808e+00 3.5087561e+00 4.8402519e+00 3.6359784e+00 3.3268022e+00 2.9240118e+00 3.8232660e+00 3.9709252e+00 3.8746323e+00 3.3427747e+00 4.0131316e+00 4.0031779e+00 3.8300809e+00 3.7945557e+00 3.4841580e+00 3.4283673e+00 2.9863682e+00 1.9060194e+00 3.1239235e-01 1.5583422e+00 4.8124784e-01 1.2220171e+00 3.3785962e+00 2.9374280e+00 3.5071305e+00 3.8740792e+00 3.5491790e+00 3.0669573e+00 2.9018296e+00 3.4251046e+00 3.3648459e+00 3.0744865e+00 4.3230411e+00 2.8485664e+00 4.1027662e+00 3.1626116e+00 2.6442554e+00 3.1724373e+00 2.8315630e+00 3.0534300e+00 4.4306138e+00 3.3882074e+00 2.9857887e+00 3.0959220e+00 4.0072094e+00 3.2240677e+00 3.1644964e+00 3.2264712e+00 3.7352584e+00 3.6230125e+00 3.1206853e+00 3.1023948e+00 3.5580276e+00 3.5096299e+00 3.0877778e+00 3.6577352e+00 2.7900553e+00 2.5838366e+00 3.3069107e+00 4.1792641e+00 2.5911812e+00 3.4684046e+00 3.3165070e+00 2.9868392e+00 3.2999163e+00 3.6373219e+00 3.1449351e+00 2.5937039e+00 2.8148578e+00 3.0518873e+00 3.1967428e+00 2.9647081e+00 4.0498613e+00 3.7819451e+00 4.3976398e+00 3.7644678e+00 4.0879497e+00 4.9574979e+00 3.7577430e+00 4.5772252e+00 4.5796941e+00 4.4589650e+00 3.5295907e+00 4.0592075e+00 4.0919364e+00 4.1226882e+00 4.0237849e+00 3.7803562e+00 3.7136035e+00 4.7772875e+00 5.6344258e+00 4.4679358e+00 4.1805116e+00 3.6013971e+00 5.1936459e+00 3.8460802e+00 3.8293150e+00 4.1365715e+00 3.6263469e+00 3.3344860e+00 4.1404384e+00 4.1465376e+00 4.7500857e+00 4.7258629e+00 4.2123837e+00 3.5757376e+00 3.9028467e+00 5.0127222e+00 3.7704782e+00 3.5495399e+00 3.2637691e+00 4.0284560e+00 4.1958516e+00 4.1011034e+00 3.7819451e+00 4.1793948e+00 4.1561376e+00 4.1029254e+00 4.2472742e+00 3.7624633e+00 3.5705606e+00 3.3154318e+00 1.8882412e+00 5.3690447e-01 1.7295385e+00 7.4445830e-01 3.5842571e+00 3.0831073e+00 3.5905412e+00 2.6850208e+00 3.1920496e+00 2.4895611e+00 3.1874455e+00 1.9825106e+00 3.1280291e+00 2.1902526e+00 2.7631963e+00 2.5991209e+00 2.9183874e+00 2.8448970e+00 2.0635463e+00 3.2072460e+00 2.5480786e+00 2.2627580e+00 3.4383056e+00 2.3172372e+00 3.0909340e+00 2.5623863e+00 3.3194064e+00 2.7506751e+00 2.8644451e+00 3.1134606e+00 3.4405052e+00 3.5767292e+00 2.7771563e+00 2.0712834e+00 2.3618912e+00 2.2737657e+00 2.3098587e+00 3.1429166e+00 2.4666493e+00 2.9899331e+00 3.3598261e+00 3.2396917e+00 2.2342806e+00 2.4357274e+00 2.4181291e+00 2.7984743e+00 2.4231383e+00 2.1599940e+00 2.3764241e+00 2.2561857e+00 2.3387588e+00 2.7074009e+00 1.8390751e+00 2.3351007e+00 4.3436399e+00 3.2756710e+00 4.4477490e+00 3.5866422e+00 4.0782068e+00 5.0677790e+00 2.7922251e+00 4.5545199e+00 4.0836814e+00 5.0715858e+00 3.7130862e+00 3.6733277e+00 4.0983149e+00 3.4111720e+00 3.6933050e+00 3.9623038e+00 3.6711803e+00 5.5579136e+00 5.4498365e+00 3.4842014e+00 4.4103781e+00 3.1690867e+00 5.1441957e+00 3.3997317e+00 4.1528029e+00 4.3901202e+00 3.2630747e+00 3.2035560e+00 3.8955732e+00 4.1857725e+00 4.6435471e+00 5.5146776e+00 3.9762768e+00 3.2193184e+00 3.3255820e+00 5.1213824e+00 4.1678001e+00 3.6124079e+00 3.1107135e+00 4.1457358e+00 4.3076787e+00 4.2130787e+00 3.2756710e+00 4.4066811e+00 4.4713485e+00 4.0952473e+00 3.6289876e+00 3.7168749e+00 3.9644506e+00 3.1662754e+00 1.5140329e+00 3.3872939e-01 1.1649855e+00 3.5691650e+00 3.1595321e+00 3.7055656e+00 4.0160835e+00 3.7376603e+00 3.2592987e+00 3.1435650e+00 3.5370651e+00 3.5437638e+00 3.2591886e+00 4.4166410e+00 3.0676819e+00 4.2127293e+00 3.3654329e+00 2.8346837e+00 3.3660906e+00 3.0613575e+00 3.2026716e+00 4.5808841e+00 3.5275705e+00 3.2454510e+00 3.2718756e+00 4.1819826e+00 3.4031279e+00 3.3454884e+00 3.4170637e+00 3.9109404e+00 3.8358967e+00 3.3305911e+00 3.2315455e+00 3.6883726e+00 3.6296117e+00 3.2506700e+00 3.8623185e+00 3.0230066e+00 2.8458833e+00 3.5111771e+00 4.3201594e+00 2.8024863e+00 3.6263297e+00 3.4825375e+00 3.1978058e+00 3.4556048e+00 3.7429398e+00 3.3240937e+00 2.7959976e+00 3.0137031e+00 3.2391776e+00 3.3180586e+00 3.1510639e+00 4.3279818e+00 4.0059487e+00 4.6233424e+00 3.9929211e+00 4.3355275e+00 5.1718231e+00 3.9512216e+00 4.7810147e+00 4.7732950e+00 4.7150495e+00 3.7777251e+00 4.2731987e+00 4.3246862e+00 4.3347988e+00 4.2753666e+00 4.0433740e+00 3.9425600e+00 5.0081332e+00 5.8406251e+00 4.6274156e+00 4.4284929e+00 3.8398842e+00 5.3940263e+00 4.0533472e+00 4.0818092e+00 4.3543675e+00 3.8429689e+00 3.5715666e+00 4.3728103e+00 4.3436347e+00 4.9498040e+00 4.9393850e+00 4.4487185e+00 3.7756717e+00 4.0901950e+00 5.2287042e+00 4.0497882e+00 3.7884247e+00 3.5028854e+00 4.2624115e+00 4.4485334e+00 4.3403092e+00 4.0059487e+00 4.4317896e+00 4.4210531e+00 4.3442496e+00 4.4457375e+00 3.9985746e+00 3.8504489e+00 3.5592028e+00 1.4309353e+00 5.3528567e-01 3.7908776e+00 3.2731841e+00 3.8215973e+00 3.1206853e+00 3.5080970e+00 2.7892862e+00 3.3363505e+00 2.4070515e+00 3.4174587e+00 2.5169099e+00 3.2261716e+00 2.8456035e+00 3.3834513e+00 3.1193847e+00 2.3599428e+00 3.4420978e+00 2.7676217e+00 2.6211360e+00 3.8782821e+00 2.7318604e+00 3.2516765e+00 2.8950591e+00 3.6956240e+00 3.0573546e+00 3.1595622e+00 3.3778208e+00 3.7543715e+00 3.8301935e+00 3.0538048e+00 2.4889600e+00 2.7975756e+00 2.7172725e+00 2.6747723e+00 3.4570094e+00 2.6710885e+00 3.0859941e+00 3.5894820e+00 3.6730945e+00 2.4678645e+00 2.8348872e+00 2.7756196e+00 3.0423378e+00 2.8112845e+00 2.6077230e+00 2.7173555e+00 2.4925318e+00 2.6151930e+00 2.9985224e+00 2.2768387e+00 2.6523384e+00 4.4886181e+00 3.5762214e+00 4.6936725e+00 3.8412437e+00 4.3086181e+00 5.3124523e+00 3.1125049e+00 4.8185220e+00 4.4330566e+00 5.1853832e+00 3.9019516e+00 3.9878172e+00 4.3438773e+00 3.7545919e+00 3.9571174e+00 4.1452084e+00 3.9080206e+00 5.6409887e+00 5.7635531e+00 3.9041153e+00 4.6071696e+00 3.4361835e+00 5.4269728e+00 3.7248960e+00 4.3153491e+00 4.5881410e+00 3.5627565e+00 3.4386758e+00 4.1762134e+00 4.4334431e+00 4.9338087e+00 5.6026788e+00 4.2556298e+00 3.5163766e+00 3.6516985e+00 5.3754384e+00 4.2899814e+00 3.8175194e+00 3.3436392e+00 4.3710164e+00 4.5233951e+00 4.4426760e+00 3.5762214e+00 4.5972906e+00 4.6375405e+00 4.3421746e+00 3.9932553e+00 3.9596590e+00 4.0813696e+00 3.3867904e+00 9.9272943e-01 3.3624661e+00 2.9811147e+00 3.5018936e+00 3.8169093e+00 3.5209477e+00 3.0838698e+00 2.9939885e+00 3.3707744e+00 3.3216375e+00 3.1074562e+00 4.2293025e+00 2.8939100e+00 3.9735251e+00 3.1779321e+00 2.6513305e+00 3.1539115e+00 2.9206559e+00 2.9923096e+00 4.3522137e+00 3.3213891e+00 3.1222272e+00 3.0540027e+00 3.9664528e+00 3.2012517e+00 3.1248526e+00 3.2007609e+00 3.6824267e+00 3.6418210e+00 3.1482435e+00 3.0088381e+00 3.4838907e+00 3.4206811e+00 3.0415159e+00 3.6826470e+00 2.9006138e+00 2.7293873e+00 3.3112277e+00 4.0819926e+00 2.6432089e+00 3.4355346e+00 3.3034492e+00 3.0164601e+00 3.2442582e+00 3.5631690e+00 3.1415769e+00 2.6264645e+00 2.8384396e+00 3.0300747e+00 3.1354811e+00 2.9649167e+00 4.2304738e+00 3.8482047e+00 4.4439318e+00 3.8285351e+00 4.1849277e+00 4.9874125e+00 3.8271288e+00 4.5862820e+00 4.5664639e+00 4.6040986e+00 3.6270951e+00 4.0846199e+00 4.1503558e+00 4.1702140e+00 4.1407964e+00 3.9121183e+00 3.7737683e+00 4.8997002e+00 5.6369181e+00 4.4180349e+00 4.2780098e+00 3.7035075e+00 5.1920494e+00 3.8582580e+00 3.9463952e+00 4.1826549e+00 3.6583100e+00 3.4129797e+00 4.2036908e+00 4.1443501e+00 4.7432980e+00 4.8153136e+00 4.2825977e+00 3.5821023e+00 3.9040345e+00 5.0374022e+00 3.9556226e+00 3.6351652e+00 3.3487998e+00 4.0905154e+00 4.2992014e+00 4.1693135e+00 3.8482047e+00 4.2897273e+00 4.2963449e+00 4.1754173e+00 4.2443816e+00 3.8297356e+00 3.7573406e+00 3.4190329e+00 3.4434283e+00 2.9833205e+00 3.5091456e+00 3.1516030e+00 3.2866494e+00 2.6849207e+00 3.0541761e+00 2.5654361e+00 3.1545670e+00 2.5403244e+00 3.3918434e+00 2.6608343e+00 3.3413616e+00 2.9302185e+00 2.2379234e+00 3.1290373e+00 2.6442995e+00 2.5077372e+00 3.8111267e+00 2.7069456e+00 3.0566678e+00 2.7098649e+00 3.5644689e+00 2.8826061e+00 2.9134932e+00 3.0944404e+00 3.4986589e+00 3.5664548e+00 2.8806273e+00 2.4158569e+00 2.8131786e+00 2.7333339e+00 2.5637470e+00 3.3370594e+00 2.5885603e+00 2.8220110e+00 3.2904739e+00 3.5710204e+00 2.3287218e+00 2.8315630e+00 2.7529707e+00 2.8293212e+00 2.7254528e+00 2.7527251e+00 2.6524994e+00 2.3299431e+00 2.4822440e+00 2.7803033e+00 2.3731496e+00 2.5423572e+00 4.2830420e+00 3.4939592e+00 4.4286678e+00 3.6575173e+00 4.1044791e+00 5.0220848e+00 3.2200733e+00 4.5468375e+00 4.2700249e+00 4.8698851e+00 3.6462342e+00 3.8237490e+00 4.0990020e+00 3.7208580e+00 3.8692104e+00 3.9203597e+00 3.6817365e+00 5.2775206e+00 5.5250867e+00 3.8676958e+00 4.3392543e+00 3.3693781e+00 5.1524083e+00 3.5650934e+00 4.0437994e+00 4.2783342e+00 3.3968462e+00 3.2517273e+00 4.0065881e+00 4.1377875e+00 4.6677357e+00 5.2142247e+00 4.0893000e+00 3.3307003e+00 3.5369003e+00 5.0771896e+00 4.0613196e+00 3.5869628e+00 3.1695162e+00 4.1006440e+00 4.2899814e+00 4.1769447e+00 3.4939592e+00 4.3422191e+00 4.3859843e+00 4.1114107e+00 3.8721945e+00 3.7365034e+00 3.8554667e+00 3.2330990e+00 7.4500632e-01 3.1271814e-01 2.7864553e+00 1.1117653e+00 1.8291315e+00 9.1459005e-01 3.2771828e+00 8.5582452e-01 2.5020950e+00 3.7722922e+00 1.4404415e+00 2.6850561e+00 1.2884095e+00 1.9346765e+00 4.6190224e-01 1.7610224e+00 1.9545276e+00 2.5064746e+00 2.4134734e+00 1.4291842e+00 1.4855627e+00 1.8305602e+00 1.4494865e+00 1.0355160e+00 6.8921053e-01 9.5529726e-01 7.2626021e-01 1.4025367e+00 2.2620298e+00 2.6646285e+00 2.6984190e+00 1.9245986e+00 1.7053476e+00 1.9940477e+00 1.3238834e+00 4.4901474e-01 2.2514668e+00 1.7899689e+00 2.4621620e+00 2.3008240e+00 1.1820572e+00 2.0593667e+00 3.3235867e+00 2.0701817e+00 1.6811909e+00 1.7438018e+00 1.2168151e+00 2.9923086e+00 1.8570167e+00 1.8407084e+00 1.9774894e+00 1.2374249e+00 1.3146181e+00 1.4369760e+00 1.6548985e+00 3.0339990e+00 1.3065206e+00 1.8441719e+00 1.9017153e+00 1.0169098e+00 1.5490835e+00 1.1480416e+00 2.3912363e+00 2.1724397e+00 1.4252775e+00 1.0284221e+00 2.2390158e+00 2.3611228e+00 2.6121814e+00 1.3139868e+00 2.0833701e+00 1.8624251e+00 1.5270661e+00 1.1605968e+00 9.3589904e-01 1.4360842e+00 1.2967707e+00 1.5740302e+00 8.5349066e-01 1.4639724e+00 2.1546616e+00 1.6538197e+00 1.2783641e+00 1.8320269e+00 1.7168897e+00 1.7042715e+00 1.0288355e+00 1.3960908e+00 1.0327124e+00 1.4702446e+00 1.2287925e+00 1.9774894e+00 1.3826233e+00 1.6072393e+00 1.3472497e+00 1.9439880e+00 1.1295026e+00 1.6414265e+00 1.5177322e+00 6.8496652e-01 2.3745921e+00 9.3211669e-01 1.2784093e+00 3.1271814e-01 2.7526949e+00 7.8034610e-01 1.8874930e+00 3.3568278e+00 7.7863029e-01 2.4620981e+00 7.9999102e-01 1.3194463e+00 4.5257749e-01 1.0705713e+00 1.5282070e+00 2.3189157e+00 1.9824340e+00 7.4029244e-01 1.0636179e+00 1.6347187e+00 1.0722301e+00 7.4849274e-01 5.3988754e-01 1.0632334e+00 7.0213871e-01 8.4383266e-01 1.8384560e+00 2.2399960e+00 2.2847962e+00 1.4577178e+00 1.3022697e+00 1.2927254e+00 6.8064066e-01 4.4417668e-01 2.0964002e+00 1.1252542e+00 1.9840858e+00 1.8038514e+00 6.0365341e-01 1.6354514e+00 2.8387736e+00 1.5364600e+00 1.0539473e+00 1.1361809e+00 7.8649633e-01 2.4634199e+00 1.2983546e+00 1.5835083e+00 1.4983760e+00 1.4748100e+00 1.0180846e+00 1.2694582e+00 2.0850659e+00 2.4405647e+00 1.6897529e+00 1.8533655e+00 2.0793529e+00 7.4797652e-01 1.3453828e+00 1.1770444e+00 1.9571621e+00 1.6977813e+00 1.1421260e+00 8.3850424e-01 2.6029823e+00 2.7071356e+00 2.3733266e+00 1.3878105e+00 1.5050081e+00 2.3020930e+00 1.2450968e+00 1.1247714e+00 1.3455945e+00 1.0453799e+00 7.4157869e-01 1.3630233e+00 1.3061954e+00 1.8456576e+00 2.6048102e+00 1.4425815e+00 9.9058911e-01 1.5658693e+00 2.1444356e+00 1.4166079e+00 7.2727886e-01 7.9343577e-01 1.1384575e+00 1.4013840e+00 1.2776135e+00 1.4983760e+00 1.4006413e+00 1.5375255e+00 1.2650236e+00 1.7250893e+00 9.0221296e-01 1.2767751e+00 9.2060977e-01 2.5673851e+00 8.6079202e-01 1.6428179e+00 8.7623959e-01 3.1131006e+00 6.6453319e-01 2.3246810e+00 3.5720588e+00 1.2918836e+00 2.4857868e+00 1.0845006e+00 1.8135469e+00 3.9472619e-01 1.6028858e+00 1.8029164e+00 2.2526468e+00 2.2305704e+00 1.2920175e+00 1.3194463e+00 1.5620078e+00 1.2567627e+00 8.7273869e-01 5.3095950e-01 7.1671402e-01 4.2827238e-01 1.2022652e+00 2.1186438e+00 2.4755072e+00 2.5212188e+00 1.7582453e+00 1.4360884e+00 1.8400908e+00 1.3147484e+00 2.6680274e-01 2.0194508e+00 1.6709595e+00 2.2587909e+00 2.1030957e+00 1.0161846e+00 1.8732616e+00 3.1496799e+00 1.8819614e+00 1.5700887e+00 1.5933926e+00 1.0543640e+00 2.8415314e+00 1.6890927e+00 1.6862498e+00 1.7038925e+00 1.0251132e+00 1.0245496e+00 1.1781513e+00 1.5212572e+00 2.8050734e+00 1.1091494e+00 1.5452835e+00 1.9080234e+00 8.5359653e-01 1.2416734e+00 8.9528108e-01 2.1088803e+00 1.9097018e+00 1.2522193e+00 7.4612152e-01 2.3284654e+00 2.1556564e+00 2.3436971e+00 1.1651790e+00 1.8364691e+00 1.6976628e+00 1.2371704e+00 1.0463225e+00 8.5302032e-01 1.1623508e+00 1.0682140e+00 1.2723284e+00 6.7955751e-01 1.2572095e+00 2.2840066e+00 1.3572135e+00 1.0082548e+00 1.5613089e+00 1.5962380e+00 1.5974627e+00 7.9671887e-01 1.1799226e+00 8.3571552e-01 1.2674750e+00 1.0543826e+00 1.7038925e+00 1.2197800e+00 1.4811026e+00 1.1132425e+00 1.6485749e+00 8.6295295e-01 1.5402909e+00 1.2957413e+00 1.7240804e+00 1.2117746e+00 2.5620931e+00 9.4346743e-01 1.9481085e+00 1.0013399e+00 1.0383354e+00 1.7091506e+00 7.5651431e-01 1.6169211e+00 1.4074199e+00 2.3606801e+00 1.6642999e+00 1.0677270e+00 9.5748562e-01 5.4702555e-01 2.2752108e+00 1.3619011e+00 1.2144903e+00 1.4245490e+00 1.7677805e+00 2.1070188e+00 2.0042865e+00 2.3026776e+00 1.5583422e+00 8.7856768e-01 3.6704030e-01 4.8644514e-01 1.0013399e+00 1.3262124e+00 1.6642999e+00 2.6524441e+00 2.3938089e+00 9.9234874e-01 1.6199145e+00 4.6126066e-01 7.3978204e-01 1.8066960e+00 7.9191984e-01 8.2250769e-01 9.3727156e-01 1.6415483e+00 1.4092680e+00 1.6304499e+00 9.1432842e-01 1.1795364e+00 3.1638147e+00 1.4103498e+00 2.9322745e+00 2.0247895e+00 2.5487652e+00 3.5082844e+00 1.0453705e+00 2.9611604e+00 1.9449446e+00 4.1343567e+00 2.6451449e+00 1.7869811e+00 2.6253748e+00 1.1973458e+00 1.9817270e+00 2.7838011e+00 2.2840066e+00 4.7706180e+00 3.4576971e+00 8.9870984e-01 3.1324336e+00 1.5639220e+00 3.4016597e+00 1.5728443e+00 3.0734808e+00 3.1995683e+00 1.6368228e+00 1.9546803e+00 2.1052860e+00 2.8313078e+00 2.9375460e+00 4.8020420e+00 2.1735035e+00 1.6493848e+00 1.3576485e+00 3.5774884e+00 3.2045691e+00 2.3952974e+00 1.8988804e+00 2.8268459e+00 2.8989852e+00 2.8927951e+00 1.4103498e+00 3.1063891e+00 3.2893581e+00 2.6241058e+00 1.4441104e+00 2.3170196e+00 3.0817543e+00 1.9124815e+00 1.0026233e+00 1.1867923e+00 2.3572427e+00 3.6939647e-01 1.6408576e+00 2.7392425e+00 8.8835337e-01 1.6805749e+00 5.5399712e-01 1.2745081e+00 7.5303835e-01 1.1820572e+00 1.1301977e+00 1.4315442e+00 1.4464138e+00 1.2423523e+00 6.4626422e-01 7.5230154e-01 6.2536527e-01 4.0664863e-01 5.0731024e-01 4.0158746e-01 6.2543628e-01 6.4884272e-01 1.4014424e+00 1.6702453e+00 1.7317202e+00 1.0390957e+00 7.1781501e-01 1.4073416e+00 1.5165187e+00 7.3502408e-01 1.2122797e+00 1.2421878e+00 1.4565053e+00 1.3559191e+00 6.8064066e-01 1.0943185e+00 2.3628816e+00 1.1638514e+00 1.1627754e+00 1.0519629e+00 5.3106808e-01 2.1057450e+00 1.0403581e+00 1.9325284e+00 1.0596309e+00 1.3779504e+00 7.6633520e-01 1.2315180e+00 1.9698667e+00 2.0697950e+00 1.4385383e+00 1.0743031e+00 2.5609592e+00 1.1664463e+00 7.0702759e-01 1.1055841e+00 1.3757605e+00 1.4784016e+00 1.4566739e+00 7.9213303e-01 3.1107848e+00 2.2607358e+00 1.5267093e+00 1.6037239e+00 1.2804073e+00 1.9864213e+00 5.4310586e-01 1.5475402e+00 1.5328931e+00 5.4647209e-01 7.9343577e-01 9.7668596e-01 1.1862065e+00 1.4760549e+00 3.1060327e+00 1.0849536e+00 3.7234239e-01 8.8571256e-01 2.0333305e+00 1.9196784e+00 9.5289573e-01 8.6297946e-01 1.2392529e+00 1.4996752e+00 1.3752205e+00 1.0596309e+00 1.6198849e+00 1.8691588e+00 1.2188552e+00 9.2906468e-01 8.7042892e-01 1.8304530e+00 9.8621003e-01 1.4220241e+00 1.5496729e+00 1.1125144e+00 7.4201890e-01 2.1434858e+00 6.0719477e-01 1.5102780e+00 5.6262711e-01 5.7267643e-01 1.3990971e+00 5.4408162e-01 5.2316125e-01 1.5323598e+00 8.2317311e-01 1.1694177e+00 5.6003943e-01 1.0600958e+00 5.1318506e-01 8.8354057e-01 1.1892978e+00 1.3456285e+00 1.4234329e+00 5.0311426e-01 8.2976237e-01 1.0655776e+00 1.1267154e+00 4.4786319e-01 6.7424840e-01 6.4241342e-01 1.4834540e+00 1.4207811e+00 1.3630799e+00 5.2795793e-01 7.8571751e-01 5.3988754e-01 6.8299624e-01 5.6992880e-01 1.6313928e+00 3.1093967e-01 5.0876385e-01 2.8653046e-01 6.5622658e-01 1.3398293e+00 2.2670334e-01 2.2472150e+00 8.9528108e-01 2.1908074e+00 1.1815770e+00 1.7549185e+00 2.8271782e+00 1.2987661e+00 2.2927322e+00 1.7056353e+00 3.1591627e+00 1.6557083e+00 1.2615426e+00 1.8432274e+00 1.1833606e+00 1.4858600e+00 1.8676774e+00 1.3771658e+00 3.7547288e+00 3.1005581e+00 1.4815836e+00 2.2650937e+00 9.5252488e-01 2.8687133e+00 1.0290036e+00 2.0855599e+00 2.2987772e+00 9.0705646e-01 9.6267538e-01 1.4839640e+00 2.0473137e+00 2.3780534e+00 3.7918985e+00 1.5792523e+00 8.4221906e-01 9.2300698e-01 2.9301148e+00 2.2149712e+00 1.3941954e+00 8.9564079e-01 1.9843979e+00 2.0984088e+00 2.1003345e+00 8.9528108e-01 2.2276119e+00 2.3923111e+00 1.8829565e+00 1.3046645e+00 1.4645285e+00 2.0631582e+00 9.0331700e-01 2.9007820e+00 1.0677270e+00 1.9884030e+00 3.5404087e+00 8.9973730e-01 2.7097598e+00 9.8896933e-01 1.4521880e+00 7.3735391e-01 1.1060455e+00 1.7358574e+00 2.5457091e+00 2.1802781e+00 5.9868400e-01 1.3038475e+00 1.8531597e+00 1.2895008e+00 1.0351584e+00 8.4116354e-01 1.3290015e+00 8.7070822e-01 1.0061402e+00 2.0523180e+00 2.4354079e+00 2.4861842e+00 1.6612475e+00 1.4482752e+00 1.3000067e+00 4.4417668e-01 6.8064066e-01 2.3457352e+00 1.2097457e+00 2.1562626e+00 1.9604379e+00 7.8034610e-01 1.8447318e+00 3.0052273e+00 1.6924215e+00 1.1656549e+00 1.2692102e+00 1.0351584e+00 2.6195047e+00 1.4577178e+00 1.3905452e+00 1.5765058e+00 1.5178511e+00 1.0862363e+00 1.2425116e+00 2.1288976e+00 2.5085097e+00 1.7889699e+00 2.0235792e+00 1.9184220e+00 6.6154242e-01 1.4831058e+00 1.2157849e+00 2.0573833e+00 1.6866006e+00 1.0122929e+00 9.0072498e-01 2.4680266e+00 2.8037035e+00 2.5716465e+00 1.3193736e+00 1.5270661e+00 2.3974481e+00 1.4129334e+00 9.9186850e-01 1.3586792e+00 1.1900564e+00 7.8649633e-01 1.4261046e+00 1.4313173e+00 1.9693923e+00 2.5032449e+00 1.4908532e+00 1.1825071e+00 1.7301809e+00 2.1927243e+00 1.1883807e+00 7.0823896e-01 8.2574748e-01 1.1508346e+00 1.3435624e+00 1.2769092e+00 1.5765058e+00 1.3121204e+00 1.3947465e+00 1.2781560e+00 1.8941016e+00 9.4449485e-01 1.0327124e+00 9.1221028e-01 2.4983699e+00 1.0001615e+00 9.3727156e-01 2.0156046e+00 1.4610933e+00 2.0818555e+00 1.4925824e+00 2.8275182e+00 1.8765007e+00 1.3658611e+00 1.8892371e+00 9.4900087e-01 2.5853758e+00 1.8063869e+00 2.0403844e+00 1.9103325e+00 2.2554051e+00 2.6063293e+00 2.6670659e+00 2.8999367e+00 1.9965452e+00 1.0765554e+00 7.8898008e-01 7.5921691e-01 1.3580560e+00 1.9753995e+00 1.7807988e+00 2.8573306e+00 2.8966014e+00 1.8587118e+00 1.7290357e+00 9.4346743e-01 1.0932187e+00 2.1982921e+00 1.2728402e+00 2.6033464e-01 1.2680818e+00 1.7824360e+00 1.6364088e+00 2.0664368e+00 3.9699460e-01 1.4644159e+00 3.6567369e+00 2.0227452e+00 3.6362580e+00 2.6431572e+00 3.1825084e+00 4.2569960e+00 1.1649315e+00 3.7040279e+00 2.8079889e+00 4.6643094e+00 3.1456885e+00 2.5368652e+00 3.2881355e+00 1.9057424e+00 2.5373943e+00 3.2972877e+00 2.8812938e+00 5.2957248e+00 4.3256332e+00 1.8261865e+00 3.7402681e+00 2.0260681e+00 4.2089146e+00 2.2931022e+00 3.6001832e+00 3.8156954e+00 2.2665638e+00 2.4364115e+00 2.8123267e+00 3.5007685e+00 3.7249152e+00 5.3249013e+00 2.8816817e+00 2.2758405e+00 2.0704519e+00 4.3322711e+00 3.6389537e+00 2.9225385e+00 2.3454418e+00 3.4545556e+00 3.5207953e+00 3.5188476e+00 2.0227452e+00 3.7070275e+00 3.8401809e+00 3.2712836e+00 2.2870689e+00 2.9197390e+00 3.4787880e+00 2.3479440e+00 1.8015956e+00 2.9300280e+00 9.4226820e-01 1.8443182e+00 6.2046469e-01 1.3340137e+00 5.0731024e-01 1.2583559e+00 1.1751408e+00 1.7063292e+00 1.5923247e+00 1.2788332e+00 7.3035754e-01 1.0391769e+00 6.6194168e-01 2.9537172e-01 2.8845946e-01 3.7622328e-01 6.2760421e-01 7.7259801e-01 1.4843174e+00 1.8353890e+00 1.8732616e+00 1.1492120e+00 9.8621003e-01 1.4916922e+00 1.4186317e+00 5.4702555e-01 1.4349060e+00 1.2616939e+00 1.6526705e+00 1.5077694e+00 6.5951091e-01 1.2429329e+00 2.5190636e+00 1.3124532e+00 1.1415080e+00 1.1102613e+00 5.1210327e-01 2.2412909e+00 1.1466385e+00 2.0209769e+00 1.3581397e+00 1.4351001e+00 9.3899770e-01 1.3860326e+00 1.9736520e+00 2.3116417e+00 1.4395013e+00 1.3256797e+00 2.5152621e+00 1.1895067e+00 1.0230389e+00 1.2126763e+00 1.7102781e+00 1.7732497e+00 1.5528794e+00 8.7017583e-01 2.9799960e+00 2.3789847e+00 1.8031686e+00 1.6479163e+00 1.5433090e+00 2.0188390e+00 8.9564079e-01 1.5340057e+00 1.4361609e+00 8.5359653e-01 9.3591761e-01 1.2375842e+00 1.0932909e+00 1.5255827e+00 2.9419617e+00 1.3503714e+00 5.7743200e-01 1.0870568e+00 2.0633836e+00 1.9646340e+00 9.8006549e-01 1.0101003e+00 1.2839264e+00 1.6205305e+00 1.4633215e+00 1.3581397e+00 1.6723912e+00 1.9304834e+00 1.3785508e+00 1.2852279e+00 1.0122929e+00 1.8669942e+00 1.1301977e+00 1.7293858e+00 1.1132823e+00 1.5940674e+00 1.2647627e+00 7.0155617e-01 2.0524860e+00 9.1916314e-01 9.0143387e-01 1.7090768e+00 7.7500385e-01 1.6060095e+00 1.1202045e+00 1.5217697e+00 1.2283051e+00 1.5431751e+00 1.8486311e+00 2.0116712e+00 2.0744305e+00 1.1308980e+00 8.6249502e-01 8.7618973e-01 9.4738284e-01 7.7051641e-01 1.2102028e+00 8.1844705e-01 1.9297683e+00 2.0868978e+00 1.6471661e+00 8.6143605e-01 6.0365341e-01 5.7743200e-01 1.3481077e+00 8.0628657e-01 1.1400599e+00 5.2860161e-01 9.6999820e-01 7.8957903e-01 1.3189781e+00 8.0128554e-01 6.6918102e-01 2.6783589e+00 1.1902996e+00 2.8052881e+00 1.7833755e+00 2.2807524e+00 3.4730319e+00 7.8369762e-01 2.9612706e+00 2.2200035e+00 3.7113566e+00 2.2079590e+00 1.7773274e+00 2.4240040e+00 1.2586273e+00 1.6606465e+00 2.3345591e+00 2.0100747e+00 4.3781379e+00 3.6673897e+00 1.6336953e+00 2.8241758e+00 1.1071867e+00 3.5077760e+00 1.5364148e+00 2.6605007e+00 2.9756588e+00 1.4305490e+00 1.5019762e+00 1.9806289e+00 2.7459951e+00 3.0159022e+00 4.4377151e+00 2.0446123e+00 1.5157660e+00 1.4706419e+00 3.5410473e+00 2.6488235e+00 2.0119986e+00 1.3953413e+00 2.5748429e+00 2.6034011e+00 2.6304123e+00 1.1902996e+00 2.7818749e+00 2.8834870e+00 2.3861430e+00 1.6719199e+00 2.0259174e+00 2.4855987e+00 1.3894554e+00 2.6621352e+00 1.3234208e+00 2.6096596e+00 2.2340939e+00 3.3444949e+00 2.5679785e+00 1.9118907e+00 1.7502251e+00 1.3868450e+00 3.2376571e+00 2.3245757e+00 2.2030084e+00 2.3874774e+00 2.7435279e+00 3.0963501e+00 2.9911365e+00 3.3313369e+00 2.5528922e+00 1.6215602e+00 1.1232628e+00 1.1083720e+00 1.9130507e+00 2.3463017e+00 2.5105454e+00 3.5809242e+00 3.3974315e+00 1.8325077e+00 2.4726944e+00 1.3889514e+00 1.6150266e+00 2.7833542e+00 1.7312416e+00 7.0111465e-01 1.8556044e+00 2.5019422e+00 2.3097506e+00 2.6016512e+00 1.2007565e+00 2.0949830e+00 4.1622891e+00 2.3984916e+00 3.9595754e+00 3.0477055e+00 3.5738045e+00 4.5102220e+00 1.5833139e+00 3.9547990e+00 2.8883510e+00 5.1681640e+00 3.6715203e+00 2.8100260e+00 3.6615020e+00 2.1175666e+00 2.9197870e+00 3.8026677e+00 3.3142297e+00 5.7988752e+00 4.3773719e+00 1.6802144e+00 4.1689985e+00 2.5051488e+00 4.3637125e+00 2.6075737e+00 4.1031841e+00 4.2218983e+00 2.6731954e+00 2.9685228e+00 3.1235748e+00 3.8333962e+00 3.9200272e+00 5.8238336e+00 3.1861618e+00 2.6684075e+00 2.3174914e+00 4.5851647e+00 4.2037872e+00 3.4173362e+00 2.9015754e+00 3.8649606e+00 3.9284352e+00 3.9274419e+00 2.3984916e+00 4.1396215e+00 4.3152972e+00 3.6556493e+00 2.4306199e+00 3.3534589e+00 4.0726739e+00 2.9019830e+00 1.9649078e+00 4.5716421e-01 6.0725725e-01 1.0082512e+00 4.0020411e-01 9.6216255e-01 1.8879475e+00 1.3283978e+00 6.9493020e-01 5.9382214e-01 1.3116762e+00 7.1128716e-01 6.9976890e-01 8.6291569e-01 1.2356595e+00 1.0989171e+00 3.1093967e-01 1.2231205e+00 1.5729927e+00 1.6302590e+00 8.2263932e-01 8.7786730e-01 6.2729876e-01 9.5483435e-01 1.0328871e+00 1.7091506e+00 4.5071694e-01 1.2824303e+00 1.1188225e+00 3.5622944e-01 1.0163696e+00 2.1159028e+00 8.2380019e-01 4.6137216e-01 4.2450569e-01 5.0629646e-01 1.7321630e+00 5.8565201e-01 1.8627348e+00 1.0140018e+00 1.9095789e+00 1.0347180e+00 1.4794093e+00 2.5851550e+00 1.6987423e+00 2.1172382e+00 1.7999889e+00 2.6937126e+00 1.1946586e+00 1.2274756e+00 1.5304430e+00 1.4222936e+00 1.3705079e+00 1.4369760e+00 1.1056213e+00 3.3133435e+00 3.0027854e+00 1.9037709e+00 1.8688892e+00 9.6470639e-01 2.7156484e+00 1.0119180e+00 1.6591942e+00 1.9679139e+00 7.8369762e-01 6.0848963e-01 1.3509456e+00 1.8177289e+00 2.2200035e+00 3.3498688e+00 1.4311753e+00 8.4040822e-01 1.2474502e+00 2.6426508e+00 1.7620244e+00 1.0560148e+00 5.3362004e-01 1.6100417e+00 1.7340403e+00 1.6942922e+00 1.0140018e+00 1.8496575e+00 1.9626001e+00 1.5340961e+00 1.4294738e+00 1.1293709e+00 1.5949054e+00 6.4398240e-01 1.7472907e+00 1.7451622e+00 2.3128200e+00 2.0364367e+00 1.1795364e+00 7.5358247e-01 8.5582452e-01 2.5764453e+00 1.4435947e+00 1.1399118e+00 1.4681660e+00 1.7387082e+00 2.0628406e+00 1.8244206e+00 2.2981140e+00 1.7651851e+00 1.0308265e+00 7.7934221e-01 7.7863029e-01 1.2082987e+00 1.5285763e+00 2.1068341e+00 2.8909913e+00 2.3684734e+00 6.2479428e-01 1.9481438e+00 9.9891776e-01 1.1557309e+00 1.9516972e+00 9.8896933e-01 1.2918836e+00 1.3154759e+00 1.9018319e+00 1.7043940e+00 1.6876317e+00 1.4136421e+00 1.4845363e+00 3.4227731e+00 1.7797546e+00 2.8993041e+00 2.1584174e+00 2.6985144e+00 3.3744038e+00 1.7790388e+00 2.8051892e+00 1.8256311e+00 4.2196161e+00 2.7909300e+00 1.8699505e+00 2.6716730e+00 1.6273208e+00 2.3931485e+00 2.9994905e+00 2.3643994e+00 4.7587356e+00 3.2663266e+00 8.6629251e-01 3.2140857e+00 2.0311002e+00 3.1918979e+00 1.6793067e+00 3.1869276e+00 3.1309476e+00 1.8104252e+00 2.1858235e+00 2.2467893e+00 2.6763967e+00 2.7532877e+00 4.7380019e+00 2.3330174e+00 1.6923429e+00 1.4009491e+00 3.4550204e+00 3.4609647e+00 2.5225714e+00 2.1699387e+00 2.8630132e+00 3.0349029e+00 2.9631214e+00 1.7797546e+00 3.2114945e+00 3.4557456e+00 2.7355167e+00 1.5237929e+00 2.4389172e+00 3.3539591e+00 2.2150193e+00 8.7774396e-01 8.7560645e-01 6.6918102e-01 8.5695467e-01 1.6281675e+00 1.2552875e+00 9.0276183e-01 4.7723749e-01 9.6922609e-01 3.4909881e-01 4.4701039e-01 6.6835176e-01 8.7807032e-01 8.7272262e-01 2.1119253e-01 1.2038778e+00 1.5064820e+00 1.5654738e+00 7.8630314e-01 5.8942278e-01 8.9320425e-01 1.1940983e+00 8.6887698e-01 1.4210091e+00 7.4201890e-01 1.2452417e+00 1.0494370e+00 2.3749211e-01 9.1435339e-01 2.1409786e+00 8.2148003e-01 6.5993495e-01 5.7516438e-01 2.8835410e-01 1.8418099e+00 6.4756318e-01 1.8787743e+00 9.0810653e-01 1.6779282e+00 7.7021931e-01 1.3319441e+00 2.3098596e+00 1.7659238e+00 1.7880416e+00 1.4280932e+00 2.6604707e+00 1.1753998e+00 9.4281519e-01 1.3471074e+00 1.3158313e+00 1.3974368e+00 1.4547739e+00 8.7568652e-01 3.2288696e+00 2.6753697e+00 1.6330922e+00 1.7674989e+00 1.0240850e+00 2.3852911e+00 7.4743804e-01 1.5932991e+00 1.7495490e+00 5.8796666e-01 5.8374436e-01 1.1339991e+00 1.5083690e+00 1.8912106e+00 3.2526896e+00 1.2423778e+00 4.2436984e-01 8.5959137e-01 2.4097678e+00 1.8344669e+00 9.0791603e-01 5.8796666e-01 1.4645285e+00 1.6477112e+00 1.6088132e+00 9.0810653e-01 1.7454599e+00 1.9428935e+00 1.4315280e+00 1.1694177e+00 9.9244707e-01 1.7007353e+00 6.6154242e-01 1.4832888e+00 6.1810529e-01 7.1128716e-01 1.8601631e+00 9.7397874e-01 1.2254650e+00 6.8496652e-01 1.4755282e+00 9.0753778e-01 1.0443931e+00 1.3169341e+00 1.6226440e+00 1.6498870e+00 7.4980278e-01 8.0686941e-01 1.1940983e+00 1.2255953e+00 5.6318359e-01 1.1503759e+00 6.6361830e-01 1.4063472e+00 1.5603679e+00 1.6837563e+00 3.6536845e-01 9.5761359e-01 8.4619410e-01 8.6958016e-01 7.7821113e-01 1.6196626e+00 5.7306091e-01 4.4786319e-01 3.6086172e-01 8.2608188e-01 1.1831978e+00 3.8480889e-01 2.4265852e+00 1.2696247e+00 2.4764170e+00 1.5584328e+00 2.0444851e+00 3.1426915e+00 1.4493286e+00 2.6430316e+00 2.1446703e+00 3.2893516e+00 1.7955663e+00 1.6408995e+00 2.1004081e+00 1.5285733e+00 1.7064050e+00 2.0142932e+00 1.6802708e+00 3.9009617e+00 3.4821230e+00 1.8809382e+00 2.4651410e+00 1.1989033e+00 3.2268928e+00 1.3782117e+00 2.2651964e+00 2.5478630e+00 1.2124370e+00 1.1789342e+00 1.8358339e+00 2.3443222e+00 2.7210373e+00 3.9221023e+00 1.9136809e+00 1.2486828e+00 1.4646971e+00 3.1951870e+00 2.3252489e+00 1.6537705e+00 1.0855082e+00 2.1947734e+00 2.3108044e+00 2.2621105e+00 1.2696247e+00 2.4484679e+00 2.5504328e+00 2.0873736e+00 1.6773040e+00 1.7023842e+00 2.1476736e+00 1.1560388e+00 1.3558057e+00 1.5283845e+00 2.1664244e+00 1.9784646e+00 1.1457159e+00 1.0355160e+00 1.4985549e+00 1.0494370e+00 6.0365341e-01 2.6033464e-01 7.3803207e-01 5.6864482e-01 9.7352372e-01 1.8229204e+00 2.2308199e+00 2.2668270e+00 1.4769275e+00 1.3385535e+00 1.5931825e+00 1.1248155e+00 2.1466080e-01 1.9117251e+00 1.3652496e+00 2.0207624e+00 1.8704283e+00 7.6880092e-01 1.6220723e+00 2.8778954e+00 1.6265611e+00 1.2621791e+00 1.3042843e+00 7.7313507e-01 2.5362196e+00 1.4082507e+00 1.8291996e+00 1.6183246e+00 1.3603639e+00 1.0878281e+00 1.3564590e+00 1.9053825e+00 2.6072470e+00 1.4737985e+00 1.6790332e+00 2.1679999e+00 9.4182667e-01 1.2929545e+00 1.1391970e+00 2.0265696e+00 1.8799960e+00 1.3547660e+00 8.8029208e-01 2.6196958e+00 2.4872661e+00 2.2706454e+00 1.4300844e+00 1.7151590e+00 2.0626281e+00 1.1997534e+00 1.2637010e+00 1.2307777e+00 1.0813975e+00 9.6603754e-01 1.3834169e+00 1.0564307e+00 1.5971466e+00 2.5708690e+00 1.4735640e+00 9.4160439e-01 1.5222760e+00 1.9572025e+00 1.7004687e+00 8.9142733e-01 1.0459007e+00 1.1049324e+00 1.4763267e+00 1.2674750e+00 1.6183246e+00 1.4769125e+00 1.6832034e+00 1.2843405e+00 1.6410601e+00 9.6706760e-01 1.5985259e+00 1.1910776e+00 1.0088064e+00 1.9822205e+00 1.3115314e+00 7.2626021e-01 8.5225534e-01 1.4476734e+00 8.6297946e-01 1.0334797e+00 1.2160426e+00 1.5358725e+00 1.3833366e+00 5.3528567e-01 1.2712561e+00 1.5367336e+00 1.6013312e+00 8.9845135e-01 9.1916314e-01 2.4152660e-01 1.0495503e+00 1.3530247e+00 1.8419620e+00 3.4651700e-01 1.2220171e+00 1.0116179e+00 6.2046469e-01 1.0696792e+00 2.0057768e+00 7.5914566e-01 4.4499696e-01 4.0664863e-01 8.1224041e-01 1.6406723e+00 5.8942278e-01 1.9060542e+00 9.6301827e-01 2.1281559e+00 1.1449868e+00 1.6017068e+00 2.8050285e+00 1.4536311e+00 2.3373419e+00 1.9472489e+00 2.8613983e+00 1.3924555e+00 1.3756347e+00 1.7433862e+00 1.3615778e+00 1.3332278e+00 1.5654312e+00 1.2872568e+00 3.4973752e+00 3.1986815e+00 1.9281666e+00 2.0588451e+00 8.3270853e-01 2.9373687e+00 1.1828955e+00 1.8231885e+00 2.1962402e+00 9.5979989e-01 7.5532639e-01 1.4672797e+00 2.0720690e+00 2.4566102e+00 3.5648048e+00 1.5414664e+00 1.0212756e+00 1.2733735e+00 2.8900916e+00 1.8289569e+00 1.2092544e+00 6.4558417e-01 1.8428644e+00 1.8966444e+00 1.9319316e+00 9.6301827e-01 2.0102936e+00 2.1030669e+00 1.7380754e+00 1.5489952e+00 1.3296349e+00 1.6538197e+00 6.3357758e-01 1.4295948e+00 5.4873947e-01 1.6126413e+00 5.8496636e-01 1.1009910e+00 6.0725725e-01 9.5139638e-01 1.3098483e+00 1.3941599e+00 1.6617787e+00 8.6702860e-01 4.2826573e-01 8.0996690e-01 8.1324137e-01 2.8553149e-01 9.9883272e-01 1.0921061e+00 1.8259719e+00 1.6053711e+00 1.1828265e+00 8.3161134e-01 7.0834786e-01 5.3106808e-01 9.8233874e-01 3.5366952e-01 1.4106682e+00 4.6484021e-01 7.5179033e-01 6.2055338e-01 7.8324937e-01 1.1546456e+00 4.7149050e-01 2.7022699e+00 1.3084256e+00 2.4620452e+00 1.5488588e+00 2.1433843e+00 3.0477876e+00 1.5122060e+00 2.4794486e+00 1.8496575e+00 3.5092631e+00 2.0205369e+00 1.5421829e+00 2.1550478e+00 1.4847626e+00 1.9338323e+00 2.2845215e+00 1.7093196e+00 4.0428524e+00 3.2768847e+00 1.4413624e+00 2.6112105e+00 1.4262166e+00 3.0341947e+00 1.2919157e+00 2.4486747e+00 2.5390232e+00 1.2420951e+00 1.3836252e+00 1.8380693e+00 2.2098781e+00 2.5420974e+00 4.0353061e+00 1.9425259e+00 1.0808550e+00 1.0871507e+00 3.1511951e+00 2.6568698e+00 1.7619640e+00 1.3391481e+00 2.2882514e+00 2.4739376e+00 2.4163220e+00 1.3084256e+00 2.5943375e+00 2.7895659e+00 2.2249457e+00 1.4927500e+00 1.8163092e+00 2.5068377e+00 1.3832520e+00 1.1807138e+00 2.3735475e+00 1.4416726e+00 7.3803207e-01 1.4480380e+00 1.6571634e+00 1.9125650e+00 1.5766889e+00 1.9793333e+00 1.6323793e+00 1.4021778e+00 1.1659675e+00 1.2498767e+00 1.3539814e+00 1.2332482e+00 2.0826771e+00 2.7811716e+00 2.1646850e+00 3.7371902e-01 2.0122804e+00 1.1585774e+00 1.3127802e+00 1.8544941e+00 1.1485726e+00 1.7450075e+00 1.3972701e+00 1.9880179e+00 1.7517164e+00 1.6394680e+00 1.8002228e+00 1.5490387e+00 2.9816674e+00 1.3976606e+00 2.4151949e+00 1.7787946e+00 2.2180206e+00 2.8804995e+00 1.7355261e+00 2.3592859e+00 1.2412454e+00 3.7977608e+00 2.4485045e+00 1.3668906e+00 2.2064746e+00 1.1631247e+00 1.9116990e+00 2.5849220e+00 2.0027932e+00 4.3925033e+00 2.6611027e+00 3.7234239e-01 2.7559143e+00 1.7089501e+00 2.6795765e+00 1.2450968e+00 2.8073641e+00 2.7667084e+00 1.4485479e+00 1.9038618e+00 1.7262603e+00 2.3286441e+00 2.2609611e+00 4.4068441e+00 1.7897439e+00 1.4300608e+00 1.1275945e+00 2.9337206e+00 3.0746426e+00 2.2005676e+00 1.9094387e+00 2.4292641e+00 2.5401664e+00 2.4975057e+00 1.3976606e+00 2.7518188e+00 2.9966927e+00 2.2416615e+00 9.2104245e-01 2.0302905e+00 3.0030765e+00 1.9507955e+00 1.9593598e+00 9.5666050e-01 1.1447875e+00 1.0324994e+00 1.3800294e+00 1.7386688e+00 1.7301705e+00 2.0251376e+00 1.2143895e+00 3.6924035e-01 2.6643250e-01 3.1271814e-01 5.3690447e-01 1.1566759e+00 1.3335853e+00 2.2553589e+00 2.0395552e+00 1.0374728e+00 1.1879760e+00 2.9406726e-01 4.0650681e-01 1.4164313e+00 3.6319073e-01 9.3305124e-01 5.5709100e-01 1.1791615e+00 9.8143688e-01 1.2231662e+00 7.9042934e-01 7.5817225e-01 2.9833953e+00 1.3537061e+00 2.7591591e+00 1.8262769e+00 2.3975382e+00 3.3499130e+00 1.2034779e+00 2.7851895e+00 1.9405021e+00 3.8845156e+00 2.3750632e+00 1.6954582e+00 2.4431790e+00 1.3394090e+00 1.9751740e+00 2.5771567e+00 2.0432035e+00 4.4739802e+00 3.4420978e+00 1.1727925e+00 2.9298786e+00 1.4800982e+00 3.2892623e+00 1.4456510e+00 2.8154123e+00 2.9321762e+00 1.4509441e+00 1.6902348e+00 2.0142932e+00 2.5791547e+00 2.8031074e+00 4.4835636e+00 2.1018908e+00 1.3894554e+00 1.2250001e+00 3.4334168e+00 2.9754074e+00 2.1241116e+00 1.6323629e+00 2.6113665e+00 2.7403495e+00 2.7045382e+00 1.3537061e+00 2.9092469e+00 3.0943267e+00 2.4717839e+00 1.4839640e+00 2.1082363e+00 2.8334846e+00 1.6627952e+00 1.2426609e+00 1.7313027e+00 1.2372185e+00 1.1631247e+00 1.1195889e+00 1.5188976e+00 1.0845006e+00 8.2263932e-01 1.9012930e+00 2.1909047e+00 2.2638611e+00 1.4908532e+00 1.2008045e+00 8.7223523e-01 5.7002996e-01 1.0697164e+00 2.2410714e+00 9.6470639e-01 1.8639992e+00 1.6786018e+00 7.4743804e-01 1.6592561e+00 2.7039438e+00 1.4162972e+00 1.0024224e+00 1.0401603e+00 1.0580730e+00 2.3284654e+00 1.2231205e+00 1.2611129e+00 1.1791615e+00 1.6899776e+00 9.5793067e-01 1.1548640e+00 2.3712314e+00 2.0275068e+00 2.0149111e+00 1.9649183e+00 2.1679212e+00 7.8905316e-01 1.3385913e+00 1.3061285e+00 1.6571634e+00 1.2299006e+00 9.3495766e-01 9.4613565e-01 2.8415314e+00 2.9130399e+00 2.3454203e+00 1.4655406e+00 1.0267382e+00 2.6085350e+00 1.2515236e+00 1.1837505e+00 1.7109084e+00 9.9111027e-01 5.2374483e-01 1.2552875e+00 1.7513749e+00 2.1661990e+00 2.9392776e+00 1.3022811e+00 1.1259771e+00 1.5663601e+00 2.4310503e+00 1.1268523e+00 7.5840628e-01 4.7680727e-01 1.3348163e+00 1.3454539e+00 1.4034689e+00 1.1791615e+00 1.4139320e+00 1.4450127e+00 1.2754470e+00 1.6940148e+00 9.2620264e-01 9.4281519e-01 4.9160020e-01 9.3054395e-01 4.1781009e-01 4.6190224e-01 8.0369154e-01 9.6816967e-01 1.1548640e+00 4.6557224e-01 8.2518769e-01 1.2073068e+00 1.2487994e+00 4.5257749e-01 7.8164792e-01 1.0374728e+00 1.4711456e+00 1.1089653e+00 1.1997868e+00 7.6195008e-01 1.0018628e+00 8.9796526e-01 5.8785093e-01 6.0098693e-01 1.8456219e+00 6.5622658e-01 6.9001472e-01 5.4715569e-01 3.1093967e-01 1.5254459e+00 4.8636669e-01 2.2683520e+00 1.0914354e+00 1.9823217e+00 1.1675117e+00 1.6963493e+00 2.6008457e+00 1.7128336e+00 2.0692339e+00 1.5728043e+00 3.0096252e+00 1.5213092e+00 1.1599200e+00 1.6580031e+00 1.3691582e+00 1.6116709e+00 1.8005954e+00 1.2641532e+00 3.5755981e+00 2.8921647e+00 1.5229350e+00 2.1046875e+00 1.2108278e+00 2.6299105e+00 8.9496218e-01 1.9702691e+00 2.0808148e+00 8.0585922e-01 9.4983854e-01 1.4326334e+00 1.7811974e+00 2.1211631e+00 3.5687116e+00 1.5311195e+00 7.1811205e-01 1.0257884e+00 2.6607812e+00 2.2075002e+00 1.3273841e+00 9.2853136e-01 1.7721541e+00 1.9757526e+00 1.8755628e+00 1.0914354e+00 2.1076593e+00 2.2924992e+00 1.7080157e+00 1.2150638e+00 1.3228669e+00 2.0678512e+00 1.0435585e+00 8.3930091e-01 1.0246689e+00 1.2483935e+00 9.2934901e-01 1.2786676e+00 1.0167074e+00 1.2794668e+00 1.2845002e+00 1.3705288e+00 1.0262066e+00 6.1158310e-01 1.6007620e+00 2.1232609e+00 1.4700482e+00 6.0145223e-01 1.5227019e+00 1.1234881e+00 1.1051628e+00 1.1975697e+00 9.1241332e-01 1.9821649e+00 1.0739839e+00 1.4719712e+00 1.2657553e+00 1.0246689e+00 1.8799916e+00 1.1304806e+00 2.3473055e+00 9.3001231e-01 1.7895396e+00 1.0784109e+00 1.5778477e+00 2.3110268e+00 1.7258314e+00 1.7588444e+00 8.0501785e-01 3.1299934e+00 1.7625999e+00 7.4394765e-01 1.5582385e+00 9.7850690e-01 1.4989725e+00 1.9419525e+00 1.2877346e+00 3.7053544e+00 2.3011615e+00 7.8305765e-01 2.1061327e+00 1.2737998e+00 2.1925140e+00 6.0604502e-01 2.1121593e+00 2.0810604e+00 8.0686941e-01 1.2420238e+00 1.1264142e+00 1.6698499e+00 1.7264467e+00 3.7248620e+00 1.2214818e+00 7.0111465e-01 5.3487449e-01 2.3978329e+00 2.4200364e+00 1.4831058e+00 1.2723027e+00 1.7715216e+00 1.9225896e+00 1.8845666e+00 9.3001231e-01 2.0954738e+00 2.3579846e+00 1.6403910e+00 5.2719130e-01 1.3587682e+00 2.3456726e+00 1.3154759e+00 5.0299964e-01 8.2155022e-01 8.8684653e-01 1.0935878e+00 4.8492463e-01 9.8860056e-01 1.2858521e+00 1.3288928e+00 6.2451737e-01 6.2760421e-01 1.0463002e+00 1.4889605e+00 1.0762241e+00 1.1975697e+00 8.4271175e-01 1.0854927e+00 8.7560645e-01 5.3352899e-01 7.0810362e-01 1.9474744e+00 7.1781501e-01 7.2553812e-01 6.1968090e-01 3.6924035e-01 1.6978139e+00 6.0510141e-01 2.1983316e+00 1.0378652e+00 1.8773521e+00 9.9490014e-01 1.5974238e+00 2.4585483e+00 1.7380659e+00 1.8957007e+00 1.4179266e+00 2.9495957e+00 1.4948761e+00 1.0683666e+00 1.5886178e+00 1.3564059e+00 1.6294524e+00 1.7819921e+00 1.1268523e+00 3.4719349e+00 2.7528980e+00 1.4535731e+00 2.0452826e+00 1.2150379e+00 2.4732935e+00 8.6167901e-01 1.8885847e+00 1.9433611e+00 7.9744128e-01 9.1854596e-01 1.3349909e+00 1.6250498e+00 1.9838258e+00 3.4743868e+00 1.4520430e+00 5.1406096e-01 7.3595190e-01 2.5794085e+00 2.1692945e+00 1.1973560e+00 9.2123504e-01 1.7205327e+00 1.9329718e+00 1.8817619e+00 1.0378652e+00 2.0262673e+00 2.2533761e+00 1.7016580e+00 1.1862896e+00 1.2748646e+00 2.0406838e+00 9.6984925e-01 3.6319073e-01 6.1968090e-01 7.8521221e-01 5.6113157e-01 1.2463647e+00 1.6309592e+00 1.6676963e+00 8.9796526e-01 8.9789119e-01 1.2621791e+00 1.3154759e+00 6.8124108e-01 1.3901973e+00 9.9970024e-01 1.4357022e+00 1.2962951e+00 4.8012872e-01 1.0246015e+00 2.2910733e+00 1.0720705e+00 8.8779355e-01 8.4724089e-01 2.4152660e-01 1.9817257e+00 8.8354057e-01 2.0655284e+00 1.2495886e+00 1.6398109e+00 9.9332012e-01 1.4769125e+00 2.2251642e+00 2.1023706e+00 1.7015856e+00 1.4609179e+00 2.6557282e+00 1.2410479e+00 1.0733560e+00 1.3593949e+00 1.6013655e+00 1.6915504e+00 1.5864800e+00 9.7957718e-01 3.1644964e+00 2.6137665e+00 1.7509262e+00 1.7860234e+00 1.3941000e+00 2.2824049e+00 8.7876634e-01 1.6464371e+00 1.6642217e+00 7.8808432e-01 8.5400786e-01 1.3018901e+00 1.3652161e+00 1.7805677e+00 3.1380968e+00 1.4095411e+00 5.8483448e-01 1.0816610e+00 2.2968622e+00 1.9911692e+00 1.0509799e+00 8.9223438e-01 1.4369760e+00 1.7217513e+00 1.5811148e+00 1.2495886e+00 1.8031513e+00 2.0209769e+00 1.4702446e+00 1.2810704e+00 1.0813343e+00 1.8691588e+00 1.0259679e+00 5.6788283e-01 5.3362004e-01 7.7368489e-01 1.6022591e+00 1.9873741e+00 2.0277089e+00 1.2494231e+00 1.1089653e+00 1.4561750e+00 1.2033093e+00 3.3742167e-01 1.6597443e+00 1.2265630e+00 1.7784712e+00 1.6384023e+00 6.1436388e-01 1.3800294e+00 2.6463489e+00 1.4025367e+00 1.1237500e+00 1.1244975e+00 5.5399712e-01 2.3227610e+00 1.2000527e+00 1.8734557e+00 1.4137602e+00 1.3887598e+00 9.6005858e-01 1.3202421e+00 1.9632584e+00 2.3879303e+00 1.4839475e+00 1.4995474e+00 2.3336107e+00 1.0014276e+00 1.1074656e+00 1.1350466e+00 1.8013325e+00 1.7379612e+00 1.3863777e+00 8.2339084e-01 2.8225734e+00 2.4523537e+00 2.0154418e+00 1.5091823e+00 1.5393373e+00 2.0723760e+00 9.8233874e-01 1.3702101e+00 1.3545502e+00 8.7875749e-01 8.4830222e-01 1.2549787e+00 1.1060185e+00 1.5823028e+00 2.7877979e+00 1.3537061e+00 7.2012544e-01 1.2954496e+00 2.0208193e+00 1.7781563e+00 8.8029208e-01 9.2256644e-01 1.1605968e+00 1.4991046e+00 1.3162835e+00 1.4137602e+00 1.5442127e+00 1.7645705e+00 1.2692218e+00 1.4162972e+00 9.1557526e-01 1.6722331e+00 1.0708496e+00 6.2826980e-01 1.0161846e+00 1.6718164e+00 1.9471640e+00 1.9947662e+00 1.3566251e+00 1.0412209e+00 1.7655766e+00 1.7163342e+00 7.1671402e-01 1.3277490e+00 1.5771462e+00 1.7793591e+00 1.6725709e+00 9.6964683e-01 1.3947746e+00 2.6556269e+00 1.5119726e+00 1.4702773e+00 1.3966512e+00 8.2199752e-01 2.4266623e+00 1.3925524e+00 2.0577807e+00 1.4034689e+00 1.2545960e+00 9.4767129e-01 1.3281960e+00 1.7401681e+00 2.4345214e+00 1.1896374e+00 1.0436620e+00 2.5015865e+00 1.2764504e+00 8.9223438e-01 1.1006735e+00 1.6953806e+00 1.7900496e+00 1.5985782e+00 8.8098199e-01 2.9595238e+00 2.0497239e+00 1.6965355e+00 1.5863720e+00 1.6496643e+00 1.7201711e+00 8.3409556e-01 1.5639220e+00 1.3496867e+00 8.9427872e-01 1.0978602e+00 1.1314785e+00 9.1432842e-01 1.2235673e+00 2.9196060e+00 1.2400775e+00 6.4083823e-01 1.0643984e+00 1.8241883e+00 2.0498818e+00 1.0696576e+00 1.1919906e+00 1.2042673e+00 1.5543054e+00 1.3831011e+00 1.4034689e+00 1.6218749e+00 1.9188761e+00 1.2920921e+00 1.1337565e+00 1.0067405e+00 1.9865217e+00 1.3029486e+00 9.5748562e-01 1.9681165e+00 2.2573397e+00 2.3235953e+00 1.5741400e+00 1.1016806e+00 1.6166760e+00 1.2896217e+00 3.8830315e-01 1.7972266e+00 1.5164233e+00 2.0064286e+00 1.8697578e+00 8.5494999e-01 1.6681709e+00 2.9309853e+00 1.6503473e+00 1.4467905e+00 1.4113341e+00 9.2189946e-01 2.6393526e+00 1.4852749e+00 1.4601858e+00 1.3160132e+00 8.7649478e-01 6.4756318e-01 8.3256255e-01 1.5094087e+00 2.4769347e+00 1.0668784e+00 1.2459961e+00 1.9408738e+00 6.5485710e-01 8.4116354e-01 6.0795234e-01 1.7154199e+00 1.4961901e+00 9.9551062e-01 3.9472619e-01 2.4940166e+00 2.0216642e+00 2.0463301e+00 1.0230389e+00 1.4612117e+00 1.6595118e+00 8.5582452e-01 9.5437259e-01 9.5694342e-01 7.7934221e-01 7.3851064e-01 8.5695467e-01 7.6638235e-01 1.1767396e+00 2.5076596e+00 9.4281519e-01 7.1971771e-01 1.2830542e+00 1.5700842e+00 1.4287578e+00 5.3095950e-01 8.6291569e-01 6.6154242e-01 1.0050638e+00 8.5606908e-01 1.3160132e+00 1.0514970e+00 1.3171874e+00 7.9433323e-01 1.2774110e+00 4.7509249e-01 1.3730080e+00 9.7659801e-01 1.1663747e+00 1.4582411e+00 1.5261646e+00 7.3570584e-01 5.8785093e-01 7.6039875e-01 1.1605726e+00 9.6964683e-01 1.4553344e+00 6.3765570e-01 1.1681709e+00 1.0005243e+00 2.9691107e-01 8.7856768e-01 2.0651943e+00 7.3735391e-01 6.0653347e-01 4.7837533e-01 3.7398306e-01 1.7406274e+00 5.5183182e-01 1.8498714e+00 8.1329726e-01 1.7508641e+00 8.2125107e-01 1.3423724e+00 2.4127395e+00 1.6384023e+00 1.9130927e+00 1.5043381e+00 2.6917823e+00 1.1782159e+00 9.6249568e-01 1.3877621e+00 1.2214135e+00 1.2719770e+00 1.4200371e+00 9.4526659e-01 3.3044115e+00 2.7645066e+00 1.6390945e+00 1.7948322e+00 8.7588404e-01 2.5003659e+00 7.4157869e-01 1.6267504e+00 1.8590427e+00 5.4310586e-01 5.2316125e-01 1.1372412e+00 1.6472029e+00 2.0021586e+00 3.3409572e+00 1.2314733e+00 5.4779717e-01 9.4822835e-01 2.4877874e+00 1.8001237e+00 9.6012811e-01 4.8644514e-01 1.5074309e+00 1.6452353e+00 1.6151033e+00 8.1329726e-01 1.7721541e+00 1.9352496e+00 1.4226963e+00 1.1564263e+00 1.0022114e+00 1.6574535e+00 5.8132667e-01 5.6318359e-01 5.3286499e-01 4.3341454e-01 1.2747045e+00 1.3163443e+00 2.1153648e+00 1.9183153e+00 1.1909838e+00 1.0657373e+00 5.8852220e-01 6.2225308e-01 1.3220343e+00 4.0443437e-01 1.0982562e+00 6.1619693e-01 1.0378442e+00 8.8917809e-01 1.0970024e+00 8.2199752e-01 6.9493020e-01 3.0003610e+00 1.5102474e+00 2.7635526e+00 1.8759428e+00 2.4405125e+00 3.3586044e+00 1.4659783e+00 2.7980993e+00 2.0759743e+00 3.8255754e+00 2.3211022e+00 1.7886572e+00 2.4450156e+00 1.5788967e+00 2.1011800e+00 2.5635734e+00 2.0416027e+00 4.3880097e+00 3.5282390e+00 1.4609179e+00 2.9105517e+00 1.6043433e+00 3.3245307e+00 1.5187698e+00 2.7743367e+00 2.8814354e+00 1.4896588e+00 1.6771528e+00 2.1027324e+00 2.5396337e+00 2.8265966e+00 4.3745133e+00 2.1946278e+00 1.4104380e+00 1.3873057e+00 3.4289479e+00 2.9514502e+00 2.1043046e+00 1.6198849e+00 2.5820471e+00 2.7513626e+00 2.6746677e+00 1.5102474e+00 2.9036877e+00 3.0793854e+00 2.4777911e+00 1.6406409e+00 2.1046875e+00 2.7982275e+00 1.6824284e+00 1.4276574e-01 7.9394533e-01 1.3473710e+00 1.5367336e+00 2.5040512e+00 2.2893871e+00 1.0820670e+00 1.4237364e+00 3.6704030e-01 5.8785093e-01 1.6733127e+00 6.1158310e-01 7.1781501e-01 7.8318003e-01 1.4288989e+00 1.2280749e+00 1.4809953e+00 7.0150436e-01 1.0034788e+00 3.1877520e+00 1.5005648e+00 2.9634183e+00 2.0359721e+00 2.5953129e+00 3.5470571e+00 1.1634940e+00 2.9839272e+00 2.0686806e+00 4.1156592e+00 2.6069476e+00 1.8659064e+00 2.6504363e+00 1.4017266e+00 2.1040122e+00 2.7893850e+00 2.2677890e+00 4.7183507e+00 3.5819899e+00 1.1465705e+00 3.1455771e+00 1.6263647e+00 3.4643576e+00 1.6254447e+00 3.0471395e+00 3.1646326e+00 1.6517237e+00 1.9156890e+00 2.1886203e+00 2.8006555e+00 2.9856138e+00 4.7315684e+00 2.2694993e+00 1.6130651e+00 1.3903392e+00 3.6256164e+00 3.1929372e+00 2.3573820e+00 1.8552594e+00 2.8291427e+00 2.9408913e+00 2.9120555e+00 1.5005648e+00 3.1237582e+00 3.3065647e+00 2.6677639e+00 1.5962380e+00 2.3224070e+00 3.0542458e+00 1.8794605e+00 8.3156200e-01 1.4460310e+00 1.6013312e+00 2.5509456e+00 2.3359909e+00 1.1395071e+00 1.4612871e+00 4.8644514e-01 6.6244727e-01 1.7247525e+00 6.6453319e-01 6.8496652e-01 8.5330525e-01 1.4567673e+00 1.2739414e+00 1.5213580e+00 6.7904052e-01 1.0560797e+00 3.2869799e+00 1.6218234e+00 3.0463976e+00 2.1264009e+00 2.6948572e+00 3.6228821e+00 1.2747978e+00 3.0537173e+00 2.1607143e+00 4.1937512e+00 2.6849827e+00 1.9680122e+00 2.7382122e+00 1.5399252e+00 2.2309601e+00 2.8826191e+00 2.3479440e+00 4.7798805e+00 3.6690959e+00 1.2447718e+00 3.2325186e+00 1.7450415e+00 3.5380106e+00 1.7243835e+00 3.1258440e+00 3.2275370e+00 1.7473394e+00 2.0003230e+00 2.2955340e+00 2.8573132e+00 3.0588804e+00 4.7837445e+00 2.3799967e+00 1.6861899e+00 1.4737985e+00 3.7047689e+00 3.2828775e+00 2.4345890e+00 1.9408738e+00 2.9104325e+00 3.0383020e+00 2.9993404e+00 1.6218234e+00 3.2132904e+00 3.3994957e+00 2.7639400e+00 1.7088499e+00 2.4110070e+00 3.1406474e+00 1.9689207e+00 8.9196595e-01 9.9107019e-01 1.7478610e+00 1.5467508e+00 1.1459117e+00 7.5303835e-01 6.0365341e-01 5.1453676e-01 9.1435339e-01 2.3749211e-01 1.4031123e+00 3.2313211e-01 7.2263841e-01 5.2290002e-01 7.1740234e-01 1.0975976e+00 3.1271814e-01 2.5686027e+00 1.1418735e+00 2.3704417e+00 1.4573208e+00 2.0173981e+00 2.9893605e+00 1.3924555e+00 2.4418079e+00 1.7809408e+00 3.4092863e+00 1.8988911e+00 1.4127713e+00 2.0371950e+00 1.3095379e+00 1.7286430e+00 2.1358640e+00 1.6228818e+00 3.9920035e+00 3.2072460e+00 1.3897098e+00 2.4925270e+00 1.2375842e+00 2.9891693e+00 1.1418958e+00 2.3510928e+00 2.4945648e+00 1.0792736e+00 1.2447083e+00 1.7021399e+00 2.1843621e+00 2.4864921e+00 3.9967418e+00 1.7954132e+00 1.0172824e+00 1.0869385e+00 3.0619758e+00 2.5242226e+00 1.6782397e+00 1.1896845e+00 2.1746675e+00 2.3309032e+00 2.2706367e+00 1.1418735e+00 2.4800313e+00 2.6530340e+00 2.0689155e+00 1.3443777e+00 1.6837594e+00 2.3748600e+00 1.2545769e+00 1.0660846e+00 1.6498377e+00 1.2783641e+00 1.1376397e+00 1.0898613e+00 1.0585628e+00 9.2189946e-01 8.0142393e-01 8.8029208e-01 1.9920533e+00 8.0501785e-01 1.0699859e+00 8.7105035e-01 7.9448303e-01 1.7999592e+00 8.1251986e-01 1.9227723e+00 4.6137216e-01 1.6965186e+00 7.0213871e-01 1.2723284e+00 2.3160615e+00 1.4526546e+00 1.7912701e+00 1.0739839e+00 2.8496420e+00 1.4032361e+00 6.3302304e-01 1.3757605e+00 7.8863556e-01 1.1001816e+00 1.5547583e+00 9.8152003e-01 3.4772360e+00 2.4799120e+00 1.1619556e+00 1.8622587e+00 7.5769247e-01 2.3162319e+00 4.6128322e-01 1.7816685e+00 1.9387331e+00 4.5729032e-01 7.5817225e-01 8.9223438e-01 1.6541379e+00 1.8404767e+00 3.5381276e+00 9.9244707e-01 4.4901474e-01 4.6557224e-01 2.4199101e+00 1.9790803e+00 1.0974789e+00 7.5914566e-01 1.5781281e+00 1.6567524e+00 1.6951864e+00 4.6137216e-01 1.8193470e+00 2.0336808e+00 1.4275349e+00 7.0834786e-01 1.0588854e+00 1.8801322e+00 7.4965096e-01 1.1803522e+00 1.5908164e+00 1.9645622e+00 4.2238505e-01 1.2220171e+00 1.0116179e+00 8.5731385e-01 1.1485726e+00 1.9317000e+00 7.9664122e-01 5.6097460e-01 5.3106808e-01 1.0334797e+00 1.5679493e+00 6.8124108e-01 2.0247774e+00 1.0499569e+00 2.3371797e+00 1.3332950e+00 1.7744903e+00 3.0154970e+00 1.3277924e+00 2.5520970e+00 2.1193865e+00 3.0297355e+00 1.5881698e+00 1.5547948e+00 1.9487821e+00 1.4037678e+00 1.3973195e+00 1.7249900e+00 1.4967902e+00 3.6762761e+00 3.3933664e+00 2.0023741e+00 2.2484516e+00 8.6702860e-01 3.1482546e+00 1.3659877e+00 2.0060004e+00 2.4114658e+00 1.1530661e+00 9.5944179e-01 1.6364369e+00 2.2989490e+00 2.6726954e+00 3.7560452e+00 1.7032717e+00 1.2286922e+00 1.4040978e+00 3.1041910e+00 1.9523740e+00 1.4097206e+00 8.4169735e-01 2.0525205e+00 2.0729884e+00 2.1328505e+00 1.0499569e+00 2.1908074e+00 2.2633850e+00 1.9289706e+00 1.6929462e+00 1.5333884e+00 1.7729821e+00 7.9671887e-01 1.1060455e+00 2.5932658e+00 1.1359179e+00 2.2153658e+00 2.0116430e+00 9.6825676e-01 1.9538525e+00 2.9958431e+00 1.7387082e+00 1.1339874e+00 1.2823616e+00 1.2471855e+00 2.5771468e+00 1.5006766e+00 1.5158960e+00 1.7131342e+00 1.9169015e+00 1.3850497e+00 1.5416258e+00 2.5358032e+00 2.4678381e+00 2.2144600e+00 2.3737220e+00 2.1274158e+00 9.8372339e-01 1.7887913e+00 1.5921275e+00 2.1896791e+00 1.7854129e+00 1.2218858e+00 1.2670969e+00 2.6904561e+00 3.2109839e+00 2.7851183e+00 1.6425353e+00 1.5729927e+00 2.8211634e+00 1.6904600e+00 1.2882518e+00 1.7618849e+00 1.4390193e+00 9.9282597e-01 1.7222401e+00 1.8692144e+00 2.3979391e+00 2.7477433e+00 1.7762263e+00 1.4761145e+00 1.9687854e+00 2.5941073e+00 1.2723200e+00 1.0497372e+00 9.7397874e-01 1.5327853e+00 1.6373339e+00 1.6177026e+00 1.7131342e+00 1.6177237e+00 1.6189835e+00 1.6013655e+00 2.1620604e+00 1.2836487e+00 1.0769609e+00 1.0246689e+00 1.9326437e+00 1.4149714e+00 2.0593667e+00 1.9008579e+00 7.7368489e-01 1.6801694e+00 2.9457974e+00 1.6627284e+00 1.3215146e+00 1.3491191e+00 8.3512263e-01 2.6175043e+00 1.4565053e+00 1.6449760e+00 1.5357227e+00 1.1692720e+00 9.2780124e-01 1.1582405e+00 1.7355630e+00 2.5925902e+00 1.3094338e+00 1.5678176e+00 2.0102053e+00 7.6952423e-01 1.1716038e+00 9.4417605e-01 1.9573929e+00 1.7612938e+00 1.1827746e+00 6.8675486e-01 2.4881500e+00 2.3327432e+00 2.2476505e+00 1.2375842e+00 1.6387331e+00 1.9108109e+00 1.1188225e+00 1.0733560e+00 1.0560148e+00 1.0005243e+00 8.6347207e-01 1.2199459e+00 9.0753778e-01 1.4483156e+00 2.4625089e+00 1.3082342e+00 8.7375509e-01 1.4601811e+00 1.8000065e+00 1.5372053e+00 7.0097130e-01 9.6204815e-01 9.1315231e-01 1.2848917e+00 1.0993651e+00 1.5357227e+00 1.2764003e+00 1.5003224e+00 1.1101208e+00 1.5658291e+00 7.8808432e-01 1.4489917e+00 1.0920053e+00 1.8302572e+00 1.0943114e+00 1.1955102e+00 1.6415483e+00 9.5491981e-01 1.7343175e+00 1.2563834e+00 1.7780218e+00 1.5661153e+00 1.3901973e+00 1.7352481e+00 1.3724737e+00 2.9349297e+00 1.4110819e+00 2.3154474e+00 1.6753059e+00 2.1644875e+00 2.7793057e+00 1.8300579e+00 2.2275691e+00 1.2267563e+00 3.6839155e+00 2.3163493e+00 1.3205795e+00 2.1115079e+00 1.3018219e+00 1.9822480e+00 2.5100153e+00 1.8661672e+00 4.2327578e+00 2.6573893e+00 6.0725725e-01 2.6633209e+00 1.7222058e+00 2.5939799e+00 1.1664463e+00 2.6821825e+00 2.5963941e+00 1.3509199e+00 1.7816323e+00 1.7046308e+00 2.1381589e+00 2.1542571e+00 4.2222578e+00 1.7881987e+00 1.2473306e+00 1.0083490e+00 2.8478148e+00 2.9960208e+00 2.0583207e+00 1.7939407e+00 2.3128527e+00 2.4854841e+00 2.4090630e+00 1.4110819e+00 2.6669716e+00 2.9270626e+00 2.1822548e+00 9.7289027e-01 1.9265423e+00 2.9135583e+00 1.8510296e+00 1.1608422e+00 9.5483435e-01 6.7975587e-01 9.6424206e-01 1.8685422e+00 6.9420840e-01 1.8699153e-01 2.6643250e-01 7.6880092e-01 1.4668684e+00 4.7680727e-01 2.1966727e+00 1.2150638e+00 2.3282955e+00 1.3855601e+00 1.8709248e+00 2.9899796e+00 1.5396352e+00 2.5003659e+00 2.1099656e+00 3.0668593e+00 1.5989333e+00 1.5788417e+00 1.9566602e+00 1.5639220e+00 1.6339738e+00 1.8236402e+00 1.4967013e+00 3.6603010e+00 3.3937895e+00 1.9915789e+00 2.2840141e+00 1.1223478e+00 3.1075626e+00 1.3520885e+00 2.0407191e+00 2.3526669e+00 1.1508346e+00 9.9970980e-01 1.7227103e+00 2.1946041e+00 2.6155078e+00 3.6958832e+00 1.8054416e+00 1.1477199e+00 1.3984076e+00 3.0713671e+00 2.0894840e+00 1.4301682e+00 9.0552938e-01 2.0395038e+00 2.1489811e+00 2.1344915e+00 1.2150638e+00 2.2517887e+00 2.3533226e+00 1.9673072e+00 1.7095802e+00 1.5528301e+00 1.9068051e+00 9.3899770e-01 3.4893361e-01 1.4098163e+00 4.4901474e-01 9.4301660e-01 4.9009568e-01 1.1908452e+00 9.6032771e-01 1.2627589e+00 7.8945238e-01 7.3502408e-01 2.8451486e+00 1.1622402e+00 2.7058576e+00 1.7424022e+00 2.2846522e+00 3.3213689e+00 9.3810350e-01 2.7757276e+00 1.8894571e+00 3.8131048e+00 2.3010216e+00 1.5984421e+00 2.3698153e+00 1.1049324e+00 1.7539088e+00 2.4591578e+00 1.9849730e+00 4.4474865e+00 3.3956070e+00 1.1104964e+00 2.8478148e+00 1.2628565e+00 3.2741783e+00 1.3548265e+00 2.7443454e+00 2.9214972e+00 1.3520885e+00 1.5950569e+00 1.8924015e+00 2.5961001e+00 2.7889301e+00 4.4811770e+00 1.9680122e+00 1.3672690e+00 1.1906665e+00 3.3943858e+00 2.8533575e+00 2.0610968e+00 1.5261646e+00 2.5498486e+00 2.6295980e+00 2.6227721e+00 1.1622402e+00 2.8191421e+00 2.9841287e+00 2.3684093e+00 1.3684638e+00 2.0228721e+00 2.7146999e+00 1.5430544e+00 1.2073068e+00 4.2737382e-01 1.1404637e+00 3.1271814e-01 9.6032771e-01 7.5303835e-01 1.1016806e+00 9.6606527e-01 5.6318359e-01 2.6951278e+00 1.0877340e+00 2.5880472e+00 1.5788417e+00 2.1577755e+00 3.1981141e+00 1.0053189e+00 2.6422640e+00 1.8441670e+00 3.6556493e+00 2.1516272e+00 1.5283932e+00 2.2572409e+00 1.1515368e+00 1.7244934e+00 2.3310687e+00 1.8210464e+00 4.2584241e+00 3.3382181e+00 1.2189366e+00 2.7191331e+00 1.1859692e+00 3.1732334e+00 1.2980649e+00 2.5768210e+00 2.7513616e+00 1.2636762e+00 1.4403062e+00 1.8020431e+00 2.4433699e+00 2.6920515e+00 4.2945779e+00 1.8903935e+00 1.2074964e+00 1.0277379e+00 3.3038558e+00 2.6967685e+00 1.8755883e+00 1.3730080e+00 2.4290237e+00 2.5228632e+00 2.5343900e+00 1.0877340e+00 2.6795155e+00 2.8549884e+00 2.2878474e+00 1.3941000e+00 1.9010194e+00 2.5529515e+00 1.3637808e+00 1.0801003e+00 2.2778358e+00 9.5491981e-01 5.9448670e-01 5.9589853e-01 3.3742167e-01 1.9403546e+00 7.3727571e-01 1.8011631e+00 1.0580730e+00 1.6859882e+00 8.4110582e-01 1.3396881e+00 2.3254107e+00 1.8940865e+00 1.8320164e+00 1.6099822e+00 2.5455416e+00 1.0698235e+00 1.0938968e+00 1.3476330e+00 1.4941922e+00 1.4633215e+00 1.3755629e+00 8.7649478e-01 3.1069376e+00 2.7702855e+00 1.8674396e+00 1.7104256e+00 1.1065179e+00 2.4455843e+00 9.1688392e-01 1.4945651e+00 1.6975565e+00 7.1757390e-01 5.5102439e-01 1.2274184e+00 1.5152116e+00 1.9568855e+00 3.1286065e+00 1.3281960e+00 6.0709980e-01 1.0827099e+00 2.4180452e+00 1.7168537e+00 8.4814328e-01 5.4968254e-01 1.4259927e+00 1.6175327e+00 1.5676792e+00 1.0580730e+00 1.6914438e+00 1.8627823e+00 1.4252775e+00 1.3670172e+00 9.8340962e-01 1.5690664e+00 6.4292875e-01 1.2799022e+00 3.7622328e-01 9.3727156e-01 7.2340544e-01 8.7070822e-01 1.0517510e+00 4.9772204e-01 2.6753497e+00 1.1327780e+00 2.4219935e+00 1.5112031e+00 2.0792734e+00 3.0229728e+00 1.3206164e+00 2.4652397e+00 1.7009790e+00 3.5349296e+00 2.0290396e+00 1.4008516e+00 2.1030738e+00 1.2197800e+00 1.7532536e+00 2.2495068e+00 1.7048463e+00 4.1210195e+00 3.1691829e+00 1.1769133e+00 2.5856062e+00 1.2767751e+00 2.9863081e+00 1.1384575e+00 2.4711712e+00 2.5838439e+00 1.1268523e+00 1.3640383e+00 1.7178039e+00 2.2416335e+00 2.4908014e+00 4.1279448e+00 1.8102703e+00 1.0585628e+00 1.0110609e+00 3.0999848e+00 2.6577347e+00 1.7876312e+00 1.3164631e+00 2.2615790e+00 2.4095273e+00 2.3580977e+00 1.1327780e+00 2.5710650e+00 2.7600070e+00 2.1383264e+00 1.2571099e+00 1.7683536e+00 2.5188615e+00 1.3683626e+00 1.3381984e+00 1.9104439e+00 1.7447553e+00 2.1191178e+00 5.2290002e-01 1.5506355e+00 3.7401310e+00 2.0532672e+00 3.6450987e+00 2.6791066e+00 3.2198971e+00 4.2474168e+00 1.2374249e+00 3.6904578e+00 2.7448024e+00 4.7359552e+00 3.2167523e+00 2.5268732e+00 3.3111493e+00 1.8901504e+00 2.5824715e+00 3.3694826e+00 2.9225385e+00 5.3651760e+00 4.2632085e+00 1.6938497e+00 3.7848480e+00 2.0962051e+00 4.1703198e+00 2.2884248e+00 3.6689921e+00 3.8480511e+00 2.2915999e+00 2.5084155e+00 2.8222270e+00 3.5057930e+00 3.6931154e+00 5.3885667e+00 2.8913445e+00 2.2944283e+00 2.0536056e+00 4.3194837e+00 3.7370068e+00 2.9859935e+00 2.4261724e+00 3.4875553e+00 3.5613793e+00 3.5512500e+00 2.0532672e+00 3.7558873e+00 3.9047630e+00 3.2988390e+00 2.2352837e+00 2.9604482e+00 3.5852990e+00 2.4345890e+00 7.1446962e-01 4.7680727e-01 8.6080744e-01 1.0528937e+00 2.6643250e-01 2.4784392e+00 9.6779954e-01 2.4056700e+00 1.4093245e+00 1.9680122e+00 3.0432377e+00 1.1095018e+00 2.5046513e+00 1.7969297e+00 3.4167115e+00 1.9006720e+00 1.3928921e+00 2.0543866e+00 1.1288261e+00 1.5650139e+00 2.0901630e+00 1.6223748e+00 4.0330923e+00 3.2470423e+00 1.3554912e+00 2.4968262e+00 1.0256272e+00 3.0550867e+00 1.1407226e+00 2.3454418e+00 2.5560105e+00 1.0597600e+00 1.1958054e+00 1.6477280e+00 2.2779375e+00 2.5604758e+00 4.0677826e+00 1.7340403e+00 1.0472149e+00 1.0317636e+00 3.1283758e+00 2.4552133e+00 1.6602736e+00 1.1211328e+00 2.2084219e+00 2.3071243e+00 2.3006257e+00 9.6779954e-01 2.4647768e+00 2.6219630e+00 2.0691920e+00 1.3232765e+00 1.6800415e+00 2.3045354e+00 1.1399118e+00 2.6525508e-01 6.6194168e-01 1.5279052e+00 4.8284931e-01 2.2240009e+00 1.2628565e+00 2.2754107e+00 1.3512603e+00 1.8635082e+00 2.9164540e+00 1.6496295e+00 2.4127395e+00 2.0563471e+00 3.0426144e+00 1.5827764e+00 1.5566996e+00 1.9230842e+00 1.6230251e+00 1.7204639e+00 1.8421714e+00 1.4471806e+00 3.6003163e+00 3.3322323e+00 1.9737130e+00 2.2612204e+00 1.2180372e+00 3.0253495e+00 1.3338820e+00 2.0126070e+00 2.2700141e+00 1.1450371e+00 1.0044165e+00 1.7168897e+00 2.0924575e+00 2.5354258e+00 3.6216411e+00 1.8094028e+00 1.0735412e+00 1.3351581e+00 3.0117528e+00 2.1161544e+00 1.3888000e+00 9.3005809e-01 2.0016409e+00 2.1479330e+00 2.1191972e+00 1.2628565e+00 2.2323235e+00 2.3582915e+00 1.9639632e+00 1.7034312e+00 1.5340961e+00 1.9379753e+00 9.6779954e-01 6.0647055e-01 1.3810470e+00 2.3749211e-01 2.2111682e+00 1.0514970e+00 2.2223402e+00 1.2585091e+00 1.7887495e+00 2.8752401e+00 1.4450035e+00 2.3620441e+00 1.8870562e+00 3.0854059e+00 1.5854288e+00 1.3907384e+00 1.8599878e+00 1.3776606e+00 1.5509730e+00 1.8163092e+00 1.3995189e+00 3.6797126e+00 3.2203837e+00 1.7354649e+00 2.2402012e+00 1.0327124e+00 2.9556082e+00 1.1508346e+00 2.0324938e+00 2.2869296e+00 9.8115739e-01 9.3443769e-01 1.5799528e+00 2.0763861e+00 2.4587811e+00 3.7098479e+00 1.6697722e+00 9.5240225e-01 1.1656779e+00 2.9602833e+00 2.1358640e+00 1.3782117e+00 8.5400786e-01 1.9683111e+00 2.0924338e+00 2.0712315e+00 1.0514970e+00 2.2110297e+00 2.3461934e+00 1.8840843e+00 1.4831574e+00 1.4659783e+00 1.9682209e+00 8.9496218e-01 1.7964452e+00 6.5622658e-01 2.0655284e+00 1.1268523e+00 1.7764179e+00 9.9332012e-01 1.5158960e+00 2.3895003e+00 1.8982283e+00 1.8651393e+00 1.5387077e+00 2.7528000e+00 1.2871947e+00 1.1001946e+00 1.4627474e+00 1.4880729e+00 1.6030181e+00 1.6047598e+00 1.0374207e+00 3.2910073e+00 2.7655862e+00 1.7002168e+00 1.8814596e+00 1.2390194e+00 2.4548042e+00 8.7876634e-01 1.7158367e+00 1.8151170e+00 7.5016118e-01 7.8272551e-01 1.3241046e+00 1.5455843e+00 1.9524619e+00 3.2834453e+00 1.4300844e+00 5.8483448e-01 1.0263139e+00 2.4682518e+00 1.9911692e+00 1.0783755e+00 7.8808432e-01 1.5539983e+00 1.7882304e+00 1.6881750e+00 1.1268523e+00 1.8822939e+00 2.0779047e+00 1.5475657e+00 1.2810704e+00 1.1339991e+00 1.8534885e+00 9.0513514e-01 1.2087510e+00 3.4293561e+00 1.8554780e+00 3.4031864e+00 2.4420991e+00 2.9637920e+00 4.0403658e+00 1.1828717e+00 3.4998500e+00 2.6632870e+00 4.3954130e+00 2.8761203e+00 2.3399831e+00 3.0445122e+00 1.7890328e+00 2.3476777e+00 3.0401775e+00 2.6527529e+00 5.0321758e+00 4.1557153e+00 1.7943181e+00 3.4850530e+00 1.8421879e+00 4.0156620e+00 2.0769537e+00 3.3460838e+00 3.5735182e+00 2.0311110e+00 2.1883894e+00 2.6137665e+00 3.2724268e+00 3.5184202e+00 5.0524110e+00 2.6818545e+00 2.0664108e+00 1.9589565e+00 4.0923995e+00 3.3884524e+00 2.6885740e+00 2.0959739e+00 3.1948685e+00 3.2743590e+00 3.2448452e+00 1.8554780e+00 3.4633688e+00 3.5839347e+00 3.0150212e+00 2.1174980e+00 2.6708841e+00 3.2242395e+00 2.1262653e+00 2.3423978e+00 1.0035466e+00 2.2827159e+00 1.3153660e+00 1.8615039e+00 2.9298417e+00 1.3184035e+00 2.4022001e+00 1.8151170e+00 3.2315412e+00 1.7166676e+00 1.3595814e+00 1.9250594e+00 1.2570691e+00 1.5534985e+00 1.9352496e+00 1.4849081e+00 3.8359772e+00 3.2064915e+00 1.5410939e+00 2.3431625e+00 1.0302848e+00 2.9742645e+00 1.1013771e+00 2.1700970e+00 2.3919091e+00 9.7531402e-01 1.0396764e+00 1.5925492e+00 2.1393811e+00 2.4733960e+00 3.8624002e+00 1.6816960e+00 9.5650957e-01 1.0890388e+00 3.0080097e+00 2.2891398e+00 1.5007156e+00 9.6470639e-01 2.0543866e+00 2.1765531e+00 2.1487165e+00 1.0035466e+00 2.3180617e+00 2.4663563e+00 1.9433990e+00 1.3718709e+00 1.5414664e+00 2.1305612e+00 1.0107221e+00 1.7770053e+00 1.3000021e+00 1.3205171e+00 8.3930091e-01 1.8258497e+00 2.8432746e+00 1.7831595e+00 2.1193624e+00 1.2896554e+00 8.9496218e-01 1.6446727e+00 1.0946825e+00 2.1632524e+00 1.4041749e+00 5.4207852e-01 1.2077572e+00 2.1213447e+00 2.4069921e+00 2.9335119e+00 8.2206766e-01 1.6918279e+00 2.1851365e+00 1.7733720e+00 7.3278119e-01 1.4407642e+00 1.6273345e+00 1.3293020e+00 1.2924610e+00 1.7503171e+00 1.9276214e+00 2.3545376e+00 1.2450968e+00 1.8184976e+00 2.1894327e+00 1.8463545e+00 3.4893361e-01 1.0719022e+00 1.3834169e+00 1.0621362e+00 7.1740234e-01 1.0327832e+00 1.7770053e+00 6.9976890e-01 5.1210327e-01 9.9313181e-01 2.0841099e+00 1.0825311e+00 5.0208681e-01 1.3466860e+00 1.7937749e+00 8.2148003e-01 1.2268833e+00 2.4485240e+00 1.2563297e+00 1.9934469e+00 1.2524426e+00 2.8471336e+00 1.4358042e+00 7.3339246e-01 1.4342819e+00 4.9772204e-01 6.9457760e-01 1.4636741e+00 1.1233354e+00 3.5605638e+00 2.5755365e+00 1.2907457e+00 1.8667489e+00 3.7622328e-01 2.4814136e+00 6.2818221e-01 1.8112028e+00 2.1131807e+00 5.7672351e-01 7.9999102e-01 8.5275415e-01 1.9102507e+00 2.0267836e+00 3.6643554e+00 9.0168685e-01 8.3216780e-01 8.3306409e-01 2.5178144e+00 1.8656026e+00 1.2019259e+00 7.6362786e-01 1.6472011e+00 1.5943283e+00 1.7001179e+00 0.0000000e+00 1.8078806e+00 1.9570111e+00 1.3920954e+00 7.6195008e-01 1.1016806e+00 1.7729341e+00 7.1446962e-01 1.0816610e+00 7.3851064e-01 7.2248857e-01 3.0483776e+00 5.6342615e-01 1.3118081e+00 1.4889605e+00 9.8006369e-01 1.1737270e+00 4.2737382e-01 2.1131807e+00 1.7428500e+00 1.0543640e+00 8.5494999e-01 2.0376324e+00 1.3288928e+00 2.4590893e+00 5.9382214e-01 1.9576761e+00 9.8006369e-01 1.3739792e+00 8.5141186e-01 6.2055338e-01 1.3918500e+00 1.3907270e+00 9.7789352e-01 6.6861320e-01 6.5233704e-01 2.1059482e+00 9.8663349e-01 1.4035018e+00 1.7831595e+00 7.7880944e-01 1.4027992e+00 9.8677196e-01 1.5191564e+00 4.3798311e-01 6.8554305e-01 6.2111408e-01 1.7937749e+00 6.4241342e-01 9.9981032e-01 6.7780188e-01 1.6099640e+00 8.3640969e-01 1.4769275e+00 1.5684930e+00 6.3173774e-01 1.7302001e+00 2.0286216e+00 1.2711306e+00 1.0474897e+00 2.1700788e+00 8.2827027e-01 5.2290002e-01 7.5863433e-01 1.2491511e+00 1.0565061e+00 9.7542502e-01 3.3872939e-01 2.7982543e+00 2.0758695e+00 1.7342859e+00 1.1984110e+00 9.9693045e-01 1.8354727e+00 6.0840510e-01 1.1145077e+00 1.3082023e+00 5.2283051e-01 5.1857575e-01 4.7149050e-01 1.1471723e+00 1.3839439e+00 2.8837687e+00 5.8522871e-01 5.3667800e-01 9.0098101e-01 1.8496383e+00 1.3956631e+00 4.8016385e-01 6.2451737e-01 9.5139638e-01 1.0316097e+00 1.1168387e+00 8.2148003e-01 1.1408175e+00 1.3888569e+00 8.7588404e-01 9.9189360e-01 4.8124784e-01 1.3365773e+00 6.0566865e-01 1.4097462e+00 2.4566860e+00 1.1582635e+00 1.2895008e+00 1.6771691e+00 6.6244727e-01 8.5330525e-01 4.2110953e-01 1.5929148e+00 1.0739839e+00 5.6992880e-01 5.5102439e-01 2.4009228e+00 1.8321979e+00 2.1944657e+00 6.8299624e-01 1.3125970e+00 1.6253273e+00 1.0353506e+00 7.4661256e-01 1.1022402e+00 9.6950963e-01 8.7649478e-01 5.0731024e-01 1.1544356e+00 1.2559800e+00 2.5390784e+00 4.9009568e-01 1.1268617e+00 1.4819274e+00 1.4649720e+00 9.9544409e-01 6.0942760e-01 9.8006526e-01 5.9589853e-01 4.3937875e-01 6.7904052e-01 1.2268833e+00 6.0365341e-01 8.3354038e-01 4.3719837e-01 1.3221954e+00 4.2932160e-01 1.0251165e+00 9.7833010e-01 3.6949435e+00 6.0653347e-01 1.6944472e+00 1.5821553e+00 1.6484241e+00 1.7861438e+00 1.1497964e+00 2.7265330e+00 2.4114658e+00 1.7100754e+00 1.5191564e+00 1.8544941e+00 9.8143688e-01 2.9288318e+00 1.1208167e+00 2.6441923e+00 4.9772204e-01 2.0065422e+00 1.3857067e+00 8.4632640e-01 2.0655380e+00 2.0890644e+00 1.6229726e+00 9.3175410e-01 6.4813570e-01 1.8882412e+00 1.6282537e+00 2.0045623e+00 2.3010727e+00 4.0443437e-01 1.9471640e+00 1.6420881e+00 2.2200703e+00 1.1092092e+00 1.3077539e+00 1.2486828e+00 2.4485240e+00 1.1714086e+00 1.4815200e+00 1.3709657e+00 2.1645801e+00 1.5528645e+00 2.0592947e+00 2.2565403e+00 3.2107953e+00 2.2989490e+00 4.0089941e+00 2.5709804e+00 1.9412285e+00 2.6814987e+00 1.0808305e+00 1.6177026e+00 2.5906314e+00 2.3241321e+00 4.7335798e+00 3.7356640e+00 1.5467170e+00 3.0855313e+00 1.1828955e+00 3.6907456e+00 1.7719327e+00 2.9776826e+00 3.3258154e+00 1.7290027e+00 1.8703975e+00 2.1032002e+00 3.0991288e+00 3.2379874e+00 4.8403184e+00 2.1396216e+00 1.8765527e+00 1.6420881e+00 3.7688016e+00 2.8977291e+00 2.3525703e+00 1.7721385e+00 2.8780663e+00 2.8053505e+00 2.9124074e+00 1.2563297e+00 3.0197298e+00 3.1129968e+00 2.6135061e+00 1.7341866e+00 2.3184326e+00 2.7661123e+00 1.7090768e+00 1.2067996e+00 1.8641580e+00 1.3940244e+00 1.3162189e+00 8.8198158e-01 2.2794791e+00 2.1012392e+00 1.5525661e+00 1.0918469e+00 2.2063254e+00 1.1211328e+00 2.4017426e+00 1.1211328e+00 2.2284888e+00 6.3765570e-01 1.5168126e+00 1.2830542e+00 7.2263841e-01 1.5939137e+00 1.6681833e+00 1.2435436e+00 4.6557224e-01 3.1271814e-01 2.2147971e+00 1.2909648e+00 1.4589882e+00 1.7351918e+00 8.5359653e-01 1.8877628e+00 1.2647627e+00 1.8001617e+00 9.2780124e-01 1.2301583e+00 1.1566759e+00 1.9934469e+00 1.1506301e+00 1.5274241e+00 1.1815770e+00 1.6939440e+00 1.2016246e+00 1.9452063e+00 1.8368886e+00 2.7696320e+00 1.7002168e+00 6.6444642e-01 1.2360344e+00 1.3162958e+00 1.5606124e+00 1.8019802e+00 1.1903794e+00 3.3139779e+00 1.5262653e+00 1.2463647e+00 1.7598642e+00 1.6038123e+00 1.5053088e+00 8.4040822e-01 1.8873059e+00 1.7273593e+00 1.0791305e+00 1.4542898e+00 8.8167165e-01 1.3277924e+00 1.1132823e+00 3.3576107e+00 9.4738284e-01 1.0119180e+00 9.3046944e-01 1.8017473e+00 2.2743623e+00 1.4404916e+00 1.5380438e+00 1.4761813e+00 1.5955619e+00 1.5999471e+00 1.2524426e+00 1.7473897e+00 2.0612423e+00 1.3691763e+00 6.7534282e-01 1.2539581e+00 2.2701663e+00 1.5558007e+00 1.5218782e+00 2.4628184e+00 1.5932824e+00 3.2458126e+00 2.5692387e+00 1.4348047e+00 1.8937847e+00 9.2060977e-01 2.4408782e+00 3.8250534e+00 1.0499398e+00 2.8336242e+00 2.0769357e+00 2.6064495e+00 1.0813975e+00 1.3021456e+00 2.4993477e+00 2.2323451e+00 2.1662332e+00 1.8260680e+00 2.0200580e+00 1.1770826e+00 2.1383117e+00 2.5736499e+00 3.0399892e+00 1.5323598e+00 1.2212784e+00 1.7944590e+00 2.3235953e+00 1.3759094e+00 1.3385913e+00 1.3604806e+00 2.8471336e+00 1.0797751e+00 9.4588685e-01 1.6150266e+00 2.9366827e+00 1.8217819e+00 1.3773939e+00 2.3541561e+00 1.1723315e+00 6.4232366e-01 1.8822595e+00 1.3566020e+00 4.2656951e-01 5.7691891e-01 2.2149281e+00 2.2825823e+00 2.4730728e+00 7.0958226e-01 1.4300979e+00 1.9425292e+00 1.2122797e+00 4.9430028e-01 1.0215032e+00 1.0391769e+00 7.2638147e-01 9.8137813e-01 1.1659675e+00 1.5397131e+00 2.3056726e+00 1.0072799e+00 1.1569911e+00 1.6871910e+00 1.6699010e+00 7.9127668e-01 4.3341454e-01 8.2155022e-01 5.7672351e-01 6.8304299e-01 6.6412342e-01 1.4358042e+00 7.0097130e-01 8.1019167e-01 6.5485710e-01 1.6386105e+00 4.6472955e-01 7.2626021e-01 8.9802947e-01 8.9083207e-01 9.8663349e-01 1.0101003e+00 1.2666796e+00 7.2340544e-01 3.1120413e+00 1.9012831e+00 1.3662822e+00 1.4214310e+00 1.0271885e+00 1.7789322e+00 2.8835410e-01 1.4717950e+00 1.5613089e+00 4.5716421e-01 8.2373020e-01 3.8830315e-01 1.2833190e+00 1.3103990e+00 3.1817011e+00 4.8644514e-01 5.9610506e-01 8.0162421e-01 1.8503155e+00 1.7547273e+00 9.3865015e-01 8.9973730e-01 1.1346946e+00 1.2001902e+00 1.2260535e+00 7.3339246e-01 1.3976606e+00 1.6479131e+00 9.4228329e-01 5.0621589e-01 7.1671402e-01 1.7153988e+00 9.3451915e-01 1.7865803e+00 1.3700593e+00 7.2638147e-01 5.3458689e-01 2.2505972e+00 1.6524504e+00 2.2440955e+00 5.5576380e-01 1.5638518e+00 1.3688560e+00 1.0552128e+00 7.1143905e-01 8.2518769e-01 1.0245496e+00 9.9235657e-01 6.7030885e-01 8.3156325e-01 9.6025744e-01 2.3337038e+00 6.8299624e-01 1.1166319e+00 1.5524781e+00 1.1685901e+00 1.1719135e+00 6.6412342e-01 1.1159239e+00 2.6643250e-01 4.7488466e-01 4.3341454e-01 1.4342819e+00 5.7691891e-01 8.8366512e-01 3.3492202e-01 1.3576953e+00 4.2110953e-01 1.2033093e+00 1.1777985e+00 8.7819565e-01 1.8719964e+00 1.5530949e+00 3.9773949e+00 2.6834336e+00 1.0194189e+00 2.2401597e+00 7.0463400e-01 2.6908242e+00 8.9981614e-01 2.2443541e+00 2.5055082e+00 9.6168382e-01 1.2786676e+00 1.1515752e+00 2.2564140e+00 2.2581551e+00 4.0837847e+00 1.1737270e+00 1.1984110e+00 1.0100915e+00 2.7760526e+00 2.2855612e+00 1.6668656e+00 1.2419907e+00 2.0207624e+00 1.9399964e+00 2.0427084e+00 4.9772204e-01 2.1876191e+00 2.3343528e+00 1.7191609e+00 7.3633268e-01 1.5086315e+00 2.2088314e+00 1.2082987e+00 1.1857824e+00 1.2636762e+00 3.3874427e+00 2.5564450e+00 1.8349829e+00 1.6578570e+00 5.8813453e-01 2.5222553e+00 1.0240850e+00 1.6676963e+00 2.1419072e+00 9.3827844e-01 9.8741108e-01 8.7169308e-01 2.0802526e+00 2.1175243e+00 3.5451448e+00 8.2097460e-01 1.3248988e+00 1.4633215e+00 2.4116155e+00 1.5361480e+00 1.2935378e+00 9.5818710e-01 1.5578153e+00 1.3192053e+00 1.5035025e+00 6.9457760e-01 1.5912796e+00 1.6259926e+00 1.1892978e+00 1.1294987e+00 1.0978602e+00 1.4813076e+00 9.1948999e-01 8.1819403e-01 2.2419326e+00 2.2807501e+00 2.5846003e+00 6.4497192e-01 1.4107908e+00 2.0247998e+00 1.3509199e+00 5.5183182e-01 1.2328847e+00 1.1911894e+00 9.0810653e-01 9.7397874e-01 1.4379681e+00 1.6702453e+00 2.3957048e+00 9.4716675e-01 1.4061835e+00 1.8616601e+00 1.6979390e+00 5.2290002e-01 7.0376604e-01 9.7757519e-01 6.9976890e-01 4.8012872e-01 6.5622658e-01 1.4636741e+00 5.9074344e-01 5.5183182e-01 5.8926015e-01 1.7101283e+00 6.2055338e-01 5.2374483e-01 1.0096792e+00 2.4983023e+00 2.0024830e+00 2.0009022e+00 9.4244262e-01 1.2563297e+00 1.6864324e+00 8.0788963e-01 8.3930091e-01 1.0038277e+00 7.0810362e-01 5.9074344e-01 6.2055338e-01 9.0121804e-01 1.2356595e+00 2.5673851e+00 7.1082758e-01 6.9066640e-01 1.1671832e+00 1.6263298e+00 1.2372185e+00 2.6033464e-01 7.2248857e-01 6.6653737e-01 8.5606908e-01 8.7588404e-01 1.1233354e+00 9.0810653e-01 1.1795009e+00 7.1867388e-01 1.2188386e+00 3.1239235e-01 1.1894366e+00 7.5921691e-01 2.7729820e+00 4.4273104e+00 1.7851048e+00 3.5860697e+00 2.3211451e+00 3.2572853e+00 1.7681972e+00 1.6466818e+00 3.1677578e+00 2.9074188e+00 2.8617360e+00 2.1557081e+00 2.3917474e+00 3.9487224e-01 2.8587345e+00 3.1370513e+00 3.5889276e+00 1.8806886e+00 2.0412779e+00 2.4100318e+00 3.0088533e+00 2.0247746e+00 2.1265123e+00 2.0926464e+00 3.5605638e+00 1.8217810e+00 1.8066213e+00 2.3669524e+00 3.5958907e+00 2.5091153e+00 2.1661990e+00 3.0374915e+00 2.7063291e+00 1.8195505e+00 2.8431665e+00 6.1655427e-01 2.1507483e+00 2.1438129e+00 1.7230435e+00 2.3108260e+00 2.5097011e+00 1.8135469e+00 1.5638528e+00 9.0791603e-01 2.8200316e+00 1.7992895e+00 2.2827159e+00 2.3805592e+00 1.0279218e+00 2.6120156e+00 2.2030084e+00 2.6289851e+00 1.7477225e+00 1.8297977e+00 1.8176515e+00 2.5755365e+00 1.8486085e+00 2.1438129e+00 1.7993706e+00 2.0846858e+00 2.0084695e+00 2.7218129e+00 2.6544629e+00 2.7850656e+00 1.6064410e+00 2.7362607e+00 1.2723027e+00 2.8153418e+00 2.8097763e+00 1.4630671e+00 1.8911656e+00 1.6976298e+00 2.3931138e+00 2.3316649e+00 4.4654565e+00 1.7621453e+00 1.4315442e+00 9.9921804e-01 3.0176875e+00 3.0491088e+00 2.1855415e+00 1.8898572e+00 2.4817766e+00 2.5552736e+00 2.5674482e+00 1.2907457e+00 2.7588865e+00 3.0041677e+00 2.2870308e+00 9.4057729e-01 2.0520412e+00 2.9779211e+00 1.8911656e+00 1.9172403e+00 1.5033800e+00 1.5778466e+00 4.2450569e-01 7.6773108e-01 1.5016932e+00 1.3345218e+00 1.1346946e+00 1.0902078e+00 1.2416734e+00 1.9196757e+00 1.1117653e+00 1.6095257e+00 2.0596575e+00 1.0943114e+00 8.7072347e-01 9.2729770e-01 1.4434261e+00 3.8830315e-01 3.6319073e-01 4.1088655e-01 1.8667489e+00 1.6562722e-01 4.2450569e-01 5.9279023e-01 1.8877121e+00 8.2518769e-01 9.7789352e-01 1.4886316e+00 2.7335291e+00 9.1459005e-01 1.8213026e+00 2.2454046e+00 7.7259801e-01 8.0376328e-01 1.0525811e+00 2.1168634e+00 2.2814168e+00 3.7089872e+00 1.0767714e+00 1.0755005e+00 1.1631285e+00 2.6946772e+00 1.7497347e+00 1.2634840e+00 7.1971771e-01 1.7438018e+00 1.6356845e+00 1.7637315e+00 3.7622328e-01 1.8511762e+00 1.9311191e+00 1.4699785e+00 1.1016806e+00 1.1928774e+00 1.6354514e+00 6.5233704e-01 2.0052498e+00 1.7681972e+00 1.2007144e+00 2.1235855e+00 2.2484715e+00 1.6942544e+00 1.0546367e+00 5.1386894e-01 2.3251407e+00 1.7093881e+00 2.0273081e+00 2.2255325e+00 6.9493020e-01 2.3316649e+00 1.8640280e+00 2.3781796e+00 1.4043141e+00 1.6126002e+00 1.5456113e+00 2.4814136e+00 1.5467508e+00 1.8811164e+00 1.5963715e+00 2.0694478e+00 1.7422855e+00 2.4276706e+00 2.4143104e+00 1.5837613e+00 1.7028549e+00 2.6643250e-01 7.3283576e-01 6.1619693e-01 1.4102704e+00 1.5157660e+00 3.3107238e+00 7.0702759e-01 4.6964680e-01 7.3731902e-01 2.0564363e+00 1.8389778e+00 9.9058911e-01 7.8305765e-01 1.2692102e+00 1.3637808e+00 1.3483908e+00 6.2818221e-01 1.5635907e+00 1.7874832e+00 1.0817627e+00 4.8284931e-01 7.9664122e-01 1.7693113e+00 8.5141186e-01 7.7538587e-01 1.4522625e+00 1.1678338e+00 1.2100516e+00 1.1294987e+00 1.4712028e+00 1.8985225e+00 1.2171256e+00 1.5155373e+00 1.9939611e+00 1.4342819e+00 6.6653737e-01 7.1511757e-01 1.2680818e+00 5.4772790e-01 6.0868934e-01 6.7484334e-01 1.8112028e+00 3.8639663e-01 5.2413598e-01 7.9227302e-01 1.9656037e+00 7.9656884e-01 7.1789533e-01 1.2970125e+00 1.6649242e+00 1.5382030e+00 1.4107908e+00 5.4248468e-01 9.6424206e-01 1.6581712e+00 1.4527629e+00 1.5643033e+00 2.0014001e+00 1.0048958e+00 1.4365091e+00 1.0328871e+00 1.6659456e+00 6.7424840e-01 1.0427348e+00 9.3481345e-01 2.1131807e+00 8.1596583e-01 1.1349095e+00 1.1009910e+00 2.0312552e+00 1.0961859e+00 1.4886316e+00 1.7139439e+00 4.8016385e-01 6.4687084e-01 1.4356300e+00 1.6309773e+00 3.2287360e+00 7.3391501e-01 4.4499696e-01 8.4121419e-01 2.1133414e+00 1.6592561e+00 8.3333283e-01 5.2066928e-01 1.2097457e+00 1.2911242e+00 1.2850973e+00 5.7672351e-01 1.4812088e+00 1.6720834e+00 1.0285902e+00 7.2340544e-01 6.8124108e-01 1.5683551e+00 6.1067563e-01 8.0990117e-01 1.4468934e+00 1.7768340e+00 2.9867606e+00 8.8098199e-01 6.6217390e-01 1.1327663e+00 2.1506380e+00 1.2980342e+00 5.4779717e-01 1.3340137e-01 1.1051628e+00 1.1634940e+00 1.1952607e+00 7.9999102e-01 1.2953104e+00 1.4320028e+00 9.9155078e-01 1.1867923e+00 5.7526462e-01 1.1726810e+00 2.6680274e-01 1.2602457e+00 1.2678174e+00 2.9703757e+00 1.3103399e-01 8.4439576e-01 1.0887336e+00 1.6811909e+00 1.4435947e+00 7.9778097e-01 8.9789119e-01 9.2528705e-01 8.7435479e-01 9.9613800e-01 8.5275415e-01 1.0871867e+00 1.3186900e+00 6.8124108e-01 8.2317311e-01 5.4397563e-01 1.4334280e+00 9.0121513e-01 6.7419212e-01 2.1234744e+00 1.3330747e+00 1.2524426e+00 1.6423187e+00 1.1112287e+00 1.7731481e+00 1.0412209e+00 1.5779602e+00 8.1552831e-01 1.2367326e+00 1.0877340e+00 1.9102507e+00 1.1360631e+00 1.4957547e+00 1.1495900e+00 1.6944472e+00 1.0511712e+00 1.7894533e+00 1.6403454e+00 2.3936810e+00 1.3012342e+00 1.5364148e+00 1.7852089e+00 7.8659640e-01 2.0467146e+00 1.4387140e+00 1.9055720e+00 1.0341095e+00 1.3049404e+00 1.1996837e+00 2.0267836e+00 1.2898173e+00 1.6473842e+00 1.2092432e+00 1.6223502e+00 1.2928268e+00 2.1087983e+00 1.9576761e+00 2.9790337e+00 3.1661621e+00 3.6343153e+00 1.9094387e+00 2.2505084e+00 2.4932991e+00 3.0919113e+00 2.0983540e+00 2.2774299e+00 2.1822207e+00 3.6643554e+00 1.9784646e+00 2.0041121e+00 2.4741311e+00 3.6564145e+00 2.5932898e+00 2.3540393e+00 3.1383476e+00 9.6758101e-01 1.2012033e+00 1.6658010e+00 1.4135473e+00 8.6985276e-01 9.6249568e-01 9.3451915e-01 8.2380019e-01 9.6993876e-01 9.0168685e-01 1.0632334e+00 1.2723027e+00 6.4232366e-01 8.7376399e-01 5.8942278e-01 1.4153467e+00 9.6559725e-01 6.0709980e-01 2.1192618e+00 1.8400866e+00 8.3619405e-01 7.2626021e-01 1.2848171e+00 1.4775384e+00 1.4500356e+00 8.3216780e-01 1.5874797e+00 1.8427499e+00 1.2442620e+00 8.6985276e-01 8.3878265e-01 1.7484907e+00 7.7500385e-01 2.4608044e+00 2.2758533e+00 1.3186900e+00 1.1601403e+00 1.7655861e+00 1.8899115e+00 1.9324044e+00 8.3306409e-01 2.0122449e+00 2.2830055e+00 1.6787550e+00 8.1019167e-01 1.3243478e+00 2.1959912e+00 1.1244567e+00 1.9511379e+00 1.7500667e+00 2.2774574e+00 1.1011934e+00 1.2684800e+00 1.1435764e+00 2.5178144e+00 1.1861264e+00 1.4342819e+00 1.3109392e+00 2.2026274e+00 1.5858048e+00 2.0711809e+00 2.3400013e+00 1.0557584e+00 1.3438727e+00 1.0821769e+00 8.4383266e-01 1.0493821e+00 1.8656026e+00 7.8957903e-01 5.5399712e-01 1.0737552e+00 2.2030214e+00 1.1115276e+00 2.1119253e-01 1.3352177e+00 6.6627781e-01 7.2272795e-01 8.6751530e-01 9.1936743e-01 1.2019259e+00 8.7588404e-01 1.0946184e+00 8.0162421e-01 1.4236959e+00 4.0664863e-01 9.8463602e-01 6.8496652e-01 1.2266388e+00 1.2615426e+00 1.3010124e+00 7.6362786e-01 1.4014424e+00 1.5148689e+00 1.0932736e+00 1.2210779e+00 6.9618131e-01 1.2059294e+00 2.0855006e-01 4.7509249e-01 3.1239235e-01 1.6472011e+00 4.6557224e-01 7.5810578e-01 4.3937875e-01 1.5999820e+00 5.6262711e-01 1.1233867e+00 1.3019241e+00 3.9472619e-01 1.5943283e+00 3.3742167e-01 4.8284931e-01 3.4893361e-01 1.6410601e+00 6.6154242e-01 9.3451915e-01 1.2980649e+00 1.7001179e+00 5.2283051e-01 6.7484334e-01 3.3872939e-01 1.6485749e+00 6.6653737e-01 1.1055440e+00 1.3931316e+00 1.8078806e+00 1.9570111e+00 1.3920954e+00 7.6195008e-01 1.1016806e+00 1.7729341e+00 7.1446962e-01 3.8639663e-01 6.2027457e-01 1.8723846e+00 8.0990117e-01 9.0447834e-01 1.4243850e+00 7.9227302e-01 2.1007225e+00 1.0230346e+00 7.1789533e-01 1.5391678e+00 1.3603920e+00 4.6137216e-01 1.1083720e+00 1.1686836e+00 1.1908452e+00 2.1561807e+00 1.2583645e+00 1.0722301e+00 7.7259801e-01 1.2001902e+00 diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-seuclidean-ml.txt b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-seuclidean-ml.txt new file mode 100644 index 0000000000000000000000000000000000000000..ce80cb1ead3f88bec0fbaf6d48cb4fc584e52168 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-seuclidean-ml.txt @@ -0,0 +1 @@ + 1.4330520e+01 1.4635426e+01 1.3450855e+01 1.4761140e+01 1.3508642e+01 1.5434417e+01 1.3887693e+01 1.5166776e+01 1.3966038e+01 1.4950451e+01 1.4564587e+01 1.3834201e+01 1.4347008e+01 1.5641962e+01 1.4689053e+01 1.4418720e+01 1.4545856e+01 1.4151822e+01 1.4669017e+01 1.5150750e+01 1.3770166e+01 1.3288969e+01 1.4048191e+01 1.4049959e+01 1.4164158e+01 1.3727834e+01 1.4074687e+01 1.4321303e+01 1.2497330e+01 1.3820273e+01 1.4441030e+01 1.4780222e+01 1.2504339e+01 1.5022245e+01 1.4263650e+01 1.3704507e+01 1.3694385e+01 1.3667517e+01 1.3177468e+01 1.4391931e+01 1.4893903e+01 1.4475753e+01 1.4440707e+01 1.3603096e+01 1.6889651e+01 1.4731174e+01 1.3337775e+01 1.5187532e+01 1.5667271e+01 1.4226037e+01 1.4203554e+01 1.5272898e+01 1.6031460e+01 1.5991549e+01 1.1855060e+01 1.4844776e+01 1.2475182e+01 1.4408126e+01 1.4836870e+01 1.3472986e+01 1.4089281e+01 1.1018298e+01 1.3183296e+01 1.4590802e+01 1.4404230e+01 1.2717623e+01 1.3983283e+01 1.4017133e+01 1.4608005e+01 1.4402553e+01 1.3977803e+01 1.4091040e+01 1.3977459e+01 1.2630449e+01 1.4160109e+01 1.3029417e+01 1.2654432e+01 1.2794946e+01 1.3194978e+01 1.4378745e+01 1.2431908e+01 1.3852651e+01 1.3748358e+01 1.4003568e+01 1.5066681e+01 1.5192826e+01 1.4370013e+01 1.5792545e+01 1.3547546e+01 1.4411543e+01 1.4794215e+01 1.4924312e+01 1.4789153e+01 1.4875055e+01 1.4208537e+01 1.2786148e+01 1.4882476e+01 1.3302010e+01 1.4354774e+01 1.4542129e+01 1.5889633e+01 1.2928185e+01 1.4877868e+01 1.2890902e+01 1.4406165e+01 1.4498123e+01 1.4303273e+01 1.3207002e+01 1.3954732e+01 1.4841248e+01 1.5427799e+01 1.4363463e+01 1.3976277e+01 1.4284878e+01 1.4457991e+01 1.3369469e+01 1.5246610e+01 1.4487573e+01 1.4525176e+01 1.4505865e+01 1.5037347e+01 1.3834927e+01 1.3758988e+01 1.3424987e+01 1.4914766e+01 1.3783923e+01 1.3434291e+01 1.2895927e+01 1.3870360e+01 1.3342977e+01 1.3094322e+01 1.3057847e+01 1.3322375e+01 1.4940650e+01 1.4476829e+01 1.4197503e+01 1.4597035e+01 1.2963234e+01 1.4011414e+01 1.3181409e+01 1.3339615e+01 1.3928735e+01 1.3508015e+01 1.3170749e+01 1.3529133e+01 1.3454724e+01 1.4883437e+01 1.4564565e+01 1.2474313e+01 1.4435790e+01 1.5285703e+01 1.3701736e+01 1.3578312e+01 1.4807311e+01 1.4281072e+01 1.2920213e+01 1.4427803e+01 1.1408611e+01 1.4097334e+01 1.2868115e+01 1.3903683e+01 1.3800332e+01 1.3439339e+01 1.4062651e+01 1.3242107e+01 1.4400424e+01 1.3826132e+01 1.5991146e+01 1.3118258e+01 1.5377390e+01 1.2858378e+01 1.5249567e+01 1.4081585e+01 1.4458052e+01 1.4175623e+01 1.4850069e+01 1.5506668e+01 1.5014770e+01 1.4337030e+01 1.5214705e+01 1.4803729e+01 1.3188675e+01 1.3437739e+01 1.3409394e+01 1.4607386e+01 1.5394271e+01 1.5946451e+01 1.3769364e+01 1.4181208e+01 1.2551765e+01 diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-spearman-ml.txt b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-spearman-ml.txt new file mode 100644 index 0000000000000000000000000000000000000000..b50fe3af1912d20aaa737eedc1b6e096a7005876 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/pdist-spearman-ml.txt @@ -0,0 +1 @@ + 9.3540954e-01 9.7904590e-01 8.6703870e-01 1.1569997e+00 8.7174317e-01 1.0627183e+00 9.1272727e-01 1.1593999e+00 9.7573357e-01 1.0072127e+00 1.0536814e+00 9.6276028e-01 9.7700570e-01 1.1513951e+00 1.0719592e+00 9.2178818e-01 1.0004680e+00 9.3689769e-01 9.8205821e-01 1.0332673e+00 9.4517852e-01 8.9437744e-01 9.7556556e-01 9.0460246e-01 9.7210921e-01 9.2230423e-01 9.9605161e-01 9.6852085e-01 8.4162016e-01 9.6667267e-01 9.7759376e-01 9.9757576e-01 7.6992499e-01 1.0151695e+00 9.8691869e-01 9.0325833e-01 8.6665467e-01 8.8844884e-01 8.4553255e-01 9.7700570e-01 9.5159916e-01 9.8906691e-01 1.0551935e+00 9.1973597e-01 1.3266247e+00 1.0982778e+00 8.4531653e-01 1.0887369e+00 1.0984938e+00 9.9851185e-01 9.0701470e-01 1.0639304e+00 1.2392919e+00 1.1422502e+00 8.1725773e-01 1.1844944e+00 7.8219022e-01 1.0817162e+00 1.2196100e+00 1.0003120e+00 1.0164536e+00 7.0724272e-01 9.7981398e-01 1.1134953e+00 1.0671107e+00 9.3600960e-01 9.9984398e-01 1.0356916e+00 1.1248005e+00 1.0696310e+00 1.0634263e+00 9.6472847e-01 9.9365137e-01 8.5724572e-01 1.1257846e+00 8.9930993e-01 9.4903090e-01 9.0667867e-01 9.1231923e-01 1.0573777e+00 9.0105011e-01 9.5255926e-01 1.0177978e+00 1.0606901e+00 1.1966997e+00 1.0891929e+00 1.0085089e+00 1.2640264e+00 9.3246925e-01 1.0198020e+00 1.2055806e+00 1.1237924e+00 1.1060666e+00 1.0517252e+00 1.0684668e+00 7.6844884e-01 1.0572697e+00 8.7373537e-01 9.6283228e-01 9.9350735e-01 1.2412601e+00 7.6322832e-01 1.0298950e+00 8.6148215e-01 1.0042724e+00 9.7012901e-01 9.3712571e-01 8.5845785e-01 8.5862586e-01 1.0336634e+00 1.0955536e+00 9.5302730e-01 9.8696670e-01 1.0633063e+00 1.0026643e+00 9.6380438e-01 1.1711251e+00 9.9273927e-01 1.0260906e+00 1.0863966e+00 1.0482808e+00 9.0361836e-01 9.2358836e-01 8.7794779e-01 1.2461206e+00 9.2985299e-01 1.0418962e+00 9.4660666e-01 9.5636364e-01 9.0646265e-01 9.9113111e-01 8.3027903e-01 9.3341734e-01 1.1378938e+00 1.0548215e+00 1.0086889e+00 1.1998920e+00 8.6063006e-01 1.0255506e+00 8.4786079e-01 1.0090729e+00 9.2542454e-01 9.5176718e-01 9.3477348e-01 9.0091809e-01 9.6404440e-01 1.1158716e+00 9.9614761e-01 7.7682568e-01 1.0605461e+00 1.0895650e+00 9.0065407e-01 8.7173117e-01 9.9821182e-01 1.2165617e+00 8.6127813e-01 1.1111071e+00 7.9015902e-01 1.0433843e+00 8.6510651e-01 1.0019202e+00 1.0154815e+00 9.4381038e-01 9.8646265e-01 1.0062526e+00 9.7426943e-01 9.8191419e-01 1.3038944e+00 8.6277828e-01 1.0830243e+00 8.6851485e-01 1.1192559e+00 9.9120312e-01 9.6540054e-01 9.1072307e-01 1.1775698e+00 1.1139154e+00 1.1083468e+00 9.9593159e-01 1.0825923e+00 1.1115032e+00 9.7430543e-01 9.5605161e-01 9.2800480e-01 9.4369037e-01 1.1136034e+00 1.1382898e+00 9.5937594e-01 9.8843084e-01 7.4563456e-01 diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/random-bool-data.txt b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/random-bool-data.txt new file mode 100644 index 0000000000000000000000000000000000000000..df0d838f517f6c0afd8954ad87bc83886e0e3de4 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/random-bool-data.txt @@ -0,0 +1,100 @@ +0 1 1 0 1 0 0 1 0 0 0 1 0 0 0 0 0 1 0 0 1 0 0 0 1 1 0 0 1 1 +1 1 1 1 1 1 1 0 0 1 1 1 0 0 0 0 1 0 1 0 1 1 1 0 1 0 1 1 1 1 +0 1 0 1 1 0 0 1 1 1 1 0 1 0 0 1 1 0 1 1 1 0 1 1 0 1 1 1 0 1 +1 1 1 0 0 1 1 0 0 1 1 1 0 0 1 1 0 1 1 1 0 1 1 0 0 0 0 1 0 0 +1 0 0 0 0 1 1 0 1 1 0 1 0 0 0 0 1 0 0 1 0 1 0 0 1 1 1 1 0 0 +1 0 1 1 0 0 0 1 1 1 1 1 0 1 1 0 1 0 1 0 1 0 0 0 0 0 0 0 1 1 +0 1 0 0 1 0 0 0 1 0 0 1 1 0 0 0 0 1 1 0 0 1 0 1 1 1 1 0 1 0 +1 0 1 1 1 0 0 0 0 1 1 0 0 0 0 1 0 1 0 0 0 1 1 1 0 1 0 0 1 0 +1 1 1 0 0 1 1 0 0 1 0 0 1 0 0 1 0 1 1 0 1 1 0 1 1 1 0 0 1 1 +1 1 0 1 0 0 1 1 1 1 1 1 1 0 1 0 1 1 1 1 0 0 0 0 0 0 1 1 0 0 +1 0 1 0 1 1 0 1 1 0 1 1 0 1 1 1 1 0 0 1 1 0 0 0 0 1 0 0 0 0 +1 1 1 1 0 1 0 0 0 0 0 1 0 1 1 1 1 0 1 1 1 1 1 1 0 1 0 1 1 1 +1 1 1 1 1 1 1 1 1 0 1 1 0 0 1 0 1 0 1 0 1 0 0 0 1 0 0 1 0 1 +0 1 1 0 0 1 1 0 0 0 0 1 0 1 1 0 1 0 1 0 1 1 0 1 0 0 1 1 1 1 +1 0 0 1 0 0 1 0 1 0 0 1 0 0 0 1 1 0 0 0 1 0 1 0 0 1 1 0 1 1 +1 0 0 1 1 0 0 1 1 0 0 1 1 1 1 1 1 1 1 0 1 1 1 1 0 1 1 1 0 0 +1 1 0 0 1 0 0 0 1 0 0 1 0 1 0 0 1 0 1 1 0 1 0 0 0 1 1 1 1 1 +0 0 0 1 1 1 1 1 0 1 0 1 1 1 1 0 0 1 1 1 1 1 0 0 1 0 1 0 0 0 +1 0 1 1 0 1 0 0 1 0 0 1 0 0 1 0 0 0 1 0 0 1 1 1 0 1 1 0 1 1 +0 0 0 0 1 0 1 0 1 1 0 0 1 0 0 0 0 0 0 0 0 1 1 1 0 0 0 1 1 1 +0 1 0 0 1 1 0 0 1 1 1 0 0 0 1 0 0 0 0 1 1 0 0 1 0 1 1 0 1 0 +1 0 1 0 1 1 1 0 0 0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 1 1 1 0 1 1 +0 0 1 0 0 0 0 0 1 1 0 0 1 1 1 1 1 1 1 1 0 1 0 0 0 0 0 0 1 0 +0 1 0 1 1 1 0 1 1 1 0 1 0 1 1 1 0 0 0 0 1 1 1 0 0 1 1 0 0 1 +0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 1 1 0 0 0 0 1 0 1 1 0 1 1 0 0 +1 0 0 0 1 0 1 0 0 1 0 1 1 0 1 0 1 0 1 0 1 1 1 0 0 0 1 1 1 0 +1 0 0 0 1 1 1 0 0 1 0 1 1 1 0 0 0 1 1 1 0 0 0 0 1 0 0 0 1 1 +0 1 0 0 0 1 1 1 0 1 1 1 0 1 0 0 1 1 1 1 0 1 0 1 0 1 1 0 1 1 +0 0 1 0 0 0 0 0 0 0 0 1 1 0 1 0 0 1 0 0 0 1 0 1 0 1 0 1 0 1 +0 0 1 0 1 0 1 1 1 1 0 0 1 1 1 1 0 0 1 1 1 1 0 0 1 0 1 0 1 0 +1 1 0 1 1 1 1 1 0 1 0 0 0 1 1 1 0 1 0 0 0 1 1 0 1 0 0 0 0 1 +0 1 0 0 0 1 0 1 1 0 0 1 0 0 0 1 1 1 0 0 1 1 0 1 1 0 0 1 0 1 +1 1 0 0 0 0 0 1 1 0 1 1 0 0 1 0 1 1 0 0 0 1 0 1 0 1 0 1 0 1 +1 1 1 0 1 0 0 1 1 0 1 1 1 0 1 0 1 1 0 0 0 1 1 0 0 1 1 1 1 1 +0 1 0 0 1 1 0 0 1 1 1 1 0 1 0 1 0 1 1 1 0 1 1 0 1 1 0 0 1 0 +1 1 1 1 0 1 0 1 0 1 1 1 1 0 1 0 1 0 1 0 1 1 0 0 1 0 1 0 0 0 +0 0 0 0 1 1 1 0 1 1 0 0 1 1 1 1 0 1 0 1 1 1 1 1 1 0 0 0 0 0 +0 1 1 1 0 0 0 1 1 1 0 1 0 0 1 1 1 1 1 0 1 0 0 1 0 0 0 0 1 1 +0 1 0 0 1 1 1 1 0 0 1 0 1 0 1 1 0 0 1 0 0 1 1 0 0 0 0 1 0 0 +1 1 0 1 0 0 1 1 0 0 1 1 1 0 0 1 1 1 0 0 0 0 1 1 1 0 1 0 0 1 +0 1 1 0 1 0 1 1 0 0 0 1 1 0 0 0 0 0 0 1 0 0 1 1 0 1 0 0 1 1 +0 0 1 1 1 0 1 0 0 1 1 0 0 0 1 1 1 0 1 0 0 0 0 1 1 0 1 1 0 0 +1 0 1 1 1 1 1 1 1 1 0 1 0 0 0 1 0 1 0 0 0 1 1 0 0 1 0 0 0 0 +1 0 1 1 1 0 1 1 1 1 0 0 1 0 1 1 1 0 0 0 0 1 1 1 1 1 0 1 0 0 +1 0 0 0 1 1 1 0 1 1 0 0 1 1 1 0 1 0 0 1 0 1 0 1 1 1 0 0 0 1 +1 0 1 0 1 0 0 0 1 0 0 1 1 0 1 1 0 0 0 1 0 1 1 0 1 0 0 1 0 0 +0 1 1 0 1 0 1 1 1 1 1 0 0 0 0 1 0 1 0 0 1 1 1 1 0 1 0 1 1 1 +0 1 0 1 1 0 1 0 0 1 0 0 1 0 0 1 1 0 1 0 0 0 1 1 1 0 0 1 0 1 +1 0 1 1 1 0 1 0 1 0 1 1 0 1 0 0 0 1 0 0 0 0 0 0 0 0 1 1 0 1 +1 1 1 1 1 1 1 1 1 1 0 0 1 0 0 1 0 0 1 1 0 0 1 1 1 1 0 1 0 1 +1 1 1 1 0 0 0 1 0 1 1 0 0 0 1 1 0 0 1 1 1 1 0 0 0 1 0 1 0 0 +1 0 1 0 0 1 1 1 1 0 1 1 0 0 1 0 0 1 1 0 1 1 1 1 1 1 0 0 0 0 +0 1 1 0 0 1 0 0 0 0 0 1 0 1 0 0 1 1 0 1 0 1 0 0 0 1 0 0 1 0 +0 0 0 1 0 0 0 1 1 1 1 1 0 0 0 1 1 0 0 0 1 1 1 0 1 0 1 1 1 0 +1 1 0 0 0 0 1 1 1 0 1 0 1 1 1 0 0 1 0 0 0 0 0 0 1 1 1 0 0 0 +1 0 1 1 1 0 1 0 1 0 0 1 1 1 1 1 0 0 1 1 0 1 1 1 1 0 0 0 0 1 +0 0 1 1 1 0 0 0 0 1 0 0 0 0 0 0 1 0 0 1 0 0 1 1 1 0 0 1 0 0 +0 0 1 1 1 1 1 0 1 0 1 0 0 1 1 1 1 0 0 0 1 0 1 1 0 1 1 1 0 0 +0 0 0 0 0 1 0 0 1 1 0 1 1 0 0 0 0 1 0 1 1 0 0 1 0 0 1 0 1 0 +1 0 0 1 0 1 1 1 0 1 0 1 1 0 0 1 1 0 1 1 1 0 1 0 0 0 1 1 1 1 +0 0 0 1 0 0 0 0 0 0 1 1 0 0 0 0 0 1 0 0 0 1 1 0 0 1 1 0 0 0 +1 0 0 1 1 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 1 0 1 1 0 0 1 0 1 0 +0 1 0 1 1 1 1 1 0 1 0 1 1 0 0 1 1 0 1 1 0 1 1 0 1 1 0 0 0 1 +1 0 1 1 1 0 0 0 1 0 0 1 0 0 0 1 0 1 1 1 0 0 1 1 1 1 0 0 0 1 +0 1 0 0 1 1 1 1 1 1 0 0 1 0 0 1 1 0 1 0 1 0 1 1 1 0 1 1 0 1 +0 0 1 0 1 1 1 0 0 0 1 0 1 0 1 1 0 0 1 1 0 1 0 1 1 0 0 1 0 1 +0 1 1 1 1 1 0 0 0 0 0 1 0 1 1 1 1 1 0 1 1 1 0 0 1 0 0 1 1 1 +1 1 1 1 0 1 1 1 1 1 1 0 0 1 1 0 1 1 0 1 0 1 0 1 0 1 1 0 0 0 +1 0 0 0 1 0 1 0 0 0 1 0 1 0 0 1 0 1 1 1 1 1 0 0 1 1 1 1 1 0 +0 0 0 0 1 1 1 0 1 0 0 1 1 0 0 1 1 1 1 0 0 1 0 1 0 0 0 1 0 0 +1 1 1 1 1 0 0 0 1 1 0 0 1 1 1 1 0 1 0 1 0 0 0 0 1 1 0 1 1 0 +1 0 1 1 0 1 0 1 0 1 1 0 1 1 1 0 0 1 0 0 1 1 0 0 1 1 0 1 0 1 +1 1 1 1 1 0 0 0 0 1 0 0 1 0 0 0 1 0 0 0 0 0 1 0 0 0 0 1 1 1 +0 1 1 0 0 1 0 0 0 0 0 0 0 0 1 1 1 0 0 1 0 1 1 1 0 1 1 1 1 1 +1 1 1 0 1 1 1 1 1 0 0 0 0 1 0 0 1 0 1 0 1 1 1 0 0 1 0 0 1 1 +1 1 0 1 0 1 0 1 0 0 1 0 0 0 1 0 1 1 0 1 1 0 1 0 0 1 0 0 1 0 +1 0 1 1 0 0 1 1 0 0 1 1 0 0 0 1 1 0 0 1 0 0 0 0 0 1 0 1 1 0 +1 1 1 1 1 0 0 1 0 0 1 1 1 0 1 0 0 1 1 1 0 1 1 1 1 1 1 1 1 1 +1 0 1 1 0 0 1 1 0 1 1 1 0 0 0 1 0 1 0 0 0 1 1 1 1 1 0 0 1 0 +0 0 0 0 0 1 1 1 0 0 0 0 0 1 1 1 1 1 0 0 1 1 0 0 1 0 0 1 0 0 +1 1 1 0 0 0 0 1 0 1 1 1 1 1 1 1 0 1 0 1 1 1 1 0 1 1 1 0 1 0 +1 0 0 1 0 1 0 0 0 0 0 0 1 0 1 0 1 1 0 1 0 1 1 0 0 1 0 1 0 1 +1 0 0 0 1 0 1 1 0 1 0 0 0 1 0 1 0 0 0 0 1 1 1 0 1 0 1 1 0 1 +0 1 0 0 0 0 1 0 1 1 1 0 1 1 0 1 0 1 0 1 1 0 0 0 0 0 0 1 1 1 +0 1 0 0 1 0 1 1 0 0 0 0 1 1 0 1 1 1 0 0 1 1 0 0 1 0 1 0 0 0 +0 1 0 1 1 1 1 1 1 1 0 0 1 0 1 0 0 0 0 0 0 1 0 0 1 0 0 1 1 0 +0 0 0 1 0 0 1 0 0 1 1 0 0 0 0 0 0 0 1 0 0 1 0 0 0 1 0 1 0 0 +1 0 0 0 1 0 1 1 1 1 1 1 1 0 1 0 1 1 1 0 0 1 0 1 0 1 0 1 0 0 +1 0 0 0 1 0 1 0 0 0 1 1 0 0 0 1 1 0 0 1 1 1 1 1 1 0 1 1 1 0 +0 0 0 1 0 0 1 0 0 0 0 1 1 0 0 0 0 0 0 1 1 1 0 1 0 0 0 1 1 0 +1 0 0 0 0 0 1 0 1 0 1 0 0 1 1 1 0 1 1 1 0 0 1 0 1 1 1 0 1 0 +0 1 0 0 1 1 1 0 0 1 0 0 0 1 0 1 0 0 0 1 0 0 1 0 0 1 1 1 0 1 +0 0 0 1 1 0 1 0 1 0 1 0 0 0 1 1 1 0 1 1 0 0 0 1 1 0 0 1 0 1 +1 1 1 1 1 1 1 1 0 0 1 1 0 0 0 1 0 1 0 1 0 0 0 1 1 0 1 0 1 0 +0 1 1 0 0 0 1 1 0 0 1 1 0 1 1 1 1 1 0 1 0 0 0 0 1 0 1 0 0 0 +1 1 1 0 1 1 1 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 1 1 0 1 1 0 0 1 +0 0 1 0 0 1 0 0 1 0 0 0 1 0 0 1 1 1 1 1 1 1 0 1 0 0 0 1 1 0 +1 1 1 0 1 1 0 1 1 0 1 1 0 1 0 0 1 0 0 0 1 1 1 1 0 1 1 0 1 1 +0 0 1 1 1 0 0 0 0 1 1 0 0 1 1 0 1 0 1 0 0 1 0 0 0 1 1 0 0 1 +0 0 0 1 0 0 1 1 1 1 1 1 0 0 1 0 0 1 0 0 0 0 1 1 1 1 1 1 0 0 diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/random-double-data.txt b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/random-double-data.txt new file mode 100644 index 0000000000000000000000000000000000000000..039ac506f5590f953ffc6598c11197d3fade2bbd --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/random-double-data.txt @@ -0,0 +1,100 @@ +1.172993630434470589e+02 1.905532343119886605e+02 2.613653823499444115e+02 1.570270816248337269e+02 2.373767637129340642e+02 2.175366144750510671e+02 2.609909144757107242e+02 2.086671686166440622e+02 2.674986450118991002e+02 1.395992762090408235e+02 1.115453060949917159e+02 1.531034842395609701e+02 2.621042034264289668e+02 2.958729454449504033e+02 2.137960368830719062e+02 2.606436280968571282e+02 2.492136530687155869e+02 2.770806237064748530e+02 2.667325121892417883e+02 2.909243437665674037e+02 1.570328417294508085e+02 1.738762543815240917e+02 1.514157955792608163e+02 2.264748814818163396e+02 1.911869834397498380e+02 2.083706054660671043e+02 2.778115921852293013e+02 1.330374814391803397e+02 2.988697222234711717e+02 2.534167825404447001e+02 +2.090964891529778242e+02 1.322006104643973003e+02 1.443415945355371832e+02 2.991388772264980389e+02 1.649302344777914868e+02 2.839528641910463875e+02 1.677159709681393736e+02 2.597553832458208944e+02 2.861055547321268477e+02 1.866431671806918189e+02 2.131812134614140177e+02 1.881465139477683124e+02 1.271865139985419262e+02 1.821608865941132649e+02 2.793653846657656459e+02 1.745982981552271838e+02 2.440893905635109888e+02 1.926469151980868446e+02 2.752453852984189098e+02 1.333479229516146347e+02 1.756311805755703404e+02 2.039367615619088383e+02 2.441861159155101575e+02 2.136111324500645594e+02 2.893808960992043922e+02 2.723220466017930335e+02 2.367879096909125565e+02 2.831541206793258425e+02 2.017643187924728068e+02 1.293072046241175030e+02 +2.311242818257193221e+02 2.180694109009666306e+02 2.728791416531455525e+02 1.239345918565636993e+02 2.885729762050686418e+02 2.082619393005260804e+02 2.331416004257805525e+02 1.003112528445347778e+02 2.796331120515330895e+02 2.804679740148056339e+02 2.466936828597247597e+02 1.422398585800914361e+02 1.312115029632765015e+02 1.324417143647877708e+02 2.161716508991076466e+02 1.791489656100356171e+02 2.239038785146145472e+02 2.456511993086799919e+02 2.885023077068626662e+02 2.127338775308419940e+02 2.468090724782538246e+02 2.704135008577740109e+02 1.144148504575758665e+02 1.641571759150080538e+02 2.473349551308716343e+02 2.366620528761779667e+02 1.208143167141831498e+02 1.403705034199327599e+02 2.061073908129479548e+02 1.482034962693051057e+02 +1.938319500339997035e+02 2.000523826243218650e+02 1.356134735235139317e+02 1.224357428573656250e+02 1.262840705282213918e+02 1.112797762573139977e+02 1.727826315738305993e+02 2.199559683100150664e+02 1.817290208723558180e+02 2.185579898773881951e+02 1.772844462934412491e+02 1.589145011846130728e+02 1.017520743541414703e+02 2.836990856171782980e+02 1.265544072638776640e+02 2.503473341476423855e+02 2.178539278172635534e+02 2.063574432066289432e+02 1.473169457524925861e+02 1.112719632489760784e+02 1.195996070145015722e+02 1.345099678548529312e+02 2.992645259487585463e+02 2.692242364540683752e+02 2.139649193607747861e+02 2.313659165106297451e+02 2.524185025119667785e+02 2.678714004815313388e+02 1.111457754393238702e+02 1.296443575800298902e+02 +1.183944097426736306e+02 2.750477277868330930e+02 1.688558971333346790e+02 1.432283295687057034e+02 2.226043174503911359e+02 1.825124733235978169e+02 1.806485153578007612e+02 2.270256019866706936e+02 2.852913053786990076e+02 2.867562520175486043e+02 2.795056496733417362e+02 1.142488895870292822e+02 1.502985045661773427e+02 2.246907359526948937e+02 2.051158858061974115e+02 2.663351441156772808e+02 2.864853431806749882e+02 2.276548949573071070e+02 2.678087640355958001e+02 2.266463576941352187e+02 1.886763304826383774e+02 1.150603609957262563e+02 1.596187994714221929e+02 1.844565420383776484e+02 1.730173420200940768e+02 1.427940137102308995e+02 1.774757620992130001e+02 2.563086691508434001e+02 1.666317348809653822e+02 1.878143419608473437e+02 +1.642344698640436036e+02 1.591648429561690818e+02 1.561851029939521140e+02 1.854367091922420059e+02 1.494951311500319093e+02 2.443780767043579942e+02 2.741090240793212160e+02 1.519200656263381006e+02 1.391711947382712538e+02 1.482414334940778815e+02 2.574425018646875287e+02 1.455120022089010945e+02 1.620904376421240727e+02 2.098493186451893848e+02 2.377904829227144887e+02 2.881187570801528750e+02 1.785609418793050054e+02 1.500483139796714340e+02 1.697371065898091729e+02 1.824143324642365087e+02 2.329862749140337712e+02 1.372006180078979298e+02 2.250666134242961789e+02 1.760894707637434067e+02 1.874161150869196035e+02 2.860410495381969440e+02 1.539271628213176086e+02 1.051658254213322152e+02 1.501619097950496666e+02 1.205717364486104515e+02 +1.275638286377957371e+02 2.620802183565458563e+02 2.290828196339760723e+02 2.591630015014513333e+02 2.102568650793322149e+02 2.385080320420775593e+02 2.683788150825365619e+02 1.808700201925492763e+02 1.972184450648797451e+02 2.382313686117472287e+02 1.733526990293641177e+02 2.369802981553972074e+02 1.835652530901061823e+02 1.274084560526275141e+02 2.403488205519001326e+02 2.713515297463850402e+02 1.455311801633137065e+02 1.889430214806582171e+02 1.676324321357484735e+02 2.327799977696781184e+02 2.846419393176552148e+02 1.510702433968490936e+02 1.361559014852734606e+02 1.732199851325496525e+02 2.451323003571785364e+02 1.833444866660036894e+02 2.451280287301300405e+02 1.669088211440060832e+02 2.768492228383354359e+02 2.445882168033535038e+02 +2.905092787520428601e+02 2.948076984760371033e+02 1.731080208454208673e+02 2.825532355845657548e+02 1.108820315678514845e+02 2.862013985457700755e+02 2.111453776876104769e+02 2.614428154999528147e+02 1.461523265575596042e+02 2.304914832379158156e+02 2.502987607420118934e+02 2.474276046141548875e+02 1.739607960146905725e+02 2.098700376203710789e+02 2.373226438948917121e+02 1.258493219462072119e+02 2.692932028872633055e+02 2.819145908444669999e+02 1.941653933285864468e+02 1.666395497972145847e+02 2.371919109091950588e+02 1.978302896313488191e+02 1.951483674191611613e+02 2.694357972099330141e+02 2.387068160427941450e+02 2.826084316255729618e+02 1.350954172043159929e+02 1.414479610501084039e+02 1.407657276334374501e+02 2.725513503737778365e+02 +2.055761393809777360e+02 1.070553196069381556e+02 1.045726024365074096e+02 1.611577217417760153e+02 1.258091705742062629e+02 1.038769334534844120e+02 2.956016304760584035e+02 1.586570076132481972e+02 1.636816353299032585e+02 2.375674325770941095e+02 2.085436646116971531e+02 2.088922128397473443e+02 2.316234644183506930e+02 2.623581653234684268e+02 1.714245300492981698e+02 2.844387943099641234e+02 1.469270259610659650e+02 1.157700922187784727e+02 2.367694595159086361e+02 1.548671738744121740e+02 2.013687686570863207e+02 1.860374943080277887e+02 1.733446602950305930e+02 2.488507085609763010e+02 2.929099979257852056e+02 1.825615338506695480e+02 1.338575452835397925e+02 1.491478381149757979e+02 1.116052925520655066e+02 2.341983606431906537e+02 +1.014445800974648222e+02 2.539987638010908597e+02 1.871788778457793399e+02 1.454231386314719998e+02 2.284640297096368045e+02 1.174773591296971915e+02 1.395683165851895637e+02 1.137193571402578414e+02 2.370662356797280950e+02 1.767292649815032064e+02 2.688513591587910696e+02 2.913902923086397436e+02 1.122392290694582897e+02 1.366157623619356229e+02 2.667409125457835444e+02 1.834435599491967537e+02 1.437174343391732236e+02 1.130622879516462120e+02 2.898543289046954214e+02 1.559795378531963479e+02 1.765577834073310157e+02 2.422955620302867885e+02 2.384835032255701321e+02 1.708163174135501094e+02 2.012159081107001839e+02 2.825663186839160517e+02 2.627299211659199045e+02 2.173916205317264883e+02 1.878835852278120910e+02 2.578733373077019451e+02 +2.843897417914848802e+02 2.685865547709703378e+02 2.810255710736182664e+02 2.572690897085278152e+02 2.416998564827035523e+02 1.770932574976374099e+02 2.021652319180342943e+02 1.414744641219446351e+02 1.464677002516696405e+02 1.831165552459343644e+02 1.157177632931430651e+02 2.625289386264841482e+02 2.972225480003540952e+02 1.024156386789293265e+02 2.305099741095138768e+02 2.241903749843916671e+02 1.157222019118702292e+02 1.533205318359311775e+02 1.179505454242311799e+02 2.666741766563739020e+02 2.792728900733587238e+02 1.222170248460037811e+02 2.573772727215269924e+02 1.535874607134987286e+02 1.231830862844115728e+02 2.584552954023608891e+02 2.541883057030129862e+02 1.001259630352790566e+02 2.332879439260797767e+02 2.240027888381033563e+02 +1.537092645679641123e+02 1.737278083620151392e+02 1.736358049797527201e+02 2.251608985235982630e+02 1.812387130195175473e+02 1.605621432944637377e+02 1.880655312831545700e+02 2.234500385148787700e+02 1.156918728696038272e+02 2.243685096423413654e+02 1.934342626327970720e+02 1.850952349553267027e+02 2.629944548485545965e+02 1.410418270562070973e+02 1.442479234012843960e+02 2.244518961458842909e+02 1.350755563946989923e+02 1.207094763037939913e+02 1.849900977633715797e+02 1.712315707730903398e+02 1.136025349108833495e+02 2.266901327137990734e+02 2.049289406654929735e+02 2.168279721613268407e+02 2.802488024880154285e+02 2.288593244920211873e+02 2.512942787545493957e+02 1.605416563468323261e+02 1.449848598254574483e+02 1.444073785399158396e+02 +1.576600406756634243e+02 1.316580100950168912e+02 2.530050469343043460e+02 1.319013133578224028e+02 2.708693079386434306e+02 1.256852413190491689e+02 1.471714019119002046e+02 1.119112141125198576e+02 1.482405279774543772e+02 2.151504825709631064e+02 1.449998801809978488e+02 2.163638771503673581e+02 1.272949254250747657e+02 2.476027791419436141e+02 2.891208457332292028e+02 2.642744540427622724e+02 1.972643066216432999e+02 2.480891057982425423e+02 1.265454595896786003e+02 2.957735252703171227e+02 1.831389323451852533e+02 2.674516147697771657e+02 1.404389674972707667e+02 1.350952754772052913e+02 2.169062951790871807e+02 2.445227715623778408e+02 1.771545655819627427e+02 2.729961759152714649e+02 2.655105689521545855e+02 1.887977700062222084e+02 +1.336462666694000632e+02 1.333709897858500995e+02 2.263366393511863350e+02 1.847175439991091821e+02 1.121699721143812383e+02 1.985314153845103533e+02 2.097626398761568396e+02 1.994292542548276970e+02 2.119822099620050722e+02 1.121578896112172430e+02 2.285640262135607372e+02 1.530452060058861719e+02 2.280757825791220625e+02 1.002584314437652893e+02 1.549763597162410349e+02 1.962603185897801836e+02 1.520023734031539107e+02 2.188357004065238129e+02 2.078620274892635678e+02 2.253215106546470281e+02 1.707542413836397373e+02 2.818584030117174279e+02 2.256862624833151472e+02 1.123882683852972377e+02 2.188298604829752776e+02 1.623779544769217296e+02 2.272253780943444212e+02 1.236449568833132560e+02 1.456708971140968174e+02 2.173334506159979753e+02 +1.355111076933105210e+02 2.882277378633141325e+02 1.458332953325788139e+02 2.038461345794760007e+02 2.077052275373579278e+02 2.430957456359013804e+02 2.398926697516154150e+02 1.861334604823129553e+02 1.056851094080089695e+02 1.250491536199931772e+02 1.475324860190441427e+02 2.446126161547439324e+02 2.283994822545897705e+02 1.411463500178549850e+02 1.017206978570942510e+02 2.805514386584911790e+02 1.128847993259780083e+02 2.326583828053989862e+02 1.968387029218569069e+02 2.013375618903088480e+02 2.981010702857409456e+02 1.018614681114941902e+02 1.799507821883679526e+02 1.133741465580100396e+02 1.235533581072856038e+02 1.980629645203880500e+02 2.289642287691829097e+02 1.596082722591768288e+02 1.905110471998515322e+02 1.789448781159623820e+02 +2.588286452268601465e+02 1.978130463173739599e+02 1.052689337312009599e+02 1.316763830509305251e+02 2.659236586726388509e+02 1.637014132384438767e+02 1.416031833329826668e+02 2.638665530652568236e+02 1.007257384115875425e+02 1.143900271701907769e+02 2.977834670475828602e+02 1.589765734727692745e+02 1.903975572290986520e+02 2.371635535037608804e+02 1.840341975670916668e+02 2.047003785265828242e+02 2.798969769773281655e+02 2.731706896262927557e+02 1.266878907904394254e+02 1.882415083052244427e+02 2.273996647906652129e+02 1.051754139634791869e+02 1.949647447346334843e+02 2.153583447980240919e+02 2.763468452623635585e+02 1.126493843527773322e+02 1.566047572050934491e+02 1.655928523150526246e+02 1.733528322945315949e+02 1.292815908595541146e+02 +1.453195062153936874e+02 1.443849872704900008e+02 2.393030362110915519e+02 2.203850914291498668e+02 2.628192548589183275e+02 1.142161203389242132e+02 2.954875947743198594e+02 1.914138981839176950e+02 1.956478457154231023e+02 1.282875398486639824e+02 2.801001077571227142e+02 2.478095646281364566e+02 2.467477848581343949e+02 2.819656424464902784e+02 2.951823714077539194e+02 1.777239847229775478e+02 1.197979896746704185e+02 1.481181033052623661e+02 1.906710229153984528e+02 2.142395628283543658e+02 2.300980272040501973e+02 2.228884003748859186e+02 2.473330601440014220e+02 1.391193242835927322e+02 2.836257563055140736e+02 1.510096324299383923e+02 2.202302141125946946e+02 1.931468179284185851e+02 1.332427495686727639e+02 2.591048546650930575e+02 +1.878681542531208208e+02 1.576240359584147654e+02 2.653849736815447500e+02 2.963544993865212973e+02 2.044592436730770828e+02 2.022626486161902903e+02 2.692262675681025144e+02 2.660999355751699227e+02 2.275843495473382347e+02 1.090849337992742818e+02 2.095602584555617227e+02 1.896271059113536808e+02 1.103822849104477513e+02 2.916911739044173260e+02 1.131212278363718582e+02 2.998892666268029643e+02 2.476782245756396605e+02 2.259689579913920738e+02 1.853942231198421950e+02 1.358270117521841200e+02 1.538630682720535674e+02 1.002148317174243601e+02 2.538393939061405433e+02 1.631649956267838206e+02 2.086654853664906000e+02 2.065167771482954322e+02 2.184161808630845485e+02 2.204789814939956045e+02 2.876785893506615821e+02 2.415299687386639675e+02 +2.578989465605797591e+02 2.309888943086805853e+02 2.139372792253111584e+02 1.438019921733897775e+02 2.686852572045135616e+02 1.347038004304963579e+02 2.662658866335509060e+02 2.378358170108797367e+02 2.901455078003721155e+02 2.653867524737770509e+02 1.011162296015096302e+02 1.236447329941733528e+02 2.440241295351771669e+02 1.285889645706482725e+02 1.234088480316093808e+02 2.765916670935633874e+02 1.132915304101479421e+02 2.967043774237617413e+02 2.960414394814537786e+02 1.923965028192617410e+02 2.177448618307050765e+02 2.328047369831131732e+02 1.702256773965170282e+02 2.320080409490440729e+02 2.962065584958517093e+02 1.421971909775941185e+02 1.416181340866144183e+02 2.318260414882616374e+02 1.990521696869427046e+02 1.291045564046920333e+02 +1.562042774178686386e+02 1.004265446278790392e+02 2.987714610921041185e+02 1.843637355858842284e+02 1.975513718825063165e+02 2.869996482942455032e+02 1.598134132589713943e+02 1.814921031876193638e+02 2.433389905907341983e+02 2.220363745053336970e+02 1.548306942100590504e+02 2.274512269554506361e+02 2.173006200058655963e+02 2.139515436667214772e+02 1.820439741095771353e+02 2.954110718222074183e+02 2.706126458816278273e+02 2.546812106115172583e+02 1.499899738326257363e+02 1.498010641912065921e+02 1.897725780579399668e+02 2.531561160917130167e+02 2.568891780637028432e+02 2.223136077092870551e+02 1.518604819103856585e+02 1.610422120589223027e+02 1.090455809489133259e+02 1.950503873748027388e+02 1.235704160644129388e+02 2.711492093024702967e+02 +2.039597038432034424e+02 2.026680584622021684e+02 1.365818873512059213e+02 2.909476552420245525e+02 1.721994194158640425e+02 1.854386667051114443e+02 2.287109571295530372e+02 1.912591665763447963e+02 1.607322994166321450e+02 2.949516230628389053e+02 2.522065912002103403e+02 1.869433122585654701e+02 1.235797649248940644e+02 1.522422059501078024e+02 2.738245135411146975e+02 1.059681837441489307e+02 1.013027238331489173e+02 1.660100598156148237e+02 2.454471731623151243e+02 2.467503196183328100e+02 2.584564749953993896e+02 2.079587352810677316e+02 1.650926041957846451e+02 2.269719270682073784e+02 2.376254891983122093e+02 1.510146656008620596e+02 2.672848371954185041e+02 2.692845974117340688e+02 2.180714754246087921e+02 2.186797802447831884e+02 +1.704231257711912519e+02 1.993416036368699906e+02 2.293703655438095268e+02 1.494582642918422266e+02 1.988970317734676030e+02 2.329763291241497711e+02 2.594871448385057420e+02 2.168089936885102134e+02 1.825320854593447280e+02 1.816754553181755796e+02 2.164740515812325725e+02 2.676208645391697019e+02 1.298365075936954725e+02 1.802664596093496243e+02 1.015344620621038132e+02 1.955048336384612639e+02 1.938953913674110083e+02 2.716932071347151805e+02 2.391085978949223829e+02 1.852300387899809380e+02 2.933293185307651356e+02 2.502753353909542966e+02 1.326128348575908262e+02 1.132638325194699433e+02 1.382024010322260494e+02 1.899310337488860796e+02 2.577639546186944699e+02 2.130234590296898887e+02 2.056292296528304746e+02 2.070746044453983927e+02 +2.712524956603344890e+02 1.103212761114690750e+02 1.501201791543782917e+02 1.588084859702673555e+02 1.780379814134324192e+02 1.938691258391782810e+02 1.322057441019641146e+02 1.105823874551086590e+02 2.879365916037821194e+02 2.457617763012990224e+02 1.036189749330240488e+02 1.682919366264929124e+02 2.271749409116763161e+02 2.468308259697249127e+02 2.530034131464132088e+02 2.481420904342841709e+02 1.546080547019561209e+02 1.278414739842506265e+02 2.234886960240669111e+02 2.535365186455997843e+02 1.599130733896959669e+02 1.151371295028686035e+02 2.378656188176093451e+02 2.901072209563180877e+02 2.524076257924749882e+02 2.849501171254129304e+02 1.802791659856764568e+02 1.527418387706650833e+02 2.578820596338672431e+02 1.208856989199291263e+02 +1.884906470590645711e+02 2.304295185581007672e+02 1.035923344330140736e+02 1.647061655195892627e+02 1.910201770870304472e+02 1.752788518438422614e+02 2.763014227316762117e+02 2.545709641405486252e+02 1.642694881393259152e+02 1.850698110761380804e+02 2.423689469305483328e+02 2.821007056776016384e+02 1.440765548977453250e+02 1.082195827231368952e+02 1.292487205530619008e+02 2.136496853657876613e+02 2.268509220579896635e+02 2.999629735037570981e+02 2.135306905316524535e+02 2.807718279523737692e+02 1.079256111018183759e+02 2.233050677333321801e+02 1.960571416898615951e+02 2.930642308139058514e+02 1.350490077967585307e+02 2.626074042719769750e+02 2.812196827814445328e+02 2.812753678081913336e+02 1.893738913514469004e+02 1.237248675858835725e+02 +2.024005284879252144e+02 2.663611407988397559e+02 2.687079844301063076e+02 1.583164038086077312e+02 1.451019436850150441e+02 1.100558451420041450e+02 2.083655450975085159e+02 2.034012033819327598e+02 2.745375932717230398e+02 1.454718097055225599e+02 1.519068131933423729e+02 2.522666952972969625e+02 2.409340029943109300e+02 1.697386944425205115e+02 1.092659514648129289e+02 2.785598218078254149e+02 1.404092026094307357e+02 2.152301424167146990e+02 1.170396027347833723e+02 2.495323893679063474e+02 2.070836095469416591e+02 2.187978925167305135e+02 1.478606128149070855e+02 1.189323178954538207e+02 2.012925160284665651e+02 2.080878545398990127e+02 1.510128433840351647e+02 1.657302151838663065e+02 2.177026636795220043e+02 1.221198981216710422e+02 +1.411258561955272341e+02 1.419717097672817374e+02 2.247481951315160984e+02 2.805973971111802712e+02 2.755562061324142178e+02 2.039769327420251557e+02 2.994080883760036045e+02 2.417843309736466040e+02 1.023751441731232319e+02 1.491356884971497152e+02 2.542464200475323821e+02 1.496044144381669128e+02 2.829129207809560285e+02 2.479316882407134699e+02 2.441205876677642550e+02 2.045492313770996020e+02 2.855582203360229414e+02 2.884005586284110336e+02 2.039668453101600676e+02 1.690279206477617890e+02 2.136822090795746760e+02 1.254275901194574772e+02 1.084851042192170922e+02 1.656011685190305229e+02 1.415195951026897774e+02 1.578115814760412263e+02 2.619737257057257693e+02 1.492347147839753347e+02 1.627213988646173561e+02 1.343297485726322691e+02 +2.544675070683062756e+02 1.367461330002975899e+02 2.928364121110963652e+02 2.024865028281971036e+02 2.758937379397792142e+02 1.293527538914390220e+02 1.003170531204512059e+02 1.514803620238746760e+02 2.603616046431354789e+02 1.790387290949859960e+02 1.954717187769221027e+02 1.325226280128280223e+02 1.522166198122710625e+02 1.162911821325583048e+02 2.798489406348742250e+02 2.521718932296424498e+02 2.622327475379161115e+02 1.027798265388270949e+02 2.437256510683693023e+02 1.911771820917219884e+02 2.722604457055863350e+02 2.850557929858495640e+02 1.953760157441756746e+02 2.473572905253965644e+02 1.891404804097296051e+02 1.514672503279451803e+02 2.213565012031598940e+02 2.253356064978207769e+02 2.044629345029305227e+02 2.805872739342098612e+02 +2.859142434488251183e+02 1.016009480575973356e+02 1.779351649172412522e+02 2.205171340775500539e+02 2.104472905774927369e+02 1.755755724600441567e+02 2.751836189782782185e+02 2.820692049982218350e+02 1.337557428916256015e+02 1.569761138230965969e+02 1.991757527032745543e+02 2.615974376894962461e+02 1.944849272958306017e+02 1.868411694165790777e+02 2.994394032068257729e+02 2.802783326794233290e+02 2.693871918204162625e+02 1.750293298802730249e+02 1.468161278725061720e+02 1.272003326865558108e+02 2.233103517167062932e+02 2.103066399402185027e+02 2.720825853079193735e+02 2.728915492341989193e+02 2.160004538807991992e+02 1.325145501710478015e+02 2.549827549782140466e+02 2.921469675413995901e+02 1.846231529604695822e+02 1.391152989663993651e+02 +2.538717579982014456e+02 1.450483481068324352e+02 2.720200816305956550e+02 1.120834821105324011e+02 1.703801876168104741e+02 1.091293661435919233e+02 1.410263490040598526e+02 1.910022197757120352e+02 2.505223413771657022e+02 2.069613533172621374e+02 1.367200764291426935e+02 1.269156762039037574e+02 1.459486945063737267e+02 1.585863332989725905e+02 1.433846106215619329e+02 2.893202513225785424e+02 1.754070497414596730e+02 1.678900237854272746e+02 2.363821059303507752e+02 1.088858921730617908e+02 1.962435837543239927e+02 2.151311182954276831e+02 1.943029551670006754e+02 1.670799798236046172e+02 1.348235227224938910e+02 2.005836112104490212e+02 2.601588534628079969e+02 1.194827586439497935e+02 2.131891535893303740e+02 1.835674362703964277e+02 +2.872207377280434457e+02 1.680389491751975299e+02 2.268072198735419533e+02 1.324343035526375729e+02 2.746241572770433095e+02 2.142161570690199710e+02 1.852290440736100550e+02 1.772431485621305285e+02 1.144750125154023266e+02 2.162070901557998468e+02 1.490690769171257557e+02 2.904041493178549445e+02 2.673617561413327621e+02 2.904362235840736730e+02 1.438791831406123833e+02 2.596893065528289526e+02 2.617155941751458386e+02 2.388486986717779246e+02 2.718819501315180105e+02 1.265484539827731680e+02 2.508989305854047700e+02 1.677208481362706323e+02 1.527665277518251230e+02 2.069026506407369084e+02 2.223100964495413336e+02 2.859845330217733022e+02 1.430291068893224349e+02 1.186508486537613436e+02 2.043257492072551713e+02 2.909823892985461953e+02 +2.385945641230763670e+02 2.011887933217761031e+02 1.622448188725907983e+02 1.738874847453056987e+02 1.669498482708885376e+02 1.853462372214463016e+02 1.514500885098960907e+02 1.569159134451362547e+02 2.521399095730983504e+02 1.246878140446721659e+02 1.758330561641313352e+02 2.722601647479554003e+02 1.679012078705679869e+02 1.710944469563905272e+02 2.012619557548435978e+02 2.130692925302264200e+02 2.489118511754019778e+02 1.553758318484749452e+02 2.531318516516165857e+02 1.895498740333992487e+02 2.010265603399928409e+02 1.805605111948569856e+02 2.471772127430102159e+02 2.822665908577009759e+02 1.256656757093761314e+02 1.218957078832023626e+02 2.851942693987446660e+02 2.434079459678487751e+02 2.183256665756584312e+02 1.881473862468819220e+02 +2.878274557836845133e+02 1.654481949983921254e+02 1.215681808546938214e+02 2.567820905945674781e+02 2.104106688330284101e+02 2.960796083414018085e+02 2.020680111052573693e+02 2.328934707961639106e+02 1.081575190462602336e+02 1.003340046261853189e+02 2.009697278729638299e+02 2.231963192062537757e+02 1.203849639323555323e+02 1.187994179134823156e+02 2.211937485225296030e+02 1.667300587261732119e+02 1.727379541915926211e+02 2.085029285798690353e+02 2.440827389167183981e+02 2.864522928573259151e+02 2.974890568790378893e+02 2.102945085846974393e+02 1.972598274048171447e+02 1.762889209976547136e+02 1.346946323322499666e+02 1.554434255958064170e+02 2.915634104756007901e+02 1.434053307556222876e+02 1.055800565037633163e+02 2.043924431141962259e+02 +1.494596010135965116e+02 1.369114048625681335e+02 1.414146701131132886e+02 1.383970135097982848e+02 1.734304788623498155e+02 1.594301265610334610e+02 1.040146208229407137e+02 2.208381597698417806e+02 2.904998286250861383e+02 1.300157615397056929e+02 2.667076669416877621e+02 1.062418844419948556e+02 2.717657999079561364e+02 1.054097765488278640e+02 2.401074677516734823e+02 1.045408432466875297e+02 1.330046749931937882e+02 2.297648034226271534e+02 1.488059718063634307e+02 1.725671935994615183e+02 1.330818497812682608e+02 2.341687919103425770e+02 2.983144736799429211e+02 2.798846823197050071e+02 2.218705077010061473e+02 2.681931695329894865e+02 2.339384973461015420e+02 2.893058480095726281e+02 1.539801301873031321e+02 2.746688360458649640e+02 +1.330701439354522222e+02 1.727884450558678395e+02 2.309082669627648272e+02 2.027633892073664299e+02 2.725503026364725656e+02 1.999882667367585896e+02 1.904108867169430255e+02 2.952458047945178805e+02 2.903769421220866320e+02 1.593020200554085477e+02 1.236139458806368623e+02 2.670862420061573062e+02 2.910830183895285472e+02 1.860711175093342149e+02 2.161724988935532963e+02 2.564488756979296795e+02 1.231566645138573648e+02 1.554206254375235403e+02 1.148558104746345521e+02 1.512714227454516163e+02 1.953024826710307025e+02 1.296022137194406127e+02 1.500450396815122076e+02 2.611742573447975246e+02 1.601671705158374550e+02 2.391666762859087214e+02 2.566415095930981352e+02 1.923304801412870404e+02 1.194174883996373353e+02 1.970722090829630986e+02 +1.912113734453868688e+02 1.498407015577022605e+02 2.038188614169363007e+02 1.315017316695561647e+02 2.564290419741012101e+02 1.890015309531812022e+02 2.451565642315005960e+02 2.794356592632736920e+02 2.286941218755985972e+02 1.959549984609147941e+02 1.183834182035568716e+02 2.102820643179567242e+02 1.748108698585573393e+02 1.534379248653211221e+02 1.919662859034699522e+02 1.273611408042816464e+02 1.848163823983119585e+02 1.719445827292381637e+02 1.098466009889928898e+02 2.781108902268393877e+02 2.089286134506138524e+02 2.324518337977864348e+02 1.983840049195213169e+02 1.897881971862217370e+02 1.057077761008814605e+02 2.693629461665184408e+02 1.359710117509105487e+02 2.191184409971657487e+02 1.295811391257115304e+02 1.272165218667991553e+02 +1.987244486959793903e+02 1.516360617950651317e+02 2.198509518241761498e+02 2.494181713303175911e+02 2.903223989223247372e+02 2.847249789220907132e+02 1.747037051964282171e+02 1.610307305098726829e+02 1.866621867053561061e+02 1.016530888490581503e+02 2.606194448419089440e+02 1.820037020201941402e+02 2.650669443765450524e+02 1.137210849453726098e+02 1.329244106101075715e+02 1.741312140090854257e+02 2.301425980066611885e+02 1.051708772384664030e+02 1.994040172335078864e+02 1.874773290907829733e+02 2.745616984783777070e+02 2.354781865911449756e+02 1.598287033335407159e+02 2.650689470710170212e+02 1.643692352330562017e+02 2.991199217036622713e+02 2.713535332162406348e+02 2.516280148665988463e+02 1.124367393830256532e+02 1.725070309959049837e+02 +1.637875882282461077e+02 1.407642428016634426e+02 2.759741260511348173e+02 1.982469453863400304e+02 2.966736241669494802e+02 2.756530253528777052e+02 1.426661371226006167e+02 1.585144634205103102e+02 2.836415355000413001e+02 2.468213340046699784e+02 2.898204535963063790e+02 1.711408259966125343e+02 1.900542569026269177e+02 1.112151031999617032e+02 2.679918109779015936e+02 2.737346364036235400e+02 2.597479311885246602e+02 1.719445390286030886e+02 2.361360157374418236e+02 1.123330408578339785e+02 1.214203690485689719e+02 2.552722899309185891e+02 2.436705678248840456e+02 1.596697357728296254e+02 2.533254006866929444e+02 2.066863222258713790e+02 1.194370826184286329e+02 2.943584774485435673e+02 1.636272134478143130e+02 1.191267138602315185e+02 +2.350924626651462006e+02 2.263138093076711357e+02 2.206572605284771385e+02 1.704171521239532296e+02 2.000250897638135257e+02 2.966317084215347109e+02 1.350543763227695138e+02 1.248113195978286285e+02 1.480602782771696297e+02 2.391913401309390679e+02 1.908758915801345779e+02 2.476074601271855045e+02 2.408834383325319095e+02 1.009169451940341560e+02 2.567526834523320645e+02 1.791854948779896688e+02 1.412277552146151152e+02 2.660711025781407670e+02 2.073940326990519054e+02 2.509760072499196610e+02 1.358593750308925223e+02 2.127422683140523532e+02 1.874643773621423293e+02 2.844455725631112273e+02 2.197223292953194118e+02 2.049519862750077266e+02 1.674367936692717365e+02 2.806316262053937294e+02 2.040091003350897836e+02 2.675290975004411962e+02 +1.483513543637005796e+02 2.384008274111940011e+02 2.834409911154408519e+02 1.344593118283445392e+02 2.346883831968173979e+02 1.381882879805813218e+02 1.241165074750676638e+02 2.186327911062819567e+02 2.466602279029802673e+02 1.573094529523951906e+02 1.568918412618390903e+02 2.289205163045023710e+02 1.170165333644822283e+02 1.742406104080407658e+02 2.082974381484526702e+02 1.600869123712819260e+02 2.399160913983472199e+02 2.877189278027444743e+02 2.845252294036096146e+02 2.342337907657317544e+02 1.496264758341107779e+02 2.905797831387872066e+02 2.824703799011629144e+02 1.047015685176013307e+02 1.056531628249932169e+02 2.778559625738202499e+02 1.693549799118289343e+02 1.654193764711911570e+02 1.062077606699500762e+02 1.159643419206647792e+02 +2.694780377267857716e+02 2.229138360502907403e+02 2.407432883969363218e+02 1.240072643521201741e+02 2.128611568148922970e+02 2.114050669978733481e+02 1.042337934877265297e+02 1.044783539591350490e+02 2.706611056394938259e+02 1.972285130309975898e+02 1.959046941044780681e+02 2.915493579522836853e+02 1.131994346897827342e+02 1.197362406389762839e+02 2.877593780027675621e+02 1.089470964294721824e+02 1.996015695685267417e+02 2.185569019121031999e+02 2.102686704320404374e+02 2.955299037924150980e+02 2.987478446256551479e+02 2.517129931888254646e+02 1.552463625479420557e+02 2.295020326441428153e+02 2.886454895961533111e+02 1.869792800456660871e+02 2.703426621835664037e+02 1.873514421416134326e+02 2.714620374401066556e+02 1.623625260081516331e+02 +1.457420078291350194e+02 1.926195242081234369e+02 1.841639049563959247e+02 1.397830290030836125e+02 1.287503203163068406e+02 1.684614546803193775e+02 2.820658047345126533e+02 2.986548244924653090e+02 2.631399932039782925e+02 2.870930868530864473e+02 1.141938207690214426e+02 2.868552010662050407e+02 2.019110175402121286e+02 2.840219745246005232e+02 2.848478851173646262e+02 1.902287203163165259e+02 2.696968940302964484e+02 1.690355482825476656e+02 2.171695948786692725e+02 1.960363641465239652e+02 2.930566891688549731e+02 1.380341365242818483e+02 1.769912313914243214e+02 1.164985277343077996e+02 2.079184380436491324e+02 2.871364788135472850e+02 1.796231479741346391e+02 1.115892945700443875e+02 1.922852518794877028e+02 1.851500906627327083e+02 +2.894943401361737187e+02 1.972990286414578804e+02 2.801948561309920933e+02 1.993490085147259947e+02 2.539099743775018112e+02 2.972486389690005240e+02 1.162404922698449354e+02 1.801898545246462504e+02 1.283416456049016858e+02 2.289248555429664407e+02 2.419505668531598985e+02 2.755101537543703216e+02 2.786083442131507013e+02 2.461931811431258552e+02 2.699066237266536064e+02 1.088542193903703179e+02 2.302113104476973149e+02 2.158136503417114227e+02 2.797451432348925096e+02 2.832754349673875822e+02 2.207567008139471909e+02 2.920947868166995249e+02 1.300092217647513735e+02 2.953259288980694350e+02 2.539624465668687492e+02 1.304833679125420645e+02 1.051395153781939484e+02 1.855592224876973830e+02 2.160289702497469477e+02 1.227895712666205981e+02 +1.029685235386965587e+02 1.410297052380113882e+02 1.832105986621241982e+02 1.016727951098498579e+02 2.130361696974732126e+02 1.817578553203918830e+02 2.644724203174304193e+02 1.713346250427240420e+02 1.297164370175517547e+02 1.072810924841072193e+02 1.083932811014470161e+02 2.860684171745337494e+02 2.893854146138399983e+02 1.677808320623732925e+02 2.343535290724524600e+02 1.209564642240636090e+02 1.329537830609780542e+02 2.924542956964438645e+02 2.733376468658280487e+02 1.397146179999238598e+02 1.103570089598620285e+02 2.231457082965310690e+02 1.056672424832338635e+02 2.887779644840117612e+02 1.127167878193751704e+02 1.387640376146708263e+02 1.791595456124304633e+02 2.709107895779202408e+02 2.238624693992912569e+02 1.773395240564728397e+02 +2.317578772498348769e+02 1.294950944138938667e+02 1.126253428029936572e+02 1.371351849575549693e+02 1.785990678455200964e+02 1.021081186758702444e+02 1.471984209931611360e+02 2.907355141803875540e+02 1.881128962816476644e+02 2.776434621780599628e+02 2.231668573818950279e+02 1.905362514139340817e+02 1.921875823712000226e+02 1.027725913116546792e+02 2.939602582690168902e+02 1.776540079128602656e+02 2.761214484196684111e+02 1.042033722248946646e+02 1.812858538041361385e+02 1.739774673118114663e+02 2.626640185867897799e+02 1.702975408841979288e+02 2.558138050153142729e+02 1.733257751657050392e+02 2.918973111180089859e+02 2.499103812623473857e+02 1.210050998380505973e+02 2.819910650801346605e+02 1.887952629909842699e+02 1.910084514453274380e+02 +2.212539479167726029e+02 2.774434360961662378e+02 2.337566454731646104e+02 2.345785537275947661e+02 2.365459264006348405e+02 1.983982238092833086e+02 2.030822332599765332e+02 1.995891111618029186e+02 2.834365683300363798e+02 1.036872616932399609e+02 2.192093181482490252e+02 2.601252995545215754e+02 2.498786393235831724e+02 2.102914196276636858e+02 1.344974807588668000e+02 2.319076536245909210e+02 2.769341510052834110e+02 2.705990780330756138e+02 1.679097240924248240e+02 2.394521666103182724e+02 2.042111123157340842e+02 1.679545908808316028e+02 1.638112120198904051e+02 2.498667640522866407e+02 1.298749690282424183e+02 2.953546510122243944e+02 2.420377599473625025e+02 1.972281420856064642e+02 1.511153679243939223e+02 1.785899871179086063e+02 +2.568297621323404201e+02 2.469847896802298237e+02 2.766623631158322496e+02 2.476135901735717937e+02 1.788596740963971570e+02 1.849716544556056874e+02 2.568516536462929594e+02 1.692762419184084877e+02 1.468834240718183537e+02 2.716053370235183593e+02 1.674083895790932957e+02 2.340636951853666687e+02 1.637725360284847227e+02 1.316562872243186177e+02 2.850086566701365882e+02 2.066513343106022944e+02 2.990778363456342390e+02 1.780020440519503495e+02 2.906711993591478631e+02 2.149926413975278479e+02 2.151504627144789765e+02 1.458362697904619836e+02 2.339644011324822657e+02 1.740513991402896181e+02 1.804876886135730842e+02 1.706585538790989176e+02 1.113370339871644603e+02 2.032819788543359039e+02 1.225434838619497526e+02 1.558188197132453183e+02 +2.752385657001058803e+02 1.704994416021052643e+02 1.607090409105587696e+02 2.031247490318933444e+02 1.333383797740430339e+02 1.922643047184382112e+02 2.665685682619526915e+02 2.611043497447243453e+02 2.444450591022788615e+02 1.012899678037660181e+02 2.236752860048796947e+02 1.164606756896235993e+02 1.768812782093617955e+02 2.532808672341815850e+02 1.308823477633827395e+02 1.683394957344131626e+02 1.787390150786144716e+02 1.962681762314343530e+02 1.178176219749694980e+02 2.151624908275416885e+02 2.951256579216935734e+02 2.058583926262361388e+02 2.348769662163374790e+02 2.500118096543036472e+02 2.065978549387351109e+02 1.732426267043477139e+02 2.575950640438621804e+02 1.826939497339359946e+02 1.586062531006688801e+02 1.141086110094916819e+02 +2.107478059550890066e+02 1.212326460542207940e+02 2.154852140069355073e+02 2.624147598788578648e+02 1.169795422214265699e+02 1.682202484364929660e+02 2.987700686247625299e+02 2.259973608163532504e+02 1.912690930240648015e+02 1.896338093439390775e+02 2.747727757049322008e+02 2.388804299971102978e+02 2.538821160842531128e+02 1.839990833334872491e+02 2.839611350159472067e+02 2.953225980324958755e+02 1.674336071760058076e+02 1.609172697163818953e+02 2.902596210806400450e+02 1.513824951234124114e+02 1.873458283487339600e+02 1.695960935104061491e+02 2.116215526550050470e+02 1.849422962892989233e+02 1.434256749723924713e+02 1.304784783123307079e+02 2.632948417544853328e+02 1.656472047377057777e+02 2.303125851744007377e+02 1.681993961373014486e+02 +1.104191565760665128e+02 1.750924257030650040e+02 1.242494131306669090e+02 1.541741282893887899e+02 2.585460716706878657e+02 2.286423505464783261e+02 1.890990979891397501e+02 2.707781238779197679e+02 2.619171833457787670e+02 2.695823002806438353e+02 1.941989480397771786e+02 1.389058748786196134e+02 1.283479072532797431e+02 2.347481590897206729e+02 1.518985431591505630e+02 1.757095590143896402e+02 2.225334593093496096e+02 2.231309387578290568e+02 1.039310896134069395e+02 2.614149485334186238e+02 2.212890027388380076e+02 1.425609106790709859e+02 1.376620423520403733e+02 2.403640719649376933e+02 1.152284694789922526e+02 2.108068210397188409e+02 2.526640691383259991e+02 2.323633859683563969e+02 2.720522122905912283e+02 2.498034621012949685e+02 +2.223449436042899947e+02 2.823923482876032267e+02 1.728419664392092727e+02 1.542710015610415724e+02 2.699062389875002737e+02 1.776741825057288793e+02 1.800001384193664080e+02 1.819433000632012636e+02 1.436484983468620840e+02 2.344086094824976954e+02 2.824459866922626361e+02 1.860318500101035681e+02 1.749968777772715498e+02 2.792448396035428004e+02 2.134719239619671498e+02 2.649346822194891047e+02 2.535109715864082602e+02 1.651109960016319178e+02 2.407385671793928736e+02 2.276937454871455770e+02 2.965404491761371446e+02 1.771850291606413634e+02 2.317902380753697855e+02 2.233400563607936817e+02 2.471010629200553694e+02 2.999085009765063319e+02 1.263611681933084725e+02 2.954593528043474180e+02 2.279026703099021915e+02 2.630592311905735414e+02 +1.662671322607742752e+02 1.600442354914371208e+02 2.476541290397616137e+02 1.471310870365195740e+02 2.302232198157895198e+02 2.833854716762933776e+02 1.464787719165046553e+02 1.913553080525503560e+02 1.014594285276723156e+02 2.182963956218923158e+02 1.629807715448000636e+02 2.692152036144454428e+02 2.287521686048013976e+02 2.982465613581407524e+02 1.646080094271899839e+02 1.685350412843276899e+02 2.638506951547767585e+02 2.931520510309920837e+02 1.395453733045734168e+02 2.192750645467382355e+02 1.118562057344099543e+02 2.210439168983162972e+02 1.977199388190010438e+02 2.248771354041466566e+02 2.967583759675493411e+02 1.144799677712354793e+02 2.877369511761256149e+02 2.831237961244747225e+02 2.909105411130262269e+02 2.550977837950437390e+02 +1.519738194711488006e+02 1.042788193386050608e+02 1.298121344332743377e+02 1.827398187867084971e+02 2.371985543371917800e+02 1.647119082252074236e+02 2.792046599520904238e+02 1.737333830141970452e+02 2.019611337599129968e+02 2.402390448779260623e+02 2.107045415433176174e+02 2.447101973248666411e+02 1.584507446746840174e+02 2.877533155913679366e+02 1.209142860803932251e+02 1.903846717728129931e+02 1.485923447895592631e+02 1.040627746119376695e+02 2.329784390325348795e+02 1.136264746597146882e+02 1.019818146651219024e+02 2.395077159260278847e+02 2.571474008697522322e+02 2.507839876514990465e+02 2.649762964978717719e+02 1.398370322453145889e+02 1.116668292809188614e+02 1.262068209877756289e+02 2.561228606182183967e+02 1.019925993853918413e+02 +2.525550526067758881e+02 2.649927164229666232e+02 1.457764901336312846e+02 1.519121804298574148e+02 1.112983565335166247e+02 2.979018464293943680e+02 2.517559946611144142e+02 1.257251989750113239e+02 2.377842966816966737e+02 2.692916709774201536e+02 1.558791612193160745e+02 2.988101508442036334e+02 1.264682305510686575e+02 2.586186621657187743e+02 2.397705732393993969e+02 1.799773948514575750e+02 2.289212202830902072e+02 2.551439950194432242e+02 2.270410183155361210e+02 2.624250216967006395e+02 2.894508375480465361e+02 1.106681053253299183e+02 1.696755343387707171e+02 2.302155275158106917e+02 1.445113211107399138e+02 1.886794441144848236e+02 2.129906512422033131e+02 2.340704769023953986e+02 1.082933010325512981e+02 1.977265970892881626e+02 +2.874406426475449052e+02 1.913451373833616742e+02 2.647704607931181044e+02 1.881279366057496532e+02 2.840067538093052804e+02 2.179159896935567247e+02 1.839859875309309132e+02 1.189702187115672132e+02 2.794517441847542614e+02 2.815599370853284427e+02 1.258259904677427699e+02 1.428483537633051412e+02 2.541426109645265967e+02 1.338781623221585164e+02 2.877181693280556374e+02 2.041742222547631513e+02 2.429167887622087392e+02 1.861891141000048435e+02 2.815058357304060337e+02 2.932279451804108703e+02 1.428092602118218792e+02 1.129541128601477595e+02 1.104970415865426503e+02 1.361068733124779726e+02 1.702082770497633533e+02 1.583852379729134157e+02 1.614070717213254511e+02 1.054529192214523476e+02 1.116913943762218366e+02 1.806474879921846366e+02 +1.904583320230821926e+02 1.477903225290235980e+02 2.926623631581093150e+02 2.267002240281469199e+02 1.643763662729302268e+02 2.199235242233247902e+02 1.853923849032223359e+02 2.941726936508506469e+02 2.665966841434134835e+02 1.199566433868006357e+02 2.951991052054676175e+02 1.594510101065885124e+02 1.458298791153635534e+02 1.532145001211049475e+02 1.411023254500616133e+02 2.140513226665028128e+02 1.678784758049908419e+02 1.708308530430679184e+02 2.099440033407245778e+02 2.664570659333852518e+02 2.959905162222905801e+02 2.829445582187913715e+02 2.588706049990775000e+02 1.722199615074994483e+02 2.869184560072056343e+02 1.681559218785307053e+02 1.503240659973911306e+02 2.588597461006905291e+02 2.678295026364270939e+02 2.154561503934444886e+02 +2.071927904539387839e+02 2.171736003654224305e+02 1.593735315924418785e+02 2.947356579175152547e+02 1.742775794491871011e+02 2.184611101357660914e+02 2.225198306238390842e+02 2.168369296352294668e+02 1.755672175076374231e+02 2.252214925755263835e+02 1.563369877784152209e+02 2.085332604119019209e+02 2.572482649031854862e+02 2.951800051631508950e+02 1.079183556031880329e+02 1.218838648771928774e+02 2.685371616407055626e+02 2.419162624723466877e+02 1.022244855205179022e+02 1.101224552326326602e+02 2.597819405832950679e+02 1.134555412120959517e+02 2.870491931154815575e+02 1.374365654160442318e+02 2.645641258978021142e+02 2.531141673781916666e+02 2.361747183362105886e+02 1.893108861581111171e+02 1.539026912190118139e+02 2.501170032332128415e+02 +2.547888423116186232e+02 1.853670755857669974e+02 1.389074705955763420e+02 2.709929622842061008e+02 1.228800068832790515e+02 2.778321736112652616e+02 1.309641642706778555e+02 1.156980811627219055e+02 1.431313378740429982e+02 1.646591400066212714e+02 1.920182917083556049e+02 2.178001706163468043e+02 2.235489712948179886e+02 1.079088316874027242e+02 2.447091545393394370e+02 2.320303973549428065e+02 2.359105911115680101e+02 2.382951907588607128e+02 1.062067779247245483e+02 2.905379355334102911e+02 2.023335418134440715e+02 2.128348219019524095e+02 2.865957710750057004e+02 1.782427960783044796e+02 2.856139874187100531e+02 1.139905905655008098e+02 2.264676166669663360e+02 2.479179013019825675e+02 1.746165350218777803e+02 2.255842464851874070e+02 +1.883869033800616819e+02 1.965817072065136699e+02 1.890868666652849015e+02 1.898737766004000491e+02 2.779218373710688184e+02 2.134628932560298722e+02 1.100835458783813436e+02 2.768750976313177148e+02 2.547073561014202880e+02 2.728160162818061281e+02 1.733645011505617504e+02 1.625036971255624394e+02 2.977754324167240156e+02 1.632372616873928450e+02 2.174045665187836107e+02 2.606964806055048030e+02 1.625508452643421720e+02 1.715067940576683441e+02 1.218481476549646629e+02 2.842560845538128547e+02 1.928678337146606623e+02 2.708765321293922739e+02 2.077020047066411621e+02 2.923591890868326004e+02 2.230876482822842206e+02 2.689925468225608256e+02 1.036588336737814586e+02 2.052618530546818363e+02 2.648220111560104897e+02 1.868396012623422280e+02 +1.785937212608853315e+02 2.973454718025594161e+02 2.368986004504845084e+02 1.146953890760472348e+02 1.265905165006724644e+02 2.255973396401841455e+02 2.163675674740596264e+02 1.527913853500098185e+02 2.283358642424602465e+02 2.759303134283557597e+02 2.876072117803540777e+02 2.029362495845153944e+02 1.212425121544320490e+02 1.100001317370093830e+02 2.335268996183764330e+02 2.375268130741384027e+02 2.336339660612213436e+02 2.462747325703657282e+02 2.841981652294566061e+02 1.081959034831858446e+02 1.291296469376330833e+02 2.602425849072438950e+02 2.575669438145553727e+02 2.135342654708205714e+02 2.294373105308322067e+02 2.706502840281193016e+02 2.928412927772634475e+02 1.330151104176747765e+02 1.533759962548247131e+02 2.744006234275867655e+02 +2.257735103076358882e+02 2.728385269717355186e+02 2.290872800510813363e+02 2.330934692803050154e+02 1.037274604992595215e+02 2.674079561164307961e+02 1.195755645916240866e+02 1.402804464035359047e+02 2.170516922702277611e+02 2.744725918691634661e+02 2.930458735600458908e+02 1.496408395971007224e+02 1.595562419103408729e+02 2.835538666488008630e+02 1.780163567793609332e+02 2.906408145890961237e+02 1.133853019218590248e+02 1.494630592331960770e+02 1.214592101712915451e+02 2.263015460193574881e+02 2.598100406717117608e+02 1.963383361449393192e+02 2.235083985338561376e+02 2.946475410923074492e+02 1.758055989844200724e+02 2.637780439251395137e+02 2.875400021086666698e+02 1.577781508415756662e+02 2.146553072676672684e+02 1.798181279868336446e+02 +2.620574340171276617e+02 2.153711882285265915e+02 2.245961661539886904e+02 2.054509343172356921e+02 2.926008719008261210e+02 2.432564531143420652e+02 2.303655720936658611e+02 1.615953803481287991e+02 2.918921003884012748e+02 2.760746977013722017e+02 1.909442200188182710e+02 1.596536528765051060e+02 2.491411570718119037e+02 2.924629085319008936e+02 2.587604848561293807e+02 1.524605619386706792e+02 2.737599884275671798e+02 2.090365453766356723e+02 1.610548024559351461e+02 1.018774121963877803e+02 2.410901898572944049e+02 1.875862586601133444e+02 2.588626077539996686e+02 2.579873618626863845e+02 2.838744453525392828e+02 2.580071516854936817e+02 2.114887112935771256e+02 2.675506009048368696e+02 1.260391751775616029e+02 1.858866479221875920e+02 +1.963224789638335892e+02 2.444908535968891954e+02 1.962779352478895589e+02 1.553096436749702889e+02 2.483662294276224429e+02 1.067992874414757978e+02 2.633849667942634483e+02 2.454321751613854588e+02 1.854433418739394028e+02 2.562889653665436072e+02 2.506342746416453622e+02 1.900819942764665598e+02 1.704565979131312474e+02 2.916979173024495822e+02 1.898592592817412310e+02 2.687872145548625440e+02 1.525347862509104004e+02 2.786582104923993484e+02 2.310813531087783872e+02 1.166208530157265386e+02 2.602471623613457723e+02 2.102772607982462034e+02 2.183751071150112466e+02 1.065011561509572999e+02 2.813176394708128782e+02 1.792292558016025623e+02 2.804083600455996361e+02 1.557890480883644102e+02 2.439522159916458861e+02 2.652201783594097719e+02 +1.425266334964659904e+02 2.075049705342416928e+02 1.704914602333145126e+02 1.886474594627911756e+02 1.252313163849750595e+02 2.836097447326676502e+02 1.406399617929505439e+02 2.414245225193989768e+02 2.576349788827002385e+02 1.486724691707949262e+02 1.092388214497626961e+02 1.685935770192617724e+02 2.033388664740227227e+02 1.390809359458484948e+02 1.056188661648174758e+02 2.350581131530574055e+02 1.964295662906907012e+02 2.578831766420791496e+02 1.109952979966328144e+02 2.027546721440710940e+02 2.501377690830167637e+02 2.111868593440530617e+02 2.324728205186171692e+02 2.453971856382445935e+02 1.723822394524685819e+02 2.872924628066301693e+02 1.140766727214026446e+02 2.221345013854892159e+02 1.728173248741775296e+02 2.676400838220500873e+02 +1.711571121866394947e+02 1.085759247733173396e+02 2.001753766691515750e+02 2.760446855018309407e+02 2.056587091496190567e+02 1.121827347031253197e+02 2.274644480946081444e+02 2.571858980756533128e+02 2.945439217283808375e+02 1.913312305877045674e+02 1.500446430731354894e+02 1.650397772114545489e+02 2.581660073502400792e+02 2.094009769144933273e+02 1.731816092302842094e+02 2.727903589313663133e+02 2.606648610353666982e+02 1.460656197586831695e+02 2.016951883706858268e+02 1.247477859691891240e+02 1.732157361502286221e+02 1.195560196858487245e+02 1.253893910664414904e+02 2.455457677441618216e+02 1.778732818035962850e+02 2.490436815297808266e+02 1.487573988963908960e+02 1.937302250034929898e+02 1.502426775501600389e+02 1.110841009912817583e+02 +2.382535443835092508e+02 1.972031918916456732e+02 2.576267295349729807e+02 1.730194312205534288e+02 1.301593684828995094e+02 1.624008376323430127e+02 2.060036399923972681e+02 1.233366573394677630e+02 2.194763391620297739e+02 1.701495187616251314e+02 1.223397596968992218e+02 1.987622577877627350e+02 2.511738650001373117e+02 2.130204435763062634e+02 1.993899817227978133e+02 1.597764561560970265e+02 1.205224890815559604e+02 2.184250491898233690e+02 1.755709834516516139e+02 2.741081010321077542e+02 2.104755291992826187e+02 2.698148014221883386e+02 1.299106544858947814e+02 2.008369880697999292e+02 2.938716155581552130e+02 2.671516623028076083e+02 1.332347035771324215e+02 1.291435420390463378e+02 1.835021202063177554e+02 2.002866194329941720e+02 +2.554906544300547182e+02 2.365682876454178540e+02 2.924004211094360244e+02 1.662852505275750730e+02 1.123350814405425808e+02 1.910015128879867632e+02 1.341551373493250594e+02 1.313122940860927770e+02 2.397311819484906152e+02 1.559268654058377024e+02 1.407120959783594003e+02 2.371419051640040152e+02 2.217591327496910480e+02 1.881187811266301537e+02 1.632462641154496907e+02 2.970940639140721373e+02 2.422917505999918433e+02 1.356966040631749593e+02 1.702398486895437486e+02 2.608644720933497183e+02 2.783751927848827563e+02 2.951746624002826138e+02 1.720706565846523688e+02 1.275268866601749096e+02 1.880990845238362681e+02 1.129502795714700625e+02 2.919985401845127626e+02 2.747497807112307555e+02 2.667734033775608395e+02 1.373740617490475699e+02 +2.115416415080857746e+02 1.431719947715498336e+02 1.718744824503889674e+02 1.075365968452523902e+02 2.220100335193473029e+02 1.965127222891928795e+02 1.062726056237197838e+02 2.631794488147562561e+02 1.658640190278337627e+02 1.169182569761068464e+02 1.645780782039788619e+02 2.940728738870184316e+02 2.979920277570993790e+02 2.125849825405138631e+02 1.533327700316632161e+02 2.655551337415409421e+02 1.329075684859120088e+02 2.686536376777100941e+02 2.299223677315555676e+02 2.123135030200585334e+02 1.474417961566917654e+02 2.899688778344954017e+02 1.439992490259426461e+02 1.606165457016644780e+02 2.854253601360321682e+02 2.837928223954166924e+02 1.868865943198568402e+02 1.809928275876523571e+02 1.583918020284682484e+02 2.384217495701244331e+02 +1.181670050605631417e+02 1.525653020190297582e+02 2.615084872177121724e+02 1.755024420886775829e+02 2.989795566898581001e+02 1.573585789513378188e+02 1.903575226478752711e+02 1.641861715477102166e+02 2.943146494922903003e+02 2.038802368327418719e+02 2.581560000437879694e+02 1.504995935930718076e+02 1.095655891680627008e+02 2.628623226127134558e+02 1.069018430130149255e+02 2.750818506761686422e+02 1.121786007219489818e+02 1.106710601660877415e+02 1.217291564359016149e+02 2.915199334459504144e+02 1.325859381653097557e+02 1.737237090326784141e+02 1.036075961875061751e+02 2.392327113385031510e+02 2.486092083099548233e+02 1.259492139939950306e+02 2.665249241620523435e+02 2.103119814995928039e+02 2.718465347096271216e+02 2.018653364759854298e+02 +2.085808638159350608e+02 2.977621083099649582e+02 1.394173606621695285e+02 2.232898484647512873e+02 1.347812725162832521e+02 1.574683348766579627e+02 1.827258429860655724e+02 2.827887224427595356e+02 2.608349632236463549e+02 2.370910079389979046e+02 2.033290260845359398e+02 1.566531500677691042e+02 2.982287288081304837e+02 2.998057140577807900e+02 1.906108269451214596e+02 2.023344526730545851e+02 1.717672594576409040e+02 2.093320563180507747e+02 2.649028095061802333e+02 2.840422446800275793e+02 2.111868958418739908e+02 1.803076798272542760e+02 2.311954915496957312e+02 1.563425451766251513e+02 2.610066662710300989e+02 1.855286443040786537e+02 1.478912573842241045e+02 2.544380211258828410e+02 2.799416317427427430e+02 2.238937193404353252e+02 +1.269470316997365131e+02 1.895539822645488357e+02 2.443421824114378467e+02 2.632321641240823737e+02 2.164919638664115951e+02 1.042697198382110884e+02 2.896061632271033659e+02 2.068164163046922681e+02 2.059671371408958294e+02 2.352532326493898722e+02 1.046233655847859296e+02 2.755187319279126541e+02 2.344641322699609987e+02 1.434858288567621969e+02 1.255438908126368176e+02 2.548141480364848803e+02 1.466719626681152704e+02 2.020892715394597872e+02 1.195107046056347713e+02 2.012968701954913797e+02 1.996902768982717191e+02 1.560547951636197013e+02 2.162555170020900164e+02 1.483278604161245084e+02 2.615607136845001151e+02 2.424344777210258997e+02 2.524090919470299070e+02 1.726167614603126026e+02 2.199373130240069258e+02 2.318614758097714912e+02 +1.590143031424979370e+02 1.933970326403360502e+02 1.227042846200323112e+02 2.107086401017011781e+02 2.844049872407889552e+02 1.420899421875644464e+02 1.736571760246831673e+02 1.130876049831349661e+02 1.470306210908964317e+02 2.959723384067232246e+02 1.438030965279091049e+02 1.685928342779160403e+02 1.351720793691902713e+02 1.909711091249450590e+02 1.477005416416634205e+02 1.010528808923594681e+02 2.205493627613245167e+02 2.367352422049318079e+02 1.224997665062844305e+02 1.620949451166091251e+02 1.270634404764108467e+02 2.673321646154778932e+02 1.618882934467209225e+02 1.208967331765591524e+02 2.073956586593529607e+02 1.223277950209799059e+02 2.625820210851194361e+02 2.262632377752408672e+02 2.222881433937307349e+02 1.716205611551696961e+02 +2.376094214038359667e+02 2.287867757784330820e+02 2.035778067022395703e+02 2.546588007138803391e+02 1.514832565507949198e+02 1.736683542684334327e+02 1.991020520349750598e+02 1.873563480883249213e+02 1.589186331386689801e+02 1.042563150975229149e+02 2.019924784676414902e+02 1.136537158101241971e+02 1.091264020137841158e+02 1.352770409719844054e+02 2.178414513482917414e+02 1.831380105899948489e+02 1.114225947990316570e+02 1.736029819106907439e+02 1.354612112967272424e+02 1.996055424300992627e+02 2.905125217944571432e+02 2.980326934372309893e+02 1.560898949881966473e+02 1.943286005606112212e+02 2.429797193518882636e+02 2.652714760000731076e+02 2.863852813340179182e+02 1.838252831614893239e+02 1.814799327205894315e+02 2.338290144642930954e+02 +2.526381992552952340e+02 2.089745531365245483e+02 1.869938021147821701e+02 2.864405091884094645e+02 1.736924996547539877e+02 1.479914815134324613e+02 2.132537252074255321e+02 1.830098172980584934e+02 2.476607236946428827e+02 1.066503395377639265e+02 1.405219898965278276e+02 2.743866427972425299e+02 2.269305408710248173e+02 2.791638036143738191e+02 1.824422387811073634e+02 1.852994662516045423e+02 2.777032940597408128e+02 2.109153407914434126e+02 2.214759900082639490e+02 1.857033490029854761e+02 1.302118293337227328e+02 1.889562709124264188e+02 1.844813915245081546e+02 2.875482403705134402e+02 2.022892465111445404e+02 2.230217175841083872e+02 2.843056043891419904e+02 2.350834055358549222e+02 2.080929758762673032e+02 2.770814576487081240e+02 +2.389430507965955428e+02 2.463651891862864147e+02 2.369578462650186452e+02 1.902366989508459199e+02 2.003468797600664004e+02 2.681735461841141728e+02 2.362787745532336601e+02 2.323782975776413480e+02 2.525302892415198812e+02 2.828059530799229151e+02 2.840327053185673662e+02 1.223941816187275435e+02 1.056255174412387134e+02 1.386503050117574105e+02 1.384325506562210535e+02 1.176641636239777426e+02 1.670688688422628161e+02 2.506322552784647826e+02 1.181229702988334083e+02 2.607048520072489737e+02 1.667476448166365515e+02 1.310085831735554223e+02 1.553111545647699927e+02 2.907454039462255651e+02 2.844644695877585718e+02 1.989933906493695019e+02 2.662036190025202131e+02 1.792754658114438371e+02 1.073664330563030944e+02 2.793141822468826376e+02 +2.640306978448612654e+02 2.458161373226257069e+02 1.015510894380497575e+02 1.527048938693112916e+02 2.893334394723561900e+02 2.994916089563248534e+02 1.054055716033572452e+02 2.278819528330843127e+02 1.890909183007994443e+02 2.134436011261824433e+02 2.654189934957544210e+02 1.780852604264427725e+02 2.222277079756825628e+02 2.689688042831336361e+02 2.232046857529678050e+02 1.778434593737022169e+02 1.336418515516146783e+02 2.739064893378349552e+02 2.065065746675076355e+02 1.329712924393647313e+02 2.176938186185978736e+02 1.918043587714230114e+02 2.280421349429639122e+02 1.182282112372680842e+02 1.370131137248831692e+02 1.716251366233928195e+02 2.412427837766657888e+02 2.738208811966829899e+02 1.471415247536169488e+02 1.638288393831292353e+02 +2.669085627842696908e+02 2.477147782526785136e+02 1.718200513884793565e+02 2.299346472745743597e+02 2.016242169414389309e+02 1.631378839470685307e+02 1.859938403107781255e+02 1.609729169019194330e+02 1.536303039404505171e+02 2.234728543554556950e+02 1.953401084257108096e+02 2.920381588589057174e+02 2.034966688752892310e+02 1.019427894404581139e+02 2.980736970140829953e+02 1.738263823108001418e+02 1.531314323312329293e+02 1.400030133312995702e+02 1.802287961283190043e+02 1.719909696301723443e+02 1.974918793689569725e+02 1.666882741246514001e+02 2.879569025675030502e+02 1.334044307903087088e+02 1.016937569869423896e+02 1.660343944328368764e+02 2.214967229035601974e+02 2.539424882366704992e+02 1.211914878013190133e+02 2.835892388637473687e+02 +1.704109091340931741e+02 1.337843054639438378e+02 1.570106251098002588e+02 2.123587857442842335e+02 2.788290802167920219e+02 2.795601449888932848e+02 1.220747715539721696e+02 1.179984498565524405e+02 1.552783750686872963e+02 1.257256444039083192e+02 2.312614004137946893e+02 1.971625968209403084e+02 1.208837070227885135e+02 2.231693789143681386e+02 2.332576722664892941e+02 1.659208209363902711e+02 1.979623049620595907e+02 2.497459328714609512e+02 2.540243570817084446e+02 1.309045902221261599e+02 2.376613837929333499e+02 2.140333351750954023e+02 2.231625169053620539e+02 2.869160136215916737e+02 1.282002159167354023e+02 1.029173927424986488e+02 2.432034421383394545e+02 1.495648010251883306e+02 1.971910657968611247e+02 1.358409247687675361e+02 +1.833826243837603442e+02 2.960483510370855811e+02 2.343723986770386318e+02 1.560358896543934293e+02 2.499669478251469172e+02 1.762005778153444169e+02 1.918050503412152921e+02 2.089352602085182866e+02 2.770127170480132008e+02 1.268157216157417224e+02 2.670673189640755822e+02 1.547628252866769287e+02 2.602514896343354849e+02 1.557532905756793866e+02 2.574076233589491949e+02 2.646855654359934533e+02 1.749681240869035719e+02 2.465698370051858035e+02 1.076897610845538082e+02 2.337637497458482301e+02 1.791847918196868932e+02 1.967068388721293104e+02 2.340964493346380095e+02 2.762770912600988140e+02 1.174465260954359564e+02 2.950490567997024982e+02 1.354710376622284116e+02 2.342233227246520642e+02 1.617966271393036379e+02 2.107879984327653915e+02 +2.493754578342164336e+02 2.275093847135933061e+02 1.466148442335522191e+02 2.261697123059220189e+02 1.213252451599347950e+02 1.628949300801819504e+02 2.100466501082228206e+02 1.508908296808102989e+02 1.488199564735201079e+02 1.727131563468088302e+02 2.306747713688439205e+02 2.570279850661015644e+02 2.309125192178541113e+02 2.422081718543400370e+02 1.769407234272878782e+02 2.688532243604371956e+02 2.276780878660686085e+02 1.065345319601523641e+02 1.535069430280279050e+02 1.717902253122074967e+02 2.876755354986605084e+02 1.683056100689713332e+02 1.120105413679224569e+02 1.755508096146901664e+02 2.095863991316655870e+02 1.523590730880595174e+02 2.944635547123552897e+02 1.444697311944634066e+02 2.165062978405008494e+02 1.410128743297030098e+02 +1.434402193906418006e+02 2.368914090178307106e+02 1.963465933374949941e+02 1.914557752364961516e+02 2.870767419320768568e+02 2.044699144835463187e+02 1.223520556576680036e+02 2.352284247043744472e+02 2.917945011866975165e+02 2.225925999946875322e+02 2.240309397680480288e+02 2.048455962243571093e+02 1.188048963943729035e+02 2.200553599997707579e+02 1.885605934416515765e+02 2.863412817843446874e+02 2.913876692311304737e+02 2.446563674684449552e+02 2.981153955140326843e+02 1.111775924383378253e+02 2.239868361016714857e+02 2.540473271011064469e+02 1.343930974769885438e+02 2.368686732696482409e+02 1.175691554116390591e+02 1.014879352562223715e+02 1.330784448687188046e+02 2.045426156006566885e+02 1.168174380391246245e+02 1.704438548713551995e+02 +2.696784010384477597e+02 2.991318545155386346e+02 2.120364825583467336e+02 1.950895785161033018e+02 1.216112431291165592e+02 2.438998438799096391e+02 1.588292735755803733e+02 2.347670069791354024e+02 1.862846309471772770e+02 2.258642611266068343e+02 1.423367506635381119e+02 2.692888471853933083e+02 2.950212092401994255e+02 2.331327670110776467e+02 1.542291422318579635e+02 2.809064569107727038e+02 2.358857646534314654e+02 2.378124255062788563e+02 2.664164586086786812e+02 1.387157904298663880e+02 2.297158046581682243e+02 2.386372312695162634e+02 1.246509391338716171e+02 2.338956320284196408e+02 1.820257170558419944e+02 1.957425768708682767e+02 1.680974560138464540e+02 1.288235048549348676e+02 1.483029350020115089e+02 1.744880718659300669e+02 +2.512494238114035738e+02 1.112846425403449615e+02 2.472643304237797395e+02 1.241745840646870818e+02 1.808849124644312099e+02 2.524760780760417731e+02 1.836118621524309447e+02 1.408362492891266982e+02 1.099623406752946693e+02 2.383967522197594064e+02 2.436606913384966049e+02 2.770699525768120566e+02 2.597573569531676867e+02 2.935649366424795517e+02 2.702790297508025219e+02 2.563597369995835606e+02 2.279477293752616447e+02 2.477470305460766440e+02 1.962131167814513333e+02 2.859744526791636190e+02 2.703401534622389590e+02 2.763052603711840902e+02 2.934416645125817809e+02 2.193475948646207030e+02 2.822891098008749395e+02 1.085391177109117820e+02 1.782208012387337703e+02 2.335496863699061976e+02 1.715066387390946829e+02 1.948062204233656303e+02 +2.879262290016004613e+02 1.676743911135137068e+02 1.403503828589753937e+02 2.744454339345198832e+02 2.935124358491533485e+02 2.920282649929100671e+02 1.390240222956847447e+02 2.426642861805074745e+02 1.217336684570653489e+02 1.311823750440439085e+02 1.647679902066092836e+02 2.962811279981685288e+02 2.945746172932865647e+02 2.005257587949587332e+02 2.072045953580022228e+02 2.893049469033056766e+02 1.913962360581630833e+02 1.823675529874825543e+02 1.830342103129283373e+02 1.222396004373517400e+02 2.248239872372262482e+02 1.170253438297526429e+02 2.853825568202013301e+02 2.214973458763422514e+02 2.563932510909227176e+02 2.144837192650675206e+02 1.793062298958048473e+02 2.920176466690815005e+02 1.515607839109829627e+02 1.981203765908239802e+02 +1.733053660232129403e+02 1.312183264386245583e+02 1.276233157677672807e+02 2.020942572504836789e+02 2.314817368496994732e+02 2.242589617101967008e+02 2.160504620978007893e+02 2.360595788588375399e+02 2.952977074031120992e+02 2.334652590044975682e+02 1.243453875174208747e+02 1.916144242306085630e+02 1.092365115042800596e+02 1.478765005471206280e+02 2.191946613400726278e+02 2.879274886834762697e+02 2.733443652356662597e+02 1.858481832262083344e+02 2.193747651131673706e+02 2.695165737089945424e+02 2.960753121523491700e+02 1.890691006834304631e+02 2.638343907584013550e+02 1.510492177865631334e+02 1.878288206285384661e+02 2.726561149875388992e+02 1.704246795027074199e+02 1.006381753343381718e+02 2.153734239260733148e+02 2.551451126036402854e+02 +1.591849792872858984e+02 1.304671215023752779e+02 1.427456440770346831e+02 2.882324895344759170e+02 1.680635293254793510e+02 1.205800311663507642e+02 2.861305963205076637e+02 1.219224106654408928e+02 2.467003871618023538e+02 2.830287806498602095e+02 1.445950870572595193e+02 2.496562286252286640e+02 1.464987579205844099e+02 2.848280464142704318e+02 2.785616857190397013e+02 1.837468579783306950e+02 1.246964377230690673e+02 1.251791080124520050e+02 1.496399061799681363e+02 1.375936265087168522e+02 2.547928467777094852e+02 2.554856419260690927e+02 1.285559318166884850e+02 2.092144446410586909e+02 2.868951534942014518e+02 1.178319347908447270e+02 1.347784205269015274e+02 2.851299399919766984e+02 1.754694686670390809e+02 1.016886128619324694e+02 +2.606618423405234353e+02 2.125366732076933545e+02 2.822772640751277322e+02 1.096405633955119185e+02 2.437561663288932721e+02 2.129146561548243994e+02 1.148823764090175530e+02 1.516868774610028368e+02 2.090025176018670265e+02 1.817684320186263562e+02 1.584667226055155709e+02 1.501973711988126468e+02 2.530199923706828713e+02 1.847948752811591930e+02 1.778871618489498303e+02 1.664551902511519188e+02 1.100020157933824265e+02 1.352000835393275509e+02 1.710981737682794801e+02 1.530513645967782566e+02 2.588476693974693035e+02 1.775587245068043956e+02 2.006331886716666588e+02 1.389709403689849694e+02 2.489553638298030194e+02 1.673604491791948021e+02 1.991154502489720812e+02 2.423848982654565418e+02 2.882603768001737308e+02 1.620650086718309240e+02 +2.723642490909132903e+02 1.680927290528325670e+02 1.005734627393615455e+02 1.598916606218045047e+02 1.672547346703738071e+02 2.361420151042074451e+02 2.741857058408131707e+02 2.533004150866734392e+02 2.036092771261417340e+02 1.091915011443997230e+02 1.145604210422382323e+02 1.209982156413156247e+02 2.749595368914399387e+02 2.177794513808643160e+02 2.054163746311436967e+02 2.185860861470465579e+02 1.504022045473846845e+02 1.713704456854883347e+02 2.175221629008602804e+02 1.230663148243889253e+02 2.419648244223723168e+02 1.383010418990747326e+02 2.040260833828849059e+02 2.966316994044250919e+02 1.630596872908637351e+02 2.562534082821714492e+02 2.549425872735235998e+02 1.983522705781282127e+02 1.524860865223137694e+02 2.736848821358530586e+02 +1.277021385004174192e+02 2.448445434866889343e+02 1.296687360965440803e+02 1.874271582575348702e+02 1.145742775945452792e+02 1.884744688522491742e+02 1.336298647132909423e+02 1.523816963142488419e+02 2.658270705367647224e+02 1.781637174983711134e+02 1.154610011723892171e+02 2.005342781476718415e+02 1.303166615041172918e+02 2.397284110571510496e+02 1.612912854182502542e+02 2.821645080329541315e+02 2.544831471501324813e+02 2.622237400581972224e+02 1.417212269902922230e+02 2.054005404298748658e+02 1.092142219674599062e+02 1.652051184306486107e+02 2.825679563619778492e+02 2.056286073102957630e+02 1.772062144904277545e+02 1.163520479257007310e+02 1.006186351926139366e+02 1.734025793931427586e+02 1.446958902579306709e+02 2.025820689614877779e+02 +1.798382687901162740e+02 1.604629760861514001e+02 2.668981169240885265e+02 2.763242846779806996e+02 1.318105471716862098e+02 2.191362245125996537e+02 2.770758446308884686e+02 2.308910816293108326e+02 2.956895796828827656e+02 1.566426856848869988e+02 2.326210561246332418e+02 1.206555816723871715e+02 2.603144096756907970e+02 1.172571782204154829e+02 2.219493974369055991e+02 2.385109304229506790e+02 2.599678734377965839e+02 2.850516346518521686e+02 1.472948582444382168e+02 2.234296740595885922e+02 1.427895312415343199e+02 2.848238578369252423e+02 2.260232767550441508e+02 1.544648385858973541e+02 1.163971462755376791e+02 1.762731012775239492e+02 1.089523563056807660e+02 1.663966154222005116e+02 1.342495772836978745e+02 2.922401077696804350e+02 +2.806557294060240224e+02 1.077657131130299604e+02 1.622983596366119059e+02 1.723469481204717795e+02 2.678046848873893850e+02 1.442059922525422451e+02 2.629931208031973711e+02 2.741083495447689415e+02 1.194142462414748707e+02 1.688961325073638022e+02 2.967954354880449728e+02 1.822107331135221671e+02 1.292333403080546645e+02 1.856814508383810391e+02 2.103923137448445573e+02 2.517859299913771451e+02 2.551152596962431574e+02 2.077883190793959898e+02 2.986930461834413677e+02 1.196764061335889551e+02 2.378823960447958257e+02 1.692017967083341432e+02 1.471250494556689432e+02 2.608355254883699672e+02 1.757172426071724942e+02 2.629426236813185369e+02 1.040244734248400533e+02 1.533558690719498827e+02 2.011860465194789072e+02 1.720545334339216765e+02 +2.966488050331527688e+02 1.809989340563203086e+02 1.871527370563514978e+02 2.315558973515319394e+02 2.657682292004950000e+02 2.237816732699509998e+02 2.282045922056215090e+02 1.846236325909775928e+02 1.644827554373339353e+02 2.760250360653360531e+02 2.492622345937652995e+02 1.483432536002697191e+02 1.527550390024584601e+02 1.573429964258168070e+02 2.090721206423400247e+02 2.535819867756219708e+02 2.420536340362719159e+02 1.691914404667937788e+02 2.388696721384086459e+02 2.593840245957078423e+02 1.331872961625781500e+02 1.116342264469163581e+02 1.680964276125217793e+02 1.555020753508222526e+02 2.422052215908822177e+02 2.626184375196450560e+02 2.674230788003709449e+02 1.948146659156083729e+02 2.663681889818526543e+02 2.795342087705012659e+02 +1.674728956867265310e+02 2.635505920196726493e+02 1.395353777027027604e+02 1.883233466008314565e+02 1.249441512057495913e+02 2.512189370435067417e+02 2.719913755602378842e+02 1.237326636617429614e+02 2.939951219495833357e+02 1.686366002602222807e+02 1.800181056076297068e+02 2.288525977776352818e+02 2.717306800175948638e+02 1.565292507387619594e+02 1.445460932655216766e+02 2.092313282690445249e+02 2.370375511382032698e+02 2.880525812713749474e+02 1.172567175017127141e+02 1.112412797274302250e+02 2.246954385922853135e+02 2.812359340959551446e+02 1.004168603505609241e+02 1.005387863078678805e+02 1.815971195408835683e+02 2.811251817522295937e+02 2.605765849402707772e+02 2.298114360271968621e+02 2.557293814584297706e+02 2.542416589790913122e+02 +2.943583269632734414e+02 1.442274778682184717e+02 2.700917391987959491e+02 2.527420049761408904e+02 1.527279900348522688e+02 1.841979337126335281e+02 2.902442440856567600e+02 2.889101481258517765e+02 1.828125218264408716e+02 1.133179379993730862e+02 1.484787634874768116e+02 2.676352293304336740e+02 1.452118425579454311e+02 2.636966617786087568e+02 1.313546620759107100e+02 1.834019443937838787e+02 2.892465421328221282e+02 2.575015388377624959e+02 1.970702343003932242e+02 2.507528167727347181e+02 1.724897096143170074e+02 2.664268628760375464e+02 1.365257050051324370e+02 1.198011035974838308e+02 1.176831988053894520e+02 1.070946883963453899e+02 1.964638491125322446e+02 2.570844982939356100e+02 1.593905150913052466e+02 1.202569936867807598e+02 +2.734271498156417692e+02 2.352133531486530842e+02 2.590835237087205769e+02 2.260994493040042528e+02 1.805421354394846105e+02 2.728408805160995598e+02 2.367263522625478913e+02 2.580210451062748689e+02 1.204524877415260562e+02 2.946465680607327613e+02 1.547220269335912803e+02 1.186203172746691337e+02 1.923878728892914864e+02 1.094127410697402354e+02 2.222837240826847278e+02 1.529333599077602628e+02 1.861450256630199647e+02 2.125583079944122176e+02 1.527591657960447264e+02 2.694001797345342766e+02 1.986063989766776388e+02 2.192493126389772442e+02 2.986827335637019587e+02 2.790660387254000625e+02 2.781487003899754313e+02 2.564198676846006606e+02 2.597551240338123648e+02 2.358970425952163907e+02 1.951628676328612357e+02 1.078208269500064347e+02 +1.190762776130697205e+02 2.951075493308472346e+02 1.091043363430719069e+02 2.824365312299846664e+02 2.445811468414383398e+02 2.538090805786315514e+02 1.230092364266577363e+02 2.633887649939744051e+02 1.865216093980499181e+02 1.540388898662323243e+02 2.047343894245035756e+02 1.431412534309083640e+02 2.857794001060171922e+02 1.492366175285521592e+02 1.380934567887849198e+02 1.331831467466375898e+02 1.149412013934811796e+02 2.205070844660474734e+02 2.939252657951740844e+02 2.049464694042562769e+02 2.047902832862141054e+02 1.810793422252176015e+02 2.005356992447976836e+02 1.381400138775680375e+02 2.582445444487385657e+02 1.698212931623984616e+02 2.252085951830697468e+02 1.808378144669676999e+02 1.307311344108444473e+02 1.050024101356033697e+02 +1.722314120162143354e+02 2.530014253763471856e+02 1.298340795948372772e+02 2.948664870226410812e+02 2.383106068289312702e+02 1.822969205106659558e+02 2.285226769051377005e+02 2.759417691711663565e+02 2.120970517474504220e+02 2.831046044310812704e+02 2.320579821788242612e+02 1.286125039667014960e+02 1.609837368065715282e+02 2.931112965353385107e+02 1.441758663366052531e+02 2.810263276191118962e+02 1.239857273771131077e+02 2.399447548605567988e+02 1.460208836055017514e+02 1.205325462037979491e+02 2.112513935912650993e+02 1.036793750016967692e+02 1.113202625217208777e+02 1.646612561683649574e+02 1.018350908838390581e+02 1.263835026124204859e+02 2.766683711501553944e+02 1.682407929561517506e+02 2.677103056024840271e+02 2.147294480454548307e+02 +2.763536852866382105e+02 1.511976958084401872e+02 1.026794659371155944e+02 1.805990415690671398e+02 2.442493962549426385e+02 1.881796213041043018e+02 1.028768312506858535e+02 2.787706953534510603e+02 2.589640601731795755e+02 1.730107396932538677e+02 2.218419822849910190e+02 2.651646152747807719e+02 1.476149140151474342e+02 1.986450675254654072e+02 1.050693447352362853e+02 1.819666738706916931e+02 2.873544952103893593e+02 1.472060704631180954e+02 1.297023844405691761e+02 2.824778443572924971e+02 2.918073394139615289e+02 2.128134400148996974e+02 2.223096450508596149e+02 2.761940547406351811e+02 1.348708672340777639e+02 1.857009592938832441e+02 1.062906640064134649e+02 2.104442283262811202e+02 2.812954268214299418e+02 2.739038950945439979e+02 +1.837264129055918147e+02 2.399207190527903322e+02 2.843910623120511900e+02 1.773207161532972975e+02 2.056581469496123873e+02 1.558029517788254168e+02 1.458438122541016924e+02 1.893030782939712253e+02 1.139027557376393673e+02 2.228775749423569437e+02 1.367670384452707140e+02 2.854480456674787092e+02 2.424985140340279202e+02 2.940521113211518696e+02 1.330693282221190259e+02 1.212599008475133076e+02 2.754747741586869552e+02 1.062856492128348549e+02 1.212724485003486166e+02 2.100514698158626743e+02 2.547262582240854272e+02 1.999488755181088777e+02 2.578561029518564283e+02 2.784200494851090752e+02 2.728829168298310606e+02 2.071711407548560544e+02 1.708729380756020362e+02 2.726254883308487251e+02 1.104364015278258364e+02 1.175773277008901090e+02 +2.554381337818412305e+02 1.634513906120204183e+02 2.309962436793083214e+02 2.460443770945291249e+02 1.618890365991254896e+02 1.046310291743186980e+02 2.772116654811295575e+02 2.098555252827713957e+02 2.309383801112169863e+02 2.845300950466865402e+02 1.268119123926061320e+02 1.697885006171669602e+02 1.901887742560337529e+02 2.605757830463372215e+02 2.755463791239279772e+02 1.771647294768940810e+02 2.403902735905423356e+02 1.774352552408031443e+02 1.796883744424403631e+02 2.736192366006921475e+02 2.118505050785533967e+02 1.873353967662169453e+02 1.802980863638028950e+02 1.869858546159753132e+02 1.200946851663063342e+02 2.350811068219035178e+02 2.018941614745772313e+02 1.010158706413519525e+02 1.661546933057649937e+02 2.570882207683835077e+02 +2.856134023048114159e+02 1.356279054667102741e+02 1.225310201562991494e+02 1.529777144242077327e+02 2.936506440162480658e+02 2.589580133771784176e+02 1.864782805190425279e+02 1.931182124516369640e+02 2.913608028278327993e+02 1.555662042949096531e+02 1.173676742008071301e+02 2.242990267171766732e+02 2.651338851871976203e+02 1.128980005738893482e+02 1.283582653966309408e+02 2.071495534530326097e+02 1.241509031508031740e+02 2.393403040292282640e+02 2.829812266966206380e+02 2.294799861563923287e+02 2.129576840814710295e+02 2.165539860914115877e+02 1.357366103660294243e+02 2.396252028023287153e+02 1.395106368224716107e+02 1.700689743264745744e+02 1.253435651632085950e+02 1.508112259783626428e+02 2.310267786371028933e+02 2.311667616985857876e+02 diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/random-int-data.txt b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/random-int-data.txt new file mode 100644 index 0000000000000000000000000000000000000000..4fd11b7509e65b01393a6af6125d1b304d524bd7 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/random-int-data.txt @@ -0,0 +1,100 @@ +-67 65 82 64 51 1 -12 2 -84 -52 12 82 -45 -84 -41 31 -49 36 -70 40 -74 -99 32 64 -6 43 -53 -43 43 96 +-58 20 25 99 -25 78 -6 59 -23 30 36 25 -8 83 -43 -7 -8 42 -90 96 46 88 31 12 68 -21 -6 7 78 -19 +-66 -51 0 13 42 -43 -30 -29 20 10 -24 -5 -42 38 -56 6 1 -80 -65 -91 89 64 -21 49 -84 41 6 -78 71 -2 +-50 -84 -50 -66 46 -88 -10 -28 -25 6 -7 10 -35 86 41 -17 72 -67 13 -67 -76 -84 -15 35 67 40 90 38 -1 -47 +-51 27 -48 26 -73 -46 -68 -56 -38 -4 49 -64 57 -86 -80 70 50 34 84 97 -76 3 -54 -89 -7 -53 15 36 -28 85 +2 -59 4 30 70 -42 -26 -1 27 -90 -18 95 -10 -36 43 24 86 -8 -100 92 80 -40 17 -93 -81 54 -8 84 -53 38 +-80 0 -71 -41 -33 9 -61 0 26 80 64 67 74 68 -72 78 -72 -52 -19 37 -33 -24 -11 -71 -53 -16 25 56 -74 0 +71 -23 49 -36 -43 -70 82 69 -100 -27 50 20 30 84 -33 90 49 39 -52 -51 -86 -76 -72 -88 12 91 -96 -61 -87 -47 +21 39 1 78 68 -80 -54 71 17 -94 34 -20 14 -5 -24 55 -84 -50 -90 -24 -79 -81 53 -50 22 -13 -92 78 -22 -50 +-47 -73 77 -93 -20 51 -37 -14 -37 -18 -8 -14 -71 29 -27 -5 54 77 -7 -2 15 -23 98 -34 -65 -78 -77 -90 -5 -35 +92 -33 71 24 43 -19 50 -40 -48 -33 -51 -14 23 40 -78 -14 -76 1 52 69 93 5 -13 30 -60 -20 -54 49 -52 93 +32 -86 21 -41 -86 -38 97 -35 -37 -89 -15 -18 -46 -37 8 63 -63 -61 57 50 43 -27 -45 98 -56 -81 16 -38 -25 -28 +-18 19 -52 -86 92 -72 23 35 20 57 69 -22 52 -66 -74 -29 -1 -10 -97 22 -97 -93 -70 87 85 -31 42 -29 -10 -36 +78 80 -93 68 41 84 -37 -62 38 -9 99 -60 90 47 -33 -40 -59 97 -28 9 35 -6 -60 -83 -39 -97 -25 -78 95 40 +79 -35 -45 -46 69 10 29 -88 98 -44 66 11 45 -58 -11 -25 51 -44 54 30 59 98 35 -28 93 86 99 19 -27 -83 +80 77 -72 57 -35 -27 86 -67 11 77 -28 -89 -30 -31 -72 64 -95 -75 92 -32 -96 -14 6 -83 -66 -58 71 -17 58 -53 +-1 17 -72 82 -57 -48 -7 -44 -80 85 -99 -9 27 -11 24 13 86 18 67 -9 12 77 98 49 49 12 -82 45 31 -68 +-13 -75 -26 17 91 12 -95 -62 -54 -60 22 50 86 58 -11 -11 -21 31 16 -15 67 90 1 80 -57 -98 35 -54 51 91 +28 -75 -31 49 0 73 75 -66 50 -77 -20 82 -40 -90 -28 32 -44 89 -75 -33 -11 -19 -55 79 18 2 -39 -49 78 -72 +14 56 78 69 -40 -20 -39 71 99 -89 60 -82 -1 -77 -42 94 -41 35 72 11 -13 89 -52 -41 -93 43 -39 -61 68 -4 +88 18 -90 -75 -49 46 -28 -48 -69 -64 77 -8 91 -65 62 -27 -19 34 10 78 82 49 -34 63 78 -88 -17 -37 -85 91 +4 36 -77 -75 -12 70 42 8 7 -31 -69 -74 -65 18 85 -92 91 16 -15 24 -74 -56 71 -70 -90 20 13 73 -68 -65 +92 22 -31 -73 -59 -78 -20 -11 -61 36 -40 34 -96 -12 51 -45 -12 12 -3 -42 -71 68 -8 -91 50 -73 -96 -46 -38 -4 +-87 44 -58 -83 70 -81 32 29 -79 45 -64 -52 57 73 -80 69 7 -22 31 -71 -34 -33 47 79 -17 6 -77 -89 3 50 +85 2 73 -88 -99 -13 -76 1 -90 51 30 -52 75 -2 -8 10 -83 -40 -5 -79 82 19 79 94 49 4 66 -76 6 -48 +29 -34 66 -93 45 -1 -98 92 -92 29 -10 64 -23 -81 -73 -62 -18 37 -29 -50 -52 90 -28 24 -4 -67 -33 25 -78 93 +57 -46 36 -16 34 -59 -96 -86 64 2 28 42 -32 6 -17 37 38 -40 -92 55 -22 -42 11 -77 12 81 -89 -39 -30 -39 +-72 -68 -41 -5 93 55 24 -6 84 77 30 33 -51 -62 6 -5 -83 60 -1 -64 7 -7 -92 31 5 -21 -34 -14 21 -33 +26 -75 -36 -54 -21 -38 -49 -20 82 73 -84 -5 -69 84 -87 12 7 -67 -40 -50 -35 -65 80 -83 -2 1 34 -16 91 82 +61 -21 1 -64 -56 -61 74 16 0 38 51 34 -35 37 -28 -52 -14 61 14 58 50 27 -43 -27 14 56 -16 -78 50 -89 +45 -47 -61 68 -41 -70 14 -51 49 -84 64 -65 88 -39 -88 28 -55 -18 81 -2 -1 -45 65 -6 62 16 71 71 -1 47 +47 60 22 -42 -5 -74 12 66 89 -82 -85 65 74 0 -18 56 -39 84 -65 -42 -33 -60 23 33 -8 -72 3 -64 -3 -25 +-70 11 -19 -12 -1 -50 -89 -61 78 28 55 92 -17 86 -17 -45 -31 68 -24 -99 -59 27 79 -2 21 -80 54 9 14 -70 +-38 52 -99 50 -46 -63 -74 -41 -43 -62 -81 38 -99 17 -94 -6 44 -20 -13 -30 71 -43 43 -28 -8 57 -93 98 4 42 +-17 -27 -60 -22 86 -49 39 -83 72 -16 82 74 73 -29 16 -59 81 -60 -96 51 -62 -55 -79 -31 -15 -67 -18 -83 -61 -86 +28 37 -44 7 -17 -10 -65 8 -78 -17 -46 -5 -35 -86 13 -16 27 24 60 -12 -48 -45 16 -33 70 -45 -63 -60 21 70 +-75 -89 -93 -93 62 -44 -39 46 31 57 72 30 -65 29 66 -53 2 -2 71 -90 -73 -40 -63 32 68 30 25 98 38 92 +88 3 5 73 -2 -61 -94 79 99 94 71 -83 -40 80 -79 -14 -34 -99 -52 27 23 13 13 -35 -74 13 43 -19 2 -62 +92 -47 -27 9 -68 -86 -57 43 9 -81 -9 69 52 -28 80 -13 -6 -44 -81 -89 -10 30 -64 86 -76 -11 -100 15 12 -62 +76 -42 39 70 74 79 84 -52 18 -58 78 53 89 58 -32 20 -51 35 12 37 -70 -21 5 97 67 -25 -25 -10 2 30 +-84 26 -60 -34 11 -27 47 85 -89 29 54 -53 66 -9 12 4 92 70 2 -12 -55 72 -62 -79 -8 68 -19 12 -8 -100 +78 -97 -76 86 -47 42 99 -3 9 49 -84 86 26 43 -26 90 23 -66 -37 -35 25 -12 -42 -12 96 -15 48 87 -95 -12 +-60 57 -30 -4 -84 24 -82 -5 34 56 76 81 -64 23 32 34 -41 -48 -6 77 -42 64 87 92 82 59 9 -71 -56 -45 +-74 -90 -27 93 33 15 -35 -73 78 23 17 -28 9 63 9 35 15 32 0 -4 -32 54 -76 14 -14 -8 16 -43 -81 57 +-2 22 85 -33 -48 74 64 -59 -27 17 -65 27 -50 -81 41 -69 -26 -29 -83 48 -81 51 58 -62 -63 -55 -63 39 32 -34 +98 -99 13 25 -10 43 -62 50 82 -90 -51 40 -71 82 27 -73 19 -62 37 10 -21 45 -94 -45 -41 -3 44 86 -2 27 +-80 -89 -57 87 -42 19 32 -49 37 -4 -30 54 46 -3 -92 89 60 37 -86 38 61 93 45 -45 -86 54 21 45 50 -53 +7 -68 71 -6 41 -72 67 45 15 46 85 59 82 19 65 75 -62 -35 47 -51 23 41 -54 27 -99 14 9 69 60 62 +99 -51 83 -47 -19 -57 -22 51 -52 52 92 80 69 1 -31 0 -19 -54 73 -5 3 82 -86 -84 -95 -83 -92 -52 -90 -79 +43 -75 62 99 66 -43 -38 -21 23 35 -63 -61 -46 5 3 -90 -28 55 87 89 -29 -46 23 -61 -5 10 -70 -63 50 -14 +39 38 10 66 -24 -45 55 -33 31 29 44 31 73 44 6 69 -21 -58 -3 93 -51 86 -16 -88 88 -30 75 78 -20 -12 +-11 11 -19 40 82 6 10 22 90 -78 -88 -49 72 69 -62 42 -23 22 -38 -98 0 -3 -43 20 9 18 -67 -7 22 21 +99 80 -55 74 43 -31 60 -26 -29 -6 75 60 92 -42 85 18 1 1 -74 -44 -12 72 -57 -98 99 62 45 -40 -39 -75 +50 30 -18 -29 -80 -59 -96 46 -99 -76 -13 -75 -93 -95 -45 62 -37 53 -96 57 -40 3 14 -45 -84 58 75 16 37 -6 +1 -47 87 -99 -22 -22 -20 71 -91 13 35 -80 75 65 -87 16 -37 99 -60 49 52 18 55 -11 18 24 -65 -80 8 -79 +-8 -87 86 -9 -64 -76 59 -52 -89 18 13 70 44 93 99 62 39 49 83 28 72 -71 -13 -71 -22 44 -87 73 -68 80 +41 -26 44 -63 -26 -83 -44 63 -51 -48 52 -8 55 73 -45 84 40 45 32 -34 -78 -46 -79 57 -40 11 34 -75 -20 91 +94 9 -35 -5 3 59 -63 2 -7 -72 -34 -70 78 99 -29 37 11 91 61 29 85 -15 59 79 47 41 19 -18 -92 47 +-59 -89 57 -72 -79 88 -85 18 -35 -96 -57 33 83 70 -55 -16 -21 72 -53 89 -44 -86 9 -44 -26 78 2 -93 -75 6 +55 73 89 80 -69 -93 -39 -88 62 49 91 -68 87 -26 40 16 -49 -53 -57 23 -97 39 -78 44 -15 1 60 -87 43 -42 +-2 -23 -74 -80 -59 52 -58 68 64 97 -86 -41 -88 35 49 3 -40 90 34 -2 3 13 -95 8 -1 6 75 92 19 -31 +57 76 65 3 37 -72 -43 57 64 -23 41 87 26 76 -18 -32 28 47 11 47 -33 -12 4 81 -92 -47 -81 43 -2 5 +68 74 66 -89 -95 -40 -78 -58 -54 -20 2 20 94 -35 58 -20 41 77 0 95 39 14 36 -40 -85 -60 -63 82 0 58 +-61 -99 61 10 -2 -31 -70 37 -77 -10 85 95 -28 70 -81 -78 -68 -33 -77 77 -6 42 -100 -68 -59 -86 -42 -74 35 -32 +64 -1 -1 -64 51 11 -65 47 -87 -8 5 58 22 -80 68 -25 24 59 -25 -75 95 -22 -73 27 86 -39 -98 -1 -17 -32 +94 -50 -53 -62 -53 46 50 38 -95 -77 40 -38 -23 -14 -68 -20 -47 23 -8 -12 -92 -69 -97 30 94 -45 47 -81 82 -60 +28 67 -48 4 74 27 -30 12 -32 35 91 -83 30 -55 -7 79 97 11 93 -45 -79 31 78 65 84 -23 -26 17 -61 43 +44 60 -88 72 31 98 55 -4 66 -14 10 -81 -40 66 -15 21 69 -98 34 3 75 18 98 -6 47 -39 31 -19 30 -51 +-6 18 -93 31 51 -20 -16 -33 -38 -19 71 4 -53 23 97 1 -28 -72 -44 -48 45 33 -76 86 64 49 -45 -34 -9 -76 +-19 8 28 -27 -51 -58 -36 63 -92 -95 70 41 -38 -49 -95 -100 43 97 -60 -5 -56 45 -13 -3 20 -10 -21 -85 -5 63 +-74 -74 -74 -39 -57 -12 51 11 -11 -22 -26 -54 71 24 -37 77 -90 77 75 86 -53 3 69 -99 -82 -59 30 81 -21 -86 +67 63 87 -15 60 -82 87 51 -39 -49 -16 74 51 17 6 47 98 89 -20 -98 97 -61 18 34 37 -36 37 -96 90 44 +53 -8 37 -76 -61 70 -77 -11 98 -80 12 -80 6 -89 8 -59 -69 -100 -52 -30 95 -58 61 29 52 -64 -51 10 16 -58 +54 -10 49 62 76 -25 80 36 13 5 59 -65 14 41 26 -78 23 -45 -51 -85 91 -43 -61 -37 94 27 -11 49 98 48 +53 -51 27 34 28 -53 18 17 31 -31 59 71 -34 25 54 -84 -34 -24 76 38 -36 15 -1 56 2 -12 0 26 -38 -62 +4 -94 -63 -21 -95 -42 -12 86 14 -86 -1 80 -48 62 -47 -52 3 91 -86 11 79 32 -24 -33 -54 19 -17 28 -33 -97 +-18 41 84 1 -83 48 -99 -64 26 -52 3 -64 68 -98 93 -79 -97 11 88 74 41 -31 -42 -35 -66 18 97 -30 19 -93 +-19 42 61 -91 -20 59 -11 -64 -60 85 -6 -71 33 -52 46 51 -86 -77 74 -4 74 -81 1 -39 -30 12 -12 20 66 60 +86 1 -67 -91 -92 -22 91 -90 -45 26 53 -6 99 46 -29 -40 -99 57 -45 -47 -3 -86 90 -78 -33 73 90 -51 -75 2 +88 -34 -2 30 -18 35 -23 90 99 -49 90 -79 94 -38 48 67 -35 -58 81 -24 18 -54 83 65 -58 -12 13 89 -59 57 +92 -99 94 -73 97 -78 -93 98 -78 95 -21 -17 -11 -92 69 -60 86 9 -36 -18 -33 -39 -65 74 -65 37 -49 87 -28 -81 +-95 2 -18 20 93 54 86 -63 -5 -89 17 -9 75 -66 -64 -82 -46 -48 82 5 -89 19 -32 -45 53 -47 21 -9 40 34 +86 87 55 -41 49 -10 -6 -7 -99 23 90 -50 -9 -81 77 65 29 -21 22 -82 19 48 -24 -72 75 -66 -69 -17 72 6 +13 37 96 31 -65 -54 -91 -27 84 52 -9 -28 85 96 14 63 -34 -29 -85 78 -75 -44 -30 -5 4 72 -45 6 13 71 +96 -69 67 59 69 46 80 42 81 30 89 -45 -10 -44 25 31 89 16 -36 86 31 92 1 5 -2 92 -11 77 20 40 +-48 98 -100 30 54 9 84 -88 5 48 93 56 -94 -89 81 33 44 -30 -95 -98 29 -33 13 -26 -59 -80 -68 -40 12 11 +82 -63 -30 -67 54 -68 50 -63 -91 -68 -45 -66 -58 16 -25 9 -50 -59 -55 4 -2 0 -63 67 30 -21 -8 55 21 -68 +9 -8 56 -6 84 81 -63 -35 81 56 -50 -54 96 -51 86 0 66 -4 -18 65 -26 -57 8 78 -54 17 18 86 21 68 +9 38 33 16 3 86 -57 28 -6 -44 -42 -2 3 -71 -86 23 34 -29 33 -30 67 63 -11 76 -65 92 30 -66 61 1 +-72 -85 -1 64 -79 -78 -1 15 -35 -32 80 33 -36 -82 24 -65 -23 29 38 -31 87 55 -18 -52 -77 -22 -11 54 62 -48 +65 -77 50 16 41 -94 -21 16 85 24 60 86 -78 -13 69 46 55 5 -27 -18 -6 -1 59 -62 -58 -99 -49 -84 89 18 +-21 -15 -55 60 78 98 67 94 58 -5 -36 42 36 73 13 72 -78 -68 41 -37 -33 -46 -80 40 13 -44 -71 -8 15 -77 +16 -93 -42 -10 14 57 -54 -3 -44 -21 30 -93 71 25 -60 -94 93 5 -94 -84 -72 1 -50 -34 23 -15 15 18 72 -29 +-22 -82 -30 -87 -88 -25 46 32 -30 -55 -79 -85 71 -89 -57 -88 21 53 -100 -64 -92 -97 56 -51 -17 -34 -31 6 -68 84 +-53 -51 90 -38 -61 57 -63 67 22 22 70 44 43 97 20 -62 -74 72 83 -32 35 -66 -29 5 -88 55 -94 94 -19 55 +57 51 29 -42 -21 63 -57 7 -48 -87 -60 -55 -77 -53 -1 -85 64 60 53 71 41 59 -61 -73 -12 86 90 10 -60 -38 +2 -9 14 67 -2 70 11 -78 26 -55 -86 -25 99 66 63 64 46 59 66 -37 -78 -70 63 1 -20 2 46 50 34 19 +-87 -40 75 -11 -88 -80 -95 -20 -92 -28 83 24 88 -39 83 -36 -61 56 99 -73 -59 -85 -49 -10 91 12 -79 -18 -15 6 +35 -74 -4 -15 40 -87 81 -22 -12 -46 14 9 98 -35 -2 -12 57 -74 -52 71 70 -70 -61 -47 89 44 33 -100 54 42 +-4 -34 80 -12 -15 -9 -8 -29 89 -55 -33 89 16 -33 -73 -82 98 27 88 59 48 20 -67 -21 -86 11 -50 46 64 -8 diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/random-uint-data.txt b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/random-uint-data.txt new file mode 100644 index 0000000000000000000000000000000000000000..c1ec7a5d64e540428507ee5a3358743b6c034ebc --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/random-uint-data.txt @@ -0,0 +1,100 @@ +52 34 59 34 64 20 89 69 26 93 95 32 17 93 77 49 51 60 51 27 60 10 61 2 16 30 41 68 65 0 +43 74 11 37 32 61 72 29 47 21 7 47 68 58 22 33 29 37 14 45 71 1 67 79 69 9 6 6 95 78 +86 20 68 67 43 5 77 70 96 37 79 71 35 30 22 4 56 28 33 50 97 17 85 52 21 5 57 19 35 97 +15 21 99 4 54 39 15 29 68 21 50 76 64 51 79 0 24 5 65 95 90 51 99 82 9 80 61 32 2 38 +46 97 53 96 51 84 18 42 30 52 82 77 72 59 1 67 72 16 14 63 70 94 20 27 38 70 86 95 41 75 +2 35 45 63 92 76 81 60 62 72 90 46 47 33 1 30 54 22 50 85 63 61 22 79 45 53 45 33 8 28 +43 41 14 79 2 77 95 16 74 19 17 78 47 12 68 55 3 2 77 10 35 86 52 33 47 26 98 42 48 86 +18 32 85 4 91 10 69 68 15 42 58 77 88 64 91 43 56 30 92 11 52 23 43 92 65 50 68 8 80 81 +20 57 38 44 62 10 80 25 32 11 70 32 13 50 41 55 44 0 28 83 5 1 34 94 55 52 56 24 76 21 +36 43 59 28 10 59 4 41 64 98 54 66 44 3 37 41 67 10 85 23 58 35 58 34 35 79 46 18 1 51 +72 63 85 51 23 91 3 56 35 72 38 26 91 0 68 98 27 10 12 71 30 1 14 47 47 88 17 68 78 46 +53 47 1 89 95 53 11 45 46 6 91 20 57 35 58 79 60 3 21 45 4 18 59 96 36 12 13 83 52 46 +33 91 82 24 97 28 50 43 65 22 14 44 32 57 33 10 34 77 58 6 27 90 26 77 62 81 87 96 0 32 +96 44 59 3 47 18 0 91 83 68 48 26 67 82 39 18 88 47 80 0 57 40 30 7 57 74 49 37 57 65 +18 44 0 46 47 30 65 79 53 8 26 42 80 76 30 61 82 93 78 25 89 49 55 15 86 63 35 74 41 11 +18 14 40 90 91 79 80 36 33 72 25 56 73 28 65 27 62 17 60 84 23 70 32 26 77 97 47 94 72 1 +82 36 68 10 83 83 40 42 51 55 82 6 37 69 93 82 64 13 54 30 45 36 87 59 1 80 39 93 11 61 +78 34 53 39 64 52 52 22 33 69 71 82 57 37 78 52 62 31 87 68 70 5 85 94 41 75 38 45 84 22 +36 23 51 15 61 76 88 85 36 96 21 60 34 61 72 60 69 81 5 17 16 82 30 61 39 96 40 70 42 71 +45 30 60 50 78 90 36 40 11 85 42 14 61 3 66 53 68 14 41 30 97 74 79 91 64 8 1 53 52 33 +55 24 35 4 49 51 44 70 93 78 25 65 1 29 96 12 93 94 13 65 4 47 84 10 90 12 36 48 21 36 +17 74 61 54 21 83 35 97 47 90 57 11 16 39 95 78 23 40 23 55 17 51 20 73 98 93 50 32 58 4 +84 76 78 33 50 29 11 20 5 93 63 22 91 92 44 85 62 25 63 92 36 26 57 33 8 74 69 64 78 91 +58 34 91 71 37 84 28 90 28 37 97 7 26 44 59 18 58 64 31 83 16 17 50 36 65 81 19 63 66 64 +20 71 1 35 87 5 47 27 6 95 86 75 74 9 94 93 26 5 61 3 97 88 0 57 21 64 46 24 86 12 +23 53 31 39 37 77 29 51 85 10 41 91 67 82 50 91 53 72 75 81 50 63 52 92 83 49 92 50 26 9 +38 43 13 87 11 45 28 16 27 61 70 52 77 9 57 42 73 22 32 95 23 91 93 63 16 44 26 9 93 83 +77 68 21 96 44 45 9 2 14 2 67 90 55 82 67 21 18 64 31 16 2 27 86 42 34 72 22 98 91 33 +89 66 87 76 0 32 81 39 55 76 23 56 51 53 75 79 30 86 1 66 64 14 46 84 92 19 95 47 77 97 +88 79 61 26 66 92 54 22 15 25 26 0 76 27 17 59 48 4 42 61 65 91 0 62 55 79 29 88 10 11 +24 89 91 39 56 36 16 86 41 31 14 35 7 71 77 74 33 11 49 7 96 83 31 63 90 49 96 22 58 86 +45 7 93 44 50 54 83 80 3 36 11 38 14 17 10 84 96 94 26 34 26 75 72 0 41 89 96 47 39 88 +0 95 2 22 68 38 0 3 51 6 13 10 14 49 75 69 25 39 63 67 12 80 37 77 10 90 60 35 84 37 +98 56 99 75 49 66 3 33 65 86 1 79 91 23 69 98 91 73 95 45 64 26 99 75 49 77 71 55 42 18 +80 39 26 94 85 42 91 27 14 57 36 34 10 44 38 77 23 39 54 25 32 5 17 9 66 3 67 94 20 11 +88 80 30 77 72 67 16 75 84 87 60 89 21 94 24 11 63 8 79 89 37 18 6 82 76 70 81 95 67 95 +92 36 55 55 43 18 76 94 30 74 95 38 45 95 54 87 22 57 4 65 15 90 90 38 73 24 67 24 36 25 +98 30 34 68 11 48 42 38 80 23 12 91 77 22 65 2 88 31 70 12 46 63 17 63 27 76 21 71 70 7 +76 29 56 12 41 66 22 96 8 6 7 13 27 10 77 90 2 76 30 24 81 88 19 16 93 13 30 24 98 96 +45 94 89 41 52 14 71 88 80 74 7 85 44 69 65 88 4 15 84 97 86 5 53 15 39 34 9 10 45 20 +95 47 45 96 71 10 36 10 90 49 7 68 14 46 97 89 82 58 69 34 93 77 90 9 27 91 29 27 22 17 +80 6 29 26 34 59 10 55 32 53 18 72 39 40 29 35 52 64 2 64 38 83 16 46 53 20 19 8 10 67 +47 44 79 32 58 82 26 69 0 26 4 73 95 98 61 96 20 38 3 92 6 5 25 24 42 49 15 92 80 16 +74 37 86 84 47 15 56 36 43 59 72 72 74 73 49 54 26 5 40 80 78 48 4 65 31 70 14 91 88 72 +91 45 73 62 83 40 49 3 27 79 80 90 3 3 58 44 7 66 77 42 37 25 20 91 47 63 71 7 72 22 +51 3 36 90 45 84 18 55 75 78 42 62 86 63 65 67 46 75 1 79 2 85 85 60 36 92 34 89 66 99 +36 99 0 63 89 65 54 58 52 28 98 27 67 1 45 71 35 52 55 55 44 23 46 89 83 37 8 2 92 75 +51 13 71 2 9 95 23 60 24 98 86 43 32 16 75 70 92 78 26 84 29 14 35 55 61 89 73 59 76 44 +59 57 28 92 33 50 70 94 89 67 70 38 53 16 35 70 35 92 39 78 88 80 71 1 93 21 87 64 49 84 +29 6 17 45 38 65 41 48 81 69 34 12 2 14 41 71 16 92 69 27 61 74 58 20 75 19 39 66 57 82 +12 8 14 85 97 31 58 31 20 76 6 42 29 95 60 94 15 84 86 69 73 52 73 57 12 66 89 65 60 84 +20 74 96 34 83 41 8 37 22 36 30 25 20 8 58 73 9 75 76 73 84 38 16 24 95 95 68 66 43 19 +33 15 25 80 48 69 63 39 16 45 6 77 14 46 38 15 64 85 49 5 59 28 9 4 23 68 59 26 1 75 +35 45 3 6 34 59 55 51 81 59 59 93 18 41 8 44 88 7 86 4 88 90 24 54 73 62 89 13 44 92 +72 60 68 83 39 32 30 15 98 92 69 94 51 48 9 0 4 1 30 92 40 1 61 82 66 4 39 10 93 87 +12 20 34 72 33 31 67 71 67 47 98 76 53 29 17 17 13 31 43 76 25 37 8 39 9 5 96 41 87 66 +96 30 2 57 57 10 14 17 86 76 35 94 42 54 18 24 19 34 12 42 18 11 83 65 86 38 45 17 60 70 +19 62 71 99 35 60 96 30 44 80 78 15 14 5 32 43 10 26 81 72 41 98 30 87 75 8 53 33 25 95 +22 0 38 57 88 7 47 83 49 41 52 1 14 93 41 3 18 42 15 57 28 74 97 2 18 48 64 25 77 69 +36 95 65 81 44 41 6 74 62 16 72 81 15 72 31 5 22 17 19 6 7 15 82 10 31 93 11 45 41 11 +22 76 14 62 34 65 82 5 57 51 51 5 1 6 17 43 28 31 90 99 48 14 96 49 95 40 87 85 40 51 +95 13 99 46 52 80 4 18 95 94 0 46 10 80 3 34 60 15 86 10 28 59 6 35 14 93 18 8 3 65 +57 37 6 31 45 85 42 34 47 92 48 40 7 17 5 74 67 62 0 74 58 21 23 3 5 24 50 54 99 19 +24 14 10 4 36 33 88 51 40 66 40 56 65 23 43 13 82 62 27 88 89 91 36 37 19 11 50 39 96 68 +82 7 39 80 52 90 57 17 61 15 51 71 82 15 21 44 4 46 75 50 78 18 63 75 98 45 6 16 57 25 +0 26 56 74 62 84 71 42 25 86 68 10 73 0 71 6 15 99 1 51 45 42 5 49 3 35 84 29 15 36 +60 78 76 3 95 73 36 57 35 44 50 42 85 57 18 69 37 42 75 79 15 12 74 72 51 36 79 3 58 71 +69 24 16 96 17 25 21 94 71 78 74 39 7 96 3 12 13 16 7 99 65 72 12 28 75 44 55 8 75 67 +3 13 92 9 92 83 69 91 65 92 29 63 46 1 4 62 29 85 47 93 81 3 15 23 63 50 17 9 13 13 +9 18 46 53 0 86 10 41 87 89 24 25 70 73 8 23 27 76 66 46 58 39 28 1 99 64 59 13 7 68 +72 57 90 50 47 57 34 27 94 39 23 31 74 77 45 74 18 49 96 8 95 50 20 81 73 55 72 2 32 15 +87 77 74 5 99 86 5 65 97 39 17 74 48 87 20 66 28 2 18 58 49 22 79 23 36 30 64 20 71 32 +35 43 66 96 63 77 18 90 47 86 94 19 88 79 23 12 38 4 56 42 36 2 77 1 3 17 64 52 31 24 +80 2 4 39 61 60 74 83 28 28 61 10 71 82 44 29 55 30 1 58 81 79 34 41 85 82 84 55 22 12 +76 77 58 92 90 0 54 28 77 68 58 12 1 81 37 28 19 60 71 59 25 83 8 49 52 11 28 65 59 70 +14 1 92 90 5 48 28 78 1 42 54 43 60 83 72 19 28 33 12 52 18 15 56 95 39 33 37 70 53 23 +53 76 26 31 18 81 83 79 25 1 82 43 50 24 63 49 5 23 66 37 80 41 63 77 2 28 15 21 32 93 +80 41 81 7 37 95 19 42 57 30 12 25 29 34 41 45 87 8 20 95 63 16 99 55 16 61 16 36 81 25 +32 30 2 81 23 25 88 30 37 76 52 77 79 58 21 58 10 0 13 32 72 80 3 75 75 25 21 9 79 18 +26 13 36 63 43 2 50 41 65 18 88 44 82 75 73 24 1 30 54 68 15 18 22 50 41 99 27 96 51 53 +22 4 76 11 85 88 28 75 1 2 92 66 63 3 58 43 53 5 1 24 99 90 87 87 41 1 85 37 98 92 +16 39 13 88 60 55 35 11 34 23 23 85 79 41 79 87 65 78 47 83 88 78 35 84 30 61 37 58 25 55 +27 33 15 76 82 79 73 92 93 78 18 38 22 96 63 92 41 9 50 96 14 55 8 60 15 61 97 56 43 22 +42 34 94 11 35 70 50 49 36 34 59 14 87 84 88 83 4 69 29 99 35 24 2 18 97 97 74 88 91 49 +33 25 71 12 60 2 48 22 81 33 27 95 54 25 53 14 20 43 26 96 98 37 64 27 72 33 78 45 22 61 +61 21 91 38 92 47 26 90 78 96 58 41 21 72 81 61 55 9 55 60 28 25 25 74 73 81 64 16 49 39 +90 89 12 93 91 23 82 36 63 58 73 81 49 32 60 39 4 84 73 16 18 26 58 85 46 28 82 91 72 7 +79 41 28 76 33 70 47 6 18 64 40 54 45 61 28 63 87 83 38 9 65 68 62 45 80 63 89 29 20 40 +20 59 58 23 61 79 35 19 78 2 26 48 90 34 69 31 31 42 92 33 18 74 28 47 45 52 36 89 19 40 +58 13 72 24 31 26 73 72 84 29 85 99 20 32 54 92 8 80 86 58 23 80 59 21 76 75 90 76 92 57 +74 53 80 51 8 88 84 63 82 99 97 77 38 9 51 61 37 20 68 47 65 21 53 82 85 96 62 65 35 4 +71 82 14 18 88 79 38 76 66 27 10 10 62 54 80 21 6 57 83 33 52 10 97 37 6 38 12 51 0 84 +95 30 75 92 84 30 55 57 32 44 53 24 77 81 34 84 69 85 91 33 50 72 62 79 62 12 59 75 99 81 +38 42 47 1 11 34 27 77 70 85 89 84 79 15 14 54 78 93 72 68 63 39 98 72 55 32 93 0 13 21 +3 15 10 15 3 31 84 89 53 5 60 41 66 77 45 12 68 68 50 68 99 64 46 54 30 56 2 90 99 78 +66 10 27 89 42 16 9 98 16 2 68 51 0 22 73 60 69 96 37 69 30 36 20 21 51 26 65 13 74 86 +94 58 34 97 77 88 90 75 47 30 6 36 89 66 48 9 20 6 52 45 0 37 99 46 11 53 53 72 94 40 +5 71 50 96 89 71 80 43 27 95 49 9 74 28 62 65 64 97 2 55 58 11 69 0 31 22 73 20 66 11 +63 39 84 62 64 5 56 92 26 86 19 20 56 85 42 48 56 51 54 29 26 95 72 38 70 61 16 54 57 19 +76 97 40 99 73 68 98 92 97 62 73 1 29 72 18 70 90 4 98 95 70 36 65 45 86 36 88 38 64 54 diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/selfdual-4d-polytope.txt b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/selfdual-4d-polytope.txt new file mode 100644 index 0000000000000000000000000000000000000000..47ce4a7ae522fc2a2bbaa9d8ca285913b8ef0712 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/tests/data/selfdual-4d-polytope.txt @@ -0,0 +1,27 @@ +# The facets of a self-dual 4-dim regular polytope +# with 24 octahedron facets. Taken from cddlib. +# Format b + Ax >= 0 + 1 1 1 1 1 + 1 1 1 1 -1 + 1 1 1 -1 1 + 1 1 1 -1 -1 + 1 1 -1 1 1 + 1 1 -1 1 -1 + 1 1 -1 -1 1 + 1 1 -1 -1 -1 + 1 -1 1 1 1 + 1 -1 1 1 -1 + 1 -1 1 -1 1 + 1 -1 1 -1 -1 + 1 -1 -1 1 1 + 1 -1 -1 1 -1 + 1 -1 -1 -1 1 + 1 -1 -1 -1 -1 + 1 2 0 0 0 + 1 0 2 0 0 + 1 0 0 2 0 + 1 0 0 0 2 + 1 -2 0 0 0 + 1 0 -2 0 0 + 1 0 0 -2 0 + 1 0 0 0 -2 diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/tests/test__plotutils.py b/venv/lib/python3.12/site-packages/scipy/spatial/tests/test__plotutils.py new file mode 100644 index 0000000000000000000000000000000000000000..0e2553bf7ad56e97b567e5b334ccf17921f7f7f3 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/tests/test__plotutils.py @@ -0,0 +1,91 @@ +import pytest +import numpy as np +from numpy.testing import assert_, assert_array_equal, assert_allclose + +try: + import matplotlib + matplotlib.rcParams['backend'] = 'Agg' + import matplotlib.pyplot as plt + has_matplotlib = True +except Exception: + has_matplotlib = False + +from scipy.spatial import \ + delaunay_plot_2d, voronoi_plot_2d, convex_hull_plot_2d, \ + Delaunay, Voronoi, ConvexHull + + +@pytest.mark.skipif(not has_matplotlib, reason="Matplotlib not available") +class TestPlotting: + points = [(0,0), (0,1), (1,0), (1,1)] + + def test_delaunay(self): + # Smoke test + fig = plt.figure() + obj = Delaunay(self.points) + s_before = obj.simplices.copy() + r = delaunay_plot_2d(obj, ax=fig.gca()) + assert_array_equal(obj.simplices, s_before) # shouldn't modify + assert_(r is fig) + delaunay_plot_2d(obj, ax=fig.gca()) + + def test_voronoi(self): + # Smoke test + fig = plt.figure() + obj = Voronoi(self.points) + r = voronoi_plot_2d(obj, ax=fig.gca()) + assert_(r is fig) + voronoi_plot_2d(obj) + voronoi_plot_2d(obj, show_vertices=False) + + def test_convex_hull(self): + # Smoke test + fig = plt.figure() + tri = ConvexHull(self.points) + r = convex_hull_plot_2d(tri, ax=fig.gca()) + assert_(r is fig) + convex_hull_plot_2d(tri) + + def test_gh_19653(self): + # aspect ratio sensitivity of voronoi_plot_2d + # infinite Voronoi edges + points = np.array([[245.059986986012, 10.971011721360075], + [320.49044143557785, 10.970258360366753], + [239.79023081978914, 13.108487516946218], + [263.38325791238833, 12.93241352743668], + [219.53334398353175, 13.346107628161008]]) + vor = Voronoi(points) + fig = voronoi_plot_2d(vor) + ax = fig.gca() + infinite_segments = ax.collections[1].get_segments() + expected_segments = np.array([[[282.77256, -254.76904], + [282.729714, -4544.744698]], + [[282.77256014, -254.76904029], + [430.08561382, 4032.67658742]], + [[229.26733285, -20.39957514], + [-168.17167404, -4291.92545966]], + [[289.93433364, 5151.40412217], + [330.40553385, 9441.18887532]]]) + assert_allclose(infinite_segments, expected_segments) + + def test_gh_19653_smaller_aspect(self): + # reasonable behavior for less extreme aspect + # ratio + points = np.array([[24.059986986012, 10.971011721360075], + [32.49044143557785, 10.970258360366753], + [23.79023081978914, 13.108487516946218], + [26.38325791238833, 12.93241352743668], + [21.53334398353175, 13.346107628161008]]) + vor = Voronoi(points) + fig = voronoi_plot_2d(vor) + ax = fig.gca() + infinite_segments = ax.collections[1].get_segments() + expected_segments = np.array([[[28.274979, 8.335027], + [28.270463, -42.19763338]], + [[28.27497869, 8.33502697], + [43.73223829, 56.44555501]], + [[22.51805823, 11.8621754], + [-12.09266506, -24.95694485]], + [[29.53092448, 78.46952378], + [33.82572726, 128.81934455]]]) + assert_allclose(infinite_segments, expected_segments) diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/tests/test__procrustes.py b/venv/lib/python3.12/site-packages/scipy/spatial/tests/test__procrustes.py new file mode 100644 index 0000000000000000000000000000000000000000..42a3c4d35bd55e2ffecefb691c805f517c56d6ca --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/tests/test__procrustes.py @@ -0,0 +1,116 @@ +import numpy as np +from numpy.testing import assert_allclose, assert_equal, assert_almost_equal +from pytest import raises as assert_raises + +from scipy.spatial import procrustes + + +class TestProcrustes: + def setup_method(self): + """creates inputs""" + # an L + self.data1 = np.array([[1, 3], [1, 2], [1, 1], [2, 1]], 'd') + + # a larger, shifted, mirrored L + self.data2 = np.array([[4, -2], [4, -4], [4, -6], [2, -6]], 'd') + + # an L shifted up 1, right 1, and with point 4 shifted an extra .5 + # to the right + # pointwise distance disparity with data1: 3*(2) + (1 + 1.5^2) + self.data3 = np.array([[2, 4], [2, 3], [2, 2], [3, 2.5]], 'd') + + # data4, data5 are standardized (trace(A*A') = 1). + # procrustes should return an identical copy if they are used + # as the first matrix argument. + shiftangle = np.pi / 8 + self.data4 = np.array([[1, 0], [0, 1], [-1, 0], + [0, -1]], 'd') / np.sqrt(4) + self.data5 = np.array([[np.cos(shiftangle), np.sin(shiftangle)], + [np.cos(np.pi / 2 - shiftangle), + np.sin(np.pi / 2 - shiftangle)], + [-np.cos(shiftangle), + -np.sin(shiftangle)], + [-np.cos(np.pi / 2 - shiftangle), + -np.sin(np.pi / 2 - shiftangle)]], + 'd') / np.sqrt(4) + + def test_procrustes(self): + # tests procrustes' ability to match two matrices. + # + # the second matrix is a rotated, shifted, scaled, and mirrored version + # of the first, in two dimensions only + # + # can shift, mirror, and scale an 'L'? + a, b, disparity = procrustes(self.data1, self.data2) + assert_allclose(b, a) + assert_almost_equal(disparity, 0.) + + # if first mtx is standardized, leaves first mtx unchanged? + m4, m5, disp45 = procrustes(self.data4, self.data5) + assert_equal(m4, self.data4) + + # at worst, data3 is an 'L' with one point off by .5 + m1, m3, disp13 = procrustes(self.data1, self.data3) + #assert_(disp13 < 0.5 ** 2) + + def test_procrustes2(self): + # procrustes disparity should not depend on order of matrices + m1, m3, disp13 = procrustes(self.data1, self.data3) + m3_2, m1_2, disp31 = procrustes(self.data3, self.data1) + assert_almost_equal(disp13, disp31) + + # try with 3d, 8 pts per + rand1 = np.array([[2.61955202, 0.30522265, 0.55515826], + [0.41124708, -0.03966978, -0.31854548], + [0.91910318, 1.39451809, -0.15295084], + [2.00452023, 0.50150048, 0.29485268], + [0.09453595, 0.67528885, 0.03283872], + [0.07015232, 2.18892599, -1.67266852], + [0.65029688, 1.60551637, 0.80013549], + [-0.6607528, 0.53644208, 0.17033891]]) + + rand3 = np.array([[0.0809969, 0.09731461, -0.173442], + [-1.84888465, -0.92589646, -1.29335743], + [0.67031855, -1.35957463, 0.41938621], + [0.73967209, -0.20230757, 0.52418027], + [0.17752796, 0.09065607, 0.29827466], + [0.47999368, -0.88455717, -0.57547934], + [-0.11486344, -0.12608506, -0.3395779], + [-0.86106154, -0.28687488, 0.9644429]]) + res1, res3, disp13 = procrustes(rand1, rand3) + res3_2, res1_2, disp31 = procrustes(rand3, rand1) + assert_almost_equal(disp13, disp31) + + def test_procrustes_shape_mismatch(self): + assert_raises(ValueError, procrustes, + np.array([[1, 2], [3, 4]]), + np.array([[5, 6, 7], [8, 9, 10]])) + + def test_procrustes_empty_rows_or_cols(self): + empty = np.array([[]]) + assert_raises(ValueError, procrustes, empty, empty) + + def test_procrustes_no_variation(self): + assert_raises(ValueError, procrustes, + np.array([[42, 42], [42, 42]]), + np.array([[45, 45], [45, 45]])) + + def test_procrustes_bad_number_of_dimensions(self): + # fewer dimensions in one dataset + assert_raises(ValueError, procrustes, + np.array([1, 1, 2, 3, 5, 8]), + np.array([[1, 2], [3, 4]])) + + # fewer dimensions in both datasets + assert_raises(ValueError, procrustes, + np.array([1, 1, 2, 3, 5, 8]), + np.array([1, 1, 2, 3, 5, 8])) + + # zero dimensions + assert_raises(ValueError, procrustes, np.array(7), np.array(11)) + + # extra dimensions + assert_raises(ValueError, procrustes, + np.array([[[11], [7]]]), + np.array([[[5, 13]]])) + diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/tests/test_distance.py b/venv/lib/python3.12/site-packages/scipy/spatial/tests/test_distance.py new file mode 100644 index 0000000000000000000000000000000000000000..6634d2c695d587cc9568a6184200dc077b0355e5 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/tests/test_distance.py @@ -0,0 +1,2297 @@ +# +# Author: Damian Eads +# Date: April 17, 2008 +# +# Copyright (C) 2008 Damian Eads +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# +# 3. The name of the author may not be used to endorse or promote +# products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS +# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +from functools import wraps, partial +import os.path +import sys +import sysconfig +import warnings +import weakref + +import numpy as np +from numpy.linalg import norm +from numpy.testing import (verbose, assert_, + assert_array_equal, assert_equal, + assert_almost_equal, assert_allclose) +import pytest + +import scipy.spatial.distance + +from scipy.spatial.distance import ( + squareform, pdist, cdist, num_obs_y, num_obs_dm, is_valid_dm, is_valid_y, + _validate_vector, _METRICS_NAMES) + +# these were missing: chebyshev cityblock +# jensenshannon and seuclidean are referenced by string name. +from scipy.spatial.distance import (braycurtis, canberra, chebyshev, cityblock, + correlation, cosine, dice, euclidean, + hamming, jaccard, jensenshannon, mahalanobis, + minkowski, rogerstanimoto, + russellrao, seuclidean, # noqa: F401 + sokalsneath, sqeuclidean, yule) +from scipy._lib._util import _apply_over_batch +from scipy.conftest import skip_xp_invalid_arg + + +@pytest.fixture(params=_METRICS_NAMES, scope="session") +def metric(request): + """ + Fixture for all metrics in scipy.spatial.distance + """ + return request.param + + +_filenames = [ + "cdist-X1.txt", + "cdist-X2.txt", + "iris.txt", + "pdist-boolean-inp.txt", + "pdist-chebyshev-ml-iris.txt", + "pdist-chebyshev-ml.txt", + "pdist-cityblock-ml-iris.txt", + "pdist-cityblock-ml.txt", + "pdist-correlation-ml-iris.txt", + "pdist-correlation-ml.txt", + "pdist-cosine-ml-iris.txt", + "pdist-cosine-ml.txt", + "pdist-double-inp.txt", + "pdist-euclidean-ml-iris.txt", + "pdist-euclidean-ml.txt", + "pdist-hamming-ml.txt", + "pdist-jaccard-ml.txt", + "pdist-jensenshannon-ml-iris.txt", + "pdist-jensenshannon-ml.txt", + "pdist-minkowski-3.2-ml-iris.txt", + "pdist-minkowski-3.2-ml.txt", + "pdist-minkowski-5.8-ml-iris.txt", + "pdist-seuclidean-ml-iris.txt", + "pdist-seuclidean-ml.txt", + "pdist-spearman-ml.txt", + "random-bool-data.txt", + "random-double-data.txt", + "random-int-data.txt", + "random-uint-data.txt", + ] + +_tdist = np.array([[0, 662, 877, 255, 412, 996], + [662, 0, 295, 468, 268, 400], + [877, 295, 0, 754, 564, 138], + [255, 468, 754, 0, 219, 869], + [412, 268, 564, 219, 0, 669], + [996, 400, 138, 869, 669, 0]], dtype='double') + +_ytdist = squareform(_tdist) + +# A hashmap of expected output arrays for the tests. These arrays +# come from a list of text files, which are read prior to testing. +# Each test loads inputs and outputs from this dictionary. +eo = {} + + +def load_testing_files(): + for fn in _filenames: + name = fn.replace(".txt", "").replace("-ml", "") + fqfn = os.path.join(os.path.dirname(__file__), 'data', fn) + fp = open(fqfn) + eo[name] = np.loadtxt(fp) + fp.close() + eo['pdist-boolean-inp'] = np.bool_(eo['pdist-boolean-inp']) + eo['random-bool-data'] = np.bool_(eo['random-bool-data']) + eo['random-float32-data'] = np.float32(eo['random-double-data']) + eo['random-int-data'] = np.long(eo['random-int-data']) + eo['random-uint-data'] = np.ulong(eo['random-uint-data']) + + +load_testing_files() + + +def _is_32bit(): + return np.intp(0).itemsize < 8 + + +def _chk_asarrays(arrays, axis=None): + arrays = [np.asanyarray(a) for a in arrays] + if axis is None: + # np < 1.10 ravel removes subclass from arrays + arrays = [np.ravel(a) if a.ndim != 1 else a + for a in arrays] + axis = 0 + arrays = tuple(np.atleast_1d(a) for a in arrays) + if axis < 0: + if not all(a.ndim == arrays[0].ndim for a in arrays): + raise ValueError("array ndim must be the same for neg axis") + axis = range(arrays[0].ndim)[axis] + return arrays + (axis,) + + +def _chk_weights(arrays, weights=None, axis=None, + force_weights=False, simplify_weights=True, + pos_only=False, neg_check=False, + nan_screen=False, mask_screen=False, + ddof=None): + chked = _chk_asarrays(arrays, axis=axis) + arrays, axis = chked[:-1], chked[-1] + + simplify_weights = simplify_weights and not force_weights + if not force_weights and mask_screen: + force_weights = any(np.ma.getmask(a) is not np.ma.nomask for a in arrays) + + if nan_screen: + has_nans = [np.isnan(np.sum(a)) for a in arrays] + if any(has_nans): + mask_screen = True + force_weights = True + arrays = tuple(np.ma.masked_invalid(a) if has_nan else a + for a, has_nan in zip(arrays, has_nans)) + + if weights is not None: + weights = np.asanyarray(weights) + elif force_weights: + weights = np.ones(arrays[0].shape[axis]) + else: + return arrays + (weights, axis) + + if ddof: + weights = _freq_weights(weights) + + if mask_screen: + weights = _weight_masked(arrays, weights, axis) + + if not all(weights.shape == (a.shape[axis],) for a in arrays): + raise ValueError("weights shape must match arrays along axis") + if neg_check and (weights < 0).any(): + raise ValueError("weights cannot be negative") + + if pos_only: + pos_weights = np.nonzero(weights > 0)[0] + if pos_weights.size < weights.size: + arrays = tuple(np.take(a, pos_weights, axis=axis) for a in arrays) + weights = weights[pos_weights] + if simplify_weights and (weights == 1).all(): + weights = None + return arrays + (weights, axis) + + +def _freq_weights(weights): + if weights is None: + return weights + int_weights = weights.astype(int) + if (weights != int_weights).any(): + raise ValueError(f"frequency (integer count-type) weights required {weights}") + return int_weights + + +def _weight_masked(arrays, weights, axis): + if axis is None: + axis = 0 + weights = np.asanyarray(weights) + for a in arrays: + axis_mask = np.ma.getmask(a) + if axis_mask is np.ma.nomask: + continue + if a.ndim > 1: + not_axes = tuple(i for i in range(a.ndim) if i != axis) + axis_mask = axis_mask.any(axis=not_axes) + weights *= 1 - axis_mask.astype(int) + return weights + + +def _rand_split(arrays, weights, axis, split_per, seed=None): + # Coerce `arrays` to float64 if integer, to avoid nan-to-integer issues + arrays = [arr.astype(np.float64) if np.issubdtype(arr.dtype, np.integer) + else arr for arr in arrays] + + # inverse operation for stats.collapse_weights + weights = np.array(weights, dtype=np.float64) # modified inplace; need a copy + seeded_rand = np.random.RandomState(seed) + + def mytake(a, ix, axis): + record = np.asanyarray(np.take(a, ix, axis=axis)) + return record.reshape([a.shape[i] if i != axis else 1 + for i in range(a.ndim)]) + + n_obs = arrays[0].shape[axis] + assert all(a.shape[axis] == n_obs for a in arrays), \ + "data must be aligned on sample axis" + for i in range(int(split_per) * n_obs): + split_ix = seeded_rand.randint(n_obs + i) + prev_w = weights[split_ix] + q = seeded_rand.rand() + weights[split_ix] = q * prev_w + weights = np.append(weights, (1. - q) * prev_w) + arrays = [np.append(a, mytake(a, split_ix, axis=axis), + axis=axis) for a in arrays] + return arrays, weights + + +assert_allclose_forgiving = partial(assert_allclose, atol=1e-5) + + +def _rough_check(a, b, compare_assert=assert_allclose_forgiving, + key=lambda x: x, w=None): + check_a = key(a) + check_b = key(b) + try: + if np.array(check_a != check_b).any(): # try strict equality for string types + compare_assert(check_a, check_b) + except AttributeError: # masked array + compare_assert(check_a, check_b) + except (TypeError, ValueError): # nested data structure + for a_i, b_i in zip(check_a, check_b): + _rough_check(a_i, b_i, compare_assert=compare_assert) + +# diff from test_stats: +# n_args=2, weight_arg='w', default_axis=None +# ma_safe = False, nan_safe = False +def _weight_checked(fn, n_args=2, default_axis=None, key=lambda x: x, weight_arg='w', + squeeze=True, silent=False, + ones_test=True, const_test=True, dup_test=True, + split_test=True, dud_test=True, ma_safe=False, ma_very_safe=False, + nan_safe=False, split_per=1.0, seed=0, + compare_assert=assert_allclose_forgiving): + """runs fn on its arguments 2 or 3 ways, checks that the results are the same, + then returns the same thing it would have returned before""" + @wraps(fn) + def wrapped(*args, **kwargs): + result = fn(*args, **kwargs) + + arrays = args[:n_args] + rest = args[n_args:] + weights = kwargs.get(weight_arg, None) + axis = kwargs.get('axis', default_axis) + + chked = _chk_weights(arrays, weights=weights, axis=axis, + force_weights=True, mask_screen=True) + arrays, weights, axis = chked[:-2], chked[-2], chked[-1] + if squeeze: + arrays = [np.atleast_1d(a.squeeze()) for a in arrays] + + try: + # WEIGHTS CHECK 1: EQUAL WEIGHTED OBSERVATIONS + args = tuple(arrays) + rest + if ones_test: + kwargs[weight_arg] = weights + _rough_check(result, fn(*args, **kwargs), key=key) + if const_test: + kwargs[weight_arg] = weights * 101.0 + _rough_check(result, fn(*args, **kwargs), key=key) + kwargs[weight_arg] = weights * 0.101 + try: + _rough_check(result, fn(*args, **kwargs), key=key) + except Exception as e: + raise type(e)((e, arrays, weights)) from e + + # WEIGHTS CHECK 2: ADDL 0-WEIGHTED OBS + if dud_test: + # add randomly resampled rows, weighted at 0 + dud_arrays, dud_weights = _rand_split(arrays, weights, axis, + split_per=split_per, seed=seed) + dud_weights[:weights.size] = weights # not exactly 1 because of masked arrays # noqa: E501 + dud_weights[weights.size:] = 0 + dud_args = tuple(dud_arrays) + rest + kwargs[weight_arg] = dud_weights + _rough_check(result, fn(*dud_args, **kwargs), key=key) + # increase the value of those 0-weighted rows + for a in dud_arrays: + indexer = [slice(None)] * a.ndim + indexer[axis] = slice(weights.size, None) + indexer = tuple(indexer) + a[indexer] = a[indexer] * 101 + dud_args = tuple(dud_arrays) + rest + _rough_check(result, fn(*dud_args, **kwargs), key=key) + # set those 0-weighted rows to NaNs + for a in dud_arrays: + indexer = [slice(None)] * a.ndim + indexer[axis] = slice(weights.size, None) + indexer = tuple(indexer) + a[indexer] = a[indexer] * np.nan + if kwargs.get("nan_policy", None) == "omit" and nan_safe: + dud_args = tuple(dud_arrays) + rest + _rough_check(result, fn(*dud_args, **kwargs), key=key) + # mask out those nan values + if ma_safe: + dud_arrays = [np.ma.masked_invalid(a) for a in dud_arrays] + dud_args = tuple(dud_arrays) + rest + _rough_check(result, fn(*dud_args, **kwargs), key=key) + if ma_very_safe: + kwargs[weight_arg] = None + _rough_check(result, fn(*dud_args, **kwargs), key=key) + del dud_arrays, dud_args, dud_weights + + # WEIGHTS CHECK 3: DUPLICATE DATA (DUMB SPLITTING) + if dup_test: + dup_arrays = [np.append(a, a, axis=axis) for a in arrays] + dup_weights = np.append(weights, weights) / 2.0 + dup_args = tuple(dup_arrays) + rest + kwargs[weight_arg] = dup_weights + _rough_check(result, fn(*dup_args, **kwargs), key=key) + del dup_args, dup_arrays, dup_weights + + # WEIGHT CHECK 3: RANDOM SPLITTING + if split_test and split_per > 0: + split = _rand_split(arrays, weights, axis, + split_per=split_per, seed=seed) + split_arrays, split_weights = split + split_args = tuple(split_arrays) + rest + kwargs[weight_arg] = split_weights + _rough_check(result, fn(*split_args, **kwargs), key=key) + except NotImplementedError as e: + # when some combination of arguments makes weighting impossible, + # this is the desired response + if not silent: + warnings.warn(f"{fn.__name__} NotImplemented weights: {e}", + stacklevel=3) + return result + return wrapped + + +wcdist = _weight_checked(cdist, default_axis=1, squeeze=False) +wcdist_no_const = _weight_checked(cdist, default_axis=1, + squeeze=False, const_test=False) +wpdist = _weight_checked(pdist, default_axis=1, squeeze=False, n_args=1) +wpdist_no_const = _weight_checked(pdist, default_axis=1, squeeze=False, + const_test=False, n_args=1) +wrogerstanimoto = _weight_checked(rogerstanimoto) +wmatching = whamming = _weight_checked(hamming, dud_test=False) +wyule = _weight_checked(yule) +wdice = _weight_checked(dice) +wcityblock = _weight_checked(cityblock) +wchebyshev = _weight_checked(chebyshev) +wcosine = _weight_checked(cosine) +wcorrelation = _weight_checked(correlation) +wjaccard = _weight_checked(jaccard) +weuclidean = _weight_checked(euclidean, const_test=False) +wsqeuclidean = _weight_checked(sqeuclidean, const_test=False) +wbraycurtis = _weight_checked(braycurtis) +wcanberra = _weight_checked(canberra, const_test=False) +wsokalsneath = _weight_checked(sokalsneath) +wrussellrao = _weight_checked(russellrao) + + +class TestCdist: + + def setup_method(self): + self.rnd_eo_names = ['random-float32-data', 'random-int-data', + 'random-uint-data', 'random-double-data', + 'random-bool-data'] + self.valid_upcasts = {'bool': [np.ulong, np.long, np.float32, np.float64], + 'uint': [np.long, np.float32, np.float64], + 'int': [np.float32, np.float64], + 'float32': [np.float64]} + + def test_cdist_extra_args(self, metric): + # Tests that args and kwargs are correctly handled + + X1 = [[1., 2., 3.], [1.2, 2.3, 3.4], [2.2, 2.3, 4.4]] + X2 = [[7., 5., 8.], [7.5, 5.8, 8.4], [5.5, 5.8, 4.4]] + kwargs = {"N0tV4l1D_p4raM": 3.14, "w": np.arange(3)} + args = [3.14] * 200 + + with pytest.raises(TypeError): + cdist(X1, X2, metric=metric, **kwargs) + with pytest.raises(TypeError): + cdist(X1, X2, metric=eval(metric), **kwargs) + with pytest.raises(TypeError): + cdist(X1, X2, metric="test_" + metric, **kwargs) + with pytest.raises(TypeError): + cdist(X1, X2, metric=metric, *args) + with pytest.raises(TypeError): + cdist(X1, X2, metric=eval(metric), *args) + with pytest.raises(TypeError): + cdist(X1, X2, metric="test_" + metric, *args) + + def test_cdist_extra_args_custom(self): + # Tests that args and kwargs are correctly handled + # also for custom metric + def _my_metric(x, y, arg, kwarg=1, kwarg2=2): + return arg + kwarg + kwarg2 + + X1 = [[1., 2., 3.], [1.2, 2.3, 3.4], [2.2, 2.3, 4.4]] + X2 = [[7., 5., 8.], [7.5, 5.8, 8.4], [5.5, 5.8, 4.4]] + kwargs = {"N0tV4l1D_p4raM": 3.14, "w": np.arange(3)} + args = [3.14] * 200 + + with pytest.raises(TypeError): + cdist(X1, X2, _my_metric) + with pytest.raises(TypeError): + cdist(X1, X2, _my_metric, *args) + with pytest.raises(TypeError): + cdist(X1, X2, _my_metric, **kwargs) + with pytest.raises(TypeError): + cdist(X1, X2, _my_metric, kwarg=2.2, kwarg2=3.3) + with pytest.raises(TypeError): + cdist(X1, X2, _my_metric, 1, 2, kwarg=2.2) + with pytest.raises(TypeError): + cdist(X1, X2, _my_metric, 1, 2, kwarg=2.2) + with pytest.raises(TypeError): + cdist(X1, X2, _my_metric, 1.1, 2.2, 3.3) + with pytest.raises(TypeError): + cdist(X1, X2, _my_metric, 1.1, 2.2) + with pytest.raises(TypeError): + cdist(X1, X2, _my_metric, 1.1) + with pytest.raises(TypeError): + cdist(X1, X2, _my_metric, 1.1, kwarg=2.2, kwarg2=3.3) + + # this should work + assert_allclose(cdist(X1, X2, metric=_my_metric, + arg=1.1, kwarg2=3.3), 5.4) + + def test_cdist_euclidean_random_unicode(self): + eps = 1e-15 + X1 = eo['cdist-X1'] + X2 = eo['cdist-X2'] + Y1 = wcdist_no_const(X1, X2, 'euclidean') + Y2 = wcdist_no_const(X1, X2, 'test_euclidean') + assert_allclose(Y1, Y2, rtol=eps, verbose=verbose > 2) + + @pytest.mark.parametrize("p", [0.1, 0.25, 1.0, 1.23, + 2.0, 3.8, 4.6, np.inf]) + def test_cdist_minkowski_random(self, p): + eps = 1e-13 + X1 = eo['cdist-X1'] + X2 = eo['cdist-X2'] + Y1 = wcdist_no_const(X1, X2, 'minkowski', p=p) + Y2 = wcdist_no_const(X1, X2, 'test_minkowski', p=p) + assert_allclose(Y1, Y2, atol=0, rtol=eps, verbose=verbose > 2) + + def test_cdist_cosine_random(self): + eps = 1e-14 + X1 = eo['cdist-X1'] + X2 = eo['cdist-X2'] + Y1 = wcdist(X1, X2, 'cosine') + + # Naive implementation + def norms(X): + return np.linalg.norm(X, axis=1).reshape(-1, 1) + + Y2 = 1 - np.dot((X1 / norms(X1)), (X2 / norms(X2)).T) + + assert_allclose(Y1, Y2, rtol=eps, verbose=verbose > 2) + + def test_cdist_mahalanobis(self): + # 1-dimensional observations + x1 = np.array([[2], [3]]) + x2 = np.array([[2], [5]]) + dist = cdist(x1, x2, metric='mahalanobis') + assert_allclose(dist, [[0.0, np.sqrt(4.5)], [np.sqrt(0.5), np.sqrt(2)]]) + + # 2-dimensional observations + x1 = np.array([[0, 0], [-1, 0]]) + x2 = np.array([[0, 2], [1, 0], [0, -2]]) + dist = cdist(x1, x2, metric='mahalanobis') + rt2 = np.sqrt(2) + assert_allclose(dist, [[rt2, rt2, rt2], [2, 2 * rt2, 2]]) + + # Too few observations + with pytest.raises(ValueError): + cdist([[0, 1]], [[2, 3]], metric='mahalanobis') + + def test_cdist_custom_notdouble(self): + class myclass: + pass + + def _my_metric(x, y): + if not isinstance(x[0], myclass) or not isinstance(y[0], myclass): + raise ValueError("Type has been changed") + return 1.123 + data = np.array([[myclass()]], dtype=object) + cdist_y = cdist(data, data, metric=_my_metric) + right_y = 1.123 + assert_equal(cdist_y, right_y, verbose=verbose > 2) + + def _check_calling_conventions(self, X1, X2, metric, eps=1e-07, **kwargs): + # helper function for test_cdist_calling_conventions + try: + y1 = cdist(X1, X2, metric=metric, **kwargs) + y2 = cdist(X1, X2, metric=eval(metric), **kwargs) + y3 = cdist(X1, X2, metric="test_" + metric, **kwargs) + except Exception as e: + e_cls = e.__class__ + if verbose > 2: + print(e_cls.__name__) + print(e) + with pytest.raises(e_cls): + cdist(X1, X2, metric=metric, **kwargs) + with pytest.raises(e_cls): + cdist(X1, X2, metric=eval(metric), **kwargs) + with pytest.raises(e_cls): + cdist(X1, X2, metric="test_" + metric, **kwargs) + else: + assert_allclose(y1, y2, rtol=eps, verbose=verbose > 2) + assert_allclose(y1, y3, rtol=eps, verbose=verbose > 2) + + def test_cdist_calling_conventions(self, metric): + # Ensures that specifying the metric with a str or scipy function + # gives the same behaviour (i.e. same result or same exception). + # NOTE: The correctness should be checked within each metric tests. + for eo_name in self.rnd_eo_names: + # subsampling input data to speed-up tests + # NOTE: num samples needs to be > than dimensions for mahalanobis + X1 = eo[eo_name][::5, ::-2] + X2 = eo[eo_name][1::5, ::2] + if verbose > 2: + print("testing: ", metric, " with: ", eo_name) + if (metric in {'dice', 'yule', 'rogerstanimoto', 'russellrao', + 'sokalsneath'} + and 'bool' not in eo_name): + # python version permits non-bools e.g. for fuzzy logic + continue + self._check_calling_conventions(X1, X2, metric) + + # Testing built-in metrics with extra args + if metric == "seuclidean": + X12 = np.vstack([X1, X2]).astype(np.float64) + V = np.var(X12, axis=0, ddof=1) + self._check_calling_conventions(X1, X2, metric, V=V) + elif metric == "mahalanobis": + X12 = np.vstack([X1, X2]).astype(np.float64) + V = np.atleast_2d(np.cov(X12.T)) + VI = np.array(np.linalg.inv(V).T) + self._check_calling_conventions(X1, X2, metric, VI=VI) + + def test_cdist_dtype_equivalence(self, metric): + # Tests that the result is not affected by type up-casting + eps = 1e-07 + tests = [(eo['random-bool-data'], self.valid_upcasts['bool']), + (eo['random-uint-data'], self.valid_upcasts['uint']), + (eo['random-int-data'], self.valid_upcasts['int']), + (eo['random-float32-data'], self.valid_upcasts['float32'])] + for test in tests: + X1 = test[0][::5, ::-2] + X2 = test[0][1::5, ::2] + try: + y1 = cdist(X1, X2, metric=metric) + except Exception as e: + e_cls = e.__class__ + if verbose > 2: + print(e_cls.__name__) + print(e) + for new_type in test[1]: + X1new = new_type(X1) + X2new = new_type(X2) + with pytest.raises(e_cls): + cdist(X1new, X2new, metric=metric) + else: + for new_type in test[1]: + y2 = cdist(new_type(X1), new_type(X2), metric=metric) + assert_allclose(y1, y2, rtol=eps, verbose=verbose > 2) + + @pytest.mark.skipif(sysconfig.get_platform() == 'win-arm64', reason="numpy#29442") + def test_cdist_out(self, metric): + # Test that out parameter works properly + eps = 1e-15 + X1 = eo['cdist-X1'] + X2 = eo['cdist-X2'] + out_r, out_c = X1.shape[0], X2.shape[0] + + kwargs = dict() + if metric == 'minkowski': + kwargs['p'] = 1.23 + out1 = np.empty((out_r, out_c), dtype=np.float64) + Y1 = cdist(X1, X2, metric, **kwargs) + Y2 = cdist(X1, X2, metric, out=out1, **kwargs) + + # test that output is numerically equivalent + assert_allclose(Y1, Y2, rtol=eps, verbose=verbose > 2) + + # test that Y_test1 and out1 are the same object + assert_(Y2 is out1) + + # test for incorrect shape + out2 = np.empty((out_r-1, out_c+1), dtype=np.float64) + with pytest.raises(ValueError): + cdist(X1, X2, metric, out=out2, **kwargs) + + # test for C-contiguous order + out3 = np.empty( + (2 * out_r, 2 * out_c), dtype=np.float64)[::2, ::2] + out4 = np.empty((out_r, out_c), dtype=np.float64, order='F') + with pytest.raises(ValueError): + cdist(X1, X2, metric, out=out3, **kwargs) + with pytest.raises(ValueError): + cdist(X1, X2, metric, out=out4, **kwargs) + + # test for incorrect dtype + out5 = np.empty((out_r, out_c), dtype=np.int64) + with pytest.raises(ValueError): + cdist(X1, X2, metric, out=out5, **kwargs) + + def test_striding(self, metric): + # test that striding is handled correct with calls to + # _copy_array_if_base_present + eps = 1e-15 + X1 = eo['cdist-X1'][::2, ::2] + X2 = eo['cdist-X2'][::2, ::2] + X1_copy = X1.copy() + X2_copy = X2.copy() + + # confirm equivalence + assert_equal(X1, X1_copy) + assert_equal(X2, X2_copy) + # confirm contiguity + assert_(not X1.flags.c_contiguous) + assert_(not X2.flags.c_contiguous) + assert_(X1_copy.flags.c_contiguous) + assert_(X2_copy.flags.c_contiguous) + + kwargs = dict() + if metric == 'minkowski': + kwargs['p'] = 1.23 + Y1 = cdist(X1, X2, metric, **kwargs) + Y2 = cdist(X1_copy, X2_copy, metric, **kwargs) + # test that output is numerically equivalent + assert_allclose(Y1, Y2, rtol=eps, verbose=verbose > 2) + + def test_cdist_refcount(self, metric): + x1 = np.random.rand(10, 10) + x2 = np.random.rand(10, 10) + + kwargs = dict() + if metric == 'minkowski': + kwargs['p'] = 1.23 + + out = cdist(x1, x2, metric=metric, **kwargs) + + # Check reference counts aren't messed up. If we only hold weak + # references, the arrays should be deallocated. + weak_refs = [weakref.ref(v) for v in (x1, x2, out)] + del x1, x2, out + + assert all(weak_ref() is None for weak_ref in weak_refs) + + +class TestPdist: + + def setup_method(self): + self.rnd_eo_names = ['random-float32-data', 'random-int-data', + 'random-uint-data', 'random-double-data', + 'random-bool-data'] + self.valid_upcasts = {'bool': [np.ulong, np.long, np.float32, np.float64], + 'uint': [np.long, np.float32, np.float64], + 'int': [np.float32, np.float64], + 'float32': [np.float64]} + + def test_pdist_extra_args(self, metric): + # Tests that args and kwargs are correctly handled + X1 = [[1., 2.], [1.2, 2.3], [2.2, 2.3]] + kwargs = {"N0tV4l1D_p4raM": 3.14, "w": np.arange(2)} + args = [3.14] * 200 + + with pytest.raises(TypeError): + pdist(X1, metric=metric, **kwargs) + with pytest.raises(TypeError): + pdist(X1, metric=eval(metric), **kwargs) + with pytest.raises(TypeError): + pdist(X1, metric="test_" + metric, **kwargs) + with pytest.raises(TypeError): + pdist(X1, metric=metric, *args) + with pytest.raises(TypeError): + pdist(X1, metric=eval(metric), *args) + with pytest.raises(TypeError): + pdist(X1, metric="test_" + metric, *args) + + def test_pdist_extra_args_custom(self): + # Tests that args and kwargs are correctly handled + # also for custom metric + def _my_metric(x, y, arg, kwarg=1, kwarg2=2): + return arg + kwarg + kwarg2 + + X1 = [[1., 2.], [1.2, 2.3], [2.2, 2.3]] + kwargs = {"N0tV4l1D_p4raM": 3.14, "w": np.arange(2)} + args = [3.14] * 200 + + with pytest.raises(TypeError): + pdist(X1, _my_metric) + with pytest.raises(TypeError): + pdist(X1, _my_metric, *args) + with pytest.raises(TypeError): + pdist(X1, _my_metric, **kwargs) + with pytest.raises(TypeError): + pdist(X1, _my_metric, kwarg=2.2, kwarg2=3.3) + with pytest.raises(TypeError): + pdist(X1, _my_metric, 1, 2, kwarg=2.2) + with pytest.raises(TypeError): + pdist(X1, _my_metric, 1, 2, kwarg=2.2) + with pytest.raises(TypeError): + pdist(X1, _my_metric, 1.1, 2.2, 3.3) + with pytest.raises(TypeError): + pdist(X1, _my_metric, 1.1, 2.2) + with pytest.raises(TypeError): + pdist(X1, _my_metric, 1.1) + with pytest.raises(TypeError): + pdist(X1, _my_metric, 1.1, kwarg=2.2, kwarg2=3.3) + + # these should work + assert_allclose(pdist(X1, metric=_my_metric, + arg=1.1, kwarg2=3.3), 5.4) + + def test_pdist_euclidean_random(self): + eps = 1e-07 + X = eo['pdist-double-inp'] + Y_right = eo['pdist-euclidean'] + Y_test1 = wpdist_no_const(X, 'euclidean') + assert_allclose(Y_test1, Y_right, rtol=eps) + + def test_pdist_euclidean_random_u(self): + eps = 1e-07 + X = eo['pdist-double-inp'] + Y_right = eo['pdist-euclidean'] + Y_test1 = wpdist_no_const(X, 'euclidean') + assert_allclose(Y_test1, Y_right, rtol=eps) + + def test_pdist_euclidean_random_float32(self): + eps = 1e-07 + X = np.float32(eo['pdist-double-inp']) + Y_right = eo['pdist-euclidean'] + Y_test1 = wpdist_no_const(X, 'euclidean') + assert_allclose(Y_test1, Y_right, rtol=eps) + + def test_pdist_euclidean_random_nonC(self): + eps = 1e-07 + X = eo['pdist-double-inp'] + Y_right = eo['pdist-euclidean'] + Y_test2 = wpdist_no_const(X, 'test_euclidean') + assert_allclose(Y_test2, Y_right, rtol=eps) + + @pytest.mark.slow + def test_pdist_euclidean_iris_double(self): + eps = 1e-7 + X = eo['iris'] + Y_right = eo['pdist-euclidean-iris'] + Y_test1 = wpdist_no_const(X, 'euclidean') + assert_allclose(Y_test1, Y_right, rtol=eps) + + @pytest.mark.slow + def test_pdist_euclidean_iris_float32(self): + eps = 1e-5 + X = np.float32(eo['iris']) + Y_right = eo['pdist-euclidean-iris'] + Y_test1 = wpdist_no_const(X, 'euclidean') + assert_allclose(Y_test1, Y_right, rtol=eps, verbose=verbose > 2) + + @pytest.mark.slow + def test_pdist_euclidean_iris_nonC(self): + # Test pdist(X, 'test_euclidean') [the non-C implementation] on the + # Iris data set. + eps = 1e-7 + X = eo['iris'] + Y_right = eo['pdist-euclidean-iris'] + Y_test2 = wpdist_no_const(X, 'test_euclidean') + assert_allclose(Y_test2, Y_right, rtol=eps) + + def test_pdist_seuclidean_random(self): + eps = 1e-7 + X = eo['pdist-double-inp'] + Y_right = eo['pdist-seuclidean'] + Y_test1 = pdist(X, 'seuclidean') + assert_allclose(Y_test1, Y_right, rtol=eps) + + def test_pdist_seuclidean_random_float32(self): + eps = 1e-7 + X = np.float32(eo['pdist-double-inp']) + Y_right = eo['pdist-seuclidean'] + Y_test1 = pdist(X, 'seuclidean') + assert_allclose(Y_test1, Y_right, rtol=eps) + + # Check no error is raise when V has float32 dtype (#11171). + V = np.var(X, axis=0, ddof=1) + Y_test2 = pdist(X, 'seuclidean', V=V) + assert_allclose(Y_test2, Y_right, rtol=eps) + + def test_pdist_seuclidean_random_nonC(self): + # Test pdist(X, 'test_sqeuclidean') [the non-C implementation] + eps = 1e-07 + X = eo['pdist-double-inp'] + Y_right = eo['pdist-seuclidean'] + Y_test2 = pdist(X, 'test_seuclidean') + assert_allclose(Y_test2, Y_right, rtol=eps) + + def test_pdist_seuclidean_iris(self): + eps = 1e-7 + X = eo['iris'] + Y_right = eo['pdist-seuclidean-iris'] + Y_test1 = pdist(X, 'seuclidean') + assert_allclose(Y_test1, Y_right, rtol=eps) + + def test_pdist_seuclidean_iris_float32(self): + # Tests pdist(X, 'seuclidean') on the Iris data set (float32). + eps = 1e-5 + X = np.float32(eo['iris']) + Y_right = eo['pdist-seuclidean-iris'] + Y_test1 = pdist(X, 'seuclidean') + assert_allclose(Y_test1, Y_right, rtol=eps) + + def test_pdist_seuclidean_iris_nonC(self): + # Test pdist(X, 'test_seuclidean') [the non-C implementation] on the + # Iris data set. + eps = 1e-7 + X = eo['iris'] + Y_right = eo['pdist-seuclidean-iris'] + Y_test2 = pdist(X, 'test_seuclidean') + assert_allclose(Y_test2, Y_right, rtol=eps) + + def test_pdist_cosine_random(self): + eps = 1e-7 + X = eo['pdist-double-inp'] + Y_right = eo['pdist-cosine'] + Y_test1 = wpdist(X, 'cosine') + assert_allclose(Y_test1, Y_right, rtol=eps) + + def test_pdist_cosine_random_float32(self): + eps = 1e-7 + X = np.float32(eo['pdist-double-inp']) + Y_right = eo['pdist-cosine'] + Y_test1 = wpdist(X, 'cosine') + assert_allclose(Y_test1, Y_right, rtol=eps) + + def test_pdist_cosine_random_nonC(self): + # Test pdist(X, 'test_cosine') [the non-C implementation] + eps = 1e-7 + X = eo['pdist-double-inp'] + Y_right = eo['pdist-cosine'] + Y_test2 = wpdist(X, 'test_cosine') + assert_allclose(Y_test2, Y_right, rtol=eps) + + @pytest.mark.slow + def test_pdist_cosine_iris(self): + eps = 1e-05 + X = eo['iris'] + Y_right = eo['pdist-cosine-iris'] + Y_test1 = wpdist(X, 'cosine') + assert_allclose(Y_test1, Y_right, atol=eps) + + @pytest.mark.slow + def test_pdist_cosine_iris_float32(self): + eps = 1e-05 + X = np.float32(eo['iris']) + Y_right = eo['pdist-cosine-iris'] + Y_test1 = wpdist(X, 'cosine') + assert_allclose(Y_test1, Y_right, atol=eps, verbose=verbose > 2) + + @pytest.mark.slow + def test_pdist_cosine_iris_nonC(self): + eps = 1e-05 + X = eo['iris'] + Y_right = eo['pdist-cosine-iris'] + Y_test2 = wpdist(X, 'test_cosine') + assert_allclose(Y_test2, Y_right, atol=eps) + + def test_pdist_cosine_bounds(self): + # Test adapted from @joernhees's example at gh-5208: case where + # cosine distance used to be negative. XXX: very sensitive to the + # specific norm computation. + x = np.abs(np.random.RandomState(1337).rand(91)) + X = np.vstack([x, x]) + assert_(wpdist(X, 'cosine')[0] >= 0, + msg='cosine distance should be non-negative') + + def test_pdist_cityblock_random(self): + eps = 1e-7 + X = eo['pdist-double-inp'] + Y_right = eo['pdist-cityblock'] + Y_test1 = wpdist_no_const(X, 'cityblock') + assert_allclose(Y_test1, Y_right, rtol=eps) + + def test_pdist_cityblock_random_float32(self): + eps = 1e-7 + X = np.float32(eo['pdist-double-inp']) + Y_right = eo['pdist-cityblock'] + Y_test1 = wpdist_no_const(X, 'cityblock') + assert_allclose(Y_test1, Y_right, rtol=eps) + + def test_pdist_cityblock_random_nonC(self): + eps = 1e-7 + X = eo['pdist-double-inp'] + Y_right = eo['pdist-cityblock'] + Y_test2 = wpdist_no_const(X, 'test_cityblock') + assert_allclose(Y_test2, Y_right, rtol=eps) + + @pytest.mark.slow + def test_pdist_cityblock_iris(self): + eps = 1e-14 + X = eo['iris'] + Y_right = eo['pdist-cityblock-iris'] + Y_test1 = wpdist_no_const(X, 'cityblock') + assert_allclose(Y_test1, Y_right, rtol=eps) + + @pytest.mark.slow + def test_pdist_cityblock_iris_float32(self): + eps = 1e-5 + X = np.float32(eo['iris']) + Y_right = eo['pdist-cityblock-iris'] + Y_test1 = wpdist_no_const(X, 'cityblock') + assert_allclose(Y_test1, Y_right, rtol=eps, verbose=verbose > 2) + + @pytest.mark.slow + def test_pdist_cityblock_iris_nonC(self): + # Test pdist(X, 'test_cityblock') [the non-C implementation] on the + # Iris data set. + eps = 1e-14 + X = eo['iris'] + Y_right = eo['pdist-cityblock-iris'] + Y_test2 = wpdist_no_const(X, 'test_cityblock') + assert_allclose(Y_test2, Y_right, rtol=eps) + + def test_pdist_correlation_random(self): + eps = 1e-7 + X = eo['pdist-double-inp'] + Y_right = eo['pdist-correlation'] + Y_test1 = wpdist(X, 'correlation') + assert_allclose(Y_test1, Y_right, rtol=eps) + + def test_pdist_correlation_random_float32(self): + eps = 1e-7 + X = np.float32(eo['pdist-double-inp']) + Y_right = eo['pdist-correlation'] + Y_test1 = wpdist(X, 'correlation') + assert_allclose(Y_test1, Y_right, rtol=eps) + + def test_pdist_correlation_random_nonC(self): + eps = 1e-7 + X = eo['pdist-double-inp'] + Y_right = eo['pdist-correlation'] + Y_test2 = wpdist(X, 'test_correlation') + assert_allclose(Y_test2, Y_right, rtol=eps) + + @pytest.mark.slow + def test_pdist_correlation_iris(self): + eps = 1e-7 + X = eo['iris'] + Y_right = eo['pdist-correlation-iris'] + Y_test1 = wpdist(X, 'correlation') + assert_allclose(Y_test1, Y_right, rtol=eps) + + @pytest.mark.slow + def test_pdist_correlation_iris_float32(self): + eps = 1e-7 + X = eo['iris'] + Y_right = np.float32(eo['pdist-correlation-iris']) + Y_test1 = wpdist(X, 'correlation') + assert_allclose(Y_test1, Y_right, rtol=eps, verbose=verbose > 2) + + @pytest.mark.slow + def test_pdist_correlation_iris_nonC(self): + if sys.maxsize > 2**32: + eps = 1e-7 + else: + pytest.skip("see gh-16456") + X = eo['iris'] + Y_right = eo['pdist-correlation-iris'] + Y_test2 = wpdist(X, 'test_correlation') + assert_allclose(Y_test2, Y_right, rtol=eps) + + @pytest.mark.parametrize("p", [0.1, 0.25, 1.0, 2.0, 3.2, np.inf]) + def test_pdist_minkowski_random_p(self, p): + eps = 1e-13 + X = eo['pdist-double-inp'] + Y1 = wpdist_no_const(X, 'minkowski', p=p) + Y2 = wpdist_no_const(X, 'test_minkowski', p=p) + assert_allclose(Y1, Y2, atol=0, rtol=eps) + + def test_pdist_minkowski_random(self): + eps = 1e-7 + X = eo['pdist-double-inp'] + Y_right = eo['pdist-minkowski-3.2'] + Y_test1 = wpdist_no_const(X, 'minkowski', p=3.2) + assert_allclose(Y_test1, Y_right, rtol=eps) + + def test_pdist_minkowski_random_float32(self): + eps = 1e-7 + X = np.float32(eo['pdist-double-inp']) + Y_right = eo['pdist-minkowski-3.2'] + Y_test1 = wpdist_no_const(X, 'minkowski', p=3.2) + assert_allclose(Y_test1, Y_right, rtol=eps) + + def test_pdist_minkowski_random_nonC(self): + eps = 1e-7 + X = eo['pdist-double-inp'] + Y_right = eo['pdist-minkowski-3.2'] + Y_test2 = wpdist_no_const(X, 'test_minkowski', p=3.2) + assert_allclose(Y_test2, Y_right, rtol=eps) + + @pytest.mark.slow + def test_pdist_minkowski_3_2_iris(self): + eps = 1e-7 + X = eo['iris'] + Y_right = eo['pdist-minkowski-3.2-iris'] + Y_test1 = wpdist_no_const(X, 'minkowski', p=3.2) + assert_allclose(Y_test1, Y_right, rtol=eps) + + @pytest.mark.slow + def test_pdist_minkowski_3_2_iris_float32(self): + eps = 1e-5 + X = np.float32(eo['iris']) + Y_right = eo['pdist-minkowski-3.2-iris'] + Y_test1 = wpdist_no_const(X, 'minkowski', p=3.2) + assert_allclose(Y_test1, Y_right, rtol=eps) + + @pytest.mark.slow + def test_pdist_minkowski_3_2_iris_nonC(self): + eps = 1e-7 + X = eo['iris'] + Y_right = eo['pdist-minkowski-3.2-iris'] + Y_test2 = wpdist_no_const(X, 'test_minkowski', p=3.2) + assert_allclose(Y_test2, Y_right, rtol=eps) + + @pytest.mark.slow + def test_pdist_minkowski_5_8_iris(self): + eps = 1e-7 + X = eo['iris'] + Y_right = eo['pdist-minkowski-5.8-iris'] + Y_test1 = wpdist_no_const(X, 'minkowski', p=5.8) + assert_allclose(Y_test1, Y_right, rtol=eps) + + @pytest.mark.slow + def test_pdist_minkowski_5_8_iris_float32(self): + eps = 1e-5 + X = np.float32(eo['iris']) + Y_right = eo['pdist-minkowski-5.8-iris'] + Y_test1 = wpdist_no_const(X, 'minkowski', p=5.8) + assert_allclose(Y_test1, Y_right, rtol=eps, verbose=verbose > 2) + + @pytest.mark.slow + def test_pdist_minkowski_5_8_iris_nonC(self): + eps = 1e-7 + X = eo['iris'] + Y_right = eo['pdist-minkowski-5.8-iris'] + Y_test2 = wpdist_no_const(X, 'test_minkowski', p=5.8) + assert_allclose(Y_test2, Y_right, rtol=eps) + + def test_pdist_mahalanobis(self): + # 1-dimensional observations + x = np.array([2.0, 2.0, 3.0, 5.0]).reshape(-1, 1) + dist = pdist(x, metric='mahalanobis') + assert_allclose(dist, [0.0, np.sqrt(0.5), np.sqrt(4.5), + np.sqrt(0.5), np.sqrt(4.5), np.sqrt(2.0)]) + + # 2-dimensional observations + x = np.array([[0, 0], [-1, 0], [0, 2], [1, 0], [0, -2]]) + dist = pdist(x, metric='mahalanobis') + rt2 = np.sqrt(2) + assert_allclose(dist, [rt2, rt2, rt2, rt2, 2, 2 * rt2, 2, 2, 2 * rt2, 2]) + + # Too few observations + with pytest.raises(ValueError): + wpdist([[0, 1], [2, 3]], metric='mahalanobis') + + def test_pdist_hamming_random(self): + eps = 1e-15 + X = eo['pdist-boolean-inp'] + Y_right = eo['pdist-hamming'] + Y_test1 = wpdist(X, 'hamming') + assert_allclose(Y_test1, Y_right, rtol=eps) + + def test_pdist_hamming_random_float32(self): + eps = 1e-15 + X = np.float32(eo['pdist-boolean-inp']) + Y_right = eo['pdist-hamming'] + Y_test1 = wpdist(X, 'hamming') + assert_allclose(Y_test1, Y_right, rtol=eps) + + def test_pdist_hamming_random_nonC(self): + eps = 1e-15 + X = eo['pdist-boolean-inp'] + Y_right = eo['pdist-hamming'] + Y_test2 = wpdist(X, 'test_hamming') + assert_allclose(Y_test2, Y_right, rtol=eps) + + def test_pdist_dhamming_random(self): + eps = 1e-15 + X = np.float64(eo['pdist-boolean-inp']) + Y_right = eo['pdist-hamming'] + Y_test1 = wpdist(X, 'hamming') + assert_allclose(Y_test1, Y_right, rtol=eps) + + def test_pdist_dhamming_random_float32(self): + eps = 1e-15 + X = np.float32(eo['pdist-boolean-inp']) + Y_right = eo['pdist-hamming'] + Y_test1 = wpdist(X, 'hamming') + assert_allclose(Y_test1, Y_right, rtol=eps) + + def test_pdist_dhamming_random_nonC(self): + eps = 1e-15 + X = np.float64(eo['pdist-boolean-inp']) + Y_right = eo['pdist-hamming'] + Y_test2 = wpdist(X, 'test_hamming') + assert_allclose(Y_test2, Y_right, rtol=eps) + + def test_pdist_jensenshannon_random(self): + eps = 1e-11 + X = eo['pdist-double-inp'] + Y_right = eo['pdist-jensenshannon'] + Y_test1 = pdist(X, 'jensenshannon') + assert_allclose(Y_test1, Y_right, rtol=eps) + + def test_pdist_jensenshannon_random_float32(self): + eps = 1e-8 + X = np.float32(eo['pdist-double-inp']) + Y_right = eo['pdist-jensenshannon'] + Y_test1 = pdist(X, 'jensenshannon') + assert_allclose(Y_test1, Y_right, rtol=eps, verbose=verbose > 2) + + def test_pdist_jensenshannon_random_nonC(self): + eps = 1e-11 + X = eo['pdist-double-inp'] + Y_right = eo['pdist-jensenshannon'] + Y_test2 = pdist(X, 'test_jensenshannon') + assert_allclose(Y_test2, Y_right, rtol=eps) + + def test_pdist_jensenshannon_iris(self): + if _is_32bit(): + # Test failing on 32-bit Linux on Azure otherwise, see gh-12810 + eps = 2.5e-10 + else: + eps = 1e-12 + + X = eo['iris'] + Y_right = eo['pdist-jensenshannon-iris'] + Y_test1 = pdist(X, 'jensenshannon') + assert_allclose(Y_test1, Y_right, atol=eps) + + def test_pdist_jensenshannon_iris_float32(self): + eps = 1e-06 + X = np.float32(eo['iris']) + Y_right = eo['pdist-jensenshannon-iris'] + Y_test1 = pdist(X, 'jensenshannon') + assert_allclose(Y_test1, Y_right, atol=eps, verbose=verbose > 2) + + def test_pdist_jensenshannon_iris_nonC(self): + eps = 5e-5 + X = eo['iris'] + Y_right = eo['pdist-jensenshannon-iris'] + Y_test2 = pdist(X, 'test_jensenshannon') + assert_allclose(Y_test2, Y_right, rtol=eps) + + def test_pdist_matching_mtica1(self): + # Test matching(*,*) with mtica example #1 (nums). + m = wmatching(np.array([1, 0, 1, 1, 0]), + np.array([1, 1, 0, 1, 1])) + m2 = wmatching(np.array([1, 0, 1, 1, 0], dtype=bool), + np.array([1, 1, 0, 1, 1], dtype=bool)) + assert_allclose(m, 0.6, rtol=0, atol=1e-10) + assert_allclose(m2, 0.6, rtol=0, atol=1e-10) + + def test_pdist_matching_mtica2(self): + # Test matching(*,*) with mtica example #2. + m = wmatching(np.array([1, 0, 1]), + np.array([1, 1, 0])) + m2 = wmatching(np.array([1, 0, 1], dtype=bool), + np.array([1, 1, 0], dtype=bool)) + assert_allclose(m, 2 / 3, rtol=0, atol=1e-10) + assert_allclose(m2, 2 / 3, rtol=0, atol=1e-10) + + def test_pdist_yule_mtica1(self): + m = wyule(np.array([1, 0, 1, 1, 0]), + np.array([1, 1, 0, 1, 1])) + m2 = wyule(np.array([1, 0, 1, 1, 0], dtype=bool), + np.array([1, 1, 0, 1, 1], dtype=bool)) + if verbose > 2: + print(m) + assert_allclose(m, 2, rtol=0, atol=1e-10) + assert_allclose(m2, 2, rtol=0, atol=1e-10) + + def test_pdist_yule_mtica2(self): + m = wyule(np.array([1, 0, 1]), + np.array([1, 1, 0])) + m2 = wyule(np.array([1, 0, 1], dtype=bool), + np.array([1, 1, 0], dtype=bool)) + if verbose > 2: + print(m) + assert_allclose(m, 2, rtol=0, atol=1e-10) + assert_allclose(m2, 2, rtol=0, atol=1e-10) + + def test_pdist_dice_mtica1(self): + m = wdice(np.array([1, 0, 1, 1, 0]), + np.array([1, 1, 0, 1, 1])) + m2 = wdice(np.array([1, 0, 1, 1, 0], dtype=bool), + np.array([1, 1, 0, 1, 1], dtype=bool)) + if verbose > 2: + print(m) + assert_allclose(m, 3 / 7, rtol=0, atol=1e-10) + assert_allclose(m2, 3 / 7, rtol=0, atol=1e-10) + + def test_pdist_dice_mtica2(self): + m = wdice(np.array([1, 0, 1]), + np.array([1, 1, 0])) + m2 = wdice(np.array([1, 0, 1], dtype=bool), + np.array([1, 1, 0], dtype=bool)) + if verbose > 2: + print(m) + assert_allclose(m, 0.5, rtol=0, atol=1e-10) + assert_allclose(m2, 0.5, rtol=0, atol=1e-10) + + def test_pdist_sokalsneath_mtica1(self): + m = sokalsneath(np.array([1, 0, 1, 1, 0]), + np.array([1, 1, 0, 1, 1])) + m2 = sokalsneath(np.array([1, 0, 1, 1, 0], dtype=bool), + np.array([1, 1, 0, 1, 1], dtype=bool)) + if verbose > 2: + print(m) + assert_allclose(m, 3 / 4, rtol=0, atol=1e-10) + assert_allclose(m2, 3 / 4, rtol=0, atol=1e-10) + + def test_pdist_sokalsneath_mtica2(self): + m = wsokalsneath(np.array([1, 0, 1]), + np.array([1, 1, 0])) + m2 = wsokalsneath(np.array([1, 0, 1], dtype=bool), + np.array([1, 1, 0], dtype=bool)) + if verbose > 2: + print(m) + assert_allclose(m, 4 / 5, rtol=0, atol=1e-10) + assert_allclose(m2, 4 / 5, rtol=0, atol=1e-10) + + def test_pdist_rogerstanimoto_mtica1(self): + m = wrogerstanimoto(np.array([1, 0, 1, 1, 0]), + np.array([1, 1, 0, 1, 1])) + m2 = wrogerstanimoto(np.array([1, 0, 1, 1, 0], dtype=bool), + np.array([1, 1, 0, 1, 1], dtype=bool)) + if verbose > 2: + print(m) + assert_allclose(m, 3 / 4, rtol=0, atol=1e-10) + assert_allclose(m2, 3 / 4, rtol=0, atol=1e-10) + + def test_pdist_rogerstanimoto_mtica2(self): + m = wrogerstanimoto(np.array([1, 0, 1]), + np.array([1, 1, 0])) + m2 = wrogerstanimoto(np.array([1, 0, 1], dtype=bool), + np.array([1, 1, 0], dtype=bool)) + if verbose > 2: + print(m) + assert_allclose(m, 4 / 5, rtol=0, atol=1e-10) + assert_allclose(m2, 4 / 5, rtol=0, atol=1e-10) + + def test_pdist_russellrao_mtica1(self): + m = wrussellrao(np.array([1, 0, 1, 1, 0]), + np.array([1, 1, 0, 1, 1])) + m2 = wrussellrao(np.array([1, 0, 1, 1, 0], dtype=bool), + np.array([1, 1, 0, 1, 1], dtype=bool)) + if verbose > 2: + print(m) + assert_allclose(m, 3 / 5, rtol=0, atol=1e-10) + assert_allclose(m2, 3 / 5, rtol=0, atol=1e-10) + + def test_pdist_russellrao_mtica2(self): + m = wrussellrao(np.array([1, 0, 1]), + np.array([1, 1, 0])) + m2 = wrussellrao(np.array([1, 0, 1], dtype=bool), + np.array([1, 1, 0], dtype=bool)) + if verbose > 2: + print(m) + assert_allclose(m, 2 / 3, rtol=0, atol=1e-10) + assert_allclose(m2, 2 / 3, rtol=0, atol=1e-10) + + @pytest.mark.slow + def test_pdist_canberra_match(self): + D = eo['iris'] + if verbose > 2: + print(D.shape, D.dtype) + eps = 1e-15 + y1 = wpdist_no_const(D, "canberra") + y2 = wpdist_no_const(D, "test_canberra") + assert_allclose(y1, y2, rtol=eps, verbose=verbose > 2) + + def test_pdist_canberra_ticket_711(self): + # Test pdist(X, 'canberra') to see if Canberra gives the right result + # as reported on gh-1238. + eps = 1e-8 + pdist_y = wpdist_no_const(([3.3], [3.4]), "canberra") + right_y = 0.01492537 + assert_allclose(pdist_y, right_y, atol=eps, verbose=verbose > 2) + + @skip_xp_invalid_arg + def test_pdist_custom_notdouble(self): + # tests that when using a custom metric the data type is not altered + class myclass: + pass + + def _my_metric(x, y): + if not isinstance(x[0], myclass) or not isinstance(y[0], myclass): + raise ValueError("Type has been changed") + return 1.123 + data = np.array([[myclass()], [myclass()]], dtype=object) + pdist_y = pdist(data, metric=_my_metric) + right_y = 1.123 + assert_equal(pdist_y, right_y, verbose=verbose > 2) + + def _check_calling_conventions(self, X, metric, eps=1e-07, **kwargs): + # helper function for test_pdist_calling_conventions + try: + y1 = pdist(X, metric=metric, **kwargs) + y2 = pdist(X, metric=eval(metric), **kwargs) + y3 = pdist(X, metric="test_" + metric, **kwargs) + except Exception as e: + e_cls = e.__class__ + if verbose > 2: + print(e_cls.__name__) + print(e) + with pytest.raises(e_cls): + pdist(X, metric=metric, **kwargs) + with pytest.raises(e_cls): + pdist(X, metric=eval(metric), **kwargs) + with pytest.raises(e_cls): + pdist(X, metric="test_" + metric, **kwargs) + else: + assert_allclose(y1, y2, rtol=eps, verbose=verbose > 2) + assert_allclose(y1, y3, rtol=eps, verbose=verbose > 2) + + def test_pdist_calling_conventions(self, metric): + # Ensures that specifying the metric with a str or scipy function + # gives the same behaviour (i.e. same result or same exception). + # NOTE: The correctness should be checked within each metric tests. + # NOTE: Extra args should be checked with a dedicated test + for eo_name in self.rnd_eo_names: + # subsampling input data to speed-up tests + # NOTE: num samples needs to be > than dimensions for mahalanobis + X = eo[eo_name][::5, ::2] + if verbose > 2: + print("testing: ", metric, " with: ", eo_name) + if metric in {'dice', 'yule', 'matching', 'rogerstanimoto', 'russellrao', + 'sokalsneath'} and 'bool' not in eo_name: + # python version permits non-bools e.g. for fuzzy logic + continue + self._check_calling_conventions(X, metric) + + # Testing built-in metrics with extra args + if metric == "seuclidean": + V = np.var(X.astype(np.float64), axis=0, ddof=1) + self._check_calling_conventions(X, metric, V=V) + elif metric == "mahalanobis": + V = np.atleast_2d(np.cov(X.astype(np.float64).T)) + VI = np.array(np.linalg.inv(V).T) + self._check_calling_conventions(X, metric, VI=VI) + + def test_pdist_dtype_equivalence(self, metric): + # Tests that the result is not affected by type up-casting + eps = 1e-07 + tests = [(eo['random-bool-data'], self.valid_upcasts['bool']), + (eo['random-uint-data'], self.valid_upcasts['uint']), + (eo['random-int-data'], self.valid_upcasts['int']), + (eo['random-float32-data'], self.valid_upcasts['float32'])] + for test in tests: + X1 = test[0][::5, ::2] + try: + y1 = pdist(X1, metric=metric) + except Exception as e: + e_cls = e.__class__ + if verbose > 2: + print(e_cls.__name__) + print(e) + for new_type in test[1]: + X2 = new_type(X1) + with pytest.raises(e_cls): + pdist(X2, metric=metric) + else: + for new_type in test[1]: + y2 = pdist(new_type(X1), metric=metric) + assert_allclose(y1, y2, rtol=eps, verbose=verbose > 2) + + def test_pdist_out(self, metric): + # Test that out parameter works properly + eps = 1e-15 + X = eo['random-float32-data'][::5, ::2] + out_size = int((X.shape[0] * (X.shape[0] - 1)) / 2) + + kwargs = dict() + if metric == 'minkowski': + kwargs['p'] = 1.23 + out1 = np.empty(out_size, dtype=np.float64) + Y_right = pdist(X, metric, **kwargs) + Y_test1 = pdist(X, metric, out=out1, **kwargs) + + # test that output is numerically equivalent + assert_allclose(Y_test1, Y_right, rtol=eps) + + # test that Y_test1 and out1 are the same object + assert_(Y_test1 is out1) + + # test for incorrect shape + out2 = np.empty(out_size + 3, dtype=np.float64) + with pytest.raises(ValueError): + pdist(X, metric, out=out2, **kwargs) + + # test for (C-)contiguous output + out3 = np.empty(2 * out_size, dtype=np.float64)[::2] + with pytest.raises(ValueError): + pdist(X, metric, out=out3, **kwargs) + + # test for incorrect dtype + out5 = np.empty(out_size, dtype=np.int64) + with pytest.raises(ValueError): + pdist(X, metric, out=out5, **kwargs) + + def test_striding(self, metric): + # test that striding is handled correct with calls to + # _copy_array_if_base_present + eps = 1e-15 + X = eo['random-float32-data'][::5, ::2] + X_copy = X.copy() + + # confirm contiguity + assert_(not X.flags.c_contiguous) + assert_(X_copy.flags.c_contiguous) + + kwargs = dict() + if metric == 'minkowski': + kwargs['p'] = 1.23 + Y1 = pdist(X, metric, **kwargs) + Y2 = pdist(X_copy, metric, **kwargs) + # test that output is numerically equivalent + assert_allclose(Y1, Y2, rtol=eps, verbose=verbose > 2) + +class TestSomeDistanceFunctions: + + def setup_method(self): + # 1D arrays + x = np.array([1.0, 2.0, 3.0]) + y = np.array([1.0, 1.0, 5.0]) + + self.cases = [(x, y)] + + def test_minkowski(self): + for x, y in self.cases: + dist1 = minkowski(x, y, p=1) + assert_almost_equal(dist1, 3.0) + dist1p5 = minkowski(x, y, p=1.5) + assert_almost_equal(dist1p5, (1.0 + 2.0**1.5)**(2. / 3)) + dist2 = minkowski(x, y, p=2) + assert_almost_equal(dist2, 5.0 ** 0.5) + dist0p25 = minkowski(x, y, p=0.25) + assert_almost_equal(dist0p25, (1.0 + 2.0 ** 0.25) ** 4) + + # Check that casting input to minimum scalar type doesn't affect result + # (issue #10262). This could be extended to more test inputs with + # np.min_scalar_type(np.max(input_matrix)). + a = np.array([352, 916]) + b = np.array([350, 660]) + assert_equal(minkowski(a, b), + minkowski(a.astype('uint16'), b.astype('uint16'))) + + def test_euclidean(self): + for x, y in self.cases: + dist = weuclidean(x, y) + assert_almost_equal(dist, np.sqrt(5)) + + def test_sqeuclidean(self): + for x, y in self.cases: + dist = wsqeuclidean(x, y) + assert_almost_equal(dist, 5.0) + + def test_cosine(self): + for x, y in self.cases: + dist = wcosine(x, y) + assert_almost_equal(dist, 1.0 - 18.0 / (np.sqrt(14) * np.sqrt(27))) + + def test_cosine_output_dtype(self): + # Regression test for gh-19541 + assert isinstance(wcorrelation([1, 1], [1, 1], centered=False), float) + assert isinstance(wcosine([1, 1], [1, 1]), float) + + def test_correlation(self): + xm = np.array([-1.0, 0, 1.0]) + ym = np.array([-4.0 / 3, -4.0 / 3, 5.0 - 7.0 / 3]) + for x, y in self.cases: + dist = wcorrelation(x, y) + assert_almost_equal(dist, 1.0 - np.dot(xm, ym) / (norm(xm) * norm(ym))) + + def test_correlation_positive(self): + # Regression test for gh-12320 (negative return value due to rounding + x = np.array([0., 0., 0., 0., 0., 0., -2., 0., 0., 0., -2., -2., -2., + 0., -2., 0., -2., 0., 0., -1., -2., 0., 1., 0., 0., -2., + 0., 0., -2., 0., -2., -2., -2., -2., -2., -2., 0.]) + y = np.array([1., 1., 1., 1., 1., 1., -1., 1., 1., 1., -1., -1., -1., + 1., -1., 1., -1., 1., 1., 0., -1., 1., 2., 1., 1., -1., + 1., 1., -1., 1., -1., -1., -1., -1., -1., -1., 1.]) + dist = correlation(x, y) + assert 0 <= dist <= 10 * np.finfo(np.float64).eps + + @pytest.mark.filterwarnings('ignore:Casting complex') + @pytest.mark.parametrize("func", [correlation, cosine]) + def test_corr_dep_complex(self, func): + x = [1+0j, 2+0j] + y = [3+0j, 4+0j] + with pytest.raises(TypeError, match="real"): + func(x, y) + + def test_mahalanobis(self): + x = np.array([1.0, 2.0, 3.0]) + y = np.array([1.0, 1.0, 5.0]) + vi = np.array([[2.0, 1.0, 0.0], [1.0, 2.0, 1.0], [0.0, 1.0, 2.0]]) + for x, y in self.cases: + dist = mahalanobis(x, y, vi) + assert_almost_equal(dist, np.sqrt(6.0)) + + +class TestSquareForm: + checked_dtypes = [np.float64, np.float32, np.int32, np.int8, bool] + + def test_squareform_matrix(self): + for dtype in self.checked_dtypes: + self.check_squareform_matrix(dtype) + + def test_squareform_vector(self): + for dtype in self.checked_dtypes: + self.check_squareform_vector(dtype) + + def check_squareform_matrix(self, dtype): + A = np.zeros((0, 0), dtype=dtype) + rA = squareform(A) + assert_equal(rA.shape, (0,)) + assert_equal(rA.dtype, dtype) + + A = np.zeros((1, 1), dtype=dtype) + rA = squareform(A) + assert_equal(rA.shape, (0,)) + assert_equal(rA.dtype, dtype) + + A = np.array([[0, 4.2], [4.2, 0]], dtype=dtype) + rA = squareform(A) + assert_equal(rA.shape, (1,)) + assert_equal(rA.dtype, dtype) + assert_array_equal(rA, np.array([4.2], dtype=dtype)) + + def check_squareform_vector(self, dtype): + v = np.zeros((0,), dtype=dtype) + rv = squareform(v) + assert_equal(rv.shape, (1, 1)) + assert_equal(rv.dtype, dtype) + assert_array_equal(rv, [[0]]) + + v = np.array([8.3], dtype=dtype) + rv = squareform(v) + assert_equal(rv.shape, (2, 2)) + assert_equal(rv.dtype, dtype) + assert_array_equal(rv, np.array([[0, 8.3], [8.3, 0]], dtype=dtype)) + + def test_squareform_multi_matrix(self): + for n in range(2, 5): + self.check_squareform_multi_matrix(n) + + def check_squareform_multi_matrix(self, n): + X = np.random.rand(n, 4) + Y = wpdist_no_const(X) + assert_equal(len(Y.shape), 1) + A = squareform(Y) + Yr = squareform(A) + s = A.shape + k = 0 + if verbose >= 3: + print(A.shape, Y.shape, Yr.shape) + assert_equal(len(s), 2) + assert_equal(len(Yr.shape), 1) + assert_equal(s[0], s[1]) + for i in range(0, s[0]): + for j in range(i + 1, s[1]): + if i != j: + assert_equal(A[i, j], Y[k]) + k += 1 + else: + assert_equal(A[i, j], 0) + + +class TestNumObsY: + + def test_num_obs_y_multi_matrix(self): + for n in range(2, 10): + X = np.random.rand(n, 4) + Y = wpdist_no_const(X) + assert_equal(num_obs_y(Y), n) + + def test_num_obs_y_1(self): + # Tests num_obs_y(y) on a condensed distance matrix over 1 + # observations. Expecting exception. + with pytest.raises(ValueError): + self.check_y(1) + + def test_num_obs_y_2(self): + # Tests num_obs_y(y) on a condensed distance matrix over 2 + # observations. + assert_(self.check_y(2)) + + def test_num_obs_y_3(self): + assert_(self.check_y(3)) + + def test_num_obs_y_4(self): + assert_(self.check_y(4)) + + def test_num_obs_y_5_10(self): + for i in range(5, 16): + self.minit(i) + + def test_num_obs_y_2_100(self): + # Tests num_obs_y(y) on 100 improper condensed distance matrices. + # Expecting exception. + a = set() + for n in range(2, 16): + a.add(n * (n - 1) / 2) + for i in range(5, 105): + if i not in a: + with pytest.raises(ValueError): + self.bad_y(i) + + def minit(self, n): + assert_(self.check_y(n)) + + def bad_y(self, n): + y = np.random.rand(n) + return num_obs_y(y) + + def check_y(self, n): + return num_obs_y(self.make_y(n)) == n + + def make_y(self, n): + return np.random.rand((n * (n - 1)) // 2) + + +class TestNumObsDM: + + def test_num_obs_dm_multi_matrix(self): + for n in range(1, 10): + X = np.random.rand(n, 4) + Y = wpdist_no_const(X) + A = squareform(Y) + if verbose >= 3: + print(A.shape, Y.shape) + assert_equal(num_obs_dm(A), n) + + def test_num_obs_dm_0(self): + # Tests num_obs_dm(D) on a 0x0 distance matrix. Expecting exception. + assert_(self.check_D(0)) + + def test_num_obs_dm_1(self): + # Tests num_obs_dm(D) on a 1x1 distance matrix. + assert_(self.check_D(1)) + + def test_num_obs_dm_2(self): + assert_(self.check_D(2)) + + def test_num_obs_dm_3(self): + assert_(self.check_D(2)) + + def test_num_obs_dm_4(self): + assert_(self.check_D(4)) + + def check_D(self, n): + return num_obs_dm(self.make_D(n)) == n + + def make_D(self, n): + return np.random.rand(n, n) + + +def is_valid_dm_throw(D): + return is_valid_dm(D, throw=True) + + +class TestIsValidDM: + + def test_is_valid_dm_improper_shape_1D_E(self): + D = np.zeros((5,), dtype=np.float64) + with pytest.raises(ValueError): + is_valid_dm_throw(D) + + def test_is_valid_dm_improper_shape_1D_F(self): + D = np.zeros((5,), dtype=np.float64) + assert_equal(is_valid_dm(D), False) + + def test_is_valid_dm_improper_shape_3D_E(self): + D = np.zeros((3, 3, 3), dtype=np.float64) + with pytest.raises(ValueError): + is_valid_dm_throw(D) + + def test_is_valid_dm_improper_shape_3D_F(self): + D = np.zeros((3, 3, 3), dtype=np.float64) + assert_equal(is_valid_dm(D), False) + + def test_is_valid_dm_nonzero_diagonal_E(self): + y = np.random.rand(10) + D = squareform(y) + for i in range(0, 5): + D[i, i] = 2.0 + with pytest.raises(ValueError): + is_valid_dm_throw(D) + + def test_is_valid_dm_nonzero_diagonal_F(self): + y = np.random.rand(10) + D = squareform(y) + for i in range(0, 5): + D[i, i] = 2.0 + assert_equal(is_valid_dm(D), False) + + def test_is_valid_dm_asymmetric_E(self): + y = np.random.rand(10) + D = squareform(y) + D[1, 3] = D[3, 1] + 1 + with pytest.raises(ValueError): + is_valid_dm_throw(D) + + def test_is_valid_dm_asymmetric_F(self): + y = np.random.rand(10) + D = squareform(y) + D[1, 3] = D[3, 1] + 1 + assert_equal(is_valid_dm(D), False) + + def test_is_valid_dm_correct_1_by_1(self): + D = np.zeros((1, 1), dtype=np.float64) + assert_equal(is_valid_dm(D), True) + + def test_is_valid_dm_correct_2_by_2(self): + y = np.random.rand(1) + D = squareform(y) + assert_equal(is_valid_dm(D), True) + + def test_is_valid_dm_correct_3_by_3(self): + y = np.random.rand(3) + D = squareform(y) + assert_equal(is_valid_dm(D), True) + + def test_is_valid_dm_correct_4_by_4(self): + y = np.random.rand(6) + D = squareform(y) + assert_equal(is_valid_dm(D), True) + + def test_is_valid_dm_correct_5_by_5(self): + y = np.random.rand(10) + D = squareform(y) + assert_equal(is_valid_dm(D), True) + + +def is_valid_y_throw(y): + return is_valid_y(y, throw=True) + + +class TestIsValidY: + # If test case name ends on "_E" then an exception is expected for the + # given input, if it ends in "_F" then False is expected for the is_valid_y + # check. Otherwise the input is expected to be valid. + + def test_is_valid_y_improper_shape_2D_E(self): + y = np.zeros((3, 3,), dtype=np.float64) + with pytest.raises(ValueError): + is_valid_y_throw(y) + + def test_is_valid_y_improper_shape_2D_F(self): + y = np.zeros((3, 3,), dtype=np.float64) + assert_equal(is_valid_y(y), False) + + def test_is_valid_y_improper_shape_3D_E(self): + y = np.zeros((3, 3, 3), dtype=np.float64) + with pytest.raises(ValueError): + is_valid_y_throw(y) + + def test_is_valid_y_improper_shape_3D_F(self): + y = np.zeros((3, 3, 3), dtype=np.float64) + assert_equal(is_valid_y(y), False) + + def test_is_valid_y_correct_2_by_2(self): + y = self.correct_n_by_n(2) + assert_equal(is_valid_y(y), True) + + def test_is_valid_y_correct_3_by_3(self): + y = self.correct_n_by_n(3) + assert_equal(is_valid_y(y), True) + + def test_is_valid_y_correct_4_by_4(self): + y = self.correct_n_by_n(4) + assert_equal(is_valid_y(y), True) + + def test_is_valid_y_correct_5_by_5(self): + y = self.correct_n_by_n(5) + assert_equal(is_valid_y(y), True) + + def test_is_valid_y_2_100(self): + a = set() + for n in range(2, 16): + a.add(n * (n - 1) / 2) + for i in range(5, 105): + if i not in a: + with pytest.raises(ValueError): + self.bad_y(i) + + def bad_y(self, n): + y = np.random.rand(n) + return is_valid_y(y, throw=True) + + def correct_n_by_n(self, n): + y = np.random.rand((n * (n - 1)) // 2) + return y + + +@pytest.mark.parametrize("p", [-10.0, -0.5, 0.0]) +def test_bad_p(p): + # Raise ValueError if p <=0. + with pytest.raises(ValueError): + minkowski([1, 2], [3, 4], p) + with pytest.raises(ValueError): + minkowski([1, 2], [3, 4], p, [1, 1]) + + +def test_sokalsneath_all_false(): + # Regression test for ticket #876 + with pytest.raises(ValueError): + sokalsneath([False, False, False], [False, False, False]) + + +def test_canberra(): + # Regression test for ticket #1430. + assert_equal(wcanberra([1, 2, 3], [2, 4, 6]), 1) + assert_equal(wcanberra([1, 1, 0, 0], [1, 0, 1, 0]), 2) + + +def test_braycurtis(): + # Regression test for ticket #1430. + assert_almost_equal(wbraycurtis([1, 2, 3], [2, 4, 6]), 1. / 3, decimal=15) + assert_almost_equal(wbraycurtis([1, 1, 0, 0], [1, 0, 1, 0]), 0.5, decimal=15) + + +def test_euclideans(): + # Regression test for ticket #1328. + x1 = np.array([1, 1, 1]) + x2 = np.array([0, 0, 0]) + + # Basic test of the calculation. + assert_almost_equal(wsqeuclidean(x1, x2), 3.0, decimal=14) + assert_almost_equal(weuclidean(x1, x2), np.sqrt(3), decimal=14) + + # Another check, with random data. + rs = np.random.RandomState(1234567890) + x = rs.rand(10) + y = rs.rand(10) + d1 = weuclidean(x, y) + d2 = wsqeuclidean(x, y) + assert_almost_equal(d1**2, d2, decimal=14) + + +def test_hamming_unequal_length(): + # Regression test for gh-4290. + x = [0, 0, 1] + y = [1, 0, 1, 0] + # Used to give an AttributeError from ndarray.mean called on bool + with pytest.raises(ValueError): + whamming(x, y) + + +def test_hamming_unequal_length_with_w(): + u = [0, 0, 1] + v = [0, 0, 1] + w = [1, 0, 1, 0] + msg = "'w' should have the same length as 'u' and 'v'." + with pytest.raises(ValueError, match=msg): + whamming(u, v, w) + + +def test_hamming_string_array(): + # https://github.com/scikit-learn/scikit-learn/issues/4014 + a = np.array(['eggs', 'spam', 'spam', 'eggs', 'spam', 'spam', 'spam', + 'spam', 'spam', 'spam', 'spam', 'eggs', 'eggs', 'spam', + 'eggs', 'eggs', 'eggs', 'eggs', 'eggs', 'spam'], + dtype='|S4') + b = np.array(['eggs', 'spam', 'spam', 'eggs', 'eggs', 'spam', 'spam', + 'spam', 'spam', 'eggs', 'spam', 'eggs', 'spam', 'eggs', + 'spam', 'spam', 'eggs', 'spam', 'spam', 'eggs'], + dtype='|S4') + desired = 0.45 + assert_allclose(whamming(a, b), desired) + + +def test_minkowski_w(): + # Regression test for gh-8142. + arr_in = np.array([[83.33333333, 100., 83.33333333, 100., 36., + 60., 90., 150., 24., 48.], + [83.33333333, 100., 83.33333333, 100., 36., + 60., 90., 150., 24., 48.]]) + p0 = pdist(arr_in, metric='minkowski', p=1, w=None) + c0 = cdist(arr_in, arr_in, metric='minkowski', p=1, w=None) + p1 = pdist(arr_in, metric='minkowski', p=1) + c1 = cdist(arr_in, arr_in, metric='minkowski', p=1) + + assert_allclose(p0, p1, rtol=1e-15) + assert_allclose(c0, c1, rtol=1e-15) + + +def test_sqeuclidean_dtypes(): + # Assert that sqeuclidean returns the right types of values. + # Integer types should be converted to floating for stability. + # Floating point types should be the same as the input. + x = [1, 2, 3] + y = [4, 5, 6] + + for dtype in [np.int8, np.int16, np.int32, np.int64]: + d = wsqeuclidean(np.asarray(x, dtype=dtype), np.asarray(y, dtype=dtype)) + assert_(np.issubdtype(d.dtype, np.floating)) + + for dtype in [np.uint8, np.uint16, np.uint32, np.uint64]: + umax = np.iinfo(dtype).max + d1 = wsqeuclidean([0], np.asarray([umax], dtype=dtype)) + d2 = wsqeuclidean(np.asarray([umax], dtype=dtype), [0]) + + assert_equal(d1, d2) + assert_equal(d1, np.float64(umax)**2) + + dtypes = [np.float32, np.float64, np.complex64, np.complex128] + for dtype in ['float16', 'float128']: + # These aren't present in older numpy versions; float128 may also not + # be present on all platforms. + if hasattr(np, dtype): + dtypes.append(getattr(np, dtype)) + + for dtype in dtypes: + d = wsqeuclidean(np.asarray(x, dtype=dtype), np.asarray(y, dtype=dtype)) + assert_equal(d.dtype, dtype) + + +def test_modifies_input(metric): + # test whether cdist or pdist modifies input arrays + X1 = np.asarray([[1., 2., 3.], + [1.2, 2.3, 3.4], + [2.2, 2.3, 4.4], + [22.2, 23.3, 44.4]]) + X1_copy = X1.copy() + cdist(X1, X1, metric) + pdist(X1, metric) + assert_array_equal(X1, X1_copy) + + +def test_Xdist_deprecated_args(metric): + # testing both cdist and pdist deprecated warnings + X1 = np.asarray([[1., 2., 3.], + [1.2, 2.3, 3.4], + [2.2, 2.3, 4.4], + [22.2, 23.3, 44.4]]) + + with pytest.raises(TypeError): + cdist(X1, X1, metric, 2.) + + with pytest.raises(TypeError): + pdist(X1, metric, 2.) + + for arg in ["p", "V", "VI"]: + kwargs = {arg: np.asarray(1.)} + + if ((arg == "V" and metric == "seuclidean") + or (arg == "VI" and metric == "mahalanobis") + or (arg == "p" and metric == "minkowski")): + continue + + with pytest.raises(TypeError): + cdist(X1, X1, metric, **kwargs) + + with pytest.raises(TypeError): + pdist(X1, metric, **kwargs) + + +def test_Xdist_non_negative_weights(metric): + X = eo['random-float32-data'][::5, ::2] + w = np.ones(X.shape[1]) + w[::5] = -w[::5] + + if metric in ['seuclidean', 'mahalanobis', 'jensenshannon']: + pytest.skip("not applicable") + + for m in [metric, eval(metric), "test_" + metric]: + with pytest.raises(ValueError): + pdist(X, m, w=w) + with pytest.raises(ValueError): + cdist(X, X, m, w=w) + + +def test__validate_vector(): + x = [1, 2, 3] + y = _validate_vector(x) + assert_array_equal(y, x) + + y = _validate_vector(x, dtype=np.float64) + assert_array_equal(y, x) + assert_equal(y.dtype, np.float64) + + x = [1] + y = _validate_vector(x) + assert_equal(y.ndim, 1) + assert_equal(y, x) + + x = 1 + with pytest.raises(ValueError, match="Input vector should be 1-D"): + _validate_vector(x) + + x = np.arange(5).reshape(1, -1, 1) + with pytest.raises(ValueError, match="Input vector should be 1-D"): + _validate_vector(x) + + x = [[1, 2], [3, 4]] + with pytest.raises(ValueError, match="Input vector should be 1-D"): + _validate_vector(x) + +def test_yule_all_same(): + # Test yule avoids a divide by zero when exactly equal + x = np.ones((2, 6), dtype=bool) + d = wyule(x[0], x[0]) + assert d == 0.0 + + d = pdist(x, 'yule') + assert_equal(d, [0.0]) + + d = cdist(x[:1], x[:1], 'yule') + assert_equal(d, [[0.0]]) + + +def test_jensenshannon(): + assert_almost_equal(jensenshannon([1.0, 0.0, 0.0], [0.0, 1.0, 0.0], 2.0), + 1.0) + assert_almost_equal(jensenshannon([1.0, 0.0], [0.5, 0.5]), + 0.46450140402245893) + assert_almost_equal(jensenshannon([1.0, 0.0, 0.0], [1.0, 0.0, 0.0]), 0.0) + + assert_almost_equal(jensenshannon([[1.0, 2.0]], [[0.5, 1.5]], axis=0), + [0.0, 0.0]) + assert_almost_equal(jensenshannon([[1.0, 2.0]], [[0.5, 1.5]], axis=1), + [0.0649045]) + assert_almost_equal(jensenshannon([[1.0, 2.0]], [[0.5, 1.5]], axis=0, + keepdims=True), [[0.0, 0.0]]) + assert_almost_equal(jensenshannon([[1.0, 2.0]], [[0.5, 1.5]], axis=1, + keepdims=True), [[0.0649045]]) + + a = np.array([[1, 2, 3, 4], + [5, 6, 7, 8], + [9, 10, 11, 12]]) + b = np.array([[13, 14, 15, 16], + [17, 18, 19, 20], + [21, 22, 23, 24]]) + + assert_almost_equal(jensenshannon(a, b, axis=0), + [0.1954288, 0.1447697, 0.1138377, 0.0927636]) + assert_almost_equal(jensenshannon(a, b, axis=1), + [0.1402339, 0.0399106, 0.0201815]) + + +def test_gh_17703(): + arr_1 = np.array([1, 0, 0]) + arr_2 = np.array([2, 0, 0]) + expected = dice(arr_1, arr_2) + actual = pdist([arr_1, arr_2], metric='dice') + assert_allclose(actual, expected) + actual = cdist(np.atleast_2d(arr_1), + np.atleast_2d(arr_2), metric='dice') + assert_allclose(actual, expected) + + +def test_immutable_input(metric): + if metric in ("jensenshannon", "mahalanobis", "seuclidean"): + pytest.skip("not applicable") + x = np.arange(10, dtype=np.float64) + x.setflags(write=False) + getattr(scipy.spatial.distance, metric)(x, x, w=x) + + +def test_gh_23109(): + a = np.array([0, 0, 1, 1]) + b = np.array([0, 1, 1, 0]) + w = np.asarray([1.5, 1.2, 0.7, 1.3]) + expected = yule(a, b, w=w) + assert_allclose(expected, 1.1954022988505748) + actual = cdist(np.atleast_2d(a), + np.atleast_2d(b), + metric='yule', w=w) + assert_allclose(actual, expected) + + +class TestJaccard: + + def test_pdist_jaccard_random(self): + eps = 1e-8 + X = eo['pdist-boolean-inp'] + Y_right = eo['pdist-jaccard'] + Y_test1 = wpdist(X, 'jaccard') + assert_allclose(Y_test1, Y_right, rtol=eps) + + def test_pdist_jaccard_random_float32(self): + eps = 1e-8 + X = np.float32(eo['pdist-boolean-inp']) + Y_right = eo['pdist-jaccard'] + Y_test1 = wpdist(X, 'jaccard') + assert_allclose(Y_test1, Y_right, rtol=eps) + + def test_pdist_jaccard_random_nonC(self): + eps = 1e-8 + X = eo['pdist-boolean-inp'] + Y_right = eo['pdist-jaccard'] + Y_test2 = wpdist(X, 'test_jaccard') + assert_allclose(Y_test2, Y_right, rtol=eps) + + def test_pdist_djaccard_random(self): + eps = 1e-8 + X = np.float64(eo['pdist-boolean-inp']) + Y_right = eo['pdist-jaccard'] + Y_test1 = wpdist(X, 'jaccard') + assert_allclose(Y_test1, Y_right, rtol=eps) + + def test_pdist_djaccard_random_float32(self): + eps = 1e-8 + X = np.float32(eo['pdist-boolean-inp']) + Y_right = eo['pdist-jaccard'] + Y_test1 = wpdist(X, 'jaccard') + assert_allclose(Y_test1, Y_right, rtol=eps) + + def test_pdist_djaccard_allzeros(self): + eps = 1e-15 + Y = pdist(np.zeros((5, 3)), 'jaccard') + assert_allclose(np.zeros(10), Y, rtol=eps) + + def test_pdist_djaccard_random_nonC(self): + eps = 1e-8 + X = np.float64(eo['pdist-boolean-inp']) + Y_right = eo['pdist-jaccard'] + Y_test2 = wpdist(X, 'test_jaccard') + assert_allclose(Y_test2, Y_right, rtol=eps) + + def test_pdist_djaccard_allzeros_nonC(self): + eps = 1e-15 + Y = pdist(np.zeros((5, 3)), 'test_jaccard') + assert_allclose(np.zeros(10), Y, rtol=eps) + + def test_pdist_jaccard_mtica1(self): + m = wjaccard(np.array([1, 0, 1, 1, 0]), + np.array([1, 1, 0, 1, 1])) + m2 = wjaccard(np.array([1, 0, 1, 1, 0], dtype=bool), + np.array([1, 1, 0, 1, 1], dtype=bool)) + assert_allclose(m, 0.6, rtol=0, atol=1e-10) + assert_allclose(m2, 0.6, rtol=0, atol=1e-10) + + def test_pdist_jaccard_mtica2(self): + m = wjaccard(np.array([1, 0, 1]), + np.array([1, 1, 0])) + m2 = wjaccard(np.array([1, 0, 1], dtype=bool), + np.array([1, 1, 0], dtype=bool)) + assert_allclose(m, 2 / 3, rtol=0, atol=1e-10) + assert_allclose(m2, 2 / 3, rtol=0, atol=1e-10) + + def test_non_01_input(self): + # Non-0/1 numeric input should be cast to bool before computation. + # See gh-21176. + x = np.array([-10, 2.5, 0]) # [True, True, False] + y = np.array([ 2, -5, 2]) # [True, True, True] + eps = np.finfo(float).eps + assert_allclose(jaccard(x, y), 1/3, rtol=eps) + assert_allclose(cdist([x], [y], 'jaccard'), [[1/3]]) + assert_allclose(pdist([x, y], 'jaccard'), [1/3]) + + +class TestChebyshev: + + def test_pdist_chebyshev_random(self): + eps = 1e-8 + X = eo['pdist-double-inp'] + Y_right = eo['pdist-chebyshev'] + Y_test1 = pdist(X, 'chebyshev') + assert_allclose(Y_test1, Y_right, rtol=eps) + + def test_pdist_chebyshev_random_float32(self): + eps = 1e-7 + X = np.float32(eo['pdist-double-inp']) + Y_right = eo['pdist-chebyshev'] + Y_test1 = pdist(X, 'chebyshev') + assert_allclose(Y_test1, Y_right, rtol=eps, verbose=verbose > 2) + + def test_pdist_chebyshev_random_nonC(self): + eps = 1e-8 + X = eo['pdist-double-inp'] + Y_right = eo['pdist-chebyshev'] + Y_test2 = pdist(X, 'test_chebyshev') + assert_allclose(Y_test2, Y_right, rtol=eps) + + def test_pdist_chebyshev_iris(self): + eps = 1e-14 + X = eo['iris'] + Y_right = eo['pdist-chebyshev-iris'] + Y_test1 = pdist(X, 'chebyshev') + assert_allclose(Y_test1, Y_right, rtol=eps) + + def test_pdist_chebyshev_iris_float32(self): + eps = 1e-5 + X = np.float32(eo['iris']) + Y_right = eo['pdist-chebyshev-iris'] + Y_test1 = pdist(X, 'chebyshev') + assert_allclose(Y_test1, Y_right, rtol=eps, verbose=verbose > 2) + + def test_pdist_chebyshev_iris_nonC(self): + eps = 1e-14 + X = eo['iris'] + Y_right = eo['pdist-chebyshev-iris'] + Y_test2 = pdist(X, 'test_chebyshev') + assert_allclose(Y_test2, Y_right, rtol=eps) + + def test_weighted(self): + # Basic test for weighted Chebyshev. Only components with non-zero + # weight participate in the 'max'. + x = [1, 2, 3] + y = [6, 5, 4] + w = [0, 1, 5] + assert_equal(chebyshev(x, y, w), 3) + assert_equal(pdist([x, y], 'chebyshev', w=w), [3]) + assert_equal(cdist([x], [y], 'chebyshev', w=w), [[3]]) + + def test_zero_weight(self): + # If the weight is identically zero, the distance should be zero. + x = [1, 2, 3] + y = [6, 5, 4] + w = [0, 0, 0] + assert_equal(chebyshev(x, y, w), 0) + assert_equal(pdist([x, y], 'chebyshev', w=w), [0]) + assert_equal(cdist([x], [y], 'chebyshev', w=w), [[0]]) + + +@pytest.mark.parametrize( + "func,p", + [ + (minkowski, 1), + (minkowski, 2), + (minkowski, 3.5), + (minkowski, np.inf), + (euclidean, None), + (sqeuclidean, None), + ], +) +@pytest.mark.parametrize("weights", [True, False]) +def test_distance_nd(func, p, weights): + # check that inputs broadcast correctly against a reference implementation + rng = np.random.default_rng(6738657865438) + ref_func = _apply_over_batch(('u', 1), ('v', 1), ('p', 1), ('w', 1))(func) + + u = rng.random((5, 2, 4)) + v = rng.random((2, 4)) + w = rng.random(4) if weights else None + kwargs = {'w': w} if p is None else {'p': p, 'w': w} + + res = func(u, v, **kwargs) + ref = ref_func(u, v, **kwargs) + + assert_allclose(res, ref) diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/tests/test_hausdorff.py b/venv/lib/python3.12/site-packages/scipy/spatial/tests/test_hausdorff.py new file mode 100644 index 0000000000000000000000000000000000000000..45d89e868c8c2daca6e9c30973399fbbfc5d52fd --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/tests/test_hausdorff.py @@ -0,0 +1,199 @@ +import numpy as np +from numpy.testing import (assert_allclose, + assert_array_equal, + assert_equal) +import pytest +from scipy.spatial.distance import directed_hausdorff +from scipy.spatial import distance +from scipy._lib._util import check_random_state + + +class TestHausdorff: + # Test various properties of the directed Hausdorff code. + + def setup_method(self): + np.random.seed(1234) + random_angles = np.random.random(100) * np.pi * 2 + random_columns = np.column_stack( + (random_angles, random_angles, np.zeros(100))) + random_columns[..., 0] = np.cos(random_columns[..., 0]) + random_columns[..., 1] = np.sin(random_columns[..., 1]) + random_columns_2 = np.column_stack( + (random_angles, random_angles, np.zeros(100))) + random_columns_2[1:, 0] = np.cos(random_columns_2[1:, 0]) * 2.0 + random_columns_2[1:, 1] = np.sin(random_columns_2[1:, 1]) * 2.0 + # move one point farther out so we don't have two perfect circles + random_columns_2[0, 0] = np.cos(random_columns_2[0, 0]) * 3.3 + random_columns_2[0, 1] = np.sin(random_columns_2[0, 1]) * 3.3 + self.path_1 = random_columns + self.path_2 = random_columns_2 + self.path_1_4d = np.insert(self.path_1, 3, 5, axis=1) + self.path_2_4d = np.insert(self.path_2, 3, 27, axis=1) + + def test_symmetry(self): + # Ensure that the directed (asymmetric) Hausdorff distance is + # actually asymmetric + + forward = directed_hausdorff(self.path_1, self.path_2)[0] + reverse = directed_hausdorff(self.path_2, self.path_1)[0] + assert forward != reverse + + def test_brute_force_comparison_forward(self): + # Ensure that the algorithm for directed_hausdorff gives the + # same result as the simple / brute force approach in the + # forward direction. + actual = directed_hausdorff(self.path_1, self.path_2)[0] + # brute force over rows: + expected = max(np.amin(distance.cdist(self.path_1, self.path_2), + axis=1)) + assert_allclose(actual, expected) + + def test_brute_force_comparison_reverse(self): + # Ensure that the algorithm for directed_hausdorff gives the + # same result as the simple / brute force approach in the + # reverse direction. + actual = directed_hausdorff(self.path_2, self.path_1)[0] + # brute force over columns: + expected = max(np.amin(distance.cdist(self.path_1, self.path_2), + axis=0)) + assert_allclose(actual, expected) + + def test_degenerate_case(self): + # The directed Hausdorff distance must be zero if both input + # data arrays match. + actual = directed_hausdorff(self.path_1, self.path_1)[0] + assert_allclose(actual, 0.0) + + def test_2d_data_forward(self): + # Ensure that 2D data is handled properly for a simple case + # relative to brute force approach. + actual = directed_hausdorff(self.path_1[..., :2], + self.path_2[..., :2])[0] + expected = max(np.amin(distance.cdist(self.path_1[..., :2], + self.path_2[..., :2]), + axis=1)) + assert_allclose(actual, expected) + + def test_4d_data_reverse(self): + # Ensure that 4D data is handled properly for a simple case + # relative to brute force approach. + actual = directed_hausdorff(self.path_2_4d, self.path_1_4d)[0] + # brute force over columns: + expected = max(np.amin(distance.cdist(self.path_1_4d, self.path_2_4d), + axis=0)) + assert_allclose(actual, expected) + + def test_indices(self): + # Ensure that correct point indices are returned -- they should + # correspond to the Hausdorff pair + path_simple_1 = np.array([[-1,-12],[0,0], [1,1], [3,7], [1,2]]) + path_simple_2 = np.array([[0,0], [1,1], [4,100], [10,9]]) + actual = directed_hausdorff(path_simple_2, path_simple_1)[1:] + expected = (2, 3) + assert_array_equal(actual, expected) + + def test_random_state(self): + # ensure that the global random state is not modified because + # the directed Hausdorff algorithm uses randomization + rs = check_random_state(None) + old_global_state = rs.get_state() + directed_hausdorff(self.path_1, self.path_2) + rs2 = check_random_state(None) + new_global_state = rs2.get_state() + assert_equal(new_global_state, old_global_state) + + @pytest.mark.parametrize("seed", [None, 27870671, np.random.default_rng(177)]) + def test_random_state_None_int(self, seed): + # check that seed values of None or int do not alter global + # random state + rs = check_random_state(None) + old_global_state = rs.get_state() + directed_hausdorff(self.path_1, self.path_2, seed) + rs2 = check_random_state(None) + new_global_state = rs2.get_state() + assert_equal(new_global_state, old_global_state) + + def test_invalid_dimensions(self): + # Ensure that a ValueError is raised when the number of columns + # is not the same + rng = np.random.default_rng(189048172503940875434364128139223470523) + A = rng.random((3, 2)) + B = rng.random((3, 5)) + msg = r"need to have the same number of columns" + with pytest.raises(ValueError, match=msg): + directed_hausdorff(A, B) + + # preserve use of legacy keyword `seed` during SPEC 7 transition + @pytest.mark.parametrize("A, B, seed, expected", [ + # the two cases from gh-11332 + ([(0,0)], + [(0,1), (0,0)], + np.int64(0), + (0.0, 0, 1)), + ([(0,0)], + [(0,1), (0,0)], + 1, + (0.0, 0, 1)), + # gh-11332 cases with a Generator + ([(0,0)], + [(0,1), (0,0)], + np.random.default_rng(0), + (0.0, 0, 1)), + ([(0,0)], + [(0,1), (0,0)], + np.random.default_rng(1), + (0.0, 0, 1)), + # slightly more complex case + ([(-5, 3), (0,0)], + [(0,1), (0,0), (-5, 3)], + 77098, + # the maximum minimum distance will + # be the last one found, but a unique + # solution is not guaranteed more broadly + (0.0, 1, 1)), + # repeated with Generator seeding + ([(-5, 3), (0,0)], + [(0,1), (0,0), (-5, 3)], + np.random.default_rng(77098), + # NOTE: using a Generator changes the + # indices but not the distance (unique solution + # not guaranteed) + (0.0, 0, 2)), + ]) + def test_subsets(self, A, B, seed, expected, num_parallel_threads): + # verify fix for gh-11332 + actual = directed_hausdorff(u=A, v=B, seed=seed) + # check distance + assert_allclose(actual[0], expected[0]) + starting_seed = seed + if hasattr(seed, 'bit_generator'): + starting_seed = seed.bit_generator._seed_seq.entropy + # check indices + if num_parallel_threads == 1 or starting_seed != 77098: + assert actual[1:] == expected[1:] + + if not isinstance(seed, np.random.RandomState): + # Check that new `rng` keyword is also accepted + actual = directed_hausdorff(u=A, v=B, rng=seed) + assert_allclose(actual[0], expected[0]) + + +@pytest.mark.xslow +def test_massive_arr_overflow(): + # on 64-bit systems we should be able to + # handle arrays that exceed the indexing + # size of a 32-bit signed integer + try: + import psutil + except ModuleNotFoundError: + pytest.skip("psutil required to check available memory") + if psutil.virtual_memory().available < 80*2**30: + # Don't run the test if there is less than 80 gig of RAM available. + pytest.skip('insufficient memory available to run this test') + size = int(3e9) + arr1 = np.zeros(shape=(size, 2)) + arr2 = np.zeros(shape=(3, 2)) + arr1[size - 1] = [5, 5] + actual = directed_hausdorff(u=arr1, v=arr2) + assert_allclose(actual[0], 7.0710678118654755) + assert_allclose(actual[1], size - 1) diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/tests/test_kdtree.py b/venv/lib/python3.12/site-packages/scipy/spatial/tests/test_kdtree.py new file mode 100644 index 0000000000000000000000000000000000000000..e86456e781f65adb9e43f0104a4ae92627f2109c --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/tests/test_kdtree.py @@ -0,0 +1,1636 @@ +# Copyright Anne M. Archibald 2008 +# Released under the scipy license + +import itertools +import os + +from numpy.testing import (assert_equal, assert_array_equal, assert_, + assert_almost_equal, assert_array_almost_equal, + assert_allclose) +from pytest import raises as assert_raises +import pytest +import numpy as np +from scipy._lib._testutils import _run_concurrent_barrier +from scipy.spatial import KDTree, Rectangle, distance_matrix, cKDTree +from scipy.spatial._ckdtree import cKDTreeNode +from scipy.spatial import minkowski_distance, minkowski_distance_p +from scipy.spatial.distance import cdist, minkowski +from scipy.sparse import dok_array, coo_array, dok_matrix, coo_matrix + + +@pytest.fixture(params=[KDTree, cKDTree]) +def kdtree_type(request): + return request.param + + +def KDTreeTest(kls): + """Class decorator to create test cases for KDTree and cKDTree + + Tests use the class variable ``kdtree_type`` as the tree constructor. + """ + if not kls.__name__.startswith('_Test'): + raise RuntimeError("Expected a class name starting with _Test") + + for tree in (KDTree, cKDTree): + test_name = kls.__name__[1:] + '_' + tree.__name__ + + if test_name in globals(): + raise RuntimeError("Duplicated test name: " + test_name) + + # Create a new sub-class with kdtree_type defined + test_case = type(test_name, (kls,), {'kdtree_type': tree}) + globals()[test_name] = test_case + return kls + + +def distance_box(a, b, p, boxsize): + diff = a - b + diff[diff > 0.5 * boxsize] -= boxsize + diff[diff < -0.5 * boxsize] += boxsize + d = minkowski(diff, 0, p) + return d + +class ConsistencyTests: + def distance(self, a, b, p): + return minkowski(a, b, p) + + def test_nearest(self): + x = self.x + d, i = self.kdtree.query(x, 1) + assert_almost_equal(d**2, np.sum((x-self.data[i])**2)) + eps = 1e-8 + assert_(np.all(np.sum((self.data-x[np.newaxis, :])**2, axis=1) > d**2-eps)) + + def test_m_nearest(self): + x = self.x + m = self.m + dd, ii = self.kdtree.query(x, m) + d = np.amax(dd) + i = ii[np.argmax(dd)] + assert_almost_equal(d**2, np.sum((x-self.data[i])**2)) + eps = 1e-8 + assert_equal( + np.sum(np.sum((self.data-x[np.newaxis, :])**2, axis=1) < d**2+eps), + m, + ) + + def test_points_near(self): + x = self.x + d = self.d + dd, ii = self.kdtree.query(x, k=self.kdtree.n, distance_upper_bound=d) + eps = 1e-8 + hits = 0 + for near_d, near_i in zip(dd, ii): + if near_d == np.inf: + continue + hits += 1 + assert_almost_equal(near_d**2, np.sum((x-self.data[near_i])**2)) + assert_(near_d < d+eps, f"near_d={near_d:g} should be less than {d:g}") + assert_equal(np.sum(self.distance(self.data, x, 2) < d**2+eps), hits) + + def test_points_near_l1(self): + x = self.x + d = self.d + dd, ii = self.kdtree.query(x, k=self.kdtree.n, p=1, distance_upper_bound=d) + eps = 1e-8 + hits = 0 + for near_d, near_i in zip(dd, ii): + if near_d == np.inf: + continue + hits += 1 + assert_almost_equal(near_d, self.distance(x, self.data[near_i], 1)) + assert_(near_d < d+eps, f"near_d={near_d:g} should be less than {d:g}") + assert_equal(np.sum(self.distance(self.data, x, 1) < d+eps), hits) + + def test_points_near_linf(self): + x = self.x + d = self.d + dd, ii = self.kdtree.query(x, k=self.kdtree.n, p=np.inf, distance_upper_bound=d) + eps = 1e-8 + hits = 0 + for near_d, near_i in zip(dd, ii): + if near_d == np.inf: + continue + hits += 1 + assert_almost_equal(near_d, self.distance(x, self.data[near_i], np.inf)) + assert_(near_d < d+eps, f"near_d={near_d:g} should be less than {d:g}") + assert_equal(np.sum(self.distance(self.data, x, np.inf) < d+eps), hits) + + def test_approx(self): + x = self.x + k = self.k + eps = 0.1 + d_real, i_real = self.kdtree.query(x, k) + d, i = self.kdtree.query(x, k, eps=eps) + assert_(np.all(d <= d_real*(1+eps))) + + +@KDTreeTest +class _Test_random(ConsistencyTests): + def setup_method(self): + self.n = 100 + self.m = 4 + np.random.seed(1234) + self.data = np.random.randn(self.n, self.m) + self.kdtree = self.kdtree_type(self.data, leafsize=2) + self.x = np.random.randn(self.m) + self.d = 0.2 + self.k = 10 + + +@KDTreeTest +class _Test_random_far(_Test_random): + def setup_method(self): + super().setup_method() + self.x = np.random.randn(self.m)+10 + + +@KDTreeTest +class _Test_small(ConsistencyTests): + def setup_method(self): + self.data = np.array([[0, 0, 0], + [0, 0, 1], + [0, 1, 0], + [0, 1, 1], + [1, 0, 0], + [1, 0, 1], + [1, 1, 0], + [1, 1, 1]]) + self.kdtree = self.kdtree_type(self.data) + self.n = self.kdtree.n + self.m = self.kdtree.m + np.random.seed(1234) + self.x = np.random.randn(3) + self.d = 0.5 + self.k = 4 + + def test_nearest(self): + assert_array_equal( + self.kdtree.query((0, 0, 0.1), 1), + (0.1, 0)) + + def test_nearest_two(self): + assert_array_equal( + self.kdtree.query((0, 0, 0.1), 2), + ([0.1, 0.9], [0, 1])) + + +@KDTreeTest +class _Test_small_nonleaf(_Test_small): + def setup_method(self): + super().setup_method() + self.kdtree = self.kdtree_type(self.data, leafsize=1) + + +class Test_vectorization_KDTree: + def setup_method(self): + self.data = np.array([[0, 0, 0], + [0, 0, 1], + [0, 1, 0], + [0, 1, 1], + [1, 0, 0], + [1, 0, 1], + [1, 1, 0], + [1, 1, 1]]) + self.kdtree = KDTree(self.data) + + def test_single_query(self): + d, i = self.kdtree.query(np.array([0, 0, 0])) + assert_(isinstance(d, float)) + assert_(np.issubdtype(i, np.signedinteger)) + + def test_vectorized_query(self): + d, i = self.kdtree.query(np.zeros((2, 4, 3))) + assert_equal(np.shape(d), (2, 4)) + assert_equal(np.shape(i), (2, 4)) + + def test_single_query_multiple_neighbors(self): + s = 23 + kk = self.kdtree.n+s + d, i = self.kdtree.query(np.array([0, 0, 0]), k=kk) + assert_equal(np.shape(d), (kk,)) + assert_equal(np.shape(i), (kk,)) + assert_(np.all(~np.isfinite(d[-s:]))) + assert_(np.all(i[-s:] == self.kdtree.n)) + + def test_vectorized_query_multiple_neighbors(self): + s = 23 + kk = self.kdtree.n+s + d, i = self.kdtree.query(np.zeros((2, 4, 3)), k=kk) + assert_equal(np.shape(d), (2, 4, kk)) + assert_equal(np.shape(i), (2, 4, kk)) + assert_(np.all(~np.isfinite(d[:, :, -s:]))) + assert_(np.all(i[:, :, -s:] == self.kdtree.n)) + + def test_query_raises_for_k_none(self): + x = 1.0 + with pytest.raises(ValueError, match="k must be an integer or*"): + self.kdtree.query(x, k=None) + +class Test_vectorization_cKDTree: + def setup_method(self): + self.data = np.array([[0, 0, 0], + [0, 0, 1], + [0, 1, 0], + [0, 1, 1], + [1, 0, 0], + [1, 0, 1], + [1, 1, 0], + [1, 1, 1]]) + self.kdtree = cKDTree(self.data) + + def test_single_query(self): + d, i = self.kdtree.query([0, 0, 0]) + assert_(isinstance(d, float)) + assert_(isinstance(i, int)) + + def test_vectorized_query(self): + d, i = self.kdtree.query(np.zeros((2, 4, 3))) + assert_equal(np.shape(d), (2, 4)) + assert_equal(np.shape(i), (2, 4)) + + def test_vectorized_query_noncontiguous_values(self): + np.random.seed(1234) + qs = np.random.randn(3, 1000).T + ds, i_s = self.kdtree.query(qs) + for q, d, i in zip(qs, ds, i_s): + assert_equal(self.kdtree.query(q), (d, i)) + + def test_single_query_multiple_neighbors(self): + s = 23 + kk = self.kdtree.n+s + d, i = self.kdtree.query([0, 0, 0], k=kk) + assert_equal(np.shape(d), (kk,)) + assert_equal(np.shape(i), (kk,)) + assert_(np.all(~np.isfinite(d[-s:]))) + assert_(np.all(i[-s:] == self.kdtree.n)) + + def test_vectorized_query_multiple_neighbors(self): + s = 23 + kk = self.kdtree.n+s + d, i = self.kdtree.query(np.zeros((2, 4, 3)), k=kk) + assert_equal(np.shape(d), (2, 4, kk)) + assert_equal(np.shape(i), (2, 4, kk)) + assert_(np.all(~np.isfinite(d[:, :, -s:]))) + assert_(np.all(i[:, :, -s:] == self.kdtree.n)) + +class ball_consistency: + tol = 0.0 + + def distance(self, a, b, p): + return minkowski(a * 1.0, b * 1.0, p) + + def test_in_ball(self): + x = np.atleast_2d(self.x) + d = np.broadcast_to(self.d, x.shape[:-1]) + l = self.T.query_ball_point(x, self.d, p=self.p, eps=self.eps) + for i, ind in enumerate(l): + dist = self.distance(self.data[ind], x[i], self.p) - d[i]*(1.+self.eps) + norm = self.distance(self.data[ind], x[i], self.p) + d[i]*(1.+self.eps) + assert_array_equal(dist < self.tol * norm, True) + + def test_found_all(self): + x = np.atleast_2d(self.x) + d = np.broadcast_to(self.d, x.shape[:-1]) + l = self.T.query_ball_point(x, self.d, p=self.p, eps=self.eps) + for i, ind in enumerate(l): + c = np.ones(self.T.n, dtype=bool) + c[ind] = False + dist = self.distance(self.data[c], x[i], self.p) - d[i]/(1.+self.eps) + norm = self.distance(self.data[c], x[i], self.p) + d[i]/(1.+self.eps) + assert_array_equal(dist > -self.tol * norm, True) + +@KDTreeTest +class _Test_random_ball(ball_consistency): + def setup_method(self): + n = 100 + m = 4 + np.random.seed(1234) + self.data = np.random.randn(n, m) + self.T = self.kdtree_type(self.data, leafsize=2) + self.x = np.random.randn(m) + self.p = 2. + self.eps = 0 + self.d = 0.2 + + +@KDTreeTest +class _Test_random_ball_periodic(ball_consistency): + def distance(self, a, b, p): + return distance_box(a, b, p, 1.0) + + def setup_method(self): + n = 10000 + m = 4 + np.random.seed(1234) + self.data = np.random.uniform(size=(n, m)) + self.T = self.kdtree_type(self.data, leafsize=2, boxsize=1) + self.x = np.full(m, 0.1) + self.p = 2. + self.eps = 0 + self.d = 0.2 + + def test_in_ball_outside(self): + l = self.T.query_ball_point(self.x + 1.0, self.d, p=self.p, eps=self.eps) + for i in l: + assert_(self.distance(self.data[i], self.x, self.p) <= self.d*(1.+self.eps)) + l = self.T.query_ball_point(self.x - 1.0, self.d, p=self.p, eps=self.eps) + for i in l: + assert_(self.distance(self.data[i], self.x, self.p) <= self.d*(1.+self.eps)) + + def test_found_all_outside(self): + c = np.ones(self.T.n, dtype=bool) + l = self.T.query_ball_point(self.x + 1.0, self.d, p=self.p, eps=self.eps) + c[l] = False + assert np.all( + self.distance(self.data[c], self.x, self.p) >= self.d/(1.+self.eps) + ) + + l = self.T.query_ball_point(self.x - 1.0, self.d, p=self.p, eps=self.eps) + c[l] = False + assert np.all( + self.distance(self.data[c], self.x, self.p) >= self.d/(1.+self.eps) + ) + + +@KDTreeTest +class _Test_random_ball_largep_issue9890(ball_consistency): + + # allow some roundoff errors due to numerical issues + tol = 1e-13 + + def setup_method(self): + n = 1000 + m = 2 + np.random.seed(123) + self.data = np.random.randint(100, 1000, size=(n, m)) + self.T = self.kdtree_type(self.data) + self.x = self.data + self.p = 100 + self.eps = 0 + self.d = 10 + + +@KDTreeTest +class _Test_random_ball_approx(_Test_random_ball): + + def setup_method(self): + super().setup_method() + self.eps = 0.1 + + +@KDTreeTest +class _Test_random_ball_approx_periodic(_Test_random_ball): + + def setup_method(self): + super().setup_method() + self.eps = 0.1 + + +@KDTreeTest +class _Test_random_ball_far(_Test_random_ball): + + def setup_method(self): + super().setup_method() + self.d = 2. + +@KDTreeTest +class _Test_random_ball_far_periodic(_Test_random_ball_periodic): + + def setup_method(self): + super().setup_method() + self.d = 2. + + +@KDTreeTest +class _Test_random_ball_l1(_Test_random_ball): + + def setup_method(self): + super().setup_method() + self.p = 1 + + +@KDTreeTest +class _Test_random_ball_linf(_Test_random_ball): + + def setup_method(self): + super().setup_method() + self.p = np.inf + + +def test_random_ball_vectorized(kdtree_type): + n = 20 + m = 5 + np.random.seed(1234) + T = kdtree_type(np.random.randn(n, m)) + + r = T.query_ball_point(np.random.randn(2, 3, m), 1) + assert_equal(r.shape, (2, 3)) + assert_(isinstance(r[0, 0], list)) + + +@pytest.mark.thread_unsafe(reason="Test spawns worker threads") +@pytest.mark.fail_slow(5) +def test_query_ball_point_multithreaded_workers(kdtree_type): + np.random.seed(0) + n = 5000 + k = 2 + points = np.random.randn(n, k) + T = kdtree_type(points) + l1 = T.query_ball_point(points, 0.003, workers=1) + l2 = T.query_ball_point(points, 0.003, workers=64) + l3 = T.query_ball_point(points, 0.003, workers=-1) + + for i in range(n): + if l1[i] or l2[i]: + assert_array_equal(l1[i], l2[i]) + + for i in range(n): + if l1[i] or l3[i]: + assert_array_equal(l1[i], l3[i]) + + +@pytest.mark.thread_unsafe(reason="Test spawns worker threads") +@pytest.mark.fail_slow(5) +def test_query_ball_point_multithreaded_explicit(kdtree_type): + rng = np.random.RandomState(3819232613) + n = 10000 + k = 2 + + points = rng.randn(n, k) + tree = kdtree_type(points) + max_workers = 10 + assert(n//max_workers * max_workers == n) + search_points = rng.randn(max_workers, n//max_workers, k) + all_search_points = np.reshape(search_points, (n, k)) + radius = 0.3 + + def worker_func(i, tree, search_points): + return tree.query_ball_point(search_points[i], radius) + + results = _run_concurrent_barrier( + max_workers, worker_func, tree, search_points) + + serial_results = tree.query_ball_point(all_search_points, radius) + # the results from concurrent searching the tree should match the results + # from searching the same set of points in one thread + assert_array_equal(np.sort(np.concatenate(results)), np.sort(serial_results)) + + +class two_trees_consistency: + + def distance(self, a, b, p): + return minkowski(a, b, p) + + def test_all_in_ball(self): + r = self.T1.query_ball_tree(self.T2, self.d, p=self.p, eps=self.eps) + for i, l in enumerate(r): + for j in l: + assert (self.distance(self.data1[i], self.data2[j], self.p) + <= self.d*(1.+self.eps)) + + def test_found_all(self): + r = self.T1.query_ball_tree(self.T2, self.d, p=self.p, eps=self.eps) + for i, l in enumerate(r): + c = np.ones(self.T2.n, dtype=bool) + c[l] = False + assert np.all(self.distance(self.data2[c], self.data1[i], self.p) + >= self.d/(1.+self.eps)) + + +@KDTreeTest +class _Test_two_random_trees(two_trees_consistency): + + def setup_method(self): + n = 50 + m = 4 + np.random.seed(1234) + self.data1 = np.random.randn(n, m) + self.T1 = self.kdtree_type(self.data1, leafsize=2) + self.data2 = np.random.randn(n, m) + self.T2 = self.kdtree_type(self.data2, leafsize=2) + self.p = 2. + self.eps = 0 + self.d = 0.2 + + +@KDTreeTest +class _Test_two_random_trees_periodic(two_trees_consistency): + def distance(self, a, b, p): + return distance_box(a, b, p, 1.0) + + def setup_method(self): + n = 50 + m = 4 + np.random.seed(1234) + self.data1 = np.random.uniform(size=(n, m)) + self.T1 = self.kdtree_type(self.data1, leafsize=2, boxsize=1.0) + self.data2 = np.random.uniform(size=(n, m)) + self.T2 = self.kdtree_type(self.data2, leafsize=2, boxsize=1.0) + self.p = 2. + self.eps = 0 + self.d = 0.2 + + +@KDTreeTest +class _Test_two_random_trees_far(_Test_two_random_trees): + + def setup_method(self): + super().setup_method() + self.d = 2 + + +@KDTreeTest +class _Test_two_random_trees_far_periodic(_Test_two_random_trees_periodic): + + def setup_method(self): + super().setup_method() + self.d = 2 + + +@KDTreeTest +class _Test_two_random_trees_linf(_Test_two_random_trees): + + def setup_method(self): + super().setup_method() + self.p = np.inf + + +@KDTreeTest +class _Test_two_random_trees_linf_periodic(_Test_two_random_trees_periodic): + + def setup_method(self): + super().setup_method() + self.p = np.inf + + +class Test_rectangle: + + def setup_method(self): + self.rect = Rectangle([0, 0], [1, 1]) + + def test_min_inside(self): + assert_almost_equal(self.rect.min_distance_point([0.5, 0.5]), 0) + + def test_min_one_side(self): + assert_almost_equal(self.rect.min_distance_point([0.5, 1.5]), 0.5) + + def test_min_two_sides(self): + assert_almost_equal(self.rect.min_distance_point([2, 2]), np.sqrt(2)) + + def test_max_inside(self): + assert_almost_equal(self.rect.max_distance_point([0.5, 0.5]), 1/np.sqrt(2)) + + def test_max_one_side(self): + assert_almost_equal(self.rect.max_distance_point([0.5, 1.5]), + np.hypot(0.5, 1.5)) + + def test_max_two_sides(self): + assert_almost_equal(self.rect.max_distance_point([2, 2]), 2*np.sqrt(2)) + + def test_split(self): + less, greater = self.rect.split(0, 0.1) + assert_array_equal(less.maxes, [0.1, 1]) + assert_array_equal(less.mins, [0, 0]) + assert_array_equal(greater.maxes, [1, 1]) + assert_array_equal(greater.mins, [0.1, 0]) + + +def test_distance_l2(): + with pytest.deprecated_call(match="1.20.0"): + assert_almost_equal(minkowski_distance([0, 0], [1, 1], 2), np.sqrt(2)) + with pytest.deprecated_call(match="1.20.0"): + assert_almost_equal(minkowski_distance_p([0, 0], [1, 1], 2), 2) + + +def test_distance_l1(): + with pytest.deprecated_call(match="1.20.0"): + assert_almost_equal(minkowski_distance([0, 0], [1, 1], 1), 2) + + +def test_distance_linf(): + with pytest.deprecated_call(match="1.20.0"): + assert_almost_equal(minkowski_distance([0, 0], [1, 1], np.inf), 1) + + +def test_distance_vectorization(): + np.random.seed(1234) + x = np.random.randn(10, 1, 3) + y = np.random.randn(1, 7, 3) + with pytest.deprecated_call(match="1.20.0"): + assert_equal(minkowski_distance(x, y).shape, (10, 7)) + + +class count_neighbors_consistency: + def test_one_radius(self): + r = 0.2 + assert_equal(self.T1.count_neighbors(self.T2, r), + np.sum([len(l) for l in self.T1.query_ball_tree(self.T2, r)])) + + def test_large_radius(self): + r = 1000 + assert_equal(self.T1.count_neighbors(self.T2, r), + np.sum([len(l) for l in self.T1.query_ball_tree(self.T2, r)])) + + def test_multiple_radius(self): + rs = np.exp(np.linspace(np.log(0.01), np.log(10), 3)) + results = self.T1.count_neighbors(self.T2, rs) + assert_(np.all(np.diff(results) >= 0)) + for r, result in zip(rs, results): + assert_equal(self.T1.count_neighbors(self.T2, r), result) + +@KDTreeTest +class _Test_count_neighbors(count_neighbors_consistency): + def setup_method(self): + n = 50 + m = 2 + np.random.seed(1234) + self.T1 = self.kdtree_type(np.random.randn(n, m), leafsize=2) + self.T2 = self.kdtree_type(np.random.randn(n, m), leafsize=2) + + +class sparse_distance_matrix_consistency: + + def distance(self, a, b, p): + return minkowski(a, b, p) + + def test_consistency_with_neighbors(self): + M = self.T1.sparse_distance_matrix(self.T2, self.r, output_type='dok_array') + r = self.T1.query_ball_tree(self.T2, self.r) + for i, l in enumerate(r): + for j in l: + assert_almost_equal( + M[i, j], + self.distance(self.T1.data[i], self.T2.data[j], self.p), + decimal=14 + ) + for ((i, j), d) in M.items(): + assert_(j in r[i]) + + def test_zero_distance(self): + # raises an exception for bug 870 (FIXME: Does it?) + self.T1.sparse_distance_matrix(self.T1, self.r, output_type='dok_array') + + def test_consistency(self): + # Test consistency with a distance_matrix + M1 = self.T1.sparse_distance_matrix(self.T2, self.r, output_type='dok_array') + expected = cdist(self.T1.data, self.T2.data) + expected[expected > self.r] = 0 + assert_array_almost_equal(M1.toarray(), expected, decimal=14) + + def test_against_logic_error_regression(self): + # regression test for gh-5077 logic error + np.random.seed(0) + too_many = np.array(np.random.randn(18, 2), dtype=int) + tree = self.kdtree_type( + too_many, balanced_tree=False, compact_nodes=False) + d = tree.sparse_distance_matrix(tree, 3, output_type='dok_array').toarray() + assert_array_almost_equal(d, d.T, decimal=14) + + def test_ckdtree_return_types(self): + # brute-force reference + ref = np.zeros((self.n, self.n)) + for i in range(self.n): + for j in range(self.n): + v = self.data1[i, :] - self.data2[j, :] + ref[i, j] = np.dot(v, v) + ref = np.sqrt(ref) + ref[ref > self.r] = 0. + # test return type 'dict' + dist = np.zeros((self.n, self.n)) + r = self.T1.sparse_distance_matrix(self.T2, self.r, output_type='dict') + for i, j in r.keys(): + dist[i, j] = r[(i, j)] + assert_array_almost_equal(ref, dist, decimal=14) + # test return type 'ndarray' + dist = np.zeros((self.n, self.n)) + r = self.T1.sparse_distance_matrix(self.T2, self.r, + output_type='ndarray') + for k in range(r.shape[0]): + i = r['i'][k] + j = r['j'][k] + v = r['v'][k] + dist[i, j] = v + assert_array_almost_equal(ref, dist, decimal=14) + # test return type 'dok_array' + r = self.T1.sparse_distance_matrix(self.T2, self.r, + output_type='dok_array') + assert_array_almost_equal(ref, r.toarray(), decimal=14) + assert isinstance(r, dok_array) + # test return type 'dok_matrix' + r = self.T1.sparse_distance_matrix(self.T2, self.r, + output_type='dok_matrix') + assert_array_almost_equal(ref, r.toarray(), decimal=14) + assert isinstance(r, dok_matrix) + # test return type 'coo_array' + r = self.T1.sparse_distance_matrix(self.T2, self.r, + output_type='coo_array') + assert_array_almost_equal(ref, r.toarray(), decimal=14) + assert isinstance(r, coo_array) + # test return type 'coo_matrix' + r = self.T1.sparse_distance_matrix(self.T2, self.r, + output_type='coo_matrix') + assert_array_almost_equal(ref, r.toarray(), decimal=14) + assert isinstance(r, coo_matrix) + # test default return type 'dok_matrix' + r = self.T1.sparse_distance_matrix(self.T2, self.r) + assert isinstance(r, dok_matrix) + + +@KDTreeTest +class _Test_sparse_distance_matrix(sparse_distance_matrix_consistency): + def setup_method(self): + n = 50 + m = 4 + np.random.seed(1234) + data1 = np.random.randn(n, m) + data2 = np.random.randn(n, m) + self.T1 = self.kdtree_type(data1, leafsize=2) + self.T2 = self.kdtree_type(data2, leafsize=2) + self.r = 0.5 + self.p = 2 + self.data1 = data1 + self.data2 = data2 + self.n = n + self.m = m + + +def test_distance_matrix(): + m = 10 + n = 11 + k = 4 + np.random.seed(1234) + xs = np.random.randn(m, k) + ys = np.random.randn(n, k) + with pytest.deprecated_call(match="1.20.0"): + ds = distance_matrix(xs, ys) + assert_equal(ds.shape, (m, n)) + for i in range(m): + for j in range(n): + with pytest.deprecated_call(match="1.20.0"): + assert_almost_equal(minkowski_distance(xs[i], ys[j]), ds[i, j]) + + +def test_distance_matrix_looping(): + m = 10 + n = 11 + k = 4 + np.random.seed(1234) + xs = np.random.randn(m, k) + ys = np.random.randn(n, k) + with pytest.deprecated_call(match="1.20.0"): + ds = distance_matrix(xs, ys) + with pytest.deprecated_call(match="1.20.0"): + dsl = distance_matrix(xs, ys, threshold=1) + assert_equal(ds, dsl) + + +def check_onetree_query(T, d): + r = T.query_ball_tree(T, d) + s = set() + for i, l in enumerate(r): + for j in l: + if i < j: + s.add((i, j)) + + assert_(s == T.query_pairs(d)) + +def test_onetree_query(kdtree_type): + np.random.seed(0) + n = 50 + k = 4 + points = np.random.randn(n, k) + T = kdtree_type(points) + check_onetree_query(T, 0.1) + + points = np.random.randn(3*n, k) + points[:n] *= 0.001 + points[n:2*n] += 2 + T = kdtree_type(points) + check_onetree_query(T, 0.1) + check_onetree_query(T, 0.001) + check_onetree_query(T, 0.00001) + check_onetree_query(T, 1e-6) + + +def test_query_pairs_single_node(kdtree_type): + tree = kdtree_type([[0, 1]]) + assert_equal(tree.query_pairs(0.5), set()) + + +def test_kdtree_query_pairs(kdtree_type): + np.random.seed(0) + n = 50 + k = 2 + r = 0.1 + r2 = r**2 + points = np.random.randn(n, k) + T = kdtree_type(points) + # brute force reference + brute = set() + for i in range(n): + for j in range(i+1, n): + v = points[i, :] - points[j, :] + if np.dot(v, v) <= r2: + brute.add((i, j)) + l0 = sorted(brute) + # test default return type + s = T.query_pairs(r) + l1 = sorted(s) + assert_array_equal(l0, l1) + # test return type 'set' + s = T.query_pairs(r, output_type='set') + l1 = sorted(s) + assert_array_equal(l0, l1) + # test return type 'ndarray' + s = set() + arr = T.query_pairs(r, output_type='ndarray') + for i in range(arr.shape[0]): + s.add((int(arr[i, 0]), int(arr[i, 1]))) + l2 = sorted(s) + assert_array_equal(l0, l2) + + +def test_query_pairs_eps(kdtree_type): + spacing = np.sqrt(2) + # irrational spacing to have potential rounding errors + x_range = np.linspace(0, 3 * spacing, 4) + y_range = np.linspace(0, 3 * spacing, 4) + xy_array = [(xi, yi) for xi in x_range for yi in y_range] + tree = kdtree_type(xy_array) + pairs_eps = tree.query_pairs(r=spacing, eps=.1) + # result: 24 with eps, 16 without due to rounding + pairs = tree.query_pairs(r=spacing * 1.01) + # result: 24 + assert_equal(pairs, pairs_eps) + + +def test_ball_point_ints(kdtree_type): + # Regression test for #1373. + x, y = np.mgrid[0:4, 0:4] + points = list(zip(x.ravel(), y.ravel())) + tree = kdtree_type(points) + assert_equal(sorted([4, 8, 9, 12]), + sorted(tree.query_ball_point((2, 0), 1))) + points = np.asarray(points, dtype=float) + tree = kdtree_type(points) + assert_equal(sorted([4, 8, 9, 12]), + sorted(tree.query_ball_point((2, 0), 1))) + + +def test_kdtree_comparisons(): + # Regression test: node comparisons were done wrong in 0.12 w/Py3. + nodes = [KDTree.node() for _ in range(3)] + assert_equal(sorted(nodes), sorted(nodes[::-1])) + + +def test_kdtree_build_modes(kdtree_type): + # check if different build modes for KDTree give similar query results + np.random.seed(0) + n = 5000 + k = 4 + points = np.random.randn(n, k) + T1 = kdtree_type(points).query(points, k=5)[-1] + T2 = kdtree_type(points, compact_nodes=False).query(points, k=5)[-1] + T3 = kdtree_type(points, balanced_tree=False).query(points, k=5)[-1] + T4 = kdtree_type(points, compact_nodes=False, + balanced_tree=False).query(points, k=5)[-1] + assert_array_equal(T1, T2) + assert_array_equal(T1, T3) + assert_array_equal(T1, T4) + +def test_kdtree_pickle(kdtree_type): + # test if it is possible to pickle a KDTree + import pickle + np.random.seed(0) + n = 50 + k = 4 + points = np.random.randn(n, k) + T1 = kdtree_type(points) + tmp = pickle.dumps(T1) + T2 = pickle.loads(tmp) + T1 = T1.query(points, k=5)[-1] + T2 = T2.query(points, k=5)[-1] + assert_array_equal(T1, T2) + +def test_kdtree_pickle_boxsize(kdtree_type): + # test if it is possible to pickle a periodic KDTree + import pickle + np.random.seed(0) + n = 50 + k = 4 + points = np.random.uniform(size=(n, k)) + T1 = kdtree_type(points, boxsize=1.0) + tmp = pickle.dumps(T1) + T2 = pickle.loads(tmp) + T1 = T1.query(points, k=5)[-1] + T2 = T2.query(points, k=5)[-1] + assert_array_equal(T1, T2) + +def test_kdtree_copy_data(kdtree_type): + # check if copy_data=True makes the kd-tree + # impervious to data corruption by modification of + # the data arrray + np.random.seed(0) + n = 5000 + k = 4 + points = np.random.randn(n, k) + T = kdtree_type(points, copy_data=True) + q = points.copy() + T1 = T.query(q, k=5)[-1] + points[...] = np.random.randn(n, k) + T2 = T.query(q, k=5)[-1] + assert_array_equal(T1, T2) + +def test_ckdtree_parallel(kdtree_type, monkeypatch): + # check if parallel=True also generates correct query results + np.random.seed(0) + n = 5000 + k = 4 + points = np.random.randn(n, k) + T = kdtree_type(points) + T1 = T.query(points, k=5, workers=64)[-1] + T2 = T.query(points, k=5, workers=-1)[-1] + T3 = T.query(points, k=5)[-1] + assert_array_equal(T1, T2) + assert_array_equal(T1, T3) + + monkeypatch.setattr(os, 'cpu_count', lambda: None) + with pytest.raises(NotImplementedError, match="Cannot determine the"): + T.query(points, 1, workers=-1) + + +def test_ckdtree_view(): + # Check that the nodes can be correctly viewed from Python. + # This test also sanity checks each node in the cKDTree, and + # thus verifies the internal structure of the kd-tree. + np.random.seed(0) + n = 100 + k = 4 + points = np.random.randn(n, k) + kdtree = cKDTree(points) + + # walk the whole kd-tree and sanity check each node + def recurse_tree(n): + assert_(isinstance(n, cKDTreeNode)) + if n.split_dim == -1: + assert_(n.lesser is None) + assert_(n.greater is None) + assert_(n.indices.shape[0] <= kdtree.leafsize) + else: + recurse_tree(n.lesser) + recurse_tree(n.greater) + x = n.lesser.data_points[:, n.split_dim] + y = n.greater.data_points[:, n.split_dim] + assert_(x.max() < y.min()) + + recurse_tree(kdtree.tree) + # check that indices are correctly retrieved + n = kdtree.tree + assert_array_equal(np.sort(n.indices), range(100)) + # check that data_points are correctly retrieved + assert_array_equal(kdtree.data[n.indices, :], n.data_points) + +# KDTree is specialized to type double points, so no need to make +# a unit test corresponding to test_ball_point_ints() + +def test_kdtree_list_k(kdtree_type): + # check kdtree periodic boundary + n = 200 + m = 2 + klist = [1, 2, 3] + kint = 3 + + np.random.seed(1234) + data = np.random.uniform(size=(n, m)) + kdtree = kdtree_type(data, leafsize=1) + + # check agreement between arange(1, k+1) and k + dd, ii = kdtree.query(data, klist) + dd1, ii1 = kdtree.query(data, kint) + assert_equal(dd, dd1) + assert_equal(ii, ii1) + + # now check skipping one element + klist = np.array([1, 3]) + kint = 3 + dd, ii = kdtree.query(data, kint) + dd1, ii1 = kdtree.query(data, klist) + assert_equal(dd1, dd[..., klist - 1]) + assert_equal(ii1, ii[..., klist - 1]) + + # check k == 1 special case + # and k == [1] non-special case + dd, ii = kdtree.query(data, 1) + dd1, ii1 = kdtree.query(data, [1]) + assert_equal(len(dd.shape), 1) + assert_equal(len(dd1.shape), 2) + assert_equal(dd, np.ravel(dd1)) + assert_equal(ii, np.ravel(ii1)) + +@pytest.mark.fail_slow(10) +def test_kdtree_box(kdtree_type): + # check ckdtree periodic boundary + n = 2000 + m = 3 + k = 3 + np.random.seed(1234) + data = np.random.uniform(size=(n, m)) + kdtree = kdtree_type(data, leafsize=1, boxsize=1.0) + + # use the standard python KDTree for the simulated periodic box + kdtree2 = kdtree_type(data, leafsize=1) + + for p in [1, 2, 3.0, np.inf]: + dd, ii = kdtree.query(data, k, p=p) + + dd1, ii1 = kdtree.query(data + 1.0, k, p=p) + assert_almost_equal(dd, dd1) + assert_equal(ii, ii1) + + dd1, ii1 = kdtree.query(data - 1.0, k, p=p) + assert_almost_equal(dd, dd1) + assert_equal(ii, ii1) + + dd2, ii2 = simulate_periodic_box(kdtree2, data, k, boxsize=1.0, p=p) + assert_almost_equal(dd, dd2) + assert_equal(ii, ii2) + +def test_kdtree_box_0boxsize(kdtree_type): + # check ckdtree periodic boundary that mimics non-periodic + n = 2000 + m = 2 + k = 3 + np.random.seed(1234) + data = np.random.uniform(size=(n, m)) + kdtree = kdtree_type(data, leafsize=1, boxsize=0.0) + + # use the standard python KDTree for the simulated periodic box + kdtree2 = kdtree_type(data, leafsize=1) + + for p in [1, 2, np.inf]: + dd, ii = kdtree.query(data, k, p=p) + + dd1, ii1 = kdtree2.query(data, k, p=p) + assert_almost_equal(dd, dd1) + assert_equal(ii, ii1) + +def test_kdtree_box_upper_bounds(kdtree_type): + data = np.linspace(0, 2, 10).reshape(-1, 2) + data[:, 1] += 10 + with pytest.raises(ValueError): + kdtree_type(data, leafsize=1, boxsize=1.0) + with pytest.raises(ValueError): + kdtree_type(data, leafsize=1, boxsize=(0.0, 2.0)) + # skip a dimension. + kdtree_type(data, leafsize=1, boxsize=(2.0, 0.0)) + +def test_kdtree_box_lower_bounds(kdtree_type): + data = np.linspace(-1, 1, 10) + assert_raises(ValueError, kdtree_type, data, leafsize=1, boxsize=1.0) + +def simulate_periodic_box(kdtree, data, k, boxsize, p): + dd = [] + ii = [] + x = np.arange(3 ** data.shape[1]) + nn = np.array(np.unravel_index(x, [3] * data.shape[1])).T + nn = nn - 1.0 + for n in nn: + image = data + n * 1.0 * boxsize + dd2, ii2 = kdtree.query(image, k, p=p) + dd2 = dd2.reshape(-1, k) + ii2 = ii2.reshape(-1, k) + dd.append(dd2) + ii.append(ii2) + dd = np.concatenate(dd, axis=-1) + ii = np.concatenate(ii, axis=-1) + + result = np.empty([len(data), len(nn) * k], dtype=[ + ('ii', 'i8'), + ('dd', 'f8')]) + result['ii'][:] = ii + result['dd'][:] = dd + result.sort(order='dd') + return result['dd'][:, :k], result['ii'][:, :k] + + +@pytest.mark.fail_asan +def test_ckdtree_memuse(): + # unit test adaptation of gh-5630 + + # NOTE: this will fail when run via valgrind, + # because rss is no longer a reliable memory usage indicator. + # It was also observed to be flaky under ASan. + + try: + import resource + except ImportError: + # resource is not available on Windows + return + # Make some data + dx, dy = 0.05, 0.05 + y, x = np.mgrid[slice(1, 5 + dy, dy), + slice(1, 5 + dx, dx)] + z = np.sin(x)**10 + np.cos(10 + y*x) * np.cos(x) + z_copy = np.empty_like(z) + z_copy[:] = z + # Place FILLVAL in z_copy at random number of random locations + FILLVAL = 99. + mask = np.random.randint(0, z.size, np.random.randint(50) + 5) + z_copy.flat[mask] = FILLVAL + igood = np.vstack(np.nonzero(x != FILLVAL)).T + ibad = np.vstack(np.nonzero(x == FILLVAL)).T + mem_use = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss + # burn-in + for i in range(10): + tree = cKDTree(igood) + # count memleaks while constructing and querying cKDTree + num_leaks = 0 + for i in range(100): + mem_use = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss + tree = cKDTree(igood) + dist, iquery = tree.query(ibad, k=4, p=2) + new_mem_use = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss + if new_mem_use > mem_use: + num_leaks += 1 + # ideally zero leaks, but errors might accidentally happen + # outside cKDTree + assert_(num_leaks < 10) + +def test_kdtree_weights(kdtree_type): + + data = np.linspace(0, 1, 4).reshape(-1, 1) + tree1 = kdtree_type(data, leafsize=1) + weights = np.ones(len(data), dtype='f4') + + nw = tree1._build_weights(weights) + assert_array_equal(nw, [4, 2, 1, 1, 2, 1, 1]) + + assert_raises(ValueError, tree1._build_weights, weights[:-1]) + + for i in range(10): + # since weights are uniform, these shall agree: + c1 = tree1.count_neighbors(tree1, np.linspace(0, 10, i)) + c2 = tree1.count_neighbors(tree1, np.linspace(0, 10, i), + weights=(weights, weights)) + c3 = tree1.count_neighbors(tree1, np.linspace(0, 10, i), + weights=(weights, None)) + c4 = tree1.count_neighbors(tree1, np.linspace(0, 10, i), + weights=(None, weights)) + tree1.count_neighbors(tree1, np.linspace(0, 10, i), + weights=weights) + + assert_array_equal(c1, c2) + assert_array_equal(c1, c3) + assert_array_equal(c1, c4) + + for i in range(len(data)): + # this tests removal of one data point by setting weight to 0 + w1 = weights.copy() + w1[i] = 0 + data2 = data[w1 != 0] + tree2 = kdtree_type(data2) + + c1 = tree1.count_neighbors(tree1, np.linspace(0, 10, 100), + weights=(w1, w1)) + # "c2 is correct" + c2 = tree2.count_neighbors(tree2, np.linspace(0, 10, 100)) + + assert_array_equal(c1, c2) + + #this asserts for two different trees, singular weights + # crashes + assert_raises(ValueError, tree1.count_neighbors, + tree2, np.linspace(0, 10, 100), weights=w1) + +@pytest.mark.fail_slow(10) +def test_kdtree_count_neighbous_multiple_r(kdtree_type): + n = 2000 + m = 2 + np.random.seed(1234) + data = np.random.normal(size=(n, m)) + kdtree = kdtree_type(data, leafsize=1) + r0 = [0, 0.01, 0.01, 0.02, 0.05] + i0 = np.arange(len(r0)) + n0 = kdtree.count_neighbors(kdtree, r0) + nnc = kdtree.count_neighbors(kdtree, r0, cumulative=False) + assert_equal(n0, nnc.cumsum()) + + for i, r in zip(itertools.permutations(i0), + itertools.permutations(r0)): + # permute n0 by i and it shall agree + n = kdtree.count_neighbors(kdtree, r) + assert_array_equal(n, n0[list(i)]) + +def test_len0_arrays(kdtree_type): + # make sure len-0 arrays are handled correctly + # in range queries (gh-5639) + rng = np.random.RandomState(1234) + X = rng.rand(10, 2) + Y = rng.rand(10, 2) + tree = kdtree_type(X) + # query_ball_point (single) + d, i = tree.query([.5, .5], k=1) + z = tree.query_ball_point([.5, .5], 0.1*d) + assert_array_equal(z, []) + # query_ball_point (multiple) + d, i = tree.query(Y, k=1) + mind = d.min() + z = tree.query_ball_point(Y, 0.1*mind) + y = np.empty(shape=(10, ), dtype=object) + y.fill([]) + assert_array_equal(y, z) + # query_ball_tree + other = kdtree_type(Y) + y = tree.query_ball_tree(other, 0.1*mind) + assert_array_equal(10*[[]], y) + # count_neighbors + y = tree.count_neighbors(other, 0.1*mind) + assert_(y == 0) + # sparse_distance_matrix + y = tree.sparse_distance_matrix(other, 0.1*mind, output_type='dok_array') + assert_array_equal(y == np.zeros((10, 10)), True) + y = tree.sparse_distance_matrix(other, 0.1*mind, output_type='coo_array') + assert_array_equal(y == np.zeros((10, 10)), True) + y = tree.sparse_distance_matrix(other, 0.1*mind, output_type='dict') + assert_equal(y, {}) + y = tree.sparse_distance_matrix(other, 0.1*mind, output_type='ndarray') + _dtype = [('i', np.intp), ('j', np.intp), ('v', np.float64)] + res_dtype = np.dtype(_dtype, align=True) + z = np.empty(shape=(0, ), dtype=res_dtype) + assert_array_equal(y, z) + # query_pairs + d, i = tree.query(X, k=2) + mind = d[:, -1].min() + y = tree.query_pairs(0.1*mind, output_type='set') + assert_equal(y, set()) + y = tree.query_pairs(0.1*mind, output_type='ndarray') + z = np.empty(shape=(0, 2), dtype=np.intp) + assert_array_equal(y, z) + +def test_kdtree_duplicated_inputs(kdtree_type): + # check kdtree with duplicated inputs + n = 1024 + for m in range(1, 8): + data = np.ones((n, m)) + data[n//2:] = 2 + + for balanced, compact in itertools.product((False, True), repeat=2): + kdtree = kdtree_type(data, balanced_tree=balanced, + compact_nodes=compact, leafsize=1) + assert kdtree.size == 3 + + tree = (kdtree.tree if kdtree_type is cKDTree else + kdtree.tree._node) + + assert_equal( + np.sort(tree.lesser.indices), + np.arange(0, n // 2)) + assert_equal( + np.sort(tree.greater.indices), + np.arange(n // 2, n)) + + +def test_kdtree_noncumulative_nondecreasing(kdtree_type): + # check kdtree with duplicated inputs + + # it shall not divide more than 3 nodes. + # root left (1), and right (2) + kdtree = kdtree_type([[0]], leafsize=1) + + assert_raises(ValueError, kdtree.count_neighbors, + kdtree, [0.1, 0], cumulative=False) + +def test_short_knn(kdtree_type): + + # The test case is based on github: #6425 by @SteveDoyle2 + + xyz = np.array([ + [0., 0., 0.], + [1.01, 0., 0.], + [0., 1., 0.], + [0., 1.01, 0.], + [1., 0., 0.], + [1., 1., 0.]], + dtype='float64') + + ckdt = kdtree_type(xyz) + + deq, ieq = ckdt.query(xyz, k=4, distance_upper_bound=0.2) + + assert_array_almost_equal(deq, + [[0., np.inf, np.inf, np.inf], + [0., 0.01, np.inf, np.inf], + [0., 0.01, np.inf, np.inf], + [0., 0.01, np.inf, np.inf], + [0., 0.01, np.inf, np.inf], + [0., np.inf, np.inf, np.inf]]) + +def test_query_ball_point_vector_r(kdtree_type): + + np.random.seed(1234) + data = np.random.normal(size=(100, 3)) + query = np.random.normal(size=(100, 3)) + tree = kdtree_type(data) + d = np.random.uniform(0, 0.3, size=len(query)) + + rvector = tree.query_ball_point(query, d) + rscalar = [tree.query_ball_point(qi, di) for qi, di in zip(query, d)] + for a, b in zip(rvector, rscalar): + assert_array_equal(sorted(a), sorted(b)) + +def test_query_ball_point_length(kdtree_type): + + np.random.seed(1234) + data = np.random.normal(size=(100, 3)) + query = np.random.normal(size=(100, 3)) + tree = kdtree_type(data) + d = 0.3 + + length = tree.query_ball_point(query, d, return_length=True) + length2 = [len(ind) for ind in tree.query_ball_point(query, d, return_length=False)] + length3 = [len(tree.query_ball_point(qi, d)) for qi in query] + length4 = [tree.query_ball_point(qi, d, return_length=True) for qi in query] + assert_array_equal(length, length2) + assert_array_equal(length, length3) + assert_array_equal(length, length4) + +def test_discontiguous(kdtree_type): + + np.random.seed(1234) + data = np.random.normal(size=(100, 3)) + d_contiguous = np.arange(100) * 0.04 + d_discontiguous = np.ascontiguousarray( + np.arange(100)[::-1] * 0.04)[::-1] + query_contiguous = np.random.normal(size=(100, 3)) + query_discontiguous = np.ascontiguousarray(query_contiguous.T).T + assert query_discontiguous.strides[-1] != query_contiguous.strides[-1] + assert d_discontiguous.strides[-1] != d_contiguous.strides[-1] + + tree = kdtree_type(data) + + length1 = tree.query_ball_point(query_contiguous, + d_contiguous, return_length=True) + length2 = tree.query_ball_point(query_discontiguous, + d_discontiguous, return_length=True) + + assert_array_equal(length1, length2) + + d1, i1 = tree.query(query_contiguous, 1) + d2, i2 = tree.query(query_discontiguous, 1) + + assert_array_equal(d1, d2) + assert_array_equal(i1, i2) + + +@pytest.mark.parametrize("balanced_tree, compact_nodes", + [(True, False), + (True, True), + (False, False), + (False, True)]) +def test_kdtree_empty_input(kdtree_type, balanced_tree, compact_nodes): + # https://github.com/scipy/scipy/issues/5040 + np.random.seed(1234) + empty_v3 = np.empty(shape=(0, 3)) + query_v3 = np.ones(shape=(1, 3)) + query_v2 = np.ones(shape=(2, 3)) + + tree = kdtree_type(empty_v3, balanced_tree=balanced_tree, + compact_nodes=compact_nodes) + length = tree.query_ball_point(query_v3, 0.3, return_length=True) + assert length == 0 + + dd, ii = tree.query(query_v2, 2) + assert ii.shape == (2, 2) + assert dd.shape == (2, 2) + assert np.isinf(dd).all() + + N = tree.count_neighbors(tree, [0, 1]) + assert_array_equal(N, [0, 0]) + + M = tree.sparse_distance_matrix(tree, 0.3, output_type='dok_array') + assert M.shape == (0, 0) + +@KDTreeTest +class _Test_sorted_query_ball_point: + def setup_method(self): + np.random.seed(1234) + self.x = np.random.randn(100, 1) + self.ckdt = self.kdtree_type(self.x) + + def test_return_sorted_True(self): + idxs_list = self.ckdt.query_ball_point(self.x, 1., return_sorted=True) + for idxs in idxs_list: + assert_array_equal(idxs, sorted(idxs)) + + for xi in self.x: + idxs = self.ckdt.query_ball_point(xi, 1., return_sorted=True) + assert_array_equal(idxs, sorted(idxs)) + + def test_return_sorted_None(self): + """Previous behavior was to sort the returned indices if there were + multiple points per query but not sort them if there was a single point + per query.""" + idxs_list = self.ckdt.query_ball_point(self.x, 1.) + for idxs in idxs_list: + assert_array_equal(idxs, sorted(idxs)) + + idxs_list_single = [self.ckdt.query_ball_point(xi, 1.) for xi in self.x] + idxs_list_False = self.ckdt.query_ball_point(self.x, 1., return_sorted=False) + for idxs0, idxs1 in zip(idxs_list_False, idxs_list_single): + assert_array_equal(idxs0, idxs1) + + +def test_kdtree_complex_data(): + # Test that KDTree rejects complex input points (gh-9108) + points = np.random.rand(10, 2).view(complex) + + with pytest.raises(TypeError, match="complex data"): + t = KDTree(points) + + t = KDTree(points.real) + + with pytest.raises(TypeError, match="complex data"): + t.query(points) + + with pytest.raises(TypeError, match="complex data"): + t.query_ball_point(points, r=1) + + +def visit_tree(i, t, points): + root = t.tree + + assert isinstance(root, KDTree.innernode) + assert root.children == points.shape[0] + + # Visit the tree and assert some basic properties for each node + nodes = [root] + + while nodes: + n = nodes.pop(-1) + + if isinstance(n, KDTree.leafnode): + assert isinstance(n.children, int) + assert n.children == len(n.idx) + assert_array_equal(points[n.idx], n._node.data_points) + else: + assert isinstance(n, KDTree.innernode) + assert isinstance(n.split_dim, int) + assert 0 <= n.split_dim < t.m + assert isinstance(n.split, float) + assert isinstance(n.children, int) + assert n.children == n.less.children + n.greater.children + nodes.append(n.greater) + nodes.append(n.less) + + + +def test_kdtree_tree_access(): + # Test KDTree.tree can be used to traverse the KDTree + np.random.seed(1234) + points = np.random.rand(100, 4) + t = KDTree(points) + visit_tree(0, t, points) + + +@pytest.mark.thread_unsafe(reason="Spawns worker threads") +def test_multithreaded_tree_access(): + # Test that lazily generating KDTree.tree works when tree generation + # is reqested from multiple threads + rng = np.random.RandomState(3116978525) + points = rng.rand(100, 4) + t = KDTree(points) + _run_concurrent_barrier(10, visit_tree, t, points) + + +def test_kdtree_attributes(): + # Test KDTree's attributes are available + np.random.seed(1234) + points = np.random.rand(100, 4) + t = KDTree(points) + + assert isinstance(t.m, int) + assert t.n == points.shape[0] + + assert isinstance(t.n, int) + assert t.m == points.shape[1] + + assert isinstance(t.leafsize, int) + assert t.leafsize == 10 + + assert_array_equal(t.maxes, np.amax(points, axis=0)) + assert_array_equal(t.mins, np.amin(points, axis=0)) + assert t.data.base is points + + +@pytest.mark.parametrize("kdtree_class", [KDTree, cKDTree]) +def test_kdtree_count_neighbors_weighted(kdtree_class): + rng = np.random.RandomState(1234) + r = np.arange(0.05, 1, 0.05) + + A = rng.random(21).reshape((7,3)) + B = rng.random(45).reshape((15,3)) + + wA = rng.random(7) + wB = rng.random(15) + + kdA = kdtree_class(A) + kdB = kdtree_class(B) + + nAB = kdA.count_neighbors(kdB, r, cumulative=False, weights=(wA,wB)) + + # Compare against brute-force + weights = wA[None, :] * wB[:, None] + dist = np.linalg.norm(A[None, :, :] - B[:, None, :], axis=-1) + expect = [np.sum(weights[(prev_radius < dist) & (dist <= radius)]) + for prev_radius, radius in zip(itertools.chain([0], r[:-1]), r)] + assert_allclose(nAB, expect) + + +def test_kdtree_nan(): + vals = [1, 5, -10, 7, -4, -16, -6, 6, 3, -11] + n = len(vals) + data = np.concatenate([vals, np.full(n, np.nan)])[:, None] + with pytest.raises(ValueError, match="must be finite"): + KDTree(data) + + +def test_nonfinite_inputs_gh_18223(): + rng = np.random.default_rng(12345) + coords = rng.uniform(size=(100, 3), low=0.0, high=0.1) + t = KDTree(coords, balanced_tree=False, compact_nodes=False) + bad_coord = [np.nan for _ in range(3)] + + with pytest.raises(ValueError, match="must be finite"): + t.query(bad_coord) + with pytest.raises(ValueError, match="must be finite"): + t.query_ball_point(bad_coord, 1) + + coords[0, :] = np.nan + with pytest.raises(ValueError, match="must be finite"): + KDTree(coords, balanced_tree=True, compact_nodes=False) + with pytest.raises(ValueError, match="must be finite"): + KDTree(coords, balanced_tree=False, compact_nodes=True) + with pytest.raises(ValueError, match="must be finite"): + KDTree(coords, balanced_tree=True, compact_nodes=True) + with pytest.raises(ValueError, match="must be finite"): + KDTree(coords, balanced_tree=False, compact_nodes=False) + + +@pytest.mark.parametrize("incantation", [cKDTree, KDTree]) +def test_gh_18800(incantation): + # our prohibition on non-finite values + # in kd-tree workflows means we need + # coercion to NumPy arrays enforced + + class ArrLike(np.ndarray): + def __new__(cls, input_array): + obj = np.asarray(input_array).view(cls) + # we override all() to mimic the problem + # pandas DataFrames encountered in gh-18800 + obj.all = None + return obj + + def __array_finalize__(self, obj): + if obj is None: + return + self.all = getattr(obj, 'all', None) + + points = [ + [66.22, 32.54], + [22.52, 22.39], + [31.01, 81.21], + ] + arr = np.array(points) + arr_like = ArrLike(arr) + tree = incantation(points, 10) + tree.query(arr_like, 1) + tree.query_ball_point(arr_like, 200) + + +def test_immutable(kdtree_type): + rng = np.random.RandomState(3965682946) + n = 1000 + k = 2 + points = rng.rand(n, k) + T = kdtree_type(points, boxsize=1) + object_attrs = { + 'm': 3, + 'n': 3, + 'size': 100, + 'leafsize': 12, + 'tree': None, + } + for attr, attr_val in object_attrs.items(): + with pytest.raises(AttributeError, match="(is not writable|has no setter)"): + setattr(T, attr, attr_val) + array_attrs = { + 'data': np.array([0, 0], dtype=np.float64), + 'indices': 7, + 'boxsize': 4, + 'maxes': 1.5, + 'mins': 1.5, + } + for attr, attr_val in array_attrs.items(): + with pytest.raises(ValueError, match="destination is read-only"): + getattr(T, attr)[:] = attr_val diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/tests/test_qhull.py b/venv/lib/python3.12/site-packages/scipy/spatial/tests/test_qhull.py new file mode 100644 index 0000000000000000000000000000000000000000..b6ca304228083fd060adc4f15e64655021bf28f2 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/tests/test_qhull.py @@ -0,0 +1,1312 @@ +import os +import copy + +import numpy as np +from numpy.testing import (assert_equal, assert_almost_equal, + assert_, assert_allclose, assert_array_equal) +import pytest +from pytest import raises as assert_raises + +import scipy.spatial._qhull as qhull +from scipy.spatial import cKDTree as KDTree # type: ignore[attr-defined] +from scipy.spatial import Voronoi + +import itertools + +def sorted_tuple(x): + return tuple(sorted(x)) + + +def assert_unordered_tuple_list_equal(a, b, tpl=tuple): + if isinstance(a, np.ndarray): + a = a.tolist() + if isinstance(b, np.ndarray): + b = b.tolist() + a = list(map(tpl, a)) + a.sort() + b = list(map(tpl, b)) + b.sort() + assert_equal(a, b) + + +np.random.seed(1234) + +points = [(0,0), (0,1), (1,0), (1,1), (0.5, 0.5), (0.5, 1.5)] + +pathological_data_1 = np.array([ + [-3.14,-3.14], [-3.14,-2.36], [-3.14,-1.57], [-3.14,-0.79], + [-3.14,0.0], [-3.14,0.79], [-3.14,1.57], [-3.14,2.36], + [-3.14,3.14], [-2.36,-3.14], [-2.36,-2.36], [-2.36,-1.57], + [-2.36,-0.79], [-2.36,0.0], [-2.36,0.79], [-2.36,1.57], + [-2.36,2.36], [-2.36,3.14], [-1.57,-0.79], [-1.57,0.79], + [-1.57,-1.57], [-1.57,0.0], [-1.57,1.57], [-1.57,-3.14], + [-1.57,-2.36], [-1.57,2.36], [-1.57,3.14], [-0.79,-1.57], + [-0.79,1.57], [-0.79,-3.14], [-0.79,-2.36], [-0.79,-0.79], + [-0.79,0.0], [-0.79,0.79], [-0.79,2.36], [-0.79,3.14], + [0.0,-3.14], [0.0,-2.36], [0.0,-1.57], [0.0,-0.79], [0.0,0.0], + [0.0,0.79], [0.0,1.57], [0.0,2.36], [0.0,3.14], [0.79,-3.14], + [0.79,-2.36], [0.79,-0.79], [0.79,0.0], [0.79,0.79], + [0.79,2.36], [0.79,3.14], [0.79,-1.57], [0.79,1.57], + [1.57,-3.14], [1.57,-2.36], [1.57,2.36], [1.57,3.14], + [1.57,-1.57], [1.57,0.0], [1.57,1.57], [1.57,-0.79], + [1.57,0.79], [2.36,-3.14], [2.36,-2.36], [2.36,-1.57], + [2.36,-0.79], [2.36,0.0], [2.36,0.79], [2.36,1.57], + [2.36,2.36], [2.36,3.14], [3.14,-3.14], [3.14,-2.36], + [3.14,-1.57], [3.14,-0.79], [3.14,0.0], [3.14,0.79], + [3.14,1.57], [3.14,2.36], [3.14,3.14], +]) + +pathological_data_2 = np.array([ + [-1, -1], [-1, 0], [-1, 1], + [0, -1], [0, 0], [0, 1], + [1, -1 - np.finfo(np.float64).eps], [1, 0], [1, 1], +]) + +bug_2850_chunks = [np.random.rand(10, 2), + np.array([[0,0], [0,1], [1,0], [1,1]]) # add corners + ] + +# same with some additional chunks +bug_2850_chunks_2 = (bug_2850_chunks + + [np.random.rand(10, 2), + 0.25 + np.array([[0,0], [0,1], [1,0], [1,1]])]) + +DATASETS = { + 'some-points': np.asarray(points), + 'random-2d': np.random.rand(30, 2), + 'random-3d': np.random.rand(30, 3), + 'random-4d': np.random.rand(30, 4), + 'random-5d': np.random.rand(30, 5), + 'random-6d': np.random.rand(10, 6), + 'random-7d': np.random.rand(10, 7), + 'random-8d': np.random.rand(10, 8), + 'pathological-1': pathological_data_1, + 'pathological-2': pathological_data_2 +} + +INCREMENTAL_DATASETS = { + 'bug-2850': (bug_2850_chunks, None), + 'bug-2850-2': (bug_2850_chunks_2, None), +} + + +def _add_inc_data(name, chunksize): + """ + Generate incremental datasets from basic data sets + """ + points = DATASETS[name] + ndim = points.shape[1] + + opts = None + nmin = ndim + 2 + + if name == 'some-points': + # since Qz is not allowed, use QJ + opts = 'QJ Pp' + elif name == 'pathological-1': + # include enough points so that we get different x-coordinates + nmin = 12 + + chunks = [points[:nmin]] + for j in range(nmin, len(points), chunksize): + chunks.append(points[j:j+chunksize]) + + new_name = f"{name}-chunk-{chunksize}" + assert new_name not in INCREMENTAL_DATASETS + INCREMENTAL_DATASETS[new_name] = (chunks, opts) + + +for name in DATASETS: + for chunksize in 1, 4, 16: + _add_inc_data(name, chunksize) + + +class Test_Qhull: + def test_swapping(self): + # Check that Qhull state swapping works + + x = qhull._Qhull(b'v', + np.array([[0,0],[0,1],[1,0],[1,1.],[0.5,0.5]]), + b'Qz') + xd = copy.deepcopy(x.get_voronoi_diagram()) + + y = qhull._Qhull(b'v', + np.array([[0,0],[0,1],[1,0],[1,2.]]), + b'Qz') + yd = copy.deepcopy(y.get_voronoi_diagram()) + + xd2 = copy.deepcopy(x.get_voronoi_diagram()) + x.close() + yd2 = copy.deepcopy(y.get_voronoi_diagram()) + y.close() + + assert_raises(RuntimeError, x.get_voronoi_diagram) + assert_raises(RuntimeError, y.get_voronoi_diagram) + + assert_allclose(xd[0], xd2[0]) + assert_unordered_tuple_list_equal(xd[1], xd2[1], tpl=sorted_tuple) + assert_unordered_tuple_list_equal(xd[2], xd2[2], tpl=sorted_tuple) + assert_unordered_tuple_list_equal(xd[3], xd2[3], tpl=sorted_tuple) + assert_array_equal(xd[4], xd2[4]) + + assert_allclose(yd[0], yd2[0]) + assert_unordered_tuple_list_equal(yd[1], yd2[1], tpl=sorted_tuple) + assert_unordered_tuple_list_equal(yd[2], yd2[2], tpl=sorted_tuple) + assert_unordered_tuple_list_equal(yd[3], yd2[3], tpl=sorted_tuple) + assert_array_equal(yd[4], yd2[4]) + + x.close() + assert_raises(RuntimeError, x.get_voronoi_diagram) + y.close() + assert_raises(RuntimeError, y.get_voronoi_diagram) + + def test_issue_8051(self): + points = np.array( + [[0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [1, 2],[2, 0], [2, 1], [2, 2]] + ) + Voronoi(points) + + +class TestUtilities: + """ + Check that utility functions work. + + """ + + def test_find_simplex(self): + # Simple check that simplex finding works + points = np.array([(0,0), (0,1), (1,1), (1,0)], dtype=np.float64) + tri = qhull.Delaunay(points) + + # +---+ + # |\ 0| + # | \ | + # |1 \| + # +---+ + + assert_equal(tri.simplices, [[1, 3, 2], [3, 1, 0]]) + + for p in [(0.25, 0.25, 1), + (0.75, 0.75, 0), + (0.3, 0.2, 1)]: + i = tri.find_simplex(p[:2]) + assert_equal(i, p[2], err_msg=f'{p!r}') + with pytest.warns(DeprecationWarning, match="`tsearch` is deprecated"): + j = qhull.tsearch(tri, p[:2]) + assert_equal(i, j) + + def test_plane_distance(self): + # Compare plane distance from hyperplane equations obtained from Qhull + # to manually computed plane equations + x = np.array([(0,0), (1, 1), (1, 0), (0.99189033, 0.37674127), + (0.99440079, 0.45182168)], dtype=np.float64) + p = np.array([0.99966555, 0.15685619], dtype=np.float64) + + tri = qhull.Delaunay(x) + + z = tri.lift_points(x) + pz = tri.lift_points(p) + + dist = tri.plane_distance(p) + + for j, v in enumerate(tri.simplices): + x1 = z[v[0]] + x2 = z[v[1]] + x3 = z[v[2]] + + n = np.cross(x1 - x3, x2 - x3) + n /= np.sqrt(np.dot(n, n)) + n *= -np.sign(n[2]) + + d = np.dot(n, pz - x3) + + assert_almost_equal(dist[j], d) + + def test_convex_hull(self): + # Simple check that the convex hull seems to works + points = np.array([(0,0), (0,1), (1,1), (1,0)], dtype=np.float64) + tri = qhull.Delaunay(points) + + # +---+ + # |\ 0| + # | \ | + # |1 \| + # +---+ + + assert_equal(tri.convex_hull, [[3, 2], [1, 2], [1, 0], [3, 0]]) + + def test_volume_area(self): + #Basic check that we get back the correct volume and area for a cube + points = np.array([(0, 0, 0), (0, 1, 0), (1, 0, 0), (1, 1, 0), + (0, 0, 1), (0, 1, 1), (1, 0, 1), (1, 1, 1)]) + hull = qhull.ConvexHull(points) + + assert_allclose(hull.volume, 1., rtol=1e-14, + err_msg="Volume of cube is incorrect") + assert_allclose(hull.area, 6., rtol=1e-14, + err_msg="Area of cube is incorrect") + + def test_random_volume_area(self): + #Test that the results for a random 10-point convex are + #coherent with the output of qconvex Qt s FA + points = np.array([(0.362568364506, 0.472712355305, 0.347003084477), + (0.733731893414, 0.634480295684, 0.950513180209), + (0.511239955611, 0.876839441267, 0.418047827863), + (0.0765906233393, 0.527373281342, 0.6509863541), + (0.146694972056, 0.596725793348, 0.894860986685), + (0.513808585741, 0.069576205858, 0.530890338876), + (0.512343805118, 0.663537132612, 0.037689295973), + (0.47282965018, 0.462176697655, 0.14061843691), + (0.240584597123, 0.778660020591, 0.722913476339), + (0.951271745935, 0.967000673944, 0.890661319684)]) + + hull = qhull.ConvexHull(points) + assert_allclose(hull.volume, 0.14562013, rtol=1e-07, + err_msg="Volume of random polyhedron is incorrect") + assert_allclose(hull.area, 1.6670425, rtol=1e-07, + err_msg="Area of random polyhedron is incorrect") + + def test_incremental_volume_area_random_input(self): + """Test that incremental mode gives the same volume/area as + non-incremental mode and incremental mode with restart""" + nr_points = 20 + dim = 3 + points = np.random.random((nr_points, dim)) + inc_hull = qhull.ConvexHull(points[:dim+1, :], incremental=True) + inc_restart_hull = qhull.ConvexHull(points[:dim+1, :], incremental=True) + for i in range(dim+1, nr_points): + hull = qhull.ConvexHull(points[:i+1, :]) + inc_hull.add_points(points[i:i+1, :]) + inc_restart_hull.add_points(points[i:i+1, :], restart=True) + assert_allclose(hull.volume, inc_hull.volume, rtol=1e-7) + assert_allclose(hull.volume, inc_restart_hull.volume, rtol=1e-7) + assert_allclose(hull.area, inc_hull.area, rtol=1e-7) + assert_allclose(hull.area, inc_restart_hull.area, rtol=1e-7) + + def _check_barycentric_transforms(self, tri, err_msg="", + unit_cube=False, + unit_cube_tol=0): + """Check that a triangulation has reasonable barycentric transforms""" + vertices = tri.points[tri.simplices] + sc = 1/(tri.ndim + 1.0) + centroids = vertices.sum(axis=1) * sc + + # Either: (i) the simplex has a `nan` barycentric transform, + # or, (ii) the centroid is in the simplex + + def barycentric_transform(tr, x): + r = tr[:,-1,:] + Tinv = tr[:,:-1,:] + return np.einsum('ijk,ik->ij', Tinv, x - r) + + eps = np.finfo(float).eps + + c = barycentric_transform(tri.transform, centroids) + with np.errstate(invalid="ignore"): + ok = np.isnan(c).all(axis=1) | (abs(c - sc)/sc < 0.1).all(axis=1) + + assert_(ok.all(), f"{err_msg} {np.nonzero(~ok)}") + + # Invalid simplices must be (nearly) zero volume + q = vertices[:,:-1,:] - vertices[:,-1,None,:] + volume = np.array([np.linalg.det(q[k,:,:]) + for k in range(tri.nsimplex)]) + ok = np.isfinite(tri.transform[:,0,0]) | (volume < np.sqrt(eps)) + assert_(ok.all(), f"{err_msg} {np.nonzero(~ok)}") + + # Also, find_simplex for the centroid should end up in some + # simplex for the non-degenerate cases + j = tri.find_simplex(centroids) + ok = (j != -1) | np.isnan(tri.transform[:,0,0]) + assert_(ok.all(), f"{err_msg} {np.nonzero(~ok)}") + + if unit_cube: + # If in unit cube, no interior point should be marked out of hull + at_boundary = (centroids <= unit_cube_tol).any(axis=1) + at_boundary |= (centroids >= 1 - unit_cube_tol).any(axis=1) + + ok = (j != -1) | at_boundary + assert_(ok.all(), f"{err_msg} {np.nonzero(~ok)}") + + @pytest.mark.fail_slow(10) + def test_degenerate_barycentric_transforms(self): + # The triangulation should not produce invalid barycentric + # transforms that stump the simplex finding + data = np.load(os.path.join(os.path.dirname(__file__), 'data', + 'degenerate_pointset.npz')) + points = data['c'] + data.close() + + tri = qhull.Delaunay(points) + + # Check that there are not too many invalid simplices + bad_count = np.isnan(tri.transform[:,0,0]).sum() + assert_(bad_count < 23, bad_count) + + # Check the transforms + self._check_barycentric_transforms(tri) + + @pytest.mark.slow + @pytest.mark.fail_slow(20) + # OK per https://github.com/scipy/scipy/pull/20487#discussion_r1572684869 + def test_more_barycentric_transforms(self): + # Triangulate some "nasty" grids + + eps = np.finfo(float).eps + + npoints = {2: 70, 3: 11, 4: 5, 5: 3} + + for ndim in range(2, 6): + # Generate a uniform grid in n-d unit cube + x = np.linspace(0, 1, npoints[ndim]) + grid = np.c_[ + list(map(np.ravel, np.broadcast_arrays(*np.ix_(*([x]*ndim))))) + ].T + + err_msg = f"ndim={ndim}" + + # Check using regular grid + tri = qhull.Delaunay(grid) + self._check_barycentric_transforms(tri, err_msg=err_msg, + unit_cube=True) + + # Check with eps-perturbations + np.random.seed(1234) + m = (np.random.rand(grid.shape[0]) < 0.2) + grid[m,:] += 2*eps*(np.random.rand(*grid[m,:].shape) - 0.5) + + tri = qhull.Delaunay(grid) + self._check_barycentric_transforms(tri, err_msg=err_msg, + unit_cube=True, + unit_cube_tol=2*eps) + + # Check with duplicated data + tri = qhull.Delaunay(np.r_[grid, grid]) + self._check_barycentric_transforms(tri, err_msg=err_msg, + unit_cube=True, + unit_cube_tol=2*eps) + + +class TestVertexNeighborVertices: + def _check(self, tri): + expected = [set() for j in range(tri.points.shape[0])] + for s in tri.simplices: + for a in s: + for b in s: + if a != b: + expected[a].add(b) + + indptr, indices = tri.vertex_neighbor_vertices + + got = [set(map(int, indices[indptr[j]:indptr[j+1]])) + for j in range(tri.points.shape[0])] + + assert_equal(got, expected, err_msg=f"{got!r} != {expected!r}") + + def test_triangle(self): + points = np.array([(0,0), (0,1), (1,0)], dtype=np.float64) + tri = qhull.Delaunay(points) + self._check(tri) + + def test_rectangle(self): + points = np.array([(0,0), (0,1), (1,1), (1,0)], dtype=np.float64) + tri = qhull.Delaunay(points) + self._check(tri) + + def test_complicated(self): + points = np.array([(0,0), (0,1), (1,1), (1,0), + (0.5, 0.5), (0.9, 0.5)], dtype=np.float64) + tri = qhull.Delaunay(points) + self._check(tri) + + +class TestDelaunay: + """ + Check that triangulation works. + + """ + def test_masked_array_fails(self): + masked_array = np.ma.masked_all(1) + assert_raises(ValueError, qhull.Delaunay, masked_array) + + # Shouldn't be inherently unsafe; retry with cpython 3.14 once traceback + # thread safety issues are fixed (also goes for other test with same name + # further down) + def test_array_with_nans_fails(self): + points_with_nan = np.array([(0,0), (0,1), (1,1), (1,np.nan)], dtype=np.float64) + assert_raises(ValueError, qhull.Delaunay, points_with_nan) + + def test_nd_simplex(self): + # simple smoke test: triangulate a n-dimensional simplex + for nd in range(2, 8): + points = np.zeros((nd+1, nd)) + for j in range(nd): + points[j,j] = 1.0 + points[-1,:] = 1.0 + + tri = qhull.Delaunay(points) + + tri.simplices.sort() + + assert_equal(tri.simplices, np.arange(nd+1, dtype=int)[None, :]) + assert_equal(tri.neighbors, -1 + np.zeros((nd+1), dtype=int)[None,:]) + + def test_2d_square(self): + # simple smoke test: 2d square + points = np.array([(0,0), (0,1), (1,1), (1,0)], dtype=np.float64) + tri = qhull.Delaunay(points) + + assert_equal(tri.simplices, [[1, 3, 2], [3, 1, 0]]) + assert_equal(tri.neighbors, [[-1, -1, 1], [-1, -1, 0]]) + + def test_duplicate_points(self): + x = np.array([0, 1, 0, 1], dtype=np.float64) + y = np.array([0, 0, 1, 1], dtype=np.float64) + + xp = np.r_[x, x] + yp = np.r_[y, y] + + # shouldn't fail on duplicate points + qhull.Delaunay(np.c_[x, y]) + qhull.Delaunay(np.c_[xp, yp]) + + def test_pathological(self): + # both should succeed + points = DATASETS['pathological-1'] + tri = qhull.Delaunay(points) + assert_equal(tri.points[tri.simplices].max(), points.max()) + assert_equal(tri.points[tri.simplices].min(), points.min()) + + points = DATASETS['pathological-2'] + tri = qhull.Delaunay(points) + assert_equal(tri.points[tri.simplices].max(), points.max()) + assert_equal(tri.points[tri.simplices].min(), points.min()) + + def test_joggle(self): + # Check that the option QJ indeed guarantees that all input points + # occur as vertices of the triangulation + + points = np.random.rand(10, 2) + points = np.r_[points, points] # duplicate input data + + tri = qhull.Delaunay(points, qhull_options="QJ Qbb Pp") + assert_array_equal(np.unique(tri.simplices.ravel()), + np.arange(len(points))) + + def test_coplanar(self): + # Check that the coplanar point output option indeed works + points = np.random.rand(10, 2) + points = np.r_[points, points] # duplicate input data + + tri = qhull.Delaunay(points) + + assert_(len(np.unique(tri.simplices.ravel())) == len(points)//2) + assert_(len(tri.coplanar) == len(points)//2) + + assert_(len(np.unique(tri.coplanar[:,2])) == len(points)//2) + + assert_(np.all(tri.vertex_to_simplex >= 0)) + + def test_furthest_site(self): + points = [(0, 0), (0, 1), (1, 0), (0.5, 0.5), (1.1, 1.1)] + tri = qhull.Delaunay(points, furthest_site=True) + + expected = np.array([(1, 4, 0), (4, 2, 0)]) # from Qhull + assert_array_equal(tri.simplices, expected) + + @pytest.mark.parametrize("name", sorted(INCREMENTAL_DATASETS)) + def test_incremental(self, name): + # Test incremental construction of the triangulation + + chunks, opts = INCREMENTAL_DATASETS[name] + points = np.concatenate(chunks, axis=0) + + obj = qhull.Delaunay(chunks[0], incremental=True, + qhull_options=opts) + for chunk in chunks[1:]: + obj.add_points(chunk) + + obj2 = qhull.Delaunay(points) + + obj3 = qhull.Delaunay(chunks[0], incremental=True, + qhull_options=opts) + if len(chunks) > 1: + obj3.add_points(np.concatenate(chunks[1:], axis=0), + restart=True) + + # Check that the incremental mode agrees with upfront mode + if name.startswith('pathological'): + # XXX: These produce valid but different triangulations. + # They look OK when plotted, but how to check them? + + assert_array_equal(np.unique(obj.simplices.ravel()), + np.arange(points.shape[0])) + assert_array_equal(np.unique(obj2.simplices.ravel()), + np.arange(points.shape[0])) + else: + assert_unordered_tuple_list_equal(obj.simplices, obj2.simplices, + tpl=sorted_tuple) + + assert_unordered_tuple_list_equal(obj2.simplices, obj3.simplices, + tpl=sorted_tuple) + + +def assert_hulls_equal(points, facets_1, facets_2): + # Check that two convex hulls constructed from the same point set + # are equal + + facets_1 = set(map(sorted_tuple, facets_1)) + facets_2 = set(map(sorted_tuple, facets_2)) + + if facets_1 != facets_2 and points.shape[1] == 2: + # The direct check fails for the pathological cases + # --- then the convex hull from Delaunay differs (due + # to rounding error etc.) from the hull computed + # otherwise, by the question whether (tricoplanar) + # points that lie almost exactly on the hull are + # included as vertices of the hull or not. + # + # So we check the result, and accept it if the Delaunay + # hull line segments are a subset of the usual hull. + + eps = 1000 * np.finfo(float).eps + + for a, b in facets_1: + for ap, bp in facets_2: + t = points[bp] - points[ap] + t /= np.linalg.norm(t) # tangent + n = np.array([-t[1], t[0]]) # normal + + # check that the two line segments are parallel + # to the same line + c1 = np.dot(n, points[b] - points[ap]) + c2 = np.dot(n, points[a] - points[ap]) + if not np.allclose(np.dot(c1, n), 0): + continue + if not np.allclose(np.dot(c2, n), 0): + continue + + # Check that the segment (a, b) is contained in (ap, bp) + c1 = np.dot(t, points[a] - points[ap]) + c2 = np.dot(t, points[b] - points[ap]) + c3 = np.dot(t, points[bp] - points[ap]) + if c1 < -eps or c1 > c3 + eps: + continue + if c2 < -eps or c2 > c3 + eps: + continue + + # OK: + break + else: + raise AssertionError("comparison fails") + + # it was OK + return + + assert_equal(facets_1, facets_2) + + +class TestConvexHull: + def test_masked_array_fails(self): + masked_array = np.ma.masked_all(1) + assert_raises(ValueError, qhull.ConvexHull, masked_array) + + def test_array_with_nans_fails(self): + points_with_nan = np.array([(0,0), (1,1), (2,np.nan)], dtype=np.float64) + assert_raises(ValueError, qhull.ConvexHull, points_with_nan) + + @pytest.mark.parametrize("name", sorted(DATASETS)) + def test_hull_consistency_tri(self, name): + # Check that a convex hull returned by qhull in ndim + # and the hull constructed from ndim delaunay agree + points = DATASETS[name] + + tri = qhull.Delaunay(points) + hull = qhull.ConvexHull(points) + + assert_hulls_equal(points, tri.convex_hull, hull.simplices) + + # Check that the hull extremes are as expected + if points.shape[1] == 2: + assert_equal(np.unique(hull.simplices), np.sort(hull.vertices)) + else: + assert_equal(np.unique(hull.simplices), hull.vertices) + + @pytest.mark.parametrize("name", sorted(INCREMENTAL_DATASETS)) + def test_incremental(self, name): + # Test incremental construction of the convex hull + chunks, _ = INCREMENTAL_DATASETS[name] + points = np.concatenate(chunks, axis=0) + + obj = qhull.ConvexHull(chunks[0], incremental=True) + for chunk in chunks[1:]: + obj.add_points(chunk) + + obj2 = qhull.ConvexHull(points) + + obj3 = qhull.ConvexHull(chunks[0], incremental=True) + if len(chunks) > 1: + obj3.add_points(np.concatenate(chunks[1:], axis=0), + restart=True) + + # Check that the incremental mode agrees with upfront mode + assert_hulls_equal(points, obj.simplices, obj2.simplices) + assert_hulls_equal(points, obj.simplices, obj3.simplices) + + def test_vertices_2d(self): + # The vertices should be in counterclockwise order in 2-D + np.random.seed(1234) + points = np.random.rand(30, 2) + + hull = qhull.ConvexHull(points) + assert_equal(np.unique(hull.simplices), np.sort(hull.vertices)) + + # Check counterclockwiseness + x, y = hull.points[hull.vertices].T + angle = np.arctan2(y - y.mean(), x - x.mean()) + assert_(np.all(np.diff(np.unwrap(angle)) > 0)) + + def test_volume_area(self): + # Basic check that we get back the correct volume and area for a cube + points = np.array([(0, 0, 0), (0, 1, 0), (1, 0, 0), (1, 1, 0), + (0, 0, 1), (0, 1, 1), (1, 0, 1), (1, 1, 1)]) + tri = qhull.ConvexHull(points) + + assert_allclose(tri.volume, 1., rtol=1e-14) + assert_allclose(tri.area, 6., rtol=1e-14) + + @pytest.mark.parametrize("incremental", [False, True]) + def test_good2d(self, incremental): + # Make sure the QGn option gives the correct value of "good". + points = np.array([[0.2, 0.2], + [0.2, 0.4], + [0.4, 0.4], + [0.4, 0.2], + [0.3, 0.6]]) + hull = qhull.ConvexHull(points=points, + incremental=incremental, + qhull_options='QG4') + expected = np.array([False, True, False, False], dtype=bool) + actual = hull.good + assert_equal(actual, expected) + + @pytest.mark.parametrize("visibility", [ + "QG4", # visible=True + "QG-4", # visible=False + ]) + @pytest.mark.parametrize("new_gen, expected", [ + # add generator that places QG4 inside hull + # so all facets are invisible + (np.array([[0.3, 0.7]]), + np.array([False, False, False, False, False], dtype=bool)), + # adding a generator on the opposite side of the square + # should preserve the single visible facet & add one invisible + # facet + (np.array([[0.3, -0.7]]), + np.array([False, True, False, False, False], dtype=bool)), + # split the visible facet on top of the square into two + # visible facets, with visibility at the end of the array + # because add_points concatenates + (np.array([[0.3, 0.41]]), + np.array([False, False, False, True, True], dtype=bool)), + # with our current Qhull options, coplanarity will not count + # for visibility; this case shifts one visible & one invisible + # facet & adds a coplanar facet + # simplex at index position 2 is the shifted visible facet + # the final simplex is the coplanar facet + (np.array([[0.5, 0.6], [0.6, 0.6]]), + np.array([False, False, True, False, False], dtype=bool)), + # place the new generator such that it envelops the query + # point within the convex hull, but only just barely within + # the double precision limit + # NOTE: testing exact degeneracy is less predictable than this + # scenario, perhaps because of the default Qt option we have + # enabled for Qhull to handle precision matters + (np.array([[0.3, 0.6 + 1e-16]]), + np.array([False, False, False, False, False], dtype=bool)), + ]) + def test_good2d_incremental_changes(self, new_gen, expected, + visibility): + # use the usual square convex hull + # generators from test_good2d + points = np.array([[0.2, 0.2], + [0.2, 0.4], + [0.4, 0.4], + [0.4, 0.2], + [0.3, 0.6]]) + hull = qhull.ConvexHull(points=points, + incremental=True, + qhull_options=visibility) + hull.add_points(new_gen) + actual = hull.good + if '-' in visibility: + expected = np.invert(expected) + assert_equal(actual, expected) + + @pytest.mark.parametrize("incremental", [False, True]) + def test_good2d_no_option(self, incremental): + # handle case where good attribute doesn't exist + # because Qgn or Qg-n wasn't specified + points = np.array([[0.2, 0.2], + [0.2, 0.4], + [0.4, 0.4], + [0.4, 0.2], + [0.3, 0.6]]) + hull = qhull.ConvexHull(points=points, + incremental=incremental) + actual = hull.good + assert actual is None + # preserve None after incremental addition + if incremental: + hull.add_points(np.zeros((1, 2))) + actual = hull.good + assert actual is None + + @pytest.mark.parametrize("incremental", [False, True]) + def test_good2d_inside(self, incremental): + # Make sure the QGn option gives the correct value of "good". + # When point n is inside the convex hull of the rest, good is + # all False. + points = np.array([[0.2, 0.2], + [0.2, 0.4], + [0.4, 0.4], + [0.4, 0.2], + [0.3, 0.3]]) + hull = qhull.ConvexHull(points=points, + incremental=incremental, + qhull_options='QG4') + expected = np.array([False, False, False, False], dtype=bool) + actual = hull.good + assert_equal(actual, expected) + + @pytest.mark.parametrize("incremental", [False, True]) + def test_good3d(self, incremental): + # Make sure the QGn option gives the correct value of "good" + # for a 3d figure + points = np.array([[0.0, 0.0, 0.0], + [0.90029516, -0.39187448, 0.18948093], + [0.48676420, -0.72627633, 0.48536925], + [0.57651530, -0.81179274, -0.09285832], + [0.67846893, -0.71119562, 0.18406710]]) + hull = qhull.ConvexHull(points=points, + incremental=incremental, + qhull_options='QG0') + expected = np.array([True, False, False, False], dtype=bool) + assert_equal(hull.good, expected) + +class TestVoronoi: + + @pytest.mark.parametrize("qhull_opts, extra_pts", [ + # option Qz (default for SciPy) will add + # an extra point at infinity + ("Qbb Qc Qz", 1), + ("Qbb Qc", 0), + ]) + @pytest.mark.parametrize("n_pts", [50, 100]) + @pytest.mark.parametrize("ndim", [2, 3]) + def test_point_region_structure(self, + qhull_opts, + n_pts, + extra_pts, + ndim): + # see gh-16773 + rng = np.random.default_rng(7790) + points = rng.random((n_pts, ndim)) + vor = Voronoi(points, qhull_options=qhull_opts) + pt_region = vor.point_region + assert pt_region.max() == n_pts - 1 + extra_pts + assert pt_region.size == len(vor.regions) - extra_pts + assert len(vor.regions) == n_pts + extra_pts + assert vor.points.shape[0] == n_pts + # if there is an empty sublist in the Voronoi + # regions data structure, it should never be + # indexed because it corresponds to an internally + # added point at infinity and is not a member of the + # generators (input points) + if extra_pts: + sublens = [len(x) for x in vor.regions] + # only one point at infinity (empty region) + # is allowed + assert sublens.count(0) == 1 + assert sublens.index(0) not in pt_region + + def test_masked_array_fails(self): + masked_array = np.ma.masked_all(1) + assert_raises(ValueError, qhull.Voronoi, masked_array) + + def test_simple(self): + # Simple case with known Voronoi diagram + points = [(0, 0), (0, 1), (0, 2), + (1, 0), (1, 1), (1, 2), + (2, 0), (2, 1), (2, 2)] + + # qhull v o Fv Qbb Qc Qz < dat + output = """ + 2 + 5 10 1 + -10.101 -10.101 + 0.5 0.5 + 0.5 1.5 + 1.5 0.5 + 1.5 1.5 + 2 0 1 + 3 2 0 1 + 2 0 2 + 3 3 0 1 + 4 1 2 4 3 + 3 4 0 2 + 2 0 3 + 3 4 0 3 + 2 0 4 + 0 + 12 + 4 0 3 0 1 + 4 0 1 0 1 + 4 1 4 1 2 + 4 1 2 0 2 + 4 2 5 0 2 + 4 3 4 1 3 + 4 3 6 0 3 + 4 4 5 2 4 + 4 4 7 3 4 + 4 5 8 0 4 + 4 6 7 0 3 + 4 7 8 0 4 + """ + self._compare_qvoronoi(points, output) + + def _compare_qvoronoi(self, points, output, **kw): + """Compare to output from 'qvoronoi o Fv < data' to Voronoi()""" + + # Parse output + output = [list(map(float, x.split())) for x in output.strip().splitlines()] + nvertex = int(output[1][0]) + vertices = list(map(tuple, output[3:2+nvertex])) # exclude inf + nregion = int(output[1][1]) + regions = [[int(y)-1 for y in x[1:]] + for x in output[2+nvertex:2+nvertex+nregion]] + ridge_points = [[int(y) for y in x[1:3]] + for x in output[3+nvertex+nregion:]] + ridge_vertices = [[int(y)-1 for y in x[3:]] + for x in output[3+nvertex+nregion:]] + + # Compare results + vor = qhull.Voronoi(points, **kw) + + def sorttuple(x): + return tuple(sorted(x)) + + assert_allclose(vor.vertices, vertices) + assert_equal(set(map(tuple, vor.regions)), + set(map(tuple, regions))) + + p1 = list(zip(list(map(sorttuple, ridge_points)), + list(map(sorttuple, ridge_vertices)))) + p2 = list(zip(list(map(sorttuple, vor.ridge_points.tolist())), + list(map(sorttuple, vor.ridge_vertices)))) + p1.sort() + p2.sort() + + assert_equal(p1, p2) + + @pytest.mark.parametrize("name", sorted(DATASETS)) + def test_ridges(self, name): + # Check that the ridges computed by Voronoi indeed separate + # the regions of nearest neighborhood, by comparing the result + # to KDTree. + + points = DATASETS[name] + + tree = KDTree(points) + vor = qhull.Voronoi(points) + + for p, v in vor.ridge_dict.items(): + # consider only finite ridges + if not np.all(np.asarray(v) >= 0): + continue + + ridge_midpoint = vor.vertices[v].mean(axis=0) + d = 1e-6 * (points[p[0]] - ridge_midpoint) + + dist, k = tree.query(ridge_midpoint + d, k=1) + assert_equal(k, p[0]) + + dist, k = tree.query(ridge_midpoint - d, k=1) + assert_equal(k, p[1]) + + def test_furthest_site(self): + points = [(0, 0), (0, 1), (1, 0), (0.5, 0.5), (1.1, 1.1)] + + # qhull v o Fv Qbb Qc Qu < dat + output = """ + 2 + 3 5 1 + -10.101 -10.101 + 0.6000000000000001 0.5 + 0.5 0.6000000000000001 + 3 0 2 1 + 2 0 1 + 2 0 2 + 0 + 3 0 2 1 + 5 + 4 0 2 0 2 + 4 0 4 1 2 + 4 0 1 0 1 + 4 1 4 0 1 + 4 2 4 0 2 + """ + self._compare_qvoronoi(points, output, furthest_site=True) + + def test_furthest_site_flag(self): + points = [(0, 0), (0, 1), (1, 0), (0.5, 0.5), (1.1, 1.1)] + + vor = Voronoi(points) + assert_equal(vor.furthest_site,False) + vor = Voronoi(points,furthest_site=True) + assert_equal(vor.furthest_site,True) + + @pytest.mark.fail_slow(10) + @pytest.mark.parametrize("name", sorted(INCREMENTAL_DATASETS)) + def test_incremental(self, name): + # Test incremental construction of the triangulation + + if INCREMENTAL_DATASETS[name][0][0].shape[1] > 3: + # too slow (testing of the result --- qhull is still fast) + return + + chunks, opts = INCREMENTAL_DATASETS[name] + points = np.concatenate(chunks, axis=0) + + obj = qhull.Voronoi(chunks[0], incremental=True, + qhull_options=opts) + for chunk in chunks[1:]: + obj.add_points(chunk) + + obj2 = qhull.Voronoi(points) + + obj3 = qhull.Voronoi(chunks[0], incremental=True, + qhull_options=opts) + if len(chunks) > 1: + obj3.add_points(np.concatenate(chunks[1:], axis=0), + restart=True) + + # -- Check that the incremental mode agrees with upfront mode + assert_equal(len(obj.point_region), len(obj2.point_region)) + assert_equal(len(obj.point_region), len(obj3.point_region)) + + # The vertices may be in different order or duplicated in + # the incremental map + for objx in obj, obj3: + vertex_map = {-1: -1} + for i, v in enumerate(objx.vertices): + for j, v2 in enumerate(obj2.vertices): + if np.allclose(v, v2): + vertex_map[i] = j + + def remap(x): + if hasattr(x, '__len__'): + return tuple({remap(y) for y in x}) + try: + return vertex_map[x] + except KeyError as e: + message = (f"incremental result has spurious vertex " + f"at {objx.vertices[x]!r}") + raise AssertionError(message) from e + + def simplified(x): + items = set(map(sorted_tuple, x)) + if () in items: + items.remove(()) + items = [x for x in items if len(x) > 1] + items.sort() + return items + + assert_equal( + simplified(remap(objx.regions)), + simplified(obj2.regions) + ) + assert_equal( + simplified(remap(objx.ridge_vertices)), + simplified(obj2.ridge_vertices) + ) + + # XXX: compare ridge_points --- not clear exactly how to do this + + +class Test_HalfspaceIntersection: + def assert_unordered_allclose(self, arr1, arr2, rtol=1e-7): + """Check that every line in arr1 is only once in arr2""" + assert_equal(arr1.shape, arr2.shape) + + truths = np.zeros((arr1.shape[0],), dtype=bool) + for l1 in arr1: + indexes = np.nonzero((abs(arr2 - l1) < rtol).all(axis=1))[0] + assert_equal(indexes.shape, (1,)) + truths[indexes[0]] = True + assert_(truths.all()) + + @pytest.mark.parametrize("dt", [np.float64, int]) + def test_cube_halfspace_intersection(self, dt): + halfspaces = np.array([[-1, 0, 0], + [0, -1, 0], + [1, 0, -2], + [0, 1, -2]], dtype=dt) + feasible_point = np.array([1, 1], dtype=dt) + + points = np.array([[0.0, 0.0], [2.0, 0.0], [0.0, 2.0], [2.0, 2.0]]) + + hull = qhull.HalfspaceIntersection(halfspaces, feasible_point) + + assert_allclose(hull.intersections, points) + + def test_self_dual_polytope_intersection(self): + fname = os.path.join(os.path.dirname(__file__), 'data', + 'selfdual-4d-polytope.txt') + ineqs = np.genfromtxt(fname) + halfspaces = -np.hstack((ineqs[:, 1:], ineqs[:, :1])) + + feas_point = np.array([0., 0., 0., 0.]) + hs = qhull.HalfspaceIntersection(halfspaces, feas_point) + + assert_equal(hs.intersections.shape, (24, 4)) + + assert_almost_equal(hs.dual_volume, 32.0) + assert_equal(len(hs.dual_facets), 24) + for facet in hs.dual_facets: + assert_equal(len(facet), 6) + + dists = halfspaces[:, -1] + halfspaces[:, :-1].dot(feas_point) + self.assert_unordered_allclose((halfspaces[:, :-1].T/dists).T, hs.dual_points) + + points = itertools.permutations([0., 0., 0.5, -0.5]) + for point in points: + assert_equal(np.sum((hs.intersections == point).all(axis=1)), 1) + + def test_wrong_feasible_point(self): + halfspaces = np.array([[-1.0, 0.0, 0.0], + [0.0, -1.0, 0.0], + [1.0, 0.0, -1.0], + [0.0, 1.0, -1.0]]) + feasible_point = np.array([0.5, 0.5, 0.5]) + #Feasible point is (ndim,) instead of (ndim-1,) + assert_raises(ValueError, + qhull.HalfspaceIntersection, halfspaces, feasible_point) + feasible_point = np.array([[0.5], [0.5]]) + #Feasible point is (ndim-1, 1) instead of (ndim-1,) + assert_raises(ValueError, + qhull.HalfspaceIntersection, halfspaces, feasible_point) + feasible_point = np.array([[0.5, 0.5]]) + #Feasible point is (1, ndim-1) instead of (ndim-1,) + assert_raises(ValueError, + qhull.HalfspaceIntersection, halfspaces, feasible_point) + + feasible_point = np.array([-0.5, -0.5]) + #Feasible point is outside feasible region + assert_raises(qhull.QhullError, + qhull.HalfspaceIntersection, halfspaces, feasible_point) + + def test_incremental(self): + #Cube + halfspaces = np.array([[0., 0., -1., -0.5], + [0., -1., 0., -0.5], + [-1., 0., 0., -0.5], + [1., 0., 0., -0.5], + [0., 1., 0., -0.5], + [0., 0., 1., -0.5]]) + #Cut each summit + extra_normals = np.array([[1., 1., 1.], + [1., 1., -1.], + [1., -1., 1.], + [1, -1., -1.]]) + offsets = np.array([[-1.]]*8) + extra_halfspaces = np.hstack((np.vstack((extra_normals, -extra_normals)), + offsets)) + + feas_point = np.array([0., 0., 0.]) + + inc_hs = qhull.HalfspaceIntersection(halfspaces, feas_point, incremental=True) + + inc_res_hs = qhull.HalfspaceIntersection(halfspaces, feas_point, + incremental=True) + + for i, ehs in enumerate(extra_halfspaces): + inc_hs.add_halfspaces(ehs[np.newaxis, :]) + + inc_res_hs.add_halfspaces(ehs[np.newaxis, :], restart=True) + + total = np.vstack((halfspaces, extra_halfspaces[:i+1, :])) + + hs = qhull.HalfspaceIntersection(total, feas_point) + + assert_allclose(inc_hs.halfspaces, inc_res_hs.halfspaces) + assert_allclose(inc_hs.halfspaces, hs.halfspaces) + + #Direct computation and restart should have points in same order + assert_allclose(hs.intersections, inc_res_hs.intersections) + #Incremental will have points in different order than direct computation + self.assert_unordered_allclose(inc_hs.intersections, hs.intersections) + + inc_hs.close() + + def test_cube(self): + # Halfspaces of the cube: + halfspaces = np.array([[-1., 0., 0., 0.], # x >= 0 + [1., 0., 0., -1.], # x <= 1 + [0., -1., 0., 0.], # y >= 0 + [0., 1., 0., -1.], # y <= 1 + [0., 0., -1., 0.], # z >= 0 + [0., 0., 1., -1.]]) # z <= 1 + point = np.array([0.5, 0.5, 0.5]) + + hs = qhull.HalfspaceIntersection(halfspaces, point) + + # qhalf H0.5,0.5,0.5 o < input.txt + qhalf_points = np.array([ + [-2, 0, 0], + [2, 0, 0], + [0, -2, 0], + [0, 2, 0], + [0, 0, -2], + [0, 0, 2]]) + qhalf_facets = [ + [2, 4, 0], + [4, 2, 1], + [5, 2, 0], + [2, 5, 1], + [3, 4, 1], + [4, 3, 0], + [5, 3, 1], + [3, 5, 0]] + + assert len(qhalf_facets) == len(hs.dual_facets) + for a, b in zip(qhalf_facets, hs.dual_facets): + assert set(a) == set(b) # facet orientation can differ + + assert_allclose(hs.dual_points, qhalf_points) + + @pytest.mark.parametrize("k", range(1,4)) + def test_halfspace_batch(self, k): + # Test that we can add halfspaces a few at a time + big_square = np.array([[ 1., 0., -2.], + [-1., 0., -2.], + [ 0., 1., -2.], + [ 0., -1., -2.]]) + + small_square = np.array([[ 1., 0., -1.], + [-1., 0., -1.], + [ 0., 1., -1.], + [ 0., -1., -1.]]) + + hs = qhull.HalfspaceIntersection(big_square, + np.array([0.3141, 0.2718]), + incremental=True) + + hs.add_halfspaces(small_square[0:k,:]) + hs.add_halfspaces(small_square[k:4,:]) + hs.close() + + # Check the intersections are correct (they are the corners of the small square) + expected_intersections = np.array([[1., 1.], + [1., -1.], + [-1., 1.], + [-1., -1.]]) + actual_intersections = hs.intersections + # They may be in any order, so just check that under some permutation + # expected=actual. + + ind1 = np.lexsort((actual_intersections[:, 1], actual_intersections[:, 0])) + ind2 = np.lexsort((expected_intersections[:, 1], expected_intersections[:, 0])) + assert_allclose(actual_intersections[ind1], expected_intersections[ind2]) + + + @pytest.mark.parametrize("halfspaces", [ + (np.array([-0.70613882, -0.45589431, 0.04178256])), + (np.array([[-0.70613882, -0.45589431, 0.04178256], + [0.70807342, -0.45464871, -0.45969769], + [0., 0.76515026, -0.35614825]])), + ]) + def test_gh_19865(self, halfspaces): + # starting off with a feasible interior point and + # adding halfspaces for which it is no longer feasible + # should result in an error rather than a problematic + # intersection polytope + initial_square = np.array( + [[1, 0, -1], [0, 1, -1], [-1, 0, -1], [0, -1, -1]] + ) + incremental_intersector = qhull.HalfspaceIntersection(initial_square, + np.zeros(2), + incremental=True) + with pytest.raises(qhull.QhullError, match="feasible.*-0.706.*"): + incremental_intersector.add_halfspaces(halfspaces) + + + def test_gh_19865_3d(self): + # 3d case where closed half space is enforced for + # feasibility + halfspaces = np.array([[1, 1, 1, -1], # doesn't exclude origin + [-1, -1, -1, -1], # doesn't exclude origin + [1, 0, 0, 0]]) # the origin is on the line + initial_cube = np.array([[1, 0, 0, -1], + [-1, 0, 0, -1], + [0, 1, 0, -1], + [0, -1, 0, -1], + [0, 0, 1, -1], + [0, 0, -1, -1]]) + incremental_intersector = qhull.HalfspaceIntersection(initial_cube, + np.zeros(3), + incremental=True) + with pytest.raises(qhull.QhullError, match="feasible.*[1 0 0 0]"): + incremental_intersector.add_halfspaces(halfspaces) + + + def test_2d_add_halfspace_input(self): + # incrementally added halfspaces should respect the 2D + # array shape requirement + initial_square = np.array( + [[1, 0, -1], [0, 1, -1], [-1, 0, -1], [0, -1, -1]] + ) + incremental_intersector = qhull.HalfspaceIntersection(initial_square, + np.zeros(2), + incremental=True) + with pytest.raises(ValueError, match="2D array"): + incremental_intersector.add_halfspaces(np.ones((4, 4, 4))) + + def test_1d_add_halfspace_input(self): + # we do allow 1D `halfspaces` input to add_halfspaces() + initial_square = np.array( + [[1, 0, -1], [0, 1, -1], [-1, 0, -1], [0, -1, -1]] + ) + incremental_intersector = qhull.HalfspaceIntersection(initial_square, + np.zeros(2), + incremental=True) + assert_allclose(incremental_intersector.dual_vertices, np.arange(4)) + incremental_intersector.add_halfspaces(np.array([2, 2, -1])) + assert_allclose(incremental_intersector.dual_vertices, np.arange(5)) + + +@pytest.mark.parametrize("diagram_type", [Voronoi, qhull.Delaunay]) +def test_gh_20623(diagram_type): + rng = np.random.default_rng(123) + invalid_data = rng.random((4, 10, 3)) + with pytest.raises(ValueError, match="dimensions"): + diagram_type(invalid_data) + + +def test_gh_21286(): + generators = np.array([[0, 0], [0, 1.1], [1, 0], [1, 1]]) + tri = qhull.Delaunay(generators) + # verify absence of segfault reported in ticket: + with pytest.raises(IndexError): + tri.find_simplex(1) + with pytest.raises(IndexError): + # strikingly, Delaunay object has shape + # () just like np.asanyarray(1) above + tri.find_simplex(tri) + + +def test_find_simplex_ndim_err(): + generators = np.array([[0, 0], [0, 1.1], [1, 0], [1, 1]]) + tri = qhull.Delaunay(generators) + with pytest.raises(ValueError): + tri.find_simplex([2, 2, 2]) diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/tests/test_slerp.py b/venv/lib/python3.12/site-packages/scipy/spatial/tests/test_slerp.py new file mode 100644 index 0000000000000000000000000000000000000000..001701e8e85f275e61e10c59086065385cc2e516 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/tests/test_slerp.py @@ -0,0 +1,465 @@ +import math +import warnings + +import numpy as np +from numpy.testing import assert_allclose + +import pytest +from scipy.spatial import geometric_slerp + + +def _generate_spherical_points(ndim=3, n_pts=2): + # generate uniform points on sphere + # see: https://stackoverflow.com/a/23785326 + # tentatively extended to arbitrary dims + # for 0-sphere it will always produce antipodes + np.random.seed(123) + points = np.random.normal(size=(n_pts, ndim)) + points /= np.linalg.norm(points, axis=1)[:, np.newaxis] + return points[0], points[1] + + +class TestGeometricSlerp: + # Test various properties of the geometric slerp code + + @pytest.mark.parametrize("n_dims", [2, 3, 5, 7, 9]) + @pytest.mark.parametrize("n_pts", [0, 3, 17]) + def test_shape_property(self, n_dims, n_pts): + # geometric_slerp output shape should match + # input dimensionality & requested number + # of interpolation points + start, end = _generate_spherical_points(n_dims, 2) + + actual = geometric_slerp(start=start, + end=end, + t=np.linspace(0, 1, n_pts)) + + assert actual.shape == (n_pts, n_dims) + + @pytest.mark.parametrize("n_dims", [2, 3, 5, 7, 9]) + @pytest.mark.parametrize("n_pts", [3, 17]) + def test_include_ends(self, n_dims, n_pts): + # geometric_slerp should return a data structure + # that includes the start and end coordinates + # when t includes 0 and 1 ends + # this is convenient for plotting surfaces represented + # by interpolations for example + + # the generator doesn't work so well for the unit + # sphere (it always produces antipodes), so use + # custom values there + start, end = _generate_spherical_points(n_dims, 2) + + actual = geometric_slerp(start=start, + end=end, + t=np.linspace(0, 1, n_pts)) + + assert_allclose(actual[0], start) + assert_allclose(actual[-1], end) + + @pytest.mark.parametrize("start, end", [ + # both arrays are not flat + (np.zeros((1, 3)), np.ones((1, 3))), + # only start array is not flat + (np.zeros((1, 3)), np.ones(3)), + # only end array is not flat + (np.zeros(1), np.ones((3, 1))), + ]) + def test_input_shape_flat(self, start, end): + # geometric_slerp should handle input arrays that are + # not flat appropriately + with pytest.raises(ValueError, match='one-dimensional'): + geometric_slerp(start=start, + end=end, + t=np.linspace(0, 1, 10)) + + @pytest.mark.parametrize("start, end", [ + # 7-D and 3-D ends + (np.zeros(7), np.ones(3)), + # 2-D and 1-D ends + (np.zeros(2), np.ones(1)), + # empty, "3D" will also get caught this way + (np.array([]), np.ones(3)), + ]) + def test_input_dim_mismatch(self, start, end): + # geometric_slerp must appropriately handle cases where + # an interpolation is attempted across two different + # dimensionalities + with pytest.raises(ValueError, match='dimensions'): + geometric_slerp(start=start, + end=end, + t=np.linspace(0, 1, 10)) + + @pytest.mark.parametrize("start, end", [ + # both empty + (np.array([]), np.array([])), + ]) + def test_input_at_least1d(self, start, end): + # empty inputs to geometric_slerp must + # be handled appropriately when not detected + # by mismatch + with pytest.raises(ValueError, match='at least two-dim'): + geometric_slerp(start=start, + end=end, + t=np.linspace(0, 1, 10)) + + @pytest.mark.parametrize("start, end, expected", [ + # North and South Poles are definitely antipodes + # but should be handled gracefully now + (np.array([0, 0, 1.0]), np.array([0, 0, -1.0]), "warning"), + # this case will issue a warning & be handled + # gracefully as well; + # North Pole was rotated very slightly + # using r = R.from_euler('x', 0.035, degrees=True) + # to achieve Euclidean distance offset from diameter by + # 9.328908379124812e-08, within the default tol + (np.array([0.00000000e+00, + -6.10865200e-04, + 9.99999813e-01]), np.array([0, 0, -1.0]), "warning"), + # this case should succeed without warning because a + # sufficiently large + # rotation was applied to North Pole point to shift it + # to a Euclidean distance of 2.3036691931821451e-07 + # from South Pole, which is larger than tol + (np.array([0.00000000e+00, + -9.59930941e-04, + 9.99999539e-01]), np.array([0, 0, -1.0]), "success"), + ]) + def test_handle_antipodes(self, start, end, expected): + # antipodal points must be handled appropriately; + # there are an infinite number of possible geodesic + # interpolations between them in higher dims + if expected == "warning": + with pytest.warns(UserWarning, match='antipodes'): + res = geometric_slerp(start=start, + end=end, + t=np.linspace(0, 1, 10)) + else: + res = geometric_slerp(start=start, + end=end, + t=np.linspace(0, 1, 10)) + + # antipodes or near-antipodes should still produce + # slerp paths on the surface of the sphere (but they + # may be ambiguous): + assert_allclose(np.linalg.norm(res, axis=1), 1.0) + + @pytest.mark.parametrize("start, end, expected", [ + # 2-D with n_pts=4 (two new interpolation points) + # this is an actual circle + (np.array([1, 0]), + np.array([0, 1]), + np.array([[1, 0], + [np.sqrt(3) / 2, 0.5], # 30 deg on unit circle + [0.5, np.sqrt(3) / 2], # 60 deg on unit circle + [0, 1]])), + # likewise for 3-D (add z = 0 plane) + # this is an ordinary sphere + (np.array([1, 0, 0]), + np.array([0, 1, 0]), + np.array([[1, 0, 0], + [np.sqrt(3) / 2, 0.5, 0], + [0.5, np.sqrt(3) / 2, 0], + [0, 1, 0]])), + # for 5-D, pad more columns with constants + # zeros are easiest--non-zero values on unit + # circle are more difficult to reason about + # at higher dims + (np.array([1, 0, 0, 0, 0]), + np.array([0, 1, 0, 0, 0]), + np.array([[1, 0, 0, 0, 0], + [np.sqrt(3) / 2, 0.5, 0, 0, 0], + [0.5, np.sqrt(3) / 2, 0, 0, 0], + [0, 1, 0, 0, 0]])), + + ]) + def test_straightforward_examples(self, start, end, expected): + # some straightforward interpolation tests, sufficiently + # simple to use the unit circle to deduce expected values; + # for larger dimensions, pad with constants so that the + # data is N-D but simpler to reason about + actual = geometric_slerp(start=start, + end=end, + t=np.linspace(0, 1, 4)) + assert_allclose(actual, expected, atol=1e-16) + + @pytest.mark.parametrize("start, end", [ + (np.array([1]), + np.array([0])), + (np.array([0]), + np.array([1])), + (np.array([-17.7]), + np.array([165.9])), + ]) + def test_0_sphere_handling(self, start, end): + # it does not make sense to interpolate the set of + # two points that is the 0-sphere + with pytest.raises(ValueError, match='at least two-dim'): + _ = geometric_slerp(start=start, + end=end, + t=np.linspace(0, 1, 4)) + + @pytest.mark.parametrize("tol", [ + # an integer currently raises + 5, + # string raises + "7", + # list and arrays also raise + [5, 6, 7], np.array(9.0), + ]) + def test_tol_type(self, tol): + # geometric_slerp() should raise if tol is not + # a suitable float type + with pytest.raises(ValueError, match='must be a float'): + _ = geometric_slerp(start=np.array([1, 0]), + end=np.array([0, 1]), + t=np.linspace(0, 1, 5), + tol=tol) + + @pytest.mark.parametrize("tol", [ + -5e-6, + -7e-10, + ]) + def test_tol_sign(self, tol): + # geometric_slerp() currently handles negative + # tol values, as long as they are floats + _ = geometric_slerp(start=np.array([1, 0]), + end=np.array([0, 1]), + t=np.linspace(0, 1, 5), + tol=tol) + + @pytest.mark.parametrize("start, end", [ + # 1-sphere (circle) with one point at origin + # and the other on the circle + (np.array([1, 0]), np.array([0, 0])), + # 2-sphere (normal sphere) with both points + # just slightly off sphere by the same amount + # in different directions + (np.array([1 + 1e-6, 0, 0]), + np.array([0, 1 - 1e-6, 0])), + # same thing in 4-D + (np.array([1 + 1e-6, 0, 0, 0]), + np.array([0, 1 - 1e-6, 0, 0])), + ]) + def test_unit_sphere_enforcement(self, start, end): + # geometric_slerp() should raise on input that clearly + # cannot be on an n-sphere of radius 1 + with pytest.raises(ValueError, match='unit n-sphere'): + geometric_slerp(start=start, + end=end, + t=np.linspace(0, 1, 5)) + + @pytest.mark.parametrize("start, end", [ + # 1-sphere 45 degree case + (np.array([1, 0]), + np.array([np.sqrt(2) / 2., + np.sqrt(2) / 2.])), + # 2-sphere 135 degree case + (np.array([1, 0]), + np.array([-np.sqrt(2) / 2., + np.sqrt(2) / 2.])), + ]) + @pytest.mark.parametrize("t_func", [ + np.linspace, np.logspace]) + def test_order_handling(self, start, end, t_func): + # geometric_slerp() should handle scenarios with + # ascending and descending t value arrays gracefully; + # results should simply be reversed + + # for scrambled / unsorted parameters, the same values + # should be returned, just in scrambled order + + num_t_vals = 20 + np.random.seed(789) + forward_t_vals = t_func(0, 10, num_t_vals) + # normalize to max of 1 + forward_t_vals /= forward_t_vals.max() + reverse_t_vals = np.flipud(forward_t_vals) + shuffled_indices = np.arange(num_t_vals) + np.random.shuffle(shuffled_indices) + scramble_t_vals = forward_t_vals.copy()[shuffled_indices] + + forward_results = geometric_slerp(start=start, + end=end, + t=forward_t_vals) + reverse_results = geometric_slerp(start=start, + end=end, + t=reverse_t_vals) + scrambled_results = geometric_slerp(start=start, + end=end, + t=scramble_t_vals) + + # check fidelity to input order + assert_allclose(forward_results, np.flipud(reverse_results)) + assert_allclose(forward_results[shuffled_indices], + scrambled_results) + + @pytest.mark.parametrize("t", [ + # string: + "15, 5, 7", + # complex numbers currently produce a warning + # but not sure we need to worry about it too much: + # [3 + 1j, 5 + 2j], + ]) + def test_t_values_conversion(self, t): + with pytest.raises(ValueError): + _ = geometric_slerp(start=np.array([1]), + end=np.array([0]), + t=t) + + def test_accept_arraylike(self): + # array-like support requested by reviewer + # in gh-10380 + actual = geometric_slerp([1, 0], [0, 1], [0, 1/3, 0.5, 2/3, 1]) + + # expected values are based on visual inspection + # of the unit circle for the progressions along + # the circumference provided in t + expected = np.array([[1, 0], + [np.sqrt(3) / 2, 0.5], + [np.sqrt(2) / 2, + np.sqrt(2) / 2], + [0.5, np.sqrt(3) / 2], + [0, 1]], dtype=np.float64) + # Tyler's original Cython implementation of geometric_slerp + # can pass at atol=0 here, but on balance we will accept + # 1e-16 for an implementation that avoids Cython and + # makes up accuracy ground elsewhere + assert_allclose(actual, expected, atol=1e-16) + + def test_scalar_t(self): + # when t is a scalar, return value is a single + # interpolated point of the appropriate dimensionality + # requested by reviewer in gh-10380 + actual = geometric_slerp([1, 0], [0, 1], 0.5) + expected = np.array([np.sqrt(2) / 2, + np.sqrt(2) / 2], dtype=np.float64) + assert actual.shape == (2,) + assert_allclose(actual, expected) + + @pytest.mark.parametrize('start', [ + np.array([1, 0, 0]), + np.array([0, 1]), + ]) + @pytest.mark.parametrize('t', [ + np.array(1), + np.array([1]), + np.array([[1]]), + np.array([[[1]]]), + np.array([]), + np.linspace(0, 1, 5), + ]) + def test_degenerate_input(self, start, t): + if np.asarray(t).ndim > 1: + with pytest.raises(ValueError): + geometric_slerp(start=start, end=start, t=t) + else: + + shape = (t.size,) + start.shape + expected = np.full(shape, start) + + actual = geometric_slerp(start=start, end=start, t=t) + assert_allclose(actual, expected) + + # Check that degenerate and non-degenerate + # inputs yield the same size + non_degenerate = geometric_slerp(start=start, end=start[::-1], t=t) + assert actual.size == non_degenerate.size + + @pytest.mark.parametrize('k', np.logspace(-10, -1, 10)) + def test_numerical_stability_pi(self, k): + # geometric_slerp should have excellent numerical + # stability for angles approaching pi between + # the start and end points + angle = np.pi - k + ts = np.linspace(0, 1, 100) + P = np.array([1, 0, 0, 0]) + Q = np.array([np.cos(angle), np.sin(angle), 0, 0]) + # the test should only be enforced for cases where + # geometric_slerp determines that the input is actually + # on the unit sphere + with warnings.catch_warnings(): + warnings.simplefilter("ignore", UserWarning) + result = geometric_slerp(P, Q, ts, 1e-18) + norms = np.linalg.norm(result, axis=1) + error = np.max(np.abs(norms - 1)) + assert error < 4e-15 + + @pytest.mark.parametrize('t', [ + [[0, 0.5]], + [[[[[[[[[0, 0.5]]]]]]]]], + ]) + def test_interpolation_param_ndim(self, t): + # regression test for gh-14465 + arr1 = np.array([0, 1]) + arr2 = np.array([1, 0]) + + with pytest.raises(ValueError): + geometric_slerp(start=arr1, + end=arr2, + t=t) + + with pytest.raises(ValueError): + geometric_slerp(start=arr1, + end=arr1, + t=t) + + + @pytest.mark.parametrize("start, end, t, expected", [ + # verify intuitive cases for t > 1 or < 0 (extrapolation) + + # quarter way around the unit circle, doubling to + # half way around (allowed antipode since non-ambiguous + # geodesic): + ([0, 1], [1, 0], 2, [0, -1]), + # 1/8 way around the unit circle, quadrupling to half way + # (another allowed non-ambiguous antipode) + ([0, 1], [math.sqrt(2)/2, math.sqrt(2)/2], 4, [0, -1]), + # 1/8 way around the unit circle, 6x to 3/4 way around (270 deg) + ([0, 1], [math.sqrt(2)/2, math.sqrt(2)/2], 6, [-1, 0]), + # 1/8 way around the unit circle, 7x to 315 deg + ([0, 1], [math.sqrt(2)/2, math.sqrt(2)/2], 7, + [-math.sqrt(2)/2, math.sqrt(2)/2]), + # the above three extrapolations in reverse order should be returned + # in that same order: + ([0, 1], + [math.sqrt(2)/2, math.sqrt(2)/2], + [7, 6, 4], + [[-math.sqrt(2)/2, math.sqrt(2)/2], + [-1, 0], + [0, -1]]), + # same case in 3d (sphere), in the xy plane: + ([0, 1, 0], + [math.sqrt(2)/2, math.sqrt(2)/2, 0], + [7, 6, 4], + [[-math.sqrt(2)/2, math.sqrt(2)/2, 0], + [-1, 0, 0], + [0, -1, 0]]), + # 1/8 way around the unit circle, moving 300 deg backwards + # should end up at pi/6 (since there is no forward travel with + # negative extrapolation) + ([0, 1], [math.sqrt(2)/2, math.sqrt(2)/2], (-300/45), [math.sqrt(3)/2, 0.5]), + ]) + def test_extrapolation_basic(self, start, end, t, expected): + actual_path = geometric_slerp(start=start, + end=end, + t=t) + assert_allclose(actual_path, expected, atol=2e-16) + + @pytest.mark.parametrize("start, end, t, expected", [ + # cases where start and end proper are antipodes + # and t > 1 or < 0; these cases should issue a warning due to + # geodesic ambiguity + + # North to South on unit sphere, then back to North again; + # there are an infinite number of possible routes, but they all + # return back to North + ([0, 0, 1], [0, 0, -1], -2, [0, 0, 1]), + # move "backwards" to South pole via an infinite number + # of possible routes + ([0, 0, 1], [0, 0, -1], -1, [0, 0, -1]), + ]) + def test_extrapolation_antipodes(self, start, end, t, expected): + with pytest.warns(UserWarning, match='antipodes'): + actual_path = geometric_slerp(start=start, end=end, t=t) + assert_allclose(actual_path, expected, atol=2.8e-16) diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/tests/test_spherical_voronoi.py b/venv/lib/python3.12/site-packages/scipy/spatial/tests/test_spherical_voronoi.py new file mode 100644 index 0000000000000000000000000000000000000000..8bf4764e9e37312d08f25870327457107b9dcd91 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/tests/test_spherical_voronoi.py @@ -0,0 +1,358 @@ +import numpy as np +import itertools +from numpy.testing import (assert_equal, + assert_almost_equal, + assert_array_equal, + assert_array_almost_equal) +import pytest +from pytest import raises as assert_raises +from scipy.spatial import SphericalVoronoi, distance +from scipy.optimize import linear_sum_assignment +from scipy.constants import golden as phi +from scipy.special import gamma + + +TOL = 1E-10 + + +def _generate_tetrahedron(): + return np.array([[1, 1, 1], [1, -1, -1], [-1, 1, -1], [-1, -1, 1]]) + + +def _generate_cube(): + return np.array(list(itertools.product([-1, 1.], repeat=3))) + + +def _generate_octahedron(): + return np.array([[-1, 0, 0], [+1, 0, 0], [0, -1, 0], + [0, +1, 0], [0, 0, -1], [0, 0, +1]]) + + +def _generate_dodecahedron(): + + x1 = _generate_cube() + x2 = np.array([[0, -phi, -1 / phi], + [0, -phi, +1 / phi], + [0, +phi, -1 / phi], + [0, +phi, +1 / phi]]) + x3 = np.array([[-1 / phi, 0, -phi], + [+1 / phi, 0, -phi], + [-1 / phi, 0, +phi], + [+1 / phi, 0, +phi]]) + x4 = np.array([[-phi, -1 / phi, 0], + [-phi, +1 / phi, 0], + [+phi, -1 / phi, 0], + [+phi, +1 / phi, 0]]) + return np.concatenate((x1, x2, x3, x4)) + + +def _generate_icosahedron(): + x = np.array([[0, -1, -phi], + [0, -1, +phi], + [0, +1, -phi], + [0, +1, +phi]]) + return np.concatenate([np.roll(x, i, axis=1) for i in range(3)]) + + +def _generate_polytope(name): + polygons = ["triangle", "square", "pentagon", "hexagon", "heptagon", + "octagon", "nonagon", "decagon", "undecagon", "dodecagon"] + polyhedra = ["tetrahedron", "cube", "octahedron", "dodecahedron", + "icosahedron"] + if name not in polygons and name not in polyhedra: + raise ValueError("unrecognized polytope") + + if name in polygons: + n = polygons.index(name) + 3 + thetas = np.linspace(0, 2 * np.pi, n, endpoint=False) + p = np.vstack([np.cos(thetas), np.sin(thetas)]).T + elif name == "tetrahedron": + p = _generate_tetrahedron() + elif name == "cube": + p = _generate_cube() + elif name == "octahedron": + p = _generate_octahedron() + elif name == "dodecahedron": + p = _generate_dodecahedron() + elif name == "icosahedron": + p = _generate_icosahedron() + + return p / np.linalg.norm(p, axis=1, keepdims=True) + + +def _hypersphere_area(dim, radius): + # https://en.wikipedia.org/wiki/N-sphere#Closed_forms + return 2 * np.pi**(dim / 2) / gamma(dim / 2) * radius**(dim - 1) + + +def _sample_sphere(n, dim, seed=None): + # Sample points uniformly at random from the hypersphere + rng = np.random.RandomState(seed=seed) + points = rng.randn(n, dim) + points /= np.linalg.norm(points, axis=1, keepdims=True) + return points + + +class TestSphericalVoronoi: + + def setup_method(self): + self.points = np.array([ + [-0.78928481, -0.16341094, 0.59188373], + [-0.66839141, 0.73309634, 0.12578818], + [0.32535778, -0.92476944, -0.19734181], + [-0.90177102, -0.03785291, -0.43055335], + [0.71781344, 0.68428936, 0.12842096], + [-0.96064876, 0.23492353, -0.14820556], + [0.73181537, -0.22025898, -0.6449281], + [0.79979205, 0.54555747, 0.25039913]] + ) + + def test_constructor(self): + center = np.array([1, 2, 3]) + radius = 2 + s1 = SphericalVoronoi(self.points) + # user input checks in SphericalVoronoi now require + # the radius / center to match the generators so adjust + # accordingly here + s2 = SphericalVoronoi(self.points * radius, radius) + s3 = SphericalVoronoi(self.points + center, center=center) + s4 = SphericalVoronoi(self.points * radius + center, radius, center) + assert_array_equal(s1.center, np.array([0, 0, 0])) + assert_equal(s1.radius, 1) + assert_array_equal(s2.center, np.array([0, 0, 0])) + assert_equal(s2.radius, 2) + assert_array_equal(s3.center, center) + assert_equal(s3.radius, 1) + assert_array_equal(s4.center, center) + assert_equal(s4.radius, radius) + + # Test a non-sequence/-ndarray based array-like + s5 = SphericalVoronoi(memoryview(self.points)) # type: ignore[arg-type] + assert_array_equal(s5.center, np.array([0, 0, 0])) + assert_equal(s5.radius, 1) + + def test_vertices_regions_translation_invariance(self): + sv_origin = SphericalVoronoi(self.points) + center = np.array([1, 1, 1]) + sv_translated = SphericalVoronoi(self.points + center, center=center) + assert_equal(sv_origin.regions, sv_translated.regions) + assert_array_almost_equal(sv_origin.vertices + center, + sv_translated.vertices) + + def test_vertices_regions_scaling_invariance(self): + sv_unit = SphericalVoronoi(self.points) + sv_scaled = SphericalVoronoi(self.points * 2, 2) + assert_equal(sv_unit.regions, sv_scaled.regions) + assert_array_almost_equal(sv_unit.vertices * 2, + sv_scaled.vertices) + + def test_old_radius_api_error(self): + with pytest.raises(ValueError, match='`radius` is `None`. *'): + SphericalVoronoi(self.points, radius=None) + + def test_sort_vertices_of_regions(self): + sv = SphericalVoronoi(self.points) + unsorted_regions = sv.regions + sv.sort_vertices_of_regions() + assert_equal(sorted(sv.regions), sorted(unsorted_regions)) + + def test_sort_vertices_of_regions_flattened(self): + expected = sorted([[0, 6, 5, 2, 3], [2, 3, 10, 11, 8, 7], [0, 6, 4, 1], + [4, 8, 7, 5, 6], [9, 11, 10], [2, 7, 5], + [1, 4, 8, 11, 9], [0, 3, 10, 9, 1]]) + expected = list(itertools.chain(*sorted(expected))) # type: ignore + sv = SphericalVoronoi(self.points) + sv.sort_vertices_of_regions() + actual = list(itertools.chain(*sorted(sv.regions))) + assert_array_equal(actual, expected) + + def test_sort_vertices_of_regions_dimensionality(self): + points = np.array([[1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1], + [0.5, 0.5, 0.5, 0.5]]) + with pytest.raises(TypeError, match="three-dimensional"): + sv = SphericalVoronoi(points) + sv.sort_vertices_of_regions() + + def test_num_vertices(self): + # for any n >= 3, a spherical Voronoi diagram has 2n - 4 + # vertices; this is a direct consequence of Euler's formula + # as explained by Dinis and Mamede (2010) Proceedings of the + # 2010 International Symposium on Voronoi Diagrams in Science + # and Engineering + sv = SphericalVoronoi(self.points) + expected = self.points.shape[0] * 2 - 4 + actual = sv.vertices.shape[0] + assert_equal(actual, expected) + + def test_voronoi_circles(self): + sv = SphericalVoronoi(self.points) + for vertex in sv.vertices: + distances = distance.cdist(sv.points, np.array([vertex])) + closest = np.array(sorted(distances)[0:3]) + assert_almost_equal(closest[0], closest[1], 7, str(vertex)) + assert_almost_equal(closest[0], closest[2], 7, str(vertex)) + + def test_duplicate_point_handling(self): + # an exception should be raised for degenerate generators + # related to Issue# 7046 + self.degenerate = np.concatenate((self.points, self.points)) + with assert_raises(ValueError): + SphericalVoronoi(self.degenerate) + + def test_incorrect_radius_handling(self): + # an exception should be raised if the radius provided + # cannot possibly match the input generators + with assert_raises(ValueError): + SphericalVoronoi(self.points, radius=0.98) + + def test_incorrect_center_handling(self): + # an exception should be raised if the center provided + # cannot possibly match the input generators + with assert_raises(ValueError): + SphericalVoronoi(self.points, center=[0.1, 0, 0]) + + @pytest.mark.parametrize("dim", range(2, 6)) + @pytest.mark.parametrize("shift", [False, True]) + def test_single_hemisphere_handling(self, dim, shift): + n = 10 + points = _sample_sphere(n, dim, seed=0) + points[:, 0] = np.abs(points[:, 0]) + center = (np.arange(dim) + 1) * shift + sv = SphericalVoronoi(points + center, center=center) + dots = np.einsum('ij,ij->i', sv.vertices - center, + sv.points[sv._simplices[:, 0]] - center) + circumradii = np.arccos(np.clip(dots, -1, 1)) + assert np.max(circumradii) > np.pi / 2 + + @pytest.mark.parametrize("n", [1, 2, 10]) + @pytest.mark.parametrize("dim", range(2, 6)) + @pytest.mark.parametrize("shift", [False, True]) + def test_rank_deficient(self, n, dim, shift): + center = (np.arange(dim) + 1) * shift + points = _sample_sphere(n, dim - 1, seed=0) + points = np.hstack([points, np.zeros((n, 1))]) + with pytest.raises(ValueError, match="Rank of input points"): + SphericalVoronoi(points + center, center=center) + + @pytest.mark.parametrize("dim", range(2, 6)) + def test_higher_dimensions(self, dim): + n = 100 + points = _sample_sphere(n, dim, seed=0) + sv = SphericalVoronoi(points) + assert sv.vertices.shape[1] == dim + assert len(sv.regions) == n + + # verify Euler characteristic + cell_counts = [] + simplices = np.sort(sv._simplices) + for i in range(1, dim + 1): + cells = [] + for indices in itertools.combinations(range(dim), i): + cells.append(simplices[:, list(indices)]) + cells = np.unique(np.concatenate(cells), axis=0) + cell_counts.append(len(cells)) + expected_euler = 1 + (-1)**(dim-1) + actual_euler = sum([(-1)**i * e for i, e in enumerate(cell_counts)]) + assert expected_euler == actual_euler + + @pytest.mark.parametrize("dim", range(2, 6)) + def test_cross_polytope_regions(self, dim): + # The hypercube is the dual of the cross-polytope, so the voronoi + # vertices of the cross-polytope lie on the points of the hypercube. + + # generate points of the cross-polytope + points = np.concatenate((-np.eye(dim), np.eye(dim))) + sv = SphericalVoronoi(points) + assert all([len(e) == 2**(dim - 1) for e in sv.regions]) + + # generate points of the hypercube + expected = np.vstack(list(itertools.product([-1, 1], repeat=dim))) + expected = expected.astype(np.float64) / np.sqrt(dim) + + # test that Voronoi vertices are correctly placed + dist = distance.cdist(sv.vertices, expected) + res = linear_sum_assignment(dist) + assert dist[res].sum() < TOL + + @pytest.mark.parametrize("dim", range(2, 6)) + def test_hypercube_regions(self, dim): + # The cross-polytope is the dual of the hypercube, so the voronoi + # vertices of the hypercube lie on the points of the cross-polytope. + + # generate points of the hypercube + points = np.vstack(list(itertools.product([-1, 1], repeat=dim))) + points = points.astype(np.float64) / np.sqrt(dim) + sv = SphericalVoronoi(points) + + # generate points of the cross-polytope + expected = np.concatenate((-np.eye(dim), np.eye(dim))) + + # test that Voronoi vertices are correctly placed + dist = distance.cdist(sv.vertices, expected) + res = linear_sum_assignment(dist) + assert dist[res].sum() < TOL + + @pytest.mark.parametrize("n", [10, 500]) + @pytest.mark.parametrize("dim", [2, 3]) + @pytest.mark.parametrize("radius", [0.5, 1, 2]) + @pytest.mark.parametrize("shift", [False, True]) + @pytest.mark.parametrize("single_hemisphere", [False, True]) + def test_area_reconstitution(self, n, dim, radius, shift, + single_hemisphere): + points = _sample_sphere(n, dim, seed=0) + + # move all points to one side of the sphere for single-hemisphere test + if single_hemisphere: + points[:, 0] = np.abs(points[:, 0]) + + center = (np.arange(dim) + 1) * shift + points = radius * points + center + + sv = SphericalVoronoi(points, radius=radius, center=center) + areas = sv.calculate_areas() + assert_almost_equal(areas.sum(), _hypersphere_area(dim, radius)) + + @pytest.mark.parametrize("poly", ["triangle", "dodecagon", + "tetrahedron", "cube", "octahedron", + "dodecahedron", "icosahedron"]) + def test_equal_area_reconstitution(self, poly): + points = _generate_polytope(poly) + n, dim = points.shape + sv = SphericalVoronoi(points) + areas = sv.calculate_areas() + assert_almost_equal(areas, _hypersphere_area(dim, 1) / n) + + def test_area_unsupported_dimension(self): + dim = 4 + points = np.concatenate((-np.eye(dim), np.eye(dim))) + sv = SphericalVoronoi(points) + with pytest.raises(TypeError, match="Only supported"): + sv.calculate_areas() + + @pytest.mark.parametrize("radius", [1, 1.]) + @pytest.mark.parametrize("center", [None, (1, 2, 3), (1., 2., 3.)]) + def test_attribute_types(self, radius, center): + points = radius * self.points + if center is not None: + points += center + + sv = SphericalVoronoi(points, radius=radius, center=center) + assert sv.points.dtype is np.dtype(np.float64) + assert sv.center.dtype is np.dtype(np.float64) + assert isinstance(sv.radius, float) + + def test_region_types(self): + # Tests that region integer type does not change + # See Issue #13412 + sv = SphericalVoronoi(self.points) + dtype = type(sv.regions[0][0]) + # also enforce nested list type per gh-19177 + for region in sv.regions: + assert isinstance(region, list) + sv.sort_vertices_of_regions() + assert isinstance(sv.regions[0][0], dtype) + sv.sort_vertices_of_regions() + assert isinstance(sv.regions[0][0], dtype) diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/transform/__init__.py b/venv/lib/python3.12/site-packages/scipy/spatial/transform/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..1490a18e2ef0b0435fdada41f6f083aa89a3d859 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/transform/__init__.py @@ -0,0 +1,31 @@ +""" +Spatial Transformations (:mod:`scipy.spatial.transform`) +======================================================== + +.. currentmodule:: scipy.spatial.transform + +This package implements various spatial transformations. For now, rotations +and rigid transforms (rotations and translations) are supported. + +Rotations in 3 dimensions +------------------------- +.. autosummary:: + :toctree: generated/ + + RigidTransform + Rotation + Slerp + RotationSpline +""" +from ._rotation import Rotation, Slerp +from ._rigid_transform import RigidTransform +from ._rotation_spline import RotationSpline + +# Deprecated namespaces, to be removed in v2.0.0 +from . import rotation + +__all__ = ['Rotation', 'Slerp', 'RotationSpline', 'RigidTransform'] + +from scipy._lib._testutils import PytestTester +test = PytestTester(__name__) +del PytestTester diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/transform/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/spatial/transform/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..24824fedc696d36b073628a2a894d3fba9a8a480 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/spatial/transform/__pycache__/__init__.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/transform/__pycache__/_rigid_transform.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/spatial/transform/__pycache__/_rigid_transform.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..93c97bfa81e8072303f7045b4f12027077fae190 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/spatial/transform/__pycache__/_rigid_transform.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/transform/__pycache__/_rigid_transform_xp.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/spatial/transform/__pycache__/_rigid_transform_xp.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d82d9b6b8fe1ad9967b3fb945910ce213023cb8b Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/spatial/transform/__pycache__/_rigid_transform_xp.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/transform/__pycache__/_rotation.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/spatial/transform/__pycache__/_rotation.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0a06f86af379ce6999dd0af04e74969e3a0a4094 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/transform/__pycache__/_rotation.cpython-312.pyc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9ccdfc246e4bae1403d1b9d572833873fd524a3cf0b539fb88b5ea2b9ec45bad +size 113251 diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/transform/__pycache__/_rotation_groups.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/spatial/transform/__pycache__/_rotation_groups.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1b24134d3dbf6da961c9ad2daddaefd3b882431e Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/spatial/transform/__pycache__/_rotation_groups.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/transform/__pycache__/_rotation_spline.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/spatial/transform/__pycache__/_rotation_spline.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..846b072a56cd9ce37bb1ee8a1bb1b9f623f75f90 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/spatial/transform/__pycache__/_rotation_spline.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/transform/__pycache__/_rotation_xp.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/spatial/transform/__pycache__/_rotation_xp.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..442357bf362673d47357e219546bbe92f9769cee Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/spatial/transform/__pycache__/_rotation_xp.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/transform/__pycache__/rotation.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/spatial/transform/__pycache__/rotation.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6c5f505df19d90ce5a9d3a505408543dba4e6613 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/spatial/transform/__pycache__/rotation.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/transform/_rigid_transform.py b/venv/lib/python3.12/site-packages/scipy/spatial/transform/_rigid_transform.py new file mode 100644 index 0000000000000000000000000000000000000000..c6af813b622372c2fd148f186820354878c393d9 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/transform/_rigid_transform.py @@ -0,0 +1,1975 @@ +from __future__ import annotations + +from collections.abc import Iterable, Iterator +from types import EllipsisType, GenericAlias, ModuleType, NotImplementedType +from collections.abc import Callable + +import numpy as np + +from scipy._lib._array_api import ( + array_namespace, + is_numpy, + ArrayLike, + Array, + xp_capabilities, +) +from scipy.spatial.transform import Rotation +from scipy.spatial.transform._rotation import _promote +import scipy.spatial.transform._rigid_transform_cy as cython_backend +import scipy.spatial.transform._rigid_transform_xp as xp_backend +import scipy._external.array_api_extra as xpx +from scipy._external.array_api_compat import device +from scipy._lib._array_api import xp_promote +from scipy._lib._util import broadcastable + + +__all__ = ["RigidTransform"] + +backend_registry = {array_namespace(np.empty(0)): cython_backend} + + +def select_backend(xp: ModuleType, cython_compatible: bool): + """Select the backend for the given array library. + + We need this selection function because the Cython backend for numpy does not + support quaternions of arbitrary dimensions. We therefore only use the Array API + backend for numpy if we are dealing with rotations of more than one leading + dimension. + """ + if is_numpy(xp) and not cython_compatible: + return xp_backend + return backend_registry.get(xp, xp_backend) + + +@xp_capabilities() +def normalize_dual_quaternion(dual_quat: ArrayLike) -> Array: + """Normalize dual quaternion.""" + xp = array_namespace(dual_quat) + dual_quat = _promote(dual_quat, xp=xp) + single = dual_quat.ndim == 1 and is_numpy(xp) + if single: + dual_quat = xpx.atleast_nd(dual_quat, ndim=2, xp=xp) + cython_compatible = dual_quat.ndim < 3 + dq = select_backend(xp, cython_compatible).normalize_dual_quaternion(dual_quat) + if single: + return dq[0] + return dq + + +rigid_transform_extra_note = ( + """The method ``apply`` is not supported with cupy<14.*. + + """) + + +@xp_capabilities( + skip_backends=[("dask.array", "missing linalg.cross/det functions")], + method_capabilities={ + "__init__": dict( + jax_jit=False, + skip_backends=[("dask.array", "missing linalg.cross/det functions")], + ), + "from_matrix": dict( + jax_jit=False, + skip_backends=[("dask.array", "missing linalg.cross/det functions")], + ), + "__getitem__": dict( + jax_jit=False, + skip_backends=[ + ("dask.array", "cannot handle zero-length rigid transforms") + ], + ), + "__setitem__": dict( + jax_jit=False, + skip_backends=[ + ("dask.array", "cannot handle zero-length rigid transforms") + ], + ), + "apply": dict( + skip_backends=[ + ("dask.array", "missing linalg.cross/det functions"), + ("cupy", "missing .mT attribute in cupy<14.*"), + ], + ), + }, + extra_note=rigid_transform_extra_note, +) +class RigidTransform: + """ + Rigid transform in 3 dimensions. + + This class provides an interface to initialize from and represent rigid + transforms (rotation and translation) in 3D space. In different fields, + this type of transform may be referred to as "*pose*" (especially in + robotics), "*extrinsic parameters*", or the "*model matrix*" (especially in + computer graphics), but the core concept is the same: a rotation and + translation describing the orientation of one 3D coordinate frame relative + to another. Mathematically, these transforms belong to the Special + Euclidean group SE(3), which encodes rotation (SO(3)) plus translation. + + The following operations on rigid transforms are supported: + + - Application on vectors + - Transformation composition + - Transformation inversion + - Transformation indexing + + Note that coordinate systems must be right-handed. Because of this, this + class more precisely represents *proper* rigid transformations in SE(3) + rather than rigid transforms in E(3) more generally [1]_. + + Indexing within a transform is supported since multiple transforms can be + stored within a single `RigidTransform` instance. + + Multiple transforms can be stored in a single `RigidTransform` object, which can be + initialized using N-dimensional arrays and supports broadcasting for all + operations. + + To create `RigidTransform` objects use ``from_...`` methods (see examples + below). ``RigidTransform(...)`` is not supposed to be instantiated directly. + + For rigorous introductions to rigid transforms, see [2]_, [3]_, and [4]_. + + Parameters + ---------- + matrix : array_like, shape (..., 4, 4) + A single transformation matrix or a stack of transformation + matrices. + normalize : bool, optional + If True, orthonormalize the rotation matrix using singular value + decomposition. If False, the rotation matrix is not checked for + orthogonality or right-handedness. + copy : bool, optional + If True, copy the input matrix. If False, a reference to the input + matrix is used. If normalize is True, the input matrix is always + copied regardless of the value of copy. + + Attributes + ---------- + single + rotation + translation + + Methods + ------- + __len__ + __getitem__ + __mul__ + __pow__ + from_matrix + from_rotation + from_translation + from_components + from_exp_coords + from_dual_quat + as_matrix + as_components + as_exp_coords + as_dual_quat + concatenate + mean + apply + inv + identity + + Notes + ----- + .. versionadded:: 1.16.0 + + References + ---------- + .. [1] https://en.wikipedia.org/wiki/Rigid_transformation + .. [2] https://motion.cs.illinois.edu/RoboticSystems/CoordinateTransformations.html + .. [3] https://www.brainvoyager.com/bv/doc/UsersGuide/CoordsAndTransforms/SpatialTransformationMatrices.html + .. [4] Kevin M. Lynch and Frank C. Park, "Modern Robotics: Mechanics, + Planning, and Control" Chapter 3.3, 2017, Cambridge University Press. + https://hades.mech.northwestern.edu/images/2/25/MR-v2.pdf#page=107.31 + .. [5] Paul Furgale, "Representing Robot Pose: The good, the bad, and the + ugly", June 9, 2014. + https://rpg.ifi.uzh.ch/docs/teaching/2024/FurgaleTutorial.pdf + + Examples + -------- + A `RigidTransform` instance can be initialized in any of the above formats + and converted to any of the others. The underlying object is independent of + the representation used for initialization. + + **Notation Conventions and Composition** + + The notation here largely follows the convention defined in [5]_. When we + name transforms, we read the subscripts from right to left. So ``tf_A_B`` + represents a transform A <- B and can be interpreted as: + + - the coordinates and orientation of B relative to A + - the transformation of points from B to A + - the pose of B described in A's coordinate system + + .. parsed-literal:: + :class: highlight-none + + tf_A_B + ^ ^ + | | + | --- from B + | + ----- to A + + When composing transforms, the order is important. Transforms are not + commutative, so in general ``tf_A_B * tf_B_C`` is not the same as + ``tf_B_C * tf_A_B``. Transforms are composed and applied to vectors + right-to-left. So ``(tf_A_B * tf_B_C).apply(p_C)`` is the same as + ``tf_A_B.apply(tf_B_C.apply(p_C))``. + + When composed, transforms should be ordered such that the multiplication + operator is surrounded by a single frame, so the frame "cancels out" and + the outside frames are left. In the example below, B cancels out and the + outside frames A and C are left. Or to put it another way, A <- C is the + same as A <- B <- C. + + .. parsed-literal:: + :class: highlight-none + + ----------- B cancels out + | | + v v + tf_A_C = tf_A_B * tf_B_C + ^ ^ + | | + ------------ to A, from C are left + + When we notate vectors, we write the subscript of the frame that the vector + is defined in. So ``p_B`` means the point ``p`` defined in frame B. To + transform this point from frame B to coordinates in frame A, we apply the + transform ``tf_A_B`` to the vector, lining things up such that the notated + B frames are next to each other and "cancel out". + + .. parsed-literal:: + :class: highlight-none + + ------------ B cancels out + | | + v v + p_A = tf_A_B.apply(p_B) + ^ + | + -------------- A is left + + **Visualization** + + >>> from scipy.spatial.transform import RigidTransform as Tf + >>> from scipy.spatial.transform import Rotation as R + >>> import numpy as np + + The following function can be used to plot transforms with Matplotlib + by showing how they transform the standard x, y, z coordinate axes: + + >>> import matplotlib.pyplot as plt + >>> colors = ("#FF6666", "#005533", "#1199EE") # Colorblind-safe RGB + >>> def plot_transformed_axes(ax, tf, name=None, scale=1): + ... r = tf.rotation + ... t = tf.translation + ... loc = np.array([t, t]) + ... for i, (axis, c) in enumerate(zip((ax.xaxis, ax.yaxis, ax.zaxis), + ... colors)): + ... axlabel = axis.axis_name + ... axis.set_label_text(axlabel) + ... axis.label.set_color(c) + ... axis.line.set_color(c) + ... axis.set_tick_params(colors=c) + ... line = np.zeros((2, 3)) + ... line[1, i] = scale + ... line_rot = r.apply(line) + ... line_plot = line_rot + loc + ... ax.plot(line_plot[:, 0], line_plot[:, 1], line_plot[:, 2], c) + ... text_loc = line[1]*1.2 + ... text_loc_rot = r.apply(text_loc) + ... text_plot = text_loc_rot + t + ... ax.text(*text_plot, axlabel.upper(), color=c, + ... va="center", ha="center") + ... ax.text(*tf.translation, name, color="k", va="center", ha="center", + ... bbox={"fc": "w", "alpha": 0.8, "boxstyle": "circle"}) + + **Defining Frames** + + Let's work through an example. + + First, define the "world frame" A, also called the "base frame". + All frames are the identity transform from their own perspective. + + >>> tf_A = Tf.identity() + + We will visualize a new frame B in A's coordinate system. So we need to + define the transform that converts coordinates from frame B to frame A + (A <- B). + + Physically, let's imagine constructing B from A by: + + 1) Rotating A by +90 degrees around its x-axis. + 2) Translating the rotated frame 2 units in A's -x direction. + + From A's perspective, B is at [-2, 0, 0] and rotated +90 degrees about the + x-axis, which is exactly the transform A <- B. + + >>> t_A_B = np.array([-2, 0, 0]) + >>> r_A_B = R.from_euler('xyz', [90, 0, 0], degrees=True) + >>> tf_A_B = Tf.from_components(t_A_B, r_A_B) + + Let's plot these frames. + + >>> fig, ax = plt.subplots(subplot_kw={"projection": "3d"}) + >>> plot_transformed_axes(ax, tf_A, name="tfA") # A plotted in A + >>> plot_transformed_axes(ax, tf_A_B, name="tfAB") # B plotted in A + >>> ax.set_title("A, B frames with respect to A") + >>> ax.set_aspect("equal") + >>> ax.figure.set_size_inches(6, 5) + >>> plt.show() + + Now let's visualize a new frame C in B's coordinate system. + Let's imagine constructing C from B by: + + 1) Translating B by 2 units in its +z direction. + 2) Rotating B by +30 degrees around its z-axis. + + >>> t_B_C = np.array([0, 0, 2]) + >>> r_B_C = R.from_euler('xyz', [0, 0, 30], degrees=True) + >>> tf_B_C = Tf.from_components(t_B_C, r_B_C) + + In order to plot these frames from a consistent perspective, we need to + calculate the transform between A and C. Note that we do not make this + transform directly, but instead compose intermediate transforms that let us + get from C to A: + + >>> tf_A_C = tf_A_B * tf_B_C # A <- B <- C + + Now we can plot these three frames from A's perspective. + + >>> fig, ax = plt.subplots(subplot_kw={"projection": "3d"}) + >>> plot_transformed_axes(ax, tf_A, name="tfA") # A plotted in A + >>> plot_transformed_axes(ax, tf_A_B, name="tfAB") # B plotted in A + >>> plot_transformed_axes(ax, tf_A_C, name="tfAC") # C plotted in A + >>> ax.set_title("A, B, C frames with respect to A") + >>> ax.set_aspect("equal") + >>> ax.figure.set_size_inches(6, 5) + >>> plt.show() + + **Transforming Vectors** + + Let's transform a vector from A, to B and C. To do this, we will first + invert the transforms we already have from B and C, to A. + + >>> tf_B_A = tf_A_B.inv() # B <- A + >>> tf_C_A = tf_A_C.inv() # C <- A + + Now we can define a point in A and use the above transforms to get its + coordinates in B and C: + + >>> p1_A = np.array([1, 0, 0]) # +1 in x_A direction + >>> p1_B = tf_B_A.apply(p1_A) + >>> p1_C = tf_C_A.apply(p1_A) + >>> print(p1_A) # Original point 1 in A + [1 0 0] + >>> print(p1_B) # Point 1 in B + [3. 0. 0.] + >>> print(p1_C) # Point 1 in C + [ 2.59807621 -1.5 -2. ] + + We can also do the reverse. We define a point in C and transform it to A: + + >>> p2_C = np.array([0, 1, 0]) # +1 in y_C direction + >>> p2_A = tf_A_C.apply(p2_C) + >>> print(p2_C) # Original point 2 in C + [0 1 0] + >>> print(p2_A) # Point 2 in A + [-2.5 -2. 0.8660254] + + Plot the frames with respect to A again, but also plot these two points: + + >>> fig, ax = plt.subplots(subplot_kw={"projection": "3d"}) + >>> plot_transformed_axes(ax, tf_A, name="tfA") # A plotted in A + >>> plot_transformed_axes(ax, tf_A_B, name="tfAB") # B plotted in A + >>> plot_transformed_axes(ax, tf_A_C, name="tfAC") # C plotted in A + >>> ax.scatter(p1_A[0], p1_A[1], p1_A[2], color=colors[0]) # +1 x_A + >>> ax.scatter(p2_A[0], p2_A[1], p2_A[2], color=colors[1]) # +1 y_C + >>> ax.set_title("A, B, C frames and points with respect to A") + >>> ax.set_aspect("equal") + >>> ax.figure.set_size_inches(6, 5) + >>> plt.show() + + **Switching Base Frames** + + Up to this point, we have been visualizing frames from A's perspective. + Let's use the transforms we defined to visualize the frames from C's + perspective. + + Now C is the "base frame" or "world frame". All frames are the identity + transform from their own perspective. + + >>> tf_C = Tf.identity() + + We've already defined the transform C <- A, and can obtain C <- B by + inverting the existing transform B <- C. + + >>> tf_C_B = tf_B_C.inv() # C <- B + + This lets us plot everything from C's perspective: + + >>> fig, ax = plt.subplots(subplot_kw={"projection": "3d"}) + >>> plot_transformed_axes(ax, tf_C, name="tfC") # C plotted in C + >>> plot_transformed_axes(ax, tf_C_B, name="tfCB") # B plotted in C + >>> plot_transformed_axes(ax, tf_C_A, name="tfCA") # A plotted in C + >>> ax.scatter(p1_C[0], p1_C[1], p1_C[2], color=colors[0]) + >>> ax.scatter(p2_C[0], p2_C[1], p2_C[2], color=colors[1]) + >>> ax.set_title("A, B, C frames and points with respect to C") + >>> ax.set_aspect("equal") + >>> ax.figure.set_size_inches(6, 5) + >>> plt.show() + """ + + # generic type compatibility with scipy-stubs + __class_getitem__: classmethod = classmethod(GenericAlias) + + def __init__(self, matrix: ArrayLike, normalize: bool = True, copy: bool = True): + """Initialize from a 4x4 transformation matrix. + + Rotations are not meant to be initialized directly. Please use one of + the `from_...` methods instead. + + Parameters + ---------- + matrix : array_like, shape (..., 4, 4) + A single transformation matrix or a stack of transformation + matrices. + normalize : bool, optional + If True, orthonormalize the rotation matrix using singular value + decomposition. If False, the rotation matrix is not checked for + orthogonality or right-handedness. + copy : bool, optional + If True, copy the input matrix. If False, a reference to the input + matrix is used. If normalize is True, the input matrix is always + copied regardless of the value of copy. + + Returns + ------- + transform : `RigidTransform` instance + """ + xp = array_namespace(matrix) + self._xp = xp + matrix = _promote(matrix, xp=xp) + if matrix.shape[-2:] != (4, 4): + raise ValueError( + f"Expected `matrix` to have shape (..., 4, 4), got {matrix.shape}." + ) + # We only need the _single flag for the cython backend. The Array API backend + # uses broadcasting by default and hence returns the correct shape without + # additional _single logic + self._single = matrix.ndim == 2 and is_numpy(xp) + if self._single: + matrix = xpx.atleast_nd(matrix, ndim=3, xp=xp) + + self._backend = select_backend(xp, matrix.ndim < 4) + self._matrix = self._backend.from_matrix(matrix, normalize, copy) + + def __repr__(self): + m = f"{self.as_matrix()!r}".splitlines() + # bump indent (+27 characters) + m[1:] = [" " * 27 + m[i] for i in range(1, len(m))] + return "RigidTransform.from_matrix(" + "\n".join(m) + ")" + + @staticmethod + def from_matrix(matrix: ArrayLike) -> RigidTransform: + """Initialize from a 4x4 transformation matrix. + + Parameters + ---------- + matrix : array_like, shape (..., 4, 4) + Transformation matrices. Each matrix[..., :, :] represents a 4x4 + transformation matrix. + + Returns + ------- + transform : `RigidTransform` instance + Object containing the rigid transform represented by the input matrix. + + Notes + ----- + 4x4 rigid transformation matrices are of the form:: + + [ tx] + [ R ty] + [ tz] + [ 0 0 0 1] + + where ``R`` is a 3x3 rotation matrix and ``t = [tx, ty, tz]`` is a 3x1 + translation vector. As rotation matrices must be proper orthogonal, the + rotation component is orthonormalized using singular value decomposition + before initialization. + + Examples + -------- + >>> from scipy.spatial.transform import RigidTransform as Tf + >>> import numpy as np + + Creating a transform from a single matrix: + + >>> m = np.array([[0, 1, 0, 2], + ... [0, 0, 1, 3], + ... [1, 0, 0, 4], + ... [0, 0, 0, 1]]) + >>> tf = Tf.from_matrix(m) + >>> tf.as_matrix() + array([[0., 1., 0., 2.], + [0., 0., 1., 3.], + [1., 0., 0., 4.], + [0., 0., 0., 1.]]) + >>> tf.single + True + + Creating a transform from an N-dimensional array of matrices: + + >>> m = np.tile(np.eye(4), (2, 5, 1, 1)) # Shape (2, 5, 4, 4) + >>> tf = Tf.from_matrix(m) + >>> tf.shape + (2, 5) + >>> tf.single + False + >>> len(tf) + 2 + + Matrices with a rotation component that is not proper orthogonal are + orthogonalized using singular value decomposition before initialization: + + >>> tf = Tf.from_matrix(np.diag([2, 2, 2, 1])) + >>> tf.as_matrix() + array([[1., 0., 0., 0.], + [0., 1., 0., 0.], + [0., 0., 1., 0.], + [0., 0., 0., 1.]]) + """ + return RigidTransform(matrix, normalize=True, copy=True) + + @staticmethod + def from_rotation(rotation: Rotation) -> RigidTransform: + """Initialize from a rotation, without a translation. + + When applying this transform to a vector ``v``, the result is the + same as if the rotation was applied to the vector. + ``Tf.from_rotation(r).apply(v) == r.apply(v)`` + + Parameters + ---------- + rotation : `Rotation` instance + A single rotation or a rotation with N leading dimensions. + + Returns + ------- + transform : `RigidTransform` instance + Object containing the rigid transform represented by the input rotation. + + Examples + -------- + >>> from scipy.spatial.transform import RigidTransform as Tf + >>> from scipy.spatial.transform import Rotation as R + >>> import numpy as np + + Creating a transform from a single rotation: + + >>> r = R.from_euler("ZYX", [90, 30, 0], degrees=True) + >>> r.apply([1, 0, 0]) + array([0. , 0.8660254, -0.5 ]) + >>> tf = Tf.from_rotation(r) + >>> tf.apply([1, 0, 0]) + array([0. , 0.8660254, -0.5 ]) + >>> tf.single + True + + The upper 3x3 submatrix of the transformation matrix is the rotation + matrix: + + >>> np.allclose(tf.as_matrix()[:3, :3], r.as_matrix(), atol=1e-12) + True + + Creating multiple transforms from a rotation with N leading dimensions: + + >>> r = R.from_euler("ZYX", [[90, 30, 0], [45, 30, 60]], degrees=True) + >>> r.apply([1, 0, 0]) + array([[0. , 0.8660254 , -0.5 ], + [0.61237244, 0.61237244, -0.5 ]]) + >>> tf = Tf.from_rotation(r) + >>> tf.apply([1, 0, 0]) + array([[0. , 0.8660254 , -0.5 ], + [0.61237244, 0.61237244, -0.5 ]]) + >>> tf.single + False + >>> len(tf) + 2 + """ + if not isinstance(rotation, Rotation): + raise TypeError( + "Expected `rotation` to be a `Rotation` instance, " + f"got {type(rotation)}." + ) + quat = rotation.as_quat() + xp = array_namespace(quat) + backend = select_backend(xp, quat.ndim < 3) + matrix = backend.from_rotation(quat) + return RigidTransform._from_raw_matrix(matrix, xp, backend) + + @staticmethod + def from_translation(translation: ArrayLike) -> RigidTransform: + """Initialize from a translation numpy array, without a rotation. + + When applying this transform to a vector ``v``, the result is the same + as if the translation and vector were added together. If ``t`` is the + displacement vector of the translation, then: + + ``Tf.from_translation(t).apply(v) == t + v`` + + Parameters + ---------- + translation : array_like, shape (..., 3) + Translation vectors. Each translation[..., :] represents a 3D + translation vector. + + Returns + ------- + transform : `RigidTransform` instance + Object containing the rigid transform represented by the input translation. + + Examples + -------- + >>> from scipy.spatial.transform import RigidTransform as Tf + >>> import numpy as np + + Creating a transform from a single translation vector: + + >>> t = np.array([2, 3, 4]) + >>> t + np.array([1, 0, 0]) + array([3, 3, 4]) + >>> tf = Tf.from_translation(t) + >>> tf.apply([1, 0, 0]) + array([3., 3., 4.]) + >>> tf.single + True + + The top 3x1 points in the rightmost column of the transformation matrix + is the translation vector: + + >>> tf.as_matrix() + array([[1., 0., 0., 2.], + [0., 1., 0., 3.], + [0., 0., 1., 4.], + [0., 0., 0., 1.]]) + >>> np.allclose(tf.as_matrix()[:3, 3], t) + True + + Creating multiple transforms from an N-dimensional array of translation + vectors: + + >>> t = np.array([[2, 3, 4], [1, 0, 0]]) + >>> t + np.array([1, 0, 0]) + array([[3, 3, 4], + [2, 0, 0]]) + >>> tf = Tf.from_translation(t) + >>> tf.apply([1, 0, 0]) + array([[3., 3., 4.], + [2., 0., 0.]]) + >>> np.allclose(tf.as_matrix()[:, :3, 3], t) + True + >>> tf.single + False + >>> len(tf) + 2 + """ + xp = array_namespace(translation) + translation = _promote(translation, xp=xp) + backend = select_backend(xp, translation.ndim < 3) + matrix = backend.from_translation(translation) + return RigidTransform._from_raw_matrix(matrix, xp, backend) + + @staticmethod + def from_components(translation: ArrayLike, rotation: Rotation) -> RigidTransform: + """Initialize a rigid transform from translation and rotation + components. + + When creating a rigid transform from a translation and rotation, the + translation is applied after the rotation, such that + ``tf = Tf.from_components(translation, rotation)`` + is equivalent to + ``tf = Tf.from_translation(translation) * Tf.from_rotation(rotation)``. + + When applying a transform to a vector ``v``, the result is the + same as if the transform was applied to the vector in the + following way: ``tf.apply(v) == translation + rotation.apply(v)`` + + Parameters + ---------- + translation : array_like, shape (..., 3) + Translation vectors. Each translation[..., :] represents a 3D + translation vector. + rotation : `Rotation` instance + Rotation objects. The shape must be broadcastable with the + translation shape. + + Returns + ------- + transform : `RigidTransform` instance + Rigid transform objects. The shape is determined by broadcasting + the translation and rotation shapes together. + + Examples + -------- + >>> from scipy.spatial.transform import RigidTransform as Tf + >>> from scipy.spatial.transform import Rotation as R + >>> import numpy as np + + Creating from a single rotation and translation: + + >>> t = np.array([2, 3, 4]) + >>> r = R.from_euler("ZYX", [90, 30, 0], degrees=True) + >>> r.as_matrix() + array([[ 0. , -1., 0. ], + [ 0.8660254, 0., 0.5 ], + [-0.5 , 0., 0.8660254 ]]) + >>> tf = Tf.from_components(t, r) + >>> tf.rotation.as_matrix() + array([[ 0. , -1., 0. ], + [ 0.8660254, 0., 0.5 ], + [-0.5 , 0., 0.8660254 ]]) + >>> tf.translation + array([2., 3., 4.]) + >>> tf.single + True + + When applying a transform to a vector ``v``, the result is the same as + if the transform was applied to the vector in the following way: + ``tf.apply(v) == translation + rotation.apply(v)`` + + >>> r.apply([1, 0, 0]) + array([0. , 0.8660254, -0.5 ]) + >>> t + r.apply([1, 0, 0]) + array([2. , 3.8660254, 3.5 ]) + >>> tf.apply([1, 0, 0]) + array([2. , 3.8660254, 3.5 ]) + """ + rotation_tf = RigidTransform.from_rotation(rotation) + return RigidTransform.from_translation(translation) * rotation_tf + + @staticmethod + def from_exp_coords(exp_coords: ArrayLike) -> RigidTransform: + r"""Initialize from exponential coordinates of transform. + + This implements the exponential map that converts 6-dimensional real + vectors to SE(3). + + An exponential coordinate vector consists of 6 elements + ``[rx, ry, rz, vx, vy, vz]``. The first 3 encode rotation (and form a + rotation vector used in `Rotation.from_rotvec`) and the last 3 encode + translation (and form a translation vector for pure translations). + The exponential mapping can be expressed as matrix exponential + ``T = exp(tau)``, where ``T`` is a 4x4 matrix representing a rigid + transform and ``tau`` is a 4x4 matrix formed from the elements of an + exponential coordinate vector:: + + tau = [ 0 -rz ry vx] + [ rz 0 -rx vy] + [-ry rx 0 vz] + [ 0 0 0 1] + + Parameters + ---------- + exp_coords : array_like, shape (..., 6) + Exponential coordinate vectors. Each exp_coords[..., :] represents + a 6D exponential coordinate vector with the expected order of + components ``[rx, ry, rz, vx, vy, vz]``. The first 3 components + encode rotation and the last 3 encode translation. + + Returns + ------- + transform : `RigidTransform` instance + Rigid transform objects with the same leading dimensions as the input. + + Examples + -------- + >>> from scipy.spatial.transform import RigidTransform as Tf + >>> import numpy as np + + Creating from a single 6d vector of exponential coordinates: + + >>> tf = Tf.from_exp_coords([ + ... -2.01041204, -0.52983629, 0.65773501, + ... 0.10386614, 0.05855009, 0.54959179]) + >>> tf.as_matrix() + array([[0.76406621, 0.10504613, -0.63652819, -0.10209961], + [0.59956454, -0.47987325, 0.64050295, 0.40158789], + [-0.2381705, -0.87102639, -0.42963687, 0.19637636], + [0., 0., 0., 1.]]) + >>> tf.single + True + + A vector of zeros represents the identity transform: + + >>> tf = Tf.from_exp_coords(np.zeros(6)) + >>> tf.as_matrix() + array([[1., 0., 0., 0.], + [0., 1., 0., 0.], + [0., 0., 1., 0.], + [0., 0., 0., 1.]]) + + The last three numbers encode translation. If the first three numbers + are zero, the last three components can be interpreted as the + translation: + + >>> tf_trans = Tf.from_exp_coords([0, 0, 0, 4.3, -2, 3.4]) + >>> tf_trans.translation + array([4.3, -2., 3.4]) + + The first three numbers encode rotation as a rotation vector: + + >>> tf_rot = Tf.from_exp_coords([0.5, 0.3, 0.1, 0, 0, 0]) + >>> tf_rot.rotation.as_rotvec() + array([0.5, 0.3, 0.1]) + + Combining translation and rotation preserves the rotation vector, + but changes the last three components as they encode translation and + rotation: + + >>> (tf_trans * tf_rot).as_exp_coords() + array([0.5, 0.3, 0.1, 3.64305882, -1.25879559, 4.46109265]) + """ + xp = array_namespace(exp_coords) + exp_coords = xp_promote(exp_coords, force_floating=True, xp=xp) + if exp_coords.shape[-1] != 6: + raise ValueError( + f"Expected `exp_coords` to have shape (..., 6), got {exp_coords.shape}." + ) + backend = select_backend(xp, cython_compatible=exp_coords.ndim < 3) + matrix = backend.from_exp_coords(exp_coords) + return RigidTransform._from_raw_matrix(matrix, xp, backend) + + @staticmethod + def from_dual_quat( + dual_quat: ArrayLike, *, scalar_first: bool = False + ) -> RigidTransform: + """Initialize from a unit dual quaternion. + + Unit dual quaternions encode orientation in a real unit quaternion + and translation in a dual quaternion. There is a double cover, i.e., + the unit dual quaternions q and -q represent the same transform. + + Unit dual quaternions must have a real quaternion with unit norm and + a dual quaternion that is orthogonal to the real quaternion to satisfy + the unit norm constraint. This function will enforce both properties + through normalization. + + Parameters + ---------- + dual_quat : array_like, shape (..., 8) + Unit dual quaternions. Each dual_quat[..., :] represents a unit + dual quaternion. The real part is stored in the first four + components and the dual part in the last four components. + scalar_first : bool, optional + Whether the scalar component goes first or last in the two + individual quaternions that represent the real and the dual part. + Default is False, i.e. the scalar-last order is used. + + Returns + ------- + transform : `RigidTransform` instance + Rigid transform objects with the same leading dimensions as the input. + + Examples + -------- + >>> from scipy.spatial.transform import RigidTransform as Tf + >>> import numpy as np + + Creating from a single unit dual quaternion: + + >>> tf = Tf.from_dual_quat([ + ... 0.0617101, -0.06483886, 0.31432811, 0.94508498, + ... 0.04985168, -0.26119618, 0.1691491, -0.07743254]) + >>> tf.as_matrix() + array([[0.79398752, -0.60213598, -0.08376202, 0.24605262], + [0.58613113, 0.79477941, -0.15740392, -0.4932833], + [0.16135089, 0.07588122, 0.98397557, 0.34262676], + [0., 0., 0., 1.]]) + >>> tf.single + True + """ + xp = array_namespace(dual_quat) + dual_quat = _promote(dual_quat, xp=xp) + backend = select_backend(xp, dual_quat.ndim < 3) + matrix = backend.from_dual_quat(dual_quat, scalar_first=scalar_first) + return RigidTransform._from_raw_matrix(matrix, xp, backend) + + @staticmethod + def identity( + num: int | None = None, *, shape: int | tuple[int, ...] | None = None + ) -> RigidTransform: + """Initialize an identity transform. + + Composition with the identity transform has no effect, and + applying the identity transform to a vector has no effect. + + Parameters + ---------- + num : int, optional + Number of identity transforms to generate. If None (default), + then a single transform is generated. + shape : int or tuple of ints, optional + Shape of the identity transforms. If specified, `num` must + be None. + + Returns + ------- + transform : `RigidTransform` instance + The identity transform. + + Examples + -------- + >>> from scipy.spatial.transform import RigidTransform as Tf + >>> from scipy.spatial.transform import Rotation as R + >>> import numpy as np + + Creating a single identity transform: + + >>> tf = Tf.identity() + >>> tf.as_matrix() + array([[1., 0., 0., 0.], + [0., 1., 0., 0.], + [0., 0., 1., 0.], + [0., 0., 0., 1.]]) + >>> tf.single + True + + The identity transform can be applied to a vector without effect: + + >>> tf.apply([1, 2, 3]) + array([1., 2., 3.]) + + The identity transform when composed with another transform has no + effect: + + >>> rng = np.random.default_rng(123) + >>> t = rng.random(3) + >>> r = R.random(rng=rng) + >>> tf = Tf.from_components(t, r) + >>> np.allclose((Tf.identity() * tf).as_matrix(), + ... tf.as_matrix(), atol=1e-12) + True + + Multiple identity transforms can be generated at once: + + >>> tf = Tf.identity(2) + >>> tf.as_matrix() + array([[[1., 0., 0., 0.], + [0., 1., 0., 0.], + [0., 0., 1., 0.], + [0., 0., 0., 1.]], + [[1., 0., 0., 0.], + [0., 1., 0., 0.], + [0., 0., 1., 0.], + [0., 0., 0., 1.]]]) + >>> tf.single + False + >>> len(tf) + 2 + """ + if num is not None and shape is not None: + raise ValueError("Only one of `num` and `shape` can be specified.") + if num is None and shape is None: + shape = () + elif num is not None: + shape = (num,) + elif isinstance(shape, int): + shape = (shape,) + elif not isinstance(shape, tuple): + raise ValueError("`shape` must be an int or a tuple of ints or None.") + matrix = np.tile(np.eye(4), shape + (1, 1)) + # No need for a backend call here since identity is easy to construct and we are + # currently not offering a backend-specific identity matrix + return RigidTransform._from_raw_matrix(matrix, array_namespace(matrix)) + + @staticmethod + def concatenate( + transforms: RigidTransform | Iterable[RigidTransform], + ) -> RigidTransform: + """ + Concatenate a sequence of `RigidTransform` objects into a + single object. + + Parameters + ---------- + transforms : sequence of `RigidTransform` + If a single `RigidTransform` instance is passed in, a copy of + it is returned. + + Returns + ------- + transform : `RigidTransform` instance + The concatenated transform. + + Examples + -------- + >>> from scipy.spatial.transform import RigidTransform as Tf + >>> tf1 = Tf.from_translation([1, 0, 0]) + >>> tf2 = Tf.from_translation([[2, 0, 0], [3, 0, 0]]) + >>> Tf.concatenate([tf1, tf2]).translation + array([[1., 0., 0.], + [2., 0., 0.], + [3., 0., 0.]]) + """ + if isinstance(transforms, RigidTransform): + return RigidTransform._from_raw_matrix( + transforms.as_matrix(), transforms._xp + ) + if not all(isinstance(x, RigidTransform) for x in transforms): + raise TypeError("input must contain RigidTransform objects only") + + xp = array_namespace(transforms[0].as_matrix()) # type:ignore[index] + matrix = xp.concat( + [xpx.atleast_nd(x.as_matrix(), ndim=3, xp=xp) for x in transforms] + ) + return RigidTransform._from_raw_matrix(matrix, xp, None) + + def mean(self, + weights: ArrayLike | None = None, + axis: None | int | tuple[int, ...] = None + ) -> RigidTransform: + """Get the mean of the transforms. + + The mean of a set of transforms is the same as the mean of its + rotation and translation components. + + The mean used for the rotation component is the chordal L2 mean (also + called the projected or induced arithmetic mean) [1]_. If ``A`` is a + set of rotation matrices, then the mean ``M`` is the rotation matrix + that minimizes the following loss function: + + .. math:: + + L(M) = \\sum_{i = 1}^{n} w_i \\lVert \\mathbf{A}_i - + \\mathbf{M} \\rVert^2 , + + where :math:`w_i`'s are the `weights` corresponding to each matrix. + + Parameters + ---------- + weights : array_like shape (..., N), optional + Weights describing the relative importance of the transforms. If + None (default), then all values in `weights` are assumed to be + equal. If given, the shape of `weights` must be broadcastable to + the transform shape. Weights must be non-negative. + axis : None, int, or tuple of ints, optional + Axis or axes along which the means are computed. The default is to + compute the mean of all transforms. + + Returns + ------- + mean : `RigidTransform` instance + Single transform containing the mean of the transforms in the + current instance. + + References + ---------- + .. [1] Hartley, Richard, et al., + "Rotation Averaging", International Journal of Computer Vision + 103, 2013, pp. 267-305. + + Examples + -------- + >>> import numpy as np + >>> from scipy.spatial.transform import RigidTransform as Tf + >>> from scipy.spatial.transform import Rotation as R + >>> rng = np.random.default_rng(seed=123) + + The mean of a set of transforms is the same as the mean of the + translation and rotation components: + + >>> t = rng.random((4, 3)) + >>> r = R.random(4, rng=rng) + >>> tf = Tf.from_components(t, r) + >>> tf.mean().as_matrix() + array([[ 0.61593485, -0.74508342, 0.25588075, 0.66999034], + [-0.59353615, -0.65246765, -0.47116962, 0.25481794], + [ 0.51801458, 0.13833531, -0.84411151, 0.52429339], + [0., 0., 0., 1.]]) + >>> Tf.from_components(t.mean(axis=0), r.mean()).as_matrix() + array([[ 0.61593485, -0.74508342, 0.25588075, 0.66999034], + [-0.59353615, -0.65246765, -0.47116962, 0.25481794], + [ 0.51801458, 0.13833531, -0.84411151, 0.52429339], + [0., 0., 0., 1.]]) + """ + mean = self._backend.mean(self._matrix, weights=weights, axis=axis) + return RigidTransform._from_raw_matrix(mean, xp=self._xp, + backend=self._backend) + + def as_matrix(self) -> Array: + """Return a copy of the matrix representation of the transform. + + 4x4 rigid transformation matrices are of the form:: + + [ tx] + [ R ty] + [ tz] + [ 0 0 0 1] + + where ``R`` is a 3x3 orthonormal rotation matrix and + ``t = [tx, ty, tz]`` is a 3x1 translation vector. + + Returns + ------- + matrix : numpy.ndarray, shape (..., 4, 4) + Transformation matrices with the same leading dimensions as the transform. + + Examples + -------- + >>> from scipy.spatial.transform import RigidTransform as Tf + >>> from scipy.spatial.transform import Rotation as R + >>> import numpy as np + + A transformation matrix is a 4x4 matrix formed from a 3x3 rotation + matrix and a 3x1 translation vector: + + >>> t = np.array([2, 3, 4]) + >>> r = R.from_matrix([[0, 0, 1], + ... [1, 0, 0], + ... [0, 1, 0]]) + >>> tf = Tf.from_components(t, r) + >>> tf.as_matrix() + array([[ 0., 0., 1., 2.], + [ 1., 0., 0., 3.], + [ 0., 1., 0., 4.], + [ 0., 0., 0., 1.]]) + + >>> Tf.identity(2).as_matrix() + array([[[1., 0., 0., 0.], + [0., 1., 0., 0.], + [0., 0., 1., 0.], + [0., 0., 0., 1.]], + [[1., 0., 0., 0.], + [0., 1., 0., 0.], + [0., 0., 1., 0.], + [0., 0., 0., 1.]]]) + """ + matrix = self._xp.asarray(self._matrix, copy=True) + if self._single: + return matrix[0, ...] + return matrix + + def as_components(self) -> tuple[Array, Rotation]: + """Return the translation and rotation components of the transform, + where the rotation is applied first, followed by the translation. + + 4x4 rigid transformation matrices are of the form:: + + [ tx] + [ R ty] + [ tz] + [ 0 0 0 1] + + Where ``R`` is a 3x3 orthonormal rotation matrix and + ``t = [tx, ty, tz]`` is a 3x1 translation vector. This function + returns the rotation corresponding to this rotation matrix + ``r = Rotation.from_matrix(R)`` and the translation vector ``t``. + + When applying a transform ``tf`` to a vector ``v``, the result is the same + as if the rotation and translation components were applied to the vector + with the following operation: + ``tf.apply(v) == translation + rotation.apply(v)``. + + Returns + ------- + translation : numpy.ndarray, shape (..., 3) + The translation of the transform. + rotation : `Rotation` instance + The rotation of the transform. + + Examples + -------- + >>> from scipy.spatial.transform import RigidTransform as Tf + >>> from scipy.spatial.transform import Rotation as R + >>> import numpy as np + + Recover the rotation and translation from a transform: + + >>> t = np.array([2, 3, 4]) + >>> r = R.from_matrix([[0, 0, 1], + ... [1, 0, 0], + ... [0, 1, 0]]) + >>> tf = Tf.from_components(t, r) + >>> t, r = tf.as_components() + >>> t + array([2., 3., 4.]) + >>> r.as_matrix() + array([[0., 0., 1.], + [1., 0., 0.], + [0., 1., 0.]]) + + The transform applied to a vector is equivalent to the rotation applied + to the vector, followed by the translation: + + >>> t + r.apply([1, 0, 0]) + array([2., 4., 4.]) + >>> tf.apply([1, 0, 0]) + array([2., 4., 4.]) + """ + return self.translation, self.rotation + + def as_exp_coords(self) -> Array: + """Return the exponential coordinates of the transform. + + This implements the logarithmic map that converts SE(3) to 6-dimensional + real vectors. + + This is an inverse of `from_exp_coords` where details on the mapping can + be found. + + Returns + ------- + exp_coords : numpy.ndarray, shape (..., 6) + Exponential coordinate vectors with the same leading dimensions as the + transform. The first three components define the rotation and the last + three components define the translation. + + Examples + -------- + >>> from scipy.spatial.transform import RigidTransform as Tf + >>> import numpy as np + + Get exponential coordinates of the identity matrix: + + >>> Tf.identity().as_exp_coords() + array([0., 0., 0., 0., 0., 0.]) + """ + exp_coords = self._backend.as_exp_coords(self._matrix) + if self._single: + exp_coords = exp_coords[0, ...] + return exp_coords + + def as_dual_quat(self, *, scalar_first: bool = False) -> Array: + """Return the dual quaternion representation of the transform. + + Unit dual quaternions encode orientation in a real unit quaternion + and translation in a dual quaternion. There is a double cover, i.e., + the unit dual quaternions q and -q represent the same transform. + + Parameters + ---------- + scalar_first : bool, optional + Whether the scalar component goes first or last in the two + individual quaternions that represent the real and the dual part. + Default is False, i.e. the scalar-last order is used. + + Returns + ------- + dual_quat : numpy.ndarray, shape (..., 8) + Unit dual quaternion vectors with the same leading dimensions as the + transform. The real part is stored in the first four components and the + dual part in the last four components. + + Examples + -------- + >>> from scipy.spatial.transform import RigidTransform as Tf + >>> import numpy as np + + Get identity dual quaternion (we use scalar-last by default): + + >>> Tf.identity().as_dual_quat() + array([0., 0., 0., 1., 0., 0., 0., 0.]) + + When we want to use the scalar-first convention, we use the argument: + + >>> Tf.identity().as_dual_quat(scalar_first=True) + array([1., 0., 0., 0., 0., 0., 0., 0.]) + """ + dual_quat = self._backend.as_dual_quat(self._matrix, scalar_first=scalar_first) + if self._single: + dual_quat = dual_quat[0] + return dual_quat + + def __len__(self) -> int: + """Return the length of the leading transform dimension. + + A transform can store an N-dimensional array of transforms. The length is the + size of the first dimension of this array. If the transform is a single + transform, the length is not defined and an error is raised. + + Returns + ------- + length : int + The number of transforms in this object. + + Raises + ------ + TypeError + If the transform is a single transform. + + Examples + -------- + >>> from scipy.spatial.transform import RigidTransform as Tf + >>> import numpy as np + >>> tf = Tf.identity(3) + >>> len(tf) + 3 + + An N-dimensional array of transforms returns its first dimension size: + + >>> t = np.ones((5, 2, 3, 1, 3)) + >>> tf = Tf.from_translation(t) + >>> len(tf) + 5 + + A single transform has no length: + + >>> tf = Tf.from_translation([1, 0, 0]) + >>> len(tf) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + TypeError: Single transform has no len(). + """ + # We don't use self._single here because we also want to raise an error for + # Array API backends that call len() on single RigidTransform objects. + if self.single: + raise TypeError("Single transform has no len") + return self._matrix.shape[0] + + def __getitem__( + self, indexer: int | slice | EllipsisType | None | ArrayLike + ) -> RigidTransform: + """Extract transform(s) at given index(es) from this object. + + Creates a new `RigidTransform` instance containing a subset of + transforms stored in this object. + + Parameters + ---------- + indexer : int or slice or array_like + Specifies which transform(s) to extract. A single indexer must be + specified, i.e. as if indexing a 1 dimensional array or list. + + Returns + ------- + transform : `RigidTransform` instance + Contains + - a single transform, if `indexer` is a single index + - a stack of transform(s), if `indexer` is a slice, or an index + array. + + Raises + ------ + TypeError + If the transform is a single transform. + + Examples + -------- + >>> from scipy.spatial.transform import RigidTransform as Tf + >>> t = [[0, 0, 0], [1, 0, 0], [2, 0, 0]] # 3 translations + >>> tf = Tf.from_translation(t) + + A single index returns a single transform: + + >>> tf[0].as_matrix() + array([[1., 0., 0., 0.], + [0., 1., 0., 0.], + [0., 0., 1., 0.], + [0., 0., 0., 1.]]) + + A slice returns a stack of transforms: + + >>> tf[1:3].translation + array([[1., 0., 0.], + [2., 0., 0.]]) + + An index array returns a stack of transforms: + + >>> tf[[0, 2]].translation + array([[0., 0., 0.], + [2., 0., 0.]]) + """ + if self.single: + raise TypeError("Single transform is not subscriptable.") + + is_array = isinstance(indexer, type(self._matrix)) + xp = self._xp + # Masking is only specified in the Array API when the array is the sole index + # This special case handling is necessary to support boolean indexing and + # integer array indexing with take (see + # https://github.com/data-apis/array-api/pull/900#issuecomment-2674432480) + # Ideally we would converge to [indexer, ...] indexing, but this is not + # supported for now. + if is_array and indexer.dtype == xp.bool: # type:ignore[union-attr] + return RigidTransform(self._matrix[indexer], normalize=False) + if is_array and xp.isdtype(indexer.dtype, "integral"): # type:ignore[union-attr] + if self._matrix.shape[0] == 0: + raise IndexError("cannot take from an empty array") + return RigidTransform( + xp.take(self._matrix, indexer, axis=0), normalize=False + ) + return RigidTransform(self._matrix[indexer, ...], normalize=False) + + def __setitem__( + self, + indexer: int | slice | EllipsisType | None | ArrayLike, + value: RigidTransform, + ): + """Set transform(s) at given index(es) in this object. + + Parameters + ---------- + indexer : int or slice or array_like + Specifies which transform(s) to replace. A single indexer must be + specified, i.e. as if indexing a 1 dimensional array or list. + + value : `RigidTransform` instance + The transform(s) to set. + + Raises + ------ + TypeError + If the transform is a single transform. + + Examples + -------- + >>> from scipy.spatial.transform import RigidTransform as Tf + >>> t = [[0, 0, 0], [1, 0, 0], [2, 0, 0]] # 3 translations + >>> tf = Tf.from_translation(t) + + Set a single transform: + + >>> tf[0] = Tf.from_translation([9, 9, 9]) + >>> tf.translation + array([[9., 9., 9.], + [1., 0., 0.], + [2., 0., 0.]]) + """ + if self.single: + raise TypeError("Single transform is not subscriptable.") + + if not isinstance(value, RigidTransform): + raise TypeError("value must be a RigidTransform object") + + self._matrix = self._backend.setitem(self._matrix, indexer, value.as_matrix()) + + def __mul__( + self, other: RigidTransform | Rotation + ) -> RigidTransform | NotImplementedType: + """Compose this transform with the other. + + If ``p`` and ``q`` are two transforms, then the composition of '``q`` + followed by ``p``' is equivalent to ``p * q``. In terms of + transformation matrices, the composition can be expressed as + ``p.as_matrix() @ q.as_matrix()``. + + In terms of translations and rotations, the composition when applied to + a vector ``v`` is equivalent to + ``p.translation + p.rotation.apply(q.translation) + + (p.rotation * q.rotation).apply(v)``. + + This function supports composition of multiple transforms at a time using + broadcasting rules. The resulting shape for two `RigidTransform` instances + ``p`` and ``q`` is `np.broadcast_shapes(p.shape, q.shape)`. + + If ``other`` is a `Rotation`, it is automatically promoted to a + `RigidTransform` with zero translation. + + Parameters + ---------- + other : `RigidTransform` or `Rotation` instance + Transform(s) or rotation(s) to be composed with this one. The shapes + must be broadcastable. + + Returns + ------- + `RigidTransform` instance + The composed transform. + + Examples + -------- + >>> from scipy.spatial.transform import RigidTransform as Tf + >>> from scipy.spatial.transform import Rotation as R + >>> import numpy as np + + Compose two transforms: + + >>> tf1 = Tf.from_translation([1, 0, 0]) + >>> tf2 = Tf.from_translation([0, 1, 0]) + >>> tf = tf1 * tf2 + >>> tf.translation + array([1., 1., 0.]) + >>> tf.single + True + + When applied to a vector, the composition of two transforms is applied + in right-to-left order. + + >>> t1, r1 = [1, 2, 3], R.from_euler('z', 60, degrees=True) + >>> t2, r2 = [0, 1, 0], R.from_euler('x', 30, degrees=True) + >>> tf1 = Tf.from_components(t1, r1) + >>> tf2 = Tf.from_components(t2, r2) + >>> tf = tf1 * tf2 + >>> tf.apply([1, 0, 0]) + array([0.6339746, 3.3660254, 3. ]) + >>> tf1.apply(tf2.apply([1, 0, 0])) + array([0.6339746, 3.3660254, 3. ]) + + When at least one of the transforms is not single, the result is a stack + of transforms. + + >>> tf1 = Tf.from_translation([1, 0, 0]) + >>> tf2 = Tf.from_translation([[0, 2, 0], [0, 0, 3]]) + >>> tf = tf1 * tf2 + >>> tf.translation + array([[1., 2., 0.], + [1., 0., 3.]]) + >>> tf.single + False + >>> len(tf) + 2 + + Broadcasting rules apply when composing multiple transforms at a time. + + >>> tf1 = Tf.from_translation(np.ones((5, 1, 3))) # Shape (5, 1, 3) + >>> tf2 = Tf.from_translation(np.ones((1, 4, 3))) # Shape (1, 4, 3) + >>> tf = tf1 * tf2 # Shape (5, 4, 3) + >>> tf.translation.shape + (5, 4, 3) + """ + if isinstance(other, Rotation): + other = RigidTransform.from_rotation(other) + elif not isinstance(other, RigidTransform): + # If other is not a RigidTransform, we return NotImplemented to allow other + # types to implement __rmul__ + return NotImplemented + if not broadcastable(self._matrix.shape, other._matrix.shape): + raise ValueError( + f"Cannot broadcast {self._matrix.shape[:-2]} transforms in " + f"first to {other._matrix.shape[:-2]} transforms in second object." + ) + cython_compatible = self._matrix.ndim < 4 and other._matrix.ndim < 4 + backend = select_backend(self._xp, cython_compatible=cython_compatible) + matrix = backend.compose_transforms(self._matrix, other._matrix) + # Only necessary for cython. Array API broadcasting handles this by default + if self._single and other._single: + matrix = matrix[0, ...] + return RigidTransform(matrix, normalize=True, copy=False) + + def __rmul__(self, other: Rotation) -> RigidTransform | NotImplementedType: + """Compose a rotation with this transform (rotation applied second). + + See `__mul__` for more details. + + Parameters + ---------- + other : `Rotation` instance + The rotation to compose with this transform. The shapes must be + broadcastable. + + Returns + ------- + `RigidTransform` instance + The composed transform. + """ + if isinstance(other, Rotation): + return RigidTransform.from_rotation(other) * self + # When other is a RigidTransform __mul__ is called, so we don't handle it here + return NotImplemented + + def __pow__(self, n: float) -> RigidTransform: + """Compose this transform with itself `n` times. + + A rigid transform `p` when raised to non-integer powers can be thought + of as finding a fraction of the transformation. For example, a power of + 0.5 finds a "halfway" transform from the identity to `p`. + + This is implemented by applying screw linear interpolation (ScLERP) + between `p` and the identity transform, where the angle of the rotation + component is scaled by `n`, and the translation is proportionally + adjusted along the screw axis. + + ``q = p ** n`` can also be expressed as + ``q = RigidTransform.from_exp_coords(p.as_exp_coords() * n)``. + + If `n` is negative, then the transform is inverted before the power + is applied. In other words, ``p ** -abs(n) == p.inv() ** abs(n)``. + + Parameters + ---------- + n : float + The number of times to compose the transform with itself. + + Returns + ------- + `RigidTransform` instance + If the input Rotation `p` contains `N` multiple rotations, then + the output will contain `N` rotations where the `i` th rotation + is equal to ``p[i] ** n``. + + Notes + ----- + There are three notable cases: if ``n == 1`` then a copy of the original + transform is returned, if ``n == 0`` then the identity transform is + returned, and if ``n == -1`` then the inverse transform is returned. + + Note that fractional powers ``n`` which effectively take a root of + rotation, do so using the shortest path smallest representation of that + angle (the principal root). This means that powers of ``n`` and ``1/n`` + are not necessarily inverses of each other. For example, a 0.5 power of + a +240 degree rotation will be calculated as the 0.5 power of a -120 + degree rotation, with the result being a rotation of -60 rather than + +120 degrees. + + Examples + -------- + >>> from scipy.spatial.transform import RigidTransform as Tf + >>> import numpy as np + + A power of 2 returns the transform composed with itself: + + >>> tf = Tf.from_translation([1, 2, 3]) + >>> (tf ** 2).translation + array([2., 4., 6.]) + >>> (tf ** 2).as_matrix() + array([[1., 0., 0., 2.], + [0., 1., 0., 4.], + [0., 0., 1., 6.], + [0., 0., 0., 1.]]) + + A negative power returns the inverse of the transform raised to the + absolute value of `n`: + + >>> (tf ** -2).translation + array([-2., -4., -6.]) + >>> np.allclose((tf ** -2).as_matrix(), (tf.inv() ** 2).as_matrix(), + ... atol=1e-12) + True + + A power of 0 returns the identity transform: + + >>> (tf ** 0).as_matrix() + array([[1., 0., 0., 0.], + [0., 1., 0., 0.], + [0., 0., 1., 0.], + [0., 0., 0., 1.]]) + + A power of 1 returns a copy of the original transform: + + >>> (tf ** 1).as_matrix() + array([[1., 0., 0., 1.], + [0., 1., 0., 2.], + [0., 0., 1., 3.], + [0., 0., 0., 1.]]) + + A fractional power returns a transform with a scaled rotation and + translated along the screw axis. Here we take the square root of the + transform, which when squared recovers the original transform: + + >>> tf_half = (tf ** 0.5) + >>> tf_half.translation + array([0.5, 1., 1.5]) + >>> (tf_half ** 2).as_matrix() + array([[1., 0., 0., 1.], + [0., 1., 0., 2.], + [0., 0., 1., 3.], + [0., 0., 0., 1.]]) + """ + matrix = self._backend.pow(self._matrix, n) + if self._single: + matrix = matrix[0, ...] + return RigidTransform._from_raw_matrix(matrix, self._xp, self._backend) + + def inv(self) -> RigidTransform: + """Invert this transform. + + Composition of a transform with its inverse results in an identity + transform. + + A rigid transform is a composition of a rotation and a translation, + where the rotation is applied first, followed by the translation. So the + inverse transform is equivalent to the inverse translation followed by + the inverse rotation. + + Returns + ------- + `RigidTransform` instance + The inverse of this transform. + + Examples + -------- + >>> from scipy.spatial.transform import RigidTransform as Tf + >>> from scipy.spatial.transform import Rotation as R + >>> import numpy as np + + A transform composed with its inverse results in an identity transform: + + >>> rng = np.random.default_rng(seed=123) + >>> t = rng.random(3) + >>> r = R.random(rng=rng) + >>> tf = Tf.from_components(t, r) + >>> tf.as_matrix() + array([[-0.45431291, 0.67276178, -0.58394466, 0.68235186], + [-0.23272031, 0.54310598, 0.80676958, 0.05382102], + [ 0.85990758, 0.50242162, -0.09017473, 0.22035987], + [ 0. , 0. , 0. , 1. ]]) + + >>> (tf.inv() * tf).as_matrix() + array([[[1., 0., 0., 0.], + [0., 1., 0., 0.], + [0., 0., 1., 0.], + [0., 0., 0., 1.]]]) + + The inverse rigid transform is the same as the inverse translation + followed by the inverse rotation: + + >>> t, r = tf.as_components() + >>> r_inv = r.inv() # inverse rotation + >>> t_inv = -t # inverse translation + >>> tf_r_inv = Tf.from_rotation(r_inv) + >>> tf_t_inv = Tf.from_translation(t_inv) + >>> np.allclose((tf_r_inv * tf_t_inv).as_matrix(), + ... tf.inv().as_matrix(), + ... atol=1e-12) + True + >>> (tf_r_inv * tf_t_inv * tf).as_matrix() + array([[[1., 0., 0., 0.], + [0., 1., 0., 0.], + [0., 0., 1., 0.], + [0., 0., 0., 1.]]]) + """ + matrix = self._backend.inv(self._matrix) + if self._single: + matrix = matrix[0, ...] + return RigidTransform._from_raw_matrix(matrix, self._xp, self._backend) + + def apply(self, vector: ArrayLike, inverse: bool = False) -> Array: + """Apply the transform to a vector. + + If the original frame transforms to the final frame by this transform, + then its application to a vector can be seen in two ways: + + - As a projection of vector components expressed in the final frame + to the original frame. + - As the physical transformation of a vector being glued to the + original frame as it transforms. In this case the vector + components are expressed in the original frame before and after + the transformation. + + In terms of the rotation matrix and translation, + this application is the same as + ``self.translation + (mat @ vector[..., np.newaxis])[..., 0]``, + where ``mat = self.rotation.as_matrix()``. + + For a single transform, this is the same as + ``self.translation + vector @ self.rotation.as_matrix().T``. + + Parameters + ---------- + vector : array_like, shape (..., 3) + Vector(s) to be transformed. Each vector[..., :] represents a 3D + vector. + inverse : bool, optional + If True, the inverse of the transform is applied to the vector. + + Returns + ------- + transformed_vector : numpy.ndarray, shape (..., 3) + The transformed vector(s) with shape determined by broadcasting + the transform and vector shapes together. + + Examples + -------- + >>> from scipy.spatial.transform import RigidTransform as Tf + >>> from scipy.spatial.transform import Rotation as R + >>> import numpy as np + + Apply a single transform to a vector. Here the transform is just a + translation, so the result is the vector added to the translation + vector. + + >>> t = np.array([1, 2, 3]) + >>> tf = Tf.from_translation(t) + >>> t + np.array([1, 0, 0]) + array([2, 2, 3]) + >>> tf.apply([1, 0, 0]) + array([2., 2., 3.]) + + Apply a single transform to a stack of vectors: + + >>> tf.apply([[1, 0, 0], [0, 1, 0]]) + array([[2., 2., 3.], + [1., 3., 3.]]) + + Apply the inverse of a transform to a vector, so the result is the + negative of the translation vector added to the vector. + + >>> -t + np.array([1, 0, 0]) + array([0, -2, -3]) + >>> tf.apply([1, 0, 0], inverse=True) + array([0., -2., -3.]) + + Broadcasting is supported when applying multiple transforms to an N-dimensional + array of vectors. + + >>> tf = Tf.from_translation(np.ones((4, 5, 1, 3))) + >>> vectors = np.zeros((2, 3)) + >>> tf.apply(vectors).shape + (4, 5, 2, 3) + + For transforms which are not just pure translations, applying it to a + vector is the same as applying the rotation component to the vector and + then adding the translation component. + + >>> r = R.from_euler('z', 60, degrees=True) + >>> tf = Tf.from_components(t, r) + >>> t + r.apply([1, 0, 0]) + array([1.5, 2.8660254, 3. ]) + >>> tf.apply([1, 0, 0]) + array([1.5, 2.8660254, 3. ]) + + When applying the inverse of a transform, the result is the negative of + the translation vector added to the vector, and then rotated by the + inverse rotation. + + >>> r.inv().apply(-t + np.array([1, 0, 0])) + array([-1.73205081, -1. , -3. ]) + >>> tf.apply([1, 0, 0], inverse=True) + array([-1.73205081, -1. , -3. ]) + """ + # We do not use the cached xp here to catch cases where matrix and vector have + # different xp instances. + xp = array_namespace(self._matrix, vector) + vector = xp.asarray( + vector, dtype=self._matrix.dtype, device=device(self._matrix) + ) + cython_compatible = self._matrix.ndim < 4 and vector.ndim < 3 + backend = select_backend(xp, cython_compatible=cython_compatible) + result = backend.apply(self._matrix, vector, inverse) + if self._single and vector.ndim == 1: + result = result[0, ...] + return result + + @property + def rotation(self) -> Rotation: + """Return the rotation component of the transform. + + A transform is a composition of a rotation and a translation, such that + when applied to a vector, the vector is first rotated and then + translated. This property returns the rotation part of the transform. + + Returns + ------- + rotation : `Rotation` instance + A single rotation or a stack of rotations. + + Examples + -------- + >>> from scipy.spatial.transform import RigidTransform as Tf + >>> from scipy.spatial.transform import Rotation as R + >>> import numpy as np + + The rotation component is extracted from the transform: + + >>> t = np.array([1, 0, 0]) + >>> r = R.random(3) + >>> tf = Tf.from_components(t, r) + >>> np.allclose(tf.rotation.as_matrix(), r.as_matrix()) + True + """ + if self._single: + return Rotation.from_matrix(self._matrix[0, :3, :3]) + return Rotation.from_matrix(self._matrix[..., :3, :3]) + + @property + def translation(self) -> Array: + """Return the translation component of the transform. + + A transform is a composition of a rotation and a translation, such that + when applied to a vector, the vector is first rotated and then + translated. This property returns the translation part of the transform. + + Returns + ------- + translation : numpy.ndarray, shape (..., 3) + Translation vectors with the same leading dimensions as the transform. + + Examples + -------- + >>> from scipy.spatial.transform import RigidTransform as Tf + >>> from scipy.spatial.transform import Rotation as R + >>> import numpy as np + + The translation component is extracted from the transform: + + >>> t = np.array([[1, 0, 0], [2, 0, 0], [3, 0, 0]]) + >>> r = R.random() + >>> tf = Tf.from_components(t, r) + >>> np.allclose(tf.translation, t) + True + """ + if self._single: + return self._xp.asarray(self._matrix[0, :3, 3], copy=True) + return self._xp.asarray(self._matrix[..., :3, 3], copy=True) + + @property + def single(self) -> bool: + """Whether this instance represents a single transform. + + Single transforms are not subscriptable, and do not have a length. + + Returns + ------- + single : bool + True if this instance represents a single transform, False + otherwise. + """ + return self._single or self._matrix.ndim == 2 + + @property + def shape(self) -> tuple[int, ...]: + """The shape of the transform's leading dimensions.""" + if self._single or self._matrix.ndim == 2: + return () + return self._matrix.shape[:-2] + + def __reduce__(self) -> tuple[Callable, tuple]: + """Reduce the RigidTransform for pickling. + + We store modules inside RigidTransforms which cannot be pickled. To circumvent + this, we pickle only the matrix and restore the cached modules from the matrix + type in `from_matrix`. + """ + matrix = self._matrix + if self._single: + matrix = matrix[0, ...] + return (self.__class__.from_matrix, (matrix,)) + + def __iter__(self) -> Iterator[RigidTransform]: + """Iterate over transforms.""" + if self._single or self._matrix.ndim == 2: + raise TypeError("Single transform is not iterable.") + # We return a generator that yields a new RigidTransform object for each + # transform in the current object. We cannot rely on the default implementation + # because jax will not raise an IndexError for out-of-bounds indices. + for i in range(self._matrix.shape[0]): + yield RigidTransform._from_raw_matrix( + self._matrix[i, ...], self._xp, self._backend + ) + + @staticmethod + def _from_raw_matrix( + matrix: Array, xp: ModuleType, backend: ModuleType | None = None + ) -> RigidTransform: + """Create a RigidTransform skipping all sanitization steps. + + This method is is intended for internal, performant creation of RigidTransforms + with matrices that are guaranteed to be valid. + """ + tf = RigidTransform.__new__(RigidTransform) + tf._single = matrix.ndim == 2 and is_numpy(xp) + if tf._single: + matrix = xpx.atleast_nd(matrix, ndim=3, xp=xp) + tf._matrix = matrix + tf._xp = xp + if backend is None: + backend = select_backend(xp, matrix.ndim < 4) + tf._backend = backend + return tf diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/transform/_rigid_transform_cy.cpython-312-x86_64-linux-gnu.so b/venv/lib/python3.12/site-packages/scipy/spatial/transform/_rigid_transform_cy.cpython-312-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..65a12dca031bbdc89b6c9ec6de629df1efefb730 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/transform/_rigid_transform_cy.cpython-312-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8d025603d3b079716af5331e0b4cf7a3fa3020d56ec106200d9258e07960c862 +size 245720 diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/transform/_rigid_transform_xp.py b/venv/lib/python3.12/site-packages/scipy/spatial/transform/_rigid_transform_xp.py new file mode 100644 index 0000000000000000000000000000000000000000..8700f3065283dcfdeece75c7ef1f4c18388d2757 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/transform/_rigid_transform_xp.py @@ -0,0 +1,448 @@ +from types import EllipsisType + +from scipy._lib._array_api import ( + array_namespace, + Array, + ArrayLike, + is_lazy_array, + xp_vector_norm, + xp_result_type, + xp_promote, + is_array_api_obj, +) +import scipy._external.array_api_extra as xpx +from scipy.spatial.transform._rotation_xp import ( + as_matrix as quat_as_matrix, + from_matrix as quat_from_matrix, + _from_matrix_orthogonal as quat_from_matrix_orthogonal, + from_rotvec as quat_from_rotvec, + as_rotvec as quat_as_rotvec, + compose_quat, + from_quat, + inv as quat_inv, + mean as quat_mean, +) +from scipy._external.array_api_compat import device as xp_device +from scipy._lib._util import broadcastable + + +def from_matrix(matrix: Array, normalize: bool = True, copy: bool = True) -> Array: + xp = array_namespace(matrix) + # Shape check should be done before calling this function + if normalize or copy: + matrix = xp.asarray(matrix, copy=True) + + last_row_ok = xp.all( + matrix[..., 3, :] == xp.asarray([0, 0, 0, 1.0], device=xp_device(matrix)), + axis=-1, + ) + lazy = is_lazy_array(matrix) + # We delay lazy branch checks until after normalization to avoid overwriting nans + # with the rotation matrix + if not lazy and xp.any(~last_row_ok): + if last_row_ok.shape == (): + idx = () + else: + idx = tuple(int(i[0]) for i in xp.nonzero(~last_row_ok)) + vals = matrix[idx + (3, ...)] + raise ValueError( + f"Expected last row of transformation matrix {idx} to be " + f"exactly [0, 0, 0, 1], got {vals}" + ) + + # The quat_from_matrix() method orthogonalizes the rotation + # component of the transformation matrix. While this does have some + # overhead in converting a rotation matrix to a quaternion and back, it + # allows for skipping singular value decomposition for near-orthogonal + # matrices, which is a computationally expensive operation. + if normalize: + rotmat = quat_as_matrix(quat_from_matrix(matrix[..., :3, :3])) + matrix = xpx.at(matrix)[..., :3, :3].set(rotmat) + # Lazy branch matrix invalidation + if lazy: + matrix = xp.where(last_row_ok[..., None, None], matrix, xp.nan) + return matrix + + +def from_rotation(quat: Array) -> Array: + xp = array_namespace(quat) + rotmat = quat_as_matrix(quat) + matrix = xp.zeros( + (*rotmat.shape[:-2], 4, 4), dtype=quat.dtype, device=xp_device(quat) + ) + matrix = xpx.at(matrix)[..., :3, :3].set(rotmat) + matrix = xpx.at(matrix)[..., 3, 3].set(1) + return matrix + + +def from_translation(translation: Array) -> Array: + xp = array_namespace(translation) + + if translation.shape[-1] != 3: + raise ValueError( + f"Expected `translation` to have shape (..., 3), got {translation.shape}." + ) + device = xp_device(translation) + dtype = xp_result_type(translation, force_floating=True, xp=xp) + eye = xp.eye(4, dtype=dtype, device=device) + + matrix = xpx.atleast_nd(eye, ndim=translation.ndim + 1, xp=xp) + matrix = xp.zeros( + (*translation.shape[:-1], 4, 4), + dtype=dtype, + device=device, + ) + matrix = xpx.at(matrix)[...].set(xp.eye(4, dtype=dtype, device=device)) + matrix = xpx.at(matrix)[..., :3, 3].set( + xp_promote(translation, force_floating=True, xp=xp) + ) + return matrix + + +def from_components(translation: Array, quat: Array) -> Array: + return compose_transforms(from_translation(translation), from_rotation(quat)) + + +def from_exp_coords(exp_coords: Array) -> Array: + rot_vec = exp_coords[..., :3] + rot_matrix = quat_as_matrix(quat_from_rotvec(rot_vec)) + translation_transform = _compute_se3_exp_translation_transform(rot_vec) + translations = (translation_transform @ exp_coords[..., 3:, None])[..., 0] + return _create_transformation_matrix(translations, rot_matrix) + + +def from_dual_quat(dual_quat: Array, *, scalar_first: bool = False) -> Array: + xp = array_namespace(dual_quat) + + if dual_quat.shape[-1] != 8: + raise ValueError( + f"Expected `dual_quat` to have shape (..., 8), got {dual_quat.shape}." + ) + + real_part = dual_quat[..., :4] + dual_part = dual_quat[..., 4:] + if scalar_first: + real_part = xp.roll(real_part, -1, axis=-1) + dual_part = xp.roll(dual_part, -1, axis=-1) + + real_part, dual_part = _normalize_dual_quaternion(real_part, dual_part) + + rot_quat = from_quat(real_part) + + translation = 2.0 * compose_quat(dual_part, quat_inv(rot_quat))[..., :3] + matrix = _create_transformation_matrix(translation, quat_as_matrix(rot_quat)) + + return matrix + + +def as_exp_coords(matrix: Array) -> Array: + xp = array_namespace(matrix) + rot_vec = quat_as_rotvec(quat_from_matrix_orthogonal(matrix[..., :3, :3])) + translation_transform = _compute_se3_log_translation_transform(rot_vec) + translations = (translation_transform @ matrix[..., :3, 3][..., None])[..., 0] + exp_coords = xp.concat([rot_vec, translations], axis=-1) + return exp_coords + + +def as_dual_quat(matrix: Array, *, scalar_first: bool = False) -> Array: + xp = array_namespace(matrix) + real_parts = quat_from_matrix_orthogonal(matrix[..., :3, :3]) + + pure_translation_quats = xp.empty( + (*matrix.shape[:-2], 4), dtype=matrix.dtype, device=xp_device(matrix) + ) + pure_translation_quats = xpx.at(pure_translation_quats)[..., :3].set( + matrix[..., :3, 3] + ) + pure_translation_quats = xpx.at(pure_translation_quats)[..., 3].set(0.0) + + dual_parts = 0.5 * compose_quat(pure_translation_quats, real_parts) + + if scalar_first: + real_parts = xp.roll(real_parts, 1, axis=-1) + dual_parts = xp.roll(dual_parts, 1, axis=-1) + + dual_quats = xp.concat([real_parts, dual_parts], axis=-1) + return dual_quats + + +def compose_transforms(tf_matrix: Array, other_tf_matrix: Array) -> Array: + if not broadcastable(tf_matrix.shape, other_tf_matrix.shape): + len_a = tf_matrix.shape[0] if tf_matrix.ndim == 3 else 1 + len_b = other_tf_matrix.shape[0] if other_tf_matrix.ndim == 3 else 1 + raise ValueError( + "Expected equal number of transforms in both or a " + "single transform in either object, got " + f"{len_a} transforms in first and {len_b}" + "transforms in second object." + ) + return tf_matrix @ other_tf_matrix + + +def inv(matrix: Array) -> Array: + xp = array_namespace(matrix) + r_inv = xp.matrix_transpose(matrix[..., :3, :3]) + # Matrix multiplication of r_inv and translation vector + t_inv = -(r_inv @ matrix[..., :3, 3][..., None])[..., 0] + matrix = xp.zeros( + (*matrix.shape[:-2], 4, 4), dtype=matrix.dtype, device=xp_device(matrix) + ) + matrix = xpx.at(matrix)[..., :3, :3].set(r_inv) + matrix = xpx.at(matrix)[..., :3, 3].set(t_inv) + matrix = xpx.at(matrix)[..., 3, 3].set(1) + return matrix + + +def apply(matrix: Array, vector: Array, inverse: bool = False) -> Array: + xp = array_namespace(matrix) + if vector.shape[-1] != 3: + raise ValueError(f"Expected vector to have shape (..., 3), got {vector.shape}.") + vec = xp.empty( + (*vector.shape[:-1], 4), dtype=vector.dtype, device=xp_device(vector) + ) + vec = xpx.at(vec)[..., :3].set(vector) + vec = xpx.at(vec)[..., 3].set(1) + vec = vec[..., None] + + if inverse: + matrix = inv(matrix) + + # We raise a ValueError manually here because letting the function run its course + # would raise heterogeneous error types and messages for different frameworks. + # However, the error only mimics numpy's error message and does not provide the + # same amount of context. + if not broadcastable(matrix.shape, vec.shape): # type:ignore[arg-type] + raise ValueError("operands could not be broadcast together") + return (matrix @ vec)[..., :3, 0] + + +def pow(matrix: Array, n: float | Array) -> Array: + xp = array_namespace(matrix) + device = xp_device(matrix) + # If n is an array, we sanitize it to a scalar and promote quat and n to + # the same dtype. + if is_array_api_obj(n): + if n.shape == (1,): # type:ignore[union-attr] + n = n[0] # type:ignore[index] + elif n.ndim != 0: # type:ignore[union-attr] + raise ValueError("Array exponent must be a scalar") + matrix, n = xp_promote(matrix, n, force_floating=True, xp=xp) + + # If n is a lazy array, we cannot take fast paths for special cases. + if is_lazy_array(n): + # Lazy execution. We compute all special cases and the general case + result = from_exp_coords(as_exp_coords(matrix) * n) + identity = xp.eye(4, dtype=matrix.dtype, device=device) + result = xp.where(n == 0, identity, result) + result = xp.where(n == -1, inv(matrix), result) + result = xp.where(n == 1, matrix, result) + return result + if n == 0: + identity = xp.eye(4, dtype=matrix.dtype, device=device) + identity = xpx.atleast_nd(identity, ndim=matrix.ndim, xp=xp) + return xp.tile(identity, (*matrix.shape[:-2], 1, 1)) + elif n == -1: + return inv(matrix) + elif n == 1: + return matrix + return from_exp_coords(as_exp_coords(matrix) * n) + + +def mean( + matrix: Array, + weights: ArrayLike | None = None, + axis: None | int | tuple[int, ...] = None +) -> Array: + xp = array_namespace(matrix) + if matrix.shape[0] == 0: + raise ValueError("Mean of an empty rotation set is undefined.") + # Axis logic: For None, we reduce over all axes. For int, we only reduce over that + # axis. For tuple, we reduce over all specified axes. + all_axes = tuple(range(matrix.ndim - 2)) + if axis is None: + axis = all_axes + elif isinstance(axis, int): + axis = (axis,) + if not isinstance(axis, tuple): + raise ValueError("`axis` must be None, int, or tuple of ints.") + # Ensure all axes are within bounds + if (axis != () and + (min(axis) < -(matrix.ndim - 2) or max(axis) > (matrix.ndim - 3)) + ): + raise ValueError( + f"axis {axis} is out of bounds for transform with shape " + f"{matrix.shape[:-2]}." + ) + # Ensure all axes are positive and unique + axis = tuple(sorted(set(x % (matrix.ndim - 2) for x in axis))) + + lazy = is_lazy_array(matrix) + quats = quat_from_matrix_orthogonal(matrix[..., :3, :3]) + if weights is None: + quats_mean = quat_mean(quats, axis=axis) + else: + neg_weights = weights < 0 # type:ignore[operator] + any_neg_weights = xp.any(neg_weights) + if not lazy and any_neg_weights: + raise ValueError("`weights` must be non-negative.") + if weights.shape != matrix.shape[:-2]: # type:ignore[union-attr] + raise ValueError( + f"Expected `weights` to match transform shape, got shape " + f"{weights.shape} for {matrix.shape[:-2]} transformations." # type:ignore[union-attr] + ) + quats_mean = quat_mean(quats, weights=weights, axis=axis) + r_mean = quat_as_matrix(quats_mean) + + t = matrix[..., :3, 3] + if weights is None: + t_mean = xp.mean(t, axis=axis) + else: + norm = xp.sum(weights[..., None], axis=axis) # type:ignore[index] + wsum = xp.sum(t * weights[..., None], axis=axis) # type:ignore[index] + t_mean = wsum / norm + + tf = _create_transformation_matrix(t_mean, r_mean) + if weights is not None and lazy: + # We cannot raise on negative weights because jit code needs to be + # non-branching. We return NaN instead + mask = xp.where(any_neg_weights, xp.nan, 1.0) # pyrefly:ignore[unbound-name] + tf = mask * tf + return tf + + +def setitem( + matrix: Array, + indexer: Array | int | tuple | slice | EllipsisType | None, + value: Array, +) -> Array: + xp = array_namespace(matrix) + if isinstance(indexer, EllipsisType): + return xpx.at(matrix)[indexer].set(value) + if is_array_api_obj(indexer) and indexer.dtype == xp.bool: # type:ignore[union-attr] + return xpx.at(matrix)[indexer].set(value) # type:ignore[index] + return xpx.at(matrix)[indexer, ...].set(value) # type:ignore[index] + + +def normalize_dual_quaternion(dual_quat: Array) -> Array: + """Normalize dual quaternion.""" + xp = array_namespace(dual_quat) + real, dual = _normalize_dual_quaternion(dual_quat[..., :4], dual_quat[..., 4:]) + return xp.concat((real, dual), axis=-1) + + +def _create_transformation_matrix( + translations: Array, rotation_matrices: Array +) -> Array: + if not translations.shape[:-1] == rotation_matrices.shape[:-2]: + raise ValueError( + "The number of rotation matrices and translations must be the same." + ) + xp = array_namespace(translations) + matrix = xp.empty( + (*translations.shape[:-1], 4, 4), + dtype=translations.dtype, + device=xp_device(translations), + ) + matrix = xpx.at(matrix)[..., :3, :3].set(rotation_matrices) + matrix = xpx.at(matrix)[..., :3, 3].set(translations) + matrix = xpx.at(matrix)[..., 3, :3].set(0) + matrix = xpx.at(matrix)[..., 3, 3].set(1) + return matrix + + +def _compute_se3_exp_translation_transform(rot_vec: Array) -> Array: + """Compute the transformation matrix from the se3 translation part to SE3 + translation. + + The transformation matrix depends on the rotation vector. + """ + xp = array_namespace(rot_vec) + device = xp_device(rot_vec) + dtype = rot_vec.dtype + angle = xp_vector_norm(rot_vec, axis=-1, keepdims=True, xp=xp) + small_scale = angle < 1e-3 + + k1_small = 0.5 - angle**2 / 24 + angle**4 / 720 + # Avoid division by zero for non-branching computations. The value will get + # discarded in the xp.where selection. + safe_angle = angle + xp.asarray(small_scale, dtype=dtype, device=device) + k1 = (1.0 - xp.cos(angle)) / safe_angle**2 + k1 = xp.where(small_scale, k1_small, k1) + + k2_small = 1 / 6 - angle**2 / 120 + angle**4 / 5040 + # Again, avoid division by zero by adding one to all near-zero angles. + safe_angle = angle + xp.asarray(small_scale, dtype=dtype, device=device) + k2 = (angle - xp.sin(angle)) / safe_angle**3 + k2 = xp.where(small_scale, k2_small, k2) + + s = _create_skew_matrix(rot_vec) + eye = xp.eye(3, dtype=dtype, device=device) + + return eye + k1[..., None] * s + k2[..., None] * s @ s + + +def _compute_se3_log_translation_transform(rot_vec: Array) -> Array: + """Compute the transformation matrix from SE3 translation to the se3 + translation part. + + It is the inverse of `_compute_se3_exp_translation_transform` in a closed + analytical form. + """ + xp = array_namespace(rot_vec) + dtype = rot_vec.dtype + device = xp_device(rot_vec) + angle = xp_vector_norm(rot_vec, axis=-1, keepdims=True, xp=xp) + mask = angle < 1e-3 + + k_small = 1 / 12 + angle**2 / 720 + angle**4 / 30240 + safe_angle = angle + xp.asarray(mask, dtype=dtype, device=device) + k = (1 - 0.5 * angle / xp.tan(0.5 * safe_angle)) / safe_angle**2 + k = xp.where(mask, k_small, k) + + s = _create_skew_matrix(rot_vec) + + return xp.eye(3, dtype=dtype, device=device) - 0.5 * s + k[..., None] * s @ s + + +def _create_skew_matrix(vec: Array) -> Array: + """Create skew-symmetric (aka cross-product) matrix for stack of vectors.""" + xp = array_namespace(vec) + result = xp.zeros((*vec.shape[:-1], 3, 3), dtype=vec.dtype, device=xp_device(vec)) + result = xpx.at(result)[..., 0, 1].set(-vec[..., 2]) + result = xpx.at(result)[..., 0, 2].set(vec[..., 1]) + result = xpx.at(result)[..., 1, 0].set(vec[..., 2]) + result = xpx.at(result)[..., 1, 2].set(-vec[..., 0]) + result = xpx.at(result)[..., 2, 0].set(-vec[..., 1]) + result = xpx.at(result)[..., 2, 1].set(vec[..., 0]) + return result + + +def _normalize_dual_quaternion( + real_part: Array, dual_part: Array +) -> tuple[Array, Array]: + """Ensure that the dual quaternion has unit norm. + + The norm is a dual number and must be 1 + 0 * epsilon, which means that + the real quaternion must have unit norm and the dual quaternion must be + orthogonal to the real quaternion. + """ + xp = array_namespace(real_part) + + real_norm = xp_vector_norm(real_part, axis=-1, keepdims=True, xp=xp) + + # special case: real quaternion is 0, we set it to identity + zero_real_mask = real_norm == 0.0 + unit_quat = xp.asarray( + [0.0, 0.0, 0.0, 1.0], dtype=real_part.dtype, device=xp_device(real_part) + ) + real_part = xp.where(zero_real_mask, unit_quat, real_part) + real_norm = xp.where(zero_real_mask, 1.0, real_norm) + + # 1. ensure unit real quaternion + real_part = real_part / real_norm + dual_part = dual_part / real_norm + + # 2. ensure orthogonality of real and dual quaternion + dual_part -= xp.sum(real_part * dual_part, axis=-1, keepdims=True) * real_part + + return real_part, dual_part diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/transform/_rotation.py b/venv/lib/python3.12/site-packages/scipy/spatial/transform/_rotation.py new file mode 100644 index 0000000000000000000000000000000000000000..6765df1d79ffc6e8288d184c85dae7307052d06a --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/transform/_rotation.py @@ -0,0 +1,2856 @@ +from __future__ import annotations + +from collections.abc import Iterator, Sequence +from types import EllipsisType, GenericAlias, ModuleType, NotImplementedType + +import numpy as np + +import scipy.spatial.transform._rotation_cy as cython_backend +import scipy.spatial.transform._rotation_xp as xp_backend +from scipy.spatial.transform._rotation_groups import create_group +from scipy._lib._array_api import ( + array_namespace, + Array, + is_numpy, + ArrayLike, + is_lazy_array, + xp_capabilities, + xp_promote, +) +from scipy._external.array_api_compat import device as xp_device +import scipy._external.array_api_extra as xpx +from scipy._lib._util import _transition_to_rng, broadcastable + +backend_registry = {array_namespace(np.empty(0)): cython_backend} + + +def select_backend(xp: ModuleType, cython_compatible: bool): + """Select the backend for the given array library. + + We need this selection function because the Cython backend for numpy does not + support quaternions of arbitrary dimensions. We therefore only use the Array API + backend for numpy if we are dealing with rotations of more than one leading + dimension. + """ + if is_numpy(xp) and not cython_compatible: + return xp_backend + return backend_registry.get(xp, xp_backend) + + +def _promote(*args: ArrayLike, xp: ModuleType) -> Array: + """Promote arrays to float64 for numpy, else according to the Array API spec. + + The return array dtype follows the following rules: + - If quat is an ArrayLike or NumPy array, we always promote to float64 + - If quat is an Array from frameworks other than NumPy, we preserve the precision + of the input array dtype. + + The first rule is required by the cython backend signatures that expect + cython.double views. The second rule is necessary to promote non-floating arrays + to the correct type in frameworks that may not support double precision (e.g. + jax by default). + """ + if is_numpy(xp): + args += (np.empty(0, dtype=np.float64),) # Force float64 conversion + out = xp_promote(*args, force_floating=True, xp=xp) + if len(args) == 2: # One argument was passed + the added empty array + return out[0] + return out[:-1] + return xp_promote(*args, force_floating=True, xp=xp) + + +rotation_extra_note = """The methods ``as_davenport``, ``apply``, and ``align_vectors`` + are not supported with cupy<14.*. + + """ + + +@xp_capabilities( + skip_backends=[("dask.array", "missing linalg.cross/det functions")], + method_capabilities={ + "__init__": dict( + jax_jit=False, + skip_backends=[("dask.array", "dask not supported")], + ), + "as_davenport": dict( + skip_backends=[ + ("dask.array", "missing linalg.cross/det functions and .mT attribute"), + ("cupy", "missing .mT attribute in cupy<14.*"), + ] + ), + "apply": dict( + skip_backends=[ + ("dask.array", "missing linalg.cross/det functions and .mT attribute"), + ("cupy", "missing .mT attribute in cupy<14.*"), + ], + ), + "__getitem__": dict( + jax_jit=False, + skip_backends=[("dask.array", "cannot handle zero-length rotations")], + ), + "__setitem__": dict( + jax_jit=False, + skip_backends=[("dask.array", "cannot handle zero-length rotations")], + ), + "align_vectors": dict( + skip_backends=[ + ("dask.array", "missing linalg.cross/det functions and .mT attribute"), + ("cupy", "missing .mT attribute in cupy<14.*"), + ], + ), + }, + extra_note=rotation_extra_note, +) +class Rotation: + """Rotation in 3 dimensions. + + This class provides an interface to initialize from and represent rotations + with: + + - Quaternions + - Rotation Matrices + - Rotation Vectors + - Modified Rodrigues Parameters + - Euler Angles + - Davenport Angles (Generalized Euler Angles) + + The following operations on rotations are supported: + + - Application on vectors + - Rotation Composition + - Rotation Inversion + - Rotation Indexing + + A `Rotation` instance can contain a single rotation transform or rotations of + multiple leading dimensions. E.g., it is possible to have an N-dimensional array of + (N, M, K) rotations. When applied to other rotations or vectors, standard + broadcasting rules apply. + + Indexing within a rotation is supported to access a subset of the rotations stored + in a `Rotation` instance. + + To create `Rotation` objects use ``from_...`` methods (see examples below). + ``Rotation(...)`` is not supposed to be instantiated directly. + + Parameters + ---------- + quat : array_like, shape (..., 4) + Quaternion representing the rotation. + normalize : bool, optional + If True, orthonormalize the rotation matrix using singular value + decomposition. If False, the rotation matrix is not checked for + orthogonality or right-handedness. + copy : bool, optional + If True, copy the input matrix. If False, a reference to the input + matrix is used. If normalize is True, the input matrix is always + copied regardless of the value of copy. + scalar_first : bool, optional + If ``True`` then `quat` is expect in scalar-first format otherwise it is + expected in scalar-last format. Defaults to ``False``. + + Attributes + ---------- + single + + Methods + ------- + __len__ + from_quat + from_matrix + from_rotvec + from_mrp + from_euler + from_davenport + as_quat + as_matrix + as_rotvec + as_mrp + as_euler + as_davenport + concatenate + apply + __mul__ + __pow__ + inv + magnitude + approx_equal + mean + reduce + create_group + __getitem__ + identity + random + align_vectors + + See Also + -------- + Slerp + + Notes + ----- + .. versionadded:: 1.2.0 + + Examples + -------- + >>> from scipy.spatial.transform import Rotation as R + >>> import numpy as np + + A `Rotation` instance can be initialized in any of the above formats and + converted to any of the others. The underlying object is independent of the + representation used for initialization. + + Consider a counter-clockwise rotation of 90 degrees about the z-axis. This + corresponds to the following quaternion (in scalar-last format): + + >>> r = R.from_quat([0, 0, np.sin(np.pi/4), np.cos(np.pi/4)]) + + The rotation can be expressed in any of the other formats: + + >>> r.as_matrix() + array([[ 2.22044605e-16, -1.00000000e+00, 0.00000000e+00], + [ 1.00000000e+00, 2.22044605e-16, 0.00000000e+00], + [ 0.00000000e+00, 0.00000000e+00, 1.00000000e+00]]) + >>> r.as_rotvec() + array([0. , 0. , 1.57079633]) + >>> r.as_euler('zyx', degrees=True) + array([90., 0., 0.]) + + The same rotation can be initialized using a rotation matrix: + + >>> r = R.from_matrix([[0, -1, 0], + ... [1, 0, 0], + ... [0, 0, 1]]) + + Representation in other formats: + + >>> r.as_quat() + array([0. , 0. , 0.70710678, 0.70710678]) + >>> r.as_rotvec() + array([0. , 0. , 1.57079633]) + >>> r.as_euler('zyx', degrees=True) + array([90., 0., 0.]) + + The rotation vector corresponding to this rotation is given by: + + >>> r = R.from_rotvec(np.pi/2 * np.array([0, 0, 1])) + + Representation in other formats: + + >>> r.as_quat() + array([0. , 0. , 0.70710678, 0.70710678]) + >>> r.as_matrix() + array([[ 2.22044605e-16, -1.00000000e+00, 0.00000000e+00], + [ 1.00000000e+00, 2.22044605e-16, 0.00000000e+00], + [ 0.00000000e+00, 0.00000000e+00, 1.00000000e+00]]) + >>> r.as_euler('zyx', degrees=True) + array([90., 0., 0.]) + + The ``from_euler`` method is quite flexible in the range of input formats + it supports. Here we initialize a single rotation about a single axis: + + >>> r = R.from_euler('z', 90, degrees=True) + + Again, the object is representation independent and can be converted to any + other format: + + >>> r.as_quat() + array([0. , 0. , 0.70710678, 0.70710678]) + >>> r.as_matrix() + array([[ 2.22044605e-16, -1.00000000e+00, 0.00000000e+00], + [ 1.00000000e+00, 2.22044605e-16, 0.00000000e+00], + [ 0.00000000e+00, 0.00000000e+00, 1.00000000e+00]]) + >>> r.as_rotvec() + array([0. , 0. , 1.57079633]) + + It is also possible to initialize multiple rotations in a single instance + using any of the ``from_...`` functions. Here we initialize a stack of 3 + rotations using the ``from_euler`` method: + + >>> r = R.from_euler('zyx', [ + ... [90, 0, 0], + ... [0, 45, 0], + ... [45, 60, 30]], degrees=True) + + The other representations also now return a stack of 3 rotations. For + example: + + >>> r.as_quat() + array([[0. , 0. , 0.70710678, 0.70710678], + [0. , 0.38268343, 0. , 0.92387953], + [0.39190384, 0.36042341, 0.43967974, 0.72331741]]) + + Applying the above rotations onto a vector: + + >>> v = [1, 2, 3] + >>> r.apply(v) + array([[-2. , 1. , 3. ], + [ 2.82842712, 2. , 1.41421356], + [ 2.24452282, 0.78093109, 2.89002836]]) + + A `Rotation` instance can be indexed and sliced as if it were an ND array: + + >>> r.as_quat() + array([[0. , 0. , 0.70710678, 0.70710678], + [0. , 0.38268343, 0. , 0.92387953], + [0.39190384, 0.36042341, 0.43967974, 0.72331741]]) + >>> p = r[0] + >>> p.as_matrix() + array([[ 2.22044605e-16, -1.00000000e+00, 0.00000000e+00], + [ 1.00000000e+00, 2.22044605e-16, 0.00000000e+00], + [ 0.00000000e+00, 0.00000000e+00, 1.00000000e+00]]) + >>> q = r[1:3] + >>> q.as_quat() + array([[0. , 0.38268343, 0. , 0.92387953], + [0.39190384, 0.36042341, 0.43967974, 0.72331741]]) + + In fact it can be converted to numpy.array: + + >>> r_array = np.asarray(r) + >>> r_array.shape + (3,) + >>> r_array[0].as_matrix() + array([[ 2.22044605e-16, -1.00000000e+00, 0.00000000e+00], + [ 1.00000000e+00, 2.22044605e-16, 0.00000000e+00], + [ 0.00000000e+00, 0.00000000e+00, 1.00000000e+00]]) + + Multiple rotations can be composed using the ``*`` operator: + + >>> r1 = R.from_euler('z', 90, degrees=True) + >>> r2 = R.from_rotvec([np.pi/4, 0, 0]) + >>> v = [1, 2, 3] + >>> r2.apply(r1.apply(v)) + array([-2. , -1.41421356, 2.82842712]) + >>> r3 = r2 * r1 # Note the order + >>> r3.apply(v) + array([-2. , -1.41421356, 2.82842712]) + + A rotation can be composed with itself using the ``**`` operator: + + >>> p = R.from_rotvec([1, 0, 0]) + >>> q = p ** 2 + >>> q.as_rotvec() + array([2., 0., 0.]) + + Finally, it is also possible to invert rotations: + + >>> r1 = R.from_euler('z', [[90], [45]], degrees=True) + >>> r2 = r1.inv() + >>> r2.as_euler('zyx', degrees=True) + array([[-90., 0., 0.], + [-45., 0., 0.]]) + + The following function can be used to plot rotations with Matplotlib by + showing how they transform the standard x, y, z coordinate axes: + + >>> import matplotlib.pyplot as plt + + >>> def plot_rotated_axes(ax, r, name=None, offset=(0, 0, 0), scale=1): + ... colors = ("#FF6666", "#005533", "#1199EE") # Colorblind-safe RGB + ... loc = np.array([offset, offset]) + ... for i, (axis, c) in enumerate(zip((ax.xaxis, ax.yaxis, ax.zaxis), + ... colors)): + ... axlabel = axis.axis_name + ... axis.set_label_text(axlabel) + ... axis.label.set_color(c) + ... axis.line.set_color(c) + ... axis.set_tick_params(colors=c) + ... line = np.zeros((2, 3)) + ... line[1, i] = scale + ... line_rot = r.apply(line) + ... line_plot = line_rot + loc + ... ax.plot(line_plot[:, 0], line_plot[:, 1], line_plot[:, 2], c) + ... text_loc = line[1]*1.2 + ... text_loc_rot = r.apply(text_loc) + ... text_plot = text_loc_rot + loc[0] + ... ax.text(*text_plot, axlabel.upper(), color=c, + ... va="center", ha="center") + ... ax.text(*offset, name, color="k", va="center", ha="center", + ... bbox={"fc": "w", "alpha": 0.8, "boxstyle": "circle"}) + + Create three rotations - the identity and two Euler rotations using + intrinsic and extrinsic conventions: + + >>> r0 = R.identity() + >>> r1 = R.from_euler("ZYX", [90, -30, 0], degrees=True) # intrinsic + >>> r2 = R.from_euler("zyx", [90, -30, 0], degrees=True) # extrinsic + + Add all three rotations to a single plot: + + >>> ax = plt.figure().add_subplot(projection="3d", proj_type="ortho") + >>> plot_rotated_axes(ax, r0, name="r0", offset=(0, 0, 0)) + >>> plot_rotated_axes(ax, r1, name="r1", offset=(3, 0, 0)) + >>> plot_rotated_axes(ax, r2, name="r2", offset=(6, 0, 0)) + >>> _ = ax.annotate( + ... "r0: Identity Rotation\\n" + ... "r1: Intrinsic Euler Rotation (ZYX)\\n" + ... "r2: Extrinsic Euler Rotation (zyx)", + ... xy=(0.6, 0.7), xycoords="axes fraction", ha="left" + ... ) + >>> ax.set(xlim=(-1.25, 7.25), ylim=(-1.25, 1.25), zlim=(-1.25, 1.25)) + >>> ax.set(xticks=range(-1, 8), yticks=[-1, 0, 1], zticks=[-1, 0, 1]) + >>> ax.set_aspect("equal", adjustable="box") + >>> ax.figure.set_size_inches(6, 5) + >>> plt.tight_layout() + + Show the plot: + + >>> plt.show() + + These examples serve as an overview into the `Rotation` class and highlight + major functionalities. For more thorough examples of the range of input and + output formats supported, consult the individual method's examples. + + """ + + # generic type compatibility with scipy-stubs + __class_getitem__: classmethod = classmethod(GenericAlias) + + def __init__( + self, + quat: ArrayLike, + normalize: bool = True, + copy: bool = True, + scalar_first: bool = False, + ): + xp = array_namespace(quat) + self._xp = xp + quat = _promote(quat, xp=xp) + if quat.shape[-1] != 4: + raise ValueError( + f"Expected `quat` to have shape (..., 4), got {quat.shape}." + ) + # Single NumPy quats or list of quats are accelerated by the cython backend. + # This backend needs inputs with fixed ndim, so we always expand to 2D and + # select the 0th element if quat was single to get the correct shape. For other + # frameworks and quaternion tensors we use the generic array API backend. + self._single = quat.ndim == 1 and is_numpy(xp) + if self._single: + quat = xpx.atleast_nd(quat, ndim=2, xp=xp) + self._backend = select_backend(xp, cython_compatible=quat.ndim < 3) + self._quat: Array = self._backend.from_quat( + quat, normalize=normalize, copy=copy, scalar_first=scalar_first + ) + + @staticmethod + def from_quat(quat: ArrayLike, *, scalar_first: bool = False) -> Rotation: + """Initialize from quaternions. + + Rotations in 3 dimensions can be represented using unit norm + quaternions [1]_. + + The 4 components of a quaternion are divided into a scalar part ``w`` + and a vector part ``(x, y, z)`` and can be expressed from the angle + ``theta`` and the axis ``n`` of a rotation as follows:: + + w = cos(theta / 2) + x = sin(theta / 2) * n_x + y = sin(theta / 2) * n_y + z = sin(theta / 2) * n_z + + There are 2 conventions to order the components in a quaternion: + + - scalar-first order -- ``(w, x, y, z)`` + - scalar-last order -- ``(x, y, z, w)`` + + The choice is controlled by `scalar_first` argument. + By default, it is False and the scalar-last order is assumed. + + Advanced users may be interested in the "double cover" of 3D space by + the quaternion representation [2]_. As of version 1.11.0, the + following subset (and only this subset) of operations on a `Rotation` + ``r`` corresponding to a quaternion ``q`` are guaranteed to preserve + the double cover property: ``r = Rotation.from_quat(q)``, + ``r.as_quat(canonical=False)``, ``r.inv()``, and composition using the + ``*`` operator such as ``r*r``. + + Parameters + ---------- + quat : array_like, shape (..., 4) + Each row is a (possibly non-unit norm) quaternion representing an + active rotation. Each quaternion will be normalized to unit norm. + scalar_first : bool, optional + Whether the scalar component goes first or last. + Default is False, i.e. the scalar-last order is assumed. + + Returns + ------- + rotation : `Rotation` instance + Object containing the rotations represented by input quaternions. + + References + ---------- + .. [1] https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation + .. [2] Hanson, Andrew J. "Visualizing quaternions." + Morgan Kaufmann Publishers Inc., San Francisco, CA. 2006. + + Examples + -------- + >>> from scipy.spatial.transform import Rotation as R + + A rotation can be initialized from a quaternion with the scalar-last + (default) or scalar-first component order as shown below: + + >>> r = R.from_quat([0, 0, 0, 1]) + >>> r.as_matrix() + array([[1., 0., 0.], + [0., 1., 0.], + [0., 0., 1.]]) + >>> r = R.from_quat([1, 0, 0, 0], scalar_first=True) + >>> r.as_matrix() + array([[1., 0., 0.], + [0., 1., 0.], + [0., 0., 1.]]) + + It is possible to initialize multiple rotations in a single object by + passing an N-dimensional array: + + >>> r = R.from_quat([[ + ... [1, 0, 0, 0], + ... [0, 0, 0, 1] + ... ]]) + >>> r.as_quat() + array([[[1., 0., 0., 0.], + [0., 0., 0., 1.]]]) + >>> r.as_quat().shape + (1, 2, 4) + + It is also possible to have a stack of a single rotation: + + >>> r = R.from_quat([[0, 0, 0, 1]]) + >>> r.as_quat() + array([[0., 0., 0., 1.]]) + >>> r.as_quat().shape + (1, 4) + + Quaternions are normalized before initialization. + + >>> r = R.from_quat([0, 0, 1, 1]) + >>> r.as_quat() + array([0. , 0. , 0.70710678, 0.70710678]) + """ + return Rotation(quat, normalize=True, scalar_first=scalar_first) + + @staticmethod + def from_matrix(matrix: ArrayLike, *, assume_valid: bool = False) -> Rotation: + """Initialize from rotation matrix. + + Rotations in 3 dimensions can be represented with 3 x 3 orthogonal + matrices [1]_. If the input is not orthogonal, an approximation is + created by orthogonalizing the input matrix using the method described + in [2]_, and then converting the orthogonal rotation matrices to + quaternions using the algorithm described in [3]_. Matrices must be + right-handed. + + Parameters + ---------- + matrix : array_like, shape (..., 3, 3) + A single matrix or an ND array of matrices, where the last two dimensions + contain the rotation matrices. + assume_valid : bool, optional + Must be False unless users can guarantee the input is a valid rotation + matrix, i.e. it is orthogonal, rows and columns have unit norm and the + determinant is 1. Setting this to True without ensuring these properties + is unsafe and will silently lead to incorrect results. If True, + normalization steps are skipped, which can improve runtime performance. + Default is False. + + Returns + ------- + rotation : `Rotation` instance + Object containing the rotations represented by the rotation + matrices. + + Notes + ----- + This function was called from_dcm before. + + .. versionadded:: 1.4.0 + + References + ---------- + .. [1] https://en.wikipedia.org/wiki/Rotation_matrix#In_three_dimensions + .. [2] https://en.wikipedia.org/wiki/Orthogonal_Procrustes_problem + .. [3] F. Landis Markley, "Unit Quaternion from Rotation Matrix", + Journal of guidance, control, and dynamics vol. 31.2, pp. + 440-442, 2008. + + Examples + -------- + >>> from scipy.spatial.transform import Rotation as R + >>> import numpy as np + + Initialize a single rotation: + + >>> r = R.from_matrix([ + ... [0, -1, 0], + ... [1, 0, 0], + ... [0, 0, 1]]) + >>> r.single + True + >>> r.as_matrix().shape + (3, 3) + + Initialize multiple rotations in a single object: + + >>> r = R.from_matrix([ + ... [ + ... [0, -1, 0], + ... [1, 0, 0], + ... [0, 0, 1], + ... ], + ... [ + ... [1, 0, 0], + ... [0, 0, -1], + ... [0, 1, 0], + ... ]]) + >>> r.as_matrix().shape + (2, 3, 3) + >>> r.single + False + >>> len(r) + 2 + + If input matrices are not special orthogonal (orthogonal with + determinant equal to +1), then a special orthogonal estimate is stored: + + >>> a = np.array([ + ... [0, -0.5, 0], + ... [0.5, 0, 0], + ... [0, 0, 0.5]]) + >>> np.linalg.det(a) + 0.125 + >>> r = R.from_matrix(a) + >>> matrix = r.as_matrix() + >>> matrix + array([[ 0., -1., 0.], + [ 1., 0., 0.], + [ 0., 0., 1.]]) + >>> np.linalg.det(matrix) + 1.0 + + It is also possible to have a stack containing a single rotation: + + >>> r = R.from_matrix([[ + ... [0, -1, 0], + ... [1, 0, 0], + ... [0, 0, 1]]]) + >>> r.as_matrix() + array([[[ 0., -1., 0.], + [ 1., 0., 0.], + [ 0., 0., 1.]]]) + >>> r.as_matrix().shape + (1, 3, 3) + + We can also create an N-dimensional array of rotations: + + >>> r = R.from_matrix(np.tile(np.eye(3), (2, 3, 1, 1))) + >>> r.shape + (2, 3) + + """ + xp = array_namespace(matrix) + matrix = _promote(matrix, xp=xp) + # Resulting quat will have 1 less dimension than matrix + backend = select_backend(xp, cython_compatible=matrix.ndim < 4) + quat = backend.from_matrix(matrix, assume_valid=assume_valid) + return Rotation._from_raw_quat(quat, xp=xp, backend=backend) + + @staticmethod + def from_rotvec(rotvec: ArrayLike, degrees: bool = False) -> Rotation: + """Initialize from rotation vectors. + + A rotation vector is a 3 dimensional vector which is co-directional to + the axis of rotation and whose norm gives the angle of rotation [1]_. + + Parameters + ---------- + rotvec : array_like, shape (..., 3) + A single vector or an ND array of vectors, where the last dimension + contains the rotation vectors. + degrees : bool, optional + If True, then the given magnitudes are assumed to be in degrees. + Default is False. + + .. versionadded:: 1.7.0 + + Returns + ------- + rotation : `Rotation` instance + Object containing the rotations represented by input rotation + vectors. + + References + ---------- + .. [1] https://en.wikipedia.org/wiki/Axis%E2%80%93angle_representation#Rotation_vector + + Examples + -------- + >>> from scipy.spatial.transform import Rotation as R + >>> import numpy as np + + Initialize a single rotation: + + >>> r = R.from_rotvec(np.pi/2 * np.array([0, 0, 1])) + >>> r.as_rotvec() + array([0. , 0. , 1.57079633]) + >>> r.as_rotvec().shape + (3,) + + Initialize a rotation in degrees, and view it in degrees: + + >>> r = R.from_rotvec(45 * np.array([0, 1, 0]), degrees=True) + >>> r.as_rotvec(degrees=True) + array([ 0., 45., 0.]) + + Initialize multiple rotations in one object: + + >>> r = R.from_rotvec([ + ... [0, 0, np.pi/2], + ... [np.pi/2, 0, 0]]) + >>> r.as_rotvec() + array([[0. , 0. , 1.57079633], + [1.57079633, 0. , 0. ]]) + >>> r.as_rotvec().shape + (2, 3) + + It is also possible to have a stack of a single rotation: + + >>> r = R.from_rotvec([[0, 0, np.pi/2]]) + >>> r.as_rotvec().shape + (1, 3) + + """ + xp = array_namespace(rotvec) + rotvec = _promote(rotvec, xp=xp) + backend = select_backend(xp, cython_compatible=rotvec.ndim < 3) + quat = backend.from_rotvec(rotvec, degrees=degrees) + return Rotation._from_raw_quat(quat, xp=xp, backend=backend) + + @staticmethod + def from_euler(seq: str, angles: ArrayLike, degrees: bool = False) -> Rotation: + """Initialize from Euler angles. + + Rotations in 3-D can be represented by a sequence of 3 + rotations around a sequence of axes. In theory, any three axes spanning + the 3-D Euclidean space are enough. In practice, the axes of rotation are + chosen to be the basis vectors. + + The three rotations can either be in a global frame of reference + (extrinsic) or in a body centred frame of reference (intrinsic), which + is attached to, and moves with, the object under rotation [1]_. + + Parameters + ---------- + seq : str + Specifies sequence of axes for rotations. Up to 3 characters + belonging to the set {'X', 'Y', 'Z'} for intrinsic rotations, or + {'x', 'y', 'z'} for extrinsic rotations. Extrinsic and intrinsic + rotations cannot be mixed in one function call. + angles : float or array_like, shape (..., [1 or 2 or 3]) + Euler angles specified in radians (`degrees` is False) or degrees + (`degrees` is True). + Each character in `seq` defines one axis around which `angles` turns. + The resulting rotation has the shape np.atleast_1d(angles).shape[:-1]. + Dimensionless angles are thus only valid for single character `seq`. + + degrees : bool, optional + If True, then the given angles are assumed to be in degrees. + Default is False. + + Returns + ------- + rotation : `Rotation` instance + Object containing the rotation represented by the sequence of + rotations around given axes with given angles. + + References + ---------- + .. [1] https://en.wikipedia.org/wiki/Euler_angles#Definition_by_intrinsic_rotations + + Examples + -------- + >>> from scipy.spatial.transform import Rotation as R + + Initialize a single rotation along a single axis: + + >>> r = R.from_euler('x', 90, degrees=True) + >>> r.as_quat().shape + (4,) + + Initialize a single rotation with a given axis sequence: + + >>> r = R.from_euler('zyx', [90, 45, 30], degrees=True) + >>> r.as_quat().shape + (4,) + + Initialize a stack with a single rotation around a single axis: + + >>> r = R.from_euler('x', [[90]], degrees=True) + >>> r.as_quat().shape + (1, 4) + + Initialize a stack with a single rotation with an axis sequence: + + >>> r = R.from_euler('zyx', [[90, 45, 30]], degrees=True) + >>> r.as_quat().shape + (1, 4) + + Initialize multiple elementary rotations in one object: + + >>> r = R.from_euler('x', [[90], [45], [30]], degrees=True) + >>> r.as_quat().shape + (3, 4) + + Initialize multiple rotations in one object: + + >>> r = R.from_euler('zyx', [[90, 45, 30], [35, 45, 90]], degrees=True) + >>> r.as_quat().shape + (2, 4) + + """ + xp = array_namespace(angles) + angles = _promote(angles, xp=xp) + backend = select_backend(xp, cython_compatible=angles.ndim < 3) + quat = backend.from_euler(seq, angles, degrees=degrees) + return Rotation._from_raw_quat(quat, xp=xp, backend=backend) + + @staticmethod + def from_davenport( + axes: ArrayLike, + order: str, + angles: ArrayLike | float, + degrees: bool = False, + ) -> Rotation: + """Initialize from Davenport angles. + + Rotations in 3-D can be represented by a sequence of 3 + rotations around a sequence of axes. + + The three rotations can either be in a global frame of reference + (extrinsic) or in a body centred frame of reference (intrinsic), which + is attached to, and moves with, the object under rotation [1]_. + + For both Euler angles and Davenport angles, consecutive axes must + be are orthogonal (``axis2`` is orthogonal to both ``axis1`` and + ``axis3``). For Euler angles, there is an additional relationship + between ``axis1`` or ``axis3``, with two possibilities: + + - ``axis1`` and ``axis3`` are also orthogonal (asymmetric sequence) + - ``axis1 == axis3`` (symmetric sequence) + + For Davenport angles, this last relationship is relaxed [2]_, and only + the consecutive orthogonal axes requirement is maintained. + + Parameters + ---------- + axes : array_like, shape (3,) or (..., [1 or 2 or 3], 3) + Axis of rotation, if one dimensional. If two or more dimensional, describes + the sequence of axes for rotations, where each axes[..., i, :] is the ith + axis. If more than one axis is given, then the second axis must be + orthogonal to both the first and third axes. + order : str + If it is equal to 'e' or 'extrinsic', the sequence will be + extrinsic. If it is equal to 'i' or 'intrinsic', sequence + will be treated as intrinsic. + angles : float or array_like, shape (..., [1 or 2 or 3]) + Angles specified in radians (`degrees` is False) or degrees + (`degrees` is True). + Each angle i in the last dimension of `angles` turns around the corresponding + axis axis[..., i, :]. The resulting rotation has the shape + np.broadcast_shapes(np.atleast_2d(axes).shape[:-2], np.atleast_1d(angles).shape[:-1]) + Dimensionless angles are thus only valid for a single axis. + + degrees : bool, optional + If True, then the given angles are assumed to be in degrees. + Default is False. + + Returns + ------- + rotation : `Rotation` instance + Object containing the rotation represented by the sequence of + rotations around given axes with given angles. + + References + ---------- + .. [1] https://en.wikipedia.org/wiki/Euler_angles#Definition_by_intrinsic_rotations + .. [2] Shuster, Malcolm & Markley, Landis. (2003). Generalization of + the Euler Angles. Journal of the Astronautical Sciences. 51. 123-132. + 10.1007/BF03546304. + + Examples + -------- + >>> from scipy.spatial.transform import Rotation as R + + Davenport angles are a generalization of Euler angles, when we use the + canonical basis axes: + + >>> ex = [1, 0, 0] + >>> ey = [0, 1, 0] + >>> ez = [0, 0, 1] + + Initialize a single rotation with a given axis sequence: + + >>> axes = [ez, ey, ex] + >>> r = R.from_davenport(axes, 'extrinsic', [90, 0, 0], degrees=True) + >>> r.as_quat().shape + (4,) + + It is equivalent to Euler angles in this case: + + >>> r.as_euler('zyx', degrees=True) + array([90., 0., -0.]) + + Initialize multiple rotations in one object: + + >>> r = R.from_davenport(axes, 'extrinsic', [[90, 45, 30], [35, 45, 90]], degrees=True) + >>> r.as_quat().shape + (2, 4) + + Using only one or two axes is also possible: + + >>> r = R.from_davenport([ez, ex], 'extrinsic', [[90, 45], [35, 45]], degrees=True) + >>> r.as_quat().shape + (2, 4) + + Non-canonical axes are possible, and they do not need to be normalized, + as long as consecutive axes are orthogonal: + + >>> e1 = [2, 0, 0] + >>> e2 = [0, 1, 0] + >>> e3 = [1, 0, 1] + >>> axes = [e1, e2, e3] + >>> r = R.from_davenport(axes, 'extrinsic', [90, 45, 30], degrees=True) + >>> r.as_quat() + [ 0.701057, 0.430459, -0.092296, 0.560986] + """ # noqa: E501 + xp = array_namespace(axes) + axes, angles = _promote(axes, angles, xp=xp) + cython_compatible = axes.ndim < 3 and angles.ndim < 2 # type:ignore[union-attr] + backend = select_backend(xp, cython_compatible=cython_compatible) + quat = backend.from_davenport(axes, order, angles, degrees) + return Rotation._from_raw_quat(quat, xp=xp, backend=backend) + + @staticmethod + def from_mrp(mrp: ArrayLike) -> Rotation: + """Initialize from Modified Rodrigues Parameters (MRPs). + + MRPs are a 3 dimensional vector co-directional to the axis of rotation and whose + magnitude is equal to ``tan(theta / 4)``, where ``theta`` is the angle of + rotation (in radians) [1]_. + + MRPs have a singularity at 360 degrees which can be avoided by ensuring the + angle of rotation does not exceed 180 degrees, i.e. switching the direction of + the rotation when it is past 180 degrees. + + Parameters + ---------- + mrp : array_like, shape (..., 3) + A single vector or an ND array of vectors, where the last dimension + contains the rotation parameters. + + Returns + ------- + rotation : `Rotation` instance + Object containing the rotations represented by input MRPs. + + Notes + ----- + + .. versionadded:: 1.6.0 + + References + ---------- + .. [1] Shuster, M. D. "A Survey of Attitude Representations", + The Journal of Astronautical Sciences, Vol. 41, No.4, 1993, + pp. 475-476 + + Examples + -------- + >>> from scipy.spatial.transform import Rotation as R + >>> import numpy as np + + Initialize a single rotation: + + >>> r = R.from_mrp([0, 0, 1]) + >>> r.as_euler('xyz', degrees=True) + array([0. , 0. , 180. ]) + >>> r.as_euler('xyz').shape + (3,) + + Initialize multiple rotations in one object: + + >>> r = R.from_mrp([ + ... [0, 0, 1], + ... [1, 0, 0]]) + >>> r.as_euler('xyz', degrees=True) + array([[0. , 0. , 180. ], + [180.0 , 0. , 0. ]]) + >>> r.as_euler('xyz').shape + (2, 3) + + It is also possible to have a stack of a single rotation: + + >>> r = R.from_mrp([[0, 0, np.pi/2]]) + >>> r.as_euler('xyz').shape + (1, 3) + + """ + xp = array_namespace(mrp) + mrp = _promote(mrp, xp=xp) + backend = select_backend(xp, cython_compatible=mrp.ndim < 3) + quat = backend.from_mrp(mrp) + return Rotation._from_raw_quat(quat, xp=xp, backend=backend) + + def as_quat(self, canonical: bool = False, *, scalar_first: bool = False) -> Array: + """Represent as quaternions. + + Rotations in 3 dimensions can be represented using unit norm + quaternions [1]_. + + The 4 components of a quaternion are divided into a scalar part ``w`` + and a vector part ``(x, y, z)`` and can be expressed from the angle + ``theta`` and the axis ``n`` of a rotation as follows:: + + w = cos(theta / 2) + x = sin(theta / 2) * n_x + y = sin(theta / 2) * n_y + z = sin(theta / 2) * n_z + + There are 2 conventions to order the components in a quaternion: + + - scalar-first order -- ``(w, x, y, z)`` + - scalar-last order -- ``(x, y, z, w)`` + + The choice is controlled by `scalar_first` argument. + By default, it is False and the scalar-last order is used. + + The mapping from quaternions to rotations is + two-to-one, i.e. quaternions ``q`` and ``-q``, where ``-q`` simply + reverses the sign of each component, represent the same spatial + rotation. + + Parameters + ---------- + canonical : `bool`, default False + Whether to map the redundant double cover of rotation space to a + unique "canonical" single cover. If True, then the quaternion is + chosen from {q, -q} such that the w term is positive. If the w term + is 0, then the quaternion is chosen such that the first nonzero + term of the x, y, and z terms is positive. + scalar_first : bool, optional + Whether the scalar component goes first or last. + Default is False, i.e. the scalar-last order is used. + + Returns + ------- + quat : `numpy.ndarray`, shape (..., 4) + Shape depends on shape of inputs used for initialization. + + References + ---------- + .. [1] https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation + + Examples + -------- + >>> from scipy.spatial.transform import Rotation as R + >>> import numpy as np + + A rotation can be represented as a quaternion with either scalar-last + (default) or scalar-first component order. + This is shown for a single rotation: + + >>> r = R.from_matrix(np.eye(3)) + >>> r.as_quat() + array([0., 0., 0., 1.]) + >>> r.as_quat(scalar_first=True) + array([1., 0., 0., 0.]) + + The resulting shape of the quaternion is always the shape of the Rotation + object with an added last dimension of size 4. E.g. when the `Rotation` object + contains an N-dimensional array (N, M, K) of rotations, the result will be a + 4-dimensional array: + + >>> r = R.from_rotvec(np.ones((2, 3, 4, 3))) + >>> r.as_quat().shape + (2, 3, 4, 4) + + Quaternions can be mapped from a redundant double cover of the + rotation space to a canonical representation with a positive w term. + + >>> r = R.from_quat([0, 0, 0, -1]) + >>> r.as_quat() + array([0. , 0. , 0. , -1.]) + >>> r.as_quat(canonical=True) + array([0. , 0. , 0. , 1.]) + """ + quat = self._backend.as_quat( + self._quat, canonical=canonical, scalar_first=scalar_first + ) + if self._single: + return quat[0, ...] + return quat + + def as_matrix(self) -> Array: + """Represent as rotation matrix. + + 3D rotations can be represented using rotation matrices, which + are 3 x 3 real orthogonal matrices with determinant equal to +1 [1]_. + + Returns + ------- + matrix : ndarray, shape (..., 3) + Shape depends on shape of inputs used for initialization. + + Notes + ----- + This function was called as_dcm before. + + .. versionadded:: 1.4.0 + + References + ---------- + .. [1] https://en.wikipedia.org/wiki/Rotation_matrix#In_three_dimensions + + Examples + -------- + >>> from scipy.spatial.transform import Rotation as R + >>> import numpy as np + + Represent a single rotation: + + >>> r = R.from_rotvec([0, 0, np.pi/2]) + >>> r.as_matrix() + array([[ 2.22044605e-16, -1.00000000e+00, 0.00000000e+00], + [ 1.00000000e+00, 2.22044605e-16, 0.00000000e+00], + [ 0.00000000e+00, 0.00000000e+00, 1.00000000e+00]]) + >>> r.as_matrix().shape + (3, 3) + + Represent a stack with a single rotation: + + >>> r = R.from_quat([[1, 1, 0, 0]]) + >>> r.as_matrix() + array([[[ 0., 1., 0.], + [ 1., 0., 0.], + [ 0., 0., -1.]]]) + >>> r.as_matrix().shape + (1, 3, 3) + + Represent multiple rotations: + + >>> r = R.from_rotvec([[np.pi/2, 0, 0], [0, 0, np.pi/2]]) + >>> r.as_matrix() + array([[[ 1.00000000e+00, 0.00000000e+00, 0.00000000e+00], + [ 0.00000000e+00, 2.22044605e-16, -1.00000000e+00], + [ 0.00000000e+00, 1.00000000e+00, 2.22044605e-16]], + [[ 2.22044605e-16, -1.00000000e+00, 0.00000000e+00], + [ 1.00000000e+00, 2.22044605e-16, 0.00000000e+00], + [ 0.00000000e+00, 0.00000000e+00, 1.00000000e+00]]]) + >>> r.as_matrix().shape + (2, 3, 3) + + """ + matrix = self._backend.as_matrix(self._quat) + if self._single: + return matrix[0, ...] + return matrix + + def as_rotvec(self, degrees: bool = False) -> Array: + """Represent as rotation vectors. + + A rotation vector is a 3 dimensional vector which is co-directional to + the axis of rotation and whose norm gives the angle of rotation [1]_. + + Parameters + ---------- + degrees : bool, optional + Returned magnitudes are in degrees if this flag is True, else they are + in radians. Default is False. + + .. versionadded:: 1.7.0 + + Returns + ------- + rotvec : ndarray, shape (..., 3) + Shape depends on shape of inputs used for initialization. + + References + ---------- + .. [1] https://en.wikipedia.org/wiki/Axis%E2%80%93angle_representation#Rotation_vector + + Examples + -------- + >>> from scipy.spatial.transform import Rotation as R + >>> import numpy as np + + Represent a single rotation: + + >>> r = R.from_euler('z', 90, degrees=True) + >>> r.as_rotvec() + array([0. , 0. , 1.57079633]) + >>> r.as_rotvec().shape + (3,) + + Represent a rotation in degrees: + + >>> r = R.from_euler('YX', (-90, -90), degrees=True) + >>> s = r.as_rotvec(degrees=True) + >>> s + array([-69.2820323, -69.2820323, -69.2820323]) + >>> np.linalg.norm(s) + 120.00000000000001 + + Represent a stack with a single rotation: + + >>> r = R.from_quat([[0, 0, 1, 1]]) + >>> r.as_rotvec() + array([[0. , 0. , 1.57079633]]) + >>> r.as_rotvec().shape + (1, 3) + + Represent multiple rotations in a single object: + + >>> r = R.from_quat([[0, 0, 1, 1], [1, 1, 0, 1]]) + >>> r.as_rotvec() + array([[0. , 0. , 1.57079633], + [1.35102172, 1.35102172, 0. ]]) + >>> r.as_rotvec().shape + (2, 3) + + """ + rotvec = self._backend.as_rotvec(self._quat, degrees=degrees) + if self._single: + return rotvec[0, ...] + return rotvec + + def as_euler( + self, seq: str, degrees: bool = False, *, suppress_warnings: bool = False + ) -> Array: + """Represent as Euler angles. + + Any orientation can be expressed as a composition of 3 elementary + rotations. Once the axis sequence has been chosen, Euler angles define + the angle of rotation around each respective axis [1]_. + + The algorithm from [2]_ has been used to calculate Euler angles for the + rotation about a given sequence of axes. + + Euler angles suffer from the problem of gimbal lock [3]_, where the + representation loses a degree of freedom and it is not possible to + determine the first and third angles uniquely. In this case, + a warning is raised (unless the ``suppress_warnings`` option is used), + and the third angle is set to zero. Note however that the returned + angles still represent the correct rotation. + + Parameters + ---------- + seq : str, length 3 + 3 characters belonging to the set {'X', 'Y', 'Z'} for intrinsic + rotations, or {'x', 'y', 'z'} for extrinsic rotations [1]_. + Adjacent axes cannot be the same. + Extrinsic and intrinsic rotations cannot be mixed in one function + call. + degrees : bool, optional + Returned angles are in degrees if this flag is True, else they are + in radians. Default is False. + suppress_warnings : bool, optional + Disable warnings about gimbal lock. Default is False. + + Returns + ------- + angles : ndarray, shape (..., 3) + Shape depends on shape of inputs used to initialize object. + The returned angles are in the range: + + - First angle belongs to [-180, 180] degrees (both inclusive) + - Third angle belongs to [-180, 180] degrees (both inclusive) + - Second angle belongs to: + + - [-90, 90] degrees if all axes are different (like xyz) + - [0, 180] degrees if first and third axes are the same + (like zxz) + + References + ---------- + .. [1] https://en.wikipedia.org/wiki/Euler_angles#Definition_by_intrinsic_rotations + .. [2] Bernardes E, Viollet S (2022) Quaternion to Euler angles + conversion: A direct, general and computationally efficient + method. PLoS ONE 17(11): e0276302. + :doi:`10.1371/journal.pone.0276302`. + .. [3] https://en.wikipedia.org/wiki/Gimbal_lock#In_applied_mathematics + + Examples + -------- + >>> from scipy.spatial.transform import Rotation as R + >>> import numpy as np + + Represent a single rotation: + + >>> r = R.from_rotvec([0, 0, np.pi/2]) + >>> r.as_euler('zxy', degrees=True) + array([90., 0., 0.]) + >>> r.as_euler('zxy', degrees=True).shape + (3,) + + Represent a stack of single rotation: + + >>> r = R.from_rotvec([[0, 0, np.pi/2]]) + >>> r.as_euler('zxy', degrees=True) + array([[90., 0., 0.]]) + >>> r.as_euler('zxy', degrees=True).shape + (1, 3) + + Represent multiple rotations in a single object: + + >>> r = R.from_rotvec([ + ... [0, 0, np.pi/2], + ... [0, -np.pi/3, 0], + ... [np.pi/4, 0, 0]]) + >>> r.as_euler('zxy', degrees=True) + array([[ 90., 0., 0.], + [ 0., 0., -60.], + [ 0., 45., 0.]]) + >>> r.as_euler('zxy', degrees=True).shape + (3, 3) + + """ + euler = self._backend.as_euler( + self._quat, seq, degrees=degrees, suppress_warnings=suppress_warnings + ) + if self._single: + return euler[0, ...] + return euler + + def as_davenport( + self, + axes: ArrayLike, + order: str, + degrees: bool = False, + *, + suppress_warnings: bool = False, + ) -> Array: + """Represent as Davenport angles. + + Any orientation can be expressed as a composition of 3 elementary + rotations. + + For both Euler angles and Davenport angles, consecutive axes must + be are orthogonal (``axis2`` is orthogonal to both ``axis1`` and + ``axis3``). For Euler angles, there is an additional relationship + between ``axis1`` or ``axis3``, with two possibilities: + + - ``axis1`` and ``axis3`` are also orthogonal (asymmetric sequence) + - ``axis1 == axis3`` (symmetric sequence) + + For Davenport angles, this last relationship is relaxed [1]_, and only + the consecutive orthogonal axes requirement is maintained. + + A slightly modified version of the algorithm from [2]_ has been used to + calculate Davenport angles for the rotation about a given sequence of + axes. + + Davenport angles, just like Euler angles, suffer from the problem of + gimbal lock [3]_, where the representation loses a degree of freedom + and it is not possible to determine the first and third angles + uniquely. In this case, a warning is raised (unless the + ``suppress_warnings`` option is used), and the third angle is set + to zero. Note however that the returned angles still represent the + correct rotation. + + Parameters + ---------- + axes : array_like, shape (..., [1 or 2 or 3], 3) or (..., 3) + Axis of rotation, if one dimensional. If N dimensional, describes the + sequence of axes for rotations, where each axes[..., i, :] is the ith + axis. If more than one axis is given, then the second axis must be + orthogonal to both the first and third axes. + order : str + If it belongs to the set {'e', 'extrinsic'}, the sequence will be + extrinsic. If it belongs to the set {'i', 'intrinsic'}, sequence + will be treated as intrinsic. + degrees : bool, optional + Returned angles are in degrees if this flag is True, else they are + in radians. Default is False. + suppress_warnings : bool, optional + Disable warnings about gimbal lock. Default is False. + + Returns + ------- + angles : ndarray, shape (..., 3) + Shape depends on shape of inputs used to initialize object. + The returned angles are in the range: + + - First angle belongs to [-180, 180] degrees (both inclusive) + - Third angle belongs to [-180, 180] degrees (both inclusive) + - Second angle belongs to a set of size 180 degrees, + given by: ``[-abs(lambda), 180 - abs(lambda)]``, where ``lambda`` + is the angle between the first and third axes. + + References + ---------- + .. [1] Shuster, Malcolm & Markley, Landis. (2003). Generalization of + the Euler Angles. Journal of the Astronautical Sciences. 51. 123-132. + 10.1007/BF03546304. + .. [2] Bernardes E, Viollet S (2022) Quaternion to Euler angles + conversion: A direct, general and computationally efficient method. + PLoS ONE 17(11): e0276302. 10.1371/journal.pone.0276302 + .. [3] https://en.wikipedia.org/wiki/Gimbal_lock#In_applied_mathematics + + Examples + -------- + >>> from scipy.spatial.transform import Rotation as R + >>> import numpy as np + + Davenport angles are a generalization of Euler angles, when we use the + canonical basis axes: + + >>> ex = [1, 0, 0] + >>> ey = [0, 1, 0] + >>> ez = [0, 0, 1] + + Represent a single rotation: + + >>> r = R.from_rotvec([0, 0, np.pi/2]) + >>> r.as_davenport([ez, ex, ey], 'extrinsic', degrees=True) + array([90., 0., 0.]) + >>> r.as_euler('zxy', degrees=True) + array([90., 0., 0.]) + >>> r.as_davenport([ez, ex, ey], 'extrinsic', degrees=True).shape + (3,) + + Represent a stack of single rotation: + + >>> r = R.from_rotvec([[0, 0, np.pi/2]]) + >>> r.as_davenport([ez, ex, ey], 'extrinsic', degrees=True) + array([[90., 0., 0.]]) + >>> r.as_davenport([ez, ex, ey], 'extrinsic', degrees=True).shape + (1, 3) + + Represent multiple rotations in a single object: + + >>> r = R.from_rotvec([ + ... [0, 0, 90], + ... [45, 0, 0]], degrees=True) + >>> r.as_davenport([ez, ex, ey], 'extrinsic', degrees=True) + array([[90., 0., 0.], + [ 0., 45., 0.]]) + >>> r.as_davenport([ez, ex, ey], 'extrinsic', degrees=True).shape + (2, 3) + """ + axes = self._xp.asarray( + axes, dtype=self._quat.dtype, device=xp_device(self._quat) + ) + davenport = self._backend.as_davenport( + self._quat, axes, order, degrees, suppress_warnings=suppress_warnings + ) + if self._single: + return davenport[0, ...] + return davenport + + def as_mrp(self) -> Array: + """Represent as Modified Rodrigues Parameters (MRPs). + + MRPs are a 3 dimensional vector co-directional to the axis of rotation and whose + magnitude is equal to ``tan(theta / 4)``, where ``theta`` is the angle of + rotation (in radians) [1]_. + + MRPs have a singularity at 360 degrees which can be avoided by ensuring the + angle of rotation does not exceed 180 degrees, i.e. switching the direction of + the rotation when it is past 180 degrees. This function will always return MRPs + corresponding to a rotation of less than or equal to 180 degrees. + + Returns + ------- + mrps : ndarray, shape (..., 3) + Shape depends on shape of inputs used for initialization. + + Notes + ----- + + .. versionadded:: 1.6.0 + + References + ---------- + .. [1] Shuster, M. D. "A Survey of Attitude Representations", + The Journal of Astronautical Sciences, Vol. 41, No.4, 1993, + pp. 475-476 + + Examples + -------- + >>> from scipy.spatial.transform import Rotation as R + >>> import numpy as np + + Represent a single rotation: + + >>> r = R.from_rotvec([0, 0, np.pi]) + >>> r.as_mrp() + array([0. , 0. , 1. ]) + >>> r.as_mrp().shape + (3,) + + Represent a stack with a single rotation: + + >>> r = R.from_euler('xyz', [[180, 0, 0]], degrees=True) + >>> r.as_mrp() + array([[1. , 0. , 0. ]]) + >>> r.as_mrp().shape + (1, 3) + + Represent multiple rotations: + + >>> r = R.from_rotvec([[np.pi/2, 0, 0], [0, 0, np.pi/2]]) + >>> r.as_mrp() + array([[0.41421356, 0. , 0. ], + [0. , 0. , 0.41421356]]) + >>> r.as_mrp().shape + (2, 3) + + """ + mrp = self._backend.as_mrp(self._quat) + if self._single: + return mrp[0, ...] + return mrp + + @staticmethod + def concatenate(rotations: Rotation | Sequence[Rotation]) -> Rotation: + """Concatenate a sequence of `Rotation` objects into a single object. + + This is useful if you want to, for example, take the mean of a set of + rotations and need to pack them into a single object to do so. + + Parameters + ---------- + rotations : sequence of `Rotation` objects + The rotations to concatenate. If a single `Rotation` object is + passed in, a copy is returned. + + Returns + ------- + concatenated : `Rotation` instance + The concatenated rotations. + + Notes + ----- + .. versionadded:: 1.8.0 + + Examples + -------- + >>> from scipy.spatial.transform import Rotation as R + >>> r1 = R.from_rotvec([0, 0, 1]) + >>> r2 = R.from_rotvec([0, 0, 2]) + >>> rc = R.concatenate([r1, r2]) + >>> rc.as_rotvec() + array([[0., 0., 1.], + [0., 0., 2.]]) + >>> rc.mean().as_rotvec() + array([0., 0., 1.5]) + + Concatenation of a split rotation recovers the original object. + + >>> rs = [r for r in rc] + >>> R.concatenate(rs).as_rotvec() + array([[0., 0., 1.], + [0., 0., 2.]]) + + Note that it may be simpler to create the desired rotations by passing + in a single list of the data during initialization, rather then by + concatenating: + + >>> R.from_rotvec([[0, 0, 1], [0, 0, 2]]).as_rotvec() + array([[0., 0., 1.], + [0., 0., 2.]]) + + """ + if isinstance(rotations, Rotation): + return Rotation(rotations.as_quat(), normalize=False, copy=True) + if not all(isinstance(x, Rotation) for x in rotations): + raise TypeError("input must contain Rotation objects only") + + xp = array_namespace(rotations[0].as_quat()) + quats = xp.concat( + [xpx.atleast_nd(x.as_quat(), ndim=2, xp=xp) for x in rotations] + ) + return Rotation._from_raw_quat(quats, xp=xp) + + def apply(self, vectors: ArrayLike, inverse: bool = False) -> Array: + """Apply this rotation to a set of vectors. + + If the original frame rotates to the final frame by this rotation, then + its application to a vector can be seen in two ways: + + - As a projection of vector components expressed in the final frame + to the original frame. + - As the physical rotation of a vector being glued to the original + frame as it rotates. In this case the vector components are + expressed in the original frame before and after the rotation. + + In terms of rotation matrices, this application is the same as + ``(self.as_matrix() @ vectors[..., np.newaxis])[..., 0]``. + For a single rotation, this is the same as + ``vectors @ self.as_matrix().T``. + + Parameters + ---------- + vectors : array_like, shape (..., 3) + Each `vectors[..., :]` represents a vector in 3D space. The shape of + rotations and shape of vectors given must follow standard numpy + broadcasting rules: either one of them equals unity or they both + equal each other. + inverse : bool, optional + If True then the inverse of the rotation(s) is applied to the input + vectors. Default is False. + + Returns + ------- + rotated_vectors : ndarray, shape (..., 3) + Result of applying rotation on input vectors. + Shape is determined according to numpy broadcasting rules. I.e., the result + will have the shape `np.broadcast_shapes(r.shape, v.shape[:-1]) + (3,)` + + Examples + -------- + >>> from scipy.spatial.transform import Rotation as R + >>> import numpy as np + + Single rotation applied on a single vector: + + >>> vector = np.array([1, 0, 0]) + >>> r = R.from_rotvec([0, 0, np.pi/2]) + >>> r.as_matrix() + array([[ 2.22044605e-16, -1.00000000e+00, 0.00000000e+00], + [ 1.00000000e+00, 2.22044605e-16, 0.00000000e+00], + [ 0.00000000e+00, 0.00000000e+00, 1.00000000e+00]]) + >>> r.apply(vector) + array([2.22044605e-16, 1.00000000e+00, 0.00000000e+00]) + >>> r.apply(vector).shape + (3,) + + Single rotation applied on multiple vectors: + + >>> vectors = np.array([ + ... [1, 0, 0], + ... [1, 2, 3]]) + >>> r = R.from_rotvec([0, 0, np.pi/4]) + >>> r.as_matrix() + array([[ 0.70710678, -0.70710678, 0. ], + [ 0.70710678, 0.70710678, 0. ], + [ 0. , 0. , 1. ]]) + >>> r.apply(vectors) + array([[ 0.70710678, 0.70710678, 0. ], + [-0.70710678, 2.12132034, 3. ]]) + >>> r.apply(vectors).shape + (2, 3) + + Multiple rotations on a single vector: + + >>> r = R.from_rotvec([[0, 0, np.pi/4], [np.pi/2, 0, 0]]) + >>> vector = np.array([1,2,3]) + >>> r.as_matrix() + array([[[ 7.07106781e-01, -7.07106781e-01, 0.00000000e+00], + [ 7.07106781e-01, 7.07106781e-01, 0.00000000e+00], + [ 0.00000000e+00, 0.00000000e+00, 1.00000000e+00]], + [[ 1.00000000e+00, 0.00000000e+00, 0.00000000e+00], + [ 0.00000000e+00, 2.22044605e-16, -1.00000000e+00], + [ 0.00000000e+00, 1.00000000e+00, 2.22044605e-16]]]) + >>> r.apply(vector) + array([[-0.70710678, 2.12132034, 3. ], + [ 1. , -3. , 2. ]]) + >>> r.apply(vector).shape + (2, 3) + + Multiple rotations on multiple vectors. Each rotation is applied on the + corresponding vector: + + >>> r = R.from_euler('zxy', [ + ... [0, 0, 90], + ... [45, 30, 60]], degrees=True) + >>> vectors = [ + ... [1, 2, 3], + ... [1, 0, -1]] + >>> r.apply(vectors) + array([[ 3. , 2. , -1. ], + [-0.09026039, 1.11237244, -0.86860844]]) + >>> r.apply(vectors).shape + (2, 3) + + Broadcasting rules apply: + + >>> r = R.from_rotvec(np.tile([0, 0, np.pi/4], (5, 1, 4, 1))) + >>> vectors = np.ones((3, 4, 3)) + >>> r.shape, vectors.shape + ((5, 1, 4), (3, 4, 3)) + >>> r.apply(vectors).shape + (5, 3, 4, 3) + + It is also possible to apply the inverse rotation: + + >>> r = R.from_euler('zxy', [ + ... [0, 0, 90], + ... [45, 30, 60]], degrees=True) + >>> vectors = [ + ... [1, 2, 3], + ... [1, 0, -1]] + >>> r.apply(vectors, inverse=True) + array([[-3. , 2. , 1. ], + [ 1.09533535, -0.8365163 , 0.3169873 ]]) + + """ + vectors = self._xp.asarray( + vectors, device=xp_device(self._quat), dtype=self._quat.dtype + ) + single_vector = vectors.ndim == 1 + # Numpy optimization: The Cython backend typing requires us to have fixed + # dimensions, so for the Numpy case we always broadcast the vector to 2D. + if vectors.shape[-1] != 3: + raise ValueError(f"Expected input of shape (..., 3), got {vectors.shape}.") + if is_numpy(self._xp): + vectors = xpx.atleast_nd(vectors, ndim=2, xp=self._xp) + cython_compatible = self._quat.ndim < 3 and vectors.ndim < 3 + backend = select_backend(self._xp, cython_compatible=cython_compatible) + result = backend.apply(self._quat, vectors, inverse=inverse) + if self._single and single_vector: + return result[0, ...] + return result + + def __mul__(self, other: Rotation) -> Rotation | NotImplementedType: + """Compose this rotation with the other. + + If `p` and `q` are two rotations, then the composition of 'q followed + by p' is equivalent to `p * q`. In terms of rotation matrices, + the composition can be expressed as + ``p.as_matrix() @ q.as_matrix()``. + + Parameters + ---------- + other : `Rotation` instance + Object containing the rotations to be composed with this one. Note + that rotation compositions are not commutative, so ``p * q`` is + generally different from ``q * p``. + + Returns + ------- + composition : `Rotation` instance + This function supports composition of multiple rotations at a time. + Composition follows standard numpy broadcasting rules. The resulting + `Rotation` object will have the shape + `np.broadcast_shapes(p.shape, q.shape)`. In dimensions with size > 1, + rotations are composed with matching indices. In dimensions with only + one rotation, the single rotation is composed with each rotation in the + other object. + + Examples + -------- + >>> from scipy.spatial.transform import Rotation as R + >>> import numpy as np + + Composition of two single rotations: + + >>> p = R.from_quat([0, 0, 1, 1]) + >>> q = R.from_quat([1, 0, 0, 1]) + >>> p.as_matrix() + array([[ 0., -1., 0.], + [ 1., 0., 0.], + [ 0., 0., 1.]]) + >>> q.as_matrix() + array([[ 1., 0., 0.], + [ 0., 0., -1.], + [ 0., 1., 0.]]) + >>> r = p * q + >>> r.as_matrix() + array([[0., 0., 1.], + [1., 0., 0.], + [0., 1., 0.]]) + + Composition of two objects containing equal number of rotations: + + >>> p = R.from_quat([[0, 0, 1, 1], [1, 0, 0, 1]]) + >>> q = R.from_rotvec([[np.pi/4, 0, 0], [-np.pi/4, 0, np.pi/4]]) + >>> p.as_quat() + array([[0. , 0. , 0.70710678, 0.70710678], + [0.70710678, 0. , 0. , 0.70710678]]) + >>> q.as_quat() + array([[ 0.38268343, 0. , 0. , 0.92387953], + [-0.37282173, 0. , 0.37282173, 0.84971049]]) + >>> r = p * q + >>> r.as_quat() + array([[ 0.27059805, 0.27059805, 0.65328148, 0.65328148], + [ 0.33721128, -0.26362477, 0.26362477, 0.86446082]]) + + Broadcasting rules apply: + >>> p = R.from_quat(np.tile(np.array([0, 0, 1, 1]), (5, 1, 1))) + >>> q = R.from_quat(np.tile(np.array([1, 0, 0, 1]), (1, 6, 1))) + >>> p.shape, q.shape + ((5, 1), (1, 6)) + >>> r = p * q + >>> r.shape + (5, 6) + """ + # Check that other is a Rotation object. We want to return NotImplemented + # instead of raising an error to allow other types to implement __rmul__. + # Python will then automatically try to delegate the multiplication to the + # other type. + # See https://github.com/scipy/scipy/issues/21541 + if not isinstance(other, Rotation): + return NotImplemented + if not broadcastable(self._quat.shape, other._quat.shape): + raise ValueError( + f"Cannot broadcast {self._quat.shape[:-1]} rotations in " + f"first to {other._quat.shape[:-1]} rotations in second object." + ) + cython_compatible = self._quat.ndim < 3 and other._quat.ndim < 3 + backend = select_backend(self._xp, cython_compatible=cython_compatible) + quat = backend.compose_quat(self._quat, other._quat) + if self._single and other._single: + quat = quat[0] + return Rotation(quat, normalize=True, copy=False) + + def __pow__(self, n: float | Array, modulus: None = None) -> Rotation: + """Compose this rotation with itself `n` times. + + Composition of a rotation ``p`` with itself can be extended to + non-integer ``n`` by considering the power ``n`` to be a scale factor + applied to the angle of rotation about the rotation's fixed axis. The + expression ``q = p ** n`` can also be expressed as + ``q = Rotation.from_rotvec(n * p.as_rotvec())``. + + If ``n`` is negative, then the rotation is inverted before the power + is applied. In other words, ``p ** -abs(n) == p.inv() ** abs(n)``. + + Parameters + ---------- + n : float | Array + The number of times to compose the rotation with itself. If `n` is + an array, then it must be 0d or 1d with shape (1,). + modulus : None + This overridden argument is not applicable to Rotations and must be + ``None``. + + Returns + ------- + power : `Rotation` instance + The resulting rotation will be of the same shape as the original rotation + object. Each element of the output is the corresponding element of the + input rotation raised to the power of ``n``. + + Notes + ----- + For example, a power of 2 will double the angle of rotation, and a + power of 0.5 will halve the angle. There are three notable cases: if + ``n == 1`` then the original rotation is returned, if ``n == 0`` + then the identity rotation is returned, and if ``n == -1`` then + ``p.inv()`` is returned. + + Note that fractional powers ``n`` which effectively take a root of + rotation, do so using the shortest path smallest representation of that + angle (the principal root). This means that powers of ``n`` and ``1/n`` + are not necessarily inverses of each other. For example, a 0.5 power of + a +240 degree rotation will be calculated as the 0.5 power of a -120 + degree rotation, with the result being a rotation of -60 rather than + +120 degrees. + + Examples + -------- + >>> from scipy.spatial.transform import Rotation as R + + Raising a rotation to a power: + + >>> p = R.from_rotvec([1, 0, 0]) + >>> q = p ** 2 + >>> q.as_rotvec() + array([2., 0., 0.]) + >>> r = p ** 0.5 + >>> r.as_rotvec() + array([0.5, 0., 0.]) + + Inverse powers do not necessarily cancel out: + + >>> p = R.from_rotvec([0, 0, 120], degrees=True) + >>> ((p ** 2) ** 0.5).as_rotvec(degrees=True) + array([ -0., -0., -60.]) + + """ + if modulus is not None: + raise NotImplementedError("modulus not supported") + quat = self._backend.pow(self._quat, n) + if self._single: + quat = quat[0] + return Rotation._from_raw_quat(quat, xp=self._xp, backend=self._backend) + + def inv(self) -> Rotation: + """Invert this rotation. + + Composition of a rotation with its inverse results in an identity + transformation. + + Returns + ------- + inverse : `Rotation` instance + Object containing inverse of the rotations in the current instance. + + Examples + -------- + >>> from scipy.spatial.transform import Rotation as R + >>> import numpy as np + + Inverting a single rotation: + + >>> p = R.from_euler('z', 45, degrees=True) + >>> q = p.inv() + >>> q.as_euler('zyx', degrees=True) + array([-45., 0., 0.]) + + Inverting multiple rotations: + + >>> p = R.from_rotvec([[0, 0, np.pi/3], [-np.pi/4, 0, 0]]) + >>> q = p.inv() + >>> q.as_rotvec() + array([[-0. , -0. , -1.04719755], + [ 0.78539816, -0. , -0. ]]) + + """ + q_inv = self._backend.inv(self._quat) + if self._single: + q_inv = q_inv[0, ...] + return Rotation._from_raw_quat(q_inv, xp=self._xp, backend=self._backend) + + def magnitude(self) -> Array: + """Get the magnitude(s) of the rotation(s). + + Returns + ------- + magnitude : ndarray or float + Angle(s) in radians, float if object contains a single rotation + and ndarray if object contains ND rotations. The magnitude + will always be in the range [0, pi]. + + Examples + -------- + >>> from scipy.spatial.transform import Rotation as R + >>> import numpy as np + >>> r = R.from_quat(np.eye(4)) + >>> r.as_quat() + array([[ 1., 0., 0., 0.], + [ 0., 1., 0., 0.], + [ 0., 0., 1., 0.], + [ 0., 0., 0., 1.]]) + >>> r.magnitude() + array([3.14159265, 3.14159265, 3.14159265, 0. ]) + + Magnitude of a single rotation: + + >>> r[0].magnitude() + 3.141592653589793 + """ + magnitude = self._backend.magnitude(self._quat) + if self._single: + # Special handling for numpy and single rotations. self._single is only set + # if xp is numpy. We therefore know that magnitude is a numpy array and + # that it contains a single element. Previously this code returned a Python + # float in that case. Here we return a numpy float64 scalar. All other + # Array API libraries return 0d arrays instead. + # See https://github.com/scipy/scipy/pull/23198#issuecomment-3003757848 + return magnitude[0] + return magnitude + + def approx_equal( + self, other: Rotation, atol: float | None = None, degrees: bool = False + ) -> Array | np.bool: + """Determine if another rotation is approximately equal to this one. + + Equality is measured by calculating the smallest angle between the + rotations, and checking to see if it is smaller than `atol`. + + Parameters + ---------- + other : `Rotation` instance + Object containing the rotations to measure against this one. + atol : float, optional + The absolute angular tolerance, below which the rotations are + considered equal. If not given, then set to 1e-8 radians by + default. + degrees : bool, optional + If True and `atol` is given, then `atol` is measured in degrees. If + False (default), then atol is measured in radians. + + Returns + ------- + approx_equal : Array or `numpy.bool` + Whether the rotations are approximately equal, `numpy.bool` if object + contains a single numpy rotation and Array if object contains multiple + rotations or is from another library. + + Examples + -------- + >>> from scipy.spatial.transform import Rotation as R + >>> import numpy as np + >>> p = R.from_quat([0, 0, 0, 1]) + >>> q = R.from_quat(np.eye(4)) + >>> p.approx_equal(q) + array([False, False, False, True]) + + Approximate equality for a single rotation: + + >>> p.approx_equal(q[0]) + np.False_ + """ + cython_compatible = self._quat.ndim < 3 and other._quat.ndim < 3 + backend = select_backend(self._xp, cython_compatible=cython_compatible) + approx_equal = backend.approx_equal( + self._quat, other._quat, atol=atol, degrees=degrees + ) + if self._single and other._single: + return approx_equal[0] + return approx_equal + + def mean( + self, + weights: ArrayLike | None = None, + axis: None | int | tuple[int, ...] = None, + ) -> Rotation: + """Get the mean of the rotations. + + The mean used is the chordal L2 mean (also called the projected or + induced arithmetic mean) [1]_. If ``A`` is a set of rotation matrices, + then the mean ``M`` is the rotation matrix that minimizes the + following loss function: + + .. math:: + + L(M) = \\sum_{i = 1}^{n} w_i \\lVert \\mathbf{A}_i - + \\mathbf{M} \\rVert^2 , + + where :math:`w_i`'s are the `weights` corresponding to each matrix. + + Parameters + ---------- + weights : array_like shape (..., N), optional + Weights describing the relative importance of the rotations. If + None (default), then all values in `weights` are assumed to be + equal. If given, the shape of `weights` must be broadcastable to + the rotation shape. Weights must be non-negative. + axis : None, int, or tuple of ints, optional + Axis or axes along which the means are computed. The default is to + compute the mean of all rotations. + + Returns + ------- + mean : `Rotation` instance + Single rotation containing the mean of the rotations in the + current instance. + + References + ---------- + .. [1] Hartley, Richard, et al., + "Rotation Averaging", International Journal of Computer Vision + 103, 2013, pp. 267-305. + + Examples + -------- + >>> from scipy.spatial.transform import Rotation as R + >>> r = R.from_euler('zyx', [[0, 0, 0], + ... [1, 0, 0], + ... [0, 1, 0], + ... [0, 0, 1]], degrees=True) + >>> r.mean().as_euler('zyx', degrees=True) + array([0.24945696, 0.25054542, 0.24945696]) + """ + mean = self._backend.mean(self._quat, weights=weights, axis=axis) + return Rotation._from_raw_quat(mean, xp=self._xp, backend=self._backend) + + def reduce( + self, + left: Rotation | None = None, + right: Rotation | None = None, + return_indices: bool = False, + ) -> Rotation | tuple[Rotation, Array, Array]: + """Reduce this rotation with the provided rotation groups. + + Reduction of a rotation ``p`` is a transformation of the form + ``q = l * p * r``, where ``l`` and ``r`` are chosen from `left` and + `right` respectively, such that rotation ``q`` has the smallest + magnitude. + + If `left` and `right` are rotation groups representing symmetries of + two objects rotated by ``p``, then ``q`` is the rotation of the + smallest magnitude to align these objects considering their symmetries. + + Parameters + ---------- + left : `Rotation` instance, optional + Object containing the left rotation(s). Default value (None) + corresponds to the identity rotation. + right : `Rotation` instance, optional + Object containing the right rotation(s). Default value (None) + corresponds to the identity rotation. + return_indices : bool, optional + Whether to return the indices of the rotations from `left` and + `right` used for reduction. + + Returns + ------- + reduced : `Rotation` instance + Object containing reduced rotations. + left_best, right_best: int ndarray + Indices of elements from `left` and `right` used for reduction. + """ + left = left.as_quat() if left is not None else None + right = right.as_quat() if right is not None else None + reduced, left_idx, right_idx = self._backend.reduce( + self._quat, left=left, right=right + ) + if self._single: + reduced = reduced[0, ...] + rot = Rotation._from_raw_quat(reduced, xp=self._xp, backend=self._backend) + if return_indices: + left_idx = left_idx if left is not None else None + right_idx = right_idx if right is not None else None + return rot, left_idx, right_idx + return rot + + @classmethod + def create_group(cls, group: str, axis: str = "Z") -> Rotation: + """Create a 3D rotation group. + + Parameters + ---------- + group : str + The name of the group. Must be one of 'I', 'O', 'T', 'Dn', 'Cn', + where `n` is a positive integer. The groups are: + + * I: Icosahedral group + * O: Octahedral group + * T: Tetrahedral group + * D: Dicyclic group + * C: Cyclic group + + axis : int + The cyclic rotation axis. Must be one of ['X', 'Y', 'Z'] (or + lowercase). Default is 'Z'. Ignored for groups 'I', 'O', and 'T'. + + Returns + ------- + rotation : `Rotation` instance + Object containing the elements of the rotation group. + + Notes + ----- + This method generates rotation groups only. The full 3-dimensional + point groups [PointGroups]_ also contain reflections. + + References + ---------- + .. [PointGroups] `Point groups + `_ + on Wikipedia. + """ + # TODO: We defer the implementation of groups for arbitrary Array API frameworks + # to the follow-up PR that adds general Array API support for Rotations. + return create_group(cls, group, axis=axis) + + def __getitem__( + self, indexer: int | slice | EllipsisType | Array | None + ) -> Rotation: + """Extract rotation(s) at given index(es) from object. + + Create a new `Rotation` instance containing a subset of rotations + stored in this object. + + Parameters + ---------- + indexer : index, slice, or index array + Specifies which rotation(s) to extract. A single indexer must be + specified, i.e. as if indexing a 1 dimensional array or list. + + Returns + ------- + rotation : `Rotation` instance + Contains + - a single rotation, if `indexer` is a single index + - a stack of rotation(s), if `indexer` is a slice, or and index + array. + + Raises + ------ + TypeError if the instance was created as a single rotation. + + Examples + -------- + >>> from scipy.spatial.transform import Rotation as R + >>> rs = R.from_quat([ + ... [1, 1, 0, 0], + ... [0, 1, 0, 1], + ... [1, 1, -1, 0]]) # These quats are normalized + >>> rs.as_quat() + array([[ 0.70710678, 0.70710678, 0. , 0. ], + [ 0. , 0.70710678, 0. , 0.70710678], + [ 0.57735027, 0.57735027, -0.57735027, 0. ]]) + + Indexing using a single index: + + >>> a = rs[0] + >>> a.as_quat() + array([0.70710678, 0.70710678, 0. , 0. ]) + + Array slicing: + + >>> b = rs[1:3] + >>> b.as_quat() + array([[ 0. , 0.70710678, 0. , 0.70710678], + [ 0.57735027, 0.57735027, -0.57735027, 0. ]]) + + List comprehension to split each rotation into its own object: + + >>> c = [r for r in rs] + >>> print([r.as_quat() for r in c]) + [array([ 0.70710678, 0.70710678, 0. , 0. ]), + array([ 0. , 0.70710678, 0. , 0.70710678]), + array([ 0.57735027, 0.57735027, -0.57735027, 0. ])] + + Concatenation of split rotations will recover the original object: + + >>> R.concatenate([a, b]).as_quat() + array([[ 0.70710678, 0.70710678, 0. , 0. ], + [ 0. , 0.70710678, 0. , 0.70710678], + [ 0.57735027, 0.57735027, -0.57735027, 0. ]]) + """ + if self._single or self._quat.ndim == 1: + raise TypeError("Single rotation is not subscriptable.") + is_array = isinstance(indexer, type(self._quat)) + # Masking is only specified in the Array API when the array is the sole index + # TODO: This special case handling is mainly a result of Array API limitations. + # Ideally we would get rid of them altogether and converge to [indexer, ...] + # indexing. + if is_array and indexer.dtype == self._xp.bool: # type:ignore[union-attr] + return Rotation(self._quat[indexer], normalize=False) + if is_array and self._xp.isdtype(indexer.dtype, "integral"): # type:ignore[union-attr] + # xp.take is implementation-defined for zero-dim arrays, hence we raise + # pre-emptively to have consistent behavior across frameworks. + if self._quat.shape[0] == 0: + raise IndexError("cannot do a non-empty take from an empty axes.") + return Rotation(self._xp.take(self._quat, indexer, axis=0), normalize=False) + return Rotation(self._quat[indexer, ...], normalize=False) + + def __setitem__(self, indexer: int | slice | EllipsisType | None, value: Rotation): + """Set rotation(s) at given index(es) from object. + + Parameters + ---------- + indexer : index, slice, or index array + Specifies which rotation(s) to replace. A single indexer must be + specified, i.e. as if indexing a 1 dimensional array or list. + + value : `Rotation` instance + The rotations to set. + + Raises + ------ + TypeError if the instance was created as a single rotation. + + Notes + ----- + + .. versionadded:: 1.8.0 + """ + if self._single or self._quat.ndim == 1: + raise TypeError("Single rotation is not subscriptable.") + + if not isinstance(value, Rotation): + raise TypeError("value must be a Rotation object") + + self._quat = self._backend.setitem(self._quat, value.as_quat(), indexer) + + @staticmethod + def identity( + num: int | None = None, *, shape: int | tuple[int, ...] | None = None + ) -> Rotation: + """Get identity rotation(s). + + Composition with the identity rotation has no effect. + + Parameters + ---------- + num : int or None, optional + Number of identity rotations to generate. If None (default), then a + single rotation is generated. + shape : int or tuple of ints, optional + Shape of identity rotations to generate. If specified, `num` must + be None. + + Returns + ------- + identity : Rotation object + The identity rotation. + """ + # TODO: We should move to one single way of specifying the output shape and + # deprecate `num`. + if num is not None and shape is not None: + raise ValueError("Only one of `num` or `shape` can be specified.") + quat = cython_backend.identity(num, shape=shape) + return Rotation._from_raw_quat(quat, xp=array_namespace(quat)) + + @staticmethod + @_transition_to_rng("random_state", position_num=2) + def random( + num: int | None = None, + rng: np.random.Generator | None = None, + *, + shape: tuple[int, ...] | None = None, + ) -> Rotation: + r"""Generate rotations that are uniformly distributed on a sphere. + + Formally, the rotations follow the Haar-uniform distribution over the SO(3) + group. + + Parameters + ---------- + num : int or None, optional + Number of random rotations to generate. If None (default), then a + single rotation is generated. + rng : `numpy.random.Generator`, optional + Pseudorandom number generator state. When `rng` is None, a new + `numpy.random.Generator` is created using entropy from the + operating system. Types other than `numpy.random.Generator` are + passed to `numpy.random.default_rng` to instantiate a `Generator`. + shape : tuple of ints, optional + Shape of random rotations to generate. If specified, `num` must be None. + + Returns + ------- + random_rotation : `Rotation` instance + Contains a single rotation if `num` is None. Otherwise contains a + stack of `num` rotations. + + Notes + ----- + This function is optimized for efficiently sampling random rotation + matrices in three dimensions. For generating random rotation matrices + in higher dimensions, see `scipy.stats.special_ortho_group`. + + Examples + -------- + >>> from scipy.spatial.transform import Rotation as R + + Sample a single rotation: + + >>> R.random().as_euler('zxy', degrees=True) + array([-110.5976185 , 55.32758512, 76.3289269 ]) # random + + Sample a stack of rotations: + + >>> R.random(5).as_euler('zxy', degrees=True) + array([[-110.5976185 , 55.32758512, 76.3289269 ], # random + [ -91.59132005, -14.3629884 , -93.91933182], + [ 25.23835501, 45.02035145, -121.67867086], + [ -51.51414184, -15.29022692, -172.46870023], + [ -81.63376847, -27.39521579, 2.60408416]]) + + See Also + -------- + scipy.stats.special_ortho_group + + """ + # TODO: We should move to one single way of specifying the output shape and + # deprecate `num`. + if num is not None and shape is not None: + raise ValueError("Only one of `num` or `shape` can be specified.") + sample = cython_backend.random(num, rng, shape=shape) + return Rotation(sample, normalize=True, copy=False) + + @staticmethod + def align_vectors( + a: ArrayLike, + b: ArrayLike, + weights: ArrayLike | None = None, + return_sensitivity: bool = False, + ) -> tuple[Rotation, float] | tuple[Rotation, float, Array]: + """Estimate a rotation to optimally align two sets of vectors. + + Find a rotation between frames A and B which best aligns a set of + vectors `a` and `b` observed in these frames. The following loss + function is minimized to solve for the rotation matrix + :math:`C`: + + .. math:: + + L(C) = \\frac{1}{2} \\sum_{i = 1}^{n} w_i \\lVert \\mathbf{a}_i - + C \\mathbf{b}_i \\rVert^2 , + + where :math:`w_i`'s are the `weights` corresponding to each vector. + + The rotation is estimated with Kabsch algorithm [1]_, and solves what + is known as the "pointing problem", or "Wahba's problem" [2]_. + + Note that the length of each vector in this formulation acts as an + implicit weight. So for use cases where all vectors need to be + weighted equally, you should normalize them to unit length prior to + calling this method. + + There are two special cases. The first is if a single vector is given + for `a` and `b`, in which the shortest distance rotation that aligns + `b` to `a` is returned. + + The second is when one of the weights is infinity. In this case, the + shortest distance rotation between the primary infinite weight vectors + is calculated as above. Then, the rotation about the aligned primary + vectors is calculated such that the secondary vectors are optimally + aligned per the above loss function. The result is the composition + of these two rotations. The result via this process is the same as the + Kabsch algorithm as the corresponding weight approaches infinity in + the limit. For a single secondary vector this is known as the + "align-constrain" algorithm [3]_. + + For both special cases (single vectors or an infinite weight), the + sensitivity matrix does not have physical meaning and an error will be + raised if it is requested. For an infinite weight, the primary vectors + act as a constraint with perfect alignment, so their contribution to + `rssd` will be forced to 0 even if they are of different lengths. + + Parameters + ---------- + a : array_like, shape (3,) or (N, 3) + Vector components observed in initial frame A. Each row of `a` + denotes a vector. + b : array_like, shape (3,) or (N, 3) + Vector components observed in another frame B. Each row of `b` + denotes a vector. + weights : array_like shape (N,), optional + Weights describing the relative importance of the vector + observations. If None (default), then all values in `weights` are + assumed to be 1. One and only one weight may be infinity, and + weights must be positive. + return_sensitivity : bool, optional + Whether to return the sensitivity matrix. See Notes for details. + Default is False. + + Returns + ------- + rotation : `Rotation` instance + Best estimate of the rotation that transforms `b` to `a`. + rssd : float + Stands for "root sum squared distance". Square root of the weighted + sum of the squared distances between the given sets of vectors + after alignment. It is equal to ``sqrt(2 * minimum_loss)``, where + ``minimum_loss`` is the loss function evaluated for the found + optimal rotation. + Note that the result will also be weighted by the vectors' + magnitudes, so perfectly aligned vector pairs will have nonzero + `rssd` if they are not of the same length. This can be avoided by + normalizing them to unit length prior to calling this method, + though note that doing this will change the resulting rotation. + sensitivity_matrix : ndarray, shape (3, 3) + Sensitivity matrix of the estimated rotation estimate as explained + in Notes. Returned only when `return_sensitivity` is True. Not + valid if aligning a single pair of vectors or if there is an + infinite weight, in which cases an error will be raised. + + Notes + ----- + The sensitivity matrix gives the sensitivity of the estimated rotation + to small perturbations of the vector measurements. Specifically we + consider the rotation estimate error as a small rotation vector of + frame A. The sensitivity matrix is proportional to the covariance of + this rotation vector assuming that the vectors in `a` was measured with + errors significantly less than their lengths. To get the true + covariance matrix, the returned sensitivity matrix must be multiplied + by harmonic mean [4]_ of variance in each observation. Note that + `weights` are supposed to be inversely proportional to the observation + variances to get consistent results. For example, if all vectors are + measured with the same accuracy of 0.01 (`weights` must be all equal), + then you should multiple the sensitivity matrix by 0.01**2 to get the + covariance. + + Refer to [5]_ for more rigorous discussion of the covariance + estimation. See [6]_ for more discussion of the pointing problem and + minimal proper pointing. + + This function does not support broadcasting or ND arrays with N > 2. + + References + ---------- + .. [1] https://en.wikipedia.org/wiki/Kabsch_algorithm + .. [2] https://en.wikipedia.org/wiki/Wahba%27s_problem + .. [3] Magner, Robert, + "Extending target tracking capabilities through trajectory and + momentum setpoint optimization." Small Satellite Conference, + 2018. + .. [4] https://en.wikipedia.org/wiki/Harmonic_mean + .. [5] F. Landis Markley, + "Attitude determination using vector observations: a fast + optimal matrix algorithm", Journal of Astronautical Sciences, + Vol. 41, No.2, 1993, pp. 261-280. + .. [6] Bar-Itzhack, Itzhack Y., Daniel Hershkowitz, and Leiba Rodman, + "Pointing in Real Euclidean Space", Journal of Guidance, + Control, and Dynamics, Vol. 20, No. 5, 1997, pp. 916-922. + + Examples + -------- + >>> import numpy as np + >>> from scipy.spatial.transform import Rotation as R + + Here we run the baseline Kabsch algorithm to best align two sets of + vectors, where there is noise on the last two vector measurements of + the ``b`` set: + + >>> a = [[0, 1, 0], [0, 1, 1], [0, 1, 1]] + >>> b = [[1, 0, 0], [1, 1.1, 0], [1, 0.9, 0]] + >>> rot, rssd, sens = R.align_vectors(a, b, return_sensitivity=True) + >>> rot.as_matrix() + array([[0., 0., 1.], + [1., 0., 0.], + [0., 1., 0.]]) + + When we apply the rotation to ``b``, we get vectors close to ``a``: + + >>> rot.apply(b) + array([[0. , 1. , 0. ], + [0. , 1. , 1.1], + [0. , 1. , 0.9]]) + + The error for the first vector is 0, and for the last two the error is + magnitude 0.1. The `rssd` is the square root of the sum of the + weighted squared errors, and the default weights are all 1, so in this + case the `rssd` is calculated as + ``sqrt(1 * 0**2 + 1 * 0.1**2 + 1 * (-0.1)**2) = 0.141421356237308`` + + >>> a - rot.apply(b) + array([[ 0., 0., 0. ], + [ 0., 0., -0.1], + [ 0., 0., 0.1]]) + >>> np.sqrt(np.sum(np.ones(3) @ (a - rot.apply(b))**2)) + 0.141421356237308 + >>> rssd + 0.141421356237308 + + The sensitivity matrix for this example is as follows: + + >>> sens + array([[0.2, 0. , 0.], + [0. , 1.5, 1.], + [0. , 1. , 1.]]) + + Special case 1: Find a minimum rotation between single vectors: + + >>> a = [1, 0, 0] + >>> b = [0, 1, 0] + >>> rot, _ = R.align_vectors(a, b) + >>> rot.as_matrix() + array([[0., 1., 0.], + [-1., 0., 0.], + [0., 0., 1.]]) + >>> rot.apply(b) + array([1., 0., 0.]) + + Special case 2: One infinite weight. Here we find a rotation between + primary and secondary vectors that can align exactly: + + >>> a = [[0, 1, 0], [0, 1, 1]] + >>> b = [[1, 0, 0], [1, 1, 0]] + >>> rot, _ = R.align_vectors(a, b, weights=[np.inf, 1]) + >>> rot.as_matrix() + array([[0., 0., 1.], + [1., 0., 0.], + [0., 1., 0.]]) + >>> rot.apply(b) + array([[0., 1., 0.], + [0., 1., 1.]]) + + Here the secondary vectors must be best-fit: + + >>> a = [[0, 1, 0], [0, 1, 1]] + >>> b = [[1, 0, 0], [1, 2, 0]] + >>> rot, _ = R.align_vectors(a, b, weights=[np.inf, 1]) + >>> rot.as_matrix() + array([[0., 0., 1.], + [1., 0., 0.], + [0., 1., 0.]]) + >>> rot.apply(b) + array([[0., 1., 0.], + [0., 1., 2.]]) + """ + xp = array_namespace(a) + a, b, weights = _promote(a, b, weights, xp=xp) + cython_compatible = ( + (a.ndim < 3) & (b.ndim < 3) & (weights is None or weights.ndim < 2) # type:ignore[union-attr] + ) + backend = select_backend(xp, cython_compatible=cython_compatible) + q, rssd, sensitivity = backend.align_vectors(a, b, weights, return_sensitivity) + if return_sensitivity: + return Rotation._from_raw_quat(q, xp=xp, backend=backend), rssd, sensitivity + return Rotation._from_raw_quat(q, xp=xp, backend=backend), rssd + + def __getstate__(self) -> tuple[Array, bool]: + return (self._quat, self._single) + + def __setstate__(self, state: tuple[Array, bool]): + quat, single = state + xp = array_namespace(quat) + self._xp = xp + self._quat = xp.asarray(quat, copy=True) + self._backend = select_backend(xp, cython_compatible=self._quat.ndim < 3) + self._single = single + + @property + def single(self) -> bool: + """Whether this instance represents a single rotation.""" + return self._single or self._quat.ndim == 1 + + @property + def shape(self) -> tuple[int, ...]: + """The shape of the rotation's leading dimensions.""" + if self._single: + return () + return self._quat.shape[:-1] + + def __bool__(self) -> bool: + """Comply with Python convention for objects to be True. + + Required because `Rotation.__len__()` is defined and not always truthy. + """ + return True + + def __len__(self) -> int: + """Number of rotations contained in this object. + + Multiple rotations can be stored in a single instance. + + Returns + ------- + length : int + Number of rotations stored in object. + + Raises + ------ + TypeError if the instance was created as a single rotation. + """ + if self._single or self._quat.ndim == 1: + raise TypeError("Single rotation has no len().") + return self._quat.shape[0] + + def __repr__(self) -> str: + m = f"{self.as_matrix()!r}".splitlines() + # bump indent (+21 characters) + m[1:] = [" " * 21 + m[i] for i in range(1, len(m))] + return "Rotation.from_matrix(" + "\n".join(m) + ")" + + def __iter__(self) -> Iterator[Rotation]: + """Iterate over rotations.""" + if self._single or self._quat.ndim == 1: + raise TypeError("Single rotation is not iterable.") + # We return a generator that yields a new Rotation object for each rotation + # in the current object. We cannot rely on the default implementation using + # __getitem__ because jax will not raise an IndexError for out-of-bounds + # indices. + for i in range(self._quat.shape[0]): + yield Rotation(self._quat[i, ...], normalize=False, copy=False) + + @staticmethod + def _from_raw_quat( + quat: Array, xp: ModuleType, backend: ModuleType | None = None + ) -> Rotation: + """Create a Rotation skipping all sanitization steps. + + This method is intended for internal, performant creation of Rotations with + quaternions that are guaranteed to be valid. + """ + rot = Rotation.__new__(Rotation) + rot._single = quat.ndim == 1 and is_numpy(xp) + if rot._single: + quat = xpx.atleast_nd(quat, ndim=2, xp=xp) + rot._quat = quat + rot._xp = xp + if backend is None: + backend = select_backend(xp, cython_compatible=quat.ndim < 3) + rot._backend = backend + return rot + + +@xp_capabilities( + skip_backends=[("dask.array", "missing linalg.cross function")], + method_capabilities={ + "__init__": dict( + jax_jit=False, + skip_backends=[("dask.array", "missing linalg.cross function")], + ), + }, +) +class Slerp: + """Spherical Linear Interpolation of Rotations. + + The interpolation between consecutive rotations is performed as a rotation + around a fixed axis with a constant angular velocity [1]_. This ensures + that the interpolated rotations follow the shortest path between initial + and final orientations. + + Parameters + ---------- + times : array_like, shape (N,) + Times of the known rotations. At least 2 times must be specified. + rotations : `Rotation` instance + Rotations to perform the interpolation between. Must contain N + rotations. + + Methods + ------- + __call__ + + See Also + -------- + Rotation + + Notes + ----- + This class only supports interpolation of rotations with a single leading + dimension. + + .. versionadded:: 1.2.0 + + References + ---------- + .. [1] https://en.wikipedia.org/wiki/Slerp#Quaternion_Slerp + + Examples + -------- + >>> from scipy.spatial.transform import Rotation as R + >>> from scipy.spatial.transform import Slerp + + Setup the fixed keyframe rotations and times: + + >>> key_rots = R.random(5, random_state=2342345) + >>> key_times = [0, 1, 2, 3, 4] + + Create the interpolator object: + + >>> slerp = Slerp(key_times, key_rots) + + Interpolate the rotations at the given times: + + >>> times = [0, 0.5, 0.25, 1, 1.5, 2, 2.75, 3, 3.25, 3.60, 4] + >>> interp_rots = slerp(times) + + The keyframe rotations expressed as Euler angles: + + >>> key_rots.as_euler('xyz', degrees=True) + array([[ 14.31443779, -27.50095894, -3.7275787 ], + [ -1.79924227, -24.69421529, 164.57701743], + [146.15020772, 43.22849451, -31.34891088], + [ 46.39959442, 11.62126073, -45.99719267], + [-88.94647804, -49.64400082, -65.80546984]]) + + The interpolated rotations expressed as Euler angles. These agree with the + keyframe rotations at both endpoints of the range of keyframe times. + + >>> interp_rots.as_euler('xyz', degrees=True) + array([[ 14.31443779, -27.50095894, -3.7275787 ], + [ 4.74588574, -32.44683966, 81.25139984], + [ 10.71094749, -31.56690154, 38.06896408], + [ -1.79924227, -24.69421529, 164.57701743], + [ 11.72796022, 51.64207311, -171.7374683 ], + [ 146.15020772, 43.22849451, -31.34891088], + [ 68.10921869, 20.67625074, -48.74886034], + [ 46.39959442, 11.62126073, -45.99719267], + [ 12.35552615, 4.21525086, -64.89288124], + [ -30.08117143, -19.90769513, -78.98121326], + [ -88.94647804, -49.64400082, -65.80546984]]) + + """ + + def __init__(self, times: ArrayLike, rotations: Rotation): + if not isinstance(rotations, Rotation): + raise TypeError("`rotations` must be a `Rotation` instance.") + + if rotations.single or len(rotations) <= 1: + raise ValueError("`rotations` must be a sequence of at least 2 rotations.") + + q = rotations.as_quat() + if q.ndim > 2: + raise ValueError( + "Rotations with more than 1 leading dimension are not supported." + ) + + xp = array_namespace(q) + times = xp.asarray(times, device=xp_device(q), dtype=q.dtype) + if times.ndim != 1: + raise ValueError( + "Expected times to be specified in a 1 dimensional array, got " + f"{times.ndim} dimensions." + ) + + if times.shape[0] != len(rotations): + raise ValueError( + "Expected number of rotations to be equal to number of timestamps " + f"given, got {len(rotations)} rotations and {times.shape[0]} " + "timestamps." + ) + self.times = times + self.timedelta = xp.diff(times) + + # We cannot check for values for lazy backends, so we cannot raise an error on + # timedelta < 0 for lazy backends. Instead, we set timedelta to nans + neg_mask = xp.any(self.timedelta <= 0) + if is_lazy_array(neg_mask): + self.timedelta = xp.where(neg_mask, xp.nan, self.timedelta) + self.times = xp.where(neg_mask, xp.nan, self.times) + elif xp.any(neg_mask): + raise ValueError("Times must be in strictly increasing order.") + + self.rotations = rotations[:-1] + self.rotvecs = (self.rotations.inv() * rotations[1:]).as_rotvec() + + def __call__(self, times: ArrayLike) -> Rotation: + """Interpolate rotations. + + Compute the interpolated rotations at the given `times`. + + Parameters + ---------- + times : array_like + Times to compute the interpolations at. Can be a scalar or + 1-dimensional. + + Returns + ------- + interpolated_rotation : `Rotation` instance + Object containing the rotations computed at given `times`. + + """ + xp = array_namespace(self.times) + device = xp_device(self.times) + # Clearly differentiate from self.times property + compute_times = xp.asarray(times, device=device, dtype=self.times.dtype) + if compute_times.ndim > 1: + raise ValueError("`times` must be at most 1-dimensional.") + + single_time = compute_times.ndim == 0 + compute_times = xpx.atleast_nd(compute_times, ndim=1, xp=xp) + + # side = 'left' (default) excludes t_min. + ind = xp.searchsorted(self.times, compute_times) - 1 + # Include t_min. Without this step, index for t_min equals -1 + ind = xpx.at(ind, compute_times == self.times[0]).set(0) + # We cannot error out on invalid indices for jit compiled code. To not produce + # an index error, we set the index to 0 in case it is out of bounds, and later + # set the result to nan. + invalid_ind: Array = (ind < 0) | (ind > len(self.rotations) - 1) + if is_lazy_array(invalid_ind): + ind = xpx.at(ind, invalid_ind).set(0) + elif xp.any(invalid_ind): + raise ValueError( + f"Interpolation times must be within the range [{self.times[0]}, " + f"{self.times[-1]}], both inclusive." + ) + + alpha = (compute_times - self.times[ind]) / self.timedelta[ind] + alpha = xpx.at(alpha, invalid_ind).set(xp.nan) + + # The array API does not support integer arrays + ellipsis indexing and won't + # stabilize this feature due to blockers in PyTorch. Therefore we need to + # construct the index for the last dimension manually. + # See https://github.com/scipy/scipy/pull/23249#discussion_r2198363047 + result = self.rotations[ind] * Rotation.from_rotvec( + self.rotvecs[ind[:, None], xp.arange(3, device=device)] * alpha[:, None] + ) + + if single_time: + result = result[0] + + return result diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/transform/_rotation_cy.cpython-312-x86_64-linux-gnu.so b/venv/lib/python3.12/site-packages/scipy/spatial/transform/_rotation_cy.cpython-312-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..e37b078b30a703fbf18b1866728a0d53144c5ed5 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/transform/_rotation_cy.cpython-312-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bdb2a72c5d97e35e0a3cbf64d20c86c9fa7e81bcb1ddf8208cd7832daa3e9b39 +size 573960 diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/transform/_rotation_groups.py b/venv/lib/python3.12/site-packages/scipy/spatial/transform/_rotation_groups.py new file mode 100644 index 0000000000000000000000000000000000000000..870e9b9e2b44bff56b8228a70607e29f8173accc --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/transform/_rotation_groups.py @@ -0,0 +1,140 @@ +import numpy as np +from scipy.constants import golden as phi + + +def icosahedral(cls): + g1 = tetrahedral(cls).as_quat() + a = 0.5 + b = 0.5 / phi + c = phi / 2 + g2 = np.array([[+a, +b, +c, 0], + [+a, +b, -c, 0], + [+a, +c, 0, +b], + [+a, +c, 0, -b], + [+a, -b, +c, 0], + [+a, -b, -c, 0], + [+a, -c, 0, +b], + [+a, -c, 0, -b], + [+a, 0, +b, +c], + [+a, 0, +b, -c], + [+a, 0, -b, +c], + [+a, 0, -b, -c], + [+b, +a, 0, +c], + [+b, +a, 0, -c], + [+b, +c, +a, 0], + [+b, +c, -a, 0], + [+b, -a, 0, +c], + [+b, -a, 0, -c], + [+b, -c, +a, 0], + [+b, -c, -a, 0], + [+b, 0, +c, +a], + [+b, 0, +c, -a], + [+b, 0, -c, +a], + [+b, 0, -c, -a], + [+c, +a, +b, 0], + [+c, +a, -b, 0], + [+c, +b, 0, +a], + [+c, +b, 0, -a], + [+c, -a, +b, 0], + [+c, -a, -b, 0], + [+c, -b, 0, +a], + [+c, -b, 0, -a], + [+c, 0, +a, +b], + [+c, 0, +a, -b], + [+c, 0, -a, +b], + [+c, 0, -a, -b], + [0, +a, +c, +b], + [0, +a, +c, -b], + [0, +a, -c, +b], + [0, +a, -c, -b], + [0, +b, +a, +c], + [0, +b, +a, -c], + [0, +b, -a, +c], + [0, +b, -a, -c], + [0, +c, +b, +a], + [0, +c, +b, -a], + [0, +c, -b, +a], + [0, +c, -b, -a]]) + return cls.from_quat(np.concatenate((g1, g2))) + + +def octahedral(cls): + g1 = tetrahedral(cls).as_quat() + c = np.sqrt(2) / 2 + g2 = np.array([[+c, 0, 0, +c], + [0, +c, 0, +c], + [0, 0, +c, +c], + [0, 0, -c, +c], + [0, -c, 0, +c], + [-c, 0, 0, +c], + [0, +c, +c, 0], + [0, -c, +c, 0], + [+c, 0, +c, 0], + [-c, 0, +c, 0], + [+c, +c, 0, 0], + [-c, +c, 0, 0]]) + return cls.from_quat(np.concatenate((g1, g2))) + + +def tetrahedral(cls): + g1 = np.eye(4) + c = 0.5 + g2 = np.array([[c, -c, -c, +c], + [c, -c, +c, +c], + [c, +c, -c, +c], + [c, +c, +c, +c], + [c, -c, -c, -c], + [c, -c, +c, -c], + [c, +c, -c, -c], + [c, +c, +c, -c]]) + return cls.from_quat(np.concatenate((g1, g2))) + + +def dicyclic(cls, n, axis=2): + g1 = cyclic(cls, n, axis).as_rotvec() + + thetas = np.linspace(0, np.pi, n, endpoint=False) + rv = np.pi * np.vstack([np.zeros(n), np.cos(thetas), np.sin(thetas)]).T + g2 = np.roll(rv, axis, axis=1) + return cls.from_rotvec(np.concatenate((g1, g2))) + + +def cyclic(cls, n, axis=2): + thetas = np.linspace(0, 2 * np.pi, n, endpoint=False) + rv = np.vstack([thetas, np.zeros(n), np.zeros(n)]).T + return cls.from_rotvec(np.roll(rv, axis, axis=1)) + + +def create_group(cls, group, axis='Z'): + if not isinstance(group, str): + raise ValueError("`group` argument must be a string") + + permitted_axes = ['x', 'y', 'z', 'X', 'Y', 'Z'] + if axis not in permitted_axes: + raise ValueError("`axis` must be one of " + ", ".join(permitted_axes)) + + if group in ['I', 'O', 'T']: + symbol = group + order = 1 + elif group[:1] in ['C', 'D'] and group[1:].isdigit(): + symbol = group[:1] + order = int(group[1:]) + else: + raise ValueError("`group` must be one of 'I', 'O', 'T', 'Dn', 'Cn'") + + if order < 1: + raise ValueError("Group order must be positive") + + axis = 'xyz'.index(axis.lower()) + if symbol == 'I': + return icosahedral(cls) + elif symbol == 'O': + return octahedral(cls) + elif symbol == 'T': + return tetrahedral(cls) + elif symbol == 'D': + return dicyclic(cls, order, axis=axis) + elif symbol == 'C': + return cyclic(cls, order, axis=axis) + else: + assert False diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/transform/_rotation_spline.py b/venv/lib/python3.12/site-packages/scipy/spatial/transform/_rotation_spline.py new file mode 100644 index 0000000000000000000000000000000000000000..9d6b99a5af898bff9ee9148b8712f861c54740fd --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/transform/_rotation_spline.py @@ -0,0 +1,465 @@ +import numpy as np +from scipy.linalg import solve_banded +from ._rotation import Rotation + + +def _create_skew_matrix(x): + """Create skew-symmetric matrices corresponding to vectors. + + Parameters + ---------- + x : ndarray, shape (n, 3) + Set of vectors. + + Returns + ------- + ndarray, shape (n, 3, 3) + """ + result = np.zeros((len(x), 3, 3)) + result[:, 0, 1] = -x[:, 2] + result[:, 0, 2] = x[:, 1] + result[:, 1, 0] = x[:, 2] + result[:, 1, 2] = -x[:, 0] + result[:, 2, 0] = -x[:, 1] + result[:, 2, 1] = x[:, 0] + return result + + +def _matrix_vector_product_of_stacks(A, b): + """Compute the product of stack of matrices and vectors.""" + return np.einsum("ijk,ik->ij", A, b) + + +def _angular_rate_to_rotvec_dot_matrix(rotvecs): + """Compute matrices to transform angular rates to rot. vector derivatives. + + The matrices depend on the current attitude represented as a rotation + vector. + + Parameters + ---------- + rotvecs : ndarray, shape (n, 3) + Set of rotation vectors. + + Returns + ------- + ndarray, shape (n, 3, 3) + """ + norm = np.linalg.norm(rotvecs, axis=1) + k = np.empty_like(norm) + + mask = norm > 1e-4 + nm = norm[mask] + k[mask] = (1 - 0.5 * nm / np.tan(0.5 * nm)) / nm**2 + mask = ~mask + nm = norm[mask] + k[mask] = 1/12 + 1/720 * nm**2 + + skew = _create_skew_matrix(rotvecs) + + result = np.empty((len(rotvecs), 3, 3)) + result[:] = np.identity(3) + result[:] += 0.5 * skew + result[:] += k[:, None, None] * np.matmul(skew, skew) + + return result + + +def _rotvec_dot_to_angular_rate_matrix(rotvecs): + """Compute matrices to transform rot. vector derivatives to angular rates. + + The matrices depend on the current attitude represented as a rotation + vector. + + Parameters + ---------- + rotvecs : ndarray, shape (n, 3) + Set of rotation vectors. + + Returns + ------- + ndarray, shape (n, 3, 3) + """ + norm = np.linalg.norm(rotvecs, axis=1) + k1 = np.empty_like(norm) + k2 = np.empty_like(norm) + + mask = norm > 1e-4 + nm = norm[mask] + k1[mask] = (1 - np.cos(nm)) / nm ** 2 + k2[mask] = (nm - np.sin(nm)) / nm ** 3 + + mask = ~mask + nm = norm[mask] + k1[mask] = 0.5 - nm ** 2 / 24 + k2[mask] = 1 / 6 - nm ** 2 / 120 + + skew = _create_skew_matrix(rotvecs) + + result = np.empty((len(rotvecs), 3, 3)) + result[:] = np.identity(3) + result[:] -= k1[:, None, None] * skew + result[:] += k2[:, None, None] * np.matmul(skew, skew) + + return result + + +def _angular_acceleration_nonlinear_term(rotvecs, rotvecs_dot): + """Compute the non-linear term in angular acceleration. + + The angular acceleration contains a quadratic term with respect to + the derivative of the rotation vector. This function computes that. + + Parameters + ---------- + rotvecs : ndarray, shape (n, 3) + Set of rotation vectors. + rotvecs_dot : ndarray, shape (n, 3) + Set of rotation vector derivatives. + + Returns + ------- + ndarray, shape (n, 3) + """ + norm = np.linalg.norm(rotvecs, axis=1) + dp = np.sum(rotvecs * rotvecs_dot, axis=1) + cp = np.cross(rotvecs, rotvecs_dot) + ccp = np.cross(rotvecs, cp) + dccp = np.cross(rotvecs_dot, cp) + + k1 = np.empty_like(norm) + k2 = np.empty_like(norm) + k3 = np.empty_like(norm) + + mask = norm > 1e-4 + nm = norm[mask] + k1[mask] = (-nm * np.sin(nm) - 2 * (np.cos(nm) - 1)) / nm ** 4 + k2[mask] = (-2 * nm + 3 * np.sin(nm) - nm * np.cos(nm)) / nm ** 5 + k3[mask] = (nm - np.sin(nm)) / nm ** 3 + + mask = ~mask + nm = norm[mask] + k1[mask] = 1/12 - nm ** 2 / 180 + k2[mask] = -1/60 + nm ** 2 / 12604 + k3[mask] = 1/6 - nm ** 2 / 120 + + dp = dp[:, None] + k1 = k1[:, None] + k2 = k2[:, None] + k3 = k3[:, None] + + return dp * (k1 * cp + k2 * ccp) + k3 * dccp + + +def _compute_angular_rate(rotvecs, rotvecs_dot): + """Compute angular rates given rotation vectors and its derivatives. + + Parameters + ---------- + rotvecs : ndarray, shape (n, 3) + Set of rotation vectors. + rotvecs_dot : ndarray, shape (n, 3) + Set of rotation vector derivatives. + + Returns + ------- + ndarray, shape (n, 3) + """ + return _matrix_vector_product_of_stacks( + _rotvec_dot_to_angular_rate_matrix(rotvecs), rotvecs_dot) + + +def _compute_angular_acceleration(rotvecs, rotvecs_dot, rotvecs_dot_dot): + """Compute angular acceleration given rotation vector and its derivatives. + + Parameters + ---------- + rotvecs : ndarray, shape (n, 3) + Set of rotation vectors. + rotvecs_dot : ndarray, shape (n, 3) + Set of rotation vector derivatives. + rotvecs_dot_dot : ndarray, shape (n, 3) + Set of rotation vector second derivatives. + + Returns + ------- + ndarray, shape (n, 3) + """ + return (_compute_angular_rate(rotvecs, rotvecs_dot_dot) + + _angular_acceleration_nonlinear_term(rotvecs, rotvecs_dot)) + + +def _create_block_3_diagonal_matrix(A, B, d): + """Create a 3-diagonal block matrix as banded. + + The matrix has the following structure: + + DB... + ADB.. + .ADB. + ..ADB + ...AD + + The blocks A, B and D are 3-by-3 matrices. The D matrices has the form + d * I. + + Parameters + ---------- + A : ndarray, shape (n, 3, 3) + Stack of A blocks. + B : ndarray, shape (n, 3, 3) + Stack of B blocks. + d : ndarray, shape (n + 1,) + Values for diagonal blocks. + + Returns + ------- + ndarray, shape (11, 3 * (n + 1)) + Matrix in the banded form as used by `scipy.linalg.solve_banded`. + """ + ind = np.arange(3) + ind_blocks = np.arange(len(A)) + + A_i = np.empty_like(A, dtype=int) + A_i[:] = ind[:, None] + A_i += 3 * (1 + ind_blocks[:, None, None]) + + A_j = np.empty_like(A, dtype=int) + A_j[:] = ind + A_j += 3 * ind_blocks[:, None, None] + + B_i = np.empty_like(B, dtype=int) + B_i[:] = ind[:, None] + B_i += 3 * ind_blocks[:, None, None] + + B_j = np.empty_like(B, dtype=int) + B_j[:] = ind + B_j += 3 * (1 + ind_blocks[:, None, None]) + + diag_i = diag_j = np.arange(3 * len(d)) + i = np.hstack((A_i.ravel(), B_i.ravel(), diag_i)) + j = np.hstack((A_j.ravel(), B_j.ravel(), diag_j)) + values = np.hstack((A.ravel(), B.ravel(), np.repeat(d, 3))) + + u = 5 + l = 5 + result = np.zeros((u + l + 1, 3 * len(d))) + result[u + i - j, j] = values + return result + + +class RotationSpline: + """Interpolate rotations with continuous angular rate and acceleration. + + The rotation vectors between each consecutive orientation are cubic + functions of time and it is guaranteed that angular rate and acceleration + are continuous. Such interpolation are analogous to cubic spline + interpolation. + + Refer to [1]_ for math and implementation details. + + Parameters + ---------- + times : array_like, shape (N,) + Times of the known rotations. At least 2 times must be specified. + rotations : `Rotation` instance + Rotations to perform the interpolation between. Must contain N + rotations. + + Methods + ------- + __call__ + + References + ---------- + .. [1] `Smooth Attitude Interpolation + `_ + + Examples + -------- + >>> from scipy.spatial.transform import Rotation, RotationSpline + >>> import numpy as np + + Define the sequence of times and rotations from the Euler angles: + + >>> times = [0, 10, 20, 40] + >>> angles = [[-10, 20, 30], [0, 15, 40], [-30, 45, 30], [20, 45, 90]] + >>> rotations = Rotation.from_euler('XYZ', angles, degrees=True) + + Create the interpolator object: + + >>> spline = RotationSpline(times, rotations) + + Interpolate the Euler angles, angular rate and acceleration: + + >>> angular_rate = np.rad2deg(spline(times, 1)) + >>> angular_acceleration = np.rad2deg(spline(times, 2)) + >>> times_plot = np.linspace(times[0], times[-1], 100) + >>> angles_plot = spline(times_plot).as_euler('XYZ', degrees=True) + >>> angular_rate_plot = np.rad2deg(spline(times_plot, 1)) + >>> angular_acceleration_plot = np.rad2deg(spline(times_plot, 2)) + + On this plot you see that Euler angles are continuous and smooth: + + >>> import matplotlib.pyplot as plt + >>> plt.plot(times_plot, angles_plot) + >>> plt.plot(times, angles, 'x') + >>> plt.title("Euler angles") + >>> plt.show() + + The angular rate is also smooth: + + >>> plt.plot(times_plot, angular_rate_plot) + >>> plt.plot(times, angular_rate, 'x') + >>> plt.title("Angular rate") + >>> plt.show() + + The angular acceleration is continuous, but not smooth. Also note that + the angular acceleration is not a piecewise-linear function, because + it is different from the second derivative of the rotation vector (which + is a piecewise-linear function as in the cubic spline). + + >>> plt.plot(times_plot, angular_acceleration_plot) + >>> plt.plot(times, angular_acceleration, 'x') + >>> plt.title("Angular acceleration") + >>> plt.show() + """ + # Parameters for the solver for angular rate. + MAX_ITER = 10 + TOL = 1e-9 + + def _solve_for_angular_rates(self, dt, angular_rates, rotvecs): + angular_rate_first = angular_rates[0].copy() + + A = _angular_rate_to_rotvec_dot_matrix(rotvecs) + A_inv = _rotvec_dot_to_angular_rate_matrix(rotvecs) + M = _create_block_3_diagonal_matrix( + 2 * A_inv[1:-1] / dt[1:-1, None, None], + 2 * A[1:-1] / dt[1:-1, None, None], + 4 * (1 / dt[:-1] + 1 / dt[1:])) + + b0 = 6 * (rotvecs[:-1] * dt[:-1, None] ** -2 + + rotvecs[1:] * dt[1:, None] ** -2) + b0[0] -= 2 / dt[0] * A_inv[0].dot(angular_rate_first) + b0[-1] -= 2 / dt[-1] * A[-1].dot(angular_rates[-1]) + + for iteration in range(self.MAX_ITER): + rotvecs_dot = _matrix_vector_product_of_stacks(A, angular_rates) + delta_beta = _angular_acceleration_nonlinear_term( + rotvecs[:-1], rotvecs_dot[:-1]) + b = b0 - delta_beta + angular_rates_new = solve_banded((5, 5), M, b.ravel()) + angular_rates_new = angular_rates_new.reshape((-1, 3)) + + delta = np.abs(angular_rates_new - angular_rates[:-1]) + angular_rates[:-1] = angular_rates_new + if np.all(delta < self.TOL * (1 + np.abs(angular_rates_new))): + break + + rotvecs_dot = _matrix_vector_product_of_stacks(A, angular_rates) + angular_rates = np.vstack((angular_rate_first, angular_rates[:-1])) + + return angular_rates, rotvecs_dot + + def __init__(self, times, rotations): + from scipy.interpolate import PPoly + + if rotations.single: + raise ValueError("`rotations` must be a sequence of rotations.") + + if rotations.as_quat().ndim > 2: + raise ValueError( + "Rotations with more than 1 leading dimension are not supported." + ) + + if len(rotations) == 1: + raise ValueError("`rotations` must contain at least 2 rotations.") + + times = np.asarray(times, dtype=float) + if times.ndim != 1: + raise ValueError("`times` must be 1-dimensional.") + + if len(times) != len(rotations): + raise ValueError("Expected number of rotations to be equal to " + "number of timestamps given, " + f"got {len(rotations)} rotations " + f"and {len(times)} timestamps.") + + dt = np.diff(times) + if np.any(dt <= 0): + raise ValueError("Values in `times` must be in a strictly " + "increasing order.") + + rotvecs = (rotations[:-1].inv() * rotations[1:]).as_rotvec() + angular_rates = rotvecs / dt[:, None] + + if len(rotations) == 2: + rotvecs_dot = angular_rates + else: + angular_rates, rotvecs_dot = self._solve_for_angular_rates( + dt, angular_rates, rotvecs) + + dt = dt[:, None] + coeff = np.empty((4, len(times) - 1, 3)) + coeff[0] = (-2 * rotvecs + dt * angular_rates + + dt * rotvecs_dot) / dt ** 3 + coeff[1] = (3 * rotvecs - 2 * dt * angular_rates + - dt * rotvecs_dot) / dt ** 2 + coeff[2] = angular_rates + coeff[3] = 0 + + self.times = times + self.rotations = rotations + self.interpolator = PPoly(coeff, times) + + def __call__(self, times, order=0): + """Compute interpolated values. + + Parameters + ---------- + times : float or array_like + Times of interest. + order : {0, 1, 2}, optional + Order of differentiation: + + * 0 (default) : return Rotation + * 1 : return the angular rate in rad/sec + * 2 : return the angular acceleration in rad/sec/sec + + Returns + ------- + Interpolated Rotation, angular rate or acceleration. + """ + if order not in [0, 1, 2]: + raise ValueError("`order` must be 0, 1 or 2.") + + times = np.asarray(times, dtype=float) + if times.ndim > 1: + raise ValueError("`times` must be at most 1-dimensional.") + + singe_time = times.ndim == 0 + times = np.atleast_1d(times) + + rotvecs = self.interpolator(times) + if order == 0: + index = np.searchsorted(self.times, times, side='right') + index -= 1 + index[index < 0] = 0 + n_segments = len(self.times) - 1 + index[index > n_segments - 1] = n_segments - 1 + result = self.rotations[index] * Rotation.from_rotvec(rotvecs) + elif order == 1: + rotvecs_dot = self.interpolator(times, 1) + result = _compute_angular_rate(rotvecs, rotvecs_dot) + elif order == 2: + rotvecs_dot = self.interpolator(times, 1) + rotvecs_dot_dot = self.interpolator(times, 2) + result = _compute_angular_acceleration(rotvecs, rotvecs_dot, + rotvecs_dot_dot) + else: + assert False + + if singe_time: + result = result[0] + + return result diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/transform/_rotation_xp.py b/venv/lib/python3.12/site-packages/scipy/spatial/transform/_rotation_xp.py new file mode 100644 index 0000000000000000000000000000000000000000..5eea00637acab3d8d914ae3bbd3bdae2a87eb9c8 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/transform/_rotation_xp.py @@ -0,0 +1,1140 @@ +"""Array API backend for the `Rotation` class. + +This module provides generic, functional implementations of the `Rotation` class methods +that work with any Array API-compatible backend. +""" + +# Parts of the implementation are adapted from the cython backend and +# https://github.com/jax-ml/jax/blob/d695aa4c63ffcebefce52794427c46bad576680c/jax/_src/scipy/spatial/transform.py. +import re +import warnings +from types import EllipsisType + +import numpy as np +from scipy._lib._array_api import ( + array_namespace, + Array, + ArrayLike, + is_lazy_array, + xp_vector_norm, + xp_result_type, + xp_promote, + is_jax, +) +from scipy._lib._util import broadcastable +from scipy._external.array_api_compat import device as xp_device +from scipy._external.array_api_compat import is_array_api_obj +import scipy._external.array_api_extra as xpx + +# mypy: disable-error-code=index +# mypy: disable-error-code=operator +# mypy: disable-error-code=union-attr + +def from_quat( + quat: Array, + normalize: bool = True, + copy: bool = True, + *, + scalar_first: bool = False, +) -> Array: + xp = array_namespace(quat) + if scalar_first: + quat = xp.roll(quat, -1, axis=-1) + # Normalize will always copy, so we avoid the extra copy if we normalize + if copy and not normalize: + quat = xp.asarray(quat, copy=True) + if normalize: + quat = _normalize_quaternion(quat) + return quat + + +def from_matrix(matrix: Array, assume_valid: bool = False) -> Array: + xp = array_namespace(matrix) + device = xp_device(matrix) + + if not assume_valid: + mask = xp.linalg.det(matrix) <= 0 + lazy = is_lazy_array(mask) + # Only non-lazy backends raise an error for non-positive determinants. + if not lazy and xp.any(mask): + ind = int(xp.nonzero(xpx.atleast_nd(mask, ndim=1, xp=xp))[0][0]) + raise ValueError( + "Non-positive determinant (left-handed or null coordinate frame) in " + f"rotation matrix {ind}: {matrix[ind, ...]}." + ) + elif lazy: + matrix = xp.where(mask[..., None, None], xp.nan, matrix) + + gramians = matrix @ xp.matrix_transpose(matrix) + eye = xp.eye(3, dtype=matrix.dtype, device=device) + is_orthogonal = xp.all( + xpx.isclose(gramians, eye, atol=1e-12, xp=xp), axis=(-2, -1) + ) + + if lazy: + # Lazy backends do not support non-concrete boolean indexing or any form of + # computation without statically known shapes, so we always compute SVD and + # use xp.where to select the result. + U, _, Vt = xp.linalg.svd(matrix, full_matrices=False) + matrix = xp.where(is_orthogonal[..., None, None], matrix, U @ Vt) + elif not xp.all(is_orthogonal): + # For eager frameworks, only compute SVD if needed. + is_not_orthogonal = ~is_orthogonal + U, _, Vt = xp.linalg.svd(matrix[is_not_orthogonal], full_matrices=False) + matrix = xpx.at(matrix)[is_not_orthogonal].set(U @ Vt) + + return _from_matrix_orthogonal(matrix) + + +def _from_matrix_orthogonal(matrix: Array) -> Array: + """Convert known orthogonal rotation matrix to quaternion""" + xp = array_namespace(matrix) + device = xp_device(matrix) + + matrix_trace = matrix[..., 0, 0] + matrix[..., 1, 1] + matrix[..., 2, 2] + decision = xp.stack( + [matrix[..., 0, 0], matrix[..., 1, 1], matrix[..., 2, 2], matrix_trace], + axis=-1, + ) + choice = xp.argmax(decision, axis=-1, keepdims=True) + quat = xp.empty((*matrix.shape[:-2], 4), dtype=matrix.dtype, device=device) + + # The Array API does not support mixing integer indexing with ellipsis, so we cannot + # follow the same pattern as the cython backend. Instead, we compute each case + # explicitly and assemble the final result with `xp.where`. + # TODO: Revisit this implementation if the array API supports mixed integer and + # ellipsis indexing. + + # Case 0 + quat_0 = xp.stack( + [ + 1 - matrix_trace[...] + 2 * matrix[..., 0, 0], + matrix[..., 1, 0] + matrix[..., 0, 1], + matrix[..., 2, 0] + matrix[..., 0, 2], + matrix[..., 2, 1] - matrix[..., 1, 2], + ], + axis=-1, + ) + quat = xp.where(choice == 0, quat_0, quat) + + # Case 1 + quat_1 = xp.stack( + [ + matrix[..., 1, 0] + matrix[..., 0, 1], + 1 - matrix_trace[...] + 2 * matrix[..., 1, 1], + matrix[..., 2, 1] + matrix[..., 1, 2], + matrix[..., 0, 2] - matrix[..., 2, 0], + ], + axis=-1, + ) + quat = xp.where(choice == 1, quat_1, quat) + + # Case 2 + quat_2 = xp.stack( + [ + matrix[..., 2, 0] + matrix[..., 0, 2], + matrix[..., 2, 1] + matrix[..., 1, 2], + 1 - matrix_trace[...] + 2 * matrix[..., 2, 2], + matrix[..., 1, 0] - matrix[..., 0, 1], + ], + axis=-1, + ) + quat = xp.where(choice == 2, quat_2, quat) + + # Case 3 + quat_3 = xp.stack( + [ + matrix[..., 2, 1] - matrix[..., 1, 2], + matrix[..., 0, 2] - matrix[..., 2, 0], + matrix[..., 1, 0] - matrix[..., 0, 1], + 1 + matrix_trace[...], + ], + axis=-1, + ) + quat = xp.where(choice == 3, quat_3, quat) + + return _normalize_quaternion(quat) + + +def from_rotvec(rotvec: Array, degrees: bool = False) -> Array: + xp = array_namespace(rotvec) + if rotvec.shape[-1] != 3: + raise ValueError( + f"Expected `rot_vec` to have shape (..., 3), got {rotvec.shape}" + ) + rotvec = _deg2rad(rotvec) if degrees else rotvec + + angle = xp_vector_norm(rotvec, axis=-1, keepdims=True, xp=xp) + small_angle = angle <= 1e-3 + angle2 = angle**2 + small_scale = 0.5 - angle2 / 48 + angle2**2 / 3840 + # We need to handle the case where angle is 0 to avoid division by zero. We use the + # value of the Taylor series approximation, but non-branching operations require + # that we still divide by the angle. Since we do not use the result where the angle + # is close to 0, this is safe. + div_angle = angle + xp.asarray(small_angle, dtype=angle.dtype) + large_scale = xp.sin(angle / 2) / div_angle + scale = xp.where(small_angle, small_scale, large_scale) + quat = xp.concat([rotvec * scale, xp.cos(angle / 2)], axis=-1) + return quat + + +def from_mrp(mrp: Array) -> Array: + xp = array_namespace(mrp) + if mrp.shape[-1] != 3: + raise ValueError(f"Expected `mrp` to have shape (..., 3), got {mrp.shape}") + mrp2_plus_1 = xp.linalg.vecdot(mrp, mrp, axis=-1)[..., None] + 1 + q_no_norm = xp.concat([2 * mrp[..., :3], (2 - mrp2_plus_1)], axis=-1) + quat = q_no_norm / mrp2_plus_1 + return quat + + +def from_euler(seq: str, angles: Array, degrees: bool = False) -> Array: + xp = array_namespace(angles) + num_axes = len(seq) + if num_axes < 1 or num_axes > 3: + raise ValueError( + "Expected axis specification to be a non-empty " + f"string of up to 3 characters, got {seq}" + ) + + intrinsic = re.match(r"^[XYZ]{1,3}$", seq) is not None + extrinsic = re.match(r"^[xyz]{1,3}$", seq) is not None + if not (intrinsic or extrinsic): + raise ValueError( + "Expected axes from `seq` to be from ['x', 'y', " + f"'z'] or ['X', 'Y', 'Z'], got {seq}" + ) + + if any(seq[i] == seq[i + 1] for i in range(num_axes - 1)): + raise ValueError(f"Expected consecutive axes to be different, got {seq}") + + if degrees: + angles = _deg2rad(angles) + + angles = xpx.atleast_nd(angles, ndim=1, xp=xp) + + if angles.shape[-1] != num_axes: + raise ValueError( + "Expected last dimension of `angles` to match number of sequence axes " + f"specified, got {angles.shape[-1]}." + ) + axes = [_elementary_basis_index(x) for x in seq.lower()] + q = _elementary_quat_compose(axes, angles, intrinsic) + return q + + +def from_davenport( + axes: Array, order: str, angles: Array, degrees: bool = False +) -> Array: + xp = array_namespace(axes) + device = xp_device(axes) + if order in ["e", "extrinsic"]: # Must be static, cannot be jitted + extrinsic = True + elif order in ["i", "intrinsic"]: + extrinsic = False + else: + raise ValueError( + "order should be 'e'/'extrinsic' for extrinsic sequences or 'i'/" + f"'intrinsic' for intrinsic sequences, got {order}" + ) + + if axes.shape[-1] != 3: + raise ValueError("Axes must be vectors of length 3.") + + axes = xpx.atleast_nd(axes, ndim=2, xp=xp) + angles = xpx.atleast_nd(angles, ndim=1, xp=xp) + num_axes = axes.shape[-2] + if num_axes < 1 or num_axes > 3: + raise ValueError(f"Expected up to 3 axes, got {num_axes}") + + axes = axes / xp_vector_norm(axes, axis=-1, keepdims=True, xp=xp) + + # Check if axes are orthogonal. Shape checks also work for lazy backends. + axes_not_orthogonal = xp.zeros(axes.shape[:-2], dtype=xp.bool, device=device) + if num_axes > 1: + # Cannot be True yet, so we do not need to use xp.logical_or + axes_not_orthogonal = axes_not_orthogonal | ( + xp.abs(xp.vecdot(axes[..., 0, :], axes[..., 1, :])) > 1e-7 + ) + if num_axes > 2: + axes_not_orthogonal = axes_not_orthogonal | ( + xp.abs(xp.vecdot(axes[..., 1, :], axes[..., 2, :])) > 1e-7 + ) + if not is_lazy_array(axes_not_orthogonal) and xp.any(axes_not_orthogonal): + raise ValueError("Consecutive axes must be orthogonal.") + else: + axes = xp.where(axes_not_orthogonal[..., None, None], xp.nan, axes) + + if degrees: + angles = _deg2rad(angles) + + if ( + not broadcastable(axes.shape[:-1], angles.shape) + or axes.shape[-2] != angles.shape[-1] + ): + raise ValueError( + f"Expected `angles` to match number of axes, got {angles.shape} angles " + f"and {axes.shape} axes." + ) + + q_shape = angles.shape[:-1] + (4,) + q = xp.zeros(q_shape, dtype=angles.dtype, device=xp_device(angles)) + q = xpx.at(q)[..., 3].set(1) + + for i in range(num_axes): + qi = from_rotvec(angles[..., i, None] * axes[..., i, :]) + q = compose_quat(qi, q) if extrinsic else compose_quat(q, qi) + return q + + +def as_quat( + quat: Array, canonical: bool = False, *, scalar_first: bool = False +) -> Array: + xp = array_namespace(quat) + if canonical: + quat = _quat_canonical(quat) + if scalar_first: + quat = xp.roll(quat, 1, axis=-1) + return quat + + +def as_matrix(quat: Array) -> Array: + xp = array_namespace(quat) + x = quat[..., 0] + y = quat[..., 1] + z = quat[..., 2] + w = quat[..., 3] + + x2 = x * x + y2 = y * y + z2 = z * z + w2 = w * w + + xy = x * y + zw = z * w + xz = x * z + yw = y * w + yz = y * z + xw = x * w + + matrix_elements = [ + x2 - y2 - z2 + w2, + 2 * (xy - zw), + 2 * (xz + yw), + 2 * (xy + zw), + -x2 + y2 - z2 + w2, + 2 * (yz - xw), + 2 * (xz - yw), + 2 * (yz + xw), + -x2 - y2 + z2 + w2, + ] + matrix = xp.reshape(xp.stack(matrix_elements, axis=-1), (*quat.shape[:-1], 3, 3)) + return matrix + + +def as_rotvec(quat: Array, degrees: bool = False) -> Array: + xp = array_namespace(quat) + quat = _quat_canonical(quat) + ax_norm = xp_vector_norm(quat[..., :3], axis=-1, keepdims=True, xp=xp) + angle = 2 * xp.atan2(ax_norm, quat[..., 3][..., None]) + small_angle = angle <= 1e-3 + angle2 = angle**2 + small_scale = 2 + angle2 / 12 + 7 * angle2**2 / 2880 + # We need to handle the case where sin(angle/2) is 0 to avoid division by zero. We + # use the value of the Taylor series approximation, but non-branching operations + # require that we still divide by the sin. Since we do not use the result where the + # angle is close to 0, adding one to the sin where we discard the result is safe. + div_sin = xp.sin(angle / 2.0) + xp.asarray(small_angle, dtype=angle.dtype) + large_scale = angle / div_sin + scale = xp.where(small_angle, small_scale, large_scale) + if degrees: + scale = _rad2deg(scale) + rotvec = scale * quat[..., :3] + return rotvec + + +def as_mrp(quat: Array) -> Array: + xp = array_namespace(quat) + one = xp.asarray(1.0, device=xp_device(quat), dtype=quat.dtype) + sign = xp.where(quat[..., 3, None] < 0, -1, one) + denominator = 1.0 + sign * quat[..., 3, None] + return sign * quat[..., :3] / denominator + + +def as_euler( + quat: Array, seq: str, degrees: bool = False, *, suppress_warnings: bool = False +) -> Array: + xp = array_namespace(quat) + + # Sanitize the sequence + if len(seq) != 3: + raise ValueError(f"Expected 3 axes, got {seq}.") + intrinsic = re.match(r"^[XYZ]{1,3}$", seq) is not None + extrinsic = re.match(r"^[xyz]{1,3}$", seq) is not None + if not (intrinsic or extrinsic): + raise ValueError( + "Expected axes from `seq` to be from ['x', 'y', 'z'] or ['X', 'Y', 'Z'], " + f"got {seq}" + ) + if any(seq[i] == seq[i + 1] for i in range(2)): + raise ValueError(f"Expected consecutive axes to be different, got {seq}") + + device = xp_device(quat) + axes = [_elementary_basis_index(x) for x in seq.lower()] + axes = axes if extrinsic else axes[::-1] + i, j, k = axes + symmetric = i == k + k = 3 - i - j if symmetric else k + + mask = xp.asarray(symmetric, device=device) + sign = xp.asarray((i - j) * (j - k) * (k - i) // 2, dtype=quat.dtype, device=device) + # Permute quaternion elements + a = xp.where(mask, quat[..., 3], quat[..., 3] - quat[..., j]) + b = xp.where(mask, quat[..., i], quat[..., i] + quat[..., k] * sign) + c = xp.where(mask, quat[..., j], quat[..., j] + quat[..., 3]) + d = xp.where(mask, quat[..., k] * sign, quat[..., k] * sign - quat[..., i]) + + angles = _get_angles( + extrinsic, symmetric, sign, xp.pi / 2, a, b, c, d, suppress_warnings + ) + return _rad2deg(angles) if degrees else angles + + +def as_davenport( + quat: Array, + axes: Array, + order: str, + degrees: bool = False, + *, + suppress_warnings: bool = False, +) -> Array: + xp = array_namespace(quat) + + # Check argument validity + if order in ["e", "extrinsic"]: + extrinsic = True + elif order in ["i", "intrinsic"]: + extrinsic = False + else: + raise ValueError( + "order should be 'e'/'extrinsic' for extrinsic sequences or 'i'/'intrinsic'" + f" for intrinsic sequences, got {order}" + ) + if axes.shape[-2] != 3: + raise ValueError(f"Expected 3 axes, got {axes.shape}.") + if axes.shape[-1] != 3: + raise ValueError("Axes must be vectors of length 3.") + if not broadcastable(axes.shape[:-2], quat.shape[:-1]): + raise ValueError( + f"Expected `axes` to match number of rotations, got {axes.shape} axes " + f"and {quat.shape} rotations." + ) + + # normalize axes + axes = axes / xp_vector_norm(axes, axis=-1, keepdims=True, xp=xp) + vdot_ax0_ax1 = xp.vecdot(axes[..., 0, :], axes[..., 1, :]) + vdot_ax1_ax2 = xp.vecdot(axes[..., 1, :], axes[..., 2, :]) + is_invalid = (vdot_ax0_ax1 >= 1e-7) | (vdot_ax1_ax2 >= 1e-7) + if is_lazy_array(is_invalid): + axes = xp.where(is_invalid[..., None, None], xp.nan, axes) + elif xp.any(is_invalid): + raise ValueError("Consecutive axes must be orthogonal.") + + angles = _compute_davenport_from_quat( + quat, + axes[..., 0, :], + axes[..., 1, :], + axes[..., 2, :], + extrinsic, + suppress_warnings, + ) + if degrees: + angles = _rad2deg(angles) + return angles + + +def inv(quat: Array) -> Array: + return xpx.at(quat)[..., :3].multiply(-1, copy=True) + + +def magnitude(quat: Array) -> Array: + xp = array_namespace(quat) + sin_q = xp_vector_norm(quat[..., :3], axis=-1, xp=xp) + cos_q = xp.abs(quat[..., 3]) + angles = 2 * xp.atan2(sin_q, cos_q) + return angles + + +def approx_equal( + quat: Array, other: Array, atol: float | None = None, degrees: bool = False +) -> Array: + if atol is None: + if degrees: + warnings.warn( + "atol must be set to use the degrees flag, defaulting to 1e-8 radians.", + stacklevel=2, + ) + atol = 1e-8 + elif degrees: + atol = _deg2rad(atol) + + if not broadcastable(quat.shape, other.shape): + raise ValueError( + f"Expected broadcastable shapes in both rotations, got {quat.shape[:-1]} " + f"rotations in first and {other.shape[:-1]} rotations in second object." + ) + + quat_result = compose_quat(other, inv(quat)) + angles = magnitude(quat_result) + return angles < atol + + +def mean( + quat: Array, + weights: ArrayLike | None = None, + axis: None | int | tuple[int, ...] = None, +) -> Array: + xp = array_namespace(quat) + device = xp_device(quat) + dtype = xp_result_type(quat, force_floating=True, xp=xp) + if quat.shape[0] == 0: + raise ValueError("Mean of an empty rotation set is undefined.") + # Axis logic: For None, we reduce over all axes. For int, we only reduce over that + # axis. For tuple, we reduce over all specified axes. + all_axes = tuple(range(quat.ndim - 1)) + if axis is None: + axis = all_axes + elif isinstance(axis, int): + axis = (axis,) + if not isinstance(axis, tuple): + raise ValueError("`axis` must be None, int, or tuple of ints.") + # Ensure all axes are within bounds + if axis != () and (min(axis) < -(quat.ndim - 1) or max(axis) > (quat.ndim - 2)): + raise ValueError( + f"axis {axis} is out of bounds for rotation with shape {quat.shape[:-1]}." + ) + # Ensure all axes are positive and unique + axis = tuple(sorted(set(x % (quat.ndim - 1) for x in axis))) + + lazy = is_lazy_array(quat) + # Branching code is okay for checks that include meta info such as shapes and types + quat_expand = quat[..., None, :] + if weights is None: + K = xp.matrix_transpose(quat_expand) @ quat_expand + else: + weights = xp.asarray(weights, dtype=dtype, device=device) + neg_weights = weights < 0 + if not lazy and xp.any(neg_weights): + raise ValueError("`weights` must be non-negative.") + elif lazy: + # We cannot check for negative weights because jit code needs to be + # non-branching. We return NaN instead + weights = xp.where(neg_weights, xp.nan, weights) + + if not broadcastable(quat.shape[:-1], weights.shape): + raise ValueError( + "Expected `weights` to be broadcastable to rotation shape, got shape " + f"{weights.shape} for {quat.shape[:-1]} rotations." + ) + + # Make sure we can transpose quat + weighted_quat = weights[..., None, None] * quat_expand + K = xp.matrix_transpose(weighted_quat) @ quat_expand + + # Move reduction axes to the end + keep_axes = tuple(i for i in all_axes if i not in axis) + axes_order = keep_axes + axis + K_reordered = xp.moveaxis(K, axes_order, all_axes) + # Reshape to flatten reduction axes + new_shape = K_reordered.shape[: len(keep_axes)] + (-1, 4, 4) + K = xp.mean(xp.reshape(K_reordered, new_shape), axis=-3) + _, v = xp.linalg.eigh(K) + return v[..., -1] + + +def reduce( + quat: Array, + left: Array | None = None, + right: Array | None = None, +) -> tuple[Array, Array | None, Array | None]: + if left is None and right is None: + return quat, None, None + # DECISION: We cannot have variable number of return arguments for jit compiled + # functions. We therefore always return the indices, and filter out later. + # TOOD: Properly support broadcasting. + xp = array_namespace(quat) + quat = xpx.atleast_nd(quat, ndim=2, xp=xp) + if left is None: + left = xp.ones_like(quat) + if right is None: + right = xp.ones_like(quat) + + # We want to calculate the real components of q = l * p * r. It can + # be shown that: + # qs = ls * ps * rs - ls * dot(pv, rv) - ps * dot(lv, rv) + # - rs * dot(lv, pv) - dot(cross(lv, pv), rv) + # where ls and lv denote the scalar and vector components of l. + + p = quat + ps, pv = _split_rotation(p, xp) + ls, lv = _split_rotation(left, xp) + rs, rv = _split_rotation(right, xp) + + # Compute each term without einsum (not accessible in the Array API) + # First term: np.einsum("i,j,k", ls, ps, rs) + term1 = ls[..., :, None, None] * ps[..., None, :, None] * rs[..., None, None, :] + # Second term: np.einsum('i,jx,kx', ls, pv, rv) + prv = xp.sum(pv[..., :, None, :] * rv[..., None, :, :], axis=-1) + term2 = ls[..., :, None, None] * prv[..., None, :, :] + # Third term: np.einsum('ix,j,kx', lv, ps, rv) + lrv = xp.sum(lv[..., :, None, :] * rv[..., None, :, :], axis=-1) + term3 = ps[..., None, :, None] * lrv[..., :, None, :] + # Fourth term: np.einsum('ix,jx,k', lv, pv, rs) + lpv = xp.sum(lv[..., :, None, :] * pv[..., None, :, :], axis=-1) + term4 = rs[..., None, None, :] * lpv[..., :, :, None] + # Fifth term: np.einsum('xyz,ix,jy,kz', e, lv, pv, rv). We want to avoid expanding + # the einsum into a 6D tensor to avoid excessive memory usage. Instead, we compute + # the cross product between lv and pv and then compute the dot product with rv. + # First compute cross products between lv and pv + lv_expanded = lv[..., :, None, :] + pv_expanded = pv[..., None, :, :] + cross_lp = xp.linalg.cross(lv_expanded, pv_expanded) + # Then compute dot product with rv + term5 = xp.sum(cross_lp[..., :, :, None, :] * rv[..., None, None, :, :], axis=-1) + # Combine all terms with proper shape alignment + qs = xp.abs(term1 - term2 - term3 - term4 - term5) + qs = xp.reshape(xp.moveaxis(qs, 1, 0), (qs.shape[1], -1)) + + # Find best indices from scalar components + max_ind = xp.argmax(xp.reshape(qs, (qs.shape[0], -1)), axis=1) + left_best = max_ind // rv.shape[0] + right_best = max_ind % rv.shape[0] + # Array API limitation: Integer index arrays are only allowed with integer indices + # TODO: Can we somehow avoid this? + all_idx = xp.reshape(xp.arange(left.shape[-1]), (1, -1)) + left_idx = xp.reshape(left_best, (-1, 1)) + left = left[left_idx, all_idx] + right_idx = xp.reshape(right_best, (-1, 1)) + right = right[right_idx, all_idx] + + # Reduce the rotation using the best indices + reduced = compose_quat(left, compose_quat(p, right)) + + if left is None: + left_best = None + if right is None: + right_best = None + return reduced, left_best, right_best + + +def apply(quat: Array, points: Array, inverse: bool = False) -> Array: + xp = array_namespace(quat) + mat = as_matrix(quat) + # We do not have access to einsum. To avoid broadcasting issues, we add a singleton + # dimension to the points array and remove it after the operation. + points = points[..., None] + if not broadcastable(mat.shape, points.shape): + raise ValueError( + f"Cannot broadcast {quat.shape[:-1]} rotations to {points.shape[:-1]} " + "vectors." + ) + if inverse: + # TODO: Replace with .mT once numpy 2.0 is the minimum supported version + return (xp.matrix_transpose(mat) @ points)[..., 0] + return (mat @ points)[..., 0] + + +def setitem( + quat: Array, value: Array, indexer: int | slice | EllipsisType | None +) -> Array: + return xpx.at(quat)[indexer, ...].set(value) + + +def align_vectors( + a: Array, b: Array, weights: Array | None = None, return_sensitivity: bool = False +) -> tuple[Array, Array, Array]: + xp = array_namespace(a) + # Check input vectors + dtype = xp_result_type(a, b, force_floating=True, xp=xp) + a_original = xp.asarray(a, dtype=dtype) + b_original = xp.asarray(b, dtype=dtype) + a = xpx.atleast_nd(a_original, ndim=2, xp=xp) + b = xpx.atleast_nd(b_original, ndim=2, xp=xp) + if a.shape[-1] != 3: + raise ValueError( + f"Expected input `a` to have shape (3,) or (N, 3), got {a_original.shape}" + ) + if b.shape[-1] != 3: + raise ValueError( + f"Expected input `b` to have shape (3,) or (N, 3), got {b_original.shape}" + ) + if a.shape != b.shape: + raise ValueError( + "Expected inputs `a` and `b` to have same shapes" + f", got {a_original.shape} and {b_original.shape} respectively." + ) + if a.ndim > 2 or b.ndim > 2: # This function does not support broadcasting + raise ValueError( + "Expected inputs `a` and `b` to have shape (3,) or (N, 3), got " + f"{a_original.shape} and {b_original.shape} respectively." + ) + N = a.shape[0] + + # Check weights + if weights is None: + weights = xp.ones(N, device=xp_device(a), dtype=a.dtype) + else: + weights = xp.asarray(weights, device=xp_device(a), dtype=a.dtype) + if weights.ndim != 1: + raise ValueError( + f"Expected `weights` to be 1 dimensional, got shape {weights.shape}." + ) + if N > 1 and (weights.shape[0] != N): + raise ValueError( + "Expected `weights` to have number of values equal to number of input " + f"vectors, got {weights.shape[0]} values and {N} vectors." + ) + # We can only check for negative weights in eager execution models. Lazy + # backends will return NaNs instead. + negative_weights = weights < 0 + if not is_lazy_array(negative_weights) and xp.any(negative_weights): + raise ValueError("`weights` may not contain negative values") + weights = xp.where(negative_weights, xp.nan, weights) + + # For the special case of a single vector pair, we use the infinite + # weight code path + weight_is_inf = xp.asarray([True]) if N == 1 else weights == xp.inf + n_inf = xp.sum(xp.astype(weight_is_inf, a.dtype)) + # We can only error out on multiple infinite weights or sensitivity return with + # infinite weights in eager execution models. Lazy backends will return NaNs. + if not is_lazy_array(n_inf): + if n_inf > 1: + raise ValueError("Only one infinite weight is allowed") + if n_inf == 1 and return_sensitivity: + raise ValueError( + "Cannot return sensitivity matrix with an " + "infinite weight or one vector pair" + ) + + weights = xp.where(n_inf > 1, xp.nan, weights) + + inf_branch = xp.any(weight_is_inf, axis=-1) + # DECISION: We cannot compute both branches for all frameworks. There are two main + # reasons: + # 1. Computing both for eager execution models is expensive. + # 2. Some operations will fail when running the unused branch because of numerical + # and algorithmical issues. Numpy e.g. will raise an exception when trying to + # compute the svd of a matrix with infinite weights. To prevent this, we only + # compute the branch that is needed. Lazy backends however require us to take the + # full compute graph. Therefore, we use xp.where for lazy backends and a branching + # version for eager frameworks. + # + # Note that we could also solve this by exploiting the externals of xpx.apply_where. + # However, we'd have to rely on the implementation details of apply_where, which is + # something we should avoid. + # See https://github.com/scipy/scipy/pull/22777#discussion_r2028868364 + if is_lazy_array(inf_branch): + q_opt, rssd, sensitivity = _align_vectors(a, b, weights) + q_opt_inf, rssd_inf, sensitivity_inf = _align_vectors_fixed(a, b, weights) + q_opt = xp.where(inf_branch, q_opt_inf, q_opt) + rssd = xp.where(inf_branch, rssd_inf, rssd) + sensitivity = xp.where(inf_branch, sensitivity_inf, sensitivity) + else: + if xp.any(inf_branch): + q_opt, rssd, sensitivity = _align_vectors_fixed(a, b, weights) + else: + q_opt, rssd, sensitivity = _align_vectors(a, b, weights) + return q_opt, rssd, sensitivity + + +def _align_vectors(a: Array, b: Array, weights: Array) -> tuple[Array, Array, Array]: + xp = array_namespace(a) + device = xp_device(a) + B = (weights[:, None] * a).mT @ b + u, s, vh = xp.linalg.svd(B) + + # Correct improper rotation if necessary (as in Kabsch algorithm) + neg_det = xp.linalg.det(u @ vh) < 0 + s = xpx.at(s)[..., -1].set(xp.where(neg_det, -s[..., -1], s[..., -1])) + u = xpx.at(u)[..., :, -1].set(xp.where(neg_det, -u[..., :, -1], u[..., :, -1])) + + C = u @ vh + + # DECISION: We cannot branch on the condition because jit code needs to be + # non-branching. Hence, we omit the check for uniqueness + # (s[1] + s[2] < 1e-16 * s[0]) + ssd = xp.sum(weights * xp.sum(b**2 + a**2, axis=-1), axis=-1) - 2 * xp.sum( + s, axis=-1 + ) + rssd = xp.sqrt(xp.maximum(ssd, xp.zeros(1, device=device)))[..., 0] + + # TODO: We currently need to always compute the sensitivity matrix because lazy code + # needs to be non-branching. We should check if compilers can optimize the where + # statement (e.g. in jax) and check if we can have an eager version that only + # evaluates the branch that is needed. + # See xpx.apply_where, issue: https://github.com/data-apis/array-api-extra/pull/141 + zeta = (s[..., 0] + s[..., 1]) * (s[..., 1] + s[..., 2]) * (s[..., 2] + s[..., 0]) + kappa = s[..., 0] * s[..., 1] + s[..., 1] * s[..., 2] + s[..., 2] * s[..., 0] + eye = xp.eye(3, dtype=a.dtype, device=device) + sensitivity = xp.mean(weights) / zeta * (kappa * eye + B @ B.mT) + q_opt = _from_matrix_orthogonal(C) + return q_opt, rssd, sensitivity + + +def _align_vectors_fixed( + a: Array, b: Array, weights: Array +) -> tuple[Array, Array, Array]: + xp = array_namespace(a) + device = xp_device(a) + N = a.shape[0] + weight_is_inf = xp.asarray([True], device=device) if N == 1 else weights == xp.inf + # We cannot use boolean masks for indexing because of jax. For the same reason, we + # also cannot use dynamic slices. As a workaround, we roll the array so that the + # infinitely weighted vector is at index 0. We then use static slices to get the + # primary and secondary vectors. + # + # Note that argmax fulfils a secondary purpose here: + # When we trace this function with jax, this function might get executed even if + # weight_is_inf does not have a single valid entry. Argmax will still give us a + # valid index which allows us to proceed with the function (the results are + # discarded anyways), whereas boolean indexing would result in invalid, zero-shaped + # arrays. + + inf_idx = xp.argmax(xp.astype(weight_is_inf, xp.uint8)) + # xp.argmax returns an Array, but the specification of xp.roll does not support + # Arrays as shifts. For lazy execution models we cannot convert to int because this + # raises a concretization error. However, jax does accept Arrays as shifts which + # allows jit compiling the function. Hence we do not convert to int for jax. + # See https://github.com/data-apis/array-api/issues/914#issuecomment-2918959918 + if not is_jax(xp): + inf_idx = int(inf_idx) + a_sorted = xp.roll(a, shift=-inf_idx, axis=0) + b_sorted = xp.roll(b, shift=-inf_idx, axis=0) + weights_sorted = xp.roll(weights, shift=-inf_idx, axis=0) + + a_pri = a_sorted[0, ...][None, ...] # Effectively [[0], ...] + b_pri = b_sorted[0, ...][None, ...] + a_pri_norm = xp_vector_norm(a_pri, axis=-1, keepdims=True, xp=xp) + b_pri_norm = xp_vector_norm(b_pri, axis=-1, keepdims=True, xp=xp) + if not is_lazy_array(a_pri_norm): + if xp.any(a_pri_norm == 0) or xp.any(b_pri_norm == 0): + raise ValueError("Cannot align zero length primary vectors") + + # We cannot error out on zero length vectors. We set the norm to NaN to avoid + # division by zero and mark the result as invalid. + a_pri_norm = xpx.at(a_pri_norm)[a_pri_norm == 0].set(xp.nan) + b_pri_norm = xpx.at(b_pri_norm)[b_pri_norm == 0].set(xp.nan) + + a_pri = a_pri / a_pri_norm + b_pri = b_pri / b_pri_norm + + # We first find the minimum angle rotation between the primary + # vectors. + cross = xp.linalg.cross(b_pri[..., 0, :], a_pri[..., 0, :]) + cross_norm = xp_vector_norm(cross, axis=-1, xp=xp) + theta = xp.atan2(cross_norm, xp.sum(a_pri[..., 0, :] * b_pri[..., 0, :], axis=-1)) + tolerance = 1e-3 # tolerance for small angle approximation (rad) + q_flip = xp.asarray([0.0, 0.0, 0.0, 1.0], dtype=a_pri.dtype, device=device) + + # Near pi radians, the Taylor series approximation of x/sin(x) diverges, so for + # numerical stability we flip pi and then rotate back by the small angle pi - theta + flip = xp.pi - theta < tolerance + # For antiparallel vectors, cross = [0, 0, 0] so we need to manually set an + # arbitrary orthogonal axis of rotation + i = xp.argmin(xp.abs(a_pri[..., 0, :])) + # TODO: Array API does not support fancy indexing __setitem__. The code is + # equivalent to doing the following: + # r_components = xp.zeros(3) + # r_components = xpx.at(r_components)[i - 1].set(a_pri[0, i - 2]) + # r_components = xpx.at(r_components)[i - 2].set(-a_pri[0, i - 1]) + r_components = xp.stack( + [ + xp.where(i == 1, a_pri[0, 2], xp.where(i == 2, -a_pri[0, 1], 0)), + xp.where(i == 2, a_pri[0, 0], xp.where(i == 0, -a_pri[0, 2], 0)), + xp.where(i == 0, a_pri[0, 1], xp.where(i == 1, -a_pri[0, 0], 0)), + ] + ) + r = xp.where(cross_norm == 0, r_components, cross) + + q_flip = xp.where(flip, from_rotvec(r / xp_vector_norm(r, xp=xp) * xp.pi), q_flip) + theta = xp.where(flip, xp.pi - theta, theta) + cross = xp.where(flip, -cross, cross) + + # Small angle Taylor series approximation for numerical stability + theta2 = theta * theta + small_scale = xp.abs(theta) < tolerance + r_small_scale = cross * (1 + theta2 / 6 + theta2 * theta2 * 7 / 360) + # We need to handle the case where theta is 0 to avoid division by zero. We use the + # value of the Taylor series approximation, but non-branching operations require + # that we still divide by the angle. Since we do not use the result where the angle + # is close to 0, this is safe. + theta = theta + xp.asarray(small_scale, dtype=theta.dtype) + r_large_scale = cross * theta / xp.sin(theta) + r = xp.where(small_scale, r_small_scale, r_large_scale) + q_pri = compose_quat(from_rotvec(r), q_flip) + + # Case 1): No secondary vectors, q_opt is q_pri -> Immediately done + # Case 2): Secondary vectors exist + # We cannot use boolean masks here because of jax + a_sec = a_sorted[1:, ...] + b_sec = b_sorted[1:, ...] + weights_sec = weights_sorted[1:] + + # We apply the first rotation to the b vectors to align the + # primary vectors, resulting in vectors c. The secondary + # vectors must now be rotated about that primary vector to best + # align c to a. + c_sec = apply(q_pri, b_sec) + + # Calculate vector components for the angle calculation. The + # angle phi to rotate a single 2D vector C to align to 2D + # vector A in the xy plane can be found with the equation + # phi = atan2(cross(C, A), dot(C, A)) + # = atan2(|C|*|A|*sin(phi), |C|*|A|*cos(phi)) + # The below equations perform the same operation, but with the + # 3D rotation restricted to the 2D plane normal to a_pri, where + # the secondary vectors are projected into that plane. We then + # find the composite angle from the weighted sum of the + # axial components in that plane, minimizing the 2D alignment + # problem. + sin_term = xp.linalg.vecdot(xp.linalg.cross(c_sec, a_sec), a_pri) + cos_term = xp.linalg.vecdot(c_sec, a_sec) - ( + xp.linalg.vecdot(c_sec, a_pri) * xp.linalg.vecdot(a_sec, a_pri) + ) + phi = xp.atan2(xp.sum(weights_sec * sin_term), xp.sum(weights_sec * cos_term)) + q_sec = from_rotvec(phi * a_pri[0, ...]) + + # Compose these to get the optimal rotation + q_opt = q_pri if N == 1 else compose_quat(q_sec, q_pri) + + # Calculate the root sum squared distance. We force the error to + # be zero for the infinite weight vectors since they will align + # exactly. + mask = ((N > 1) | (weights[0] == xp.inf)) & weight_is_inf + # Skip non-infinite weight single vectors pairs, we used the + # infinite weight code path but don't want to zero that weight + weights_inf_zero = xpx.at(weights, mask).set(0, copy=True, xp=xp) + a_est = apply(q_opt, b) + rssd = xp.sqrt(xp.sum(weights_inf_zero @ (a - a_est) ** 2)) + + mask = xp.any(xp.isnan(weights), axis=-1) + q_opt = xp.where(mask, xp.nan, q_opt) + return q_opt, rssd, xp.asarray(xp.nan, device=device) + + +def pow(quat: Array, n: float | Array) -> Array: + xp = array_namespace(quat) + device = xp_device(quat) + # If n is an array, we sanitize it to a scalar and promote quat and n to + # the same dtype. + if is_array_api_obj(n): + if n.shape == (1,): # pyrefly:ignore[missing-attribute] + n = n[0] # pyrefly:ignore[bad-index] + elif n.ndim != 0: # pyrefly:ignore[missing-attribute] + raise ValueError("Array exponent must be a scalar") + quat, n = xp_promote(quat, n, force_floating=True, xp=xp) + + # If n is a lazy array, we cannot take fast paths for special cases. + if is_lazy_array(n): + result = from_rotvec(n * as_rotvec(quat)) # general scaling of rotation angle + # Special cases 0 -> identity, -1 -> inv, 1 -> copy + identity = xp.zeros((*quat.shape[:-1], 4), dtype=quat.dtype, device=device) + identity = xpx.at(identity)[..., 3].set(1) + result = xp.where(n == 0, identity, result) + result = xp.where(n == -1, inv(quat), result) + result = xp.where(n == 1, quat, result) + return result + if n == 0: + identity = xp.zeros((*quat.shape[:-1], 4), dtype=quat.dtype, device=device) + return xpx.at(identity)[..., 3].set(1) + if n == -1: + return inv(quat) + if n == 1: + return quat + return from_rotvec(n * as_rotvec(quat)) + + +def _normalize_quaternion(quat: Array) -> Array: + xp = array_namespace(quat) + quat_norm = xp_vector_norm(quat, axis=-1, keepdims=True, xp=xp) + zero_norm = quat_norm == 0 + if is_lazy_array(quat_norm): + quat = xp.where(zero_norm, xp.nan, quat) + elif xp.any(zero_norm): + raise ValueError("Found zero norm quaternions in `quat`.") + return quat / quat_norm + + +def _quat_canonical(quat: Array) -> Array: + xp = array_namespace(quat) + mask = quat[..., 3] < 0 + zero_w = quat[..., 3] == 0 + mask = mask | (zero_w & (quat[..., 0] < 0)) + zero_wx = zero_w & (quat[..., 0] == 0) + mask = mask | (zero_wx & (quat[..., 1] < 0)) + zero_wxy = zero_wx & (quat[..., 1] == 0) + mask = mask | (zero_wxy & (quat[..., 2] < 0)) + return xp.where(mask[..., None], -quat, quat) + + +def _elementary_basis_index(axis: str) -> int: + if axis == "x": + return 0 + elif axis == "y": + return 1 + elif axis == "z": + return 2 + raise ValueError(f"Expected axis to be from ['x', 'y', 'z'], got {axis}") + + +def _compute_davenport_from_quat( + quat: Array, + n1: Array, + n2: Array, + n3: Array, + extrinsic: bool, + suppress_warnings: bool, +) -> Array: + # The algorithm assumes extrinsic frame transformations. The algorithm + # in the paper is formulated for rotation quaternions, which are stored + # directly by Rotation. + # Adapt the algorithm for our case by reversing both axis sequence and + # angles for intrinsic rotations when needed + xp = array_namespace(quat) + n1, n3 = (n1, n3) if extrinsic else (n3, n1) + + n_cross = xp.linalg.cross(n1, n2) + lamb = xp.atan2(xp.vecdot(n3, n_cross), xp.vecdot(n3, n1)) + + # alternative set of angles compatible with as_euler implementation + mask = lamb < 0 + n2 = xp.where(mask[..., None], -n2, n2) + lamb = xp.where(mask, -lamb, lamb) + n_cross = xp.where(mask[..., None], -n_cross, n_cross) + correct_set = mask + + quat_lamb = xp.concat( + (xp.sin(lamb / 2)[..., None] * n2, xp.cos(lamb / 2)[..., None]), axis=-1 + ) + + q_trans = compose_quat(quat_lamb, quat) + a = q_trans[..., 3] + b = xp.linalg.vecdot(q_trans[..., :3], n1) + c = xp.linalg.vecdot(q_trans[..., :3], n2) + d = xp.linalg.vecdot(q_trans[..., :3], n_cross) + + angles = _get_angles(extrinsic, False, 1, lamb, a, b, c, d, suppress_warnings) + angles = xpx.at(angles)[..., 1].set( + xp.where(correct_set, -angles[..., 1], angles[..., 1]) + ) + + return angles + + +def _elementary_quat_compose(axes: list[int], angles: Array, intrinsic: bool) -> Array: + xp = array_namespace(angles) + device = xp_device(angles) + quat = _make_elementary_quat(axes[0], angles[..., 0], device=device, xp=xp) + for i in range(1, len(axes)): + ax_quat = _make_elementary_quat(axes[i], angles[..., i], device=device, xp=xp) + quat = compose_quat(quat, ax_quat) if intrinsic else compose_quat(ax_quat, quat) + return quat + + +def _make_elementary_quat(axis: int, angle: Array, device, xp) -> Array: + quat = xp.zeros((*angle.shape, 4), dtype=angle.dtype, device=device) + quat = xpx.at(quat)[..., 3].set(xp.cos(angle / 2.0)) + quat = xpx.at(quat)[..., axis].set(xp.sin(angle / 2.0)) + return quat + + +def _get_angles( + extrinsic: bool, + symmetric: bool, + sign: int, + lamb: float, + a: Array, + b: Array, + c: Array, + d: Array, + suppress_warnings: bool, +) -> Array: + xp = array_namespace(a) + device = xp_device(a) + eps = 1e-7 + half_sum = xp.atan2(b, a) + half_diff = xp.atan2(d, c) + # We zero-initialize to automatically cover singular cases where the second angle is + # not defined uniquely. + angles = xp.zeros((*a.shape, 3), dtype=a.dtype, device=device) + + angles = xpx.at(angles)[..., 1].set(2 * xp.atan2(xp.hypot(c, d), xp.hypot(a, b))) + + angle_first = 0 if extrinsic else 2 + angle_third = 2 if extrinsic else 0 + + # Check if the second angle is close to 0 or pi, causing a singularity. + # - Case 0: Second angle is neither close to 0 nor pi. + # - Case 1: Second angle is close to 0. + # - Case 2: Second angle is close to pi. + case1 = xp.abs(angles[..., 1]) <= eps + case2 = xp.abs(angles[..., 1] - xp.pi) <= eps + case0 = ~(case1 | case2) + if not suppress_warnings and not is_lazy_array(case0) and xp.any(~case0): + warnings.warn( + "Gimbal lock detected. Setting third angle to zero " + "since it is not possible to uniquely determine " + "all angles.", + stacklevel=3, + ) + + # This writes case1 into a0 where True and case2 everywhere else. This is sound + # because we later overwrite any values without singularity with the regular value + # of case0. The second angle is covered by default since we zero-initialized the + # second dimension. + a0 = xp.where(case1, 2 * half_sum, 2 * half_diff * (-1 if extrinsic else 1)) + angles = xpx.at(angles)[..., 0].set(a0) + + # We overwrite the values of angles without singularities (case0) + a1 = xp.where(case0, half_sum - half_diff, angles[..., angle_first]) + angles = xpx.at(angles)[..., angle_first].set(a1) + + # Same as above but for the third angle. We overwrite the non-singular case0 values + a3 = xp.where(case0, half_sum + half_diff, angles[..., angle_third]) + if not symmetric: + a3 = a3 * sign + angles = xpx.at(angles)[..., 1].set(angles[..., 1] - lamb) + angles = xpx.at(angles)[..., angle_third].set(a3) + + angles = (angles + xp.pi) % (2 * xp.pi) - xp.pi + return angles + + +def compose_quat(p: Array, q: Array) -> Array: + xp = array_namespace(p) + cross = xp.linalg.cross(p[..., :3], q[..., :3]) + qx = p[..., 3] * q[..., 0] + q[..., 3] * p[..., 0] + cross[..., 0] + qy = p[..., 3] * q[..., 1] + q[..., 3] * p[..., 1] + cross[..., 1] + qz = p[..., 3] * q[..., 2] + q[..., 3] * p[..., 2] + cross[..., 2] + qw = ( + p[..., 3] * q[..., 3] + - p[..., 0] * q[..., 0] + - p[..., 1] * q[..., 1] + - p[..., 2] * q[..., 2] + ) + quat = xp.stack([qx, qy, qz, qw], axis=-1) + return quat + + +def _split_rotation(q: Array, xp) -> tuple[Array, Array]: + q = xpx.atleast_nd(q, ndim=2, xp=xp) + return q[..., -1], q[..., :-1] + + +def _deg2rad(angles: Array) -> Array: + return angles * (np.pi / 180.0) + + +def _rad2deg(angles: Array) -> Array: + return angles * (180.0 / np.pi) diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/transform/rotation.py b/venv/lib/python3.12/site-packages/scipy/spatial/transform/rotation.py new file mode 100644 index 0000000000000000000000000000000000000000..a719437415124f4afc842b98c708836c1fe68f22 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/transform/rotation.py @@ -0,0 +1,21 @@ +# This file is not meant for public use and will be removed in SciPy v2.0.0. +# Use the `scipy.spatial` namespace for importing the functions +# included below. + +from scipy._lib.deprecation import _sub_module_deprecation + + +__all__ = [ # noqa: F822 + 'Rotation', + 'Slerp', +] + + +def __dir__(): + return __all__ + + +def __getattr__(name): + return _sub_module_deprecation(sub_package="spatial.transform", module="rotation", + private_modules=["_rotation"], all=__all__, + attribute=name) diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/transform/tests/__init__.py b/venv/lib/python3.12/site-packages/scipy/spatial/transform/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/transform/tests/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/spatial/transform/tests/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6402ba71c2db9709c94c35835d906d028f0d615c Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/spatial/transform/tests/__pycache__/__init__.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/transform/tests/__pycache__/test_rigid_transform.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/spatial/transform/tests/__pycache__/test_rigid_transform.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b6f6d61cf694b1cd1b18e8f026e2f79c2d7a145c Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/spatial/transform/tests/__pycache__/test_rigid_transform.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/transform/tests/__pycache__/test_rotation.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/spatial/transform/tests/__pycache__/test_rotation.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dc8fcaec8997fb57ff8db4f92fda6b0286d33174 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/transform/tests/__pycache__/test_rotation.cpython-312.pyc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ff8cc6f9ff3f01693846e18385e4bbd6ceaa0d76b42181cdb02ab68cfee7002c +size 200207 diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/transform/tests/__pycache__/test_rotation_groups.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/spatial/transform/tests/__pycache__/test_rotation_groups.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..733d34ae48de8df0e12c3465b966a03049d5b88a Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/spatial/transform/tests/__pycache__/test_rotation_groups.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/transform/tests/__pycache__/test_rotation_spline.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/spatial/transform/tests/__pycache__/test_rotation_spline.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..988ceb21aedb46cbb93981a29b03a07ca9fcec3d Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/spatial/transform/tests/__pycache__/test_rotation_spline.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/transform/tests/test_rigid_transform.py b/venv/lib/python3.12/site-packages/scipy/spatial/transform/tests/test_rigid_transform.py new file mode 100644 index 0000000000000000000000000000000000000000..23d38c12987415a366533f160910ab3006031564 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/transform/tests/test_rigid_transform.py @@ -0,0 +1,1531 @@ +import pickle +from itertools import product + +import pytest + +import numpy as np +from scipy.spatial.transform import Rotation, RigidTransform +from scipy.spatial.transform._rigid_transform import normalize_dual_quaternion +from scipy._lib._array_api import ( + is_lazy_array, + xp_vector_norm, + is_numpy, + xp_assert_close, + make_xp_test_case, + xp_assert_equal, + xp_promote +) +import scipy._external.array_api_extra as xpx + + +def rotation_to_xp(r: Rotation, xp): + dtype = xpx.default_dtype(xp) + return Rotation.from_quat(xp.asarray(r.as_quat(), dtype=dtype)) + + +def rigid_transform_to_xp(r: RigidTransform, xp): + dtype = xpx.default_dtype(xp) + return RigidTransform.from_matrix(xp.asarray(r.as_matrix(), dtype=dtype)) + + +@make_xp_test_case((RigidTransform, "as_matrix")) +def test_repr(xp): + actual = repr(RigidTransform.from_matrix(xp.eye(4))) + expected = """\ +RigidTransform.from_matrix(array([[1., 0., 0., 0.], + [0., 1., 0., 0.], + [0., 0., 1., 0.], + [0., 0., 0., 1.]]))""" + if is_numpy(xp): + assert actual == expected + else: + assert actual.startswith("RigidTransform.from_matrix(") + + tf = RigidTransform.from_matrix(xp.asarray(RigidTransform.identity(2).as_matrix())) + actual = repr(tf) + expected = """\ +RigidTransform.from_matrix(array([[[1., 0., 0., 0.], + [0., 1., 0., 0.], + [0., 0., 1., 0.], + [0., 0., 0., 1.]], + + [[1., 0., 0., 0.], + [0., 1., 0., 0.], + [0., 0., 1., 0.], + [0., 0., 0., 1.]]]))""" + if is_numpy(xp): + assert actual == expected + else: + assert actual.startswith("RigidTransform.from_matrix(") + + +@make_xp_test_case((RigidTransform, "from_rotation")) +@pytest.mark.parametrize("ndim", range(1, 4)) +def test_from_rotation(xp, ndim: int): + atol = 1e-12 + rng = np.random.default_rng(0) + shape = (ndim,) * (ndim - 1) + (4,) + r = rotation_to_xp(Rotation.from_quat(rng.normal(size=shape)), xp=xp) + tf = RigidTransform.from_rotation(r) + xp_assert_close(tf.as_matrix()[..., :3, :3], r.as_matrix(), atol=atol) + xp_assert_close(tf.as_matrix()[..., :3, 3], xp.zeros(shape[:-1] + (3,)), atol=atol) + xp_assert_close(tf.as_matrix()[..., 3, :3], xp.zeros(shape[:-1] + (3,)), atol=atol) + xp_assert_close(tf.as_matrix()[..., 3, 3], xp.ones(shape[:-1]), atol=atol) + assert tf.single == (ndim == 1) + + +@make_xp_test_case((RigidTransform, "from_translation")) +@pytest.mark.parametrize("ndim", range(1, 4)) +def test_from_translation(xp, ndim: int): + shape = (ndim,) * (ndim - 1) + t = xp.reshape(xp.arange(ndim ** (ndim-1) * 3), shape + (3,)) + tf = RigidTransform.from_translation(t) + + expected = xp.tile(xp.eye(4), shape + (1, 1)) + t_float = xp_promote(t, force_floating=True, xp=xp) + expected = xpx.at(expected)[..., :3, 3].set(t_float) + xp_assert_close(tf.as_matrix(), expected) + assert tf.single == (ndim == 1) + + +def test_from_translation_array_like(): + # Test single translation + t = [1, 2, 3] + tf = RigidTransform.from_translation(t) + tf_expected = RigidTransform.from_translation(np.array(t)) + xp_assert_close(tf.as_matrix(), tf_expected.as_matrix()) + assert tf.single + + # Test multiple translations + t = [[1, 2, 3], [4, 5, 6]] + tf = RigidTransform.from_translation(t) + tf_expected = RigidTransform.from_translation(np.array(t)) + xp_assert_close(tf.as_matrix(), tf_expected.as_matrix()) + assert not tf.single + + +@make_xp_test_case((RigidTransform, "from_matrix"), (RigidTransform, "as_matrix")) +@pytest.mark.parametrize("ndim", range(1, 4)) +def test_from_matrix(xp, ndim: int): + atol = 1e-12 + shape = (ndim,) * (ndim - 1) + (4, 4) + dtype = xpx.default_dtype(xp) + + matrix = xp.tile(xp.eye(4), shape[:-2] + (1, 1)) + t = xp.reshape(xp.arange(ndim ** (ndim-1) * 3, dtype=dtype), shape[:-2] + (3,)) + matrix = xpx.at(matrix)[..., :3, 3].set(t) + + tf = RigidTransform.from_matrix(matrix) + xp_assert_close(tf.as_matrix(), matrix, atol=atol) + assert tf.single == (ndim == 1) + + # Test non-1 determinant + matrix = xp.tile(xp.eye(4), shape[:-2] + (1, 1)) + matrix = xpx.at(matrix)[..., :3, :3].set(xp.eye(3) * 2.0) + tf = RigidTransform.from_matrix(matrix) + expected = xp.tile(xp.eye(4), shape[:-2] + (1, 1)) + xp_assert_close(tf.as_matrix(), expected, atol=atol) + + # Test non-orthogonal rotation matrix + matrix = xp.tile(xp.eye(4), shape[:-2] + (1, 1)) + # matrix is equivalent to [[1, 1, 0, 0], + # [0, 1, 0, 0], + # [0, 0, 1, 0], + # [0, 0, 0, 1]] + matrix = xpx.at(matrix)[..., 0, 1].set(1.0) + tf = RigidTransform.from_matrix(matrix) + expected = xp.tile(xp.eye(4), shape[:-2] + (1, 1)) + expected = xpx.at(expected)[..., 0, 0].set(0.894427) + expected = xpx.at(expected)[..., 0, 1].set(0.447214) + expected = xpx.at(expected)[..., 1, 0].set(-0.447214) + expected = xpx.at(expected)[..., 1, 1].set(0.894427) + xp_assert_close(tf.as_matrix(), expected, atol=1e-6) + + # Test invalid matrix + invalid = xp.tile(xp.eye(4), shape[:-2] + (1, 1)) + invalid = xpx.at(invalid)[..., 3, 3].set(2) # Invalid last row + if is_lazy_array(invalid): + tf = RigidTransform.from_matrix(invalid) + assert xp.all(xp.isnan(tf.as_matrix())) + else: + with pytest.raises(ValueError): + RigidTransform.from_matrix(invalid) + + +def test_from_matrix_array_like(): + # Test single transform matrix + matrix = [[1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1]] + expected = np.eye(4) + tf = RigidTransform.from_matrix(matrix) + xp_assert_close(tf.as_matrix(), expected) + assert tf.single + + # Test multiple transform matrices + matrices = [matrix, matrix] + tf = RigidTransform.from_matrix(matrices) + for i in range(len(matrices)): + xp_assert_close(tf.as_matrix()[i, ...], expected) + assert not tf.single + + +@make_xp_test_case((RigidTransform, "from_components")) +@pytest.mark.parametrize("r_ndim", range(1, 3)) +@pytest.mark.parametrize("t_ndim", range(1, 3)) +def test_from_components(xp, r_ndim: int, t_ndim: int): + atol = 1e-12 + dims = (6, 5, 4, 3) # Common shape + q_shape = dims[:r_ndim - 1][::-1] + (4,) + t_shape = dims[:t_ndim - 1][::-1] + (3,) + tf_shape = np.broadcast_shapes(q_shape[:-1], t_shape[:-1]) + (4, 4) + rng = np.random.default_rng(0) + + t = xp.reshape(xp.arange(np.prod(t_shape[:-1]) * 3), t_shape) + r = rotation_to_xp(Rotation.from_quat(rng.random(size=q_shape)), xp=xp) + tf = RigidTransform.from_components(t, r) + + expected = xp.zeros(tf_shape) + expected = xpx.at(expected)[..., :3, :3].set(r.as_matrix()) + t_float = xp_promote(t, force_floating=True, xp=xp) + expected = xpx.at(expected)[..., :3, 3].set(t_float) + expected = xpx.at(expected)[..., 3, 3].set(1) + xp_assert_close(tf.as_matrix(), expected, atol=atol) + assert tf.single == (r_ndim == 1 and t_ndim == 1) + + +def test_from_components_array_like(): + rng = np.random.default_rng(123) + # Test single rotation and translation + t = [1, 2, 3] + r = Rotation.random(rng=rng) + tf = RigidTransform.from_components(t, r) + tf_expected = RigidTransform.from_components(np.array(t), r) + xp_assert_close(tf.as_matrix(), tf_expected.as_matrix(), atol=1e-12) + assert tf.single + + # Test multiple rotations and translations + t = [[1, 2, 3], [4, 5, 6]] + r = Rotation.random(len(t), rng=rng) + tf = RigidTransform.from_components(t, r) + tf_expected = RigidTransform.from_components(np.array(t), r) + xp_assert_close(tf.as_matrix(), tf_expected.as_matrix(), atol=1e-12) + assert not tf.single + + +@make_xp_test_case((RigidTransform, "as_components")) +@pytest.mark.parametrize("ndim", range(1, 4)) +def test_as_components(xp, ndim): + dtype = xpx.default_dtype(xp) + atol = 1e-12 if dtype == xp.float64 else 1e-6 + shape = (ndim,) * (ndim - 1) + rng = np.random.default_rng(123) + t = xp.asarray(rng.normal(size=shape + (3,)), dtype=dtype) + r = rotation_to_xp(Rotation.from_quat(rng.random(shape + (4,))), xp=xp) + tf = RigidTransform.from_components(t, r) + new_t, new_r = tf.as_components() + assert xp.all(new_r.approx_equal(r, atol=atol)) + xp_assert_close(new_t, t, atol=atol) + + +@make_xp_test_case((RigidTransform, "from_exp_coords")) +@pytest.mark.parametrize("dim", range(1, 4)) +def test_from_exp_coords(xp, dim: int): + shape = (dim,) * (dim - 1) + # example from 3.3 of + # https://hades.mech.northwestern.edu/images/2/25/MR-v2.pdf + dtype = xpx.default_dtype(xp) + angle1 = np.deg2rad(30.0) + mat = xp.asarray([ + [np.cos(angle1), -np.sin(angle1), 0.0, 1.0], + [np.sin(angle1), np.cos(angle1), 0.0, 2.0], + [0.0, 0.0, 1.0, 0.0], + [0.0, 0.0, 0.0, 1.0] + ], dtype=dtype) + mat = xp.tile(mat, shape + (1, 1)) + tf1 = RigidTransform.from_matrix(mat) + angle2 = np.deg2rad(60.0) + mat = xp.asarray([ + [np.cos(angle2), -np.sin(angle2), 0.0, 2.0], + [np.sin(angle2), np.cos(angle2), 0.0, 1.0], + [0.0, 0.0, 1.0, 0.0], + [0.0, 0.0, 0.0, 1.0] + ], dtype=dtype) + mat = xp.tile(mat, shape + (1, 1)) + tf2 = RigidTransform.from_matrix(mat) + expected = tf2 * tf1.inv() + deg2rag = xp.asarray(np.deg2rad(30.0)) + exp_coords = deg2rag * xp.asarray([0.0, 0.0, 1.0, 3.37, -3.37, 0.0]) + exp_coords = xp.tile(exp_coords, shape + (1,)) + actual = RigidTransform.from_exp_coords(exp_coords) + xp_assert_close(actual.as_matrix(), expected.as_matrix(), atol=1e-2) + + # test cases generated by comparison to pytransform3d + exp_coords = xp.asarray([ + [-2.01041204, -0.52983629, 0.65773501, + 0.10386614, 0.05855009, 0.54959179], + [-0.22537438, -0.24132627, -2.4747121, + -0.09158594, 1.88075832, -0.03197204] + ]) + exp_coords = xp.tile(exp_coords, shape + (1,)) + expected_matrix = xp.asarray([ + [[0.76406621, 0.10504613, -0.63652819, -0.10209961], + [0.59956454, -0.47987325, 0.64050295, 0.40158789], + [-0.2381705, -0.87102639, -0.42963687, 0.19637636], + [0., 0., 0., 1.]], + [[-0.78446989, 0.61157488, 0.10287448, 1.33330055], + [-0.58017785, -0.78232107, 0.22664378, 0.52660831], + [0.21909052, 0.11810973, 0.96852952, -0.02968529], + [0., 0., 0., 1.]] + ]) + expected_matrix = xp.tile(expected_matrix, shape + (1, 1)) + xp_assert_close( + RigidTransform.from_exp_coords(exp_coords).as_matrix(), + expected_matrix, atol=1e-8) + + # identity + expected_matrix = xp.tile(xp.eye(4), shape + (1, 1)) + exp_coords = xp.zeros(shape + (6,), dtype=dtype) + xp_assert_close( + RigidTransform.from_exp_coords(exp_coords).as_matrix(), + expected_matrix, atol=1e-12) + + # only translation + expected_matrix = xp.asarray([ + [[1.0, 0.0, 0.0, 3.0], + [0.0, 1.0, 0.0, -5.4], + [0.0, 0.0, 1.0, 100.2], + [0.0, 0.0, 0.0, 1.0]], + [[1.0, 0.0, 0.0, -3.0], + [0.0, 1.0, 0.0, 13.3], + [0.0, 0.0, 1.0, 1.3], + [0.0, 0.0, 0.0, 1.0]] + ]) + expected_matrix = xp.tile(expected_matrix, shape + (1, 1, 1)) + exp_coords = xp.asarray([ + [0.0, 0.0, 0.0, 3.0, -5.4, 100.2], + [0.0, 0.0, 0.0, -3.0, 13.3, 1.3], + ]) + exp_coords = xp.tile(exp_coords, shape + (1, 1)) + actual = RigidTransform.from_exp_coords(exp_coords) + xp_assert_close(actual.as_matrix(), expected_matrix, atol=1e-12) + + # only rotation + angles = xp.asarray([[34, -12, 0.5], [-102, -55, 30]]) + angles = xp.tile(angles, shape + (1, 1)) + rot = Rotation.from_euler('zyx', angles, degrees=True) + rotvec = rot.as_rotvec() + expected_matrix = xp.tile(xp.eye(4), shape + (2, 1, 1)) + expected_matrix = xpx.at(expected_matrix)[..., :3, :3].set(rot.as_matrix()) + exp_coords = xp.concat((rotvec, xp.zeros_like(rotvec)), axis=-1) + actual = RigidTransform.from_exp_coords(exp_coords) + xp_assert_close(actual.as_matrix(), expected_matrix, atol=1e-12) + + +def test_from_exp_coords_array_like(): + rng = np.random.default_rng(123) + # Test single transform + t = np.array([1, 2, 3]) + r = Rotation.random(rng=rng) + tf_expected = RigidTransform.from_components(t, r) + exp_coords = tf_expected.as_exp_coords().tolist() + assert isinstance(exp_coords, list) + tf = RigidTransform.from_exp_coords(exp_coords) + xp_assert_close(tf.as_matrix(), tf_expected.as_matrix(), atol=1e-12) + + # Test multiple transforms + t = [[1, 2, 3], [4, 5, 6]] + r = Rotation.random(len(t), rng=rng) + tf_expected = RigidTransform.from_components(t, r) + exp_coords = tf_expected.as_exp_coords().tolist() + assert isinstance(exp_coords, list) + tf = RigidTransform.from_exp_coords(exp_coords) + xp_assert_close(tf.as_matrix(), tf_expected.as_matrix(), atol=1e-12) + + +@make_xp_test_case((RigidTransform, "as_exp_coords")) +@pytest.mark.parametrize("ndim", range(1, 4)) +def test_as_exp_coords(xp, ndim: int): + shape = (ndim,) * (ndim - 1) + # identity + expected = xp.zeros(shape + (6,)) + actual = RigidTransform.from_exp_coords(expected).as_exp_coords() + xp_assert_close(actual, expected, atol=1e-12) + + rng = np.random.default_rng(10) + + # pure rotation + rot_vec = xp.asarray(rng.normal(scale=0.1, size=shape + (1000, 3))) + tf = RigidTransform.from_rotation(Rotation.from_rotvec(rot_vec)) + exp_coords = tf.as_exp_coords() + xp_assert_close(exp_coords[..., :3], rot_vec, atol=1e-12) + expected = xp.zeros_like(rot_vec) + xp_assert_close(exp_coords[..., 3:], expected, atol=1e-16) + + # pure translation + translation = xp.asarray(rng.normal(scale=100.0, size=shape + (1000, 3))) + tf = RigidTransform.from_translation(translation) + exp_coords = tf.as_exp_coords() + xp_assert_close(exp_coords[..., :3], expected, atol=1e-16) + xp_assert_close(exp_coords[..., 3:], translation, atol=1e-15) + + +@make_xp_test_case((RigidTransform, "from_dual_quat")) +@pytest.mark.parametrize("ndim", range(1, 4)) +def test_from_dual_quat(xp, ndim: int): + dtype = xpx.default_dtype(xp) + atol = 1e-12 if dtype == xp.float64 else 1e-7 + shape = (ndim,) * (ndim - 1) + + # identity + dq = xp.asarray([0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0], dtype=dtype) + dq = xp.tile(dq, shape + (1,)) + expected = xp.tile(xp.eye(4), shape + (1, 1)) + xp_assert_close(RigidTransform.from_dual_quat(dq).as_matrix(), expected, atol=atol) + dq = xp.asarray([1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], dtype=dtype) + dq = xp.tile(dq, shape + (1,)) + xp_assert_close(RigidTransform.from_dual_quat(dq, scalar_first=True).as_matrix(), + expected, atol=atol) + + # only translation + dq = xp.asarray([0, 0, 0, 1, 0.25, 0.15, -0.7, 0], dtype=dtype) + dq = xp.tile(dq, shape + (1,)) + actual = RigidTransform.from_dual_quat(dq) + expected_matrix = xp.asarray([ + [1, 0, 0, 0.5], + [0, 1, 0, 0.3], + [0, 0, 1, -1.4], + [0, 0, 0, 1] + ]) + expected_matrix = xp.tile(expected_matrix, shape + (1, 1)) + xp_assert_close(actual.as_matrix(), expected_matrix, atol=atol) + dq = xp.asarray([1, 0, 0, 0, 0, 0.25, 0.15, -0.7], dtype=dtype) + dq = xp.tile(dq, shape + (1,)) + actual = RigidTransform.from_dual_quat(dq, scalar_first=True) + xp_assert_close(actual.as_matrix(), expected_matrix, atol=atol) + + # only rotation + angles = xp.asarray([65, -13, 90], dtype=dtype) + angles = xp.tile(angles, shape + (1,)) + actual_rot = Rotation.from_euler("xyz", angles, degrees=True) + qrot = actual_rot.as_quat() + dq = xp.concat((qrot, xp.zeros_like(qrot)), axis=-1) + actual = RigidTransform.from_dual_quat(dq) + expected_matrix = xp.tile(xp.eye(4), shape + (1, 1)) + expected_matrix = xpx.at(expected_matrix)[..., :3, :3].set(actual_rot.as_matrix()) + xp_assert_close(actual.as_matrix(), expected_matrix, atol=atol) + + qrot = actual_rot.as_quat(scalar_first=True) + dq = xp.concat((qrot, xp.zeros_like(qrot)), axis=-1) + actual = RigidTransform.from_dual_quat(dq, scalar_first=True) + expected_matrix = xp.tile(xp.eye(4), shape + (1, 1)) + expected_matrix = xpx.at(expected_matrix)[..., :3, :3].set(actual_rot.as_matrix()) + xp_assert_close(actual.as_matrix(), expected_matrix, atol=atol) + + # rotation and translation + # rtol is set to 1e-7 because xp_assert_close deviates from + # np.testing.assert_allclose in that it does not automatically default to 1e-7 for + # floating point inputs. + # See https://numpy.org/doc/2.2/reference/generated/numpy.testing.assert_allclose.html + dq = xp.asarray( + [[0.0617101, -0.06483886, 0.31432811, 0.94508498, + 0.04985168, -0.26119618, 0.1691491, -0.07743254], + [0.19507259, 0.49404931, -0.06091285, 0.8450749, + 0.65049656, -0.30782513, 0.16566752, 0.04174544]]) + dq = xp.tile(dq, shape + (1, 1)) + actual = RigidTransform.from_dual_quat(dq) + expected_matrix = xp.asarray( + [[[0.79398752, -0.60213598, -0.08376202, 0.24605262], + [0.58613113, 0.79477941, -0.15740392, -0.4932833], + [0.16135089, 0.07588122, 0.98397557, 0.34262676], + [0., 0., 0., 1.]], + [[0.50440981, 0.2957028, 0.81125249, 1.20934468], + [0.08979911, 0.91647262, -0.3898898, -0.70540077], + [-0.8587822, 0.26951399, 0.43572393, -0.47776265], + [0., 0., 0., 1.]]]) + expected_matrix = xp.tile(expected_matrix, shape + (1, 1, 1)) + xp_assert_close(actual.as_matrix(), expected_matrix, atol=atol, rtol=1e-7) + + dq = xp.asarray( + [[0.94508498, 0.0617101, -0.06483886, 0.31432811, + -0.07743254, 0.04985168, -0.26119618, 0.1691491], + [0.8450749, 0.19507259, 0.49404931, -0.06091285, + 0.04174544, 0.65049656, -0.30782513, 0.16566752]]) + dq = xp.tile(dq, shape + (1, 1)) + actual = RigidTransform.from_dual_quat(dq, scalar_first=True) + xp_assert_close(actual.as_matrix(), expected_matrix, atol=atol, rtol=1e-7) + + # unnormalized dual quaternions + + # invalid real quaternion with norm 0 + dq = xp.zeros(shape + (8,)) + actual = RigidTransform.from_dual_quat(dq) + expected = xp.tile(xp.eye(4), shape + (1, 1)) + xp_assert_close(actual.as_matrix(), expected, atol=atol) + + # real quaternion with norm != 1 + unnormalized_dual_quat = xp.asarray( + [-0.2547655, 1.23506123, 0.20230088, 0.24247194, # norm 1.3 + 0.38559628, 0.08184063, 0.1755943, -0.1582222] # orthogonal + ) + xp_assert_close(xp_vector_norm(unnormalized_dual_quat[:4]), xp.asarray(1.3)[()], + atol=atol) + xp_assert_close(xp.vecdot(unnormalized_dual_quat[:4], + unnormalized_dual_quat[4:])[()], + xp.asarray(0.0)[()], atol=1e-8) + + dq = xp.tile(unnormalized_dual_quat, shape + (1,)) + dual_quat = RigidTransform.from_dual_quat(dq).as_dual_quat() + + expected_ones = xp.ones(shape) if shape != () else xp.asarray(1.0)[()] + expected_zeros = xp.zeros(shape) if shape != () else xp.asarray(0.0)[()] + xp_assert_close(xp_vector_norm(dual_quat[..., :4], axis=-1), expected_ones, + atol=1e-12) + vecdot = xp.vecdot(dual_quat[..., :4], dual_quat[..., 4:]) + vecdot = vecdot[()] if vecdot.shape == () else vecdot + xp_assert_close(vecdot, expected_zeros, atol=atol) + + # real and dual quaternion are not orthogonal + unnormalized_dual_quat = xp.asarray( + [0.20824458, 0.75098079, 0.54542913, -0.30849493, # unit norm + -0.16051025, 0.10742978, 0.21277201, 0.20596935] # not orthogonal + ) + xp_assert_close(xp_vector_norm(unnormalized_dual_quat[:4]), xp.asarray(1.0)[()], + atol=atol) + assert xp.vecdot(unnormalized_dual_quat[:4], unnormalized_dual_quat[4:]) != 0.0 + dq = xp.tile(unnormalized_dual_quat, shape + (1,)) + dual_quat = RigidTransform.from_dual_quat(dq).as_dual_quat() + + xp_assert_close(xp_vector_norm(dual_quat[..., :4], axis=-1), expected_ones, + atol=1e-12) + vecdot = xp.vecdot(dual_quat[..., :4], dual_quat[..., 4:]) + vecdot = vecdot[()] if vecdot.shape == () else vecdot + xp_assert_close(vecdot, expected_zeros, atol=atol) + + # invalid real quaternion with norm 0, non-orthogonal dual quaternion + unnormalized_dual_quat = xp.asarray( + [0.0, 0.0, 0.0, 0.0, -0.16051025, 0.10742978, 0.21277201, 0.20596935]) + assert xp.vecdot(xp.asarray([0.0, 0, 0, 1]), unnormalized_dual_quat[4:]) != 0.0 + dq = xp.tile(unnormalized_dual_quat, shape + (1,)) + dual_quat = RigidTransform.from_dual_quat(dq).as_dual_quat() + + xp_assert_close(xp_vector_norm(dual_quat[..., :4], axis=-1), expected_ones, + atol=1e-12) + vecdot = xp.vecdot(dual_quat[..., :4], dual_quat[..., 4:]) + vecdot = vecdot[()] if vecdot.shape == () else vecdot + xp_assert_close(vecdot, expected_zeros, atol=atol) + + # compensation for precision loss in real quaternion + rng = np.random.default_rng(1000) + t = xp.asarray(rng.normal(size=shape + (3,)), dtype=dtype) + q = xp.asarray(rng.normal(size=shape + (4,)), dtype=dtype) + r = Rotation.from_quat(q) + random_dual_quats = RigidTransform.from_components(t, r).as_dual_quat() + + # ensure that random quaternions are not normalized + random_dual_quats = xpx.at(random_dual_quats)[..., :4].add(0.01) + assert not xp.any(xpx.isclose(xp_vector_norm(random_dual_quats[..., :4], axis=-1), + 1.0, atol=0.0001)) + dual_quat_norm = RigidTransform.from_dual_quat( + random_dual_quats).as_dual_quat() + xp_assert_close(xp_vector_norm(dual_quat_norm[..., :4], axis=-1), expected_ones, + atol=atol) + + # compensation for precision loss in dual quaternion, results in violation + # of orthogonality constraint + t = xp.asarray(rng.normal(size=shape + (3,)), dtype=dtype) + q = xp.asarray(rng.normal(size=shape + (4,)), dtype=dtype) + r = Rotation.from_quat(q) + random_dual_quats = RigidTransform.from_components(t, r).as_dual_quat() + + # ensure that random quaternions are not normalized + random_dual_quats = xpx.at(random_dual_quats)[..., 4:].add(0.1) + q_norm = xp.vecdot(random_dual_quats[..., :4], random_dual_quats[..., 4:]) + assert not xp.any(xpx.isclose(q_norm, 0.0, atol=0.0001)) + dual_quat_norm = RigidTransform.from_dual_quat( + random_dual_quats).as_dual_quat() + vecdot = xp.vecdot(dual_quat[..., :4], dual_quat[..., 4:]) + vecdot = vecdot[()] if vecdot.shape == () else vecdot + xp_assert_close(vecdot, expected_zeros, atol=atol) + xp_assert_close(random_dual_quats[..., :4], dual_quat_norm[..., :4], atol=atol) + + +def test_from_dual_quat_array_like(): + rng = np.random.default_rng(123) + # Test single transform + t = np.array([1, 2, 3]) + r = Rotation.random(rng=rng) + tf_expected = RigidTransform.from_components(t, r) + dual_quat = tf_expected.as_dual_quat().tolist() + assert isinstance(dual_quat, list) + tf = RigidTransform.from_dual_quat(dual_quat) + xp_assert_close(tf.as_matrix(), tf_expected.as_matrix(), atol=1e-12) + + # Test multiple transforms + t = [[1, 2, 3], [4, 5, 6]] + r = Rotation.random(len(t), rng=rng) + tf_expected = RigidTransform.from_components(t, r) + dual_quat = tf_expected.as_dual_quat().tolist() + assert isinstance(dual_quat, list) + tf = RigidTransform.from_dual_quat(dual_quat) + xp_assert_close(tf.as_matrix(), tf_expected.as_matrix(), atol=1e-12) + + +@make_xp_test_case((RigidTransform, "as_dual_quat")) +@pytest.mark.parametrize("ndim", range(1, 4)) +def test_as_dual_quat(xp, ndim: int): + dtype = xpx.default_dtype(xp) + shape = (ndim,) * (ndim - 1) + # identity + expected = xp.asarray([0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0], dtype=dtype) + actual = rigid_transform_to_xp(RigidTransform.identity(), xp).as_dual_quat() + xp_assert_close(actual, expected, atol=1e-12) + + expected = xp.asarray([1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) + tf = rigid_transform_to_xp(RigidTransform.identity(), xp) + actual = tf.as_dual_quat(scalar_first=True) + xp_assert_close(actual, expected, atol=1e-12) + + rng = np.random.default_rng(10) + + # only rotation + for _ in range(10): + q = xp.asarray(rng.normal(size=shape + (4,)), dtype=dtype) + real_part = Rotation.from_quat(q).as_quat() + dual_part = xp.zeros_like(real_part) + expected = xp.concat((real_part, dual_part), axis=-1) + actual = RigidTransform.from_dual_quat(expected).as_dual_quat() + # because of double cover: + actual = actual * xp.sign(actual[..., 0, None]) + expected = expected * xp.sign(expected[..., 0, None]) + xp_assert_close(actual, expected, atol=1e-12) + + # only translation + for _ in range(10): + tf = 0.5 * xp.asarray(rng.normal(size=shape + (3,)), dtype=dtype) + expected = xp.zeros(shape + (8,), dtype=dtype) + expected = xpx.at(expected)[..., 3].set(1.0) + expected = xpx.at(expected)[..., 4:7].set(tf) + actual = RigidTransform.from_dual_quat(expected).as_dual_quat() + # because of double cover: + actual = actual * xp.sign(actual[..., 0, None]) + expected = expected * xp.sign(expected[..., 0, None]) + xp_assert_close(actual, expected, atol=1e-12) + + # rotation and translation + for _ in range(10): + t = xp.asarray(rng.normal(size=shape + (3,)), dtype=dtype) + r = Rotation.from_quat(xp.asarray(rng.normal(size=shape + (4,)), dtype=dtype)) + expected = RigidTransform.from_components(t, r).as_dual_quat() + actual = RigidTransform.from_dual_quat(expected).as_dual_quat() + # because of double cover: + actual = actual * xp.sign(actual[..., 0, None]) + expected = expected * xp.sign(expected[..., 0, None]) + xp_assert_close(actual, expected, atol=1e-12) + + +@make_xp_test_case( + (RigidTransform, "from_components"), (RigidTransform, "as_components"), + (RigidTransform, "from_exp_coords"), (RigidTransform, "as_exp_coords"), + (RigidTransform, "from_matrix"), (RigidTransform, "as_matrix"), + (RigidTransform, "from_dual_quat"), (RigidTransform, "as_dual_quat") +) +@pytest.mark.parametrize("ndim", range(1, 4)) +def test_from_as_internal_consistency(xp, ndim: int): + dtype = xpx.default_dtype(xp) + atol = 1e-12 + n = 10 + rng = np.random.default_rng(10) + shape = (n,) + (ndim,) * (ndim - 1) + + t = xp.asarray(rng.normal(size=shape + (3,)), dtype=dtype) + r = Rotation.from_quat(xp.asarray(rng.normal(size=shape + (4,)), dtype=dtype)) + tf0 = RigidTransform.from_components(t, r) + + tf1 = RigidTransform.from_components(*tf0.as_components()) + xp_assert_close(tf0.as_matrix(), tf1.as_matrix(), atol=atol) + + tf1 = RigidTransform.from_components(tf0.translation, tf0.rotation) + xp_assert_close(tf0.as_matrix(), tf1.as_matrix(), atol=atol) + + tf1 = RigidTransform.from_exp_coords(tf0.as_exp_coords()) + xp_assert_close(tf0.as_matrix(), tf1.as_matrix(), atol=atol) + + tf1 = RigidTransform.from_matrix(tf0.as_matrix()) + xp_assert_close(tf0.as_matrix(), tf1.as_matrix(), atol=atol) + + tf1 = RigidTransform.from_dual_quat(tf0.as_dual_quat()) + xp_assert_close(tf0.as_matrix(), tf1.as_matrix(), atol=atol) + + # exp_coords small rotation + t = xp.asarray(rng.normal(scale=1000.0, size=shape + (3,)), dtype=dtype) + rotvec = xp.asarray(rng.normal(scale=1e-10, size=shape + (3,)), dtype=dtype) + r = Rotation.from_rotvec(rotvec) + tf0 = RigidTransform.from_components(t, r) + tf1 = RigidTransform.from_exp_coords(tf0.as_exp_coords()) + xp_assert_close(tf0.as_matrix(), tf1.as_matrix(), atol=atol) + + +def test_identity(): + # We do not use xp here because identity always returns numpy arrays + atol = 1e-12 + # Test single identity + tf = RigidTransform.identity() + xp_assert_close(tf.as_matrix(), np.eye(4), atol=atol) + # Test multiple identities + tf = RigidTransform.identity(5) + xp_assert_close(tf.as_matrix(), np.array([np.eye(4)] * 5), atol=atol) + # Test shape + tf = RigidTransform.identity(shape=3) + expected = np.tile(np.eye(4), (3, 1, 1)) + xp_assert_close(tf.as_matrix(), expected, atol=atol) + tf = RigidTransform.identity(shape=(2, 3)) + expected = np.tile(np.eye(4), (2, 3, 1, 1)) + xp_assert_close(tf.as_matrix(), expected, atol=atol) + # Test errors + with pytest.raises(ValueError, match="Only one of `num` and `shape` can be."): + RigidTransform.identity(10, shape=(2, 3)) + with pytest.raises(TypeError, match="takes from 0 to 1 positional arguments"): + RigidTransform.identity(None, (-1, 3)) # Shape is kwarg only + with pytest.raises(ValueError, match="`shape` must be an int or a tuple of ints"): + RigidTransform.identity(shape="invalid") + + +@make_xp_test_case((RigidTransform, "apply")) +def test_apply(xp): + atol = 1e-12 + # Broadcast shape: (6, 5, 4, 2) ( + (3,) for vectors, + (4,) for rotations) + vector_shapes = [(), (1,), (2,), (1, 2), (5, 1, 2)] + tf_shapes = [(), (1,), (2,), (1, 2), (4, 2), (1, 4, 2), (5, 4, 2), (6, 5, 4, 2)] + rng = np.random.default_rng(123) + + for tf_shape, v_shape in product(tf_shapes, vector_shapes): + # Random rotation and translation + t = xp.asarray(rng.normal(size=tf_shape + (3,))) + q = xp.asarray(rng.normal(size=tf_shape + (4,))) + r = Rotation.from_quat(q) + tf = RigidTransform.from_components(t, r) + + vecs = xp.asarray(rng.normal(size=v_shape + (3,))) + expected = t + r.apply(vecs) + res = tf.apply(vecs) + assert res.shape == np.broadcast_shapes(tf_shape, v_shape) + (3,) + xp_assert_close(res, expected, atol=atol) + + +def test_apply_array_like(): + rng = np.random.default_rng(123) + # Single vector + t = np.array([1, 2, 3]) + r = Rotation.random(rng=rng) + tf = RigidTransform.from_components(t, r) + vec = [1, 0, 0] + expected = t + r.apply(vec) + xp_assert_close(tf.apply(vec), expected, atol=1e-12) + + # Multiple vectors + t = np.array([[1, 2, 3], [4, 5, 6]]) + r = Rotation.random(len(t), rng=rng) + tf = RigidTransform.from_components(t, r) + vec = [[1, 0, 0], [0, 1, 0]] + expected = t + r.apply(vec) + xp_assert_close(tf.apply(vec), expected, atol=1e-12) + + +def test_apply_matrix_equivalence(): + """Test documented equivalence for single transform: + `apply(vector) == translation + vector @ rotation.as_matrix().T.`""" + t = np.array([1.0, 2.0, 3.0]) + r = Rotation.from_rotvec([0, 0, 1]) + tf = RigidTransform.from_components(t, r) + # Single vector (3,) + v = np.array([1.0, 0.0, 0.0]) + xp_assert_close(tf.apply(v), t + v @ r.as_matrix().T) + # Multiple vectors (P, 3) + arr = np.array([[1, 0, 0], [0, 1, 0]], dtype=float) + xp_assert_close(tf.apply(arr), t + arr @ r.as_matrix().T) + + +@make_xp_test_case((RigidTransform, "apply")) +def test_inverse_apply(xp): + atol = 1e-12 + # Broadcast shape: (6, 5, 4, 2) ( + (3,) for vectors, + (4,) for rotations) + vector_shapes = [(), (1,), (2,), (1, 2), (5, 1, 2)] + tf_shapes = [(), (1,), (2,), (1, 2), (4, 2), (1, 4, 2), (5, 4, 2), (6, 5, 4, 2)] + rng = np.random.default_rng(123) + + for tf_shape, v_shape in product(tf_shapes, vector_shapes): + # Random rotation and translation + t = xp.asarray(rng.normal(size=tf_shape + (3,))) + q = xp.asarray(rng.normal(size=tf_shape + (4,))) + r = Rotation.from_quat(q) + tf = RigidTransform.from_components(t, r) + + vecs = xp.asarray(rng.normal(size=v_shape + (3,))) + expected = tf.inv().apply(vecs) + res = tf.apply(vecs, inverse=True) + assert res.shape == np.broadcast_shapes(tf_shape, v_shape) + (3,) + xp_assert_close(res, expected, atol=atol) + + +@make_xp_test_case((RigidTransform, "apply")) +def test_rotation_alone(xp): + atol = 1e-12 + + r = Rotation.from_euler('z', xp.asarray(90), degrees=True) + tf = RigidTransform.from_rotation(r) + vec = xp.asarray([1, 0, 0]) + expected = r.apply(vec) + xp_assert_close(tf.apply(vec), expected, atol=atol) + + +@make_xp_test_case((RigidTransform, "apply")) +def test_translation_alone(xp): + atol = 1e-12 + t = xp.asarray([1.0, 2, 3]) + tf = RigidTransform.from_translation(t) + vec = xp.asarray([5.0, 6, 7]) + expected = t + vec + xp_assert_close(tf.apply(vec), expected, atol=atol) + + +@make_xp_test_case((RigidTransform, "apply"), (RigidTransform, "__mul__")) +def test_composition(xp): + atol = 1e-12 + tf_shapes = [(), (1,), (2,), (1, 2), (4, 2), (5, 4, 2)] + dtype = xpx.default_dtype(xp) + rng = np.random.default_rng(123) + + for tf_shape1, tf_shape2 in product(tf_shapes, repeat=2): + # Random rotation and translation + t1 = xp.asarray(rng.normal(size=tf_shape1 + (3,))) + q1 = xp.asarray(rng.normal(size=tf_shape1 + (4,))) + r1 = Rotation.from_quat(q1) + tf1 = RigidTransform.from_components(t1, r1) + + t2 = xp.asarray(rng.normal(size=tf_shape2 + (3,))) + q2 = xp.asarray(rng.normal(size=tf_shape2 + (4,))) + r2 = Rotation.from_quat(q2) + tf2 = RigidTransform.from_components(t2, r2) + + composed = tf2 * tf1 + vec = xp.asarray(rng.normal(size=(3,)), dtype=dtype) + expected = tf2.apply(tf1.apply(vec)) + res = composed.apply(vec) + assert res.shape == np.broadcast_shapes(tf_shape1, tf_shape2) + (3,) + xp_assert_close(res, expected, atol=atol) + + expected = t2 + r2.apply(t1 + r1.apply(vec)) + xp_assert_close(composed.apply(vec), expected, atol=atol) + assert composed.single == (tf1.single and tf2.single) + + +@make_xp_test_case((RigidTransform, "__pow__"), (RigidTransform, "__mul__")) +@pytest.mark.parametrize("ndim", range(1, 4)) +def test_pow(xp, ndim: int): + dtype = xpx.default_dtype(xp) + atol = 1e-12 if dtype == xp.float64 else 1e-6 + num = 10 + rng = np.random.default_rng(100) + shape = (num,) + (ndim,) * (ndim - 1) + t = xp.asarray(rng.normal(size=shape + (3,)), dtype=dtype) + q = xp.asarray(rng.normal(size=shape + (4,)), dtype=dtype) + r = Rotation.from_quat(q) + p = RigidTransform.from_components(t, r) + p_inv = p.inv() + + # Test the short-cuts and other integers + for n in [-5, -2, -1, 0, 1, 2, 5]: + q = p**n + # Regression test for gh-24436 + assert isinstance(q._matrix, type(p._matrix)) + r = RigidTransform.from_matrix(xp.tile(xp.eye(4), shape + (1, 1))) + for _ in range(abs(n)): + if n > 0: + r = r * p + else: + r = r * p_inv + xp_assert_close(q.as_matrix(), r.as_matrix(), atol=atol) + + # Test shape preservation of single + single_tf = RigidTransform.identity() + assert (single_tf**n).as_matrix().shape == (4, 4) + + # Test fractional powers + q = p**0.5 + xp_assert_close((q * q).as_matrix(), p.as_matrix(), atol=atol) + q = p**-0.5 + xp_assert_close((q * q).as_matrix(), p.inv().as_matrix(), atol=atol) + q = p** 1.5 + xp_assert_close((q * q).as_matrix(), (p**3).as_matrix(), atol=atol) + q = p** -1.5 + xp_assert_close((q * q).as_matrix(), (p**-3).as_matrix(), atol=atol) + + # pow function + tf = pow(RigidTransform.from_matrix(xp.eye(4)), 2) + xp_assert_close(tf.as_matrix(), xp.eye(4), atol=atol) + + +@make_xp_test_case((RigidTransform, "__pow__")) +@pytest.mark.parametrize("ndim", range(1, 4)) +def test_pow_equivalence_with_rotation(xp, ndim: int): + atol = 1e-12 + num = 10 + rng = np.random.default_rng(100) + dtype = xpx.default_dtype(xp) + shape = (num,) + (ndim,) * (ndim - 1) + + r = Rotation.from_quat(xp.asarray(rng.normal(size=shape + (4,)), dtype=dtype)) + p = RigidTransform.from_rotation(r) + for n in [-5, -2, -1.5, -1, -0.5, 0.0, 0.5, 1, 1.5, 2, 5]: + xp_assert_close((p**n).rotation.as_matrix(), (r**n).as_matrix(), atol=atol) + + +@make_xp_test_case((RigidTransform, "inv"), (RigidTransform, "__mul__")) +@pytest.mark.parametrize("ndim", range(1, 4)) +def test_inverse(xp, ndim: int): + dtype = xpx.default_dtype(xp) + atol = 1e-12 if dtype == xp.float64 else 1e-6 + rng = np.random.default_rng(100) + shape = (ndim,) * (ndim - 1) + + # Test inverse transform + r = Rotation.from_quat(xp.asarray(rng.normal(size=shape + (4,)), dtype=dtype)) + t = xp.asarray(rng.normal(size=shape + (3,)), dtype=dtype) + tf = RigidTransform.from_components(t, r) + + # Test that tf * tf.inv() equals identity + tf_inv = tf.inv() + composed = tf * tf_inv + expected = xp.tile(xp.eye(4), shape + (1, 1)) + xp_assert_close(composed.as_matrix(), expected, atol=atol) + + +@make_xp_test_case((RigidTransform, "as_matrix")) +@pytest.mark.parametrize("ndim", range(1, 4)) +def test_properties(xp, ndim: int): + atol = 1e-12 if xpx.default_dtype(xp) == xp.float64 else 1e-6 + shape = (ndim,) * (ndim - 1) + dtype = xpx.default_dtype(xp) + rng = np.random.default_rng(100) + + # Test rotation and translation properties + r = Rotation.from_quat(xp.asarray(rng.normal(size=shape + (4,)), dtype=dtype)) + t = xp.asarray(rng.normal(size=shape + (3,)), dtype=dtype) + tf = RigidTransform.from_components(t, r) + + xp_assert_close(tf.rotation.as_matrix(), r.as_matrix(), atol=atol) + assert xp.all(tf.rotation.approx_equal(r, atol=atol)) + xp_assert_close(tf.translation, t, atol=atol) + # Test that we don't return views that would modify the original array + xpx.at(tf.translation)[..., 0].set(0.0) + xp_assert_close(tf.translation, t, atol=atol) + assert tf.single == (shape == ()) + + +@make_xp_test_case((RigidTransform, "__getitem__")) +def test_indexing(xp): + atol = 1e-12 + + # Test indexing for multiple transforms + r = Rotation.from_euler('zyx', xp.asarray([[90, 0, 0], [0, 90, 0]]), degrees=True) + t = xp.asarray([[1.0, 2, 3], [4, 5, 6]]) + tf = RigidTransform.from_components(t, r) + + # Test single index + xp_assert_close(tf[0].as_matrix()[:3, :3], r[0].as_matrix(), atol=atol) + xp_assert_close(tf[0].as_matrix()[:3, 3], t[0, ...], atol=atol) + + # Test slice + tf_slice = tf[0:2] + xp_assert_close(tf_slice.as_matrix()[:, :3, :3], r[0:2].as_matrix(), atol=atol) + xp_assert_close(tf_slice.as_matrix()[:, :3, 3], t[0:2, ...], atol=atol) + + # Test boolean indexing + tf_masked = tf[xp.asarray([True, True])] + xp_assert_close(tf_masked.as_matrix()[:, :3, :3], r.as_matrix(), atol=atol) + xp_assert_close(tf_masked.as_matrix()[:, :3, 3], t, atol=atol) + + tf_masked = tf[xp.asarray([False, True])] + xp_assert_close(tf_masked.as_matrix()[:, :3, :3], + r[xp.asarray([False, True])].as_matrix(), atol=atol) + xp_assert_close(tf_masked.as_matrix()[:, :3, 3], t[xp.asarray([False, True])], + atol=atol) + + tf_masked = tf[xp.asarray([False, False])] + assert len(tf_masked) == 0 + + # Test integer array indexing + idx = xp.asarray([0, 1]) + xp_assert_close(tf[idx].as_matrix()[:, :3, :3], r[idx].as_matrix(), atol=atol) + xp_assert_close(tf[idx].as_matrix()[:, :3, 3], t, atol=atol) + + +def test_indexing_array_like(): + atol = 1e-12 + + r = Rotation.from_euler('zyx', np.array([[90, 0, 0], [0, 90, 0]]), degrees=True) + t = np.array([[1.0, 2, 3], [4, 5, 6]]) + tf = RigidTransform.from_components(t, r) + + tf_masked = tf[[False, True]] + xp_assert_close(tf_masked.as_matrix()[:, :3, :3], r[[False, True]].as_matrix(), + atol=atol) + xp_assert_close(tf_masked.as_matrix()[:, :3, 3], t[[False, True]], atol=atol) + tf_masked = tf[[False, False]] + assert len(tf_masked) == 0 + + +@make_xp_test_case((RigidTransform, "concatenate")) +def test_concatenate(xp): + atol = 1e-12 + + # Test concatenation of transforms + t1 = xp.asarray([1, 0, 0]) + r1 = Rotation.from_euler('z', xp.asarray(90), degrees=True) + tf1 = RigidTransform.from_components(t1, r1) + + t2 = xp.asarray([0, 1, 0]) + r2 = Rotation.from_euler('x', xp.asarray(90), degrees=True) + tf2 = RigidTransform.from_components(t2, r2) + + # Concatenate single transforms + concatenated1 = RigidTransform.concatenate([tf1, tf2]) + xp_assert_close(concatenated1[0].as_matrix(), tf1.as_matrix(), atol=atol) + xp_assert_close(concatenated1[1].as_matrix(), tf2.as_matrix(), atol=atol) + + # Concatenate multiple transforms + concatenated2 = RigidTransform.concatenate([tf1, concatenated1]) + xp_assert_close(concatenated2[0].as_matrix(), tf1.as_matrix(), atol=atol) + xp_assert_close(concatenated2[1].as_matrix(), tf1.as_matrix(), atol=atol) + xp_assert_close(concatenated2[2].as_matrix(), tf2.as_matrix(), atol=atol) + + # Test ND concatenation + tf3 = RigidTransform.from_translation(xp.reshape(xp.arange(18), (3, 2, 3))) + tf4 = RigidTransform.from_translation(xp.reshape(xp.arange(18) + 18, (3, 2, 3))) + concatenated3 = RigidTransform.concatenate([tf3, tf4]) + xp_assert_close(concatenated3.as_matrix()[:3, ...], tf3.as_matrix(), atol=atol) + xp_assert_close(concatenated3.as_matrix()[3:, ...], tf4.as_matrix(), atol=atol) + + +@make_xp_test_case((RigidTransform, "from_matrix")) +def test_input_validation(xp): + # Test invalid matrix shapes + inputs = [xp.eye(3), xp.zeros((4, 3)), []] + for input in inputs: + with pytest.raises(ValueError, match="Expected `matrix` to have shape"): + RigidTransform.from_matrix(input) + + # Test invalid last row + for ndim in range(3): + shape = (ndim,) * (ndim - 1) + matrix = xp.zeros(shape + (4, 4)) + matrix = xpx.at(matrix)[...].set(xp.eye(4)) + matrix = xpx.at(matrix)[..., 3, :].set(xp.asarray([1.0, 0, 0, 1])) + if is_lazy_array(matrix): + matrix = RigidTransform.from_matrix(matrix).as_matrix() + assert xp.all(xp.isnan(matrix[..., 3, :])) + else: + with pytest.raises(ValueError, match="last row of transformation matrix"): + RigidTransform.from_matrix(matrix) + + # Test left handed rotation matrix + matrix = xp.eye(4) + matrix = xpx.at(matrix)[0, 0].set(-1) + if is_lazy_array(matrix): + matrix = RigidTransform.from_matrix(matrix).as_matrix() + assert xp.all(xp.isnan(matrix[..., :3, :3])) + else: + with pytest.raises(ValueError, match="Non-positive determinant"): + RigidTransform(matrix, normalize=True) + + # Test non-Rotation input + with pytest.raises(TypeError, + match="Expected `rotation` to be a `Rotation` instance"): + RigidTransform.from_rotation(xp.eye(3)) + + +@make_xp_test_case((RigidTransform, "mean")) +@pytest.mark.parametrize("ndim", range(1, 4)) +def test_mean(xp, ndim: int): + atol = 1e-12 + rng = np.random.default_rng(123) + + dtype = xpx.default_dtype(xp) + t = xp.asarray(rng.normal(size=(ndim,) * (ndim - 1) + (3,)), dtype=dtype) + q = xp.asarray(rng.normal(size=(ndim,) * (ndim - 1) + (4,)), dtype=dtype) + r = Rotation.from_quat(q) + tf = RigidTransform.from_components(t, r) + + # Unweighted mean + axis = tuple(range(t.ndim - 1)) + t_mean = xp.mean(t, axis=axis) + r_mean = r.mean() + tf_mean = tf.mean() + assert tf_mean.shape == () + xp_assert_close(tf_mean.as_matrix(), + RigidTransform.from_components(t_mean, r_mean).as_matrix(), + atol=atol) + + # Weighted mean + if ndim == 1: + weights = None + t_mean = t + else: + weights = xp.asarray(rng.random(size=(ndim,) * (ndim - 1)), dtype=dtype) + norm = xp.sum(weights[..., None], axis=axis) + wsum = xp.sum(t * weights[..., None], axis=axis) + t_mean = wsum/norm + r_mean = r.mean(weights=weights) + tf_mean = tf.mean(weights=weights) + assert tf_mean.shape == () + xp_assert_close(tf_mean.as_matrix(), + RigidTransform.from_components(t_mean, r_mean).as_matrix(), + atol=atol) + + +@make_xp_test_case( + (RigidTransform, "from_rotation"), (RigidTransform, "mean"), (Rotation, "magnitude") +) +@pytest.mark.parametrize("ndim", range(1, 5)) +def test_mean_axis(xp, ndim: int): + axes = xp.tile(xp.concat((-xp.eye(3), xp.eye(3))), (3,) * (ndim - 1) + (1, 1)) + theta = xp.pi / 4 + r = Rotation.from_rotvec(theta * axes) + tf = RigidTransform.from_rotation(r) + + # Test mean over last axis + desired = xp.full(axes.shape[:-2], 0.0) + if ndim == 1: + desired = desired[()] + atol = 1e-6 if xpx.default_dtype(xp) is xp.float32 else 1e-10 + xp_assert_close(tf.mean(axis=-1).rotation.magnitude(), desired, atol=atol) + + # Test tuple axes + desired = xp.full(axes.shape[1:-2], 0.0) + if ndim < 3: + desired = desired[()] + xp_assert_close(tf.mean(axis=(0, -1)).rotation.magnitude(), desired, atol=atol) + + # Empty axis tuple should return RigidTransform unchanged + tf_mean = tf.mean(axis=()) + xp_assert_close(tf_mean.as_matrix(), tf.as_matrix(), atol=atol) + + +@make_xp_test_case((RigidTransform, "mean"), (Rotation, "magnitude")) +def test_mean_compare_axis(xp): + # Create a random set of transforms and compare the mean over an axis with + # the mean without axis of the sliced transform + atol = 1e-10 if xpx.default_dtype(xp) == xp.float64 else 1e-6 + rng = np.random.default_rng(0) + q = xp.asarray(rng.normal(size=(4, 5, 6, 4)), dtype=xpx.default_dtype(xp)) + r = Rotation.from_quat(q) + t = xp.asarray(rng.normal(size=(4, 5, 6, 3)), dtype=xpx.default_dtype(xp)) + tf = RigidTransform.from_components(t, r) + + mean_0 = tf.mean(axis=0) + for i in range(q.shape[1]): + for j in range(q.shape[2]): + r_slice = Rotation.from_quat(q[:, i, j, ...]) + t_slice = t[:, i, j, ...] + mean_slice_tf = RigidTransform.from_components(t_slice, r_slice).mean() + xp_assert_close( + (mean_0[i][j].rotation * mean_slice_tf.rotation.inv()).magnitude(), + xp.asarray(0.0)[()], atol=atol, + ) + xp_assert_close( + mean_0[i][j].translation, mean_slice_tf.translation, atol=atol, + ) + mean_1_2 = tf.mean(axis=(1, 2)) + for i in range(q.shape[0]): + r_slice = Rotation.from_quat(q[i, ...]) + t_slice = t[i, ...] + mean_slice_tf = RigidTransform.from_components(t_slice, r_slice).mean() + xp_assert_close( + (mean_1_2[i].rotation * mean_slice_tf.rotation.inv()).magnitude(), + xp.asarray(0.0)[()], atol=atol, + ) + xp_assert_close( + mean_1_2[i].translation, mean_slice_tf.translation, atol=atol, + ) + + +@make_xp_test_case((RigidTransform, "mean")) +def test_mean_invalid_weights(xp): + tf = RigidTransform.from_matrix(xp.tile(xp.eye(4), (4, 1, 1))) + if is_lazy_array(tf.as_matrix()): + m = tf.mean(weights=-xp.ones(4)) + assert xp.all(xp.isnan(m.as_matrix())) + else: + with pytest.raises(ValueError, match="non-negative"): + tf.mean(weights=-xp.ones(4)) + + # Test weight shape mismatch + tf = RigidTransform.from_matrix(xp.eye(4)) + with pytest.raises(ValueError, match="Expected `weights` to"): + tf.mean(weights=xp.ones((2,))) + tf = RigidTransform.from_matrix(xp.tile(xp.eye(4), (3, 2, 1, 1, 1))) + with pytest.raises(ValueError, match="Expected `weights` to"): + tf.mean(weights=xp.ones((2, 1))) + + +@make_xp_test_case((RigidTransform, "from_translation")) +def test_translation_validation(xp): + # Test invalid translation shapes + with pytest.raises(ValueError, match="Expected `translation` to have shape"): + RigidTransform.from_translation(xp.asarray([1, 2])) + + with pytest.raises(ValueError, match="Expected `translation` to have shape"): + RigidTransform.from_translation(xp.zeros((2, 2))) + + +@make_xp_test_case((RigidTransform, "apply")) +def test_vector_validation(xp): + tf = rigid_transform_to_xp(RigidTransform.identity(2), xp=xp) + + # Test invalid vector shapes + with pytest.raises(ValueError, match="Expected vector to have shape"): + tf.apply(xp.asarray([1, 2])) + + with pytest.raises(ValueError, match="Expected vector to have shape"): + tf.apply(xp.zeros((2, 2))) + + with pytest.raises(ValueError, match="operands could not be broadcast"): + tf.apply(xp.zeros((1, 4, 3))) + + +@make_xp_test_case((RigidTransform, "__getitem__")) +def test_indexing_validation(xp): + tf = RigidTransform.from_matrix(xp.eye(4)) + + # Test indexing on single transform + with pytest.raises(TypeError, match="Single transform is not subscriptable"): + tf[0] + + with pytest.raises(TypeError, match="Single transform is not subscriptable"): + tf[0:1] + + # Test length on single transform + with pytest.raises(TypeError, match="Single transform has no len"): + len(tf) + + +@make_xp_test_case((RigidTransform, "__mul__")) +def test_composition_validation(xp): + tf2 = RigidTransform.from_translation(xp.asarray([[1, 2, 3], [4, 5, 6]])) + tf3 = RigidTransform.from_translation(xp.asarray([[1, 2, 3], [4, 5, 6], [7, 8, 9]])) + + # Test incompatible shapes + with pytest.raises(ValueError, match="Cannot broadcast"): + tf2 * tf3 + + tf4 = RigidTransform.from_matrix(xp.tile(xp.eye(4), (1, 4, 1, 1))) + # Test invalid broadcasting shape + with pytest.raises(ValueError, match="Cannot broadcast"): + tf2 * tf4 + + +@make_xp_test_case((RigidTransform, "__mul__"), (RigidTransform, "__rmul__")) +def test_rotation_promotion(xp): + """Test that Rotation is promoted to RigidTransform in composition.""" + atol = 1e-12 + dtype = xpx.default_dtype(xp) + rng = np.random.default_rng(0) + + t = xp.asarray(rng.normal(size=(3,)), dtype=dtype) + r1 = rotation_to_xp(Rotation.random(rng=rng), xp) + r2 = rotation_to_xp(Rotation.random(rng=rng), xp) + tf = RigidTransform.from_components(t, r2) + + # RigidTransform * Rotation + result = tf * r1 + expected = tf * RigidTransform.from_rotation(r1) + assert isinstance(result, RigidTransform) + xp_assert_close(result.as_matrix(), expected.as_matrix(), atol=atol) + + # Rotation * RigidTransform + result = r1 * tf + expected = RigidTransform.from_rotation(r1) * tf + assert isinstance(result, RigidTransform) + xp_assert_close(result.as_matrix(), expected.as_matrix(), atol=atol) + + +@make_xp_test_case((RigidTransform, "concatenate")) +def test_concatenate_validation(xp): + tf = RigidTransform.from_matrix(xp.eye(4)) + + # Test invalid inputs + with pytest.raises(TypeError, + match="input must contain RigidTransform objects"): + RigidTransform.concatenate([tf, xp.eye(4)]) + + # Test incompatible shapes + tf2 = RigidTransform.from_translation(xp.ones((1, 1, 1, 3))) + # Frameworks have a highly heterogeneous way of reporting errors for this case + with pytest.raises((ValueError, TypeError, RuntimeError)): + RigidTransform.concatenate([tf, tf2]) + + +@make_xp_test_case((RigidTransform, "__setitem__")) +def test_setitem(xp): + tf = RigidTransform.from_translation(xp.asarray([[1, 2, 3], [4, 5, 6], [7, 8, 9]])) + single = RigidTransform.from_translation(xp.asarray([1, 1, 1])) + double = RigidTransform.from_translation(xp.asarray([[2, 2, 2], [3, 3, 3]])) + triple = RigidTransform.from_translation(xp.asarray([[3, 3, 3], + [4, 4, 4], + [5, 5, 5]])) + + # Test indexing with integer index + tf[0] = single + xp_assert_close(tf.translation, xp.asarray([[1.0, 1, 1], [4, 5, 6], [7, 8, 9]])) + + # Test indexing with slice + tf = RigidTransform.from_translation(xp.asarray([[1, 2, 3], [4, 5, 6], [7, 8, 9]])) + tf[:2] = double + xp_assert_close(tf.translation, xp.asarray([[2.0, 2, 2], [3, 3, 3], [7, 8, 9]])) + + # Test indexing with ellipsis + tf = RigidTransform.from_translation(xp.asarray([[1, 2, 3], [4, 5, 6], [7, 8, 9]])) + tf[...] = triple + xp_assert_close(tf.translation, xp.asarray([[3.0, 3, 3], [4, 4, 4], [5, 5, 5]])) + + # Test indexing with boolean array + tf = RigidTransform.from_translation(xp.asarray([[1, 2, 3], [4, 5, 6], [7, 8, 9]])) + mask = xp.asarray([True, False, True]) + tf[mask] = double + xp_assert_close(tf.translation, xp.asarray([[2.0, 2, 2], [4, 5, 6], [3, 3, 3]])) + + +@make_xp_test_case((RigidTransform, "__setitem__")) +@pytest.mark.skip_xp_backends("array_api_strict", + reason="doesn't support fancy indexing __setitem__") +def test_setitem_fancy_indexing(xp): + double = RigidTransform.from_translation(xp.asarray([[2, 2, 2], [3, 3, 3]])) + tf = RigidTransform.from_translation(xp.asarray([[1, 2, 3], [4, 5, 6], [7, 8, 9]])) + idx = xp.asarray([0, 2]) + tf[idx] = double + xp_assert_close(tf.translation, xp.asarray([[2.0, 2, 2], [4, 5, 6], [3, 3, 3]])) + + +@make_xp_test_case((RigidTransform, "__setitem__")) +def test_setitem_validation(xp): + tf = RigidTransform.from_translation(xp.asarray([[1, 2, 3], [4, 5, 6]])) + single = RigidTransform.from_matrix(xp.eye(4)) + + # Test setting item on single transform + with pytest.raises(TypeError, match="Single transform is not subscriptable"): + single[0] = tf + + # Test invalid value type + with pytest.raises(TypeError, match="value must be a RigidTransform"): + tf[0] = xp.eye(4) + + +@pytest.mark.skip_xp_backends("jax.numpy", + reason="JAX does not support memory sharing") +@make_xp_test_case((RigidTransform, "as_matrix")) +def test_copy_flag(xp): + # Test that copy=True creates new memory + matrix = xp.eye(4) + tf = RigidTransform(matrix, normalize=False, copy=True) + matrix[0, 0] = 2 + assert tf.as_matrix()[0, 0] == 1 + + # Test that copy=False shares memory + matrix = xp.eye(4) + tf = RigidTransform(matrix, normalize=False, copy=False) + matrix[0, 0] = 2 + assert tf.as_matrix()[0, 0] == 2 + + +@make_xp_test_case(normalize_dual_quaternion) +@pytest.mark.parametrize("ndim", range(1, 4)) +def test_normalize_dual_quaternion(xp, ndim: int): + dtype = xpx.default_dtype(xp) + atol = 1e-12 if dtype == xp.float64 else 1e-6 + rng = np.random.default_rng(100) + shape = (ndim,) * (ndim - 1) + + dual_quat = normalize_dual_quaternion(xp.zeros((1, 8))) + xp_assert_close(xp_vector_norm(dual_quat[0, :4], axis=-1), xp.asarray(1.0)[()], + atol=1e-12) + xp_assert_close(xp.vecdot(dual_quat[0, :4], dual_quat[0, 4:])[()], + xp.asarray(0.0)[()], atol=1e-12) + + dual_quat = xp.asarray(rng.normal(size=shape + (8,)), dtype=dtype) + dual_quat = normalize_dual_quaternion(dual_quat) + expected = xp.ones(shape) if shape != () else xp.asarray(1.0)[()] + xp_assert_close(xp_vector_norm(dual_quat[..., :4], axis=-1), expected, atol=atol) + expected = xp.zeros(shape) if shape != () else xp.asarray(0.0)[()] + vecdot = xp.vecdot(dual_quat[..., :4], dual_quat[..., 4:]) + vecdot = vecdot[()] if vecdot.shape == () else vecdot + xp_assert_close(vecdot, expected, atol=atol) + + +@make_xp_test_case( + (RigidTransform, "from_matrix"), (RigidTransform, "from_rotation"), + (RigidTransform, "from_translation"), (RigidTransform, "from_components"), + (RigidTransform, "from_exp_coords"), (RigidTransform, "from_dual_quat") +) +def test_empty_transform_construction(xp): + tf = RigidTransform.from_matrix(xp.empty((0, 4, 4))) + assert len(tf) == 0 + assert not tf.single + + tf = RigidTransform.from_rotation(Rotation.from_quat(xp.zeros((0, 4)))) + assert len(tf) == 0 + assert not tf.single + + tf = RigidTransform.from_translation(xp.empty((0, 3))) + assert len(tf) == 0 + assert not tf.single + + empty_rot = Rotation.from_quat(xp.zeros((0, 4))) + tf = RigidTransform.from_components(xp.empty((0, 3)), empty_rot) + assert len(tf) == 0 + assert not tf.single + + tf = RigidTransform.from_exp_coords(xp.empty((0, 6))) + assert len(tf) == 0 + assert not tf.single + + tf = RigidTransform.from_dual_quat(xp.empty((0, 8))) + assert len(tf) == 0 + assert not tf.single + + tf = RigidTransform.identity(0) + assert len(tf) == 0 + assert not tf.single + + +@make_xp_test_case((RigidTransform, "from_matrix"), (RigidTransform, "as_components"), + (RigidTransform, "as_exp_coords"), (RigidTransform, "as_dual_quat")) +def test_empty_transform_representation(xp): + tf = RigidTransform.from_matrix(xp.empty((0, 4, 4))) + + assert len(tf.rotation) == 0 + assert tf.translation.shape == (0, 3) + + t, r = tf.as_components() + assert t.shape == (0, 3) + assert len(r) == 0 + + assert tf.as_matrix().shape == (0, 4, 4) + assert tf.as_exp_coords().shape == (0, 6) + assert tf.as_dual_quat().shape == (0, 8) + + +@make_xp_test_case((RigidTransform, "from_matrix"), (RigidTransform, "apply")) +def test_empty_transform_application(xp): + tf = RigidTransform.from_matrix(xp.empty((0, 4, 4))) + + assert tf.apply(xp.zeros((3,))).shape == (0, 3) + assert tf.apply(xp.empty((0, 3))).shape == (0, 3) + + with pytest.raises(ValueError, match="operands could not be broadcast together"): + tf.apply(xp.zeros((2, 3))) + + +@make_xp_test_case((RigidTransform, "from_matrix"), (RigidTransform, "__mul__")) +def test_empty_transform_composition(xp): + tf_empty = RigidTransform.from_matrix(xp.empty((0, 4, 4))) + tf_single = RigidTransform.from_matrix(xp.eye(4)) + tf_many = rigid_transform_to_xp(RigidTransform.identity(3), xp=xp) + + assert len(tf_empty * tf_empty) == 0 + assert len(tf_empty * tf_single) == 0 + assert len(tf_single * tf_empty) == 0 + + with pytest.raises(ValueError, match="Cannot broadcast"): + tf_many * tf_empty + + with pytest.raises(ValueError, match="Cannot broadcast"): + tf_empty * tf_many + + +@make_xp_test_case((RigidTransform, "from_matrix"), (RigidTransform, "concatenate")) +def test_empty_transform_concatenation(xp): + tf_empty = RigidTransform.from_matrix(xp.empty((0, 4, 4))) + tf_single = RigidTransform.from_matrix(xp.eye(4)) + tf_many = rigid_transform_to_xp(RigidTransform.identity(2), xp=xp) + + assert len(RigidTransform.concatenate([tf_empty, tf_empty])) == 0 + assert len(RigidTransform.concatenate([tf_empty, tf_single])) == 1 + assert len(RigidTransform.concatenate([tf_single, tf_empty])) == 1 + assert len(RigidTransform.concatenate([tf_empty, tf_many])) == 2 + assert len(RigidTransform.concatenate([tf_many, tf_empty])) == 2 + assert len(RigidTransform.concatenate([tf_many, tf_empty, tf_single])) == 3 + + +@make_xp_test_case((RigidTransform, "from_matrix"), (RigidTransform, "inv"), + (RigidTransform, "__pow__")) +def test_empty_transform_inv_and_pow(xp): + tf = RigidTransform.from_matrix(xp.empty((0, 4, 4))) + assert len(tf.inv()) == 0 + assert len(tf ** 0) == 0 + assert len(tf ** 1) == 0 + assert len(tf ** -1) == 0 + assert len(tf ** 0.5) == 0 + + +@make_xp_test_case((RigidTransform, "__getitem__")) +def test_empty_transform_indexing(xp): + tf_many = rigid_transform_to_xp(RigidTransform.identity(3), xp=xp) + tf_zero = tf_many[xp.asarray([], dtype=xp.int32)] + assert len(tf_zero) == 0 + + # Array API does not specify out-of-bounds indexing. Only check for numpy. + if is_numpy(xp): + assert len(tf_zero[:5]) == 0 # Slices can go out of bounds. + + with pytest.raises(IndexError): + tf_zero[0] + + with pytest.raises(IndexError): + tf_zero[xp.asarray([0, 2])] + + with pytest.raises(IndexError): + tf_zero[xp.asarray([False, True])] + + +@make_xp_test_case((RigidTransform, "from_matrix")) +@pytest.mark.skip_xp_backends("array_api_strict", + reason="array API doesn't support pickling") +def test_pickling(xp): + # Note: Array API makes no provision for arrays to be pickleable, so + # it's OK to skip this test for the backends that don't support it + mat = xp.eye(4) + mat = xpx.at(mat)[0, 3].set(2.0) + tf = RigidTransform.from_matrix(mat) + pkl = pickle.dumps(tf) + unpickled = pickle.loads(pkl) + xp_assert_close(tf.as_matrix(), unpickled.as_matrix(), atol=1e-15) + + +@make_xp_test_case( + (RigidTransform, "as_matrix"), + (RigidTransform, "__iter__"), + (RigidTransform, "identity"), +) +def test_rigid_transform_iter(xp): + r = rigid_transform_to_xp(RigidTransform.identity(3), xp) + for i, r_i in enumerate(r): + assert isinstance(r_i, RigidTransform) + xp_assert_equal(r_i.as_matrix(), r[i].as_matrix()) + if i > len(r): + raise RuntimeError("Iteration exceeded length of transforms") + + +@make_xp_test_case((RigidTransform, "from_translation")) +@pytest.mark.parametrize("dim", range(1, 5)) +def test_shape_property(xp, dim: int): + shape = (dim,) * (dim - 1) + tf = RigidTransform.from_translation(xp.zeros(shape + (3,))) + assert tf.shape == shape + + +def test_non_writeable(): + mat = np.eye(4) + mat.flags.writeable = False + RigidTransform.from_matrix(mat) # Regression test against gh-24378 diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/transform/tests/test_rotation.py b/venv/lib/python3.12/site-packages/scipy/spatial/transform/tests/test_rotation.py new file mode 100644 index 0000000000000000000000000000000000000000..b2f96b93c20e41389ef64912285147e77a52cddd --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/transform/tests/test_rotation.py @@ -0,0 +1,3223 @@ +import math + +import pytest + +import numpy as np +from numpy.testing import assert_equal +from scipy.spatial.transform import Rotation, Slerp +import scipy.spatial.transform._rotation_cy as cython_backend +import scipy.spatial.transform._rotation_xp as xp_backend +from scipy.stats import special_ortho_group +from itertools import permutations, product +from contextlib import contextmanager +import warnings +from scipy._lib._array_api import ( + xp_assert_equal, + array_namespace, + is_numpy, + is_lazy_array, + xp_vector_norm, + xp_assert_close, + eager_warns, + xp_default_dtype, + make_xp_test_case, + make_xp_pytest_marks, + xp_device_type, +) +import scipy._external.array_api_extra as xpx + +import pickle +import copy + + +# from_quat and as_quat are used in almost all tests, so we mark them module-wide +pytestmark = make_xp_pytest_marks((Rotation, "as_quat"), (Rotation, "from_quat")) + + +def basis_vec(axis): + if axis == 'x': + return [1, 0, 0] + elif axis == 'y': + return [0, 1, 0] + elif axis == 'z': + return [0, 0, 1] + + +def rotation_to_xp(r: Rotation, xp): + dtype = xpx.default_dtype(xp) + return Rotation.from_quat(xp.asarray(r.as_quat(), dtype=dtype)) + + +def test_init_non_array(): + Rotation((0, 0, 0, 1)) + Rotation([0, 0, 0, 1]) + Rotation([[[0, 0, 0, 1]]]) + + +def test_cython_backend_selection(): + r = Rotation.from_quat(np.array([0, 0, 0, 1])) + assert r._backend is cython_backend + r = Rotation.from_quat(np.array([[0, 0, 0, 1]])) + assert r._backend is cython_backend + r = Rotation.from_quat(np.array([[[0, 0, 0, 1]]])) + assert r._backend is xp_backend + + +def test_numpy_float32_inputs(): + Rotation.from_quat(np.array([1, 0, 0, 0], dtype=np.float32)) + + +def test_generic_quat_matrix(xp): + x = xp.asarray([[3.0, 4, 0, 0], [5, 12, 0, 0]]) + r = Rotation.from_quat(x) + expected_quat = x / xp.asarray([[5.0], [13.0]]) + xp_assert_close(r.as_quat(), expected_quat) + + +@pytest.mark.parametrize("ndim", list(range(1, 6))) +def test_from_single_nd_quaternion(xp, ndim: int): + x = xp.asarray([3.0, 4, 0, 0]) + x = xp.reshape(x, (1,) * (ndim - 1) + (4,)) + r = Rotation.from_quat(x) + expected_quat = x / 5 + xp_assert_close(r.as_quat(), expected_quat) + + +@make_xp_test_case((Rotation, "as_matrix")) +def test_from_quat_scalar_first(xp): + rng = np.random.RandomState(0) + + r = Rotation.from_quat(xp.asarray([1, 0, 0, 0]), scalar_first=True) + xp_assert_close(r.as_matrix(), xp.eye(3), rtol=1e-15, atol=1e-16) + + q = xp.tile(xp.asarray([1, 0, 0, 0]), (10, 1)) + r = Rotation.from_quat(q, scalar_first=True) + xp_assert_close( + r.as_matrix(), xp.tile(xp.eye(3), (10, 1, 1)), rtol=1e-15, atol=1e-16 + ) + + q = xp.asarray(rng.randn(100, 4)) + q /= xp_vector_norm(q, axis=1)[:, None] + for i in range(q.shape[0]): # Array API conforming loop + qi = q[i, ...] + r = Rotation.from_quat(qi, scalar_first=True) + xp_assert_close(xp.roll(r.as_quat(), 1), qi, rtol=1e-15) + + r = Rotation.from_quat(q, scalar_first=True) + xp_assert_close(xp.roll(r.as_quat(), 1, axis=1), q, rtol=1e-15) + + +def test_from_quat_array_like(): + rng = np.random.default_rng(123) + # Single rotation + r_expected = Rotation.random(rng=rng) + r = Rotation.from_quat(r_expected.as_quat().tolist()) + assert r_expected.approx_equal(r, atol=1e-12) + + # Multiple rotations + r_expected = Rotation.random(3, rng=rng) + r = Rotation.from_quat(r_expected.as_quat().tolist()) + assert np.all(r_expected.approx_equal(r, atol=1e-12)) + + # Tensor of rotations + q = rng.normal(size=(3, 4, 5, 4)) + q /= xp_vector_norm(q, axis=-1)[..., None] + r_expected = Rotation.from_quat(q) + r = Rotation.from_quat(q) + assert np.all(r_expected.approx_equal(r, atol=1e-12)) + + +def test_from_quat_int_dtype(xp): + r = Rotation.from_quat(xp.asarray([1, 0, 0, 0])) + assert r.as_quat().dtype == xp_default_dtype(xp) + + +def test_quat_canonical(xp): + # Case 0: w < 0 + q = xp.asarray([0.0, 0, 0, -1]) + xp_assert_close(Rotation.from_quat(q).as_quat(canonical=True), -q) + # Case 1: w == 0, x < 0 + q = xp.asarray([-1.0, 0, 0, 0]) + xp_assert_close(Rotation.from_quat(q).as_quat(canonical=True), -q) + # Case 2: w == 0, x == 0, y < 0 + q = xp.asarray([0.0, -1, 0, 0]) + xp_assert_close(Rotation.from_quat(q).as_quat(canonical=True), -q) + # Case 3: w == 0, x == 0, y == 0, z < 0 + q = xp.asarray([0.0, 0, -1, 0]) + xp_assert_close(Rotation.from_quat(q).as_quat(canonical=True), -q) + # Other cases: w > 0, y < 0 + q = xp.asarray([0.0, -0.1, 0, 0.9]) + q = q / xp_vector_norm(q) + xp_assert_close(Rotation.from_quat(q).as_quat(canonical=True), q) + # Other cases: w > 0, z < 0 + q = xp.asarray([0.0, 0.0, -0.1, 0.9]) + q = q / xp_vector_norm(q) + xp_assert_close(Rotation.from_quat(q).as_quat(canonical=True), q) + + +@make_xp_test_case((Rotation, "from_euler")) +def test_as_quat_scalar_first(xp): + rng = np.random.RandomState(0) + + r = Rotation.from_euler('xyz', xp.zeros(3)) + xp_assert_close(r.as_quat(scalar_first=True), xp.asarray([1.0, 0, 0, 0]), + rtol=1e-15, atol=1e-16) + + r = Rotation.from_euler('xyz', xp.zeros((10, 3))) + xp_assert_close(r.as_quat(scalar_first=True), + xp.tile(xp.asarray([1.0, 0, 0, 0]), (10, 1)), + rtol=1e-15, atol=1e-16) + + q = xp.asarray(rng.randn(100, 4)) + q /= xp_vector_norm(q, axis=1)[:, None] + for i in range(q.shape[0]): # Array API conforming loop + qi = q[i, ...] + r = Rotation.from_quat(qi) + xp_assert_close(r.as_quat(scalar_first=True), xp.roll(qi, 1), + rtol=1e-15) + + xp_assert_close(r.as_quat(canonical=True, scalar_first=True), + xp.roll(r.as_quat(canonical=True), 1), + rtol=1e-15) + + r = Rotation.from_quat(q) + xp_assert_close(r.as_quat(scalar_first=True), xp.roll(q, 1, axis=1), + rtol=1e-15) + + xp_assert_close(r.as_quat(canonical=True, scalar_first=True), + xp.roll(r.as_quat(canonical=True), 1, axis=1), rtol=1e-15) + + +def test_from_square_quat_matrix(xp): + # Ensure proper norm array broadcasting + x = xp.asarray([ + [3.0, 0, 0, 4], + [5, 0, 12, 0], + [0, 0, 0, 1], + [-1, -1, -1, 1], + [0, 0, 0, -1], # Check double cover + [-1, -1, -1, -1] # Check double cover + ]) + r = Rotation.from_quat(x) + expected_quat = x / xp.asarray([[5.0], [13], [1], [2], [1], [2]]) + xp_assert_close(r.as_quat(), expected_quat) + + +def test_quat_double_to_canonical_single_cover(xp): + x = xp.asarray([ + [-1.0, 0, 0, 0], + [0, -1, 0, 0], + [0, 0, -1, 0], + [0, 0, 0, -1], + [-1, -1, -1, -1] + ]) + r = Rotation.from_quat(x) + expected_quat = xp.abs(x) / xp_vector_norm(x, axis=1)[:, None] + xp_assert_close(r.as_quat(canonical=True), expected_quat) + + +@make_xp_test_case((Rotation, "inv"), (Rotation, "__mul__")) +def test_quat_double_cover(xp): + # See the Rotation.from_quat() docstring for scope of the quaternion + # double cover property. + # Check from_quat and as_quat(canonical=False) + q = xp.asarray([0.0, 0, 0, -1]) + r = Rotation.from_quat(q) + xp_assert_equal(q, r.as_quat(canonical=False)) + # Check composition and inverse + q = xp.asarray([1.0, 0, 0, 1])/math.sqrt(2) # 90 deg rotation about x + r = Rotation.from_quat(q) + r3 = r*r*r + xp_assert_close(r.as_quat(canonical=False)*math.sqrt(2), + xp.asarray([1.0, 0, 0, 1])) + xp_assert_close(r.inv().as_quat(canonical=False)*math.sqrt(2), + xp.asarray([-1.0, 0, 0, 1])) + xp_assert_close(r3.as_quat(canonical=False)*math.sqrt(2), + xp.asarray([1.0, 0, 0, -1])) + xp_assert_close(r3.inv().as_quat(canonical=False)*math.sqrt(2), + xp.asarray([-1.0, 0, 0, -1])) + + # More sanity checks + xp_assert_close((r*r.inv()).as_quat(canonical=False), + xp.asarray([0.0, 0, 0, 1]), atol=2e-16) + xp_assert_close((r3*r3.inv()).as_quat(canonical=False), + xp.asarray([0.0, 0, 0, 1]), atol=2e-16) + xp_assert_close((r*r3).as_quat(canonical=False), + xp.asarray([0.0, 0, 0, -1]), atol=2e-16) + xp_assert_close((r.inv() * r3.inv()).as_quat(canonical=False), + xp.asarray([0.0, 0, 0, -1]), atol=2e-16) + + +@pytest.mark.parametrize("ndim", list(range(1, 6))) +def test_from_quat_wrong_shape(xp, ndim: int): + quat = xp.zeros((*((1,) * ndim), 5)) + with pytest.raises(ValueError, match="Expected `quat` to have shape"): + Rotation.from_quat(quat) + + +def test_zero_norms_from_quat(xp): + x = xp.asarray([ + [3, 4, 0, 0], + [0, 0, 0, 0], + [5, 0, 12, 0] + ]) + if is_lazy_array(x): + assert xp.all(xp.isnan(Rotation.from_quat(x).as_quat()[1, ...])) + else: + with pytest.raises(ValueError): + Rotation.from_quat(x) + + +@make_xp_test_case((Rotation, "as_matrix")) +@pytest.mark.parametrize("ndim", list(range(1, 6))) +def test_as_matrix_single_nd_quaternion(xp, ndim: int): + quat = xp.asarray([0, 0, 1, 1]) + quat = xp.reshape(quat, (1,) * (ndim - 1) + (4,)) + mat = Rotation.from_quat(quat).as_matrix() + expected_mat = xp.asarray([ + [0.0, -1, 0], + [1, 0, 0], + [0, 0, 1] + ]) + expected_mat = xp.reshape(expected_mat, (1,) * (ndim - 1) + (3, 3)) + xp_assert_close(mat, expected_mat, atol=1e-16) + + +@make_xp_test_case((Rotation, "as_matrix")) +def test_as_matrix_from_square_input(xp): + quats = xp.asarray([ + [0, 0, 1, 1], + [0, 1, 0, 1], + [0, 0, 0, 1], + [0, 0, 0, -1] + ]) + mat = Rotation.from_quat(quats).as_matrix() + assert_equal(mat.shape, (4, 3, 3)) + + expected0 = xp.asarray([ + [0.0, -1, 0], + [1, 0, 0], + [0, 0, 1] + ]) + xp_assert_close(mat[0, ...], expected0, atol=1e-16) + + expected1 = xp.asarray([ + [0.0, 0, 1], + [0, 1, 0], + [-1, 0, 0] + ]) + xp_assert_close(mat[1, ...], expected1, atol=1e-16) + xp_assert_close(mat[2, ...], xp.eye(3)) + xp_assert_close(mat[3, ...], xp.eye(3)) + + +@make_xp_test_case((Rotation, "as_matrix")) +def test_as_matrix_from_generic_input(xp): + quats = xp.asarray([ + [0, 0, 1, 1], + [0, 1, 0, 1], + [1, 2, 3, 4] + ]) + mat = Rotation.from_quat(quats).as_matrix() + assert_equal(mat.shape, (3, 3, 3)) + + expected0 = xp.asarray([ + [0.0, -1, 0], + [1, 0, 0], + [0, 0, 1] + ]) + xp_assert_close(mat[0, ...], expected0, atol=1e-16) + + expected1 = xp.asarray([ + [0.0, 0, 1], + [0, 1, 0], + [-1, 0, 0] + ]) + xp_assert_close(mat[1, ...], expected1, atol=1e-16) + + expected2 = xp.asarray([ + [0.4, -2, 2.2], + [2.8, 1, 0.4], + [-1, 2, 2] + ]) / 3 + xp_assert_close(mat[2, ...], expected2) + + +@make_xp_test_case((Rotation, "from_matrix")) +@pytest.mark.parametrize("ndim", list(range(1, 6))) +def test_from_single_nd_matrix(xp, ndim: int): + mat = xp.asarray([ + [0, 0, 1], + [1, 0, 0], + [0, 1, 0] + ]) + mat = xp.reshape(mat, (1,) * (ndim - 1) + (3, 3)) + expected_quat = xp.asarray([0.5, 0.5, 0.5, 0.5]) + expected_quat = xp.reshape(expected_quat, (1,) * (ndim - 1) + (4,)) + xp_assert_close(Rotation.from_matrix(mat).as_quat(), expected_quat) + + +@make_xp_test_case((Rotation, "from_matrix")) +def test_from_matrix_calculation(xp): + atol = 1e-8 + expected_quat = xp.asarray([1.0, 1, 6, 1]) / math.sqrt(39) + mat = xp.asarray([ + [-0.8974359, -0.2564103, 0.3589744], + [0.3589744, -0.8974359, 0.2564103], + [0.2564103, 0.3589744, 0.8974359] + ]) + xp_assert_close(Rotation.from_matrix(mat).as_quat(), expected_quat, atol=atol) + xp_assert_close(Rotation.from_matrix(xp.reshape(mat, (1, 3, 3))).as_quat(), + xp.reshape(expected_quat, (1, 4)), + atol=atol) + + +@make_xp_test_case((Rotation, "from_matrix"), (Rotation, "as_matrix")) +def test_matrix_calculation_pipeline(xp): + mat = xp.asarray(special_ortho_group.rvs(3, size=10, random_state=0)) + xp_assert_close(Rotation.from_matrix(mat).as_matrix(), mat) + + +@make_xp_test_case((Rotation, "from_matrix"), (Rotation, "as_matrix")) +def test_from_matrix_ortho_output(xp): + dtype = xpx.default_dtype(xp) + atol = 1e-12 if dtype == xp.float64 else 1e-6 + rnd = np.random.RandomState(0) + mat = xp.asarray(rnd.random_sample((100, 3, 3)), dtype=dtype) + dets = xp.linalg.det(mat) + for i in range(dets.shape[0]): + # Make sure we have a right-handed rotation matrix + if dets[i] < 0: + mat = xpx.at(mat)[i, ...].set(-mat[i, ...]) + ortho_mat = Rotation.from_matrix(mat).as_matrix() + + mult_result = xp.matmul(ortho_mat, xp.matrix_transpose(ortho_mat)) + + eye3d = xp.zeros((100, 3, 3)) + xp.eye(3) + xp_assert_close(mult_result, eye3d, atol=atol) + + +@make_xp_test_case((Rotation, "from_matrix"), (Rotation, "as_matrix")) +def test_from_matrix_normalize(xp): + mat = xp.asarray([ + [1, 1, 0], + [0, 1, 0], + [0, 0, 1]]) + expected = xp.asarray([[ 0.894427, 0.447214, 0.0], + [-0.447214, 0.894427, 0.0], + [ 0.0, 0.0, 1.0]]) + xp_assert_close(Rotation.from_matrix(mat).as_matrix(), expected, atol=1e-6) + + mat = xp.asarray([ + [0, -0.5, 0 ], + [0.5, 0 , 0 ], + [0, 0 , 0.5]]) + expected = xp.asarray([[0.0, -1, 0], + [ 1, 0, 0], + [ 0, 0, 1]]) + xp_assert_close(Rotation.from_matrix(mat).as_matrix(), expected, atol=1e-6) + + # Test a mix of normalized and non-normalized matrices + mat = xp.stack([mat, xp.eye(3)]) + expected = xp.stack([expected, xp.eye(3)]) + xp_assert_close(Rotation.from_matrix(mat).as_matrix(), expected, atol=1e-6) + + +@make_xp_test_case((Rotation, "from_matrix"), (Rotation, "as_matrix")) +def test_from_matrix_assume_valid(xp): + rng = np.random.default_rng(0) + dtype = xpx.default_dtype(xp) + atol = 1e-12 if dtype == xp.float64 else 1e-6 + # Test that normal matrices remain unchanged + rot = rotation_to_xp(Rotation.from_quat(rng.normal(size=(10, 4))), xp) + rot_no_norm = Rotation.from_matrix(rot.as_matrix(), assume_valid=True) + assert xp.all(rot.approx_equal(rot_no_norm, atol=atol)) + # We make no guarantees about matrices that are not orthogonal or do not + # have unit norm + + +@make_xp_test_case((Rotation, "from_matrix"), (Rotation, "as_matrix")) +def test_from_matrix_non_positive_determinant(xp): + mat = xp.eye(3) + mat = xpx.at(mat)[0, 0].set(0) + if is_lazy_array(mat): + assert xp.all(xp.isnan(Rotation.from_matrix(mat).as_matrix())) + else: + with pytest.raises(ValueError, match="Non-positive determinant"): + Rotation.from_matrix(mat) + + mat = xpx.at(mat)[0, 0].set(-1) + if is_lazy_array(mat): + assert xp.all(xp.isnan(Rotation.from_matrix(mat).as_matrix())) + else: + with pytest.raises(ValueError, match="Non-positive determinant"): + Rotation.from_matrix(mat) + + +def test_from_matrix_array_like(): + rng = np.random.default_rng(123) + # Single rotation + r_expected = Rotation.random(rng=rng) + r = Rotation.from_matrix(r_expected.as_matrix().tolist()) + assert r_expected.approx_equal(r, atol=1e-12) + + # Multiple rotations + r_expected = Rotation.random(3, rng=rng) + r = Rotation.from_matrix(r_expected.as_matrix().tolist()) + assert np.all(r_expected.approx_equal(r, atol=1e-12)) + + +@make_xp_test_case((Rotation, "from_matrix")) +def test_from_matrix_int_dtype(xp): + mat = xp.asarray([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) + r = Rotation.from_matrix(mat) + assert r.as_quat().dtype == xp_default_dtype(xp) + + +@make_xp_test_case((Rotation, "from_rotvec")) +@pytest.mark.parametrize("ndim", list(range(1, 6))) +def test_from_nd_single_rotvec(xp, ndim: int): + atol = 1e-7 + rotvec = xp.asarray([1, 0, 0]) + rotvec = xp.reshape(rotvec, (1,) * (ndim - 1) + (3,)) + expected_quat = xp.asarray([0.4794255, 0, 0, 0.8775826]) + expected_quat = xp.reshape(expected_quat, (1,) * (ndim - 1) + (4,)) + result = Rotation.from_rotvec(rotvec) + xp_assert_close(result.as_quat(), expected_quat, atol=atol) + + +@make_xp_test_case((Rotation, "from_rotvec")) +def test_from_generic_rotvec(xp): + atol = 1e-7 + rotvec = xp.asarray([ + [1, 2, 2], + [1, -1, 0.5], + [0, 0, 0]]) + expected_quat = xp.asarray([ + [0.3324983, 0.6649967, 0.6649967, 0.0707372], + [0.4544258, -0.4544258, 0.2272129, 0.7316889], + [0, 0, 0, 1] + ]) + xp_assert_close(Rotation.from_rotvec(rotvec).as_quat(), expected_quat, atol=atol) + + +@make_xp_test_case((Rotation, "from_rotvec")) +def test_from_rotvec_small_angle(xp): + rotvec = xp.asarray([ + [5e-4 / math.sqrt(3), -5e-4 / math.sqrt(3), 5e-4 / math.sqrt(3)], + [0.2, 0.3, 0.4], + [0, 0, 0] + ]) + + quat = Rotation.from_rotvec(rotvec).as_quat() + # cos(theta/2) ~~ 1 for small theta + xp_assert_close(quat[0, 3], xp.asarray(1.0)[()]) + # sin(theta/2) / theta ~~ 0.5 for small theta + xp_assert_close(quat[0, :3], rotvec[0, ...] * 0.5) + + xp_assert_close(quat[1, 3], xp.asarray(0.9639685)[()]) + xp_assert_close(quat[1, :3], + xp.asarray([ + 0.09879603932153465, + 0.14819405898230198, + 0.19759207864306931])) + + xp_assert_equal(quat[2, ...], xp.asarray([0.0, 0, 0, 1])) + + +@make_xp_test_case((Rotation, "from_rotvec"), (Rotation, "as_rotvec")) +def test_from_rotvec_array_like(): + rng = np.random.default_rng(123) + # Single rotation + r_expected = Rotation.random(rng=rng) + r = Rotation.from_rotvec(r_expected.as_rotvec().tolist()) + assert r_expected.approx_equal(r, atol=1e-12) + + # Multiple rotations + r_expected = Rotation.random(3, rng=rng) + r = Rotation.from_rotvec(r_expected.as_rotvec().tolist()) + assert np.all(r_expected.approx_equal(r, atol=1e-12)) + + +@make_xp_test_case((Rotation, "from_rotvec")) +def test_from_rotvec_int_dtype(xp): + rotvec = xp.asarray([1, 0, 0]) + r = Rotation.from_rotvec(rotvec) + assert r.as_quat().dtype == xp_default_dtype(xp) + + +@make_xp_test_case((Rotation, "from_rotvec")) +def test_degrees_from_rotvec(xp): + rotvec1 = xp.asarray([1 / 3 ** (1/3)] * 3) + rot1 = Rotation.from_rotvec(rotvec1, degrees=True) + quat1 = rot1.as_quat() + + # deg2rad is not implemented in Array API -> / 180 * xp.pi + rotvec2 = xp.asarray(rotvec1 / 180 * xp.pi) + rot2 = Rotation.from_rotvec(rotvec2) + quat2 = rot2.as_quat() + + xp_assert_close(quat1, quat2) + + +@make_xp_test_case((Rotation, "from_rotvec")) +def test_malformed_1d_from_rotvec(xp): + with pytest.raises(ValueError, match='Expected `rot_vec` to have shape'): + Rotation.from_rotvec(xp.asarray([1, 2])) + + +@make_xp_test_case((Rotation, "from_rotvec")) +@pytest.mark.parametrize("ndim", list(range(1, 6))) +def test_malformed_nd_from_rotvec(xp, ndim: int): + shape = (1,) * (ndim - 1) + (2,) + with pytest.raises(ValueError, match='Expected `rot_vec` to have shape'): + Rotation.from_rotvec(xp.ones(shape)) + + +@make_xp_test_case((Rotation, "as_rotvec")) +def test_as_generic_rotvec(xp): + dtype = xpx.default_dtype(xp) + atol = 1e-15 if dtype == xp.float64 else 1e-7 + quat = xp.asarray([ + [1, 2, -1, 0.5], + [1, -1, 1, 0.0003], + [0, 0, 0, 1] + ]) + quat /= xp_vector_norm(quat, axis=-1, keepdims=True) + + rotvec = Rotation.from_quat(quat).as_rotvec() + angle = xp_vector_norm(rotvec, axis=-1) + + xp_assert_close(quat[:, 3], xp.cos(angle / 2)) + xp_assert_close(xp.linalg.cross(rotvec, quat[:, :3]), xp.zeros((3, 3)), atol=atol) + + +@make_xp_test_case((Rotation, "as_rotvec")) +@pytest.mark.parametrize("ndim", list(range(1, 4))) +def test_as_rotvec_single_nd_input(xp, ndim: int): + quat = xp.asarray([1, 2, -3, 2]) + quat = xp.reshape(quat, (1,) * (ndim - 1) + (4,)) + expected_rotvec = xp.asarray([0.5772381, 1.1544763, -1.7317144]) + expected_rotvec = xp.reshape(expected_rotvec, (1,) * (ndim - 1) + (3,)) + actual_rotvec = Rotation.from_quat(quat).as_rotvec() + + assert_equal(actual_rotvec.shape, expected_rotvec.shape) + xp_assert_close(actual_rotvec, expected_rotvec) + + +@make_xp_test_case((Rotation, "from_matrix"), (Rotation, "as_rotvec")) +def test_as_rotvec_degrees(xp): + # x->y, y->z, z->x + mat = xp.asarray([[0, 0, 1], [1, 0, 0], [0, 1, 0]]) + rot = Rotation.from_matrix(mat) + rotvec = rot.as_rotvec(degrees=True) + angle = xp_vector_norm(rotvec, axis=-1) + xp_assert_close(angle, xp.asarray(120.0)[()]) + xp_assert_close(rotvec[0], rotvec[1]) + xp_assert_close(rotvec[1], rotvec[2]) + + +@make_xp_test_case((Rotation, "from_rotvec"), (Rotation, "as_rotvec")) +def test_rotvec_calc_pipeline(xp): + # Include small angles + rotvec = xp.asarray([ + [0, 0, 0], + [1, -1, 2], + [-3e-4, 3.5e-4, 7.5e-5] + ]) + xp_assert_close(Rotation.from_rotvec(rotvec).as_rotvec(), rotvec) + xp_assert_close(Rotation.from_rotvec(rotvec, degrees=True).as_rotvec(degrees=True), + rotvec) + + +@make_xp_test_case((Rotation, "from_mrp")) +@pytest.mark.parametrize("ndim", range(1, 4)) +def test_from_mrp_single_nd_input(xp, ndim: int): + mrp = xp.asarray([0, 0, 1.0]) + mrp = xp.reshape(mrp, (1,) * (ndim - 1) + (3,)) + expected_quat = xp.asarray([0.0, 0, 1, 0]) + expected_quat = xp.reshape(expected_quat, (1,) * (ndim - 1) + (4,)) + result = Rotation.from_mrp(mrp) + xp_assert_close(result.as_quat(), expected_quat, atol=1e-12) + # Regression test for gh-24555 + assert isinstance(result._quat, type(array_namespace(mrp).empty(0))) + + +def test_from_mrp_array_like(): + rng = np.random.default_rng(123) + # Single rotation + r_expected = Rotation.random(rng=rng) + r = Rotation.from_mrp(r_expected.as_mrp().tolist()) + assert r_expected.approx_equal(r, atol=1e-12) + + # Multiple rotations + r_expected = Rotation.random(3, rng=rng) + r = Rotation.from_mrp(r_expected.as_mrp().tolist()) + assert np.all(r_expected.approx_equal(r, atol=1e-12)) + + +@make_xp_test_case((Rotation, "from_mrp")) +def test_from_mrp_int_dtype(xp): + mrp = xp.asarray([0, 0, 1]) + r = Rotation.from_mrp(mrp) + assert r.as_quat().dtype == xp_default_dtype(xp) + + +@make_xp_test_case((Rotation, "from_mrp")) +def test_from_generic_mrp(xp): + mrp = xp.asarray([ + [1, 2, 2], + [1, -1, 0.5], + [0, 0, 0]]) + expected_quat = xp.asarray([ + [0.2, 0.4, 0.4, -0.8], + [0.61538462, -0.61538462, 0.30769231, -0.38461538], + [0, 0, 0, 1]]) + xp_assert_close(Rotation.from_mrp(mrp).as_quat(), expected_quat) + + +@make_xp_test_case((Rotation, "from_mrp")) +@pytest.mark.parametrize("ndim", range(1, 4)) +def test_malformed_nd_from_mrp(xp, ndim: int): + shape = (1,) * (ndim - 1) + (2,) + with pytest.raises(ValueError, match='Expected `mrp` to have shape'): + Rotation.from_mrp(xp.ones(shape)) + + +@make_xp_test_case((Rotation, "as_mrp")) +def test_as_generic_mrp(xp): + quat = xp.asarray([ + [1, 2, -1, 0.5], + [1, -1, 1, 0.0003], + [0, 0, 0, 1]]) + quat /= xp_vector_norm(quat, axis=1)[:, None] + + expected_mrp = xp.asarray([ + [0.33333333, 0.66666667, -0.33333333], + [0.57725028, -0.57725028, 0.57725028], + [0, 0, 0]]) + xp_assert_close(Rotation.from_quat(quat).as_mrp(), expected_mrp) + + +@make_xp_test_case((Rotation, "from_euler"), (Rotation, "as_mrp")) +def test_past_180_degree_rotation(xp): + # ensure that a > 180 degree rotation is returned as a <180 rotation in MRPs + # in this case 270 should be returned as -90 + expected_mrp = xp.asarray([-math.tan(xp.pi / 2 / 4), 0.0, 0]) + xp_assert_close( + Rotation.from_euler('xyz', xp.asarray([270, 0, 0]), degrees=True).as_mrp(), + expected_mrp, + ) + + +@make_xp_test_case((Rotation, "as_mrp")) +@pytest.mark.parametrize("ndim", list(range(1, 4))) +def test_as_mrp_single_nd_input(xp, ndim: int): + quat = xp.asarray([1, 2, -3, 2]) + quat = xp.reshape(quat, (1,) * (ndim - 1) + (4,)) + expected_mrp = xp.asarray([0.16018862, 0.32037724, -0.48056586]) + expected_mrp = xp.reshape(expected_mrp, (1,) * (ndim - 1) + (3,)) + actual_mrp = Rotation.from_quat(quat).as_mrp() + + assert_equal(actual_mrp.shape, expected_mrp.shape) + xp_assert_close(actual_mrp, expected_mrp) + + +@make_xp_test_case((Rotation, "from_mrp"), (Rotation, "as_mrp")) +def test_mrp_calc_pipeline(xp): + actual_mrp = xp.asarray([ + [0, 0, 0], + [1, -1, 2], + [0.41421356, 0, 0], + [0.1, 0.2, 0.1]]) + expected_mrp = xp.asarray([ + [0, 0, 0], + [-0.16666667, 0.16666667, -0.33333333], + [0.41421356, 0, 0], + [0.1, 0.2, 0.1]]) + xp_assert_close(Rotation.from_mrp(actual_mrp).as_mrp(), expected_mrp) + + +@make_xp_test_case((Rotation, "from_euler")) +def test_from_euler_single_rotation(xp): + quat = Rotation.from_euler("z", xp.asarray(90), degrees=True).as_quat() + expected_quat = xp.asarray([0.0, 0, 1, 1]) / math.sqrt(2) + xp_assert_close(quat, expected_quat) + + +@make_xp_test_case((Rotation, "from_euler")) +def test_from_euler_input_validation(xp): + # Single sequence with multiple angles + with pytest.raises(ValueError, match="Expected last dimension of `angles` to"): + Rotation.from_euler("X", xp.asarray([0, 90])) + # Multiple sequences with single angle + with pytest.raises(ValueError, match="Expected last dimension of `angles` to"): + Rotation.from_euler("XYZ", xp.asarray([90])) + + +@make_xp_test_case((Rotation, "from_euler")) +@pytest.mark.parametrize("ndim", list(range(1, 4))) +def test_from_euler_nd_rotation(xp, ndim: int): + angles = xp.reshape(xp.asarray([0, 0, 90]), (1,) * (ndim - 1) + (3,)) + quat = Rotation.from_euler("xyz", angles, degrees=True).as_quat() + expected_quat = xp.asarray([0.0, 0, 1, 1]) / math.sqrt(2) + expected_quat = xp.reshape(expected_quat, (1,) * (ndim - 1) + (4,)) + xp_assert_close(quat, expected_quat) + + +@make_xp_test_case((Rotation, "from_euler"), (Rotation, "as_matrix")) +def test_single_intrinsic_extrinsic_rotation(xp): + extrinsic = Rotation.from_euler('z', xp.asarray(90), degrees=True).as_matrix() + intrinsic = Rotation.from_euler('Z', xp.asarray(90), degrees=True).as_matrix() + xp_assert_close(extrinsic, intrinsic) + + +@make_xp_test_case((Rotation, "from_euler")) +def test_from_euler_rotation_order(xp): + # Intrinsic rotation is same as extrinsic with order reversed + rnd = np.random.RandomState(0) + a = xp.asarray(rnd.randint(low=0, high=180, size=(6, 3))) + b = xp.flip(a, axis=-1) + x = Rotation.from_euler('xyz', a, degrees=True).as_quat() + y = Rotation.from_euler('ZYX', b, degrees=True).as_quat() + xp_assert_close(x, y) + + +@make_xp_test_case((Rotation, "from_euler"), (Rotation, "as_matrix")) +def test_from_euler_elementary_extrinsic_rotation(xp): + atol = 1e-12 + # Simple test to check if extrinsic rotations are implemented correctly + mat = Rotation.from_euler('zx', xp.asarray([90, 90]), degrees=True).as_matrix() + expected_mat = xp.asarray([ + [0.0, -1, 0], + [0, 0, -1], + [1, 0, 0] + ]) + xp_assert_close(mat, expected_mat, atol=atol) + + +@make_xp_test_case((Rotation, "from_euler"), (Rotation, "as_matrix")) +def test_from_euler_intrinsic_rotation_312(xp): + atol = 1e-7 + angles = xp.asarray([ + [30, 60, 45], + [30, 60, 30], + [45, 30, 60] + ]) + mat = Rotation.from_euler('ZXY', angles, degrees=True).as_matrix() + + xp_assert_close(mat[0, ...], xp.asarray([ + [0.3061862, -0.2500000, 0.9185587], + [0.8838835, 0.4330127, -0.1767767], + [-0.3535534, 0.8660254, 0.3535534] + ]), atol=atol) + + xp_assert_close(mat[1, ...], xp.asarray([ + [0.5334936, -0.2500000, 0.8080127], + [0.8080127, 0.4330127, -0.3995191], + [-0.2500000, 0.8660254, 0.4330127] + ]), atol=atol) + + xp_assert_close(mat[2, ...], xp.asarray([ + [0.0473672, -0.6123725, 0.7891491], + [0.6597396, 0.6123725, 0.4355958], + [-0.7500000, 0.5000000, 0.4330127] + ]), atol=atol) + + +@make_xp_test_case((Rotation, "from_euler"), (Rotation, "as_matrix")) +def test_from_euler_intrinsic_rotation_313(xp): + angles = xp.asarray([ + [30, 60, 45], + [30, 60, 30], + [45, 30, 60] + ]) + mat = Rotation.from_euler('ZXZ', angles, degrees=True).as_matrix() + + xp_assert_close(mat[0, ...], xp.asarray([ + [0.43559574, -0.78914913, 0.4330127], + [0.65973961, -0.04736717, -0.750000], + [0.61237244, 0.61237244, 0.500000] + ])) + + xp_assert_close(mat[1, ...], xp.asarray([ + [0.6250000, -0.64951905, 0.4330127], + [0.64951905, 0.1250000, -0.750000], + [0.4330127, 0.750000, 0.500000] + ])) + + xp_assert_close(mat[2, ...], xp.asarray([ + [-0.1767767, -0.91855865, 0.35355339], + [0.88388348, -0.30618622, -0.35355339], + [0.4330127, 0.25000000, 0.8660254] + ])) + + +@make_xp_test_case((Rotation, "from_euler"), (Rotation, "as_matrix")) +def test_from_euler_extrinsic_rotation_312(xp): + angles = xp.asarray([ + [30, 60, 45], + [30, 60, 30], + [45, 30, 60] + ]) + mat = Rotation.from_euler('zxy', angles, degrees=True).as_matrix() + + xp_assert_close(mat[0, ...], xp.asarray([ + [0.91855865, 0.1767767, 0.35355339], + [0.25000000, 0.4330127, -0.8660254], + [-0.30618622, 0.88388348, 0.35355339] + ])) + + xp_assert_close(mat[1, ...], xp.asarray([ + [0.96650635, -0.0580127, 0.2500000], + [0.25000000, 0.4330127, -0.8660254], + [-0.0580127, 0.89951905, 0.4330127] + ])) + + xp_assert_close(mat[2, ...], xp.asarray([ + [0.65973961, -0.04736717, 0.7500000], + [0.61237244, 0.61237244, -0.5000000], + [-0.43559574, 0.78914913, 0.4330127] + ])) + + +@make_xp_test_case((Rotation, "from_euler"), (Rotation, "as_matrix")) +def test_from_euler_extrinsic_rotation_313(xp): + angles = xp.asarray([ + [30, 60, 45], + [30, 60, 30], + [45, 30, 60] + ]) + mat = Rotation.from_euler('zxz', angles, degrees=True).as_matrix() + + xp_assert_close(mat[0, ...], xp.asarray([ + [0.43559574, -0.65973961, 0.61237244], + [0.78914913, -0.04736717, -0.61237244], + [0.4330127, 0.75000000, 0.500000] + ])) + + xp_assert_close(mat[1, ...], xp.asarray([ + [0.62500000, -0.64951905, 0.4330127], + [0.64951905, 0.12500000, -0.750000], + [0.4330127, 0.75000000, 0.500000] + ])) + + xp_assert_close(mat[2, ...], xp.asarray([ + [-0.1767767, -0.88388348, 0.4330127], + [0.91855865, -0.30618622, -0.250000], + [0.35355339, 0.35355339, 0.8660254] + ])) + + +def test_from_euler_array_like(): + rng = np.random.default_rng(123) + order = "xyz" + # Single rotation + r_expected = Rotation.random(rng=rng) + r = Rotation.from_euler(order, r_expected.as_euler(order).tolist()) + assert r_expected.approx_equal(r, atol=1e-12) + + # Multiple rotations + r_expected = Rotation.random(3, rng=rng) + r = Rotation.from_euler(order, r_expected.as_euler(order).tolist()) + assert np.all(r_expected.approx_equal(r, atol=1e-12)) + + +def test_from_euler_scalar(): + rng = np.random.default_rng(123) + deg = rng.uniform(low=-180, high=180) + r_expected = Rotation.from_euler("x", deg, degrees=True) + r = Rotation.from_euler("x", float(deg), degrees=True) + assert r_expected.approx_equal(r, atol=1e-12) + + +@make_xp_test_case((Rotation, "from_euler"), (Rotation, "as_euler")) +@pytest.mark.parametrize("seq_tuple", list(permutations("xyz"))) +@pytest.mark.parametrize("intrinsic", (False, True)) +def test_as_euler_asymmetric_axes(xp, seq_tuple, intrinsic): + # helper function for mean error tests + def test_stats(error, mean_max, rms_max): + mean = xp.mean(error, axis=0) + std = xp.std(error, axis=0) + rms = xp.hypot(mean, std) + assert xp.all(xp.abs(mean) < mean_max) + assert xp.all(rms < rms_max) + + rnd = np.random.RandomState(0) + n = 1000 + angles = np.empty((n, 3)) + angles[:, 0] = rnd.uniform(low=-np.pi, high=np.pi, size=(n,)) + angles[:, 1] = rnd.uniform(low=-np.pi / 2, high=np.pi / 2, size=(n,)) + angles[:, 2] = rnd.uniform(low=-np.pi, high=np.pi, size=(n,)) + angles = xp.asarray(angles) + + seq = "".join(seq_tuple) + if intrinsic: + # Extrinsic rotation (w.r.t. global world) at lower case + # intrinsic (WRT the object itself) lower case. + seq = seq.upper() + rotation = Rotation.from_euler(seq, angles) + angles_quat = rotation.as_euler(seq) + xp_assert_close(angles, angles_quat, atol=0, rtol=1e-12) + test_stats(angles_quat - angles, 1e-15, 1e-14) + + +@make_xp_test_case((Rotation, "from_euler"), (Rotation, "as_euler")) +@pytest.mark.parametrize("seq_tuple", list(permutations("xyz"))) +@pytest.mark.parametrize("intrinsic", (False, True)) +def test_as_euler_symmetric_axes(xp, seq_tuple, intrinsic): + # helper function for mean error tests + def test_stats(error, mean_max, rms_max): + mean = xp.mean(error, axis=0) + std = xp.std(error, axis=0) + rms = xp.hypot(mean, std) + assert xp.all(xp.abs(mean) < mean_max) + assert xp.all(rms < rms_max) + + rnd = np.random.RandomState(0) + n = 1000 + angles = np.empty((n, 3)) + angles[:, 0] = rnd.uniform(low=-np.pi, high=np.pi, size=(n,)) + angles[:, 1] = rnd.uniform(low=0, high=np.pi, size=(n,)) + angles[:, 2] = rnd.uniform(low=-np.pi, high=np.pi, size=(n,)) + angles = xp.asarray(angles) + + # Rotation of the form A/B/A are rotation around symmetric axes + seq = "".join([seq_tuple[0], seq_tuple[1], seq_tuple[0]]) + if intrinsic: + seq = seq.upper() + rotation = Rotation.from_euler(seq, angles) + angles_quat = rotation.as_euler(seq) + xp_assert_close(angles, angles_quat, atol=0, rtol=1.1e-13) + test_stats(angles_quat - angles, 1e-16, 1e-14) + + +@contextmanager +def maybe_warn_gimbal_lock(should_warn, xp): + if should_warn: + # We can only warn on non-lazy backends because we'd need to condition on + # traced booleans + with eager_warns(UserWarning, match="Gimbal lock", xp=xp): + yield + + else: + with warnings.catch_warnings(): + warnings.simplefilter("error") + yield + + +@make_xp_test_case( + (Rotation, "from_euler"), (Rotation, "as_matrix"), (Rotation, "as_euler") +) +@pytest.mark.parametrize("seq_tuple", list(permutations("xyz"))) +@pytest.mark.parametrize("intrinsic", (False, True)) +@pytest.mark.parametrize("suppress_warnings", (False, True)) +def test_as_euler_degenerate_asymmetric_axes( + xp, seq_tuple, intrinsic, suppress_warnings +): + dtype = xpx.default_dtype(xp) + atol = 1e-12 if dtype == xp.float64 else 1e-6 + # Since we cannot check for angle equality, we check for rotation matrix + # equality + angles = xp.asarray([ + [45, 90, 35], + [35, -90, 20], + [35, 90, 25], + [25, -90, 15]]) + + seq = "".join(seq_tuple) + if intrinsic: + # Extrinsic rotation (w.r.t. global world) at lower case + # Intrinsic (WRT the object itself) upper case. + seq = seq.upper() + rotation = Rotation.from_euler(seq, angles, degrees=True) + mat_expected = rotation.as_matrix() + + with maybe_warn_gimbal_lock(not suppress_warnings, xp): + angle_estimates = rotation.as_euler( + seq, degrees=True, suppress_warnings=suppress_warnings + ) + mat_estimated = Rotation.from_euler(seq, angle_estimates, degrees=True).as_matrix() + + xp_assert_close(mat_expected, mat_estimated, atol=atol) + + +@make_xp_test_case( + (Rotation, "from_euler"), (Rotation, "as_matrix"), (Rotation, "as_euler") +) +@pytest.mark.parametrize("seq_tuple", list(permutations("xyz"))) +@pytest.mark.parametrize("intrinsic", (False, True)) +@pytest.mark.parametrize("suppress_warnings", (False, True)) +def test_as_euler_degenerate_symmetric_axes( + xp, seq_tuple, intrinsic, suppress_warnings +): + dtype = xpx.default_dtype(xp) + atol = 1e-12 if dtype == xp.float64 else 1e-6 + # Since we cannot check for angle equality, we check for rotation matrix + # equality + angles = xp.asarray([ + [15, 0, 60], + [35, 0, 75], + [60, 180, 35], + [15, -180, 25]]) + + # Rotation of the form A/B/A are rotation around symmetric axes + seq = "".join([seq_tuple[0], seq_tuple[1], seq_tuple[0]]) + if intrinsic: + # Extrinsic rotation (w.r.t. global world) at lower case + # Intrinsic (WRT the object itself) upper case. + seq = seq.upper() + rotation = Rotation.from_euler(seq, angles, degrees=True) + mat_expected = rotation.as_matrix() + + with maybe_warn_gimbal_lock(not suppress_warnings, xp): + angle_estimates = rotation.as_euler( + seq, degrees=True, suppress_warnings=suppress_warnings + ) + mat_estimated = Rotation.from_euler(seq, angle_estimates, degrees=True).as_matrix() + + xp_assert_close(mat_expected, mat_estimated, atol=atol) + + +@make_xp_test_case((Rotation, "from_euler"), (Rotation, "as_euler")) +@pytest.mark.parametrize("ndim", list(range(1, 4))) +def test_as_euler_nd_rotation(xp, ndim: int): + mat = xp.asarray([ + [0.0, -1, 0], + [1, 0, 0], + [0, 0, 1] + ]) + mat = xp.reshape(mat, (1,) * (ndim - 1) + (3, 3)) + angles = Rotation.from_matrix(mat).as_euler("xyz", degrees=True) + expected_angles = xp.asarray([0, 0, 90.0]) + expected_angles = xp.reshape(expected_angles, (1,) * (ndim - 1) + (3,)) + xp_assert_close(angles, expected_angles, atol=1e-12) + + +@make_xp_test_case((Rotation, "as_matrix"), (Rotation, "inv")) +def test_inv(xp): + dtype = xpx.default_dtype(xp) + atol = 1e-12 if dtype == xp.float64 else 1e-7 + rnd = np.random.RandomState(0) + n = 10 + # preserve use of old random_state during SPEC 7 transition + p = Rotation.random(num=n, random_state=rnd) + p = rotation_to_xp(p, xp) + p_mat = p.as_matrix() + q_mat = p.inv().as_matrix() + + result1 = p_mat @ q_mat + result2 = q_mat @ p_mat + + eye3d = xp.empty((n, 3, 3)) + eye3d = xpx.at(eye3d)[..., :3, :3].set(xp.eye(3)) + + xp_assert_close(result1, eye3d, atol=atol) + xp_assert_close(result2, eye3d, atol=atol) + + # Batched version + batch_shape = (10, 3, 7) + atol = 1e-12 if dtype == xp.float64 else 1e-6 + quat = xp.asarray(rnd.normal(size=batch_shape + (4,)), dtype=dtype) + r = Rotation.from_quat(quat) + p_mat = r.as_matrix() + q_mat = r.inv().as_matrix() + result1 = p_mat @ q_mat + result2 = q_mat @ p_mat + eye_nd = xp.empty(batch_shape + (3, 3)) + eye_nd = xpx.at(eye_nd)[..., :3, :3].set(xp.eye(3)) + xp_assert_close(result1, eye_nd, atol=atol) + xp_assert_close(result2, eye_nd, atol=atol) + + +@make_xp_test_case((Rotation, "inv"), (Rotation, "as_matrix")) +def test_inv_single_rotation(xp): + dtype = xpx.default_dtype(xp) + atol = 1e-12 if dtype == xp.float64 else 1e-7 + rng = np.random.default_rng(146972845698875399755764481408308808739) + p = rotation_to_xp(Rotation.random(rng=rng), xp) + q = p.inv() + + p_mat = p.as_matrix() + q_mat = q.as_matrix() + res1 = xp.matmul(p_mat, q_mat) + res2 = xp.matmul(q_mat, p_mat) + + eye = xp.eye(3) + + xp_assert_close(res1, eye, atol=atol) + xp_assert_close(res2, eye, atol=atol) + + x = rotation_to_xp(Rotation.random(num=1, rng=rng), xp) + y = x.inv() + + x_matrix = x.as_matrix() + y_matrix = y.as_matrix() + result1 = xp.linalg.matmul(x_matrix, y_matrix) + result2 = xp.linalg.matmul(y_matrix, x_matrix) + + eye3d = xp.empty((1, 3, 3)) + eye3d = xpx.at(eye3d)[..., :3, :3].set(xp.eye(3)) + + xp_assert_close(result1, eye3d, atol=atol) + xp_assert_close(result2, eye3d, atol=atol) + + +@make_xp_test_case((Rotation, "magnitude"), (Rotation, "inv")) +def test_identity_magnitude(xp): + n = 10 + r = rotation_to_xp(Rotation.identity(n), xp) + expected = xp.zeros(n) + xp_assert_close(r.magnitude(), expected) + xp_assert_close(r.inv().magnitude(), expected) + + +@make_xp_test_case((Rotation, "magnitude"), (Rotation, "inv")) +def test_single_identity_magnitude(xp): + r = rotation_to_xp(Rotation.identity(), xp) + assert r.magnitude() == 0 + assert r.inv().magnitude() == 0 + + +@make_xp_test_case((Rotation, "inv"), (Rotation, "magnitude")) +def test_identity_invariance(xp): + dtype = xpx.default_dtype(xp) + atol = 1e-12 if dtype == xp.float64 else 1e-7 + n = 10 + p = rotation_to_xp(Rotation.random(n, rng=0), xp) + q = rotation_to_xp(Rotation.identity(n), xp) + result = p * q + xp_assert_close(p.as_quat(), result.as_quat()) + + result = result * p.inv() + xp_assert_close(result.magnitude(), xp.zeros(n), atol=atol) + + +@make_xp_test_case((Rotation, "inv"), (Rotation, "magnitude")) +def test_single_identity_invariance(xp): + dtype = xpx.default_dtype(xp) + atol = 1e-12 if dtype == xp.float64 else 1e-7 + n = 10 + p = rotation_to_xp(Rotation.random(n, rng=0), xp) + q = rotation_to_xp(Rotation.identity(), xp) + result = p * q + xp_assert_close(p.as_quat(), result.as_quat()) + + result = result * p.inv() + xp_assert_close(result.magnitude(), xp.zeros(n), atol=atol) + + +def test_identity_shape(): # Not an xp test, identity is using numpy only for now + r = Rotation.identity(shape=()) + assert r.as_quat().shape == (4,) + r = Rotation.identity(shape=5) # Shape can be int + assert r.as_quat().shape == (5, 4) + r = Rotation.identity(shape=(2, 3)) + assert r.as_quat().shape == (2, 3, 4) + # Test values + r = Rotation.identity(shape=(2, 2, 3)) + xp_assert_equal(r.as_quat().reshape(-1, 4), np.tile(np.eye(4)[-1], (2 * 2 * 3, 1))) + # Errors + with pytest.raises(ValueError, match="`shape` must be an int or a tuple of ints"): + Rotation.identity(shape=2.5) + with pytest.raises(ValueError, match="Only one of `num` or `shape` can be"): + Rotation.identity(num=3, shape=(2, 2)) + with pytest.raises(TypeError, match="takes from 0 to 1 positional arguments"): + Rotation.identity(3, 3) + + +@make_xp_test_case((Rotation, "magnitude")) +@pytest.mark.parametrize("ndim", list(range(1, 4))) +def test_magnitude(xp, ndim: int): + quat_shape = (1,) * (ndim - 1) + (4,) + quat = xp.reshape(xp.eye(4), quat_shape + (4,)) + r = Rotation.from_quat(quat) + result = r.magnitude() + expected_result = xp.asarray([xp.pi, xp.pi, xp.pi, 0]) + expected_result = xp.reshape(expected_result, quat_shape) + xp_assert_close(result, expected_result) + + r = Rotation.from_quat(-quat) + result = r.magnitude() + xp_assert_close(result, expected_result) + + +@make_xp_test_case((Rotation, "magnitude")) +def test_magnitude_single_rotation(xp): + r = Rotation.from_quat(xp.eye(4)) + result1 = r[0].magnitude() + xp_assert_close(result1, xp.asarray(xp.pi)[()]) + + result2 = r[3].magnitude() + xp_assert_close(result2, xp.asarray(0.0)[()]) + + +@make_xp_test_case( + (Rotation, "inv"), (Rotation, "magnitude"), (Rotation, "approx_equal") +) +def test_approx_equal(xp): + rng = np.random.default_rng(146972845698875399755764481408308808739) + p = Rotation.random(10, rng=rng) + q = Rotation.random(10, rng=rng) + r_mag = (p * q.inv()).magnitude() + p = rotation_to_xp(p, xp) + q = rotation_to_xp(q, xp) + # ensure we get mix of Trues and Falses + atol = xp.asarray(np.median(r_mag)) + xp_assert_equal(p.approx_equal(q, atol), (xp.asarray(r_mag) < atol)) + + +@make_xp_test_case((Rotation, "from_rotvec"), (Rotation, "approx_equal")) +def test_approx_equal_single_rotation(xp): + # also tests passing single argument to approx_equal + p = Rotation.from_rotvec(xp.asarray([0, 0, 1e-9])) # less than default atol of 1e-8 + q = Rotation.from_quat(xp.eye(4)) + assert p.approx_equal(q[3]) + assert not p.approx_equal(q[0]) + # Regression test for gh-24769: single approx_equal should return a bool + assert isinstance(p.approx_equal(q[0]), np.bool_) or not is_numpy(xp) + + # test passing atol and using degrees + assert not p.approx_equal(q[3], atol=1e-10) + assert not p.approx_equal(q[3], atol=1e-8, degrees=True) + with pytest.warns(UserWarning, match="atol must be set"): + assert p.approx_equal(q[3], degrees=True) + + +@make_xp_test_case( + (Rotation, "inv"), (Rotation, "magnitude"), (Rotation, "approx_equal") +) +def test_approx_equal_batched(xp): + # Same shapes + batch_shape = (2, 10, 3) + rng = np.random.default_rng(0) + p = Rotation.from_quat(rng.normal(size=batch_shape + (4,))) + q = Rotation.from_quat(rng.normal(size=batch_shape + (4,))) + r_mag = (p * q.inv()).magnitude() # Must be computed as numpy array for np.median + p = rotation_to_xp(p, xp) + q = rotation_to_xp(q, xp) + assert r_mag.shape == batch_shape + # ensure we get mix of Trues and Falses + atol = xp.asarray(np.median(r_mag)) + xp_assert_equal(p.approx_equal(q, atol), (xp.asarray(r_mag) < atol)) + + # Broadcastable shapes of same length + p = Rotation.from_quat(rng.normal(size=batch_shape + (4,))) + q = Rotation.from_quat(rng.normal(size=(1, 10, 1, 4))) + r_mag = (p * q.inv()).magnitude() + p = rotation_to_xp(p, xp) + q = rotation_to_xp(q, xp) + assert r_mag.shape == batch_shape + atol = xp.asarray(np.median(r_mag)) + xp_assert_equal(p.approx_equal(q, atol), (xp.asarray(r_mag) < atol)) + + # Broadcastable shapes of different length + p = Rotation.from_quat(rng.normal(size=batch_shape + (4,))) + q = Rotation.from_quat(rng.normal(size=(1, 3, 4))) + r_mag = (p * q.inv()).magnitude() + p = rotation_to_xp(p, xp) + q = rotation_to_xp(q, xp) + assert r_mag.shape == batch_shape + atol = xp.asarray(np.median(r_mag)) + xp_assert_equal(p.approx_equal(q, atol), (xp.asarray(r_mag) < atol)) + + +@make_xp_test_case((Rotation, "approx_equal")) +def test_approx_equal_batched_input_validation(xp): + p = Rotation.from_quat(xp.ones((2, 3, 4))) + q = Rotation.from_quat(xp.ones((3, 2, 4))) + with pytest.raises(ValueError, match="broadcastable shapes"): + p.approx_equal(q) + + p = Rotation.from_quat(xp.ones((2, 4))) + q = Rotation.from_quat(xp.ones((3, 4))) + with pytest.raises(ValueError, match="broadcastable shapes"): + p.approx_equal(q) + + +@make_xp_test_case( + (Rotation, "from_rotvec"), (Rotation, "mean"), (Rotation, "magnitude") +) +@pytest.mark.parametrize("ndim", list(range(1, 4))) +def test_mean(xp, ndim: int): + axes = xp.concat((-xp.eye(3), xp.eye(3))) + axes = xp.reshape(axes, (1,) * (ndim - 1) + (6, 3)) + thetas = xp.linspace(0, xp.pi / 2, 100) + desired = xp.asarray(0.0)[()] + atol = 1e-6 if xp_default_dtype(xp) is xp.float32 else 1e-10 + for t in thetas: + r_mean = Rotation.from_rotvec(t * axes).mean() + assert r_mean.shape == () + xp_assert_close(r_mean.magnitude(), desired, atol=atol) + + +@make_xp_test_case( + (Rotation, "from_rotvec"), (Rotation, "mean"), (Rotation, "magnitude") +) +@pytest.mark.parametrize("ndim", list(range(1, 5))) +def test_mean_axis(xp, ndim: int): + axes = xp.tile(xp.concat((-xp.eye(3), xp.eye(3))), (3,) * (ndim - 1) + (1, 1)) + theta = xp.pi / 4 + r = Rotation.from_rotvec(theta * axes) + + # Test mean over last axis + desired = xp.full(axes.shape[:-2], 0.0) + if ndim == 1: + desired = desired[()] + atol = 1e-6 if xp_default_dtype(xp) is xp.float32 else 1e-10 + xp_assert_close(r.mean(axis=-1).magnitude(), desired, atol=atol) + + # Test tuple axes + desired = xp.full(axes.shape[1:-2], 0.0) + if ndim < 3: + desired = desired[()] + xp_assert_close(r.mean(axis=(0, -1)).magnitude(), desired, atol=atol) + + # Empty axis tuple should return Rotation unchanged + r_mean = r.mean(axis=()) + xp_assert_close(r_mean.as_quat(canonical=True), r.as_quat(canonical=True), + atol=atol) + + +@make_xp_test_case((Rotation, "mean"), (Rotation, "magnitude")) +def test_mean_compare_axis(xp): + # Create a random set of rotations and compare the mean over an axis with the + # mean without axis of the sliced quaternion + atol = 1e-10 if xpx.default_dtype(xp) == xp.float64 else 1e-6 + rng = np.random.default_rng(0) + q = xp.asarray(rng.normal(size=(4, 5, 6, 4)), dtype=xpx.default_dtype(xp)) + r = Rotation.from_quat(q) + + mean_0 = r.mean(axis=0) + for i in range(q.shape[1]): + for j in range(q.shape[2]): + mean_slice = Rotation.from_quat(q[:, i, j, ...]).mean() + xp_assert_close((mean_0[i][j] * mean_slice.inv()).magnitude(), + xp.asarray(0.0)[()], atol=atol) + mean_1_2 = r.mean(axis=(1, 2)) + for i in range(q.shape[0]): + mean_slice = Rotation.from_quat(q[i, ...]).mean() + xp_assert_close((mean_1_2[i] * mean_slice.inv()).magnitude(), + xp.asarray(0.0)[()], atol=atol) + + +@make_xp_test_case((Rotation, "from_rotvec"), (Rotation, "mean"), (Rotation, "inv"), + (Rotation, "magnitude")) +@pytest.mark.parametrize("ndim", list(range(1, 4))) +def test_weighted_mean(xp, ndim: int): + # test that doubling a weight is equivalent to including a rotation twice. + thetas = xp.linspace(0, xp.pi / 2, 100) + + # Create batched copies of the same setup + batch_shape = (ndim,) * (ndim - 1) + axes = xp.asarray([[0.0, 0, 0], [1, 0, 0], [1, 0, 0]]) + weights = xp.asarray([1, 2]) + axes = xp.tile(axes, batch_shape + (1, 1)) + weights = xp.tile(weights, batch_shape + (1,)) + + expected = xp.asarray(0.0)[()] + for t in thetas: + rw = Rotation.from_rotvec(t * axes[..., :2, :]) + mw = rw.mean(weights=weights) + + r = Rotation.from_rotvec(t * axes) + m = r.mean() + assert m.shape == () + xp_assert_close((m * mw.inv()).magnitude(), expected, atol=1e-6) + + +@make_xp_test_case((Rotation, "mean")) +def test_mean_input_validation(xp): + r = Rotation.from_quat(xp.eye(4)) + if is_lazy_array(r.as_quat()): + m = r.mean(weights=-xp.ones(4)) + assert xp.all(xp.isnan(m._quat)) + else: + with pytest.raises(ValueError, match="non-negative"): + r.mean(weights=-xp.ones(4)) + + # Test weight shape mismatch + r = Rotation.from_quat(xp.ones((3, 4))) + with pytest.raises(ValueError, match="Expected `weights` to"): + r.mean(weights=xp.ones((2,))) + r = Rotation.from_quat(xp.ones((2, 3, 4))) + with pytest.raises(ValueError, match="Expected `weights` to"): + r.mean(weights=xp.ones((2, 2))) + + # Test wrong axis + with pytest.raises(ValueError, match=r"axis .* is out of bounds"): + r.mean(axis=3) + with pytest.raises(ValueError, match=r"axis .* is out of bounds"): + r.mean(axis=(-1, 2)) + with pytest.raises(ValueError, match="`axis` must be None, int, or tuple of ints."): + r.mean(axis="0") + with pytest.raises(ValueError, match=r"axis .* is out of bounds"): + r.mean(axis=-12) + + +@make_xp_test_case((Rotation, "reduce")) +def test_reduction_no_indices(xp): + r = Rotation.from_quat(xp.asarray([0.0, 0.0, 0.0, 1.0])) + result = r.reduce(return_indices=False) + assert isinstance(result, Rotation) + + +@make_xp_test_case((Rotation, "reduce")) +def test_reduction_none_indices(xp): + r = Rotation.from_quat(xp.asarray([0.0, 0.0, 0.0, 1.0])) + result = r.reduce(return_indices=True) + assert type(result) is tuple + assert len(result) == 3 + + reduced, left_best, right_best = result + assert left_best is None + assert right_best is None + + +@make_xp_test_case((Rotation, "reduce"), (Rotation, "inv"), (Rotation, "magnitude")) +def test_reduction_scalar_calculation(xp): + dtype = xpx.default_dtype(xp) + atol = 1e-12 if dtype == xp.float64 else 1e-6 + rng = np.random.default_rng(146972845698875399755764481408308808739) + l_np = Rotation.random(5, rng=rng) + r_np = Rotation.random(10, rng=rng) + p_np = Rotation.random(7, rng=rng) + l = rotation_to_xp(l_np, xp) + r = rotation_to_xp(r_np, xp) + p = rotation_to_xp(p_np, xp) + reduced, left_best, right_best = p.reduce(l, r, return_indices=True) + + # Loop implementation of the vectorized calculation in Rotation.reduce + scalars = np.zeros((len(l_np), len(p_np), len(r_np))) + for i, li in enumerate(l_np): + for j, pj in enumerate(p_np): + for k, rk in enumerate(r_np): + scalars[i, j, k] = np.abs((li * pj * rk).as_quat()[3]) + scalars = np.reshape(np.moveaxis(scalars, 1, 0), (scalars.shape[1], -1)) + + max_ind = np.argmax(np.reshape(scalars, (len(p), -1)), axis=1) + left_best_check = xp.asarray(max_ind // len(r)) + right_best_check = xp.asarray(max_ind % len(r)) + assert xp.all(left_best == left_best_check) + assert xp.all(right_best == right_best_check) + + reduced_check = l[left_best_check] * p * r[right_best_check] + mag = (reduced.inv() * reduced_check).magnitude() + xp_assert_close(mag, xp.zeros(len(p)), atol=atol) + + +@make_xp_test_case((Rotation, "from_matrix"), (Rotation, "apply")) +def test_apply_single_rotation_single_point(xp): + dtype = xpx.default_dtype(xp) + mat = xp.asarray([ + [0, -1, 0], + [1, 0, 0], + [0, 0, 1] + ]) + r_1d = Rotation.from_matrix(mat) + r_2d = Rotation.from_matrix(xp.expand_dims(mat, axis=0)) + + v_1d = xp.asarray([1.0, 2, 3], dtype=dtype) + v_2d = xp.expand_dims(v_1d, axis=0) + v1d_rotated = xp.asarray([-2.0, 1, 3], dtype=dtype) + v2d_rotated = xp.expand_dims(v1d_rotated, axis=0) + + xp_assert_close(r_1d.apply(v_1d), v1d_rotated) + xp_assert_close(r_1d.apply(v_2d), v2d_rotated) + xp_assert_close(r_2d.apply(v_1d), v2d_rotated) + xp_assert_close(r_2d.apply(v_2d), v2d_rotated) + + v1d_inverse = xp.asarray([2.0, -1, 3], dtype=dtype) + v2d_inverse = xp.expand_dims(v1d_inverse, axis=0) + + xp_assert_close(r_1d.apply(v_1d, inverse=True), v1d_inverse) + xp_assert_close(r_1d.apply(v_2d, inverse=True), v2d_inverse) + xp_assert_close(r_2d.apply(v_1d, inverse=True), v2d_inverse) + xp_assert_close(r_2d.apply(v_2d, inverse=True), v2d_inverse) + + +@make_xp_test_case((Rotation, "from_matrix"), (Rotation, "apply")) +@pytest.mark.parametrize("ndim", list(range(1, 4))) +def test_apply_single_rotation_multiple_points(xp, ndim: int): + dtype = xpx.default_dtype(xp) + mat = xp.asarray([ + [0, -1, 0], + [1, 0, 0], + [0, 0, 1] + ]) + r1 = Rotation.from_matrix(mat) + r2 = Rotation.from_matrix(xp.expand_dims(mat, axis=0)) + + rng = np.random.default_rng(0) + batch_shape = (ndim,) * (ndim - 1) + v = xp.asarray(rng.normal(size=batch_shape + (2, 3)), dtype=dtype) + v_rotated = xp.stack([-v[..., 1], v[..., 0], v[..., 2]], axis=-1) + + xp_assert_close(r1.apply(v), v_rotated) + xp_assert_close(r2.apply(v), v_rotated) + + v_inverse = xp.stack([v[..., 1], -v[..., 0], v[..., 2]], axis=-1) + + xp_assert_close(r1.apply(v, inverse=True), v_inverse) + xp_assert_close(r2.apply(v, inverse=True), v_inverse) + + +@make_xp_test_case((Rotation, "from_matrix"), (Rotation, "apply")) +@pytest.mark.parametrize("ndim", list(range(1, 5))) +def test_apply_multiple_rotations_single_point(xp, ndim: int): + dtype = xpx.default_dtype(xp) + mat = np.empty((2, 3, 3)) + mat[0] = np.array([ + [0, -1, 0], + [1, 0, 0], + [0, 0, 1] + ]) + mat[1] = np.array([ + [1, 0, 0], + [0, 0, -1], + [0, 1, 0] + ]) + mat = xp.asarray(mat, dtype=dtype) + batch_shape = (ndim,) * (ndim - 1) + mat = xp.tile(mat, batch_shape + (1, 1, 1)) + r = Rotation.from_matrix(mat) + + v1 = xp.asarray([1, 2, 3]) + v2 = xp.expand_dims(v1, axis=0) + + v_rotated = xp.asarray([[-2.0, 1, 3], [1, -3, 2]]) + v_rotated = xp.tile(v_rotated, batch_shape + (1, 1)) + + xp_assert_close(r.apply(v1), v_rotated) + xp_assert_close(r.apply(v2), v_rotated) + + v_inverse = xp.asarray([[2.0, -1, 3], [1, 3, -2]]) + v_inverse = xp.tile(v_inverse, batch_shape + (1, 1)) + + xp_assert_close(r.apply(v1, inverse=True), v_inverse) + xp_assert_close(r.apply(v2, inverse=True), v_inverse) + + +@make_xp_test_case((Rotation, "from_matrix"), (Rotation, "apply")) +@pytest.mark.parametrize("ndim", list(range(1, 5))) +def test_apply_multiple_rotations_multiple_points(xp, ndim: int): + dtype = xpx.default_dtype(xp) + mat = np.empty((2, 3, 3)) + mat[0] = np.array([ + [0, -1, 0], + [1, 0, 0], + [0, 0, 1] + ]) + mat[1] = np.array([ + [1, 0, 0], + [0, 0, -1], + [0, 1, 0] + ]) + mat = xp.asarray(mat, dtype=dtype) + batch_shape = (ndim,) * (ndim - 1) + mat = xp.tile(mat, batch_shape + (1, 1, 1)) + r = Rotation.from_matrix(mat) + + v = xp.asarray([[1, 2, 3], [4, 5, 6]], dtype=dtype) + v_rotated = xp.asarray([[-2.0, 1, 3], [4, -6, 5]], dtype=dtype) + v_rotated = xp.tile(v_rotated, batch_shape + (1, 1)) + xp_assert_close(r.apply(v), v_rotated) + + v_inverse = xp.asarray([[2.0, -1, 3], [4, 6, -5]], dtype=dtype) + v_inverse = xp.tile(v_inverse, batch_shape + (1, 1)) + xp_assert_close(r.apply(v, inverse=True), v_inverse) + + +@make_xp_test_case((Rotation, "apply")) +def test_apply_shapes(xp): + rng = np.random.default_rng(0) + # Broadcast shape: (6, 5, 4, 2) ( + (3,) for vectors, + (4,) for rotations) + vector_shapes = [(), (1,), (2,), (1, 2), (5, 1, 2)] + rot_shapes = [(), (1,), (2,), (1, 2), (4, 2), (1, 4, 2), (5, 4, 2), (6, 5, 4, 2)] + + for q_shape, v_shape in product(rot_shapes, vector_shapes): + v = xp.asarray(rng.normal(size=v_shape + (3,))) + q = xp.asarray(rng.normal(size=q_shape + (4,))) + r = Rotation.from_quat(q) + shape = np.broadcast_shapes(q_shape, v_shape) + (3,) + x = r.apply(v) + assert x.shape == shape + x = r.apply(v, inverse=True) + assert x.shape == shape + + +def test_apply_array_like(): + rng = np.random.default_rng(123) + # Single vector + r = Rotation.random(rng=rng) + t = rng.uniform(-100, 100, size=(3,)) + v = r.apply(t.tolist()) + v_expected = r.apply(t) + xp_assert_close(v, v_expected, atol=1e-12) + # Multiple vectors + t = rng.uniform(-100, 100, size=(2, 3)) + v = r.apply(t.tolist()) + v_expected = r.apply(t) + xp_assert_close(v, v_expected, atol=1e-12) + + +def test_apply_matrix_equivalence(): + """Test documented equivalence for single rotation: + `apply(vectors) == vectors @ as_matrix().T.`""" + r = Rotation.from_rotvec([0, 0, 1]) + # Single vector (3,) + v = np.array([1.0, 0.0, 0.0]) + xp_assert_close(r.apply(v), v @ r.as_matrix().T) + # Multiple vectors (P, 3) + arr = np.array([[1, 0, 0], [1, 2, 3]], dtype=float) + xp_assert_close(r.apply(arr), arr @ r.as_matrix().T) + # (3, 3) case: `as_matrix() @ vectors` would not error but give wrong result + arr33 = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]], dtype=float) + xp_assert_close(r.apply(arr33), arr33 @ r.as_matrix().T) + wrong_result = r.as_matrix() @ arr33 + assert not np.allclose(r.apply(arr33), wrong_result) + + +@make_xp_test_case((Rotation, "apply")) +def test_apply_input_validation(xp): + r = Rotation.from_quat(xp.ones(4)) + with pytest.raises(ValueError, match="Expected input of shape"): + r.apply(xp.ones(2)) + with pytest.raises(ValueError, match="Expected input of shape"): + r.apply(xp.ones((2, 2))) + r = Rotation.from_quat(xp.ones((2, 4))) + with pytest.raises(ValueError, match="Cannot broadcast"): + r.apply(xp.ones((3, 3))) + r = Rotation.from_quat(xp.ones((1, 7, 2, 4))) + with pytest.raises(ValueError, match="Cannot broadcast"): + r.apply(xp.ones((2, 2, 3))) + + +@make_xp_test_case( + (Rotation, "from_matrix"), (Rotation, "as_matrix"), (Rotation, "__getitem__") +) +@pytest.mark.parametrize("ndim", list(range(1, 4))) +def test_getitem(xp, ndim: int): + rng = np.random.default_rng(0) + quat = rng.normal(size=(2, ) + (ndim,) * (ndim - 1) + (4,)) + mat = xp.asarray(Rotation.from_quat(quat).as_matrix()) + r = Rotation.from_matrix(mat) + + xp_assert_close(r[0].as_matrix(), mat[0, ...], atol=1e-15) + xp_assert_close(r[1].as_matrix(), mat[1, ...], atol=1e-15) + xp_assert_close(r[:-1].as_matrix(), xp.expand_dims(mat[0, ...], axis=0), atol=1e-15) + + +@make_xp_test_case((Rotation, "__getitem__")) +def test_getitem_single(xp): + with pytest.raises(TypeError, match='not subscriptable'): + Rotation.from_quat(xp.asarray([0, 0, 0, 1]))[0] + + +@make_xp_test_case( + (Rotation, "from_matrix"), (Rotation, "__getitem__"), (Rotation, "as_matrix") +) +def test_getitem_array_like(): + mat = np.array([[[0.0, -1, 0], + [1, 0, 0], + [0, 0, 1]], + [[1, 0, 0], + [0, 0, -1], + [0, 1, 0]]]) + r = Rotation.from_matrix(mat) + xp_assert_close(r[[0]].as_matrix(), mat[[0]], atol=1e-15) + xp_assert_close(r[[0, 1]].as_matrix(), mat[[0, 1]], atol=1e-15) + + +@make_xp_test_case((Rotation, "__setitem__")) +def test_setitem_single(xp): + r = Rotation.from_quat(xp.asarray([0, 0, 0, 1])) + with pytest.raises(TypeError, match='not subscriptable'): + r[0] = Rotation.from_quat(xp.asarray([0, 0, 0, 1])) + + +@make_xp_test_case((Rotation, "__setitem__")) +def test_setitem_slice(xp): + rng = np.random.default_rng(146972845698875399755764481408308808739) + r1 = rotation_to_xp(Rotation.random(10, rng=rng), xp) + r2 = rotation_to_xp(Rotation.random(5, rng=rng), xp) + r1[1:6] = r2 + xp_assert_equal(r1[1:6].as_quat(), r2.as_quat()) + + # Multiple dimensions + r1 = Rotation.from_quat(xp.asarray(rng.normal(size=(3, 5, 4)))) + r2 = Rotation.from_quat(xp.asarray(rng.normal(size=(2, 5, 4)))) + r1[1:3] = r2 + xp_assert_equal(r1[1:3].as_quat(), r2.as_quat()) + + +@make_xp_test_case((Rotation, "__setitem__")) +def test_setitem_integer(xp): + rng = np.random.default_rng(146972845698875399755764481408308808739) + r1 = rotation_to_xp(Rotation.random(10, rng=rng), xp) + r2 = rotation_to_xp(Rotation.random(rng=rng), xp) + r1[1] = r2 + xp_assert_equal(r1[1].as_quat(), r2.as_quat()) + + # Multiple dimensions + r1 = Rotation.from_quat(xp.asarray(rng.normal(size=(3, 5, 4)))) + r2 = Rotation.from_quat(xp.asarray(rng.normal(size=(5, 4)))) + r1[1] = r2 + xp_assert_equal(r1[1].as_quat(), r2.as_quat()) + + +@make_xp_test_case((Rotation, "__setitem__")) +def test_setitem_wrong_type(xp): + r = rotation_to_xp(Rotation.random(10, rng=0), xp) + with pytest.raises(TypeError, match='Rotation object'): + r[0] = 1 + + +@make_xp_test_case((Rotation, "from_matrix")) +def test_n_rotations(xp): + mat = np.empty((2, 3, 3)) + mat[0] = np.array([ + [0, -1, 0], + [1, 0, 0], + [0, 0, 1] + ]) + mat[1] = np.array([ + [1, 0, 0], + [0, 0, -1], + [0, 1, 0] + ]) + mat = xp.asarray(mat) + r = Rotation.from_matrix(mat) + + assert_equal(len(r), 2) + assert_equal(len(r[:-1]), 1) + + +def test_random_rotation(): + # No xp testing since random rotations are always using NumPy + rng = np.random.default_rng(0) + assert_equal(Rotation.random(rng=rng).as_quat().shape, (4,)) + assert_equal(Rotation.random(None, rng=rng).as_quat().shape, (4,)) + assert_equal(Rotation.random(1, rng=rng).as_quat().shape, (1, 4)) + assert_equal(Rotation.random(5, rng=rng).as_quat().shape, (5, 4)) + # Shape argument + assert_equal(Rotation.random(rng=rng, shape=()).as_quat().shape, (4,)) + assert_equal(Rotation.random(rng=rng, shape=(3,)).as_quat().shape, (3, 4)) + assert_equal(Rotation.random(rng=rng, shape=(2, 3)).as_quat().shape, (2, 3, 4)) + # Values should be the same for num=prod(shape) + rng1, rng2 = np.random.default_rng(42), np.random.default_rng(42) + r_num = Rotation.random(6, rng=rng1) + r_shape = Rotation.random(rng=rng2, shape=(2, 3)) + xp_assert_close(r_num.as_quat(), r_shape.as_quat().reshape(6, 4), atol=1e-12) + # Errors + with pytest.raises(ValueError, match="Only one of `num` or `shape` can be"): + Rotation.random(num=3,rng=rng, shape=(2, 2)) + with pytest.raises(ValueError, match="`shape` must be an int or a tuple of ints"): + Rotation.random(rng=rng, shape=2.5) + with pytest.raises(TypeError, match="takes from 0 to 2 positional arguments"): + Rotation.random(1, rng, None) # Shape should be kwarg only + + +@make_xp_test_case((Rotation, "align_vectors"), (Rotation, "as_matrix")) +def test_align_vectors_no_rotation(xp): + dtype = xpx.default_dtype(xp) + atol = 1e-12 if dtype == xp.float64 else 1e-5 + x = xp.asarray([[1, 2, 3], [4, 5, 6]], dtype=dtype) + y = xp.asarray(x, copy=True) + + r, rssd = Rotation.align_vectors(x, y) + xp_assert_close(r.as_matrix(), xp.eye(3), atol=atol) + xp_assert_close(rssd, xp.asarray(0.0)[()], check_shape=False, atol=1e-6) + + +@make_xp_test_case((Rotation, "apply"), (Rotation, "align_vectors")) +def test_align_vectors_no_noise(xp): + dtype = xpx.default_dtype(xp) + atol = 1e-7 if dtype == xp.float64 else 2e-3 + rng = np.random.default_rng(14697284569885399755764481408308808739) + c = rotation_to_xp(Rotation.random(rng=rng), xp) + b = xp.asarray(rng.normal(size=(5, 3)), dtype=dtype) + a = c.apply(b) + + est, rssd = Rotation.align_vectors(a, b) + xp_assert_close(c.as_quat(), est.as_quat()) + xp_assert_close(rssd, xp.asarray(0.0)[()], check_shape=False, atol=atol) + + +@make_xp_test_case((Rotation, "align_vectors"), (Rotation, "apply")) +def test_align_vectors_improper_rotation(xp): + dtype = xpx.default_dtype(xp) + atol = 1e-7 if dtype == xp.float64 else 1e-3 + # Tests correct logic for issue #10444 + x = xp.asarray([[0.89299824, -0.44372674, 0.0752378], + [0.60221789, -0.47564102, -0.6411702]]) + y = xp.asarray([[0.02386536, -0.82176463, 0.5693271], + [-0.27654929, -0.95191427, -0.1318321]]) + + est, rssd = Rotation.align_vectors(x, y) + xp_assert_close(x, est.apply(y), atol=1e-6) + xp_assert_close(rssd, xp.asarray(0.0)[()], check_shape=False, atol=atol) + + +@make_xp_test_case((Rotation, "align_vectors")) +def test_align_vectors_rssd_sensitivity(xp): + rssd_expected = xp.asarray(0.141421356237308)[()] + sens_expected = xp.asarray([[0.2, 0. , 0.], + [0. , 1.5, 1.], + [0. , 1. , 1.]]) + atol = 1e-6 + a = xp.asarray([[0, 1, 0], [0, 1, 1], [0, 1, 1]]) + b = xp.asarray([[1, 0, 0], [1, 1.1, 0], [1, 0.9, 0]]) + rot, rssd, sens = Rotation.align_vectors(a, b, return_sensitivity=True) + xp_assert_close(rssd, rssd_expected, atol=atol) + xp_assert_close(sens, sens_expected, atol=atol) + + +@make_xp_test_case((Rotation, "align_vectors"), (Rotation, "as_matrix")) +def test_align_vectors_scaled_weights(xp): + n = 10 + a = xp.asarray(Rotation.random(n, rng=0).apply([1, 0, 0])) + b = xp.asarray(Rotation.random(n, rng=1).apply([1, 0, 0])) + scale = 2 + + est1, rssd1, cov1 = Rotation.align_vectors(a, b, xp.ones(n), True) + est2, rssd2, cov2 = Rotation.align_vectors(a, b, scale * xp.ones(n), True) + + xp_assert_close(est1.as_matrix(), est2.as_matrix()) + xp_assert_close(math.sqrt(scale) * rssd1, rssd2, atol=1e-6) + xp_assert_close(cov1, cov2) + + +@make_xp_test_case( + (Rotation, "apply"), (Rotation, "from_rotvec"), (Rotation, "align_vectors") +) +def test_align_vectors_noise(xp): + dtype = xpx.default_dtype(xp) + rng = np.random.default_rng(146972845698875399755764481408308808739) + n_vectors = 100 + rot = rotation_to_xp(Rotation.random(rng=rng), xp) + vectors = xp.asarray(rng.normal(size=(n_vectors, 3)), dtype=dtype) + result = rot.apply(vectors) + + # The paper adds noise as independently distributed angular errors + sigma = np.deg2rad(1) + tolerance = 1.5 * sigma + noise = Rotation.from_rotvec( + xp.asarray(rng.normal(size=(n_vectors, 3), scale=sigma), dtype=dtype) + ) + + # Attitude errors must preserve norm. Hence apply individual random + # rotations to each vector. + noisy_result = noise.apply(result) + + est, rssd, cov = Rotation.align_vectors(noisy_result, vectors, + return_sensitivity=True) + + # Use rotation compositions to find out closeness + error_vector = (rot * est.inv()).as_rotvec() + xp_assert_close(error_vector[0], xp.asarray(0.0)[()], atol=tolerance) + xp_assert_close(error_vector[1], xp.asarray(0.0)[()], atol=tolerance) + xp_assert_close(error_vector[2], xp.asarray(0.0)[()], atol=tolerance) + + # Check error bounds using covariance matrix + cov *= xp.asarray(sigma) + xp_assert_close(cov[0, 0], xp.asarray(0.0)[()], atol=tolerance) + xp_assert_close(cov[1, 1], xp.asarray(0.0)[()], atol=tolerance) + xp_assert_close(cov[2, 2], xp.asarray(0.0)[()], atol=tolerance) + + rssd_check = xp.sum((noisy_result - est.apply(vectors)) ** 2) ** 0.5 + xp_assert_close(rssd, rssd_check, check_shape=False) + + +@make_xp_test_case((Rotation, "align_vectors")) +def test_align_vectors_invalid_input(xp): + with pytest.raises(ValueError, match="Expected input `a` to have shape"): + a, b = xp.asarray([1, 2, 3, 4]), xp.asarray([1, 2, 3]) + Rotation.align_vectors(a, b) + + with pytest.raises(ValueError, match="Expected input `b` to have shape"): + a, b = xp.asarray([1, 2, 3]), xp.asarray([1, 2, 3, 4]) + Rotation.align_vectors(a, b) + + with pytest.raises(ValueError, match="Expected inputs `a` and `b` " + "to have same shapes"): + a, b = xp.asarray([[1, 2, 3], [4, 5, 6]]), xp.asarray([[1, 2, 3]]) + Rotation.align_vectors(a, b) + + with pytest.raises(ValueError, + match="Expected `weights` to be 1 dimensional"): + a, b = xp.asarray([[1, 2, 3]]), xp.asarray([[1, 2, 3]]) + weights = xp.asarray([[1]]) + Rotation.align_vectors(a, b, weights) + + with pytest.raises(ValueError, + match="Expected `weights` to have number of values"): + a, b = xp.asarray([[1, 2, 3], [4, 5, 6]]), xp.asarray([[1, 2, 3], [4, 5, 6]]) + weights = xp.asarray([1, 2, 3]) + Rotation.align_vectors(a, b, weights) + + a, b = xp.asarray([[1, 2, 3]]), xp.asarray([[1, 2, 3]]) + weights = xp.asarray([-1]) + if is_lazy_array(weights): + r, rssd = Rotation.align_vectors(a, b, weights) + assert xp.all(xp.isnan(r.as_quat())), "Quaternion should be nan" + assert xp.isnan(rssd), "RSSD should be nan" + else: + with pytest.raises(ValueError, + match="`weights` may not contain negative values"): + Rotation.align_vectors(a, b, weights) + + a, b = xp.asarray([[1, 2, 3], [4, 5, 6]]), xp.asarray([[1, 2, 3], [4, 5, 6]]) + weights = xp.asarray([xp.inf, xp.inf]) + if is_lazy_array(weights): + r, rssd = Rotation.align_vectors(a, b, weights) + assert xp.all(xp.isnan(r.as_quat())), "Quaternion should be nan" + assert xp.isnan(rssd), "RSSD should be nan" + else: + with pytest.raises(ValueError, + match="Only one infinite weight is allowed"): + Rotation.align_vectors(a, b, weights) + + a, b = xp.asarray([[0, 0, 0]]), xp.asarray([[1, 2, 3]]) + if is_lazy_array(a): + r, rssd = Rotation.align_vectors(a, b) + assert xp.all(xp.isnan(r.as_quat())), "Quaternion should be nan" + assert xp.isnan(rssd), "RSSD should be nan" + else: + with pytest.raises(ValueError, + match="Cannot align zero length primary vectors"): + Rotation.align_vectors(a, b) + + a, b = xp.asarray([[1, 2, 3], [4, 5, 6]]), xp.asarray([[1, 2, 3], [4, 5, 6]]) + weights = xp.asarray([xp.inf, 1]) + if is_lazy_array(a): + r, rssd, sens = Rotation.align_vectors(a, b, weights, return_sensitivity=True) + assert xp.all(xp.isnan(sens)), "Sensitivity matrix should be nan" + else: + with pytest.raises(ValueError, + match="Cannot return sensitivity matrix"): + Rotation.align_vectors(a, b, weights, return_sensitivity=True) + + a, b = xp.asarray([[1, 2, 3]]), xp.asarray([[1, 2, 3]]) + if is_lazy_array(a): + r, rssd, sens = Rotation.align_vectors(a, b, return_sensitivity=True) + assert xp.all(xp.isnan(sens)), "Sensitivity matrix should be nan" + else: + with pytest.raises(ValueError, + match="Cannot return sensitivity matrix"): + Rotation.align_vectors(a, b, return_sensitivity=True) + + # No broadcast support for align_vectors + a, b = xp.asarray([[[1, 2, 3]]]), xp.asarray([[[1, 2, 3]]]) + with pytest.raises(ValueError, + match="Expected inputs `a` and `b` to have shape"): + Rotation.align_vectors(a, b) + + +@make_xp_test_case( + (Rotation, "align_vectors"), (Rotation, "as_matrix"), (Rotation, "apply") +) +def test_align_vectors_align_constrain(xp): + # Align the primary +X B axis with the primary +Y A axis, and rotate about + # it such that the +Y B axis (residual of the [1, 1, 0] secondary b vector) + # is aligned with the +Z A axis (residual of the [0, 1, 1] secondary a + # vector) + dtype = xpx.default_dtype(xp) + atol = 1e-12 if dtype == xp.float64 else 1e-6 + b = xp.asarray([[1, 0, 0], [1, 1, 0]]) + a = xp.asarray([[0.0, 1, 0], [0, 1, 1]]) + m_expected = xp.asarray([[0.0, 0, 1], + [1, 0, 0], + [0, 1, 0]]) + R, rssd = Rotation.align_vectors(a, b, weights=xp.asarray([xp.inf, 1])) + xp_assert_close(R.as_matrix(), m_expected, atol=atol) + xp_assert_close(R.apply(b), a, atol=atol) # Pri and sec align exactly + xp_assert_close(rssd, xp.asarray(0.0)[()], atol=atol) + + # Do the same but with an inexact secondary rotation + b = xp.asarray([[1, 0, 0], [1, 2, 0]]) + rssd_expected = 1.0 + R, rssd = Rotation.align_vectors(a, b, weights=xp.asarray([xp.inf, 1])) + xp_assert_close(R.as_matrix(), m_expected, atol=atol) + xp_assert_close(R.apply(b)[0, ...], a[0, ...], atol=atol) # Only pri aligns exactly + assert xpx.isclose(rssd, rssd_expected, atol=atol, xp=xp) + a_expected = xp.asarray([[0.0, 1, 0], [0, 1, 2]]) + xp_assert_close(R.apply(b), a_expected, atol=atol) + + # Check random vectors + b = xp.asarray([[1, 2, 3], [-2, 3, -1]]) + a = xp.asarray([[-1.0, 3, 2], [1, -1, 2]]) + rssd_expected = 1.3101595297515016 + R, rssd = Rotation.align_vectors(a, b, weights=xp.asarray([xp.inf, 1])) + xp_assert_close(R.apply(b)[0, ...], a[0, ...], atol=atol) # Only pri aligns exactly + assert xpx.isclose(rssd, rssd_expected, atol=atol, xp=xp) + + +@make_xp_test_case((Rotation, "align_vectors"), (Rotation, "as_matrix")) +def test_align_vectors_near_inf(xp): + # align_vectors should return near the same result for high weights as for + # infinite weights. rssd will be different with floating point error on the + # exactly aligned vector being multiplied by a large non-infinite weight + dtype = xpx.default_dtype(xp) + if dtype == xp.float32: + pytest.skip("Align vectors near inf is numerically unstable in float32") + n = 100 + mats = [] + for i in range(6): + mats.append(Rotation.random(n, rng=10 + i).as_matrix()) + + for i in range(n): + # Get random pairs of 3-element vectors + a = xp.asarray(np.array([1 * mats[0][i][0], 2 * mats[1][i][0]]), dtype=dtype) + b = xp.asarray(np.array([3 * mats[2][i][0], 4 * mats[3][i][0]]), dtype=dtype) + + R, _ = Rotation.align_vectors(a, b, weights=[1e10, 1]) + R2, _ = Rotation.align_vectors(a, b, weights=[xp.inf, 1]) + xp_assert_close(R.as_matrix(), R2.as_matrix(), atol=1e-4) + + for i in range(n): + # Get random triplets of 3-element vectors + a = xp.asarray(np.array([1*mats[0][i][0], 2*mats[1][i][0], 3*mats[2][i][0]]), + dtype=dtype) + b = xp.asarray(np.array([4*mats[3][i][0], 5*mats[4][i][0], 6*mats[5][i][0]]), + dtype=dtype) + + R, _ = Rotation.align_vectors(a, b, weights=[1e10, 2, 1]) + R2, _ = Rotation.align_vectors(a, b, weights=[xp.inf, 2, 1]) + xp_assert_close(R.as_matrix(), R2.as_matrix(), atol=1e-4) + + +@make_xp_test_case((Rotation, "align_vectors"), (Rotation, "as_matrix")) +def test_align_vectors_parallel(xp): + atol = 1e-12 + a = xp.asarray([[1.0, 0, 0], [0, 1, 0]]) + b = xp.asarray([[0.0, 1, 0], [0, 1, 0]]) + m_expected = xp.asarray([[0.0, 1, 0], + [-1, 0, 0], + [0, 0, 1]]) + R, _ = Rotation.align_vectors(a, b, weights=[xp.inf, 1]) + xp_assert_close(R.as_matrix(), m_expected, atol=atol) + R, _ = Rotation.align_vectors(a[0, ...], b[0, ...]) + xp_assert_close(R.as_matrix(), m_expected, atol=atol) + xp_assert_close(R.apply(b[0, ...]), a[0, ...], atol=atol) + + b = xp.asarray([[1, 0, 0], [1, 0, 0]]) + m_expected = xp.asarray([[1.0, 0, 0], + [0, 1, 0], + [0, 0, 1]]) + R, _ = Rotation.align_vectors(a, b, weights=[xp.inf, 1]) + xp_assert_close(R.as_matrix(), m_expected, atol=atol) + R, _ = Rotation.align_vectors(a[0, ...], b[0, ...]) + xp_assert_close(R.as_matrix(), m_expected, atol=atol) + xp_assert_close(R.apply(b[0, ...]), a[0, ...], atol=atol) + + +@make_xp_test_case( + (Rotation, "align_vectors"), (Rotation, "magnitude"), (Rotation, "apply"), + (Rotation, "from_rotvec"), (Rotation, "as_rotvec"), (Rotation, "as_matrix") +) +def test_align_vectors_antiparallel(xp): + dtype = xpx.default_dtype(xp) + # Test exact 180 deg rotation + atol = 1e-12 if dtype == xp.float64 else 1e-7 + + as_to_test = np.array([[[1.0, 0, 0], [0, 1, 0]], + [[0, 1, 0], [1, 0, 0]], + [[0, 0, 1], [0, 1, 0]]]) + + bs_to_test = np.array([[-a[0], a[1]] for a in as_to_test]) + for a, b in zip(as_to_test, bs_to_test): + a, b = xp.asarray(a, dtype=dtype), xp.asarray(b, dtype=dtype) + R, _ = Rotation.align_vectors(a, b, weights=[xp.inf, 1]) + xp_assert_close(R.magnitude(), xp.asarray(xp.pi)[()], atol=atol) + xp_assert_close(R.apply(b[0, ...]), a[0, ...], atol=atol) + + # Test exact rotations near 180 deg + Rs = Rotation.random(100, rng=0) + dRs = Rotation.from_rotvec(Rs.as_rotvec()*1e-4) # scale down to small angle + a = [[ 1, 0, 0], [0, 1, 0]] + b = [[-1, 0, 0], [0, 1, 0]] + as_to_test = [] + for dR in dRs: + as_to_test.append(np.array([dR.apply(a[0]), a[1]])) + + # GPU computations may be less accurate. See e.g. + # https://github.com/jax-ml/jax/issues/18934 and + # https://docs.pytorch.org/docs/stable/generated/torch.set_float32_matmul_precision.html + # We currently have no unified way to check which device is being used. Cupy will + # always run on the GPU regardless of SCIPY_DEVICE, hence the explicit check for + # cupy. Note that the current implementation lets other frameworks, e.g. numpy, run + # on the CPU regardless of SCIPY_DEVICE but with increased GPU tolerances. + if xp_device_type(xp.asarray(0)) == "cuda": + atol = 1e-7 + + for a in as_to_test: + a, b = xp.asarray(a), xp.asarray(b) + R, _ = Rotation.align_vectors(a, b, weights=[xp.inf, 1]) + R2, _ = Rotation.align_vectors(a, b, weights=[1e10, 1]) + xp_assert_close(R.as_matrix(), R2.as_matrix(), atol=atol) + + +@make_xp_test_case((Rotation, "align_vectors"), (Rotation, "apply")) +def test_align_vectors_primary_only(xp): + dtype = xpx.default_dtype(xp) + atol = 1e-12 if dtype == xp.float64 else 1e-5 + mats_a = Rotation.random(100, rng=0).as_matrix() + mats_b = Rotation.random(100, rng=1).as_matrix() + + for mat_a, mat_b in zip(mats_a, mats_b): + # Get random 3-element unit vectors + a = xp.asarray(mat_a[0], dtype=dtype) + b = xp.asarray(mat_b[0], dtype=dtype) + + # Compare to align_vectors with primary only + R, rssd = Rotation.align_vectors(a, b) + xp_assert_close(R.apply(b), a, atol=atol) + xp_assert_close(rssd, xp.asarray(0.0)[()], atol=atol) + + +def test_align_vectors_array_like(): + rng = np.random.default_rng(123) + c = Rotation.random(rng=rng) + b = rng.normal(size=(5, 3)) + a = c.apply(b) + + est_expected, rssd_expected = Rotation.align_vectors(a, b) + est, rssd = Rotation.align_vectors(a.tolist(), b.tolist()) + xp_assert_close(est_expected.as_quat(), est.as_quat()) + xp_assert_close(rssd, rssd_expected) + + +@make_xp_test_case((Rotation, "apply"), (Rotation, "align_vectors")) +def test_align_vectors_mixed_dtypes(xp): + dtype = xpx.default_dtype(xp) + rng = np.random.default_rng(123) + c = rotation_to_xp(Rotation.random(rng=rng), xp) + b = xp.asarray(rng.normal(size=(5, 3)), dtype=dtype) + a = xp.asarray(c.apply(b), dtype=xp.float32) # Intentionally float32 + # Check that the dtype of the output is the result type of a and b + est, _ = Rotation.align_vectors(a, b) + xp_assert_close(est.as_quat(), c.as_quat()) + + +@make_xp_test_case((Rotation, "__repr__")) +def test_repr_single_rotation(xp): + q = xp.asarray([0, 0, 0, 1]) + actual = repr(Rotation.from_quat(q)) + if is_numpy(xp): + expected = """\ +Rotation.from_matrix(array([[1., 0., 0.], + [0., 1., 0.], + [0., 0., 1.]]))""" + assert actual == expected + else: + assert actual.startswith("Rotation.from_matrix(") + + +def test_repr_rotation_sequence(xp): + q = xp.asarray([[0.0, 1, 0, 1], [0, 0, 1, 1]]) / math.sqrt(2) + actual = f"{Rotation.from_quat(q)!r}" + if is_numpy(xp): + expected = """\ +Rotation.from_matrix(array([[[ 0., 0., 1.], + [ 0., 1., 0.], + [-1., 0., 0.]], + + [[ 0., -1., 0.], + [ 1., 0., 0.], + [ 0., 0., 1.]]]))""" + def stripped(s: str) -> str: + # don't fail due to leading whitespace differences + return "\n".join(map(str.lstrip, s.splitlines())) + + assert stripped(actual) == stripped(expected) + else: + assert actual.startswith("Rotation.from_matrix(") + + +@make_xp_test_case((Slerp, "__init__"), (Slerp, "__call__")) +def test_slerp(xp): + rnd = np.random.RandomState(0) + + key_rots = Rotation.from_quat(xp.asarray(rnd.uniform(size=(5, 4)))) + key_quats = key_rots.as_quat() + + key_times = [0, 1, 2, 3, 4] + interpolator = Slerp(key_times, key_rots) + assert isinstance(interpolator.times, type(xp.asarray(0))) + + times = [0, 0.5, 0.25, 1, 1.5, 2, 2.75, 3, 3.25, 3.60, 4] + interp_rots = interpolator(times) + interp_quats = interp_rots.as_quat() + + # Dot products are affected by sign of quaternions + mask = (interp_quats[:, -1] < 0)[:, None] + interp_quats = xp.where(mask, -interp_quats, interp_quats) + # Checking for quaternion equality, perform same operation + mask = (key_quats[:, -1] < 0)[:, None] + key_quats = xp.where(mask, -key_quats, key_quats) + + # Equality at keyframes, including both endpoints + xp_assert_close(interp_quats[0, ...], key_quats[0, ...]) + xp_assert_close(interp_quats[3, ...], key_quats[1, ...]) + xp_assert_close(interp_quats[5, ...], key_quats[2, ...]) + xp_assert_close(interp_quats[7, ...], key_quats[3, ...]) + xp_assert_close(interp_quats[10, ...], key_quats[4, ...]) + + # Constant angular velocity between keyframes. Check by equating + # cos(theta) between quaternion pairs with equal time difference. + cos_theta1 = xp.sum(interp_quats[0, ...] * interp_quats[2, ...]) + cos_theta2 = xp.sum(interp_quats[2, ...] * interp_quats[1, ...]) + xp_assert_close(cos_theta1, cos_theta2) + + cos_theta4 = xp.sum(interp_quats[3, ...] * interp_quats[4, ...]) + cos_theta5 = xp.sum(interp_quats[4, ...] * interp_quats[5, ...]) + xp_assert_close(cos_theta4, cos_theta5) + + # theta1: 0 -> 0.25, theta3 : 0.5 -> 1 + # Use double angle formula for double the time difference + cos_theta3 = xp.sum(interp_quats[1, ...] * interp_quats[3, ...]) + xp_assert_close(cos_theta3, 2 * (cos_theta1**2) - 1) + + # Miscellaneous checks + assert_equal(len(interp_rots), len(times)) + + +@make_xp_test_case((Slerp, "__init__")) +def test_slerp_rot_is_rotation(xp): + with pytest.raises(TypeError, match="must be a `Rotation` instance"): + r = xp.asarray([[1,2,3,4], + [0,0,0,1]]) + t = xp.asarray([0, 1]) + Slerp(t, r) + + +SLERP_EXCEPTION_MESSAGE = "must be a sequence of at least 2 rotations" + + +@make_xp_test_case((Slerp, "__init__")) +def test_slerp_single_rot(xp): + r = Rotation.from_quat(xp.asarray([[1.0, 2, 3, 4]])) + with pytest.raises(ValueError, match=SLERP_EXCEPTION_MESSAGE): + Slerp([1], r) + + +@make_xp_test_case((Slerp, "__init__")) +def test_slerp_rot_len0(xp): + r = Rotation.random() + r = Rotation.from_quat(xp.asarray(r.as_quat())) + with pytest.raises(ValueError, match=SLERP_EXCEPTION_MESSAGE): + Slerp([], r) + + +@make_xp_test_case((Slerp, "__init__")) +def test_slerp_rot_len1(xp): + r = Rotation.random(1) + r = Rotation.from_quat(xp.asarray(r.as_quat())) + with pytest.raises(ValueError, match=SLERP_EXCEPTION_MESSAGE): + Slerp([1], r) + + +@make_xp_test_case((Slerp, "__init__")) +def test_slerp_tensor_rot(xp): + r = Rotation.from_quat(xp.ones((2, 2, 4))) + with pytest.raises(ValueError, match="Rotations with more than 1 leading"): + Slerp([1, 2], r) + + +@make_xp_test_case((Slerp, "__init__")) +def test_slerp_time_dim_mismatch(xp): + with pytest.raises(ValueError, + match="times to be specified in a 1 dimensional array"): + rnd = np.random.RandomState(0) + r = Rotation.from_quat(xp.asarray(rnd.uniform(size=(2, 4)))) + t = xp.asarray([[1], + [2]]) + Slerp(t, r) + + +@make_xp_test_case((Slerp, "__init__")) +def test_slerp_num_rotations_mismatch(xp): + with pytest.raises(ValueError, match="number of rotations to be equal to " + "number of timestamps"): + rnd = np.random.RandomState(0) + r = Rotation.from_quat(xp.asarray(rnd.uniform(size=(5, 4)))) + t = xp.arange(7) + Slerp(t, r) + + +@make_xp_test_case((Slerp, "__init__")) +def test_slerp_equal_times(xp): + rnd = np.random.RandomState(0) + q = xp.asarray(rnd.uniform(size=(5, 4))) + r = Rotation.from_quat(q) + t = [0, 1, 2, 2, 4] + if is_lazy_array(q): + s = Slerp(t, r) + assert xp.all(xp.isnan(s.times)) + else: + with pytest.raises(ValueError, match="strictly increasing order"): + Slerp(t, r) + + +@make_xp_test_case((Slerp, "__init__")) +def test_slerp_decreasing_times(xp): + rnd = np.random.RandomState(0) + q = xp.asarray(rnd.uniform(size=(5, 4))) + r = Rotation.from_quat(q) + t = [0, 1, 3, 2, 4] + if is_lazy_array(q): + s = Slerp(t, r) + assert xp.all(xp.isnan(s.times)) + else: + with pytest.raises(ValueError, match="strictly increasing order"): + Slerp(t, r) + + +@make_xp_test_case((Slerp, "__init__"), (Slerp, "__call__")) +def test_slerp_call_time_dim_mismatch(xp): + rnd = np.random.RandomState(0) + r = Rotation.from_quat(xp.asarray(rnd.uniform(size=(5, 4)))) + t = xp.arange(5) + s = Slerp(t, r) + + with pytest.raises(ValueError, + match="`times` must be at most 1-dimensional."): + interp_times = xp.asarray([[3.5], + [4.2]]) + s(interp_times) + + +@make_xp_test_case((Slerp, "__init__"), (Slerp, "__call__")) +def test_slerp_call_time_out_of_range(xp): + rnd = np.random.RandomState(0) + r = Rotation.from_quat(xp.asarray(rnd.uniform(size=(5, 4)))) + t = xp.arange(5) + 1 + s = Slerp(t, r) + + times_low = xp.asarray([0, 1, 2]) + times_high = xp.asarray([1, 2, 6]) + if is_lazy_array(times_low): + q = s(times_low).as_quat() + in_range = xp.logical_and(times_low >= xp.min(t), times_low <= xp.max(t)) + assert xp.all(xp.isnan(q[~in_range, ...])) + assert xp.all(~xp.isnan(q[in_range, ...])) + q = s(times_high).as_quat() + in_range = xp.logical_and(times_high >= xp.min(t), times_high <= xp.max(t)) + assert xp.all(xp.isnan(q[~in_range, ...])) + assert xp.all(~xp.isnan(q[in_range, ...])) + else: + with pytest.raises(ValueError, match="times must be within the range"): + s(times_low) + with pytest.raises(ValueError, match="times must be within the range"): + s(times_high) + + +@make_xp_test_case( + (Slerp, "__init__"), (Slerp, "__call__"), (Rotation, "from_euler"), + (Rotation, "inv"), (Rotation, "magnitude") +) +def test_slerp_call_scalar_time(xp): + dtype = xpx.default_dtype(xp) + atol = 1e-16 if dtype == xp.float64 else 1e-7 + r = Rotation.from_euler('X', xp.asarray([[0], [80]]), degrees=True) + s = Slerp([0, 1], r) + + r_interpolated = s(0.25) + r_interpolated_expected = Rotation.from_euler('X', xp.asarray(20), degrees=True) + + delta = r_interpolated * r_interpolated_expected.inv() + + xp_assert_close(delta.magnitude(), xp.asarray(0.0)[()], atol=atol) + + +@make_xp_test_case((Rotation, "__mul__")) +def test_multiplication(xp): + r1 = Rotation.from_quat(xp.asarray([0, 0, 0, 1])) + r2 = Rotation.from_quat(xp.asarray([0, 0, 0, 1])) + r3 = r1 * r2 + xp_assert_close(r3.as_quat(), xp.asarray([0.0, 0, 0, 1])) + + # Check that multiplication with other types fails + with pytest.raises(TypeError, match="unsupported operand type"): + r1 * 2 + # Check that __mul__ returns NotImplemented so that other types can implement + # __rmul__. See https://github.com/scipy/scipy/issues/21541 + assert r1.__mul__(1) is NotImplemented + + +@make_xp_test_case((Rotation, "__mul__")) +def test_multiplication_nd(xp): + # multiple dimensions + rng = np.random.default_rng(0) + r1 = Rotation.from_quat(xp.asarray(rng.normal(size=(2, 3, 4)))) + r2 = Rotation.from_quat(xp.asarray(rng.normal(size=(2, 3, 4)))) + r3 = r1 * r2 + assert r3.as_quat().shape == (2, 3, 4) + + # same shape len, different dimensions + r1 = Rotation.from_quat(xp.asarray(rng.normal(size=(1, 3, 4)))) + r2 = Rotation.from_quat(xp.asarray(rng.normal(size=(2, 1, 4)))) + r3 = r1 * r2 + assert r3.as_quat().shape == (2, 3, 4) + + # different shape len, different dimensions + r1 = Rotation.from_quat(xp.asarray(rng.normal(size=(3, 1, 4, 4)))) + r2 = Rotation.from_quat(xp.asarray(rng.normal(size=(2, 4, 4)))) + r3 = r1 * r2 + assert r3.as_quat().shape == (3, 2, 4, 4) + + # transition between 2D and 3D with 2D rotation as first argument. This needs to + # choose the xp_backend even though r1's backend is cython + r1 = Rotation.from_quat(xp.asarray(rng.normal(size=(2, 4)))) + r2 = Rotation.from_quat(xp.asarray(rng.normal(size=(2, 2, 4)))) + r3 = r1 * r2 + assert r3.as_quat().shape == (2, 2, 4) + + +@make_xp_test_case((Rotation, "__mul__")) +def test_multiplication_errors(xp): + rng = np.random.default_rng(0) + r1 = Rotation.from_quat(xp.asarray(rng.normal(size=(2, 4)))) + r2 = Rotation.from_quat(xp.asarray(rng.normal(size=(1, 4, 4)))) + with pytest.raises(ValueError, match="Cannot broadcast"): + r1 * r2 + + +@make_xp_test_case((Rotation, "__mul__")) +def test_multiplication_stability(xp): + qs = rotation_to_xp(Rotation.random(50, rng=0), xp) + rs = rotation_to_xp(Rotation.random(1000, rng=1), xp) + expected = xp.ones(len(rs)) + for r in qs: + rs = rs * r * rs + xp_assert_close(xp_vector_norm(rs.as_quat(), axis=1), expected) + + +@make_xp_test_case( + (Rotation, "inv"), (Rotation, "__pow__"), (Rotation, "inv"), + (Rotation, "magnitude"), (Rotation, "from_rotvec"), (Rotation, "as_rotvec") +) +@pytest.mark.parametrize("ndim", list(range(1, 4))) +def test_pow(xp, ndim: int): + dtype = xpx.default_dtype(xp) + atol = 1e-14 if dtype == xp.float64 else 1e-6 + rng = np.random.default_rng(0) + batch_shape = (ndim,) * (ndim - 1) + quat = rng.normal(size=batch_shape + (4,)) + p = Rotation.from_quat(xp.asarray(quat)) + p_inv = p.inv() + # Test the short-cuts and other integers + for n in [-5, -2, -1, 0, 1, 2, 5]: + # Test accuracy + q = p ** n + q_identity = xp.asarray([0., 0, 0, 1]) + # Regression test for gh-24436 + assert isinstance(q._quat, type(q_identity)) + r = Rotation.from_quat(xp.tile(q_identity, batch_shape + (1,))) + for _ in range(abs(n)): + if n > 0: + r = r * p + else: + r = r * p_inv + ang = (q * r.inv()).magnitude() + assert xp.all(ang < atol) + + # Test shape preservation + r = Rotation.from_quat(xp.tile(q_identity, batch_shape + (1,))) + assert (r**n).as_quat().shape == batch_shape + (4,) + + # Large angle fractional + for n in [-1.5, -0.5, -0.0, 0.0, 0.5, 1.5]: + q = p ** n + r = Rotation.from_rotvec(n * p.as_rotvec()) + xp_assert_close(q.as_quat(), r.as_quat(), atol=atol) + + # Array exponent + n = [-5, -2, -1.5, -1, -0.5, -0.0, 0, 0.0, 0.5, 1.0, 1.5, 2] + for exponent in n: + r = p ** exponent + r_array = p ** xp.asarray([exponent]) # Test with 1D array + xp_assert_close(r.as_quat(), r_array.as_quat()) + r_array = p ** xp.asarray(exponent) # Test with scalar + xp_assert_close(r.as_quat(), r_array.as_quat()) + + # Small angle + rotvec = xp.zeros(batch_shape + (3,)) + rotvec = xpx.at(rotvec)[..., 0].set(1e-12) + p = Rotation.from_rotvec(rotvec) + n = 3 + q = p ** n + r = Rotation.from_rotvec(n * p.as_rotvec()) + xp_assert_close(q.as_quat(), r.as_quat(), atol=atol) + + # Array exponent + q = p ** xp.asarray([n]) # Test with 1D array + r = Rotation.from_rotvec(n * p.as_rotvec()) + xp_assert_close(q.as_quat(), r.as_quat(), atol=atol) + q = p ** xp.asarray(n) # Test with scalar + r = Rotation.from_rotvec(n * p.as_rotvec()) + xp_assert_close(q.as_quat(), r.as_quat(), atol=atol) + + +@make_xp_test_case((Rotation, "__pow__")) +def test_pow_errors(xp): + p = rotation_to_xp(Rotation.random(rng=0), xp) + with pytest.raises(NotImplementedError, match='modulus not supported'): + pow(p, 1, 1) + with pytest.raises(ValueError, match="Array exponent must be a scalar"): + p ** xp.asarray([1, 2]) + with pytest.raises(ValueError, match="Array exponent must be a scalar"): + p ** xp.asarray([[1], [2]]) + + +def test_rotation_within_numpy_array(): + # TODO: Do we want to support this for all Array API frameworks? + single = Rotation.random(rng=0) + multiple = Rotation.random(2, rng=1) + + array = np.array(single) + assert_equal(array.shape, ()) + + array = np.array(multiple) + assert_equal(array.shape, (2,)) + xp_assert_close(array[0].as_matrix(), multiple[0].as_matrix()) + xp_assert_close(array[1].as_matrix(), multiple[1].as_matrix()) + + array = np.array([single]) + assert_equal(array.shape, (1,)) + assert_equal(array[0], single) + + array = np.array([multiple]) + assert_equal(array.shape, (1, 2)) + xp_assert_close(array[0, 0].as_matrix(), multiple[0].as_matrix()) + xp_assert_close(array[0, 1].as_matrix(), multiple[1].as_matrix()) + + array = np.array([single, multiple], dtype=object) + assert_equal(array.shape, (2,)) + assert_equal(array[0], single) + assert_equal(array[1], multiple) + + array = np.array([multiple, multiple, multiple]) + assert_equal(array.shape, (3, 2)) + + +@make_xp_test_case((Rotation, "as_matrix")) +@pytest.mark.skip_xp_backends("array_api_strict", + reason="array API doesn't support pickling") +def test_pickling(xp): + r = Rotation.from_quat(xp.asarray([0, 0, math.sin(np.pi/4), math.cos(np.pi/4)])) + pkl = pickle.dumps(r) + unpickled = pickle.loads(pkl) + xp_assert_close(r.as_matrix(), unpickled.as_matrix(), atol=1e-15) + + +@make_xp_test_case((Rotation, "as_matrix")) +@pytest.mark.skip_xp_backends("array_api_strict", + reason="array API doesn't support deepcopy") +def test_deepcopy(xp): + r = Rotation.from_quat(xp.asarray([0, 0, math.sin(np.pi/4), math.cos(np.pi/4)])) + r1 = copy.deepcopy(r) + xp_assert_close(r.as_matrix(), r1.as_matrix(), atol=1e-15) + + +def test_as_euler_contiguous(): + # The Array API does not specify contiguous arrays, so we can only check for NumPy + r = Rotation.from_quat([0, 0, 0, 1]) + e1 = r.as_euler('xyz') # extrinsic euler rotation + e2 = r.as_euler('XYZ') # intrinsic + assert e1.flags['C_CONTIGUOUS'] is True + assert e2.flags['C_CONTIGUOUS'] is True + assert all(i >= 0 for i in e1.strides) + assert all(i >= 0 for i in e2.strides) + + +@make_xp_test_case((Rotation, "concatenate")) +def test_concatenate(xp): + rotation = rotation_to_xp(Rotation.random(10, rng=0), xp) + sizes = [1, 2, 3, 1, 3] + starts = [0] + list(np.cumsum(sizes)) + split = [rotation[i:i + n] for i, n in zip(starts, sizes)] + result = Rotation.concatenate(split) + xp_assert_equal(rotation.as_quat(), result.as_quat()) + + # Test Rotation input for multiple rotations + result = Rotation.concatenate(rotation) + xp_assert_equal(rotation.as_quat(), result.as_quat()) + + # Test that a copy is returned + assert rotation is not result + + # Test Rotation input for single rotations + rng = np.random.default_rng(0) + quat = xp.asarray(rng.normal(size=(5, 2, 4))) + rotation = Rotation.from_quat(quat) + r1 = Rotation.from_quat(quat[:3, ...]) + r2 = Rotation.from_quat(quat[3:, ...]) + result = Rotation.concatenate([r1, r2]) + xp_assert_equal(rotation.as_quat(), result.as_quat()) + + +@make_xp_test_case((Rotation, "concatenate")) +def test_concatenate_wrong_type(xp): + with pytest.raises(TypeError, match='Rotation objects only'): + rot = Rotation(xp.asarray(Rotation.identity().as_quat())) + Rotation.concatenate([rot, 1, None]) + + +@make_xp_test_case((Rotation, "concatenate")) +def test_concatenate_wrong_shape(xp): + r1 = Rotation.from_quat(xp.ones((5, 2, 4))) + r2 = Rotation.from_quat(xp.ones((1, 4))) + # Frameworks throw inconsistent error types on concat failures + with pytest.raises((ValueError, RuntimeError, TypeError)): + Rotation.concatenate([r1, r2]) + + +# Regression test for gh-16663 +@make_xp_test_case() +def test_len_and_bool(xp): + rotation_multi_one = Rotation(xp.asarray([[0, 0, 0, 1]])) + rotation_multi = Rotation(xp.asarray([[0, 0, 0, 1], [0, 0, 0, 1]])) + rotation_single = Rotation(xp.asarray([0, 0, 0, 1])) + + assert len(rotation_multi_one) == 1 + assert len(rotation_multi) == 2 + with pytest.raises(TypeError, match="Single rotation has no len()."): + len(rotation_single) + + rotation_batched = Rotation.from_quat(xp.ones((3, 2, 4))) + assert len(rotation_batched) == 3 + + # Rotation should always be truthy. See gh-16663 + assert rotation_multi_one + assert rotation_multi + assert rotation_single + + +@make_xp_test_case((Rotation, "from_davenport")) +def test_from_davenport_single_rotation(xp): + axis = xp.asarray([0, 0, 1]) + quat = Rotation.from_davenport(axis, 'extrinsic', 90, + degrees=True).as_quat() + expected_quat = xp.asarray([0.0, 0, 1, 1]) / math.sqrt(2) + xp_assert_close(quat, expected_quat) + + +@make_xp_test_case((Rotation, "from_rotvec"), (Rotation, "from_davenport")) +def test_from_davenport_one_or_two_axes(xp): + ez = xp.asarray([0.0, 0, 1]) + ey = xp.asarray([0.0, 1, 0]) + + # Single rotation, single axis, axes.shape == (3, ) + rot = Rotation.from_rotvec(ez * xp.pi/4) + rot_dav = Rotation.from_davenport(ez, 'e', xp.pi/4) + xp_assert_close(rot.as_quat(canonical=True), rot_dav.as_quat(canonical=True)) + + # Single rotation, single axis, axes.shape == (1, 3), angles.shape == (1, ) + # -> Still single rotation + axes = xp.reshape(ez, (1, 3)) # Torch can't create tensors from xp.asarray([ez]) + rot = Rotation.from_rotvec(ez * xp.pi/4) + rot_dav = Rotation.from_davenport(axes, 'e', [xp.pi/4]) + xp_assert_close(rot.as_quat(canonical=True), rot_dav.as_quat(canonical=True)) + + # Single rotation, two axes, axes.shape == (2, 3) + axes = xp.stack([ez, ey], axis=0) + rot = Rotation.from_rotvec(axes * xp.asarray([[xp.pi/4], [xp.pi/6]])) + rot = rot[0] * rot[1] + axes_dav = xp.stack([ey, ez], axis=0) + rot_dav = Rotation.from_davenport(axes_dav, 'e', [xp.pi/6, xp.pi/4]) + xp_assert_close(rot.as_quat(canonical=True), rot_dav.as_quat(canonical=True)) + + # Two rotations, single axis, axes.shape == (3, ) + axes = xp.stack([ez, ez], axis=0) + rot = Rotation.from_rotvec(axes * xp.asarray([[xp.pi/6], [xp.pi/4]])) + axes_dav = xp.reshape(ez, (1, 3)) + rot_dav = Rotation.from_davenport(axes_dav, 'e', [[xp.pi/6], [xp.pi/4]]) + xp_assert_close(rot.as_quat(canonical=True), rot_dav.as_quat(canonical=True)) + + +@make_xp_test_case((Rotation, "from_davenport")) +@pytest.mark.parametrize("ndim", list(range(1, 4))) +def test_from_davenport_shapes(xp, ndim: int): + # The shape rules for ND rotations are as follows: + # axes.shape[-2] must be angles.shape[-1] + # Resulting shape is np.broadcast_shapes(axes.shape[:-2], angles.shape[:-1]) + (4,) + rng = np.random.default_rng(0) + batch_shape = (ndim,) * (ndim - 1) + # Create random, orthogonal axes + r = Rotation.from_quat(xp.asarray(rng.normal(size=(4,)))) + axes = r.as_matrix() + # axes = (3,) + angles = xp.asarray(rng.normal(size=batch_shape + (1,))) + rot = Rotation.from_davenport(axes[0, ...], 'e', angles) + assert rot.as_quat().shape == batch_shape + (4,) + # axes = (1, 3) + angles = xp.asarray(rng.normal(size=batch_shape + (1,))) + rot = Rotation.from_davenport(axes[0, None, ...], 'e', angles) + assert rot.as_quat().shape == batch_shape + (4,) + # axes = (2, 3) + angles = xp.asarray(rng.normal(size=batch_shape + (2,))) + rot = Rotation.from_davenport(axes[:2, ...], 'e', angles) + assert rot.as_quat().shape == batch_shape + (4,) + + # axes = (...,3, 3) + r = Rotation.from_quat(xp.asarray(rng.normal(size=batch_shape + (4,)))) + axes = r.as_matrix() + angles = xp.asarray(rng.normal(size=batch_shape + (3,))) + rot = Rotation.from_davenport(axes, 'e', angles) + assert rot.as_quat().shape == batch_shape + (4,) + + +@make_xp_test_case((Rotation, "from_davenport")) +def test_from_davenport_broadcast(xp): + rng = np.random.default_rng(0) + # Create random, orthogonal axes + r = Rotation.from_quat(xp.asarray(rng.normal(size=(4, 9, 1, 4)))) + axes = r.as_matrix() + angles = xp.asarray(rng.normal(size=(1, 4, 3))) + rot = Rotation.from_davenport(axes, 'e', angles) + # (4, 9, 1, 3) + (3,) axes, (1, 4, 3) angles -> (4, 9, 4) + (4,) for quaternion + assert rot.as_quat().shape == (4, 9, 4, 4) + + +@make_xp_test_case((Rotation, "from_davenport")) +def test_from_davenport_invalid_input(xp): + ez = [0, 0, 1] + ey = [0, 1, 0] + ezy = [0, 1, 1] + # We can only raise in non-lazy frameworks. + axes = xp.asarray([ez, ezy]) + if is_lazy_array(axes): + q = Rotation.from_davenport(axes, 'e', [0, 0]).as_quat() + assert xp.all(xp.isnan(q)) + else: + with pytest.raises(ValueError, match="must be orthogonal"): + Rotation.from_davenport(axes, 'e', [0, 0]) + axes = xp.asarray([ez, ey, ezy]) + if is_lazy_array(axes): + q = Rotation.from_davenport(axes, 'e', [0, 0, 0]).as_quat() + assert xp.all(xp.isnan(q)) + else: + with pytest.raises(ValueError, match="must be orthogonal"): + Rotation.from_davenport(axes, 'e', [0, 0, 0]) + with pytest.raises(ValueError, match="order should be"): + Rotation.from_davenport(xp.asarray([ez]), 'xyz', [0]) + with pytest.raises(ValueError, match="Expected `angles`"): + Rotation.from_davenport(xp.asarray([ez, ey, ez]), 'e', [0, 1, 2, 3]) + with pytest.raises(ValueError, match="Expected `angles`"): # Too many angles + Rotation.from_davenport(xp.asarray(ez), 'e', [0, 1]) + with pytest.raises(ValueError, match="Expected `angles`"): # Too few angles + Rotation.from_davenport(xp.asarray([ez, ey, ez]), 'e', [0, 1]) + + +def test_from_davenport_array_like(): + rng = np.random.default_rng(123) + # Single rotation + e1 = np.array([1, 0, 0]) + e2 = np.array([0, 1, 0]) + e3 = np.array([0, 0, 1]) + r_expected = Rotation.random(rng=rng) + angles = r_expected.as_davenport([e1, e2, e3], 'e') + r = Rotation.from_davenport([e1, e2, e3], 'e', angles.tolist()) + assert r_expected.approx_equal(r, atol=1e-12) + + # Multiple rotations + r_expected = Rotation.random(2, rng=rng) + angles = r_expected.as_davenport([e1, e2, e3], 'e') + r = Rotation.from_davenport([e1, e2, e3], 'e', angles.tolist()) + assert np.all(r_expected.approx_equal(r, atol=1e-12)) + + +@make_xp_test_case((Rotation, "from_davenport"), (Rotation, "as_davenport")) +def test_as_davenport(xp): + dtype = xpx.default_dtype(xp) + rnd = np.random.RandomState(0) + n = 100 + angles = np.empty((n, 3)) + angles[:, 0] = rnd.uniform(low=-np.pi, high=np.pi, size=(n,)) + angles_middle = rnd.uniform(low=0, high=np.pi, size=(n,)) + angles[:, 2] = rnd.uniform(low=-np.pi, high=np.pi, size=(n,)) + lambdas = rnd.uniform(low=0, high=np.pi, size=(20,)) + + e1 = xp.asarray([1.0, 0, 0]) + e2 = xp.asarray([0.0, 1, 0]) + + for lamb in lambdas: + e3 = xp.asarray(Rotation.from_rotvec(lamb*e2).apply(e1)) + ax_lamb = xp.stack([e1, e2, e3], axis=0) + angles[:, 1] = angles_middle - lamb + for order in ['extrinsic', 'intrinsic']: + ax = ax_lamb if order == "intrinsic" else xp.flip(ax_lamb, axis=0) + rot = Rotation.from_davenport(ax, order, xp.asarray(angles, dtype=dtype)) + angles_dav = rot.as_davenport(ax, order) + xp_assert_close(angles_dav, xp.asarray(angles, dtype=dtype)) + + +@make_xp_test_case((Rotation, "from_davenport"), (Rotation, "as_davenport")) +def test_as_davenport_nd(xp): + rng = np.random.default_rng(0) + r = Rotation.from_quat(xp.asarray(rng.normal(size=(4, 9, 1, 4)))) + axes = r.as_matrix() # Get orthogonal axes + angles = xp.asarray(rng.uniform(low=-np.pi, high=np.pi, size=(4, 9, 1, 3))) + angles = xpx.at(angles)[..., 1].set(angles[..., 1] / 2) + + for order in ['extrinsic', 'intrinsic']: + if order == "intrinsic": + axes = xp.flip(axes, axis=-2) + rot = Rotation.from_davenport(axes, order, angles) + angles_dav = rot.as_davenport(axes, order) + xp_assert_close(angles_dav, angles) + + +@make_xp_test_case( + (Rotation, "from_davenport"), (Rotation, "as_davenport"), (Rotation, "as_matrix") +) +@pytest.mark.parametrize("suppress_warnings", (False, True)) +def test_as_davenport_degenerate(xp, suppress_warnings): + dtype = xpx.default_dtype(xp) + atol = 1e-12 if dtype == xp.float64 else 1e-6 + # Since we cannot check for angle equality, we check for rotation matrix + # equality + rnd = np.random.RandomState(0) + n = 5 + angles = np.empty((n, 3)) + + # symmetric sequences + angles[:, 0] = rnd.uniform(low=-np.pi, high=np.pi, size=(n,)) + angles_middle = [rnd.choice([0, np.pi]) for i in range(n)] + angles[:, 2] = rnd.uniform(low=-np.pi, high=np.pi, size=(n,)) + lambdas = rnd.uniform(low=0, high=np.pi, size=(5,)) + + e1 = xp.asarray([1.0, 0, 0]) + e2 = xp.asarray([0.0, 1, 0]) + + for lamb in lambdas: + e3 = xp.asarray(Rotation.from_rotvec(lamb*e2).apply(e1)) + ax_lamb = xp.stack([e1, e2, e3], axis=0) + angles[:, 1] = angles_middle - lamb + for order in ['extrinsic', 'intrinsic']: + ax = ax_lamb if order == 'intrinsic' else xp.flip(ax_lamb, axis=0) + rot = Rotation.from_davenport(ax, order, xp.asarray(angles, dtype=dtype)) + with maybe_warn_gimbal_lock(not suppress_warnings, xp): + angles_dav = rot.as_davenport( + ax, + order, + suppress_warnings=suppress_warnings + ) + mat_expected = rot.as_matrix() + rot_estimated = Rotation.from_davenport(ax, order, angles_dav) + mat_estimated = rot_estimated.as_matrix() + xp_assert_close(mat_expected, mat_estimated, atol=atol) + + +@make_xp_test_case((Rotation, "from_euler"), (Rotation, "from_davenport")) +def test_compare_from_davenport_from_euler(xp): + dtype = xpx.default_dtype(xp) + rnd = np.random.RandomState(0) + n = 100 + angles = np.empty((n, 3)) + + # symmetric sequences + rtol = 1e-12 if dtype == xp.float64 else 1e-5 + angles[:, 0] = rnd.uniform(low=-np.pi, high=np.pi, size=(n,)) + angles[:, 1] = rnd.uniform(low=0, high=np.pi, size=(n,)) + angles[:, 2] = rnd.uniform(low=-np.pi, high=np.pi, size=(n,)) + angles = xp.asarray(angles, dtype=dtype) + for order in ['extrinsic', 'intrinsic']: + for seq_tuple in permutations('xyz'): + seq = ''.join([seq_tuple[0], seq_tuple[1], seq_tuple[0]]) + ax = xp.asarray([basis_vec(i) for i in seq], dtype=dtype) + if order == 'intrinsic': + seq = seq.upper() + eul = Rotation.from_euler(seq, angles) + dav = Rotation.from_davenport(ax, order, angles) + xp_assert_close(eul.as_quat(canonical=True), dav.as_quat(canonical=True), + rtol=rtol) + + # asymmetric sequences + angles = xpx.at(angles)[:, 1].subtract(np.pi / 2) + for order in ['extrinsic', 'intrinsic']: + for seq_tuple in permutations('xyz'): + seq = ''.join(seq_tuple) + ax = xp.asarray([basis_vec(i) for i in seq], dtype=dtype) + if order == 'intrinsic': + seq = seq.upper() + eul = Rotation.from_euler(seq, angles) + dav = Rotation.from_davenport(ax, order, angles) + xp_assert_close(eul.as_quat(), dav.as_quat(), rtol=rtol) + + +@make_xp_test_case( + (Rotation, "from_euler"), (Rotation, "as_euler"), (Rotation, "as_davenport") +) +def test_compare_as_davenport_as_euler(xp): + rnd = np.random.RandomState(0) + n = 100 + angles = np.empty((n, 3)) + + # symmetric sequences + angles[:, 0] = rnd.uniform(low=-np.pi, high=np.pi, size=(n,)) + angles[:, 1] = rnd.uniform(low=0, high=np.pi, size=(n,)) + angles[:, 2] = rnd.uniform(low=-np.pi, high=np.pi, size=(n,)) + for order in ['extrinsic', 'intrinsic']: + for seq_tuple in permutations('xyz'): + seq = ''.join([seq_tuple[0], seq_tuple[1], seq_tuple[0]]) + ax = [basis_vec(i) for i in seq] + if order == 'intrinsic': + seq = seq.upper() + rot = Rotation.from_euler(seq, xp.asarray(angles)) + eul = rot.as_euler(seq) + dav = rot.as_davenport(xp.asarray(ax), order) + xp_assert_close(eul, dav, rtol=1e-12) + + # asymmetric sequences + angles[:, 1] -= np.pi / 2 + for order in ['extrinsic', 'intrinsic']: + for seq_tuple in permutations('xyz'): + seq = ''.join(seq_tuple) + ax = [basis_vec(i) for i in seq] + if order == 'intrinsic': + seq = seq.upper() + rot = Rotation.from_euler(seq, xp.asarray(angles)) + eul = rot.as_euler(seq) + dav = rot.as_davenport(xp.asarray(ax), order) + xp_assert_close(eul, dav, rtol=1e-12) + + +@make_xp_test_case( + (Rotation, "from_matrix"), (Rotation, "from_euler"), (Rotation, "from_rotvec"), + (Rotation, "from_davenport"), (Rotation, "from_mrp") +) +def test_zero_rotation_construction(xp): + r = Rotation.random(num=0) + assert len(r) == 0 + + r_ide = Rotation.identity(num=0) + assert len(r_ide) == 0 + + r_get = Rotation.random(num=3)[[]] + assert len(r_get) == 0 + + r_quat = Rotation.from_quat(xp.zeros((0, 4))) + assert len(r_quat) == 0 + + r_matrix = Rotation.from_matrix(xp.zeros((0, 3, 3))) + assert len(r_matrix) == 0 + + r_euler = Rotation.from_euler("xyz", xp.zeros((0, 3))) + assert len(r_euler) == 0 + + r_vec = Rotation.from_rotvec(xp.zeros((0, 3))) + assert len(r_vec) == 0 + + r_dav = Rotation.from_davenport(xp.eye(3), "extrinsic", xp.zeros((0, 3))) + assert len(r_dav) == 0 + + r_mrp = Rotation.from_mrp(xp.zeros((0, 3))) + assert len(r_mrp) == 0 + + +@make_xp_test_case( + (Rotation, "as_matrix"), (Rotation, "as_euler"), (Rotation, "as_rotvec"), + (Rotation, "as_mrp"), (Rotation, "as_davenport") +) +def test_zero_rotation_representation(xp): + r = Rotation.from_quat(xp.zeros((0, 4))) + assert r.as_quat().shape == (0, 4) + assert r.as_matrix().shape == (0, 3, 3) + assert r.as_euler("xyz").shape == (0, 3) + assert r.as_rotvec().shape == (0, 3) + assert r.as_mrp().shape == (0, 3) + assert r.as_davenport(xp.eye(3), "extrinsic").shape == (0, 3) + + +@make_xp_test_case((Rotation, "apply")) +def test_zero_rotation_array_rotation(xp): + r = Rotation.from_quat(xp.zeros((0, 4))) + + v = xp.asarray([1, 2, 3]) + v_rotated = r.apply(v) + assert v_rotated.shape == (0, 3) + + v0 = xp.zeros((0, 3)) + v0_rot = r.apply(v0) + assert v0_rot.shape == (0, 3) + + v2 = xp.ones((2, 3)) + with pytest.raises( + ValueError, match="Cannot broadcast"): + r.apply(v2) + + +@make_xp_test_case((Rotation, "__mul__")) +def test_zero_rotation_multiplication(xp): + r = Rotation.from_quat(xp.zeros((0, 4))) + + r_single = Rotation.from_quat(xp.asarray([0.0, 0, 0, 1])) + r_mult_left = r * r_single + assert len(r_mult_left) == 0 + + r_mult_right = r_single * r + assert len(r_mult_right) == 0 + + r0 = Rotation.from_quat(xp.zeros((0, 4))) + r_mult = r * r0 + assert len(r_mult) == 0 + + r2 = rotation_to_xp(Rotation.random(2), xp) + with pytest.raises(ValueError, match="Cannot broadcast"): + r0 * r2 + + with pytest.raises(ValueError, match="Cannot broadcast"): + r2 * r0 + + +@make_xp_test_case((Rotation, "concatenate")) +def test_zero_rotation_concatentation(xp): + r = Rotation.from_quat(xp.zeros((0, 4))) + + r0 = Rotation.concatenate([r, r]) + assert len(r0) == 0 + + r1 = Rotation.from_quat(xp.asarray([0.0, 0, 0, 1])) + r1 = r.concatenate([r1, r]) + assert len(r1) == 1 + + r3 = rotation_to_xp(Rotation.random(3), xp) + r3 = r.concatenate([r3, r]) + assert len(r3) == 3 + + r4 = rotation_to_xp(Rotation.random(4), xp) + r4 = r.concatenate([r, r4]) + r4 = r.concatenate([r, r4]) + assert len(r4) == 4 + + +@make_xp_test_case((Rotation, "__pow__")) +def test_zero_rotation_power(xp): + r = Rotation.from_quat(xp.zeros((0, 4))) + for pp in [-1.5, -1, 0, 1, 1.5]: + pow0 = r**pp + assert len(pow0) == 0 + + +@make_xp_test_case((Rotation, "inv")) +def test_zero_rotation_inverse(xp): + r = Rotation.from_quat(xp.zeros((0, 4))) + r_inv = r.inv() + assert len(r_inv) == 0 + + +@make_xp_test_case((Rotation, "magnitude")) +def test_zero_rotation_magnitude(xp): + r = Rotation.from_quat(xp.zeros((0, 4))) + magnitude = r.magnitude() + assert magnitude.shape == (0,) + + +@make_xp_test_case((Rotation, "mean")) +def test_zero_rotation_mean(xp): + r = Rotation.from_quat(xp.zeros((0, 4))) + with pytest.raises(ValueError, match="Mean of an empty rotation set is undefined."): + r.mean() + + +@make_xp_test_case((Rotation, "approx_equal")) +def test_zero_rotation_approx_equal(xp): + r = Rotation.from_quat(xp.zeros((0, 4))) + r0 = Rotation.from_quat(xp.zeros((0, 4))) + assert r.approx_equal(r0).shape == (0,) + r1 = Rotation.from_quat(xp.asarray([0.0, 0, 0, 1])) + assert r.approx_equal(r1).shape == (0,) + r2 = rotation_to_xp(Rotation.random(), xp) + assert r2.approx_equal(r).shape == (0,) + + approx_msg = "Expected broadcastable shapes in both rotations" + r3 = rotation_to_xp(Rotation.random(2), xp) + with pytest.raises(ValueError, match=approx_msg): + r.approx_equal(r3) + + with pytest.raises(ValueError, match=approx_msg): + r3.approx_equal(r) + + +@pytest.mark.skip_xp_backends("jax.numpy", + reason="JAX out-of-bounds indexing deviates from numpy") +def test_zero_rotation_get_set(xp): + r = Rotation.from_quat(xp.zeros((0, 4))) + + r_get = r[xp.asarray([], dtype=xp.bool)] + assert len(r_get) == 0 + + r_slice = r[:0] + assert len(r_slice) == 0 + + with pytest.raises(IndexError): + r[xp.asarray([0])] + + with pytest.raises(IndexError): + r[xp.asarray([True])] + + with pytest.raises(IndexError): + r[0] = Rotation.from_quat(xp.asarray([0, 0, 0, 1])) + + +@make_xp_test_case((Rotation, "__getitem__")) +def test_boolean_indexes(xp): + r = rotation_to_xp(Rotation.random(3), xp) + + r0 = r[xp.asarray([False, False, False])] + assert len(r0) == 0 + + r1 = r[xp.asarray([False, True, False])] + assert len(r1) == 1 + + r3 = r[xp.asarray([True, True, True])] + assert len(r3) == 3 + + # Multiple dimensions + r = Rotation.from_quat(xp.ones((3, 2, 4))) + r4 = r[xp.asarray([True, False, False])] + assert len(r4) == 1 + assert r4.as_quat().shape == (1, 2, 4) + + with pytest.raises(IndexError): + r[xp.asarray([True, True])] + + +@make_xp_test_case((Rotation, "__iter__")) +def test_rotation_iter(xp): + r = rotation_to_xp(Rotation.random(3), xp) + for i, r_i in enumerate(r): + assert isinstance(r_i, Rotation) + xp_assert_equal(r_i.as_quat(), r[i].as_quat()) + if i > len(r): + raise RuntimeError("Iteration exceeded length of rotations") + + +@pytest.mark.parametrize("ndim", list(range(1, 5))) +def test_rotation_shape(xp, ndim: int): + shape = tuple(range(2, 2 + ndim)[:ndim - 1]) + quat = xp.ones(shape + (4,)) + r = Rotation.from_quat(quat) + assert r.shape == shape, f"Got {r.shape}, expected {shape}" + + +def test_non_writeable(): + q = np.array([0, 0, 0, 1.0]) + q.flags.writeable = False + Rotation.from_quat(q) # Regression test against gh-24354, should not raise + + +def test_gh_24751(): + points = np.array([[0.0, 0.0, 0.0]]) + points.flags.writeable = False + rot = Rotation.from_euler("z", 0.0) + rot.apply(points) + + +def test_rotvec_non_writeable(): + rotvec = np.array([0, 0, 1]) * np.pi / 2 + rotvec.flags.writeable = False + Rotation.from_rotvec(rotvec) diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/transform/tests/test_rotation_groups.py b/venv/lib/python3.12/site-packages/scipy/spatial/transform/tests/test_rotation_groups.py new file mode 100644 index 0000000000000000000000000000000000000000..3f8a53b7abfede9b241fed2190e0f1244d76a9ee --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/transform/tests/test_rotation_groups.py @@ -0,0 +1,169 @@ +import pytest + +import numpy as np +from numpy.testing import assert_array_almost_equal +from scipy.spatial.transform import Rotation +from scipy.optimize import linear_sum_assignment +from scipy.spatial.distance import cdist +from scipy.constants import golden as phi +from scipy.spatial import cKDTree + + +TOL = 1E-12 +NS = list(range(1, 13)) +NAMES = ["I", "O", "T"] + [f"C{n}" for n in NS] + [f"D{n}" for n in NS] +SIZES = [60, 24, 12] + list(NS) + [2 * n for n in NS] + + +def _calculate_rmsd(P, Q): + """Calculates the root-mean-square distance between the points of P and Q. + The distance is taken as the minimum over all possible matchings. It is + zero if P and Q are identical and non-zero if not. + """ + distance_matrix = cdist(P, Q, metric='sqeuclidean') + matching = linear_sum_assignment(distance_matrix) + return np.sqrt(distance_matrix[matching].sum()) + + +def _generate_pyramid(n, axis): + thetas = np.linspace(0, 2 * np.pi, n + 1)[:-1] + P = np.vstack([np.zeros(n), np.cos(thetas), np.sin(thetas)]).T + P = np.concatenate((P, [[1, 0, 0]])) + return np.roll(P, axis, axis=1) + + +def _generate_prism(n, axis): + thetas = np.linspace(0, 2 * np.pi, n + 1)[:-1] + bottom = np.vstack([-np.ones(n), np.cos(thetas), np.sin(thetas)]).T + top = np.vstack([+np.ones(n), np.cos(thetas), np.sin(thetas)]).T + P = np.concatenate((bottom, top)) + return np.roll(P, axis, axis=1) + + +def _generate_icosahedron(): + x = np.array([[0, -1, -phi], + [0, -1, +phi], + [0, +1, -phi], + [0, +1, +phi]]) + return np.concatenate([np.roll(x, i, axis=1) for i in range(3)]) + + +def _generate_octahedron(): + return np.array([[-1, 0, 0], [+1, 0, 0], [0, -1, 0], + [0, +1, 0], [0, 0, -1], [0, 0, +1]]) + + +def _generate_tetrahedron(): + return np.array([[1, 1, 1], [1, -1, -1], [-1, 1, -1], [-1, -1, 1]]) + + +@pytest.mark.parametrize("name", [-1, None, True, np.array(['C3'])]) +def test_group_type(name): + with pytest.raises(ValueError, + match="must be a string"): + Rotation.create_group(name) + + +@pytest.mark.parametrize("name", ["Q", " ", "CA", "C ", "DA", "D ", "I2", ""]) +def test_group_name(name): + with pytest.raises(ValueError, + match="must be one of 'I', 'O', 'T', 'Dn', 'Cn'"): + Rotation.create_group(name) + + +@pytest.mark.parametrize("name", ["C0", "D0"]) +def test_group_order_positive(name): + with pytest.raises(ValueError, + match="Group order must be positive"): + Rotation.create_group(name) + + +@pytest.mark.parametrize("axis", ['A', 'b', 0, 1, 2, 4, False, None]) +def test_axis_valid(axis): + with pytest.raises(ValueError, + match="`axis` must be one of"): + Rotation.create_group("C1", axis) + + +def test_icosahedral(): + """The icosahedral group fixes the rotations of an icosahedron. Here we + test that the icosahedron is invariant after application of the elements + of the rotation group.""" + P = _generate_icosahedron() + for g in Rotation.create_group("I"): + g = Rotation.from_quat(g.as_quat()) + assert _calculate_rmsd(P, g.apply(P)) < TOL + + +def test_octahedral(): + """Test that the octahedral group correctly fixes the rotations of an + octahedron.""" + P = _generate_octahedron() + for g in Rotation.create_group("O"): + assert _calculate_rmsd(P, g.apply(P)) < TOL + + +def test_tetrahedral(): + """Test that the tetrahedral group correctly fixes the rotations of a + tetrahedron.""" + P = _generate_tetrahedron() + for g in Rotation.create_group("T"): + assert _calculate_rmsd(P, g.apply(P)) < TOL + + +@pytest.mark.parametrize("n", NS) +@pytest.mark.parametrize("axis", 'XYZ') +def test_dicyclic(n, axis): + """Test that the dicyclic group correctly fixes the rotations of a + prism.""" + P = _generate_prism(n, axis='XYZ'.index(axis)) + for g in Rotation.create_group(f"D{n}", axis=axis): + assert _calculate_rmsd(P, g.apply(P)) < TOL + + +@pytest.mark.parametrize("n", NS) +@pytest.mark.parametrize("axis", 'XYZ') +def test_cyclic(n, axis): + """Test that the cyclic group correctly fixes the rotations of a + pyramid.""" + P = _generate_pyramid(n, axis='XYZ'.index(axis)) + for g in Rotation.create_group(f"C{n}", axis=axis): + assert _calculate_rmsd(P, g.apply(P)) < TOL + + +@pytest.mark.parametrize("name, size", list(zip(NAMES, SIZES))) +def test_group_sizes(name, size): + assert len(Rotation.create_group(name)) == size + + +@pytest.mark.parametrize("name, size", list(zip(NAMES, SIZES))) +def test_group_no_duplicates(name, size): + g = Rotation.create_group(name) + kdtree = cKDTree(g.as_quat()) + assert len(kdtree.query_pairs(1E-3)) == 0 + + +@pytest.mark.parametrize("name, size", list(zip(NAMES, SIZES))) +def test_group_symmetry(name, size): + g = Rotation.create_group(name) + q = np.concatenate((-g.as_quat(), g.as_quat())) + distance = np.sort(cdist(q, q)) + deltas = np.max(distance, axis=0) - np.min(distance, axis=0) + assert (deltas < TOL).all() + + +@pytest.mark.parametrize("name", NAMES) +def test_reduction(name): + """Test that the elements of the rotation group are correctly + mapped onto the identity rotation.""" + g = Rotation.create_group(name) + f = g.reduce(g) + assert_array_almost_equal(f.magnitude(), np.zeros(len(g))) + + +@pytest.mark.parametrize("name", NAMES) +def test_single_reduction(name): + g = Rotation.create_group(name) + f = g[-1].reduce(g) + assert_array_almost_equal(f.magnitude(), 0) + assert f.as_quat().shape == (4,) diff --git a/venv/lib/python3.12/site-packages/scipy/spatial/transform/tests/test_rotation_spline.py b/venv/lib/python3.12/site-packages/scipy/spatial/transform/tests/test_rotation_spline.py new file mode 100644 index 0000000000000000000000000000000000000000..d268659e06416506243593bc8a4c2e8febf3f98e --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/spatial/transform/tests/test_rotation_spline.py @@ -0,0 +1,188 @@ +from itertools import product +import numpy as np +from numpy.testing import assert_allclose +import pytest +from scipy.spatial.transform import Rotation, RotationSpline +from scipy.spatial.transform._rotation_spline import ( + _angular_rate_to_rotvec_dot_matrix, + _rotvec_dot_to_angular_rate_matrix, + _matrix_vector_product_of_stacks, + _angular_acceleration_nonlinear_term, + _create_block_3_diagonal_matrix) + + +pytestmark = pytest.mark.skip_xp_backends(np_only=True) + + +def test_angular_rate_to_rotvec_conversions(): + np.random.seed(0) + rv = np.random.randn(4, 3) + A = _angular_rate_to_rotvec_dot_matrix(rv) + A_inv = _rotvec_dot_to_angular_rate_matrix(rv) + + # When the rotation vector is aligned with the angular rate, then + # the rotation vector rate and angular rate are the same. + assert_allclose(_matrix_vector_product_of_stacks(A, rv), rv) + assert_allclose(_matrix_vector_product_of_stacks(A_inv, rv), rv) + + # A and A_inv must be reciprocal to each other. + I_stack = np.empty((4, 3, 3)) + I_stack[:] = np.eye(3) + assert_allclose(np.matmul(A, A_inv), I_stack, atol=1e-15) + + +def test_angular_rate_nonlinear_term(): + # The only simple test is to check that the term is zero when + # the rotation vector + np.random.seed(0) + rv = np.random.rand(4, 3) + assert_allclose(_angular_acceleration_nonlinear_term(rv, rv), 0, + atol=1e-19) + + +def test_create_block_3_diagonal_matrix(): + np.random.seed(0) + A = np.empty((4, 3, 3)) + A[:] = np.arange(1, 5)[:, None, None] + + B = np.empty((4, 3, 3)) + B[:] = -np.arange(1, 5)[:, None, None] + d = 10 * np.arange(10, 15) + + banded = _create_block_3_diagonal_matrix(A, B, d) + + # Convert the banded matrix to the full matrix. + k, l = list(zip(*product(np.arange(banded.shape[0]), + np.arange(banded.shape[1])))) + k = np.asarray(k) + l = np.asarray(l) + + i = k - 5 + l + j = l + values = banded.ravel() + mask = (i >= 0) & (i < 15) + i = i[mask] + j = j[mask] + values = values[mask] + full = np.zeros((15, 15)) + full[i, j] = values + + zero = np.zeros((3, 3)) + eye = np.eye(3) + + # Create the reference full matrix in the most straightforward manner. + ref = np.block([ + [d[0] * eye, B[0], zero, zero, zero], + [A[0], d[1] * eye, B[1], zero, zero], + [zero, A[1], d[2] * eye, B[2], zero], + [zero, zero, A[2], d[3] * eye, B[3]], + [zero, zero, zero, A[3], d[4] * eye], + ]) + + assert_allclose(full, ref, atol=1e-19) + + +def test_spline_2_rotations(): + times = [0, 10] + rotations = Rotation.from_euler('xyz', [[0, 0, 0], [10, -20, 30]], + degrees=True) + spline = RotationSpline(times, rotations) + + rv = (rotations[0].inv() * rotations[1]).as_rotvec() + rate = rv / (times[1] - times[0]) + times_check = np.array([-1, 5, 12]) + dt = times_check - times[0] + rv_ref = rate * dt[:, None] + + assert_allclose(spline(times_check).as_rotvec(), rv_ref) + assert_allclose(spline(times_check, 1), np.resize(rate, (3, 3))) + assert_allclose(spline(times_check, 2), 0, atol=1e-16) + + +def test_constant_attitude(): + times = np.arange(10) + rotations = Rotation.from_rotvec(np.ones((10, 3))) + spline = RotationSpline(times, rotations) + + times_check = np.linspace(-1, 11) + assert_allclose(spline(times_check).as_rotvec(), 1, rtol=1e-15) + assert_allclose(spline(times_check, 1), 0, atol=1e-17) + assert_allclose(spline(times_check, 2), 0, atol=1e-17) + + assert_allclose(spline(5.5).as_rotvec(), 1, rtol=1e-15) + assert_allclose(spline(5.5, 1), 0, atol=1e-17) + assert_allclose(spline(5.5, 2), 0, atol=1e-17) + + +def test_spline_properties(): + times = np.array([0, 5, 15, 27]) + angles = [[-5, 10, 27], [3, 5, 38], [-12, 10, 25], [-15, 20, 11]] + + rotations = Rotation.from_euler('xyz', angles, degrees=True) + spline = RotationSpline(times, rotations) + + assert_allclose(spline(times).as_euler('xyz', degrees=True), angles) + assert_allclose(spline(0).as_euler('xyz', degrees=True), angles[0]) + + h = 1e-8 + rv0 = spline(times).as_rotvec() + rvm = spline(times - h).as_rotvec() + rvp = spline(times + h).as_rotvec() + # rtol bumped from 1e-15 to 1.5e-15 in gh18414 for linux 32 bit + assert_allclose(rv0, 0.5 * (rvp + rvm), rtol=1.5e-15) + + r0 = spline(times, 1) + rm = spline(times - h, 1) + rp = spline(times + h, 1) + assert_allclose(r0, 0.5 * (rm + rp), rtol=1e-14) + + a0 = spline(times, 2) + am = spline(times - h, 2) + ap = spline(times + h, 2) + assert_allclose(a0, am, rtol=1e-7) + assert_allclose(a0, ap, rtol=1e-7) + + +def test_error_handling(): + with pytest.raises(ValueError): + RotationSpline([1.0], Rotation.random()) + + r = Rotation.random(10) + t = np.arange(10).reshape(5, 2) + with pytest.raises(ValueError): + RotationSpline(t, r) + + t = np.arange(9) + with pytest.raises(ValueError): + RotationSpline(t, r) + + t = np.arange(10) + t[5] = 0 + with pytest.raises(ValueError): + RotationSpline(t, r) + + t = np.arange(10) + + s = RotationSpline(t, r) + with pytest.raises(ValueError): + s(10, -1) + + with pytest.raises(ValueError): + s(np.arange(10).reshape(5, 2)) + + r = Rotation.from_quat(np.array([[[0.0, 0, 0, 1], [1, 0, 0 ,0]]])) + t = np.arange(2) + with pytest.raises(ValueError): + RotationSpline(t, r) # More than 2 dimensions + + +@pytest.mark.skip_xp_backends("numpy") +def test_xp_errors(xp): + times = xp.asarray([0, 10]) + r = Rotation.random(2) + r = Rotation.from_quat(xp.asarray(r.as_quat())) + s = RotationSpline(times, r) + t = xp.asarray([0.5, 1.5]) + # RotationSpline does not have native Array API support, so we check that it + # converts any array to NumPy and outputs NumPy arrays. + assert isinstance(s(t).as_quat(), np.ndarray) diff --git a/venv/lib/python3.12/site-packages/scipy/special/__init__.pxd b/venv/lib/python3.12/site-packages/scipy/special/__init__.pxd new file mode 100644 index 0000000000000000000000000000000000000000..1daa9fb379572aac4bc9b6d74330a18c5c52bf79 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/__init__.pxd @@ -0,0 +1 @@ +from scipy.special cimport cython_special diff --git a/venv/lib/python3.12/site-packages/scipy/special/__init__.py b/venv/lib/python3.12/site-packages/scipy/special/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..9bcbfd132896a63ccf689c54ab9c36fae7db695a --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/__init__.py @@ -0,0 +1,842 @@ +""" +======================================== +Special functions (:mod:`scipy.special`) +======================================== + +.. currentmodule:: scipy.special + +.. toctree:: + :hidden: + + special.cython_special + +Almost all of the functions below accept NumPy arrays as input +arguments as well as single numbers. This means they follow +broadcasting and automatic array-looping rules. Technically, +they are `NumPy universal functions +`_. +Functions which do not accept NumPy arrays are marked by a warning +in the section description. + +.. seealso:: + + `scipy.special.cython_special` -- Typed Cython versions of special functions + + +Error handling +============== + +Errors are handled by returning NaNs or other appropriate values. +Some of the special function routines can emit warnings or raise +exceptions when an error occurs. By default this is disabled, except +for memory allocation errors, which result in an exception being raised. +To query and control the current error handling state the following +functions are provided. + +.. autosummary:: + :toctree: generated/ + + geterr -- Get the current way of handling special-function errors. + seterr -- Set how special-function errors are handled. + errstate -- Context manager for special-function error handling. + SpecialFunctionWarning -- Warning that can be emitted by special functions. + SpecialFunctionError -- Exception that can be raised by special functions. + +Available functions +=================== + +Airy functions +-------------- + +.. autosummary:: + :toctree: generated/ + + airy -- Airy functions and their derivatives. + airye -- Exponentially scaled Airy functions and their derivatives. + ai_zeros -- Compute `nt` zeros and values of the Airy function Ai and its derivative. + bi_zeros -- Compute `nt` zeros and values of the Airy function Bi and its derivative. + itairy -- Integrals of Airy functions + + +Elliptic functions and integrals +-------------------------------- + +.. autosummary:: + :toctree: generated/ + + ellipj -- Jacobian elliptic functions. + ellipk -- Complete elliptic integral of the first kind. + ellipkm1 -- Complete elliptic integral of the first kind around `m` = 1. + ellipkinc -- Incomplete elliptic integral of the first kind. + ellipe -- Complete elliptic integral of the second kind. + ellipeinc -- Incomplete elliptic integral of the second kind. + elliprc -- Degenerate symmetric integral RC. + elliprd -- Symmetric elliptic integral of the second kind. + elliprf -- Completely-symmetric elliptic integral of the first kind. + elliprg -- Completely-symmetric elliptic integral of the second kind. + elliprj -- Symmetric elliptic integral of the third kind. + +Bessel functions +---------------- + +.. autosummary:: + :toctree: generated/ + + jv -- Bessel function of the first kind of real order and \ + complex argument. + jve -- Exponentially scaled Bessel function of order `v`. + yn -- Bessel function of the second kind of integer order and \ + real argument. + yv -- Bessel function of the second kind of real order and \ + complex argument. + yve -- Exponentially scaled Bessel function of the second kind \ + of real order. + iv -- Modified Bessel function of the first kind of real order. + ive -- Exponentially scaled modified Bessel function of the \ + first kind. + kn -- Modified Bessel function of the second kind of integer \ + order `n` + kv -- Modified Bessel function of the second kind of real order \ + `v` + kve -- Exponentially scaled modified Bessel function of the \ + second kind. + hankel1 -- Hankel function of the first kind. + hankel1e -- Exponentially scaled Hankel function of the first kind. + hankel2 -- Hankel function of the second kind. + hankel2e -- Exponentially scaled Hankel function of the second kind. + wright_bessel -- Wright's generalized Bessel function. + log_wright_bessel -- Logarithm of Wright's generalized Bessel function. + +The following function does not accept NumPy arrays (it is not a +universal function): + +.. autosummary:: + :toctree: generated/ + + lmbda -- Jahnke-Emden Lambda function, Lambdav(x). + +Zeros of Bessel functions +^^^^^^^^^^^^^^^^^^^^^^^^^ + +The following functions do not accept NumPy arrays (they are not +universal functions): + +.. autosummary:: + :toctree: generated/ + + jnjnp_zeros -- Compute zeros of integer-order Bessel functions Jn and Jn'. + jnyn_zeros -- Compute nt zeros of Bessel functions Jn(x), Jn'(x), Yn(x), and Yn'(x). + jn_zeros -- Compute zeros of integer-order Bessel function Jn(x). + jnp_zeros -- Compute zeros of integer-order Bessel function derivative Jn'(x). + yn_zeros -- Compute zeros of integer-order Bessel function Yn(x). + ynp_zeros -- Compute zeros of integer-order Bessel function derivative Yn'(x). + y0_zeros -- Compute nt zeros of Bessel function Y0(z), and derivative at each zero. + y1_zeros -- Compute nt zeros of Bessel function Y1(z), and derivative at each zero. + y1p_zeros -- Compute nt zeros of Bessel derivative Y1'(z), and value at each zero. + +Faster versions of common Bessel functions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autosummary:: + :toctree: generated/ + + j0 -- Bessel function of the first kind of order 0. + j1 -- Bessel function of the first kind of order 1. + y0 -- Bessel function of the second kind of order 0. + y1 -- Bessel function of the second kind of order 1. + i0 -- Modified Bessel function of order 0. + i0e -- Exponentially scaled modified Bessel function of order 0. + i1 -- Modified Bessel function of order 1. + i1e -- Exponentially scaled modified Bessel function of order 1. + k0 -- Modified Bessel function of the second kind of order 0, :math:`K_0`. + k0e -- Exponentially scaled modified Bessel function K of order 0 + k1 -- Modified Bessel function of the second kind of order 1, :math:`K_1(x)`. + k1e -- Exponentially scaled modified Bessel function K of order 1. + +Integrals of Bessel functions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autosummary:: + :toctree: generated/ + + itj0y0 -- Integrals of Bessel functions of order 0. + it2j0y0 -- Integrals related to Bessel functions of order 0. + iti0k0 -- Integrals of modified Bessel functions of order 0. + it2i0k0 -- Integrals related to modified Bessel functions of order 0. + besselpoly -- Weighted integral of a Bessel function. + +Derivatives of Bessel functions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autosummary:: + :toctree: generated/ + + jvp -- Compute nth derivative of Bessel function Jv(z) with respect to `z`. + yvp -- Compute nth derivative of Bessel function Yv(z) with respect to `z`. + ivp -- Compute nth derivative of modified Bessel function Iv(z) with respect to `z`. + kvp -- Compute nth derivative of real-order modified Bessel function Kv(z) + h1vp -- Compute nth derivative of Hankel function H1v(z) with respect to `z`. + h2vp -- Compute nth derivative of Hankel function H2v(z) with respect to `z`. + +Spherical Bessel functions +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autosummary:: + :toctree: generated/ + + spherical_jn -- Spherical Bessel function of the first kind or its derivative. + spherical_yn -- Spherical Bessel function of the second kind or its derivative. + spherical_in -- Modified spherical Bessel function of the first kind or its derivative. + spherical_kn -- Modified spherical Bessel function of the second kind or its derivative. + +Riccati-Bessel functions +^^^^^^^^^^^^^^^^^^^^^^^^ + +The following functions do not accept NumPy arrays (they are not +universal functions): + +.. autosummary:: + :toctree: generated/ + + riccati_jn -- Compute Riccati-Bessel function of the first kind and its derivative. + riccati_yn -- Compute Riccati-Bessel function of the second kind and its derivative. + +Struve functions +---------------- + +.. autosummary:: + :toctree: generated/ + + struve -- Struve function. + modstruve -- Modified Struve function. + itstruve0 -- Integral of the Struve function of order 0. + it2struve0 -- Integral related to the Struve function of order 0. + itmodstruve0 -- Integral of the modified Struve function of order 0. + + +Raw statistical functions +------------------------- + +.. seealso:: :mod:`scipy.stats`: Friendly versions of these functions. + +Binomial distribution +^^^^^^^^^^^^^^^^^^^^^ + +.. autosummary:: + :toctree: generated/ + + bdtr -- Binomial distribution cumulative distribution function. + bdtrc -- Binomial distribution survival function. + bdtri -- Inverse function to `bdtr` with respect to `p`. + bdtrik -- Inverse function to `bdtr` with respect to `k`. + bdtrin -- Inverse function to `bdtr` with respect to `n`. + +Beta distribution +^^^^^^^^^^^^^^^^^ + +.. autosummary:: + :toctree: generated/ + + btdtria -- Inverse of `betainc` with respect to `a`. + btdtrib -- Inverse of `betainc` with respect to `b`. + +F distribution +^^^^^^^^^^^^^^ + +.. autosummary:: + :toctree: generated/ + + fdtr -- F cumulative distribution function. + fdtrc -- F survival function. + fdtri -- The `p`-th quantile of the F-distribution. + fdtridfd -- Inverse to `fdtr` vs dfd. + +Gamma distribution +^^^^^^^^^^^^^^^^^^ + +.. autosummary:: + :toctree: generated/ + + gdtr -- Gamma distribution cumulative distribution function. + gdtrc -- Gamma distribution survival function. + gdtria -- Inverse of `gdtr` vs a. + gdtrib -- Inverse of `gdtr` vs b. + gdtrix -- Inverse of `gdtr` vs x. + +Negative binomial distribution +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autosummary:: + :toctree: generated/ + + nbdtr -- Negative binomial cumulative distribution function. + nbdtrc -- Negative binomial survival function. + nbdtri -- Inverse of `nbdtr` vs `p`. + nbdtrik -- Inverse of `nbdtr` vs `k`. + nbdtrin -- Inverse of `nbdtr` vs `n`. + +Noncentral F distribution +^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autosummary:: + :toctree: generated/ + + ncfdtr -- Cumulative distribution function of the non-central F distribution. + ncfdtridfd -- Calculate degrees of freedom (denominator) for the noncentral F-distribution. + ncfdtridfn -- Calculate degrees of freedom (numerator) for the noncentral F-distribution. + ncfdtri -- Inverse cumulative distribution function of the non-central F distribution. + ncfdtrinc -- Calculate non-centrality parameter for non-central F distribution. + +Noncentral t distribution +^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autosummary:: + :toctree: generated/ + + nctdtr -- Cumulative distribution function of the non-central `t` distribution. + nctdtridf -- Calculate degrees of freedom for non-central t distribution. + nctdtrit -- Inverse cumulative distribution function of the non-central t distribution. + nctdtrinc -- Calculate non-centrality parameter for non-central t distribution. + +Normal distribution +^^^^^^^^^^^^^^^^^^^ + +.. autosummary:: + :toctree: generated/ + + nrdtrimn -- Calculate mean of normal distribution given other params. + nrdtrisd -- Calculate standard deviation of normal distribution given other params. + ndtr -- Normal cumulative distribution function. + log_ndtr -- Logarithm of normal cumulative distribution function. + ndtri -- Inverse of `ndtr` vs x. + ndtri_exp -- Inverse of `log_ndtr` vs x. + +Poisson distribution +^^^^^^^^^^^^^^^^^^^^ + +.. autosummary:: + :toctree: generated/ + + pdtr -- Poisson cumulative distribution function. + pdtrc -- Poisson survival function. + pdtri -- Inverse to `pdtr` vs m. + pdtrik -- Inverse to `pdtr` vs k. + +Student t distribution +^^^^^^^^^^^^^^^^^^^^^^ + +.. autosummary:: + :toctree: generated/ + + stdtr -- Student t distribution cumulative distribution function. + stdtridf -- Inverse of `stdtr` vs df. + stdtrit -- Inverse of `stdtr` vs `t`. + +Chi square distribution +^^^^^^^^^^^^^^^^^^^^^^^ + +.. autosummary:: + :toctree: generated/ + + chdtr -- Chi square cumulative distribution function. + chdtrc -- Chi square survival function. + chdtri -- Inverse to `chdtrc`. + chdtriv -- Inverse to `chdtr` vs `v`. + +Non-central chi square distribution +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autosummary:: + :toctree: generated/ + + chndtr -- Non-central chi square cumulative distribution function. + chndtridf -- Inverse to `chndtr` vs `df`. + chndtrinc -- Inverse to `chndtr` vs `nc`. + chndtrix -- Inverse to `chndtr` vs `x`. + +Kolmogorov distribution +^^^^^^^^^^^^^^^^^^^^^^^ + +.. autosummary:: + :toctree: generated/ + + smirnov -- Kolmogorov-Smirnov complementary cumulative distribution function. + smirnovi -- Inverse to `smirnov`. + kolmogorov -- Complementary cumulative distribution function of Kolmogorov distribution. + kolmogi -- Inverse function to `kolmogorov`. + +Box-Cox transformation +^^^^^^^^^^^^^^^^^^^^^^ + +.. autosummary:: + :toctree: generated/ + + boxcox -- Compute the Box-Cox transformation. + boxcox1p -- Compute the Box-Cox transformation of 1 + `x`. + inv_boxcox -- Compute the inverse of the Box-Cox transformation. + inv_boxcox1p -- Compute the inverse of the Box-Cox transformation. + + +Sigmoidal functions +^^^^^^^^^^^^^^^^^^^ + +.. autosummary:: + :toctree: generated/ + + logit -- Logit ufunc for ndarrays. + expit -- Logistic sigmoid function. + log_expit -- Logarithm of the logistic sigmoid function. + +Miscellaneous +^^^^^^^^^^^^^ + +.. autosummary:: + :toctree: generated/ + + tklmbda -- Tukey-Lambda cumulative distribution function. + owens_t -- Owen's T Function. + + +Information Theory functions +---------------------------- + +.. autosummary:: + :toctree: generated/ + + entr -- Elementwise function for computing entropy. + rel_entr -- Elementwise function for computing relative entropy. + kl_div -- Elementwise function for computing Kullback-Leibler divergence. + huber -- Huber loss function. + pseudo_huber -- Pseudo-Huber loss function. + + +Gamma and related functions +--------------------------- + +.. autosummary:: + :toctree: generated/ + + gamma -- Gamma function. + gammaln -- Logarithm of the absolute value of the Gamma function for real inputs. + loggamma -- Principal branch of the logarithm of the Gamma function. + gammasgn -- Sign of the gamma function. + gammainc -- Regularized lower incomplete gamma function. + gammaincinv -- Inverse to `gammainc`. + gammaincc -- Regularized upper incomplete gamma function. + gammainccinv -- Inverse to `gammaincc`. + beta -- Beta function. + betaln -- Natural logarithm of absolute value of beta function. + betainc -- Incomplete beta integral. + betaincc -- Complemented incomplete beta integral. + betaincinv -- Inverse function to beta integral. + betainccinv -- Inverse of the complemented incomplete beta integral. + psi -- The digamma function. + rgamma -- Gamma function inverted. + polygamma -- Polygamma function n. + multigammaln -- Returns the log of multivariate gamma, also sometimes called the generalized gamma. + digamma -- psi(x[, out]). + poch -- Rising factorial (z)_m. + +Error function and Fresnel integrals +------------------------------------ + +.. autosummary:: + :toctree: generated/ + + erf -- Returns the error function of complex argument. + erfc -- Complementary error function, ``1 - erf(x)``. + erfcx -- Scaled complementary error function, ``exp(x**2) * erfc(x)``. + erfi -- Imaginary error function, ``-i erf(i z)``. + erfinv -- Inverse function for erf. + erfcinv -- Inverse function for erfc. + wofz -- Faddeeva function. + dawsn -- Dawson's integral. + fresnel -- Fresnel sin and cos integrals. + fresnel_zeros -- Compute nt complex zeros of sine and cosine Fresnel integrals S(z) and C(z). + modfresnelp -- Modified Fresnel positive integrals. + modfresnelm -- Modified Fresnel negative integrals. + voigt_profile -- Voigt profile. + +The following functions do not accept NumPy arrays (they are not +universal functions): + +.. autosummary:: + :toctree: generated/ + + erf_zeros -- Compute nt complex zeros of error function erf(z). + fresnelc_zeros -- Compute nt complex zeros of cosine Fresnel integral C(z). + fresnels_zeros -- Compute nt complex zeros of sine Fresnel integral S(z). + +Legendre functions +------------------ + +.. autosummary:: + :toctree: generated/ + + legendre_p -- Legendre polynomials of the first kind. + legendre_p_all -- All Legendre polynomials of the first kind up to a specified order. + assoc_legendre_p -- Associated Legendre polynomials of the first kind. + assoc_legendre_p_all -- All associated Legendre polynomials of the first kind up to a specified order and degree. + sph_legendre_p -- Spherical Legendre polynomials of the first kind. + sph_legendre_p_all -- All spherical Legendre polynomials of the first kind up to a specified order and degree. + sph_harm_y -- Spherical harmonics. + sph_harm_y_all -- All spherical harmonics up to a specified order and degree. + +The following functions are in the process of being deprecated in favor of the above, +which provide a more flexible and consistent interface. + +.. autosummary:: + :toctree: generated/ + + lpmv -- Associated Legendre function of integer order and real degree. + lqn -- Legendre function of the second kind. + lqmn -- Sequence of associated Legendre functions of the second kind. + +Ellipsoidal harmonics +--------------------- + +.. autosummary:: + :toctree: generated/ + + ellip_harm -- Ellipsoidal harmonic functions E^p_n(l). + ellip_harm_2 -- Ellipsoidal harmonic functions F^p_n(l). + ellip_normal -- Ellipsoidal harmonic normalization constants gamma^p_n. + +Orthogonal polynomials +---------------------- + +The following functions evaluate values of orthogonal polynomials: + +.. autosummary:: + :toctree: generated/ + + assoc_laguerre -- Compute the generalized (associated) Laguerre polynomial of degree n and order k. + eval_legendre -- Evaluate Legendre polynomial at a point. + eval_chebyt -- Evaluate Chebyshev polynomial of the first kind at a point. + eval_chebyu -- Evaluate Chebyshev polynomial of the second kind at a point. + eval_chebyc -- Evaluate Chebyshev polynomial of the first kind on [-2, 2] at a point. + eval_chebys -- Evaluate Chebyshev polynomial of the second kind on [-2, 2] at a point. + eval_jacobi -- Evaluate Jacobi polynomial at a point. + eval_laguerre -- Evaluate Laguerre polynomial at a point. + eval_genlaguerre -- Evaluate generalized Laguerre polynomial at a point. + eval_hermite -- Evaluate physicist's Hermite polynomial at a point. + eval_hermitenorm -- Evaluate probabilist's (normalized) Hermite polynomial at a point. + eval_gegenbauer -- Evaluate Gegenbauer polynomial at a point. + eval_sh_legendre -- Evaluate shifted Legendre polynomial at a point. + eval_sh_chebyt -- Evaluate shifted Chebyshev polynomial of the first kind at a point. + eval_sh_chebyu -- Evaluate shifted Chebyshev polynomial of the second kind at a point. + eval_sh_jacobi -- Evaluate shifted Jacobi polynomial at a point. + +The following functions compute roots and quadrature weights for +orthogonal polynomials: + +.. autosummary:: + :toctree: generated/ + + roots_legendre -- Gauss-Legendre quadrature. + roots_chebyt -- Gauss-Chebyshev (first kind) quadrature. + roots_chebyu -- Gauss-Chebyshev (second kind) quadrature. + roots_chebyc -- Gauss-Chebyshev (first kind) quadrature. + roots_chebys -- Gauss-Chebyshev (second kind) quadrature. + roots_jacobi -- Gauss-Jacobi quadrature. + roots_laguerre -- Gauss-Laguerre quadrature. + roots_genlaguerre -- Gauss-generalized Laguerre quadrature. + roots_hermite -- Gauss-Hermite (physicist's) quadrature. + roots_hermitenorm -- Gauss-Hermite (statistician's) quadrature. + roots_gegenbauer -- Gauss-Gegenbauer quadrature. + roots_sh_legendre -- Gauss-Legendre (shifted) quadrature. + roots_sh_chebyt -- Gauss-Chebyshev (first kind, shifted) quadrature. + roots_sh_chebyu -- Gauss-Chebyshev (second kind, shifted) quadrature. + roots_sh_jacobi -- Gauss-Jacobi (shifted) quadrature. + +The functions below, in turn, return the polynomial coefficients in +``orthopoly1d`` objects, which function similarly as `numpy.poly1d`. +The ``orthopoly1d`` class also has an attribute ``weights``, which returns +the roots, weights, and total weights for the appropriate form of Gaussian +quadrature. These are returned in an ``n x 3`` array with roots in the first +column, weights in the second column, and total weights in the final column. +Note that ``orthopoly1d`` objects are converted to `~numpy.poly1d` when doing +arithmetic, and lose information of the original orthogonal polynomial. + +.. autosummary:: + :toctree: generated/ + + legendre -- Legendre polynomial. + chebyt -- Chebyshev polynomial of the first kind. + chebyu -- Chebyshev polynomial of the second kind. + chebyc -- Chebyshev polynomial of the first kind on :math:`[-2, 2]`. + chebys -- Chebyshev polynomial of the second kind on :math:`[-2, 2]`. + jacobi -- Jacobi polynomial. + laguerre -- Laguerre polynomial. + genlaguerre -- Generalized (associated) Laguerre polynomial. + hermite -- Physicist's Hermite polynomial. + hermitenorm -- Normalized (probabilist's) Hermite polynomial. + gegenbauer -- Gegenbauer (ultraspherical) polynomial. + sh_legendre -- Shifted Legendre polynomial. + sh_chebyt -- Shifted Chebyshev polynomial of the first kind. + sh_chebyu -- Shifted Chebyshev polynomial of the second kind. + sh_jacobi -- Shifted Jacobi polynomial. + +.. warning:: + + Computing values of high-order polynomials (around ``order > 20``) using + polynomial coefficients is numerically unstable. To evaluate polynomial + values, the ``eval_*`` functions should be used instead. + + +Hypergeometric functions +------------------------ + +.. autosummary:: + :toctree: generated/ + + hyp2f1 -- Gauss hypergeometric function 2F1(a, b; c; z). + hyp1f1 -- Confluent hypergeometric function 1F1(a, b; x). + hyperu -- Confluent hypergeometric function U(a, b, x) of the second kind. + hyp0f1 -- Confluent hypergeometric limit function 0F1. + + +Parabolic cylinder functions +---------------------------- + +.. autosummary:: + :toctree: generated/ + + pbdv -- Parabolic cylinder function D. + pbvv -- Parabolic cylinder function V. + pbwa -- Parabolic cylinder function W. + +The following functions do not accept NumPy arrays (they are not +universal functions): + +.. autosummary:: + :toctree: generated/ + + pbdv_seq -- Parabolic cylinder functions Dv(x) and derivatives. + pbvv_seq -- Parabolic cylinder functions Vv(x) and derivatives. + pbdn_seq -- Parabolic cylinder functions Dn(z) and derivatives. + +Mathieu and related functions +----------------------------- + +.. autosummary:: + :toctree: generated/ + + mathieu_a -- Characteristic value of even Mathieu functions. + mathieu_b -- Characteristic value of odd Mathieu functions. + +The following functions do not accept NumPy arrays (they are not +universal functions): + +.. autosummary:: + :toctree: generated/ + + mathieu_even_coef -- Fourier coefficients for even Mathieu and modified Mathieu functions. + mathieu_odd_coef -- Fourier coefficients for even Mathieu and modified Mathieu functions. + +The following return both function and first derivative: + +.. autosummary:: + :toctree: generated/ + + mathieu_cem -- Even Mathieu function and its derivative. + mathieu_sem -- Odd Mathieu function and its derivative. + mathieu_modcem1 -- Even modified Mathieu function of the first kind and its derivative. + mathieu_modcem2 -- Even modified Mathieu function of the second kind and its derivative. + mathieu_modsem1 -- Odd modified Mathieu function of the first kind and its derivative. + mathieu_modsem2 -- Odd modified Mathieu function of the second kind and its derivative. + +Spheroidal wave functions +------------------------- + +.. autosummary:: + :toctree: generated/ + + pro_ang1 -- Prolate spheroidal angular function of the first kind and its derivative. + pro_rad1 -- Prolate spheroidal radial function of the first kind and its derivative. + pro_rad2 -- Prolate spheroidal radial function of the second kind and its derivative. + obl_ang1 -- Oblate spheroidal angular function of the first kind and its derivative. + obl_rad1 -- Oblate spheroidal radial function of the first kind and its derivative. + obl_rad2 -- Oblate spheroidal radial function of the second kind and its derivative. + pro_cv -- Characteristic value of prolate spheroidal function. + obl_cv -- Characteristic value of oblate spheroidal function. + pro_cv_seq -- Characteristic values for prolate spheroidal wave functions. + obl_cv_seq -- Characteristic values for oblate spheroidal wave functions. + +The following functions require pre-computed characteristic value: + +.. autosummary:: + :toctree: generated/ + + pro_ang1_cv -- Prolate spheroidal angular function pro_ang1 for precomputed characteristic value. + pro_rad1_cv -- Prolate spheroidal radial function pro_rad1 for precomputed characteristic value. + pro_rad2_cv -- Prolate spheroidal radial function pro_rad2 for precomputed characteristic value. + obl_ang1_cv -- Oblate spheroidal angular function obl_ang1 for precomputed characteristic value. + obl_rad1_cv -- Oblate spheroidal radial function obl_rad1 for precomputed characteristic value. + obl_rad2_cv -- Oblate spheroidal radial function obl_rad2 for precomputed characteristic value. + +Kelvin functions +---------------- + +.. autosummary:: + :toctree: generated/ + + kelvin -- Kelvin functions as complex numbers. + kelvin_zeros -- Compute nt zeros of all Kelvin functions. + ber -- Kelvin function ber. + bei -- Kelvin function bei + berp -- Derivative of the Kelvin function `ber`. + beip -- Derivative of the Kelvin function `bei`. + ker -- Kelvin function ker. + kei -- Kelvin function ker. + kerp -- Derivative of the Kelvin function ker. + keip -- Derivative of the Kelvin function kei. + +The following functions do not accept NumPy arrays (they are not +universal functions): + +.. autosummary:: + :toctree: generated/ + + ber_zeros -- Compute nt zeros of the Kelvin function ber(x). + bei_zeros -- Compute nt zeros of the Kelvin function bei(x). + berp_zeros -- Compute nt zeros of the Kelvin function ber'(x). + beip_zeros -- Compute nt zeros of the Kelvin function bei'(x). + ker_zeros -- Compute nt zeros of the Kelvin function ker(x). + kei_zeros -- Compute nt zeros of the Kelvin function kei(x). + kerp_zeros -- Compute nt zeros of the Kelvin function ker'(x). + keip_zeros -- Compute nt zeros of the Kelvin function kei'(x). + +Combinatorics +------------- + +.. autosummary:: + :toctree: generated/ + + comb -- The number of combinations of N things taken k at a time. + perm -- Permutations of N things taken k at a time, i.e., k-permutations of N. + stirling2 -- Stirling numbers of the second kind. + +Lambert W and related functions +------------------------------- + +.. autosummary:: + :toctree: generated/ + + lambertw -- Lambert W function. + wrightomega -- Wright Omega function. + +Other special functions +----------------------- + +.. autosummary:: + :toctree: generated/ + + agm -- Arithmetic, Geometric Mean. + bernoulli -- Bernoulli numbers B0..Bn (inclusive). + binom -- Binomial coefficient + diric -- Periodic sinc function, also called the Dirichlet kernel. + euler -- Euler numbers E0..En (inclusive). + expn -- Exponential integral E_n. + exp1 -- Exponential integral E_1 of complex argument z. + expi -- Exponential integral Ei. + factorial -- The factorial of a number or array of numbers. + factorial2 -- Double factorial. + factorialk -- Multifactorial of n of order k, n(!!...!). + shichi -- Hyperbolic sine and cosine integrals. + sici -- Sine and cosine integrals. + softmax -- Softmax function. + log_softmax -- Logarithm of softmax function. + spence -- Spence's function, also known as the dilogarithm. + zeta -- Riemann zeta function. + zetac -- Riemann zeta function minus 1. + softplus -- Softplus function. + +Convenience functions +--------------------- + +.. autosummary:: + :toctree: generated/ + + cbrt -- Cube root of `x`. + exp10 -- 10**x. + exp2 -- 2**x. + radian -- Convert from degrees to radians. + cosdg -- Cosine of the angle `x` given in degrees. + sindg -- Sine of angle given in degrees. + tandg -- Tangent of angle x given in degrees. + cotdg -- Cotangent of the angle `x` given in degrees. + log1p -- Calculates log(1+x) for use when `x` is near zero. + expm1 -- ``exp(x) - 1`` for use when `x` is near zero. + cosm1 -- ``cos(x) - 1`` for use when `x` is near zero. + powm1 -- ``x**y - 1`` for use when `y` is near zero or `x` is near 1. + round -- Round to nearest integer. + xlogy -- Compute ``x*log(y)`` so that the result is 0 if ``x = 0``. + xlog1py -- Compute ``x*log1p(y)`` so that the result is 0 if ``x = 0``. + logsumexp -- Compute the log of the sum of exponentials of input elements. + exprel -- Relative error exponential, (exp(x)-1)/x, for use when `x` is near zero. + sinc -- Return the sinc function. + +""" # noqa: E501 + + +from ._sf_error import SpecialFunctionWarning, SpecialFunctionError + +from . import _ufuncs +from ._ufuncs import * + +from . import _basic +from ._basic import * + +# Replace some function definitions from _ufuncs and _basic +# to add Array API support +from ._support_alternative_backends import * + +from ._logsumexp import logsumexp, softmax, log_softmax + +from . import _multiufuncs +from ._multiufuncs import * + +from . import _orthogonal +from ._orthogonal import * + +from ._ellip_harm import ( + ellip_harm, + ellip_harm_2, + ellip_normal +) +from ._lambertw import lambertw +from ._spherical_bessel import ( + spherical_jn, + spherical_yn, + spherical_in, + spherical_kn +) + +# Deprecated namespaces, to be removed in v2.0.0 +from . import add_newdocs, basic, orthogonal, specfun, sf_error, spfun_stats + +# We replace some function definitions from _ufuncs with those from +# _support_alternative_backends above, but those are all listed in _ufuncs.__all__, +# so there is no need to consider _support_alternative_backends.__all__ here. +__all__ = _ufuncs.__all__ + _basic.__all__ + _orthogonal.__all__ + _multiufuncs.__all__ +__all__ += [ + 'SpecialFunctionWarning', + 'SpecialFunctionError', + 'logsumexp', + 'softmax', + 'log_softmax', + 'multigammaln', # pyrefly:ignore[bad-dunder-all] + 'ellip_harm', + 'ellip_harm_2', + 'ellip_normal', + 'lambertw', + 'spherical_jn', + 'spherical_yn', + 'spherical_in', + 'spherical_kn', +] + +from scipy._lib._testutils import PytestTester +test = PytestTester(__name__) +del PytestTester diff --git a/venv/lib/python3.12/site-packages/scipy/special/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..091146dbb8587dc547e07c2391e682a75bac5a33 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/__pycache__/__init__.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/__pycache__/_add_newdocs.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/__pycache__/_add_newdocs.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ede93074ee1663e5951b9370e8f4e5fee4cbedc9 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/__pycache__/_add_newdocs.cpython-312.pyc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:509a004a2b56498d64a81dff93d31be7892e5e052233b3300ebc12f0d5a35bcd +size 243149 diff --git a/venv/lib/python3.12/site-packages/scipy/special/__pycache__/_basic.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/__pycache__/_basic.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fa45676b46825116268ea3224a78b6e522fb3e5f --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/__pycache__/_basic.cpython-312.pyc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:699d81df2dbe3b90b69e6f33e1d7af6b26d9e5c26f22236de2d2da4e7f8f3605 +size 118363 diff --git a/venv/lib/python3.12/site-packages/scipy/special/__pycache__/_ellip_harm.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/__pycache__/_ellip_harm.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..04a470d52d12a4f679c83aeb7e569555d7030971 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/__pycache__/_ellip_harm.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/__pycache__/_input_validation.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/__pycache__/_input_validation.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cdbd7fe921e47b8334824a7f024d7ca923e5b84c Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/__pycache__/_input_validation.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/__pycache__/_lambertw.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/__pycache__/_lambertw.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..17e327cf82b9f3008d84b0099eb57fb7e365a8f9 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/__pycache__/_lambertw.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/__pycache__/_logsumexp.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/__pycache__/_logsumexp.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bae6ceb191d90a09661b63e21ad13a21e1c81329 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/__pycache__/_logsumexp.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/__pycache__/_mptestutils.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/__pycache__/_mptestutils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..30bf15c719f43c78873452add02bfcf21e36bce8 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/__pycache__/_mptestutils.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/__pycache__/_multiufuncs.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/__pycache__/_multiufuncs.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d5e591d849e6fe58b5a65b0760342362e35a7e49 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/__pycache__/_multiufuncs.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/__pycache__/_orthogonal.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/__pycache__/_orthogonal.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cfe7eae648df496899b8df55fe7d910942267f2c Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/__pycache__/_orthogonal.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/__pycache__/_sf_error.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/__pycache__/_sf_error.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c6ebc6ca2c2536636dc64d97750af14facadd7e1 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/__pycache__/_sf_error.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/__pycache__/_spfun_stats.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/__pycache__/_spfun_stats.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..122bd953544999d6e5018247f90791cd973ee7d8 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/__pycache__/_spfun_stats.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/__pycache__/_spherical_bessel.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/__pycache__/_spherical_bessel.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cead7df4c39109294bcce0a06524cc62df52f522 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/__pycache__/_spherical_bessel.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/__pycache__/_support_alternative_backends.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/__pycache__/_support_alternative_backends.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b21353904a5359651528a69bfeb3aadd22e2a9a2 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/__pycache__/_support_alternative_backends.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/__pycache__/_testutils.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/__pycache__/_testutils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e732e4700bbd475e7031abf74b2e94ad458305aa Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/__pycache__/_testutils.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/__pycache__/_ufunc_tools.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/__pycache__/_ufunc_tools.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4d452cf6c5c75c0c54b8f9c28fb595e064d6a099 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/__pycache__/_ufunc_tools.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/__pycache__/add_newdocs.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/__pycache__/add_newdocs.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..12671f833e5e0534e6f99fe962ec5c4cadeffc92 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/__pycache__/add_newdocs.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/__pycache__/basic.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/__pycache__/basic.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3e21a4ca95941712e07938e6b2e97e257e73c7b0 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/__pycache__/basic.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/__pycache__/orthogonal.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/__pycache__/orthogonal.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d18117e210e5964568a9bcc8d9bcbc16e0ac6cb7 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/__pycache__/orthogonal.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/__pycache__/sf_error.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/__pycache__/sf_error.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..638a26ce1a97420f33be1803098a0aa054b7a101 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/__pycache__/sf_error.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/__pycache__/specfun.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/__pycache__/specfun.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f07e109a7ae6c7106b47bd95ae4fbbd02b59bd3a Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/__pycache__/specfun.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/__pycache__/spfun_stats.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/__pycache__/spfun_stats.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..11e57df934e969c0e0ad7ec24825c5da895ea739 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/__pycache__/spfun_stats.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/_add_newdocs.py b/venv/lib/python3.12/site-packages/scipy/special/_add_newdocs.py new file mode 100644 index 0000000000000000000000000000000000000000..63d2581d620d4c20640f94573f36a25885f6a1a5 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/_add_newdocs.py @@ -0,0 +1,8913 @@ +# Docstrings for generated ufuncs +# +# The syntax is designed to look like the function add_newdoc is being +# called from numpy.lib, but in this file add_newdoc puts the +# docstrings in a dictionary. This dictionary is used in +# _generate_pyx.py to generate the docstrings for the ufuncs in +# scipy.special at the C level when the ufuncs are created at compile +# time. + +docdict: dict[str, str] = {} + + +def get(name): + return docdict.get(name) + + +def add_newdoc(name, doc): + docdict[name] = doc + + +add_newdoc("_sf_error_test_function", + """ + Private function; do not use. + """) + + +add_newdoc("_cosine_cdf", + """ + _cosine_cdf(x) + + Cumulative distribution function (CDF) of the cosine distribution:: + + { 0, x < -pi + cdf(x) = { (pi + x + sin(x))/(2*pi), -pi <= x <= pi + { 1, x > pi + + Parameters + ---------- + x : array_like + `x` must contain real numbers. + + Returns + ------- + scalar or ndarray + The cosine distribution CDF evaluated at `x`. + + """) + +add_newdoc("_cosine_invcdf", + """ + _cosine_invcdf(p) + + Inverse of the cumulative distribution function (CDF) of the cosine + distribution. + + The CDF of the cosine distribution is:: + + cdf(x) = (pi + x + sin(x))/(2*pi) + + This function computes the inverse of cdf(x). + + Parameters + ---------- + p : array_like + `p` must contain real numbers in the interval ``0 <= p <= 1``. + `nan` is returned for values of `p` outside the interval [0, 1]. + + Returns + ------- + scalar or ndarray + The inverse of the cosine distribution CDF evaluated at `p`. + + """) + +add_newdoc("_ellip_harm", + """ + Internal function, use `ellip_harm` instead. + """) + +add_newdoc("_ellip_norm", + """ + Internal function, use `ellip_norm` instead. + """) + +add_newdoc("wrightomega", + r""" + wrightomega(z, out=None) + + Wright Omega function. + + Defined as the solution to + + .. math:: + + \omega + \log(\omega) = z + + where :math:`\log` is the principal branch of the complex logarithm. + + Parameters + ---------- + z : array_like + Points at which to evaluate the Wright Omega function + out : ndarray, optional + Optional output array for the function values + + Returns + ------- + omega : scalar or ndarray + Values of the Wright Omega function + + See Also + -------- + lambertw : The Lambert W function + + Notes + ----- + .. versionadded:: 0.19.0 + + The function can also be defined as + + .. math:: + + \omega(z) = W_{K(z)}(e^z) + + where :math:`K(z) = \lceil (\Im(z) - \pi)/(2\pi) \rceil` is the + unwinding number and :math:`W` is the Lambert W function. + + The implementation here is taken from [1]_. + + References + ---------- + .. [1] Lawrence, Corless, and Jeffrey, "Algorithm 917: Complex + Double-Precision Evaluation of the Wright :math:`\omega` + Function." ACM Transactions on Mathematical Software, + 2012. :doi:`10.1145/2168773.2168779`. + + Examples + -------- + >>> import numpy as np + >>> from scipy.special import wrightomega, lambertw + + >>> wrightomega([-2, -1, 0, 1, 2]) + array([0.12002824, 0.27846454, 0.56714329, 1. , 1.5571456 ]) + + Complex input: + + >>> wrightomega(3 + 5j) + (1.5804428632097158+3.8213626783287937j) + + Verify that ``wrightomega(z)`` satisfies ``w + log(w) = z``: + + >>> w = -5 + 4j + >>> wrightomega(w + np.log(w)) + (-5+4j) + + Verify the connection to ``lambertw``: + + >>> z = 0.5 + 3j + >>> wrightomega(z) + (0.0966015889280649+1.4937828458191993j) + >>> lambertw(np.exp(z)) + (0.09660158892806493+1.4937828458191993j) + + >>> z = 0.5 + 4j + >>> wrightomega(z) + (-0.3362123489037213+2.282986001579032j) + >>> lambertw(np.exp(z), k=1) + (-0.33621234890372115+2.282986001579032j) + """) + + +add_newdoc("agm", + """ + agm(a, b, out=None) + + Compute the arithmetic-geometric mean of `a` and `b`. + + Start with a_0 = a and b_0 = b and iteratively compute:: + + a_{n+1} = (a_n + b_n)/2 + b_{n+1} = sqrt(a_n*b_n) + + a_n and b_n converge to the same limit as n increases; their common + limit is agm(a, b). + + Parameters + ---------- + a, b : array_like + Real values only. If the values are both negative, the result + is negative. If one value is negative and the other is positive, + `nan` is returned. + out : ndarray, optional + Optional output array for the function values + + Returns + ------- + scalar or ndarray + The arithmetic-geometric mean of `a` and `b`. + + Examples + -------- + >>> import numpy as np + >>> from scipy.special import agm + >>> a, b = 24.0, 6.0 + >>> agm(a, b) + 13.458171481725614 + + Compare that result to the iteration: + + >>> while a != b: + ... a, b = (a + b)/2, np.sqrt(a*b) + ... print("a = %19.16f b=%19.16f" % (a, b)) + ... + a = 15.0000000000000000 b=12.0000000000000000 + a = 13.5000000000000000 b=13.4164078649987388 + a = 13.4582039324993694 b=13.4581390309909850 + a = 13.4581714817451772 b=13.4581714817060547 + a = 13.4581714817256159 b=13.4581714817256159 + + When array-like arguments are given, broadcasting applies: + + >>> a = np.array([[1.5], [3], [6]]) # a has shape (3, 1). + >>> b = np.array([6, 12, 24, 48]) # b has shape (4,). + >>> agm(a, b) + array([[ 3.36454287, 5.42363427, 9.05798751, 15.53650756], + [ 4.37037309, 6.72908574, 10.84726853, 18.11597502], + [ 6. , 8.74074619, 13.45817148, 21.69453707]]) + """) + +add_newdoc("bdtr", + r""" + bdtr(k, n, p, out=None) + + Binomial distribution cumulative distribution function. + + Sum of the terms 0 through `floor(k)` of the Binomial probability density. + + .. math:: + \mathrm{bdtr}(k, n, p) = + \sum_{j=0}^{\lfloor k \rfloor} {{n}\choose{j}} p^j (1-p)^{n-j} + + Parameters + ---------- + k : array_like + Number of successes (double), rounded down to the nearest integer. + n : array_like + Number of events (int). + p : array_like + Probability of success in a single event (float). + out : ndarray, optional + Optional output array for the function values + + Returns + ------- + y : scalar or ndarray + Probability of `floor(k)` or fewer successes in `n` independent events with + success probabilities of `p`. + + Notes + ----- + The terms are not summed directly; instead the regularized incomplete beta + function is employed, according to the formula, + + .. math:: + \mathrm{bdtr}(k, n, p) = + I_{1 - p}(n - \lfloor k \rfloor, \lfloor k \rfloor + 1). + + Wrapper for the Cephes [1]_ routine `bdtr`. + + References + ---------- + .. [1] Cephes Mathematical Functions Library, + https://netlib.org/cephes/ + + """) + +add_newdoc("bdtrc", + r""" + bdtrc(k, n, p, out=None) + + Binomial distribution survival function. + + Sum of the terms `floor(k) + 1` through `n` of the binomial probability + density, + + .. math:: + \mathrm{bdtrc}(k, n, p) = + \sum_{j=\lfloor k \rfloor +1}^n {{n}\choose{j}} p^j (1-p)^{n-j} + + Parameters + ---------- + k : array_like + Number of successes (double), rounded down to nearest integer. + n : array_like + Number of events (int) + p : array_like + Probability of success in a single event. + out : ndarray, optional + Optional output array for the function values + + Returns + ------- + y : scalar or ndarray + Probability of `floor(k) + 1` or more successes in `n` independent + events with success probabilities of `p`. + + See Also + -------- + bdtr + betainc + + Notes + ----- + The terms are not summed directly; instead the regularized incomplete beta + function is employed, according to the formula, + + .. math:: + \mathrm{bdtrc}(k, n, p) = I_{p}(\lfloor k \rfloor + 1, n - \lfloor k \rfloor). + + Wrapper for the Cephes [1]_ routine `bdtrc`. + + References + ---------- + .. [1] Cephes Mathematical Functions Library, + https://netlib.org/cephes/ + + """) + +add_newdoc("bdtri", + r""" + bdtri(k, n, y, out=None) + + Inverse function to `bdtr` with respect to `p`. + + Finds the event probability `p` such that the sum of the terms 0 through + `k` of the binomial probability density is equal to the given cumulative + probability `y`. + + Parameters + ---------- + k : array_like + Number of successes (float), rounded down to the nearest integer. + n : array_like + Number of events (float) + y : array_like + Cumulative probability (probability of `k` or fewer successes in `n` + events). + out : ndarray, optional + Optional output array for the function values + + Returns + ------- + p : scalar or ndarray + The event probability such that `bdtr(\lfloor k \rfloor, n, p) = y`. + + See Also + -------- + bdtr + betaincinv + + Notes + ----- + The computation is carried out using the inverse beta integral function + and the relation,:: + + 1 - p = betaincinv(n - k, k + 1, y). + + Wrapper for the Cephes [1]_ routine `bdtri`. + + References + ---------- + .. [1] Cephes Mathematical Functions Library, + https://netlib.org/cephes/ + """) + +add_newdoc("bdtrik", + """ + bdtrik(y, n, p, out=None) + + Inverse function to `bdtr` with respect to `k`. + + Finds the number of successes `k` such that the sum of the terms 0 through + `k` of the Binomial probability density for `n` events with probability + `p` is equal to the given cumulative probability `y`. + + Parameters + ---------- + y : array_like + Cumulative probability (probability of `k` or fewer successes in `n` + events). + n : array_like + Number of events (float). + p : array_like + Success probability (float). + out : ndarray, optional + Optional output array for the function values + + Returns + ------- + k : scalar or ndarray + The number of successes `k` such that `bdtr(k, n, p) = y`. + + See Also + -------- + bdtr + + Notes + ----- + Formula 26.5.24 of [1]_ (or equivalently [2]_) is used to reduce the binomial + distribution to the cumulative incomplete beta distribution. + + Computation of `k` involves a search for a value that produces the desired + value of `y`. The search relies on the monotonicity of `y` with `k`. + + Wrapper for the CDFLIB [3]_ Fortran routine `cdfbin`. + + References + ---------- + .. [1] Milton Abramowitz and Irene A. Stegun, eds. + Handbook of Mathematical Functions with Formulas, + Graphs, and Mathematical Tables. New York: Dover, 1972. + .. [2] NIST Digital Library of Mathematical Functions + https://dlmf.nist.gov/8.17.5#E5 + .. [3] Barry Brown, James Lovato, and Kathy Russell, + CDFLIB: Library of Fortran Routines for Cumulative Distribution + Functions, Inverses, and Other Parameters. + + """) + +add_newdoc("bdtrin", + r""" + bdtrin(k, y, p, out=None) + + Inverse function to `bdtr` with respect to `n`. + + Finds the number of events `n` such that the sum of the terms 0 through + `k` of the Binomial probability density for events with probability `p` is + equal to the given cumulative probability `y`. + + Parameters + ---------- + k : array_like + Number of successes (float). + y : array_like + Cumulative probability (probability of `k` or fewer successes in `n` + events). + p : array_like + Success probability (float). + out : ndarray, optional + Optional output array for the function values + + Returns + ------- + n : scalar or ndarray + The number of events `n` such that `bdtr(k, n, p) = y`. + + See Also + -------- + bdtr + + Notes + ----- + This function uses the `find_minimum_number_of_trials` method of the + `binomial_distribution` class of the Boost.Math C++ library [1]_. + + References + ---------- + .. [1] The Boost Developers. "Boost C++ Libraries". https://www.boost.org/. + + Examples + -------- + How often do we have to flip a fair coin to have at least a 90% chance + of getting 10 heads? `bdtrin` answers this question: + + >>> import scipy.special as sc + >>> k = 10 # number of times we want heads + >>> p = 0.5 # probability of flipping heads + >>> y = 0.9 # cumulative probability + >>> result = sc.bdtrin(k, y, p) + >>> result + 15.90442928275109 + + To verify, compute the cumulative probability of getting 10 or fewer + successes in 16 trials with probability 0.5 using the binomial + distribution from `scipy.stats`. Since `bdtrin` returns a non-integer + number of trials, we round up to the next integer: + + >>> from scipy.stats import Binomial + >>> Binomial(n=16, p=p).cdf(k) + 0.8949432373046875 + + """) + +add_newdoc("btdtria", + r""" + btdtria(p, b, x, out=None) + + Inverse of `betainc` with respect to `a`. + + This is the inverse of the beta cumulative distribution function, `betainc`, + considered as a function of `a`, returning the value of `a` for which + `betainc(a, b, x) = p`, or + + .. math:: + p = \int_0^x \frac{\Gamma(a + b)}{\Gamma(a)\Gamma(b)} t^{a-1} (1-t)^{b-1}\,dt + + Parameters + ---------- + p : array_like + Cumulative probability, in [0, 1]. + b : array_like + Shape parameter (`b` > 0). + x : array_like + The quantile, in [0, 1]. + out : ndarray, optional + Optional output array for the function values + + Returns + ------- + a : scalar or ndarray + The value of the shape parameter `a` such that `betainc(a, b, x) = p`. + + See Also + -------- + betainc : Regularized incomplete beta function + betaincinv : Inverse of the regularized incomplete beta function + btdtrib : Inverse of the beta cumulative distribution function, with respect to `b`. + + Notes + ----- + This function wraps the ``ibeta_inva`` routine from the + Boost Math C++ library [1]_. + + References + ---------- + .. [1] The Boost Developers. "Boost C++ Libraries". https://www.boost.org/. + + Examples + -------- + >>> import scipy.special as sc + + This function is the inverse of `betainc` for fixed + values of :math:`b` and :math:`x`. + + >>> a, b, x = 1.2, 3.1, 0.2 + >>> y = sc.betainc(a, b, x) + >>> sc.btdtria(y, b, x) + 1.2 + + """) + +add_newdoc("btdtrib", + r""" + btdtria(a, p, x, out=None) + + Inverse of `betainc` with respect to `b`. + + This is the inverse of the beta cumulative distribution function, `betainc`, + considered as a function of `b`, returning the value of `b` for which + `betainc(a, b, x) = p`, or + + .. math:: + p = \int_0^x \frac{\Gamma(a + b)}{\Gamma(a)\Gamma(b)} t^{a-1} (1-t)^{b-1}\,dt + + Parameters + ---------- + a : array_like + Shape parameter (`a` > 0). + p : array_like + Cumulative probability, in [0, 1]. + x : array_like + The quantile, in [0, 1]. + out : ndarray, optional + Optional output array for the function values + + Returns + ------- + b : scalar or ndarray + The value of the shape parameter `b` such that `betainc(a, b, x) = p`. + + See Also + -------- + betainc : Regularized incomplete beta function + betaincinv : Inverse of the regularized incomplete beta function with + respect to `x`. + btdtria : Inverse of the beta cumulative distribution function, with respect to `a`. + + Notes + ----- + Wrapper for the `ibeta_invb` routine from the Boost Math C++ library [1]_. + + References + ---------- + .. [1] The Boost Developers. "Boost C++ Libraries". https://www.boost.org/. + + Examples + -------- + >>> import scipy.special as sc + >>> a, b, x = 1.2, 3.1, 0.2 + >>> y = sc.betainc(a, b, x) + + `btdtrib` is the inverse of `betainc` for fixed values of :math:`a` and + :math:`x`: + + >>> sc.btdtrib(a, y, x) + 3.1 + + """) + +add_newdoc( + "betainc", + r""" + betainc(a, b, x, out=None) + + Regularized incomplete beta function. + + Computes the regularized incomplete beta function, defined as [1]_: + + .. math:: + + I_x(a, b) = \frac{\Gamma(a+b)}{\Gamma(a)\Gamma(b)} \int_0^x + t^{a-1}(1-t)^{b-1}dt, + + for :math:`0 \leq x \leq 1`. + + This function is the cumulative distribution function for the beta + distribution; its range is [0, 1]. + + Parameters + ---------- + a, b : array_like + Positive, real-valued parameters. + x : array_like + Real-valued such that :math:`0 \leq x \leq 1`, + the upper limit of integration. + out : ndarray, optional + Optional output array for the function values. + + Returns + ------- + scalar or ndarray + Value of the regularized incomplete beta function. + + See Also + -------- + beta : beta function + betaincinv : inverse of the regularized incomplete beta function + betaincc : complement of the regularized incomplete beta function + scipy.stats.beta : beta distribution + + Notes + ----- + The term *regularized* in the name of this function refers to the + scaling of the function by the gamma function terms shown in the + formula. When not qualified as *regularized*, the name *incomplete + beta function* often refers to just the integral expression, + without the gamma terms. One can use the function `beta` from + `scipy.special` to get this "nonregularized" incomplete beta + function by multiplying the result of ``betainc(a, b, x)`` by + ``beta(a, b)``. + + ``betainc(a, b, x)`` is treated as a two parameter family of functions + of a single variable `x`, rather than as a function of three variables. + This impacts only the limiting cases ``a = 0``, ``b = 0``, ``a = inf``, + ``b = inf``. + + In general + + .. math:: + + \lim_{(a, b) \rightarrow (a_0, b_0)} \mathrm{betainc}(a, b, x) + + is treated as a pointwise limit in ``x``. Thus for example, + ``betainc(0, b, 0)`` equals ``0`` for ``b > 0``, although it would be + indeterminate when considering the simultaneous limit ``(a, x) -> (0+, 0+)``. + + This function wraps the ``ibeta`` routine from the + Boost Math C++ library [2]_. + + References + ---------- + .. [1] NIST Digital Library of Mathematical Functions + https://dlmf.nist.gov/8.17 + .. [2] The Boost Developers. "Boost C++ Libraries". https://www.boost.org/. + + Examples + -------- + + Let :math:`B(a, b)` be the `beta` function. + + >>> import scipy.special as sc + + The coefficient in terms of `gamma` is equal to + :math:`1/B(a, b)`. Also, when :math:`x=1` + the integral is equal to :math:`B(a, b)`. + Therefore, :math:`I_{x=1}(a, b) = 1` for any :math:`a, b`. + + >>> sc.betainc(0.2, 3.5, 1.0) + 1.0 + + It satisfies + :math:`I_x(a, b) = x^a F(a, 1-b, a+1, x)/ (aB(a, b))`, + where :math:`F` is the hypergeometric function `hyp2f1`: + + >>> a, b, x = 1.4, 3.1, 0.5 + >>> x**a * sc.hyp2f1(a, 1 - b, a + 1, x)/(a * sc.beta(a, b)) + 0.8148904036225295 + >>> sc.betainc(a, b, x) + 0.8148904036225296 + + This functions satisfies the relationship + :math:`I_x(a, b) = 1 - I_{1-x}(b, a)`: + + >>> sc.betainc(2.2, 3.1, 0.4) + 0.49339638807619446 + >>> 1 - sc.betainc(3.1, 2.2, 1 - 0.4) + 0.49339638807619446 + + """) + + +add_newdoc( + "betaincc", + r""" + betaincc(a, b, x, out=None) + + Complement of the regularized incomplete beta function. + + Computes the complement of the regularized incomplete beta function, + defined as [1]_: + + .. math:: + + \bar{I}_x(a, b) = 1 - I_x(a, b) + = 1 - \frac{\Gamma(a+b)}{\Gamma(a)\Gamma(b)} \int_0^x + t^{a-1}(1-t)^{b-1}dt, + + for :math:`0 \leq x \leq 1`. + + Parameters + ---------- + a, b : array_like + Positive, real-valued parameters. + x : array_like + Real-valued such that :math:`0 \leq x \leq 1`, + the upper limit of integration. + out : ndarray, optional + Optional output array for the function values. + + Returns + ------- + scalar or ndarray + Value of the complement of the regularized incomplete beta function. + + See Also + -------- + betainc : regularized incomplete beta function + betaincinv : inverse of the regularized incomplete beta function + betainccinv : + inverse of the complement of the regularized incomplete beta function + beta : beta function + scipy.stats.beta : beta distribution + + Notes + ----- + .. versionadded:: 1.11.0 + + Like `betainc`, ``betaincc(a, b, x)`` is treated as a two parameter + family of functions of a single variable `x`, rather than as a function of + three variables. See the `betainc` docstring for more info on how this + impacts limiting cases. + + This function wraps the ``ibetac`` routine from the + Boost Math C++ library [2]_. + + References + ---------- + .. [1] NIST Digital Library of Mathematical Functions + https://dlmf.nist.gov/8.17 + .. [2] The Boost Developers. "Boost C++ Libraries". https://www.boost.org/. + + Examples + -------- + >>> from scipy.special import betaincc, betainc + + The naive calculation ``1 - betainc(a, b, x)`` loses precision when + the values of ``betainc(a, b, x)`` are close to 1: + + >>> 1 - betainc(0.5, 8, [0.9, 0.99, 0.999]) + array([2.0574632e-09, 0.0000000e+00, 0.0000000e+00]) + + By using ``betaincc``, we get the correct values: + + >>> betaincc(0.5, 8, [0.9, 0.99, 0.999]) + array([2.05746321e-09, 1.97259354e-17, 1.96467954e-25]) + + """) + +add_newdoc( + "betaincinv", + r""" + betaincinv(a, b, y, out=None) + + Inverse of the regularized incomplete beta function. + + Computes :math:`x` such that: + + .. math:: + + y = I_x(a, b) = \frac{\Gamma(a+b)}{\Gamma(a)\Gamma(b)} + \int_0^x t^{a-1}(1-t)^{b-1}dt, + + where :math:`I_x` is the normalized incomplete beta function `betainc` + and :math:`\Gamma` is the `gamma` function [1]_. + + Parameters + ---------- + a, b : array_like + Positive, real-valued parameters + y : array_like + Real-valued input + out : ndarray, optional + Optional output array for function values + + Returns + ------- + scalar or ndarray + Value of the inverse of the regularized incomplete beta function + + See Also + -------- + betainc : regularized incomplete beta function + gamma : gamma function + + Notes + ----- + This function wraps the ``ibeta_inv`` routine from the + Boost Math C++ library [2]_. + + References + ---------- + .. [1] NIST Digital Library of Mathematical Functions + https://dlmf.nist.gov/8.17 + .. [2] The Boost Developers. "Boost C++ Libraries". https://www.boost.org/. + + Examples + -------- + >>> import scipy.special as sc + + This function is the inverse of `betainc` for fixed + values of :math:`a` and :math:`b`. + + >>> a, b = 1.2, 3.1 + >>> y = sc.betainc(a, b, 0.2) + >>> sc.betaincinv(a, b, y) + 0.2 + >>> + >>> a, b = 7.5, 0.4 + >>> x = sc.betaincinv(a, b, 0.5) + >>> sc.betainc(a, b, x) + 0.5 + + """) + + +add_newdoc( + "betainccinv", + r""" + betainccinv(a, b, y, out=None) + + Inverse of the complemented regularized incomplete beta function. + + Computes :math:`x` such that: + + .. math:: + + y = 1 - I_x(a, b) = 1 - \frac{\Gamma(a+b)}{\Gamma(a)\Gamma(b)} + \int_0^x t^{a-1}(1-t)^{b-1}dt, + + where :math:`I_x` is the normalized incomplete beta function `betainc` + and :math:`\Gamma` is the `gamma` function [1]_. + + Parameters + ---------- + a, b : array_like + Positive, real-valued parameters + y : array_like + Real-valued input + out : ndarray, optional + Optional output array for function values + + Returns + ------- + scalar or ndarray + Value of the inverse of the regularized incomplete beta function + + See Also + -------- + betainc : regularized incomplete beta function + betaincc : complement of the regularized incomplete beta function + + Notes + ----- + .. versionadded:: 1.11.0 + + This function wraps the ``ibetac_inv`` routine from the + Boost Math C++ library [2]_. + + References + ---------- + .. [1] NIST Digital Library of Mathematical Functions + https://dlmf.nist.gov/8.17 + .. [2] The Boost Developers. "Boost C++ Libraries". https://www.boost.org/. + + Examples + -------- + >>> from scipy.special import betainccinv, betaincc + + This function is the inverse of `betaincc` for fixed + values of :math:`a` and :math:`b`. + + >>> a, b = 1.2, 3.1 + >>> y = betaincc(a, b, 0.2) + >>> betainccinv(a, b, y) + 0.2 + + >>> a, b = 7, 2.5 + >>> x = betainccinv(a, b, 0.875) + >>> betaincc(a, b, x) + 0.875 + + """) + +add_newdoc("boxcox", + r""" + boxcox(x, lmbda, out=None) + + Compute the Box-Cox transformation. + + The Box-Cox transformation is + + .. math:: + + y = \begin{cases} + (x^\lambda - 1) / \lambda & \text{if } \lambda \neq 0 \\ + \log(x) & \text{if } \lambda = 0 + \end{cases} + + Returns ``nan`` if :math:`x < 0`. + Returns ``-inf`` if :math:`x = 0` and :math:`\lambda \leq 0`. + + Parameters + ---------- + x : array_like + Data to be transformed. + lmbda : array_like + Power parameter :math:`\lambda` of the Box-Cox transform. + out : ndarray, optional + Optional output array for the function values. + + Returns + ------- + y : scalar or ndarray + Transformed data. + + See Also + -------- + boxcox1p : Box-Cox transformation of ``1 + x``. + inv_boxcox : Inverse of the Box-Cox transformation. + + Notes + ----- + + .. versionadded:: 0.14.0 + + Examples + -------- + >>> from scipy.special import boxcox + >>> boxcox([1, 4, 10], 2.5) + array([ 0. , 12.4 , 126.09110641]) + >>> boxcox(2, [0, 1, 2]) + array([ 0.69314718, 1. , 1.5 ]) + """) + +add_newdoc("boxcox1p", + r""" + boxcox1p(x, lmbda, out=None) + + Compute the Box-Cox transformation of :math:`1 + x`. + + The Box-Cox transformation computed by `boxcox1p` is + + .. math:: + + y = \begin{cases} + ((1+x)^\lambda - 1) / \lambda & \text{if } \lambda \neq 0 \\ + \log(1+x) & \text{if } \lambda = 0 + \end{cases} + + Returns ``nan`` if :math:`x < -1`. + Returns ``-inf`` if :math:`x = -1` and :math:`\lambda \leq 0`. + + Parameters + ---------- + x : array_like + Data to be transformed. + lmbda : array_like + Power parameter :math:`\lambda` of the Box-Cox transform. + out : ndarray, optional + Optional output array for the function values. + + Returns + ------- + y : scalar or ndarray + Transformed data. + + See Also + -------- + boxcox : Box-Cox transformation. + inv_boxcox1p : Inverse of the Box-Cox transformation of ``1 + x``. + + Notes + ----- + + .. versionadded:: 0.14.0 + + Examples + -------- + >>> from scipy.special import boxcox1p + >>> boxcox1p(1e-4, [0, 0.5, 1]) + array([ 9.99950003e-05, 9.99975001e-05, 1.00000000e-04]) + >>> boxcox1p([0.01, 0.1], 0.25) + array([ 0.00996272, 0.09645476]) + """) + +add_newdoc("inv_boxcox", + r""" + inv_boxcox(y, lmbda, out=None) + + Compute the inverse of the Box-Cox transformation. + + Find :math:`x` such that + + .. math:: + + y = \begin{cases} + (x^\lambda - 1) / \lambda & \text{if } \lambda \neq 0 \\ + \log(x) & \text{if } \lambda = 0 + \end{cases} + + Parameters + ---------- + y : array_like + Transformed data (input to the inverse transform). + lmbda : array_like + Power parameter :math:`\lambda` of the Box-Cox transform. + out : ndarray, optional + Optional output array for the function values. + + Returns + ------- + x : scalar or ndarray + Original data (inverse Box-Cox transform of `y`). + + See Also + -------- + boxcox : Box-Cox transformation. + inv_boxcox1p : Inverse of the Box-Cox transformation of ``1 + x``. + + Notes + ----- + + .. versionadded:: 0.16.0 + + Examples + -------- + >>> from scipy.special import boxcox, inv_boxcox + >>> y = boxcox([1, 4, 10], 2.5) + >>> inv_boxcox(y, 2.5) + array([1., 4., 10.]) + """) + +add_newdoc("inv_boxcox1p", + r""" + inv_boxcox1p(y, lmbda, out=None) + + Compute the inverse of the Box-Cox transformation of :math:`1 + x`. + + Find :math:`x` such that + + .. math:: + + y = \begin{cases} + ((1+x)^\lambda - 1) / \lambda & \text{if } \lambda \neq 0 \\ + \log(1+x) & \text{if } \lambda = 0 + \end{cases} + + Parameters + ---------- + y : array_like + Transformed data (input to the inverse transform). + lmbda : array_like + Power parameter :math:`\lambda` of the Box-Cox transform. + out : ndarray, optional + Optional output array for the function values. + + Returns + ------- + x : scalar or ndarray + Original data (inverse Box-Cox transform of `y`). + + See Also + -------- + boxcox1p : Box-Cox transformation of ``1 + x``. + inv_boxcox : Inverse of the Box-Cox transformation. + + Notes + ----- + + .. versionadded:: 0.16.0 + + Examples + -------- + >>> from scipy.special import boxcox1p, inv_boxcox1p + >>> y = boxcox1p([1, 4, 10], 2.5) + >>> inv_boxcox1p(y, 2.5) + array([1., 4., 10.]) + """) + +add_newdoc("chdtr", + r""" + chdtr(v, x, out=None) + + Chi square cumulative distribution function. + + Returns the area under the left tail (from 0 to `x`) of the Chi + square probability density function with `v` degrees of freedom: + + .. math:: + + \frac{1}{2^{v/2} \Gamma(v/2)} \int_0^x t^{v/2 - 1} e^{-t/2} dt + + Here :math:`\Gamma` is the Gamma function; see `gamma`. This + integral can be expressed in terms of the regularized lower + incomplete gamma function `gammainc` as + ``gammainc(v / 2, x / 2)``. [1]_ + + Parameters + ---------- + v : array_like + Degrees of freedom. + x : array_like + Upper bound of the integral. + out : ndarray, optional + Optional output array for the function results. + + Returns + ------- + scalar or ndarray + Values of the cumulative distribution function. + + See Also + -------- + chdtrc, chdtri, chdtriv, gammainc + + References + ---------- + .. [1] Chi-Square distribution, + https://www.itl.nist.gov/div898/handbook/eda/section3/eda3666.htm + + Examples + -------- + >>> import numpy as np + >>> import scipy.special as sc + + It can be expressed in terms of the regularized lower incomplete + gamma function. + + >>> v = 1 + >>> x = np.arange(4) + >>> sc.chdtr(v, x) + array([0. , 0.68268949, 0.84270079, 0.91673548]) + >>> sc.gammainc(v / 2, x / 2) + array([0. , 0.68268949, 0.84270079, 0.91673548]) + + """) + +add_newdoc("chdtrc", + r""" + chdtrc(v, x, out=None) + + Chi square survival function. + + Returns the area under the right hand tail (from `x` to infinity) + of the Chi square probability density function with `v` degrees of + freedom: + + .. math:: + + \frac{1}{2^{v/2} \Gamma(v/2)} \int_x^\infty t^{v/2 - 1} e^{-t/2} dt + + Here :math:`\Gamma` is the Gamma function; see `gamma`. This + integral can be expressed in terms of the regularized upper + incomplete gamma function `gammaincc` as + ``gammaincc(v / 2, x / 2)``. [1]_ + + Parameters + ---------- + v : array_like + Degrees of freedom. + x : array_like + Lower bound of the integral. + out : ndarray, optional + Optional output array for the function results. + + Returns + ------- + scalar or ndarray + Values of the survival function. + + See Also + -------- + chdtr, chdtri, chdtriv, gammaincc + + References + ---------- + .. [1] Chi-Square distribution, + https://www.itl.nist.gov/div898/handbook/eda/section3/eda3666.htm + + Examples + -------- + >>> import numpy as np + >>> import scipy.special as sc + + It can be expressed in terms of the regularized upper incomplete + gamma function. + + >>> v = 1 + >>> x = np.arange(4) + >>> sc.chdtrc(v, x) + array([1. , 0.31731051, 0.15729921, 0.08326452]) + >>> sc.gammaincc(v / 2, x / 2) + array([1. , 0.31731051, 0.15729921, 0.08326452]) + + """) + +add_newdoc("chdtri", + """ + chdtri(v, p, out=None) + + Inverse to `chdtrc` with respect to `x`. + + Returns `x` such that ``chdtrc(v, x) == p``. + + Parameters + ---------- + v : array_like + Degrees of freedom. + p : array_like + Probability. + out : ndarray, optional + Optional output array for the function results. + + Returns + ------- + x : scalar or ndarray + Value so that the probability a Chi square random variable + with `v` degrees of freedom is greater than `x` equals `p`. + + See Also + -------- + chdtrc, chdtr, chdtriv + + References + ---------- + .. [1] Chi-Square distribution, + https://www.itl.nist.gov/div898/handbook/eda/section3/eda3666.htm + + Examples + -------- + >>> import scipy.special as sc + + It inverts `chdtrc`. + + >>> v, p = 1, 0.3 + >>> sc.chdtrc(v, sc.chdtri(v, p)) + 0.3 + >>> x = 1 + >>> sc.chdtri(v, sc.chdtrc(v, x)) + 1.0 + + """) + +add_newdoc("chdtriv", + """ + chdtriv(p, x, out=None) + + Inverse to `chdtr` with respect to `v`. + + Returns `v` such that ``chdtr(v, x) == p``. + + Parameters + ---------- + p : array_like + Probability that the Chi square random variable is less than + or equal to `x`. + x : array_like + Nonnegative input. + out : ndarray, optional + Optional output array for the function results. + + Returns + ------- + scalar or ndarray + Degrees of freedom. + + See Also + -------- + chdtr, chdtrc, chdtri + + Notes + ----- + This function wraps routines from the Boost Math C++ library [1]_. + + References + ---------- + .. [1] The Boost Developers. "Boost C++ Libraries". https://www.boost.org/. + .. [2] Chi-Square distribution, + https://www.itl.nist.gov/div898/handbook/eda/section3/eda3666.htm + + Examples + -------- + >>> import scipy.special as sc + + It inverts `chdtr`. + + >>> p, x = 0.5, 1 + >>> sc.chdtr(sc.chdtriv(p, x), x) + 0.5000000000000003 + >>> v = 1 + >>> sc.chdtriv(sc.chdtr(v, x), x) + 1.0 + + """) + +add_newdoc("chndtr", + r""" + chndtr(x, df, nc, out=None) + + Non-central chi-squared cumulative distribution function. + + The cumulative distribution function is given by + + .. math:: + + F_{\nu,\lambda}(x) + = \sum_{j=0}^{\infty} + e^{-\lambda / 2} + \frac{(\lambda / 2)^j}{j!} + F_{\chi^2_{\nu + 2j}}(x), + + where :math:`\nu > 0` is the degrees of freedom (``df``), :math:`\lambda \geq 0` + is the non-centrality parameter (``nc``), and :math:`F_{\chi^2_{\nu + 2j}}` is the + CDF of the central chi-squared distribution with :math:`\nu + 2j` degrees of + freedom. + + Parameters + ---------- + x : array_like + Upper bound of the integral; must satisfy ``x >= 0``. + df : array_like + Degrees of freedom; must satisfy ``df > 0``. + nc : array_like + Non-centrality parameter; must satisfy ``nc >= 0``. + out : ndarray, optional + Optional output array for the function results. + + Returns + ------- + cdf : scalar or ndarray + Value of the non-central chi-squared cumulative distribution function. + + See Also + -------- + chndtrix: Noncentral Chi Squared distribution quantile + chndtridf: Inverse of `chndtr` with respect to `df` + chndtrinc: Inverse of `chndtr` with respect to `nc` + scipy.stats.ncx2: Non-central chi-squared distribution + + Notes + ----- + The noncentral chi squared distribution is also available in + `scipy.stats.ncx2`. ``scipy.stats.ncx2.cdf`` is equivalent to `chndtr`. + + This function wraps routines from the Boost Math C++ library [1]_. + + References + ---------- + .. [1] The Boost Developers. "Boost C++ Libraries". https://www.boost.org/. + + Examples + -------- + >>> import numpy as np + >>> import scipy.special as sc + + Compute the noncentral chi squared distribution CDF at one point. + + >>> x = 4.0 + >>> df = 1.0 + >>> nc = 5.0 + >>> sc.chndtr(x, df, nc) + 0.40667858759710945 + + Plot the noncentral chi squared distribution CDF for different parameters. + + >>> import matplotlib.pyplot as plt + >>> x = np.linspace(0, 40, 1000) + >>> plt.plot(x, sc.chndtr(x, 1, 5), label=r"$df=1,\ nc=5$") + >>> plt.plot(x, sc.chndtr(x, 5, 10), label=r"$df=5,\ nc=10$") + >>> plt.legend() + >>> plt.show() + + """) + +add_newdoc("chndtrix", + """ + chndtrix(p, df, nc, out=None) + + Inverse to `chndtr` vs `x`. + + Calculated using a search to find a value for `x` that produces the + desired value of `p`. + + Parameters + ---------- + p : array_like + Probability; must satisfy ``0 <= p < 1`` + df : array_like + Degrees of freedom; must satisfy ``df > 0`` + nc : array_like + Non-centrality parameter; must satisfy ``nc >= 0`` + out : ndarray, optional + Optional output array for the function results + + Returns + ------- + x : scalar or ndarray + Value so that the probability a non-central Chi square random variable + with `df` degrees of freedom and non-centrality, `nc`, is greater than + `x` equals `p`. + + See Also + -------- + chndtr : Noncentral chi-squared distribution CDF + chndtridf : inverse of `chndtr` with respect to `cdf` + chndtrinc : inverse of `chndtr` with respect to `nc` + scipy.stats.ncx2 : Non-central chi-squared distribution + + Notes + ----- + The noncentral chi squared distribution is also available in + `scipy.stats.ncx2`. ``scipy.stats.ncx2.ppf`` is equivalent to `chndtrix`. + + This function wraps routines from the Boost Math C++ library [1]_. + + References + ---------- + .. [1] The Boost Developers. "Boost C++ Libraries". https://www.boost.org/. + + Examples + -------- + >>> from scipy.special import chndtrix, chndtr + + Compute the noncentral chi squared distribution CDF at one point. + >>> x, df, nc = 3, 5, 10 + >>> p = chndtr(x, df, nc) + + `chndtrix` is the inverse of `chndtr` with respect to `x`: + + >>> chndtrix(p, df, nc) + 3.0 + + """) + +add_newdoc("chndtridf", + """ + chndtridf(x, p, nc, out=None) + + Inverse to `chndtr` vs `df`. + + Calculated using a search to find a value for `df` that produces the + desired value of `p`. + + Parameters + ---------- + x : array_like + Upper bound of the integral; must satisfy ``x >= 0`` + p : array_like + Probability; must satisfy ``0 <= p < 1`` + nc : array_like + Non-centrality parameter; must satisfy ``nc >= 0`` + out : ndarray, optional + Optional output array for the function results + + Returns + ------- + df : scalar or ndarray + Degrees of freedom + + See Also + -------- + chndtr : Noncentral chi-squared distribution CDF + chndtrix : inverse of `chndtr` with respect to `x` + chndtrinc : inverse of `chndtr` with respect to `nc` + scipy.stats.ncx2 : Non-central chi-squared distribution + + Notes + ----- + The noncentral chi squared distribution is also available in + `scipy.stats.ncx2`. + + This function wraps routines from the Boost Math C++ library [1]_. + + References + ---------- + .. [1] The Boost Developers. "Boost C++ Libraries". https://www.boost.org/. + + Examples + -------- + >>> from scipy.special import chndtridf, chndtr + + Compute the noncentral chi squared distribution CDF at one point. + + >>> x, df, nc = 3, 5, 10 + >>> p = chndtr(x, df, nc) + + `chndtridf` is the inverse of `chndtr` with respect to `df`: + + >>> chndtridf(x, p, nc) + 5.0 + + """) + +add_newdoc("chndtrinc", + """ + chndtrinc(x, df, p, out=None) + + Inverse of `chndtr` with respect to `nc`. + + Finds the non-centrality parameter `nc` such that + + .. math:: + + \\operatorname{chndtr}(x, df, nc) = p. + + Parameters + ---------- + x : array_like + Upper bound of the integral; must satisfy ``x >= 0``. + df : array_like + Degrees of freedom; must satisfy ``df > 0``. + p : array_like + Probability; must satisfy ``0 <= p < 1``. + out : ndarray, optional + Optional output array for the function results. + + Returns + ------- + nc : scalar or ndarray + Non-centrality parameter. + + See Also + -------- + chndtr : Noncentral chi-squared distribution CDF + chndtridf : Inverse of `chndtr` with respect to `df` + chndtrinc : Inverse of `chndtr` with respect to `nc` + scipy.stats.ncx2 : Non-central chi-squared distribution + + Notes + ----- + The noncentral chi squared distribution is also available in + `scipy.stats.ncx2`. + + This function wraps routines from the Boost Math C++ library [1]_. + + References + ---------- + .. [1] The Boost Developers. "Boost C++ Libraries". https://www.boost.org/. + + Examples + -------- + >>> from scipy.special import chndtrinc, chndtr + + Compute the noncentral chi squared distribution CDF at one point. + + >>> x, df, nc = 3, 5, 10 + >>> p = chndtr(x, df, nc) + + `chndtrinc` is the inverse of `chndtr` with respect to `nc`: + + >>> chndtrinc(x, df, p) + 10.0 + + """) + +add_newdoc( + "elliprc", + r""" + elliprc(x, y, out=None) + + Degenerate symmetric elliptic integral. + + The function RC is defined as [1]_ + + .. math:: + + R_{\mathrm{C}}(x, y) = + \frac{1}{2} \int_0^{+\infty} (t + x)^{-1/2} (t + y)^{-1} dt + = R_{\mathrm{F}}(x, y, y) + + Parameters + ---------- + x, y : array_like + Real or complex input parameters. `x` can be any number in the + complex plane cut along the negative real axis. `y` must be non-zero. + out : ndarray, optional + Optional output array for the function values + + Returns + ------- + R : scalar or ndarray + Value of the integral. If `y` is real and negative, the Cauchy + principal value is returned. If both of `x` and `y` are real, the + return value is real. Otherwise, the return value is complex. + + See Also + -------- + elliprf : Completely-symmetric elliptic integral of the first kind. + elliprd : Symmetric elliptic integral of the second kind. + elliprg : Completely-symmetric elliptic integral of the second kind. + elliprj : Symmetric elliptic integral of the third kind. + + Notes + ----- + RC is a degenerate case of the symmetric integral RF: ``elliprc(x, y) == + elliprf(x, y, y)``. It is an elementary function rather than an elliptic + integral. + + The code implements Carlson's algorithm based on the duplication theorems + and series expansion up to the 7th order. [2]_ + + .. versionadded:: 1.8.0 + + References + ---------- + .. [1] B. C. Carlson, ed., Chapter 19 in "Digital Library of Mathematical + Functions," NIST, US Dept. of Commerce. + https://dlmf.nist.gov/19.16.E6 + .. [2] B. C. Carlson, "Numerical computation of real or complex elliptic + integrals," Numer. Algorithm, vol. 10, no. 1, pp. 13-26, 1995. + :doi:`10.1007/BF02198293`. https://arxiv.org/abs/math/9409227 + + Examples + -------- + Basic homogeneity property: + + >>> import numpy as np + >>> from scipy.special import elliprc + + >>> x = 1.2 + 3.4j + >>> y = 5. + >>> scale = 0.3 + 0.4j + >>> elliprc(scale*x, scale*y) + (0.5484493976710874-0.4169557678995833j) + + >>> elliprc(x, y)/np.sqrt(scale) + (0.5484493976710874-0.41695576789958333j) + + When the two arguments coincide, the integral is particularly + simple: + + >>> x = 1.2 + 3.4j + >>> elliprc(x, x) + (0.4299173120614631-0.3041729818745595j) + + >>> 1/np.sqrt(x) + (0.4299173120614631-0.30417298187455954j) + + Another simple case: the first argument vanishes: + + >>> y = 1.2 + 3.4j + >>> elliprc(0, y) + (0.6753125346116815-0.47779380263880866j) + + >>> np.pi/2/np.sqrt(y) + (0.6753125346116815-0.4777938026388088j) + + When `x` and `y` are both positive, we can express + :math:`R_C(x,y)` in terms of more elementary functions. For the + case :math:`0 \le x < y`, + + >>> x = 3.2 + >>> y = 6. + >>> elliprc(x, y) + 0.44942991498453444 + + >>> np.arctan(np.sqrt((y-x)/x))/np.sqrt(y-x) + 0.44942991498453433 + + And for the case :math:`0 \le y < x`, + + >>> x = 6. + >>> y = 3.2 + >>> elliprc(x,y) + 0.4989837501576147 + + >>> np.log((np.sqrt(x)+np.sqrt(x-y))/np.sqrt(y))/np.sqrt(x-y) + 0.49898375015761476 + + """) + +add_newdoc( + "elliprd", + r""" + elliprd(x, y, z, out=None) + + Symmetric elliptic integral of the second kind. + + The function RD is defined as [1]_ + + .. math:: + + R_{\mathrm{D}}(x, y, z) = + \frac{3}{2} \int_0^{+\infty} [(t + x) (t + y)]^{-1/2} (t + z)^{-3/2} + dt + + Parameters + ---------- + x, y, z : array_like + Real or complex input parameters. `x` or `y` can be any number in the + complex plane cut along the negative real axis, but at most one of them + can be zero, while `z` must be non-zero. + out : ndarray, optional + Optional output array for the function values + + Returns + ------- + R : scalar or ndarray + Value of the integral. If all of `x`, `y`, and `z` are real, the + return value is real. Otherwise, the return value is complex. + + See Also + -------- + elliprc : Degenerate symmetric elliptic integral. + elliprf : Completely-symmetric elliptic integral of the first kind. + elliprg : Completely-symmetric elliptic integral of the second kind. + elliprj : Symmetric elliptic integral of the third kind. + + Notes + ----- + RD is a degenerate case of the elliptic integral RJ: ``elliprd(x, y, z) == + elliprj(x, y, z, z)``. + + The code implements Carlson's algorithm based on the duplication theorems + and series expansion up to the 7th order. [2]_ + + .. versionadded:: 1.8.0 + + References + ---------- + .. [1] B. C. Carlson, ed., Chapter 19 in "Digital Library of Mathematical + Functions," NIST, US Dept. of Commerce. + https://dlmf.nist.gov/19.16.E5 + .. [2] B. C. Carlson, "Numerical computation of real or complex elliptic + integrals," Numer. Algorithm, vol. 10, no. 1, pp. 13-26, 1995. + :doi:`10.1007/BF02198293`. https://arxiv.org/abs/math/9409227 + + Examples + -------- + Basic homogeneity property: + + >>> import numpy as np + >>> from scipy.special import elliprd + + >>> x = 1.2 + 3.4j + >>> y = 5. + >>> z = 6. + >>> scale = 0.3 + 0.4j + >>> elliprd(scale*x, scale*y, scale*z) + (-0.03703043835680379-0.24500934665683802j) + + >>> elliprd(x, y, z)*np.power(scale, -1.5) + (-0.0370304383568038-0.24500934665683805j) + + All three arguments coincide: + + >>> x = 1.2 + 3.4j + >>> elliprd(x, x, x) + (-0.03986825876151896-0.14051741840449586j) + + >>> np.power(x, -1.5) + (-0.03986825876151894-0.14051741840449583j) + + The so-called "second lemniscate constant": + + >>> elliprd(0, 2, 1)/3 + 0.5990701173677961 + + >>> from scipy.special import gamma + >>> gamma(0.75)**2/np.sqrt(2*np.pi) + 0.5990701173677959 + + """) + +add_newdoc( + "elliprf", + r""" + elliprf(x, y, z, out=None) + + Completely-symmetric elliptic integral of the first kind. + + The function RF is defined as [1]_ + + .. math:: + + R_{\mathrm{F}}(x, y, z) = + \frac{1}{2} \int_0^{+\infty} [(t + x) (t + y) (t + z)]^{-1/2} dt + + Parameters + ---------- + x, y, z : array_like + Real or complex input parameters. `x`, `y`, or `z` can be any number in + the complex plane cut along the negative real axis, but at most one of + them can be zero. + out : ndarray, optional + Optional output array for the function values + + Returns + ------- + R : scalar or ndarray + Value of the integral. If all of `x`, `y`, and `z` are real, the return + value is real. Otherwise, the return value is complex. + + See Also + -------- + elliprc : Degenerate symmetric integral. + elliprd : Symmetric elliptic integral of the second kind. + elliprg : Completely-symmetric elliptic integral of the second kind. + elliprj : Symmetric elliptic integral of the third kind. + + Notes + ----- + The code implements Carlson's algorithm based on the duplication theorems + and series expansion up to the 7th order (cf.: + https://dlmf.nist.gov/19.36.i) and the AGM algorithm for the complete + integral. [2]_ + + .. versionadded:: 1.8.0 + + References + ---------- + .. [1] B. C. Carlson, ed., Chapter 19 in "Digital Library of Mathematical + Functions," NIST, US Dept. of Commerce. + https://dlmf.nist.gov/19.16.E1 + .. [2] B. C. Carlson, "Numerical computation of real or complex elliptic + integrals," Numer. Algorithm, vol. 10, no. 1, pp. 13-26, 1995. + :doi:`10.1007/BF02198293`. https://arxiv.org/abs/math/9409227 + + Examples + -------- + Basic homogeneity property: + + >>> import numpy as np + >>> from scipy.special import elliprf + + >>> x = 1.2 + 3.4j + >>> y = 5. + >>> z = 6. + >>> scale = 0.3 + 0.4j + >>> elliprf(scale*x, scale*y, scale*z) + (0.5328051227278146-0.4008623567957094j) + + >>> elliprf(x, y, z)/np.sqrt(scale) + (0.5328051227278147-0.4008623567957095j) + + All three arguments coincide: + + >>> x = 1.2 + 3.4j + >>> elliprf(x, x, x) + (0.42991731206146316-0.30417298187455954j) + + >>> 1/np.sqrt(x) + (0.4299173120614631-0.30417298187455954j) + + The so-called "first lemniscate constant": + + >>> elliprf(0, 1, 2) + 1.3110287771460598 + + >>> from scipy.special import gamma + >>> gamma(0.25)**2/(4*np.sqrt(2*np.pi)) + 1.3110287771460598 + + """) + +add_newdoc( + "elliprg", + r""" + elliprg(x, y, z, out=None) + + Completely-symmetric elliptic integral of the second kind. + + The function RG is defined as [1]_ + + .. math:: + + R_{\mathrm{G}}(x, y, z) = + \frac{1}{4} \int_0^{+\infty} [(t + x) (t + y) (t + z)]^{-1/2} + \left(\frac{x}{t + x} + \frac{y}{t + y} + \frac{z}{t + z}\right) t + dt + + Parameters + ---------- + x, y, z : array_like + Real or complex input parameters. `x`, `y`, or `z` can be any number in + the complex plane cut along the negative real axis. + out : ndarray, optional + Optional output array for the function values + + Returns + ------- + R : scalar or ndarray + Value of the integral. If all of `x`, `y`, and `z` are real, the return + value is real. Otherwise, the return value is complex. + + See Also + -------- + elliprc : Degenerate symmetric integral. + elliprd : Symmetric elliptic integral of the second kind. + elliprf : Completely-symmetric elliptic integral of the first kind. + elliprj : Symmetric elliptic integral of the third kind. + + Notes + ----- + The implementation uses the relation [1]_ + + .. math:: + + 2 R_{\mathrm{G}}(x, y, z) = + z R_{\mathrm{F}}(x, y, z) - + \frac{1}{3} (x - z) (y - z) R_{\mathrm{D}}(x, y, z) + + \sqrt{\frac{x y}{z}} + + and the symmetry of `x`, `y`, `z` when at least one non-zero parameter can + be chosen as the pivot. When one of the arguments is close to zero, the AGM + method is applied instead. Other special cases are computed following Ref. + [2]_ + + .. versionadded:: 1.8.0 + + References + ---------- + .. [1] B. C. Carlson, "Numerical computation of real or complex elliptic + integrals," Numer. Algorithm, vol. 10, no. 1, pp. 13-26, 1995. + :doi:`10.1007/BF02198293`. https://arxiv.org/abs/math/9409227 + .. [2] B. C. Carlson, ed., Chapter 19 in "Digital Library of Mathematical + Functions," NIST, US Dept. of Commerce. + https://dlmf.nist.gov/19.16.E1 + https://dlmf.nist.gov/19.20.ii + + Examples + -------- + Basic homogeneity property: + + >>> import numpy as np + >>> from scipy.special import elliprg + + >>> x = 1.2 + 3.4j + >>> y = 5. + >>> z = 6. + >>> scale = 0.3 + 0.4j + >>> elliprg(scale*x, scale*y, scale*z) + (1.195936862005246+0.8470988320464167j) + + >>> elliprg(x, y, z)*np.sqrt(scale) + (1.195936862005246+0.8470988320464165j) + + Simplifications: + + >>> elliprg(0, y, y) + 1.756203682760182 + + >>> 0.25*np.pi*np.sqrt(y) + 1.7562036827601817 + + >>> elliprg(0, 0, z) + 1.224744871391589 + + >>> 0.5*np.sqrt(z) + 1.224744871391589 + + The surface area of a triaxial ellipsoid with semiaxes ``a``, ``b``, and + ``c`` is given by + + .. math:: + + S = 4 \pi a b c R_{\mathrm{G}}(1 / a^2, 1 / b^2, 1 / c^2). + + >>> def ellipsoid_area(a, b, c): + ... r = 4.0 * np.pi * a * b * c + ... return r * elliprg(1.0 / (a * a), 1.0 / (b * b), 1.0 / (c * c)) + >>> print(ellipsoid_area(1, 3, 5)) + 108.62688289491807 + """) + +add_newdoc( + "elliprj", + r""" + elliprj(x, y, z, p, out=None) + + Symmetric elliptic integral of the third kind. + + The function RJ is defined as [1]_ + + .. math:: + + R_{\mathrm{J}}(x, y, z, p) = + \frac{3}{2} \int_0^{+\infty} [(t + x) (t + y) (t + z)]^{-1/2} + (t + p)^{-1} dt + + .. warning:: + This function should be considered experimental when the inputs are + unbalanced. Check correctness with another independent implementation. + + Parameters + ---------- + x, y, z, p : array_like + Real or complex input parameters. `x`, `y`, or `z` are numbers in + the complex plane cut along the negative real axis (subject to further + constraints, see Notes), and at most one of them can be zero. `p` must + be non-zero. + out : ndarray, optional + Optional output array for the function values + + Returns + ------- + R : scalar or ndarray + Value of the integral. If all of `x`, `y`, `z`, and `p` are real, the + return value is real. Otherwise, the return value is complex. + + If `p` is real and negative, while `x`, `y`, and `z` are real, + non-negative, and at most one of them is zero, the Cauchy principal + value is returned. [1]_ [2]_ + + See Also + -------- + elliprc : Degenerate symmetric integral. + elliprd : Symmetric elliptic integral of the second kind. + elliprf : Completely-symmetric elliptic integral of the first kind. + elliprg : Completely-symmetric elliptic integral of the second kind. + + Notes + ----- + The code implements Carlson's algorithm based on the duplication theorems + and series expansion up to the 7th order. [3]_ The algorithm is slightly + different from its earlier incarnation as it appears in [1]_, in that the + call to `elliprc` (or ``atan``/``atanh``, see [4]_) is no longer needed in + the inner loop. Asymptotic approximations are used where arguments differ + widely in the order of magnitude. [5]_ + + The input values are subject to certain sufficient but not necessary + constraints when input arguments are complex. Notably, ``x``, ``y``, and + ``z`` must have non-negative real parts, unless two of them are + non-negative and complex-conjugates to each other while the other is a real + non-negative number. [1]_ If the inputs do not satisfy the sufficient + condition described in Ref. [1]_ they are rejected outright with the output + set to NaN. + + In the case where one of ``x``, ``y``, and ``z`` is equal to ``p``, the + function ``elliprd`` should be preferred because of its less restrictive + domain. + + .. versionadded:: 1.8.0 + + References + ---------- + .. [1] B. C. Carlson, "Numerical computation of real or complex elliptic + integrals," Numer. Algorithm, vol. 10, no. 1, pp. 13-26, 1995. + :doi:`10.1007/BF02198293`. https://arxiv.org/abs/math/9409227 + .. [2] B. C. Carlson, ed., Chapter 19 in "Digital Library of Mathematical + Functions," NIST, US Dept. of Commerce. + https://dlmf.nist.gov/19.20.iii + .. [3] B. C. Carlson, J. FitzSimmons, "Reduction Theorems for Elliptic + Integrands with the Square Root of Two Quadratic Factors," J. + Comput. Appl. Math., vol. 118, nos. 1-2, pp. 71-85, 2000. + :doi:`10.1016/S0377-0427(00)00282-X`. + .. [4] F. Johansson, "Numerical Evaluation of Elliptic Functions, Elliptic + Integrals and Modular Forms," in J. Blumlein, C. Schneider, P. + Paule, eds., "Elliptic Integrals, Elliptic Functions and Modular + Forms in Quantum Field Theory," pp. 269-293, 2019 (Cham, + Switzerland: Springer Nature Switzerland). + :doi:`10.1007/978-3-030-04480-0`. https://arxiv.org/abs/1806.06725 + .. [5] B. C. Carlson, J. L. Gustafson, "Asymptotic Approximations for + Symmetric Elliptic Integrals," SIAM J. Math. Anls., vol. 25, no. 2, + pp. 288-303, 1994. :doi:`10.1137/S0036141092228477`. + https://arxiv.org/abs/math/9310223 + + Examples + -------- + Basic homogeneity property: + + >>> import numpy as np + >>> from scipy.special import elliprj + + >>> x = 1.2 + 3.4j + >>> y = 5. + >>> z = 6. + >>> p = 7. + >>> scale = 0.3 - 0.4j + >>> elliprj(scale*x, scale*y, scale*z, scale*p) + (0.10834905565679157+0.19694950747103812j) + + >>> elliprj(x, y, z, p)*np.power(scale, -1.5) + (0.10834905565679556+0.19694950747103854j) + + Reduction to simpler elliptic integral: + + >>> elliprj(x, y, z, z) + (0.08288462362195129-0.028376809745123258j) + + >>> from scipy.special import elliprd + >>> elliprd(x, y, z) + (0.08288462362195136-0.028376809745123296j) + + All arguments coincide: + + >>> elliprj(x, x, x, x) + (-0.03986825876151896-0.14051741840449586j) + + >>> np.power(x, -1.5) + (-0.03986825876151894-0.14051741840449583j) + + """) + +add_newdoc("entr", + r""" + entr(x, out=None) + + Elementwise function for computing entropy. + + .. math:: \text{entr}(x) = \begin{cases} - x \log(x) & x > 0 \\ 0 & x = 0 + \\ -\infty & \text{otherwise} \end{cases} + + Parameters + ---------- + x : ndarray + Input array. + out : ndarray, optional + Optional output array for the function values + + Returns + ------- + res : scalar or ndarray + The value of the elementwise entropy function at the given points `x`. + + See Also + -------- + kl_div, rel_entr, scipy.stats.entropy + + Notes + ----- + .. versionadded:: 0.15.0 + + This function is concave. + + The origin of this function is in convex programming; see [1]_. + Given a probability distribution :math:`p_1, \ldots, p_n`, + the definition of entropy in the context of *information theory* is + + .. math:: + + \sum_{i = 1}^n \mathrm{entr}(p_i). + + To compute the latter quantity, use `scipy.stats.entropy`. + + References + ---------- + .. [1] Boyd, Stephen and Lieven Vandenberghe. *Convex optimization*. + Cambridge University Press, 2004. + :doi:`10.1017/CBO9780511804441`. + + Examples + -------- + >>> import numpy as np + >>> from scipy.special import entr + + Calculate the entropy (in nats) of a 3-outcome probability distribution + + >>> p = np.array([0.2, 0.5, 0.3]) + >>> entr(p) + array([0.32188758, 0.34657359, 0.36119184]) + >>> entr(p).sum() + 1.0296530140645737 + + """) + +add_newdoc( + "erfinv", + """ + erfinv(y, out=None) + + Inverse of the error function. + + Computes the inverse of the error function. + + In the complex domain, there is no unique complex number w satisfying + erf(w)=z. This indicates a true inverse function would be multivalued. + When the domain restricts to the real, -1 < x < 1, there is a unique real + number satisfying erf(erfinv(x)) = x. + + Parameters + ---------- + y : ndarray + Argument at which to evaluate. Domain: [-1, 1] + out : ndarray, optional + Optional output array for the function values + + Returns + ------- + erfinv : scalar or ndarray + The inverse of erf of y, element-wise + + See Also + -------- + erf : Error function of a complex argument + erfc : Complementary error function, ``1 - erf(x)`` + erfcinv : Inverse of the complementary error function + + Notes + ----- + This function wraps the ``erf_inv`` routine from the + Boost Math C++ library [1]_. + + References + ---------- + .. [1] The Boost Developers. "Boost C++ Libraries". https://www.boost.org/. + + Examples + -------- + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from scipy.special import erfinv, erf + + >>> erfinv(0.5) + 0.4769362762044699 + + >>> y = np.linspace(-1.0, 1.0, num=9) + >>> x = erfinv(y) + >>> x + array([ -inf, -0.81341985, -0.47693628, -0.22531206, 0. , + 0.22531206, 0.47693628, 0.81341985, inf]) + + Verify that ``erf(erfinv(y))`` is ``y``. + + >>> erf(x) + array([-1. , -0.75, -0.5 , -0.25, 0. , 0.25, 0.5 , 0.75, 1. ]) + + Plot the function: + + >>> y = np.linspace(-1, 1, 200) + >>> fig, ax = plt.subplots() + >>> ax.plot(y, erfinv(y)) + >>> ax.grid(True) + >>> ax.set_xlabel('y') + >>> ax.set_title('erfinv(y)') + >>> plt.show() + + """) + +add_newdoc( + "erfcinv", + """ + erfcinv(y, out=None) + + Inverse of the complementary error function. + + Computes the inverse of the complementary error function. + + In the complex domain, there is no unique complex number :math:`w` satisfying + :math:`\\operatorname{erfc}(w) = z`. This indicates a true inverse function + would be multivalued. + When the domain restricts to the real interval :math:`0 < x < 2`, there is + a unique real number satisfying + + .. math:: + + \\operatorname{erfc}(\\operatorname{erfcinv}(x)) = x + + It is related to the inverse of the error function by + + .. math:: + + \\operatorname{erfcinv}(1 - x) = \\operatorname{erfinv}(x) + + Parameters + ---------- + y : ndarray + Argument at which to evaluate. Domain: :math:`[0, 2]` + out : ndarray, optional + Optional output array for the function values + + Returns + ------- + erfcinv : scalar or ndarray + The inverse of :math:`\\operatorname{erfc}` of :math:`y`, element-wise + + See Also + -------- + erf : Error function + erfc : Complementary error function + erfinv : Inverse of the error function + + Examples + -------- + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from scipy.special import erfcinv + + >>> erfcinv(0.5) + 0.4769362762044699 + + >>> y = np.linspace(0.0, 2.0, num=11) + >>> erfcinv(y) + array([ inf, 0.9061938 , 0.59511608, 0.37080716, 0.17914345, + -0. , -0.17914345, -0.37080716, -0.59511608, -0.9061938 , + -inf]) + + Plot the function: + + >>> y = np.linspace(0, 2, 200) + >>> fig, ax = plt.subplots() + >>> ax.plot(y, erfcinv(y)) + >>> ax.grid(True) + >>> ax.set_xlabel('y') + >>> ax.set_title('erfcinv(y)') + >>> plt.show() + + """) + +add_newdoc("eval_jacobi", + r""" + eval_jacobi(n, alpha, beta, x, out=None) + + Evaluate Jacobi polynomial at a point. + + The Jacobi polynomials can be defined via the Gauss hypergeometric + function :math:`{}_2F_1` as + + .. math:: + + P_n^{(\alpha, \beta)}(x) = \frac{(\alpha + 1)_n}{\Gamma(n + 1)} + {}_2F_1(-n, 1 + \alpha + \beta + n; \alpha + 1; (1 - x)/2) + + where :math:`(\cdot)_n` is the Pochhammer symbol; see `poch`. When + :math:`n` is an integer the result is a polynomial of degree + :math:`n`. See 22.5.42 in [AS]_ or [DLMF]_ for details. + + Parameters + ---------- + n : array_like + Degree of the polynomial. If not an integer the result is + determined via the relation to the Gauss hypergeometric + function. + alpha : array_like + Parameter. + beta : array_like + Parameter. + x : array_like + Points at which to evaluate the polynomial. + out : ndarray, optional + Optional output array for the function values. + + Returns + ------- + P : scalar or ndarray + Values of the Jacobi polynomial. + + See Also + -------- + roots_jacobi : roots and quadrature weights of Jacobi polynomials + jacobi : Jacobi polynomial object + hyp2f1 : Gauss hypergeometric function + + References + ---------- + .. [AS] Milton Abramowitz and Irene A. Stegun, eds. + Handbook of Mathematical Functions with Formulas, + Graphs, and Mathematical Tables. New York: Dover, 1972. + .. [DLMF] NIST Digital Library of Mathematical Functions, + https://dlmf.nist.gov/18.5.E7 + + """) + +add_newdoc("eval_sh_jacobi", + r""" + eval_sh_jacobi(n, p, q, x, out=None) + + Evaluate shifted Jacobi polynomial at a point. + + Defined by + + .. math:: + + G_n^{(p, q)}(x) + = \binom{2n + p - 1}{n}^{-1} P_n^{(p - q, q - 1)}(2x - 1), + + where :math:`P_n^{(\cdot, \cdot)}` is the n-th Jacobi polynomial. + See 22.5.2 in [AS]_ (or equivalently [DLMF]_) for details. + + Parameters + ---------- + n : int + Degree of the polynomial. If not an integer, the result is + determined via the relation to `binom` and `eval_jacobi`. + p : float + Parameter + q : float + Parameter + out : ndarray, optional + Optional output array for the function values + + Returns + ------- + G : scalar or ndarray + Values of the shifted Jacobi polynomial. + + See Also + -------- + roots_sh_jacobi : roots and quadrature weights of shifted Jacobi + polynomials + sh_jacobi : shifted Jacobi polynomial object + eval_jacobi : evaluate Jacobi polynomials + + References + ---------- + .. [AS] Milton Abramowitz and Irene A. Stegun, eds. + Handbook of Mathematical Functions with Formulas, + Graphs, and Mathematical Tables. New York: Dover, 1972. + .. [DLMF] NIST Digital Library of Mathematical Functions, + https://dlmf.nist.gov/18.1.E2 + + """) + +add_newdoc("eval_gegenbauer", + r""" + eval_gegenbauer(n, alpha, x, out=None) + + Evaluate Gegenbauer (ultraspherical) polynomial at a point. + + The Gegenbauer polynomials can be defined via the Gauss + hypergeometric function :math:`{}_2F_1` as + + .. math:: + + C_n^{(\alpha)}(x) = \frac{(2\alpha)_n}{\Gamma(n + 1)} + {}_2F_1(-n, 2\alpha + n; \alpha + 1/2; (1 - x)/2). + + where :math:`(\cdot)_n` is the Pochhammer symbol; see `poch`. When + :math:`n` is an integer the result is a polynomial of degree + :math:`n`. See 22.5.46 in [AS]_ (or equivalently [DLMF]_) for details. + + Parameters + ---------- + n : array_like + Degree of the polynomial. If not an integer, the result is + determined via the relation to the Gauss hypergeometric + function. + alpha : array_like + Parameter. + x : array_like + Points at which to evaluate the Gegenbauer polynomial. + out : ndarray, optional + Optional output array for the function values. + + Returns + ------- + C : scalar or ndarray + Values of the Gegenbauer polynomial. + + See Also + -------- + roots_gegenbauer : roots and quadrature weights of Gegenbauer + polynomials + gegenbauer : Gegenbauer polynomial object + hyp2f1 : Gauss hypergeometric function + + References + ---------- + .. [AS] Milton Abramowitz and Irene A. Stegun, eds. + Handbook of Mathematical Functions with Formulas, + Graphs, and Mathematical Tables. New York: Dover, 1972. + .. [DLMF] NIST Digital Library of Mathematical Functions, + https://dlmf.nist.gov/18.5.E9 + + """) + +add_newdoc("eval_chebyt", + r""" + eval_chebyt(n, x, out=None) + + Evaluate Chebyshev polynomial of the first kind at a point. + + The Chebyshev polynomials of the first kind can be defined via the + Gauss hypergeometric function :math:`{}_2F_1` as + + .. math:: + + T_n(x) = {}_2F_1(n, -n; 1/2; (1 - x)/2). + + When :math:`n` is an integer the result is a polynomial of degree + :math:`n`. See 22.5.47 in [AS]_ (or equivalently [DLMF]_) for details. + + Parameters + ---------- + n : array_like + Degree of the polynomial. If not an integer, the result is + determined via the relation to the Gauss hypergeometric + function. + x : array_like + Points at which to evaluate the Chebyshev polynomial + out : ndarray, optional + Optional output array for the function values + + Returns + ------- + T : scalar or ndarray + Values of the Chebyshev polynomial + + See Also + -------- + roots_chebyt : roots and quadrature weights of Chebyshev + polynomials of the first kind + chebyu : Chebychev polynomial object + eval_chebyu : evaluate Chebyshev polynomials of the second kind + hyp2f1 : Gauss hypergeometric function + numpy.polynomial.chebyshev.Chebyshev : Chebyshev series + + Notes + ----- + This routine is numerically stable for `x` in ``[-1, 1]`` at least + up to order ``10000``. + + References + ---------- + .. [AS] Milton Abramowitz and Irene A. Stegun, eds. + Handbook of Mathematical Functions with Formulas, + Graphs, and Mathematical Tables. New York: Dover, 1972. + .. [DLMF] NIST Digital Library of Mathematical Functions, + https://dlmf.nist.gov/18.5.E11_2 + + """) + +add_newdoc("eval_chebyu", + r""" + eval_chebyu(n, x, out=None) + + Evaluate Chebyshev polynomial of the second kind at a point. + + The Chebyshev polynomials of the second kind can be defined via + the Gauss hypergeometric function :math:`{}_2F_1` as + + .. math:: + + U_n(x) = (n + 1) {}_2F_1(-n, n + 2; 3/2; (1 - x)/2). + + When :math:`n` is an integer the result is a polynomial of degree + :math:`n`. See 22.5.48 in [AS]_ (or equivalently [DLMF]_) for details. + + Parameters + ---------- + n : array_like + Degree of the polynomial. If not an integer, the result is + determined via the relation to the Gauss hypergeometric + function. + x : array_like + Points at which to evaluate the Chebyshev polynomial + out : ndarray, optional + Optional output array for the function values + + Returns + ------- + U : scalar or ndarray + Values of the Chebyshev polynomial + + See Also + -------- + roots_chebyu : roots and quadrature weights of Chebyshev + polynomials of the second kind + chebyu : Chebyshev polynomial object + eval_chebyt : evaluate Chebyshev polynomials of the first kind + hyp2f1 : Gauss hypergeometric function + + References + ---------- + .. [AS] Milton Abramowitz and Irene A. Stegun, eds. + Handbook of Mathematical Functions with Formulas, + Graphs, and Mathematical Tables. New York: Dover, 1972. + .. [DLMF] NIST Digital Library of Mathematical Functions, + https://dlmf.nist.gov/18.5.E11_4 + + """) + +add_newdoc("eval_chebys", + r""" + eval_chebys(n, x, out=None) + + Evaluate Chebyshev polynomial of the second kind on [-2, 2] at a + point. + + These polynomials are defined as + + .. math:: + + S_n(x) = U_n(x/2) + + where :math:`U_n` is a Chebyshev polynomial of the second kind. + See 22.5.13 in [AS]_ (or equivalently [DLMF]_) for details. + + Parameters + ---------- + n : array_like + Degree of the polynomial. If not an integer, the result is + determined via the relation to `eval_chebyu`. + x : array_like + Points at which to evaluate the Chebyshev polynomial + out : ndarray, optional + Optional output array for the function values + + Returns + ------- + S : scalar or ndarray + Values of the Chebyshev polynomial + + See Also + -------- + roots_chebys : roots and quadrature weights of Chebyshev + polynomials of the second kind on [-2, 2] + chebys : Chebyshev polynomial object + eval_chebyu : evaluate Chebyshev polynomials of the second kind + + References + ---------- + .. [AS] Milton Abramowitz and Irene A. Stegun, eds. + Handbook of Mathematical Functions with Formulas, + Graphs, and Mathematical Tables. New York: Dover, 1972. + .. [DLMF] NIST Digital Library of Mathematical Functions, + https://dlmf.nist.gov/18.1.E3 + + Examples + -------- + >>> import numpy as np + >>> import scipy.special as sc + + They are a scaled version of the Chebyshev polynomials of the + second kind. + + >>> x = np.linspace(-2, 2, 6) + >>> sc.eval_chebys(3, x) + array([-4. , 0.672, 0.736, -0.736, -0.672, 4. ]) + >>> sc.eval_chebyu(3, x / 2) + array([-4. , 0.672, 0.736, -0.736, -0.672, 4. ]) + + """) + +add_newdoc("eval_chebyc", + r""" + eval_chebyc(n, x, out=None) + + Evaluate Chebyshev polynomial of the first kind on [-2, 2] at a + point. + + These polynomials are defined as + + .. math:: + + C_n(x) = 2 T_n(x/2) + + where :math:`T_n` is a Chebyshev polynomial of the first kind. See + 22.5.11 in [AS]_ (or equivalently [DLMF]_) for details. + + Parameters + ---------- + n : array_like + Degree of the polynomial. If not an integer, the result is + determined via the relation to `eval_chebyt`. + x : array_like + Points at which to evaluate the Chebyshev polynomial + out : ndarray, optional + Optional output array for the function values + + Returns + ------- + C : scalar or ndarray + Values of the Chebyshev polynomial + + See Also + -------- + roots_chebyc : roots and quadrature weights of Chebyshev + polynomials of the first kind on [-2, 2] + chebyc : Chebyshev polynomial object + numpy.polynomial.chebyshev.Chebyshev : Chebyshev series + eval_chebyt : evaluate Chebycshev polynomials of the first kind + + References + ---------- + .. [AS] Milton Abramowitz and Irene A. Stegun, eds. + Handbook of Mathematical Functions with Formulas, + Graphs, and Mathematical Tables. New York: Dover, 1972. + .. [DLMF] NIST Digital Library of Mathematical Functions, + https://dlmf.nist.gov/18.1.E3 + + Examples + -------- + >>> import numpy as np + >>> import scipy.special as sc + + They are a scaled version of the Chebyshev polynomials of the + first kind. + + >>> x = np.linspace(-2, 2, 6) + >>> sc.eval_chebyc(3, x) + array([-2. , 1.872, 1.136, -1.136, -1.872, 2. ]) + >>> 2 * sc.eval_chebyt(3, x / 2) + array([-2. , 1.872, 1.136, -1.136, -1.872, 2. ]) + + """) + +add_newdoc("eval_sh_chebyt", + r""" + eval_sh_chebyt(n, x, out=None) + + Evaluate shifted Chebyshev polynomial of the first kind at a + point. + + These polynomials are defined as + + .. math:: + + T_n^*(x) = T_n(2x - 1) + + where :math:`T_n` is a Chebyshev polynomial of the first kind. See + 22.5.14 in [AS]_ (or equivalently [DLMF]_) for details. + + Parameters + ---------- + n : array_like + Degree of the polynomial. If not an integer, the result is + determined via the relation to `eval_chebyt`. + x : array_like + Points at which to evaluate the shifted Chebyshev polynomial + out : ndarray, optional + Optional output array for the function values + + Returns + ------- + T : scalar or ndarray + Values of the shifted Chebyshev polynomial + + See Also + -------- + roots_sh_chebyt : roots and quadrature weights of shifted + Chebyshev polynomials of the first kind + sh_chebyt : shifted Chebyshev polynomial object + eval_chebyt : evaluate Chebyshev polynomials of the first kind + numpy.polynomial.chebyshev.Chebyshev : Chebyshev series + + References + ---------- + .. [AS] Milton Abramowitz and Irene A. Stegun, eds. + Handbook of Mathematical Functions with Formulas, + Graphs, and Mathematical Tables. New York: Dover, 1972. + .. [DLMF] NIST Digital Library of Mathematical Functions, + https://dlmf.nist.gov/18.7.E7 + + """) + +add_newdoc("eval_sh_chebyu", + r""" + eval_sh_chebyu(n, x, out=None) + + Evaluate shifted Chebyshev polynomial of the second kind at a + point. + + These polynomials are defined as + + .. math:: + + U_n^*(x) = U_n(2x - 1) + + where :math:`U_n` is a Chebyshev polynomial of the first kind. See + 22.5.15 in [AS]_ (or equivalently [DLMF]_) for details. + + Parameters + ---------- + n : array_like + Degree of the polynomial. If not an integer, the result is + determined via the relation to `eval_chebyu`. + x : array_like + Points at which to evaluate the shifted Chebyshev polynomial + out : ndarray, optional + Optional output array for the function values + + Returns + ------- + U : scalar or ndarray + Values of the shifted Chebyshev polynomial + + See Also + -------- + roots_sh_chebyu : roots and quadrature weights of shifted + Chebychev polynomials of the second kind + sh_chebyu : shifted Chebyshev polynomial object + eval_chebyu : evaluate Chebyshev polynomials of the second kind + + References + ---------- + .. [AS] Milton Abramowitz and Irene A. Stegun, eds. + Handbook of Mathematical Functions with Formulas, + Graphs, and Mathematical Tables. New York: Dover, 1972. + .. [DLMF] NIST Digital Library of Mathematical Functions, + https://dlmf.nist.gov/18.7.E8 + + """) + +add_newdoc("eval_legendre", + r""" + eval_legendre(n, x, out=None) + + Evaluate Legendre polynomial at a point. + + The Legendre polynomials can be defined via the Gauss + hypergeometric function :math:`{}_2F_1` as + + .. math:: + + P_n(x) = {}_2F_1(-n, n + 1; 1; (1 - x)/2). + + When :math:`n` is an integer the result is a polynomial of degree + :math:`n`. See 22.5.49 in [AS]_ (or equivalently [DLMF]_) for details. + + Parameters + ---------- + n : array_like + Degree of the polynomial. If not an integer, the result is + determined via the relation to the Gauss hypergeometric + function. + x : array_like + Points at which to evaluate the Legendre polynomial + out : ndarray, optional + Optional output array for the function values + + Returns + ------- + P : scalar or ndarray + Values of the Legendre polynomial + + See Also + -------- + roots_legendre : roots and quadrature weights of Legendre + polynomials + legendre : Legendre polynomial object + hyp2f1 : Gauss hypergeometric function + numpy.polynomial.legendre.Legendre : Legendre series + + References + ---------- + .. [AS] Milton Abramowitz and Irene A. Stegun, eds. + Handbook of Mathematical Functions with Formulas, + Graphs, and Mathematical Tables. New York: Dover, 1972. + .. [DLMF] NIST Digital Library of Mathematical Functions, + https://dlmf.nist.gov/15.9.E7 + + Examples + -------- + >>> import numpy as np + >>> from scipy.special import eval_legendre + + Evaluate the zero-order Legendre polynomial at x = 0 + + >>> eval_legendre(0, 0) + 1.0 + + Evaluate the first-order Legendre polynomial between -1 and 1 + + >>> X = np.linspace(-1, 1, 5) # Domain of Legendre polynomials + >>> eval_legendre(1, X) + array([-1. , -0.5, 0. , 0.5, 1. ]) + + Evaluate Legendre polynomials of order 0 through 4 at x = 0 + + >>> N = range(0, 5) + >>> eval_legendre(N, 0) + array([ 1. , 0. , -0.5 , 0. , 0.375]) + + Plot Legendre polynomials of order 0 through 4 + + >>> X = np.linspace(-1, 1) + + >>> import matplotlib.pyplot as plt + >>> for n in range(0, 5): + ... y = eval_legendre(n, X) + ... plt.plot(X, y, label=r'$P_{}(x)$'.format(n)) + + >>> plt.title("Legendre Polynomials") + >>> plt.xlabel("x") + >>> plt.ylabel(r'$P_n(x)$') + >>> plt.legend(loc='lower right') + >>> plt.show() + + """) + +add_newdoc("eval_sh_legendre", + r""" + eval_sh_legendre(n, x, out=None) + + Evaluate shifted Legendre polynomial at a point. + + These polynomials are defined as + + .. math:: + + P_n^*(x) = P_n(2x - 1) + + where :math:`P_n` is a Legendre polynomial. See 2.2.11 in [AS]_ + or [DLMF]_ for details. + + Parameters + ---------- + n : array_like + Degree of the polynomial. If not an integer, the value is + determined via the relation to `eval_legendre`. + x : array_like + Points at which to evaluate the shifted Legendre polynomial + out : ndarray, optional + Optional output array for the function values + + Returns + ------- + P : scalar or ndarray + Values of the shifted Legendre polynomial + + See Also + -------- + roots_sh_legendre : roots and quadrature weights of shifted + Legendre polynomials + sh_legendre : shifted Legendre polynomial object + eval_legendre : evaluate Legendre polynomials + numpy.polynomial.legendre.Legendre : Legendre series + + References + ---------- + .. [AS] Milton Abramowitz and Irene A. Stegun, eds. + Handbook of Mathematical Functions with Formulas, + Graphs, and Mathematical Tables. New York: Dover, 1972. + .. [DLMF] NIST Digital Library of Mathematical Functions, + https://dlmf.nist.gov/18.7.E10 + + """) + +add_newdoc("eval_genlaguerre", + r""" + eval_genlaguerre(n, alpha, x, out=None) + + Evaluate generalized Laguerre polynomial at a point. + + The generalized Laguerre polynomials can be defined via the + confluent hypergeometric function :math:`{}_1F_1` as + + .. math:: + + L_n^{(\alpha)}(x) = \binom{n + \alpha}{n} + {}_1F_1(-n, \alpha + 1, x). + + When :math:`n` is an integer the result is a polynomial of degree + :math:`n`. See 22.5.54 in [AS]_ or [DLMF]_ for details. The Laguerre + polynomials are the special case where :math:`\alpha = 0`. + + Parameters + ---------- + n : array_like + Degree of the polynomial. If not an integer, the result is + determined via the relation to the confluent hypergeometric + function. + alpha : array_like + Parameter; must have ``alpha > -1`` + x : array_like + Points at which to evaluate the generalized Laguerre + polynomial + out : ndarray, optional + Optional output array for the function values + + Returns + ------- + L : scalar or ndarray + Values of the generalized Laguerre polynomial + + See Also + -------- + roots_genlaguerre : roots and quadrature weights of generalized + Laguerre polynomials + genlaguerre : generalized Laguerre polynomial object + hyp1f1 : confluent hypergeometric function + eval_laguerre : evaluate Laguerre polynomials + + References + ---------- + .. [AS] Milton Abramowitz and Irene A. Stegun, eds. + Handbook of Mathematical Functions with Formulas, + Graphs, and Mathematical Tables. New York: Dover, 1972. + .. [DLMF] NIST Digital Library of Mathematical Functions, + https://dlmf.nist.gov/18.5.E12 + + """) + +add_newdoc("eval_laguerre", + r""" + eval_laguerre(n, x, out=None) + + Evaluate Laguerre polynomial at a point. + + The Laguerre polynomials can be defined via the confluent + hypergeometric function :math:`{}_1F_1` as + + .. math:: + + L_n(x) = {}_1F_1(-n, 1, x). + + See 22.5.16 and 22.5.54 in [AS]_ (or equivalently [DLMF1]_ and [DLMF2]_) + for details. When :math:`n` is an integer the result is a polynomial + of degree :math:`n`. + + Parameters + ---------- + n : array_like + Degree of the polynomial. If not an integer the result is + determined via the relation to the confluent hypergeometric + function. + x : array_like + Points at which to evaluate the Laguerre polynomial + out : ndarray, optional + Optional output array for the function values + + Returns + ------- + L : scalar or ndarray + Values of the Laguerre polynomial + + See Also + -------- + roots_laguerre : roots and quadrature weights of Laguerre + polynomials + laguerre : Laguerre polynomial object + numpy.polynomial.laguerre.Laguerre : Laguerre series + eval_genlaguerre : evaluate generalized Laguerre polynomials + + References + ---------- + .. [AS] Milton Abramowitz and Irene A. Stegun, eds. + Handbook of Mathematical Functions with Formulas, + Graphs, and Mathematical Tables. New York: Dover, 1972. + .. [DLMF1] NIST Digital Library of Mathematical Functions, + https://dlmf.nist.gov/18.1#I1.ix7.p1 + .. [DLMF2] NIST Digital Library of Mathematical Functions, + https://dlmf.nist.gov/18.5.E12 + + """) + +add_newdoc("eval_hermite", + r""" + eval_hermite(n, x, out=None) + + Evaluate physicist's Hermite polynomial at a point. + + Defined by + + .. math:: + + H_n(x) = (-1)^n e^{x^2} \frac{d^n}{dx^n} e^{-x^2}; + + :math:`H_n` is a polynomial of degree :math:`n`. See 22.11.7 in + [AS]_ or [DLMF]_ for details. + + Parameters + ---------- + n : array_like + Degree of the polynomial + x : array_like + Points at which to evaluate the Hermite polynomial + out : ndarray, optional + Optional output array for the function values + + Returns + ------- + H : scalar or ndarray + Values of the Hermite polynomial + + See Also + -------- + roots_hermite : roots and quadrature weights of physicist's + Hermite polynomials + hermite : physicist's Hermite polynomial object + numpy.polynomial.hermite.Hermite : Physicist's Hermite series + eval_hermitenorm : evaluate Probabilist's Hermite polynomials + + References + ---------- + .. [AS] Milton Abramowitz and Irene A. Stegun, eds. + Handbook of Mathematical Functions with Formulas, + Graphs, and Mathematical Tables. New York: Dover, 1972. + .. [DLMF] NIST Digital Library of Mathematical Functions, + https://dlmf.nist.gov/18.5.T1 + + """) + +add_newdoc("eval_hermitenorm", + r""" + eval_hermitenorm(n, x, out=None) + + Evaluate probabilist's (normalized) Hermite polynomial at a + point. + + Defined by + + .. math:: + + He_n(x) = (-1)^n e^{x^2/2} \frac{d^n}{dx^n} e^{-x^2/2}; + + :math:`He_n` is a polynomial of degree :math:`n`. See 22.11.8 in + [AS]_ or [DLMF]_ for details. + + Parameters + ---------- + n : array_like + Degree of the polynomial + x : array_like + Points at which to evaluate the Hermite polynomial + out : ndarray, optional + Optional output array for the function values + + Returns + ------- + He : scalar or ndarray + Values of the Hermite polynomial + + See Also + -------- + roots_hermitenorm : roots and quadrature weights of probabilist's + Hermite polynomials + hermitenorm : probabilist's Hermite polynomial object + numpy.polynomial.hermite_e.HermiteE : Probabilist's Hermite series + eval_hermite : evaluate physicist's Hermite polynomials + + References + ---------- + .. [AS] Milton Abramowitz and Irene A. Stegun, eds. + Handbook of Mathematical Functions with Formulas, + Graphs, and Mathematical Tables. New York: Dover, 1972. + .. [DLMF] NIST Digital Library of Mathematical Functions, + https://dlmf.nist.gov/18.5.T1 + + """) + +add_newdoc("expn", + r""" + expn(n, x, out=None) + + Generalized exponential integral En. + + For integer :math:`n \geq 0` and real :math:`x \geq 0` the + generalized exponential integral is defined as [DLMF]_ + + .. math:: + + E_n(x) = x^{n - 1} \int_x^\infty \frac{e^{-t}}{t^n} dt. + + Parameters + ---------- + n : array_like + Non-negative integers + x : array_like + Real argument + out : ndarray, optional + Optional output array for the function results + + Returns + ------- + scalar or ndarray + Values of the generalized exponential integral + + See Also + -------- + exp1 : special case of :math:`E_n` for :math:`n = 1` + expi : related to :math:`E_n` when :math:`n = 1` + + References + ---------- + .. [DLMF] Digital Library of Mathematical Functions, 8.19.2 + https://dlmf.nist.gov/8.19#E2 + + Examples + -------- + >>> import numpy as np + >>> import scipy.special as sc + + Its domain is nonnegative n and x. + + >>> sc.expn(-1, 1.0), sc.expn(1, -1.0) + (nan, nan) + + It has a pole at ``x = 0`` for ``n = 1, 2``; for larger ``n`` it + is equal to ``1 / (n - 1)``. + + >>> sc.expn([0, 1, 2, 3, 4], 0) + array([ inf, inf, 1. , 0.5 , 0.33333333]) + + For n equal to 0 it reduces to ``exp(-x) / x``. + + >>> x = np.array([1, 2, 3, 4]) + >>> sc.expn(0, x) + array([0.36787944, 0.06766764, 0.01659569, 0.00457891]) + >>> np.exp(-x) / x + array([0.36787944, 0.06766764, 0.01659569, 0.00457891]) + + For n equal to 1 it reduces to `exp1`. + + >>> sc.expn(1, x) + array([0.21938393, 0.04890051, 0.01304838, 0.00377935]) + >>> sc.exp1(x) + array([0.21938393, 0.04890051, 0.01304838, 0.00377935]) + + """) + +add_newdoc("fdtr", + r""" + fdtr(dfn, dfd, x, out=None) + + F cumulative distribution function. + + Returns the value of the cumulative distribution function of the + F-distribution, also known as Snedecor's F-distribution or the + Fisher-Snedecor distribution. + + The F-distribution with parameters :math:`d_n` and :math:`d_d` is the + distribution of the random variable, + + .. math:: + X = \frac{U_n/d_n}{U_d/d_d}, + + where :math:`U_n` and :math:`U_d` are random variables distributed + :math:`\chi^2`, with :math:`d_n` and :math:`d_d` degrees of freedom, + respectively. + + Parameters + ---------- + dfn : array_like + First parameter (positive float). + dfd : array_like + Second parameter (positive float). + x : array_like + Argument (nonnegative float). + out : ndarray, optional + Optional output array for the function values + + Returns + ------- + y : scalar or ndarray + The CDF of the F-distribution with parameters `dfn` and `dfd` at `x`. + + See Also + -------- + fdtrc : F distribution survival function + fdtri : F distribution inverse cumulative distribution + scipy.stats.f : F distribution + + Notes + ----- + The regularized incomplete beta function is used, according to the + formula, + + .. math:: + F(d_n, d_d; x) = I_{xd_n/(d_d + xd_n)}(d_n/2, d_d/2). + + Wrapper for a routine from the Boost Math C++ library [1]_. The + F distribution is also available as `scipy.stats.f`. Calling + `fdtr` directly can improve performance compared to the ``cdf`` + method of `scipy.stats.f` (see last example below). + + References + ---------- + .. [1] The Boost Developers. "Boost C++ Libraries". https://www.boost.org/. + + + Examples + -------- + Calculate the function for ``dfn=1`` and ``dfd=2`` at ``x=1``. + + >>> import numpy as np + >>> from scipy.special import fdtr + >>> fdtr(1, 2, 1) + 0.5773502691896258 + + Calculate the function at several points by providing a NumPy array for + `x`. + + >>> x = np.array([0.5, 2., 3.]) + >>> fdtr(1, 2, x) + array([0.4472136 , 0.70710678, 0.77459667]) + + Plot the function for several parameter sets. + + >>> import matplotlib.pyplot as plt + >>> dfn_parameters = [1, 5, 10, 50] + >>> dfd_parameters = [1, 1, 2, 3] + >>> linestyles = ['solid', 'dashed', 'dotted', 'dashdot'] + >>> parameters_list = list(zip(dfn_parameters, dfd_parameters, + ... linestyles)) + >>> x = np.linspace(0, 30, 1000) + >>> fig, ax = plt.subplots() + >>> for parameter_set in parameters_list: + ... dfn, dfd, style = parameter_set + ... fdtr_vals = fdtr(dfn, dfd, x) + ... ax.plot(x, fdtr_vals, label=rf"$d_n={dfn},\, d_d={dfd}$", + ... ls=style) + >>> ax.legend() + >>> ax.set_xlabel("$x$") + >>> ax.set_title("F distribution cumulative distribution function") + >>> plt.show() + + The F distribution is also available as `scipy.stats.f`. Using `fdtr` + directly can be much faster than calling the ``cdf`` method of + `scipy.stats.f`, especially for small arrays or individual values. + To get the same results one must use the following parametrization: + ``stats.f(dfn, dfd).cdf(x)=fdtr(dfn, dfd, x)``. + + >>> from scipy.stats import f + >>> dfn, dfd = 1, 2 + >>> x = 1 + >>> fdtr_res = fdtr(dfn, dfd, x) # this will often be faster than below + >>> f_dist_res = f(dfn, dfd).cdf(x) + >>> fdtr_res == f_dist_res # test that results are equal + True + """) + +add_newdoc("fdtrc", + r""" + fdtrc(dfn, dfd, x, out=None) + + F survival function. + + Returns the complemented F-distribution function (the integral of the + density from `x` to infinity). + + Parameters + ---------- + dfn : array_like + First parameter (positive float). + dfd : array_like + Second parameter (positive float). + x : array_like + Argument (nonnegative float). + out : ndarray, optional + Optional output array for the function values + + Returns + ------- + y : scalar or ndarray + The complemented F-distribution function with parameters `dfn` and + `dfd` at `x`. + + See Also + -------- + fdtr : F distribution cumulative distribution function + fdtri : F distribution inverse cumulative distribution function + scipy.stats.f : F distribution + + Notes + ----- + The regularized incomplete beta function is used, according to the + formula, + + .. math:: + F(d_n, d_d; x) = I_{d_d/(d_d + xd_n)}(d_d/2, d_n/2). + + Wrapper for a routine from the Boost Math C++ library [1]_. The + F distribution is also available as `scipy.stats.f`. Calling + `fdtrc` directly can improve performance compared to the ``sf`` + method of `scipy.stats.f` (see last example below). + + References + ---------- + .. [1] The Boost Developers. "Boost C++ Libraries". https://www.boost.org/. + + Examples + -------- + Calculate the function for ``dfn=1`` and ``dfd=2`` at ``x=1``. + + >>> import numpy as np + >>> from scipy.special import fdtrc + >>> fdtrc(1, 2, 1) + 0.42264973081037427 + + Calculate the function at several points by providing a NumPy array for + `x`. + + >>> x = np.array([0.5, 2., 3.]) + >>> fdtrc(1, 2, x) + array([0.5527864 , 0.29289322, 0.22540333]) + + Plot the function for several parameter sets. + + >>> import matplotlib.pyplot as plt + >>> dfn_parameters = [1, 5, 10, 50] + >>> dfd_parameters = [1, 1, 2, 3] + >>> linestyles = ['solid', 'dashed', 'dotted', 'dashdot'] + >>> parameters_list = list(zip(dfn_parameters, dfd_parameters, + ... linestyles)) + >>> x = np.linspace(0, 30, 1000) + >>> fig, ax = plt.subplots() + >>> for parameter_set in parameters_list: + ... dfn, dfd, style = parameter_set + ... fdtrc_vals = fdtrc(dfn, dfd, x) + ... ax.plot(x, fdtrc_vals, label=rf"$d_n={dfn},\, d_d={dfd}$", + ... ls=style) + >>> ax.legend() + >>> ax.set_xlabel("$x$") + >>> ax.set_title("F distribution survival function") + >>> plt.show() + + The F distribution is also available as `scipy.stats.f`. Using `fdtrc` + directly can be much faster than calling the ``sf`` method of + `scipy.stats.f`, especially for small arrays or individual values. + To get the same results one must use the following parametrization: + ``stats.f(dfn, dfd).sf(x)=fdtrc(dfn, dfd, x)``. + + >>> from scipy.stats import f + >>> dfn, dfd = 1, 2 + >>> x = 1 + >>> fdtrc_res = fdtrc(dfn, dfd, x) # this will often be faster than below + >>> f_dist_res = f(dfn, dfd).sf(x) + >>> f_dist_res == fdtrc_res # test that results are equal + True + """) + +add_newdoc("fdtri", + r""" + fdtri(dfn, dfd, p, out=None) + + The `p`-th quantile of the F-distribution. + + This function is the inverse of the F-distribution CDF, `fdtr`, returning + the `x` such that `fdtr(dfn, dfd, x) = p`. + + Parameters + ---------- + dfn : array_like + First parameter (positive float). + dfd : array_like + Second parameter (positive float). + p : array_like + Cumulative probability, in [0, 1]. + out : ndarray, optional + Optional output array for the function values + + Returns + ------- + x : scalar or ndarray + The quantile corresponding to `p`. + + See Also + -------- + fdtr : F distribution cumulative distribution function + fdtrc : F distribution survival function + scipy.stats.f : F distribution + + Notes + ----- + Wrapper for a routine from the Boost Math C++ library [1]_. The + F distribution is also available as `scipy.stats.f`. Calling + `fdtri` directly can improve performance compared to the ``ppf`` + method of `scipy.stats.f` (see last example below). + + References + ---------- + .. [1] The Boost Developers. "Boost C++ Libraries". https://www.boost.org/. + + Examples + -------- + `fdtri` represents the inverse of the F distribution CDF which is + available as `fdtr`. Here, we calculate the CDF for ``df1=1``, ``df2=2`` + at ``x=3``. `fdtri` then returns ``3`` given the same values for `df1`, + `df2` and the computed CDF value. + + >>> import numpy as np + >>> from scipy.special import fdtri, fdtr + >>> df1, df2 = 1, 2 + >>> x = 3 + >>> cdf_value = fdtr(df1, df2, x) + >>> fdtri(df1, df2, cdf_value) + 3.000000000000006 + + Calculate the function at several points by providing a NumPy array for + `x`. + + >>> x = np.array([0.1, 0.4, 0.7]) + >>> fdtri(1, 2, x) + array([0.02020202, 0.38095238, 1.92156863]) + + Plot the function for several parameter sets. + + >>> import matplotlib.pyplot as plt + >>> dfn_parameters = [50, 10, 1, 50] + >>> dfd_parameters = [0.5, 1, 1, 5] + >>> linestyles = ['solid', 'dashed', 'dotted', 'dashdot'] + >>> parameters_list = list(zip(dfn_parameters, dfd_parameters, + ... linestyles)) + >>> x = np.linspace(0, 1, 1000) + >>> fig, ax = plt.subplots() + >>> for parameter_set in parameters_list: + ... dfn, dfd, style = parameter_set + ... fdtri_vals = fdtri(dfn, dfd, x) + ... ax.plot(x, fdtri_vals, label=rf"$d_n={dfn},\, d_d={dfd}$", + ... ls=style) + >>> ax.legend() + >>> ax.set_xlabel("$x$") + >>> title = "F distribution inverse cumulative distribution function" + >>> ax.set_title(title) + >>> ax.set_ylim(0, 30) + >>> plt.show() + + The F distribution is also available as `scipy.stats.f`. Using `fdtri` + directly can be much faster than calling the ``ppf`` method of + `scipy.stats.f`, especially for small arrays or individual values. + To get the same results one must use the following parametrization: + ``stats.f(dfn, dfd).ppf(x)=fdtri(dfn, dfd, x)``. + + >>> from scipy.stats import f + >>> dfn, dfd = 1, 2 + >>> x = 0.7 + >>> fdtri_res = fdtri(dfn, dfd, x) # this will often be faster than below + >>> f_dist_res = f(dfn, dfd).ppf(x) + >>> f_dist_res == fdtri_res # test that results are equal + True + """) + +add_newdoc("fdtridfd", + """ + fdtridfd(dfn, p, x, out=None) + + Inverse to `fdtr` vs dfd. + + Finds the F density argument dfd such that ``fdtr(dfn, dfd, x) == p``. + + Parameters + ---------- + dfn : array_like + First parameter (positive float). + p : array_like + Cumulative probability, in [0, 1]. + x : array_like + Argument (nonnegative float). + out : ndarray, optional + Optional output array for the function values + + Returns + ------- + dfd : scalar or ndarray + `dfd` such that ``fdtr(dfn, dfd, x) == p``. + + See Also + -------- + fdtr : F distribution cumulative distribution function + fdtrc : F distribution survival function + fdtri : F distribution quantile function + scipy.stats.f : F distribution + + Examples + -------- + Compute the F distribution cumulative distribution function for one + parameter set. + + >>> from scipy.special import fdtridfd, fdtr + >>> dfn, dfd, x = 10, 5, 2 + >>> cdf_value = fdtr(dfn, dfd, x) + >>> cdf_value + 0.7700248806501017 + + Verify that `fdtridfd` recovers the original value for `dfd`: + + >>> fdtridfd(dfn, cdf_value, x) + 5.0 + """) + +''' +commented out as fdtridfn seems to have bugs and is not in functions.json +see: https://github.com/scipy/scipy/pull/15622#discussion_r811440983 + +add_newdoc( + "fdtridfn", + """ + fdtridfn(p, dfd, x, out=None) + + Inverse to `fdtr` vs dfn + + finds the F density argument dfn such that ``fdtr(dfn, dfd, x) == p``. + + + Parameters + ---------- + p : array_like + Cumulative probability, in [0, 1]. + dfd : array_like + Second parameter (positive float). + x : array_like + Argument (nonnegative float). + out : ndarray, optional + Optional output array for the function values + + Returns + ------- + dfn : scalar or ndarray + `dfn` such that ``fdtr(dfn, dfd, x) == p``. + + See Also + -------- + fdtr, fdtrc, fdtri, fdtridfd + + + """) +''' + +add_newdoc("gdtr", + r""" + gdtr(a, b, x, out=None) + + Gamma distribution cumulative distribution function. + + Returns the integral from zero to `x` of the gamma probability density + function, + + .. math:: + + F(x) = \int_0^x \frac{a^b}{\Gamma(b)} t^{b-1} e^{-at}\,dt, + + where :math:`\Gamma` is the gamma function. + + Parameters + ---------- + a : array_like + The rate parameter of the gamma distribution, sometimes denoted + :math:`\beta` (float). It is also the reciprocal of the scale + parameter :math:`\theta`. + b : array_like + The shape parameter of the gamma distribution, sometimes denoted + :math:`\alpha` (float). + x : array_like + The quantile (upper limit of integration; float). + out : ndarray, optional + Optional output array for the function values + + Returns + ------- + scalar or ndarray + The CDF of the gamma distribution with parameters `a` and `b` + evaluated at `x`. + + See Also + -------- + gdtrc : 1 - CDF of the gamma distribution. + scipy.stats.gamma: Gamma distribution + + Notes + ----- + The evaluation is carried out using the relation to the incomplete gamma + integral (regularized gamma function). + + Wrapper for the Cephes [1]_ routine `gdtr`. Calling `gdtr` directly can + improve performance compared to the ``cdf`` method of `scipy.stats.gamma` + (see last example below). + + References + ---------- + .. [1] Cephes Mathematical Functions Library, + https://netlib.org/cephes/ + + Examples + -------- + Compute the function for ``a=1``, ``b=2`` at ``x=5``. + + >>> import numpy as np + >>> from scipy.special import gdtr + >>> import matplotlib.pyplot as plt + >>> gdtr(1., 2., 5.) + 0.9595723180054873 + + Compute the function for ``a=1`` and ``b=2`` at several points by + providing a NumPy array for `x`. + + >>> xvalues = np.array([1., 2., 3., 4]) + >>> gdtr(1., 1., xvalues) + array([0.63212056, 0.86466472, 0.95021293, 0.98168436]) + + `gdtr` can evaluate different parameter sets by providing arrays with + broadcasting compatible shapes for `a`, `b` and `x`. Here we compute the + function for three different `a` at four positions `x` and ``b=3``, + resulting in a 3x4 array. + + >>> a = np.array([[0.5], [1.5], [2.5]]) + >>> x = np.array([1., 2., 3., 4]) + >>> a.shape, x.shape + ((3, 1), (4,)) + + >>> gdtr(a, 3., x) + array([[0.01438768, 0.0803014 , 0.19115317, 0.32332358], + [0.19115317, 0.57680992, 0.82642193, 0.9380312 ], + [0.45618688, 0.87534798, 0.97974328, 0.9972306 ]]) + + Plot the function for four different parameter sets. + + >>> a_parameters = [0.3, 1, 2, 6] + >>> b_parameters = [2, 10, 15, 20] + >>> linestyles = ['solid', 'dashed', 'dotted', 'dashdot'] + >>> parameters_list = list(zip(a_parameters, b_parameters, linestyles)) + >>> x = np.linspace(0, 30, 1000) + >>> fig, ax = plt.subplots() + >>> for parameter_set in parameters_list: + ... a, b, style = parameter_set + ... gdtr_vals = gdtr(a, b, x) + ... ax.plot(x, gdtr_vals, label=fr"$a= {a},\, b={b}$", ls=style) + >>> ax.legend() + >>> ax.set_xlabel("$x$") + >>> ax.set_title("Gamma distribution cumulative distribution function") + >>> plt.show() + + The gamma distribution is also available as `scipy.stats.gamma`. Using + `gdtr` directly can be much faster than calling the ``cdf`` method of + `scipy.stats.gamma`, especially for small arrays or individual values. + To get the same results one must use the following parametrization: + ``stats.gamma(b, scale=1/a).cdf(x)=gdtr(a, b, x)``. + + >>> from scipy.stats import gamma + >>> a = 2. + >>> b = 3 + >>> x = 1. + >>> gdtr_result = gdtr(a, b, x) # this will often be faster than below + >>> gamma_dist_result = gamma(b, scale=1/a).cdf(x) + >>> gdtr_result == gamma_dist_result # test that results are equal + True + """) + +add_newdoc("gdtrc", + r""" + gdtrc(a, b, x, out=None) + + Gamma distribution survival function. + + Integral from `x` to infinity of the gamma probability density function, + + .. math:: + + S(x) = \int_x^\infty \frac{a^b}{\Gamma(b)} t^{b-1} e^{-at}\,dt, + + where :math:`\Gamma` is the gamma function. + + Parameters + ---------- + a : array_like + The rate parameter of the gamma distribution, sometimes denoted + :math:`\beta` (float). It is also the reciprocal of the scale + parameter :math:`\theta`. + b : array_like + The shape parameter of the gamma distribution, sometimes denoted + :math:`\alpha` (float). + x : array_like + The quantile (lower limit of integration; float). + out : ndarray, optional + Optional output array for the function values + + Returns + ------- + scalar or ndarray + The survival function of the gamma distribution with parameters `a` + and `b` evaluated at `x`. + + See Also + -------- + gdtr: Gamma distribution cumulative distribution function + scipy.stats.gamma: Gamma distribution + gdtrix + + Notes + ----- + The evaluation is carried out using the relation to the incomplete gamma + integral (regularized gamma function). + + Wrapper for the Cephes [1]_ routine `gdtrc`. Calling `gdtrc` directly can + improve performance compared to the ``sf`` method of `scipy.stats.gamma` + (see last example below). + + References + ---------- + .. [1] Cephes Mathematical Functions Library, + https://netlib.org/cephes/ + + Examples + -------- + Compute the function for ``a=1`` and ``b=2`` at ``x=5``. + + >>> import numpy as np + >>> from scipy.special import gdtrc + >>> import matplotlib.pyplot as plt + >>> gdtrc(1., 2., 5.) + 0.04042768199451279 + + Compute the function for ``a=1``, ``b=2`` at several points by providing + a NumPy array for `x`. + + >>> xvalues = np.array([1., 2., 3., 4]) + >>> gdtrc(1., 1., xvalues) + array([0.36787944, 0.13533528, 0.04978707, 0.01831564]) + + `gdtrc` can evaluate different parameter sets by providing arrays with + broadcasting compatible shapes for `a`, `b` and `x`. Here we compute the + function for three different `a` at four positions `x` and ``b=3``, + resulting in a 3x4 array. + + >>> a = np.array([[0.5], [1.5], [2.5]]) + >>> x = np.array([1., 2., 3., 4]) + >>> a.shape, x.shape + ((3, 1), (4,)) + + >>> gdtrc(a, 3., x) + array([[0.98561232, 0.9196986 , 0.80884683, 0.67667642], + [0.80884683, 0.42319008, 0.17357807, 0.0619688 ], + [0.54381312, 0.12465202, 0.02025672, 0.0027694 ]]) + + Plot the function for four different parameter sets. + + >>> a_parameters = [0.3, 1, 2, 6] + >>> b_parameters = [2, 10, 15, 20] + >>> linestyles = ['solid', 'dashed', 'dotted', 'dashdot'] + >>> parameters_list = list(zip(a_parameters, b_parameters, linestyles)) + >>> x = np.linspace(0, 30, 1000) + >>> fig, ax = plt.subplots() + >>> for parameter_set in parameters_list: + ... a, b, style = parameter_set + ... gdtrc_vals = gdtrc(a, b, x) + ... ax.plot(x, gdtrc_vals, label=fr"$a= {a},\, b={b}$", ls=style) + >>> ax.legend() + >>> ax.set_xlabel("$x$") + >>> ax.set_title("Gamma distribution survival function") + >>> plt.show() + + The gamma distribution is also available as `scipy.stats.gamma`. + Using `gdtrc` directly can be much faster than calling the ``sf`` method + of `scipy.stats.gamma`, especially for small arrays or individual + values. To get the same results one must use the following parametrization: + ``stats.gamma(b, scale=1/a).sf(x)=gdtrc(a, b, x)``. + + >>> from scipy.stats import gamma + >>> a = 2 + >>> b = 3 + >>> x = 1. + >>> gdtrc_result = gdtrc(a, b, x) # this will often be faster than below + >>> gamma_dist_result = gamma(b, scale=1/a).sf(x) + >>> gdtrc_result == gamma_dist_result # test that results are equal + True + """) + +add_newdoc("gdtria", + """ + gdtria(p, b, x, out=None) + + Inverse of `gdtr` vs a. + + Returns the inverse with respect to the parameter `a` of ``p = + gdtr(a, b, x)``, the cumulative distribution function of the gamma + distribution. + + Parameters + ---------- + p : array_like + Probability values. + b : array_like + `b` parameter values of `gdtr(a, b, x)`. `b` is the "shape" parameter + of the gamma distribution. + x : array_like + Nonnegative real values, from the domain of the gamma distribution. + out : ndarray, optional + If a fourth argument is given, it must be a numpy.ndarray whose size + matches the broadcast result of `a`, `b` and `x`. `out` is then the + array returned by the function. + + Returns + ------- + a : scalar or ndarray + Values of the `a` parameter such that ``p = gdtr(a, b, x)`. ``1/a`` + is the "scale" parameter of the gamma distribution. + + See Also + -------- + gdtr : CDF of the gamma distribution. + gdtrib : Inverse with respect to `b` of `gdtr(a, b, x)`. + gdtrix : Inverse with respect to `x` of `gdtr(a, b, x)`. + gammaincinv : Inverse of the incomplete regularized gamma function. + + Notes + ----- + `gdtria` is implemented in terms of the incomplete gamma inverse as + ``gdtria(p, b, x) = gammaincinv(b, p)/x``. + + Examples + -------- + First evaluate `gdtr`. + + >>> from scipy.special import gdtr, gdtria + >>> p = gdtr(1.2, 3.4, 5.6) + >>> print(p) + 0.94378087442 + + Verify the inverse. + + >>> gdtria(p, 3.4, 5.6) + 1.2 + """) + +add_newdoc("gdtrib", + """ + gdtrib(a, p, x, out=None) + + Inverse of `gdtr` vs b. + + Returns the inverse with respect to the parameter `b` of ``p = + gdtr(a, b, x)``, the cumulative distribution function of the gamma + distribution. + + Parameters + ---------- + a : array_like + `a` parameter values of ``gdtr(a, b, x)`. ``1/a`` is the "scale" + parameter of the gamma distribution. + p : array_like + Probability values. + x : array_like + Nonnegative real values, from the domain of the gamma distribution. + out : ndarray, optional + If a fourth argument is given, it must be a numpy.ndarray whose size + matches the broadcast result of `a`, `b` and `x`. `out` is then the + array returned by the function. + + Returns + ------- + b : scalar or ndarray + Values of the `b` parameter such that `p = gdtr(a, b, x)`. `b` is + the "shape" parameter of the gamma distribution. + + See Also + -------- + gdtr : CDF of the gamma distribution. + gdtria : Inverse with respect to `a` of `gdtr(a, b, x)`. + gdtrix : Inverse with respect to `x` of `gdtr(a, b, x)`. + + Notes + ----- + + The cumulative distribution function `p` is computed using the Cephes [1]_ + routines `igam` and `igamc`. Computation of `b` involves a search for a value + that produces the desired value of `p` using Chandrupatla's bracketing + root finding algorithm [2]_. + + Note that there are some edge cases where `gdtrib` is extended by taking + limits where they are uniquely defined. In particular + ``x == 0`` with ``p > 0`` and ``p == 0`` with ``x > 0``. + For these edge cases, a numerical result will be returned for + ``gdtrib(a, p, x)`` even though ``gdtr(a, gdtrib(a, p, x), x)`` is + undefined. + + References + ---------- + .. [1] Cephes Mathematical Functions Library, + https://netlib.org/cephes/ + .. [2] Chandrupatla, Tirupathi R. + "A new hybrid quadratic/bisection algorithm for finding the zero of a + nonlinear function without using derivatives". + Advances in Engineering Software, 28(3), 145-149. + :doi:`10.1016/s0965-9978(96)00051-8`. + + Examples + -------- + First evaluate `gdtr`. + + >>> from scipy.special import gdtr, gdtrib + >>> p = gdtr(1.2, 3.4, 5.6) + >>> print(p) + 0.94378087442 + + Verify the inverse. + + >>> gdtrib(1.2, p, 5.6) + 3.3999999999999995 + """) + +add_newdoc("gdtrix", + """ + gdtrix(a, b, p, out=None) + + Inverse of `gdtr` vs x. + + Returns the inverse with respect to the parameter `x` of ``p = + gdtr(a, b, x)``, the cumulative distribution function of the gamma + distribution. This is also known as the pth quantile of the + distribution. + + Parameters + ---------- + a : array_like + `a` parameter values of ``gdtr(a, b, x)``. ``1/a`` is the "scale" + parameter of the gamma distribution. + b : array_like + `b` parameter values of ``gdtr(a, b, x)``. `b` is the "shape" parameter + of the gamma distribution. + p : array_like + Probability values. + out : ndarray, optional + If a fourth argument is given, it must be a numpy.ndarray whose size + matches the broadcast result of `a`, `b` and `x`. `out` is then the + array returned by the function. + + Returns + ------- + x : scalar or ndarray + Values of the `x` parameter such that `p = gdtr(a, b, x)`. + + See Also + -------- + gdtr : CDF of the gamma distribution. + gdtria : Inverse with respect to `a` of ``gdtr(a, b, x)``. + gdtrib : Inverse with respect to `b` of ``gdtr(a, b, x)``. + gammaincinv : Inverse of the incomplete regularized gamma function. + + Notes + ----- + `gdtrix` is implemented in terms of the incomplete gamma inverse as + ``gdtrix(a, b, p) = gammaincinv(b, p)/a``. + + Examples + -------- + First evaluate `gdtr`. + + >>> from scipy.special import gdtr, gdtrix + >>> p = gdtr(1.2, 3.4, 5.6) + >>> print(p) + 0.94378087442 + + Verify the inverse. + + >>> gdtrix(1.2, 3.4, p) + 5.6 + """) + + + +add_newdoc("huber", + r""" + huber(delta, r, out=None) + + Huber loss function. + + .. math:: \text{huber}(\delta, r) = \begin{cases} \infty & \delta < 0 \\ + \frac{1}{2}r^2 & 0 \le \delta, | r | \le \delta \\ + \delta ( |r| - \frac{1}{2}\delta ) & \text{otherwise} \end{cases} + + Parameters + ---------- + delta : ndarray + Input array, indicating the quadratic vs. linear loss changepoint. + r : ndarray + Input array, possibly representing residuals. + out : ndarray, optional + Optional output array for the function values + + Returns + ------- + scalar or ndarray + The computed Huber loss function values. + + See Also + -------- + pseudo_huber : smooth approximation of this function + + Notes + ----- + `huber` is useful as a loss function in robust statistics or machine + learning to reduce the influence of outliers as compared to the common + squared error loss, residuals with a magnitude higher than `delta` are + not squared [1]_. + + Typically, `r` represents residuals, the difference + between a model prediction and data. Then, for :math:`|r|\leq\delta`, + `huber` resembles the squared error and for :math:`|r|>\delta` the + absolute error. This way, the Huber loss often achieves + a fast convergence in model fitting for small residuals like the squared + error loss function and still reduces the influence of outliers + (:math:`|r|>\delta`) like the absolute error loss. As :math:`\delta` is + the cutoff between squared and absolute error regimes, it has + to be tuned carefully for each problem. `huber` is also + convex, making it suitable for gradient based optimization. + + .. versionadded:: 0.15.0 + + References + ---------- + .. [1] Peter Huber. "Robust Estimation of a Location Parameter", + 1964. Annals of Statistics. 53 (1): 73 - 101. + + Examples + -------- + Import all necessary modules. + + >>> import numpy as np + >>> from scipy.special import huber + >>> import matplotlib.pyplot as plt + + Compute the function for ``delta=1`` at ``r=2`` + + >>> huber(1., 2.) + 1.5 + + Compute the function for different `delta` by providing a NumPy array or + list for `delta`. + + >>> huber([1., 3., 5.], 4.) + array([3.5, 7.5, 8. ]) + + Compute the function at different points by providing a NumPy array or + list for `r`. + + >>> huber(2., np.array([1., 1.5, 3.])) + array([0.5 , 1.125, 4. ]) + + The function can be calculated for different `delta` and `r` by + providing arrays for both with compatible shapes for broadcasting. + + >>> r = np.array([1., 2.5, 8., 10.]) + >>> deltas = np.array([[1.], [5.], [9.]]) + >>> print(r.shape, deltas.shape) + (4,) (3, 1) + + >>> huber(deltas, r) + array([[ 0.5 , 2. , 7.5 , 9.5 ], + [ 0.5 , 3.125, 27.5 , 37.5 ], + [ 0.5 , 3.125, 32. , 49.5 ]]) + + Plot the function for different `delta`. + + >>> x = np.linspace(-4, 4, 500) + >>> deltas = [1, 2, 3] + >>> linestyles = ["dashed", "dotted", "dashdot"] + >>> fig, ax = plt.subplots() + >>> combined_plot_parameters = list(zip(deltas, linestyles)) + >>> for delta, style in combined_plot_parameters: + ... ax.plot(x, huber(delta, x), label=fr"$\delta={delta}$", ls=style) + >>> ax.legend(loc="upper center") + >>> ax.set_xlabel("$x$") + >>> ax.set_title(r"Huber loss function $h_{\delta}(x)$") + >>> ax.set_xlim(-4, 4) + >>> ax.set_ylim(0, 8) + >>> plt.show() + """) + +add_newdoc("hyp0f1", + r""" + hyp0f1(v, z, out=None) + + Confluent hypergeometric limit function 0F1. + + Parameters + ---------- + v : array_like + Real-valued parameter + z : array_like + Real- or complex-valued argument + out : ndarray, optional + Optional output array for the function results + + Returns + ------- + scalar or ndarray + The confluent hypergeometric limit function + + Notes + ----- + This function is defined as: + + .. math:: _0F_1(v, z) = \sum_{k=0}^{\infty}\frac{z^k}{(v)_k k!}. + + It's also the limit as :math:`q \to \infty` of :math:`_1F_1(q; v; z/q)`, + and satisfies the differential equation :math:`f''(z) + vf'(z) = + f(z)`. See [1]_ for more information. + + References + ---------- + .. [1] Wolfram MathWorld, "Confluent Hypergeometric Limit Function", + https://mathworld.wolfram.com/ConfluentHypergeometricLimitFunction.html + + Examples + -------- + >>> import numpy as np + >>> import scipy.special as sc + + It is one when `z` is zero. + + >>> sc.hyp0f1(1, 0) + 1.0 + + It is the limit of the confluent hypergeometric function as `q` + goes to infinity. + + >>> q = np.array([1, 10, 100, 1000]) + >>> v = 1 + >>> z = 1 + >>> sc.hyp1f1(q, v, z / q) + array([2.71828183, 2.31481985, 2.28303778, 2.27992985]) + >>> sc.hyp0f1(v, z) + 2.2795853023360673 + + It is related to Bessel functions. + + >>> n = 1 + >>> x = np.linspace(0, 1, 5) + >>> sc.jv(n, x) + array([0. , 0.12402598, 0.24226846, 0.3492436 , 0.44005059]) + >>> (0.5 * x)**n / sc.factorial(n) * sc.hyp0f1(n + 1, -0.25 * x**2) + array([0. , 0.12402598, 0.24226846, 0.3492436 , 0.44005059]) + + """) + +add_newdoc("hyp1f1", + r""" + hyp1f1(a, b, x, out=None) + + Confluent hypergeometric function 1F1. + + The confluent hypergeometric function is defined by the series + + .. math:: + + {}_1F_1(a; b; x) = \sum_{k = 0}^\infty \frac{(a)_k}{(b)_k k!} x^k. + + See [DLMF]_ for more details. Here :math:`(\cdot)_k` is the + Pochhammer symbol; see `poch`. + + Parameters + ---------- + a, b : array_like + Real parameters + x : array_like + Real or complex argument + out : ndarray, optional + Optional output array for the function results + + Returns + ------- + scalar or ndarray + Values of the confluent hypergeometric function + + See Also + -------- + hyperu : another confluent hypergeometric function + hyp0f1 : confluent hypergeometric limit function + hyp2f1 : Gaussian hypergeometric function + + Notes + ----- + For real values, this function uses the ``hyp1f1`` routine from the C++ Boost + library [2]_, for complex values a C translation of the specfun + Fortran library [3]_. + + References + ---------- + .. [DLMF] NIST Digital Library of Mathematical Functions + https://dlmf.nist.gov/13.2#E2 + .. [2] The Boost Developers. "Boost C++ Libraries". https://www.boost.org/. + .. [3] S. Zhang and J.M. Jin, "Computation of Special Functions", Wiley 1996. + + Examples + -------- + >>> import numpy as np + >>> import scipy.special as sc + + It is one when `x` is zero: + + >>> sc.hyp1f1(0.5, 0.5, 0) + 1.0 + + It is singular when `b` is a nonpositive integer. + + >>> sc.hyp1f1(0.5, -1, 0) + inf + + It is a polynomial when `a` is a nonpositive integer. + + >>> a, b, x = -1, 0.5, np.array([1.0, 2.0, 3.0, 4.0]) + >>> sc.hyp1f1(a, b, x) + array([-1., -3., -5., -7.]) + >>> 1 + (a / b) * x + array([-1., -3., -5., -7.]) + + It reduces to the exponential function when ``a = b``. + + >>> sc.hyp1f1(2, 2, [1, 2, 3, 4]) + array([ 2.71828183, 7.3890561 , 20.08553692, 54.59815003]) + >>> np.exp([1, 2, 3, 4]) + array([ 2.71828183, 7.3890561 , 20.08553692, 54.59815003]) + + """) + +add_newdoc("hyperu", + r""" + hyperu(a, b, x, out=None) + + Confluent hypergeometric function U. + + It is defined as the solution to the equation + + .. math:: + + x \frac{d^2w}{dx^2} + (b - x) \frac{dw}{dx} - aw = 0 + + which satisfies the property + + .. math:: + + U(a, b, x) \sim x^{-a} + + as :math:`x \to \infty`. See [DLMF]_ for more details. + + Parameters + ---------- + a, b : array_like + Real-valued parameters + x : array_like + Real-valued argument + out : ndarray, optional + Optional output array for the function values + + Returns + ------- + scalar or ndarray + Values of `U` + + References + ---------- + .. [DLMF] NIST Digital Library of Mathematics Functions + https://dlmf.nist.gov/13.2#E6 + + Examples + -------- + >>> import numpy as np + >>> import scipy.special as sc + + It has a branch cut along the negative `x` axis. + + >>> x = np.linspace(-0.1, -10, 5) + >>> sc.hyperu(1, 1, x) + array([nan, nan, nan, nan, nan]) + + It approaches zero as `x` goes to infinity. + + >>> x = np.array([1, 10, 100]) + >>> sc.hyperu(1, 1, x) + array([0.59634736, 0.09156333, 0.00990194]) + + It satisfies Kummer's transformation. + + >>> a, b, x = 2, 1, 1 + >>> sc.hyperu(a, b, x) + 0.1926947246463881 + >>> x**(1 - b) * sc.hyperu(a - b + 1, 2 - b, x) + 0.1926947246463881 + + """) + +add_newdoc("_igam_fac", + """ + Internal function, do not use. + """) + +add_newdoc("kl_div", + r""" + kl_div(x, y, out=None) + + Elementwise function for computing Kullback-Leibler divergence. + + .. math:: + + \mathrm{kl\_div}(x, y) = + \begin{cases} + x \log(x / y) - x + y & x > 0, y > 0 \\ + y & x = 0, y \ge 0 \\ + \infty & \text{otherwise} + \end{cases} + + Parameters + ---------- + x, y : array_like + Real arguments + out : ndarray, optional + Optional output array for the function results + + Returns + ------- + scalar or ndarray + Values of the Kullback-Liebler divergence. + + See Also + -------- + entr, rel_entr, scipy.stats.entropy + + Notes + ----- + .. versionadded:: 0.15.0 + + This function is non-negative and is jointly convex in `x` and `y`. + + The origin of this function is in convex programming; see [1]_ for + details. This is why the function contains the extra :math:`-x + + y` terms over what might be expected from the Kullback-Leibler + divergence. For a version of the function without the extra terms, + see `rel_entr`. + + References + ---------- + .. [1] Boyd, Stephen and Lieven Vandenberghe. *Convex optimization*. + Cambridge University Press, 2004. + :doi:`10.1017/CBO9780511804441`. + + """) + +add_newdoc("kn", + r""" + kn(n, x, out=None) + + Modified Bessel function of the second kind of integer order `n`. + + Returns the modified Bessel function of the second kind for integer order + `n` at real `z`. + + These are also sometimes called functions of the third kind, Basset + functions, or Macdonald functions. + + Parameters + ---------- + n : array_like of int + Order of Bessel functions (floats will truncate with a warning) + x : array_like of float + Argument at which to evaluate the Bessel functions + out : ndarray, optional + Optional output array for the function results. + + Returns + ------- + scalar or ndarray + Value of the Modified Bessel function of the second kind, + :math:`K_n(x)`. + + See Also + -------- + kv : Same function, but accepts real order and complex argument + kvp : Derivative of this function + + Notes + ----- + Wrapper for AMOS [1]_ routine `zbesk`. For a discussion of the + algorithm used, see [2]_ and the references therein. + + References + ---------- + .. [1] Donald E. Amos, "AMOS, A Portable Package for Bessel Functions + of a Complex Argument and Nonnegative Order", + https://netlib.org/amos/ + .. [2] Donald E. Amos, "Algorithm 644: A portable package for Bessel + functions of a complex argument and nonnegative order", ACM + TOMS Vol. 12 Issue 3, Sept. 1986, p. 265. + + Examples + -------- + Plot the function of several orders for real input: + + >>> import numpy as np + >>> from scipy.special import kn + >>> import matplotlib.pyplot as plt + >>> x = np.linspace(0, 5, 1000) + >>> for N in range(6): + ... plt.plot(x, kn(N, x), label='$K_{}(x)$'.format(N)) + >>> plt.ylim(0, 10) + >>> plt.legend() + >>> plt.title(r'Modified Bessel function of the second kind $K_n(x)$') + >>> plt.show() + + Calculate for a single value at multiple orders: + + >>> kn([4, 5, 6], 1) + array([ 44.23241585, 360.9605896 , 3653.83831186]) + """) + +add_newdoc("kolmogi", + """ + kolmogi(p, out=None) + + Inverse Survival Function of Kolmogorov distribution. + + It is the inverse function to `kolmogorov`. + Returns y such that ``kolmogorov(y) == p``. + + Parameters + ---------- + p : float array_like + Probability + out : ndarray, optional + Optional output array for the function results + + Returns + ------- + scalar or ndarray + The value(s) of kolmogi(p) + + See Also + -------- + kolmogorov : The Survival Function for the distribution + scipy.stats.kstwobign : Provides the functionality as a continuous distribution + smirnov, smirnovi : Functions for the one-sided distribution + + Notes + ----- + `kolmogorov` is used by `stats.kstest` in the application of the + Kolmogorov-Smirnov Goodness of Fit test. For historical reasons this + function is exposed in `scpy.special`, but the recommended way to achieve + the most accurate CDF/SF/PDF/PPF/ISF computations is to use the + `stats.kstwobign` distribution. + + Examples + -------- + >>> from scipy.special import kolmogi + >>> kolmogi([0, 0.1, 0.25, 0.5, 0.75, 0.9, 1.0]) + array([ inf, 1.22384787, 1.01918472, 0.82757356, 0.67644769, + 0.57117327, 0. ]) + + """) + +add_newdoc("kolmogorov", + r""" + kolmogorov(y, out=None) + + Complementary cumulative distribution (Survival Function) function of + Kolmogorov distribution. + + Returns the complementary cumulative distribution function of + Kolmogorov's limiting distribution (``D_n*\sqrt(n)`` as n goes to infinity) + of a two-sided test for equality between an empirical and a theoretical + distribution. It is equal to the (limit as n->infinity of the) + probability that ``sqrt(n) * max absolute deviation > y``. + + Parameters + ---------- + y : float array_like + Absolute deviation between the Empirical CDF (ECDF) and the target CDF, + multiplied by sqrt(n). + out : ndarray, optional + Optional output array for the function results + + Returns + ------- + scalar or ndarray + The value(s) of kolmogorov(y) + + See Also + -------- + kolmogi : The Inverse Survival Function for the distribution + scipy.stats.kstwobign : Provides the functionality as a continuous distribution + smirnov, smirnovi : Functions for the one-sided distribution + + Notes + ----- + `kolmogorov` is used by `stats.kstest` in the application of the + Kolmogorov-Smirnov Goodness of Fit test. For historical reasons this + function is exposed in `scpy.special`, but the recommended way to achieve + the most accurate CDF/SF/PDF/PPF/ISF computations is to use the + `stats.kstwobign` distribution. + + Examples + -------- + Show the probability of a gap at least as big as 0, 0.5 and 1.0. + + >>> import numpy as np + >>> from scipy.special import kolmogorov + >>> from scipy.stats import kstwobign + >>> kolmogorov([0, 0.5, 1.0]) + array([ 1. , 0.96394524, 0.26999967]) + + Compare a sample of size 1000 drawn from a Laplace(0, 1) distribution against + the target distribution, a Normal(0, 1) distribution. + + >>> from scipy.stats import norm, laplace + >>> rng = np.random.default_rng() + >>> n = 1000 + >>> lap01 = laplace(0, 1) + >>> x = np.sort(lap01.rvs(n, random_state=rng)) + >>> np.mean(x), np.std(x) + (-0.05841730131499543, 1.3968109101997568) + + Construct the Empirical CDF and the K-S statistic Dn. + + >>> target = norm(0,1) # Normal mean 0, stddev 1 + >>> cdfs = target.cdf(x) + >>> ecdfs = np.arange(n+1, dtype=float)/n + >>> gaps = np.column_stack([cdfs - ecdfs[:n], ecdfs[1:] - cdfs]) + >>> Dn = np.max(gaps) + >>> Kn = np.sqrt(n) * Dn + >>> print('Dn=%f, sqrt(n)*Dn=%f' % (Dn, Kn)) + Dn=0.043363, sqrt(n)*Dn=1.371265 + >>> print(chr(10).join(['For a sample of size n drawn from a N(0, 1) distribution:', + ... ' the approximate Kolmogorov probability that sqrt(n)*Dn>=%f is %f' % + ... (Kn, kolmogorov(Kn)), + ... ' the approximate Kolmogorov probability that sqrt(n)*Dn<=%f is %f' % + ... (Kn, kstwobign.cdf(Kn))])) + For a sample of size n drawn from a N(0, 1) distribution: + the approximate Kolmogorov probability that sqrt(n)*Dn>=1.371265 is 0.046533 + the approximate Kolmogorov probability that sqrt(n)*Dn<=1.371265 is 0.953467 + + Plot the Empirical CDF against the target N(0, 1) CDF. + + >>> import matplotlib.pyplot as plt + >>> plt.step(np.concatenate([[-3], x]), ecdfs, where='post', label='Empirical CDF') + >>> x3 = np.linspace(-3, 3, 100) + >>> plt.plot(x3, target.cdf(x3), label='CDF for N(0, 1)') + >>> plt.ylim([0, 1]); plt.grid(True); plt.legend(); + >>> # Add vertical lines marking Dn+ and Dn- + >>> iminus, iplus = np.argmax(gaps, axis=0) + >>> plt.vlines([x[iminus]], ecdfs[iminus], cdfs[iminus], + ... color='r', linestyle='dashed', lw=4) + >>> plt.vlines([x[iplus]], cdfs[iplus], ecdfs[iplus+1], + ... color='r', linestyle='dashed', lw=4) + >>> plt.show() + """) + +add_newdoc("_kolmogc", + r""" + Internal function, do not use. + """) + +add_newdoc("_kolmogci", + r""" + Internal function, do not use. + """) + +add_newdoc("_kolmogp", + r""" + Internal function, do not use. + """) + +add_newdoc("_lanczos_sum_expg_scaled", + """ + Internal function, do not use. + """) + +add_newdoc( + "_landau_pdf", + """ + _landau_pdf(x, loc, scale) + + Probability density function of the Landau distribution. + + Parameters + ---------- + x : array_like + Real-valued argument + loc : array_like + Real-valued distribution location + scale : array_like + Positive, real-valued distribution scale + + Returns + ------- + scalar or ndarray + """) + +add_newdoc( + "_landau_cdf", + """ + _landau_cdf(x, loc, scale) + + Cumulative distribution function of the Landau distribution. + + Parameters + ---------- + x : array_like + Real-valued argument + loc : array_like + Real-valued distribution location + scale : array_like + Positive, real-valued distribution scale + + Returns + ------- + scalar or ndarray + """) + +add_newdoc( + "_landau_sf", + """ + _landau_sf(x, loc, scale) + + Survival function of the Landau distribution. + + Parameters + ---------- + x : array_like + Real-valued argument + loc : array_like + Real-valued distribution location + scale : array_like + Positive, real-valued distribution scale + + Returns + ------- + scalar or ndarray + """) + +add_newdoc( + "_landau_ppf", + """ + _landau_ppf(p, loc, scale) + + Percent point function of the Landau distribution. + + Parameters + ---------- + p : array_like + Real-valued argument between 0 and 1 + loc : array_like + Real-valued distribution location + scale : array_like + Positive, real-valued distribution scale + + Returns + ------- + scalar or ndarray + """) + +add_newdoc( + "_landau_isf", + """ + _landau_isf(p, loc, scale) + + Inverse survival function of the Landau distribution. + + Parameters + ---------- + p : array_like + Real-valued argument between 0 and 1 + loc : array_like + Real-valued distribution location + scale : array_like + Positive, real-valued distribution scale + + Returns + ------- + scalar or ndarray + """) + +add_newdoc("_lgam1p", + """ + Internal function, do not use. + """) + +add_newdoc("lpmv", + r""" + lpmv(m, v, x, out=None) + + Associated Legendre function of integer order and real degree. + + Defined as + + .. math:: + + P_v^m(x) = (-1)^m (1 - x^2)^{m/2} \frac{d^m}{dx^m} (P_v(x)) + + where + + .. math:: + + P_v(x) = \sum_{k = 0}^\infty \frac{(-v)_k (v + 1)_k}{(k!)^2} + \left(\frac{1 - x}{2}\right)^k + + is the Legendre function of the first kind. Here :math:`(\cdot)_k` + is the Pochhammer symbol; see `poch`. + + Parameters + ---------- + m : array_like + Order (int or float). If passed a float not equal to an + integer the function returns NaN. + v : array_like + Degree (float). + x : array_like + Argument (float). Must have ``|x| <= 1``. + out : ndarray, optional + Optional output array for the function results. + + Returns + ------- + pmv : scalar or ndarray + Value of the associated Legendre function. + + Notes + ----- + Note that this implementation includes the Condon-Shortley phase. + + References + ---------- + .. [1] S. Zhang and J.M. Jin, "Computation of Special Functions", Wiley 1996. + + Examples + -------- + Compute a single associated Legendre function value: + + >>> from scipy.special import lpmv + >>> lpmv(1, 2.5, 0.3) + -0.1292299 + + Plot the associated Legendre functions for orders ``m = 1`` and + ``m = 2`` and degrees ``v = 0, ..., 5``: + + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> nus = np.arange(0, 6) + >>> xs = np.linspace(-1+1e-5, 1-1e-5, 101) + + Order 1. + + >>> fig, ax = plt.subplots() + >>> ax.plot(xs, lpmv(1, nus, xs[:, np.newaxis]), "-") + >>> ax.legend([rf"$\nu={nu}$" for nu in nus]) + >>> ax.set_xlabel(r"$x$") + >>> ax.set_ylabel(r"$P_\nu^1(x)$") + >>> plt.show() + + Order 2. + + >>> fig, ax = plt.subplots() + >>> ax.plot(xs, lpmv(2, nus, xs[:, np.newaxis]), "-") + >>> ax.legend([rf"$\nu={nu}$" for nu in nus]) + >>> ax.set_xlabel(r"$x$") + >>> ax.set_ylabel(r"$P_\nu^2(x)$") + >>> plt.show() + """) + +add_newdoc("nbdtr", + r""" + nbdtr(k, n, p, out=None) + + Negative binomial cumulative distribution function. + + Returns the sum of the terms 0 through `k` of the negative binomial + distribution probability mass function, + + .. math:: + + F(k) = \sum_{j=0}^k {{n + j - 1}\choose{j}} p^n (1 - p)^j. + + In a sequence of Bernoulli trials with individual success probabilities + `p`, this is the probability that `k` or fewer failures precede the nth + success. + + Parameters + ---------- + k : array_like + The maximum number of allowed failures (nonnegative int). + n : array_like + The target number of successes (positive int). + p : array_like + Probability of success in a single event (float). + out : ndarray, optional + Optional output array for the function results + + Returns + ------- + scalar or ndarray + The probability of `k` or fewer failures before `n` successes in a + sequence of events with individual success probability `p`. + + See Also + -------- + nbdtrc : Negative binomial survival function + nbdtrik : Negative binomial quantile function + scipy.stats.nbinom : Negative binomial distribution + + Notes + ----- + If floating point values are passed for `k` or `n`, they will be truncated + to integers. + + The terms are not summed directly; instead the regularized incomplete beta + function is employed, according to the formula, + + .. math:: + \mathrm{nbdtr}(k, n, p) = I_{p}(n, k + 1). + + Wrapper for the Cephes [1]_ routine `nbdtr`. + + The negative binomial distribution is also available as + `scipy.stats.nbinom`. Using `nbdtr` directly can improve performance + compared to the ``cdf`` method of `scipy.stats.nbinom` (see last example). + + References + ---------- + .. [1] Cephes Mathematical Functions Library, + https://netlib.org/cephes/ + + Examples + -------- + Compute the function for ``k=10`` and ``n=5`` at ``p=0.5``. + + >>> import numpy as np + >>> from scipy.special import nbdtr + >>> nbdtr(10, 5, 0.5) + 0.940765380859375 + + Compute the function for ``n=10`` and ``p=0.5`` at several points by + providing a NumPy array or list for `k`. + + >>> nbdtr([5, 10, 15], 10, 0.5) + array([0.15087891, 0.58809853, 0.88523853]) + + Plot the function for four different parameter sets. + + >>> import matplotlib.pyplot as plt + >>> k = np.arange(130) + >>> n_parameters = [20, 20, 20, 80] + >>> p_parameters = [0.2, 0.5, 0.8, 0.5] + >>> linestyles = ['solid', 'dashed', 'dotted', 'dashdot'] + >>> parameters_list = list(zip(p_parameters, n_parameters, + ... linestyles)) + >>> fig, ax = plt.subplots(figsize=(8, 8)) + >>> for parameter_set in parameters_list: + ... p, n, style = parameter_set + ... nbdtr_vals = nbdtr(k, n, p) + ... ax.plot(k, nbdtr_vals, label=rf"$n={n},\, p={p}$", + ... ls=style) + >>> ax.legend() + >>> ax.set_xlabel("$k$") + >>> ax.set_title("Negative binomial cumulative distribution function") + >>> plt.show() + + The negative binomial distribution is also available as + `scipy.stats.nbinom`. Using `nbdtr` directly can be much faster than + calling the ``cdf`` method of `scipy.stats.nbinom`, especially for small + arrays or individual values. To get the same results one must use the + following parametrization: ``nbinom(n, p).cdf(k)=nbdtr(k, n, p)``. + + >>> from scipy.stats import nbinom + >>> k, n, p = 5, 3, 0.5 + >>> nbdtr_res = nbdtr(k, n, p) # this will often be faster than below + >>> stats_res = nbinom(n, p).cdf(k) + >>> stats_res, nbdtr_res # test that results are equal + (0.85546875, 0.85546875) + + `nbdtr` can evaluate different parameter sets by providing arrays with + shapes compatible for broadcasting for `k`, `n` and `p`. Here we compute + the function for three different `k` at four locations `p`, resulting in + a 3x4 array. + + >>> k = np.array([[5], [10], [15]]) + >>> p = np.array([0.3, 0.5, 0.7, 0.9]) + >>> k.shape, p.shape + ((3, 1), (4,)) + + >>> nbdtr(k, 5, p) + array([[0.15026833, 0.62304687, 0.95265101, 0.9998531 ], + [0.48450894, 0.94076538, 0.99932777, 0.99999999], + [0.76249222, 0.99409103, 0.99999445, 1. ]]) + """) + +add_newdoc("nbdtrc", + r""" + nbdtrc(k, n, p, out=None) + + Negative binomial survival function. + + Returns the sum of the terms `k + 1` to infinity of the negative binomial + distribution probability mass function, + + .. math:: + + S(k) = \sum_{j=k + 1}^\infty {{n + j - 1}\choose{j}} p^n (1 - p)^j. + + In a sequence of Bernoulli trials with individual success probabilities + `p`, this is the probability that more than `k` failures precede the nth + success. + + Parameters + ---------- + k : array_like + The maximum number of allowed failures (nonnegative int). + n : array_like + The target number of successes (positive int). + p : array_like + Probability of success in a single event (float). + out : ndarray, optional + Optional output array for the function results + + Returns + ------- + scalar or ndarray + The probability of `k + 1` or more failures before `n` successes in a + sequence of events with individual success probability `p`. + + See Also + -------- + nbdtr : Negative binomial cumulative distribution function + nbdtrik : Negative binomial percentile function + scipy.stats.nbinom : Negative binomial distribution + + Notes + ----- + If floating point values are passed for `k` or `n`, they will be truncated + to integers. + + The terms are not summed directly; instead the regularized incomplete beta + function is employed, according to the formula, + + .. math:: + \mathrm{nbdtrc}(k, n, p) = I_{1 - p}(k + 1, n). + + Wrapper for the Cephes [1]_ routine `nbdtrc`. + + The negative binomial distribution is also available as + `scipy.stats.nbinom`. Using `nbdtrc` directly can improve performance + compared to the ``sf`` method of `scipy.stats.nbinom` (see last example). + + References + ---------- + .. [1] Cephes Mathematical Functions Library, + https://netlib.org/cephes/ + + Examples + -------- + Compute the function for ``k=10`` and ``n=5`` at ``p=0.5``. + + >>> import numpy as np + >>> from scipy.special import nbdtrc + >>> nbdtrc(10, 5, 0.5) + 0.059234619140624986 + + Compute the function for ``n=10`` and ``p=0.5`` at several points by + providing a NumPy array or list for `k`. + + >>> nbdtrc([5, 10, 15], 10, 0.5) + array([0.84912109, 0.41190147, 0.11476147]) + + Plot the function for four different parameter sets. + + >>> import matplotlib.pyplot as plt + >>> k = np.arange(130) + >>> n_parameters = [20, 20, 20, 80] + >>> p_parameters = [0.2, 0.5, 0.8, 0.5] + >>> linestyles = ['solid', 'dashed', 'dotted', 'dashdot'] + >>> parameters_list = list(zip(p_parameters, n_parameters, + ... linestyles)) + >>> fig, ax = plt.subplots(figsize=(8, 8)) + >>> for parameter_set in parameters_list: + ... p, n, style = parameter_set + ... nbdtrc_vals = nbdtrc(k, n, p) + ... ax.plot(k, nbdtrc_vals, label=rf"$n={n},\, p={p}$", + ... ls=style) + >>> ax.legend() + >>> ax.set_xlabel("$k$") + >>> ax.set_title("Negative binomial distribution survival function") + >>> plt.show() + + The negative binomial distribution is also available as + `scipy.stats.nbinom`. Using `nbdtrc` directly can be much faster than + calling the ``sf`` method of `scipy.stats.nbinom`, especially for small + arrays or individual values. To get the same results one must use the + following parametrization: ``nbinom(n, p).sf(k)=nbdtrc(k, n, p)``. + + >>> from scipy.stats import nbinom + >>> k, n, p = 3, 5, 0.5 + >>> nbdtr_res = nbdtrc(k, n, p) # this will often be faster than below + >>> stats_res = nbinom(n, p).sf(k) + >>> stats_res, nbdtr_res # test that results are equal + (0.6367187499999999, 0.6367187499999999) + + `nbdtrc` can evaluate different parameter sets by providing arrays with + shapes compatible for broadcasting for `k`, `n` and `p`. Here we compute + the function for three different `k` at four locations `p`, resulting in + a 3x4 array. + + >>> k = np.array([[5], [10], [15]]) + >>> p = np.array([0.3, 0.5, 0.7, 0.9]) + >>> k.shape, p.shape + ((3, 1), (4,)) + + >>> nbdtrc(k, 5, p) + array([[8.49731667e-01, 3.76953125e-01, 4.73489874e-02, 1.46902600e-04], + [5.15491059e-01, 5.92346191e-02, 6.72234070e-04, 9.29610100e-09], + [2.37507779e-01, 5.90896606e-03, 5.55025308e-06, 3.26346760e-13]]) + """) + +add_newdoc( + "nbdtri", + r""" + nbdtri(k, n, y, out=None) + + Returns the inverse with respect to the parameter `p` of + ``y = nbdtr(k, n, p)``, the negative binomial cumulative distribution + function. + + Parameters + ---------- + k : array_like + The maximum number of allowed failures (nonnegative int). + n : array_like + The target number of successes (positive int). + y : array_like + The probability of `k` or fewer failures before `n` successes (float). + out : ndarray, optional + Optional output array for the function results + + Returns + ------- + p : scalar or ndarray + Probability of success in a single event (float) such that + `nbdtr(k, n, p) = y`. + + See Also + -------- + nbdtr : Cumulative distribution function of the negative binomial. + nbdtrc : Negative binomial survival function. + scipy.stats.nbinom : negative binomial distribution. + nbdtrik : Inverse with respect to `k` of `nbdtr(k, n, p)`. + nbdtrin : Inverse with respect to `n` of `nbdtr(k, n, p)`. + scipy.stats.nbinom : Negative binomial distribution + + Notes + ----- + Wrapper for the Cephes [1]_ routine `nbdtri`. + + The negative binomial distribution is also available as + `scipy.stats.nbinom`. Using `nbdtri` directly can improve performance + compared to the ``ppf`` method of `scipy.stats.nbinom`. + + References + ---------- + .. [1] Cephes Mathematical Functions Library, + https://netlib.org/cephes/ + + Examples + -------- + `nbdtri` is the inverse of `nbdtr` with respect to `p`. + Up to floating point errors the following holds: + ``nbdtri(k, n, nbdtr(k, n, p))=p``. + + >>> import numpy as np + >>> from scipy.special import nbdtri, nbdtr + >>> k, n, p = 5, 10, 0.2 + >>> cdf_val = nbdtr(k, n, p) + >>> nbdtri(k, n, cdf_val) + 0.20000000000000004 + + Compute the function for ``k=10`` and ``n=5`` at several points by + providing a NumPy array or list for `y`. + + >>> y = np.array([0.1, 0.4, 0.8]) + >>> nbdtri(3, 5, y) + array([0.34462319, 0.51653095, 0.69677416]) + + Plot the function for three different parameter sets. + + >>> import matplotlib.pyplot as plt + >>> n_parameters = [5, 20, 30, 30] + >>> k_parameters = [20, 20, 60, 80] + >>> linestyles = ['solid', 'dashed', 'dotted', 'dashdot'] + >>> parameters_list = list(zip(n_parameters, k_parameters, linestyles)) + >>> cdf_vals = np.linspace(0, 1, 1000) + >>> fig, ax = plt.subplots(figsize=(8, 8)) + >>> for parameter_set in parameters_list: + ... n, k, style = parameter_set + ... nbdtri_vals = nbdtri(k, n, cdf_vals) + ... ax.plot(cdf_vals, nbdtri_vals, label=rf"$k={k},\ n={n}$", + ... ls=style) + >>> ax.legend() + >>> ax.set_ylabel("$p$") + >>> ax.set_xlabel("$CDF$") + >>> title = "nbdtri: inverse of negative binomial CDF with respect to $p$" + >>> ax.set_title(title) + >>> plt.show() + + `nbdtri` can evaluate different parameter sets by providing arrays with + shapes compatible for broadcasting for `k`, `n` and `p`. Here we compute + the function for three different `k` at four locations `p`, resulting in + a 3x4 array. + + >>> k = np.array([[5], [10], [15]]) + >>> y = np.array([0.3, 0.5, 0.7, 0.9]) + >>> k.shape, y.shape + ((3, 1), (4,)) + + >>> nbdtri(k, 5, y) + array([[0.37258157, 0.45169416, 0.53249956, 0.64578407], + [0.24588501, 0.30451981, 0.36778453, 0.46397088], + [0.18362101, 0.22966758, 0.28054743, 0.36066188]]) + """) + +add_newdoc("nbdtrik", + r""" + nbdtrik(y, n, p, out=None) + + Negative binomial percentile function. + + Returns the inverse with respect to the parameter `k` of + ``y = nbdtr(k, n, p)``, the negative binomial cumulative distribution + function. + + Parameters + ---------- + y : array_like + The probability of `k` or fewer failures before `n` successes (float). + n : array_like + The target number of successes (positive int). + p : array_like + Probability of success in a single event (float). + out : ndarray, optional + Optional output array for the function results + + Returns + ------- + k : scalar or ndarray + The maximum number of allowed failures such that `nbdtr(k, n, p) = y`. + + See Also + -------- + nbdtr : Cumulative distribution function of the negative binomial. + nbdtrc : Survival function of the negative binomial. + nbdtri : Inverse with respect to `p` of `nbdtr(k, n, p)`. + nbdtrin : Inverse with respect to `n` of `nbdtr(k, n, p)`. + scipy.stats.nbinom : Negative binomial distribution + + Notes + ----- + This function wraps routines from the Boost Math C++ library [1]_. + + References + ---------- + .. [1] The Boost Developers. "Boost C++ Libraries". https://www.boost.org/. + + Examples + -------- + Compute the negative binomial cumulative distribution function for an + exemplary parameter set. + + >>> import numpy as np + >>> from scipy.special import nbdtr, nbdtrik + >>> k, n, p = 5, 2, 0.5 + >>> cdf_value = nbdtr(k, n, p) + >>> cdf_value + 0.9375 + + Verify that `nbdtrik` recovers the original value for `k`. + + >>> nbdtrik(cdf_value, n, p) + 5.0 + + Plot the function for different parameter sets. + + >>> import matplotlib.pyplot as plt + >>> p_parameters = [0.2, 0.5, 0.7, 0.5] + >>> n_parameters = [30, 30, 30, 80] + >>> linestyles = ['solid', 'dashed', 'dotted', 'dashdot'] + >>> parameters_list = list(zip(p_parameters, n_parameters, linestyles)) + >>> cdf_vals = np.linspace(0, 1, 1000) + >>> fig, ax = plt.subplots(figsize=(8, 8)) + >>> for parameter_set in parameters_list: + ... p, n, style = parameter_set + ... nbdtrik_vals = nbdtrik(cdf_vals, n, p) + ... ax.plot(cdf_vals, nbdtrik_vals, label=rf"$n={n},\ p={p}$", + ... ls=style) + >>> ax.legend() + >>> ax.set_ylabel("$k$") + >>> ax.set_xlabel("$CDF$") + >>> ax.set_title("Negative binomial percentile function") + >>> plt.show() + + The negative binomial distribution is also available as + `scipy.stats.nbinom`. The percentile function method ``ppf`` + returns the result of `nbdtrik` rounded up to integers: + + >>> from scipy.stats import nbinom + >>> q, n, p = 0.6, 5, 0.5 + >>> nbinom.ppf(q, n, p), nbdtrik(q, n, p) + (5.0, 4.800428460273882) + + """) + +add_newdoc("nbdtrin", + r""" + nbdtrin(k, y, p, out=None) + + Inverse of `nbdtr` vs `n`. + + Returns the inverse with respect to the parameter `n` of + ``y = nbdtr(k, n, p)``, the negative binomial cumulative distribution + function. + + Parameters + ---------- + k : array_like + The maximum number of allowed failures (nonnegative int). + y : array_like + The probability of `k` or fewer failures before `n` successes (float). + p : array_like + Probability of success in a single event (float). + out : ndarray, optional + Optional output array for the function results + + Returns + ------- + n : scalar or ndarray + The number of successes `n` such that `nbdtr(k, n, p) = y`. + + See Also + -------- + nbdtr : Cumulative distribution function of the negative binomial. + nbdtri : Inverse with respect to `p` of `nbdtr(k, n, p)`. + nbdtrik : Inverse with respect to `k` of `nbdtr(k, n, p)`. + + Notes + ----- + This function wraps routines from the Boost Math C++ library [1]_. + + Formula 26.5.26 of [2]_ or [3]_, + + .. math:: + \sum_{j=k + 1}^\infty {{n + j - 1} + \choose{j}} p^n (1 - p)^j = I_{1 - p}(k + 1, n), + + is used to reduce calculation of the cumulative distribution function to + that of a regularized incomplete beta :math:`I`. + + Computation of `n` involves a search for a value that produces the desired + value of `y`. The search relies on the monotonicity of `y` with `n`. + + References + ---------- + .. [1] The Boost Developers. "Boost C++ Libraries". https://www.boost.org/. + .. [2] Milton Abramowitz and Irene A. Stegun, eds. + Handbook of Mathematical Functions with Formulas, + Graphs, and Mathematical Tables. New York: Dover, 1972. + .. [3] NIST Digital Library of Mathematical Functions + https://dlmf.nist.gov/8.17.E24 + + Examples + -------- + Compute the negative binomial cumulative distribution function for an + exemplary parameter set. + + >>> from scipy.special import nbdtr, nbdtrin + >>> k, n, p = 5, 2, 0.5 + >>> cdf_value = nbdtr(k, n, p) + >>> cdf_value + 0.9375 + + Verify that `nbdtrin` recovers the original value for `n` up to floating + point accuracy. + + >>> nbdtrin(k, cdf_value, p) + 2.0 + """) + +add_newdoc("ncfdtr", + r""" + ncfdtr(dfn, dfd, nc, f, out=None) + + Cumulative distribution function of the non-central F distribution. + + The non-central F describes the distribution of, + + .. math:: + Z = \frac{X/d_n}{Y/d_d} + + where :math:`X` and :math:`Y` are independently distributed, with + :math:`X` distributed non-central :math:`\chi^2` with noncentrality + parameter `nc` and :math:`d_n` degrees of freedom, and :math:`Y` + distributed :math:`\chi^2` with :math:`d_d` degrees of freedom. + + Parameters + ---------- + dfn : array_like + Degrees of freedom of the numerator sum of squares. Range (0, inf). + dfd : array_like + Degrees of freedom of the denominator sum of squares. Range (0, inf). + nc : array_like + Noncentrality parameter. Range [0, inf). + f : array_like + Quantiles, i.e. the upper limit of integration. + out : ndarray, optional + Optional output array for the function results + + Returns + ------- + cdf : scalar or ndarray + The calculated CDF. If all inputs are scalar, the return will be a + float. Otherwise it will be an array. + + See Also + -------- + ncfdtri : Quantile function; inverse of `ncfdtr` with respect to `f`. + ncfdtridfd : Inverse of `ncfdtr` with respect to `dfd`. + ncfdtridfn : Inverse of `ncfdtr` with respect to `dfn`. + ncfdtrinc : Inverse of `ncfdtr` with respect to `nc`. + scipy.stats.ncf : Non-central F distribution. + + Notes + ----- + This function calculates the CDF of the non-central f distribution using + the Boost Math C++ library [1]_. + + The cumulative distribution function is computed using Formula 26.6.20 of + [2]_: + + .. math:: + F(d_n, d_d, n_c, f) = \sum_{j=0}^\infty e^{-n_c/2} + \frac{(n_c/2)^j}{j!} I_{x}(\frac{d_n}{2} + j, \frac{d_d}{2}), + + where :math:`I` is the regularized incomplete beta function, and + :math:`x = f d_n/(f d_n + d_d)`. + + Note that argument order of `ncfdtr` is different from that of the + similar ``cdf`` method of `scipy.stats.ncf`: `f` is the last + parameter of `ncfdtr` but the first parameter of ``scipy.stats.ncf.cdf``. + + References + ---------- + .. [1] The Boost Developers. "Boost C++ Libraries". https://www.boost.org/. + .. [2] Milton Abramowitz and Irene A. Stegun, eds. + Handbook of Mathematical Functions with Formulas, + Graphs, and Mathematical Tables. New York: Dover, 1972. + + Examples + -------- + >>> import numpy as np + >>> from scipy import special + >>> from scipy import stats + >>> import matplotlib.pyplot as plt + + Plot the CDF of the non-central F distribution, for nc=0. Compare with the + F-distribution from scipy.stats: + + >>> x = np.linspace(-1, 8, num=500) + >>> dfn = 3 + >>> dfd = 2 + >>> ncf_stats = stats.f.cdf(x, dfn, dfd) + >>> ncf_special = special.ncfdtr(dfn, dfd, 0, x) + + >>> fig = plt.figure() + >>> ax = fig.add_subplot(111) + >>> ax.plot(x, ncf_stats, 'b-', lw=3) + >>> ax.plot(x, ncf_special, 'r-') + >>> plt.show() + + """) + +add_newdoc("ncfdtri", + """ + ncfdtri(dfn, dfd, nc, p, out=None) + + Inverse with respect to `f` of the CDF of the non-central F distribution. + + See `ncfdtr` for more details. + + Parameters + ---------- + dfn : array_like + Degrees of freedom of the numerator sum of squares. Range (0, inf). + dfd : array_like + Degrees of freedom of the denominator sum of squares. Range (0, inf). + nc : array_like + Noncentrality parameter. Range [0, inf). + p : array_like + Value of the cumulative distribution function. Must be in the + range [0, 1]. + out : ndarray, optional + Optional output array for the function results + + Returns + ------- + f : scalar or ndarray + Quantiles, i.e., the upper limit of integration. + + See Also + -------- + ncfdtr : CDF of the non-central F distribution. + ncfdtridfd : Inverse of `ncfdtr` with respect to `dfd`. + ncfdtridfn : Inverse of `ncfdtr` with respect to `dfn`. + ncfdtrinc : Inverse of `ncfdtr` with respect to `nc`. + scipy.stats.ncf : Non-central F distribution. + + Notes + ----- + This function calculates the Quantile of the non-central f distribution + using the Boost Math C++ library [1]_. + + Note that argument order of `ncfdtri` is different from that of the + similar ``ppf`` method of `scipy.stats.ncf`. `p` is the last parameter + of `ncfdtri` but the first parameter of ``scipy.stats.ncf.ppf``. + + References + ---------- + .. [1] The Boost Developers. "Boost C++ Libraries". https://www.boost.org/. + + Examples + -------- + >>> from scipy.special import ncfdtr, ncfdtri + + Compute the CDF for several values of `f`: + + >>> f = [0.5, 1, 1.5] + >>> p = ncfdtr(2, 3, 1.5, f) + >>> p + array([ 0.20782291, 0.36107392, 0.47345752]) + + Compute the inverse. We recover the values of `f`, as expected: + + >>> ncfdtri(2, 3, 1.5, p) + array([ 0.5, 1. , 1.5]) + + """) + +add_newdoc("ncfdtridfd", + """ + ncfdtridfd(dfn, p, nc, f, out=None) + + Calculate degrees of freedom (denominator) for the noncentral F-distribution. + + This is the inverse with respect to `dfd` of `ncfdtr`. + See `ncfdtr` for more details. + + Parameters + ---------- + dfn : array_like + Degrees of freedom of the numerator sum of squares. Range (0, inf). + p : array_like + Value of the cumulative distribution function. Must be in the + range [0, 1]. + nc : array_like + Noncentrality parameter. Should be in range (0, 1e4). + f : array_like + Quantiles, i.e., the upper limit of integration. + out : ndarray, optional + Optional output array for the function results + + Returns + ------- + dfd : scalar or ndarray + Degrees of freedom of the denominator sum of squares. + + See Also + -------- + ncfdtr : CDF of the non-central F distribution. + ncfdtri : Quantile function; inverse of `ncfdtr` with respect to `f`. + ncfdtridfn : Inverse of `ncfdtr` with respect to `dfn`. + ncfdtrinc : Inverse of `ncfdtr` with respect to `nc`. + + Notes + ----- + The value of the cumulative noncentral F distribution is not necessarily + monotone in either degrees of freedom. There thus may be two values that + provide a given CDF value. This routine assumes monotonicity and will + find an arbitrary one of the two values. + + Examples + -------- + >>> from scipy.special import ncfdtr, ncfdtridfd + + Compute the CDF for several values of `dfd`: + + >>> dfd = [1, 2, 3] + >>> p = ncfdtr(2, dfd, 0.25, 15) + >>> p + array([ 0.8097138 , 0.93020416, 0.96787852]) + + Compute the inverse. We recover the values of `dfd`, as expected: + + >>> ncfdtridfd(2, p, 0.25, 15) + array([ 1., 2., 3.]) + + """) + +add_newdoc("ncfdtridfn", + """ + ncfdtridfn(p, dfd, nc, f, out=None) + + Calculate degrees of freedom (numerator) for the noncentral F-distribution. + + This is the inverse with respect to `dfn` of `ncfdtr`. + See `ncfdtr` for more details. + + Parameters + ---------- + p : array_like + Value of the cumulative distribution function. Must be in the + range [0, 1]. + dfd : array_like + Degrees of freedom of the denominator sum of squares. Range (0, inf). + nc : array_like + Noncentrality parameter. Should be in range (0, 1e4). + f : float + Quantiles, i.e., the upper limit of integration. + out : ndarray, optional + Optional output array for the function results + + Returns + ------- + dfn : scalar or ndarray + Degrees of freedom of the numerator sum of squares. + + See Also + -------- + ncfdtr : CDF of the non-central F distribution. + ncfdtri : Quantile function; inverse of `ncfdtr` with respect to `f`. + ncfdtridfd : Inverse of `ncfdtr` with respect to `dfd`. + ncfdtrinc : Inverse of `ncfdtr` with respect to `nc`. + + Notes + ----- + The value of the cumulative noncentral F distribution is not necessarily + monotone in either degrees of freedom. There thus may be two values that + provide a given CDF value. This routine assumes monotonicity and will + find an arbitrary one of the two values. + + Examples + -------- + >>> from scipy.special import ncfdtr, ncfdtridfn + + Compute the CDF for several values of `dfn`: + + >>> dfn = [1, 2, 3] + >>> p = ncfdtr(dfn, 2, 0.25, 15) + >>> p + array([ 0.92562363, 0.93020416, 0.93188394]) + + Compute the inverse. We recover the values of `dfn`, as expected: + + >>> ncfdtridfn(p, 2, 0.25, 15) + array([ 1., 2., 3.]) + + """) + +add_newdoc("ncfdtrinc", + """ + ncfdtrinc(dfn, dfd, p, f, out=None) + + Calculate non-centrality parameter for non-central F distribution. + + This is the inverse with respect to `nc` of `ncfdtr`. + See `ncfdtr` for more details. + + Parameters + ---------- + dfn : array_like + Degrees of freedom of the numerator sum of squares. Range (0, inf). + dfd : array_like + Degrees of freedom of the denominator sum of squares. Range (0, inf). + p : array_like + Value of the cumulative distribution function. Must be in the + range [0, 1]. + f : array_like + Quantiles, i.e., the upper limit of integration. + out : ndarray, optional + Optional output array for the function results + + Returns + ------- + nc : scalar or ndarray + Noncentrality parameter. + + See Also + -------- + ncfdtr : CDF of the non-central F distribution. + ncfdtri : Quantile function; inverse of `ncfdtr` with respect to `f`. + ncfdtridfd : Inverse of `ncfdtr` with respect to `dfd`. + ncfdtridfn : Inverse of `ncfdtr` with respect to `dfn`. + + Examples + -------- + >>> from scipy.special import ncfdtr, ncfdtrinc + + Compute the CDF for several values of `nc`: + + >>> nc = [0.5, 1.5, 2.0] + >>> p = ncfdtr(2, 3, nc, 15) + >>> p + array([ 0.96309246, 0.94327955, 0.93304098]) + + Compute the inverse. We recover the values of `nc`, as expected: + + >>> ncfdtrinc(2, 3, p, 15) + array([ 0.5, 1.5, 2. ]) + + """) + +add_newdoc("nctdtr", + """ + nctdtr(df, nc, t, out=None) + + Cumulative distribution function of the non-central `t` distribution. + + Parameters + ---------- + df : array_like + Degrees of freedom of the distribution. Should be in range (0, inf). + nc : array_like + Noncentrality parameter. + t : array_like + Quantiles, i.e., the upper limit of integration. + out : ndarray, optional + Optional output array for the function results + + Returns + ------- + cdf : scalar or ndarray + The calculated CDF. If all inputs are scalar, the return will be a + float. Otherwise, it will be an array. + + See Also + -------- + nctdtrit : Inverse CDF (iCDF) of the non-central t distribution. + nctdtridf : Calculate degrees of freedom, given CDF and iCDF values. + nctdtrinc : Calculate non-centrality parameter, given CDF iCDF values. + + Notes + ----- + This function calculates the CDF of the non-central t distribution using + the Boost Math C++ library [1]_. + + Note that the argument order of `nctdtr` is different from that of the + similar ``cdf`` method of `scipy.stats.nct`: `t` is the last + parameter of `nctdtr` but the first parameter of ``scipy.stats.nct.cdf``. + + References + ---------- + .. [1] The Boost Developers. "Boost C++ Libraries". https://www.boost.org/. + + Examples + -------- + >>> import numpy as np + >>> from scipy import special + >>> from scipy import stats + >>> import matplotlib.pyplot as plt + + Plot the CDF of the non-central t distribution, for nc=0. Compare with the + t-distribution from scipy.stats: + + >>> x = np.linspace(-5, 5, num=500) + >>> df = 3 + >>> nct_stats = stats.t.cdf(x, df) + >>> nct_special = special.nctdtr(df, 0, x) + + >>> fig = plt.figure() + >>> ax = fig.add_subplot(111) + >>> ax.plot(x, nct_stats, 'b-', lw=3) + >>> ax.plot(x, nct_special, 'r-') + >>> plt.show() + + """) + +add_newdoc("nctdtridf", + """ + nctdtridf(p, nc, t, out=None) + + Calculate degrees of freedom for non-central t distribution. + + See `nctdtr` for more details. + + Parameters + ---------- + p : array_like + CDF values, in range (0, 1]. + nc : array_like + Noncentrality parameter. Should be in range (-1e6, 1e6). + t : array_like + Quantiles, i.e., the upper limit of integration. + out : ndarray, optional + Optional output array for the function results + + Returns + ------- + df : scalar or ndarray + The degrees of freedom. If all inputs are scalar, the return will be a + float. Otherwise, it will be an array. + + See Also + -------- + nctdtr : CDF of the non-central `t` distribution. + nctdtrit : Inverse CDF (iCDF) of the non-central t distribution. + nctdtrinc : Calculate non-centrality parameter, given CDF iCDF values. + + Examples + -------- + >>> from scipy.special import nctdtr, nctdtridf + + Compute the CDF for several values of `df`: + + >>> df = [1, 2, 3] + >>> p = nctdtr(df, 0.25, 1) + >>> p + array([0.67491974, 0.716464 , 0.73349456]) + + Compute the inverse. We recover the values of `df`, as expected: + + >>> nctdtridf(p, 0.25, 1) + array([1., 2., 3.]) + + """) + +add_newdoc("nctdtrinc", + """ + nctdtrinc(df, p, t, out=None) + + Calculate non-centrality parameter for non-central t distribution. + + See `nctdtr` for more details. + + Parameters + ---------- + df : array_like + Degrees of freedom of the distribution. Should be in range (0, inf). + p : array_like + CDF values, in range (0, 1]. + t : array_like + Quantiles, i.e., the upper limit of integration. + out : ndarray, optional + Optional output array for the function results + + Returns + ------- + nc : scalar or ndarray + Noncentrality parameter + + See Also + -------- + nctdtr : CDF of the non-central `t` distribution. + nctdtrit : Inverse CDF (iCDF) of the non-central t distribution. + nctdtridf : Calculate degrees of freedom, given CDF and iCDF values. + + Examples + -------- + >>> from scipy.special import nctdtr, nctdtrinc + + Compute the CDF for several values of `nc`: + + >>> nc = [0.5, 1.5, 2.5] + >>> p = nctdtr(3, nc, 1.5) + >>> p + array([0.77569497, 0.45524533, 0.1668691 ]) + + Compute the inverse. We recover the values of `nc`, as expected: + + >>> nctdtrinc(3, p, 1.5) + array([0.5, 1.5, 2.5]) + + """) + +add_newdoc("nctdtrit", + """ + nctdtrit(df, nc, p, out=None) + + Inverse cumulative distribution function of the non-central t distribution. + + See `nctdtr` for more details. + + Parameters + ---------- + df : array_like + Degrees of freedom of the distribution. Should be in range (0, inf). + nc : array_like + Noncentrality parameter. + p : array_like + CDF values, in range (0, 1]. + out : ndarray, optional + Optional output array for the function results + + Returns + ------- + t : scalar or ndarray + Quantiles + + See Also + -------- + nctdtr : CDF of the non-central `t` distribution. + nctdtridf : Calculate degrees of freedom, given CDF and iCDF values. + nctdtrinc : Calculate non-centrality parameter, given CDF iCDF values. + + Notes + ----- + This function calculates the quantile of the non-central t distribution using + the Boost Math C++ library [1]_. + + Note that the argument order of `nctdtrit` is different from that of the + similar ``ppf`` method of `scipy.stats.nct`: `t` is the last + parameter of `nctdtrit` but the first parameter of ``scipy.stats.nct.ppf``. + + References + ---------- + .. [1] The Boost Developers. "Boost C++ Libraries". https://www.boost.org/. + + Examples + -------- + >>> from scipy.special import nctdtr, nctdtrit + + Compute the CDF for several values of `t`: + + >>> t = [0.5, 1, 1.5] + >>> p = nctdtr(3, 1, t) + >>> p + array([0.29811049, 0.46922687, 0.6257559 ]) + + Compute the inverse. We recover the values of `t`, as expected: + + >>> nctdtrit(3, 1, p) + array([0.5, 1. , 1.5]) + + """) + +add_newdoc("nrdtrimn", + """ + nrdtrimn(p, std, x, out=None) + + Calculate mean of normal distribution given other params. + + Parameters + ---------- + p : array_like + CDF values, in range (0, 1]. + std : array_like + Standard deviation. + x : array_like + Quantiles, i.e. the upper limit of integration. + out : ndarray, optional + Optional output array for the function results + + Returns + ------- + mn : scalar or ndarray + The mean of the normal distribution. + + See Also + -------- + scipy.stats.norm : Normal distribution + ndtr : Standard normal cumulative probability distribution + ndtri : Inverse of standard normal CDF with respect to quantile + nrdtrisd : Inverse of normal distribution CDF with respect to + standard deviation + + Examples + -------- + `nrdtrimn` can be used to recover the mean of a normal distribution + if we know the CDF value `p` for a given quantile `x` and the + standard deviation `std`. First, we calculate + the normal distribution CDF for an exemplary parameter set. + + >>> from scipy.stats import norm + >>> mean = 3. + >>> std = 2. + >>> x = 6. + >>> p = norm.cdf(x, loc=mean, scale=std) + >>> p + 0.9331927987311419 + + Verify that `nrdtrimn` returns the original value for `mean`. + + >>> from scipy.special import nrdtrimn + >>> nrdtrimn(p, std, x) + 3.0000000000000004 + + """) + +add_newdoc("nrdtrisd", + """ + nrdtrisd(mn, p, x, out=None) + + Calculate standard deviation of normal distribution given other params. + + Parameters + ---------- + mn : scalar or ndarray + The mean of the normal distribution. + p : array_like + CDF values, in range (0, 1]. + x : array_like + Quantiles, i.e. the upper limit of integration. + + out : ndarray, optional + Optional output array for the function results + + Returns + ------- + std : scalar or ndarray + Standard deviation. + + See Also + -------- + scipy.stats.norm : Normal distribution + ndtr : Standard normal cumulative probability distribution + ndtri : Inverse of standard normal CDF with respect to quantile + nrdtrimn : Inverse of normal distribution CDF with respect to + mean + + Examples + -------- + `nrdtrisd` can be used to recover the standard deviation of a normal + distribution if we know the CDF value `p` for a given quantile `x` and + the mean `mn`. First, we calculate the normal distribution CDF for an + exemplary parameter set. + + >>> from scipy.stats import norm + >>> mean = 3. + >>> std = 2. + >>> x = 6. + >>> p = norm.cdf(x, loc=mean, scale=std) + >>> p + 0.9331927987311419 + + Verify that `nrdtrisd` returns the original value for `std`. + + >>> from scipy.special import nrdtrisd + >>> nrdtrisd(mean, p, x) + 2.0000000000000004 + + """) + +add_newdoc("ndtri", + """ + ndtri(p, out=None) + + Inverse of `ndtr`. + + Returns the quantile `x` such that the cumulative distribution function of the + standard normal distribution evaluated at `x` equals `p`, that is, ``ndtr(x) == p``. + + Parameters + ---------- + p : array_like + Probability values. + out : ndarray, optional + Optional output array for the function results. + + Returns + ------- + x : scalar or ndarray + Quantile(s) corresponding to the probabilitie(s) in `p`. + + See Also + -------- + ndtr : Standard normal cumulative distribution function + ndtri_exp : Inverse of log_ndtr + + Examples + -------- + `ndtri` is the percentile (quantile) function of the standard normal distribution, + i.e., the inverse of the cumulative distribution function `ndtr`. + + First, compute a cumulative distribution value: + + >>> import numpy as np + >>> from scipy.special import ndtri, ndtr + >>> cdf_val = ndtr(2) + >>> cdf_val + 0.9772498680518208 + + Verify that `ndtri` yields the original value for `x` up to floating point errors. + + >>> ndtri(cdf_val) + 2.0000000000000004 + + Plot the percentile function over a range of probabilities. + + >>> import matplotlib.pyplot as plt + >>> p = np.linspace(1e-3, 1 - 1e-3, 201) + >>> fig, ax = plt.subplots() + >>> ax.plot(p, ndtri(p)) + >>> ax.set_title("Standard normal percentile function") + >>> plt.show() + """) + +add_newdoc("pdtr", + r""" + pdtr(k, m, out=None) + + Poisson cumulative distribution function. + + Defined as the probability that a Poisson-distributed random + variable with event rate :math:`m` is less than or equal to + :math:`k`. More concretely, this works out to be [1]_ + + .. math:: + + \exp(-m) \sum_{j = 0}^{\lfloor{k}\rfloor} \frac{m^j}{j!}. + + Parameters + ---------- + k : array_like + Number of occurrences (nonnegative, real) + m : array_like + Shape parameter (nonnegative, real) + out : ndarray, optional + Optional output array for the function results + + Returns + ------- + scalar or ndarray + Values of the Poisson cumulative distribution function + + See Also + -------- + pdtrc : Poisson survival function + pdtrik : inverse of `pdtr` with respect to `k` + pdtri : inverse of `pdtr` with respect to `m` + + References + ---------- + .. [1] https://en.wikipedia.org/wiki/Poisson_distribution + + Examples + -------- + >>> import numpy as np + >>> import scipy.special as sc + + It is a cumulative distribution function, so it converges to 1 + monotonically as `k` goes to infinity. + + >>> sc.pdtr([1, 10, 100, np.inf], 1) + array([0.73575888, 0.99999999, 1. , 1. ]) + + It is discontinuous at integers and constant between integers. + + >>> sc.pdtr([1, 1.5, 1.9, 2], 1) + array([0.73575888, 0.73575888, 0.73575888, 0.9196986 ]) + + """) + +add_newdoc("pdtrc", + """ + pdtrc(k, m, out=None) + + Poisson survival function. + + Returns the sum of the terms from k+1 to infinity of the Poisson + distribution: sum(exp(-m) * m**j / j!, j=k+1..inf) = gammainc( + k+1, m). Arguments must both be non-negative doubles. + + Parameters + ---------- + k : array_like + Number of occurrences (nonnegative, real) + m : array_like + Shape parameter (nonnegative, real) + out : ndarray, optional + Optional output array for the function results + + Returns + ------- + scalar or ndarray + Values of the Poisson survival function + + See Also + -------- + pdtr : Poisson cumulative distribution function + pdtrik : inverse of `pdtr` with respect to `k` + pdtri : inverse of `pdtr` with respect to `m` + + Examples + -------- + >>> import numpy as np + >>> import scipy.special as sc + + It is a survival function, so it decreases to 0 + monotonically as `k` goes to infinity. + + >>> k = np.array([1, 10, 100, np.inf]) + >>> sc.pdtrc(k, 1) + array([2.64241118e-001, 1.00477664e-008, 3.94147589e-161, 0.00000000e+000]) + + It can be expressed in terms of the lower incomplete gamma + function `gammainc`. + + >>> sc.gammainc(k + 1, 1) + array([2.64241118e-001, 1.00477664e-008, 3.94147589e-161, 0.00000000e+000]) + + """) + +add_newdoc("pdtri", + """ + pdtri(k, y, out=None) + + Inverse of `pdtr` with respect to `m`. + + Returns the Poisson variable `m` such that the sum from 0 to `k` of + the Poisson density is equal to the given probability `y`: + calculated by ``gammainccinv(k + 1, y)``. `k` must be a nonnegative + integer and `y` between 0 and 1. + + Parameters + ---------- + k : array_like + Number of occurrences (nonnegative, real). + y : array_like + Probability. + out : ndarray, optional + Optional output array for the function results. + + Returns + ------- + scalar or ndarray + Values of the shape parameter `m` such that ``pdtr(k, m) = y``. + + See Also + -------- + pdtr : Poisson cumulative distribution function + pdtrc : Poisson survival function + pdtrik : Inverse of `pdtr` with respect to `k` + + Examples + -------- + >>> import scipy.special as sc + + Compute the CDF for several values of `m`: + + >>> k = 1 + >>> m = [0.5, 1, 1.5] + >>> p = sc.pdtr(k, m) + >>> p + array([0.90979599, 0.73575888, 0.5578254 ]) + + Invert the CDF with respect to the Poisson mean. We recover the values + of `m`, as expected: + + >>> sc.pdtri(k, p) + array([0.5, 1. , 1.5]) + + Verify the relation with `gammainccinv`: + + >>> sc.gammainccinv(k + 1, p) + array([0.5, 1. , 1.5]) + + + """) + +add_newdoc("pdtrik", + """ + pdtrik(p, m, out=None) + + Inverse of `pdtr` with respect to `k`. + + Parameters + ---------- + p : array_like + Probability + m : array_like + Shape parameter (nonnegative, real) + out : ndarray, optional + Optional output array for the function results + + Returns + ------- + scalar or ndarray + The number of occurrences `k` such that ``pdtr(k, m) = p`` + + See Also + -------- + pdtr : Poisson cumulative distribution function + pdtrc : Poisson survival function + pdtri : inverse of `pdtr` with respect to `m` + + Notes + ----- + This function relies on the ``gamma_q_inva`` function from the Boost + Math C++ library [1]_. + + References + ---------- + .. [1] The Boost Developers. "Boost C++ Libraries". https://www.boost.org/. + + Examples + -------- + >>> import scipy.special as sc + + Compute the CDF for several values of `k`: + + >>> k = [1, 2, 3] + >>> p = sc.pdtr(k, 2) + >>> p + array([0.40600585, 0.67667642, 0.85712346]) + + Compute the inverse. We recover the values of `k`, as expected: + + >>> sc.pdtrik(p, 2) + array([1., 2., 3.]) + + """) + +add_newdoc("poch", + r""" + poch(z, m, out=None) + + Pochhammer symbol. + + The Pochhammer symbol (rising factorial) is defined as + + .. math:: + + (z)_m = \frac{\Gamma(z + m)}{\Gamma(z)} + + For positive integer `m` it reads + + .. math:: + + (z)_m = z (z + 1) ... (z + m - 1) + + See [DLMF]_ for more details. + + Parameters + ---------- + z, m : array_like + Real-valued arguments. + out : ndarray, optional + Optional output array for the function results + + Returns + ------- + scalar or ndarray + The value of the function. + + References + ---------- + .. [DLMF] Nist, Digital Library of Mathematical Functions + https://dlmf.nist.gov/5.2#iii + + Examples + -------- + >>> import scipy.special as sc + + It is 1 when m is 0. + + >>> sc.poch([1, 2, 3, 4], 0) + array([1., 1., 1., 1.]) + + For z equal to 1 it reduces to the factorial function. + + >>> sc.poch(1, 5) + 120.0 + >>> 1 * 2 * 3 * 4 * 5 + 120 + + It can be expressed in terms of the gamma function. + + >>> z, m = 3.7, 2.1 + >>> sc.poch(z, m) + 20.529581933776953 + >>> sc.gamma(z + m) / sc.gamma(z) + 20.52958193377696 + + """) + +add_newdoc("powm1", """ + powm1(x, y, out=None) + + Computes ``x**y - 1``. + + This function is useful when `y` is near 0, or when `x` is near 1. + + The function is implemented for real types only (unlike ``numpy.power``, + which accepts complex inputs). + + Parameters + ---------- + x : array_like + The base. Must be a real type (i.e. integer or float, not complex). + y : array_like + The exponent. Must be a real type (i.e. integer or float, not complex). + + Returns + ------- + array_like + Result of the calculation + + Notes + ----- + .. versionadded:: 1.10.0 + + The underlying code is implemented for single precision and double + precision floats only. Unlike `numpy.power`, integer inputs to + `powm1` are converted to floating point, and complex inputs are + not accepted. + + Note the following edge cases: + + * ``powm1(x, 0)`` returns 0 for any ``x``, including 0, ``inf`` + and ``nan``. + * ``powm1(1, y)`` returns 0 for any ``y``, including ``nan`` + and ``inf``. + + This function wraps the ``powm1`` routine from the + Boost Math C++ library [1]_. + + References + ---------- + .. [1] The Boost Developers. "Boost C++ Libraries". https://www.boost.org/. + + Examples + -------- + >>> import numpy as np + >>> from scipy.special import powm1 + + >>> x = np.array([1.2, 10.0, 0.9999999975]) + >>> y = np.array([1e-9, 1e-11, 0.1875]) + >>> powm1(x, y) + array([ 1.82321557e-10, 2.30258509e-11, -4.68749998e-10]) + + It can be verified that the relative errors in those results + are less than 2.5e-16. + + Compare that to the result of ``x**y - 1``, where the + relative errors are all larger than 8e-8: + + >>> x**y - 1 + array([ 1.82321491e-10, 2.30258035e-11, -4.68750039e-10]) + + """) + + +add_newdoc("pseudo_huber", + r""" + pseudo_huber(delta, r, out=None) + + Pseudo-Huber loss function. + + .. math:: \mathrm{pseudo\_huber}(\delta, r) = + \delta^2 \left( \sqrt{ 1 + \left( \frac{r}{\delta} \right)^2 } - 1 \right) + + Parameters + ---------- + delta : array_like + Input array, indicating the soft quadratic vs. linear loss changepoint. + r : array_like + Input array, possibly representing residuals. + out : ndarray, optional + Optional output array for the function results + + Returns + ------- + res : scalar or ndarray + The computed Pseudo-Huber loss function values. + + See Also + -------- + huber: Similar function which this function approximates + + Notes + ----- + Like `huber`, `pseudo_huber` often serves as a robust loss function + in statistics or machine learning to reduce the influence of outliers. + Unlike `huber`, `pseudo_huber` is smooth. + + Typically, `r` represents residuals, the difference + between a model prediction and data. Then, for :math:`|r|\leq\delta`, + `pseudo_huber` resembles the squared error and for :math:`|r|>\delta` the + absolute error. This way, the Pseudo-Huber loss often achieves + a fast convergence in model fitting for small residuals like the squared + error loss function and still reduces the influence of outliers + (:math:`|r|>\delta`) like the absolute error loss. As :math:`\delta` is + the cutoff between squared and absolute error regimes, it has + to be tuned carefully for each problem. `pseudo_huber` is also + convex, making it suitable for gradient based optimization. [1]_ [2]_ + + .. versionadded:: 0.15.0 + + References + ---------- + .. [1] Hartley, Zisserman, "Multiple View Geometry in Computer Vision". + 2003. Cambridge University Press. p. 619 + .. [2] Charbonnier et al. "Deterministic edge-preserving regularization + in computed imaging". 1997. IEEE Trans. Image Processing. + 6 (2): 298 - 311. + + Examples + -------- + Import all necessary modules. + + >>> import numpy as np + >>> from scipy.special import pseudo_huber, huber + >>> import matplotlib.pyplot as plt + + Calculate the function for ``delta=1`` at ``r=2``. + + >>> pseudo_huber(1., 2.) + 1.2360679774997898 + + Calculate the function at ``r=2`` for different `delta` by providing + a list or NumPy array for `delta`. + + >>> pseudo_huber([1., 2., 4.], 3.) + array([2.16227766, 3.21110255, 4. ]) + + Calculate the function for ``delta=1`` at several points by providing + a list or NumPy array for `r`. + + >>> pseudo_huber(2., np.array([1., 1.5, 3., 4.])) + array([0.47213595, 1. , 3.21110255, 4.94427191]) + + The function can be calculated for different `delta` and `r` by + providing arrays for both with compatible shapes for broadcasting. + + >>> r = np.array([1., 2.5, 8., 10.]) + >>> deltas = np.array([[1.], [5.], [9.]]) + >>> print(r.shape, deltas.shape) + (4,) (3, 1) + + >>> pseudo_huber(deltas, r) + array([[ 0.41421356, 1.6925824 , 7.06225775, 9.04987562], + [ 0.49509757, 2.95084972, 22.16990566, 30.90169944], + [ 0.49846624, 3.06693762, 27.37435121, 40.08261642]]) + + Plot the function for different `delta`. + + >>> x = np.linspace(-4, 4, 500) + >>> deltas = [1, 2, 3] + >>> linestyles = ["dashed", "dotted", "dashdot"] + >>> fig, ax = plt.subplots() + >>> combined_plot_parameters = list(zip(deltas, linestyles)) + >>> for delta, style in combined_plot_parameters: + ... ax.plot(x, pseudo_huber(delta, x), label=rf"$\delta={delta}$", + ... ls=style) + >>> ax.legend(loc="upper center") + >>> ax.set_xlabel("$x$") + >>> ax.set_title(r"Pseudo-Huber loss function $h_{\delta}(x)$") + >>> ax.set_xlim(-4, 4) + >>> ax.set_ylim(0, 8) + >>> plt.show() + + Finally, illustrate the difference between `huber` and `pseudo_huber` by + plotting them and their gradients with respect to `r`. The plot shows + that `pseudo_huber` is continuously differentiable while `huber` is not + at the points :math:`\pm\delta`. + + >>> def huber_grad(delta, x): + ... grad = np.copy(x) + ... linear_area = np.argwhere(np.abs(x) > delta) + ... grad[linear_area]=delta*np.sign(x[linear_area]) + ... return grad + >>> def pseudo_huber_grad(delta, x): + ... return x* (1+(x/delta)**2)**(-0.5) + >>> x=np.linspace(-3, 3, 500) + >>> delta = 1. + >>> fig, ax = plt.subplots(figsize=(7, 7)) + >>> ax.plot(x, huber(delta, x), label="Huber", ls="dashed") + >>> ax.plot(x, huber_grad(delta, x), label="Huber Gradient", ls="dashdot") + >>> ax.plot(x, pseudo_huber(delta, x), label="Pseudo-Huber", ls="dotted") + >>> ax.plot(x, pseudo_huber_grad(delta, x), label="Pseudo-Huber Gradient", + ... ls="solid") + >>> ax.legend(loc="upper center") + >>> plt.show() + """) + +add_newdoc("rel_entr", + r""" + rel_entr(x, y, out=None) + + Elementwise function for computing relative entropy. + + .. math:: + + \mathrm{rel\_entr}(x, y) = + \begin{cases} + x \log(x / y) & x > 0, y > 0 \\ + 0 & x = 0, y \ge 0 \\ + \infty & \text{otherwise} + \end{cases} + + Parameters + ---------- + x, y : array_like + Input arrays + out : ndarray, optional + Optional output array for the function results + + Returns + ------- + scalar or ndarray + Relative entropy of the inputs + + See Also + -------- + entr, kl_div, scipy.stats.entropy + + Notes + ----- + .. versionadded:: 0.15.0 + + This function is jointly convex in x and y. + + The origin of this function is in convex programming; see + [1]_. Given two discrete probability distributions :math:`p_1, + \ldots, p_n` and :math:`q_1, \ldots, q_n`, the definition of relative + entropy in the context of *information theory* is + + .. math:: + + \sum_{i = 1}^n \mathrm{rel\_entr}(p_i, q_i). + + To compute the latter quantity, use `scipy.stats.entropy`. + + See [2]_ for details. + + References + ---------- + .. [1] Boyd, Stephen and Lieven Vandenberghe. *Convex optimization*. + Cambridge University Press, 2004. + :doi:`10.1017/CBO9780511804441`. + .. [2] Kullback-Leibler divergence, + https://en.wikipedia.org/wiki/Kullback%E2%80%93Leibler_divergence + + """) + +add_newdoc("round", + """ + round(x, out=None) + + Round to the nearest integer. + + Returns the nearest integer to `x`. If `x` ends in 0.5 exactly, + the nearest even integer is chosen. + + Parameters + ---------- + x : array_like + Real valued input. + out : ndarray, optional + Optional output array for the function results. + + Returns + ------- + scalar or ndarray + The nearest integers to the elements of `x`. The result is of + floating type, not integer type. + + Examples + -------- + >>> import scipy.special as sc + + It rounds to even. + + >>> sc.round([0.5, 1.5]) + array([0., 2.]) + + """) + +add_newdoc("shichi", + r""" + shichi(x, out=None) + + Hyperbolic sine and cosine integrals. + + The hyperbolic sine integral is + + .. math:: + + \int_0^x \frac{\sinh{t}}{t}dt + + and the hyperbolic cosine integral is + + .. math:: + + \gamma + \log(x) + \int_0^x \frac{\cosh{t} - 1}{t} dt + + where :math:`\gamma` is Euler's constant and :math:`\log` is the + principal branch of the logarithm [1]_ (see also [2]_). + + Parameters + ---------- + x : array_like + Real or complex points at which to compute the hyperbolic sine + and cosine integrals. + out : tuple of ndarray, optional + Optional output arrays for the function results + + Returns + ------- + si : scalar or ndarray + Hyperbolic sine integral at ``x`` + ci : scalar or ndarray + Hyperbolic cosine integral at ``x`` + + See Also + -------- + sici : Sine and cosine integrals. + exp1 : Exponential integral E1. + expi : Exponential integral Ei. + + Notes + ----- + For real arguments with ``x < 0``, ``chi`` is the real part of the + hyperbolic cosine integral. For such points ``chi(x)`` and ``chi(x + + 0j)`` differ by a factor of ``1j*pi``. + + For real arguments the function is computed by calling Cephes' + [3]_ *shichi* routine. For complex arguments the algorithm is based + on Mpmath's [4]_ *shi* and *chi* routines. + + References + ---------- + .. [1] Milton Abramowitz and Irene A. Stegun, eds. + Handbook of Mathematical Functions with Formulas, + Graphs, and Mathematical Tables. New York: Dover, 1972. + (See Section 5.2.) + .. [2] NIST Digital Library of Mathematical Functions + https://dlmf.nist.gov/6.2.E15 and https://dlmf.nist.gov/6.2.E16 + .. [3] Cephes Mathematical Functions Library, + https://netlib.org/cephes/ + .. [4] Fredrik Johansson and others. + "mpmath: a Python library for arbitrary-precision floating-point + arithmetic" (Version 0.19) https://mpmath.org/ + + Examples + -------- + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from scipy.special import shichi, sici + + `shichi` accepts real or complex input: + + >>> shichi(0.5) + (0.5069967498196671, -0.05277684495649357) + >>> shichi(0.5 + 2.5j) + ((0.11772029666668238+1.831091777729851j), + (0.29912435887648825+1.7395351121166562j)) + + The hyperbolic sine and cosine integrals Shi(z) and Chi(z) are + related to the sine and cosine integrals Si(z) and Ci(z) by + + * Shi(z) = -i*Si(i*z) + * Chi(z) = Ci(-i*z) + i*pi/2 + + >>> z = 0.25 + 5j + >>> shi, chi = shichi(z) + >>> shi, -1j*sici(1j*z)[0] # Should be the same. + ((-0.04834719325101729+1.5469354086921228j), + (-0.04834719325101729+1.5469354086921228j)) + >>> chi, sici(-1j*z)[1] + 1j*np.pi/2 # Should be the same. + ((-0.19568708973868087+1.556276312103824j), + (-0.19568708973868087+1.556276312103824j)) + + Plot the functions evaluated on the real axis: + + >>> xp = np.geomspace(1e-8, 4.0, 250) + >>> x = np.concatenate((-xp[::-1], xp)) + >>> shi, chi = shichi(x) + + >>> fig, ax = plt.subplots() + >>> ax.plot(x, shi, label='Shi(x)') + >>> ax.plot(x, chi, '--', label='Chi(x)') + >>> ax.set_xlabel('x') + >>> ax.set_title('Hyperbolic Sine and Cosine Integrals') + >>> ax.legend(shadow=True, framealpha=1, loc='lower right') + >>> ax.grid(True) + >>> plt.show() + + """) + +add_newdoc("sici", + r""" + sici(x, out=None) + + Sine and cosine integrals. + + The sine integral is + + .. math:: + + \int_0^x \frac{\sin{t}}{t}dt + + and the cosine integral is + + .. math:: + + \gamma + \log(x) + \int_0^x \frac{\cos{t} - 1}{t}dt + + where :math:`\gamma` is Euler's constant and :math:`\log` is the + principal branch of the logarithm [1]_ (see also [2]_). + + Parameters + ---------- + x : array_like + Real or complex points at which to compute the sine and cosine + integrals. + out : tuple of ndarray, optional + Optional output arrays for the function results + + Returns + ------- + si : scalar or ndarray + Sine integral at ``x`` + ci : scalar or ndarray + Cosine integral at ``x`` + + See Also + -------- + shichi : Hyperbolic sine and cosine integrals. + exp1 : Exponential integral E1. + expi : Exponential integral Ei. + + Notes + ----- + For real arguments with ``x < 0``, ``ci`` is the real part of the + cosine integral. For such points ``ci(x)`` and ``ci(x + 0j)`` + differ by a factor of ``1j*pi``. + + For real arguments the function is computed by calling Cephes' + [3]_ *sici* routine. For complex arguments the algorithm is based + on Mpmath's [4]_ *si* and *ci* routines. + + References + ---------- + .. [1] Milton Abramowitz and Irene A. Stegun, eds. + Handbook of Mathematical Functions with Formulas, + Graphs, and Mathematical Tables. New York: Dover, 1972. + (See Section 5.2.) + .. [2] NIST Digital Library of Mathematical Functions + https://dlmf.nist.gov/6.2.E9, https://dlmf.nist.gov/6.2.E12, + and https://dlmf.nist.gov/6.2.E13 + .. [3] Cephes Mathematical Functions Library, + https://netlib.org/cephes/ + .. [4] Fredrik Johansson and others. + "mpmath: a Python library for arbitrary-precision floating-point + arithmetic" (Version 0.19) https://mpmath.org/ + + Examples + -------- + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from scipy.special import sici, exp1 + + `sici` accepts real or complex input: + + >>> sici(2.5) + (1.7785201734438267, 0.2858711963653835) + >>> sici(2.5 + 3j) + ((4.505735874563953+0.06863305018999577j), + (0.0793644206906966-2.935510262937543j)) + + For z in the right half plane, the sine and cosine integrals are + related to the exponential integral E1 (implemented in SciPy as + `scipy.special.exp1`) by + + * Si(z) = (E1(i*z) - E1(-i*z))/2i + pi/2 + * Ci(z) = -(E1(i*z) + E1(-i*z))/2 + + See [1]_ (equations 5.2.21 and 5.2.23). + + We can verify these relations: + + >>> z = 2 - 3j + >>> sici(z) + ((4.54751388956229-1.3991965806460565j), + (1.408292501520851+2.9836177420296055j)) + + >>> (exp1(1j*z) - exp1(-1j*z))/2j + np.pi/2 # Same as sine integral + (4.54751388956229-1.3991965806460565j) + + >>> -(exp1(1j*z) + exp1(-1j*z))/2 # Same as cosine integral + (1.408292501520851+2.9836177420296055j) + + Plot the functions evaluated on the real axis; the dotted horizontal + lines are at pi/2 and -pi/2: + + >>> x = np.linspace(-16, 16, 150) + >>> si, ci = sici(x) + + >>> fig, ax = plt.subplots() + >>> ax.plot(x, si, label='Si(x)') + >>> ax.plot(x, ci, '--', label='Ci(x)') + >>> ax.legend(shadow=True, framealpha=1, loc='upper left') + >>> ax.set_xlabel('x') + >>> ax.set_title('Sine and Cosine Integrals') + >>> ax.axhline(np.pi/2, linestyle=':', alpha=0.5, color='k') + >>> ax.axhline(-np.pi/2, linestyle=':', alpha=0.5, color='k') + >>> ax.grid(True) + >>> plt.show() + + """) + +add_newdoc("smirnov", + r""" + smirnov(n, d, out=None) + + Kolmogorov-Smirnov complementary cumulative distribution function. + + Returns the exact Kolmogorov-Smirnov complementary cumulative + distribution function,(aka the Survival Function) of Dn+ (or Dn-) + for a one-sided test of equality between an empirical and a + theoretical distribution. It is equal to the probability that the + maximum difference between a theoretical distribution and an empirical + one based on `n` samples is greater than d. + + Parameters + ---------- + n : int + Number of samples + d : float array_like + Deviation between the Empirical CDF (ECDF) and the target CDF. + out : ndarray, optional + Optional output array for the function results + + Returns + ------- + scalar or ndarray + The value(s) of smirnov(n, d), Prob(Dn+ >= d) (Also Prob(Dn- >= d)) + + See Also + -------- + smirnovi : The Inverse Survival Function for the distribution + scipy.stats.ksone : Provides the functionality as a continuous distribution + kolmogorov, kolmogi : Functions for the two-sided distribution + + Notes + ----- + `smirnov` is used by `stats.kstest` in the application of the + Kolmogorov-Smirnov Goodness of Fit test. For historical reasons this + function is exposed in `scpy.special`, but the recommended way to achieve + the most accurate CDF/SF/PDF/PPF/ISF computations is to use the + `stats.ksone` distribution. + + Examples + -------- + >>> import numpy as np + >>> from scipy.special import smirnov + >>> from scipy.stats import norm + + Show the probability of a gap at least as big as 0, 0.5 and 1.0 for a + sample of size 5. + + >>> smirnov(5, [0, 0.5, 1.0]) + array([ 1. , 0.056, 0. ]) + + Compare a sample of size 5 against N(0, 1), the standard normal + distribution with mean 0 and standard deviation 1. + + `x` is the sample. + + >>> x = np.array([-1.392, -0.135, 0.114, 0.190, 1.82]) + + >>> target = norm(0, 1) + >>> cdfs = target.cdf(x) + >>> cdfs + array([0.0819612 , 0.44630594, 0.5453811 , 0.57534543, 0.9656205 ]) + + Construct the empirical CDF and the K-S statistics (Dn+, Dn-, Dn). + + >>> n = len(x) + >>> ecdfs = np.arange(n+1, dtype=float)/n + >>> cols = np.column_stack([x, ecdfs[1:], cdfs, cdfs - ecdfs[:n], + ... ecdfs[1:] - cdfs]) + >>> with np.printoptions(precision=3): + ... print(cols) + [[-1.392 0.2 0.082 0.082 0.118] + [-0.135 0.4 0.446 0.246 -0.046] + [ 0.114 0.6 0.545 0.145 0.055] + [ 0.19 0.8 0.575 -0.025 0.225] + [ 1.82 1. 0.966 0.166 0.034]] + >>> gaps = cols[:, -2:] + >>> Dnpm = np.max(gaps, axis=0) + >>> print(f'Dn-={Dnpm[0]:f}, Dn+={Dnpm[1]:f}') + Dn-=0.246306, Dn+=0.224655 + >>> probs = smirnov(n, Dnpm) + >>> print(f'For a sample of size {n} drawn from N(0, 1):', + ... f' Smirnov n={n}: Prob(Dn- >= {Dnpm[0]:f}) = {probs[0]:.4f}', + ... f' Smirnov n={n}: Prob(Dn+ >= {Dnpm[1]:f}) = {probs[1]:.4f}', + ... sep='\n') + For a sample of size 5 drawn from N(0, 1): + Smirnov n=5: Prob(Dn- >= 0.246306) = 0.4711 + Smirnov n=5: Prob(Dn+ >= 0.224655) = 0.5245 + + Plot the empirical CDF and the standard normal CDF. + + >>> import matplotlib.pyplot as plt + >>> plt.step(np.concatenate(([-2.5], x, [2.5])), + ... np.concatenate((ecdfs, [1])), + ... where='post', label='Empirical CDF') + >>> xx = np.linspace(-2.5, 2.5, 100) + >>> plt.plot(xx, target.cdf(xx), '--', label='CDF for N(0, 1)') + + Add vertical lines marking Dn+ and Dn-. + + >>> iminus, iplus = np.argmax(gaps, axis=0) + >>> plt.vlines([x[iminus]], ecdfs[iminus], cdfs[iminus], color='r', + ... alpha=0.5, lw=4) + >>> plt.vlines([x[iplus]], cdfs[iplus], ecdfs[iplus+1], color='m', + ... alpha=0.5, lw=4) + + >>> plt.grid(True) + >>> plt.legend(framealpha=1, shadow=True) + >>> plt.show() + """) + +add_newdoc("smirnovi", + """ + smirnovi(n, p, out=None) + + Inverse to `smirnov`. + + Returns `d` such that ``smirnov(n, d) == p``, the critical value + corresponding to `p`. + + Parameters + ---------- + n : int + Number of samples + p : float array_like + Probability + out : ndarray, optional + Optional output array for the function results + + Returns + ------- + scalar or ndarray + The value(s) of smirnovi(n, p), the critical values. + + See Also + -------- + smirnov : The Survival Function (SF) for the distribution + scipy.stats.ksone : Provides the functionality as a continuous distribution + kolmogorov, kolmogi : Functions for the two-sided distribution + scipy.stats.kstwobign : Two-sided Kolmogorov-Smirnov distribution, large n + + Notes + ----- + `smirnov` is used by `stats.kstest` in the application of the + Kolmogorov-Smirnov Goodness of Fit test. For historical reasons this + function is exposed in `scpy.special`, but the recommended way to achieve + the most accurate CDF/SF/PDF/PPF/ISF computations is to use the + `stats.ksone` distribution. + + Examples + -------- + >>> from scipy.special import smirnovi, smirnov + + >>> n = 24 + >>> deviations = [0.1, 0.2, 0.3] + + Use `smirnov` to compute the complementary CDF of the Smirnov + distribution for the given number of samples and deviations. + + >>> p = smirnov(n, deviations) + >>> p + array([0.58105083, 0.12826832, 0.01032231]) + + The inverse function ``smirnovi(n, p)`` returns ``deviations``. + + >>> smirnovi(n, p) + array([0.1, 0.2, 0.3]) + + """) + +add_newdoc("_smirnovc", + """ + _smirnovc(n, d) + Internal function, do not use. + """) + +add_newdoc("_smirnovci", + """ + Internal function, do not use. + """) + +add_newdoc("_smirnovp", + """ + _smirnovp(n, p) + Internal function, do not use. + """) + +add_newdoc("spence", + r""" + spence(z, out=None) + + Spence's function, also known as the dilogarithm. + + It is defined to be + + .. math:: + \int_1^z \frac{\log(t)}{1 - t}dt + + for complex :math:`z`, where the contour of integration is taken + to avoid the branch cut of the logarithm. Spence's function is + analytic everywhere except the negative real axis where it has a + branch cut. + + Parameters + ---------- + z : array_like + Points at which to evaluate Spence's function + out : ndarray, optional + Optional output array for the function results + + Returns + ------- + s : scalar or ndarray + Computed values of Spence's function + + Notes + ----- + There is a different convention which defines Spence's function by + the integral + + .. math:: + -\int_0^z \frac{\log(1 - t)}{t}dt; + + this is our ``spence(1 - z)``. + + Examples + -------- + >>> import numpy as np + >>> from scipy.special import spence + >>> import matplotlib.pyplot as plt + + The function is defined for complex inputs: + + >>> spence([1-1j, 1.5+2j, 3j, -10-5j]) + array([-0.20561676+0.91596559j, -0.86766909-1.39560134j, + -0.59422064-2.49129918j, -1.14044398+6.80075924j]) + + For complex inputs on the branch cut, which is the negative real axis, + the function returns the limit for ``z`` with positive imaginary part. + For example, in the following, note the sign change of the imaginary + part of the output for ``z = -2`` and ``z = -2 - 1e-8j``: + + >>> spence([-2 + 1e-8j, -2, -2 - 1e-8j]) + array([2.32018041-3.45139229j, 2.32018042-3.4513923j , + 2.32018041+3.45139229j]) + + The function returns ``nan`` for real inputs on the branch cut: + + >>> spence(-1.5) + nan + + Verify some particular values: ``spence(0) = pi**2/6``, + ``spence(1) = 0`` and ``spence(2) = -pi**2/12``. + + >>> spence([0, 1, 2]) + array([ 1.64493407, 0. , -0.82246703]) + >>> np.pi**2/6, -np.pi**2/12 + (1.6449340668482264, -0.8224670334241132) + + Verify the identity:: + + spence(z) + spence(1 - z) = pi**2/6 - log(z)*log(1 - z) + + >>> z = 3 + 4j + >>> spence(z) + spence(1 - z) + (-2.6523186143876067+1.8853470951513935j) + >>> np.pi**2/6 - np.log(z)*np.log(1 - z) + (-2.652318614387606+1.885347095151394j) + + Plot the function for positive real input. + + >>> fig, ax = plt.subplots() + >>> x = np.linspace(0, 6, 400) + >>> ax.plot(x, spence(x)) + >>> ax.grid() + >>> ax.set_xlabel('x') + >>> ax.set_title('spence(x)') + >>> plt.show() + """) + +add_newdoc( + "stdtr", + r""" + stdtr(df, t, out=None) + + Student t distribution cumulative distribution function. + + Returns the integral: + + .. math:: + \frac{\Gamma((df+1)/2)}{\sqrt{\pi df} \Gamma(df/2)} + \int_{-\infty}^t (1+x^2/df)^{-(df+1)/2}\, dx + + Parameters + ---------- + df : array_like + Degrees of freedom + t : array_like + Upper bound of the integral + out : ndarray, optional + Optional output array for the function results + + Returns + ------- + scalar or ndarray + Value of the Student t CDF at t + + See Also + -------- + stdtridf : inverse of stdtr with respect to `df` + stdtrit : inverse of stdtr with respect to `t` + scipy.stats.t : student t distribution + + Notes + ----- + The student t distribution is also available as `scipy.stats.t`. + Calling `stdtr` directly can improve performance compared to the + ``cdf`` method of `scipy.stats.t` (see last example below). + + The function is computed using the Boost Math library [1]_, which + relies on the incomplete beta function. + + References + ---------- + .. [1] Boost C++ Libraries, https://www.boost.org/ + + Examples + -------- + Calculate the function for ``df=3`` at ``t=1``. + + >>> import numpy as np + >>> from scipy.special import stdtr + >>> import matplotlib.pyplot as plt + >>> stdtr(3, 1) + 0.8044988905221148 + + Plot the function for three different degrees of freedom. + + >>> x = np.linspace(-10, 10, 1000) + >>> fig, ax = plt.subplots() + >>> parameters = [(1, "solid"), (3, "dashed"), (10, "dotted")] + >>> for (df, linestyle) in parameters: + ... ax.plot(x, stdtr(df, x), ls=linestyle, label=f"$df={df}$") + >>> ax.legend() + >>> ax.set_title("Student t distribution cumulative distribution function") + >>> plt.show() + + The function can be computed for several degrees of freedom at the same + time by providing a NumPy array or list for `df`: + + >>> stdtr([1, 2, 3], 1) + array([0.75 , 0.78867513, 0.80449889]) + + It is possible to calculate the function at several points for several + different degrees of freedom simultaneously by providing arrays for `df` + and `t` with shapes compatible for broadcasting. Compute `stdtr` at + 4 points for 3 degrees of freedom resulting in an array of shape 3x4. + + >>> dfs = np.array([[1], [2], [3]]) + >>> t = np.array([2, 4, 6, 8]) + >>> dfs.shape, t.shape + ((3, 1), (4,)) + + >>> stdtr(dfs, t) + array([[0.85241638, 0.92202087, 0.94743154, 0.96041658], + [0.90824829, 0.97140452, 0.98666426, 0.99236596], + [0.93033702, 0.98599577, 0.99536364, 0.99796171]]) + + The t distribution is also available as `scipy.stats.t`. Calling `stdtr` + directly can be much faster than calling the ``cdf`` method of + `scipy.stats.t`. To get the same results, one must use the following + parametrization: ``scipy.stats.t(df).cdf(x) = stdtr(df, x)``. + + >>> from scipy.stats import t + >>> df, x = 3, 1 + >>> stdtr_result = stdtr(df, x) # this can be faster than below + >>> stats_result = t(df).cdf(x) + >>> stats_result == stdtr_result # test that results are equal + True + """) + +add_newdoc("stdtridf", + """ + stdtridf(p, t, out=None) + + Inverse of `stdtr` vs df. + + Returns the argument df such that stdtr(df, t) is equal to `p`. + + Parameters + ---------- + p : array_like + Probability + t : array_like + Upper bound of the integral + out : ndarray, optional + Optional output array for the function results + + Returns + ------- + df : scalar or ndarray + Value of `df` such that ``stdtr(df, t) == p`` + + See Also + -------- + stdtr : Student t CDF + stdtrit : inverse of stdtr with respect to `t` + scipy.stats.t : Student t distribution + + Examples + -------- + Compute the student t cumulative distribution function for one + parameter set. + + >>> from scipy.special import stdtr, stdtridf + >>> df, x = 5, 2 + >>> cdf_value = stdtr(df, x) + >>> cdf_value + 0.9490302605850709 + + Verify that `stdtridf` recovers the original value for `df` given + the CDF value and `x`. + + >>> stdtridf(cdf_value, x) + 5.0 + """) + +add_newdoc("stdtrit", + """ + stdtrit(df, p, out=None) + + The `p`-th quantile of the student t distribution. + + This function is the inverse of the student t distribution cumulative + distribution function (CDF), returning `t` such that `stdtr(df, t) = p`. + + Returns the argument `t` such that stdtr(df, t) is equal to `p`. + + Parameters + ---------- + df : array_like + Degrees of freedom + p : array_like + Probability + out : ndarray, optional + Optional output array for the function results + + Returns + ------- + t : scalar or ndarray + Value of `t` such that ``stdtr(df, t) == p`` + + See Also + -------- + stdtr : Student t CDF + stdtridf : inverse of stdtr with respect to `df` + scipy.stats.t : Student t distribution + + Notes + ----- + The student t distribution is also available as `scipy.stats.t`. Calling + `stdtrit` directly can improve performance compared to the ``ppf`` + method of `scipy.stats.t` (see last example below). + + The function is computed using the Boost Math library [1]_, which + relies on the incomplete beta function. + + References + ---------- + .. [1] Boost C++ Libraries, https://www.boost.org/ + + Examples + -------- + `stdtrit` represents the inverse of the student t distribution CDF which + is available as `stdtr`. Here, we calculate the CDF for ``df`` at + ``x=1``. `stdtrit` then returns ``1`` up to floating point errors + given the same value for `df` and the computed CDF value. + + >>> import numpy as np + >>> from scipy.special import stdtr, stdtrit + >>> import matplotlib.pyplot as plt + >>> df = 3 + >>> x = 1 + >>> cdf_value = stdtr(df, x) + >>> stdtrit(df, cdf_value) + 0.9999999994418539 + + Plot the function for three different degrees of freedom. + + >>> x = np.linspace(0, 1, 1000) + >>> parameters = [(1, "solid"), (2, "dashed"), (5, "dotted")] + >>> fig, ax = plt.subplots() + >>> for (df, linestyle) in parameters: + ... ax.plot(x, stdtrit(df, x), ls=linestyle, label=f"$df={df}$") + >>> ax.legend() + >>> ax.set_ylim(-10, 10) + >>> ax.set_title("Student t distribution quantile function") + >>> plt.show() + + The function can be computed for several degrees of freedom at the same + time by providing a NumPy array or list for `df`: + + >>> stdtrit([1, 2, 3], 0.7) + array([0.72654253, 0.6172134 , 0.58438973]) + + It is possible to calculate the function at several points for several + different degrees of freedom simultaneously by providing arrays for `df` + and `p` with shapes compatible for broadcasting. Compute `stdtrit` at + 4 points for 3 degrees of freedom resulting in an array of shape 3x4. + + >>> dfs = np.array([[1], [2], [3]]) + >>> p = np.array([0.2, 0.4, 0.7, 0.8]) + >>> dfs.shape, p.shape + ((3, 1), (4,)) + + >>> stdtrit(dfs, p) + array([[-1.37638192, -0.3249197 , 0.72654253, 1.37638192], + [-1.06066017, -0.28867513, 0.6172134 , 1.06066017], + [-0.97847231, -0.27667066, 0.58438973, 0.97847231]]) + + The t distribution is also available as `scipy.stats.t`. Calling `stdtrit` + directly can be much faster than calling the ``ppf`` method of + `scipy.stats.t`. To get the same results, one must use the following + parametrization: ``scipy.stats.t(df).ppf(x) = stdtrit(df, x)``. + + >>> from scipy.stats import t + >>> df, x = 3, 0.5 + >>> stdtrit_result = stdtrit(df, x) # this can be faster than below + >>> stats_result = t(df).ppf(x) + >>> stats_result == stdtrit_result # test that results are equal + True + """) + +add_newdoc( + "tklmbda", + r""" + tklmbda(x, lmbda, out=None) + + Cumulative distribution function of the Tukey lambda distribution. + + Parameters + ---------- + x, lmbda : array_like + Parameters + out : ndarray, optional + Optional output array for the function results + + Returns + ------- + cdf : scalar or ndarray + Value of the Tukey lambda CDF + + See Also + -------- + scipy.stats.tukeylambda : Tukey lambda distribution + + Examples + -------- + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from scipy.special import tklmbda, expit + + Compute the cumulative distribution function (CDF) of the Tukey lambda + distribution at several ``x`` values for `lmbda` = -1.5. + + >>> x = np.linspace(-2, 2, 9) + >>> x + array([-2. , -1.5, -1. , -0.5, 0. , 0.5, 1. , 1.5, 2. ]) + >>> tklmbda(x, -1.5) + array([0.34688734, 0.3786554 , 0.41528805, 0.45629737, 0.5 , + 0.54370263, 0.58471195, 0.6213446 , 0.65311266]) + + When `lmbda` is 0, the function is the logistic sigmoid function, + which is implemented in `scipy.special` as `expit`. + + >>> tklmbda(x, 0) + array([0.11920292, 0.18242552, 0.26894142, 0.37754067, 0.5 , + 0.62245933, 0.73105858, 0.81757448, 0.88079708]) + >>> expit(x) + array([0.11920292, 0.18242552, 0.26894142, 0.37754067, 0.5 , + 0.62245933, 0.73105858, 0.81757448, 0.88079708]) + + When `lmbda` is 1, the Tukey lambda distribution is uniform on the + interval [-1, 1], so the CDF increases linearly. + + >>> t = np.linspace(-1, 1, 9) + >>> tklmbda(t, 1) + array([0. , 0.125, 0.25 , 0.375, 0.5 , 0.625, 0.75 , 0.875, 1. ]) + + In the following, we generate plots for several values of `lmbda`. + + The first figure shows graphs for `lmbda` <= 0. + + >>> styles = ['-', '-.', '--', ':'] + >>> fig, ax = plt.subplots() + >>> x = np.linspace(-12, 12, 500) + >>> for k, lmbda in enumerate([-1.0, -0.5, 0.0]): + ... y = tklmbda(x, lmbda) + ... ax.plot(x, y, styles[k], label=rf'$\lambda$ = {lmbda:-4.1f}') + + >>> ax.set_title(r'tklmbda(x, $\lambda$)') + >>> ax.set_label('x') + >>> ax.legend(framealpha=1, shadow=True) + >>> ax.grid(True) + + The second figure shows graphs for `lmbda` > 0. The dots in the + graphs show the bounds of the support of the distribution. + + >>> fig, ax = plt.subplots() + >>> x = np.linspace(-4.2, 4.2, 500) + >>> lmbdas = [0.25, 0.5, 1.0, 1.5] + >>> for k, lmbda in enumerate(lmbdas): + ... y = tklmbda(x, lmbda) + ... ax.plot(x, y, styles[k], label=fr'$\lambda$ = {lmbda}') + + >>> ax.set_prop_cycle(None) + >>> for lmbda in lmbdas: + ... ax.plot([-1/lmbda, 1/lmbda], [0, 1], '.', ms=8) + + >>> ax.set_title(r'tklmbda(x, $\lambda$)') + >>> ax.set_xlabel('x') + >>> ax.legend(framealpha=1, shadow=True) + >>> ax.grid(True) + + >>> plt.tight_layout() + >>> plt.show() + + The CDF of the Tukey lambda distribution is also implemented as the + ``cdf`` method of `scipy.stats.tukeylambda`. In the following, + ``tukeylambda.cdf(x, -0.5)`` and ``tklmbda(x, -0.5)`` compute the + same values: + + >>> from scipy.stats import tukeylambda + >>> x = np.linspace(-2, 2, 9) + + >>> tukeylambda.cdf(x, -0.5) + array([0.21995157, 0.27093858, 0.33541677, 0.41328161, 0.5 , + 0.58671839, 0.66458323, 0.72906142, 0.78004843]) + + >>> tklmbda(x, -0.5) + array([0.21995157, 0.27093858, 0.33541677, 0.41328161, 0.5 , + 0.58671839, 0.66458323, 0.72906142, 0.78004843]) + + The implementation in ``tukeylambda`` also provides location and scale + parameters, and other methods such as ``pdf()`` (the probability + density function) and ``ppf()`` (the inverse of the CDF), so for + working with the Tukey lambda distribution, ``tukeylambda`` is more + generally useful. The primary advantage of ``tklmbda`` is that it is + significantly faster than ``tukeylambda.cdf``. + """) + +add_newdoc("yn", + r""" + yn(n, x, out=None) + + Bessel function of the second kind of integer order and real argument. + + Parameters + ---------- + n : array_like + Order (integer). + x : array_like + Argument (float). + out : ndarray, optional + Optional output array for the function results + + Returns + ------- + Y : scalar or ndarray + Value of the Bessel function, :math:`Y_n(x)`. + + See Also + -------- + yv : For real order and real or complex argument. + y0: faster implementation of this function for order 0 + y1: faster implementation of this function for order 1 + + Notes + ----- + Wrapper for the Cephes [1]_ routine `yn`. + + The function is evaluated by forward recurrence on `n`, starting with + values computed by the Cephes routines `y0` and `y1`. If ``n = 0`` or 1, + the routine for `y0` or `y1` is called directly. + + References + ---------- + .. [1] Cephes Mathematical Functions Library, + https://netlib.org/cephes/ + + Examples + -------- + Evaluate the function of order 0 at one point. + + >>> from scipy.special import yn + >>> yn(0, 1.) + 0.08825696421567697 + + Evaluate the function at one point for different orders. + + >>> yn(0, 1.), yn(1, 1.), yn(2, 1.) + (0.08825696421567697, -0.7812128213002888, -1.6506826068162546) + + The evaluation for different orders can be carried out in one call by + providing a list or NumPy array as argument for the `v` parameter: + + >>> yn([0, 1, 2], 1.) + array([ 0.08825696, -0.78121282, -1.65068261]) + + Evaluate the function at several points for order 0 by providing an + array for `z`. + + >>> import numpy as np + >>> points = np.array([0.5, 3., 8.]) + >>> yn(0, points) + array([-0.44451873, 0.37685001, 0.22352149]) + + If `z` is an array, the order parameter `v` must be broadcastable to + the correct shape if different orders shall be computed in one call. + To calculate the orders 0 and 1 for a 1D array: + + >>> orders = np.array([[0], [1]]) + >>> orders.shape + (2, 1) + + >>> yn(orders, points) + array([[-0.44451873, 0.37685001, 0.22352149], + [-1.47147239, 0.32467442, -0.15806046]]) + + Plot the functions of order 0 to 3 from 0 to 10. + + >>> import matplotlib.pyplot as plt + >>> fig, ax = plt.subplots() + >>> x = np.linspace(0., 10., 1000) + >>> for i in range(4): + ... ax.plot(x, yn(i, x), label=f'$Y_{i!r}$') + >>> ax.set_ylim(-3, 1) + >>> ax.legend() + >>> plt.show() + """) + + +add_newdoc("_struve_asymp_large_z", + """ + _struve_asymp_large_z(v, z, is_h) + + Internal function for testing `struve` & `modstruve` + + Evaluates using asymptotic expansion + + Returns + ------- + v, err + """) + +add_newdoc("_struve_power_series", + """ + _struve_power_series(v, z, is_h) + + Internal function for testing `struve` & `modstruve` + + Evaluates using power series + + Returns + ------- + v, err + """) + +add_newdoc("_struve_bessel_series", + """ + _struve_bessel_series(v, z, is_h) + + Internal function for testing `struve` & `modstruve` + + Evaluates using Bessel function series + + Returns + ------- + v, err + """) + +add_newdoc("_spherical_jn", + """ + Internal function, use `spherical_jn` instead. + """) + +add_newdoc("_spherical_jn_d", + """ + Internal function, use `spherical_jn` instead. + """) + +add_newdoc("_spherical_yn", + """ + Internal function, use `spherical_yn` instead. + """) + +add_newdoc("_spherical_yn_d", + """ + Internal function, use `spherical_yn` instead. + """) + +add_newdoc("_spherical_in", + """ + Internal function, use `spherical_in` instead. + """) + +add_newdoc("_spherical_in_d", + """ + Internal function, use `spherical_in` instead. + """) + +add_newdoc("_spherical_kn", + """ + Internal function, use `spherical_kn` instead. + """) + +add_newdoc("_spherical_kn_d", + """ + Internal function, use `spherical_kn` instead. + """) + +add_newdoc("owens_t", + """ + owens_t(h, a, out=None) + + Owen's T Function. + + The function T(h, a) gives the probability of the event + (X > h and 0 < Y < a * X) where X and Y are independent + standard normal random variables. + + Parameters + ---------- + h : array_like + Input value. + a : array_like + Input value. + out : ndarray, optional + Optional output array for the function results + + Returns + ------- + t: scalar or ndarray + Probability of the event (X > h and 0 < Y < a * X), + where X and Y are independent standard normal random variables. + + References + ---------- + .. [1] M. Patefield and D. Tandy, "Fast and accurate calculation of + Owen's T Function", Statistical Software vol. 5, pp. 1-25, 2000. + + Examples + -------- + >>> from scipy import special + >>> a = 3.5 + >>> h = 0.78 + >>> special.owens_t(h, a) + 0.10877216734852274 + """) + +add_newdoc("_factorial", + """ + Internal function, do not use. + """) + +add_newdoc("ndtri_exp", + r""" + ndtri_exp(y, out=None) + + Inverse of `log_ndtr` vs x. Allows for greater precision than + `ndtri` composed with `numpy.exp` for very small values of y and for + y close to 0. + + Parameters + ---------- + y : array_like of float + Function argument + out : ndarray, optional + Optional output array for the function results + + Returns + ------- + scalar or ndarray + Inverse of the log CDF of the standard normal distribution, evaluated + at y. + + See Also + -------- + log_ndtr : log of the standard normal cumulative distribution function + ndtr : standard normal cumulative distribution function + ndtri : standard normal percentile function + + Examples + -------- + >>> import numpy as np + >>> import scipy.special as sc + + `ndtri_exp` agrees with the naive implementation when the latter does + not suffer from underflow. + + >>> sc.ndtri_exp(-1) + -0.33747496376420244 + >>> sc.ndtri(np.exp(-1)) + -0.33747496376420244 + + For extreme values of y, the naive approach fails + + >>> sc.ndtri(np.exp(-800)) + -inf + >>> sc.ndtri(np.exp(-1e-20)) + inf + + whereas `ndtri_exp` is still able to compute the result to high precision. + + >>> sc.ndtri_exp(-800) + -39.88469483825668 + >>> sc.ndtri_exp(-1e-20) + 9.262340089798409 + """) + + +add_newdoc("_stirling2_inexact", + r""" + Internal function, do not use. + """) + +add_newdoc( + "_beta_pdf", + r""" + _beta_pdf(x, a, b) + + Probability density function of beta distribution. + + Parameters + ---------- + x : array_like + Real-valued such that :math:`0 \leq x \leq 1`, + the upper limit of integration + a, b : array_like + Positive, real-valued parameters + + Returns + ------- + scalar or ndarray + + """) + +add_newdoc( + "_beta_ppf", + r""" + _beta_ppf(x, a, b) + + Percent point function of beta distribution. + + Parameters + ---------- + x : array_like + Real-valued such that :math:`0 \leq x \leq 1`, + the upper limit of integration + a, b : array_like + Positive, real-valued parameters + + Returns + ------- + scalar or ndarray + + """) + +add_newdoc( + "_invgauss_ppf", + """ + _invgauss_ppf(x, mu) + + Percent point function of inverse gaussian distribution. + + Parameters + ---------- + x : array_like + Positive real-valued + mu : array_like + Positive, real-valued parameters + + Returns + ------- + scalar or ndarray + + """) + +add_newdoc( + "_invgauss_isf", + """ + _invgauss_isf(x, mu, s) + + Inverse survival function of inverse gaussian distribution. + + Parameters + ---------- + x : array_like + Positive real-valued + mu : array_like + Positive, real-valued parameters + s : array_like + Positive, real-valued parameters + + Returns + ------- + scalar or ndarray + + """) + +add_newdoc( + "_cauchy_ppf", + """ + _cauchy_ppf(p, loc, scale) + + Percent point function (i.e. quantile) of the Cauchy distribution. + + Parameters + ---------- + p : array_like + Probabilities + loc : array_like + Location parameter of the distribution. + scale : array_like + Scale parameter of the distribution. + + Returns + ------- + scalar or ndarray + + """) + +add_newdoc( + "_cauchy_isf", + """ + _cauchy_isf(p, loc, scale) + + Inverse survival function of the Cauchy distribution. + + Parameters + ---------- + p : array_like + Probabilities + loc : array_like + Location parameter of the distribution. + scale : array_like + Scale parameter of the distribution. + + Returns + ------- + scalar or ndarray + + """) + +add_newdoc( + "_ncx2_pdf", + """ + _ncx2_pdf(x, k, l) + + Probability density function of Non-central chi-squared distribution. + + Parameters + ---------- + x : array_like + Positive real-valued + k, l : array_like + Positive, real-valued parameters + + Returns + ------- + scalar or ndarray + + """) + +add_newdoc( + "_ncx2_cdf", + """ + _ncx2_cdf(x, k, l) + + Cumulative density function of Non-central chi-squared distribution. + + Parameters + ---------- + x : array_like + Positive real-valued + k, l : array_like + Positive, real-valued parameters + + Returns + ------- + scalar or ndarray + + """) + +add_newdoc( + "_ncx2_ppf", + """ + _ncx2_ppf(x, k, l) + + Percent point function of Non-central chi-squared distribution. + + Parameters + ---------- + x : array_like + Positive real-valued + k, l : array_like + Positive, real-valued parameters + + Returns + ------- + scalar or ndarray + + """) + +add_newdoc( + "_ncx2_sf", + """ + _ncx2_sf(x, k, l) + + Survival function of Non-central chi-squared distribution. + + Parameters + ---------- + x : array_like + Positive real-valued + k, l : array_like + Positive, real-valued parameters + + Returns + ------- + scalar or ndarray + + """) + +add_newdoc( + "_ncx2_isf", + """ + _ncx2_isf(x, k, l) + + Inverse survival function of Non-central chi-squared distribution. + + Parameters + ---------- + x : array_like + Positive real-valued + k, l : array_like + Positive, real-valued parameters + + Returns + ------- + scalar or ndarray + + """) + +add_newdoc( + "_ncf_pdf", + """ + _ncf_pdf(x, v1, v2, l) + + Probability density function of noncentral F-distribution. + + Parameters + ---------- + x : array_like + Positive real-valued + v1, v2, l : array_like + Positive, real-valued parameters + + Returns + ------- + scalar or ndarray + + """) + +add_newdoc( + "_ncf_cdf", + """ + _ncf_cdf(x, v1, v2, l) + + Cumulative density function of noncentral F-distribution. + + Parameters + ---------- + x : array_like + Positive real-valued + v1, v2, l : array_like + Positive, real-valued parameters + + Returns + ------- + scalar or ndarray + + """) + +add_newdoc( + "_ncf_ppf", + """ + _ncf_ppf(x, v1, v2, l) + + Percent point function of noncentral F-distribution. + + Parameters + ---------- + x : array_like + Positive real-valued + v1, v2, l : array_like + Positive, real-valued parameters + + Returns + ------- + scalar or ndarray + + """) + +add_newdoc( + "_ncf_sf", + """ + _ncf_sf(x, v1, v2, l) + + Survival function of noncentral F-distribution. + + Parameters + ---------- + x : array_like + Positive real-valued + v1, v2, l : array_like + Positive, real-valued parameters + + Returns + ------- + scalar or ndarray + + """) + +add_newdoc( + "_ncf_isf", + """ + _ncf_isf(x, v1, v2, l) + + Inverse survival function of noncentral F-distribution. + + Parameters + ---------- + x : array_like + Positive real-valued + v1, v2, l : array_like + Positive, real-valued parameters + + Returns + ------- + scalar or ndarray + + """) + +add_newdoc( + "_ncf_mean", + """ + _ncf_mean(v1, v2, l) + + Mean of noncentral F-distribution. + + Parameters + ---------- + v1, v2, l : array_like + Positive, real-valued parameters + + Returns + ------- + scalar or ndarray + + """) + +add_newdoc( + "_ncf_variance", + """ + _ncf_variance(v1, v2, l) + + Variance of noncentral F-distribution. + + Parameters + ---------- + v1, v2, l : array_like + Positive, real-valued parameters + + Returns + ------- + scalar or ndarray + + """) + +add_newdoc( + "_ncf_skewness", + """ + _ncf_skewness(v1, v2, l) + + Skewness of noncentral F-distribution. + + Parameters + ---------- + v1, v2, l : array_like + Positive, real-valued parameters + + Returns + ------- + scalar or ndarray + + """) + +add_newdoc( + "_ncf_kurtosis_excess", + """ + _ncf_kurtosis_excess(v1, v2, l) + + Kurtosis excess of noncentral F-distribution. + + Parameters + ---------- + v1, v2, l : array_like + Positive, real-valued parameters + + Returns + ------- + scalar or ndarray + + """) + +add_newdoc( + "_nct_cdf", + """ + _nct_cdf(x, v, l) + + Cumulative density function of noncentral t-distribution. + + Parameters + ---------- + x : array_like + Real-valued + v : array_like + Positive, real-valued parameters + l : array_like + Real-valued parameters + + Returns + ------- + scalar or ndarray + + """) + +add_newdoc( + "_nct_pdf", + """ + _nct_pdf(x, v, l) + + Probability density function of noncentral t-distribution. + + Parameters + ---------- + x : array_like + Real-valued + v : array_like + Positive, real-valued parameters + l : array_like + Real-valued parameters + + Returns + ------- + scalar or ndarray + + """) + + +add_newdoc( + "_nct_ppf", + """ + _nct_ppf(x, v, l) + + Percent point function of noncentral t-distribution. + + Parameters + ---------- + x : array_like + Real-valued + v : array_like + Positive, real-valued parameters + l : array_like + Real-valued parameters + + Returns + ------- + scalar or ndarray + + """) + +add_newdoc( + "_nct_sf", + """ + _nct_sf(x, v, l) + + Survival function of noncentral t-distribution. + + Parameters + ---------- + x : array_like + Real-valued + v : array_like + Positive, real-valued parameters + l : array_like + Real-valued parameters + + Returns + ------- + scalar or ndarray + + """) + +add_newdoc( + "_nct_isf", + """ + _nct_isf(x, v, l) + + Inverse survival function of noncentral t-distribution. + + Parameters + ---------- + x : array_like + Real-valued + v : array_like + Positive, real-valued parameters + l : array_like + Real-valued parameters + + Returns + ------- + scalar or ndarray + + """) + +add_newdoc( + "_nct_mean", + """ + _nct_mean(v, l) + + Mean of noncentral t-distribution. + + Parameters + ---------- + v : array_like + Positive, real-valued parameters + l : array_like + Real-valued parameters + + Returns + ------- + scalar or ndarray + + """) + +add_newdoc( + "_nct_variance", + """ + _nct_variance(v, l) + + Variance of noncentral t-distribution. + + Parameters + ---------- + v : array_like + Positive, real-valued parameters + l : array_like + Real-valued parameters + + Returns + ------- + scalar or ndarray + + """) + +add_newdoc( + "_nct_skewness", + """ + _nct_skewness(v, l) + + Skewness of noncentral t-distribution. + + Parameters + ---------- + v : array_like + Positive, real-valued parameters + l : array_like + Real-valued parameters + + Returns + ------- + scalar or ndarray + + """) + +add_newdoc( + "_nct_kurtosis_excess", + """ + _nct_kurtosis_excess(v, l) + + Kurtosis excess of noncentral t-distribution. + + Parameters + ---------- + v : array_like + Positive, real-valued parameters + l : array_like + Real-valued parameters + + Returns + ------- + scalar or ndarray + + """) + +add_newdoc( + "_skewnorm_cdf", + """ + _skewnorm_cdf(x, l, sc, sh) + + Cumulative density function of skewnorm distribution. + + Parameters + ---------- + x : array_like + Real-valued + l : array_like + Real-valued parameters + sc : array_like + Positive, Real-valued parameters + sh : array_like + Real-valued parameters + + Returns + ------- + scalar or ndarray + + """) + +add_newdoc( + "_skewnorm_ppf", + """ + _skewnorm_ppf(x, l, sc, sh) + + Percent point function of skewnorm distribution. + + Parameters + ---------- + x : array_like + Real-valued + l : array_like + Real-valued parameters + sc : array_like + Positive, Real-valued parameters + sh : array_like + Real-valued parameters + + Returns + ------- + scalar or ndarray + + """) + +add_newdoc( + "_skewnorm_isf", + """ + _skewnorm_isf(x, l, sc, sh) + + Inverse survival function of skewnorm distribution. + + Parameters + ---------- + x : array_like + Real-valued + l : array_like + Real-valued parameters + sc : array_like + Positive, Real-valued parameters + sh : array_like + Real-valued parameters + + Returns + ------- + scalar or ndarray + + """) + +add_newdoc( + "_binom_pmf", + """ + _binom_pmf(x, n, p) + + Probability mass function of binomial distribution. + + Parameters + ---------- + x : array_like + Real-valued + n : array_like + Positive, integer-valued parameter + p : array_like + Positive, real-valued parameter + + Returns + ------- + scalar or ndarray + + """) + +add_newdoc( + "_binom_cdf", + """ + _binom_cdf(x, n, p) + + Cumulative density function of binomial distribution. + + Parameters + ---------- + x : array_like + Real-valued + n : array_like + Positive, integer-valued parameter + p : array_like + Positive, real-valued parameter + + Returns + ------- + scalar or ndarray + + """) + +add_newdoc( + "_binom_ppf", + """ + _binom_ppf(x, n, p) + + Percent point function of binomial distribution. + + Parameters + ---------- + x : array_like + Real-valued + n : array_like + Positive, integer-valued parameter + p : array_like + Positive, real-valued parameter + + Returns + ------- + scalar or ndarray + + """) + +add_newdoc( + "_binom_sf", + """ + _binom_sf(x, n, p) + + Survival function of binomial distribution. + + Parameters + ---------- + x : array_like + Real-valued + n : array_like + Positive, integer-valued parameter + p : array_like + Positive, real-valued parameter + + Returns + ------- + scalar or ndarray + + """) + +add_newdoc( + "_binom_isf", + """ + _binom_isf(x, n, p) + + Inverse survival function of binomial distribution. + + Parameters + ---------- + x : array_like + Real-valued + n : array_like + Positive, integer-valued parameter + p : array_like + Positive, real-valued parameter + + Returns + ------- + scalar or ndarray + + """) + +add_newdoc( + "_nbinom_pmf", + """ + _nbinom_pmf(x, r, p) + + Probability mass function of negative binomial distribution. + + Parameters + ---------- + x : array_like + Real-valued + r : array_like + Positive, integer-valued parameter + p : array_like + Positive, real-valued parameter + + Returns + ------- + scalar or ndarray + + """) + +add_newdoc( + "_nbinom_cdf", + """ + _nbinom_cdf(x, r, p) + + Cumulative density function of negative binomial distribution. + + Parameters + ---------- + x : array_like + Real-valued + r : array_like + Positive, integer-valued parameter + p : array_like + Positive, real-valued parameter + + Returns + ------- + scalar or ndarray + + """) + +add_newdoc( + "_nbinom_ppf", + """ + _nbinom_ppf(x, r, p) + + Percent point function of negative binomial distribution. + + Parameters + ---------- + x : array_like + Real-valued + r : array_like + Positive, integer-valued parameter + p : array_like + Positive, real-valued parameter + + Returns + ------- + scalar or ndarray + + """) + +add_newdoc( + "_nbinom_sf", + """ + _nbinom_sf(x, r, p) + + Survival function of negative binomial distribution. + + Parameters + ---------- + x : array_like + Real-valued + r : array_like + Positive, integer-valued parameter + p : array_like + Positive, real-valued parameter + + Returns + ------- + scalar or ndarray + + """) + +add_newdoc( + "_nbinom_isf", + """ + _nbinom_isf(x, r, p) + + Inverse survival function of negative binomial distribution. + + Parameters + ---------- + x : array_like + Real-valued + r : array_like + Positive, integer-valued parameter + p : array_like + Positive, real-valued parameter + + Returns + ------- + scalar or ndarray + + """) + +add_newdoc( + "_nbinom_mean", + """ + _nbinom_mean(r, p) + + Mean of negative binomial distribution. + + Parameters + ---------- + r : array_like + Positive, integer-valued parameter + p : array_like + Positive, real-valued parameter + + Returns + ------- + scalar or ndarray + + """) + +add_newdoc( + "_nbinom_variance", + """ + _nbinom_variance(r, p) + + Variance of negative binomial distribution. + + Parameters + ---------- + r : array_like + Positive, integer-valued parameter + p : array_like + Positive, real-valued parameter + + Returns + ------- + scalar or ndarray + + """) + +add_newdoc( + "_nbinom_skewness", + """ + _nbinom_skewness(r, p) + + Skewness of negative binomial distribution. + + Parameters + ---------- + r : array_like + Positive, integer-valued parameter + p : array_like + Positive, real-valued parameter + + Returns + ------- + scalar or ndarray + + """) + +add_newdoc( + "_nbinom_kurtosis_excess", + """ + _nbinom_kurtosis_excess(r, p) + + Kurtosis excess of negative binomial distribution. + + Parameters + ---------- + r : array_like + Positive, integer-valued parameter + p : array_like + Positive, real-valued parameter + + Returns + ------- + scalar or ndarray + + """) + +add_newdoc( + "_hypergeom_pmf", + """ + _hypergeom_pmf(x, r, N, M) + + Probability mass function of hypergeometric distribution. + + Parameters + ---------- + x : array_like + Real-valued + r, N, M : array_like + Positive, integer-valued parameter + + Returns + ------- + scalar or ndarray + + """) + +add_newdoc( + "_hypergeom_cdf", + """ + _hypergeom_cdf(x, r, N, M) + + Cumulative density function of hypergeometric distribution. + + Parameters + ---------- + x : array_like + Real-valued + r, N, M : array_like + Positive, integer-valued parameter + + Returns + ------- + scalar or ndarray + """) + +add_newdoc( + "_hypergeom_sf", + """ + _hypergeom_sf(x, r, N, M) + + Survival function of hypergeometric distribution. + + Parameters + ---------- + x : array_like + Real-valued + r, N, M : array_like + Positive, integer-valued parameter + + Returns + ------- + scalar or ndarray + """) + +add_newdoc( + "_hypergeom_mean", + """ + _hypergeom_mean(r, N, M) + + Mean of hypergeometric distribution. + + Parameters + ---------- + r, N, M : array_like + Positive, integer-valued parameter + + Returns + ------- + scalar or ndarray + + """) + +add_newdoc( + "_hypergeom_variance", + """ + _hypergeom_variance(r, N, M) + + Variance of hypergeometric distribution. + + Parameters + ---------- + r, N, M : array_like + Positive, integer-valued parameter + + Returns + ------- + scalar or ndarray + + """) + +add_newdoc( + "_hypergeom_skewness", + """ + _hypergeom_skewness(r, N, M) + + Skewness of hypergeometric distribution. + + Parameters + ---------- + r, N, M : array_like + Positive, integer-valued parameter + + Returns + ------- + scalar or ndarray + + """) diff --git a/venv/lib/python3.12/site-packages/scipy/special/_basic.py b/venv/lib/python3.12/site-packages/scipy/special/_basic.py new file mode 100644 index 0000000000000000000000000000000000000000..8e017deb4db1eec2562a69f506fcdfed18d3de30 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/_basic.py @@ -0,0 +1,3504 @@ +# +# Author: Travis Oliphant, 2002 +# + +import numpy as np +import math +import warnings +from collections import defaultdict +from heapq import heapify, heappop +from numpy import (pi, asarray, floor, isscalar, sqrt, where, + sin, place, issubdtype, extract, inexact, nan, zeros, sinc) + +from . import _ufuncs +from ._ufuncs import (mathieu_a, mathieu_b, iv, jv, gamma, rgamma, + psi, hankel1, hankel2, yv, kv, poch, binom, + _stirling2_inexact) + +from ._gufuncs import _lqn, _lqmn, _rctj, _rcty +from ._input_validation import _nonneg_int_or_fail +from . import _specfun +from ._comb import _comb_int + + +__all__ = [ + 'ai_zeros', + 'assoc_laguerre', + 'bei_zeros', + 'beip_zeros', + 'ber_zeros', + 'bernoulli', + 'berp_zeros', + 'bi_zeros', + 'comb', + 'digamma', + 'diric', + 'erf_zeros', + 'euler', + 'factorial', + 'factorial2', + 'factorialk', + 'fresnel_zeros', + 'fresnelc_zeros', + 'fresnels_zeros', + 'h1vp', + 'h2vp', + 'ivp', + 'jn_zeros', + 'jnjnp_zeros', + 'jnp_zeros', + 'jnyn_zeros', + 'jvp', + 'kei_zeros', + 'keip_zeros', + 'kelvin_zeros', + 'ker_zeros', + 'kerp_zeros', + 'kvp', + 'lmbda', + 'lqmn', + 'lqn', + 'mathieu_even_coef', + 'mathieu_odd_coef', + 'obl_cv_seq', + 'pbdn_seq', + 'pbdv_seq', + 'pbvv_seq', + 'perm', + 'polygamma', + 'pro_cv_seq', + 'riccati_jn', + 'riccati_yn', + 'sinc', + 'softplus', + 'stirling2', + 'y0_zeros', + 'y1_zeros', + 'y1p_zeros', + 'yn_zeros', + 'ynp_zeros', + 'yvp', + 'zeta' +] + + +# mapping k to last n such that factorialk(n, k) < np.iinfo(np.int64).max +_FACTORIALK_LIMITS_64BITS = {1: 20, 2: 33, 3: 44, 4: 54, 5: 65, + 6: 74, 7: 84, 8: 93, 9: 101} +# mapping k to last n such that factorialk(n, k) < np.iinfo(np.int32).max +_FACTORIALK_LIMITS_32BITS = {1: 12, 2: 19, 3: 25, 4: 31, 5: 37, + 6: 43, 7: 47, 8: 51, 9: 56} + + +def diric(x, n): + """Periodic sinc function, also called the Dirichlet kernel. + + The Dirichlet kernel is defined as:: + + diric(x, n) = sin(x * n/2) / (n * sin(x / 2)), + + where `n` is a positive integer. + + Parameters + ---------- + x : array_like + Input data + n : int + Integer defining the periodicity. + + Returns + ------- + diric : ndarray + Value of periodic sinc function. + + Examples + -------- + >>> import numpy as np + >>> from scipy import special + >>> import matplotlib.pyplot as plt + + >>> x = np.linspace(-8*np.pi, 8*np.pi, num=201) + >>> plt.figure(figsize=(8, 8)); + >>> for idx, n in enumerate([2, 3, 4, 9]): + ... plt.subplot(2, 2, idx+1) + ... plt.plot(x, special.diric(x, n)) + ... plt.title('diric, n={}'.format(n)) + >>> plt.show() + + The following example demonstrates that `diric` gives the magnitudes + (modulo the sign and scaling) of the Fourier coefficients of a + rectangular pulse. + + Suppress output of values that are effectively 0: + + >>> np.set_printoptions(suppress=True) + + Create a signal `x` of length `m` with `k` ones: + + >>> m = 8 + >>> k = 3 + >>> x = np.zeros(m) + >>> x[:k] = 1 + + Use the FFT to compute the Fourier transform of `x`, and + inspect the magnitudes of the coefficients: + + >>> np.abs(np.fft.fft(x)) + array([ 3. , 2.41421356, 1. , 0.41421356, 1. , + 0.41421356, 1. , 2.41421356]) + + Now find the same values (up to sign) using `diric`. We multiply + by `k` to account for the different scaling conventions of + `numpy.fft.fft` and `diric`: + + >>> theta = np.linspace(0, 2*np.pi, m, endpoint=False) + >>> k * special.diric(theta, k) + array([ 3. , 2.41421356, 1. , -0.41421356, -1. , + -0.41421356, 1. , 2.41421356]) + """ + x, n = asarray(x), asarray(n) + n = asarray(n + (x-x)) + x = asarray(x + (n-n)) + if issubdtype(x.dtype, inexact): + ytype = x.dtype + else: + ytype = float + y = zeros(x.shape, ytype) + + # empirical minval for 32, 64 or 128 bit float computations + # where sin(x/2) < minval, result is fixed at +1 or -1 + if np.finfo(ytype).eps < 1e-18: + minval = 1e-11 + elif np.finfo(ytype).eps < 1e-15: + minval = 1e-7 + else: + minval = 1e-3 + + mask1 = (n <= 0) | (n != floor(n)) + place(y, mask1, nan) + + x = x / 2 + denom = sin(x) + mask2 = (1-mask1) & (abs(denom) < minval) + xsub = extract(mask2, x) + nsub = extract(mask2, n) + zsub = xsub / pi + place(y, mask2, pow(-1, np.round(zsub)*(nsub-1))) + + mask = (1-mask1) & (1-mask2) + xsub = extract(mask, x) + nsub = extract(mask, n) + dsub = extract(mask, denom) + place(y, mask, sin(nsub*xsub)/(nsub*dsub)) + return y + + +def jnjnp_zeros(nt): + """Compute zeros of integer-order Bessel functions Jn and Jn'. + + Results are arranged in order of the magnitudes of the zeros. + + Parameters + ---------- + nt : int + Number (<=1200) of zeros to compute + + Returns + ------- + zo[l-1] : ndarray + Value of the lth zero of Jn(x) and Jn'(x). Of length `nt`. + n[l-1] : ndarray + Order of the Jn(x) or Jn'(x) associated with lth zero. Of length `nt`. + m[l-1] : ndarray + Serial number of the zeros of Jn(x) or Jn'(x) associated + with lth zero. Of length `nt`. + t[l-1] : ndarray + 0 if lth zero in zo is zero of Jn(x), 1 if it is a zero of Jn'(x). Of + length `nt`. + + See Also + -------- + jn_zeros, jnp_zeros : to get separated arrays of zeros. + + References + ---------- + .. [1] Zhang, Shanjie and Jin, Jianming. "Computation of Special + Functions", John Wiley and Sons, 1996, chapter 5. + https://people.sc.fsu.edu/~jburkardt/f77_src/special_functions/special_functions.html + + """ + if not isscalar(nt) or (floor(nt) != nt) or (nt > 1200): + raise ValueError("Number must be integer <= 1200.") + nt = int(nt) + n, m, t, zo = _specfun.jdzo(nt) + return zo[1:nt+1], n[:nt], m[:nt], t[:nt] + + +def jnyn_zeros(n, nt): + """Compute nt zeros of Bessel functions Jn(x), Jn'(x), Yn(x), and Yn'(x). + + Returns 4 arrays of length `nt`, corresponding to the first `nt` + zeros of Jn(x), Jn'(x), Yn(x), and Yn'(x), respectively. The zeros + are returned in ascending order. + + Parameters + ---------- + n : int + Order of the Bessel functions + nt : int + Number (<=1200) of zeros to compute + + Returns + ------- + Jn : ndarray + First `nt` zeros of Jn + Jnp : ndarray + First `nt` zeros of Jn' + Yn : ndarray + First `nt` zeros of Yn + Ynp : ndarray + First `nt` zeros of Yn' + + See Also + -------- + jn_zeros, jnp_zeros, yn_zeros, ynp_zeros + + References + ---------- + .. [1] Zhang, Shanjie and Jin, Jianming. "Computation of Special + Functions", John Wiley and Sons, 1996, chapter 5. + https://people.sc.fsu.edu/~jburkardt/f77_src/special_functions/special_functions.html + + Examples + -------- + Compute the first three roots of :math:`J_1`, :math:`J_1'`, + :math:`Y_1` and :math:`Y_1'`. + + >>> from scipy.special import jnyn_zeros + >>> jn_roots, jnp_roots, yn_roots, ynp_roots = jnyn_zeros(1, 3) + >>> jn_roots, yn_roots + (array([ 3.83170597, 7.01558667, 10.17346814]), + array([2.19714133, 5.42968104, 8.59600587])) + + Plot :math:`J_1`, :math:`J_1'`, :math:`Y_1`, :math:`Y_1'` and their roots. + + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from scipy.special import jnyn_zeros, jvp, jn, yvp, yn + >>> jn_roots, jnp_roots, yn_roots, ynp_roots = jnyn_zeros(1, 3) + >>> fig, ax = plt.subplots() + >>> xmax= 11 + >>> x = np.linspace(0, xmax) + >>> x[0] += 1e-15 + >>> ax.plot(x, jn(1, x), label=r"$J_1$", c='r') + >>> ax.plot(x, jvp(1, x, 1), label=r"$J_1'$", c='b') + >>> ax.plot(x, yn(1, x), label=r"$Y_1$", c='y') + >>> ax.plot(x, yvp(1, x, 1), label=r"$Y_1'$", c='c') + >>> zeros = np.zeros((3, )) + >>> ax.scatter(jn_roots, zeros, s=30, c='r', zorder=5, + ... label=r"$J_1$ roots") + >>> ax.scatter(jnp_roots, zeros, s=30, c='b', zorder=5, + ... label=r"$J_1'$ roots") + >>> ax.scatter(yn_roots, zeros, s=30, c='y', zorder=5, + ... label=r"$Y_1$ roots") + >>> ax.scatter(ynp_roots, zeros, s=30, c='c', zorder=5, + ... label=r"$Y_1'$ roots") + >>> ax.hlines(0, 0, xmax, color='k') + >>> ax.set_ylim(-0.6, 0.6) + >>> ax.set_xlim(0, xmax) + >>> ax.legend(ncol=2, bbox_to_anchor=(1., 0.75)) + >>> plt.tight_layout() + >>> plt.show() + """ + if not (isscalar(nt) and isscalar(n)): + raise ValueError("Arguments must be scalars.") + if (floor(n) != n) or (floor(nt) != nt): + raise ValueError("Arguments must be integers.") + if (nt <= 0): + raise ValueError("nt > 0") + return _specfun.jyzo(abs(n), nt) + + +def jn_zeros(n, nt): + r"""Compute zeros of integer-order Bessel functions Jn. + + Compute `nt` zeros of the Bessel functions :math:`J_n(x)` on the + interval :math:`(0, \infty)`. The zeros are returned in ascending + order. Note that this interval excludes the zero at :math:`x = 0` + that exists for :math:`n > 0`. + + Parameters + ---------- + n : int + Order of Bessel function + nt : int + Number of zeros to return + + Returns + ------- + ndarray + First `nt` zeros of the Bessel function. + + See Also + -------- + jv: Real-order Bessel functions of the first kind + jnp_zeros: Zeros of :math:`Jn'` + + References + ---------- + .. [1] Zhang, Shanjie and Jin, Jianming. "Computation of Special + Functions", John Wiley and Sons, 1996, chapter 5. + https://people.sc.fsu.edu/~jburkardt/f77_src/special_functions/special_functions.html + + Examples + -------- + Compute the first four positive roots of :math:`J_3`. + + >>> from scipy.special import jn_zeros + >>> jn_zeros(3, 4) + array([ 6.3801619 , 9.76102313, 13.01520072, 16.22346616]) + + Plot :math:`J_3` and its first four positive roots. Note + that the root located at 0 is not returned by `jn_zeros`. + + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from scipy.special import jn, jn_zeros + >>> j3_roots = jn_zeros(3, 4) + >>> xmax = 18 + >>> xmin = -1 + >>> x = np.linspace(xmin, xmax, 500) + >>> fig, ax = plt.subplots() + >>> ax.plot(x, jn(3, x), label=r'$J_3$') + >>> ax.scatter(j3_roots, np.zeros((4, )), s=30, c='r', + ... label=r"$J_3$_Zeros", zorder=5) + >>> ax.scatter(0, 0, s=30, c='k', + ... label=r"Root at 0", zorder=5) + >>> ax.hlines(0, 0, xmax, color='k') + >>> ax.set_xlim(xmin, xmax) + >>> plt.legend() + >>> plt.show() + """ + return jnyn_zeros(n, nt)[0] + + +def jnp_zeros(n, nt): + r"""Compute zeros of integer-order Bessel function derivatives Jn'. + + Compute `nt` zeros of the functions :math:`J_n'(x)` on the + interval :math:`(0, \infty)`. The zeros are returned in ascending + order. Note that this interval excludes the zero at :math:`x = 0` + that exists for :math:`n > 1`. + + Parameters + ---------- + n : int + Order of Bessel function + nt : int + Number of zeros to return + + Returns + ------- + ndarray + First `nt` zeros of the Bessel function. + + See Also + -------- + jvp: Derivatives of integer-order Bessel functions of the first kind + jv: Float-order Bessel functions of the first kind + + References + ---------- + .. [1] Zhang, Shanjie and Jin, Jianming. "Computation of Special + Functions", John Wiley and Sons, 1996, chapter 5. + https://people.sc.fsu.edu/~jburkardt/f77_src/special_functions/special_functions.html + + Examples + -------- + Compute the first four roots of :math:`J_2'`. + + >>> from scipy.special import jnp_zeros + >>> jnp_zeros(2, 4) + array([ 3.05423693, 6.70613319, 9.96946782, 13.17037086]) + + As `jnp_zeros` yields the roots of :math:`J_n'`, it can be used to + compute the locations of the peaks of :math:`J_n`. Plot + :math:`J_2`, :math:`J_2'` and the locations of the roots of :math:`J_2'`. + + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from scipy.special import jn, jnp_zeros, jvp + >>> j2_roots = jnp_zeros(2, 4) + >>> xmax = 15 + >>> x = np.linspace(0, xmax, 500) + >>> fig, ax = plt.subplots() + >>> ax.plot(x, jn(2, x), label=r'$J_2$') + >>> ax.plot(x, jvp(2, x, 1), label=r"$J_2'$") + >>> ax.hlines(0, 0, xmax, color='k') + >>> ax.scatter(j2_roots, np.zeros((4, )), s=30, c='r', + ... label=r"Roots of $J_2'$", zorder=5) + >>> ax.set_ylim(-0.4, 0.8) + >>> ax.set_xlim(0, xmax) + >>> plt.legend() + >>> plt.show() + """ + return jnyn_zeros(n, nt)[1] + + +def yn_zeros(n, nt): + r"""Compute zeros of integer-order Bessel function Yn(x). + + Compute `nt` zeros of the functions :math:`Y_n(x)` on the interval + :math:`(0, \infty)`. The zeros are returned in ascending order. + + Parameters + ---------- + n : int + Order of Bessel function + nt : int + Number of zeros to return + + Returns + ------- + ndarray + First `nt` zeros of the Bessel function. + + See Also + -------- + yn: Bessel function of the second kind for integer order + yv: Bessel function of the second kind for real order + + References + ---------- + .. [1] Zhang, Shanjie and Jin, Jianming. "Computation of Special + Functions", John Wiley and Sons, 1996, chapter 5. + https://people.sc.fsu.edu/~jburkardt/f77_src/special_functions/special_functions.html + + Examples + -------- + Compute the first four roots of :math:`Y_2`. + + >>> from scipy.special import yn_zeros + >>> yn_zeros(2, 4) + array([ 3.38424177, 6.79380751, 10.02347798, 13.20998671]) + + Plot :math:`Y_2` and its first four roots. + + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from scipy.special import yn, yn_zeros + >>> xmin = 2 + >>> xmax = 15 + >>> x = np.linspace(xmin, xmax, 500) + >>> fig, ax = plt.subplots() + >>> ax.hlines(0, xmin, xmax, color='k') + >>> ax.plot(x, yn(2, x), label=r'$Y_2$') + >>> ax.scatter(yn_zeros(2, 4), np.zeros((4, )), s=30, c='r', + ... label='Roots', zorder=5) + >>> ax.set_ylim(-0.4, 0.4) + >>> ax.set_xlim(xmin, xmax) + >>> plt.legend() + >>> plt.show() + """ + return jnyn_zeros(n, nt)[2] + + +def ynp_zeros(n, nt): + r"""Compute zeros of integer-order Bessel function derivatives Yn'(x). + + Compute `nt` zeros of the functions :math:`Y_n'(x)` on the + interval :math:`(0, \infty)`. The zeros are returned in ascending + order. + + Parameters + ---------- + n : int + Order of Bessel function + nt : int + Number of zeros to return + + Returns + ------- + ndarray + First `nt` zeros of the Bessel derivative function. + + + See Also + -------- + yvp + + References + ---------- + .. [1] Zhang, Shanjie and Jin, Jianming. "Computation of Special + Functions", John Wiley and Sons, 1996, chapter 5. + https://people.sc.fsu.edu/~jburkardt/f77_src/special_functions/special_functions.html + + Examples + -------- + Compute the first four roots of the first derivative of the + Bessel function of second kind for order 0 :math:`Y_0'`. + + >>> from scipy.special import ynp_zeros + >>> ynp_zeros(0, 4) + array([ 2.19714133, 5.42968104, 8.59600587, 11.74915483]) + + Plot :math:`Y_0`, :math:`Y_0'` and confirm visually that the roots of + :math:`Y_0'` are located at local extrema of :math:`Y_0`. + + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from scipy.special import yn, ynp_zeros, yvp + >>> zeros = ynp_zeros(0, 4) + >>> xmax = 13 + >>> x = np.linspace(0, xmax, 500) + >>> fig, ax = plt.subplots() + >>> ax.plot(x, yn(0, x), label=r'$Y_0$') + >>> ax.plot(x, yvp(0, x, 1), label=r"$Y_0'$") + >>> ax.scatter(zeros, np.zeros((4, )), s=30, c='r', + ... label=r"Roots of $Y_0'$", zorder=5) + >>> for root in zeros: + ... y0_extremum = yn(0, root) + ... lower = min(0, y0_extremum) + ... upper = max(0, y0_extremum) + ... ax.vlines(root, lower, upper, color='r') + >>> ax.hlines(0, 0, xmax, color='k') + >>> ax.set_ylim(-0.6, 0.6) + >>> ax.set_xlim(0, xmax) + >>> plt.legend() + >>> plt.show() + """ + return jnyn_zeros(n, nt)[3] + + +def y0_zeros(nt, complex=False): + """Compute nt zeros of Bessel function Y0(z), and derivative at each zero. + + The derivatives are given by Y0'(z0) = -Y1(z0) at each zero z0. + + Parameters + ---------- + nt : int + Number of zeros to return + complex : bool, default False + Set to False to return only the real zeros; set to True to return only + the complex zeros with negative real part and positive imaginary part. + Note that the complex conjugates of the latter are also zeros of the + function, but are not returned by this routine. + + Returns + ------- + z0n : ndarray + Location of nth zero of Y0(z) + y0pz0n : ndarray + Value of derivative Y0'(z0) for nth zero + + References + ---------- + .. [1] Zhang, Shanjie and Jin, Jianming. "Computation of Special + Functions", John Wiley and Sons, 1996, chapter 5. + https://people.sc.fsu.edu/~jburkardt/f77_src/special_functions/special_functions.html + + Examples + -------- + Compute the first 4 real roots and the derivatives at the roots of + :math:`Y_0`: + + >>> import numpy as np + >>> from scipy.special import y0_zeros + >>> zeros, grads = y0_zeros(4) + >>> with np.printoptions(precision=5): + ... print(f"Roots: {zeros}") + ... print(f"Gradients: {grads}") + Roots: [ 0.89358+0.j 3.95768+0.j 7.08605+0.j 10.22235+0.j] + Gradients: [-0.87942+0.j 0.40254+0.j -0.3001 +0.j 0.2497 +0.j] + + Plot the real part of :math:`Y_0` and the first four computed roots. + + >>> import matplotlib.pyplot as plt + >>> from scipy.special import y0 + >>> xmin = 0 + >>> xmax = 11 + >>> x = np.linspace(xmin, xmax, 500) + >>> fig, ax = plt.subplots() + >>> ax.hlines(0, xmin, xmax, color='k') + >>> ax.plot(x, y0(x), label=r'$Y_0$') + >>> zeros, grads = y0_zeros(4) + >>> ax.scatter(zeros.real, np.zeros((4, )), s=30, c='r', + ... label=r'$Y_0$_zeros', zorder=5) + >>> ax.set_ylim(-0.5, 0.6) + >>> ax.set_xlim(xmin, xmax) + >>> plt.legend(ncol=2) + >>> plt.show() + + Compute the first 4 complex roots and the derivatives at the roots of + :math:`Y_0` by setting ``complex=True``: + + >>> y0_zeros(4, True) + (array([ -2.40301663+0.53988231j, -5.5198767 +0.54718001j, + -8.6536724 +0.54841207j, -11.79151203+0.54881912j]), + array([ 0.10074769-0.88196771j, -0.02924642+0.5871695j , + 0.01490806-0.46945875j, -0.00937368+0.40230454j])) + """ + if not isscalar(nt) or (floor(nt) != nt) or (nt <= 0): + raise ValueError("Arguments must be scalar positive integer.") + kf = 0 + kc = not complex + return _specfun.cyzo(nt, kf, kc) + + +def y1_zeros(nt, complex=False): + """Compute nt zeros of Bessel function Y1(z), and derivative at each zero. + + The derivatives are given by Y1'(z1) = Y0(z1) at each zero z1. + + Parameters + ---------- + nt : int + Number of zeros to return + complex : bool, default False + Set to False to return only the real zeros; set to True to return only + the complex zeros with negative real part and positive imaginary part. + Note that the complex conjugates of the latter are also zeros of the + function, but are not returned by this routine. + + Returns + ------- + z1n : ndarray + Location of nth zero of Y1(z) + y1pz1n : ndarray + Value of derivative Y1'(z1) for nth zero + + References + ---------- + .. [1] Zhang, Shanjie and Jin, Jianming. "Computation of Special + Functions", John Wiley and Sons, 1996, chapter 5. + https://people.sc.fsu.edu/~jburkardt/f77_src/special_functions/special_functions.html + + Examples + -------- + Compute the first 4 real roots and the derivatives at the roots of + :math:`Y_1`: + + >>> import numpy as np + >>> from scipy.special import y1_zeros + >>> zeros, grads = y1_zeros(4) + >>> with np.printoptions(precision=5): + ... print(f"Roots: {zeros}") + ... print(f"Gradients: {grads}") + Roots: [ 2.19714+0.j 5.42968+0.j 8.59601+0.j 11.74915+0.j] + Gradients: [ 0.52079+0.j -0.34032+0.j 0.27146+0.j -0.23246+0.j] + + Extract the real parts: + + >>> realzeros = zeros.real + >>> realzeros + array([ 2.19714133, 5.42968104, 8.59600587, 11.74915483]) + + Plot :math:`Y_1` and the first four computed roots. + + >>> import matplotlib.pyplot as plt + >>> from scipy.special import y1 + >>> xmin = 0 + >>> xmax = 13 + >>> x = np.linspace(xmin, xmax, 500) + >>> zeros, grads = y1_zeros(4) + >>> fig, ax = plt.subplots() + >>> ax.hlines(0, xmin, xmax, color='k') + >>> ax.plot(x, y1(x), label=r'$Y_1$') + >>> ax.scatter(zeros.real, np.zeros((4, )), s=30, c='r', + ... label=r'$Y_1$_zeros', zorder=5) + >>> ax.set_ylim(-0.5, 0.5) + >>> ax.set_xlim(xmin, xmax) + >>> plt.legend() + >>> plt.show() + + Compute the first 4 complex roots and the derivatives at the roots of + :math:`Y_1` by setting ``complex=True``: + + >>> y1_zeros(4, True) + (array([ -0.50274327+0.78624371j, -3.83353519+0.56235654j, + -7.01590368+0.55339305j, -10.17357383+0.55127339j]), + array([-0.45952768+1.31710194j, 0.04830191-0.69251288j, + -0.02012695+0.51864253j, 0.011614 -0.43203296j])) + """ + if not isscalar(nt) or (floor(nt) != nt) or (nt <= 0): + raise ValueError("Arguments must be scalar positive integer.") + kf = 1 + kc = not complex + return _specfun.cyzo(nt, kf, kc) + + +def y1p_zeros(nt, complex=False): + """Compute nt zeros of Bessel derivative Y1'(z), and value at each zero. + + The values are given by Y1(z1) at each z1 where Y1'(z1)=0. + + Parameters + ---------- + nt : int + Number of zeros to return + complex : bool, default False + Set to False to return only the real zeros; set to True to return only + the complex zeros with negative real part and positive imaginary part. + Note that the complex conjugates of the latter are also zeros of the + function, but are not returned by this routine. + + Returns + ------- + z1pn : ndarray + Location of nth zero of Y1'(z) + y1z1pn : ndarray + Value of derivative Y1(z1) for nth zero + + References + ---------- + .. [1] Zhang, Shanjie and Jin, Jianming. "Computation of Special + Functions", John Wiley and Sons, 1996, chapter 5. + https://people.sc.fsu.edu/~jburkardt/f77_src/special_functions/special_functions.html + + Examples + -------- + Compute the first four roots of :math:`Y_1'` and the values of + :math:`Y_1` at these roots. + + >>> import numpy as np + >>> from scipy.special import y1p_zeros + >>> y1grad_roots, y1_values = y1p_zeros(4) + >>> with np.printoptions(precision=5): + ... print(f"Y1' Roots: {y1grad_roots.real}") + ... print(f"Y1 values: {y1_values.real}") + Y1' Roots: [ 3.68302 6.9415 10.1234 13.28576] + Y1 values: [ 0.41673 -0.30317 0.25091 -0.21897] + + `y1p_zeros` can be used to calculate the extremal points of :math:`Y_1` + directly. Here we plot :math:`Y_1` and the first four extrema. + + >>> import matplotlib.pyplot as plt + >>> from scipy.special import y1, yvp + >>> y1_roots, y1_values_at_roots = y1p_zeros(4) + >>> real_roots = y1_roots.real + >>> xmax = 15 + >>> x = np.linspace(0, xmax, 500) + >>> x[0] += 1e-15 + >>> fig, ax = plt.subplots() + >>> ax.plot(x, y1(x), label=r'$Y_1$') + >>> ax.plot(x, yvp(1, x, 1), label=r"$Y_1'$") + >>> ax.scatter(real_roots, np.zeros((4, )), s=30, c='r', + ... label=r"Roots of $Y_1'$", zorder=5) + >>> ax.scatter(real_roots, y1_values_at_roots.real, s=30, c='k', + ... label=r"Extrema of $Y_1$", zorder=5) + >>> ax.hlines(0, 0, xmax, color='k') + >>> ax.set_ylim(-0.5, 0.5) + >>> ax.set_xlim(0, xmax) + >>> ax.legend(ncol=2, bbox_to_anchor=(1., 0.75)) + >>> plt.tight_layout() + >>> plt.show() + """ + if not isscalar(nt) or (floor(nt) != nt) or (nt <= 0): + raise ValueError("Arguments must be scalar positive integer.") + kf = 2 + kc = not complex + return _specfun.cyzo(nt, kf, kc) + + +def _bessel_diff_formula(v, z, n, L, phase): + # from AMS55. + # L(v, z) = J(v, z), Y(v, z), H1(v, z), H2(v, z), phase = -1 + # L(v, z) = I(v, z) or exp(v*pi*i)K(v, z), phase = 1 + # For K, you can pull out the exp((v-k)*pi*i) into the caller + v = asarray(v) + p = 1.0 + s = L(v-n, z) + for i in range(1, n+1): + p = phase * (p * (n-i+1)) / i # = choose(k, i) + s += p*L(v-n + i*2, z) + return s / (2.**n) + + +def jvp(v, z, n=1): + """Compute derivatives of Bessel functions of the first kind. + + Compute the nth derivative of the Bessel function `Jv` with + respect to `z`. + + Parameters + ---------- + v : array_like or float + Order of Bessel function + z : complex + Argument at which to evaluate the derivative; can be real or + complex. + n : int, default 1 + Order of derivative. For 0 returns the Bessel function `jv` itself. + + Returns + ------- + scalar or ndarray + Values of the derivative of the Bessel function. + + Notes + ----- + The derivative is computed using the relation DLFM 10.6.7 [2]_. + + References + ---------- + .. [1] Zhang, Shanjie and Jin, Jianming. "Computation of Special + Functions", John Wiley and Sons, 1996, chapter 5. + https://people.sc.fsu.edu/~jburkardt/f77_src/special_functions/special_functions.html + + .. [2] NIST Digital Library of Mathematical Functions. + https://dlmf.nist.gov/10.6.E7 + + Examples + -------- + + Compute the Bessel function of the first kind of order 0 and + its first two derivatives at 1. + + >>> from scipy.special import jvp + >>> jvp(0, 1, 0), jvp(0, 1, 1), jvp(0, 1, 2) + (0.7651976865579666, -0.44005058574493355, -0.3251471008130331) + + Compute the first derivative of the Bessel function of the first + kind for several orders at 1 by providing an array for `v`. + + >>> jvp([0, 1, 2], 1, 1) + array([-0.44005059, 0.3251471 , 0.21024362]) + + Compute the first derivative of the Bessel function of the first + kind of order 0 at several points by providing an array for `z`. + + >>> import numpy as np + >>> points = np.array([0., 1.5, 3.]) + >>> jvp(0, points, 1) + array([-0. , -0.55793651, -0.33905896]) + + Plot the Bessel function of the first kind of order 1 and its + first three derivatives. + + >>> import matplotlib.pyplot as plt + >>> x = np.linspace(-10, 10, 1000) + >>> fig, ax = plt.subplots() + >>> ax.plot(x, jvp(1, x, 0), label=r"$J_1$") + >>> ax.plot(x, jvp(1, x, 1), label=r"$J_1'$") + >>> ax.plot(x, jvp(1, x, 2), label=r"$J_1''$") + >>> ax.plot(x, jvp(1, x, 3), label=r"$J_1'''$") + >>> plt.legend() + >>> plt.show() + """ + n = _nonneg_int_or_fail(n, 'n') + if n == 0: + return jv(v, z) + else: + return _bessel_diff_formula(v, z, n, jv, -1) + + +def yvp(v, z, n=1): + """Compute derivatives of Bessel functions of the second kind. + + Compute the nth derivative of the Bessel function `Yv` with + respect to `z`. + + Parameters + ---------- + v : array_like of float + Order of Bessel function + z : complex + Argument at which to evaluate the derivative + n : int, default 1 + Order of derivative. For 0 returns the BEssel function `yv` + + Returns + ------- + scalar or ndarray + nth derivative of the Bessel function. + + See Also + -------- + yv : Bessel functions of the second kind + + Notes + ----- + The derivative is computed using the relation DLFM 10.6.7 [2]_. + + References + ---------- + .. [1] Zhang, Shanjie and Jin, Jianming. "Computation of Special + Functions", John Wiley and Sons, 1996, chapter 5. + https://people.sc.fsu.edu/~jburkardt/f77_src/special_functions/special_functions.html + + .. [2] NIST Digital Library of Mathematical Functions. + https://dlmf.nist.gov/10.6.E7 + + Examples + -------- + Compute the Bessel function of the second kind of order 0 and + its first two derivatives at 1. + + >>> from scipy.special import yvp + >>> yvp(0, 1, 0), yvp(0, 1, 1), yvp(0, 1, 2) + (0.088256964215677, 0.7812128213002889, -0.8694697855159659) + + Compute the first derivative of the Bessel function of the second + kind for several orders at 1 by providing an array for `v`. + + >>> yvp([0, 1, 2], 1, 1) + array([0.78121282, 0.86946979, 2.52015239]) + + Compute the first derivative of the Bessel function of the + second kind of order 0 at several points by providing an array for `z`. + + >>> import numpy as np + >>> points = np.array([0.5, 1.5, 3.]) + >>> yvp(0, points, 1) + array([ 1.47147239, 0.41230863, -0.32467442]) + + Plot the Bessel function of the second kind of order 1 and its + first three derivatives. + + >>> import matplotlib.pyplot as plt + >>> x = np.linspace(0, 5, 1000) + >>> x[0] += 1e-15 + >>> fig, ax = plt.subplots() + >>> ax.plot(x, yvp(1, x, 0), label=r"$Y_1$") + >>> ax.plot(x, yvp(1, x, 1), label=r"$Y_1'$") + >>> ax.plot(x, yvp(1, x, 2), label=r"$Y_1''$") + >>> ax.plot(x, yvp(1, x, 3), label=r"$Y_1'''$") + >>> ax.set_ylim(-10, 10) + >>> plt.legend() + >>> plt.show() + """ + n = _nonneg_int_or_fail(n, 'n') + if n == 0: + return yv(v, z) + else: + return _bessel_diff_formula(v, z, n, yv, -1) + + +def kvp(v, z, n=1): + """Compute derivatives of real-order modified Bessel function Kv(z). + + Kv(z) is the modified Bessel function of the second kind. + Derivative is calculated with respect to `z`. + + Parameters + ---------- + v : array_like of float + Order of Bessel function + z : array_like of complex + Argument at which to evaluate the derivative + n : int, default 1 + Order of derivative. For 0 returns the Bessel function `kv` itself. + + Returns + ------- + out : ndarray + The results + + See Also + -------- + kv + + Notes + ----- + The derivative is computed using the relation DLFM 10.29.5 [2]_. + + References + ---------- + .. [1] Zhang, Shanjie and Jin, Jianming. "Computation of Special + Functions", John Wiley and Sons, 1996, chapter 6. + https://people.sc.fsu.edu/~jburkardt/f77_src/special_functions/special_functions.html + + .. [2] NIST Digital Library of Mathematical Functions. + https://dlmf.nist.gov/10.29.E5 + + Examples + -------- + Compute the modified bessel function of the second kind of order 0 and + its first two derivatives at 1. + + >>> from scipy.special import kvp + >>> kvp(0, 1, 0), kvp(0, 1, 1), kvp(0, 1, 2) + (0.42102443824070834, -0.6019072301972346, 1.0229316684379428) + + Compute the first derivative of the modified Bessel function of the second + kind for several orders at 1 by providing an array for `v`. + + >>> kvp([0, 1, 2], 1, 1) + array([-0.60190723, -1.02293167, -3.85158503]) + + Compute the first derivative of the modified Bessel function of the + second kind of order 0 at several points by providing an array for `z`. + + >>> import numpy as np + >>> points = np.array([0.5, 1.5, 3.]) + >>> kvp(0, points, 1) + array([-1.65644112, -0.2773878 , -0.04015643]) + + Plot the modified bessel function of the second kind and its + first three derivatives. + + >>> import matplotlib.pyplot as plt + >>> x = np.linspace(0, 5, 1000) + >>> fig, ax = plt.subplots() + >>> ax.plot(x, kvp(1, x, 0), label=r"$K_1$") + >>> ax.plot(x, kvp(1, x, 1), label=r"$K_1'$") + >>> ax.plot(x, kvp(1, x, 2), label=r"$K_1''$") + >>> ax.plot(x, kvp(1, x, 3), label=r"$K_1'''$") + >>> ax.set_ylim(-2.5, 2.5) + >>> plt.legend() + >>> plt.show() + """ + n = _nonneg_int_or_fail(n, 'n') + if n == 0: + return kv(v, z) + else: + return (-1)**n * _bessel_diff_formula(v, z, n, kv, 1) + + +def ivp(v, z, n=1): + """Compute derivatives of modified Bessel functions of the first kind. + + Compute the nth derivative of the modified Bessel function `Iv` + with respect to `z`. + + Parameters + ---------- + v : array_like or float + Order of Bessel function + z : array_like + Argument at which to evaluate the derivative; can be real or + complex. + n : int, default 1 + Order of derivative. For 0, returns the Bessel function `iv` itself. + + Returns + ------- + scalar or ndarray + nth derivative of the modified Bessel function. + + See Also + -------- + iv + + Notes + ----- + The derivative is computed using the relation DLFM 10.29.5 [2]_. + + References + ---------- + .. [1] Zhang, Shanjie and Jin, Jianming. "Computation of Special + Functions", John Wiley and Sons, 1996, chapter 6. + https://people.sc.fsu.edu/~jburkardt/f77_src/special_functions/special_functions.html + + .. [2] NIST Digital Library of Mathematical Functions. + https://dlmf.nist.gov/10.29.E5 + + Examples + -------- + Compute the modified Bessel function of the first kind of order 0 and + its first two derivatives at 1. + + >>> from scipy.special import ivp + >>> ivp(0, 1, 0), ivp(0, 1, 1), ivp(0, 1, 2) + (1.2660658777520084, 0.565159103992485, 0.7009067737595233) + + Compute the first derivative of the modified Bessel function of the first + kind for several orders at 1 by providing an array for `v`. + + >>> ivp([0, 1, 2], 1, 1) + array([0.5651591 , 0.70090677, 0.29366376]) + + Compute the first derivative of the modified Bessel function of the + first kind of order 0 at several points by providing an array for `z`. + + >>> import numpy as np + >>> points = np.array([0., 1.5, 3.]) + >>> ivp(0, points, 1) + array([0. , 0.98166643, 3.95337022]) + + Plot the modified Bessel function of the first kind of order 1 and its + first three derivatives. + + >>> import matplotlib.pyplot as plt + >>> x = np.linspace(-5, 5, 1000) + >>> fig, ax = plt.subplots() + >>> ax.plot(x, ivp(1, x, 0), label=r"$I_1$") + >>> ax.plot(x, ivp(1, x, 1), label=r"$I_1'$") + >>> ax.plot(x, ivp(1, x, 2), label=r"$I_1''$") + >>> ax.plot(x, ivp(1, x, 3), label=r"$I_1'''$") + >>> plt.legend() + >>> plt.show() + """ + n = _nonneg_int_or_fail(n, 'n') + if n == 0: + return iv(v, z) + else: + return _bessel_diff_formula(v, z, n, iv, 1) + + +def h1vp(v, z, n=1): + """Compute derivatives of Hankel function H1v(z) with respect to `z`. + + Parameters + ---------- + v : array_like + Order of Hankel function + z : array_like + Argument at which to evaluate the derivative. Can be real or + complex. + n : int, default 1 + Order of derivative. For 0 returns the Hankel function `hankel1` itself. + + Returns + ------- + scalar or ndarray + Values of the derivative of the Hankel function. + + See Also + -------- + hankel1 + + Notes + ----- + The derivative is computed using the relation DLFM 10.6.7 [2]_. + + References + ---------- + .. [1] Zhang, Shanjie and Jin, Jianming. "Computation of Special + Functions", John Wiley and Sons, 1996, chapter 5. + https://people.sc.fsu.edu/~jburkardt/f77_src/special_functions/special_functions.html + + .. [2] NIST Digital Library of Mathematical Functions. + https://dlmf.nist.gov/10.6.E7 + + Examples + -------- + Compute the Hankel function of the first kind of order 0 and + its first two derivatives at 1. + + >>> from scipy.special import h1vp + >>> h1vp(0, 1, 0), h1vp(0, 1, 1), h1vp(0, 1, 2) + ((0.7651976865579664+0.088256964215677j), + (-0.44005058574493355+0.7812128213002889j), + (-0.3251471008130329-0.8694697855159659j)) + + Compute the first derivative of the Hankel function of the first kind + for several orders at 1 by providing an array for `v`. + + >>> h1vp([0, 1, 2], 1, 1) + array([-0.44005059+0.78121282j, 0.3251471 +0.86946979j, + 0.21024362+2.52015239j]) + + Compute the first derivative of the Hankel function of the first kind + of order 0 at several points by providing an array for `z`. + + >>> import numpy as np + >>> points = np.array([0.5, 1.5, 3.]) + >>> h1vp(0, points, 1) + array([-0.24226846+1.47147239j, -0.55793651+0.41230863j, + -0.33905896-0.32467442j]) + """ + n = _nonneg_int_or_fail(n, 'n') + if n == 0: + return hankel1(v, z) + else: + return _bessel_diff_formula(v, z, n, hankel1, -1) + + +def h2vp(v, z, n=1): + """Compute derivatives of Hankel function H2v(z) with respect to `z`. + + Parameters + ---------- + v : array_like + Order of Hankel function + z : array_like + Argument at which to evaluate the derivative. Can be real or + complex. + n : int, default 1 + Order of derivative. For 0 returns the Hankel function `hankel2` itself. + + Returns + ------- + scalar or ndarray + Values of the derivative of the Hankel function. + + See Also + -------- + hankel2 + + Notes + ----- + The derivative is computed using the relation DLFM 10.6.7 [2]_. + + References + ---------- + .. [1] Zhang, Shanjie and Jin, Jianming. "Computation of Special + Functions", John Wiley and Sons, 1996, chapter 5. + https://people.sc.fsu.edu/~jburkardt/f77_src/special_functions/special_functions.html + + .. [2] NIST Digital Library of Mathematical Functions. + https://dlmf.nist.gov/10.6.E7 + + Examples + -------- + Compute the Hankel function of the second kind of order 0 and + its first two derivatives at 1. + + >>> from scipy.special import h2vp + >>> h2vp(0, 1, 0), h2vp(0, 1, 1), h2vp(0, 1, 2) + ((0.7651976865579664-0.088256964215677j), + (-0.44005058574493355-0.7812128213002889j), + (-0.3251471008130329+0.8694697855159659j)) + + Compute the first derivative of the Hankel function of the second kind + for several orders at 1 by providing an array for `v`. + + >>> h2vp([0, 1, 2], 1, 1) + array([-0.44005059-0.78121282j, 0.3251471 -0.86946979j, + 0.21024362-2.52015239j]) + + Compute the first derivative of the Hankel function of the second kind + of order 0 at several points by providing an array for `z`. + + >>> import numpy as np + >>> points = np.array([0.5, 1.5, 3.]) + >>> h2vp(0, points, 1) + array([-0.24226846-1.47147239j, -0.55793651-0.41230863j, + -0.33905896+0.32467442j]) + """ + n = _nonneg_int_or_fail(n, 'n') + if n == 0: + return hankel2(v, z) + else: + return _bessel_diff_formula(v, z, n, hankel2, -1) + + +def riccati_jn(n, x): + r"""Compute Riccati-Bessel function of the first kind and its derivative. + + The Riccati-Bessel function of the first kind is defined as :math:`x + j_n(x)`, where :math:`j_n` is the spherical Bessel function of the first + kind of order :math:`n`. + + This function computes the value and first derivative of the + Riccati-Bessel function for all orders up to and including `n`. + + Parameters + ---------- + n : int + Maximum order of function to compute + x : float + Argument at which to evaluate + + Returns + ------- + jn : ndarray + Value of j0(x), ..., jn(x) + jnp : ndarray + First derivative j0'(x), ..., jn'(x) + + Notes + ----- + The computation is carried out via backward recurrence, using the + relation DLMF 10.51.1 [2]_. + + Wrapper for a Fortran routine created by Shanjie Zhang and Jianming + Jin [1]_. + + References + ---------- + .. [1] Zhang, Shanjie and Jin, Jianming. "Computation of Special + Functions", John Wiley and Sons, 1996. + https://people.sc.fsu.edu/~jburkardt/f77_src/special_functions/special_functions.html + .. [2] NIST Digital Library of Mathematical Functions. + https://dlmf.nist.gov/10.51.E1 + + """ + if not (isscalar(n) and isscalar(x)): + raise ValueError("arguments must be scalars.") + n = _nonneg_int_or_fail(n, 'n', strict=False) + if (n == 0): + n1 = 1 + else: + n1 = n + + jn = np.empty((n1 + 1,), dtype=np.float64) + jnp = np.empty_like(jn) + + _rctj(x, out=(jn, jnp)) + return jn[:(n+1)], jnp[:(n+1)] + + +def riccati_yn(n, x): + """Compute Riccati-Bessel function of the second kind and its derivative. + + The Riccati-Bessel function of the second kind is defined here as :math:`+x + y_n(x)`, where :math:`y_n` is the spherical Bessel function of the second + kind of order :math:`n`. *Note that this is in contrast to a common convention + that includes a minus sign in the definition.* + + This function computes the value and first derivative of the function for + all orders up to and including `n`. + + Parameters + ---------- + n : int + Maximum order of function to compute + x : float + Argument at which to evaluate + + Returns + ------- + yn : ndarray + Value of y0(x), ..., yn(x) + ynp : ndarray + First derivative y0'(x), ..., yn'(x) + + Notes + ----- + The computation is carried out via ascending recurrence, using the + relation DLMF 10.51.1 [2]_. + + Wrapper for a Fortran routine created by Shanjie Zhang and Jianming + Jin [1]_. + + References + ---------- + .. [1] Zhang, Shanjie and Jin, Jianming. "Computation of Special + Functions", John Wiley and Sons, 1996. + https://people.sc.fsu.edu/~jburkardt/f77_src/special_functions/special_functions.html + .. [2] NIST Digital Library of Mathematical Functions. + https://dlmf.nist.gov/10.51.E1 + + """ + if not (isscalar(n) and isscalar(x)): + raise ValueError("arguments must be scalars.") + n = _nonneg_int_or_fail(n, 'n', strict=False) + if (n == 0): + n1 = 1 + else: + n1 = n + + yn = np.empty((n1 + 1,), dtype=np.float64) + ynp = np.empty_like(yn) + _rcty(x, out=(yn, ynp)) + + return yn[:(n+1)], ynp[:(n+1)] + + +def erf_zeros(nt): + """Compute the first nt zero in the first quadrant, ordered by absolute value. + + Zeros in the other quadrants can be obtained by using the symmetries + erf(-z) = erf(z) and erf(conj(z)) = conj(erf(z)). + + + Parameters + ---------- + nt : int + The number of zeros to compute + + Returns + ------- + The locations of the zeros of erf : ndarray (complex) + Complex values at which zeros of erf(z) + + References + ---------- + .. [1] Zhang, Shanjie and Jin, Jianming. "Computation of Special + Functions", John Wiley and Sons, 1996. + https://people.sc.fsu.edu/~jburkardt/f77_src/special_functions/special_functions.html + + Examples + -------- + >>> from scipy import special + >>> special.erf_zeros(1) + array([1.45061616+1.880943j]) + + Check that erf is (close to) zero for the value returned by erf_zeros + + >>> special.erf(special.erf_zeros(1)) + array([4.95159469e-14-1.16407394e-16j]) + + """ + if (floor(nt) != nt) or (nt <= 0) or not isscalar(nt): + raise ValueError("Argument must be positive scalar integer.") + return _specfun.cerzo(nt) + + +def fresnelc_zeros(nt): + """Compute nt complex zeros of cosine Fresnel integral C(z). + + Parameters + ---------- + nt : int + Number of zeros to compute + + Returns + ------- + fresnelc_zeros: ndarray + Zeros of the cosine Fresnel integral + + References + ---------- + .. [1] Zhang, Shanjie and Jin, Jianming. "Computation of Special + Functions", John Wiley and Sons, 1996. + https://people.sc.fsu.edu/~jburkardt/f77_src/special_functions/special_functions.html + + """ + if (floor(nt) != nt) or (nt <= 0) or not isscalar(nt): + raise ValueError("Argument must be positive scalar integer.") + return _specfun.fcszo(1, nt) + + +def fresnels_zeros(nt): + """Compute nt complex zeros of sine Fresnel integral S(z). + + Parameters + ---------- + nt : int + Number of zeros to compute + + Returns + ------- + fresnels_zeros: ndarray + Zeros of the sine Fresnel integral + + References + ---------- + .. [1] Zhang, Shanjie and Jin, Jianming. "Computation of Special + Functions", John Wiley and Sons, 1996. + https://people.sc.fsu.edu/~jburkardt/f77_src/special_functions/special_functions.html + + """ + if (floor(nt) != nt) or (nt <= 0) or not isscalar(nt): + raise ValueError("Argument must be positive scalar integer.") + return _specfun.fcszo(2, nt) + + +def fresnel_zeros(nt): + """Compute nt complex zeros of sine and cosine Fresnel integrals S(z) and C(z). + + Parameters + ---------- + nt : int + Number of zeros to compute + + Returns + ------- + zeros_sine: ndarray + Zeros of the sine Fresnel integral + zeros_cosine : ndarray + Zeros of the cosine Fresnel integral + + References + ---------- + .. [1] Zhang, Shanjie and Jin, Jianming. "Computation of Special + Functions", John Wiley and Sons, 1996. + https://people.sc.fsu.edu/~jburkardt/f77_src/special_functions/special_functions.html + + """ + if (floor(nt) != nt) or (nt <= 0) or not isscalar(nt): + raise ValueError("Argument must be positive scalar integer.") + return _specfun.fcszo(2, nt), _specfun.fcszo(1, nt) + + +def assoc_laguerre(x, n, k=0.0): + r"""Compute the generalized (associated) Laguerre polynomial of degree :math:`n` + and order :math:`k`. + + This function evaluates the generalized Laguerre polynomial + + .. math:: + + L^{(k)}_n(x). + + The polynomial is orthogonal over :math:`[0, \infty)` with weight + function + + .. math:: + + e^{-x}x^k + + for :math:`k > -1`. + + Parameters + ---------- + x : float or ndarray + Points where to evaluate the Laguerre polynomial + n : int + Degree of the Laguerre polynomial + k : int + Order of the Laguerre polynomial + + Returns + ------- + assoc_laguerre: float or ndarray + Associated laguerre polynomial values + + Notes + ----- + `assoc_laguerre` is a simple wrapper around `eval_genlaguerre`, with + reversed argument order ``(x, n, k=0.0) --> (n, k, x)``. + + Examples + -------- + Evaluate the associated Laguerre polynomial :math:`L_3^{(2)}` at + :math:`x = 1`: + + >>> import numpy as np + >>> from scipy.special import assoc_laguerre, eval_genlaguerre + >>> np.isclose(assoc_laguerre(1, 3, 2), 7/3) + True + + `assoc_laguerre` is equivalent to `eval_genlaguerre` with reversed + argument order: + + >>> x = np.linspace(0, 5, 6) + >>> np.allclose(assoc_laguerre(x, 3, 2), eval_genlaguerre(3, 2, x)) + True + + Plot :math:`L_3^{(k)}` for several values of :math:`k`: + + >>> import matplotlib.pyplot as plt + >>> x = np.linspace(0, 8, 400) + >>> fig, ax = plt.subplots() + >>> for k in range(3): + ... ax.plot(x, assoc_laguerre(x, 3, k), label=rf"$k={k}$") + >>> ax.set_title(r"Associated Laguerre polynomials $L_3^{(k)}$") + >>> ax.set_xlabel("x") + >>> ax.legend(loc="best") + >>> plt.show() + + """ + return _ufuncs.eval_genlaguerre(n, k, x) + + +digamma = psi + + +def polygamma(n, x): + r"""Polygamma functions. + + Defined as :math:`\psi^{(n)}(x)` where :math:`\psi` is the + `digamma` function. See [dlmf]_ for details. + + Parameters + ---------- + n : array_like + The order of the derivative of the digamma function; must be + integral + x : array_like + Real valued input + + Returns + ------- + ndarray + Function results + + See Also + -------- + digamma + + References + ---------- + .. [dlmf] NIST, Digital Library of Mathematical Functions, + https://dlmf.nist.gov/5.15 + + Examples + -------- + >>> from scipy import special + >>> x = [2, 3, 25.5] + >>> special.polygamma(1, x) + array([ 0.64493407, 0.39493407, 0.03999467]) + >>> special.polygamma(0, x) == special.psi(x) + array([ True, True, True], dtype=bool) + + """ + n, x = asarray(n), asarray(x) + fac2 = (-1.0)**(n+1) * gamma(n+1.0) * zeta(n+1, x) + return where(n == 0, psi(x), fac2) + + +def mathieu_even_coef(m, q): + r"""Fourier coefficients for even Mathieu and modified Mathieu functions. + + The Fourier series of the even solutions of the Mathieu differential + equation are of the form + + .. math:: \mathrm{ce}_{2n}(z, q) = \sum_{k=0}^{\infty} A_{(2n)}^{(2k)} \cos 2kz + + .. math:: \mathrm{ce}_{2n+1}(z, q) = + \sum_{k=0}^{\infty} A_{(2n+1)}^{(2k+1)} \cos (2k+1)z + + This function returns the coefficients :math:`A_{(2n)}^{(2k)}` for even + input m=2n, and the coefficients :math:`A_{(2n+1)}^{(2k+1)}` for odd input + m=2n+1. + + Parameters + ---------- + m : int + Order of Mathieu functions. Must be non-negative. + q : float (>=0) + Parameter of Mathieu functions. Must be non-negative. + + Returns + ------- + Ak : ndarray + Even or odd Fourier coefficients, corresponding to even or odd m. + + References + ---------- + .. [1] Zhang, Shanjie and Jin, Jianming. "Computation of Special + Functions", John Wiley and Sons, 1996. + https://people.sc.fsu.edu/~jburkardt/f77_src/special_functions/special_functions.html + .. [2] NIST Digital Library of Mathematical Functions + https://dlmf.nist.gov/28.4#i + + """ + if not (isscalar(m) and isscalar(q)): + raise ValueError("m and q must be scalars.") + if (q < 0): + raise ValueError("q >=0") + if (m != floor(m)) or (m < 0): + raise ValueError("m must be an integer >=0.") + + if (q <= 1): + qm = 7.5 + 56.1*sqrt(q) - 134.7*q + 90.7*sqrt(q)*q + else: + qm = 17.0 + 3.1*sqrt(q) - .126*q + .0037*sqrt(q)*q + km = int(qm + 0.5*m) + if km > 251: + warnings.warn("Too many predicted coefficients.", RuntimeWarning, stacklevel=2) + kd = 1 + m = int(floor(m)) + if m % 2: + kd = 2 + + a = mathieu_a(m, q) + fc = _specfun.fcoef(kd, m, q, a) + return fc[:km] + + +def mathieu_odd_coef(m, q): + r"""Fourier coefficients for odd Mathieu and modified Mathieu functions. + + The Fourier series of the odd solutions of the Mathieu differential + equation are of the form + + .. math:: \mathrm{se}_{2n+1}(z, q) = + \sum_{k=0}^{\infty} B_{(2n+1)}^{(2k+1)} \sin (2k+1)z + + .. math:: \mathrm{se}_{2n+2}(z, q) = + \sum_{k=0}^{\infty} B_{(2n+2)}^{(2k+2)} \sin (2k+2)z + + This function returns the coefficients :math:`B_{(2n+2)}^{(2k+2)}` for even + input m=2n+2, and the coefficients :math:`B_{(2n+1)}^{(2k+1)}` for odd + input m=2n+1. + + Parameters + ---------- + m : int + Order of Mathieu functions. Must be non-negative. + q : float (>=0) + Parameter of Mathieu functions. Must be non-negative. + + Returns + ------- + Bk : ndarray + Even or odd Fourier coefficients, corresponding to even or odd m. + + References + ---------- + .. [1] Zhang, Shanjie and Jin, Jianming. "Computation of Special + Functions", John Wiley and Sons, 1996. + https://people.sc.fsu.edu/~jburkardt/f77_src/special_functions/special_functions.html + + """ + if not (isscalar(m) and isscalar(q)): + raise ValueError("m and q must be scalars.") + if (q < 0): + raise ValueError("q >=0") + if (m != floor(m)) or (m <= 0): + raise ValueError("m must be an integer > 0") + + if (q <= 1): + qm = 7.5 + 56.1*sqrt(q) - 134.7*q + 90.7*sqrt(q)*q + else: + qm = 17.0 + 3.1*sqrt(q) - .126*q + .0037*sqrt(q)*q + km = int(qm + 0.5*m) + if km > 251: + warnings.warn("Too many predicted coefficients.", RuntimeWarning, stacklevel=2) + kd = 4 + m = int(floor(m)) + if m % 2: + kd = 3 + + b = mathieu_b(m, q) + fc = _specfun.fcoef(kd, m, q, b) + return fc[:km] + + +def lqmn(m, n, z): + """Sequence of associated Legendre functions of the second kind. + + Computes the associated Legendre function of the second kind of order m and + degree n, ``Qmn(z)`` = :math:`Q_n^m(z)`, and its derivative, ``Qmn'(z)``. + Returns two arrays of size ``(m+1, n+1)`` containing ``Qmn(z)`` and + ``Qmn'(z)`` for all orders from ``0..m`` and degrees from ``0..n``. + + Parameters + ---------- + m : int + ``|m| <= n``; the order of the Legendre function. + n : int + where ``n >= 0``; the degree of the Legendre function. Often + called ``l`` (lower case L) in descriptions of the associated + Legendre function + z : array_like, complex + Input value. + + Returns + ------- + Qmn_z : (m+1, n+1) array + Values for all orders 0..m and degrees 0..n + Qmn_d_z : (m+1, n+1) array + Derivatives for all orders 0..m and degrees 0..n + + References + ---------- + .. [1] Zhang, Shanjie and Jin, Jianming. "Computation of Special + Functions", John Wiley and Sons, 1996. + https://people.sc.fsu.edu/~jburkardt/f77_src/special_functions/special_functions.html + + """ + if not isscalar(m) or (m < 0): + raise ValueError("m must be a non-negative integer.") + if not isscalar(n) or (n < 0): + raise ValueError("n must be a non-negative integer.") + + m, n = int(m), int(n) # Convert to int to maintain backwards compatibility. + # Ensure neither m nor n == 0 + mm = max(1, m) + nn = max(1, n) + + z = np.asarray(z) + if (not np.issubdtype(z.dtype, np.inexact)): + z = z.astype(np.float64) + + if np.iscomplexobj(z): + q = np.empty((mm + 1, nn + 1) + z.shape, dtype=np.complex128) + else: + q = np.empty((mm + 1, nn + 1) + z.shape, dtype=np.float64) + qd = np.empty_like(q) + if (z.ndim == 0): + _lqmn(z, out=(q, qd)) + else: + # new axes must be last for the ufunc + _lqmn(z, + out=(np.moveaxis(q, (0, 1), (-2, -1)), + np.moveaxis(qd, (0, 1), (-2, -1)))) + + return q[:(m+1), :(n+1)], qd[:(m+1), :(n+1)] + + +def bernoulli(n): + """Bernoulli numbers B0..Bn (inclusive). + + Parameters + ---------- + n : int + Indicated the number of terms in the Bernoulli series to generate. + + Returns + ------- + ndarray + The Bernoulli numbers ``[B(0), B(1), ..., B(n)]``. + + References + ---------- + .. [1] Zhang, Shanjie and Jin, Jianming. "Computation of Special + Functions", John Wiley and Sons, 1996. + https://people.sc.fsu.edu/~jburkardt/f77_src/special_functions/special_functions.html + .. [2] "Bernoulli number", Wikipedia, https://en.wikipedia.org/wiki/Bernoulli_number + + Examples + -------- + >>> import numpy as np + >>> from scipy.special import bernoulli, zeta + >>> bernoulli(4) + array([ 1. , -0.5 , 0.16666667, 0. , -0.03333333]) + + The Wikipedia article ([2]_) points out the relationship between the + Bernoulli numbers and the zeta function, ``B_n^+ = -n * zeta(1 - n)`` + for ``n > 0``: + + >>> n = np.arange(1, 5) + >>> -n * zeta(1 - n) + array([ 0.5 , 0.16666667, -0. , -0.03333333]) + + Note that, in the notation used in the wikipedia article, + `bernoulli` computes ``B_n^-`` (i.e. it used the convention that + ``B_1`` is -1/2). The relation given above is for ``B_n^+``, so the + sign of 0.5 does not match the output of ``bernoulli(4)``. + + """ + if not isscalar(n) or (n < 0): + raise ValueError("n must be a non-negative integer.") + n = int(n) + if (n < 2): + n1 = 2 + else: + n1 = n + return _specfun.bernob(int(n1))[:(n+1)] + + +def euler(n): + """Euler numbers E(0), E(1), ..., E(n). + + The Euler numbers [1]_ are also known as the secant numbers. + + Because ``euler(n)`` returns floating point values, it does not give + exact values for large `n`. The first inexact value is E(22). + + Parameters + ---------- + n : int + The highest index of the Euler number to be returned. + + Returns + ------- + ndarray + The Euler numbers [E(0), E(1), ..., E(n)]. + The odd Euler numbers, which are all zero, are included. + + References + ---------- + .. [1] Sequence A122045, The On-Line Encyclopedia of Integer Sequences, + https://oeis.org/A122045 + .. [2] Zhang, Shanjie and Jin, Jianming. "Computation of Special + Functions", John Wiley and Sons, 1996. + https://people.sc.fsu.edu/~jburkardt/f77_src/special_functions/special_functions.html + + Examples + -------- + >>> import numpy as np + >>> from scipy.special import euler + >>> euler(6) + array([ 1., 0., -1., 0., 5., 0., -61.]) + + >>> euler(13).astype(np.int64) + array([ 1, 0, -1, 0, 5, 0, -61, + 0, 1385, 0, -50521, 0, 2702765, 0]) + + >>> euler(22)[-1] # Exact value of E(22) is -69348874393137901. + -69348874393137976.0 + + """ + if not isscalar(n) or (n < 0): + raise ValueError("n must be a non-negative integer.") + n = int(n) + if (n < 2): + n1 = 2 + else: + n1 = n + return _specfun.eulerb(n1)[:(n+1)] + + +def lqn(n, z): + """Legendre functions of the second kind. + + Compute sequence of Legendre functions of the second kind, ``Qn(z)`` and + derivatives for all degrees from 0 to `n` (inclusive). + Returns two arrays of size ``(n+1,) + z.shape`` containing ``Qn(z)`` and + ``Qn'(z)``. + + Parameters + ---------- + n : int + Maximum degree of the Legendre functions. + z : array_like, complex + Real or complex input values. + + Returns + ------- + Qn_z : ndarray, shape (n+1,) + shape(z) + Values for all degrees ``0..n`` + Qn_d_z : ndarray, shape (n+1,) + shape(z) + Derivatives for all degrees ``0..n`` + + References + ---------- + .. [1] Zhang, Shanjie and Jin, Jianming. "Computation of Special + Functions", John Wiley and Sons, 1996. + https://people.sc.fsu.edu/~jburkardt/f77_src/special_functions/special_functions.html + + Examples + -------- + Compute :math:`Q_n(x)` and its derivatives on an interval. + + >>> import numpy as np + >>> from scipy.special import lqn + >>> import matplotlib.pyplot as plt + + >>> xs = np.linspace(-2, 2, 200) + >>> n_max = 3 + >>> Qn, dQn = lqn(n_max, xs) + + Plot the Legendre functions of the second kind :math:`Q_n(x)`. + + >>> fig, ax = plt.subplots() + >>> ax.plot(xs, Qn.T, "-") + >>> ax.set_xlabel(r"$x$") + >>> ax.set_ylabel(r"$Q_n(x)$") + >>> ax.legend([fr"$n={n}$" for n in range(n_max + 1)]) + >>> plt.show() + + Plot the derivatives :math:`Q_n'(x)`. + + >>> fig, ax = plt.subplots() + >>> ax.plot(xs, dQn.T, "-") + >>> ax.set_xlabel(r"$x$") + >>> ax.set_ylabel(r"$Q_n'(x)$") + >>> ax.legend([fr"$n={n}$" for n in range(n_max + 1)]) + >>> plt.show() + """ + n = _nonneg_int_or_fail(n, 'n', strict=False) + if (n < 1): + n1 = 1 + else: + n1 = n + + z = np.asarray(z) + if (not np.issubdtype(z.dtype, np.inexact)): + z = z.astype(float) + + if np.iscomplexobj(z): + qn = np.empty((n1 + 1,) + z.shape, dtype=np.complex128) + else: + qn = np.empty((n1 + 1,) + z.shape, dtype=np.float64) + qd = np.empty_like(qn) + if (z.ndim == 0): + _lqn(z, out=(qn, qd)) + else: + # new axes must be last for the ufunc + _lqn(z, + out=(np.moveaxis(qn, 0, -1), + np.moveaxis(qd, 0, -1))) + + return qn[:(n+1)], qd[:(n+1)] + + +def ai_zeros(nt): + """ + Compute `nt` zeros and values of the Airy function Ai and its derivative. + + Computes the first `nt` zeros, `a`, of the Airy function Ai(x); + first `nt` zeros, `ap`, of the derivative of the Airy function Ai'(x); + the corresponding values Ai(a'); + and the corresponding values Ai'(a). + + Parameters + ---------- + nt : int + Number of zeros to compute + + Returns + ------- + a : ndarray + First `nt` zeros of Ai(x) + ap : ndarray + First `nt` zeros of Ai'(x) + ai : ndarray + Values of Ai(x) evaluated at first `nt` zeros of Ai'(x) + aip : ndarray + Values of Ai'(x) evaluated at first `nt` zeros of Ai(x) + + References + ---------- + .. [1] Zhang, Shanjie and Jin, Jianming. "Computation of Special + Functions", John Wiley and Sons, 1996. + https://people.sc.fsu.edu/~jburkardt/f77_src/special_functions/special_functions.html + + Examples + -------- + >>> from scipy import special + >>> a, ap, ai, aip = special.ai_zeros(3) + >>> a + array([-2.33810741, -4.08794944, -5.52055983]) + >>> ap + array([-1.01879297, -3.24819758, -4.82009921]) + >>> ai + array([ 0.53565666, -0.41901548, 0.38040647]) + >>> aip + array([ 0.70121082, -0.80311137, 0.86520403]) + + """ + kf = 1 + if not isscalar(nt) or (floor(nt) != nt) or (nt <= 0): + raise ValueError("nt must be a positive integer scalar.") + return _specfun.airyzo(nt, kf) + + +def bi_zeros(nt): + """ + Compute `nt` zeros and values of the Airy function Bi and its derivative. + + Computes the first `nt` zeros, b, of the Airy function Bi(x); + first `nt` zeros, b', of the derivative of the Airy function Bi'(x); + the corresponding values Bi(b'); + and the corresponding values Bi'(b). + + Parameters + ---------- + nt : int + Number of zeros to compute + + Returns + ------- + b : ndarray + First `nt` zeros of Bi(x) + bp : ndarray + First `nt` zeros of Bi'(x) + bi : ndarray + Values of Bi(x) evaluated at first `nt` zeros of Bi'(x) + bip : ndarray + Values of Bi'(x) evaluated at first `nt` zeros of Bi(x) + + References + ---------- + .. [1] Zhang, Shanjie and Jin, Jianming. "Computation of Special + Functions", John Wiley and Sons, 1996. + https://people.sc.fsu.edu/~jburkardt/f77_src/special_functions/special_functions.html + + Examples + -------- + >>> from scipy import special + >>> b, bp, bi, bip = special.bi_zeros(3) + >>> b + array([-1.17371322, -3.2710933 , -4.83073784]) + >>> bp + array([-2.29443968, -4.07315509, -5.51239573]) + >>> bi + array([-0.45494438, 0.39652284, -0.36796916]) + >>> bip + array([ 0.60195789, -0.76031014, 0.83699101]) + + """ + kf = 2 + if not isscalar(nt) or (floor(nt) != nt) or (nt <= 0): + raise ValueError("nt must be a positive integer scalar.") + return _specfun.airyzo(nt, kf) + + +def lmbda(v, x): + r"""Jahnke-Emden Lambda function, Lambdav(x). + + This function is defined as [2]_, + + .. math:: \Lambda_v(x) = \Gamma(v+1) \frac{J_v(x)}{(x/2)^v}, + + where :math:`\Gamma` is the gamma function and :math:`J_v` is the + Bessel function of the first kind. + + Parameters + ---------- + v : float + Order of the Lambda function + x : float + Value at which to evaluate the function and derivatives + + Returns + ------- + vl : ndarray + Values of Lambda_vi(x), for vi=v-int(v), vi=1+v-int(v), ..., vi=v. + dl : ndarray + Derivatives Lambda_vi'(x), for vi=v-int(v), vi=1+v-int(v), ..., vi=v. + + References + ---------- + .. [1] Zhang, Shanjie and Jin, Jianming. "Computation of Special + Functions", John Wiley and Sons, 1996. + https://people.sc.fsu.edu/~jburkardt/f77_src/special_functions/special_functions.html + .. [2] Jahnke, E. and Emde, F. "Tables of Functions with Formulae and + Curves" (4th ed.), Dover, 1945 + """ + if not (isscalar(v) and isscalar(x)): + raise ValueError("arguments must be scalars.") + if (v < 0): + raise ValueError("argument must be > 0.") + n = int(v) + v0 = v - n + if (n < 1): + n1 = 1 + else: + n1 = n + v1 = n1 + v0 + if (v != floor(v)): + vm, vl, dl = _specfun.lamv(v1, x) + else: + vm, vl, dl = _specfun.lamn(v1, x) + return vl[:(n+1)], dl[:(n+1)] + + +def pbdv_seq(v, x): + """Parabolic cylinder functions Dv(x) and derivatives. + + Parameters + ---------- + v : float + Order of the parabolic cylinder function + x : float + Value at which to evaluate the function and derivatives + + Returns + ------- + dv : ndarray + Values of D_vi(x), for vi=v-int(v), vi=1+v-int(v), ..., vi=v. + dp : ndarray + Derivatives D_vi'(x), for vi=v-int(v), vi=1+v-int(v), ..., vi=v. + + References + ---------- + .. [1] Zhang, Shanjie and Jin, Jianming. "Computation of Special + Functions", John Wiley and Sons, 1996, chapter 13. + https://people.sc.fsu.edu/~jburkardt/f77_src/special_functions/special_functions.html + + """ + if not (isscalar(v) and isscalar(x)): + raise ValueError("arguments must be scalars.") + n = int(v) + v0 = v-n + if (n < 1): + n1 = 1 + else: + n1 = n + v1 = n1 + v0 + dv, dp, pdf, pdd = _specfun.pbdv(v1, x) + return dv[:n1+1], dp[:n1+1] + + +def pbvv_seq(v, x): + """Parabolic cylinder functions Vv(x) and derivatives. + + Parameters + ---------- + v : float + Order of the parabolic cylinder function + x : float + Value at which to evaluate the function and derivatives + + Returns + ------- + dv : ndarray + Values of V_vi(x), for vi=v-int(v), vi=1+v-int(v), ..., vi=v. + dp : ndarray + Derivatives V_vi'(x), for vi=v-int(v), vi=1+v-int(v), ..., vi=v. + + References + ---------- + .. [1] Zhang, Shanjie and Jin, Jianming. "Computation of Special + Functions", John Wiley and Sons, 1996, chapter 13. + https://people.sc.fsu.edu/~jburkardt/f77_src/special_functions/special_functions.html + + """ + if not (isscalar(v) and isscalar(x)): + raise ValueError("arguments must be scalars.") + n = int(v) + v0 = v-n + if (n <= 1): + n1 = 1 + else: + n1 = n + v1 = n1 + v0 + dv, dp, pdf, pdd = _specfun.pbvv(v1, x) + return dv[:n1+1], dp[:n1+1] + + +def pbdn_seq(n, z): + """Parabolic cylinder functions Dn(z) and derivatives. + + Parameters + ---------- + n : int + Order of the parabolic cylinder function + z : complex + Value at which to evaluate the function and derivatives + + Returns + ------- + dv : ndarray + Values of D_i(z), for i=0, ..., i=n. + dp : ndarray + Derivatives D_i'(z), for i=0, ..., i=n. + + References + ---------- + .. [1] Zhang, Shanjie and Jin, Jianming. "Computation of Special + Functions", John Wiley and Sons, 1996, chapter 13. + https://people.sc.fsu.edu/~jburkardt/f77_src/special_functions/special_functions.html + + """ + if not (isscalar(n) and isscalar(z)): + raise ValueError("arguments must be scalars.") + if (floor(n) != n): + raise ValueError("n must be an integer.") + if (abs(n) <= 1): + n1 = 1 + else: + n1 = n + cpb, cpd = _specfun.cpbdn(n1, z) + return cpb[:n1+1], cpd[:n1+1] + + +def ber_zeros(nt): + """Compute nt zeros of the Kelvin function ber. + + Parameters + ---------- + nt : int + Number of zeros to compute. Must be positive. + + Returns + ------- + ndarray + First `nt` zeros of the Kelvin function. + + See Also + -------- + ber + + References + ---------- + .. [1] Zhang, Shanjie and Jin, Jianming. "Computation of Special + Functions", John Wiley and Sons, 1996. + https://people.sc.fsu.edu/~jburkardt/f77_src/special_functions/special_functions.html + + """ + if not isscalar(nt) or (floor(nt) != nt) or (nt <= 0): + raise ValueError("nt must be positive integer scalar.") + return _specfun.klvnzo(nt, 1) + + +def bei_zeros(nt): + """Compute nt zeros of the Kelvin function bei. + + Parameters + ---------- + nt : int + Number of zeros to compute. Must be positive. + + Returns + ------- + ndarray + First `nt` zeros of the Kelvin function. + + See Also + -------- + bei + + References + ---------- + .. [1] Zhang, Shanjie and Jin, Jianming. "Computation of Special + Functions", John Wiley and Sons, 1996. + https://people.sc.fsu.edu/~jburkardt/f77_src/special_functions/special_functions.html + + """ + if not isscalar(nt) or (floor(nt) != nt) or (nt <= 0): + raise ValueError("nt must be positive integer scalar.") + return _specfun.klvnzo(nt, 2) + + +def ker_zeros(nt): + """Compute nt zeros of the Kelvin function ker. + + Parameters + ---------- + nt : int + Number of zeros to compute. Must be positive. + + Returns + ------- + ndarray + First `nt` zeros of the Kelvin function. + + See Also + -------- + ker + + References + ---------- + .. [1] Zhang, Shanjie and Jin, Jianming. "Computation of Special + Functions", John Wiley and Sons, 1996. + https://people.sc.fsu.edu/~jburkardt/f77_src/special_functions/special_functions.html + + """ + if not isscalar(nt) or (floor(nt) != nt) or (nt <= 0): + raise ValueError("nt must be positive integer scalar.") + return _specfun.klvnzo(nt, 3) + + +def kei_zeros(nt): + """Compute nt zeros of the Kelvin function kei. + + Parameters + ---------- + nt : int + Number of zeros to compute. Must be positive. + + Returns + ------- + ndarray + First `nt` zeros of the Kelvin function. + + See Also + -------- + kei + + References + ---------- + .. [1] Zhang, Shanjie and Jin, Jianming. "Computation of Special + Functions", John Wiley and Sons, 1996. + https://people.sc.fsu.edu/~jburkardt/f77_src/special_functions/special_functions.html + + """ + if not isscalar(nt) or (floor(nt) != nt) or (nt <= 0): + raise ValueError("nt must be positive integer scalar.") + return _specfun.klvnzo(nt, 4) + + +def berp_zeros(nt): + """Compute nt zeros of the derivative of the Kelvin function ber. + + Parameters + ---------- + nt : int + Number of zeros to compute. Must be positive. + + Returns + ------- + ndarray + First `nt` zeros of the derivative of the Kelvin function. + + See Also + -------- + ber, berp + + References + ---------- + .. [1] Zhang, Shanjie and Jin, Jianming. "Computation of Special + Functions", John Wiley and Sons, 1996. + https://people.sc.fsu.edu/~jburkardt/f77_src/special_functions/special_functions.html + + + Examples + -------- + Compute the first 5 zeros of the derivative of the Kelvin function. + + >>> from scipy.special import berp_zeros + >>> berp_zeros(5) + array([ 6.03871081, 10.51364251, 14.96844542, 19.41757493, 23.86430432]) + + """ + if not isscalar(nt) or (floor(nt) != nt) or (nt <= 0): + raise ValueError("nt must be positive integer scalar.") + return _specfun.klvnzo(nt, 5) + + +def beip_zeros(nt): + """Compute nt zeros of the derivative of the Kelvin function bei. + + Parameters + ---------- + nt : int + Number of zeros to compute. Must be positive. + + Returns + ------- + ndarray + First `nt` zeros of the derivative of the Kelvin function. + + See Also + -------- + bei, beip + + References + ---------- + .. [1] Zhang, Shanjie and Jin, Jianming. "Computation of Special + Functions", John Wiley and Sons, 1996. + https://people.sc.fsu.edu/~jburkardt/f77_src/special_functions/special_functions.html + + """ + if not isscalar(nt) or (floor(nt) != nt) or (nt <= 0): + raise ValueError("nt must be positive integer scalar.") + return _specfun.klvnzo(nt, 6) + + +def kerp_zeros(nt): + """Compute nt zeros of the derivative of the Kelvin function ker. + + Parameters + ---------- + nt : int + Number of zeros to compute. Must be positive. + + Returns + ------- + ndarray + First `nt` zeros of the derivative of the Kelvin function. + + See Also + -------- + ker, kerp + + References + ---------- + .. [1] Zhang, Shanjie and Jin, Jianming. "Computation of Special + Functions", John Wiley and Sons, 1996. + https://people.sc.fsu.edu/~jburkardt/f77_src/special_functions/special_functions.html + + """ + if not isscalar(nt) or (floor(nt) != nt) or (nt <= 0): + raise ValueError("nt must be positive integer scalar.") + return _specfun.klvnzo(nt, 7) + + +def keip_zeros(nt): + """Compute nt zeros of the derivative of the Kelvin function kei. + + Parameters + ---------- + nt : int + Number of zeros to compute. Must be positive. + + Returns + ------- + ndarray + First `nt` zeros of the derivative of the Kelvin function. + + See Also + -------- + kei, keip + + References + ---------- + .. [1] Zhang, Shanjie and Jin, Jianming. "Computation of Special + Functions", John Wiley and Sons, 1996. + https://people.sc.fsu.edu/~jburkardt/f77_src/special_functions/special_functions.html + + """ + if not isscalar(nt) or (floor(nt) != nt) or (nt <= 0): + raise ValueError("nt must be positive integer scalar.") + return _specfun.klvnzo(nt, 8) + + +def kelvin_zeros(nt): + """Compute `nt` zeros of all Kelvin functions. + + Parameters + ---------- + nt : int + Number of zeros to compute for each function. + + Returns + ------- + zeros : tuple of arrays + Length-8 tuple of arrays of length `nt`. The tuple contains the arrays of zeros + of ``(ber, bei, ker, kei, ber', bei', ker', kei')``. + + References + ---------- + .. [1] Zhang, Shanjie and Jin, Jianming. "Computation of Special + Functions", John Wiley and Sons, 1996. + https://people.sc.fsu.edu/~jburkardt/f77_src/special_functions/special_functions.html + + """ + if not isscalar(nt) or (floor(nt) != nt) or (nt <= 0): + raise ValueError("nt must be positive integer scalar.") + return (_specfun.klvnzo(nt, 1), + _specfun.klvnzo(nt, 2), + _specfun.klvnzo(nt, 3), + _specfun.klvnzo(nt, 4), + _specfun.klvnzo(nt, 5), + _specfun.klvnzo(nt, 6), + _specfun.klvnzo(nt, 7), + _specfun.klvnzo(nt, 8)) + + +def pro_cv_seq(m, n, c): + """Characteristic values for prolate spheroidal wave functions. + + Compute a sequence of characteristic values for the prolate + spheroidal wave functions for mode `m` and n'=m..n and spheroidal + parameter `c`. + + Parameters + ---------- + m, n : int + Non-negative mode parameters. + c : float + Spheroidal parameter. + + Returns + ------- + cv : array of floats + Characteristic values. + + References + ---------- + .. [1] Zhang, Shanjie and Jin, Jianming. "Computation of Special + Functions", John Wiley and Sons, 1996. + https://people.sc.fsu.edu/~jburkardt/f77_src/special_functions/special_functions.html + + """ + if not (isscalar(m) and isscalar(n) and isscalar(c)): + raise ValueError("Arguments must be scalars.") + if (n != floor(n)) or (m != floor(m)): + raise ValueError("Modes must be integers.") + if (n-m > 199): + raise ValueError("Difference between n and m is too large.") + maxL = n-m+1 + return _specfun.segv(m, n, c, 1)[1][:maxL] + + +def obl_cv_seq(m, n, c): + """Characteristic values for oblate spheroidal wave functions. + + Compute a sequence of characteristic values for the oblate + spheroidal wave functions for mode m and n'=m..n and spheroidal + parameter c. + + Parameters + ---------- + m, n : int + Non-negative mode parameters. + c : float + Spheroidal parameter. + + Returns + ------- + cv : array of floats + Characteristic values. + + References + ---------- + .. [1] Zhang, Shanjie and Jin, Jianming. "Computation of Special + Functions", John Wiley and Sons, 1996. + https://people.sc.fsu.edu/~jburkardt/f77_src/special_functions/special_functions.html + + """ + if not (isscalar(m) and isscalar(n) and isscalar(c)): + raise ValueError("Arguments must be scalars.") + if (n != floor(n)) or (m != floor(m)): + raise ValueError("Modes must be integers.") + if (n-m > 199): + raise ValueError("Difference between n and m is too large.") + maxL = n-m+1 + return _specfun.segv(m, n, c, -1)[1][:maxL] + + +def comb(N, k, *, exact=False, repetition=False): + """The number of combinations of N things taken k at a time. + + This is often expressed as "N choose k". + + Parameters + ---------- + N : int, ndarray + Number of things. + k : int, ndarray + Number of elements taken. + exact : bool, optional + For integers, if `exact` is False, then floating point precision is + used, otherwise the result is computed exactly. + repetition : bool, optional + If `repetition` is True, then the number of combinations with + repetition is computed. + + Returns + ------- + val : int, float, ndarray + The total number of combinations. + + See Also + -------- + binom : Binomial coefficient considered as a function of two real + variables. + + Notes + ----- + - Array arguments accepted only for exact=False case. + - If N < 0, or k < 0, then 0 is returned. + - If k > N and repetition=False, then 0 is returned. + + Examples + -------- + >>> import numpy as np + >>> from scipy.special import comb + >>> k = np.array([3, 4]) + >>> n = np.array([10, 10]) + >>> comb(n, k, exact=False) + array([ 120., 210.]) + >>> comb(10, 3, exact=True) + 120 + >>> comb(10, 3, exact=True, repetition=True) + 220 + + """ + if repetition: + # Special case: C(n, 0) with repetition = 1 for n >= 0 + # Without this check, comb(0, 0, repetition=True) would compute + # comb(-1, 0) which incorrectly returns 0 + if exact: + if k == 0 and int(N) == N and N >= 0: + return 1 + else: + k, N = asarray(k), asarray(N) + cond = (k == 0) & (N >= 0) + vals = binom(N + k - 1, k) + if isinstance(vals, np.ndarray): + vals[cond] = 1.0 + elif cond: + vals = np.float64(1.0) + return vals + return comb(N + k - 1, k, exact=exact) + if exact: + if int(N) == N and int(k) == k: + # _comb_int casts inputs to integers, which is safe & intended here + return _comb_int(N, k) + else: + raise ValueError("Non-integer `N` and `k` with `exact=True` is not " + "supported.") + else: + k, N = asarray(k), asarray(N) + cond = (k <= N) & (N >= 0) & (k >= 0) + vals = binom(N, k) + if isinstance(vals, np.ndarray): + vals[~cond] = 0 + elif not cond: + vals = np.float64(0) + return vals + + +def perm(N, k, exact=False): + """Permutations of N things taken k at a time, i.e., k-permutations of N. + + It's also known as "partial permutations". + + Parameters + ---------- + N : int, ndarray + Number of things. + k : int, ndarray + Number of elements taken. + exact : bool, optional + If ``True``, calculate the answer exactly using long integer arithmetic (`N` + and `k` must be scalar integers). If ``False``, a floating point approximation + is calculated (more rapidly) using `poch`. Default is ``False``. + + Returns + ------- + val : int, ndarray + The number of k-permutations of N. + + Notes + ----- + - Array arguments accepted only for exact=False case. + - If k > N, N < 0, or k < 0, then a 0 is returned. + + Examples + -------- + >>> import numpy as np + >>> from scipy.special import perm + >>> k = np.array([3, 4]) + >>> n = np.array([10, 10]) + >>> perm(n, k) + array([ 720., 5040.]) + >>> perm(10, 3, exact=True) + 720 + + """ + if exact: + N = np.squeeze(N)[()] # for backward compatibility (accepted size 1 arrays) + k = np.squeeze(k)[()] + if not (isscalar(N) and isscalar(k)): + raise ValueError("`N` and `k` must be scalar integers with `exact=True`.") + + floor_N, floor_k = int(N), int(k) + non_integral = not (floor_N == N and floor_k == k) + if non_integral: + raise ValueError("Non-integer `N` and `k` with `exact=True` is not " + "supported.") + + if (k > N) or (N < 0) or (k < 0): + return 0 + + val = 1 + for i in range(floor_N - floor_k + 1, floor_N + 1): + val *= i + return val + else: + k, N = asarray(k), asarray(N) + cond = (k <= N) & (N >= 0) & (k >= 0) + vals = poch(N - k + 1, k) + if isinstance(vals, np.ndarray): + vals[~cond] = 0 + elif not cond: + vals = np.float64(0) + return vals + + +# https://stackoverflow.com/a/16327037 +def _range_prod(lo, hi, k=1): + """ + Product of a range of numbers spaced k apart (from hi). + + For k=1, this returns the product of + lo * (lo+1) * (lo+2) * ... * (hi-2) * (hi-1) * hi + = hi! / (lo-1)! + + For k>1, it correspond to taking only every k'th number when + counting down from hi - e.g. 18!!!! = _range_prod(1, 18, 4). + + Breaks into smaller products first for speed: + _range_prod(2, 9) = ((2*3)*(4*5))*((6*7)*(8*9)) + """ + if lo == 1 and k == 1: + return math.factorial(hi) + + if lo + k < hi: + mid = (hi + lo) // 2 + if k > 1: + # make sure mid is a multiple of k away from hi + mid = mid - ((mid - hi) % k) + return _range_prod(lo, mid, k) * _range_prod(mid + k, hi, k) + elif lo + k == hi: + return lo * hi + else: + return hi + + +def _factorialx_array_exact(n, k=1): + """ + Exact computation of factorial for an array. + + The factorials are computed in incremental fashion, by taking + the sorted unique values of n and multiplying the intervening + numbers between the different unique values. + + In other words, the factorial for the largest input is only + computed once, with each other result computed in the process. + + k > 1 corresponds to the multifactorial. + """ + un = np.unique(n) + + # Convert to object array if np.int64 can't handle size + if k in _FACTORIALK_LIMITS_64BITS.keys(): + if un[-1] > _FACTORIALK_LIMITS_64BITS[k]: + # e.g. k=1: 21! > np.iinfo(np.int64).max + dt = object + elif un[-1] > _FACTORIALK_LIMITS_32BITS[k]: + # e.g. k=3: 26!!! > np.iinfo(np.int32).max + dt = np.int64 + else: + dt = np.dtype("long") + else: + # for k >= 10, we always use object + dt = object + + out = np.empty_like(n, dtype=dt) + + # Handle invalid/trivial values + un = un[un > 1] + out[n < 2] = 1 + out[n < 0] = 0 + + # Calculate products of each range of numbers + # we can only multiply incrementally if the values are k apart; + # therefore we partition `un` into "lanes", i.e. its residues modulo k + for lane in range(0, k): + ul = un[(un % k) == lane] if k > 1 else un + if ul.size: + # after np.unique, un resp. ul are sorted, ul[0] is the smallest; + # cast to python ints to avoid overflow with np.int-types + val = _range_prod(1, int(ul[0]), k=k) + out[n == ul[0]] = val + for i in range(len(ul) - 1): + # by the filtering above, we have ensured that prev & current + # are a multiple of k apart + prev = ul[i] + current = ul[i + 1] + # we already multiplied all factors until prev; continue + # building the full factorial from the following (`prev + 1`); + # use int() for the same reason as above + val *= _range_prod(int(prev + 1), int(current), k=k) + out[n == current] = val + + return out + + +def _factorialx_array_approx(n, k, extend): + """ + Calculate approximation to multifactorial for array n and integer k. + + Ensure that values aren't calculated unnecessarily. + """ + if extend == "complex": + return _factorialx_approx_core(n, k=k, extend=extend) + + # at this point we are guaranteed that extend='zero' and that k>0 is an integer + result = zeros(n.shape) + # keep nans as nans + place(result, np.isnan(n), np.nan) + # only compute where n >= 0 (excludes nans), everything else is 0 + cond = (n >= 0) + n_to_compute = extract(cond, n) + place(result, cond, _factorialx_approx_core(n_to_compute, k=k, extend=extend)) + return result + + +def _gamma1p(vals): + """ + returns gamma(n+1), though with NaN at -1 instead of inf, c.f. #21827 + """ + res = gamma(vals + 1) + # replace infinities at -1 (from gamma function at 0) with nan + # gamma only returns inf for real inputs; can ignore complex case + if isinstance(res, np.ndarray): + if not _is_subdtype(vals.dtype, "c"): + res[vals == -1] = np.nan + elif np.isinf(res) and vals == -1: + res = np.float64("nan") + return res + + +def _factorialx_approx_core(n, k, extend): + """ + Core approximation to multifactorial for array n and integer k. + """ + if k == 1: + # shortcut for k=1; same for both extensions, because we assume the + # handling of extend == 'zero' happens in _factorialx_array_approx + result = _gamma1p(n) + if isinstance(n, np.ndarray): + # gamma does not maintain 0-dim arrays; fix it + result = np.array(result) + return result + + if extend == "complex": + # see https://numpy.org/doc/stable/reference/generated/numpy.power.html + p_dtype = complex if (_is_subdtype(type(k), "c") or k < 0) else None + with warnings.catch_warnings(): + # do not warn about 0 * inf, nan / nan etc.; the results are correct + warnings.simplefilter("ignore", RuntimeWarning) + # don't use `(n-1)/k` in np.power; underflows if 0 is of a uintX type + result = np.power(k, n / k, dtype=p_dtype) * _gamma1p(n / k) + result *= rgamma(1 / k + 1) / np.power(k, 1 / k, dtype=p_dtype) + if isinstance(n, np.ndarray): + # ensure we keep array-ness for 0-dim inputs; already n/k above loses it + result = np.array(result) + return result + + # at this point we are guaranteed that extend='zero' and that k>0 is an integer + n_mod_k = n % k + # scalar case separately, unified handling would be inefficient for arrays; + # don't use isscalar due to numpy/numpy#23574; 0-dim arrays treated below + if not isinstance(n, np.ndarray): + with warnings.catch_warnings(): + # large n cause overflow warnings, but infinity is fine + warnings.simplefilter("ignore", RuntimeWarning) + return ( + np.power(k, (n - n_mod_k) / k) + * gamma(n / k + 1) / gamma(n_mod_k / k + 1) + * max(n_mod_k, 1) + ) + + # factor that's independent of the residue class (see factorialk docstring) + with warnings.catch_warnings(): + # large n cause overflow warnings, but infinity is fine + warnings.simplefilter("ignore", RuntimeWarning) + result = np.power(k, n / k) * gamma(n / k + 1) + # factor dependent on residue r (for `r=0` it's 1, so we skip `r=0` + # below and thus also avoid evaluating `max(r, 1)`) + def corr(k, r): return np.power(k, -r / k) / gamma(r / k + 1) * r + for r in np.unique(n_mod_k): + if r == 0: + continue + # cast to int because uint types break on `-r` + result[n_mod_k == r] *= corr(k, int(r)) + return result + + +def _is_subdtype(dtype, dtypes): + """ + Shorthand for calculating whether dtype is subtype of some dtypes. + + Also allows specifying a list instead of just a single dtype. + + Additionaly, the most important supertypes from + https://numpy.org/doc/stable/reference/arrays.scalars.html + can optionally be specified using abbreviations as follows: + "i": np.integer + "f": np.floating + "c": np.complexfloating + "n": np.number (contains the other three) + """ + dtypes = dtypes if isinstance(dtypes, list) else [dtypes] + # map single character abbreviations, if they are in dtypes + mapping = { + "i": np.integer, + "f": np.floating, + "c": np.complexfloating, + "n": np.number + } + dtypes = [mapping.get(x, x) for x in dtypes] + return any(np.issubdtype(dtype, dt) for dt in dtypes) + + +def _factorialx_wrapper(fname, n, k, exact, extend): + """ + Shared implementation for factorial, factorial2 & factorialk. + """ + if extend not in ("zero", "complex"): + raise ValueError( + f"argument `extend` must be either 'zero' or 'complex', received: {extend}" + ) + if exact and extend == "complex": + raise ValueError("Incompatible options: `exact=True` and `extend='complex'`") + + msg_unsup = ( + "Unsupported data type for {vname} in {fname}: {dtype}\n" + ) + if fname == "factorial": + msg_unsup += ( + "Permitted data types are integers and floating point numbers, " + "as well as complex numbers if `extend='complex' is passed." + ) + else: + msg_unsup += ( + "Permitted data types are integers, as well as floating point " + "numbers and complex numbers if `extend='complex' is passed." + ) + msg_exact_not_possible = ( + "`exact=True` only supports integers, cannot use data type {dtype}" + ) + msg_needs_complex = ( + "In order to use non-integer arguments, you must opt into this by passing " + "`extend='complex'`. Note that this changes the result for all negative " + "arguments (which by default return 0)." + ) + + if fname == "factorial2": + msg_needs_complex += (" Additionally, it will rescale the values of the double" + " factorial at even integers by a factor of sqrt(2/pi).") + elif fname == "factorialk": + msg_needs_complex += (" Additionally, it will perturb the values of the" + " multifactorial at most positive integers `n`.") + # check type of k + if not _is_subdtype(type(k), ["i", "f", "c"]): + raise ValueError(msg_unsup.format(vname="`k`", fname=fname, dtype=type(k))) + elif _is_subdtype(type(k), ["f", "c"]) and extend != "complex": + raise ValueError(msg_needs_complex) + # check value of k + if extend == "zero" and k < 1: + msg = f"For `extend='zero'`, k must be a positive integer, received: {k}" + raise ValueError(msg) + elif k == 0: + raise ValueError("Parameter k cannot be zero!") + + # factorial allows floats also for extend="zero" + types_requiring_complex = "c" if fname == "factorial" else ["f", "c"] + + # don't use isscalar due to numpy/numpy#23574; 0-dim arrays treated below + if np.ndim(n) == 0 and not isinstance(n, np.ndarray): + # scalar cases + if not _is_subdtype(type(n), ["i", "f", "c", type(None)]): + raise ValueError(msg_unsup.format(vname="`n`", fname=fname, dtype=type(n))) + elif _is_subdtype(type(n), types_requiring_complex) and extend != "complex": + raise ValueError(msg_needs_complex) + elif n is None or np.isnan(n): + complexify = (extend == "complex") and _is_subdtype(type(n), "c") + return np.complex128("nan+nanj") if complexify else np.float64("nan") + elif extend == "zero" and n < 0: + return 0 if exact else np.float64(0) + elif n in {0, 1}: + return 1 if exact else np.float64(1) + elif exact and _is_subdtype(type(n), "i"): + # calculate with integers; cast away other int types (like unsigned) + return _range_prod(1, int(n), k=k) + elif exact: + # only relevant for factorial + raise ValueError(msg_exact_not_possible.format(dtype=type(n))) + # approximation + return _factorialx_approx_core(n, k=k, extend=extend) + + # arrays & array-likes + n = asarray(n) + + if not _is_subdtype(n.dtype, ["i", "f", "c"]): + raise ValueError(msg_unsup.format(vname="`n`", fname=fname, dtype=n.dtype)) + elif _is_subdtype(n.dtype, types_requiring_complex) and extend != "complex": + raise ValueError(msg_needs_complex) + elif exact and _is_subdtype(n.dtype, ["f"]): + # only relevant for factorial + raise ValueError(msg_exact_not_possible.format(dtype=n.dtype)) + + if n.size == 0: + # return empty arrays unchanged + return n + elif exact: + # calculate with integers + return _factorialx_array_exact(n, k=k) + # approximation + return _factorialx_array_approx(n, k=k, extend=extend) + + +def factorial(n, exact=False, extend="zero"): + """ + The factorial of a number or array of numbers. + + The factorial of non-negative integer `n` is the product of all + positive integers less than or equal to `n`:: + + n! = n * (n - 1) * (n - 2) * ... * 1 + + Parameters + ---------- + n : int or float or complex (or array_like thereof) + Input values for ``n!``. Complex values require ``extend='complex'``. + By default, the return value for ``n < 0`` is 0. + exact : bool, optional + If ``exact`` is set to True, calculate the answer exactly using + integer arithmetic, otherwise approximate using the gamma function + (faster, but yields floats instead of integers). + Default is False. + extend : str, optional + One of ``'zero'`` or ``'complex'``; this determines how values ``n<0`` + are handled - by default they are 0, but it is possible to opt into the + complex extension of the factorial (see below). + + Returns + ------- + nf : int or float or complex or ndarray + Factorial of ``n``, as integer, float or complex (depending on ``exact`` + and ``extend``). Array inputs are returned as arrays. + + Notes + ----- + For arrays with ``exact=True``, the factorial is computed only once, for + the largest input, with each other result computed in the process. + The output dtype is increased to ``int64`` or ``object`` if necessary. + + With ``exact=False`` the factorial is approximated using the gamma + function (which is also the definition of the complex extension): + + .. math:: n! = \\Gamma(n+1) + + Examples + -------- + >>> import numpy as np + >>> from scipy.special import factorial + >>> arr = np.array([3, 4, 5]) + >>> factorial(arr, exact=False) + array([ 6., 24., 120.]) + >>> factorial(arr, exact=True) + array([ 6, 24, 120]) + >>> factorial(5, exact=True) + 120 + + """ + return _factorialx_wrapper("factorial", n, k=1, exact=exact, extend=extend) + + +def factorial2(n, exact=False, extend="zero"): + """ + Double factorial. + + This is the factorial with every second value skipped. E.g., ``7!! = 7 * 5 + * 3 * 1``. It can be approximated numerically as:: + + n!! = 2 ** (n / 2) * gamma(n / 2 + 1) * sqrt(2 / pi) n odd + = 2 ** (n / 2) * gamma(n / 2 + 1) n even + = 2 ** (n / 2) * (n / 2)! n even + + The formula for odd ``n`` is the basis for the complex extension. + + Parameters + ---------- + n : int or float or complex (or array_like thereof) + Input values for ``n!!``. Non-integer values require ``extend='complex'``. + By default, the return value for ``n < 0`` is 0. + exact : bool, optional + If ``exact`` is set to True, calculate the answer exactly using + integer arithmetic, otherwise use above approximation (faster, + but yields floats instead of integers). + Default is False. + extend : str, optional + One of ``'zero'`` or ``'complex'``; this determines how values ``n<0`` + are handled - by default they are 0, but it is possible to opt into the + complex extension of the double factorial. This also enables passing + complex values to ``n``. + + .. warning:: + + Using the ``'complex'`` extension also changes the values of the + double factorial for even integers, reducing them by a factor of + ``sqrt(2/pi) ~= 0.79``, see [1]. + + Returns + ------- + nf : int or float or complex or ndarray + Double factorial of ``n``, as integer, float or complex (depending on + ``exact`` and ``extend``). Array inputs are returned as arrays. + + References + ---------- + .. [1] Complex extension to double factorial + https://en.wikipedia.org/wiki/Double_factorial#Complex_arguments + + Examples + -------- + >>> from scipy.special import factorial2 + >>> factorial2(7, exact=False) + np.float64(105.00000000000001) + >>> factorial2(7, exact=True) + 105 + """ + return _factorialx_wrapper("factorial2", n, k=2, exact=exact, extend=extend) + + +def factorialk(n, k, exact=False, extend="zero"): + """ + Multifactorial of n of order k, n(!!...!). + + This is the multifactorial of n skipping k values. For example, + + factorialk(17, 4) = 17!!!! = 17 * 13 * 9 * 5 * 1 + + In particular, for any integer ``n``, we have + + factorialk(n, 1) = factorial(n) + + factorialk(n, 2) = factorial2(n) + + Parameters + ---------- + n : int or float or complex (or array_like thereof) + Input values for multifactorial. Non-integer values require + ``extend='complex'``. By default, the return value for ``n < 0`` is 0. + k : int or float or complex (or array_like thereof) + Order of multifactorial. Non-integer values require ``extend='complex'``. + exact : bool, optional + If ``exact`` is set to True, calculate the answer exactly using + integer arithmetic, otherwise use an approximation (faster, + but yields floats instead of integers) + Default is False. + extend : str, optional + One of ``'zero'`` or ``'complex'``; this determines how values ``n<0`` are + handled - by default they are 0, but it is possible to opt into the complex + extension of the multifactorial. This enables passing complex values, + not only to ``n`` but also to ``k``. + + .. warning:: + + Using the ``'complex'`` extension also changes the values of the + multifactorial at integers ``n != 1 (mod k)`` by a factor depending + on both ``k`` and ``n % k``, see below or [1]. + + Returns + ------- + nf : int or float or complex or ndarray + Multifactorial (order ``k``) of ``n``, as integer, float or complex (depending + on ``exact`` and ``extend``). Array inputs are returned as arrays. + + Notes + ----- + While less straight-forward than for the double-factorial, it's possible to + calculate a general approximation formula of n!(k) by studying ``n`` for a given + remainder ``r < k`` (thus ``n = m * k + r``, resp. ``r = n % k``), which can be + put together into something valid for all integer values ``n >= 0`` & ``k > 0``:: + + n!(k) = k ** ((n - r)/k) * gamma(n/k + 1) / gamma(r/k + 1) * max(r, 1) + + This is the basis of the approximation when ``exact=False``. + + In principle, any fixed choice of ``r`` (ignoring its relation ``r = n%k`` + to ``n``) would provide a suitable analytic continuation from integer ``n`` + to complex ``z`` (not only satisfying the functional equation but also + being logarithmically convex, c.f. Bohr-Mollerup theorem) -- in fact, the + choice of ``r`` above only changes the function by a constant factor. The + final constraint that determines the canonical continuation is ``f(1) = 1``, + which forces ``r = 1`` (see also [1]).:: + + z!(k) = k ** ((z - 1)/k) * gamma(z/k + 1) / gamma(1/k + 1) + + References + ---------- + .. [1] Complex extension to multifactorial + https://en.wikipedia.org/wiki/Double_factorial#Alternative_extension_of_the_multifactorial + + Examples + -------- + >>> from scipy.special import factorialk + >>> factorialk(5, k=1, exact=True) + 120 + >>> factorialk(5, k=3, exact=True) + 10 + >>> factorialk([5, 7, 9], k=3, exact=True) + array([ 10, 28, 162]) + >>> factorialk([5, 7, 9], k=3, exact=False) + array([ 10., 28., 162.]) + """ + return _factorialx_wrapper("factorialk", n, k=k, exact=exact, extend=extend) + + +def stirling2(N, K, *, exact=False): + r"""Generate Stirling number(s) of the second kind. + + Stirling numbers of the second kind count the number of ways to + partition a set with N elements into K non-empty subsets. + + The values this function returns are calculated using a dynamic + program which avoids redundant computation across the subproblems + in the solution. For array-like input, this implementation also + avoids redundant computation across the different Stirling number + calculations. + + The numbers are sometimes denoted + + .. math:: + + {N \brace{K}} + + see [1]_ for details. This is often expressed-verbally-as + "N subset K". + + Parameters + ---------- + N : int, ndarray + Number of things. + K : int, ndarray + Number of non-empty subsets taken. + exact : bool, optional + Uses dynamic programming (DP) with floating point + numbers for smaller arrays and uses a second order approximation due to + Temme for larger entries of `N` and `K` that allows trading speed for + accuracy. See [2]_ for a description. Temme approximation is used for + values ``n>50``. The max error from the DP has max relative error + ``4.5*10^-16`` for ``n<=50`` and the max error from the Temme approximation + has max relative error ``5*10^-5`` for ``51 <= n < 70`` and + ``9*10^-6`` for ``70 <= n < 101``. Note that these max relative errors will + decrease further as `n` increases. + + Returns + ------- + val : int, float, ndarray + The number of partitions. + + See Also + -------- + comb : The number of combinations of N things taken k at a time. + + Notes + ----- + - If N < 0, or K < 0, then 0 is returned. + - If K > N, then 0 is returned. + + The output type will always be `int` or ndarray of `object`. + The input must contain either numpy or python integers otherwise a + TypeError is raised. + + References + ---------- + .. [1] R. L. Graham, D. E. Knuth and O. Patashnik, "Concrete + Mathematics: A Foundation for Computer Science," Addison-Wesley + Publishing Company, Boston, 1989. Chapter 6, page 258. + + .. [2] Temme, Nico M. "Asymptotic estimates of Stirling numbers." + Studies in Applied Mathematics 89.3 (1993): 233-243. + + Examples + -------- + >>> import numpy as np + >>> from scipy.special import stirling2 + >>> k = np.array([3, -1, 3]) + >>> n = np.array([10, 10, 9]) + >>> stirling2(n, k) + array([9330.0, 0.0, 3025.0]) + + """ + output_is_scalar = np.isscalar(N) and np.isscalar(K) + # make a min-heap of unique (n,k) pairs + N, K = asarray(N), asarray(K) + if not np.issubdtype(N.dtype, np.integer): + raise TypeError("Argument `N` must contain only integers") + if not np.issubdtype(K.dtype, np.integer): + raise TypeError("Argument `K` must contain only integers") + if not exact: + # NOTE: here we allow np.uint via casting to double types prior to + # passing to private ufunc dispatcher. All dispatched functions + # take double type for (n,k) arguments and return double. + return _stirling2_inexact(N.astype(float), K.astype(float)) + nk_pairs = list( + set([(n.take(0), k.take(0)) + for n, k in np.nditer([N, K], ['refs_ok'])]) + ) + heapify(nk_pairs) + # base mapping for small values + snsk_vals = defaultdict(int) + for pair in [(0, 0), (1, 1), (2, 1), (2, 2)]: + snsk_vals[pair] = 1 + # for each pair in the min-heap, calculate the value, store for later + n_old, n_row = 2, [0, 1, 1] + while nk_pairs: + n, k = heappop(nk_pairs) + if n < 2 or k > n or k <= 0: + continue + elif k == n or k == 1: + snsk_vals[(n, k)] = 1 + continue + elif n != n_old: + num_iters = n - n_old + while num_iters > 0: + n_row.append(1) + # traverse from back to remove second row + for j in range(len(n_row)-2, 1, -1): + n_row[j] = n_row[j]*j + n_row[j-1] + num_iters -= 1 + snsk_vals[(n, k)] = n_row[k] + else: + snsk_vals[(n, k)] = n_row[k] + n_old, n_row = n, n_row + out_types = [object, object, object] if exact else [float, float, float] + # for each pair in the map, fetch the value, and populate the array + it = np.nditer( + [N, K, None], + ['buffered', 'refs_ok'], + [['readonly'], ['readonly'], ['writeonly', 'allocate']], + op_dtypes=out_types, + ) + with it: + while not it.finished: + it[2] = snsk_vals[(int(it[0]), int(it[1]))] + it.iternext() + output = it.operands[2] + # If N and K were both scalars, convert output to scalar. + if output_is_scalar: + output = output.take(0) + return output + + +def zeta(x, q=None, out=None): + r""" + Riemann or Hurwitz zeta function. + + Parameters + ---------- + x : array_like of float or complex + Input data + q : array_like of float, optional + Input data, must be real. Defaults to Riemann zeta. When `q` is + ``None``, complex inputs `x` are supported. If `q` is not ``None``, + then currently only real inputs `x` with ``x >= 1`` are supported, + even when ``q = 1.0`` (corresponding to the Riemann zeta function). + + out : ndarray, optional + Output array for the computed values. + + Returns + ------- + out : array_like + Values of zeta(x). + + See Also + -------- + zetac + + Notes + ----- + The two-argument version is the Hurwitz zeta function + + .. math:: + + \zeta(x, q) = \sum_{k=0}^{\infty} \frac{1}{(k + q)^x}; + + see [dlmf]_ for details. The Riemann zeta function corresponds to + the case when ``q = 1``. + + For complex inputs with ``q = None``, points with + ``abs(z.imag) > 1e9`` and ``0 <= abs(z.real) < 2.5`` are currently not + supported due to slow convergence causing excessive runtime. + + References + ---------- + .. [dlmf] NIST, Digital Library of Mathematical Functions, + https://dlmf.nist.gov/25.11#i + + Examples + -------- + >>> import numpy as np + >>> from scipy.special import zeta, polygamma, factorial + + Some specific values: + + >>> zeta(2), np.pi**2/6 + (1.6449340668482266, 1.6449340668482264) + + >>> zeta(4), np.pi**4/90 + (1.0823232337111381, 1.082323233711138) + + First nontrivial zero: + + >>> zeta(0.5 + 14.134725141734695j) + 0 + 0j + + Relation to the `polygamma` function: + + >>> m = 3 + >>> x = 1.25 + >>> polygamma(m, x) + array(2.782144009188397) + >>> (-1)**(m+1) * factorial(m) * zeta(m+1, x) + 2.7821440091883969 + + """ + if q is None: + return _ufuncs._riemann_zeta(x, out) + else: + return _ufuncs._zeta(x, q, out) + + +def softplus(x, **kwargs): + r""" + Compute the softplus function element-wise. + + The softplus function is defined as: ``softplus(x) = log(1 + exp(x))``. + It is a smooth approximation of the rectifier function (ReLU). + + Parameters + ---------- + x : array_like + Input value. + **kwargs + For other keyword-only arguments, see the + `ufunc docs `_. + + Returns + ------- + softplus : ndarray + Logarithm of ``exp(0) + exp(x)``. + + Examples + -------- + >>> from scipy import special + + >>> special.softplus(0) + 0.6931471805599453 + + >>> special.softplus([-1, 0, 1]) + array([0.31326169, 0.69314718, 1.31326169]) + """ + return np.logaddexp(0, x, **kwargs) diff --git a/venv/lib/python3.12/site-packages/scipy/special/_comb.cpython-312-x86_64-linux-gnu.so b/venv/lib/python3.12/site-packages/scipy/special/_comb.cpython-312-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..b7f5d7c747cb09fa7d5bf30c797d48b1b68115b3 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/_comb.cpython-312-x86_64-linux-gnu.so differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/_ellip_harm.py b/venv/lib/python3.12/site-packages/scipy/special/_ellip_harm.py new file mode 100644 index 0000000000000000000000000000000000000000..28ebd6cd4402674f4eed8bb9a790f733f3808927 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/_ellip_harm.py @@ -0,0 +1,214 @@ +import numpy as np + +from ._ufuncs import _ellip_harm +from ._ellip_harm_2 import _ellipsoid, _ellipsoid_norm + + +def ellip_harm(h2, k2, n, p, s, signm=1, signn=1): + r""" + Ellipsoidal harmonic functions E^p_n(l). + + These are also known as Lamé functions of the first kind, and are + solutions to the Lamé equation: + + .. math:: (s^2 - h^2)(s^2 - k^2)E''(s) + + s(2s^2 - h^2 - k^2)E'(s) + (a - q s^2)E(s) = 0 + + where :math:`q = (n+1)n` and :math:`a` is the eigenvalue (not + returned) corresponding to the solutions. + + Parameters + ---------- + h2 : float + ``h**2`` + k2 : float + ``k**2``; should be larger than ``h**2`` + n : int + Degree + p : int + Order, can range between ``[1, 2n+1]`` + s : float + Coordinate + signm : {1, -1}, optional + Sign of prefactor of functions. Can be +/-1. See Notes. + signn : {1, -1}, optional + Sign of prefactor of functions. Can be +/-1. See Notes. + + Returns + ------- + E : float + the harmonic :math:`E^p_n(s)` + + See Also + -------- + ellip_harm_2, ellip_normal + + Notes + ----- + The geometric interpretation of the ellipsoidal functions is + explained in [2]_, [3]_, [4]_. The `signm` and `signn` arguments control the + sign of prefactors for functions according to their type:: + + K : +1 + L : signm + M : signn + N : signm*signn + + .. versionadded:: 0.15.0 + + References + ---------- + .. [1] Digital Library of Mathematical Functions 29.12 + https://dlmf.nist.gov/29.12 + .. [2] Bardhan and Knepley, "Computational science and + re-discovery: open-source implementations of + ellipsoidal harmonics for problems in potential theory", + Comput. Sci. Disc. 5, 014006 (2012) + :doi:`10.1088/1749-4699/5/1/014006`. + .. [3] David J.and Dechambre P, "Computation of Ellipsoidal + Gravity Field Harmonics for small solar system bodies" + pp. 30-36, 2000 + .. [4] George Dassios, "Ellipsoidal Harmonics: Theory and Applications" + pp. 418, 2012 + + Examples + -------- + >>> from scipy.special import ellip_harm + >>> w = ellip_harm(5,8,1,1,2.5) + >>> w + 2.5 + + Check that the functions indeed are solutions to the Lamé equation: + + >>> import numpy as np + >>> from scipy.interpolate import UnivariateSpline + >>> def eigenvalue(f, df, ddf): + ... r = (((s**2 - h**2) * (s**2 - k**2) * ddf + ... + s * (2*s**2 - h**2 - k**2) * df + ... - n * (n + 1)*s**2*f) / f) + ... return -r.mean(), r.std() + >>> s = np.linspace(0.1, 10, 200) + >>> k, h, n, p = 8.0, 2.2, 3, 2 + >>> E = ellip_harm(h**2, k**2, n, p, s) + >>> E_spl = UnivariateSpline(s, E) + >>> a, a_err = eigenvalue(E_spl(s), E_spl(s,1), E_spl(s,2)) + >>> a, a_err + (583.44366156701483, 6.4580890640310646e-11) + + """ # noqa: E501 + return _ellip_harm(h2, k2, n, p, s, signm, signn) + + +_ellip_harm_2_vec = np.vectorize(_ellipsoid, otypes='d') + + +def ellip_harm_2(h2, k2, n, p, s): + r""" + Ellipsoidal harmonic functions F^p_n(l). + + These are also known as Lamé functions of the second kind, and are + solutions to the Lamé equation: + + .. math:: (s^2 - h^2)(s^2 - k^2)F''(s) + + s(2s^2 - h^2 - k^2)F'(s) + (a - q s^2)F(s) = 0 + + where :math:`q = (n+1)n` and :math:`a` is the eigenvalue (not + returned) corresponding to the solutions. + + Parameters + ---------- + h2 : float + ``h**2`` + k2 : float + ``k**2``; should be larger than ``h**2`` + n : int + Degree. + p : int + Order, can range between [1,2n+1]. + s : float + Coordinate + + Returns + ------- + F : float + The harmonic :math:`F^p_n(s)` + + See Also + -------- + ellip_harm, ellip_normal + + Notes + ----- + Lamé functions of the second kind are related to the functions of the first kind: + + .. math:: + + F^p_n(s)=(2n + 1)E^p_n(s)\int_{0}^{1/s} + \frac{du}{(E^p_n(1/u))^2\sqrt{(1-u^2k^2)(1-u^2h^2)}} + + .. versionadded:: 0.15.0 + + Examples + -------- + >>> from scipy.special import ellip_harm_2 + >>> w = ellip_harm_2(5,8,2,1,10) + >>> w + 0.00108056853382 + + """ + with np.errstate(all='ignore'): + return _ellip_harm_2_vec(h2, k2, n, p, s) + + +def _ellip_normal_vec(h2, k2, n, p): + return _ellipsoid_norm(h2, k2, n, p) + + +_ellip_normal_vec = np.vectorize(_ellip_normal_vec, otypes='d') + + +def ellip_normal(h2, k2, n, p): + r""" + Ellipsoidal harmonic normalization constants gamma^p_n. + + The normalization constant is defined as + + .. math:: + + \gamma^p_n=8\int_{0}^{h}dx\int_{h}^{k}dy + \frac{(y^2-x^2)(E^p_n(y)E^p_n(x))^2}{\sqrt((k^2-y^2)(y^2-h^2)(h^2-x^2)(k^2-x^2)} + + Parameters + ---------- + h2 : float + ``h**2`` + k2 : float + ``k**2``; should be larger than ``h**2`` + n : int + Degree. + p : int + Order, can range between [1,2n+1]. + + Returns + ------- + gamma : float + The normalization constant :math:`\gamma^p_n` + + See Also + -------- + ellip_harm, ellip_harm_2 + + Notes + ----- + .. versionadded:: 0.15.0 + + Examples + -------- + >>> from scipy.special import ellip_normal + >>> w = ellip_normal(5,8,3,7) + >>> w + 1723.38796997 + + """ + with np.errstate(all='ignore'): + return _ellip_normal_vec(h2, k2, n, p) diff --git a/venv/lib/python3.12/site-packages/scipy/special/_ellip_harm_2.cpython-312-x86_64-linux-gnu.so b/venv/lib/python3.12/site-packages/scipy/special/_ellip_harm_2.cpython-312-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..9e563ebe4669b2c03a3e2c008768bfb027208b84 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/_ellip_harm_2.cpython-312-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b4748cee5d175467c3a2c68fca886cf607bd0a58096472dd0beba2f2f4687da0 +size 123528 diff --git a/venv/lib/python3.12/site-packages/scipy/special/_gufuncs.cpython-312-x86_64-linux-gnu.so b/venv/lib/python3.12/site-packages/scipy/special/_gufuncs.cpython-312-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..14b2bf4422ba814850d48c4ea1fc6c8f85d1197c --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/_gufuncs.cpython-312-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:19efbca6732a4489ac6ae872640b407bc83974a668fa4b7d4b55e3606cab301e +size 975504 diff --git a/venv/lib/python3.12/site-packages/scipy/special/_input_validation.py b/venv/lib/python3.12/site-packages/scipy/special/_input_validation.py new file mode 100644 index 0000000000000000000000000000000000000000..e5b7fe36df87617bf91655623ee0076c37a4d08a --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/_input_validation.py @@ -0,0 +1,17 @@ +import math +import operator + +def _nonneg_int_or_fail(n, var_name, strict=True): + try: + if strict: + # Raises an exception if float + n = operator.index(n) + elif n == math.floor(n): + n = int(n) + else: + raise ValueError() + if n < 0: + raise ValueError() + except (ValueError, TypeError) as err: + raise err.__class__(f"{var_name} must be a non-negative integer") from err + return n diff --git a/venv/lib/python3.12/site-packages/scipy/special/_lambertw.py b/venv/lib/python3.12/site-packages/scipy/special/_lambertw.py new file mode 100644 index 0000000000000000000000000000000000000000..f758c7c21fdddc0ec1b84727d90c6de7f34a094e --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/_lambertw.py @@ -0,0 +1,149 @@ +from ._ufuncs import _lambertw + +import numpy as np + + +def lambertw(z, k=0, tol=1e-8): + r""" + lambertw(z, k=0, tol=1e-8) + + Lambert W function. + + The Lambert W function `W(z)` is defined as the inverse function + of ``w * exp(w)``. In other words, the value of ``W(z)`` is + such that ``z = W(z) * exp(W(z))`` for any complex number + ``z``. + + The Lambert W function is a multivalued function with infinitely + many branches. Each branch gives a separate solution of the + equation ``z = w exp(w)``. Here, the branches are indexed by the + integer `k`. + + Parameters + ---------- + z : array_like + Input argument. + k : int, optional + Branch index. + tol : float, optional + Evaluation tolerance. + + Returns + ------- + w : array + `w` will have the same shape as `z`. + + See Also + -------- + wrightomega : the Wright Omega function + + Notes + ----- + All branches are supported by `lambertw`: + + * ``lambertw(z)`` gives the principal solution (branch 0) + * ``lambertw(z, k)`` gives the solution on branch `k` + + The Lambert W function has two partially real branches: the + principal branch (`k = 0`) is real for real ``z > -1/e``, and the + ``k = -1`` branch is real for ``-1/e < z < 0``. All branches except + ``k = 0`` have a logarithmic singularity at ``z = 0``. + + **Possible issues** + + The evaluation can become inaccurate very close to the branch point + at ``-1/e``. In some corner cases, `lambertw` might currently + fail to converge, or can end up on the wrong branch. + + **Algorithm** + + Halley's iteration is used to invert ``w * exp(w)``, using a first-order + asymptotic approximation (O(log(w)) or `O(w)`) as the initial estimate. + + The definition, implementation and choice of branches is based on [2]_. + + References + ---------- + .. [1] https://en.wikipedia.org/wiki/Lambert_W_function + .. [2] Corless et al, "On the Lambert W function", Adv. Comp. Math. 5 + (1996) 329-359. + https://cs.uwaterloo.ca/research/tr/1993/03/W.pdf + + Examples + -------- + The Lambert W function is the inverse of ``w exp(w)``: + + >>> import numpy as np + >>> from scipy.special import lambertw + >>> w = lambertw(1) + >>> w + (0.56714329040978384+0j) + >>> w * np.exp(w) + (1.0+0j) + + Any branch gives a valid inverse: + + >>> w = lambertw(1, k=3) + >>> w + (-2.8535817554090377+17.113535539412148j) + >>> w*np.exp(w) + (1.0000000000000002+1.609823385706477e-15j) + + **Applications to equation-solving** + + The Lambert W function may be used to solve various kinds of + equations. We give two examples here. + + First, the function can be used to solve implicit equations of the + form + + :math:`x = a + b e^{c x}` + + for :math:`x`. We assume :math:`c` is not zero. After a little + algebra, the equation may be written + + :math:`z e^z = -b c e^{a c}` + + where :math:`z = c (a - x)`. :math:`z` may then be expressed using + the Lambert W function + + :math:`z = W(-b c e^{a c})` + + giving + + :math:`x = a - W(-b c e^{a c})/c` + + For example, + + >>> a = 3 + >>> b = 2 + >>> c = -0.5 + + The solution to :math:`x = a + b e^{c x}` is: + + >>> x = a - lambertw(-b*c*np.exp(a*c))/c + >>> x + (3.3707498368978794+0j) + + Verify that it solves the equation: + + >>> a + b*np.exp(c*x) + (3.37074983689788+0j) + + The Lambert W function may also be used find the value of the infinite + power tower :math:`z^{z^{z^{\ldots}}}`: + + >>> def tower(z, n): + ... if n == 0: + ... return z + ... return z ** tower(z, n-1) + ... + >>> tower(0.5, 100) + 0.641185744504986 + >>> -lambertw(-np.log(0.5)) / np.log(0.5) + (0.64118574450498589+0j) + """ + # TODO: special expert should inspect this + # interception; better place to do it? + k = np.asarray(k, dtype=np.dtype("long")) + return _lambertw(z, k, tol) diff --git a/venv/lib/python3.12/site-packages/scipy/special/_logsumexp.py b/venv/lib/python3.12/site-packages/scipy/special/_logsumexp.py new file mode 100644 index 0000000000000000000000000000000000000000..fdfdcded06e22591ef48117d5ff8af83c339cf74 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/_logsumexp.py @@ -0,0 +1,420 @@ +import numpy as np +from scipy._lib._array_api import ( + array_namespace, + xp_capabilities, + xp_device, + xp_size, + xp_promote, + xp_float_to_complex, +) +from scipy._external import array_api_extra as xpx + +__all__ = ["logsumexp", "softmax", "log_softmax"] + + +@xp_capabilities() +def logsumexp(a, axis=None, b=None, keepdims=False, return_sign=False): + """Compute the log of the sum of exponentials of input elements. + + Parameters + ---------- + a : array_like + Input array. + axis : None or int or tuple of ints, optional + Axis or axes over which the sum is taken. By default `axis` is None, + and all elements are summed. + + .. versionadded:: 0.11.0 + b : array-like, optional + Scaling factor for exp(`a`) must be of the same shape as `a` or + broadcastable to `a`. These values may be negative in order to + implement subtraction. + + .. versionadded:: 0.12.0 + keepdims : bool, optional + If this is set to True, the axes which are reduced are left in the + result as dimensions with size one. With this option, the result + will broadcast correctly against the original array. + + .. versionadded:: 0.15.0 + return_sign : bool, optional + If this is set to True, the result will be a pair containing sign + information; if False, results that are negative will be returned + as NaN. Default is False (no sign information). + + .. versionadded:: 0.16.0 + + Returns + ------- + res : ndarray + The result, ``np.log(np.sum(np.exp(a)))`` calculated in a numerically + more stable way. If `b` is given then ``np.log(np.sum(b*np.exp(a)))`` + is returned. If ``return_sign`` is True, ``res`` contains the log of + the absolute value of the argument. + sgn : ndarray + If ``return_sign`` is True, this will be an array of floating-point + numbers matching res containing +1, 0, -1 (for real-valued inputs) + or a complex phase (for complex inputs). This gives the sign of the + argument of the logarithm in ``res``. + If ``return_sign`` is False, only one result is returned. + + See Also + -------- + :data:`numpy.logaddexp` + :data:`numpy.logaddexp2` + + Notes + ----- + NumPy has a logaddexp function which is very similar to `logsumexp`, but + only handles two arguments. `logaddexp.reduce` is similar to this + function, but may be less stable. + + The logarithm is a multivalued function: for each :math:`x` there is an + infinite number of :math:`z` such that :math:`exp(z) = x`. The convention + is to return the :math:`z` whose imaginary part lies in :math:`(-pi, pi]`. + + Examples + -------- + >>> import numpy as np + >>> from scipy.special import logsumexp + >>> a = np.arange(10) + >>> logsumexp(a) + 9.4586297444267107 + >>> np.log(np.sum(np.exp(a))) + 9.4586297444267107 + + With weights + + >>> a = np.arange(10) + >>> b = np.arange(10, 0, -1) + >>> logsumexp(a, b=b) + 9.9170178533034665 + >>> np.log(np.sum(b*np.exp(a))) + 9.9170178533034647 + + Returning a sign flag + + >>> logsumexp([1,2],b=[1,-1],return_sign=True) + (1.5413248546129181, -1.0) + + Notice that `logsumexp` does not directly support masked arrays. To use it + on a masked array, convert the mask into zero weights: + + >>> a = np.ma.array([np.log(2), 2, np.log(3)], + ... mask=[False, True, False]) + >>> b = (~a.mask).astype(int) + >>> logsumexp(a.data, b=b), np.log(5) + 1.6094379124341005, 1.6094379124341005 + + """ + xp = array_namespace(a, b) + a, b = xp_promote(a, b, broadcast=True, force_floating=True, xp=xp) + a = xpx.atleast_nd(a, ndim=1, xp=xp) + b = xpx.atleast_nd(b, ndim=1, xp=xp) if b is not None else b + axis = tuple(range(a.ndim)) if axis is None else axis + + if xp_size(a) != 0: + with np.errstate(divide='ignore', invalid='ignore', over='ignore'): + # Where result is infinite, we use the direct logsumexp calculation to + # delegate edge case handling to the behavior of `xp.log` and `xp.exp`, + # which should follow the C99 standard for complex values. + b_exp_a = xp.exp(a) if b is None else b * xp.exp(a) + sum_ = xp.sum(b_exp_a, axis=axis, keepdims=True) + sgn_inf = xp.sign(sum_) if return_sign else None + sum_ = xp.abs(sum_) if return_sign else sum_ + out_inf = xp.log(sum_) + + with np.errstate(divide='ignore', invalid='ignore'): # log of zero is OK + out, sgn = _logsumexp(a, b, axis=axis, return_sign=return_sign, xp=xp) + + # Replace infinite results. This probably could be done with an + # `apply_where`-like strategy to avoid redundant calculation, but currently + # `apply_where` itself is only for elementwise functions. + out_finite = xp.isfinite(out) + out = xp.where(out_finite, out, out_inf) + sgn = xp.where(out_finite, sgn, sgn_inf) if return_sign else sgn + else: + shape = np.asarray(a.shape) # NumPy is convenient for shape manipulation + shape[axis] = 1 + out = xp.full(tuple(shape), -xp.inf, dtype=a.dtype, device=xp_device(a)) + sgn = xp.sign(out) + + if xp.isdtype(out.dtype, 'complex floating'): + if return_sign: + real = xp.real(sgn) + imag = xp_float_to_complex(_wrap_radians(xp.imag(sgn), xp=xp), xp=xp) + sgn = real + imag*1j + else: + real = xp.real(out) + imag = xp_float_to_complex(_wrap_radians(xp.imag(out), xp=xp), xp=xp) + out = real + imag*1j + + # Deal with shape details - reducing dimensions and convert 0-D to scalar for NumPy + out = xp.squeeze(out, axis=axis) if not keepdims else out + sgn = xp.squeeze(sgn, axis=axis) if (sgn is not None and not keepdims) else sgn + out = out[()] if out.ndim == 0 else out + sgn = sgn[()] if (sgn is not None and sgn.ndim == 0) else sgn + + return (out, sgn) if return_sign else out + + +def _wrap_radians(x, *, xp): + # Wrap radians to (-pi, pi] interval + wrapped = -((-x + xp.pi) % (2 * xp.pi) - xp.pi) + # preserve relative precision + no_wrap = xp.abs(x) < xp.pi + return xp.where(no_wrap, x, wrapped) + + +def _elements_and_indices_with_max_real(a, *, axis=-1, xp): + # This is an array-API compatible `max` function that works something + # like `np.max` for complex input. The important part is that it finds + # the element with maximum real part. When there are multiple complex values + # with this real part, it doesn't matter which we choose. + # We could use `argmax` on real component, but array API doesn't yet have + # `take_along_axis`, and even if it did, we would have problems with axis tuples. + # Feel free to rewrite! It's ugly, but it's not the purpose of the PR, and + # it gets the job done. + + if xp.isdtype(a.dtype, "complex floating"): + # select all elements with max real part. + real_a = xp.real(a) + max_ = xp.max(real_a, axis=axis, keepdims=True) + mask = real_a == max_ + + # Of those, choose one arbitrarily. This is a reasonably + # simple, array-API compatible way of doing so that doesn't + # have a problem with `axis` being a tuple or None. + i = xp.reshape(xp.arange(xp_size(a), device=xp_device(a)), a.shape) + i = xpx.at(i, ~mask).set(-1) + max_i = xp.max(i, axis=axis, keepdims=True) + mask = i == max_i + a = xp.where(mask, a, 0.) + max_ = xp.sum(a, axis=axis, dtype=a.dtype, keepdims=True) + else: + max_ = xp.max(a, axis=axis, keepdims=True) + mask = a == max_ + + return max_, mask + + +def _logsumexp(a, b, *, axis, return_sign, xp): + # This has been around for about a decade, so let's consider it a feature: + # Even if element of `a` is infinite or NaN, it adds nothing to the sum if + # the corresponding weight is zero. + if b is not None: + a = xpx.at(a, b == 0).set(-xp.inf, copy=True) + + # Find element with maximum real part, since this is what affects the magnitude + # of the exponential. Possible enhancement: include log of `b` magnitude in `a`. + a_max, i_max = _elements_and_indices_with_max_real(a, axis=axis, xp=xp) + + # for precision, these terms are separated out of the main sum. + a = xpx.at(a, i_max).set(-xp.inf, copy=True if b is None else None) + i_max_dt = xp.astype(i_max, a.dtype) + # This is an inefficient way of getting `m` because it is the sum of a sparse + # array; however, this is the simplest way I can think of to get the right shape. + b_i_max = i_max_dt if b is None else b * i_max_dt + m = xp.sum(b_i_max, axis=axis, keepdims=True, dtype=a.dtype) + + # Shift, exponentiate, scale, and sum + exp = b * xp.exp(a - a_max) if b is not None else xp.exp(a - a_max) + s = xp.sum(exp, axis=axis, keepdims=True, dtype=exp.dtype) + s = xp.where(s == 0, s, s/m) + + # Separate sign/magnitude information + # Originally, this was only performed if `return_sign=True`. + # However, this is also needed if any elements of `m < 0` or `s < -1`. + # An improvement would be to perform the calculations only on these entries. + + sgn = xp.sign(s + 1) * xp.sign(m) + + if xp.isdtype(s.dtype, "real floating"): + # The log functions need positive arguments + s = xp.where(s < -1, -s - 2, s) + m = xp.abs(m) + else: + # `a_max` can have a sign component for complex input + sgn = sgn * xp.exp(xp.imag(a_max) * 1.0j) + + # Take log and undo shift + out = xp.log1p(s) + xp.log(m) + a_max + + if return_sign: + out = xp.real(out) + elif xp.isdtype(out.dtype, 'real floating'): + out = xpx.at(out)[sgn < 0].set(xp.nan) + + return out, sgn + + +@xp_capabilities() +def softmax(x, axis=None): + r"""Compute the softmax function. + + The softmax function transforms each element of a collection by + computing the exponential of each element divided by the sum of the + exponentials of all the elements. That is, if `x` is a one-dimensional + numpy array:: + + softmax(x) = np.exp(x)/sum(np.exp(x)) + + Parameters + ---------- + x : array_like + Input array. + axis : int or tuple of ints, optional + Axis to compute values along. Default is None and softmax will be + computed over the entire array `x`. + + Returns + ------- + s : ndarray + An array the same shape as `x`. The result will sum to 1 along the + specified axis. + + Notes + ----- + The formula for the softmax function :math:`\sigma(x)` for a vector + :math:`x = \{x_0, x_1, ..., x_{n-1}\}` is + + .. math:: \sigma(x)_j = \frac{e^{x_j}}{\sum_k e^{x_k}} + + The `softmax` function is the gradient of `logsumexp`. + + The implementation uses shifting to avoid overflow. See [1]_ for more + details. + + .. versionadded:: 1.2.0 + + References + ---------- + .. [1] P. Blanchard, D.J. Higham, N.J. Higham, "Accurately computing the + log-sum-exp and softmax functions", IMA Journal of Numerical Analysis, + Vol.41(4), :doi:`10.1093/imanum/draa038`. + + Examples + -------- + >>> import numpy as np + >>> from scipy.special import softmax + >>> np.set_printoptions(precision=5) + + >>> x = np.array([[1, 0.5, 0.2, 3], + ... [1, -1, 7, 3], + ... [2, 12, 13, 3]]) + ... + + Compute the softmax transformation over the entire array. + + >>> m = softmax(x) + >>> m + array([[ 4.48309e-06, 2.71913e-06, 2.01438e-06, 3.31258e-05], + [ 4.48309e-06, 6.06720e-07, 1.80861e-03, 3.31258e-05], + [ 1.21863e-05, 2.68421e-01, 7.29644e-01, 3.31258e-05]]) + + >>> m.sum() + 1.0 + + Compute the softmax transformation along the first axis (i.e., the + columns). + + >>> m = softmax(x, axis=0) + + >>> m + array([[ 2.11942e-01, 1.01300e-05, 2.75394e-06, 3.33333e-01], + [ 2.11942e-01, 2.26030e-06, 2.47262e-03, 3.33333e-01], + [ 5.76117e-01, 9.99988e-01, 9.97525e-01, 3.33333e-01]]) + + >>> m.sum(axis=0) + array([ 1., 1., 1., 1.]) + + Compute the softmax transformation along the second axis (i.e., the rows). + + >>> m = softmax(x, axis=1) + >>> m + array([[ 1.05877e-01, 6.42177e-02, 4.75736e-02, 7.82332e-01], + [ 2.42746e-03, 3.28521e-04, 9.79307e-01, 1.79366e-02], + [ 1.22094e-05, 2.68929e-01, 7.31025e-01, 3.31885e-05]]) + + >>> m.sum(axis=1) + array([ 1., 1., 1.]) + + """ + xp = array_namespace(x) + x = xp.asarray(x) + x_max = xp.max(x, axis=axis, keepdims=True) + exp_x_shifted = xp.exp(x - x_max) + return exp_x_shifted / xp.sum(exp_x_shifted, axis=axis, keepdims=True) + + +@xp_capabilities() +def log_softmax(x, axis=None): + r"""Compute the logarithm of the softmax function. + + In principle:: + + log_softmax(x) = log(softmax(x)) + + but using a more accurate implementation. + + Parameters + ---------- + x : array_like + Input array. + axis : int or tuple of ints, optional + Axis to compute values along. Default is None and softmax will be + computed over the entire array `x`. + + Returns + ------- + s : ndarray or scalar + An array with the same shape as `x`. Exponential of the result will + sum to 1 along the specified axis. If `x` is a scalar, a scalar is + returned. + + Notes + ----- + `log_softmax` is more accurate than ``np.log(softmax(x))`` with inputs that + make `softmax` saturate (see examples below). + + .. versionadded:: 1.5.0 + + Examples + -------- + >>> import numpy as np + >>> from scipy.special import log_softmax + >>> from scipy.special import softmax + >>> np.set_printoptions(precision=5) + + >>> x = np.array([1000.0, 1.0]) + + >>> y = log_softmax(x) + >>> y + array([ 0., -999.]) + + >>> with np.errstate(divide='ignore'): + ... y = np.log(softmax(x)) + ... + >>> y + array([ 0., -inf]) + + """ + xp = array_namespace(x) + x = xp.asarray(x) + + x_max = xp.max(x, axis=axis, keepdims=True) + + if x_max.ndim > 0: + x_max = xpx.at(x_max, ~xp.isfinite(x_max)).set(0) + elif not xp.isfinite(x_max): + x_max = 0 + + tmp = x - x_max + exp_tmp = xp.exp(tmp) + + # suppress warnings about log of zero + with np.errstate(divide='ignore'): + s = xp.sum(exp_tmp, axis=axis, keepdims=True) + out = xp.log(s) + + return tmp - out diff --git a/venv/lib/python3.12/site-packages/scipy/special/_mptestutils.py b/venv/lib/python3.12/site-packages/scipy/special/_mptestutils.py new file mode 100644 index 0000000000000000000000000000000000000000..9e519093dface79e21f16d7063541ad107f5ca96 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/_mptestutils.py @@ -0,0 +1,453 @@ +import os +import sys +import time +from itertools import zip_longest + +import numpy as np +from numpy.testing import assert_ +import pytest + +from scipy.special._testutils import assert_func_equal + +try: + import mpmath +except ImportError: + pass + + +# ------------------------------------------------------------------------------ +# Machinery for systematic tests with mpmath +# ------------------------------------------------------------------------------ + +class Arg: + """Generate a set of numbers on the real axis, concentrating on + 'interesting' regions and covering all orders of magnitude. + + """ + + def __init__(self, a=-np.inf, b=np.inf, inclusive_a=True, inclusive_b=True): + if a > b: + raise ValueError("a should be less than or equal to b") + if a == -np.inf: + a = -0.5*np.finfo(float).max + if b == np.inf: + b = 0.5*np.finfo(float).max + self.a, self.b = a, b + + self.inclusive_a, self.inclusive_b = inclusive_a, inclusive_b + + def _positive_values(self, a, b, n): + if a < 0: + raise ValueError("a should be positive") + + # Try to put half of the points into a linspace between a and + # 10 the other half in a logspace. + if n % 2 == 0: + nlogpts = n//2 + nlinpts = nlogpts + else: + nlogpts = n//2 + nlinpts = nlogpts + 1 + + if a >= 10: + # Outside of linspace range; just return a logspace. + pts = np.logspace(np.log10(a), np.log10(b), n) + elif a > 0 and b < 10: + # Outside of logspace range; just return a linspace + pts = np.linspace(a, b, n) + elif a > 0: + # Linspace between a and 10 and a logspace between 10 and + # b. + linpts = np.linspace(a, 10, nlinpts, endpoint=False) + logpts = np.logspace(1, np.log10(b), nlogpts) + pts = np.hstack((linpts, logpts)) + elif a == 0 and b <= 10: + # Linspace between 0 and b and a logspace between 0 and + # the smallest positive point of the linspace + linpts = np.linspace(0, b, nlinpts) + if linpts.size > 1: + right = np.log10(linpts[1]) + else: + right = -30 + logpts = np.logspace(-30, right, nlogpts, endpoint=False) + pts = np.hstack((logpts, linpts)) + else: + # Linspace between 0 and 10, logspace between 0 and the + # smallest positive point of the linspace, and a logspace + # between 10 and b. + if nlogpts % 2 == 0: + nlogpts1 = nlogpts//2 + nlogpts2 = nlogpts1 + else: + nlogpts1 = nlogpts//2 + nlogpts2 = nlogpts1 + 1 + linpts = np.linspace(0, 10, nlinpts, endpoint=False) + if linpts.size > 1: + right = np.log10(linpts[1]) + else: + right = -30 + logpts1 = np.logspace(-30, right, nlogpts1, endpoint=False) + logpts2 = np.logspace(1, np.log10(b), nlogpts2) + pts = np.hstack((logpts1, linpts, logpts2)) + + return np.sort(pts) + + def values(self, n): + """Return an array containing n numbers.""" + a, b = self.a, self.b + if a == b: + return np.zeros(n) + + if not self.inclusive_a: + n += 1 + if not self.inclusive_b: + n += 1 + + if n % 2 == 0: + n1 = n//2 + n2 = n1 + else: + n1 = n//2 + n2 = n1 + 1 + + if a >= 0: + pospts = self._positive_values(a, b, n) + negpts = [] + elif b <= 0: + pospts = [] + negpts = -self._positive_values(-b, -a, n) + else: + pospts = self._positive_values(0, b, n1) + negpts = -self._positive_values(0, -a, n2 + 1) + # Don't want to get zero twice + negpts = negpts[1:] + pts = np.hstack((negpts[::-1], pospts)) + + if not self.inclusive_a: + pts = pts[1:] + if not self.inclusive_b: + pts = pts[:-1] + return pts + + +class FixedArg: + def __init__(self, values): + self._values = np.asarray(values) + + def values(self, n): + return self._values + + +class ComplexArg: + def __init__(self, a=complex(-np.inf, -np.inf), b=complex(np.inf, np.inf)): + self.real = Arg(a.real, b.real) + self.imag = Arg(a.imag, b.imag) + + def values(self, n): + m = int(np.floor(np.sqrt(n))) + x = self.real.values(m) + y = self.imag.values(m + 1) + return (x[:,None] + 1j*y[None,:]).ravel() + + +class IntArg: + def __init__(self, a=-1000, b=1000): + self.a = a + self.b = b + + def values(self, n): + v1 = Arg(self.a, self.b).values(max(1 + n//2, n-5)).astype(int) + v2 = np.arange(-5, 5) + v = np.unique(np.r_[v1, v2]) + v = v[(v >= self.a) & (v < self.b)] + return v + + +def get_args(argspec, n): + if isinstance(argspec, np.ndarray): + args = argspec.copy() + else: + nargs = len(argspec) + ms = np.asarray( + [1.5 if isinstance(spec, ComplexArg) else 1.0 for spec in argspec] + ) + ms = (n**(ms/sum(ms))).astype(int) + 1 + + args = [spec.values(m) for spec, m in zip(argspec, ms)] + args = np.array(np.broadcast_arrays(*np.ix_(*args))).reshape(nargs, -1).T + + return args + + +class MpmathData: + def __init__(self, scipy_func, mpmath_func, arg_spec, name=None, + dps=None, prec=None, n=None, rtol=1e-7, atol=1e-300, + ignore_inf_sign=False, distinguish_nan_and_inf=True, + nan_ok=True, param_filter=None): + + # mpmath tests are really slow (see gh-6989). Use a small number of + # points by default, increase back to 5000 (old default) if XSLOW is + # set + if n is None: + try: + is_xslow = int(os.environ.get('SCIPY_XSLOW', '0')) + except ValueError: + is_xslow = False + + n = 5000 if is_xslow else 500 + + self.scipy_func = scipy_func + self.mpmath_func = mpmath_func + self.arg_spec = arg_spec + self.dps = dps + self.prec = prec + self.n = n + self.rtol = rtol + self.atol = atol + self.ignore_inf_sign = ignore_inf_sign + self.nan_ok = nan_ok + if isinstance(self.arg_spec, np.ndarray): + self.is_complex = np.issubdtype(self.arg_spec.dtype, np.complexfloating) + else: + self.is_complex = any( + [isinstance(arg, ComplexArg) for arg in self.arg_spec] + ) + self.ignore_inf_sign = ignore_inf_sign + self.distinguish_nan_and_inf = distinguish_nan_and_inf + if not name or name == '': + name = getattr(scipy_func, '__name__', None) + if not name or name == '': + name = getattr(mpmath_func, '__name__', None) + self.name = name + self.param_filter = param_filter + + def check(self): + np.random.seed(1234) + + # Generate values for the arguments + argarr = get_args(self.arg_spec, self.n) + + # Check + old_dps, old_prec = mpmath.mp.dps, mpmath.mp.prec + try: + if self.dps is not None: + dps_list = [self.dps] + else: + dps_list = [20] + if self.prec is not None: + mpmath.mp.prec = self.prec + + # Proper casting of mpmath input and output types. Using + # native mpmath types as inputs gives improved precision + # in some cases. + if np.issubdtype(argarr.dtype, np.complexfloating): + pytype = mpc2complex + + def mptype(x): + return mpmath.mpc(complex(x)) + else: + def mptype(x): + return mpmath.mpf(float(x)) + + def pytype(x): + if abs(x.imag) > 1e-16*(1 + abs(x.real)): + return np.nan + else: + return mpf2float(x.real) + + # Try out different dps until one (or none) works + for j, dps in enumerate(dps_list): + mpmath.mp.dps = dps + + try: + assert_func_equal( + self.scipy_func, + lambda *a: pytype(self.mpmath_func(*map(mptype, a))), + argarr, + vectorized=False, + rtol=self.rtol, + atol=self.atol, + ignore_inf_sign=self.ignore_inf_sign, + distinguish_nan_and_inf=self.distinguish_nan_and_inf, + nan_ok=self.nan_ok, + param_filter=self.param_filter + ) + break + except AssertionError: + if j >= len(dps_list)-1: + # reraise the Exception + tp, value, tb = sys.exc_info() + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value + finally: + mpmath.mp.dps, mpmath.mp.prec = old_dps, old_prec + + def __repr__(self): + if self.is_complex: + return f"" + else: + return f"" + + +def assert_mpmath_equal(*a, **kw): + d = MpmathData(*a, **kw) + d.check() + + +def nonfunctional_tooslow(func): + return pytest.mark.skip( + reason=" Test not yet functional (too slow), needs more work." + )(func) + + +# ------------------------------------------------------------------------------ +# Tools for dealing with mpmath quirks +# ------------------------------------------------------------------------------ + +def mpf2float(x): + """ + Convert an mpf to the nearest floating point number. Just using + float directly doesn't work because of results like this: + + with mp.workdps(50): + float(mpf("0.99999999999999999")) = 0.9999999999999999 + + """ + return float(mpmath.nstr(x, 17, min_fixed=0, max_fixed=0)) + + +def mpc2complex(x): + return complex(mpf2float(x.real), mpf2float(x.imag)) + + +def trace_args(func): + def tofloat(x): + if isinstance(x, mpmath.mpc): + return complex(x) + else: + return float(x) + + def wrap(*a, **kw): + sys.stderr.write(f"{tuple(map(tofloat, a))!r}: ") + sys.stderr.flush() + try: + r = func(*a, **kw) + sys.stderr.write(f"-> {r!r}") + finally: + sys.stderr.write("\n") + sys.stderr.flush() + return r + return wrap + + +try: + import signal + POSIX = ('setitimer' in dir(signal)) +except ImportError: + POSIX = False + + +class TimeoutError(Exception): + pass + + +def time_limited(timeout=0.5, return_val=np.nan, use_sigalrm=True): + """ + Decorator for setting a timeout for pure-Python functions. + + If the function does not return within `timeout` seconds, the + value `return_val` is returned instead. + + On POSIX this uses SIGALRM by default. On non-POSIX, settrace is + used. Do not use this with threads: the SIGALRM implementation + does probably not work well. The settrace implementation only + traces the current thread. + + The settrace implementation slows down execution speed. Slowdown + by a factor around 10 is probably typical. + """ + if POSIX and use_sigalrm: + def sigalrm_handler(signum, frame): + raise TimeoutError() + + def deco(func): + def wrap(*a, **kw): + old_handler = signal.signal(signal.SIGALRM, sigalrm_handler) + signal.setitimer(signal.ITIMER_REAL, timeout) + try: + return func(*a, **kw) + except TimeoutError: + return return_val + finally: + signal.setitimer(signal.ITIMER_REAL, 0) + signal.signal(signal.SIGALRM, old_handler) + return wrap + else: + def deco(func): + def wrap(*a, **kw): + start_time = time.time() + + def trace(frame, event, arg): + if time.time() - start_time > timeout: + raise TimeoutError() + return trace + sys.settrace(trace) + try: + return func(*a, **kw) + except TimeoutError: + sys.settrace(None) + return return_val + finally: + sys.settrace(None) + return wrap + return deco + + +def exception_to_nan(func): + """Decorate function to return nan if it raises an exception""" + def wrap(*a, **kw): + try: + return func(*a, **kw) + except Exception: + return np.nan + return wrap + + +def inf_to_nan(func): + """Decorate function to return nan if it returns inf""" + def wrap(*a, **kw): + v = func(*a, **kw) + if not np.isfinite(v): + return np.nan + return v + return wrap + + +def mp_assert_allclose(res, std, atol=0, rtol=1e-17): + """ + Compare lists of mpmath.mpf's or mpmath.mpc's directly so that it + can be done to higher precision than double. + """ + failures = [] + for k, (resval, stdval) in enumerate(zip_longest(res, std)): + if resval is None or stdval is None: + raise ValueError('Lengths of inputs res and std are not equal.') + if mpmath.fabs(resval - stdval) > atol + rtol*mpmath.fabs(stdval): + failures.append((k, resval, stdval)) + + nfail = len(failures) + if nfail > 0: + ndigits = int(abs(np.log10(rtol))) + msg = [""] + msg.append(f"Bad results ({nfail} out of {k + 1}) for the following points:") + for k, resval, stdval in failures: + resrep = mpmath.nstr(resval, ndigits, min_fixed=0, max_fixed=0) + stdrep = mpmath.nstr(stdval, ndigits, min_fixed=0, max_fixed=0) + if stdval == 0: + rdiff = "inf" + else: + rdiff = mpmath.fabs((resval - stdval)/stdval) + rdiff = mpmath.nstr(rdiff, 3) + msg.append(f"{k}: {resrep} != {stdrep} (rdiff {rdiff})") + assert_(False, "\n".join(msg)) diff --git a/venv/lib/python3.12/site-packages/scipy/special/_multiufuncs.py b/venv/lib/python3.12/site-packages/scipy/special/_multiufuncs.py new file mode 100644 index 0000000000000000000000000000000000000000..f62002ef701f0edffeb8ec348559f1436f80ab08 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/_multiufuncs.py @@ -0,0 +1,744 @@ +import collections +import numbers +import numpy as np + +from ._input_validation import _nonneg_int_or_fail + +from ._special_ufuncs import (legendre_p, assoc_legendre_p, + sph_legendre_p, sph_harm_y) +from ._gufuncs import (legendre_p_all, assoc_legendre_p_all, + sph_legendre_p_all, sph_harm_y_all) + +__all__ = [ + "assoc_legendre_p", + "assoc_legendre_p_all", + "legendre_p", + "legendre_p_all", + "sph_harm_y", + "sph_harm_y_all", + "sph_legendre_p", + "sph_legendre_p_all", +] + + +class MultiUFunc: + def __init__(self, ufunc_or_ufuncs, name=None, doc=None, *, + force_complex_output=False, **default_kwargs): + if not isinstance(ufunc_or_ufuncs, np.ufunc): + if isinstance(ufunc_or_ufuncs, collections.abc.Mapping): + ufuncs_iter = ufunc_or_ufuncs.values() + elif isinstance(ufunc_or_ufuncs, collections.abc.Iterable): + ufuncs_iter = ufunc_or_ufuncs + else: + raise ValueError("ufunc_or_ufuncs should be a ufunc or a" + " ufunc collection") + + # Perform input validation to ensure all ufuncs in ufuncs are + # actually ufuncs and all take the same input types. + seen_input_types = set() + for ufunc in ufuncs_iter: + if not isinstance(ufunc, np.ufunc): + raise ValueError("All ufuncs must have type `numpy.ufunc`." + f" Received {ufunc_or_ufuncs}") + seen_input_types.add(frozenset(x.split("->")[0] for x in ufunc.types)) + if len(seen_input_types) > 1: + raise ValueError("All ufuncs must take the same input types.") + + self.__name__ = name + self._ufunc_or_ufuncs = ufunc_or_ufuncs + self.__doc = doc + self.__force_complex_output = force_complex_output + self._default_kwargs = default_kwargs + self._resolve_out_shapes = None + self._finalize_out = None + self._key = None + self._ufunc_default_args = lambda *args, **kwargs: () + self._ufunc_default_kwargs = lambda *args, **kwargs: {} + self.__text_signature__ = doc.split("\n\n")[0] if doc else None + + @property + def __doc__(self): # pyrefly:ignore[bad-override] + return self.__doc + + def _override_key(self, func): + """Set `key` method by decorating a function. + """ + self._key = func + + def _override_ufunc_default_args(self, func): + self._ufunc_default_args = func + + def _override_ufunc_default_kwargs(self, func): + self._ufunc_default_kwargs = func + + def _override_resolve_out_shapes(self, func): + """Set `resolve_out_shapes` method by decorating a function.""" + if func.__doc__ is None: + func.__doc__ = \ + """Resolve to output shapes based on relevant inputs.""" + func.__name__ = "resolve_out_shapes" + self._resolve_out_shapes = func + + def _override_finalize_out(self, func): + self._finalize_out = func + + def _resolve_ufunc(self, **kwargs): + """Resolve to a ufunc based on keyword arguments.""" + + if isinstance(self._ufunc_or_ufuncs, np.ufunc): + return self._ufunc_or_ufuncs + + ufunc_key = self._key(**kwargs) + return self._ufunc_or_ufuncs[ufunc_key] + + def __call__(self, *args, **kwargs): + kwargs = self._default_kwargs | kwargs + + args += self._ufunc_default_args(**kwargs) + + ufunc = self._resolve_ufunc(**kwargs) + + # array arguments to be passed to the ufunc + ufunc_args = [np.asarray(arg) for arg in args[-ufunc.nin:]] + + ufunc_kwargs = self._ufunc_default_kwargs(**kwargs) + + if (self._resolve_out_shapes is not None): + ufunc_arg_shapes = tuple(np.shape(ufunc_arg) for ufunc_arg in ufunc_args) + ufunc_out_shapes = self._resolve_out_shapes(*args[:-ufunc.nin], + *ufunc_arg_shapes, ufunc.nout, + **kwargs) + + ufunc_arg_dtypes = tuple(ufunc_arg.dtype if hasattr(ufunc_arg, 'dtype') + else np.dtype(type(ufunc_arg)) + for ufunc_arg in ufunc_args) + + if hasattr(ufunc, 'resolve_dtypes'): + ufunc_dtypes = ufunc_arg_dtypes + ufunc.nout * (None,) + ufunc_dtypes = ufunc.resolve_dtypes(ufunc_dtypes) + ufunc_out_dtypes = ufunc_dtypes[-ufunc.nout:] + else: + ufunc_out_dtype = np.result_type(*ufunc_arg_dtypes) + if (not np.issubdtype(ufunc_out_dtype, np.inexact)): + ufunc_out_dtype = np.float64 + + ufunc_out_dtypes = ufunc.nout * (ufunc_out_dtype,) + + if self.__force_complex_output: + ufunc_out_dtypes = tuple(np.result_type(1j, ufunc_out_dtype) + for ufunc_out_dtype in ufunc_out_dtypes) + + out = tuple(np.empty(ufunc_out_shape, dtype=ufunc_out_dtype) + for ufunc_out_shape, ufunc_out_dtype + in zip(ufunc_out_shapes, ufunc_out_dtypes)) + + ufunc_kwargs['out'] = out + + out = ufunc(*ufunc_args, **ufunc_kwargs) + if (self._finalize_out is not None): + out = self._finalize_out(out) + + return out + + +sph_legendre_p = MultiUFunc( + sph_legendre_p, + "sph_legendre_p", + r"""sph_legendre_p(n, m, theta, *, diff_n=0) + + Spherical Legendre polynomial of the first kind. + + Parameters + ---------- + n : array_like of ints + Degree of the spherical Legendre polynomial. Must have ``n >= 0``. + m : array_like of ints + Order of the spherical Legendre polynomial. + theta : array_like + Input value. + diff_n : int, optional + A non-negative integer. Compute and return all derivatives up + to order `diff_n`. Default is 0. + + Returns + ------- + ndarray or tuple of ndarray + Spherical Legendre polynomial with ``diff_n`` derivatives. + + Notes + ----- + The spherical counterpart of an (unnormalized) associated Legendre polynomial has + the additional factor + + .. math:: + + \sqrt{\frac{(2 n + 1) (n - m)!}{4 \pi (n + m)!}} + + It is the same as the spherical harmonic :math:`Y_{n}^{m}(\theta, \phi)` + with :math:`\phi = 0`. + """, diff_n=0 +) + + +@sph_legendre_p._override_key +def _(diff_n): + diff_n = _nonneg_int_or_fail(diff_n, "diff_n", strict=False) + if not 0 <= diff_n <= 2: + raise ValueError( + "diff_n is currently only implemented for orders 0, 1, and 2," + f" received: {diff_n}." + ) + return diff_n + + +@sph_legendre_p._override_finalize_out +def _(out): + return np.moveaxis(out, -1, 0) + + +sph_legendre_p_all = MultiUFunc( + sph_legendre_p_all, + "sph_legendre_p_all", + """sph_legendre_p_all(n, m, theta, *, diff_n=0) + + All spherical Legendre polynomials of the first kind up to the + specified degree `n`, order `m`, and all derivatives up + to order `diff_n`. + + Parameters + ---------- + n : int + Degree of the spherical Legendre polynomials. Must have ``n >= 0``. + m : int + Order of the spherical Legendre polynomials. + theta : array_like + Input value. + diff_n : int, optional + A non-negative integer. Compute and return all derivatives up + to order `diff_n`. Default is 0. + + Returns + ------- + ndarray + Output shape is ``(diff_n + 1, n + 1, 2 * m + 1, ...)``. The entry at + ``(i, j, k)`` corresponds to the ``i``-th derivative, degree ``j``, and + order ``k`` for all ``0 <= i <= diff_n``, ``0 <= j <= n``, and + ``-m <= k <= m``. + + See Also + -------- + sph_legendre_p + """, diff_n=0 +) + + +@sph_legendre_p_all._override_key +def _(diff_n): + diff_n = _nonneg_int_or_fail(diff_n, "diff_n", strict=False) + if not 0 <= diff_n <= 2: + raise ValueError( + "diff_n is currently only implemented for orders 0, 1, and 2," + f" received: {diff_n}." + ) + return diff_n + + +@sph_legendre_p_all._override_ufunc_default_kwargs +def _(diff_n): + return {'axes': [()] + [(0, 1, -1)]} + + +@sph_legendre_p_all._override_resolve_out_shapes +def _(n, m, theta_shape, nout, diff_n): + if not isinstance(n, numbers.Integral) or (n < 0): + raise ValueError("n must be a non-negative integer.") + + return ((n + 1, 2 * abs(m) + 1) + theta_shape + (diff_n + 1,),) + + +@sph_legendre_p_all._override_finalize_out +def _(out): + return np.moveaxis(out, -1, 0) + + +assoc_legendre_p = MultiUFunc( + assoc_legendre_p, + "assoc_legendre_p", + r"""assoc_legendre_p(n, m, z, *, branch_cut=2, norm=False, diff_n=0) + + Associated Legendre polynomial of the first kind. + + Defined as + + .. math:: + + P_n^m(z) = (-1)^m (1 - z^2)^{m/2} + \frac{d^m}{dz^m} P_n(z) + + where :math:`P_n` is the Legendre polynomial. + + This definition includes the Condon-Shortley phase :math:`(-1)^m`. + + Parameters + ---------- + n : array_like of ints + Degree of the associated Legendre polynomial. Must have ``n >= 0``. + m : array_like of ints + Order of the associated Legendre polynomial. + z : array_like + Input value. + branch_cut : array_like of ints, optional + Selects branch cut. Must be 2 (default) or 3. + 2: cut on the real axis ``|z| > 1`` + 3: cut on the real axis ``-1 < z < 1`` + norm : bool, optional + If ``True``, compute the normalized associated Legendre polynomial. + Default is ``False``. + diff_n : int, optional + A non-negative integer. Compute and return all derivatives up + to order ``diff_n``. Default is 0. + + Returns + ------- + ndarray or tuple of ndarray + Associated Legendre polynomial with ``diff_n`` derivatives. + + Notes + ----- + The normalized counterpart of an (unnormalized) associated Legendre + polynomial has the additional factor + + .. math:: + + \sqrt{\frac{(2 n + 1) (n - m)!}{2 (n + m)!}} + + Examples + -------- + Evaluate :math:`P_2^1(z)` on :math:`[-1, 1]`: + + >>> import numpy as np + >>> from scipy.special import assoc_legendre_p + >>> z = np.linspace(-1, 1, 11) + >>> expected = -3 * z * np.sqrt(1 - z**2) + >>> np.allclose(assoc_legendre_p(2, 1, z), expected) + True + + Compute the normalized associated Legendre polynomial: + + >>> scale = np.sqrt(5/12) + >>> np.allclose(assoc_legendre_p(2, 1, z, norm=True), scale * expected) + True + """, branch_cut=2, norm=False, diff_n=0 +) + + +@assoc_legendre_p._override_key +def _(branch_cut, norm, diff_n): + diff_n = _nonneg_int_or_fail(diff_n, "diff_n", strict=False) + if not 0 <= diff_n <= 2: + raise ValueError( + "diff_n is currently only implemented for orders 0, 1, and 2," + f" received: {diff_n}." + ) + return norm, diff_n + + +@assoc_legendre_p._override_ufunc_default_args +def _(branch_cut, norm, diff_n): + return branch_cut, + + +@assoc_legendre_p._override_finalize_out +def _(out): + return np.moveaxis(out, -1, 0) + + +assoc_legendre_p_all = MultiUFunc( + assoc_legendre_p_all, + "assoc_legendre_p_all", + """assoc_legendre_p_all(n, m, z, *, branch_cut=2, norm=False, diff_n=0) + + All associated Legendre polynomials of the first kind up to the + specified degree `n`, order `m`, and all derivatives up + to order `diff_n`. + + Parameters + ---------- + n : int + Degree of the associated Legendre polynomials. Must have ``n >= 0``. + m : int + Order of the associated Legendre polynomials. + z : array_like + Input value. + branch_cut : array_like of ints, optional + Selects branch cut. Must be 2 (default) or 3. + 2: cut on the real axis ``|z| > 1`` + 3: cut on the real axis ``-1 < z < 1`` + norm : bool, optional + If ``True``, compute the normalized associated Legendre polynomials. + Default is ``False``. + diff_n : int, optional + A non-negative integer. Compute and return all derivatives up + to order ``diff_n``. Default is 0. + + Returns + ------- + ndarray + Output shape is ``(diff_n + 1, n + 1, 2 * m + 1, ...)``. The entry at + ``(i, j, k)`` corresponds to the ``i``-th derivative, degree ``j``, and + order ``k`` for all ``0 <= i <= diff_n``, ``0 <= j <= n``, and + ``-m <= k <= m``. + + See Also + -------- + assoc_legendre_p + """, branch_cut=2, norm=False, diff_n=0 +) + + +@assoc_legendre_p_all._override_key +def _(branch_cut, norm, diff_n): + if not ((isinstance(diff_n, numbers.Integral)) + and diff_n >= 0): + raise ValueError( + f"diff_n must be a non-negative integer, received: {diff_n}." + ) + if not 0 <= diff_n <= 2: + raise ValueError( + "diff_n is currently only implemented for orders 0, 1, and 2," + f" received: {diff_n}." + ) + return norm, diff_n + + +@assoc_legendre_p_all._override_ufunc_default_args +def _(branch_cut, norm, diff_n): + return branch_cut, + + +@assoc_legendre_p_all._override_ufunc_default_kwargs +def _(branch_cut, norm, diff_n): + return {'axes': [(), ()] + [(0, 1, -1)]} + + +@assoc_legendre_p_all._override_resolve_out_shapes +def _(n, m, z_shape, branch_cut_shape, nout, **kwargs): + diff_n = kwargs['diff_n'] + + if not isinstance(n, numbers.Integral) or (n < 0): + raise ValueError("n must be a non-negative integer.") + if not isinstance(m, numbers.Integral) or (m < 0): + raise ValueError("m must be a non-negative integer.") + + return ((n + 1, 2 * abs(m) + 1) + + np.broadcast_shapes(z_shape, branch_cut_shape) + (diff_n + 1,),) + + +@assoc_legendre_p_all._override_finalize_out +def _(out): + return np.moveaxis(out, -1, 0) + + +legendre_p = MultiUFunc( + legendre_p, + "legendre_p", + """legendre_p(n, z, *, diff_n=0) + + Legendre polynomial of the first kind. + + Parameters + ---------- + n : array_like of ints + Degree of the Legendre polynomial. Must have ``n >= 0``. + z : array_like + Input value. + diff_n : int, optional + A non-negative integer. Compute and return all derivatives up + to order ``diff_n``. Default is 0. + + Returns + ------- + p : ndarray or tuple of ndarray + Legendre polynomial with ``diff_n`` derivatives. + + See Also + -------- + legendre + + References + ---------- + .. [1] Zhang, Shanjie and Jin, Jianming. "Computation of Special + Functions", John Wiley and Sons, 1996. + https://people.sc.fsu.edu/~jburkardt/f77_src/special_functions/special_functions.html + + Examples + -------- + Evaluate the Legendre polynomial :math:`P_3` at :math:`z = 0.5`: + + >>> import numpy as np + >>> from scipy.special import legendre_p + >>> np.allclose(legendre_p(3, 0.5), -0.4375) + True + + Compute the value and first derivative with respect to ``z``: + + >>> p, dp = legendre_p(3, 0.5, diff_n=1) + >>> np.allclose([p, dp], [-0.4375, 0.375]) + True + """, diff_n=0 +) + + +@legendre_p._override_key +def _(diff_n): + if (not isinstance(diff_n, numbers.Integral)) or (diff_n < 0): + raise ValueError( + f"diff_n must be a non-negative integer, received: {diff_n}." + ) + if not 0 <= diff_n <= 2: + raise NotImplementedError( + "diff_n is currently only implemented for orders 0, 1, and 2," + f" received: {diff_n}." + ) + return diff_n + + +@legendre_p._override_finalize_out +def _(out): + return np.moveaxis(out, -1, 0) + + +legendre_p_all = MultiUFunc( + legendre_p_all, + "legendre_p_all", + """legendre_p_all(n, z, *, diff_n=0) + + All Legendre polynomials of the first kind up to the specified degree + `n` and all derivatives up to order `diff_n`. + + Parameters + ---------- + n : int + Degree of the Legendre polynomials. Must have ``n >= 0``. + z : array_like + Input value. + diff_n : int, optional + A non-negative integer. Compute and return all derivatives up + to order ``diff_n``. Default is 0. + + Returns + ------- + ndarray + Output shape is ``(diff_n + 1, n + 1, ...)``. The entry at ``(i, j)`` + corresponds to the ``i``-th derivative and degree ``j`` for all + ``0 <= i <= diff_n`` and ``0 <= j <= n``. + + See Also + -------- + legendre_p + """, diff_n=0 +) + + +@legendre_p_all._override_key +def _(diff_n): + diff_n = _nonneg_int_or_fail(diff_n, "diff_n", strict=False) + if not 0 <= diff_n <= 2: + raise ValueError( + "diff_n is currently only implemented for orders 0, 1, and 2," + f" received: {diff_n}." + ) + return diff_n + + +@legendre_p_all._override_ufunc_default_kwargs +def _(diff_n): + return {'axes': [(), (0, -1)]} + + +@legendre_p_all._override_resolve_out_shapes +def _(n, z_shape, nout, diff_n): + n = _nonneg_int_or_fail(n, 'n', strict=False) + + return nout * ((n + 1,) + z_shape + (diff_n + 1,),) + + +@legendre_p_all._override_finalize_out +def _(out): + return np.moveaxis(out, -1, 0) + + +sph_harm_y = MultiUFunc( + sph_harm_y, + "sph_harm_y", + r"""sph_harm_y(n, m, theta, phi, *, diff_n=0) + + Spherical harmonics. + + They are defined as + + .. math:: + + Y_n^m(\theta,\phi) = \sqrt{\frac{2 n + 1}{4 \pi} \frac{(n - m)!}{(n + m)!}} + P_n^m(\cos(\theta)) e^{i m \phi} + + where :math:`P_n^m` are the (unnormalized) associated Legendre polynomials. + + Parameters + ---------- + n : array_like of ints + Degree of the harmonic. Must have ``n >= 0``. This is + often denoted by ``l`` (lower case L) in descriptions of + spherical harmonics. + m : array_like of ints + Order of the harmonic. + theta : array_like + Polar (colatitudinal) coordinate; must be in ``[0, pi]``. + phi : array_like + Azimuthal (longitudinal) coordinate; must be in ``[0, 2*pi]``. + diff_n : int, optional + A non-negative integer. Compute and return all derivatives up + to order `diff_n`. Default is 0. + + Returns + ------- + ndarray or tuple of ndarray + Spherical harmonics with `diff_n` derivatives. + + Notes + ----- + There are different conventions for the meanings of the input + arguments ``theta`` and ``phi``. In SciPy ``theta`` is the + polar angle and ``phi`` is the azimuthal angle. It is common to + see the opposite convention, that is, ``theta`` as the azimuthal angle + and ``phi`` as the polar angle. + + Note that SciPy's spherical harmonics include the Condon-Shortley + phase [2]_ because it is part of `sph_legendre_p`. + + With SciPy's conventions, the first several spherical harmonics + are + + .. math:: + + Y_0^0(\theta, \phi) &= \frac{1}{2} \sqrt{\frac{1}{\pi}} \\ + Y_1^{-1}(\theta, \phi) &= \frac{1}{2} \sqrt{\frac{3}{2\pi}} + e^{-i\phi} \sin(\theta) \\ + Y_1^0(\theta, \phi) &= \frac{1}{2} \sqrt{\frac{3}{\pi}} + \cos(\theta) \\ + Y_1^1(\theta, \phi) &= -\frac{1}{2} \sqrt{\frac{3}{2\pi}} + e^{i\phi} \sin(\theta). + + References + ---------- + .. [1] Digital Library of Mathematical Functions, 14.30. + https://dlmf.nist.gov/14.30 + .. [2] https://en.wikipedia.org/wiki/Spherical_harmonics#Condon.E2.80.93Shortley_phase + """, force_complex_output=True, diff_n=0 +) + + +@sph_harm_y._override_key +def _(diff_n): + diff_n = _nonneg_int_or_fail(diff_n, "diff_n", strict=False) + if not 0 <= diff_n <= 2: + raise ValueError( + "diff_n is currently only implemented for orders 0, 1, and 2," + f" received: {diff_n}." + ) + return diff_n + + +@sph_harm_y._override_finalize_out +def _(out): + if (out.shape[-1] == 1): + return out[..., 0, 0] + + if (out.shape[-1] == 2): + return out[..., 0, 0], out[..., [1, 0], [0, 1]] + + if (out.shape[-1] == 3): + return (out[..., 0, 0], out[..., [1, 0], [0, 1]], + out[..., [[2, 1], [1, 0]], [[0, 1], [1, 2]]]) + + +sph_harm_y_all = MultiUFunc( + sph_harm_y_all, + "sph_harm_y_all", + """sph_harm_y_all(n, m, theta, phi, *, diff_n=0) + + All spherical harmonics up to the specified degree `n`, order `m`, + and all derivatives up to order `diff_n`. + + Parameters + ---------- + n : int + Degree of the harmonics. Must have ``n >= 0``. This is + often denoted by ``l`` (lower case L) in descriptions of + spherical harmonics. + m : int + Order of the harmonics. + theta : array_like + Polar (colatitudinal) coordinate; must be in ``[0, pi]``. + phi : array_like + Azimuthal (longitudinal) coordinate; must be in ``[0, 2*pi]``. + diff_n : int, optional + A non-negative integer. Compute and return all derivatives up + to order `diff_n`. Default is 0. + + Returns + ------- + ndarray or tuple of ndarray + Returns a tuple of length ``diff_n + 1`` (if ``diff_n > 0``). The first + entry corresponds to the spherical harmonics, the second entry + (if ``diff_n >= 1``) to the gradient, and the third entry + (if ``diff_n >= 2``) to the Hessian matrix. Each entry is an array of + shape ``(n + 1, 2 * m + 1, ...)``, where the entry at ``(i, j)`` + corresponds to degree ``i`` and order ``j`` for all ``0 <= i <= n`` + and ``-m <= j <= m``. + + See Also + -------- + sph_harm_y + """, force_complex_output=True, diff_n=0 +) + + +@sph_harm_y_all._override_key +def _(diff_n): + diff_n = _nonneg_int_or_fail(diff_n, "diff_n", strict=False) + if not 0 <= diff_n <= 2: + raise ValueError( + "diff_n is currently only implemented for orders 2," + f" received: {diff_n}." + ) + return diff_n + + +@sph_harm_y_all._override_ufunc_default_kwargs +def _(diff_n): + return {'axes': [(), ()] + [(0, 1, -2, -1)]} + + +@sph_harm_y_all._override_resolve_out_shapes +def _(n, m, theta_shape, phi_shape, nout, **kwargs): + diff_n = kwargs['diff_n'] + + if not isinstance(n, numbers.Integral) or (n < 0): + raise ValueError("n must be a non-negative integer.") + + return ((n + 1, 2 * abs(m) + 1) + np.broadcast_shapes(theta_shape, phi_shape) + + (diff_n + 1, diff_n + 1),) + + +@sph_harm_y_all._override_finalize_out +def _(out): + if (out.shape[-1] == 1): + return out[..., 0, 0] + + if (out.shape[-1] == 2): + return out[..., 0, 0], out[..., [1, 0], [0, 1]] + + if (out.shape[-1] == 3): + return (out[..., 0, 0], out[..., [1, 0], [0, 1]], + out[..., [[2, 1], [1, 0]], [[0, 1], [1, 2]]]) diff --git a/venv/lib/python3.12/site-packages/scipy/special/_orthogonal.py b/venv/lib/python3.12/site-packages/scipy/special/_orthogonal.py new file mode 100644 index 0000000000000000000000000000000000000000..de51d61f6912abd1d28686d2638d1b31527ab836 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/_orthogonal.py @@ -0,0 +1,2856 @@ +""" +A collection of functions to find the weights and abscissas for +Gaussian Quadrature. + +These calculations are done by finding the eigenvalues of a +tridiagonal matrix whose entries are dependent on the coefficients +in the recursion formula for the orthogonal polynomials with the +corresponding weighting function over the interval. + +Many recursion relations for orthogonal polynomials are given: + +.. math:: + + a1n f_{n+1} (x) = (a2n + a3n x ) f_n (x) - a4n f_{n-1} (x) + +The recursion relation of interest is + +.. math:: + + P_{n+1} (x) = (x - A_n) P_n (x) - B_n P_{n-1} (x) + +where :math:`P` has a different normalization than :math:`f`. + +The coefficients can be found as: + +.. math:: + + A_n = -a2n / a3n + \\qquad + B_n = ( a4n / a3n \\sqrt{h_n-1 / h_n})^2 + +where + +.. math:: + + h_n = \\int_a^b w(x) f_n(x)^2 + +assume: + +.. math:: + + P_0 (x) = 1 + \\qquad + P_{-1} (x) == 0 + +For the mathematical background, see [golub.welsch-1969-mathcomp]_ and +[abramowitz.stegun-1965]_. + +References +---------- +.. [golub.welsch-1969-mathcomp] + Golub, Gene H, and John H Welsch. 1969. Calculation of Gauss + Quadrature Rules. *Mathematics of Computation* 23, 221-230+s1--s10. + +.. [abramowitz.stegun-1965] + Abramowitz, Milton, and Irene A Stegun. (1965) *Handbook of + Mathematical Functions: with Formulas, Graphs, and Mathematical + Tables*. Gaithersburg, MD: National Bureau of Standards. + http://www.math.sfu.ca/~cbm/aands/ + +.. [townsend.trogdon.olver-2014] + Townsend, A. and Trogdon, T. and Olver, S. (2014) + *Fast computation of Gauss quadrature nodes and + weights on the whole real line*. :arXiv:`1410.5286`. + +.. [townsend.trogdon.olver-2015] + Townsend, A. and Trogdon, T. and Olver, S. (2015) + *Fast computation of Gauss quadrature nodes and + weights on the whole real line*. + IMA Journal of Numerical Analysis + :doi:`10.1093/imanum/drv002`. +""" +# +# Author: Travis Oliphant 2000 +# Updated Sep. 2003 (fixed bugs --- tested to be accurate) + +# SciPy imports. +import numpy as np +from numpy import (exp, inf, pi, sqrt, floor, sin, cos, around, + hstack, arccos, arange) +from scipy.special import airy + +# Local imports. +# There is no .pyi file for _specfun +from . import _specfun # type: ignore +from . import _ufuncs +_gam = _ufuncs.gamma + +_polyfuns = ['legendre', 'chebyt', 'chebyu', 'chebyc', 'chebys', + 'jacobi', 'laguerre', 'genlaguerre', 'hermite', + 'hermitenorm', 'gegenbauer', 'sh_legendre', 'sh_chebyt', + 'sh_chebyu', 'sh_jacobi'] + +# Correspondence between new and old names of root functions +_rootfuns_map = {'roots_legendre': 'p_roots', + 'roots_chebyt': 't_roots', + 'roots_chebyu': 'u_roots', + 'roots_chebyc': 'c_roots', + 'roots_chebys': 's_roots', + 'roots_jacobi': 'j_roots', + 'roots_laguerre': 'l_roots', + 'roots_genlaguerre': 'la_roots', + 'roots_hermite': 'h_roots', + 'roots_hermitenorm': 'he_roots', + 'roots_gegenbauer': 'cg_roots', + 'roots_sh_legendre': 'ps_roots', + 'roots_sh_chebyt': 'ts_roots', + 'roots_sh_chebyu': 'us_roots', + 'roots_sh_jacobi': 'js_roots'} + +__all__ = _polyfuns + list(_rootfuns_map.keys()) + + +class orthopoly1d(np.poly1d): + + def __init__(self, roots, weights=None, hn=1.0, kn=1.0, wfunc=None, + limits=None, monic=False, eval_func=None): + equiv_weights = [weights[k] / wfunc(roots[k]) for + k in range(len(roots))] + mu = sqrt(hn) + if monic: + evf = eval_func + if evf: + knn = kn + def eval_func(x): + return evf(x) / knn + mu = mu / abs(kn) + kn = 1.0 + + # compute coefficients from roots, then scale + poly = np.poly1d(roots, r=True) + np.poly1d.__init__(self, poly.coeffs * float(kn)) + + self.weights = np.array(list(zip(roots, weights, equiv_weights))) + self.weight_func = wfunc + self.limits = limits + self.normcoef = mu + + # Note: eval_func will be discarded on arithmetic + self._eval_func = eval_func + + def __call__(self, v): + if self._eval_func and not isinstance(v, np.poly1d): + return self._eval_func(v) + else: + return np.poly1d.__call__(self, v) + + def _scale(self, p): + if p == 1.0: + return + self._coeffs *= p + + evf = self._eval_func + if evf: + self._eval_func = lambda x: evf(x) * p + self.normcoef *= p + + +def _gen_roots_and_weights(n, mu0, an_func, bn_func, f, df, symmetrize, mu): + """[x,w] = gen_roots_and_weights(n,an_func,sqrt_bn_func,mu) + + Returns the roots (x) of an nth order orthogonal polynomial, + and weights (w) to use in appropriate Gaussian quadrature with that + orthogonal polynomial. + + The polynomials have the recurrence relation + P_n+1(x) = (x - A_n) P_n(x) - B_n P_n-1(x) + + an_func(n) should return A_n + sqrt_bn_func(n) should return sqrt(B_n) + mu ( = h_0 ) is the integral of the weight over the orthogonal + interval + """ + # lazy import to prevent to prevent linalg dependency for whole module (gh-23420) + from scipy import linalg + k = np.arange(n, dtype='d') + c = np.zeros((2, n)) + c[0,1:] = bn_func(k[1:]) + c[1,:] = an_func(k) + x = linalg.eigvals_banded(c, overwrite_a_band=True) + + # improve roots by one application of Newton's method + y = f(n, x) + dy = df(n, x) + x -= y/dy + + # fm and dy may contain very large/small values, so we + # log-normalize them to maintain precision in the product fm*dy + fm = f(n-1, x) + log_fm = np.log(np.abs(fm)) + log_dy = np.log(np.abs(dy)) + fm /= np.exp((log_fm.max() + log_fm.min()) / 2.) + dy /= np.exp((log_dy.max() + log_dy.min()) / 2.) + w = 1.0 / (fm * dy) + + if symmetrize: + w = (w + w[::-1]) / 2 + x = (x - x[::-1]) / 2 + + w *= mu0 / w.sum() + + if mu: + return x, w, mu0 + else: + return x, w + +# Jacobi Polynomials 1 P^(alpha,beta)_n(x) + + +def roots_jacobi(n, alpha, beta, mu=False): + r"""Gauss-Jacobi quadrature. + + Compute the sample points and weights for Gauss-Jacobi + quadrature. The sample points are the roots of the nth degree + Jacobi polynomial, :math:`P^{\alpha, \beta}_n(x)`. These sample + points and weights correctly integrate polynomials of degree + :math:`2n - 1` or less over the interval :math:`[-1, 1]` with + weight function :math:`w(x) = (1 - x)^{\alpha} (1 + + x)^{\beta}`. See 22.2.1 in [AS]_ for details. + + Parameters + ---------- + n : int + Quadrature order. + alpha : float + alpha must be > -1. + beta : float + beta must be > -1. + mu : bool, optional + If True, return the sum of the weights in addition to sample points and weights. + + Returns + ------- + x : ndarray + Sample points. + w : ndarray + Weights. + mu : float, optional + Sum of the weights, only returned if `mu=True`. + + See Also + -------- + scipy.integrate.fixed_quad + + References + ---------- + .. [AS] Milton Abramowitz and Irene A. Stegun, eds. + Handbook of Mathematical Functions with Formulas, + Graphs, and Mathematical Tables. New York: Dover, 1972. + + Examples + -------- + >>> from scipy.special import roots_jacobi + >>> x, w = roots_jacobi(3, 0.5, 0.5) + >>> x + array([-0.70710678, 0. , 0.70710678]) + >>> w + array([0.39269908, 0.78539816, 0.39269908]) + + >>> x, w, mu = roots_jacobi(3, 0.5, 0.5, mu=True) + >>> mu + 1.5707963267948966 # Sum of weights, equals pi/2 for alpha = beta = 0.5 + """ + + m = int(n) + if n < 1 or n != m: + raise ValueError("n must be a positive integer.") + if alpha <= -1 or beta <= -1: + raise ValueError("alpha and beta must be greater than -1.") + + if alpha == 0.0 and beta == 0.0: + return roots_legendre(m, mu) + if alpha == beta: + return roots_gegenbauer(m, alpha+0.5, mu) + + if (alpha + beta) <= 1000: + mu0 = 2.0**(alpha+beta+1) * _ufuncs.beta(alpha+1, beta+1) + else: + # Avoid overflows in pow and beta for very large parameters + mu0 = np.exp((alpha + beta + 1) * np.log(2.0) + + _ufuncs.betaln(alpha+1, beta+1)) + a = alpha + b = beta + if a + b == 0.0: + def an_func(k): + return np.where(k == 0, (b - a) / (2 + a + b), 0.0) + else: + def an_func(k): + return np.where( + k == 0, + (b - a) / (2 + a + b), + (b * b - a * a) / ((2.0 * k + a + b) * (2.0 * k + a + b + 2)) + ) + + def bn_func(k): + return ( + 2.0 / (2.0 * k + a + b) + * np.sqrt((k + a) * (k + b) / (2 * k + a + b + 1)) + * np.where(k == 1, 1.0, np.sqrt(k * (k + a + b) / (2.0 * k + a + b - 1))) + ) + + def f(n, x): + return _ufuncs.eval_jacobi(n, a, b, x) + def df(n, x): + return 0.5 * (n + a + b + 1) * _ufuncs.eval_jacobi(n - 1, a + 1, b + 1, x) + return _gen_roots_and_weights(m, mu0, an_func, bn_func, f, df, False, mu) + + +def jacobi(n, alpha, beta, monic=False): + r"""Jacobi polynomial. + + Defined to be the solution of + + .. math:: + + \begin{aligned} + (1 - x^2)\frac{d^2}{dx^2} P_n^{(\alpha, \beta)}(x) + &+ \left(\beta - \alpha - (\alpha + \beta + 2)x\right) + \frac{d}{dx} P_n^{(\alpha, \beta)}(x) \\ + &+ n(n + \alpha + \beta + 1) P_n^{(\alpha, \beta)}(x) = 0 + \end{aligned} + + for :math:`\alpha, \beta > -1`; :math:`P_n^{(\alpha, \beta)}` is a + polynomial of degree :math:`n`. + + Parameters + ---------- + n : int + Degree of the polynomial. + alpha : float + Parameter, must be greater than -1. + beta : float + Parameter, must be greater than -1. + monic : bool, optional + If `True`, scale the leading coefficient to be 1. Default is + `False`. + + Returns + ------- + P : orthopoly1d + Jacobi polynomial. + + Notes + ----- + For fixed :math:`\alpha, \beta`, the polynomials + :math:`P_n^{(\alpha, \beta)}` are orthogonal over :math:`[-1, 1]` + with weight function :math:`(1 - x)^\alpha(1 + x)^\beta`. + + References + ---------- + .. [AS] Milton Abramowitz and Irene A. Stegun, eds. + Handbook of Mathematical Functions with Formulas, + Graphs, and Mathematical Tables. New York: Dover, 1972. + + Examples + -------- + The Jacobi polynomials satisfy the recurrence relation: + + .. math:: + P_n^{(\alpha, \beta-1)}(x) - P_n^{(\alpha-1, \beta)}(x) + = P_{n-1}^{(\alpha, \beta)}(x) + + This can be verified, for example, for :math:`\alpha = \beta = 2` + and :math:`n = 1` over the interval :math:`[-1, 1]`: + + >>> import numpy as np + >>> from scipy.special import jacobi + >>> x = np.arange(-1.0, 1.0, 0.01) + >>> np.allclose(jacobi(0, 2, 2)(x), + ... jacobi(1, 2, 1)(x) - jacobi(1, 1, 2)(x)) + True + + Plot of the Jacobi polynomial :math:`P_5^{(\alpha, -0.5)}` for + different values of :math:`\alpha`: + + >>> import matplotlib.pyplot as plt + >>> x = np.arange(-1.0, 1.0, 0.01) + >>> fig, ax = plt.subplots() + >>> ax.set_ylim(-2.0, 2.0) + >>> ax.set_title(r'Jacobi polynomials $P_5^{(\alpha, -0.5)}$') + >>> for alpha in np.arange(0, 4, 1): + ... ax.plot(x, jacobi(5, alpha, -0.5)(x), label=rf'$\alpha={alpha}$') + >>> plt.legend(loc='best') + >>> plt.show() + + """ + if n < 0: + raise ValueError("n must be nonnegative.") + + def wfunc(x): + return (1 - x) ** alpha * (1 + x) ** beta + if n == 0: + return orthopoly1d([], [], 1.0, 1.0, wfunc, (-1, 1), monic, + eval_func=np.ones_like) + x, w, mu = roots_jacobi(n, alpha, beta, mu=True) + ab1 = alpha + beta + 1.0 + hn = 2**ab1 / (2 * n + ab1) * _gam(n + alpha + 1) + hn *= _gam(n + beta + 1.0) / _gam(n + 1) / _gam(n + ab1) + kn = _gam(2 * n + ab1) / 2.0**n / _gam(n + 1) / _gam(n + ab1) + # here kn = coefficient on x^n term + p = orthopoly1d(x, w, hn, kn, wfunc, (-1, 1), monic, + lambda x: _ufuncs.eval_jacobi(n, alpha, beta, x)) + return p + +# Jacobi Polynomials shifted G_n(p,q,x) + + +def roots_sh_jacobi(n, p1, q1, mu=False): + """Gauss-Jacobi (shifted) quadrature. + + Compute the sample points and weights for Gauss-Jacobi (shifted) + quadrature. The sample points are the roots of the nth degree + shifted Jacobi polynomial, :math:`G^{p,q}_n(x)`. These sample + points and weights correctly integrate polynomials of degree + :math:`2n - 1` or less over the interval :math:`[0, 1]` with + weight function :math:`w(x) = (1 - x)^{p-q} x^{q-1}`. See 22.2.2 + in [AS]_ for details. + + Parameters + ---------- + n : int + quadrature order. + p1 : float + (p1 - q1) must be > -1. + q1 : float + q1 must be > 0. + mu : bool, optional + If True, return the sum of the weights, optional. + + Returns + ------- + x : ndarray + Sample points + w : ndarray + Weights + mu : float + Sum of the weights + + See Also + -------- + scipy.integrate.fixed_quad + + References + ---------- + .. [AS] Milton Abramowitz and Irene A. Stegun, eds. + Handbook of Mathematical Functions with Formulas, + Graphs, and Mathematical Tables. New York: Dover, 1972. + + """ + if (p1-q1) <= -1 or q1 <= 0: + message = "(p - q) must be greater than -1, and q must be greater than 0." + raise ValueError(message) + x, w, m = roots_jacobi(n, p1-q1, q1-1, True) + x = (x + 1) / 2 + scale = 2.0**p1 + w /= scale + m /= scale + if mu: + return x, w, m + else: + return x, w + + +def sh_jacobi(n, p, q, monic=False): + r"""Shifted Jacobi polynomial. + + Defined by + + .. math:: + + G_n^{(p, q)}(x) + = \binom{2n + p - 1}{n}^{-1}P_n^{(p - q, q - 1)}(2x - 1), + + where :math:`P_n^{(\cdot, \cdot)}` is the nth Jacobi polynomial. + + Parameters + ---------- + n : int + Degree of the polynomial. + p : float + Parameter, must have :math:`p > q - 1`. + q : float + Parameter, must be greater than 0. + monic : bool, optional + If `True`, scale the leading coefficient to be 1. Default is + `False`. + + Returns + ------- + G : orthopoly1d + Shifted Jacobi polynomial. + + Notes + ----- + For fixed :math:`p, q`, the polynomials :math:`G_n^{(p, q)}` are + orthogonal over :math:`[0, 1]` with weight function :math:`(1 - + x)^{p - q}x^{q - 1}`. + + Examples + -------- + Evaluate the shifted Jacobi polynomial :math:`G_3^{(2, 1)}` at + :math:`x = 0.5`: + + >>> import numpy as np + >>> from scipy.special import binom, jacobi, sh_jacobi + >>> np.isclose(sh_jacobi(3, 2, 1)(0.5), -3/280) + True + + The polynomial is related to the Jacobi polynomial + :math:`P_n^{(p - q, q - 1)}`: + + >>> x = np.linspace(0, 1, 5) + >>> n, p, q = 3, 2, 1 + >>> scale = 1 / binom(2*n + p - 1, n) + >>> np.allclose(sh_jacobi(n, p, q)(x), + ... scale * jacobi(n, p - q, q - 1)(2*x - 1)) + True + + Plot :math:`G_3^{(p, 1)}` for several values of :math:`p`: + + >>> import matplotlib.pyplot as plt + >>> x = np.linspace(0, 1, 400) + >>> fig, ax = plt.subplots() + >>> for p in [1, 2, 3]: + ... ax.plot(x, sh_jacobi(3, p, 1)(x), label=rf"$p={p}$") + >>> ax.set_title(r"Shifted Jacobi polynomials $G_3^{(p, 1)}$") + >>> ax.set_xlabel("x") + >>> ax.legend(loc="best") + >>> plt.show() + + """ + if n < 0: + raise ValueError("n must be nonnegative.") + + def wfunc(x): + return (1.0 - x) ** (p - q) * x ** (q - 1.0) + if n == 0: + return orthopoly1d([], [], 1.0, 1.0, wfunc, (-1, 1), monic, + eval_func=np.ones_like) + n1 = n + x, w = roots_sh_jacobi(n1, p, q) + hn = _gam(n + 1) * _gam(n + q) * _gam(n + p) * _gam(n + p - q + 1) + hn /= (2 * n + p) * (_gam(2 * n + p)**2) + # kn = 1.0 in standard form so monic is redundant. Kept for compatibility. + kn = 1.0 + pp = orthopoly1d(x, w, hn, kn, wfunc=wfunc, limits=(0, 1), monic=monic, + eval_func=lambda x: _ufuncs.eval_sh_jacobi(n, p, q, x)) + return pp + +# Generalized Laguerre L^(alpha)_n(x) + + +def roots_genlaguerre(n, alpha, mu=False): + r"""Gauss-generalized Laguerre quadrature. + + Compute the sample points and weights for Gauss-generalized + Laguerre quadrature. The sample points are the roots of the nth + degree generalized Laguerre polynomial, :math:`L^{\alpha}_n(x)`. + These sample points and weights correctly integrate polynomials of + degree :math:`2n - 1` or less over the interval :math:`[0, + \infty]` with weight function :math:`w(x) = x^{\alpha} + e^{-x}`. See 22.3.9 in [AS]_ for details. + + Parameters + ---------- + n : int + quadrature order + alpha : float + alpha must be > -1 + mu : bool, optional + If True, return the sum of the weights, optional. + + Returns + ------- + x : ndarray + Sample points + w : ndarray + Weights + mu : float + Sum of the weights + + See Also + -------- + scipy.integrate.fixed_quad + + References + ---------- + .. [AS] Milton Abramowitz and Irene A. Stegun, eds. + Handbook of Mathematical Functions with Formulas, + Graphs, and Mathematical Tables. New York: Dover, 1972. + + """ + m = int(n) + if n < 1 or n != m: + raise ValueError("n must be a positive integer.") + if alpha < -1: + raise ValueError("alpha must be greater than -1.") + + mu0 = _ufuncs.gamma(alpha + 1) + + if m == 1: + x = np.array([alpha+1.0], 'd') + w = np.array([mu0], 'd') + if mu: + return x, w, mu0 + else: + return x, w + + def an_func(k): + return 2 * k + alpha + 1 + def bn_func(k): + return -np.sqrt(k * (k + alpha)) + def f(n, x): + return _ufuncs.eval_genlaguerre(n, alpha, x) + def df(n, x): + return (n * _ufuncs.eval_genlaguerre(n, alpha, x) + - (n + alpha) * _ufuncs.eval_genlaguerre(n - 1, alpha, x)) / x + return _gen_roots_and_weights(m, mu0, an_func, bn_func, f, df, False, mu) + + +def genlaguerre(n, alpha, monic=False): + r"""Generalized (associated) Laguerre polynomial. + + Defined to be the solution of + + .. math:: + x\frac{d^2}{dx^2}L_n^{(\alpha)}(x) + + (\alpha + 1 - x)\frac{d}{dx}L_n^{(\alpha)}(x) + + nL_n^{(\alpha)}(x) = 0, + + where :math:`\alpha > -1`; :math:`L_n^{(\alpha)}` is a polynomial + of degree :math:`n`. + + Parameters + ---------- + n : int + Degree of the polynomial. + alpha : float + Parameter, must be greater than -1. + monic : bool, optional + If `True`, scale the leading coefficient to be 1. Default is + `False`. + + Returns + ------- + L : orthopoly1d + Generalized Laguerre polynomial. + + See Also + -------- + laguerre : Laguerre polynomial. + hyp1f1 : confluent hypergeometric function + + Notes + ----- + For fixed :math:`\alpha`, the polynomials :math:`L_n^{(\alpha)}` + are orthogonal over :math:`[0, \infty)` with weight function + :math:`e^{-x}x^\alpha`. + + The Laguerre polynomials are the special case where :math:`\alpha + = 0`. + + References + ---------- + .. [AS] Milton Abramowitz and Irene A. Stegun, eds. + Handbook of Mathematical Functions with Formulas, + Graphs, and Mathematical Tables. New York: Dover, 1972. + + Examples + -------- + The generalized Laguerre polynomials are closely related to the confluent + hypergeometric function :math:`{}_1F_1`: + + .. math:: + L_n^{(\alpha)}(x) = \binom{n + \alpha}{n} {}_1F_1(-n, \alpha +1, x) + + This can be verified, for example, for :math:`n = \alpha = 3` over the + interval :math:`[-1, 1]`: + + >>> import numpy as np + >>> from scipy.special import binom, genlaguerre, hyp1f1 + >>> x = np.arange(-1.0, 1.0, 0.01) + >>> np.allclose(genlaguerre(3, 3)(x), binom(6, 3) * hyp1f1(-3, 4, x)) + True + + This is the plot of the generalized Laguerre polynomials + :math:`L_3^{(\alpha)}` for some values of :math:`\alpha`: + + >>> import matplotlib.pyplot as plt + >>> x = np.arange(-4.0, 12.0, 0.01) + >>> fig, ax = plt.subplots() + >>> ax.set_ylim(-5.0, 10.0) + >>> ax.set_title(r'Generalized Laguerre polynomials $L_3^{(\alpha)}$') + >>> for alpha in np.arange(0, 5): + ... ax.plot(x, genlaguerre(3, alpha)(x), label=rf"$L_3^{{({alpha})}}$") + >>> plt.legend(loc='best') + >>> plt.show() + + """ + if alpha <= -1: + raise ValueError("alpha must be > -1") + if n < 0: + raise ValueError("n must be nonnegative.") + + if n == 0: + n1 = n + 1 + else: + n1 = n + x, w = roots_genlaguerre(n1, alpha) + def wfunc(x): + return exp(-x) * x ** alpha + if n == 0: + x, w = [], [] + hn = _gam(n + alpha + 1) / _gam(n + 1) + kn = (-1)**n / _gam(n + 1) + p = orthopoly1d(x, w, hn, kn, wfunc, (0, inf), monic, + lambda x: _ufuncs.eval_genlaguerre(n, alpha, x)) + return p + +# Laguerre L_n(x) + + +def roots_laguerre(n, mu=False): + r"""Gauss-Laguerre quadrature. + + Compute the sample points and weights for Gauss-Laguerre + quadrature. The sample points are the roots of the nth degree + Laguerre polynomial, :math:`L_n(x)`. These sample points and + weights correctly integrate polynomials of degree :math:`2n - 1` + or less over the interval :math:`[0, \infty]` with weight function + :math:`w(x) = e^{-x}`. See 22.2.13 in [AS]_ for details. + + Parameters + ---------- + n : int + quadrature order + mu : bool, optional + If True, return the sum of the weights, optional. + + Returns + ------- + x : ndarray + Sample points + w : ndarray + Weights + mu : float + Sum of the weights + + See Also + -------- + scipy.integrate.fixed_quad + numpy.polynomial.laguerre.laggauss + + References + ---------- + .. [AS] Milton Abramowitz and Irene A. Stegun, eds. + Handbook of Mathematical Functions with Formulas, + Graphs, and Mathematical Tables. New York: Dover, 1972. + + """ + return roots_genlaguerre(n, 0.0, mu=mu) + + +def laguerre(n, monic=False): + r"""Laguerre polynomial. + + Defined to be the solution of + + .. math:: + x\frac{d^2}{dx^2}L_n(x) + (1 - x)\frac{d}{dx}L_n(x) + + nL_n(x) = 0; + + :math:`L_n` is a polynomial of degree :math:`n`. + + Parameters + ---------- + n : int + Degree of the polynomial. + monic : bool, optional + If `True`, scale the leading coefficient to be 1. Default is + `False`. + + Returns + ------- + L : orthopoly1d + Laguerre Polynomial. + + See Also + -------- + genlaguerre : Generalized (associated) Laguerre polynomial. + + Notes + ----- + The polynomials :math:`L_n` are orthogonal over :math:`[0, + \infty)` with weight function :math:`e^{-x}`. + + References + ---------- + .. [AS] Milton Abramowitz and Irene A. Stegun, eds. + Handbook of Mathematical Functions with Formulas, + Graphs, and Mathematical Tables. New York: Dover, 1972. + + Examples + -------- + The Laguerre polynomials :math:`L_n` are the special case + :math:`\alpha = 0` of the generalized Laguerre polynomials + :math:`L_n^{(\alpha)}`. + Let's verify it on the interval :math:`[-1, 1]`: + + >>> import numpy as np + >>> from scipy.special import genlaguerre + >>> from scipy.special import laguerre + >>> x = np.arange(-1.0, 1.0, 0.01) + >>> np.allclose(genlaguerre(3, 0)(x), laguerre(3)(x)) + True + + The polynomials :math:`L_n` also satisfy the recurrence relation: + + .. math:: + (n + 1)L_{n+1}(x) = (2n +1 -x)L_n(x) - nL_{n-1}(x) + + This can be easily checked on :math:`[0, 1]` for :math:`n = 3`: + + >>> x = np.arange(0.0, 1.0, 0.01) + >>> np.allclose(4 * laguerre(4)(x), + ... (7 - x) * laguerre(3)(x) - 3 * laguerre(2)(x)) + True + + This is the plot of the first few Laguerre polynomials :math:`L_n`: + + >>> import matplotlib.pyplot as plt + >>> x = np.arange(-1.0, 5.0, 0.01) + >>> fig, ax = plt.subplots() + >>> ax.set_ylim(-5.0, 5.0) + >>> ax.set_title(r'Laguerre polynomials $L_n$') + >>> for n in np.arange(0, 5): + ... ax.plot(x, laguerre(n)(x), label=rf'$L_{n}$') + >>> plt.legend(loc='best') + >>> plt.show() + + """ + if n < 0: + raise ValueError("n must be nonnegative.") + + if n == 0: + n1 = n + 1 + else: + n1 = n + x, w = roots_laguerre(n1) + if n == 0: + x, w = [], [] + hn = 1.0 + kn = (-1)**n / _gam(n + 1) + p = orthopoly1d(x, w, hn, kn, lambda x: exp(-x), (0, inf), monic, + lambda x: _ufuncs.eval_laguerre(n, x)) + return p + +# Hermite 1 H_n(x) + + +def roots_hermite(n, mu=False): + r"""Gauss-Hermite (physicist's) quadrature. + + Compute the sample points and weights for Gauss-Hermite + quadrature. The sample points are the roots of the nth degree + Hermite polynomial, :math:`H_n(x)`. These sample points and + weights correctly integrate polynomials of degree :math:`2n - 1` + or less over the interval :math:`[-\infty, \infty]` with weight + function :math:`w(x) = e^{-x^2}`. See 22.2.14 in [AS]_ for + details. + + Parameters + ---------- + n : int + quadrature order + mu : bool, optional + If True, return the sum of the weights, optional. + + Returns + ------- + x : ndarray + Sample points + w : ndarray + Weights + mu : float + Sum of the weights + + See Also + -------- + scipy.integrate.fixed_quad + numpy.polynomial.hermite.hermgauss + roots_hermitenorm + + Notes + ----- + For small n up to 150 a modified version of the Golub-Welsch + algorithm is used. Nodes are computed from the eigenvalue + problem and improved by one step of a Newton iteration. + The weights are computed from the well-known analytical formula. + + For n larger than 150 an optimal asymptotic algorithm is applied + which computes nodes and weights in a numerically stable manner. + The algorithm has linear runtime making computation for very + large n (several thousand or more) feasible. + + References + ---------- + .. [townsend.trogdon.olver-2014] + Townsend, A. and Trogdon, T. and Olver, S. (2014) + *Fast computation of Gauss quadrature nodes and + weights on the whole real line*. :arXiv:`1410.5286`. + .. [townsend.trogdon.olver-2015] + Townsend, A. and Trogdon, T. and Olver, S. (2015) + *Fast computation of Gauss quadrature nodes and + weights on the whole real line*. + IMA Journal of Numerical Analysis + :doi:`10.1093/imanum/drv002`. + .. [AS] Milton Abramowitz and Irene A. Stegun, eds. + Handbook of Mathematical Functions with Formulas, + Graphs, and Mathematical Tables. New York: Dover, 1972. + + """ + m = int(n) + if n < 1 or n != m: + raise ValueError("n must be a positive integer.") + + mu0 = np.sqrt(np.pi) + if n <= 150: + def an_func(k): + return 0.0 * k + def bn_func(k): + return np.sqrt(k / 2.0) + f = _ufuncs.eval_hermite + def df(n, x): + return 2.0 * n * _ufuncs.eval_hermite(n - 1, x) + return _gen_roots_and_weights(m, mu0, an_func, bn_func, f, df, True, mu) + else: + nodes, weights = _roots_hermite_asy(m) + if mu: + return nodes, weights, mu0 + else: + return nodes, weights + + +def _compute_tauk(n, k, maxit=5): + """Helper function for Tricomi initial guesses + + For details, see formula 3.1 in lemma 3.1 in the + original paper. + + Parameters + ---------- + n : int + Quadrature order + k : ndarray of type int + Index of roots :math:`\tau_k` to compute + maxit : int + Number of Newton maxit performed, the default + value of 5 is sufficient. + + Returns + ------- + tauk : ndarray + Roots of equation 3.1 + + See Also + -------- + initial_nodes_a + roots_hermite_asy + """ + a = n % 2 - 0.5 + c = (4.0*floor(n/2.0) - 4.0*k + 3.0)*pi / (4.0*floor(n/2.0) + 2.0*a + 2.0) + def f(x): + return x - sin(x) - c + def df(x): + return 1.0 - cos(x) + xi = 0.5*pi + for i in range(maxit): + xi = xi - f(xi)/df(xi) + return xi + + +def _initial_nodes_a(n, k): + r"""Tricomi initial guesses + + Computes an initial approximation to the square of the `k`-th + (positive) root :math:`x_k` of the Hermite polynomial :math:`H_n` + of order :math:`n`. The formula is the one from lemma 3.1 in the + original paper. The guesses are accurate except in the region + near :math:`\sqrt{2n + 1}`. + + Parameters + ---------- + n : int + Quadrature order + k : ndarray of type int + Index of roots to compute + + Returns + ------- + xksq : ndarray + Square of the approximate roots + + See Also + -------- + initial_nodes + roots_hermite_asy + """ + tauk = _compute_tauk(n, k) + sigk = cos(0.5*tauk)**2 + a = n % 2 - 0.5 + nu = 4.0*floor(n/2.0) + 2.0*a + 2.0 + # Initial approximation of Hermite roots (square) + xksq = nu*sigk - 1.0/(3.0*nu) * (5.0/(4.0*(1.0-sigk)**2) - 1.0/(1.0-sigk) - 0.25) + return xksq + + +def _initial_nodes_b(n, k): + r"""Gatteschi initial guesses + + Computes an initial approximation to the square of the kth + (positive) root :math:`x_k` of the Hermite polynomial :math:`H_n` + of order :math:`n`. The formula is the one from lemma 3.2 in the + original paper. The guesses are accurate in the region just + below :math:`\sqrt{2n + 1}`. + + Parameters + ---------- + n : int + Quadrature order + k : ndarray of type int + Index of roots to compute + + Returns + ------- + xksq : ndarray + Square of the approximate root + + See Also + -------- + initial_nodes + roots_hermite_asy + """ + a = n % 2 - 0.5 + nu = 4.0*floor(n/2.0) + 2.0*a + 2.0 + # Airy roots by approximation + ak = _specfun.airyzo(k.max(), 1)[0][::-1] + # Initial approximation of Hermite roots (square) + xksq = (nu + + 2.0**(2.0/3.0) * ak * nu**(1.0/3.0) + + 1.0/5.0 * 2.0**(4.0/3.0) * ak**2 * nu**(-1.0/3.0) + + (9.0/140.0 - 12.0/175.0 * ak**3) * nu**(-1.0) + + (16.0/1575.0 * ak + 92.0/7875.0 * ak**4) * 2.0**(2.0/3.0) * nu**(-5.0/3.0) + - (15152.0/3031875.0 * ak**5 + 1088.0/121275.0 * ak**2) + * 2.0**(1.0/3.0) * nu**(-7.0/3.0)) + return xksq + + +def _initial_nodes(n): + """Initial guesses for the Hermite roots + + Computes an initial approximation to the non-negative + roots :math:`x_k` of the Hermite polynomial :math:`H_n` + of order :math:`n`. The Tricomi and Gatteschi initial + guesses are used in the region where they are accurate. + + Parameters + ---------- + n : int + Quadrature order + + Returns + ------- + xk : ndarray + Approximate roots + + See Also + -------- + roots_hermite_asy + """ + # Turnover point + # linear polynomial fit to error of 10, 25, 40, ..., 1000 point rules + fit = 0.49082003*n - 4.37859653 + turnover = around(fit).astype(int) + # Compute all approximations + ia = arange(1, int(floor(n*0.5)+1)) + ib = ia[::-1] + xasq = _initial_nodes_a(n, ia[:turnover+1]) + xbsq = _initial_nodes_b(n, ib[turnover+1:]) + # Combine + iv = sqrt(hstack([xasq, xbsq])) + # Central node is always zero + if n % 2 == 1: + iv = hstack([0.0, iv]) + return iv + + +def _pbcf(n, theta): + r"""Asymptotic series expansion of parabolic cylinder function + + The implementation is based on sections 3.2 and 3.3 from the + original paper. Compared to the published version this code + adds one more term to the asymptotic series. The detailed + formulas can be found at [parabolic-asymptotics]_. The evaluation + is done in a transformed variable :math:`\theta := \arccos(t)` + where :math:`t := x / \mu` and :math:`\mu := \sqrt{2n + 1}`. + + Parameters + ---------- + n : int + Quadrature order + theta : ndarray + Transformed position variable + + Returns + ------- + U : ndarray + Value of the parabolic cylinder function :math:`U(a, \theta)`. + Ud : ndarray + Value of the derivative :math:`U^{\prime}(a, \theta)` of + the parabolic cylinder function. + + See Also + -------- + roots_hermite_asy + + References + ---------- + .. [parabolic-asymptotics] + https://dlmf.nist.gov/12.10#vii + """ + st = sin(theta) + ct = cos(theta) + # https://dlmf.nist.gov/12.10#vii + mu = 2.0*n + 1.0 + # https://dlmf.nist.gov/12.10#E23 + eta = 0.5*theta - 0.5*st*ct + # https://dlmf.nist.gov/12.10#E39 + zeta = -(3.0*eta/2.0) ** (2.0/3.0) + # https://dlmf.nist.gov/12.10#E40 + phi = (-zeta / st**2) ** (0.25) + # Coefficients + # https://dlmf.nist.gov/12.10#E43 + a0 = 1.0 + a1 = 0.10416666666666666667 + a2 = 0.08355034722222222222 + a3 = 0.12822657455632716049 + a4 = 0.29184902646414046425 + a5 = 0.88162726744375765242 + b0 = 1.0 + b1 = -0.14583333333333333333 + b2 = -0.09874131944444444444 + b3 = -0.14331205391589506173 + b4 = -0.31722720267841354810 + b5 = -0.94242914795712024914 + # Polynomials + # https://dlmf.nist.gov/12.10#E9 + # https://dlmf.nist.gov/12.10#E10 + ctp = ct ** arange(16).reshape((-1,1)) + u0 = 1.0 + u1 = (1.0*ctp[3,:] - 6.0*ct) / 24.0 + u2 = (-9.0*ctp[4,:] + 249.0*ctp[2,:] + 145.0) / 1152.0 + u3 = (-4042.0*ctp[9,:] + 18189.0*ctp[7,:] - 28287.0*ctp[5,:] + - 151995.0*ctp[3,:] - 259290.0*ct) / 414720.0 + u4 = (72756.0*ctp[10,:] - 321339.0*ctp[8,:] - 154982.0*ctp[6,:] + + 50938215.0*ctp[4,:] + 122602962.0*ctp[2,:] + 12773113.0) / 39813120.0 + u5 = (82393456.0*ctp[15,:] - 617950920.0*ctp[13,:] + 1994971575.0*ctp[11,:] + - 3630137104.0*ctp[9,:] + 4433574213.0*ctp[7,:] - 37370295816.0*ctp[5,:] + - 119582875013.0*ctp[3,:] - 34009066266.0*ct) / 6688604160.0 + v0 = 1.0 + v1 = (1.0*ctp[3,:] + 6.0*ct) / 24.0 + v2 = (15.0*ctp[4,:] - 327.0*ctp[2,:] - 143.0) / 1152.0 + v3 = (-4042.0*ctp[9,:] + 18189.0*ctp[7,:] - 36387.0*ctp[5,:] + + 238425.0*ctp[3,:] + 259290.0*ct) / 414720.0 + v4 = (-121260.0*ctp[10,:] + 551733.0*ctp[8,:] - 151958.0*ctp[6,:] + - 57484425.0*ctp[4,:] - 132752238.0*ctp[2,:] - 12118727) / 39813120.0 + v5 = (82393456.0*ctp[15,:] - 617950920.0*ctp[13,:] + 2025529095.0*ctp[11,:] + - 3750839308.0*ctp[9,:] + 3832454253.0*ctp[7,:] + 35213253348.0*ctp[5,:] + + 130919230435.0*ctp[3,:] + 34009066266*ct) / 6688604160.0 + # Airy Evaluation (Bi and Bip unused) + Ai, Aip, Bi, Bip = airy(mu**(4.0/6.0) * zeta) + # Prefactor for U + P = 2.0*sqrt(pi) * mu**(1.0/6.0) * phi + # Terms for U + # https://dlmf.nist.gov/12.10#E42 + phip = phi ** arange(6, 31, 6).reshape((-1,1)) + A0 = b0*u0 + A1 = (b2*u0 + phip[0,:]*b1*u1 + phip[1,:]*b0*u2) / zeta**3 + A2 = (b4*u0 + phip[0,:]*b3*u1 + phip[1,:]*b2*u2 + phip[2,:]*b1*u3 + + phip[3,:]*b0*u4) / zeta**6 + B0 = -(a1*u0 + phip[0,:]*a0*u1) / zeta**2 + B1 = -(a3*u0 + phip[0,:]*a2*u1 + phip[1,:]*a1*u2 + phip[2,:]*a0*u3) / zeta**5 + B2 = -(a5*u0 + phip[0,:]*a4*u1 + phip[1,:]*a3*u2 + phip[2,:]*a2*u3 + + phip[3,:]*a1*u4 + phip[4,:]*a0*u5) / zeta**8 + # U + # https://dlmf.nist.gov/12.10#E35 + U = P * (Ai * (A0 + A1/mu**2.0 + A2/mu**4.0) + + Aip * (B0 + B1/mu**2.0 + B2/mu**4.0) / mu**(8.0/6.0)) + # Prefactor for derivative of U + Pd = sqrt(2.0*pi) * mu**(2.0/6.0) / phi + # Terms for derivative of U + # https://dlmf.nist.gov/12.10#E46 + C0 = -(b1*v0 + phip[0,:]*b0*v1) / zeta + C1 = -(b3*v0 + phip[0,:]*b2*v1 + phip[1,:]*b1*v2 + phip[2,:]*b0*v3) / zeta**4 + C2 = -(b5*v0 + phip[0,:]*b4*v1 + phip[1,:]*b3*v2 + phip[2,:]*b2*v3 + + phip[3,:]*b1*v4 + phip[4,:]*b0*v5) / zeta**7 + D0 = a0*v0 + D1 = (a2*v0 + phip[0,:]*a1*v1 + phip[1,:]*a0*v2) / zeta**3 + D2 = (a4*v0 + phip[0,:]*a3*v1 + phip[1,:]*a2*v2 + phip[2,:]*a1*v3 + + phip[3,:]*a0*v4) / zeta**6 + # Derivative of U + # https://dlmf.nist.gov/12.10#E36 + Ud = Pd * (Ai * (C0 + C1/mu**2.0 + C2/mu**4.0) / mu**(4.0/6.0) + + Aip * (D0 + D1/mu**2.0 + D2/mu**4.0)) + return U, Ud + + +def _newton(n, x_initial, maxit=5): + """Newton iteration for polishing the asymptotic approximation + to the zeros of the Hermite polynomials. + + Parameters + ---------- + n : int + Quadrature order + x_initial : ndarray + Initial guesses for the roots + maxit : int + Maximal number of Newton iterations. + The default 5 is sufficient, usually + only one or two steps are needed. + + Returns + ------- + nodes : ndarray + Quadrature nodes + weights : ndarray + Quadrature weights + + See Also + -------- + roots_hermite_asy + """ + # Variable transformation + mu = sqrt(2.0*n + 1.0) + t = x_initial / mu + theta = arccos(t) + # Newton iteration + for i in range(maxit): + u, ud = _pbcf(n, theta) + dtheta = u / (sqrt(2.0) * mu * sin(theta) * ud) + theta = theta + dtheta + if max(abs(dtheta)) < 1e-14: + break + # Undo variable transformation + x = mu * cos(theta) + # Central node is always zero + if n % 2 == 1: + x[0] = 0.0 + # Compute weights + w = exp(-x**2) / (2.0*ud**2) + return x, w + + +def _roots_hermite_asy(n): + r"""Gauss-Hermite (physicist's) quadrature for large n. + + Computes the sample points and weights for Gauss-Hermite quadrature. + The sample points are the roots of the nth degree Hermite polynomial, + :math:`H_n(x)`. These sample points and weights correctly integrate + polynomials of degree :math:`2n - 1` or less over the interval + :math:`[-\infty, \infty]` with weight function :math:`f(x) = e^{-x^2}`. + + This method relies on asymptotic expansions which work best for n > 150. + The algorithm has linear runtime making computation for very large n + feasible. + + Parameters + ---------- + n : int + quadrature order + + Returns + ------- + nodes : ndarray + Quadrature nodes + weights : ndarray + Quadrature weights + + See Also + -------- + roots_hermite + + References + ---------- + .. [townsend.trogdon.olver-2014] + Townsend, A. and Trogdon, T. and Olver, S. (2014) + *Fast computation of Gauss quadrature nodes and + weights on the whole real line*. :arXiv:`1410.5286`. + + .. [townsend.trogdon.olver-2015] + Townsend, A. and Trogdon, T. and Olver, S. (2015) + *Fast computation of Gauss quadrature nodes and + weights on the whole real line*. + IMA Journal of Numerical Analysis + :doi:`10.1093/imanum/drv002`. + """ + iv = _initial_nodes(n) + nodes, weights = _newton(n, iv) + # Combine with negative parts + if n % 2 == 0: + nodes = hstack([-nodes[::-1], nodes]) + weights = hstack([weights[::-1], weights]) + else: + nodes = hstack([-nodes[-1:0:-1], nodes]) + weights = hstack([weights[-1:0:-1], weights]) + # Scale weights + weights *= sqrt(pi) / sum(weights) + return nodes, weights + + +def hermite(n, monic=False): + r"""Physicist's Hermite polynomial. + + Defined by + + .. math:: + + H_n(x) = (-1)^ne^{x^2}\frac{d^n}{dx^n}e^{-x^2}; + + :math:`H_n` is a polynomial of degree :math:`n`. + + Parameters + ---------- + n : int + Degree of the polynomial. + monic : bool, optional + If `True`, scale the leading coefficient to be 1. Default is + `False`. + + Returns + ------- + H : orthopoly1d + Physicist's Hermite polynomial. + + Notes + ----- + The polynomials :math:`H_n` are orthogonal over :math:`(-\infty, + \infty)` with weight function :math:`e^{-x^2}`. + + Examples + -------- + >>> from scipy import special + >>> import matplotlib.pyplot as plt + >>> import numpy as np + + >>> p_monic = special.hermite(3, monic=True) + >>> p_monic + poly1d([ 1. , 0. , -1.5, 0. ]) + >>> p_monic(1) + -0.49999999999999983 + >>> x = np.linspace(-3, 3, 400) + >>> y = p_monic(x) + >>> plt.plot(x, y) + >>> plt.title("Monic Hermite polynomial of degree 3") + >>> plt.xlabel("x") + >>> plt.ylabel("H_3(x)") + >>> plt.show() + + """ + if n < 0: + raise ValueError("n must be nonnegative.") + + if n == 0: + n1 = n + 1 + else: + n1 = n + x, w = roots_hermite(n1) + def wfunc(x): + return exp(-x * x) + if n == 0: + x, w = [], [] + hn = 2**n * _gam(n + 1) * sqrt(pi) + kn = 2**n + p = orthopoly1d(x, w, hn, kn, wfunc, (-inf, inf), monic, + lambda x: _ufuncs.eval_hermite(n, x)) + return p + +# Hermite 2 He_n(x) + + +def roots_hermitenorm(n, mu=False): + r"""Gauss-Hermite (statistician's) quadrature. + + Compute the sample points and weights for Gauss-Hermite + quadrature. The sample points are the roots of the nth degree + Hermite polynomial, :math:`He_n(x)`. These sample points and + weights correctly integrate polynomials of degree :math:`2n - 1` + or less over the interval :math:`[-\infty, \infty]` with weight + function :math:`w(x) = e^{-x^2/2}`. See 22.2.15 in [AS]_ for more + details. + + Parameters + ---------- + n : int + quadrature order. + mu : bool, optional + If True, return the sum of the weights, optional. + + Returns + ------- + x : ndarray + Sample points. + w : ndarray + Weights. + mu : float + Sum of the weights. + + See Also + -------- + scipy.integrate.fixed_quad + numpy.polynomial.hermite_e.hermegauss + + Notes + ----- + For small n up to 150 a modified version of the Golub-Welsch + algorithm is used. Nodes are computed from the eigenvalue + problem and improved by one step of a Newton iteration. + The weights are computed from the well-known analytical formula. + + For n larger than 150 an optimal asymptotic algorithm is used + which computes nodes and weights in a numerical stable manner. + The algorithm has linear runtime making computation for very + large n (several thousand or more) feasible. + + References + ---------- + .. [AS] Milton Abramowitz and Irene A. Stegun, eds. + Handbook of Mathematical Functions with Formulas, + Graphs, and Mathematical Tables. New York: Dover, 1972. + + """ + m = int(n) + if n < 1 or n != m: + raise ValueError("n must be a positive integer.") + + mu0 = np.sqrt(2.0*np.pi) + if n <= 150: + def an_func(k): + return 0.0 * k + def bn_func(k): + return np.sqrt(k) + f = _ufuncs.eval_hermitenorm + def df(n, x): + return n * _ufuncs.eval_hermitenorm(n - 1, x) + return _gen_roots_and_weights(m, mu0, an_func, bn_func, f, df, True, mu) + else: + nodes, weights = _roots_hermite_asy(m) + # Transform + nodes *= sqrt(2) + weights *= sqrt(2) + if mu: + return nodes, weights, mu0 + else: + return nodes, weights + + +def hermitenorm(n, monic=False): + r"""Probabilist's Hermite polynomial. + + Defined by + + .. math:: + + He_n(x) = (-1)^ne^{x^2/2}\frac{d^n}{dx^n}e^{-x^2/2}; + + :math:`He_n` is a polynomial of degree :math:`n`. + + Parameters + ---------- + n : int + Degree of the polynomial. + monic : bool, optional + If `True`, scale the leading coefficient to be 1. Default is + `False`. + + Returns + ------- + He : orthopoly1d + Probabilist's Hermite polynomial. + + Notes + ----- + + The polynomials :math:`He_n` are orthogonal over :math:`(-\infty, + \infty)` with weight function :math:`e^{-x^2/2}`. + + Examples + -------- + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from scipy.special import hermitenorm + + >>> p_monic = hermitenorm(3) + >>> p_monic + poly1d([ 1., 0., -3., 0.]) + + Evaluate the probabilist's Hermite polynomial of degree 3 at x = 1: + + >>> p_monic(1) + np.float64(-2.0) + + Plot probabilist's Hermite polynomials of degree 0 to 4: + + >>> x = np.linspace(-3, 3, 100) + >>> fig, ax = plt.subplots() + >>> for i in range(5): + ... ax.plot(x, hermitenorm(i)(x), label=f"n={i}") + >>> plt.title(f"Probabilist's Hermite polynomials $He_n$") + >>> plt.xlabel("x") + >>> plt.ylabel(rf"$He_n(x)$") + >>> plt.legend(loc="best") + >>> plt.show() + + """ + if n < 0: + raise ValueError("n must be nonnegative.") + + if n == 0: + n1 = n + 1 + else: + n1 = n + x, w = roots_hermitenorm(n1) + def wfunc(x): + return exp(-x * x / 2.0) + if n == 0: + x, w = [], [] + hn = sqrt(2 * pi) * _gam(n + 1) + kn = 1.0 + p = orthopoly1d(x, w, hn, kn, wfunc=wfunc, limits=(-inf, inf), monic=monic, + eval_func=lambda x: _ufuncs.eval_hermitenorm(n, x)) + return p + +# The remainder of the polynomials can be derived from the ones above. + +# Ultraspherical (Gegenbauer) C^(alpha)_n(x) + + +def roots_gegenbauer(n, alpha, mu=False): + r"""Gauss-Gegenbauer quadrature. + + Compute the sample points and weights for Gauss-Gegenbauer + quadrature. The sample points are the roots of the nth degree + Gegenbauer polynomial, :math:`C^{\alpha}_n(x)`. These sample + points and weights correctly integrate polynomials of degree + :math:`2n - 1` or less over the interval :math:`[-1, 1]` with + weight function :math:`w(x) = (1 - x^2)^{\alpha - 1/2}`. See + 22.2.3 in [AS]_ for more details. + + Parameters + ---------- + n : int + quadrature order. + alpha : float + alpha must be > -0.5. + mu : bool, optional + If True, return the sum of the weights, optional. + + Returns + ------- + x : ndarray + Sample points + w : ndarray + Weights + mu : float + Sum of the weights + + See Also + -------- + scipy.integrate.fixed_quad + + References + ---------- + .. [AS] Milton Abramowitz and Irene A. Stegun, eds. + Handbook of Mathematical Functions with Formulas, + Graphs, and Mathematical Tables. New York: Dover, 1972. + + """ + m = int(n) + if n < 1 or n != m: + raise ValueError("n must be a positive integer.") + if alpha < -0.5: + raise ValueError("alpha must be greater than -0.5.") + elif alpha == 0.0: + # C(n,0,x) == 0 uniformly, however, as alpha->0, C(n,alpha,x)->T(n,x) + # strictly, we should just error out here, since the roots are not + # really defined, but we used to return something useful, so let's + # keep doing so. + return roots_chebyt(n, mu) + + if alpha <= 170: + mu0 = (np.sqrt(np.pi) * _ufuncs.gamma(alpha + 0.5)) \ + / _ufuncs.gamma(alpha + 1) + else: + # For large alpha we use a Taylor series expansion around inf, + # expressed as a 6th order polynomial of a^-1 and using Horner's + # method to minimize computation and maximize precision + inv_alpha = 1. / alpha + coeffs = np.array([0.000207186, -0.00152206, -0.000640869, + 0.00488281, 0.0078125, -0.125, 1.]) + mu0 = coeffs[0] + for term in range(1, len(coeffs)): + mu0 = mu0 * inv_alpha + coeffs[term] + mu0 = mu0 * np.sqrt(np.pi / alpha) + def an_func(k): + return 0.0 * k + def bn_func(k): + return np.sqrt(k * (k + 2 * alpha - 1) / (4 * (k + alpha) * (k + alpha - 1))) + def f(n, x): + return _ufuncs.eval_gegenbauer(n, alpha, x) + def df(n, x): + return ( + -n * x * _ufuncs.eval_gegenbauer(n, alpha, x) + + (n + 2 * alpha - 1) * _ufuncs.eval_gegenbauer(n - 1, alpha, x) + ) / (1 - x ** 2) + return _gen_roots_and_weights(m, mu0, an_func, bn_func, f, df, True, mu) + + +def gegenbauer(n, alpha, monic=False): + r"""Gegenbauer (ultraspherical) polynomial. + + Defined to be the solution of the second-order linear ordinary differential equation + + .. math:: + (1 - x^2)\frac{d^2}{dx^2}C_n^{(\alpha)}(x) + - (2\alpha + 1)x\frac{d}{dx}C_n^{(\alpha)}(x) + + n(n + 2\alpha)C_n^{(\alpha)}(x) = 0 + + for :math:`\alpha > -1/2`; :math:`C_n^{(\alpha)}` is a polynomial + of degree :math:`n`. + + Parameters + ---------- + n : int + Degree of the polynomial. + alpha : float + Parameter, must be greater than -0.5. + monic : bool, optional + If `True`, scale the leading coefficient to be 1. Default is + `False`. + + Returns + ------- + C : orthopoly1d + Gegenbauer polynomial. + + Notes + ----- + The polynomials :math:`C_n^{(\alpha)}` are orthogonal on + :math:`[-1,1]` with respect to the weight function :math:`(1 - x^2)^{(\alpha - + 1/2)}`. + + Examples + -------- + >>> import numpy as np + >>> from scipy import special + >>> import matplotlib.pyplot as plt + + We can initialize a variable ``p`` as a Gegenbauer polynomial using the + `gegenbauer` function and evaluate at a point ``x = 1``. + + >>> p = special.gegenbauer(3, 0.5, monic=False) + >>> p + poly1d([ 2.5, 0. , -1.5, 0. ]) + >>> p(1) + 1.0 + + To evaluate ``p`` at various points ``x`` in the interval ``(-3, 3)``, + simply pass an array ``x`` to ``p`` as follows: + + >>> x = np.linspace(-3, 3, 400) + >>> y = p(x) + + We can then visualize ``x, y`` using `matplotlib.pyplot`. + + >>> fig, ax = plt.subplots() + >>> ax.plot(x, y) + >>> ax.set_title("Gegenbauer (ultraspherical) polynomial of degree 3") + >>> ax.set_xlabel("x") + >>> ax.set_ylabel(r"$C_3^{(0.5)}(x)$") + >>> plt.show() + + """ + if not np.isfinite(alpha) or alpha <= -0.5 : + raise ValueError("`alpha` must be a finite number greater than -1/2") + base = jacobi(n, alpha - 0.5, alpha - 0.5, monic=monic) + if monic or n == 0: + return base + # Abrahmowitz and Stegan 22.5.20 + factor = (_gam(2*alpha + n) * _gam(alpha + 0.5) / + _gam(2*alpha) / _gam(alpha + 0.5 + n)) + base._scale(factor) + base.__dict__['_eval_func'] = lambda x: _ufuncs.eval_gegenbauer(float(n), + alpha, x) + return base + +# Chebyshev of the first kind: T_n(x) = +# n! sqrt(pi) / _gam(n+1./2)* P^(-1/2,-1/2)_n(x) +# Computed anew. + + +def roots_chebyt(n, mu=False): + r"""Gauss-Chebyshev (first kind) quadrature. + + Computes the sample points and weights for Gauss-Chebyshev + quadrature. The sample points are the roots of the nth degree + Chebyshev polynomial of the first kind, :math:`T_n(x)`. These + sample points and weights correctly integrate polynomials of + degree :math:`2n - 1` or less over the interval :math:`[-1, 1]` + with weight function :math:`w(x) = 1/\sqrt{1 - x^2}`. See 22.2.4 + in [AS]_ for more details. + + Parameters + ---------- + n : int + quadrature order + mu : bool, optional + If True, return the sum of the weights, optional. + + Returns + ------- + x : ndarray + Sample points + w : ndarray + Weights + mu : float + Sum of the weights + + See Also + -------- + scipy.integrate.fixed_quad + numpy.polynomial.chebyshev.chebgauss + + References + ---------- + .. [AS] Milton Abramowitz and Irene A. Stegun, eds. + Handbook of Mathematical Functions with Formulas, + Graphs, and Mathematical Tables. New York: Dover, 1972. + + """ + m = int(n) + if n < 1 or n != m: + raise ValueError('n must be a positive integer.') + x = _ufuncs._sinpi(np.arange(-m + 1, m, 2) / (2*m)) + w = np.full_like(x, pi/m) + if mu: + return x, w, pi + else: + return x, w + + +def chebyt(n, monic=False): + r"""Chebyshev polynomial of the first kind. + + Defined to be the solution of + + .. math:: + (1 - x^2)\frac{d^2}{dx^2}T_n(x) - x\frac{d}{dx}T_n(x) + + n^2T_n(x) = 0; + + :math:`T_n` is a polynomial of degree :math:`n`. + + Parameters + ---------- + n : int + Degree of the polynomial. + monic : bool, optional + If `True`, scale the leading coefficient to be 1. Default is + `False`. + + Returns + ------- + T : orthopoly1d + Chebyshev polynomial of the first kind. + + See Also + -------- + chebyu : Chebyshev polynomial of the second kind. + + Notes + ----- + The polynomials :math:`T_n` are orthogonal over :math:`[-1, 1]` + with weight function :math:`(1 - x^2)^{-1/2}`. + + References + ---------- + .. [AS] Milton Abramowitz and Irene A. Stegun, eds. + Handbook of Mathematical Functions with Formulas, + Graphs, and Mathematical Tables. New York: Dover, 1972. + + Examples + -------- + Chebyshev polynomials of the first kind of order :math:`n` can + be obtained as the determinant of specific :math:`n \times n` + matrices. As an example we can check how the points obtained from + the determinant of the following :math:`3 \times 3` matrix + lay exactly on :math:`T_3`: + + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from scipy.linalg import det + >>> from scipy.special import chebyt + >>> x = np.arange(-1.0, 1.0, 0.01) + >>> fig, ax = plt.subplots() + >>> ax.set_ylim(-2.0, 2.0) + >>> ax.set_title(r'Chebyshev polynomial $T_3$') + >>> ax.plot(x, chebyt(3)(x), label=rf'$T_3$') + >>> for p in np.arange(-1.0, 1.0, 0.1): + ... ax.plot(p, + ... det(np.array([[p, 1, 0], [1, 2*p, 1], [0, 1, 2*p]])), + ... 'rx') + >>> plt.legend(loc='best') + >>> plt.show() + + They are also related to the Jacobi Polynomials + :math:`P_n^{(-0.5, -0.5)}` through the relation: + + .. math:: + P_n^{(-0.5, -0.5)}(x) = \frac{1}{4^n} \binom{2n}{n} T_n(x) + + Let's verify it for :math:`n = 3`: + + >>> from scipy.special import binom + >>> from scipy.special import jacobi + >>> x = np.arange(-1.0, 1.0, 0.01) + >>> np.allclose(jacobi(3, -0.5, -0.5)(x), + ... 1/64 * binom(6, 3) * chebyt(3)(x)) + True + + We can plot the Chebyshev polynomials :math:`T_n` for some values + of :math:`n`: + + >>> x = np.arange(-1.5, 1.5, 0.01) + >>> fig, ax = plt.subplots() + >>> ax.set_ylim(-4.0, 4.0) + >>> ax.set_title(r'Chebyshev polynomials $T_n$') + >>> for n in np.arange(2,5): + ... ax.plot(x, chebyt(n)(x), label=rf'$T_n={n}$') + >>> plt.legend(loc='best') + >>> plt.show() + + """ + if n < 0: + raise ValueError("n must be nonnegative.") + + def wfunc(x): + return 1.0 / sqrt(1 - x * x) + if n == 0: + return orthopoly1d([], [], pi, 1.0, wfunc, (-1, 1), monic, + lambda x: _ufuncs.eval_chebyt(n, x)) + n1 = n + x, w, mu = roots_chebyt(n1, mu=True) + hn = pi / 2 + kn = 2**(n - 1) + p = orthopoly1d(x, w, hn, kn, wfunc, (-1, 1), monic, + lambda x: _ufuncs.eval_chebyt(n, x)) + return p + +# Chebyshev of the second kind +# U_n(x) = (n+1)! sqrt(pi) / (2*_gam(n+3./2)) * P^(1/2,1/2)_n(x) + + +def roots_chebyu(n, mu=False): + r"""Gauss-Chebyshev (second kind) quadrature. + + Computes the sample points and weights for Gauss-Chebyshev + quadrature. The sample points are the roots of the nth degree + Chebyshev polynomial of the second kind, :math:`U_n(x)`. These + sample points and weights correctly integrate polynomials of + degree :math:`2n - 1` or less over the interval :math:`[-1, 1]` + with weight function :math:`w(x) = \sqrt{1 - x^2}`. See 22.2.5 in + [AS]_ for details. + + Parameters + ---------- + n : int + quadrature order + mu : bool, optional + If True, return the sum of the weights, optional. + + Returns + ------- + x : ndarray + Sample points + w : ndarray + Weights + mu : float + Sum of the weights + + See Also + -------- + scipy.integrate.fixed_quad + + References + ---------- + .. [AS] Milton Abramowitz and Irene A. Stegun, eds. + Handbook of Mathematical Functions with Formulas, + Graphs, and Mathematical Tables. New York: Dover, 1972. + + """ + m = int(n) + if n < 1 or n != m: + raise ValueError('n must be a positive integer.') + t = np.arange(m, 0, -1) * pi / (m + 1) + x = np.cos(t) + w = pi * np.sin(t)**2 / (m + 1) + if mu: + return x, w, pi / 2 + else: + return x, w + + +def chebyu(n, monic=False): + r"""Chebyshev polynomial of the second kind. + + Defined to be the solution of + + .. math:: + (1 - x^2)\frac{d^2}{dx^2}U_n(x) - 3x\frac{d}{dx}U_n(x) + + n(n + 2)U_n(x) = 0; + + :math:`U_n` is a polynomial of degree :math:`n`. + + Parameters + ---------- + n : int + Degree of the polynomial. + monic : bool, optional + If `True`, scale the leading coefficient to be 1. Default is + `False`. + + Returns + ------- + U : orthopoly1d + Chebyshev polynomial of the second kind. + + See Also + -------- + chebyt : Chebyshev polynomial of the first kind. + + Notes + ----- + The polynomials :math:`U_n` are orthogonal over :math:`[-1, 1]` + with weight function :math:`(1 - x^2)^{1/2}`. + + References + ---------- + .. [AS] Milton Abramowitz and Irene A. Stegun, eds. + Handbook of Mathematical Functions with Formulas, + Graphs, and Mathematical Tables. New York: Dover, 1972. + + Examples + -------- + Chebyshev polynomials of the second kind of order :math:`n` can + be obtained as the determinant of specific :math:`n \times n` + matrices. As an example we can check how the points obtained from + the determinant of the following :math:`3 \times 3` matrix + lay exactly on :math:`U_3`: + + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from scipy.linalg import det + >>> from scipy.special import chebyu + >>> x = np.arange(-1.0, 1.0, 0.01) + >>> fig, ax = plt.subplots() + >>> ax.set_ylim(-2.0, 2.0) + >>> ax.set_title(r'Chebyshev polynomial $U_3$') + >>> ax.plot(x, chebyu(3)(x), label=rf'$U_3$') + >>> for p in np.arange(-1.0, 1.0, 0.1): + ... ax.plot(p, + ... det(np.array([[2*p, 1, 0], [1, 2*p, 1], [0, 1, 2*p]])), + ... 'rx') + >>> plt.legend(loc='best') + >>> plt.show() + + They satisfy the recurrence relation: + + .. math:: + U_{2n-1}(x) = 2 T_n(x)U_{n-1}(x) + + where :math:`T_n` is the Chebyshev polynomial of the first kind. + Let's verify it for :math:`n = 2`: + + >>> from scipy.special import chebyt + >>> x = np.arange(-1.0, 1.0, 0.01) + >>> np.allclose(chebyu(3)(x), 2 * chebyt(2)(x) * chebyu(1)(x)) + True + + We can plot the Chebyshev polynomials :math:`U_n` for some values + of :math:`n`: + + >>> x = np.arange(-1.0, 1.0, 0.01) + >>> fig, ax = plt.subplots() + >>> ax.set_ylim(-1.5, 1.5) + >>> ax.set_title(r'Chebyshev polynomials $U_n$') + >>> for n in np.arange(1,5): + ... ax.plot(x, chebyu(n)(x), label=rf'$U_n={n}$') + >>> plt.legend(loc='best') + >>> plt.show() + + """ + base = jacobi(n, 0.5, 0.5, monic=monic) + if monic: + return base + factor = sqrt(pi) / 2.0 * _gam(n + 2) / _gam(n + 1.5) + base._scale(factor) + return base + +# Chebyshev of the first kind C_n(x) + + +def roots_chebyc(n, mu=False): + r"""Gauss-Chebyshev (first kind) quadrature. + + Compute the sample points and weights for Gauss-Chebyshev + quadrature. The sample points are the roots of the nth degree + Chebyshev polynomial of the first kind, :math:`C_n(x)`. These + sample points and weights correctly integrate polynomials of + degree :math:`2n - 1` or less over the interval :math:`[-2, 2]` + with weight function :math:`w(x) = 1 / \sqrt{1 - (x/2)^2}`. See + 22.2.6 in [AS]_ for more details. + + Parameters + ---------- + n : int + quadrature order + mu : bool, optional + If True, return the sum of the weights, optional. + + Returns + ------- + x : ndarray + Sample points + w : ndarray + Weights + mu : float + Sum of the weights + + See Also + -------- + scipy.integrate.fixed_quad + + References + ---------- + .. [AS] Milton Abramowitz and Irene A. Stegun, eds. + Handbook of Mathematical Functions with Formulas, + Graphs, and Mathematical Tables. New York: Dover, 1972. + + """ + x, w, m = roots_chebyt(n, True) + x *= 2 + w *= 2 + m *= 2 + if mu: + return x, w, m + else: + return x, w + + +def chebyc(n, monic=False): + r"""Chebyshev polynomial of the first kind on :math:`[-2, 2]`. + + Defined as :math:`C_n(x) = 2T_n(x/2)`, where :math:`T_n` is the + nth Chebyshev polynomial of the first kind. + + Parameters + ---------- + n : int + Degree of the polynomial. + monic : bool, optional + If `True`, scale the leading coefficient to be 1. Default is + `False`. + + Returns + ------- + C : orthopoly1d + Chebyshev polynomial of the first kind on :math:`[-2, 2]`. + + See Also + -------- + chebyt : Chebyshev polynomial of the first kind. + + Notes + ----- + The polynomials :math:`C_n(x)` are orthogonal over :math:`[-2, 2]` + with weight function :math:`1/\sqrt{1 - (x/2)^2}`. + + References + ---------- + .. [1] Abramowitz and Stegun, "Handbook of Mathematical Functions" + Section 22. National Bureau of Standards, 1972. + + Examples + -------- + Evaluate the Chebyshev polynomial :math:`C_3` at :math:`x = 1`: + + >>> import numpy as np + >>> from scipy.special import chebyc, chebyt + >>> np.isclose(chebyc(3)(1), -2.0) + True + + The polynomial :math:`C_n` is a scaled Chebyshev polynomial of the + first kind: + + >>> x = np.linspace(-2, 2, 5) + >>> np.allclose(chebyc(3)(x), 2 * chebyt(3)(x/2)) + True + + Plot :math:`C_n` for several values of :math:`n`: + + >>> import matplotlib.pyplot as plt + >>> x = np.linspace(-2, 2, 400) + >>> fig, ax = plt.subplots() + >>> for n in range(4): + ... ax.plot(x, chebyc(n)(x), label=rf"$C_{n}$") + >>> ax.set_title(r"Chebyshev polynomials $C_n$") + >>> ax.set_xlabel("x") + >>> ax.legend(loc="best") + >>> plt.show() + """ + if n < 0: + raise ValueError("n must be nonnegative.") + + if n == 0: + n1 = n + 1 + else: + n1 = n + x, w = roots_chebyc(n1) + if n == 0: + x, w = [], [] + hn = 4 * pi * ((n == 0) + 1) + kn = 1.0 + p = orthopoly1d(x, w, hn, kn, + wfunc=lambda x: 1.0 / sqrt(1 - x * x / 4.0), + limits=(-2, 2), monic=monic) + if not monic: + p._scale(2.0 / p(2)) + p.__dict__['_eval_func'] = lambda x: _ufuncs.eval_chebyc(n, x) + return p + +# Chebyshev of the second kind S_n(x) + + +def roots_chebys(n, mu=False): + r"""Gauss-Chebyshev (second kind) quadrature. + + Compute the sample points and weights for Gauss-Chebyshev + quadrature. The sample points are the roots of the nth degree + Chebyshev polynomial of the second kind, :math:`S_n(x)`. These + sample points and weights correctly integrate polynomials of + degree :math:`2n - 1` or less over the interval :math:`[-2, 2]` + with weight function :math:`w(x) = \sqrt{1 - (x/2)^2}`. See 22.2.7 + in [AS]_ for more details. + + Parameters + ---------- + n : int + quadrature order + mu : bool, optional + If True, return the sum of the weights, optional. + + Returns + ------- + x : ndarray + Sample points + w : ndarray + Weights + mu : float + Sum of the weights + + See Also + -------- + scipy.integrate.fixed_quad + + References + ---------- + .. [AS] Milton Abramowitz and Irene A. Stegun, eds. + Handbook of Mathematical Functions with Formulas, + Graphs, and Mathematical Tables. New York: Dover, 1972. + + """ + x, w, m = roots_chebyu(n, True) + x *= 2 + w *= 2 + m *= 2 + if mu: + return x, w, m + else: + return x, w + + +def chebys(n, monic=False): + r"""Chebyshev polynomial of the second kind on :math:`[-2, 2]`. + + Defined as :math:`S_n(x) = U_n(x/2)` where :math:`U_n` is the + nth Chebyshev polynomial of the second kind. + + Parameters + ---------- + n : int + Degree of the polynomial. + monic : bool, optional + If `True`, scale the leading coefficient to be 1. Default is + `False`. + + Returns + ------- + S : orthopoly1d + Chebyshev polynomial of the second kind on :math:`[-2, 2]`. + + See Also + -------- + chebyu : Chebyshev polynomial of the second kind + + Notes + ----- + The polynomials :math:`S_n(x)` are orthogonal over :math:`[-2, 2]` + with weight function :math:`\sqrt{1 - (x/2)^2}`. + + References + ---------- + .. [1] Abramowitz and Stegun, "Handbook of Mathematical Functions" + Section 22. National Bureau of Standards, 1972. + + Examples + -------- + Evaluate the Chebyshev polynomial of the second kind :math:`S_3` at :math:`x = 1`: + + >>> import numpy as np + >>> from scipy.special import chebys, chebyu + >>> np.isclose(chebys(3)(1), -1.0) + True + + The polynomial :math:`S_n` is a scaled Chebyshev polynomial of the + second kind: + + >>> x = np.linspace(-2, 2, 5) + >>> np.allclose(chebys(3)(x), chebyu(3)(x/2)) + True + + Plot :math:`S_n` for several values of :math:`n`: + + >>> import matplotlib.pyplot as plt + >>> x = np.linspace(-2, 2, 400) + >>> fig, ax = plt.subplots() + >>> for n in range(4): + ... ax.plot(x, chebys(n)(x), label=rf"$S_{n}$") + >>> ax.set_title(r"Chebyshev polynomials $S_n$") + >>> ax.set_xlabel("x") + >>> ax.legend(loc="best") + >>> plt.show() + """ + if n < 0: + raise ValueError("n must be nonnegative.") + + if n == 0: + n1 = n + 1 + else: + n1 = n + x, w = roots_chebys(n1) + if n == 0: + x, w = [], [] + hn = pi + kn = 1.0 + p = orthopoly1d(x, w, hn, kn, + wfunc=lambda x: sqrt(1 - x * x / 4.0), + limits=(-2, 2), monic=monic) + if not monic: + factor = (n + 1.0) / p(2) + p._scale(factor) + p.__dict__['_eval_func'] = lambda x: _ufuncs.eval_chebys(n, x) + return p + +# Shifted Chebyshev of the first kind T^*_n(x) + + +def roots_sh_chebyt(n, mu=False): + r"""Gauss-Chebyshev (first kind, shifted) quadrature. + + Compute the sample points and weights for Gauss-Chebyshev + quadrature. The sample points are the roots of the nth degree + shifted Chebyshev polynomial of the first kind, :math:`T_n(x)`. + These sample points and weights correctly integrate polynomials of + degree :math:`2n - 1` or less over the interval :math:`[0, 1]` + with weight function :math:`w(x) = 1/\sqrt{x - x^2}`. See 22.2.8 + in [AS]_ for more details. + + Parameters + ---------- + n : int + quadrature order. + mu : bool, optional + If True, return the sum of the weights, optional. + + Returns + ------- + x : ndarray + Sample points. + w : ndarray + Weights. + mu : float + Sum of the weights. + + See Also + -------- + scipy.integrate.fixed_quad + + References + ---------- + .. [AS] Milton Abramowitz and Irene A. Stegun, eds. + Handbook of Mathematical Functions with Formulas, + Graphs, and Mathematical Tables. New York: Dover, 1972. + + """ + xw = roots_chebyt(n, mu) + return ((xw[0] + 1) / 2,) + xw[1:] + + +def sh_chebyt(n, monic=False): + r"""Shifted Chebyshev polynomial of the first kind. + + Defined as :math:`T^*_n(x) = T_n(2x - 1)` for :math:`T_n` the nth + Chebyshev polynomial of the first kind. + + Parameters + ---------- + n : int + Degree of the polynomial. + monic : bool, optional + If `True`, scale the leading coefficient to be 1. Default is + `False`. + + Returns + ------- + T : orthopoly1d + Shifted Chebyshev polynomial of the first kind. + + Notes + ----- + The polynomials :math:`T^*_n` are orthogonal over :math:`[0, 1]` + with weight function :math:`(x - x^2)^{-1/2}`. + + Examples + -------- + Evaluate the shifted Chebyshev polynomial of the first kind :math:`T^*_3` at + :math:`x = 0.75`: + + >>> import numpy as np + >>> from scipy.special import chebyt, sh_chebyt + >>> np.isclose(sh_chebyt(3)(0.75), -1.0) + True + + The polynomial :math:`T^*_n` is the Chebyshev polynomial + :math:`T_n` shifted from :math:`[-1, 1]` to :math:`[0, 1]`: + + >>> x = np.linspace(0, 1, 5) + >>> np.allclose(sh_chebyt(3)(x), chebyt(3)(2*x - 1)) + True + + Plot :math:`T^*_n` for several values of :math:`n`: + + >>> import matplotlib.pyplot as plt + >>> x = np.linspace(0, 1, 400) + >>> fig, ax = plt.subplots() + >>> for n in range(4): + ... ax.plot(x, sh_chebyt(n)(x), label=rf"$T^*_{n}$") + >>> ax.set_title(r"Shifted Chebyshev polynomials $T^*_n$") + >>> ax.set_xlabel("x") + >>> ax.legend(loc="best") + >>> plt.show() + + """ + base = sh_jacobi(n, 0.0, 0.5, monic=monic) + if monic: + return base + if n > 0: + factor = 4**n / 2.0 + else: + factor = 1.0 + base._scale(factor) + return base + + +# Shifted Chebyshev of the second kind U^*_n(x) +def roots_sh_chebyu(n, mu=False): + r"""Gauss-Chebyshev (second kind, shifted) quadrature. + + Computes the sample points and weights for Gauss-Chebyshev + quadrature. The sample points are the roots of the nth degree + shifted Chebyshev polynomial of the second kind, :math:`U_n(x)`. + These sample points and weights correctly integrate polynomials of + degree :math:`2n - 1` or less over the interval :math:`[0, 1]` + with weight function :math:`w(x) = \sqrt{x - x^2}`. See 22.2.9 in + [AS]_ for more details. + + Parameters + ---------- + n : int + quadrature order + mu : bool, optional + If True, return the sum of the weights, optional. + + Returns + ------- + x : ndarray + Sample points + w : ndarray + Weights + mu : float + Sum of the weights + + See Also + -------- + scipy.integrate.fixed_quad + + References + ---------- + .. [AS] Milton Abramowitz and Irene A. Stegun, eds. + Handbook of Mathematical Functions with Formulas, + Graphs, and Mathematical Tables. New York: Dover, 1972. + + """ + x, w, m = roots_chebyu(n, True) + x = (x + 1) / 2 + m_us = _ufuncs.beta(1.5, 1.5) + w *= m_us / m + if mu: + return x, w, m_us + else: + return x, w + + +def sh_chebyu(n, monic=False): + r"""Shifted Chebyshev polynomial of the second kind. + + Defined as :math:`U^*_n(x) = U_n(2x - 1)` for :math:`U_n` the nth + Chebyshev polynomial of the second kind. + + Parameters + ---------- + n : int + Degree of the polynomial. + monic : bool, optional + If `True`, scale the leading coefficient to be 1. Default is + `False`. + + Returns + ------- + U : orthopoly1d + Shifted Chebyshev polynomial of the second kind. + + Notes + ----- + The polynomials :math:`U^*_n` are orthogonal over :math:`[0, 1]` + with weight function :math:`(x - x^2)^{1/2}`. + + Examples + -------- + Evaluate the shifted Chebyshev polynomial of the second kind :math:`U^*_3` at + :math:`x = 0.75`: + + >>> import numpy as np + >>> from scipy.special import chebyu, sh_chebyu + >>> np.isclose(sh_chebyu(3)(0.75), -1.0) + True + + The polynomial :math:`U^*_n` is the Chebyshev polynomial + :math:`U_n` shifted from :math:`[-1, 1]` to :math:`[0, 1]`: + + >>> x = np.linspace(0, 1, 5) + >>> np.allclose(sh_chebyu(3)(x), chebyu(3)(2*x - 1)) + True + + Plot :math:`U^*_n` for several values of :math:`n`: + + >>> import matplotlib.pyplot as plt + >>> x = np.linspace(0, 1, 400) + >>> fig, ax = plt.subplots() + >>> for n in range(4): + ... ax.plot(x, sh_chebyu(n)(x), label=rf"$U^*_{n}$") + >>> ax.set_title(r"Shifted Chebyshev polynomials $U^*_n$") + >>> ax.set_xlabel("x") + >>> ax.legend(loc="best") + >>> plt.show() + + """ + base = sh_jacobi(n, 2.0, 1.5, monic=monic) + if monic: + return base + factor = 4**n + base._scale(factor) + return base + +# Legendre + + +def roots_legendre(n, mu=False): + r"""Gauss-Legendre quadrature. + + Compute the sample points and weights for Gauss-Legendre + quadrature [GL]_. The sample points are the roots of the nth degree + Legendre polynomial :math:`P_n(x)`. These sample points and + weights correctly integrate polynomials of degree :math:`2n - 1` + or less over the interval :math:`[-1, 1]` with weight function + :math:`w(x) = 1`. See 2.2.10 in [AS]_ for more details. + + Parameters + ---------- + n : int + quadrature order. + mu : bool, optional + If True, return the sum of the weights, optional. + + Returns + ------- + x : ndarray + Sample points. + w : ndarray + Weights. + mu : float + Sum of the weights. + + See Also + -------- + scipy.integrate.fixed_quad + numpy.polynomial.legendre.leggauss + + References + ---------- + .. [AS] Milton Abramowitz and Irene A. Stegun, eds. + Handbook of Mathematical Functions with Formulas, + Graphs, and Mathematical Tables. New York: Dover, 1972. + .. [GL] Gauss-Legendre quadrature, Wikipedia, + https://en.wikipedia.org/wiki/Gauss%E2%80%93Legendre_quadrature + + Examples + -------- + >>> import numpy as np + >>> from scipy.special import roots_legendre, eval_legendre + >>> roots, weights = roots_legendre(9) + + ``roots`` holds the roots, and ``weights`` holds the weights for + Gauss-Legendre quadrature. + + >>> roots + array([-0.96816024, -0.83603111, -0.61337143, -0.32425342, 0. , + 0.32425342, 0.61337143, 0.83603111, 0.96816024]) + >>> weights + array([0.08127439, 0.18064816, 0.2606107 , 0.31234708, 0.33023936, + 0.31234708, 0.2606107 , 0.18064816, 0.08127439]) + + Verify that we have the roots by evaluating the degree 9 Legendre + polynomial at ``roots``. All the values are approximately zero: + + >>> eval_legendre(9, roots) + array([-8.88178420e-16, -2.22044605e-16, 1.11022302e-16, 1.11022302e-16, + 0.00000000e+00, -5.55111512e-17, -1.94289029e-16, 1.38777878e-16, + -8.32667268e-17]) + + Here we'll show how the above values can be used to estimate the + integral from 1 to 2 of f(t) = t + 1/t with Gauss-Legendre + quadrature [GL]_. First define the function and the integration + limits. + + >>> def f(t): + ... return t + 1/t + ... + >>> a = 1 + >>> b = 2 + + We'll use ``integral(f(t), t=a, t=b)`` to denote the definite integral + of f from t=a to t=b. The sample points in ``roots`` are from the + interval [-1, 1], so we'll rewrite the integral with the simple change + of variable:: + + x = 2/(b - a) * t - (a + b)/(b - a) + + with inverse:: + + t = (b - a)/2 * x + (a + b)/2 + + Then:: + + integral(f(t), a, b) = + (b - a)/2 * integral(f((b-a)/2*x + (a+b)/2), x=-1, x=1) + + We can approximate the latter integral with the values returned + by `roots_legendre`. + + Map the roots computed above from [-1, 1] to [a, b]. + + >>> t = (b - a)/2 * roots + (a + b)/2 + + Approximate the integral as the weighted sum of the function values. + + >>> (b - a)/2 * f(t).dot(weights) + 2.1931471805599276 + + Compare that to the exact result, which is 3/2 + log(2): + + >>> 1.5 + np.log(2) + 2.1931471805599454 + + """ + m = int(n) + if n < 1 or n != m: + raise ValueError("n must be a positive integer.") + + mu0 = 2.0 + def an_func(k): + return 0.0 * k + def bn_func(k): + return k * np.sqrt(1.0 / (4 * k * k - 1)) + f = _ufuncs.eval_legendre + def df(n, x): + return (-n * x * _ufuncs.eval_legendre(n, x) + + n * _ufuncs.eval_legendre(n - 1, x)) / (1 - x ** 2) + return _gen_roots_and_weights(m, mu0, an_func, bn_func, f, df, True, mu) + + +def legendre(n, monic=False): + r"""Legendre polynomial. + + Defined to be the solution of + + .. math:: + \frac{d}{dx}\left[(1 - x^2)\frac{d}{dx}P_n(x)\right] + + n(n + 1)P_n(x) = 0; + + :math:`P_n(x)` is a polynomial of degree :math:`n`. + + Parameters + ---------- + n : int + Degree of the polynomial. + monic : bool, optional + If `True`, scale the leading coefficient to be 1. Default is + `False`. + + Returns + ------- + P : orthopoly1d + Legendre polynomial. + + Notes + ----- + The polynomials :math:`P_n` are orthogonal over :math:`[-1, 1]` + with weight function 1. + + Examples + -------- + Generate the 3rd-order Legendre polynomial 1/2*(5x^3 + 0x^2 - 3x + 0): + + >>> from scipy.special import legendre + >>> legendre(3) + poly1d([ 2.5, 0. , -1.5, 0. ]) + + """ + if n < 0: + raise ValueError("n must be nonnegative.") + + if n == 0: + n1 = n + 1 + else: + n1 = n + x, w = roots_legendre(n1) + if n == 0: + x, w = [], [] + hn = 2.0 / (2 * n + 1) + kn = _gam(2 * n + 1) / _gam(n + 1)**2 / 2.0**n + p = orthopoly1d(x, w, hn, kn, wfunc=lambda x: 1.0, limits=(-1, 1), + monic=monic, + eval_func=lambda x: _ufuncs.eval_legendre(n, x)) + return p + +# Shifted Legendre P^*_n(x) + + +def roots_sh_legendre(n, mu=False): + r"""Gauss-Legendre (shifted) quadrature. + + Compute the sample points and weights for Gauss-Legendre + quadrature. The sample points are the roots of the nth degree + shifted Legendre polynomial :math:`P^*_n(x)`. These sample points + and weights correctly integrate polynomials of degree :math:`2n - + 1` or less over the interval :math:`[0, 1]` with weight function + :math:`w(x) = 1.0`. See 2.2.11 in [AS]_ for details. + + Parameters + ---------- + n : int + quadrature order + mu : bool, optional + If True, return the sum of the weights, optional. + + Returns + ------- + x : ndarray + Sample points + w : ndarray + Weights + mu : float + Sum of the weights + + See Also + -------- + scipy.integrate.fixed_quad + + References + ---------- + .. [AS] Milton Abramowitz and Irene A. Stegun, eds. + Handbook of Mathematical Functions with Formulas, + Graphs, and Mathematical Tables. New York: Dover, 1972. + + Examples + -------- + + Compute nodes and weights for a 7th-order shifted Gauss-Legendre quadrature: + + >>> from scipy.special import roots_sh_legendre, eval_sh_legendre + >>> x, w = roots_sh_legendre(7) + >>> x + array([0.02544604, 0.12923441, 0.29707742, 0.5, 0.70292258, 0.87076559, + 0.97455396]) + >>> w + array([0.06474248, 0.1398527, 0.19091503, 0.20897959, 0.19091503, 0.1398527, + 0.06474248]) + + Verify that ``x`` are the roots of the degree-7 shifted Legendre polynomial: + + >>> eval_sh_legendre(7, x) + array([5.55111512e-16, 1.11022302e-16, 3.33066907e-16, 0.00000000e+00, + -2.22044605e-16, -1.11022302e-16, -1.85962357e-15]) + + The sum of the weights for shifted Gauss-Legendre quadrature is always 1: + + >>> x, w, mu = roots_sh_legendre(10, mu=True) + >>> mu + 1.0 # Sum of weights of shifted Gauss-Legendre quadrature is always 1 + + """ + x, w = roots_legendre(n) + x = (x + 1) / 2 + w /= 2 + if mu: + return x, w, 1.0 + else: + return x, w + + +def sh_legendre(n, monic=False): + r""" + Shifted Legendre polynomial. + + Defined as :math:`P^*_n(x) = P_n(2x - 1)` for :math:`P_n` the nth + Legendre polynomial. + + Parameters + ---------- + n : int + Degree of the polynomial. + monic : bool, optional + If `True`, scale the leading coefficient to be 1. Default is + `False`. + + Returns + ------- + P : orthopoly1d + Shifted Legendre polynomial. + + See Also + -------- + scipy.special.legendre + scipy.special.roots_sh_legendre + + Notes + ----- + The polynomials :math:`P^*_n` are orthogonal over :math:`[0, 1]` + with weight function 1. + + Examples + -------- + The shifted Legendre polynomials :math:`P_n^*` are related + to the non-shifted polynomials :math:`P_n` by + :math:`P_n^*(x) = P_n(2x - 1)`. We can verify this on the + interval :math:`[0, 1]`: + + >>> import numpy as np + >>> from scipy.special import sh_legendre, legendre + >>> from scipy.integrate import trapezoid + >>> x = np.arange(0.0, 1.0, 0.01) + >>> n = 3 + >>> np.allclose(sh_legendre(n)(x), legendre(n)(2*x - 1)) + True + + The polynomials :math:`P_n^*` satisfy a recurrence + relation obtained by the change of variables + :math:`t = 2x - 1` in the standard Legendre recurrence: + + .. math:: + + (n+1) P_{n+1}^*(x) = (2n+1)(2x-1)\,P_n^*(x) - n\,P_{n-1}^*(x). + + This can be easily checked on :math:`[0, 1]` + for :math:`n = 3`: + + >>> n = 3 + >>> x = np.linspace(0.0, 1.0, 101) + >>> lhs = (n + 1) * sh_legendre(n + 1)(x) + >>> rhs = ( + ... (2*n + 1) * (2*x - 1) * sh_legendre(n)(x) + ... - n * sh_legendre(n - 1)(x) + ... ) + >>> np.allclose(lhs, rhs) + True + + Orthogonality over :math:`[0,1]` with weight 1 can be + checked numerically; for example, :math:`P_2^*` + is orthogonal to :math:`P_3^*`: + + >>> x = np.linspace(0.0, 1.0, 400) + >>> y = sh_legendre(2)(x) * sh_legendre(3)(x) + >>> np.isclose(trapezoid(y, x), 0.0, atol=1e-12) + True + """ + if n < 0: + raise ValueError("n must be nonnegative.") + + def wfunc(x): + return 0.0 * x + 1.0 + if n == 0: + return orthopoly1d([], [], 1.0, 1.0, wfunc, (0, 1), monic, + lambda x: _ufuncs.eval_sh_legendre(n, x)) + x, w = roots_sh_legendre(n) + hn = 1.0 / (2 * n + 1.0) + kn = _gam(2 * n + 1) / _gam(n + 1)**2 + p = orthopoly1d(x, w, hn, kn, wfunc, limits=(0, 1), monic=monic, + eval_func=lambda x: _ufuncs.eval_sh_legendre(n, x)) + return p + + +# Make the old root function names an alias for the new ones +_modattrs = globals() +for newfun, oldfun in _rootfuns_map.items(): + _modattrs[oldfun] = _modattrs[newfun] + __all__.append(oldfun) diff --git a/venv/lib/python3.12/site-packages/scipy/special/_orthogonal.pyi b/venv/lib/python3.12/site-packages/scipy/special/_orthogonal.pyi new file mode 100644 index 0000000000000000000000000000000000000000..d2f95577650a2c2112c088412a88d1ba6e5ca30a --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/_orthogonal.pyi @@ -0,0 +1,329 @@ +from typing import ( + Any, + Literal, + overload, +) +from collections.abc import Callable + +import numpy as np + +_IntegerType = int | np.integer +_FloatingType = float | np.floating +_PointsAndWeights = tuple[np.ndarray, np.ndarray] +_PointsAndWeightsAndMu = tuple[np.ndarray, np.ndarray, float] + +_ArrayLike0D = bool | int | float | complex | str | bytes | np.generic + +__all__ = [ + 'legendre', + 'chebyt', + 'chebyu', + 'chebyc', + 'chebys', + 'jacobi', + 'laguerre', + 'genlaguerre', + 'hermite', + 'hermitenorm', + 'gegenbauer', + 'sh_legendre', + 'sh_chebyt', + 'sh_chebyu', + 'sh_jacobi', + 'roots_legendre', + 'roots_chebyt', + 'roots_chebyu', + 'roots_chebyc', + 'roots_chebys', + 'roots_jacobi', + 'roots_laguerre', + 'roots_genlaguerre', + 'roots_hermite', + 'roots_hermitenorm', + 'roots_gegenbauer', + 'roots_sh_legendre', + 'roots_sh_chebyt', + 'roots_sh_chebyu', + 'roots_sh_jacobi', +] + +@overload +def roots_jacobi( + n: _IntegerType, + alpha: _FloatingType, + beta: _FloatingType, +) -> _PointsAndWeights: ... +@overload +def roots_jacobi( + n: _IntegerType, + alpha: _FloatingType, + beta: _FloatingType, + mu: Literal[False], +) -> _PointsAndWeights: ... +@overload +def roots_jacobi( + n: _IntegerType, + alpha: _FloatingType, + beta: _FloatingType, + mu: Literal[True], +) -> _PointsAndWeightsAndMu: ... + +@overload +def roots_sh_jacobi( + n: _IntegerType, + p1: _FloatingType, + q1: _FloatingType, +) -> _PointsAndWeights: ... +@overload +def roots_sh_jacobi( + n: _IntegerType, + p1: _FloatingType, + q1: _FloatingType, + mu: Literal[False], +) -> _PointsAndWeights: ... +@overload +def roots_sh_jacobi( + n: _IntegerType, + p1: _FloatingType, + q1: _FloatingType, + mu: Literal[True], +) -> _PointsAndWeightsAndMu: ... + +@overload +def roots_genlaguerre( + n: _IntegerType, + alpha: _FloatingType, +) -> _PointsAndWeights: ... +@overload +def roots_genlaguerre( + n: _IntegerType, + alpha: _FloatingType, + mu: Literal[False], +) -> _PointsAndWeights: ... +@overload +def roots_genlaguerre( + n: _IntegerType, + alpha: _FloatingType, + mu: Literal[True], +) -> _PointsAndWeightsAndMu: ... + +@overload +def roots_laguerre(n: _IntegerType) -> _PointsAndWeights: ... +@overload +def roots_laguerre( + n: _IntegerType, + mu: Literal[False], +) -> _PointsAndWeights: ... +@overload +def roots_laguerre( + n: _IntegerType, + mu: Literal[True], +) -> _PointsAndWeightsAndMu: ... + +@overload +def roots_hermite(n: _IntegerType) -> _PointsAndWeights: ... +@overload +def roots_hermite( + n: _IntegerType, + mu: Literal[False], +) -> _PointsAndWeights: ... +@overload +def roots_hermite( + n: _IntegerType, + mu: Literal[True], +) -> _PointsAndWeightsAndMu: ... + +@overload +def roots_hermitenorm(n: _IntegerType) -> _PointsAndWeights: ... +@overload +def roots_hermitenorm( + n: _IntegerType, + mu: Literal[False], +) -> _PointsAndWeights: ... +@overload +def roots_hermitenorm( + n: _IntegerType, + mu: Literal[True], +) -> _PointsAndWeightsAndMu: ... + +@overload +def roots_gegenbauer( + n: _IntegerType, + alpha: _FloatingType, +) -> _PointsAndWeights: ... +@overload +def roots_gegenbauer( + n: _IntegerType, + alpha: _FloatingType, + mu: Literal[False], +) -> _PointsAndWeights: ... +@overload +def roots_gegenbauer( + n: _IntegerType, + alpha: _FloatingType, + mu: Literal[True], +) -> _PointsAndWeightsAndMu: ... + +@overload +def roots_chebyt(n: _IntegerType) -> _PointsAndWeights: ... +@overload +def roots_chebyt( + n: _IntegerType, + mu: Literal[False], +) -> _PointsAndWeights: ... +@overload +def roots_chebyt( + n: _IntegerType, + mu: Literal[True], +) -> _PointsAndWeightsAndMu: ... + +@overload +def roots_chebyu(n: _IntegerType) -> _PointsAndWeights: ... +@overload +def roots_chebyu( + n: _IntegerType, + mu: Literal[False], +) -> _PointsAndWeights: ... +@overload +def roots_chebyu( + n: _IntegerType, + mu: Literal[True], +) -> _PointsAndWeightsAndMu: ... + +@overload +def roots_chebyc(n: _IntegerType) -> _PointsAndWeights: ... +@overload +def roots_chebyc( + n: _IntegerType, + mu: Literal[False], +) -> _PointsAndWeights: ... +@overload +def roots_chebyc( + n: _IntegerType, + mu: Literal[True], +) -> _PointsAndWeightsAndMu: ... + +@overload +def roots_chebys(n: _IntegerType) -> _PointsAndWeights: ... +@overload +def roots_chebys( + n: _IntegerType, + mu: Literal[False], +) -> _PointsAndWeights: ... +@overload +def roots_chebys( + n: _IntegerType, + mu: Literal[True], +) -> _PointsAndWeightsAndMu: ... + +@overload +def roots_sh_chebyt(n: _IntegerType) -> _PointsAndWeights: ... +@overload +def roots_sh_chebyt( + n: _IntegerType, + mu: Literal[False], +) -> _PointsAndWeights: ... +@overload +def roots_sh_chebyt( + n: _IntegerType, + mu: Literal[True], +) -> _PointsAndWeightsAndMu: ... + +@overload +def roots_sh_chebyu(n: _IntegerType) -> _PointsAndWeights: ... +@overload +def roots_sh_chebyu( + n: _IntegerType, + mu: Literal[False], +) -> _PointsAndWeights: ... +@overload +def roots_sh_chebyu( + n: _IntegerType, + mu: Literal[True], +) -> _PointsAndWeightsAndMu: ... + +@overload +def roots_legendre(n: _IntegerType) -> _PointsAndWeights: ... +@overload +def roots_legendre( + n: _IntegerType, + mu: Literal[False], +) -> _PointsAndWeights: ... +@overload +def roots_legendre( + n: _IntegerType, + mu: Literal[True], +) -> _PointsAndWeightsAndMu: ... + +@overload +def roots_sh_legendre(n: _IntegerType) -> _PointsAndWeights: ... +@overload +def roots_sh_legendre( + n: _IntegerType, + mu: Literal[False], +) -> _PointsAndWeights: ... +@overload +def roots_sh_legendre( + n: _IntegerType, + mu: Literal[True], +) -> _PointsAndWeightsAndMu: ... + +class orthopoly1d(np.poly1d): + def __init__( + self, + roots: np.typing.ArrayLike, + weights: np.typing.ArrayLike | None = None, + hn: float = ..., + kn: float = ..., + wfunc: Callable[[float], float] | None = None, + limits: tuple[float, float] | None = None, + monic: bool = ..., + eval_func: np.ufunc | None = None, + ) -> None: ... + @property + def limits(self) -> tuple[float, float]: ... + def weight_func(self, x: float) -> float: ... + @overload + def __call__(self, x: _ArrayLike0D) -> Any: ... + @overload + def __call__(self, x: np.poly1d) -> np.poly1d: ... # type: ignore[overload-overlap] + @overload + def __call__(self, x: np.typing.ArrayLike) -> np.ndarray: ... + +def legendre(n: _IntegerType, monic: bool = ...) -> orthopoly1d: ... +def chebyt(n: _IntegerType, monic: bool = ...) -> orthopoly1d: ... +def chebyu(n: _IntegerType, monic: bool = ...) -> orthopoly1d: ... +def chebyc(n: _IntegerType, monic: bool = ...) -> orthopoly1d: ... +def chebys(n: _IntegerType, monic: bool = ...) -> orthopoly1d: ... +def jacobi( + n: _IntegerType, + alpha: _FloatingType, + beta: _FloatingType, + monic: bool = ..., +) -> orthopoly1d: ... +def laguerre(n: _IntegerType, monic: bool = ...) -> orthopoly1d: ... +def genlaguerre( + n: _IntegerType, + alpha: _FloatingType, + monic: bool = ..., +) -> orthopoly1d: ... +def hermite(n: _IntegerType, monic: bool = ...) -> orthopoly1d: ... +def hermitenorm(n: _IntegerType, monic: bool = ...) -> orthopoly1d: ... +def gegenbauer( + n: _IntegerType, + alpha: _FloatingType, + monic: bool = ..., +) -> orthopoly1d: ... +def sh_legendre(n: _IntegerType, monic: bool = ...) -> orthopoly1d: ... +def sh_chebyt(n: _IntegerType, monic: bool = ...) -> orthopoly1d: ... +def sh_chebyu(n: _IntegerType, monic: bool = ...) -> orthopoly1d: ... +def sh_jacobi( + n: _IntegerType, + p: _FloatingType, + q: _FloatingType, + monic: bool = ..., +) -> orthopoly1d: ... + +# These functions are not public, but still need stubs because they +# get checked in the tests. +def _roots_hermite_asy(n: _IntegerType) -> _PointsAndWeights: ... diff --git a/venv/lib/python3.12/site-packages/scipy/special/_precompute/__init__.py b/venv/lib/python3.12/site-packages/scipy/special/_precompute/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/venv/lib/python3.12/site-packages/scipy/special/_precompute/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/_precompute/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c66414c8ff5ad601a19a235410a562fe480bd18d Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/_precompute/__pycache__/__init__.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/_precompute/__pycache__/cosine_cdf.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/_precompute/__pycache__/cosine_cdf.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..579c7ca8080c4d080b715c51ea2e2c77249d3115 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/_precompute/__pycache__/cosine_cdf.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/_precompute/__pycache__/expn_asy.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/_precompute/__pycache__/expn_asy.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8ce672510950d38eedba06ebe6569a4eb583aaa3 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/_precompute/__pycache__/expn_asy.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/_precompute/__pycache__/gammainc_asy.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/_precompute/__pycache__/gammainc_asy.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c075b6d75d7b438f77aa68c699ff5a07db86b387 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/_precompute/__pycache__/gammainc_asy.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/_precompute/__pycache__/gammainc_data.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/_precompute/__pycache__/gammainc_data.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9dc46370d9e30c4e79632af3055dcebaa4c98897 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/_precompute/__pycache__/gammainc_data.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/_precompute/__pycache__/hyp2f1_data.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/_precompute/__pycache__/hyp2f1_data.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..849016aff15102fcb202d06f8f4f624372d3da97 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/_precompute/__pycache__/hyp2f1_data.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/_precompute/__pycache__/lambertw.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/_precompute/__pycache__/lambertw.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a2b6d205acf403fddec815573cefef55c05f990c Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/_precompute/__pycache__/lambertw.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/_precompute/__pycache__/loggamma.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/_precompute/__pycache__/loggamma.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..40996e63cf324bef8bb2bbb70204a27dc68f0fa5 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/_precompute/__pycache__/loggamma.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/_precompute/__pycache__/struve_convergence.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/_precompute/__pycache__/struve_convergence.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9ba5e0839b8ebc7fe0315bc611c9d19cb33f8eb9 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/_precompute/__pycache__/struve_convergence.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/_precompute/__pycache__/utils.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/_precompute/__pycache__/utils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6a6e6771dd3d866b9884cfa5a9bab8a42045d0f5 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/_precompute/__pycache__/utils.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/_precompute/__pycache__/wright_bessel.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/_precompute/__pycache__/wright_bessel.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..07bfd3d136eaa929a7b40308157311fd0262813e Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/_precompute/__pycache__/wright_bessel.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/_precompute/__pycache__/wright_bessel_data.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/_precompute/__pycache__/wright_bessel_data.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a2ba9d697b7078042eb6a937a37d4bf1f650d3f1 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/_precompute/__pycache__/wright_bessel_data.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/_precompute/__pycache__/wrightomega.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/_precompute/__pycache__/wrightomega.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d32c88039ef37cbad8c34f9b9a8881a2be8ab391 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/_precompute/__pycache__/wrightomega.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/_precompute/__pycache__/zetac.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/_precompute/__pycache__/zetac.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a3143eca264c20d78a6cf8c461bbcf1618a6ffa7 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/_precompute/__pycache__/zetac.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/_precompute/cosine_cdf.py b/venv/lib/python3.12/site-packages/scipy/special/_precompute/cosine_cdf.py new file mode 100644 index 0000000000000000000000000000000000000000..662c12bc74b31478c87471fbd1cce8bea285e765 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/_precompute/cosine_cdf.py @@ -0,0 +1,17 @@ +import mpmath + + +def f(x): + return (mpmath.pi + x + mpmath.sin(x)) / (2*mpmath.pi) + + +# Note: 40 digits might be overkill; a few more digits than the default +# might be sufficient. +mpmath.mp.dps = 40 +ts = mpmath.taylor(f, -mpmath.pi, 20) +p, q = mpmath.pade(ts, 9, 10) + +p = [float(c) for c in p] +q = [float(c) for c in q] +print('p =', p) +print('q =', q) diff --git a/venv/lib/python3.12/site-packages/scipy/special/_precompute/expn_asy.py b/venv/lib/python3.12/site-packages/scipy/special/_precompute/expn_asy.py new file mode 100644 index 0000000000000000000000000000000000000000..3491b8acd588a2cacfc48f0a3a60c6ae88c3e8c5 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/_precompute/expn_asy.py @@ -0,0 +1,54 @@ +"""Precompute the polynomials for the asymptotic expansion of the +generalized exponential integral. + +Sources +------- +[1] NIST, Digital Library of Mathematical Functions, + https://dlmf.nist.gov/8.20#ii + +""" +import os + +try: + import sympy + from sympy import Poly + x = sympy.symbols('x') +except ImportError: + pass + + +def generate_A(K): + A = [Poly(1, x)] + for k in range(K): + A.append(Poly(1 - 2*k*x, x)*A[k] + Poly(x*(x + 1))*A[k].diff()) + return A + + +WARNING = """\ +/* This file was automatically generated by _precompute/expn_asy.py. + * Do not edit it manually! + */ +""" + + +def main(): + print(__doc__) + fn = os.path.join('..', 'cephes', 'expn.h') + + K = 12 + A = generate_A(K) + with open(fn + '.new', 'w') as f: + f.write(WARNING) + f.write(f"#define nA {len(A)}\n") + for k, Ak in enumerate(A): + ', '.join([str(x.evalf(18)) for x in Ak.coeffs()]) + f.write(f"static const double A{k}[] = {{tmp}};\n") + ", ".join([f"A{k}" for k in range(K + 1)]) + f.write("static const double *A[] = {{tmp}};\n") + ", ".join([str(Ak.degree()) for Ak in A]) + f.write("static const int Adegs[] = {{tmp}};\n") + os.rename(fn + '.new', fn) + + +if __name__ == "__main__": + main() diff --git a/venv/lib/python3.12/site-packages/scipy/special/_precompute/gammainc_asy.py b/venv/lib/python3.12/site-packages/scipy/special/_precompute/gammainc_asy.py new file mode 100644 index 0000000000000000000000000000000000000000..98035457c78706ae01c02273ae1ab458b4ca140d --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/_precompute/gammainc_asy.py @@ -0,0 +1,116 @@ +""" +Precompute coefficients of Temme's asymptotic expansion for gammainc. + +This takes about 8 hours to run on a 2.3 GHz Macbook Pro with 4GB ram. + +Sources: +[1] NIST, "Digital Library of Mathematical Functions", + https://dlmf.nist.gov/ + +""" +import os +from scipy.special._precompute.utils import lagrange_inversion + +try: + import mpmath as mp +except ImportError: + pass + + +def compute_a(n): + """a_k from DLMF 5.11.6""" + a = [mp.sqrt(2)/2] + for k in range(1, n): + ak = a[-1]/k + for j in range(1, len(a)): + ak -= a[j]*a[-j]/(j + 1) + ak /= a[0]*(1 + mp.mpf(1)/(k + 1)) + a.append(ak) + return a + + +def compute_g(n): + """g_k from DLMF 5.11.3/5.11.5""" + a = compute_a(2*n) + g = [mp.sqrt(2)*mp.rf(0.5, k)*a[2*k] for k in range(n)] + return g + + +def eta(lam): + """Function from DLMF 8.12.1 shifted to be centered at 0.""" + if lam > 0: + return mp.sqrt(2*(lam - mp.log(lam + 1))) + elif lam < 0: + return -mp.sqrt(2*(lam - mp.log(lam + 1))) + else: + return 0 + + +def compute_alpha(n): + """alpha_n from DLMF 8.12.13""" + coeffs = mp.taylor(eta, 0, n - 1) + return lagrange_inversion(coeffs) + + +def compute_d(K, N): + """d_{k, n} from DLMF 8.12.12""" + M = N + 2*K + d0 = [-mp.mpf(1)/3] + alpha = compute_alpha(M + 2) + for n in range(1, M): + d0.append((n + 2)*alpha[n+2]) + d = [d0] + g = compute_g(K) + for k in range(1, K): + dk = [] + for n in range(M - 2*k): + dk.append((-1)**k*g[k]*d[0][n] + (n + 2)*d[k-1][n+2]) + d.append(dk) + for k in range(K): + d[k] = d[k][:N] + return d + + +header = \ +r"""/* This file was automatically generated by _precomp/gammainc.py. + * Do not edit it manually! + */ + +#ifndef IGAM_H +#define IGAM_H + +#define K {} +#define N {} + +static const double d[K][N] = +{{""" + +footer = \ +r""" +#endif +""" + + +def main(): + print(__doc__) + K = 25 + N = 25 + with mp.workdps(50): + d = compute_d(K, N) + fn = os.path.join(os.path.dirname(__file__), '..', 'cephes', 'igam.h') + with open(fn + '.new', 'w') as f: + f.write(header.format(K, N)) + for k, row in enumerate(d): + row = [mp.nstr(x, 17, min_fixed=0, max_fixed=0) for x in row] + f.write('{') + f.write(", ".join(row)) + if k < K - 1: + f.write('},\n') + else: + f.write('}};\n') + f.write(footer) + os.rename(fn + '.new', fn) + + +if __name__ == "__main__": + main() diff --git a/venv/lib/python3.12/site-packages/scipy/special/_precompute/gammainc_data.py b/venv/lib/python3.12/site-packages/scipy/special/_precompute/gammainc_data.py new file mode 100644 index 0000000000000000000000000000000000000000..ebbe39f6159fb80c424dbb38eedeba46bd8cccf2 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/_precompute/gammainc_data.py @@ -0,0 +1,124 @@ +"""Compute gammainc and gammaincc for large arguments and parameters +and save the values to data files for use in tests. We can't just +compare to mpmath's gammainc in test_mpmath.TestSystematic because it +would take too long. + +Note that mpmath's gammainc is computed using hypercomb, but since it +doesn't allow the user to increase the maximum number of terms used in +the series it doesn't converge for many arguments. To get around this +we copy the mpmath implementation but use more terms. + +This takes about 17 minutes to run on a 2.3 GHz Macbook Pro with 4GB +ram. + +Sources: +[1] Fredrik Johansson and others. mpmath: a Python library for + arbitrary-precision floating-point arithmetic (version 0.19), + December 2013. http://mpmath.org/. + +""" +import os +from time import time +import numpy as np +from numpy import pi + +from scipy.special._mptestutils import mpf2float + +try: + import mpmath as mp +except ImportError: + pass + + +def gammainc(a, x, dps=50, maxterms=10**8): + """Compute gammainc exactly like mpmath does but allow for more + summands in hypercomb. See + + mpmath/functions/expintegrals.py#L134 + + in the mpmath GitHub repository. + + """ + with mp.workdps(dps): + z, a, b = mp.mpf(a), mp.mpf(x), mp.mpf(x) + G = [z] + negb = mp.fneg(b, exact=True) + + def h(z): + T1 = [mp.exp(negb), b, z], [1, z, -1], [], G, [1], [1+z], b + return (T1,) + + res = mp.hypercomb(h, [z], maxterms=maxterms) + return mpf2float(res) + + +def gammaincc(a, x, dps=50, maxterms=10**8): + """Compute gammaincc exactly like mpmath does but allow for more + terms in hypercomb. See + + mpmath/functions/expintegrals.py#L187 + + in the mpmath GitHub repository. + + """ + with mp.workdps(dps): + z, a = a, x + + if mp.isint(z): + try: + # mpmath has a fast integer path + return mpf2float(mp.gammainc(z, a=a, regularized=True)) + except mp.libmp.NoConvergence: + pass + nega = mp.fneg(a, exact=True) + G = [z] + # Use 2F0 series when possible; fall back to lower gamma representation + try: + def h(z): + r = z-1 + return [([mp.exp(nega), a], [1, r], [], G, [1, -r], [], 1/nega)] + return mpf2float(mp.hypercomb(h, [z], force_series=True)) + except mp.libmp.NoConvergence: + def h(z): + T1 = [], [1, z-1], [z], G, [], [], 0 + T2 = [-mp.exp(nega), a, z], [1, z, -1], [], G, [1], [1+z], a + return T1, T2 + return mpf2float(mp.hypercomb(h, [z], maxterms=maxterms)) + + +def main(): + t0 = time() + # It would be nice to have data for larger values, but either this + # requires prohibitively large precision (dps > 800) or mpmath has + # a bug. For example, gammainc(1e20, 1e20, dps=800) returns a + # value around 0.03, while the true value should be close to 0.5 + # (DLMF 8.12.15). + print(__doc__) + pwd = os.path.dirname(__file__) + r = np.logspace(4, 14, 30) + ltheta = np.logspace(np.log10(pi/4), np.log10(np.arctan(0.6)), 30) + utheta = np.logspace(np.log10(pi/4), np.log10(np.arctan(1.4)), 30) + + regimes = [(gammainc, ltheta), (gammaincc, utheta)] + for func, theta in regimes: + rg, thetag = np.meshgrid(r, theta) + a, x = rg*np.cos(thetag), rg*np.sin(thetag) + a, x = a.flatten(), x.flatten() + dataset = [] + for i, (a0, x0) in enumerate(zip(a, x)): + if func == gammaincc: + # Exploit the fast integer path in gammaincc whenever + # possible so that the computation doesn't take too + # long + a0, x0 = np.floor(a0), np.floor(x0) + dataset.append((a0, x0, func(a0, x0))) + dataset = np.array(dataset) + filename = os.path.join(pwd, '..', 'tests', 'data', 'local', + f'{func.__name__}.txt') + np.savetxt(filename, dataset) + + print(f"{(time() - t0)/60} minutes elapsed") + + +if __name__ == "__main__": + main() diff --git a/venv/lib/python3.12/site-packages/scipy/special/_precompute/hyp2f1_data.py b/venv/lib/python3.12/site-packages/scipy/special/_precompute/hyp2f1_data.py new file mode 100644 index 0000000000000000000000000000000000000000..c4adf14f49184bf75048a28a823909d24e778e04 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/_precompute/hyp2f1_data.py @@ -0,0 +1,484 @@ +"""This script evaluates scipy's implementation of hyp2f1 against mpmath's. + +Author: Albert Steppi + +This script is long running and generates a large output file. With default +arguments, the generated file is roughly 700MB in size and it takes around +40 minutes using an Intel(R) Core(TM) i5-8250U CPU with n_jobs set to 8 +(full utilization). There are optional arguments which can be used to restrict +(or enlarge) the computations performed. These are described below. +The output of this script can be analyzed to identify suitable test cases and +to find parameter and argument regions where hyp2f1 needs to be improved. + +The script has one mandatory positional argument for specifying the path to +the location where the output file is to be placed, and 4 optional arguments +--n_jobs, --grid_size, --regions, and --parameter_groups. --n_jobs specifies +the number of processes to use if running in parallel. The default value is 1. +The other optional arguments are explained below. + +Produces a tab separated values file with 11 columns. The first four columns +contain the parameters a, b, c and the argument z. The next two contain |z| and +a region code for which region of the complex plane belongs to. The regions are + + 0) z == 1 + 1) |z| < 0.9 and real(z) >= 0 + 2) |z| <= 1 and real(z) < 0 + 3) 0.9 <= |z| <= 1 and |1 - z| < 0.9: + 4) 0.9 <= |z| <= 1 and |1 - z| >= 0.9 and real(z) >= 0: + 5) 1 < |z| < 1.1 and |1 - z| >= 0.9 and real(z) >= 0 + 6) |z| > 1 and not in 5) + +The --regions optional argument allows the user to specify a list of regions +to which computation will be restricted. + +Parameters a, b, c are taken from a 10 * 10 * 10 grid with values at + + -16, -8, -4, -2, -1, 1, 2, 4, 8, 16 + +with random perturbations applied. + +There are 9 parameter groups handling the following cases. + + 1) A, B, C, B - A, C - A, C - B, C - A - B all non-integral. + 2) B - A integral + 3) C - A integral + 4) C - B integral + 5) C - A - B integral + 6) A integral + 7) B integral + 8) C integral + 9) Wider range with c - a - b > 0. + +The seventh column of the output file is an integer between 1 and 8 specifying +the parameter group as above. + +The --parameter_groups optional argument allows the user to specify a list of +parameter groups to which computation will be restricted. + +The argument z is taken from a grid in the box + -box_size <= real(z) <= box_size, -box_size <= imag(z) <= box_size. +with grid size specified using the optional command line argument --grid_size, +and box_size specified with the command line argument --box_size. +The default value of grid_size is 20 and the default value of box_size is 2.0, +yielding a 20 * 20 grid in the box with corners -2-2j, -2+2j, 2-2j, 2+2j. + +The final four columns have the expected value of hyp2f1 for the given +parameters and argument as calculated with mpmath, the observed value +calculated with scipy's hyp2f1, the relative error, and the absolute error. + +As special cases of hyp2f1 are moved from the original Fortran implementation +into Cython, this script can be used to ensure that no regressions occur and +to point out where improvements are needed. +""" + + +import os +import csv +import argparse +import numpy as np +from itertools import product +from multiprocessing import Pool + + +from scipy.special import hyp2f1 +from scipy.special.tests.test_hyp2f1 import mp_hyp2f1 + + +def get_region(z): + """Assign numbers for regions where hyp2f1 must be handled differently.""" + if z == 1 + 0j: + return 0 + elif abs(z) < 0.9 and z.real >= 0: + return 1 + elif abs(z) <= 1 and z.real < 0: + return 2 + elif 0.9 <= abs(z) <= 1 and abs(1 - z) < 0.9: + return 3 + elif 0.9 <= abs(z) <= 1 and abs(1 - z) >= 0.9: + return 4 + elif 1 < abs(z) < 1.1 and abs(1 - z) >= 0.9 and z.real >= 0: + return 5 + else: + return 6 + + +def get_result(a, b, c, z, group): + """Get results for given parameter and value combination.""" + expected, observed = mp_hyp2f1(a, b, c, z), hyp2f1(a, b, c, z) + if ( + np.isnan(observed) and np.isnan(expected) or + expected == observed + ): + relative_error = 0.0 + absolute_error = 0.0 + elif np.isnan(observed): + # Set error to infinity if result is nan when not expected to be. + # Makes results easier to interpret. + relative_error = float("inf") + absolute_error = float("inf") + else: + absolute_error = abs(expected - observed) + relative_error = absolute_error / abs(expected) + + return ( + a, + b, + c, + z, + abs(z), + get_region(z), + group, + expected, + observed, + relative_error, + absolute_error, + ) + + +def get_result_no_mp(a, b, c, z, group): + """Get results for given parameter and value combination.""" + expected, observed = complex('nan'), hyp2f1(a, b, c, z) + relative_error, absolute_error = float('nan'), float('nan') + return ( + a, + b, + c, + z, + abs(z), + get_region(z), + group, + expected, + observed, + relative_error, + absolute_error, + ) + + +def get_results(params, Z, n_jobs=1, compute_mp=True): + """Batch compute results for multiple parameter and argument values. + + Parameters + ---------- + params : iterable + iterable of tuples of floats (a, b, c) specifying parameter values + a, b, c for hyp2f1 + Z : iterable of complex + Arguments at which to evaluate hyp2f1 + n_jobs : Optional[int] + Number of jobs for parallel execution. + + Returns + ------- + list + List of tuples of results values. See return value in source code + of `get_result`. + """ + input_ = ( + (a, b, c, z, group) for (a, b, c, group), z in product(params, Z) + ) + + with Pool(n_jobs) as pool: + rows = pool.starmap( + get_result if compute_mp else get_result_no_mp, + input_ + ) + return rows + + +def _make_hyp2f1_test_case(a, b, c, z, rtol): + """Generate string for single test case as used in test_hyp2f1.py.""" + expected = mp_hyp2f1(a, b, c, z) + return ( + " pytest.param(\n" + " Hyp2f1TestCase(\n" + f" a={a},\n" + f" b={b},\n" + f" c={c},\n" + f" z={z},\n" + f" expected={expected},\n" + f" rtol={rtol},\n" + " ),\n" + " )," + ) + + +def make_hyp2f1_test_cases(rows): + """Generate string for a list of test cases for test_hyp2f1.py. + + Parameters + ---------- + rows : list + List of lists of the form [a, b, c, z, rtol] where a, b, c, z are + parameters and the argument for hyp2f1 and rtol is an expected + relative error for the associated test case. + + Returns + ------- + str + String for a list of test cases. The output string can be printed + or saved to a file and then copied into an argument for + `pytest.mark.parameterize` within `scipy.special.tests.test_hyp2f1.py`. + """ + result = "[\n" + result += '\n'.join( + _make_hyp2f1_test_case(a, b, c, z, rtol) + for a, b, c, z, rtol in rows + ) + result += "\n]" + return result + + +def main( + outpath, + n_jobs=1, + box_size=2.0, + grid_size=20, + regions=None, + parameter_groups=None, + compute_mp=True, +): + outpath = os.path.realpath(os.path.expanduser(outpath)) + + random_state = np.random.RandomState(1234) + # Parameters a, b, c selected near these values. + root_params = np.array( + [-16, -8, -4, -2, -1, 1, 2, 4, 8, 16] + ) + # Perturbations to apply to root values. + perturbations = 0.1 * random_state.random_sample( + size=(3, len(root_params)) + ) + + params = [] + # Parameter group 1 + # ----------------- + # No integer differences. This has been confirmed for the above seed. + A = root_params + perturbations[0, :] + B = root_params + perturbations[1, :] + C = root_params + perturbations[2, :] + params.extend( + sorted( + ((a, b, c, 1) for a, b, c in product(A, B, C)), + key=lambda x: max(abs(x[0]), abs(x[1])), + ) + ) + + # Parameter group 2 + # ----------------- + # B - A an integer + A = root_params + 0.5 + B = root_params + 0.5 + C = root_params + perturbations[1, :] + params.extend( + sorted( + ((a, b, c, 2) for a, b, c in product(A, B, C)), + key=lambda x: max(abs(x[0]), abs(x[1])), + ) + ) + + # Parameter group 3 + # ----------------- + # C - A an integer + A = root_params + 0.5 + B = root_params + perturbations[1, :] + C = root_params + 0.5 + params.extend( + sorted( + ((a, b, c, 3) for a, b, c in product(A, B, C)), + key=lambda x: max(abs(x[0]), abs(x[1])), + ) + ) + + # Parameter group 4 + # ----------------- + # C - B an integer + A = root_params + perturbations[0, :] + B = root_params + 0.5 + C = root_params + 0.5 + params.extend( + sorted( + ((a, b, c, 4) for a, b, c in product(A, B, C)), + key=lambda x: max(abs(x[0]), abs(x[1])), + ) + ) + + # Parameter group 5 + # ----------------- + # C - A - B an integer + A = root_params + 0.25 + B = root_params + 0.25 + C = root_params + 0.5 + params.extend( + sorted( + ((a, b, c, 5) for a, b, c in product(A, B, C)), + key=lambda x: max(abs(x[0]), abs(x[1])), + ) + ) + + # Parameter group 6 + # ----------------- + # A an integer + A = root_params + B = root_params + perturbations[0, :] + C = root_params + perturbations[1, :] + params.extend( + sorted( + ((a, b, c, 6) for a, b, c in product(A, B, C)), + key=lambda x: max(abs(x[0]), abs(x[1])), + ) + ) + + # Parameter group 7 + # ----------------- + # B an integer + A = root_params + perturbations[0, :] + B = root_params + C = root_params + perturbations[1, :] + params.extend( + sorted( + ((a, b, c, 7) for a, b, c in product(A, B, C)), + key=lambda x: max(abs(x[0]), abs(x[1])), + ) + ) + + # Parameter group 8 + # ----------------- + # C an integer + A = root_params + perturbations[0, :] + B = root_params + perturbations[1, :] + C = root_params + params.extend( + sorted( + ((a, b, c, 8) for a, b, c in product(A, B, C)), + key=lambda x: max(abs(x[0]), abs(x[1])), + ) + ) + + # Parameter group 9 + # ----------------- + # Wide range of magnitudes, c - a - b > 0. + phi = (1 + np.sqrt(5))/2 + P = phi**np.arange(16) + P = np.hstack([-P, P]) + group_9_params = sorted( + ( + (a, b, c, 9) for a, b, c in product(P, P, P) if c - a - b > 0 + ), + key=lambda x: max(abs(x[0]), abs(x[1])), + ) + + if parameter_groups is not None: + # Group 9 params only used if specified in arguments. + params.extend(group_9_params) + params = [ + (a, b, c, group) for a, b, c, group in params + if group in parameter_groups + ] + + # grid_size * grid_size grid in box with corners + # -2 - 2j, -2 + 2j, 2 - 2j, 2 + 2j + X, Y = np.meshgrid( + np.linspace(-box_size, box_size, grid_size), + np.linspace(-box_size, box_size, grid_size) + ) + Z = X + Y * 1j + Z = Z.flatten().tolist() + # Add z = 1 + 0j (region 0). + Z.append(1 + 0j) + if regions is not None: + Z = [z for z in Z if get_region(z) in regions] + + # Evaluate scipy and mpmath's hyp2f1 for all parameter combinations + # above against all arguments in the grid Z + rows = get_results(params, Z, n_jobs=n_jobs, compute_mp=compute_mp) + + with open(outpath, "w", newline="") as f: + writer = csv.writer(f, delimiter="\t") + writer.writerow( + [ + "a", + "b", + "c", + "z", + "|z|", + "region", + "parameter_group", + "expected", # mpmath's hyp2f1 + "observed", # scipy's hyp2f1 + "relative_error", + "absolute_error", + ] + ) + for row in rows: + writer.writerow(row) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Test scipy's hyp2f1 against mpmath's on a grid in the" + " complex plane over a grid of parameter values. Saves output to file" + " specified in positional argument \"outpath\"." + " Caution: With default arguments, the generated output file is" + " roughly 700MB in size. Script may take several hours to finish if" + " \"--n_jobs\" is set to 1." + ) + parser.add_argument( + "outpath", type=str, help="Path to output tsv file." + ) + parser.add_argument( + "--n_jobs", + type=int, + default=1, + help="Number of jobs for multiprocessing.", + ) + parser.add_argument( + "--box_size", + type=float, + default=2.0, + help="hyp2f1 is evaluated in box of side_length 2*box_size centered" + " at the origin." + ) + parser.add_argument( + "--grid_size", + type=int, + default=20, + help="hyp2f1 is evaluated on grid_size * grid_size grid in box of side" + " length 2*box_size centered at the origin." + ) + parser.add_argument( + "--parameter_groups", + type=int, + nargs='+', + default=None, + help="Restrict to supplied parameter groups. See the Docstring for" + " this module for more info on parameter groups. Calculate for all" + " parameter groups by default." + ) + parser.add_argument( + "--regions", + type=int, + nargs='+', + default=None, + help="Restrict to argument z only within the supplied regions. See" + " the Docstring for this module for more info on regions. Calculate" + " for all regions by default." + ) + parser.add_argument( + "--no_mp", + action='store_true', + help="If this flag is set, do not compute results with mpmath. Saves" + " time if results have already been computed elsewhere. Fills in" + " \"expected\" column with None values." + ) + args = parser.parse_args() + compute_mp = not args.no_mp + print(args.parameter_groups) + main( + args.outpath, + n_jobs=args.n_jobs, + box_size=args.box_size, + grid_size=args.grid_size, + parameter_groups=args.parameter_groups, + regions=args.regions, + compute_mp=compute_mp, + ) diff --git a/venv/lib/python3.12/site-packages/scipy/special/_precompute/lambertw.py b/venv/lib/python3.12/site-packages/scipy/special/_precompute/lambertw.py new file mode 100644 index 0000000000000000000000000000000000000000..1fdbf35b2cf85f1f7a6e73579546ed5cfe508fa6 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/_precompute/lambertw.py @@ -0,0 +1,68 @@ +"""Compute a Pade approximation for the principal branch of the +Lambert W function around 0 and compare it to various other +approximations. + +""" +import numpy as np + +try: + import mpmath + import matplotlib.pyplot as plt +except ImportError: + pass + + +def lambertw_pade(): + derivs = [mpmath.diff(mpmath.lambertw, 0, n=n) for n in range(6)] + p, q = mpmath.pade(derivs, 3, 2) + return p, q + + +def main(): + print(__doc__) + with mpmath.workdps(50): + p, q = lambertw_pade() + p, q = p[::-1], q[::-1] + print(f"p = {p}") + print(f"q = {q}") + + x, y = np.linspace(-1.5, 1.5, 75), np.linspace(-1.5, 1.5, 75) + x, y = np.meshgrid(x, y) + z = x + 1j*y + lambertw_std = [] + for z0 in z.flatten(): + lambertw_std.append(complex(mpmath.lambertw(z0))) + lambertw_std = np.array(lambertw_std).reshape(x.shape) + + fig, axes = plt.subplots(nrows=3, ncols=1) + # Compare Pade approximation to true result + p = np.array([float(p0) for p0 in p]) + q = np.array([float(q0) for q0 in q]) + pade_approx = np.polyval(p, z)/np.polyval(q, z) + pade_err = abs(pade_approx - lambertw_std) + axes[0].pcolormesh(x, y, pade_err) + # Compare two terms of asymptotic series to true result + asy_approx = np.log(z) - np.log(np.log(z)) + asy_err = abs(asy_approx - lambertw_std) + axes[1].pcolormesh(x, y, asy_err) + # Compare two terms of the series around the branch point to the + # true result + p = np.sqrt(2*(np.exp(1)*z + 1)) + series_approx = -1 + p - p**2/3 + series_err = abs(series_approx - lambertw_std) + im = axes[2].pcolormesh(x, y, series_err) + + fig.colorbar(im, ax=axes.ravel().tolist()) + plt.show() + + fig, ax = plt.subplots(nrows=1, ncols=1) + pade_better = pade_err < asy_err + im = ax.pcolormesh(x, y, pade_better) + t = np.linspace(-0.3, 0.3) + ax.plot(-2.5*abs(t) - 0.2, t, 'r') + fig.colorbar(im, ax=ax) + plt.show() + + +if __name__ == '__main__': + main() diff --git a/venv/lib/python3.12/site-packages/scipy/special/_precompute/loggamma.py b/venv/lib/python3.12/site-packages/scipy/special/_precompute/loggamma.py new file mode 100644 index 0000000000000000000000000000000000000000..74051ac7b46c70dc01919a362d05a8bbbe11333a --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/_precompute/loggamma.py @@ -0,0 +1,43 @@ +"""Precompute series coefficients for log-Gamma.""" + +try: + import mpmath +except ImportError: + pass + + +def stirling_series(N): + with mpmath.workdps(100): + coeffs = [mpmath.bernoulli(2*n)/(2*n*(2*n - 1)) + for n in range(1, N + 1)] + return coeffs + + +def taylor_series_at_1(N): + coeffs = [] + with mpmath.workdps(100): + coeffs.append(-mpmath.euler) + for n in range(2, N + 1): + coeffs.append((-1)**n*mpmath.zeta(n)/n) + return coeffs + + +def main(): + print(__doc__) + print() + stirling_coeffs = [mpmath.nstr(x, 20, min_fixed=0, max_fixed=0) + for x in stirling_series(8)[::-1]] + taylor_coeffs = [mpmath.nstr(x, 20, min_fixed=0, max_fixed=0) + for x in taylor_series_at_1(23)[::-1]] + print("Stirling series coefficients") + print("----------------------------") + print("\n".join(stirling_coeffs)) + print() + print("Taylor series coefficients") + print("--------------------------") + print("\n".join(taylor_coeffs)) + print() + + +if __name__ == '__main__': + main() diff --git a/venv/lib/python3.12/site-packages/scipy/special/_precompute/struve_convergence.py b/venv/lib/python3.12/site-packages/scipy/special/_precompute/struve_convergence.py new file mode 100644 index 0000000000000000000000000000000000000000..dbf6009368540dbf603b61f5b72510f0acd1a65b --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/_precompute/struve_convergence.py @@ -0,0 +1,131 @@ +""" +Convergence regions of the expansions used in ``struve.c`` + +Note that for v >> z both functions tend rapidly to 0, +and for v << -z, they tend to infinity. + +The floating-point functions over/underflow in the lower left and right +corners of the figure. + + +Figure legend +============= + +Red region + Power series is close (1e-12) to the mpmath result + +Blue region + Asymptotic series is close to the mpmath result + +Green region + Bessel series is close to the mpmath result + +Dotted colored lines + Boundaries of the regions + +Solid colored lines + Boundaries estimated by the routine itself. These will be used + for determining which of the results to use. + +Black dashed line + The line z = 0.7*|v| + 12 + +""" +import numpy as np +import matplotlib.pyplot as plt + +import mpmath + + +def err_metric(a, b, atol=1e-290): + m = abs(a - b) / (atol + abs(b)) + m[np.isinf(b) & (a == b)] = 0 + return m + + +def do_plot(is_h=True): + from scipy.special._ufuncs import (_struve_power_series, + _struve_asymp_large_z, + _struve_bessel_series) + + vs = np.linspace(-1000, 1000, 91) + zs = np.sort(np.r_[1e-5, 1.0, np.linspace(0, 700, 91)[1:]]) + + rp = _struve_power_series(vs[:,None], zs[None,:], is_h) + ra = _struve_asymp_large_z(vs[:,None], zs[None,:], is_h) + rb = _struve_bessel_series(vs[:,None], zs[None,:], is_h) + + mpmath.mp.dps = 50 + if is_h: + def sh(v, z): + return float(mpmath.struveh(mpmath.mpf(v), mpmath.mpf(z))) + else: + def sh(v, z): + return float(mpmath.struvel(mpmath.mpf(v), mpmath.mpf(z))) + ex = np.vectorize(sh, otypes='d')(vs[:,None], zs[None,:]) + + err_a = err_metric(ra[0], ex) + 1e-300 + err_p = err_metric(rp[0], ex) + 1e-300 + err_b = err_metric(rb[0], ex) + 1e-300 + + err_est_a = abs(ra[1]/ra[0]) + err_est_p = abs(rp[1]/rp[0]) + err_est_b = abs(rb[1]/rb[0]) + + z_cutoff = 0.7*abs(vs) + 12 + + levels = [-1000, -12] + + plt.cla() + + plt.hold(1) + plt.contourf(vs, zs, np.log10(err_p).T, + levels=levels, colors=['r', 'r'], alpha=0.1) + plt.contourf(vs, zs, np.log10(err_a).T, + levels=levels, colors=['b', 'b'], alpha=0.1) + plt.contourf(vs, zs, np.log10(err_b).T, + levels=levels, colors=['g', 'g'], alpha=0.1) + + plt.contour(vs, zs, np.log10(err_p).T, + levels=levels, colors=['r', 'r'], linestyles=[':', ':']) + plt.contour(vs, zs, np.log10(err_a).T, + levels=levels, colors=['b', 'b'], linestyles=[':', ':']) + plt.contour(vs, zs, np.log10(err_b).T, + levels=levels, colors=['g', 'g'], linestyles=[':', ':']) + + lp = plt.contour(vs, zs, np.log10(err_est_p).T, + levels=levels, colors=['r', 'r'], linestyles=['-', '-']) + la = plt.contour(vs, zs, np.log10(err_est_a).T, + levels=levels, colors=['b', 'b'], linestyles=['-', '-']) + lb = plt.contour(vs, zs, np.log10(err_est_b).T, + levels=levels, colors=['g', 'g'], linestyles=['-', '-']) + + plt.clabel(lp, fmt={-1000: 'P', -12: 'P'}) + plt.clabel(la, fmt={-1000: 'A', -12: 'A'}) + plt.clabel(lb, fmt={-1000: 'B', -12: 'B'}) + + plt.plot(vs, z_cutoff, 'k--') + + plt.xlim(vs.min(), vs.max()) + plt.ylim(zs.min(), zs.max()) + + plt.xlabel('v') + plt.ylabel('z') + + +def main(): + plt.clf() + plt.subplot(121) + do_plot(True) + plt.title('Struve H') + + plt.subplot(122) + do_plot(False) + plt.title('Struve L') + + plt.savefig('struve_convergence.png') + plt.show() + + +if __name__ == "__main__": + main() diff --git a/venv/lib/python3.12/site-packages/scipy/special/_precompute/utils.py b/venv/lib/python3.12/site-packages/scipy/special/_precompute/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..55cf4083ed5e5a6628fd3316c02ce1a5ce21a92c --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/_precompute/utils.py @@ -0,0 +1,38 @@ +try: + import mpmath as mp +except ImportError: + pass + +try: + from sympy.abc import x +except ImportError: + pass + + +def lagrange_inversion(a): + """Given a series + + f(x) = a[1]*x + a[2]*x**2 + ... + a[n-1]*x**(n - 1), + + use the Lagrange inversion formula to compute a series + + g(x) = b[1]*x + b[2]*x**2 + ... + b[n-1]*x**(n - 1) + + so that f(g(x)) = g(f(x)) = x mod x**n. We must have a[0] = 0, so + necessarily b[0] = 0 too. + + The algorithm is naive and could be improved, but speed isn't an + issue here and it's easy to read. + + """ + n = len(a) + f = sum(a[i]*x**i for i in range(n)) + h = (x/f).series(x, 0, n).removeO() + hpower = [h**0] + for k in range(n): + hpower.append((hpower[-1]*h).expand()) + b = [mp.mpf(0)] + for k in range(1, n): + b.append(hpower[k].coeff(x, k - 1)/k) + b = [mp.mpf(x) for x in b] + return b diff --git a/venv/lib/python3.12/site-packages/scipy/special/_precompute/wright_bessel.py b/venv/lib/python3.12/site-packages/scipy/special/_precompute/wright_bessel.py new file mode 100644 index 0000000000000000000000000000000000000000..51d56b1cd5c47c7ef005d21aad9827a1e85ec0d9 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/_precompute/wright_bessel.py @@ -0,0 +1,342 @@ +"""Precompute coefficients of several series expansions +of Wright's generalized Bessel function Phi(a, b, x). + +See https://dlmf.nist.gov/10.46.E1 with rho=a, beta=b, z=x. +""" +from argparse import ArgumentParser, RawTextHelpFormatter +import numpy as np +from scipy.integrate import quad +from scipy.optimize import minimize_scalar, curve_fit +from time import time + +try: + import sympy + from sympy import EulerGamma, Rational, S, Sum, \ + factorial, gamma, gammasimp, pi, polygamma, symbols, zeta + from sympy.polys.polyfuncs import horner +except ImportError: + pass + + +def series_small_a(): + """Tylor series expansion of Phi(a, b, x) in a=0 up to order 5. + """ + order = 5 + a, b, x, k = symbols("a b x k") + A = [] # terms with a + X = [] # terms with x + B = [] # terms with b (polygammas) + # Phi(a, b, x) = exp(x)/gamma(b) * sum(A[i] * X[i] * B[i]) + expression = Sum(x**k/factorial(k)/gamma(a*k+b), (k, 0, S.Infinity)) + expression = gamma(b)/sympy.exp(x) * expression + + # nth term of taylor series in a=0: a^n/n! * (d^n Phi(a, b, x)/da^n at a=0) + for n in range(0, order+1): + term = expression.diff(a, n).subs(a, 0).simplify().doit() + # set the whole bracket involving polygammas to 1 + x_part = (term.subs(polygamma(0, b), 1) + .replace(polygamma, lambda *args: 0)) + # sign convention: x part always positive + x_part *= (-1)**n + + A.append(a**n/factorial(n)) + X.append(horner(x_part)) + B.append(horner((term/x_part).simplify())) + + s = "Tylor series expansion of Phi(a, b, x) in a=0 up to order 5.\n" + s += "Phi(a, b, x) = exp(x)/gamma(b) * sum(A[i] * X[i] * B[i], i=0..5)\n" + for name, c in zip(['A', 'X', 'B'], [A, X, B]): + for i in range(len(c)): + s += f"\n{name}[{i}] = " + str(c[i]) + return s + + +# expansion of digamma +def dg_series(z, n): + """Symbolic expansion of digamma(z) in z=0 to order n. + + See https://dlmf.nist.gov/5.7.E4 and with https://dlmf.nist.gov/5.5.E2 + """ + k = symbols("k") + return -1/z - EulerGamma + \ + sympy.summation((-1)**k * zeta(k) * z**(k-1), (k, 2, n+1)) + + +def pg_series(k, z, n): + """Symbolic expansion of polygamma(k, z) in z=0 to order n.""" + return sympy.diff(dg_series(z, n+k), z, k) + + +def series_small_a_small_b(): + """Tylor series expansion of Phi(a, b, x) in a=0 and b=0 up to order 5. + + Be aware of cancellation of poles in b=0 of digamma(b)/Gamma(b) and + polygamma functions. + + digamma(b)/Gamma(b) = -1 - 2*M_EG*b + O(b^2) + digamma(b)^2/Gamma(b) = 1/b + 3*M_EG + b*(-5/12*PI^2+7/2*M_EG^2) + O(b^2) + polygamma(1, b)/Gamma(b) = 1/b + M_EG + b*(1/12*PI^2 + 1/2*M_EG^2) + O(b^2) + and so on. + """ + order = 5 + a, b, x, k = symbols("a b x k") + M_PI, M_EG, M_Z3 = symbols("M_PI M_EG M_Z3") + c_subs = {pi: M_PI, EulerGamma: M_EG, zeta(3): M_Z3} + A = [] # terms with a + X = [] # terms with x + B = [] # terms with b (polygammas expanded) + C = [] # terms that generate B + # Phi(a, b, x) = exp(x) * sum(A[i] * X[i] * B[i]) + # B[0] = 1 + # B[k] = sum(C[k] * b**k/k!, k=0..) + # Note: C[k] can be obtained from a series expansion of 1/gamma(b). + expression = gamma(b)/sympy.exp(x) * \ + Sum(x**k/factorial(k)/gamma(a*k+b), (k, 0, S.Infinity)) + + # nth term of taylor series in a=0: a^n/n! * (d^n Phi(a, b, x)/da^n at a=0) + for n in range(0, order+1): + term = expression.diff(a, n).subs(a, 0).simplify().doit() + # set the whole bracket involving polygammas to 1 + x_part = (term.subs(polygamma(0, b), 1) + .replace(polygamma, lambda *args: 0)) + # sign convention: x part always positive + x_part *= (-1)**n + # expansion of polygamma part with 1/gamma(b) + pg_part = term/x_part/gamma(b) + if n >= 1: + # Note: highest term is digamma^n + pg_part = pg_part.replace(polygamma, + lambda k, x: pg_series(k, x, order+1+n)) + pg_part = (pg_part.series(b, 0, n=order+1-n) + .removeO() + .subs(polygamma(2, 1), -2*zeta(3)) + .simplify() + ) + + A.append(a**n/factorial(n)) + X.append(horner(x_part)) + B.append(pg_part) + + # Calculate C and put in the k! + C = sympy.Poly(B[1].subs(c_subs), b).coeffs() + C.reverse() + for i in range(len(C)): + C[i] = (C[i] * factorial(i)).simplify() + + s = "Tylor series expansion of Phi(a, b, x) in a=0 and b=0 up to order 5." + s += "\nPhi(a, b, x) = exp(x) * sum(A[i] * X[i] * B[i], i=0..5)\n" + s += "B[0] = 1\n" + s += "B[i] = sum(C[k+i-1] * b**k/k!, k=0..)\n" + s += "\nM_PI = pi" + s += "\nM_EG = EulerGamma" + s += "\nM_Z3 = zeta(3)" + for name, c in zip(['A', 'X'], [A, X]): + for i in range(len(c)): + s += f"\n{name}[{i}] = " + s += str(c[i]) + # For C, do also compute the values numerically + for i in range(len(C)): + s += f"\n# C[{i}] = " + s += str(C[i]) + s += f"\nC[{i}] = " + s += str(C[i].subs({M_EG: EulerGamma, M_PI: pi, M_Z3: zeta(3)}) + .evalf(17)) + + # Does B have the assumed structure? + s += "\n\nTest if B[i] does have the assumed structure." + s += "\nC[i] are derived from B[1] alone." + s += "\nTest B[2] == C[1] + b*C[2] + b^2/2*C[3] + b^3/6*C[4] + .." + test = sum([b**k/factorial(k) * C[k+1] for k in range(order-1)]) + test = (test - B[2].subs(c_subs)).simplify() + s += f"\ntest successful = {test==S(0)}" + s += "\nTest B[3] == C[2] + b*C[3] + b^2/2*C[4] + .." + test = sum([b**k/factorial(k) * C[k+2] for k in range(order-2)]) + test = (test - B[3].subs(c_subs)).simplify() + s += f"\ntest successful = {test==S(0)}" + return s + + +def asymptotic_series(): + """Asymptotic expansion for large x. + + Phi(a, b, x) ~ Z^(1/2-b) * exp((1+a)/a * Z) * sum_k (-1)^k * C_k / Z^k + Z = (a*x)^(1/(1+a)) + + Wright (1935) lists the coefficients C_0 and C_1 (he calls them a_0 and + a_1). With slightly different notation, Paris (2017) lists coefficients + c_k up to order k=3. + Paris (2017) uses ZP = (1+a)/a * Z (ZP = Z of Paris) and + C_k = C_0 * (-a/(1+a))^k * c_k + """ + order = 8 + + class g(sympy.Function): + """Helper function g according to Wright (1935) + + g(n, rho, v) = (1 + (rho+2)/3 * v + (rho+2)*(rho+3)/(2*3) * v^2 + ...) + + Note: Wright (1935) uses square root of above definition. + """ + nargs = 3 + + @classmethod + def eval(cls, n, rho, v): + if not n >= 0: + raise ValueError("must have n >= 0") + elif n == 0: + return 1 + else: + return g(n-1, rho, v) \ + + gammasimp(gamma(rho+2+n)/gamma(rho+2)) \ + / gammasimp(gamma(3+n)/gamma(3))*v**n + + class coef_C(sympy.Function): + """Calculate coefficients C_m for integer m. + + C_m is the coefficient of v^(2*m) in the Taylor expansion in v=0 of + Gamma(m+1/2)/(2*pi) * (2/(rho+1))^(m+1/2) * (1-v)^(-b) + * g(rho, v)^(-m-1/2) + """ + nargs = 3 + + @classmethod + def eval(cls, m, rho, beta): + if not m >= 0: + raise ValueError("must have m >= 0") + + v = symbols("v") + expression = (1-v)**(-beta) * g(2*m, rho, v)**(-m-Rational(1, 2)) + res = expression.diff(v, 2*m).subs(v, 0) / factorial(2*m) + res = res * (gamma(m + Rational(1, 2)) / (2*pi) + * (2/(rho+1))**(m + Rational(1, 2))) + return res + + # in order to have nice ordering/sorting of expressions, we set a = xa. + xa, b, xap1 = symbols("xa b xap1") + C0 = coef_C(0, xa, b) + # a1 = a(1, rho, beta) + s = "Asymptotic expansion for large x\n" + s += "Phi(a, b, x) = Z**(1/2-b) * exp((1+a)/a * Z) \n" + s += " * sum((-1)**k * C[k]/Z**k, k=0..6)\n\n" + s += "Z = pow(a * x, 1/(1+a))\n" + s += "A[k] = pow(a, k)\n" + s += "B[k] = pow(b, k)\n" + s += "Ap1[k] = pow(1+a, k)\n\n" + s += "C[0] = 1./sqrt(2. * M_PI * Ap1[1])\n" + for i in range(1, order+1): + expr = (coef_C(i, xa, b) / (C0/(1+xa)**i)).simplify() + factor = [x.denominator() for x in sympy.Poly(expr).coeffs()] + factor = sympy.lcm(factor) + expr = (expr * factor).simplify().collect(b, sympy.factor) + expr = expr.xreplace({xa+1: xap1}) + s += f"C[{i}] = C[0] / ({factor} * Ap1[{i}])\n" + s += f"C[{i}] *= {str(expr)}\n\n" + import re + re_a = re.compile(r'xa\*\*(\d+)') + s = re_a.sub(r'A[\1]', s) + re_b = re.compile(r'b\*\*(\d+)') + s = re_b.sub(r'B[\1]', s) + s = s.replace('xap1', 'Ap1[1]') + s = s.replace('xa', 'a') + # max integer = 2^31-1 = 2,147,483,647. Solution: Put a point after 10 + # or more digits. + re_digits = re.compile(r'(\d{10,})') + s = re_digits.sub(r'\1.', s) + return s + + +def optimal_epsilon_integral(): + """Fit optimal choice of epsilon for integral representation. + + The integrand of + int_0^pi P(eps, a, b, x, phi) * dphi + can exhibit oscillatory behaviour. It stems from the cosine of P and can be + minimized by minimizing the arc length of the argument + f(phi) = eps * sin(phi) - x * eps^(-a) * sin(a * phi) + (1 - b) * phi + of cos(f(phi)). + We minimize the arc length in eps for a grid of values (a, b, x) and fit a + parametric function to it. + """ + def fp(eps, a, b, x, phi): + """Derivative of f w.r.t. phi.""" + eps_a = np.power(1. * eps, -a) + return eps * np.cos(phi) - a * x * eps_a * np.cos(a * phi) + 1 - b + + def arclength(eps, a, b, x, epsrel=1e-2, limit=100): + """Compute Arc length of f. + + Note that the arc length of a function f from t0 to t1 is given by + int_t0^t1 sqrt(1 + f'(t)^2) dt + """ + return quad(lambda phi: np.sqrt(1 + fp(eps, a, b, x, phi)**2), + 0, np.pi, + epsrel=epsrel, limit=100)[0] + + # grid of minimal arc length values + data_a = [1e-3, 0.1, 0.5, 0.9, 1, 2, 4, 5, 6, 8] + data_b = [0, 1, 4, 7, 10] + data_x = [1, 1.5, 2, 4, 10, 20, 50, 100, 200, 500, 1e3, 5e3, 1e4] + data_a, data_b, data_x = np.meshgrid(data_a, data_b, data_x) + data_a, data_b, data_x = (data_a.flatten(), data_b.flatten(), + data_x.flatten()) + best_eps = [] + for i in range(data_x.size): + best_eps.append( + minimize_scalar(lambda eps: arclength(eps, data_a[i], data_b[i], + data_x[i]), + bounds=(1e-3, 1000), + method='Bounded', options={'xatol': 1e-3}).x + ) + best_eps = np.array(best_eps) + # pandas would be nice, but here a dictionary is enough + df = {'a': data_a, + 'b': data_b, + 'x': data_x, + 'eps': best_eps, + } + + def func(data, A0, A1, A2, A3, A4, A5): + """Compute parametric function to fit.""" + a = data['a'] + b = data['b'] + x = data['x'] + return (A0 * b * np.exp(-0.5 * a) + + np.exp(A1 + 1 / (1 + a) * np.log(x) - A2 * np.exp(-A3 * a) + + A4 / (1 + np.exp(A5 * a)))) + + func_params = list(curve_fit(func, df, df['eps'], method='trf')[0]) + + s = "Fit optimal eps for integrand P via minimal arc length\n" + s += "with parametric function:\n" + s += "optimal_eps = (A0 * b * exp(-a/2) + exp(A1 + 1 / (1 + a) * log(x)\n" + s += " - A2 * exp(-A3 * a) + A4 / (1 + exp(A5 * a)))\n\n" + s += "Fitted parameters A0 to A5 are:\n" + s += ', '.join([f'{x:.5g}' for x in func_params]) + return s + + +def main(): + t0 = time() + parser = ArgumentParser(description=__doc__, + formatter_class=RawTextHelpFormatter) + parser.add_argument('action', type=int, choices=[1, 2, 3, 4], + help='chose what expansion to precompute\n' + '1 : Series for small a\n' + '2 : Series for small a and small b\n' + '3 : Asymptotic series for large x\n' + ' This may take some time (>4h).\n' + '4 : Fit optimal eps for integral representation.' + ) + args = parser.parse_args() + + switch = {1: lambda: print(series_small_a()), + 2: lambda: print(series_small_a_small_b()), + 3: lambda: print(asymptotic_series()), + 4: lambda: print(optimal_epsilon_integral()) + } + switch.get(args.action, lambda: print("Invalid input."))() + print(f"\n{(time() - t0)/60:.1f} minutes elapsed.\n") + + +if __name__ == '__main__': + main() diff --git a/venv/lib/python3.12/site-packages/scipy/special/_precompute/wright_bessel_data.py b/venv/lib/python3.12/site-packages/scipy/special/_precompute/wright_bessel_data.py new file mode 100644 index 0000000000000000000000000000000000000000..1de9b4fe552ca9178c452194ab84af6ca5daac71 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/_precompute/wright_bessel_data.py @@ -0,0 +1,152 @@ +"""Compute a grid of values for Wright's generalized Bessel function +and save the values to data files for use in tests. Using mpmath directly in +tests would take too long. + +This takes about 10 minutes to run on a 2.7 GHz i7 Macbook Pro. +""" +from functools import lru_cache +import os +from time import time + +import numpy as np +from scipy.special._mptestutils import mpf2float + +try: + import mpmath as mp +except ImportError: + pass + +# exp_inf: smallest value x for which exp(x) == inf +exp_inf = 709.78271289338403 + + +# 64 Byte per value +@lru_cache(maxsize=100_000) +def rgamma_cached(x, dps): + with mp.workdps(dps): + return mp.rgamma(x) + + +def mp_wright_bessel(a, b, x, dps=50, maxterms=2000): + """Compute Wright's generalized Bessel function as Series with mpmath. + """ + with mp.workdps(dps): + a, b, x = mp.mpf(a), mp.mpf(b), mp.mpf(x) + res = mp.nsum(lambda k: x**k / mp.fac(k) + * rgamma_cached(a * k + b, dps=dps), + [0, mp.inf], + tol=dps, method='s', steps=[maxterms] + ) + return mpf2float(res) + + +def main(): + t0 = time() + print(__doc__) + pwd = os.path.dirname(__file__) + eps = np.finfo(float).eps * 100 + + a_range = np.array([eps, + 1e-4 * (1 - eps), 1e-4, 1e-4 * (1 + eps), + 1e-3 * (1 - eps), 1e-3, 1e-3 * (1 + eps), + 0.1, 0.5, + 1 * (1 - eps), 1, 1 * (1 + eps), + 1.5, 2, 4.999, 5, 10]) + b_range = np.array([0, eps, 1e-10, 1e-5, 0.1, 1, 2, 10, 20, 100]) + x_range = np.array([0, eps, 1 - eps, 1, 1 + eps, + 1.5, + 2 - eps, 2, 2 + eps, + 9 - eps, 9, 9 + eps, + 10 * (1 - eps), 10, 10 * (1 + eps), + 100 * (1 - eps), 100, 100 * (1 + eps), + 500, exp_inf, 1e3, 1e5, 1e10, 1e20]) + + a_range, b_range, x_range = np.meshgrid(a_range, b_range, x_range, + indexing='ij') + a_range = a_range.flatten() + b_range = b_range.flatten() + x_range = x_range.flatten() + + # filter out some values, especially too large x + bool_filter = ~((a_range < 5e-3) & (x_range >= exp_inf)) + bool_filter = bool_filter & ~((a_range < 0.2) & (x_range > exp_inf)) + bool_filter = bool_filter & ~((a_range < 0.5) & (x_range > 1e3)) + bool_filter = bool_filter & ~((a_range < 0.56) & (x_range > 5e3)) + bool_filter = bool_filter & ~((a_range < 1) & (x_range > 1e4)) + bool_filter = bool_filter & ~((a_range < 1.4) & (x_range > 1e5)) + bool_filter = bool_filter & ~((a_range < 1.8) & (x_range > 1e6)) + bool_filter = bool_filter & ~((a_range < 2.2) & (x_range > 1e7)) + bool_filter = bool_filter & ~((a_range < 2.5) & (x_range > 1e8)) + bool_filter = bool_filter & ~((a_range < 2.9) & (x_range > 1e9)) + bool_filter = bool_filter & ~((a_range < 3.3) & (x_range > 1e10)) + bool_filter = bool_filter & ~((a_range < 3.7) & (x_range > 1e11)) + bool_filter = bool_filter & ~((a_range < 4) & (x_range > 1e12)) + bool_filter = bool_filter & ~((a_range < 4.4) & (x_range > 1e13)) + bool_filter = bool_filter & ~((a_range < 4.7) & (x_range > 1e14)) + bool_filter = bool_filter & ~((a_range < 5.1) & (x_range > 1e15)) + bool_filter = bool_filter & ~((a_range < 5.4) & (x_range > 1e16)) + bool_filter = bool_filter & ~((a_range < 5.8) & (x_range > 1e17)) + bool_filter = bool_filter & ~((a_range < 6.2) & (x_range > 1e18)) + bool_filter = bool_filter & ~((a_range < 6.2) & (x_range > 1e18)) + bool_filter = bool_filter & ~((a_range < 6.5) & (x_range > 1e19)) + bool_filter = bool_filter & ~((a_range < 6.9) & (x_range > 1e20)) + + # filter out known values that do not meet the required numerical accuracy + # see test test_wright_data_grid_failures + failing = np.array([ + [0.1, 100, 709.7827128933841], + [0.5, 10, 709.7827128933841], + [0.5, 10, 1000], + [0.5, 100, 1000], + [1, 20, 100000], + [1, 100, 100000], + [1.0000000000000222, 20, 100000], + [1.0000000000000222, 100, 100000], + [1.5, 0, 500], + [1.5, 2.220446049250313e-14, 500], + [1.5, 1.e-10, 500], + [1.5, 1.e-05, 500], + [1.5, 0.1, 500], + [1.5, 20, 100000], + [1.5, 100, 100000], + ]).tolist() + + does_fail = np.full_like(a_range, False, dtype=bool) + for i in range(x_range.size): + if [a_range[i], b_range[i], x_range[i]] in failing: + does_fail[i] = True + + # filter and flatten + a_range = a_range[bool_filter] + b_range = b_range[bool_filter] + x_range = x_range[bool_filter] + does_fail = does_fail[bool_filter] + + dataset = [] + print(f"Computing {x_range.size} single points.") + print("Tests will fail for the following data points:") + for i in range(x_range.size): + a = a_range[i] + b = b_range[i] + x = x_range[i] + # take care of difficult corner cases + maxterms = 1000 + if a < 1e-6 and x >= exp_inf/10: + maxterms = 2000 + f = mp_wright_bessel(a, b, x, maxterms=maxterms) + if does_fail[i]: + print("failing data point a, b, x, value = " + f"[{a}, {b}, {x}, {f}]") + else: + dataset.append((a, b, x, f)) + dataset = np.array(dataset) + + filename = os.path.join(pwd, '..', 'tests', 'data', 'local', + 'wright_bessel.txt') + np.savetxt(filename, dataset) + + print(f"{(time() - t0)/60:.1f} minutes elapsed") + + +if __name__ == "__main__": + main() diff --git a/venv/lib/python3.12/site-packages/scipy/special/_precompute/wrightomega.py b/venv/lib/python3.12/site-packages/scipy/special/_precompute/wrightomega.py new file mode 100644 index 0000000000000000000000000000000000000000..0bcd0345a9c1b90c45b0e9e3340ab4da4ec5c6d7 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/_precompute/wrightomega.py @@ -0,0 +1,41 @@ +import numpy as np + +try: + import mpmath +except ImportError: + pass + + +def mpmath_wrightomega(x): + return mpmath.lambertw(mpmath.exp(x), mpmath.mpf('-0.5')) + + +def wrightomega_series_error(x): + series = x + desired = mpmath_wrightomega(x) + return abs(series - desired) / desired + + +def wrightomega_exp_error(x): + exponential_approx = mpmath.exp(x) + desired = mpmath_wrightomega(x) + return abs(exponential_approx - desired) / desired + + +def main(): + desired_error = 2 * np.finfo(float).eps + print('Series Error') + for x in [1e5, 1e10, 1e15, 1e20]: + with mpmath.workdps(100): + error = wrightomega_series_error(x) + print(x, error, error < desired_error) + + print('Exp error') + for x in [-10, -25, -50, -100, -200, -400, -700, -740]: + with mpmath.workdps(100): + error = wrightomega_exp_error(x) + print(x, error, error < desired_error) + + +if __name__ == '__main__': + main() diff --git a/venv/lib/python3.12/site-packages/scipy/special/_precompute/zetac.py b/venv/lib/python3.12/site-packages/scipy/special/_precompute/zetac.py new file mode 100644 index 0000000000000000000000000000000000000000..d408b1a2fffb6872287452923fcc9394adc13a7c --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/_precompute/zetac.py @@ -0,0 +1,27 @@ +"""Compute the Taylor series for zeta(x) - 1 around x = 0.""" +try: + import mpmath +except ImportError: + pass + + +def zetac_series(N): + coeffs = [] + with mpmath.workdps(100): + coeffs.append(-1.5) + for n in range(1, N): + coeff = mpmath.diff(mpmath.zeta, 0, n)/mpmath.factorial(n) + coeffs.append(coeff) + return coeffs + + +def main(): + print(__doc__) + coeffs = zetac_series(10) + coeffs = [mpmath.nstr(x, 20, min_fixed=0, max_fixed=0) + for x in coeffs] + print("\n".join(coeffs[::-1])) + + +if __name__ == '__main__': + main() diff --git a/venv/lib/python3.12/site-packages/scipy/special/_sf_error.py b/venv/lib/python3.12/site-packages/scipy/special/_sf_error.py new file mode 100644 index 0000000000000000000000000000000000000000..e1edc9800759dfda9e49bde1becc775a64bce958 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/_sf_error.py @@ -0,0 +1,15 @@ +"""Warnings and Exceptions that can be raised by special functions.""" +import warnings + + +class SpecialFunctionWarning(Warning): + """Warning that can be emitted by special functions.""" + pass + + +warnings.simplefilter("always", category=SpecialFunctionWarning) + + +class SpecialFunctionError(Exception): + """Exception that can be raised by special functions.""" + pass diff --git a/venv/lib/python3.12/site-packages/scipy/special/_specfun.cpython-312-x86_64-linux-gnu.so b/venv/lib/python3.12/site-packages/scipy/special/_specfun.cpython-312-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..6dc753a47f1952fe995cf414ce5d795fd12c19c7 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/_specfun.cpython-312-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2c27f344e2f8d3e97e3b070ee440592ba90324ae4263524ff4e85168388d6924 +size 228336 diff --git a/venv/lib/python3.12/site-packages/scipy/special/_special_ufuncs.cpython-312-x86_64-linux-gnu.so b/venv/lib/python3.12/site-packages/scipy/special/_special_ufuncs.cpython-312-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..def03c1f1dee7edd728a07429558b89844386033 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/_special_ufuncs.cpython-312-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5aa5984456082656fa66012b11e4eebff25e1e993cf6d50460a083086f0679d3 +size 1659864 diff --git a/venv/lib/python3.12/site-packages/scipy/special/_spfun_stats.py b/venv/lib/python3.12/site-packages/scipy/special/_spfun_stats.py new file mode 100644 index 0000000000000000000000000000000000000000..0b54f474ffd5fedeaa5cc44ce363e0761818f216 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/_spfun_stats.py @@ -0,0 +1,178 @@ +# Last Change: Sat Mar 21 02:00 PM 2009 J + +# Copyright (c) 2001, 2002 Enthought, Inc. +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# a. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# b. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# c. Neither the name of the Enthought nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +# DAMAGE. + +"""Some more special functions which may be useful for multivariate statistical +analysis.""" + +import numpy as np + +import scipy.special._gufuncs as _gufuncs +from scipy.special import gammaln as loggam +from scipy.special._ufunc_tools import _with_cache_optimization + + +__all__ = ['multigammaln'] + + +def multigammaln(a, d): + r"""Returns the log of multivariate gamma, also sometimes called the + generalized gamma. + + Parameters + ---------- + a : ndarray + The multivariate gamma is computed for each item of `a`. + d : int + The dimension of the space of integration. + + Returns + ------- + res : ndarray + The values of the log multivariate gamma at the given points `a`. + + Notes + ----- + The formal definition of the multivariate gamma of dimension d for a real + `a` is + + .. math:: + + \Gamma_d(a) = \int_{A>0} e^{-tr(A)} |A|^{a - (d+1)/2} dA + + with the condition :math:`a > (d-1)/2`, and :math:`A > 0` being the set of + all the positive definite matrices of dimension `d`. Note that `a` is a + scalar: the integrand only is multivariate, the argument is not (the + function is defined over a subset of the real set). + + This can be proven to be equal to the much friendlier equation + + .. math:: + + \Gamma_d(a) = \pi^{d(d-1)/4} \prod_{i=1}^{d} \Gamma(a - (i-1)/2). + + References + ---------- + R. J. Muirhead, Aspects of multivariate statistical theory (Wiley Series in + probability and mathematical statistics). + + Examples + -------- + >>> import numpy as np + >>> from scipy.special import multigammaln, gammaln + >>> a = 23.5 + >>> d = 10 + >>> multigammaln(a, d) + 454.1488605074416 + + Verify that the result agrees with the logarithm of the equation + shown above: + + >>> d*(d-1)/4*np.log(np.pi) + gammaln(a - 0.5*np.arange(0, d)).sum() + 454.1488605074416 + """ + a = np.asarray(a) + # Support for 0d arrays is needed for array_api_strict and dask. + d = np.asarray(d)[()] + if not np.isscalar(d) or (np.floor(d) != d): + raise ValueError("d should be a positive integer (dimension)") + if np.any(a <= 0.5 * (d - 1)): + raise ValueError(f"condition a ({a}) > 0.5 * (d-1) ({0.5 * (d-1)}) not met") + + res = (d * (d-1) * 0.25) * np.log(np.pi) + res += np.sum(loggam([(a - (j - 1.)/2) for j in range(1, d+1)]), axis=0) + return res + + +_poisson_binom_pmf_doc = ( + """Returns pmf of Poisson Binomial distribution. + + Parameters + ---------- + k : array + Number of successes at which to evaluate pmf. + + p : array + Success probabilities of independent Bernoulli trials. + + Notes + ----- + This is equivalent to a gufunc with signature ``()(i)->()``. + The last dimension of `p` contains success probabilities and + the preceding dimensions are batch dimensions. The batch + dimensions are broadcast against ``k``. + + The output will be C contiguous regardless of the contiguity of + `k` and `p`. + + """ +) + + +_poisson_binom_pmf = _with_cache_optimization( + name="_poisson_binom_pmf", + arg_names=["k", "p"], + docstring=_poisson_binom_pmf_doc, + ufunc=_gufuncs._poisson_binom_pmf, + cache_arg_indices=[1], +) + + +_poisson_binom_cdf_doc = ( + """Returns cdf of Poisson Binomial distribution. + + Parameters + ---------- + k : array + Number of successes at which to evaluate cdf. + + p : array + Success probabilities of independent Bernoulli trials. + + Notes + ----- + This is equivalent to a gufunc with signature ``()(i)->()``. + The last dimension of `p` contains success probabilities and + the preceding dimensions are batch dimensions. The batch + dimensions are broadcast against ``k``. + + The output will be C contiguous regardless of the contiguity of + `k` and `p`. + + """ +) + +_poisson_binom_cdf = _with_cache_optimization( + name="_poisson_binom_cdf", + arg_names=["k", "p"], + docstring=_poisson_binom_cdf_doc, + ufunc=_gufuncs._poisson_binom_cdf, + cache_arg_indices=[1], +) diff --git a/venv/lib/python3.12/site-packages/scipy/special/_spherical_bessel.py b/venv/lib/python3.12/site-packages/scipy/special/_spherical_bessel.py new file mode 100644 index 0000000000000000000000000000000000000000..56b345b78ad41f8e5c3c0a303b287611f7be9f4c --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/_spherical_bessel.py @@ -0,0 +1,401 @@ +from functools import wraps +import scipy._external.array_api_extra as xpx +import numpy as np +from ._ufuncs import (_spherical_jn, _spherical_yn, _spherical_in, + _spherical_kn, _spherical_jn_d, _spherical_yn_d, + _spherical_in_d, _spherical_kn_d) + + +def use_reflection(sign_n_even=None, reflection_fun=None): + # - If reflection_fun is not specified, reflects negative `z` and multiplies + # output by appropriate sign (indicated by `sign_n_even`). + # - If reflection_fun is specified, calls `reflection_fun` instead of `fun`. + # See DLMF 10.47(v) https://dlmf.nist.gov/10.47 + def decorator(fun): + def standard_reflection(n, z, derivative): + # sign_n_even indicates the sign when the order `n` is even + sign = np.where(n % 2 == 0, sign_n_even, -sign_n_even) + # By the chain rule, differentiation at `-z` adds a minus sign + sign = -sign if derivative else sign + # Evaluate at positive z (minus negative z) and adjust the sign + return fun(n, -z, derivative) * sign + + @wraps(fun) + def wrapper(n, z, derivative=False): + z = np.asarray(z) + + if np.issubdtype(z.dtype, np.complexfloating): + return fun(n, z, derivative) # complex dtype just works + + f2 = standard_reflection if reflection_fun is None else reflection_fun + return xpx.apply_where(z.real >= 0, (n, z), + lambda n, z: fun(n, z, derivative), + lambda n, z: f2(n, z, derivative))[()] + return wrapper + return decorator + + +@use_reflection(+1) # See DLMF 10.47(v) https://dlmf.nist.gov/10.47 +def spherical_jn(n, z, derivative=False): + r"""Spherical Bessel function of the first kind or its derivative. + + Defined as [1]_, + + .. math:: j_n(z) = \sqrt{\frac{\pi}{2z}} J_{n + 1/2}(z), + + where :math:`J_n` is the Bessel function of the first kind. + + Parameters + ---------- + n : int, array_like + Order of the Bessel function (n >= 0). + z : complex or float, array_like + Argument of the Bessel function. + derivative : bool, optional + If True, the value of the derivative (rather than the function + itself) is returned. + + Returns + ------- + jn : ndarray + Value or derivative of spherical Bessel function of the first kind. + + Notes + ----- + For real arguments greater than the order, the function is computed + using the ascending recurrence [2]_. For small real or complex + arguments, the definitional relation to the cylindrical Bessel function + of the first kind is used. + + The derivative is computed using the relations [3]_, + + .. math:: + j_n'(z) = j_{n-1}(z) - \frac{n + 1}{z} j_n(z). + + j_0'(z) = -j_1(z) + + + .. versionadded:: 0.18.0 + + References + ---------- + .. [1] https://dlmf.nist.gov/10.47.E3 + .. [2] https://dlmf.nist.gov/10.51.E1 + .. [3] https://dlmf.nist.gov/10.51.E2 + .. [AS] Milton Abramowitz and Irene A. Stegun, eds. + Handbook of Mathematical Functions with Formulas, + Graphs, and Mathematical Tables. New York: Dover, 1972. + + Examples + -------- + The spherical Bessel functions of the first kind :math:`j_n` accept + both real and complex second argument. They can return a complex type: + + >>> from scipy.special import spherical_jn + >>> spherical_jn(0, 3+5j) + (-9.878987731663194-8.021894345786002j) + >>> type(spherical_jn(0, 3+5j)) + + + We can verify the relation for the derivative from the Notes + for :math:`n=3` in the interval :math:`[1, 2]`: + + >>> import numpy as np + >>> x = np.arange(1.0, 2.0, 0.01) + >>> np.allclose(spherical_jn(3, x, True), + ... spherical_jn(2, x) - 4/x * spherical_jn(3, x)) + True + + The first few :math:`j_n` with real argument: + + >>> import matplotlib.pyplot as plt + >>> x = np.arange(0.0, 10.0, 0.01) + >>> fig, ax = plt.subplots() + >>> ax.set_ylim(-0.5, 1.5) + >>> ax.set_title(r'Spherical Bessel functions $j_n$') + >>> for n in np.arange(0, 4): + ... ax.plot(x, spherical_jn(n, x), label=rf'$j_{n}$') + >>> plt.legend(loc='best') + >>> plt.show() + + """ + n = np.asarray(n, dtype=np.dtype("long")) + if derivative: + return _spherical_jn_d(n, z) + else: + return _spherical_jn(n, z) + + +@use_reflection(-1) # See DLMF 10.47(v) https://dlmf.nist.gov/10.47 +def spherical_yn(n, z, derivative=False): + r"""Spherical Bessel function of the second kind or its derivative. + + Defined as [1]_, + + .. math:: y_n(z) = \sqrt{\frac{\pi}{2z}} Y_{n + 1/2}(z), + + where :math:`Y_n` is the Bessel function of the second kind. + + Parameters + ---------- + n : int, array_like + Order of the Bessel function (n >= 0). + z : complex or float, array_like + Argument of the Bessel function. + derivative : bool, optional + If True, the value of the derivative (rather than the function + itself) is returned. + + Returns + ------- + yn : ndarray + Value or derivative of spherical Bessel function of the second kind. + + Notes + ----- + For real arguments, the function is computed using the ascending + recurrence [2]_. For complex arguments, the definitional relation to + the cylindrical Bessel function of the second kind is used. + + The derivative is computed using the relations [3]_, + + .. math:: + y_n' = y_{n-1} - \frac{n + 1}{z} y_n. + + y_0' = -y_1 + + + .. versionadded:: 0.18.0 + + References + ---------- + .. [1] https://dlmf.nist.gov/10.47.E4 + .. [2] https://dlmf.nist.gov/10.51.E1 + .. [3] https://dlmf.nist.gov/10.51.E2 + .. [AS] Milton Abramowitz and Irene A. Stegun, eds. + Handbook of Mathematical Functions with Formulas, + Graphs, and Mathematical Tables. New York: Dover, 1972. + + Examples + -------- + The spherical Bessel functions of the second kind :math:`y_n` accept + both real and complex second argument. They can return a complex type: + + >>> from scipy.special import spherical_yn + >>> spherical_yn(0, 3+5j) + (8.022343088587197-9.880052589376795j) + >>> type(spherical_yn(0, 3+5j)) + + + We can verify the relation for the derivative from the Notes + for :math:`n=3` in the interval :math:`[1, 2]`: + + >>> import numpy as np + >>> x = np.arange(1.0, 2.0, 0.01) + >>> np.allclose(spherical_yn(3, x, True), + ... spherical_yn(2, x) - 4/x * spherical_yn(3, x)) + True + + The first few :math:`y_n` with real argument: + + >>> import matplotlib.pyplot as plt + >>> x = np.arange(0.0, 10.0, 0.01) + >>> fig, ax = plt.subplots() + >>> ax.set_ylim(-2.0, 1.0) + >>> ax.set_title(r'Spherical Bessel functions $y_n$') + >>> for n in np.arange(0, 4): + ... ax.plot(x, spherical_yn(n, x), label=rf'$y_{n}$') + >>> plt.legend(loc='best') + >>> plt.show() + + """ + n = np.asarray(n, dtype=np.dtype("long")) + if derivative: + return _spherical_yn_d(n, z) + else: + return _spherical_yn(n, z) + + +@use_reflection(+1) # See DLMF 10.47(v) https://dlmf.nist.gov/10.47 +def spherical_in(n, z, derivative=False): + r"""Modified spherical Bessel function of the first kind or its derivative. + + Defined as [1]_, + + .. math:: i_n(z) = \sqrt{\frac{\pi}{2z}} I_{n + 1/2}(z), + + where :math:`I_n` is the modified Bessel function of the first kind. + + Parameters + ---------- + n : int, array_like + Order of the Bessel function (n >= 0). + z : complex or float, array_like + Argument of the Bessel function. + derivative : bool, optional + If True, the value of the derivative (rather than the function + itself) is returned. + + Returns + ------- + in : ndarray + Value or derivative of modified spherical Bessel function of the first kind. + + Notes + ----- + The function is computed using its definitional relation to the + modified cylindrical Bessel function of the first kind. + + The derivative is computed using the relations [2]_, + + .. math:: + i_n' = i_{n-1} - \frac{n + 1}{z} i_n. + + i_1' = i_0 + + + .. versionadded:: 0.18.0 + + References + ---------- + .. [1] https://dlmf.nist.gov/10.47.E7 + .. [2] https://dlmf.nist.gov/10.51.E5 + .. [AS] Milton Abramowitz and Irene A. Stegun, eds. + Handbook of Mathematical Functions with Formulas, + Graphs, and Mathematical Tables. New York: Dover, 1972. + + Examples + -------- + The modified spherical Bessel functions of the first kind :math:`i_n` + accept both real and complex second argument. + They can return a complex type: + + >>> from scipy.special import spherical_in + >>> spherical_in(0, 3+5j) + (-1.1689867793369182-1.2697305267234222j) + >>> type(spherical_in(0, 3+5j)) + + + We can verify the relation for the derivative from the Notes + for :math:`n=3` in the interval :math:`[1, 2]`: + + >>> import numpy as np + >>> x = np.arange(1.0, 2.0, 0.01) + >>> np.allclose(spherical_in(3, x, True), + ... spherical_in(2, x) - 4/x * spherical_in(3, x)) + True + + The first few :math:`i_n` with real argument: + + >>> import matplotlib.pyplot as plt + >>> x = np.arange(0.0, 6.0, 0.01) + >>> fig, ax = plt.subplots() + >>> ax.set_ylim(-0.5, 5.0) + >>> ax.set_title(r'Modified spherical Bessel functions $i_n$') + >>> for n in np.arange(0, 4): + ... ax.plot(x, spherical_in(n, x), label=rf'$i_{n}$') + >>> plt.legend(loc='best') + >>> plt.show() + + """ + n = np.asarray(n, dtype=np.dtype("long")) + if derivative: + return _spherical_in_d(n, z) + else: + return _spherical_in(n, z) + + +def spherical_kn_reflection(n, z, derivative=False): + # More complex than the other cases, and this will likely be re-implemented + # in C++ anyway. Would require multiple function evaluations. Probably about + # as fast to just resort to complex math, and much simpler. + return spherical_kn(n, z + 0j, derivative=derivative).real + + +@use_reflection(reflection_fun=spherical_kn_reflection) +def spherical_kn(n, z, derivative=False): + r"""Modified spherical Bessel function of the second kind or its derivative. + + Defined as [1]_, + + .. math:: k_n(z) = \sqrt{\frac{\pi}{2z}} K_{n + 1/2}(z), + + where :math:`K_n` is the modified Bessel function of the second kind. + + Parameters + ---------- + n : int, array_like + Order of the Bessel function (n >= 0). + z : complex or float, array_like + Argument of the Bessel function. + derivative : bool, optional + If True, the value of the derivative (rather than the function + itself) is returned. + + Returns + ------- + kn : ndarray + Value or derivative of modified spherical Bessel function of the second kind. + + Notes + ----- + The function is computed using its definitional relation to the + modified cylindrical Bessel function of the second kind. + + The derivative is computed using the relations [2]_, + + .. math:: + k_n' = -k_{n-1} - \frac{n + 1}{z} k_n. + + k_0' = -k_1 + + + .. versionadded:: 0.18.0 + + References + ---------- + .. [1] https://dlmf.nist.gov/10.47.E9 + .. [2] https://dlmf.nist.gov/10.51.E5 + .. [AS] Milton Abramowitz and Irene A. Stegun, eds. + Handbook of Mathematical Functions with Formulas, + Graphs, and Mathematical Tables. New York: Dover, 1972. + + Examples + -------- + The modified spherical Bessel functions of the second kind :math:`k_n` + accept both real and complex second argument. + They can return a complex type: + + >>> from scipy.special import spherical_kn + >>> spherical_kn(0, 3+5j) + (0.012985785614001561+0.003354691603137546j) + >>> type(spherical_kn(0, 3+5j)) + + + We can verify the relation for the derivative from the Notes + for :math:`n=3` in the interval :math:`[1, 2]`: + + >>> import numpy as np + >>> x = np.arange(1.0, 2.0, 0.01) + >>> np.allclose(spherical_kn(3, x, True), + ... - 4/x * spherical_kn(3, x) - spherical_kn(2, x)) + True + + The first few :math:`k_n` with real argument: + + >>> import matplotlib.pyplot as plt + >>> x = np.arange(0.0, 4.0, 0.01) + >>> fig, ax = plt.subplots() + >>> ax.set_ylim(0.0, 5.0) + >>> ax.set_title(r'Modified spherical Bessel functions $k_n$') + >>> for n in np.arange(0, 4): + ... ax.plot(x, spherical_kn(n, x), label=rf'$k_{n}$') + >>> plt.legend(loc='best') + >>> plt.show() + + """ + n = np.asarray(n, dtype=np.dtype("long")) + if derivative: + return _spherical_kn_d(n, z) + else: + return _spherical_kn(n, z) diff --git a/venv/lib/python3.12/site-packages/scipy/special/_support_alternative_backends.py b/venv/lib/python3.12/site-packages/scipy/special/_support_alternative_backends.py new file mode 100644 index 0000000000000000000000000000000000000000..262b974ee26d3bc1c7f69a9275e7527faff78ffc --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/_support_alternative_backends.py @@ -0,0 +1,933 @@ +import functools +import operator +from collections.abc import Callable, Mapping +from dataclasses import dataclass +from types import ModuleType + +import numpy as np +from scipy._lib._array_api import ( + array_namespace, scipy_namespace_for, is_numpy, is_dask, is_marray, is_jax_array, + is_jax, xp_promote, xp_capabilities, SCIPY_ARRAY_API, get_native_namespace_name, + is_array_api_obj +) +import scipy._external.array_api_extra as xpx +from . import _basic +from . import _spfun_stats +from . import _ufuncs + +# mypy: disable-error-code=dict-item + +def _special_namespace_for(xp): + spx = scipy_namespace_for(xp) + return getattr(spx, "special", None) + + +@dataclass +class _FuncInfo: + # NumPy-only function. IT MUST BE ELEMENTWISE. + func: Callable + # Number of arguments, not counting out= + # This is for testing purposes only, due to the fact that + # inspect.signature() just returns *args for ufuncs. + n_args: int + # @xp_capabilities decorator, for the purpose of + # documentation and unit testing. Omit to indicate + # full support for all backends. + xp_capabilities: Callable[[Callable], Callable] | None = None + # Generic implementation to fall back on if there is no native dispatch + # available. This is a function that accepts (main namespace, scipy namespace) + # and returns the final callable, or None if not available. + generic_impl: Callable[ + [ModuleType, ModuleType | None], Callable | None + ] | None = None + # Handle case where a backend uses an alternative name for a function. + # Should map backend names to alternative function names. + alt_names_map: dict[str, str] | None = None + # Some functions only take integer arrays for some arguments. + int_only: tuple[bool, ...] | None = None + # For testing purposes, whether tests should only use positive values + # for some arguments. If bool and equal to True, restrict to positive + # values for all arguments. To restrict only some arguments to positive + # values, pass a tuple of bool of the same length as the number of + # arguments, the ith entry in the tuple controls positive_only for + # the ith argument. To make backend specific choices for positive_only, + # pass in a dict mapping backend names to bool or tuple[bool, ...]. + positive_only: ( + bool | tuple[bool, ...] | Mapping[str, bool | tuple[bool, ...]] + ) = False + # Some special functions are not ufuncs and ufunc-specific tests + # should not be applied to these. + is_ufunc: bool = True + # Some non-ufunc special functions take only Python ints for some arguments. + # If so, python_int_only should be a tuple of the same length as the number + # of arguments,with value True if the corresponding argument needs to be a + # Python int. + # Can also take a dict mapping backends to such tuples if an argument being + # Python int only is backend specific. + python_int_only: dict[str, tuple[bool, ...]] | tuple[bool, ...] | None = None + # Some functions which seem to be scalar also accept 0d arrays. + scalar_or_0d_only: dict[str, tuple[bool, ...]] | tuple[bool, ...] | None = None + # Some functions may not work well with very large integer valued arguments. + test_large_ints: bool = True + # Some non-ufunc special functions don't decay 0d arrays to scalar. + produces_0d: bool = False + # Whether or not uses native PyTorch or falls back to NumPy/SciPy. This + # is needed because in PyTorch, the default dtype affects promotion + # rules when mixing integer and floating dtypes, so relying on a + # NumPy/SciPy fallback when the default dtype is other than float64 can lead + # to float64 output when native PyTorch would have e.g. float32 output. This + # must be accounted for in tests. Not putting this in xp_capabilities for now, + # but in the future I think it's likely we may want to add a warning to + # xp_capabilities when not using native PyTorch on CPU. + torch_native: bool = True + # Place a backend in this tuple if `func` is available as `xp.func` but not + # available in the `scipy.special` namespace for this backend. + # One example is `jax.numpy.sinc` being available but not `jax.scipy.special.sinc`. + backends_with_func_in_xp: tuple[str, ...] = () + + @property + def name(self): + return self.func.__name__ + + # These are needed by @lru_cache below + def __hash__(self): + return hash(self.func) + + def __eq__(self, other): + return isinstance(other, _FuncInfo) and self.func == other.func + + @property + def wrapper(self): + if self.name in globals(): + # Already initialised. We are likely in a unit test. + # Return function potentially overridden by xpx.testing.lazy_xp_function. + import scipy.special + return getattr(scipy.special, self.name) + + if SCIPY_ARRAY_API: + @functools.wraps(self.func) + def wrapped(*args, **kwargs): + xp = array_namespace(*args) + return self._wrapper_for(xp)(*args, **kwargs) + + # Allow pickling the function. Normally this is done by @wraps, + # but in this case it doesn't work because self.func is a ufunc. + wrapped.__module__ = "scipy.special" + wrapped.__qualname__ = self.name + func = wrapped + else: + func = self.func + + capabilities = self.xp_capabilities or xp_capabilities() + # In order to retain a naked ufunc when SCIPY_ARRAY_API is + # disabled, xp_capabilities must apply its changes in place. + cap_func = capabilities(func) + assert cap_func is func + return func + + @functools.lru_cache(1000) + def _wrapper_for(self, xp): + if is_numpy(xp): + return self.func + + # If a native implementation is available, use that + in_xp = get_native_namespace_name(xp) in self.backends_with_func_in_xp + namespace = xp if in_xp else _special_namespace_for(xp) + f = _get_native_func( + xp, namespace, self.name, alt_names_map=self.alt_names_map + ) + if f is not None: + return f + + if in_xp: + # when namespace is passed to self.generic_impl below, we want to + # make sure that it is the special namespace for xp and not xp + # itself, so raise if xp was incorrectly placed in + # `backends_with_func_in_xp`. + raise RuntimeError( + f"func {self.func} is not available as {xp.__name__}.{self.func}" + f" but {xp.__name__} was passed in ``in_xp``." + ) + + # If generic Array API implementation is available, use that + if self.generic_impl is not None: + f = self.generic_impl(xp, namespace) + if f is not None: + return f + + if is_marray(xp): + # Unwrap the array, apply the function on the wrapped namespace, + # and then re-wrap it. + # IMPORTANT: this only works because all functions in this module + # are elementwise. Otherwise, we would not be able to define a + # general rule for mask propagation. + + _f = globals()[self.name] # Allow nested wrapping + def f(*args, _f=_f, xp=xp, **kwargs): + data_args = [getattr(arg, 'data', arg) for arg in args] + out = _f(*data_args, **kwargs) + mask = functools.reduce(operator.or_, + (getattr(arg, 'mask', False) for arg in args)) + return xp.asarray(out, mask=mask) + + return f + + if is_dask(xp): + # Apply the function to each block of the Dask array. + # IMPORTANT: map_blocks works only because all functions in this module + # are elementwise. It would be a grave mistake to apply this to gufuncs + # or any other function with reductions, as they would change their + # output depending on chunking! + + _f = globals()[self.name] # Allow nested wrapping + def f(*args, _f=_f, xp=xp, **kwargs): + # Hide dtype kwarg from map_blocks + return xp.map_blocks(functools.partial(_f, **kwargs), *args) + + return f + + # As a final resort, use the NumPy/SciPy implementation + _f = self.func + + if is_jax(xp) and self.is_ufunc: + # if this is a ufunc, we can use the resolve_dtypes method to figure + # out what the output dtype should be and use lazy_apply to make this + # work with the JAX JIT. Since pure_callback used in lazy_apply will + # convert all of the inputs to eager JAX arrays, we will also need to + # get the input dtypes inferred from resolve_dtypes so that Python + # scalar inputs can be cast to the correct dtype under NEP50 weak + # promotion rules rather than getting promoted to the default currently + # set in JAX. One cannot just use xp_promote for the input dtypes because + # some ufuncs have integer only args. + def f(*args, _f=_f, xp=xp, **kwargs): + dtypes = (arg.dtype if is_jax_array(arg) else type(arg) for arg in args) + # result_dtypes needs an arg for the dtype of the optional out param. + # Use `None` since `out` is incompatible with JAX's immutability. + dtypes = (*dtypes, None) + # JAX uses NumPy dtypes so we can just pass these directly to + # resolve_dtypes. TODO: generalize to other lazy backends. + dtypes = _f.resolve_dtypes(dtypes) + out_dtype = dtypes[-1] + args = [ + xp.asarray(arg, dtype=dtype) + for arg, dtype in zip(args, dtypes[:-1]) + ] + return xpx.lazy_apply( + _f, *args, xp=xp, as_numpy=True, dtype=out_dtype, **kwargs + ) + else: + def f(*args, _f=_f, xp=xp, **kwargs): + # Check with `is_array_api_obj` to keep Python scalars untouched so that + # NEP50 can be followed. + args = [ + np.asarray(arg) if is_array_api_obj(arg) else arg for arg in args + ] + out = _f(*args, **kwargs) + return xp.asarray(out) + + return f + + +def _get_native_func(xp, namespace, f_name, *, alt_names_map=None): + if alt_names_map is None: + alt_names_map = {} + f_name = alt_names_map.get(get_native_namespace_name(xp), f_name) + f = getattr(namespace, f_name, None) + if f is None and hasattr(xp, 'special'): + # Currently dead branch, in anticipation of 'special' Array API extension + # https://github.com/data-apis/array-api/issues/725 + f = getattr(xp.special, f_name, None) + return f + + +def _rel_entr(xp, spsx): + def __rel_entr(x, y, *, xp=xp): + # https://github.com/data-apis/array-api-extra/issues/160 + mxp = array_namespace(x._meta, y._meta) if is_dask(xp) else xp + x, y = xp_promote(x, y, broadcast=True, force_floating=True, xp=xp) + xy_pos = (x > 0) & (y > 0) + xy_inf = xp.isinf(x) & xp.isinf(y) + res = xpx.apply_where( + xy_pos & ~xy_inf, + (x, y), + # Note: for very large x, this can overflow. + lambda x, y: x * (mxp.log(x) - mxp.log(y)), + fill_value=xp.inf + ) + res = xpx.at(res)[(x == 0) & (y >= 0)].set(0) + res = xpx.at(res)[xp.isnan(x) | xp.isnan(y) | (xy_pos & xy_inf)].set(xp.nan) + return res + + return __rel_entr + + +def _xlogy(xp, spsx): + def __xlogy(x, y, *, xp=xp): + x, y = xp_promote(x, y, force_floating=True, xp=xp) + with np.errstate(divide='ignore', invalid='ignore'): + temp = x * xp.log(y) + return xp.where(x == 0., 0., temp) + return __xlogy + + + +def _chdtr(xp, spsx): + # The difference between this and just using `gammainc` + # defined by `get_array_special_func` is that if `gammainc` + # isn't found, we don't want to use the SciPy version; we'll + # return None here and use the SciPy version of `chdtr`. + gammainc = _get_native_func(xp, spsx, 'gammainc') + if gammainc is None: + return None + + def __chdtr(v, x): + res = gammainc(v / 2, x / 2) # this is almost all we need + # The rest can be removed when google/jax#20507 is resolved + mask = (v == 0) & (x > 0) # JAX returns NaN + res = xp.where(mask, 1., res) + mask = xp.isinf(v) & xp.isinf(x) # JAX returns 1.0 + return xp.where(mask, xp.nan, res) + return __chdtr + + +def _chdtrc(xp, spsx): + # The difference between this and just using `gammaincc` + # defined by `get_array_special_func` is that if `gammaincc` + # isn't found, we don't want to use the SciPy version; we'll + # return None here and use the SciPy version of `chdtrc`. + gammaincc = _get_native_func(xp, spsx, 'gammaincc') + if gammaincc is None: + return None + + def __chdtrc(v, x): + res = xp.where(x >= 0, gammaincc(v/2, x/2), 1) + i_nan = ((x == 0) & (v == 0)) | xp.isnan(x) | xp.isnan(v) | (v < 0) + res = xp.where(i_nan, xp.nan, res) + return res + return __chdtrc + + +def _betaincc(xp, spsx): + betainc = _get_native_func(xp, spsx, 'betainc') + if betainc is None: + return None + + def __betaincc(a, b, x): + # not perfect; might want to just rely on SciPy + return betainc(b, a, 1-x) + return __betaincc + + +def _stdtr(xp, spsx): + betainc = _get_native_func(xp, spsx, 'betainc') + if betainc is None: + return None + + def __stdtr(df, t): + x = df / (t ** 2 + df) + tail = betainc(df / 2, 0.5, x) / 2 + return xp.where(t < 0, tail, 1 - tail) + + return __stdtr + + +def _stdtrit(xp, spsx): + # Need either native stdtr or native betainc + stdtr = _get_native_func(xp, spsx, 'stdtr') or _stdtr(xp, spsx) + # If betainc is not defined, the root-finding would be done with `xp` + # despite `stdtr` being evaluated with SciPy/NumPy `stdtr`. Save the + # conversions: in this case, just evaluate `stdtrit` with SciPy/NumPy. + if stdtr is None: + return None + + from scipy.optimize.elementwise import bracket_root, find_root + + def __stdtrit(df, p): + def fun(t, df, p): return stdtr(df, t) - p + res_bracket = bracket_root(fun, xp.zeros_like(p), args=(df, p)) + res_root = find_root(fun, res_bracket.bracket, args=(df, p)) + return res_root.x + + return __stdtrit + + +# Inventory of automatically dispatched functions +# IMPORTANT: these must all be **elementwise** functions! + +# PyTorch doesn't implement `betainc`. +# On torch CPU we can fall back to NumPy, but on GPU it won't work. +_needs_betainc = xp_capabilities(cpu_only=True, exceptions=["jax.numpy", "cupy"]) + +_special_funcs = ( + _FuncInfo( + _ufuncs.bdtr, 3, + xp_capabilities( + cpu_only=True, exceptions=["cupy"], + jax_jit=True, + ), + int_only=(False, True, False), torch_native=False, + ), + _FuncInfo( + _ufuncs.bdtrc, 3, + xp_capabilities( + cpu_only=True, exceptions=["cupy"], + jax_jit=True, + ), + int_only=(False, True, False), torch_native=False, + ), + _FuncInfo( + _ufuncs.bdtri, 3, + xp_capabilities( + cpu_only=True, exceptions=["cupy"], + jax_jit=True, + ), + int_only=(False, True, False), torch_native=False, + ), + _FuncInfo(_ufuncs.betainc, 3, _needs_betainc, torch_native=False), + _FuncInfo(_ufuncs.betaincc, 3, _needs_betainc, generic_impl=_betaincc, + torch_native=False), + _FuncInfo( + _ufuncs.betaincinv, 3, + xp_capabilities( + cpu_only=True, exceptions=["cupy"], + jax_jit=True, + ), + test_large_ints=False, positive_only=True, torch_native=False, + ), + _FuncInfo( + _ufuncs.betaln, 2, + xp_capabilities(cpu_only=True, exceptions=["cupy", "jax.numpy"]), + # For betaln, nan mismatches can occur at negative integer a or b of + # sufficiently large magnitude. + positive_only={"jax.numpy": True}, torch_native=False, + ), + _FuncInfo( + _ufuncs.binom, 2, + xp_capabilities( + cpu_only=True, exceptions=["cupy"], + jax_jit=True, + ), + torch_native=False, + ), + _FuncInfo( + _ufuncs.boxcox, 2, + xp_capabilities( + cpu_only=True, exceptions=["cupy"], + jax_jit=True, + ), + torch_native=False, + ), + _FuncInfo( + _ufuncs.boxcox1p, 2, + xp_capabilities( + cpu_only=True, exceptions=["cupy"], + jax_jit=True, + ), + torch_native=False, + ), + _FuncInfo( + _ufuncs.cbrt, 1, + xp_capabilities( + cpu_only=True, exceptions=["cupy"], + jax_jit=True, + ), + torch_native=False, + ), + _FuncInfo(_ufuncs.chdtr, 2, generic_impl=_chdtr), + _FuncInfo(_ufuncs.chdtrc, 2, generic_impl=_chdtrc, + # scipy/scipy#20972 + positive_only={"cupy": True, "jax.numpy": True, "torch": True}), + _FuncInfo( + _ufuncs.chdtri, 2, + xp_capabilities( + cpu_only=True, exceptions=["cupy"], + jax_jit=True, + ), + torch_native=False, + ), + _FuncInfo( + _ufuncs.cosdg, 1, + xp_capabilities( + cpu_only=True, exceptions=["cupy"], + jax_jit=True, + ), + test_large_ints=False, torch_native=False, + ), + _FuncInfo( + _ufuncs.cosm1, 1, + xp_capabilities( + cpu_only=True, exceptions=["cupy"], + jax_jit=True, + ), + torch_native=False, + ), + _FuncInfo( + _ufuncs.cotdg, 1, + xp_capabilities( + cpu_only=True, exceptions=["cupy"], + jax_jit=True, + ), + torch_native=False, + ), + _FuncInfo( + _ufuncs.ellipk, 1, + xp_capabilities( + cpu_only=True, exceptions=["cupy"], + jax_jit=True, + ), + torch_native=False, + ), + _FuncInfo( + _ufuncs.ellipkm1, 1, + xp_capabilities( + cpu_only=True, exceptions=["cupy"], + jax_jit=True, + ), + torch_native=False, + ), + _FuncInfo(_ufuncs.entr, 1), + _FuncInfo(_ufuncs.erf, 1), + _FuncInfo(_ufuncs.erfc, 1), + _FuncInfo( + _ufuncs.erfcx, 1, + xp_capabilities( + cpu_only=True, exceptions=["cupy", "torch"], + jax_jit=True, + ), + torch_native=False, + ), + _FuncInfo(_ufuncs.erfinv, 1), + _FuncInfo( + _ufuncs.exp1, 1, + xp_capabilities( + cpu_only=True, exceptions=["cupy"], + jax_jit=True, + ), + torch_native=False, + ), + _FuncInfo( + _ufuncs.exp10, 1, + xp_capabilities( + cpu_only=True, exceptions=["cupy"], + jax_jit=True, + ), + torch_native=False, + ), + _FuncInfo( + _ufuncs.exp2, 1, + xp_capabilities( + cpu_only=True, exceptions=["cupy"], + jax_jit=True, + ), + torch_native=False, + ), + _FuncInfo( + _ufuncs.exprel, 1, + xp_capabilities( + cpu_only=True, exceptions=["cupy"], + jax_jit=True, + ), + torch_native=False, + ), + _FuncInfo( + _ufuncs.expi, 1, + xp_capabilities(cpu_only=True, exceptions=["cupy", "jax.numpy"]), + torch_native=False, + ), + _FuncInfo(_ufuncs.expit, 1), + _FuncInfo( + _ufuncs.expn, 2, + xp_capabilities(cpu_only=True, exceptions=["cupy", "jax.numpy"]), + # Inconsistent behavior for negative n. expn is not defined here without + # taking analytic continuation. + positive_only=True, + int_only=(True, False), test_large_ints=False, + torch_native=False, + ), + _FuncInfo( + _ufuncs.fdtr, 3, + xp_capabilities( + cpu_only=True, exceptions=["cupy"], + jax_jit=True, + ), + torch_native=False, + ), + _FuncInfo( + _ufuncs.fdtrc, 3, + xp_capabilities( + cpu_only=True, exceptions=["cupy"], + jax_jit=True, + ), + torch_native=False, + ), + _FuncInfo( + _ufuncs.fdtri, 3, + xp_capabilities( + cpu_only=True, exceptions=["cupy"], + jax_jit=True, + ), + torch_native=False, + ), + _FuncInfo( + _ufuncs.gamma, 1, + xp_capabilities(cpu_only=True, exceptions=["cupy", "jax.numpy"]), + torch_native=False, + ), + _FuncInfo(_ufuncs.gammainc, 2), + _FuncInfo( + _ufuncs.gammaincc, 2, + # google/jax#20699 + positive_only={"jax.numpy": True}, + ), + _FuncInfo( + _ufuncs.gammainccinv, 2, + xp_capabilities( + cpu_only=True, exceptions=["cupy"], + jax_jit=True, + ), + torch_native=False, + ), + _FuncInfo( + _ufuncs.gammaincinv, 2, + xp_capabilities( + cpu_only=True, exceptions=["cupy"], + jax_jit=True, + ), + torch_native=False, + ), + _FuncInfo(_ufuncs.gammaln, 1), + _FuncInfo( + _ufuncs.gammasgn, 1, + xp_capabilities(cpu_only=True, exceptions=["cupy", "jax.numpy"]), + torch_native=False, + ), + _FuncInfo( + _ufuncs.gdtr, 3, + xp_capabilities( + cpu_only=True, exceptions=["cupy"], + jax_jit=True, + ), + torch_native=False, + ), + _FuncInfo( + _ufuncs.gdtrc, 3, + xp_capabilities( + cpu_only=True, exceptions=["cupy"], + jax_jit=True, + ), + torch_native=False, + ), + _FuncInfo( + _ufuncs.huber, 2, + xp_capabilities( + cpu_only=True, exceptions=["cupy"], + jax_jit=True, + ), + torch_native=False, + ), + _FuncInfo( + _ufuncs.hyp1f1, 3, + xp_capabilities(cpu_only=True, exceptions=["jax.numpy"]), + positive_only={"jax.numpy": True}, test_large_ints=False, + torch_native=False, + ), + # Comment out when jax>=0.6.1 is available in Conda for CI. + # (or add version requirements to xp_capabilities). + # _FuncInfo( + # _ufuncs.hyp2f1, 4, + # xp_capabilities(cpu_only=True, exceptions=["jax.numpy"]), + # positive_only={"jax.numpy": True}, test_large_ints=False, + # torch_native=False, + # ), + _FuncInfo( + _ufuncs.inv_boxcox, 2, + xp_capabilities( + cpu_only=True, exceptions=["cupy"], + jax_jit=True, + ), + torch_native=False, + ), + _FuncInfo( + _ufuncs.inv_boxcox1p, 2, + xp_capabilities( + cpu_only=True, exceptions=["cupy"], + jax_jit=True, + ), + torch_native=False, + ), + _FuncInfo(_ufuncs.i0, 1), + _FuncInfo(_ufuncs.i0e, 1), + _FuncInfo(_ufuncs.i1, 1), + _FuncInfo(_ufuncs.i1e, 1), + _FuncInfo( + _ufuncs.j0, 1, + xp_capabilities( + cpu_only=True, exceptions=["cupy", "torch"], + jax_jit=True, + ), + alt_names_map={"torch": "bessel_j0"}, test_large_ints=False, + ), + _FuncInfo( + _ufuncs.j1, 1, + xp_capabilities( + cpu_only=True, exceptions=["cupy", "torch"], + jax_jit=True, + ), + alt_names_map={"torch": "bessel_j1"}, test_large_ints=False, + ), + _FuncInfo( + _ufuncs.k0, 1, + xp_capabilities( + cpu_only=True, exceptions=["cupy", "torch"], + jax_jit=True, + ), + alt_names_map={"torch": "modified_bessel_k0"}, + ), + _FuncInfo( + _ufuncs.k0e, 1, + xp_capabilities( + cpu_only=True, exceptions=["cupy", "torch"], + jax_jit=True, + ), + alt_names_map={"torch": "scaled_modified_bessel_k0"}, + test_large_ints=False, + ), + _FuncInfo( + _ufuncs.k1, 1, + xp_capabilities( + cpu_only=True, exceptions=["cupy", "torch"], + jax_jit=True, + ), + alt_names_map={"torch": "modified_bessel_k1"}, + ), + _FuncInfo( + _ufuncs.k1e, 1, + xp_capabilities( + cpu_only=True, exceptions=["cupy", "torch"], + jax_jit=True, + ), + alt_names_map={"torch": "scaled_modified_bessel_k1"}, + test_large_ints=False), + _FuncInfo( + _ufuncs.kl_div, 2, + xp_capabilities(cpu_only=True, exceptions=["cupy", "jax.numpy"]), + torch_native=False, + ), + _FuncInfo(_ufuncs.log_ndtr, 1), + _FuncInfo( + _ufuncs.loggamma, 1, + xp_capabilities( + cpu_only=True, exceptions=["cupy"], + jax_jit=True, + ), + torch_native=False, + ), + _FuncInfo(_ufuncs.logit, 1), + _FuncInfo( + _ufuncs.lpmv, 3, + xp_capabilities( + cpu_only=True, exceptions=["cupy"], + jax_jit=True, + ), + torch_native=False, + test_large_ints=False, + ), + _FuncInfo( + _spfun_stats.multigammaln, 2, + is_ufunc=False, + python_int_only={ + "cupy": (False, True), + "jax.numpy": (False, True), + "torch": (False, True), + }, + scalar_or_0d_only={ + "array_api_strict": (False, True), + "numpy": (False, True), + "dask.array": (False, True), + "marray": (False, True), + }, + int_only=(False, True), test_large_ints=False, + positive_only=True, torch_native=False, + ), + _FuncInfo( + _ufuncs.nbdtr, 3, + xp_capabilities( + cpu_only=True, exceptions=["cupy"], + jax_jit=True, + ), + int_only=(True, True, False), positive_only=True, + torch_native=False, + ), + _FuncInfo( + _ufuncs.nbdtrc, 3, + xp_capabilities( + cpu_only=True, exceptions=["cupy"], + jax_jit=True, + ), + int_only=(True, True, False), positive_only=True, + torch_native=False, + ), + _FuncInfo( + _ufuncs.nbdtri, 3, + xp_capabilities( + cpu_only=True, exceptions=["cupy"], + jax_jit=True, + ), + int_only=(True, True, False), positive_only=True, + torch_native=False, + ), + _FuncInfo(_ufuncs.ndtr, 1), + _FuncInfo(_ufuncs.ndtri, 1), + _FuncInfo( + _ufuncs.pdtr, 2, + xp_capabilities( + cpu_only=True, exceptions=["cupy"], + jax_jit=True, + ), + positive_only=True, torch_native=False, + ), + _FuncInfo( + _ufuncs.pdtrc, 2, + xp_capabilities( + cpu_only=True, exceptions=["cupy"], + jax_jit=True, + ), + positive_only=True, torch_native=False, + ), + _FuncInfo( + _ufuncs.pdtri, 2, + xp_capabilities( + cpu_only=True, exceptions=["cupy"], + jax_jit=True, + ), + int_only=(True, False), positive_only=True, + torch_native=False, + ), + _FuncInfo( + _ufuncs.poch, 2, + xp_capabilities(cpu_only=True, exceptions=["cupy", "jax.numpy"]), + test_large_ints=False, torch_native=False, + ), + _FuncInfo( + _ufuncs.pseudo_huber, 2, + xp_capabilities( + cpu_only=True, exceptions=["cupy"], + jax_jit=True, + ), + torch_native=False, + ), + _FuncInfo( + _basic.polygamma, 2, int_only=(True, False), is_ufunc=False, + scalar_or_0d_only={"torch": (True, False)}, produces_0d=True, + positive_only={"torch": (True, False), "jax.numpy": True}, + test_large_ints=False, + ), + _FuncInfo(_ufuncs.psi, 1, alt_names_map={"jax.numpy": "digamma"}), + _FuncInfo( + _ufuncs.radian, 3, + xp_capabilities( + cpu_only=True, exceptions=["cupy"], + jax_jit=True, + ), + torch_native=False, + ), + _FuncInfo(_ufuncs.rel_entr, 2, generic_impl=_rel_entr), + _FuncInfo( + _ufuncs.rgamma, 1, + xp_capabilities( + cpu_only=True, exceptions=["cupy"], + jax_jit=True, + ), + torch_native=False, + ), + _FuncInfo( + _basic.sinc, 1, + xp_capabilities( + cpu_only=True, exceptions=["cupy", "torch"], + jax_jit=True, + ), + is_ufunc=False, + backends_with_func_in_xp=("jax.numpy",), + ), + _FuncInfo( + _ufuncs.sindg, 1, + xp_capabilities( + cpu_only=True, exceptions=["cupy"], + jax_jit=True, + ), + test_large_ints=False, torch_native=False, + ), + _FuncInfo( + _ufuncs.spence, 1, + xp_capabilities(cpu_only=True, exceptions=["jax.numpy"]), + torch_native=False, + ), + _FuncInfo(_ufuncs.stdtr, 2, _needs_betainc, generic_impl=_stdtr, + torch_native=False), + _FuncInfo( + _ufuncs.stdtrit, 2, + xp_capabilities( + cpu_only=True, exceptions=["cupy"], # needs betainc + skip_backends=[("jax.numpy", "no scipy.optimize support")], + ), + generic_impl=_stdtrit, torch_native=False, + ), + _FuncInfo( + _ufuncs.tandg, 1, + xp_capabilities( + cpu_only=True, exceptions=["cupy"], + jax_jit=True, + ), + test_large_ints=False, torch_native=False, + ), + _FuncInfo(_ufuncs.xlog1py, 2), + _FuncInfo(_ufuncs.xlogy, 2, generic_impl=_xlogy), + _FuncInfo( + _ufuncs.y0, 1, + xp_capabilities( + cpu_only=True, exceptions=["cupy", "torch"], + jax_jit=True, + ), + alt_names_map={"torch": "bessel_y0"}, test_large_ints=False, + ), + _FuncInfo( + _ufuncs.y1, 1, + xp_capabilities( + cpu_only=True, exceptions=["cupy", "torch"], + jax_jit=True, + ), + alt_names_map={"torch": "bessel_y1"}, test_large_ints=False, + ), + _FuncInfo( + _ufuncs.yn, 2, + xp_capabilities( + cpu_only=True, exceptions=["cupy"], + jax_jit=True, + ), + positive_only={"cupy": (True, False)}, int_only=(True, False), + test_large_ints=False, torch_native=False, + ), + _FuncInfo( + _basic.zeta, 2, is_ufunc=False, + positive_only={"jax.numpy": True, "torch": (True, False)}, + test_large_ints=False, + ), + _FuncInfo( + _ufuncs.zetac, 1, + xp_capabilities( + cpu_only=True, exceptions=["cupy"], + jax_jit=True, + ), + torch_native=False, + ), +) + +# Override ufuncs. +# When SCIPY_ARRAY_API is disabled, this exclusively updates the docstrings in place +# and populates the xp_capabilities table, while retaining the original ufuncs. +globals().update({nfo.func.__name__: nfo.wrapper for nfo in _special_funcs}) +# digamma is an alias for psi. Define here so it also has alternative backend +# support. Add noqa because the linter gets confused by the sneaky way psi +# is inserted into globals above. +digamma = psi # type:ignore[name-defined] # noqa: F821 +__all__ = [nfo.func.__name__ for nfo in _special_funcs] + ["digamma"] diff --git a/venv/lib/python3.12/site-packages/scipy/special/_test_internal.cpython-312-x86_64-linux-gnu.so b/venv/lib/python3.12/site-packages/scipy/special/_test_internal.cpython-312-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..aa9d4ba427d00583713a5a7b92a13654be00b3e7 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/_test_internal.cpython-312-x86_64-linux-gnu.so differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/_test_internal.pyi b/venv/lib/python3.12/site-packages/scipy/special/_test_internal.pyi new file mode 100644 index 0000000000000000000000000000000000000000..881b731f8f105c3be38e56fd6dc9e48c6b829553 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/_test_internal.pyi @@ -0,0 +1,9 @@ +from typing import Literal + +import numpy as np + +def have_fenv() -> bool: ... +def random_double(size: int, rng: np.random.RandomState) -> np.ndarray[tuple[int], np.dtype[np.float64]]: ... +def test_add_round(size: int, mode: Literal["up", "down"], rng: np.random.RandomState) -> None: ... +def _dd_exp(xhi: float, xlo: float) -> tuple[float, float]: ... +def _dd_log(xhi: float, xlo: float) -> tuple[float, float]: ... diff --git a/venv/lib/python3.12/site-packages/scipy/special/_testutils.py b/venv/lib/python3.12/site-packages/scipy/special/_testutils.py new file mode 100644 index 0000000000000000000000000000000000000000..3ff4130dc3eb504a772f9b0ea2a2c74df714d999 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/_testutils.py @@ -0,0 +1,321 @@ +import os +import functools +import operator +from scipy._external.packaging_version import version + +import numpy as np +from numpy.testing import assert_ +import pytest + +import scipy.special as sc + +__all__ = ['with_special_errors', 'assert_func_equal', 'FuncData'] + + +#------------------------------------------------------------------------------ +# Check if a module is present to be used in tests +#------------------------------------------------------------------------------ + +class MissingModule: + def __init__(self, name): + self.name = name + + +def check_version(module, min_ver): + if type(module) is MissingModule: + return pytest.mark.skip(reason=f"{module.name} is not installed") + return pytest.mark.skipif( + version.parse(module.__version__) < version.Version(min_ver), + reason=f"{module.__name__} version >= {min_ver} required" + ) + + +#------------------------------------------------------------------------------ +# Enable convergence and loss of precision warnings -- turn off one by one +#------------------------------------------------------------------------------ + +def with_special_errors(func): + """ + Enable special function errors (such as underflow, overflow, + loss of precision, etc.) + """ + @functools.wraps(func) + def wrapper(*a, **kw): + with sc.errstate(all='raise'): + res = func(*a, **kw) + return res + return wrapper + + +#------------------------------------------------------------------------------ +# Comparing function values at many data points at once, with helpful +# error reports +#------------------------------------------------------------------------------ + +def assert_func_equal(func, results, points, rtol=None, atol=None, + param_filter=None, knownfailure=None, + vectorized=True, dtype=None, nan_ok=False, + ignore_inf_sign=False, distinguish_nan_and_inf=True): + if hasattr(points, 'next'): + # it's a generator + points = list(points) + + points = np.asarray(points) + if points.ndim == 1: + points = points[:,None] + nparams = points.shape[1] + + if hasattr(results, '__name__'): + # function + data = points + result_columns = None + result_func = results + else: + # dataset + data = np.c_[points, results] + result_columns = list(range(nparams, data.shape[1])) + result_func = None + + fdata = FuncData(func, data, list(range(nparams)), + result_columns=result_columns, result_func=result_func, + rtol=rtol, atol=atol, param_filter=param_filter, + knownfailure=knownfailure, nan_ok=nan_ok, vectorized=vectorized, + ignore_inf_sign=ignore_inf_sign, + distinguish_nan_and_inf=distinguish_nan_and_inf) + fdata.check() + + +class FuncData: + """ + Data set for checking a special function. + + Parameters + ---------- + func : function + Function to test + data : numpy array + columnar data to use for testing + param_columns : int or tuple of ints + Columns indices in which the parameters to `func` lie. + Can be imaginary integers to indicate that the parameter + should be cast to complex. + result_columns : int or tuple of ints, optional + Column indices for expected results from `func`. + result_func : callable, optional + Function to call to obtain results. + rtol : float, optional + Required relative tolerance. Default is 5*eps. + atol : float, optional + Required absolute tolerance. Default is 5*tiny. + param_filter : function, or tuple of functions/Nones, optional + Filter functions to exclude some parameter ranges. + If omitted, no filtering is done. + knownfailure : str, optional + Known failure error message to raise when the test is run. + If omitted, no exception is raised. + nan_ok : bool, optional + If nan is always an accepted result. + vectorized : bool, optional + Whether all functions passed in are vectorized. + ignore_inf_sign : bool, optional + Whether to ignore signs of infinities. + (Doesn't matter for complex-valued functions.) + distinguish_nan_and_inf : bool, optional + If True, treat numbers which contain nans or infs as + equal. Sets ignore_inf_sign to be True. + + """ + + def __init__(self, func, data, param_columns, result_columns=None, + result_func=None, rtol=None, atol=None, param_filter=None, + knownfailure=None, dataname=None, nan_ok=False, vectorized=True, + ignore_inf_sign=False, distinguish_nan_and_inf=True): + self.func = func + self.data = data + self.dataname = dataname + if not hasattr(param_columns, '__len__'): + param_columns = (param_columns,) + self.param_columns = tuple(param_columns) + if result_columns is not None: + if not hasattr(result_columns, '__len__'): + result_columns = (result_columns,) + self.result_columns = tuple(result_columns) + if result_func is not None: + message = "Only result_func or result_columns should be provided" + raise ValueError(message) + elif result_func is not None: + self.result_columns = None + else: + raise ValueError("Either result_func or result_columns should be provided") + self.result_func = result_func + self.rtol = rtol + self.atol = atol + if not hasattr(param_filter, '__len__'): + param_filter = (param_filter,) + self.param_filter = param_filter + self.knownfailure = knownfailure + self.nan_ok = nan_ok + self.vectorized = vectorized + self.ignore_inf_sign = ignore_inf_sign + self.distinguish_nan_and_inf = distinguish_nan_and_inf + if not self.distinguish_nan_and_inf: + self.ignore_inf_sign = True + + def get_tolerances(self, dtype): + if not np.issubdtype(dtype, np.inexact): + dtype = np.dtype(float) + info = np.finfo(dtype) + rtol, atol = self.rtol, self.atol + if rtol is None: + rtol = 5*info.eps + if atol is None: + atol = 5*info.tiny + return rtol, atol + + def check(self, data=None, dtype=None, dtypes=None): + """Check the special function against the data.""" + __tracebackhide__ = operator.methodcaller( + 'errisinstance', AssertionError + ) + + if self.knownfailure: + pytest.xfail(reason=self.knownfailure) + + if data is None: + data = self.data + + if dtype is None: + dtype = data.dtype + else: + data = data.astype(dtype) + + rtol, atol = self.get_tolerances(dtype) + + # Apply given filter functions + if self.param_filter: + param_mask = np.ones((data.shape[0],), np.bool_) + for j, filter in zip(self.param_columns, self.param_filter): + if filter: + param_mask &= list(filter(data[:,j])) + data = data[param_mask] + + # Pick parameters from the correct columns + params = [] + for idx, j in enumerate(self.param_columns): + if np.iscomplexobj(j): + j = int(j.imag) + params.append(data[:,j].astype(complex)) + elif dtypes and idx < len(dtypes): + params.append(data[:, j].astype(dtypes[idx])) + else: + params.append(data[:,j]) + + # Helper for evaluating results + def eval_func_at_params(func, skip_mask=None): + if self.vectorized: + got = func(*params) + else: + got = [] + for j in range(len(params[0])): + if skip_mask is not None and skip_mask[j]: + got.append(np.nan) + continue + got.append(func(*tuple([params[i][j] for i in range(len(params))]))) + got = np.asarray(got) + if not isinstance(got, tuple): + got = (got,) + return got + + # Evaluate function to be tested + got = eval_func_at_params(self.func) + + # Grab the correct results + if self.result_columns is not None: + # Correct results passed in with the data + wanted = tuple([data[:,icol] for icol in self.result_columns]) + else: + # Function producing correct results passed in + skip_mask = None + if self.nan_ok and len(got) == 1: + # Don't spend time evaluating what doesn't need to be evaluated + skip_mask = np.isnan(got[0]) + wanted = eval_func_at_params(self.result_func, skip_mask=skip_mask) + + # Check the validity of each output returned + assert_(len(got) == len(wanted)) + + for output_num, (x, y) in enumerate(zip(got, wanted)): + if np.issubdtype(x.dtype, np.complexfloating) or self.ignore_inf_sign: + pinf_x = np.isinf(x) + pinf_y = np.isinf(y) + minf_x = np.isinf(x) + minf_y = np.isinf(y) + else: + pinf_x = np.isposinf(x) + pinf_y = np.isposinf(y) + minf_x = np.isneginf(x) + minf_y = np.isneginf(y) + nan_x = np.isnan(x) + nan_y = np.isnan(y) + + with np.errstate(all='ignore'): + abs_y = np.absolute(y) + abs_y[~np.isfinite(abs_y)] = 0 + diff = np.absolute(x - y) + diff[~np.isfinite(diff)] = 0 + + rdiff = diff / np.absolute(y) + rdiff[~np.isfinite(rdiff)] = 0 + + tol_mask = (diff <= atol + rtol*abs_y) + pinf_mask = (pinf_x == pinf_y) + minf_mask = (minf_x == minf_y) + + nan_mask = (nan_x == nan_y) + + bad_j = ~(tol_mask & pinf_mask & minf_mask & nan_mask) + + point_count = bad_j.size + if self.nan_ok: + bad_j &= ~nan_x + bad_j &= ~nan_y + point_count -= (nan_x | nan_y).sum() + + if not self.distinguish_nan_and_inf and not self.nan_ok: + # If nan's are okay we've already covered all these cases + inf_x = np.isinf(x) + inf_y = np.isinf(y) + both_nonfinite = (inf_x & nan_y) | (nan_x & inf_y) + bad_j &= ~both_nonfinite + point_count -= both_nonfinite.sum() + + if np.any(bad_j): + # Some bad results: inform what, where, and how bad + msg = [""] + msg.append(f"Max |adiff|: {diff[bad_j].max():g}") + msg.append(f"Max |rdiff|: {rdiff[bad_j].max():g}") + msg.append(f"Bad results ({np.sum(bad_j)} out of " + f"{point_count}) for the following points " + f"(in output {output_num}):") + for j in np.nonzero(bad_j)[0]: + j = int(j) + def fmt(x): + return f'{np.array2string(x[j], precision=18):30s}' + a = " ".join(map(fmt, params)) + b = " ".join(map(fmt, got)) + c = " ".join(map(fmt, wanted)) + d = fmt(rdiff) + msg.append(f"{a} => {b} != {c} (rdiff {d})") + assert_(False, "\n".join(msg)) + + def __repr__(self): + """Pretty-printing""" + if np.any(list(map(np.iscomplexobj, self.param_columns))): + is_complex = " (complex)" + else: + is_complex = "" + if self.dataname: + return (f"") + else: + return f"" diff --git a/venv/lib/python3.12/site-packages/scipy/special/_ufunc_tools.py b/venv/lib/python3.12/site-packages/scipy/special/_ufunc_tools.py new file mode 100644 index 0000000000000000000000000000000000000000..d9f97ff78afa4857e18383c7e70c3fbd31da884f --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/_ufunc_tools.py @@ -0,0 +1,162 @@ +"""Helpers for producing efficient wrappers of ufuncs. +""" + +import re +import numpy as np + + +def _parse_core_ndims(signature): + """Return tuple of num core dims per input from gufunc signature.""" + input_sig = signature.split('->')[0] + groups = re.findall(r"\((.*?)\)", input_sig) + return tuple(0 if not g.strip() else g.count(',') + 1 for g in groups) + + +def _with_cache_optimization( + *, + name, + arg_names, + docstring, + ufunc, + cache_arg_indices, +): + """Helper to ensure optimal iteration order for ufuncs that use caching. + + This concerns internal caches which are only live over the course of one + call to a ufunc to avoid repeated computations during the course of the + loops. See the notes below for more information. + + Parameters + ---------- + name : str + arg_names : list[str] + The function name and arg names are passed in so that the wrapper + can be generated with the create name and argument names, improving + documentation and autocomplete. + docstring : str + ufunc : numpy.ufunc + cache_arg_indices : list[int] + Arguments to ufunc which are used in the kernel to compute an output + which is being cached for reuse when iterating over other arguments. + + Returns + ------- + callable + A wrapper for ufunc which transposes the axes of the inputs to ensure + iteration precedes in such a way to allow the cache within the ufunc + kernel to eliminate redundant computation. + + Notes + ----- + There is a common pattern in ufunc kernels exemplified by the situation + where some of the arguments are used to compute coeffients of an expansion + that is taken over one or more of the other arguments. A classic example is + Mathieu functions, which compute coefficients corresponding to the shape + parameter q and order m which in principle could be reused for varying + values of the parameter x. + + It had long been the case that the expensive computation of coefficients is + repeated unnecessarily across values of x. It is possible to add a cache to + the ufunc kernel which stores the expansion coefficients and only updates + if the pointers into the q and m arrays advance during the course of the + ufunc loops. Such a cache is instantiated each time a ufunc is called and + only lives during the course of the loops that are carried out for that + particular call. + + Whether such a cache actually helps depends on the order in which iteration + occurs. Ideally, one would want q and m to advance most slowly and for the + iterations over x for fixed q and m to be pushed to the inner most loops. + This helper replaces each input array (and a pre-allocated output array) + with a view where the axes which should vary most slowly are transposed to + the ends and forces computation in C order. This ensures iteration proceeds + in the optimal order. + + Note that because the pre-allocated output array used internally is C + contiguous, the output will be C contiguous regardless of contiguity of + the inputs. + + """ + + # Need to keep track of the number of core dimensions for each input + # since core dimensions don't participate in broadcasting. + core_ndims = ( + _parse_core_ndims(ufunc.signature) + if ufunc.signature is not None + else (0,)*ufunc.nin + ) + + def _wrapper(*args): + args = [np.asarray(arg) for arg in args] + + # Fast path for when the arguments which are used in the cached + # computation don't have batches. + if all(args[i].ndim == core_ndims[i] for i in cache_arg_indices): + return ufunc(*args) + + # To get batch_shapes, need to exclude core dimensions. Again, the core + # dimensions won't participate in broadcasting. + batch_shapes = [ + arg.shape[:-core_ndims[i]] if core_ndims[i] > 0 else arg.shape + for i, arg in enumerate(args) + ] + batch_shape = np.broadcast_shapes(*batch_shapes) + batch_ndim = len(batch_shape) + + # Broadcast each arg so that the batch shapes all agree, but the + # core dimensions may still differ. + args_b = [ + np.broadcast_to(arg, batch_shape + arg.shape[-core_ndims[i]:]) + if core_ndims[i] > 0 else np.broadcast_to(arg, batch_shape) + for i, arg in enumerate(args) + ] + + # After broadcasting, determine which axes have stride-length + # zero for each of the args participating in the cache. The cached + # value will remain the same for iterations across these axes. + varying_axes = [] + constant_axes = [] + + for ax in range(batch_ndim): + is_constant = all( + args_b[i].strides[ax] == 0 for i in cache_arg_indices + ) + if is_constant: + constant_axes.append(ax) + else: + varying_axes.append(ax) + sorted_batch_axes = varying_axes + constant_axes + + # Push the non-varying axes to the end so that they will vary most + # slowly when iterating in C order. + args_t = [] + for i, arg_b in enumerate(args_b): + axes = sorted_batch_axes + list( + range(batch_ndim, batch_ndim + core_ndims[i]) + ) + args_t.append(np.transpose(arg_b, axes=axes)) + + # Premake the output array. + input_dtypes = tuple(arg.dtype for arg in args_t) + (None,)*ufunc.nout + out_dtype = ufunc.resolve_dtypes(input_dtypes)[-1] + out_final = np.empty(batch_shape, dtype=out_dtype, order='C') + # a view of the output array with axes sorted as needed. + out_t = np.transpose(out_final, axes=sorted_batch_axes) + + # Set out to the above view, but return the C contiguous output. + # This avoids having non-contiguous output. + ufunc(*args_t, out=out_t, order='C') + return out_final + + # Do some metaprogramming with exec so that the arg names and func + # name are as expected. + arg_str = ", ".join(arg_names) + code = ( + f"""def {name}({arg_str}): + return _wrapper({arg_str}) + """ + ) + namespace = {"_wrapper": _wrapper} + exec(code, namespace) + wrapper = namespace[name] + wrapper.__doc__ = docstring + return wrapper diff --git a/venv/lib/python3.12/site-packages/scipy/special/_ufuncs.cpython-312-x86_64-linux-gnu.so b/venv/lib/python3.12/site-packages/scipy/special/_ufuncs.cpython-312-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..865df35720e065f5fa674ca618dc52a999785ec4 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/_ufuncs.cpython-312-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d68fb91d24c37818f6c2de52b63c8257c29617d62a58d1aabdc51dc9d055d4b5 +size 1496216 diff --git a/venv/lib/python3.12/site-packages/scipy/special/_ufuncs.pyi b/venv/lib/python3.12/site-packages/scipy/special/_ufuncs.pyi new file mode 100644 index 0000000000000000000000000000000000000000..73e74d31e9ad53644bc66b6896c7f62404562b83 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/_ufuncs.pyi @@ -0,0 +1,522 @@ +from typing import Any + +import numpy as np + +__all__ = [ + 'geterr', + 'seterr', + 'errstate', + 'agm', + 'airy', + 'airye', + 'bdtr', + 'bdtrc', + 'bdtri', + 'bdtrik', + 'bdtrin', + 'bei', + 'beip', + 'ber', + 'berp', + 'besselpoly', + 'beta', + 'betainc', + 'betaincc', + 'betainccinv', + 'betaincinv', + 'betaln', + 'binom', + 'boxcox', + 'boxcox1p', + 'btdtria', + 'btdtrib', + 'cbrt', + 'chdtr', + 'chdtrc', + 'chdtri', + 'chdtriv', + 'chndtr', + 'chndtridf', + 'chndtrinc', + 'chndtrix', + 'cosdg', + 'cosm1', + 'cotdg', + 'dawsn', + 'ellipe', + 'ellipeinc', + 'ellipj', + 'ellipk', + 'ellipkinc', + 'ellipkm1', + 'elliprc', + 'elliprd', + 'elliprf', + 'elliprg', + 'elliprj', + 'entr', + 'erf', + 'erfc', + 'erfcinv', + 'erfcx', + 'erfi', + 'erfinv', + 'eval_chebyc', + 'eval_chebys', + 'eval_chebyt', + 'eval_chebyu', + 'eval_gegenbauer', + 'eval_genlaguerre', + 'eval_hermite', + 'eval_hermitenorm', + 'eval_jacobi', + 'eval_laguerre', + 'eval_legendre', + 'eval_sh_chebyt', + 'eval_sh_chebyu', + 'eval_sh_jacobi', + 'eval_sh_legendre', + 'exp1', + 'exp10', + 'exp2', + 'expi', + 'expit', + 'expm1', + 'expn', + 'exprel', + 'fdtr', + 'fdtrc', + 'fdtri', + 'fdtridfd', + 'fresnel', + 'gamma', + 'gammainc', + 'gammaincc', + 'gammainccinv', + 'gammaincinv', + 'gammaln', + 'gammasgn', + 'gdtr', + 'gdtrc', + 'gdtria', + 'gdtrib', + 'gdtrix', + 'hankel1', + 'hankel1e', + 'hankel2', + 'hankel2e', + 'huber', + 'hyp0f1', + 'hyp1f1', + 'hyp2f1', + 'hyperu', + 'i0', + 'i0e', + 'i1', + 'i1e', + 'inv_boxcox', + 'inv_boxcox1p', + 'it2i0k0', + 'it2j0y0', + 'it2struve0', + 'itairy', + 'iti0k0', + 'itj0y0', + 'itmodstruve0', + 'itstruve0', + 'iv', + 'ive', + 'j0', + 'j1', + 'jn', + 'jv', + 'jve', + 'k0', + 'k0e', + 'k1', + 'k1e', + 'kei', + 'keip', + 'kelvin', + 'ker', + 'kerp', + 'kl_div', + 'kn', + 'kolmogi', + 'kolmogorov', + 'kv', + 'kve', + 'log1p', + 'log_expit', + 'log_ndtr', + 'log_wright_bessel', + 'loggamma', + 'logit', + 'lpmv', + 'mathieu_a', + 'mathieu_b', + 'mathieu_cem', + 'mathieu_modcem1', + 'mathieu_modcem2', + 'mathieu_modsem1', + 'mathieu_modsem2', + 'mathieu_sem', + 'modfresnelm', + 'modfresnelp', + 'modstruve', + 'nbdtr', + 'nbdtrc', + 'nbdtri', + 'nbdtrik', + 'nbdtrin', + 'ncfdtr', + 'ncfdtri', + 'ncfdtridfd', + 'ncfdtridfn', + 'ncfdtrinc', + 'nctdtr', + 'nctdtridf', + 'nctdtrinc', + 'nctdtrit', + 'ndtr', + 'ndtri', + 'ndtri_exp', + 'nrdtrimn', + 'nrdtrisd', + 'obl_ang1', + 'obl_ang1_cv', + 'obl_cv', + 'obl_rad1', + 'obl_rad1_cv', + 'obl_rad2', + 'obl_rad2_cv', + 'owens_t', + 'pbdv', + 'pbvv', + 'pbwa', + 'pdtr', + 'pdtrc', + 'pdtri', + 'pdtrik', + 'poch', + 'powm1', + 'pro_ang1', + 'pro_ang1_cv', + 'pro_cv', + 'pro_rad1', + 'pro_rad1_cv', + 'pro_rad2', + 'pro_rad2_cv', + 'pseudo_huber', + 'psi', + 'radian', + 'rel_entr', + 'rgamma', + 'round', + 'shichi', + 'sici', + 'sindg', + 'smirnov', + 'smirnovi', + 'spence', + 'stdtr', + 'stdtridf', + 'stdtrit', + 'struve', + 'tandg', + 'tklmbda', + 'voigt_profile', + 'wofz', + 'wright_bessel', + 'wrightomega', + 'xlog1py', + 'xlogy', + 'y0', + 'y1', + 'yn', + 'yv', + 'yve', + 'zetac' +] + +def geterr() -> dict[str, str]: ... +def seterr(**kwargs: str) -> dict[str, str]: ... + +class errstate: + def __init__(self, **kargs: str) -> None: ... + def __enter__(self) -> None: ... + def __exit__( + self, + exc_type: Any, # Unused + exc_value: Any, # Unused + traceback: Any, # Unused + ) -> None: ... + +_cosine_cdf: np.ufunc +_cosine_invcdf: np.ufunc +_bivariate_normal_cdf: np.ufunc +_cospi: np.ufunc +_ellip_harm: np.ufunc +_factorial: np.ufunc +_gen_harmonic: np.ufunc +_igam_fac: np.ufunc +_kolmogc: np.ufunc +_kolmogci: np.ufunc +_kolmogp: np.ufunc +_lambertw: np.ufunc +_lanczos_sum_expg_scaled: np.ufunc +_lgam1p: np.ufunc +_log1mexp: np.ufunc +_log1pmx: np.ufunc +_normalized_gen_harmonic: np.ufunc +_riemann_zeta: np.ufunc +_scaled_exp1: np.ufunc +_sf_error_test_function: np.ufunc +_sinpi: np.ufunc +_smirnovc: np.ufunc +_smirnovci: np.ufunc +_smirnovp: np.ufunc +_spherical_in: np.ufunc +_spherical_in_d: np.ufunc +_spherical_jn: np.ufunc +_spherical_jn_d: np.ufunc +_spherical_kn: np.ufunc +_spherical_kn_d: np.ufunc +_spherical_yn: np.ufunc +_spherical_yn_d: np.ufunc +_stirling2_inexact: np.ufunc +_struve_asymp_large_z: np.ufunc +_struve_bessel_series: np.ufunc +_struve_power_series: np.ufunc +_zeta: np.ufunc +agm: np.ufunc +airy: np.ufunc +airye: np.ufunc +bdtr: np.ufunc +bdtrc: np.ufunc +bdtri: np.ufunc +bdtrik: np.ufunc +bdtrin: np.ufunc +bei: np.ufunc +beip: np.ufunc +ber: np.ufunc +berp: np.ufunc +besselpoly: np.ufunc +beta: np.ufunc +betainc: np.ufunc +betaincc: np.ufunc +betainccinv: np.ufunc +betaincinv: np.ufunc +betaln: np.ufunc +binom: np.ufunc +boxcox1p: np.ufunc +boxcox: np.ufunc +btdtria: np.ufunc +btdtrib: np.ufunc +cbrt: np.ufunc +chdtr: np.ufunc +chdtrc: np.ufunc +chdtri: np.ufunc +chdtriv: np.ufunc +chndtr: np.ufunc +chndtridf: np.ufunc +chndtrinc: np.ufunc +chndtrix: np.ufunc +cosdg: np.ufunc +cosm1: np.ufunc +cotdg: np.ufunc +dawsn: np.ufunc +ellipe: np.ufunc +ellipeinc: np.ufunc +ellipj: np.ufunc +ellipk: np.ufunc +ellipkinc: np.ufunc +ellipkm1: np.ufunc +elliprc: np.ufunc +elliprd: np.ufunc +elliprf: np.ufunc +elliprg: np.ufunc +elliprj: np.ufunc +entr: np.ufunc +erf: np.ufunc +erfc: np.ufunc +erfcinv: np.ufunc +erfcx: np.ufunc +erfi: np.ufunc +erfinv: np.ufunc +eval_chebyc: np.ufunc +eval_chebys: np.ufunc +eval_chebyt: np.ufunc +eval_chebyu: np.ufunc +eval_gegenbauer: np.ufunc +eval_genlaguerre: np.ufunc +eval_hermite: np.ufunc +eval_hermitenorm: np.ufunc +eval_jacobi: np.ufunc +eval_laguerre: np.ufunc +eval_legendre: np.ufunc +eval_sh_chebyt: np.ufunc +eval_sh_chebyu: np.ufunc +eval_sh_jacobi: np.ufunc +eval_sh_legendre: np.ufunc +exp10: np.ufunc +exp1: np.ufunc +exp2: np.ufunc +expi: np.ufunc +expit: np.ufunc +expm1: np.ufunc +expn: np.ufunc +exprel: np.ufunc +fdtr: np.ufunc +fdtrc: np.ufunc +fdtri: np.ufunc +fdtridfd: np.ufunc +fresnel: np.ufunc +gamma: np.ufunc +gammainc: np.ufunc +gammaincc: np.ufunc +gammainccinv: np.ufunc +gammaincinv: np.ufunc +gammaln: np.ufunc +gammasgn: np.ufunc +gdtr: np.ufunc +gdtrc: np.ufunc +gdtria: np.ufunc +gdtrib: np.ufunc +gdtrix: np.ufunc +hankel1: np.ufunc +hankel1e: np.ufunc +hankel2: np.ufunc +hankel2e: np.ufunc +huber: np.ufunc +hyp0f1: np.ufunc +hyp1f1: np.ufunc +hyp2f1: np.ufunc +hyperu: np.ufunc +i0: np.ufunc +i0e: np.ufunc +i1: np.ufunc +i1e: np.ufunc +inv_boxcox1p: np.ufunc +inv_boxcox: np.ufunc +it2i0k0: np.ufunc +it2j0y0: np.ufunc +it2struve0: np.ufunc +itairy: np.ufunc +iti0k0: np.ufunc +itj0y0: np.ufunc +itmodstruve0: np.ufunc +itstruve0: np.ufunc +iv: np.ufunc +ive: np.ufunc +j0: np.ufunc +j1: np.ufunc +jn: np.ufunc +jv: np.ufunc +jve: np.ufunc +k0: np.ufunc +k0e: np.ufunc +k1: np.ufunc +k1e: np.ufunc +kei: np.ufunc +keip: np.ufunc +kelvin: np.ufunc +ker: np.ufunc +kerp: np.ufunc +kl_div: np.ufunc +kn: np.ufunc +kolmogi: np.ufunc +kolmogorov: np.ufunc +kv: np.ufunc +kve: np.ufunc +log1p: np.ufunc +log_expit: np.ufunc +log_ndtr: np.ufunc +log_wright_bessel: np.ufunc +loggamma: np.ufunc +logit: np.ufunc +lpmv: np.ufunc +mathieu_a: np.ufunc +mathieu_b: np.ufunc +mathieu_cem: np.ufunc +mathieu_modcem1: np.ufunc +mathieu_modcem2: np.ufunc +mathieu_modsem1: np.ufunc +mathieu_modsem2: np.ufunc +mathieu_sem: np.ufunc +modfresnelm: np.ufunc +modfresnelp: np.ufunc +modstruve: np.ufunc +nbdtr: np.ufunc +nbdtrc: np.ufunc +nbdtri: np.ufunc +nbdtrik: np.ufunc +nbdtrin: np.ufunc +ncfdtr: np.ufunc +ncfdtri: np.ufunc +ncfdtridfd: np.ufunc +ncfdtridfn: np.ufunc +ncfdtrinc: np.ufunc +nctdtr: np.ufunc +nctdtridf: np.ufunc +nctdtrinc: np.ufunc +nctdtrit: np.ufunc +ndtr: np.ufunc +ndtri: np.ufunc +ndtri_exp: np.ufunc +nrdtrimn: np.ufunc +nrdtrisd: np.ufunc +obl_ang1: np.ufunc +obl_ang1_cv: np.ufunc +obl_cv: np.ufunc +obl_rad1: np.ufunc +obl_rad1_cv: np.ufunc +obl_rad2: np.ufunc +obl_rad2_cv: np.ufunc +owens_t: np.ufunc +pbdv: np.ufunc +pbvv: np.ufunc +pbwa: np.ufunc +pdtr: np.ufunc +pdtrc: np.ufunc +pdtri: np.ufunc +pdtrik: np.ufunc +poch: np.ufunc +powm1: np.ufunc +pro_ang1: np.ufunc +pro_ang1_cv: np.ufunc +pro_cv: np.ufunc +pro_rad1: np.ufunc +pro_rad1_cv: np.ufunc +pro_rad2: np.ufunc +pro_rad2_cv: np.ufunc +pseudo_huber: np.ufunc +psi: np.ufunc +radian: np.ufunc +rel_entr: np.ufunc +rgamma: np.ufunc +round: np.ufunc +shichi: np.ufunc +sici: np.ufunc +sindg: np.ufunc +smirnov: np.ufunc +smirnovi: np.ufunc +spence: np.ufunc +stdtr: np.ufunc +stdtridf: np.ufunc +stdtrit: np.ufunc +struve: np.ufunc +tandg: np.ufunc +tklmbda: np.ufunc +voigt_profile: np.ufunc +wofz: np.ufunc +wright_bessel: np.ufunc +wrightomega: np.ufunc +xlog1py: np.ufunc +xlogy: np.ufunc +y0: np.ufunc +y1: np.ufunc +yn: np.ufunc +yv: np.ufunc +yve: np.ufunc +zetac: np.ufunc diff --git a/venv/lib/python3.12/site-packages/scipy/special/_ufuncs_cxx.cpython-312-x86_64-linux-gnu.so b/venv/lib/python3.12/site-packages/scipy/special/_ufuncs_cxx.cpython-312-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..6ccf8f19fa935cebaedbd7d347f273e5dd87a52a --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/_ufuncs_cxx.cpython-312-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:28dd61ac417abb7d4b9917d787d87146ba5ce6df1ce7e42f5a993e7aea66c9d7 +size 2081448 diff --git a/venv/lib/python3.12/site-packages/scipy/special/add_newdocs.py b/venv/lib/python3.12/site-packages/scipy/special/add_newdocs.py new file mode 100644 index 0000000000000000000000000000000000000000..5549717d35710d71655e42c836625cde9346bcc3 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/add_newdocs.py @@ -0,0 +1,15 @@ +# This file is not meant for public use and will be removed in SciPy v2.0.0. + +from scipy._lib.deprecation import _sub_module_deprecation + +__all__: list[str] = [] + + +def __dir__(): + return __all__ + + +def __getattr__(name): + return _sub_module_deprecation(sub_package="special", module="add_newdocs", + private_modules=["_add_newdocs"], all=__all__, + attribute=name) diff --git a/venv/lib/python3.12/site-packages/scipy/special/basic.py b/venv/lib/python3.12/site-packages/scipy/special/basic.py new file mode 100644 index 0000000000000000000000000000000000000000..46c179a893c4c9022a7cdd77c8923ff5a012b0eb --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/basic.py @@ -0,0 +1,84 @@ +# This file is not meant for public use and will be removed in SciPy v2.0.0. +# Use the `scipy.special` namespace for importing the functions +# included below. + +from scipy._lib.deprecation import _sub_module_deprecation + + +__all__ = [ # noqa: F822 + 'ai_zeros', + 'assoc_laguerre', + 'bei_zeros', + 'beip_zeros', + 'ber_zeros', + 'bernoulli', + 'berp_zeros', + 'bi_zeros', + 'comb', + 'digamma', + 'diric', + 'erf_zeros', + 'euler', + 'factorial', + 'factorial2', + 'factorialk', + 'fresnel_zeros', + 'fresnelc_zeros', + 'fresnels_zeros', + 'gamma', + 'h1vp', + 'h2vp', + 'hankel1', + 'hankel2', + 'iv', + 'ivp', + 'jn_zeros', + 'jnjnp_zeros', + 'jnp_zeros', + 'jnyn_zeros', + 'jv', + 'jvp', + 'kei_zeros', + 'keip_zeros', + 'kelvin_zeros', + 'ker_zeros', + 'kerp_zeros', + 'kv', + 'kvp', + 'lmbda', + 'lqmn', + 'lqn', + 'mathieu_a', + 'mathieu_b', + 'mathieu_even_coef', + 'mathieu_odd_coef', + 'obl_cv_seq', + 'pbdn_seq', + 'pbdv_seq', + 'pbvv_seq', + 'perm', + 'polygamma', + 'pro_cv_seq', + 'psi', + 'riccati_jn', + 'riccati_yn', + 'sinc', + 'y0_zeros', + 'y1_zeros', + 'y1p_zeros', + 'yn_zeros', + 'ynp_zeros', + 'yv', + 'yvp', + 'zeta' +] + + +def __dir__(): + return __all__ + + +def __getattr__(name): + return _sub_module_deprecation(sub_package="special", module="basic", + private_modules=["_basic", "_ufuncs"], all=__all__, + attribute=name) diff --git a/venv/lib/python3.12/site-packages/scipy/special/cython_special.cpython-312-x86_64-linux-gnu.so b/venv/lib/python3.12/site-packages/scipy/special/cython_special.cpython-312-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..a8b7feff637c6539cd436288d5276b51d611f424 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/cython_special.cpython-312-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ebd81a54547f57694319ac176a50b7a148e1f41c1dae687a65e2347454b9e699 +size 2446128 diff --git a/venv/lib/python3.12/site-packages/scipy/special/cython_special.pxd b/venv/lib/python3.12/site-packages/scipy/special/cython_special.pxd new file mode 100644 index 0000000000000000000000000000000000000000..7a6fbae7a279708ab702ef544c7b17250f81f6c1 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/cython_special.pxd @@ -0,0 +1,258 @@ + +ctypedef fused number_t: + double complex + double + +cpdef number_t spherical_jn(Py_ssize_t n, number_t z, bint derivative=*) noexcept nogil +cpdef number_t spherical_yn(Py_ssize_t n, number_t z, bint derivative=*) noexcept nogil +cpdef number_t spherical_in(Py_ssize_t n, number_t z, bint derivative=*) noexcept nogil +cpdef number_t spherical_kn(Py_ssize_t n, number_t z, bint derivative=*) noexcept nogil + +ctypedef fused Dd_number_t: + double complex + double + +ctypedef fused df_number_t: + double + float + +ctypedef fused dfg_number_t: + double + float + long double + +ctypedef fused dlp_number_t: + double + long + Py_ssize_t + +cpdef double voigt_profile(double x0, double x1, double x2) noexcept nogil +cpdef double agm(double x0, double x1) noexcept nogil +cdef void airy(Dd_number_t x0, Dd_number_t *y0, Dd_number_t *y1, Dd_number_t *y2, Dd_number_t *y3) noexcept nogil +cdef void airye(Dd_number_t x0, Dd_number_t *y0, Dd_number_t *y1, Dd_number_t *y2, Dd_number_t *y3) noexcept nogil +cpdef double bdtr(double x0, dlp_number_t x1, double x2) noexcept nogil +cpdef double bdtrc(double x0, dlp_number_t x1, double x2) noexcept nogil +cpdef double bdtri(double x0, dlp_number_t x1, double x2) noexcept nogil +cpdef double bdtrik(double x0, double x1, double x2) noexcept nogil +cpdef double bdtrin(double x0, double x1, double x2) noexcept nogil +cpdef double bei(double x0) noexcept nogil +cpdef double beip(double x0) noexcept nogil +cpdef double ber(double x0) noexcept nogil +cpdef double berp(double x0) noexcept nogil +cpdef double besselpoly(double x0, double x1, double x2) noexcept nogil +cpdef double beta(double x0, double x1) noexcept nogil +cpdef df_number_t betainc(df_number_t x0, df_number_t x1, df_number_t x2) noexcept nogil +cpdef df_number_t betaincc(df_number_t x0, df_number_t x1, df_number_t x2) noexcept nogil +cpdef df_number_t betaincinv(df_number_t x0, df_number_t x1, df_number_t x2) noexcept nogil +cpdef df_number_t betainccinv(df_number_t x0, df_number_t x1, df_number_t x2) noexcept nogil +cpdef double betaln(double x0, double x1) noexcept nogil +cpdef double binom(double x0, double x1) noexcept nogil +cpdef double boxcox(double x0, double x1) noexcept nogil +cpdef double boxcox1p(double x0, double x1) noexcept nogil +cpdef df_number_t btdtria(df_number_t x0, df_number_t x1, df_number_t x2) noexcept nogil +cpdef df_number_t btdtrib(df_number_t x0, df_number_t x1, df_number_t x2) noexcept nogil +cpdef double cbrt(double x0) noexcept nogil +cpdef double chdtr(double x0, double x1) noexcept nogil +cpdef double chdtrc(double x0, double x1) noexcept nogil +cpdef double chdtri(double x0, double x1) noexcept nogil +cpdef df_number_t chdtriv(df_number_t x0, df_number_t x1) noexcept nogil +cpdef df_number_t chndtr(df_number_t x0, df_number_t x1, df_number_t x2) noexcept nogil +cpdef df_number_t chndtridf(df_number_t x0, df_number_t x1, df_number_t x2) noexcept nogil +cpdef df_number_t chndtrinc(df_number_t x0, df_number_t x1, df_number_t x2) noexcept nogil +cpdef df_number_t chndtrix(df_number_t x0, df_number_t x1, df_number_t x2) noexcept nogil +cpdef double cosdg(double x0) noexcept nogil +cpdef double cosm1(double x0) noexcept nogil +cpdef double cotdg(double x0) noexcept nogil +cpdef Dd_number_t dawsn(Dd_number_t x0) noexcept nogil +cpdef double ellipe(double x0) noexcept nogil +cpdef double ellipeinc(double x0, double x1) noexcept nogil +cdef void ellipj(double x0, double x1, double *y0, double *y1, double *y2, double *y3) noexcept nogil +cpdef double ellipkinc(double x0, double x1) noexcept nogil +cpdef double ellipkm1(double x0) noexcept nogil +cpdef double ellipk(double x0) noexcept nogil +cpdef Dd_number_t elliprc(Dd_number_t x0, Dd_number_t x1) noexcept nogil +cpdef Dd_number_t elliprd(Dd_number_t x0, Dd_number_t x1, Dd_number_t x2) noexcept nogil +cpdef Dd_number_t elliprf(Dd_number_t x0, Dd_number_t x1, Dd_number_t x2) noexcept nogil +cpdef Dd_number_t elliprg(Dd_number_t x0, Dd_number_t x1, Dd_number_t x2) noexcept nogil +cpdef Dd_number_t elliprj(Dd_number_t x0, Dd_number_t x1, Dd_number_t x2, Dd_number_t x3) noexcept nogil +cpdef double entr(double x0) noexcept nogil +cpdef Dd_number_t erf(Dd_number_t x0) noexcept nogil +cpdef Dd_number_t erfc(Dd_number_t x0) noexcept nogil +cpdef Dd_number_t erfcx(Dd_number_t x0) noexcept nogil +cpdef Dd_number_t erfi(Dd_number_t x0) noexcept nogil +cpdef df_number_t erfinv(df_number_t x0) noexcept nogil +cpdef double erfcinv(double x0) noexcept nogil +cpdef Dd_number_t eval_chebyc(dlp_number_t x0, Dd_number_t x1) noexcept nogil +cpdef Dd_number_t eval_chebys(dlp_number_t x0, Dd_number_t x1) noexcept nogil +cpdef Dd_number_t eval_chebyt(dlp_number_t x0, Dd_number_t x1) noexcept nogil +cpdef Dd_number_t eval_chebyu(dlp_number_t x0, Dd_number_t x1) noexcept nogil +cpdef Dd_number_t eval_gegenbauer(dlp_number_t x0, double x1, Dd_number_t x2) noexcept nogil +cpdef Dd_number_t eval_genlaguerre(dlp_number_t x0, double x1, Dd_number_t x2) noexcept nogil +cpdef double eval_hermite(Py_ssize_t x0, double x1) noexcept nogil +cpdef double eval_hermitenorm(Py_ssize_t x0, double x1) noexcept nogil +cpdef Dd_number_t eval_jacobi(dlp_number_t x0, double x1, double x2, Dd_number_t x3) noexcept nogil +cpdef Dd_number_t eval_laguerre(dlp_number_t x0, Dd_number_t x1) noexcept nogil +cpdef Dd_number_t eval_legendre(dlp_number_t x0, Dd_number_t x1) noexcept nogil +cpdef Dd_number_t eval_sh_chebyt(dlp_number_t x0, Dd_number_t x1) noexcept nogil +cpdef Dd_number_t eval_sh_chebyu(dlp_number_t x0, Dd_number_t x1) noexcept nogil +cpdef Dd_number_t eval_sh_jacobi(dlp_number_t x0, double x1, double x2, Dd_number_t x3) noexcept nogil +cpdef Dd_number_t eval_sh_legendre(dlp_number_t x0, Dd_number_t x1) noexcept nogil +cpdef Dd_number_t exp1(Dd_number_t x0) noexcept nogil +cpdef double exp10(double x0) noexcept nogil +cpdef double exp2(double x0) noexcept nogil +cpdef Dd_number_t expi(Dd_number_t x0) noexcept nogil +cpdef dfg_number_t expit(dfg_number_t x0) noexcept nogil +cpdef Dd_number_t expm1(Dd_number_t x0) noexcept nogil +cpdef double expn(dlp_number_t x0, double x1) noexcept nogil +cpdef double exprel(double x0) noexcept nogil +cpdef df_number_t fdtr(df_number_t x0, df_number_t x1, df_number_t x2) noexcept nogil +cpdef df_number_t fdtrc(df_number_t x0, df_number_t x1, df_number_t x2) noexcept nogil +cpdef df_number_t fdtri(df_number_t x0, df_number_t x1, df_number_t x2) noexcept nogil +cpdef double fdtridfd(double x0, double x1, double x2) noexcept nogil +cdef void fresnel(Dd_number_t x0, Dd_number_t *y0, Dd_number_t *y1) noexcept nogil +cpdef Dd_number_t gamma(Dd_number_t x0) noexcept nogil +cpdef double gammainc(double x0, double x1) noexcept nogil +cpdef double gammaincc(double x0, double x1) noexcept nogil +cpdef double gammainccinv(double x0, double x1) noexcept nogil +cpdef double gammaincinv(double x0, double x1) noexcept nogil +cpdef double gammaln(double x0) noexcept nogil +cpdef double gammasgn(double x0) noexcept nogil +cpdef double gdtr(double x0, double x1, double x2) noexcept nogil +cpdef double gdtrc(double x0, double x1, double x2) noexcept nogil +cpdef double gdtria(double x0, double x1, double x2) noexcept nogil +cpdef double gdtrib(double x0, double x1, double x2) noexcept nogil +cpdef double gdtrix(double x0, double x1, double x2) noexcept nogil +cpdef double complex hankel1(double x0, double complex x1) noexcept nogil +cpdef double complex hankel1e(double x0, double complex x1) noexcept nogil +cpdef double complex hankel2(double x0, double complex x1) noexcept nogil +cpdef double complex hankel2e(double x0, double complex x1) noexcept nogil +cpdef double huber(double x0, double x1) noexcept nogil +cpdef Dd_number_t hyp0f1(double x0, Dd_number_t x1) noexcept nogil +cpdef Dd_number_t hyp1f1(double x0, double x1, Dd_number_t x2) noexcept nogil +cpdef Dd_number_t hyp2f1(double x0, double x1, double x2, Dd_number_t x3) noexcept nogil +cpdef double hyperu(double x0, double x1, double x2) noexcept nogil +cpdef double i0(double x0) noexcept nogil +cpdef double i0e(double x0) noexcept nogil +cpdef double i1(double x0) noexcept nogil +cpdef double i1e(double x0) noexcept nogil +cpdef double inv_boxcox(double x0, double x1) noexcept nogil +cpdef double inv_boxcox1p(double x0, double x1) noexcept nogil +cdef void it2i0k0(double x0, double *y0, double *y1) noexcept nogil +cdef void it2j0y0(double x0, double *y0, double *y1) noexcept nogil +cpdef double it2struve0(double x0) noexcept nogil +cdef void itairy(double x0, double *y0, double *y1, double *y2, double *y3) noexcept nogil +cdef void iti0k0(double x0, double *y0, double *y1) noexcept nogil +cdef void itj0y0(double x0, double *y0, double *y1) noexcept nogil +cpdef double itmodstruve0(double x0) noexcept nogil +cpdef double itstruve0(double x0) noexcept nogil +cpdef Dd_number_t iv(double x0, Dd_number_t x1) noexcept nogil +cpdef Dd_number_t ive(double x0, Dd_number_t x1) noexcept nogil +cpdef double j0(double x0) noexcept nogil +cpdef double j1(double x0) noexcept nogil +cpdef Dd_number_t jv(double x0, Dd_number_t x1) noexcept nogil +cpdef Dd_number_t jve(double x0, Dd_number_t x1) noexcept nogil +cpdef double k0(double x0) noexcept nogil +cpdef double k0e(double x0) noexcept nogil +cpdef double k1(double x0) noexcept nogil +cpdef double k1e(double x0) noexcept nogil +cpdef double kei(double x0) noexcept nogil +cpdef double keip(double x0) noexcept nogil +cdef void kelvin(double x0, double complex *y0, double complex *y1, double complex *y2, double complex *y3) noexcept nogil +cpdef double ker(double x0) noexcept nogil +cpdef double kerp(double x0) noexcept nogil +cpdef double kl_div(double x0, double x1) noexcept nogil +cpdef double kn(dlp_number_t x0, double x1) noexcept nogil +cpdef double kolmogi(double x0) noexcept nogil +cpdef double kolmogorov(double x0) noexcept nogil +cpdef Dd_number_t kv(double x0, Dd_number_t x1) noexcept nogil +cpdef Dd_number_t kve(double x0, Dd_number_t x1) noexcept nogil +cpdef Dd_number_t log1p(Dd_number_t x0) noexcept nogil +cpdef dfg_number_t log_expit(dfg_number_t x0) noexcept nogil +cpdef Dd_number_t log_ndtr(Dd_number_t x0) noexcept nogil +cpdef Dd_number_t loggamma(Dd_number_t x0) noexcept nogil +cpdef dfg_number_t logit(dfg_number_t x0) noexcept nogil +cpdef double lpmv(double x0, double x1, double x2) noexcept nogil +cpdef double mathieu_a(double x0, double x1) noexcept nogil +cpdef double mathieu_b(double x0, double x1) noexcept nogil +cdef void mathieu_cem(double x0, double x1, double x2, double *y0, double *y1) noexcept nogil +cdef void mathieu_modcem1(double x0, double x1, double x2, double *y0, double *y1) noexcept nogil +cdef void mathieu_modcem2(double x0, double x1, double x2, double *y0, double *y1) noexcept nogil +cdef void mathieu_modsem1(double x0, double x1, double x2, double *y0, double *y1) noexcept nogil +cdef void mathieu_modsem2(double x0, double x1, double x2, double *y0, double *y1) noexcept nogil +cdef void mathieu_sem(double x0, double x1, double x2, double *y0, double *y1) noexcept nogil +cdef void modfresnelm(double x0, double complex *y0, double complex *y1) noexcept nogil +cdef void modfresnelp(double x0, double complex *y0, double complex *y1) noexcept nogil +cpdef double modstruve(double x0, double x1) noexcept nogil +cpdef double nbdtr(dlp_number_t x0, dlp_number_t x1, double x2) noexcept nogil +cpdef double nbdtrc(dlp_number_t x0, dlp_number_t x1, double x2) noexcept nogil +cpdef double nbdtri(dlp_number_t x0, dlp_number_t x1, double x2) noexcept nogil +cpdef double nbdtrik(double x0, double x1, double x2) noexcept nogil +cpdef double nbdtrin(double x0, double x1, double x2) noexcept nogil +cpdef df_number_t ncfdtr(df_number_t x0, df_number_t x1, df_number_t x2, df_number_t x3) noexcept nogil +cpdef df_number_t ncfdtri(df_number_t x0, df_number_t x1, df_number_t x2, df_number_t x3) noexcept nogil +cpdef double ncfdtridfd(double x0, double x1, double x2, double x3) noexcept nogil +cpdef double ncfdtridfn(double x0, double x1, double x2, double x3) noexcept nogil +cpdef double ncfdtrinc(double x0, double x1, double x2, double x3) noexcept nogil +cpdef df_number_t nctdtr(df_number_t x0, df_number_t x1, df_number_t x2) noexcept nogil +cpdef double nctdtridf(double x0, double x1, double x2) noexcept nogil +cpdef double nctdtrinc(double x0, double x1, double x2) noexcept nogil +cpdef df_number_t nctdtrit(df_number_t x0, df_number_t x1, df_number_t x2) noexcept nogil +cpdef Dd_number_t ndtr(Dd_number_t x0) noexcept nogil +cpdef double ndtri(double x0) noexcept nogil +cpdef double nrdtrimn(double x0, double x1, double x2) noexcept nogil +cpdef double nrdtrisd(double x0, double x1, double x2) noexcept nogil +cdef void obl_ang1(double x0, double x1, double x2, double x3, double *y0, double *y1) noexcept nogil +cdef void obl_ang1_cv(double x0, double x1, double x2, double x3, double x4, double *y0, double *y1) noexcept nogil +cpdef double obl_cv(double x0, double x1, double x2) noexcept nogil +cdef void obl_rad1(double x0, double x1, double x2, double x3, double *y0, double *y1) noexcept nogil +cdef void obl_rad1_cv(double x0, double x1, double x2, double x3, double x4, double *y0, double *y1) noexcept nogil +cdef void obl_rad2(double x0, double x1, double x2, double x3, double *y0, double *y1) noexcept nogil +cdef void obl_rad2_cv(double x0, double x1, double x2, double x3, double x4, double *y0, double *y1) noexcept nogil +cpdef double owens_t(double x0, double x1) noexcept nogil +cdef void pbdv(double x0, double x1, double *y0, double *y1) noexcept nogil +cdef void pbvv(double x0, double x1, double *y0, double *y1) noexcept nogil +cdef void pbwa(double x0, double x1, double *y0, double *y1) noexcept nogil +cpdef double pdtr(double x0, double x1) noexcept nogil +cpdef double pdtrc(double x0, double x1) noexcept nogil +cpdef double pdtri(dlp_number_t x0, double x1) noexcept nogil +cpdef df_number_t pdtrik(df_number_t x0, df_number_t x1) noexcept nogil +cpdef double poch(double x0, double x1) noexcept nogil +cpdef df_number_t powm1(df_number_t x0, df_number_t x1) noexcept nogil +cdef void pro_ang1(double x0, double x1, double x2, double x3, double *y0, double *y1) noexcept nogil +cdef void pro_ang1_cv(double x0, double x1, double x2, double x3, double x4, double *y0, double *y1) noexcept nogil +cpdef double pro_cv(double x0, double x1, double x2) noexcept nogil +cdef void pro_rad1(double x0, double x1, double x2, double x3, double *y0, double *y1) noexcept nogil +cdef void pro_rad1_cv(double x0, double x1, double x2, double x3, double x4, double *y0, double *y1) noexcept nogil +cdef void pro_rad2(double x0, double x1, double x2, double x3, double *y0, double *y1) noexcept nogil +cdef void pro_rad2_cv(double x0, double x1, double x2, double x3, double x4, double *y0, double *y1) noexcept nogil +cpdef double pseudo_huber(double x0, double x1) noexcept nogil +cpdef Dd_number_t psi(Dd_number_t x0) noexcept nogil +cpdef double radian(double x0, double x1, double x2) noexcept nogil +cpdef double rel_entr(double x0, double x1) noexcept nogil +cpdef Dd_number_t rgamma(Dd_number_t x0) noexcept nogil +cpdef double round(double x0) noexcept nogil +cdef void shichi(Dd_number_t x0, Dd_number_t *y0, Dd_number_t *y1) noexcept nogil +cdef void sici(Dd_number_t x0, Dd_number_t *y0, Dd_number_t *y1) noexcept nogil +cpdef double sindg(double x0) noexcept nogil +cpdef double smirnov(dlp_number_t x0, double x1) noexcept nogil +cpdef double smirnovi(dlp_number_t x0, double x1) noexcept nogil +cpdef Dd_number_t spence(Dd_number_t x0) noexcept nogil +cpdef df_number_t stdtr(df_number_t x0, df_number_t x1) noexcept nogil +cpdef double stdtridf(double x0, double x1) noexcept nogil +cpdef df_number_t stdtrit(df_number_t x0, df_number_t x1) noexcept nogil +cpdef double struve(double x0, double x1) noexcept nogil +cpdef double tandg(double x0) noexcept nogil +cpdef double tklmbda(double x0, double x1) noexcept nogil +cpdef double complex wofz(double complex x0) noexcept nogil +cpdef Dd_number_t wrightomega(Dd_number_t x0) noexcept nogil +cpdef Dd_number_t xlog1py(Dd_number_t x0, Dd_number_t x1) noexcept nogil +cpdef Dd_number_t xlogy(Dd_number_t x0, Dd_number_t x1) noexcept nogil +cpdef double y0(double x0) noexcept nogil +cpdef double y1(double x0) noexcept nogil +cpdef double yn(dlp_number_t x0, double x1) noexcept nogil +cpdef Dd_number_t yv(double x0, Dd_number_t x1) noexcept nogil +cpdef Dd_number_t yve(double x0, Dd_number_t x1) noexcept nogil +cpdef double zetac(double x0) noexcept nogil +cpdef double wright_bessel(double x0, double x1, double x2) noexcept nogil +cpdef double log_wright_bessel(double x0, double x1, double x2) noexcept nogil +cpdef double ndtri_exp(double x0) noexcept nogil diff --git a/venv/lib/python3.12/site-packages/scipy/special/cython_special.pyi b/venv/lib/python3.12/site-packages/scipy/special/cython_special.pyi new file mode 100644 index 0000000000000000000000000000000000000000..024e962b10df8892631eaad20223f7fc8378ea83 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/cython_special.pyi @@ -0,0 +1,3 @@ +from typing import Any + +def __getattr__(name) -> Any: ... diff --git a/venv/lib/python3.12/site-packages/scipy/special/orthogonal.py b/venv/lib/python3.12/site-packages/scipy/special/orthogonal.py new file mode 100644 index 0000000000000000000000000000000000000000..0b13a08a96cb683d72a4a00d6962446e1779c88a --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/orthogonal.py @@ -0,0 +1,45 @@ +# This file is not meant for public use and will be removed in SciPy v2.0.0. +# Use the `scipy.special` namespace for importing the functions +# included below. + +from scipy._lib.deprecation import _sub_module_deprecation + + +_polyfuns = ['legendre', 'chebyt', 'chebyu', 'chebyc', 'chebys', + 'jacobi', 'laguerre', 'genlaguerre', 'hermite', + 'hermitenorm', 'gegenbauer', 'sh_legendre', 'sh_chebyt', + 'sh_chebyu', 'sh_jacobi'] + +# Correspondence between new and old names of root functions +_rootfuns_map = {'roots_legendre': 'p_roots', + 'roots_chebyt': 't_roots', + 'roots_chebyu': 'u_roots', + 'roots_chebyc': 'c_roots', + 'roots_chebys': 's_roots', + 'roots_jacobi': 'j_roots', + 'roots_laguerre': 'l_roots', + 'roots_genlaguerre': 'la_roots', + 'roots_hermite': 'h_roots', + 'roots_hermitenorm': 'he_roots', + 'roots_gegenbauer': 'cg_roots', + 'roots_sh_legendre': 'ps_roots', + 'roots_sh_chebyt': 'ts_roots', + 'roots_sh_chebyu': 'us_roots', + 'roots_sh_jacobi': 'js_roots'} + + +__all__ = _polyfuns + list(_rootfuns_map.keys()) + [ # noqa: F822 + 'airy', 'p_roots', 't_roots', 'u_roots', 'c_roots', 's_roots', + 'j_roots', 'l_roots', 'la_roots', 'h_roots', 'he_roots', 'cg_roots', + 'ps_roots', 'ts_roots', 'us_roots', 'js_roots' +] + + +def __dir__(): + return __all__ + + +def __getattr__(name): + return _sub_module_deprecation(sub_package="special", module="orthogonal", + private_modules=["_orthogonal"], all=__all__, + attribute=name) diff --git a/venv/lib/python3.12/site-packages/scipy/special/sf_error.py b/venv/lib/python3.12/site-packages/scipy/special/sf_error.py new file mode 100644 index 0000000000000000000000000000000000000000..00ff73756acd4219a4ba94eb089bce7d4c32266d --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/sf_error.py @@ -0,0 +1,20 @@ +# This file is not meant for public use and will be removed in SciPy v2.0.0. +# Use the `scipy.special` namespace for importing the functions +# included below. + +from scipy._lib.deprecation import _sub_module_deprecation + +__all__ = [ # noqa: F822 + 'SpecialFunctionWarning', + 'SpecialFunctionError' +] + + +def __dir__(): + return __all__ + + +def __getattr__(name): + return _sub_module_deprecation(sub_package="special", module="sf_error", + private_modules=["_sf_error"], all=__all__, + attribute=name) diff --git a/venv/lib/python3.12/site-packages/scipy/special/specfun.py b/venv/lib/python3.12/site-packages/scipy/special/specfun.py new file mode 100644 index 0000000000000000000000000000000000000000..f23284e66f19b55ca1dee380bef02c9a26662ef8 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/specfun.py @@ -0,0 +1,21 @@ +# This file is not meant for public use and will be removed in SciPy v2.0.0. +# Use the `scipy.special` namespace for importing the functions +# included below. + +from scipy._lib.deprecation import _sub_module_deprecation + +# ruff: noqa: F822 +__all__ = [ + 'lqmn', + 'pbdv' +] + + +def __dir__(): + return __all__ + + +def __getattr__(name): + return _sub_module_deprecation(sub_package="special", module="specfun", + private_modules=["_basic", "_specfun"], all=__all__, + attribute=name) diff --git a/venv/lib/python3.12/site-packages/scipy/special/spfun_stats.py b/venv/lib/python3.12/site-packages/scipy/special/spfun_stats.py new file mode 100644 index 0000000000000000000000000000000000000000..a1e58487aaa547483c9f2531ac4efc2ad5e4795c --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/spfun_stats.py @@ -0,0 +1,17 @@ +# This file is not meant for public use and will be removed in SciPy v2.0.0. +# Use the `scipy.special` namespace for importing the functions +# included below. + +from scipy._lib.deprecation import _sub_module_deprecation + +__all__ = ['multigammaln'] # noqa: F822 + + +def __dir__(): + return __all__ + + +def __getattr__(name): + return _sub_module_deprecation(sub_package="special", module="spfun_stats", + private_modules=["_spfun_stats"], all=__all__, + attribute=name) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__init__.py b/venv/lib/python3.12/site-packages/scipy/special/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8727150882c0a47fd8ae94f309540b2a1ed020e2 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/__init__.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_basic.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_basic.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..41ddb75e37beee660d9eab7ba76e1c28c3f3d013 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_basic.cpython-312.pyc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3fa95c55659533dc3a0db567073658573b92a9e49b0d30190f9ebd93239c0d9b +size 318540 diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_bdtr.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_bdtr.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7c5c2fd43bce765cedf1dca37365cc76ec55aad2 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_bdtr.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_boost_ufuncs.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_boost_ufuncs.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6e70deef1cac250f81e00ab65e985a51a0ca9a72 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_boost_ufuncs.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_boxcox.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_boxcox.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..89fbf36f8c54e68534ab6a52b6189e183cbe6b28 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_boxcox.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_cdflib.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_cdflib.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d41d9db19c0f5f92e84497c94f172940f7f0baf9 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_cdflib.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_cdft_asymptotic.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_cdft_asymptotic.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1fea06c25fe0b011a44e4c89c478a500db5e4dea Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_cdft_asymptotic.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_cephes_intp_cast.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_cephes_intp_cast.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..be8a7197d22ddeb4a240a57420e49c385de604f9 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_cephes_intp_cast.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_cosine_distr.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_cosine_distr.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b088ab4ccfbb434332a6326617e011ecdc97bf5c Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_cosine_distr.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_cython_abi.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_cython_abi.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7b77012d8b8a54c68b1ef819b4781a08e438c9dc Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_cython_abi.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_cython_special.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_cython_special.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7792812f99f14a72eda98d6d2213f45c53ee71d8 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_cython_special.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_data.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_data.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..155c22d0e7ef5e1f826a09b6e6b59007e6a8e933 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_data.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_dd.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_dd.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eae3f40a74b17a6e0bb9559abda26e32c39bdb96 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_dd.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_digamma.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_digamma.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..02399c71811b2f1e4fa4c1f64a57a76014dc3fb7 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_digamma.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_ellip_harm.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_ellip_harm.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a516feb589d32a53bd3fd7ca3d4e56a825abe6d7 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_ellip_harm.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_erfinv.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_erfinv.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..74e4870caaa58a5da8b722d303c870fdc49de76a Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_erfinv.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_exponential_integrals.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_exponential_integrals.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c442293b70205b1f46f18ed92c92737bd518a17c Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_exponential_integrals.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_extending.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_extending.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..578ddce8ecd4f5ec49aa55c8d10a58a8b910febf Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_extending.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_faddeeva.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_faddeeva.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..204d4e0bab8dff56dcaae964b01660e583575b69 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_faddeeva.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_gamma.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_gamma.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..da167e1593398b5e2ce89bcc20847a401ef2d539 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_gamma.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_gammainc.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_gammainc.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fb763eaa8a801f8f7fe66fc911daad6b51059168 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_gammainc.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_gen_harmonic.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_gen_harmonic.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7a52ee1c19248036bc57ea1c9199e7c574a52742 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_gen_harmonic.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_hyp2f1.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_hyp2f1.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f494047d53d104852220cf20693a02b017d84441 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_hyp2f1.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_hypergeometric.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_hypergeometric.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0ade04f8951a94876c4fd3faf4594f39801ad0e7 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_hypergeometric.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_iv_ratio.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_iv_ratio.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6ffa002cccfc34c7d65b62742bff2bb5e5cad59c Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_iv_ratio.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_kolmogorov.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_kolmogorov.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c9277c03d7856bb1cb6cc023ce82fe34371f72b2 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_kolmogorov.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_lambertw.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_lambertw.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1752996658eea738f10fade5b9e69a403e766de1 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_lambertw.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_legendre.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_legendre.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..08f1e6a6dfdb36dbf7dadc4ffdcece62d31c0ba6 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_legendre.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_log1mexp.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_log1mexp.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..891abf2ae6f81623c925ab9adc5fc3e3f9dcac31 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_log1mexp.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_loggamma.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_loggamma.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..baf5f2471c4b02cadca67fd80c055d8873276ec5 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_loggamma.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_logit.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_logit.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0e211d8ecef099956e0bd95b5be29751477a032c Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_logit.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_logsumexp.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_logsumexp.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c19f7d33a54e6c01586969f5812fe58ecdf70e0b Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_logsumexp.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_mpmath.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_mpmath.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..044d7eec8c9dd726ca90df643a2499318acc512e --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_mpmath.cpython-312.pyc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9322462a3846e831b7a9f24355d3339a2a7d83073f222142115337f4f5edc2f7 +size 131381 diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_nan_inputs.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_nan_inputs.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ff66e6d313ff7856a6d677f3ac8dbb2ce429b824 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_nan_inputs.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_ndtr.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_ndtr.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1cf21b655d9d13df2b88b81e71016eb987c54cd8 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_ndtr.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_ndtri_exp.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_ndtri_exp.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..122c1949aac7c7d685e77a30cf6e0a993ca7ad34 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_ndtri_exp.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_orthogonal.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_orthogonal.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a316a1e91a14b30977159652ec611493c548620c Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_orthogonal.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_orthogonal_eval.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_orthogonal_eval.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..990fa8ec0d6f304cd23e8221195db53ba2c133e0 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_orthogonal_eval.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_owens_t.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_owens_t.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b64ba9e85570ece136c7c168060496a8190325bf Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_owens_t.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_pcf.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_pcf.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f10d3c74ac48a6b52131447f58ac4c25d747bc85 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_pcf.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_pdtr.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_pdtr.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..62ada0f44f4b4de6498cb6438cb93d5162c0e6b9 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_pdtr.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_powm1.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_powm1.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9e367c333f0e4bc898c17e30bfdc5e9930fea750 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_powm1.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_precompute_expn_asy.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_precompute_expn_asy.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2fc023d2f8aec3282068d6979752ed9f07ecd414 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_precompute_expn_asy.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_precompute_gammainc.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_precompute_gammainc.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..76b6e4cebb7af8b4aa1667909b61d5bba3fd1eb5 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_precompute_gammainc.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_precompute_utils.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_precompute_utils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..25ae7dfd0daa421354105f03434e5227556f2d91 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_precompute_utils.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_round.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_round.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..817d566878d550e6700b4b2298e1749f1b12012d Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_round.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_sf_error.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_sf_error.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d63c8e9e9a9081890ac28c3a201f0c6df1762946 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_sf_error.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_sici.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_sici.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..67463c9ee6db500719c211d4a9db68f017b7e65e Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_sici.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_specfun.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_specfun.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..025b50ff9633a60605d85b813e2a45eea8b4e255 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_specfun.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_spence.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_spence.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..328243f5b79ff65c8786bb536eaa79afb97b7be8 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_spence.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_spfun_stats.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_spfun_stats.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9ecad17681676d8edf0e965ca604fa2c28fbb68c Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_spfun_stats.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_sph_harm.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_sph_harm.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9f6b29518bd26ce2c65082a6afa9da18cf397d44 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_sph_harm.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_spherical_bessel.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_spherical_bessel.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f88d81e8390e676bc37b6324d733dc78f0e2168d Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_spherical_bessel.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_support_alternative_backends.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_support_alternative_backends.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e095026d48dd11d667b31bea6a280efa3e78aa32 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_support_alternative_backends.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_trig.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_trig.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a66dba8ea8230aee040ae9af1b3de1ef233438a3 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_trig.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_ufunc_infra.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_ufunc_infra.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a6e54b27cef7bd3eb039fba8c370c611e45180de Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_ufunc_infra.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_ufunc_signatures.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_ufunc_signatures.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..00c1c73737542bbd488bb0eb048bba45433794d8 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_ufunc_signatures.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_wright_bessel.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_wright_bessel.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b76931b2b84ab06c4a502a1802751cb29044b4d6 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_wright_bessel.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_wrightomega.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_wrightomega.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2a31359fda9d84ef88f8f1ca30a2dff434cbc435 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_wrightomega.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_zeta.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_zeta.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a4539020db223e66e63014ded44b5ae67cf5ffbd Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/__pycache__/test_zeta.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/_cython_examples/extending.pyx b/venv/lib/python3.12/site-packages/scipy/special/tests/_cython_examples/extending.pyx new file mode 100644 index 0000000000000000000000000000000000000000..ca3bf2167f0f7726f8b0acb60ed8b8798a518d79 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/_cython_examples/extending.pyx @@ -0,0 +1,12 @@ +#!/usr/bin/env python3 +#cython: language_level=3 +#cython: boundscheck=False +#cython: wraparound=False + +from scipy.special.cython_special cimport beta, gamma + +cpdef double cy_beta(double a, double b): + return beta(a, b) + +cpdef double complex cy_gamma(double complex z): + return gamma(z) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/_cython_examples/meson.build b/venv/lib/python3.12/site-packages/scipy/special/tests/_cython_examples/meson.build new file mode 100644 index 0000000000000000000000000000000000000000..a2b2c581ef780758d79796b953abc35008a7a951 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/_cython_examples/meson.build @@ -0,0 +1,34 @@ +project('random-build-examples', 'c', 'cpp', 'cython') + +fs = import('fs') + +py3 = import('python').find_installation(pure: false) + +cy = meson.get_compiler('cython') + +if not cy.version().version_compare('>=3.2.0') + error('tests requires Cython >= 3.2.0') +endif + +cython_args = [] +if cy.version().version_compare('>=3.1.0') + cython_args += ['-Xfreethreading_compatible=True'] +endif + +py3.extension_module( + 'extending', + 'extending.pyx', + install: false, + cython_args: cython_args, + c_args: ['-DCYTHON_CCOMPLEX=0'] # see gh-18975 for why we need this +) + +extending_cpp = fs.copyfile('extending.pyx', 'extending_cpp.pyx') +py3.extension_module( + 'extending_cpp', + extending_cpp, + install: false, + override_options : ['cython_language=cpp'], + cython_args: cython_args, + cpp_args: ['-DCYTHON_CCOMPLEX=0'] +) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/cython_abi_signatures.json b/venv/lib/python3.12/site-packages/scipy/special/tests/cython_abi_signatures.json new file mode 100644 index 0000000000000000000000000000000000000000..6c83a72d0d2760b350833e9b5fd947e3f59aa8fc --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/cython_abi_signatures.json @@ -0,0 +1,400 @@ +{ + "scipy.special.cython_special": { + "__pyx_fuse_0_0eval_chebyc": "__pyx_t_double_complex (double, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_0_0eval_chebys": "__pyx_t_double_complex (double, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_0_0eval_chebyt": "__pyx_t_double_complex (double, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_0_0eval_chebyu": "__pyx_t_double_complex (double, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_0_0eval_gegenbauer": "__pyx_t_double_complex (double, double, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_0_0eval_genlaguerre": "__pyx_t_double_complex (double, double, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_0_0eval_jacobi": "__pyx_t_double_complex (double, double, double, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_0_0eval_laguerre": "__pyx_t_double_complex (double, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_0_0eval_legendre": "__pyx_t_double_complex (double, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_0_0eval_sh_chebyt": "__pyx_t_double_complex (double, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_0_0eval_sh_chebyu": "__pyx_t_double_complex (double, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_0_0eval_sh_jacobi": "__pyx_t_double_complex (double, double, double, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_0_0eval_sh_legendre": "__pyx_t_double_complex (double, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_0_1eval_chebyc": "double (double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_0_1eval_chebys": "double (double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_0_1eval_chebyt": "double (double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_0_1eval_chebyu": "double (double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_0_1eval_gegenbauer": "double (double, double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_0_1eval_genlaguerre": "double (double, double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_0_1eval_jacobi": "double (double, double, double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_0_1eval_laguerre": "double (double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_0_1eval_legendre": "double (double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_0_1eval_sh_chebyt": "double (double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_0_1eval_sh_chebyu": "double (double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_0_1eval_sh_jacobi": "double (double, double, double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_0_1eval_sh_legendre": "double (double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_0airy": "void (__pyx_t_double_complex, __pyx_t_double_complex *, __pyx_t_double_complex *, __pyx_t_double_complex *, __pyx_t_double_complex *)", + "__pyx_fuse_0airye": "void (__pyx_t_double_complex, __pyx_t_double_complex *, __pyx_t_double_complex *, __pyx_t_double_complex *, __pyx_t_double_complex *)", + "__pyx_fuse_0bdtr": "double (double, double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_0bdtrc": "double (double, double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_0bdtri": "double (double, double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_0betainc": "double (double, double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_0betaincc": "double (double, double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_0betainccinv": "double (double, double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_0betaincinv": "double (double, double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_0btdtria": "double (double, double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_0btdtrib": "double (double, double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_0chdtriv": "double (double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_0chndtr": "double (double, double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_0chndtridf": "double (double, double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_0chndtrinc": "double (double, double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_0chndtrix": "double (double, double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_0dawsn": "__pyx_t_double_complex (__pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_0elliprc": "__pyx_t_double_complex (__pyx_t_double_complex, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_0elliprd": "__pyx_t_double_complex (__pyx_t_double_complex, __pyx_t_double_complex, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_0elliprf": "__pyx_t_double_complex (__pyx_t_double_complex, __pyx_t_double_complex, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_0elliprg": "__pyx_t_double_complex (__pyx_t_double_complex, __pyx_t_double_complex, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_0elliprj": "__pyx_t_double_complex (__pyx_t_double_complex, __pyx_t_double_complex, __pyx_t_double_complex, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_0erf": "__pyx_t_double_complex (__pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_0erfc": "__pyx_t_double_complex (__pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_0erfcx": "__pyx_t_double_complex (__pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_0erfi": "__pyx_t_double_complex (__pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_0erfinv": "double (double, int __pyx_skip_dispatch)", + "__pyx_fuse_0exp1": "__pyx_t_double_complex (__pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_0expi": "__pyx_t_double_complex (__pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_0expit": "double (double, int __pyx_skip_dispatch)", + "__pyx_fuse_0expm1": "__pyx_t_double_complex (__pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_0expn": "double (double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_0fdtr": "double (double, double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_0fdtrc": "double (double, double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_0fdtri": "double (double, double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_0fresnel": "void (__pyx_t_double_complex, __pyx_t_double_complex *, __pyx_t_double_complex *)", + "__pyx_fuse_0gamma": "__pyx_t_double_complex (__pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_0hyp0f1": "__pyx_t_double_complex (double, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_0hyp1f1": "__pyx_t_double_complex (double, double, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_0hyp2f1": "__pyx_t_double_complex (double, double, double, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_0iv": "__pyx_t_double_complex (double, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_0ive": "__pyx_t_double_complex (double, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_0jv": "__pyx_t_double_complex (double, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_0jve": "__pyx_t_double_complex (double, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_0kn": "double (double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_0kv": "__pyx_t_double_complex (double, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_0kve": "__pyx_t_double_complex (double, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_0log1p": "__pyx_t_double_complex (__pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_0log_expit": "double (double, int __pyx_skip_dispatch)", + "__pyx_fuse_0log_ndtr": "__pyx_t_double_complex (__pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_0loggamma": "__pyx_t_double_complex (__pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_0logit": "double (double, int __pyx_skip_dispatch)", + "__pyx_fuse_0nbdtr": "double (double, double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_0nbdtrc": "double (double, double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_0nbdtri": "double (double, double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_0ncfdtr": "double (double, double, double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_0ncfdtri": "double (double, double, double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_0nctdtr": "double (double, double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_0nctdtrit": "double (double, double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_0ndtr": "__pyx_t_double_complex (__pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_0pdtri": "double (double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_0pdtrik": "double (double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_0powm1": "double (double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_0psi": "__pyx_t_double_complex (__pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_0rgamma": "__pyx_t_double_complex (__pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_0shichi": "void (__pyx_t_double_complex, __pyx_t_double_complex *, __pyx_t_double_complex *)", + "__pyx_fuse_0sici": "void (__pyx_t_double_complex, __pyx_t_double_complex *, __pyx_t_double_complex *)", + "__pyx_fuse_0smirnov": "double (double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_0smirnovi": "double (double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_0spence": "__pyx_t_double_complex (__pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_0spherical_in": "__pyx_t_double_complex (Py_ssize_t, __pyx_t_double_complex, int __pyx_skip_dispatch, struct __pyx_fuse_0__pyx_opt_args_5scipy_7special_14cython_special_spherical_in *__pyx_optional_args)", + "__pyx_fuse_0spherical_jn": "__pyx_t_double_complex (Py_ssize_t, __pyx_t_double_complex, int __pyx_skip_dispatch, struct __pyx_fuse_0__pyx_opt_args_5scipy_7special_14cython_special_spherical_jn *__pyx_optional_args)", + "__pyx_fuse_0spherical_kn": "__pyx_t_double_complex (Py_ssize_t, __pyx_t_double_complex, int __pyx_skip_dispatch, struct __pyx_fuse_0__pyx_opt_args_5scipy_7special_14cython_special_spherical_kn *__pyx_optional_args)", + "__pyx_fuse_0spherical_yn": "__pyx_t_double_complex (Py_ssize_t, __pyx_t_double_complex, int __pyx_skip_dispatch, struct __pyx_fuse_0__pyx_opt_args_5scipy_7special_14cython_special_spherical_yn *__pyx_optional_args)", + "__pyx_fuse_0stdtr": "double (double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_0stdtrit": "double (double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_0wrightomega": "__pyx_t_double_complex (__pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_0xlog1py": "__pyx_t_double_complex (__pyx_t_double_complex, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_0xlogy": "__pyx_t_double_complex (__pyx_t_double_complex, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_0yn": "double (double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_0yv": "__pyx_t_double_complex (double, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_0yve": "__pyx_t_double_complex (double, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_1_0eval_chebyc": "__pyx_t_double_complex (long, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_1_0eval_chebys": "__pyx_t_double_complex (long, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_1_0eval_chebyt": "__pyx_t_double_complex (long, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_1_0eval_chebyu": "__pyx_t_double_complex (long, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_1_0eval_gegenbauer": "__pyx_t_double_complex (long, double, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_1_0eval_genlaguerre": "__pyx_t_double_complex (long, double, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_1_0eval_jacobi": "__pyx_t_double_complex (long, double, double, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_1_0eval_laguerre": "__pyx_t_double_complex (long, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_1_0eval_legendre": "__pyx_t_double_complex (long, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_1_0eval_sh_chebyt": "__pyx_t_double_complex (long, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_1_0eval_sh_chebyu": "__pyx_t_double_complex (long, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_1_0eval_sh_jacobi": "__pyx_t_double_complex (long, double, double, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_1_0eval_sh_legendre": "__pyx_t_double_complex (long, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_1_1eval_chebyc": "double (long, double, int __pyx_skip_dispatch)", + "__pyx_fuse_1_1eval_chebys": "double (long, double, int __pyx_skip_dispatch)", + "__pyx_fuse_1_1eval_chebyt": "double (long, double, int __pyx_skip_dispatch)", + "__pyx_fuse_1_1eval_chebyu": "double (long, double, int __pyx_skip_dispatch)", + "__pyx_fuse_1_1eval_gegenbauer": "double (long, double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_1_1eval_genlaguerre": "double (long, double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_1_1eval_jacobi": "double (long, double, double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_1_1eval_laguerre": "double (long, double, int __pyx_skip_dispatch)", + "__pyx_fuse_1_1eval_legendre": "double (long, double, int __pyx_skip_dispatch)", + "__pyx_fuse_1_1eval_sh_chebyt": "double (long, double, int __pyx_skip_dispatch)", + "__pyx_fuse_1_1eval_sh_chebyu": "double (long, double, int __pyx_skip_dispatch)", + "__pyx_fuse_1_1eval_sh_jacobi": "double (long, double, double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_1_1eval_sh_legendre": "double (long, double, int __pyx_skip_dispatch)", + "__pyx_fuse_1airy": "void (double, double *, double *, double *, double *)", + "__pyx_fuse_1airye": "void (double, double *, double *, double *, double *)", + "__pyx_fuse_1bdtr": "double (double, long, double, int __pyx_skip_dispatch)", + "__pyx_fuse_1bdtrc": "double (double, long, double, int __pyx_skip_dispatch)", + "__pyx_fuse_1bdtri": "double (double, long, double, int __pyx_skip_dispatch)", + "__pyx_fuse_1betainc": "float (float, float, float, int __pyx_skip_dispatch)", + "__pyx_fuse_1betaincc": "float (float, float, float, int __pyx_skip_dispatch)", + "__pyx_fuse_1betainccinv": "float (float, float, float, int __pyx_skip_dispatch)", + "__pyx_fuse_1betaincinv": "float (float, float, float, int __pyx_skip_dispatch)", + "__pyx_fuse_1btdtria": "float (float, float, float, int __pyx_skip_dispatch)", + "__pyx_fuse_1btdtrib": "float (float, float, float, int __pyx_skip_dispatch)", + "__pyx_fuse_1chdtriv": "float (float, float, int __pyx_skip_dispatch)", + "__pyx_fuse_1chndtr": "float (float, float, float, int __pyx_skip_dispatch)", + "__pyx_fuse_1chndtridf": "float (float, float, float, int __pyx_skip_dispatch)", + "__pyx_fuse_1chndtrinc": "float (float, float, float, int __pyx_skip_dispatch)", + "__pyx_fuse_1chndtrix": "float (float, float, float, int __pyx_skip_dispatch)", + "__pyx_fuse_1dawsn": "double (double, int __pyx_skip_dispatch)", + "__pyx_fuse_1elliprc": "double (double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_1elliprd": "double (double, double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_1elliprf": "double (double, double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_1elliprg": "double (double, double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_1elliprj": "double (double, double, double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_1erf": "double (double, int __pyx_skip_dispatch)", + "__pyx_fuse_1erfc": "double (double, int __pyx_skip_dispatch)", + "__pyx_fuse_1erfcx": "double (double, int __pyx_skip_dispatch)", + "__pyx_fuse_1erfi": "double (double, int __pyx_skip_dispatch)", + "__pyx_fuse_1erfinv": "float (float, int __pyx_skip_dispatch)", + "__pyx_fuse_1exp1": "double (double, int __pyx_skip_dispatch)", + "__pyx_fuse_1expi": "double (double, int __pyx_skip_dispatch)", + "__pyx_fuse_1expit": "float (float, int __pyx_skip_dispatch)", + "__pyx_fuse_1expm1": "double (double, int __pyx_skip_dispatch)", + "__pyx_fuse_1expn": "double (long, double, int __pyx_skip_dispatch)", + "__pyx_fuse_1fdtr": "float (float, float, float, int __pyx_skip_dispatch)", + "__pyx_fuse_1fdtrc": "float (float, float, float, int __pyx_skip_dispatch)", + "__pyx_fuse_1fdtri": "float (float, float, float, int __pyx_skip_dispatch)", + "__pyx_fuse_1fresnel": "void (double, double *, double *)", + "__pyx_fuse_1gamma": "double (double, int __pyx_skip_dispatch)", + "__pyx_fuse_1hyp0f1": "double (double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_1hyp1f1": "double (double, double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_1hyp2f1": "double (double, double, double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_1iv": "double (double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_1ive": "double (double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_1jv": "double (double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_1jve": "double (double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_1kn": "double (long, double, int __pyx_skip_dispatch)", + "__pyx_fuse_1kv": "double (double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_1kve": "double (double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_1log1p": "double (double, int __pyx_skip_dispatch)", + "__pyx_fuse_1log_expit": "float (float, int __pyx_skip_dispatch)", + "__pyx_fuse_1log_ndtr": "double (double, int __pyx_skip_dispatch)", + "__pyx_fuse_1loggamma": "double (double, int __pyx_skip_dispatch)", + "__pyx_fuse_1logit": "float (float, int __pyx_skip_dispatch)", + "__pyx_fuse_1nbdtr": "double (long, long, double, int __pyx_skip_dispatch)", + "__pyx_fuse_1nbdtrc": "double (long, long, double, int __pyx_skip_dispatch)", + "__pyx_fuse_1nbdtri": "double (long, long, double, int __pyx_skip_dispatch)", + "__pyx_fuse_1ncfdtr": "float (float, float, float, float, int __pyx_skip_dispatch)", + "__pyx_fuse_1ncfdtri": "float (float, float, float, float, int __pyx_skip_dispatch)", + "__pyx_fuse_1nctdtr": "float (float, float, float, int __pyx_skip_dispatch)", + "__pyx_fuse_1nctdtrit": "float (float, float, float, int __pyx_skip_dispatch)", + "__pyx_fuse_1ndtr": "double (double, int __pyx_skip_dispatch)", + "__pyx_fuse_1pdtri": "double (long, double, int __pyx_skip_dispatch)", + "__pyx_fuse_1pdtrik": "float (float, float, int __pyx_skip_dispatch)", + "__pyx_fuse_1powm1": "float (float, float, int __pyx_skip_dispatch)", + "__pyx_fuse_1psi": "double (double, int __pyx_skip_dispatch)", + "__pyx_fuse_1rgamma": "double (double, int __pyx_skip_dispatch)", + "__pyx_fuse_1shichi": "void (double, double *, double *)", + "__pyx_fuse_1sici": "void (double, double *, double *)", + "__pyx_fuse_1smirnov": "double (long, double, int __pyx_skip_dispatch)", + "__pyx_fuse_1smirnovi": "double (long, double, int __pyx_skip_dispatch)", + "__pyx_fuse_1spence": "double (double, int __pyx_skip_dispatch)", + "__pyx_fuse_1spherical_in": "double (Py_ssize_t, double, int __pyx_skip_dispatch, struct __pyx_fuse_1__pyx_opt_args_5scipy_7special_14cython_special_spherical_in *__pyx_optional_args)", + "__pyx_fuse_1spherical_jn": "double (Py_ssize_t, double, int __pyx_skip_dispatch, struct __pyx_fuse_1__pyx_opt_args_5scipy_7special_14cython_special_spherical_jn *__pyx_optional_args)", + "__pyx_fuse_1spherical_kn": "double (Py_ssize_t, double, int __pyx_skip_dispatch, struct __pyx_fuse_1__pyx_opt_args_5scipy_7special_14cython_special_spherical_kn *__pyx_optional_args)", + "__pyx_fuse_1spherical_yn": "double (Py_ssize_t, double, int __pyx_skip_dispatch, struct __pyx_fuse_1__pyx_opt_args_5scipy_7special_14cython_special_spherical_yn *__pyx_optional_args)", + "__pyx_fuse_1stdtr": "float (float, float, int __pyx_skip_dispatch)", + "__pyx_fuse_1stdtrit": "float (float, float, int __pyx_skip_dispatch)", + "__pyx_fuse_1wrightomega": "double (double, int __pyx_skip_dispatch)", + "__pyx_fuse_1xlog1py": "double (double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_1xlogy": "double (double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_1yn": "double (long, double, int __pyx_skip_dispatch)", + "__pyx_fuse_1yv": "double (double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_1yve": "double (double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_2_0eval_chebyc": "__pyx_t_double_complex (Py_ssize_t, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_2_0eval_chebys": "__pyx_t_double_complex (Py_ssize_t, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_2_0eval_chebyt": "__pyx_t_double_complex (Py_ssize_t, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_2_0eval_chebyu": "__pyx_t_double_complex (Py_ssize_t, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_2_0eval_gegenbauer": "__pyx_t_double_complex (Py_ssize_t, double, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_2_0eval_genlaguerre": "__pyx_t_double_complex (Py_ssize_t, double, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_2_0eval_jacobi": "__pyx_t_double_complex (Py_ssize_t, double, double, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_2_0eval_laguerre": "__pyx_t_double_complex (Py_ssize_t, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_2_0eval_legendre": "__pyx_t_double_complex (Py_ssize_t, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_2_0eval_sh_chebyt": "__pyx_t_double_complex (Py_ssize_t, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_2_0eval_sh_chebyu": "__pyx_t_double_complex (Py_ssize_t, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_2_0eval_sh_jacobi": "__pyx_t_double_complex (Py_ssize_t, double, double, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_2_0eval_sh_legendre": "__pyx_t_double_complex (Py_ssize_t, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "__pyx_fuse_2_1eval_chebyc": "double (Py_ssize_t, double, int __pyx_skip_dispatch)", + "__pyx_fuse_2_1eval_chebys": "double (Py_ssize_t, double, int __pyx_skip_dispatch)", + "__pyx_fuse_2_1eval_chebyt": "double (Py_ssize_t, double, int __pyx_skip_dispatch)", + "__pyx_fuse_2_1eval_chebyu": "double (Py_ssize_t, double, int __pyx_skip_dispatch)", + "__pyx_fuse_2_1eval_gegenbauer": "double (Py_ssize_t, double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_2_1eval_genlaguerre": "double (Py_ssize_t, double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_2_1eval_jacobi": "double (Py_ssize_t, double, double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_2_1eval_laguerre": "double (Py_ssize_t, double, int __pyx_skip_dispatch)", + "__pyx_fuse_2_1eval_legendre": "double (Py_ssize_t, double, int __pyx_skip_dispatch)", + "__pyx_fuse_2_1eval_sh_chebyt": "double (Py_ssize_t, double, int __pyx_skip_dispatch)", + "__pyx_fuse_2_1eval_sh_chebyu": "double (Py_ssize_t, double, int __pyx_skip_dispatch)", + "__pyx_fuse_2_1eval_sh_jacobi": "double (Py_ssize_t, double, double, double, int __pyx_skip_dispatch)", + "__pyx_fuse_2_1eval_sh_legendre": "double (Py_ssize_t, double, int __pyx_skip_dispatch)", + "__pyx_fuse_2bdtr": "double (double, Py_ssize_t, double, int __pyx_skip_dispatch)", + "__pyx_fuse_2bdtrc": "double (double, Py_ssize_t, double, int __pyx_skip_dispatch)", + "__pyx_fuse_2bdtri": "double (double, Py_ssize_t, double, int __pyx_skip_dispatch)", + "__pyx_fuse_2expit": "long double (long double, int __pyx_skip_dispatch)", + "__pyx_fuse_2expn": "double (Py_ssize_t, double, int __pyx_skip_dispatch)", + "__pyx_fuse_2kn": "double (Py_ssize_t, double, int __pyx_skip_dispatch)", + "__pyx_fuse_2log_expit": "long double (long double, int __pyx_skip_dispatch)", + "__pyx_fuse_2logit": "long double (long double, int __pyx_skip_dispatch)", + "__pyx_fuse_2nbdtr": "double (Py_ssize_t, Py_ssize_t, double, int __pyx_skip_dispatch)", + "__pyx_fuse_2nbdtrc": "double (Py_ssize_t, Py_ssize_t, double, int __pyx_skip_dispatch)", + "__pyx_fuse_2nbdtri": "double (Py_ssize_t, Py_ssize_t, double, int __pyx_skip_dispatch)", + "__pyx_fuse_2pdtri": "double (Py_ssize_t, double, int __pyx_skip_dispatch)", + "__pyx_fuse_2smirnov": "double (Py_ssize_t, double, int __pyx_skip_dispatch)", + "__pyx_fuse_2smirnovi": "double (Py_ssize_t, double, int __pyx_skip_dispatch)", + "__pyx_fuse_2yn": "double (Py_ssize_t, double, int __pyx_skip_dispatch)", + "agm": "double (double, double, int __pyx_skip_dispatch)", + "bdtrik": "double (double, double, double, int __pyx_skip_dispatch)", + "bdtrin": "double (double, double, double, int __pyx_skip_dispatch)", + "bei": "double (double, int __pyx_skip_dispatch)", + "beip": "double (double, int __pyx_skip_dispatch)", + "ber": "double (double, int __pyx_skip_dispatch)", + "berp": "double (double, int __pyx_skip_dispatch)", + "besselpoly": "double (double, double, double, int __pyx_skip_dispatch)", + "beta": "double (double, double, int __pyx_skip_dispatch)", + "betaln": "double (double, double, int __pyx_skip_dispatch)", + "binom": "double (double, double, int __pyx_skip_dispatch)", + "boxcox": "double (double, double, int __pyx_skip_dispatch)", + "boxcox1p": "double (double, double, int __pyx_skip_dispatch)", + "cbrt": "double (double, int __pyx_skip_dispatch)", + "chdtr": "double (double, double, int __pyx_skip_dispatch)", + "chdtrc": "double (double, double, int __pyx_skip_dispatch)", + "chdtri": "double (double, double, int __pyx_skip_dispatch)", + "cosdg": "double (double, int __pyx_skip_dispatch)", + "cosm1": "double (double, int __pyx_skip_dispatch)", + "cotdg": "double (double, int __pyx_skip_dispatch)", + "ellipe": "double (double, int __pyx_skip_dispatch)", + "ellipeinc": "double (double, double, int __pyx_skip_dispatch)", + "ellipj": "void (double, double, double *, double *, double *, double *)", + "ellipk": "double (double, int __pyx_skip_dispatch)", + "ellipkinc": "double (double, double, int __pyx_skip_dispatch)", + "ellipkm1": "double (double, int __pyx_skip_dispatch)", + "entr": "double (double, int __pyx_skip_dispatch)", + "erfcinv": "double (double, int __pyx_skip_dispatch)", + "eval_hermite": "double (Py_ssize_t, double, int __pyx_skip_dispatch)", + "eval_hermitenorm": "double (Py_ssize_t, double, int __pyx_skip_dispatch)", + "exp10": "double (double, int __pyx_skip_dispatch)", + "exp2": "double (double, int __pyx_skip_dispatch)", + "exprel": "double (double, int __pyx_skip_dispatch)", + "fdtridfd": "double (double, double, double, int __pyx_skip_dispatch)", + "gammainc": "double (double, double, int __pyx_skip_dispatch)", + "gammaincc": "double (double, double, int __pyx_skip_dispatch)", + "gammainccinv": "double (double, double, int __pyx_skip_dispatch)", + "gammaincinv": "double (double, double, int __pyx_skip_dispatch)", + "gammaln": "double (double, int __pyx_skip_dispatch)", + "gammasgn": "double (double, int __pyx_skip_dispatch)", + "gdtr": "double (double, double, double, int __pyx_skip_dispatch)", + "gdtrc": "double (double, double, double, int __pyx_skip_dispatch)", + "gdtria": "double (double, double, double, int __pyx_skip_dispatch)", + "gdtrib": "double (double, double, double, int __pyx_skip_dispatch)", + "gdtrix": "double (double, double, double, int __pyx_skip_dispatch)", + "hankel1": "__pyx_t_double_complex (double, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "hankel1e": "__pyx_t_double_complex (double, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "hankel2": "__pyx_t_double_complex (double, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "hankel2e": "__pyx_t_double_complex (double, __pyx_t_double_complex, int __pyx_skip_dispatch)", + "huber": "double (double, double, int __pyx_skip_dispatch)", + "hyperu": "double (double, double, double, int __pyx_skip_dispatch)", + "i0": "double (double, int __pyx_skip_dispatch)", + "i0e": "double (double, int __pyx_skip_dispatch)", + "i1": "double (double, int __pyx_skip_dispatch)", + "i1e": "double (double, int __pyx_skip_dispatch)", + "inv_boxcox": "double (double, double, int __pyx_skip_dispatch)", + "inv_boxcox1p": "double (double, double, int __pyx_skip_dispatch)", + "it2i0k0": "void (double, double *, double *)", + "it2j0y0": "void (double, double *, double *)", + "it2struve0": "double (double, int __pyx_skip_dispatch)", + "itairy": "void (double, double *, double *, double *, double *)", + "iti0k0": "void (double, double *, double *)", + "itj0y0": "void (double, double *, double *)", + "itmodstruve0": "double (double, int __pyx_skip_dispatch)", + "itstruve0": "double (double, int __pyx_skip_dispatch)", + "j0": "double (double, int __pyx_skip_dispatch)", + "j1": "double (double, int __pyx_skip_dispatch)", + "k0": "double (double, int __pyx_skip_dispatch)", + "k0e": "double (double, int __pyx_skip_dispatch)", + "k1": "double (double, int __pyx_skip_dispatch)", + "k1e": "double (double, int __pyx_skip_dispatch)", + "kei": "double (double, int __pyx_skip_dispatch)", + "keip": "double (double, int __pyx_skip_dispatch)", + "kelvin": "void (double, __pyx_t_double_complex *, __pyx_t_double_complex *, __pyx_t_double_complex *, __pyx_t_double_complex *)", + "ker": "double (double, int __pyx_skip_dispatch)", + "kerp": "double (double, int __pyx_skip_dispatch)", + "kl_div": "double (double, double, int __pyx_skip_dispatch)", + "kolmogi": "double (double, int __pyx_skip_dispatch)", + "kolmogorov": "double (double, int __pyx_skip_dispatch)", + "log_wright_bessel": "double (double, double, double, int __pyx_skip_dispatch)", + "lpmv": "double (double, double, double, int __pyx_skip_dispatch)", + "mathieu_a": "double (double, double, int __pyx_skip_dispatch)", + "mathieu_b": "double (double, double, int __pyx_skip_dispatch)", + "mathieu_cem": "void (double, double, double, double *, double *)", + "mathieu_modcem1": "void (double, double, double, double *, double *)", + "mathieu_modcem2": "void (double, double, double, double *, double *)", + "mathieu_modsem1": "void (double, double, double, double *, double *)", + "mathieu_modsem2": "void (double, double, double, double *, double *)", + "mathieu_sem": "void (double, double, double, double *, double *)", + "modfresnelm": "void (double, __pyx_t_double_complex *, __pyx_t_double_complex *)", + "modfresnelp": "void (double, __pyx_t_double_complex *, __pyx_t_double_complex *)", + "modstruve": "double (double, double, int __pyx_skip_dispatch)", + "nbdtrik": "double (double, double, double, int __pyx_skip_dispatch)", + "nbdtrin": "double (double, double, double, int __pyx_skip_dispatch)", + "ncfdtridfd": "double (double, double, double, double, int __pyx_skip_dispatch)", + "ncfdtridfn": "double (double, double, double, double, int __pyx_skip_dispatch)", + "ncfdtrinc": "double (double, double, double, double, int __pyx_skip_dispatch)", + "nctdtridf": "double (double, double, double, int __pyx_skip_dispatch)", + "nctdtrinc": "double (double, double, double, int __pyx_skip_dispatch)", + "ndtri": "double (double, int __pyx_skip_dispatch)", + "ndtri_exp": "double (double, int __pyx_skip_dispatch)", + "nrdtrimn": "double (double, double, double, int __pyx_skip_dispatch)", + "nrdtrisd": "double (double, double, double, int __pyx_skip_dispatch)", + "obl_ang1": "void (double, double, double, double, double *, double *)", + "obl_ang1_cv": "void (double, double, double, double, double, double *, double *)", + "obl_cv": "double (double, double, double, int __pyx_skip_dispatch)", + "obl_rad1": "void (double, double, double, double, double *, double *)", + "obl_rad1_cv": "void (double, double, double, double, double, double *, double *)", + "obl_rad2": "void (double, double, double, double, double *, double *)", + "obl_rad2_cv": "void (double, double, double, double, double, double *, double *)", + "owens_t": "double (double, double, int __pyx_skip_dispatch)", + "pbdv": "void (double, double, double *, double *)", + "pbvv": "void (double, double, double *, double *)", + "pbwa": "void (double, double, double *, double *)", + "pdtr": "double (double, double, int __pyx_skip_dispatch)", + "pdtrc": "double (double, double, int __pyx_skip_dispatch)", + "poch": "double (double, double, int __pyx_skip_dispatch)", + "pro_ang1": "void (double, double, double, double, double *, double *)", + "pro_ang1_cv": "void (double, double, double, double, double, double *, double *)", + "pro_cv": "double (double, double, double, int __pyx_skip_dispatch)", + "pro_rad1": "void (double, double, double, double, double *, double *)", + "pro_rad1_cv": "void (double, double, double, double, double, double *, double *)", + "pro_rad2": "void (double, double, double, double, double *, double *)", + "pro_rad2_cv": "void (double, double, double, double, double, double *, double *)", + "pseudo_huber": "double (double, double, int __pyx_skip_dispatch)", + "radian": "double (double, double, double, int __pyx_skip_dispatch)", + "rel_entr": "double (double, double, int __pyx_skip_dispatch)", + "round": "double (double, int __pyx_skip_dispatch)", + "sindg": "double (double, int __pyx_skip_dispatch)", + "stdtridf": "double (double, double, int __pyx_skip_dispatch)", + "struve": "double (double, double, int __pyx_skip_dispatch)", + "tandg": "double (double, int __pyx_skip_dispatch)", + "tklmbda": "double (double, double, int __pyx_skip_dispatch)", + "voigt_profile": "double (double, double, double, int __pyx_skip_dispatch)", + "wofz": "__pyx_t_double_complex (__pyx_t_double_complex, int __pyx_skip_dispatch)", + "wright_bessel": "double (double, double, double, int __pyx_skip_dispatch)", + "y0": "double (double, int __pyx_skip_dispatch)", + "y1": "double (double, int __pyx_skip_dispatch)", + "zetac": "double (double, int __pyx_skip_dispatch)" + } +} diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/data/__init__.py b/venv/lib/python3.12/site-packages/scipy/special/tests/data/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/data/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/special/tests/data/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5d3d92447dae7877d32b8b94f893100a6468e1db Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/special/tests/data/__pycache__/__init__.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/data/boost.npz b/venv/lib/python3.12/site-packages/scipy/special/tests/data/boost.npz new file mode 100644 index 0000000000000000000000000000000000000000..6ada4fe916077ff0349c07c92407a647f5ab81d8 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/data/boost.npz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:57b5c2b67ee01c741536aae6ad9f8f128106b7fdff152afcf3d8f774b0645844 +size 1270643 diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/data/gsl.npz b/venv/lib/python3.12/site-packages/scipy/special/tests/data/gsl.npz new file mode 100644 index 0000000000000000000000000000000000000000..a4e2a204e7cfdc7b251d0dea3b810dba42c20fe3 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/data/gsl.npz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cbf1afdd27999806a710279910a2eb2f9f7f559033c7ecb796deaa10c44feb31 +size 51433 diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/data/local.npz b/venv/lib/python3.12/site-packages/scipy/special/tests/data/local.npz new file mode 100644 index 0000000000000000000000000000000000000000..7d8bcb0730326acedc98a112af6c2fc752602fe1 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/data/local.npz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6c29e58ce827084f84db9f1bba96045a61ce69f1d3ea3d7c828a79c133cf88f2 +size 203438 diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_basic.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_basic.py new file mode 100644 index 0000000000000000000000000000000000000000..cfa9587f9c19ce9d37b54e049c6ff9de89b80d3b --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_basic.py @@ -0,0 +1,4868 @@ +# this program corresponds to special.py + +### Means test is not done yet +# E Means test is giving error (E) +# F Means test is failing (F) +# EF Means test is giving error and Failing +#! Means test is segfaulting +# 8 Means test runs forever + +### test_besselpoly +### test_modfresnelp +### test_modfresnelm +# test_pbdv_seq +### test_pbvv_seq +### test_sph_harm + +import functools +import itertools +import operator +import platform +import sys +import warnings + +import numpy as np +from numpy import (array, isnan, r_, arange, finfo, pi, sin, cos, tan, exp, + log, zeros, sqrt, asarray, inf, nan_to_num, real, arctan, double, + array_equal) + +import pytest +from pytest import raises as assert_raises +from numpy.testing import (assert_equal, assert_array_equal, assert_, + assert_allclose, assert_array_almost_equal_nulp) + +from scipy import special +import scipy.special._ufuncs as cephes +from scipy.special import ellipe, ellipk, ellipkm1 +from scipy.special import elliprc, elliprd, elliprf, elliprg, elliprj +from scipy.special import softplus +from scipy.special import mathieu_odd_coef, mathieu_even_coef, stirling2 +from scipy._lib._array_api import xp_assert_close, xp_assert_equal, SCIPY_ARRAY_API + +from scipy.special._basic import ( + _FACTORIALK_LIMITS_64BITS, _FACTORIALK_LIMITS_32BITS, _is_subdtype +) +from scipy.special._testutils import with_special_errors, \ + assert_func_equal, FuncData +from scipy.integrate import quad + +import math + + +native_int = np.int32 if ( + sys.platform == 'win32' + or platform.architecture()[0] == "32bit" +) else np.int64 + + +class TestCephes: + def test_airy(self): + cephes.airy(0) + + def test_airye(self): + cephes.airye(0) + + def test_binom(self): + n = np.array([0.264, 4, 5.2, 17]) + k = np.array([2, 0.4, 7, 3.3]) + nk = np.array(np.broadcast_arrays(n[:,None], k[None,:]) + ).reshape(2, -1).T + rknown = np.array([[-0.097152, 0.9263051596159367, 0.01858423645695389, + -0.007581020651518199],[6, 2.0214389119675666, 0, 2.9827344527963846], + [10.92, 2.22993515861399, -0.00585728, 10.468891352063146], + [136, 3.5252179590758828, 19448, 1024.5526916174495]]) + assert_func_equal(cephes.binom, rknown.ravel(), nk, rtol=1e-13) + + # Test branches in implementation + rng = np.random.RandomState(1234) + n = np.r_[np.arange(-7, 30), 1000*rng.rand(30) - 500] + k = np.arange(0, 102) + nk = np.array(np.broadcast_arrays(n[:,None], k[None,:]) + ).reshape(2, -1).T + + assert_func_equal(cephes.binom, + cephes.binom(nk[:,0], nk[:,1] * (1 + 1e-15)), + nk, + atol=1e-10, rtol=1e-10) + + def test_binom_2(self): + # Test branches in implementation + np.random.seed(1234) + n = np.r_[np.logspace(1, 300, 20)] + k = np.arange(0, 102) + nk = np.array(np.broadcast_arrays(n[:,None], k[None,:]) + ).reshape(2, -1).T + + assert_func_equal(cephes.binom, + cephes.binom(nk[:,0], nk[:,1] * (1 + 1e-15)), + nk, + atol=1e-10, rtol=1e-10) + + def test_binom_exact(self): + @np.vectorize + def binom_int(n, k): + n = int(n) + k = int(k) + num = 1 + den = 1 + for i in range(1, k+1): + num *= i + n - k + den *= i + return float(num/den) + + np.random.seed(1234) + n = np.arange(1, 15) + k = np.arange(0, 15) + nk = np.array(np.broadcast_arrays(n[:,None], k[None,:]) + ).reshape(2, -1).T + nk = nk[nk[:,0] >= nk[:,1]] + assert_func_equal(cephes.binom, + binom_int(nk[:,0], nk[:,1]), + nk, + atol=0, rtol=0) + + def test_binom_nooverflow_8346(self): + # Test (binom(n, k) doesn't overflow prematurely */ + dataset = [ + (1000, 500, 2.70288240945436551e+299), + (1002, 501, 1.08007396880791225e+300), + (1004, 502, 4.31599279169058121e+300), + (1006, 503, 1.72468101616263781e+301), + (1008, 504, 6.89188009236419153e+301), + (1010, 505, 2.75402257948335448e+302), + (1012, 506, 1.10052048531923757e+303), + (1014, 507, 4.39774063758732849e+303), + (1016, 508, 1.75736486108312519e+304), + (1018, 509, 7.02255427788423734e+304), + (1020, 510, 2.80626776829962255e+305), + (1022, 511, 1.12140876377061240e+306), + (1024, 512, 4.48125455209897109e+306), + (1026, 513, 1.79075474304149900e+307), + (1028, 514, 7.15605105487789676e+307) + ] + dataset = np.asarray(dataset) + FuncData(cephes.binom, dataset, (0, 1), 2, rtol=1e-12).check() + + def test_bdtr(self): + assert_equal(cephes.bdtr(1,1,0.5),1.0) + + def test_bdtri(self): + assert_equal(cephes.bdtri(1,3,0.5),0.5) + + def test_bdtrc(self): + assert_equal(cephes.bdtrc(1,3,0.5),0.5) + + def test_bdtrik(self): + cephes.bdtrik(1,3,0.5) + + def test_bei(self): + assert_equal(cephes.bei(0),0.0) + + def test_beip(self): + assert_equal(cephes.beip(0),0.0) + + def test_ber(self): + assert_equal(cephes.ber(0),1.0) + + def test_berp(self): + assert_equal(cephes.berp(0),0.0) + + def test_besselpoly(self): + assert_equal(cephes.besselpoly(0,0,0),1.0) + + def test_cbrt(self): + assert_allclose(cephes.cbrt(1), 1.0, atol=1e-6, rtol=0) + + def test_chdtr(self): + assert_equal(cephes.chdtr(1,0),0.0) + + def test_chdtrc(self): + assert_equal(cephes.chdtrc(1,0),1.0) + + def test_chdtri(self): + assert_equal(cephes.chdtri(1,1),0.0) + + def test_chndtrix(self): + assert_equal(cephes.chndtrix(0,1,0),0.0) + + def test_cosdg(self): + assert_equal(cephes.cosdg(0),1.0) + + def test_cosm1(self): + assert_equal(cephes.cosm1(0),0.0) + + def test_cotdg(self): + assert_allclose(cephes.cotdg(45), 1.0, atol=1.5e-7, rtol=0) + + def test_dawsn(self): + assert_equal(cephes.dawsn(0),0.0) + assert_allclose(cephes.dawsn(1.23), 0.50053727749081767) + + def test_diric(self): + # Test behavior near multiples of 2pi. Regression test for issue + # described in gh-4001. + n_odd = [1, 5, 25] + x = np.array(2*np.pi + 5e-5).astype(np.float32) + assert_allclose(special.diric(x, n_odd), 1.0, atol=1.5e-7, rtol=0) + x = np.array(2*np.pi + 1e-9).astype(np.float64) + assert_allclose(special.diric(x, n_odd), 1.0, atol=1.5e-15, rtol=0) + x = np.array(2*np.pi + 1e-15).astype(np.float64) + assert_allclose(special.diric(x, n_odd), 1.0, atol=1.5e-15, rtol=0) + if hasattr(np, 'float128'): + # No float128 available in 32-bit numpy + x = np.array(2*np.pi + 1e-12).astype(np.float128) + assert_allclose(special.diric(x, n_odd), 1.0, atol=1.5e-19, rtol=0) + + n_even = [2, 4, 24] + x = np.array(2*np.pi + 1e-9).astype(np.float64) + assert_allclose(special.diric(x, n_even), -1.0, atol=1.5e-15, rtol=0) + + # Test at some values not near a multiple of pi + x = np.arange(0.2*np.pi, 1.0*np.pi, 0.2*np.pi) + octave_result = [0.872677996249965, 0.539344662916632, + 0.127322003750035, -0.206011329583298] + assert_allclose(special.diric(x, 3), octave_result, atol=1.5e-15, rtol=0) + + def test_diric_broadcasting(self): + x = np.arange(5) + n = np.array([1, 3, 7]) + assert_(special.diric(x[:, np.newaxis], n).shape == (x.size, n.size)) + + def test_ellipe(self): + assert_equal(cephes.ellipe(1),1.0) + + def test_ellipeinc(self): + assert_equal(cephes.ellipeinc(0,1),0.0) + + def test_ellipj(self): + cephes.ellipj(0,1) + + def test_ellipk(self): + assert_allclose(ellipk(0), pi/2) + + def test_ellipkinc(self): + assert_equal(cephes.ellipkinc(0,0),0.0) + + def test_erf(self): + assert_equal(cephes.erf(0), 0.0) + + def test_erf_symmetry(self): + x = 5.905732037710919 + assert_equal(cephes.erf(x) + cephes.erf(-x), 0.0) + + def test_erfc(self): + assert_equal(cephes.erfc(0), 1.0) + + def test_exp10(self): + assert_allclose(cephes.exp10(2), 100.0, atol=1e-6, rtol=0) + + def test_exp2(self): + assert_equal(cephes.exp2(2),4.0) + + def test_expm1(self): + assert_equal(cephes.expm1(0),0.0) + assert_equal(cephes.expm1(np.inf), np.inf) + assert_equal(cephes.expm1(-np.inf), -1) + assert_equal(cephes.expm1(np.nan), np.nan) + + def test_expm1_complex(self): + expm1 = cephes.expm1 + assert_equal(expm1(0 + 0j), 0 + 0j) + assert_equal(expm1(complex(np.inf, 0)), complex(np.inf, 0)) + assert_equal(expm1(complex(np.inf, 1)), complex(np.inf, np.inf)) + assert_equal(expm1(complex(np.inf, 2)), complex(-np.inf, np.inf)) + assert_equal(expm1(complex(np.inf, 4)), complex(-np.inf, -np.inf)) + assert_equal(expm1(complex(np.inf, 5)), complex(np.inf, -np.inf)) + assert_equal(expm1(complex(1, np.inf)), complex(np.nan, np.nan)) + assert_equal(expm1(complex(0, np.inf)), complex(np.nan, np.nan)) + assert_equal(expm1(complex(np.inf, np.inf)), complex(np.inf, np.nan)) + assert_equal(expm1(complex(-np.inf, np.inf)), complex(-1, 0)) + assert_equal(expm1(complex(-np.inf, np.nan)), complex(-1, 0)) + assert_equal(expm1(complex(np.inf, np.nan)), complex(np.inf, np.nan)) + assert_equal(expm1(complex(0, np.nan)), complex(np.nan, np.nan)) + assert_equal(expm1(complex(1, np.nan)), complex(np.nan, np.nan)) + assert_equal(expm1(complex(np.nan, 1)), complex(np.nan, np.nan)) + assert_equal(expm1(complex(np.nan, np.nan)), complex(np.nan, np.nan)) + + @pytest.mark.xfail(reason='The real part of expm1(z) bad at these points') + def test_expm1_complex_hard(self): + # The real part of this function is difficult to evaluate when + # z.real = -log(cos(z.imag)). + y = np.array([0.1, 0.2, 0.3, 5, 11, 20]) + x = -np.log(np.cos(y)) + z = x + 1j*y + + # evaluate using mpmath.expm1 with dps=1000 + expected = np.array([-5.5507901846769623e-17+0.10033467208545054j, + 2.4289354732893695e-18+0.20271003550867248j, + 4.5235500262585768e-17+0.30933624960962319j, + 7.8234305217489006e-17-3.3805150062465863j, + -1.3685191953697676e-16-225.95084645419513j, + 8.7175620481291045e-17+2.2371609442247422j]) + found = cephes.expm1(z) + # this passes. + assert_array_almost_equal_nulp(found.imag, expected.imag, 3) + # this fails. + assert_array_almost_equal_nulp(found.real, expected.real, 20) + + def test_fdtr(self): + assert_equal(cephes.fdtr(1, 1, 0), 0.0) + # Computed using Wolfram Alpha: CDF[FRatioDistribution[1e-6, 5], 10] + assert_allclose(cephes.fdtr(1e-6, 5, 10), 0.9999940790193488, + rtol=1e-12) + + def test_fdtrc(self): + assert_equal(cephes.fdtrc(1, 1, 0), 1.0) + # Computed using Wolfram Alpha: + # 1 - CDF[FRatioDistribution[2, 1/10], 1e10] + assert_allclose(cephes.fdtrc(2, 0.1, 1e10), 0.27223784621293512, + rtol=1e-12) + + def test_fdtri(self): + assert_allclose(cephes.fdtri(1, 1, [0.499, 0.501]), + array([0.9937365, 1.00630298]), rtol=1e-6) + # From Wolfram Alpha: + # CDF[FRatioDistribution[1/10, 1], 3] = 0.8756751669632105666874... + p = 0.8756751669632105666874 + assert_allclose(cephes.fdtri(0.1, 1, p), 3, rtol=1e-12) + + def test_gh20835(self): + # gh-20835 reported fdtri failing for extreme inputs + dfd, dfn, x = 1, 50000, 29.72591544307521 + assert_allclose(cephes.fdtri(dfd, dfn, cephes.fdtr(dfd, dfn, x)), x, rtol=1e-15) + + def test_fdtri_mysterious_failure(self): + assert_allclose(cephes.fdtri(1, 1, 0.5), 1) + + def test_fdtridfd(self): + assert_equal(cephes.fdtridfd(1,0,0),5.0) + + def test_fresnel(self): + assert_equal(cephes.fresnel(0),(0.0,0.0)) + + def test_gamma(self): + assert_equal(cephes.gamma(5),24.0) + + def test_gammainccinv(self): + assert_equal(cephes.gammainccinv(5,1),0.0) + + def test_gammaln(self): + cephes.gammaln(10) + + def test_gammasgn(self): + vals = np.array( + [-np.inf, -4, -3.5, -2.3, -0.0, 0.0, 1, 4.2, np.inf], np.float64 + ) + reference = np.array( + [np.nan, np.nan, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 1.0], np.float64 + ) + assert_array_equal(cephes.gammasgn(vals), reference) + + def test_gdtr(self): + assert_equal(cephes.gdtr(1,1,0),0.0) + + def test_gdtr_inf(self): + assert_equal(cephes.gdtr(1,1,np.inf),1.0) + + def test_gdtrc(self): + assert_equal(cephes.gdtrc(1,1,0),1.0) + + def test_gdtria(self): + assert_equal(cephes.gdtria(0,1,1),0.0) + + def test_gdtrib(self): + cephes.gdtrib(1,0,1) + # assert_equal(cephes.gdtrib(1,0,1),5.0) + + def test_gdtrix(self): + cephes.gdtrix(1,1,.1) + + def test_hankel1(self): + cephes.hankel1(1,1) + + def test_hankel1e(self): + cephes.hankel1e(1,1) + + def test_hankel2(self): + cephes.hankel2(1,1) + + def test_hankel2e(self): + cephes.hankel2e(1,1) + + def test_hyp1f1(self): + assert_allclose(cephes.hyp1f1(1, 1, 1), exp(1.0), atol=1e-6, rtol=0) + assert_allclose(cephes.hyp1f1(3, 4, -6), 0.026056422099537251095, + atol=1e-6, rtol=0) + cephes.hyp1f1(1,1,1) + + def test_hyp2f1(self): + assert_equal(cephes.hyp2f1(1,1,1,0),1.0) + + def test_i0(self): + assert_equal(cephes.i0(0),1.0) + + def test_i0e(self): + assert_equal(cephes.i0e(0),1.0) + + def test_i1(self): + assert_equal(cephes.i1(0),0.0) + + def test_i1e(self): + assert_equal(cephes.i1e(0),0.0) + + def test_it2i0k0(self): + cephes.it2i0k0(1) + + def test_it2j0y0(self): + cephes.it2j0y0(1) + + def test_it2struve0(self): + cephes.it2struve0(1) + + def test_itairy(self): + cephes.itairy(1) + + def test_iti0k0(self): + assert_equal(cephes.iti0k0(0),(0.0,0.0)) + + def test_itj0y0(self): + assert_equal(cephes.itj0y0(0),(0.0,0.0)) + + def test_itmodstruve0(self): + assert_equal(cephes.itmodstruve0(0),0.0) + + def test_itstruve0(self): + assert_equal(cephes.itstruve0(0),0.0) + + def test_iv(self): + assert_equal(cephes.iv(1,0),0.0) + + def test_ive(self): + assert_equal(cephes.ive(1,0),0.0) + + def test_j0(self): + assert_equal(cephes.j0(0),1.0) + + def test_j1(self): + assert_equal(cephes.j1(0),0.0) + + def test_jn(self): + assert_equal(cephes.jn(0,0),1.0) + + def test_jv(self): + assert_equal(cephes.jv(0,0),1.0) + + def test_jve(self): + assert_equal(cephes.jve(0,0),1.0) + + def test_k0(self): + cephes.k0(2) + + def test_k0e(self): + cephes.k0e(2) + + def test_k1(self): + cephes.k1(2) + + def test_k1e(self): + cephes.k1e(2) + + def test_kei(self): + cephes.kei(2) + + def test_keip(self): + assert_equal(cephes.keip(0),0.0) + + def test_ker(self): + cephes.ker(2) + + def test_kerp(self): + cephes.kerp(2) + + def test_kelvin(self): + cephes.kelvin(2) + + def test_kn(self): + cephes.kn(1,1) + + def test_kolmogi(self): + assert_equal(cephes.kolmogi(1),0.0) + assert_(np.isnan(cephes.kolmogi(np.nan))) + + def test_kolmogorov(self): + assert_equal(cephes.kolmogorov(0), 1.0) + + def test_kolmogp(self): + assert_equal(cephes._kolmogp(0), -0.0) + + def test_kolmogc(self): + assert_equal(cephes._kolmogc(0), 0.0) + + def test_kolmogci(self): + assert_equal(cephes._kolmogci(0), 0.0) + assert_(np.isnan(cephes._kolmogci(np.nan))) + + def test_kv(self): + cephes.kv(1,1) + + def test_kve(self): + cephes.kve(1,1) + + def test_log1p(self): + log1p = cephes.log1p + assert_equal(log1p(0), 0.0) + assert_equal(log1p(-1), -np.inf) + assert_equal(log1p(-2), np.nan) + assert_equal(log1p(np.inf), np.inf) + + def test_log1p_complex(self): + log1p = cephes.log1p + c = complex + assert_equal(log1p(0 + 0j), 0 + 0j) + assert_equal(log1p(c(-1, 0)), c(-np.inf, 0)) + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", "invalid value encountered in multiply", RuntimeWarning) + assert_allclose(log1p(c(1, np.inf)), c(np.inf, np.pi/2)) + assert_equal(log1p(c(1, np.nan)), c(np.nan, np.nan)) + assert_allclose(log1p(c(-np.inf, 1)), c(np.inf, np.pi)) + assert_equal(log1p(c(np.inf, 1)), c(np.inf, 0)) + assert_allclose(log1p(c(-np.inf, np.inf)), c(np.inf, 3*np.pi/4)) + assert_allclose(log1p(c(np.inf, np.inf)), c(np.inf, np.pi/4)) + assert_equal(log1p(c(np.inf, np.nan)), c(np.inf, np.nan)) + assert_equal(log1p(c(-np.inf, np.nan)), c(np.inf, np.nan)) + assert_equal(log1p(c(np.nan, np.inf)), c(np.inf, np.nan)) + assert_equal(log1p(c(np.nan, 1)), c(np.nan, np.nan)) + assert_equal(log1p(c(np.nan, np.nan)), c(np.nan, np.nan)) + + def test_lpmv(self): + assert_equal(cephes.lpmv(0,0,1),1.0) + + def test_mathieu_a_q0(self): + # When q is 0, the exact result is m**2. + m = np.array([1, 2, 5]) + assert_equal(cephes.mathieu_a(m, 0), m**2) + + # Reference values were computed with Wolfram Alpha: + # MathieuCharacteristicA[m, q] + @pytest.mark.parametrize( + 'm, q, ref', + [(0, 8, -10.6067292355526479852024), + (3, 3/2, 9.19330104768060974047804), + (5, 1/4, 25.0013021454698022809572), + (8, -10, 64.8008910105046444848962)] + ) + def test_mathieu_a(self, m, q, ref): + y = cephes.mathieu_a(m, q) + assert_allclose(y, ref, rtol=1e-15) + + def test_mathieu_b_q0(self): + # When q is 0, the exact result is m**2. + m = np.array([1, 2, 5]) + assert_equal(cephes.mathieu_b(m, 0), m**2) + + # Reference values were computed with Wolfram Alpha: + # MathieuCharacteristicB[m, q] + @pytest.mark.parametrize( + 'm, q, ref', + [(1, 15, -22.5130034974234666335), + (5, 3, 25.1870798027185125480), + (9, 1/4, 81.00039062627570760760), + (10, -3, 100.0454683359769326164)] + ) + def test_mathieu_b(self, m, q, ref): + y = cephes.mathieu_b(m, q) + assert_allclose(y, ref, rtol=1e-15) + + def test_mathieu_cem(self): + assert_equal(cephes.mathieu_cem(1,0,0),(1.0,0.0)) + + # Test AMS 20.2.27 + @np.vectorize + def ce_smallq(m, q, z): + z *= np.pi/180 + if m == 0: + # + O(q^2) + return 2**(-0.5) * (1 - .5*q*cos(2*z)) + elif m == 1: + # + O(q^2) + return cos(z) - q/8 * cos(3*z) + elif m == 2: + # + O(q^2) + return cos(2*z) - q*(cos(4*z)/12 - 1/4) + else: + # + O(q^2) + return cos(m*z) - q*(cos((m+2)*z)/(4*(m+1)) - cos((m-2)*z)/(4*(m-1))) + m = np.arange(0, 100) + q = np.r_[0, np.logspace(-30, -9, 10)] + assert_allclose(cephes.mathieu_cem(m[:,None], q[None,:], 0.123)[0], + ce_smallq(m[:,None], q[None,:], 0.123), + rtol=1e-14, atol=0) + + def test_mathieu_sem(self): + assert_equal(cephes.mathieu_sem(1,0,0),(0.0,1.0)) + + # Test AMS 20.2.27 + @np.vectorize + def se_smallq(m, q, z): + z *= np.pi/180 + if m == 1: + # + O(q^2) + return sin(z) - q/8 * sin(3*z) + elif m == 2: + # + O(q^2) + return sin(2*z) - q*sin(4*z)/12 + else: + # + O(q^2) + return sin(m*z) - q*(sin((m+2)*z)/(4*(m+1)) - sin((m-2)*z)/(4*(m-1))) + m = np.arange(1, 100) + q = np.r_[0, np.logspace(-30, -9, 10)] + assert_allclose(cephes.mathieu_sem(m[:,None], q[None,:], 0.123)[0], + se_smallq(m[:,None], q[None,:], 0.123), + rtol=1e-14, atol=0) + + def test_mathieu_modcem1(self): + assert_equal(cephes.mathieu_modcem1(1,0,0),(0.0,0.0)) + + def test_mathieu_modcem2(self): + cephes.mathieu_modcem2(1,1,1) + + # Test reflection relation AMS 20.6.19 + m = np.arange(0, 4)[:,None,None] + q = np.r_[np.logspace(-2, 2, 10)][None,:,None] + z = np.linspace(0, 1, 7)[None,None,:] + + y1 = cephes.mathieu_modcem2(m, q, -z)[0] + + fr = -cephes.mathieu_modcem2(m, q, 0)[0] / cephes.mathieu_modcem1(m, q, 0)[0] + y2 = (-cephes.mathieu_modcem2(m, q, z)[0] + - 2*fr*cephes.mathieu_modcem1(m, q, z)[0]) + + assert_allclose(y1, y2, rtol=1e-10) + + def test_mathieu_modsem1(self): + assert_equal(cephes.mathieu_modsem1(1,0,0),(0.0,0.0)) + + def test_mathieu_modsem2(self): + cephes.mathieu_modsem2(1,1,1) + + # Test reflection relation AMS 20.6.20 + m = np.arange(1, 4)[:,None,None] + q = np.r_[np.logspace(-2, 2, 10)][None,:,None] + z = np.linspace(0, 1, 7)[None,None,:] + + y1 = cephes.mathieu_modsem2(m, q, -z)[0] + fr = cephes.mathieu_modsem2(m, q, 0)[1] / cephes.mathieu_modsem1(m, q, 0)[1] + y2 = (cephes.mathieu_modsem2(m, q, z)[0] + - 2*fr*cephes.mathieu_modsem1(m, q, z)[0]) + assert_allclose(y1, y2, rtol=1e-10) + + def test_mathieu_overflow(self): + # Check that these return NaNs instead of causing a SEGV + assert_equal(cephes.mathieu_cem(10000, 0, 1.3), (np.nan, np.nan)) + assert_equal(cephes.mathieu_sem(10000, 0, 1.3), (np.nan, np.nan)) + assert_equal(cephes.mathieu_cem(10000, 1.5, 1.3), (np.nan, np.nan)) + assert_equal(cephes.mathieu_sem(10000, 1.5, 1.3), (np.nan, np.nan)) + assert_equal(cephes.mathieu_modcem1(10000, 1.5, 1.3), (np.nan, np.nan)) + assert_equal(cephes.mathieu_modsem1(10000, 1.5, 1.3), (np.nan, np.nan)) + assert_equal(cephes.mathieu_modcem2(10000, 1.5, 1.3), (np.nan, np.nan)) + assert_equal(cephes.mathieu_modsem2(10000, 1.5, 1.3), (np.nan, np.nan)) + + def test_mathieu_ticket_1847(self): + # Regression test --- this call had some out-of-bounds access + # and could return nan occasionally + for k in range(60): + v = cephes.mathieu_modsem2(2, 100, -1) + # Values from ACM TOMS 804 (derivate by numerical differentiation) + assert_allclose(v[0], 0.1431742913063671074347, rtol=1e-10) + assert_allclose(v[1], 0.9017807375832909144719, rtol=1e-4) + + def test_modfresnelm(self): + cephes.modfresnelm(0) + + def test_modfresnelp(self): + cephes.modfresnelp(0) + + def test_modstruve(self): + assert_equal(cephes.modstruve(1,0),0.0) + + def test_nbdtr(self): + assert_equal(cephes.nbdtr(1,1,1),1.0) + + def test_nbdtrc(self): + assert_equal(cephes.nbdtrc(1,1,1),0.0) + + def test_nbdtri(self): + assert_equal(cephes.nbdtri(1,1,1),1.0) + + def test_nbdtrik(self): + cephes.nbdtrik(1,.4,.5) + + def test_ncfdtr(self): + assert_equal(cephes.ncfdtr(1,1,1,0),0.0) + + def test_ncfdtri(self): + assert_equal(cephes.ncfdtri(1, 1, 1, 0), 0.0) + f = [0.5, 1, 1.5] + p = cephes.ncfdtr(2, 3, 1.5, f) + assert_allclose(cephes.ncfdtri(2, 3, 1.5, p), f) + + @pytest.mark.xfail( + reason=( + "ncfdtr uses a Boost math implementation but ncfdtridfd" + "inverts the less accurate cdflib implementation of ncfdtr." + ) + ) + def test_ncfdtridfd(self): + dfd = [1, 2, 3] + p = cephes.ncfdtr(2, dfd, 0.25, 15) + assert_allclose(cephes.ncfdtridfd(2, p, 0.25, 15), dfd) + + @pytest.mark.xfail( + reason=( + "ncfdtr uses a Boost math implementation but ncfdtridfn" + "inverts the less accurate cdflib implementation of ncfdtr." + ) + ) + def test_ncfdtridfn(self): + dfn = [0.1, 1, 2, 3, 1e4] + p = cephes.ncfdtr(dfn, 2, 0.25, 15) + assert_allclose(cephes.ncfdtridfn(p, 2, 0.25, 15), dfn, rtol=1e-5) + + @pytest.mark.xfail( + reason=( + "ncfdtr uses a Boost math implementation but ncfdtrinc" + "inverts the less accurate cdflib implementation of ncfdtr." + ) + ) + def test_ncfdtrinc(self): + nc = [0.5, 1.5, 2.0] + p = cephes.ncfdtr(2, 3, nc, 15) + assert_allclose(cephes.ncfdtrinc(2, 3, p, 15), nc) + + def test_nctdtr(self): + assert_equal(cephes.nctdtr(1,0,0),0.5) + assert_equal(cephes.nctdtr(9, 65536, 45), 0.0) + + assert_allclose(cephes.nctdtr(np.inf, 1., 1.), 0.5, atol=1e-4, rtol=0) + assert_(np.isnan(cephes.nctdtr(2., np.inf, 10.))) + assert_allclose(cephes.nctdtr(2., 1., np.inf), 1., atol=1e-6, rtol=0) + + assert_(np.isnan(cephes.nctdtr(np.nan, 1., 1.))) + assert_(np.isnan(cephes.nctdtr(2., np.nan, 1.))) + assert_(np.isnan(cephes.nctdtr(2., 1., np.nan))) + + def test_nctdtridf(self): + cephes.nctdtridf(1,0.5,0) + + def test_nctdtrinc(self): + cephes.nctdtrinc(1,0,0) + + def test_nctdtrit(self): + cephes.nctdtrit(.1,0.2,.5) + + def test_nrdtrimn(self): + assert_allclose(cephes.nrdtrimn(0.5, 1, 1), 1.0, atol=1e-6, rtol=0) + + def test_obl_ang1(self): + cephes.obl_ang1(1,1,1,0) + + def test_obl_ang1_cv(self): + result = cephes.obl_ang1_cv(1,1,1,1,0) + assert_allclose(result[0], 1.0, atol=1.5e-7, rtol=0) + assert_allclose(result[1], 0.0, atol=1.5e-7, rtol=0) + + def test_obl_cv(self): + assert_equal(cephes.obl_cv(1,1,0),2.0) + + def test_obl_rad1(self): + cephes.obl_rad1(1,1,1,0) + + def test_obl_rad1_cv(self): + cephes.obl_rad1_cv(1,1,1,1,0) + + def test_obl_rad2(self): + cephes.obl_rad2(1,1,1,0) + + def test_obl_rad2_cv(self): + cephes.obl_rad2_cv(1,1,1,1,0) + + def test_pbdv(self): + assert_equal(cephes.pbdv(1,0),(0.0,1.0)) + + def test_pbvv(self): + cephes.pbvv(1,0) + + def test_pbwa(self): + cephes.pbwa(1,0) + + def test_pdtr(self): + val = cephes.pdtr(0, 1) + assert_allclose(val, np.exp(-1), atol=1.5e-7, rtol=0) + # Edge case: m = 0. + val = cephes.pdtr([0, 1, 2], 0) + assert_array_equal(val, [1, 1, 1]) + + def test_pdtrc(self): + val = cephes.pdtrc(0, 1) + assert_allclose(val, 1 - np.exp(-1), atol=1.5e-7, rtol=0) + # Edge case: m = 0. + val = cephes.pdtrc([0, 1, 2], 0.0) + assert_array_equal(val, [0, 0, 0]) + + def test_pdtri(self): + with warnings.catch_warnings(): + msg = "floating point number truncated to an integer" + warnings.filterwarnings("ignore", msg, RuntimeWarning) + cephes.pdtri(0.5,0.5) + + + def test_pro_ang1(self): + cephes.pro_ang1(1,1,1,0) + + def test_pro_ang1_cv(self): + assert_allclose(cephes.pro_ang1_cv(1, 1, 1, 1, 0), array((1.0, 0.0)), + atol=1.5e-7, rtol=0) + + def test_pro_cv(self): + assert_equal(cephes.pro_cv(1,1,0),2.0) + + def test_pro_rad1(self): + cephes.pro_rad1(1,1,1,0.1) + + def test_pro_rad1_cv(self): + cephes.pro_rad1_cv(1,1,1,1,0) + + def test_pro_rad2(self): + cephes.pro_rad2(1,1,1,0) + + def test_pro_rad2_cv(self): + cephes.pro_rad2_cv(1,1,1,1,0) + + def test_psi(self): + cephes.psi(1) + + def test_radian(self): + assert_equal(cephes.radian(0,0,0),0) + + def test_rgamma(self): + assert_equal(cephes.rgamma(1),1.0) + + def test_round(self): + assert_equal(cephes.round(3.4),3.0) + assert_equal(cephes.round(-3.4),-3.0) + assert_equal(cephes.round(3.6),4.0) + assert_equal(cephes.round(-3.6),-4.0) + assert_equal(cephes.round(3.5),4.0) + assert_equal(cephes.round(-3.5),-4.0) + + def test_shichi(self): + cephes.shichi(1) + + def test_sici(self): + cephes.sici(1) + + s, c = cephes.sici(np.inf) + assert_allclose(s, np.pi * 0.5, atol=1.5e-7, rtol=0) + assert_allclose(c, 0, atol=1.5e-7, rtol=0) + + s, c = cephes.sici(-np.inf) + assert_allclose(s, -np.pi * 0.5, atol=1.5e-7, rtol=0) + assert_(np.isnan(c), "cosine integral(-inf) is not nan") + + def test_sindg(self): + assert_equal(cephes.sindg(90),1.0) + + def test_smirnov(self): + assert_equal(cephes.smirnov(1,.1),0.9) + assert_(np.isnan(cephes.smirnov(1,np.nan))) + + def test_smirnovp(self): + assert_equal(cephes._smirnovp(1, .1), -1) + assert_equal(cephes._smirnovp(2, 0.75), -2*(0.25)**(2-1)) + assert_equal(cephes._smirnovp(3, 0.75), -3*(0.25)**(3-1)) + assert_(np.isnan(cephes._smirnovp(1, np.nan))) + + def test_smirnovc(self): + assert_equal(cephes._smirnovc(1,.1),0.1) + assert_(np.isnan(cephes._smirnovc(1,np.nan))) + x10 = np.linspace(0, 1, 11, endpoint=True) + assert_allclose(cephes._smirnovc(3, x10), 1 - cephes.smirnov(3, x10), + atol=1.5e-7, rtol=0) + x4 = np.linspace(0, 1, 5, endpoint=True) + assert_allclose(cephes._smirnovc(4, x4), 1 - cephes.smirnov(4, x4), + atol=1.5e-7, rtol=0) + + def test_smirnovi(self): + assert_allclose(cephes.smirnov(1, cephes.smirnovi(1, 0.4)), 0.4, + atol=1.5e-7, rtol=0) + assert_allclose(cephes.smirnov(1, cephes.smirnovi(1, 0.6)), 0.6, + atol=1.5e-7, rtol=0) + assert_(np.isnan(cephes.smirnovi(1,np.nan))) + + def test_smirnovci(self): + assert_allclose(cephes._smirnovc(1, cephes._smirnovci(1, 0.4)), 0.4, + atol=1.5e-7, rtol=0) + assert_allclose(cephes._smirnovc(1, cephes._smirnovci(1, 0.6)), 0.6, + atol=1.5e-7, rtol=0) + assert_(np.isnan(cephes._smirnovci(1,np.nan))) + + def test_spence(self): + assert_equal(cephes.spence(1),0.0) + + def test_stdtr(self): + assert_equal(cephes.stdtr(1,0),0.5) + assert_allclose(cephes.stdtr(1, 1), 0.75, atol=1.5e-7, rtol=0) + assert_allclose(cephes.stdtr(1, 2), 0.852416382349, atol=1.5e-7, rtol=0) + + def test_stdtridf(self): + cephes.stdtridf(0.7,1) + + def test_stdtrit(self): + cephes.stdtrit(1,0.7) + + def test_struve(self): + assert_equal(cephes.struve(0,0),0.0) + + def test_tandg(self): + assert_equal(cephes.tandg(45),1.0) + + def test_tklmbda(self): + assert_allclose(cephes.tklmbda(1, 1), 1.0, atol=1.5e-7, rtol=0) + + def test_y0(self): + cephes.y0(1) + + def test_y1(self): + cephes.y1(1) + + def test_yn(self): + cephes.yn(1,1) + + def test_yv(self): + cephes.yv(1,1) + + def test_yve(self): + cephes.yve(1,1) + + def test_wofz(self): + z = [complex(624.2,-0.26123), complex(-0.4,3.), complex(0.6,2.), + complex(-1.,1.), complex(-1.,-9.), complex(-1.,9.), + complex(-0.0000000234545,1.1234), complex(-3.,5.1), + complex(-53,30.1), complex(0.0,0.12345), + complex(11,1), complex(-22,-2), complex(9,-28), + complex(21,-33), complex(1e5,1e5), complex(1e14,1e14) + ] + w = [ + complex(-3.78270245518980507452677445620103199303131110e-7, + 0.000903861276433172057331093754199933411710053155), + complex(0.1764906227004816847297495349730234591778719532788, + -0.02146550539468457616788719893991501311573031095617), + complex(0.2410250715772692146133539023007113781272362309451, + 0.06087579663428089745895459735240964093522265589350), + complex(0.30474420525691259245713884106959496013413834051768, + -0.20821893820283162728743734725471561394145872072738), + complex(7.317131068972378096865595229600561710140617977e34, + 8.321873499714402777186848353320412813066170427e34), + complex(0.0615698507236323685519612934241429530190806818395, + -0.00676005783716575013073036218018565206070072304635), + complex(0.3960793007699874918961319170187598400134746631, + -5.593152259116644920546186222529802777409274656e-9), + complex(0.08217199226739447943295069917990417630675021771804, + -0.04701291087643609891018366143118110965272615832184), + complex(0.00457246000350281640952328010227885008541748668738, + -0.00804900791411691821818731763401840373998654987934), + complex(0.8746342859608052666092782112565360755791467973338452, + 0.), + complex(0.00468190164965444174367477874864366058339647648741, + 0.0510735563901306197993676329845149741675029197050), + complex(-0.0023193175200187620902125853834909543869428763219, + -0.025460054739731556004902057663500272721780776336), + complex(9.11463368405637174660562096516414499772662584e304, + 3.97101807145263333769664875189354358563218932e305), + complex(-4.4927207857715598976165541011143706155432296e281, + -2.8019591213423077494444700357168707775769028e281), + complex(2.820947917809305132678577516325951485807107151e-6, + 2.820947917668257736791638444590253942253354058e-6), + complex(2.82094791773878143474039725787438662716372268e-15, + 2.82094791773878143474039725773333923127678361e-15) + ] + assert_func_equal(cephes.wofz, w, z, rtol=1e-13) + + +class TestAiry: + def test_airy(self): + # This tests the airy function to ensure 8 place accuracy in computation + + x = special.airy(.99) + assert_allclose(x, array([0.13689066, -0.16050153, 1.19815925, 0.92046818]), + atol=1.5e-8, rtol=0) + x = special.airy(.41) + assert_allclose(x, array([0.25238916, -.23480512, 0.80686202, 0.51053919]), + atol=1.5e-8, rtol=0) + x = special.airy(-.36) + assert_allclose(x, array([0.44508477,-0.23186773,0.44939534,0.48105354]), + atol=1.5e-8, rtol=0) + + def test_airye(self): + a = special.airye(0.01) + b = special.airy(0.01) + b1 = [None]*4 + for n in range(2): + b1[n] = b[n]*exp(2.0/3.0*0.01*sqrt(0.01)) + for n in range(2,4): + b1[n] = b[n]*exp(-abs(real(2.0/3.0*0.01*sqrt(0.01)))) + assert_allclose(a, b1, atol=1.5e-6, rtol=0) + + def test_bi_zeros(self): + bi = special.bi_zeros(2) + bia = (array([-1.17371322, -3.2710930]), + array([-2.29443968, -4.07315509]), + array([-0.45494438, 0.39652284]), + array([0.60195789, -0.76031014])) + assert_allclose(bi, bia, atol=1.5e-4, rtol=0) + + bi = special.bi_zeros(5) + assert_allclose(bi[0], array([-1.173713222709127, + -3.271093302836352, + -4.830737841662016, + -6.169852128310251, + -7.376762079367764]), + atol=1.5e-11, rtol=0) + + assert_allclose(bi[1], array([-2.294439682614122, + -4.073155089071828, + -5.512395729663599, + -6.781294445990305, + -7.940178689168587]), + atol=1.5e-10, rtol=0) + + assert_allclose(bi[2], array([-0.454944383639657, + 0.396522836094465, + -0.367969161486959, + 0.349499116831805, + -0.336026240133662]), + atol=1.5e-11, rtol=0) + + assert_allclose(bi[3], array([0.601957887976239, + -0.760310141492801, + 0.836991012619261, + -0.88947990142654, + 0.929983638568022]), + atol=1.5e-10, rtol=0) + + def test_ai_zeros(self): + ai = special.ai_zeros(1) + assert_allclose(ai, (array([-2.33810741]), + array([-1.01879297]), + array([0.5357]), + array([0.7012])), + atol=1.5e-4, rtol=0) + + @pytest.mark.fail_slow(5) + def test_ai_zeros_big(self): + z, zp, ai_zpx, aip_zx = special.ai_zeros(50000) + ai_z, aip_z, _, _ = special.airy(z) + ai_zp, aip_zp, _, _ = special.airy(zp) + + ai_envelope = 1/abs(z)**(1./4) + aip_envelope = abs(zp)**(1./4) + + # Check values + assert_allclose(ai_zpx, ai_zp, rtol=1e-10) + assert_allclose(aip_zx, aip_z, rtol=1e-10) + + # Check they are zeros + assert_allclose(ai_z/ai_envelope, 0, atol=1e-10, rtol=0) + assert_allclose(aip_zp/aip_envelope, 0, atol=1e-10, rtol=0) + + # Check first zeros, DLMF 9.9.1 + assert_allclose(z[:6], + [-2.3381074105, -4.0879494441, -5.5205598281, + -6.7867080901, -7.9441335871, -9.0226508533], rtol=1e-10) + assert_allclose(zp[:6], + [-1.0187929716, -3.2481975822, -4.8200992112, + -6.1633073556, -7.3721772550, -8.4884867340], rtol=1e-10) + + @pytest.mark.fail_slow(5) + def test_bi_zeros_big(self): + z, zp, bi_zpx, bip_zx = special.bi_zeros(50000) + _, _, bi_z, bip_z = special.airy(z) + _, _, bi_zp, bip_zp = special.airy(zp) + + bi_envelope = 1/abs(z)**(1./4) + bip_envelope = abs(zp)**(1./4) + + # Check values + assert_allclose(bi_zpx, bi_zp, rtol=1e-10) + assert_allclose(bip_zx, bip_z, rtol=1e-10) + + # Check they are zeros + assert_allclose(bi_z/bi_envelope, 0, atol=1e-10, rtol=0) + assert_allclose(bip_zp/bip_envelope, 0, atol=1e-10, rtol=0) + + # Check first zeros, DLMF 9.9.2 + assert_allclose(z[:6], + [-1.1737132227, -3.2710933028, -4.8307378417, + -6.1698521283, -7.3767620794, -8.4919488465], rtol=1e-10) + assert_allclose(zp[:6], + [-2.2944396826, -4.0731550891, -5.5123957297, + -6.7812944460, -7.9401786892, -9.0195833588], rtol=1e-10) + + +class TestAssocLaguerre: + def test_assoc_laguerre(self): + a1 = special.genlaguerre(11,1) + a2 = special.assoc_laguerre(.2,11,1) + assert_allclose(a2, a1(.2), atol=1.5e-8, rtol=0) + a2 = special.assoc_laguerre(1,11,1) + assert_allclose(a2, a1(1), atol=1.5e-8, rtol=0) + + +class TestBesselpoly: + def test_besselpoly(self): + pass + + +class TestKelvin: + def test_bei(self): + mbei = special.bei(2) + # This may not be exact. + assert_allclose(mbei, 0.9722916273066613, atol=1.5e-5, rtol=0) + + def test_beip(self): + mbeip = special.beip(2) + # This may not be exact. + assert_allclose(mbeip, 0.91701361338403631, atol=1.5e-5, rtol=0) + + def test_ber(self): + mber = special.ber(2) + # This may not be exact. + assert_allclose(mber, 0.75173418271380821, atol=1.5e-5, rtol=0) + + def test_berp(self): + mberp = special.berp(2) + # This may not be exact. + assert_allclose(mberp, -0.49306712470943909, atol=1.5e-5, rtol=0) + + def test_bei_zeros(self): + # Abramowitz & Stegun, Table 9.12 + bi = special.bei_zeros(5) + assert_allclose(bi, array([5.02622, + 9.45541, + 13.89349, + 18.33398, + 22.77544]), + atol=1.5e-4, rtol=0) + + def test_beip_zeros(self): + bip = special.beip_zeros(5) + assert_allclose(bip, array([3.772673304934953, + 8.280987849760042, + 12.742147523633703, + 17.193431752512542, + 21.641143941167325]), + atol=1.5e-8, rtol=0) + + def test_ber_zeros(self): + ber = special.ber_zeros(5) + assert_allclose(ber, array([2.84892, + 7.23883, + 11.67396, + 16.11356, + 20.55463]), + atol=1.5e-4, rtol=0) + + def test_berp_zeros(self): + brp = special.berp_zeros(5) + assert_allclose(brp, array([6.03871, + 10.51364, + 14.96844, + 19.41758, + 23.86430]), + atol=1.5e-4, rtol=0) + + def test_kelvin(self): + mkelv = special.kelvin(2) + assert_allclose(mkelv, (special.ber(2) + special.bei(2)*1j, + special.ker(2) + special.kei(2)*1j, + special.berp(2) + special.beip(2)*1j, + special.kerp(2) + special.keip(2)*1j), + atol=1.5e-8, rtol=0) + + def test_kei(self): + mkei = special.kei(2) + assert_allclose(mkei, -0.20240006776470432, atol=1.5e-5, rtol=0) + + def test_keip(self): + mkeip = special.keip(2) + assert_allclose(mkeip, 0.21980790991960536, atol=1.5e-5, rtol=0) + + def test_ker(self): + mker = special.ker(2) + assert_allclose(mker, -0.041664513991509472, atol=1.5e-5, rtol=0) + + def test_kerp(self): + mkerp = special.kerp(2) + assert_allclose(mkerp, -0.10660096588105264, atol=1.5e-5, rtol=0) + + def test_kei_zeros(self): + kei = special.kei_zeros(5) + assert_allclose(kei, array([3.91467, + 8.34422, + 12.78256, + 17.22314, + 21.66464]), + atol=1.5e-4, rtol=0) + + def test_keip_zeros(self): + keip = special.keip_zeros(5) + assert_allclose(keip, array([4.93181, + 9.40405, + 13.85827, + 18.30717, + 22.75379]), + atol=1.5e-4, rtol=0) + + # numbers come from 9.9 of A&S pg. 381 + def test_kelvin_zeros(self): + tmp = special.kelvin_zeros(5) + berz, beiz, kerz, keiz, berpz, beipz, kerpz, keipz = tmp + assert_allclose(berz, array([2.84892, + 7.23883, + 11.67396, + 16.11356, + 20.55463]), + atol=1.5e-4, rtol=0) + assert_allclose(beiz, array([5.02622, + 9.45541, + 13.89349, + 18.33398, + 22.77544]), + atol=1.5e-4, rtol=0) + assert_allclose(kerz, array([1.71854, + 6.12728, + 10.56294, + 15.00269, + 19.44382]), + atol=1.5e-4, rtol=0) + assert_allclose(keiz, array([3.91467, + 8.34422, + 12.78256, + 17.22314, + 21.66464]), + atol=1.5e-4, rtol=0) + assert_allclose(berpz, array([6.03871, + 10.51364, + 14.96844, + 19.41758, + 23.86430]), + atol=1.5e-4, rtol=0) + assert_allclose(beipz, array([3.77267, + # table from 1927 had 3.77320 + # but this is more accurate + 8.28099, + 12.74215, + 17.19343, + 21.64114]), + atol=1.5e-4, rtol=0) + assert_allclose(kerpz, array([2.66584, + 7.17212, + 11.63218, + 16.08312, + 20.53068]), + atol=1.5e-4, rtol=0) + assert_allclose(keipz, array([4.93181, + 9.40405, + 13.85827, + 18.30717, + 22.75379]), + atol=1.5e-4, rtol=0) + + def test_ker_zeros(self): + ker = special.ker_zeros(5) + assert_allclose(ker, array([1.71854, + 6.12728, + 10.56294, + 15.00269, + 19.44381]), + atol=1.5e-4, rtol=0) + + def test_kerp_zeros(self): + kerp = special.kerp_zeros(5) + assert_allclose(kerp, array([2.66584, + 7.17212, + 11.63218, + 16.08312, + 20.53068]), + atol=1.5e-4, rtol=0) + + +class TestBernoulli: + def test_bernoulli(self): + brn = special.bernoulli(5) + assert_allclose(brn, array([1.0000, + -0.5000, + 0.1667, + 0.0000, + -0.0333, + 0.0000]), + atol=1.5e-4, rtol=0) + + +class TestBeta: + """ + Test beta and betaln. + """ + + def test_beta(self): + assert_equal(special.beta(1, 1), 1.0) + assert_allclose(special.beta(-100.3, 1e-200), special.gamma(1e-200)) + assert_allclose(special.beta(0.0342, 171), 24.070498359873497, + rtol=1e-13, atol=0) + + bet = special.beta(2, 4) + betg = (special.gamma(2)*special.gamma(4))/special.gamma(6) + assert_allclose(bet, betg, rtol=1e-13) + + def test_beta_inf(self): + assert_(np.isinf(special.beta(-1, 2))) + + def test_betaln(self): + assert_equal(special.betaln(1, 1), 0.0) + assert_allclose(special.betaln(-100.3, 1e-200), + special.gammaln(1e-200)) + assert_allclose(special.betaln(0.0342, 170), 3.1811881124242447, + rtol=1e-14, atol=0) + + betln = special.betaln(2, 4) + bet = log(abs(special.beta(2, 4))) + assert_allclose(betln, bet, rtol=1e-13) + + +class TestBetaInc: + """ + Tests for betainc, betaincinv, betaincc, betainccinv. + """ + + def test_a1_b1(self): + # betainc(1, 1, x) is x. + x = np.array([0, 0.25, 1]) + assert_equal(special.betainc(1, 1, x), x) + assert_equal(special.betaincinv(1, 1, x), x) + assert_equal(special.betaincc(1, 1, x), 1 - x) + assert_equal(special.betainccinv(1, 1, x), 1 - x) + + # Nontrivial expected values computed with mpmath: + # from mpmath import mp + # mp.dps = 100 + # p = mp.betainc(a, b, 0, x, regularized=True) + # + # or, e.g., + # + # p = 0.25 + # a, b = 0.0342, 171 + # x = mp.findroot( + # lambda t: mp.betainc(a, b, 0, t, regularized=True) - p, + # (8e-21, 9e-21), + # solver='anderson', + # ) + # + @pytest.mark.parametrize( + 'a, b, x, p', + [(2, 4, 0.3138101704556974, 0.5), + (0.0342, 171.0, 1e-10, 0.552699169018070910641), + # gh-3761: + (0.0342, 171, 8.42313169354797e-21, 0.25), + # gh-4244: + (0.0002742794749792665, 289206.03125, 1.639984034231756e-56, + 0.9688708782196045), + # gh-12796: + (4, 99997, 0.0001947841578892121, 0.999995)]) + def test_betainc_and_inverses(self, a, b, x, p): + p1 = special.betainc(a, b, x) + assert_allclose(p1, p, rtol=1e-15) + x1 = special.betaincinv(a, b, p) + assert_allclose(x1, x, rtol=5e-13) + a1 = special.btdtria(p, b, x) + assert_allclose(a1, a, rtol=1e-13) + b1 = special.btdtrib(a, p, x) + assert_allclose(b1, b, rtol=1e-13) + + # Expected values computed with mpmath: + # from mpmath import mp + # mp.dps = 100 + # p = mp.betainc(a, b, x, 1, regularized=True) + @pytest.mark.parametrize('a, b, x, p', + [(2.5, 3.0, 0.25, 0.833251953125), + (7.5, 13.25, 0.375, 0.43298734645560368593), + (0.125, 7.5, 0.425, 0.0006688257851314237), + (0.125, 18.0, 1e-6, 0.72982359145096327654), + (0.125, 18.0, 0.996, 7.2745875538380150586e-46), + (0.125, 24.0, 0.75, 3.70853404816862016966e-17), + (16.0, 0.75, 0.99999999975, + 5.4408759277418629909e-07), + # gh-4677 (numbers from stackoverflow question): + (0.4211959643503401, 16939.046996018118, + 0.000815296167195521, 1e-7)]) + def test_betaincc_betainccinv(self, a, b, x, p): + p1 = special.betaincc(a, b, x) + assert_allclose(p1, p, rtol=5e-15) + x1 = special.betainccinv(a, b, p) + assert_allclose(x1, x, rtol=8e-15) + + @pytest.mark.parametrize( + 'a, b, y, ref', + [(14.208308325339239, 14.208308325339239, 7.703145458496392e-307, + 8.566004561846704e-23), + (14.0, 14.5, 1e-280, 2.9343915006642424e-21), + (3.5, 15.0, 4e-95, 1.3290751429289227e-28), + (10.0, 1.25, 2e-234, 3.982659092143654e-24), + (4.0, 99997.0, 5e-88, 3.309800566862242e-27)] + ) + def test_betaincinv_tiny_y(self, a, b, y, ref): + # Test with extremely small y values. This test includes + # a regression test for an issue in the boost code; + # see https://github.com/boostorg/math/issues/961 + # + # The reference values were computed with mpmath. For example, + # + # from mpmath import mp + # mp.dps = 1000 + # a = 14.208308325339239 + # p = 7.703145458496392e-307 + # x = mp.findroot(lambda t: mp.betainc(a, a, 0, t, + # regularized=True) - p, + # x0=8.566e-23) + # print(float(x)) + # + x = special.betaincinv(a, b, y) + assert_allclose(x, ref, rtol=1e-14) + + @pytest.mark.parametrize('func', [special.betainc, special.betaincinv, + special.btdtria, special.btdtrib, + special.betaincc, special.betainccinv]) + @pytest.mark.parametrize('args', [(-1.0, 2, 0.5), (1.5, -2.0, 0.5), + (1.5, 2.0, -0.3), (1.5, 2.0, 1.1)]) + def test_betainc_domain_errors(self, func, args): + with special.errstate(domain='raise'): + with pytest.raises(special.SpecialFunctionError, match='domain'): + func(*args) + + @pytest.mark.parametrize( + "args,expected", + [ + ((0.0, 0.0, 0.0), np.nan), + ((0.0, 0.0, 0.5), np.nan), + ((0.0, 0.0, 1.0), np.nan), + ((np.inf, np.inf, 0.0), np.nan), + ((np.inf, np.inf, 0.5), np.nan), + ((np.inf, np.inf, 1.0), np.nan), + ((0.0, 1.0, 0.0), 0.0), + ((0.0, 1.0, 0.5), 1.0), + ((0.0, 1.0, 1.0), 1.0), + ((1.0, 0.0, 0.0), 0.0), + ((1.0, 0.0, 0.5), 0.0), + ((1.0, 0.0, 1.0), 1.0), + ((0.0, np.inf, 0.0), 0.0), + ((0.0, np.inf, 0.5), 1.0), + ((0.0, np.inf, 1.0), 1.0), + ((np.inf, 0.0, 0.0), 0.0), + ((np.inf, 0.0, 0.5), 0.0), + ((np.inf, 0.0, 1.0), 1.0), + ((1.0, np.inf, 0.0), 0.0), + ((1.0, np.inf, 0.5), 1.0), + ((1.0, np.inf, 1.0), 1.0), + ((np.inf, 1.0, 0.0), 0.0), + ((np.inf, 1.0, 0.5), 0.0), + ((np.inf, 1.0, 1.0), 1.0), + ] + ) + def test_betainc_edge_cases(self, args, expected): + observed = special.betainc(*args) + assert_equal(observed, expected) + + @pytest.mark.parametrize( + "args,expected", + [ + ((0.0, 0.0, 0.0), np.nan), + ((0.0, 0.0, 0.5), np.nan), + ((0.0, 0.0, 1.0), np.nan), + ((np.inf, np.inf, 0.0), np.nan), + ((np.inf, np.inf, 0.5), np.nan), + ((np.inf, np.inf, 1.0), np.nan), + ((0.0, 1.0, 0.0), 1.0), + ((0.0, 1.0, 0.5), 0.0), + ((0.0, 1.0, 1.0), 0.0), + ((1.0, 0.0, 0.0), 1.0), + ((1.0, 0.0, 0.5), 1.0), + ((1.0, 0.0, 1.0), 0.0), + ((0.0, np.inf, 0.0), 1.0), + ((0.0, np.inf, 0.5), 0.0), + ((0.0, np.inf, 1.0), 0.0), + ((np.inf, 0.0, 0.0), 1.0), + ((np.inf, 0.0, 0.5), 1.0), + ((np.inf, 0.0, 1.0), 0.0), + ((1.0, np.inf, 0.0), 1.0), + ((1.0, np.inf, 0.5), 0.0), + ((1.0, np.inf, 1.0), 0.0), + ((np.inf, 1.0, 0.0), 1.0), + ((np.inf, 1.0, 0.5), 1.0), + ((np.inf, 1.0, 1.0), 0.0), + ] + ) + def test_betaincc_edge_cases(self, args, expected): + observed = special.betaincc(*args) + assert_equal(observed, expected) + + @pytest.mark.parametrize('dtype', [np.float32, np.float64]) + def test_gh21426(self, dtype): + # Test for gh-21426: betaincinv must not return NaN + a = np.array([5.], dtype=dtype) + x = np.array([0.5], dtype=dtype) + result = special.betaincinv(a, a, x) + assert_allclose(result, x, rtol=10 * np.finfo(dtype).eps) + + @pytest.mark.parametrize("dtype, rtol", + [(np.float32, 1e-4), + (np.float64, 1e-15)]) + @pytest.mark.parametrize("a, b, x, reference", + [(1e-20, 1e-21, 0.5, 0.0909090909090909), + (1e-15, 1e-16, 0.5, 0.09090909090909091)]) + def test_gh22682(self, a, b, x, reference, dtype, rtol): + # gh-22682: betainc returned incorrect results for tiny + # single precision inputs. test that this is resolved + a = np.array(a, dtype=dtype) + b = np.array(b, dtype=dtype) + x = np.array(x, dtype=dtype) + res = special.betainc(a, b, x) + assert_allclose(res, reference, rtol=rtol) + + +class TestCombinatorics: + def test_comb(self): + assert_allclose(special.comb([10, 10], [3, 4]), [120., 210.]) + assert_allclose(special.comb(10, 3), 120.) + assert_equal(special.comb(10, 3, exact=True), 120) + assert_equal(special.comb(10, 3, exact=True, repetition=True), 220) + + assert_allclose([special.comb(20, k, exact=True) for k in range(21)], + special.comb(20, list(range(21))), atol=1e-15) + + ii = np.iinfo(int).max + 1 + assert_equal(special.comb(ii, ii-1, exact=True), ii) + + expected = 100891344545564193334812497256 + assert special.comb(100, 50, exact=True) == expected + + def test_comb_with_np_int64(self): + n = 70 + k = 30 + np_n = np.int64(n) + np_k = np.int64(k) + res_np = special.comb(np_n, np_k, exact=True) + res_py = special.comb(n, k, exact=True) + assert res_np == res_py + + def test_comb_zeros(self): + assert_equal(special.comb(2, 3, exact=True), 0) + assert_equal(special.comb(-1, 3, exact=True), 0) + assert_equal(special.comb(2, -1, exact=True), 0) + assert_equal(special.comb(2, -1, exact=False), 0) + assert_allclose(special.comb([2, -1, 2, 10], [3, 3, -1, 3]), [0., 0., 0., 120.]) + + def test_comb_exact_non_int_error(self): + msg = "`exact=True`" + with pytest.raises(ValueError, match=msg): + special.comb(3.4, 4, exact=True) + with pytest.raises(ValueError, match=msg): + special.comb(3, 4.4, exact=True) + + @pytest.mark.parametrize('N', [0, 5, 10]) + @pytest.mark.parametrize('exact', [True, False]) + def test_comb_repetition_k_zero(self, N, exact): + # Regression test for gh-23867 + # C(n, 0) should always be 1 for n >= 0, regardless of repetition + actual = special.comb(N, 0, exact=exact, repetition=True) + assert actual == 1 + assert type(actual) is int if exact else np.float64 + + def test_comb_repetition_k_zero_array(self): + # Test array-like input with exact=False for gh-23867 + N = np.array([0, 5, 10]) + result = special.comb(N, 0, exact=False, repetition=True) + expected = np.array([1.0, 1.0, 1.0]) + assert_equal(result, expected) + + def test_perm(self): + assert_allclose(special.perm([10, 10], [3, 4]), [720., 5040.]) + assert_allclose(special.perm(10, 3), 720., atol=1.5e-7, rtol=0) + assert_equal(special.perm(10, 3, exact=True), 720) + + def test_perm_zeros(self): + assert_equal(special.perm(2, 3, exact=True), 0) + assert_equal(special.perm(-1, 3, exact=True), 0) + assert_equal(special.perm(2, -1, exact=True), 0) + assert_equal(special.perm(2, -1, exact=False), 0) + assert_allclose(special.perm([2, -1, 2, 10], [3, 3, -1, 3]), [0., 0., 0., 720.]) + + def test_perm_iv(self): + # currently `exact=True` only support scalars + with pytest.raises(ValueError, match="scalar integers"): + special.perm([1, 2], [4, 5], exact=True) + + with pytest.raises(ValueError, match="Non-integer"): + special.perm(4.6, 6, exact=True) + with pytest.raises(ValueError, match="Non-integer"): + special.perm(-4.6, 3, exact=True) + with pytest.raises(ValueError, match="Non-integer"): + special.perm(4, -3.9, exact=True) + with pytest.raises(ValueError, match="Non-integer"): + special.perm(6.0, 4.6, exact=True) + + +class TestTrigonometric: + def test_cbrt(self): + cb = special.cbrt(27) + cbrl = 27**(1.0/3.0) + assert_allclose(cb, cbrl, atol=1.5e-7, rtol=0) + + def test_cbrtmore(self): + cb1 = special.cbrt(27.9) + cbrl1 = 27.9**(1.0/3.0) + assert_allclose(cb1, cbrl1, atol=1.5e-8, rtol=0) + + def test_cosdg(self): + cdg = special.cosdg(90) + cdgrl = cos(pi/2.0) + assert_allclose(cdg, cdgrl, atol=1.5e-8, rtol=0) + + def test_cosdgmore(self): + cdgm = special.cosdg(30) + cdgmrl = cos(pi/6.0) + assert_allclose(cdgm, cdgmrl, atol=1.5e-8, rtol=0) + + def test_cosm1(self): + cs = (special.cosm1(0),special.cosm1(.3),special.cosm1(pi/10)) + csrl = (cos(0)-1,cos(.3)-1,cos(pi/10)-1) + assert_allclose(cs, csrl, atol=1.5e-8, rtol=0) + + def test_cotdg(self): + ct = special.cotdg(30) + ctrl = tan(pi/6.0)**(-1) + assert_allclose(ct, ctrl, atol=1.5e-8, rtol=0) + + def test_cotdgmore(self): + ct1 = special.cotdg(45) + ctrl1 = tan(pi/4.0)**(-1) + assert_allclose(ct1, ctrl1, atol=1.5e-8, rtol=0) + + def test_specialpoints(self): + assert_allclose(special.cotdg(45), 1.0, atol=1.5e-14, rtol=0) + assert_allclose(special.cotdg(-45), -1.0, atol=1.5e-14, rtol=0) + assert_allclose(special.cotdg(90), 0.0, atol=1.5e-14, rtol=0) + assert_allclose(special.cotdg(-90), 0.0, atol=1.5e-14, rtol=0) + assert_allclose(special.cotdg(135), -1.0, atol=1.5e-14, rtol=0) + assert_allclose(special.cotdg(-135), 1.0, atol=1.5e-14, rtol=0) + assert_allclose(special.cotdg(225), 1.0, atol=1.5e-14, rtol=0) + assert_allclose(special.cotdg(-225), -1.0, atol=1.5e-14, rtol=0) + assert_allclose(special.cotdg(270), 0.0, atol=1.5e-14, rtol=0) + assert_allclose(special.cotdg(-270), 0.0, atol=1.5e-14, rtol=0) + assert_allclose(special.cotdg(315), -1.0, atol=1.5e-14, rtol=0) + assert_allclose(special.cotdg(-315), 1.0, atol=1.5e-14, rtol=0) + assert_allclose(special.cotdg(765), 1.0, atol=1.5e-14, rtol=0) + + def test_sinc(self): + # the sinc implementation and more extensive sinc tests are in numpy + assert_array_equal(special.sinc([0]), 1) + assert_equal(special.sinc(0.0), 1.0) + + def test_sindg(self): + sn = special.sindg(90) + assert_equal(sn,1.0) + + def test_sindgmore(self): + snm = special.sindg(30) + snmrl = sin(pi/6.0) + assert_allclose(snm, snmrl, atol=1.5e-8, rtol=0) + snm1 = special.sindg(45) + snmrl1 = sin(pi/4.0) + assert_allclose(snm1, snmrl1, atol=1.5e-8, rtol=0) + + +class TestTandg: + + def test_tandg(self): + tn = special.tandg(30) + tnrl = tan(pi/6.0) + assert_allclose(tn, tnrl, atol=1.5e-8, rtol=0) + + def test_tandgmore(self): + tnm = special.tandg(45) + tnmrl = tan(pi/4.0) + assert_allclose(tnm, tnmrl, atol=1.5e-8, rtol=0) + tnm1 = special.tandg(60) + tnmrl1 = tan(pi/3.0) + assert_allclose(tnm1, tnmrl1, atol=1.5e-8, rtol=0) + + def test_specialpoints(self): + assert_allclose(special.tandg(0), 0.0, atol=1.5e-14, rtol=0) + assert_allclose(special.tandg(45), 1.0, atol=1.5e-14, rtol=0) + assert_allclose(special.tandg(-45), -1.0, atol=1.5e-14, rtol=0) + assert_allclose(special.tandg(135), -1.0, atol=1.5e-14, rtol=0) + assert_allclose(special.tandg(-135), 1.0, atol=1.5e-14, rtol=0) + assert_allclose(special.tandg(180), 0.0, atol=1.5e-14, rtol=0) + assert_allclose(special.tandg(-180), 0.0, atol=1.5e-14, rtol=0) + assert_allclose(special.tandg(225), 1.0, atol=1.5e-14, rtol=0) + assert_allclose(special.tandg(-225), -1.0, atol=1.5e-14, rtol=0) + assert_allclose(special.tandg(315), -1.0, atol=1.5e-14, rtol=0) + assert_allclose(special.tandg(-315), 1.0, atol=1.5e-14, rtol=0) + + +class TestEllip: + def test_ellipj_nan(self): + """Regression test for #912.""" + special.ellipj(0.5, np.nan) + + def test_ellipj(self): + el = special.ellipj(0.2,0) + rel = [sin(0.2),cos(0.2),1.0,0.20] + assert_allclose(el, rel, atol=1.5e-13, rtol=0) + + def test_ellipk(self): + elk = special.ellipk(.2) + assert_allclose(elk, 1.659623598610528, atol=1.5e-11, rtol=0) + + assert_equal(special.ellipkm1(0.0), np.inf) + assert_equal(special.ellipkm1(1.0), pi/2) + assert_equal(special.ellipkm1(np.inf), 0.0) + assert_equal(special.ellipkm1(np.nan), np.nan) + assert_equal(special.ellipkm1(-1), np.nan) + assert_allclose(special.ellipk(-10), 0.7908718902387385) + + def test_ellipkinc(self): + elkinc = special.ellipkinc(pi/2,.2) + elk = special.ellipk(0.2) + assert_allclose(elkinc, elk, atol=1.5e-15, rtol=0) + alpha = 20*pi/180 + phi = 45*pi/180 + m = sin(alpha)**2 + elkinc = special.ellipkinc(phi,m) + assert_allclose(elkinc, 0.79398143, atol=1.5e-8, rtol=0) + # From pg. 614 of A & S + + assert_equal(special.ellipkinc(pi/2, 0.0), pi/2) + assert_equal(special.ellipkinc(pi/2, 1.0), np.inf) + assert_equal(special.ellipkinc(pi/2, -np.inf), 0.0) + assert_equal(special.ellipkinc(pi/2, np.nan), np.nan) + assert_equal(special.ellipkinc(pi/2, 2), np.nan) + assert_equal(special.ellipkinc(0, 0.5), 0.0) + assert_equal(special.ellipkinc(np.inf, 0.5), np.inf) + assert_equal(special.ellipkinc(-np.inf, 0.5), -np.inf) + assert_equal(special.ellipkinc(np.inf, np.inf), np.nan) + assert_equal(special.ellipkinc(np.inf, -np.inf), np.nan) + assert_equal(special.ellipkinc(-np.inf, -np.inf), np.nan) + assert_equal(special.ellipkinc(-np.inf, np.inf), np.nan) + assert_equal(special.ellipkinc(np.nan, 0.5), np.nan) + assert_equal(special.ellipkinc(np.nan, np.nan), np.nan) + + assert_allclose(special.ellipkinc(0.38974112035318718, 1), 0.4, rtol=1e-14) + assert_allclose(special.ellipkinc(1.5707, -10), 0.79084284661724946) + + def test_ellipkinc_2(self): + # Regression test for gh-3550 + # ellipkinc(phi, mbad) was NaN and mvals[2:6] were twice the correct value + mbad = 0.68359375000000011 + phi = 0.9272952180016123 + m = np.nextafter(mbad, 0) + mvals = [] + for j in range(10): + mvals.append(m) + m = np.nextafter(m, 1) + f = special.ellipkinc(phi, mvals) + assert_array_almost_equal_nulp(f, np.full_like(f, 1.0259330100195334), 1) + # this bug also appears at phi + n * pi for at least small n + f1 = special.ellipkinc(phi + pi, mvals) + assert_array_almost_equal_nulp(f1, np.full_like(f1, 5.1296650500976675), 2) + + def test_ellipkinc_singular(self): + # ellipkinc(phi, 1) has closed form and is finite only for phi in (-pi/2, pi/2) + xlog = np.logspace(-300, -17, 25) + xlin = np.linspace(1e-17, 0.1, 25) + xlin2 = np.linspace(0.1, pi/2, 25, endpoint=False) + + assert_allclose(special.ellipkinc(xlog, 1), np.arcsinh(np.tan(xlog)), + rtol=1e14) + assert_allclose(special.ellipkinc(xlin, 1), np.arcsinh(np.tan(xlin)), + rtol=1e14) + assert_allclose(special.ellipkinc(xlin2, 1), np.arcsinh(np.tan(xlin2)), + rtol=1e14) + assert_equal(special.ellipkinc(np.pi/2, 1), np.inf) + assert_allclose(special.ellipkinc(-xlog, 1), np.arcsinh(np.tan(-xlog)), + rtol=1e14) + assert_allclose(special.ellipkinc(-xlin, 1), np.arcsinh(np.tan(-xlin)), + rtol=1e14) + assert_allclose(special.ellipkinc(-xlin2, 1), np.arcsinh(np.tan(-xlin2)), + rtol=1e14) + assert_equal(special.ellipkinc(-np.pi/2, 1), np.inf) + + def test_ellipe(self): + ele = special.ellipe(.2) + assert_allclose(ele, 1.4890350580958529, atol=1.5e-8, rtol=0) + + assert_equal(special.ellipe(0.0), pi/2) + assert_equal(special.ellipe(1.0), 1.0) + assert_equal(special.ellipe(-np.inf), np.inf) + assert_equal(special.ellipe(np.nan), np.nan) + assert_equal(special.ellipe(2), np.nan) + assert_allclose(special.ellipe(-10), 3.6391380384177689) + + def test_ellipeinc(self): + eleinc = special.ellipeinc(pi/2,.2) + ele = special.ellipe(0.2) + assert_allclose(eleinc, ele, atol=1.5e-14, rtol=0) + # pg 617 of A & S + alpha, phi = 52*pi/180,35*pi/180 + m = sin(alpha)**2 + eleinc = special.ellipeinc(phi,m) + assert_allclose(eleinc, 0.58823065, atol=1.5e-8, rtol=0) + + assert_equal(special.ellipeinc(pi/2, 0.0), pi/2) + assert_equal(special.ellipeinc(pi/2, 1.0), 1.0) + assert_equal(special.ellipeinc(pi/2, -np.inf), np.inf) + assert_equal(special.ellipeinc(pi/2, np.nan), np.nan) + assert_equal(special.ellipeinc(pi/2, 2), np.nan) + assert_equal(special.ellipeinc(0, 0.5), 0.0) + assert_equal(special.ellipeinc(np.inf, 0.5), np.inf) + assert_equal(special.ellipeinc(-np.inf, 0.5), -np.inf) + assert_equal(special.ellipeinc(np.inf, -np.inf), np.inf) + assert_equal(special.ellipeinc(-np.inf, -np.inf), -np.inf) + assert_equal(special.ellipeinc(np.inf, np.inf), np.nan) + assert_equal(special.ellipeinc(-np.inf, np.inf), np.nan) + assert_equal(special.ellipeinc(np.nan, 0.5), np.nan) + assert_equal(special.ellipeinc(np.nan, np.nan), np.nan) + assert_allclose(special.ellipeinc(1.5707, -10), 3.6388185585822876) + + def test_ellipeinc_2(self): + # Regression test for gh-3550 + # ellipeinc(phi, mbad) was NaN and mvals[2:6] were twice the correct value + mbad = 0.68359375000000011 + phi = 0.9272952180016123 + m = np.nextafter(mbad, 0) + mvals = [] + for j in range(10): + mvals.append(m) + m = np.nextafter(m, 1) + f = special.ellipeinc(phi, mvals) + assert_array_almost_equal_nulp(f, np.full_like(f, 0.84442884574781019), 2) + # this bug also appears at phi + n * pi for at least small n + f1 = special.ellipeinc(phi + pi, mvals) + assert_array_almost_equal_nulp(f1, np.full_like(f1, 3.3471442287390509), 4) + + +class TestEllipCarlson: + """Test for Carlson elliptic integrals ellipr[cdfgj]. + The special values used in these tests can be found in Sec. 3 of Carlson + (1994), https://arxiv.org/abs/math/9409227 + """ + def test_elliprc(self): + assert_allclose(elliprc(1, 1), 1) + assert elliprc(1, inf) == 0.0 + assert isnan(elliprc(1, 0)) + assert elliprc(1, complex(1, inf)) == 0.0 + args = array([[0.0, 0.25], + [2.25, 2.0], + [0.0, 1.0j], + [-1.0j, 1.0j], + [0.25, -2.0], + [1.0j, -1.0]]) + expected_results = array([np.pi, + np.log(2.0), + 1.1107207345396 * (1.0-1.0j), + 1.2260849569072-0.34471136988768j, + np.log(2.0) / 3.0, + 0.77778596920447+0.19832484993429j]) + for i, arr in enumerate(args): + assert_allclose(elliprc(*arr), expected_results[i]) + + def test_elliprd(self): + assert_allclose(elliprd(1, 1, 1), 1) + assert_allclose(elliprd(0, 2, 1) / 3.0, 0.59907011736779610371) + assert elliprd(1, 1, inf) == 0.0 + assert np.isinf(elliprd(1, 1, 0)) + assert np.isinf(elliprd(1, 1, complex(0, 0))) + assert np.isinf(elliprd(0, 1, complex(0, 0))) + assert isnan(elliprd(1, 1, -np.finfo(np.float64).tiny / 2.0)) + assert isnan(elliprd(1, 1, complex(-1, 0))) + args = array([[0.0, 2.0, 1.0], + [2.0, 3.0, 4.0], + [1.0j, -1.0j, 2.0], + [0.0, 1.0j, -1.0j], + [0.0, -1.0+1.0j, 1.0j], + [-2.0-1.0j, -1.0j, -1.0+1.0j]]) + expected_results = array([1.7972103521034, + 0.16510527294261, + 0.65933854154220, + 1.2708196271910+2.7811120159521j, + -1.8577235439239-0.96193450888839j, + 1.8249027393704-1.2218475784827j]) + for i, arr in enumerate(args): + assert_allclose(elliprd(*arr), expected_results[i]) + + def test_elliprf(self): + assert_allclose(elliprf(1, 1, 1), 1) + assert_allclose(elliprf(0, 1, 2), 1.31102877714605990523) + assert elliprf(1, inf, 1) == 0.0 + assert np.isinf(elliprf(0, 1, 0)) + assert isnan(elliprf(1, 1, -1)) + assert elliprf(complex(inf), 0, 1) == 0.0 + assert isnan(elliprf(1, 1, complex(-inf, 1))) + args = array([[1.0, 2.0, 0.0], + [1.0j, -1.0j, 0.0], + [0.5, 1.0, 0.0], + [-1.0+1.0j, 1.0j, 0.0], + [2.0, 3.0, 4.0], + [1.0j, -1.0j, 2.0], + [-1.0+1.0j, 1.0j, 1.0-1.0j]]) + expected_results = array([1.3110287771461, + 1.8540746773014, + 1.8540746773014, + 0.79612586584234-1.2138566698365j, + 0.58408284167715, + 1.0441445654064, + 0.93912050218619-0.53296252018635j]) + for i, arr in enumerate(args): + assert_allclose(elliprf(*arr), expected_results[i]) + + def test_elliprg(self): + assert_allclose(elliprg(1, 1, 1), 1) + assert_allclose(elliprg(0, 0, 1), 0.5) + assert_allclose(elliprg(0, 0, 0), 0) + assert np.isinf(elliprg(1, inf, 1)) + assert np.isinf(elliprg(complex(inf), 1, 1)) + args = array([[0.0, 16.0, 16.0], + [2.0, 3.0, 4.0], + [0.0, 1.0j, -1.0j], + [-1.0+1.0j, 1.0j, 0.0], + [-1.0j, -1.0+1.0j, 1.0j], + [0.0, 0.0796, 4.0]]) + expected_results = array([np.pi, + 1.7255030280692, + 0.42360654239699, + 0.44660591677018+0.70768352357515j, + 0.36023392184473+0.40348623401722j, + 1.0284758090288]) + for i, arr in enumerate(args): + assert_allclose(elliprg(*arr), expected_results[i]) + + def test_elliprj(self): + assert_allclose(elliprj(1, 1, 1, 1), 1) + assert elliprj(1, 1, inf, 1) == 0.0 + assert isnan(elliprj(1, 0, 0, 0)) + assert isnan(elliprj(-1, 1, 1, 1)) + assert elliprj(1, 1, 1, inf) == 0.0 + args = array([[0.0, 1.0, 2.0, 3.0], + [2.0, 3.0, 4.0, 5.0], + [2.0, 3.0, 4.0, -1.0+1.0j], + [1.0j, -1.0j, 0.0, 2.0], + [-1.0+1.0j, -1.0-1.0j, 1.0, 2.0], + [1.0j, -1.0j, 0.0, 1.0-1.0j], + [-1.0+1.0j, -1.0-1.0j, 1.0, -3.0+1.0j], + [2.0, 3.0, 4.0, -0.5], # Cauchy principal value + [2.0, 3.0, 4.0, -5.0]]) # Cauchy principal value + expected_results = array([0.77688623778582, + 0.14297579667157, + 0.13613945827771-0.38207561624427j, + 1.6490011662711, + 0.94148358841220, + 1.8260115229009+1.2290661908643j, + -0.61127970812028-1.0684038390007j, + 0.24723819703052, # Cauchy principal value + -0.12711230042964]) # Caucny principal value + for i, arr in enumerate(args): + assert_allclose(elliprj(*arr), expected_results[i]) + + @pytest.mark.xfail(reason="Insufficient accuracy on 32-bit") + def test_elliprj_hard(self): + assert_allclose(elliprj(6.483625725195452e-08, + 1.1649136528196886e-27, + 3.6767340167168e+13, + 0.493704617023468), + 8.63426920644241857617477551054e-6, + rtol=5e-15, atol=1e-20) + assert_allclose(elliprj(14.375105857849121, + 9.993988969725365e-11, + 1.72844262269944e-26, + 5.898871222598245e-06), + 829774.1424801627252574054378691828, + rtol=5e-15, atol=1e-20) + + +class TestEllipLegendreCarlsonIdentities: + """Test identities expressing the Legendre elliptic integrals in terms + of Carlson's symmetric integrals. These identities can be found + in the DLMF https://dlmf.nist.gov/19.25#i . + """ + + def setup_class(self): + self.m_n1_1 = np.arange(-1., 1., 0.01) + # For double, this is -(2**1024) + self.max_neg = finfo(double).min + # Lots of very negative numbers + self.very_neg_m = -1. * 2.**arange(-1 + + np.log2(-self.max_neg), 0., + -1.) + self.ms_up_to_1 = np.concatenate(([self.max_neg], + self.very_neg_m, + self.m_n1_1)) + + def test_k(self): + """Test identity: + K(m) = R_F(0, 1-m, 1) + """ + m = self.ms_up_to_1 + assert_allclose(ellipk(m), elliprf(0., 1.-m, 1.)) + + def test_km1(self): + """Test identity: + K(m) = R_F(0, 1-m, 1) + But with the ellipkm1 function + """ + # For double, this is 2**-1022 + tiny = finfo(double).tiny + # All these small powers of 2, up to 2**-1 + m1 = tiny * 2.**arange(0., -np.log2(tiny)) + assert_allclose(ellipkm1(m1), elliprf(0., m1, 1.)) + + def test_e(self): + """Test identity: + E(m) = 2*R_G(0, 1-k^2, 1) + """ + m = self.ms_up_to_1 + assert_allclose(ellipe(m), 2.*elliprg(0., 1.-m, 1.)) + + +class TestErf: + + def test_erf(self): + er = special.erf(.25) + assert_allclose(er, 0.2763263902, atol=1.5e-8, rtol=0) + + def test_erf_zeros(self): + erz = special.erf_zeros(5) + erzr = array([1.45061616+1.88094300j, + 2.24465928+2.61657514j, + 2.83974105+3.17562810j, + 3.33546074+3.64617438j, + 3.76900557+4.06069723j]) + assert_allclose(erz, erzr, atol=1.5e-4, rtol=0) + + def _check_variant_func(self, func, other_func, rtol, atol=0): + rng = np.random.RandomState(1234) + n = 10000 + x = rng.pareto(0.02, n) * (2*rng.randint(0, 2, n) - 1) + y = rng.pareto(0.02, n) * (2*rng.randint(0, 2, n) - 1) + z = x + 1j*y + + with np.errstate(all='ignore'): + w = other_func(z) + w_real = other_func(x).real + + mask = np.isfinite(w) + w = w[mask] + z = z[mask] + + mask = np.isfinite(w_real) + w_real = w_real[mask] + x = x[mask] + + # test both real and complex variants + assert_func_equal(func, w, z, rtol=rtol, atol=atol) + assert_func_equal(func, w_real, x, rtol=rtol, atol=atol) + + def test_erfc_consistent(self): + self._check_variant_func( + cephes.erfc, + lambda z: 1 - cephes.erf(z), + rtol=1e-12, + atol=1e-14 # <- the test function loses precision + ) + + def test_erfcx_consistent(self): + self._check_variant_func( + cephes.erfcx, + lambda z: np.exp(z*z) * cephes.erfc(z), + rtol=1e-12 + ) + + def test_erfi_consistent(self): + self._check_variant_func( + cephes.erfi, + lambda z: -1j * cephes.erf(1j*z), + rtol=1e-12 + ) + + def test_dawsn_consistent(self): + self._check_variant_func( + cephes.dawsn, + lambda z: sqrt(pi)/2 * np.exp(-z*z) * cephes.erfi(z), + rtol=1e-12 + ) + + def test_erf_nan_inf(self): + vals = [np.nan, -np.inf, np.inf] + expected = [np.nan, -1, 1] + assert_allclose(special.erf(vals), expected, rtol=1e-15) + + def test_erfc_nan_inf(self): + vals = [np.nan, -np.inf, np.inf] + expected = [np.nan, 2, 0] + assert_allclose(special.erfc(vals), expected, rtol=1e-15) + + def test_erfcx_nan_inf(self): + vals = [np.nan, -np.inf, np.inf] + expected = [np.nan, np.inf, 0] + assert_allclose(special.erfcx(vals), expected, rtol=1e-15) + + def test_erfi_nan_inf(self): + vals = [np.nan, -np.inf, np.inf] + expected = [np.nan, -np.inf, np.inf] + assert_allclose(special.erfi(vals), expected, rtol=1e-15) + + def test_dawsn_nan_inf(self): + vals = [np.nan, -np.inf, np.inf] + expected = [np.nan, -0.0, 0.0] + assert_allclose(special.dawsn(vals), expected, rtol=1e-15) + + def test_wofz_nan_inf(self): + vals = [np.nan, -np.inf, np.inf] + expected = [np.nan + np.nan * 1.j, 0.-0.j, 0.+0.j] + assert_allclose(special.wofz(vals), expected, rtol=1e-15) + + +class TestEuler: + def test_euler(self): + eu0 = special.euler(0) + eu1 = special.euler(1) + eu2 = special.euler(2) # just checking segfaults + assert_allclose(eu0, [1], rtol=1e-15) + assert_allclose(eu1, [1, 0], rtol=1e-15) + assert_allclose(eu2, [1, 0, -1], rtol=1e-15) + eu24 = special.euler(24) + mathworld = [1,1,5,61,1385,50521,2702765,199360981, + 19391512145,2404879675441, + 370371188237525,69348874393137901, + 15514534163557086905] + correct = zeros((25,),'d') + for k in range(0,13): + if (k % 2): + correct[2*k] = -float(mathworld[k]) + else: + correct[2*k] = float(mathworld[k]) + with np.errstate(all='ignore'): + err = nan_to_num((eu24-correct)/correct) + errmax = max(err) + assert_allclose(errmax, 0.0, atol=1.5e-14, rtol=0) + + +class TestExp: + def test_exp2(self): + ex = special.exp2(2) + exrl = 2**2 + assert_equal(ex,exrl) + + def test_exp2more(self): + exm = special.exp2(2.5) + exmrl = 2**(2.5) + assert_allclose(exm, exmrl, atol=1.5e-8, rtol=0) + + def test_exp10(self): + ex = special.exp10(2) + exrl = 10**2 + assert_allclose(ex, exrl, atol=1e-6, rtol=0) + + def test_exp10more(self): + exm = special.exp10(2.5) + exmrl = 10**(2.5) + assert_allclose(exm, exmrl, atol=1.5e-8, rtol=0) + + def test_expm1(self): + ex = (special.expm1(2), special.expm1(3), special.expm1(4)) + exrl = (exp(2) - 1, exp(3) - 1, exp(4) - 1) + assert_allclose(ex, exrl, atol=1.5e-8, rtol=0) + + def test_expm1more(self): + ex1 = (special.expm1(2), special.expm1(2.1), special.expm1(2.2)) + exrl1 = (exp(2) - 1, exp(2.1) - 1, exp(2.2) - 1) + assert_allclose(ex1, exrl1, atol=1.5e-8, rtol=0) + + +def assert_really_equal(x, y, rtol=None): + """ + Sharper assertion function that is stricter about matching types, not just values + + This is useful/necessary in some cases: + * dtypes for arrays that have the same _values_ (e.g. element 1.0 vs 1) + * distinguishing complex from real NaN + * result types for scalars + + We still want to be able to allow a relative tolerance for the values though. + The main logic comparison logic is handled by the xp_assert_* functions. + """ + def assert_func(x, y): + xp_assert_equal(x, y) if rtol is None else xp_assert_close(x, y, rtol=rtol) + + def assert_complex_nan(x): + assert np.isnan(x.real) and np.isnan(x.imag) + + assert type(x) is type(y), f"types not equal: {type(x)}, {type(y)}" + + # ensure we also compare the values _within_ an array appropriately, + # e.g. assert_equal does not distinguish different complex nans in arrays + if isinstance(x, np.ndarray): + # assert_equal does not compare (all) types, only values + assert x.dtype == y.dtype + # for empty arrays resp. to ensure shapes match + assert_func(x, y) + for elem_x, elem_y in zip(x.ravel(), y.ravel()): + assert_really_equal(elem_x, elem_y, rtol=rtol) + elif np.isnan(x) and np.isnan(y) and _is_subdtype(type(x), "c"): + assert_complex_nan(x) and assert_complex_nan(y) + # no need to consider complex infinities due to numpy/numpy#25493 + else: + assert_func(x, y) + + +class TestFactorialFunctions: + def factorialk_ref(self, n, k, exact, extend): + if exact: + return special.factorialk(n, k=k, exact=True) + # for details / explanation see factorialk-docstring + r = np.mod(n, k) if extend == "zero" else 1 + vals = np.power(k, (n - r)/k) * special.gamma(n/k + 1) * special.rgamma(r/k + 1) + # np.maximum is element-wise, which is what we want + return vals * np.maximum(r, 1) + + @pytest.mark.parametrize("exact,extend", + [(True, "zero"), (False, "zero"), (False, "complex")]) + def test_factorialx_scalar_return_type(self, exact, extend): + kw = {"exact": exact, "extend": extend} + assert np.isscalar(special.factorial(1, **kw)) + assert np.isscalar(special.factorial2(1, **kw)) + assert np.isscalar(special.factorialk(1, k=3, **kw)) + + @pytest.mark.parametrize("n", [-1, -2, -3]) + @pytest.mark.parametrize("exact", [True, False]) + def test_factorialx_negative_extend_zero(self, exact, n): + kw = {"exact": exact} + assert_equal(special.factorial(n, **kw), 0) + assert_equal(special.factorial2(n, **kw), 0) + assert_equal(special.factorialk(n, k=3, **kw), 0) + + @pytest.mark.parametrize("exact", [True, False]) + def test_factorialx_negative_extend_zero_array(self, exact): + kw = {"exact": exact} + rtol = 1e-15 + n = [-5, -4, 0, 1] + # Consistent output for n < 0 + expected = np.array([0, 0, 1, 1], dtype=native_int if exact else np.float64) + assert_really_equal(special.factorial(n, **kw), expected, rtol=rtol) + assert_really_equal(special.factorial2(n, **kw), expected, rtol=rtol) + assert_really_equal(special.factorialk(n, k=3, **kw), expected, rtol=rtol) + + @pytest.mark.parametrize("n", [-1.1, -2.2, -3.3]) + def test_factorialx_negative_extend_complex(self, n): + kw = {"extend": "complex"} + exp_1 = {-1.1: -10.686287021193184771, + -2.2: 4.8509571405220931958, + -3.3: -1.4471073942559181166} + exp_2 = {-1.1: 1.0725776858167496309, + -2.2: -3.9777171783768419874, + -3.3: -0.99588841846200555977} + exp_k = {-1.1: 0.73565345382163025659, + -2.2: 1.1749163167190809498, + -3.3: -2.4780584257450583713} + rtol = 3e-15 + assert_allclose(special.factorial(n, **kw), exp_1[n], rtol=rtol) + assert_allclose(special.factorial2(n, **kw), exp_2[n], rtol=rtol) + assert_allclose(special.factorialk(n, k=3, **kw), exp_k[n], rtol=rtol) + assert_allclose(special.factorial([n], **kw)[0], exp_1[n], rtol=rtol) + assert_allclose(special.factorial2([n], **kw)[0], exp_2[n], rtol=rtol) + assert_allclose(special.factorialk([n], k=3, **kw)[0], exp_k[n], rtol=rtol) + + @pytest.mark.parametrize("imag", [0, 0j]) + @pytest.mark.parametrize("n_outer", [-1, -2, -3]) + def test_factorialx_negative_extend_complex_poles(self, n_outer, imag): + kw = {"extend": "complex"} + def _check(n): + complexify = _is_subdtype(type(n), "c") + # like for gamma, we expect complex nans for complex inputs + complex_nan = np.complex128("nan+nanj") + exp = np.complex128("nan+nanj") if complexify else np.float64("nan") + # poles are at negative integers that are multiples of k + assert_really_equal(special.factorial(n, **kw), exp) + assert_really_equal(special.factorial2(n * 2, **kw), exp) + assert_really_equal(special.factorialk(n * 3, k=3, **kw), exp) + # also test complex k for factorialk + c = 1.5 - 2j + assert_really_equal(special.factorialk(n * c, k=c, **kw), complex_nan) + # same for array case + assert_really_equal(special.factorial([n], **kw)[0], exp) + assert_really_equal(special.factorial2([n * 2], **kw)[0], exp) + assert_really_equal(special.factorialk([n * 3], k=3, **kw)[0], exp) + assert_really_equal(special.factorialk([n * c], k=c, **kw)[0], complex_nan) + # more specific tests in test_factorial{,2,k}_complex_reference + + # imag ensures we test both real and complex representations of the poles + _check(n_outer + imag) + # check for large multiple of period + _check(100_000 * n_outer + imag) + + @pytest.mark.parametrize("boxed", [True, False]) + @pytest.mark.parametrize("extend", ["zero", "complex"]) + @pytest.mark.parametrize( + "n", + [ + np.nan, np.float64("nan"), np.nan + np.nan*1j, np.complex128("nan+nanj"), + np.inf, np.inf + 0j, -np.inf, -np.inf + 0j, None, np.datetime64("nat", "s") + ], + ids=[ + "NaN", "np.float64('nan')", "NaN+i*NaN", "np.complex128('nan+nanj')", + "inf", "inf+0i", "-inf", "-inf+0i", "None", "NaT" + ] + ) + @pytest.mark.parametrize( + "factorialx", + [special.factorial, special.factorial2, special.factorialk] + ) + def test_factorialx_inf_nan(self, factorialx, n, extend, boxed): + # NaNs not allowed (by dtype) for exact=True + kw = {"exact": False, "extend": extend} + if factorialx == special.factorialk: + kw["k"] = 3 + + # None is allowed for scalars, but would cause object type in array case + permissible_types = ["i", "f", "c"] if boxed else ["i", "f", "c", type(None)] + # factorial allows floats also for extend="zero" + types_need_complex_ext = "c" if factorialx == special.factorial else ["f", "c"] + + if not _is_subdtype(type(n), permissible_types): + with pytest.raises(ValueError, match="Unsupported data type.*"): + factorialx([n] if boxed else n, **kw) + elif _is_subdtype(type(n), types_need_complex_ext) and extend != "complex": + with pytest.raises(ValueError, match="In order to use non-integer.*"): + factorialx([n] if boxed else n, **kw) + else: + # account for type and whether extend="complex" + complexify = (extend == "complex") and _is_subdtype(type(n), "c") + # note that the type of the naïve `np.nan + np.nan * 1j` is `complex` + # instead of `numpy.complex128`, which trips up assert_really_equal + expected = np.complex128("nan+nanj") if complexify else np.float64("nan") + # the only exception are real infinities + if _is_subdtype(type(n), "f") and np.isinf(n): + # unchanged for positive infinity; negative one depends on extension + neg_inf_result = np.float64(0 if (extend == "zero") else "nan") + expected = np.float64("inf") if (n > 0) else neg_inf_result + + result = factorialx([n], **kw)[0] if boxed else factorialx(n, **kw) + assert_really_equal(result, expected) + # also tested in test_factorial{,2,k}_{array,scalar}_corner_cases + + @pytest.mark.parametrize("extend", [0, 1.1, np.nan, "string"]) + def test_factorialx_raises_extend(self, extend): + with pytest.raises(ValueError, match="argument `extend` must be.*"): + special.factorial(1, extend=extend) + with pytest.raises(ValueError, match="argument `extend` must be.*"): + special.factorial2(1, extend=extend) + with pytest.raises(ValueError, match="argument `extend` must be.*"): + special.factorialk(1, k=3, exact=True, extend=extend) + + @pytest.mark.parametrize("levels", range(1, 5)) + @pytest.mark.parametrize("exact", [True, False]) + def test_factorialx_array_shape(self, levels, exact): + def _nest_me(x, k=1): + """ + Double x and nest it k times + + For example: + >>> _nest_me([3, 4], 2) + [[[3, 4], [3, 4]], [[3, 4], [3, 4]]] + """ + if k == 0: + return x + else: + return _nest_me([x, x], k-1) + + def _check(res, nucleus): + exp = np.array(_nest_me(nucleus, k=levels), dtype=object) + # test that ndarray shape is maintained + # need to cast to float due to numpy/numpy#21220 + assert_allclose(res.astype(np.float64), exp.astype(np.float64)) + + n = np.array(_nest_me([5, 25], k=levels)) + exp_nucleus = {1: [120, math.factorial(25)], + # correctness of factorial{2,k}() is tested elsewhere + 2: [15, special.factorial2(25, exact=True)], + 3: [10, special.factorialk(25, 3, exact=True)]} + + _check(special.factorial(n, exact=exact), exp_nucleus[1]) + _check(special.factorial2(n, exact=exact), exp_nucleus[2]) + _check(special.factorialk(n, 3, exact=exact), exp_nucleus[3]) + + @pytest.mark.parametrize("exact", [True, False]) + @pytest.mark.parametrize("dtype", [ + None, int, np.int8, np.int16, np.int32, np.int64, + np.uint8, np.uint16, np.uint32, np.uint64 + ]) + @pytest.mark.parametrize("dim", range(0, 5)) + def test_factorialx_array_dimension(self, dim, dtype, exact): + n = np.array(5, dtype=dtype, ndmin=dim) + exp = {1: 120, 2: 15, 3: 10} + assert_allclose(special.factorial(n, exact=exact), + np.array(exp[1], ndmin=dim)) + assert_allclose(special.factorial2(n, exact=exact), + np.array(exp[2], ndmin=dim)) + assert_allclose(special.factorialk(n, 3, exact=exact), + np.array(exp[3], ndmin=dim)) + + @pytest.mark.parametrize("exact", [True, False]) + @pytest.mark.parametrize("level", range(1, 5)) + def test_factorialx_array_like(self, level, exact): + def _nest_me(x, k=1): + if k == 0: + return x + else: + return _nest_me([x], k-1) + + n = _nest_me([5], k=level-1) # nested list + exp_nucleus = {1: 120, 2: 15, 3: 10} + assert_func = assert_array_equal if exact else assert_allclose + assert_func(special.factorial(n, exact=exact), + np.array(exp_nucleus[1], ndmin=level)) + assert_func(special.factorial2(n, exact=exact), + np.array(exp_nucleus[2], ndmin=level)) + assert_func(special.factorialk(n, 3, exact=exact), + np.array(exp_nucleus[3], ndmin=level)) + + @pytest.mark.fail_slow(5) + @pytest.mark.parametrize("dtype", [np.uint8, np.uint16, np.uint32, np.uint64]) + @pytest.mark.parametrize("exact,extend", + [(True, "zero"), (False, "zero"), (False, "complex")]) + def test_factorialx_uint(self, exact, extend, dtype): + # ensure that uint types work correctly as inputs + kw = {"exact": exact, "extend": extend} + assert_func = assert_array_equal if exact else assert_allclose + def _check(n): + n_ref = n.astype(np.int64) if isinstance(n, np.ndarray) else np.int64(n) + assert_func(special.factorial(n, **kw), special.factorial(n_ref, **kw)) + assert_func(special.factorial2(n, **kw), special.factorial2(n_ref, **kw)) + assert_func(special.factorialk(n, k=3, **kw), + special.factorialk(n_ref, k=3, **kw)) + def _check_inf(n): + # produce inf of same type/dimension + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + shaped_inf = n / 0 + assert_func(special.factorial(n, **kw), shaped_inf) + assert_func(special.factorial2(n, **kw), shaped_inf) + assert_func(special.factorialk(n, k=3, **kw), shaped_inf) + + _check(dtype(0)) + _check(dtype(1)) + _check(np.array(0, dtype=dtype)) + _check(np.array([0, 1], dtype=dtype)) + # test that maximal uint values work as well + N = dtype(np.iinfo(dtype).max) + # TODO: cannot use N itself yet; factorial uses `gamma(N+1)` resp. `(hi+lo)//2` + if dtype == np.uint64: + if exact: + # avoid attempting huge calculation + pass + else: + # N does not fit into int64 --> cannot use _check + _check_inf(dtype(N-1)) + _check_inf(np.array(N-1, dtype=dtype)) + _check_inf(np.array([N-1], dtype=dtype)) + elif dtype in [np.uint8, np.uint16] or not exact: + # factorial(65535, exact=True) has 287189 digits and is calculated almost + # instantaneously on modern hardware; however, dtypes bigger than uint16 + # would blow up runtime and memory consumption for exact=True + _check(N-1) + _check(np.array(N-1, dtype=dtype)) + _check(np.array([N-2, N-1], dtype=dtype)) + + # note that n=170 is the last integer such that factorial(n) fits float64 + @pytest.mark.parametrize('n', range(30, 180, 10)) + def test_factorial_accuracy(self, n): + # Compare exact=True vs False, i.e. that the accuracy of the + # approximation is better than the specified tolerance. + + rtol = 6e-14 if sys.platform == 'win32' else 1e-15 + # need to cast exact result to float due to numpy/numpy#21220 + assert_allclose(float(special.factorial(n, exact=True)), + special.factorial(n, exact=False), rtol=rtol) + assert_allclose(special.factorial([n], exact=True).astype(float), + special.factorial([n], exact=False), rtol=rtol) + + @pytest.mark.parametrize('n', + list(range(0, 22)) + list(range(30, 180, 10))) + def test_factorial_int_reference(self, n): + # Compare all with math.factorial + correct = math.factorial(n) + assert_array_equal(correct, special.factorial(n, exact=True)) + assert_array_equal(correct, special.factorial([n], exact=True)[0]) + + rtol = 8e-14 if sys.platform == 'win32' else 1e-15 + # need to cast exact result to float due to numpy/numpy#21220 + correct = float(correct) + assert_allclose(correct, special.factorial(n, exact=False), rtol=rtol) + assert_allclose(correct, special.factorial([n], exact=False)[0], rtol=rtol) + + # extend="complex" only works for exact=False + kw = {"exact": False, "extend": "complex"} + assert_allclose(correct, special.factorial(n, **kw), rtol=rtol) + assert_allclose(correct, special.factorial([n], **kw)[0], rtol=rtol) + + def test_factorial_float_reference(self): + def _check(n, expected): + rtol = 8e-14 if sys.platform == 'win32' else 1e-15 + assert_allclose(special.factorial(n), expected, rtol=rtol) + assert_allclose(special.factorial([n])[0], expected, rtol=rtol) + # using floats with `exact=True` raises an error for scalars and arrays + with pytest.raises(ValueError, match="`exact=True` only supports.*"): + special.factorial(n, exact=True) + with pytest.raises(ValueError, match="`exact=True` only supports.*"): + special.factorial([n], exact=True) + + # Reference values from mpmath for gamma(n+1) + _check(0.01, 0.994325851191506032181932988) + _check(1.11, 1.051609009483625091514147465) + _check(5.55, 314.9503192327208241614959052) + _check(11.1, 50983227.84411615655137170553) + _check(33.3, 2.493363339642036352229215273e+37) + _check(55.5, 9.479934358436729043289162027e+73) + _check(77.7, 3.060540559059579022358692625e+114) + _check(99.9, 5.885840419492871504575693337e+157) + # close to maximum for float64 + _check(170.6243, 1.79698185749571048960082e+308) + + def test_factorial_complex_reference(self): + def _check(n, expected): + rtol = 3e-15 if sys.platform == 'win32' else 2e-15 + kw = {"exact": False, "extend": "complex"} + assert_allclose(special.factorial(n, **kw), expected, rtol=rtol) + assert_allclose(special.factorial([n], **kw)[0], expected, rtol=rtol) + + # Reference values from mpmath.gamma(n+1) + # negative & complex values + _check(-0.5, expected=1.7724538509055160276) + _check(-0.5 + 0j, expected=1.7724538509055160276 + 0j) + _check(2 + 2j, expected=-0.42263728631120216694 + 0.87181425569650686062j) + # close to poles + _check(-0.9999, expected=9999.422883232725532) + _check(-1 + 0.0001j, expected=-0.57721565582674219 - 9999.9999010944009697j) + + @pytest.mark.parametrize("dtype", [np.int64, np.float64, + np.complex128, object]) + @pytest.mark.parametrize("extend", ["zero", "complex"]) + @pytest.mark.parametrize("exact", [True, False]) + @pytest.mark.parametrize("dim", range(0, 5)) + # test empty & non-empty arrays, with nans and mixed + @pytest.mark.parametrize( + "content", + [[], [1], [1.1], [np.nan], [np.nan + np.nan * 1j], [np.nan, 1]], + ids=["[]", "[1]", "[1.1]", "[NaN]", "[NaN+i*NaN]", "[NaN, 1]"], + ) + def test_factorial_array_corner_cases(self, content, dim, exact, extend, dtype): + if dtype is object and SCIPY_ARRAY_API: + pytest.skip("object arrays unsupported in array API mode") + # get dtype without calling array constructor (that might fail or mutate) + if dtype is np.int64 and any(np.isnan(x) or (x != int(x)) for x in content): + pytest.skip("impossible combination") + if dtype == np.float64 and any(_is_subdtype(type(x), "c") for x in content): + pytest.skip("impossible combination") + + kw = {"exact": exact, "extend": extend} + # np.array(x, ndim=0) will not be 0-dim. unless x is too + content = content if (dim > 0 or len(content) != 1) else content[0] + n = np.array(content, ndmin=dim, dtype=dtype) + + result = None + if extend == "complex" and exact: + with pytest.raises(ValueError, match="Incompatible options:.*"): + special.factorial(n, **kw) + elif not _is_subdtype(n.dtype, ["i", "f", "c"]): + with pytest.raises(ValueError, match="Unsupported data type.*"): + special.factorial(n, **kw) + elif _is_subdtype(n.dtype, "c") and extend != "complex": + with pytest.raises(ValueError, match="In order to use non-integer.*"): + special.factorial(n, **kw) + elif exact and not _is_subdtype(n.dtype, "i"): + with pytest.raises(ValueError, match="`exact=True` only supports.*"): + special.factorial(n, **kw) + else: + result = special.factorial(n, **kw) + + if result is not None: + # use scalar case as reference; tested separately in *_scalar_corner_cases + ref = [special.factorial(x, **kw) for x in n.ravel()] + # unpack length-1 lists so that np.array(x, ndim=0) works correctly + ref = ref[0] if len(ref) == 1 else ref + # result is empty if and only if n is empty, and has the same dimension + # as n; dtype stays the same, except when not empty and not exact: + if n.size: + cx = (extend == "complex") and _is_subdtype(n.dtype, "c") + dtype = np.complex128 if cx else (native_int if exact else np.float64) + expected = np.array(ref, ndmin=dim, dtype=dtype) + assert_really_equal(result, expected, rtol=1e-15) + + @pytest.mark.parametrize("extend", ["zero", "complex"]) + @pytest.mark.parametrize("exact", [True, False]) + @pytest.mark.parametrize("n", [1, 1.1, 2 + 2j, np.nan, np.nan + np.nan*1j, None], + ids=["1", "1.1", "2+2j", "NaN", "NaN+i*NaN", "None"]) + def test_factorial_scalar_corner_cases(self, n, exact, extend): + kw = {"exact": exact, "extend": extend} + if extend == "complex" and exact: + with pytest.raises(ValueError, match="Incompatible options:.*"): + special.factorial(n, **kw) + elif not _is_subdtype(type(n), ["i", "f", "c", type(None)]): + with pytest.raises(ValueError, match="Unsupported data type.*"): + special.factorial(n, **kw) + elif _is_subdtype(type(n), "c") and extend != "complex": + with pytest.raises(ValueError, match="In order to use non-integer.*"): + special.factorial(n, **kw) + elif n is None or np.isnan(n): + # account for dtype and whether extend="complex" + complexify = (extend == "complex") and _is_subdtype(type(n), "c") + expected = np.complex128("nan+nanj") if complexify else np.float64("nan") + assert_really_equal(special.factorial(n, **kw), expected) + elif exact and _is_subdtype(type(n), "f"): + with pytest.raises(ValueError, match="`exact=True` only supports.*"): + special.factorial(n, **kw) + else: + assert_equal(special.factorial(n, **kw), special.gamma(n + 1)) + + # use odd increment to make sure both odd & even numbers are tested! + @pytest.mark.parametrize('n', range(30, 180, 11)) + def test_factorial2_accuracy(self, n): + # Compare exact=True vs False, i.e. that the accuracy of the + # approximation is better than the specified tolerance. + + rtol = 2e-14 if sys.platform == 'win32' else 1e-15 + # need to cast exact result to float due to numpy/numpy#21220 + assert_allclose(float(special.factorial2(n, exact=True)), + special.factorial2(n, exact=False), rtol=rtol) + assert_allclose(special.factorial2([n], exact=True).astype(float), + special.factorial2([n], exact=False), rtol=rtol) + + @pytest.mark.parametrize('n', + list(range(0, 22)) + list(range(30, 180, 11))) + def test_factorial2_int_reference(self, n): + # Compare all with correct value + + # Cannot use np.product due to overflow + correct = functools.reduce(operator.mul, list(range(n, 0, -2)), 1) + + assert_array_equal(correct, special.factorial2(n, exact=True)) + assert_array_equal(correct, special.factorial2([n], exact=True)[0]) + + rtol = 2e-14 if sys.platform == 'win32' else 1e-15 + # need to cast exact result to float due to numpy/numpy#21220 + correct = float(correct) + assert_allclose(correct, special.factorial2(n, exact=False), rtol=rtol) + assert_allclose(correct, special.factorial2([n], exact=False)[0], rtol=rtol) + + # extend="complex" only works for exact=False + kw = {"exact": False, "extend": "complex"} + # approximation only matches exactly for `n == 1 (mod k)`, see docstring + if n % 2 == 1: + assert_allclose(correct, special.factorial2(n, **kw), rtol=rtol) + assert_allclose(correct, special.factorial2([n], **kw)[0], rtol=rtol) + + def test_factorial2_complex_reference(self): + # this tests for both floats and complex + def _check(n, expected): + rtol = 5e-15 + kw = {"exact": False, "extend": "complex"} + assert_allclose(special.factorial2(n, **kw), expected, rtol=rtol) + assert_allclose(special.factorial2([n], **kw)[0], expected, rtol=rtol) + + # Reference values from mpmath for: + # mpmath.power(2, n/2) * mpmath.gamma(n/2 + 1) * mpmath.sqrt(2 / mpmath.pi) + _check(3, expected=3) + _check(4, expected=special.factorial2(4) * math.sqrt(2 / math.pi)) + _check(20, expected=special.factorial2(20) * math.sqrt(2 / math.pi)) + # negative & complex values + _check(-0.5, expected=0.82217895866245855122) + _check(-0.5 + 0j, expected=0.82217895866245855122 + 0j) + _check(3 + 3j, expected=-1.0742236630142471526 + 1.4421398439387262897j) + # close to poles + _check(-1.9999, expected=7978.8918745523440682) + _check(-2 + 0.0001j, expected=0.0462499835314308444 - 7978.84559148876374493j) + + @pytest.mark.parametrize("dtype", [np.int64, np.float64, + np.complex128, object]) + @pytest.mark.parametrize("extend", ["zero", "complex"]) + @pytest.mark.parametrize("exact", [True, False]) + @pytest.mark.parametrize("dim", range(0, 5)) + # test empty & non-empty arrays, with nans and mixed + @pytest.mark.parametrize( + "content", + [[], [1], [1.1], [np.nan], [np.nan + np.nan * 1j], [np.nan, 1]], + ids=["[]", "[1]", "[1.1]", "[NaN]", "[NaN+i*NaN]", "[NaN, 1]"], + ) + def test_factorial2_array_corner_cases(self, content, dim, exact, extend, dtype): + # get dtype without calling array constructor (that might fail or mutate) + if dtype == np.int64 and any(np.isnan(x) or (x != int(x)) for x in content): + pytest.skip("impossible combination") + if dtype == np.float64 and any(_is_subdtype(type(x), "c") for x in content): + pytest.skip("impossible combination") + + kw = {"exact": exact, "extend": extend} + # np.array(x, ndim=0) will not be 0-dim. unless x is too + content = content if (dim > 0 or len(content) != 1) else content[0] + n = np.array(content, ndmin=dim, dtype=dtype) + + result = None + if extend == "complex" and exact: + with pytest.raises(ValueError, match="Incompatible options:.*"): + special.factorial2(n, **kw) + elif not _is_subdtype(n.dtype, ["i", "f", "c"]): + with pytest.raises(ValueError, match="Unsupported data type.*"): + special.factorial2(n, **kw) + elif _is_subdtype(n.dtype, ["f", "c"]) and extend != "complex": + with pytest.raises(ValueError, match="In order to use non-integer.*"): + special.factorial2(n, **kw) + else: + result = special.factorial2(n, **kw) + + if result is not None: + # use scalar case as reference; tested separately in *_scalar_corner_cases + ref = [special.factorial2(x, **kw) for x in n.ravel()] + # unpack length-1 lists so that np.array(x, ndim=0) works correctly + ref = ref[0] if len(ref) == 1 else ref + # result is empty if and only if n is empty, and has the same dimension + # as n; dtype stays the same, except when not empty and not exact: + if n.size: + cx = (extend == "complex") and _is_subdtype(n.dtype, "c") + dtype = np.complex128 if cx else (native_int if exact else np.float64) + expected = np.array(ref, ndmin=dim, dtype=dtype) + assert_really_equal(result, expected, rtol=2e-15) + + @pytest.mark.parametrize("extend", ["zero", "complex"]) + @pytest.mark.parametrize("exact", [True, False]) + @pytest.mark.parametrize("n", [1, 1.1, 2 + 2j, np.nan, np.nan + np.nan*1j, None], + ids=["1", "1.1", "2+2j", "NaN", "NaN+i*NaN", "None"]) + def test_factorial2_scalar_corner_cases(self, n, exact, extend): + kw = {"exact": exact, "extend": extend} + if extend == "complex" and exact: + with pytest.raises(ValueError, match="Incompatible options:.*"): + special.factorial2(n, **kw) + elif not _is_subdtype(type(n), ["i", "f", "c", type(None)]): + with pytest.raises(ValueError, match="Unsupported data type.*"): + special.factorial2(n, **kw) + elif _is_subdtype(type(n), ["f", "c"]) and extend != "complex": + with pytest.raises(ValueError, match="In order to use non-integer.*"): + special.factorial2(n, **kw) + elif n is None or np.isnan(n): + # account for dtype and whether extend="complex" + complexify = (extend == "complex") and _is_subdtype(type(n), "c") + expected = np.complex128("nan+nanj") if complexify else np.float64("nan") + assert_really_equal(special.factorial2(n, **kw), expected) + else: + expected = self.factorialk_ref(n, k=2, **kw) + assert_really_equal(special.factorial2(n, **kw), expected, rtol=1e-15) + + @pytest.mark.parametrize("k", range(1, 5)) + # note that n=170 is the last integer such that factorial(n) fits float64; + # use odd increment to make sure both odd & even numbers are tested + @pytest.mark.parametrize('n', range(170, 20, -29)) + def test_factorialk_accuracy(self, n, k): + # Compare exact=True vs False, i.e. that the accuracy of the + # approximation is better than the specified tolerance. + + rtol = 6e-14 if sys.platform == 'win32' else 2e-14 + # need to cast exact result to float due to numpy/numpy#21220 + assert_allclose(float(special.factorialk(n, k=k, exact=True)), + special.factorialk(n, k=k, exact=False), rtol=rtol) + assert_allclose(special.factorialk([n], k=k, exact=True).astype(float), + special.factorialk([n], k=k, exact=False), rtol=rtol) + + @pytest.mark.parametrize('k', list(range(1, 5)) + [10, 20]) + @pytest.mark.parametrize('n', + list(range(0, 22)) + list(range(22, 100, 11))) + def test_factorialk_int_reference(self, n, k): + # Compare all with correct value + + # Would be nice to use np.product here, but that's + # broken on windows, see numpy/numpy#21219 + correct = functools.reduce(operator.mul, list(range(n, 0, -k)), 1) + + assert_array_equal(correct, special.factorialk(n, k, exact=True)) + assert_array_equal(correct, special.factorialk([n], k, exact=True)[0]) + + rtol = 3e-14 if sys.platform == 'win32' else 1e-14 + # need to cast exact result to float due to numpy/numpy#21220 + correct = float(correct) + assert_allclose(correct, special.factorialk(n, k, exact=False), rtol=rtol) + assert_allclose(correct, special.factorialk([n], k, exact=False)[0], rtol=rtol) + + # extend="complex" only works for exact=False + kw = {"k": k, "exact": False, "extend": "complex"} + # approximation only matches exactly for `n == 1 (mod k)`, see docstring + if n % k == 1: + rtol = 2e-14 + assert_allclose(correct, special.factorialk(n, **kw), rtol=rtol) + assert_allclose(correct, special.factorialk([n], **kw)[0], rtol=rtol) + + def test_factorialk_complex_reference(self): + # this tests for both floats and complex + def _check(n, k, exp): + rtol = 1e-14 + kw = {"k": k, "exact": False, "extend": "complex"} + assert_allclose(special.factorialk(n, **kw), exp, rtol=rtol) + assert_allclose(special.factorialk([n], **kw)[0], exp, rtol=rtol) + + # Reference values from mpmath for: + # mpmath.power(k, (n-1)/k) * mpmath.gamma(n/k + 1) / mpmath.gamma(1/k + 1) + _check(n=4, k=3, exp=special.factorialk(4, k=3, exact=True)) + _check(n=5, k=3, exp=7.29011132947227083) + _check(n=6.5, k=3, exp=19.6805080113566010) + # non-integer k + _check(n=3, k=2.5, exp=2.58465740293218541) + _check(n=11, k=2.5, exp=1963.5) # ==11*8.5*6*3.5; c.f. n == 1 (mod k) + _check(n=-3 + 3j + 1, k=-3 + 3j, exp=-2 + 3j) + # complex values + _check(n=4 + 4j, k=4, exp=-0.67855904082768043854 + 2.1993925819930311497j) + _check(n=4, k=4 - 4j, exp=1.9775338957222718742 + 0.92607172675423901371j) + _check(n=4 + 4j, k=4 - 4j, exp=0.1868492880824934475 + 0.87660580316894290247j) + # negative values + _check(n=-0.5, k=3, exp=0.72981013240713739354) + _check(n=-0.5 + 0j, k=3, exp=0.72981013240713739354 + 0j) + _check(n=2.9, k=-0.7, exp=0.45396591474966867296 + 0.56925525174685228866j) + _check(n=-0.6, k=-0.7, exp=-0.07190820089634757334 - 0.090170031876701730081j) + # close to poles + _check(n=-2.9999, k=3, exp=7764.7170695908828364) + _check(n=-3 + 0.0001j, k=3, exp=0.1349475632879599864 - 7764.5821055158365027j) + + @pytest.mark.parametrize("dtype", [np.int64, np.float64, + np.complex128, object]) + @pytest.mark.parametrize("extend", ["zero", "complex"]) + @pytest.mark.parametrize("exact", [True, False]) + @pytest.mark.parametrize("dim", range(0, 5)) + # test empty & non-empty arrays, with nans and mixed + @pytest.mark.parametrize( + "content", + [[], [1], [1.1], [np.nan], [np.nan + np.nan * 1j], [np.nan, 1]], + ids=["[]", "[1]", "[1.1]", "[NaN]", "[NaN+i*NaN]", "[NaN, 1]"], + ) + def test_factorialk_array_corner_cases(self, content, dim, exact, extend, dtype): + # get dtype without calling array constructor (that might fail or mutate) + if dtype == np.int64 and any(np.isnan(x) or (x != int(x)) for x in content): + pytest.skip("impossible combination") + if dtype == np.float64 and any(_is_subdtype(type(x), "c") for x in content): + pytest.skip("impossible combination") + + kw = {"k": 3, "exact": exact, "extend": extend} + # np.array(x, ndim=0) will not be 0-dim. unless x is too + content = content if (dim > 0 or len(content) != 1) else content[0] + n = np.array(content, ndmin=dim, dtype=dtype) + + result = None + if extend == "complex" and exact: + with pytest.raises(ValueError, match="Incompatible options:.*"): + special.factorialk(n, **kw) + elif not _is_subdtype(n.dtype, ["i", "f", "c"]): + with pytest.raises(ValueError, match="Unsupported data type.*"): + special.factorialk(n, **kw) + elif _is_subdtype(n.dtype, ["f", "c"]) and extend != "complex": + with pytest.raises(ValueError, match="In order to use non-integer.*"): + special.factorialk(n, **kw) + else: + result = special.factorialk(n, **kw) + + if result is not None: + # use scalar case as reference; tested separately in *_scalar_corner_cases + ref = [special.factorialk(x, **kw) for x in n.ravel()] + # unpack length-1 lists so that np.array(x, ndim=0) works correctly + ref = ref[0] if len(ref) == 1 else ref + # result is empty if and only if n is empty, and has the same dimension + # as n; dtype stays the same, except when not empty and not exact: + if n.size: + cx = (extend == "complex") and _is_subdtype(n.dtype, "c") + dtype = np.complex128 if cx else (native_int if exact else np.float64) + expected = np.array(ref, ndmin=dim, dtype=dtype) + assert_really_equal(result, expected, rtol=2e-15) + + @pytest.mark.parametrize("extend", ["zero", "complex"]) + @pytest.mark.parametrize("exact", [True, False]) + @pytest.mark.parametrize("k", range(1, 5)) + @pytest.mark.parametrize("n", [1, 1.1, 2 + 2j, np.nan, np.nan + np.nan*1j, None], + ids=["1", "1.1", "2+2j", "NaN", "NaN+i*NaN", "None"]) + def test_factorialk_scalar_corner_cases(self, n, k, exact, extend): + kw = {"k": k, "exact": exact, "extend": extend} + if extend == "complex" and exact: + with pytest.raises(ValueError, match="Incompatible options:.*"): + special.factorialk(n, **kw) + elif not _is_subdtype(type(n), ["i", "f", "c", type(None)]): + with pytest.raises(ValueError, match="Unsupported data type.*"): + special.factorialk(n, **kw) + elif _is_subdtype(type(n), ["f", "c"]) and extend != "complex": + with pytest.raises(ValueError, match="In order to use non-integer.*"): + special.factorialk(n, **kw) + elif n is None or np.isnan(n): + # account for dtype and whether extend="complex" + complexify = (extend == "complex") and _is_subdtype(type(n), "c") + expected = np.complex128("nan+nanj") if complexify else np.float64("nan") + assert_really_equal(special.factorialk(n, **kw), expected) + else: + expected = self.factorialk_ref(n, **kw) + assert_really_equal(special.factorialk(n, **kw), expected, rtol=1e-15) + + @pytest.mark.parametrize("boxed", [True, False]) + @pytest.mark.parametrize("exact,extend", + [(True, "zero"), (False, "zero"), (False, "complex")]) + @pytest.mark.parametrize("k", [-1, -1.0, 0, 0.0, 0 + 1j, 1.1, np.nan]) + def test_factorialk_raises_k_complex(self, k, exact, extend, boxed): + n = [1] if boxed else 1 + kw = {"k": k, "exact": exact, "extend": extend} + if extend == "zero": + msg = "In order to use non-integer.*" + if _is_subdtype(type(k), "i") and (k < 1): + msg = "For `extend='zero'`.*" + with pytest.raises(ValueError, match=msg): + special.factorialk(n, **kw) + elif k == 0: + with pytest.raises(ValueError, match="Parameter k cannot be zero!"): + special.factorialk(n, **kw) + else: + # no error + special.factorialk(n, **kw) + + @pytest.mark.parametrize("boxed", [True, False]) + @pytest.mark.parametrize("exact,extend", + [(True, "zero"), (False, "zero"), (False, "complex")]) + # neither integer, float nor complex + @pytest.mark.parametrize("k", ["string", np.datetime64("nat", "s")], + ids=["string", "NaT"]) + def test_factorialk_raises_k_other(self, k, exact, extend, boxed): + n = [1] if boxed else 1 + kw = {"k": k, "exact": exact, "extend": extend} + with pytest.raises(ValueError, match="Unsupported data type.*"): + special.factorialk(n, **kw) + + @pytest.mark.parametrize("exact,extend", + [(True, "zero"), (False, "zero"), (False, "complex")]) + @pytest.mark.parametrize("k", range(1, 12)) + def test_factorialk_dtype(self, k, exact, extend): + kw = {"k": k, "exact": exact, "extend": extend} + if exact and k in _FACTORIALK_LIMITS_64BITS.keys(): + n = np.array([_FACTORIALK_LIMITS_32BITS[k]]) + assert_equal(special.factorialk(n, **kw).dtype, np.long) + assert_equal(special.factorialk(n + 1, **kw).dtype, np.int64) + # assert maximality of limits for given dtype + assert special.factorialk(n + 1, **kw) > np.iinfo(np.int32).max + + n = np.array([_FACTORIALK_LIMITS_64BITS[k]]) + assert_equal(special.factorialk(n, **kw).dtype, np.int64) + assert_equal(special.factorialk(n + 1, **kw).dtype, object) + assert special.factorialk(n + 1, **kw) > np.iinfo(np.int64).max + else: + n = np.array([_FACTORIALK_LIMITS_64BITS.get(k, 1)]) + # for exact=True and k >= 10, we always return object; + # for exact=False it's always float (unless input is complex) + dtype = object if exact else np.float64 + assert_equal(special.factorialk(n, **kw).dtype, dtype) + + def test_factorial_mixed_nan_inputs(self): + x = np.array([np.nan, 1, 2, 3, np.nan]) + expected = np.array([np.nan, 1, 2, 6, np.nan]) + assert_equal(special.factorial(x, exact=False), expected) + with pytest.raises(ValueError, match="`exact=True` only supports.*"): + special.factorial(x, exact=True) + + +class TestFresnel: + @pytest.mark.parametrize("z, s, c", [ + # some positive value + (.5, 0.064732432859999287, 0.49234422587144644), + (.5 + .0j, 0.064732432859999287, 0.49234422587144644), + # negative half annulus + # https://github.com/scipy/scipy/issues/12309 + # Reference values can be reproduced with + # https://www.wolframalpha.com/input/?i=FresnelS%5B-2.0+%2B+0.1i%5D + # https://www.wolframalpha.com/input/?i=FresnelC%5B-2.0+%2B+0.1i%5D + ( + -2.0 + 0.1j, + -0.3109538687728942-0.0005870728836383176j, + -0.4879956866358554+0.10670801832903172j + ), + ( + -0.1 - 1.5j, + -0.03918309471866977+0.7197508454568574j, + 0.09605692502968956-0.43625191013617465j + ), + # a different algorithm kicks in for "large" values, i.e., |z| >= 4.5, + # make sure to test both float and complex values; a different + # algorithm is used + (6.0, 0.44696076, 0.49953147), + (6.0 + 0.0j, 0.44696076, 0.49953147), + (6.0j, -0.44696076j, 0.49953147j), + (-6.0 + 0.0j, -0.44696076, -0.49953147), + (-6.0j, 0.44696076j, -0.49953147j), + # inf + (np.inf, 0.5, 0.5), + (-np.inf, -0.5, -0.5), + ]) + def test_fresnel_values(self, z, s, c): + frs = array(special.fresnel(z)) + assert_allclose(frs, array([s, c]), atol=1.5e-8, rtol=0) + + # values from pg 329 Table 7.11 of A & S + # slightly corrected in 4th decimal place + def test_fresnel_zeros(self): + szo, czo = special.fresnel_zeros(5) + assert_allclose(szo, array([2.0093+0.2885j, + 2.8335+0.2443j, + 3.4675+0.2185j, + 4.0026+0.2009j, + 4.4742+0.1877j]), + atol=1.5e-3, rtol=0) + assert_allclose(czo, array([1.7437+0.3057j, + 2.6515+0.2529j, + 3.3204+0.2240j, + 3.8757+0.2047j, + 4.3611+0.1907j]), + atol=1.5e-3, rtol=0) + vals1 = special.fresnel(szo)[0] + vals2 = special.fresnel(czo)[1] + assert_allclose(vals1, 0, atol=1.5e-14, rtol=0) + assert_allclose(vals2, 0, atol=1.5e-14, rtol=0) + + def test_fresnelc_zeros(self): + szo, czo = special.fresnel_zeros(6) + frc = special.fresnelc_zeros(6) + assert_allclose(frc, czo, atol=1.5e-12, rtol=0) + + def test_fresnels_zeros(self): + szo, czo = special.fresnel_zeros(5) + frs = special.fresnels_zeros(5) + assert_allclose(frs, szo, atol=1.5e-12, rtol=0) + + +class TestGamma: + def test_gamma(self): + gam = special.gamma(5) + assert_equal(gam,24.0) + + def test_gammaln(self): + gamln = special.gammaln(3) + lngam = log(special.gamma(3)) + assert_allclose(gamln, lngam, atol=1.5e-8, rtol=0) + + def test_gammainccinv(self): + gccinv = special.gammainccinv(.5,.5) + gcinv = special.gammaincinv(.5,.5) + assert_allclose(gccinv, gcinv, atol=1.5e-8, rtol=0) + + @with_special_errors + def test_gammaincinv(self): + y = special.gammaincinv(.4,.4) + x = special.gammainc(.4,y) + assert_allclose(x, 0.4, atol=1.5e-10, rtol=0) + y = special.gammainc(10, 0.05) + x = special.gammaincinv(10, 2.5715803516000736e-20) + assert_allclose(0.05, x, atol=1.5e-10, rtol=0) + assert_allclose(y, 2.5715803516000736e-20, atol=1.5e-10, rtol=0) + x = special.gammaincinv(50, 8.20754777388471303050299243573393e-18) + assert_allclose(11.0, x, atol=1.5e-10, rtol=0) + + @with_special_errors + def test_975(self): + # Regression test for ticket #975 -- switch point in algorithm + # check that things work OK at the point, immediately next floats + # around it, and a bit further away + pts = [0.25, + np.nextafter(0.25, 0), 0.25 - 1e-12, + np.nextafter(0.25, 1), 0.25 + 1e-12] + for pt in pts: + y = special.gammaincinv(.4, pt) + x = special.gammainc(0.4, y) + assert_allclose(x, pt, rtol=1e-12) + + def test_rgamma(self): + rgam = special.rgamma(8) + rlgam = 1/special.gamma(8) + assert_allclose(rgam, rlgam, atol=1.5e-8, rtol=0) + + def test_infinity(self): + assert_equal(special.rgamma(-1), 0) + + @pytest.mark.parametrize( + "x,expected", + [ + # infinities + ([-np.inf, np.inf], [np.nan, np.inf]), + # negative and positive zero + ([-0.0, 0.0], [-np.inf, np.inf]), + # small poles + (range(-32, 0), np.full(32, np.nan)), + # medium sized poles + (range(-1024, -32, 99), np.full(11, np.nan)), + # large pole + ([-4.141512231792294e+16], [np.nan]), + ] + ) + def test_poles(self, x, expected): + assert_array_equal(special.gamma(x), expected) + + +class TestHankel: + + def test_negv1(self): + assert_allclose(special.hankel1(-3, 2), -special.hankel1(3, 2), + atol=1.5e-14, rtol=0) + + def test_hankel1(self): + hank1 = special.hankel1(1,.1) + hankrl = (special.jv(1,.1) + special.yv(1,.1)*1j) + assert_allclose(hank1, hankrl, atol=1.5e-8, rtol=0) + + def test_negv1e(self): + assert_allclose(special.hankel1e(-3, 2), -special.hankel1e(3, 2), + atol=1.5e-14, rtol=0) + + def test_hankel1e(self): + hank1e = special.hankel1e(1,.1) + hankrle = special.hankel1(1,.1)*exp(-.1j) + assert_allclose(hank1e, hankrle, atol=1.5e-8, rtol=0) + + def test_negv2(self): + assert_allclose(special.hankel2(-3, 2), -special.hankel2(3, 2), + atol=1.5e-14, rtol=0) + + def test_hankel2(self): + hank2 = special.hankel2(1,.1) + hankrl2 = (special.jv(1,.1) - special.yv(1,.1)*1j) + assert_allclose(hank2, hankrl2, atol=1.5e-8, rtol=0) + + def test_neg2e(self): + assert_allclose(special.hankel2e(-3, 2), -special.hankel2e(3, 2), + atol=1.5e-14, rtol=0) + + def test_hankl2e(self): + hank2e = special.hankel2e(1,.1) + hankrl2e = special.hankel2e(1,.1) + assert_allclose(hank2e, hankrl2e, atol=1.5e-8, rtol=0) + + def test_hankel2_gh4517(self): + # Test edge case reported in https://github.com/scipy/scipy/issues/4517 + res = special.hankel2(0, 0) + assert np.isnan(res.real) + assert np.isposinf(res.imag) + + +class TestHyper: + def test_h1vp(self): + h1 = special.h1vp(1,.1) + h1real = (special.jvp(1,.1) + special.yvp(1,.1)*1j) + assert_allclose(h1, h1real, atol=1.5e-8, rtol=0) + + def test_h2vp(self): + h2 = special.h2vp(1,.1) + h2real = (special.jvp(1,.1) - special.yvp(1,.1)*1j) + assert_allclose(h2, h2real, atol=1.5e-8, rtol=0) + + def test_hyp0f1(self): + # scalar input + assert_allclose(special.hyp0f1(2.5, 0.5), 1.21482702689997, rtol=1e-12) + assert_allclose(special.hyp0f1(2.5, 0), 1.0, rtol=1e-15) + + # float input, expected values match mpmath + x = special.hyp0f1(3.0, [-1.5, -1, 0, 1, 1.5]) + expected = np.array([0.58493659229143, 0.70566805723127, 1.0, + 1.37789689539747, 1.60373685288480]) + assert_allclose(x, expected, rtol=1e-12) + + # complex input + x = special.hyp0f1(3.0, np.array([-1.5, -1, 0, 1, 1.5]) + 0.j) + assert_allclose(x, expected.astype(complex), rtol=1e-12) + + # test broadcasting + x1 = [0.5, 1.5, 2.5] + x2 = [0, 1, 0.5] + x = special.hyp0f1(x1, x2) + expected = [1.0, 1.8134302039235093, 1.21482702689997] + assert_allclose(x, expected, rtol=1e-12) + x = special.hyp0f1(np.vstack([x1] * 2), x2) + assert_allclose(x, np.vstack([expected] * 2), rtol=1e-12) + assert_raises(ValueError, special.hyp0f1, + np.vstack([x1] * 3), [0, 1]) + + def test_hyp0f1_gh5764(self): + # Just checks the point that failed; there's a more systematic + # test in test_mpmath + res = special.hyp0f1(0.8, 0.5 + 0.5*1J) + # The expected value was generated using mpmath + assert_allclose(res, 1.6139719776441115 + 1J*0.80893054061790665, + atol=1.5e-7, rtol=0) + + def test_hyp1f1(self): + hyp1 = special.hyp1f1(.1,.1,.3) + assert_allclose(hyp1, 1.3498588075760032, atol=1.5e-7, rtol=0) + + # test contributed by Moritz Deger (2008-05-29) + # https://github.com/scipy/scipy/issues/1186 (Trac #659) + + # reference data obtained from mathematica [ a, b, x, m(a,b,x)]: + # produced with test_hyp1f1.nb + ref_data = array([ + [-8.38132975e+00, -1.28436461e+01, -2.91081397e+01, 1.04178330e+04], + [2.91076882e+00, -6.35234333e+00, -1.27083993e+01, 6.68132725e+00], + [-1.42938258e+01, 1.80869131e-01, 1.90038728e+01, 1.01385897e+05], + [5.84069088e+00, 1.33187908e+01, 2.91290106e+01, 1.59469411e+08], + [-2.70433202e+01, -1.16274873e+01, -2.89582384e+01, 1.39900152e+24], + [4.26344966e+00, -2.32701773e+01, 1.91635759e+01, 6.13816915e+21], + [1.20514340e+01, -3.40260240e+00, 7.26832235e+00, 1.17696112e+13], + [2.77372955e+01, -1.99424687e+00, 3.61332246e+00, 3.07419615e+13], + [1.50310939e+01, -2.91198675e+01, -1.53581080e+01, -3.79166033e+02], + [1.43995827e+01, 9.84311196e+00, 1.93204553e+01, 2.55836264e+10], + [-4.08759686e+00, 1.34437025e+01, -1.42072843e+01, 1.70778449e+01], + [8.05595738e+00, -1.31019838e+01, 1.52180721e+01, 3.06233294e+21], + [1.81815804e+01, -1.42908793e+01, 9.57868793e+00, -2.84771348e+20], + [-2.49671396e+01, 1.25082843e+01, -1.71562286e+01, 2.36290426e+07], + [2.67277673e+01, 1.70315414e+01, 6.12701450e+00, 7.77917232e+03], + [2.49565476e+01, 2.91694684e+01, 6.29622660e+00, 2.35300027e+02], + [6.11924542e+00, -1.59943768e+00, 9.57009289e+00, 1.32906326e+11], + [-1.47863653e+01, 2.41691301e+01, -1.89981821e+01, 2.73064953e+03], + [2.24070483e+01, -2.93647433e+00, 8.19281432e+00, -6.42000372e+17], + [8.04042600e-01, 1.82710085e+01, -1.97814534e+01, 5.48372441e-01], + [1.39590390e+01, 1.97318686e+01, 2.37606635e+00, 5.51923681e+00], + [-4.66640483e+00, -2.00237930e+01, 7.40365095e+00, 4.50310752e+00], + [2.76821999e+01, -6.36563968e+00, 1.11533984e+01, -9.28725179e+23], + [-2.56764457e+01, 1.24544906e+00, 1.06407572e+01, 1.25922076e+01], + [3.20447808e+00, 1.30874383e+01, 2.26098014e+01, 2.03202059e+04], + [-1.24809647e+01, 4.15137113e+00, -2.92265700e+01, 2.39621411e+08], + [2.14778108e+01, -2.35162960e+00, -1.13758664e+01, 4.46882152e-01], + [-9.85469168e+00, -3.28157680e+00, 1.67447548e+01, -1.07342390e+07], + [1.08122310e+01, -2.47353236e+01, -1.15622349e+01, -2.91733796e+03], + [-2.67933347e+01, -3.39100709e+00, 2.56006986e+01, -5.29275382e+09], + [-8.60066776e+00, -8.02200924e+00, 1.07231926e+01, 1.33548320e+06], + [-1.01724238e-01, -1.18479709e+01, -2.55407104e+01, 1.55436570e+00], + [-3.93356771e+00, 2.11106818e+01, -2.57598485e+01, 2.13467840e+01], + [3.74750503e+00, 1.55687633e+01, -2.92841720e+01, 1.43873509e-02], + [6.99726781e+00, 2.69855571e+01, -1.63707771e+01, 3.08098673e-02], + [-2.31996011e+01, 3.47631054e+00, 9.75119815e-01, 1.79971073e-02], + [2.38951044e+01, -2.91460190e+01, -2.50774708e+00, 9.56934814e+00], + [1.52730825e+01, 5.77062507e+00, 1.21922003e+01, 1.32345307e+09], + [1.74673917e+01, 1.89723426e+01, 4.94903250e+00, 9.90859484e+01], + [1.88971241e+01, 2.86255413e+01, 5.52360109e-01, 1.44165360e+00], + [1.02002319e+01, -1.66855152e+01, -2.55426235e+01, 6.56481554e+02], + [-1.79474153e+01, 1.22210200e+01, -1.84058212e+01, 8.24041812e+05], + [-1.36147103e+01, 1.32365492e+00, -7.22375200e+00, 9.92446491e+05], + [7.57407832e+00, 2.59738234e+01, -1.34139168e+01, 3.64037761e-02], + [2.21110169e+00, 1.28012666e+01, 1.62529102e+01, 1.33433085e+02], + [-2.64297569e+01, -1.63176658e+01, -1.11642006e+01, -2.44797251e+13], + [-2.46622944e+01, -3.02147372e+00, 8.29159315e+00, -3.21799070e+05], + [-1.37215095e+01, -1.96680183e+01, 2.91940118e+01, 3.21457520e+12], + [-5.45566105e+00, 2.81292086e+01, 1.72548215e-01, 9.66973000e-01], + [-1.55751298e+00, -8.65703373e+00, 2.68622026e+01, -3.17190834e+16], + [2.45393609e+01, -2.70571903e+01, 1.96815505e+01, 1.80708004e+37], + [5.77482829e+00, 1.53203143e+01, 2.50534322e+01, 1.14304242e+06], + [-1.02626819e+01, 2.36887658e+01, -2.32152102e+01, 7.28965646e+02], + [-1.30833446e+00, -1.28310210e+01, 1.87275544e+01, -9.33487904e+12], + [5.83024676e+00, -1.49279672e+01, 2.44957538e+01, -7.61083070e+27], + [-2.03130747e+01, 2.59641715e+01, -2.06174328e+01, 4.54744859e+04], + [1.97684551e+01, -2.21410519e+01, -2.26728740e+01, 3.53113026e+06], + [2.73673444e+01, 2.64491725e+01, 1.57599882e+01, 1.07385118e+07], + [5.73287971e+00, 1.21111904e+01, 1.33080171e+01, 2.63220467e+03], + [-2.82751072e+01, 2.08605881e+01, 9.09838900e+00, -6.60957033e-07], + [1.87270691e+01, -1.74437016e+01, 1.52413599e+01, 6.59572851e+27], + [6.60681457e+00, -2.69449855e+00, 9.78972047e+00, -2.38587870e+12], + [1.20895561e+01, -2.51355765e+01, 2.30096101e+01, 7.58739886e+32], + [-2.44682278e+01, 2.10673441e+01, -1.36705538e+01, 4.54213550e+04], + [-4.50665152e+00, 3.72292059e+00, -4.83403707e+00, 2.68938214e+01], + [-7.46540049e+00, -1.08422222e+01, -1.72203805e+01, -2.09402162e+02], + [-2.00307551e+01, -7.50604431e+00, -2.78640020e+01, 4.15985444e+19], + [1.99890876e+01, 2.20677419e+01, -2.51301778e+01, 1.23840297e-09], + [2.03183823e+01, -7.66942559e+00, 2.10340070e+01, 1.46285095e+31], + [-2.90315825e+00, -2.55785967e+01, -9.58779316e+00, 2.65714264e-01], + [2.73960829e+01, -1.80097203e+01, -2.03070131e+00, 2.52908999e+02], + [-2.11708058e+01, -2.70304032e+01, 2.48257944e+01, 3.09027527e+08], + [2.21959758e+01, 4.00258675e+00, -1.62853977e+01, -9.16280090e-09], + [1.61661840e+01, -2.26845150e+01, 2.17226940e+01, -8.24774394e+33], + [-3.35030306e+00, 1.32670581e+00, 9.39711214e+00, -1.47303163e+01], + [7.23720726e+00, -2.29763909e+01, 2.34709682e+01, -9.20711735e+29], + [2.71013568e+01, 1.61951087e+01, -7.11388906e-01, 2.98750911e-01], + [8.40057933e+00, -7.49665220e+00, 2.95587388e+01, 6.59465635e+29], + [-1.51603423e+01, 1.94032322e+01, -7.60044357e+00, 1.05186941e+02], + [-8.83788031e+00, -2.72018313e+01, 1.88269907e+00, 1.81687019e+00], + [-1.87283712e+01, 5.87479570e+00, -1.91210203e+01, 2.52235612e+08], + [-5.61338513e-01, 2.69490237e+01, 1.16660111e-01, 9.97567783e-01], + [-5.44354025e+00, -1.26721408e+01, -4.66831036e+00, 1.06660735e-01], + [-2.18846497e+00, 2.33299566e+01, 9.62564397e+00, 3.03842061e-01], + [6.65661299e+00, -2.39048713e+01, 1.04191807e+01, 4.73700451e+13], + [-2.57298921e+01, -2.60811296e+01, 2.74398110e+01, -5.32566307e+11], + [-1.11431826e+01, -1.59420160e+01, -1.84880553e+01, -1.01514747e+02], + [6.50301931e+00, 2.59859051e+01, -2.33270137e+01, 1.22760500e-02], + [-1.94987891e+01, -2.62123262e+01, 3.90323225e+00, 1.71658894e+01], + [7.26164601e+00, -1.41469402e+01, 2.81499763e+01, -2.50068329e+31], + [-1.52424040e+01, 2.99719005e+01, -2.85753678e+01, 1.31906693e+04], + [5.24149291e+00, -1.72807223e+01, 2.22129493e+01, 2.50748475e+25], + [3.63207230e-01, -9.54120862e-02, -2.83874044e+01, 9.43854939e-01], + [-2.11326457e+00, -1.25707023e+01, 1.17172130e+00, 1.20812698e+00], + [2.48513582e+00, 1.03652647e+01, -1.84625148e+01, 6.47910997e-02], + [2.65395942e+01, 2.74794672e+01, 1.29413428e+01, 2.89306132e+05], + [-9.49445460e+00, 1.59930921e+01, -1.49596331e+01, 3.27574841e+02], + [-5.89173945e+00, 9.96742426e+00, 2.60318889e+01, -3.15842908e-01], + [-1.15387239e+01, -2.21433107e+01, -2.17686413e+01, 1.56724718e-01], + [-5.30592244e+00, -2.42752190e+01, 1.29734035e+00, 1.31985534e+00] + ]) + + for a,b,c,expected in ref_data: + result = special.hyp1f1(a,b,c) + assert_(abs(expected - result)/expected < 1e-4) + + def test_hyp1f1_gh2957(self): + hyp1 = special.hyp1f1(0.5, 1.5, -709.7827128933) + hyp2 = special.hyp1f1(0.5, 1.5, -709.7827128934) + assert_allclose(hyp1, hyp2, atol=1.5e-12, rtol=0) + + def test_hyp1f1_gh2282(self): + hyp = special.hyp1f1(0.5, 1.5, -1000) + assert_allclose(hyp, 0.028024956081989643, atol=1.5e-12, rtol=0) + + def test_hyp2f1(self): + # a collection of special cases taken from AMS 55 + values = [ + [0.5, 1, 1.5, 0.2**2, 0.5/0.2*log((1+0.2)/(1-0.2))], + [0.5, 1, 1.5, -0.2**2, 1./0.2*arctan(0.2)], + [1, 1, 2, 0.2, -1/0.2*log(1-0.2)], + [3, 3.5, 1.5, 0.2**2, 0.5/0.2/(-5)*((1+0.2)**(-5)-(1-0.2)**(-5))], + [-3, 3, 0.5, sin(0.2)**2, cos(2*3*0.2)], + [3, 4, 8, 1, + special.gamma(8) * special.gamma(8-4-3) + / special.gamma(8-3) / special.gamma(8-4)], + [3, 2, 3-2+1, -1, + 1./2**3*sqrt(pi) * special.gamma(1+3-2) + / special.gamma(1+0.5*3-2) / special.gamma(0.5+0.5*3)], + [5, 2, 5-2+1, -1, + 1./2**5*sqrt(pi) * special.gamma(1+5-2) + / special.gamma(1+0.5*5-2) / special.gamma(0.5+0.5*5)], + [4, 0.5+4, 1.5-2*4, -1./3, + (8./9)**(-2*4)*special.gamma(4./3) * special.gamma(1.5-2*4) + / special.gamma(3./2) / special.gamma(4./3-2*4)], + # and some others + # ticket #424 + [1.5, -0.5, 1.0, -10.0, 4.1300097765277476484], + # negative integer a or b, with c-a-b integer and x > 0.9 + [-2,3,1,0.95,0.715], + [2,-3,1,0.95,-0.007], + [-6,3,1,0.95,0.0000810625], + [2,-5,1,0.95,-0.000029375], + # huge negative integers + (10, -900, 10.5, 0.99, 1.91853705796607664803709475658e-24), + (10, -900, -10.5, 0.99, 3.54279200040355710199058559155e-18), + ] + for i, (a, b, c, x, v) in enumerate(values): + cv = special.hyp2f1(a, b, c, x) + assert_allclose(cv, v, atol=1.5e-8, rtol=0, err_msg=f'test #{i}') + + def test_hyperu(self): + val1 = special.hyperu(1,0.1,100) + assert_allclose(val1, 0.0098153, atol=1.5e-7, rtol=0) + a,b = [0.3,0.6,1.2,-2.7],[1.5,3.2,-0.4,-3.2] + a,b = asarray(a), asarray(b) + z = 0.5 + hypu = special.hyperu(a,b,z) + hprl = (pi/sin(pi*b))*(special.hyp1f1(a,b,z) / + (special.gamma(1+a-b)*special.gamma(b)) - + z**(1-b)*special.hyp1f1(1+a-b,2-b,z) + / (special.gamma(a)*special.gamma(2-b))) + assert_allclose(hypu, hprl, atol=1.5e-12, rtol=0) + + def test_hyperu_gh2287(self): + assert_allclose(special.hyperu(1, 1.5, 20.2), 0.048360918656699191, + atol=1.5e-12, rtol=0) + + +class TestBessel: + def test_itj0y0(self): + it0 = array(special.itj0y0(.2)) + assert_allclose(it0, array([0.19933433254006822, -0.34570883800412566]), + atol=1.5e-8, rtol=0) + + def test_it2j0y0(self): + it2 = array(special.it2j0y0(.2)) + assert_allclose(it2, array([0.0049937546274601858, -0.43423067011231614]), + atol=1.5e-8, rtol=0) + + def test_negv_iv(self): + assert_equal(special.iv(3,2), special.iv(-3,2)) + + def test_j0(self): + oz = special.j0(.1) + ozr = special.jn(0,.1) + assert_allclose(oz, ozr, atol=1.5e-8, rtol=0) + + def test_j1(self): + o1 = special.j1(.1) + o1r = special.jn(1,.1) + assert_allclose(o1, o1r, atol=1.5e-8, rtol=0) + + def test_jn(self): + jnnr = special.jn(1,.2) + assert_allclose(jnnr, 0.099500832639235995, atol=1.5e-8, rtol=0) + + def test_negv_jv(self): + assert_allclose(special.jv(-3, 2), -special.jv(3, 2), atol=1.5e-14, rtol=0) + + def test_jv(self): + values = [[0, 0.1, 0.99750156206604002], + [2./3, 1e-8, 0.3239028506761532e-5], + [2./3, 1e-10, 0.1503423854873779e-6], + [3.1, 1e-10, 0.1711956265409013e-32], + [2./3, 4.0, -0.2325440850267039], + ] + for i, (v, x, y) in enumerate(values): + yc = special.jv(v, x) + assert_allclose(yc, y, atol=1.5e-8, rtol=0, err_msg=f'test #{i}') + + def test_negv_jve(self): + assert_allclose(special.jve(-3, 2), -special.jve(3, 2), + atol=1.5e-14, rtol=0) + + def test_jve(self): + jvexp = special.jve(1,.2) + assert_allclose(jvexp, 0.099500832639235995, atol=1.5e-8, rtol=0) + jvexp1 = special.jve(1,.2+1j) + z = .2+1j + jvexpr = special.jv(1,z)*exp(-abs(z.imag)) + assert_allclose(jvexp1, jvexpr, atol=1.5e-8, rtol=0) + + def test_jn_zeros(self): + jn0 = special.jn_zeros(0,5) + jn1 = special.jn_zeros(1,5) + assert_allclose(jn0, array([2.4048255577, + 5.5200781103, + 8.6537279129, + 11.7915344391, + 14.9309177086]), + atol=1.5e-4, rtol=0) + assert_allclose(jn1, array([3.83171, + 7.01559, + 10.17347, + 13.32369, + 16.47063]), + atol=1.5e-4, rtol=0) + + jn102 = special.jn_zeros(102,5) + assert_allclose(jn102, array([110.89174935992040343, + 117.83464175788308398, + 123.70194191713507279, + 129.02417238949092824, + 134.00114761868422559]), rtol=1e-13) + + jn301 = special.jn_zeros(301,5) + assert_allclose(jn301, array([313.59097866698830153, + 323.21549776096288280, + 331.22338738656748796, + 338.39676338872084500, + 345.03284233056064157]), rtol=1e-13) + + def test_jn_zeros_slow(self): + jn0 = special.jn_zeros(0, 300) + assert_allclose(jn0[260-1], 816.02884495068867280, rtol=1e-13) + assert_allclose(jn0[280-1], 878.86068707124422606, rtol=1e-13) + assert_allclose(jn0[300-1], 941.69253065317954064, rtol=1e-13) + + jn10 = special.jn_zeros(10, 300) + assert_allclose(jn10[260-1], 831.67668514305631151, rtol=1e-13) + assert_allclose(jn10[280-1], 894.51275095371316931, rtol=1e-13) + assert_allclose(jn10[300-1], 957.34826370866539775, rtol=1e-13) + + jn3010 = special.jn_zeros(3010,5) + assert_allclose(jn3010, array([3036.86590780927, + 3057.06598526482, + 3073.66360690272, + 3088.37736494778, + 3101.86438139042]), rtol=1e-8) + + def test_jnjnp_zeros(self): + jn = special.jn + + def jnp(n, x): + return (jn(n-1,x) - jn(n+1,x))/2 + for nt in range(1, 30): + z, n, m, t = special.jnjnp_zeros(nt) + for zz, nn, tt in zip(z, n, t): + if tt == 0: + assert_allclose(jn(nn, zz), 0, atol=1e-6) + elif tt == 1: + assert_allclose(jnp(nn, zz), 0, atol=1e-6) + else: + raise AssertionError(f"Invalid t return for nt={nt}") + + def test_jnp_zeros(self): + jnp = special.jnp_zeros(1,5) + assert_allclose(jnp, array([1.84118, + 5.33144, + 8.53632, + 11.70600, + 14.86359]), + atol=1.5e-4, rtol=0) + jnp = special.jnp_zeros(443,5) + assert_allclose(special.jvp(443, jnp), 0, atol=1e-15) + + def test_jnyn_zeros(self): + jnz = special.jnyn_zeros(1, 5) + assert_allclose(jnz, (array([3.83171, + 7.01559, + 10.17347, + 13.32369, + 16.47063]), + array([1.84118, + 5.33144, + 8.53632, + 11.70600, + 14.86359]), + array([2.19714, + 5.42968, + 8.59601, + 11.74915, + 14.89744]), + array([3.68302, + 6.94150, + 10.12340, + 13.28576, + 16.44006])), + atol=1.5e-5, rtol=0) + + def test_jvp(self): + jvprim = special.jvp(2,2) + jv0 = (special.jv(1,2)-special.jv(3,2))/2 + assert_allclose(jvprim, jv0, atol=1.5e-10, rtol=0) + + def test_k0(self): + ozk = special.k0(.1) + ozkr = special.kv(0,.1) + assert_allclose(ozk,ozkr, atol=1.5e-8, rtol=0) + + def test_k0e(self): + ozke = special.k0e(.1) + ozker = special.kve(0,.1) + assert_allclose(ozke, ozker, atol=1.5e-8, rtol=0) + + def test_k1(self): + o1k = special.k1(.1) + o1kr = special.kv(1,.1) + assert_allclose(o1k,o1kr, atol=1.5e-8, rtol=0) + + def test_k1e(self): + o1ke = special.k1e(.1) + o1ker = special.kve(1,.1) + assert_allclose(o1ke, o1ker, atol=1.5e-8, rtol=0) + + def test_jacobi(self): + a = 5*np.random.random() - 1 + b = 5*np.random.random() - 1 + P0 = special.jacobi(0,a,b) + P1 = special.jacobi(1,a,b) + P2 = special.jacobi(2,a,b) + P3 = special.jacobi(3,a,b) + + assert_allclose(P0.c, [1], atol=1.5e-13, rtol=0) + assert_allclose(P1.c, array([a + b + 2, a - b]) / 2.0, + atol=1.5e-13, rtol=0) + cp = [(a+b+3)*(a+b+4), 4*(a+b+3)*(a+2), 4*(a+1)*(a+2)] + p2c = [cp[0],cp[1]-2*cp[0],cp[2]-cp[1]+cp[0]] + assert_allclose(P2.c, array(p2c) / 8.0, atol=1.5e-13, rtol=0) + cp = [(a+b+4)*(a+b+5)*(a+b+6),6*(a+b+4)*(a+b+5)*(a+3), + 12*(a+b+4)*(a+2)*(a+3),8*(a+1)*(a+2)*(a+3)] + p3c = [cp[0],cp[1]-3*cp[0],cp[2]-2*cp[1]+3*cp[0],cp[3]-cp[2]+cp[1]-cp[0]] + assert_allclose(P3.c, array(p3c) / 48.0, atol=1.5e-13, rtol=0) + + def test_kn(self): + kn1 = special.kn(0,.2) + assert_allclose(kn1, 1.7527038555281462, atol=1.5e-8, rtol=0) + + def test_negv_kv(self): + assert_equal(special.kv(3.0, 2.2), special.kv(-3.0, 2.2)) + + def test_kv0(self): + kv0 = special.kv(0,.2) + assert_allclose(kv0, 1.7527038555281462, atol=1.5e-10, rtol=0) + + def test_kv1(self): + kv1 = special.kv(1,0.2) + assert_allclose(kv1, 4.775972543220472, atol=1.5e-10, rtol=0) + + def test_kv2(self): + kv2 = special.kv(2,0.2) + assert_allclose(kv2, 49.51242928773287, atol=1.5e-10, rtol=0) + + def test_kn_largeorder(self): + assert_allclose(special.kn(32, 1), 1.7516596664574289e+43) + + def test_kv_largearg(self): + assert_equal(special.kv(0, 1e19), 0) + + def test_negv_kve(self): + assert_equal(special.kve(3.0, 2.2), special.kve(-3.0, 2.2)) + + def test_kve(self): + kve1 = special.kve(0,.2) + kv1 = special.kv(0,.2)*exp(.2) + assert_allclose(kve1, kv1, atol=1.5e-8, rtol=0) + z = .2+1j + kve2 = special.kve(0,z) + kv2 = special.kv(0,z)*exp(z) + assert_allclose(kve2, kv2, atol=1.5e-8, rtol=0) + + def test_kvp_v0n1(self): + z = 2.2 + assert_allclose(-special.kv(1, z), special.kvp(0, z, n=1), + atol=1.5e-10, rtol=0) + + def test_kvp_n1(self): + v = 3. + z = 2.2 + xc = -special.kv(v+1,z) + v/z*special.kv(v,z) + x = special.kvp(v,z, n=1) + # this function (kvp) is broken + assert_allclose(xc, x, atol=1.5e-10, rtol=0) + + def test_kvp_n2(self): + v = 3. + z = 2.2 + xc = (z**2+v**2-v)/z**2 * special.kv(v,z) + special.kv(v+1,z)/z + x = special.kvp(v, z, n=2) + assert_allclose(xc, x, atol=1.5e-10, rtol=0) + + def test_y0(self): + oz = special.y0(.1) + ozr = special.yn(0,.1) + assert_allclose(oz, ozr, atol=1.5e-8, rtol=0) + + def test_y1(self): + o1 = special.y1(.1) + o1r = special.yn(1,.1) + assert_allclose(o1,o1r, atol=1.5e-8, rtol=0) + + def test_y0_zeros(self): + yo,ypo = special.y0_zeros(2) + zo,zpo = special.y0_zeros(2,complex=1) + all = r_[yo,zo] + allval = r_[ypo,zpo] + assert_allclose(abs(special.yv(0.0, all)), 0.0, atol=1.5e-11, rtol=0) + assert_allclose(abs(special.yv(1, all) - allval), 0.0, atol=1.5e-11, rtol=0) + + def test_y1_zeros(self): + y1 = special.y1_zeros(1) + assert_allclose(y1, (array([2.19714]), array([0.52079])), + atol=1.5e-5, rtol=0) + + def test_y1p_zeros(self): + y1p = special.y1p_zeros(1,complex=1) + assert_allclose(y1p, (array([0.5768+0.904j]), array([-0.7635+0.5892j])), + atol=1.5e-3, rtol=0) + + def test_yn_zeros(self): + an = special.yn_zeros(4,2) + assert_allclose(an, array([5.64515, 9.36162]), atol=1.5e-5, rtol=0) + an = special.yn_zeros(443,5) + assert_allclose(an, [450.13573091578090314, + 463.05692376675001542, + 472.80651546418663566, + 481.27353184725625838, + 488.98055964441374646], + rtol=1e-15,) + + def test_ynp_zeros(self): + ao = special.ynp_zeros(0,2) + assert_allclose(ao, array([2.19714133, 5.42968104]), atol=1.5e-6, rtol=0) + ao = special.ynp_zeros(43,5) + assert_allclose(special.yvp(43, ao), 0, atol=1e-15) + ao = special.ynp_zeros(443,5) + assert_allclose(special.yvp(443, ao), 0, atol=1e-9) + + def test_ynp_zeros_large_order(self): + ao = special.ynp_zeros(443,5) + assert_allclose(special.yvp(443, ao), 0, atol=1e-14) + + def test_yn(self): + yn2n = special.yn(1,.2) + assert_allclose(yn2n, -3.3238249881118471, atol=1.5e-8, rtol=0) + + def test_yn_gh_20405(self): + # Enforce correct asymptotic behavior for large n. + observed = cephes.yn(500, 1) + assert observed == -np.inf + + def test_negv_yv(self): + assert_allclose(special.yv(-3, 2), -special.yv(3, 2), + atol=1.5e-14, rtol=0) + + def test_yv(self): + yv2 = special.yv(1,.2) + assert_allclose(yv2, -3.3238249881118471, atol=1.5e-8, rtol=0) + + def test_negv_yve(self): + assert_allclose(special.yve(-3, 2), -special.yve(3, 2), + atol=1.5e-14, rtol=0) + + def test_yve(self): + yve2 = special.yve(1,.2) + assert_allclose(yve2, -3.3238249881118471, atol=1.5e-8, rtol=0) + yve2r = special.yv(1,.2+1j)*exp(-1) + yve22 = special.yve(1,.2+1j) + assert_allclose(yve22, yve2r, atol=1.5e-8, rtol=0) + + def test_yvp(self): + yvpr = (special.yv(1,.2) - special.yv(3,.2))/2.0 + yvp1 = special.yvp(2,.2) + assert_allclose(yvp1, yvpr, atol=1.5e-10, rtol=0) + + def _cephes_vs_amos_points(self): + """Yield points at which to compare Cephes implementation to AMOS""" + # check several points, including large-amplitude ones + v = [-120, -100.3, -20., -10., -1., -.5, 0., 1., 12.49, 120., 301] + z = [-1300, -11, -10, -1, 1., 10., 200.5, 401., 600.5, 700.6, 1300, + 10003] + yield from itertools.product(v, z) + + # check half-integers; these are problematic points at least + # for cephes/iv + yield from itertools.product(0.5 + arange(-60, 60), [3.5]) + + def check_cephes_vs_amos(self, f1, f2, rtol=1e-11, atol=0, skip=None): + for v, z in self._cephes_vs_amos_points(): + if skip is not None and skip(v, z): + continue + c1, c2, c3 = f1(v, z), f1(v,z+0j), f2(int(v), z) + if np.isinf(c1): + assert_(np.abs(c2) >= 1e300, (v, z)) + elif np.isnan(c1): + assert_(c2.imag != 0, (v, z)) + else: + assert_allclose(c1, c2, err_msg=(v, z), rtol=rtol, atol=atol) + if v == int(v): + assert_allclose(c3, c2, err_msg=(v, z), + rtol=rtol, atol=atol) + + @pytest.mark.xfail(platform.machine() == 'ppc64le', + reason="fails on ppc64le") + def test_jv_cephes_vs_amos(self): + self.check_cephes_vs_amos(special.jv, special.jn, rtol=1e-10, atol=1e-305) + + @pytest.mark.xfail(platform.machine() == 'ppc64le', + reason="fails on ppc64le") + def test_yv_cephes_vs_amos(self): + self.check_cephes_vs_amos(special.yv, special.yn, rtol=1e-11, atol=1e-305) + + def test_yv_cephes_vs_amos_only_small_orders(self): + def skipper(v, z): + return abs(v) > 50 + self.check_cephes_vs_amos(special.yv, special.yn, rtol=1e-11, atol=1e-305, + skip=skipper) + + def test_iv_cephes_vs_amos(self): + with np.errstate(all='ignore'): + self.check_cephes_vs_amos(special.iv, special.iv, rtol=5e-9, atol=1e-305) + + @pytest.mark.slow + def test_iv_cephes_vs_amos_mass_test(self): + N = 1000000 + np.random.seed(1) + v = np.random.pareto(0.5, N) * (-1)**np.random.randint(2, size=N) + x = np.random.pareto(0.2, N) * (-1)**np.random.randint(2, size=N) + + imsk = (np.random.randint(8, size=N) == 0) + v[imsk] = v[imsk].astype(np.int64) + + with np.errstate(all='ignore'): + c1 = special.iv(v, x) + c2 = special.iv(v, x+0j) + + # deal with differences in the inf and zero cutoffs + c1[abs(c1) > 1e300] = np.inf + c2[abs(c2) > 1e300] = np.inf + c1[abs(c1) < 1e-300] = 0 + c2[abs(c2) < 1e-300] = 0 + + dc = abs(c1/c2 - 1) + dc[np.isnan(dc)] = 0 + + k = np.argmax(dc) + + # Most error apparently comes from AMOS and not our implementation; + # there are some problems near integer orders there + assert_( + dc[k] < 2e-7, + (v[k], x[k], special.iv(v[k], x[k]), special.iv(v[k], x[k]+0j)) + ) + + def test_kv_cephes_vs_amos(self): + self.check_cephes_vs_amos(special.kv, special.kn, rtol=1e-9, atol=1e-305) + self.check_cephes_vs_amos(special.kv, special.kv, rtol=1e-9, atol=1e-305) + + def test_ticket_623(self): + assert_allclose(special.jv(3, 4), 0.43017147387562193) + assert_allclose(special.jv(301, 1300), 0.0183487151115275) + assert_allclose(special.jv(301, 1296.0682), -0.0224174325312048) + + def test_ticket_853(self): + """Negative-order Bessels""" + # cephes + assert_allclose(special.jv(-1, 1), -0.4400505857449335) + assert_allclose(special.jv(-2, 1), 0.1149034849319005) + assert_allclose(special.yv(-1, 1), 0.7812128213002887) + assert_allclose(special.yv(-2, 1), -1.650682606816255) + assert_allclose(special.iv(-1, 1), 0.5651591039924851) + assert_allclose(special.iv(-2, 1), 0.1357476697670383) + assert_allclose(special.kv(-1, 1), 0.6019072301972347) + assert_allclose(special.kv(-2, 1), 1.624838898635178) + assert_allclose(special.jv(-0.5, 1), 0.43109886801837607952) + assert_allclose(special.yv(-0.5, 1), 0.6713967071418031) + assert_allclose(special.iv(-0.5, 1), 1.231200214592967) + assert_allclose(special.kv(-0.5, 1), 0.4610685044478945) + # amos + assert_allclose(special.jv(-1, 1+0j), -0.4400505857449335) + assert_allclose(special.jv(-2, 1+0j), 0.1149034849319005) + assert_allclose(special.yv(-1, 1+0j), 0.7812128213002887) + assert_allclose(special.yv(-2, 1+0j), -1.650682606816255) + + assert_allclose(special.iv(-1, 1+0j), 0.5651591039924851) + assert_allclose(special.iv(-2, 1+0j), 0.1357476697670383) + assert_allclose(special.kv(-1, 1+0j), 0.6019072301972347) + assert_allclose(special.kv(-2, 1+0j), 1.624838898635178) + + assert_allclose(special.jv(-0.5, 1+0j), 0.43109886801837607952) + assert_allclose(special.jv(-0.5, 1+1j), 0.2628946385649065-0.827050182040562j) + assert_allclose(special.yv(-0.5, 1+0j), 0.6713967071418031) + assert_allclose(special.yv(-0.5, 1+1j), 0.967901282890131+0.0602046062142816j) + + assert_allclose(special.iv(-0.5, 1+0j), 1.231200214592967) + assert_allclose(special.iv(-0.5, 1+1j), 0.77070737376928+0.39891821043561j) + assert_allclose(special.kv(-0.5, 1+0j), 0.4610685044478945) + assert_allclose(special.kv(-0.5, 1+1j), 0.06868578341999-0.38157825981268j) + + assert_allclose(special.jve(-0.5,1+0.3j), special.jv(-0.5, 1+0.3j)*exp(-0.3)) + assert_allclose(special.yve(-0.5,1+0.3j), special.yv(-0.5, 1+0.3j)*exp(-0.3)) + assert_allclose(special.ive(-0.5,0.3+1j), special.iv(-0.5, 0.3+1j)*exp(-0.3)) + assert_allclose(special.kve(-0.5,0.3+1j), special.kv(-0.5, 0.3+1j)*exp(0.3+1j)) + + assert_allclose( + special.hankel1(-0.5, 1+1j), + special.jv(-0.5, 1+1j) + 1j*special.yv(-0.5,1+1j) + ) + assert_allclose( + special.hankel2(-0.5, 1+1j), + special.jv(-0.5, 1+1j) - 1j*special.yv(-0.5,1+1j) + ) + + def test_ticket_854(self): + """Real-valued Bessel domains""" + assert_(isnan(special.jv(0.5, -1))) + assert_(isnan(special.iv(0.5, -1))) + assert_(isnan(special.yv(0.5, -1))) + assert_(isnan(special.yv(1, -1))) + assert_(isnan(special.kv(0.5, -1))) + assert_(isnan(special.kv(1, -1))) + assert_(isnan(special.jve(0.5, -1))) + assert_(isnan(special.ive(0.5, -1))) + assert_(isnan(special.yve(0.5, -1))) + assert_(isnan(special.yve(1, -1))) + assert_(isnan(special.kve(0.5, -1))) + assert_(isnan(special.kve(1, -1))) + assert_(isnan(special.airye(-1)[0:2]).all(), special.airye(-1)) + assert_(not isnan(special.airye(-1)[2:4]).any(), special.airye(-1)) + + def test_gh_7909(self): + assert_(special.kv(1.5, 0) == np.inf) + assert_(special.kve(1.5, 0) == np.inf) + + def test_ticket_503(self): + """Real-valued Bessel I overflow""" + assert_allclose(special.iv(1, 700), 1.528500390233901e302) + assert_allclose(special.iv(1000, 1120), 1.301564549405821e301) + + def test_iv_hyperg_poles(self): + assert_allclose(special.iv(-0.5, 1), 1.231200214592967) + + def iv_series(self, v, z, n=200): + k = arange(0, n).astype(double) + r = (v+2*k)*log(.5*z) - special.gammaln(k+1) - special.gammaln(v+k+1) + r[isnan(r)] = inf + r = exp(r) + err = abs(r).max() * finfo(double).eps * n + abs(r[-1])*10 + return r.sum(), err + + def test_i0_series(self): + for z in [1., 10., 200.5]: + value, err = self.iv_series(0, z) + assert_allclose(special.i0(z), value, atol=err, err_msg=z) + + def test_i1_series(self): + for z in [1., 10., 200.5]: + value, err = self.iv_series(1, z) + assert_allclose(special.i1(z), value, atol=err, err_msg=z) + + def test_iv_series(self): + for v in [-20., -10., -1., 0., 1., 12.49, 120.]: + for z in [1., 10., 200.5, -1+2j]: + value, err = self.iv_series(v, z) + assert_allclose(special.iv(v, z), value, atol=err, err_msg=(v, z)) + + def test_i0(self): + values = [[0.0, 1.0], + [1e-10, 1.0], + [0.1, 0.9071009258], + [0.5, 0.6450352706], + [1.0, 0.4657596077], + [2.5, 0.2700464416], + [5.0, 0.1835408126], + [20.0, 0.0897803119], + ] + for i, (x, v) in enumerate(values): + cv = special.i0(x) * exp(-x) + assert_allclose(cv, v, atol=1.5e-8, rtol=0, err_msg=f'test #{i}') + + def test_i0e(self): + oize = special.i0e(.1) + oizer = special.ive(0, .1) + assert_allclose(oize, oizer, atol=1.5e-8, rtol=0) + + def test_i1(self): + values = [[0.0, 0.0], + [1e-10, 0.4999999999500000e-10], + [0.1, 0.0452984468], + [0.5, 0.1564208032], + [1.0, 0.2079104154], + [5.0, 0.1639722669], + [20.0, 0.0875062222], + ] + for i, (x, v) in enumerate(values): + cv = special.i1(x) * exp(-x) + assert_allclose(cv, v, atol=1.5e-8, rtol=0, err_msg=f'test #{i}') + + def test_i1e(self): + oi1e = special.i1e(.1) + oi1er = special.ive(1, .1) + assert_allclose(oi1e, oi1er, atol=1.5e-8, rtol=0) + + def test_iti0k0(self): + iti0 = array(special.iti0k0(5)) + assert_allclose(iti0, array([31.848667776169801, 1.5673873907283657]), + atol=1.5e-5, rtol=0) + + def test_it2i0k0(self): + it2k = special.it2i0k0(.1) + assert_allclose(it2k, array([0.0012503906973464409, 3.3309450354686687]), + atol=1.5e-6, rtol=0) + + def test_iv(self): + iv1 = special.iv(0,.1)*exp(-.1) + assert_allclose(iv1, 0.90710092578230106, atol=1.5e-10, rtol=0) + + def test_negv_ive(self): + assert_equal(special.ive(3,2), special.ive(-3,2)) + + def test_ive(self): + ive1 = special.ive(0,.1) + iv1 = special.iv(0,.1)*exp(-.1) + assert_allclose(ive1, iv1, atol=1.5e-10, rtol=0) + + def test_ivp0(self): + assert_allclose(special.iv(1, 2), special.ivp(0, 2), atol=1.5e-10, rtol=0) + + def test_ivp(self): + y = (special.iv(0,2) + special.iv(2,2))/2 + x = special.ivp(1,2) + assert_allclose(x, y, atol=1.5e-10, rtol=0) + + @pytest.mark.parametrize("x, expected", + [(1e15, 6.156638646885021e-09), # 1/sqrt(eps) < x < 1/eps + (1e30, -5.589003016686147e-16) # x > 1/eps + ]) + def test_gh22705(self, x, expected): + # reference values computed with mpmath + # from mpmath import mp + # mp.dps = 1000 + # float(mp.besselj(0, mp.mpf('1e15'))) + assert_allclose(special.j0(x), expected, rtol=5e-15) + + +class TestLaguerre: + def test_laguerre(self): + lag0 = special.laguerre(0) + lag1 = special.laguerre(1) + lag2 = special.laguerre(2) + lag3 = special.laguerre(3) + lag4 = special.laguerre(4) + lag5 = special.laguerre(5) + assert_allclose(lag0.c, [1], atol=1.5e-13, rtol=0) + assert_allclose(lag1.c, [-1, 1], atol=1.5e-13, rtol=0) + assert_allclose(lag2.c, array([1, -4,2]) / 2.0, atol=1.5e-13, rtol=0) + assert_allclose(lag3.c, array([-1, 9,-18,6])/6.0, atol=1.5e-13, rtol=0) + assert_allclose(lag4.c, array([1, -16,72,-96,24])/24.0, + atol=1.5e-13, rtol=0) + assert_allclose(lag5.c, array([-1, 25, -200, 600, -600, 120]) / 120.0, + atol=1.5e-13, rtol=0) + + def test_genlaguerre(self): + k = 5*np.random.random() - 0.9 + lag0 = special.genlaguerre(0,k) + lag1 = special.genlaguerre(1,k) + lag2 = special.genlaguerre(2,k) + lag3 = special.genlaguerre(3,k) + assert_equal(lag0.c, [1]) + assert_equal(lag1.c, [-1, k + 1]) + assert_allclose(lag2.c, array([1, -2 * (k + 2), (k + 1.) * (k + 2.)]) / 2.0, + atol=1.5e-7, rtol=0) + expected = array([-1, + 3 * (k + 3), + -3 * (k + 2) * (k + 3), + (k + 1) * (k + 2) * (k + 3)]) / 6.0 + assert_allclose(lag3.c, expected, atol=1.5e-7, rtol=0) + + +class TestLambda: + def test_lmbda(self): + lam = special.lmbda(1,.1) + lamr = ( + array([special.jn(0,.1), 2*special.jn(1,.1)/.1]), + array([special.jvp(0,.1), -2*special.jv(1,.1)/.01 + 2*special.jvp(1,.1)/.1]) + ) + assert_allclose(lam, lamr, atol=1.5e-8, rtol=0) + + +class TestLog1p: + def test_log1p(self): + l1p = (special.log1p(10), special.log1p(11), special.log1p(12)) + l1prl = (log(11), log(12), log(13)) + assert_allclose(l1p, l1prl, atol=1.5e-8, rtol=0) + + def test_log1pmore(self): + l1pm = (special.log1p(1), special.log1p(1.1), special.log1p(1.2)) + l1pmrl = (log(2),log(2.1),log(2.2)) + assert_allclose(l1pm, l1pmrl, atol=1.5e-8, rtol=0) + + +def ce_fourier_coefficient_using_integral(k, n, q): + """ + Compute the Fourier coefficient of the even Mathieu function. + The integral definition of a Fourier coefficient is used. + This function is used as an alternative implementation of + mathieu_even_coef(). + """ + period = 180 if n % 2 == 0 else 360 + # For k = 0, the factor outside the integral is (1/period). + # For k = 1, 2, 3, ..., the factor is (2/period). + c = (1/period)*quad(lambda t: special.mathieu_cem(n, q, t)[0], + -period/2, period/2, + weight='cos', wvar=2*np.pi*k/period, epsrel=1e-14)[0] + if k > 0: + c *= 2 + return c + + +def se_fourier_coefficient_using_integral(k, n, q): + """ + Compute the Fourier coefficient of the odd Mathieu function. + The integral definition of a Fourier coefficient is used. + This function is used as an alternative implementation of + mathieu_odd_coef(). + """ + # For k == 0, the result is 0. (The test code won't call this + # function with k == 0, but we'll check anyway.) + if k == 0: + return 0.0 + period = 180 if n % 2 == 0 else 360 + c = (2/period)*quad(lambda t: special.mathieu_sem(n, q, t)[0], + -period/2, period/2, + weight='sin', wvar=2*np.pi*k/period, epsrel=1e-14)[0] + return c + + +class TestMathieu: + + @pytest.mark.parametrize('n, q', [(4, 3.5), (8, 4.25)]) + def test_mathieu_even_coef_against_integral_n_even(self, n, q): + # Get the nonzero Fourier coefficients. For the even Mathieu functions + # with even n, these are the coefficients of the cosine series. None of + # the coefficients are 0 for k = 0, 1, 2, 3, ... + A = special.mathieu_even_coef(n, q) + # Compare the first four nonzero Fourier coefficients to the coefficients + # computed using the integral definition. + c = [ce_fourier_coefficient_using_integral(k, n, q) for k in range(4)] + assert_allclose(c, A[:len(c)], rtol=1e-10) + + @pytest.mark.parametrize('n, q', [(3, 3.5), (7, 2)]) + def test_mathieu_even_coef_against_integral_n_odd(self, n, q): + # Get the nonzero Fourier coefficients. For the even Mathieu functions + # with odd n, these are the coefficients of the cosine series. Only the + # coefficients c[k] for k = 1, 3, 5, 7, ... are nonzero. These are the + # values returned by mathieu_even_coef(n, q). + A = special.mathieu_even_coef(n, q) + # Compare the first 4 nonzero Fourier coefficients to the coefficients + # computed using the integral definition. + c = [ce_fourier_coefficient_using_integral(k, n, q) for k in range(1, 9, 2)] + assert_allclose(c, A[:len(c)], rtol=1e-10) + + @pytest.mark.parametrize('n, q', [(2, 3.5), (10, 2)]) + def test_mathieu_odd_coef_against_integral_n_even(self, n, q): + # Get the nonzero Fourier coefficients. For the odd Mathieu functions + # with even n, these are the coefficients of the sine series. Only the + # coefficients c[k] for k = 1, 2, 3, 4, ... are nonzero. These are the + # values returned by mathieu_odd_coef(n, q). + B = special.mathieu_odd_coef(n, q) + # Compare the first 4 nonzero Fourier coefficients to the coefficients + # computed using the integral definition. + c = [se_fourier_coefficient_using_integral(k, n, q) for k in range(1, 5)] + assert_allclose(c, B[:len(c)], rtol=1e-10) + + @pytest.mark.parametrize('n, q', [(3, 3.5), (7, 2)]) + def test_mathieu_odd_coef_against_integral_n_odd(self, n, q): + # Get the nonzero Fourier coefficients. For the odd Mathieu functions + # with odd n, these are the coefficients of the sine series. Only the + # coefficients c[k] for k = 1, 3, 5, 7, ... are nonzero. These are the + # values returned by mathieu_odd_coef(n, q). + B = special.mathieu_odd_coef(n, q) + # Compare the first 4 nonzero Fourier coefficients to the coefficients + # computed using the integral definition. + c = [se_fourier_coefficient_using_integral(k, n, q) for k in range(1, 9, 2)] + assert_allclose(c, B[:len(c)], rtol=1e-10) + + +class TestFresnelIntegral: + + def test_modfresnelp(self): + pass + + def test_modfresnelm(self): + pass + + +class TestOblCvSeq: + def test_obl_cv_seq(self): + obl = special.obl_cv_seq(0,3,1) + assert_allclose(obl, array([-0.348602, + 1.393206, + 5.486800, + 11.492120]), + atol=1.5e-5, rtol=0) + + +class TestParabolicCylinder: + def test_pbdn_seq(self): + pb = special.pbdn_seq(1, .1) + assert_allclose(pb, (array([0.9975, + 0.0998]), + array([-0.0499, + 0.9925])), + atol=1.5e-4, rtol=0) + + def test_pbdv(self): + special.pbdv(1,.2) + 1/2*(.2)*special.pbdv(1,.2)[0] - special.pbdv(0,.2)[0] + + def test_pbdv_seq(self): + pbn = special.pbdn_seq(1,.1) + pbv = special.pbdv_seq(1,.1) + assert_allclose(pbv, (real(pbn[0]), real(pbn[1])), atol=1.5e-4, rtol=0) + + def test_pbdv_points(self): + # simple case + eta = np.linspace(-10, 10, 5) + z = 2**(eta/2)*np.sqrt(np.pi)*special.rgamma(.5-.5*eta) + assert_allclose(special.pbdv(eta, 0.)[0], z, rtol=1e-14, atol=1e-14) + + # some points + assert_allclose(special.pbdv(10.34, 20.44)[0], 1.3731383034455e-32, rtol=1e-12) + assert_allclose(special.pbdv(-9.53, 3.44)[0], 3.166735001119246e-8, rtol=1e-12) + + def test_pbdv_gradient(self): + x = np.linspace(-4, 4, 8)[:,None] + eta = np.linspace(-10, 10, 5)[None,:] + + p = special.pbdv(eta, x) + eps = 1e-7 + 1e-7*abs(x) + dp = (special.pbdv(eta, x + eps)[0] - special.pbdv(eta, x - eps)[0]) / eps / 2. + assert_allclose(p[1], dp, rtol=1e-6, atol=1e-6) + + def test_pbvv_gradient(self): + x = np.linspace(-4, 4, 8)[:,None] + eta = np.linspace(-10, 10, 5)[None,:] + + p = special.pbvv(eta, x) + eps = 1e-7 + 1e-7*abs(x) + dp = (special.pbvv(eta, x + eps)[0] - special.pbvv(eta, x - eps)[0]) / eps / 2. + assert_allclose(p[1], dp, rtol=1e-6, atol=1e-6) + + def test_pbvv_seq(self): + res1, res2 = special.pbvv_seq(2, 3) + assert_allclose(res1, np.array([2.976319645712036, + 1.358840996329579, + 0.5501016716383508])) + assert_allclose(res2, np.array([3.105638472238475, + 0.9380581512176672, + 0.533688488872053])) + + +class TestPolygamma: + # from Table 6.2 (pg. 271) of A&S + def test_polygamma(self): + poly2 = special.polygamma(2, 1) + poly3 = special.polygamma(3, 1) + assert_allclose(poly2, -2.4041138063, atol=1.5e-10, rtol=0) + assert_allclose(poly3, 6.4939394023, atol=1.5e-10, rtol=0) + + # Test polygamma(0, x) == psi(x) + x = [2, 3, 1.1e14] + assert_allclose(special.polygamma(0, x), special.psi(x), + atol=1.5e-7, rtol=0) + + # Test broadcasting + n = [0, 1, 2] + x = [0.5, 1.5, 2.5] + expected = [-1.9635100260214238, 0.93480220054467933, + -0.23620405164172739] + assert_allclose(special.polygamma(n, x), expected, atol=1.5e-7, rtol=0) + expected = np.vstack([expected]*2) + assert_allclose(special.polygamma(n, np.vstack([x]*2)), expected, + atol=1.5e-7, rtol=0) + assert_allclose(special.polygamma(np.vstack([n]*2), x), expected, + atol=1.5e-7, rtol=0) + + +class TestProCvSeq: + def test_pro_cv_seq(self): + prol = special.pro_cv_seq(0, 3, 1) + assert_allclose(prol, array([0.319000, + 2.593084, + 6.533471, + 12.514462]), + atol=1.5e-5, rtol=0) + + +class TestPsi: + def test_psi(self): + ps = special.psi(1) + assert_allclose(ps, -0.57721566490153287, atol=1.5e-8, rtol=0) + + +class TestRadian: + def test_radian(self): + rad = special.radian(90, 0, 0) + assert_allclose(rad, pi/2.0, atol=1.5e-5, rtol=0) + + def test_radianmore(self): + rad1 = special.radian(90, 1, 60) + assert_allclose(rad1, pi/2 + 0.0005816135199345904, atol=1.5e-5, rtol=0) + + +class TestRiccati: + def test_riccati_jn(self): + N, x = 2, 0.2 + S = np.empty((N, N)) + for n in range(N): + j = special.spherical_jn(n, x) + jp = special.spherical_jn(n, x, derivative=True) + S[0,n] = x*j + S[1,n] = x*jp + j + assert_allclose(S, special.riccati_jn(n, x), atol=1.5e-8, rtol=0) + + def test_riccati_yn(self): + N, x = 2, 0.2 + C = np.empty((N, N)) + for n in range(N): + y = special.spherical_yn(n, x) + yp = special.spherical_yn(n, x, derivative=True) + C[0,n] = x*y + C[1,n] = x*yp + y + assert_allclose(C, special.riccati_yn(n, x), atol=1.5e-8, rtol=0) + + +class TestSoftplus: + def test_softplus(self): + # Test cases for the softplus function. Selected based on Eq.(10) of: + # Mächler, M. (2012). log1mexp-note.pdf. Rmpfr: R MPFR - Multiple Precision + # Floating-Point Reliable. Retrieved from: + # https://cran.r-project.org/web/packages/Rmpfr/vignettes/log1mexp-note.pdf + # Reference values computed with `mpmath` + import numpy as np + rng = np.random.default_rng(3298432985245) + n = 3 + a1 = rng.uniform(-100, -37, size=n) + a2 = rng.uniform(-37, 18, size=n) + a3 = rng.uniform(18, 33.3, size=n) + a4 = rng.uniform(33.33, 100, size=n) + a = np.stack([a1, a2, a3, a4]) + + # from mpmath import mp + # mp.dps = 100 + # @np.vectorize + # def softplus(x): + # return float(mp.log(mp.one + mp.exp(x))) + # softplus(a).tolist() + ref = [[1.692721323272333e-42, 7.42673911145206e-41, 8.504608846033205e-35], + [1.8425343736349797, 9.488245799395577e-15, 7.225195764021444e-08], + [31.253760266045106, 27.758244090327832, 29.995959179643634], + [73.26040086468937, 76.24944728617226, 37.83955519155184]] + + res = softplus(a) + assert_allclose(res, ref, rtol=2e-15) + + def test_softplus_with_kwargs(self): + x = np.arange(5) - 2 + out = np.ones(5) + ref = out.copy() + where = x > 0 + + softplus(x, out=out, where=where) + ref[where] = softplus(x[where]) + assert_allclose(out, ref) + + +class TestRound: + def test_round(self): + rnd = list(map(int, (special.round(10.1), + special.round(10.4), + special.round(10.5), + special.round(10.6)))) + + # Note: According to the documentation, scipy.special.round is + # supposed to round to the nearest even number if the fractional + # part is exactly 0.5. On some platforms, this does not appear + # to work and thus this test may fail. However, this unit test is + # correctly written. + rndrl = (10,10,10,11) + assert_array_equal(rnd,rndrl) + +class TestStruve: + def _series(self, v, z, n=100): + """Compute Struve function & error estimate from its power series.""" + k = arange(0, n) + r = (-1)**k * (.5*z)**(2*k+v+1)/special.gamma(k+1.5)/special.gamma(k+v+1.5) + err = abs(r).max() * finfo(double).eps * n + return r.sum(), err + + def test_vs_series(self): + """Check Struve function versus its power series""" + for v in [-20, -10, -7.99, -3.4, -1, 0, 1, 3.4, 12.49, 16]: + for z in [1, 10, 19, 21, 30]: + value, err = self._series(v, z) + assert_allclose(special.struve(v, z), value, rtol=0, atol=err), (v, z) + + def test_some_values(self): + assert_allclose(special.struve(-7.99, 21), 0.0467547614113, rtol=1e-7) + assert_allclose(special.struve(-8.01, 21), 0.0398716951023, rtol=1e-8) + assert_allclose(special.struve(-3.0, 200), 0.0142134427432, rtol=1e-12) + assert_allclose(special.struve(-8.0, -41), 0.0192469727846, rtol=1e-11) + assert_equal(special.struve(-12, -41), -special.struve(-12, 41)) + assert_equal(special.struve(+12, -41), -special.struve(+12, 41)) + assert_equal(special.struve(-11, -41), +special.struve(-11, 41)) + assert_equal(special.struve(+11, -41), +special.struve(+11, 41)) + + assert_(isnan(special.struve(-7.1, -1))) + assert_(isnan(special.struve(-10.1, -1))) + + def test_regression_679(self): + """Regression test for #679""" + assert_allclose(special.struve(-1.0, 20 - 1e-8), + special.struve(-1.0, 20 + 1e-8)) + assert_allclose(special.struve(-2.0, 20 - 1e-8), + special.struve(-2.0, 20 + 1e-8)) + assert_allclose(special.struve(-4.3, 20 - 1e-8), + special.struve(-4.3, 20 + 1e-8)) + + +def test_chi2_smalldf(): + assert_allclose(special.chdtr(0.6, 3), 0.957890536704110, atol=1.5e-7, rtol=0) + + +def test_ch2_inf(): + assert_equal(special.chdtr(0.7,np.inf), 1.0) + + +@pytest.mark.parametrize("x", [-np.inf, -1.0, -0.0, 0.0, np.inf, np.nan]) +def test_chi2_v_nan(x): + assert np.isnan(special.chdtr(np.nan, x)) + + +@pytest.mark.parametrize("v", [-np.inf, -1.0, -0.0, 0.0, np.inf, np.nan]) +def test_chi2_x_nan(v): + assert np.isnan(special.chdtr(v, np.nan)) + + +@pytest.mark.parametrize("x", [-np.inf, -1.0, -0.0, 0.0, np.inf, np.nan]) +def test_chi2c_v_nan(x): + assert np.isnan(special.chdtrc(np.nan, x)) + + +@pytest.mark.parametrize("v", [-np.inf, -1.0, -0.0, 0.0, np.inf, np.nan]) +def test_chi2c_x_nan(v): + assert np.isnan(special.chdtrc(v, np.nan)) + + +def test_chi2_edgecases_gh20972(): + # Tests that a variety of edgecases for chi square distribution functions + # correctly return NaN when and only when they are supposed to, when + # computed through different related ufuncs. See gh-20972. + v = np.asarray([-0.01, 0, 0.01, 1, np.inf])[:, np.newaxis] + x = np.asarray([-np.inf, -0.01, 0, 0.01, np.inf]) + + # Check that `gammainc` is NaN when it should be and finite otherwise + ref = special.gammainc(v / 2, x / 2) + mask = (x < 0) | (v < 0) | (x == 0) & (v == 0) | np.isinf(v) & np.isinf(x) + assert np.all(np.isnan(ref[mask])) + assert np.all(np.isfinite(ref[~mask])) + + # Use `gammainc` as a reference for the rest + assert_allclose(special.chdtr(v, x), ref) + assert_allclose(special.gdtr(1, v / 2, x / 2), ref) + assert_allclose(1 - special.gammaincc(v / 2, x / 2), ref) + assert_allclose(1 - special.chdtrc(v, x), ref) + assert_allclose(1 - special.gdtrc(1, v / 2, x / 2), ref) + + +def test_chi2c_smalldf(): + assert_allclose(special.chdtrc(0.6, 3), 1 - 0.957890536704110, + atol=1.5e-7, rtol=0) + + +def test_chi2_inv_smalldf(): + assert_allclose(special.chdtri(0.6, 1 - 0.957890536704110), 3, + atol=1.5e-7, rtol=0) + + +def test_agm_simple(): + rtol = 1e-13 + + # Gauss's constant + assert_allclose(1/special.agm(1, np.sqrt(2)), 0.834626841674073186, + rtol=rtol) + + # These values were computed using Wolfram Alpha, with the + # function ArithmeticGeometricMean[a, b]. + agm13 = 1.863616783244897 + agm15 = 2.604008190530940 + agm35 = 3.936235503649555 + assert_allclose(special.agm([[1], [3]], [1, 3, 5]), + [[1, agm13, agm15], + [agm13, 3, agm35]], rtol=rtol) + + # Computed by the iteration formula using mpmath, + # with mpmath.mp.prec = 1000: + agm12 = 1.4567910310469068 + assert_allclose(special.agm(1, 2), agm12, rtol=rtol) + assert_allclose(special.agm(2, 1), agm12, rtol=rtol) + assert_allclose(special.agm(-1, -2), -agm12, rtol=rtol) + assert_allclose(special.agm(24, 6), 13.458171481725614, rtol=rtol) + assert_allclose(special.agm(13, 123456789.5), 11111458.498599306, + rtol=rtol) + assert_allclose(special.agm(1e30, 1), 2.229223055945383e+28, rtol=rtol) + assert_allclose(special.agm(1e-22, 1), 0.030182566420169886, rtol=rtol) + assert_allclose(special.agm(1e150, 1e180), 2.229223055945383e+178, + rtol=rtol) + assert_allclose(special.agm(1e180, 1e-150), 2.0634722510162677e+177, + rtol=rtol) + assert_allclose(special.agm(1e-150, 1e-170), 3.3112619670463756e-152, + rtol=rtol) + fi = np.finfo(1.0) + assert_allclose(special.agm(fi.tiny, fi.max), 1.9892072050015473e+305, + rtol=rtol) + assert_allclose(special.agm(0.75*fi.max, fi.max), 1.564904312298045e+308, + rtol=rtol) + assert_allclose(special.agm(fi.tiny, 3*fi.tiny), 4.1466849866735005e-308, + rtol=rtol) + + # zero, nan and inf cases. + assert_equal(special.agm(0, 0), 0) + assert_equal(special.agm(99, 0), 0) + + assert_equal(special.agm(-1, 10), np.nan) + assert_equal(special.agm(0, np.inf), np.nan) + assert_equal(special.agm(np.inf, 0), np.nan) + assert_equal(special.agm(0, -np.inf), np.nan) + assert_equal(special.agm(-np.inf, 0), np.nan) + assert_equal(special.agm(np.inf, -np.inf), np.nan) + assert_equal(special.agm(-np.inf, np.inf), np.nan) + assert_equal(special.agm(1, np.nan), np.nan) + assert_equal(special.agm(np.nan, -1), np.nan) + + assert_equal(special.agm(1, np.inf), np.inf) + assert_equal(special.agm(np.inf, 1), np.inf) + assert_equal(special.agm(-1, -np.inf), -np.inf) + assert_equal(special.agm(-np.inf, -1), -np.inf) + + +def test_legacy(): + # Legacy behavior: truncating arguments to integers + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", "floating point number truncated to an integer", RuntimeWarning) + assert_equal(special.expn(1, 0.3), special.expn(1.8, 0.3)) + assert_equal(special.nbdtrc(1, 2, 0.3), special.nbdtrc(1.8, 2.8, 0.3)) + assert_equal(special.nbdtr(1, 2, 0.3), special.nbdtr(1.8, 2.8, 0.3)) + assert_equal(special.nbdtri(1, 2, 0.3), special.nbdtri(1.8, 2.8, 0.3)) + assert_equal(special.pdtri(1, 0.3), special.pdtri(1.8, 0.3)) + assert_equal(special.kn(1, 0.3), special.kn(1.8, 0.3)) + assert_equal(special.yn(1, 0.3), special.yn(1.8, 0.3)) + assert_equal(special.smirnov(1, 0.3), special.smirnov(1.8, 0.3)) + assert_equal(special.smirnovi(1, 0.3), special.smirnovi(1.8, 0.3)) + + +# This lock can be removed once errstate is made thread-safe (see gh-21956) +@pytest.fixture +def errstate_lock(): + import threading + return threading.Lock() + + +@with_special_errors +def test_error_raising(errstate_lock): + with errstate_lock: + with special.errstate(all='raise'): + assert_raises(special.SpecialFunctionError, special.iv, 1, 1e99j) + + +def test_xlogy(): + def xfunc(x, y): + with np.errstate(invalid='ignore'): + if x == 0 and not np.isnan(y): + return x + else: + return x*np.log(y) + + z1 = np.asarray([(0,0), (0, np.nan), (0, np.inf), (1.0, 2.0)], dtype=float) + z2 = np.r_[z1, [(0, 1j), (1, 1j)]] + + w1 = np.vectorize(xfunc)(z1[:,0], z1[:,1]) + assert_func_equal(special.xlogy, w1, z1, rtol=1e-13, atol=1e-13) + w2 = np.vectorize(xfunc)(z2[:,0], z2[:,1]) + assert_func_equal(special.xlogy, w2, z2, rtol=1e-13, atol=1e-13) + + +def test_xlog1py(): + def xfunc(x, y): + with np.errstate(invalid='ignore'): + if x == 0 and not np.isnan(y): + return x + else: + return x * np.log1p(y) + + z1 = np.asarray([(0,0), (0, np.nan), (0, np.inf), (1.0, 2.0), + (1, 1e-30)], dtype=float) + w1 = np.vectorize(xfunc)(z1[:,0], z1[:,1]) + assert_func_equal(special.xlog1py, w1, z1, rtol=1e-13, atol=1e-13) + + +def test_entr(): + def xfunc(x): + if x < 0: + return -np.inf + else: + return -special.xlogy(x, x) + values = (0, 0.5, 1.0, np.inf) + signs = [-1, 1] + arr = [] + for sgn, v in itertools.product(signs, values): + arr.append(sgn * v) + z = np.array(arr, dtype=float) + w = np.vectorize(xfunc, otypes=[np.float64])(z) + assert_func_equal(special.entr, w, z, rtol=1e-13, atol=1e-13) + + +def test_kl_div(): + def xfunc(x, y): + if x < 0 or y < 0 or (y == 0 and x != 0): + # extension of natural domain to preserve convexity + return np.inf + elif np.isposinf(x) or np.isposinf(y): + # limits within the natural domain + return np.inf + elif x == 0: + return y + else: + return special.xlogy(x, x/y) - x + y + values = (0, 0.5, 1.0) + signs = [-1, 1] + arr = [] + for sgna, va, sgnb, vb in itertools.product(signs, values, signs, values): + arr.append((sgna*va, sgnb*vb)) + z = np.array(arr, dtype=float) + w = np.vectorize(xfunc, otypes=[np.float64])(z[:,0], z[:,1]) + assert_func_equal(special.kl_div, w, z, rtol=1e-13, atol=1e-13) + + +def test_rel_entr(): + def xfunc(x, y): + if x > 0 and y > 0: + return special.xlogy(x, x/y) + elif x == 0 and y >= 0: + return 0 + else: + return np.inf + values = (0, 0.5, 1.0) + signs = [-1, 1] + arr = [] + for sgna, va, sgnb, vb in itertools.product(signs, values, signs, values): + arr.append((sgna*va, sgnb*vb)) + z = np.array(arr, dtype=float) + w = np.vectorize(xfunc, otypes=[np.float64])(z[:,0], z[:,1]) + assert_func_equal(special.rel_entr, w, z, rtol=1e-13, atol=1e-13) + + +def test_rel_entr_gh_20710_near_zero(): + # Check accuracy of inputs which are very close + inputs = np.array([ + # x, y + (0.9456657713430001, 0.9456657713430094), + (0.48066098564791515, 0.48066098564794774), + (0.786048657854401, 0.7860486578542367), + ]) + # Known values produced using `x * mpmath.log(x / y)` with dps=30 + expected = [ + -9.325873406851269e-15, + -3.258504577274724e-14, + 1.6431300764454033e-13, + ] + x = inputs[:, 0] + y = inputs[:, 1] + assert_allclose(special.rel_entr(x, y), expected, rtol=1e-13, atol=0) + + +def test_rel_entr_gh_20710_overflow(): + special.seterr(all='ignore') + inputs = np.array([ + # x, y + # Overflow + (4, 2.22e-308), + # Underflow + (1e-200, 1e+200), + # Subnormal + (2.22e-308, 1e15), + ]) + # Known values produced using `x * mpmath.log(x / y)` with dps=30 + expected = [ + 2839.139983229607, + -9.210340371976183e-198, + -1.6493212008074475e-305, + ] + x = inputs[:, 0] + y = inputs[:, 1] + assert_allclose(special.rel_entr(x, y), expected, rtol=1e-13, atol=0) + + +def test_huber(): + assert_equal(special.huber(-1, 1.5), np.inf) + assert_allclose(special.huber(2, 1.5), 0.5 * np.square(1.5)) + assert_allclose(special.huber(2, 2.5), 2 * (2.5 - 0.5 * 2)) + + def xfunc(delta, r): + if delta < 0: + return np.inf + elif np.abs(r) < delta: + return 0.5 * np.square(r) + else: + return delta * (np.abs(r) - 0.5 * delta) + + z = np.random.randn(10, 2) + w = np.vectorize(xfunc, otypes=[np.float64])(z[:,0], z[:,1]) + assert_func_equal(special.huber, w, z, rtol=1e-13, atol=1e-13) + + +def test_pseudo_huber(): + def xfunc(delta, r): + if delta < 0: + return np.inf + elif (not delta) or (not r): + return 0 + else: + return delta**2 * (np.sqrt(1 + (r/delta)**2) - 1) + + z = np.array(np.random.randn(10, 2).tolist() + [[0, 0.5], [0.5, 0]]) + w = np.vectorize(xfunc, otypes=[np.float64])(z[:,0], z[:,1]) + assert_func_equal(special.pseudo_huber, w, z, rtol=1e-13, atol=1e-13) + + +def test_pseudo_huber_small_r(): + delta = 1.0 + r = 1e-18 + y = special.pseudo_huber(delta, r) + # expected computed with mpmath: + # import mpmath + # mpmath.mp.dps = 200 + # r = mpmath.mpf(1e-18) + # expected = float(mpmath.sqrt(1 + r**2) - 1) + expected = 5.0000000000000005e-37 + assert_allclose(y, expected, rtol=1e-13) + + +def test_runtime_warning(): + with pytest.warns(RuntimeWarning, + match=r'Too many predicted coefficients'): + mathieu_odd_coef(1000, 1000) + with pytest.warns(RuntimeWarning, + match=r'Too many predicted coefficients'): + mathieu_even_coef(1000, 1000) + + +class TestStirling2: + table = [ + [1], + [0, 1], + [0, 1, 1], + [0, 1, 3, 1], + [0, 1, 7, 6, 1], + [0, 1, 15, 25, 10, 1], + [0, 1, 31, 90, 65, 15, 1], + [0, 1, 63, 301, 350, 140, 21, 1], + [0, 1, 127, 966, 1701, 1050, 266, 28, 1], + [0, 1, 255, 3025, 7770, 6951, 2646, 462, 36, 1], + [0, 1, 511, 9330, 34105, 42525, 22827, 5880, 750, 45, 1], + ] + + @pytest.mark.parametrize("is_exact, comp, kwargs", [ + (True, assert_equal, {}), + (False, assert_allclose, {'rtol': 1e-12}) + ]) + def test_table_cases(self, is_exact, comp, kwargs): + for n in range(1, len(self.table)): + k_values = list(range(n+1)) + row = self.table[n] + comp(row, stirling2([n], k_values, exact=is_exact), **kwargs) + + @pytest.mark.parametrize("is_exact, comp, kwargs", [ + (True, assert_equal, {}), + (False, assert_allclose, {'rtol': 1e-12}) + ]) + def test_valid_single_integer(self, is_exact, comp, kwargs): + comp(stirling2(0, 0, exact=is_exact), self.table[0][0], **kwargs) + comp(stirling2(4, 2, exact=is_exact), self.table[4][2], **kwargs) + # a single 2-tuple of integers as arguments must return an int and not + # an array whereas arrays of single values should return array + comp(stirling2(5, 3, exact=is_exact), 25, **kwargs) + comp(stirling2([5], [3], exact=is_exact), [25], **kwargs) + + @pytest.mark.parametrize("is_exact, comp, kwargs", [ + (True, assert_equal, {}), + (False, assert_allclose, {'rtol': 1e-12}) + ]) + def test_negative_integer(self, is_exact, comp, kwargs): + # negative integers for n or k arguments return 0 + comp(stirling2(-1, -1, exact=is_exact), 0, **kwargs) + comp(stirling2(-1, 2, exact=is_exact), 0, **kwargs) + comp(stirling2(2, -1, exact=is_exact), 0, **kwargs) + + @pytest.mark.parametrize("is_exact, comp, kwargs", [ + (True, assert_equal, {}), + (False, assert_allclose, {'rtol': 1e-12}) + ]) + def test_array_inputs(self, is_exact, comp, kwargs): + ans = [self.table[10][3], self.table[10][4]] + comp(stirling2(asarray([10, 10]), + asarray([3, 4]), + exact=is_exact), + ans) + comp(stirling2([10, 10], + asarray([3, 4]), + exact=is_exact), + ans) + comp(stirling2(asarray([10, 10]), + [3, 4], + exact=is_exact), + ans) + + @pytest.mark.parametrize("is_exact, comp, kwargs", [ + (True, assert_equal, {}), + (False, assert_allclose, {'rtol': 1e-13}) + ]) + def test_mixed_values(self, is_exact, comp, kwargs): + # negative values-of either n or k-should return 0 for the entry + ans = [0, 1, 3, 25, 1050, 5880, 9330] + n = [-1, 0, 3, 5, 8, 10, 10] + k = [-2, 0, 2, 3, 5, 7, 3] + comp(stirling2(n, k, exact=is_exact), ans, **kwargs) + + def test_correct_parity(self): + """Test parity follows well known identity. + + en.wikipedia.org/wiki/Stirling_numbers_of_the_second_kind#Parity + """ + n, K = 100, np.arange(101) + assert_equal( + stirling2(n, K, exact=True) % 2, + [math.comb(n - (k // 2) - 1, n - k) % 2 for k in K], + ) + + def test_big_numbers(self): + # via mpmath (bigger than 32bit) + ans = asarray([48063331393110, 48004081105038305]) + n = [25, 30] + k = [17, 4] + assert array_equal(stirling2(n, k, exact=True), ans) + # bigger than 64 bit + ans = asarray([2801934359500572414253157841233849412, + 14245032222277144547280648984426251]) + n = [42, 43] + k = [17, 23] + assert array_equal(stirling2(n, k, exact=True), ans) + + @pytest.mark.parametrize("N", [4.5, 3., 4+1j, "12", np.nan]) + @pytest.mark.parametrize("K", [3.5, 3, "2", None]) + @pytest.mark.parametrize("is_exact", [True, False]) + def test_unsupported_input_types(self, N, K, is_exact): + # object, float, string, complex are not supported and raise TypeError + with pytest.raises(TypeError): + stirling2(N, K, exact=is_exact) + + @pytest.mark.parametrize("is_exact", [True, False]) + def test_numpy_array_int_object_dtype(self, is_exact): + # python integers with arbitrary precision are *not* allowed as + # object type in numpy arrays are inconsistent from api perspective + ans = asarray(self.table[4][1:]) + n = asarray([4, 4, 4, 4], dtype=object) + k = asarray([1, 2, 3, 4], dtype=object) + with pytest.raises(TypeError): + array_equal(stirling2(n, k, exact=is_exact), ans) + + @pytest.mark.parametrize("is_exact, comp, kwargs", [ + (True, assert_equal, {}), + (False, assert_allclose, {'rtol': 1e-13}) + ]) + def test_numpy_array_unsigned_int_dtype(self, is_exact, comp, kwargs): + # numpy unsigned integers are allowed as dtype in numpy arrays + ans = asarray(self.table[4][1:]) + n = asarray([4, 4, 4, 4], dtype=np.ulong) + k = asarray([1, 2, 3, 4], dtype=np.ulong) + comp(stirling2(n, k, exact=False), ans, **kwargs) + + @pytest.mark.parametrize("is_exact, comp, kwargs", [ + (True, assert_equal, {}), + (False, assert_allclose, {'rtol': 1e-13}) + ]) + def test_broadcasting_arrays_correctly(self, is_exact, comp, kwargs): + # broadcasting is handled by stirling2 + # test leading 1s are replicated + ans = asarray([[1, 15, 25, 10], [1, 7, 6, 1]]) # shape (2,4) + n = asarray([[5, 5, 5, 5], [4, 4, 4, 4]]) # shape (2,4) + k = asarray([1, 2, 3, 4]) # shape (4,) + comp(stirling2(n, k, exact=is_exact), ans, **kwargs) + # test that dims both mismatch broadcast correctly (5,1) & (6,) + n = asarray([[4], [4], [4], [4], [4]]) + k = asarray([0, 1, 2, 3, 4, 5]) + ans = asarray([[0, 1, 7, 6, 1, 0] for _ in range(5)]) + comp(stirling2(n, k, exact=False), ans, **kwargs) + + def test_temme_rel_max_error(self): + # python integers with arbitrary precision are *not* allowed as + # object type in numpy arrays are inconsistent from api perspective + x = list(range(51, 101, 5)) + for n in x: + k_entries = list(range(1, n+1)) + denom = stirling2([n], k_entries, exact=True) + num = denom - stirling2([n], k_entries, exact=False) + assert np.max(np.abs(num / denom)) < 2e-5 diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_bdtr.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_bdtr.py new file mode 100644 index 0000000000000000000000000000000000000000..4d59c1e8c594166a99b213a217b7851c292d9d64 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_bdtr.py @@ -0,0 +1,114 @@ +import warnings + +import numpy as np +import scipy.special as sc +import pytest +from numpy.testing import assert_allclose, assert_array_equal + + +class TestBdtr: + def test(self): + val = sc.bdtr(0, 1, 0.5) + assert_allclose(val, 0.5) + + def test_sum_is_one(self): + val = sc.bdtr([0, 1, 2], 2, 0.5) + assert_array_equal(val, [0.25, 0.75, 1.0]) + + def test_rounding(self): + double_val = sc.bdtr([0.1, 1.1, 2.1], 2, 0.5) + int_val = sc.bdtr([0, 1, 2], 2, 0.5) + assert_array_equal(double_val, int_val) + + @pytest.mark.parametrize('k, n, p', [ + (np.inf, 2, 0.5), + (1.0, np.inf, 0.5), + (1.0, 2, np.inf) + ]) + def test_inf(self, k, n, p): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + val = sc.bdtr(k, n, p) + assert np.isnan(val) + + def test_domain(self): + val = sc.bdtr(-1.1, 1, 0.5) + assert np.isnan(val) + + +class TestBdtrc: + def test_value(self): + val = sc.bdtrc(0, 1, 0.5) + assert_allclose(val, 0.5) + + def test_sum_is_one(self): + val = sc.bdtrc([0, 1, 2], 2, 0.5) + assert_array_equal(val, [0.75, 0.25, 0.0]) + + def test_rounding(self): + double_val = sc.bdtrc([0.1, 1.1, 2.1], 2, 0.5) + int_val = sc.bdtrc([0, 1, 2], 2, 0.5) + assert_array_equal(double_val, int_val) + + @pytest.mark.parametrize('k, n, p', [ + (np.inf, 2, 0.5), + (1.0, np.inf, 0.5), + (1.0, 2, np.inf) + ]) + def test_inf(self, k, n, p): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + val = sc.bdtrc(k, n, p) + assert np.isnan(val) + + def test_domain(self): + val = sc.bdtrc(-1.1, 1, 0.5) + val2 = sc.bdtrc(2.1, 1, 0.5) + assert np.isnan(val2) + assert_allclose(val, 1.0) + + def test_bdtr_bdtrc_sum_to_one(self): + bdtr_vals = sc.bdtr([0, 1, 2], 2, 0.5) + bdtrc_vals = sc.bdtrc([0, 1, 2], 2, 0.5) + vals = bdtr_vals + bdtrc_vals + assert_allclose(vals, [1.0, 1.0, 1.0]) + + +class TestBdtri: + def test_value(self): + val = sc.bdtri(0, 1, 0.5) + assert_allclose(val, 0.5) + + def test_sum_is_one(self): + val = sc.bdtri([0, 1], 2, 0.5) + actual = np.asarray([1 - 1/np.sqrt(2), 1/np.sqrt(2)]) + assert_allclose(val, actual) + + def test_rounding(self): + double_val = sc.bdtri([0.1, 1.1], 2, 0.5) + int_val = sc.bdtri([0, 1], 2, 0.5) + assert_allclose(double_val, int_val) + + @pytest.mark.parametrize('k, n, p', [ + (np.inf, 2, 0.5), + (1.0, np.inf, 0.5), + (1.0, 2, np.inf) + ]) + def test_inf(self, k, n, p): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + val = sc.bdtri(k, n, p) + assert np.isnan(val) + + @pytest.mark.parametrize('k, n, p', [ + (-1.1, 1, 0.5), + (2.1, 1, 0.5) + ]) + def test_domain(self, k, n, p): + val = sc.bdtri(k, n, p) + assert np.isnan(val) + + def test_bdtr_bdtri_roundtrip(self): + bdtr_vals = sc.bdtr([0, 1, 2], 2, 0.5) + roundtrip_vals = sc.bdtri([0, 1, 2], 2, bdtr_vals) + assert_allclose(roundtrip_vals, [0.5, 0.5, np.nan]) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_boost_ufuncs.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_boost_ufuncs.py new file mode 100644 index 0000000000000000000000000000000000000000..e782765da6cf26d10317492c8da3e2f8b1b2b07f --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_boost_ufuncs.py @@ -0,0 +1,83 @@ +import pytest +import numpy as np +from numpy.testing import assert_allclose +import scipy.special._ufuncs as scu +from scipy.integrate import tanhsinh + + +type_char_to_type_tol = {'f': (np.float32, 32*np.finfo(np.float32).eps), + 'd': (np.float64, 32*np.finfo(np.float64).eps)} + + +# Each item in this list is +# (func, args, expected_value) +# All the values can be represented exactly, even with np.float32. +# +# This is not an exhaustive test data set of all the functions! +# It is a spot check of several functions, primarily for +# checking that the different data types are handled correctly. +test_data = [ + (scu._beta_pdf, (0.5, 2, 3), 1.5), + (scu._beta_pdf, (0, 1, 5), 5.0), + (scu._beta_pdf, (1, 5, 1), 5.0), + (scu._beta_ppf, (0.5, 5., 5.), 0.5), # gh-21303 + (scu._binom_cdf, (1, 3, 0.5), 0.5), + (scu._binom_pmf, (1, 4, 0.5), 0.25), + (scu._hypergeom_cdf, (2, 3, 5, 6), 0.5), + (scu._nbinom_cdf, (1, 4, 0.25), 0.015625), + (scu._ncf_mean, (10, 12, 2.5), 1.5), +] + + +@pytest.mark.parametrize('func, args, expected', test_data) +def test_stats_boost_ufunc(func, args, expected): + type_sigs = func.types + type_chars = [sig.split('->')[-1] for sig in type_sigs] + for type_char in type_chars: + typ, rtol = type_char_to_type_tol[type_char] + args = [typ(arg) for arg in args] + # Harmless overflow warnings are a "feature" of some wrappers on some + # platforms. This test is about dtype and accuracy, so let's avoid false + # test failures cause by these warnings. See gh-17432. + with np.errstate(over='ignore'): + value = func(*args) + assert isinstance(value, typ) + assert_allclose(value, expected, rtol=rtol) + + +def test_landau(): + # Test that Landau distribution ufuncs are wrapped as expected; + # accuracy is tested by Boost. + x = np.linspace(-3, 10, 10) + args = (0, 1) + res = tanhsinh(lambda x: scu._landau_pdf(x, *args), -np.inf, x) + cdf = scu._landau_cdf(x, *args) + assert_allclose(res.integral, cdf) + sf = scu._landau_sf(x, *args) + assert_allclose(sf, 1-cdf) + ppf = scu._landau_ppf(cdf, *args) + assert_allclose(ppf, x) + isf = scu._landau_isf(sf, *args) + assert_allclose(isf, x, rtol=1e-6) + +def test_gh22956(): + _ = scu._ncx2_pdf(30, 1e307, 16) + +@pytest.mark.parametrize("func", [scu._binom_cdf, scu._binom_sf]) +@pytest.mark.parametrize("dtype", [np.float32, np.float64]) +def test_extreme_inputs_for_binomial_probabilities(func, dtype): + # certain inputs caused C++ exceptions in boost + # resulting in Python interpreter crashes + k = 3e18 + n = 10e18 + p = 0.3 + func(dtype(k), dtype(n), dtype(p)) + +@pytest.mark.parametrize("func", [scu._binom_ppf, scu._binom_isf]) +@pytest.mark.parametrize("dtype", [np.float32, np.float64]) +def test_extreme_inputs_for_binomial_quantiles(func, dtype): + # certain inputs caused C++ exceptions in boost + # resulting in Python interpreter crashes + n = 10e18 + p = 0.5 + func(dtype(p), dtype(n), dtype(p)) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_boxcox.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_boxcox.py new file mode 100644 index 0000000000000000000000000000000000000000..703a1fdee635a484fe53f14e98fb3ce08c811447 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_boxcox.py @@ -0,0 +1,125 @@ +import numpy as np +from numpy.testing import assert_equal, assert_allclose +from scipy.special import boxcox, boxcox1p, inv_boxcox, inv_boxcox1p +import pytest + + +# There are more tests of boxcox and boxcox1p in test_mpmath.py. + +def test_boxcox_basic(): + x = np.array([0.5, 1, 2, 4]) + + # lambda = 0 => y = log(x) + y = boxcox(x, 0) + assert_allclose(y, np.log(x), atol=1.5e-7, rtol=0) + + # lambda = 1 => y = x - 1 + y = boxcox(x, 1) + assert_allclose(y, x - 1, atol=1.5e-7, rtol=0) + + # lambda = 2 => y = 0.5*(x**2 - 1) + y = boxcox(x, 2) + assert_allclose(y, 0.5*(x**2 - 1), atol=1.5e-7, rtol=0) + + # x = 0 and lambda > 0 => y = -1 / lambda + lam = np.array([0.5, 1, 2]) + y = boxcox(0, lam) + assert_allclose(y, -1.0 / lam, atol=1.5e-7, rtol=0) + +def test_boxcox_underflow(): + x = 1 + 1e-15 + lmbda = 1e-306 + y = boxcox(x, lmbda) + assert_allclose(y, np.log(x), rtol=1e-14) + + +def test_boxcox_nonfinite(): + # x < 0 => y = nan + x = np.array([-1, -1, -0.5]) + y = boxcox(x, [0.5, 2.0, -1.5]) + assert_equal(y, np.array([np.nan, np.nan, np.nan])) + + # x = 0 and lambda <= 0 => y = -inf + x = 0 + y = boxcox(x, [-2.5, 0]) + assert_equal(y, np.array([-np.inf, -np.inf])) + + +def test_boxcox1p_basic(): + x = np.array([-0.25, -1e-20, 0, 1e-20, 0.25, 1, 3]) + + # lambda = 0 => y = log(1+x) + y = boxcox1p(x, 0) + assert_allclose(y, np.log1p(x), atol=1.5e-7, rtol=0) + + # lambda = 1 => y = x + y = boxcox1p(x, 1) + assert_allclose(y, x, atol=1.5e-7, rtol=0) + + # lambda = 2 => y = 0.5*((1+x)**2 - 1) = 0.5*x*(2 + x) + y = boxcox1p(x, 2) + assert_allclose(y, 0.5*x*(2 + x), atol=1.5e-7, rtol=0) + + # x = -1 and lambda > 0 => y = -1 / lambda + lam = np.array([0.5, 1, 2]) + y = boxcox1p(-1, lam) + assert_allclose(y, -1.0 / lam, atol=1.5e-7, rtol=0) + + +def test_boxcox1p_underflow(): + x = np.array([1e-15, 1e-306]) + lmbda = np.array([1e-306, 1e-18]) + y = boxcox1p(x, lmbda) + assert_allclose(y, np.log1p(x), rtol=1e-14) + + +def test_boxcox1p_nonfinite(): + # x < -1 => y = nan + x = np.array([-2, -2, -1.5]) + y = boxcox1p(x, [0.5, 2.0, -1.5]) + assert_equal(y, np.array([np.nan, np.nan, np.nan])) + + # x = -1 and lambda <= 0 => y = -inf + x = -1 + y = boxcox1p(x, [-2.5, 0]) + assert_equal(y, np.array([-np.inf, -np.inf])) + + +def test_inv_boxcox(): + x = np.array([0., 1., 2.]) + lam = np.array([0., 1., 2.]) + y = boxcox(x, lam) + x2 = inv_boxcox(y, lam) + assert_allclose(x, x2, atol=1.5e-7, rtol=0) + + x = np.array([0., 1., 2.]) + lam = np.array([0., 1., 2.]) + y = boxcox1p(x, lam) + x2 = inv_boxcox1p(y, lam) + assert_allclose(x, x2, atol=1.5e-7, rtol=0) + + +def test_inv_boxcox1p_underflow(): + x = 1e-15 + lam = 1e-306 + y = inv_boxcox1p(x, lam) + assert_allclose(y, x, rtol=1e-14) + + +@pytest.mark.parametrize( + "x, lmb", + [[100, 155], + [0.01, -155]] +) +def test_boxcox_premature_overflow(x, lmb): + # test boxcox & inv_boxcox + y = boxcox(x, lmb) + assert np.isfinite(y) + x_inv = inv_boxcox(y, lmb) + assert_allclose(x, x_inv) + + # test boxcox1p & inv_boxcox1p + y1p = boxcox1p(x-1, lmb) + assert np.isfinite(y1p) + x1p_inv = inv_boxcox1p(y1p, lmb) + assert_allclose(x-1, x1p_inv) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_cdflib.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_cdflib.py new file mode 100644 index 0000000000000000000000000000000000000000..48938605349829a024f1b9bff5b7af96e57cec6a --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_cdflib.py @@ -0,0 +1,980 @@ +""" +Test cdflib functions versus mpmath, if available. + +The following functions still need tests: + +- ncfdtridfn +- ncfdtridfd +- ncfdtrinc +- nctdtridf +- nctdtrinc + +""" +import itertools + +import numpy as np +from numpy.testing import assert_equal, assert_allclose, assert_array_equal +import pytest + +import scipy.special as sp +from scipy.special._testutils import ( + MissingModule, check_version, FuncData) +from scipy.special._mptestutils import ( + Arg, IntArg, get_args, mpf2float, assert_mpmath_equal) + +try: + import mpmath +except ImportError: + mpmath = MissingModule('mpmath') + + +class ProbArg: + """Generate a set of probabilities on [0, 1].""" + + def __init__(self): + # Include the endpoints for compatibility with Arg et. al. + self.a = 0 + self.b = 1 + + def values(self, n): + """Return an array containing approximately n numbers.""" + m = max(1, n//3) + v1 = np.logspace(-30, np.log10(0.3), m) + v2 = np.linspace(0.3, 0.7, m + 1, endpoint=False)[1:] + v3 = 1 - np.logspace(np.log10(0.3), -15, m) + v = np.r_[v1, v2, v3] + return np.unique(v) + + +class EndpointFilter: + def __init__(self, a, b, rtol, atol): + self.a = a + self.b = b + self.rtol = rtol + self.atol = atol + + def __call__(self, x): + mask1 = np.abs(x - self.a) < self.rtol*np.abs(self.a) + self.atol + mask2 = np.abs(x - self.b) < self.rtol*np.abs(self.b) + self.atol + return np.where(mask1 | mask2, False, True) + + +class _CDFData: + def __init__(self, spfunc, mpfunc, index, argspec, spfunc_first=True, + dps=20, n=5000, rtol=None, atol=None, + endpt_rtol=None, endpt_atol=None): + self.spfunc = spfunc + self.mpfunc = mpfunc + self.index = index + self.argspec = argspec + self.spfunc_first = spfunc_first + self.dps = dps + self.n = n + self.rtol = rtol + self.atol = atol + + if not isinstance(argspec, list): + self.endpt_rtol = None + self.endpt_atol = None + elif endpt_rtol is not None or endpt_atol is not None: + if isinstance(endpt_rtol, list): + self.endpt_rtol = endpt_rtol + else: + self.endpt_rtol = [endpt_rtol]*len(self.argspec) + if isinstance(endpt_atol, list): + self.endpt_atol = endpt_atol + else: + self.endpt_atol = [endpt_atol]*len(self.argspec) + else: + self.endpt_rtol = None + self.endpt_atol = None + + def idmap(self, *args): + if self.spfunc_first: + res = self.spfunc(*args) + if np.isnan(res): + return np.nan + args = list(args) + args[self.index] = res + with mpmath.workdps(self.dps): + res = self.mpfunc(*tuple(args)) + # Imaginary parts are spurious + res = mpf2float(res.real) + else: + with mpmath.workdps(self.dps): + res = self.mpfunc(*args) + res = mpf2float(res.real) + args = list(args) + args[self.index] = res + res = self.spfunc(*tuple(args)) + return res + + def get_param_filter(self): + if self.endpt_rtol is None and self.endpt_atol is None: + return None + + filters = [] + for rtol, atol, spec in zip(self.endpt_rtol, self.endpt_atol, self.argspec): + if rtol is None and atol is None: + filters.append(None) + continue + elif rtol is None: + rtol = 0.0 + elif atol is None: + atol = 0.0 + + filters.append(EndpointFilter(spec.a, spec.b, rtol, atol)) + return filters + + def check(self): + # Generate values for the arguments + args = get_args(self.argspec, self.n) + param_filter = self.get_param_filter() + param_columns = tuple(range(args.shape[1])) + result_columns = args.shape[1] + args = np.hstack((args, args[:, self.index].reshape(args.shape[0], 1))) + FuncData(self.idmap, args, + param_columns=param_columns, result_columns=result_columns, + rtol=self.rtol, atol=self.atol, vectorized=False, + param_filter=param_filter).check() + + +def _assert_inverts(*a, **kw): + d = _CDFData(*a, **kw) + d.check() + + +def _binomial_cdf(k, n, p): + k, n, p = mpmath.mpf(k), mpmath.mpf(n), mpmath.mpf(p) + if k <= 0: + return mpmath.mpf(0) + elif k >= n: + return mpmath.mpf(1) + + onemp = mpmath.fsub(1, p, exact=True) + return mpmath.betainc(n - k, k + 1, x2=onemp, regularized=True) + + +def _f_cdf(dfn, dfd, x): + if x < 0: + return mpmath.mpf(0) + dfn, dfd, x = mpmath.mpf(dfn), mpmath.mpf(dfd), mpmath.mpf(x) + ub = dfn*x/(dfn*x + dfd) + res = mpmath.betainc(dfn/2, dfd/2, x2=ub, regularized=True) + return res + + +def _student_t_cdf(df, t, dps=None): + if dps is None: + dps = mpmath.mp.dps + with mpmath.workdps(dps): + df, t = mpmath.mpf(df), mpmath.mpf(t) + fac = mpmath.hyp2f1(0.5, 0.5*(df + 1), 1.5, -t**2/df) + fac *= t*mpmath.gamma(0.5*(df + 1)) + fac /= mpmath.sqrt(mpmath.pi*df)*mpmath.gamma(0.5*df) + return 0.5 + fac + + +def _noncentral_chi_pdf(t, df, nc): + res = mpmath.besseli(df/2 - 1, mpmath.sqrt(nc*t)) + res *= mpmath.exp(-(t + nc)/2)*(t/nc)**(df/4 - 1/2)/2 + return res + + +def _noncentral_chi_cdf(x, df, nc, dps=None): + if dps is None: + dps = mpmath.mp.dps + x, df, nc = mpmath.mpf(x), mpmath.mpf(df), mpmath.mpf(nc) + with mpmath.workdps(dps): + res = mpmath.quad(lambda t: _noncentral_chi_pdf(t, df, nc), [0, x]) + return res + + +def _tukey_lmbda_quantile(p, lmbda): + # For lmbda != 0 + return (p**lmbda - (1 - p)**lmbda)/lmbda + + +@pytest.mark.slow +@check_version(mpmath, '0.19') +class TestCDFlib: + + def test_bdtrin(self): + _assert_inverts( + sp.bdtrin, + _binomial_cdf, + 1, [IntArg(1, 3000), ProbArg(), ProbArg()], + rtol=1e-12, endpt_atol=[None, None, 1e-6]) + + def test_btdtria(self): + _assert_inverts( + sp.btdtria, + lambda a, b, x: mpmath.betainc(a, b, x2=x, regularized=True), + 0, [ProbArg(), Arg(0, 3e2, inclusive_a=False), + Arg(0, 1, inclusive_a=False, inclusive_b=False)], + rtol=1e-12) + + def test_btdtrib(self): + # Use small values of a or mpmath doesn't converge + _assert_inverts( + sp.btdtrib, + lambda a, b, x: mpmath.betainc(a, b, x2=x, regularized=True), + 1, + [Arg(0, 1e2, inclusive_a=False), ProbArg(), + Arg(0, 1, inclusive_a=False, inclusive_b=False)], + rtol=1e-7, + endpt_atol=[None, 1e-18, 1e-15]) + + @pytest.mark.xfail(run=False) + def test_fdtridfd(self): + _assert_inverts( + sp.fdtridfd, + _f_cdf, + 1, + [IntArg(1, 100), ProbArg(), Arg(0, 100, inclusive_a=False)], + rtol=1e-7) + + def test_gdtria(self): + _assert_inverts( + sp.gdtria, + lambda a, b, x: mpmath.gammainc(b, b=a*x, regularized=True), + 0, + [ProbArg(), Arg(0, 1e3, inclusive_a=False), + Arg(0, 1e4, inclusive_a=False)], + rtol=1e-12, + endpt_atol=[None, 1e-7, 1e-10]) + + def test_gdtrib(self): + # Use small values of a and x or mpmath doesn't converge + _assert_inverts( + sp.gdtrib, + lambda a, b, x: mpmath.gammainc(b, b=a*x, regularized=True), + 1, + [Arg(0, 1e2, inclusive_a=False), ProbArg(), + Arg(0, 1e3, inclusive_a=False)], + rtol=1e-5) + + def test_gdtrix(self): + _assert_inverts( + sp.gdtrix, + lambda a, b, x: mpmath.gammainc(b, b=a*x, regularized=True), + 2, + [Arg(0, 1e3, inclusive_a=False), Arg(0, 1e3, inclusive_a=False), + ProbArg()], + rtol=1e-7, + endpt_atol=[None, 1e-7, 1e-10]) + + # Overall nrdtrimn and nrdtrisd are not performing well with infeasible/edge + # combinations of sigma and x, hence restricted the domains to still use the + # testing machinery, also see gh-20069 + + # nrdtrimn signature: p, sd, x + # nrdtrisd signature: mn, p, x + def test_nrdtrimn(self): + _assert_inverts( + sp.nrdtrimn, + lambda x, y, z: mpmath.ncdf(z, x, y), + 0, + [ProbArg(), # CDF value p + Arg(0.1, np.inf, inclusive_a=False, inclusive_b=False), # sigma + Arg(-1e10, 1e10)], # x + rtol=1e-5) + + def test_nrdtrisd(self): + _assert_inverts( + sp.nrdtrisd, + lambda x, y, z: mpmath.ncdf(z, x, y), + 1, + [Arg(-np.inf, 10, inclusive_a=False, inclusive_b=False), # mn + ProbArg(), # CDF value p + Arg(10, 1e100)], # x + rtol=1e-5) + + def test_stdtr(self): + # Ideally the left endpoint for Arg() should be 0. + assert_mpmath_equal( + sp.stdtr, + _student_t_cdf, + [IntArg(1, 100), Arg(1e-10, np.inf)], rtol=1e-7) + + @pytest.mark.xfail(run=False) + def test_stdtridf(self): + _assert_inverts( + sp.stdtridf, + _student_t_cdf, + 0, [ProbArg(), Arg()], rtol=1e-7) + + def test_stdtrit(self): + _assert_inverts( + sp.stdtrit, + _student_t_cdf, + 1, [IntArg(1, 100), ProbArg()], rtol=1e-7, + endpt_atol=[None, 1e-10]) + + def test_chdtriv(self): + _assert_inverts( + sp.chdtriv, + lambda v, x: mpmath.gammainc(v/2, b=x/2, regularized=True), + 0, [ProbArg(), IntArg(1, 100)], rtol=1e-4) + + + @pytest.mark.xfail(run=False) + def test_chndtridf(self): + # Use a larger atol since mpmath is doing numerical integration + _assert_inverts( + sp.chndtridf, + _noncentral_chi_cdf, + 1, [Arg(0, 100, inclusive_a=False), ProbArg(), + Arg(0, 100, inclusive_a=False)], + n=1000, rtol=1e-4, atol=1e-15) + + @pytest.mark.xfail(run=False) + def test_chndtrinc(self): + # Use a larger atol since mpmath is doing numerical integration + _assert_inverts( + sp.chndtrinc, + _noncentral_chi_cdf, + 2, [Arg(0, 100, inclusive_a=False), IntArg(1, 100), ProbArg()], + n=1000, rtol=1e-4, atol=1e-15) + + def test_chndtrix(self): + # Use a larger atol since mpmath is doing numerical integration + _assert_inverts( + sp.chndtrix, + _noncentral_chi_cdf, + 0, [ProbArg(), IntArg(1, 100), Arg(0, 100, inclusive_a=False)], + n=1000, rtol=1e-4, atol=1e-15, + endpt_atol=[1e-6, None, None]) + + def test_tklmbda_zero_shape(self): + # When lmbda = 0 the CDF has a simple closed form + one = mpmath.mpf(1) + assert_mpmath_equal( + lambda x: sp.tklmbda(x, 0), + lambda x: one/(mpmath.exp(-x) + one), + [Arg()], rtol=1e-7) + + def test_tklmbda_neg_shape(self): + _assert_inverts( + sp.tklmbda, + _tukey_lmbda_quantile, + 0, [ProbArg(), Arg(-25, 0, inclusive_b=False)], + spfunc_first=False, rtol=1e-5, + endpt_atol=[1e-9, 1e-5]) + + @pytest.mark.xfail(run=False) + def test_tklmbda_pos_shape(self): + _assert_inverts( + sp.tklmbda, + _tukey_lmbda_quantile, + 0, [ProbArg(), Arg(0, 100, inclusive_a=False)], + spfunc_first=False, rtol=1e-5) + + # The values of lmdba are chosen so that 1/lmbda is exact. + @pytest.mark.parametrize('lmbda', [0.5, 1.0, 8.0]) + def test_tklmbda_lmbda1(self, lmbda): + bound = 1/lmbda + assert_equal(sp.tklmbda([-bound, bound], lmbda), [0.0, 1.0]) + + +funcs = [ + ("btdtria", 3), + ("btdtrib", 3), + ("bdtrik", 3), + ("bdtrin", 3), + ("chdtriv", 2), + ("chndtr", 3), + ("chndtrix", 3), + ("chndtridf", 3), + ("chndtrinc", 3), + ("fdtridfd", 3), + ("ncfdtr", 4), + ("ncfdtri", 4), + ("ncfdtridfn", 4), + ("ncfdtridfd", 4), + ("ncfdtrinc", 4), + ("gdtrix", 3), + ("gdtrib", 3), + ("gdtria", 3), + ("nbdtrik", 3), + ("nbdtrin", 3), + ("nrdtrimn", 3), + ("nrdtrisd", 3), + ("pdtrik", 2), + ("stdtr", 2), + ("stdtrit", 2), + ("stdtridf", 2), + ("nctdtr", 3), + ("nctdtrit", 3), + ("nctdtridf", 3), + ("nctdtrinc", 3), + ("tklmbda", 2), +] + + +@pytest.mark.parametrize('func,numargs', funcs, ids=[x[0] for x in funcs]) +def test_nonfinite(func, numargs): + + rng = np.random.default_rng(1701299355559735) + func = getattr(sp, func) + args_choices = [(float(x), np.nan, np.inf, -np.inf) for x in rng.random(numargs)] + + for args in itertools.product(*args_choices): + res = func(*args) + + if any(np.isnan(x) for x in args): + # Nan inputs should result to nan output + assert_equal(res, np.nan) + else: + # All other inputs should return something (but not + # raise exceptions or cause hangs) + pass + + +def test_chndtrix_gh2158(): + # test that gh-2158 is resolved; previously this blew up + res = sp.chndtrix(0.999999, 2, np.arange(20.)+1e-6) + + # Generated in R + # options(digits=16) + # ncp <- seq(0, 19) + 1e-6 + # print(qchisq(0.999999, df = 2, ncp = ncp)) + res_exp = [27.63103493142305, 35.25728589950540, 39.97396073236288, + 43.88033702110538, 47.35206403482798, 50.54112500166103, + 53.52720257322766, 56.35830042867810, 59.06600769498512, + 61.67243118946381, 64.19376191277179, 66.64228141346548, + 69.02756927200180, 71.35726934749408, 73.63759723904816, + 75.87368842650227, 78.06984431185720, 80.22971052389806, + 82.35640899964173, 84.45263768373256] + assert_allclose(res, res_exp) + + +def test_nctdtrinc_gh19896(): + # test that gh-19896 is resolved. + # Compared to SciPy 1.11 results from Fortran code. + dfarr = [0.001, 0.98, 9.8, 98, 980, 10000, 98, 9.8, 0.98, 0.001] + parr = [0.001, 0.1, 0.3, 0.8, 0.999, 0.001, 0.1, 0.3, 0.8, 0.999] + tarr = [0.0015, 0.15, 1.5, 15, 300, 0.0015, 0.15, 1.5, 15, 300] + desired = [3.090232306168629, 1.406141304556198, 2.014225177124157, + 13.727067118283456, 278.9765683871208, 3.090232306168629, + 1.4312427877936222, 2.014225177124157, 3.712743137978295, + -3.086951096691082] + actual = sp.nctdtrinc(dfarr, parr, tarr) + assert_allclose(actual, desired, rtol=5e-12, atol=0.0) + + +def test_stdtr_stdtrit_neg_inf(): + # -inf was treated as +inf and values from the normal were returned + assert np.all(np.isnan(sp.stdtr(-np.inf, [-np.inf, -1.0, 0.0, 1.0, np.inf]))) + assert np.all(np.isnan(sp.stdtrit(-np.inf, [0.0, 0.25, 0.5, 0.75, 1.0]))) + + +def test_bdtrik_nbdtrik_inf(): + y = np.array( + [np.nan,-np.inf,-10.0, -1.0, 0.0, .00001, .5, 0.9999, 1.0, 10.0, np.inf]) + y = y[:,None] + p = np.atleast_2d( + [np.nan, -np.inf, -10.0, -1.0, 0.0, .00001, .5, 1.0, np.inf]) + assert np.all(np.isnan(sp.bdtrik(y, np.inf, p))) + assert np.all(np.isnan(sp.nbdtrik(y, np.inf, p))) + + +@pytest.mark.parametrize( + "dfn,dfd,nc,f,expected_cdf", + [[100.0, 0.1, 0.1, 100.0, 0.29787396410092676], + [100.0, 100.0, 0.01, 0.1, 4.4344737598690424e-26], + [100.0, 0.01, 0.1, 0.01, 0.002848616633080384], + [10.0, 0.01, 1.0, 0.1, 0.012339557729057956], + [100.0, 100.0, 0.01, 0.01, 1.8926477420964936e-72], + [1.0, 100.0, 100.0, 0.1, 1.7925940526821304e-22], + [1.0, 0.01, 100.0, 10.0, 0.012334711965024968], + [1.0, 0.01, 10.0, 0.01, 0.00021944525290299], + [10.0, 1.0, 0.1, 100.0, 0.9219345555070705], + [0.1, 0.1, 1.0, 1.0, 0.3136335813423239], + [100.0, 100.0, 0.1, 10.0, 1.0], + [1.0, 0.1, 100.0, 10.0, 0.02926064279680897], + [1e-100, 3, 1.5, 1e100, 0.611815287345399] + ] +) +def test_ncfdtr_ncfdtri(dfn, dfd, nc, f, expected_cdf): + # Reference values computed with mpmath with the following script + # + # import numpy as np + # + # from mpmath import mp + # from scipy.special import ncfdtr + # + # mp.dps = 100 + # + # def mp_ncfdtr(dfn, dfd, nc, f): + # # Uses formula 26.2.20 from Abramowitz and Stegun. + # dfn, dfd, nc, f = map(mp.mpf, (dfn, dfd, nc, f)) + # def term(j): + # result = mp.exp(-nc/2)*(nc/2)**j / mp.factorial(j) + # result *= mp.betainc( + # dfn/2 + j, dfd/2, 0, f*dfn/(f*dfn + dfd), regularized=True + # ) + # return result + # result = mp.nsum(term, [0, mp.inf]) + # return float(result) + # + # dfn = np.logspace(-2, 2, 5) + # dfd = np.logspace(-2, 2, 5) + # nc = np.logspace(-2, 2, 5) + # f = np.logspace(-2, 2, 5) + # + # dfn, dfd, nc, f = np.meshgrid(dfn, dfd, nc, f) + # dfn, dfd, nc, f = map(np.ravel, (dfn, dfd, nc, f)) + # + # cases = [] + # re = [] + # for x0, x1, x2, x3 in zip(*(dfn, dfd, nc, f)): + # observed = ncfdtr(x0, x1, x2, x3) + # expected = mp_ncfdtr(x0, x1, x2, x3) + # cases.append((x0, x1, x2, x3, expected)) + # re.append((abs(expected - observed)/abs(expected))) + # + # assert np.max(re) < 1e-13 + # + # rng = np.random.default_rng(1234) + # sample_idx = rng.choice(len(re), replace=False, size=12) + # cases = np.array(cases)[sample_idx].tolist() + assert_allclose(sp.ncfdtr(dfn, dfd, nc, f), expected_cdf, rtol=1e-13, atol=0) + # testing tails where the CDF reaches 0 or 1 does not make sense for inverses + # of a CDF as they are not bijective in these regions + if 0 < expected_cdf < 1: + assert_allclose(sp.ncfdtri(dfn, dfd, nc, expected_cdf), f, rtol=5e-11) + +@pytest.mark.parametrize( + "args", + [(-1.0, 0.1, 0.1, 0.5), + (1, -1.0, 0.1, 0.5), + (1, 1, -1.0, 0.5), + (1, 1, 1, 100), + (1, 1, 1, -1)] +) +def test_ncfdtri_domain_error(args): + with sp.errstate(domain="raise"): + with pytest.raises(sp.SpecialFunctionError, match="domain"): + sp.ncfdtri(*args) + +class TestNoncentralTFunctions: + + # Reference values computed with mpmath with the following script + # Formula from: + # Lenth, Russell V (1989). "Algorithm AS 243: Cumulative Distribution Function + # of the Non-central t Distribution". Journal of the Royal Statistical Society, + # Series C. 38 (1): 185-189 + # + # Warning: may take a long time to run + # + # from mpmath import mp + # mp.dps = 400 + + # def nct_cdf(df, nc, x): + # df, nc, x = map(mp.mpf, (df, nc, x)) + + # def f(df, nc, x): + # phi = mp.ncdf(-nc) + # y = x * x / (x * x + df) + # constant = mp.exp(-nc * nc / 2.) + # def term(j): + # intermediate = constant * (nc *nc / 2.)**j + # p = intermediate/mp.factorial(j) + # q = nc / (mp.sqrt(2.) * mp.gamma(j + 1.5)) * intermediate + # first_beta_term = mp.betainc(j + 0.5, df/2., x2=y, + # regularized=True) + # second_beta_term = mp.betainc(j + mp.one, df/2., x2=y, + # regularized=True) + # return p * first_beta_term + q * second_beta_term + + # sum_term = mp.nsum(term, [0, mp.inf]) + # f = phi + 0.5 * sum_term + # return f + + # if x >= 0: + # result = f(df, nc, x) + # else: + # result = mp.one - f(df, -nc, x) + # return float(result) + + @pytest.mark.parametrize("df, nc, x, expected_cdf", [ + (0.98, -3.8, 0.0015, 0.9999279987514815), + (0.98, -3.8, 0.15, 0.9999528361700505), + (0.98, -3.8, 1.5, 0.9999908823016942), + (0.98, -3.8, 15, 0.9999990264591945), + (0.98, 0.38, 0.0015, 0.35241533122693), + (0.98, 0.38, 0.15, 0.39749697267146983), + (0.98, 0.38, 1.5, 0.716862963488558), + (0.98, 0.38, 15, 0.9656246449257494), + (0.98, 3.8, 0.0015, 7.26973354942293e-05), + (0.98, 3.8, 0.15, 0.00012416481147589105), + (0.98, 3.8, 1.5, 0.035388035775454095), + (0.98, 3.8, 15, 0.7954826975430583), + (0.98, 38, 0.0015, 3.02106943e-316), + (0.98, 38, 0.15, 6.069970616996603e-309), + (0.98, 38, 1.5, 2.591995360483094e-97), + (0.98, 38, 15, 0.011927265886910935), + (9.8, -3.8, 0.0015, 0.9999280776192786), + (9.8, -3.8, 0.15, 0.9999599410685442), + (9.8, -3.8, 1.5, 0.9999997432394788), + (9.8, -3.8, 15, 0.9999999999999984), + (9.8, 0.38, 0.0015, 0.3525155979107491), + (9.8, 0.38, 0.15, 0.40763120140379194), + (9.8, 0.38, 1.5, 0.8476794017024651), + (9.8, 0.38, 15, 0.9999999297116268), + (9.8, 3.8, 0.0015, 7.277620328149153e-05), + (9.8, 3.8, 0.15, 0.00013024802220900652), + (9.8, 3.8, 1.5, 0.013477432800072933), + (9.8, 3.8, 15, 0.999850151230648), + (9.8, 38, 0.0015, 3.05066095e-316), + (9.8, 38, 0.15, 1.79065514676e-313), + (9.8, 38, 1.5, 2.0935940165900746e-249), + (9.8, 38, 15, 2.252076291604796e-09), + (98, -3.8, 0.0015, 0.9999280875149109), + (98, -3.8, 0.15, 0.9999608250170452), + (98, -3.8, 1.5, 0.9999999304757682), + (98, -3.8, 15, 1.0), + (98, 0.38, 0.0015, 0.35252817848596313), + (98, 0.38, 0.15, 0.40890253001794846), + (98, 0.38, 1.5, 0.8664672830006552), + (98, 0.38, 15, 1.0), + (98, 3.8, 0.0015, 7.278609891281275e-05), + (98, 3.8, 0.15, 0.0001310318674827004), + (98, 3.8, 1.5, 0.010990879189991727), + (98, 3.8, 15, 0.9999999999999989), + (98, 38, 0.0015, 3.05437385e-316), + (98, 38, 0.15, 9.1668336166e-314), + (98, 38, 1.5, 1.8085884236563926e-288), + (98, 38, 15, 2.7740532792035907e-50), + (980, -3.8, 0.0015, 0.9999280885188965), + (980, -3.8, 0.15, 0.9999609144559273), + (980, -3.8, 1.5, 0.9999999410050979), + (980, -3.8, 15, 1.0), + (980, 0.38, 0.0015, 0.3525294548792812), + (980, 0.38, 0.15, 0.4090315324657382), + (980, 0.38, 1.5, 0.8684247068517293), + (980, 0.38, 15, 1.0), + (980, 3.8, 0.0015, 7.278710289828983e-05), + (980, 3.8, 0.15, 0.00013111131667906573), + (980, 3.8, 1.5, 0.010750678886113882), + (980, 3.8, 15, 1.0), + (980, 38, 0.0015, 3.0547506e-316), + (980, 38, 0.15, 8.6191646313e-314), + (980, 38, 15, 5.407535300713606e-105) + ]) + def test_gh19896(self, df, nc, x, expected_cdf): + # test that gh-19896 is resolved. + # Originally this was a regression test that used the old Fortran results + # as a reference. The Fortran results were not accurate, so the reference + # values were recomputed with mpmath. + nctdtr_result = sp.nctdtr(df, nc, x) + assert_allclose(nctdtr_result, expected_cdf, rtol=1e-13, atol=1e-303) + + def test_gh19896_loose_tolerance(self): + # edge case with very small CDF value, requires a looser tolerance to pass + assert_allclose(sp.nctdtr(980, 38, 1.5), 1.1824454111413493e-291, rtol=1e-8) + + def test_nctdtr_gh8344(self): + # test that gh-8344 is resolved. + df, nc, x = 3000, 3, 0.1 + expected = 0.0018657780826323328 + assert_allclose(sp.nctdtr(df, nc, x), expected, rtol=1e-14) + + @pytest.mark.parametrize( + "df, nc, x, expected, rtol", + [[3., 5., -2., 1.5645373999149622e-09, 2e-8], + [1000., 10., 1., 1.1493552133826623e-19, 1e-13], + [1e-5, -6., 2., 0.9999999990135003, 1e-13], + [10., 20., 0.15, 6.426530505957303e-88, 1e-13], + [1., 1., np.inf, 1.0, 0.0], + [1., 1., -np.inf, 0.0, 0.0] + ] + ) + def test_nctdtr_accuracy(self, df, nc, x, expected, rtol): + assert_allclose(sp.nctdtr(df, nc, x), expected, rtol=rtol) + + @pytest.mark.parametrize("df, nc, x, expected_cdf", [ + (0.98, 38, 1.5, 2.591995360483094e-97), + (3000, 3, 0.1, 0.0018657780826323328), + (0.98, -3.8, 15, 0.9999990264591945), + (9.8, 38, 15, 2.252076291604796e-09), + + ]) + def test_nctdtrit(self, df, nc, x, expected_cdf): + assert_allclose(sp.nctdtrit(df, nc, expected_cdf), x, rtol=1e-10) + + +class TestNegativeBinomialFunctions: + + @pytest.mark.parametrize("function", [sp.nbdtrin, sp.nbdtrik]) + @pytest.mark.parametrize("args", + [(np.nan, 1, 1), (1, np.nan, 1), (1, 1, np.nan), + (np.nan, np.nan, 1), (np.nan, 1, np.nan), (1, np.nan, np.nan), + (np.nan, np.nan, np.nan)] + ) + def test_nan_propagation(self, function, args): + assert np.isnan(function(*args)) + + @pytest.mark.parametrize("args", + [(-1, 1, 1), (1, -1, 1), (1, 1, -1), (-1, -1, 1), + (-1, 1, -1), (1, -1, -1), (-1, -1, -1), + (1, 1.1, 0.9), (1, 0.9, 1.1)] + ) + def test_domain_error_nbdtrin(self, args): + with sp.errstate(domain="raise"): + with pytest.raises(sp.SpecialFunctionError, match="domain"): + sp.nbdtrin(*args) + + @pytest.mark.parametrize("args", + [(-1, 1, 1), (1, -1, 1), (1, 1, -1), (-1, -1, 1), + (-1, 1, -1), (1, -1, -1), (-1, -1, -1), + (1, 0.9, 1.1)] + ) + def test_domain_error_nbdtrik(self, args): + with sp.errstate(domain="raise"): + with pytest.raises(sp.SpecialFunctionError, match="domain"): + sp.nbdtrik(*args) + + @pytest.mark.parametrize("k, y, p, n, rtol", + [(5, 0.3214569091796875, 0.25, 3, 5e-16), + (10, 3.820379007878081e-70, 0.15, 100, 1e-15), + (100, 0.975574722927043, 0.15, 10, 1e-14), + (2, 3.503253250000002e-73, 0.001, 25, 5e-15), + (2, 0.9999993271029954, 0.999, 15, 1e-10), + (500, 0.9999999998603178, 0.1, 15, 1e-8), + (0, 1.0000000000000006e-10, 0.1, 10, 5e-16)]) + def test_inverses(self, k, y, p, n, rtol): + # The following code was used to generate the values. + # import mpmath + # import scipy + # mpmath.mp.dps = 1000 + + # def neg_binomial_cdf(k, n, p): + # return mpmath.betainc(n, k+1, x1=0, x2=p, regularized=True) + + assert_allclose(sp.nbdtrin(k, y, p), n, rtol) + assert_allclose(sp.nbdtrik(y, n, p), k, rtol) + + +class TestNoncentralChiSquaredFunctions: + + def test_chndtr_and_inverses_against_wolfram_alpha(self): + # Each row holds (x, nu, lam, expected_value) + # These values were computed using Wolfram Alpha with + # CDF[NoncentralChiSquareDistribution[nu, lam], x] + values = np.array([ + [25.00, 20.0, 400, 4.1210655112396197139e-57], + [25.00, 8.00, 250, 2.3988026526832425878e-29], + [0.001, 8.00, 40., 5.3761806201366039084e-24], + [0.010, 8.00, 40., 5.45396231055999457039e-20], + [20.00, 2.00, 107, 1.39390743555819597802e-9], + [22.50, 2.00, 107, 7.11803307138105870671e-9], + [25.00, 2.00, 107, 3.11041244829864897313e-8], + [3.000, 2.00, 1.0, 0.62064365321954362734], + [350.0, 300., 10., 0.93880128006276407710], + [100.0, 13.5, 10., 0.99999999650104210949], + [700.0, 20.0, 400, 0.99999999925680650105], + [150.0, 13.5, 10., 0.99999999999999983046], + [160.0, 13.5, 10., 0.99999999999999999518], # 1.0 + ]) + cdf = sp.chndtr(values[:, 0], values[:, 1], values[:, 2]) + assert_allclose(cdf, values[:, 3], rtol=1e-13) + # the last two values are very close to 1.0, so we do not + # test the inverses for them + x = sp.chndtrix(values[:, 3], values[:, 1], values[:, 2]) + assert_allclose(x[:-2], values[:-2, 0], rtol=1e-8) + df = sp.chndtridf(values[:, 0], values[:, 3], values[:, 2]) + assert_allclose(df[:-2], values[:-2, 1], rtol=1e-8) + nc = sp.chndtrinc(values[:, 0], values[:, 1], values[:, 3]) + assert_allclose(nc[:-2], values[:-2, 2], rtol=1e-8) + + # CDF Reference values computed with mpmath with the following script + # Formula from: + # https://www.boost.org/doc/libs/1_87_0/libs/math/doc/html/math_toolkit/dist_ref/dists/nc_chi_squared_dist.html # noqa: E501 + # which cites: + # Computing discrete mixtures of continuous distributions: noncentral chisquare, + # noncentral t and the distribution of the square of the + # sample multiple correlation coefficient", + # Denise Benton and K. Krishnamoorthy, + # Computational Statistics & Data Analysis, 43, (2003), 249-267 + + # Warning: may take a long time to run + # from mpmath import mp + # + # mp.dps = 400 + # + # def noncentral_chi_squared_cdf(x, df, nc): + # x, df, nc = map(mp.mpf, (x, df, nc)) + # def term(i): + # return mp.exp(-nc/2) * (nc/2)**i / mp.factorial(i) * + # mp.gammainc(df/2 + i, 0, x/2, regularized=True) + # return float(mp.nsum(term, [0, mp.inf])) + + @pytest.mark.parametrize( + "x, df, nc, expected_cdf, rtol_cdf, rtol_inv", + [(0.1, 200, 50, 1.1311224867205481e-299, 1e-13, 1e-13), + (1e-12, 20, 50, 3.737446313006551e-141, 1e-13, 1e-13), + (1, 200, 50, 8.09760974833666e-200, 1e-13, 1e-13), + (9e4, 1e5, 1e3, 1.6895533704217566e-141, 5e-12, 5e-12), + (30, 3, 1.5, 0.9999508759095675, 5e-13, 5e-13), + (500, 300, 1.5, 0.9999999999944232, 1e-13, 1e-5), + (1400, 30, 1e3, 0.9999999612246238, 1e-13, 1e-9), + (400, 50, 200, 0.9999948255072892, 1e-13, 1e-11)] + ) + def test_tails(self, x, df, nc, expected_cdf, rtol_cdf, rtol_inv): + chndtr_result = sp.chndtr(x, df, nc) + assert_allclose(chndtr_result, expected_cdf, rtol=rtol_cdf) + assert_allclose(sp.chndtrix(expected_cdf, df, nc), x, rtol=rtol_inv) + assert_allclose(sp.chndtridf(x, expected_cdf, nc), df, rtol=rtol_inv) + assert_allclose(sp.chndtrinc(x, df, expected_cdf), nc, rtol=rtol_inv) + + # test round-trip calculation + assert_allclose(sp.chndtrix(chndtr_result, df, nc), x, rtol=rtol_inv) + assert_allclose(sp.chndtridf(x, chndtr_result, nc), df, rtol=1e-7) + assert_allclose(sp.chndtrinc(x, df, chndtr_result), nc, rtol=1e-5) + + @pytest.mark.parametrize("x, df", + [(1, 3), (1, 0), (1, np.inf), (np.inf, 1)] + ) + def test_chndtr_with_nc_zero_equals_chdtr(self, x, df): + assert_allclose(sp.chndtr(x, df, 0), sp.chdtr(df, x), rtol=1e-15) + + @pytest.mark.parametrize("fun", + [sp.chndtr, sp.chndtrix, sp.chndtridf, sp.chndtrinc] + ) + @pytest.mark.parametrize("args", + [(-1, 1, 1), (1, -1, 1), (1, 1, -1), (-1, -1, 1), + (-1, 1, -1), (1, -1, -1), (-1, -1, -1)] + ) + def test_domain_error(self, args, fun): + with sp.errstate(domain="raise"): + with pytest.raises(sp.SpecialFunctionError, match="domain"): + fun(*args) + + @pytest.mark.parametrize("fun", + [sp.chndtr, sp.chndtrix, sp.chndtridf, sp.chndtrinc] + ) + @pytest.mark.parametrize("args", + [(np.nan, 1, 1), (1, np.nan, 1), (1, 1, np.nan), + (np.nan, np.nan, 1), (np.nan, 1, np.nan), (1, np.nan, np.nan), + (np.nan, np.nan, np.nan)] + ) + def test_nan_propagation(self, fun, args): + assert np.isnan(fun(*args)) + + @pytest.mark.parametrize( + "x, df, nc, expected", + [(1, 0, 1, np.nan), + (1, 0, np.inf, np.nan), + (1, np.inf, 1, np.nan), + (1, np.inf, np.inf, 0), + (1, 1, np.inf, 0), + (np.inf, 0, 1, np.nan), + (np.inf, 1, np.inf, np.nan), + (np.inf, 1, 1, 1), + (np.inf, np.inf, np.inf, np.nan)] + ) + def test_chndtr_edge_cases(self, x, df, nc, expected): + assert_allclose(sp.chndtr(x, df, nc), expected, rtol=1e-15) + + +@pytest.mark.parametrize("x", [0.1, 100]) +def test_chdtriv_p_equals_1_returns_0(x): + assert sp.chdtriv(1, x) == 0 + + +class TestPdtrik: + @pytest.mark.parametrize("p, m, expected", + [(0, 0.5, 0), + (0.5, 0, 0)]) + def test_edge_cases(self, p, m, expected): + assert sp.pdtrik(p, m) == expected + + @pytest.mark.parametrize("m", (0.1, 1, 10)) + def test_p_equals_1_returns_nan(self, m): + assert np.isnan(sp.pdtrik(1, m)) + + def test_small_probabilities(self): + # Edge case: m = 0 or very small. + k = sp.pdtrik([[0], [0.25], [0.95]], [0, 1e-20, 1e-6]) + assert_array_equal(k, np.zeros((3, 3))) + + def test_roundtrip_against_pdtr(self): + m = [10, 50, 500] + k = 5 + p = sp.pdtr(k, m) + assert_allclose(sp.pdtrik(p, m), k, rtol=1e-15) + + @pytest.mark.parametrize("p, m, k", + [(1.8976107553682285e-40, 100, 2), + (0.48670120172085135, 100, 99), + (8.30383406699052e-69, 1000, 500), + (2.252837804125894e-227, 100_000, 90_000)]) + def test_accuracy(self, p, m, k): + # Reference values for p were computed with mpmath using + # mp.gammainc(k+1, a=m, regularized=True) + assert_allclose(sp.pdtrik(p, m), k, rtol=1e-15) + +@pytest.mark.parametrize("a, b, p, ref", [ + (0, 0, 0, np.nan), + (0, 0, 1, np.nan), + (0, np.inf, 0, np.nan), + (0, np.inf, 1, np.nan), + (np.inf, 0, 0, np.nan), + (np.inf, 0, 1, np.nan), + (np.inf, np.inf, 0, np.nan), + (np.inf, np.inf, 1, np.nan) +]) +def test_gdtrix_edge_cases(a, b, p, ref): + assert_equal(sp.gdtrix(a, b, p), ref) + +@pytest.mark.parametrize("p, b, x, ref", [ + (0, 0, 0, np.nan), + (0, 0, np.inf, np.nan), + (0, 1, 0, np.nan), + (0, 1, np.inf, 0), + (np.inf, 0, 0, np.nan), + (np.inf, 0, np.inf, np.nan), + (np.inf, 1, 0, np.nan), + (np.inf, 1, np.inf, np.nan) +]) +def test_gdtria_edge_cases(p, b, x, ref): + assert_equal(sp.gdtria(p, b, x), ref) + +@pytest.mark.parametrize("y, n, p, k", [ + (6.110236824190975e-78, 100, 0.9, 10), + (4.0188255421199357e-224, 1000, 0.9, 500), + (0.9999998319058928, 1000, 0.5, 580), + (0.9999734386011124, 100, 0.9, 99) +]) +def test_bdtrik(y, n, p, k): + # Reference values for y were computed with mpmath using + # the _binomial_cdf function from above. + assert_allclose(sp.bdtrik(y, n, p), k, rtol=1e-11) + + +@pytest.mark.parametrize("p, std, x, ref", [ + (0, 0.1, 0, np.nan), + (0.1, np.inf, 1, np.inf), + (0.1, 1, np.inf, np.inf), + (0.1, 1, -np.inf, -np.inf), + (0.1, np.inf, np.inf, np.inf), + (0.1, 0.1, np.inf, np.inf), + (0.1, 1, -np.inf, -np.inf), + (0.3, -1, 1, np.nan) +]) +def test_nrdtrimn_edge_cases(p, std, x, ref): + assert_equal(sp.nrdtrimn(p, std, x), ref) + + +@pytest.mark.parametrize("mn, p, x, ref", [ + (0, 0, 0, np.nan), + (0, 1, 0, np.nan), + (0, 1, np.inf, np.nan), + (0.1, 1, 1, np.nan), + (0.1, 0.1, np.inf, -np.inf), + (0.1, 0.1, -np.inf, np.inf), +]) +def test_nrdtrisd_edge_cases(mn, p, x, ref): + assert_equal(sp.nrdtrisd(mn, p, x), ref) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_cdft_asymptotic.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_cdft_asymptotic.py new file mode 100644 index 0000000000000000000000000000000000000000..2f21b197871a230e3170c21969a8edcc8ebbdfc2 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_cdft_asymptotic.py @@ -0,0 +1,52 @@ +# gh-14777 regression tests +# Test stdtr and stdtrit with infinite df and large values of df + +import numpy as np +from numpy.testing import assert_allclose, assert_equal +from scipy.special import stdtr, stdtrit, ndtr, ndtri + + +def test_stdtr_vs_R_large_df(): + df = [1e10, 1e12, 1e120, np.inf] + t = 1. + res = stdtr(df, t) + # R Code: + # options(digits=20) + # pt(1., c(1e10, 1e12, 1e120, Inf)) + res_R = [0.84134474605644460343, + 0.84134474606842180044, + 0.84134474606854281475, + 0.84134474606854292578] + assert_allclose(res, res_R, rtol=1e-15) + # last value should also agree with ndtr + assert_equal(res[3], ndtr(1.)) + + +def test_stdtrit_vs_R_large_df(): + df = [1e10, 1e12, 1e120, np.inf] + p = 0.1 + res = stdtrit(df, p) + # R Code: + # options(digits=20) + # qt(0.1, c(1e10, 1e12, 1e120, Inf)) + res_R = [-1.2815515656292593150, + -1.2815515655454472466, + -1.2815515655446008125, + -1.2815515655446008125] + assert_allclose(res, res_R, rtol=1e-15, atol=1e-15) + # last value should also agree with ndtri + # actually the result from stdtrit is closer to R than ndtri, + # so we accept a deviation of one ULP + epsilon = np.finfo(np.float64).eps + assert_allclose(res[3], ndtri(0.1), rtol=epsilon, atol=epsilon) + + +def test_stdtr_stdtri_invalid(): + # a mix of large and inf df with t/p equal to nan + df = [1e10, 1e12, 1e120, np.inf] + x = np.nan + res1 = stdtr(df, x) + res2 = stdtrit(df, x) + res_ex = 4*[np.nan] + assert_equal(res1, res_ex) + assert_equal(res2, res_ex) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_cephes_intp_cast.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_cephes_intp_cast.py new file mode 100644 index 0000000000000000000000000000000000000000..05f3d1ae5c101ff50c75d1065e5e234063d192e4 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_cephes_intp_cast.py @@ -0,0 +1,29 @@ +import pytest +import numpy as np +from scipy.special._ufuncs import ( + _smirnovc, _smirnovci, _smirnovp, + _struve_asymp_large_z, _struve_bessel_series, _struve_power_series, + bdtr, bdtrc, bdtri, expn, kn, nbdtr, nbdtrc, nbdtri, pdtri, + smirnov, smirnovi, yn +) + + +# +# For each ufunc here, verify that the default integer type, np.intp, +# can be safely cast to the integer type found in the input type signatures. +# For this particular set of functions, the code expects to find just one +# integer type among the input signatures. +# +@pytest.mark.parametrize( + 'ufunc', + [_smirnovc, _smirnovci, _smirnovp, + _struve_asymp_large_z, _struve_bessel_series, _struve_power_series, + bdtr, bdtrc, bdtri, expn, kn, nbdtr, nbdtrc, nbdtri, pdtri, + smirnov, smirnovi, yn], +) +def test_intp_safe_cast(ufunc): + int_chars = {'i', 'l', 'q'} + int_input = [set(sig.split('->')[0]) & int_chars for sig in ufunc.types] + int_char = ''.join(s.pop() if s else '' for s in int_input) + assert len(int_char) == 1, "More integer types in the signatures than expected" + assert np.can_cast(np.intp, np.dtype(int_char)) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_cosine_distr.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_cosine_distr.py new file mode 100644 index 0000000000000000000000000000000000000000..27e3ca2699d0d1d0b58665f125d99c166095696d --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_cosine_distr.py @@ -0,0 +1,83 @@ +import numpy as np +from numpy.testing import assert_allclose +import pytest +from scipy.special._ufuncs import _cosine_cdf, _cosine_invcdf + + +# These values are (x, p) where p is the expected exact value of +# _cosine_cdf(x). These values will be tested for exact agreement. +_coscdf_exact = [ + (-4.0, 0.0), + (0, 0.5), + (np.pi, 1.0), + (4.0, 1.0), +] + +@pytest.mark.parametrize("x, expected", _coscdf_exact) +def test_cosine_cdf_exact(x, expected): + assert _cosine_cdf(x) == expected + + +# These values are (x, p), where p is the expected value of +# _cosine_cdf(x). The expected values were computed with mpmath using +# 50 digits of precision. These values will be tested for agreement +# with the computed values using a very small relative tolerance. +# The value at -np.pi is not 0, because -np.pi does not equal -π. +_coscdf_close = [ + (3.1409, 0.999999999991185), + (2.25, 0.9819328173287907), + # -1.6 is the threshold below which the Pade approximant is used. + (-1.599, 0.08641959838382553), + (-1.601, 0.086110582992713), + (-2.0, 0.0369709335961611), + (-3.0, 7.522387241801384e-05), + (-3.1415, 2.109869685443648e-14), + (-3.14159, 4.956444476505336e-19), + (-np.pi, 4.871934450264861e-50), +] + +@pytest.mark.parametrize("x, expected", _coscdf_close) +def test_cosine_cdf(x, expected): + assert_allclose(_cosine_cdf(x), expected, rtol=5e-15) + + +# These values are (p, x) where x is the expected exact value of +# _cosine_invcdf(p). These values will be tested for exact agreement. +_cosinvcdf_exact = [ + (0.0, -np.pi), + (0.5, 0.0), + (1.0, np.pi), +] + +@pytest.mark.parametrize("p, expected", _cosinvcdf_exact) +def test_cosine_invcdf_exact(p, expected): + assert _cosine_invcdf(p) == expected + + +def test_cosine_invcdf_invalid_p(): + # Check that p values outside of [0, 1] return nan. + assert np.isnan(_cosine_invcdf([-0.1, 1.1])).all() + + +# These values are (p, x), where x is the expected value of _cosine_invcdf(p). +# The expected values were computed with mpmath using 50 digits of precision. +_cosinvcdf_close = [ + (1e-50, -np.pi), + (1e-14, -3.1415204137058454), + (1e-08, -3.1343686589124524), + (0.0018001, -2.732563923138336), + (0.010, -2.41276589008678), + (0.060, -1.7881244975330157), + (0.125, -1.3752523669869274), + (0.250, -0.831711193579736), + (0.400, -0.3167954512395289), + (0.419, -0.25586025626919906), + (0.421, -0.24947570750445663), + (0.750, 0.831711193579736), + (0.940, 1.7881244975330153), + (0.9999999996, 3.1391220839917167), +] + +@pytest.mark.parametrize("p, expected", _cosinvcdf_close) +def test_cosine_invcdf(p, expected): + assert_allclose(_cosine_invcdf(p), expected, rtol=1e-14) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_cython_abi.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_cython_abi.py new file mode 100644 index 0000000000000000000000000000000000000000..56b61ca951d511bf2a73c8c87e3cc7028ed91b0e --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_cython_abi.py @@ -0,0 +1,87 @@ +# --------------------------------------------------------------------------- +# AUTO-GENERATED - do not edit by hand. +# Re-generate with: python tools/generate_cython_abi_tests.py special +# +# Generated against SciPy 1.17.0 on 2026-05-08 +# Python 3.14.4 +# --------------------------------------------------------------------------- + +""" +Regression test for the cython_special binary interface. + +The __pyx_capi__ dict of each module contains PyCapsule objects whose +*name* string encodes the C-level function signature that Cython checks +at import time when another module does ``cimport``. Any change to +those strings is a binary incompatibility that breaks every downstream +package (e.g., scikit-learn, statsmodels) compiled against the previous release, +without any compile-time warning. + +The expected signatures are stored in cython_abi_signatures.json. +The test fails if: + * an existing name disappears, or + * an existing name's signature string changes. +Adding new names is explicitly allowed (it is not a break). +""" + +import ctypes +import importlib +import importlib.resources +import json + +import pytest +import scipy + +CYTHON_BLAS_ILP64 = ( + scipy.show_config(mode='dicts')['Build Dependencies']['blas']['cython blas ilp64'] +) + + +# --------------------------------------------------------------------------- +# Helper +# --------------------------------------------------------------------------- + +_get_capsule_name = ctypes.pythonapi.PyCapsule_GetName +_get_capsule_name.restype = ctypes.c_char_p +_get_capsule_name.argtypes = [ctypes.py_object] + + +def _extract_capi(module): + """Return {name: signature} for every entry in module.__pyx_capi__.""" + result = {} + for name, capsule in module.__pyx_capi__.items(): + raw = _get_capsule_name(capsule) + if raw is not None: + result[name] = raw.decode('utf-8') + return result + + +_sig_file = importlib.resources.files('scipy.special.tests').joinpath( + 'cython_abi_signatures.json' +) +with importlib.resources.as_file(_sig_file) as _f: + _EXPECTED = json.loads(_f.read_text()) + + +def test_cython_special_abi_stability(): + """No existing cython_special signature may change or disappear.""" + expected = _EXPECTED['scipy.special.cython_special'] + mod = importlib.import_module('scipy.special.cython_special') + actual = _extract_capi(mod) + errors = [] + for name, expected_sig in expected.items(): + if name not in actual: + errors.append( + f"REMOVED {name!r} (was {expected_sig!r})" + ) + elif actual[name] != expected_sig: + errors.append( + f"CHANGED {name!r}\n" + f" expected: {expected_sig!r}\n" + f" actual: {actual[name]!r}" + ) + if errors: + joined = '\n'.join(errors) + pytest.fail( + f"scipy.special.cython_special.__pyx_capi__ has {len(errors)} " + f"ABI breakage(s):\n{joined}" + ) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_cython_special.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_cython_special.py new file mode 100644 index 0000000000000000000000000000000000000000..a9307767ddaeb43dd8c319ac8197198f935f132a --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_cython_special.py @@ -0,0 +1,363 @@ +import warnings + +from collections.abc import Callable + +import pytest +from itertools import product +from numpy.testing import assert_allclose +from scipy import special +from scipy.special import cython_special + + +bint_points = [True, False] +int_points = [-10, -1, 1, 10] +real_points = [-10.0, -1.0, 1.0, 10.0] +complex_points = [complex(*tup) for tup in product(real_points, repeat=2)] + + +CYTHON_SIGNATURE_MAP = { + 'b': 'bint', + 'f': 'float', + 'd': 'double', + 'g': 'long double', + 'F': 'float complex', + 'D': 'double complex', + 'G': 'long double complex', + 'i': 'int', + 'l': 'long' +} + + +TEST_POINTS = { + 'b': bint_points, + 'f': real_points, + 'd': real_points, + 'g': real_points, + 'F': complex_points, + 'D': complex_points, + 'G': complex_points, + 'i': int_points, + 'l': int_points, +} + + +PARAMS: list[tuple[Callable, Callable, tuple[str, ...], str | None]] = [ + (special.agm, cython_special.agm, ('dd',), None), + (special.airy, cython_special._airy_pywrap, ('d', 'D'), None), + (special.airye, cython_special._airye_pywrap, ('d', 'D'), None), + (special.bdtr, cython_special.bdtr, ('dld', 'ddd'), None), + (special.bdtrc, cython_special.bdtrc, ('dld', 'ddd'), None), + (special.bdtri, cython_special.bdtri, ('dld', 'ddd'), None), + (special.bdtrik, cython_special.bdtrik, ('ddd',), None), + (special.bdtrin, cython_special.bdtrin, ('ddd',), None), + (special.bei, cython_special.bei, ('d',), None), + (special.beip, cython_special.beip, ('d',), None), + (special.ber, cython_special.ber, ('d',), None), + (special.berp, cython_special.berp, ('d',), None), + (special.besselpoly, cython_special.besselpoly, ('ddd',), None), + (special.beta, cython_special.beta, ('dd',), None), + (special.betainc, cython_special.betainc, ('ddd',), None), + (special.betaincc, cython_special.betaincc, ('ddd',), None), + (special.betaincinv, cython_special.betaincinv, ('ddd',), None), + (special.betainccinv, cython_special.betainccinv, ('ddd',), None), + (special.betaln, cython_special.betaln, ('dd',), None), + (special.binom, cython_special.binom, ('dd',), None), + (special.boxcox, cython_special.boxcox, ('dd',), None), + (special.boxcox1p, cython_special.boxcox1p, ('dd',), None), + (special.btdtria, cython_special.btdtria, ('ddd',), None), + (special.btdtrib, cython_special.btdtrib, ('ddd',), None), + (special.cbrt, cython_special.cbrt, ('d',), None), + (special.chdtr, cython_special.chdtr, ('dd',), None), + (special.chdtrc, cython_special.chdtrc, ('dd',), None), + (special.chdtri, cython_special.chdtri, ('dd',), None), + (special.chdtriv, cython_special.chdtriv, ('dd',), None), + (special.chndtr, cython_special.chndtr, ('ddd',), None), + (special.chndtridf, cython_special.chndtridf, ('ddd',), None), + (special.chndtrinc, cython_special.chndtrinc, ('ddd',), None), + (special.chndtrix, cython_special.chndtrix, ('ddd',), None), + (special.cosdg, cython_special.cosdg, ('d',), None), + (special.cosm1, cython_special.cosm1, ('d',), None), + (special.cotdg, cython_special.cotdg, ('d',), None), + (special.dawsn, cython_special.dawsn, ('d', 'D'), None), + (special.ellipe, cython_special.ellipe, ('d',), None), + (special.ellipeinc, cython_special.ellipeinc, ('dd',), None), + (special.ellipj, cython_special._ellipj_pywrap, ('dd',), None), + (special.ellipkinc, cython_special.ellipkinc, ('dd',), None), + (special.ellipkm1, cython_special.ellipkm1, ('d',), None), + (special.ellipk, cython_special.ellipk, ('d',), None), + (special.elliprc, cython_special.elliprc, ('dd', 'DD'), None), + (special.elliprd, cython_special.elliprd, ('ddd', 'DDD'), None), + (special.elliprf, cython_special.elliprf, ('ddd', 'DDD'), None), + (special.elliprg, cython_special.elliprg, ('ddd', 'DDD'), None), + (special.elliprj, cython_special.elliprj, ('dddd', 'DDDD'), None), + (special.entr, cython_special.entr, ('d',), None), + (special.erf, cython_special.erf, ('d', 'D'), None), + (special.erfc, cython_special.erfc, ('d', 'D'), None), + (special.erfcx, cython_special.erfcx, ('d', 'D'), None), + (special.erfi, cython_special.erfi, ('d', 'D'), None), + (special.erfinv, cython_special.erfinv, ('d',), None), + (special.erfcinv, cython_special.erfcinv, ('d',), None), + (special.eval_chebyc, cython_special.eval_chebyc, ('dd', 'dD', 'ld'), None), + (special.eval_chebys, cython_special.eval_chebys, ('dd', 'dD', 'ld'), + 'd and l differ for negative int'), + (special.eval_chebyt, cython_special.eval_chebyt, ('dd', 'dD', 'ld'), + 'd and l differ for negative int'), + (special.eval_chebyu, cython_special.eval_chebyu, ('dd', 'dD', 'ld'), + 'd and l differ for negative int'), + (special.eval_gegenbauer, cython_special.eval_gegenbauer, ('ddd', 'ddD', 'ldd'), + 'd and l differ for negative int'), + (special.eval_genlaguerre, cython_special.eval_genlaguerre, ('ddd', 'ddD', 'ldd'), + 'd and l differ for negative int'), + (special.eval_hermite, cython_special.eval_hermite, ('ld',), None), + (special.eval_hermitenorm, cython_special.eval_hermitenorm, ('ld',), None), + (special.eval_jacobi, cython_special.eval_jacobi, ('dddd', 'dddD', 'lddd'), + 'd and l differ for negative int'), + (special.eval_laguerre, cython_special.eval_laguerre, ('dd', 'dD', 'ld'), + 'd and l differ for negative int'), + (special.eval_legendre, cython_special.eval_legendre, ('dd', 'dD', 'ld'), None), + (special.eval_sh_chebyt, cython_special.eval_sh_chebyt, ('dd', 'dD', 'ld'), None), + (special.eval_sh_chebyu, cython_special.eval_sh_chebyu, ('dd', 'dD', 'ld'), + 'd and l differ for negative int'), + (special.eval_sh_jacobi, cython_special.eval_sh_jacobi, ('dddd', 'dddD', 'lddd'), + 'd and l differ for negative int'), + (special.eval_sh_legendre, cython_special.eval_sh_legendre, ('dd', 'dD', 'ld'), + None), + (special.exp1, cython_special.exp1, ('d', 'D'), None), + (special.exp10, cython_special.exp10, ('d',), None), + (special.exp2, cython_special.exp2, ('d',), None), + (special.expi, cython_special.expi, ('d', 'D'), None), + (special.expit, cython_special.expit, ('f', 'd', 'g'), None), + (special.expm1, cython_special.expm1, ('d', 'D'), None), + (special.expn, cython_special.expn, ('ld', 'dd'), None), + (special.exprel, cython_special.exprel, ('d',), None), + (special.fdtr, cython_special.fdtr, ('ddd',), None), + (special.fdtrc, cython_special.fdtrc, ('ddd',), None), + (special.fdtri, cython_special.fdtri, ('ddd',), None), + (special.fdtridfd, cython_special.fdtridfd, ('ddd',), None), + (special.fresnel, cython_special._fresnel_pywrap, ('d', 'D'), None), + (special.gamma, cython_special.gamma, ('d', 'D'), None), + (special.gammainc, cython_special.gammainc, ('dd',), None), + (special.gammaincc, cython_special.gammaincc, ('dd',), None), + (special.gammainccinv, cython_special.gammainccinv, ('dd',), None), + (special.gammaincinv, cython_special.gammaincinv, ('dd',), None), + (special.gammaln, cython_special.gammaln, ('d',), None), + (special.gammasgn, cython_special.gammasgn, ('d',), None), + (special.gdtr, cython_special.gdtr, ('ddd',), None), + (special.gdtrc, cython_special.gdtrc, ('ddd',), None), + (special.gdtria, cython_special.gdtria, ('ddd',), None), + (special.gdtrib, cython_special.gdtrib, ('ddd',), None), + (special.gdtrix, cython_special.gdtrix, ('ddd',), None), + (special.hankel1, cython_special.hankel1, ('dD',), None), + (special.hankel1e, cython_special.hankel1e, ('dD',), None), + (special.hankel2, cython_special.hankel2, ('dD',), None), + (special.hankel2e, cython_special.hankel2e, ('dD',), None), + (special.huber, cython_special.huber, ('dd',), None), + (special.hyp0f1, cython_special.hyp0f1, ('dd', 'dD'), None), + (special.hyp1f1, cython_special.hyp1f1, ('ddd', 'ddD'), None), + (special.hyp2f1, cython_special.hyp2f1, ('dddd', 'dddD'), None), + (special.hyperu, cython_special.hyperu, ('ddd',), None), + (special.i0, cython_special.i0, ('d',), None), + (special.i0e, cython_special.i0e, ('d',), None), + (special.i1, cython_special.i1, ('d',), None), + (special.i1e, cython_special.i1e, ('d',), None), + (special.inv_boxcox, cython_special.inv_boxcox, ('dd',), None), + (special.inv_boxcox1p, cython_special.inv_boxcox1p, ('dd',), None), + (special.it2i0k0, cython_special._it2i0k0_pywrap, ('d',), None), + (special.it2j0y0, cython_special._it2j0y0_pywrap, ('d',), None), + (special.it2struve0, cython_special.it2struve0, ('d',), None), + (special.itairy, cython_special._itairy_pywrap, ('d',), None), + (special.iti0k0, cython_special._iti0k0_pywrap, ('d',), None), + (special.itj0y0, cython_special._itj0y0_pywrap, ('d',), None), + (special.itmodstruve0, cython_special.itmodstruve0, ('d',), None), + (special.itstruve0, cython_special.itstruve0, ('d',), None), + (special.iv, cython_special.iv, ('dd', 'dD'), None), + (special.ive, cython_special.ive, ('dd', 'dD'), None), + (special.j0, cython_special.j0, ('d',), None), + (special.j1, cython_special.j1, ('d',), None), + (special.jv, cython_special.jv, ('dd', 'dD'), None), + (special.jve, cython_special.jve, ('dd', 'dD'), None), + (special.k0, cython_special.k0, ('d',), None), + (special.k0e, cython_special.k0e, ('d',), None), + (special.k1, cython_special.k1, ('d',), None), + (special.k1e, cython_special.k1e, ('d',), None), + (special.kei, cython_special.kei, ('d',), None), + (special.keip, cython_special.keip, ('d',), None), + (special.kelvin, cython_special._kelvin_pywrap, ('d',), None), + (special.ker, cython_special.ker, ('d',), None), + (special.kerp, cython_special.kerp, ('d',), None), + (special.kl_div, cython_special.kl_div, ('dd',), None), + (special.kn, cython_special.kn, ('ld', 'dd'), None), + (special.kolmogi, cython_special.kolmogi, ('d',), None), + (special.kolmogorov, cython_special.kolmogorov, ('d',), None), + (special.kv, cython_special.kv, ('dd', 'dD'), None), + (special.kve, cython_special.kve, ('dd', 'dD'), None), + (special.log1p, cython_special.log1p, ('d', 'D'), None), + (special.log_expit, cython_special.log_expit, ('f', 'd', 'g'), None), + (special.log_ndtr, cython_special.log_ndtr, ('d', 'D'), None), + (special.log_wright_bessel, cython_special.log_wright_bessel, ('ddd',), None), + (special.ndtri_exp, cython_special.ndtri_exp, ('d',), None), + (special.loggamma, cython_special.loggamma, ('D',), None), + (special.logit, cython_special.logit, ('f', 'd', 'g'), None), + (special.lpmv, cython_special.lpmv, ('ddd',), None), + (special.mathieu_a, cython_special.mathieu_a, ('dd',), None), + (special.mathieu_b, cython_special.mathieu_b, ('dd',), None), + (special.mathieu_cem, cython_special._mathieu_cem_pywrap, ('ddd',), None), + (special.mathieu_modcem1, cython_special._mathieu_modcem1_pywrap, ('ddd',), None), + (special.mathieu_modcem2, cython_special._mathieu_modcem2_pywrap, ('ddd',), None), + (special.mathieu_modsem1, cython_special._mathieu_modsem1_pywrap, ('ddd',), None), + (special.mathieu_modsem2, cython_special._mathieu_modsem2_pywrap, ('ddd',), None), + (special.mathieu_sem, cython_special._mathieu_sem_pywrap, ('ddd',), None), + (special.modfresnelm, cython_special._modfresnelm_pywrap, ('d',), None), + (special.modfresnelp, cython_special._modfresnelp_pywrap, ('d',), None), + (special.modstruve, cython_special.modstruve, ('dd',), None), + (special.nbdtr, cython_special.nbdtr, ('lld', 'ddd'), None), + (special.nbdtrc, cython_special.nbdtrc, ('lld', 'ddd'), None), + (special.nbdtri, cython_special.nbdtri, ('lld', 'ddd'), None), + (special.nbdtrik, cython_special.nbdtrik, ('ddd',), None), + (special.nbdtrin, cython_special.nbdtrin, ('ddd',), None), + (special.ncfdtr, cython_special.ncfdtr, ('dddd',), None), + (special.ncfdtri, cython_special.ncfdtri, ('dddd',), None), + (special.ncfdtridfd, cython_special.ncfdtridfd, ('dddd',), None), + (special.ncfdtridfn, cython_special.ncfdtridfn, ('dddd',), None), + (special.ncfdtrinc, cython_special.ncfdtrinc, ('dddd',), None), + (special.nctdtr, cython_special.nctdtr, ('ddd',), None), + (special.nctdtridf, cython_special.nctdtridf, ('ddd',), None), + (special.nctdtrinc, cython_special.nctdtrinc, ('ddd',), None), + (special.nctdtrit, cython_special.nctdtrit, ('ddd',), None), + (special.ndtr, cython_special.ndtr, ('d', 'D'), None), + (special.ndtri, cython_special.ndtri, ('d',), None), + (special.nrdtrimn, cython_special.nrdtrimn, ('ddd',), None), + (special.nrdtrisd, cython_special.nrdtrisd, ('ddd',), None), + (special.obl_ang1, cython_special._obl_ang1_pywrap, ('dddd',), None), + (special.obl_ang1_cv, cython_special._obl_ang1_cv_pywrap, ('ddddd',), None), + (special.obl_cv, cython_special.obl_cv, ('ddd',), None), + (special.obl_rad1, cython_special._obl_rad1_pywrap, ('dddd',), "see gh-6211"), + (special.obl_rad1_cv, cython_special._obl_rad1_cv_pywrap, ('ddddd',), + "see gh-6211"), + (special.obl_rad2, cython_special._obl_rad2_pywrap, ('dddd',), "see gh-6211"), + (special.obl_rad2_cv, cython_special._obl_rad2_cv_pywrap, ('ddddd',), + "see gh-6211"), + (special.pbdv, cython_special._pbdv_pywrap, ('dd',), None), + (special.pbvv, cython_special._pbvv_pywrap, ('dd',), None), + (special.pbwa, cython_special._pbwa_pywrap, ('dd',), None), + (special.pdtr, cython_special.pdtr, ('dd', 'dd'), None), + (special.pdtrc, cython_special.pdtrc, ('dd', 'dd'), None), + (special.pdtri, cython_special.pdtri, ('ld', 'dd'), None), + (special.pdtrik, cython_special.pdtrik, ('dd',), None), + (special.poch, cython_special.poch, ('dd',), None), + (special.powm1, cython_special.powm1, ('dd',), None), + (special.pro_ang1, cython_special._pro_ang1_pywrap, ('dddd',), None), + (special.pro_ang1_cv, cython_special._pro_ang1_cv_pywrap, ('ddddd',), None), + (special.pro_cv, cython_special.pro_cv, ('ddd',), None), + (special.pro_rad1, cython_special._pro_rad1_pywrap, ('dddd',), "see gh-6211"), + (special.pro_rad1_cv, cython_special._pro_rad1_cv_pywrap, ('ddddd',), + "see gh-6211"), + (special.pro_rad2, cython_special._pro_rad2_pywrap, ('dddd',), "see gh-6211"), + (special.pro_rad2_cv, cython_special._pro_rad2_cv_pywrap, ('ddddd',), + "see gh-6211"), + (special.pseudo_huber, cython_special.pseudo_huber, ('dd',), None), + (special.psi, cython_special.psi, ('d', 'D'), None), + (special.radian, cython_special.radian, ('ddd',), None), + (special.rel_entr, cython_special.rel_entr, ('dd',), None), + (special.rgamma, cython_special.rgamma, ('d', 'D'), None), + (special.round, cython_special.round, ('d',), None), + (special.spherical_jn, cython_special.spherical_jn, ('ld', 'ldb', 'lD', 'lDb'), + "Python version supports negative reals; Cython version doesn't - see gh-21629"), + (special.spherical_yn, cython_special.spherical_yn, ('ld', 'ldb', 'lD', 'lDb'), + "Python version supports negative reals; Cython version doesn't - see gh-21629"), + (special.spherical_in, cython_special.spherical_in, ('ld', 'ldb', 'lD', 'lDb'), + "Python version supports negative reals; Cython version doesn't - see gh-21629"), + (special.spherical_kn, cython_special.spherical_kn, ('ld', 'ldb', 'lD', 'lDb'), + "Python version supports negative reals; Cython version doesn't - see gh-21629"), + (special.shichi, cython_special._shichi_pywrap, ('d', 'D'), None), + (special.sici, cython_special._sici_pywrap, ('d', 'D'), None), + (special.sindg, cython_special.sindg, ('d',), None), + (special.smirnov, cython_special.smirnov, ('ld', 'dd'), None), + (special.smirnovi, cython_special.smirnovi, ('ld', 'dd'), None), + (special.spence, cython_special.spence, ('d', 'D'), None), + (special.stdtr, cython_special.stdtr, ('dd',), None), + (special.stdtridf, cython_special.stdtridf, ('dd',), None), + (special.stdtrit, cython_special.stdtrit, ('dd',), None), + (special.struve, cython_special.struve, ('dd',), None), + (special.tandg, cython_special.tandg, ('d',), None), + (special.tklmbda, cython_special.tklmbda, ('dd',), None), + (special.voigt_profile, cython_special.voigt_profile, ('ddd',), None), + (special.wofz, cython_special.wofz, ('D',), None), + (special.wright_bessel, cython_special.wright_bessel, ('ddd',), None), + (special.wrightomega, cython_special.wrightomega, ('D',), None), + (special.xlog1py, cython_special.xlog1py, ('dd', 'DD'), None), + (special.xlogy, cython_special.xlogy, ('dd', 'DD'), None), + (special.y0, cython_special.y0, ('d',), None), + (special.y1, cython_special.y1, ('d',), None), + (special.yn, cython_special.yn, ('ld', 'dd'), None), + (special.yv, cython_special.yv, ('dd', 'dD'), None), + (special.yve, cython_special.yve, ('dd', 'dD'), None), + (special.zetac, cython_special.zetac, ('d',), None), + (special.owens_t, cython_special.owens_t, ('dd',), None) +] + + +IDS = [x[0].__name__ for x in PARAMS] + + +def _generate_test_points(typecodes): + axes = tuple(TEST_POINTS[x] for x in typecodes) + pts = list(product(*axes)) + return pts + + +def test_cython_api_completeness(): + # Check that everything is tested + for name in dir(cython_special): + func = getattr(cython_special, name) + if callable(func) and not name.startswith('_'): + for _, cyfun, _, _ in PARAMS: + if cyfun is func: + break + else: + raise RuntimeError(f"{name} missing from tests!") + + +@pytest.mark.fail_slow(20) +@pytest.mark.parametrize("param", PARAMS, ids=IDS) +def test_cython_api(param): + pyfunc, cyfunc, specializations, knownfailure = param + if knownfailure: + pytest.xfail(reason=knownfailure) + + # Check which parameters are expected to be fused types + max_params = max(len(spec) for spec in specializations) + values = [set() for _ in range(max_params)] + for typecodes in specializations: + for j, v in enumerate(typecodes): + values[j].add(v) + seen = set() + is_fused_code = [False] * len(values) + for j, v in enumerate(values): + vv = tuple(sorted(v)) + if vv in seen: + continue + is_fused_code[j] = (len(v) > 1) + seen.add(vv) + + # Check results + for typecodes in specializations: + # Pick the correct specialized function + signature = [CYTHON_SIGNATURE_MAP[code] + for j, code in enumerate(typecodes) + if is_fused_code[j]] + + if signature: + cy_spec_func = cyfunc[tuple(signature)] + else: + signature = None + cy_spec_func = cyfunc + + # Test it + pts = _generate_test_points(typecodes) + for pt in pts: + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + pyval = pyfunc(*pt) + cyval = cy_spec_func(*pt) + assert_allclose(cyval, pyval, err_msg=f"{pt} {typecodes} {signature}") diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_data.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_data.py new file mode 100644 index 0000000000000000000000000000000000000000..eafcecd6836710726c0248bdaf02542e7efb8223 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_data.py @@ -0,0 +1,679 @@ +import importlib.resources +import warnings + +import numpy as np +import pytest + +from scipy.special import ( + lpmv, lqn, lqmn, eval_legendre, eval_hermite, + eval_laguerre, eval_genlaguerre, binom, cbrt, expm1, log1p, zeta, + jn, jv, jvp, yn, yv, yvp, iv, ivp, kn, kv, kvp, + gamma, gammaln, gammainc, gammaincc, gammaincinv, gammainccinv, digamma, + beta, betainc, betaincinv, poch, + ellipe, ellipeinc, ellipk, ellipkm1, ellipkinc, + elliprc, elliprd, elliprf, elliprg, elliprj, + erf, erfc, erfinv, erfcinv, exp1, expi, expn, + bdtrik, btdtria, btdtrib, chndtr, gdtr, gdtrc, gdtrix, gdtrib, + nbdtrik, pdtrik, owens_t, + mathieu_a, mathieu_b, mathieu_cem, mathieu_sem, mathieu_modcem1, + mathieu_modsem1, mathieu_modcem2, mathieu_modsem2, + ellip_harm, ellip_harm_2, spherical_jn, spherical_yn, wright_bessel +) +from scipy.integrate import IntegrationWarning + +from scipy.special._testutils import FuncData + + +# The npz files are generated, and hence may live in the build dir. We can only +# access them through `importlib.resources`, not an explicit path from `__file__` +_datadir = importlib.resources.files('scipy.special.tests.data') + +_boost_npz = _datadir.joinpath('boost.npz') +with importlib.resources.as_file(_boost_npz) as f: + DATASETS_BOOST = np.load(f) + +_gsl_npz = _datadir.joinpath('gsl.npz') +with importlib.resources.as_file(_gsl_npz) as f: + DATASETS_GSL = np.load(f) + +_local_npz = _datadir.joinpath('local.npz') +with importlib.resources.as_file(_local_npz) as f: + DATASETS_LOCAL = np.load(f) + + +def data(func, dataname, *a, **kw): + kw.setdefault('dataname', dataname) + return FuncData(func, DATASETS_BOOST[dataname], *a, **kw) + + +def data_gsl(func, dataname, *a, **kw): + kw.setdefault('dataname', dataname) + return FuncData(func, DATASETS_GSL[dataname], *a, **kw) + + +def data_local(func, dataname, *a, **kw): + kw.setdefault('dataname', dataname) + return FuncData(func, DATASETS_LOCAL[dataname], *a, **kw) + + +def ellipk_(k): + return ellipk(k*k) + + +def ellipkinc_(f, k): + return ellipkinc(f, k*k) + + +def ellipe_(k): + return ellipe(k*k) + + +def ellipeinc_(f, k): + return ellipeinc(f, k*k) + + +def zeta_(x): + return zeta(x, 1.) + + +def assoc_legendre_p_boost_(nu, mu, x): + # the boost test data is for integer orders only + return lpmv(mu, nu.astype(int), x) + +def legendre_p_via_assoc_(nu, x): + return lpmv(0, nu, x) + +def lqn_(n, x): + return lqn(n.astype('l'), x)[0][-1] + +def legendre_q_via_lqmn(n, x): + return lqmn(0, n, x)[0][0,-1] + +def mathieu_ce_rad(m, q, x): + return mathieu_cem(m, q, x*180/np.pi)[0] + + +def mathieu_se_rad(m, q, x): + return mathieu_sem(m, q, x*180/np.pi)[0] + + +def mathieu_mc1_scaled(m, q, x): + # GSL follows a different normalization. + # We follow Abramowitz & Stegun, they apparently something else. + return mathieu_modcem1(m, q, x)[0] * np.sqrt(np.pi/2) + + +def mathieu_ms1_scaled(m, q, x): + return mathieu_modsem1(m, q, x)[0] * np.sqrt(np.pi/2) + + +def mathieu_mc2_scaled(m, q, x): + return mathieu_modcem2(m, q, x)[0] * np.sqrt(np.pi/2) + + +def mathieu_ms2_scaled(m, q, x): + return mathieu_modsem2(m, q, x)[0] * np.sqrt(np.pi/2) + +def eval_legendre_ld(n, x): + return eval_legendre(n.astype('l'), x) + +def eval_legendre_dd(n, x): + return eval_legendre(n.astype('d'), x) + +def eval_hermite_ld(n, x): + return eval_hermite(n.astype('l'), x) + +def eval_laguerre_ld(n, x): + return eval_laguerre(n.astype('l'), x) + +def eval_laguerre_dd(n, x): + return eval_laguerre(n.astype('d'), x) + +def eval_genlaguerre_ldd(n, a, x): + return eval_genlaguerre(n.astype('l'), a, x) + +def eval_genlaguerre_ddd(n, a, x): + return eval_genlaguerre(n.astype('d'), a, x) + +def bdtrik_comp(y, n, p): + return bdtrik(1-y, n, p) + +def btdtria_comp(p, b, x): + return btdtria(1-p, b, x) + +def btdtrib_comp(a, p, x): + return btdtrib(a, 1-p, x) + +def gdtr_(p, x): + return gdtr(1.0, p, x) + +def gdtrc_(p, x): + return gdtrc(1.0, p, x) + +def gdtrix_(b, p): + return gdtrix(1.0, b, p) + +def gdtrix_comp(b, p): + return gdtrix(1.0, b, 1-p) + +def gdtrib_(p, x): + return gdtrib(1.0, p, x) + +def gdtrib_comp(p, x): + return gdtrib(1.0, 1-p, x) + +def nbdtrik_comp(y, n, p): + return nbdtrik(1-y, n, p) + +def pdtrik_comp(p, m): + return pdtrik(1-p, m) + +def poch_(z, m): + return 1.0 / poch(z, m) + +def poch_minus(z, m): + return 1.0 / poch(z, -m) + +def spherical_jn_(n, x): + return spherical_jn(n.astype('l'), x) + +def spherical_yn_(n, x): + return spherical_yn(n.astype('l'), x) + +def cexpm1(x, y): + z = expm1(x + 1j*y) + return z.real, z.imag + +def clog1p(x, y): + z = log1p(x + 1j*y) + return z.real, z.imag + + +BOOST_TESTS = [ + data(assoc_legendre_p_boost_, 'assoc_legendre_p_ipp-assoc_legendre_p', + (0,1,2), 3, rtol=1e-11), + + data(legendre_p_via_assoc_, 'legendre_p_ipp-legendre_p', + (0,1), 2, rtol=1e-11), + data(legendre_p_via_assoc_, 'legendre_p_large_ipp-legendre_p_large', + (0,1), 2, rtol=9.6e-14), + data(eval_legendre_ld, 'legendre_p_ipp-legendre_p', + (0,1), 2, rtol=6e-14), + data(eval_legendre_ld, 'legendre_p_large_ipp-legendre_p_large', + (0,1), 2, rtol=2e-13), + data(eval_legendre_dd, 'legendre_p_ipp-legendre_p', + (0,1), 2, rtol=2e-14), + data(eval_legendre_dd, 'legendre_p_large_ipp-legendre_p_large', + (0,1), 2, rtol=2e-13), + + data(lqn_, 'legendre_p_ipp-legendre_p', + (0,1), 3, rtol=2e-14, vectorized=False), + data(lqn_, 'legendre_p_large_ipp-legendre_p_large', + (0,1), 3, rtol=2e-12, vectorized=False), + data(legendre_q_via_lqmn, 'legendre_p_ipp-legendre_p', + (0,1), 3, rtol=2e-14, vectorized=False), + data(legendre_q_via_lqmn, 'legendre_p_large_ipp-legendre_p_large', + (0,1), 3, rtol=2e-12, vectorized=False), + + data(beta, 'beta_exp_data_ipp-beta_exp_data', + (0,1), 2, rtol=1e-13), + data(beta, 'beta_exp_data_ipp-beta_exp_data', + (0,1), 2, rtol=1e-13), + data(beta, 'beta_med_data_ipp-beta_med_data', + (0,1), 2, rtol=5e-13), + + data(betainc, 'ibeta_small_data_ipp-ibeta_small_data', + (0,1,2), 5, rtol=6e-15), + data(betainc, 'ibeta_data_ipp-ibeta_data', + (0,1,2), 5, rtol=5e-13), + data(betainc, 'ibeta_int_data_ipp-ibeta_int_data', + (0,1,2), 5, rtol=2e-14), + data(betainc, 'ibeta_large_data_ipp-ibeta_large_data', + (0,1,2), 5, rtol=4e-10), + + data(betaincinv, 'ibeta_inv_data_ipp-ibeta_inv_data', + (0,1,2), 3, rtol=1e-5), + + data(btdtria, 'ibeta_inva_data_ipp-ibeta_inva_data', + (2,0,1), 3, rtol=5e-9), + data(btdtria_comp, 'ibeta_inva_data_ipp-ibeta_inva_data', + (2,0,1), 4, rtol=5e-9), + + data(btdtrib, 'ibeta_inva_data_ipp-ibeta_inva_data', + (0,2,1), 5, rtol=5e-9), + data(btdtrib_comp, 'ibeta_inva_data_ipp-ibeta_inva_data', + (0,2,1), 6, rtol=5e-9), + + data(binom, 'binomial_data_ipp-binomial_data', + (0,1), 2, rtol=1e-13), + data(binom, 'binomial_large_data_ipp-binomial_large_data', + (0,1), 2, rtol=5e-13), + + data(bdtrik, 'binomial_quantile_ipp-binomial_quantile_data', + (2,0,1), 3, rtol=5e-9), + data(bdtrik_comp, 'binomial_quantile_ipp-binomial_quantile_data', + (2,0,1), 4, rtol=5e-9), + + data(nbdtrik, 'negative_binomial_quantile_ipp-negative_binomial_quantile_data', + (2,0,1), 3, rtol=4e-9), + data(nbdtrik_comp, + 'negative_binomial_quantile_ipp-negative_binomial_quantile_data', + (2,0,1), 4, rtol=4e-9), + + data(pdtrik, 'poisson_quantile_ipp-poisson_quantile_data', + (1,0), 2, rtol=3e-9), + data(pdtrik_comp, 'poisson_quantile_ipp-poisson_quantile_data', + (1,0), 3, rtol=4e-9), + + data(cbrt, 'cbrt_data_ipp-cbrt_data', 1, 0), + + data(digamma, 'digamma_data_ipp-digamma_data', 0, 1), + data(digamma, 'digamma_data_ipp-digamma_data', 0j, 1), + data(digamma, 'digamma_neg_data_ipp-digamma_neg_data', 0, 1, rtol=2e-13), + data(digamma, 'digamma_neg_data_ipp-digamma_neg_data', 0j, 1, rtol=1e-13), + data(digamma, 'digamma_root_data_ipp-digamma_root_data', 0, 1, rtol=1e-15), + data(digamma, 'digamma_root_data_ipp-digamma_root_data', 0j, 1, rtol=1e-15), + data(digamma, 'digamma_small_data_ipp-digamma_small_data', 0, 1, rtol=1e-15), + data(digamma, 'digamma_small_data_ipp-digamma_small_data', 0j, 1, rtol=1e-14), + + data(ellipk_, 'ellint_k_data_ipp-ellint_k_data', 0, 1), + data(ellipkinc_, 'ellint_f_data_ipp-ellint_f_data', (0,1), 2, rtol=1e-14), + data(ellipe_, 'ellint_e_data_ipp-ellint_e_data', 0, 1), + data(ellipeinc_, 'ellint_e2_data_ipp-ellint_e2_data', (0,1), 2, rtol=1e-14), + + data(erf, 'erf_data_ipp-erf_data', 0, 1), + data(erf, 'erf_data_ipp-erf_data', 0j, 1, rtol=1e-13), + data(erfc, 'erf_data_ipp-erf_data', 0, 2, rtol=6e-15), + data(erf, 'erf_large_data_ipp-erf_large_data', 0, 1), + data(erf, 'erf_large_data_ipp-erf_large_data', 0j, 1), + data(erfc, 'erf_large_data_ipp-erf_large_data', 0, 2, rtol=4e-14), + data(erf, 'erf_small_data_ipp-erf_small_data', 0, 1), + data(erf, 'erf_small_data_ipp-erf_small_data', 0j, 1, rtol=1e-13), + data(erfc, 'erf_small_data_ipp-erf_small_data', 0, 2), + + data(erfinv, 'erf_inv_data_ipp-erf_inv_data', 0, 1), + data(erfcinv, 'erfc_inv_data_ipp-erfc_inv_data', 0, 1), + data(erfcinv, 'erfc_inv_big_data_ipp-erfc_inv_big_data', 0, 1, + param_filter=(lambda s: s > 0)), + + data(exp1, 'expint_1_data_ipp-expint_1_data', 1, 2, rtol=1e-13), + data(exp1, 'expint_1_data_ipp-expint_1_data', 1j, 2, rtol=5e-9), + data(expi, 'expinti_data_ipp-expinti_data', 0, 1, rtol=1e-13), + data(expi, 'expinti_data_double_ipp-expinti_data_double', 0, 1, rtol=1e-13), + data(expi, 'expinti_data_long_ipp-expinti_data_long', 0, 1), + + data(expn, 'expint_small_data_ipp-expint_small_data', (0,1), 2), + data(expn, 'expint_data_ipp-expint_data', (0,1), 2, rtol=1e-14), + + data(gamma, 'test_gamma_data_ipp-near_0', 0, 1), + data(gamma, 'test_gamma_data_ipp-near_1', 0, 1), + data(gamma, 'test_gamma_data_ipp-near_2', 0, 1), + data(gamma, 'test_gamma_data_ipp-near_m10', 0, 1), + data(gamma, 'test_gamma_data_ipp-near_m55', 0, 1, rtol=7e-12), + data(gamma, 'test_gamma_data_ipp-factorials', 0, 1, rtol=4e-14), + data(gamma, 'test_gamma_data_ipp-near_0', 0j, 1, rtol=2e-9), + data(gamma, 'test_gamma_data_ipp-near_1', 0j, 1, rtol=2e-9), + data(gamma, 'test_gamma_data_ipp-near_2', 0j, 1, rtol=2e-9), + data(gamma, 'test_gamma_data_ipp-near_m10', 0j, 1, rtol=2e-9), + data(gamma, 'test_gamma_data_ipp-near_m55', 0j, 1, rtol=2e-9), + data(gamma, 'test_gamma_data_ipp-factorials', 0j, 1, rtol=2e-13), + data(gammaln, 'test_gamma_data_ipp-near_0', 0, 2, rtol=5e-11), + data(gammaln, 'test_gamma_data_ipp-near_1', 0, 2, rtol=5e-11), + data(gammaln, 'test_gamma_data_ipp-near_2', 0, 2, rtol=2e-10), + data(gammaln, 'test_gamma_data_ipp-near_m10', 0, 2, rtol=5e-11), + data(gammaln, 'test_gamma_data_ipp-near_m55', 0, 2, rtol=5e-11), + data(gammaln, 'test_gamma_data_ipp-factorials', 0, 2), + + data(gammainc, 'igamma_small_data_ipp-igamma_small_data', (0,1), 5, rtol=5e-15), + data(gammainc, 'igamma_med_data_ipp-igamma_med_data', (0,1), 5, rtol=2e-13), + data(gammainc, 'igamma_int_data_ipp-igamma_int_data', (0,1), 5, rtol=2e-13), + data(gammainc, 'igamma_big_data_ipp-igamma_big_data', (0,1), 5, rtol=1e-12), + + data(gdtr_, 'igamma_small_data_ipp-igamma_small_data', (0,1), 5, rtol=1e-13), + data(gdtr_, 'igamma_med_data_ipp-igamma_med_data', (0,1), 5, rtol=2e-13), + data(gdtr_, 'igamma_int_data_ipp-igamma_int_data', (0,1), 5, rtol=2e-13), + data(gdtr_, 'igamma_big_data_ipp-igamma_big_data', (0,1), 5, rtol=2e-9), + + data(gammaincc, 'igamma_small_data_ipp-igamma_small_data', + (0,1), 3, rtol=1e-13), + data(gammaincc, 'igamma_med_data_ipp-igamma_med_data', + (0,1), 3, rtol=2e-13), + data(gammaincc, 'igamma_int_data_ipp-igamma_int_data', + (0,1), 3, rtol=4e-14), + data(gammaincc, 'igamma_big_data_ipp-igamma_big_data', + (0,1), 3, rtol=1e-11), + + data(gdtrc_, 'igamma_small_data_ipp-igamma_small_data', (0,1), 3, rtol=1e-13), + data(gdtrc_, 'igamma_med_data_ipp-igamma_med_data', (0,1), 3, rtol=2e-13), + data(gdtrc_, 'igamma_int_data_ipp-igamma_int_data', (0,1), 3, rtol=4e-14), + data(gdtrc_, 'igamma_big_data_ipp-igamma_big_data', (0,1), 3, rtol=1e-11), + + data(gdtrib_, 'igamma_inva_data_ipp-igamma_inva_data', (1,0), 2, rtol=5e-9), + data(gdtrib_comp, 'igamma_inva_data_ipp-igamma_inva_data', (1,0), 3, rtol=5e-9), + + data(poch_, 'tgamma_delta_ratio_data_ipp-tgamma_delta_ratio_data', + (0,1), 2, rtol=2e-13), + data(poch_, 'tgamma_delta_ratio_int_ipp-tgamma_delta_ratio_int', + (0,1), 2,), + data(poch_, 'tgamma_delta_ratio_int2_ipp-tgamma_delta_ratio_int2', + (0,1), 2,), + data(poch_minus, 'tgamma_delta_ratio_data_ipp-tgamma_delta_ratio_data', + (0,1), 3, rtol=2e-13), + data(poch_minus, 'tgamma_delta_ratio_int_ipp-tgamma_delta_ratio_int', + (0,1), 3), + data(poch_minus, 'tgamma_delta_ratio_int2_ipp-tgamma_delta_ratio_int2', + (0,1), 3), + + data(eval_hermite_ld, 'hermite_ipp-hermite', + (0,1), 2, rtol=2e-14), + + data(eval_laguerre_ld, 'laguerre2_ipp-laguerre2', + (0,1), 2, rtol=7e-12), + data(eval_laguerre_dd, 'laguerre2_ipp-laguerre2', + (0,1), 2, knownfailure='hyp2f1 insufficiently accurate.'), + data(eval_genlaguerre_ldd, 'laguerre3_ipp-laguerre3', + (0,1,2), 3, rtol=2e-13), + data(eval_genlaguerre_ddd, 'laguerre3_ipp-laguerre3', + (0,1,2), 3, knownfailure='hyp2f1 insufficiently accurate.'), + + data(log1p, 'log1p_expm1_data_ipp-log1p_expm1_data', 0, 1), + data(expm1, 'log1p_expm1_data_ipp-log1p_expm1_data', 0, 2), + + data(iv, 'bessel_i_data_ipp-bessel_i_data', + (0,1), 2, rtol=1e-12), + data(iv, 'bessel_i_data_ipp-bessel_i_data', + (0,1j), 2, rtol=2e-10, atol=1e-306), + data(iv, 'bessel_i_int_data_ipp-bessel_i_int_data', + (0,1), 2, rtol=1e-9), + data(iv, 'bessel_i_int_data_ipp-bessel_i_int_data', + (0,1j), 2, rtol=2e-10), + + data(ivp, 'bessel_i_prime_int_data_ipp-bessel_i_prime_int_data', + (0,1), 2, rtol=1.2e-13), + data(ivp, 'bessel_i_prime_int_data_ipp-bessel_i_prime_int_data', + (0,1j), 2, rtol=1.2e-13, atol=1e-300), + + data(jn, 'bessel_j_int_data_ipp-bessel_j_int_data', (0,1), 2, rtol=1e-12), + data(jn, 'bessel_j_int_data_ipp-bessel_j_int_data', (0,1j), 2, rtol=1e-12), + data(jn, 'bessel_j_large_data_ipp-bessel_j_large_data', (0,1), 2, rtol=6e-11), + data(jn, 'bessel_j_large_data_ipp-bessel_j_large_data', (0,1j), 2, rtol=6e-11), + + data(jv, 'bessel_j_int_data_ipp-bessel_j_int_data', (0,1), 2, rtol=1e-12), + data(jv, 'bessel_j_int_data_ipp-bessel_j_int_data', (0,1j), 2, rtol=1e-12), + data(jv, 'bessel_j_data_ipp-bessel_j_data', (0,1), 2, rtol=1e-12), + data(jv, 'bessel_j_data_ipp-bessel_j_data', (0,1j), 2, rtol=1e-12), + + data(jvp, 'bessel_j_prime_int_data_ipp-bessel_j_prime_int_data', + (0,1), 2, rtol=1e-13), + data(jvp, 'bessel_j_prime_int_data_ipp-bessel_j_prime_int_data', + (0,1j), 2, rtol=1e-13), + data(jvp, 'bessel_j_prime_large_data_ipp-bessel_j_prime_large_data', + (0,1), 2, rtol=1e-11), + data(jvp, 'bessel_j_prime_large_data_ipp-bessel_j_prime_large_data', + (0,1j), 2, rtol=2e-11), + + data(kn, 'bessel_k_int_data_ipp-bessel_k_int_data', (0,1), 2, rtol=1e-12), + + data(kv, 'bessel_k_int_data_ipp-bessel_k_int_data', (0,1), 2, rtol=1e-12), + data(kv, 'bessel_k_int_data_ipp-bessel_k_int_data', (0,1j), 2, rtol=1e-12), + data(kv, 'bessel_k_data_ipp-bessel_k_data', (0,1), 2, rtol=1e-12), + data(kv, 'bessel_k_data_ipp-bessel_k_data', (0,1j), 2, rtol=1e-12), + + data(kvp, 'bessel_k_prime_int_data_ipp-bessel_k_prime_int_data', + (0,1), 2, rtol=3e-14), + data(kvp, 'bessel_k_prime_int_data_ipp-bessel_k_prime_int_data', + (0,1j), 2, rtol=3e-14), + data(kvp, 'bessel_k_prime_data_ipp-bessel_k_prime_data', (0,1), 2, rtol=7e-14), + data(kvp, 'bessel_k_prime_data_ipp-bessel_k_prime_data', (0,1j), 2, rtol=7e-14), + + data(yn, 'bessel_y01_data_ipp-bessel_y01_data', (0,1), 2, rtol=1e-12), + data(yn, 'bessel_yn_data_ipp-bessel_yn_data', (0,1), 2, rtol=1e-12), + + data(yv, 'bessel_yn_data_ipp-bessel_yn_data', (0,1), 2, rtol=1e-12), + data(yv, 'bessel_yn_data_ipp-bessel_yn_data', (0,1j), 2, rtol=1e-12), + data(yv, 'bessel_yv_data_ipp-bessel_yv_data', (0,1), 2, rtol=1e-10), + data(yv, 'bessel_yv_data_ipp-bessel_yv_data', (0,1j), 2, rtol=1e-10), + + data(yvp, 'bessel_yv_prime_data_ipp-bessel_yv_prime_data', + (0, 1), 2, rtol=4e-9), + data(yvp, 'bessel_yv_prime_data_ipp-bessel_yv_prime_data', + (0, 1j), 2, rtol=4e-9), + + data(zeta_, 'zeta_data_ipp-zeta_data', 0, 1, + param_filter=(lambda s: s > 1)), + data(zeta_, 'zeta_neg_data_ipp-zeta_neg_data', 0, 1, + param_filter=(lambda s: s > 1)), + data(zeta_, 'zeta_1_up_data_ipp-zeta_1_up_data', 0, 1, + param_filter=(lambda s: s > 1)), + data(zeta_, 'zeta_1_below_data_ipp-zeta_1_below_data', 0, 1, + param_filter=(lambda s: s > 1)), + + data(gammaincinv, 'gamma_inv_small_data_ipp-gamma_inv_small_data', + (0,1), 2, rtol=1e-11), + data(gammaincinv, 'gamma_inv_data_ipp-gamma_inv_data', + (0,1), 2, rtol=1e-14), + data(gammaincinv, 'gamma_inv_big_data_ipp-gamma_inv_big_data', + (0,1), 2, rtol=1e-11), + + data(gammainccinv, 'gamma_inv_small_data_ipp-gamma_inv_small_data', + (0,1), 3, rtol=1e-12), + data(gammainccinv, 'gamma_inv_data_ipp-gamma_inv_data', + (0,1), 3, rtol=1e-14), + data(gammainccinv, 'gamma_inv_big_data_ipp-gamma_inv_big_data', + (0,1), 3, rtol=1e-14), + + data(gdtrix_, 'gamma_inv_small_data_ipp-gamma_inv_small_data', + (0,1), 2, rtol=3e-13, knownfailure='gdtrix unflow some points'), + data(gdtrix_, 'gamma_inv_data_ipp-gamma_inv_data', + (0,1), 2, rtol=3e-15), + data(gdtrix_, 'gamma_inv_big_data_ipp-gamma_inv_big_data', + (0,1), 2), + data(gdtrix_comp, 'gamma_inv_small_data_ipp-gamma_inv_small_data', + (0,1), 2, knownfailure='gdtrix bad some points'), + data(gdtrix_comp, 'gamma_inv_data_ipp-gamma_inv_data', + (0,1), 3, rtol=6e-15), + data(gdtrix_comp, 'gamma_inv_big_data_ipp-gamma_inv_big_data', + (0,1), 3), + + data(chndtr, 'nccs_ipp-nccs', + (2,0,1), 3, rtol=3e-5), + data(chndtr, 'nccs_big_ipp-nccs_big', + (2,0,1), 3, rtol=5e-4, knownfailure='chndtr inaccurate some points'), + + data(spherical_jn_, 'sph_bessel_data_ipp-sph_bessel_data', + (0,1), 2, rtol=1e-13), + data(spherical_yn_, 'sph_neumann_data_ipp-sph_neumann_data', + (0,1), 2, rtol=8e-15), + + data(owens_t, 'owens_t_ipp-owens_t', + (0, 1), 2, rtol=5e-14), + data(owens_t, 'owens_t_large_data_ipp-owens_t_large_data', + (0, 1), 2, rtol=8e-12), + + # -- test data exists in boost but is not used in scipy -- + + # ibeta_derivative_data_ipp/ibeta_derivative_data.txt + # ibeta_derivative_int_data_ipp/ibeta_derivative_int_data.txt + # ibeta_derivative_large_data_ipp/ibeta_derivative_large_data.txt + # ibeta_derivative_small_data_ipp/ibeta_derivative_small_data.txt + + # bessel_y01_prime_data_ipp/bessel_y01_prime_data.txt + # bessel_yn_prime_data_ipp/bessel_yn_prime_data.txt + # sph_bessel_prime_data_ipp/sph_bessel_prime_data.txt + # sph_neumann_prime_data_ipp/sph_neumann_prime_data.txt + + # ellint_d2_data_ipp/ellint_d2_data.txt + # ellint_d_data_ipp/ellint_d_data.txt + # ellint_pi2_data_ipp/ellint_pi2_data.txt + # ellint_pi3_data_ipp/ellint_pi3_data.txt + # ellint_pi3_large_data_ipp/ellint_pi3_large_data.txt + data(elliprc, 'ellint_rc_data_ipp-ellint_rc_data', (0, 1), 2, + rtol=5e-16), + data(elliprd, 'ellint_rd_data_ipp-ellint_rd_data', (0, 1, 2), 3, + rtol=5e-16), + data(elliprd, 'ellint_rd_0xy_ipp-ellint_rd_0xy', (0, 1, 2), 3, + rtol=5e-16), + data(elliprd, 'ellint_rd_0yy_ipp-ellint_rd_0yy', (0, 1, 2), 3, + rtol=5e-16), + data(elliprd, 'ellint_rd_xxx_ipp-ellint_rd_xxx', (0, 1, 2), 3, + rtol=5e-16), + # Some of the following rtol for elliprd may be larger than 5e-16 to + # work around some hard cases in the Boost test where we get slightly + # larger error than the ideal bound when the x (==y) input is close to + # zero. + # Also the accuracy on 32-bit builds with g++ may suffer from excess + # loss of precision; see GCC bugzilla 323 + # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=323 + data(elliprd, 'ellint_rd_xxz_ipp-ellint_rd_xxz', (0, 1, 2), 3, + rtol=6.5e-16), + data(elliprd, 'ellint_rd_xyy_ipp-ellint_rd_xyy', (0, 1, 2), 3, + rtol=6e-16), + data(elliprf, 'ellint_rf_data_ipp-ellint_rf_data', (0, 1, 2), 3, + rtol=5e-16), + data(elliprf, 'ellint_rf_xxx_ipp-ellint_rf_xxx', (0, 1, 2), 3, + rtol=5e-16), + data(elliprf, 'ellint_rf_xyy_ipp-ellint_rf_xyy', (0, 1, 2), 3, + rtol=5e-16), + data(elliprf, 'ellint_rf_xy0_ipp-ellint_rf_xy0', (0, 1, 2), 3, + rtol=5e-16), + data(elliprf, 'ellint_rf_0yy_ipp-ellint_rf_0yy', (0, 1, 2), 3, + rtol=5e-16), + # The accuracy of R_G is primarily limited by R_D that is used + # internally. It is generally worse than R_D. Notice that we increased + # the rtol for R_G here. The cases with duplicate arguments are + # slightly less likely to be unbalanced (at least two arguments are + # already balanced) so the error bound is slightly better. Again, + # precision with g++ 32-bit is even worse. + data(elliprg, 'ellint_rg_ipp-ellint_rg', (0, 1, 2), 3, + rtol=8.0e-16), + data(elliprg, 'ellint_rg_xxx_ipp-ellint_rg_xxx', (0, 1, 2), 3, + rtol=6e-16), + data(elliprg, 'ellint_rg_xyy_ipp-ellint_rg_xyy', (0, 1, 2), 3, + rtol=7.5e-16), + data(elliprg, 'ellint_rg_xy0_ipp-ellint_rg_xy0', (0, 1, 2), 3, + rtol=5e-16), + data(elliprg, 'ellint_rg_00x_ipp-ellint_rg_00x', (0, 1, 2), 3, + rtol=5e-16), + data(elliprj, 'ellint_rj_data_ipp-ellint_rj_data', (0, 1, 2, 3), 4, + rtol=5e-16, atol=1e-25, + param_filter=(lambda s: s <= 5e-26,)), + # ellint_rc_data_ipp/ellint_rc_data.txt + # ellint_rd_0xy_ipp/ellint_rd_0xy.txt + # ellint_rd_0yy_ipp/ellint_rd_0yy.txt + # ellint_rd_data_ipp/ellint_rd_data.txt + # ellint_rd_xxx_ipp/ellint_rd_xxx.txt + # ellint_rd_xxz_ipp/ellint_rd_xxz.txt + # ellint_rd_xyy_ipp/ellint_rd_xyy.txt + # ellint_rf_0yy_ipp/ellint_rf_0yy.txt + # ellint_rf_data_ipp/ellint_rf_data.txt + # ellint_rf_xxx_ipp/ellint_rf_xxx.txt + # ellint_rf_xy0_ipp/ellint_rf_xy0.txt + # ellint_rf_xyy_ipp/ellint_rf_xyy.txt + # ellint_rg_00x_ipp/ellint_rg_00x.txt + # ellint_rg_ipp/ellint_rg.txt + # ellint_rg_xxx_ipp/ellint_rg_xxx.txt + # ellint_rg_xy0_ipp/ellint_rg_xy0.txt + # ellint_rg_xyy_ipp/ellint_rg_xyy.txt + # ellint_rj_data_ipp/ellint_rj_data.txt + # ellint_rj_e2_ipp/ellint_rj_e2.txt + # ellint_rj_e3_ipp/ellint_rj_e3.txt + # ellint_rj_e4_ipp/ellint_rj_e4.txt + # ellint_rj_zp_ipp/ellint_rj_zp.txt + + # jacobi_elliptic_ipp/jacobi_elliptic.txt + # jacobi_elliptic_small_ipp/jacobi_elliptic_small.txt + # jacobi_large_phi_ipp/jacobi_large_phi.txt + # jacobi_near_1_ipp/jacobi_near_1.txt + # jacobi_zeta_big_phi_ipp/jacobi_zeta_big_phi.txt + # jacobi_zeta_data_ipp/jacobi_zeta_data.txt + + # heuman_lambda_data_ipp/heuman_lambda_data.txt + + # hypergeometric_0F2_ipp/hypergeometric_0F2.txt + # hypergeometric_1F1_big_ipp/hypergeometric_1F1_big.txt + # hypergeometric_1F1_ipp/hypergeometric_1F1.txt + # hypergeometric_1F1_small_random_ipp/hypergeometric_1F1_small_random.txt + # hypergeometric_1F2_ipp/hypergeometric_1F2.txt + # hypergeometric_1f1_large_regularized_ipp/hypergeometric_1f1_large_regularized.txt # noqa: E501 + # hypergeometric_1f1_log_large_unsolved_ipp/hypergeometric_1f1_log_large_unsolved.txt # noqa: E501 + # hypergeometric_2F0_half_ipp/hypergeometric_2F0_half.txt + # hypergeometric_2F0_integer_a2_ipp/hypergeometric_2F0_integer_a2.txt + # hypergeometric_2F0_ipp/hypergeometric_2F0.txt + # hypergeometric_2F0_large_z_ipp/hypergeometric_2F0_large_z.txt + # hypergeometric_2F1_ipp/hypergeometric_2F1.txt + # hypergeometric_2F2_ipp/hypergeometric_2F2.txt + + # ncbeta_big_ipp/ncbeta_big.txt + # nct_small_delta_ipp/nct_small_delta.txt + # nct_asym_ipp/nct_asym.txt + # ncbeta_ipp/ncbeta.txt + + # powm1_data_ipp/powm1_big_data.txt + # powm1_sqrtp1m1_test_hpp/sqrtp1m1_data.txt + + # sinc_data_ipp/sinc_data.txt + + # test_gamma_data_ipp/gammap1m1_data.txt + # tgamma_ratio_data_ipp/tgamma_ratio_data.txt + + # trig_data_ipp/trig_data.txt + # trig_data2_ipp/trig_data2.txt +] + + +@pytest.mark.parametrize('test', BOOST_TESTS, ids=repr) +def test_boost(test): + _test_factory(test) + + +GSL_TESTS = [ + data_gsl(mathieu_a, 'mathieu_ab', (0, 1), 2, rtol=1e-13, atol=1e-13), + data_gsl(mathieu_b, 'mathieu_ab', (0, 1), 3, rtol=1e-13, atol=1e-13), + + # Also the GSL output has limited accuracy... + data_gsl(mathieu_ce_rad, 'mathieu_ce_se', (0, 1, 2), 3, rtol=1e-7, atol=1e-13), + data_gsl(mathieu_se_rad, 'mathieu_ce_se', (0, 1, 2), 4, rtol=1e-7, atol=1e-13), + + data_gsl(mathieu_mc1_scaled, 'mathieu_mc_ms', + (0, 1, 2), 3, rtol=1e-7, atol=1e-13), + data_gsl(mathieu_ms1_scaled, 'mathieu_mc_ms', + (0, 1, 2), 4, rtol=1e-7, atol=1e-13), + + data_gsl(mathieu_mc2_scaled, 'mathieu_mc_ms', + (0, 1, 2), 5, rtol=1e-7, atol=1e-13), + data_gsl(mathieu_ms2_scaled, 'mathieu_mc_ms', + (0, 1, 2), 6, rtol=1e-7, atol=1e-13), +] + + +@pytest.mark.parametrize('test', GSL_TESTS, ids=repr) +def test_gsl(test): + _test_factory(test) + + +LOCAL_TESTS = [ + data_local(ellipkinc, 'ellipkinc_neg_m', (0, 1), 2), + data_local(ellipkm1, 'ellipkm1', 0, 1), + data_local(ellipeinc, 'ellipeinc_neg_m', (0, 1), 2), + data_local(clog1p, 'log1p_expm1_complex', (0,1), (2,3), rtol=1e-14), + data_local(cexpm1, 'log1p_expm1_complex', (0,1), (4,5), rtol=1e-14), + data_local(gammainc, 'gammainc', (0, 1), 2, rtol=1e-12), + data_local(gammaincc, 'gammaincc', (0, 1), 2, rtol=1e-11), + data_local(ellip_harm_2, 'ellip',(0, 1, 2, 3, 4), 6, rtol=1e-10, atol=1e-13), + data_local(ellip_harm, 'ellip',(0, 1, 2, 3, 4), 5, rtol=1e-10, atol=1e-13), + data_local(wright_bessel, 'wright_bessel', (0, 1, 2), 3, rtol=1e-11), +] + + +@pytest.mark.parametrize('test', LOCAL_TESTS, ids=repr) +def test_local(test): + _test_factory(test) + + +def _test_factory(test, dtype=np.float64): + """Boost test""" + with warnings.catch_warnings(): + msg = "The occurrence of roundoff error is detected" + warnings.filterwarnings("ignore", msg, IntegrationWarning) + with np.errstate(all='ignore'): + test.check(dtype=dtype) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_dd.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_dd.py new file mode 100644 index 0000000000000000000000000000000000000000..6da2c8ddddd7341cbb777216e9f2f3cce536a51e --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_dd.py @@ -0,0 +1,42 @@ +# Tests for a few of the "double-double" C++ functions defined in +# special/cephes/dd_real.h. Prior to gh-20390 which translated these +# functions from C to C++, there were test cases for _dd_expm1. It +# was determined that this function is not used anywhere internally +# in SciPy, so this function was not translated. + + +import pytest +from numpy.testing import assert_allclose +from scipy.special._test_internal import _dd_exp, _dd_log + + +# Each tuple in test_data contains: +# (dd_func, xhi, xlo, expected_yhi, expected_ylo) +# The expected values were computed with mpmath, e.g. +# +# import mpmath +# mpmath.mp.dps = 100 +# xhi = 10.0 +# xlo = 0.0 +# x = mpmath.mpf(xhi) + mpmath.mpf(xlo) +# y = mpmath.log(x) +# expected_yhi = float(y) +# expected_ylo = float(y - expected_yhi) +# +test_data = [ + (_dd_exp, -0.3333333333333333, -1.850371707708594e-17, + 0.7165313105737893, -2.0286948382455594e-17), + (_dd_exp, 0.0, 0.0, 1.0, 0.0), + (_dd_exp, 10.0, 0.0, 22026.465794806718, -1.3780134700517372e-12), + (_dd_log, 0.03125, 0.0, -3.4657359027997265, -4.930038229799327e-18), + (_dd_log, 10.0, 0.0, 2.302585092994046, -2.1707562233822494e-16), +] + + +@pytest.mark.parametrize('dd_func, xhi, xlo, expected_yhi, expected_ylo', + test_data) +def test_dd(dd_func, xhi, xlo, expected_yhi, expected_ylo): + yhi, ylo = dd_func(xhi, xlo) + assert yhi == expected_yhi, (f"high double ({yhi}) does not equal the " + f"expected value {expected_yhi}") + assert_allclose(ylo, expected_ylo, rtol=5e-15) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_digamma.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_digamma.py new file mode 100644 index 0000000000000000000000000000000000000000..d7f27dc7b71c1ae928b4bdd8bd987df9ca420bab --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_digamma.py @@ -0,0 +1,45 @@ +import numpy as np +from numpy import pi, log, sqrt +from numpy.testing import assert_, assert_equal + +from scipy.special._testutils import FuncData +import scipy.special as sc + +# Euler-Mascheroni constant +euler = 0.57721566490153286 + + +def test_consistency(): + # Make sure the implementation of digamma for real arguments + # agrees with the implementation of digamma for complex arguments. + + # It's all poles after -1e16 + x = np.r_[-np.logspace(15, -30, 200), np.logspace(-30, 300, 200)] + dataset = np.vstack((x + 0j, sc.digamma(x))).T + FuncData(sc.digamma, dataset, 0, 1, rtol=5e-14, nan_ok=True).check() + + +def test_special_values(): + # Test special values from Gauss's digamma theorem. See + # + # https://en.wikipedia.org/wiki/Digamma_function + + dataset = [ + (1, -euler), + (0.5, -2*log(2) - euler), + (1/3, -pi/(2*sqrt(3)) - 3*log(3)/2 - euler), + (1/4, -pi/2 - 3*log(2) - euler), + (1/6, -pi*sqrt(3)/2 - 2*log(2) - 3*log(3)/2 - euler), + (1/8, + -pi/2 - 4*log(2) - (pi + log(2 + sqrt(2)) - log(2 - sqrt(2)))/sqrt(2) - euler) + ] + + dataset = np.asarray(dataset) + FuncData(sc.digamma, dataset, 0, 1, rtol=1e-14).check() + + +def test_nonfinite(): + pts = [0.0, -0.0, np.inf] + std = [-np.inf, np.inf, np.inf] + assert_equal(sc.digamma(pts), std) + assert_(all(np.isnan(sc.digamma([-np.inf, -1])))) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_ellip_harm.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_ellip_harm.py new file mode 100644 index 0000000000000000000000000000000000000000..68526bc31e7a6caedae7de1cb8f033f9b221062a --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_ellip_harm.py @@ -0,0 +1,288 @@ +# +# Tests for the Ellipsoidal Harmonic Function, +# Distributed under the same license as SciPy itself. +# +import warnings + +import numpy as np +from numpy.testing import assert_equal, assert_allclose, assert_ +from scipy.special._testutils import assert_func_equal +from scipy.special import ellip_harm, ellip_harm_2, ellip_normal +from scipy.integrate import IntegrationWarning +from numpy import sqrt, pi + + +def test_ellip_potential(): + def change_coefficient(lambda1, mu, nu, h2, k2): + x = sqrt(lambda1**2*mu**2*nu**2/(h2*k2)) + y = sqrt((lambda1**2 - h2)*(mu**2 - h2)*(h2 - nu**2)/(h2*(k2 - h2))) + z = sqrt((lambda1**2 - k2)*(k2 - mu**2)*(k2 - nu**2)/(k2*(k2 - h2))) + return x, y, z + + def solid_int_ellip(lambda1, mu, nu, n, p, h2, k2): + return (ellip_harm(h2, k2, n, p, lambda1)*ellip_harm(h2, k2, n, p, mu) + * ellip_harm(h2, k2, n, p, nu)) + + def solid_int_ellip2(lambda1, mu, nu, n, p, h2, k2): + return (ellip_harm_2(h2, k2, n, p, lambda1) + * ellip_harm(h2, k2, n, p, mu)*ellip_harm(h2, k2, n, p, nu)) + + def summation(lambda1, mu1, nu1, lambda2, mu2, nu2, h2, k2): + tol = 1e-8 + sum1 = 0 + for n in range(20): + xsum = 0 + for p in range(1, 2*n+2): + xsum += (4*pi*(solid_int_ellip(lambda2, mu2, nu2, n, p, h2, k2) + * solid_int_ellip2(lambda1, mu1, nu1, n, p, h2, k2)) / + (ellip_normal(h2, k2, n, p)*(2*n + 1))) + if abs(xsum) < 0.1*tol*abs(sum1): + break + sum1 += xsum + return sum1, xsum + + def potential(lambda1, mu1, nu1, lambda2, mu2, nu2, h2, k2): + x1, y1, z1 = change_coefficient(lambda1, mu1, nu1, h2, k2) + x2, y2, z2 = change_coefficient(lambda2, mu2, nu2, h2, k2) + res = sqrt((x2 - x1)**2 + (y2 - y1)**2 + (z2 - z1)**2) + return 1/res + + pts = [ + (120, sqrt(19), 2, 41, sqrt(17), 2, 15, 25), + (120, sqrt(16), 3.2, 21, sqrt(11), 2.9, 11, 20), + ] + + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", "The occurrence of roundoff error", IntegrationWarning) + warnings.filterwarnings( + "ignore", "The maximum number of subdivisions", IntegrationWarning) + + for p in pts: + err_msg = repr(p) + exact = potential(*p) + result, last_term = summation(*p) + assert_allclose(exact, result, atol=0, rtol=1e-8, err_msg=err_msg) + assert_(abs(result - exact) < 10*abs(last_term), err_msg) + + +def test_ellip_norm(): + + def G01(h2, k2): + return 4*pi + + def G11(h2, k2): + return 4*pi*h2*k2/3 + + def G12(h2, k2): + return 4*pi*h2*(k2 - h2)/3 + + def G13(h2, k2): + return 4*pi*k2*(k2 - h2)/3 + + def G22(h2, k2): + res = (2*(h2**4 + k2**4) - 4*h2*k2*(h2**2 + k2**2) + 6*h2**2*k2**2 + + sqrt(h2**2 + k2**2 - h2*k2)*(-2*(h2**3 + k2**3) + 3*h2*k2*(h2 + k2))) + return 16*pi/405*res + + def G21(h2, k2): + res = (2*(h2**4 + k2**4) - 4*h2*k2*(h2**2 + k2**2) + 6*h2**2*k2**2 + + sqrt(h2**2 + k2**2 - h2*k2)*(2*(h2**3 + k2**3) - 3*h2*k2*(h2 + k2))) + return 16*pi/405*res + + def G23(h2, k2): + return 4*pi*h2**2*k2*(k2 - h2)/15 + + def G24(h2, k2): + return 4*pi*h2*k2**2*(k2 - h2)/15 + + def G25(h2, k2): + return 4*pi*h2*k2*(k2 - h2)**2/15 + + def G32(h2, k2): + res = (16*(h2**4 + k2**4) - 36*h2*k2*(h2**2 + k2**2) + 46*h2**2*k2**2 + + sqrt(4*(h2**2 + k2**2) - 7*h2*k2)*(-8*(h2**3 + k2**3) + + 11*h2*k2*(h2 + k2))) + return 16*pi/13125*k2*h2*res + + def G31(h2, k2): + res = (16*(h2**4 + k2**4) - 36*h2*k2*(h2**2 + k2**2) + 46*h2**2*k2**2 + + sqrt(4*(h2**2 + k2**2) - 7*h2*k2)*(8*(h2**3 + k2**3) - + 11*h2*k2*(h2 + k2))) + return 16*pi/13125*h2*k2*res + + def G34(h2, k2): + res = (6*h2**4 + 16*k2**4 - 12*h2**3*k2 - 28*h2*k2**3 + 34*h2**2*k2**2 + + sqrt(h2**2 + 4*k2**2 - h2*k2)*(-6*h2**3 - 8*k2**3 + 9*h2**2*k2 + + 13*h2*k2**2)) + return 16*pi/13125*h2*(k2 - h2)*res + + def G33(h2, k2): + res = (6*h2**4 + 16*k2**4 - 12*h2**3*k2 - 28*h2*k2**3 + 34*h2**2*k2**2 + + sqrt(h2**2 + 4*k2**2 - h2*k2)*(6*h2**3 + 8*k2**3 - 9*h2**2*k2 - + 13*h2*k2**2)) + return 16*pi/13125*h2*(k2 - h2)*res + + def G36(h2, k2): + res = (16*h2**4 + 6*k2**4 - 28*h2**3*k2 - 12*h2*k2**3 + 34*h2**2*k2**2 + + sqrt(4*h2**2 + k2**2 - h2*k2)*(-8*h2**3 - 6*k2**3 + 13*h2**2*k2 + + 9*h2*k2**2)) + return 16*pi/13125*k2*(k2 - h2)*res + + def G35(h2, k2): + res = (16*h2**4 + 6*k2**4 - 28*h2**3*k2 - 12*h2*k2**3 + 34*h2**2*k2**2 + + sqrt(4*h2**2 + k2**2 - h2*k2)*(8*h2**3 + 6*k2**3 - 13*h2**2*k2 - + 9*h2*k2**2)) + return 16*pi/13125*k2*(k2 - h2)*res + + def G37(h2, k2): + return 4*pi*h2**2*k2**2*(k2 - h2)**2/105 + + known_funcs = {(0, 1): G01, (1, 1): G11, (1, 2): G12, (1, 3): G13, + (2, 1): G21, (2, 2): G22, (2, 3): G23, (2, 4): G24, + (2, 5): G25, (3, 1): G31, (3, 2): G32, (3, 3): G33, + (3, 4): G34, (3, 5): G35, (3, 6): G36, (3, 7): G37} + + def _ellip_norm(n, p, h2, k2): + func = known_funcs[n, p] + return func(h2, k2) + _ellip_norm = np.vectorize(_ellip_norm) + + def ellip_normal_known(h2, k2, n, p): + return _ellip_norm(n, p, h2, k2) + + # generate both large and small h2 < k2 pairs + np.random.seed(1234) + h2 = np.random.pareto(0.5, size=1) + k2 = h2 * (1 + np.random.pareto(0.5, size=h2.size)) + + points = [] + for n in range(4): + for p in range(1, 2*n+2): + points.append((h2, k2, np.full(h2.size, n), np.full(h2.size, p))) + points = np.array(points) + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", "The occurrence of roundoff error", IntegrationWarning) + assert_func_equal(ellip_normal, ellip_normal_known, points, rtol=1e-12) + + +def test_ellip_harm_2(): + + def I1(h2, k2, s): + res = (ellip_harm_2(h2, k2, 1, 1, s)/(3 * ellip_harm(h2, k2, 1, 1, s)) + + ellip_harm_2(h2, k2, 1, 2, s)/(3 * ellip_harm(h2, k2, 1, 2, s)) + + ellip_harm_2(h2, k2, 1, 3, s)/(3 * ellip_harm(h2, k2, 1, 3, s))) + return res + + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", "The occurrence of roundoff error", IntegrationWarning) + assert_allclose(I1(5, 8, 10), 1/(10*sqrt((100-5)*(100-8))), + atol=1.5e-7, rtol=0) + + # Values produced by code from arXiv:1204.0267 + assert_allclose(ellip_harm_2(5, 8, 2, 1, 10), 0.00108056853382, + atol=1.5e-7, rtol=0) + assert_allclose(ellip_harm_2(5, 8, 2, 2, 10), 0.00105820513809, + atol=1.5e-7, rtol=0) + assert_allclose(ellip_harm_2(5, 8, 2, 3, 10), 0.00106058384743, + atol=1.5e-7, rtol=0) + assert_allclose(ellip_harm_2(5, 8, 2, 4, 10), 0.00106774492306, + atol=1.5e-7, rtol=0) + assert_allclose(ellip_harm_2(5, 8, 2, 5, 10), 0.00107976356454, + atol=1.5e-7, rtol=0) + + +def test_ellip_harm(): + + def E01(h2, k2, s): + return 1 + + def E11(h2, k2, s): + return s + + def E12(h2, k2, s): + return sqrt(abs(s*s - h2)) + + def E13(h2, k2, s): + return sqrt(abs(s*s - k2)) + + def E21(h2, k2, s): + return s*s - 1/3*((h2 + k2) + sqrt(abs((h2 + k2)*(h2 + k2)-3*h2*k2))) + + def E22(h2, k2, s): + return s*s - 1/3*((h2 + k2) - sqrt(abs((h2 + k2)*(h2 + k2)-3*h2*k2))) + + def E23(h2, k2, s): + return s * sqrt(abs(s*s - h2)) + + def E24(h2, k2, s): + return s * sqrt(abs(s*s - k2)) + + def E25(h2, k2, s): + return sqrt(abs((s*s - h2)*(s*s - k2))) + + def E31(h2, k2, s): + return s*s*s - (s/5)*(2*(h2 + k2) + sqrt(4*(h2 + k2)*(h2 + k2) - + 15*h2*k2)) + + def E32(h2, k2, s): + return s*s*s - (s/5)*(2*(h2 + k2) - sqrt(4*(h2 + k2)*(h2 + k2) - + 15*h2*k2)) + + def E33(h2, k2, s): + return sqrt(abs(s*s - h2))*(s*s - 1/5*((h2 + 2*k2) + sqrt(abs((h2 + + 2*k2)*(h2 + 2*k2) - 5*h2*k2)))) + + def E34(h2, k2, s): + return sqrt(abs(s*s - h2))*(s*s - 1/5*((h2 + 2*k2) - sqrt(abs((h2 + + 2*k2)*(h2 + 2*k2) - 5*h2*k2)))) + + def E35(h2, k2, s): + return sqrt(abs(s*s - k2))*(s*s - 1/5*((2*h2 + k2) + sqrt(abs((2*h2 + + k2)*(2*h2 + k2) - 5*h2*k2)))) + + def E36(h2, k2, s): + return sqrt(abs(s*s - k2))*(s*s - 1/5*((2*h2 + k2) - sqrt(abs((2*h2 + + k2)*(2*h2 + k2) - 5*h2*k2)))) + + def E37(h2, k2, s): + return s * sqrt(abs((s*s - h2)*(s*s - k2))) + + assert_equal(ellip_harm(5, 8, 1, 2, 2.5, 1, 1), + ellip_harm(5, 8, 1, 2, 2.5)) + + known_funcs = {(0, 1): E01, (1, 1): E11, (1, 2): E12, (1, 3): E13, + (2, 1): E21, (2, 2): E22, (2, 3): E23, (2, 4): E24, + (2, 5): E25, (3, 1): E31, (3, 2): E32, (3, 3): E33, + (3, 4): E34, (3, 5): E35, (3, 6): E36, (3, 7): E37} + + point_ref = [] + + def ellip_harm_known(h2, k2, n, p, s): + for i in range(h2.size): + func = known_funcs[(int(n[i]), int(p[i]))] + point_ref.append(func(h2[i], k2[i], s[i])) + return point_ref + + rng = np.random.RandomState(1234) + h2 = rng.pareto(0.5, size=30) + k2 = h2*(1 + rng.pareto(0.5, size=h2.size)) + s = rng.pareto(0.5, size=h2.size) + points = [] + for i in range(h2.size): + for n in range(4): + for p in range(1, 2*n+2): + points.append((h2[i], k2[i], n, p, s[i])) + points = np.array(points) + assert_func_equal(ellip_harm, ellip_harm_known, points, rtol=1e-12) + + +def test_ellip_harm_invalid_p(): + # Regression test. This should return nan. + n = 4 + # Make p > 2*n + 1. + p = 2*n + 2 + result = ellip_harm(0.5, 2.0, n, p, 0.2) + assert np.isnan(result) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_erfinv.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_erfinv.py new file mode 100644 index 0000000000000000000000000000000000000000..98739b93fc6ad75a41a7b80107ee696453b12a09 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_erfinv.py @@ -0,0 +1,89 @@ +import numpy as np +from numpy.testing import assert_allclose, assert_equal +import pytest + +import scipy.special as sc + + +class TestInverseErrorFunction: + def test_compliment(self): + # Test erfcinv(1 - x) == erfinv(x) + x = np.linspace(-1, 1, 101) + assert_allclose(sc.erfcinv(1 - x), sc.erfinv(x), rtol=0, atol=1e-15) + + def test_literal_values(self): + # The expected values were calculated with mpmath: + # + # import mpmath + # mpmath.mp.dps = 200 + # for y in [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]: + # x = mpmath.erfinv(y) + # print(x) + # + y = np.array([0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]) + actual = sc.erfinv(y) + expected = [ + 0.0, + 0.08885599049425769, + 0.1791434546212917, + 0.2724627147267543, + 0.37080715859355795, + 0.4769362762044699, + 0.5951160814499948, + 0.7328690779592167, + 0.9061938024368233, + 1.1630871536766743, + ] + assert_allclose(actual, expected, rtol=0, atol=1e-15) + + @pytest.mark.parametrize( + 'f, x, y', + [ + (sc.erfinv, -1, -np.inf), + (sc.erfinv, 0, 0), + (sc.erfinv, 1, np.inf), + (sc.erfinv, -100, np.nan), + (sc.erfinv, 100, np.nan), + (sc.erfcinv, 0, np.inf), + (sc.erfcinv, 1, -0.0), + (sc.erfcinv, 2, -np.inf), + (sc.erfcinv, -100, np.nan), + (sc.erfcinv, 100, np.nan), + ], + ids=[ + 'erfinv at lower bound', + 'erfinv at midpoint', + 'erfinv at upper bound', + 'erfinv below lower bound', + 'erfinv above upper bound', + 'erfcinv at lower bound', + 'erfcinv at midpoint', + 'erfcinv at upper bound', + 'erfcinv below lower bound', + 'erfcinv above upper bound', + ] + ) + def test_domain_bounds(self, f, x, y): + assert_equal(f(x), y) + + def test_erfinv_asympt(self): + # regression test for gh-12758: erfinv(x) loses precision at small x + # expected values precomputed with mpmath: + # >>> mpmath.mp.dps = 100 + # >>> expected = [float(mpmath.erfinv(t)) for t in x] + x = np.array([1e-20, 1e-15, 1e-14, 1e-10, 1e-8, 0.9e-7, 1.1e-7, 1e-6]) + expected = np.array([8.86226925452758e-21, + 8.862269254527581e-16, + 8.86226925452758e-15, + 8.862269254527581e-11, + 8.86226925452758e-09, + 7.97604232907484e-08, + 9.74849617998037e-08, + 8.8622692545299e-07]) + assert_allclose(sc.erfinv(x), expected, + rtol=1e-15) + + # also test the roundtrip consistency + assert_allclose(sc.erf(sc.erfinv(x)), + x, + rtol=5e-15) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_exponential_integrals.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_exponential_integrals.py new file mode 100644 index 0000000000000000000000000000000000000000..d6081580b338fbf1c1eb39a846047ee0ea684e66 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_exponential_integrals.py @@ -0,0 +1,123 @@ +import pytest + +import numpy as np +from numpy.testing import assert_allclose +import scipy.special as sc + + +class TestExp1: + + def test_branch_cut(self): + assert np.isnan(sc.exp1(-1)) + assert sc.exp1(complex(-1, 0)).imag == ( + -sc.exp1(complex(-1, -0.0)).imag + ) + + assert_allclose( + sc.exp1(complex(-1, 0)), + sc.exp1(-1 + 1e-20j), + atol=0, + rtol=1e-15 + ) + assert_allclose( + sc.exp1(complex(-1, -0.0)), + sc.exp1(-1 - 1e-20j), + atol=0, + rtol=1e-15 + ) + + def test_834(self): + # Regression test for #834 + a = sc.exp1(-complex(19.9999990)) + b = sc.exp1(-complex(19.9999991)) + assert_allclose(a.imag, b.imag, atol=0, rtol=1e-15) + + +class TestScaledExp1: + + @pytest.mark.parametrize('x, expected', [(0, 0), (np.inf, 1)]) + def test_limits(self, x, expected): + y = sc._ufuncs._scaled_exp1(x) + assert y == expected + + # The expected values were computed with mpmath, e.g.: + # + # from mpmath import mp + # mp.dps = 80 + # x = 1e-25 + # print(float(x*mp.exp(x)*np.expint(1, x))) + # + # prints 5.698741165994961e-24 + # + # The method used to compute _scaled_exp1 changes at x=1 + # and x=1250, so values at those inputs, and values just + # above and below them, are included in the test data. + @pytest.mark.parametrize('x, expected', + [(1e-25, 5.698741165994961e-24), + (0.1, 0.20146425447084518), + (0.9995, 0.5962509885831002), + (1.0, 0.5963473623231941), + (1.0005, 0.5964436833238044), + (2.5, 0.7588145912149602), + (10.0, 0.9156333393978808), + (100.0, 0.9901942286733019), + (500.0, 0.9980079523802055), + (1000.0, 0.9990019940238807), + (1249.5, 0.9992009578306811), + (1250.0, 0.9992012769377913), + (1250.25, 0.9992014363957858), + (2000.0, 0.9995004992514963), + (1e4, 0.9999000199940024), + (1e10, 0.9999999999), + (1e15, 0.999999999999999), + ]) + def test_scaled_exp1(self, x, expected): + y = sc._ufuncs._scaled_exp1(x) + assert_allclose(y, expected, rtol=2e-15) + + +class TestExpi: + + @pytest.mark.parametrize('result', [ + sc.expi(complex(-1, 0)), + sc.expi(complex(-1, -0.0)), + sc.expi(-1) + ]) + def test_branch_cut(self, result): + desired = -0.21938393439552027368 # Computed using Mpmath + assert_allclose(result, desired, atol=0, rtol=1e-14) + + def test_near_branch_cut(self): + lim_from_above = sc.expi(-1 + 1e-20j) + lim_from_below = sc.expi(-1 - 1e-20j) + assert_allclose( + lim_from_above.real, + lim_from_below.real, + atol=0, + rtol=1e-15 + ) + assert_allclose( + lim_from_above.imag, + -lim_from_below.imag, + atol=0, + rtol=1e-15 + ) + + def test_continuity_on_positive_real_axis(self): + assert_allclose( + sc.expi(complex(1, 0)), + sc.expi(complex(1, -0.0)), + atol=0, + rtol=1e-15 + ) + + @pytest.mark.parametrize('x, expected', [(0, -np.inf), (np.inf, np.inf)]) + def test_limits(self, x, expected): + y = sc.expi(x) + assert y == expected + + +class TestExpn: + + def test_out_of_domain(self): + assert all(np.isnan([sc.expn(-1, 1.0), sc.expn(1, -1.0)])) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_extending.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_extending.py new file mode 100644 index 0000000000000000000000000000000000000000..3ecaf51545e006e37a451a08f356ce0392a3159c --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_extending.py @@ -0,0 +1,28 @@ +import os +import platform +import sysconfig + +import pytest + +from scipy._lib._testutils import IS_EDITABLE,_test_cython_extension, cython +from scipy.special import beta, gamma + + +@pytest.mark.fail_slow(40) +# essential per https://github.com/scipy/scipy/pull/20487#discussion_r1567057247 +@pytest.mark.skipif(IS_EDITABLE, + reason='Editable install cannot find .pxd headers.') +@pytest.mark.skipif((platform.system() == 'Windows' and + sysconfig.get_config_var('Py_GIL_DISABLED')), + reason='gh-22039') +@pytest.mark.skipif(platform.machine() in ["wasm32", "wasm64"], + reason="Can't start subprocess") +@pytest.mark.skipif(cython is None, reason="requires cython") +def test_cython(tmp_path): + srcdir = os.path.dirname(os.path.dirname(__file__)) + extensions, extensions_cpp = _test_cython_extension(tmp_path, srcdir) + # actually test the cython c-extensions + assert extensions.cy_beta(0.5, 0.1) == beta(0.5, 0.1) + assert extensions.cy_gamma(0.5 + 1.0j) == gamma(0.5 + 1.0j) + assert extensions_cpp.cy_beta(0.5, 0.1) == beta(0.5, 0.1) + assert extensions_cpp.cy_gamma(0.5 + 1.0j) == gamma(0.5 + 1.0j) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_faddeeva.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_faddeeva.py new file mode 100644 index 0000000000000000000000000000000000000000..8868f66c47ce0d4bbb21c78435a6c89d44065252 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_faddeeva.py @@ -0,0 +1,85 @@ +import pytest + +import numpy as np +from numpy.testing import assert_allclose +import scipy.special as sc +from scipy.special._testutils import FuncData + + +class TestVoigtProfile: + + @pytest.mark.parametrize('x, sigma, gamma', [ + (np.nan, 1, 1), + (0, np.nan, 1), + (0, 1, np.nan), + (1, np.nan, 0), + (np.nan, 1, 0), + (1, 0, np.nan), + (np.nan, 0, 1), + (np.nan, 0, 0) + ]) + def test_nan(self, x, sigma, gamma): + assert np.isnan(sc.voigt_profile(x, sigma, gamma)) + + @pytest.mark.parametrize('x, desired', [ + (-np.inf, 0), + (np.inf, 0) + ]) + def test_inf(self, x, desired): + assert sc.voigt_profile(x, 1, 1) == desired + + def test_against_mathematica(self): + # Results obtained from Mathematica by computing + # + # PDF[VoigtDistribution[gamma, sigma], x] + # + points = np.array([ + [-7.89, 45.06, 6.66, 0.0077921073660388806401], + [-0.05, 7.98, 24.13, 0.012068223646769913478], + [-13.98, 16.83, 42.37, 0.0062442236362132357833], + [-12.66, 0.21, 6.32, 0.010052516161087379402], + [11.34, 4.25, 21.96, 0.0113698923627278917805], + [-11.56, 20.40, 30.53, 0.0076332760432097464987], + [-9.17, 25.61, 8.32, 0.011646345779083005429], + [16.59, 18.05, 2.50, 0.013637768837526809181], + [9.11, 2.12, 39.33, 0.0076644040807277677585], + [-43.33, 0.30, 45.68, 0.0036680463875330150996] + ]) + FuncData( + sc.voigt_profile, + points, + (0, 1, 2), + 3, + atol=0, + rtol=1e-15 + ).check() + + def test_symmetry(self): + x = np.linspace(0, 10, 20) + assert_allclose( + sc.voigt_profile(x, 1, 1), + sc.voigt_profile(-x, 1, 1), + rtol=1e-15, + atol=0 + ) + + @pytest.mark.parametrize('x, sigma, gamma, desired', [ + (0, 0, 0, np.inf), + (1, 0, 0, 0) + ]) + def test_corner_cases(self, x, sigma, gamma, desired): + assert sc.voigt_profile(x, sigma, gamma) == desired + + @pytest.mark.parametrize('sigma1, gamma1, sigma2, gamma2', [ + (0, 1, 1e-16, 1), + (1, 0, 1, 1e-16), + (0, 0, 1e-16, 1e-16) + ]) + def test_continuity(self, sigma1, gamma1, sigma2, gamma2): + x = np.linspace(1, 10, 20) + assert_allclose( + sc.voigt_profile(x, sigma1, gamma1), + sc.voigt_profile(x, sigma2, gamma2), + rtol=1e-16, + atol=1e-16 + ) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_gamma.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_gamma.py new file mode 100644 index 0000000000000000000000000000000000000000..2e3fbd17dddeed73d311566a930f52899e3b9db6 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_gamma.py @@ -0,0 +1,12 @@ +import numpy as np +import scipy.special as sc + + +class TestRgamma: + + def test_gh_11315(self): + assert sc.rgamma(-35) == 0 + + def test_rgamma_zeros(self): + x = np.array([0, -10, -100, -1000, -10000]) + assert np.all(sc.rgamma(x) == 0) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_gammainc.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_gammainc.py new file mode 100644 index 0000000000000000000000000000000000000000..b5a0ffb65d6deebf77fe82ebee2dca61f2067c59 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_gammainc.py @@ -0,0 +1,152 @@ +import pytest + +import numpy as np +from numpy.testing import assert_allclose, assert_array_equal + +import scipy.special as sc +from scipy.special._testutils import FuncData + + +INVALID_POINTS = [ + (1, -1), + (0, 0), + (-1, 1), + (np.nan, 1), + (1, np.nan) +] + + +class TestGammainc: + + @pytest.mark.parametrize('a, x', INVALID_POINTS) + def test_domain(self, a, x): + assert np.isnan(sc.gammainc(a, x)) + + def test_a_eq_0_x_gt_0(self): + assert sc.gammainc(0, 1) == 1 + + @pytest.mark.parametrize('a, x, desired', [ + (np.inf, 1, 0), + (np.inf, 0, 0), + (np.inf, np.inf, np.nan), + (1, np.inf, 1) + ]) + def test_infinite_arguments(self, a, x, desired): + result = sc.gammainc(a, x) + if np.isnan(desired): + assert np.isnan(result) + else: + assert result == desired + + @pytest.mark.parametrize("x", [-np.inf, -1.0, -0.0, 0.0, np.inf, np.nan]) + def test_a_nan(self, x): + assert np.isnan(sc.gammainc(np.nan, x)) + + @pytest.mark.parametrize("a", [-np.inf, -1.0, -0.0, 0.0, np.inf, np.nan]) + def test_x_nan(self, a): + assert np.isnan(sc.gammainc(a, np.nan)) + + def test_infinite_limits(self): + # Test that large arguments converge to the hard-coded limits + # at infinity. + assert_allclose( + sc.gammainc(1000, 100), + sc.gammainc(np.inf, 100), + atol=1e-200, # Use `atol` since the function converges to 0. + rtol=0 + ) + assert sc.gammainc(100, 1000) == sc.gammainc(100, np.inf) + + def test_x_zero(self): + a = np.arange(1, 10) + assert_array_equal(sc.gammainc(a, 0), 0) + + def test_limit_check(self): + result = sc.gammainc(1e-10, 1) + limit = sc.gammainc(0, 1) + assert np.isclose(result, limit) + + def gammainc_line(self, x): + # The line a = x where a simpler asymptotic expansion (analog + # of DLMF 8.12.15) is available. + c = np.array([-1/3, -1/540, 25/6048, 101/155520, + -3184811/3695155200, -2745493/8151736420]) + res = 0 + xfac = 1 + for ck in c: + res -= ck*xfac + xfac /= x + res /= np.sqrt(2*np.pi*x) + res += 0.5 + return res + + def test_line(self): + x = np.logspace(np.log10(25), 300, 500) + a = x + dataset = np.vstack((a, x, self.gammainc_line(x))).T + FuncData(sc.gammainc, dataset, (0, 1), 2, rtol=1e-11).check() + + def test_roundtrip(self): + a = np.logspace(-5, 10, 100) + x = np.logspace(-5, 10, 100) + + y = sc.gammaincinv(a, sc.gammainc(a, x)) + assert_allclose(x, y, rtol=1e-10) + + +class TestGammaincc: + + @pytest.mark.parametrize('a, x', INVALID_POINTS) + def test_domain(self, a, x): + assert np.isnan(sc.gammaincc(a, x)) + + def test_a_eq_0_x_gt_0(self): + assert sc.gammaincc(0, 1) == 0 + + @pytest.mark.parametrize('a, x, desired', [ + (np.inf, 1, 1), + (np.inf, 0, 1), + (np.inf, np.inf, np.nan), + (1, np.inf, 0) + ]) + def test_infinite_arguments(self, a, x, desired): + result = sc.gammaincc(a, x) + if np.isnan(desired): + assert np.isnan(result) + else: + assert result == desired + + @pytest.mark.parametrize("x", [-np.inf, -1.0, -0.0, 0.0, np.inf, np.nan]) + def test_a_nan(self, x): + assert np.isnan(sc.gammaincc(np.nan, x)) + + @pytest.mark.parametrize("a", [-np.inf, -1.0, -0.0, 0.0, np.inf, np.nan]) + def test_x_nan(self, a): + assert np.isnan(sc.gammaincc(a, np.nan)) + + def test_infinite_limits(self): + # Test that large arguments converge to the hard-coded limits + # at infinity. + assert sc.gammaincc(1000, 100) == sc.gammaincc(np.inf, 100) + assert_allclose( + sc.gammaincc(100, 1000), + sc.gammaincc(100, np.inf), + atol=1e-200, # Use `atol` since the function converges to 0. + rtol=0 + ) + + def test_limit_check(self): + result = sc.gammaincc(1e-10,1) + limit = sc.gammaincc(0,1) + assert np.isclose(result, limit) + + def test_x_zero(self): + a = np.arange(1, 10) + assert_array_equal(sc.gammaincc(a, 0), 1) + + def test_roundtrip(self): + a = np.logspace(-5, 10, 100) + x = np.logspace(-5, 10, 100) + + y = sc.gammainccinv(a, sc.gammaincc(a, x)) + assert_allclose(x, y, rtol=1e-14) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_gen_harmonic.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_gen_harmonic.py new file mode 100644 index 0000000000000000000000000000000000000000..0896142d571ae1d3815ab2eb99a775d52b7d9169 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_gen_harmonic.py @@ -0,0 +1,99 @@ +import numpy as np +from numpy.testing import assert_allclose, assert_equal +import pytest +from scipy.special._ufuncs import _gen_harmonic, _normalized_gen_harmonic + + +# +# In the following tests, reference values were computed with mpmath. +# + +@pytest.mark.parametrize('typ', [np.int32, np.int64, np.float64]) +@pytest.mark.parametrize( + 'n, a, ref', + [(8, 9.0, 1.0020083884212339), + (1000, 2.5, 1.3414661912046497), + (10, 1.5, 1.9953364933456017), + (10000, 1.25, 4.1951168257387765), + (10000,1.00001, 9.787182620770265), + (80, 1.000002, 4.965460167788836), + (75, 1 + 1e-12, 4.901355630543771), + (100, 1 + 1e-14, 5.187377517639515), + (100, 1 + 8e-16, 5.187377517639611), + (100, 1.0, 5.187377517639621), + (7, 1.0, 2.592857142857143), + (8000, 1.0, 9.564474984261423), + (5, 1 - 1e-12, 2.2833333333347143), + (25000, 1 - 1e-12, 10.703866768669737), + (1000, 0.995, 7.6058022857089975), + (1000, 0.75, 19.055178975831392), + (10000, 0.25, 1332.5700547197382), + (5, 1e-8, 4.999999952125083), + (15, 1e-16, 14.999999999999996), + (100, 0.0, 100.0), + (4, -1.0, 10.0), + (75, -1.5, 19811.38815892374)] +) +def test_gen_harmonic(typ, n, a, ref): + h = _gen_harmonic(typ(n), a) + assert_allclose(h, ref, rtol=5e-15) + + +@pytest.mark.parametrize('typ', [np.int32, np.int64, np.float64]) +@pytest.mark.parametrize( + 'n, a, ref', + [(10, np.inf, 1.0), + (1, np.nan, 1.0), + (1, -np.inf, 1.0), + (3, np.nan, np.nan), + (-3, 1.0, np.nan)] +) +def test_gen_harmonic_exact_cases(typ, n, a, ref): + h = _gen_harmonic(typ(n), a) + assert_equal(h, ref) + + +def test_gen_harmonic_n_nan(): + h = _gen_harmonic(np.nan, 0.75) + assert_equal(h, np.nan) + + +@pytest.mark.parametrize('typ', [np.int32, np.int64, np.float64]) +@pytest.mark.parametrize( + 'j, k, n, a, ref', + [(400, 5000, 5000, 10.0, 4.2821759663214485e-25), + (400, 5000, 5000, 3.5, 1.11086549102426e-07), + (1, 2, 3, 1.5, 0.8755176866163012), + (300, 500, 500, 1 + 1e-14, 0.07559343891632035), + (1500, 2500, 3000, 1 - 1e-12, 0.05957291246371843), + (10, 12, 16, 0.5, 0.13601665344521513), + (16, 16, 20, 0.125, 0.04583107002260924), + (10, 12, 16, -0.5, 0.22359306724308234), + (1, 8000, 10000, -1.5, 0.5724512895513029)] +) +def test_normalized_gen_harmonic(typ, j, k, n, a, ref): + h = _normalized_gen_harmonic(typ(j), typ(k), typ(n), a) + assert_allclose(h, ref, 5e-15) + + +@pytest.mark.parametrize('typ', [np.int32, np.int64, np.float64]) +@pytest.mark.parametrize( + 'j, k, n, a, ref', + [(1, 1, 1, 0.5, 1.0), + (1, 1, 1, np.nan, 1.0), + (1, 2, 5, np.nan, np.nan), + (1, 2, 1, 1.25, np.nan), + (1, 2, 3, np.inf, 1.0), + (2, 3, 4, np.inf, 0.0), + (1, 1, 10, -np.inf, 0.0), + (2, 3, 4, -np.inf, np.nan), + (3, 6, 8, 0.0, 0.5)] +) +def test_normalized_gen_harmonic_exact_cases(typ, j, k, n, a, ref): + h = _normalized_gen_harmonic(typ(j), typ(k), typ(n), a) + assert_equal(h, ref) + + +def test_normalized_gen_harmonic_input_nan(): + h = _normalized_gen_harmonic(1.0, np.nan, 10.0, 1.05) + assert_equal(h, np.nan) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_hyp2f1.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_hyp2f1.py new file mode 100644 index 0000000000000000000000000000000000000000..8e51a48f91440583ddaa88ffe09d48e4376055cf --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_hyp2f1.py @@ -0,0 +1,2566 @@ +"""Tests for hyp2f1 for complex values. + +Author: Albert Steppi, with credit to Adam Kullberg (FormerPhycisist) for +the implementation of mp_hyp2f1 below, which modifies mpmath's hyp2f1 to +return the same branch as scipy's on the standard branch cut. +""" + +import sys +import pytest +import numpy as np +from typing import NamedTuple +from numpy.testing import assert_allclose + +from scipy.special import hyp2f1 +from scipy.special._testutils import check_version, MissingModule + + +try: + import mpmath +except ImportError: + mpmath = MissingModule("mpmath") + + +def mp_hyp2f1(a, b, c, z): + """Return mpmath hyp2f1 calculated on same branch as scipy hyp2f1. + + For most values of a,b,c mpmath returns the x - 0j branch of hyp2f1 on the + branch cut x=(1,inf) whereas scipy's hyp2f1 calculates the x + 0j branch. + Thus, to generate the right comparison values on the branch cut, we + evaluate mpmath.hyp2f1 at x + 1e-15*j. + + The exception to this occurs when c-a=-m in which case both mpmath and + scipy calculate the x + 0j branch on the branch cut. When this happens + mpmath.hyp2f1 will be evaluated at the original z point. + """ + on_branch_cut = z.real > 1.0 and abs(z.imag) < 1.0e-15 + cond1 = abs(c - a - round(c - a)) < 1.0e-15 and round(c - a) <= 0 + cond2 = abs(c - b - round(c - b)) < 1.0e-15 and round(c - b) <= 0 + # Make sure imaginary part is *exactly* zero + if on_branch_cut: + z = z.real + 0.0j + if on_branch_cut and not (cond1 or cond2): + z_mpmath = z.real + 1.0e-15j + else: + z_mpmath = z + return complex(mpmath.hyp2f1(a, b, c, z_mpmath)) + + +class Hyp2f1TestCase(NamedTuple): + a: float + b: float + c: float + z: complex + expected: complex + rtol: float + + +class TestHyp2f1: + """Tests for hyp2f1 for complex values. + + Expected values for test cases were computed using mpmath. See + `scipy.special._precompute.hyp2f1_data`. The verbose style of specifying + test cases is used for readability and to make it easier to mark individual + cases as expected to fail. Expected failures are used to highlight cases + where improvements are needed. See + `scipy.special._precompute.hyp2f1_data.make_hyp2f1_test_cases` for a + function to generate the boilerplate for the test cases. + + Assertions have been added to each test to ensure that the test cases match + the situations that are intended. A final test `test_test_hyp2f1` checks + that the expected values in the test cases actually match what is computed + by mpmath. This test is marked slow even though it isn't particularly slow + so that it won't run by default on continuous integration builds. + """ + @pytest.mark.parametrize( + "hyp2f1_test_case", + [ + pytest.param( + Hyp2f1TestCase( + a=0.5, + b=0.2, + c=-10, + z=0.2 + 0.2j, + expected=np.inf + 0j, + rtol=0 + ) + ), + pytest.param( + Hyp2f1TestCase( + a=0.5, + b=0.2, + c=-10, + z=0 + 0j, + expected=1 + 0j, + rtol=0 + ), + ), + pytest.param( + Hyp2f1TestCase( + a=0.5, + b=0, + c=-10, + z=0.2 + 0.2j, + expected=1 + 0j, + rtol=0 + ), + ), + pytest.param( + Hyp2f1TestCase( + a=0.5, + b=0, + c=0, + z=0.2 + 0.2j, + expected=1 + 0j, + rtol=0, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=0.5, + b=0.2, + c=0, + z=0.2 + 0.2j, + expected=np.inf + 0j, + rtol=0, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=0.5, + b=0.2, + c=0, + z=0 + 0j, + expected=np.nan + 0j, + rtol=0, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=0.5, + b=-5, + c=-10, + z=0.2 + 0.2j, + expected=(1.0495404166666666+0.05708208333333334j), + rtol=1e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=0.5, + b=-10, + c=-10, + z=0.2 + 0.2j, + expected=(1.092966013125+0.13455014673750001j), + rtol=1e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-10, + b=-20, + c=-10, + z=0.2 + 0.2j, + expected=(-0.07712512000000005+0.12752814080000005j), + rtol=1e-13, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-1, + b=3.2, + c=-1, + z=0.2 + 0.2j, + expected=(1.6400000000000001+0.6400000000000001j), + rtol=1e-13, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-2, + b=1.2, + c=-4, + z=1 + 0j, + expected=1.8200000000000001 + 0j, + rtol=1e-15, + ), + ), + ] + ) + def test_c_non_positive_int(self, hyp2f1_test_case): + a, b, c, z, expected, rtol = hyp2f1_test_case + assert_allclose(hyp2f1(a, b, c, z), expected, rtol=rtol) + + @pytest.mark.parametrize( + "hyp2f1_test_case", + [ + pytest.param( + Hyp2f1TestCase( + a=0.5, + b=0.2, + c=1.5, + z=1 + 0j, + expected=1.1496439092239847 + 0j, + rtol=1e-15 + ), + ), + pytest.param( + Hyp2f1TestCase( + a=12.3, + b=8.0, + c=20.31, + z=1 + 0j, + expected=69280986.75273195 + 0j, + rtol=1e-15 + ), + ), + pytest.param( + Hyp2f1TestCase( + a=290.2, + b=321.5, + c=700.1, + z=1 + 0j, + expected=1.3396562400934e117 + 0j, + rtol=1e-12, + ), + ), + # Note that here even mpmath produces different results for + # results that should be equivalent. + pytest.param( + Hyp2f1TestCase( + a=9.2, + b=621.5, + c=700.1, + z=(1+0j), + expected=(952726652.4158565+0j), + rtol=5e-13, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=621.5, + b=9.2, + c=700.1, + z=(1+0j), + expected=(952726652.4160284+0j), + rtol=5e-12, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-101.2, + b=-400.4, + c=-172.1, + z=(1+0j), + expected=(2.2253618341394838e+37+0j), + rtol=1e-13, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-400.4, + b=-101.2, + c=-172.1, + z=(1+0j), + expected=(2.2253618341394838e+37+0j), + rtol=5e-13, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=172.5, + b=-201.3, + c=151.2, + z=(1+0j), + expected=(7.072266653650905e-135+0j), + rtol=5e-13, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-201.3, + b=172.5, + c=151.2, + z=(1+0j), + expected=(7.072266653650905e-135+0j), + rtol=5e-13, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-102.1, + b=-20.3, + c=1.3, + z=1 + 0j, + expected=2.7899070752746906e22 + 0j, + rtol=3e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-202.6, + b=60.3, + c=1.5, + z=1 + 0j, + expected=-1.3113641413099326e-56 + 0j, + rtol=1e-12, + ), + ), + ], + ) + def test_unital_argument(self, hyp2f1_test_case): + """Tests for case z = 1, c - a - b > 0. + + Expected answers computed using mpmath. + """ + a, b, c, z, expected, rtol = hyp2f1_test_case + assert z == 1 and c - a - b > 0 # Tests the test + assert_allclose(hyp2f1(a, b, c, z), expected, rtol=rtol) + + @pytest.mark.parametrize( + "hyp2f1_test_case", + [ + pytest.param( + Hyp2f1TestCase( + a=0.5, + b=0.2, + c=1.3, + z=-1 + 0j, + expected=0.9428846409614143 + 0j, + rtol=1e-15), + ), + pytest.param( + Hyp2f1TestCase( + a=12.3, + b=8.0, + c=5.300000000000001, + z=-1 + 0j, + expected=-4.845809986595704e-06 + 0j, + rtol=1e-15 + ), + ), + pytest.param( + Hyp2f1TestCase( + a=221.5, + b=90.2, + c=132.3, + z=-1 + 0j, + expected=2.0490488728377282e-42 + 0j, + rtol=1e-7, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-102.1, + b=-20.3, + c=-80.8, + z=-1 + 0j, + expected=45143784.46783885 + 0j, + rtol=1e-7, + ), + marks=pytest.mark.xfail( + condition=sys.maxsize < 2**32, + reason="Fails on 32 bit.", + ) + ), + ], + ) + def test_special_case_z_near_minus_1(self, hyp2f1_test_case): + """Tests for case z ~ -1, c ~ 1 + a - b + + Expected answers computed using mpmath. + """ + a, b, c, z, expected, rtol = hyp2f1_test_case + assert abs(1 + a - b - c) < 1e-15 and abs(z + 1) < 1e-15 + assert_allclose(hyp2f1(a, b, c, z), expected, rtol=rtol) + + @pytest.mark.parametrize( + "hyp2f1_test_case", + [ + pytest.param( + Hyp2f1TestCase( + a=-4, + b=2.02764642551431, + c=1.0561196186065624, + z=(0.9473684210526314-0.10526315789473695j), + expected=(0.0031961077109535375-0.0011313924606557173j), + rtol=1e-12, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-8, + b=-7.937789122896016, + c=-15.964218273004214, + z=(2-0.10526315789473695j), + expected=(0.005543763196412503-0.0025948879065698306j), + rtol=5e-13, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-8, + b=8.095813935368371, + c=4.0013768449590685, + z=(0.9473684210526314-0.10526315789473695j), + expected=(-0.0003054674127221263-9.261359291755414e-05j), + rtol=1e-10, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-4, + b=-3.956227226099288, + c=-3.9316537064827854, + z=(1.1578947368421053-0.3157894736842106j), + expected=(-0.0020809502580892937-0.0041877333232365095j), + rtol=5e-12, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=2.02764642551431, + b=-4, + c=2.050308316530781, + z=(0.9473684210526314-0.10526315789473695j), + expected=(0.0011282435590058734+0.0002027062303465851j), + rtol=5e-13, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-7.937789122896016, + b=-8, + c=-15.964218273004214, + z=(1.3684210526315788+0.10526315789473673j), + expected=(-9.134907719238265e-05-0.00040219233987390723j), + rtol=5e-12, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=4.080187217753502, + b=-4, + c=4.0013768449590685, + z=(0.9473684210526314-0.10526315789473695j), + expected=(-0.000519013062087489-0.0005855883076830948j), + rtol=5e-12, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-10000, + b=2.2, + c=93459345.3, + z=(2+2j), + expected=(0.9995292071559088-0.00047047067522659253j), + rtol=1e-12, + ), + ), + ] + ) + def test_a_b_negative_int(self, hyp2f1_test_case): + a, b, c, z, expected, rtol = hyp2f1_test_case + assert a == int(a) and a < 0 or b == int(b) and b < 0 # Tests the test + assert_allclose(hyp2f1(a, b, c, z), expected, rtol=rtol) + + @pytest.mark.parametrize( + "hyp2f1_test_case", + [ + pytest.param( + Hyp2f1TestCase( + a=-0.5, + b=-0.9629749245209605, + c=-15.5, + z=(1.1578947368421053-1.1578947368421053j), + expected=(0.9778506962676361+0.044083801141231616j), + rtol=3e-12, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=8.5, + b=-3.9316537064827854, + c=1.5, + z=(0.9473684210526314-0.10526315789473695j), + expected=(4.0793167523167675-10.11694246310966j), + rtol=6e-12, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=8.5, + b=-0.9629749245209605, + c=2.5, + z=(1.1578947368421053-0.10526315789473695j), + expected=(-2.9692999501916915+0.6394599899845594j), + rtol=1e-11, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-0.5, + b=-0.9629749245209605, + c=-15.5, + z=(1.5789473684210522-1.1578947368421053j), + expected=(0.9493076367106102-0.04316852977183447j), + rtol=1e-11, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-0.9220024191881196, + b=-0.5, + c=-15.5, + z=(0.5263157894736841+0.10526315789473673j), + expected=(0.9844377175631795-0.003120587561483841j), + rtol=1e-10, + ), + ), + ], + ) + def test_a_b_neg_int_after_euler_hypergeometric_transformation( + self, hyp2f1_test_case + ): + a, b, c, z, expected, rtol = hyp2f1_test_case + assert ( # Tests the test + (abs(c - a - int(c - a)) < 1e-15 and c - a < 0) or + (abs(c - b - int(c - b)) < 1e-15 and c - b < 0) + ) + assert_allclose(hyp2f1(a, b, c, z), expected, rtol=rtol) + + @pytest.mark.parametrize( + "hyp2f1_test_case", + [ + pytest.param( + Hyp2f1TestCase( + a=-0.9220024191881196, + b=-0.9629749245209605, + c=-15.963511401609862, + z=(0.10526315789473673-0.3157894736842106j), + expected=(0.9941449585778349+0.01756335047931358j), + rtol=1e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=1.0272592605282642, + b=-0.9629749245209605, + c=-15.963511401609862, + z=(0.5263157894736841+0.5263157894736841j), + expected=(1.0388722293372104-0.09549450380041416j), + rtol=5e-11, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=2.02764642551431, + b=1.0561196186065624, + c=-7.93846038215665, + z=(0.10526315789473673+0.7368421052631575j), + expected=(2.1948378809826434+24.934157235172222j), + rtol=5e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=2.02764642551431, + b=16.088264119063613, + c=8.031683612216888, + z=(0.3157894736842106-0.736842105263158j), + expected=(-0.4075277891264672-0.06819344579666956j), + rtol=2e-12, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=4.080187217753502, + b=2.050308316530781, + c=8.031683612216888, + z=(0.7368421052631575-0.10526315789473695j), + expected=(2.833535530740603-0.6925373701408158j), + rtol=5e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=2.02764642551431, + b=2.050308316530781, + c=4.078873014294075, + z=(0.10526315789473673-0.3157894736842106j), + expected=(1.005347176329683-0.3580736009337313j), + rtol=5e-16, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-0.9220024191881196, + b=-0.9629749245209605, + c=-15.963511401609862, + z=(0.3157894736842106-0.5263157894736843j), + expected=(0.9824353641135369+0.029271018868990268j), + rtol=5e-13, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-0.9220024191881196, + b=-0.9629749245209605, + c=-159.63511401609862, + z=(0.3157894736842106-0.5263157894736843j), + expected=(0.9982436200365834+0.002927268199671111j), + rtol=1e-7, + ), + marks=pytest.mark.xfail(reason="Poor convergence.") + ), + pytest.param( + Hyp2f1TestCase( + a=2.02764642551431, + b=16.088264119063613, + c=8.031683612216888, + z=(0.5263157894736841-0.5263157894736843j), + expected=(-0.6906825165778091+0.8176575137504892j), + rtol=5e-13, + ), + ), + ] + ) + def test_region1(self, hyp2f1_test_case): + """|z| < 0.9 and real(z) >= 0.""" + a, b, c, z, expected, rtol = hyp2f1_test_case + assert abs(z) < 0.9 and z.real >= 0 # Tests the test + assert_allclose(hyp2f1(a, b, c, z), expected, rtol=rtol) + + @pytest.mark.parametrize( + "hyp2f1_test_case", + [ + pytest.param( + Hyp2f1TestCase( + a=2.02764642551431, + b=1.0561196186065624, + c=4.078873014294075, + z=(-0.3157894736842106+0.7368421052631575j), + expected=(0.7751915029081136+0.24068493258607315j), + rtol=5e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=16.087593263474208, + b=16.088264119063613, + c=2.0397202577726152, + z=(-0.9473684210526316-0.3157894736842106j), + expected=(6.564549348474962e-07+1.6761570598334562e-06j), + rtol=5e-09, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=1.0272592605282642, + b=2.050308316530781, + c=16.056809865262608, + z=(-0.10526315789473695-0.10526315789473695j), + expected=(0.9862043298997204-0.013293151372712681j), + rtol=5e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=4.080187217753502, + b=8.077282662161238, + c=16.056809865262608, + z=(-0.3157894736842106-0.736842105263158j), + expected=(0.16163826638754716-0.41378530376373734j), + rtol=5e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=2.02764642551431, + b=2.050308316530781, + c=-0.906685989801748, + z=(-0.5263157894736843+0.3157894736842106j), + expected=(-6.256871535165936+0.13824973858225484j), + rtol=1e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=2.02764642551431, + b=8.077282662161238, + c=-3.9924618758357022, + z=(-0.9473684210526316-0.3157894736842106j), + expected=(75.54672526086316+50.56157041797548j), + rtol=5e-12, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=16.087593263474208, + b=8.077282662161238, + c=-1.9631175993998025, + z=(-0.5263157894736843+0.5263157894736841j), + expected=(282.0602536306534-82.31597306936214j), + rtol=5e-13, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=8.095813935368371, + b=-3.9316537064827854, + c=8.031683612216888, + z=(-0.5263157894736843-0.10526315789473695j), + expected=(5.179603735575851+1.4445374002099813j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=4.080187217753502, + b=-7.949900487447654, + c=1.0651378143226575, + z=(-0.3157894736842106-0.9473684210526316j), + expected=(2317.623517606141-269.51476321010324j), + rtol=5e-13, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=16.087593263474208, + b=-1.92872979730171, + c=2.0397202577726152, + z=(-0.736842105263158-0.3157894736842106j), + expected=(29.179154096175836+22.126690357535043j), + rtol=5e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=8.095813935368371, + b=-3.9316537064827854, + c=-15.963511401609862, + z=(-0.736842105263158-0.10526315789473695j), + expected=(0.20820247892032057-0.04763956711248794j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=1.0272592605282642, + b=-15.964218273004214, + c=-1.9631175993998025, + z=(-0.3157894736842106-0.5263157894736843j), + expected=(-157471.63920142158+991294.0587828817j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=8.095813935368371, + b=-7.949900487447654, + c=-7.93846038215665, + z=(-0.10526315789473695-0.10526315789473695j), + expected=(0.30765349653210194-0.2979706363594157j), + rtol=1e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-3.956227226099288, + b=1.0561196186065624, + c=8.031683612216888, + z=(-0.9473684210526316-0.10526315789473695j), + expected=(1.6787607400597109+0.10056620134616838j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-7.937789122896016, + b=16.088264119063613, + c=4.078873014294075, + z=(-0.5263157894736843-0.736842105263158j), + expected=(7062.07842506049-12768.77955655703j), + rtol=5e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-7.937789122896016, + b=16.088264119063613, + c=2.0397202577726152, + z=(-0.3157894736842106+0.7368421052631575j), + expected=(54749.216391029935-23078.144720887536j), + rtol=2e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-3.956227226099288, + b=1.0561196186065624, + c=-0.906685989801748, + z=(-0.10526315789473695-0.10526315789473695j), + expected=(1.21521766411428-4.449385173946672j), + rtol=5e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-15.980848054962111, + b=4.0013768449590685, + c=-1.9631175993998025, + z=(-0.736842105263158+0.5263157894736841j), + expected=(19234693144.196907+1617913967.7294445j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-1.9214641416286231, + b=1.0561196186065624, + c=-15.963511401609862, + z=(-0.5263157894736843+0.3157894736842106j), + expected=(0.9345201094534371+0.03745712558992195j), + rtol=5e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-7.937789122896016, + b=-0.9629749245209605, + c=2.0397202577726152, + z=(-0.10526315789473695+0.10526315789473673j), + expected=(0.605732446296829+0.398171533680972j), + rtol=5e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-1.9214641416286231, + b=-15.964218273004214, + c=2.0397202577726152, + z=(-0.10526315789473695-0.5263157894736843j), + expected=(-9.753761888305416-4.590126012666959j), + rtol=5e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-3.956227226099288, + b=-1.92872979730171, + c=2.0397202577726152, + z=(-0.10526315789473695+0.3157894736842106j), + expected=(0.45587226291120714+1.0694545265819797j), + rtol=5e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-0.9220024191881196, + b=-7.949900487447654, + c=-0.906685989801748, + z=(-0.736842105263158+0.3157894736842106j), + expected=(12.334808243233418-76.26089051819054j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-0.9220024191881196, + b=-7.949900487447654, + c=-15.963511401609862, + z=(-0.5263157894736843+0.10526315789473673j), + expected=(1.2396019687632678-0.047507973161146286j), + rtol=1e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-15.980848054962111, + b=-0.9629749245209605, + c=-0.906685989801748, + z=(-0.3157894736842106-0.5263157894736843j), + expected=(97.7889554372208-18.999754543400016j), + rtol=5e-13, + ), + ), + ] + ) + def test_region2(self, hyp2f1_test_case): + """|z| < 1 and real(z) < 0.""" + a, b, c, z, expected, rtol = hyp2f1_test_case + assert abs(z) < 1 and z.real < 0 # Tests the test + assert_allclose(hyp2f1(a, b, c, z), expected, rtol=rtol) + + @pytest.mark.parametrize( + "hyp2f1_test_case", + [ + pytest.param( + Hyp2f1TestCase( + a=16.25, + b=-3.75, + c=-3.5, + z=(0.5263157894736841+0.7368421052631575j), + expected=(-1279.4894322256655-2302.914821389276j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-15.75, + b=8.25, + c=-1.5, + z=(0.9473684210526314+0.3157894736842106j), + expected=(-8889.452798586273-11961.162305065242j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=2.25, + b=2.25, + c=-1.5, + z=(0.5263157894736841-0.736842105263158j), + expected=(-236.58971357952055-238.5228224781136j), + rtol=5e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-0.75, + b=-7.75, + c=-15.5, + z=(0.5263157894736841+0.7368421052631575j), + expected=(0.8116076584352279-0.29360565398246036j), + rtol=5e-16, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=1.25, + b=4.25, + c=-0.5, + z=(0.5263157894736841-0.736842105263158j), + expected=(-28.119407485189985+98.89858821348005j), + rtol=5e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-0.75, + b=2.25, + c=1.5, + z=(0.5263157894736841+0.7368421052631575j), + expected=(0.5311049067450484-0.9434347326448517j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=8.25, + b=-15.75, + c=-7.5, + z=(0.9473684210526314+0.10526315789473673j), + expected=(1262084.378141873+1775569.6338380123j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-15.75, + b=-7.75, + c=-15.5, + z=(0.5263157894736841-0.736842105263158j), + expected=(-0.009810480794804165+0.3648997569257999j), + rtol=1e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=8.25, + b=2.25, + c=-3.5, + z=(0.5263157894736841-0.736842105263158j), + expected=(585660.8815535795-33646.68398590896j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=4.080187217753502, + b=-3.9316537064827854, + c=-15.963511401609862, + z=(0.9473684210526314-0.10526315789473695j), + expected=(181899621848365.2-173207123998705.7j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-15.75, + b=8.25, + c=-0.5, + z=(0.9473684210526314-0.10526315789473695j), + expected=(0.04271686244952705-0.14087902824639406j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-7.937789122896016, + b=-1.92872979730171, + c=-0.906685989801748, + z=(0.9473684210526314-0.3157894736842106j), + expected=(-449.5119088817207+320.1423128036188j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-3.956227226099288, + b=1.0561196186065624, + c=8.031683612216888, + z=(0.9473684210526314-0.10526315789473695j), + expected=(0.6361479738012501+0.028575620091205088j), + rtol=5e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=4.25, + b=16.25, + c=16.5, + z=(0.5263157894736841+0.7368421052631575j), + expected=(-0.9038811840552261-1.5356250756164884j), + rtol=1e-8, + ), + marks=pytest.mark.xfail( + reason="Unhandled parameters." + ) + ), + pytest.param( + Hyp2f1TestCase( + a=8.25, + b=-1.75, + c=-1.5, + z=(0.9473684210526314+0.3157894736842106j), + expected=(653.0109150415394-4554.162605155542j), + rtol=1e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-15.75, + b=-3.75, + c=4.5, + z=(0.9473684210526314-0.10526315789473695j), + expected=(118.7009859241035-34.18713648654642j), + rtol=1e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=4.25, + b=-15.75, + c=-3.5, + z=(0.5263157894736841+0.7368421052631575j), + expected=(-540204.4774526551+4970059.109251281j), + rtol=1e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=8.25, + b=-15.75, + c=-0.5, + z=(0.5263157894736841-0.736842105263158j), + expected=(2253490.972258385+3318620.683390017j), + rtol=5e-13, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=16.25, + b=-7.75, + c=-7.5, + z=(0.9473684210526314+0.3157894736842106j), + expected=(-46159826.46716958-17880663.82218242j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-0.9220024191881196, + b=-7.949900487447654, + c=-7.93846038215665, + z=(0.9473684210526314-0.10526315789473695j), + expected=(0.07116833581404514+0.11823358038036977j), + rtol=5e-13, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=16.087593263474208, + b=4.0013768449590685, + c=-7.93846038215665, + z=(0.7368421052631575+0.5263157894736841j), + expected=(4.7724909620664006e+17-6.039064078946702e+16j), + rtol=5e-13, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=1.25, + b=-7.75, + c=1.5, + z=(0.9473684210526314-0.10526315789473695j), + expected=(0.0188022179759303+0.002921737281641378j), + rtol=5e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=1.0272592605282642, + b=1.0561196186065624, + c=-7.93846038215665, + z=(0.7368421052631575-0.5263157894736843j), + expected=(-9203.462928334846+12390.110518017136j), + rtol=5e-13, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-3.75, + b=-15.75, + c=8.5, + z=(0.7368421052631575+0.5263157894736841j), + expected=(6.468457061368628+24.190040684917374j), + rtol=6e-16, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=2.02764642551431, + b=16.088264119063613, + c=2.0397202577726152, + z=(0.7368421052631575+0.5263157894736841j), + expected=(2408.3451340186543-4275.257316636014j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-3.75, + b=-7.75, + c=8.5, + z=(0.7368421052631575-0.5263157894736843j), + expected=(4.1379984626381345-5.183654781039423j), + rtol=5e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=8.25, + b=-7.75, + c=-0.5, + z=(0.5263157894736841+0.7368421052631575j), + expected=(-81177.775295738+56079.73286548954j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=4.080187217753502, + b=2.050308316530781, + c=-0.906685989801748, + z=(0.9473684210526314+0.3157894736842106j), + expected=(1192868.5068926765+3624210.8182139914j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-7.937789122896016, + b=-1.92872979730171, + c=8.031683612216888, + z=(0.5263157894736841+0.7368421052631575j), + expected=(1.8286341846195202+1.9295255682312178j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=1.0272592605282642, + b=1.0561196186065624, + c=16.056809865262608, + z=(0.7368421052631575-0.5263157894736843j), + expected=(1.0514645669696452-0.0430834059440128j), + rtol=5e-10, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=16.087593263474208, + b=-15.964218273004214, + c=2.0397202577726152, + z=(0.5263157894736841+0.7368421052631575j), + expected=(541983.236432269+288200.2043029435j), + rtol=5e-13, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=2.25, + b=8.25, + c=1.5, + z=(0.5263157894736841-0.736842105263158j), + expected=(-10.931988086039945+1.9136272843579096j), + rtol=1e-15, + ), + ), + ] + ) + def test_region3(self, hyp2f1_test_case): + """0.9 <= |z| <= 1 and |1 - z| < 0.9.""" + a, b, c, z, expected, rtol = hyp2f1_test_case + assert 0.9 <= abs(z) <= 1 and abs(1 - z) < 0.9 # Tests the test + assert_allclose(hyp2f1(a, b, c, z), expected, rtol=rtol) + + @pytest.mark.parametrize( + "hyp2f1_test_case", + [ + pytest.param( + Hyp2f1TestCase( + a=16.25, + b=4.25, + c=2.5, + z=(0.4931034482758623-0.7965517241379311j), + expected=(38.41207903409937-30.510151276075792j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=2.0, + b=16.087593263474208, + c=16.088264119063613, + z=(0.5689655172413794-0.7965517241379311j), + expected=(-0.6667857912761286-1.0206224321443573j), + rtol=1e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=8.0, + b=1.0272592605282642, + c=-7.949900487447654, + z=(0.4931034482758623-0.7965517241379311j), + expected=(1679024.1647997478-2748129.775857212j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=4.080187217753502, + b=16.0, + c=-7.949900487447654, + z=(0.4931034482758623-0.7965517241379311j), + expected=(424747226301.16986-1245539049327.2856j), + rtol=1e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=2.02764642551431, + b=-15.964218273004214, + c=4.0, + z=(0.4931034482758623-0.7965517241379311j), + expected=(-0.0057826199201757595+0.026359861999025885j), + rtol=5e-06, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=2.02764642551431, + b=-0.9629749245209605, + c=2.0397202577726152, + z=(0.5689655172413794-0.7965517241379311j), + expected=(0.4671901063492606+0.7769632229834897j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=2.0, + b=-3.956227226099288, + c=-7.949900487447654, + z=(0.4931034482758623+0.7965517241379312j), + expected=(0.9422283708145973+1.3476905754773343j), + rtol=5e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=1.0, + b=-15.980848054962111, + c=-15.964218273004214, + z=(0.4931034482758623-0.7965517241379311j), + expected=(0.4168719497319604-0.9770953555235625j), + rtol=5e-10, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-0.5, + b=16.088264119063613, + c=2.5, + z=(0.5689655172413794+0.7965517241379312j), + expected=(1.279096377550619-2.173827694297929j), + rtol=5e-12, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-1.9214641416286231, + b=4.0013768449590685, + c=2.0397202577726152, + z=(0.4931034482758623+0.7965517241379312j), + expected=(-2.071520656161738-0.7846098268395909j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-0.9220024191881196, + b=8.0, + c=-0.9629749245209605, + z=(0.5689655172413794-0.7965517241379311j), + expected=(-7.740015495862889+3.386766435696699j), + rtol=5e-12, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-1.9214641416286231, + b=16.088264119063613, + c=-7.93846038215665, + z=(0.4931034482758623+0.7965517241379312j), + expected=(-6318.553685853241-7133.416085202879j), + rtol=5e-9, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-15.980848054962111, + b=-3.9316537064827854, + c=16.056809865262608, + z=(0.5689655172413794+0.7965517241379312j), + expected=(-0.8854577905547399+8.135089099967278j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-1.9214641416286231, + b=-0.9629749245209605, + c=4.078873014294075, + z=(0.4931034482758623+0.7965517241379312j), + expected=(1.224291301521487+0.36014711766402485j), + rtol=1e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-15.75, + b=-0.75, + c=-1.5, + z=(0.4931034482758623+0.7965517241379312j), + expected=(-1.5765685855028473-3.9399766961046323j), + rtol=1e-3, + ), + marks=pytest.mark.xfail( + reason="Unhandled parameters." + ) + ), + pytest.param( + Hyp2f1TestCase( + a=-15.980848054962111, + b=-1.92872979730171, + c=-7.93846038215665, + z=(0.5689655172413794-0.7965517241379311j), + expected=(56.794588688231194+4.556286783533971j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=4.5, + b=4.5, + c=2.050308316530781, + z=(0.5689655172413794+0.7965517241379312j), + expected=(-4.251456563455306+6.737837111569671j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=4.5, + b=8.5, + c=-1.92872979730171, + z=(0.4931034482758623-0.7965517241379311j), + expected=(2177143.9156599627-3313617.2748088865j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=2.5, + b=-1.5, + c=4.0013768449590685, + z=(0.4931034482758623-0.7965517241379311j), + expected=(0.45563554481603946+0.6212000158060831j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=8.5, + b=-7.5, + c=-15.964218273004214, + z=(0.4931034482758623+0.7965517241379312j), + expected=(61.03201617828073-37.185626416756214j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-15.5, + b=16.5, + c=4.0013768449590685, + z=(0.4931034482758623+0.7965517241379312j), + expected=(-33143.425963520735+20790.608514722644j), + rtol=1e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-0.5, + b=4.5, + c=-0.9629749245209605, + z=(0.5689655172413794+0.7965517241379312j), + expected=(30.778600270824423-26.65160354466787j), + rtol=5e-13, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-0.5, + b=-3.5, + c=16.088264119063613, + z=(0.5689655172413794-0.7965517241379311j), + expected=(1.0629792615560487-0.08308454486044772j), + rtol=1e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-3.5, + b=-7.5, + c=-0.9629749245209605, + z=(0.4931034482758623-0.7965517241379311j), + expected=(17431.571802591767+3553.7129767034507j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=2.25, + b=8.25, + c=16.5, + z=(0.11379310344827598+0.9482758620689657j), + expected=(0.4468600750211926+0.7313214934036885j), + rtol=1e-3, + ), + marks=pytest.mark.xfail( + reason="Unhandled parameters." + ) + ), + pytest.param( + Hyp2f1TestCase( + a=8.25, + b=16.25, + c=4.5, + z=(0.3413793103448277+0.8724137931034486j), + expected=(-3.905704438293991+3.693347860329299j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=4.25, + b=4.25, + c=-0.5, + z=(0.11379310344827598-0.9482758620689655j), + expected=(-40.31777941834244-89.89852492432011j), + rtol=5e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=1.0272592605282642, + b=8.0, + c=-15.964218273004214, + z=(0.11379310344827598-0.9482758620689655j), + expected=(52584.347773055284-109197.86244309516j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=8.095813935368371, + b=-15.964218273004214, + c=16.056809865262608, + z=(0.03793103448275881+0.9482758620689657j), + expected=(-1.187733570412592-1.5147865053584582j), + rtol=5e-10, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=4.080187217753502, + b=-3.9316537064827854, + c=1.0651378143226575, + z=(0.26551724137931054+0.9482758620689657j), + expected=(13.077494677898947+35.071599628224966j), + rtol=5e-13, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=4.080187217753502, + b=-3.5, + c=-3.5, + z=(0.26551724137931054+0.8724137931034486j), + expected=(-0.5359656237994614-0.2344483936591811j), + rtol=5e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=4.25, + b=-3.75, + c=-1.5, + z=(0.26551724137931054+0.9482758620689657j), + expected=(1204.8114871663133+64.41022826840198j), + rtol=5e-13, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-1.9214641416286231, + b=16.0, + c=4.0013768449590685, + z=(0.03793103448275881-0.9482758620689655j), + expected=(-9.85268872413994+7.011107558429154j), + rtol=5e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-7.937789122896016, + b=16.0, + c=4.0013768449590685, + z=(0.3413793103448277-0.8724137931034484j), + expected=(528.5522951158454-1412.21630264791j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-15.5, + b=1.0561196186065624, + c=-7.5, + z=(0.4172413793103451+0.8724137931034486j), + expected=(133306.45260685298+256510.7045225382j), + rtol=5e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-7.937789122896016, + b=8.077282662161238, + c=-15.963511401609862, + z=(0.3413793103448277-0.8724137931034484j), + expected=(-0.998555715276967+2.774198742229889j), + rtol=5e-11, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-7.75, + b=-0.75, + c=1.5, + z=(0.11379310344827598-0.9482758620689655j), + expected=(2.072445019723025-2.9793504811373515j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-15.5, + b=-1.92872979730171, + c=1.5, + z=(0.11379310344827598-0.9482758620689655j), + expected=(-41.87581944176649-32.52980303527139j), + rtol=5e-13, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-3.75, + b=-15.75, + c=-0.5, + z=(0.11379310344827598-0.9482758620689655j), + expected=(-3729.6214864209774-30627.510509112635j), + rtol=5e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-3.956227226099288, + b=-15.964218273004214, + c=-0.906685989801748, + z=(0.03793103448275881+0.9482758620689657j), + expected=(-131615.07820609974+145596.13384245415j), + rtol=5e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=1.5, + b=16.5, + c=16.088264119063613, + z=(0.26551724137931054+0.8724137931034486j), + expected=(0.18981844071070744+0.7855036242583742j), + rtol=1e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=16.5, + b=8.5, + c=-3.9316537064827854, + z=(0.11379310344827598-0.9482758620689655j), + expected=(110224529.2376068+128287212.04290268j), + rtol=5e-13, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=2.5, + b=-7.5, + c=4.0013768449590685, + z=(0.3413793103448277-0.8724137931034484j), + expected=(0.2722302180888523-0.21790187837266162j), + rtol=1.2e-12, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=8.5, + b=-7.5, + c=-15.964218273004214, + z=(0.11379310344827598-0.9482758620689655j), + expected=(-2.8252338010989035+2.430661949756161j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-3.5, + b=16.5, + c=4.0013768449590685, + z=(0.03793103448275881+0.9482758620689657j), + expected=(-20.604894257647945+74.5109432558078j), + rtol=5e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-7.5, + b=8.5, + c=-0.9629749245209605, + z=(0.3413793103448277+0.8724137931034486j), + expected=(-2764422.521269463-3965966.9965808876j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-1.5, + b=-0.5, + c=1.0561196186065624, + z=(0.26551724137931054+0.9482758620689657j), + expected=(1.2262338560994905+0.6545051266925549j), + rtol=1e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-0.5, + b=-15.5, + c=-7.949900487447654, + z=(0.4172413793103451-0.8724137931034484j), + expected=(-2258.1590330318213+8860.193389158803j), + rtol=1.4e-10, + ), + ), + ] + ) + def test_region4(self, hyp2f1_test_case): + """0.9 <= |z| <= 1 and |1 - z| >= 1. + + This region is unhandled by of the standard transformations and + needs special care. + """ + a, b, c, z, expected, rtol = hyp2f1_test_case + assert 0.9 <= abs(z) <= 1 and abs(1 - z) >= 0.9 # Tests the test + assert_allclose(hyp2f1(a, b, c, z), expected, rtol=rtol) + + @pytest.mark.parametrize( + "hyp2f1_test_case", + [ + pytest.param( + Hyp2f1TestCase( + a=4.5, + b=16.088264119063613, + c=8.5, + z=(0.6448275862068968+0.8724137931034486j), + expected=(0.018601324701770394-0.07618420586062377j), + rtol=5e-08, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=8.25, + b=4.25, + c=4.5, + z=(0.6448275862068968-0.8724137931034484j), + expected=(-1.391549471425551-0.118036604903893j), + rtol=5e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=2.02764642551431, + b=2.050308316530781, + c=-1.9631175993998025, + z=(0.6448275862068968+0.8724137931034486j), + expected=(-2309.178768155151-1932.7247727595172j), + rtol=5e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=16.087593263474208, + b=1.0, + c=-15.964218273004214, + z=(0.6448275862068968+0.8724137931034486j), + expected=(85592537010.05054-8061416766688.324j), + rtol=2e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=8.095813935368371, + b=-0.5, + c=1.5, + z=(0.6448275862068968+0.8724137931034486j), + expected=(1.2334498208515172-2.1639498536219732j), + rtol=5e-11, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=16.087593263474208, + b=-15.964218273004214, + c=4.0, + z=(0.6448275862068968+0.8724137931034486j), + expected=(102266.35398605966-44976.97828737755j), + rtol=1e-3, + ), + marks=pytest.mark.xfail( + reason="Unhandled parameters." + ) + ), + pytest.param( + Hyp2f1TestCase( + a=4.0, + b=-3.956227226099288, + c=-15.964218273004214, + z=(0.6448275862068968-0.8724137931034484j), + expected=(-2.9590030930007236-4.190770764773225j), + rtol=5e-13, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=4.080187217753502, + b=-15.5, + c=-7.5, + z=(0.5689655172413794-0.8724137931034484j), + expected=(-112554838.92074208+174941462.9202412j), + rtol=5e-05, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-15.980848054962111, + b=2.050308316530781, + c=1.0, + z=(0.6448275862068968-0.8724137931034484j), + expected=(3.7519882374080145+7.360753798667486j), + rtol=5e-13, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-7.937789122896016, + b=2.050308316530781, + c=4.0, + z=(0.6448275862068968-0.8724137931034484j), + expected=(0.000181132943964693+0.07742903103815582j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-7.937789122896016, + b=4.0013768449590685, + c=-1.9631175993998025, + z=(0.5689655172413794+0.8724137931034486j), + expected=(386338.760913596-386166.51762171905j), + rtol=5e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-15.980848054962111, + b=8.0, + c=-1.92872979730171, + z=(0.6448275862068968+0.8724137931034486j), + expected=(1348667126.3444858-2375132427.158893j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-3.5, + b=-0.9629749245209605, + c=4.5, + z=(0.5689655172413794+0.8724137931034486j), + expected=(1.428353429538678+0.6472718120804372j), + rtol=5e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-7.937789122896016, + b=-0.9629749245209605, + c=2.0397202577726152, + z=(0.5689655172413794-0.8724137931034484j), + expected=(3.1439267526119643-3.145305240375117j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-1.9214641416286231, + b=-15.964218273004214, + c=-7.93846038215665, + z=(0.6448275862068968-0.8724137931034484j), + expected=(75.27467675681773+144.0946946292215j), + rtol=1e-07, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-3.75, + b=-7.75, + c=-7.5, + z=(0.5689655172413794+0.8724137931034486j), + expected=(-0.3699450626264222+0.8732812475910993j), + rtol=5e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=1.5, + b=16.5, + c=1.0561196186065624, + z=(0.5689655172413794-0.8724137931034484j), + expected=(5.5361025821300665-2.4709693474656285j), + rtol=5e-09, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=1.5, + b=8.5, + c=-3.9316537064827854, + z=(0.6448275862068968-0.8724137931034484j), + expected=(-782805.6699207705-537192.581278909j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=2.5, + b=-15.5, + c=1.0561196186065624, + z=(0.6448275862068968+0.8724137931034486j), + expected=(12.345113400639693-14.993248992902007j), + rtol=0.0005, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=1.5, + b=-0.5, + c=-15.964218273004214, + z=(0.6448275862068968+0.8724137931034486j), + expected=(23.698109392667842+97.15002033534108j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-7.5, + b=16.5, + c=4.0013768449590685, + z=(0.6448275862068968-0.8724137931034484j), + expected=(1115.2978631811834+915.9212658718577j), + rtol=5e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-15.5, + b=16.5, + c=-0.9629749245209605, + z=(0.6448275862068968+0.8724137931034486j), + expected=(642077722221.6489+535274495398.21027j), + rtol=5e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-7.5, + b=-3.5, + c=4.0013768449590685, + z=(0.5689655172413794+0.8724137931034486j), + expected=(-5.689219222945697+16.877463062787143j), + rtol=5e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-15.5, + b=-1.5, + c=-0.9629749245209605, + z=(0.5689655172413794-0.8724137931034484j), + expected=(-44.32070290703576+1026.9127058617403j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=16.25, + b=2.25, + c=4.5, + z=(0.11379310344827598-1.024137931034483j), + expected=(-0.021965227124574663+0.009908300237809064j), + rtol=1e-3, + ), + marks=pytest.mark.xfail( + reason="Unhandled parameters." + ) + ), + pytest.param( + Hyp2f1TestCase( + a=2.02764642551431, + b=1.5, + c=16.5, + z=(0.26551724137931054+1.024137931034483j), + expected=(1.0046072901244183+0.19945500134119992j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=16.087593263474208, + b=1.0, + c=-3.9316537064827854, + z=(0.3413793103448277+0.9482758620689657j), + expected=(21022.30133421465+49175.98317370489j), + rtol=5e-13, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=4.080187217753502, + b=16.088264119063613, + c=-1.9631175993998025, + z=(0.4172413793103451-0.9482758620689655j), + expected=(-7024239.358547302+2481375.02681063j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=16.25, + b=-15.75, + c=1.5, + z=(0.18965517241379315+1.024137931034483j), + expected=(92371704.94848-403546832.548352j), + rtol=5e-06, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=8.5, + b=-7.949900487447654, + c=8.5, + z=(0.26551724137931054-1.024137931034483j), + expected=(1.9335109845308265+5.986542524829654j), + rtol=5e-10, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=8.095813935368371, + b=-1.92872979730171, + c=-7.93846038215665, + z=(0.4931034482758623+0.8724137931034486j), + expected=(-122.52639696039328-59.72428067512221j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=16.25, + b=-1.75, + c=-1.5, + z=(0.4931034482758623+0.9482758620689657j), + expected=(-90.40642053579428+50.50649180047921j), + rtol=5e-08, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-3.5, + b=8.077282662161238, + c=16.5, + z=(0.4931034482758623+0.9482758620689657j), + expected=(-0.2155745818150323-0.564628986876639j), + rtol=5e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-0.9220024191881196, + b=1.0561196186065624, + c=8.031683612216888, + z=(0.4172413793103451-0.9482758620689655j), + expected=(0.9503140488280465+0.11574960074292677j), + rtol=5e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-0.75, + b=2.25, + c=-15.5, + z=(0.4172413793103451+0.9482758620689657j), + expected=(0.9285862488442175+0.8203699266719692j), + rtol=5e-13, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-7.75, + b=4.25, + c=-15.5, + z=(0.3413793103448277-0.9482758620689655j), + expected=(-1.0509834850116921-1.1145522325486075j), + rtol=1.1e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-7.937789122896016, + b=-0.9629749245209605, + c=2.0397202577726152, + z=(0.4931034482758623-0.9482758620689655j), + expected=(2.88119116536769-3.4249933450696806j), + rtol=5e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-15.5, + b=-15.964218273004214, + c=16.5, + z=(0.18965517241379315+1.024137931034483j), + expected=(199.65868451496038+347.79384207302877j), + rtol=5e-12, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-15.75, + b=-15.75, + c=-3.5, + z=(0.4931034482758623-0.8724137931034484j), + expected=(-208138312553.07013+58631611809.026955j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-7.937789122896016, + b=-15.5, + c=-7.5, + z=(0.3413793103448277+0.9482758620689657j), + expected=(-23032.90519856288-18256.94050457296j), + rtol=5e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=4.5, + b=1.5, + c=1.0561196186065624, + z=(0.4931034482758623-0.8724137931034484j), + expected=(1.507342459587056+1.2332023580148403j), + rtol=1e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=2.5, + b=4.5, + c=-3.9316537064827854, + z=(0.4172413793103451+0.9482758620689657j), + expected=(7044.766127108853-40210.365567285575j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=1.5, + b=-1.5, + c=1.0561196186065624, + z=(0.03793103448275881+1.024137931034483j), + expected=(0.2725347741628333-2.247314875514784j), + rtol=1e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=4.5, + b=-1.5, + c=-7.949900487447654, + z=(0.26551724137931054+1.024137931034483j), + expected=(-11.250200011017546+12.597393659160472j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-7.5, + b=8.5, + c=16.088264119063613, + z=(0.26551724137931054+1.024137931034483j), + expected=(-0.18515160890991517+0.7959014164484782j), + rtol=2e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-7.5, + b=16.5, + c=-3.9316537064827854, + z=(0.3413793103448277-1.024137931034483j), + expected=(998246378.8556538+1112032928.103645j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-1.5, + b=-3.5, + c=2.050308316530781, + z=(0.03793103448275881+1.024137931034483j), + expected=(0.5527670397711952+2.697662715303637j), + rtol=1.2e-15, # rtol bumped from 1e-15 in gh18414 + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-15.5, + b=-1.5, + c=-0.9629749245209605, + z=(0.4931034482758623-0.8724137931034484j), + expected=(55.396931662136886+968.467463806326j), + rtol=5e-14, + ), + ), + ] + ) + def test_region5(self, hyp2f1_test_case): + """1 < |z| < 1.1 and |1 - z| >= 0.9 and real(z) >= 0""" + a, b, c, z, expected, rtol = hyp2f1_test_case + assert 1 < abs(z) < 1.1 and abs(1 - z) >= 0.9 and z.real >= 0 + assert_allclose(hyp2f1(a, b, c, z), expected, rtol=rtol) + + @pytest.mark.parametrize( + "hyp2f1_test_case", + [ + pytest.param( + Hyp2f1TestCase( + a=8.095813935368371, + b=4.0013768449590685, + c=4.078873014294075, + z=(-0.9473684210526316+0.5263157894736841j), + expected=(-0.0018093573941378783+0.003481887377423739j), + rtol=5e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=16.087593263474208, + b=2.050308316530781, + c=1.0651378143226575, + z=(-0.736842105263158-0.736842105263158j), + expected=(-0.00023401243818780545-1.7983496305603562e-05j), + rtol=1e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=1.0272592605282642, + b=8.077282662161238, + c=4.078873014294075, + z=(-0.5263157894736843-0.9473684210526316j), + expected=(0.22359773002226846-0.24092487123993353j), + rtol=1e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=1.0272592605282642, + b=2.050308316530781, + c=-15.963511401609862, + z=(-0.9473684210526316-0.5263157894736843j), + expected=(1.191573745740011+0.14347394589721466j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=4.080187217753502, + b=4.0013768449590685, + c=-15.963511401609862, + z=(-0.9473684210526316-0.5263157894736843j), + expected=(31.822620756901784-66.09094396747611j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=4.080187217753502, + b=8.077282662161238, + c=-7.93846038215665, + z=(-0.9473684210526316+0.5263157894736841j), + expected=(207.16750179245952+34.80478274924269j), + rtol=5e-12, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=8.095813935368371, + b=-7.949900487447654, + c=8.031683612216888, + z=(-0.736842105263158+0.7368421052631575j), + expected=(-159.62429364277145+9.154224290644898j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=1.0272592605282642, + b=-1.92872979730171, + c=16.056809865262608, + z=(-0.9473684210526316+0.5263157894736841j), + expected=(1.121122351247184-0.07170260470126685j), + rtol=5e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=16.087593263474208, + b=-0.9629749245209605, + c=16.056809865262608, + z=(-0.9473684210526316+0.5263157894736841j), + expected=(1.9040596681316053-0.4951799449960107j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=1.0272592605282642, + b=-1.92872979730171, + c=-0.906685989801748, + z=(-0.9473684210526316-0.5263157894736843j), + expected=(-14.496623497780739-21.897524523299875j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=4.080187217753502, + b=-3.9316537064827854, + c=-3.9924618758357022, + z=(-0.5263157894736843-0.9473684210526316j), + expected=(36.33473466026878+253.88728442029577j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=1.0272592605282642, + b=-15.964218273004214, + c=-0.906685989801748, + z=(-0.9473684210526316+0.5263157894736841j), + expected=(1505052.5653144997-50820766.81043443j), + rtol=1e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-3.956227226099288, + b=4.0013768449590685, + c=1.0651378143226575, + z=(-0.5263157894736843+0.9473684210526314j), + expected=(-127.79407519260877-28.69899444941112j), + rtol=5e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-1.9214641416286231, + b=8.077282662161238, + c=16.056809865262608, + z=(-0.9473684210526316-0.5263157894736843j), + expected=(2.0623331933754976+0.741234463565458j), + rtol=5e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-3.956227226099288, + b=8.077282662161238, + c=2.0397202577726152, + z=(-0.9473684210526316+0.5263157894736841j), + expected=(30.729193458862525-292.5700835046965j), + rtol=1e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-1.9214641416286231, + b=1.0561196186065624, + c=-1.9631175993998025, + z=(-0.5263157894736843-0.9473684210526316j), + expected=(1.1285917906203495-0.735264575450189j), + rtol=5e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-0.9220024191881196, + b=1.0561196186065624, + c=-3.9924618758357022, + z=(-0.736842105263158+0.7368421052631575j), + expected=(0.6356474446678052-0.02429663008952248j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-1.9214641416286231, + b=16.088264119063613, + c=-7.93846038215665, + z=(-0.736842105263158+0.7368421052631575j), + expected=(0.4718880510273174+0.655083067736377j), + rtol=1e-11, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-7.937789122896016, + b=-3.9316537064827854, + c=16.056809865262608, + z=(-0.9473684210526316+0.5263157894736841j), + expected=(-0.14681550942352714+0.16092206364265146j), + rtol=5e-11, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-0.9220024191881196, + b=-15.964218273004214, + c=1.0651378143226575, + z=(-0.5263157894736843+0.9473684210526314j), + expected=(-6.436835190526225+22.883156700606182j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-0.9220024191881196, + b=-7.949900487447654, + c=4.078873014294075, + z=(-0.9473684210526316-0.5263157894736843j), + expected=(-0.7505682955068583-1.1026583264249945j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-3.956227226099288, + b=-3.9316537064827854, + c=-7.93846038215665, + z=(-0.9473684210526316-0.5263157894736843j), + expected=(3.6247814989198166+2.596041360148318j), + rtol=5e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-3.956227226099288, + b=-15.964218273004214, + c=-1.9631175993998025, + z=(-0.5263157894736843-0.9473684210526316j), + expected=(-59537.65287927933-669074.4342539902j), + rtol=5e-15, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=-3.956227226099288, + b=-15.964218273004214, + c=-1.9631175993998025, + z=(-0.9473684210526316-0.5263157894736843j), + expected=(-433084.9970266166+431088.393918521j), + rtol=5e-14, + ), + ), + pytest.param( + Hyp2f1TestCase( + a=1, + b=1, + c=4, + z=(3 + 4j), + expected=(0.49234384000963544+0.6051340616612397j), + rtol=5e-14, + ), + ), + ] + ) + def test_region6(self, hyp2f1_test_case): + """|z| > 1 but not in region 5.""" + a, b, c, z, expected, rtol = hyp2f1_test_case + assert ( + abs(z) > 1 and + not (1 < abs(z) < 1.1 and abs(1 - z) >= 0.9 and z.real >= 0) + ) + assert_allclose(hyp2f1(a, b, c, z), expected, rtol=rtol) + + + @pytest.mark.parametrize( + "hyp2f1_test_case", + [ + # Broke when fixing gamma pole behavior in gh-21827 + pytest.param( + Hyp2f1TestCase( + a=1.3, + b=-0.2, + c=0.3, + z=-2.1, + expected=1.8202169687521206, + rtol=5e-15, + ), + ), + ] + ) + def test_miscellaneous(self, hyp2f1_test_case ): + a, b, c, z, expected, rtol = hyp2f1_test_case + assert_allclose(hyp2f1(a, b, c, z), expected, rtol=rtol) + + @pytest.mark.slow + @check_version(mpmath, "1.0.0") + def test_test_hyp2f1(self): + """Test that expected values match what is computed by mpmath. + + This gathers the parameters for the test cases out of the pytest marks. + The parameters are a, b, c, z, expected, rtol, where expected should + be the value of hyp2f1(a, b, c, z) computed with mpmath. The test + recomputes hyp2f1(a, b, c, z) using mpmath and verifies that expected + actually is the correct value. This allows the data for the tests to + live within the test code instead of an external datafile, while + avoiding having to compute the results with mpmath during the test, + except for when slow tests are being run. + """ + test_methods = [ + test_method for test_method in dir(self) + if test_method.startswith('test') and + # Filter properties and attributes (futureproofing). + callable(getattr(self, test_method)) and + # Filter out this test + test_method != 'test_test_hyp2f1' + ] + for test_method in test_methods: + params = self._get_test_parameters(getattr(self, test_method)) + for a, b, c, z, expected, _ in params: + assert_allclose(mp_hyp2f1(a, b, c, z), expected, rtol=2.25e-16) + + def _get_test_parameters(self, test_method): + """Get pytest.mark parameters for a test in this class.""" + return [ + case.values[0] for mark in test_method.pytestmark + if mark.name == 'parametrize' + for case in mark.args[1] + ] + +class TestHyp2f1ExtremeInputs: + + @pytest.mark.parametrize("a", [1.0, 2.0, 3.0, -np.inf, np.inf]) + @pytest.mark.parametrize("b", [3.0, 4.0, 5.0, -np.inf, np.inf]) + @pytest.mark.parametrize("c", [3.0, 5.0, 6.0, 7.0]) + @pytest.mark.parametrize("z", [4.0 + 1.0j]) + def test_inf_a_b(self, a, b, c, z): + if np.any(np.isinf(np.asarray([a, b]))): + assert(np.isnan(hyp2f1(a, b, c, z))) + + def test_large_a_b(self): + assert(np.isnan(hyp2f1(10**7, 1.0, 3.0, 4.0 + 1.0j))) + assert(np.isnan(hyp2f1(-10**7, 1.0, 3.0, 4.0 + 1.0j))) + + assert(np.isnan(hyp2f1(1.0, 10**7, 3.0, 4.0 + 1.0j))) + assert(np.isnan(hyp2f1(1.0, -10**7, 3.0, 4.0 + 1.0j))) + + # Already correct in main but testing for surety + assert(np.isnan(hyp2f1(np.inf, 1.0, 3.0, 4.0))) + assert(np.isnan(hyp2f1(1.0, np.inf, 3.0, 4.0))) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_hypergeometric.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_hypergeometric.py new file mode 100644 index 0000000000000000000000000000000000000000..1ad092491905ae66c39cd881836054aef3dc6f6e --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_hypergeometric.py @@ -0,0 +1,234 @@ +import pytest +import numpy as np +from numpy.testing import assert_allclose, assert_equal +import scipy.special as sc + + +class TestHyperu: + + def test_negative_x(self): + a, b, x = np.meshgrid( + [-1, -0.5, 0, 0.5, 1], + [-1, -0.5, 0, 0.5, 1], + np.linspace(-100, -1, 10), + ) + assert np.all(np.isnan(sc.hyperu(a, b, x))) + + def test_special_cases(self): + assert sc.hyperu(0, 1, 1) == 1.0 + + @pytest.mark.parametrize('a', [0.5, 1, np.nan]) + @pytest.mark.parametrize('b', [1, 2, np.nan]) + @pytest.mark.parametrize('x', [0.25, 3, np.nan]) + def test_nan_inputs(self, a, b, x): + assert np.isnan(sc.hyperu(a, b, x)) == np.any(np.isnan([a, b, x])) + + @pytest.mark.parametrize( + 'a,b,x,expected', + [(0.21581740448533887, 1.0, 1e-05, 3.6030558839391325), + (0.21581740448533887, 1.0, 0.00021544346900318823, 2.8783254988948976), + (0.21581740448533887, 1.0, 0.004641588833612777, 2.154928216691109), + (0.21581740448533887, 1.0, 0.1, 1.446546638718792), + (0.0030949064301273865, 1.0, 1e-05, 1.0356696454116199), + (0.0030949064301273865, 1.0, 0.00021544346900318823, 1.0261510362481985), + (0.0030949064301273865, 1.0, 0.004641588833612777, 1.0166326903402296), + (0.0030949064301273865, 1.0, 0.1, 1.0071174207698674), + (0.1509924314279033, 1.0, 1e-05, 2.806173846998948), + (0.1509924314279033, 1.0, 0.00021544346900318823, 2.3092158526816124), + (0.1509924314279033, 1.0, 0.004641588833612777, 1.812905980588048), + (0.1509924314279033, 1.0, 0.1, 1.3239738117634872), + (-0.010678995342969011, 1.0, 1e-05, 0.8775194903781114), + (-0.010678995342969011, 1.0, 0.00021544346900318823, 0.9101008998540128), + (-0.010678995342969011, 1.0, 0.004641588833612777, 0.9426854294058609), + (-0.010678995342969011, 1.0, 0.1, 0.9753065150174902), + (-0.06556622211831487, 1.0, 1e-05, 0.26435429752668904), + (-0.06556622211831487, 1.0, 0.00021544346900318823, 0.4574756033875781), + (-0.06556622211831487, 1.0, 0.004641588833612777, 0.6507121093358457), + (-0.06556622211831487, 1.0, 0.1, 0.8453129788602187), + (-0.21628242470175185, 1.0, 1e-05, -1.2318314201114489), + (-0.21628242470175185, 1.0, 0.00021544346900318823, -0.6704694233529538), + (-0.21628242470175185, 1.0, 0.004641588833612777, -0.10795098653682857), + (-0.21628242470175185, 1.0, 0.1, 0.4687227684115524)] + ) + def test_gh_15650_mp(self, a, b, x, expected): + # See https://github.com/scipy/scipy/issues/15650 + # b == 1, |a| < 0.25, 0 < x < 1 + # + # This purpose of this test is to check the accuracy of results + # in the region that was impacted by gh-15650. + # + # Reference values computed with mpmath using the script: + # + # import itertools as it + # import numpy as np + # + # from mpmath import mp + # + # rng = np.random.default_rng(1234) + # + # cases = [] + # for a, x in it.product( + # np.random.uniform(-0.25, 0.25, size=6), + # np.logspace(-5, -1, 4), + # ): + # with mp.workdps(100): + # cases.append((float(a), 1.0, float(x), float(mp.hyperu(a, 1.0, x)))) + assert_allclose(sc.hyperu(a, b, x), expected, rtol=1e-13) + + def test_gh_15650_sanity(self): + # The purpose of this test is to sanity check hyperu in the region that + # was impacted by gh-15650 by making sure there are no excessively large + # results, as were reported there. + a = np.linspace(-0.5, 0.5, 500) + x = np.linspace(1e-6, 1e-1, 500) + a, x = np.meshgrid(a, x) + results = sc.hyperu(a, 1.0, x) + assert np.all(np.abs(results) < 1e3) + + +class TestHyp1f1: + + @pytest.mark.parametrize('a, b, x', [ + (np.nan, 1, 1), + (1, np.nan, 1), + (1, 1, np.nan) + ]) + def test_nan_inputs(self, a, b, x): + assert np.isnan(sc.hyp1f1(a, b, x)) + + def test_poles(self): + assert_equal(sc.hyp1f1(1, [0, -1, -2, -3, -4], 0.5), np.inf) + + @pytest.mark.parametrize('a, b, x, result', [ + (-1, 1, 0.5, 0.5), + (1, 1, 0.5, 1.6487212707001281468), + (2, 1, 0.5, 2.4730819060501922203), + (1, 2, 0.5, 1.2974425414002562937), + (-10, 1, 0.5, -0.38937441413785204475) + ]) + def test_special_cases(self, a, b, x, result): + # Hit all the special case branches at the beginning of the + # function. Desired answers computed using Mpmath. + assert_allclose(sc.hyp1f1(a, b, x), result, atol=0, rtol=1e-15) + + @pytest.mark.parametrize('a, b, x, result', [ + (1, 1, 0.44, 1.5527072185113360455), + (-1, 1, 0.44, 0.55999999999999999778), + (100, 100, 0.89, 2.4351296512898745592), + (-100, 100, 0.89, 0.40739062490768104667), + (1.5, 100, 59.99, 3.8073513625965598107), + (-1.5, 100, 59.99, 0.25099240047125826943) + ]) + def test_geometric_convergence(self, a, b, x, result): + # Test the region where we are relying on the ratio of + # + # (|a| + 1) * |x| / |b| + # + # being small. Desired answers computed using Mpmath + assert_allclose(sc.hyp1f1(a, b, x), result, atol=0, rtol=1e-15) + + @pytest.mark.parametrize('a, b, x, result', [ + (-1, 1, 1.5, -0.5), + (-10, 1, 1.5, 0.41801777430943080357), + (-25, 1, 1.5, 0.25114491646037839809), + (-50, 1, 1.5, -0.25683643975194756115), + (-80, 1, 1.5, -0.24554329325751503601), + (-150, 1, 1.5, -0.173364795515420454496), + ]) + def test_a_negative_integer(self, a, b, x, result): + # Desired answers computed using Mpmath. + assert_allclose(sc.hyp1f1(a, b, x), result, atol=0, rtol=2e-14) + + @pytest.mark.parametrize('a, b, x, expected', [ + (0.01, 150, -4, 0.99973683897677527773), # gh-3492 + (1, 5, 0.01, 1.0020033381011970966), # gh-3593 + (50, 100, 0.01, 1.0050126452421463411), # gh-3593 + (1, 0.3, -1e3, -7.011932249442947651455e-04), # gh-14149 + (1, 0.3, -1e4, -7.001190321418937164734e-05), # gh-14149 + (9, 8.5, -350, -5.224090831922378361082e-20), # gh-17120 + (9, 8.5, -355, -4.595407159813368193322e-20), # gh-17120 + (75, -123.5, 15, 3.425753920814889017493e+06), + ]) + def test_assorted_cases(self, a, b, x, expected): + # Expected values were computed with mpmath.hyp1f1(a, b, x). + assert_allclose(sc.hyp1f1(a, b, x), expected, atol=0, rtol=1e-14) + + def test_a_neg_int_and_b_equal_x(self): + # This is a case where the Boost wrapper will call hypergeometric_pFq + # instead of hypergeometric_1F1. When we use a version of Boost in + # which https://github.com/boostorg/math/issues/833 is fixed, this + # test case can probably be moved into test_assorted_cases. + # The expected value was computed with mpmath.hyp1f1(a, b, x). + a = -10.0 + b = 2.5 + x = 2.5 + expected = 0.0365323664364104338721 + computed = sc.hyp1f1(a, b, x) + assert_allclose(computed, expected, atol=0, rtol=1e-13) + + @pytest.mark.parametrize('a, b, x, desired', [ + (-1, -2, 2, 2), + (-1, -4, 10, 3.5), + (-2, -2, 1, 2.5) + ]) + def test_gh_11099(self, a, b, x, desired): + # All desired results computed using Mpmath + assert sc.hyp1f1(a, b, x) == desired + + @pytest.mark.parametrize('a', [-3, -2]) + def test_x_zero_a_and_b_neg_ints_and_a_ge_b(self, a): + assert sc.hyp1f1(a, -3, 0) == 1 + + # In the following tests with complex z, the reference values + # were computed with mpmath.hyp1f1(a, b, z), and verified with + # Wolfram Alpha Hypergeometric1F1(a, b, z), except for the + # case a=0.1, b=1, z=7-24j, where Wolfram Alpha reported + # "Standard computation time exceeded". That reference value + # was confirmed in an online Matlab session, with the commands + # + # > format long + # > hypergeom(0.1, 1, 7-24i) + # ans = + # -3.712349651834209 + 4.554636556672912i + # + @pytest.mark.parametrize( + 'a, b, z, ref', + [(-0.25, 0.5, 1+2j, 1.1814553180903435-1.2792130661292984j), + (0.25, 0.5, 1+2j, 0.24636797405707597+1.293434354945675j), + (25, 1.5, -2j, -516.1771262822523+407.04142751922024j), + (12, -1.5, -10+20j, -5098507.422706547-1341962.8043508842j), + pytest.param( + 10, 250, 10-15j, 1.1985998416598884-0.8613474402403436j, + marks=pytest.mark.xfail, + ), + pytest.param( + 0.1, 1, 7-24j, -3.712349651834209+4.554636556672913j, + marks=pytest.mark.xfail, + ) + ], + ) + def test_complex_z(self, a, b, z, ref): + h = sc.hyp1f1(a, b, z) + assert_allclose(h, ref, rtol=4e-15) + + # The "legacy edge cases" mentioned in the comments in the following + # tests refers to the behavior of hyp1f1(a, b, x) when b is a nonpositive + # integer. In some subcases, the behavior of SciPy does not match that + # of Boost (1.81+), mpmath and Mathematica (via Wolfram Alpha online). + # If the handling of these edges cases is changed to agree with those + # libraries, these test will have to be updated. + + @pytest.mark.parametrize('b', [0, -1, -5]) + def test_legacy_case1(self, b): + # Test results of hyp1f1(0, n, x) for n <= 0. + # This is a legacy edge case. + # Boost (versions greater than 1.80), Mathematica (via Wolfram Alpha + # online) and mpmath all return 1 in this case, but SciPy's hyp1f1 + # returns inf. + assert_equal(sc.hyp1f1(0, b, [-1.5, 0, 1.5]), [np.inf, np.inf, np.inf]) + + def test_legacy_case2(self): + # This is a legacy edge case. + # In software such as boost (1.81+), mpmath and Mathematica, + # the value is 1. + assert sc.hyp1f1(-4, -3, 0) == np.inf diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_iv_ratio.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_iv_ratio.py new file mode 100644 index 0000000000000000000000000000000000000000..dafc35982c4d4869ead2136e74466f1c44c64556 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_iv_ratio.py @@ -0,0 +1,249 @@ +# This file contains unit tests for iv_ratio() and related functions. + +import pytest +import numpy as np +from numpy.testing import assert_equal, assert_allclose +from scipy.special._ufuncs import ( # type: ignore[attr-defined] + _iv_ratio as iv_ratio, + _iv_ratio_c as iv_ratio_c, +) + + +class TestIvRatio: + + @pytest.mark.parametrize('v,x,r', [ + (0.5, 0.16666666666666666, 0.16514041292462933), + (0.5, 0.3333333333333333, 0.32151273753163434), + (0.5, 0.5, 0.46211715726000974), + (0.5, 0.6666666666666666, 0.5827829453479101), + (0.5, 0.8333333333333335, 0.6822617902381698), + (1, 0.3380952380952381, 0.1666773049170313), + (1, 0.7083333333333333, 0.33366443586989925), + (1, 1.1666666666666667, 0.5023355231537423), + (1, 1.8666666666666665, 0.674616572252164), + (1, 3.560606060606061, 0.844207659503163), + (2.34, 0.7975238095238094, 0.16704903081553285), + (2.34, 1.7133333333333334, 0.3360215931268845), + (2.34, 2.953333333333333, 0.50681909317803), + (2.34, 5.0826666666666656, 0.6755252698800679), + (2.34, 10.869696969696973, 0.8379351104498762), + (56.789, 19.46575238095238, 0.1667020505391409), + (56.789, 42.55008333333333, 0.33353809996933026), + (56.789, 75.552, 0.5003932381177826), + (56.789, 135.76026666666667, 0.6670528221946127), + (56.789, 307.8642424242425, 0.8334999441460798), + ]) + def test_against_reference_values(self, v, x, r): + """The reference values are computed using mpmath as follows. + + from mpmath import mp + mp.dps = 100 + + def iv_ratio_mp(v, x): + return mp.besseli(v, x) / mp.besseli(v - 1, x) + + def _sample(n, *, v): + '''Return n positive real numbers x such that iv_ratio(v, x) are + roughly evenly spaced over (0, 1). The formula is taken from [1]. + + [1] Banerjee A., Dhillon, I. S., Ghosh, J., Sra, S. (2005). + "Clustering on the Unit Hypersphere using von Mises-Fisher + Distributions." Journal of Machine Learning Research, + 6(46):1345-1382. + ''' + r = np.arange(1, n+1) / (n+1) + return r * (2*v-r*r) / (1-r*r) + + for v in (0.5, 1, 2.34, 56.789): + xs = _sample(5, v=v) + for x in xs: + print(f"({v}, {x}, {float(iv_ratio_mp(v,x))}),") + """ + assert_allclose(iv_ratio(v, x), r, rtol=4e-16, atol=0) + + @pytest.mark.parametrize('v,x,r', [ + (1, np.inf, 1), + (np.inf, 1, 0), + ]) + def test_inf(self, v, x, r): + """If exactly one of v or x is inf and the other is within domain, + should return 0 or 1 accordingly.""" + assert_equal(iv_ratio(v, x), r) + + @pytest.mark.parametrize('v', [0.49, -np.inf, np.nan, np.inf]) + @pytest.mark.parametrize('x', [-np.finfo(float).smallest_normal, + -np.finfo(float).smallest_subnormal, + -np.inf, np.nan, np.inf]) + def test_nan(self, v, x): + """If at least one argument is out of domain, or if v = x = inf, + the function should return nan.""" + assert_equal(iv_ratio(v, x), np.nan) + + @pytest.mark.parametrize('v', [0.5, 1, np.finfo(float).max, np.inf]) + def test_zero_x(self, v): + """If x is +/-0.0, return x to ensure iv_ratio is an odd function.""" + assert_equal(iv_ratio(v, 0.0), 0.0) + assert_equal(iv_ratio(v, -0.0), -0.0) + + @pytest.mark.parametrize('v,x', [ + (1, np.finfo(float).smallest_normal), + (1, np.finfo(float).smallest_subnormal), + (1, np.finfo(float).smallest_subnormal*2), + (1e20, 123), + (np.finfo(float).max, 1), + (np.finfo(float).max, np.sqrt(np.finfo(float).max)), + ]) + def test_tiny_x(self, v, x): + """If x is much less than v, the bounds + + x x + --------------------------- <= R <= ----------------------- + v-0.5+sqrt(x**2+(v+0.5)**2) v-1+sqrt(x**2+(v+1)**2) + + collapses to R ~= x/2v. Test against this asymptotic expression. + """ + assert_equal(iv_ratio(v, x), (0.5*x)/v) + + @pytest.mark.parametrize('v,x', [ + (1, 1e16), + (1e20, 1e40), + (np.sqrt(np.finfo(float).max), np.finfo(float).max), + ]) + def test_huge_x(self, v, x): + """If x is much greater than v, the bounds + + x x + --------------------------- <= R <= --------------------------- + v-0.5+sqrt(x**2+(v+0.5)**2) v-0.5+sqrt(x**2+(v-0.5)**2) + + collapses to R ~= 1. Test against this asymptotic expression. + """ + assert_equal(iv_ratio(v, x), 1.0) + + @pytest.mark.parametrize('v,x', [ + (np.finfo(float).max, np.finfo(float).max), + (np.finfo(float).max / 3, np.finfo(float).max), + (np.finfo(float).max, np.finfo(float).max / 3), + ]) + def test_huge_v_x(self, v, x): + """If both x and v are very large, the bounds + + x x + --------------------------- <= R <= ----------------------- + v-0.5+sqrt(x**2+(v+0.5)**2) v-1+sqrt(x**2+(v+1)**2) + + collapses to R ~= x/(v+sqrt(x**2+v**2). Test against this asymptotic + expression, and in particular that no numerical overflow occurs during + intermediate calculations. + """ + t = x / v + expected = t / (1 + np.hypot(1, t)) + assert_allclose(iv_ratio(v, x), expected, rtol=4e-16, atol=0) + + +class TestIvRatioC: + + @pytest.mark.parametrize('v,x,r', [ + (0.5, 0.16666666666666666, 0.8348595870753707), + (0.5, 0.3333333333333333, 0.6784872624683657), + (0.5, 0.5, 0.5378828427399902), + (0.5, 0.6666666666666666, 0.4172170546520899), + (0.5, 0.8333333333333335, 0.3177382097618302), + (1, 0.3380952380952381, 0.8333226950829686), + (1, 0.7083333333333333, 0.6663355641301008), + (1, 1.1666666666666667, 0.4976644768462577), + (1, 1.8666666666666665, 0.325383427747836), + (1, 3.560606060606061, 0.155792340496837), + (2.34, 0.7975238095238094, 0.8329509691844672), + (2.34, 1.7133333333333334, 0.6639784068731155), + (2.34, 2.953333333333333, 0.49318090682197), + (2.34, 5.0826666666666656, 0.3244747301199321), + (2.34, 10.869696969696973, 0.16206488955012377), + (56.789, 19.46575238095238, 0.8332979494608591), + (56.789, 42.55008333333333, 0.6664619000306697), + (56.789, 75.552, 0.4996067618822174), + (56.789, 135.76026666666667, 0.3329471778053873), + (56.789, 307.8642424242425, 0.16650005585392025), + ]) + def test_against_reference_values(self, v, x, r): + """The reference values are one minus those of TestIvRatio.""" + assert_allclose(iv_ratio_c(v, x), r, rtol=1e-15, atol=0) + + @pytest.mark.parametrize('v,x,r', [ + (1, np.inf, 0), + (np.inf, 1, 1), + ]) + def test_inf(self, v, x, r): + """If exactly one of v or x is inf and the other is within domain, + should return 0 or 1 accordingly.""" + assert_equal(iv_ratio_c(v, x), r) + + @pytest.mark.parametrize('v', [0.49, -np.inf, np.nan, np.inf]) + @pytest.mark.parametrize('x', [-np.finfo(float).smallest_normal, + -np.finfo(float).smallest_subnormal, + -np.inf, np.nan, np.inf]) + def test_nan(self, v, x): + """If at least one argument is out of domain, or if v = x = inf, + the function should return nan.""" + assert_equal(iv_ratio_c(v, x), np.nan) + + @pytest.mark.parametrize('v', [0.5, 1, np.finfo(float).max, np.inf]) + def test_zero_x(self, v): + """If x is +/-0.0, return 1.""" + assert_equal(iv_ratio_c(v, 0.0), 1.0) + assert_equal(iv_ratio_c(v, -0.0), 1.0) + + @pytest.mark.parametrize('v,x', [ + (1, np.finfo(float).smallest_normal), + (1, np.finfo(float).smallest_subnormal), + (1, np.finfo(float).smallest_subnormal*2), + (1e20, 123), + (np.finfo(float).max, 1), + (np.finfo(float).max, np.sqrt(np.finfo(float).max)), + ]) + def test_tiny_x(self, v, x): + """If x is much less than v, the bounds + + x x + --------------------------- <= R <= ----------------------- + v-0.5+sqrt(x**2+(v+0.5)**2) v-1+sqrt(x**2+(v+1)**2) + + collapses to 1-R ~= 1-x/2v. Test against this asymptotic expression. + """ + assert_equal(iv_ratio_c(v, x), 1.0-(0.5*x)/v) + + @pytest.mark.parametrize('v,x', [ + (1, 1e16), + (1e20, 1e40), + (np.sqrt(np.finfo(float).max), np.finfo(float).max), + ]) + def test_huge_x(self, v, x): + """If x is much greater than v, the bounds + + x x + --------------------------- <= R <= --------------------------- + v-0.5+sqrt(x**2+(v+0.5)**2) v-0.5+sqrt(x**2+(v-0.5)**2) + + collapses to 1-R ~= (v-0.5)/x. Test against this asymptotic expression. + """ + assert_allclose(iv_ratio_c(v, x), (v-0.5)/x, rtol=1e-15, atol=0) + + @pytest.mark.parametrize('v,x', [ + (np.finfo(float).max, np.finfo(float).max), + (np.finfo(float).max / 3, np.finfo(float).max), + (np.finfo(float).max, np.finfo(float).max / 3), + ]) + def test_huge_v_x(self, v, x): + """If both x and v are very large, the bounds + + x x + --------------------------- <= R <= ----------------------- + v-0.5+sqrt(x**2+(v+0.5)**2) v-1+sqrt(x**2+(v+1)**2) + + collapses to 1 - R ~= 1 - x/(v+sqrt(x**2+v**2). Test against this + asymptotic expression, and in particular that no numerical overflow + occurs during intermediate calculations. + """ + t = x / v + expected = 1 - t / (1 + np.hypot(1, t)) + assert_allclose(iv_ratio_c(v, x), expected, rtol=4e-16, atol=0) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_kolmogorov.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_kolmogorov.py new file mode 100644 index 0000000000000000000000000000000000000000..ade38115706808809890e1eba3b938562b0af6e6 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_kolmogorov.py @@ -0,0 +1,491 @@ +import itertools + +import numpy as np +from numpy.testing import assert_ +from scipy.special._testutils import FuncData + +from scipy.special import kolmogorov, kolmogi, smirnov, smirnovi +from scipy.special._ufuncs import (_kolmogc, _kolmogci, _kolmogp, + _smirnovc, _smirnovci, _smirnovp) + +_rtol = 1e-10 + +class TestSmirnov: + def test_nan(self): + assert_(np.isnan(smirnov(1, np.nan))) + + def test_basic(self): + dataset = [(1, 0.1, 0.9), + (1, 0.875, 0.125), + (2, 0.875, 0.125 * 0.125), + (3, 0.875, 0.125 * 0.125 * 0.125)] + + dataset = np.asarray(dataset) + FuncData( + smirnov, dataset, (0, 1), 2, rtol=_rtol + ).check(dtypes=[int, float, float]) + dataset[:, -1] = 1 - dataset[:, -1] + FuncData( + _smirnovc, dataset, (0, 1), 2, rtol=_rtol + ).check(dtypes=[int, float, float]) + + def test_x_equals_0(self): + dataset = [(n, 0, 1) for n in itertools.chain(range(2, 20), range(1010, 1020))] + dataset = np.asarray(dataset) + FuncData( + smirnov, dataset, (0, 1), 2, rtol=_rtol + ).check(dtypes=[int, float, float]) + dataset[:, -1] = 1 - dataset[:, -1] + FuncData( + _smirnovc, dataset, (0, 1), 2, rtol=_rtol + ).check(dtypes=[int, float, float]) + + def test_x_equals_1(self): + dataset = [(n, 1, 0) for n in itertools.chain(range(2, 20), range(1010, 1020))] + dataset = np.asarray(dataset) + FuncData( + smirnov, dataset, (0, 1), 2, rtol=_rtol + ).check(dtypes=[int, float, float]) + dataset[:, -1] = 1 - dataset[:, -1] + FuncData( + _smirnovc, dataset, (0, 1), 2, rtol=_rtol + ).check(dtypes=[int, float, float]) + + def test_x_equals_0point5(self): + dataset = [(1, 0.5, 0.5), + (2, 0.5, 0.25), + (3, 0.5, 0.166666666667), + (4, 0.5, 0.09375), + (5, 0.5, 0.056), + (6, 0.5, 0.0327932098765), + (7, 0.5, 0.0191958707681), + (8, 0.5, 0.0112953186035), + (9, 0.5, 0.00661933257355), + (10, 0.5, 0.003888705)] + + dataset = np.asarray(dataset) + FuncData( + smirnov, dataset, (0, 1), 2, rtol=_rtol + ).check(dtypes=[int, float, float]) + dataset[:, -1] = 1 - dataset[:, -1] + FuncData( + _smirnovc, dataset, (0, 1), 2, rtol=_rtol + ).check(dtypes=[int, float, float]) + + def test_n_equals_1(self): + x = np.linspace(0, 1, 101, endpoint=True) + dataset = np.column_stack([[1]*len(x), x, 1-x]) + FuncData( + smirnov, dataset, (0, 1), 2, rtol=_rtol + ).check(dtypes=[int, float, float]) + dataset[:, -1] = 1 - dataset[:, -1] + FuncData( + _smirnovc, dataset, (0, 1), 2, rtol=_rtol + ).check(dtypes=[int, float, float]) + + def test_n_equals_2(self): + x = np.linspace(0.5, 1, 101, endpoint=True) + p = np.power(1-x, 2) + n = np.array([2] * len(x)) + dataset = np.column_stack([n, x, p]) + FuncData( + smirnov, dataset, (0, 1), 2, rtol=_rtol + ).check(dtypes=[int, float, float]) + dataset[:, -1] = 1 - dataset[:, -1] + FuncData( + _smirnovc, dataset, (0, 1), 2, rtol=_rtol + ).check(dtypes=[int, float, float]) + + def test_n_equals_3(self): + x = np.linspace(0.7, 1, 31, endpoint=True) + p = np.power(1-x, 3) + n = np.array([3] * len(x)) + dataset = np.column_stack([n, x, p]) + FuncData( + smirnov, dataset, (0, 1), 2, rtol=_rtol + ).check(dtypes=[int, float, float]) + dataset[:, -1] = 1 - dataset[:, -1] + FuncData( + _smirnovc, dataset, (0, 1), 2, rtol=_rtol + ).check(dtypes=[int, float, float]) + + def test_n_large(self): + # test for large values of n + # Probabilities should go down as n goes up + x = 0.4 + pvals = np.array([smirnov(n, x) for n in range(400, 1100, 20)]) + dfs = np.diff(pvals) + assert_(np.all(dfs <= 0), msg=f'Not all diffs negative {dfs}') + + +class TestSmirnovi: + def test_nan(self): + assert_(np.isnan(smirnovi(1, np.nan))) + + def test_basic(self): + dataset = [(1, 0.4, 0.6), + (1, 0.6, 0.4), + (1, 0.99, 0.01), + (1, 0.01, 0.99), + (2, 0.125 * 0.125, 0.875), + (3, 0.125 * 0.125 * 0.125, 0.875), + (10, 1.0 / 16 ** 10, 1 - 1.0 / 16)] + + dataset = np.asarray(dataset) + FuncData( + smirnovi, dataset, (0, 1), 2, rtol=_rtol + ).check(dtypes=[int, float, float]) + dataset[:, 1] = 1 - dataset[:, 1] + FuncData( + _smirnovci, dataset, (0, 1), 2, rtol=_rtol + ).check(dtypes=[int, float, float]) + + def test_x_equals_0(self): + dataset = [(n, 0, 1) for n in itertools.chain(range(2, 20), range(1010, 1020))] + dataset = np.asarray(dataset) + FuncData( + smirnovi, dataset, (0, 1), 2, rtol=_rtol + ).check(dtypes=[int, float, float]) + dataset[:, 1] = 1 - dataset[:, 1] + FuncData( + _smirnovci, dataset, (0, 1), 2, rtol=_rtol + ).check(dtypes=[int, float, float]) + + def test_x_equals_1(self): + dataset = [(n, 1, 0) for n in itertools.chain(range(2, 20), range(1010, 1020))] + dataset = np.asarray(dataset) + FuncData( + smirnovi, dataset, (0, 1), 2, rtol=_rtol + ).check(dtypes=[int, float, float]) + dataset[:, 1] = 1 - dataset[:, 1] + FuncData( + _smirnovci, dataset, (0, 1), 2, rtol=_rtol + ).check(dtypes=[int, float, float]) + + def test_n_equals_1(self): + pp = np.linspace(0, 1, 101, endpoint=True) + # dataset = np.array([(1, p, 1-p) for p in pp]) + dataset = np.column_stack([[1]*len(pp), pp, 1-pp]) + FuncData( + smirnovi, dataset, (0, 1), 2, rtol=_rtol + ).check(dtypes=[int, float, float]) + dataset[:, 1] = 1 - dataset[:, 1] + FuncData( + _smirnovci, dataset, (0, 1), 2, rtol=_rtol + ).check(dtypes=[int, float, float]) + + def test_n_equals_2(self): + x = np.linspace(0.5, 1, 101, endpoint=True) + p = np.power(1-x, 2) + n = np.array([2] * len(x)) + dataset = np.column_stack([n, p, x]) + FuncData( + smirnovi, dataset, (0, 1), 2, rtol=_rtol + ).check(dtypes=[int, float, float]) + dataset[:, 1] = 1 - dataset[:, 1] + FuncData( + _smirnovci, dataset, (0, 1), 2, rtol=_rtol + ).check(dtypes=[int, float, float]) + + def test_n_equals_3(self): + x = np.linspace(0.7, 1, 31, endpoint=True) + p = np.power(1-x, 3) + n = np.array([3] * len(x)) + dataset = np.column_stack([n, p, x]) + FuncData( + smirnovi, dataset, (0, 1), 2, rtol=_rtol + ).check(dtypes=[int, float, float]) + dataset[:, 1] = 1 - dataset[:, 1] + FuncData( + _smirnovci, dataset, (0, 1), 2, rtol=_rtol + ).check(dtypes=[int, float, float]) + + def test_round_trip(self): + def _sm_smi(n, p): + return smirnov(n, smirnovi(n, p)) + + def _smc_smci(n, p): + return _smirnovc(n, _smirnovci(n, p)) + + dataset = [(1, 0.4, 0.4), + (1, 0.6, 0.6), + (2, 0.875, 0.875), + (3, 0.875, 0.875), + (3, 0.125, 0.125), + (10, 0.999, 0.999), + (10, 0.0001, 0.0001)] + + dataset = np.asarray(dataset) + FuncData( + _sm_smi, dataset, (0, 1), 2, rtol=_rtol + ).check(dtypes=[int, float, float]) + FuncData( + _smc_smci, dataset, (0, 1), 2, rtol=_rtol + ).check(dtypes=[int, float, float]) + + def test_x_equals_0point5(self): + dataset = [(1, 0.5, 0.5), + (2, 0.5, 0.366025403784), + (2, 0.25, 0.5), + (3, 0.5, 0.297156508177), + (4, 0.5, 0.255520481121), + (5, 0.5, 0.234559536069), + (6, 0.5, 0.21715965898), + (7, 0.5, 0.202722580034), + (8, 0.5, 0.190621765256), + (9, 0.5, 0.180363501362), + (10, 0.5, 0.17157867006)] + + dataset = np.asarray(dataset) + FuncData( + smirnovi, dataset, (0, 1), 2, rtol=_rtol + ).check(dtypes=[int, float, float]) + dataset[:, 1] = 1 - dataset[:, 1] + FuncData( + _smirnovci, dataset, (0, 1), 2, rtol=_rtol + ).check(dtypes=[int, float, float]) + + +class TestSmirnovp: + def test_nan(self): + assert_(np.isnan(_smirnovp(1, np.nan))) + + def test_basic(self): + # Check derivative at endpoints + n1_10 = np.arange(1, 10) + dataset0 = np.column_stack([n1_10, + np.full_like(n1_10, 0), + np.full_like(n1_10, -1)]) + FuncData( + _smirnovp, dataset0, (0, 1), 2, rtol=_rtol + ).check(dtypes=[int, float, float]) + + n2_10 = np.arange(2, 10) + dataset1 = np.column_stack([n2_10, + np.full_like(n2_10, 1.0), + np.full_like(n2_10, 0)]) + FuncData( + _smirnovp, dataset1, (0, 1), 2, rtol=_rtol + ).check(dtypes=[int, float, float]) + + def test_oneminusoneovern(self): + # Check derivative at x=1-1/n + n = np.arange(1, 20) + x = 1.0/n + xm1 = 1-1.0/n + pp1 = -n * x**(n-1) + pp1 -= (1-np.sign(n-2)**2) * 0.5 # n=2, x=0.5, 1-1/n = 0.5, need to adjust + dataset1 = np.column_stack([n, xm1, pp1]) + FuncData( + _smirnovp, dataset1, (0, 1), 2, rtol=_rtol + ).check(dtypes=[int, float, float]) + + def test_oneovertwon(self): + # Check derivative at x=1/2n (Discontinuous at x=1/n, so check at x=1/2n) + n = np.arange(1, 20) + x = 1.0/2/n + pp = -(n*x+1) * (1+x)**(n-2) + dataset0 = np.column_stack([n, x, pp]) + FuncData( + _smirnovp, dataset0, (0, 1), 2, rtol=_rtol + ).check(dtypes=[int, float, float]) + + def test_oneovern(self): + # Check derivative at x=1/n + # (Discontinuous at x=1/n, hard to tell if x==1/n, only use n=power of 2) + n = 2**np.arange(1, 10) + x = 1.0/n + pp = -(n*x+1) * (1+x)**(n-2) + 0.5 + dataset0 = np.column_stack([n, x, pp]) + FuncData( + _smirnovp, dataset0, (0, 1), 2, rtol=_rtol + ).check(dtypes=[int, float, float]) + + def test_oneovernclose(self): + # Check derivative at x=1/n + # (Discontinuous at x=1/n, test on either side: x=1/n +/- 2epsilon) + n = np.arange(3, 20) + + x = 1.0/n - 2*np.finfo(float).eps + pp = -(n*x+1) * (1+x)**(n-2) + dataset0 = np.column_stack([n, x, pp]) + FuncData( + _smirnovp, dataset0, (0, 1), 2, rtol=_rtol + ).check(dtypes=[int, float, float]) + + x = 1.0/n + 2*np.finfo(float).eps + pp = -(n*x+1) * (1+x)**(n-2) + 1 + dataset1 = np.column_stack([n, x, pp]) + FuncData( + _smirnovp, dataset1, (0, 1), 2, rtol=_rtol + ).check(dtypes=[int, float, float]) + + +class TestKolmogorov: + def test_nan(self): + assert_(np.isnan(kolmogorov(np.nan))) + + def test_basic(self): + dataset = [(0, 1.0), + (0.5, 0.96394524366487511), + (0.8275735551899077, 0.5000000000000000), + (1, 0.26999967167735456), + (2, 0.00067092525577969533)] + + dataset = np.asarray(dataset) + FuncData(kolmogorov, dataset, (0,), 1, rtol=_rtol).check() + + def test_linspace(self): + x = np.linspace(0, 2.0, 21) + dataset = [1.0000000000000000, 1.0000000000000000, 0.9999999999994950, + 0.9999906941986655, 0.9971923267772983, 0.9639452436648751, + 0.8642827790506042, 0.7112351950296890, 0.5441424115741981, + 0.3927307079406543, 0.2699996716773546, 0.1777181926064012, + 0.1122496666707249, 0.0680922218447664, 0.0396818795381144, + 0.0222179626165251, 0.0119520432391966, 0.0061774306344441, + 0.0030676213475797, 0.0014636048371873, 0.0006709252557797] + + dataset_c = [0.0000000000000000, 6.609305242245699e-53, 5.050407338670114e-13, + 9.305801334566668e-06, 0.0028076732227017, 0.0360547563351249, + 0.1357172209493958, 0.2887648049703110, 0.4558575884258019, + 0.6072692920593457, 0.7300003283226455, 0.8222818073935988, + 0.8877503333292751, 0.9319077781552336, 0.9603181204618857, + 0.9777820373834749, 0.9880479567608034, 0.9938225693655559, + 0.9969323786524203, 0.9985363951628127, 0.9993290747442203] + + dataset = np.column_stack([x, dataset]) + FuncData(kolmogorov, dataset, (0,), 1, rtol=_rtol).check() + dataset_c = np.column_stack([x, dataset_c]) + FuncData(_kolmogc, dataset_c, (0,), 1, rtol=_rtol).check() + + def test_linspacei(self): + p = np.linspace(0, 1.0, 21, endpoint=True) + dataset = [np.inf, 1.3580986393225507, 1.2238478702170823, + 1.1379465424937751, 1.0727491749396481, 1.0191847202536859, + 0.9730633753323726, 0.9320695842357622, 0.8947644549851197, + 0.8601710725555463, 0.8275735551899077, 0.7964065373291559, + 0.7661855555617682, 0.7364542888171910, 0.7067326523068980, + 0.6764476915028201, 0.6448126061663567, 0.6105590999244391, + 0.5711732651063401, 0.5196103791686224, 0.0000000000000000] + + dataset_c = [0.0000000000000000, 0.5196103791686225, 0.5711732651063401, + 0.6105590999244391, 0.6448126061663567, 0.6764476915028201, + 0.7067326523068980, 0.7364542888171910, 0.7661855555617682, + 0.7964065373291559, 0.8275735551899077, 0.8601710725555463, + 0.8947644549851196, 0.9320695842357622, 0.9730633753323727, + 1.0191847202536859, 1.0727491749396481, 1.1379465424937754, + 1.2238478702170825, 1.3580986393225509, np.inf] + + dataset = np.column_stack([p[1:], dataset[1:]]) + FuncData(kolmogi, dataset, (0,), 1, rtol=_rtol).check() + dataset_c = np.column_stack([p[:-1], dataset_c[:-1]]) + FuncData(_kolmogci, dataset_c, (0,), 1, rtol=_rtol).check() + + def test_smallx(self): + epsilon = 0.1 ** np.arange(1, 14) + x = np.array([0.571173265106, 0.441027698518, 0.374219690278, 0.331392659217, + 0.300820537459, 0.277539353999, 0.259023494805, 0.243829561254, + 0.231063086389, 0.220135543236, 0.210641372041, 0.202290283658, + 0.19487060742]) + + dataset = np.column_stack([x, 1-epsilon]) + FuncData(kolmogorov, dataset, (0,), 1, rtol=_rtol).check() + + def test_round_trip(self): + def _ki_k(_x): + return kolmogi(kolmogorov(_x)) + + def _kci_kc(_x): + return _kolmogci(_kolmogc(_x)) + + x = np.linspace(0.0, 2.0, 21, endpoint=True) + # Exclude 0.1, 0.2. 0.2 almost makes succeeds, but 0.1 has no chance. + x02 = x[(x == 0) | (x > 0.21)] + dataset02 = np.column_stack([x02, x02]) + FuncData(_ki_k, dataset02, (0,), 1, rtol=_rtol).check() + + dataset = np.column_stack([x, x]) + FuncData(_kci_kc, dataset, (0,), 1, rtol=_rtol).check() + + +class TestKolmogi: + def test_nan(self): + assert_(np.isnan(kolmogi(np.nan))) + + def test_basic(self): + dataset = [(1.0, 0), + (0.96394524366487511, 0.5), + (0.9, 0.571173265106), + (0.5000000000000000, 0.8275735551899077), + (0.26999967167735456, 1), + (0.00067092525577969533, 2)] + + dataset = np.asarray(dataset) + FuncData(kolmogi, dataset, (0,), 1, rtol=_rtol).check() + + def test_smallpcdf(self): + epsilon = 0.5 ** np.arange(1, 55, 3) + # kolmogi(1-p) == _kolmogci(p) if 1-(1-p) == p, but not necessarily otherwise + # Use epsilon s.t. 1-(1-epsilon)) == epsilon, + # so can use same x-array for both results + + x = np.array([0.8275735551899077, 0.5345255069097583, 0.4320114038786941, + 0.3736868442620478, 0.3345161714909591, 0.3057833329315859, + 0.2835052890528936, 0.2655578150208676, 0.2506869966107999, + 0.2380971058736669, 0.2272549289962079, 0.2177876361600040, + 0.2094254686862041, 0.2019676748836232, 0.1952612948137504, + 0.1891874239646641, 0.1836520225050326, 0.1785795904846466]) + + dataset = np.column_stack([1-epsilon, x]) + FuncData(kolmogi, dataset, (0,), 1, rtol=_rtol).check() + + dataset = np.column_stack([epsilon, x]) + FuncData(_kolmogci, dataset, (0,), 1, rtol=_rtol).check() + + def test_smallpsf(self): + epsilon = 0.5 ** np.arange(1, 55, 3) + # kolmogi(p) == _kolmogci(1-p) if 1-(1-p) == p, but not necessarily otherwise + # Use epsilon s.t. 1-(1-epsilon)) == epsilon, + # so can use same x-array for both results + + x = np.array([0.8275735551899077, 1.3163786275161036, 1.6651092133663343, + 1.9525136345289607, 2.2027324540033235, 2.4272929437460848, + 2.6327688477341593, 2.8233300509220260, 3.0018183401530627, + 3.1702735084088891, 3.3302184446307912, 3.4828258153113318, + 3.6290214150152051, 3.7695513262825959, 3.9050272690877326, + 4.0359582187082550, 4.1627730557884890, 4.2858371743264527]) + + dataset = np.column_stack([epsilon, x]) + FuncData(kolmogi, dataset, (0,), 1, rtol=_rtol).check() + + dataset = np.column_stack([1-epsilon, x]) + FuncData(_kolmogci, dataset, (0,), 1, rtol=_rtol).check() + + def test_round_trip(self): + def _k_ki(_p): + return kolmogorov(kolmogi(_p)) + + p = np.linspace(0.1, 1.0, 10, endpoint=True) + dataset = np.column_stack([p, p]) + FuncData(_k_ki, dataset, (0,), 1, rtol=_rtol).check() + + +class TestKolmogp: + def test_nan(self): + assert_(np.isnan(_kolmogp(np.nan))) + + def test_basic(self): + dataset = [(0.000000, -0.0), + (0.200000, -1.532420541338916e-10), + (0.400000, -0.1012254419260496), + (0.600000, -1.324123244249925), + (0.800000, -1.627024345636592), + (1.000000, -1.071948558356941), + (1.200000, -0.538512430720529), + (1.400000, -0.2222133182429472), + (1.600000, -0.07649302775520538), + (1.800000, -0.02208687346347873), + (2.000000, -0.005367402045629683)] + + dataset = np.asarray(dataset) + FuncData(_kolmogp, dataset, (0,), 1, rtol=_rtol).check() diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_lambertw.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_lambertw.py new file mode 100644 index 0000000000000000000000000000000000000000..ac26dfb9ff65dbfd94740220a95a16c902b4be3d --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_lambertw.py @@ -0,0 +1,109 @@ +# +# Tests for the lambertw function, +# Adapted from the MPMath tests [1] by Yosef Meller, mellerf@netvision.net.il +# Distributed under the same license as SciPy itself. +# +# [1] mpmath source code, Subversion revision 992 +# http://code.google.com/p/mpmath/source/browse/trunk/mpmath/tests/test_functions2.py?spec=svn994&r=992 + +import pytest +import numpy as np +from numpy.testing import assert_, assert_equal, assert_allclose +from scipy.special import lambertw +from numpy import nan, inf, pi, e, isnan, log, r_, array, complex128 + +from scipy.special._testutils import FuncData + + +def test_values(): + assert_(isnan(lambertw(nan))) + assert_equal(lambertw(inf,1).real, inf) + assert_equal(lambertw(inf,1).imag, 2*pi) + assert_equal(lambertw(-inf,1).real, inf) + assert_equal(lambertw(-inf,1).imag, 3*pi) + + assert_equal(lambertw(1.), lambertw(1., 0)) + + data = [ + (0,0, 0), + (0+0j,0, 0), + (inf,0, inf), + (0,-1, -inf), + (0,1, -inf), + (0,3, -inf), + (e,0, 1), + (1,0, 0.567143290409783873), + (-pi/2,0, 1j*pi/2), + (-log(2)/2,0, -log(2)), + (0.25,0, 0.203888354702240164), + (-0.25,0, -0.357402956181388903), + (-1./10000,0, -0.000100010001500266719), + (-0.25,-1, -2.15329236411034965), + (0.25,-1, -3.00899800997004620-4.07652978899159763j), + (-0.25,-1, -2.15329236411034965), + (0.25,1, -3.00899800997004620+4.07652978899159763j), + (-0.25,1, -3.48973228422959210+7.41405453009603664j), + (-4,0, 0.67881197132094523+1.91195078174339937j), + (-4,1, -0.66743107129800988+7.76827456802783084j), + (-4,-1, 0.67881197132094523-1.91195078174339937j), + (1000,0, 5.24960285240159623), + (1000,1, 4.91492239981054535+5.44652615979447070j), + (1000,-1, 4.91492239981054535-5.44652615979447070j), + (1000,5, 3.5010625305312892+29.9614548941181328j), + (3+4j,0, 1.281561806123775878+0.533095222020971071j), + (-0.4+0.4j,0, -0.10396515323290657+0.61899273315171632j), + (3+4j,1, -0.11691092896595324+5.61888039871282334j), + (3+4j,-1, 0.25856740686699742-3.85211668616143559j), + (-0.5,-1, -0.794023632344689368-0.770111750510379110j), + (-1./10000,1, -11.82350837248724344+6.80546081842002101j), + (-1./10000,-1, -11.6671145325663544), + (-1./10000,-2, -11.82350837248724344-6.80546081842002101j), + (-1./100000,4, -14.9186890769540539+26.1856750178782046j), + (-1./100000,5, -15.0931437726379218666+32.5525721210262290086j), + ((2+1j)/10,0, 0.173704503762911669+0.071781336752835511j), + ((2+1j)/10,1, -3.21746028349820063+4.56175438896292539j), + ((2+1j)/10,-1, -3.03781405002993088-3.53946629633505737j), + ((2+1j)/10,4, -4.6878509692773249+23.8313630697683291j), + (-(2+1j)/10,0, -0.226933772515757933-0.164986470020154580j), + (-(2+1j)/10,1, -2.43569517046110001+0.76974067544756289j), + (-(2+1j)/10,-1, -3.54858738151989450-6.91627921869943589j), + (-(2+1j)/10,4, -4.5500846928118151+20.6672982215434637j), + (pi,0, 1.073658194796149172092178407024821347547745350410314531), + + # Former bug in generated branch, + (-0.5+0.002j,0, -0.78917138132659918344 + 0.76743539379990327749j), + (-0.5-0.002j,0, -0.78917138132659918344 - 0.76743539379990327749j), + (-0.448+0.4j,0, -0.11855133765652382241 + 0.66570534313583423116j), + (-0.448-0.4j,0, -0.11855133765652382241 - 0.66570534313583423116j), + ] + data = array(data, dtype=complex128) + + def w(x, y): + return lambertw(x, y.real.astype(int)) + with np.errstate(all='ignore'): + FuncData(w, data, (0,1), 2, rtol=1e-10, atol=1e-13).check() + + +def test_ufunc(): + assert_allclose(lambertw(r_[0., e, 1.]), r_[0., 1., 0.567143290409783873], + atol=1.5e-7, rtol=0) + + +def test_lambertw_ufunc_loop_selection(): + # see https://github.com/scipy/scipy/issues/4895 + dt = np.dtype(np.complex128) + assert_equal(lambertw(0, 0, 0).dtype, dt) + assert_equal(lambertw([0], 0, 0).dtype, dt) + assert_equal(lambertw(0, [0], 0).dtype, dt) + assert_equal(lambertw(0, 0, [0]).dtype, dt) + assert_equal(lambertw([0], [0], [0]).dtype, dt) + + +@pytest.mark.parametrize('z', [1e-316, -2e-320j, -5e-318+1e-320j]) +def test_lambertw_subnormal_k0(z): + # Verify that subnormal inputs are handled correctly on + # the branch k=0 (regression test for gh-16291). + w = lambertw(z) + # For values this small, we can be sure that numerically, + # lambertw(z) is z. + assert w == z diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_legendre.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_legendre.py new file mode 100644 index 0000000000000000000000000000000000000000..7625313ef258de23307c6c4a432069f5c4cd0cd1 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_legendre.py @@ -0,0 +1,1421 @@ +import math +import warnings + +import numpy as np + +import pytest +from numpy.testing import assert_equal, assert_allclose + +from scipy import special +from scipy.special import (legendre_p, legendre_p_all, assoc_legendre_p, + assoc_legendre_p_all, sph_legendre_p, sph_legendre_p_all) + + +# Base polynomials come from Abrahmowitz and Stegan +class TestLegendre: + def test_legendre(self): + leg0 = special.legendre(0) + leg1 = special.legendre(1) + leg2 = special.legendre(2) + leg3 = special.legendre(3) + leg4 = special.legendre(4) + leg5 = special.legendre(5) + assert_equal(leg0.c, [1]) + assert_equal(leg1.c, [1,0]) + assert_allclose(leg2.c, np.array([3, 0, -1])/2.0, + atol=1.5e-13, rtol=0) + assert_allclose(leg3.c, np.array([5, 0, -3, 0])/2.0, + atol=1.5e-7, rtol=0) + assert_allclose(leg4.c, np.array([35, 0, -30, 0, 3])/8.0, + atol=1.5e-7, rtol=0) + assert_allclose(leg5.c, np.array([63, 0, -70, 0, 15, 0])/8.0, + atol=1.5e-7, rtol=0) + +class TestLegendreP: + @pytest.mark.parametrize("shape", [(10,), (4, 9), (3, 5, 7)]) + def test_ode(self, shape): + rng = np.random.default_rng(1234) + + n = rng.integers(0, 100, shape) + x = rng.uniform(-1, 1, shape) + + p, p_jac, p_hess = legendre_p(n, x, diff_n=2) + + assert p.shape == shape + assert p_jac.shape == p.shape + assert p_hess.shape == p_jac.shape + + err = (1 - x * x) * p_hess - 2 * x * p_jac + n * (n + 1) * p + np.testing.assert_allclose(err, 0, atol=1e-10) + + @pytest.mark.parametrize("n_max", [1, 2, 4, 8, 16, 32]) + @pytest.mark.parametrize("x_shape", [(10,), (4, 9), (3, 5, 7)]) + def test_all_ode(self, n_max, x_shape): + rng = np.random.default_rng(1234) + + x = rng.uniform(-1, 1, x_shape) + p, p_jac, p_hess = legendre_p_all(n_max, x, diff_n=2) + + n = np.arange(n_max + 1) + n = np.expand_dims(n, axis = tuple(range(1, x.ndim + 1))) + + assert p.shape == (len(n),) + x.shape + assert p_jac.shape == p.shape + assert p_hess.shape == p_jac.shape + + err = (1 - x * x) * p_hess - 2 * x * p_jac + n * (n + 1) * p + np.testing.assert_allclose(err, 0, atol=1e-10) + + +class TestAssocLegendreP: + def test_assoc_legendre_gh23101(self): + z = np.array([-1, -.5, 0, .5, 1]) + expected = assoc_legendre_p_1_0(z) + result = assoc_legendre_p(1, 0, z) + assert_allclose(np.squeeze(result), expected) + + expected = assoc_legendre_p_3_0(z) + result = assoc_legendre_p(3, 0, z) + assert_allclose(np.squeeze(result), expected) + + @pytest.mark.parametrize("shape", [(10,), (4, 9), (3, 5, 7, 10)]) + @pytest.mark.parametrize("m_max", [5, 4]) + @pytest.mark.parametrize("n_max", [7, 10]) + def test_lpmn(self, shape, n_max, m_max): + rng = np.random.default_rng(1234) + + x = rng.uniform(-0.99, 0.99, shape) + p_all, p_all_jac, p_all_hess = \ + assoc_legendre_p_all(n_max, m_max, x, diff_n=2) + + n = np.arange(n_max + 1) + n = np.expand_dims(n, axis = tuple(range(1, x.ndim + 2))) + + m = np.concatenate([np.arange(m_max + 1), np.arange(-m_max, 0)]) + m = np.expand_dims(m, axis = (0,) + tuple(range(2, x.ndim + 2))) + + x = np.expand_dims(x, axis = (0, 1)) + p, p_jac, p_hess = assoc_legendre_p(n, m, x, diff_n=2) + + np.testing.assert_allclose(p, p_all) + np.testing.assert_allclose(p_jac, p_all_jac) + np.testing.assert_allclose(p_hess, p_all_hess) + + @pytest.mark.parametrize("shape", [(10,), (4, 9), (3, 5, 7, 10)]) + @pytest.mark.parametrize("norm", [True, False]) + def test_ode(self, shape, norm): + rng = np.random.default_rng(1234) + + n = rng.integers(0, 10, shape) + m = rng.integers(-10, 10, shape) + x = rng.uniform(-1, 1, shape) + + p, p_jac, p_hess = assoc_legendre_p(n, m, x, norm=norm, diff_n=2) + + assert p.shape == shape + assert p_jac.shape == p.shape + assert p_hess.shape == p_jac.shape + + np.testing.assert_allclose((1 - x * x) * p_hess, + 2 * x * p_jac - (n * (n + 1) - m * m / (1 - x * x)) * p, + rtol=1e-05, atol=1e-08) + + @pytest.mark.parametrize("shape", [(10,), (4, 9), (3, 5, 7)]) + def test_all(self, shape): + rng = np.random.default_rng(1234) + + n_max = 20 + m_max = 20 + + x = rng.uniform(-0.99, 0.99, shape) + + p, p_jac, p_hess = assoc_legendre_p_all(n_max, m_max, x, diff_n=2) + + m = np.concatenate([np.arange(m_max + 1), np.arange(-m_max, 0)]) + n = np.arange(n_max + 1) + + n = np.expand_dims(n, axis = tuple(range(1, x.ndim + 2))) + m = np.expand_dims(m, axis = (0,) + tuple(range(2, x.ndim + 2))) + np.testing.assert_allclose((1 - x * x) * p_hess, + 2 * x * p_jac - (n * (n + 1) - m * m / (1 - x * x)) * p, + rtol=1e-05, atol=1e-08) + + @pytest.mark.parametrize("shape", [(10,), (4, 9), (3, 5, 7)]) + @pytest.mark.parametrize("norm", [True, False]) + def test_specific(self, shape, norm): + rng = np.random.default_rng(1234) + + x = rng.uniform(-0.99, 0.99, shape) + + p, p_jac = assoc_legendre_p_all(4, 4, x, norm=norm, diff_n=1) + + np.testing.assert_allclose(p[0, 0], + assoc_legendre_p_0_0(x, norm=norm)) + np.testing.assert_allclose(p[0, 1], 0) + np.testing.assert_allclose(p[0, 2], 0) + np.testing.assert_allclose(p[0, 3], 0) + np.testing.assert_allclose(p[0, 4], 0) + np.testing.assert_allclose(p[0, -3], 0) + np.testing.assert_allclose(p[0, -2], 0) + np.testing.assert_allclose(p[0, -1], 0) + + np.testing.assert_allclose(p[1, 0], + assoc_legendre_p_1_0(x, norm=norm)) + np.testing.assert_allclose(p[1, 1], + assoc_legendre_p_1_1(x, norm=norm)) + np.testing.assert_allclose(p[1, 2], 0) + np.testing.assert_allclose(p[1, 3], 0) + np.testing.assert_allclose(p[1, 4], 0) + np.testing.assert_allclose(p[1, -4], 0) + np.testing.assert_allclose(p[1, -3], 0) + np.testing.assert_allclose(p[1, -2], 0) + np.testing.assert_allclose(p[1, -1], + assoc_legendre_p_1_m1(x, norm=norm)) + + np.testing.assert_allclose(p[2, 0], + assoc_legendre_p_2_0(x, norm=norm)) + np.testing.assert_allclose(p[2, 1], + assoc_legendre_p_2_1(x, norm=norm)) + np.testing.assert_allclose(p[2, 2], + assoc_legendre_p_2_2(x, norm=norm)) + np.testing.assert_allclose(p[2, 3], 0) + np.testing.assert_allclose(p[2, 4], 0) + np.testing.assert_allclose(p[2, -4], 0) + np.testing.assert_allclose(p[2, -3], 0) + np.testing.assert_allclose(p[2, -2], + assoc_legendre_p_2_m2(x, norm=norm)) + np.testing.assert_allclose(p[2, -1], + assoc_legendre_p_2_m1(x, norm=norm)) + + np.testing.assert_allclose(p[3, 0], + assoc_legendre_p_3_0(x, norm=norm)) + np.testing.assert_allclose(p[3, 1], + assoc_legendre_p_3_1(x, norm=norm)) + np.testing.assert_allclose(p[3, 2], + assoc_legendre_p_3_2(x, norm=norm)) + np.testing.assert_allclose(p[3, 3], + assoc_legendre_p_3_3(x, norm=norm)) + np.testing.assert_allclose(p[3, 4], 0) + np.testing.assert_allclose(p[3, -4], 0) + np.testing.assert_allclose(p[3, -3], + assoc_legendre_p_3_m3(x, norm=norm)) + np.testing.assert_allclose(p[3, -2], + assoc_legendre_p_3_m2(x, norm=norm)) + np.testing.assert_allclose(p[3, -1], + assoc_legendre_p_3_m1(x, norm=norm)) + + np.testing.assert_allclose(p[4, 0], + assoc_legendre_p_4_0(x, norm=norm)) + np.testing.assert_allclose(p[4, 1], + assoc_legendre_p_4_1(x, norm=norm)) + np.testing.assert_allclose(p[4, 2], + assoc_legendre_p_4_2(x, norm=norm)) + np.testing.assert_allclose(p[4, 3], + assoc_legendre_p_4_3(x, norm=norm)) + np.testing.assert_allclose(p[4, 4], + assoc_legendre_p_4_4(x, norm=norm)) + np.testing.assert_allclose(p[4, -4], + assoc_legendre_p_4_m4(x, norm=norm)) + np.testing.assert_allclose(p[4, -3], + assoc_legendre_p_4_m3(x, norm=norm)) + np.testing.assert_allclose(p[4, -2], + assoc_legendre_p_4_m2(x, norm=norm)) + np.testing.assert_allclose(p[4, -1], + assoc_legendre_p_4_m1(x, norm=norm)) + + np.testing.assert_allclose(p_jac[0, 0], + assoc_legendre_p_0_0_jac(x, norm=norm)) + np.testing.assert_allclose(p_jac[0, 1], 0) + np.testing.assert_allclose(p_jac[0, 2], 0) + np.testing.assert_allclose(p_jac[0, 3], 0) + np.testing.assert_allclose(p_jac[0, 4], 0) + np.testing.assert_allclose(p_jac[0, -4], 0) + np.testing.assert_allclose(p_jac[0, -3], 0) + np.testing.assert_allclose(p_jac[0, -2], 0) + np.testing.assert_allclose(p_jac[0, -1], 0) + + np.testing.assert_allclose(p_jac[1, 0], + assoc_legendre_p_1_0_jac(x, norm=norm)) + np.testing.assert_allclose(p_jac[1, 1], + assoc_legendre_p_1_1_jac(x, norm=norm)) + np.testing.assert_allclose(p_jac[1, 2], 0) + np.testing.assert_allclose(p_jac[1, 3], 0) + np.testing.assert_allclose(p_jac[1, 4], 0) + np.testing.assert_allclose(p_jac[1, -4], 0) + np.testing.assert_allclose(p_jac[1, -3], 0) + np.testing.assert_allclose(p_jac[1, -2], 0) + np.testing.assert_allclose(p_jac[1, -1], + assoc_legendre_p_1_m1_jac(x, norm=norm)) + + np.testing.assert_allclose(p_jac[2, 0], + assoc_legendre_p_2_0_jac(x, norm=norm)) + np.testing.assert_allclose(p_jac[2, 1], + assoc_legendre_p_2_1_jac(x, norm=norm)) + np.testing.assert_allclose(p_jac[2, 2], + assoc_legendre_p_2_2_jac(x, norm=norm)) + np.testing.assert_allclose(p_jac[2, 3], 0) + np.testing.assert_allclose(p_jac[2, 4], 0) + np.testing.assert_allclose(p_jac[2, -4], 0) + np.testing.assert_allclose(p_jac[2, -3], 0) + np.testing.assert_allclose(p_jac[2, -2], + assoc_legendre_p_2_m2_jac(x, norm=norm)) + np.testing.assert_allclose(p_jac[2, -1], + assoc_legendre_p_2_m1_jac(x, norm=norm)) + + np.testing.assert_allclose(p_jac[3, 0], + assoc_legendre_p_3_0_jac(x, norm=norm)) + np.testing.assert_allclose(p_jac[3, 1], + assoc_legendre_p_3_1_jac(x, norm=norm)) + np.testing.assert_allclose(p_jac[3, 2], + assoc_legendre_p_3_2_jac(x, norm=norm)) + np.testing.assert_allclose(p_jac[3, 3], + assoc_legendre_p_3_3_jac(x, norm=norm)) + np.testing.assert_allclose(p_jac[3, 4], 0) + np.testing.assert_allclose(p_jac[3, -4], 0) + np.testing.assert_allclose(p_jac[3, -3], + assoc_legendre_p_3_m3_jac(x, norm=norm)) + np.testing.assert_allclose(p_jac[3, -2], + assoc_legendre_p_3_m2_jac(x, norm=norm)) + np.testing.assert_allclose(p_jac[3, -1], + assoc_legendre_p_3_m1_jac(x, norm=norm)) + + np.testing.assert_allclose(p_jac[4, 0], + assoc_legendre_p_4_0_jac(x, norm=norm)) + np.testing.assert_allclose(p_jac[4, 1], + assoc_legendre_p_4_1_jac(x, norm=norm)) + np.testing.assert_allclose(p_jac[4, 2], + assoc_legendre_p_4_2_jac(x, norm=norm)) + np.testing.assert_allclose(p_jac[4, 3], + assoc_legendre_p_4_3_jac(x, norm=norm)) + np.testing.assert_allclose(p_jac[4, 4], + assoc_legendre_p_4_4_jac(x, norm=norm)) + np.testing.assert_allclose(p_jac[4, -4], + assoc_legendre_p_4_m4_jac(x, norm=norm)) + np.testing.assert_allclose(p_jac[4, -3], + assoc_legendre_p_4_m3_jac(x, norm=norm)) + np.testing.assert_allclose(p_jac[4, -2], + assoc_legendre_p_4_m2_jac(x, norm=norm)) + np.testing.assert_allclose(p_jac[4, -1], + assoc_legendre_p_4_m1_jac(x, norm=norm)) + + @pytest.mark.parametrize("m_max", [7]) + @pytest.mark.parametrize("n_max", [10]) + @pytest.mark.parametrize("x", [1, -1]) + def test_all_limits(self, m_max, n_max, x): + p, p_jac = assoc_legendre_p_all(n_max, m_max, x, diff_n=1) + + n = np.arange(n_max + 1) + + np.testing.assert_allclose(p_jac[:, 0], + pow(x, n + 1) * n * (n + 1) / 2) + np.testing.assert_allclose(p_jac[:, 1], + np.where(n >= 1, pow(x, n) * np.inf, 0)) + np.testing.assert_allclose(p_jac[:, 2], + np.where(n >= 2, -pow(x, n + 1) * (n + 2) * (n + 1) * n * (n - 1) / 4, 0)) + np.testing.assert_allclose(p_jac[:, -2], + np.where(n >= 2, -pow(x, n + 1) / 4, 0)) + np.testing.assert_allclose(p_jac[:, -1], + np.where(n >= 1, -pow(x, n) * np.inf, 0)) + + for m in range(3, m_max + 1): + np.testing.assert_allclose(p_jac[:, m], 0) + np.testing.assert_allclose(p_jac[:, -m], 0) + + @pytest.mark.parametrize("n", np.arange(0, 20)) + def test_assoc_legendre_p_norm_m0(self, n): + # gh 24099 + m = 0 + z = np.linspace(-1, 1, 101) + p = assoc_legendre_p(n, m, z, norm=False) + p_norm = assoc_legendre_p(n, m, z, norm=True) + factor = np.sqrt((2 * n + 1) / 2) + np.testing.assert_allclose(p_norm, factor * p) + + @pytest.mark.parametrize("n", np.arange(0, 20)) + def test_assoc_legendre_p_all_norm_m0(self, n): + # gh 24099 + m = 0 + z = np.linspace(-1, 1, 101) + p_all = assoc_legendre_p_all(n, m, z, norm=False)[0, : n + 1, 0, :] + p_norm_all = assoc_legendre_p_all(n, m, z, norm=True)[0, : n + 1, 0, :] + factor = np.sqrt((2 * np.arange(n + 1) + 1) / 2) + factor = factor[:, np.newaxis] + np.testing.assert_allclose(p_norm_all, factor * p_all) + + +class TestMultiAssocLegendreP: + @pytest.mark.parametrize("shape", [(1000,), (4, 9), (3, 5, 7)]) + @pytest.mark.parametrize("branch_cut", [2, 3]) + @pytest.mark.parametrize("z_min, z_max", [(-10 - 10j, 10 + 10j), + (-1, 1), (-10j, 10j)]) + @pytest.mark.parametrize("norm", [True, False]) + def test_specific(self, shape, branch_cut, z_min, z_max, norm): + rng = np.random.default_rng(1234) + + z = rng.uniform(z_min.real, z_max.real, shape) + \ + 1j * rng.uniform(z_min.imag, z_max.imag, shape) + + p, p_jac = assoc_legendre_p_all(4, 4, + z, branch_cut=branch_cut, norm=norm, diff_n=1) + + np.testing.assert_allclose(p[0, 0], + assoc_legendre_p_0_0(z, branch_cut=branch_cut, norm=norm)) + np.testing.assert_allclose(p[0, 1], 0) + np.testing.assert_allclose(p[0, 2], 0) + np.testing.assert_allclose(p[0, 3], 0) + np.testing.assert_allclose(p[0, 4], 0) + np.testing.assert_allclose(p[0, -4], 0) + np.testing.assert_allclose(p[0, -3], 0) + np.testing.assert_allclose(p[0, -2], 0) + np.testing.assert_allclose(p[0, -1], 0) + + np.testing.assert_allclose(p[1, 0], + assoc_legendre_p_1_0(z, branch_cut=branch_cut, norm=norm)) + np.testing.assert_allclose(p[1, 1], + assoc_legendre_p_1_1(z, branch_cut=branch_cut, norm=norm)) + np.testing.assert_allclose(p[1, 2], 0) + np.testing.assert_allclose(p[1, 3], 0) + np.testing.assert_allclose(p[1, 4], 0) + np.testing.assert_allclose(p[1, -4], 0) + np.testing.assert_allclose(p[1, -3], 0) + np.testing.assert_allclose(p[1, -2], 0) + np.testing.assert_allclose(p[1, -1], + assoc_legendre_p_1_m1(z, branch_cut=branch_cut, norm=norm)) + + np.testing.assert_allclose(p[2, 0], + assoc_legendre_p_2_0(z, branch_cut=branch_cut, norm=norm)) + np.testing.assert_allclose(p[2, 1], + assoc_legendre_p_2_1(z, branch_cut=branch_cut, norm=norm)) + np.testing.assert_allclose(p[2, 2], + assoc_legendre_p_2_2(z, branch_cut=branch_cut, norm=norm)) + np.testing.assert_allclose(p[2, 3], 0) + np.testing.assert_allclose(p[2, 4], 0) + np.testing.assert_allclose(p[2, -4], 0) + np.testing.assert_allclose(p[2, -3], 0) + np.testing.assert_allclose(p[2, -2], + assoc_legendre_p_2_m2(z, branch_cut=branch_cut, norm=norm)) + np.testing.assert_allclose(p[2, -1], + assoc_legendre_p_2_m1(z, branch_cut=branch_cut, norm=norm)) + + np.testing.assert_allclose(p[3, 0], + assoc_legendre_p_3_0(z, branch_cut=branch_cut, norm=norm)) + np.testing.assert_allclose(p[3, 1], + assoc_legendre_p_3_1(z, branch_cut=branch_cut, norm=norm)) + np.testing.assert_allclose(p[3, 2], + assoc_legendre_p_3_2(z, branch_cut=branch_cut, norm=norm)) + np.testing.assert_allclose(p[3, 3], + assoc_legendre_p_3_3(z, branch_cut=branch_cut, norm=norm)) + np.testing.assert_allclose(p[3, 4], 0) + np.testing.assert_allclose(p[3, -4], 0) + np.testing.assert_allclose(p[3, -3], + assoc_legendre_p_3_m3(z, branch_cut=branch_cut, norm=norm)) + np.testing.assert_allclose(p[3, -2], + assoc_legendre_p_3_m2(z, branch_cut=branch_cut, norm=norm)) + np.testing.assert_allclose(p[3, -1], + assoc_legendre_p_3_m1(z, branch_cut=branch_cut, norm=norm)) + + np.testing.assert_allclose(p[4, 0], + assoc_legendre_p_4_0(z, branch_cut=branch_cut, norm=norm)) + np.testing.assert_allclose(p[4, 1], + assoc_legendre_p_4_1(z, branch_cut=branch_cut, norm=norm)) + np.testing.assert_allclose(p[4, 2], + assoc_legendre_p_4_2(z, branch_cut=branch_cut, norm=norm)) + np.testing.assert_allclose(p[4, 3], + assoc_legendre_p_4_3(z, branch_cut=branch_cut, norm=norm)) + np.testing.assert_allclose(p[4, 4], + assoc_legendre_p_4_4(z, branch_cut=branch_cut, norm=norm)) + np.testing.assert_allclose(p[4, -4], + assoc_legendre_p_4_m4(z, branch_cut=branch_cut, norm=norm)) + np.testing.assert_allclose(p[4, -3], + assoc_legendre_p_4_m3(z, branch_cut=branch_cut, norm=norm)) + np.testing.assert_allclose(p[4, -2], + assoc_legendre_p_4_m2(z, branch_cut=branch_cut, norm=norm)) + np.testing.assert_allclose(p[4, -1], + assoc_legendre_p_4_m1(z, branch_cut=branch_cut, norm=norm)) + + np.testing.assert_allclose(p_jac[0, 0], + assoc_legendre_p_0_0_jac(z, branch_cut=branch_cut, norm=norm)) + np.testing.assert_allclose(p_jac[0, 1], 0) + np.testing.assert_allclose(p_jac[0, 2], 0) + np.testing.assert_allclose(p_jac[0, 3], 0) + np.testing.assert_allclose(p_jac[0, 4], 0) + np.testing.assert_allclose(p_jac[0, -4], 0) + np.testing.assert_allclose(p_jac[0, -3], 0) + np.testing.assert_allclose(p_jac[0, -2], 0) + np.testing.assert_allclose(p_jac[0, -1], 0) + + np.testing.assert_allclose(p_jac[1, 0], + assoc_legendre_p_1_0_jac(z, branch_cut=branch_cut, norm=norm)) + np.testing.assert_allclose(p_jac[1, 1], + assoc_legendre_p_1_1_jac(z, branch_cut=branch_cut, norm=norm)) + np.testing.assert_allclose(p_jac[1, 2], 0) + np.testing.assert_allclose(p_jac[1, 3], 0) + np.testing.assert_allclose(p_jac[1, 4], 0) + np.testing.assert_allclose(p_jac[1, -4], 0) + np.testing.assert_allclose(p_jac[1, -3], 0) + np.testing.assert_allclose(p_jac[1, -2], 0) + np.testing.assert_allclose(p_jac[1, -1], + assoc_legendre_p_1_m1_jac(z, branch_cut=branch_cut, norm=norm)) + + np.testing.assert_allclose(p_jac[2, 0], + assoc_legendre_p_2_0_jac(z, branch_cut=branch_cut, norm=norm)) + np.testing.assert_allclose(p_jac[2, 1], + assoc_legendre_p_2_1_jac(z, branch_cut=branch_cut, norm=norm)) + np.testing.assert_allclose(p_jac[2, 2], + assoc_legendre_p_2_2_jac(z, branch_cut=branch_cut, norm=norm)) + np.testing.assert_allclose(p_jac[2, 3], 0) + np.testing.assert_allclose(p_jac[2, 4], 0) + np.testing.assert_allclose(p_jac[2, -4], 0) + np.testing.assert_allclose(p_jac[2, -3], 0) + np.testing.assert_allclose(p_jac[2, -2], + assoc_legendre_p_2_m2_jac(z, branch_cut=branch_cut, norm=norm)) + np.testing.assert_allclose(p_jac[2, -1], + assoc_legendre_p_2_m1_jac(z, branch_cut=branch_cut, norm=norm)) + + np.testing.assert_allclose(p_jac[3, 0], + assoc_legendre_p_3_0_jac(z, branch_cut=branch_cut, norm=norm)) + np.testing.assert_allclose(p_jac[3, 1], + assoc_legendre_p_3_1_jac(z, branch_cut=branch_cut, norm=norm)) + np.testing.assert_allclose(p_jac[3, 2], + assoc_legendre_p_3_2_jac(z, branch_cut=branch_cut, norm=norm)) + np.testing.assert_allclose(p_jac[3, 3], + assoc_legendre_p_3_3_jac(z, branch_cut=branch_cut, norm=norm)) + np.testing.assert_allclose(p_jac[3, 4], 0) + np.testing.assert_allclose(p_jac[3, -4], 0) + np.testing.assert_allclose(p_jac[3, -3], + assoc_legendre_p_3_m3_jac(z, branch_cut=branch_cut, norm=norm)) + np.testing.assert_allclose(p_jac[3, -2], + assoc_legendre_p_3_m2_jac(z, branch_cut=branch_cut, norm=norm)) + np.testing.assert_allclose(p_jac[3, -1], + assoc_legendre_p_3_m1_jac(z, branch_cut=branch_cut, norm=norm)) + + np.testing.assert_allclose(p_jac[4, 0], + assoc_legendre_p_4_0_jac(z, branch_cut=branch_cut, norm=norm)) + np.testing.assert_allclose(p_jac[4, 1], + assoc_legendre_p_4_1_jac(z, branch_cut=branch_cut, norm=norm)) + np.testing.assert_allclose(p_jac[4, 2], + assoc_legendre_p_4_2_jac(z, branch_cut=branch_cut, norm=norm)) + np.testing.assert_allclose(p_jac[4, 3], + assoc_legendre_p_4_3_jac(z, branch_cut=branch_cut, norm=norm)) + np.testing.assert_allclose(p_jac[4, 4], + assoc_legendre_p_4_4_jac(z, branch_cut=branch_cut, norm=norm)) + np.testing.assert_allclose(p_jac[4, -4], + assoc_legendre_p_4_m4_jac(z, branch_cut=branch_cut, norm=norm)) + np.testing.assert_allclose(p_jac[4, -3], + assoc_legendre_p_4_m3_jac(z, branch_cut=branch_cut, norm=norm)) + np.testing.assert_allclose(p_jac[4, -2], + assoc_legendre_p_4_m2_jac(z, branch_cut=branch_cut, norm=norm)) + np.testing.assert_allclose(p_jac[4, -1], + assoc_legendre_p_4_m1_jac(z, branch_cut=branch_cut, norm=norm)) + +class TestSphLegendreP: + @pytest.mark.parametrize("shape", [(10,), (4, 9), (3, 5, 7)]) + def test_specific(self, shape): + rng = np.random.default_rng(1234) + + theta = rng.uniform(-np.pi, np.pi, shape) + + p, p_jac = sph_legendre_p_all(4, 4, theta, diff_n=1) + + np.testing.assert_allclose(p[0, 0], + sph_legendre_p_0_0(theta)) + np.testing.assert_allclose(p[0, 1], 0) + np.testing.assert_allclose(p[0, 2], 0) + np.testing.assert_allclose(p[0, 3], 0) + np.testing.assert_allclose(p[0, 4], 0) + np.testing.assert_allclose(p[0, -3], 0) + np.testing.assert_allclose(p[0, -2], 0) + np.testing.assert_allclose(p[0, -1], 0) + + np.testing.assert_allclose(p[1, 0], + sph_legendre_p_1_0(theta)) + np.testing.assert_allclose(p[1, 1], + sph_legendre_p_1_1(theta)) + np.testing.assert_allclose(p[1, 2], 0) + np.testing.assert_allclose(p[1, 3], 0) + np.testing.assert_allclose(p[1, 4], 0) + np.testing.assert_allclose(p[1, -4], 0) + np.testing.assert_allclose(p[1, -3], 0) + np.testing.assert_allclose(p[1, -2], 0) + np.testing.assert_allclose(p[1, -1], + sph_legendre_p_1_m1(theta)) + + np.testing.assert_allclose(p[2, 0], + sph_legendre_p_2_0(theta)) + np.testing.assert_allclose(p[2, 1], + sph_legendre_p_2_1(theta)) + np.testing.assert_allclose(p[2, 2], + sph_legendre_p_2_2(theta)) + np.testing.assert_allclose(p[2, 3], 0) + np.testing.assert_allclose(p[2, 4], 0) + np.testing.assert_allclose(p[2, -4], 0) + np.testing.assert_allclose(p[2, -3], 0) + np.testing.assert_allclose(p[2, -2], + sph_legendre_p_2_m2(theta)) + np.testing.assert_allclose(p[2, -1], + sph_legendre_p_2_m1(theta)) + + np.testing.assert_allclose(p[3, 0], + sph_legendre_p_3_0(theta)) + np.testing.assert_allclose(p[3, 1], + sph_legendre_p_3_1(theta)) + np.testing.assert_allclose(p[3, 2], + sph_legendre_p_3_2(theta)) + np.testing.assert_allclose(p[3, 3], + sph_legendre_p_3_3(theta)) + np.testing.assert_allclose(p[3, 4], 0) + np.testing.assert_allclose(p[3, -4], 0) + np.testing.assert_allclose(p[3, -3], + sph_legendre_p_3_m3(theta)) + np.testing.assert_allclose(p[3, -2], + sph_legendre_p_3_m2(theta)) + np.testing.assert_allclose(p[3, -1], + sph_legendre_p_3_m1(theta)) + + np.testing.assert_allclose(p[4, 0], + sph_legendre_p_4_0(theta)) + np.testing.assert_allclose(p[4, 1], + sph_legendre_p_4_1(theta)) + np.testing.assert_allclose(p[4, 2], + sph_legendre_p_4_2(theta)) + np.testing.assert_allclose(p[4, 3], + sph_legendre_p_4_3(theta)) + np.testing.assert_allclose(p[4, 4], + sph_legendre_p_4_4(theta)) + np.testing.assert_allclose(p[4, -4], + sph_legendre_p_4_m4(theta)) + np.testing.assert_allclose(p[4, -3], + sph_legendre_p_4_m3(theta)) + np.testing.assert_allclose(p[4, -2], + sph_legendre_p_4_m2(theta)) + np.testing.assert_allclose(p[4, -1], + sph_legendre_p_4_m1(theta)) + + np.testing.assert_allclose(p_jac[0, 0], + sph_legendre_p_0_0_jac(theta)) + np.testing.assert_allclose(p_jac[0, 1], 0) + np.testing.assert_allclose(p_jac[0, 2], 0) + np.testing.assert_allclose(p_jac[0, 3], 0) + np.testing.assert_allclose(p_jac[0, 4], 0) + np.testing.assert_allclose(p_jac[0, -3], 0) + np.testing.assert_allclose(p_jac[0, -2], 0) + np.testing.assert_allclose(p_jac[0, -1], 0) + + np.testing.assert_allclose(p_jac[1, 0], + sph_legendre_p_1_0_jac(theta)) + np.testing.assert_allclose(p_jac[1, 1], + sph_legendre_p_1_1_jac(theta)) + np.testing.assert_allclose(p_jac[1, 2], 0) + np.testing.assert_allclose(p_jac[1, 3], 0) + np.testing.assert_allclose(p_jac[1, 4], 0) + np.testing.assert_allclose(p_jac[1, -4], 0) + np.testing.assert_allclose(p_jac[1, -3], 0) + np.testing.assert_allclose(p_jac[1, -2], 0) + np.testing.assert_allclose(p_jac[1, -1], + sph_legendre_p_1_m1_jac(theta)) + + np.testing.assert_allclose(p_jac[2, 0], + sph_legendre_p_2_0_jac(theta)) + np.testing.assert_allclose(p_jac[2, 1], + sph_legendre_p_2_1_jac(theta)) + np.testing.assert_allclose(p_jac[2, 2], + sph_legendre_p_2_2_jac(theta)) + np.testing.assert_allclose(p_jac[2, 3], 0) + np.testing.assert_allclose(p_jac[2, 4], 0) + np.testing.assert_allclose(p_jac[2, -4], 0) + np.testing.assert_allclose(p_jac[2, -3], 0) + np.testing.assert_allclose(p_jac[2, -2], + sph_legendre_p_2_m2_jac(theta)) + np.testing.assert_allclose(p_jac[2, -1], + sph_legendre_p_2_m1_jac(theta)) + + np.testing.assert_allclose(p_jac[3, 0], + sph_legendre_p_3_0_jac(theta)) + np.testing.assert_allclose(p_jac[3, 1], + sph_legendre_p_3_1_jac(theta)) + np.testing.assert_allclose(p_jac[3, 2], + sph_legendre_p_3_2_jac(theta)) + np.testing.assert_allclose(p_jac[3, 3], + sph_legendre_p_3_3_jac(theta)) + np.testing.assert_allclose(p_jac[3, 4], 0) + np.testing.assert_allclose(p_jac[3, -4], 0) + np.testing.assert_allclose(p_jac[3, -3], + sph_legendre_p_3_m3_jac(theta)) + np.testing.assert_allclose(p_jac[3, -2], + sph_legendre_p_3_m2_jac(theta)) + np.testing.assert_allclose(p_jac[3, -1], + sph_legendre_p_3_m1_jac(theta)) + + np.testing.assert_allclose(p_jac[4, 0], + sph_legendre_p_4_0_jac(theta)) + np.testing.assert_allclose(p_jac[4, 1], + sph_legendre_p_4_1_jac(theta)) + np.testing.assert_allclose(p_jac[4, 2], + sph_legendre_p_4_2_jac(theta)) + np.testing.assert_allclose(p_jac[4, 3], + sph_legendre_p_4_3_jac(theta)) + np.testing.assert_allclose(p_jac[4, 4], + sph_legendre_p_4_4_jac(theta)) + np.testing.assert_allclose(p_jac[4, -4], + sph_legendre_p_4_m4_jac(theta)) + np.testing.assert_allclose(p_jac[4, -3], + sph_legendre_p_4_m3_jac(theta)) + np.testing.assert_allclose(p_jac[4, -2], + sph_legendre_p_4_m2_jac(theta)) + np.testing.assert_allclose(p_jac[4, -1], + sph_legendre_p_4_m1_jac(theta)) + + @pytest.mark.parametrize("shape", [(10,), (4, 9), (3, 5, 7, 10)]) + def test_ode(self, shape): + rng = np.random.default_rng(1234) + + n = rng.integers(0, 10, shape) + m = rng.integers(-10, 10, shape) + theta = rng.uniform(-np.pi, np.pi, shape) + + p, p_jac, p_hess = sph_legendre_p(n, m, theta, diff_n=2) + + assert p.shape == shape + assert p_jac.shape == p.shape + assert p_hess.shape == p_jac.shape + + np.testing.assert_allclose(np.sin(theta) * p_hess, -np.cos(theta) * p_jac + - (n * (n + 1) * np.sin(theta) - m * m / np.sin(theta)) * p, + rtol=1e-05, atol=1e-08) + +class TestLegendreFunctions: + """ + @pytest.mark.parametrize("m_max", [3]) + @pytest.mark.parametrize("n_max", [5]) + @pytest.mark.parametrize("z", [-1]) + def test_clpmn_all_limits(self, m_max, n_max, z): + rng = np.random.default_rng(1234) + + type = 2 + + p, p_jac = special.clpmn_all(m_max, n_max, type, z, diff_n=1) + + n = np.arange(n_max + 1) + + np.testing.assert_allclose(p_jac[0], pow(z, n + 1) * n * (n + 1) / 2) + np.testing.assert_allclose(p_jac[1], np.where(n >= 1, pow(z, n) * np.inf, 0)) + np.testing.assert_allclose(p_jac[2], np.where(n >= 2, + -pow(z, n + 1) * (n + 2) * (n + 1) * n * (n - 1) / 4, 0)) + np.testing.assert_allclose(p_jac[-2], np.where(n >= 2, -pow(z, n + 1) / 4, 0)) + np.testing.assert_allclose(p_jac[-1], np.where(n >= 1, -pow(z, n) * np.inf, 0)) + + for m in range(3, m_max + 1): + np.testing.assert_allclose(p_jac[m], 0) + np.testing.assert_allclose(p_jac[-m], 0) + """ + + def test_lpmv(self): + lp = special.lpmv(0, 2, .5) + assert_allclose(lp, -0.125, atol=1.5e-7, rtol=0) + lp = special.lpmv(0, 40, .001) + assert_allclose(lp, 0.1252678976534484, atol=1.5e-7, rtol=0) + + # XXX: this is outside the domain of the current implementation, + # so ensure it returns a NaN rather than a wrong answer. + with np.errstate(all='ignore'): + lp = special.lpmv(-1,-1,.001) + assert lp != 0 or np.isnan(lp) + + def test_lqmn(self): + lqmnf = special.lqmn(0, 2, .5) + lqf = special.lqn(2, .5) + assert_allclose(lqmnf[0][0], lqf[0], atol=1.5e-4, rtol=0) + assert_allclose(lqmnf[1][0], lqf[1], atol=1.5e-4, rtol=0) + + def test_lqmn_gt1(self): + """algorithm for real arguments changes at 1.0001 + test against analytical result for m=2, n=1 + """ + x0 = 1.0001 + delta = 0.00002 + for x in (x0-delta, x0+delta): + lq = special.lqmn(2, 1, x)[0][-1, -1] + expected = 2/(x*x-1) + assert_allclose(lq, expected, atol=1.5e-7, rtol=0) + + def test_lqmn_shape(self): + a, b = special.lqmn(4, 4, 1.1) + assert_equal(a.shape, (5, 5)) + assert_equal(b.shape, (5, 5)) + + a, b = special.lqmn(4, 0, 1.1) + assert_equal(a.shape, (5, 1)) + assert_equal(b.shape, (5, 1)) + + def test_lqn(self): + lqf = special.lqn(2, .5) + assert_allclose(lqf, (np.array([0.5493, -0.7253, -0.8187]), + np.array([1.3333, 1.216, -0.8427])), + atol=1.5e-4, rtol=0) + + @pytest.mark.parametrize("function", [special.lqn]) + @pytest.mark.parametrize("n", [1, 2, 4, 8, 16, 32]) + @pytest.mark.parametrize("z_complex", [False, True]) + @pytest.mark.parametrize("z_inexact", [False, True]) + @pytest.mark.parametrize( + "input_shape", + [ + (), (1, ), (2, ), (2, 1), (1, 2), (2, 2), (2, 2, 1), (2, 2, 2) + ] + ) + def test_array_inputs_lxn(self, function, n, z_complex, z_inexact, input_shape): + """Tests for correct output shapes.""" + rng = np.random.default_rng(1234) + if z_inexact: + z = rng.integers(-3, 3, size=input_shape) + else: + z = rng.uniform(-1, 1, size=input_shape) + + if z_complex: + z = 1j * z + 0.5j * z + + with warnings.catch_warnings(): + warnings.simplefilter("ignore", category=DeprecationWarning) + P_z, P_d_z = function(n, z) + assert P_z.shape == (n + 1, ) + input_shape + assert P_d_z.shape == (n + 1, ) + input_shape + + @pytest.mark.parametrize("function", [special.lqmn]) + @pytest.mark.parametrize( + "m,n", + [(0, 1), (1, 2), (1, 4), (3, 8), (11, 16), (19, 32)] + ) + @pytest.mark.parametrize("z_inexact", [False, True]) + @pytest.mark.parametrize( + "input_shape", [ + (), (1, ), (2, ), (2, 1), (1, 2), (2, 2), (2, 2, 1) + ] + ) + def test_array_inputs_lxmn(self, function, m, n, z_inexact, input_shape): + """Tests for correct output shapes and dtypes.""" + rng = np.random.default_rng(1234) + if z_inexact: + z = rng.integers(-3, 3, size=input_shape) + else: + z = rng.uniform(-1, 1, size=input_shape) + + P_z, P_d_z = function(m, n, z) + assert P_z.shape == (m + 1, n + 1) + input_shape + assert P_d_z.shape == (m + 1, n + 1) + input_shape + + @pytest.mark.parametrize("function", [special.lqmn]) + @pytest.mark.parametrize( + "m,n", + [(0, 1), (1, 2), (1, 4), (3, 8), (11, 16), (19, 32)] + ) + @pytest.mark.parametrize( + "input_shape", [ + (), (1, ), (2, ), (2, 1), (1, 2), (2, 2), (2, 2, 1) + ] + ) + def test_array_inputs_clxmn(self, function, m, n, input_shape): + """Tests for correct output shapes and dtypes.""" + rng = np.random.default_rng(1234) + z = rng.uniform(-1, 1, size=input_shape) + z = 1j * z + 0.5j * z + + with warnings.catch_warnings(): + warnings.simplefilter("ignore", category=DeprecationWarning) + P_z, P_d_z = function(m, n, z) + + assert P_z.shape == (m + 1, n + 1) + input_shape + assert P_d_z.shape == (m + 1, n + 1) + input_shape + +def assoc_legendre_factor(n, m, norm): + if norm: + return (math.sqrt((2 * n + 1) * + math.factorial(n - m) / (2 * math.factorial(n + m)))) + + return 1 + +def assoc_legendre_p_0_0(z, *, branch_cut=2, norm=False): + fac = assoc_legendre_factor(0, 0, norm) + + return np.full_like(z, fac) + +def assoc_legendre_p_1_0(z, *, branch_cut=2, norm=False): + fac = assoc_legendre_factor(1, 0, norm) + + return fac * z + +def assoc_legendre_p_1_1(z, *, branch_cut=2, norm=False): + branch_sign = np.where(branch_cut == 3, np.where(np.signbit(np.real(z)), 1, -1), -1) + branch_cut_sign = np.where(branch_cut == 3, -1, 1) + fac = assoc_legendre_factor(1, 1, norm) + + w = np.sqrt(np.where(branch_cut == 3, z * z - 1, 1 - z * z)) + + return branch_cut_sign * branch_sign * fac * w + +def assoc_legendre_p_1_m1(z, *, branch_cut=2, norm=False): + branch_cut_sign = np.where(branch_cut == 3, -1, 1) + fac = assoc_legendre_factor(1, -1, norm) + + return (-branch_cut_sign * fac * + assoc_legendre_p_1_1(z, branch_cut=branch_cut) / 2) + +def assoc_legendre_p_2_0(z, *, branch_cut=2, norm=False): + fac = assoc_legendre_factor(2, 0, norm) + + return fac * (3 * z * z - 1) / 2 + +def assoc_legendre_p_2_1(z, *, branch_cut=2, norm=False): + fac = assoc_legendre_factor(2, 1, norm) + + return (3 * fac * z * + assoc_legendre_p_1_1(z, branch_cut=branch_cut)) + +def assoc_legendre_p_2_2(z, *, branch_cut=2, norm=False): + branch_cut_sign = np.where(branch_cut == 3, -1, 1) + fac = assoc_legendre_factor(2, 2, norm) + + return 3 * branch_cut_sign * fac * (1 - z * z) + +def assoc_legendre_p_2_m2(z, *, branch_cut=2, norm=False): + branch_cut_sign = np.where(branch_cut == 3, -1, 1) + fac = assoc_legendre_factor(2, -2, norm) + + return branch_cut_sign * fac * (1 - z * z) / 8 + +def assoc_legendre_p_2_m1(z, *, branch_cut=2, norm=False): + branch_cut_sign = np.where(branch_cut == 3, -1, 1) + fac = assoc_legendre_factor(2, -1, norm) + + return (-branch_cut_sign * fac * z * + assoc_legendre_p_1_1(z, branch_cut=branch_cut) / 2) + +def assoc_legendre_p_3_0(z, *, branch_cut=2, norm=False): + fac = assoc_legendre_factor(3, 0, norm) + + return fac * (5 * z * z - 3) * z / 2 + +def assoc_legendre_p_3_1(z, *, branch_cut=2, norm=False): + fac = assoc_legendre_factor(3, 1, norm) + + return (3 * fac * (5 * z * z - 1) * + assoc_legendre_p_1_1(z, branch_cut=branch_cut) / 2) + +def assoc_legendre_p_3_2(z, *, branch_cut=2, norm=False): + branch_cut_sign = np.where(branch_cut == 3, -1, 1) + fac = assoc_legendre_factor(3, 2, norm) + + return 15 * branch_cut_sign * fac * (1 - z * z) * z + +def assoc_legendre_p_3_3(z, *, branch_cut=2, norm=False): + branch_cut_sign = np.where(branch_cut == 3, -1, 1) + fac = assoc_legendre_factor(3, 3, norm) + + return (15 * branch_cut_sign * fac * (1 - z * z) * + assoc_legendre_p_1_1(z, branch_cut=branch_cut)) + +def assoc_legendre_p_3_m3(z, *, branch_cut=2, norm=False): + fac = assoc_legendre_factor(3, -3, norm) + + return (fac * (z * z - 1) * + assoc_legendre_p_1_1(z, branch_cut=branch_cut) / 48) + +def assoc_legendre_p_3_m2(z, *, branch_cut=2, norm=False): + branch_cut_sign = np.where(branch_cut == 3, -1, 1) + fac = assoc_legendre_factor(3, -2, norm) + + return branch_cut_sign * fac * (1 - z * z) * z / 8 + +def assoc_legendre_p_3_m1(z, *, branch_cut=2, norm=False): + branch_cut_sign = np.where(branch_cut == 3, -1, 1) + fac = assoc_legendre_factor(3, -1, norm) + + return (branch_cut_sign * fac * (1 - 5 * z * z) * + assoc_legendre_p_1_1(z, branch_cut=branch_cut) / 8) + +def assoc_legendre_p_4_0(z, *, branch_cut=2, norm=False): + fac = assoc_legendre_factor(4, 0, norm) + + return fac * ((35 * z * z - 30) * z * z + 3) / 8 + +def assoc_legendre_p_4_1(z, *, branch_cut=2, norm=False): + fac = assoc_legendre_factor(4, 1, norm) + + return (5 * fac * (7 * z * z - 3) * z * + assoc_legendre_p_1_1(z, branch_cut=branch_cut) / 2) + +def assoc_legendre_p_4_2(z, *, branch_cut=2, norm=False): + branch_cut_sign = np.where(branch_cut == 3, -1, 1) + fac = assoc_legendre_factor(4, 2, norm) + + return 15 * branch_cut_sign * fac * ((8 - 7 * z * z) * z * z - 1) / 2 + +def assoc_legendre_p_4_3(z, *, branch_cut=2, norm=False): + branch_cut_sign = np.where(branch_cut == 3, -1, 1) + fac = assoc_legendre_factor(4, 3, norm) + + return (105 * branch_cut_sign * fac * (1 - z * z) * z * + assoc_legendre_p_1_1(z, branch_cut=branch_cut)) + +def assoc_legendre_p_4_4(z, *, branch_cut=2, norm=False): + fac = assoc_legendre_factor(4, 4, norm) + + return 105 * fac * np.square(z * z - 1) + +def assoc_legendre_p_4_m4(z, *, branch_cut=2, norm=False): + fac = assoc_legendre_factor(4, -4, norm) + + return fac * np.square(z * z - 1) / 384 + +def assoc_legendre_p_4_m3(z, *, branch_cut=2, norm=False): + fac = assoc_legendre_factor(4, -3, norm) + + return (fac * (z * z - 1) * z * + assoc_legendre_p_1_1(z, branch_cut=branch_cut) / 48) + +def assoc_legendre_p_4_m2(z, *, branch_cut=2, norm=False): + branch_cut_sign = np.where(branch_cut == 3, -1, 1) + fac = assoc_legendre_factor(4, -2, norm) + + return branch_cut_sign * fac * ((8 - 7 * z * z) * z * z - 1) / 48 + +def assoc_legendre_p_4_m1(z, *, branch_cut=2, norm=False): + branch_cut_sign = np.where(branch_cut == 3, -1, 1) + fac = assoc_legendre_factor(4, -1, norm) + + return (branch_cut_sign * fac * (3 - 7 * z * z) * z * + assoc_legendre_p_1_1(z, branch_cut=branch_cut) / 8) + +def assoc_legendre_p_1_1_jac_div_z(z, branch_cut=2): + branch_sign = np.where(branch_cut == 3, np.where(np.signbit(np.real(z)), 1, -1), -1) + + out11_div_z = (-branch_sign / + np.sqrt(np.where(branch_cut == 3, z * z - 1, 1 - z * z))) + + return out11_div_z + +def assoc_legendre_p_0_0_jac(z, *, branch_cut=2, norm=False): + return np.zeros_like(z) + +def assoc_legendre_p_1_0_jac(z, *, branch_cut=2, norm=False): + fac = assoc_legendre_factor(1, 0, norm) + + return np.full_like(z, fac) + +def assoc_legendre_p_1_1_jac(z, *, branch_cut=2, norm=False): + fac = assoc_legendre_factor(1, 1, norm) + + return (fac * z * + assoc_legendre_p_1_1_jac_div_z(z, branch_cut=branch_cut)) + +def assoc_legendre_p_1_m1_jac(z, *, branch_cut=2, norm=False): + branch_cut_sign = np.where(branch_cut == 3, -1, 1) + fac = assoc_legendre_factor(1, -1, norm) + + return (-branch_cut_sign * fac * z * + assoc_legendre_p_1_1_jac_div_z(z, branch_cut=branch_cut) / 2) + +def assoc_legendre_p_2_0_jac(z, *, branch_cut=2, norm=False): + fac = assoc_legendre_factor(2, 0, norm) + + return 3 * fac * z + +def assoc_legendre_p_2_1_jac(z, *, branch_cut=2, norm=False): + fac = assoc_legendre_factor(2, 1, norm) + + return (3 * fac * (2 * z * z - 1) * + assoc_legendre_p_1_1_jac_div_z(z, branch_cut=branch_cut)) + +def assoc_legendre_p_2_2_jac(z, *, branch_cut=2, norm=False): + branch_cut_sign = np.where(branch_cut == 3, -1, 1) + fac = assoc_legendre_factor(2, 2, norm) + + return -6 * branch_cut_sign * fac * z + +def assoc_legendre_p_2_m1_jac(z, *, branch_cut=2, norm=False): + branch_cut_sign = np.where(branch_cut == 3, -1, 1) + fac = assoc_legendre_factor(2, -1, norm) + + return (branch_cut_sign * fac * (1 - 2 * z * z) * + assoc_legendre_p_1_1_jac_div_z(z, branch_cut=branch_cut) / 2) + +def assoc_legendre_p_2_m2_jac(z, *, branch_cut=2, norm=False): + branch_cut_sign = np.where(branch_cut == 3, -1, 1) + fac = assoc_legendre_factor(2, -2, norm) + + return -branch_cut_sign * fac * z / 4 + +def assoc_legendre_p_3_0_jac(z, *, branch_cut=2, norm=False): + fac = assoc_legendre_factor(3, 0, norm) + + return 3 * fac * (5 * z * z - 1) / 2 + +def assoc_legendre_p_3_1_jac(z, *, branch_cut=2, norm=False): + fac = assoc_legendre_factor(3, 1, norm) + + return (3 * fac * (15 * z * z - 11) * z * + assoc_legendre_p_1_1_jac_div_z(z, branch_cut=branch_cut) / 2) + +def assoc_legendre_p_3_2_jac(z, *, branch_cut=2, norm=False): + branch_cut_sign = np.where(branch_cut == 3, -1, 1) + fac = assoc_legendre_factor(3, 2, norm) + + return 15 * branch_cut_sign * fac * (1 - 3 * z * z) + +def assoc_legendre_p_3_3_jac(z, *, branch_cut=2, norm=False): + branch_cut_sign = np.where(branch_cut == 3, -1, 1) + fac = assoc_legendre_factor(3, 3, norm) + + return (45 * branch_cut_sign * fac * (1 - z * z) * z * + assoc_legendre_p_1_1_jac_div_z(z, branch_cut=branch_cut)) + +def assoc_legendre_p_3_m3_jac(z, *, branch_cut=2, norm=False): + fac = assoc_legendre_factor(3, -3, norm) + + return (fac * (z * z - 1) * z * + assoc_legendre_p_1_1_jac_div_z(z, branch_cut=branch_cut) / 16) + +def assoc_legendre_p_3_m2_jac(z, *, branch_cut=2, norm=False): + branch_cut_sign = np.where(branch_cut == 3, -1, 1) + fac = assoc_legendre_factor(3, -2, norm) + + return branch_cut_sign * fac * (1 - 3 * z * z) / 8 + +def assoc_legendre_p_3_m1_jac(z, *, branch_cut=2, norm=False): + branch_cut_sign = np.where(branch_cut == 3, -1, 1) + fac = assoc_legendre_factor(3, -1, norm) + + return (branch_cut_sign * fac * (11 - 15 * z * z) * z * + assoc_legendre_p_1_1_jac_div_z(z, branch_cut=branch_cut) / 8) + +def assoc_legendre_p_4_0_jac(z, *, branch_cut=2, norm=False): + fac = assoc_legendre_factor(4, 0, norm) + + return 5 * fac * (7 * z * z - 3) * z / 2 + +def assoc_legendre_p_4_1_jac(z, *, branch_cut=2, norm=False): + fac = assoc_legendre_factor(4, 1, norm) + + return (5 * fac * ((28 * z * z - 27) * z * z + 3) * + assoc_legendre_p_1_1_jac_div_z(z, branch_cut=branch_cut) / 2) + +def assoc_legendre_p_4_2_jac(z, *, branch_cut=2, norm=False): + branch_cut_sign = np.where(branch_cut == 3, -1, 1) + fac = assoc_legendre_factor(4, 2, norm) + + return 30 * branch_cut_sign * fac * (4 - 7 * z * z) * z + +def assoc_legendre_p_4_3_jac(z, *, branch_cut=2, norm=False): + branch_cut_sign = np.where(branch_cut == 3, -1, 1) + fac = assoc_legendre_factor(4, 3, norm) + + return (105 * branch_cut_sign * fac * ((5 - 4 * z * z) * z * z - 1) * + assoc_legendre_p_1_1_jac_div_z(z, branch_cut=branch_cut)) + +def assoc_legendre_p_4_4_jac(z, *, branch_cut=2, norm=False): + fac = assoc_legendre_factor(4, 4, norm) + + return 420 * fac * (z * z - 1) * z + +def assoc_legendre_p_4_m4_jac(z, *, branch_cut=2, norm=False): + fac = assoc_legendre_factor(4, -4, norm) + + return fac * (z * z - 1) * z / 96 + +def assoc_legendre_p_4_m3_jac(z, *, branch_cut=2, norm=False): + fac = assoc_legendre_factor(4, -3, norm) + + return (fac * ((4 * z * z - 5) * z * z + 1) * + assoc_legendre_p_1_1_jac_div_z(z, branch_cut=branch_cut) / 48) + +def assoc_legendre_p_4_m2_jac(z, *, branch_cut=2, norm=False): + branch_cut_sign = np.where(branch_cut == 3, -1, 1) + fac = assoc_legendre_factor(4, -2, norm) + + return branch_cut_sign * fac * (4 - 7 * z * z) * z / 12 + +def assoc_legendre_p_4_m1_jac(z, *, branch_cut=2, norm=False): + branch_cut_sign = np.where(branch_cut == 3, -1, 1) + fac = assoc_legendre_factor(4, -1, norm) + + return (branch_cut_sign * fac * ((27 - 28 * z * z) * z * z - 3) * + assoc_legendre_p_1_1_jac_div_z(z, branch_cut=branch_cut) / 8) + +def sph_legendre_factor(n, m): + return assoc_legendre_factor(n, m, norm=True) / np.sqrt(2 * np.pi) + +def sph_legendre_p_0_0(theta): + fac = sph_legendre_factor(0, 0) + + return np.full_like(theta, fac) + +def sph_legendre_p_1_0(theta): + fac = sph_legendre_factor(1, 0) + + return fac * np.cos(theta) + +def sph_legendre_p_1_1(theta): + fac = sph_legendre_factor(1, 1) + + return -fac * np.abs(np.sin(theta)) + +def sph_legendre_p_1_m1(theta): + fac = sph_legendre_factor(1, -1) + + return fac * np.abs(np.sin(theta)) / 2 + +def sph_legendre_p_2_0(theta): + fac = sph_legendre_factor(2, 0) + + return fac * (3 * np.square(np.cos(theta)) - 1) / 2 + +def sph_legendre_p_2_1(theta): + fac = sph_legendre_factor(2, 1) + + return -3 * fac * np.abs(np.sin(theta)) * np.cos(theta) + +def sph_legendre_p_2_2(theta): + fac = sph_legendre_factor(2, 2) + + return 3 * fac * (1 - np.square(np.cos(theta))) + +def sph_legendre_p_2_m2(theta): + fac = sph_legendre_factor(2, -2) + + return fac * (1 - np.square(np.cos(theta))) / 8 + +def sph_legendre_p_2_m1(theta): + fac = sph_legendre_factor(2, -1) + + return fac * np.cos(theta) * np.abs(np.sin(theta)) / 2 + +def sph_legendre_p_3_0(theta): + fac = sph_legendre_factor(3, 0) + + return (fac * (5 * np.square(np.cos(theta)) - 3) * + np.cos(theta) / 2) + +def sph_legendre_p_3_1(theta): + fac = sph_legendre_factor(3, 1) + + return (-3 * fac * (5 * np.square(np.cos(theta)) - 1) * + np.abs(np.sin(theta)) / 2) + +def sph_legendre_p_3_2(theta): + fac = sph_legendre_factor(3, 2) + + return (-15 * fac * (np.square(np.cos(theta)) - 1) * + np.cos(theta)) + +def sph_legendre_p_3_3(theta): + fac = sph_legendre_factor(3, 3) + + return -15 * fac * np.power(np.abs(np.sin(theta)), 3) + +def sph_legendre_p_3_m3(theta): + fac = sph_legendre_factor(3, -3) + + return fac * np.power(np.abs(np.sin(theta)), 3) / 48 + +def sph_legendre_p_3_m2(theta): + fac = sph_legendre_factor(3, -2) + + return (-fac * (np.square(np.cos(theta)) - 1) * + np.cos(theta) / 8) + +def sph_legendre_p_3_m1(theta): + fac = sph_legendre_factor(3, -1) + + return (fac * (5 * np.square(np.cos(theta)) - 1) * + np.abs(np.sin(theta)) / 8) + +def sph_legendre_p_4_0(theta): + fac = sph_legendre_factor(4, 0) + + return (fac * (35 * np.square(np.square(np.cos(theta))) - + 30 * np.square(np.cos(theta)) + 3) / 8) + +def sph_legendre_p_4_1(theta): + fac = sph_legendre_factor(4, 1) + + return (-5 * fac * (7 * np.square(np.cos(theta)) - 3) * + np.cos(theta) * np.abs(np.sin(theta)) / 2) + +def sph_legendre_p_4_2(theta): + fac = sph_legendre_factor(4, 2) + + return (-15 * fac * (7 * np.square(np.cos(theta)) - 1) * + (np.square(np.cos(theta)) - 1) / 2) + +def sph_legendre_p_4_3(theta): + fac = sph_legendre_factor(4, 3) + + return -105 * fac * np.power(np.abs(np.sin(theta)), 3) * np.cos(theta) + +def sph_legendre_p_4_4(theta): + fac = sph_legendre_factor(4, 4) + + return 105 * fac * np.square(np.square(np.cos(theta)) - 1) + +def sph_legendre_p_4_m4(theta): + fac = sph_legendre_factor(4, -4) + + return fac * np.square(np.square(np.cos(theta)) - 1) / 384 + +def sph_legendre_p_4_m3(theta): + fac = sph_legendre_factor(4, -3) + + return (fac * np.power(np.abs(np.sin(theta)), 3) * + np.cos(theta) / 48) + +def sph_legendre_p_4_m2(theta): + fac = sph_legendre_factor(4, -2) + + return (-fac * (7 * np.square(np.cos(theta)) - 1) * + (np.square(np.cos(theta)) - 1) / 48) + +def sph_legendre_p_4_m1(theta): + fac = sph_legendre_factor(4, -1) + + return (fac * (7 * np.square(np.cos(theta)) - 3) * + np.cos(theta) * np.abs(np.sin(theta)) / 8) + +def sph_legendre_p_0_0_jac(theta): + return np.zeros_like(theta) + +def sph_legendre_p_1_0_jac(theta): + fac = sph_legendre_factor(1, 0) + + return -fac * np.sin(theta) + +def sph_legendre_p_1_1_jac(theta): + fac = sph_legendre_factor(1, 1) + + return -fac * np.cos(theta) * (2 * np.heaviside(np.sin(theta), 1) - 1) + +def sph_legendre_p_1_m1_jac(theta): + fac = sph_legendre_factor(1, -1) + + return fac * np.cos(theta) * (2 * np.heaviside(np.sin(theta), 1) - 1) / 2 + +def sph_legendre_p_2_0_jac(theta): + fac = sph_legendre_factor(2, 0) + + return -3 * fac * np.cos(theta) * np.sin(theta) + +def sph_legendre_p_2_1_jac(theta): + fac = sph_legendre_factor(2, 1) + + return (3 * fac * (-np.square(np.cos(theta)) * + (2 * np.heaviside(np.sin(theta), 1) - 1) + + np.abs(np.sin(theta)) * np.sin(theta))) + +def sph_legendre_p_2_2_jac(theta): + fac = sph_legendre_factor(2, 2) + + return 6 * fac * np.sin(theta) * np.cos(theta) + +def sph_legendre_p_2_m2_jac(theta): + fac = sph_legendre_factor(2, -2) + + return fac * np.sin(theta) * np.cos(theta) / 4 + +def sph_legendre_p_2_m1_jac(theta): + fac = sph_legendre_factor(2, -1) + + return (-fac * (-np.square(np.cos(theta)) * + (2 * np.heaviside(np.sin(theta), 1) - 1) + + np.abs(np.sin(theta)) * np.sin(theta)) / 2) + +def sph_legendre_p_3_0_jac(theta): + fac = sph_legendre_factor(3, 0) + + return 3 * fac * (1 - 5 * np.square(np.cos(theta))) * np.sin(theta) / 2 + +def sph_legendre_p_3_1_jac(theta): + fac = sph_legendre_factor(3, 1) + + return (3 * fac * (11 - 15 * np.square(np.cos(theta))) * np.cos(theta) * + (2 * np.heaviside(np.sin(theta), 1) - 1) / 2) + +def sph_legendre_p_3_2_jac(theta): + fac = sph_legendre_factor(3, 2) + + return 15 * fac * (3 * np.square(np.cos(theta)) - 1) * np.sin(theta) + +def sph_legendre_p_3_3_jac(theta): + fac = sph_legendre_factor(3, 3) + + return -45 * fac * np.abs(np.sin(theta)) * np.sin(theta) * np.cos(theta) + +def sph_legendre_p_3_m3_jac(theta): + fac = sph_legendre_factor(3, -3) + + return fac * np.abs(np.sin(theta)) * np.sin(theta) * np.cos(theta) / 16 + +def sph_legendre_p_3_m2_jac(theta): + fac = sph_legendre_factor(3, -2) + + return fac * (3 * np.square(np.cos(theta)) - 1) * np.sin(theta) / 8 + +def sph_legendre_p_3_m1_jac(theta): + fac = sph_legendre_factor(3, -1) + + return (-fac * (11 - 15 * np.square(np.cos(theta))) * + np.cos(theta) * + (2 * np.heaviside(np.sin(theta), 1) - 1) / 8) + +def sph_legendre_p_4_0_jac(theta): + fac = sph_legendre_factor(4, 0) + + return (-5 * fac * (7 * np.square(np.cos(theta)) - 3) * + np.sin(theta) * np.cos(theta) / 2) + +def sph_legendre_p_4_1_jac(theta): + fac = sph_legendre_factor(4, 1) + + return (5 * fac * (-3 + 27 * np.square(np.cos(theta)) - + 28 * np.square(np.square(np.cos(theta)))) * + (2 * np.heaviside(np.sin(theta), 1) - 1) / 2) + +def sph_legendre_p_4_2_jac(theta): + fac = sph_legendre_factor(4, 2) + + return (30 * fac * (7 * np.square(np.cos(theta)) - 4) * + np.sin(theta) * np.cos(theta)) + +def sph_legendre_p_4_3_jac(theta): + fac = sph_legendre_factor(4, 3) + + return (-105 * fac * (4 * np.square(np.cos(theta)) - 1) * + np.abs(np.sin(theta)) * np.sin(theta)) + +def sph_legendre_p_4_4_jac(theta): + fac = sph_legendre_factor(4, 4) + + return (-420 * fac * (np.square(np.cos(theta)) - 1) * + np.sin(theta) * np.cos(theta)) + +def sph_legendre_p_4_m4_jac(theta): + fac = sph_legendre_factor(4, -4) + + return (-fac * (np.square(np.cos(theta)) - 1) * + np.sin(theta) * np.cos(theta) / 96) + +def sph_legendre_p_4_m3_jac(theta): + fac = sph_legendre_factor(4, -3) + + return (fac * (4 * np.square(np.cos(theta)) - 1) * + np.abs(np.sin(theta)) * np.sin(theta) / 48) + +def sph_legendre_p_4_m2_jac(theta): + fac = sph_legendre_factor(4, -2) + + return (fac * (7 * np.square(np.cos(theta)) - 4) * np.sin(theta) * + np.cos(theta) / 12) + +def sph_legendre_p_4_m1_jac(theta): + fac = sph_legendre_factor(4, -1) + + return (-fac * (-3 + 27 * np.square(np.cos(theta)) - + 28 * np.square(np.square(np.cos(theta)))) * + (2 * np.heaviside(np.sin(theta), 1) - 1) / 8) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_log1mexp.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_log1mexp.py new file mode 100644 index 0000000000000000000000000000000000000000..ed8b1958eabd973704c32e9be157397c7b3472e3 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_log1mexp.py @@ -0,0 +1,85 @@ +import numpy as np +import pytest + +from numpy.testing import assert_allclose, assert_equal + +from scipy.special._ufuncs import _log1mexp + +# # Test cases generated with the script +# +# import numpy as np + +# from mpmath import mp + + +# def mp_log1mexp(x): +# with mp.workdps(324): +# return float(mp.log(mp.one - mp.exp(x))) + +# X = np.concat([-np.logspace(-1, -300, 20), np.linspace(-745, -1, 20)]) + +# cases = [(float(x), mp_log1mexp(x)) for x in X] + +@pytest.mark.parametrize( + "x,expected", + [ + (-0.1, -2.3521684610440907), + (-1.8329807108324374e-17, -38.538003135374026), + (-3.359818286283788e-33, -74.773421177754), + (-6.1584821106602796e-49, -111.00883922013399), + (-1.1288378916846929e-64, -147.24425726251397), + (-2.0691380811148324e-80, -183.47967530489393), + (-3.792690190732269e-96, -219.71509334727392), + (-6.951927961775534e-112, -255.95051138965394), + (-1.2742749857031425e-127, -292.1859294320339), + (-2.3357214690901785e-143, -328.42134747441384), + (-4.281332398719571e-159, -364.6567655167938), + (-7.847599703514559e-175, -400.8921835591739), + (-1.4384498882876776e-190, -437.1276016015538), + (-2.6366508987304307e-206, -473.3630196439338), + (-4.832930238571653e-222, -509.59843768631384), + (-8.858667904100796e-238, -545.8338557286938), + (-1.623776739188744e-253, -582.0692737710738), + (-2.9763514416312156e-269, -618.3046918134538), + (-5.455594781168782e-285, -654.5401098558336), + (-1e-300, -690.7755278982137), + (-745.0, -5e-324), + (-705.8421052631579, -2.8619931451743316e-307), + (-666.6842105263158, -2.9021923726875757e-290), + (-627.5263157894738, -2.9429562339405562e-273), + (-588.3684210526316, -2.9842926597143714e-256), + (-549.2105263157895, -3.0262096921839423e-239), + (-510.0526315789474, -3.0687154864846747e-222), + (-470.89473684210526, -3.1118183122979086e-205), + (-431.7368421052632, -3.155526555459449e-188), + (-392.5789473684211, -3.1998487195921207e-171), + (-353.42105263157896, -3.2447934277596653e-154), + (-314.2631578947369, -3.2903694241438367e-137), + (-275.1052631578948, -3.3365855757467166e-120), + (-235.94736842105266, -3.3834508741152875e-103), + (-196.78947368421052, -3.4309744370903894e-86), + (-157.63157894736844, -3.4791655105810003e-69), + (-118.47368421052636, -3.528033470363468e-52), + (-79.31578947368428, -3.577587823905024e-35), + (-40.157894736842195, -3.627838212213697e-18), + (-1.0, -0.4586751453870819), + ] +) +def test_log1mexp(x, expected): + observed = _log1mexp(x) + assert_allclose(observed, expected, rtol=1e-15, atol=1e-300) + + +@pytest.mark.parametrize("x", [1.1, 1e10, np.inf]) +def test_log1mexp_out_of_domain(x): + observed = _log1mexp(x) + assert np.isnan(observed) + + +@pytest.mark.parametrize( + "x,expected", + [(-np.inf, -0.0), (0.0, -np.inf), (-0.0, -np.inf), (np.nan, np.nan)] +) +def test_log1mexp_extreme(x, expected): + observed = _log1mexp(x) + assert_equal(expected, observed) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_loggamma.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_loggamma.py new file mode 100644 index 0000000000000000000000000000000000000000..2fcb5a20037de46df939895d38fbe5fe6b85c9aa --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_loggamma.py @@ -0,0 +1,70 @@ +import numpy as np +from numpy.testing import assert_allclose, assert_ + +from scipy.special._testutils import FuncData +from scipy.special import gamma, gammaln, loggamma + + +def test_identities1(): + # test the identity exp(loggamma(z)) = gamma(z) + x = np.array([-99.5, -9.5, -0.5, 0.5, 9.5, 99.5]) + y = x.copy() + x, y = np.meshgrid(x, y) + z = (x + 1J*y).flatten() + dataset = np.vstack((z, gamma(z))).T + + def f(z): + return np.exp(loggamma(z)) + + FuncData(f, dataset, 0, 1, rtol=1e-14, atol=1e-14).check() + + +def test_identities2(): + # test the identity loggamma(z + 1) = log(z) + loggamma(z) + x = np.array([-99.5, -9.5, -0.5, 0.5, 9.5, 99.5]) + y = x.copy() + x, y = np.meshgrid(x, y) + z = (x + 1J*y).flatten() + dataset = np.vstack((z, np.log(z) + loggamma(z))).T + + def f(z): + return loggamma(z + 1) + + FuncData(f, dataset, 0, 1, rtol=1e-14, atol=1e-14).check() + + +def test_complex_dispatch_realpart(): + # Test that the real parts of loggamma and gammaln agree on the + # real axis. + x = np.r_[-np.logspace(10, -10), np.logspace(-10, 10)] + 0.5 + + dataset = np.vstack((x, gammaln(x))).T + + def f(z): + z = np.array(z, dtype='complex128') + return loggamma(z).real + + FuncData(f, dataset, 0, 1, rtol=1e-14, atol=1e-14).check() + + +def test_real_dispatch(): + x = np.logspace(-10, 10) + 0.5 + dataset = np.vstack((x, gammaln(x))).T + + FuncData(loggamma, dataset, 0, 1, rtol=1e-14, atol=1e-14).check() + assert_(loggamma(0) == np.inf) + assert_(np.isnan(loggamma(-1))) + + +def test_gh_6536(): + z = loggamma(complex(-3.4, +0.0)) + zbar = loggamma(complex(-3.4, -0.0)) + assert_allclose(z, zbar.conjugate(), rtol=1e-15, atol=0) + + +def test_branch_cut(): + # Make sure negative zero is treated correctly + x = -np.logspace(300, -30, 100) + z = np.asarray([complex(x0, 0.0) for x0 in x]) + zbar = np.asarray([complex(x0, -0.0) for x0 in x]) + assert_allclose(z, zbar.conjugate(), rtol=1e-15, atol=0) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_logit.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_logit.py new file mode 100644 index 0000000000000000000000000000000000000000..ae8dd8851d8932dcbbda68c7f11edbe2daafcf00 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_logit.py @@ -0,0 +1,161 @@ +import numpy as np +from numpy.testing import assert_equal, assert_allclose +from scipy.special import logit, expit, log_expit + + +class TestLogit: + + def check_logit_out(self, a, expected): + actual = logit(a) + assert_equal(actual.dtype, a.dtype) + rtol = 16*np.finfo(a.dtype).eps + assert_allclose(actual, expected, rtol=rtol) + + def test_float32(self): + a = np.concatenate((np.linspace(0, 1, 10, dtype=np.float32), + [np.float32(0.0001), np.float32(0.49999), + np.float32(0.50001)])) + # Expected values computed with mpmath from float32 inputs, e.g. + # from mpmath import mp + # mp.dps = 200 + # a = np.float32(1/9) + # print(np.float32(mp.log(a) - mp.log1p(-a))) + # prints `-2.0794415`. + expected = np.array([-np.inf, -2.0794415, -1.2527629, -6.9314712e-01, + -2.2314353e-01, 2.2314365e-01, 6.9314724e-01, + 1.2527630, 2.0794415, np.inf, + -9.2102404, -4.0054321e-05, 4.0054321e-05], + dtype=np.float32) + self.check_logit_out(a, expected) + + def test_float64(self): + a = np.concatenate((np.linspace(0, 1, 10, dtype=np.float64), + [1e-8, 0.4999999999999, 0.50000000001])) + # Expected values computed with mpmath. + expected = np.array([-np.inf, + -2.079441541679836, + -1.252762968495368, + -0.6931471805599454, + -0.22314355131420985, + 0.22314355131420985, + 0.6931471805599452, + 1.2527629684953674, + 2.0794415416798353, + np.inf, + -18.420680733952366, + -3.999023334699814e-13, + 4.000000330961484e-11]) + self.check_logit_out(a, expected) + + def test_nan(self): + expected = np.array([np.nan]*4) + with np.errstate(invalid='ignore'): + actual = logit(np.array([-3., -2., 2., 3.])) + + assert_equal(expected, actual) + + +class TestExpit: + def check_expit_out(self, dtype, expected): + a = np.linspace(-4, 4, 10) + a = np.array(a, dtype=dtype) + actual = expit(a) + assert_allclose(actual, expected, atol=1.5e-7, rtol=0) + assert_equal(actual.dtype, np.dtype(dtype)) + + def test_float32(self): + expected = np.array([0.01798621, 0.04265125, + 0.09777259, 0.20860852, + 0.39068246, 0.60931754, + 0.79139149, 0.9022274, + 0.95734876, 0.98201376], dtype=np.float32) + self.check_expit_out('f4', expected) + + def test_float64(self): + expected = np.array([0.01798621, 0.04265125, + 0.0977726, 0.20860853, + 0.39068246, 0.60931754, + 0.79139147, 0.9022274, + 0.95734875, 0.98201379]) + self.check_expit_out('f8', expected) + + def test_large(self): + for dtype in (np.float32, np.float64, np.longdouble): + for n in (88, 89, 709, 710, 11356, 11357): + n = np.array(n, dtype=dtype) + assert_allclose(expit(n), 1.0, atol=1e-20) + assert_allclose(expit(-n), 0.0, atol=1e-20) + assert_equal(expit(n).dtype, dtype) + assert_equal(expit(-n).dtype, dtype) + + +class TestLogExpit: + + def test_large_negative(self): + x = np.array([-10000.0, -750.0, -500.0, -35.0]) + y = log_expit(x) + assert_equal(y, x) + + def test_large_positive(self): + x = np.array([750.0, 1000.0, 10000.0]) + y = log_expit(x) + # y will contain -0.0, and -0.0 is used in the expected value, + # but assert_equal does not check the sign of zeros, and I don't + # think the sign is an essential part of the test (i.e. it would + # probably be OK if log_expit(1000) returned 0.0 instead of -0.0). + assert_equal(y, np.array([-0.0, -0.0, -0.0])) + + def test_basic_float64(self): + x = np.array([-32, -20, -10, -3, -1, -0.1, -1e-9, + 0, 1e-9, 0.1, 1, 10, 100, 500, 710, 725, 735]) + y = log_expit(x) + # + # Expected values were computed with mpmath: + # + # import mpmath + # + # mpmath.mp.dps = 100 + # + # def mp_log_expit(x): + # return -mpmath.log1p(mpmath.exp(-x)) + # + # expected = [float(mp_log_expit(t)) for t in x] + # + expected = [-32.000000000000014, -20.000000002061153, + -10.000045398899218, -3.048587351573742, + -1.3132616875182228, -0.7443966600735709, + -0.6931471810599453, -0.6931471805599453, + -0.6931471800599454, -0.6443966600735709, + -0.3132616875182228, -4.539889921686465e-05, + -3.720075976020836e-44, -7.124576406741286e-218, + -4.47628622567513e-309, -1.36930634e-315, + -6.217e-320] + + # When tested locally, only one value in y was not exactly equal to + # expected. That was for x=1, and the y value differed from the + # expected by 1 ULP. For this test, however, I'll use rtol=1e-15. + assert_allclose(y, expected, rtol=1e-15) + + def test_basic_float32(self): + x = np.array([-32, -20, -10, -3, -1, -0.1, -1e-9, + 0, 1e-9, 0.1, 1, 10, 100], dtype=np.float32) + y = log_expit(x) + # + # Expected values were computed with mpmath: + # + # import mpmath + # + # mpmath.mp.dps = 100 + # + # def mp_log_expit(x): + # return -mpmath.log1p(mpmath.exp(-x)) + # + # expected = [np.float32(mp_log_expit(t)) for t in x] + # + expected = np.array([-32.0, -20.0, -10.000046, -3.0485873, + -1.3132616, -0.7443967, -0.6931472, + -0.6931472, -0.6931472, -0.64439666, + -0.3132617, -4.5398898e-05, -3.8e-44], + dtype=np.float32) + + assert_allclose(y, expected, rtol=5e-7) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_logsumexp.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_logsumexp.py new file mode 100644 index 0000000000000000000000000000000000000000..51196a3d5e3ec3376b4311c374d13bec37f37f00 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_logsumexp.py @@ -0,0 +1,481 @@ +import itertools as it +import math +import pytest + +import numpy as np + +from scipy._lib._array_api import (is_array_api_strict, make_xp_test_case, + xp_default_dtype, xp_device) +from scipy._lib._array_api_no_0d import (xp_assert_equal, xp_assert_close, + xp_assert_less) + +from scipy.special import log_softmax, logsumexp, softmax +from scipy.special._logsumexp import _wrap_radians + + +dtypes = ['float32', 'float64', 'int32', 'int64', 'complex64', 'complex128'] +integral_dtypes = ['int32', 'int64'] + + +@pytest.mark.uses_xp_capabilities(False, reason="private") +def test_wrap_radians(xp): + x = xp.asarray([-math.pi-1, -math.pi, -1, -1e-300, + 0, 1e-300, 1, math.pi, math.pi+1]) + ref = xp.asarray([math.pi-1, math.pi, -1, -1e-300, + 0, 1e-300, 1, math.pi, -math.pi+1]) + res = _wrap_radians(x, xp=xp) + xp_assert_close(res, ref, atol=0) + + +# numpy warning filters don't work for dask (dask/dask#3245) +# (also we should not expect the numpy warning filter to work for any Array API +# library) +@pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning") +@pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning") +@pytest.mark.filterwarnings("ignore:overflow encountered:RuntimeWarning") +@make_xp_test_case(logsumexp) +class TestLogSumExp: + def test_logsumexp(self, xp): + # Test with zero-size array + a = xp.asarray([]) + desired = xp.asarray(-xp.inf) + xp_assert_equal(logsumexp(a), desired) + + # Test whether logsumexp() function correctly handles large inputs. + a = xp.arange(200., dtype=xp.float64) + desired = xp.log(xp.sum(xp.exp(a))) + xp_assert_close(logsumexp(a), desired) + + # Now test with large numbers + b = xp.asarray([1000., 1000.]) + desired = xp.asarray(1000.0 + math.log(2.0)) + xp_assert_close(logsumexp(b), desired) + + n = 1000 + b = xp.full((n,), 10000) + desired = xp.asarray(10000.0 + math.log(n)) + xp_assert_close(logsumexp(b), desired) + + x = xp.asarray([1e-40] * 1000000) + logx = xp.log(x) + X = xp.stack([x, x]) + logX = xp.stack([logx, logx]) + xp_assert_close(xp.exp(logsumexp(logX)), xp.sum(X)) + xp_assert_close(xp.exp(logsumexp(logX, axis=0)), xp.sum(X, axis=0)) + xp_assert_close(xp.exp(logsumexp(logX, axis=1)), xp.sum(X, axis=1)) + + # Handling special values properly + inf = xp.asarray([xp.inf]) + nan = xp.asarray([xp.nan]) + xp_assert_equal(logsumexp(inf), inf[0]) + xp_assert_equal(logsumexp(-inf), -inf[0]) + xp_assert_equal(logsumexp(nan), nan[0]) + xp_assert_equal(logsumexp(xp.asarray([-xp.inf, -xp.inf])), -inf[0]) + + # Handling an array with different magnitudes on the axes + a = xp.asarray([[1e10, 1e-10], + [-1e10, -np.inf]]) + ref = xp.asarray([1e10, -1e10]) + xp_assert_close(logsumexp(a, axis=-1), ref) + + # Test keeping dimensions + ref = xp.expand_dims(ref, axis=-1) + xp_assert_close(logsumexp(a, axis=-1, keepdims=True), ref) + + # Test multiple axes + xp_assert_close(logsumexp(a, axis=(-1, -2)), xp.asarray(1e10)) + + def test_logsumexp_b(self, xp): + a = xp.arange(200., dtype=xp.float64) + b = xp.arange(200., 0., -1.) + desired = xp.log(xp.sum(b*xp.exp(a))) + xp_assert_close(logsumexp(a, b=b), desired) + + a = xp.asarray([1000, 1000]) + b = xp.asarray([1.2, 1.2]) + desired = xp.asarray(1000 + math.log(2 * 1.2)) + xp_assert_close(logsumexp(a, b=b), desired) + + x = xp.asarray([1e-40] * 100000) + b = xp.linspace(1, 1000, 100000) + logx = xp.log(x) + X = xp.stack((x, x)) + logX = xp.stack((logx, logx)) + B = xp.stack((b, b)) + xp_assert_close(xp.exp(logsumexp(logX, b=B)), xp.sum(B * X)) + xp_assert_close(xp.exp(logsumexp(logX, b=B, axis=0)), xp.sum(B * X, axis=0)) + xp_assert_close(xp.exp(logsumexp(logX, b=B, axis=1)), xp.sum(B * X, axis=1)) + + def test_logsumexp_sign(self, xp): + a = xp.asarray([1, 1, 1]) + b = xp.asarray([1, -1, -1]) + + r, s = logsumexp(a, b=b, return_sign=True) + xp_assert_close(r, xp.asarray(1.)) + xp_assert_equal(s, xp.asarray(-1.)) + + def test_logsumexp_sign_zero(self, xp): + a = xp.asarray([1, 1]) + b = xp.asarray([1, -1]) + + r, s = logsumexp(a, b=b, return_sign=True) + assert not xp.isfinite(r) + assert not xp.isnan(r) + assert r < 0 + assert s == 0 + + def test_logsumexp_sign_shape(self, xp): + a = xp.ones((1, 2, 3, 4)) + b = xp.ones_like(a) + + r, s = logsumexp(a, axis=2, b=b, return_sign=True) + assert r.shape == s.shape == (1, 2, 4) + + r, s = logsumexp(a, axis=(1, 3), b=b, return_sign=True) + assert r.shape == s.shape == (1,3) + + def test_logsumexp_complex_sign(self, xp): + a = xp.asarray([1 + 1j, 2 - 1j, -2 + 3j]) + + r, s = logsumexp(a, return_sign=True) + + expected_sumexp = xp.sum(xp.exp(a)) + # This is the numpy>=2.0 convention for np.sign + expected_sign = expected_sumexp / xp.abs(expected_sumexp) + + xp_assert_close(s, expected_sign) + xp_assert_close(s * xp.exp(r), expected_sumexp) + + def test_logsumexp_shape(self, xp): + a = xp.ones((1, 2, 3, 4)) + b = xp.ones_like(a) + + r = logsumexp(a, axis=2, b=b) + assert r.shape == (1, 2, 4) + + r = logsumexp(a, axis=(1, 3), b=b) + assert r.shape == (1, 3) + + def test_logsumexp_b_zero(self, xp): + a = xp.asarray([1, 10000]) + b = xp.asarray([1, 0]) + + xp_assert_close(logsumexp(a, b=b), xp.asarray(1.)) + + def test_logsumexp_b_shape(self, xp): + a = xp.zeros((4, 1, 2, 1)) + b = xp.ones((3, 1, 5)) + + logsumexp(a, b=b) + + @pytest.mark.parametrize('arg', (1, [1, 2, 3])) + def test_xp_invalid_input(self, arg): + assert logsumexp(arg) == logsumexp(np.asarray(np.atleast_1d(arg))) + + def test_array_like(self): + a = [1000, 1000] + desired = np.asarray(1000.0 + math.log(2.0)) + xp_assert_close(logsumexp(a), desired) + + @pytest.mark.parametrize('dtype', dtypes) + def test_dtypes_a(self, dtype, xp): + dtype = getattr(xp, dtype) + a = xp.asarray([1000., 1000.], dtype=dtype) + desired_dtype = (xp.asarray(1.).dtype if xp.isdtype(dtype, 'integral') + else dtype) # true for all libraries tested + desired = xp.asarray(1000.0 + math.log(2.0), dtype=desired_dtype) + xp_assert_close(logsumexp(a), desired) + + @pytest.mark.parametrize('dtype_a', dtypes) + @pytest.mark.parametrize('dtype_b', dtypes) + def test_dtypes_ab(self, dtype_a, dtype_b, xp): + xp_dtype_a = getattr(xp, dtype_a) + xp_dtype_b = getattr(xp, dtype_b) + a = xp.asarray([2, 1], dtype=xp_dtype_a) + b = xp.asarray([1, -1], dtype=xp_dtype_b) + if is_array_api_strict(xp): + # special-case for `TypeError: array_api_strict.float32 and + # and array_api_strict.int64 cannot be type promoted together` + xp_float_dtypes = [dtype for dtype in [xp_dtype_a, xp_dtype_b] + if not xp.isdtype(dtype, 'integral')] + if len(xp_float_dtypes) < 2: # at least one is integral + xp_float_dtypes.append(xp.asarray(1.).dtype) + desired_dtype = xp.result_type(*xp_float_dtypes) + else: + desired_dtype = xp.result_type(xp_dtype_a, xp_dtype_b) + if xp.isdtype(desired_dtype, 'integral'): + desired_dtype = xp_default_dtype(xp) + desired = xp.asarray(math.log(math.exp(2) - math.exp(1)), dtype=desired_dtype) + xp_assert_close(logsumexp(a, b=b), desired) + + def test_gh18295(self, xp): + # gh-18295 noted loss of precision when real part of one element is much + # larger than the rest. Check that this is resolved. + a = xp.asarray([0.0, -40.0]) + res = logsumexp(a) + ref = xp.logaddexp(a[0], a[1]) + xp_assert_close(res, ref) + + @pytest.mark.parametrize('dtype', ['complex64', 'complex128']) + def test_gh21610(self, xp, dtype): + # gh-21610 noted that `logsumexp` could return imaginary components + # outside the range (-pi, pi]. Check that this is resolved. + # While working on this, I noticed that all other tests passed even + # when the imaginary component of the result was zero. This suggested + # the need of a stronger test with imaginary dtype. + rng = np.random.default_rng(324984329582349862) + dtype = getattr(xp, dtype) + shape = (10, 100) + x = rng.uniform(1, 40, shape) + 1.j * rng.uniform(1, 40, shape) + x = xp.asarray(x, dtype=dtype) + + res = logsumexp(x, axis=1) + ref = xp.log(xp.sum(xp.exp(x), axis=1)) + max = xp.full_like(xp.imag(res), xp.pi) + xp_assert_less(xp.abs(xp.imag(res)), max) + xp_assert_close(res, ref) + + out, sgn = logsumexp(x, return_sign=True, axis=1) + ref = xp.sum(xp.exp(x), axis=1) + xp_assert_less(xp.abs(xp.imag(sgn)), max) + xp_assert_close(out, xp.real(xp.log(ref))) + xp_assert_close(sgn, ref/xp.abs(ref)) + + def test_gh21709_small_imaginary(self, xp): + # Test that `logsumexp` does not lose relative precision of + # small imaginary components + x = xp.asarray([0, 0.+2.2204460492503132e-17j]) + res = logsumexp(x) + # from mpmath import mp + # mp.dps = 100 + # x, y = mp.mpc(0), mp.mpc('0', '2.2204460492503132e-17') + # ref = complex(mp.log(mp.exp(x) + mp.exp(y))) + ref = xp.asarray(0.6931471805599453+1.1102230246251566e-17j) + xp_assert_close(xp.real(res), xp.real(ref)) + xp_assert_close(xp.imag(res), xp.imag(ref), atol=0, rtol=1e-15) + + + @pytest.mark.parametrize('x,y', list(it.product( + [ + -np.inf, + np.inf, + complex(-np.inf, 0.), + complex(-np.inf, -0.), + complex(-np.inf, np.inf), + complex(-np.inf, -np.inf), + complex(np.inf, 0.), + complex(np.inf, -0.), + complex(np.inf, np.inf), + complex(np.inf, -np.inf), + # Phase in each quadrant. + complex(-np.inf, 0.7533), + complex(-np.inf, 2.3562), + complex(-np.inf, 3.9270), + complex(-np.inf, 5.4978), + complex(np.inf, 0.7533), + complex(np.inf, 2.3562), + complex(np.inf, 3.9270), + complex(np.inf, 5.4978), + ], repeat=2) + )) + def test_gh22601_infinite_elements(self, x, y, xp): + # Test that `logsumexp` does reasonable things in the presence of + # real and complex infinities. + res = logsumexp(xp.asarray([x, y])) + ref = xp.log(xp.sum(xp.exp(xp.asarray([x, y])))) + xp_assert_equal(res, ref) + + def test_no_writeback(self, xp): + """Test that logsumexp doesn't accidentally write back to its parameters.""" + a = xp.asarray([5., 4.]) + b = xp.asarray([3., 2.]) + logsumexp(a) + logsumexp(a, b=b) + xp_assert_equal(a, xp.asarray([5., 4.])) + xp_assert_equal(b, xp.asarray([3., 2.])) + + @pytest.mark.parametrize("x_raw", [1.0, 1.0j, []]) + def test_device(self, x_raw, xp, devices): + """Test input device propagation to output.""" + for d in devices: + x = xp.asarray(x_raw, device=d) + assert xp_device(logsumexp(x)) == xp_device(x) + assert xp_device(logsumexp(x, b=x)) == xp_device(x) + + def test_gh22903(self, xp): + # gh-22903 reported that `logsumexp` produced NaN where the weight associated + # with the max magnitude element was negative and `return_sign=False`, even if + # the net result should be the log of a positive number. + + # result is log of positive number + a = xp.asarray([3.06409428, 0.37251854, 3.87471931]) + b = xp.asarray([1.88190708, 2.84174795, -0.85016884]) + xp_assert_close(logsumexp(a, b=b), logsumexp(a, b=b, return_sign=True)[0]) + + # result is log of negative number + b = xp.asarray([1.88190708, 2.84174795, -3.85016884]) + xp_assert_close(logsumexp(a, b=b), xp.asarray(xp.nan)) + + @pytest.mark.parametrize("a, b, sign_ref", + [([np.inf], None, 1.), + ([np.inf], [-1.], -1.)]) + def test_gh23548(self, xp, a, b, sign_ref): + # gh-23548 reported that `logsumexp` with `return_sign=True` returned a sign + # of NaN with infinite reals + a, b = xp.asarray(a), xp.asarray(b) if b is not None else None + val, sign = logsumexp(a, b=b, return_sign=True) + assert xp.isinf(val) + xp_assert_equal(sign, xp.asarray(sign_ref)) + + +@make_xp_test_case(softmax) +class TestSoftmax: + def test_softmax_fixtures(self, xp): + xp_assert_close(softmax(xp.asarray([1000., 0., 0., 0.])), + xp.asarray([1., 0., 0., 0.]), rtol=1e-13) + xp_assert_close(softmax(xp.asarray([1., 1.])), + xp.asarray([.5, .5]), rtol=1e-13) + xp_assert_close(softmax(xp.asarray([0., 1.])), + xp.asarray([1., np.e])/(1 + np.e), + rtol=1e-13) + + # Expected value computed using mpmath (with mpmath.mp.dps = 200) and then + # converted to float. + x = xp.arange(4, dtype=xp.float64) + expected = xp.asarray([0.03205860328008499, + 0.08714431874203256, + 0.23688281808991013, + 0.6439142598879722], dtype=xp.float64) + + xp_assert_close(softmax(x), expected, rtol=1e-13) + + # Translation property. If all the values are changed by the same amount, + # the softmax result does not change. + xp_assert_close(softmax(x + 100), expected, rtol=1e-13) + + # When axis=None, softmax operates on the entire array, and preserves + # the shape. + xp_assert_close(softmax(xp.reshape(x, (2, 2))), + xp.reshape(expected, (2, 2)), rtol=1e-13) + + def test_softmax_multi_axes(self, xp): + xp_assert_close(softmax(xp.asarray([[1000., 0.], [1000., 0.]]), axis=0), + xp.asarray([[.5, .5], [.5, .5]]), rtol=1e-13) + xp_assert_close(softmax(xp.asarray([[1000., 0.], [1000., 0.]]), axis=1), + xp.asarray([[1., 0.], [1., 0.]]), rtol=1e-13) + + # Expected value computed using mpmath (with mpmath.mp.dps = 200) and then + # converted to float. + x = xp.asarray([[-25., 0., 25., 50.], + [ 1., 325., 749., 750.]]) + expected = xp.asarray([[2.678636961770877e-33, + 1.9287498479371314e-22, + 1.3887943864771144e-11, + 0.999999999986112], + [0.0, + 1.9444526359919372e-185, + 0.2689414213699951, + 0.7310585786300048]]) + xp_assert_close(softmax(x, axis=1), expected, rtol=1e-13) + xp_assert_close(softmax(x.T, axis=0), expected.T, rtol=1e-13) + + # 3-d input, with a tuple for the axis. + x3d = xp.reshape(x, (2, 2, 2)) + xp_assert_close(softmax(x3d, axis=(1, 2)), + xp.reshape(expected, (2, 2, 2)), rtol=1e-13) + + @pytest.mark.xfail_xp_backends("array_api_strict", reason="int->float promotion") + def test_softmax_int_array(self, xp): + xp_assert_close(softmax(xp.asarray([1000, 0, 0, 0])), + xp.asarray([1., 0., 0., 0.]), rtol=1e-13) + + def test_softmax_scalar(self): + xp_assert_close(softmax(1000), np.asarray(1.), rtol=1e-13) + + def test_softmax_array_like(self): + xp_assert_close(softmax([1000, 0, 0, 0]), + np.asarray([1., 0., 0., 0.]), rtol=1e-13) + + +@make_xp_test_case(log_softmax) +class TestLogSoftmax: + def test_log_softmax_basic(self, xp): + xp_assert_close(log_softmax(xp.asarray([1000., 1.])), + xp.asarray([0., -999.]), rtol=1e-13) + + @pytest.mark.xfail_xp_backends("array_api_strict", reason="int->float promotion") + def test_log_softmax_int_array(self, xp): + xp_assert_close(log_softmax(xp.asarray([1000, 1])), + xp.asarray([0., -999.]), rtol=1e-13) + + def test_log_softmax_scalar(self): + xp_assert_close(log_softmax(1.0), 0.0, rtol=1e-13) + + def test_log_softmax_array_like(self): + xp_assert_close(log_softmax([1000, 1]), + np.asarray([0., -999.]), rtol=1e-13) + + @staticmethod + def data_1d(xp): + x = xp.arange(4, dtype=xp.float64) + # Expected value computed using mpmath (with mpmath.mp.dps = 200) + expect = [-3.4401896985611953, + -2.4401896985611953, + -1.4401896985611953, + -0.44018969856119533] + return x, xp.asarray(expect, dtype=xp.float64) + + @staticmethod + def data_2d(xp): + x = xp.reshape(xp.arange(8, dtype=xp.float64), (2, 4)) + + # Expected value computed using mpmath (with mpmath.mp.dps = 200) + expect = [[-3.4401896985611953, + -2.4401896985611953, + -1.4401896985611953, + -0.44018969856119533], + [-3.4401896985611953, + -2.4401896985611953, + -1.4401896985611953, + -0.44018969856119533]] + return x, xp.asarray(expect, dtype=xp.float64) + + @pytest.mark.parametrize("offset", [0, 100]) + def test_log_softmax_translation(self, offset, xp): + # Translation property. If all the values are changed by the same amount, + # the softmax result does not change. + x, expect = self.data_1d(xp) + x += offset + xp_assert_close(log_softmax(x), expect, rtol=1e-13) + + def test_log_softmax_noneaxis(self, xp): + # When axis=None, softmax operates on the entire array, and preserves + # the shape. + x, expect = self.data_1d(xp) + x = xp.reshape(x, (2, 2)) + expect = xp.reshape(expect, (2, 2)) + xp_assert_close(log_softmax(x), expect, rtol=1e-13) + + @pytest.mark.parametrize('axis_2d, expected_2d', [ + (0, np.log(0.5) * np.ones((2, 2))), + (1, [[0., -999.], [0., -999.]]), + ]) + def test_axes(self, axis_2d, expected_2d, xp): + x = xp.asarray([[1000., 1.], [1000., 1.]]) + xp_assert_close(log_softmax(x, axis=axis_2d), + xp.asarray(expected_2d, dtype=x.dtype), rtol=1e-13) + + def test_log_softmax_2d_axis1(self, xp): + x, expect = self.data_2d(xp) + xp_assert_close(log_softmax(x, axis=1), expect, rtol=1e-13) + + def test_log_softmax_2d_axis0(self, xp): + x, expect = self.data_2d(xp) + xp_assert_close(log_softmax(x.T, axis=0), expect.T, rtol=1e-13) + + def test_log_softmax_3d(self, xp): + # 3D input, with a tuple for the axis. + x, expect = self.data_2d(xp) + x = xp.reshape(x, (2, 2, 2)) + expect = xp.reshape(expect, (2, 2, 2)) + xp_assert_close(log_softmax(x, axis=(1, 2)), expect, rtol=1e-13) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_mpmath.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_mpmath.py new file mode 100644 index 0000000000000000000000000000000000000000..8a2c5d9874eb1f80c211a2bfa1bcbf06502ac84b --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_mpmath.py @@ -0,0 +1,2160 @@ +""" +Test SciPy functions versus mpmath, if available. + +""" +import numpy as np +from numpy.testing import assert_, assert_allclose +from numpy import pi +import pytest +import itertools + +from scipy._external.packaging_version import version + +import scipy.special as sc +from scipy.special._testutils import ( + MissingModule, check_version, FuncData, + assert_func_equal) +from scipy.special._mptestutils import ( + Arg, FixedArg, ComplexArg, IntArg, assert_mpmath_equal, + nonfunctional_tooslow, trace_args, time_limited, exception_to_nan, + inf_to_nan) +from scipy.special._ufuncs import ( + _sinpi, _cospi, _lgam1p, _lanczos_sum_expg_scaled, _log1pmx, + _igam_fac) + +try: + import mpmath +except ImportError: + mpmath = MissingModule('mpmath') + +pytestmark = pytest.mark.thread_unsafe( + reason=("mpmath gmpy2 backend is not thread-safe, " + "see https://github.com/mpmath/mpmath/issues/974")) + +# ------------------------------------------------------------------------------ +# expi +# ------------------------------------------------------------------------------ + +@check_version(mpmath, '0.10') +def test_expi_complex(): + dataset = [] + for r in np.logspace(-99, 2, 10): + for p in np.linspace(0, 2*np.pi, 30): + z = r*np.exp(1j*p) + dataset.append((z, complex(mpmath.ei(z)))) + dataset = np.array(dataset, dtype=np.cdouble) + + FuncData(sc.expi, dataset, 0, 1).check() + + +# ------------------------------------------------------------------------------ +# expn +# ------------------------------------------------------------------------------ + +@check_version(mpmath, '0.19') +def test_expn_large_n(): + # Test the transition to the asymptotic regime of n. + dataset = [] + for n in [50, 51]: + for x in np.logspace(0, 4, 200): + with mpmath.workdps(100): + dataset.append((n, x, float(mpmath.expint(n, x)))) + dataset = np.asarray(dataset) + + FuncData(sc.expn, dataset, (0, 1), 2, rtol=1e-13).check() + +# ------------------------------------------------------------------------------ +# hyp0f1 +# ------------------------------------------------------------------------------ + + +@check_version(mpmath, '0.19') +def test_hyp0f1_gh5764(): + # Do a small and somewhat systematic test that runs quickly + dataset = [] + axis = [-99.5, -9.5, -0.5, 0.5, 9.5, 99.5] + for v in axis: + for x in axis: + for y in axis: + z = x + 1j*y + # mpmath computes the answer correctly at dps ~ 17 but + # fails for 20 < dps < 120 (uses a different method); + # set the dps high enough that this isn't an issue + with mpmath.workdps(120): + res = complex(mpmath.hyp0f1(v, z)) + dataset.append((v, z, res)) + dataset = np.array(dataset) + + FuncData(lambda v, z: sc.hyp0f1(v.real, z), dataset, (0, 1), 2, + rtol=1e-13).check() + + +@check_version(mpmath, '0.19') +def test_hyp0f1_gh_1609(): + # this is a regression test for gh-1609 + vv = np.linspace(150, 180, 21) + af = sc.hyp0f1(vv, 0.5) + mf = np.array([mpmath.hyp0f1(v, 0.5) for v in vv]) + assert_allclose(af, mf.astype(float), rtol=1e-12) + + +# ------------------------------------------------------------------------------ +# hyperu +# ------------------------------------------------------------------------------ + +@check_version(mpmath, '1.1.0') +def test_hyperu_around_0(): + dataset = [] + # DLMF 13.2.14-15 test points. + for n in np.arange(-5, 5): + for b in np.linspace(-5, 5, 20): + a = -n + dataset.append((a, b, 0, float(mpmath.hyperu(a, b, 0)))) + a = -n + b - 1 + dataset.append((a, b, 0, float(mpmath.hyperu(a, b, 0)))) + # DLMF 13.2.16-22 test points. + for a in [-10.5, -1.5, -0.5, 0, 0.5, 1, 10]: + for b in [-1.0, -0.5, 0, 0.5, 1, 1.5, 2, 2.5]: + dataset.append((a, b, 0, float(mpmath.hyperu(a, b, 0)))) + dataset = np.array(dataset) + + FuncData(sc.hyperu, dataset, (0, 1, 2), 3, rtol=1e-15, atol=5e-13).check() + + +# ------------------------------------------------------------------------------ +# hyp2f1 +# ------------------------------------------------------------------------------ + +@check_version(mpmath, '1.0.0') +def test_hyp2f1_strange_points(): + pts = [ + (2, -1, -1, 0.7), # expected: 2.4 + (2, -2, -2, 0.7), # expected: 3.87 + ] + pts += list(itertools.product([2, 1, -0.7, -1000], repeat=4)) + pts = [ + (a, b, c, x) for a, b, c, x in pts + if b == c and round(b) == b and b < 0 and b != -1000 + ] + kw = dict(eliminate=True) + dataset = [p + (float(mpmath.hyp2f1(*p, **kw)),) for p in pts] + dataset = np.array(dataset, dtype=np.float64) + + FuncData(sc.hyp2f1, dataset, (0,1,2,3), 4, rtol=1e-10).check() + + +@check_version(mpmath, '0.13') +def test_hyp2f1_real_some_points(): + pts = [ + (1, 2, 3, 0), + (1./3, 2./3, 5./6, 27./32), + (1./4, 1./2, 3./4, 80./81), + (2,-2, -3, 3), + (2, -3, -2, 3), + (2, -1.5, -1.5, 3), + (1, 2, 3, 0), + (0.7235, -1, -5, 0.3), + (0.25, 1./3, 2, 0.999), + (0.25, 1./3, 2, -1), + (2, 3, 5, 0.99), + (3./2, -0.5, 3, 0.99), + (2, 2.5, -3.25, 0.999), + (-8, 18.016500331508873, 10.805295997850628, 0.90875647507000001), + (-10, 900, -10.5, 0.99), + (-10, 900, 10.5, 0.99), + (-1, 2, 1, 1.0), + (-1, 2, 1, -1.0), + (-3, 13, 5, 1.0), + (-3, 13, 5, -1.0), + (0.5, 1 - 270.5, 1.5, 0.999**2), # from issue 1561 + ] + dataset = [p + (float(mpmath.hyp2f1(*p)),) for p in pts] + dataset = np.array(dataset, dtype=np.float64) + + with np.errstate(invalid='ignore'): + FuncData(sc.hyp2f1, dataset, (0,1,2,3), 4, rtol=1e-10).check() + + +@check_version(mpmath, '0.14') +def test_hyp2f1_some_points_2(): + # Taken from mpmath unit tests -- this point failed for mpmath 0.13 but + # was fixed in their SVN since then + pts = [ + (112, (51,10), (-9,10), -0.99999), + (10,-900,10.5,0.99), + (10,-900,-10.5,0.99), + ] + + def fev(x): + if isinstance(x, tuple): + return float(x[0]) / x[1] + else: + return x + + dataset = [tuple(map(fev, p)) + (float(mpmath.hyp2f1(*p)),) for p in pts] + dataset = np.array(dataset, dtype=np.float64) + + FuncData(sc.hyp2f1, dataset, (0,1,2,3), 4, rtol=1e-10).check() + + +@check_version(mpmath, '0.13') +def test_hyp2f1_real_some(): + dataset = [] + for a in [-10, -5, -1.8, 1.8, 5, 10]: + for b in [-2.5, -1, 1, 7.4]: + for c in [-9, -1.8, 5, 20.4]: + for z in [-10, -1.01, -0.99, 0, 0.6, 0.95, 1.5, 10]: + try: + v = float(mpmath.hyp2f1(a, b, c, z)) + except Exception: + continue + dataset.append((a, b, c, z, v)) + dataset = np.array(dataset, dtype=np.float64) + + with np.errstate(invalid='ignore'): + FuncData(sc.hyp2f1, dataset, (0,1,2,3), 4, rtol=1e-9, + ignore_inf_sign=True).check() + + +@check_version(mpmath, '0.12') +@pytest.mark.slow +def test_hyp2f1_real_random(): + npoints = 500 + dataset = np.zeros((npoints, 5), np.float64) + + np.random.seed(1234) + dataset[:, 0] = np.random.pareto(1.5, npoints) + dataset[:, 1] = np.random.pareto(1.5, npoints) + dataset[:, 2] = np.random.pareto(1.5, npoints) + dataset[:, 3] = 2*np.random.rand(npoints) - 1 + + dataset[:, 0] *= (-1)**np.random.randint(2, npoints) + dataset[:, 1] *= (-1)**np.random.randint(2, npoints) + dataset[:, 2] *= (-1)**np.random.randint(2, npoints) + + for ds in dataset: + if mpmath.__version__ < '0.14': + # mpmath < 0.14 fails for c too much smaller than a, b + if abs(ds[:2]).max() > abs(ds[2]): + ds[2] = abs(ds[:2]).max() + ds[4] = float(mpmath.hyp2f1(*tuple(ds[:4]))) + + FuncData(sc.hyp2f1, dataset, (0, 1, 2, 3), 4, rtol=1e-9).check() + + +# ------------------------------------------------------------------------------ +# erf (complex) +# ------------------------------------------------------------------------------ + +@pytest.mark.slow() +@check_version(mpmath, '0.14') +def test_erf_complex(): + # need to increase mpmath precision for this test + old_dps, old_prec = mpmath.mp.dps, mpmath.mp.prec + try: + mpmath.mp.dps = 70 + x1, y1 = np.meshgrid(np.linspace(-10, 1, 31), np.linspace(-10, 1, 11)) + x2, y2 = np.meshgrid(np.logspace(-80, .8, 31), np.logspace(-80, .8, 11)) + points = np.r_[x1.ravel(),x2.ravel()] + 1j*np.r_[y1.ravel(), y2.ravel()] + + assert_func_equal(sc.erf, lambda x: complex(mpmath.erf(x)), points, + vectorized=False, rtol=1e-13) + assert_func_equal(sc.erfc, lambda x: complex(mpmath.erfc(x)), points, + vectorized=False, rtol=1e-13) + finally: + mpmath.mp.dps, mpmath.mp.prec = old_dps, old_prec + + +# ------------------------------------------------------------------------------ +# lpmv +# ------------------------------------------------------------------------------ + +@check_version(mpmath, '0.15') +def test_lpmv(): + pts = [] + for x in [-0.99, -0.557, 1e-6, 0.132, 1]: + pts.extend([ + (1, 1, x), + (1, -1, x), + (-1, 1, x), + (-1, -2, x), + (1, 1.7, x), + (1, -1.7, x), + (-1, 1.7, x), + (-1, -2.7, x), + (1, 10, x), + (1, 11, x), + (3, 8, x), + (5, 11, x), + (-3, 8, x), + (-5, 11, x), + (3, -8, x), + (5, -11, x), + (-3, -8, x), + (-5, -11, x), + (3, 8.3, x), + (5, 11.3, x), + (-3, 8.3, x), + (-5, 11.3, x), + (3, -8.3, x), + (5, -11.3, x), + (-3, -8.3, x), + (-5, -11.3, x), + ]) + + def mplegenp(nu, mu, x): + if mu == int(mu) and x == 1: + # mpmath 0.17 gets this wrong + if mu == 0: + return 1 + else: + return 0 + return mpmath.legenp(nu, mu, x) + + dataset = [p + (mplegenp(p[1], p[0], p[2]),) for p in pts] + dataset = np.array(dataset, dtype=np.float64) + + def evf(mu, nu, x): + return sc.lpmv(mu.astype(int), nu, x) + + with np.errstate(invalid='ignore'): + FuncData(evf, dataset, (0,1,2), 3, rtol=1e-10, atol=1e-14).check() + + +# ------------------------------------------------------------------------------ +# beta +# ------------------------------------------------------------------------------ + +@pytest.mark.slow +@check_version(mpmath, '0.15') +def test_beta(): + np.random.seed(1234) + + b = np.r_[np.logspace(-200, 200, 4), + np.logspace(-10, 10, 4), + np.logspace(-1, 1, 4), + np.arange(-10, 11, 1), + np.arange(-10, 11, 1) + 0.5, + -1, -2.3, -3, -100.3, -10003.4] + a = b + + ab = np.array(np.broadcast_arrays(a[:,None], b[None,:])).reshape(2, -1).T + + old_dps, old_prec = mpmath.mp.dps, mpmath.mp.prec + try: + mpmath.mp.dps = 400 + + assert_func_equal(sc.beta, + lambda a, b: float(mpmath.beta(a, b)), + ab, + vectorized=False, + rtol=1e-10, + ignore_inf_sign=True) + + assert_func_equal( + sc.betaln, + lambda a, b: float(mpmath.log(abs(mpmath.beta(a, b)))), + ab, + vectorized=False, + rtol=1e-10) + finally: + mpmath.mp.dps, mpmath.mp.prec = old_dps, old_prec + + +# ------------------------------------------------------------------------------ +# loggamma +# ------------------------------------------------------------------------------ + +LOGGAMMA_TAYLOR_RADIUS = 0.2 + + +@check_version(mpmath, '0.19') +def test_loggamma_taylor_transition(): + # Make sure there isn't a big jump in accuracy when we move from + # using the Taylor series to using the recurrence relation. + + r = LOGGAMMA_TAYLOR_RADIUS + np.array([-0.1, -0.01, 0, 0.01, 0.1]) + theta = np.linspace(0, 2*np.pi, 20) + r, theta = np.meshgrid(r, theta) + dz = r*np.exp(1j*theta) + z = np.r_[1 + dz, 2 + dz].flatten() + + dataset = [(z0, complex(mpmath.loggamma(z0))) for z0 in z] + dataset = np.array(dataset) + + FuncData(sc.loggamma, dataset, 0, 1, rtol=5e-14).check() + + +@check_version(mpmath, '0.19') +def test_loggamma_taylor(): + # Test around the zeros at z = 1, 2. + + r = np.logspace(-16, np.log10(LOGGAMMA_TAYLOR_RADIUS), 10) + theta = np.linspace(0, 2*np.pi, 20) + r, theta = np.meshgrid(r, theta) + dz = r*np.exp(1j*theta) + z = np.r_[1 + dz, 2 + dz].flatten() + + dataset = [(z0, complex(mpmath.loggamma(z0))) for z0 in z] + dataset = np.array(dataset) + + FuncData(sc.loggamma, dataset, 0, 1, rtol=5e-14).check() + + +# ------------------------------------------------------------------------------ +# rgamma +# ------------------------------------------------------------------------------ + +@check_version(mpmath, '0.19') +@pytest.mark.slow +def test_rgamma_zeros(): + # Test around the zeros at z = 0, -1, -2, ..., -169. (After -169 we + # get values that are out of floating point range even when we're + # within 0.1 of the zero.) + + # Can't use too many points here or the test takes forever. + dx = np.r_[-np.logspace(-1, -13, 3), 0, np.logspace(-13, -1, 3)] + dy = dx.copy() + dx, dy = np.meshgrid(dx, dy) + dz = dx + 1j*dy + zeros = np.arange(0, -170, -1).reshape(1, 1, -1) + z = (zeros + np.dstack((dz,)*zeros.size)).flatten() + with mpmath.workdps(100): + dataset = [(z0, complex(mpmath.rgamma(z0))) for z0 in z] + + dataset = np.array(dataset) + FuncData(sc.rgamma, dataset, 0, 1, rtol=1e-12).check() + + +# ------------------------------------------------------------------------------ +# digamma +# ------------------------------------------------------------------------------ + +@check_version(mpmath, '0.19') +@pytest.mark.slow +def test_digamma_roots(): + # Test the special-cased roots for digamma. + root = mpmath.findroot(mpmath.digamma, 1.5) + roots = [float(root)] + root = mpmath.findroot(mpmath.digamma, -0.5) + roots.append(float(root)) + roots = np.array(roots) + + # If we test beyond a radius of 0.24 mpmath will take forever. + dx = np.r_[-0.24, -np.logspace(-1, -15, 10), 0, np.logspace(-15, -1, 10), 0.24] + dy = dx.copy() + dx, dy = np.meshgrid(dx, dy) + dz = dx + 1j*dy + z = (roots + np.dstack((dz,)*roots.size)).flatten() + with mpmath.workdps(30): + dataset = [(z0, complex(mpmath.digamma(z0))) for z0 in z] + + dataset = np.array(dataset) + FuncData(sc.digamma, dataset, 0, 1, rtol=1e-14).check() + + +@check_version(mpmath, '0.19') +def test_digamma_negreal(): + # Test digamma around the negative real axis. Don't do this in + # TestSystematic because the points need some jiggering so that + # mpmath doesn't take forever. + + digamma = exception_to_nan(mpmath.digamma) + + x = -np.logspace(300, -30, 100) + y = np.r_[-np.logspace(0, -3, 5), 0, np.logspace(-3, 0, 5)] + x, y = np.meshgrid(x, y) + z = (x + 1j*y).flatten() + + with mpmath.workdps(40): + dataset = [(z0, complex(digamma(z0))) for z0 in z] + dataset = np.asarray(dataset) + + FuncData(sc.digamma, dataset, 0, 1, rtol=1e-13).check() + + +@check_version(mpmath, '0.19') +def test_digamma_boundary(): + # Check that there isn't a jump in accuracy when we switch from + # using the asymptotic series to the reflection formula. + + x = -np.logspace(300, -30, 100) + y = np.array([-6.1, -5.9, 5.9, 6.1]) + x, y = np.meshgrid(x, y) + z = (x + 1j*y).flatten() + + with mpmath.workdps(30): + dataset = [(z0, complex(mpmath.digamma(z0))) for z0 in z] + dataset = np.asarray(dataset) + + FuncData(sc.digamma, dataset, 0, 1, rtol=1e-13).check() + + +# ------------------------------------------------------------------------------ +# gammainc +# ------------------------------------------------------------------------------ + +@check_version(mpmath, '0.19') +@pytest.mark.slow +def test_gammainc_boundary(): + # Test the transition to the asymptotic series. + small = 20 + a = np.linspace(0.5*small, 2*small, 50) + x = a.copy() + a, x = np.meshgrid(a, x) + a, x = a.flatten(), x.flatten() + with mpmath.workdps(100): + dataset = [(a0, x0, float(mpmath.gammainc(a0, b=x0, regularized=True))) + for a0, x0 in zip(a, x)] + dataset = np.array(dataset) + + FuncData(sc.gammainc, dataset, (0, 1), 2, rtol=1e-12).check() + + +# ------------------------------------------------------------------------------ +# spence +# ------------------------------------------------------------------------------ + +@check_version(mpmath, '0.19') +@pytest.mark.slow +def test_spence_circle(): + # The trickiest region for spence is around the circle |z - 1| = 1, + # so test that region carefully. + + def spence(z): + return complex(mpmath.polylog(2, 1 - z)) + + r = np.linspace(0.5, 1.5) + theta = np.linspace(0, 2*pi) + z = (1 + np.outer(r, np.exp(1j*theta))).flatten() + dataset = np.asarray([(z0, spence(z0)) for z0 in z]) + + FuncData(sc.spence, dataset, 0, 1, rtol=1e-14).check() + + +# ------------------------------------------------------------------------------ +# sinpi and cospi +# ------------------------------------------------------------------------------ + +@check_version(mpmath, '0.19') +def test_sinpi_zeros(): + eps = np.finfo(float).eps + dx = np.r_[-np.logspace(0, -13, 3), 0, np.logspace(-13, 0, 3)] + dy = dx.copy() + dx, dy = np.meshgrid(dx, dy) + dz = dx + 1j*dy + zeros = np.arange(-100, 100, 1).reshape(1, 1, -1) + z = (zeros + np.dstack((dz,)*zeros.size)).flatten() + dataset = np.asarray([(z0, complex(mpmath.sinpi(z0))) + for z0 in z]) + FuncData(_sinpi, dataset, 0, 1, rtol=2*eps).check() + + +@check_version(mpmath, '0.19') +def test_cospi_zeros(): + eps = np.finfo(float).eps + dx = np.r_[-np.logspace(0, -13, 3), 0, np.logspace(-13, 0, 3)] + dy = dx.copy() + dx, dy = np.meshgrid(dx, dy) + dz = dx + 1j*dy + zeros = (np.arange(-100, 100, 1) + 0.5).reshape(1, 1, -1) + z = (zeros + np.dstack((dz,)*zeros.size)).flatten() + dataset = np.asarray([(z0, complex(mpmath.cospi(z0))) + for z0 in z]) + + FuncData(_cospi, dataset, 0, 1, rtol=2*eps).check() + + +# ------------------------------------------------------------------------------ +# ellipj +# ------------------------------------------------------------------------------ + +@check_version(mpmath, '0.19') +def test_dn_quarter_period(): + def dn(u, m): + return sc.ellipj(u, m)[2] + + def mpmath_dn(u, m): + return float(mpmath.ellipfun("dn", u=u, m=m)) + + m = np.linspace(0, 1, 20) + du = np.r_[-np.logspace(-1, -15, 10), 0, np.logspace(-15, -1, 10)] + dataset = [] + for m0 in m: + u0 = float(mpmath.ellipk(m0)) + for du0 in du: + p = u0 + du0 + dataset.append((p, m0, mpmath_dn(p, m0))) + dataset = np.asarray(dataset) + + FuncData(dn, dataset, (0, 1), 2, rtol=1e-10).check() + + +# ------------------------------------------------------------------------------ +# Wright Omega +# ------------------------------------------------------------------------------ + +def _mpmath_wrightomega(z, dps): + with mpmath.workdps(dps): + z = mpmath.mpc(z) + unwind = mpmath.ceil((z.imag - mpmath.pi)/(2*mpmath.pi)) + res = mpmath.lambertw(mpmath.exp(z), unwind) + return res + + +@pytest.mark.slow +@check_version(mpmath, '0.19') +def test_wrightomega_branch(): + x = -np.logspace(10, 0, 25) + picut_above = [np.nextafter(np.pi, np.inf)] + picut_below = [np.nextafter(np.pi, -np.inf)] + npicut_above = [np.nextafter(-np.pi, np.inf)] + npicut_below = [np.nextafter(-np.pi, -np.inf)] + for i in range(50): + picut_above.append(np.nextafter(picut_above[-1], np.inf)) + picut_below.append(np.nextafter(picut_below[-1], -np.inf)) + npicut_above.append(np.nextafter(npicut_above[-1], np.inf)) + npicut_below.append(np.nextafter(npicut_below[-1], -np.inf)) + y = np.hstack((picut_above, picut_below, npicut_above, npicut_below)) + x, y = np.meshgrid(x, y) + z = (x + 1j*y).flatten() + + dataset = np.asarray([(z0, complex(_mpmath_wrightomega(z0, 25))) + for z0 in z]) + + FuncData(sc.wrightomega, dataset, 0, 1, rtol=1e-8).check() + + +@pytest.mark.slow +@check_version(mpmath, '0.19') +def test_wrightomega_region1(): + # This region gets less coverage in the TestSystematic test + x = np.linspace(-2, 1) + y = np.linspace(1, 2*np.pi) + x, y = np.meshgrid(x, y) + z = (x + 1j*y).flatten() + + dataset = np.asarray([(z0, complex(_mpmath_wrightomega(z0, 25))) + for z0 in z]) + + FuncData(sc.wrightomega, dataset, 0, 1, rtol=1e-15).check() + + +@pytest.mark.slow +@check_version(mpmath, '0.19') +def test_wrightomega_region2(): + # This region gets less coverage in the TestSystematic test + x = np.linspace(-2, 1) + y = np.linspace(-2*np.pi, -1) + x, y = np.meshgrid(x, y) + z = (x + 1j*y).flatten() + + dataset = np.asarray([(z0, complex(_mpmath_wrightomega(z0, 25))) + for z0 in z]) + + FuncData(sc.wrightomega, dataset, 0, 1, rtol=1e-15).check() + + +# ------------------------------------------------------------------------------ +# lambertw +# ------------------------------------------------------------------------------ + +@pytest.mark.slow +@check_version(mpmath, '0.19') +def test_lambertw_smallz(): + x, y = np.linspace(-1, 1, 25), np.linspace(-1, 1, 25) + x, y = np.meshgrid(x, y) + z = (x + 1j*y).flatten() + + dataset = np.asarray([(z0, complex(mpmath.lambertw(z0))) + for z0 in z]) + + FuncData(sc.lambertw, dataset, 0, 1, rtol=1e-13).check() + + +# ------------------------------------------------------------------------------ +# Systematic tests +# ------------------------------------------------------------------------------ + +HYPERKW = dict(maxprec=200, maxterms=200) + + +@pytest.mark.slow +@check_version(mpmath, '0.17') +class TestSystematic: + + def test_airyai(self): + # oscillating function, limit range + assert_mpmath_equal(lambda z: sc.airy(z)[0], + mpmath.airyai, + [Arg(-1e8, 1e8)], + rtol=1e-5) + assert_mpmath_equal(lambda z: sc.airy(z)[0], + mpmath.airyai, + [Arg(-1e3, 1e3)]) + + def test_airyai_complex(self): + assert_mpmath_equal(lambda z: sc.airy(z)[0], + mpmath.airyai, + [ComplexArg()]) + + def test_airyai_prime(self): + # oscillating function, limit range + assert_mpmath_equal(lambda z: sc.airy(z)[1], lambda z: + mpmath.airyai(z, derivative=1), + [Arg(-1e8, 1e8)], + rtol=1e-5) + assert_mpmath_equal(lambda z: sc.airy(z)[1], lambda z: + mpmath.airyai(z, derivative=1), + [Arg(-1e3, 1e3)]) + + def test_airyai_prime_complex(self): + assert_mpmath_equal(lambda z: sc.airy(z)[1], lambda z: + mpmath.airyai(z, derivative=1), + [ComplexArg()]) + + def test_airybi(self): + # oscillating function, limit range + assert_mpmath_equal(lambda z: sc.airy(z)[2], lambda z: + mpmath.airybi(z), + [Arg(-1e8, 1e8)], + rtol=1e-5) + assert_mpmath_equal(lambda z: sc.airy(z)[2], lambda z: + mpmath.airybi(z), + [Arg(-1e3, 1e3)]) + + def test_airybi_complex(self): + assert_mpmath_equal(lambda z: sc.airy(z)[2], lambda z: + mpmath.airybi(z), + [ComplexArg()]) + + def test_airybi_prime(self): + # oscillating function, limit range + assert_mpmath_equal(lambda z: sc.airy(z)[3], lambda z: + mpmath.airybi(z, derivative=1), + [Arg(-1e8, 1e8)], + rtol=1e-5) + assert_mpmath_equal(lambda z: sc.airy(z)[3], lambda z: + mpmath.airybi(z, derivative=1), + [Arg(-1e3, 1e3)]) + + def test_airybi_prime_complex(self): + assert_mpmath_equal(lambda z: sc.airy(z)[3], lambda z: + mpmath.airybi(z, derivative=1), + [ComplexArg()]) + + def test_bei(self): + assert_mpmath_equal(sc.bei, + exception_to_nan(lambda z: mpmath.bei(0, z, **HYPERKW)), + [Arg(-1e3, 1e3)]) + + def test_ber(self): + assert_mpmath_equal(sc.ber, + exception_to_nan(lambda z: mpmath.ber(0, z, **HYPERKW)), + [Arg(-1e3, 1e3)]) + + def test_bernoulli(self): + assert_mpmath_equal(lambda n: sc.bernoulli(int(n))[int(n)], + lambda n: float(mpmath.bernoulli(int(n))), + [IntArg(0, 13000)], + rtol=1e-9, n=13000) + + def test_besseli(self): + assert_mpmath_equal( + sc.iv, + exception_to_nan(lambda v, z: mpmath.besseli(v, z, **HYPERKW)), + [Arg(-1e100, 1e100), Arg()], + atol=1e-270, + ) + + def test_besseli_complex(self): + assert_mpmath_equal( + lambda v, z: sc.iv(v.real, z), + exception_to_nan(lambda v, z: mpmath.besseli(v, z, **HYPERKW)), + [Arg(-1e100, 1e100), ComplexArg()], + ) + + def test_besselj(self): + assert_mpmath_equal( + sc.jv, + exception_to_nan(lambda v, z: mpmath.besselj(v, z, **HYPERKW)), + [Arg(-1e100, 1e100), Arg(-1e3, 1e3)], + ignore_inf_sign=True, + ) + + # loss of precision at large arguments due to oscillation + assert_mpmath_equal( + sc.jv, + exception_to_nan(lambda v, z: mpmath.besselj(v, z, **HYPERKW)), + [Arg(-1e100, 1e100), Arg(-1e8, 1e8)], + ignore_inf_sign=True, + rtol=1e-5, + ) + + def test_besselj_complex(self): + assert_mpmath_equal( + lambda v, z: sc.jv(v.real, z), + exception_to_nan(lambda v, z: mpmath.besselj(v, z, **HYPERKW)), + [Arg(), ComplexArg()] + ) + + def test_besselk(self): + assert_mpmath_equal( + sc.kv, + mpmath.besselk, + [Arg(-200, 200), Arg(0, np.inf)], + nan_ok=False, + rtol=1e-12, + ) + + def test_besselk_int(self): + assert_mpmath_equal( + sc.kn, + mpmath.besselk, + [IntArg(-200, 200), Arg(0, np.inf)], + nan_ok=False, + rtol=1e-12, + ) + + def test_besselk_complex(self): + assert_mpmath_equal( + lambda v, z: sc.kv(v.real, z), + exception_to_nan(lambda v, z: mpmath.besselk(v, z, **HYPERKW)), + [Arg(-1e100, 1e100), ComplexArg()], + ) + + def test_bessely(self): + def mpbessely(v, x): + r = float(mpmath.bessely(v, x, **HYPERKW)) + if abs(r) > 1e305: + # overflowing to inf a bit earlier is OK + r = np.inf * np.sign(r) + if abs(r) == 0 and x == 0: + # invalid result from mpmath, point x=0 is a divergence + return np.nan + return r + assert_mpmath_equal( + sc.yv, + exception_to_nan(mpbessely), + [Arg(-1e100, 1e100), Arg(-1e8, 1e8)], + n=5000, + ) + + def test_bessely_complex(self): + def mpbessely(v, x): + r = complex(mpmath.bessely(v, x, **HYPERKW)) + if abs(r) > 1e305: + # overflowing to inf a bit earlier is OK + with np.errstate(invalid='ignore'): + r = np.inf * np.sign(r) + return r + assert_mpmath_equal( + lambda v, z: sc.yv(v.real, z), + exception_to_nan(mpbessely), + [Arg(), ComplexArg()], + n=15000, + ) + + def test_bessely_int(self): + def mpbessely(v, x): + r = float(mpmath.bessely(v, x)) + if abs(r) == 0 and x == 0: + # invalid result from mpmath, point x=0 is a divergence + return np.nan + return r + assert_mpmath_equal( + lambda v, z: sc.yn(int(v), z), + exception_to_nan(mpbessely), + [IntArg(-1000, 1000), Arg(-1e8, 1e8)], + ) + + def test_beta(self): + bad_points = [] + + def beta(a, b, nonzero=False): + if a < -1e12 or b < -1e12: + # Function is defined here only at integers, but due + # to loss of precision this is numerically + # ill-defined. Don't compare values here. + return np.nan + if (a < 0 or b < 0) and (abs(float(a + b)) % 1) == 0: + # close to a zero of the function: mpmath and scipy + # will not round here the same, so the test needs to be + # run with an absolute tolerance + if nonzero: + bad_points.append((float(a), float(b))) + return np.nan + return mpmath.beta(a, b) + + assert_mpmath_equal( + sc.beta, + lambda a, b: beta(a, b, nonzero=True), + [Arg(), Arg()], + dps=400, + ignore_inf_sign=True, + ) + + assert_mpmath_equal( + sc.beta, + beta, + np.array(bad_points), + dps=400, + ignore_inf_sign=True, + atol=1e-11, + ) + + def test_betainc(self): + assert_mpmath_equal( + sc.betainc, + time_limited()( + exception_to_nan( + lambda a, b, x: mpmath.betainc(a, b, 0, x, regularized=True) + ) + ), + [Arg(), Arg(), Arg()], + ) + + def test_betaincc(self): + assert_mpmath_equal( + sc.betaincc, + time_limited()( + exception_to_nan( + lambda a, b, x: mpmath.betainc(a, b, x, 1, regularized=True) + ) + ), + [Arg(), Arg(), Arg()], + dps=400, + ) + + def test_binom(self): + bad_points = [] + + def binomial(n, k, nonzero=False): + if abs(k) > 1e8*(abs(n) + 1): + # The binomial is rapidly oscillating in this region, + # and the function is numerically ill-defined. Don't + # compare values here. + return np.nan + if n < k and abs(float(n-k) - np.round(float(n-k))) < 1e-15: + # close to a zero of the function: mpmath and scipy + # will not round here the same, so the test needs to be + # run with an absolute tolerance + if nonzero: + bad_points.append((float(n), float(k))) + return np.nan + return mpmath.binomial(n, k) + + assert_mpmath_equal( + sc.binom, + lambda n, k: binomial(n, k, nonzero=True), + [Arg(), Arg()], + dps=400, + ) + + assert_mpmath_equal( + sc.binom, + binomial, + np.array(bad_points), + dps=400, + atol=1e-14, + ) + + def test_chebyt_int(self): + assert_mpmath_equal( + lambda n, x: sc.eval_chebyt(int(n), x), + exception_to_nan(lambda n, x: mpmath.chebyt(n, x, **HYPERKW)), + [IntArg(), Arg()], + dps=50, + ) + + @pytest.mark.xfail(run=False, reason="some cases in hyp2f1 not fully accurate") + def test_chebyt(self): + assert_mpmath_equal( + sc.eval_chebyt, + lambda n, x: time_limited()( + exception_to_nan(mpmath.chebyt) + )(n, x, **HYPERKW), + [Arg(-101, 101), Arg()], + n=10000, + ) + + def test_chebyu_int(self): + assert_mpmath_equal( + lambda n, x: sc.eval_chebyu(int(n), x), + exception_to_nan(lambda n, x: mpmath.chebyu(n, x, **HYPERKW)), + [IntArg(), Arg()], + dps=50, + ) + + @pytest.mark.xfail(run=False, reason="some cases in hyp2f1 not fully accurate") + def test_chebyu(self): + assert_mpmath_equal( + sc.eval_chebyu, + lambda n, x: time_limited()( + exception_to_nan(mpmath.chebyu) + )(n, x, **HYPERKW), + [Arg(-101, 101), Arg()], + ) + + def test_chi(self): + def chi(x): + return sc.shichi(x)[1] + assert_mpmath_equal(chi, mpmath.chi, [Arg()]) + # check asymptotic series cross-over + assert_mpmath_equal(chi, mpmath.chi, [FixedArg([88 - 1e-9, 88, 88 + 1e-9])]) + + def test_chi_complex(self): + def chi(z): + return sc.shichi(z)[1] + # chi oscillates as Im[z] -> +- inf, so limit range + assert_mpmath_equal( + chi, + mpmath.chi, + [ComplexArg(complex(-np.inf, -1e8), complex(np.inf, 1e8))], + rtol=1e-12, + ) + + def test_ci(self): + def ci(x): + return sc.sici(x)[1] + # oscillating function: limit range + assert_mpmath_equal(ci, mpmath.ci, [Arg(-1e8, 1e8)]) + + def test_ci_complex(self): + def ci(z): + return sc.sici(z)[1] + # ci oscillates as Re[z] -> +- inf, so limit range + assert_mpmath_equal( + ci, + mpmath.ci, + [ComplexArg(complex(-1e8, -np.inf), complex(1e8, np.inf))], + rtol=1e-8, + ) + + def test_cospi(self): + eps = np.finfo(float).eps + assert_mpmath_equal(_cospi, mpmath.cospi, [Arg()], nan_ok=False, rtol=2*eps) + + def test_cospi_complex(self): + assert_mpmath_equal( + _cospi, + mpmath.cospi, + [ComplexArg()], + nan_ok=False, + rtol=1e-13, + ) + + def test_digamma(self): + assert_mpmath_equal( + sc.digamma, + exception_to_nan(mpmath.digamma), + [Arg()], + rtol=1e-12, + dps=50, + ) + + def test_digamma_complex(self): + # Test on a cut plane because mpmath will hang. See + # test_digamma_negreal for tests on the negative real axis. + def param_filter(z): + return np.where((z.real < 0) & (np.abs(z.imag) < 1.12), False, True) + + assert_mpmath_equal( + sc.digamma, + exception_to_nan(mpmath.digamma), + [ComplexArg()], + rtol=1e-13, + dps=40, + param_filter=param_filter + ) + + def test_e1(self): + assert_mpmath_equal( + sc.exp1, + mpmath.e1, + [Arg()], + rtol=1e-14, + ) + + def test_e1_complex(self): + # E_1 oscillates as Im[z] -> +- inf, so limit range + assert_mpmath_equal( + sc.exp1, + mpmath.e1, + [ComplexArg(complex(-np.inf, -1e8), complex(np.inf, 1e8))], + rtol=1e-11, + ) + + # Check cross-over region + assert_mpmath_equal( + sc.exp1, + mpmath.e1, + (np.linspace(-50, 50, 171)[:, None] + + np.r_[0, np.logspace(-3, 2, 61), -np.logspace(-3, 2, 11)]*1j).ravel(), + rtol=1e-11, + ) + assert_mpmath_equal( + sc.exp1, + mpmath.e1, + (np.linspace(-50, -35, 10000) + 0j), + rtol=1e-11, + ) + + def test_exprel(self): + assert_mpmath_equal( + sc.exprel, + lambda x: mpmath.expm1(x)/x if x != 0 else mpmath.mpf('1.0'), + [Arg(a=-np.log(np.finfo(np.float64).max), + b=np.log(np.finfo(np.float64).max))], + ) + assert_mpmath_equal( + sc.exprel, + lambda x: mpmath.expm1(x)/x if x != 0 else mpmath.mpf('1.0'), + np.array([1e-12, 1e-24, 0, 1e12, 1e24, np.inf]), + rtol=1e-11, + ) + assert_(np.isinf(sc.exprel(np.inf))) + assert_(sc.exprel(-np.inf) == 0) + + def test_expm1_complex(self): + # Oscillates as a function of Im[z], so limit range to avoid loss of precision + assert_mpmath_equal( + sc.expm1, + mpmath.expm1, + [ComplexArg(complex(-np.inf, -1e7), complex(np.inf, 1e7))], + ) + + def test_log1p_complex(self): + assert_mpmath_equal( + sc.log1p, + lambda x: mpmath.log(x+1), + [ComplexArg()], + dps=60, + ) + + def test_log1pmx(self): + assert_mpmath_equal( + _log1pmx, + lambda x: mpmath.log(x + 1) - x, + [Arg()], + dps=60, + rtol=1e-14, + ) + + def test_ei(self): + assert_mpmath_equal(sc.expi, mpmath.ei, [Arg()], rtol=1e-11) + + def test_ei_complex(self): + # Ei oscillates as Im[z] -> +- inf, so limit range + assert_mpmath_equal( + sc.expi, + mpmath.ei, + [ComplexArg(complex(-np.inf, -1e8), complex(np.inf, 1e8))], + rtol=1e-9, + ) + + def test_ellipe(self): + assert_mpmath_equal(sc.ellipe, mpmath.ellipe, [Arg(b=1.0)]) + + def test_ellipeinc(self): + assert_mpmath_equal(sc.ellipeinc, mpmath.ellipe, [Arg(-1e3, 1e3), Arg(b=1.0)]) + + def test_ellipeinc_largephi(self): + assert_mpmath_equal(sc.ellipeinc, mpmath.ellipe, [Arg(), Arg()]) + + def test_ellipf(self): + assert_mpmath_equal(sc.ellipkinc, mpmath.ellipf, [Arg(-1e3, 1e3), Arg()]) + + def test_ellipf_largephi(self): + assert_mpmath_equal(sc.ellipkinc, mpmath.ellipf, [Arg(), Arg()]) + + def test_ellipk(self): + assert_mpmath_equal(sc.ellipk, mpmath.ellipk, [Arg(b=1.0)]) + assert_mpmath_equal( + sc.ellipkm1, + lambda m: mpmath.ellipk(1 - m), + [Arg(a=0.0)], + dps=400, + ) + + def test_ellipkinc(self): + def ellipkinc(phi, m): + return mpmath.ellippi(0, phi, m) + assert_mpmath_equal( + sc.ellipkinc, + ellipkinc, + [Arg(-1e3, 1e3), Arg(b=1.0)], + ignore_inf_sign=True, + ) + + def test_ellipkinc_largephi(self): + def ellipkinc(phi, m): + return mpmath.ellippi(0, phi, m) + assert_mpmath_equal( + sc.ellipkinc, + ellipkinc, + [Arg(), Arg(b=1.0)], + ignore_inf_sign=True, + ) + + def test_ellipfun_sn(self): + def sn(u, m): + # mpmath doesn't get the zero at u = 0--fix that + if u == 0: + return 0 + else: + return mpmath.ellipfun("sn", u=u, m=m) + + # Oscillating function --- limit range of first argument; the + # loss of precision there is an expected numerical feature + # rather than an actual bug + assert_mpmath_equal( + lambda u, m: sc.ellipj(u, m)[0], + sn, + [Arg(-1e6, 1e6), Arg(a=0, b=1)], + rtol=1e-8, + ) + + def test_ellipfun_cn(self): + # see comment in ellipfun_sn + assert_mpmath_equal( + lambda u, m: sc.ellipj(u, m)[1], + lambda u, m: mpmath.ellipfun("cn", u=u, m=m), + [Arg(-1e6, 1e6), Arg(a=0, b=1)], + rtol=1e-8, + ) + + def test_ellipfun_dn(self): + # see comment in ellipfun_sn + assert_mpmath_equal( + lambda u, m: sc.ellipj(u, m)[2], + lambda u, m: mpmath.ellipfun("dn", u=u, m=m), + [Arg(-1e6, 1e6), Arg(a=0, b=1)], + rtol=1e-8, + ) + + def test_erf(self): + assert_mpmath_equal(sc.erf, lambda z: mpmath.erf(z), [Arg()]) + + def test_erf_complex(self): + assert_mpmath_equal(sc.erf, lambda z: mpmath.erf(z), [ComplexArg()], n=200) + + def test_erfc(self): + assert_mpmath_equal( + sc.erfc, + exception_to_nan(lambda z: mpmath.erfc(z)), + [Arg()], + rtol=1e-13, + ) + + def test_erfc_complex(self): + assert_mpmath_equal( + sc.erfc, + exception_to_nan(lambda z: mpmath.erfc(z)), + [ComplexArg()], + n=200, + ) + + def test_erfi(self): + assert_mpmath_equal(sc.erfi, mpmath.erfi, [Arg()], n=200) + + def test_erfi_complex(self): + assert_mpmath_equal(sc.erfi, mpmath.erfi, [ComplexArg()], n=200) + + def test_ndtr(self): + assert_mpmath_equal( + sc.ndtr, + exception_to_nan(lambda z: mpmath.ncdf(z)), + [Arg()], + n=200, + ) + + def test_ndtr_complex(self): + assert_mpmath_equal( + sc.ndtr, + lambda z: mpmath.erfc(-z/np.sqrt(2.))/2., + [ComplexArg(a=complex(-10000, -10000), b=complex(10000, 10000))], + n=400, + ) + + def test_log_ndtr(self): + assert_mpmath_equal( + sc.log_ndtr, + exception_to_nan(lambda z: mpmath.log(mpmath.ncdf(z))), + [Arg()], n=600, dps=300, rtol=1e-13, + ) + + def test_log_ndtr_complex(self): + assert_mpmath_equal( + sc.log_ndtr, + exception_to_nan(lambda z: mpmath.log(mpmath.erfc(-z/np.sqrt(2.))/2.)), + [ComplexArg(a=complex(-10000, -100), b=complex(10000, 100))], + n=200, dps=300, + ) + + def test_eulernum(self): + assert_mpmath_equal( + lambda n: sc.euler(n)[-1], + mpmath.eulernum, + [IntArg(1, 10000)], + n=10000, + ) + + def test_expint(self): + assert_mpmath_equal( + sc.expn, + mpmath.expint, + [IntArg(0, 200), Arg(0, np.inf)], + rtol=1e-13, + dps=160, + ) + + def test_fresnels(self): + def fresnels(x): + return sc.fresnel(x)[0] + assert_mpmath_equal(fresnels, mpmath.fresnels, [Arg()]) + + def test_fresnelc(self): + def fresnelc(x): + return sc.fresnel(x)[1] + assert_mpmath_equal(fresnelc, mpmath.fresnelc, [Arg()]) + + def test_gamma(self): + assert_mpmath_equal(sc.gamma, exception_to_nan(mpmath.gamma), [Arg()]) + + def test_gamma_complex(self): + assert_mpmath_equal( + sc.gamma, + exception_to_nan(mpmath.gamma), + [ComplexArg()], + rtol=5e-13, + ) + + def test_gammainc(self): + # Larger arguments are tested in test_data.py:test_local + assert_mpmath_equal( + sc.gammainc, + lambda z, b: mpmath.gammainc(z, b=b, regularized=True), + [Arg(0, 1e4, inclusive_a=False), Arg(0, 1e4)], + nan_ok=False, + rtol=1e-11, + ) + + def test_gammaincc(self): + # Larger arguments are tested in test_data.py:test_local + assert_mpmath_equal( + sc.gammaincc, + lambda z, a: mpmath.gammainc(z, a=a, regularized=True), + [Arg(0, 1e4, inclusive_a=False), Arg(0, 1e4)], + nan_ok=False, + rtol=1e-11, + ) + + def test_gammaln(self): + # The real part of loggamma is log(|gamma(z)|). + def f(z): + return mpmath.loggamma(z).real + + assert_mpmath_equal(sc.gammaln, exception_to_nan(f), [Arg()]) + + @pytest.mark.xfail(run=False) + def test_gegenbauer(self): + assert_mpmath_equal( + sc.eval_gegenbauer, + exception_to_nan(mpmath.gegenbauer), + [Arg(-1e3, 1e3), Arg(), Arg()], + ) + + def test_gegenbauer_int(self): + # Redefine functions to deal with numerical + mpmath issues + def gegenbauer(n, a, x): + # Avoid overflow at large `a` (mpmath would need an even larger + # dps to handle this correctly, so just skip this region) + if abs(a) > 1e100: + return np.nan + + # Deal with n=0, n=1 correctly; mpmath 0.17 doesn't do these + # always correctly + if n == 0: + r = 0.0 if a == 0.0 else 1.0 + elif n == 1: + r = 2*a*x + else: + r = mpmath.gegenbauer(n, a, x) + + # Mpmath 0.17 gives wrong results (spurious zero) in some cases, so + # compute the value by perturbing the result + if float(r) == 0 and a < -1 and float(a) == int(float(a)): + r = mpmath.gegenbauer(n, a + mpmath.mpf('1e-50'), x) + if abs(r) < mpmath.mpf('1e-50'): + r = mpmath.mpf('0.0') + + # Differing overflow thresholds in scipy vs. mpmath + if abs(r) > 1e270: + return np.inf + return r + + def sc_gegenbauer(n, a, x): + r = sc.eval_gegenbauer(int(n), a, x) + # Differing overflow thresholds in scipy vs. mpmath + if abs(r) > 1e270: + return np.inf + return r + assert_mpmath_equal( + sc_gegenbauer, + exception_to_nan(gegenbauer), + [IntArg(0, 100), Arg(-1e9, 1e9), Arg()], + dps=100, ignore_inf_sign=True, rtol=1e-6, + ) + + # Check the small-x expansion + assert_mpmath_equal( + sc_gegenbauer, + exception_to_nan(gegenbauer), + [IntArg(0, 100), Arg(), FixedArg(np.logspace(-30, -4, 30))], + dps=100, ignore_inf_sign=True, + ) + + @pytest.mark.xfail(run=False) + def test_gegenbauer_complex(self): + assert_mpmath_equal( + lambda n, a, x: sc.eval_gegenbauer(int(n), a.real, x), + exception_to_nan(mpmath.gegenbauer), + [IntArg(0, 100), Arg(), ComplexArg()], + ) + + @nonfunctional_tooslow + def test_gegenbauer_complex_general(self): + assert_mpmath_equal( + lambda n, a, x: sc.eval_gegenbauer(n.real, a.real, x), + exception_to_nan(mpmath.gegenbauer), + [Arg(-1e3, 1e3), Arg(), ComplexArg()], + ) + + def test_hankel1(self): + assert_mpmath_equal( + sc.hankel1, + exception_to_nan(lambda v, x: mpmath.hankel1(v, x, **HYPERKW)), + [Arg(-1e20, 1e20), Arg()], + ) + + def test_hankel2(self): + assert_mpmath_equal( + sc.hankel2, + exception_to_nan(lambda v, x: mpmath.hankel2(v, x, **HYPERKW)), + [Arg(-1e20, 1e20), Arg()], + ) + + @pytest.mark.xfail(run=False, reason="issues at intermediately large orders") + def test_hermite(self): + assert_mpmath_equal( + lambda n, x: sc.eval_hermite(int(n), x), + exception_to_nan(mpmath.hermite), + [IntArg(0, 10000), Arg()], + ) + + # hurwitz: same as zeta + + def test_hyp0f1(self): + # mpmath reports no convergence unless maxterms is large enough + KW = dict(maxprec=400, maxterms=1500) + # n=500 (non-xslow default) fails for one bad point + assert_mpmath_equal( + sc.hyp0f1, + lambda a, x: mpmath.hyp0f1(a, x, **KW), + [Arg(-1e7, 1e7), Arg(0, 1e5)], + n=5000, + ) + # NB: The range of the second parameter ("z") is limited from below + # because of an overflow in the intermediate calculations. The way + # for fix it is to implement an asymptotic expansion for Bessel J + # (similar to what is implemented for Bessel I here). + + def test_hyp0f1_complex(self): + assert_mpmath_equal( + lambda a, z: sc.hyp0f1(a.real, z), + exception_to_nan(lambda a, x: mpmath.hyp0f1(a, x, **HYPERKW)), + [Arg(-10, 10), ComplexArg(complex(-120, -120), complex(120, 120))], + ) + # NB: The range of the first parameter ("v") are limited by an overflow + # in the intermediate calculations. Can be fixed by implementing an + # asymptotic expansion for Bessel functions for large order. + + def test_hyp1f1(self): + def mpmath_hyp1f1(a, b, x): + try: + return mpmath.hyp1f1(a, b, x) + except ZeroDivisionError: + return np.inf + + assert_mpmath_equal( + sc.hyp1f1, + mpmath_hyp1f1, + [Arg(-50, 50), Arg(1, 50, inclusive_a=False), Arg(-50, 50)], + n=500, + nan_ok=False, + ) + + @pytest.mark.xfail(run=False) + def test_hyp1f1_complex(self): + assert_mpmath_equal( + inf_to_nan(lambda a, b, x: sc.hyp1f1(a.real, b.real, x)), + exception_to_nan(lambda a, b, x: mpmath.hyp1f1(a, b, x, **HYPERKW)), + [Arg(-1e3, 1e3), Arg(-1e3, 1e3), ComplexArg()], + n=2000, + ) + + @nonfunctional_tooslow + def test_hyp2f1_complex(self): + # SciPy's hyp2f1 seems to have performance and accuracy problems + assert_mpmath_equal( + lambda a, b, c, x: sc.hyp2f1(a.real, b.real, c.real, x), + exception_to_nan(lambda a, b, c, x: mpmath.hyp2f1(a, b, c, x, **HYPERKW)), + [Arg(-1e2, 1e2), Arg(-1e2, 1e2), Arg(-1e2, 1e2), ComplexArg()], + n=10, + ) + + @pytest.mark.xfail(run=False) + def test_hyperu(self): + assert_mpmath_equal( + sc.hyperu, + exception_to_nan(lambda a, b, x: mpmath.hyperu(a, b, x, **HYPERKW)), + [Arg(), Arg(), Arg()], + ) + + @pytest.mark.xfail_on_32bit("mpmath issue gh-342: " + "unsupported operand mpz, long for pow") + def test_igam_fac(self): + def mp_igam_fac(a, x): + return mpmath.power(x, a)*mpmath.exp(-x)/mpmath.gamma(a) + + assert_mpmath_equal( + _igam_fac, + mp_igam_fac, + [Arg(0, 1e14, inclusive_a=False), Arg(0, 1e14)], + rtol=1e-10, + dps=29, + ) + + def test_j0(self): + # The Bessel function at large arguments is j0(x) ~ cos(x + phi)/sqrt(x) + # and at large arguments the phase of the cosine loses precision. + # + # This is numerically expected behavior, so we compare only up to + # 1e8 = 1e15 * 1e-7 + assert_mpmath_equal(sc.j0, mpmath.j0, [Arg(-1e3, 1e3)]) + assert_mpmath_equal(sc.j0, mpmath.j0, [Arg(-1e8, 1e8)], rtol=1e-5) + + def test_j1(self): + # See comment in test_j0 + assert_mpmath_equal(sc.j1, mpmath.j1, [Arg(-1e3, 1e3)]) + assert_mpmath_equal(sc.j1, mpmath.j1, [Arg(-1e8, 1e8)], rtol=1e-5) + + @pytest.mark.xfail(run=False) + def test_jacobi(self): + assert_mpmath_equal( + sc.eval_jacobi, + exception_to_nan(lambda a, b, c, x: mpmath.jacobi(a, b, c, x, **HYPERKW)), + [Arg(), Arg(), Arg(), Arg()], + ) + assert_mpmath_equal( + lambda n, b, c, x: sc.eval_jacobi(int(n), b, c, x), + exception_to_nan(lambda a, b, c, x: mpmath.jacobi(a, b, c, x, **HYPERKW)), + [IntArg(), Arg(), Arg(), Arg()], + ) + + def test_jacobi_int(self): + # Redefine functions to deal with numerical + mpmath issues + def jacobi(n, a, b, x): + # Mpmath does not handle n=0 case always correctly + if n == 0: + return 1.0 + return mpmath.jacobi(n, a, b, x) + assert_mpmath_equal( + lambda n, a, b, x: sc.eval_jacobi(int(n), a, b, x), + lambda n, a, b, x: exception_to_nan(jacobi)(n, a, b, x, **HYPERKW), + [IntArg(), Arg(), Arg(), Arg()], + n=4095, + dps=50, + ) + + def test_kei(self): + def kei(x): + if x == 0: + # work around mpmath issue at x=0 + return -pi/4 + return exception_to_nan(mpmath.kei)(0, x, **HYPERKW) + assert_mpmath_equal(sc.kei, kei, [Arg(-1e30, 1e30)], n=1000) + + def test_ker(self): + assert_mpmath_equal( + sc.ker, + exception_to_nan(lambda x: mpmath.ker(0, x, **HYPERKW)), + [Arg(-1e30, 1e30)], + n=1000, + ) + + @nonfunctional_tooslow + def test_laguerre(self): + assert_mpmath_equal( + trace_args(sc.eval_laguerre), + lambda n, x: exception_to_nan(mpmath.laguerre)(n, x, **HYPERKW), + [Arg(), Arg()], + ) + + def test_laguerre_int(self): + assert_mpmath_equal( + lambda n, x: sc.eval_laguerre(int(n), x), + lambda n, x: exception_to_nan(mpmath.laguerre)(n, x, **HYPERKW), + [IntArg(), Arg()], + n=20000, + ) + + @pytest.mark.xfail_on_32bit("see gh-3551 for bad points") + def test_lambertw_real(self): + assert_mpmath_equal( + lambda x, k: sc.lambertw(x, int(k.real)), + lambda x, k: mpmath.lambertw(x, int(k.real)), + [ComplexArg(-np.inf, np.inf), IntArg(0, 10)], + rtol=1e-13, nan_ok=False, + ) + + def test_lanczos_sum_expg_scaled(self): + maxgamma = 171.624376956302725 + e = np.exp(1) + g = 6.024680040776729583740234375 + + def gamma(x): + with np.errstate(over='ignore'): + fac = ((x + g - 0.5)/e)**(x - 0.5) + if fac != np.inf: + res = fac*_lanczos_sum_expg_scaled(x) + else: + fac = ((x + g - 0.5)/e)**(0.5*(x - 0.5)) + res = fac*_lanczos_sum_expg_scaled(x) + res *= fac + return res + + assert_mpmath_equal( + gamma, + mpmath.gamma, + [Arg(0, maxgamma, inclusive_a=False)], + rtol=1e-13, + ) + + @nonfunctional_tooslow + def test_legendre(self): + assert_mpmath_equal(sc.eval_legendre, mpmath.legendre, [Arg(), Arg()]) + + def test_legendre_int(self): + assert_mpmath_equal( + lambda n, x: sc.eval_legendre(int(n), x), + lambda n, x: exception_to_nan(mpmath.legendre)(n, x, **HYPERKW), + [IntArg(), Arg()], + n=20000, + ) + + # Check the small-x expansion + assert_mpmath_equal( + lambda n, x: sc.eval_legendre(int(n), x), + lambda n, x: exception_to_nan(mpmath.legendre)(n, x, **HYPERKW), + [IntArg(), FixedArg(np.logspace(-30, -4, 20))], + ) + + @pytest.mark.xfail(run=False, reason="apparently picks wrong function at |z| > 1") + def test_legenq(self): + def lqnm(n, m, z): + return sc.lqmn(m, n, z)[0][-1,-1] + + def legenq(n, m, z): + if abs(z) < 1e-15: + # mpmath has bad performance here + return np.nan + return exception_to_nan(mpmath.legenq)(n, m, z, type=2) + + assert_mpmath_equal( + lqnm, + legenq, + [IntArg(0, 100), IntArg(0, 100), Arg()], + ) + + @nonfunctional_tooslow + def test_legenq_complex(self): + def lqnm(n, m, z): + return sc.lqmn(int(m.real), int(n.real), z)[0][-1,-1] + + def legenq(n, m, z): + if abs(z) < 1e-15: + # mpmath has bad performance here + return np.nan + return exception_to_nan(mpmath.legenq)(int(n.real), int(m.real), z, type=2) + + assert_mpmath_equal( + lqnm, + legenq, + [IntArg(0, 100), IntArg(0, 100), ComplexArg()], + n=100, + ) + + def test_lgam1p(self): + def param_filter(x): + # Filter the poles + return np.where((np.floor(x) == x) & (x <= 0), False, True) + + def mp_lgam1p(z): + # The real part of loggamma is log(|gamma(z)|) + return mpmath.loggamma(1 + z).real + + assert_mpmath_equal( + _lgam1p, + mp_lgam1p, + [Arg()], + rtol=1e-13, + dps=100, + param_filter=param_filter, + ) + + def test_loggamma(self): + def mpmath_loggamma(z): + try: + res = mpmath.loggamma(z) + except ValueError: + res = complex(np.nan, np.nan) + return res + + assert_mpmath_equal( + sc.loggamma, + mpmath_loggamma, + [ComplexArg()], + nan_ok=False, + distinguish_nan_and_inf=False, + rtol=5e-14, + ) + + @pytest.mark.xfail(run=False) + def test_pcfd(self): + def pcfd(v, x): + return sc.pbdv(v, x)[0] + assert_mpmath_equal( + pcfd, + exception_to_nan(lambda v, x: mpmath.pcfd(v, x, **HYPERKW)), + [Arg(), Arg()], + ) + + @pytest.mark.xfail(run=False, reason="it's not the same as the mpmath function --- " + "maybe different definition?") + def test_pcfv(self): + def pcfv(v, x): + return sc.pbvv(v, x)[0] + assert_mpmath_equal( + pcfv, + lambda v, x: time_limited()(exception_to_nan(mpmath.pcfv))(v, x, **HYPERKW), + [Arg(), Arg()], + n=1000, + ) + + def test_pcfw(self): + def pcfw(a, x): + return sc.pbwa(a, x)[0] + + def dpcfw(a, x): + return sc.pbwa(a, x)[1] + + def mpmath_dpcfw(a, x): + return mpmath.diff(mpmath.pcfw, (a, x), (0, 1)) + + # The Zhang and Jin implementation only uses Taylor series and + # is thus accurate in only a very small range. + assert_mpmath_equal( + pcfw, + mpmath.pcfw, + [Arg(-5, 5), Arg(-5, 5)], + rtol=2e-8, + n=100, + ) + + assert_mpmath_equal( + dpcfw, + mpmath_dpcfw, + [Arg(-5, 5), Arg(-5, 5)], + rtol=2e-9, + n=100, + ) + + @pytest.mark.xfail(run=False, + reason="issues at large arguments (atol OK, rtol not) " + "and = version.Version("1.0.0"): + # no workarounds needed + mppoch = mpmath.rf + else: + def mppoch(a, m): + # deal with cases where the result in double precision + # hits exactly a non-positive integer, but the + # corresponding extended-precision mpf floats don't + if float(a + m) == int(a + m) and float(a + m) <= 0: + a = mpmath.mpf(a) + m = int(a + m) - a + return mpmath.rf(a, m) + + assert_mpmath_equal(sc.poch, mppoch, [Arg(), Arg()], dps=400) + + def test_sinpi(self): + eps = np.finfo(float).eps + assert_mpmath_equal( + _sinpi, + mpmath.sinpi, + [Arg()], + nan_ok=False, + rtol=2*eps, + ) + + def test_sinpi_complex(self): + assert_mpmath_equal( + _sinpi, + mpmath.sinpi, + [ComplexArg()], + nan_ok=False, + rtol=2e-14, + ) + + def test_shi(self): + def shi(x): + return sc.shichi(x)[0] + assert_mpmath_equal(shi, mpmath.shi, [Arg()]) + # check asymptotic series cross-over + assert_mpmath_equal(shi, mpmath.shi, [FixedArg([88 - 1e-9, 88, 88 + 1e-9])]) + + def test_shi_complex(self): + def shi(z): + return sc.shichi(z)[0] + # shi oscillates as Im[z] -> +- inf, so limit range + assert_mpmath_equal( + shi, + mpmath.shi, + [ComplexArg(complex(-np.inf, -1e8), complex(np.inf, 1e8))], + rtol=1e-12, + ) + + def test_si(self): + def si(x): + return sc.sici(x)[0] + assert_mpmath_equal(si, mpmath.si, [Arg()]) + + def test_si_complex(self): + def si(z): + return sc.sici(z)[0] + # si oscillates as Re[z] -> +- inf, so limit range + assert_mpmath_equal( + si, + mpmath.si, + [ComplexArg(complex(-1e8, -np.inf), complex(1e8, np.inf))], + rtol=1e-12, + ) + + def test_spence(self): + # mpmath uses a different convention for the dilogarithm + def dilog(x): + return mpmath.polylog(2, 1 - x) + # Spence has a branch cut on the negative real axis + assert_mpmath_equal( + sc.spence, + exception_to_nan(dilog), + [Arg(0, np.inf)], + rtol=1e-14, + ) + + def test_spence_complex(self): + def dilog(z): + return mpmath.polylog(2, 1 - z) + assert_mpmath_equal( + sc.spence, + exception_to_nan(dilog), + [ComplexArg()], + rtol=1e-14, + ) + + def test_struveh(self): + assert_mpmath_equal( + sc.struve, + exception_to_nan(mpmath.struveh), + [Arg(-1e4, 1e4), Arg(0, 1e4)], + rtol=5e-10, + ) + + def test_struvel(self): + def mp_struvel(v, z): + if v < 0 and z < -v and abs(v) > 1000: + # larger DPS needed for correct results + old_dps = mpmath.mp.dps + try: + mpmath.mp.dps = 500 + return mpmath.struvel(v, z) + finally: + mpmath.mp.dps = old_dps + return mpmath.struvel(v, z) + + assert_mpmath_equal( + sc.modstruve, + exception_to_nan(mp_struvel), + [Arg(-1e4, 1e4), Arg(0, 1e4)], + rtol=5e-10, + ignore_inf_sign=True, + ) + + def test_wrightomega_real(self): + def mpmath_wrightomega_real(x): + return mpmath.lambertw(mpmath.exp(x), mpmath.mpf('-0.5')) + + # For x < -1000 the Wright Omega function is just 0 to double + # precision, and for x > 1e21 it is just x to double + # precision. + assert_mpmath_equal( + sc.wrightomega, + mpmath_wrightomega_real, + [Arg(-1000, 1e21)], + rtol=5e-15, + atol=0, + nan_ok=False, + ) + + def test_wrightomega(self): + assert_mpmath_equal( + sc.wrightomega, + lambda z: _mpmath_wrightomega(z, 25), + [ComplexArg()], + rtol=1e-14, + nan_ok=False, + ) + + def test_hurwitz_zeta(self): + assert_mpmath_equal( + sc.zeta, + exception_to_nan(mpmath.zeta), + [Arg(a=1, b=1e10, inclusive_a=False), Arg(a=0, inclusive_a=False)], + ) + + def test_riemann_zeta(self): + assert_mpmath_equal( + sc.zeta, + lambda x: mpmath.zeta(x) if x != 1 else mpmath.inf, + [Arg(-100, 100)], + nan_ok=False, + rtol=5e-13, + ) + + def test_zetac(self): + assert_mpmath_equal( + sc.zetac, + lambda x: mpmath.zeta(x) - 1 if x != 1 else mpmath.inf, + [Arg(-100, 100)], + nan_ok=False, + dps=45, + rtol=5e-13, + ) + + def test_boxcox(self): + + def mp_boxcox(x, lmbda): + x = mpmath.mp.mpf(x) + lmbda = mpmath.mp.mpf(lmbda) + if lmbda == 0: + return mpmath.mp.log(x) + else: + return mpmath.mp.powm1(x, lmbda) / lmbda + + assert_mpmath_equal( + sc.boxcox, + exception_to_nan(mp_boxcox), + [Arg(a=0, inclusive_a=False), Arg()], + n=200, + dps=60, + rtol=1e-13, + ) + + def test_boxcox1p(self): + + def mp_boxcox1p(x, lmbda): + x = mpmath.mp.mpf(x) + lmbda = mpmath.mp.mpf(lmbda) + one = mpmath.mp.mpf(1) + if lmbda == 0: + return mpmath.mp.log(one + x) + else: + return mpmath.mp.powm1(one + x, lmbda) / lmbda + + assert_mpmath_equal( + sc.boxcox1p, + exception_to_nan(mp_boxcox1p), + [Arg(a=-1, inclusive_a=False), Arg()], + n=200, + dps=60, + rtol=1e-13, + ) + + def test_spherical_jn(self): + def mp_spherical_jn(n, z): + arg = mpmath.mpmathify(z) + out = (mpmath.besselj(n + mpmath.mpf(1)/2, arg) / + mpmath.sqrt(2*arg/mpmath.pi)) + if arg.imag == 0: + return out.real + else: + return out + + assert_mpmath_equal( + lambda n, z: sc.spherical_jn(int(n), z), + exception_to_nan(mp_spherical_jn), + [IntArg(0, 200), Arg(-1e8, 1e8)], + dps=300, + # underflow of `spherical_jn` is a bit premature; see gh-21629 + param_filter=(None, lambda z: np.abs(z) > 1e-20), + ) + + def test_spherical_jn_complex(self): + def mp_spherical_jn(n, z): + arg = mpmath.mpmathify(z) + out = (mpmath.besselj(n + mpmath.mpf(1)/2, arg) / + mpmath.sqrt(2*arg/mpmath.pi)) + if arg.imag == 0: + return out.real + else: + return out + + assert_mpmath_equal( + lambda n, z: sc.spherical_jn(int(n.real), z), + exception_to_nan(mp_spherical_jn), + [IntArg(0, 200), ComplexArg()] + ) + + def test_spherical_yn(self): + def mp_spherical_yn(n, z): + arg = mpmath.mpmathify(z) + out = (mpmath.bessely(n + mpmath.mpf(1)/2, arg) / + mpmath.sqrt(2*arg/mpmath.pi)) + if arg.imag == 0: + return out.real + else: + return out + + assert_mpmath_equal( + lambda n, z: sc.spherical_yn(int(n), z), + exception_to_nan(mp_spherical_yn), + [IntArg(0, 200), Arg(-1e10, 1e10)], + dps=100, + ) + + def test_spherical_yn_complex(self): + def mp_spherical_yn(n, z): + arg = mpmath.mpmathify(z) + out = (mpmath.bessely(n + mpmath.mpf(1)/2, arg) / + mpmath.sqrt(2*arg/mpmath.pi)) + if arg.imag == 0: + return out.real + else: + return out + + assert_mpmath_equal( + lambda n, z: sc.spherical_yn(int(n.real), z), + exception_to_nan(mp_spherical_yn), + [IntArg(0, 200), ComplexArg()], + ) + + def test_spherical_in(self): + def mp_spherical_in(n, z): + arg = mpmath.mpmathify(z) + out = (mpmath.besseli(n + mpmath.mpf(1)/2, arg) / + mpmath.sqrt(2*arg/mpmath.pi)) + if arg.imag == 0: + return out.real + else: + return out + + assert_mpmath_equal( + lambda n, z: sc.spherical_in(int(n), z), + exception_to_nan(mp_spherical_in), + [IntArg(0, 200), Arg()], + dps=200, + atol=10**(-278), + ) + + def test_spherical_in_complex(self): + def mp_spherical_in(n, z): + arg = mpmath.mpmathify(z) + out = (mpmath.besseli(n + mpmath.mpf(1)/2, arg) / + mpmath.sqrt(2*arg/mpmath.pi)) + if arg.imag == 0: + return out.real + else: + return out + + assert_mpmath_equal( + lambda n, z: sc.spherical_in(int(n.real), z), + exception_to_nan(mp_spherical_in), + [IntArg(0, 200), ComplexArg()], + ) + + def test_spherical_kn(self): + def mp_spherical_kn(n, z): + arg = mpmath.mpmathify(z) + out = (mpmath.besselk(n + mpmath.mpf(1)/2, arg) / + mpmath.sqrt(2*arg/mpmath.pi)) + if mpmath.mpmathify(z).imag == 0: + return out.real + else: + return out + + assert_mpmath_equal( + lambda n, z: sc.spherical_kn(int(n), z), + exception_to_nan(mp_spherical_kn), + [IntArg(0, 150), Arg()], + dps=100, + ) + + @pytest.mark.xfail(run=False, + reason="Accuracy issues near z = -1 inherited from kv.") + def test_spherical_kn_complex(self): + def mp_spherical_kn(n, z): + arg = mpmath.mpmathify(z) + out = (mpmath.besselk(n + mpmath.mpf(1)/2, arg) / + mpmath.sqrt(2*arg/mpmath.pi)) + if arg.imag == 0: + return out.real + else: + return out + + assert_mpmath_equal( + lambda n, z: sc.spherical_kn(int(n.real), z), + exception_to_nan(mp_spherical_kn), + [IntArg(0, 200), ComplexArg()], + dps=200, + ) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_nan_inputs.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_nan_inputs.py new file mode 100644 index 0000000000000000000000000000000000000000..76af3032b62484b630713631e23d95110161b759 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_nan_inputs.py @@ -0,0 +1,72 @@ +"""Test how the ufuncs in special handle nan inputs. + +""" +import warnings + +from collections.abc import Callable + +import numpy as np +from numpy.testing import assert_array_equal, assert_ +import pytest +import scipy.special as sc + + +KNOWNFAILURES: dict[str, Callable] = {} + +POSTPROCESSING: dict[str, Callable] = {} + + +def _get_ufuncs(): + ufuncs = [] + ufunc_names = [] + for name in sorted(sc.__dict__): + obj = sc.__dict__[name] + if not isinstance(obj, np.ufunc): + continue + msg = KNOWNFAILURES.get(obj) + if msg is None: + ufuncs.append(obj) + ufunc_names.append(name) + else: + fail = pytest.mark.xfail(run=False, reason=msg) + ufuncs.append(pytest.param(obj, marks=fail)) + ufunc_names.append(name) + return ufuncs, ufunc_names + + +UFUNCS, UFUNC_NAMES = _get_ufuncs() + + +@pytest.mark.parametrize("func", UFUNCS, ids=UFUNC_NAMES) +def test_nan_inputs(func): + args = (np.nan,)*func.nin + with warnings.catch_warnings(): + # Ignore warnings about unsafe casts from legacy wrappers + warnings.filterwarnings( + "ignore", + "floating point number truncated to an integer", + RuntimeWarning + ) + try: + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + res = func(*args) + except TypeError: + # One of the arguments doesn't take real inputs + return + if func in POSTPROCESSING: + res = POSTPROCESSING[func](*res) + + msg = f"got {res} instead of nan" + assert_array_equal(np.isnan(res), True, err_msg=msg) + + +def test_legacy_cast(): + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", + "floating point number truncated to an integer", + RuntimeWarning + ) + res = sc.bdtrc(np.nan, 1, 0.5) + assert_(np.isnan(res)) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_ndtr.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_ndtr.py new file mode 100644 index 0000000000000000000000000000000000000000..ba9b689b34384585cc65204000febcb99c910d55 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_ndtr.py @@ -0,0 +1,77 @@ +import numpy as np +from numpy.testing import assert_equal, assert_allclose +import scipy.special as sc + + +def test_ndtr(): + assert_equal(sc.ndtr(0), 0.5) + assert_allclose(sc.ndtr(1), 0.8413447460685429) + + +class TestNdtri: + + def test_zero(self): + assert sc.ndtri(0.5) == 0.0 + + def test_asymptotes(self): + assert_equal(sc.ndtri([0.0, 1.0]), [-np.inf, np.inf]) + + def test_outside_of_domain(self): + assert all(np.isnan(sc.ndtri([-1.5, 1.5]))) + + +class TestLogNdtr: + + # The expected values in these tests were computed with mpmath: + # + # def log_ndtr_mp(x): + # return mpmath.log(mpmath.ncdf(x)) + # + + def test_log_ndtr_moderate_le8(self): + x = np.array([-0.75, -0.25, 0, 0.5, 1.5, 2.5, 3, 4, 5, 7, 8]) + expected = np.array([-1.4844482299196562, + -0.9130617648111351, + -0.6931471805599453, + -0.3689464152886564, + -0.06914345561223398, + -0.006229025485860002, + -0.0013508099647481938, + -3.167174337748927e-05, + -2.866516129637636e-07, + -1.279812543886654e-12, + -6.220960574271786e-16]) + y = sc.log_ndtr(x) + assert_allclose(y, expected, rtol=1e-14) + + def test_log_ndtr_values_8_16(self): + x = np.array([8.001, 8.06, 8.15, 8.5, 10, 12, 14, 16]) + expected = [-6.170639424817055e-16, + -3.814722443652823e-16, + -1.819621363526629e-16, + -9.479534822203318e-18, + -7.619853024160525e-24, + -1.776482112077679e-33, + -7.7935368191928e-45, + -6.388754400538087e-58] + y = sc.log_ndtr(x) + assert_allclose(y, expected, rtol=5e-14) + + def test_log_ndtr_values_16_31(self): + x = np.array([16.15, 20.3, 21.4, 26.2, 30.9]) + expected = [-5.678084565148492e-59, + -6.429244467698346e-92, + -6.680402412553295e-102, + -1.328698078458869e-151, + -5.972288641838264e-210] + y = sc.log_ndtr(x) + assert_allclose(y, expected, rtol=2e-13) + + def test_log_ndtr_values_gt31(self): + x = np.array([31.6, 32.8, 34.9, 37.1]) + expected = [-1.846036234858162e-219, + -2.9440539964066835e-236, + -3.71721649450857e-267, + -1.4047119663106221e-301] + y = sc.log_ndtr(x) + assert_allclose(y, expected, rtol=3e-13) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_ndtri_exp.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_ndtri_exp.py new file mode 100644 index 0000000000000000000000000000000000000000..82a9fbd3bcda117770e00018facda3f56630a6bc --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_ndtri_exp.py @@ -0,0 +1,94 @@ +import pytest +import numpy as np +from numpy.testing import assert_equal, assert_allclose +from scipy.special import log_ndtr, ndtri_exp +from scipy.special._testutils import assert_func_equal + + +def log_ndtr_ndtri_exp(y): + return log_ndtr(ndtri_exp(y)) + + +@pytest.fixture(scope="class") +def uniform_random_points(): + random_state = np.random.RandomState(1234) + points = random_state.random_sample(1000) + return points + + +class TestNdtriExp: + """Tests that ndtri_exp is sufficiently close to an inverse of log_ndtr. + + We have separate tests for the five intervals (-inf, -10), + [-10, -2), [-2, -0.14542), [-0.14542, -1e-6), and [-1e-6, 0). + ndtri_exp(y) is computed in three different ways depending on if y + is in (-inf, -2), [-2, log(1 - exp(-2))], or [log(1 - exp(-2), 0). + Each of these intervals is given its own test with two additional tests + for handling very small values and values very close to zero. + """ + + @pytest.mark.parametrize( + "test_input", [-1e1, -1e2, -1e10, -1e20, -np.finfo(float).max] + ) + def test_very_small_arg(self, test_input, uniform_random_points): + scale = test_input + points = scale * (0.5 * uniform_random_points + 0.5) + assert_func_equal( + log_ndtr_ndtri_exp, + lambda y: y, points, + rtol=1e-14, + nan_ok=True + ) + + @pytest.mark.parametrize( + "interval,expected_rtol", + [ + ((-10, -2), 1e-14), + ((-2, -0.14542), 1e-12), + ((-0.14542, -1e-6), 1e-10), + ((-1e-6, 0), 1e-6), + ], + ) + def test_in_interval(self, interval, expected_rtol, uniform_random_points): + left, right = interval + points = (right - left) * uniform_random_points + left + assert_func_equal( + log_ndtr_ndtri_exp, + lambda y: y, points, + rtol=expected_rtol, + nan_ok=True + ) + + def test_extreme(self): + # bigneg is not quite the largest negative double precision value. + # Here's why: + # The round-trip calculation + # y = ndtri_exp(bigneg) + # bigneg2 = log_ndtr(y) + # where bigneg is a very large negative value, would--with infinite + # precision--result in bigneg2 == bigneg. When bigneg is large enough, + # y is effectively equal to -sqrt(2)*sqrt(-bigneg), and log_ndtr(y) is + # effectively -(y/sqrt(2))**2. If we use bigneg = np.finfo(float).min, + # then by construction, the theoretical value is the most negative + # finite value that can be represented with 64 bit float point. This + # means tiny changes in how the computation proceeds can result in the + # return value being -inf. (E.g. changing the constant representation + # of 1/sqrt(2) from 0.7071067811865475--which is the value returned by + # 1/np.sqrt(2)--to 0.7071067811865476--which is the most accurate 64 + # bit floating point representation of 1/sqrt(2)--results in the + # round-trip that starts with np.finfo(float).min returning -inf. So + # we'll move the bigneg value a few ULPs towards 0 to avoid this + # sensitivity. + # Use the reduce method to apply nextafter four times. + bigneg = np.nextafter.reduce([np.finfo(float).min, 0, 0, 0, 0]) + # tinyneg is approx. -2.225e-308. + tinyneg = -np.finfo(float).tiny + x = np.array([tinyneg, bigneg]) + result = log_ndtr_ndtri_exp(x) + assert_allclose(result, x, rtol=1e-12) + + def test_asymptotes(self): + assert_equal(ndtri_exp([-np.inf, 0.0]), [-np.inf, np.inf]) + + def test_outside_domain(self): + assert np.isnan(ndtri_exp(1.0)) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_orthogonal.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_orthogonal.py new file mode 100644 index 0000000000000000000000000000000000000000..130d52073b3da0a27fc7a15ca18506ca9094942f --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_orthogonal.py @@ -0,0 +1,823 @@ +import pytest +from pytest import raises as assert_raises + +import numpy as np +from numpy import array, sqrt +from numpy.testing import assert_equal, assert_allclose + +from scipy import integrate +import scipy.special as sc +from scipy.special import gamma +import scipy.special._orthogonal as orth + + +class TestCheby: + def test_chebyc(self): + C0 = orth.chebyc(0) + C1 = orth.chebyc(1) + with np.errstate(all='ignore'): + C2 = orth.chebyc(2) + C3 = orth.chebyc(3) + C4 = orth.chebyc(4) + C5 = orth.chebyc(5) + + assert_allclose(C0.c, [2], atol=1.5e-13, rtol=0) + assert_allclose(C1.c, [1, 0], atol=1.5e-13, rtol=0) + assert_allclose(C2.c, [1, 0, -2], atol=1.5e-13, rtol=0) + assert_allclose(C3.c, [1, 0, -3, 0], atol=1.5e-13, rtol=0) + assert_allclose(C4.c, [1, 0, -4, 0, 2], atol=1.5e-13, rtol=0) + assert_allclose(C5.c, [1, 0, -5, 0, 5, 0],atol=1.5e-13, rtol=0) + + def test_chebys(self): + S0 = orth.chebys(0) + S1 = orth.chebys(1) + S2 = orth.chebys(2) + S3 = orth.chebys(3) + S4 = orth.chebys(4) + S5 = orth.chebys(5) + assert_allclose(S0.c, [1], atol=1.5e-13, rtol=0) + assert_allclose(S1.c, [1, 0], atol=1.5e-13, rtol=0) + assert_allclose(S2.c, [1, 0, -1], atol=1.5e-13, rtol=0) + assert_allclose(S3.c, [1, 0, -2, 0], atol=1.5e-13, rtol=0) + assert_allclose(S4.c, [1, 0, -3, 0, 1], atol=1.5e-13, rtol=0) + assert_allclose(S5.c, [1, 0, -4, 0, 3, 0], atol=1.5e-13, rtol=0) + + def test_chebyt(self): + T0 = orth.chebyt(0) + T1 = orth.chebyt(1) + T2 = orth.chebyt(2) + T3 = orth.chebyt(3) + T4 = orth.chebyt(4) + T5 = orth.chebyt(5) + assert_allclose(T0.c, [1], atol=1.5e-13, rtol=0) + assert_allclose(T1.c, [1, 0], atol=1.5e-13, rtol=0) + assert_allclose(T2.c, [2, 0, -1], atol=1.5e-13, rtol=0) + assert_allclose(T3.c, [4, 0, -3, 0], atol=1.5e-13, rtol=0) + assert_allclose(T4.c, [8, 0, -8, 0, 1], atol=1.5e-13, rtol=0) + assert_allclose(T5.c, [16, 0, -20, 0, 5, 0], atol=1.5e-13, rtol=0) + + def test_chebyu(self): + U0 = orth.chebyu(0) + U1 = orth.chebyu(1) + U2 = orth.chebyu(2) + U3 = orth.chebyu(3) + U4 = orth.chebyu(4) + U5 = orth.chebyu(5) + assert_allclose(U0.c, [1], atol=1.5e-13, rtol=0) + assert_allclose(U1.c, [2, 0], atol=1.5e-13, rtol=0) + assert_allclose(U2.c, [4, 0, -1], atol=1.5e-13, rtol=0) + assert_allclose(U3.c, [8, 0, -4, 0], atol=1.5e-13, rtol=0) + assert_allclose(U4.c, [16, 0, -12, 0, 1], atol=1.5e-13, rtol=0) + assert_allclose(U5.c, [32, 0, -32, 0, 6, 0], atol=1.5e-13, rtol=0) + + +class TestGegenbauer: + + def test_gegenbauer(self): + a = 5*np.random.random() - 0.5 + if np.any(a == 0): + a = -0.2 + Ca0 = orth.gegenbauer(0,a) + Ca1 = orth.gegenbauer(1,a) + Ca2 = orth.gegenbauer(2,a) + Ca3 = orth.gegenbauer(3,a) + Ca4 = orth.gegenbauer(4,a) + Ca5 = orth.gegenbauer(5,a) + + assert_allclose(Ca0.c, array([1]), atol=1.5e-13, rtol=0) + assert_allclose(Ca1.c, array([2*a, 0]), atol=1.5e-13, rtol=0) + assert_allclose(Ca2.c, array([2*a*(a + 1), 0, -a]), + atol=1.5e-13, rtol=0) + assert_allclose(Ca3.c, array([4*sc.poch(a, 3), 0,-6*a*(a + 1), 0])/3.0, + atol=1.5e-11, rtol=0) + assert_allclose(Ca4.c, array([4*sc.poch(a, 4), 0, -12*sc.poch(a, 3), + 0, 3*a*(a + 1)])/6.0, + atol=1.5e-11, rtol=0) + assert_allclose(Ca5.c, array([4*sc.poch(a, 5), 0, -20*sc.poch(a, 4), + 0, 15*sc.poch(a, 3), 0])/15.0, + atol=1.5e-11, rtol=0) + + @pytest.mark.parametrize('a', [0, 1]) + def test_n_zero_gh8888(self, a): + # gh-8888 reported that gegenbauer(0, 0) returns NaN polynomial + Cn0 = orth.gegenbauer(0, a) + assert_equal(Cn0.c, np.asarray([1.])) + + def test_valid_alpha(self): + # Check input validation of `alpha` + message = '`alpha` must be a finite number greater...' + with pytest.raises(ValueError, match=message): + orth.gegenbauer(0, np.nan) + with pytest.raises(ValueError, match=message): + orth.gegenbauer(1, -0.5) + with pytest.raises(ValueError, match=message): + orth.gegenbauer(2, -np.inf) + + +class TestHermite: + def test_hermite(self): + H0 = orth.hermite(0) + H1 = orth.hermite(1) + H2 = orth.hermite(2) + H3 = orth.hermite(3) + H4 = orth.hermite(4) + H5 = orth.hermite(5) + assert_allclose(H0.c, [1], atol=1.5e-13, rtol=0) + assert_allclose(H1.c, [2, 0], atol=1.5e-13, rtol=0) + assert_allclose(H2.c, [4, 0, -2], atol=1.5e-13, rtol=0) + assert_allclose(H3.c, [8, 0, -12, 0], atol=1.5e-13, rtol=0) + assert_allclose(H4.c, [16, 0, -48, 0, 12], atol=1.5e-12, rtol=0) + assert_allclose(H5.c, [32, 0, -160, 0, 120, 0], atol=1.5e-12, rtol=0) + + def test_hermitenorm(self): + # He_n(x) = 2**(-n/2) H_n(x/sqrt(2)) + psub = np.poly1d([1.0/sqrt(2),0]) + H0 = orth.hermitenorm(0) + H1 = orth.hermitenorm(1) + H2 = orth.hermitenorm(2) + H3 = orth.hermitenorm(3) + H4 = orth.hermitenorm(4) + H5 = orth.hermitenorm(5) + he0 = orth.hermite(0)(psub) + he1 = orth.hermite(1)(psub) / sqrt(2) + he2 = orth.hermite(2)(psub) / 2.0 + he3 = orth.hermite(3)(psub) / (2*sqrt(2)) + he4 = orth.hermite(4)(psub) / 4.0 + he5 = orth.hermite(5)(psub) / (4.0*sqrt(2)) + + assert_allclose(H0.c, he0.c, atol=1.5e-13, rtol=0) + assert_allclose(H1.c, he1.c, atol=1.5e-13, rtol=0) + assert_allclose(H2.c, he2.c, atol=1.5e-13, rtol=0) + assert_allclose(H3.c, he3.c, atol=1.5e-13, rtol=0) + assert_allclose(H4.c, he4.c, atol=1.5e-13, rtol=0) + assert_allclose(H5.c, he5.c, atol=1.5e-13, rtol=0) + + +class TestShLegendre: + def test_sh_legendre(self): + # P*_n(x) = P_n(2x-1) + psub = np.poly1d([2,-1]) + Ps0 = orth.sh_legendre(0) + Ps1 = orth.sh_legendre(1) + Ps2 = orth.sh_legendre(2) + Ps3 = orth.sh_legendre(3) + Ps4 = orth.sh_legendre(4) + Ps5 = orth.sh_legendre(5) + pse0 = orth.legendre(0)(psub) + pse1 = orth.legendre(1)(psub) + pse2 = orth.legendre(2)(psub) + pse3 = orth.legendre(3)(psub) + pse4 = orth.legendre(4)(psub) + pse5 = orth.legendre(5)(psub) + assert_allclose(Ps0.c, pse0.c, atol=1.5e-13, rtol=0) + assert_allclose(Ps1.c, pse1.c, atol=1.5e-13, rtol=0) + assert_allclose(Ps2.c, pse2.c, atol=1.5e-13, rtol=0) + assert_allclose(Ps3.c, pse3.c, atol=1.5e-13, rtol=0) + assert_allclose(Ps4.c, pse4.c, atol=1.5e-12, rtol=0) + assert_allclose(Ps5.c, pse5.c, atol=1.5e-12, rtol=0) + + +class TestShChebyt: + def test_sh_chebyt(self): + # T*_n(x) = T_n(2x-1) + psub = np.poly1d([2,-1]) + Ts0 = orth.sh_chebyt(0) + Ts1 = orth.sh_chebyt(1) + Ts2 = orth.sh_chebyt(2) + Ts3 = orth.sh_chebyt(3) + Ts4 = orth.sh_chebyt(4) + Ts5 = orth.sh_chebyt(5) + tse0 = orth.chebyt(0)(psub) + tse1 = orth.chebyt(1)(psub) + tse2 = orth.chebyt(2)(psub) + tse3 = orth.chebyt(3)(psub) + tse4 = orth.chebyt(4)(psub) + tse5 = orth.chebyt(5)(psub) + assert_allclose(Ts0.c, tse0.c, atol=1.5e-13, rtol=0) + assert_allclose(Ts1.c, tse1.c, atol=1.5e-13, rtol=0) + assert_allclose(Ts2.c, tse2.c, atol=1.5e-13, rtol=0) + assert_allclose(Ts3.c, tse3.c, atol=1.5e-13, rtol=0) + assert_allclose(Ts4.c, tse4.c, atol=1.5e-12, rtol=0) + assert_allclose(Ts5.c, tse5.c, atol=1.5e-12, rtol=0) + + +class TestShChebyu: + def test_sh_chebyu(self): + # U*_n(x) = U_n(2x-1) + psub = np.poly1d([2,-1]) + Us0 = orth.sh_chebyu(0) + Us1 = orth.sh_chebyu(1) + Us2 = orth.sh_chebyu(2) + Us3 = orth.sh_chebyu(3) + Us4 = orth.sh_chebyu(4) + Us5 = orth.sh_chebyu(5) + use0 = orth.chebyu(0)(psub) + use1 = orth.chebyu(1)(psub) + use2 = orth.chebyu(2)(psub) + use3 = orth.chebyu(3)(psub) + use4 = orth.chebyu(4)(psub) + use5 = orth.chebyu(5)(psub) + assert_allclose(Us0.c, use0.c, atol=1.5e-13, rtol=0) + assert_allclose(Us1.c, use1.c, atol=1.5e-13, rtol=0) + assert_allclose(Us2.c, use2.c, atol=1.5e-13, rtol=0) + assert_allclose(Us3.c, use3.c, atol=1.5e-13, rtol=0) + assert_allclose(Us4.c, use4.c, atol=1.5e-12, rtol=0) + assert_allclose(Us5.c, use5.c, atol=1.5e-11, rtol=0) + + +class TestShJacobi: + def test_sh_jacobi(self): + # G^(p,q)_n(x) = n! gamma(n+p)/gamma(2*n+p) * P^(p-q,q-1)_n(2*x-1) + def conv(n, p): + return gamma(n + 1) * gamma(n + p) / gamma(2 * n + p) + psub = np.poly1d([2,-1]) + q = 4 * np.random.random() + p = q-1 + 2*np.random.random() + # print("shifted jacobi p,q = ", p, q) + G0 = orth.sh_jacobi(0,p,q) + G1 = orth.sh_jacobi(1,p,q) + G2 = orth.sh_jacobi(2,p,q) + G3 = orth.sh_jacobi(3,p,q) + G4 = orth.sh_jacobi(4,p,q) + G5 = orth.sh_jacobi(5,p,q) + ge0 = orth.jacobi(0,p-q,q-1)(psub) * conv(0,p) + ge1 = orth.jacobi(1,p-q,q-1)(psub) * conv(1,p) + ge2 = orth.jacobi(2,p-q,q-1)(psub) * conv(2,p) + ge3 = orth.jacobi(3,p-q,q-1)(psub) * conv(3,p) + ge4 = orth.jacobi(4,p-q,q-1)(psub) * conv(4,p) + ge5 = orth.jacobi(5,p-q,q-1)(psub) * conv(5,p) + + assert_allclose(G0.c, ge0.c, atol=1.5e-13, rtol=0) + assert_allclose(G1.c, ge1.c, atol=1.5e-13, rtol=0) + assert_allclose(G2.c, ge2.c, atol=1.5e-13, rtol=0) + assert_allclose(G3.c, ge3.c, atol=1.5e-13, rtol=0) + assert_allclose(G4.c, ge4.c, atol=1.5e-13, rtol=0) + assert_allclose(G5.c, ge5.c, atol=1.5e-13, rtol=0) + + +class TestCall: + def test_call(self): + poly = [] + for n in range(5): + poly.extend([x.strip() for x in + (f""" + orth.jacobi({n},0.3,0.9) + orth.sh_jacobi({n},0.3,0.9) + orth.genlaguerre({n},0.3) + orth.laguerre({n}) + orth.hermite({n}) + orth.hermitenorm({n}) + orth.gegenbauer({n},0.3) + orth.chebyt({n}) + orth.chebyu({n}) + orth.chebyc({n}) + orth.chebys({n}) + orth.sh_chebyt({n}) + orth.sh_chebyu({n}) + orth.legendre({n}) + orth.sh_legendre({n}) + """).split()]) + with np.errstate(all='ignore'): + for pstr in poly: + p = eval(pstr) + assert_allclose(p(0.315), np.poly1d(p.coef)(0.315), + atol=1.5e-7, rtol=0, err_msg=pstr) + + +class TestGenlaguerre: + def test_regression(self): + assert_equal(orth.genlaguerre(1, 1, monic=False)(0), 2.) + assert_equal(orth.genlaguerre(1, 1, monic=True)(0), -2.) + assert_equal(orth.genlaguerre(1, 1, monic=False), np.poly1d([-1, 2])) + assert_equal(orth.genlaguerre(1, 1, monic=True), np.poly1d([1, -2])) + + +def verify_gauss_quad(root_func, eval_func, weight_func, a, b, N, + rtol=1e-15, atol=5e-14): + # this test is copied from numpy's TestGauss in test_hermite.py + x, w, mu = root_func(N, True) + + n = np.arange(N, dtype=np.dtype("long")) + v = eval_func(n[:,np.newaxis], x) + vv = np.dot(v*w, v.T) + vd = 1 / np.sqrt(vv.diagonal()) + vv = vd[:, np.newaxis] * vv * vd + assert_allclose(vv, np.eye(N), rtol, atol) + + # check that the integral of 1 is correct + assert_allclose(w.sum(), mu, rtol, atol) + + # compare the results of integrating a function with quad. + def f(x): + return x ** 3 - 3 * x ** 2 + x - 2 + resI = integrate.quad(lambda x: f(x)*weight_func(x), a, b) + resG = np.vdot(f(x), w) + rtol = 1e-6 if 1e-6 < resI[1] else resI[1] * 10 + assert_allclose(resI[0], resG, rtol=rtol) + +def test_roots_jacobi(): + def rf(a, b): + return lambda n, mu: sc.roots_jacobi(n, a, b, mu) + def ef(a, b): + return lambda n, x: sc.eval_jacobi(n, a, b, x) + def wf(a, b): + return lambda x: (1 - x) ** a * (1 + x) ** b + + vgq = verify_gauss_quad + vgq(rf(-0.5, -0.75), ef(-0.5, -0.75), wf(-0.5, -0.75), -1., 1., 5) + vgq(rf(-0.5, -0.75), ef(-0.5, -0.75), wf(-0.5, -0.75), -1., 1., + 25, atol=1e-12) + vgq(rf(-0.5, -0.75), ef(-0.5, -0.75), wf(-0.5, -0.75), -1., 1., + 100, atol=1e-11) + + vgq(rf(0.5, -0.5), ef(0.5, -0.5), wf(0.5, -0.5), -1., 1., 5) + vgq(rf(0.5, -0.5), ef(0.5, -0.5), wf(0.5, -0.5), -1., 1., 25, atol=1.5e-13) + vgq(rf(0.5, -0.5), ef(0.5, -0.5), wf(0.5, -0.5), -1., 1., 100, atol=2e-12) + + vgq(rf(1, 0.5), ef(1, 0.5), wf(1, 0.5), -1., 1., 5, atol=2e-13) + vgq(rf(1, 0.5), ef(1, 0.5), wf(1, 0.5), -1., 1., 25, atol=2e-13) + vgq(rf(1, 0.5), ef(1, 0.5), wf(1, 0.5), -1., 1., 100, atol=1e-12) + + vgq(rf(0.9, 2), ef(0.9, 2), wf(0.9, 2), -1., 1., 5) + vgq(rf(0.9, 2), ef(0.9, 2), wf(0.9, 2), -1., 1., 25, atol=1e-13) + vgq(rf(0.9, 2), ef(0.9, 2), wf(0.9, 2), -1., 1., 100, atol=3e-13) + + vgq(rf(18.24, 27.3), ef(18.24, 27.3), wf(18.24, 27.3), -1., 1., 5) + vgq(rf(18.24, 27.3), ef(18.24, 27.3), wf(18.24, 27.3), -1., 1., 25, + atol=1.1e-14) + vgq(rf(18.24, 27.3), ef(18.24, 27.3), wf(18.24, 27.3), -1., 1., + 100, atol=1e-13) + + vgq(rf(47.1, -0.2), ef(47.1, -0.2), wf(47.1, -0.2), -1., 1., 5, atol=1e-13) + vgq(rf(47.1, -0.2), ef(47.1, -0.2), wf(47.1, -0.2), -1., 1., 25, atol=2e-13) + vgq(rf(47.1, -0.2), ef(47.1, -0.2), wf(47.1, -0.2), -1., 1., + 100, atol=1e-11) + + vgq(rf(1., 658.), ef(1., 658.), wf(1., 658.), -1., 1., 5, atol=2e-13) + vgq(rf(1., 658.), ef(1., 658.), wf(1., 658.), -1., 1., 25, atol=1e-12) + vgq(rf(1., 658.), ef(1., 658.), wf(1., 658.), -1., 1., 100, atol=1e-11) + vgq(rf(1., 658.), ef(1., 658.), wf(1., 658.), -1., 1., 250, atol=1e-11) + + vgq(rf(511., 511.), ef(511., 511.), wf(511., 511.), -1., 1., 5, + atol=1e-12) + vgq(rf(511., 511.), ef(511., 511.), wf(511., 511.), -1., 1., 25, + atol=1e-11) + vgq(rf(511., 511.), ef(511., 511.), wf(511., 511.), -1., 1., 100, + atol=1e-10) + + vgq(rf(511., 512.), ef(511., 512.), wf(511., 512.), -1., 1., 5, + atol=1e-12) + vgq(rf(511., 512.), ef(511., 512.), wf(511., 512.), -1., 1., 25, + atol=1e-11) + vgq(rf(511., 512.), ef(511., 512.), wf(511., 512.), -1., 1., 100, + atol=1e-10) + + vgq(rf(1000., 500.), ef(1000., 500.), wf(1000., 500.), -1., 1., 5, + atol=1e-12) + vgq(rf(1000., 500.), ef(1000., 500.), wf(1000., 500.), -1., 1., 25, + atol=1e-11) + vgq(rf(1000., 500.), ef(1000., 500.), wf(1000., 500.), -1., 1., 100, + atol=1e-10) + + vgq(rf(2.25, 68.9), ef(2.25, 68.9), wf(2.25, 68.9), -1., 1., 5) + vgq(rf(2.25, 68.9), ef(2.25, 68.9), wf(2.25, 68.9), -1., 1., 25, + atol=1e-13) + vgq(rf(2.25, 68.9), ef(2.25, 68.9), wf(2.25, 68.9), -1., 1., 100, + atol=1e-13) + + # when alpha == beta == 0, P_n^{a,b}(x) == P_n(x) + xj, wj = sc.roots_jacobi(6, 0.0, 0.0) + xl, wl = sc.roots_legendre(6) + assert_allclose(xj, xl, 1e-14, 1e-14) + assert_allclose(wj, wl, 1e-14, 1e-14) + + # when alpha == beta != 0, P_n^{a,b}(x) == C_n^{alpha+0.5}(x) + xj, wj = sc.roots_jacobi(6, 4.0, 4.0) + xc, wc = sc.roots_gegenbauer(6, 4.5) + assert_allclose(xj, xc, 1e-14, 1e-14) + assert_allclose(wj, wc, 1e-14, 1e-14) + + x, w = sc.roots_jacobi(5, 2, 3, False) + y, v, m = sc.roots_jacobi(5, 2, 3, True) + assert_allclose(x, y, 1e-14, 1e-14) + assert_allclose(w, v, 1e-14, 1e-14) + + muI, muI_err = integrate.quad(wf(2,3), -1, 1) + assert_allclose(m, muI, rtol=muI_err) + + assert_raises(ValueError, sc.roots_jacobi, 0, 1, 1) + assert_raises(ValueError, sc.roots_jacobi, 3.3, 1, 1) # type: ignore[call-overload] + assert_raises(ValueError, sc.roots_jacobi, 3, -2, 1) + assert_raises(ValueError, sc.roots_jacobi, 3, 1, -2) + assert_raises(ValueError, sc.roots_jacobi, 3, -2, -2) + +def test_roots_sh_jacobi(): + def rf(a, b): + return lambda n, mu: sc.roots_sh_jacobi(n, a, b, mu) + def ef(a, b): + return lambda n, x: sc.eval_sh_jacobi(n, a, b, x) + def wf(a, b): + return lambda x: (1.0 - x) ** (a - b) * x ** (b - 1.0) + + vgq = verify_gauss_quad + vgq(rf(-0.5, 0.25), ef(-0.5, 0.25), wf(-0.5, 0.25), 0., 1., 5) + vgq(rf(-0.5, 0.25), ef(-0.5, 0.25), wf(-0.5, 0.25), 0., 1., + 25, atol=1e-12) + vgq(rf(-0.5, 0.25), ef(-0.5, 0.25), wf(-0.5, 0.25), 0., 1., + 100, atol=1e-11) + + vgq(rf(0.5, 0.5), ef(0.5, 0.5), wf(0.5, 0.5), 0., 1., 5) + vgq(rf(0.5, 0.5), ef(0.5, 0.5), wf(0.5, 0.5), 0., 1., 25, atol=1e-13) + vgq(rf(0.5, 0.5), ef(0.5, 0.5), wf(0.5, 0.5), 0., 1., 100, atol=1e-12) + + vgq(rf(1, 0.5), ef(1, 0.5), wf(1, 0.5), 0., 1., 5) + vgq(rf(1, 0.5), ef(1, 0.5), wf(1, 0.5), 0., 1., 25, atol=1.5e-13) + vgq(rf(1, 0.5), ef(1, 0.5), wf(1, 0.5), 0., 1., 100, atol=2e-12) + + vgq(rf(2, 0.9), ef(2, 0.9), wf(2, 0.9), 0., 1., 5) + vgq(rf(2, 0.9), ef(2, 0.9), wf(2, 0.9), 0., 1., 25, atol=1e-13) + vgq(rf(2, 0.9), ef(2, 0.9), wf(2, 0.9), 0., 1., 100, atol=1e-12) + + vgq(rf(27.3, 18.24), ef(27.3, 18.24), wf(27.3, 18.24), 0., 1., 5) + vgq(rf(27.3, 18.24), ef(27.3, 18.24), wf(27.3, 18.24), 0., 1., 25) + vgq(rf(27.3, 18.24), ef(27.3, 18.24), wf(27.3, 18.24), 0., 1., + 100, atol=1e-13) + + vgq(rf(47.1, 0.2), ef(47.1, 0.2), wf(47.1, 0.2), 0., 1., 5, atol=1e-12) + vgq(rf(47.1, 0.2), ef(47.1, 0.2), wf(47.1, 0.2), 0., 1., 25, atol=1e-11) + vgq(rf(47.1, 0.2), ef(47.1, 0.2), wf(47.1, 0.2), 0., 1., 100, atol=1e-10) + + vgq(rf(68.9, 2.25), ef(68.9, 2.25), wf(68.9, 2.25), 0., 1., 5, atol=3.5e-14) + vgq(rf(68.9, 2.25), ef(68.9, 2.25), wf(68.9, 2.25), 0., 1., 25, atol=2e-13) + vgq(rf(68.9, 2.25), ef(68.9, 2.25), wf(68.9, 2.25), 0., 1., + 100, atol=1e-12) + + x, w = sc.roots_sh_jacobi(5, 3, 2, False) + y, v, m = sc.roots_sh_jacobi(5, 3, 2, True) + assert_allclose(x, y, 1e-14, 1e-14) + assert_allclose(w, v, 1e-14, 1e-14) + + muI, muI_err = integrate.quad(wf(3,2), 0, 1) + assert_allclose(m, muI, rtol=muI_err) + + assert_raises(ValueError, sc.roots_sh_jacobi, 0, 1, 1) + assert_raises(ValueError, sc.roots_sh_jacobi, 3.3, 1, 1) # type: ignore[call-overload] + assert_raises(ValueError, sc.roots_sh_jacobi, 3, 1, 2) # p - q <= -1 + assert_raises(ValueError, sc.roots_sh_jacobi, 3, 2, -1) # q <= 0 + assert_raises(ValueError, sc.roots_sh_jacobi, 3, -2, -1) # both + +def test_roots_hermite(): + rootf = sc.roots_hermite + evalf = sc.eval_hermite + weightf = orth.hermite(5).weight_func + + verify_gauss_quad(rootf, evalf, weightf, -np.inf, np.inf, 5) + verify_gauss_quad(rootf, evalf, weightf, -np.inf, np.inf, 25, atol=1e-13) + verify_gauss_quad(rootf, evalf, weightf, -np.inf, np.inf, 100, atol=1e-12) + + # Golub-Welsch branch + x, w = sc.roots_hermite(5, False) + y, v, m = sc.roots_hermite(5, True) + assert_allclose(x, y, 1e-14, 1e-14) + assert_allclose(w, v, 1e-14, 1e-14) + + muI, muI_err = integrate.quad(weightf, -np.inf, np.inf) + assert_allclose(m, muI, rtol=muI_err) + + # Asymptotic branch (switch over at n >= 150) + x, w = sc.roots_hermite(200, False) + y, v, m = sc.roots_hermite(200, True) + assert_allclose(x, y, 1e-14, 1e-14) + assert_allclose(w, v, 1e-14, 1e-14) + assert_allclose(sum(v), m, 1e-14, 1e-14) + + assert_raises(ValueError, sc.roots_hermite, 0) + assert_raises(ValueError, sc.roots_hermite, 3.3) # type: ignore[call-overload] + +def test_roots_hermite_asy(): + # Recursion for Hermite functions + def hermite_recursion(n, nodes): + H = np.zeros((n, nodes.size)) + H[0,:] = np.pi**(-0.25) * np.exp(-0.5*nodes**2) + if n > 1: + H[1,:] = sqrt(2.0) * nodes * H[0,:] + for k in range(2, n): + H[k,:] = sqrt(2.0/k) * nodes * H[k-1,:] - sqrt((k-1.0)/k) * H[k-2,:] + return H + + # This tests only the nodes + def test(N, rtol=1e-15, atol=1e-14): + x, w = orth._roots_hermite_asy(N) + H = hermite_recursion(N+1, x) + assert_allclose(H[-1,:], np.zeros(N), rtol, atol) + assert_allclose(sum(w), sqrt(np.pi), rtol, atol) + + test(150, atol=1e-12) + test(151, atol=1e-12) + test(300, atol=1e-12) + test(301, atol=1e-12) + test(500, atol=1e-12) + test(501, atol=1e-12) + test(999, atol=1e-12) + test(1000, atol=1e-12) + test(2000, atol=1e-12) + test(5000, atol=1e-12) + +def test_roots_hermitenorm(): + rootf = sc.roots_hermitenorm + evalf = sc.eval_hermitenorm + weightf = orth.hermitenorm(5).weight_func + + verify_gauss_quad(rootf, evalf, weightf, -np.inf, np.inf, 5) + verify_gauss_quad(rootf, evalf, weightf, -np.inf, np.inf, 25, atol=1e-13) + verify_gauss_quad(rootf, evalf, weightf, -np.inf, np.inf, 100, atol=1e-12) + + x, w = sc.roots_hermitenorm(5, False) + y, v, m = sc.roots_hermitenorm(5, True) + assert_allclose(x, y, 1e-14, 1e-14) + assert_allclose(w, v, 1e-14, 1e-14) + + muI, muI_err = integrate.quad(weightf, -np.inf, np.inf) + assert_allclose(m, muI, rtol=muI_err) + + assert_raises(ValueError, sc.roots_hermitenorm, 0) + assert_raises(ValueError, sc.roots_hermitenorm, 3.3) # type: ignore[call-overload] + +def test_roots_gegenbauer(): + def rootf(a): + return lambda n, mu: sc.roots_gegenbauer(n, a, mu) + def evalf(a): + return lambda n, x: sc.eval_gegenbauer(n, a, x) + def weightf(a): + return lambda x: (1 - x ** 2) ** (a - 0.5) + + vgq = verify_gauss_quad + vgq(rootf(-0.25), evalf(-0.25), weightf(-0.25), -1., 1., 5) + vgq(rootf(-0.25), evalf(-0.25), weightf(-0.25), -1., 1., 25, atol=1e-12) + vgq(rootf(-0.25), evalf(-0.25), weightf(-0.25), -1., 1., 100, atol=1e-11) + + vgq(rootf(0.1), evalf(0.1), weightf(0.1), -1., 1., 5) + vgq(rootf(0.1), evalf(0.1), weightf(0.1), -1., 1., 25, atol=1e-13) + vgq(rootf(0.1), evalf(0.1), weightf(0.1), -1., 1., 100, atol=1e-12) + + vgq(rootf(1), evalf(1), weightf(1), -1., 1., 5) + vgq(rootf(1), evalf(1), weightf(1), -1., 1., 25, atol=1e-13) + vgq(rootf(1), evalf(1), weightf(1), -1., 1., 100, atol=1e-12) + + vgq(rootf(10), evalf(10), weightf(10), -1., 1., 5) + vgq(rootf(10), evalf(10), weightf(10), -1., 1., 25, atol=1e-13) + vgq(rootf(10), evalf(10), weightf(10), -1., 1., 100, atol=1e-12) + + vgq(rootf(50), evalf(50), weightf(50), -1., 1., 5, atol=1e-13) + vgq(rootf(50), evalf(50), weightf(50), -1., 1., 25, atol=1e-12) + vgq(rootf(50), evalf(50), weightf(50), -1., 1., 100, atol=1e-11) + + # Alpha=170 is where the approximation used in roots_gegenbauer changes + vgq(rootf(170), evalf(170), weightf(170), -1., 1., 5, atol=1e-13) + vgq(rootf(170), evalf(170), weightf(170), -1., 1., 25, atol=1e-12) + vgq(rootf(170), evalf(170), weightf(170), -1., 1., 100, atol=1e-11) + vgq(rootf(170.5), evalf(170.5), weightf(170.5), -1., 1., 5, atol=1.25e-13) + vgq(rootf(170.5), evalf(170.5), weightf(170.5), -1., 1., 25, atol=1e-12) + vgq(rootf(170.5), evalf(170.5), weightf(170.5), -1., 1., 100, atol=1e-11) + + # Test for failures, e.g. overflows, resulting from large alphas + vgq(rootf(238), evalf(238), weightf(238), -1., 1., 5, atol=1e-13) + vgq(rootf(238), evalf(238), weightf(238), -1., 1., 25, atol=1e-12) + vgq(rootf(238), evalf(238), weightf(238), -1., 1., 100, atol=1e-11) + vgq(rootf(512.5), evalf(512.5), weightf(512.5), -1., 1., 5, atol=1e-12) + vgq(rootf(512.5), evalf(512.5), weightf(512.5), -1., 1., 25, atol=1e-11) + vgq(rootf(512.5), evalf(512.5), weightf(512.5), -1., 1., 100, atol=1e-10) + + # this is a special case that the old code supported. + # when alpha = 0, the gegenbauer polynomial is uniformly 0. but it goes + # to a scaled down copy of T_n(x) there. + vgq(rootf(0), sc.eval_chebyt, weightf(0), -1., 1., 5) + vgq(rootf(0), sc.eval_chebyt, weightf(0), -1., 1., 25) + vgq(rootf(0), sc.eval_chebyt, weightf(0), -1., 1., 100, atol=1e-12) + + x, w = sc.roots_gegenbauer(5, 2, False) + y, v, m = sc.roots_gegenbauer(5, 2, True) + assert_allclose(x, y, 1e-14, 1e-14) + assert_allclose(w, v, 1e-14, 1e-14) + + muI, muI_err = integrate.quad(weightf(2), -1, 1) + assert_allclose(m, muI, rtol=muI_err) + + assert_raises(ValueError, sc.roots_gegenbauer, 0, 2) + assert_raises(ValueError, sc.roots_gegenbauer, 3.3, 2) # type: ignore[call-overload] + assert_raises(ValueError, sc.roots_gegenbauer, 3, -.75) + +def test_roots_chebyt(): + weightf = orth.chebyt(5).weight_func + verify_gauss_quad(sc.roots_chebyt, sc.eval_chebyt, weightf, -1., 1., 5) + verify_gauss_quad(sc.roots_chebyt, sc.eval_chebyt, weightf, -1., 1., 25) + verify_gauss_quad(sc.roots_chebyt, sc.eval_chebyt, weightf, -1., 1., 100, + atol=1e-12) + + x, w = sc.roots_chebyt(5, False) + y, v, m = sc.roots_chebyt(5, True) + assert_allclose(x, y, 1e-14, 1e-14) + assert_allclose(w, v, 1e-14, 1e-14) + + muI, muI_err = integrate.quad(weightf, -1, 1) + assert_allclose(m, muI, rtol=muI_err) + + assert_raises(ValueError, sc.roots_chebyt, 0) + assert_raises(ValueError, sc.roots_chebyt, 3.3) # type: ignore[call-overload] + +def test_chebyt_symmetry(): + x, w = sc.roots_chebyt(21) + pos, neg = x[:10], x[11:] + assert_equal(neg, -pos[::-1]) + assert_equal(x[10], 0) + +def test_roots_chebyu(): + weightf = orth.chebyu(5).weight_func + verify_gauss_quad(sc.roots_chebyu, sc.eval_chebyu, weightf, -1., 1., 5) + verify_gauss_quad(sc.roots_chebyu, sc.eval_chebyu, weightf, -1., 1., 25) + verify_gauss_quad(sc.roots_chebyu, sc.eval_chebyu, weightf, -1., 1., 100) + + x, w = sc.roots_chebyu(5, False) + y, v, m = sc.roots_chebyu(5, True) + assert_allclose(x, y, 1e-14, 1e-14) + assert_allclose(w, v, 1e-14, 1e-14) + + muI, muI_err = integrate.quad(weightf, -1, 1) + assert_allclose(m, muI, rtol=muI_err) + + assert_raises(ValueError, sc.roots_chebyu, 0) + assert_raises(ValueError, sc.roots_chebyu, 3.3) # type: ignore[call-overload] + +def test_roots_chebyc(): + weightf = orth.chebyc(5).weight_func + verify_gauss_quad(sc.roots_chebyc, sc.eval_chebyc, weightf, -2., 2., 5) + verify_gauss_quad(sc.roots_chebyc, sc.eval_chebyc, weightf, -2., 2., 25) + verify_gauss_quad(sc.roots_chebyc, sc.eval_chebyc, weightf, -2., 2., 100, + atol=1e-12) + + x, w = sc.roots_chebyc(5, False) + y, v, m = sc.roots_chebyc(5, True) + assert_allclose(x, y, 1e-14, 1e-14) + assert_allclose(w, v, 1e-14, 1e-14) + + muI, muI_err = integrate.quad(weightf, -2, 2) + assert_allclose(m, muI, rtol=muI_err) + + assert_raises(ValueError, sc.roots_chebyc, 0) + assert_raises(ValueError, sc.roots_chebyc, 3.3) # type: ignore[call-overload] + +def test_roots_chebys(): + weightf = orth.chebys(5).weight_func + verify_gauss_quad(sc.roots_chebys, sc.eval_chebys, weightf, -2., 2., 5) + verify_gauss_quad(sc.roots_chebys, sc.eval_chebys, weightf, -2., 2., 25) + verify_gauss_quad(sc.roots_chebys, sc.eval_chebys, weightf, -2., 2., 100) + + x, w = sc.roots_chebys(5, False) + y, v, m = sc.roots_chebys(5, True) + assert_allclose(x, y, 1e-14, 1e-14) + assert_allclose(w, v, 1e-14, 1e-14) + + muI, muI_err = integrate.quad(weightf, -2, 2) + assert_allclose(m, muI, rtol=muI_err) + + assert_raises(ValueError, sc.roots_chebys, 0) + assert_raises(ValueError, sc.roots_chebys, 3.3) # type: ignore[call-overload] + +def test_roots_sh_chebyt(): + weightf = orth.sh_chebyt(5).weight_func + verify_gauss_quad(sc.roots_sh_chebyt, sc.eval_sh_chebyt, weightf, 0., 1., 5) + verify_gauss_quad(sc.roots_sh_chebyt, sc.eval_sh_chebyt, weightf, 0., 1., 25) + verify_gauss_quad(sc.roots_sh_chebyt, sc.eval_sh_chebyt, weightf, 0., 1., + 100, atol=1e-13) + + x, w = sc.roots_sh_chebyt(5, False) + y, v, m = sc.roots_sh_chebyt(5, True) + assert_allclose(x, y, 1e-14, 1e-14) + assert_allclose(w, v, 1e-14, 1e-14) + + muI, muI_err = integrate.quad(weightf, 0, 1) + assert_allclose(m, muI, rtol=muI_err) + + assert_raises(ValueError, sc.roots_sh_chebyt, 0) + assert_raises(ValueError, sc.roots_sh_chebyt, 3.3) # type: ignore[call-overload] + +def test_roots_sh_chebyu(): + weightf = orth.sh_chebyu(5).weight_func + verify_gauss_quad(sc.roots_sh_chebyu, sc.eval_sh_chebyu, weightf, 0., 1., 5) + verify_gauss_quad(sc.roots_sh_chebyu, sc.eval_sh_chebyu, weightf, 0., 1., 25) + verify_gauss_quad(sc.roots_sh_chebyu, sc.eval_sh_chebyu, weightf, 0., 1., + 100, atol=1e-13) + + x, w = sc.roots_sh_chebyu(5, False) + y, v, m = sc.roots_sh_chebyu(5, True) + assert_allclose(x, y, 1e-14, 1e-14) + assert_allclose(w, v, 1e-14, 1e-14) + + muI, muI_err = integrate.quad(weightf, 0, 1) + assert_allclose(m, muI, rtol=muI_err) + + assert_raises(ValueError, sc.roots_sh_chebyu, 0) + assert_raises(ValueError, sc.roots_sh_chebyu, 3.3) # type: ignore[call-overload] + +def test_roots_legendre(): + weightf = orth.legendre(5).weight_func + verify_gauss_quad(sc.roots_legendre, sc.eval_legendre, weightf, -1., 1., 5) + verify_gauss_quad(sc.roots_legendre, sc.eval_legendre, weightf, -1., 1., + 25, atol=1e-13) + verify_gauss_quad(sc.roots_legendre, sc.eval_legendre, weightf, -1., 1., + 100, atol=1e-12) + + x, w = sc.roots_legendre(5, False) + y, v, m = sc.roots_legendre(5, True) + assert_allclose(x, y, 1e-14, 1e-14) + assert_allclose(w, v, 1e-14, 1e-14) + + muI, muI_err = integrate.quad(weightf, -1, 1) + assert_allclose(m, muI, rtol=muI_err) + + assert_raises(ValueError, sc.roots_legendre, 0) + assert_raises(ValueError, sc.roots_legendre, 3.3) # type: ignore[call-overload] + +def test_roots_sh_legendre(): + weightf = orth.sh_legendre(5).weight_func + verify_gauss_quad(sc.roots_sh_legendre, sc.eval_sh_legendre, weightf, 0., 1., 5) + verify_gauss_quad(sc.roots_sh_legendre, sc.eval_sh_legendre, weightf, 0., 1., + 25, atol=1e-13) + verify_gauss_quad(sc.roots_sh_legendre, sc.eval_sh_legendre, weightf, 0., 1., + 100, atol=1e-12) + + x, w = sc.roots_sh_legendre(5, False) + y, v, m = sc.roots_sh_legendre(5, True) + assert_allclose(x, y, 1e-14, 1e-14) + assert_allclose(w, v, 1e-14, 1e-14) + + muI, muI_err = integrate.quad(weightf, 0, 1) + assert_allclose(m, muI, rtol=muI_err) + + assert_raises(ValueError, sc.roots_sh_legendre, 0) + assert_raises(ValueError, sc.roots_sh_legendre, 3.3) # type: ignore[call-overload] + +def test_roots_laguerre(): + weightf = orth.laguerre(5).weight_func + verify_gauss_quad(sc.roots_laguerre, sc.eval_laguerre, weightf, 0., np.inf, 5) + verify_gauss_quad(sc.roots_laguerre, sc.eval_laguerre, weightf, 0., np.inf, + 25, atol=1e-13) + verify_gauss_quad(sc.roots_laguerre, sc.eval_laguerre, weightf, 0., np.inf, + 100, atol=1e-12) + + x, w = sc.roots_laguerre(5, False) + y, v, m = sc.roots_laguerre(5, True) + assert_allclose(x, y, 1e-14, 1e-14) + assert_allclose(w, v, 1e-14, 1e-14) + + muI, muI_err = integrate.quad(weightf, 0, np.inf) + assert_allclose(m, muI, rtol=muI_err) + + assert_raises(ValueError, sc.roots_laguerre, 0) + assert_raises(ValueError, sc.roots_laguerre, 3.3) # type: ignore[call-overload] + +def test_roots_genlaguerre(): + def rootf(a): + return lambda n, mu: sc.roots_genlaguerre(n, a, mu) + def evalf(a): + return lambda n, x: sc.eval_genlaguerre(n, a, x) + def weightf(a): + return lambda x: x ** a * np.exp(-x) + + vgq = verify_gauss_quad + vgq(rootf(-0.5), evalf(-0.5), weightf(-0.5), 0., np.inf, 5) + vgq(rootf(-0.5), evalf(-0.5), weightf(-0.5), 0., np.inf, 25, atol=1e-13) + vgq(rootf(-0.5), evalf(-0.5), weightf(-0.5), 0., np.inf, 100, atol=1e-12) + + vgq(rootf(0.1), evalf(0.1), weightf(0.1), 0., np.inf, 5) + vgq(rootf(0.1), evalf(0.1), weightf(0.1), 0., np.inf, 25, atol=1e-13) + vgq(rootf(0.1), evalf(0.1), weightf(0.1), 0., np.inf, 100, atol=1.6e-13) + + vgq(rootf(1), evalf(1), weightf(1), 0., np.inf, 5) + vgq(rootf(1), evalf(1), weightf(1), 0., np.inf, 25, atol=1e-13) + vgq(rootf(1), evalf(1), weightf(1), 0., np.inf, 100, atol=1.03e-13) + + vgq(rootf(10), evalf(10), weightf(10), 0., np.inf, 5) + vgq(rootf(10), evalf(10), weightf(10), 0., np.inf, 25, atol=1e-13) + vgq(rootf(10), evalf(10), weightf(10), 0., np.inf, 100, atol=1e-12) + + vgq(rootf(50), evalf(50), weightf(50), 0., np.inf, 5) + vgq(rootf(50), evalf(50), weightf(50), 0., np.inf, 25, atol=1e-13) + vgq(rootf(50), evalf(50), weightf(50), 0., np.inf, 100, rtol=1e-14, atol=2e-13) + + x, w = sc.roots_genlaguerre(5, 2, False) + y, v, m = sc.roots_genlaguerre(5, 2, True) + assert_allclose(x, y, 1e-14, 1e-14) + assert_allclose(w, v, 1e-14, 1e-14) + + muI, muI_err = integrate.quad(weightf(2.), 0., np.inf) + assert_allclose(m, muI, rtol=muI_err) + + assert_raises(ValueError, sc.roots_genlaguerre, 0, 2) + assert_raises(ValueError, sc.roots_genlaguerre, 3.3, 2) # type: ignore[call-overload] + assert_raises(ValueError, sc.roots_genlaguerre, 3, -1.1) + + +def test_gh_6721(): + # Regression test for gh_6721. This should not raise. + sc.chebyt(65)(0.2) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_orthogonal_eval.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_orthogonal_eval.py new file mode 100644 index 0000000000000000000000000000000000000000..c70105aeaed63019acb9c4136cda3079af0b6a3a --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_orthogonal_eval.py @@ -0,0 +1,383 @@ +import numpy as np +from numpy.testing import assert_, assert_allclose +import pytest + +from scipy.special import _ufuncs, gammasgn +import scipy.special._orthogonal as orth +from scipy.special._testutils import FuncData + + +def test_eval_chebyt(): + n = np.arange(0, 10000, 7, dtype=np.dtype("long")) + x = 2*np.random.rand() - 1 + v1 = np.cos(n*np.arccos(x)) + v2 = _ufuncs.eval_chebyt(n, x) + assert_(np.allclose(v1, v2, rtol=1e-15)) + + +def test_eval_chebyt_gh20129(): + # https://github.com/scipy/scipy/issues/20129 + assert _ufuncs.eval_chebyt(7, 2 + 0j) == 5042.0 + + +def test_eval_genlaguerre_restriction(): + # check it returns nan for alpha <= -1 + assert_(np.isnan(_ufuncs.eval_genlaguerre(0, -1, 0))) + assert_(np.isnan(_ufuncs.eval_genlaguerre(0.1, -1, 0))) + + +def test_warnings(): + # ticket 1334 + with np.errstate(all='raise'): + # these should raise no fp warnings + _ufuncs.eval_legendre(1, 0) + _ufuncs.eval_laguerre(1, 1) + _ufuncs.eval_gegenbauer(1, 1, 0) + + +class TestPolys: + """ + Check that the eval_* functions agree with the constructed polynomials + + """ + + def check_poly(self, func, cls, param_ranges=(), x_range=(), nn=10, + nparam=10, nx=10, rtol=1e-8): + rng = np.random.RandomState(1234) + + dataset = [] + for n in np.arange(nn): + params = [a + (b-a)*rng.rand(nparam) for a,b in param_ranges] + params = np.asarray(params).T + if not param_ranges: + params = [0] + for p in params: + if param_ranges: + p = (n,) + tuple(p) + else: + p = (n,) + x = x_range[0] + (x_range[1] - x_range[0])*rng.rand(nx) + x[0] = x_range[0] # always include domain start point + x[1] = x_range[1] # always include domain end point + poly = np.poly1d(cls(*p).coef) + z = np.c_[np.tile(p, (nx,1)), x, poly(x)] + dataset.append(z) + + dataset = np.concatenate(dataset, axis=0) + + def polyfunc(*p): + p = (p[0].astype(np.dtype("long")),) + p[1:] + return func(*p) + + with np.errstate(all='raise'): + ds = FuncData(polyfunc, dataset, list(range(len(param_ranges)+2)), -1, + rtol=rtol) + ds.check() + + def test_jacobi(self): + self.check_poly(_ufuncs.eval_jacobi, orth.jacobi, + param_ranges=[(-0.99, 10), (-0.99, 10)], + x_range=[-1, 1], rtol=1e-5) + + def test_sh_jacobi(self): + self.check_poly(_ufuncs.eval_sh_jacobi, orth.sh_jacobi, + param_ranges=[(1, 10), (0, 1)], x_range=[0, 1], + rtol=1e-5) + + def test_gegenbauer(self): + self.check_poly(_ufuncs.eval_gegenbauer, orth.gegenbauer, + param_ranges=[(-0.499, 10)], x_range=[-1, 1], + rtol=1e-7) + + def test_chebyt(self): + self.check_poly(_ufuncs.eval_chebyt, orth.chebyt, + param_ranges=[], x_range=[-1, 1]) + + def test_chebyu(self): + self.check_poly(_ufuncs.eval_chebyu, orth.chebyu, + param_ranges=[], x_range=[-1, 1]) + + def test_chebys(self): + self.check_poly(_ufuncs.eval_chebys, orth.chebys, + param_ranges=[], x_range=[-2, 2]) + + def test_chebyc(self): + self.check_poly(_ufuncs.eval_chebyc, orth.chebyc, + param_ranges=[], x_range=[-2, 2]) + + def test_sh_chebyt(self): + with np.errstate(all='ignore'): + self.check_poly(_ufuncs.eval_sh_chebyt, orth.sh_chebyt, + param_ranges=[], x_range=[0, 1]) + + def test_sh_chebyu(self): + self.check_poly(_ufuncs.eval_sh_chebyu, orth.sh_chebyu, + param_ranges=[], x_range=[0, 1]) + + def test_legendre(self): + self.check_poly(_ufuncs.eval_legendre, orth.legendre, + param_ranges=[], x_range=[-1, 1]) + + def test_sh_legendre(self): + with np.errstate(all='ignore'): + self.check_poly(_ufuncs.eval_sh_legendre, orth.sh_legendre, + param_ranges=[], x_range=[0, 1]) + + def test_genlaguerre(self): + self.check_poly(_ufuncs.eval_genlaguerre, orth.genlaguerre, + param_ranges=[(-0.99, 10)], x_range=[0, 100]) + + def test_laguerre(self): + self.check_poly(_ufuncs.eval_laguerre, orth.laguerre, + param_ranges=[], x_range=[0, 100]) + + def test_hermite(self): + self.check_poly(_ufuncs.eval_hermite, orth.hermite, + param_ranges=[], x_range=[-100, 100]) + + def test_hermitenorm(self): + self.check_poly(_ufuncs.eval_hermitenorm, orth.hermitenorm, + param_ranges=[], x_range=[-100, 100]) + + +class TestRecurrence: + """ + Check that the eval_* functions sig='ld->d' and 'dd->d' agree. + + """ + + def check_poly(self, func, param_ranges=(), x_range=(), nn=10, + nparam=10, nx=10, rtol=1e-8): + rng = np.random.default_rng(1234) + + dataset = [] + for n in np.arange(nn): + params = [a + (b-a)*rng.random(nparam) for a,b in param_ranges] + params = np.asarray(params).T + if not param_ranges: + params = [0] + for p in params: + if param_ranges: + p = (n,) + tuple(p) + else: + p = (n,) + x = x_range[0] + (x_range[1] - x_range[0])*rng.random(nx) + x[0] = x_range[0] # always include domain start point + x[1] = x_range[1] # always include domain end point + kw = dict(sig=(len(p)+1)*'d'+'->d') + z = np.c_[np.tile(p, (nx,1)), x, func(*(p + (x,)), **kw)] + dataset.append(z) + + dataset = np.concatenate(dataset, axis=0) + + def polyfunc(*p): + p0 = p[0].astype(np.intp) + p = (p0,) + p[1:] + p0_type_char = p0.dtype.char + kw = dict(sig=p0_type_char + (len(p)-1)*'d' + '->d') + return func(*p, **kw) + + with np.errstate(all='raise'): + ds = FuncData(polyfunc, dataset, list(range(len(param_ranges)+2)), -1, + rtol=rtol) + ds.check() + + def test_jacobi(self): + self.check_poly(_ufuncs.eval_jacobi, + param_ranges=[(-0.99, 10), (-0.99, 10)], + x_range=[-1, 1]) + + def test_sh_jacobi(self): + self.check_poly(_ufuncs.eval_sh_jacobi, + param_ranges=[(1, 10), (0, 1)], x_range=[0, 1]) + + def test_gegenbauer(self): + self.check_poly(_ufuncs.eval_gegenbauer, + param_ranges=[(-0.499, 10)], x_range=[-1, 1]) + + def test_chebyt(self): + self.check_poly(_ufuncs.eval_chebyt, + param_ranges=[], x_range=[-1, 1]) + + def test_chebyu(self): + self.check_poly(_ufuncs.eval_chebyu, + param_ranges=[], x_range=[-1, 1]) + + def test_chebys(self): + self.check_poly(_ufuncs.eval_chebys, + param_ranges=[], x_range=[-2, 2]) + + def test_chebyc(self): + self.check_poly(_ufuncs.eval_chebyc, + param_ranges=[], x_range=[-2, 2]) + + def test_sh_chebyt(self): + self.check_poly(_ufuncs.eval_sh_chebyt, + param_ranges=[], x_range=[0, 1]) + + def test_sh_chebyu(self): + self.check_poly(_ufuncs.eval_sh_chebyu, + param_ranges=[], x_range=[0, 1]) + + def test_legendre(self): + self.check_poly(_ufuncs.eval_legendre, + param_ranges=[], x_range=[-1, 1]) + + def test_sh_legendre(self): + self.check_poly(_ufuncs.eval_sh_legendre, + param_ranges=[], x_range=[0, 1]) + + def test_genlaguerre(self): + self.check_poly(_ufuncs.eval_genlaguerre, + param_ranges=[(-0.99, 10)], x_range=[0, 100]) + + def test_laguerre(self): + self.check_poly(_ufuncs.eval_laguerre, + param_ranges=[], x_range=[0, 100]) + + def test_hermite(self): + v = _ufuncs.eval_hermite(70, 1.0) + a = -1.457076485701412e60 + assert_allclose(v, a) + + +def test_hermite_domain(): + # Regression test for gh-11091. + assert np.isnan(_ufuncs.eval_hermite(-1, 1.0)) + assert np.isnan(_ufuncs.eval_hermitenorm(-1, 1.0)) + + +@pytest.mark.parametrize("n", [0, 1, 2]) +@pytest.mark.parametrize("x", [0, 1, np.nan]) +def test_hermite_nan(n, x): + # Regression test for gh-11369. + assert np.isnan(_ufuncs.eval_hermite(n, x)) == np.any(np.isnan([n, x])) + assert np.isnan(_ufuncs.eval_hermitenorm(n, x)) == np.any(np.isnan([n, x])) + + +@pytest.mark.parametrize('n', [0, 1, 2, 3.2]) +@pytest.mark.parametrize('alpha', [1, np.nan]) +@pytest.mark.parametrize('x', [2, np.nan]) +def test_genlaguerre_nan(n, alpha, x): + # Regression test for gh-11361. + nan_laguerre = np.isnan(_ufuncs.eval_genlaguerre(n, alpha, x)) + nan_arg = np.any(np.isnan([n, alpha, x])) + assert nan_laguerre == nan_arg + + +@pytest.mark.parametrize('n', [0, 1, 2, 3.2]) +@pytest.mark.parametrize('alpha', [0.0, 1, np.nan]) +@pytest.mark.parametrize('x', [1e-6, 2, np.nan]) +def test_gegenbauer_nan(n, alpha, x): + # Regression test for gh-11370. + nan_gegenbauer = np.isnan(_ufuncs.eval_gegenbauer(n, alpha, x)) + nan_arg = np.any(np.isnan([n, alpha, x])) + assert nan_gegenbauer == nan_arg + +@pytest.mark.parametrize("n", np.arange(10)) +@pytest.mark.parametrize("alpha", [-0.45, -0.25, -0.125, 0.0, 0.25, 1.0, 2.0]) +@pytest.mark.parametrize("x", [-np.inf, np.inf]) +def test_gegenbauer_infinity(n, alpha, x): + # gh-11713 - check correct handling of x = +inf and x = -inf + if alpha == 0.0: + expected = 0.0 + elif n == 0.0: + expected = 1.0 + else: + # sign of leading coefficient: 2^n * Gamma(n+alpha) / (n! Gamma(alpha)) + lead_sign = gammasgn(n + alpha) * gammasgn(alpha) + expected = lead_sign * (np.sign(x) ** n) * np.inf + assert_allclose(_ufuncs.eval_gegenbauer(int(n), alpha, x), expected, rtol=1e-10) + assert_allclose(_ufuncs.eval_gegenbauer(float(n), alpha, x), expected, rtol=1e-10) + +@pytest.mark.parametrize( + "n, expected", + [ + (0, [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]), + (1, [-2.0, -1.8, -1.6, -1.4, -1.2, -1.0, -0.8, -0.6, -0.4, -0.2, 0.0]), + (2, [3.0, 2.16, 1.44, 0.84, 0.36, 0.0, -0.24, -0.36, -0.36, -0.24, 0.0]), + (3, [-4.0, -1.98, -0.64, 0.14, 0.48, 0.5, 0.32, 0.06, -0.16, -0.22, 0.0]), + ( + 4, + [5.0, 1.332, -0.288, -0.658, -0.408, 0.0, 0.272, 0.282, 0.072, -0.148, 0.0], + ), + ( + 5, + [ + -6.0, + -0.43308, + 0.79104, + 0.36876, + -0.21312, + -0.375, + -0.14208, + 0.15804, + 0.19776, + -0.04812, + 0.0, + ], + ), + ], +) +def test_jacobi_alpha_minus_one_beta_plus_one(n, expected): + # gh-7001 - expected values were computed with mathematica + x = np.linspace(-1.0, 1.0, 11) + a, b = -1, 1 # alpha, beta + assert_allclose(_ufuncs.eval_jacobi(n, a, b, x), expected, rtol=1e-10, atol=1e-14) + assert_allclose( + _ufuncs.eval_jacobi(float(n), a, b, x), expected, rtol=1e-10, atol=1e-14 + ) + + +@pytest.mark.parametrize( + "n, expected", + [ + (0, [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]), + (1, [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]), + (2, [0.0, -0.09, -0.16, -0.21, -0.24, -0.25, -0.24, -0.21, -0.16, -0.09, 0.0]), + ( + 3, + [0.0, 0.144, 0.192, 0.168, 0.096, 0.0, -0.096, -0.168, -0.192, -0.144, 0.0], + ), + ( + 4, + [ + 0.0, + -0.1485, + -0.096, + 0.0315, + 0.144, + 0.1875, + 0.144, + 0.0315, + -0.096, + -0.1485, + 0.0, + ], + ), + ( + 5, + [ + 0.0, + 0.10656, + -0.04608, + -0.15792, + -0.13056, + 0.0, + 0.13056, + 0.15792, + 0.04608, + -0.10656, + 0.0, + ], + ), + ], +) +def test_jacobi_alpha_minus_one_beta_minus_one(n, expected): + # gh-7001 - expected values were computed with mathematica + x = np.linspace(-1.0, 1.0, 11) + a, b = -1, -1 # alpha, beta + assert_allclose(_ufuncs.eval_jacobi(n, a, b, x), expected, rtol=1e-10, atol=1e-14) + assert_allclose( + _ufuncs.eval_jacobi(float(n), a, b, x), expected, rtol=1e-10, atol=1e-14 + ) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_owens_t.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_owens_t.py new file mode 100644 index 0000000000000000000000000000000000000000..8d15aead25302023c5f07d8392c0931995764ced --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_owens_t.py @@ -0,0 +1,53 @@ +import numpy as np +from numpy.testing import assert_equal, assert_allclose + +import scipy.special as sc + + +def test_symmetries(): + np.random.seed(1234) + a, h = np.random.rand(100), np.random.rand(100) + assert_equal(sc.owens_t(h, a), sc.owens_t(-h, a)) + assert_equal(sc.owens_t(h, a), -sc.owens_t(h, -a)) + + +def test_special_cases(): + assert_equal(sc.owens_t(5, 0), 0) + assert_allclose(sc.owens_t(0, 5), 0.5*np.arctan(5)/np.pi, + rtol=5e-14) + # Target value is 0.5*Phi(5)*(1 - Phi(5)) for Phi the CDF of the + # standard normal distribution + assert_allclose(sc.owens_t(5, 1), 1.4332574485503512543e-07, + rtol=5e-14) + + +def test_nans(): + assert_equal(sc.owens_t(20, np.nan), np.nan) + assert_equal(sc.owens_t(np.nan, 20), np.nan) + assert_equal(sc.owens_t(np.nan, np.nan), np.nan) + + +def test_infs(): + h, a = 0, np.inf + # T(0, a) = 1/2π * arctan(a) + res = 1/(2*np.pi) * np.arctan(a) + assert_allclose(sc.owens_t(h, a), res, rtol=5e-14) + assert_allclose(sc.owens_t(h, -a), -res, rtol=5e-14) + + h = 1 + # Refer Owens T function definition in Wikipedia + # https://en.wikipedia.org/wiki/Owen%27s_T_function + # Value approximated through Numerical Integration + # using scipy.integrate.quad + # quad(lambda x: 1/(2*pi)*(exp(-0.5*(1*1)*(1+x*x))/(1+x*x)), 0, inf) + res = 0.07932762696572854 + assert_allclose(sc.owens_t(h, np.inf), res, rtol=5e-14) + assert_allclose(sc.owens_t(h, -np.inf), -res, rtol=5e-14) + + assert_equal(sc.owens_t(np.inf, 1), 0) + assert_equal(sc.owens_t(-np.inf, 1), 0) + + assert_equal(sc.owens_t(np.inf, np.inf), 0) + assert_equal(sc.owens_t(-np.inf, np.inf), 0) + assert_equal(sc.owens_t(np.inf, -np.inf), -0.0) + assert_equal(sc.owens_t(-np.inf, -np.inf), -0.0) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_pcf.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_pcf.py new file mode 100644 index 0000000000000000000000000000000000000000..a8c42aa688081fb58f79ad2c8ea932d03b33523b --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_pcf.py @@ -0,0 +1,24 @@ +"""Tests for parabolic cylinder functions. + +""" +import numpy as np +from numpy.testing import assert_allclose, assert_equal +import scipy.special as sc + + +def test_pbwa_segfault(): + # Regression test for https://github.com/scipy/scipy/issues/6208. + # + # Data generated by mpmath. + # + w = 1.02276567211316867161 + wp = -0.48887053372346189882 + assert_allclose(sc.pbwa(0, 0), (w, wp), rtol=1e-13, atol=0) + + +def test_pbwa_nan(): + # Check that NaN's are returned outside of the range in which the + # implementation is accurate. + pts = [(-6, -6), (-6, 6), (6, -6), (6, 6)] + for p in pts: + assert_equal(sc.pbwa(*p), (np.nan, np.nan)) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_pdtr.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_pdtr.py new file mode 100644 index 0000000000000000000000000000000000000000..f2159e6bd3acd60f4bdc98fc9ce0f6d44ac76485 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_pdtr.py @@ -0,0 +1,48 @@ +import numpy as np +import scipy.special as sc +from numpy.testing import assert_allclose, assert_array_equal + + +class TestPdtr: + def test(self): + val = sc.pdtr(0, 1) + assert_allclose(val, np.exp(-1), atol=1.5e-7, rtol=0) + + def test_m_zero(self): + val = sc.pdtr([0, 1, 2], 0) + assert_allclose(val, [1, 1, 1], atol=1.5e-7, rtol=0) + + def test_rounding(self): + double_val = sc.pdtr([0.1, 1.1, 2.1], 1.0) + int_val = sc.pdtr([0, 1, 2], 1.0) + assert_array_equal(double_val, int_val) + + def test_inf(self): + val = sc.pdtr(np.inf, 1.0) + assert_allclose(val, 1.0, atol=1.5e-7, rtol=0) + + def test_domain(self): + val = sc.pdtr(-1.1, 1.0) + assert np.isnan(val) + +class TestPdtrc: + def test_value(self): + val = sc.pdtrc(0, 1) + assert_allclose(val, 1 - np.exp(-1), atol=1.5e-7, rtol=0) + + def test_m_zero(self): + val = sc.pdtrc([0, 1, 2], 0.0) + assert_array_equal(val, [0, 0, 0]) + + def test_rounding(self): + double_val = sc.pdtrc([0.1, 1.1, 2.1], 1.0) + int_val = sc.pdtrc([0, 1, 2], 1.0) + assert_array_equal(double_val, int_val) + + def test_inf(self): + val = sc.pdtrc(np.inf, 1.0) + assert_allclose(val, 0.0, atol=1.5e-7, rtol=0) + + def test_domain(self): + val = sc.pdtrc(-1.1, 1.0) + assert np.isnan(val) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_powm1.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_powm1.py new file mode 100644 index 0000000000000000000000000000000000000000..3d809963f64ddaedf6b59de80dcd5f7ca8fa18a9 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_powm1.py @@ -0,0 +1,65 @@ +import pytest +import numpy as np +from numpy.testing import assert_allclose +from scipy.special import powm1 + + +# Expected values were computed with mpmath, e.g. +# +# >>> import mpmath +# >>> mpmath.np.dps = 200 +# >>> print(float(mpmath.powm1(2.0, 1e-7)) +# 6.931472045825965e-08 +# +powm1_test_cases = [ + (1.25, 0.75, 0.18217701125396976, 1e-15), + (2.0, 1e-7, 6.931472045825965e-08, 1e-15), + (25.0, 5e-11, 1.6094379125636148e-10, 1e-15), + (0.99996, 0.75, -3.0000150002530058e-05, 1e-15), + (0.9999999999990905, 20, -1.81898940353014e-11, 1e-15), + (-1.25, 751.0, -6.017550852453444e+72, 2e-15) +] + + +@pytest.mark.parametrize('x, y, expected, rtol', powm1_test_cases) +def test_powm1(x, y, expected, rtol): + p = powm1(x, y) + assert_allclose(p, expected, rtol=rtol) + + +@pytest.mark.parametrize('x, y, expected', + [(0.0, 0.0, 0.0), + (0.0, -1.5, np.inf), + (0.0, 1.75, -1.0), + (-1.5, 2.0, 1.25), + (-1.5, 3.0, -4.375), + (np.nan, 0.0, 0.0), + (1.0, np.nan, 0.0), + (1.0, np.inf, 0.0), + (1.0, -np.inf, 0.0), + (np.inf, 7.5, np.inf), + (np.inf, -7.5, -1.0), + (3.25, np.inf, np.inf), + (np.inf, np.inf, np.inf), + (np.inf, -np.inf, -1.0), + (np.inf, 0.0, 0.0), + (-np.inf, 0.0, 0.0), + (-np.inf, 2.0, np.inf), + (-np.inf, 3.0, -np.inf), + (-1.0, float(2**53 - 1), -2.0)]) +def test_powm1_exact_cases(x, y, expected): + # Test cases where we have an exact expected value. + p = powm1(x, y) + assert p == expected + + +@pytest.mark.parametrize('x, y', + [(-1.25, 751.03), + (-1.25, np.inf), + (np.nan, np.nan), + (-np.inf, -np.inf), + (-np.inf, 2.5)]) +def test_powm1_return_nan(x, y): + # Test cases where the expected return value is nan. + p = powm1(x, y) + assert np.isnan(p) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_precompute_expn_asy.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_precompute_expn_asy.py new file mode 100644 index 0000000000000000000000000000000000000000..7b6c6cba21d5c1d5fdfab879153ba8f125e98d5f --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_precompute_expn_asy.py @@ -0,0 +1,24 @@ +from numpy.testing import assert_equal + +from scipy.special._testutils import check_version, MissingModule +from scipy.special._precompute.expn_asy import generate_A + +try: + import sympy + from sympy import Poly +except ImportError: + sympy = MissingModule("sympy") + + +@check_version(sympy, "1.0") +def test_generate_A(): + # Data from DLMF 8.20.5 + x = sympy.symbols('x') + Astd = [Poly(1, x), + Poly(1, x), + Poly(1 - 2*x), + Poly(1 - 8*x + 6*x**2)] + Ares = generate_A(len(Astd)) + + for p, q in zip(Astd, Ares): + assert_equal(p, q) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_precompute_gammainc.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_precompute_gammainc.py new file mode 100644 index 0000000000000000000000000000000000000000..86278b065a58bdde3fcde32a2f7ec0d53d17077d --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_precompute_gammainc.py @@ -0,0 +1,112 @@ +import pytest + +from scipy.special._testutils import MissingModule, check_version +from scipy.special._mptestutils import ( + Arg, IntArg, mp_assert_allclose, assert_mpmath_equal) +from scipy.special._precompute.gammainc_asy import ( + compute_g, compute_alpha, compute_d) +from scipy.special._precompute.gammainc_data import gammainc, gammaincc + +try: + import sympy +except ImportError: + sympy = MissingModule('sympy') + +try: + import mpmath as mp +except ImportError: + mp = MissingModule('mpmath') + + +pytestmark = pytest.mark.thread_unsafe( + reason=("mpmath gmpy2 backend is not thread-safe, " + "see https://github.com/mpmath/mpmath/issues/974")) + +@check_version(mp, '0.19') +def test_g(): + # Test data for the g_k. See DLMF 5.11.4. + with mp.workdps(30): + g = [mp.mpf(1), mp.mpf(1)/12, mp.mpf(1)/288, + -mp.mpf(139)/51840, -mp.mpf(571)/2488320, + mp.mpf(163879)/209018880, mp.mpf(5246819)/75246796800] + mp_assert_allclose(compute_g(7), g) + + +@pytest.mark.slow +@check_version(mp, '0.19') +@check_version(sympy, '0.7') +@pytest.mark.xfail_on_32bit("rtol only 2e-11, see gh-6938") +def test_alpha(): + # Test data for the alpha_k. See DLMF 8.12.14. + with mp.workdps(30): + alpha = [mp.mpf(0), mp.mpf(1), mp.mpf(1)/3, mp.mpf(1)/36, + -mp.mpf(1)/270, mp.mpf(1)/4320, mp.mpf(1)/17010, + -mp.mpf(139)/5443200, mp.mpf(1)/204120] + mp_assert_allclose(compute_alpha(9), alpha) + + +@pytest.mark.xslow +@check_version(mp, '0.19') +@check_version(sympy, '0.7') +def test_d(): + # Compare the d_{k, n} to the results in appendix F of [1]. + # + # Sources + # ------- + # [1] DiDonato and Morris, Computation of the Incomplete Gamma + # Function Ratios and their Inverse, ACM Transactions on + # Mathematical Software, 1986. + + with mp.workdps(50): + dataset = [(0, 0, -mp.mpf('0.333333333333333333333333333333')), + (0, 12, mp.mpf('0.102618097842403080425739573227e-7')), + (1, 0, -mp.mpf('0.185185185185185185185185185185e-2')), + (1, 12, mp.mpf('0.119516285997781473243076536700e-7')), + (2, 0, mp.mpf('0.413359788359788359788359788360e-2')), + (2, 12, -mp.mpf('0.140925299108675210532930244154e-7')), + (3, 0, mp.mpf('0.649434156378600823045267489712e-3')), + (3, 12, -mp.mpf('0.191111684859736540606728140873e-7')), + (4, 0, -mp.mpf('0.861888290916711698604702719929e-3')), + (4, 12, mp.mpf('0.288658297427087836297341274604e-7')), + (5, 0, -mp.mpf('0.336798553366358150308767592718e-3')), + (5, 12, mp.mpf('0.482409670378941807563762631739e-7')), + (6, 0, mp.mpf('0.531307936463992223165748542978e-3')), + (6, 12, -mp.mpf('0.882860074633048352505085243179e-7')), + (7, 0, mp.mpf('0.344367606892377671254279625109e-3')), + (7, 12, -mp.mpf('0.175629733590604619378669693914e-6')), + (8, 0, -mp.mpf('0.652623918595309418922034919727e-3')), + (8, 12, mp.mpf('0.377358774161109793380344937299e-6')), + (9, 0, -mp.mpf('0.596761290192746250124390067179e-3')), + (9, 12, mp.mpf('0.870823417786464116761231237189e-6'))] + d = compute_d(10, 13) + res = [d[k][n] for k, n, std in dataset] + std = [x[2] for x in dataset] + mp_assert_allclose(res, std) + + +@check_version(mp, '0.19') +def test_gammainc(): + # Quick check that the gammainc in + # special._precompute.gammainc_data agrees with mpmath's + # gammainc. + assert_mpmath_equal(gammainc, + lambda a, x: mp.gammainc(a, b=x, regularized=True), + [Arg(0, 100, inclusive_a=False), Arg(0, 100)], + nan_ok=False, rtol=1e-17, n=50, dps=50) + + +@pytest.mark.xslow +@check_version(mp, '0.19') +def test_gammaincc(): + # Check that the gammaincc in special._precompute.gammainc_data + # agrees with mpmath's gammainc. + assert_mpmath_equal(lambda a, x: gammaincc(a, x, dps=1000), + lambda a, x: mp.gammainc(a, a=x, regularized=True), + [Arg(20, 100), Arg(20, 100)], + nan_ok=False, rtol=1e-17, n=50, dps=1000) + + # Test the fast integer path + assert_mpmath_equal(gammaincc, + lambda a, x: mp.gammainc(a, a=x, regularized=True), + [IntArg(1, 100), Arg(0, 100)], + nan_ok=False, rtol=1e-17, n=50, dps=50) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_precompute_utils.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_precompute_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..89616b92329691ca76039fe11a7e08f7f3db1150 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_precompute_utils.py @@ -0,0 +1,36 @@ +import pytest + +from scipy.special._testutils import MissingModule, check_version +from scipy.special._mptestutils import mp_assert_allclose +from scipy.special._precompute.utils import lagrange_inversion + +try: + import sympy +except ImportError: + sympy = MissingModule('sympy') + +try: + import mpmath as mp +except ImportError: + mp = MissingModule('mpmath') + + +@pytest.mark.slow +@check_version(sympy, '0.7') +@check_version(mp, '0.19') +class TestInversion: + @pytest.mark.xfail_on_32bit("rtol only 2e-9, see gh-6938") + def test_log(self): + with mp.workdps(30): + logcoeffs = mp.taylor(lambda x: mp.log(1 + x), 0, 10) + expcoeffs = mp.taylor(lambda x: mp.exp(x) - 1, 0, 10) + invlogcoeffs = lagrange_inversion(logcoeffs) + mp_assert_allclose(invlogcoeffs, expcoeffs) + + @pytest.mark.xfail_on_32bit("rtol only 1e-15, see gh-6938") + def test_sin(self): + with mp.workdps(30): + sincoeffs = mp.taylor(mp.sin, 0, 10) + asincoeffs = mp.taylor(mp.asin, 0, 10) + invsincoeffs = lagrange_inversion(sincoeffs) + mp_assert_allclose(invsincoeffs, asincoeffs, atol=1e-30) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_round.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_round.py new file mode 100644 index 0000000000000000000000000000000000000000..ba28b7c1cff95327df0ed69086dd92b93399c3c4 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_round.py @@ -0,0 +1,18 @@ +import numpy as np +import pytest + +from scipy.special import _test_internal + + +@pytest.mark.fail_slow(20) +@pytest.mark.skipif(not _test_internal.have_fenv(), reason="no fenv()") +def test_add_round_up(): + rng = np.random.RandomState(1234) + _test_internal.test_add_round(10**5, 'up', rng) + + +@pytest.mark.fail_slow(20) +@pytest.mark.skipif(not _test_internal.have_fenv(), reason="no fenv()") +def test_add_round_down(): + rng = np.random.RandomState(1234) + _test_internal.test_add_round(10**5, 'down', rng) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_sf_error.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_sf_error.py new file mode 100644 index 0000000000000000000000000000000000000000..337bd0c65ebd712bfda1385072c15a47e8a495c2 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_sf_error.py @@ -0,0 +1,153 @@ +import sys +import warnings + +import numpy as np +from numpy.testing import assert_, assert_equal, HAS_REFCOUNT +import pytest +from pytest import raises as assert_raises + +import scipy.special as sc +from scipy.special._ufuncs import _sf_error_test_function + +_sf_error_code_map = { + # skip 'ok' + 'singular': 1, + 'underflow': 2, + 'overflow': 3, + 'slow': 4, + 'loss': 5, + 'no_result': 6, + 'domain': 7, + 'arg': 8, + 'other': 9, + 'memory': 10, +} + +_sf_error_actions = [ + 'ignore', + 'warn', + 'raise' +] + + +def _check_action(fun, args, action): + # TODO: special expert should correct + # the coercion at the true location? + args = np.asarray(args, dtype=np.dtype("long")) + if action == 'warn': + with pytest.warns(sc.SpecialFunctionWarning): + fun(*args) + elif action == 'raise': + with assert_raises(sc.SpecialFunctionError): + fun(*args) + else: + # action == 'ignore', make sure there are no warnings/exceptions + with warnings.catch_warnings(): + warnings.simplefilter("error") + fun(*args) + + +def test_geterr(): + err = sc.geterr() + for key, value in err.items(): + assert_(key in _sf_error_code_map) + assert_(value in _sf_error_actions) + + +def test_seterr(): + entry_err = sc.geterr() + try: + for category, error_code in _sf_error_code_map.items(): + for action in _sf_error_actions: + geterr_olderr = sc.geterr() + seterr_olderr = sc.seterr(**{category: action}) + assert_(geterr_olderr == seterr_olderr) + newerr = sc.geterr() + assert_(newerr[category] == action) + geterr_olderr.pop(category) + newerr.pop(category) + assert_(geterr_olderr == newerr) + _check_action(_sf_error_test_function, (error_code,), action) + finally: + sc.seterr(**entry_err) + + +@pytest.mark.thread_unsafe(reason="module refcounts are not stable in multiple threads") +@pytest.mark.skipif(not HAS_REFCOUNT, reason="Python lacks refcounts") +def test_sf_error_special_refcount(): + # Regression test for gh-16233. + # Check that the reference count of scipy.special is not increased + # when a SpecialFunctionError is raised. + refcount_before = sys.getrefcount(sc) + with sc.errstate(all='raise'): + with pytest.raises(sc.SpecialFunctionError, match='domain error'): + sc.ndtri(2.0) + refcount_after = sys.getrefcount(sc) + assert refcount_after == refcount_before + + +def test_errstate_pyx_basic(): + olderr = sc.geterr() + with sc.errstate(singular='raise'): + with assert_raises(sc.SpecialFunctionError): + sc.loggamma(0) + assert_equal(olderr, sc.geterr()) + + +def test_errstate_c_basic(): + olderr = sc.geterr() + with sc.errstate(domain='raise'): + with assert_raises(sc.SpecialFunctionError): + sc.spence(-1) + assert_equal(olderr, sc.geterr()) + + +def test_errstate_cpp_basic(): + olderr = sc.geterr() + with sc.errstate(underflow='raise'): + with assert_raises(sc.SpecialFunctionError): + sc.wrightomega(-1000) + assert_equal(olderr, sc.geterr()) + + +def test_errstate_cpp_scipy_special(): + olderr = sc.geterr() + with sc.errstate(singular='raise'): + with assert_raises(sc.SpecialFunctionError): + sc.lambertw(0, 1) + assert_equal(olderr, sc.geterr()) + + +def test_errstate_cpp_alt_ufunc_machinery(): + olderr = sc.geterr() + with sc.errstate(singular='raise'): + with assert_raises(sc.SpecialFunctionError): + sc.gammaln(0) + assert_equal(olderr, sc.geterr()) + + +def test_errstate(): + for category, error_code in _sf_error_code_map.items(): + for action in _sf_error_actions: + olderr = sc.geterr() + with sc.errstate(**{category: action}): + _check_action(_sf_error_test_function, (error_code,), action) + assert_equal(olderr, sc.geterr()) + + +def test_errstate_all_but_one(): + olderr = sc.geterr() + with sc.errstate(all='raise', singular='ignore'): + sc.gammaln(0) + with assert_raises(sc.SpecialFunctionError): + sc.spence(-1.0) + assert_equal(olderr, sc.geterr()) + + +def test_check_overflow_message(): + # Regression test for a bug where the overflow and underflow + # messages were switched. + with pytest.raises(sc.SpecialFunctionError, match="overflow"): + with sc.errstate(all='raise'): + # This should trigger an overflow: + sc.yn(3, 1e-105) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_sici.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_sici.py new file mode 100644 index 0000000000000000000000000000000000000000..d33c1795641ba74f777fcfdcffe80d86463477e3 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_sici.py @@ -0,0 +1,36 @@ +import numpy as np + +import scipy.special as sc +from scipy.special._testutils import FuncData + + +def test_sici_consistency(): + # Make sure the implementation of sici for real arguments agrees + # with the implementation of sici for complex arguments. + + # On the negative real axis Cephes drops the imaginary part in ci + def sici(x): + si, ci = sc.sici(x + 0j) + return si.real, ci.real + + x = np.r_[-np.logspace(8, -30, 200), 0, np.logspace(-30, 8, 200)] + si, ci = sc.sici(x) + dataset = np.column_stack((x, si, ci)) + FuncData(sici, dataset, 0, (1, 2), rtol=1e-12).check() + + +def test_shichi_consistency(): + # Make sure the implementation of shichi for real arguments agrees + # with the implementation of shichi for complex arguments. + + # On the negative real axis Cephes drops the imaginary part in chi + def shichi(x): + shi, chi = sc.shichi(x + 0j) + return shi.real, chi.real + + # Overflow happens quickly, so limit range + x = np.r_[-np.logspace(np.log10(700), -30, 200), 0, + np.logspace(-30, np.log10(700), 200)] + shi, chi = sc.shichi(x) + dataset = np.column_stack((x, shi, chi)) + FuncData(shichi, dataset, 0, (1, 2), rtol=1e-14).check() diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_specfun.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_specfun.py new file mode 100644 index 0000000000000000000000000000000000000000..f36fd2915be8c8d14138493c34dfd5733a2a1e6d --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_specfun.py @@ -0,0 +1,48 @@ +""" +Various made-up tests to hit different branches of the code in specfun.c +""" + +import numpy as np +from numpy.testing import assert_allclose +from scipy import special + + +def test_cva2_cv0_branches(): + res, resp = special.mathieu_cem([40, 129], [13, 14], [30, 45]) + assert_allclose(res, np.array([-0.3741211, 0.74441928])) + assert_allclose(resp, np.array([-37.02872758, -86.13549877])) + + res, resp = special.mathieu_sem([40, 129], [13, 14], [30, 45]) + assert_allclose(res, np.array([0.92955551, 0.66771207])) + assert_allclose(resp, np.array([-14.91073448, 96.02954185])) + + +def test_chgm_branches(): + res = special.eval_genlaguerre(-3.2, 3, 2.5) + assert_allclose(res, -0.7077721935779854) + + +def test_hygfz_branches(): + """(z == 1.0) && (c-a-b > 0.0)""" + res = special.hyp2f1(1.5, 2.5, 4.5, 1.+0.j) + assert_allclose(res, 10.30835089459151+0j) + """(cabs(z+1) < eps) && (fabs(c-a+b - 1.0) < eps)""" + res = special.hyp2f1(5+5e-16, 2, 2, -1.0 + 5e-16j) + assert_allclose(res, 0.031249999999999986+3.9062499999999994e-17j) + + +def test_pro_rad1(): + # https://github.com/scipy/scipy/issues/21058 + # Reference values taken from WolframAlpha + # SpheroidalS1(1, 1, 30, 1.1) + # SpheroidalS1Prime(1, 1, 30, 1.1) + res = special.pro_rad1(1, 1, 30, 1.1) + assert_allclose(res, (0.009657872296166435, 3.253369651472877), rtol=2e-5) + +def test_pro_rad2(): + # https://github.com/scipy/scipy/issues/21461 + # Reference values taken from WolframAlpha + # SpheroidalS2(0, 0, 3, 1.02) + # SpheroidalS2Prime(0, 0, 3, 1.02) + res = special.pro_rad2(0, 0, 3, 1.02) + assert_allclose(res, (-0.35089596858528077, 13.652764213480872), rtol=10e-10) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_spence.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_spence.py new file mode 100644 index 0000000000000000000000000000000000000000..fbb26ac281dff81ea71b30318731065fe5a78f94 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_spence.py @@ -0,0 +1,32 @@ +import numpy as np +from numpy import sqrt, log, pi +from scipy.special._testutils import FuncData +from scipy.special import spence + + +def test_consistency(): + # Make sure the implementation of spence for real arguments + # agrees with the implementation of spence for imaginary arguments. + + x = np.logspace(-30, 300, 200) + dataset = np.vstack((x + 0j, spence(x))).T + FuncData(spence, dataset, 0, 1, rtol=1e-14).check() + + +def test_special_points(): + # Check against known values of Spence's function. + + phi = (1 + sqrt(5))/2 + dataset = [(1, 0), + (2, -pi**2/12), + (0.5, pi**2/12 - log(2)**2/2), + (0, pi**2/6), + (-1, pi**2/4 - 1j*pi*log(2)), + ((-1 + sqrt(5))/2, pi**2/15 - log(phi)**2), + ((3 - sqrt(5))/2, pi**2/10 - log(phi)**2), + (phi, -pi**2/15 + log(phi)**2/2), + # Corrected from Zagier, "The Dilogarithm Function" + ((3 + sqrt(5))/2, -pi**2/10 - log(phi)**2)] + + dataset = np.asarray(dataset) + FuncData(spence, dataset, 0, 1, rtol=1e-14).check() diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_spfun_stats.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_spfun_stats.py new file mode 100644 index 0000000000000000000000000000000000000000..7dd4164bbab3ba88765617148dc7ea488bf81c65 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_spfun_stats.py @@ -0,0 +1,61 @@ +import numpy as np +from numpy.testing import (assert_array_equal, assert_array_almost_equal_nulp, + assert_allclose) +from pytest import raises as assert_raises + +from scipy.special import gammaln, multigammaln # type:ignore[attr-defined] + + +class TestMultiGammaLn: + + def test1(self): + # A test of the identity + # Gamma_1(a) = Gamma(a) + np.random.seed(1234) + a = np.abs(np.random.randn()) + assert_array_equal(multigammaln(a, 1), gammaln(a)) + + def test2(self): + # A test of the identity + # Gamma_2(a) = sqrt(pi) * Gamma(a) * Gamma(a - 0.5) + a = np.array([2.5, 10.0]) + result = multigammaln(a, 2) + expected = np.log(np.sqrt(np.pi)) + gammaln(a) + gammaln(a - 0.5) + assert_allclose(result, expected, atol=1.5e-7, rtol=0) + + def test_bararg(self): + assert_raises(ValueError, multigammaln, 0.5, 1.2) + + +def _check_multigammaln_array_result(a, d): + # Test that the shape of the array returned by multigammaln + # matches the input shape, and that all the values match + # the value computed when multigammaln is called with a scalar. + result = multigammaln(a, d) + assert_array_equal(a.shape, result.shape) + a1 = a.ravel() + result1 = result.ravel() + for i in range(a.size): + assert_array_almost_equal_nulp(result1[i], multigammaln(a1[i], d)) + + +def test_multigammaln_array_arg(): + # Check that the array returned by multigammaln has the correct + # shape and contains the correct values. The cases have arrays + # with several different shapes. + # The cases include a regression test for ticket #1849 + # (a = np.array([2.0]), an array with a single element). + np.random.seed(1234) + + cases = [ + # a, d + (np.abs(np.random.randn(3, 2)) + 5, 5), + (np.abs(np.random.randn(1, 2)) + 5, 5), + (np.arange(10.0, 18.0).reshape(2, 2, 2), 3), + (np.array([2.0]), 3), + (np.float64(2.0), 3), + ] + + for a, d in cases: + _check_multigammaln_array_result(a, d) + diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_sph_harm.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_sph_harm.py new file mode 100644 index 0000000000000000000000000000000000000000..117f0938379082125f97d738d27364f7821eaefc --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_sph_harm.py @@ -0,0 +1,48 @@ +import numpy as np +import pytest + +from numpy.testing import assert_allclose +import scipy.special as sc + +class TestSphHarm: + @pytest.mark.slow + def test_p(self): + m_max = 20 + n_max = 10 + + theta = np.linspace(0, np.pi) + phi = np.linspace(0, 2*np.pi) + theta, phi = np.meshgrid(theta, phi) + + y, y_jac, y_hess = sc.sph_harm_y_all(n_max, m_max, theta, phi, diff_n=2) + p, p_jac, p_hess = sc.sph_legendre_p_all(n_max, m_max, theta, diff_n=2) + + m = np.concatenate([np.arange(m_max + 1), np.arange(-m_max, 0)]) + m = np.expand_dims(m, axis=(0,)+tuple(range(2,theta.ndim+2))) + + assert_allclose(y, p * np.exp(1j * m * phi)) + + assert_allclose(y_jac[..., 0], p_jac * np.exp(1j * m * phi)) + assert_allclose(y_jac[..., 1], 1j * m * p * np.exp(1j * m * phi)) + + assert_allclose(y_hess[..., 0, 0], p_hess * np.exp(1j * m * phi)) + assert_allclose(y_hess[..., 0, 1], 1j * m * p_jac * np.exp(1j * m * phi)) + assert_allclose(y_hess[..., 1, 0], y_hess[..., 0, 1]) + assert_allclose(y_hess[..., 1, 1], -m * m * p * np.exp(1j * m * phi)) + + @pytest.mark.parametrize("n_max", [7, 10, 50]) + @pytest.mark.parametrize("m_max", [1, 4, 5, 9, 14]) + def test_all(self, n_max, m_max): + theta = np.linspace(0, np.pi) + phi = np.linspace(0, 2 * np.pi) + + n = np.arange(n_max + 1) + n = np.expand_dims(n, axis=tuple(range(1,theta.ndim+2))) + + m = np.concatenate([np.arange(m_max + 1), np.arange(-m_max, 0)]) + m = np.expand_dims(m, axis=(0,)+tuple(range(2,theta.ndim+2))) + + y_actual = sc.sph_harm_y_all(n_max, m_max, theta, phi) + y_desired = sc.sph_harm_y(n, m, theta, phi) + + np.testing.assert_allclose(y_actual, y_desired, rtol=1e-05) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_spherical_bessel.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_spherical_bessel.py new file mode 100644 index 0000000000000000000000000000000000000000..8444a4c16aa9e3b9ffc204f824a3f136a030f2be --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_spherical_bessel.py @@ -0,0 +1,407 @@ +# +# Tests of spherical Bessel functions. +# +import warnings + +import numpy as np +from numpy.testing import assert_allclose +import pytest +from numpy import sin, cos, sinh, cosh, exp, inf, nan, r_, pi + +from scipy.special import spherical_jn, spherical_yn, spherical_in, spherical_kn +from scipy.integrate import quad + + +class TestSphericalJn: + def test_spherical_jn_exact(self): + # https://dlmf.nist.gov/10.49.E3 + # Note: exact expression is numerically stable only for small + # n or z >> n. + x = np.array([0.12, 1.23, 12.34, 123.45, 1234.5]) + assert_allclose(spherical_jn(2, x), + (-1/x + 3/x**3)*sin(x) - 3/x**2*cos(x)) + + def test_spherical_jn_recurrence_complex(self): + # https://dlmf.nist.gov/10.51.E1 + n = np.array([1, 2, 3, 7, 12]) + x = 1.1 + 1.5j + assert_allclose(spherical_jn(n - 1, x) + spherical_jn(n + 1, x), + (2*n + 1)/x*spherical_jn(n, x)) + + def test_spherical_jn_recurrence_real(self): + # https://dlmf.nist.gov/10.51.E1 + n = np.array([1, 2, 3, 7, 12]) + x = 0.12 + assert_allclose(spherical_jn(n - 1, x) + spherical_jn(n + 1,x), + (2*n + 1)/x*spherical_jn(n, x)) + + def test_spherical_jn_inf_real(self): + # https://dlmf.nist.gov/10.52.E3 + n = 6 + x = np.array([-inf, inf]) + assert_allclose(spherical_jn(n, x), np.array([0, 0])) + + def test_spherical_jn_inf_complex(self): + # https://dlmf.nist.gov/10.52.E3 + n = 7 + x = np.array([-inf + 0j, inf + 0j, inf*(1+1j)]) + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", "invalid value encountered in multiply", RuntimeWarning) + assert_allclose(spherical_jn(n, x), np.array([0, 0, inf*(1+1j)])) + + def test_spherical_jn_large_arg_1(self): + # https://github.com/scipy/scipy/issues/2165 + # Reference value computed using mpmath, via + # besselj(n + mpf(1)/2, z)*sqrt(pi/(2*z)) + assert_allclose(spherical_jn(2, 3350.507), -0.00029846226538040747) + + def test_spherical_jn_large_arg_2(self): + # https://github.com/scipy/scipy/issues/1641 + # Reference value computed using mpmath, via + # besselj(n + mpf(1)/2, z)*sqrt(pi/(2*z)) + assert_allclose(spherical_jn(2, 10000), 3.0590002633029811e-05) + + def test_spherical_jn_at_zero(self): + # https://dlmf.nist.gov/10.52.E1 + # But note that n = 0 is a special case: j0 = sin(x)/x -> 1 + n = np.array([0, 1, 2, 5, 10, 100]) + x = 0 + assert_allclose(spherical_jn(n, x), np.array([1, 0, 0, 0, 0, 0])) + + +class TestSphericalYn: + def test_spherical_yn_exact(self): + # https://dlmf.nist.gov/10.49.E5 + # Note: exact expression is numerically stable only for small + # n or z >> n. + x = np.array([0.12, 1.23, 12.34, 123.45, 1234.5]) + assert_allclose(spherical_yn(2, x), + (1/x - 3/x**3)*cos(x) - 3/x**2*sin(x)) + + def test_spherical_yn_recurrence_real(self): + # https://dlmf.nist.gov/10.51.E1 + n = np.array([1, 2, 3, 7, 12]) + x = 0.12 + assert_allclose(spherical_yn(n - 1, x) + spherical_yn(n + 1,x), + (2*n + 1)/x*spherical_yn(n, x)) + + def test_spherical_yn_recurrence_complex(self): + # https://dlmf.nist.gov/10.51.E1 + n = np.array([1, 2, 3, 7, 12]) + x = 1.1 + 1.5j + assert_allclose(spherical_yn(n - 1, x) + spherical_yn(n + 1, x), + (2*n + 1)/x*spherical_yn(n, x)) + + def test_spherical_yn_inf_real(self): + # https://dlmf.nist.gov/10.52.E3 + n = 6 + x = np.array([-inf, inf]) + assert_allclose(spherical_yn(n, x), np.array([0, 0])) + + def test_spherical_yn_inf_complex(self): + # https://dlmf.nist.gov/10.52.E3 + n = 7 + x = np.array([-inf + 0j, inf + 0j, inf*(1+1j)]) + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", "invalid value encountered in multiply", RuntimeWarning) + assert_allclose(spherical_yn(n, x), np.array([0, 0, inf*(1+1j)])) + + def test_spherical_yn_at_zero(self): + # https://dlmf.nist.gov/10.52.E2 + n = np.array([0, 1, 2, 5, 10, 100]) + x = 0 + assert_allclose(spherical_yn(n, x), np.full(n.shape, -inf)) + + def test_spherical_yn_at_zero_complex(self): + # Consistently with numpy: + # >>> -np.cos(0)/0 + # -inf + # >>> -np.cos(0+0j)/(0+0j) + # (-inf + nan*j) + n = np.array([0, 1, 2, 5, 10, 100]) + x = 0 + 0j + assert_allclose(spherical_yn(n, x), np.full(n.shape, nan)) + + +class TestSphericalJnYnCrossProduct: + def test_spherical_jn_yn_cross_product_1(self): + # https://dlmf.nist.gov/10.50.E3 + n = np.array([1, 5, 8]) + x = np.array([0.1, 1, 10]) + left = (spherical_jn(n + 1, x) * spherical_yn(n, x) - + spherical_jn(n, x) * spherical_yn(n + 1, x)) + right = 1/x**2 + assert_allclose(left, right) + + def test_spherical_jn_yn_cross_product_2(self): + # https://dlmf.nist.gov/10.50.E3 + n = np.array([1, 5, 8]) + x = np.array([0.1, 1, 10]) + left = (spherical_jn(n + 2, x) * spherical_yn(n, x) - + spherical_jn(n, x) * spherical_yn(n + 2, x)) + right = (2*n + 3)/x**3 + assert_allclose(left, right) + + +class TestSphericalIn: + def test_spherical_in_exact(self): + # https://dlmf.nist.gov/10.49.E9 + x = np.array([0.12, 1.23, 12.34, 123.45]) + assert_allclose(spherical_in(2, x), + (1/x + 3/x**3)*sinh(x) - 3/x**2*cosh(x)) + + def test_spherical_in_recurrence_real(self): + # https://dlmf.nist.gov/10.51.E4 + n = np.array([1, 2, 3, 7, 12]) + x = 0.12 + assert_allclose(spherical_in(n - 1, x) - spherical_in(n + 1,x), + (2*n + 1)/x*spherical_in(n, x)) + + def test_spherical_in_recurrence_complex(self): + # https://dlmf.nist.gov/10.51.E1 + n = np.array([1, 2, 3, 7, 12]) + x = 1.1 + 1.5j + assert_allclose(spherical_in(n - 1, x) - spherical_in(n + 1,x), + (2*n + 1)/x*spherical_in(n, x)) + + def test_spherical_in_inf_real(self): + # https://dlmf.nist.gov/10.52.E3 + n = 5 + x = np.array([-inf, inf]) + assert_allclose(spherical_in(n, x), np.array([-inf, inf])) + + def test_spherical_in_inf_complex(self): + # https://dlmf.nist.gov/10.52.E5 + # Ideally, i1n(n, 1j*inf) = 0 and i1n(n, (1+1j)*inf) = (1+1j)*inf, but + # this appears impossible to achieve because C99 regards any complex + # value with at least one infinite part as a complex infinity, so + # 1j*inf cannot be distinguished from (1+1j)*inf. Therefore, nan is + # the correct return value. + n = 7 + x = np.array([-inf + 0j, inf + 0j, inf*(1+1j)]) + assert_allclose(spherical_in(n, x), np.array([-inf, inf, nan])) + + def test_spherical_in_at_zero(self): + # https://dlmf.nist.gov/10.52.E1 + # But note that n = 0 is a special case: i0 = sinh(x)/x -> 1 + n = np.array([0, 1, 2, 5, 10, 100]) + x = 0 + assert_allclose(spherical_in(n, x), np.array([1, 0, 0, 0, 0, 0])) + + +class TestSphericalKn: + def test_spherical_kn_exact(self): + # https://dlmf.nist.gov/10.49.E13 + x = np.array([0.12, 1.23, 12.34, 123.45]) + assert_allclose(spherical_kn(2, x), + pi/2*exp(-x)*(1/x + 3/x**2 + 3/x**3)) + + def test_spherical_kn_recurrence_real(self): + # https://dlmf.nist.gov/10.51.E4 + n = np.array([1, 2, 3, 7, 12]) + x = 0.12 + assert_allclose( + (-1)**(n - 1)*spherical_kn(n - 1, x) - (-1)**(n + 1)*spherical_kn(n + 1,x), + (-1)**n*(2*n + 1)/x*spherical_kn(n, x) + ) + + def test_spherical_kn_recurrence_complex(self): + # https://dlmf.nist.gov/10.51.E4 + n = np.array([1, 2, 3, 7, 12]) + x = 1.1 + 1.5j + assert_allclose( + (-1)**(n - 1)*spherical_kn(n - 1, x) - (-1)**(n + 1)*spherical_kn(n + 1,x), + (-1)**n*(2*n + 1)/x*spherical_kn(n, x) + ) + + def test_spherical_kn_inf_real(self): + # https://dlmf.nist.gov/10.52.E6 + n = 5 + x = np.array([-inf, inf]) + assert_allclose(spherical_kn(n, x), np.array([-inf, 0])) + + def test_spherical_kn_inf_complex(self): + # https://dlmf.nist.gov/10.52.E6 + # The behavior at complex infinity depends on the sign of the real + # part: if Re(z) >= 0, then the limit is 0; if Re(z) < 0, then it's + # z*inf. This distinction cannot be captured, so we return nan. + n = 7 + x = np.array([-inf + 0j, inf + 0j, inf*(1+1j)]) + assert_allclose(spherical_kn(n, x), np.array([-inf, 0, nan])) + + def test_spherical_kn_at_zero(self): + # https://dlmf.nist.gov/10.52.E2 + n = np.array([0, 1, 2, 5, 10, 100]) + x = 0 + assert_allclose(spherical_kn(n, x), np.full(n.shape, inf)) + + def test_spherical_kn_at_zero_complex(self): + # https://dlmf.nist.gov/10.52.E2 + n = np.array([0, 1, 2, 5, 10, 100]) + x = 0 + 0j + assert_allclose(spherical_kn(n, x), np.full(n.shape, nan)) + + +class SphericalDerivativesTestCase: + def fundamental_theorem(self, n, a, b): + integral, tolerance = quad(lambda z: self.df(n, z), a, b) + assert_allclose(integral, + self.f(n, b) - self.f(n, a), + atol=tolerance) + + @pytest.mark.slow + def test_fundamental_theorem_0(self): + self.fundamental_theorem(0, 3.0, 15.0) + + @pytest.mark.slow + def test_fundamental_theorem_7(self): + self.fundamental_theorem(7, 0.5, 1.2) + + +class TestSphericalJnDerivatives(SphericalDerivativesTestCase): + def f(self, n, z): + return spherical_jn(n, z) + + def df(self, n, z): + return spherical_jn(n, z, derivative=True) + + def test_spherical_jn_d_zero(self): + n = np.array([0, 1, 2, 3, 7, 15]) + assert_allclose(spherical_jn(n, 0, derivative=True), + np.array([0, 1/3, 0, 0, 0, 0])) + + +class TestSphericalYnDerivatives(SphericalDerivativesTestCase): + def f(self, n, z): + return spherical_yn(n, z) + + def df(self, n, z): + return spherical_yn(n, z, derivative=True) + + +class TestSphericalInDerivatives(SphericalDerivativesTestCase): + def f(self, n, z): + return spherical_in(n, z) + + def df(self, n, z): + return spherical_in(n, z, derivative=True) + + def test_spherical_in_d_zero(self): + n = np.array([0, 1, 2, 3, 7, 15]) + spherical_in(n, 0, derivative=False) + assert_allclose(spherical_in(n, 0, derivative=True), + np.array([0, 1/3, 0, 0, 0, 0])) + + +class TestSphericalKnDerivatives(SphericalDerivativesTestCase): + def f(self, n, z): + return spherical_kn(n, z) + + def df(self, n, z): + return spherical_kn(n, z, derivative=True) + + +class TestSphericalOld: + # These are tests from the TestSpherical class of test_basic.py, + # rewritten to use spherical_* instead of sph_* but otherwise unchanged. + + def test_sph_in(self): + # This test reproduces test_basic.TestSpherical.test_sph_in. + i1n = np.empty((2,2)) + x = 0.2 + + i1n[0][0] = spherical_in(0, x) + i1n[0][1] = spherical_in(1, x) + i1n[1][0] = spherical_in(0, x, derivative=True) + i1n[1][1] = spherical_in(1, x, derivative=True) + + inp0 = (i1n[0][1]) + inp1 = (i1n[0][0] - 2.0/0.2 * i1n[0][1]) + assert_allclose(i1n[0], np.array([1.0066800127054699381, + 0.066933714568029540839]), + atol=1.5e-12, rtol=0.0) + assert_allclose(i1n[1], [inp0, inp1], atol=1.5e-12, rtol=0) + + def test_sph_in_kn_order0(self): + x = 1. + sph_i0 = np.empty((2,)) + sph_i0[0] = spherical_in(0, x) + sph_i0[1] = spherical_in(0, x, derivative=True) + sph_i0_expected = np.array([np.sinh(x)/x, + np.cosh(x)/x-np.sinh(x)/x**2]) + assert_allclose(r_[sph_i0], sph_i0_expected, atol=1.5e-7, rtol=0) + + sph_k0 = np.empty((2,)) + sph_k0[0] = spherical_kn(0, x) + sph_k0[1] = spherical_kn(0, x, derivative=True) + sph_k0_expected = np.array([0.5*pi*exp(-x)/x, + -0.5*pi*exp(-x)*(1/x+1/x**2)]) + assert_allclose(r_[sph_k0], sph_k0_expected, atol=1.5e-7, rtol=0) + + def test_sph_jn(self): + s1 = np.empty((2,3)) + x = 0.2 + + s1[0][0] = spherical_jn(0, x) + s1[0][1] = spherical_jn(1, x) + s1[0][2] = spherical_jn(2, x) + s1[1][0] = spherical_jn(0, x, derivative=True) + s1[1][1] = spherical_jn(1, x, derivative=True) + s1[1][2] = spherical_jn(2, x, derivative=True) + + s10 = -s1[0][1] + s11 = s1[0][0]-2.0/0.2*s1[0][1] + s12 = s1[0][1]-3.0/0.2*s1[0][2] + assert_allclose(s1[0], [0.99334665397530607731, + 0.066400380670322230863, + 0.0026590560795273856680], + atol=1.5e-12, rtol=0) + assert_allclose(s1[1], [s10, s11, s12], atol=1.5e-12, rtol=0) + + def test_sph_kn(self): + kn = np.empty((2,3)) + x = 0.2 + + kn[0][0] = spherical_kn(0, x) + kn[0][1] = spherical_kn(1, x) + kn[0][2] = spherical_kn(2, x) + kn[1][0] = spherical_kn(0, x, derivative=True) + kn[1][1] = spherical_kn(1, x, derivative=True) + kn[1][2] = spherical_kn(2, x, derivative=True) + + kn0 = -kn[0][1] + kn1 = -kn[0][0]-2.0/0.2*kn[0][1] + kn2 = -kn[0][1]-3.0/0.2*kn[0][2] + assert_allclose(kn[0], [6.4302962978445670140, + 38.581777787067402086, + 585.15696310385559829], + atol=1.5e-12, rtol=0) + assert_allclose(kn[1], [kn0, kn1, kn2], atol=1.5e-9, rtol=0) + + def test_sph_yn(self): + sy1 = spherical_yn(2, 0.2) + sy2 = spherical_yn(0, 0.2) + # previous values in the system + assert_allclose(sy1, -377.52483, atol=1.5e-5, rtol=0) + assert_allclose(sy2, -4.9003329, atol=1.5e-5, rtol=0) + sphpy = (spherical_yn(0, 0.2) - 2*spherical_yn(2, 0.2))/3 + sy3 = spherical_yn(1, 0.2, derivative=True) + # compare correct derivative val. (correct =-system val). + assert_allclose(sy3, sphpy, atol=1.5e-4, rtol=0) + + +@pytest.mark.parametrize('derivative', [False, True]) +@pytest.mark.parametrize('fun', [spherical_jn, spherical_in, + spherical_yn, spherical_kn]) +def test_negative_real_gh14582(derivative, fun): + # gh-14582 reported that the spherical Bessel functions did not work + # with negative real argument `z`. Check that this is resolved. + rng = np.random.default_rng(3598435982345987234) + size = 25 + n = rng.integers(0, 10, size=size) + z = rng.standard_normal(size=size) + res = fun(n, z, derivative=derivative) + ref = fun(n, z+0j, derivative=derivative) + assert_allclose(res, ref.real) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_support_alternative_backends.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_support_alternative_backends.py new file mode 100644 index 0000000000000000000000000000000000000000..34d239e4e8f8a619b59ef79f5ce2e4842e5c41a0 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_support_alternative_backends.py @@ -0,0 +1,404 @@ +from functools import partial +import pickle + +import pytest +from hypothesis import given, strategies +import hypothesis.extra.numpy as npst +from packaging import version + +from scipy import special +from scipy.special._support_alternative_backends import _special_funcs +from scipy._lib._array_api_no_0d import xp_assert_close +from scipy._lib._array_api import (is_cupy, is_dask, is_jax, is_torch, + make_xp_pytest_param, make_xp_test_case, + get_native_namespace_name) +from scipy._external.array_api_compat import numpy as np +import scipy._external.array_api_extra as xpx + + +# Run all tests in this module in the Array API CI, including those without +# the xp fixture +pytestmark = pytest.mark.array_api_backends + +lazy_xp_modules = [special] + + +def _skip_or_tweak_alternative_backends(xp, nfo, dtypes, int_only): + """Skip tests for specific intersections of scipy.special functions + vs. backends vs. dtypes vs. devices. + Also suggest bespoke tweaks. + + Returns + ------- + positive_only : list[bool] + Whether you should exclusively test positive inputs. + dtypes : list[str] + dtype strings 'float64', 'int32', 'int64', etc. with integer types + mapped to the type of the NumPy default int. + """ + f_name = nfo.name + if isinstance(nfo.positive_only, dict): + positive_only = nfo.positive_only.get(get_native_namespace_name(xp), False) + else: + positive_only = nfo.positive_only + if isinstance(positive_only, bool): + positive_only = [positive_only]*nfo.n_args + + dtypes = [np.intp.__name__ if dtype == "intp" else dtype for dtype in dtypes] + + if f_name in {'betaincinv'} and is_cupy(xp): + pytest.xfail("CuPy uses different convention for out of domain input.") + + if not any('int' in dtype for dtype in dtypes): + return positive_only, dtypes + + # Integer-specific issues from this point onwards + + if f_name in {'gamma', 'gammasgn'} and is_cupy(xp): + # CuPy has not yet updated gamma pole behavior to match + # https://github.com/scipy/scipy/pull/21827. + positive_only = [True] + + if f_name in {'poch'} and is_jax(xp): + # Jax uses a different convention at gamma poles. + positive_only = [True, True] + + if f_name == 'multigammaln': + pytest.skip("multigammaln raises for out of domain inputs.") + + if ((is_torch(xp) and f_name in {'gammainc', 'gammaincc'}) + or (is_cupy(xp) and f_name in {'stdtr', 'i0e', 'i1e'}) + or (is_jax(xp) and f_name in {'stdtr', 'ndtr', 'ndtri', 'log_ndtr', 'hyp1f1', + 'hyp2f1', 'spence', 'kl_div'}) + ): + pytest.skip(f"`{f_name}` does not support integer types") + + # int/float mismatched args support is sketchy + if (any('float' in dtype for dtype in dtypes) + and ((is_torch(xp) and f_name in ('rel_entr', 'xlogy', 'polygamma', + 'zeta', 'xlog1py')) + or (is_jax(xp) and f_name in ('gammainc', 'gammaincc', 'expn', + 'rel_entr', 'xlogy', 'betaln', + 'polygamma', 'zeta', 'poch', + 'xlog1py'))) + ): + pytest.xfail("dtypes do not match") + + if (is_torch(xp) and xpx.default_dtype(xp) == xp.float32): + # On PyTorch with float32 default dtype, all ints are promoted + # to float32, but when falling back to NumPy/SciPy int64 is promoted + # instead to float64. Integer only parameters essentially do not + # participate in determination of the result type in PyTorch with + # float32 default dtype, but will impact the output dtype as if + # they were float64 when falling back to NumPy/SciPy. + if not nfo.torch_native: + pytest.xfail("dtypes do not match") + + return positive_only, dtypes + + +@pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") +@pytest.mark.filterwarnings("ignore:overflow encountered:RuntimeWarning") +@pytest.mark.parametrize('shapes', [[(0,)]*4, [tuple()]*4, [(10,)]*4, + [(10,), (11, 1), (12, 1, 1), (13, 1, 1, 1)]]) +@pytest.mark.parametrize('base_dtype', ['float32', 'float64', 'intp']) +@pytest.mark.parametrize( + 'func,nfo', [make_xp_pytest_param(i.wrapper, i) for i in _special_funcs]) +def test_support_alternative_backends(xp, func, nfo, base_dtype, shapes): + int_only = nfo.int_only + if int_only is None: + int_only = (False, ) * nfo.n_args + dtypes = (base_dtype, ) * nfo.n_args + else: + dtypes = tuple( + 'intp' if needs_int else base_dtype for needs_int in int_only + ) + + positive_only, dtypes = _skip_or_tweak_alternative_backends( + xp, nfo, dtypes, int_only + ) + + dtypes_np = [getattr(np, dtype) for dtype in dtypes] + dtypes_xp = [getattr(xp, dtype) for dtype in dtypes] + + shapes = shapes[:nfo.n_args] + rng = np.random.default_rng(984254252920492019) + args_np = [] + + # Handle cases where there's an argument which only takes scalar values. + python_int_only = nfo.python_int_only + if isinstance(python_int_only, dict): + python_int_only = python_int_only.get(get_native_namespace_name(xp)) + scalar_or_0d_only = nfo.scalar_or_0d_only + if isinstance(scalar_or_0d_only, dict): + scalar_or_0d_only = scalar_or_0d_only.get(get_native_namespace_name(xp)) + + test_large_ints = nfo.test_large_ints + if isinstance(nfo.test_large_ints, dict): + test_large_ints = test_large_ints.get(get_native_namespace_name(xp), False) + + if python_int_only is None: + python_int_only = [False] * nfo.n_args + if scalar_or_0d_only is None: + scalar_or_0d_only = [False] * nfo.n_args + + no_shape = [ + cond1 or cond2 for cond1, cond2 in zip(python_int_only, scalar_or_0d_only) + ] + + shapes = [shape if not cond else None for shape, cond in zip(shapes, no_shape)] + + for dtype, dtype_np, shape, needs_python_int in zip( + dtypes, dtypes_np, shapes, python_int_only + ): + if 'int' in dtype and test_large_ints: + iinfo = np.iinfo(dtype_np) + rand = partial(rng.integers, iinfo.min, iinfo.max + 1) + elif 'int' in dtype: + rand = partial(rng.integers, -20, 21) + else: + rand = rng.standard_normal + val = rand(size=shape, dtype=dtype_np) + if needs_python_int: + # The logic above for determining shapes guarantees that + # shape will be None in the above line when a Python int is required, + # so this can safely be converted to an int. + val = int(val) + args_np.append(val) + + args_np = [ + np.abs(arg) if cond else arg for arg, cond in zip(args_np, positive_only) + ] + + args_xp = [ + xp.asarray(arg, dtype=dtype_xp) if not needs_python_int + else arg + for arg, dtype_xp, needs_python_int + in zip(args_np, dtypes_xp, python_int_only) + ] + + args_np = [ + np.asarray(arg, dtype=dtype_np) if not needs_python_int + else arg + for arg, dtype_np, needs_python_int + in zip(args_np, dtypes_np, python_int_only) + ] + + if is_dask(xp): + # We're using map_blocks to dispatch the function to Dask. + # This is the correct thing to do IF all tested functions are elementwise; + # otherwise the output would change depending on chunking. + # Try to trigger bugs related to having multiple chunks. + args_xp = [arg.rechunk(5) for arg in args_xp] + + res = nfo.wrapper(*args_xp) # Also wrapped by lazy_xp_function + ref = nfo.func(*args_np) # Unwrapped ufunc + if ( + is_torch(xp) + and xpx.default_dtype(xp) == xp.float32 + and "float64" not in dtypes + ): + # int64 promotes like float32 on torch with default dtype = float32 + # cast reference if needed + ref = np.float32(ref) + # When dtype_np is integer, the output dtype can be float + atol = 0 if ref.dtype.kind in 'iu' else 10 * np.finfo(ref.dtype).eps + rtol = None + if is_torch(xp) and func.__name__ == 'j1': + # If we end up needing more function/backend specific tolerance + # adjustments, this should be factored out properly. + atol = 1e-7 + rtol = 1e-5 + xp_assert_close( + res, xp.asarray(ref), rtol=rtol, atol=atol, check_0d=nfo.produces_0d + ) + + +@pytest.mark.parametrize( + 'func, nfo', + [make_xp_pytest_param(i.wrapper, i) for i in _special_funcs if i.n_args >= 2]) +@pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") +def test_support_alternative_backends_mismatched_dtypes(xp, func, nfo): + """Test mix-n-match of int and float arguments""" + if func.__name__ in {'expn', 'polygamma', 'multigammaln', 'bdtr', 'bdtrc', 'bdtri', + 'nbdtr', 'nbdtrc', 'nbdtri', 'pdtri'}: + pytest.skip(f"dtypes for {func.__name__} make it a bad fit for this test.") + dtypes = ['intp', 'float32', 'float64', 'float64'][:nfo.n_args] + + positive_only, dtypes = _skip_or_tweak_alternative_backends( + xp, nfo, dtypes, (False,)*nfo.n_args + ) + dtypes_np = [getattr(np, dtype) for dtype in dtypes] + dtypes_xp = [getattr(xp, dtype) for dtype in dtypes] + + rng = np.random.default_rng(984254252920492019) + iinfo = np.iinfo(np.intp) + if nfo.test_large_ints: + randint = partial(rng.integers, iinfo.min, iinfo.max + 1) + else: + randint = partial(rng.integers, -20, 21) + args_np = [ + randint(size=1, dtype=np.int64), + rng.standard_normal(size=1, dtype=np.float32), + rng.standard_normal(size=1, dtype=np.float64), + rng.standard_normal(size=1, dtype=np.float64), + ][:nfo.n_args] + args_np = [ + np.abs(arg) if cond else arg for arg, cond in zip(args_np, positive_only) + ] + + args_xp = [xp.asarray(arg, dtype=dtype_xp) + for arg, dtype_xp in zip(args_np, dtypes_xp)] + args_np = [np.asarray(arg, dtype=dtype_np) + for arg, dtype_np in zip(args_np, dtypes_np)] + + res = nfo.wrapper(*args_xp) # Also wrapped by lazy_xp_function + ref = nfo.func(*args_np) # Unwrapped ufunc + if ( + is_torch(xp) + and xpx.default_dtype(xp) == xp.float32 + and "float64" not in dtypes + ): + # int64 promotes like float32 on torch with default dtype = float32 + # cast reference if needed + ref = np.float32(ref) + + atol = 10 * np.finfo(ref.dtype).eps + xp_assert_close(res, xp.asarray(ref), atol=atol) + + +@pytest.mark.xslow +@given(data=strategies.data()) +@pytest.mark.fail_slow(5) +@pytest.mark.parametrize( + 'func,nfo', [make_xp_pytest_param(nfo.wrapper, nfo) for nfo in _special_funcs]) +@pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") +@pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning:dask") +@pytest.mark.filterwarnings("ignore:overflow encountered:RuntimeWarning:dask") +@pytest.mark.filterwarnings( + "ignore:overflow encountered:RuntimeWarning:array_api_strict" +) +def test_support_alternative_backends_hypothesis(xp, func, nfo, data): + if func.__name__ in {'expn', 'polygamma', 'multigammaln', 'bdtr', 'bdtrc', 'bdtri', + 'nbdtr', 'nbdtrc', 'nbdtri', 'pdtri'}: + pytest.skip(f"dtypes for {func.__name__} make it a bad fit for this test.") + dtype = data.draw(strategies.sampled_from(['float32', 'float64', 'intp'])) + positive_only, dtypes = _skip_or_tweak_alternative_backends( + xp, nfo, [dtype], (False,)*nfo.n_args + ) + dtype_np = getattr(np, dtypes[0]) + dtype_xp = getattr(xp, dtypes[0]) + + elements = {'allow_subnormal': False} + # Most failures are due to NaN or infinity; uncomment to suppress them + # elements['allow_infinity'] = False + # elements['allow_nan'] = False + if any(positive_only): + elements['min_value'] = 0 + + shapes, _ = data.draw( + npst.mutually_broadcastable_shapes(num_shapes=nfo.n_args)) + args_np = [data.draw(npst.arrays(dtype_np, shape, elements=elements)) + for shape in shapes] + + args_xp = [xp.asarray(arg, dtype=dtype_xp) for arg in args_np] + args_np = [np.asarray(arg, dtype=dtype_np) for arg in args_np] + + res = nfo.wrapper(*args_xp) # Also wrapped by lazy_xp_function + ref = nfo.func(*args_np) # Unwrapped ufunc + if ( + is_torch(xp) + and xpx.default_dtype(xp) == xp.float32 + and dtype != "float64" + ): + # int64 promotes like float32 on torch with default dtype = float32 + # cast reference if needed + ref = np.float32(ref) + + # When dtype_np is integer, the output dtype can be float + atol = 0 if ref.dtype.kind in 'iu' else 10 * np.finfo(ref.dtype).eps + xp_assert_close(res, xp.asarray(ref), atol=atol) + + +@pytest.mark.filterwarnings("ignore:numpy.core is deprecated:DeprecationWarning") +@pytest.mark.parametrize("func", [nfo.wrapper for nfo in _special_funcs]) +def test_pickle(func): + roundtrip = pickle.loads(pickle.dumps(func)) + assert roundtrip is func + + +@pytest.mark.parametrize("func", [nfo.wrapper for nfo in _special_funcs]) +def test_repr(func): + assert func.__name__ in repr(func) + assert "locals" not in repr(func) + + +@pytest.mark.skipif( + version.parse(np.__version__) < version.parse("2.2"), # type:ignore[attr-defined] + reason="Can't update ufunc __doc__ when SciPy is compiled vs. NumPy < 2.2") +@pytest.mark.parametrize('func', [nfo.wrapper for nfo in _special_funcs]) +def test_doc(func): + """xp_capabilities updates the docstring in place. + Make sure it does so exactly once, including when SCIPY_ARRAY_API is not set. + """ + match = "has experimental support for Python Array API" + assert func.__doc__.count(match) == 1 + + +@pytest.mark.parametrize( + 'func,n_args,int_only,is_ufunc', + [(nfo.wrapper, nfo.n_args, nfo.int_only, nfo.is_ufunc) + for nfo in _special_funcs] +) +def test_ufunc_kwargs(func, n_args, int_only, is_ufunc): + """Test that numpy-specific out= and dtype= keyword arguments + of ufuncs still work when SCIPY_ARRAY_API is set. + """ + if not is_ufunc: + pytest.skip(f"{func.__name__} is not a ufunc.") + if int_only is None: + int_only = (False, ) * n_args + # out= + args = [ + np.asarray([.1, .2]) if not needs_int + else np.asarray([1, 2]) + for needs_int in int_only + ] + out = np.empty(2) + y = func(*args, out=out) + xp_assert_close(y, out) + + # out= with out.dtype != args.dtype + out = np.empty(2, dtype=np.float32) + y = func(*args, out=out) + xp_assert_close(y, out) + + if func.__name__ in {"bdtr", "bdtrc", "bdtri"}: + # The below function evaluation will trigger a deprecation warning + # with dtype=np.float32. This will go away if the trigger is actually + # pulled on the deprecation. + return + + # dtype= + y = func(*args, dtype=np.float32) + assert y.dtype == np.float32 + + +@make_xp_test_case(special.chdtr) +def test_chdtr_gh21311(xp): + # the edge case behavior of generic chdtr was not right; see gh-21311 + # be sure to test at least these cases + # should add `np.nan` into the mix when gh-21317 is resolved + x = np.asarray([-np.inf, -1., 0., 1., np.inf]) + v = x.reshape(-1, 1) + ref = special.chdtr(v, x) + res = special.chdtr(xp.asarray(v), xp.asarray(x)) + xp_assert_close(res, xp.asarray(ref)) + + +@make_xp_test_case(special.fdtrc) +def test_mixed_arrays_and_python_scalars(xp): + # Tests that the delegation infrastructure respects NEP50. + res = special.fdtrc(1.1, 2., xp.asarray(1., dtype=xp.float32)) + ref = xp.asarray(0.4349004, dtype=xp.float32) + xp_assert_close(res, ref) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_trig.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_trig.py new file mode 100644 index 0000000000000000000000000000000000000000..e5f8f79f8d2cac51bf9476f04f13df2bfac587a5 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_trig.py @@ -0,0 +1,76 @@ +import warnings + +import pytest +import numpy as np +from numpy.testing import assert_equal, assert_allclose + +from scipy.special._ufuncs import _sinpi as sinpi +from scipy.special._ufuncs import _cospi as cospi + + +def test_integer_real_part(): + x = np.arange(-100, 101) + y = np.hstack((-np.linspace(310, -30, 10), np.linspace(-30, 310, 10))) + x, y = np.meshgrid(x, y) + z = x + 1j*y + # In the following we should be *exactly* right + res = sinpi(z) + assert_equal(res.real, 0.0) + res = cospi(z) + assert_equal(res.imag, 0.0) + + +def test_half_integer_real_part(): + x = np.arange(-100, 101) + 0.5 + y = np.hstack((-np.linspace(310, -30, 10), np.linspace(-30, 310, 10))) + x, y = np.meshgrid(x, y) + z = x + 1j*y + # In the following we should be *exactly* right + res = sinpi(z) + assert_equal(res.imag, 0.0) + res = cospi(z) + assert_equal(res.real, 0.0) + + +@pytest.mark.skip("Temporary skip while gh-19526 is being resolved") +def test_intermediate_overlow(): + # Make sure we avoid overflow in situations where cosh/sinh would + # overflow but the product with sin/cos would not + sinpi_pts = [complex(1 + 1e-14, 227), + complex(1e-35, 250), + complex(1e-301, 445)] + # Data generated with mpmath + sinpi_std = [complex(-8.113438309924894e+295, -np.inf), + complex(1.9507801934611995e+306, np.inf), + complex(2.205958493464539e+306, np.inf)] + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", "invalid value encountered in multiply", RuntimeWarning) + for p, std in zip(sinpi_pts, sinpi_std): + res = sinpi(p) + assert_allclose(res.real, std.real) + assert_allclose(res.imag, std.imag) + + # Test for cosine, less interesting because cos(0) = 1. + p = complex(0.5 + 1e-14, 227) + std = complex(-8.113438309924894e+295, -np.inf) + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", "invalid value encountered in multiply", RuntimeWarning) + res = cospi(p) + assert_allclose(res.real, std.real) + assert_allclose(res.imag, std.imag) + + +def test_zero_sign(): + y = sinpi(-0.0) + assert y == 0.0 + assert np.signbit(y) + + y = sinpi(0.0) + assert y == 0.0 + assert not np.signbit(y) + + y = cospi(0.5) + assert y == 0.0 + assert not np.signbit(y) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_ufunc_infra.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_ufunc_infra.py new file mode 100644 index 0000000000000000000000000000000000000000..30b08cde365de9c115935d1100001f8ba1349276 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_ufunc_infra.py @@ -0,0 +1,34 @@ +import numpy as np +import pytest + +from scipy.special import mathieu_sem +# from scipy.special._ufuncs import _mathieu_sem + + +@pytest.mark.parametrize("m_shape,q_shape,x_shape,where_shape", [ + ((5, 1), (1, 1), (1, 10), ()), + ((2, 3), (2, 3), (2, 3), ()), + ((1, 5), (1, 5), (10, 5), ()), + ((5, 1), (1, 1), (1, 10), (1, 10)), + ((2, 3), (2, 3), (2, 3), (2, 3)), + ((1, 5), (1, 5), (10, 5), (10, 5)), + ((5, 1), (1, 1), (1, 10), (5, 1)), +]) +def test_out(m_shape, q_shape, x_shape, where_shape): + rng = np.random.default_rng(1234) + + m = rng.integers(1, 20, m_shape) + q = rng.uniform(0, 10, q_shape) + x = rng.uniform(0, 90, x_shape) + + if where_shape == (): + batch_shape = np.broadcast_shapes(m_shape, q_shape, x_shape) + where = True + else: + where = rng.choice([True, False], size=where_shape) + batch_shape = np.broadcast_shapes(m_shape, q_shape, x_shape, where_shape) + + out0 = np.empty(batch_shape) + out1 = np.empty(batch_shape) + res0, res1 = mathieu_sem(m, q, x, out=(out0, out1), where=where) + assert res0 is out0 and res1 is out1 diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_ufunc_signatures.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_ufunc_signatures.py new file mode 100644 index 0000000000000000000000000000000000000000..e4fdb02a855797923a792dfce7747a10f6b407db --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_ufunc_signatures.py @@ -0,0 +1,119 @@ +"""Test that all ufuncs have float32-preserving signatures. + +This was once guaranteed through the code generation script for +generating ufuncs, `scipy/special/_generate_pyx.py`. Starting with +gh-20260, SciPy developers have begun moving to generate ufuncs +through direct use of the NumPy C API (through C++). Existence of +float32 preserving signatures must now be tested since it is no +longer guaranteed. +""" + +import numpy as np +import pytest +import scipy.special._ufuncs +import scipy.special._gufuncs +import scipy.special as special + +from numpy.testing import assert_equal + + +# Single precision is not implemented for these ufuncs; +# floating point inputs must be float64. +exceptions = ['_gen_harmonic', '_normalized_gen_harmonic'] + +_ufuncs = [] +for funcname in dir(scipy.special._ufuncs): + if funcname not in exceptions: + _ufuncs.append(getattr(scipy.special._ufuncs, funcname)) +for funcname in dir(scipy.special._gufuncs): + _ufuncs.append(getattr(scipy.special._gufuncs, funcname)) + +# Not all module members are actually ufuncs +_ufuncs = [func for func in _ufuncs if isinstance(func, np.ufunc)] + +@pytest.mark.parametrize("ufunc", _ufuncs) +def test_ufunc_signatures(ufunc): + + # From _generate_pyx.py + # "Don't add float32 versions of ufuncs with integer arguments, as this + # can lead to incorrect dtype selection if the integer arguments are + # arrays, but float arguments are scalars. + # This may be a NumPy bug, but we need to work around it. + # cf. gh-4895, https://github.com/numpy/numpy/issues/5895" + types = set(sig for sig in ufunc.types + if not ("l" in sig or "i" in sig or "q" in sig or "p" in sig)) + + # Generate the full expanded set of signatures which should exist. There + # should be matching float and double versions of any existing signature. + expanded_types = set() + for sig in types: + expanded_types.update( + [sig.replace("d", "f").replace("D", "F"), + sig.replace("f", "d").replace("F", "D")] + ) + assert types == expanded_types + + +def _get_nan_val(typecode): + return { + "f": np.asarray(np.nan, dtype=np.float32), + "d": np.asarray(np.nan, dtype=np.float64), + "g": np.asarray(np.nan, dtype=np.longdouble), + "F": np.asarray(np.nan + np.nan*1j, dtype=np.complex64), + "D": np.asarray(np.nan + np.nan*1j, dtype=np.complex128), + "G": np.asarray(np.nan + np.nan*1j, dtype=np.clongdouble), + }[typecode] + +skips = { + # These hit https://github.com/scipy/xsf/issues/94 + "eval_chebyc", + "eval_chebys", + "eval_chebyt", + "eval_chebyu", + "eval_gegenbauer", + "eval_genlaguerre", + "eval_jacobi", + "eval_laguerre", + "eval_legendre", + "eval_sh_chebyt", + "eval_sh_chebyu", + "eval_sh_jacobi", + "eval_sh_legendre", + "hyp1f1", + "hyp2f1", + # warns if called with noninteger values of some args + "bdtr", + "bdtrc", + "bdtri", +} +_multi_arg_public_ufuncs = [ + ufunc for ufunc in _ufuncs if ufunc.nargs - ufunc.nout > 1 + and ufunc.__name__ not in skips and ufunc.__name__ in special.__all__ +] + + +@pytest.mark.parametrize("ufunc", _multi_arg_public_ufuncs) +def test_nep50(ufunc): + # Test that functions with multiple arguments respect nep50 promotion rules. + rng = np.random.default_rng(1234) + # As in test_ufunc_signatures, filter out signatures involving integers. + types = set(sig for sig in ufunc.types + if not ("l" in sig or "i" in sig or "q" in sig or "p" in sig)) + for sig in types: + input_types, output_types = sig.split("->") + # since we only care about dtypes and not values here, just use an appropriately + # typed nan for each argument. + args = [_get_nan_val(typecode) for typecode in input_types] + # swap out a random one of the nans with an appropriately typed numpy scalar. + idx = rng.choice(len(args)) + args[idx] = float("nan") if np.isrealobj(args[idx]) else complex("nan") + result = ufunc(*args) + result = [result] if len(output_types) == 1 else result + result = np.asarray(result) + + # Test that the output is an appropriately typed nan. This also implicitly + # tests that ufuncs propagate nans correctly. + assert_equal( + result, np.asarray([_get_nan_val(typecode) for typecode in output_types]), + strict=True + ) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_wright_bessel.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_wright_bessel.py new file mode 100644 index 0000000000000000000000000000000000000000..ef30163f55c90850fe32ca600eb47547e4fa5e03 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_wright_bessel.py @@ -0,0 +1,205 @@ +# Reference MPMATH implementation: +# +# import mpmath +# from mpmath import nsum +# +# def Wright_Series_MPMATH(a, b, z, dps=50, method='r+s+e', steps=[1000]): +# """Compute Wright' generalized Bessel function as Series. +# +# This uses mpmath for arbitrary precision. +# """ +# with mpmath.workdps(dps): +# res = nsum(lambda k: z**k/mpmath.fac(k) * mpmath.rgamma(a*k+b), +# [0, mpmath.inf], +# tol=dps, method=method, steps=steps +# ) +# +# return res + +from itertools import product + +import pytest +import numpy as np +from numpy.testing import assert_equal, assert_allclose + +import scipy.special as sc +from scipy.special import log_wright_bessel, loggamma, rgamma, wright_bessel + + +@pytest.mark.parametrize('a', [0, 1e-6, 0.1, 0.5, 1, 10]) +@pytest.mark.parametrize('b', [0, 1e-6, 0.1, 0.5, 1, 10]) +def test_wright_bessel_zero(a, b): + """Test at x = 0.""" + assert_equal(wright_bessel(a, b, 0.), rgamma(b)) + assert_allclose(log_wright_bessel(a, b, 0.), -loggamma(b)) + + +@pytest.mark.parametrize('b', [0, 1e-6, 0.1, 0.5, 1, 10]) +@pytest.mark.parametrize('x', [0, 1e-6, 0.1, 0.5, 1]) +def test_wright_bessel_iv(b, x): + """Test relation of wright_bessel and modified bessel function iv. + + iv(z) = (1/2*z)**v * Phi(1, v+1; 1/4*z**2). + See https://dlmf.nist.gov/10.46.E2 + """ + if x != 0: + v = b - 1 + wb = wright_bessel(1, v + 1, x**2 / 4.) + # Note: iv(v, x) has precision of less than 1e-12 for some cases + # e.g v=1-1e-6 and x=1e-06) + assert_allclose(np.power(x / 2., v) * wb, + sc.iv(v, x), + rtol=1e-11, atol=1e-11) + + +@pytest.mark.parametrize('a', [0, 1e-6, 0.1, 0.5, 1, 10]) +@pytest.mark.parametrize('b', [1, 1 + 1e-3, 2, 5, 10]) +@pytest.mark.parametrize('x', [0, 1e-6, 0.1, 0.5, 1, 5, 10, 100]) +def test_wright_functional(a, b, x): + """Test functional relation of wright_bessel. + + Phi(a, b-1, z) = a*z*Phi(a, b+a, z) + (b-1)*Phi(a, b, z) + + Note that d/dx Phi(a, b, x) = Phi(a, b-1, x) + See Eq. (22) of + B. Stankovic, On the Function of E. M. Wright, + Publ. de l' Institut Mathematique, Beograd, + Nouvelle S`er. 10 (1970), 113-124. + """ + assert_allclose(wright_bessel(a, b - 1, x), + a * x * wright_bessel(a, b + a, x) + + (b - 1) * wright_bessel(a, b, x), + rtol=1e-8, atol=1e-8) + + +# grid of rows [a, b, x, value, accuracy] that do not reach 1e-11 accuracy +# see output of: +# cd scipy/scipy/_precompute +# python wright_bessel_data.py +grid_a_b_x_value_acc = np.array([ + [0.1, 100.0, 709.7827128933841, 8.026353022981087e+34, 2e-8], + [0.5, 10.0, 709.7827128933841, 2.680788404494657e+48, 9e-8], + [0.5, 10.0, 1000.0, 2.005901980702872e+64, 1e-8], + [0.5, 100.0, 1000.0, 3.4112367580445246e-117, 6e-8], + [1.0, 20.0, 100000.0, 1.7717158630699857e+225, 3e-11], + [1.0, 100.0, 100000.0, 1.0269334596230763e+22, np.nan], + [1.0000000000000222, 20.0, 100000.0, 1.7717158630001672e+225, 3e-11], + [1.0000000000000222, 100.0, 100000.0, 1.0269334595866202e+22, np.nan], + [1.5, 0.0, 500.0, 15648961196.432373, 3e-11], + [1.5, 2.220446049250313e-14, 500.0, 15648961196.431465, 3e-11], + [1.5, 1e-10, 500.0, 15648961192.344728, 3e-11], + [1.5, 1e-05, 500.0, 15648552437.334162, 3e-11], + [1.5, 0.1, 500.0, 12049870581.10317, 2e-11], + [1.5, 20.0, 100000.0, 7.81930438331405e+43, 3e-9], + [1.5, 100.0, 100000.0, 9.653370857459075e-130, np.nan], + ]) + + +@pytest.mark.xfail +@pytest.mark.parametrize( + 'a, b, x, phi', + grid_a_b_x_value_acc[:, :4].tolist()) +def test_wright_data_grid_failures(a, b, x, phi): + """Test cases of test_data that do not reach relative accuracy of 1e-11""" + assert_allclose(wright_bessel(a, b, x), phi, rtol=1e-11) + + +@pytest.mark.parametrize( + 'a, b, x, phi, accuracy', + grid_a_b_x_value_acc.tolist()) +def test_wright_data_grid_less_accurate(a, b, x, phi, accuracy): + """Test cases of test_data that do not reach relative accuracy of 1e-11 + + Here we test for reduced accuracy or even nan. + """ + if np.isnan(accuracy): + assert np.isnan(wright_bessel(a, b, x)) + else: + assert_allclose(wright_bessel(a, b, x), phi, rtol=accuracy) + + +@pytest.mark.parametrize( + 'a, b, x', + list( + product([0, 0.1, 0.5, 1.5, 5, 10], [1, 2], [1e-3, 1, 1.5, 5, 10]) + ) +) +def test_log_wright_bessel_same_as_wright_bessel(a, b, x): + """Test that log_wright_bessel equals log of wright_bessel.""" + assert_allclose( + log_wright_bessel(a, b, x), + np.log(wright_bessel(a, b, x)), + rtol=1e-8, + ) + + +# Computed with, see also mp_wright_bessel from wright_bessel_data.py: +# +# from functools import lru_cache +# import mpmath as mp +# +# @lru_cache(maxsize=1_000_000) +# def rgamma_cached(x, dps): +# with mp.workdps(dps): +# return mp.rgamma(x) +# +# def mp_log_wright_bessel(a, b, x, dps=100, maxterms=10_000, method="d"): +# """Compute log of Wright's generalized Bessel function as Series with mpmath.""" +# with mp.workdps(dps): +# a, b, x = mp.mpf(a), mp.mpf(b), mp.mpf(x) +# res = mp.nsum(lambda k: x**k / mp.fac(k) +# * rgamma_cached(a * k + b, dps=dps), +# [0, mp.inf], +# tol=dps, method=method, steps=[maxterms] +# ) +# return mp.log(res) +# +# Sometimes, one needs to set maxterms as high as 1_00_000 to get accurate results for +# phi. +# At the end of the day, we can only hope that results are correct for very large x, +# e.g. by the asymptotic series, as there is no way to produce those in "exact" +# arithmetic. +# Note: accuracy = np.nan means log_wright_bessel returns nan. +@pytest.mark.parametrize( + 'a, b, x, phi, accuracy', + [ + (0, 0, 0, -np.inf, 1e-11), + (0, 0, 1, -np.inf, 1e-11), + (0, 1, 1.23, 1.23, 1e-11), + (0, 1, 1e50, 1e50, 1e-11), + (1e-5, 0, 700, 695.0421608273609, 1e-11), + (1e-5, 0, 1e3, 995.40052566540066, 1e-11), + (1e-5, 100, 1e3, 640.8197935670078, 1e-11), + (1e-3, 0, 1e4, 9987.2229532297262, 1e-11), + (1e-3, 0, 1e5, 99641.920687169507, 1e-11), + (1e-3, 0, 1e6, 994118.55560054416, 1e-11), # maxterms=1_000_000 + (1e-3, 10, 1e5, 99595.47710802537, 1e-11), + (1e-3, 50, 1e5, 99401.240922855647, 1e-3), + (1e-3, 100, 1e5, 99143.465191656527, np.nan), + (0.5, 0, 1e5, 4074.1112442197941, 1e-11), + (0.5, 0, 1e7, 87724.552120038896, 1e-11), + (0.5, 100, 1e5, 3350.3928746306163, np.nan), + (0.5, 100, 1e7, 86696.109975301719, 1e-11), + (1, 0, 1e5, 634.06765787997266, 1e-11), + (1, 0, 1e8, 20003.339639312035, 1e-11), + (1.5, 0, 1e5, 197.01777556071194, 1e-11), + (1.5, 0, 1e8, 3108.987414395706, 1e-11), + (1.5, 100, 1e8, 2354.8915946283275, np.nan), + (5, 0, 1e5, 9.8980480013203547, 1e-11), + (5, 0, 1e8, 33.642337258687465, 1e-11), + (5, 0, 1e12, 157.53704288117429, 1e-11), + (5, 100, 1e5, -359.13419630792148, 1e-11), + (5, 100, 1e12, -337.07722086995229, 1e-4), + (5, 100, 1e20, 2588.2471229986845, 2e-6), + (100, 0, 1e5, -347.62127990460517, 1e-11), + (100, 0, 1e20, -313.08250350969449, 1e-11), + (100, 100, 1e5, -359.1342053695754, 1e-11), + (100, 100, 1e20, -359.1342053695754, 1e-11), + ] +) +def test_log_wright_bessel(a, b, x, phi, accuracy): + """Test for log_wright_bessel, in particular for large x.""" + if np.isnan(accuracy): + assert np.isnan(log_wright_bessel(a, b, x)) + else: + assert_allclose(log_wright_bessel(a, b, x), phi, rtol=accuracy) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_wrightomega.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_wrightomega.py new file mode 100644 index 0000000000000000000000000000000000000000..c1a93ca007e42fea3a0dec634c51b37f03effa9e --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_wrightomega.py @@ -0,0 +1,117 @@ +import pytest +import numpy as np +from numpy.testing import assert_, assert_equal, assert_allclose + +import scipy.special as sc +from scipy.special._testutils import assert_func_equal + + +def test_wrightomega_nan(): + pts = [complex(np.nan, 0), + complex(0, np.nan), + complex(np.nan, np.nan), + complex(np.nan, 1), + complex(1, np.nan)] + for p in pts: + res = sc.wrightomega(p) + assert_(np.isnan(res.real)) + assert_(np.isnan(res.imag)) + + +def test_wrightomega_inf_branch(): + pts = [complex(-np.inf, np.pi/4), + complex(-np.inf, -np.pi/4), + complex(-np.inf, 3*np.pi/4), + complex(-np.inf, -3*np.pi/4)] + expected_results = [complex(0.0, 0.0), + complex(0.0, -0.0), + complex(-0.0, 0.0), + complex(-0.0, -0.0)] + for p, expected in zip(pts, expected_results): + res = sc.wrightomega(p) + # We can't use assert_equal(res, expected) because in older versions of + # numpy, assert_equal doesn't check the sign of the real and imaginary + # parts when comparing complex zeros. It does check the sign when the + # arguments are *real* scalars. + assert_equal(res.real, expected.real) + assert_equal(res.imag, expected.imag) + + +def test_wrightomega_inf(): + pts = [complex(np.inf, 10), + complex(-np.inf, 10), + complex(10, np.inf), + complex(10, -np.inf)] + for p in pts: + assert_equal(sc.wrightomega(p), p) + + +def test_wrightomega_singular(): + pts = [complex(-1.0, np.pi), + complex(-1.0, -np.pi)] + for p in pts: + res = sc.wrightomega(p) + assert_equal(res, -1.0) + assert_(np.signbit(res.imag) == np.bool_(False)) + + +@pytest.mark.parametrize('x, desired', [ + (-np.inf, 0), + (np.inf, np.inf), +]) +def test_wrightomega_real_infinities(x, desired): + assert sc.wrightomega(x) == desired + + +def test_wrightomega_real_nan(): + assert np.isnan(sc.wrightomega(np.nan)) + + +def test_wrightomega_real_series_crossover(): + desired_error = 2 * np.finfo(float).eps + crossover = 1e20 + x_before_crossover = np.nextafter(crossover, -np.inf) + x_after_crossover = np.nextafter(crossover, np.inf) + # Computed using Mpmath + desired_before_crossover = 99999999999999983569.948 + desired_after_crossover = 100000000000000016337.948 + assert_allclose( + sc.wrightomega(x_before_crossover), + desired_before_crossover, + atol=0, + rtol=desired_error, + ) + assert_allclose( + sc.wrightomega(x_after_crossover), + desired_after_crossover, + atol=0, + rtol=desired_error, + ) + + +def test_wrightomega_exp_approximation_crossover(): + desired_error = 2 * np.finfo(float).eps + crossover = -50 + x_before_crossover = np.nextafter(crossover, np.inf) + x_after_crossover = np.nextafter(crossover, -np.inf) + # Computed using Mpmath + desired_before_crossover = 1.9287498479639314876e-22 + desired_after_crossover = 1.9287498479639040784e-22 + assert_allclose( + sc.wrightomega(x_before_crossover), + desired_before_crossover, + atol=0, + rtol=desired_error, + ) + assert_allclose( + sc.wrightomega(x_after_crossover), + desired_after_crossover, + atol=0, + rtol=desired_error, + ) + + +def test_wrightomega_real_versus_complex(): + x = np.linspace(-500, 500, 1001) + results = sc.wrightomega(x + 0j).real + assert_func_equal(sc.wrightomega, results, x, atol=0, rtol=1e-14) diff --git a/venv/lib/python3.12/site-packages/scipy/special/tests/test_zeta.py b/venv/lib/python3.12/site-packages/scipy/special/tests/test_zeta.py new file mode 100644 index 0000000000000000000000000000000000000000..988f9a716196fb8f65a92a0a2e894038c2474cfa --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/special/tests/test_zeta.py @@ -0,0 +1,301 @@ +import scipy +import scipy.special as sc +import sys +import numpy as np +import pytest + +from numpy.testing import assert_equal, assert_allclose + + +def test_zeta(): + assert_allclose(sc.zeta(2,2), np.pi**2/6 - 1, rtol=1e-12) + + +def test_zetac(): + # Expected values in the following were computed using Wolfram + # Alpha's `Zeta[x] - 1` + x = [-2.1, 0.8, 0.9999, 9, 50, 75] + desired = [ + -0.9972705002153750, + -5.437538415895550, + -10000.42279161673, + 0.002008392826082214, + 8.881784210930816e-16, + 2.646977960169853e-23, + ] + assert_allclose(sc.zetac(x), desired, rtol=1e-12) + + +def test_zetac_special_cases(): + assert sc.zetac(np.inf) == 0 + assert np.isnan(sc.zetac(-np.inf)) + assert sc.zetac(0) == -1.5 + assert sc.zetac(1.0) == np.inf + + assert_equal(sc.zetac([-2, -50, -100]), -1) + + +def test_riemann_zeta_special_cases(): + assert np.isnan(sc.zeta(np.nan)) + assert sc.zeta(np.inf) == 1 + assert sc.zeta(0) == -0.5 + + # Riemann zeta is zero add negative even integers. + assert_equal(sc.zeta([-2, -4, -6, -8, -10]), 0) + + assert_allclose(sc.zeta(2), np.pi**2/6, rtol=1e-12) + assert_allclose(sc.zeta(4), np.pi**4/90, rtol=1e-12) + + +def test_riemann_zeta_avoid_overflow(): + s = -260.00000000001 + desired = -5.6966307844402683127e+297 # Computed with Mpmath + assert_allclose(sc.zeta(s), desired, atol=0, rtol=5e-14) + + +@pytest.mark.parametrize( + "z, desired, rtol", + [ + ## Test cases taken from mpmath with the script: + + # import numpy as np + # import scipy.stats as stats + + # from mpmath import mp + + # # seed = np.random.SeedSequence().entropy + # seed = 154689806791763421822480125722191067828 + # rng = np.random.default_rng(seed) + # default_rtol = 1e-13 + + # # A small point in each quadrant outside of the critical strip + # cases = [] + # for x_sign, y_sign in [1, 1], [1, -1], [-1, 1], [-1, -1]: + # x = x_sign * rng.uniform(2, 8) + # y = y_sign * rng.uniform(2, 8) + # z = x + y*1j + # reference = complex(mp.zeta(z)) + # cases.append((z, reference, default_rtol)) + + # # Moderately large imaginary part in each quadrant outside of critical strip + # for x_sign, y_sign in [1, 1], [1, -1], [-1, 1], [-1, -1]: + # x = x_sign * rng.uniform(2, 8) + # y = y_sign * rng.uniform(50, 80) + # z = x + y*1j + # reference = complex(mp.zeta(z)) + # cases.append((z, reference, default_rtol)) + + # # points in critical strip + # x = rng.uniform(0.0, 1.0, size=5) + # y = np.exp(rng.uniform(0, 5, size=5)) + # z = x + y*1j + # for t in z: + # reference = complex(mp.zeta(t)) + # cases.append((complex(t), reference, default_rtol)) + # z = x - y*1j + # for t in z: + # reference = complex(mp.zeta(t)) + # cases.append((complex(t), reference, default_rtol)) + + # # Near small trivial zeros + # x = np.array([-2, -4, -6, -8]) + # y = np.array([1e-15, -1e-15]) + # x, y = np.meshgrid(x, y) + # x, y = x.ravel(), y.ravel() + # z = x + y*1j + # for t in z: + # reference = complex(mp.zeta(t)) + # cases.append((complex(t), reference, 1e-7)) + + # # Some other points near real axis + # x = np.array([-0.5, 0, 0.2, 0.75]) + # y = np.array([1e-15, -1e-15]) + # x, y = np.meshgrid(x, y) + # x, y = x.ravel(), y.ravel() + # z = x + y*1j + # for t in z: + # reference = complex(mp.zeta(t)) + # cases.append((complex(t), reference, 1e-7)) + + # # Moderately large real part + # x = np.array([49.33915930750887, 50.55805244181687]) + # y = rng.uniform(20, 100, size=3) + # x, y = np.meshgrid(x, y) + # x, y = x.ravel(), y.ravel() + # z = x + y*1j + # for t in z: + # reference = complex(mp.zeta(t)) + # cases.append((complex(t), reference, default_rtol)) + + # # Very large imaginary part + # x = np.array([0.5, 34.812847097948854, 50.55805244181687]) + # y = np.array([1e6, -1e6]) + # x, y = np.meshgrid(x, y) + # x, y = x.ravel(), y.ravel() + # z = x + y*1j + # for t in z: + # reference = complex(mp.zeta(t)) + # rtol = 1e-7 if t.real == 0.5 else default_rtol + # cases.append((complex(t), reference, rtol)) + # + # # Naive implementation of reflection formula suffers internal overflow + # x = -rng.uniform(200, 300, 3) + # y = np.array([rng.uniform(10, 30), -rng.uniform(10, 30)]) + # x, y = np.meshgrid(x, y) + # x, y = x.ravel(), y.ravel() + # z = x + y*1j + # for t in z: + # reference = complex(mp.zeta(t)) + # cases.append((complex(t), reference, default_rtol)) + # + # A small point in each quadrant outside of the critical strip + ((3.12838509346655+7.111085974836645j), + (1.0192654793474945+0.08795174413289127j), + 1e-13), + ((7.06791362314716-7.219497492626728j), + (1.0020740683598117-0.006752725913243711j), + 1e-13), + ((-6.806227077655519+2.724411451005281j), + (0.06312488213559667-0.061641496333765956j), + 1e-13), + ((-3.0170751511621026-6.3686522550665945j), + (-0.10330747857150148-1.214541994832571j), + 1e-13), + # Moderately large imaginary part in each quadrant outside of critical strip + ((6.133994402212294+60.03091448000761j), + (0.9885701843417336+0.009636925981078128j), + 1e-13), + ((6.17268142822657-64.74883149743795j), + (1.0080474225840865+0.012032804974965354j), + 1e-13), + ((-3.462191939791879+76.16258975567534j), + (18672.072070850158+2908.5104826247184j), + 1e-13), + ((-6.955735216531752-74.75791554155748j), + (-77672258.72276545+71625206.0401107j), + 1e-13), + # Points in critical strip + ((0.4088038289823922+1.4596830498094384j), + (0.3032837969400845-0.47272237994110344j), + 1e-13), + ((0.9673493951209633+4.918968547259143j), + (0.7488756907431944+0.17281553371482428j), + 1e-13), + ((0.8692482679977754+66.6142398421354j), + (0.5831942469552066-0.26848904799062334j), + 1e-13), + ((0.42771847720003764+21.783747851715468j), + (0.4767032638444329+0.6898148744603123j), + 1e-13), + ((0.20479494678428956+33.17656449538932j), + (-0.6983038977487848+0.18060923618150224j), + 1e-13), + ((0.4088038289823922-1.4596830498094384j), + (0.3032837969400845+0.47272237994110344j), + 1e-13), + ((0.9673493951209633-4.918968547259143j), + (0.7488756907431944-0.17281553371482428j), + 1e-13), + ((0.8692482679977754-66.6142398421354j), + (0.5831942469552066+0.26848904799062334j), + 1e-13), + ((0.42771847720003764-21.783747851715468j), + (0.4767032638444329-0.6898148744603123j), + 1e-13), + ((0.20479494678428956-33.17656449538932j), + (-0.6983038977487848-0.18060923618150224j), + 1e-13), + # Near small trivial zeros + ((-2+1e-15j), (3.288175809370978e-32-3.0448457058393275e-17j), 1e-07), + ((-4+1e-15j), (-2.868707923051182e-33+7.983811450268625e-18j), 1e-07), + ((-6+1e-15j), (-1.7064292323640116e-34-5.8997591435159376e-18j), 1e-07), + ((-8+1e-15j), (2.5060859548261706e-33+8.316161985602247e-18j), 1e-07), + ((-2-1e-15j), (3.288175809371319e-32+3.0448457058393275e-17j), 1e-07), + ((-4-1e-15j), (-2.8687079230520114e-33-7.983811450268625e-18j), 1e-07), + ((-6-1e-15j), (-1.70642923235801e-34+5.8997591435159376e-18j), 1e-07), + ((-8-1e-15j), (2.5060859548253293e-33-8.316161985602247e-18j), 1e-07), + # Some other points near real axis + ((-0.5+1e-15j), (-0.20788622497735457-3.608543395999408e-16j), 1e-07), + (1e-15j, (-0.5-9.189384239689193e-16j), 1e-07), + ((0.2+1e-15j), (-0.7339209248963406-1.4828001150329085e-15j), 1e-07), + ((0.75+1e-15j), (-3.4412853869452227-1.5924832114302393e-14j), 1e-13), + ((-0.5-1e-15j), (-0.20788622497735457+3.608543395999408e-16j), 1e-07), + (-1e-15j, (-0.5+9.189387416062746e-16j), 1e-07), + ((0.2-1e-15j), (-0.7339209248963406+1.4828004007675122e-15j), 1e-07), + ((0.75-1e-15j), (-3.4412853869452227+1.5924831974403957e-14j), 1e-13), + # Moderately large real part + ((49.33915930750887+53.213478698903955j), + (1.0000000000000009+1.0212452494616078e-15j), + 1e-13), + ((50.55805244181687+53.213478698903955j), + (1.0000000000000004+4.387394180390787e-16j), + 1e-13), + ((49.33915930750887+40.6366015728302j), + (0.9999999999999986-1.502268709924849e-16j), + 1e-13), + ((50.55805244181687+40.6366015728302j), + (0.9999999999999994-6.453929613571651e-17j), + 1e-13), + ((49.33915930750887+85.83555435273925j), + (0.9999999999999987-2.7014400611995846e-16j), + 1e-13), + ((50.55805244181687+85.83555435273925j), + (0.9999999999999994-1.160571605555322e-16j), + 1e-13), + # Very large imaginary part + ((0.5+1e6j), (0.0760890697382271+2.805102101019299j), 1e-07), + ((34.812847097948854+1e6j), + (1.0000000000102545+3.150848654056419e-11j), + 1e-13), + ((50.55805244181687+1e6j), + (1.0000000000000002+5.736517078070873e-16j), + 1e-13), + ((0.5-1e6j), (0.0760890697382271-2.805102101019299j), 1e-07), + ((34.812847097948854-1e6j), + (1.0000000000102545-3.150848654056419e-11j), + 1e-13), + ((50.55805244181687-1e6j), + (1.0000000000000002-5.736517078070873e-16j), + 1e-13), + ((-294.86605461349745+13.992648136816397j), (-np.inf+np.inf*1j), 1e-13), + ((-294.86605461349745-16.147667799398363j), (np.inf-np.inf*1j), 1e-13), + ] +) +def test_riemann_zeta_complex(z, desired, rtol): + assert_allclose(sc.zeta(z), desired, rtol=rtol) + + +# Some of the test cases below fail for intel compilers +cpp_compiler = scipy.__config__.CONFIG["Compilers"]["c++"]["name"] +gcc_linux = cpp_compiler == "gcc" and sys.platform == "linux" +clang_macOS = cpp_compiler == "clang" and sys.platform == "darwin" + + +@pytest.mark.skipif( + not (gcc_linux or clang_macOS), + reason="Underflow may not be avoided on other platforms", +) +@pytest.mark.parametrize( + "z, desired, rtol", + [ + # Test cases generated as part of same script for + # test_riemann_zeta_complex. These cases are split off because + # they fail on some platforms. + # + # Naive implementation of reflection formula suffers internal overflow + ((-217.40285743524163+13.992648136816397j), + (-6.012818500554211e+249-1.926943776932387e+250j), + 5e-13,), + ((-237.71710702931668+13.992648136816397j), + (-8.823803086106129e+281-5.009074181335139e+281j), + 1e-13,), + ((-217.40285743524163-16.147667799398363j), + (-5.111612904844256e+251-4.907132127666742e+250j), + 5e-13,), + ((-237.71710702931668-16.147667799398363j), + (-1.3256112779883167e+283-2.253002003455494e+283j), + 5e-13,), + ], +) +def test_riemann_zeta_complex_avoid_underflow(z, desired, rtol): + assert_allclose(sc.zeta(z), desired, rtol=rtol) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__init__.py b/venv/lib/python3.12/site-packages/scipy/stats/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e67ecbe12091add03d50815fe8307e746af3b85b --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/__init__.py @@ -0,0 +1,644 @@ +""" +.. _statsrefmanual: + +========================================== +Statistical functions (:mod:`scipy.stats`) +========================================== + +.. currentmodule:: scipy.stats + +This module contains a large number of probability distributions, +summary and frequency statistics, correlation functions and statistical +tests, masked statistics, kernel density estimation, quasi-Monte Carlo +functionality, and more. + +Statistics is a very large area, and there are topics that are out of scope +for SciPy and are covered by other packages. Some of the most important ones +are: + +- `statsmodels `__: + regression, linear models, time series analysis, extensions to topics + also covered by ``scipy.stats``. +- `Pandas `__: tabular data, time series + functionality, interfaces to other statistical languages. +- `PyMC `__: Bayesian statistical + modeling, probabilistic machine learning. +- `scikit-learn `__: classification, regression, + model selection. +- `Seaborn `__: statistical data visualization. +- `rpy2 `__: Python to R bridge. + + +Probability distributions +========================= + +Each univariate distribution is an instance of a subclass of `rv_continuous` +(`rv_discrete` for discrete distributions): + +.. autosummary:: + :toctree: generated/ + + rv_continuous + rv_discrete + rv_histogram + +Continuous distributions +------------------------ + +.. autosummary:: + :toctree: generated/ + + alpha -- Alpha + anglit -- Anglit + arcsine -- Arcsine + argus -- Argus + beta -- Beta + betaprime -- Beta Prime + bradford -- Bradford + burr -- Burr (Type III) + burr12 -- Burr (Type XII) + cauchy -- Cauchy + chi -- Chi + chi2 -- Chi-squared + cosine -- Cosine + crystalball -- Crystalball + dgamma -- Double Gamma + dpareto_lognorm -- Double Pareto Lognormal + dweibull -- Double Weibull + erlang -- Erlang + expon -- Exponential + exponnorm -- Exponentially Modified Normal + exponweib -- Exponentiated Weibull + exponpow -- Exponential Power + f -- F (Snecdor F) + fatiguelife -- Fatigue Life (Birnbaum-Saunders) + fisk -- Fisk + foldcauchy -- Folded Cauchy + foldnorm -- Folded Normal + genlogistic -- Generalized Logistic + gennorm -- Generalized normal + genpareto -- Generalized Pareto + genexpon -- Generalized Exponential + genextreme -- Generalized Extreme Value + gausshyper -- Gauss Hypergeometric + gamma -- Gamma + gengamma -- Generalized gamma + genhalflogistic -- Generalized Half Logistic + genhyperbolic -- Generalized Hyperbolic + geninvgauss -- Generalized Inverse Gaussian + gibrat -- Gibrat + gompertz -- Gompertz (Truncated Gumbel) + gumbel_r -- Right Sided Gumbel, Log-Weibull, Fisher-Tippett, Extreme Value Type I + gumbel_l -- Left Sided Gumbel, etc. + halfcauchy -- Half Cauchy + halflogistic -- Half Logistic + halfnorm -- Half Normal + halfgennorm -- Generalized Half Normal + hypsecant -- Hyperbolic Secant + invgamma -- Inverse Gamma + invgauss -- Inverse Gaussian + invweibull -- Inverse Weibull + irwinhall -- Irwin-Hall + jf_skew_t -- Jones and Faddy Skew-T + johnsonsb -- Johnson SB + johnsonsu -- Johnson SU + kappa4 -- Kappa 4 parameter + kappa3 -- Kappa 3 parameter + ksone -- Distribution of Kolmogorov-Smirnov one-sided test statistic + kstwo -- Distribution of Kolmogorov-Smirnov two-sided test statistic + kstwobign -- Limiting Distribution of scaled Kolmogorov-Smirnov two-sided test statistic. + landau -- Landau + laplace -- Laplace + laplace_asymmetric -- Asymmetric Laplace + levy -- Levy + levy_l + levy_stable + logistic -- Logistic + loggamma -- Log-Gamma + loglaplace -- Log-Laplace (Log Double Exponential) + lognorm -- Log-Normal + loguniform -- Log-Uniform + lomax -- Lomax (Pareto of the second kind) + maxwell -- Maxwell + mielke -- Mielke's Beta-Kappa + moyal -- Moyal + nakagami -- Nakagami + ncx2 -- Non-central chi-squared + ncf -- Non-central F + nct -- Non-central Student's T + norm -- Normal (Gaussian) + norminvgauss -- Normal Inverse Gaussian + pareto -- Pareto + pearson3 -- Pearson type III + powerlaw -- Power-function + powerlognorm -- Power log normal + powernorm -- Power normal + rdist -- R-distribution + rayleigh -- Rayleigh + rel_breitwigner -- Relativistic Breit-Wigner + rice -- Rice + recipinvgauss -- Reciprocal Inverse Gaussian + semicircular -- Semicircular + skewcauchy -- Skew Cauchy + skewnorm -- Skew normal + studentized_range -- Studentized Range + t -- Student's T + trapezoid -- Trapezoidal + triang -- Triangular + truncexpon -- Truncated Exponential + truncnorm -- Truncated Normal + truncpareto -- Truncated Pareto + truncweibull_min -- Truncated minimum Weibull distribution + tukeylambda -- Tukey-Lambda + uniform -- Uniform + vonmises -- Von-Mises (Circular) + vonmises_line -- Von-Mises (Line) + wald -- Wald + weibull_min -- Minimum Weibull (see Frechet) + weibull_max -- Maximum Weibull (see Frechet) + wrapcauchy -- Wrapped Cauchy + +The ``fit`` method of the univariate continuous distributions uses +maximum likelihood estimation to fit the distribution to a data set. +The ``fit`` method can accept regular data or *censored data*. +Censored data is represented with instances of the `CensoredData` +class. + +.. autosummary:: + :toctree: generated/ + + CensoredData + + +Multivariate distributions +-------------------------- + +.. autosummary:: + :toctree: generated/ + + multivariate_normal -- Multivariate normal distribution + matrix_normal -- Matrix normal distribution + dirichlet -- Dirichlet + dirichlet_multinomial -- Dirichlet multinomial distribution + wishart -- Wishart + invwishart -- Inverse Wishart + multinomial -- Multinomial distribution + special_ortho_group -- SO(N) group + ortho_group -- O(N) group + unitary_group -- U(N) group + random_correlation -- random correlation matrices + multivariate_t -- Multivariate t-distribution + multivariate_hypergeom -- Multivariate hypergeometric distribution + normal_inverse_gamma -- Normal-inverse-gamma distribution + random_table -- Distribution of random tables with given marginals + uniform_direction -- Uniform distribution on S(N-1) + vonmises_fisher -- Von Mises-Fisher distribution + matrix_t -- Matrix variate t distribution + +`scipy.stats.multivariate_normal` methods accept instances +of the following class to represent the covariance. + +.. autosummary:: + :toctree: generated/ + + Covariance -- Representation of a covariance matrix + + +Discrete distributions +---------------------- + +.. autosummary:: + :toctree: generated/ + + bernoulli -- Bernoulli + betabinom -- Beta-Binomial + betanbinom -- Beta-Negative Binomial + binom -- Binomial + boltzmann -- Boltzmann (Truncated Discrete Exponential) + dlaplace -- Discrete Laplacian + geom -- Geometric + hypergeom -- Hypergeometric + logser -- Logarithmic (Log-Series, Series) + nbinom -- Negative Binomial + nchypergeom_fisher -- Fisher's Noncentral Hypergeometric + nchypergeom_wallenius -- Wallenius's Noncentral Hypergeometric + nhypergeom -- Negative Hypergeometric + planck -- Planck (Discrete Exponential) + poisson -- Poisson + poisson_binom -- Poisson Binomial + randint -- Discrete Uniform + skellam -- Skellam + yulesimon -- Yule-Simon + zipf -- Zipf (Zeta) + zipfian -- Zipfian + + +An overview of statistical functions is given below. Many of these functions +have a similar version in `scipy.stats.mstats` which work for masked arrays. + +Summary statistics +================== + +.. autosummary:: + :toctree: generated/ + + describe -- Descriptive statistics + gmean -- Geometric mean + hmean -- Harmonic mean + pmean -- Power mean + kurtosis -- Fisher or Pearson kurtosis + mode -- Modal value + moment -- Central moment + lmoment + expectile -- Expectile + skew -- Skewness + kstat -- + kstatvar -- + tmean -- Truncated arithmetic mean + tvar -- Truncated variance + tmin -- + tmax -- + tstd -- + tsem -- + variation -- Coefficient of variation + rankdata + tiecorrect + trim_mean + gstd -- Geometric Standard Deviation + iqr + sem + bayes_mvs + mvsdist + entropy + differential_entropy + median_abs_deviation + +Frequency statistics +==================== + +.. autosummary:: + :toctree: generated/ + + quantile + estimated_cdf + cumfreq + relfreq + percentileofscore + scoreatpercentile + +.. autosummary:: + :toctree: generated/ + + binned_statistic -- Compute a binned statistic for a set of data. + binned_statistic_2d -- Compute a 2-D binned statistic for a set of data. + binned_statistic_dd -- Compute a d-D binned statistic for a set of data. + +.. _hypotests: + +Hypothesis Tests and related functions +====================================== +SciPy has many functions for performing hypothesis tests that return a +test statistic and a p-value, and several of them return confidence intervals +and/or other related information. + +The headings below are based on common uses of the functions within, but due to +the wide variety of statistical procedures, any attempt at coarse-grained +categorization will be imperfect. Also, note that tests within the same heading +are not interchangeable in general (e.g. many have different distributional +assumptions). + +One Sample Tests / Paired Sample Tests +-------------------------------------- +One sample tests are typically used to assess whether a single sample was +drawn from a specified distribution or a distribution with specified properties +(e.g. zero mean). + +.. autosummary:: + :toctree: generated/ + + ttest_1samp + binomtest + quantile_test + skewtest + kurtosistest + normaltest + jarque_bera + shapiro + anderson + cramervonmises + ks_1samp + goodness_of_fit + chisquare + power_divergence + +Paired sample tests are often used to assess whether two samples were drawn +from the same distribution; they differ from the independent sample tests below +in that each observation in one sample is treated as paired with a +closely-related observation in the other sample (e.g. when environmental +factors are controlled between observations within a pair but not among pairs). +They can also be interpreted or used as one-sample tests (e.g. tests on the +mean or median of *differences* between paired observations). + +.. autosummary:: + :toctree: generated/ + + ttest_rel + wilcoxon + +Association/Correlation Tests +----------------------------- + +These tests are often used to assess whether there is a relationship (e.g. +linear) between paired observations in multiple samples or among the +coordinates of multivariate observations. + +.. autosummary:: + :toctree: generated/ + + linregress + pearsonr + spearmanrho + pointbiserialr + kendalltau + chatterjeexi + weightedtau + somersd + siegelslopes + theilslopes + page_trend_test + multiscale_graphcorr + spearmanr + +These association tests and are to work with samples in the form of contingency +tables. Supporting functions are available in `scipy.stats.contingency`. + +.. autosummary:: + :toctree: generated/ + + chi2_contingency + fisher_exact + barnard_exact + boschloo_exact + +Independent Sample Tests +------------------------ +Independent sample tests are typically used to assess whether multiple samples +were independently drawn from the same distribution or different distributions +with a shared property (e.g. equal means). + +Some tests are specifically for comparing two samples. + +.. autosummary:: + :toctree: generated/ + + ttest_ind_from_stats + poisson_means_test + ttest_ind + mannwhitneyu + bws_test + ranksums + brunnermunzel + mood + ansari + cramervonmises_2samp + epps_singleton_2samp + ks_2samp + kstest + +Others are generalized to multiple samples. + +.. autosummary:: + :toctree: generated/ + + f_oneway + tukey_hsd + dunnett + kruskal + alexandergovern + fligner + levene + bartlett + median_test + friedmanchisquare + anderson_ksamp + +Resampling and Monte Carlo Methods +---------------------------------- +The following functions can reproduce the p-value and confidence interval +results of most of the functions above, and often produce accurate results in a +wider variety of conditions. They can also be used to perform hypothesis tests +and generate confidence intervals for custom statistics. This flexibility comes +at the cost of greater computational requirements and stochastic results. + +.. autosummary:: + :toctree: generated/ + + monte_carlo_test + permutation_test + bootstrap + power + +Instances of the following object can be passed into some hypothesis test +functions to perform a resampling or Monte Carlo version of the hypothesis +test. + +.. autosummary:: + :toctree: generated/ + + MonteCarloMethod + PermutationMethod + BootstrapMethod + +Multiple Hypothesis Testing and Meta-Analysis +--------------------------------------------- +These functions are for assessing the results of individual tests as a whole. +Functions for performing specific multiple hypothesis tests (e.g. post hoc +tests) are listed above. + +.. autosummary:: + :toctree: generated/ + + combine_pvalues + false_discovery_control + +Random Variables +================ + +.. autosummary:: + :toctree: generated/ + + make_distribution + Normal + Logistic + Uniform + Binomial + Mixture + order_statistic + truncate + abs + exp + log + +Other statistical functionality +=============================== +.. toctree:: + :maxdepth: 1 + + stats.qmc + stats.contingency + stats.mstats + stats.sampling + +Transformations +--------------- + +.. autosummary:: + :toctree: generated/ + + boxcox + boxcox_normmax + boxcox_llf + yeojohnson + yeojohnson_normmax + yeojohnson_llf + obrientransform + sigmaclip + trimboth + trim1 + zmap + zscore + gzscore + +Statistical distances +--------------------- + +.. autosummary:: + :toctree: generated/ + + wasserstein_distance + wasserstein_distance_nd + energy_distance + +Fitting / Survival Analysis +--------------------------- + +.. autosummary:: + :toctree: generated/ + + fit + ecdf + logrank + +Directional statistical functions +--------------------------------- + +.. autosummary:: + :toctree: generated/ + + directional_stats + circmean + circvar + circstd + +Sensitivity Analysis +-------------------- + +.. autosummary:: + :toctree: generated/ + + sobol_indices + +Plot-tests +---------- + +.. autosummary:: + :toctree: generated/ + + ppcc_max + ppcc_plot + probplot + boxcox_normplot + yeojohnson_normplot + +Univariate and multivariate kernel density estimation +----------------------------------------------------- + +.. autosummary:: + :toctree: generated/ + + gaussian_kde + +Warnings / Errors used in :mod:`scipy.stats` +-------------------------------------------- + +.. autosummary:: + :toctree: generated/ + + DegenerateDataWarning + ConstantInputWarning + NearConstantInputWarning + FitError + +Result classes used in :mod:`scipy.stats` +----------------------------------------- + +.. warning:: + + These classes are private, but they are included here because instances + of them are returned by other statistical functions. User import and + instantiation is not supported. + +.. toctree:: + :maxdepth: 2 + + stats._result_classes + +""" # noqa: E501 + +from ._warnings_errors import (ConstantInputWarning, NearConstantInputWarning, + DegenerateDataWarning, FitError) +from ._stats_py import * +from ._variation import variation +from .distributions import * +from ._morestats import * +from ._multicomp import * +from ._binomtest import binomtest +from ._binned_statistic import * +from ._kde import gaussian_kde +from . import mstats +from . import qmc +from ._multivariate import * +from . import contingency +from .contingency import chi2_contingency +from ._censored_data import CensoredData +from ._resampling import (bootstrap, monte_carlo_test, permutation_test, power, + MonteCarloMethod, PermutationMethod, BootstrapMethod) +from ._entropy import * +from ._hypotests import * +from ._page_trend_test import page_trend_test +from ._mannwhitneyu import mannwhitneyu +from ._bws_test import bws_test +from ._fit import fit, goodness_of_fit +from ._covariance import Covariance +from ._sensitivity_analysis import * +from ._survival import * +from ._distribution_infrastructure import ( + make_distribution, Mixture, order_statistic, truncate, exp, log, abs +) +from ._new_distributions import Normal, Logistic, Uniform, Binomial +from ._mgc import multiscale_graphcorr +from ._correlation import chatterjeexi, spearmanrho, theilslopes, siegelslopes +from ._quantile import quantile, estimated_cdf + + +# Deprecated namespaces, to be removed in v2.0.0 +from . import ( + biasedurn, kde, morestats, mstats_basic, mstats_extras, mvn, stats +) + + +__all__ = [s for s in dir() if not s.startswith("_")] # Remove dunders. + +from scipy._lib._testutils import PytestTester +test = PytestTester(__name__) +del PytestTester diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9336de319b919a5eff31e35f27e671a4db8dea6c Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/__init__.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_axis_nan_policy.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_axis_nan_policy.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6265c431eb48c457b9651b2a96c486db6344f01c Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_axis_nan_policy.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_binned_statistic.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_binned_statistic.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..73fc74bab9d415661f53938220c8cca87e08d802 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_binned_statistic.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_binomtest.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_binomtest.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d72c0304f8fc49ea64fe39eab4d14b1cfec59905 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_binomtest.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_bws_test.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_bws_test.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9aae41e4b936442cf79a8af12fd36f267c31e503 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_bws_test.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_censored_data.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_censored_data.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6b246927f2a1dd11ea786b28477996f4cadb2808 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_censored_data.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_common.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_common.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5b5b6fc0bb53c56026c76b56c6a0c3d2773f816b Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_common.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_constants.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_constants.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ed1bf9f5bf7f5680b6495bbc107b7109552ef9ca Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_constants.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_continued_fraction.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_continued_fraction.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0b00713ea6a52d2bbba5e5e4a9bdada5a3e91e03 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_continued_fraction.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_continuous_distns.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_continuous_distns.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7b3f304aa700f89ace98a2f15523dc6c1cd45b52 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_continuous_distns.cpython-312.pyc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60a0457f16fca789995ff35f87ba330d5632725c33d727993e367a9eca90f669 +size 593057 diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_correlation.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_correlation.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..053eb99a3669de02ea4348149ed686c2109dc039 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_correlation.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_covariance.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_covariance.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c0f25152e2dd83e8112ce42afc27544ae20ca29c Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_covariance.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_crosstab.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_crosstab.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7b31f97ae7d0fac25c3c99451b7a6b63a4b4dec9 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_crosstab.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_discrete_distns.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_discrete_distns.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6edd4413625d5c5b2e6638d9c97d3b8a3156217d Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_discrete_distns.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_distn_infrastructure.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_distn_infrastructure.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e000206e848bebb1cdeec897ca6f4de6df7134a6 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_distn_infrastructure.cpython-312.pyc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ac9c5e28172821be85e084056bb18bd1803e24ca88a0f46927428f3dc122defb +size 181122 diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_distr_params.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_distr_params.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..01b353a0100fed774a50aacd83279076f1c99675 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_distr_params.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_distribution_infrastructure.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_distribution_infrastructure.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6da74ed32cbe9fcf995e1e22ab34e3ba047f5201 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_distribution_infrastructure.cpython-312.pyc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1ca250330d71a56b28b77bbcd1751a52fab0ef3669610869560b3450d7b51ff5 +size 272577 diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_entropy.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_entropy.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..457ddcf139536f95649817e4b9058ebc81bf11e9 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_entropy.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_finite_differences.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_finite_differences.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ec3cdd2eeaa039a609deb9b821c81938f3ae5d0d Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_finite_differences.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_fit.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_fit.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d630655deae4a5ab02f26ad91ac0a837a49553c3 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_fit.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_hypotests.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_hypotests.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c8be47d8cb2fc4b848f6a07a514d3d8cae91e84c Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_hypotests.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_kde.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_kde.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..296446d214267dbf9246ef3e49b01369f3505940 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_kde.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_ksstats.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_ksstats.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b2b0a81ba33e6212a4ceb148a6895c46e5c54270 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_ksstats.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_mannwhitneyu.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_mannwhitneyu.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..80eba74daf15a623998d345ad2327d7ea774eca2 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_mannwhitneyu.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_mgc.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_mgc.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ab623737ff9e922f775ccdefb276bd7fe1d45eb5 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_mgc.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_morestats.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_morestats.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6cbee83bd4db1e04279909ade467facbe7f49a22 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_morestats.cpython-312.pyc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:78e467b1d973b4dad0693d1e20e401ee3e73781e493f61af4ea374468c3b0cd4 +size 217681 diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_mstats_basic.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_mstats_basic.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a7a9f38d3a2ea2eac89e8e0eea501643f020acdd --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_mstats_basic.cpython-312.pyc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6c92589f86f96e912c6d897bfcb5eac419c3b2afc9354c991bfcc510f0b16294 +size 159527 diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_mstats_extras.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_mstats_extras.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..16cd02631d084c3d7790a470d510a7a3723e3ae5 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_mstats_extras.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_multicomp.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_multicomp.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..082c2fd8df7d29ef245fb1c1b6f563837b8e7ea6 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_multicomp.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_multivariate.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_multivariate.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5150dfba484c89ec1ae35a9e3241b9d7fb00368f --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_multivariate.cpython-312.pyc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2025e940dd16002a465ef1acf2205f2cf1aff1a19afc2e579d6a2e1d4c82953a +size 326287 diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_new_distributions.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_new_distributions.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..73020dfc3decb4d74bb351211ae5c699d9128f3a Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_new_distributions.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_odds_ratio.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_odds_ratio.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3e5c49ebe5677be4985c097aa9cf4bbd701d4b07 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_odds_ratio.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_page_trend_test.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_page_trend_test.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8278fece9a7c446d52acf0a32a1f011a01c766e5 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_page_trend_test.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_probability_distribution.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_probability_distribution.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..959fb926046f10f8b77f1911295615c3c072888e Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_probability_distribution.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_qmc.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_qmc.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dcb337c1a779724f84145e3fbe736a371db09529 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_qmc.cpython-312.pyc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3012cbc1800188908d1679674dc3be43f51bd4f232dacc3fb95d2bb94585e54e +size 120612 diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_qmvnt.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_qmvnt.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..283c05320f27863433f3f2654ac12866243c7609 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_qmvnt.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_quantile.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_quantile.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7c6f4ce6864d43bce2d5eef132c480d0800c5f9b Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_quantile.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_relative_risk.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_relative_risk.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2fe638fe2928331514db8cb07811028282461bd6 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_relative_risk.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_resampling.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_resampling.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..19736cb9e81fe0a44e4bfde112f6964de5acaa25 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_resampling.cpython-312.pyc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7f7d97c856736df7c426d0abd0b54843df8964660558139285993f2013339dd1 +size 108296 diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_result_classes.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_result_classes.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ea6b9b76b140eb996ea94bd3da0ba2a38605d68f Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_result_classes.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_sampling.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_sampling.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2544f3acc476f34543a10959627b0ccf8f7438fd Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_sampling.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_sensitivity_analysis.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_sensitivity_analysis.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ae1c1957e5d60b6f3a9ed63ad28064cfe101a34a Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_sensitivity_analysis.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_stats_mstats_common.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_stats_mstats_common.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..68b6443201d334924a8006096cf5910e5502d1b6 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_stats_mstats_common.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_stats_py.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_stats_py.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4994db26dfde0da16855b7b4961d6485d1d4e725 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_stats_py.cpython-312.pyc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:feccbe7ab1100ca3f671ae5b1cb45863078e213f5ca1ee7334a2aa0e404ce231 +size 467271 diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_survival.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_survival.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b421c36a456e37c0feaa8c0694cfc81418a0608d Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_survival.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_tukeylambda_stats.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_tukeylambda_stats.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1d4eca958df9991db0d78128ae6eca575c219c3d Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_tukeylambda_stats.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_variation.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_variation.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7a86d04f034253d7a53273158e9aa8bf152851f6 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_variation.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_warnings_errors.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_warnings_errors.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3d903c18da76e819e1ea04e5dc4b665803fe622e Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_warnings_errors.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_wilcoxon.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_wilcoxon.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..de0dbce4823d7312c2d24b6a89bea6fc8219131b Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/_wilcoxon.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/biasedurn.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/biasedurn.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..409607f0fdb61ca1216d14d7e3de4fb3af790ce9 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/biasedurn.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/contingency.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/contingency.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b54397a4791097a4666e8d31a67b1123e2328b13 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/contingency.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/distributions.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/distributions.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e25988c1d1de92470052679b2fab495cb9c95c92 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/distributions.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/kde.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/kde.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ffc59c2aa1976326f715fec409387df7af7a3175 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/kde.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/morestats.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/morestats.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..199dcfea2f587afe3b0b37c1c3cedf4a84ddf71d Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/morestats.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/mstats.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/mstats.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2229e1ca31d53dcb8667f8c64c50b50be4885218 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/mstats.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/mstats_basic.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/mstats_basic.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4eabe8a5d9751710cc3fa41bab1c400a361776d6 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/mstats_basic.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/mstats_extras.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/mstats_extras.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9050c7a868548faf2059957a3e43eabb4966e739 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/mstats_extras.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/mvn.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/mvn.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2c1a205dc94a201819cfa500921273311bf30a2b Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/mvn.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/qmc.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/qmc.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bfb52826377858b3f69c539f78466bc218e2fe3c Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/qmc.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/sampling.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/sampling.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..07dc31c14099e150c70f492dd038333223a82b18 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/sampling.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/stats.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/stats.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aa51cfebb8f6b8d6b8306476fbcff0b098f17618 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/__pycache__/stats.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_ansari_swilk_statistics.cpython-312-x86_64-linux-gnu.so b/venv/lib/python3.12/site-packages/scipy/stats/_ansari_swilk_statistics.cpython-312-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..0f8a4c1bddd72f60bdc1ab0fc9782d211d364157 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_ansari_swilk_statistics.cpython-312-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5612c6f57a8e97a467d88019f5428314e8b8bc7907dec7028671f1629d08db7a +size 107992 diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_axis_nan_policy.py b/venv/lib/python3.12/site-packages/scipy/stats/_axis_nan_policy.py new file mode 100644 index 0000000000000000000000000000000000000000..5b47171d2667e066af9a6e74c6a0fb739f8d9480 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_axis_nan_policy.py @@ -0,0 +1,725 @@ +# Many scipy.stats functions support `axis` and `nan_policy` parameters. +# When the two are combined, it can be tricky to get all the behavior just +# right. This file contains utility functions useful for scipy.stats functions +# that support `axis` and `nan_policy`, including a decorator that +# automatically adds `axis` and `nan_policy` arguments to a function. + +import math +import warnings +import numpy as np +from functools import wraps +from scipy._lib._array_api import xp_ravel +from scipy._lib._docscrape import FunctionDoc, Parameter +from scipy._lib._util import _contains_nan, AxisError, _get_nan +from scipy._lib._array_api import (array_namespace, is_numpy, xp_size, xp_copy, + xp_promote, is_dask, is_jax, xp_capabilities) +import scipy._external.array_api_extra as xpx + +import inspect + +too_small_1d_not_omit = ( + "One or more sample arguments is too small; all " + "returned values will be NaN. " + "See documentation for sample size requirements.") + +too_small_1d_omit = ( + "After omitting NaNs, one or more sample arguments " + "is too small; all returned values will be NaN. " + "See documentation for sample size requirements.") + +too_small_nd_not_omit = ( + "All axis-slices of one or more sample arguments are " + "too small; all elements of returned arrays will be NaN. " + "See documentation for sample size requirements.") + +too_small_nd_omit = ( + "After omitting NaNs, one or more axis-slices of one " + "or more sample arguments is too small; corresponding " + "elements of returned arrays will be NaN. " + "See documentation for sample size requirements.") + +class SmallSampleWarning(RuntimeWarning): + pass + + +def _broadcast_arrays(arrays, axis=None, xp=None): + """ + Broadcast shapes of arrays, ignoring incompatibility of specified axes + """ + arrays = tuple(arrays) + if not arrays: + return arrays + xp = array_namespace(*arrays) if xp is None else xp + arrays = [xp.asarray(arr) for arr in arrays] + shapes = [arr.shape for arr in arrays] + new_shapes = _broadcast_shapes(shapes, axis) + if axis is None: + new_shapes = [new_shapes]*len(arrays) + return [xp.broadcast_to(array, new_shape) + for array, new_shape in zip(arrays, new_shapes)] + + +def _broadcast_shapes(shapes, axis=None): + """ + Broadcast shapes, ignoring incompatibility of specified axes + """ + if not shapes: + return shapes + + # input validation + if axis is not None: + axis = np.atleast_1d(axis) + message = '`axis` must be an integer, a tuple of integers, or `None`.' + try: + with np.errstate(invalid='ignore'): + axis_int = axis.astype(int) + except ValueError as e: + raise AxisError(message) from e + if not np.array_equal(axis_int, axis): + raise AxisError(message) + axis = axis_int + + # First, ensure all shapes have same number of dimensions by prepending 1s. + n_dims = max([len(shape) for shape in shapes]) + new_shapes = np.ones((len(shapes), n_dims), dtype=int) + for row, shape in zip(new_shapes, shapes): + row[len(row)-len(shape):] = shape # can't use negative indices (-0:) + + # Remove the shape elements of the axes to be ignored, but remember them. + if axis is not None: + axis[axis < 0] = n_dims + axis[axis < 0] + axis = np.sort(axis) + if axis[-1] >= n_dims or axis[0] < 0: + message = (f"`axis` is out of bounds " + f"for array of dimension {n_dims}") + raise AxisError(message) + + if len(np.unique(axis)) != len(axis): + raise AxisError("`axis` must contain only distinct elements") + + removed_shapes = new_shapes[:, axis] + new_shapes = np.delete(new_shapes, axis, axis=1) + + # If arrays are broadcastable, shape elements that are 1 may be replaced + # with a corresponding non-1 shape element. Assuming arrays are + # broadcastable, that final shape element can be found with: + new_shape = np.max(new_shapes, axis=0) + # except in case of an empty array: + new_shape *= new_shapes.all(axis=0) + + # Among all arrays, there can only be one unique non-1 shape element. + # Therefore, if any non-1 shape element does not match what we found + # above, the arrays must not be broadcastable after all. + if np.any(~((new_shapes == 1) | (new_shapes == new_shape))): + raise ValueError("Array shapes are incompatible for broadcasting.") + + if axis is not None: + # Add back the shape elements that were ignored + new_axis = axis - np.arange(len(axis)) + new_shapes = [tuple(np.insert(new_shape, new_axis, removed_shape)) + for removed_shape in removed_shapes] + return new_shapes + else: + return tuple(new_shape) + + +def _broadcast_array_shapes_remove_axis(arrays, axis=None): + """ + Broadcast shapes of arrays, dropping specified axes + + Given a sequence of arrays `arrays` and an integer or tuple `axis`, find + the shape of the broadcast result after consuming/dropping `axis`. + In other words, return output shape of a typical hypothesis test on + `arrays` vectorized along `axis`. + + Examples + -------- + >>> import numpy as np + >>> from scipy.stats._axis_nan_policy import _broadcast_array_shapes_remove_axis + >>> a = np.zeros((5, 2, 1)) + >>> b = np.zeros((9, 3)) + >>> _broadcast_array_shapes_remove_axis((a, b), 1) + (5, 3) + """ + # Note that here, `axis=None` means do not consume/drop any axes - _not_ + # ravel arrays before broadcasting. + shapes = [arr.shape for arr in arrays] + return _broadcast_shapes_remove_axis(shapes, axis) + + +def _broadcast_shapes_remove_axis(shapes, axis=None): + """ + Broadcast shapes, dropping specified axes + + Same as _broadcast_array_shapes_remove_axis, but given a sequence + of array shapes `shapes` instead of the arrays themselves. + """ + shapes = _broadcast_shapes(shapes, axis) + shape = shapes[0] + if axis is not None: + shape = np.delete(shape, axis) + return tuple(shape) + + +@xp_capabilities() +def _broadcast_concatenate(arrays, axis, paired=False, xp=None): + """Concatenate arrays along an axis with broadcasting.""" + xp = array_namespace(*arrays) if xp is None else xp + arrays = _broadcast_arrays(arrays, axis if not paired else None, xp=xp) + res = xp.concat(arrays, axis=axis) + return res + + +def _remove_nans(samples, paired, xp=None): + "Remove nans from paired or unpaired 1D samples" + # potential optimization: don't copy arrays that don't contain nans + xp = array_namespace(*samples) + if not paired: + return [sample[~xp.isnan(sample)] for sample in samples] + + # for paired samples, we need to remove the whole pair when any part + # has a nan + nans = xp.isnan(samples[0]) + for sample in samples[1:]: + nans = nans | xp.isnan(sample) + not_nans = ~nans + return [sample[not_nans] for sample in samples] + + +def _remove_sentinel(samples, paired, sentinel): + "Remove sentinel values from paired or unpaired 1D samples" + # could consolidate with `_remove_nans`, but it's not quite as simple as + # passing `sentinel=np.nan` because `(np.nan == np.nan) is False` + + # potential optimization: don't copy arrays that don't contain sentinel + if not paired: + return [sample[sample != sentinel] for sample in samples] + + # for paired samples, we need to remove the whole pair when any part + # has a nan + sentinels = (samples[0] == sentinel) + for sample in samples[1:]: + sentinels = sentinels | (sample == sentinel) + not_sentinels = ~sentinels + return [sample[not_sentinels] for sample in samples] + + +def _masked_arrays_2_sentinel_arrays(samples): + # masked arrays in `samples` are converted to regular arrays, and values + # corresponding with masked elements are replaced with a sentinel value + + # return without modifying arrays if none have a mask + has_mask = False + for sample in samples: + mask = getattr(sample, 'mask', False) + has_mask = has_mask or np.any(mask) + if not has_mask: + return samples, None # None means there is no sentinel value + + # Choose a sentinel value. We can't use `np.nan`, because sentinel (masked) + # values are always omitted, but there are different nan policies. + dtype = np.result_type(*samples) + dtype = dtype if np.issubdtype(dtype, np.number) else np.float64 + for i in range(len(samples)): + # Things get more complicated if the arrays are of different types. + # We could have different sentinel values for each array, but + # the purpose of this code is convenience, not efficiency. + samples[i] = samples[i].astype(dtype, copy=False) + + inexact = np.issubdtype(dtype, np.inexact) + info = np.finfo if inexact else np.iinfo + max_possible, min_possible = info(dtype).max, info(dtype).min + nextafter = np.nextafter if inexact else (lambda x, _: x - 1) + + sentinel = max_possible + # For simplicity, min_possible/np.infs are not candidate sentinel values + while sentinel > min_possible: + for sample in samples: + if np.any(sample == sentinel): # choose a new sentinel value + sentinel = nextafter(sentinel, -np.inf) + break + else: # when sentinel value is OK, break the while loop + break + else: + message = ("This function replaces masked elements with sentinel " + "values, but the data contains all distinct values of this " + "data type. Consider promoting the dtype to `np.float64`.") + raise ValueError(message) + + # replace masked elements with sentinel value + out_samples = [] + for sample in samples: + mask = getattr(sample, 'mask', None) + if mask is not None: # turn all masked arrays into sentinel arrays + mask = np.broadcast_to(mask, sample.shape) + sample = sample.data.copy() if np.any(mask) else sample.data + sample = np.asarray(sample) # `sample.data` could be a memoryview? + sample[mask] = sentinel + out_samples.append(sample) + + return out_samples, sentinel + + +def _check_empty_inputs(samples, axis, xp=None): + """ + Check for empty sample; return appropriate output for a vectorized hypotest + """ + xp = array_namespace(*samples) if xp is None else xp + # if none of the samples are empty, we need to perform the test + if not any(xp_size(sample) == 0 for sample in samples): + return None + # otherwise, the statistic and p-value will be either empty arrays or + # arrays with NaNs. Produce the appropriate array and return it. + output_shape = _broadcast_array_shapes_remove_axis(samples, axis) + NaN = _get_nan(*samples) + output = xp.full(output_shape, xp.nan, dtype=NaN.dtype) + return output + + +def _add_reduced_axes(res, reduced_axes, keepdims, xp=np): + """ + Add reduced axes back to all the arrays in the result object + if keepdims = True. + """ + return ([xpx.expand_dims(output, axis=reduced_axes) + if not isinstance(output, int) else output for output in res] + if keepdims else res) + + +# Standard docstring / signature entries for `axis`, `nan_policy`, `keepdims` +_name = 'axis' +_desc = ( + """If an int, the axis of the input along which to compute the statistic. +The statistic of each axis-slice (e.g. row) of the input will appear in a +corresponding element of the output. +If ``None``, the input will be raveled before computing the statistic.""" + .split('\n')) + + +def _get_axis_params(default_axis=0, _name=_name, _desc=_desc): # bind NOW + _type = f"int or None, default: {default_axis}" + _axis_parameter_doc = Parameter(_name, _type, _desc) + _axis_parameter = inspect.Parameter(_name, + inspect.Parameter.KEYWORD_ONLY, + default=default_axis) + return _axis_parameter_doc, _axis_parameter + + +_name = 'nan_policy' +_type = "{'propagate', 'omit', 'raise'}" +_desc = ( + """Defines how to handle input NaNs. + +- ``propagate``: if a NaN is present in the axis slice (e.g. row) along + which the statistic is computed, the corresponding entry of the output + will be NaN. +- ``omit``: NaNs will be omitted when performing the calculation. + If insufficient data remains in the axis slice along which the + statistic is computed, the corresponding entry of the output will be + NaN. +- ``raise``: if a NaN is present, a ``ValueError`` will be raised.""" + .split('\n')) +_nan_policy_parameter_doc = Parameter(_name, _type, _desc) +_nan_policy_parameter = inspect.Parameter(_name, + inspect.Parameter.KEYWORD_ONLY, + default='propagate') + +_name = 'keepdims' +_type = "bool, default: False" +_desc = ( + """If this is set to True, the axes which are reduced are left +in the result as dimensions with size one. With this option, +the result will broadcast correctly against the input array.""" + .split('\n')) +_keepdims_parameter_doc = Parameter(_name, _type, _desc) +_keepdims_parameter = inspect.Parameter(_name, + inspect.Parameter.KEYWORD_ONLY, + default=False) + +_standard_note_addition = ( + """\nBeginning in SciPy 1.9, ``np.matrix`` inputs (not recommended for new +code) are converted to ``np.ndarray`` before the calculation is performed. In +this case, the output will be a scalar or ``np.ndarray`` of appropriate shape +rather than a 2D ``np.matrix``. Similarly, while masked elements of masked +arrays are ignored, the output will be a scalar or ``np.ndarray`` rather than a +masked array with ``mask=False``.""").split('\n') + + +def _axis_nan_policy_factory(tuple_to_result, default_axis=0, + n_samples=1, paired=False, + result_to_tuple=None, too_small=0, + n_outputs=2, kwd_samples=(), override=None): + """Factory for a wrapper that adds axis/nan_policy params to a function. + + Parameters + ---------- + tuple_to_result : callable + Callable that returns an object of the type returned by the function + being wrapped (e.g. the namedtuple or dataclass returned by a + statistical test) provided the separate components (e.g. statistic, + pvalue). + default_axis : int, default: 0 + The default value of the axis argument. Standard is 0 except when + backwards compatibility demands otherwise (e.g. `None`). + n_samples : int or callable, default: 1 + The number of data samples accepted by the function + (e.g. `mannwhitneyu`), a callable that accepts a dictionary of + parameters passed into the function and returns the number of data + samples (e.g. `wilcoxon`), or `None` to indicate an arbitrary number + of samples (e.g. `kruskal`). + paired : {False, True} + Whether the function being wrapped treats the samples as paired (i.e. + corresponding elements of each sample should be considered as different + components of the same sample.) + result_to_tuple : callable, optional + Function that unpacks the results of the function being wrapped into + a tuple. This is essentially the inverse of `tuple_to_result`. Default + is `None`, which is appropriate for statistical tests that return a + statistic, pvalue tuple (rather than, e.g., a non-iterable dataclass). + too_small : int or callable, default: 0 + The largest unacceptably small sample for the function being wrapped. + For example, some functions require samples of size two or more or they + raise an error. This argument prevents the error from being raised when + input is not 1D and instead places a NaN in the corresponding element + of the result. If callable, it must accept a list of samples, axis, + and a dictionary of keyword arguments passed to the wrapper function as + arguments and return a bool indicating whether the samples passed are + too small. + n_outputs : int or callable, default: 2 + The number of outputs produced by the function given 1d sample(s). For + example, hypothesis tests that return a namedtuple or result object + with attributes ``statistic`` and ``pvalue`` use the default + ``n_outputs=2``; summary statistics with scalar output use + ``n_outputs=1``. Alternatively, may be a callable that accepts a + dictionary of arguments passed into the wrapped function and returns + the number of outputs corresponding with those arguments. + kwd_samples : sequence, default: () + The names of keyword parameters that should be treated as samples. For + example, `gmean` accepts as its first argument a sample `a` but + also `weights` as a fourth, optional keyword argument. In this case, we + use `n_samples=1` and kwd_samples=['weights']. + override : dict, default: {'vectorization': False, 'nan_propagation': True} + Pass a dictionary with ``'vectorization': True`` to ensure that the + decorator overrides the function's behavior for multimensional input. + Use ``'nan_propagation': False`` to ensure that the decorator does not + override the function's behavior for ``nan_policy='propagate'``. + """ + # Specify which existing behaviors the decorator must override + temp = override or {} + override = {'vectorization': False, + 'nan_propagation': True} + override.update(temp) + + if result_to_tuple is None: + def result_to_tuple(res, _): + return res + + if not callable(too_small): + def is_too_small(samples, *ts_args, axis=-1, **ts_kwargs): + for sample in samples: + if sample.shape[axis] <= too_small: + return True + return False + else: + is_too_small = too_small + + def axis_nan_policy_decorator(hypotest_fun_in): + @wraps(hypotest_fun_in) + def axis_nan_policy_wrapper(*args, _no_deco=False, **kwds): + + if _no_deco: # for testing, decorator does nothing + return hypotest_fun_in(*args, **kwds) + + # For now, skip the decorator entirely if using array API. In the future, + # we'll probably want to use it for `keepdims`, `axis` tuples, etc. + if len(args) == 0: # extract sample from `kwds` if there are no `args` + used_kwd_samples = list(set(kwds).intersection(set(kwd_samples))) + temp = used_kwd_samples[:1] + else: + temp = args[0] + + if is_dask(array_namespace(temp)): + msg = ("Use of `nan_policy` and `keepdims` " + "is incompatible with lazy arrays.") + if 'nan_policy' in kwds or 'keepdims' in kwds: + raise NotImplementedError(msg) + return hypotest_fun_in(*args, **kwds) + + # We need to be flexible about whether position or keyword + # arguments are used, but we need to make sure users don't pass + # both for the same parameter. To complicate matters, some + # functions accept samples with *args, and some functions already + # accept `axis` and `nan_policy` as positional arguments. + # The strategy is to make sure that there is no duplication + # between `args` and `kwds`, combine the two into `kwds`, then + # the samples, `nan_policy`, and `axis` from `kwds`, as they are + # dealt with separately. + + # Check for intersection between positional and keyword args + params = list(inspect.signature(hypotest_fun_in).parameters) + if n_samples is None: + # Give unique names to each positional sample argument + # Note that *args can't be provided as a keyword argument + params = [f"arg{i}" for i in range(len(args))] + params[1:] + + # raise if there are too many positional args + maxarg = (np.inf if inspect.getfullargspec(hypotest_fun_in).varargs + else len(inspect.getfullargspec(hypotest_fun_in).args)) + if len(args) > maxarg: # let the function raise the right error + hypotest_fun_in(*args, **kwds) + + # raise if multiple values passed for same parameter + d_args = dict(zip(params, args)) + intersection = set(d_args) & set(kwds) + if intersection: # let the function raise the right error + hypotest_fun_in(*args, **kwds) + + # Consolidate other positional and keyword args into `kwds` + kwds.update(d_args) + + # rename avoids UnboundLocalError + if callable(n_samples): + # Future refactoring idea: no need for callable n_samples. + # Just replace `n_samples` and `kwd_samples` with a single + # list of the names of all samples, and treat all of them + # as `kwd_samples` are treated below. + n_samp = n_samples(kwds) + else: + n_samp = n_samples or len(args) + + # get the number of outputs + n_out = n_outputs # rename to avoid UnboundLocalError + if callable(n_out): + n_out = n_out(kwds) + + # If necessary, rearrange function signature: accept other samples + # as positional args right after the first n_samp args + kwd_samp = [name for name in kwd_samples + if kwds.get(name, None) is not None] + n_kwd_samp = len(kwd_samp) + if not kwd_samp: + hypotest_fun_out = hypotest_fun_in + else: + def hypotest_fun_out(*samples, **kwds): + new_kwds = dict(zip(kwd_samp, samples[n_samp:])) + kwds.update(new_kwds) + return hypotest_fun_in(*samples[:n_samp], **kwds) + + # Extract the things we need here + try: # if something is missing + samples = [kwds.pop(param) for param in (params[:n_samp] + kwd_samp)] + xp = array_namespace(*samples) + samples = xp_promote(*samples, xp=xp) + samples = (samples,) if not isinstance(samples, tuple) else samples + samples = [xpx.atleast_nd(sample, ndim=1) for sample in samples] + except KeyError: # let the function raise the right error + # might need to revisit this if required arg is not a "sample" + hypotest_fun_in(*args, **kwds) + vectorized = True if 'axis' in params else False + vectorized = vectorized and not override['vectorization'] + axis = kwds.pop('axis', default_axis) + nan_policy = kwds.pop('nan_policy', 'propagate') + keepdims = kwds.pop("keepdims", False) + del args # avoid the possibility of passing both `args` and `kwds` + + # convert masked arrays to regular arrays with sentinel values + sentinel = None + if is_numpy(xp): + samples, sentinel = _masked_arrays_2_sentinel_arrays(samples) + + # standardize to always work along last axis + reduced_axes = axis + if axis is None: + if samples: + # when axis=None, take the maximum of all dimensions since + # all the dimensions are reduced. + n_dims = max([xp.asarray(sample).ndim for sample in samples]) + reduced_axes = tuple(range(n_dims)) + samples = [xp_ravel(sample) for sample in samples] + else: + # don't ignore any axes when broadcasting if paired + samples = _broadcast_arrays(samples, axis=axis if not paired else None) + axis = (axis,) if np.isscalar(axis) else axis + n_axes = len(axis) + # move all axes in `axis` to the end to be raveled + samples = [xp.moveaxis(sample, axis, tuple(range(-len(axis), 0))) + for sample in samples] + shapes = [sample.shape for sample in samples] + # New shape is unchanged for all axes _not_ in `axis` + # At the end, we append the product of the shapes of the axes + # in `axis`. Appending -1 doesn't work for zero-size arrays! + new_shapes = [shape[:-n_axes] + (math.prod(shape[-n_axes:]),) + for shape in shapes] + samples = [xp.reshape(sample, new_shape) + for sample, new_shape in zip(samples, new_shapes)] + axis = -1 # work over the last axis + + NaN = _get_nan(*samples) if samples else xp.nan + + # if axis is not needed, just handle nan_policy and return + ndims = np.array([sample.ndim for sample in samples]) # NumPy OK for ndims + if np.all(ndims <= 1): + # Addresses nan_policy == "raise" + if nan_policy != 'propagate' or override['nan_propagation']: + contains_nan = [_contains_nan(sample, nan_policy) + for sample in samples] + else: + # Behave as though there are no NaNs (even if there are) + contains_nan = [False] * len(samples) + + # To give JAX most benefits of the `_axis_nan_policy` decorator without + # value-dependent branching, the decorator will always treat JAX arrays + # as if there are no NaNs. Typically, this means that NaNs will + # multidimensional naturally. However, there may be cases in which this + # doesn't produce the desired result, so for now, JAX users should + # treat all functions as though there is no explicit`nan_policy`. + # Future work tracked in gh-14651. + any_contains_nan = not is_jax(xp) and any(contains_nan) + # Addresses nan_policy == "propagate" + if any_contains_nan and (nan_policy == 'propagate' + and override['nan_propagation']): + res = xp.full(n_out, xp.nan, dtype=NaN.dtype) + res = _add_reduced_axes(res, reduced_axes, keepdims) + return tuple_to_result(*res) + + # Addresses nan_policy == "omit" + too_small_msg = too_small_1d_not_omit + if any_contains_nan and nan_policy == 'omit': + # consider passing in contains_nan + samples = _remove_nans(samples, paired) + too_small_msg = too_small_1d_omit + + if sentinel: + samples = _remove_sentinel(samples, paired, sentinel) + + if is_too_small(samples, kwds): + warnings.warn(too_small_msg, SmallSampleWarning, stacklevel=2) + res = xp.full(n_out, xp.nan, dtype=NaN.dtype) + res = _add_reduced_axes(res, reduced_axes, keepdims) + return tuple_to_result(*res) + + res = hypotest_fun_out(*samples, **kwds) + res = result_to_tuple(res, n_out) + res = _add_reduced_axes(res, reduced_axes, keepdims) + return tuple_to_result(*res) + + # check for empty input + empty_output = _check_empty_inputs(samples, axis, xp=xp) + # only return empty output if zero sized input is too small. + if ( + empty_output is not None + and (is_too_small(samples, kwds) or xp_size(empty_output) == 0) + ): + if is_too_small(samples, kwds) and xp_size(empty_output) != 0: + warnings.warn(too_small_nd_not_omit, SmallSampleWarning, + stacklevel=2) + res = [xp_copy(empty_output) for i in range(n_out)] + res = _add_reduced_axes(res, reduced_axes, keepdims) + return tuple_to_result(*res) + + if not is_numpy(xp) and 'nan_policy' in kwds: + msg = ("Use of `nan_policy` is incompatible with multidimensional " + "non-NumPy arrays.") + raise NotImplementedError(msg) + + if not is_numpy(xp): + res = hypotest_fun_out(*samples, axis=axis, **kwds) + res = result_to_tuple(res, n_out) + res = _add_reduced_axes(res, reduced_axes, keepdims, xp=xp) + return tuple_to_result(*res) + + # otherwise, concatenate all samples along axis, remembering where + # each separate sample begins + lengths = np.array([sample.shape[axis] for sample in samples]) + split_indices = np.cumsum(lengths) + x = _broadcast_concatenate(samples, axis, paired=paired) + + # Addresses nan_policy == "raise" + if nan_policy != 'propagate' or override['nan_propagation']: + contains_nan = _contains_nan(x, nan_policy) + else: + contains_nan = False # behave like there are no NaNs + + if vectorized and not contains_nan and not sentinel: + res = hypotest_fun_out(*samples, axis=axis, **kwds) + res = result_to_tuple(res, n_out) + res = _add_reduced_axes(res, reduced_axes, keepdims) + return tuple_to_result(*res) + + # Addresses nan_policy == "omit" + if contains_nan and nan_policy == 'omit': + def hypotest_fun(x): + samples = np.split(x, split_indices)[:n_samp+n_kwd_samp] + samples = _remove_nans(samples, paired) + if sentinel: + samples = _remove_sentinel(samples, paired, sentinel) + if is_too_small(samples, kwds): + warnings.warn(too_small_nd_omit, SmallSampleWarning, + stacklevel=4) + return np.full(n_out, NaN) + return result_to_tuple(hypotest_fun_out(*samples, **kwds), n_out) + + # Addresses nan_policy == "propagate" + elif (contains_nan and nan_policy == 'propagate' + and override['nan_propagation']): + def hypotest_fun(x): + if np.isnan(x).any(): + return np.full(n_out, NaN) + + samples = np.split(x, split_indices)[:n_samp+n_kwd_samp] + if sentinel: + samples = _remove_sentinel(samples, paired, sentinel) + if is_too_small(samples, kwds): + return np.full(n_out, NaN) + return result_to_tuple(hypotest_fun_out(*samples, **kwds), n_out) + + else: + def hypotest_fun(x): + samples = np.split(x, split_indices)[:n_samp+n_kwd_samp] + if sentinel: + samples = _remove_sentinel(samples, paired, sentinel) + if is_too_small(samples, kwds): + return np.full(n_out, NaN) + return result_to_tuple(hypotest_fun_out(*samples, **kwds), n_out) + + x = np.moveaxis(x, axis, 0) + res = np.apply_along_axis(hypotest_fun, axis=0, arr=x) + res = _add_reduced_axes(res, reduced_axes, keepdims) + return tuple_to_result(*res) + + _axis_parameter_doc, _axis_parameter = _get_axis_params(default_axis) + doc = FunctionDoc(axis_nan_policy_wrapper) + parameter_names = [param.name for param in doc['Parameters']] + if 'axis' in parameter_names: + doc['Parameters'][parameter_names.index('axis')] = ( + _axis_parameter_doc) + else: + doc['Parameters'].append(_axis_parameter_doc) + if 'nan_policy' in parameter_names: + doc['Parameters'][parameter_names.index('nan_policy')] = ( + _nan_policy_parameter_doc) + else: + doc['Parameters'].append(_nan_policy_parameter_doc) + if 'keepdims' in parameter_names: + doc['Parameters'][parameter_names.index('keepdims')] = ( + _keepdims_parameter_doc) + else: + doc['Parameters'].append(_keepdims_parameter_doc) + doc['Notes'] += _standard_note_addition + doc = str(doc).split("\n", 1)[1].lstrip(" \n") # remove signature + axis_nan_policy_wrapper.__doc__ = str(doc) + + sig = inspect.signature(axis_nan_policy_wrapper) + parameters = sig.parameters + parameter_list = list(parameters.values()) + if 'axis' not in parameters: + parameter_list.append(_axis_parameter) + if 'nan_policy' not in parameters: + parameter_list.append(_nan_policy_parameter) + if 'keepdims' not in parameters: + parameter_list.append(_keepdims_parameter) + sig = sig.replace(parameters=parameter_list) + axis_nan_policy_wrapper.__signature__ = sig + + return axis_nan_policy_wrapper + return axis_nan_policy_decorator diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_biasedurn.cpython-312-x86_64-linux-gnu.so b/venv/lib/python3.12/site-packages/scipy/stats/_biasedurn.cpython-312-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..ad1fd20250ade99c557428e4f19cb1189c72dc8c --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_biasedurn.cpython-312-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5de2fd26b29b90c9e3090f1b0b738782f601b92222ba94d9cb6a4b1a898b46c5 +size 239008 diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_biasedurn.pxd b/venv/lib/python3.12/site-packages/scipy/stats/_biasedurn.pxd new file mode 100644 index 0000000000000000000000000000000000000000..92785f08dbec30a4db286fcb85b42d7221e2228e --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_biasedurn.pxd @@ -0,0 +1,27 @@ +# Declare the class with cdef +cdef extern from "biasedurn/stocc.h" nogil: + cdef cppclass CFishersNCHypergeometric: + CFishersNCHypergeometric(int, int, int, double, double) except + + int mode() + double mean() + double variance() + double probability(int x) + double moments(double * mean, double * var) + + cdef cppclass CWalleniusNCHypergeometric: + CWalleniusNCHypergeometric() except + + CWalleniusNCHypergeometric(int, int, int, double, double) except + + int mode() + double mean() + double variance() + double probability(int x) + double moments(double * mean, double * var) + + cdef cppclass StochasticLib3: + StochasticLib3(int seed) except + + double Random() except + + void SetAccuracy(double accur) + int FishersNCHyp (int n, int m, int N, double odds) except + + int WalleniusNCHyp (int n, int m, int N, double odds) except + + double(*next_double)() + double(*next_normal)(const double m, const double s) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_binned_statistic.py b/venv/lib/python3.12/site-packages/scipy/stats/_binned_statistic.py new file mode 100644 index 0000000000000000000000000000000000000000..5a11a1caa4bc48d2e6a0a42793f79635a3e58d36 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_binned_statistic.py @@ -0,0 +1,806 @@ +import builtins +from warnings import catch_warnings, simplefilter +import numpy as np +from operator import index +from collections import namedtuple + +from scipy._lib._array_api import xp_capabilities + +__all__ = ['binned_statistic', + 'binned_statistic_2d', + 'binned_statistic_dd'] + + +BinnedStatisticResult = namedtuple('BinnedStatisticResult', + ('statistic', 'bin_edges', 'binnumber')) + + +@xp_capabilities(np_only=True) +def binned_statistic(x, values, statistic='mean', + bins=10, range=None): + """ + Compute a binned statistic for one or more sets of data. + + This is a generalization of a histogram function. A histogram divides + the space into bins, and returns the count of the number of points in + each bin. This function allows the computation of the sum, mean, median, + or other statistic of the values (or set of values) within each bin. + + Parameters + ---------- + x : (N,) array_like + A sequence of values to be binned. + values : (N,) array_like or list of (N,) array_like + The data on which the statistic will be computed. This must be + the same shape as `x`, or a set of sequences - each the same shape as + `x`. If `values` is a set of sequences, the statistic will be computed + on each independently. + statistic : str or callable, optional + The statistic to compute (default is 'mean'). + The following statistics are available: + + * 'mean' : compute the mean of values for points within each bin. + Empty bins will be represented by NaN. + * 'std' : compute the standard deviation within each bin. This + is implicitly calculated with ddof=0. + * 'median' : compute the median of values for points within each + bin. Empty bins will be represented by NaN. + * 'count' : compute the count of points within each bin. This is + identical to an unweighted histogram. `values` array is not + referenced. + * 'sum' : compute the sum of values for points within each bin. + This is identical to a weighted histogram. + * 'min' : compute the minimum of values for points within each bin. + Empty bins will be represented by NaN. + * 'max' : compute the maximum of values for point within each bin. + Empty bins will be represented by NaN. + * function : a user-defined function which takes a 1D array of + values, and outputs a single numerical statistic. This function + will be called on the values in each bin. Empty bins will be + represented by function([]), or NaN if this returns an error. + + bins : int or sequence of scalars, optional + If `bins` is an int, it defines the number of equal-width bins in the + given range (10 by default). If `bins` is a sequence, it defines the + bin edges, including the rightmost edge, allowing for non-uniform bin + widths. Values in `x` that are smaller than lowest bin edge are + assigned to bin number 0, values beyond the highest bin are assigned to + ``bins[-1]``. If the bin edges are specified, the number of bins will + be, (nx = len(bins)-1). + range : (float, float) or [(float, float)], optional + The lower and upper range of the bins. If not provided, range + is simply ``(x.min(), x.max())``. Values outside the range are + ignored. + + Returns + ------- + statistic : array + The values of the selected statistic in each bin. + bin_edges : array of dtype float + Return the bin edges ``(length(statistic)+1)``. + binnumber: 1-D ndarray of ints + Indices of the bins (corresponding to `bin_edges`) in which each value + of `x` belongs. Same length as `values`. A binnumber of `i` means the + corresponding value is between (bin_edges[i-1], bin_edges[i]). + + See Also + -------- + numpy.digitize, numpy.histogram, binned_statistic_2d, binned_statistic_dd + + Notes + ----- + All but the last (righthand-most) bin is half-open. In other words, if + `bins` is ``[1, 2, 3, 4]``, then the first bin is ``[1, 2)`` (including 1, + but excluding 2) and the second ``[2, 3)``. The last bin, however, is + ``[3, 4]``, which *includes* 4. + + .. versionadded:: 0.11.0 + + Examples + -------- + >>> import numpy as np + >>> from scipy import stats + >>> import matplotlib.pyplot as plt + + First some basic examples: + + Create two evenly spaced bins in the range of the given sample, and sum the + corresponding values in each of those bins: + + >>> values = [1.0, 1.0, 2.0, 1.5, 3.0] + >>> stats.binned_statistic([1, 1, 2, 5, 7], values, 'sum', bins=2) + BinnedStatisticResult(statistic=array([4. , 4.5]), + bin_edges=array([1., 4., 7.]), binnumber=array([1, 1, 1, 2, 2])) + + Multiple arrays of values can also be passed. The statistic is calculated + on each set independently: + + >>> values = [[1.0, 1.0, 2.0, 1.5, 3.0], [2.0, 2.0, 4.0, 3.0, 6.0]] + >>> stats.binned_statistic([1, 1, 2, 5, 7], values, 'sum', bins=2) + BinnedStatisticResult(statistic=array([[4. , 4.5], + [8. , 9. ]]), bin_edges=array([1., 4., 7.]), + binnumber=array([1, 1, 1, 2, 2])) + + >>> stats.binned_statistic([1, 2, 1, 2, 4], np.arange(5), statistic='mean', + ... bins=3) + BinnedStatisticResult(statistic=array([1., 2., 4.]), + bin_edges=array([1., 2., 3., 4.]), + binnumber=array([1, 2, 1, 2, 3])) + + As a second example, we now generate some random data of sailing boat speed + as a function of wind speed, and then determine how fast our boat is for + certain wind speeds: + + >>> rng = np.random.default_rng() + >>> windspeed = 8 * rng.random(500) + >>> boatspeed = .3 * windspeed**.5 + .2 * rng.random(500) + >>> bin_means, bin_edges, binnumber = stats.binned_statistic(windspeed, + ... boatspeed, statistic='median', bins=[1,2,3,4,5,6,7]) + >>> plt.figure() + >>> plt.plot(windspeed, boatspeed, 'b.', label='raw data') + >>> plt.hlines(bin_means, bin_edges[:-1], bin_edges[1:], colors='g', lw=5, + ... label='binned statistic of data') + >>> plt.legend() + + Now we can use ``binnumber`` to select all datapoints with a windspeed + below 1: + + >>> low_boatspeed = boatspeed[binnumber == 0] + + As a final example, we will use ``bin_edges`` and ``binnumber`` to make a + plot of a distribution that shows the mean and distribution around that + mean per bin, on top of a regular histogram and the probability + distribution function: + + >>> x = np.linspace(0, 5, num=500) + >>> x_pdf = stats.maxwell.pdf(x) + >>> samples = stats.maxwell.rvs(size=10000) + + >>> bin_means, bin_edges, binnumber = stats.binned_statistic(x, x_pdf, + ... statistic='mean', bins=25) + >>> bin_width = (bin_edges[1] - bin_edges[0]) + >>> bin_centers = bin_edges[1:] - bin_width/2 + + >>> plt.figure() + >>> plt.hist(samples, bins=50, density=True, histtype='stepfilled', + ... alpha=0.2, label='histogram of data') + >>> plt.plot(x, x_pdf, 'r-', label='analytical pdf') + >>> plt.hlines(bin_means, bin_edges[:-1], bin_edges[1:], colors='g', lw=2, + ... label='binned statistic of data') + >>> plt.plot((binnumber - 0.5) * bin_width, x_pdf, 'g.', alpha=0.5) + >>> plt.legend(fontsize=10) + >>> plt.show() + + """ + try: + N = len(bins) + except TypeError: + N = 1 + + if N != 1: + bins = [np.asarray(bins, float)] + + if range is not None: + if len(range) == 2: + range = [range] + + medians, edges, binnumbers = binned_statistic_dd( + [x], values, statistic, bins, range) + + return BinnedStatisticResult(medians, edges[0], binnumbers) + + +BinnedStatistic2dResult = namedtuple('BinnedStatistic2dResult', + ('statistic', 'x_edge', 'y_edge', + 'binnumber')) + + +@xp_capabilities(np_only=True) +def binned_statistic_2d(x, y, values, statistic='mean', + bins=10, range=None, expand_binnumbers=False): + """ + Compute a bidimensional binned statistic for one or more sets of data. + + This is a generalization of a histogram2d function. A histogram divides + the space into bins, and returns the count of the number of points in + each bin. This function allows the computation of the sum, mean, median, + or other statistic of the values (or set of values) within each bin. + + Parameters + ---------- + x : (N,) array_like + A sequence of values to be binned along the first dimension. + y : (N,) array_like + A sequence of values to be binned along the second dimension. + values : (N,) array_like or list of (N,) array_like + The data on which the statistic will be computed. This must be + the same shape as `x`, or a list of sequences - each with the same + shape as `x`. If `values` is such a list, the statistic will be + computed on each independently. + statistic : str or callable, optional + The statistic to compute (default is 'mean'). + The following statistics are available: + + * 'mean' : compute the mean of values for points within each bin. + Empty bins will be represented by NaN. + * 'std' : compute the standard deviation within each bin. This + is implicitly calculated with ddof=0. + * 'median' : compute the median of values for points within each + bin. Empty bins will be represented by NaN. + * 'count' : compute the count of points within each bin. This is + identical to an unweighted histogram. `values` array is not + referenced. + * 'sum' : compute the sum of values for points within each bin. + This is identical to a weighted histogram. + * 'min' : compute the minimum of values for points within each bin. + Empty bins will be represented by NaN. + * 'max' : compute the maximum of values for point within each bin. + Empty bins will be represented by NaN. + * function : a user-defined function which takes a 1D array of + values, and outputs a single numerical statistic. This function + will be called on the values in each bin. Empty bins will be + represented by function([]), or NaN if this returns an error. + + bins : int or [int, int] or array_like or [array, array], optional + The bin specification: + + * the number of bins for the two dimensions (nx = ny = bins), + * the number of bins in each dimension (nx, ny = bins), + * the bin edges for the two dimensions (x_edge = y_edge = bins), + * the bin edges in each dimension (x_edge, y_edge = bins). + + If the bin edges are specified, the number of bins will be, + (nx = len(x_edge)-1, ny = len(y_edge)-1). + + range : (2,2) array_like, optional + The leftmost and rightmost edges of the bins along each dimension + (if not specified explicitly in the `bins` parameters): + [[xmin, xmax], [ymin, ymax]]. All values outside of this range will be + considered outliers and not tallied in the histogram. + expand_binnumbers : bool, optional + 'False' (default): the returned `binnumber` is a shape (N,) array of + linearized bin indices. + 'True': the returned `binnumber` is 'unraveled' into a shape (2,N) + ndarray, where each row gives the bin numbers in the corresponding + dimension. + See the `binnumber` returned value, and the `Examples` section. + + .. versionadded:: 0.17.0 + + Returns + ------- + statistic : (nx, ny) ndarray + The values of the selected statistic in each two-dimensional bin. + x_edge : (nx + 1) ndarray + The bin edges along the first dimension. + y_edge : (ny + 1) ndarray + The bin edges along the second dimension. + binnumber : (N,) array of ints or (2,N) ndarray of ints + This assigns to each element of `sample` an integer that represents the + bin in which this observation falls. The representation depends on the + `expand_binnumbers` argument. See `Notes` for details. + + + See Also + -------- + numpy.digitize, numpy.histogram2d, binned_statistic, binned_statistic_dd + + Notes + ----- + Binedges: + All but the last (righthand-most) bin is half-open. In other words, if + `bins` is ``[1, 2, 3, 4]``, then the first bin is ``[1, 2)`` (including 1, + but excluding 2) and the second ``[2, 3)``. The last bin, however, is + ``[3, 4]``, which *includes* 4. + + `binnumber`: + This returned argument assigns to each element of `sample` an integer that + represents the bin in which it belongs. The representation depends on the + `expand_binnumbers` argument. If 'False' (default): The returned + `binnumber` is a shape (N,) array of linearized indices mapping each + element of `sample` to its corresponding bin (using row-major ordering). + Note that the returned linearized bin indices are used for an array with + extra bins on the outer binedges to capture values outside of the defined + bin bounds. + If 'True': The returned `binnumber` is a shape (2,N) ndarray where + each row indicates bin placements for each dimension respectively. In each + dimension, a binnumber of `i` means the corresponding value is between + (D_edge[i-1], D_edge[i]), where 'D' is either 'x' or 'y'. + + .. versionadded:: 0.11.0 + + Examples + -------- + >>> from scipy import stats + + Calculate the counts with explicit bin-edges: + + >>> x = [0.1, 0.1, 0.1, 0.6] + >>> y = [2.1, 2.6, 2.1, 2.1] + >>> binx = [0.0, 0.5, 1.0] + >>> biny = [2.0, 2.5, 3.0] + >>> ret = stats.binned_statistic_2d(x, y, None, 'count', bins=[binx, biny]) + >>> ret.statistic + array([[2., 1.], + [1., 0.]]) + + The bin in which each sample is placed is given by the `binnumber` + returned parameter. By default, these are the linearized bin indices: + + >>> ret.binnumber + array([5, 6, 5, 9]) + + The bin indices can also be expanded into separate entries for each + dimension using the `expand_binnumbers` parameter: + + >>> ret = stats.binned_statistic_2d(x, y, None, 'count', bins=[binx, biny], + ... expand_binnumbers=True) + >>> ret.binnumber + array([[1, 1, 1, 2], + [1, 2, 1, 1]]) + + Which shows that the first three elements belong in the xbin 1, and the + fourth into xbin 2; and so on for y. + + """ + + # This code is based on np.histogram2d + try: + N = len(bins) + except TypeError: + N = 1 + + if N != 1 and N != 2: + xedges = yedges = np.asarray(bins, float) + bins = [xedges, yedges] + + medians, edges, binnumbers = binned_statistic_dd( + [x, y], values, statistic, bins, range, + expand_binnumbers=expand_binnumbers) + + return BinnedStatistic2dResult(medians, edges[0], edges[1], binnumbers) + + +BinnedStatisticddResult = namedtuple('BinnedStatisticddResult', + ('statistic', 'bin_edges', + 'binnumber')) + + +def _bincount(x, weights): + if np.iscomplexobj(weights): + a = np.bincount(x, np.real(weights)) + b = np.bincount(x, np.imag(weights)) + z = a + b*1j + + else: + z = np.bincount(x, weights) + return z + + +@xp_capabilities(np_only=True) +def binned_statistic_dd(sample, values, statistic='mean', + bins=10, range=None, expand_binnumbers=False, + binned_statistic_result=None): + """ + Compute a multidimensional binned statistic for a set of data. + + This is a generalization of a histogramdd function. A histogram divides + the space into bins, and returns the count of the number of points in + each bin. This function allows the computation of the sum, mean, median, + or other statistic of the values within each bin. + + Parameters + ---------- + sample : array_like + Data to histogram passed as a sequence of N arrays of length D, or + as an (N,D) array. + values : (N,) array_like or list of (N,) array_like + The data on which the statistic will be computed. This must be + the same shape as `sample`, or a list of sequences - each with the + same shape as `sample`. If `values` is such a list, the statistic + will be computed on each independently. + statistic : str or callable, optional + The statistic to compute (default is 'mean'). + The following statistics are available: + + * 'mean' : compute the mean of values for points within each bin. + Empty bins will be represented by NaN. + * 'median' : compute the median of values for points within each + bin. Empty bins will be represented by NaN. + * 'count' : compute the count of points within each bin. This is + identical to an unweighted histogram. `values` array is not + referenced. + * 'sum' : compute the sum of values for points within each bin. + This is identical to a weighted histogram. + * 'std' : compute the standard deviation within each bin. This + is implicitly calculated with ddof=0. If the number of values + within a given bin is 0 or 1, the computed standard deviation value + will be 0 for the bin. + * 'min' : compute the minimum of values for points within each bin. + Empty bins will be represented by NaN. + * 'max' : compute the maximum of values for point within each bin. + Empty bins will be represented by NaN. + * function : a user-defined function which takes a 1D array of + values, and outputs a single numerical statistic. This function + will be called on the values in each bin. Empty bins will be + represented by function([]), or NaN if this returns an error. + + bins : sequence or positive int, optional + The bin specification must be in one of the following forms: + + * A sequence of arrays describing the bin edges along each dimension. + * The number of bins for each dimension (nx, ny, ... = bins). + * The number of bins for all dimensions (nx = ny = ... = bins). + + range : sequence, optional + A sequence of lower and upper bin edges to be used if the edges are + not given explicitly in `bins`. Defaults to the minimum and maximum + values along each dimension. + expand_binnumbers : bool, optional + 'False' (default): the returned `binnumber` is a shape (N,) array of + linearized bin indices. + 'True': the returned `binnumber` is 'unraveled' into a shape (D,N) + ndarray, where each row gives the bin numbers in the corresponding + dimension. + See the `binnumber` returned value, and the `Examples` section of + `binned_statistic_2d`. + binned_statistic_result : binnedStatisticddResult + Result of a previous call to the function in order to reuse bin edges + and bin numbers with new values and/or a different statistic. + To reuse bin numbers, `expand_binnumbers` must have been set to False + (the default) + + .. versionadded:: 0.17.0 + + Returns + ------- + statistic : ndarray, shape(nx1, nx2, nx3,...) + The values of the selected statistic in each two-dimensional bin. + bin_edges : list of ndarrays + A list of D arrays describing the (nxi + 1) bin edges for each + dimension. + binnumber : (N,) array of ints or (D,N) ndarray of ints + This assigns to each element of `sample` an integer that represents the + bin in which this observation falls. The representation depends on the + `expand_binnumbers` argument. See `Notes` for details. + + + See Also + -------- + numpy.digitize, numpy.histogramdd, binned_statistic, binned_statistic_2d + + Notes + ----- + Binedges: + All but the last (righthand-most) bin is half-open in each dimension. In + other words, if `bins` is ``[1, 2, 3, 4]``, then the first bin is + ``[1, 2)`` (including 1, but excluding 2) and the second ``[2, 3)``. The + last bin, however, is ``[3, 4]``, which *includes* 4. + + `binnumber`: + This returned argument assigns to each element of `sample` an integer that + represents the bin in which it belongs. The representation depends on the + `expand_binnumbers` argument. If 'False' (default): The returned + `binnumber` is a shape (N,) array of linearized indices mapping each + element of `sample` to its corresponding bin (using row-major ordering). + If 'True': The returned `binnumber` is a shape (D,N) ndarray where + each row indicates bin placements for each dimension respectively. In each + dimension, a binnumber of `i` means the corresponding value is between + (bin_edges[D][i-1], bin_edges[D][i]), for each dimension 'D'. + + .. versionadded:: 0.11.0 + + Examples + -------- + >>> import numpy as np + >>> from scipy import stats + >>> import matplotlib.pyplot as plt + >>> from mpl_toolkits.mplot3d import Axes3D + + Take an array of 600 (x, y) coordinates as an example. + `binned_statistic_dd` can handle arrays of higher dimension `D`. But a plot + of dimension `D+1` is required. + + >>> mu = np.array([0., 1.]) + >>> sigma = np.array([[1., -0.5],[-0.5, 1.5]]) + >>> multinormal = stats.multivariate_normal(mu, sigma) + >>> data = multinormal.rvs(size=600, random_state=235412) + >>> data.shape + (600, 2) + + Create bins and count how many arrays fall in each bin: + + >>> N = 60 + >>> x = np.linspace(-3, 3, N) + >>> y = np.linspace(-3, 4, N) + >>> ret = stats.binned_statistic_dd(data, np.arange(600), bins=[x, y], + ... statistic='count') + >>> bincounts = ret.statistic + + Set the volume and the location of bars: + + >>> dx = x[1] - x[0] + >>> dy = y[1] - y[0] + >>> x, y = np.meshgrid(x[:-1]+dx/2, y[:-1]+dy/2) + >>> z = 0 + + >>> bincounts = bincounts.ravel() + >>> x = x.ravel() + >>> y = y.ravel() + + >>> fig = plt.figure() + >>> ax = fig.add_subplot(111, projection='3d') + >>> with np.errstate(divide='ignore'): # silence random axes3d warning + ... ax.bar3d(x, y, z, dx, dy, bincounts) + + Reuse bin numbers and bin edges with new values: + + >>> ret2 = stats.binned_statistic_dd(data, -np.arange(600), + ... binned_statistic_result=ret, + ... statistic='mean') + """ + known_stats = ['mean', 'median', 'count', 'sum', 'std', 'min', 'max'] + if not callable(statistic) and statistic not in known_stats: + raise ValueError(f'invalid statistic {statistic!r}') + + try: + bins = index(bins) + except TypeError: + # bins is not an integer + pass + # If bins was an integer-like object, now it is an actual Python int. + + # For context: see gh-23751 and linked issues/PRs + if np.isnan(sample).any(): + message = ("The sample on which `values` is to be binned contains at least one " + "NaN, and the meaning is ambiguous. Consider removing or replacing " + "with numerical outliers.") + raise ValueError(message) + + # `Ndim` is the number of dimensions (e.g. `2` for `binned_statistic_2d`) + # `Dlen` is the length of elements along each dimension. + # This code is based on np.histogramdd + try: + # `sample` is an ND-array. + Dlen, Ndim = sample.shape + except (AttributeError, ValueError): + # `sample` is a sequence of 1D arrays. + sample = np.atleast_2d(sample).T + Dlen, Ndim = sample.shape + + # Store initial shape of `values` to preserve it in the output + values = np.asarray(values) + input_shape = list(values.shape) + # Make sure that `values` is 2D to iterate over rows + values = np.atleast_2d(values) + Vdim, Vlen = values.shape + + # Make sure `values` match `sample` + if statistic != 'count' and Vlen != Dlen: + raise AttributeError('The number of `values` elements must match the ' + 'length of each `sample` dimension.') + + try: + M = len(bins) + if M != Ndim: + raise AttributeError('The dimension of bins must be equal ' + 'to the dimension of the sample x.') + except TypeError: + bins = Ndim * [bins] + + if binned_statistic_result is None: + nbin, edges, dedges = _bin_edges(sample, bins, range) + binnumbers = _bin_numbers(sample, nbin, edges, dedges) + else: + edges = binned_statistic_result.bin_edges + nbin = np.array([len(edges[i]) + 1 for i in builtins.range(Ndim)]) + # +1 for outlier bins + dedges = [np.diff(edges[i]) for i in builtins.range(Ndim)] + binnumbers = binned_statistic_result.binnumber + + # Avoid overflow with double precision. Complex `values` -> `complex128`. + result_type = np.result_type(values, np.float64) + result = np.empty([Vdim, nbin.prod()], dtype=result_type) + + if statistic in {'mean', np.mean}: + result.fill(np.nan) + flatcount = _bincount(binnumbers, None) + a = flatcount.nonzero() + for vv in builtins.range(Vdim): + flatsum = _bincount(binnumbers, values[vv]) + result[vv, a] = flatsum[a] / flatcount[a] + elif statistic in {'std', np.std}: + result.fill(np.nan) + flatcount = _bincount(binnumbers, None) + a = flatcount.nonzero() + for vv in builtins.range(Vdim): + flatsum = _bincount(binnumbers, values[vv]) + delta = values[vv] - flatsum[binnumbers] / flatcount[binnumbers] + std = np.sqrt( + _bincount(binnumbers, delta*np.conj(delta))[a] / flatcount[a] + ) + result[vv, a] = std + result = np.real(result) + elif statistic == 'count': + result = np.empty([Vdim, nbin.prod()], dtype=np.float64) + result.fill(0) + flatcount = _bincount(binnumbers, None) + a = np.arange(len(flatcount)) + result[:, a] = flatcount[np.newaxis, :] + elif statistic in {'sum', np.sum}: + result.fill(0) + for vv in builtins.range(Vdim): + flatsum = _bincount(binnumbers, values[vv]) + a = np.arange(len(flatsum)) + result[vv, a] = flatsum + elif statistic in {'median', np.median}: + result.fill(np.nan) + for vv in builtins.range(Vdim): + i = np.lexsort((values[vv], binnumbers)) + _, j, counts = np.unique(binnumbers[i], + return_index=True, return_counts=True) + mid = j + (counts - 1) / 2 + mid_a = values[vv, i][np.floor(mid).astype(int)] + mid_b = values[vv, i][np.ceil(mid).astype(int)] + medians = (mid_a + mid_b) / 2 + result[vv, binnumbers[i][j]] = medians + elif statistic in {'min', np.min}: + result.fill(np.nan) + for vv in builtins.range(Vdim): + i = np.argsort(values[vv])[::-1] # Reversed so the min is last + result[vv, binnumbers[i]] = values[vv, i] + elif statistic in {'max', np.max}: + result.fill(np.nan) + for vv in builtins.range(Vdim): + i = np.argsort(values[vv]) + result[vv, binnumbers[i]] = values[vv, i] + elif callable(statistic): + with np.errstate(invalid='ignore'), catch_warnings(): + simplefilter("ignore", RuntimeWarning) + try: + null = statistic([]) + except Exception: + null = np.nan + if np.iscomplexobj(null): + result = result.astype(np.complex128) + result.fill(null) + try: + _calc_binned_statistic( + Vdim, binnumbers, result, values, statistic + ) + except ValueError: + result = result.astype(np.complex128) + _calc_binned_statistic( + Vdim, binnumbers, result, values, statistic + ) + + # Shape into a proper matrix + result = result.reshape(np.append(Vdim, nbin)) + + # Remove outliers (indices 0 and -1 for each bin-dimension). + core = tuple([slice(None)] + Ndim * [slice(1, -1)]) + result = result[core] + + # Unravel binnumbers into an ndarray, each row the bins for each dimension + if expand_binnumbers and Ndim > 1: + binnumbers = np.asarray(np.unravel_index(binnumbers, nbin)) + + if np.any(result.shape[1:] != nbin - 2): + raise RuntimeError('Internal Shape Error') + + # Reshape to have output (`result`) match input (`values`) shape + result = result.reshape(input_shape[:-1] + list(nbin-2)) + + return BinnedStatisticddResult(result, edges, binnumbers) + + +def _calc_binned_statistic(Vdim, bin_numbers, result, values, stat_func): + unique_bin_numbers = np.unique(bin_numbers) + for vv in builtins.range(Vdim): + bin_map = _create_binned_data(bin_numbers, unique_bin_numbers, + values, vv) + for i in unique_bin_numbers: + stat = stat_func(np.array(bin_map[i])) + if np.iscomplexobj(stat) and not np.iscomplexobj(result): + raise ValueError("The statistic function returns complex ") + result[vv, i] = stat + + +def _create_binned_data(bin_numbers, unique_bin_numbers, values, vv): + """ Create hashmap of bin ids to values in bins + key: bin number + value: list of binned data + """ + bin_map = dict() + for i in unique_bin_numbers: + bin_map[i] = [] + for i in builtins.range(len(bin_numbers)): + bin_map[bin_numbers[i]].append(values[vv, i]) + return bin_map + + +def _bin_edges(sample, bins=None, range=None): + """ Create edge arrays + """ + Dlen, Ndim = sample.shape + + nbin = np.empty(Ndim, int) # Number of bins in each dimension + edges = Ndim * [None] # Bin edges for each dim (will be 2D array) + dedges = Ndim * [None] # Spacing between edges (will be 2D array) + + # Preserve sample floating point precision in bin edges + edges_dtype = (sample.dtype if np.issubdtype(sample.dtype, np.floating) + else float) + + # Select range for each dimension + # Used only if number of bins is given. + if range is None: + sample = sample.astype(float, copy=True) + sample[np.isinf(sample)] = np.nan + smin = np.atleast_1d(np.nanmin(sample, axis=0)) + smax = np.atleast_1d(np.nanmax(sample, axis=0)) + else: + if len(range) != Ndim: + raise ValueError( + f"range given for {len(range)} dimensions; {Ndim} required") + smin = np.empty(Ndim) + smax = np.empty(Ndim) + for i in builtins.range(Ndim): + if range[i][1] < range[i][0]: + raise ValueError( + f"In {f'dimension {i + 1} of ' if Ndim > 1 else ''}range," + " start must be <= stop") + smin[i], smax[i] = range[i] + + # Make sure the bins have a finite width. + for i in builtins.range(len(smin)): + if smin[i] == smax[i]: + smin[i] = smin[i] - .5 + smax[i] = smax[i] + .5 + + # Create edge arrays + for i in builtins.range(Ndim): + if np.isscalar(bins[i]): + nbin[i] = bins[i] + 2 # +2 for outlier bins + edges[i] = np.linspace(smin[i], smax[i], nbin[i] - 1, + dtype=edges_dtype) + else: + edges[i] = np.asarray(bins[i], edges_dtype) + nbin[i] = len(edges[i]) + 1 # +1 for outlier bins + dedges[i] = np.diff(edges[i]) + + nbin = np.asarray(nbin) + + return nbin, edges, dedges + + +def _bin_numbers(sample, nbin, edges, dedges): + """Compute the bin number each sample falls into, in each dimension + """ + Dlen, Ndim = sample.shape + + sampBin = [ + np.digitize(sample[:, i], edges[i]) + for i in range(Ndim) + ] + + # Using `digitize`, values that fall on an edge are put in the right bin. + # For the rightmost bin, we want values equal to the right + # edge to be counted in the last bin, and not as an outlier. + for i in range(Ndim): + # Find the rounding precision + dedges_min = dedges[i].min() + if dedges_min == 0: + raise ValueError('The smallest edge difference is numerically 0.') + decimal = int(-np.log10(dedges_min)) + 6 + # Find which points are on the rightmost edge. + on_edge = np.where((sample[:, i] >= edges[i][-1]) & + (np.around(sample[:, i], decimal) == + np.around(edges[i][-1], decimal)))[0] + # Shift these points one bin to the left. + sampBin[i][on_edge] -= 1 + + # Compute the sample indices in the flattened statistic matrix. + binnumbers = np.ravel_multi_index(sampBin, nbin) + + return binnumbers diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_binomtest.py b/venv/lib/python3.12/site-packages/scipy/stats/_binomtest.py new file mode 100644 index 0000000000000000000000000000000000000000..98251adbb66a025d401d9a66e23c9cd7221cf826 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_binomtest.py @@ -0,0 +1,410 @@ +import numpy as np +import scipy._external.array_api_extra as xpx +from scipy._lib._array_api import xp_capabilities, array_namespace, xp_promote, is_jax +from scipy.optimize.elementwise import find_root +from scipy.special import ndtri +from scipy.special import _ufuncs as scu +from ._common import ConfidenceInterval + + +class BinomTestResult: + """ + Result of `scipy.stats.binomtest`. + + Attributes + ---------- + k : int + The number of successes (copied from `binomtest` input). + n : int + The number of trials (copied from `binomtest` input). + alternative : str + Indicates the alternative hypothesis specified in the input + to `binomtest`. It will be one of ``'two-sided'``, ``'greater'``, + or ``'less'``. + statistic: float + The estimate of the proportion of successes. + pvalue : float + The p-value of the hypothesis test. + + """ + def __init__(self, k, n, alternative, statistic, pvalue, xp): + self.k = k + self.n = n + self.alternative = alternative + self.statistic = statistic + self.pvalue = pvalue + self._xp = xp + + # add alias for backward compatibility + self.proportion_estimate = statistic + + def __repr__(self): + s = ("BinomTestResult(" + f"k={self.k}, " + f"n={self.n}, " + f"alternative={self.alternative!r}, " + f"statistic={self.statistic}, " + f"pvalue={self.pvalue})") + return s + + def proportion_ci(self, confidence_level=0.95, method='exact'): + """ + Compute the confidence interval for ``statistic``. + + Parameters + ---------- + confidence_level : float, optional + Confidence level for the computed confidence interval + of the estimated proportion. Default is 0.95. + method : {'exact', 'wilson', 'wilsoncc'}, optional + Selects the method used to compute the confidence interval + for the estimate of the proportion: + + 'exact' : + Use the Clopper-Pearson exact method [1]_. + 'wilson' : + Wilson's method, without continuity correction ([2]_, [3]_). + 'wilsoncc' : + Wilson's method, with continuity correction ([2]_, [3]_). + + Default is ``'exact'``. + + Returns + ------- + ci : ``ConfidenceInterval`` object + The object has attributes ``low`` and ``high`` that hold the + lower and upper bounds of the confidence interval. + + References + ---------- + .. [1] C. J. Clopper and E. S. Pearson, The use of confidence or + fiducial limits illustrated in the case of the binomial, + Biometrika, Vol. 26, No. 4, pp 404-413 (Dec. 1934). + .. [2] E. B. Wilson, Probable inference, the law of succession, and + statistical inference, J. Amer. Stat. Assoc., 22, pp 209-212 + (1927). + .. [3] Robert G. Newcombe, Two-sided confidence intervals for the + single proportion: comparison of seven methods, Statistics + in Medicine, 17, pp 857-872 (1998). + + Examples + -------- + >>> from scipy.stats import binomtest + >>> result = binomtest(k=7, n=50, p=0.1) + >>> result.statistic + 0.14 + >>> result.proportion_ci() + ConfidenceInterval(low=0.05819170033997342, high=0.26739600249700846) + """ + if method not in ('exact', 'wilson', 'wilsoncc'): + raise ValueError(f"method ('{method}') must be one of 'exact', " + "'wilson' or 'wilsoncc'.") + if not (0 <= confidence_level <= 1): + raise ValueError(f'confidence_level ({confidence_level}) must be in ' + 'the interval [0, 1].') + + xp = self._xp + k, n, confidence_level = xp_promote(self.k, self.n, confidence_level, xp=xp) + if method == 'exact': + low, high = _binom_exact_conf_int( + k, n, confidence_level, self.alternative, xp=xp) + else: + # method is 'wilson' or 'wilsoncc' + correction = method == 'wilsoncc' + low, high = _binom_wilson_conf_int( + k, n, confidence_level, self.alternative, correction, xp=xp) + low = low[()] if low.ndim == 0 else low + high = high[()] if high.ndim == 0 else high + return ConfidenceInterval(low=low, high=high) + + +def _binom_exact_conf_int(k, n, confidence_level, alternative, *, xp): + """ + Compute the estimate and confidence interval for the binomial test. + + Returns proportion, prop_low, prop_high + """ + init = (xp.zeros_like(k), xp.ones_like(k)) + args = (k, n) + alpha = ((1 - confidence_level) / 2 if alternative == 'two-sided' + else 1 - confidence_level) + + # I think using the private methods here is fine, since we will only evaluate with + # valid `p`, `k`, and `n` (or all NaNs). One exception is when `k=0` and + # `binom._sf(k-1, n, p)`: evaluates to NaN, but that's not a problem because + # `plow` has a special case for `k=0` below. + plow = (xp.zeros_like(k) if alternative == 'less' else + find_root(lambda p, k, n: _SimpleBinomial(n, p).sf(k-1) - alpha, + init, args=args).x) + phigh = (xp.ones_like(k) if alternative == 'greater' else + find_root(lambda p, k, n: _SimpleBinomial(n, p).cdf(k) - alpha, + init, args=args).x) + + plow = xp.where(k == 0, 0.0, plow) + phigh = xp.where(k == n, 1.0, phigh) + return plow, phigh + + +def _binom_wilson_conf_int(k, n, confidence_level, alternative, correction, *, xp): + # This function assumes that the arguments have already been validated. + # In particular, `alternative` must be one of 'two-sided', 'less' or + # 'greater'. + p = k / n + if alternative == 'two-sided': + z = ndtri(0.5 + 0.5*confidence_level) + else: + z = ndtri(confidence_level) + + # For reference, the formulas implemented here are from + # Newcombe (1998) (ref. [3] in the proportion_ci docstring). + denom = 2*(n + z**2) + center = (2*n*p + z**2)/denom + q = 1 - p + + if correction: + with np.errstate(divide='ignore', invalid='ignore'): + dlo = (1 + z*xp.sqrt(z**2 - 2 - 1/n + 4*p*(n*q + 1)))/denom + dhi = (1 + z*xp.sqrt(z**2 + 2 - 1/n + 4*p*(n*q - 1)))/denom + else: + delta = z / denom * xp.sqrt(4*n*p*q + z**2) + dlo, dhi = delta, delta + + lo = xp.where((k == 0) | (alternative == 'less'), 0.0, center - dlo) + hi = xp.where((k == n) | (alternative == 'greater'), 1.0, center + dhi) + return lo, hi + + +@xp_capabilities(skip_backends=[('dask.array', "")], cpu_only=True, + reason="binomial distribution ufuncs only available for NumPy", + extra_note="`alternative='two-sided'` is incompatible with JAX arrays.") +def binomtest(k, n, p=0.5, alternative='two-sided'): + """ + Perform a test that the probability of success is p. + + The binomial test [1]_ is a test of the null hypothesis that the + probability of success in a Bernoulli experiment is `p`. + + Details of the test can be found in many texts on statistics, such + as section 24.5 of [2]_. + + The documentation is written as though the function accepts and returns Python + scalars, but the function is vectorized to work elementwise with NumPy arrays. + + Parameters + ---------- + k : int + The number of successes. + n : int + The number of trials. + p : float, optional + The hypothesized probability of success, i.e. the expected + proportion of successes. The value must be in the interval + ``0 <= p <= 1``. The default value is ``p = 0.5``. + alternative : {'two-sided', 'greater', 'less'}, optional + Indicates the alternative hypothesis. The default value is + 'two-sided'. + + Returns + ------- + result : `~scipy.stats._result_classes.BinomTestResult` instance + The return value is an object with the following attributes: + + k : int + The number of successes (copied from `binomtest` input). + n : int + The number of trials (copied from `binomtest` input). + alternative : str + Indicates the alternative hypothesis specified in the input + to `binomtest`. It will be one of ``'two-sided'``, ``'greater'``, + or ``'less'``. + statistic : float + The estimate of the proportion of successes. + pvalue : float + The p-value of the hypothesis test. + + The object has the following methods: + + proportion_ci(confidence_level=0.95, method='exact') : + Compute the confidence interval for ``statistic``. + + Notes + ----- + .. versionadded:: 1.7.0 + + References + ---------- + .. [1] Binomial test, https://en.wikipedia.org/wiki/Binomial_test + .. [2] Jerrold H. Zar, Biostatistical Analysis (fifth edition), + Prentice Hall, Upper Saddle River, New Jersey USA (2010) + + Examples + -------- + >>> from scipy.stats import binomtest + + A car manufacturer claims that no more than 10% of their cars are unsafe. + 15 cars are inspected for safety, 3 were found to be unsafe. Test the + manufacturer's claim: + + >>> result = binomtest(3, n=15, p=0.1, alternative='greater') + >>> result.pvalue + 0.18406106910639114 + + The null hypothesis cannot be rejected at the 5% level of significance + because the returned p-value is greater than the critical value of 5%. + + The test statistic is equal to the estimated proportion, which is simply + ``3/15``: + + >>> result.statistic + 0.2 + + We can use the `proportion_ci()` method of the result to compute the + confidence interval of the estimate: + + >>> result.proportion_ci(confidence_level=0.95) + ConfidenceInterval(low=0.05684686759024681, high=1.0) + + """ + xp = array_namespace(k, n, p) + k, n, p = xp_promote(k, n, p, force_floating=True, broadcast=True, xp=xp) + k_valid = (k >= 0) & (k <= n) & (k == xp.floor(k)) + n_valid = (n >= 1) & (n == xp.floor(n)) + p_valid = (p >= 0) & (p <= 1) + valid = k_valid & n_valid & p_valid + k = xp.where(valid, k, xp.nan) + n = xp.where(valid, n, xp.nan) + p = xp.where(valid, p, xp.nan) + + if alternative not in ('two-sided', 'less', 'greater'): + raise ValueError(f"alternative ('{alternative}') not recognized; \n" + "must be 'two-sided', 'less' or 'greater'") + + B = _SimpleBinomial(n, p) + if alternative == 'less': + pval = B.cdf(k) + elif alternative == 'greater': + pval = B.sf(k - 1) + else: + if is_jax(xp): + message = "`alternative='two-sided'` is incompatible with JAX arrays." + raise ValueError(message) + + # alternative is 'two-sided' + d = B.pmf(k) + rerr = 1 + 1e-7 + + def k_lt_pn(d, k, p, n): + B = _SimpleBinomial(n, p) + ix = _binary_search_for_binom_tst(lambda x1: -B.pmf(x1), -d*rerr, + xp.ceil(p * n), n, xp=xp) + # y is the number of terms between mode and n that are <= d*rerr. + # ix gave us the first term where a(ix) <= d*rerr < a(ix-1) + # if the first equality doesn't hold, y=n-ix. Otherwise, we + # need to include ix as well as the equality holds. Note that + # the equality will hold in very very rare situations due to rerr. + y = n - ix + xp.asarray(d*rerr == B.pmf(ix), dtype=ix.dtype) + pval = B.cdf(k) + B.sf(n - y) + return pval + + def k_gte_pn(d, k, p, n): + B = _SimpleBinomial(n, p) + ix = _binary_search_for_binom_tst(B.pmf, d*rerr, + xp.zeros_like(n), xp.floor(p * n), xp=xp) + # y is the number of terms between 0 and mode that are <= d*rerr. + # we need to add a 1 to account for the 0 index. + # For comparing this with old behavior, see + # tst_binary_srch_for_binom_tst method in test_morestats. + y = ix + 1 + pval = B.cdf(y-1) + B.sf(k-1) + return pval + + pval = xpx.apply_where(k < p*n, (d, k, p, n), k_lt_pn, k_gte_pn) + # xp.minimum(1.0, pval) but for data-apis/array-api-compat#271 + pval = xp.minimum(xp.asarray(1.0, dtype=pval.dtype), pval) + + statistic = xp.where(valid, k/n, xp.nan) + pval = xp.where(valid, pval, xp.nan) + if statistic.ndim == 0: + k, n, statistic, pval = k[()], n[()], statistic[()], pval[()] + + result = BinomTestResult(k=k, n=n, alternative=alternative, + statistic=statistic, pvalue=pval, xp=xp) + return result + + +def _binary_search_for_binom_tst(a, d, lo, hi, *, xp): + """ + Conducts an implicit binary search on a function specified by `a`. + + Meant to be used on the binomial PMF for the case of two-sided tests + to obtain the value on the other side of the mode where the tail + probability should be computed. The values on either side of + the mode are always in order, meaning binary search is applicable. + + Parameters + ---------- + a : callable + The function over which to perform binary search. Its values + for inputs lo and hi should be in ascending order. + d : float + The value to search. + lo : int + The lower end of range to search. + hi : int + The higher end of the range to search. + + Returns + ------- + int + The index, i between lo and hi + such that a(i)<=d d + hi = xpx.at(hi)[i_gt].set(mid[i_gt] - 1) + + i_eq = (midval == d) + mid_i_eq = mid[i_eq] + lo = xpx.at(lo)[i_eq].set(mid_i_eq) + hi = xpx.at(hi)[i_eq].set(mid_i_eq) + + return xp.where(a(lo) <= d, lo, lo-1) + + +class _SimpleBinomial: + # A very simple, array-API compatible binomial distribution for use in + # hypothesis tests. May be replaced by new infrastructure Binomial + # distribution in due time. + def __init__(self, n, p, *, xp=None): + xp = array_namespace(n, p) if xp is None else xp + self.n = n + self.p = p + self.xp = xp + + def f(self, x, fun): + xp = self.xp + return xpx.lazy_apply(fun, x, self.n, self.p, as_numpy=True, xp=xp) + + def cdf(self, x): + return self.xp.where(x >= 0, self.f(self.xp.floor(x), scu._binom_cdf), 0.0) + + def sf(self, x): + return self.xp.where(x >= 0, self.f(self.xp.floor(x), scu._binom_sf), 1.0) + + def ppf(self, x): + return self.f(x, scu._binom_ppf) + + def isf(self, x): + return self.f(x, scu._binom_isf) + + def pmf(self, x): + return self.f(self.xp.floor(x), scu._binom_pmf) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_bws_test.py b/venv/lib/python3.12/site-packages/scipy/stats/_bws_test.py new file mode 100644 index 0000000000000000000000000000000000000000..786b5ebe57ada5aa0bda7dc583494004769daf3f --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_bws_test.py @@ -0,0 +1,204 @@ +import numpy as np +from functools import partial + +from scipy import stats +from scipy.stats._axis_nan_policy import _broadcast_arrays +from scipy._external import array_api_extra as xpx +from scipy._lib._array_api import (xp_capabilities, array_namespace, xp_ravel, + xp_result_type) + + +def _bws_input_validation(x, y, alternative, axis, method): + ''' Input validation and standardization for bws test''' + xp = array_namespace(x, y) + x, y = xp.asarray(x), xp.asarray(y) + x, y = xpx.atleast_nd(x, ndim=1), xpx.atleast_nd(y, ndim=1) + + axis_none = axis is None + ndim = max(x.ndim, y.ndim) + if axis_none: + x = xp_ravel(x, xp=xp) + y = xp_ravel(y, xp=xp) + axis = 0 + elif np.iterable(axis) or int(axis) != axis: + message = "`axis` must be an integer or None." + raise ValueError(message) + elif (axis >= ndim) or (axis < -ndim): + message = "`axis` is not compatible with the shapes of the inputs." + raise ValueError(message) + axis = int(axis) + + x, y = _broadcast_arrays((x, y), axis=axis, xp=xp) + nx, ny = x.shape[-1], y.shape[-1] + if nx < 2 or ny < 2: + raise ValueError('`x` and `y` must contain at least two observations each.') + + x, y = xp.moveaxis(x, axis, -1), xp.moveaxis(y, axis, -1) + z = stats.rankdata(xp.concat((x, y), axis=-1), axis=-1) + z = xp.astype(z, xp_result_type(x, y, force_floating=True, xp=xp)) + x, y = z[..., :x.shape[-1]], z[..., x.shape[-1]:] + x, y = xp.moveaxis(x, -1, axis), xp.moveaxis(y, -1, axis) + + alternatives = {'two-sided', 'less', 'greater'} + alternative = alternative.lower() + if alternative not in alternatives: + raise ValueError(f'`alternative` must be one of {alternatives}.') + + method = stats.PermutationMethod() if method is None else method + if not isinstance(method, stats.PermutationMethod): + raise ValueError('`method` must be an instance of ' + '`scipy.stats.PermutationMethod`') + + return x, y, alternative, axis, method, xp + + +def _bws_statistic(x, y, alternative, axis, xp): + '''Compute the BWS test statistic for two independent samples''' + # Public function currently does not accept `axis`, but `permutation_test` + # uses `axis` to make vectorized call. + + Ri, Hj = xp.sort(x, axis=axis), xp.sort(y, axis=axis) + n, m = Ri.shape[axis], Hj.shape[axis] + i, j = xp.arange(1, n+1, dtype=Ri.dtype), xp.arange(1, m+1, dtype=Hj.dtype) + + Bx_num = Ri - (m + n)/n * i + By_num = Hj - (m + n)/m * j + + if alternative == 'two-sided': + Bx_num = xpx.at(Bx_num)[...].multiply(Bx_num) + By_num = xpx.at(By_num)[...].multiply(By_num) + else: + Bx_num = xpx.at(Bx_num)[...].multiply(xp.abs(Bx_num)) + By_num = xpx.at(By_num)[...].multiply(xp.abs(By_num)) + + Bx_den = i/(n+1) * (1 - i/(n+1)) * m*(m+n)/n + By_den = j/(m+1) * (1 - j/(m+1)) * n*(m+n)/m + + Bx = 1/n * xp.sum(Bx_num/Bx_den, axis=axis) + By = 1/m * xp.sum(By_num/By_den, axis=axis) + + B = (Bx + By) / 2 if alternative == 'two-sided' else (Bx - By) / 2 + + return B + + +@xp_capabilities(skip_backends=[('dask.array', 'no rankdata')]) +def bws_test(x, y, *, alternative="two-sided", axis=0, method=None): + r'''Perform the Baumgartner-Weiss-Schindler test on two independent samples. + + The Baumgartner-Weiss-Schindler (BWS) test is a nonparametric test of + the null hypothesis that the distribution underlying sample `x` + is the same as the distribution underlying sample `y`. Unlike + the Kolmogorov-Smirnov, Wilcoxon, and Cramer-Von Mises tests, + the BWS test weights the integral by the variance of the difference + in cumulative distribution functions (CDFs), emphasizing the tails of the + distributions, which increases the power of the test in many applications. + + Parameters + ---------- + x, y : array-like + Arrays of samples. Shapes must be broadcastable except along `axis`. + alternative : {'two-sided', 'less', 'greater'}, optional + Defines the alternative hypothesis. Default is 'two-sided'. + Let *F(u)* and *G(u)* be the cumulative distribution functions of the + distributions underlying `x` and `y`, respectively. Then the following + alternative hypotheses are available: + + * 'two-sided': the distributions are not equal, i.e. *F(u) ≠ G(u)* for + at least one *u*. + * 'less': the distribution underlying `x` is stochastically less than + the distribution underlying `y`, i.e. *F(u) >= G(u)* for all *u*. + * 'greater': the distribution underlying `x` is stochastically greater + than the distribution underlying `y`, i.e. *F(u) <= G(u)* for all + *u*. + + Under a more restrictive set of assumptions, the alternative hypotheses + can be expressed in terms of the locations of the distributions; + see [2] section 5.1. + axis : int or None, default: 0 + Axis along which the quantiles are computed. + ``None`` ravels both `x` and `y` before performing the calculation. + method : PermutationMethod, optional + Configures the method used to compute the p-value. The default is + the default `PermutationMethod` object. + + Returns + ------- + res : PermutationTestResult + An object with attributes: + + statistic : float + The observed test statistic of the data. + pvalue : float + The p-value for the given alternative. + null_distribution : ndarray + The values of the test statistic generated under the null hypothesis. + + See Also + -------- + scipy.stats.wilcoxon, scipy.stats.mannwhitneyu, scipy.stats.ttest_ind + + Notes + ----- + When ``alternative=='two-sided'``, the statistic is defined by the + equations given in [1]_ Section 2. This statistic is not appropriate for + one-sided alternatives; in that case, the statistic is the *negative* of + that given by the equations in [1]_ Section 2. Consequently, when the + distribution of the first sample is stochastically greater than that of the + second sample, the statistic will tend to be positive. + + References + ---------- + .. [1] Neuhäuser, M. (2005). Exact Tests Based on the + Baumgartner-Weiss-Schindler Statistic: A Survey. Statistical Papers, + 46(1), 1-29. + .. [2] Fay, M. P., & Proschan, M. A. (2010). Wilcoxon-Mann-Whitney or t-test? + On assumptions for hypothesis tests and multiple interpretations of + decision rules. Statistics surveys, 4, 1. + + Examples + -------- + We follow the example of table 3 in [1]_: Fourteen children were divided + randomly into two groups. Their ranks at performing a specific tests are + as follows. + + >>> import numpy as np + >>> x = [1, 2, 3, 4, 6, 7, 8] + >>> y = [5, 9, 10, 11, 12, 13, 14] + + We use the BWS test to assess whether there is a statistically significant + difference between the two groups. + The null hypothesis is that there is no difference in the distributions of + performance between the two groups. We decide that a significance level of + 1% is required to reject the null hypothesis in favor of the alternative + that the distributions are different. + Since the number of samples is very small, we can compare the observed test + statistic against the *exact* distribution of the test statistic under the + null hypothesis. + + >>> from scipy.stats import bws_test + >>> res = bws_test(x, y) + >>> print(res.statistic) + 5.132167152575315 + + This agrees with :math:`B = 5.132` reported in [1]_. The *p*-value produced + by `bws_test` also agrees with :math:`p = 0.0029` reported in [1]_. + + >>> print(res.pvalue) + 0.002913752913752914 + + Because the p-value is below our threshold of 1%, we take this as evidence + against the null hypothesis in favor of the alternative that there is a + difference in performance between the two groups. + ''' + + x, y, alternative, axis, method, xp = _bws_input_validation(x, y, alternative, + axis, method) + bws_statistic = partial(_bws_statistic, alternative=alternative, xp=xp) + + permutation_alternative = 'less' if alternative == 'less' else 'greater' + res = stats.permutation_test((x, y), bws_statistic, + alternative=permutation_alternative, axis=axis, + **method._asdict()) + + return res diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_censored_data.py b/venv/lib/python3.12/site-packages/scipy/stats/_censored_data.py new file mode 100644 index 0000000000000000000000000000000000000000..14c90aa3edb8c5303642a9b36a3323fe6fbcac2c --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_censored_data.py @@ -0,0 +1,468 @@ +from types import GenericAlias +import numpy as np + + +def _validate_1d(a, name, allow_inf=False): + if np.ndim(a) != 1: + raise ValueError(f'`{name}` must be a one-dimensional sequence.') + if np.isnan(a).any(): + raise ValueError(f'`{name}` must not contain nan.') + if not allow_inf and np.isinf(a).any(): + raise ValueError(f'`{name}` must contain only finite values.') + + +def _validate_interval(interval): + interval = np.asarray(interval) + if interval.shape == (0,): + # The input was a sequence with length 0. + interval = interval.reshape((0, 2)) + if interval.ndim != 2 or interval.shape[-1] != 2: + raise ValueError('`interval` must be a two-dimensional array with ' + 'shape (m, 2), where m is the number of ' + 'interval-censored values, but got shape ' + f'{interval.shape}') + + if np.isnan(interval).any(): + raise ValueError('`interval` must not contain nan.') + if np.isinf(interval).all(axis=1).any(): + raise ValueError('In each row in `interval`, both values must not' + ' be infinite.') + if (interval[:, 0] > interval[:, 1]).any(): + raise ValueError('In each row of `interval`, the left value must not' + ' exceed the right value.') + + uncensored_mask = interval[:, 0] == interval[:, 1] + left_mask = np.isinf(interval[:, 0]) + right_mask = np.isinf(interval[:, 1]) + interval_mask = np.isfinite(interval).all(axis=1) & ~uncensored_mask + + uncensored2 = interval[uncensored_mask, 0] + left2 = interval[left_mask, 1] + right2 = interval[right_mask, 0] + interval2 = interval[interval_mask] + + return uncensored2, left2, right2, interval2 + + +def _validate_x_censored(x, censored): + x = np.asarray(x) + if x.ndim != 1: + raise ValueError('`x` must be one-dimensional.') + censored = np.asarray(censored) + if censored.ndim != 1: + raise ValueError('`censored` must be one-dimensional.') + if (~np.isfinite(x)).any(): + raise ValueError('`x` must not contain nan or inf.') + if censored.size != x.size: + raise ValueError('`x` and `censored` must have the same length.') + return x, censored.astype(bool) + + +class CensoredData: + """ + Instances of this class represent censored data. + + Instances may be passed to the ``fit`` method of continuous + univariate SciPy distributions for maximum likelihood estimation. + The *only* method of the univariate continuous distributions that + understands `CensoredData` is the ``fit`` method. An instance of + `CensoredData` can not be passed to methods such as ``pdf`` and + ``cdf``. + + An observation is said to be *censored* when the precise value is unknown, + but it has a known upper and/or lower bound. The conventional terminology + is: + + * left-censored: an observation is below a certain value but it is + unknown by how much. + * right-censored: an observation is above a certain value but it is + unknown by how much. + * interval-censored: an observation lies somewhere on an interval between + two values. + + Left-, right-, and interval-censored data can be represented by + `CensoredData`. + + For convenience, the class methods ``left_censored`` and + ``right_censored`` are provided to create a `CensoredData` + instance from a single one-dimensional array of measurements + and a corresponding boolean array to indicate which measurements + are censored. The class method ``interval_censored`` accepts two + one-dimensional arrays that hold the lower and upper bounds of the + intervals. + + Parameters + ---------- + uncensored : array_like, 1D + Uncensored observations. + left : array_like, 1D + Left-censored observations. + right : array_like, 1D + Right-censored observations. + interval : array_like, 2D, with shape (m, 2) + Interval-censored observations. Each row ``interval[k, :]`` + represents the interval for the kth interval-censored observation. + + Notes + ----- + In the input array `interval`, the lower bound of the interval may + be ``-inf``, and the upper bound may be ``inf``, but at least one must be + finite. When the lower bound is ``-inf``, the row represents a left- + censored observation, and when the upper bound is ``inf``, the row + represents a right-censored observation. If the length of an interval + is 0 (i.e. ``interval[k, 0] == interval[k, 1]``, the observation is + treated as uncensored. So one can represent all the types of censored + and uncensored data in ``interval``, but it is generally more convenient + to use `uncensored`, `left` and `right` for uncensored, left-censored and + right-censored observations, respectively. + + Examples + -------- + In the most general case, a censored data set may contain values that + are left-censored, right-censored, interval-censored, and uncensored. + For example, here we create a data set with five observations. Two + are uncensored (values 1 and 1.5), one is a left-censored observation + of 0, one is a right-censored observation of 10 and one is + interval-censored in the interval [2, 3]. + + >>> import numpy as np + >>> from scipy.stats import CensoredData + >>> data = CensoredData(uncensored=[1, 1.5], left=[0], right=[10], + ... interval=[[2, 3]]) + >>> print(data) + CensoredData(5 values: 2 not censored, 1 left-censored, + 1 right-censored, 1 interval-censored) + + Equivalently, + + >>> data = CensoredData(interval=[[1, 1], + ... [1.5, 1.5], + ... [-np.inf, 0], + ... [10, np.inf], + ... [2, 3]]) + >>> print(data) + CensoredData(5 values: 2 not censored, 1 left-censored, + 1 right-censored, 1 interval-censored) + + A common case is to have a mix of uncensored observations and censored + observations that are all right-censored (or all left-censored). For + example, consider an experiment in which six devices are started at + various times and left running until they fail. Assume that time is + measured in hours, and the experiment is stopped after 30 hours, even + if all the devices have not failed by that time. We might end up with + data such as this:: + + Device Start-time Fail-time Time-to-failure + 1 0 13 13 + 2 2 24 22 + 3 5 22 17 + 4 8 23 15 + 5 10 *** >20 + 6 12 *** >18 + + Two of the devices had not failed when the experiment was stopped; + the observations of the time-to-failure for these two devices are + right-censored. We can represent this data with + + >>> data = CensoredData(uncensored=[13, 22, 17, 15], right=[20, 18]) + >>> print(data) + CensoredData(6 values: 4 not censored, 2 right-censored) + + Alternatively, we can use the method `CensoredData.right_censored` to + create a representation of this data. The time-to-failure observations + are put the list ``ttf``. The ``censored`` list indicates which values + in ``ttf`` are censored. + + >>> ttf = [13, 22, 17, 15, 20, 18] + >>> censored = [False, False, False, False, True, True] + + Pass these lists to `CensoredData.right_censored` to create an + instance of `CensoredData`. + + >>> data = CensoredData.right_censored(ttf, censored) + >>> print(data) + CensoredData(6 values: 4 not censored, 2 right-censored) + + If the input data is interval censored and already stored in two + arrays, one holding the low end of the intervals and another + holding the high ends, the class method ``interval_censored`` can + be used to create the `CensoredData` instance. + + This example creates an instance with four interval-censored values. + The intervals are [10, 11], [0.5, 1], [2, 3], and [12.5, 13.5]. + + >>> a = [10, 0.5, 2, 12.5] # Low ends of the intervals + >>> b = [11, 1.0, 3, 13.5] # High ends of the intervals + >>> data = CensoredData.interval_censored(low=a, high=b) + >>> print(data) + CensoredData(4 values: 0 not censored, 4 interval-censored) + + Finally, we create and censor some data from the `weibull_min` + distribution, and then fit `weibull_min` to that data. We'll assume + that the location parameter is known to be 0. + + >>> from scipy.stats import weibull_min + >>> rng = np.random.default_rng() + + Create the random data set. + + >>> x = weibull_min.rvs(2.5, loc=0, scale=30, size=250, random_state=rng) + >>> x[x > 40] = 40 # Right-censor values greater or equal to 40. + + Create the `CensoredData` instance with the `right_censored` method. + The censored values are those where the value is 40. + + >>> data = CensoredData.right_censored(x, x == 40) + >>> print(data) + CensoredData(250 values: 215 not censored, 35 right-censored) + + 35 values have been right-censored. + + Fit `weibull_min` to the censored data. We expect to shape and scale + to be approximately 2.5 and 30, respectively. + + >>> weibull_min.fit(data, floc=0) + (2.3575922823897315, 0, 30.40650074451254) + + """ + + # generic type compatibility with scipy-stubs + __class_getitem__: classmethod = classmethod(GenericAlias) + + def __init__(self, uncensored=None, *, left=None, right=None, + interval=None): + if uncensored is None: + uncensored = [] + if left is None: + left = [] + if right is None: + right = [] + if interval is None: + interval = np.empty((0, 2)) + + _validate_1d(uncensored, 'uncensored') + _validate_1d(left, 'left') + _validate_1d(right, 'right') + uncensored2, left2, right2, interval2 = _validate_interval(interval) + + self._uncensored = np.concatenate((uncensored, uncensored2)) + self._left = np.concatenate((left, left2)) + self._right = np.concatenate((right, right2)) + # Note that by construction, the private attribute _interval + # will be a 2D array that contains only finite values representing + # intervals with nonzero but finite length. + self._interval = interval2 + + def __repr__(self): + uncensored_str = " ".join(np.array_repr(self._uncensored).split()) + left_str = " ".join(np.array_repr(self._left).split()) + right_str = " ".join(np.array_repr(self._right).split()) + interval_str = " ".join(np.array_repr(self._interval).split()) + return (f"CensoredData(uncensored={uncensored_str}, left={left_str}, " + f"right={right_str}, interval={interval_str})") + + def __str__(self): + num_nc = len(self._uncensored) + num_lc = len(self._left) + num_rc = len(self._right) + num_ic = len(self._interval) + n = num_nc + num_lc + num_rc + num_ic + parts = [f'{num_nc} not censored'] + if num_lc > 0: + parts.append(f'{num_lc} left-censored') + if num_rc > 0: + parts.append(f'{num_rc} right-censored') + if num_ic > 0: + parts.append(f'{num_ic} interval-censored') + return f'CensoredData({n} values: ' + ', '.join(parts) + ')' + + # This is not a complete implementation of the arithmetic operators. + # All we need is subtracting a scalar and dividing by a scalar. + + def __sub__(self, other): + return CensoredData(uncensored=self._uncensored - other, + left=self._left - other, + right=self._right - other, + interval=self._interval - other) + + def __truediv__(self, other): + return CensoredData(uncensored=self._uncensored / other, + left=self._left / other, + right=self._right / other, + interval=self._interval / other) + + def __len__(self): + """ + The number of values (censored and not censored). + """ + return (len(self._uncensored) + len(self._left) + len(self._right) + + len(self._interval)) + + def num_censored(self): + """ + Number of censored values. + + Returns + ------- + int + The number of censored values. + """ + return len(self._left) + len(self._right) + len(self._interval) + + @classmethod + def right_censored(cls, x, censored): + """ + Create a `CensoredData` instance of right-censored data. + + Parameters + ---------- + x : array_like + `x` is the array of observed data or measurements. + `x` must be a one-dimensional sequence of finite numbers. + censored : array_like of bool + `censored` must be a one-dimensional sequence of boolean + values. If ``censored[k]`` is True, the corresponding value + in `x` is right-censored. That is, the value ``x[k]`` + is the lower bound of the true (but unknown) value. + + Returns + ------- + data : `CensoredData` + An instance of `CensoredData` that represents the + collection of uncensored and right-censored values. + + Examples + -------- + >>> from scipy.stats import CensoredData + + Two uncensored values (4 and 10) and two right-censored values + (24 and 25). + + >>> data = CensoredData.right_censored([4, 10, 24, 25], + ... [False, False, True, True]) + >>> data + CensoredData(uncensored=array([ 4., 10.]), + left=array([], dtype=float64), right=array([24., 25.]), + interval=array([], shape=(0, 2), dtype=float64)) + >>> print(data) + CensoredData(4 values: 2 not censored, 2 right-censored) + """ + x, censored = _validate_x_censored(x, censored) + return cls(uncensored=x[~censored], right=x[censored]) + + @classmethod + def left_censored(cls, x, censored): + """ + Create a `CensoredData` instance of left-censored data. + + Parameters + ---------- + x : array_like + `x` is the array of observed data or measurements. + `x` must be a one-dimensional sequence of finite numbers. + censored : array_like of bool + `censored` must be a one-dimensional sequence of boolean + values. If ``censored[k]`` is True, the corresponding value + in `x` is left-censored. That is, the value ``x[k]`` + is the upper bound of the true (but unknown) value. + + Returns + ------- + data : `CensoredData` + An instance of `CensoredData` that represents the + collection of uncensored and left-censored values. + + Examples + -------- + >>> from scipy.stats import CensoredData + + Two uncensored values (0.12 and 0.033) and two left-censored values + (both 1e-3). + + >>> data = CensoredData.left_censored([0.12, 0.033, 1e-3, 1e-3], + ... [False, False, True, True]) + >>> data + CensoredData(uncensored=array([0.12 , 0.033]), + left=array([0.001, 0.001]), right=array([], dtype=float64), + interval=array([], shape=(0, 2), dtype=float64)) + >>> print(data) + CensoredData(4 values: 2 not censored, 2 left-censored) + """ + x, censored = _validate_x_censored(x, censored) + return cls(uncensored=x[~censored], left=x[censored]) + + @classmethod + def interval_censored(cls, low, high): + """ + Create a `CensoredData` instance of interval-censored data. + + This method is useful when all the data is interval-censored, and + the low and high ends of the intervals are already stored in + separate one-dimensional arrays. + + Parameters + ---------- + low : array_like + The one-dimensional array containing the low ends of the + intervals. + high : array_like + The one-dimensional array containing the high ends of the + intervals. + + Returns + ------- + data : `CensoredData` + An instance of `CensoredData` that represents the + collection of censored values. + + Examples + -------- + >>> import numpy as np + >>> from scipy.stats import CensoredData + + ``a`` and ``b`` are the low and high ends of a collection of + interval-censored values. + + >>> a = [0.5, 2.0, 3.0, 5.5] + >>> b = [1.0, 2.5, 3.5, 7.0] + >>> data = CensoredData.interval_censored(low=a, high=b) + >>> print(data) + CensoredData(4 values: 0 not censored, 4 interval-censored) + """ + _validate_1d(low, 'low', allow_inf=True) + _validate_1d(high, 'high', allow_inf=True) + if len(low) != len(high): + raise ValueError('`low` and `high` must have the same length.') + interval = np.column_stack((low, high)) + uncensored, left, right, interval = _validate_interval(interval) + return cls(uncensored=uncensored, left=left, right=right, + interval=interval) + + def _uncensor(self): + """ + This function is used when a non-censored version of the data + is needed to create a rough estimate of the parameters of a + distribution via the method of moments or some similar method. + The data is "uncensored" by taking the given endpoints as the + data for the left- or right-censored data, and the mean for the + interval-censored data. + """ + data = np.concatenate((self._uncensored, self._left, self._right, + self._interval.mean(axis=1))) + return data + + def _supported(self, a, b): + """ + Return a subset of self containing the values that are in + (or overlap with) the interval (a, b). + """ + uncensored = self._uncensored + uncensored = uncensored[(a < uncensored) & (uncensored < b)] + left = self._left + left = left[a < left] + right = self._right + right = right[right < b] + interval = self._interval + interval = interval[(a < interval[:, 1]) & (interval[:, 0] < b)] + return CensoredData(uncensored, left=left, right=right, + interval=interval) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_common.py b/venv/lib/python3.12/site-packages/scipy/stats/_common.py new file mode 100644 index 0000000000000000000000000000000000000000..4011d425cc4afea3c7ee8937526b13f1f92b0850 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_common.py @@ -0,0 +1,5 @@ +from collections import namedtuple + + +ConfidenceInterval = namedtuple("ConfidenceInterval", ["low", "high"]) +ConfidenceInterval. __doc__ = "Class for confidence intervals." diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_constants.py b/venv/lib/python3.12/site-packages/scipy/stats/_constants.py new file mode 100644 index 0000000000000000000000000000000000000000..b539ce8146ebdbc8e08c66143461b04d742804f2 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_constants.py @@ -0,0 +1,42 @@ +""" +Statistics-related constants. + +""" +import numpy as np + + +# The smallest representable positive number such that 1.0 + _EPS != 1.0. +_EPS = np.finfo(float).eps + +# The largest [in magnitude] usable floating value. +_XMAX = np.finfo(float).max + +# The log of the largest usable floating value; useful for knowing +# when exp(something) will overflow +_LOGXMAX = np.log(_XMAX) + +# The smallest [in magnitude] usable (i.e. not subnormal) double precision +# floating value. +_XMIN = np.finfo(float).tiny + +# The log of the smallest [in magnitude] usable (i.e not subnormal) +# double precision floating value. +_LOGXMIN = np.log(_XMIN) + +# -special.psi(1) +_EULER = 0.577215664901532860606512090082402431042 + +# special.zeta(3, 1) Apery's constant +_ZETA3 = 1.202056903159594285399738161511449990765 + +# sqrt(pi) +_SQRT_PI = 1.772453850905516027298167483341145182798 + +# sqrt(2/pi) +_SQRT_2_OVER_PI = 0.7978845608028654 + +# log(pi) +_LOG_PI = 1.1447298858494002 + +# log(sqrt(2/pi)) +_LOG_SQRT_2_OVER_PI = -0.22579135264472744 diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_continued_fraction.py b/venv/lib/python3.12/site-packages/scipy/stats/_continued_fraction.py new file mode 100644 index 0000000000000000000000000000000000000000..4966b1c99ed092ba3578aa7ca15b5ba8ea4bc6a9 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_continued_fraction.py @@ -0,0 +1,387 @@ +import numpy as np + +from scipy._lib._array_api import ( + array_namespace, xp_ravel, xp_copy, xp_promote +) +import scipy._lib._elementwise_iterative_method as eim +from scipy._lib._util import _RichResult +from scipy import special + +# Todo: +# Avoid special-casing key 'n' in _lib._elementwise_iterative_method::_check_termination +# Rearrange termination condition to allow absolute and relative tolerances? +# Interpret/return |f_n - f_{n-1}| as an error estimate? +# Return gracefully for size=0 arrays + +def _logaddexp(x, y, xp=None): + # logaddexp that supports complex numbers + xp = array_namespace(x, y) if xp is None else xp + x, y = xp.broadcast_arrays(x, y) + xy = xp.stack((x, y), axis=0) + return special.logsumexp(xy, axis=0) + + +def _continued_fraction_iv(a, b, args, tolerances, maxiter, log): + # Input validation for `_continued_fraction` + + if not callable(a) or not callable(b): + raise ValueError('`a` and `b` must be callable.') + + if not np.iterable(args): + args = (args,) + + # Call each callable once to determine namespace and dtypes + a0, b0 = a(0, *args), b(0, *args) + xp = array_namespace(a0, b0, *args) + a0, b0, *args = xp_promote(a0, b0, *args, force_floating=True, broadcast=True, + xp=xp) + shape, dtype = a0.shape, a0.dtype + a0, b0, *args = (xp_ravel(arg) for arg in (a0, b0) + tuple(args)) + + tolerances = {} if tolerances is None else tolerances + eps = tolerances.get('eps', None) + tiny = tolerances.get('tiny', None) + + # tolerances are floats, not arrays, so it's OK to use NumPy + message = ('`eps` and `tiny` must be (or represent the logarithm of) ' + 'finite, positive, real scalars.') + tols = np.asarray([eps if eps is not None else 1, + tiny if tiny is not None else 1]) + not_real = (not np.issubdtype(tols.dtype, np.number) + or np.issubdtype(tols.dtype, np.complexfloating)) + not_positive = np.any(tols <= 0) if not log else False + not_finite = not np.all(np.isfinite(tols)) + not_scalar = tols.shape != (2,) + if not_real or not_positive or not_finite or not_scalar: + raise ValueError(message) + + maxiter_int = int(maxiter) + if maxiter != maxiter_int or maxiter < 0: + raise ValueError('`maxiter` must be a non-negative integer.') + + if not isinstance(log, bool): + raise ValueError('`log` must be boolean.') + + return a, b, args, eps, tiny, maxiter, log, a0, b0, shape, dtype, xp + + +def _continued_fraction(a, b, *, args=(), tolerances=None, maxiter=100, log=False): + r"""Evaluate a generalized continued fraction numerically. + + `_continued_fraction` iteratively evaluates convergents of a continued fraction + given coefficients returned by callables `a` and `b`. Iteration terminates when + `maxiter` terms have been evaluated or a termination criterion controlled by + `tolerances` is satisfied, and the final convergent is returned as the ``f`` + attribute of the result object. + + This function works elementwise when `args` contains (broadcastable) arrays. + + Parameters + ---------- + a, b: callable + Functions that provide the *numerator* and *denominator* coefficients of + the continued fraction, respectively. + + The signature of each must be:: + + a(n: int, *argsj) -> ndarray + + where ``n`` is the coefficient number and ``argsj`` is a tuple, which may + contain an arbitrary number of arrays of any shape. `a` and `b` must be + elementwise functions: each scalar element ``a(n, *argsj)[i]`` must equal + ``a(n, *[argj[i] for argj in argsj])`` for valid indices ``i``. + `a` and `b` must not mutate the arrays in ``argsj``. + + The result shape is the broadcasted shape of ``a(0, *args)`` and + ``b(0, *args)``. The dtype used throughout computation is the result dtype + of these terms if it is a float, and the default float of the array library + otherwise. The numerical value of ``a(0, *args)`` is ignored, and + the value of the leading term ``b(0, *args)`` is the so-called "integer" + part of the continued fraction (although it need not be integral). + + args : tuple of array_like, optional + Additional positional *array* arguments to be passed to `a` and `b`. Arrays + must be broadcastable with one another. If the coefficient callables + require additional arguments that are not broadcastable with one + another, wrap them with callables `a` and `b` such that `a` and `b` accept + only ``n`` and broadcastable array arguments. + tolerances : dictionary of floats, optional + Tolerances and numerical thresholds used by the algorithm. Currently, + valid keys of the dictionary are: + + - ``eps`` - the convergence threshold of Lentz' algorithm + - ``tiny`` - not strictly a "tolerance", but a very small positive number + used to avoid division by zero + + The default `eps` is the precision of the appropriate dtype, and the default + `tiny` is the precision squared. [1]_ advises that ``eps`` is "as small as + you like", but for most purposes, it should not be set smaller than the default + because it may prevent convergence of the algorithm. [1]_ also advises that + ``tiny`` should be less than typical values of ``eps * b(n)``, so the default + is a good choice unless the :math:`b_n` are very small. See [1]_ for details. + maxiter : int, default: 100 + The maximum number of iterations of the algorithm to perform. + log : bool, default: False + If True, `a` and `b` return the (natural) logarithm of the terms, `tolerances` + contains the logarithm of the tolerances, and the result object reports the + logarithm of the convergent. + + Returns + ------- + res : _RichResult + An object similar to an instance of `scipy.optimize.OptimizeResult` with the + following attributes. The descriptions are written as though the values will + be scalars; however, if `f` returns an array, the outputs will be + arrays of the same shape. + + success : bool array + ``True`` where the algorithm terminated successfully (status ``0``); + ``False`` otherwise. + status : int array + An integer representing the exit status of the algorithm. + + - ``0`` : The algorithm converged to the specified tolerances. + - ``-2`` : The maximum number of iterations was reached. + - ``-3`` : A non-finite value was encountered. + + f : float array + The convergent which satisfied a termination criterion. + nit : int array + The number of iterations of the algorithm that were performed. + nfev : int array + The number of terms that were evaluated. + + Notes + ----- + A generalized continued fraction is an expression of the form + + .. math:: + + b_0 + \frac{a_1}{b_1 + \frac{a_2}{b_2 + \frac{a_3}{b_3 + \cdots}}} + + Successive "convergents" approximate the infinitely recursive continued fraction + with a finite number of terms :math:`a_n` and :math:`b_n`, which are provided + by callables `a` and `b`, respectively. This implementation follows the modified + Lentz algorithm ([1]_, [2]_) to evaluate successive convergents until a + termination condition is satisfied. + + References + ---------- + .. [1] Press, William H., and Saul A. Teukolsky. "Evaluating continued fractions + and computing exponential integrals." Computers in Physics 2.5 (1988): 88-89. + .. [2] Lentz's algorithm. Wikipedia. + https://en.wikipedia.org/wiki/Lentz%27s_algorithm + .. [3] Continued fraction. Wikipedia. + https://en.wikipedia.org/wiki/Continued_fraction + .. [4] Generalized continued fraction. Wikipedia. + https://en.wikipedia.org/wiki/Generalized_continued_fraction + + Examples + -------- + The "simple continued fraction" of :math:`\pi` is given in [3]_ as + + .. math:: + + 3 + \frac{1}{7 + \frac{1}{15 + \frac{1}{1 + \cdots}}} + + where the :math:`b_n` terms follow no obvious pattern: + + >>> b = [3, 7, 15, 1, 292, 1, 1, 1, 2, 1, 3, 1] + + and the :math:`a_n` terms are all :math:`1`. + In this case, all the terms have been precomputed, so we call `_continued_fraction` + with simple callables which simply return the precomputed coefficients: + + >>> import numpy as np + >>> from scipy.special._continued_fraction import _continued_fraction + >>> res = _continued_fraction(a=lambda n: 1, b=lambda n: b[n], maxiter=len(b) - 1) + >>> (res.f - np.pi) / np.pi + np.float64(7.067899292141148e-15) + + A generalized continued fraction for :math:`\pi` is given by: + + .. math:: + + 3 + \frac{1^2}{6 + \frac{3^2}{6 + \frac{5^2}{6 + \cdots}}} + + We define the coefficient callables as: + + >>> def a(n): + ... return (2*n - 1)**2 + >>> + >>> def b(n): + ... if n == 0: + ... return 3 + ... else: + ... return 6 + + Then the continued fraction can be evaluated as: + + >>> res = _continued_fraction(a, b) + >>> res + success: False + status: -2 + f: 3.1415924109719846 + nit: 100 + nfev: 101 + + Note that the requested tolerance was not reached within the (default) + maximum number of iterations because it converges very slowly. + An expression that converges more rapidly is expressed as the difference + between two continued fractions. We will compute both of them in one + vectorized call to `_continued_fraction`. + + >>> u, v = 5, 239 + >>> + >>> def a(n, a1, _): + ... # The shape of the output must be the shape of the arguments + ... shape = a1.shape + ... if n == 0: + ... return np.zeros(shape) + ... elif n == 1: + ... return a1 + ... else: + ... return np.full(shape, (n-1)**2) + >>> + >>> def b(n, _, uv): + ... shape = uv.shape + ... if n == 0: + ... return np.zeros(shape) + ... return np.full(shape, (2*n - 1)*uv) + >>> + >>> res = _continued_fraction(a, b, args=([16, 4], [u, v])) + >>> res + success: [ True True] + status: [0 0] + f: [ 3.158e+00 1.674e-02] + nit: [10 4] + nfev: [11 5] + + Note that the second term converged in fewer than half the number of iterations + as the first. The approximation of :math:`\pi` is the difference between the two: + + >>> pi = res.f[0] - res.f[1] + >>> (pi - np.pi) / np.pi + np.float64(2.8271597168564594e-16) + + If it is more efficient to compute the :math:`a_n` and :math:`b_n` terms together, + consider instantiating a class with a method that computes both terms and stores + the results in an attribute. Separate methods of the class retrieve the + coefficients, and these methods are passed to `_continued_fraction` as arguments + `a` and `b`. Similarly,if the coefficients can be computed recursively in terms of + previous coefficients, use a class to maintain state between callable evaluations. + + """ + + res = _continued_fraction_iv(a, b, args, tolerances, maxiter, log) + a, b, args, eps, tiny, maxiter, log, a0, b0, shape, dtype, xp = res + callback = None # don't want to test it, but easy to add later + + # The EIM framework was designed for the case in where there would + # be only one callable, and all arguments of the callable would be + # arrays. We're going a bit beyond that here, since we have two callables, + # and the first argument is an integer (the number of the term). Rather + # than complicate the framework, we wrap the user-provided callables to + # make this problem fit within the existing framework. + + def a(n, *args, a=a): + n = int(xp.real(xp_ravel(n))[0]) + return a(n, *args) + + def b(n, *args, b=b): + n = int(xp.real(xp_ravel(n))[0]) + return b(n, *args) + + def func(n, *args): + return xp.stack((a(n, *args), b(n, *args)), axis=-1) + + status = xp.full_like(a0, eim._EINPROGRESS, dtype=xp.int32) # in progress + nit, nfev = 0, 1 # one function evaluation (per function) performed above + maxiter = 100 if maxiter is None else maxiter + + # Quotations describing the algorithm are from [1]_ + # "... as small as you like, say eps" + if eps is None: + eps = xp.finfo(dtype).eps if not log else np.log(xp.finfo(dtype).eps) + + # "The parameter tiny should be less than typical values of eps |b_n|" + if tiny is None: + tiny = xp.finfo(dtype).eps**2 if not log else 2*np.log(xp.finfo(dtype).eps) + + # "Set f0 and C0 to the value b0 or to tiny if b0=0. Set D0 = 0. + zero = -xp.inf if log else 0 + fn = xp.where(b0 == zero, tiny, b0) + Cnm1 = xp_copy(fn) + Dnm1 = xp.full_like(fn, zero) + + CnDn = xp.full_like(fn, xp.inf) + + work = _RichResult(n=0, fn=fn, Cnm1=Cnm1, Dnm1=Dnm1, CnDn=CnDn, + eps=eps, tiny=tiny, + nit=nit, nfev=nfev, status=status) + res_work_pairs = [('status', 'status'), ('f', 'fn'), + ('nit', 'nit'), ('nfev', 'nfev')] + + def pre_func_eval(work): + work.n = xp.reshape(xp.asarray(work.n + 1), (-1,)) + return work.n + + def post_func_eval(n, ab, work): + an, bn = ab[..., 0], ab[..., 1] + + zero = 0 if not log else -xp.inf + + # "Set D_n = 1/(b_n + a_n D_{n-1}) or 1/tiny, if the denominator vanishes" + denominator = (bn + an*work.Dnm1 if not log + else _logaddexp(bn, an + work.Dnm1, xp=xp)) + denominator[denominator == zero] = tiny + Dn = (1/denominator if not log + else -denominator) + + # "Set C_n = b_n + a_n / C_{n-1} (or =tiny, if the expression vanishes)" + Cn = (bn + an / work.Cnm1 if not log + else _logaddexp(bn, an - work.Cnm1, xp=xp)) + Cn[Cn == zero] = tiny + + # "and set f_n = f_{n-1} C_n D_n" + work.CnDn = (Cn * Dn if not log + else Cn + Dn) + work.fn = (work.fn * work.CnDn if not log + else work.fn + work.CnDn) + + + work.Cnm1, work.Dnm1 = Cn, Dn + + def check_termination(work): + # Check for all terminal conditions and record statuses. + stop = xp.zeros_like(work.CnDn, dtype=xp.bool) + + # "You quit when |D_n C_n - 1| is as small as you like, say eps" + pij = xp.full_like(work.CnDn, xp.pi*1j) if log else None + residual = (xp.abs(work.CnDn - 1) if not log + else xp.real(_logaddexp(work.CnDn, pij, xp=xp))) + i = residual < work.eps + work.status[i] = eim._ECONVERGED + stop[i] = True + + # If function value is NaN, report failure. + i = (~xp.isfinite(work.fn) if not log + else ~(xp.isfinite(work.fn) | (work.fn == -xp.inf))) + work.status[i] = eim._EVALUEERR + stop[i] = True + + return stop + + def post_termination_check(work): + pass + + def customize_result(res, shape): + # Only needed pre-NEP 50 + res['f'] = xp.asarray(res['f'], dtype=dtype) + res['f'] = res['f'][()] if res['f'].ndim == 0 else res['f'] + return shape + + return eim._loop(work, callback, shape, maxiter, func, args, dtype, + pre_func_eval, post_func_eval, check_termination, + post_termination_check, customize_result, res_work_pairs, + xp=xp) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_continuous_distns.py b/venv/lib/python3.12/site-packages/scipy/stats/_continuous_distns.py new file mode 100644 index 0000000000000000000000000000000000000000..98029b3ec326e29bb33ce9832232b89ab31e2641 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_continuous_distns.py @@ -0,0 +1,12628 @@ +# +# Author: Travis Oliphant 2002-2011 with contributions from +# SciPy Developers 2004-2011 +# +import warnings +from collections.abc import Iterable +from functools import wraps, cached_property +import ctypes +import operator + +import numpy as np +from numpy.polynomial import Polynomial +from scipy.interpolate import BSpline +from scipy._lib.doccer import (extend_notes_in_docstring, + replace_notes_in_docstring, + inherit_docstring_from) +from scipy._lib._ccallback import LowLevelCallable +from scipy import optimize +from scipy import integrate +import scipy.special as sc + +import scipy.special._ufuncs as scu +from scipy._lib._util import _lazyselect +import scipy._external.array_api_extra as xpx +from scipy._lib._array_api import xp_promote + +from . import _stats +from ._tukeylambda_stats import (tukeylambda_variance as _tlvar, + tukeylambda_kurtosis as _tlkurt) +from ._distn_infrastructure import (_vectorize_rvs_over_shapes, + get_distribution_names, _kurtosis, _isintegral, + rv_continuous, _skew, _get_fixed_fit_value, _check_shape, _ShapeInfo) +from ._ksstats import kolmogn, kolmognp, kolmogni +from ._constants import (_XMIN, _LOGXMIN, _EULER, _ZETA3, _SQRT_PI, + _SQRT_2_OVER_PI, _LOG_PI, _LOG_SQRT_2_OVER_PI) +from ._censored_data import CensoredData +from scipy.optimize import root_scalar +from scipy.stats._warnings_errors import FitError +import scipy.stats as stats + +def _remove_optimizer_parameters(kwds): + """ + Remove the optimizer-related keyword arguments 'loc', 'scale' and + 'optimizer' from `kwds`. Then check that `kwds` is empty, and + raise `TypeError("Unknown arguments: %s." % kwds)` if it is not. + + This function is used in the fit method of distributions that override + the default method and do not use the default optimization code. + + `kwds` is modified in-place. + """ + kwds.pop('loc', None) + kwds.pop('scale', None) + kwds.pop('optimizer', None) + kwds.pop('method', None) + if kwds: + raise TypeError(f"Unknown arguments: {kwds}.") + + +def _call_super_mom(fun): + # If fit method is overridden only for MLE and doesn't specify what to do + # if method == 'mm' or with censored data, this decorator calls the generic + # implementation. + @wraps(fun) + def wrapper(self, data, *args, **kwds): + method = kwds.get('method', 'mle').lower() + censored = isinstance(data, CensoredData) + if method == 'mm' or (censored and data.num_censored() > 0): + return super(type(self), self).fit(data, *args, **kwds) + else: + if censored: + # data is an instance of CensoredData, but actually holds + # no censored values, so replace it with the array of + # uncensored values. + data = data._uncensored + return fun(self, data, *args, **kwds) + + return wrapper + + +def _get_left_bracket(fun, rbrack, lbrack=None): + # find left bracket for `root_scalar`. A guess for lbrack may be provided. + lbrack = lbrack or rbrack - 1 + diff = rbrack - lbrack + + # if there is no sign change in `fun` between the brackets, expand + # rbrack - lbrack until a sign change occurs + def interval_contains_root(lbrack, rbrack): + # return true if the signs disagree. + return np.sign(fun(lbrack)) != np.sign(fun(rbrack)) + + while not interval_contains_root(lbrack, rbrack): + diff *= 2 + lbrack = rbrack - diff + + msg = ("The solver could not find a bracket containing a " + "root to an MLE first order condition.") + if np.isinf(lbrack): + raise FitSolverError(msg) + + return lbrack + + +class ksone_gen(rv_continuous): + r"""Kolmogorov-Smirnov one-sided test statistic distribution. + + This is the distribution of the one-sided Kolmogorov-Smirnov (KS) + statistics :math:`D_n^+` and :math:`D_n^-` + for a finite sample size ``n >= 1`` (the shape parameter). + + %(before_notes)s + + See Also + -------- + kstwobign, kstwo, kstest + + Notes + ----- + :math:`D_n^+` and :math:`D_n^-` are given by + + .. math:: + + D_n^+ &= \text{sup}_x (F_n(x) - F(x)),\\ + D_n^- &= \text{sup}_x (F(x) - F_n(x)),\\ + + where :math:`F` is a continuous CDF and :math:`F_n` is an empirical CDF. + `ksone` describes the distribution under the null hypothesis of the KS test + that the empirical CDF corresponds to :math:`n` i.i.d. random variates + with CDF :math:`F`. + + %(after_notes)s + + References + ---------- + .. [1] Birnbaum, Z. W. and Tingey, F.H. "One-sided confidence contours + for probability distribution functions", The Annals of Mathematical + Statistics, 22(4), pp 592-596 (1951). + + Examples + -------- + >>> import numpy as np + >>> from scipy.stats import ksone + >>> import matplotlib.pyplot as plt + >>> fig, ax = plt.subplots(1, 1) + + Display the probability density function (``pdf``): + + >>> n = 1e+03 + >>> x = np.linspace(ksone.ppf(0.01, n), + ... ksone.ppf(0.99, n), 100) + >>> ax.plot(x, ksone.pdf(x, n), + ... 'r-', lw=5, alpha=0.6, label='ksone pdf') + + Alternatively, the distribution object can be called (as a function) + to fix the shape, location and scale parameters. This returns a "frozen" + RV object holding the given parameters fixed. + + Freeze the distribution and display the frozen ``pdf``: + + >>> rv = ksone(n) + >>> ax.plot(x, rv.pdf(x), 'k-', lw=2, label='frozen pdf') + >>> ax.legend(loc='best', frameon=False) + >>> plt.show() + + Check accuracy of ``cdf`` and ``ppf``: + + >>> vals = ksone.ppf([0.001, 0.5, 0.999], n) + >>> np.allclose([0.001, 0.5, 0.999], ksone.cdf(vals, n)) + True + + """ + def _argcheck(self, n): + return (n >= 1) & (n == np.round(n)) + + def _shape_info(self): + return [_ShapeInfo("n", True, (1, np.inf), (True, False))] + + def _pdf(self, x, n): + return -scu._smirnovp(n, x) + + def _cdf(self, x, n): + return scu._smirnovc(n, x) + + def _sf(self, x, n): + return sc.smirnov(n, x) + + def _ppf(self, q, n): + return scu._smirnovci(n, q) + + def _isf(self, q, n): + return sc.smirnovi(n, q) + + +ksone = ksone_gen(a=0.0, b=1.0, name='ksone') + + +class kstwo_gen(rv_continuous): + r"""Kolmogorov-Smirnov two-sided test statistic distribution. + + This is the distribution of the two-sided Kolmogorov-Smirnov (KS) + statistic :math:`D_n` for a finite sample size ``n >= 1`` + (the shape parameter). + + %(before_notes)s + + See Also + -------- + kstwobign, ksone, kstest + + Notes + ----- + :math:`D_n` is given by + + .. math:: + + D_n = \text{sup}_x |F_n(x) - F(x)| + + where :math:`F` is a (continuous) CDF and :math:`F_n` is an empirical CDF. + `kstwo` describes the distribution under the null hypothesis of the KS test + that the empirical CDF corresponds to :math:`n` i.i.d. random variates + with CDF :math:`F`. + + %(after_notes)s + + References + ---------- + .. [1] Simard, R., L'Ecuyer, P. "Computing the Two-Sided + Kolmogorov-Smirnov Distribution", Journal of Statistical Software, + Vol 39, 11, 1-18 (2011). + + Examples + -------- + >>> import numpy as np + >>> from scipy.stats import kstwo + >>> import matplotlib.pyplot as plt + >>> fig, ax = plt.subplots(1, 1) + + Display the probability density function (``pdf``): + + >>> n = 10 + >>> x = np.linspace(kstwo.ppf(0.01, n), + ... kstwo.ppf(0.99, n), 100) + >>> ax.plot(x, kstwo.pdf(x, n), + ... 'r-', lw=5, alpha=0.6, label='kstwo pdf') + + Alternatively, the distribution object can be called (as a function) + to fix the shape, location and scale parameters. This returns a "frozen" + RV object holding the given parameters fixed. + + Freeze the distribution and display the frozen ``pdf``: + + >>> rv = kstwo(n) + >>> ax.plot(x, rv.pdf(x), 'k-', lw=2, label='frozen pdf') + >>> ax.legend(loc='best', frameon=False) + >>> plt.show() + + Check accuracy of ``cdf`` and ``ppf``: + + >>> vals = kstwo.ppf([0.001, 0.5, 0.999], n) + >>> np.allclose([0.001, 0.5, 0.999], kstwo.cdf(vals, n)) + True + + """ + def _argcheck(self, n): + return (n >= 1) & (n == np.round(n)) + + def _shape_info(self): + return [_ShapeInfo("n", True, (1, np.inf), (True, False))] + + def _get_support(self, n): + return (0.5/(n if not isinstance(n, Iterable) else np.asanyarray(n)), + 1.0) + + def _pdf(self, x, n): + return kolmognp(n, x) + + def _cdf(self, x, n): + return kolmogn(n, x) + + def _sf(self, x, n): + return kolmogn(n, x, cdf=False) + + def _ppf(self, q, n): + return kolmogni(n, q, cdf=True) + + def _isf(self, q, n): + return kolmogni(n, q, cdf=False) + + +# Use the pdf, (not the ppf) to compute moments +kstwo = kstwo_gen(momtype=0, a=0.0, b=1.0, name='kstwo') + + +class kstwobign_gen(rv_continuous): + r"""Limiting distribution of scaled Kolmogorov-Smirnov two-sided test statistic. + + This is the asymptotic distribution of the two-sided Kolmogorov-Smirnov + statistic :math:`\sqrt{n} D_n` that measures the maximum absolute + distance of the theoretical (continuous) CDF from the empirical CDF. + (see `kstest`). + + %(before_notes)s + + See Also + -------- + ksone, kstwo, kstest + + Notes + ----- + :math:`\sqrt{n} D_n` is given by + + .. math:: + + D_n = \text{sup}_x |F_n(x) - F(x)| + + where :math:`F` is a continuous CDF and :math:`F_n` is an empirical CDF. + `kstwobign` describes the asymptotic distribution (i.e. the limit of + :math:`\sqrt{n} D_n`) under the null hypothesis of the KS test that the + empirical CDF corresponds to i.i.d. random variates with CDF :math:`F`. + + %(after_notes)s + + References + ---------- + .. [1] Feller, W. "On the Kolmogorov-Smirnov Limit Theorems for Empirical + Distributions", Ann. Math. Statist. Vol 19, 177-189 (1948). + + %(example)s + + """ + def _shape_info(self): + return [] + + def _pdf(self, x): + return -scu._kolmogp(x) + + def _cdf(self, x): + return scu._kolmogc(x) + + def _sf(self, x): + return sc.kolmogorov(x) + + def _ppf(self, q): + return scu._kolmogci(q) + + def _isf(self, q): + return sc.kolmogi(q) + + +kstwobign = kstwobign_gen(a=0.0, name='kstwobign') + + +## Normal distribution + +# loc = mu, scale = std +# Keep these implementations out of the class definition so they can be reused +# by other distributions. +_norm_pdf_C = np.sqrt(2*np.pi) +_norm_pdf_logC = np.log(_norm_pdf_C) + + +def _norm_pdf(x): + return np.exp(-x**2/2.0) / _norm_pdf_C + + +def _norm_logpdf(x): + return -x**2 / 2.0 - _norm_pdf_logC + + +def _norm_cdf(x): + return sc.ndtr(x) + + +def _norm_logcdf(x): + return sc.log_ndtr(x) + + +def _norm_ppf(q): + return sc.ndtri(q) + + +def _norm_sf(x): + return _norm_cdf(-x) + + +def _norm_logsf(x): + return _norm_logcdf(-x) + + +def _norm_isf(q): + return -_norm_ppf(q) + + +class norm_gen(rv_continuous): + r"""A normal continuous random variable. + + The location (``loc``) keyword specifies the mean. + The scale (``scale``) keyword specifies the standard deviation. + + %(before_notes)s + + Notes + ----- + The probability density function for `norm` is: + + .. math:: + + f(x) = \frac{\exp(-x^2/2)}{\sqrt{2\pi}} + + for a real number :math:`x`. + + %(after_notes)s + + %(example)s + + """ + def _shape_info(self): + return [] + + def _rvs(self, size=None, random_state=None): + return random_state.standard_normal(size) + + def _pdf(self, x): + # norm.pdf(x) = exp(-x**2/2)/sqrt(2*pi) + return _norm_pdf(x) + + def _logpdf(self, x): + return _norm_logpdf(x) + + def _cdf(self, x): + return _norm_cdf(x) + + def _logcdf(self, x): + return _norm_logcdf(x) + + def _sf(self, x): + return _norm_sf(x) + + def _logsf(self, x): + return _norm_logsf(x) + + def _ppf(self, q): + return _norm_ppf(q) + + def _isf(self, q): + return _norm_isf(q) + + def _stats(self): + return 0.0, 1.0, 0.0, 0.0 + + def _entropy(self): + return 0.5*(np.log(2*np.pi)+1) + + @_call_super_mom + @replace_notes_in_docstring(rv_continuous, notes="""\ + For the normal distribution, method of moments and maximum likelihood + estimation give identical fits, and explicit formulas for the estimates + are available. + This function uses these explicit formulas for the maximum likelihood + estimation of the normal distribution parameters, so the + `optimizer` and `method` arguments are ignored.\n\n""") + def fit(self, data, **kwds): + floc = kwds.pop('floc', None) + fscale = kwds.pop('fscale', None) + + _remove_optimizer_parameters(kwds) + + if floc is not None and fscale is not None: + # This check is for consistency with `rv_continuous.fit`. + # Without this check, this function would just return the + # parameters that were given. + raise ValueError("All parameters fixed. There is nothing to " + "optimize.") + + data = np.asarray(data) + + if not np.isfinite(data).all(): + raise ValueError("The data contains non-finite values.") + + if floc is None: + loc = data.mean() + else: + loc = floc + + if fscale is None: + scale = np.sqrt(((data - loc)**2).mean()) + else: + scale = fscale + + return loc, scale + + def _munp(self, n): + """ + @returns Moments of standard normal distribution for integer n >= 0 + + See eq. 16 of https://arxiv.org/abs/1209.4340v2 + """ + if n == 0: + return 1. + if n % 2 == 0: + return sc.factorial2(int(n) - 1) + else: + return 0. + + +norm = norm_gen(name='norm') + + +class alpha_gen(rv_continuous): + r"""An alpha continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `alpha` ([1]_, [2]_) is: + + .. math:: + + f(x, a) = \frac{1}{x^2 \Phi(a) \sqrt{2\pi}} * + \exp(-\frac{1}{2} (a-1/x)^2) + + where :math:`\Phi` is the normal CDF, :math:`x > 0`, and :math:`a > 0`. + + `alpha` takes ``a`` as a shape parameter. + + %(after_notes)s + + References + ---------- + .. [1] Johnson, Kotz, and Balakrishnan, "Continuous Univariate + Distributions, Volume 1", Second Edition, John Wiley and Sons, + p. 173 (1994). + .. [2] Anthony A. Salvia, "Reliability applications of the Alpha + Distribution", IEEE Transactions on Reliability, Vol. R-34, + No. 3, pp. 251-252 (1985). + + %(example)s + + """ + _support_mask = rv_continuous._open_support_mask + + def _shape_info(self): + return [_ShapeInfo("a", False, (0, np.inf), (False, False))] + + def _pdf(self, x, a): + # alpha.pdf(x, a) = 1/(x**2*Phi(a)*sqrt(2*pi)) * exp(-1/2 * (a-1/x)**2) + return 1.0/(x**2)/_norm_cdf(a)*_norm_pdf(a-1.0/x) + + def _logpdf(self, x, a): + return -2*np.log(x) + _norm_logpdf(a-1.0/x) - np.log(_norm_cdf(a)) + + def _cdf(self, x, a): + return _norm_cdf(a-1.0/x) / _norm_cdf(a) + + def _ppf(self, q, a): + return 1.0/np.asarray(a - _norm_ppf(q*_norm_cdf(a))) + + def _stats(self, a): + return [np.inf]*2 + [np.nan]*2 + + +alpha = alpha_gen(a=0.0, name='alpha') + + +class anglit_gen(rv_continuous): + r"""An anglit continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `anglit` is: + + .. math:: + + f(x) = \sin(2x + \pi/2) = \cos(2x) + + for :math:`-\pi/4 \le x \le \pi/4`. + + %(after_notes)s + + %(example)s + + """ + def _shape_info(self): + return [] + + def _pdf(self, x): + # anglit.pdf(x) = sin(2*x + \pi/2) = cos(2*x) + return np.cos(2*x) + + def _cdf(self, x): + return np.sin(x+np.pi/4)**2.0 + + def _sf(self, x): + return np.cos(x + np.pi / 4) ** 2.0 + + def _ppf(self, q): + return np.arcsin(np.sqrt(q))-np.pi/4 + + def _stats(self): + return 0.0, np.pi*np.pi/16-0.5, 0.0, -2*(np.pi**4 - 96)/(np.pi*np.pi-8)**2 + + def _entropy(self): + return 1-np.log(2) + + +anglit = anglit_gen(a=-np.pi/4, b=np.pi/4, name='anglit') + + +class arcsine_gen(rv_continuous): + r"""An arcsine continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `arcsine` is: + + .. math:: + + f(x) = \frac{1}{\pi \sqrt{x (1-x)}} + + for :math:`0 < x < 1`. + + %(after_notes)s + + %(example)s + + """ + def _shape_info(self): + return [] + + def _pdf(self, x): + # arcsine.pdf(x) = 1/(pi*sqrt(x*(1-x))) + with np.errstate(divide='ignore'): + return 1.0/np.pi/np.sqrt(x*(1-x)) + + def _cdf(self, x): + return 2.0/np.pi*np.arcsin(np.sqrt(x)) + + def _ppf(self, q): + return np.sin(np.pi/2.0*q)**2.0 + + def _stats(self): + mu = 0.5 + mu2 = 1.0/8 + g1 = 0 + g2 = -3.0/2.0 + return mu, mu2, g1, g2 + + def _entropy(self): + return -0.24156447527049044468 + + +arcsine = arcsine_gen(a=0.0, b=1.0, name='arcsine') + + +class FitDataError(ValueError): + """Raised when input data is inconsistent with fixed parameters.""" + # This exception is raised by, for example, beta_gen.fit when both floc + # and fscale are fixed and there are values in the data not in the open + # interval (floc, floc+fscale). + def __init__(self, distr, lower, upper): + self.args = ( + "Invalid values in `data`. Maximum likelihood " + f"estimation with {distr!r} requires that {lower!r} < " + f"(x - loc)/scale < {upper!r} for each x in `data`.", + ) + + +class FitSolverError(FitError): + """ + Raised when a solver fails to converge while fitting a distribution. + """ + # This exception is raised by, for example, beta_gen.fit when + # optimize.fsolve returns with ier != 1. + def __init__(self, mesg): + emsg = "Solver for the MLE equations failed to converge: " + emsg += mesg.replace('\n', '') + self.args = (emsg,) + + +def _beta_mle_a(a, b, n, s1): + # The zeros of this function give the MLE for `a`, with + # `b`, `n` and `s1` given. `s1` is the sum of the logs of + # the data. `n` is the number of data points. + psiab = sc.psi(a + b) + func = s1 - n * (-psiab + sc.psi(a)) + return func + + +def _beta_mle_ab(theta, n, s1, s2): + # Zeros of this function are critical points of + # the maximum likelihood function. Solving this system + # for theta (which contains a and b) gives the MLE for a and b + # given `n`, `s1` and `s2`. `s1` is the sum of the logs of the data, + # and `s2` is the sum of the logs of 1 - data. `n` is the number + # of data points. + a, b = theta + psiab = sc.psi(a + b) + func = [s1 - n * (-psiab + sc.psi(a)), + s2 - n * (-psiab + sc.psi(b))] + return func + + +class beta_gen(rv_continuous): + r"""A beta continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `beta` is: + + .. math:: + + f(x, a, b) = \frac{\Gamma(a+b) x^{a-1} (1-x)^{b-1}} + {\Gamma(a) \Gamma(b)} + + for :math:`0 <= x <= 1`, :math:`a > 0`, :math:`b > 0`, where + :math:`\Gamma` is the gamma function (`scipy.special.gamma`). + + `beta` takes :math:`a` and :math:`b` as shape parameters. + + This distribution uses routines from the Boost Math C++ library for + the computation of the ``pdf``, ``cdf``, ``ppf``, ``sf`` and ``isf`` + methods. [1]_ + + Maximum likelihood estimates of parameters are only available when the location and + scale are fixed. When either of these parameters is free, ``beta.fit`` resorts to + numerical optimization, but this problem is unbounded: the location and scale may be + chosen to make the minimum and maximum elements of the data coincide with the + endpoints of the support, and the shape parameters may be chosen to make the PDF at + these points infinite. For best results, pass ``floc`` and ``fscale`` keyword + arguments to fix the location and scale, or use `scipy.stats.fit` with + ``method='mse'``. + + %(after_notes)s + + References + ---------- + .. [1] The Boost Developers. "Boost C++ Libraries". https://www.boost.org/. + + %(example)s + + """ + def _shape_info(self): + ia = _ShapeInfo("a", False, (0, np.inf), (False, False)) + ib = _ShapeInfo("b", False, (0, np.inf), (False, False)) + return [ia, ib] + + def _rvs(self, a, b, size=None, random_state=None): + return random_state.beta(a, b, size) + + def _pdf(self, x, a, b): + # gamma(a+b) * x**(a-1) * (1-x)**(b-1) + # beta.pdf(x, a, b) = ------------------------------------ + # gamma(a)*gamma(b) + with np.errstate(over='ignore'): + return scu._beta_pdf(x, a, b) + + def _logpdf(self, x, a, b): + lPx = sc.xlog1py(b - 1.0, -x) + sc.xlogy(a - 1.0, x) + lPx -= sc.betaln(a, b) + return lPx + + def _cdf(self, x, a, b): + return sc.betainc(a, b, x) + + def _sf(self, x, a, b): + return sc.betaincc(a, b, x) + + def _isf(self, x, a, b): + return sc.betainccinv(a, b, x) + + def _ppf(self, q, a, b): + return scu._beta_ppf(q, a, b) + + def _stats(self, a, b): + a_plus_b = a + b + _beta_mean = a/a_plus_b + _beta_variance = a*b / (a_plus_b**2 * (a_plus_b + 1)) + _beta_skewness = ((2 * (b - a) * np.sqrt(a_plus_b + 1)) / + ((a_plus_b + 2) * np.sqrt(a * b))) + _beta_kurtosis_excess_n = 6 * ((a - b)**2 * (a_plus_b + 1) - + a * b * (a_plus_b + 2)) + _beta_kurtosis_excess_d = a * b * (a_plus_b + 2) * (a_plus_b + 3) + _beta_kurtosis_excess = _beta_kurtosis_excess_n / _beta_kurtosis_excess_d + return ( + _beta_mean, + _beta_variance, + _beta_skewness, + _beta_kurtosis_excess) + + def _fitstart(self, data): + if isinstance(data, CensoredData): + data = data._uncensor() + + g1 = _skew(data) + g2 = _kurtosis(data) + + def func(x): + a, b = x + sk = 2*(b-a)*np.sqrt(a + b + 1) / (a + b + 2) / np.sqrt(a*b) + ku = a**3 - a**2*(2*b-1) + b**2*(b+1) - 2*a*b*(b+2) + ku /= a*b*(a+b+2)*(a+b+3) + ku *= 6 + return [sk-g1, ku-g2] + a, b = optimize.fsolve(func, (1.0, 1.0)) + return super()._fitstart(data, args=(a, b)) + + @_call_super_mom + @extend_notes_in_docstring(rv_continuous, notes="""\ + In the special case where `method="MLE"` and + both `floc` and `fscale` are given, a + `ValueError` is raised if any value `x` in `data` does not satisfy + `floc < x < floc + fscale`.\n\n""") + def fit(self, data, *args, **kwds): + # Override rv_continuous.fit, so we can more efficiently handle the + # case where floc and fscale are given. + + floc = kwds.get('floc', None) + fscale = kwds.get('fscale', None) + + if floc is None or fscale is None: + # do general fit + return super().fit(data, *args, **kwds) + + # We already got these from kwds, so just pop them. + kwds.pop('floc', None) + kwds.pop('fscale', None) + + f0 = _get_fixed_fit_value(kwds, ['f0', 'fa', 'fix_a']) + f1 = _get_fixed_fit_value(kwds, ['f1', 'fb', 'fix_b']) + + _remove_optimizer_parameters(kwds) + + if f0 is not None and f1 is not None: + # This check is for consistency with `rv_continuous.fit`. + raise ValueError("All parameters fixed. There is nothing to " + "optimize.") + + # Special case: loc and scale are constrained, so we are fitting + # just the shape parameters. This can be done much more efficiently + # than the method used in `rv_continuous.fit`. (See the subsection + # "Two unknown parameters" in the section "Maximum likelihood" of + # the Wikipedia article on the Beta distribution for the formulas.) + + if not np.isfinite(data).all(): + raise ValueError("The data contains non-finite values.") + + # Normalize the data to the interval [0, 1]. + data = (np.ravel(data) - floc) / fscale + if np.any(data <= 0) or np.any(data >= 1): + raise FitDataError("beta", lower=floc, upper=floc + fscale) + + xbar = data.mean() + + if f0 is not None or f1 is not None: + # One of the shape parameters is fixed. + + if f0 is not None: + # The shape parameter a is fixed, so swap the parameters + # and flip the data. We always solve for `a`. The result + # will be swapped back before returning. + b = f0 + data = 1 - data + xbar = 1 - xbar + else: + b = f1 + + # Initial guess for a. Use the formula for the mean of the beta + # distribution, E[x] = a / (a + b), to generate a reasonable + # starting point based on the mean of the data and the given + # value of b. + a = b * xbar / (1 - xbar) + + # Compute the MLE for `a` by solving _beta_mle_a. + theta, info, ier, mesg = optimize.fsolve( + _beta_mle_a, a, + args=(b, len(data), np.log(data).sum()), + full_output=True + ) + if ier != 1: + raise FitSolverError(mesg=mesg) + a = theta[0] + + if f0 is not None: + # The shape parameter a was fixed, so swap back the + # parameters. + a, b = b, a + + else: + # Neither of the shape parameters is fixed. + + # s1 and s2 are used in the extra arguments passed to _beta_mle_ab + # by optimize.fsolve. + s1 = np.log(data).sum() + s2 = sc.log1p(-data).sum() + + # Use the "method of moments" to estimate the initial + # guess for a and b. + fac = xbar * (1 - xbar) / data.var(ddof=0) - 1 + a = xbar * fac + b = (1 - xbar) * fac + + # Compute the MLE for a and b by solving _beta_mle_ab. + theta, info, ier, mesg = optimize.fsolve( + _beta_mle_ab, [a, b], + args=(len(data), s1, s2), + full_output=True + ) + if ier != 1: + raise FitSolverError(mesg=mesg) + a, b = theta + + return a, b, floc, fscale + + def _entropy(self, a, b): + def regular(a, b): + return (sc.betaln(a, b) - (a - 1) * sc.psi(a) - + (b - 1) * sc.psi(b) + (a + b - 2) * sc.psi(a + b)) + + def asymptotic_ab_large(a, b): + sum_ab = a + b + log_term = 0.5 * ( + np.log(2*np.pi) + np.log(a) + np.log(b) - 3*np.log(sum_ab) + 1 + ) + t1 = 110/sum_ab + 20*sum_ab**-2.0 + sum_ab**-3.0 - 2*sum_ab**-4.0 + t2 = -50/a - 10*a**-2.0 - a**-3.0 + a**-4.0 + t3 = -50/b - 10*b**-2.0 - b**-3.0 + b**-4.0 + return log_term + (t1 + t2 + t3) / 120 + + def asymptotic_b_large(a, b): + sum_ab = a + b + t1 = sc.gammaln(a) - (a - 1) * sc.psi(a) + t2 = ( + - 1/(2*b) + 1/(12*b) - b**-2.0/12 - b**-3.0/120 + b**-4.0/120 + + b**-5.0/252 - b**-6.0/252 + 1/sum_ab - 1/(12*sum_ab) + + sum_ab**-2.0/6 + sum_ab**-3.0/120 - sum_ab**-4.0/60 + - sum_ab**-5.0/252 + sum_ab**-6.0/126 + ) + log_term = sum_ab*np.log1p(a/b) + np.log(b) - 2*np.log(sum_ab) + return t1 + t2 + log_term + + def asymptotic_a_large(a, b): + return asymptotic_b_large(b, a) + + def threshold_large(v): + j = np.floor(np.log10(v)) + d = np.floor(v / 10 ** j) + 2 + return xpx.apply_where(v != 1.0, (d, j), lambda d_, j_: d_ * 10**(7 + j_), + fill_value=1000) + + threshold_a = threshold_large(a) + threshold_b = threshold_large(b) + return _lazyselect([(a >= 4.96e6) & (b >= 4.96e6), + (a <= 4.9e6) & (b - a >= 1e6) & (b >= threshold_a), + (b <= 4.9e6) & (a - b >= 1e6) & (a >= threshold_b), + (a < 4.9e6) & (b < 4.9e6) + ], + [asymptotic_ab_large, asymptotic_b_large, + asymptotic_a_large, regular], + [a, b] + ) + + +beta = beta_gen(a=0.0, b=1.0, name='beta') + + +class betaprime_gen(rv_continuous): + r"""A beta prime continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `betaprime` is: + + .. math:: + + f(x, a, b) = \frac{x^{a-1} (1+x)^{-a-b}}{\beta(a, b)} + + for :math:`x >= 0`, :math:`a > 0`, :math:`b > 0`, where + :math:`\beta(a, b)` is the beta function (see `scipy.special.beta`). + + `betaprime` takes ``a`` and ``b`` as shape parameters. + + The distribution is related to the `beta` distribution as follows: + If :math:`X` follows a beta distribution with parameters :math:`a, b`, + then :math:`Y = X/(1-X)` has a beta prime distribution with + parameters :math:`a, b` ([1]_). + + The beta prime distribution is a reparametrized version of the + F distribution. The beta prime distribution with shape parameters + ``a`` and ``b`` and ``scale = s`` is equivalent to the F distribution + with parameters ``d1 = 2*a``, ``d2 = 2*b`` and ``scale = (a/b)*s``. + For example, + + >>> from scipy.stats import betaprime, f + >>> x = [1, 2, 5, 10] + >>> a = 12 + >>> b = 5 + >>> betaprime.pdf(x, a, b, scale=2) + array([0.00541179, 0.08331299, 0.14669185, 0.03150079]) + >>> f.pdf(x, 2*a, 2*b, scale=(a/b)*2) + array([0.00541179, 0.08331299, 0.14669185, 0.03150079]) + + %(after_notes)s + + References + ---------- + .. [1] Beta prime distribution, Wikipedia, + https://en.wikipedia.org/wiki/Beta_prime_distribution + + %(example)s + + """ + _support_mask = rv_continuous._open_support_mask + + def _shape_info(self): + ia = _ShapeInfo("a", False, (0, np.inf), (False, False)) + ib = _ShapeInfo("b", False, (0, np.inf), (False, False)) + return [ia, ib] + + def _rvs(self, a, b, size=None, random_state=None): + u1 = gamma.rvs(a, size=size, random_state=random_state) + u2 = gamma.rvs(b, size=size, random_state=random_state) + return u1 / u2 + + def _pdf(self, x, a, b): + # betaprime.pdf(x, a, b) = x**(a-1) * (1+x)**(-a-b) / beta(a, b) + return np.exp(self._logpdf(x, a, b)) + + def _logpdf(self, x, a, b): + return sc.xlogy(a - 1.0, x) - sc.xlog1py(a + b, x) - sc.betaln(a, b) + + def _cdf(self, x, a, b): + # note: f2 is the direct way to compute the cdf if the relationship + # to the beta distribution is used. + # however, for very large x, x/(1+x) == 1. since the distribution + # has very fat tails if b is small, this can cause inaccurate results + # use the following relationship of the incomplete beta function: + # betainc(x, a, b) = 1 - betainc(1-x, b, a) + # see gh-17631 + return xpx.apply_where( + x > 1, (x, a, b), + lambda x_, a_, b_: beta._sf(1 / (1 + x_), b_, a_), + lambda x_, a_, b_: beta._cdf(x_ / (1 + x_), a_, b_)) + + def _sf(self, x, a, b): + return xpx.apply_where( + x > 1, (x, a, b), + lambda x_, a_, b_: beta._cdf(1 / (1 + x_), b_, a_), + lambda x_, a_, b_: beta._sf(x_ / (1 + x_), a_, b_)) + + def _ppf(self, p, a, b): + p, a, b = np.broadcast_arrays(p, a, b) + # By default, compute the ppf by solving the following: + # p = beta._cdf(x/(1+x), a, b). This implies x = r/(1-r) with + # r = beta._ppf(p, a, b). This can cause numerical issues if r is + # very close to 1. In that case, invert the alternative expression of + # the cdf: p = beta._sf(1/(1+x), b, a). + r = stats.beta._ppf(p, a, b) + with np.errstate(divide='ignore'): + out = r / (1 - r) + rnear1 = r > 0.9999 + if np.isscalar(r): + if rnear1: + out = 1/stats.beta._isf(p, b, a) - 1 + else: + out[rnear1] = 1/stats.beta._isf(p[rnear1], b[rnear1], a[rnear1]) - 1 + return out + + def _munp(self, n, a, b): + return xpx.apply_where( + b > n, (a, b), + lambda a, b: np.prod([(a+i-1)/(b-i) for i in range(1, int(n)+1)], axis=0), + fill_value=np.inf) + + +betaprime = betaprime_gen(a=0.0, name='betaprime') + + +class bradford_gen(rv_continuous): + r"""A Bradford continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `bradford` is: + + .. math:: + + f(x, c) = \frac{c}{\log(1+c) (1+cx)} + + for :math:`0 <= x <= 1` and :math:`c > 0`. + + `bradford` takes ``c`` as a shape parameter for :math:`c`. + + %(after_notes)s + + %(example)s + + """ + def _shape_info(self): + return [_ShapeInfo("c", False, (0, np.inf), (False, False))] + + def _pdf(self, x, c): + # bradford.pdf(x, c) = c / (k * (1+c*x)) + return c / (c*x + 1.0) / sc.log1p(c) + + def _cdf(self, x, c): + return sc.log1p(c*x) / sc.log1p(c) + + def _ppf(self, q, c): + return sc.expm1(q * sc.log1p(c)) / c + + def _stats(self, c, moments='mv'): + k = np.log(1.0+c) + mu = (c-k)/(c*k) + mu2 = ((c+2.0)*k-2.0*c)/(2*c*k*k) + g1 = None + g2 = None + if 's' in moments: + g1 = np.sqrt(2)*(12*c*c-9*c*k*(c+2)+2*k*k*(c*(c+3)+3)) + g1 /= np.sqrt(c*(c*(k-2)+2*k))*(3*c*(k-2)+6*k) + if 'k' in moments: + g2 = (c**3*(k-3)*(k*(3*k-16)+24)+12*k*c*c*(k-4)*(k-3) + + 6*c*k*k*(3*k-14) + 12*k**3) + g2 /= 3*c*(c*(k-2)+2*k)**2 + return mu, mu2, g1, g2 + + def _entropy(self, c): + k = np.log(1+c) + return k/2.0 - np.log(c/k) + + +bradford = bradford_gen(a=0.0, b=1.0, name='bradford') + + +class burr_gen(rv_continuous): + r"""A Burr (Type III) continuous random variable. + + %(before_notes)s + + See Also + -------- + fisk : a special case of either `burr` or `burr12` with ``d=1`` + burr12 : Burr Type XII distribution + mielke : Mielke Beta-Kappa / Dagum distribution + + Notes + ----- + The probability density function for `burr` is: + + .. math:: + + f(x; c, d) = c d \frac{x^{-c - 1}} + {{(1 + x^{-c})}^{d + 1}} + + for :math:`x >= 0` and :math:`c, d > 0`. + + `burr` takes ``c`` and ``d`` as shape parameters for :math:`c` and + :math:`d`. + + This is the PDF corresponding to the third CDF given in Burr's list; + specifically, it is equation (11) in Burr's paper [1]_. The distribution + is also commonly referred to as the Dagum distribution [2]_. If the + parameter :math:`c < 1` then the mean of the distribution does not + exist and if :math:`c < 2` the variance does not exist [2]_. + The PDF is finite at the left endpoint :math:`x = 0` if :math:`c * d >= 1`. + + %(after_notes)s + + References + ---------- + .. [1] Burr, I. W. "Cumulative frequency functions", Annals of + Mathematical Statistics, 13(2), pp 215-232 (1942). + .. [2] https://en.wikipedia.org/wiki/Dagum_distribution + .. [3] Kleiber, Christian. "A guide to the Dagum distributions." + Modeling Income Distributions and Lorenz Curves pp 97-117 (2008). + + %(example)s + + """ + # Do not set _support_mask to rv_continuous._open_support_mask + # Whether the left-hand endpoint is suitable for pdf evaluation is dependent + # on the values of c and d: if c*d >= 1, the pdf is finite, otherwise infinite. + + def _shape_info(self): + ic = _ShapeInfo("c", False, (0, np.inf), (False, False)) + id = _ShapeInfo("d", False, (0, np.inf), (False, False)) + return [ic, id] + + def _pdf(self, x, c, d): + # burr.pdf(x, c, d) = c * d * x**(-c-1) * (1+x**(-c))**(-d-1) + output = xpx.apply_where( + x == 0, (x, c, d), + lambda x_, c_, d_: c_ * d_ * (x_**(c_*d_-1)) / (1 + x_**c_), + lambda x_, c_, d_: (c_ * d_ * (x_ ** (-c_ - 1.0)) / + ((1 + x_ ** (-c_)) ** (d_ + 1.0)))) + return output[()] if output.ndim == 0 else output + + def _logpdf(self, x, c, d): + output = xpx.apply_where( + x == 0, (x, c, d), + lambda x_, c_, d_: (np.log(c_) + np.log(d_) + sc.xlogy(c_*d_ - 1, x_) + - (d_+1) * sc.log1p(x_**(c_))), + lambda x_, c_, d_: (np.log(c_) + np.log(d_) + + sc.xlogy(-c_ - 1, x_) + - sc.xlog1py(d_+1, x_**(-c_)))) + return output[()] if output.ndim == 0 else output + + def _cdf(self, x, c, d): + return (1 + x**(-c))**(-d) + + def _logcdf(self, x, c, d): + return sc.log1p(x**(-c)) * (-d) + + def _sf(self, x, c, d): + return np.exp(self._logsf(x, c, d)) + + def _logsf(self, x, c, d): + return np.log1p(- (1 + x**(-c))**(-d)) + + def _ppf(self, q, c, d): + return (q**(-1.0/d) - 1)**(-1.0/c) + + def _isf(self, q, c, d): + _q = sc.xlog1py(-1.0 / d, -q) + return sc.expm1(_q) ** (-1.0 / c) + + def _stats(self, c, d): + nc = np.arange(1, 5).reshape(4,1) / c + # ek is the kth raw moment, e1 is the mean e2-e1**2 variance etc. + e1, e2, e3, e4 = sc.beta(d + nc, 1. - nc) * d + mu = np.where(c > 1.0, e1, np.nan) + mu2_if_c = e2 - mu**2 + mu2 = np.where(c > 2.0, mu2_if_c, np.nan) + g1 = xpx.apply_where( + c > 3.0, (e1, e2, e3, mu2_if_c), + lambda e1, e2, e3, mu2_if_c: ((e3 - 3*e2*e1 + 2*e1**3) + / np.sqrt((mu2_if_c)**3)), + fill_value=np.nan) + g2 = xpx.apply_where( + c > 4.0, (e1, e2, e3, e4, mu2_if_c), + lambda e1, e2, e3, e4, mu2_if_c: ( + ((e4 - 4*e3*e1 + 6*e2*e1**2 - 3*e1**4) / mu2_if_c**2) - 3), + fill_value=np.nan) + if np.ndim(c) == 0: + return mu.item(), mu2.item(), g1.item(), g2.item() + return mu, mu2, g1, g2 + + def _munp(self, n, c, d): + def __munp(n, c, d): + nc = 1. * n / c + return d * sc.beta(1.0 - nc, d + nc) + n, c, d = np.asarray(n), np.asarray(c), np.asarray(d) + return xpx.apply_where((c > n) & (n == n) & (d == d), + (n, c, d), __munp, fill_value=np.nan) + + +burr = burr_gen(a=0.0, name='burr') + + +class burr12_gen(rv_continuous): + r"""A Burr (Type XII) continuous random variable. + + %(before_notes)s + + See Also + -------- + fisk : a special case of either `burr` or `burr12` with ``d=1`` + burr : Burr Type III distribution + + Notes + ----- + The probability density function for `burr12` is: + + .. math:: + + f(x; c, d) = c d \frac{x^{c-1}} + {(1 + x^c)^{d + 1}} + + for :math:`x >= 0` and :math:`c, d > 0`. + + `burr12` takes ``c`` and ``d`` as shape parameters for :math:`c` + and :math:`d`. + + This is the PDF corresponding to the twelfth CDF given in Burr's list; + specifically, it is equation (20) in Burr's paper [1]_. + + %(after_notes)s + + The Burr type 12 distribution is also sometimes referred to as + the Singh-Maddala distribution from NIST [2]_. + + References + ---------- + .. [1] Burr, I. W. "Cumulative frequency functions", Annals of + Mathematical Statistics, 13(2), pp 215-232 (1942). + + .. [2] https://www.itl.nist.gov/div898/software/dataplot/refman2/auxillar/b12pdf.htm + + .. [3] "Burr distribution", + https://en.wikipedia.org/wiki/Burr_distribution + + %(example)s + + """ + def _shape_info(self): + ic = _ShapeInfo("c", False, (0, np.inf), (False, False)) + id = _ShapeInfo("d", False, (0, np.inf), (False, False)) + return [ic, id] + + def _pdf(self, x, c, d): + # burr12.pdf(x, c, d) = c * d * x**(c-1) * (1+x**(c))**(-d-1) + return np.exp(self._logpdf(x, c, d)) + + def _logpdf(self, x, c, d): + return np.log(c) + np.log(d) + sc.xlogy(c - 1, x) + sc.xlog1py(-d-1, x**c) + + def _cdf(self, x, c, d): + return -sc.expm1(self._logsf(x, c, d)) + + def _logcdf(self, x, c, d): + return sc.log1p(-(1 + x**c)**(-d)) + + def _sf(self, x, c, d): + return np.exp(self._logsf(x, c, d)) + + def _logsf(self, x, c, d): + return sc.xlog1py(-d, x**c) + + def _ppf(self, q, c, d): + # The following is an implementation of + # ((1 - q)**(-1.0/d) - 1)**(1.0/c) + # that does a better job handling small values of q. + return sc.expm1(-1/d * sc.log1p(-q))**(1/c) + + def _isf(self, p, c, d): + return sc.expm1(-1/d * np.log(p))**(1/c) + + def _munp(self, n, c, d): + def moment_if_exists(n, c, d): + nc = 1. * n / c + return d * sc.beta(1.0 + nc, d - nc) + + return xpx.apply_where(c * d > n, (n, c, d), moment_if_exists, + fill_value=np.nan) + + +burr12 = burr12_gen(a=0.0, name='burr12') + + +class fisk_gen(burr_gen): + r"""A Fisk continuous random variable. + + The Fisk distribution is also known as the log-logistic distribution. + + %(before_notes)s + + See Also + -------- + burr + + Notes + ----- + The probability density function for `fisk` is: + + .. math:: + + f(x, c) = \frac{c x^{c-1}} + {(1 + x^c)^2} + + for :math:`x >= 0` and :math:`c > 0`. + + Please note that the above expression can be transformed into the following + one, which is also commonly used: + + .. math:: + + f(x, c) = \frac{c x^{-c-1}} + {(1 + x^{-c})^2} + + `fisk` takes ``c`` as a shape parameter for :math:`c`. + + `fisk` is a special case of `burr` or `burr12` with ``d=1``. + + Suppose ``X`` is a logistic random variable with location ``l`` + and scale ``s``. Then ``Y = exp(X)`` is a Fisk (log-logistic) + random variable with ``scale = exp(l)`` and shape ``c = 1/s``. + + %(after_notes)s + + %(example)s + + """ + def _shape_info(self): + return [_ShapeInfo("c", False, (0, np.inf), (False, False))] + + def _pdf(self, x, c): + # fisk.pdf(x, c) = c * x**(-c-1) * (1 + x**(-c))**(-2) + return burr._pdf(x, c, 1.0) + + def _cdf(self, x, c): + return burr._cdf(x, c, 1.0) + + def _sf(self, x, c): + return burr._sf(x, c, 1.0) + + def _logpdf(self, x, c): + # fisk.pdf(x, c) = c * x**(-c-1) * (1 + x**(-c))**(-2) + return burr._logpdf(x, c, 1.0) + + def _logcdf(self, x, c): + return burr._logcdf(x, c, 1.0) + + def _logsf(self, x, c): + return burr._logsf(x, c, 1.0) + + def _ppf(self, x, c): + return burr._ppf(x, c, 1.0) + + def _isf(self, q, c): + return burr._isf(q, c, 1.0) + + def _munp(self, n, c): + return burr._munp(n, c, 1.0) + + def _stats(self, c): + return burr._stats(c, 1.0) + + def _entropy(self, c): + return 2 - np.log(c) + + +fisk = fisk_gen(a=0.0, name='fisk') + + +class cauchy_gen(rv_continuous): + r"""A Cauchy continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `cauchy` is + + .. math:: + + f(x) = \frac{1}{\pi (1 + x^2)} + + for a real number :math:`x`. + + This distribution uses routines from the Boost Math C++ library for + the computation of the ``ppf`` and ``isf`` methods. [1]_ + + %(after_notes)s + + References + ---------- + .. [1] The Boost Developers. "Boost C++ Libraries". https://www.boost.org/. + + %(example)s + + """ + def _shape_info(self): + return [] + + def _pdf(self, x): + # cauchy.pdf(x) = 1 / (pi * (1 + x**2)) + with np.errstate(over='ignore'): + return 1.0/np.pi/(1.0+x*x) + + def _logpdf(self, x): + # The formulas + # log(1/(pi*(1 + x**2))) = -log(pi) - log(1 + x**2) + # = -log(pi) - log(x**2*(1 + 1/x**2)) + # = -log(pi) - (2log(|x|) + log1p(1/x**2)) + # are used here. + absx = np.abs(x) + # In the following apply_where, `f1` provides better precision than `f2` + # for small and moderate x, while `f2` avoids the overflow that can + # occur with absx**2. + return xpx.apply_where( + absx < 1, absx, + lambda absx: -_LOG_PI - np.log1p(absx**2), + lambda absx: (-_LOG_PI - (2*np.log(absx) + np.log1p((1/absx)**2)))) + + def _cdf(self, x): + return np.arctan2(1, -x)/np.pi + + def _ppf(self, q): + return scu._cauchy_ppf(q, 0, 1) + + def _sf(self, x): + return np.arctan2(1, x)/np.pi + + def _isf(self, q): + return scu._cauchy_isf(q, 0, 1) + + def _stats(self): + return np.nan, np.nan, np.nan, np.nan + + def _entropy(self): + return np.log(4*np.pi) + + def _fitstart(self, data, args=None): + # Initialize ML guesses using quartiles instead of moments. + if isinstance(data, CensoredData): + data = data._uncensor() + p25, p50, p75 = np.percentile(data, [25, 50, 75]) + return p50, (p75 - p25)/2 + + +cauchy = cauchy_gen(name='cauchy') + + +class chi_gen(rv_continuous): + r"""A chi continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `chi` is: + + .. math:: + + f(x, k) = \frac{1}{2^{k/2-1} \Gamma \left( k/2 \right)} + x^{k-1} \exp \left( -x^2/2 \right) + + for :math:`x >= 0` and :math:`k > 0` (degrees of freedom, denoted ``df`` + in the implementation). :math:`\Gamma` is the gamma function + (`scipy.special.gamma`). + + Special cases of `chi` are: + + - ``chi(1, loc, scale)`` is equivalent to `halfnorm` + - ``chi(2, 0, scale)`` is equivalent to `rayleigh` + - ``chi(3, 0, scale)`` is equivalent to `maxwell` + + `chi` takes ``df`` as a shape parameter. + + %(after_notes)s + + %(example)s + + """ + def _shape_info(self): + return [_ShapeInfo("df", False, (0, np.inf), (False, False))] + + def _rvs(self, df, size=None, random_state=None): + return np.sqrt(chi2.rvs(df, size=size, random_state=random_state)) + + def _pdf(self, x, df): + # x**(df-1) * exp(-x**2/2) + # chi.pdf(x, df) = ------------------------- + # 2**(df/2-1) * gamma(df/2) + return np.exp(self._logpdf(x, df)) + + def _logpdf(self, x, df): + l = np.log(2) - .5*np.log(2)*df - sc.gammaln(.5*df) + return l + sc.xlogy(df - 1., x) - .5*x**2 + + def _cdf(self, x, df): + return sc.gammainc(.5*df, .5*x**2) + + def _sf(self, x, df): + return sc.gammaincc(.5*df, .5*x**2) + + def _ppf(self, q, df): + return np.sqrt(2*sc.gammaincinv(.5*df, q)) + + def _isf(self, q, df): + return np.sqrt(2*sc.gammainccinv(.5*df, q)) + + def _stats(self, df): + # poch(df/2, 1/2) = gamma(df/2 + 1/2) / gamma(df/2) + mu = np.sqrt(2) * sc.poch(0.5 * df, 0.5) + mu2 = df - mu*mu + g1 = (2*mu**3.0 + mu*(1-2*df))/np.asarray(np.power(mu2, 1.5)) + g2 = 2*df*(1.0-df)-6*mu**4 + 4*mu**2 * (2*df-1) + g2 /= np.asarray(mu2**2.0) + return mu, mu2, g1, g2 + + def _entropy(self, df): + + def regular_formula(df): + return (sc.gammaln(.5 * df) + + 0.5 * (df - np.log(2) - (df - 1) * sc.digamma(0.5 * df))) + + def asymptotic_formula(df): + return (0.5 + np.log(np.pi)/2 - (df**-1)/6 - (df**-2)/6 + - 4/45*(df**-3) + (df**-4)/15) + + return xpx.apply_where(df < 300, df, regular_formula, asymptotic_formula) + + +chi = chi_gen(a=0.0, name='chi') + + +class chi2_gen(rv_continuous): + r"""A chi-squared continuous random variable. + + For the noncentral chi-square distribution, see `ncx2`. + + %(before_notes)s + + See Also + -------- + ncx2 + + Notes + ----- + The probability density function for `chi2` is: + + .. math:: + + f(x, k) = \frac{1}{2^{k/2} \Gamma \left( k/2 \right)} + x^{k/2-1} \exp \left( -x/2 \right) + + for :math:`x > 0` and :math:`k > 0` (degrees of freedom, denoted ``df`` + in the implementation). + + `chi2` takes ``df`` as a shape parameter. + + The chi-squared distribution is a special case of the gamma + distribution, with gamma parameters ``a = df/2``, ``loc = 0`` and + ``scale = 2``. + + %(after_notes)s + + %(example)s + + """ + def _shape_info(self): + return [_ShapeInfo("df", False, (0, np.inf), (False, False))] + + def _rvs(self, df, size=None, random_state=None): + return random_state.chisquare(df, size) + + def _pdf(self, x, df): + # chi2.pdf(x, df) = 1 / (2*gamma(df/2)) * (x/2)**(df/2-1) * exp(-x/2) + return np.exp(self._logpdf(x, df)) + + def _logpdf(self, x, df): + return sc.xlogy(df/2.-1, x) - x/2. - sc.gammaln(df/2.) - (np.log(2)*df)/2. + + def _cdf(self, x, df): + return sc.chdtr(df, x) + + def _sf(self, x, df): + return sc.chdtrc(df, x) + + def _isf(self, p, df): + return sc.chdtri(df, p) + + def _ppf(self, p, df): + return 2*sc.gammaincinv(df/2, p) + + def _stats(self, df): + mu = df + mu2 = 2*df + g1 = 2*np.sqrt(2.0/df) + g2 = 12.0/df + return mu, mu2, g1, g2 + + def _entropy(self, df): + half_df = 0.5 * df + + def regular_formula(half_df): + return (half_df + np.log(2) + sc.gammaln(half_df) + + (1 - half_df) * sc.psi(half_df)) + + def asymptotic_formula(half_df): + # plug in the above formula the following asymptotic + # expansions: + # ln(gamma(a)) ~ (a - 0.5) * ln(a) - a + 0.5 * ln(2 * pi) + + # 1/(12 * a) - 1/(360 * a**3) + # psi(a) ~ ln(a) - 1/(2 * a) - 1/(3 * a**2) + 1/120 * a**4) + c = np.log(2) + 0.5*(1 + np.log(2*np.pi)) + h = 0.5/half_df + return (h*(-2/3 + h*(-1/3 + h*(-4/45 + h/7.5))) + + 0.5*np.log(half_df) + c) + + return xpx.apply_where(half_df < 125, half_df, + regular_formula, asymptotic_formula) + + +chi2 = chi2_gen(a=0.0, name='chi2') + + +class cosine_gen(rv_continuous): + r"""A cosine continuous random variable. + + %(before_notes)s + + Notes + ----- + The cosine distribution is an approximation to the normal distribution. + The probability density function for `cosine` is: + + .. math:: + + f(x) = \frac{1}{2\pi} (1+\cos(x)) + + for :math:`-\pi \le x \le \pi`. + + %(after_notes)s + + %(example)s + + """ + def _shape_info(self): + return [] + + def _pdf(self, x): + # cosine.pdf(x) = 1/(2*pi) * (1+cos(x)) + return 1.0/2/np.pi*(1+np.cos(x)) + + def _logpdf(self, x): + c = np.cos(x) + return xpx.apply_where(c != -1, c, + lambda c: np.log1p(c) - np.log(2*np.pi), + fill_value=-np.inf) + + def _cdf(self, x): + return scu._cosine_cdf(x) + + def _sf(self, x): + return scu._cosine_cdf(-x) + + def _ppf(self, p): + return scu._cosine_invcdf(p) + + def _isf(self, p): + return -scu._cosine_invcdf(p) + + def _stats(self): + v = (np.pi * np.pi / 3.0) - 2.0 + k = -6.0 * (np.pi**4 - 90) / (5.0 * (np.pi * np.pi - 6)**2) + return 0.0, v, 0.0, k + + def _entropy(self): + return np.log(4*np.pi)-1.0 + + +cosine = cosine_gen(a=-np.pi, b=np.pi, name='cosine') + + +class dgamma_gen(rv_continuous): + r"""A double gamma continuous random variable. + + The double gamma distribution is also known as the reflected gamma + distribution [1]_. + + %(before_notes)s + + Notes + ----- + The probability density function for `dgamma` is: + + .. math:: + + f(x, a) = \frac{1}{2\Gamma(a)} |x|^{a-1} \exp(-|x|) + + for a real number :math:`x` and :math:`a > 0`. :math:`\Gamma` is the + gamma function (`scipy.special.gamma`). + + `dgamma` takes ``a`` as a shape parameter for :math:`a`. + + %(after_notes)s + + References + ---------- + .. [1] Johnson, Kotz, and Balakrishnan, "Continuous Univariate + Distributions, Volume 1", Second Edition, John Wiley and Sons + (1994). + + %(example)s + + """ + def _shape_info(self): + return [_ShapeInfo("a", False, (0, np.inf), (False, False))] + + def _rvs(self, a, size=None, random_state=None): + u = random_state.uniform(size=size) + gm = gamma.rvs(a, size=size, random_state=random_state) + return gm * np.where(u >= 0.5, 1, -1) + + def _pdf(self, x, a): + # dgamma.pdf(x, a) = 1 / (2*gamma(a)) * abs(x)**(a-1) * exp(-abs(x)) + ax = abs(x) + return 1.0/(2*sc.gamma(a))*ax**(a-1.0) * np.exp(-ax) + + def _logpdf(self, x, a): + ax = abs(x) + return sc.xlogy(a - 1.0, ax) - ax - np.log(2) - sc.gammaln(a) + + def _cdf(self, x, a): + return np.where(x > 0, + 0.5 + 0.5*sc.gammainc(a, x), + 0.5*sc.gammaincc(a, -x)) + + def _sf(self, x, a): + return np.where(x > 0, + 0.5*sc.gammaincc(a, x), + 0.5 + 0.5*sc.gammainc(a, -x)) + + def _entropy(self, a): + return stats.gamma._entropy(a) - np.log(0.5) + + def _ppf(self, q, a): + return np.where(q > 0.5, + sc.gammaincinv(a, 2*q - 1), + -sc.gammainccinv(a, 2*q)) + + def _isf(self, q, a): + return np.where(q > 0.5, + -sc.gammaincinv(a, 2*q - 1), + sc.gammainccinv(a, 2*q)) + + def _stats(self, a): + mu2 = a*(a+1.0) + return 0.0, mu2, 0.0, (a+2.0)*(a+3.0)/mu2-3.0 + + +dgamma = dgamma_gen(name='dgamma') + + +class dpareto_lognorm_gen(rv_continuous): + r"""A double Pareto lognormal continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `dpareto_lognorm` is: + + .. math:: + + f(x, \mu, \sigma, \alpha, \beta) = + \frac{\alpha \beta}{(\alpha + \beta) x} + \phi\left( \frac{\log x - \mu}{\sigma} \right) + \left( R(y_1) + R(y_2) \right) + + where :math:`R(t) = \frac{1 - \Phi(t)}{\phi(t)}`, + :math:`\phi` and :math:`\Phi` are the normal PDF and CDF, respectively, + :math:`y_1 = \alpha \sigma - \frac{\log x - \mu}{\sigma}`, + and :math:`y_2 = \beta \sigma + \frac{\log x - \mu}{\sigma}` + for real numbers :math:`x` and :math:`\mu`, :math:`\sigma > 0`, + :math:`\alpha > 0`, and :math:`\beta > 0` [1]_. + + `dpareto_lognorm` takes + ``u`` as a shape parameter for :math:`\mu`, + ``s`` as a shape parameter for :math:`\sigma`, + ``a`` as a shape parameter for :math:`\alpha`, and + ``b`` as a shape parameter for :math:`\beta`. + + A random variable :math:`X` distributed according to the PDF above + can be represented as :math:`X = U \frac{V_1}{V_2}` where :math:`U`, + :math:`V_1`, and :math:`V_2` are independent, :math:`U` is lognormally + distributed such that :math:`\log U \sim N(\mu, \sigma^2)`, and + :math:`V_1` and :math:`V_2` follow Pareto distributions with parameters + :math:`\alpha` and :math:`\beta`, respectively [2]_. + + %(after_notes)s + + References + ---------- + .. [1] Hajargasht, Gholamreza, and William E. Griffiths. "Pareto-lognormal + distributions: Inequality, poverty, and estimation from grouped income + data." Economic Modelling 33 (2013): 593-604. + .. [2] Reed, William J., and Murray Jorgensen. "The double Pareto-lognormal + distribution - a new parametric model for size distributions." + Communications in Statistics - Theory and Methods 33.8 (2004): 1733-1753. + + %(example)s + + """ + _logphi = norm._logpdf + _logPhi = norm._logcdf + _logPhic = norm._logsf + _phi = norm._pdf + _Phi = norm._cdf + _Phic = norm._sf + + def _R(self, z): + return self._Phic(z) / self._phi(z) + + def _logR(self, z): + return self._logPhic(z) - self._logphi(z) + + def _shape_info(self): + return [_ShapeInfo("u", False, (-np.inf, np.inf), (False, False)), + _ShapeInfo("s", False, (0, np.inf), (False, False)), + _ShapeInfo("a", False, (0, np.inf), (False, False)), + _ShapeInfo("b", False, (0, np.inf), (False, False))] + + def _argcheck(self, u, s, a, b): + return (s > 0) & (a > 0) & (b > 0) + + def _rvs(self, u, s, a, b, size=None, random_state=None): + # From [1] after Equation (12): "To generate pseudo-random + # deviates from the dPlN distribution, one can exponentiate + # pseudo-random deviates from NL generated using (6)." + Z = random_state.normal(u, s, size=size) + E1 = random_state.standard_exponential(size=size) + E2 = random_state.standard_exponential(size=size) + return np.exp(Z + E1 / a - E2 / b) + + def _logpdf(self, x, u, s, a, b): + with np.errstate(invalid='ignore', divide='ignore'): + log_y, m = np.log(x), u # compare against [1] Eq. 1 + z = (log_y - m) / s + x1 = a * s - z + x2 = b * s + z + out = np.asarray(np.log(a) + np.log(b) - np.log(a + b) - log_y) + out += self._logphi(z) + out += np.logaddexp(self._logR(x1), self._logR(x2)) + out[(x == 0) | np.isinf(x)] = -np.inf + return out[()] + + def _logcdf(self, x, u, s, a, b): + with np.errstate(invalid='ignore', divide='ignore'): + log_y, m = np.log(x), u # compare against [1] Eq. 2 + z = (log_y - m) / s + x1 = a * s - z + x2 = b * s + z + t1 = self._logPhi(z) + t2 = self._logphi(z) + t3 = (np.log(b) + self._logR(x1)) + t4 = (np.log(a) + self._logR(x2)) + t1, t2, t3, t4, one = np.broadcast_arrays(t1, t2, t3, t4, 1) + # t3 can be smaller than t4, so we have to consider log of negative number + # This would be much simpler, but `return_sign` is available, so use it? + # t5 = sc.logsumexp([t3, t4 + np.pi*1j]) + t5, sign = sc.logsumexp([t3, t4], b=[one, -one], axis=0, return_sign=True) + temp = [t1, t2 + t5 - np.log(a + b)] + out = np.asarray(sc.logsumexp(temp, b=[one, -one*sign], axis=0)) + out[x == 0] = -np.inf + return out[()] + + def _logsf(self, x, u, s, a, b): + return scu._log1mexp(self._logcdf(x, u, s, a, b)) + + # Infrastructure doesn't seem to do this, so... + + def _pdf(self, x, u, s, a, b): + return np.exp(self._logpdf(x, u, s, a, b)) + + def _cdf(self, x, u, s, a, b): + return np.exp(self._logcdf(x, u, s, a, b)) + + def _sf(self, x, u, s, a, b): + return np.exp(self._logsf(x, u, s, a, b)) + + def _munp(self, n, u, s, a, b): + m, k = u, float(n) # compare against [1] Eq. 6 + out = (a * b) / ((a - k) * (b + k)) * np.exp(k * m + k ** 2 * s ** 2 / 2) + out = np.asarray(out) + out[a <= k] = np.nan + return out + + +dpareto_lognorm = dpareto_lognorm_gen(a=0, name='dpareto_lognorm') + + +class dweibull_gen(rv_continuous): + r"""A double Weibull continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `dweibull` is given by + + .. math:: + + f(x, c) = c / 2 |x|^{c-1} \exp(-|x|^c) + + for a real number :math:`x` and :math:`c > 0`. + + `dweibull` takes ``c`` as a shape parameter for :math:`c`. + + %(after_notes)s + + %(example)s + + """ + def _shape_info(self): + return [_ShapeInfo("c", False, (0, np.inf), (False, False))] + + def _rvs(self, c, size=None, random_state=None): + u = random_state.uniform(size=size) + w = weibull_min.rvs(c, size=size, random_state=random_state) + return w * (np.where(u >= 0.5, 1, -1)) + + def _pdf(self, x, c): + # dweibull.pdf(x, c) = c / 2 * abs(x)**(c-1) * exp(-abs(x)**c) + ax = abs(x) + Px = c / 2.0 * ax**(c-1.0) * np.exp(-ax**c) + return Px + + def _logpdf(self, x, c): + ax = abs(x) + return np.log(c) - np.log(2.0) + sc.xlogy(c - 1.0, ax) - ax**c + + def _cdf(self, x, c): + Cx1 = 0.5 * np.exp(-abs(x)**c) + return np.where(x > 0, 1 - Cx1, Cx1) + + def _ppf(self, q, c): + fac = 2. * np.where(q <= 0.5, q, 1. - q) + fac = np.power(-np.log(fac), 1.0 / c) + return np.where(q > 0.5, fac, -fac) + + def _sf(self, x, c): + half_weibull_min_sf = 0.5 * stats.weibull_min._sf(np.abs(x), c) + return np.where(x > 0, half_weibull_min_sf, 1 - half_weibull_min_sf) + + def _isf(self, q, c): + double_q = 2. * np.where(q <= 0.5, q, 1. - q) + weibull_min_isf = stats.weibull_min._isf(double_q, c) + return np.where(q > 0.5, -weibull_min_isf, weibull_min_isf) + + def _munp(self, n, c): + return (1 - (n % 2)) * sc.gamma(1.0 + 1.0 * n / c) + + # since we know that all odd moments are zeros, return them at once. + # returning Nones from _stats makes the public stats call _munp + # so overall we're saving one or two gamma function evaluations here. + def _stats(self, c): + return 0, None, 0, None + + def _entropy(self, c): + h = stats.weibull_min._entropy(c) - np.log(0.5) + return h + + +dweibull = dweibull_gen(name='dweibull') + + +class expon_gen(rv_continuous): + r"""An exponential continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `expon` is: + + .. math:: + + f(x) = \exp(-x) + + for :math:`x \ge 0`. + + %(after_notes)s + + A common parameterization for `expon` is in terms of the rate parameter + ``lambda``, such that ``pdf = lambda * exp(-lambda * x)``. This + parameterization corresponds to using ``scale = 1 / lambda``. + + The exponential distribution is a special case of the gamma + distributions, with gamma shape parameter ``a = 1``. + + %(example)s + + """ + def _shape_info(self): + return [] + + def _rvs(self, size=None, random_state=None): + return random_state.standard_exponential(size) + + def _pdf(self, x): + # expon.pdf(x) = exp(-x) + return np.exp(-x) + + def _logpdf(self, x): + return -x + + def _cdf(self, x): + return -sc.expm1(-x) + + def _ppf(self, q): + return -sc.log1p(-q) + + def _sf(self, x): + return np.exp(-x) + + def _logsf(self, x): + return -x + + def _isf(self, q): + return -np.log(q) + + def _stats(self): + return 1.0, 1.0, 2.0, 6.0 + + def _entropy(self): + return 1.0 + + @_call_super_mom + @replace_notes_in_docstring(rv_continuous, notes="""\ + When `method='MLE'`, + this function uses explicit formulas for the maximum likelihood + estimation of the exponential distribution parameters, so the + `optimizer`, `loc` and `scale` keyword arguments are + ignored.\n\n""") + def fit(self, data, *args, **kwds): + if len(args) > 0: + raise TypeError("Too many arguments.") + + floc = kwds.pop('floc', None) + fscale = kwds.pop('fscale', None) + + _remove_optimizer_parameters(kwds) + + if floc is not None and fscale is not None: + # This check is for consistency with `rv_continuous.fit`. + raise ValueError("All parameters fixed. There is nothing to " + "optimize.") + + data = np.asarray(data) + + if not np.isfinite(data).all(): + raise ValueError("The data contains non-finite values.") + + data_min = data.min() + + if floc is None: + # ML estimate of the location is the minimum of the data. + loc = data_min + else: + loc = floc + if data_min < loc: + # There are values that are less than the specified loc. + raise FitDataError("expon", lower=floc, upper=np.inf) + + if fscale is None: + # ML estimate of the scale is the shifted mean. + scale = data.mean() - loc + else: + scale = fscale + + # We expect the return values to be floating point, so ensure it + # by explicitly converting to float. + return float(loc), float(scale) + + +expon = expon_gen(a=0.0, name='expon') + + +class exponnorm_gen(rv_continuous): + r"""An exponentially modified Normal continuous random variable. + + Also known as the exponentially modified Gaussian distribution [1]_. + + %(before_notes)s + + Notes + ----- + The probability density function for `exponnorm` is: + + .. math:: + + f(x, K) = \frac{1}{2K} \exp\left(\frac{1}{2 K^2} - x / K \right) + \text{erfc}\left(-\frac{x - 1/K}{\sqrt{2}}\right) + + where :math:`x` is a real number and :math:`K > 0`. + + It can be thought of as the sum of a standard normal random variable + and an independent exponentially distributed random variable with rate + ``1/K``. + + %(after_notes)s + + An alternative parameterization of this distribution (for example, in + the Wikipedia article [1]_) involves three parameters, :math:`\mu`, + :math:`\lambda` and :math:`\sigma`. + + In the present parameterization this corresponds to having ``loc`` and + ``scale`` equal to :math:`\mu` and :math:`\sigma`, respectively, and + shape parameter :math:`K = 1/(\sigma\lambda)`. + + .. versionadded:: 0.16.0 + + References + ---------- + .. [1] Exponentially modified Gaussian distribution, Wikipedia, + https://en.wikipedia.org/wiki/Exponentially_modified_Gaussian_distribution + + %(example)s + + """ + def _shape_info(self): + return [_ShapeInfo("K", False, (0, np.inf), (False, False))] + + def _rvs(self, K, size=None, random_state=None): + expval = random_state.standard_exponential(size) * K + gval = random_state.standard_normal(size) + return expval + gval + + def _pdf(self, x, K): + return np.exp(self._logpdf(x, K)) + + def _logpdf(self, x, K): + u = (-x + 1.0 / K) / np.sqrt(2) + def logpdf_erfcx(x, K, u): + erfcx_term = np.log(sc.erfcx(u)) + return -np.log(2) - 0.5 * (x ** 2) - np.log(K) + erfcx_term + def logpdf_default(x, K, u): + invK = 1.0 / K + exparg = invK * (-x + 0.5 * invK) + return exparg + _norm_logcdf(x - invK) - np.log(K) + use_erfcx = np.logical_and(u >= 0, K < 1) + return xpx.apply_where( + use_erfcx, (x, K, u), logpdf_erfcx, logpdf_default) + + def _cdf(self, x, K): + u = (-x + 1.0 / K) / np.sqrt(2) + def cdf_erfcx(x, K, u): + return _norm_cdf(x) - 0.5 * np.exp(-0.5 * x ** 2) * sc.erfcx(u) + def cdf_default(x, K, u): + invK = 1.0 / K + expval = invK * (0.5 * invK - x) + logprod = expval + _norm_logcdf(x - invK) + return _norm_cdf(x) - np.exp(logprod) + use_erfcx = np.logical_and(u >= 0, K < 1) + return xpx.apply_where( + use_erfcx, (x, K, u), cdf_erfcx, cdf_default) + + def _sf(self, x, K): + u = (-x + 1.0 / K) / np.sqrt(2) + def sf_erfcx(x, K, u): + return _norm_cdf(-x) + 0.5 * np.exp(-0.5 * x ** 2) * sc.erfcx(u) + def sf_default(x, K, u): + invK = 1.0 / K + expval = invK * (0.5 * invK - x) + logprod = expval + _norm_logcdf(x - invK) + return _norm_cdf(-x) + np.exp(logprod) + use_erfcx = np.logical_and(u >= 0, K < 1) + return xpx.apply_where( + use_erfcx, (x, K, u), sf_erfcx, sf_default) + + def _stats(self, K): + K2 = K * K + opK2 = 1.0 + K2 + skw = 2 * K**3 * opK2**(-1.5) + krt = 6.0 * K2 * K2 * opK2**(-2) + return K, opK2, skw, krt + +exponnorm = exponnorm_gen(name='exponnorm') + + +def _pow1pm1(x, y): + """ + Compute (1 + x)**y - 1. + + Uses expm1 and xlog1py to avoid loss of precision when + (1 + x)**y is close to 1. + + Note that the inverse of this function with respect to x is + ``_pow1pm1(x, 1/y)``. That is, if + + t = _pow1pm1(x, y) + + then + + x = _pow1pm1(t, 1/y) + """ + return np.expm1(sc.xlog1py(y, x)) + + +class exponweib_gen(rv_continuous): + r"""An exponentiated Weibull continuous random variable. + + %(before_notes)s + + See Also + -------- + weibull_min, numpy.random.Generator.weibull + + Notes + ----- + The probability density function for `exponweib` is: + + .. math:: + + f(x, a, c) = a c [1-\exp(-x^c)]^{a-1} \exp(-x^c) x^{c-1} + + and its cumulative distribution function is: + + .. math:: + + F(x, a, c) = [1-\exp(-x^c)]^a + + for :math:`x > 0`, :math:`a > 0`, :math:`c > 0`. + + `exponweib` takes :math:`a` and :math:`c` as shape parameters: + + * :math:`a` is the exponentiation parameter, + with the special case :math:`a=1` corresponding to the + (non-exponentiated) Weibull distribution `weibull_min`. + * :math:`c` is the shape parameter of the non-exponentiated Weibull law. + + %(after_notes)s + + References + ---------- + https://en.wikipedia.org/wiki/Exponentiated_Weibull_distribution + + %(example)s + + """ + def _shape_info(self): + ia = _ShapeInfo("a", False, (0, np.inf), (False, False)) + ic = _ShapeInfo("c", False, (0, np.inf), (False, False)) + return [ia, ic] + + def _pdf(self, x, a, c): + # exponweib.pdf(x, a, c) = + # a * c * (1-exp(-x**c))**(a-1) * exp(-x**c)*x**(c-1) + return np.exp(self._logpdf(x, a, c)) + + def _logpdf(self, x, a, c): + negxc = -x**c + exm1c = -sc.expm1(negxc) + logp = (np.log(a) + np.log(c) + sc.xlogy(a - 1.0, exm1c) + + negxc + sc.xlogy(c - 1.0, x)) + return logp + + def _cdf(self, x, a, c): + exm1c = -sc.expm1(-x**c) + return exm1c**a + + def _ppf(self, q, a, c): + return (-sc.log1p(-q**(1.0/a)))**np.asarray(1.0/c) + + def _sf(self, x, a, c): + return -_pow1pm1(-np.exp(-x**c), a) + + def _isf(self, p, a, c): + return (-np.log(-_pow1pm1(-p, 1/a)))**(1/c) + + +exponweib = exponweib_gen(a=0.0, name='exponweib') + + +class exponpow_gen(rv_continuous): + r"""An exponential power continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `exponpow` is: + + .. math:: + + f(x, b) = b x^{b-1} \exp(1 + x^b - \exp(x^b)) + + for :math:`x \ge 0`, :math:`b > 0`. Note that this is a different + distribution from the exponential power distribution that is also known + under the names "generalized normal" or "generalized Gaussian". + + `exponpow` takes ``b`` as a shape parameter for :math:`b`. + + %(after_notes)s + + References + ---------- + http://www.math.wm.edu/~leemis/chart/UDR/PDFs/Exponentialpower.pdf + + %(example)s + + """ + def _shape_info(self): + return [_ShapeInfo("b", False, (0, np.inf), (False, False))] + + def _pdf(self, x, b): + # exponpow.pdf(x, b) = b * x**(b-1) * exp(1 + x**b - exp(x**b)) + return np.exp(self._logpdf(x, b)) + + def _logpdf(self, x, b): + xb = x**b + f = 1 + np.log(b) + sc.xlogy(b - 1.0, x) + xb - np.exp(xb) + return f + + def _cdf(self, x, b): + return -sc.expm1(-sc.expm1(x**b)) + + def _sf(self, x, b): + return np.exp(-sc.expm1(x**b)) + + def _isf(self, x, b): + return (sc.log1p(-np.log(x)))**(1./b) + + def _ppf(self, q, b): + return pow(sc.log1p(-sc.log1p(-q)), 1.0/b) + + +exponpow = exponpow_gen(a=0.0, name='exponpow') + + +class fatiguelife_gen(rv_continuous): + r"""A fatigue-life (Birnbaum-Saunders) continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `fatiguelife` is: + + .. math:: + + f(x, c) = \frac{x+1}{2c\sqrt{2\pi x^3}} \exp(-\frac{(x-1)^2}{2x c^2}) + + for :math:`x >= 0` and :math:`c > 0`. + + `fatiguelife` takes ``c`` as a shape parameter for :math:`c`. + + %(after_notes)s + + References + ---------- + .. [1] "Birnbaum-Saunders distribution", + https://en.wikipedia.org/wiki/Birnbaum-Saunders_distribution + + %(example)s + + """ + _support_mask = rv_continuous._open_support_mask + + def _shape_info(self): + return [_ShapeInfo("c", False, (0, np.inf), (False, False))] + + def _rvs(self, c, size=None, random_state=None): + z = random_state.standard_normal(size) + x = 0.5*c*z + x2 = x*x + t = 1.0 + 2*x2 + 2*x*np.sqrt(1 + x2) + return t + + def _pdf(self, x, c): + # fatiguelife.pdf(x, c) = + # (x+1) / (2*c*sqrt(2*pi*x**3)) * exp(-(x-1)**2/(2*x*c**2)) + return np.exp(self._logpdf(x, c)) + + def _logpdf(self, x, c): + return (np.log(x+1) - (x-1)**2 / (2.0*x*c**2) - np.log(2*c) - + 0.5*(np.log(2*np.pi) + 3*np.log(x))) + + def _cdf(self, x, c): + return _norm_cdf(1.0 / c * (np.sqrt(x) - 1.0/np.sqrt(x))) + + def _ppf(self, q, c): + tmp = c * _norm_ppf(q) + return 0.25 * (tmp + np.sqrt(tmp**2 + 4))**2 + + def _sf(self, x, c): + return _norm_sf(1.0 / c * (np.sqrt(x) - 1.0/np.sqrt(x))) + + def _isf(self, q, c): + tmp = -c * _norm_ppf(q) + return 0.25 * (tmp + np.sqrt(tmp**2 + 4))**2 + + def _stats(self, c): + # NB: the formula for kurtosis in wikipedia seems to have an error: + # it's 40, not 41. At least it disagrees with the one from Wolfram + # Alpha. And the latter one, below, passes the tests, while the wiki + # one doesn't So far I didn't have the guts to actually check the + # coefficients from the expressions for the raw moments. + c2 = c*c + mu = c2 / 2.0 + 1.0 + den = 5.0 * c2 + 4.0 + mu2 = c2*den / 4.0 + g1 = 4 * c * (11*c2 + 6.0) / np.power(den, 1.5) + g2 = 6 * c2 * (93*c2 + 40.0) / den**2.0 + return mu, mu2, g1, g2 + + +fatiguelife = fatiguelife_gen(a=0.0, name='fatiguelife') + + +class foldcauchy_gen(rv_continuous): + r"""A folded Cauchy continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `foldcauchy` is: + + .. math:: + + f(x, c) = \frac{1}{\pi (1+(x-c)^2)} + \frac{1}{\pi (1+(x+c)^2)} + + for :math:`x \ge 0` and :math:`c \ge 0`. + + `foldcauchy` takes ``c`` as a shape parameter for :math:`c`. + + %(example)s + + """ + def _argcheck(self, c): + return c >= 0 + + def _shape_info(self): + return [_ShapeInfo("c", False, (0, np.inf), (True, False))] + + def _rvs(self, c, size=None, random_state=None): + return abs(cauchy.rvs(loc=c, size=size, + random_state=random_state)) + + def _pdf(self, x, c): + # foldcauchy.pdf(x, c) = 1/(pi*(1+(x-c)**2)) + 1/(pi*(1+(x+c)**2)) + return 1.0/np.pi*(1.0/(1+(x-c)**2) + 1.0/(1+(x+c)**2)) + + def _cdf(self, x, c): + return 1.0/np.pi*(np.arctan(x-c) + np.arctan(x+c)) + + def _sf(self, x, c): + # 1 - CDF(x, c) = 1 - (atan(x - c) + atan(x + c))/pi + # = ((pi/2 - atan(x - c)) + (pi/2 - atan(x + c)))/pi + # = (acot(x - c) + acot(x + c))/pi + # = (atan2(1, x - c) + atan2(1, x + c))/pi + return (np.arctan2(1, x - c) + np.arctan2(1, x + c))/np.pi + + def _stats(self, c): + return np.inf, np.inf, np.nan, np.nan + + +foldcauchy = foldcauchy_gen(a=0.0, name='foldcauchy') + + +class f_gen(rv_continuous): + r"""An F continuous random variable. + + For the noncentral F distribution, see `ncf`. + + %(before_notes)s + + See Also + -------- + ncf + + Notes + ----- + The F distribution with :math:`df_1 > 0` and :math:`df_2 > 0` degrees of freedom is + the distribution of the ratio of two independent chi-squared distributions with + :math:`df_1` and :math:`df_2` degrees of freedom, after rescaling by + :math:`df_2 / df_1`. + + The probability density function for `f` is: + + .. math:: + + f(x, df_1, df_2) = \frac{df_2^{df_2/2} df_1^{df_1/2} x^{df_1 / 2-1}} + {(df_2+df_1 x)^{(df_1+df_2)/2} + B(df_1/2, df_2/2)} + + for :math:`x > 0`. + + `f` accepts shape parameters ``dfn`` and ``dfd`` for :math:`df_1`, the degrees of + freedom of the chi-squared distribution in the numerator, and :math:`df_2`, the + degrees of freedom of the chi-squared distribution in the denominator, respectively. + + %(after_notes)s + + %(example)s + + """ + def _shape_info(self): + idfn = _ShapeInfo("dfn", False, (0, np.inf), (False, False)) + idfd = _ShapeInfo("dfd", False, (0, np.inf), (False, False)) + return [idfn, idfd] + + def _rvs(self, dfn, dfd, size=None, random_state=None): + return random_state.f(dfn, dfd, size) + + def _pdf(self, x, dfn, dfd): + # df2**(df2/2) * df1**(df1/2) * x**(df1/2-1) + # F.pdf(x, df1, df2) = -------------------------------------------- + # (df2+df1*x)**((df1+df2)/2) * B(df1/2, df2/2) + return np.exp(self._logpdf(x, dfn, dfd)) + + def _logpdf(self, x, dfn, dfd): + n = 1.0 * dfn + m = 1.0 * dfd + lPx = (m/2 * np.log(m) + n/2 * np.log(n) + sc.xlogy(n/2 - 1, x) + - (((n+m)/2) * np.log(m + n*x) + sc.betaln(n/2, m/2))) + return lPx + + def _cdf(self, x, dfn, dfd): + return sc.fdtr(dfn, dfd, x) + + def _sf(self, x, dfn, dfd): + return sc.fdtrc(dfn, dfd, x) + + def _ppf(self, q, dfn, dfd): + return sc.fdtri(dfn, dfd, q) + + def _stats(self, dfn, dfd): + v1, v2 = 1. * dfn, 1. * dfd + v2_2, v2_4, v2_6, v2_8 = v2 - 2., v2 - 4., v2 - 6., v2 - 8. + + mu = xpx.apply_where( + v2 > 2, (v2, v2_2), + lambda v2, v2_2: v2 / v2_2, + fill_value=np.inf) + + mu2 = xpx.apply_where( + v2 > 4, (v1, v2, v2_2, v2_4), + lambda v1, v2, v2_2, v2_4: + 2 * v2 * v2 * (v1 + v2_2) / (v1 * v2_2**2 * v2_4), + fill_value=np.inf) + + g1 = xpx.apply_where( + v2 > 6, (v1, v2_2, v2_4, v2_6), + lambda v1, v2_2, v2_4, v2_6: + (2 * v1 + v2_2) / v2_6 * np.sqrt(v2_4 / (v1 * (v1 + v2_2))), + fill_value=np.nan) + g1 *= np.sqrt(8.) + + g2 = xpx.apply_where( + v2 > 8, (g1, v2_6, v2_8), + lambda g1, v2_6, v2_8: (8 + g1 * g1 * v2_6) / v2_8, + fill_value=np.nan) + g2 *= 3. / 2. + + return mu, mu2, g1, g2 + + def _entropy(self, dfn, dfd): + # the formula found in literature is incorrect. This one yields the + # same result as numerical integration using the generic entropy + # definition. This is also tested in tests/test_conntinous_basic + half_dfn = 0.5 * dfn + half_dfd = 0.5 * dfd + half_sum = 0.5 * (dfn + dfd) + + return (np.log(dfd) - np.log(dfn) + sc.betaln(half_dfn, half_dfd) + + (1 - half_dfn) * sc.psi(half_dfn) - (1 + half_dfd) * + sc.psi(half_dfd) + half_sum * sc.psi(half_sum)) + + +f = f_gen(a=0.0, name='f') + + +## Folded Normal +## abs(Z) where (Z is normal with mu=L and std=S so that c=abs(L)/S) +## +## note: regress docs have scale parameter correct, but first parameter +## he gives is a shape parameter A = c * scale + +## Half-normal is folded normal with shape-parameter c=0. + +class foldnorm_gen(rv_continuous): + r"""A folded normal continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `foldnorm` is: + + .. math:: + + f(x, c) = \sqrt{2/\pi} cosh(c x) \exp(-\frac{x^2+c^2}{2}) + + for :math:`x \ge 0` and :math:`c \ge 0`. + + `foldnorm` takes ``c`` as a shape parameter for :math:`c`. + + %(after_notes)s + + %(example)s + + """ + def _argcheck(self, c): + return c >= 0 + + def _shape_info(self): + return [_ShapeInfo("c", False, (0, np.inf), (True, False))] + + def _rvs(self, c, size=None, random_state=None): + return abs(random_state.standard_normal(size) + c) + + def _pdf(self, x, c): + # foldnormal.pdf(x, c) = sqrt(2/pi) * cosh(c*x) * exp(-(x**2+c**2)/2) + return _norm_pdf(x + c) + _norm_pdf(x-c) + + def _cdf(self, x, c): + sqrt_two = np.sqrt(2) + return 0.5 * (sc.erf((x - c)/sqrt_two) + sc.erf((x + c)/sqrt_two)) + + def _sf(self, x, c): + return _norm_sf(x - c) + _norm_sf(x + c) + + def _stats(self, c): + # Regina C. Elandt, Technometrics 3, 551 (1961) + # https://www.jstor.org/stable/1266561 + # + c2 = c*c + expfac = np.exp(-0.5*c2) / np.sqrt(2.*np.pi) + + mu = 2.*expfac + c * sc.erf(c/np.sqrt(2)) + mu2 = c2 + 1 - mu*mu + + g1 = 2. * (mu*mu*mu - c2*mu - expfac) + g1 /= np.power(mu2, 1.5) + + g2 = c2 * (c2 + 6.) + 3 + 8.*expfac*mu + g2 += (2. * (c2 - 3.) - 3. * mu**2) * mu**2 + g2 = g2 / mu2**2.0 - 3. + + return mu, mu2, g1, g2 + + +foldnorm = foldnorm_gen(a=0.0, name='foldnorm') + + +class weibull_min_gen(rv_continuous): + r"""Weibull minimum continuous random variable. + + The Weibull Minimum Extreme Value distribution, from extreme value theory + (Fisher-Gnedenko theorem), is also often simply called the Weibull + distribution. It arises as the limiting distribution of the rescaled + minimum of iid random variables. + + %(before_notes)s + + See Also + -------- + weibull_max, numpy.random.Generator.weibull, exponweib + + Notes + ----- + The probability density function for `weibull_min` is: + + .. math:: + + f(x, c) = c x^{c-1} \exp(-x^c) + + for :math:`x > 0`, :math:`c > 0`. + + `weibull_min` takes ``c`` as a shape parameter for :math:`c`. + (named :math:`k` in Wikipedia article and :math:`a` in + ``numpy.random.weibull``). Special shape values are :math:`c=1` and + :math:`c=2` where Weibull distribution reduces to the `expon` and + `rayleigh` distributions respectively. + + Suppose ``X`` is an exponentially distributed random variable with + scale ``s``. Then ``Y = X**k`` is `weibull_min` distributed with shape + ``c = 1/k`` and scale ``s**k``. + + %(after_notes)s + + References + ---------- + https://en.wikipedia.org/wiki/Weibull_distribution + + https://en.wikipedia.org/wiki/Fisher-Tippett-Gnedenko_theorem + + %(example)s + + """ + def _shape_info(self): + return [_ShapeInfo("c", False, (0, np.inf), (False, False))] + + def _pdf(self, x, c): + # weibull_min.pdf(x, c) = c * x**(c-1) * exp(-x**c) + return c*pow(x, c-1)*np.exp(-pow(x, c)) + + def _logpdf(self, x, c): + return np.log(c) + sc.xlogy(c - 1, x) - pow(x, c) + + def _cdf(self, x, c): + return -sc.expm1(-pow(x, c)) + + def _ppf(self, q, c): + return pow(-sc.log1p(-q), 1.0/c) + + def _sf(self, x, c): + return np.exp(self._logsf(x, c)) + + def _logsf(self, x, c): + return -pow(x, c) + + def _isf(self, q, c): + return (-np.log(q))**(1/c) + + def _munp(self, n, c): + return sc.gamma(1.0+n*1.0/c) + + def _entropy(self, c): + return -_EULER / c - np.log(c) + _EULER + 1 + + @extend_notes_in_docstring(rv_continuous, notes="""\ + If ``method='mm'``, parameters fixed by the user are respected, and the + remaining parameters are used to match distribution and sample moments + where possible. For example, if the user fixes the location with + ``floc``, the parameters will only match the distribution skewness and + variance to the sample skewness and variance; no attempt will be made + to match the means or minimize a norm of the errors. + \n\n""") + def fit(self, data, *args, **kwds): + + if isinstance(data, CensoredData): + if data.num_censored() == 0: + data = data._uncensor() + else: + return super().fit(data, *args, **kwds) + + if kwds.pop('superfit', False): + return super().fit(data, *args, **kwds) + + # this extracts fixed shape, location, and scale however they + # are specified, and also leaves them in `kwds` + data, fc, floc, fscale = _check_fit_input_parameters(self, data, + args, kwds) + method = kwds.get("method", "mle").lower() + + # See https://en.wikipedia.org/wiki/Weibull_distribution#Moments for + # moment formulas. + def skew(c): + gamma1 = sc.gamma(1+1/c) + gamma2 = sc.gamma(1+2/c) + gamma3 = sc.gamma(1+3/c) + num = 2 * gamma1**3 - 3*gamma1*gamma2 + gamma3 + den = (gamma2 - gamma1**2)**(3/2) + return num/den + + # For c in [1e2, 3e4], population skewness appears to approach + # asymptote near -1.139, but past c > 3e4, skewness begins to vary + # wildly, and MoM won't provide a good guess. Get out early. + s = stats.skew(data) + max_c = 1e4 + s_min = skew(max_c) + if s < s_min and method != "mm" and fc is None and not args: + return super().fit(data, *args, **kwds) + + # If method is method of moments, we don't need the user's guesses. + # Otherwise, extract the guesses from args and kwds. + if method == "mm": + c, loc, scale = None, None, None + else: + c = args[0] if len(args) else None + loc = kwds.pop('loc', None) + scale = kwds.pop('scale', None) + + if fc is None and c is None: # not fixed and no guess: use MoM + # Solve for c that matches sample distribution skewness to sample + # skewness. + # we start having numerical issues with `weibull_min` with + # parameters outside this range - and not just in this method. + # We could probably improve the situation by doing everything + # in the log space, but that is for another time. + c = root_scalar(lambda c: skew(c) - s, bracket=[0.02, max_c], + method='bisect').root + elif fc is not None: # fixed: use it + c = fc + + if fscale is None and scale is None: + v = np.var(data) + scale = np.sqrt(v / (sc.gamma(1+2/c) - sc.gamma(1+1/c)**2)) + elif fscale is not None: + scale = fscale + + if floc is None and loc is None: + m = np.mean(data) + loc = m - scale*sc.gamma(1 + 1/c) + elif floc is not None: + loc = floc + + if method == 'mm': + return c, loc, scale + else: + # At this point, parameter "guesses" may equal the fixed parameters + # in kwds. No harm in passing them as guesses, too. + return super().fit(data, c, loc=loc, scale=scale, **kwds) + + +weibull_min = weibull_min_gen(a=0.0, name='weibull_min') + + +class truncweibull_min_gen(rv_continuous): + r"""A doubly truncated Weibull minimum continuous random variable. + + %(before_notes)s + + See Also + -------- + weibull_min, truncexpon + + Notes + ----- + The probability density function for `truncweibull_min` is: + + .. math:: + + f(x, a, b, c) = \frac{c x^{c-1} \exp(-x^c)}{\exp(-a^c) - \exp(-b^c)} + + for :math:`a < x <= b`, :math:`0 \le a < b` and :math:`c > 0`. + + `truncweibull_min` takes :math:`a`, :math:`b`, and :math:`c` as shape + parameters. + + Notice that the truncation values, :math:`a` and :math:`b`, are defined in + standardized form: + + .. math:: + + a = (u_l - loc)/scale + b = (u_r - loc)/scale + + where :math:`u_l` and :math:`u_r` are the specific left and right + truncation values, respectively. In other words, the support of the + distribution becomes :math:`(a*scale + loc) < x <= (b*scale + loc)` when + :math:`loc` and/or :math:`scale` are provided. + + %(after_notes)s + + References + ---------- + + .. [1] Rinne, H. "The Weibull Distribution: A Handbook". CRC Press (2009). + + %(example)s + + """ + def _argcheck(self, c, a, b): + return (a >= 0.) & (b > a) & (c > 0.) + + def _shape_info(self): + ic = _ShapeInfo("c", False, (0, np.inf), (False, False)) + ia = _ShapeInfo("a", False, (0, np.inf), (True, False)) + ib = _ShapeInfo("b", False, (0, np.inf), (False, False)) + return [ic, ia, ib] + + def _fitstart(self, data): + # Arbitrary, but default a=b=c=1 is not valid + return super()._fitstart(data, args=(1, 0, 1)) + + def _get_support(self, c, a, b): + return a, b + + def _pdf(self, x, c, a, b): + denum = (np.exp(-pow(a, c)) - np.exp(-pow(b, c))) + return (c * pow(x, c-1) * np.exp(-pow(x, c))) / denum + + def _logpdf(self, x, c, a, b): + logdenum = np.log(np.exp(-pow(a, c)) - np.exp(-pow(b, c))) + return np.log(c) + sc.xlogy(c - 1, x) - pow(x, c) - logdenum + + def _cdf(self, x, c, a, b): + num = (np.exp(-pow(a, c)) - np.exp(-pow(x, c))) + denum = (np.exp(-pow(a, c)) - np.exp(-pow(b, c))) + return num / denum + + def _logcdf(self, x, c, a, b): + lognum = np.log(np.exp(-pow(a, c)) - np.exp(-pow(x, c))) + logdenum = np.log(np.exp(-pow(a, c)) - np.exp(-pow(b, c))) + return lognum - logdenum + + def _sf(self, x, c, a, b): + num = (np.exp(-pow(x, c)) - np.exp(-pow(b, c))) + denum = (np.exp(-pow(a, c)) - np.exp(-pow(b, c))) + return num / denum + + def _logsf(self, x, c, a, b): + lognum = np.log(np.exp(-pow(x, c)) - np.exp(-pow(b, c))) + logdenum = np.log(np.exp(-pow(a, c)) - np.exp(-pow(b, c))) + return lognum - logdenum + + def _isf(self, q, c, a, b): + return pow( + -np.log((1 - q) * np.exp(-pow(b, c)) + q * np.exp(-pow(a, c))), 1/c + ) + + def _ppf(self, q, c, a, b): + return pow( + -np.log((1 - q) * np.exp(-pow(a, c)) + q * np.exp(-pow(b, c))), 1/c + ) + + def _munp(self, n, c, a, b): + gamma_fun = sc.gamma(n/c + 1.) * ( + sc.gammainc(n/c + 1., pow(b, c)) - sc.gammainc(n/c + 1., pow(a, c)) + ) + denum = (np.exp(-pow(a, c)) - np.exp(-pow(b, c))) + return gamma_fun / denum + + +truncweibull_min = truncweibull_min_gen(name='truncweibull_min') +truncweibull_min._support = ('a', 'b') + + +class weibull_max_gen(rv_continuous): + r"""Weibull maximum continuous random variable. + + The Weibull Maximum Extreme Value distribution, from extreme value theory + (Fisher-Gnedenko theorem), is the limiting distribution of rescaled + maximum of iid random variables. This is the distribution of -X + if X is from the `weibull_min` function. + + %(before_notes)s + + See Also + -------- + weibull_min + + Notes + ----- + The probability density function for `weibull_max` is: + + .. math:: + + f(x, c) = c (-x)^{c-1} \exp(-(-x)^c) + + for :math:`x < 0`, :math:`c > 0`. + + `weibull_max` takes ``c`` as a shape parameter for :math:`c`. + + %(after_notes)s + + References + ---------- + https://en.wikipedia.org/wiki/Weibull_distribution + + https://en.wikipedia.org/wiki/Fisher-Tippett-Gnedenko_theorem + + %(example)s + + """ + def _shape_info(self): + return [_ShapeInfo("c", False, (0, np.inf), (False, False))] + + def _pdf(self, x, c): + # weibull_max.pdf(x, c) = c * (-x)**(c-1) * exp(-(-x)**c) + return c*pow(-x, c-1)*np.exp(-pow(-x, c)) + + def _logpdf(self, x, c): + return np.log(c) + sc.xlogy(c-1, -x) - pow(-x, c) + + def _cdf(self, x, c): + return np.exp(-pow(-x, c)) + + def _logcdf(self, x, c): + return -pow(-x, c) + + def _sf(self, x, c): + return -sc.expm1(-pow(-x, c)) + + def _ppf(self, q, c): + return -pow(-np.log(q), 1.0/c) + + def _munp(self, n, c): + val = sc.gamma(1.0+n*1.0/c) + if int(n) % 2: + sgn = -1 + else: + sgn = 1 + return sgn * val + + def _entropy(self, c): + return -_EULER / c - np.log(c) + _EULER + 1 + + +weibull_max = weibull_max_gen(b=0.0, name='weibull_max') + + +class genlogistic_gen(rv_continuous): + r"""A generalized logistic continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `genlogistic` is: + + .. math:: + + f(x, c) = c \frac{\exp(-x)} + {(1 + \exp(-x))^{c+1}} + + for real :math:`x` and :math:`c > 0`. In literature, different + generalizations of the logistic distribution can be found. This is the type 1 + generalized logistic distribution according to [1]_. It is also referred to + as the skew-logistic distribution [2]_. + + `genlogistic` takes ``c`` as a shape parameter for :math:`c`. + + %(after_notes)s + + References + ---------- + .. [1] Johnson et al. "Continuous Univariate Distributions", Volume 2, + Wiley. 1995. + .. [2] "Generalized Logistic Distribution", Wikipedia, + https://en.wikipedia.org/wiki/Generalized_logistic_distribution + + %(example)s + + """ + def _shape_info(self): + return [_ShapeInfo("c", False, (0, np.inf), (False, False))] + + def _pdf(self, x, c): + # genlogistic.pdf(x, c) = c * exp(-x) / (1 + exp(-x))**(c+1) + return np.exp(self._logpdf(x, c)) + + def _logpdf(self, x, c): + # Two mathematically equivalent expressions for log(pdf(x, c)): + # log(pdf(x, c)) = log(c) - x - (c + 1)*log(1 + exp(-x)) + # = log(c) + c*x - (c + 1)*log(1 + exp(x)) + mult = -(c - 1) * (x < 0) - 1 + absx = np.abs(x) + return np.log(c) + mult*absx - (c+1) * sc.log1p(np.exp(-absx)) + + def _cdf(self, x, c): + Cx = (1+np.exp(-x))**(-c) + return Cx + + def _logcdf(self, x, c): + return -c * np.log1p(np.exp(-x)) + + def _ppf(self, q, c): + return -np.log(sc.powm1(q, -1.0/c)) + + def _sf(self, x, c): + return -sc.expm1(self._logcdf(x, c)) + + def _isf(self, q, c): + return self._ppf(1 - q, c) + + def _stats(self, c): + mu = _EULER + sc.psi(c) + mu2 = np.pi*np.pi/6.0 + sc.zeta(2, c) + g1 = -2*sc.zeta(3, c) + 2*_ZETA3 + g1 /= np.power(mu2, 1.5) + g2 = np.pi**4/15.0 + 6*sc.zeta(4, c) + g2 /= mu2**2.0 + return mu, mu2, g1, g2 + + def _entropy(self, c): + return xpx.apply_where( + c < 8e6, c, + lambda c: -np.log(c) + sc.psi(c + 1) + _EULER + 1, + # asymptotic expansion: psi(c) ~ log(c) - 1 / (2 * c) + # a = -log(c) + psi(c + 1) + # = -log(c) + psi(c) + 1 / c + # ~ -log(c) + log(c) - 1 / (2 * c) + 1 / c + # = 1 / (2 * c) + lambda c: 1 / (2 * c) + _EULER + 1) + + +genlogistic = genlogistic_gen(name='genlogistic') + + +class genpareto_gen(rv_continuous): + r"""A generalized Pareto continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `genpareto` is: + + .. math:: + + f(x, c) = (1 + c x)^{-1 - 1/c} + + defined for :math:`x \ge 0` if :math:`c \ge 0`, and for + :math:`0 \le x \le -1/c` if :math:`c < 0`. + + `genpareto` takes ``c`` as a shape parameter for :math:`c`. + + For :math:`c=0`, `genpareto` reduces to the exponential + distribution, `expon`: + + .. math:: + + f(x, 0) = \exp(-x) + + For :math:`c=-1`, `genpareto` is uniform on ``[0, 1]``: + + .. math:: + + f(x, -1) = 1 + + %(after_notes)s + + %(example)s + + """ + def _argcheck(self, c): + return np.isfinite(c) + + def _shape_info(self): + return [_ShapeInfo("c", False, (-np.inf, np.inf), (False, False))] + + def _get_support(self, c): + c = np.asarray(c) + a = np.broadcast_arrays(self.a, c)[0].copy() + b = xpx.apply_where(c < 0, c, lambda c: -1. / c, + fill_value=np.inf) + return a, b + + def _pdf(self, x, c): + # genpareto.pdf(x, c) = (1 + c * x)**(-1 - 1/c) + return np.exp(self._logpdf(x, c)) + + def _logpdf(self, x, c): + return xpx.apply_where((x == x) & (c != 0), (x, c), + lambda x, c: -sc.xlog1py(c + 1., c*x) / c, + fill_value=-x) + + def _cdf(self, x, c): + return -sc.inv_boxcox1p(-x, -c) + + def _sf(self, x, c): + return sc.inv_boxcox(-x, -c) + + def _logsf(self, x, c): + return xpx.apply_where((x == x) & (c != 0), (x, c), + lambda x, c: -sc.log1p(c*x) / c, + fill_value=-x) + + def _ppf(self, q, c): + return -sc.boxcox1p(-q, -c) + + def _isf(self, q, c): + return -sc.boxcox(q, -c) + + def _stats(self, c, moments='mv'): + m, v, s, k = None, None, None, None + + if 'm' in moments: + m = xpx.apply_where(c < 1, c, + lambda xi: 1 / (1 - xi), + fill_value=np.inf) + + if 'v' in moments: + v = xpx.apply_where(c < 1/2, c, + lambda xi: 1 / (1 - xi)**2 / (1 - 2 * xi), + fill_value=np.nan) + + if 's' in moments: + s = xpx.apply_where( + c < 1/3, c, + lambda xi: 2 * (1 + xi) * np.sqrt(1 - 2*xi) / (1 - 3*xi), + fill_value=np.nan) + + if 'k' in moments: + k = xpx.apply_where( + c < 1/4, c, + lambda xi: 3 * (1 - 2*xi) * (2*xi**2 + xi + 3) + / (1 - 3*xi) / (1 - 4*xi) - 3, + fill_value=np.nan) + + return m, v, s, k + + def _munp(self, n, c): + def __munp(c): + val = 0.0 + k = np.arange(0, n + 1) + for ki, cnk in zip(k, sc.comb(n, k)): + val = val + cnk * (-1) ** ki / (1.0 - c * ki) + return np.where(c * n < 1, val * (-1.0 / c) ** n, np.inf) + + return xpx.apply_where(c != 0, c, __munp, fill_value=sc.gamma(n + 1)) + + def _entropy(self, c): + return 1. + c + + +genpareto = genpareto_gen(a=0.0, name='genpareto') + + +class genexpon_gen(rv_continuous): + r"""A generalized exponential continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `genexpon` is: + + .. math:: + + f(x, a, b, c) = (a + b (1 - \exp(-c x))) + \exp(-a x - b x + \frac{b}{c} (1-\exp(-c x))) + + for :math:`x \ge 0`, :math:`a, b, c > 0`. + + `genexpon` takes :math:`a`, :math:`b` and :math:`c` as shape parameters. + + %(after_notes)s + + References + ---------- + H.K. Ryu, "An Extension of Marshall and Olkin's Bivariate Exponential + Distribution", Journal of the American Statistical Association, 1993. + + N. Balakrishnan, Asit P. Basu (editors), *The Exponential Distribution: + Theory, Methods and Applications*, Gordon and Breach, 1995. + ISBN 10: 2884491929 + + %(example)s + + """ + def _shape_info(self): + ia = _ShapeInfo("a", False, (0, np.inf), (False, False)) + ib = _ShapeInfo("b", False, (0, np.inf), (False, False)) + ic = _ShapeInfo("c", False, (0, np.inf), (False, False)) + return [ia, ib, ic] + + def _pdf(self, x, a, b, c): + # genexpon.pdf(x, a, b, c) = (a + b * (1 - exp(-c*x))) * \ + # exp(-a*x - b*x + b/c * (1-exp(-c*x))) + return (a + b*(-sc.expm1(-c*x)))*np.exp((-a-b)*x + + b*(-sc.expm1(-c*x))/c) + + def _logpdf(self, x, a, b, c): + return np.log(a+b*(-sc.expm1(-c*x))) + (-a-b)*x+b*(-sc.expm1(-c*x))/c + + def _cdf(self, x, a, b, c): + return -sc.expm1((-a-b)*x + b*(-sc.expm1(-c*x))/c) + + def _ppf(self, p, a, b, c): + s = a + b + t = (b - c*np.log1p(-p))/s + return (t + sc.lambertw(-b/s * np.exp(-t)).real)/c + + def _sf(self, x, a, b, c): + return np.exp((-a-b)*x + b*(-sc.expm1(-c*x))/c) + + def _isf(self, p, a, b, c): + s = a + b + t = (b - c*np.log(p))/s + return (t + sc.lambertw(-b/s * np.exp(-t)).real)/c + + +genexpon = genexpon_gen(a=0.0, name='genexpon') + + +class genextreme_gen(rv_continuous): + r"""A generalized extreme value continuous random variable. + + %(before_notes)s + + See Also + -------- + gumbel_r + + Notes + ----- + For :math:`c=0`, `genextreme` is equal to `gumbel_r` with + probability density function + + .. math:: + + f(x) = \exp(-\exp(-x)) \exp(-x), + + where :math:`-\infty < x < \infty`. + + For :math:`c \ne 0`, the probability density function for `genextreme` is: + + .. math:: + + f(x, c) = \exp(-(1-c x)^{1/c}) (1-c x)^{1/c-1}, + + where :math:`-\infty < x \le 1/c` if :math:`c > 0` and + :math:`1/c \le x < \infty` if :math:`c < 0`. + + Note that several sources and software packages use the opposite + convention for the sign of the shape parameter :math:`c`. + + `genextreme` takes ``c`` as a shape parameter for :math:`c`. + + %(after_notes)s + + %(example)s + + """ + def _argcheck(self, c): + return np.isfinite(c) + + def _shape_info(self): + return [_ShapeInfo("c", False, (-np.inf, np.inf), (False, False))] + + def _get_support(self, c): + c = np.asarray(c) + _b = np.where(c > 0, 1.0 / np.maximum(c, _XMIN), np.inf) + _a = np.where(c < 0, 1.0 / np.minimum(c, -_XMIN), -np.inf) + return _a, _b + + def _loglogcdf(self, x, c): + # Returns log(-log(cdf(x, c))) + return xpx.apply_where( + (x == x) & (c != 0), (x, c), + lambda x, c: sc.log1p(-c*x)/c, + fill_value=-x) + + def _pdf(self, x, c): + # genextreme.pdf(x, c) = + # exp(-exp(-x))*exp(-x), for c==0 + # exp(-(1-c*x)**(1/c))*(1-c*x)**(1/c-1), for x \le 1/c, c > 0 + return np.exp(self._logpdf(x, c)) + + def _logpdf(self, x, c): + # Suppress warnings 0 * inf + cx = xpx.apply_where((x == x) & (c != 0), (c, x), + operator.mul, fill_value=0.0) + logex2 = sc.log1p(-cx) + logpex2 = self._loglogcdf(x, c) + pex2 = np.exp(logpex2) + # Handle special cases + np.putmask(logpex2, (c == 0) & (x == -np.inf), 0.0) + logpdf = xpx.apply_where( + ~((cx == 1) | (cx == -np.inf)), + (pex2, logpex2, logex2), + lambda pex2, lpex2, lex2: -pex2 + lpex2 - lex2, + fill_value=-np.inf) + np.putmask(logpdf, (c == 1) & (x == 1), 0.0) + return logpdf + + def _logcdf(self, x, c): + return -np.exp(self._loglogcdf(x, c)) + + def _cdf(self, x, c): + return np.exp(self._logcdf(x, c)) + + def _sf(self, x, c): + return -sc.expm1(self._logcdf(x, c)) + + def _ppf(self, q, c): + x = -np.log(-np.log(q)) + return xpx.apply_where( + (x == x) & (c != 0), (x, c), + lambda x, c: -sc.expm1(-c * x) / c, + fill_value=x) + + def _isf(self, q, c): + x = -np.log(-sc.log1p(-q)) + return xpx.apply_where( + (x == x) & (c != 0), (x, c), + lambda x, c: -sc.expm1(-c * x) / c, + fill_value=x) + + def _stats(self, c): + def g(n): + return sc.gamma(n * c + 1) + g1 = g(1) + g2 = g(2) + g3 = g(3) + g4 = g(4) + g2mg12 = np.where(abs(c) < 1e-7, (c*np.pi)**2.0/6.0, g2-g1**2.0) + def gam2k_f(c): + return sc.expm1(sc.gammaln(2.0*c+1.0)-2*sc.gammaln(c + 1.0))/c**2.0 + gam2k = xpx.apply_where(abs(c) >= 1e-7, c, gam2k_f, fill_value=np.pi**2.0/6.0) + eps = 1e-14 + def gamk_f(c): + return sc.expm1(sc.gammaln(c + 1))/c + gamk = xpx.apply_where(abs(c) >= eps, c, gamk_f, fill_value=-_EULER) + + # mean + m = np.where(c < -1.0, np.nan, -gamk) + + # variance + v = np.where(c < -0.5, np.nan, g1**2.0*gam2k) + + # skewness + def sk1_eval(c, *args): + def sk1_eval_f(c, g1, g2, g3, g2mg12): + return np.sign(c)*(-g3 + (g2 + 2*g2mg12)*g1)/g2mg12**1.5 + return xpx.apply_where(c >= -1./3, (c, *args), + sk1_eval_f, fill_value=np.nan) + + sk_fill = 12*np.sqrt(6)*_ZETA3/np.pi**3 + args = (g1, g2, g3, g2mg12) + sk = xpx.apply_where(abs(c) > eps**0.29, (c, *args), + sk1_eval, fill_value=sk_fill) + + # kurtosis + def ku1_eval(c, *args): + def ku1_eval_f(g1, g2, g3, g4, g2mg12): + return (g4 + (-4*g3 + 3*(g2 + g2mg12)*g1)*g1)/g2mg12**2 - 3 + return xpx.apply_where(c >= -1./4, args, ku1_eval_f, fill_value=np.nan) + + args = (g1, g2, g3, g4, g2mg12) + ku = xpx.apply_where(abs(c) > eps**0.23, (c, *args), + ku1_eval, fill_value=12.0/5.0) + + return m, v, sk, ku + + def _fitstart(self, data): + if isinstance(data, CensoredData): + data = data._uncensor() + # This is better than the default shape of (1,). + g = _skew(data) + if g < 0: + a = 0.5 + else: + a = -0.5 + return super()._fitstart(data, args=(a,)) + + def _munp(self, n, c): + k = np.arange(0, n+1) + k = np.reshape(k, (-1,) + (1,)*c.ndim) + vals = 1.0/c**n * np.sum( + sc.comb(n, k) * (-1)**k * sc.gamma(c*k + 1), + axis=0) + return np.where(c*n > -1, vals, np.inf) + + def _entropy(self, c): + return _EULER*(1 - c) + 1 + + +genextreme = genextreme_gen(name='genextreme') + + +def _digammainv(y): + """Inverse of the digamma function (real positive arguments only). + + This function is used in the `fit` method of `gamma_gen`. + The function uses either optimize.fsolve or optimize.newton + to solve `sc.digamma(x) - y = 0`. There is probably room for + improvement, but currently it works over a wide range of y: + + >>> import numpy as np + >>> rng = np.random.default_rng() + >>> y = 64*rng.standard_normal(1000000) + >>> y.min(), y.max() + (-311.43592651416662, 351.77388222276869) + >>> x = [_digammainv(t) for t in y] + >>> np.abs(sc.digamma(x) - y).max() + 1.1368683772161603e-13 + + """ + _em = 0.5772156649015328606065120 + + def func(x): + return sc.digamma(x) - y + + if y > -0.125: + x0 = np.exp(y) + 0.5 + if y < 10: + # Some experimentation shows that newton reliably converges + # must faster than fsolve in this y range. For larger y, + # newton sometimes fails to converge. + value = optimize.newton(func, x0, tol=1e-10) + return value + elif y > -3: + x0 = np.exp(y/2.332) + 0.08661 + else: + x0 = 1.0 / (-y - _em) + + value, info, ier, mesg = optimize.fsolve(func, x0, xtol=1e-11, + full_output=True) + if ier != 1: + raise RuntimeError(f"_digammainv: fsolve failed, y = {y!r}") + + return value[0] + + +## Gamma (Use MATLAB and MATHEMATICA (b=theta=scale, a=alpha=shape) definition) + +## gamma(a, loc, scale) with a an integer is the Erlang distribution +## gamma(1, loc, scale) is the Exponential distribution +## gamma(df/2, 0, 2) is the chi2 distribution with df degrees of freedom. + +class gamma_gen(rv_continuous): + r"""A gamma continuous random variable. + + %(before_notes)s + + See Also + -------- + erlang, expon + + Notes + ----- + The probability density function for `gamma` is: + + .. math:: + + f(x, a) = \frac{x^{a-1} e^{-x}}{\Gamma(a)} + + for :math:`x \ge 0`, :math:`a > 0`. Here :math:`\Gamma(a)` refers to the + gamma function. + + `gamma` takes ``a`` as a shape parameter for :math:`a`. + + When :math:`a` is an integer, `gamma` reduces to the Erlang + distribution, and when :math:`a=1` to the exponential distribution. + + Gamma distributions are sometimes parameterized with two variables, + with a probability density function of: + + .. math:: + + f(x, \alpha, \beta) = + \frac{\beta^\alpha x^{\alpha - 1} e^{-\beta x }}{\Gamma(\alpha)} + + Note that this parameterization is equivalent to the above, with + ``scale = 1 / beta``. + + %(after_notes)s + + %(example)s + + """ + def _shape_info(self): + return [_ShapeInfo("a", False, (0, np.inf), (False, False))] + + def _rvs(self, a, size=None, random_state=None): + return random_state.standard_gamma(a, size) + + def _pdf(self, x, a): + # gamma.pdf(x, a) = x**(a-1) * exp(-x) / gamma(a) + return np.exp(self._logpdf(x, a)) + + def _logpdf(self, x, a): + return sc.xlogy(a-1.0, x) - x - sc.gammaln(a) + + def _cdf(self, x, a): + return sc.gammainc(a, x) + + def _sf(self, x, a): + return sc.gammaincc(a, x) + + def _ppf(self, q, a): + return sc.gammaincinv(a, q) + + def _isf(self, q, a): + return sc.gammainccinv(a, q) + + def _stats(self, a): + return a, a, 2.0/np.sqrt(a), 6.0/a + + def _munp(self, n, a): + return sc.poch(a, n) + + def _entropy(self, a): + + def regular_formula(a): + return sc.psi(a) * (1-a) + a + sc.gammaln(a) + + def asymptotic_formula(a): + # plug in above formula the expansions: + # psi(a) ~ ln(a) - 1/2a - 1/12a^2 + 1/120a^4 + # gammaln(a) ~ a * ln(a) - a - 1/2 * ln(a) + 1/2 ln(2 * pi) + + # 1/12a - 1/360a^3 + return (0.5 * (1. + np.log(2*np.pi) + np.log(a)) - 1/(3 * a) + - (a**-2.)/12 - (a**-3.)/90 + (a**-4.)/120) + + return xpx.apply_where(a < 250, a, regular_formula, asymptotic_formula) + + def _fitstart(self, data): + # The skewness of the gamma distribution is `2 / np.sqrt(a)`. + # We invert that to estimate the shape `a` using the skewness + # of the data. The formula is regularized with 1e-8 in the + # denominator to allow for degenerate data where the skewness + # is close to 0. + if isinstance(data, CensoredData): + data = data._uncensor() + sk = _skew(data) + a = 4 / (1e-8 + sk**2) + return super()._fitstart(data, args=(a,)) + + @extend_notes_in_docstring(rv_continuous, notes="""\ + When the location is fixed by using the argument `floc` + and `method='MLE'`, this + function uses explicit formulas or solves a simpler numerical + problem than the full ML optimization problem. So in that case, + the `optimizer`, `loc` and `scale` arguments are ignored. + \n\n""") + def fit(self, data, *args, **kwds): + floc = kwds.get('floc', None) + method = kwds.get('method', 'mle') + + if (isinstance(data, CensoredData) or + floc is None and method.lower() != 'mm'): + # loc is not fixed or we're not doing standard MLE. + # Use the default fit method. + return super().fit(data, *args, **kwds) + + # We already have this value, so just pop it from kwds. + kwds.pop('floc', None) + + f0 = _get_fixed_fit_value(kwds, ['f0', 'fa', 'fix_a']) + fscale = kwds.pop('fscale', None) + + _remove_optimizer_parameters(kwds) + + if f0 is not None and floc is not None and fscale is not None: + # This check is for consistency with `rv_continuous.fit`. + # Without this check, this function would just return the + # parameters that were given. + raise ValueError("All parameters fixed. There is nothing to " + "optimize.") + + # Fixed location is handled by shifting the data. + data = np.asarray(data) + + if not np.isfinite(data).all(): + raise ValueError("The data contains non-finite values.") + + # Use explicit formulas for mm (gh-19884) + if method.lower() == 'mm': + m1 = np.mean(data) + m2 = np.var(data) + m3 = np.mean((data - m1) ** 3) + a, loc, scale = f0, floc, fscale + # Three unknowns + if a is None and loc is None and scale is None: + scale = m3 / (2 * m2) + # Two unknowns + if loc is None and scale is None: + scale = np.sqrt(m2 / a) + if a is None and scale is None: + scale = m2 / (m1 - loc) + if a is None and loc is None: + a = m2 / (scale ** 2) + # One unknown + if a is None: + a = (m1 - loc) / scale + if loc is None: + loc = m1 - a * scale + if scale is None: + scale = (m1 - loc) / a + return a, loc, scale + + # Special case: loc is fixed. + + # NB: data == loc is ok if a >= 1; the below check is more strict. + if np.any(data <= floc): + raise FitDataError("gamma", lower=floc, upper=np.inf) + + if floc != 0: + # Don't do the subtraction in-place, because `data` might be a + # view of the input array. + data = data - floc + xbar = data.mean() + + # Three cases to handle: + # * shape and scale both free + # * shape fixed, scale free + # * shape free, scale fixed + + if fscale is None: + # scale is free + if f0 is not None: + # shape is fixed + a = f0 + else: + # shape and scale are both free. + # The MLE for the shape parameter `a` is the solution to: + # np.log(a) - sc.digamma(a) - np.log(xbar) + + # np.log(data).mean() = 0 + s = np.log(xbar) - np.log(data).mean() + aest = (3-s + np.sqrt((s-3)**2 + 24*s)) / (12*s) + xa = aest*(1-0.4) + xb = aest*(1+0.4) + a = optimize.brentq(lambda a: np.log(a) - sc.digamma(a) - s, + xa, xb, disp=0) + + # The MLE for the scale parameter is just the data mean + # divided by the shape parameter. + scale = xbar / a + else: + # scale is fixed, shape is free + # The MLE for the shape parameter `a` is the solution to: + # sc.digamma(a) - np.log(data).mean() + np.log(fscale) = 0 + c = np.log(data).mean() - np.log(fscale) + a = _digammainv(c) + scale = fscale + + return a, floc, scale + + +gamma = gamma_gen(a=0.0, name='gamma') + + +class erlang_gen(gamma_gen): + """An Erlang continuous random variable. + + %(before_notes)s + + See Also + -------- + gamma + + Notes + ----- + The Erlang distribution is a special case of the Gamma distribution, with + the shape parameter `a` an integer. Note that this restriction is not + enforced by `erlang`. It will, however, generate a warning the first time + a non-integer value is used for the shape parameter. + + Refer to `gamma` for examples. + + """ + + def _argcheck(self, a): + allint = np.all(np.floor(a) == a) + if not allint: + # An Erlang distribution shouldn't really have a non-integer + # shape parameter, so warn the user. + message = ('The shape parameter of the erlang distribution ' + f'has been given a non-integer value {a!r}.') + warnings.warn(message, RuntimeWarning, stacklevel=3) + return a > 0 + + def _shape_info(self): + return [_ShapeInfo("a", True, (1, np.inf), (True, False))] + + def _fitstart(self, data): + # Override gamma_gen_fitstart so that an integer initial value is + # used. (Also regularize the division, to avoid issues when + # _skew(data) is 0 or close to 0.) + if isinstance(data, CensoredData): + data = data._uncensor() + a = int(4.0 / (1e-8 + _skew(data)**2)) + return super(gamma_gen, self)._fitstart(data, args=(a,)) + + # Trivial override of the fit method, so we can monkey-patch its + # docstring. + @extend_notes_in_docstring(rv_continuous, notes="""\ + The Erlang distribution is generally defined to have integer values + for the shape parameter. This is not enforced by the `erlang` class. + When fitting the distribution, it will generally return a non-integer + value for the shape parameter. By using the keyword argument + `f0=`, the fit method can be constrained to fit the data to + a specific integer shape parameter.""") + def fit(self, data, *args, **kwds): + return super().fit(data, *args, **kwds) + + +erlang = erlang_gen(a=0.0, name='erlang') + + +class gengamma_gen(rv_continuous): + r"""A generalized gamma continuous random variable. + + %(before_notes)s + + See Also + -------- + gamma, halfgennorm, invgamma, weibull_min + + Notes + ----- + The probability density function for `gengamma` is ([1]_, [2]_): + + .. math:: + + f(x, a, c) = \frac{|c| x^{c a-1} \exp(-x^c)}{\Gamma(a)} + + for :math:`x \ge 0`, :math:`a > 0`, and :math:`c \ne 0`. + :math:`\Gamma` is the gamma function (`scipy.special.gamma`). + + `gengamma` takes :math:`a` and :math:`c` as shape parameters. + + The SciPy distribution `halfgennorm` is a special case of + `gengamma`: ``halfgennorm(beta) = gengamma(a=1/beta, c=beta)``. + + %(after_notes)s + + References + ---------- + .. [1] E.W. Stacy, "A Generalization of the Gamma Distribution", + Annals of Mathematical Statistics, Vol 33(3), pp. 1187--1192. + .. [2] "Generalized gamma distribution", Wikipedia, + https://en.wikipedia.org/wiki/Generalized_gamma_distribution + + %(example)s + + """ + def _argcheck(self, a, c): + return (a > 0) & (c != 0) + + def _shape_info(self): + ia = _ShapeInfo("a", False, (0, np.inf), (False, False)) + ic = _ShapeInfo("c", False, (-np.inf, np.inf), (False, False)) + return [ia, ic] + + def _pdf(self, x, a, c): + return np.exp(self._logpdf(x, a, c)) + + def _logpdf(self, x, a, c): + return xpx.apply_where( + (x != 0) | (c > 0), (x, c, a), + lambda x, c, a: (np.log(abs(c)) + sc.xlogy(c*a - 1, x) + - x**c - sc.gammaln(a)), + fill_value=-np.inf) + + def _cdf(self, x, a, c): + xc = x**c + val1 = sc.gammainc(a, xc) + val2 = sc.gammaincc(a, xc) + return np.where(c > 0, val1, val2) + + def _rvs(self, a, c, size=None, random_state=None): + r = random_state.standard_gamma(a, size=size) + return r**(1./c) + + def _sf(self, x, a, c): + xc = x**c + val1 = sc.gammainc(a, xc) + val2 = sc.gammaincc(a, xc) + return np.where(c > 0, val2, val1) + + def _ppf(self, q, a, c): + val1 = sc.gammaincinv(a, q) + val2 = sc.gammainccinv(a, q) + return np.where(c > 0, val1, val2)**(1.0/c) + + def _isf(self, q, a, c): + val1 = sc.gammaincinv(a, q) + val2 = sc.gammainccinv(a, q) + return np.where(c > 0, val2, val1)**(1.0/c) + + def _munp(self, n, a, c): + # Pochhammer symbol: sc.pocha,n) = gamma(a+n)/gamma(a) + return sc.poch(a, n*1.0/c) + + def _entropy(self, a, c): + def regular(a, c): + val = sc.psi(a) + A = a * (1 - val) + val / c + B = sc.gammaln(a) - np.log(abs(c)) + h = A + B + return h + + def asymptotic(a, c): + # using asymptotic expansions for gammaln and psi (see gh-18093) + return (norm._entropy() - np.log(a)/2 + - np.log(np.abs(c)) + (a**-1.)/6 - (a**-3.)/90 + + (np.log(a) - (a**-1.)/2 - (a**-2.)/12 + (a**-4.)/120)/c) + + return xpx.apply_where(a >= 200, (a, c), asymptotic, regular) + + +gengamma = gengamma_gen(a=0.0, name='gengamma') + + +class genhalflogistic_gen(rv_continuous): + r"""A generalized half-logistic continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `genhalflogistic` is: + + .. math:: + + f(x, c) = \frac{2 (1 - c x)^{1/(c-1)}}{[1 + (1 - c x)^{1/c}]^2} + + for :math:`0 \le x \le 1/c`, and :math:`c > 0`. + + `genhalflogistic` takes ``c`` as a shape parameter for :math:`c`. + + %(after_notes)s + + %(example)s + + """ + def _shape_info(self): + return [_ShapeInfo("c", False, (0, np.inf), (False, False))] + + def _get_support(self, c): + return self.a, 1.0/c + + def _pdf(self, x, c): + # genhalflogistic.pdf(x, c) = + # 2 * (1-c*x)**(1/c-1) / (1+(1-c*x)**(1/c))**2 + limit = 1.0/c + tmp = np.asarray(1-c*x) + tmp0 = tmp**(limit-1) + tmp2 = tmp0*tmp + return 2*tmp0 / (1+tmp2)**2 + + def _cdf(self, x, c): + limit = 1.0/c + tmp = np.asarray(1-c*x) + tmp2 = tmp**(limit) + return (1.0-tmp2) / (1+tmp2) + + def _ppf(self, q, c): + return 1.0/c*(1-((1.0-q)/(1.0+q))**c) + + def _entropy(self, c): + return 2 - (2*c+1)*np.log(2) + + +genhalflogistic = genhalflogistic_gen(a=0.0, name='genhalflogistic') + + +class genhyperbolic_gen(rv_continuous): + r"""A generalized hyperbolic continuous random variable. + + %(before_notes)s + + See Also + -------- + t, norminvgauss, geninvgauss, laplace, cauchy + + Notes + ----- + The probability density function for `genhyperbolic` is: + + .. math:: + + f(x, p, a, b) = + \frac{(a^2 - b^2)^{p/2}} + {\sqrt{2\pi}a^{p-1/2} + K_p\Big(\sqrt{a^2 - b^2}\Big)} + e^{bx} \times \frac{K_{p - 1/2} + (a \sqrt{1 + x^2})} + {(\sqrt{1 + x^2})^{1/2 - p}} + + for :math:`x, p \in ( - \infty; \infty)`, + :math:`|b| < a` if :math:`p \ge 0`, + :math:`|b| \le a` if :math:`p < 0`. + :math:`K_{p}(.)` denotes the modified Bessel function of the second + kind and order :math:`p` (`scipy.special.kv`) + + `genhyperbolic` takes ``p`` as a tail parameter, + ``a`` as a shape parameter, + ``b`` as a skewness parameter. + + %(after_notes)s + + The original parameterization of the Generalized Hyperbolic Distribution + is found in [1]_ as follows + + .. math:: + + f(x, \lambda, \alpha, \beta, \delta, \mu) = + \frac{(\gamma/\delta)^\lambda}{\sqrt{2\pi}K_\lambda(\delta \gamma)} + e^{\beta (x - \mu)} \times \frac{K_{\lambda - 1/2} + (\alpha \sqrt{\delta^2 + (x - \mu)^2})} + {(\sqrt{\delta^2 + (x - \mu)^2} / \alpha)^{1/2 - \lambda}} + + for :math:`x \in ( - \infty; \infty)`, + :math:`\gamma := \sqrt{\alpha^2 - \beta^2}`, + :math:`\lambda, \mu \in ( - \infty; \infty)`, + :math:`\delta \ge 0, |\beta| < \alpha` if :math:`\lambda \ge 0`, + :math:`\delta > 0, |\beta| \le \alpha` if :math:`\lambda < 0`. + + The location-scale-based parameterization implemented in + SciPy is based on [2]_, where :math:`a = \alpha\delta`, + :math:`b = \beta\delta`, :math:`p = \lambda`, + :math:`scale=\delta` and :math:`loc=\mu` + + Moments are implemented based on [3]_ and [4]_. + + For the distributions that are a special case such as Student's t, + it is not recommended to rely on the implementation of genhyperbolic. + To avoid potential numerical problems and for performance reasons, + the methods of the specific distributions should be used. + + References + ---------- + .. [1] O. Barndorff-Nielsen, "Hyperbolic Distributions and Distributions + on Hyperbolae", Scandinavian Journal of Statistics, Vol. 5(3), + pp. 151-157, 1978. https://www.jstor.org/stable/4615705 + + .. [2] Eberlein E., Prause K. (2002) The Generalized Hyperbolic Model: + Financial Derivatives and Risk Measures. In: Geman H., Madan D., + Pliska S.R., Vorst T. (eds) Mathematical Finance - Bachelier + Congress 2000. Springer Finance. Springer, Berlin, Heidelberg. + :doi:`10.1007/978-3-662-12429-1_12` + + .. [3] Scott, David J, Würtz, Diethelm, Dong, Christine and Tran, + Thanh Tam, (2009), Moments of the generalized hyperbolic + distribution, MPRA Paper, University Library of Munich, Germany, + https://EconPapers.repec.org/RePEc:pra:mprapa:19081. + + .. [4] E. Eberlein and E. A. von Hammerstein. Generalized hyperbolic + and inverse Gaussian distributions: Limiting cases and approximation + of processes. FDM Preprint 80, April 2003. University of Freiburg. + https://freidok.uni-freiburg.de/fedora/objects/freidok:7974/datastreams/FILE1/content + + %(example)s + + """ + + def _argcheck(self, p, a, b): + return (np.logical_and(np.abs(b) < a, p >= 0) + | np.logical_and(np.abs(b) <= a, p < 0)) + + def _shape_info(self): + ip = _ShapeInfo("p", False, (-np.inf, np.inf), (False, False)) + ia = _ShapeInfo("a", False, (0, np.inf), (True, False)) + ib = _ShapeInfo("b", False, (-np.inf, np.inf), (False, False)) + return [ip, ia, ib] + + def _fitstart(self, data): + # Arbitrary, but the default p = a = b = 1 is not valid; the + # distribution requires |b| < a if p >= 0. + return super()._fitstart(data, args=(1, 1, 0.5)) + + def _logpdf(self, x, p, a, b): + # kve instead of kv works better for large values of p + # and smaller values of sqrt(a^2 - b^2) + @np.vectorize + def _logpdf_single(x, p, a, b): + return _stats.genhyperbolic_logpdf(x, p, a, b) + + return _logpdf_single(x, p, a, b) + + def _pdf(self, x, p, a, b): + # kve instead of kv works better for large values of p + # and smaller values of sqrt(a^2 - b^2) + @np.vectorize + def _pdf_single(x, p, a, b): + return _stats.genhyperbolic_pdf(x, p, a, b) + + return _pdf_single(x, p, a, b) + + # np.vectorize isn't currently designed to be used as a decorator, + # so use a lambda instead. This allows us to decorate the function + # with `np.vectorize` and still provide the `otypes` parameter. + @lambda func: np.vectorize(func, otypes=[np.float64]) + @staticmethod + def _integrate_pdf(x0, x1, p, a, b): + """ + Integrate the pdf of the genhyberbolic distribution from x0 to x1. + This is a private function used by _cdf() and _sf() only; either x0 + will be -inf or x1 will be inf. + """ + user_data = np.array([p, a, b], float).ctypes.data_as(ctypes.c_void_p) + llc = LowLevelCallable.from_cython(_stats, '_genhyperbolic_pdf', + user_data) + d = np.sqrt((a + b)*(a - b)) + mean = b/d * sc.kv(p + 1, d) / sc.kv(p, d) + epsrel = 1e-10 + epsabs = 0 + if x0 < mean < x1: + # If the interval includes the mean, integrate over the two + # intervals [x0, mean] and [mean, x1] and add. If we try to do + # the integral in one call of quad and the non-infinite endpoint + # is far in the tail, quad might return an incorrect result + # because it does not "see" the peak of the PDF. + intgrl = (integrate.quad(llc, x0, mean, + epsrel=epsrel, epsabs=epsabs)[0] + + integrate.quad(llc, mean, x1, + epsrel=epsrel, epsabs=epsabs)[0]) + else: + intgrl = integrate.quad(llc, x0, x1, + epsrel=epsrel, epsabs=epsabs)[0] + if np.isnan(intgrl): + msg = ("Infinite values encountered in scipy.special.kve. " + "Values replaced by NaN to avoid incorrect results.") + warnings.warn(msg, RuntimeWarning, stacklevel=3) + return max(0.0, min(1.0, intgrl)) + + def _cdf(self, x, p, a, b): + return self._integrate_pdf(-np.inf, x, p, a, b) + + def _sf(self, x, p, a, b): + return self._integrate_pdf(x, np.inf, p, a, b) + + def _rvs(self, p, a, b, size=None, random_state=None): + # note: X = b * V + sqrt(V) * X has a + # generalized hyperbolic distribution + # if X is standard normal and V is + # geninvgauss(p = p, b = t2, loc = loc, scale = t3) + t1 = np.float_power(a, 2) - np.float_power(b, 2) + # b in the GIG + t2 = np.float_power(t1, 0.5) + # scale in the GIG + t3 = np.float_power(t1, - 0.5) + gig = geninvgauss.rvs( + p=p, + b=t2, + scale=t3, + size=size, + random_state=random_state + ) + normst = norm.rvs(size=size, random_state=random_state) + + return b * gig + np.sqrt(gig) * normst + + def _stats(self, p, a, b): + # https://mpra.ub.uni-muenchen.de/19081/1/MPRA_paper_19081.pdf + # https://freidok.uni-freiburg.de/fedora/objects/freidok:7974/datastreams/FILE1/content + # standardized moments + p, a, b = np.broadcast_arrays(p, a, b) + t1 = np.float_power(a, 2) - np.float_power(b, 2) + t1 = np.float_power(t1, 0.5) + t2 = np.float_power(1, 2) * np.float_power(t1, - 1) + integers = np.linspace(0, 4, 5) + # make integers perpendicular to existing dimensions + integers = integers.reshape(integers.shape + (1,) * p.ndim) + b0, b1, b2, b3, b4 = sc.kv(p + integers, t1) + r1, r2, r3, r4 = (b / b0 for b in (b1, b2, b3, b4)) + + m = b * t2 * r1 + v = ( + t2 * r1 + np.float_power(b, 2) * np.float_power(t2, 2) * + (r2 - np.float_power(r1, 2)) + ) + m3e = ( + np.float_power(b, 3) * np.float_power(t2, 3) * + (r3 - 3 * b2 * b1 * np.float_power(b0, -2) + + 2 * np.float_power(r1, 3)) + + 3 * b * np.float_power(t2, 2) * + (r2 - np.float_power(r1, 2)) + ) + s = m3e * np.float_power(v, - 3 / 2) + m4e = ( + np.float_power(b, 4) * np.float_power(t2, 4) * + (r4 - 4 * b3 * b1 * np.float_power(b0, - 2) + + 6 * b2 * np.float_power(b1, 2) * np.float_power(b0, - 3) - + 3 * np.float_power(r1, 4)) + + np.float_power(b, 2) * np.float_power(t2, 3) * + (6 * r3 - 12 * b2 * b1 * np.float_power(b0, - 2) + + 6 * np.float_power(r1, 3)) + + 3 * np.float_power(t2, 2) * r2 + ) + k = m4e * np.float_power(v, -2) - 3 + + return m, v, s, k + + +genhyperbolic = genhyperbolic_gen(name='genhyperbolic') + + +class gompertz_gen(rv_continuous): + r"""A Gompertz (or truncated Gumbel) continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `gompertz` is: + + .. math:: + + f(x, c) = c \exp(x) \exp(-c (e^x-1)) + + for :math:`x \ge 0`, :math:`c > 0`. + + `gompertz` takes ``c`` as a shape parameter for :math:`c`. + + %(after_notes)s + + %(example)s + + """ + def _shape_info(self): + return [_ShapeInfo("c", False, (0, np.inf), (False, False))] + + def _pdf(self, x, c): + # gompertz.pdf(x, c) = c * exp(x) * exp(-c*(exp(x)-1)) + return np.exp(self._logpdf(x, c)) + + def _logpdf(self, x, c): + return np.log(c) + x - c * sc.expm1(x) + + def _cdf(self, x, c): + return -sc.expm1(-c * sc.expm1(x)) + + def _ppf(self, q, c): + return sc.log1p(-1.0 / c * sc.log1p(-q)) + + def _sf(self, x, c): + return np.exp(-c * sc.expm1(x)) + + def _isf(self, p, c): + return sc.log1p(-np.log(p)/c) + + def _entropy(self, c): + return 1.0 - np.log(c) - sc._ufuncs._scaled_exp1(c)/c + + +gompertz = gompertz_gen(a=0.0, name='gompertz') + + +def _average_with_log_weights(x, logweights): + x = np.asarray(x) + logweights = np.asarray(logweights) + maxlogw = logweights.max() + weights = np.exp(logweights - maxlogw) + return np.average(x, weights=weights) + + +class gumbel_r_gen(rv_continuous): + r"""A right-skewed Gumbel continuous random variable. + + %(before_notes)s + + See Also + -------- + gumbel_l, gompertz, genextreme + + Notes + ----- + The probability density function for `gumbel_r` is: + + .. math:: + + f(x) = \exp(-(x + e^{-x})) + + for real :math:`x`. + + The Gumbel distribution is sometimes referred to as a type I Fisher-Tippett + distribution. It is also related to the extreme value distribution, + log-Weibull and Gompertz distributions. + + %(after_notes)s + + %(example)s + + """ + def _shape_info(self): + return [] + + def _pdf(self, x): + # gumbel_r.pdf(x) = exp(-(x + exp(-x))) + return np.exp(self._logpdf(x)) + + def _logpdf(self, x): + return -x - np.exp(-x) + + def _cdf(self, x): + return np.exp(-np.exp(-x)) + + def _logcdf(self, x): + return -np.exp(-x) + + def _ppf(self, q): + return -np.log(-np.log(q)) + + def _sf(self, x): + return -sc.expm1(-np.exp(-x)) + + def _isf(self, p): + return -np.log(-np.log1p(-p)) + + def _stats(self): + return _EULER, np.pi*np.pi/6.0, 12*np.sqrt(6)/np.pi**3 * _ZETA3, 12.0/5 + + def _entropy(self): + # https://en.wikipedia.org/wiki/Gumbel_distribution + return _EULER + 1. + + @_call_super_mom + @inherit_docstring_from(rv_continuous) + def fit(self, data, *args, **kwds): + data, floc, fscale = _check_fit_input_parameters(self, data, + args, kwds) + + # By the method of maximum likelihood, the estimators of the + # location and scale are the roots of the equations defined in + # `func` and the value of the expression for `loc` that follows. + # The first `func` is a first order derivative of the log-likelihood + # equation and the second is from Source: Statistical Distributions, + # 3rd Edition. Evans, Hastings, and Peacock (2000), Page 101. + + def get_loc_from_scale(scale): + return -scale * (sc.logsumexp(-data / scale) - np.log(len(data))) + + if fscale is not None: + # if the scale is fixed, the location can be analytically + # determined. + scale = fscale + loc = get_loc_from_scale(scale) + else: + # A different function is solved depending on whether the location + # is fixed. + if floc is not None: + loc = floc + + # equation to use if the location is fixed. + # note that one cannot use the equation in Evans, Hastings, + # and Peacock (2000) (since it assumes that the derivative + # w.r.t. the log-likelihood is zero). however, it is easy to + # derive the MLE condition directly if loc is fixed + def func(scale): + term1 = (loc - data) * np.exp((loc - data) / scale) + data + term2 = len(data) * (loc + scale) + return term1.sum() - term2 + else: + + # equation to use if both location and scale are free + def func(scale): + sdata = -data / scale + wavg = _average_with_log_weights(data, logweights=sdata) + return data.mean() - wavg - scale + + # set brackets for `root_scalar` to use when optimizing over the + # scale such that a root is likely between them. Use user supplied + # guess or default 1. + brack_start = kwds.get('scale', 1) + lbrack, rbrack = brack_start / 2, brack_start * 2 + + # if a root is not between the brackets, iteratively expand them + # until they include a sign change, checking after each bracket is + # modified. + def interval_contains_root(lbrack, rbrack): + # return true if the signs disagree. + return (np.sign(func(lbrack)) != + np.sign(func(rbrack))) + while (not interval_contains_root(lbrack, rbrack) + and (lbrack > 0 or rbrack < np.inf)): + lbrack /= 2 + rbrack *= 2 + + res = optimize.root_scalar(func, bracket=(lbrack, rbrack), + rtol=1e-14, xtol=1e-14) + scale = res.root + loc = floc if floc is not None else get_loc_from_scale(scale) + return loc, scale + + +gumbel_r = gumbel_r_gen(name='gumbel_r') + + +class gumbel_l_gen(rv_continuous): + r"""A left-skewed Gumbel continuous random variable. + + %(before_notes)s + + See Also + -------- + gumbel_r, gompertz, genextreme + + Notes + ----- + The probability density function for `gumbel_l` is: + + .. math:: + + f(x) = \exp(x - e^x) + + for real :math:`x`. + + The Gumbel distribution is sometimes referred to as a type I Fisher-Tippett + distribution. It is also related to the extreme value distribution, + log-Weibull and Gompertz distributions. + + %(after_notes)s + + %(example)s + + """ + + def _shape_info(self): + return [] + + def _pdf(self, x): + # gumbel_l.pdf(x) = exp(x - exp(x)) + return np.exp(self._logpdf(x)) + + def _logpdf(self, x): + return x - np.exp(x) + + def _cdf(self, x): + return -sc.expm1(-np.exp(x)) + + def _ppf(self, q): + return np.log(-sc.log1p(-q)) + + def _logsf(self, x): + return -np.exp(x) + + def _sf(self, x): + return np.exp(-np.exp(x)) + + def _isf(self, x): + return np.log(-np.log(x)) + + def _stats(self): + return -_EULER, np.pi*np.pi/6.0, \ + -12*np.sqrt(6)/np.pi**3 * _ZETA3, 12.0/5 + + def _entropy(self): + return _EULER + 1. + + @_call_super_mom + @inherit_docstring_from(rv_continuous) + def fit(self, data, *args, **kwds): + # The fit method of `gumbel_r` can be used for this distribution with + # small modifications. The process to do this is + # 1. pass the sign negated data into `gumbel_r.fit` + # - if the location is fixed, it should also be negated. + # 2. negate the sign of the resulting location, leaving the scale + # unmodified. + # `gumbel_r.fit` holds necessary input checks. + + if kwds.get('floc') is not None: + kwds['floc'] = -kwds['floc'] + loc_r, scale_r, = gumbel_r.fit(-np.asarray(data), *args, **kwds) + return -loc_r, scale_r + + +gumbel_l = gumbel_l_gen(name='gumbel_l') + + +class halfcauchy_gen(rv_continuous): + r"""A Half-Cauchy continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `halfcauchy` is: + + .. math:: + + f(x) = \frac{2}{\pi (1 + x^2)} + + for :math:`x \ge 0`. + + %(after_notes)s + + %(example)s + + """ + def _shape_info(self): + return [] + + def _pdf(self, x): + # halfcauchy.pdf(x) = 2 / (pi * (1 + x**2)) + return 2.0/np.pi/(1.0+x*x) + + def _logpdf(self, x): + return np.log(2.0/np.pi) - sc.log1p(x*x) + + def _cdf(self, x): + return 2.0/np.pi*np.arctan(x) + + def _ppf(self, q): + return np.tan(np.pi/2*q) + + def _sf(self, x): + return 2.0/np.pi * np.arctan2(1, x) + + def _isf(self, p): + return 1.0/np.tan(np.pi*p/2) + + def _stats(self): + return np.inf, np.inf, np.nan, np.nan + + def _entropy(self): + return np.log(2*np.pi) + + @_call_super_mom + @inherit_docstring_from(rv_continuous) + def fit(self, data, *args, **kwds): + if kwds.pop('superfit', False): + return super().fit(data, *args, **kwds) + + data, floc, fscale = _check_fit_input_parameters(self, data, + args, kwds) + + # location is independent from the scale + data_min = np.min(data) + if floc is not None: + if data_min < floc: + # There are values that are less than the specified loc. + raise FitDataError("halfcauchy", lower=floc, upper=np.inf) + loc = floc + else: + # if not provided, location MLE is the minimal data point + loc = data_min + + # find scale + def find_scale(loc, data): + shifted_data = data - loc + n = data.size + shifted_data_squared = np.square(shifted_data) + + def fun_to_solve(scale): + denominator = scale**2 + shifted_data_squared + return 2 * np.sum(shifted_data_squared/denominator) - n + + small = np.finfo(1.0).tiny**0.5 # avoid underflow + res = root_scalar(fun_to_solve, bracket=(small, np.max(shifted_data))) + return res.root + + if fscale is not None: + scale = fscale + else: + scale = find_scale(loc, data) + + return loc, scale + + +halfcauchy = halfcauchy_gen(a=0.0, name='halfcauchy') + + +class halflogistic_gen(rv_continuous): + r"""A half-logistic continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `halflogistic` is: + + .. math:: + + f(x) = \frac{ 2 e^{-x} }{ (1+e^{-x})^2 } + = \frac{1}{2} \text{sech}(x/2)^2 + + for :math:`x \ge 0`. + + %(after_notes)s + + References + ---------- + .. [1] Asgharzadeh et al (2011). "Comparisons of Methods of Estimation for the + Half-Logistic Distribution". Selcuk J. Appl. Math. 93-108. + + %(example)s + + """ + def _shape_info(self): + return [] + + def _pdf(self, x): + # halflogistic.pdf(x) = 2 * exp(-x) / (1+exp(-x))**2 + # = 1/2 * sech(x/2)**2 + return np.exp(self._logpdf(x)) + + def _logpdf(self, x): + return np.log(2) - x - 2. * sc.log1p(np.exp(-x)) + + def _cdf(self, x): + return np.tanh(x/2.0) + + def _ppf(self, q): + return 2*np.arctanh(q) + + def _sf(self, x): + return 2 * sc.expit(-x) + + def _isf(self, q): + return xpx.apply_where(q < 0.5, q, + lambda q: -sc.logit(0.5 * q), + lambda q: 2*np.arctanh(1 - q)) + + def _munp(self, n): + if n == 0: + return 1 # otherwise returns NaN + if n == 1: + return 2*np.log(2) + if n == 2: + return np.pi*np.pi/3.0 + if n == 3: + return 9*_ZETA3 + if n == 4: + return 7*np.pi**4 / 15.0 + return 2*(1-pow(2.0, 1-n))*sc.gamma(n+1)*sc.zeta(n, 1) + + def _entropy(self): + return 2-np.log(2) + + @_call_super_mom + @inherit_docstring_from(rv_continuous) + def fit(self, data, *args, **kwds): + if kwds.pop('superfit', False): + return super().fit(data, *args, **kwds) + + data, floc, fscale = _check_fit_input_parameters(self, data, + args, kwds) + + def find_scale(data, loc): + # scale is solution to a fix point problem ([1] 2.6) + # use approximate MLE as starting point ([1] 3.1) + n_observations = data.shape[0] + sorted_data = np.sort(data, axis=0) + p = np.arange(1, n_observations + 1)/(n_observations + 1) + q = 1 - p + pp1 = 1 + p + alpha = p - 0.5 * q * pp1 * np.log(pp1 / q) + beta = 0.5 * q * pp1 + sorted_data = sorted_data - loc + B = 2 * np.sum(alpha[1:] * sorted_data[1:]) + C = 2 * np.sum(beta[1:] * sorted_data[1:]**2) + # starting guess + scale = ((B + np.sqrt(B**2 + 8 * n_observations * C)) + /(4 * n_observations)) + + # relative tolerance of fix point iterator + rtol = 1e-8 + relative_residual = 1 + shifted_mean = sorted_data.mean() # y_mean - y_min + + # find fix point by repeated application of eq. (2.6) + # simplify as + # exp(-x) / (1 + exp(-x)) = 1 / (1 + exp(x)) + # = expit(-x)) + while relative_residual > rtol: + sum_term = sorted_data * sc.expit(-sorted_data/scale) + scale_new = shifted_mean - 2/n_observations * sum_term.sum() + relative_residual = abs((scale - scale_new)/scale) + scale = scale_new + return scale + + # location is independent from the scale + data_min = np.min(data) + if floc is not None: + if data_min < floc: + # There are values that are less than the specified loc. + raise FitDataError("halflogistic", lower=floc, upper=np.inf) + loc = floc + else: + # if not provided, location MLE is the minimal data point + loc = data_min + + # scale depends on location + scale = fscale if fscale is not None else find_scale(data, loc) + + return loc, scale + + +halflogistic = halflogistic_gen(a=0.0, name='halflogistic') + + +class halfnorm_gen(rv_continuous): + r"""A half-normal continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `halfnorm` is: + + .. math:: + + f(x) = \sqrt{2/\pi} \exp(-x^2 / 2) + + for :math:`x >= 0`. + + `halfnorm` is a special case of `chi` with ``df=1``. + + %(after_notes)s + + %(example)s + + """ + def _shape_info(self): + return [] + + def _rvs(self, size=None, random_state=None): + return abs(random_state.standard_normal(size=size)) + + def _pdf(self, x): + # halfnorm.pdf(x) = sqrt(2/pi) * exp(-x**2/2) + return np.sqrt(2.0/np.pi)*np.exp(-x*x/2.0) + + def _logpdf(self, x): + return 0.5 * np.log(2.0/np.pi) - x*x/2.0 + + def _cdf(self, x): + return sc.erf(x / np.sqrt(2)) + + def _ppf(self, q): + return _norm_ppf((1+q)/2.0) + + def _sf(self, x): + return 2 * _norm_sf(x) + + def _isf(self, p): + return _norm_isf(p/2) + + def _stats(self): + return (np.sqrt(2.0/np.pi), + 1-2.0/np.pi, + np.sqrt(2)*(4-np.pi)/(np.pi-2)**1.5, + 8*(np.pi-3)/(np.pi-2)**2) + + def _entropy(self): + return 0.5*np.log(np.pi/2.0)+0.5 + + @_call_super_mom + @inherit_docstring_from(rv_continuous) + def fit(self, data, *args, **kwds): + if kwds.pop('superfit', False): + return super().fit(data, *args, **kwds) + + data, floc, fscale = _check_fit_input_parameters(self, data, + args, kwds) + + data_min = np.min(data) + + if floc is not None: + if data_min < floc: + # There are values that are less than the specified loc. + raise FitDataError("halfnorm", lower=floc, upper=np.inf) + loc = floc + else: + loc = data_min + + if fscale is not None: + scale = fscale + else: + scale = stats.moment(data, order=2, center=loc)**0.5 + + return loc, scale + + +halfnorm = halfnorm_gen(a=0.0, name='halfnorm') + + +class hypsecant_gen(rv_continuous): + r"""A hyperbolic secant continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `hypsecant` is: + + .. math:: + + f(x) = \frac{1}{\pi} \text{sech}(x) + + for a real number :math:`x`. + + %(after_notes)s + + %(example)s + + """ + def _shape_info(self): + return [] + + def _pdf(self, x): + # hypsecant.pdf(x) = 1/pi * sech(x) + return 1.0/(np.pi*np.cosh(x)) + + def _cdf(self, x): + return 2.0/np.pi*np.arctan(np.exp(x)) + + def _ppf(self, q): + return np.log(np.tan(np.pi*q/2.0)) + + def _sf(self, x): + return 2.0/np.pi*np.arctan(np.exp(-x)) + + def _isf(self, q): + return -np.log(np.tan(np.pi*q/2.0)) + + def _stats(self): + return 0, np.pi*np.pi/4, 0, 2 + + def _entropy(self): + return np.log(2*np.pi) + + +hypsecant = hypsecant_gen(name='hypsecant') + + +class gausshyper_gen(rv_continuous): + r"""A Gauss hypergeometric continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `gausshyper` is: + + .. math:: + + f(x, a, b, c, z) = C x^{a-1} (1-x)^{b-1} (1+zx)^{-c} + + for :math:`0 \le x \le 1`, :math:`a,b > 0`, :math:`c` a real number, + :math:`z > -1`, and :math:`C = \frac{1}{B(a, b) F[2, 1](c, a; a+b; -z)}`. + :math:`F[2, 1]` is the Gauss hypergeometric function + `scipy.special.hyp2f1`. + + `gausshyper` takes :math:`a`, :math:`b`, :math:`c` and :math:`z` as shape + parameters. + + %(after_notes)s + + References + ---------- + .. [1] Armero, C., and M. J. Bayarri. "Prior Assessments for Prediction in + Queues." *Journal of the Royal Statistical Society*. Series D (The + Statistician) 43, no. 1 (1994): 139-53. :doi:`10.2307/2348939`. + + %(example)s + + """ + + def _argcheck(self, a, b, c, z): + # z > -1 per gh-10134 + return (a > 0) & (b > 0) & (c == c) & (z > -1) + + def _shape_info(self): + ia = _ShapeInfo("a", False, (0, np.inf), (False, False)) + ib = _ShapeInfo("b", False, (0, np.inf), (False, False)) + ic = _ShapeInfo("c", False, (-np.inf, np.inf), (False, False)) + iz = _ShapeInfo("z", False, (-1, np.inf), (False, False)) + return [ia, ib, ic, iz] + + def _pdf(self, x, a, b, c, z): + normalization_constant = sc.beta(a, b) * sc.hyp2f1(c, a, a + b, -z) + return (1./normalization_constant * x**(a - 1.) * (1. - x)**(b - 1.0) + / (1.0 + z*x)**c) + + def _munp(self, n, a, b, c, z): + fac = sc.beta(n+a, b) / sc.beta(a, b) + num = sc.hyp2f1(c, a+n, a+b+n, -z) + den = sc.hyp2f1(c, a, a+b, -z) + return fac*num / den + + +gausshyper = gausshyper_gen(a=0.0, b=1.0, name='gausshyper') + + +class invgamma_gen(rv_continuous): + r"""An inverted gamma continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `invgamma` is: + + .. math:: + + f(x, a) = \frac{x^{-a-1}}{\Gamma(a)} \exp(-\frac{1}{x}) + + for :math:`x >= 0`, :math:`a > 0`. :math:`\Gamma` is the gamma function + (`scipy.special.gamma`). + + `invgamma` takes ``a`` as a shape parameter for :math:`a`. + + `invgamma` is a special case of `gengamma` with ``c=-1``, and it is a + different parameterization of the scaled inverse chi-squared distribution. + Specifically, if the scaled inverse chi-squared distribution is + parameterized with degrees of freedom :math:`\nu` and scaling parameter + :math:`\tau^2`, then it can be modeled using `invgamma` with + ``a=`` :math:`\nu/2` and ``scale=`` :math:`\nu \tau^2/2`. + + %(after_notes)s + + %(example)s + + """ + _support_mask = rv_continuous._open_support_mask + + def _shape_info(self): + return [_ShapeInfo("a", False, (0, np.inf), (False, False))] + + def _pdf(self, x, a): + # invgamma.pdf(x, a) = x**(-a-1) / gamma(a) * exp(-1/x) + return np.exp(self._logpdf(x, a)) + + def _logpdf(self, x, a): + return -(a+1) * np.log(x) - sc.gammaln(a) - 1.0/x + + def _cdf(self, x, a): + return sc.gammaincc(a, 1.0 / x) + + def _ppf(self, q, a): + return 1.0 / sc.gammainccinv(a, q) + + def _sf(self, x, a): + return sc.gammainc(a, 1.0 / x) + + def _isf(self, q, a): + return 1.0 / sc.gammaincinv(a, q) + + def _stats(self, a, moments='mvsk'): + m1 = xpx.apply_where(a > 1, a, + lambda x: 1. / (x - 1.), + fill_value=np.inf) + m2 = xpx.apply_where(a > 2, a, + lambda x: 1. / (x - 1.)**2 / (x - 2.), + fill_value=np.inf) + + g1, g2 = None, None + if 's' in moments: + g1 = xpx.apply_where(a > 3, a, + lambda x: 4. * np.sqrt(x - 2.) / (x - 3.), + fill_value=np.nan) + if 'k' in moments: + g2 = xpx.apply_where(a > 4, a, + lambda x: 6. * (5. * x - 11.) / (x - 3.) / (x - 4.), + fill_value=np.nan) + + return m1, m2, g1, g2 + + def _entropy(self, a): + def regular(a): + h = a - (a + 1.0) * sc.psi(a) + sc.gammaln(a) + return h + + def asymptotic(a): + # gammaln(a) ~ a * ln(a) - a - 0.5 * ln(a) + 0.5 * ln(2 * pi) + # psi(a) ~ ln(a) - 1 / (2 * a) + h = ((1 - 3*np.log(a) + np.log(2) + np.log(np.pi))/2 + + 2/3*a**-1. + a**-2./12 - a**-3./90 - a**-4./120) + return h + + h = xpx.apply_where(a >= 200, a, asymptotic, regular) + return h + + +invgamma = invgamma_gen(a=0.0, name='invgamma') + + +class invgauss_gen(rv_continuous): + r"""An inverse Gaussian continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `invgauss` is: + + .. math:: + + f(x; \mu) = \frac{1}{\sqrt{2 \pi x^3}} + \exp\left(-\frac{(x-\mu)^2}{2 \mu^2 x}\right) + + for :math:`x \ge 0` and :math:`\mu > 0`. + + `invgauss` takes ``mu`` as a shape parameter for :math:`\mu`. + + %(after_notes)s + + A common shape-scale parameterization of the inverse Gaussian distribution + has density + + .. math:: + + f(x; \nu, \lambda) = \sqrt{\frac{\lambda}{2 \pi x^3}} + \exp\left( -\frac{\lambda(x-\nu)^2}{2 \nu^2 x}\right) + + Using ``nu`` for :math:`\nu` and ``lam`` for :math:`\lambda`, this + parameterization is equivalent to the one above with ``mu = nu/lam``, + ``loc = 0``, and ``scale = lam``. + + This distribution uses routines from the Boost Math C++ library for + the computation of the ``ppf`` and ``isf`` methods. [1]_ + + References + ---------- + .. [1] The Boost Developers. "Boost C++ Libraries". https://www.boost.org/. + + %(example)s + + """ + _support_mask = rv_continuous._open_support_mask + + def _shape_info(self): + return [_ShapeInfo("mu", False, (0, np.inf), (False, False))] + + def _rvs(self, mu, size=None, random_state=None): + return random_state.wald(mu, 1.0, size=size) + + def _pdf(self, x, mu): + # invgauss.pdf(x, mu) = + # 1 / sqrt(2*pi*x**3) * exp(-(x-mu)**2/(2*x*mu**2)) + return 1.0/np.sqrt(2*np.pi*x**3.0)*np.exp(-1.0/(2*x)*(x/mu - 1)**2) + + def _logpdf(self, x, mu): + return -0.5*np.log(2*np.pi) - 1.5*np.log(x) - (x/mu - 1)**2/(2*x) + + # approach adapted from equations in + # https://journal.r-project.org/archive/2016-1/giner-smyth.pdf, + # not R code. see gh-13616 + + def _logcdf(self, x, mu): + fac = 1 / np.sqrt(x) + a = _norm_logcdf(fac * (x/mu - 1)) + b = 2 / mu + _norm_logcdf(-fac * (x/mu + 1)) + return a + np.log1p(np.exp(b - a)) + + def _logsf(self, x, mu): + fac = 1 / np.sqrt(x) + a = _norm_logsf(fac * (x/mu - 1)) + b = 2 / mu + _norm_logcdf(-fac * (x/mu + 1)) + return a + np.log1p(-np.exp(b - a)) + + def _sf(self, x, mu): + return np.exp(self._logsf(x, mu)) + + def _cdf(self, x, mu): + return np.exp(self._logcdf(x, mu)) + + def _ppf(self, x, mu): + with np.errstate(divide='ignore', over='ignore', invalid='ignore'): + x, mu = np.broadcast_arrays(x, mu) + ppf = np.asarray(scu._invgauss_ppf(x, mu, 1)) + i_wt = x > 0.5 # "wrong tail" - sometimes too inaccurate + ppf[i_wt] = scu._invgauss_isf(1-x[i_wt], mu[i_wt], 1) + i_nan = np.isnan(ppf) + ppf[i_nan] = super()._ppf(x[i_nan], mu[i_nan]) + return ppf + + def _isf(self, x, mu): + with np.errstate(divide='ignore', over='ignore', invalid='ignore'): + x, mu = np.broadcast_arrays(x, mu) + isf = scu._invgauss_isf(x, mu, 1) + i_wt = x > 0.5 # "wrong tail" - sometimes too inaccurate + isf[i_wt] = scu._invgauss_ppf(1-x[i_wt], mu[i_wt], 1) + i_nan = np.isnan(isf) + isf[i_nan] = super()._isf(x[i_nan], mu[i_nan]) + return isf + + def _stats(self, mu): + return mu, mu**3.0, 3*np.sqrt(mu), 15*mu + + @inherit_docstring_from(rv_continuous) + def fit(self, data, *args, **kwds): + method = kwds.get('method', 'mle') + + if (isinstance(data, CensoredData) or isinstance(self, wald_gen) + or method.lower() == 'mm'): + return super().fit(data, *args, **kwds) + + data, fshape_s, floc, fscale = _check_fit_input_parameters(self, data, + args, kwds) + ''' + Source: Statistical Distributions, 3rd Edition. Evans, Hastings, + and Peacock (2000), Page 121. Their shape parameter is equivalent to + SciPy's with the conversion `fshape_s = fshape / scale`. + + MLE formulas are not used in 3 conditions: + + - `loc` is not fixed + - `mu` is fixed + + These cases fall back on the superclass fit method. + + - `loc` is fixed but translation results in negative data raises + a `FitDataError`. + ''' + if floc is None or fshape_s is not None: + return super().fit(data, *args, **kwds) + elif np.any(data - floc < 0): + raise FitDataError("invgauss", lower=0, upper=np.inf) + else: + data = data - floc + fshape_n = np.mean(data) + if fscale is None: + fscale = len(data) / (np.sum(data ** -1 - fshape_n ** -1)) + fshape_s = fshape_n / fscale + return fshape_s, floc, fscale + + def _entropy(self, mu): + """ + Ref.: https://moser-isi.ethz.ch/docs/papers/smos-2012-10.pdf (eq. 9) + """ + # a = log(2*pi*e*mu**3) + # = 1 + log(2*pi) + 3 * log(mu) + a = 1. + np.log(2 * np.pi) + 3 * np.log(mu) + # b = exp(2/mu) * exp1(2/mu) + # = _scaled_exp1(2/mu) / (2/mu) + r = 2/mu + b = sc._ufuncs._scaled_exp1(r)/r + return 0.5 * a - 1.5 * b + + +invgauss = invgauss_gen(a=0.0, name='invgauss') + + +class geninvgauss_gen(rv_continuous): + r"""A Generalized Inverse Gaussian continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `geninvgauss` is: + + .. math:: + + f(x, p, b) = x^{p-1} \exp(-b (x + 1/x) / 2) / (2 K_p(b)) + + where ``x > 0``, `p` is a real number and ``b > 0``\([1]_). + :math:`K_p` is the modified Bessel function of second kind of order `p` + (`scipy.special.kv`). + + %(after_notes)s + + The inverse Gaussian distribution `stats.invgauss(mu)` is a special case of + `geninvgauss` with ``p = -1/2``, ``b = 1 / mu`` and ``scale = mu``. + + Generating random variates is challenging for this distribution. The + implementation is based on [2]_. + + References + ---------- + .. [1] O. Barndorff-Nielsen, P. Blaesild, C. Halgreen, "First hitting time + models for the generalized inverse gaussian distribution", + Stochastic Processes and their Applications 7, pp. 49--54, 1978. + + .. [2] W. Hoermann and J. Leydold, "Generating generalized inverse Gaussian + random variates", Statistics and Computing, 24(4), p. 547--557, 2014. + + %(example)s + + """ + def _argcheck(self, p, b): + return (p == p) & (b > 0) + + def _shape_info(self): + ip = _ShapeInfo("p", False, (-np.inf, np.inf), (False, False)) + ib = _ShapeInfo("b", False, (0, np.inf), (False, False)) + return [ip, ib] + + def _logpdf(self, x, p, b): + # kve instead of kv works better for large values of b + # warn if kve produces infinite values and replace by nan + # otherwise c = -inf and the results are often incorrect + def logpdf_single(x, p, b): + return _stats.geninvgauss_logpdf(x, p, b) + + logpdf_single = np.vectorize(logpdf_single, otypes=[np.float64]) + + z = logpdf_single(x, p, b) + if np.isnan(z).any(): + msg = ("Infinite values encountered in scipy.special.kve(p, b). " + "Values replaced by NaN to avoid incorrect results.") + warnings.warn(msg, RuntimeWarning, stacklevel=3) + return z + + def _pdf(self, x, p, b): + # relying on logpdf avoids overflow of x**(p-1) for large x and p + return np.exp(self._logpdf(x, p, b)) + + def _cdf(self, x, p, b): + _a, _b = self._get_support(p, b) + + def _cdf_single(x, p, b): + user_data = np.array([p, b], float).ctypes.data_as(ctypes.c_void_p) + llc = LowLevelCallable.from_cython(_stats, '_geninvgauss_pdf', + user_data) + + return integrate.quad(llc, _a, x)[0] + + _cdf_single = np.vectorize(_cdf_single, otypes=[np.float64]) + + return _cdf_single(x, p, b) + + def _logquasipdf(self, x, p, b): + # log of the quasi-density (w/o normalizing constant) used in _rvs + return xpx.apply_where(x > 0, (x, p, b), + lambda x, p, b: (p - 1)*np.log(x) - b*(x + 1/x)/2, + fill_value=-np.inf) + + def _rvs(self, p, b, size=None, random_state=None): + # if p and b are scalar, use _rvs_scalar, otherwise need to create + # output by iterating over parameters + if np.isscalar(p) and np.isscalar(b): + out = self._rvs_scalar(p, b, size, random_state) + elif p.size == 1 and b.size == 1: + out = self._rvs_scalar(p.item(), b.item(), size, random_state) + else: + # When this method is called, size will be a (possibly empty) + # tuple of integers. It will not be None; if `size=None` is passed + # to `rvs()`, size will be the empty tuple (). + + p, b = np.broadcast_arrays(p, b) + # p and b now have the same shape. + + # `shp` is the shape of the blocks of random variates that are + # generated for each combination of parameters associated with + # broadcasting p and b. + # bc is a tuple the same length as size. The values + # in bc are bools. If bc[j] is True, it means that + # entire axis is filled in for a given combination of the + # broadcast arguments. + shp, bc = _check_shape(p.shape, size) + + # `numsamples` is the total number of variates to be generated + # for each combination of the input arguments. + numsamples = int(np.prod(shp)) + + # `out` is the array to be returned. It is filled in the + # loop below. + out = np.empty(size) + + it = np.nditer([p, b], + flags=['multi_index'], + op_flags=[['readonly'], ['readonly']]) + while not it.finished: + # Convert the iterator's multi_index into an index into the + # `out` array where the call to _rvs_scalar() will be stored. + # Where bc is True, we use a full slice; otherwise we use the + # index value from it.multi_index. len(it.multi_index) might + # be less than len(bc), and in that case we want to align these + # two sequences to the right, so the loop variable j runs from + # -len(size) to 0. This doesn't cause an IndexError, as + # bc[j] will be True in those cases where it.multi_index[j] + # would cause an IndexError. + idx = tuple((it.multi_index[j] if not bc[j] else slice(None)) + for j in range(-len(size), 0)) + out[idx] = self._rvs_scalar(it[0], it[1], numsamples, + random_state).reshape(shp) + it.iternext() + + if size == (): + out = out.item() + return out + + def _rvs_scalar(self, p, b, numsamples, random_state): + # following [2], the quasi-pdf is used instead of the pdf for the + # generation of rvs + invert_res = False + if not numsamples: + numsamples = 1 + if p < 0: + # note: if X is geninvgauss(p, b), then 1/X is geninvgauss(-p, b) + p = -p + invert_res = True + m = self._mode(p, b) + + # determine method to be used following [2] + ratio_unif = True + if p >= 1 or b > 1: + # ratio of uniforms with mode shift below + mode_shift = True + elif b >= min(0.5, 2 * np.sqrt(1 - p) / 3): + # ratio of uniforms without mode shift below + mode_shift = False + else: + # new algorithm in [2] + ratio_unif = False + + # prepare sampling of rvs + size1d = tuple(np.atleast_1d(numsamples)) + N = np.prod(size1d) # number of rvs needed, reshape upon return + x = np.zeros(N) + simulated = 0 + + if ratio_unif: + # use ratio of uniforms method + if mode_shift: + a2 = -2 * (p + 1) / b - m + a1 = 2 * m * (p - 1) / b - 1 + # find roots of x**3 + a2*x**2 + a1*x + m (Cardano's formula) + p1 = a1 - a2**2 / 3 + q1 = 2 * a2**3 / 27 - a2 * a1 / 3 + m + phi = np.arccos(-q1 * np.sqrt(-27 / p1**3) / 2) + s1 = -np.sqrt(-4 * p1 / 3) + root1 = s1 * np.cos(phi / 3 + np.pi / 3) - a2 / 3 + root2 = -s1 * np.cos(phi / 3) - a2 / 3 + # root3 = s1 * np.cos(phi / 3 - np.pi / 3) - a2 / 3 + + # if g is the quasipdf, rescale: g(x) / g(m) which we can write + # as exp(log(g(x)) - log(g(m))). This is important + # since for large values of p and b, g cannot be evaluated. + # denote the rescaled quasipdf by h + lm = self._logquasipdf(m, p, b) + d1 = self._logquasipdf(root1, p, b) - lm + d2 = self._logquasipdf(root2, p, b) - lm + # compute the bounding rectangle w.r.t. h. Note that + # np.exp(0.5*d1) = np.sqrt(g(root1)/g(m)) = np.sqrt(h(root1)) + vmin = (root1 - m) * np.exp(0.5 * d1) + vmax = (root2 - m) * np.exp(0.5 * d2) + umax = 1 # umax = sqrt(h(m)) = 1 + + def logqpdf(x): + return self._logquasipdf(x, p, b) - lm + + c = m + else: + # ratio of uniforms without mode shift + # compute np.sqrt(quasipdf(m)) + umax = np.exp(0.5*self._logquasipdf(m, p, b)) + xplus = ((1 + p) + np.sqrt((1 + p)**2 + b**2))/b + vmin = 0 + # compute xplus * np.sqrt(quasipdf(xplus)) + vmax = xplus * np.exp(0.5 * self._logquasipdf(xplus, p, b)) + c = 0 + + def logqpdf(x): + return self._logquasipdf(x, p, b) + + if vmin >= vmax: + raise ValueError("vmin must be smaller than vmax.") + if umax <= 0: + raise ValueError("umax must be positive.") + + i = 1 + while simulated < N: + k = N - simulated + # simulate uniform rvs on [0, umax] and [vmin, vmax] + u = umax * random_state.uniform(size=k) + v = random_state.uniform(size=k) + v = vmin + (vmax - vmin) * v + rvs = v / u + c + # rewrite acceptance condition u**2 <= pdf(rvs) by taking logs + accept = (2*np.log(u) <= logqpdf(rvs)) + num_accept = np.sum(accept) + if num_accept > 0: + x[simulated:(simulated + num_accept)] = rvs[accept] + simulated += num_accept + + if (simulated == 0) and (i*N >= 50000): + msg = ("Not a single random variate could be generated " + f"in {i*N} attempts. Sampling does not appear to " + "work for the provided parameters.") + raise RuntimeError(msg) + i += 1 + else: + # use new algorithm in [2] + x0 = b / (1 - p) + xs = np.max((x0, 2 / b)) + k1 = np.exp(self._logquasipdf(m, p, b)) + A1 = k1 * x0 + if x0 < 2 / b: + k2 = np.exp(-b) + if p > 0: + A2 = k2 * ((2 / b)**p - x0**p) / p + else: + A2 = k2 * np.log(2 / b**2) + else: + k2, A2 = 0, 0 + k3 = xs**(p - 1) + A3 = 2 * k3 * np.exp(-xs * b / 2) / b + A = A1 + A2 + A3 + + # [2]: rejection constant is < 2.73; so expected runtime is finite + while simulated < N: + k = N - simulated + h, rvs = np.zeros(k), np.zeros(k) + # simulate uniform rvs on [x1, x2] and [0, y2] + u = random_state.uniform(size=k) + v = A * random_state.uniform(size=k) + cond1 = v <= A1 + cond2 = np.logical_not(cond1) & (v <= A1 + A2) + cond3 = np.logical_not(cond1 | cond2) + # subdomain (0, x0) + rvs[cond1] = x0 * v[cond1] / A1 + h[cond1] = k1 + # subdomain (x0, 2 / b) + if p > 0: + rvs[cond2] = (x0**p + (v[cond2] - A1) * p / k2)**(1 / p) + else: + rvs[cond2] = b * np.exp((v[cond2] - A1) * np.exp(b)) + h[cond2] = k2 * rvs[cond2]**(p - 1) + # subdomain (xs, infinity) + z = np.exp(-xs * b / 2) - b * (v[cond3] - A1 - A2) / (2 * k3) + rvs[cond3] = -2 / b * np.log(z) + h[cond3] = k3 * np.exp(-rvs[cond3] * b / 2) + # apply rejection method + accept = (np.log(u * h) <= self._logquasipdf(rvs, p, b)) + num_accept = sum(accept) + if num_accept > 0: + x[simulated:(simulated + num_accept)] = rvs[accept] + simulated += num_accept + + rvs = np.reshape(x, size1d) + if invert_res: + rvs = 1 / rvs + return rvs + + def _mode(self, p, b): + # distinguish cases to avoid catastrophic cancellation (see [2]) + if p < 1: + return b / (np.sqrt((p - 1)**2 + b**2) + 1 - p) + else: + return (np.sqrt((1 - p)**2 + b**2) - (1 - p)) / b + + def _munp(self, n, p, b): + num = sc.kve(p + n, b) + denom = sc.kve(p, b) + inf_vals = np.isinf(num) | np.isinf(denom) + if inf_vals.any(): + msg = ("Infinite values encountered in the moment calculation " + "involving scipy.special.kve. Values replaced by NaN to " + "avoid incorrect results.") + warnings.warn(msg, RuntimeWarning, stacklevel=3) + m = np.full_like(num, np.nan, dtype=np.float64) + m[~inf_vals] = num[~inf_vals] / denom[~inf_vals] + else: + m = num / denom + return m + + +geninvgauss = geninvgauss_gen(a=0.0, name="geninvgauss") + + +@np.vectorize(otypes=[np.float64]) +def _norminvgauss_quadrature(x, y, a, b): + # gh-23196 reported that the norminvgauss CDF would drop to zero in the far right + # tail. The SF had a similar problem, dropping to zero at the far left. + # This fixes the bug by using 1 - SF to compute the CDF in the right tail and + # 1 - CDF to compute the SF in the left tail. The mean is guaranteed to be beyond + # the median, so there is no loss of precision due to subtractive cancellation. + mean = b / np.sqrt((a + b) * (a - b)) + if np.isneginf(x) and y > abs(mean): + return 1 - integrate.quad(norminvgauss._pdf, y, np.inf, args=(a, b))[0] + if np.isposinf(y) and x < -abs(mean): + return 1 - integrate.quad(norminvgauss._pdf, -np.inf, x, args=(a, b))[0] + else: + res = integrate.quad(norminvgauss._pdf, x, y, args=(a, b))[0] + return np.clip(res, 0, 1) + + +class norminvgauss_gen(rv_continuous): + r"""A Normal Inverse Gaussian continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `norminvgauss` is: + + .. math:: + + f(x, a, b) = \frac{a \, K_1(a \sqrt{1 + x^2})}{\pi \sqrt{1 + x^2}} \, + \exp(\sqrt{a^2 - b^2} + b x) + + where :math:`x` is a real number, the parameter :math:`a` is the tail + heaviness and :math:`b` is the asymmetry parameter satisfying + :math:`a > 0` and :math:`|b| <= a`. + :math:`K_1` is the modified Bessel function of second kind + (`scipy.special.k1`). + + %(after_notes)s + + A normal inverse Gaussian random variable `Y` with parameters `a` and `b` + can be expressed as a normal mean-variance mixture: + ``Y = b * V + sqrt(V) * X`` where `X` is ``norm(0,1)`` and `V` is + ``invgauss(mu=1/sqrt(a**2 - b**2))``. This representation is used + to generate random variates. + + Another common parametrization of the distribution (see Equation 2.1 in + [2]_) is given by the following expression of the pdf: + + .. math:: + + g(x, \alpha, \beta, \delta, \mu) = + \frac{\alpha\delta K_1\left(\alpha\sqrt{\delta^2 + (x - \mu)^2}\right)} + {\pi \sqrt{\delta^2 + (x - \mu)^2}} \, + e^{\delta \sqrt{\alpha^2 - \beta^2} + \beta (x - \mu)} + + In SciPy, this corresponds to + :math:`a=\alpha \delta, b=\beta \delta, \text{loc}=\mu, \text{scale}=\delta`. + + References + ---------- + .. [1] O. Barndorff-Nielsen, "Hyperbolic Distributions and Distributions on + Hyperbolae", Scandinavian Journal of Statistics, Vol. 5(3), + pp. 151-157, 1978. + + .. [2] O. Barndorff-Nielsen, "Normal Inverse Gaussian Distributions and + Stochastic Volatility Modelling", Scandinavian Journal of + Statistics, Vol. 24, pp. 1-13, 1997. + + %(example)s + + """ + _support_mask = rv_continuous._open_support_mask + + def _argcheck(self, a, b): + return (a > 0) & (np.absolute(b) < a) + + def _shape_info(self): + ia = _ShapeInfo("a", False, (0, np.inf), (False, False)) + ib = _ShapeInfo("b", False, (-np.inf, np.inf), (False, False)) + return [ia, ib] + + def _fitstart(self, data): + # Arbitrary, but the default a = b = 1 is not valid; the distribution + # requires |b| < a. + return super()._fitstart(data, args=(1, 0.5)) + + def _pdf(self, x, a, b): + gamma = np.sqrt((a + b) * (a - b)) + fac1 = a / np.pi + sq = np.hypot(1, x) # reduce overflows + return fac1 * sc.k1e(a * sq) * np.exp(b*x - a*sq + gamma) / sq + + def _cdf(self, x, a, b): + return _norminvgauss_quadrature(-np.inf, x, a, b) + + def _sf(self, x, a, b): + return _norminvgauss_quadrature(x, np.inf, a, b) + + def _isf(self, q, a, b): + def _isf_scalar(q, a, b): + + def eq(x, a, b, q): + # Solve eq(x, a, b, q) = 0 to obtain isf(x, a, b) = q. + return self._sf(x, a, b) - q + + # Find a bracketing interval for the root. + # Start at the mean, and grow the length of the interval + # by 2 each iteration until there is a sign change in eq. + xm = self.mean(a, b) + em = eq(xm, a, b, q) + if em == 0: + # Unlikely, but might as well check. + return xm + if em > 0: + delta = 1 + left = xm + right = xm + delta + while eq(right, a, b, q) > 0: + delta = 2*delta + right = xm + delta + else: + # em < 0 + delta = 1 + right = xm + left = xm - delta + while eq(left, a, b, q) < 0: + delta = 2*delta + left = xm - delta + result = optimize.brentq(eq, left, right, args=(a, b, q), + xtol=self.xtol) + return result + + if np.isscalar(q): + return _isf_scalar(q, a, b) + else: + result = [] + for (q0, a0, b0) in zip(q, a, b): + result.append(_isf_scalar(q0, a0, b0)) + return np.array(result) + + def _rvs(self, a, b, size=None, random_state=None): + # note: X = b * V + sqrt(V) * X is norminvgaus(a,b) if X is standard + # normal and V is invgauss(mu=1/sqrt(a**2 - b**2)) + gamma = np.sqrt((a + b) * (a - b)) + ig = invgauss.rvs(mu=1/gamma, size=size, random_state=random_state) + return b * ig + np.sqrt(ig) * norm.rvs(size=size, + random_state=random_state) + + def _stats(self, a, b): + gamma = np.sqrt((a + b) * (a - b)) + mean = b / gamma + variance = a**2 / gamma**3 + skewness = 3.0 * b / (a * np.sqrt(gamma)) + kurtosis = 3.0 * (1 + 4 * b**2 / a**2) / gamma + return mean, variance, skewness, kurtosis + + +norminvgauss = norminvgauss_gen(name="norminvgauss") + + +class invweibull_gen(rv_continuous): + """An inverted Weibull continuous random variable. + + This distribution is also known as the Fréchet distribution or the + type II extreme value distribution. + + %(before_notes)s + + Notes + ----- + The probability density function for `invweibull` is: + + .. math:: + + f(x, c) = c x^{-c-1} \\exp(-x^{-c}) + + for :math:`x > 0`, :math:`c > 0`. + + `invweibull` takes ``c`` as a shape parameter for :math:`c`. + + %(after_notes)s + + References + ---------- + F.R.S. de Gusmao, E.M.M Ortega and G.M. Cordeiro, "The generalized inverse + Weibull distribution", Stat. Papers, vol. 52, pp. 591-619, 2011. + + %(example)s + + """ + _support_mask = rv_continuous._open_support_mask + + def _shape_info(self): + return [_ShapeInfo("c", False, (0, np.inf), (False, False))] + + def _pdf(self, x, c): + # invweibull.pdf(x, c) = c * x**(-c-1) * exp(-x**(-c)) + xc1 = np.power(x, -c - 1.0) + xc2 = np.power(x, -c) + xc2 = np.exp(-xc2) + return c * xc1 * xc2 + + def _cdf(self, x, c): + xc1 = np.power(x, -c) + return np.exp(-xc1) + + def _sf(self, x, c): + return -np.expm1(-x**-c) + + def _ppf(self, q, c): + return np.power(-np.log(q), -1.0/c) + + def _isf(self, p, c): + return (-np.log1p(-p))**(-1/c) + + def _munp(self, n, c): + return sc.gamma(1 - n / c) + + def _entropy(self, c): + return 1+_EULER + _EULER / c - np.log(c) + + def _fitstart(self, data, args=None): + # invweibull requires c > 1 for the first moment to exist, so use 2.0 + args = (2.0,) if args is None else args + return super()._fitstart(data, args=args) + + +invweibull = invweibull_gen(a=0, name='invweibull') + + +class jf_skew_t_gen(rv_continuous): + r"""Jones and Faddy skew-t distribution. + + %(before_notes)s + + Notes + ----- + The probability density function for `jf_skew_t` is: + + .. math:: + + f(x; a, b) = C_{a,b}^{-1} + \left(1+\frac{x}{\left(a+b+x^2\right)^{1/2}}\right)^{a+1/2} + \left(1-\frac{x}{\left(a+b+x^2\right)^{1/2}}\right)^{b+1/2} + + for real numbers :math:`a>0` and :math:`b>0`, where + :math:`C_{a,b} = 2^{a+b-1}B(a,b)(a+b)^{1/2}`, and :math:`B` denotes the + beta function (`scipy.special.beta`). + + When :math:`ab`, the distribution is positively skewed. If :math:`a=b`, then + we recover the `t` distribution with :math:`2a` degrees of freedom. + + `jf_skew_t` takes :math:`a` and :math:`b` as shape parameters. + + %(after_notes)s + + References + ---------- + .. [1] M.C. Jones and M.J. Faddy. "A skew extension of the t distribution, + with applications" *Journal of the Royal Statistical Society*. + Series B (Statistical Methodology) 65, no. 1 (2003): 159-174. + :doi:`10.1111/1467-9868.00378` + + %(example)s + + """ + def _shape_info(self): + ia = _ShapeInfo("a", False, (0, np.inf), (False, False)) + ib = _ShapeInfo("b", False, (0, np.inf), (False, False)) + return [ia, ib] + + def _pdf(self, x, a, b): + c = 2 ** (a + b - 1) * sc.beta(a, b) * np.sqrt(a + b) + d1 = (1 + x / np.sqrt(a + b + x ** 2)) ** (a + 0.5) + d2 = (1 - x / np.sqrt(a + b + x ** 2)) ** (b + 0.5) + return d1 * d2 / c + + def _rvs(self, a, b, size=None, random_state=None): + d1 = random_state.beta(a, b, size) + d2 = (2 * d1 - 1) * np.sqrt(a + b) + d3 = 2 * np.sqrt(d1 * (1 - d1)) + return d2 / d3 + + def _cdf(self, x, a, b): + y = (1 + x / np.sqrt(a + b + x ** 2)) * 0.5 + return sc.betainc(a, b, y) + + def _sf(self, x, a, b): + y = (1 + x / np.sqrt(a + b + x ** 2)) * 0.5 + return sc.betaincc(a, b, y) + + def _ppf(self, q, a, b): + d1 = beta.ppf(q, a, b) + d2 = (2 * d1 - 1) * np.sqrt(a + b) + d3 = 2 * np.sqrt(d1 * (1 - d1)) + return d2 / d3 + + def _munp(self, n, a, b): + """Returns the n-th moment(s) where all the following hold: + + - n >= 0 + - a > n / 2 + - b > n / 2 + + The result is np.nan in all other cases. + """ + def nth_moment(n_k, a_k, b_k): + """Computes E[T^(n_k)] where T is skew-t distributed with + parameters a_k and b_k. + """ + num = (a_k + b_k) ** (0.5 * n_k) + denom = 2 ** n_k * sc.beta(a_k, b_k) + + indices = np.arange(n_k + 1) + sgn = np.where(indices % 2 > 0, -1, 1) + d = sc.beta(a_k + 0.5 * n_k - indices, b_k - 0.5 * n_k + indices) + sum_terms = sc.comb(n_k, indices) * sgn * d + + return num / denom * sum_terms.sum() + + nth_moment_valid = (a > 0.5 * n) & (b > 0.5 * n) & (n >= 0) + return xpx.apply_where( + nth_moment_valid, + (n, a, b), + np.vectorize(nth_moment, otypes=[np.float64]), + fill_value=np.nan, + ) + + +jf_skew_t = jf_skew_t_gen(name='jf_skew_t') + + +class johnsonsb_gen(rv_continuous): + r"""A Johnson SB continuous random variable. + + %(before_notes)s + + See Also + -------- + johnsonsu + + Notes + ----- + The probability density function for `johnsonsb` is: + + .. math:: + + f(x, a, b) = \frac{b}{x(1-x)} \phi(a + b \log \frac{x}{1-x} ) + + where :math:`x`, :math:`a`, and :math:`b` are real scalars; :math:`b > 0` + and :math:`x \in [0,1]`. :math:`\phi` is the pdf of the normal + distribution. + + `johnsonsb` takes :math:`a` and :math:`b` as shape parameters. + + %(after_notes)s + + %(example)s + + """ + _support_mask = rv_continuous._open_support_mask + + def _argcheck(self, a, b): + return (b > 0) & (a == a) + + def _shape_info(self): + ia = _ShapeInfo("a", False, (-np.inf, np.inf), (False, False)) + ib = _ShapeInfo("b", False, (0, np.inf), (False, False)) + return [ia, ib] + + def _pdf(self, x, a, b): + # johnsonsb.pdf(x, a, b) = b / (x*(1-x)) * phi(a + b * log(x/(1-x))) + trm = _norm_pdf(a + b*sc.logit(x)) + return b*1.0/(x*(1-x))*trm + + def _cdf(self, x, a, b): + return _norm_cdf(a + b*sc.logit(x)) + + def _ppf(self, q, a, b): + return sc.expit(1.0 / b * (_norm_ppf(q) - a)) + + def _sf(self, x, a, b): + return _norm_sf(a + b*sc.logit(x)) + + def _isf(self, q, a, b): + return sc.expit(1.0 / b * (_norm_isf(q) - a)) + + +johnsonsb = johnsonsb_gen(a=0.0, b=1.0, name='johnsonsb') + + +class johnsonsu_gen(rv_continuous): + r"""A Johnson SU continuous random variable. + + %(before_notes)s + + See Also + -------- + johnsonsb + + Notes + ----- + The probability density function for `johnsonsu` is: + + .. math:: + + f(x, a, b) = \frac{b}{\sqrt{x^2 + 1}} + \phi(a + b \log(x + \sqrt{x^2 + 1})) + + where :math:`x`, :math:`a`, and :math:`b` are real scalars; :math:`b > 0`. + :math:`\phi` is the pdf of the normal distribution. + + `johnsonsu` takes :math:`a` and :math:`b` as shape parameters. + + The first four central moments are calculated according to the formulas + in [1]_. + + %(after_notes)s + + References + ---------- + .. [1] Taylor Enterprises. "Johnson Family of Distributions". + https://variation.com/wp-content/distribution_analyzer_help/hs126.htm + + %(example)s + + """ + def _argcheck(self, a, b): + return (b > 0) & (a == a) + + def _shape_info(self): + ia = _ShapeInfo("a", False, (-np.inf, np.inf), (False, False)) + ib = _ShapeInfo("b", False, (0, np.inf), (False, False)) + return [ia, ib] + + def _pdf(self, x, a, b): + # johnsonsu.pdf(x, a, b) = b / sqrt(x**2 + 1) * + # phi(a + b * log(x + sqrt(x**2 + 1))) + x2 = x*x + trm = _norm_pdf(a + b * np.arcsinh(x)) + return b*1.0/np.sqrt(x2+1.0)*trm + + def _cdf(self, x, a, b): + return _norm_cdf(a + b * np.arcsinh(x)) + + def _ppf(self, q, a, b): + return np.sinh((_norm_ppf(q) - a) / b) + + def _sf(self, x, a, b): + return _norm_sf(a + b * np.arcsinh(x)) + + def _isf(self, x, a, b): + return np.sinh((_norm_isf(x) - a) / b) + + def _stats(self, a, b, moments='mv'): + # Naive implementation of first and second moment to address gh-18071. + # https://variation.com/wp-content/distribution_analyzer_help/hs126.htm + # Numerical improvements left to future enhancements. + mu, mu2, g1, g2 = None, None, None, None + + bn2 = b**-2. + expbn2 = np.exp(bn2) + a_b = a / b + + if 'm' in moments: + mu = -expbn2**0.5 * np.sinh(a_b) + if 'v' in moments: + mu2 = 0.5*sc.expm1(bn2)*(expbn2*np.cosh(2*a_b) + 1) + if 's' in moments: + t1 = expbn2**.5 * sc.expm1(bn2)**0.5 + t2 = 3*np.sinh(a_b) + t3 = expbn2 * (expbn2 + 2) * np.sinh(3*a_b) + denom = np.sqrt(2) * (1 + expbn2 * np.cosh(2*a_b))**(3/2) + g1 = -t1 * (t2 + t3) / denom + if 'k' in moments: + t1 = 3 + 6*expbn2 + t2 = 4*expbn2**2 * (expbn2 + 2) * np.cosh(2*a_b) + t3 = expbn2**2 * np.cosh(4*a_b) + t4 = -3 + 3*expbn2**2 + 2*expbn2**3 + expbn2**4 + denom = 2*(1 + expbn2*np.cosh(2*a_b))**2 + g2 = (t1 + t2 + t3*t4) / denom - 3 + return mu, mu2, g1, g2 + + +johnsonsu = johnsonsu_gen(name='johnsonsu') + + +class landau_gen(rv_continuous): + r"""A Landau continuous random variable. + + %(before_notes)s + + See Also + -------- + :ref:`landau_energy_loss` : Extended example in a particle physics context. + + Notes + ----- + The probability density function for `landau` ([1]_, [2]_) is: + + .. math:: + + f(x) = \frac{1}{\pi}\int_0^\infty \exp(-t \log t - xt)\sin(\pi t) dt + + for a real number :math:`x`. + + %(after_notes)s + + Often (e.g. [2]_), the Landau distribution is parameterized in terms of a + location parameter :math:`\mu` and scale parameter :math:`c`, the latter of + which *also* introduces a location shift. If ``mu`` and ``c`` are used to + represent these parameters, this corresponds with SciPy's parameterization + with ``loc = mu + 2*c / np.pi * np.log(c)`` and ``scale = c``. + + This distribution uses routines from the Boost Math C++ library for + the computation of the ``pdf``, ``cdf``, ``ppf``, ``sf`` and ``isf`` + methods. [1]_ + + References + ---------- + .. [1] Landau, L. (1944). "On the energy loss of fast particles by + ionization". J. Phys. (USSR). 8: 201. + .. [2] "Landau Distribution", Wikipedia, + https://en.wikipedia.org/wiki/Landau_distribution + .. [3] Chambers, J. M., Mallows, C. L., & Stuck, B. (1976). + "A method for simulating stable random variables." + Journal of the American Statistical Association, 71(354), 340-344. + .. [4] The Boost Developers. "Boost C++ Libraries". https://www.boost.org/. + .. [5] Yoshimura, T. "Numerical Evaluation and High Precision Approximation + Formula for Landau Distribution". + :doi:`10.36227/techrxiv.171822215.53612870/v2` + + %(example)s + + """ + def _shape_info(self): + return [] + + def _entropy(self): + # Computed with mpmath - see gh-19145 + return 2.37263644000448182 + + def _pdf(self, x): + return scu._landau_pdf(x, 0, 1) + + def _cdf(self, x): + return scu._landau_cdf(x, 0, 1) + + def _sf(self, x): + return scu._landau_sf(x, 0, 1) + + def _ppf(self, p): + return scu._landau_ppf(p, 0, 1) + + def _isf(self, p): + return scu._landau_isf(p, 0, 1) + + def _stats(self): + return np.nan, np.nan, np.nan, np.nan + + def _munp(self, n): + return np.nan if n > 0 else 1 + + def _fitstart(self, data, args=None): + # Initialize ML guesses using quartiles instead of moments. + if isinstance(data, CensoredData): + data = data._uncensor() + p25, p50, p75 = np.percentile(data, [25, 50, 75]) + return p50, (p75 - p25)/2 + + def _rvs(self, size=None, random_state=None): + # Method from https://www.jstor.org/stable/2285309 Eq. 2.4 + pi_2 = np.pi / 2 + U = random_state.uniform(-np.pi / 2, np.pi / 2, size=size) + W = random_state.standard_exponential(size=size) + S = 2 / np.pi * ((pi_2 + U) * np.tan(U) + - np.log((pi_2 * W * np.cos(U)) / (pi_2 + U))) + return S + + +landau = landau_gen(name='landau') + + +class laplace_gen(rv_continuous): + r"""A Laplace continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `laplace` is + + .. math:: + + f(x) = \frac{1}{2} \exp(-|x|) + + for a real number :math:`x`. + + %(after_notes)s + + %(example)s + + """ + def _shape_info(self): + return [] + + def _rvs(self, size=None, random_state=None): + return random_state.laplace(0, 1, size=size) + + def _pdf(self, x): + # laplace.pdf(x) = 1/2 * exp(-abs(x)) + return 0.5*np.exp(-abs(x)) + + def _cdf(self, x): + with np.errstate(over='ignore'): + return np.where(x > 0, 1.0 - 0.5*np.exp(-x), 0.5*np.exp(x)) + + def _sf(self, x): + # By symmetry... + return self._cdf(-x) + + def _ppf(self, q): + return np.where(q > 0.5, -np.log(2*(1-q)), np.log(2*q)) + + def _isf(self, q): + # By symmetry... + return -self._ppf(q) + + def _stats(self): + return 0, 2, 0, 3 + + def _entropy(self): + return np.log(2)+1 + + @_call_super_mom + @replace_notes_in_docstring(rv_continuous, notes="""\ + This function uses explicit formulas for the maximum likelihood + estimation of the Laplace distribution parameters, so the keyword + arguments `loc`, `scale`, and `optimizer` are ignored.\n\n""") + def fit(self, data, *args, **kwds): + data, floc, fscale = _check_fit_input_parameters(self, data, + args, kwds) + + # Source: Statistical Distributions, 3rd Edition. Evans, Hastings, + # and Peacock (2000), Page 124 + + if floc is None: + floc = np.median(data) + + if fscale is None: + fscale = (np.sum(np.abs(data - floc))) / len(data) + + return floc, fscale + + +laplace = laplace_gen(name='laplace') + + +class laplace_asymmetric_gen(rv_continuous): + r"""An asymmetric Laplace continuous random variable. + + %(before_notes)s + + See Also + -------- + laplace : Laplace distribution + + Notes + ----- + The probability density function for `laplace_asymmetric` is + + .. math:: + + f(x, \kappa) &= \frac{1}{\kappa+\kappa^{-1}}\exp(-x\kappa),\quad x\ge0\\ + &= \frac{1}{\kappa+\kappa^{-1}}\exp(x/\kappa),\quad x<0\\ + + for :math:`-\infty < x < \infty`, :math:`\kappa > 0`. + + `laplace_asymmetric` takes ``kappa`` as a shape parameter for + :math:`\kappa`. For :math:`\kappa = 1`, it is identical to a + Laplace distribution. + + %(after_notes)s + + Note that the scale parameter of some references is the reciprocal of + SciPy's ``scale``. For example, :math:`\lambda = 1/2` in the + parameterization of [1]_ is equivalent to ``scale = 2`` with + `laplace_asymmetric`. + + References + ---------- + .. [1] "Asymmetric Laplace distribution", Wikipedia + https://en.wikipedia.org/wiki/Asymmetric_Laplace_distribution + + .. [2] Kozubowski TJ and Podgórski K. A Multivariate and + Asymmetric Generalization of Laplace Distribution, + Computational Statistics 15, 531--540 (2000). + :doi:`10.1007/PL00022717` + + %(example)s + + """ + def _shape_info(self): + return [_ShapeInfo("kappa", False, (0, np.inf), (False, False))] + + def _pdf(self, x, kappa): + return np.exp(self._logpdf(x, kappa)) + + def _logpdf(self, x, kappa): + kapinv = 1/kappa + lPx = x * np.where(x >= 0, -kappa, kapinv) + lPx -= np.log(kappa+kapinv) + return lPx + + def _cdf(self, x, kappa): + kapinv = 1/kappa + kappkapinv = kappa+kapinv + return np.where(x >= 0, + 1 - np.exp(-x*kappa)*(kapinv/kappkapinv), + np.exp(x*kapinv)*(kappa/kappkapinv)) + + def _sf(self, x, kappa): + kapinv = 1/kappa + kappkapinv = kappa+kapinv + return np.where(x >= 0, + np.exp(-x*kappa)*(kapinv/kappkapinv), + 1 - np.exp(x*kapinv)*(kappa/kappkapinv)) + + def _ppf(self, q, kappa): + kapinv = 1/kappa + kappkapinv = kappa+kapinv + return np.where(q >= kappa/kappkapinv, + -np.log((1 - q)*kappkapinv*kappa)*kapinv, + np.log(q*kappkapinv/kappa)*kappa) + + def _isf(self, q, kappa): + kapinv = 1/kappa + kappkapinv = kappa+kapinv + return np.where(q <= kapinv/kappkapinv, + -np.log(q*kappkapinv*kappa)*kapinv, + np.log((1 - q)*kappkapinv/kappa)*kappa) + + def _stats(self, kappa): + kapinv = 1/kappa + mn = kapinv - kappa + var = kapinv*kapinv + kappa*kappa + g1 = 2.0*(1-np.power(kappa, 6))/np.power(1+np.power(kappa, 4), 1.5) + g2 = 6.0*(1+np.power(kappa, 8))/np.power(1+np.power(kappa, 4), 2) + return mn, var, g1, g2 + + def _entropy(self, kappa): + return 1 + np.log(kappa+1/kappa) + + +laplace_asymmetric = laplace_asymmetric_gen(name='laplace_asymmetric') + + +def _check_fit_input_parameters(dist, data, args, kwds): + if not isinstance(data, CensoredData): + data = np.asarray(data) + + floc = kwds.get('floc', None) + fscale = kwds.get('fscale', None) + + num_shapes = len(dist.shapes.split(",")) if dist.shapes else 0 + fshape_keys = [] + fshapes = [] + + # user has many options for fixing the shape, so here we standardize it + # into 'f' + the number of the shape. + # Adapted from `_reduce_func` in `_distn_infrastructure.py`: + if dist.shapes: + shapes = dist.shapes.replace(',', ' ').split() + for j, s in enumerate(shapes): + key = 'f' + str(j) + names = [key, 'f' + s, 'fix_' + s] + val = _get_fixed_fit_value(kwds, names) + fshape_keys.append(key) + fshapes.append(val) + if val is not None: + kwds[key] = val + + # determine if there are any unknown arguments in kwds + known_keys = {'loc', 'scale', 'optimizer', 'method', + 'floc', 'fscale', *fshape_keys} + unknown_keys = set(kwds).difference(known_keys) + if unknown_keys: + raise TypeError(f"Unknown keyword arguments: {unknown_keys}.") + + if len(args) > num_shapes: + raise TypeError("Too many positional arguments.") + + if None not in {floc, fscale, *fshapes}: + # This check is for consistency with `rv_continuous.fit`. + # Without this check, this function would just return the + # parameters that were given. + raise RuntimeError("All parameters fixed. There is nothing to " + "optimize.") + + uncensored = data._uncensor() if isinstance(data, CensoredData) else data + if not np.isfinite(uncensored).all(): + raise ValueError("The data contains non-finite values.") + + return (data, *fshapes, floc, fscale) + + +class levy_gen(rv_continuous): + r"""A Levy continuous random variable. + + %(before_notes)s + + See Also + -------- + levy_stable, levy_l + + Notes + ----- + The probability density function for `levy` is: + + .. math:: + + f(x) = \frac{1}{\sqrt{2\pi x^3}} \exp\left(-\frac{1}{2x}\right) + + for :math:`x > 0`. + + This is the same as the Levy-stable distribution with :math:`a=1/2` and + :math:`b=1`. + + %(after_notes)s + + Examples + -------- + >>> import numpy as np + >>> from scipy.stats import levy + >>> import matplotlib.pyplot as plt + >>> fig, ax = plt.subplots(1, 1) + + Calculate the first four moments: + + >>> mean, var, skew, kurt = levy.stats(moments='mvsk') + + Display the probability density function (``pdf``): + + >>> # `levy` is very heavy-tailed. + >>> # To show a nice plot, let's cut off the upper 40 percent. + >>> a, b = levy.ppf(0), levy.ppf(0.6) + >>> x = np.linspace(a, b, 100) + >>> ax.plot(x, levy.pdf(x), + ... 'r-', lw=5, alpha=0.6, label='levy pdf') + + Alternatively, the distribution object can be called (as a function) + to fix the shape, location and scale parameters. This returns a "frozen" + RV object holding the given parameters fixed. + + Freeze the distribution and display the frozen ``pdf``: + + >>> rv = levy() + >>> ax.plot(x, rv.pdf(x), 'k-', lw=2, label='frozen pdf') + + Check accuracy of ``cdf`` and ``ppf``: + + >>> vals = levy.ppf([0.001, 0.5, 0.999]) + >>> np.allclose([0.001, 0.5, 0.999], levy.cdf(vals)) + True + + Generate random numbers: + + >>> r = levy.rvs(size=1000) + + And compare the histogram: + + >>> # manual binning to ignore the tail + >>> bins = np.concatenate((np.linspace(a, b, 20), [np.max(r)])) + >>> ax.hist(r, bins=bins, density=True, histtype='stepfilled', alpha=0.2) + >>> ax.set_xlim([x[0], x[-1]]) + >>> ax.legend(loc='best', frameon=False) + >>> plt.show() + + """ + _support_mask = rv_continuous._open_support_mask + + def _shape_info(self): + return [] + + def _pdf(self, x): + # levy.pdf(x) = 1 / (x * sqrt(2*pi*x)) * exp(-1/(2*x)) + return 1 / np.sqrt(2*np.pi*x) / x * np.exp(-1/(2*x)) + + def _cdf(self, x): + # Equivalent to 2*norm.sf(np.sqrt(1/x)) + return sc.erfc(np.sqrt(0.5 / x)) + + def _sf(self, x): + return sc.erf(np.sqrt(0.5 / x)) + + def _ppf(self, q): + # Equivalent to 1.0/(norm.isf(q/2)**2) or 0.5/(erfcinv(q)**2) + val = _norm_isf(q/2) + return 1.0 / (val * val) + + def _isf(self, p): + return 1/(2*sc.erfinv(p)**2) + + def _stats(self): + return np.inf, np.inf, np.nan, np.nan + + +levy = levy_gen(a=0.0, name="levy") + + +class levy_l_gen(rv_continuous): + r"""A left-skewed Levy continuous random variable. + + %(before_notes)s + + See Also + -------- + levy, levy_stable + + Notes + ----- + The probability density function for `levy_l` is: + + .. math:: + f(x) = \frac{1}{|x| \sqrt{2\pi |x|}} \exp{ \left(-\frac{1}{2|x|} \right)} + + for :math:`x < 0`. + + This is the same as the Levy-stable distribution with :math:`a=1/2` and + :math:`b=-1`. + + %(after_notes)s + + Examples + -------- + >>> import numpy as np + >>> from scipy.stats import levy_l + >>> import matplotlib.pyplot as plt + >>> fig, ax = plt.subplots(1, 1) + + Calculate the first four moments: + + >>> mean, var, skew, kurt = levy_l.stats(moments='mvsk') + + Display the probability density function (``pdf``): + + >>> # `levy_l` is very heavy-tailed. + >>> # To show a nice plot, let's cut off the lower 40 percent. + >>> a, b = levy_l.ppf(0.4), levy_l.ppf(1) + >>> x = np.linspace(a, b, 100) + >>> ax.plot(x, levy_l.pdf(x), + ... 'r-', lw=5, alpha=0.6, label='levy_l pdf') + + Alternatively, the distribution object can be called (as a function) + to fix the shape, location and scale parameters. This returns a "frozen" + RV object holding the given parameters fixed. + + Freeze the distribution and display the frozen ``pdf``: + + >>> rv = levy_l() + >>> ax.plot(x, rv.pdf(x), 'k-', lw=2, label='frozen pdf') + + Check accuracy of ``cdf`` and ``ppf``: + + >>> vals = levy_l.ppf([0.001, 0.5, 0.999]) + >>> np.allclose([0.001, 0.5, 0.999], levy_l.cdf(vals)) + True + + Generate random numbers: + + >>> r = levy_l.rvs(size=1000) + + And compare the histogram: + + >>> # manual binning to ignore the tail + >>> bins = np.concatenate(([np.min(r)], np.linspace(a, b, 20))) + >>> ax.hist(r, bins=bins, density=True, histtype='stepfilled', alpha=0.2) + >>> ax.set_xlim([x[0], x[-1]]) + >>> ax.legend(loc='best', frameon=False) + >>> plt.show() + + """ + _support_mask = rv_continuous._open_support_mask + + def _shape_info(self): + return [] + + def _pdf(self, x): + # levy_l.pdf(x) = 1 / (abs(x) * sqrt(2*pi*abs(x))) * exp(-1/(2*abs(x))) + ax = abs(x) + return 1/np.sqrt(2*np.pi*ax)/ax*np.exp(-1/(2*ax)) + + def _cdf(self, x): + ax = abs(x) + return 2 * _norm_cdf(1 / np.sqrt(ax)) - 1 + + def _sf(self, x): + ax = abs(x) + return 2 * _norm_sf(1 / np.sqrt(ax)) + + def _ppf(self, q): + val = _norm_ppf((q + 1.0) / 2) + return -1.0 / (val * val) + + def _isf(self, p): + return -1/_norm_isf(p/2)**2 + + def _stats(self): + return np.inf, np.inf, np.nan, np.nan + + +levy_l = levy_l_gen(b=0.0, name="levy_l") + + +class logistic_gen(rv_continuous): + r"""A logistic (or Sech-squared) continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `logistic` is: + + .. math:: + + f(x) = \frac{\exp(-x)} + {(1+\exp(-x))^2} + + `logistic` is a special case of `genlogistic` with ``c=1``. + + Remark that the survival function (``logistic.sf``) is equal to the + Fermi-Dirac distribution describing fermionic statistics. + + %(after_notes)s + + %(example)s + + """ + def _shape_info(self): + return [] + + def _rvs(self, size=None, random_state=None): + return random_state.logistic(size=size) + + def _pdf(self, x): + # logistic.pdf(x) = exp(-x) / (1+exp(-x))**2 + return np.exp(self._logpdf(x)) + + def _logpdf(self, x): + y = -np.abs(x) + return y - 2. * sc.log1p(np.exp(y)) + + def _cdf(self, x): + return sc.expit(x) + + def _logcdf(self, x): + return sc.log_expit(x) + + def _ppf(self, q): + return sc.logit(q) + + def _sf(self, x): + return sc.expit(-x) + + def _logsf(self, x): + return sc.log_expit(-x) + + def _isf(self, q): + return -sc.logit(q) + + def _stats(self): + return 0, np.pi*np.pi/3.0, 0, 6.0/5.0 + + def _entropy(self): + # https://en.wikipedia.org/wiki/Logistic_distribution + return 2.0 + + @_call_super_mom + @inherit_docstring_from(rv_continuous) + def fit(self, data, *args, **kwds): + if kwds.pop('superfit', False): + return super().fit(data, *args, **kwds) + + data, floc, fscale = _check_fit_input_parameters(self, data, + args, kwds) + n = len(data) + + # rv_continuous provided guesses + loc, scale = self._fitstart(data) + # these are trumped by user-provided guesses + loc, scale = kwds.get('loc', loc), kwds.get('scale', scale) + + # the maximum likelihood estimators `a` and `b` of the location and + # scale parameters are roots of the two equations described in `func`. + # Source: Statistical Distributions, 3rd Edition. Evans, Hastings, and + # Peacock (2000), Page 130 + + def dl_dloc(loc, scale=fscale): + c = (data - loc) / scale + return np.sum(sc.expit(c)) - n/2 + + def dl_dscale(scale, loc=floc): + c = (data - loc) / scale + return np.sum(c*np.tanh(c/2)) - n + + def func(params): + loc, scale = params + return dl_dloc(loc, scale), dl_dscale(scale, loc) + + if fscale is not None and floc is None: + res = optimize.root(dl_dloc, (loc,)) + loc = res.x[0] + scale = fscale + elif floc is not None and fscale is None: + res = optimize.root(dl_dscale, (scale,)) + scale = res.x[0] + loc = floc + else: + res = optimize.root(func, (loc, scale)) + loc, scale = res.x + + # Note: gh-18176 reported data for which the reported MLE had + # `scale < 0`. To fix the bug, we return abs(scale). This is OK because + # `dl_dscale` and `dl_dloc` are even and odd functions of `scale`, + # respectively, so if `-scale` is a solution, so is `scale`. + scale = abs(scale) + return ((loc, scale) if res.success + else super().fit(data, *args, **kwds)) + + +logistic = logistic_gen(name='logistic') + + +class loggamma_gen(rv_continuous): + r"""A log gamma continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `loggamma` is: + + .. math:: + + f(x, c) = \frac{\exp(c x - \exp(x))} + {\Gamma(c)} + + for all :math:`x, c > 0`. Here, :math:`\Gamma` is the + gamma function (`scipy.special.gamma`). + + `loggamma` takes ``c`` as a shape parameter for :math:`c`. + + %(after_notes)s + + %(example)s + + """ + + def _shape_info(self): + return [_ShapeInfo("c", False, (0, np.inf), (False, False))] + + def _rvs(self, c, size=None, random_state=None): + # Use the property of the gamma distribution Gamma(c) + # Gamma(c) ~ Gamma(c + 1)*U**(1/c), + # where U is uniform on [0, 1]. (See, e.g., + # G. Marsaglia and W.W. Tsang, "A simple method for generating gamma + # variables", https://doi.org/10.1145/358407.358414) + # So + # log(Gamma(c)) ~ log(Gamma(c + 1)) + log(U)/c + # Generating a sample with this formulation is a bit slower + # than the more obvious log(Gamma(c)), but it avoids loss + # of precision when c << 1. + return (np.log(random_state.gamma(c + 1, size=size)) + + np.log(random_state.uniform(size=size))/c) + + def _pdf(self, x, c): + # loggamma.pdf(x, c) = exp(c*x-exp(x)) / gamma(c) + return np.exp(c*x-np.exp(x)-sc.gammaln(c)) + + def _logpdf(self, x, c): + return c*x - np.exp(x) - sc.gammaln(c) + + def _cdf(self, x, c): + # This function is gammainc(c, exp(x)), where gammainc(c, z) is + # the regularized incomplete gamma function. + # The first term in a series expansion of gamminc(c, z) is + # z**c/Gamma(c+1); see 6.5.29 of Abramowitz & Stegun (and refer + # back to 6.5.1, 6.5.2 and 6.5.4 for the relevant notation). + # This can also be found in the wikipedia article + # https://en.wikipedia.org/wiki/Incomplete_gamma_function. + # Here we use that formula when x is sufficiently negative that + # exp(x) will result in subnormal numbers and lose precision. + # We evaluate the log of the expression first to allow the possible + # cancellation of the terms in the division, and then exponentiate. + # That is, + # exp(x)**c/Gamma(c+1) = exp(log(exp(x)**c/Gamma(c+1))) + # = exp(c*x - gammaln(c+1)) + return xpx.apply_where( + x < _LOGXMIN, (x, c), + lambda x, c: np.exp(c*x - sc.gammaln(c+1)), + lambda x, c: sc.gammainc(c, np.exp(x))) + + def _ppf(self, q, c): + # The expression used when g < _XMIN inverts the one term expansion + # given in the comments of _cdf(). + g = sc.gammaincinv(c, q) + return xpx.apply_where( + g < _XMIN, (g, q, c), + lambda g, q, c: (np.log(q) + sc.gammaln(c+1))/c, + lambda g, q, c: np.log(g)) + + def _sf(self, x, c): + # See the comments for _cdf() for how x < _LOGXMIN is handled. + return xpx.apply_where( + x < _LOGXMIN, (x, c), + lambda x, c: -np.expm1(c*x - sc.gammaln(c+1)), + lambda x, c: sc.gammaincc(c, np.exp(x))) + + def _isf(self, q, c): + # The expression used when g < _XMIN inverts the complement of + # the one term expansion given in the comments of _cdf(). + g = sc.gammainccinv(c, q) + return xpx.apply_where( + g < _XMIN, (g, q, c), + lambda g, q, c: (np.log1p(-q) + sc.gammaln(c+1))/c, + lambda g, q, c: np.log(g)) + + def _stats(self, c): + # See, for example, "A Statistical Study of Log-Gamma Distribution", by + # Ping Shing Chan (thesis, McMaster University, 1993). + mean = sc.digamma(c) + var = sc.polygamma(1, c) + skewness = sc.polygamma(2, c) / np.power(var, 1.5) + excess_kurtosis = sc.polygamma(3, c) / (var*var) + return mean, var, skewness, excess_kurtosis + + def _entropy(self, c): + def regular(c): + h = sc.gammaln(c) - c * sc.digamma(c) + c + return h + + def asymptotic(c): + # using asymptotic expansions for gammaln and psi (see gh-18093) + term = -0.5*np.log(c) + c**-1./6 - c**-3./90 + c**-5./210 + h = norm._entropy() + term + return h + + return xpx.apply_where(c >= 45, c, asymptotic, regular) + + +loggamma = loggamma_gen(name='loggamma') + + +class loglaplace_gen(rv_continuous): + r"""A log-Laplace continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `loglaplace` is: + + .. math:: + + f(x, c) = \begin{cases}\frac{c}{2} x^{ c-1} &\text{for } 0 < x < 1\\ + \frac{c}{2} x^{-c-1} &\text{for } x \ge 1 + \end{cases} + + for :math:`c > 0`. + + `loglaplace` takes ``c`` as a shape parameter for :math:`c`. + + %(after_notes)s + + Suppose a random variable ``X`` follows the Laplace distribution with + location ``a`` and scale ``b``. Then ``Y = exp(X)`` follows the + log-Laplace distribution with ``c = 1 / b`` and ``scale = exp(a)``. + + References + ---------- + T.J. Kozubowski and K. Podgorski, "A log-Laplace growth rate model", + The Mathematical Scientist, vol. 28, pp. 49-60, 2003. + + %(example)s + + """ + def _shape_info(self): + return [_ShapeInfo("c", False, (0, np.inf), (False, False))] + + def _pdf(self, x, c): + # loglaplace.pdf(x, c) = c / 2 * x**(c-1), for 0 < x < 1 + # = c / 2 * x**(-c-1), for x >= 1 + cd2 = c/2.0 + c = np.where(x < 1, c, -c) + return cd2*x**(c-1) + + def _cdf(self, x, c): + return np.where(x < 1, 0.5*x**c, 1-0.5*x**(-c)) + + def _sf(self, x, c): + return np.where(x < 1, 1 - 0.5*x**c, 0.5*x**(-c)) + + def _ppf(self, q, c): + return np.where(q < 0.5, (2.0*q)**(1.0/c), (2*(1.0-q))**(-1.0/c)) + + def _isf(self, q, c): + return np.where(q > 0.5, (2.0*(1.0 - q))**(1.0/c), (2*q)**(-1.0/c)) + + def _munp(self, n, c): + with np.errstate(divide='ignore'): + c2, n2 = c**2, n**2 + return np.where(n2 < c2, c2 / (c2 - n2), np.inf) + + def _entropy(self, c): + return np.log(2.0/c) + 1.0 + + @_call_super_mom + @inherit_docstring_from(rv_continuous) + def fit(self, data, *args, **kwds): + data, fc, floc, fscale = _check_fit_input_parameters(self, data, + args, kwds) + + # Specialize MLE only when location is known. + if floc is None: + return super(type(self), self).fit(data, *args, **kwds) + + # Raise an error if any observation has zero likelihood. + if np.any(data <= floc): + raise FitDataError("loglaplace", lower=floc, upper=np.inf) + + # Remove location from data. + if floc != 0: + data = data - floc + + # When location is zero, the log-Laplace distribution is related to + # the Laplace distribution in that if X ~ Laplace(loc=a, scale=b), + # then Y = exp(X) ~ LogLaplace(c=1/b, loc=0, scale=exp(a)). It can + # be shown that the MLE for Y is the same as the MLE for X = ln(Y). + # Therefore, we reuse the formulas from laplace.fit() and transform + # the result back into log-laplace's parameter space. + a, b = laplace.fit(np.log(data), + floc=np.log(fscale) if fscale is not None else None, + fscale=1/fc if fc is not None else None, + method='mle') + loc = floc + scale = np.exp(a) if fscale is None else fscale + c = 1 / b if fc is None else fc + return c, loc, scale + +loglaplace = loglaplace_gen(a=0.0, name='loglaplace') + + +def _lognorm_logpdf(x, s): + return xpx.apply_where( + x != 0, (x, s), + lambda x, s: (-np.log(x)**2 / (2 * s**2) + - np.log(s * x * np.sqrt(2 * np.pi))), + fill_value=-np.inf) + + +class lognorm_gen(rv_continuous): + r"""A lognormal continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `lognorm` is: + + .. math:: + + f(x, s) = \frac{1}{s x \sqrt{2\pi}} + \exp\left(-\frac{\log^2(x)}{2s^2}\right) + + for :math:`x > 0`, :math:`s > 0`. + + `lognorm` takes ``s`` as a shape parameter for :math:`s`. + + %(after_notes)s + + Suppose a normally distributed random variable ``X`` has mean ``mu`` and + standard deviation ``sigma``. Then ``Y = exp(X)`` is lognormally + distributed with ``s = sigma`` and ``scale = exp(mu)``. + + %(example)s + + The logarithm of a log-normally distributed random variable is + normally distributed: + + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from scipy import stats + >>> fig, ax = plt.subplots(1, 1) + >>> mu, sigma = 2, 0.5 + >>> X = stats.norm(loc=mu, scale=sigma) + >>> Y = stats.lognorm(s=sigma, scale=np.exp(mu)) + >>> x = np.linspace(*X.interval(0.999)) + >>> y = Y.rvs(size=10000) + >>> ax.plot(x, X.pdf(x), label='X (pdf)') + >>> ax.hist(np.log(y), density=True, bins=x, label='log(Y) (histogram)') + >>> ax.legend() + >>> plt.show() + + """ + _support_mask = rv_continuous._open_support_mask + + def _shape_info(self): + return [_ShapeInfo("s", False, (0, np.inf), (False, False))] + + def _rvs(self, s, size=None, random_state=None): + return np.exp(s * random_state.standard_normal(size)) + + def _pdf(self, x, s): + # lognorm.pdf(x, s) = 1 / (s*x*sqrt(2*pi)) * exp(-1/2*(log(x)/s)**2) + return np.exp(self._logpdf(x, s)) + + def _logpdf(self, x, s): + return _lognorm_logpdf(x, s) + + def _cdf(self, x, s): + return _norm_cdf(np.log(x) / s) + + def _logcdf(self, x, s): + return _norm_logcdf(np.log(x) / s) + + def _ppf(self, q, s): + return np.exp(s * _norm_ppf(q)) + + def _sf(self, x, s): + return _norm_sf(np.log(x) / s) + + def _logsf(self, x, s): + return _norm_logsf(np.log(x) / s) + + def _isf(self, q, s): + return np.exp(s * _norm_isf(q)) + + def _stats(self, s): + p = np.exp(s*s) + mu = np.sqrt(p) + mu2 = p*(p-1) + g1 = np.sqrt(p-1)*(2+p) + g2 = np.polyval([1, 2, 3, 0, -6.0], p) + return mu, mu2, g1, g2 + + def _entropy(self, s): + return 0.5 * (1 + np.log(2*np.pi) + 2 * np.log(s)) + + @_call_super_mom + @extend_notes_in_docstring(rv_continuous, notes="""\ + When `method='MLE'` and + the location parameter is fixed by using the `floc` argument, + this function uses explicit formulas for the maximum likelihood + estimation of the log-normal shape and scale parameters, so the + `optimizer`, `loc` and `scale` keyword arguments are ignored. + If the location is free, a likelihood maximum is found by + setting its partial derivative w.r.t. location to 0, and + solving by substituting the analytical expressions of shape + and scale (or provided parameters). + See, e.g., equation 3.1 in + A. Clifford Cohen & Betty Jones Whitten (1980) + Estimation in the Three-Parameter Lognormal Distribution, + Journal of the American Statistical Association, 75:370, 399-404 + https://doi.org/10.2307/2287466 + \n\n""") + def fit(self, data, *args, **kwds): + if kwds.pop('superfit', False): + return super().fit(data, *args, **kwds) + + parameters = _check_fit_input_parameters(self, data, args, kwds) + data, fshape, floc, fscale = parameters + data_min = np.min(data) + + def get_shape_scale(loc): + # Calculate maximum likelihood scale and shape with analytical + # formulas unless provided by the user + if fshape is None or fscale is None: + lndata = np.log(data - loc) + scale = fscale or np.exp(lndata.mean()) + shape = fshape or np.sqrt(np.mean((lndata - np.log(scale))**2)) + return shape, scale + + def dL_dLoc(loc): + # Derivative of (positive) LL w.r.t. loc + shape, scale = get_shape_scale(loc) + shifted = data - loc + return np.sum((1 + np.log(shifted/scale)/shape**2)/shifted) + + def ll(loc): + # (Positive) log-likelihood + shape, scale = get_shape_scale(loc) + return -self.nnlf((shape, loc, scale), data) + + if floc is None: + # The location must be less than the minimum of the data. + # Back off a bit to avoid numerical issues. + spacing = np.spacing(data_min) + rbrack = data_min - spacing + + # Find the right end of the bracket by successive doubling of the + # distance to data_min. We're interested in a maximum LL, so the + # slope dL_dLoc_rbrack should be negative at the right end. + # optimization for later: share shape, scale + dL_dLoc_rbrack = dL_dLoc(rbrack) + ll_rbrack = ll(rbrack) + delta = 2 * spacing # 2 * (data_min - rbrack) + while dL_dLoc_rbrack >= -1e-6: + rbrack = data_min - delta + dL_dLoc_rbrack = dL_dLoc(rbrack) + delta *= 2 + + if not np.isfinite(rbrack) or not np.isfinite(dL_dLoc_rbrack): + # If we never find a negative slope, either we missed it or the + # slope is always positive. It's usually the latter, + # which means + # loc = data_min - spacing + # But sometimes when shape and/or scale are fixed there are + # other issues, so be cautious. + return super().fit(data, *args, **kwds) + + # Now find the left end of the bracket. Guess is `rbrack-1` + # unless that is too small of a difference to resolve. Double + # the size of the interval until the left end is found. + lbrack = np.minimum(np.nextafter(rbrack, -np.inf), rbrack-1) + dL_dLoc_lbrack = dL_dLoc(lbrack) + delta = 2 * (rbrack - lbrack) + while (np.isfinite(lbrack) and np.isfinite(dL_dLoc_lbrack) + and np.sign(dL_dLoc_lbrack) == np.sign(dL_dLoc_rbrack)): + lbrack = rbrack - delta + dL_dLoc_lbrack = dL_dLoc(lbrack) + delta *= 2 + + # I don't recall observing this, but just in case... + if not np.isfinite(lbrack) or not np.isfinite(dL_dLoc_lbrack): + return super().fit(data, *args, **kwds) + + # If we have a valid bracket, find the root + res = root_scalar(dL_dLoc, bracket=(lbrack, rbrack)) + if not res.converged: + return super().fit(data, *args, **kwds) + + # If the slope was positive near the minimum of the data, + # the maximum LL could be there instead of at the root. Compare + # the LL of the two points to decide. + ll_root = ll(res.root) + loc = res.root if ll_root > ll_rbrack else data_min-spacing + + else: + if floc >= data_min: + raise FitDataError("lognorm", lower=0., upper=np.inf) + loc = floc + + shape, scale = get_shape_scale(loc) + if not (self._argcheck(shape) and scale > 0): + return super().fit(data, *args, **kwds) + return shape, loc, scale + + +lognorm = lognorm_gen(a=0.0, name='lognorm') + + +class gibrat_gen(rv_continuous): + r"""A Gibrat continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `gibrat` is: + + .. math:: + + f(x) = \frac{1}{x \sqrt{2\pi}} \exp(-\frac{1}{2} (\log(x))^2) + + for :math:`x >= 0`. + + `gibrat` is a special case of `lognorm` with ``s=1``. + + %(after_notes)s + + %(example)s + + """ + _support_mask = rv_continuous._open_support_mask + + def _shape_info(self): + return [] + + def _rvs(self, size=None, random_state=None): + return np.exp(random_state.standard_normal(size)) + + def _pdf(self, x): + # gibrat.pdf(x) = 1/(x*sqrt(2*pi)) * exp(-1/2*(log(x))**2) + return np.exp(self._logpdf(x)) + + def _logpdf(self, x): + return _lognorm_logpdf(x, 1.0) + + def _cdf(self, x): + return _norm_cdf(np.log(x)) + + def _ppf(self, q): + return np.exp(_norm_ppf(q)) + + def _sf(self, x): + return _norm_sf(np.log(x)) + + def _isf(self, p): + return np.exp(_norm_isf(p)) + + def _stats(self): + p = np.e + mu = np.sqrt(p) + mu2 = p * (p - 1) + g1 = np.sqrt(p - 1) * (2 + p) + g2 = np.polyval([1, 2, 3, 0, -6.0], p) + return mu, mu2, g1, g2 + + def _entropy(self): + return 0.5 * np.log(2 * np.pi) + 0.5 + + +gibrat = gibrat_gen(a=0.0, name='gibrat') + + +class maxwell_gen(rv_continuous): + r"""A Maxwell continuous random variable. + + %(before_notes)s + + Notes + ----- + A special case of a `chi` distribution, with ``df=3``, ``loc=0.0``, + and given ``scale = a``, where ``a`` is the parameter used in the + Mathworld description [1]_. + + The probability density function for `maxwell` is: + + .. math:: + + f(x) = \sqrt{2/\pi}x^2 \exp(-x^2/2) + + for :math:`x >= 0`. + + %(after_notes)s + + References + ---------- + .. [1] http://mathworld.wolfram.com/MaxwellDistribution.html + + %(example)s + """ + def _shape_info(self): + return [] + + def _rvs(self, size=None, random_state=None): + return chi.rvs(3.0, size=size, random_state=random_state) + + def _pdf(self, x): + # maxwell.pdf(x) = sqrt(2/pi)x**2 * exp(-x**2/2) + return _SQRT_2_OVER_PI*x*x*np.exp(-x*x/2.0) + + def _logpdf(self, x): + # Allow x=0 without 'divide by zero' warnings + with np.errstate(divide='ignore'): + return _LOG_SQRT_2_OVER_PI + 2*np.log(x) - 0.5*x*x + + def _cdf(self, x): + return sc.gammainc(1.5, x*x/2.0) + + def _ppf(self, q): + return np.sqrt(2*sc.gammaincinv(1.5, q)) + + def _sf(self, x): + return sc.gammaincc(1.5, x*x/2.0) + + def _isf(self, q): + return np.sqrt(2*sc.gammainccinv(1.5, q)) + + def _stats(self): + val = 3*np.pi-8 + return (2*np.sqrt(2.0/np.pi), + 3-8/np.pi, + np.sqrt(2)*(32-10*np.pi)/val**1.5, + (-12*np.pi*np.pi + 160*np.pi - 384) / val**2.0) + + def _entropy(self): + return _EULER + 0.5*np.log(2*np.pi)-0.5 + + +maxwell = maxwell_gen(a=0.0, name='maxwell') + + +class mielke_gen(rv_continuous): + r"""A Mielke Beta-Kappa / Dagum continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `mielke` is: + + .. math:: + + f(x, k, s) = \frac{k x^{k-1}}{(1+x^s)^{1+k/s}} + + for :math:`x > 0` and :math:`k, s > 0`. The distribution is sometimes + called Dagum distribution ([2]_). It was already defined in [3]_, called + a Burr Type III distribution (`burr` with parameters ``c=s`` and + ``d=k/s``). + + `mielke` takes ``k`` and ``s`` as shape parameters. + + %(after_notes)s + + References + ---------- + .. [1] Mielke, P.W., 1973 "Another Family of Distributions for Describing + and Analyzing Precipitation Data." J. Appl. Meteor., 12, 275-280 + .. [2] Dagum, C., 1977 "A new model for personal income distribution." + Economie Appliquee, 33, 327-367. + .. [3] Burr, I. W. "Cumulative frequency functions", Annals of + Mathematical Statistics, 13(2), pp 215-232 (1942). + + %(example)s + + """ + def _shape_info(self): + ik = _ShapeInfo("k", False, (0, np.inf), (False, False)) + i_s = _ShapeInfo("s", False, (0, np.inf), (False, False)) + return [ik, i_s] + + def _pdf(self, x, k, s): + return k*x**(k-1.0) / (1.0+x**s)**(1.0+k*1.0/s) + + def _logpdf(self, x, k, s): + # Allow x=0 without 'divide by zero' warnings. + with np.errstate(divide='ignore'): + return np.log(k) + np.log(x)*(k - 1) - np.log1p(x**s)*(1 + k/s) + + def _cdf(self, x, k, s): + return x**k / (1.0+x**s)**(k*1.0/s) + + def _ppf(self, q, k, s): + qsk = pow(q, s*1.0/k) + return pow(qsk/(1.0-qsk), 1.0/s) + + def _munp(self, n, k, s): + def nth_moment(n, k, s): + # n-th moment is defined for -k < n < s + return sc.gamma((k+n)/s)*sc.gamma(1-n/s)/sc.gamma(k/s) + + return xpx.apply_where(n < s, (n, k, s), nth_moment, fill_value=np.inf) + + +mielke = mielke_gen(a=0.0, name='mielke') + + +class kappa4_gen(rv_continuous): + r"""Kappa 4 parameter distribution. + + %(before_notes)s + + Notes + ----- + The probability density function for kappa4 is: + + .. math:: + + f(x, h, k) = (1 - k x)^{1/k - 1} (1 - h (1 - k x)^{1/k})^{1/h-1} + + if :math:`h` and :math:`k` are not equal to 0. + + If :math:`h` or :math:`k` are zero then the pdf can be simplified: + + :math:`h = 0` and :math:`k \neq 0`:: + + kappa4.pdf(x, h, k) = (1.0 - k*x)**(1.0/k - 1.0)* + exp(-(1.0 - k*x)**(1.0/k)) + + :math:`h \neq 0` and :math:`k = 0`:: + + kappa4.pdf(x, h, k) = exp(-x)*(1.0 - h*exp(-x))**(1.0/h - 1.0) + + :math:`h = 0` and :math:`k = 0`:: + + kappa4.pdf(x, h, k) = exp(-x)*exp(-exp(-x)) + + kappa4 takes :math:`h` and :math:`k` as shape parameters. + + The kappa4 distribution returns other distributions when certain + :math:`h` and :math:`k` values are used. + + +------+-------------+----------------+------------------+ + | h | k=0.0 | k=1.0 | -inf<=k<=inf | + +======+=============+================+==================+ + | -1.0 | Logistic | | Generalized | + | | | | Logistic(1) | + | | | | | + | | logistic(x) | | | + +------+-------------+----------------+------------------+ + | 0.0 | Gumbel | Reverse | Generalized | + | | | Exponential(2) | Extreme Value | + | | | | | + | | gumbel_r(x) | | genextreme(x, k) | + +------+-------------+----------------+------------------+ + | 1.0 | Exponential | Uniform | Generalized | + | | | | Pareto | + | | | | | + | | expon(x) | uniform(x) | genpareto(x, -k) | + +------+-------------+----------------+------------------+ + + (1) There are at least five generalized logistic distributions. + Four are described here: + https://en.wikipedia.org/wiki/Generalized_logistic_distribution + The "fifth" one is the one kappa4 should match which currently + isn't implemented in scipy: + https://en.wikipedia.org/wiki/Talk:Generalized_logistic_distribution + https://www.mathwave.com/help/easyfit/html/analyses/distributions/gen_logistic.html + (2) This distribution is currently not in scipy. + + References + ---------- + J.C. Finney, "Optimization of a Skewed Logistic Distribution With Respect + to the Kolmogorov-Smirnov Test", A Dissertation Submitted to the Graduate + Faculty of the Louisiana State University and Agricultural and Mechanical + College, (August, 2004), + https://digitalcommons.lsu.edu/gradschool_dissertations/3672 + + J.R.M. Hosking, "The four-parameter kappa distribution". IBM J. Res. + Develop. 38 (3), 25 1-258 (1994). + + B. Kumphon, A. Kaew-Man, P. Seenoi, "A Rainfall Distribution for the Lampao + Site in the Chi River Basin, Thailand", Journal of Water Resource and + Protection, vol. 4, 866-869, (2012). + :doi:`10.4236/jwarp.2012.410101` + + C. Winchester, "On Estimation of the Four-Parameter Kappa Distribution", A + Thesis Submitted to Dalhousie University, Halifax, Nova Scotia, (March + 2000). + http://www.nlc-bnc.ca/obj/s4/f2/dsk2/ftp01/MQ57336.pdf + + %(after_notes)s + + %(example)s + + """ + def _argcheck(self, h, k): + shape = np.broadcast_arrays(h, k)[0].shape + return np.full(shape, fill_value=True) + + def _shape_info(self): + ih = _ShapeInfo("h", False, (-np.inf, np.inf), (False, False)) + ik = _ShapeInfo("k", False, (-np.inf, np.inf), (False, False)) + return [ih, ik] + + def _get_support(self, h, k): + condlist = [np.logical_and(h > 0, k > 0), + np.logical_and(h > 0, k == 0), + np.logical_and(h > 0, k < 0), + np.logical_and(h <= 0, k > 0), + np.logical_and(h <= 0, k == 0), + np.logical_and(h <= 0, k < 0)] + + def f0(h, k): + return (1.0 - np.float_power(h, -k))/k + + def f1(h, k): + return np.log(h) + + def f3(h, k): + a = np.empty(np.shape(h)) + a[:] = -np.inf + return a + + def f5(h, k): + return 1.0/k + + _a = _lazyselect(condlist, + [f0, f1, f0, f3, f3, f5], + [h, k], + default=np.nan) + + def f0(h, k): + return 1.0/k + + def f1(h, k): + a = np.empty(np.shape(h)) + a[:] = np.inf + return a + + _b = _lazyselect(condlist, + [f0, f1, f1, f0, f1, f1], + [h, k], + default=np.nan) + return _a, _b + + def _pdf(self, x, h, k): + # kappa4.pdf(x, h, k) = (1.0 - k*x)**(1.0/k - 1.0)* + # (1.0 - h*(1.0 - k*x)**(1.0/k))**(1.0/h-1) + return np.exp(self._logpdf(x, h, k)) + + def _logpdf(self, x, h, k): + condlist = [np.logical_and(h != 0, k != 0), + np.logical_and(h == 0, k != 0), + np.logical_and(h != 0, k == 0), + np.logical_and(h == 0, k == 0)] + + def f0(x, h, k): + '''pdf = (1.0 - k*x)**(1.0/k - 1.0)*( + 1.0 - h*(1.0 - k*x)**(1.0/k))**(1.0/h-1.0) + logpdf = ... + ''' + return (sc.xlog1py(1.0/k - 1.0, -k*x) + + sc.xlog1py(1.0/h - 1.0, -h*(1.0 - k*x)**(1.0/k))) + + def f1(x, h, k): + '''pdf = (1.0 - k*x)**(1.0/k - 1.0)*np.exp(-( + 1.0 - k*x)**(1.0/k)) + logpdf = ... + ''' + return sc.xlog1py(1.0/k - 1.0, -k*x) - (1.0 - k*x)**(1.0/k) + + def f2(x, h, k): + '''pdf = np.exp(-x)*(1.0 - h*np.exp(-x))**(1.0/h - 1.0) + logpdf = ... + ''' + return -x + sc.xlog1py(1.0/h - 1.0, -h*np.exp(-x)) + + def f3(x, h, k): + '''pdf = np.exp(-x-np.exp(-x)) + logpdf = ... + ''' + return -x - np.exp(-x) + + return _lazyselect(condlist, + [f0, f1, f2, f3], + [x, h, k], + default=np.nan) + + def _cdf(self, x, h, k): + return np.exp(self._logcdf(x, h, k)) + + def _logcdf(self, x, h, k): + condlist = [np.logical_and(h != 0, k != 0), + np.logical_and(h == 0, k != 0), + np.logical_and(h != 0, k == 0), + np.logical_and(h == 0, k == 0)] + + def f0(x, h, k): + '''cdf = (1.0 - h*(1.0 - k*x)**(1.0/k))**(1.0/h) + logcdf = ... + ''' + return (1.0/h)*sc.log1p(-h*(1.0 - k*x)**(1.0/k)) + + def f1(x, h, k): + '''cdf = np.exp(-(1.0 - k*x)**(1.0/k)) + logcdf = ... + ''' + return -(1.0 - k*x)**(1.0/k) + + def f2(x, h, k): + '''cdf = (1.0 - h*np.exp(-x))**(1.0/h) + logcdf = ... + ''' + return (1.0/h)*sc.log1p(-h*np.exp(-x)) + + def f3(x, h, k): + '''cdf = np.exp(-np.exp(-x)) + logcdf = ... + ''' + return -np.exp(-x) + + return _lazyselect(condlist, + [f0, f1, f2, f3], + [x, h, k], + default=np.nan) + + def _ppf(self, q, h, k): + condlist = [np.logical_and(h != 0, k != 0), + np.logical_and(h == 0, k != 0), + np.logical_and(h != 0, k == 0), + np.logical_and(h == 0, k == 0)] + + def f0(q, h, k): + return 1.0/k*(1.0 - ((1.0 - (q**h))/h)**k) + + def f1(q, h, k): + return 1.0/k*(1.0 - (-np.log(q))**k) + + def f2(q, h, k): + '''ppf = -np.log((1.0 - (q**h))/h) + ''' + return -sc.log1p(-(q**h)) + np.log(h) + + def f3(q, h, k): + return -np.log(-np.log(q)) + + return _lazyselect(condlist, + [f0, f1, f2, f3], + [q, h, k], + default=np.nan) + + def _get_stats_info(self, h, k): + condlist = [ + np.logical_and(h < 0, k >= 0), + k < 0, + ] + + def f0(h, k): + return (-1.0/h*k).astype(int) + + def f1(h, k): + return (-1.0/k).astype(int) + + return _lazyselect(condlist, [f0, f1], [h, k], default=5) + + def _stats(self, h, k): + maxr = self._get_stats_info(h, k) + outputs = [None if np.any(r < maxr) else np.nan for r in range(1, 5)] + return outputs[:] + + def _mom1_sc(self, m, *args): + maxr = self._get_stats_info(args[0], args[1]) + if m >= maxr: + return np.nan + return integrate.quad(self._mom_integ1, 0, 1, args=(m,)+args)[0] + + +kappa4 = kappa4_gen(name='kappa4') + + +class kappa3_gen(rv_continuous): + r"""Kappa 3 parameter distribution. + + %(before_notes)s + + Notes + ----- + The probability density function for `kappa3` is: + + .. math:: + + f(x, a) = a (a + x^a)^{-(a + 1)/a} + + for :math:`x > 0` and :math:`a > 0`. + + `kappa3` takes ``a`` as a shape parameter for :math:`a`. + + References + ---------- + P.W. Mielke and E.S. Johnson, "Three-Parameter Kappa Distribution Maximum + Likelihood and Likelihood Ratio Tests", Methods in Weather Research, + 701-707, (September, 1973), + :doi:`10.1175/1520-0493(1973)101<0701:TKDMLE>2.3.CO;2` + + B. Kumphon, "Maximum Entropy and Maximum Likelihood Estimation for the + Three-Parameter Kappa Distribution", Open Journal of Statistics, vol 2, + 415-419 (2012), :doi:`10.4236/ojs.2012.24050` + + %(after_notes)s + + %(example)s + + """ + def _shape_info(self): + return [_ShapeInfo("a", False, (0, np.inf), (False, False))] + + def _pdf(self, x, a): + # kappa3.pdf(x, a) = a*(a + x**a)**(-(a + 1)/a), for x > 0 + return a*(a + x**a)**(-1.0/a-1) + + def _cdf(self, x, a): + return x*(a + x**a)**(-1.0/a) + + def _sf(self, x, a): + x, a = np.broadcast_arrays(x, a) # some code paths pass scalars + sf = super()._sf(x, a) + + # When the SF is small, another formulation is typically more accurate. + # However, it blows up for large `a`, so use it only if it also returns + # a small value of the SF. + cutoff = 0.01 + i = sf < cutoff + sf2 = -sc.expm1(sc.xlog1py(-1.0 / a[i], a[i] * x[i]**-a[i])) + i2 = sf2 > cutoff + sf2[i2] = sf[i][i2] # replace bad values with original values + + sf[i] = sf2 + return sf + + def _ppf(self, q, a): + return (a/(q**-a - 1.0))**(1.0/a) + + def _isf(self, q, a): + lg = sc.xlog1py(-a, -q) + denom = sc.expm1(lg) + return (a / denom)**(1.0 / a) + + def _stats(self, a): + outputs = [None if np.any(i < a) else np.nan for i in range(1, 5)] + return outputs[:] + + def _mom1_sc(self, m, *args): + if np.any(m >= args[0]): + return np.nan + return integrate.quad(self._mom_integ1, 0, 1, args=(m,)+args)[0] + + +kappa3 = kappa3_gen(a=0.0, name='kappa3') + + +class moyal_gen(rv_continuous): + r"""A Moyal continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `moyal` is: + + .. math:: + + f(x) = \exp(-(x + \exp(-x))/2) / \sqrt{2\pi} + + for a real number :math:`x`. + + %(after_notes)s + + This distribution has utility in high-energy physics and radiation + detection. It describes the energy loss of a charged relativistic + particle due to ionization of the medium [1]_. It also provides an + approximation for the Landau distribution. For an in depth description + see [2]_. For additional description, see [3]_. + + References + ---------- + .. [1] J.E. Moyal, "XXX. Theory of ionization fluctuations", + The London, Edinburgh, and Dublin Philosophical Magazine + and Journal of Science, vol 46, 263-280, (1955). + :doi:`10.1080/14786440308521076` (gated) + .. [2] G. Cordeiro et al., "The beta Moyal: A useful skew distribution", + International Journal of Research and Reviews in Applied Sciences, + vol 10, 171-192, (2012). + https://www.arpapress.com/files/volumes/vol10issue2/ijrras_10_2_02.pdf + .. [3] C. Walck, "Handbook on Statistical Distributions for + Experimentalists; International Report SUF-PFY/96-01", Chapter 26, + University of Stockholm: Stockholm, Sweden, (2007). + http://www.stat.rice.edu/~dobelman/textfiles/DistributionsHandbook.pdf + + .. versionadded:: 1.1.0 + + %(example)s + + """ + def _shape_info(self): + return [] + + def _rvs(self, size=None, random_state=None): + u1 = gamma.rvs(a=0.5, scale=2, size=size, + random_state=random_state) + return -np.log(u1) + + def _pdf(self, x): + return np.exp(-0.5 * (x + np.exp(-x))) / np.sqrt(2*np.pi) + + def _cdf(self, x): + return sc.erfc(np.exp(-0.5 * x) / np.sqrt(2)) + + def _sf(self, x): + return sc.erf(np.exp(-0.5 * x) / np.sqrt(2)) + + def _ppf(self, x): + return -np.log(2 * sc.erfcinv(x)**2) + + def _stats(self): + mu = np.log(2) + np.euler_gamma + mu2 = np.pi**2 / 2 + g1 = 28 * np.sqrt(2) * sc.zeta(3) / np.pi**3 + g2 = 4. + return mu, mu2, g1, g2 + + def _munp(self, n): + if n == 1.0: + return np.log(2) + np.euler_gamma + elif n == 2.0: + return np.pi**2 / 2 + (np.log(2) + np.euler_gamma)**2 + elif n == 3.0: + tmp1 = 1.5 * np.pi**2 * (np.log(2)+np.euler_gamma) + tmp2 = (np.log(2)+np.euler_gamma)**3 + tmp3 = 14 * sc.zeta(3) + return tmp1 + tmp2 + tmp3 + elif n == 4.0: + tmp1 = 4 * 14 * sc.zeta(3) * (np.log(2) + np.euler_gamma) + tmp2 = 3 * np.pi**2 * (np.log(2) + np.euler_gamma)**2 + tmp3 = (np.log(2) + np.euler_gamma)**4 + tmp4 = 7 * np.pi**4 / 4 + return tmp1 + tmp2 + tmp3 + tmp4 + else: + # return generic for higher moments + # return rv_continuous._mom1_sc(self, n, b) + return self._mom1_sc(n) + + +moyal = moyal_gen(name="moyal") + + +class nakagami_gen(rv_continuous): + r"""A Nakagami continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `nakagami` is: + + .. math:: + + f(x, \nu) = \frac{2 \nu^\nu}{\Gamma(\nu)} x^{2\nu-1} \exp(-\nu x^2) + + for :math:`x >= 0`, :math:`\nu > 0`. The distribution was introduced in + [2]_, see also [1]_ for further information. + + `nakagami` takes ``nu`` as a shape parameter for :math:`\nu`. + + %(after_notes)s + + References + ---------- + .. [1] "Nakagami distribution", Wikipedia + https://en.wikipedia.org/wiki/Nakagami_distribution + .. [2] M. Nakagami, "The m-distribution - A general formula of intensity + distribution of rapid fading", Statistical methods in radio wave + propagation, Pergamon Press, 1960, 3-36. + :doi:`10.1016/B978-0-08-009306-2.50005-4` + + %(example)s + + """ + def _argcheck(self, nu): + return nu > 0 + + def _shape_info(self): + return [_ShapeInfo("nu", False, (0, np.inf), (False, False))] + + def _pdf(self, x, nu): + return np.exp(self._logpdf(x, nu)) + + def _logpdf(self, x, nu): + # nakagami.pdf(x, nu) = 2 * nu**nu / gamma(nu) * + # x**(2*nu-1) * exp(-nu*x**2) + return (np.log(2) + sc.xlogy(nu, nu) - sc.gammaln(nu) + + sc.xlogy(2*nu - 1, x) - nu*x**2) + + def _cdf(self, x, nu): + return sc.gammainc(nu, nu*x*x) + + def _ppf(self, q, nu): + return np.sqrt(1.0/nu*sc.gammaincinv(nu, q)) + + def _sf(self, x, nu): + return sc.gammaincc(nu, nu*x*x) + + def _isf(self, p, nu): + return np.sqrt(1/nu * sc.gammainccinv(nu, p)) + + def _stats(self, nu): + mu = sc.poch(nu, 0.5)/np.sqrt(nu) + mu2 = 1.0-mu*mu + g1 = mu * (1 - 4*nu*mu2) / 2.0 / nu / np.power(mu2, 1.5) + g2 = -6*mu**4*nu + (8*nu-2)*mu**2-2*nu + 1 + g2 /= nu*mu2**2.0 + return mu, mu2, g1, g2 + + def _entropy(self, nu): + shape = np.shape(nu) + # because somehow this isn't taken care of by the infrastructure... + nu = np.atleast_1d(nu) + A = sc.gammaln(nu) + B = nu - (nu - 0.5) * sc.digamma(nu) + C = -0.5 * np.log(nu) - np.log(2) + h = A + B + C + # This is the asymptotic sum of A and B (see gh-17868) + norm_entropy = stats.norm._entropy() + # Above, this is lost to rounding error for large nu, so use the + # asymptotic sum when the approximation becomes accurate + i = nu > 5e4 # roundoff error ~ approximation error + # -1 / (12 * nu) is the O(1/nu) term; see gh-17929 + h[i] = C[i] + norm_entropy - 1/(12*nu[i]) + return h.reshape(shape)[()] + + def _rvs(self, nu, size=None, random_state=None): + # this relationship can be found in [1] or by a direct calculation + return np.sqrt(random_state.standard_gamma(nu, size=size) / nu) + + def _fitstart(self, data, args=None): + if isinstance(data, CensoredData): + data = data._uncensor() + if args is None: + args = (1.0,) * self.numargs + # Analytical justified estimates + # see: https://docs.scipy.org/doc/scipy/reference/tutorial/stats/continuous_nakagami.html + loc = np.min(data) + scale = np.sqrt(np.sum((data - loc)**2) / len(data)) + return args + (loc, scale) + + +nakagami = nakagami_gen(a=0.0, name="nakagami") + + +# The function name ncx2 is an abbreviation for noncentral chi squared. +def _ncx2_log_pdf(x, df, nc): + # We use (xs**2 + ns**2)/2 = (xs - ns)**2/2 + xs*ns, and include the + # factor of exp(-xs*ns) into the ive function to improve numerical + # stability at large values of xs. See also `rice.pdf`. + df2 = df/2.0 - 1.0 + xs, ns = np.sqrt(x), np.sqrt(nc) + res = sc.xlogy(df2/2.0, x/nc) - 0.5*(xs - ns)**2 + corr = sc.ive(df2, xs*ns) / 2.0 + # Return res + np.log(corr) avoiding np.log(0) + return xpx.apply_where( + corr > 0, + (res, corr), + lambda r, c: r + np.log(c), + fill_value=-np.inf) + + +class ncx2_gen(rv_continuous): + r"""A non-central chi-squared continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `ncx2` is: + + .. math:: + + f(x, k, \lambda) = \frac{1}{2} \exp(-(\lambda+x)/2) + (x/\lambda)^{(k-2)/4} I_{(k-2)/2}(\sqrt{\lambda x}) + + for :math:`x >= 0`, :math:`k > 0` and :math:`\lambda \ge 0`. + :math:`k` specifies the degrees of freedom (denoted ``df`` in the + implementation) and :math:`\lambda` is the non-centrality parameter + (denoted ``nc`` in the implementation). :math:`I_\nu` denotes the + modified Bessel function of first order of degree :math:`\nu` + (`scipy.special.iv`). + + `ncx2` takes ``df`` and ``nc`` as shape parameters. + + This distribution uses routines from the Boost Math C++ library for + the computation of the ``pdf``, ``cdf``, ``ppf``, ``sf`` and ``isf`` + methods. [1]_ + + %(after_notes)s + + References + ---------- + .. [1] The Boost Developers. "Boost C++ Libraries". https://www.boost.org/. + + %(example)s + + """ + def _argcheck(self, df, nc): + return (df > 0) & np.isfinite(df) & (nc >= 0) + + def _shape_info(self): + idf = _ShapeInfo("df", False, (0, np.inf), (False, False)) + inc = _ShapeInfo("nc", False, (0, np.inf), (True, False)) + return [idf, inc] + + def _rvs(self, df, nc, size=None, random_state=None): + return random_state.noncentral_chisquare(df, nc, size) + + def _logpdf(self, x, df, nc): + return xpx.apply_where(nc != 0, (x, df, nc), _ncx2_log_pdf, + lambda x, df, _: chi2._logpdf(x, df)) + + def _pdf(self, x, df, nc): + with np.errstate(over='ignore'): # see gh-17432 + return xpx.apply_where(nc != 0, (x, df, nc), scu._ncx2_pdf, + lambda x, df, _: chi2._pdf(x, df)) + + def _cdf(self, x, df, nc): + with np.errstate(over='ignore'): # see gh-17432 + return xpx.apply_where(nc != 0, (x, df, nc), sc.chndtr, + lambda x, df, _: chi2._cdf(x, df)) + + def _ppf(self, q, df, nc): + with np.errstate(over='ignore'): # see gh-17432 + return xpx.apply_where(nc != 0, (q, df, nc), sc.chndtrix, + lambda x, df, _: chi2._ppf(x, df)) + + def _sf(self, x, df, nc): + with np.errstate(over='ignore'): # see gh-17432 + return xpx.apply_where(nc != 0, (x, df, nc), scu._ncx2_sf, + lambda x, df, _: chi2._sf(x, df)) + + def _isf(self, x, df, nc): + with np.errstate(over='ignore'): # see gh-17432 + return xpx.apply_where(nc != 0, (x, df, nc), scu._ncx2_isf, + lambda x, df, _: chi2._isf(x, df)) + + def _stats(self, df, nc): + _ncx2_mean = df + nc + def k_plus_cl(k, l, c): + return k + c*l + _ncx2_variance = 2.0 * k_plus_cl(df, nc, 2.0) + _ncx2_skewness = (np.sqrt(8.0) * k_plus_cl(df, nc, 3) / + np.sqrt(k_plus_cl(df, nc, 2.0)**3)) + _ncx2_kurtosis_excess = (12.0 * k_plus_cl(df, nc, 4.0) / + k_plus_cl(df, nc, 2.0)**2) + return ( + _ncx2_mean, + _ncx2_variance, + _ncx2_skewness, + _ncx2_kurtosis_excess, + ) + + +ncx2 = ncx2_gen(a=0.0, name='ncx2') + + +class ncf_gen(rv_continuous): + r"""A non-central F distribution continuous random variable. + + %(before_notes)s + + See Also + -------- + scipy.stats.f : Fisher distribution + + Notes + ----- + The probability density function for `ncf` is: + + .. math:: + + f(x, n_1, n_2, \lambda) = + \exp\left(\frac{\lambda}{2} + + \lambda n_1 \frac{x}{2(n_1 x + n_2)} + \right) + n_1^{n_1/2} n_2^{n_2/2} x^{n_1/2 - 1} \\ + (n_2 + n_1 x)^{-(n_1 + n_2)/2} + \gamma(n_1/2) \gamma(1 + n_2/2) \\ + \frac{L^{\frac{n_1}{2}-1}_{n_2/2} + \left(-\lambda n_1 \frac{x}{2(n_1 x + n_2)}\right)} + {B(n_1/2, n_2/2) + \gamma\left(\frac{n_1 + n_2}{2}\right)} + + for :math:`n_1, n_2 > 0`, :math:`\lambda \ge 0`. Here :math:`n_1` is the + degrees of freedom in the numerator, :math:`n_2` the degrees of freedom in + the denominator, :math:`\lambda` the non-centrality parameter, + :math:`\gamma` is the logarithm of the Gamma function, :math:`L_n^k` is a + generalized Laguerre polynomial and :math:`B` is the beta function. + + `ncf` takes ``dfn``, ``dfd`` and ``nc`` as shape parameters. If ``nc=0``, + the distribution becomes equivalent to the Fisher distribution. + + This distribution uses routines from the Boost Math C++ library for + the computation of the ``pdf``, ``cdf``, ``ppf``, ``stats``, ``sf`` and + ``isf`` methods. [1]_ + + %(after_notes)s + + References + ---------- + .. [1] The Boost Developers. "Boost C++ Libraries". https://www.boost.org/. + + %(example)s + + """ + def _argcheck(self, dfn, dfd, nc): + return (dfn > 0) & (dfd > 0) & (nc >= 0) + + def _shape_info(self): + idf1 = _ShapeInfo("dfn", False, (0, np.inf), (False, False)) + idf2 = _ShapeInfo("dfd", False, (0, np.inf), (False, False)) + inc = _ShapeInfo("nc", False, (0, np.inf), (True, False)) + return [idf1, idf2, inc] + + def _rvs(self, dfn, dfd, nc, size=None, random_state=None): + return random_state.noncentral_f(dfn, dfd, nc, size) + + def _pdf(self, x, dfn, dfd, nc): + return scu._ncf_pdf(x, dfn, dfd, nc) + + def _cdf(self, x, dfn, dfd, nc): + return sc.ncfdtr(dfn, dfd, nc, x) + + def _ppf(self, q, dfn, dfd, nc): + with np.errstate(over='ignore'): # see gh-17432 + return sc.ncfdtri(dfn, dfd, nc, q) + + def _sf(self, x, dfn, dfd, nc): + return scu._ncf_sf(x, dfn, dfd, nc) + + def _isf(self, x, dfn, dfd, nc): + with np.errstate(over='ignore'): # see gh-17432 + return scu._ncf_isf(x, dfn, dfd, nc) + + # # Produces bogus values as written - maybe it's close, though? + # def _munp(self, n, dfn, dfd, nc): + # val = (dfn * 1.0/dfd)**n + # term = sc.gammaln(n+0.5*dfn) + sc.gammaln(0.5*dfd-n) - sc.gammaln(dfd*0.5) + # val *= np.exp(-nc / 2.0+term) + # val *= sc.hyp1f1(n+0.5*dfn, 0.5*dfn, 0.5*nc) + # return val + + def _stats(self, dfn, dfd, nc, moments='mv'): + mu = scu._ncf_mean(dfn, dfd, nc) + mu2 = scu._ncf_variance(dfn, dfd, nc) + g1 = scu._ncf_skewness(dfn, dfd, nc) if 's' in moments else None + g2 = scu._ncf_kurtosis_excess( # isn't really excess kurtosis! + dfn, dfd, nc) - 3 if 'k' in moments else None + # Mathematica: Kurtosis[NoncentralFRatioDistribution[27, 27, 0.415784417992261]] + return mu, mu2, g1, g2 + + +ncf = ncf_gen(a=0.0, name='ncf') + + +class t_gen(rv_continuous): + r"""A Student's t continuous random variable. + + For the noncentral t distribution, see `nct`. + + %(before_notes)s + + See Also + -------- + nct + + Notes + ----- + The probability density function for `t` is: + + .. math:: + + f(x, \nu) = \frac{\Gamma((\nu+1)/2)} + {\sqrt{\pi \nu} \Gamma(\nu/2)} + (1+x^2/\nu)^{-(\nu+1)/2} + + where :math:`x` is a real number and the degrees of freedom parameter + :math:`\nu` (denoted ``df`` in the implementation) satisfies + :math:`\nu > 0`. :math:`\Gamma` is the gamma function + (`scipy.special.gamma`). + + %(after_notes)s + + %(example)s + + """ + def _shape_info(self): + return [_ShapeInfo("df", False, (0, np.inf), (False, False))] + + def _rvs(self, df, size=None, random_state=None): + return random_state.standard_t(df, size=size) + + def _pdf(self, x, df): + return xpx.apply_where( + df == np.inf, (x, df), + lambda x, df: norm._pdf(x), + lambda x, df: np.exp(self._logpdf(x, df))) + + def _logpdf(self, x, df): + + def t_logpdf(x, df): + return (np.log(sc.poch(0.5 * df, 0.5)) + - 0.5 * (np.log(df) + np.log(np.pi)) + - (df + 1)/2*np.log1p(x * x/df)) + + def norm_logpdf(x, df): + return norm._logpdf(x) + + return xpx.apply_where(df == np.inf, (x, df), norm_logpdf, t_logpdf) + + def _cdf(self, x, df): + return sc.stdtr(df, x) + + def _sf(self, x, df): + return sc.stdtr(df, -x) + + def _ppf(self, q, df): + return sc.stdtrit(df, q) + + def _isf(self, q, df): + return -sc.stdtrit(df, q) + + def _stats(self, df): + # infinite df -> normal distribution (0.0, 1.0, 0.0, 0.0) + infinite_df = np.isposinf(df) + + mu = np.where(df > 1, 0.0, np.inf) + + condlist = ((df > 1) & (df <= 2), + (df > 2) & np.isfinite(df), + infinite_df) + choicelist = (lambda df: np.broadcast_to(np.inf, df.shape), + lambda df: df / (df-2.0), + lambda df: np.broadcast_to(1, df.shape)) + mu2 = _lazyselect(condlist, choicelist, (df,), np.nan) + + g1 = np.where(df > 3, 0.0, np.nan) + + condlist = ((df > 2) & (df <= 4), + (df > 4) & np.isfinite(df), + infinite_df) + choicelist = (lambda df: np.broadcast_to(np.inf, df.shape), + lambda df: 6.0 / (df-4.0), + lambda df: np.broadcast_to(0, df.shape)) + g2 = _lazyselect(condlist, choicelist, (df,), np.nan) + + return mu, mu2, g1, g2 + + def _entropy(self, df): + + def regular(df): + half = df/2 + half1 = (df + 1)/2 + return (half1*(sc.digamma(half1) - sc.digamma(half)) + + np.log(np.sqrt(df)*sc.beta(half, 0.5))) + + def asymptotic(df): + # Formula from Wolfram Alpha: + # "asymptotic expansion (d+1)/2 * (digamma((d+1)/2) - digamma(d/2)) + # + log(sqrt(d) * beta(d/2, 1/2))" + h = (norm._entropy() + 1/df + (df**-2.)/4 - (df**-3.)/6 + - (df**-4.)/8 + 3/10*(df**-5.) + (df**-6.)/4) + return h + + return xpx.apply_where(df >= 100, df, asymptotic, regular) + + +t = t_gen(name='t') + + +class nct_gen(rv_continuous): + r"""A non-central Student's t continuous random variable. + + %(before_notes)s + + Notes + ----- + If :math:`Y` is a standard normal random variable and :math:`V` is + an independent chi-square random variable (`chi2`) with :math:`k` degrees + of freedom, then + + .. math:: + + X = \frac{Y + c}{\sqrt{V/k}} + + has a non-central Student's t distribution on the real line. + The degrees of freedom parameter :math:`k` (denoted ``df`` in the + implementation) satisfies :math:`k > 0` and the noncentrality parameter + :math:`c` (denoted ``nc`` in the implementation) is a real number. + + This distribution uses routines from the Boost Math C++ library for + the computation of the ``pdf``, ``cdf``, ``ppf``, ``sf`` and ``isf`` + methods. [1]_ + + %(after_notes)s + + References + ---------- + .. [1] The Boost Developers. "Boost C++ Libraries". https://www.boost.org/. + + %(example)s + + """ + def _argcheck(self, df, nc): + return (df > 0) & (nc == nc) + + def _shape_info(self): + idf = _ShapeInfo("df", False, (0, np.inf), (False, False)) + inc = _ShapeInfo("nc", False, (-np.inf, np.inf), (False, False)) + return [idf, inc] + + def _rvs(self, df, nc, size=None, random_state=None): + n = norm.rvs(loc=nc, size=size, random_state=random_state) + c2 = chi2.rvs(df, size=size, random_state=random_state) + return n * np.sqrt(df) / np.sqrt(c2) + + def _pdf(self, x, df, nc): + return scu._nct_pdf(x, df, nc) + + def _cdf(self, x, df, nc): + return sc.nctdtr(df, nc, x) + + def _ppf(self, q, df, nc): + return sc.nctdtrit(df, nc, q) + + def _sf(self, x, df, nc): + with np.errstate(over='ignore'): # see gh-17432 + return np.clip(scu._nct_sf(x, df, nc), 0, 1) + + def _isf(self, x, df, nc): + with np.errstate(over='ignore'): # see gh-17432 + return scu._nct_isf(x, df, nc) + + def _stats(self, df, nc, moments='mv'): + mu = scu._nct_mean(df, nc) + mu2 = scu._nct_variance(df, nc) + g1 = scu._nct_skewness(df, nc) if 's' in moments else None + g2 = scu._nct_kurtosis_excess(df, nc) if 'k' in moments else None + return mu, mu2, g1, g2 + + +nct = nct_gen(name="nct") + + +class pareto_gen(rv_continuous): + r"""A Pareto continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `pareto` is: + + .. math:: + + f(x, b) = \frac{b}{x^{b+1}} + + for :math:`x \ge 1`, :math:`b > 0`. + + `pareto` takes ``b`` as a shape parameter for :math:`b`. + + %(after_notes)s + + %(example)s + + """ + def _shape_info(self): + return [_ShapeInfo("b", False, (0, np.inf), (False, False))] + + def _pdf(self, x, b): + # pareto.pdf(x, b) = b / x**(b+1) + return b * x**(-b-1) + + def _cdf(self, x, b): + return 1 - x**(-b) + + def _ppf(self, q, b): + return pow(1-q, -1.0/b) + + def _sf(self, x, b): + return x**(-b) + + def _isf(self, q, b): + return np.power(q, -1.0 / b) + + def _stats(self, b, moments='mv'): + mu, mu2, g1, g2 = None, None, None, None + if 'm' in moments: + mask = b > 1 + bt = np.extract(mask, b) + mu = np.full(np.shape(b), fill_value=np.inf) + np.place(mu, mask, bt / (bt-1.0)) + if 'v' in moments: + mask = b > 2 + bt = np.extract(mask, b) + mu2 = np.full(np.shape(b), fill_value=np.inf) + np.place(mu2, mask, bt / (bt-2.0) / (bt-1.0)**2) + if 's' in moments: + mask = b > 3 + bt = np.extract(mask, b) + g1 = np.full(np.shape(b), fill_value=np.nan) + vals = 2 * (bt + 1.0) * np.sqrt(bt - 2.0) / ((bt - 3.0) * np.sqrt(bt)) + np.place(g1, mask, vals) + if 'k' in moments: + mask = b > 4 + bt = np.extract(mask, b) + g2 = np.full(np.shape(b), fill_value=np.nan) + vals = (6.0*np.polyval([1.0, 1.0, -6, -2], bt) / + np.polyval([1.0, -7.0, 12.0, 0.0], bt)) + np.place(g2, mask, vals) + return mu, mu2, g1, g2 + + def _entropy(self, b): + return 1 + 1.0/b - np.log(b) + + @_call_super_mom + @inherit_docstring_from(rv_continuous) + def fit(self, data, *args, **kwds): + parameters = _check_fit_input_parameters(self, data, args, kwds) + data, fshape, floc, fscale = parameters + + # ensure that any fixed parameters don't violate constraints of the + # distribution before continuing. + if floc is not None and np.min(data) - floc < (fscale or 0): + raise FitDataError("pareto", lower=1, upper=np.inf) + + ndata = data.shape[0] + + def get_shape(scale, location): + # The first-order necessary condition on `shape` can be solved in + # closed form + return ndata / np.sum(np.log((data - location) / scale)) + + if floc is fscale is None: + # The support of the distribution is `(x - loc)/scale > 0`. + # The method of Lagrange multipliers turns this constraint + # into an equation that can be solved numerically. + # See gh-12545 for details. + + def dL_dScale(shape, scale): + # The partial derivative of the log-likelihood function w.r.t. + # the scale. + return ndata * shape / scale + + def dL_dLocation(shape, location): + # The partial derivative of the log-likelihood function w.r.t. + # the location. + return (shape + 1) * np.sum(1 / (data - location)) + + def fun_to_solve(scale): + # optimize the scale by setting the partial derivatives + # w.r.t. location and scale equal and solving. + location = np.min(data) - scale + shape = fshape or get_shape(scale, location) + return dL_dLocation(shape, location) - dL_dScale(shape, scale) + + def interval_contains_root(lbrack, rbrack): + # return true if the signs disagree. + return (np.sign(fun_to_solve(lbrack)) != + np.sign(fun_to_solve(rbrack))) + + # set brackets for `root_scalar` to use when optimizing over the + # scale such that a root is likely between them. Use user supplied + # guess or default 1. + brack_start = float(kwds.get('scale', 1)) + lbrack, rbrack = brack_start / 2, brack_start * 2 + # if a root is not between the brackets, iteratively expand them + # until they include a sign change, checking after each bracket is + # modified. + while (not interval_contains_root(lbrack, rbrack) + and (lbrack > 0 or rbrack < np.inf)): + lbrack /= 2 + rbrack *= 2 + res = root_scalar(fun_to_solve, bracket=[lbrack, rbrack]) + if res.converged: + scale = res.root + loc = np.min(data) - scale + shape = fshape or get_shape(scale, loc) + + # The Pareto distribution requires that its parameters satisfy + # the condition `fscale + floc <= min(data)`. However, to + # avoid numerical issues, we require that `fscale + floc` + # is strictly less than `min(data)`. If this condition + # is not satisfied, reduce the scale with `np.nextafter` to + # ensure that data does not fall outside of the support. + if not (scale + loc) < np.min(data): + scale = np.min(data) - loc + scale = np.nextafter(scale, 0) + return shape, loc, scale + else: + return super().fit(data, **kwds) + elif floc is None: + loc = np.min(data) - fscale + else: + loc = floc + # Source: Evans, Hastings, and Peacock (2000), Statistical + # Distributions, 3rd. Ed., John Wiley and Sons. Page 149. + scale = fscale or np.min(data) - loc + shape = fshape or get_shape(scale, loc) + return shape, loc, scale + + +pareto = pareto_gen(a=1.0, name="pareto") + + +class lomax_gen(rv_continuous): + r"""A Lomax (Pareto of the second kind) continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `lomax` is: + + .. math:: + + f(x, c) = \frac{c}{(1+x)^{c+1}} + + for :math:`x \ge 0`, :math:`c > 0`. + + `lomax` takes ``c`` as a shape parameter for :math:`c`. + + `lomax` is a special case of `pareto` with ``loc=-1.0``. + + %(after_notes)s + + %(example)s + + """ + def _shape_info(self): + return [_ShapeInfo("c", False, (0, np.inf), (False, False))] + + def _pdf(self, x, c): + # lomax.pdf(x, c) = c / (1+x)**(c+1) + return c*1.0/(1.0+x)**(c+1.0) + + def _logpdf(self, x, c): + return np.log(c) - (c+1)*sc.log1p(x) + + def _cdf(self, x, c): + return -sc.expm1(-c*sc.log1p(x)) + + def _sf(self, x, c): + return np.exp(-c*sc.log1p(x)) + + def _logsf(self, x, c): + return -c*sc.log1p(x) + + def _ppf(self, q, c): + return sc.expm1(-sc.log1p(-q)/c) + + def _isf(self, q, c): + return q**(-1.0 / c) - 1 + + def _stats(self, c): + mu, mu2, g1, g2 = pareto.stats(c, loc=-1.0, moments='mvsk') + return mu, mu2, g1, g2 + + def _entropy(self, c): + return 1+1.0/c-np.log(c) + + +lomax = lomax_gen(a=0.0, name="lomax") + + +class pearson3_gen(rv_continuous): + r""" + A pearson type III continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `pearson3` is: + + .. math:: + + f(x, \kappa) = \frac{|\beta|}{\Gamma(\alpha)} + (\beta (x - \zeta))^{\alpha - 1} + \exp(-\beta (x - \zeta)) + + where: + + .. math:: + + \beta = \frac{2}{\kappa} + + \alpha = \beta^2 = \frac{4}{\kappa^2} + + \zeta = -\frac{\alpha}{\beta} = -\beta + + :math:`\Gamma` is the gamma function (`scipy.special.gamma`). + Pass the skew :math:`\kappa` into `pearson3` as the shape parameter + ``skew``. + + %(after_notes)s + + References + ---------- + R.W. Vogel and D.E. McMartin, "Probability Plot Goodness-of-Fit and + Skewness Estimation Procedures for the Pearson Type 3 Distribution", Water + Resources Research, Vol.27, 3149-3158 (1991). + + L.R. Salvosa, "Tables of Pearson's Type III Function", Ann. Math. Statist., + Vol.1, 191-198 (1930). + + "Using Modern Computing Tools to Fit the Pearson Type III Distribution to + Aviation Loads Data", Office of Aviation Research (2003). + + %(example)s + """ + def _preprocess(self, x, skew): + # The real 'loc' and 'scale' are handled in the calling pdf(...). The + # local variables 'loc' and 'scale' within pearson3._pdf are set to + # the defaults just to keep them as part of the equations for + # documentation. + loc = 0.0 + scale = 1.0 + + # If skew is small, return _norm_pdf. The divide between pearson3 + # and norm was found by brute force and is approximately a skew of + # 0.000016. No one, I hope, would actually use a skew value even + # close to this small. + norm2pearson_transition = 0.000016 + + ans, x, skew = np.broadcast_arrays(1.0, x, skew) + ans = ans.copy() + + # mask is True where skew is small enough to use the normal approx. + mask = np.absolute(skew) < norm2pearson_transition + invmask = ~mask + + beta = 2.0 / (skew[invmask] * scale) + alpha = (scale * beta)**2 + zeta = loc - alpha / beta + + transx = beta * (x[invmask] - zeta) + return ans, x, transx, mask, invmask, beta, alpha, zeta + + def _argcheck(self, skew): + # The _argcheck function in rv_continuous only allows positive + # arguments. The skew argument for pearson3 can be zero (which I want + # to handle inside pearson3._pdf) or negative. So just return True + # for all skew args. + return np.isfinite(skew) + + def _shape_info(self): + return [_ShapeInfo("skew", False, (-np.inf, np.inf), (False, False))] + + def _stats(self, skew): + m = 0.0 + v = 1.0 + s = skew + k = 1.5*skew**2 + return m, v, s, k + + def _pdf(self, x, skew): + # pearson3.pdf(x, skew) = abs(beta) / gamma(alpha) * + # (beta * (x - zeta))**(alpha - 1) * exp(-beta*(x - zeta)) + # Do the calculation in _logpdf since helps to limit + # overflow/underflow problems + ans = np.exp(self._logpdf(x, skew)) + if ans.ndim == 0: + if np.isnan(ans): + return 0.0 + return ans + ans[np.isnan(ans)] = 0.0 + return ans + + def _logpdf(self, x, skew): + # PEARSON3 logpdf GAMMA logpdf + # np.log(abs(beta)) + # + (alpha - 1)*np.log(beta*(x - zeta)) + (a - 1)*np.log(x) + # - beta*(x - zeta) - x + # - sc.gammalnalpha) - sc.gammalna) + ans, x, transx, mask, invmask, beta, alpha, _ = ( + self._preprocess(x, skew)) + + ans[mask] = np.log(_norm_pdf(x[mask])) + # use logpdf instead of _logpdf to fix issue mentioned in gh-12640 + # (_logpdf does not return correct result for alpha = 1) + ans[invmask] = np.log(abs(beta)) + gamma.logpdf(transx, alpha) + return ans + + def _cdf(self, x, skew): + ans, x, transx, mask, invmask, _, alpha, _ = ( + self._preprocess(x, skew)) + + ans[mask] = _norm_cdf(x[mask]) + + skew = np.broadcast_to(skew, invmask.shape) + invmask1a = np.logical_and(invmask, skew > 0) + invmask1b = skew[invmask] > 0 + # use cdf instead of _cdf to fix issue mentioned in gh-12640 + # (_cdf produces NaNs for inputs outside support) + ans[invmask1a] = gamma.cdf(transx[invmask1b], alpha[invmask1b]) + + # The gamma._cdf approach wasn't working with negative skew. + # Note that multiplying the skew by -1 reflects about x=0. + # So instead of evaluating the CDF with negative skew at x, + # evaluate the SF with positive skew at -x. + invmask2a = np.logical_and(invmask, skew < 0) + invmask2b = skew[invmask] < 0 + # gamma._sf produces NaNs when transx < 0, so use gamma.sf + ans[invmask2a] = gamma.sf(transx[invmask2b], alpha[invmask2b]) + + return ans + + def _sf(self, x, skew): + ans, x, transx, mask, invmask, _, alpha, _ = ( + self._preprocess(x, skew)) + + ans[mask] = _norm_sf(x[mask]) + + skew = np.broadcast_to(skew, invmask.shape) + invmask1a = np.logical_and(invmask, skew > 0) + invmask1b = skew[invmask] > 0 + ans[invmask1a] = gamma.sf(transx[invmask1b], alpha[invmask1b]) + + invmask2a = np.logical_and(invmask, skew < 0) + invmask2b = skew[invmask] < 0 + ans[invmask2a] = gamma.cdf(transx[invmask2b], alpha[invmask2b]) + + return ans + + def _rvs(self, skew, size=None, random_state=None): + skew = np.broadcast_to(skew, size) + ans, _, _, mask, invmask, beta, alpha, zeta = ( + self._preprocess([0], skew)) + + nsmall = mask.sum() + nbig = mask.size - nsmall + ans[mask] = random_state.standard_normal(nsmall) + ans[invmask] = random_state.standard_gamma(alpha, nbig)/beta + zeta + + if size == (): + ans = ans[0] + return ans + + def _ppf(self, q, skew): + ans, q, _, mask, invmask, beta, alpha, zeta = ( + self._preprocess(q, skew)) + ans[mask] = _norm_ppf(q[mask]) + q = q[invmask] + q[beta < 0] = 1 - q[beta < 0] # for negative skew; see gh-17050 + ans[invmask] = sc.gammaincinv(alpha, q)/beta + zeta + return ans + + @_call_super_mom + @extend_notes_in_docstring(rv_continuous, notes="""\ + Note that method of moments (`method='MM'`) is not + available for this distribution.\n\n""") + def fit(self, data, *args, **kwds): + if kwds.get("method", None) == 'MM': + raise NotImplementedError("Fit `method='MM'` is not available for " + "the Pearson3 distribution. Please try " + "the default `method='MLE'`.") + else: + return super(type(self), self).fit(data, *args, **kwds) + + +pearson3 = pearson3_gen(name="pearson3") + + +class powerlaw_gen(rv_continuous): + r"""A power-function continuous random variable. + + %(before_notes)s + + See Also + -------- + pareto + + Notes + ----- + The probability density function for `powerlaw` is: + + .. math:: + + f(x, a) = a x^{a-1} + + for :math:`0 \le x \le 1`, :math:`a > 0`. + + `powerlaw` takes ``a`` as a shape parameter for :math:`a`. + + %(after_notes)s + + For example, the support of `powerlaw` can be adjusted from the default + interval ``[0, 1]`` to the interval ``[c, c+d]`` by setting ``loc=c`` and + ``scale=d``. For a power-law distribution with infinite support, see + `pareto`. For a power-law distribution described by PDF: + + .. math:: + + f(x; a, l, h) = \frac{a}{h^a - l^2} x^{a-1} + + with :math:`a \neq 0` and :math:`0 < l < x < h`, see `truncpareto`. + + `powerlaw` is a special case of `beta` with ``b=1``. + + %(example)s + + """ + def _shape_info(self): + return [_ShapeInfo("a", False, (0, np.inf), (False, False))] + + def _pdf(self, x, a): + # powerlaw.pdf(x, a) = a * x**(a-1) + return a*x**(a-1.0) + + def _logpdf(self, x, a): + return np.log(a) + sc.xlogy(a - 1, x) + + def _cdf(self, x, a): + return x**(a*1.0) + + def _logcdf(self, x, a): + return a*np.log(x) + + def _ppf(self, q, a): + return pow(q, 1.0/a) + + def _sf(self, p, a): + return -sc.powm1(p, a) + + def _munp(self, n, a): + # The following expression is correct for all real n (provided a > 0). + return a / (a + n) + + def _stats(self, a): + return (a / (a + 1.0), + a / (a + 2.0) / (a + 1.0) ** 2, + -2.0 * ((a - 1.0) / (a + 3.0)) * np.sqrt((a + 2.0) / a), + 6 * np.polyval([1, -1, -6, 2], a) / (a * (a + 3.0) * (a + 4))) + + def _entropy(self, a): + return 1 - 1.0/a - np.log(a) + + def _support_mask(self, x, a): + return (super()._support_mask(x, a) + & ((x != 0) | (a >= 1))) + + @_call_super_mom + @extend_notes_in_docstring(rv_continuous, notes="""\ + Notes specifically for ``powerlaw.fit``: If the location is a free + parameter and the value returned for the shape parameter is less than + one, the true maximum likelihood approaches infinity. This causes + numerical difficulties, and the resulting estimates are approximate. + \n\n""") + def fit(self, data, *args, **kwds): + # Summary of the strategy: + # + # 1) If the scale and location are fixed, return the shape according + # to a formula. + # + # 2) If the scale is fixed, there are two possibilities for the other + # parameters - one corresponding with shape less than one, and + # another with shape greater than one. Calculate both, and return + # whichever has the better log-likelihood. + # + # At this point, the scale is known to be free. + # + # 3) If the location is fixed, return the scale and shape according to + # formulas (or, if the shape is fixed, the fixed shape). + # + # At this point, the location and scale are both free. There are + # separate equations depending on whether the shape is less than one or + # greater than one. + # + # 4a) If the shape is less than one, there are formulas for shape, + # location, and scale. + # 4b) If the shape is greater than one, there are formulas for shape + # and scale, but there is a condition for location to be solved + # numerically. + # + # If the shape is fixed and less than one, we use 4a. + # If the shape is fixed and greater than one, we use 4b. + # If the shape is also free, we calculate fits using both 4a and 4b + # and choose the one that results a better log-likelihood. + # + # In many cases, the use of `np.nextafter` is used to avoid numerical + # issues. + if kwds.pop('superfit', False): + return super().fit(data, *args, **kwds) + + if len(np.unique(data)) == 1: + return super().fit(data, *args, **kwds) + + data, fshape, floc, fscale = _check_fit_input_parameters(self, data, + args, kwds) + penalized_nllf_args = [data, (self._fitstart(data),)] + penalized_nllf = self._reduce_func(penalized_nllf_args, {})[1] + + # ensure that any fixed parameters don't violate constraints of the + # distribution before continuing. The support of the distribution + # is `0 < (x - loc)/scale < 1`. + if floc is not None: + if not data.min() > floc: + raise FitDataError('powerlaw', 0, 1) + if fscale is not None and not data.max() <= floc + fscale: + raise FitDataError('powerlaw', 0, 1) + + if fscale is not None: + if fscale <= 0: + raise ValueError("Negative or zero `fscale` is outside the " + "range allowed by the distribution.") + if fscale <= np.ptp(data): + msg = "`fscale` must be greater than the range of data." + raise ValueError(msg) + + def get_shape(data, loc, scale): + # The first-order necessary condition on `shape` can be solved in + # closed form. It can be used no matter the assumption of the + # value of the shape. + N = len(data) + return - N / (np.sum(np.log(data - loc)) - N*np.log(scale)) + + def get_scale(data, loc): + # analytical solution for `scale` based on the location. + # It can be used no matter the assumption of the value of the + # shape. + return data.max() - loc + + # 1) The location and scale are both fixed. Analytically determine the + # shape. + if fscale is not None and floc is not None: + return get_shape(data, floc, fscale), floc, fscale + + # 2) The scale is fixed. There are two possibilities for the other + # parameters. Choose the option with better log-likelihood. + if fscale is not None: + # using `data.min()` as the optimal location + loc_lt1 = np.nextafter(data.min(), -np.inf) + shape_lt1 = fshape or get_shape(data, loc_lt1, fscale) + ll_lt1 = penalized_nllf((shape_lt1, loc_lt1, fscale), data) + + # using `data.max() - scale` as the optimal location + loc_gt1 = np.nextafter(data.max() - fscale, np.inf) + shape_gt1 = fshape or get_shape(data, loc_gt1, fscale) + ll_gt1 = penalized_nllf((shape_gt1, loc_gt1, fscale), data) + + if ll_lt1 < ll_gt1: + return shape_lt1, loc_lt1, fscale + else: + return shape_gt1, loc_gt1, fscale + + # 3) The location is fixed. Return the analytical scale and the + # analytical (or fixed) shape. + if floc is not None: + scale = get_scale(data, floc) + shape = fshape or get_shape(data, floc, scale) + return shape, floc, scale + + # 4) Location and scale are both free + # 4a) Use formulas that assume `shape <= 1`. + + def fit_loc_scale_w_shape_lt_1(): + loc = np.nextafter(data.min(), -np.inf) + if np.abs(loc) < np.finfo(loc.dtype).tiny: + loc = np.sign(loc) * np.finfo(loc.dtype).tiny + scale = np.nextafter(get_scale(data, loc), np.inf) + shape = fshape or get_shape(data, loc, scale) + return shape, loc, scale + + # 4b) Fit under the assumption that `shape > 1`. The support + # of the distribution is `(x - loc)/scale <= 1`. The method of Lagrange + # multipliers turns this constraint into the condition that + # dL_dScale - dL_dLocation must be zero, which is solved numerically. + # (Alternatively, substitute the constraint into the objective + # function before deriving the likelihood equation for location.) + + def dL_dScale(data, shape, scale): + # The partial derivative of the log-likelihood function w.r.t. + # the scale. + return -data.shape[0] * shape / scale + + def dL_dLocation(data, shape, loc): + # The partial derivative of the log-likelihood function w.r.t. + # the location. + return (shape - 1) * np.sum(1 / (loc - data)) # -1/(data-loc) + + def dL_dLocation_star(loc): + # The derivative of the log-likelihood function w.r.t. + # the location, given optimal shape and scale + scale = np.nextafter(get_scale(data, loc), -np.inf) + shape = fshape or get_shape(data, loc, scale) + return dL_dLocation(data, shape, loc) + + def fun_to_solve(loc): + # optimize the location by setting the partial derivatives + # w.r.t. location and scale equal and solving. + scale = np.nextafter(get_scale(data, loc), -np.inf) + shape = fshape or get_shape(data, loc, scale) + return (dL_dScale(data, shape, scale) + - dL_dLocation(data, shape, loc)) + + def fit_loc_scale_w_shape_gt_1(): + # set brackets for `root_scalar` to use when optimizing over the + # location such that a root is likely between them. + rbrack = np.nextafter(data.min(), -np.inf) + + # if the sign of `dL_dLocation_star` is positive at rbrack, + # we're not going to find the root we're looking for + delta = (data.min() - rbrack) + while dL_dLocation_star(rbrack) > 0: + rbrack = data.min() - delta + delta *= 2 + + def interval_contains_root(lbrack, rbrack): + # Check if the interval (lbrack, rbrack) contains the root. + return (np.sign(fun_to_solve(lbrack)) + != np.sign(fun_to_solve(rbrack))) + + lbrack = rbrack - 1 + + # if the sign doesn't change between the brackets, move the left + # bracket until it does. (The right bracket remains fixed at the + # maximum permissible value.) + i = 1.0 + while (not interval_contains_root(lbrack, rbrack) + and lbrack != -np.inf): + lbrack = (data.min() - i) + i *= 2 + + root = optimize.root_scalar(fun_to_solve, bracket=(lbrack, rbrack)) + + loc = np.nextafter(root.root, -np.inf) + scale = np.nextafter(get_scale(data, loc), np.inf) + shape = fshape or get_shape(data, loc, scale) + return shape, loc, scale + + # Shape is fixed - choose 4a or 4b accordingly. + if fshape is not None and fshape <= 1: + return fit_loc_scale_w_shape_lt_1() + elif fshape is not None and fshape > 1: + return fit_loc_scale_w_shape_gt_1() + + # Shape is free + fit_shape_lt1 = fit_loc_scale_w_shape_lt_1() + ll_lt1 = self.nnlf(fit_shape_lt1, data) + + fit_shape_gt1 = fit_loc_scale_w_shape_gt_1() + ll_gt1 = self.nnlf(fit_shape_gt1, data) + + if ll_lt1 <= ll_gt1 and fit_shape_lt1[0] <= 1: + return fit_shape_lt1 + elif ll_lt1 > ll_gt1 and fit_shape_gt1[0] > 1: + return fit_shape_gt1 + else: + return super().fit(data, *args, **kwds) + + +powerlaw = powerlaw_gen(a=0.0, b=1.0, name="powerlaw") + + +class powerlognorm_gen(rv_continuous): + r"""A power log-normal continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `powerlognorm` is: + + .. math:: + + f(x, c, s) = \frac{c}{x s} \phi(\log(x)/s) + (\Phi(-\log(x)/s))^{c-1} + + where :math:`\phi` is the normal pdf, and :math:`\Phi` is the normal cdf, + and :math:`x > 0`, :math:`s, c > 0`. + + `powerlognorm` takes :math:`c` and :math:`s` as shape parameters. + + %(after_notes)s + + %(example)s + + """ + _support_mask = rv_continuous._open_support_mask + + def _shape_info(self): + ic = _ShapeInfo("c", False, (0, np.inf), (False, False)) + i_s = _ShapeInfo("s", False, (0, np.inf), (False, False)) + return [ic, i_s] + + def _pdf(self, x, c, s): + return np.exp(self._logpdf(x, c, s)) + + def _logpdf(self, x, c, s): + return (np.log(c) - np.log(x) - np.log(s) + + _norm_logpdf(np.log(x) / s) + + _norm_logcdf(-np.log(x) / s) * (c - 1.)) + + def _cdf(self, x, c, s): + return -sc.expm1(self._logsf(x, c, s)) + + def _ppf(self, q, c, s): + return self._isf(1 - q, c, s) + + def _sf(self, x, c, s): + return np.exp(self._logsf(x, c, s)) + + def _logsf(self, x, c, s): + return _norm_logcdf(-np.log(x) / s) * c + + def _isf(self, q, c, s): + return np.exp(-_norm_ppf(q**(1/c)) * s) + + +powerlognorm = powerlognorm_gen(a=0.0, name="powerlognorm") + + +class powernorm_gen(rv_continuous): + r"""A power normal continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `powernorm` is: + + .. math:: + + f(x, c) = c \phi(x) (\Phi(-x))^{c-1} + + where :math:`\phi` is the normal pdf, :math:`\Phi` is the normal cdf, + :math:`x` is any real, and :math:`c > 0` [1]_. + + `powernorm` takes ``c`` as a shape parameter for :math:`c`. + + %(after_notes)s + + References + ---------- + .. [1] NIST Engineering Statistics Handbook, Section 1.3.6.6.13, + https://www.itl.nist.gov/div898/handbook//eda/section3/eda366d.htm + + %(example)s + + """ + def _shape_info(self): + return [_ShapeInfo("c", False, (0, np.inf), (False, False))] + + def _pdf(self, x, c): + # powernorm.pdf(x, c) = c * phi(x) * (Phi(-x))**(c-1) + return c*_norm_pdf(x) * (_norm_cdf(-x)**(c-1.0)) + + def _logpdf(self, x, c): + return np.log(c) + _norm_logpdf(x) + (c-1)*_norm_logcdf(-x) + + def _cdf(self, x, c): + return -sc.expm1(self._logsf(x, c)) + + def _ppf(self, q, c): + return -_norm_ppf(pow(1.0 - q, 1.0 / c)) + + def _sf(self, x, c): + return np.exp(self._logsf(x, c)) + + def _logsf(self, x, c): + return c * _norm_logcdf(-x) + + def _isf(self, q, c): + return -_norm_ppf(np.exp(np.log(q) / c)) + + +powernorm = powernorm_gen(name='powernorm') + + +class rdist_gen(rv_continuous): + r"""An R-distributed (symmetric beta) continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `rdist` is: + + .. math:: + + f(x, c) = \frac{(1-x^2)^{c/2-1}}{B(1/2, c/2)} + + for :math:`-1 \le x \le 1`, :math:`c > 0`. `rdist` is also called the + symmetric beta distribution: if B has a `beta` distribution with + parameters (c/2, c/2), then X = 2*B - 1 follows a R-distribution with + parameter c. + + `rdist` takes ``c`` as a shape parameter for :math:`c`. + + This distribution includes the following distribution kernels as + special cases:: + + c = 2: uniform + c = 3: `semicircular` + c = 4: Epanechnikov (parabolic) + c = 6: quartic (biweight) + c = 8: triweight + + %(after_notes)s + + %(example)s + + """ + def _shape_info(self): + return [_ShapeInfo("c", False, (0, np.inf), (False, False))] + + # use relation to the beta distribution for pdf, cdf, etc + def _pdf(self, x, c): + return np.exp(self._logpdf(x, c)) + + def _logpdf(self, x, c): + return -np.log(2) + beta._logpdf((x + 1)/2, c/2, c/2) + + def _cdf(self, x, c): + return beta._cdf((x + 1)/2, c/2, c/2) + + def _sf(self, x, c): + return beta._sf((x + 1)/2, c/2, c/2) + + def _ppf(self, q, c): + return 2*beta._ppf(q, c/2, c/2) - 1 + + def _rvs(self, c, size=None, random_state=None): + return 2 * random_state.beta(c/2, c/2, size) - 1 + + def _munp(self, n, c): + numerator = (1 - (n % 2)) * sc.beta((n + 1.0) / 2, c / 2.0) + return numerator / sc.beta(1. / 2, c / 2.) + + +rdist = rdist_gen(a=-1.0, b=1.0, name="rdist") + + +class rayleigh_gen(rv_continuous): + r"""A Rayleigh continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `rayleigh` is: + + .. math:: + + f(x) = x \exp(-x^2/2) + + for :math:`x \ge 0`. + + `rayleigh` is a special case of `chi` with ``df=2``. + + %(after_notes)s + + %(example)s + + """ + _support_mask = rv_continuous._open_support_mask + + def _shape_info(self): + return [] + + def _rvs(self, size=None, random_state=None): + return chi.rvs(2, size=size, random_state=random_state) + + def _pdf(self, r): + # rayleigh.pdf(r) = r * exp(-r**2/2) + return np.exp(self._logpdf(r)) + + def _logpdf(self, r): + return np.log(r) - 0.5 * r * r + + def _cdf(self, r): + return -sc.expm1(-0.5 * r**2) + + def _ppf(self, q): + return np.sqrt(-2 * sc.log1p(-q)) + + def _sf(self, r): + return np.exp(self._logsf(r)) + + def _logsf(self, r): + return -0.5 * r * r + + def _isf(self, q): + return np.sqrt(-2 * np.log(q)) + + def _stats(self): + val = 4 - np.pi + return (np.sqrt(np.pi/2), + val/2, + 2*(np.pi-3)*np.sqrt(np.pi)/val**1.5, + 6*np.pi/val-16/val**2) + + def _entropy(self): + return _EULER/2.0 + 1 - 0.5*np.log(2) + + @_call_super_mom + @extend_notes_in_docstring(rv_continuous, notes="""\ + Notes specifically for ``rayleigh.fit``: If the location is fixed with + the `floc` parameter, this method uses an analytical formula to find + the scale. Otherwise, this function uses a numerical root finder on + the first order conditions of the log-likelihood function to find the + MLE. Only the (optional) `loc` parameter is used as the initial guess + for the root finder; the `scale` parameter and any other parameters + for the optimizer are ignored.\n\n""") + def fit(self, data, *args, **kwds): + if kwds.pop('superfit', False): + return super().fit(data, *args, **kwds) + data, floc, fscale = _check_fit_input_parameters(self, data, + args, kwds) + + def scale_mle(loc): + # Source: Statistical Distributions, 3rd Edition. Evans, Hastings, + # and Peacock (2000), Page 175 + return (np.sum((data - loc) ** 2) / (2 * len(data))) ** .5 + + def loc_mle(loc): + # This implicit equation for `loc` is used when + # both `loc` and `scale` are free. + xm = data - loc + s1 = xm.sum() + s2 = (xm**2).sum() + s3 = (1/xm).sum() + return s1 - s2/(2*len(data))*s3 + + def loc_mle_scale_fixed(loc, scale=fscale): + # This implicit equation for `loc` is used when + # `scale` is fixed but `loc` is not. + xm = data - loc + return xm.sum() - scale**2 * (1/xm).sum() + + if floc is not None: + # `loc` is fixed, analytically determine `scale`. + if np.any(data - floc <= 0): + raise FitDataError("rayleigh", lower=1, upper=np.inf) + else: + return floc, scale_mle(floc) + + # Account for user provided guess of `loc`. + loc0 = kwds.get('loc') + if loc0 is None: + # Use _fitstart to estimate loc; ignore the returned scale. + loc0 = self._fitstart(data)[0] + + fun = loc_mle if fscale is None else loc_mle_scale_fixed + rbrack = np.nextafter(np.min(data), -np.inf) + lbrack = _get_left_bracket(fun, rbrack) + res = optimize.root_scalar(fun, bracket=(lbrack, rbrack)) + if not res.converged: + raise FitSolverError(res.flag) + loc = res.root + scale = fscale or scale_mle(loc) + return loc, scale + + +rayleigh = rayleigh_gen(a=0.0, name="rayleigh") + + +class reciprocal_gen(rv_continuous): + r"""A loguniform or reciprocal continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for this class is: + + .. math:: + + f(x, a, b) = \frac{1}{x \log(b/a)} + + for :math:`a \le x \le b`, :math:`b > a > 0`. This class takes + :math:`a` and :math:`b` as shape parameters. + + %(after_notes)s + + %(example)s + + This doesn't show the equal probability of ``0.01``, ``0.1`` and + ``1``. This is best when the x-axis is log-scaled: + + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> fig, ax = plt.subplots(1, 1) + >>> ax.hist(np.log10(r)) + >>> ax.set_ylabel("Frequency") + >>> ax.set_xlabel("Value of random variable") + >>> ax.xaxis.set_major_locator(plt.FixedLocator([-2, -1, 0])) + >>> ticks = ["$10^{{ {} }}$".format(i) for i in [-2, -1, 0]] + >>> ax.set_xticklabels(ticks) # doctest: +SKIP + >>> plt.show() + + This random variable will be log-uniform regardless of the base chosen for + ``a`` and ``b``. Let's specify with base ``2`` instead: + + >>> rvs = %(name)s(2**-2, 2**0).rvs(size=1000) + + Values of ``1/4``, ``1/2`` and ``1`` are equally likely with this random + variable. Here's the histogram: + + >>> fig, ax = plt.subplots(1, 1) + >>> ax.hist(np.log2(rvs)) + >>> ax.set_ylabel("Frequency") + >>> ax.set_xlabel("Value of random variable") + >>> ax.xaxis.set_major_locator(plt.FixedLocator([-2, -1, 0])) + >>> ticks = ["$2^{{ {} }}$".format(i) for i in [-2, -1, 0]] + >>> ax.set_xticklabels(ticks) # doctest: +SKIP + >>> plt.show() + + """ + def _argcheck(self, a, b): + return (a > 0) & (b > a) + + def _shape_info(self): + ia = _ShapeInfo("a", False, (0, np.inf), (False, False)) + ib = _ShapeInfo("b", False, (0, np.inf), (False, False)) + return [ia, ib] + + def _fitstart(self, data): + if isinstance(data, CensoredData): + data = data._uncensor() + # Reasonable, since support is [a, b] + return super()._fitstart(data, args=(np.min(data), np.max(data))) + + def _get_support(self, a, b): + return a, b + + def _pdf(self, x, a, b): + # reciprocal.pdf(x, a, b) = 1 / (x*(log(b) - log(a))) + return np.exp(self._logpdf(x, a, b)) + + def _logpdf(self, x, a, b): + return -np.log(x) - np.log(np.log(b) - np.log(a)) + + def _cdf(self, x, a, b): + return (np.log(x)-np.log(a)) / (np.log(b) - np.log(a)) + + def _ppf(self, q, a, b): + return np.exp(np.log(a) + q*(np.log(b) - np.log(a))) + + def _munp(self, n, a, b): + if n == 0: + return 1.0 + t1 = 1 / (np.log(b) - np.log(a)) / n + t2 = np.real(np.exp(_log_diff(n * np.log(b), n*np.log(a)))) + return t1 * t2 + + def _entropy(self, a, b): + return 0.5*(np.log(a) + np.log(b)) + np.log(np.log(b) - np.log(a)) + + fit_note = """\ + `loguniform`/`reciprocal` is over-parameterized. `fit` automatically + fixes `scale` to 1 unless `fscale` is provided by the user.\n\n""" + + @extend_notes_in_docstring(rv_continuous, notes=fit_note) + def fit(self, data, *args, **kwds): + fscale = kwds.pop('fscale', 1) + return super().fit(data, *args, fscale=fscale, **kwds) + + # Details related to the decision of not defining + # the survival function for this distribution can be + # found in the PR: https://github.com/scipy/scipy/pull/18614 + + +loguniform = reciprocal_gen(name="loguniform") +reciprocal = reciprocal_gen(name="reciprocal") +loguniform._support = ('a', 'b') +reciprocal._support = ('a', 'b') + + +class rice_gen(rv_continuous): + r"""A Rice continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `rice` is: + + .. math:: + + f(x, b) = x \exp(- \frac{x^2 + b^2}{2}) I_0(x b) + + for :math:`x >= 0`, :math:`b > 0`. :math:`I_0` is the modified Bessel + function of order zero (`scipy.special.i0`). + + `rice` takes ``b`` as a shape parameter for :math:`b`. + + %(after_notes)s + + The Rice distribution describes the length, :math:`r`, of a 2-D vector with + components :math:`(U+u, V+v)`, where :math:`U, V` are constant, :math:`u, + v` are independent Gaussian random variables with standard deviation + :math:`s`. Let :math:`R = \sqrt{U^2 + V^2}`. Then the pdf of :math:`r` is + ``rice.pdf(x, R/s, scale=s)``. + + %(example)s + + """ + def _argcheck(self, b): + return b >= 0 + + def _shape_info(self): + return [_ShapeInfo("b", False, (0, np.inf), (True, False))] + + def _rvs(self, b, size=None, random_state=None): + # https://en.wikipedia.org/wiki/Rice_distribution + t = b/np.sqrt(2) + random_state.standard_normal(size=(2,) + size) + return np.sqrt((t*t).sum(axis=0)) + + def _cdf(self, x, b): + return sc.chndtr(np.square(x), 2, np.square(b)) + + def _ppf(self, q, b): + return np.sqrt(sc.chndtrix(q, 2, np.square(b))) + + def _pdf(self, x, b): + # rice.pdf(x, b) = x * exp(-(x**2+b**2)/2) * I[0](x*b) + # + # We use (x**2 + b**2)/2 = ((x-b)**2)/2 + xb. + # The factor of np.exp(-xb) is then included in the i0e function + # in place of the modified Bessel function, i0, improving + # numerical stability for large values of xb. + return x * np.exp(-(x-b)*(x-b)/2.0) * sc.i0e(x*b) + + def _munp(self, n, b): + nd2 = n/2.0 + n1 = 1 + nd2 + b2 = b*b/2.0 + return (2.0**(nd2) * np.exp(-b2) * sc.gamma(n1) * + sc.hyp1f1(n1, 1, b2)) + + +rice = rice_gen(a=0.0, name="rice") + +class irwinhall_gen(rv_continuous): + r"""An Irwin-Hall (Uniform Sum) continuous random variable. + + An `Irwin-Hall `_ + continuous random variable is the sum of :math:`n` independent + standard uniform random variables [1]_ [2]_. + + %(before_notes)s + + Notes + ----- + Applications include `Rao's Spacing Test + `_, + a more powerful alternative to the Rayleigh test + when the data are not unimodal, and radar [3]_. + + Conveniently, the pdf and cdf are the :math:`n`-fold convolution of + the ones for the standard uniform distribution, which is also the + definition of the cardinal B-splines of degree :math:`n-1` + having knots evenly spaced from :math:`1` to :math:`n` [4]_ [5]_. + + The Bates distribution, which represents the *mean* of statistically + independent, uniformly distributed random variables, is simply the + Irwin-Hall distribution scaled by :math:`1/n`. For example, the frozen + distribution ``bates = irwinhall(10, scale=1/10)`` represents the + distribution of the mean of 10 uniformly distributed random variables. + + %(after_notes)s + + References + ---------- + .. [1] P. Hall, "The distribution of means for samples of size N drawn + from a population in which the variate takes values between 0 and 1, + all such values being equally probable", + Biometrika, Volume 19, Issue 3-4, December 1927, Pages 240-244, + :doi:`10.1093/biomet/19.3-4.240`. + .. [2] J. O. Irwin, "On the frequency distribution of the means of samples + from a population having any law of frequency with finite moments, + with special reference to Pearson's Type II, + Biometrika, Volume 19, Issue 3-4, December 1927, Pages 225-239, + :doi:`0.1093/biomet/19.3-4.225`. + .. [3] K. Buchanan, T. Adeyemi, C. Flores-Molina, S. Wheeland and D. Overturf, + "Sidelobe behavior and bandwidth characteristics + of distributed antenna arrays," + 2018 United States National Committee of + URSI National Radio Science Meeting (USNC-URSI NRSM), + Boulder, CO, USA, 2018, pp. 1-2. + https://www.usnc-ursi-archive.org/nrsm/2018/papers/B15-9.pdf. + .. [4] Amos Ron, "Lecture 1: Cardinal B-splines and convolution operators", p. 1 + https://pages.cs.wisc.edu/~deboor/887/lec1new.pdf. + .. [5] Trefethen, N. (2012, July). B-splines and convolution. Chebfun. + Retrieved April 30, 2024, from http://www.chebfun.org/examples/approx/BSplineConv.html. + + %(example)s + """ # noqa: E501 + + @replace_notes_in_docstring(rv_continuous, notes="""\ + Raises a ``NotImplementedError`` for the Irwin-Hall distribution because + the generic `fit` implementation is unreliable and no custom implementation + is available. Consider using `scipy.stats.fit`.\n\n""") + def fit(self, data, *args, **kwds): + fit_notes = ("The generic `fit` implementation is unreliable for this " + "distribution, and no custom implementation is available. " + "Consider using `scipy.stats.fit`.") + raise NotImplementedError(fit_notes) + + def _argcheck(self, n): + return (n > 0) & _isintegral(n) & np.isrealobj(n) + + def _get_support(self, n): + return 0, n + + def _shape_info(self): + return [_ShapeInfo("n", True, (1, np.inf), (True, False))] + + def _munp(self, order, n): + # see https://link.springer.com/content/pdf/10.1007/s10959-020-01050-9.pdf + # page 640, with m=n, j=n+order + def vmunp(order, n): + n = np.asarray(n, dtype=np.int64) + return (sc.stirling2(n+order, n, exact=True) + / sc.comb(n+order, n, exact=True)) + + # exact rationals, but we convert to float anyway + return np.vectorize(vmunp, otypes=[np.float64])(order, n) + + @staticmethod + def _cardbspl(n): + t = np.arange(n+1) + return BSpline.basis_element(t) + + def _pdf(self, x, n): + def vpdf(x, n): + return self._cardbspl(n)(x) + return np.vectorize(vpdf, otypes=[np.float64])(x, n) + + def _cdf(self, x, n): + def vcdf(x, n): + return self._cardbspl(n).antiderivative()(x) + return np.vectorize(vcdf, otypes=[np.float64])(x, n) + + def _sf(self, x, n): + def vsf(x, n): + return self._cardbspl(n).antiderivative()(n-x) + return np.vectorize(vsf, otypes=[np.float64])(x, n) + + def _rvs(self, n, size=None, random_state=None, *args): + @_vectorize_rvs_over_shapes + def _rvs1(n, size=None, random_state=None): + n = np.floor(n).astype(int) + usize = (n,) if size is None else (n, *size) + return random_state.uniform(size=usize).sum(axis=0) + return _rvs1(n, size=size, random_state=random_state) + + def _stats(self, n): + # mgf = ((exp(t) - 1)/t)**n + # m'th derivative follows from the generalized Leibniz rule + # Moments follow directly from the definition as the sum of n iid unif(0,1) + # and the summation rules for moments of a sum of iid random variables + # E(IH((n))) = n*E(U(0,1)) = n/2 + # Var(IH((n))) = n*Var(U(0,1)) = n/12 + # Skew(IH((n))) = Skew(U(0,1))/sqrt(n) = 0 + # Kurt(IH((n))) = Kurt(U(0,1))/n = -6/(5*n) -- Fisher's excess kurtosis + # See e.g. https://en.wikipedia.org/wiki/Irwin%E2%80%93Hall_distribution + + return n/2, n/12, 0, -6/(5*n) + + +irwinhall = irwinhall_gen(name="irwinhall") +irwinhall._support = (0.0, 'n') + + +class recipinvgauss_gen(rv_continuous): + r"""A reciprocal inverse Gaussian continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `recipinvgauss` is: + + .. math:: + + f(x, \mu) = \frac{1}{\sqrt{2\pi x}} + \exp\left(\frac{-(1-\mu x)^2}{2\mu^2x}\right) + + for :math:`x \ge 0`. + + `recipinvgauss` takes ``mu`` as a shape parameter for :math:`\mu`. + + %(after_notes)s + + %(example)s + + """ + def _shape_info(self): + return [_ShapeInfo("mu", False, (0, np.inf), (False, False))] + + def _pdf(self, x, mu): + # recipinvgauss.pdf(x, mu) = + # 1/sqrt(2*pi*x) * exp(-(1-mu*x)**2/(2*x*mu**2)) + return np.exp(self._logpdf(x, mu)) + + def _logpdf(self, x, mu): + return xpx.apply_where( + x > 0, (x, mu), + lambda x, mu: (-(1 - mu*x)**2.0 / (2*x*mu**2.0) + - 0.5*np.log(2*np.pi*x)), + fill_value=-np.inf) + + def _cdf(self, x, mu): + trm1 = 1.0/mu - x + trm2 = 1.0/mu + x + isqx = 1.0/np.sqrt(x) + return _norm_cdf(-isqx*trm1) - np.exp(2.0/mu)*_norm_cdf(-isqx*trm2) + + def _sf(self, x, mu): + trm1 = 1.0/mu - x + trm2 = 1.0/mu + x + isqx = 1.0/np.sqrt(x) + return _norm_cdf(isqx*trm1) + np.exp(2.0/mu)*_norm_cdf(-isqx*trm2) + + def _rvs(self, mu, size=None, random_state=None): + return 1.0/random_state.wald(mu, 1.0, size=size) + + +recipinvgauss = recipinvgauss_gen(a=0.0, name='recipinvgauss') + + +class semicircular_gen(rv_continuous): + r"""A semicircular continuous random variable. + + %(before_notes)s + + See Also + -------- + rdist + + Notes + ----- + The probability density function for `semicircular` is: + + .. math:: + + f(x) = \frac{2}{\pi} \sqrt{1-x^2} + + for :math:`-1 \le x \le 1`. + + The distribution is a special case of `rdist` with ``c = 3``. + + %(after_notes)s + + References + ---------- + .. [1] "Wigner semicircle distribution", + https://en.wikipedia.org/wiki/Wigner_semicircle_distribution + + %(example)s + + """ + def _shape_info(self): + return [] + + def _pdf(self, x): + return 2.0/np.pi*np.sqrt(1-x*x) + + def _logpdf(self, x): + return np.log(2/np.pi) + 0.5*sc.log1p(-x*x) + + def _cdf(self, x): + return 0.5+1.0/np.pi*(x*np.sqrt(1-x*x) + np.arcsin(x)) + + def _ppf(self, q): + return rdist._ppf(q, 3) + + def _rvs(self, size=None, random_state=None): + # generate values uniformly distributed on the area under the pdf + # (semi-circle) by randomly generating the radius and angle + r = np.sqrt(random_state.uniform(size=size)) + a = np.cos(np.pi * random_state.uniform(size=size)) + return r * a + + def _stats(self): + return 0, 0.25, 0, -1.0 + + def _entropy(self): + return 0.64472988584940017414 + + +semicircular = semicircular_gen(a=-1.0, b=1.0, name="semicircular") + + +class skewcauchy_gen(rv_continuous): + r"""A skewed Cauchy random variable. + + %(before_notes)s + + See Also + -------- + cauchy : Cauchy distribution + + Notes + ----- + + The probability density function for `skewcauchy` is: + + .. math:: + + f(x) = \frac{1}{\pi \left(\frac{x^2}{\left(a\, \text{sign}(x) + 1 + \right)^2} + 1 \right)} + + for a real number :math:`x` and skewness parameter :math:`-1 < a < 1`. + + When :math:`a=0`, the distribution reduces to the usual Cauchy + distribution. + + %(after_notes)s + + References + ---------- + .. [1] "Skewed generalized *t* distribution", Wikipedia + https://en.wikipedia.org/wiki/Skewed_generalized_t_distribution#Skewed_Cauchy_distribution + + %(example)s + + """ + def _argcheck(self, a): + return np.abs(a) < 1 + + def _shape_info(self): + return [_ShapeInfo("a", False, (-1.0, 1.0), (False, False))] + + def _pdf(self, x, a): + return 1 / (np.pi * (x**2 / (a * np.sign(x) + 1)**2 + 1)) + + def _cdf(self, x, a): + return np.where(x <= 0, + (1 - a) / 2 + (1 - a) / np.pi * np.arctan(x / (1 - a)), + (1 - a) / 2 + (1 + a) / np.pi * np.arctan(x / (1 + a))) + + def _ppf(self, x, a): + i = x < self._cdf(0, a) + return np.where(i, + np.tan(np.pi / (1 - a) * (x - (1 - a) / 2)) * (1 - a), + np.tan(np.pi / (1 + a) * (x - (1 - a) / 2)) * (1 + a)) + + def _stats(self, a, moments='mvsk'): + return np.nan, np.nan, np.nan, np.nan + + def _fitstart(self, data): + # Use 0 as the initial guess of the skewness shape parameter. + # For the location and scale, estimate using the median and + # quartiles. + if isinstance(data, CensoredData): + data = data._uncensor() + p25, p50, p75 = np.percentile(data, [25, 50, 75]) + return 0.0, p50, (p75 - p25)/2 + + +skewcauchy = skewcauchy_gen(name='skewcauchy') + + +class skewnorm_gen(rv_continuous): + r"""A skew-normal random variable. + + %(before_notes)s + + Notes + ----- + The pdf is:: + + skewnorm.pdf(x, a) = 2 * norm.pdf(x) * norm.cdf(a*x) + + `skewnorm` takes a real number :math:`a` as a skewness parameter + When ``a = 0`` the distribution is identical to a normal distribution + (`norm`). `rvs` implements the method of [1]_. + + This distribution uses routines from the Boost Math C++ library for + the computation of ``cdf``, ``ppf`` and ``isf`` methods. [2]_ + + %(after_notes)s + + References + ---------- + .. [1] A. Azzalini and A. Capitanio (1999). Statistical applications of + the multivariate skew-normal distribution. J. Roy. Statist. Soc., + B 61, 579-602. :arxiv:`0911.2093` + .. [2] The Boost Developers. "Boost C++ Libraries". https://www.boost.org/. + + %(example)s + + """ + def _argcheck(self, a): + return np.isfinite(a) + + def _shape_info(self): + return [_ShapeInfo("a", False, (-np.inf, np.inf), (False, False))] + + def _pdf(self, x, a): + return xpx.apply_where( + a == 0, (x, a), + lambda x, a: _norm_pdf(x), + lambda x, a: 2.*_norm_pdf(x)*_norm_cdf(a*x)) + + def _logpdf(self, x, a): + return xpx.apply_where( + a == 0, (x, a), + lambda x, a: _norm_logpdf(x), + lambda x, a: np.log(2)+_norm_logpdf(x)+_norm_logcdf(a*x)) + + def _cdf(self, x, a): + a = np.atleast_1d(a) + cdf = scu._skewnorm_cdf(x, 0.0, 1.0, a) + # for some reason, a isn't broadcasted if some of x are invalid + a = np.broadcast_to(a, cdf.shape) + # Boost is not accurate in left tail when a > 0 + i_small_cdf = (cdf < 1e-6) & (a > 0) + cdf[i_small_cdf] = super()._cdf(x[i_small_cdf], a[i_small_cdf]) + return np.clip(cdf, 0, 1) + + def _ppf(self, x, a): + return scu._skewnorm_ppf(x, 0.0, 1.0, a) + + def _sf(self, x, a): + # Boost's SF is implemented this way. Use whatever customizations + # we made in the _cdf. + return self._cdf(-x, -a) + + def _isf(self, x, a): + return scu._skewnorm_isf(x, 0.0, 1.0, a) + + def _rvs(self, a, size=None, random_state=None): + u0 = random_state.normal(size=size) + v = random_state.normal(size=size) + d = a/np.sqrt(1 + a**2) + u1 = d*u0 + v*np.sqrt(1 - d**2) + return np.where(u0 >= 0, u1, -u1) + + def _stats(self, a, moments='mvsk'): + output = [None, None, None, None] + const = np.sqrt(2/np.pi) * a/np.sqrt(1 + a**2) + + if 'm' in moments: + output[0] = const + if 'v' in moments: + output[1] = 1 - const**2 + if 's' in moments: + output[2] = ((4 - np.pi)/2) * (const/np.sqrt(1 - const**2))**3 + if 'k' in moments: + output[3] = (2*(np.pi - 3)) * (const**4/(1 - const**2)**2) + + return output + + # For odd order, the each noncentral moment of the skew-normal distribution + # with location 0 and scale 1 can be expressed as a polynomial in delta, + # where delta = a/sqrt(1 + a**2) and `a` is the skew-normal shape + # parameter. The dictionary _skewnorm_odd_moments defines those + # polynomials for orders up to 19. The dict is implemented as a cached + # property to reduce the impact of the creation of the dict on import time. + @cached_property + def _skewnorm_odd_moments(self): + skewnorm_odd_moments = { + 1: Polynomial([1]), + 3: Polynomial([3, -1]), + 5: Polynomial([15, -10, 3]), + 7: Polynomial([105, -105, 63, -15]), + 9: Polynomial([945, -1260, 1134, -540, 105]), + 11: Polynomial([10395, -17325, 20790, -14850, 5775, -945]), + 13: Polynomial([135135, -270270, 405405, -386100, 225225, -73710, + 10395]), + 15: Polynomial([2027025, -4729725, 8513505, -10135125, 7882875, + -3869775, 1091475, -135135]), + 17: Polynomial([34459425, -91891800, 192972780, -275675400, + 268017750, -175429800, 74220300, -18378360, + 2027025]), + 19: Polynomial([654729075, -1964187225, 4714049340, -7856748900, + 9166207050, -7499623950, 4230557100, -1571349780, + 346621275, -34459425]), + } + return skewnorm_odd_moments + + def _munp(self, order, a): + if order % 2: + if order > 19: + raise NotImplementedError("skewnorm noncentral moments not " + "implemented for odd orders greater " + "than 19.") + # Use the precomputed polynomials that were derived from the + # moment generating function. + delta = a/np.sqrt(1 + a**2) + return (delta * self._skewnorm_odd_moments[order](delta**2) + * _SQRT_2_OVER_PI) + else: + # For even order, the moment is just (order-1)!!, where !! is the + # notation for the double factorial; for an odd integer m, m!! is + # m*(m-2)*...*3*1. + # We could use special.factorial2, but we know the argument is odd, + # so avoid the overhead of that function and compute the result + # directly here. + return sc.gamma((order + 1)/2) * 2**(order/2) / _SQRT_PI + + @extend_notes_in_docstring(rv_continuous, notes="""\ + If ``method='mm'``, parameters fixed by the user are respected, and the + remaining parameters are used to match distribution and sample moments + where possible. For example, if the user fixes the location with + ``floc``, the parameters will only match the distribution skewness and + variance to the sample skewness and variance; no attempt will be made + to match the means or minimize a norm of the errors. + Note that the maximum possible skewness magnitude of a + `scipy.stats.skewnorm` distribution is approximately 0.9952717; if the + magnitude of the data's sample skewness exceeds this, the returned + shape parameter ``a`` will be infinite. + \n\n""") + def fit(self, data, *args, **kwds): + if kwds.pop("superfit", False): + return super().fit(data, *args, **kwds) + if isinstance(data, CensoredData): + if data.num_censored() == 0: + data = data._uncensor() + else: + return super().fit(data, *args, **kwds) + + # this extracts fixed shape, location, and scale however they + # are specified, and also leaves them in `kwds` + data, fa, floc, fscale = _check_fit_input_parameters(self, data, + args, kwds) + method = kwds.get("method", "mle").lower() + + # See https://en.wikipedia.org/wiki/Skew_normal_distribution for + # moment formulas. + def skew_d(d): # skewness in terms of delta + return (4-np.pi)/2 * ((d * np.sqrt(2 / np.pi))**3 + / (1 - 2*d**2 / np.pi)**(3/2)) + + def d_skew(skew): # delta in terms of skewness + s_23 = np.abs(skew)**(2/3) + return np.sign(skew) * np.sqrt( + np.pi/2 * s_23 / (s_23 + ((4 - np.pi)/2)**(2/3)) + ) + + # If method is method of moments, we don't need the user's guesses. + # Otherwise, extract the guesses from args and kwds. + if method == "mm": + a, loc, scale = None, None, None + else: + a = args[0] if len(args) else None + loc = kwds.pop('loc', None) + scale = kwds.pop('scale', None) + + if fa is None and a is None: # not fixed and no guess: use MoM + # Solve for a that matches sample distribution skewness to sample + # skewness. + s = stats.skew(data) + if method == 'mle': + # For MLE initial conditions, clip skewness to a large but + # reasonable value in case the data skewness is out-of-range. + s = np.clip(s, -0.99, 0.99) + else: + s_max = skew_d(1) + s = np.clip(s, -s_max, s_max) + d = d_skew(s) + with np.errstate(divide='ignore'): + a = np.sqrt(np.divide(d**2, (1-d**2)))*np.sign(s) + else: + a = fa if fa is not None else a + d = a / np.sqrt(1 + a**2) + + if fscale is None and scale is None: + v = np.var(data) + scale = np.sqrt(v / (1 - 2*d**2/np.pi)) + elif fscale is not None: + scale = fscale + + if floc is None and loc is None: + m = np.mean(data) + loc = m - scale*d*np.sqrt(2/np.pi) + elif floc is not None: + loc = floc + + if method == 'mm': + return a, loc, scale + else: + # At this point, parameter "guesses" may equal the fixed parameters + # in kwds. No harm in passing them as guesses, too. + return super().fit(data, a, loc=loc, scale=scale, **kwds) + + +skewnorm = skewnorm_gen(name='skewnorm') + + +class trapezoid_gen(rv_continuous): + r""" + A trapezoidal continuous random variable. + + %(before_notes)s + + Notes + ----- + The trapezoidal distribution can be represented with an up-sloping line + from ``loc`` to ``(loc + c*scale)``, then constant to ``(loc + d*scale)`` + and then downsloping from ``(loc + d*scale)`` to ``(loc+scale)``. This + defines the trapezoid base from ``loc`` to ``(loc+scale)`` and the flat + top from ``c`` to ``d`` proportional to the position along the base + with ``0 <= c <= d <= 1``. When ``c=d``, this is equivalent to `triang` + with the same values for `loc`, `scale` and `c`. + The method of [1]_ is used for computing moments. + + `trapezoid` takes :math:`c` and :math:`d` as shape parameters. + + %(after_notes)s + + The standard form is in the range [0, 1] with c the mode. + The location parameter shifts the start to `loc`. + The scale parameter changes the width from 1 to `scale`. + + References + ---------- + .. [1] Kacker, R.N. and Lawrence, J.F. (2007). Trapezoidal and triangular + distributions for Type B evaluation of standard uncertainty. + Metrologia 44, 117-127. :doi:`10.1088/0026-1394/44/2/003` + + %(example)s + """ + def _argcheck(self, c, d): + return (c >= 0) & (c <= 1) & (d >= 0) & (d <= 1) & (d >= c) + + def _shape_info(self): + ic = _ShapeInfo("c", False, (0, 1.0), (True, True)) + id = _ShapeInfo("d", False, (0, 1.0), (True, True)) + return [ic, id] + + def _pdf(self, x, c, d): + u = 2 / (d-c+1) + + return _lazyselect([x < c, + (c <= x) & (x <= d), + x > d], + [lambda x, c, d, u: u * x / c, + lambda x, c, d, u: u, + lambda x, c, d, u: u * (1-x) / (1-d)], + (x, c, d, u)) + + def _cdf(self, x, c, d): + return _lazyselect([x < c, + (c <= x) & (x <= d), + x > d], + [lambda x, c, d: x**2 / c / (d-c+1), + lambda x, c, d: (c + 2 * (x-c)) / (d-c+1), + lambda x, c, d: 1-((1-x) ** 2 + / (d-c+1) / (1-d))], + (x, c, d)) + + def _ppf(self, q, c, d): + qc, qd = self._cdf(c, c, d), self._cdf(d, c, d) + condlist = [q < qc, q <= qd, q > qd] + choicelist = [np.sqrt(q * c * (1 + d - c)), + 0.5 * q * (1 + d - c) + 0.5 * c, + 1 - np.sqrt((1 - q) * (d - c + 1) * (1 - d))] + return np.select(condlist, choicelist) + + def _munp(self, n, c, d): + # Using the parameterization from Kacker, 2007, with + # a=bottom left, c=top left, d=top right, b=bottom right, then + # E[X^n] = h/(n+1)/(n+2) [(b^{n+2}-d^{n+2})/(b-d) + # - ((c^{n+2} - a^{n+2})/(c-a)] + # with h = 2/((b-a) - (d-c)). The corresponding parameterization + # in scipy, has a'=loc, c'=loc+c*scale, d'=loc+d*scale, b'=loc+scale, + # which for standard form reduces to a'=0, b'=1, c'=c, d'=d. + # Substituting into E[X^n] gives the bd' term as (1 - d^{n+2})/(1 - d) + # and the ac' term as c^{n-1} for the standard form. The bd' term has + # numerical difficulties near d=1, so replace (1 - d^{n+2})/(1-d) + # with expm1((n+2)*log(d))/(d-1). + # Testing with n=18 for c=(1e-30,1-eps) shows that this is stable. + # We still require an explicit test for d=1 to prevent divide by zero, + # and now a test for d=0 to prevent log(0). + ab_term = c**(n+1) + dc_term = _lazyselect( + [d == 0.0, (0.0 < d) & (d < 1.0), d == 1.0], + [lambda d: 1.0, + lambda d: np.expm1((n+2) * np.log(d)) / (d-1.0), + lambda d: n+2], + [d]) + val = 2.0 / (1.0+d-c) * (dc_term - ab_term) / ((n+1) * (n+2)) + return val + + def _entropy(self, c, d): + # Using the parameterization from Wikipedia (van Dorp, 2003) + # with a=bottom left, c=top left, d=top right, b=bottom right + # gives a'=loc, b'=loc+c*scale, c'=loc+d*scale, d'=loc+scale, + # which for loc=0, scale=1 is a'=0, b'=c, c'=d, d'=1. + # Substituting into the entropy formula from Wikipedia gives + # the following result. + return 0.5 * (1.0-d+c) / (1.0+d-c) + np.log(0.5 * (1.0+d-c)) + + def _fitstart(self, data, args=None): + # Arbitrary, but c=d=1 fails due to being on edge of bounds + if args is None: + args = (0.33, 0.66) + return super()._fitstart(data, args=args) + + +trapezoid = trapezoid_gen(a=0.0, b=1.0, name="trapezoid") + + +class triang_gen(rv_continuous): + r"""A triangular continuous random variable. + + %(before_notes)s + + Notes + ----- + The triangular distribution can be represented with an up-sloping line from + ``loc`` to ``(loc + c*scale)`` and then downsloping for ``(loc + c*scale)`` + to ``(loc + scale)``. + + `triang` takes ``c`` as a shape parameter for :math:`0 \le c \le 1`. + + %(after_notes)s + + The standard form is in the range [0, 1] with c the mode. + The location parameter shifts the start to `loc`. + The scale parameter changes the width from 1 to `scale`. + + %(example)s + + """ + def _rvs(self, c, size=None, random_state=None): + return random_state.triangular(0, c, 1, size) + + def _argcheck(self, c): + return (c >= 0) & (c <= 1) + + def _shape_info(self): + return [_ShapeInfo("c", False, (0, 1.0), (True, True))] + + def _pdf(self, x, c): + # 0: edge case where c=0 + # 1: generalised case for x < c, don't use x <= c, as it doesn't cope + # with c = 0. + # 2: generalised case for x >= c, but doesn't cope with c = 1 + # 3: edge case where c=1 + r = _lazyselect([c == 0, + x < c, + (x >= c) & (c != 1), + c == 1], + [lambda x, c: 2 - 2 * x, + lambda x, c: 2 * x / c, + lambda x, c: 2 * (1 - x) / (1 - c), + lambda x, c: 2 * x], + (x, c)) + return r + + def _cdf(self, x, c): + r = _lazyselect([c == 0, + x < c, + (x >= c) & (c != 1), + c == 1], + [lambda x, c: 2*x - x*x, + lambda x, c: x * x / c, + lambda x, c: (x*x - 2*x + c) / (c-1), + lambda x, c: x * x], + (x, c)) + return r + + def _ppf(self, q, c): + return np.where(q < c, np.sqrt(c * q), 1-np.sqrt((1-c) * (1-q))) + + def _stats(self, c): + return ((c+1.0)/3.0, + (1.0-c+c*c)/18, + np.sqrt(2)*(2*c-1)*(c+1)*(c-2) / (5*np.power((1.0-c+c*c), 1.5)), + -3.0/5.0) + + def _entropy(self, c): + return 0.5-np.log(2) + + +triang = triang_gen(a=0.0, b=1.0, name="triang") + + +class truncexpon_gen(rv_continuous): + r"""A truncated exponential continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `truncexpon` is: + + .. math:: + + f(x, b) = \frac{\exp(-x)}{1 - \exp(-b)} + + for :math:`0 <= x <= b`. + + `truncexpon` takes ``b`` as a shape parameter for :math:`b`. + + %(after_notes)s + + %(example)s + + """ + def _shape_info(self): + return [_ShapeInfo("b", False, (0, np.inf), (False, False))] + + def _get_support(self, b): + return self.a, b + + def _pdf(self, x, b): + # truncexpon.pdf(x, b) = exp(-x) / (1-exp(-b)) + return np.exp(-x)/(-sc.expm1(-b)) + + def _logpdf(self, x, b): + return -x - np.log(-sc.expm1(-b)) + + def _cdf(self, x, b): + return sc.expm1(-x)/sc.expm1(-b) + + def _ppf(self, q, b): + return -sc.log1p(q*sc.expm1(-b)) + + def _sf(self, x, b): + return (np.exp(-b) - np.exp(-x))/sc.expm1(-b) + + def _isf(self, q, b): + return -np.log(np.exp(-b) - q * sc.expm1(-b)) + + def _munp(self, n, b): + # wrong answer with formula, same as in continuous.pdf + # return sc.gamman+1)-sc.gammainc1+n, b) + if n == 1: + return (1-(b+1)*np.exp(-b))/(-sc.expm1(-b)) + elif n == 2: + return 2*(1-0.5*(b*b+2*b+2)*np.exp(-b))/(-sc.expm1(-b)) + else: + # return generic for higher moments + return super()._munp(n, b) + + def _entropy(self, b): + eB = np.exp(b) + return np.log(eB-1)+(1+eB*(b-1.0))/(1.0-eB) + + +truncexpon = truncexpon_gen(a=0.0, name='truncexpon') +truncexpon._support = (0.0, 'b') + + +# logsumexp trick for log(p + q) with only log(p) and log(q) +def _log_sum(log_p, log_q): + return sc.logsumexp([log_p, log_q], axis=0) + + +# same as above, but using -exp(x) = exp(x + πi) +def _log_diff(log_p, log_q): + return sc.logsumexp([log_p, log_q+np.pi*1j], axis=0) + + +def _log_gauss_mass(a, b): + """Log of Gaussian probability mass within an interval""" + a, b = np.broadcast_arrays(a, b) + + # Calculations in right tail are inaccurate, so we'll exploit the + # symmetry and work only in the left tail + case_left = b <= 0 + case_right = a > 0 + case_central = ~(case_left | case_right) + + def mass_case_left(a, b): + return _log_diff(_norm_logcdf(b), _norm_logcdf(a)) + + def mass_case_right(a, b): + return mass_case_left(-b, -a) + + def mass_case_central(a, b): + # Previously, this was implemented as: + # left_mass = mass_case_left(a, 0) + # right_mass = mass_case_right(0, b) + # return _log_sum(left_mass, right_mass) + # Catastrophic cancellation occurs as np.exp(log_mass) approaches 1. + # Correct for this with an alternative formulation. + # We're not concerned with underflow here: if only one term + # underflows, it was insignificant; if both terms underflow, + # the result can't accurately be represented in logspace anyway + # because sc.log1p(x) ~ x for small x. + return sc.log1p(-_norm_cdf(a) - _norm_cdf(-b)) + + # _lazyselect not working; don't care to debug it + out = np.full_like(a, fill_value=np.nan, dtype=np.complex128) + if a[case_left].size: + out[case_left] = mass_case_left(a[case_left], b[case_left]) + if a[case_right].size: + out[case_right] = mass_case_right(a[case_right], b[case_right]) + if a[case_central].size: + out[case_central] = mass_case_central(a[case_central], b[case_central]) + return np.real(out) # discard ~0j + + +class truncnorm_gen(rv_continuous): + r"""A truncated normal continuous random variable. + + %(before_notes)s + + Notes + ----- + This distribution is the normal distribution centered on ``loc`` (default + 0), with standard deviation ``scale`` (default 1), and truncated at ``a`` + and ``b`` *standard deviations* from ``loc``. For arbitrary ``loc`` and + ``scale``, ``a`` and ``b`` are *not* the abscissae at which the shifted + and scaled distribution is truncated. + + .. note:: + If ``a_trunc`` and ``b_trunc`` are the abscissae at which we wish + to truncate the distribution (as opposed to the number of standard + deviations from ``loc``), then we can calculate the distribution + parameters ``a`` and ``b`` as follows:: + + a, b = (a_trunc - loc) / scale, (b_trunc - loc) / scale + + This is a common point of confusion. For additional clarification, + please see the example below. + + %(example)s + + In the examples above, ``loc=0`` and ``scale=1``, so the plot is truncated + at ``a`` on the left and ``b`` on the right. However, suppose we were to + produce the same histogram with ``loc = 1`` and ``scale=0.5``. + + >>> loc, scale = 1, 0.5 + >>> rv = truncnorm(a, b, loc=loc, scale=scale) + >>> x = np.linspace(truncnorm.ppf(0.01, a, b), + ... truncnorm.ppf(0.99, a, b), 100) + >>> r = rv.rvs(size=1000) + + >>> fig, ax = plt.subplots(1, 1) + >>> ax.plot(x, rv.pdf(x), 'k-', lw=2, label='frozen pdf') + >>> ax.hist(r, density=True, bins='auto', histtype='stepfilled', alpha=0.2) + >>> ax.set_xlim(a, b) + >>> ax.legend(loc='best', frameon=False) + >>> plt.show() + + Note that the distribution is no longer appears to be truncated at + abscissae ``a`` and ``b``. That is because the *standard* normal + distribution is first truncated at ``a`` and ``b``, *then* the resulting + distribution is scaled by ``scale`` and shifted by ``loc``. If we instead + want the shifted and scaled distribution to be truncated at ``a`` and + ``b``, we need to transform these values before passing them as the + distribution parameters. + + >>> a_transformed, b_transformed = (a - loc) / scale, (b - loc) / scale + >>> rv = truncnorm(a_transformed, b_transformed, loc=loc, scale=scale) + >>> x = np.linspace(truncnorm.ppf(0.01, a, b), + ... truncnorm.ppf(0.99, a, b), 100) + >>> r = rv.rvs(size=10000) + + >>> fig, ax = plt.subplots(1, 1) + >>> ax.plot(x, rv.pdf(x), 'k-', lw=2, label='frozen pdf') + >>> ax.hist(r, density=True, bins='auto', histtype='stepfilled', alpha=0.2) + >>> ax.set_xlim(a-0.1, b+0.1) + >>> ax.legend(loc='best', frameon=False) + >>> plt.show() + """ + + def _argcheck(self, a, b): + return a < b + + def _shape_info(self): + ia = _ShapeInfo("a", False, (-np.inf, np.inf), (True, False)) + ib = _ShapeInfo("b", False, (-np.inf, np.inf), (False, True)) + return [ia, ib] + + def _fitstart(self, data): + # Reasonable, since support is [a, b] + if isinstance(data, CensoredData): + data = data._uncensor() + return super()._fitstart(data, args=(np.min(data), np.max(data))) + + def _get_support(self, a, b): + return a, b + + def _pdf(self, x, a, b): + return np.exp(self._logpdf(x, a, b)) + + def _logpdf(self, x, a, b): + return _norm_logpdf(x) - _log_gauss_mass(a, b) + + def _cdf(self, x, a, b): + return np.exp(self._logcdf(x, a, b)) + + def _logcdf(self, x, a, b): + x, a, b = np.broadcast_arrays(x, a, b) + logcdf = np.asarray(_log_gauss_mass(a, x) - _log_gauss_mass(a, b)) + i = logcdf > -0.1 # avoid catastrophic cancellation + if np.any(i): + logcdf[i] = np.log1p(-np.exp(self._logsf(x[i], a[i], b[i]))) + return logcdf + + def _sf(self, x, a, b): + return np.exp(self._logsf(x, a, b)) + + def _logsf(self, x, a, b): + x, a, b = np.broadcast_arrays(x, a, b) + logsf = np.asarray(_log_gauss_mass(x, b) - _log_gauss_mass(a, b)) + i = logsf > -0.1 # avoid catastrophic cancellation + if np.any(i): + logsf[i] = np.log1p(-np.exp(self._logcdf(x[i], a[i], b[i]))) + return logsf + + def _entropy(self, a, b): + A = _norm_cdf(a) + B = _norm_cdf(b) + Z = B - A + C = np.log(np.sqrt(2 * np.pi * np.e) * Z) + D = (a * _norm_pdf(a) - b * _norm_pdf(b)) / (2 * Z) + h = C + D + return h + + def _ppf(self, q, a, b): + q, a, b = np.broadcast_arrays(q, a, b) + + case_left = a < 0 + case_right = ~case_left + + def ppf_left(q, a, b): + log_Phi_x = _log_sum(_norm_logcdf(a), + np.log(q) + _log_gauss_mass(a, b)) + return sc.ndtri_exp(log_Phi_x) + + def ppf_right(q, a, b): + log_Phi_x = _log_sum(_norm_logcdf(-b), + np.log1p(-q) + _log_gauss_mass(a, b)) + return -sc.ndtri_exp(log_Phi_x) + + out = np.empty_like(q) + + q_left = q[case_left] + q_right = q[case_right] + + if q_left.size: + out[case_left] = ppf_left(q_left, a[case_left], b[case_left]) + if q_right.size: + out[case_right] = ppf_right(q_right, a[case_right], b[case_right]) + + return out + + def _isf(self, q, a, b): + # Mostly copy-paste of _ppf, but I think this is simpler than combining + q, a, b = np.broadcast_arrays(q, a, b) + + case_left = b < 0 + case_right = ~case_left + + def isf_left(q, a, b): + log_Phi_x = _log_diff(_norm_logcdf(b), + np.log(q) + _log_gauss_mass(a, b)) + return sc.ndtri_exp(np.real(log_Phi_x)) + + def isf_right(q, a, b): + log_Phi_x = _log_diff(_norm_logcdf(-a), + np.log1p(-q) + _log_gauss_mass(a, b)) + return -sc.ndtri_exp(np.real(log_Phi_x)) + + out = np.empty_like(q) + + q_left = q[case_left] + q_right = q[case_right] + + if q_left.size: + out[case_left] = isf_left(q_left, a[case_left], b[case_left]) + if q_right.size: + out[case_right] = isf_right(q_right, a[case_right], b[case_right]) + + return out + + def _munp(self, n, a, b): + def n_th_moment(n, a, b): + """ + Returns n-th moment. Defined only if n >= 0. + Function cannot broadcast due to the loop over n + """ + ab = np.asarray([a, b]) + pA, pB = self._pdf(ab, a, b) + probs = np.asarray([pA, -pB]) + cond = probs != 0 + moments = [0, 1] + for k in range(1, n+1): + # a or b might be infinite, and the corresponding pdf value + # is 0 in that case, but nan is returned for the + # multiplication. However, as b->infinity, pdf(b)*b**k -> 0. + # So it is safe to use xpx.apply_where to avoid the nan. + vals = xpx.apply_where(cond, (probs, ab), + lambda x, y: x * y**(k-1), + fill_value=0) + mk = np.sum(vals) + (k-1) * moments[-2] + moments.append(mk) + return moments[-1] + + return xpx.apply_where((n >= 0) & (a == a) & (b == b), (n, a, b), + np.vectorize(n_th_moment, otypes=[np.float64]), + fill_value=np.nan) + + def _stats(self, a, b, moments='mv'): + pA, pB = self.pdf(np.array([a, b]), a, b) + + def _truncnorm_stats_scalar(a, b, pA, pB): + ab = np.asarray([a, b]) + m1 = pA - pB + mu = m1 + # use xpx.apply_where to avoid nan (See detailed comment in _munp) + probs = np.asarray([pA, -pB]) + cond = probs != 0 + vals = xpx.apply_where(cond, (probs, ab), lambda x, y: x*y, + fill_value=0) + m2 = 1 + np.sum(vals) + vals = xpx.apply_where(cond, (probs, ab - mu), lambda x, y: x*y, + fill_value=0) + # mu2 = m2 - mu**2, but not as numerically stable as: + # mu2 = (a-mu)*pA - (b-mu)*pB + 1 + mu2 = 1 + np.sum(vals) + vals = xpx.apply_where(cond, (probs, ab), lambda x, y: x*y**2, + fill_value=0) + m3 = 2*m1 + np.sum(vals) + vals = xpx.apply_where(cond, (probs, ab), lambda x, y: x*y**3, + fill_value=0) + m4 = 3*m2 + np.sum(vals) + + mu3 = m3 + m1 * (-3*m2 + 2*m1**2) + g1 = mu3 / np.power(mu2, 1.5) + mu4 = m4 + m1*(-4*m3 + 3*m1*(2*m2 - m1**2)) + g2 = mu4 / mu2**2 - 3 + return mu, mu2, g1, g2 + + _truncnorm_stats = np.vectorize(_truncnorm_stats_scalar) + return _truncnorm_stats(a, b, pA, pB) + + +truncnorm = truncnorm_gen(name='truncnorm', momtype=1) +truncnorm._support = ('a', 'b') + + +class truncpareto_gen(rv_continuous): + r"""An upper truncated Pareto continuous random variable. + + %(before_notes)s + + See Also + -------- + pareto : Pareto distribution + + Notes + ----- + The probability density function for `truncpareto` is: + + .. math:: + + f(x, b, c) = \frac{b}{1 - c^{-b}} \frac{1}{x^{b+1}} + + for :math:`b \neq 0`, :math:`c > 1` and :math:`1 \le x \le c`. + + `truncpareto` takes `b` and `c` as shape parameters for :math:`b` and + :math:`c`. + + Notice that the upper truncation value :math:`c` is defined in + standardized form so that random values of an unscaled, unshifted variable + are within the range ``[1, c]``. + If ``u_r`` is the upper bound to a scaled and/or shifted variable, + then ``c = (u_r - loc) / scale``. In other words, the support of the + distribution becomes ``(scale + loc) <= x <= (c*scale + loc)`` when + `scale` and/or `loc` are provided. + + The ``fit`` method assumes that :math:`b` is positive; it does not produce + good results when the data is more consistent with negative :math:`b`. + + `truncpareto` can also be used to model a general power law distribution + with PDF: + + .. math:: + + f(x; a, l, h) = \frac{a}{h^a - l^a} x^{a-1} + + for :math:`a \neq 0` and :math:`0 < l < x < h`. Suppose :math:`a`, + :math:`l`, and :math:`h` are represented in code as ``a``, ``l``, and + ``h``, respectively. In this case, use `truncpareto` with parameters + ``b = -a``, ``c = h / l``, ``scale = l``, and ``loc = 0``. + + %(after_notes)s + + References + ---------- + .. [1] Burroughs, S. M., and Tebbens S. F. + "Upper-truncated power laws in natural systems." + Pure and Applied Geophysics 158.4 (2001): 741-757. + + %(example)s + + """ + + def _shape_info(self): + ib = _ShapeInfo("b", False, (-np.inf, np.inf), (False, False)) + ic = _ShapeInfo("c", False, (1.0, np.inf), (False, False)) + return [ib, ic] + + def _argcheck(self, b, c): + return (b != 0.) & (c > 1.) + + def _get_support(self, b, c): + return self.a, c + + def _pdf(self, x, b, c): + # here and below, avoid int to negative int power + x, b, c = xp_promote(x, b, c, force_floating=True, xp=np) + return b * x**-(b+1) / (1 - 1/c**b) + + def _logpdf(self, x, b, c): + x, b, c = xp_promote(x, b, c, force_floating=True, xp=np) + return xpx.apply_where(b > 0, (x, b, c), self._logpdf_pos_b, super()._logpdf) + + def _logpdf_pos_b(self, x, b, c): + return np.log(b) - np.log(-np.expm1(-b*np.log(c))) - (b+1)*np.log(x) + + def _cdf(self, x, b, c): + x, b, c = xp_promote(x, b, c, force_floating=True, xp=np) + return (1 - x**-b) / (1 - 1/c**b) + + def _logcdf(self, x, b, c): + x, b, c = xp_promote(x, b, c, force_floating=True, xp=np) + return xpx.apply_where(b > 0, (x, b, c), self._logcdf_pos_b, super()._logcdf) + + def _logcdf_pos_b(self, x, b, c): + return np.log1p(-x**-b) - np.log1p(-1/c**b) + + def _ppf(self, q, b, c): + q, b, c = xp_promote(q, b, c, force_floating=True, xp=np) + return pow(1 - (1 - 1/c**b)*q, -1/b) + + def _sf(self, x, b, c): + x, b, c = xp_promote(x, b, c, force_floating=True, xp=np) + return (x**-b - 1/c**b) / (1 - 1/c**b) + + def _logsf(self, x, b, c): + x, b, c = xp_promote(x, b, c, force_floating=True, xp=np) + return xpx.apply_where(b > 0, (x, b, c), self._logsf_pos_b, super()._logsf) + + def _logsf_pos_b(self, x, b, c): + return np.log(x**-b - 1/c**b) - np.log1p(-1/c**b) + + def _isf(self, q, b, c): + q, b, c = xp_promote(q, b, c, force_floating=True, xp=np) + return pow(1/c**b + (1 - 1/c**b)*q, -1/b) + + def _entropy(self, b, c): + return -(np.log(b/(1 - 1/c**b)) + + (b+1)*(np.log(c)/(c**b - 1) - 1/b)) + + def _munp(self, n, b, c): + n, b, c = xp_promote(n, b, c, force_floating=True, xp=np) + if (n == b).all(): + return b*np.log(c) / (1 - 1/c**b) + else: + return b / (b-n) * (c**b - c**n) / (c**b - 1) + + def _fitstart(self, data): + if isinstance(data, CensoredData): + data = data._uncensor() + b, loc, scale = pareto.fit(data) + c = (max(data) - loc)/scale + return b, c, loc, scale + + @_call_super_mom + @inherit_docstring_from(rv_continuous) + def fit(self, data, *args, **kwds): + if kwds.pop("superfit", False): + return super().fit(data, *args, **kwds) + + def log_mean(x): + return np.mean(np.log(x)) + + def harm_mean(x): + return 1/np.mean(1/x) + + def get_b(c, loc, scale): + u = (data-loc)/scale + harm_m = harm_mean(u) + log_m = log_mean(u) + quot = (harm_m-1)/log_m + return (1 - (quot-1) / (quot - (1 - 1/c)*harm_m/np.log(c)))/log_m + + def get_c(loc, scale): + return (mx - loc)/scale + + def get_loc(fc, fscale): + if fscale: # (fscale and fc) or (fscale and not fc) + loc = mn - fscale + return loc + if fc: + loc = (fc*mn - mx)/(fc - 1) + return loc + + def get_scale(loc): + return mn - loc + + # Functions used for optimisation; partial derivatives of + # the Lagrangian, set to equal 0. + + def dL_dLoc(loc, b_=None): + # Partial derivative wrt location. + # Optimised upon when no parameters, or only b, are fixed. + scale = get_scale(loc) + c = get_c(loc, scale) + b = get_b(c, loc, scale) if b_ is None else b_ + harm_m = harm_mean((data - loc)/scale) + return 1 - (1 + (c - 1)/(c**(b+1) - c)) * (1 - 1/(b+1)) * harm_m + + def dL_dB(b, logc, logm): + # Partial derivative wrt b. + # Optimised upon whenever at least one parameter but b is fixed, + # and b is free. + return b - np.log1p(b*logc / (1 - b*logm)) / logc + + def fallback(data, *args, **kwargs): + # Should any issue arise, default to the general fit method. + return super(truncpareto_gen, self).fit(data, *args, **kwargs) + + parameters = _check_fit_input_parameters(self, data, args, kwds) + data, fb, fc, floc, fscale = parameters + mn, mx = data.min(), data.max() + mn_inf = np.nextafter(mn, -np.inf) + + if (fb is not None + and fc is not None + and floc is not None + and fscale is not None): + raise ValueError("All parameters fixed." + "There is nothing to optimize.") + elif fc is None and floc is None and fscale is None: + if fb is None: + def cond_b(loc): + # b is positive only if this function is positive + scale = get_scale(loc) + c = get_c(loc, scale) + harm_m = harm_mean((data - loc)/scale) + return (1 + 1/(c-1)) * np.log(c) / harm_m - 1 + + # This gives an upper bound on loc allowing for a positive b. + # Iteratively look for a bracket for root_scalar. + mn_inf = np.nextafter(mn, -np.inf) + rbrack = mn_inf + i = 0 + lbrack = rbrack - 1 + while ((lbrack > -np.inf) + and (cond_b(lbrack)*cond_b(rbrack) >= 0)): + i += 1 + lbrack = rbrack - np.power(2., i) + if not lbrack > -np.inf: + return fallback(data, *args, **kwds) + res = root_scalar(cond_b, bracket=(lbrack, rbrack)) + if not res.converged: + return fallback(data, *args, **kwds) + + # Determine the MLE for loc. + # Iteratively look for a bracket for root_scalar. + rbrack = res.root - 1e-3 # grad_loc is numerically ill-behaved + lbrack = rbrack - 1 + i = 0 + while ((lbrack > -np.inf) + and (dL_dLoc(lbrack)*dL_dLoc(rbrack) >= 0)): + i += 1 + lbrack = rbrack - np.power(2., i) + if not lbrack > -np.inf: + return fallback(data, *args, **kwds) + res = root_scalar(dL_dLoc, bracket=(lbrack, rbrack)) + if not res.converged: + return fallback(data, *args, **kwds) + loc = res.root + scale = get_scale(loc) + c = get_c(loc, scale) + b = get_b(c, loc, scale) + + std_data = (data - loc)/scale + # The expression of b relies on b being bounded above. + up_bound_b = min(1/log_mean(std_data), + 1/(harm_mean(std_data)-1)) + if not (b < up_bound_b): + return fallback(data, *args, **kwds) + else: + # We know b is positive (or a FitError will be triggered) + # so we let loc get close to min(data). + rbrack = mn_inf + lbrack = mn_inf - 1 + i = 0 + # Iteratively look for a bracket for root_scalar. + while (lbrack > -np.inf + and (dL_dLoc(lbrack, fb) + * dL_dLoc(rbrack, fb) >= 0)): + i += 1 + lbrack = rbrack - 2**i + if not lbrack > -np.inf: + return fallback(data, *args, **kwds) + res = root_scalar(dL_dLoc, (fb,), + bracket=(lbrack, rbrack)) + if not res.converged: + return fallback(data, *args, **kwds) + loc = res.root + scale = get_scale(loc) + c = get_c(loc, scale) + b = fb + else: + # At least one of the parameters determining the support is fixed; + # the others then have analytical expressions from the constraints. + # The completely determined case (fixed c, loc and scale) + # has to be checked for not overflowing the support. + # If not fixed, b has to be determined numerically. + loc = floc if floc is not None else get_loc(fc, fscale) + scale = fscale or get_scale(loc) + c = fc or get_c(loc, scale) + + # Unscaled, translated values should be positive when the location + # is fixed. If it is not the case, we end up with negative `scale` + # and `c`, which would trigger a FitError before exiting the + # method. + if floc is not None and data.min() - floc < 0: + raise FitDataError("truncpareto", lower=1, upper=c) + + # Standardised values should be within the distribution support + # when all parameters controlling it are fixed. If it not the case, + # `fc` is overridden by `c` determined from `floc` and `fscale` when + # raising the exception. + if fc and (floc is not None) and fscale: + if data.max() > fc*fscale + floc: + raise FitDataError("truncpareto", lower=1, + upper=get_c(loc, scale)) + + # The other constraints should be automatically satisfied + # from the analytical expressions of the parameters. + # If fc or fscale are respectively less than one or less than 0, + # a FitError is triggered before exiting the method. + + if fb is None: + std_data = (data - loc)/scale + logm = log_mean(std_data) + logc = np.log(c) + # Condition for a positive root to exist. + if not (2*logm < logc): + return fallback(data, *args, **kwds) + + lbrack = 1/logm + 1/(logm - logc) + rbrack = np.nextafter(1/logm, 0) + try: + res = root_scalar(dL_dB, (logc, logm), + bracket=(lbrack, rbrack)) + # we should then never get there + if not res.converged: + return fallback(data, *args, **kwds) + b = res.root + except ValueError: + b = rbrack + else: + b = fb + + # The distribution requires that `scale+loc <= data <= c*scale+loc`. + # To avoid numerical issues, some tuning may be necessary. + # We adjust `scale` to satisfy the lower bound, and we adjust + # `c` to satisfy the upper bound. + if not (scale+loc) < mn: + if fscale: + loc = np.nextafter(loc, -np.inf) + else: + scale = get_scale(loc) + scale = np.nextafter(scale, 0) + if not (c*scale+loc) > mx: + c = get_c(loc, scale) + c = np.nextafter(c, np.inf) + + if not (np.all(self._argcheck(b, c)) and (scale > 0)): + return fallback(data, *args, **kwds) + + params_override = b, c, loc, scale + if floc is None and fscale is None: + # Based on testing in gh-16782, the following methods are only + # reliable if either `floc` or `fscale` are provided. They are + # fast, though, so might as well see if they are better than the + # generic method. + params_super = fallback(data, *args, **kwds) + nllf_override = self.nnlf(params_override, data) + nllf_super = self.nnlf(params_super, data) + if nllf_super < nllf_override: + return params_super + + return params_override + + +truncpareto = truncpareto_gen(a=1.0, name='truncpareto') +truncpareto._support = (1.0, 'c') + + +class tukeylambda_gen(rv_continuous): + r"""A Tukey-Lamdba continuous random variable. + + %(before_notes)s + + Notes + ----- + A flexible distribution, able to represent and interpolate between the + following distributions: + + - Cauchy (:math:`lambda = -1`) + - logistic (:math:`lambda = 0`) + - approx Normal (:math:`lambda = 0.14`) + - uniform from -1 to 1 (:math:`lambda = 1`) + + `tukeylambda` takes a real number :math:`lambda` (denoted ``lam`` + in the implementation) as a shape parameter. + + %(after_notes)s + + %(example)s + + """ + _support_mask = rv_continuous._open_support_mask + + def _argcheck(self, lam): + return np.isfinite(lam) + + def _shape_info(self): + return [_ShapeInfo("lam", False, (-np.inf, np.inf), (False, False))] + + def _get_support(self, lam): + b = xpx.apply_where(lam > 0, lam, + lambda lam: 1/lam, + fill_value=np.inf) + return -b, b + + def _pdf(self, x, lam): + Fx = np.asarray(sc.tklmbda(x, lam)) + Px = Fx**(lam-1.0) + (np.asarray(1-Fx))**(lam-1.0) + with np.errstate(divide='ignore'): + Px = 1.0/np.asarray(Px) + return np.where((lam <= 0) | (abs(x) < 1.0/np.asarray(lam)), Px, 0.0) + + def _cdf(self, x, lam): + return sc.tklmbda(x, lam) + + def _ppf(self, q, lam): + return sc.boxcox(q, lam) - sc.boxcox1p(-q, lam) + + def _stats(self, lam): + return 0, _tlvar(lam), 0, _tlkurt(lam) + + def _entropy(self, lam): + @np.vectorize + def entropy_1d(lam): + def integ(p): + return np.log(pow(p, lam - 1) + pow(1 - p, lam - 1)) + return integrate.quad(integ, 0, 1)[0] + return entropy_1d(lam) + + +tukeylambda = tukeylambda_gen(name='tukeylambda') + + +class FitUniformFixedScaleDataError(FitDataError): + def __init__(self, ptp, fscale): + self.args = ( + "Invalid values in `data`. Maximum likelihood estimation with " + "the uniform distribution and fixed scale requires that " + f"np.ptp(data) <= fscale, but np.ptp(data) = {ptp} and " + f"fscale = {fscale}." + ) + + +class uniform_gen(rv_continuous): + r"""A uniform continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `uniform` is: + + .. math:: + + f(x) = \begin{cases} + 1 & \text{for } 0 \le x \le 1 \\ + 0 & \text{otherwise} + \end{cases} + + In the standard form, the distribution is uniform on ``[0, 1]``. Using + the parameters ``loc`` and ``scale``, one obtains the uniform distribution + on ``[loc, loc + scale]``. + + References + ---------- + .. [1] "Continuous uniform distribution", Wikipedia, + https://en.wikipedia.org/wiki/Continuous_uniform_distribution + + %(example)s + + """ + def _shape_info(self): + return [] + + def _rvs(self, size=None, random_state=None): + return random_state.uniform(0.0, 1.0, size) + + def _pdf(self, x): + return 1.0*(x == x) + + def _cdf(self, x): + return x + + def _ppf(self, q): + return q + + def _stats(self): + return 0.5, 1.0/12, 0, -1.2 + + def _entropy(self): + return 0.0 + + @_call_super_mom + def fit(self, data, *args, **kwds): + """ + Maximum likelihood estimate for the location and scale parameters. + + `uniform.fit` uses only the following parameters. Because exact + formulas are used, the parameters related to optimization that are + available in the `fit` method of other distributions are ignored + here. The only positional argument accepted is `data`. + + Parameters + ---------- + data : array_like + Data to use in calculating the maximum likelihood estimate. + floc : float, optional + Hold the location parameter fixed to the specified value. + fscale : float, optional + Hold the scale parameter fixed to the specified value. + + Returns + ------- + loc, scale : float + Maximum likelihood estimates for the location and scale. + + Notes + ----- + An error is raised if `floc` is given and any values in `data` are + less than `floc`, or if `fscale` is given and `fscale` is less + than ``data.max() - data.min()``. An error is also raised if both + `floc` and `fscale` are given. + + Examples + -------- + >>> import numpy as np + >>> from scipy.stats import uniform + + We'll fit the uniform distribution to `x`: + + >>> x = np.array([2, 2.5, 3.1, 9.5, 13.0]) + + For a uniform distribution MLE, the location is the minimum of the + data, and the scale is the maximum minus the minimum. + + >>> loc, scale = uniform.fit(x) + >>> loc + 2.0 + >>> scale + 11.0 + + If we know the data comes from a uniform distribution where the support + starts at 0, we can use ``floc=0``: + + >>> loc, scale = uniform.fit(x, floc=0) + >>> loc + 0.0 + >>> scale + 13.0 + + Alternatively, if we know the length of the support is 12, we can use + ``fscale=12``: + + >>> loc, scale = uniform.fit(x, fscale=12) + >>> loc + 1.5 + >>> scale + 12.0 + + In that last example, the support interval is [1.5, 13.5]. This + solution is not unique. For example, the distribution with ``loc=2`` + and ``scale=12`` has the same likelihood as the one above. When + `fscale` is given and it is larger than ``data.max() - data.min()``, + the parameters returned by the `fit` method center the support over + the interval ``[data.min(), data.max()]``. + + """ + if len(args) > 0: + raise TypeError("Too many arguments.") + + floc = kwds.pop('floc', None) + fscale = kwds.pop('fscale', None) + + _remove_optimizer_parameters(kwds) + + if floc is not None and fscale is not None: + # This check is for consistency with `rv_continuous.fit`. + raise ValueError("All parameters fixed. There is nothing to " + "optimize.") + + data = np.asarray(data) + + if not np.isfinite(data).all(): + raise ValueError("The data contains non-finite values.") + + # MLE for the uniform distribution + # -------------------------------- + # The PDF is + # + # f(x, loc, scale) = {1/scale for loc <= x <= loc + scale + # {0 otherwise} + # + # The likelihood function is + # L(x, loc, scale) = (1/scale)**n + # where n is len(x), assuming loc <= x <= loc + scale for all x. + # The log-likelihood is + # l(x, loc, scale) = -n*log(scale) + # The log-likelihood is maximized by making scale as small as possible, + # while keeping loc <= x <= loc + scale. So if neither loc nor scale + # are fixed, the log-likelihood is maximized by choosing + # loc = x.min() + # scale = np.ptp(x) + # If loc is fixed, it must be less than or equal to x.min(), and then + # the scale is + # scale = x.max() - loc + # If scale is fixed, it must not be less than np.ptp(x). If scale is + # greater than np.ptp(x), the solution is not unique. Note that the + # likelihood does not depend on loc, except for the requirement that + # loc <= x <= loc + scale. All choices of loc for which + # x.max() - scale <= loc <= x.min() + # have the same log-likelihood. In this case, we choose loc such that + # the support is centered over the interval [data.min(), data.max()]: + # loc = x.min() = 0.5*(scale - np.ptp(x)) + + if fscale is None: + # scale is not fixed. + if floc is None: + # loc is not fixed, scale is not fixed. + loc = data.min() + scale = np.ptp(data) + else: + # loc is fixed, scale is not fixed. + loc = floc + scale = data.max() - loc + if data.min() < loc: + raise FitDataError("uniform", lower=loc, upper=loc + scale) + else: + # loc is not fixed, scale is fixed. + ptp = np.ptp(data) + if ptp > fscale: + raise FitUniformFixedScaleDataError(ptp=ptp, fscale=fscale) + # If ptp < fscale, the ML estimate is not unique; see the comments + # above. We choose the distribution for which the support is + # centered over the interval [data.min(), data.max()]. + loc = data.min() - 0.5*(fscale - ptp) + scale = fscale + + # We expect the return values to be floating point, so ensure it + # by explicitly converting to float. + return float(loc), float(scale) + + +uniform = uniform_gen(a=0.0, b=1.0, name='uniform') + + +class vonmises_gen(rv_continuous): + r"""A Von Mises continuous random variable. + + %(before_notes)s + + See Also + -------- + scipy.stats.vonmises_fisher : Von-Mises Fisher distribution on a + hypersphere + + Notes + ----- + The probability density function for `vonmises` and `vonmises_line` is: + + .. math:: + + f(x, \kappa) = \frac{ \exp(\kappa \cos(x)) }{ 2 \pi I_0(\kappa) } + + for :math:`-\pi \le x \le \pi`, :math:`\kappa \ge 0`. :math:`I_0` is the + modified Bessel function of order zero (`scipy.special.i0`). + + `vonmises` is a circular distribution which does not restrict the + distribution to a fixed interval. Currently, there is no circular + distribution framework in SciPy. The ``cdf`` is implemented such that + ``cdf(x + 2*np.pi) == cdf(x) + 1``. + + `vonmises_line` is the same distribution, defined on :math:`[-\pi, \pi]` + on the real line. This is a regular (i.e. non-circular) distribution. + + Note about distribution parameters: `vonmises` and `vonmises_line` take + ``kappa`` as a shape parameter (concentration) and ``loc`` as the location + (circular mean). A ``scale`` parameter is accepted but does not have any + effect. + + References + ---------- + .. [1] Mardia, K. V. and Jupp, P. E. *Directional Statistics*. + John Wiley & Sons, 1999, p. 36. + .. [2] "von Mises distribution", Wikipedia, + https://en.wikipedia.org/wiki/Von_Mises_distribution + + Examples + -------- + Import the necessary modules. + + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from scipy.stats import vonmises + + Define distribution parameters. + + >>> loc = 0.5 * np.pi # circular mean + >>> kappa = 1 # concentration + + Compute the probability density at ``x=0`` via the ``pdf`` method. + + >>> vonmises.pdf(0, loc=loc, kappa=kappa) + 0.12570826359722018 + + Verify that the percentile function ``ppf`` inverts the cumulative + distribution function ``cdf`` up to floating point accuracy. + + >>> x = 1 + >>> cdf_value = vonmises.cdf(x, loc=loc, kappa=kappa) + >>> ppf_value = vonmises.ppf(cdf_value, loc=loc, kappa=kappa) + >>> x, cdf_value, ppf_value + (1, 0.31489339900904967, 1.0000000000000004) + + Draw 1000 random variates by calling the ``rvs`` method. + + >>> sample_size = 1000 + >>> sample = vonmises(loc=loc, kappa=kappa).rvs(sample_size) + + Plot the von Mises density on a Cartesian and polar grid to emphasize + that it is a circular distribution. + + >>> fig = plt.figure(figsize=(12, 6)) + >>> left = plt.subplot(121) + >>> right = plt.subplot(122, projection='polar') + >>> x = np.linspace(-np.pi, np.pi, 500) + >>> vonmises_pdf = vonmises.pdf(x, loc=loc, kappa=kappa) + >>> ticks = [0, 0.15, 0.3] + + The left image contains the Cartesian plot. + + >>> left.plot(x, vonmises_pdf) + >>> left.set_yticks(ticks) + >>> number_of_bins = int(np.sqrt(sample_size)) + >>> left.hist(sample, density=True, bins=number_of_bins) + >>> left.set_title("Cartesian plot") + >>> left.set_xlim(-np.pi, np.pi) + >>> left.grid(True) + + The right image contains the polar plot. + + >>> right.plot(x, vonmises_pdf, label="PDF") + >>> right.set_yticks(ticks) + >>> right.hist(sample, density=True, bins=number_of_bins, + ... label="Histogram") + >>> right.set_title("Polar plot") + >>> right.legend(bbox_to_anchor=(0.15, 1.06)) + + """ + def _shape_info(self): + return [_ShapeInfo("kappa", False, (0, np.inf), (True, False))] + + def _argcheck(self, kappa): + return kappa >= 0 + + def _rvs(self, kappa, size=None, random_state=None): + return random_state.vonmises(0.0, kappa, size=size) + + @inherit_docstring_from(rv_continuous) + def rvs(self, *args, **kwds): + rvs = super().rvs(*args, **kwds) + return np.mod(rvs + np.pi, 2*np.pi) - np.pi + + def _pdf(self, x, kappa): + # vonmises.pdf(x, kappa) = exp(kappa * cos(x)) / (2*pi*I[0](kappa)) + # = exp(kappa * (cos(x) - 1)) / + # (2*pi*exp(-kappa)*I[0](kappa)) + # = exp(kappa * cosm1(x)) / (2*pi*i0e(kappa)) + return np.exp(kappa*sc.cosm1(x)) / (2*np.pi*sc.i0e(kappa)) + + def _logpdf(self, x, kappa): + # vonmises.pdf(x, kappa) = exp(kappa * cosm1(x)) / (2*pi*i0e(kappa)) + return kappa * sc.cosm1(x) - np.log(2*np.pi) - np.log(sc.i0e(kappa)) + + def _cdf(self, x, kappa): + return _stats.von_mises_cdf(kappa, x) + + def _stats_skip(self, kappa): + return 0, None, 0, None + + def _entropy(self, kappa): + # vonmises.entropy(kappa) = -kappa * I[1](kappa) / I[0](kappa) + + # log(2 * np.pi * I[0](kappa)) + # = -kappa * I[1](kappa) * exp(-kappa) / + # (I[0](kappa) * exp(-kappa)) + + # log(2 * np.pi * + # I[0](kappa) * exp(-kappa) / exp(-kappa)) + # = -kappa * sc.i1e(kappa) / sc.i0e(kappa) + + # log(2 * np.pi * i0e(kappa)) + kappa + return (-kappa * sc.i1e(kappa) / sc.i0e(kappa) + + np.log(2 * np.pi * sc.i0e(kappa)) + kappa) + + @extend_notes_in_docstring(rv_continuous, notes="""\ + The default limits of integration are endpoints of the interval + of width ``2*pi`` centered at `loc` (e.g. ``[-pi, pi]`` when + ``loc=0``).\n\n""") + def expect(self, func=None, args=(), loc=0, scale=1, lb=None, ub=None, + conditional=False, **kwds): + _a, _b = -np.pi, np.pi + + if lb is None: + lb = loc + _a + if ub is None: + ub = loc + _b + + return super().expect(func, args, loc, + scale, lb, ub, conditional, **kwds) + + @_call_super_mom + @extend_notes_in_docstring(rv_continuous, notes="""\ + Fit data is assumed to represent angles and will be wrapped onto the + unit circle. `f0` and `fscale` are ignored; the returned shape is + always the maximum likelihood estimate and the scale is always + 1. Initial guesses are ignored.\n\n""") + def fit(self, data, *args, **kwds): + if kwds.pop('superfit', False): + return super().fit(data, *args, **kwds) + + data, fshape, floc, fscale = _check_fit_input_parameters(self, data, + args, kwds) + if self.a == -np.pi: + # vonmises line case, here the default fit method will be used + return super().fit(data, *args, **kwds) + + # wrap data to interval [0, 2*pi] + data = np.mod(data, 2 * np.pi) + + def find_mu(data): + return stats.circmean(data) + + def find_kappa(data, loc): + # Usually, sources list the following as the equation to solve for + # the MLE of the shape parameter: + # r = I[1](kappa)/I[0](kappa), where r = mean resultant length + # This is valid when the location is the MLE of location. + # More generally, when the location may be fixed at an arbitrary + # value, r should be defined as follows: + r = np.sum(np.cos(loc - data))/len(data) + # See gh-18128 for more information. + + # The function r[0](kappa) := I[1](kappa)/I[0](kappa) is monotonic + # increasing from r[0](0) = 0 to r[0](+inf) = 1. The partial + # derivative of the log likelihood function with respect to kappa + # is monotonic decreasing in kappa. + if r == 1: + # All observations are (almost) equal to the mean. Return + # some large kappa such that r[0](kappa) = 1.0 numerically. + return 1e16 + elif r > 0: + def solve_for_kappa(kappa): + return sc.i1e(kappa)/sc.i0e(kappa) - r + + # The bounds of the root of r[0](kappa) = r are derived from + # selected bounds of r[0](x) given in [1, Eq. 11 & 16]. See + # gh-20102 for details. + # + # [1] Amos, D. E. (1973). Computation of Modified Bessel + # Functions and Their Ratios. Mathematics of Computation, + # 28(125): 239-251. + lower_bound = r/(1-r)/(1+r) + upper_bound = 2*lower_bound + + # The bounds are violated numerically for certain values of r, + # where solve_for_kappa evaluated at the bounds have the same + # sign. This indicates numerical imprecision of i1e()/i0e(). + # Return the violated bound in this case as it's more accurate. + if solve_for_kappa(lower_bound) >= 0: + return lower_bound + elif solve_for_kappa(upper_bound) <= 0: + return upper_bound + else: + root_res = root_scalar(solve_for_kappa, method="brentq", + bracket=(lower_bound, upper_bound)) + return root_res.root + else: + # if the provided floc is very far from the circular mean, + # the mean resultant length r can become negative. + # In that case, the equation + # I[1](kappa)/I[0](kappa) = r does not have a solution. + # The maximum likelihood kappa is then 0 which practically + # results in the uniform distribution on the circle. As + # vonmises is defined for kappa > 0, return instead the + # smallest floating point value. + # See gh-18190 for more information + return np.finfo(float).tiny + + # location likelihood equation has a solution independent of kappa + loc = floc if floc is not None else find_mu(data) + # shape likelihood equation depends on location + shape = fshape if fshape is not None else find_kappa(data, loc) + + loc = np.mod(loc + np.pi, 2 * np.pi) - np.pi # ensure in [-pi, pi] + return shape, loc, 1 # scale is not handled + + +vonmises = vonmises_gen(name='vonmises') +vonmises_line = vonmises_gen(a=-np.pi, b=np.pi, name='vonmises_line') + + +class wald_gen(invgauss_gen): + r"""A Wald continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `wald` is: + + .. math:: + + f(x) = \frac{1}{\sqrt{2\pi x^3}} \exp(- \frac{ (x-1)^2 }{ 2x }) + + for :math:`x >= 0`. + + `wald` is a special case of `invgauss` with ``mu=1``. + + %(after_notes)s + + %(example)s + """ + _support_mask = rv_continuous._open_support_mask + + def _shape_info(self): + return [] + + def _rvs(self, size=None, random_state=None): + return random_state.wald(1.0, 1.0, size=size) + + def _pdf(self, x): + # wald.pdf(x) = 1/sqrt(2*pi*x**3) * exp(-(x-1)**2/(2*x)) + return invgauss._pdf(x, 1.0) + + def _cdf(self, x): + return invgauss._cdf(x, 1.0) + + def _sf(self, x): + return invgauss._sf(x, 1.0) + + def _ppf(self, x): + return invgauss._ppf(x, 1.0) + + def _isf(self, x): + return invgauss._isf(x, 1.0) + + def _logpdf(self, x): + return invgauss._logpdf(x, 1.0) + + def _logcdf(self, x): + return invgauss._logcdf(x, 1.0) + + def _logsf(self, x): + return invgauss._logsf(x, 1.0) + + def _stats(self): + return 1.0, 1.0, 3.0, 15.0 + + def _entropy(self): + return invgauss._entropy(1.0) + + +wald = wald_gen(a=0.0, name="wald") + + +class wrapcauchy_gen(rv_continuous): + r"""A wrapped Cauchy continuous random variable. + + %(before_notes)s + + Notes + ----- + The probability density function for `wrapcauchy` is: + + .. math:: + + f(x, c) = \frac{1-c^2}{2\pi (1+c^2 - 2c \cos(x))} + + for :math:`0 \le x \le 2\pi`, :math:`0 < c < 1`. + + `wrapcauchy` takes ``c`` as a shape parameter for :math:`c`. + + %(after_notes)s + + %(example)s + + """ + def _argcheck(self, c): + return (c > 0) & (c < 1) + + def _shape_info(self): + return [_ShapeInfo("c", False, (0, 1), (False, False))] + + def _pdf(self, x, c): + # wrapcauchy.pdf(x, c) = (1-c**2) / (2*pi*(1+c**2-2*c*cos(x))) + return (1.0-c*c)/(2*np.pi*(1+c*c-2*c*np.cos(x))) + + def _cdf(self, x, c): + + def f1(x, cr): + # CDF for 0 <= x < pi + return 1/np.pi * np.arctan(cr*np.tan(x/2)) + + def f2(x, cr): + # CDF for pi <= x <= 2*pi + return 1 - 1/np.pi * np.arctan(cr*np.tan((2*np.pi - x)/2)) + + cr = (1 + c)/(1 - c) + return xpx.apply_where(x < np.pi, (x, cr), f1, f2) + + def _ppf(self, q, c): + val = (1.0-c)/(1.0+c) + rcq = 2*np.arctan(val*np.tan(np.pi*q)) + rcmq = 2*np.pi-2*np.arctan(val*np.tan(np.pi*(1-q))) + return np.where(q < 1.0/2, rcq, rcmq) + + def _entropy(self, c): + return np.log(2*np.pi*(1-c*c)) + + def _fitstart(self, data): + # Use 0.5 as the initial guess of the shape parameter. + # For the location and scale, use the minimum and + # peak-to-peak/(2*pi), respectively. + if isinstance(data, CensoredData): + data = data._uncensor() + return 0.5, np.min(data), np.ptp(data)/(2*np.pi) + + @inherit_docstring_from(rv_continuous) + def rvs(self, *args, **kwds): + rvs = super().rvs(*args, **kwds) + return np.mod(rvs, 2*np.pi) + +wrapcauchy = wrapcauchy_gen(a=0.0, b=2*np.pi, name='wrapcauchy') + + +class gennorm_gen(rv_continuous): + r"""A (symmetric) generalized normal continuous random variable. + + %(before_notes)s + + See Also + -------- + laplace : Laplace distribution + norm : normal distribution + + Notes + ----- + The (symmetric) generalized normal distribution is also known as the + Subbotin distribution, exponential power distribution, and generalized + error distribution [1]_. + + The probability density function for `gennorm` is [1]_: + + .. math:: + + f(x, \beta) = \frac{\beta}{2 \Gamma(1/\beta)} \exp(-|x|^\beta), + + where :math:`x` is a real number, :math:`\beta > 0` and + :math:`\Gamma` is the gamma function (`scipy.special.gamma`). + + `gennorm` takes ``beta`` as a shape parameter for :math:`\beta`. + For :math:`\beta = 1`, it is identical to a Laplace distribution. + For :math:`\beta = 2`, it is identical to a normal distribution + (with ``scale=1/sqrt(2)``). + + References + ---------- + + .. [1] "Generalized normal distribution, Version 1", + https://en.wikipedia.org/wiki/Generalized_normal_distribution#Version_1 + + .. [2] Nardon, Martina, and Paolo Pianca. "Simulation techniques for + generalized Gaussian densities." Journal of Statistical + Computation and Simulation 79.11 (2009): 1317-1329 + + .. [3] Wicklin, Rick. "Simulate data from a generalized Gaussian + distribution" in The DO Loop blog, September 21, 2016, + https://blogs.sas.com/content/iml/2016/09/21/simulate-generalized-gaussian-sas.html + + %(example)s + + """ + def _shape_info(self): + return [_ShapeInfo("beta", False, (0, np.inf), (False, False))] + + def _pdf(self, x, beta): + return np.exp(self._logpdf(x, beta)) + + def _logpdf(self, x, beta): + return np.log(0.5*beta) - sc.gammaln(1.0/beta) - abs(x)**beta + + def _cdf(self, x, beta): + c = 0.5 * np.sign(x) + # evaluating (.5 + c) first prevents numerical cancellation + return (0.5 + c) - c * sc.gammaincc(1.0/beta, abs(x)**beta) + + def _ppf(self, x, beta): + c = np.sign(x - 0.5) + # evaluating (1. + c) first prevents numerical cancellation + return c * sc.gammainccinv(1.0/beta, (1.0 + c) - 2.0*c*x)**(1.0/beta) + + def _sf(self, x, beta): + return self._cdf(-x, beta) + + def _isf(self, x, beta): + return -self._ppf(x, beta) + + def _munp(self, n, beta): + if n == 0: + return 1. + if n % 2 == 0: + c1, cn = sc.gammaln([1.0/beta, (n + 1.0)/beta]) + return np.exp(cn - c1) + else: + return 0. + + def _stats(self, beta): + c1, c3, c5 = sc.gammaln([1.0/beta, 3.0/beta, 5.0/beta]) + return 0., np.exp(c3 - c1), 0., np.exp(c5 + c1 - 2.0*c3) - 3. + + def _entropy(self, beta): + return 1. / beta - np.log(.5 * beta) + sc.gammaln(1. / beta) + + def _rvs(self, beta, size=None, random_state=None): + # see [2]_ for the algorithm + # see [3]_ for reference implementation in SAS + z = random_state.gamma(1/beta, size=size) + y = z ** (1/beta) + # convert y to array to ensure masking support + y = np.asarray(y) + mask = random_state.random(size=y.shape) < 0.5 + y[mask] = -y[mask] + return y + + +gennorm = gennorm_gen(name='gennorm') + + +class halfgennorm_gen(rv_continuous): + r"""The upper half of a generalized normal continuous random variable. + + %(before_notes)s + + See Also + -------- + gengamma : generalized gamma distribution + gennorm : generalized normal distribution + expon : exponential distribution + halfnorm : half normal distribution + + Notes + ----- + The probability density function for `halfgennorm` is: + + .. math:: + + f(x, \beta) = \frac{\beta}{\Gamma(1/\beta)} \exp(-|x|^\beta) + + for :math:`x, \beta > 0`. :math:`\Gamma` is the gamma function + (`scipy.special.gamma`). + + `halfgennorm` takes ``beta`` as a shape parameter for :math:`\beta`. + For :math:`\beta = 1`, it is identical to an exponential distribution. + For :math:`\beta = 2`, it is identical to a half normal distribution + (with ``scale=1/sqrt(2)``). + + `halfgennorm` is the upper half of a generalized normal continuous + random variable [1]_. + + `halfgennorm` is a special case of the generalized gamma distribution [2]_, + which is implemented in SciPy as `gengamma`: + ``halfgennorm(beta) = gengamma(a=1/beta, c=beta)``. + + References + ---------- + + .. [1] "Generalized normal distribution, Version 1", Wikipedia, + https://en.wikipedia.org/wiki/Generalized_normal_distribution#Version_1 + .. [2] "Generalized gamma distribution", Wikipedia, + https://en.wikipedia.org/wiki/Generalized_gamma_distribution + + %(example)s + + """ + def _shape_info(self): + return [_ShapeInfo("beta", False, (0, np.inf), (False, False))] + + def _pdf(self, x, beta): + # beta + # halfgennorm.pdf(x, beta) = ------------- exp(-|x|**beta) + # gamma(1/beta) + return np.exp(self._logpdf(x, beta)) + + def _logpdf(self, x, beta): + return np.log(beta) - sc.gammaln(1.0/beta) - x**beta + + def _cdf(self, x, beta): + return sc.gammainc(1.0/beta, x**beta) + + def _ppf(self, x, beta): + return sc.gammaincinv(1.0/beta, x)**(1.0/beta) + + def _sf(self, x, beta): + return sc.gammaincc(1.0/beta, x**beta) + + def _isf(self, x, beta): + return sc.gammainccinv(1.0/beta, x)**(1.0/beta) + + def _munp(self, n, beta): + return sc.poch(1/beta, n/beta) + + def _entropy(self, beta): + return 1.0/beta - np.log(beta) + sc.gammaln(1.0/beta) + + +halfgennorm = halfgennorm_gen(a=0, name='halfgennorm') + + +class crystalball_gen(rv_continuous): + r""" + Crystalball distribution. + + %(before_notes)s + + Notes + ----- + The probability density function for `crystalball` is: + + .. math:: + + f(x, \beta, m) = \begin{cases} + N \exp(-x^2 / 2), &\text{for } x > -\beta\\ + N A (B - x)^{-m} &\text{for } x \le -\beta + \end{cases} + + where :math:`A = (m / |\beta|)^m \exp(-\beta^2 / 2)`, + :math:`B = m/|\beta| - |\beta|` and :math:`N` is a normalisation constant. + + `crystalball` takes :math:`\beta > 0` and :math:`m > 1` as shape + parameters. :math:`\beta` defines the point where the pdf changes + from a power-law to a Gaussian distribution. :math:`m` is the power + of the power-law tail. + + %(after_notes)s + + .. versionadded:: 0.19.0 + + References + ---------- + .. [1] "Crystal Ball Function", + https://en.wikipedia.org/wiki/Crystal_Ball_function + + %(example)s + """ + def _argcheck(self, beta, m): + """ + Shape parameter bounds are m > 1 and beta > 0. + """ + return (m > 1) & (beta > 0) + + def _shape_info(self): + ibeta = _ShapeInfo("beta", False, (0, np.inf), (False, False)) + im = _ShapeInfo("m", False, (1, np.inf), (False, False)) + return [ibeta, im] + + def _fitstart(self, data): + # Arbitrary, but the default m=1 is not valid + return super()._fitstart(data, args=(1, 1.5)) + + def _pdf(self, x, beta, m): + """ + Return PDF of the crystalball function. + + -- + | exp(-x**2 / 2), for x > -beta + crystalball.pdf(x, beta, m) = N * | + | A * (B - x)**(-m), for x <= -beta + -- + """ + N = 1.0 / (m/beta / (m-1) * np.exp(-beta**2 / 2.0) + + _norm_pdf_C * _norm_cdf(beta)) + + def rhs(x, beta, m): + return np.exp(-x**2 / 2) + + def lhs(x, beta, m): + return ((m/beta)**m * np.exp(-beta**2 / 2.0) * + (m/beta - beta - x)**(-m)) + + return N * xpx.apply_where(x > -beta, (x, beta, m), rhs, lhs) + + def _logpdf(self, x, beta, m): + """ + Return the log of the PDF of the crystalball function. + """ + N = 1.0 / (m/beta / (m-1) * np.exp(-beta**2 / 2.0) + + _norm_pdf_C * _norm_cdf(beta)) + + def rhs(x, beta, m): + return -x**2/2 + + def lhs(x, beta, m): + return m*np.log(m/beta) - beta**2/2 - m*np.log(m/beta - beta - x) + + return np.log(N) + xpx.apply_where(x > -beta, (x, beta, m), rhs, lhs) + + def _cdf(self, x, beta, m): + """ + Return CDF of the crystalball function + """ + N = 1.0 / (m/beta / (m-1) * np.exp(-beta**2 / 2.0) + + _norm_pdf_C * _norm_cdf(beta)) + + def rhs(x, beta, m): + return ((m/beta) * np.exp(-beta**2 / 2.0) / (m-1) + + _norm_pdf_C * (_norm_cdf(x) - _norm_cdf(-beta))) + + def lhs(x, beta, m): + return ((m/beta)**m * np.exp(-beta**2 / 2.0) * + (m/beta - beta - x)**(-m+1) / (m-1)) + + return N * xpx.apply_where(x > -beta, (x, beta, m), rhs, lhs) + + def _sf(self, x, beta, m): + """ + Survival function of the crystalball distribution. + """ + + def rhs(x, beta, m): + # M is the same as 1/N used elsewhere. + M = m/beta/(m - 1)*np.exp(-beta**2/2) + _norm_pdf_C*_norm_cdf(beta) + return _norm_pdf_C*_norm_sf(x)/M + + def lhs(x, beta, m): + # Default behavior is OK in the left tail of the SF. + return 1 - self._cdf(x, beta, m) + + return xpx.apply_where(x > -beta, (x, beta, m), rhs, lhs) + + def _ppf(self, p, beta, m): + N = 1.0 / (m/beta / (m-1) * np.exp(-beta**2 / 2.0) + + _norm_pdf_C * _norm_cdf(beta)) + pbeta = N * (m/beta) * np.exp(-beta**2/2) / (m - 1) + + def ppf_less(p, beta, m): + eb2 = np.exp(-beta**2/2) + C = (m/beta) * eb2 / (m-1) + N = 1/(C + _norm_pdf_C * _norm_cdf(beta)) + return (m/beta - beta - + ((m - 1)*(m/beta)**(-m)/eb2*p/N)**(1/(1-m))) + + def ppf_greater(p, beta, m): + eb2 = np.exp(-beta**2/2) + C = (m/beta) * eb2 / (m-1) + N = 1/(C + _norm_pdf_C * _norm_cdf(beta)) + return _norm_ppf(_norm_cdf(-beta) + (1/_norm_pdf_C)*(p/N - C)) + + return xpx.apply_where(p < pbeta, (p, beta, m), ppf_less, ppf_greater) + + def _munp(self, n, beta, m): + """ + Returns the n-th non-central moment of the crystalball function. + """ + N = 1.0 / (m/beta / (m-1) * np.exp(-beta**2 / 2.0) + + _norm_pdf_C * _norm_cdf(beta)) + + def n_th_moment(n, beta, m): + """ + Returns n-th moment. Defined only if n+1 < m + Function cannot broadcast due to the loop over n + """ + A = (m/beta)**m * np.exp(-beta**2 / 2.0) + B = m/beta - beta + rhs = (2**((n-1)/2.0) * sc.gamma((n+1)/2) * + (1.0 + (-1)**n * sc.gammainc((n+1)/2, beta**2 / 2))) + lhs = np.zeros(rhs.shape) + for k in range(int(n) + 1): + lhs += (sc.binom(n, k) * B**(n-k) * (-1)**k / (m - k - 1) * + (m/beta)**(-m + k + 1)) + return A * lhs + rhs + + return N * xpx.apply_where(n + 1 < m, (n, beta, m), + np.vectorize(n_th_moment, otypes=[np.float64]), + fill_value=np.inf) + + +crystalball = crystalball_gen(name='crystalball', longname="A Crystalball Function") + + +def _argus_phi(chi): + """ + Utility function for the argus distribution used in the pdf, sf and + moment calculation. + Note that for all x > 0: + gammainc(1.5, x**2/2) = 2 * (_norm_cdf(x) - x * _norm_pdf(x) - 0.5). + This can be verified directly by noting that the cdf of Gamma(1.5) can + be written as erf(sqrt(x)) - 2*sqrt(x)*exp(-x)/sqrt(Pi). + We use gammainc instead of the usual definition because it is more precise + for small chi. + """ + return sc.gammainc(1.5, chi**2/2) / 2 + + +class argus_gen(rv_continuous): + r""" + Argus distribution. + + %(before_notes)s + + Notes + ----- + The probability density function for `argus` is: + + .. math:: + + f(x, \chi) = \frac{\chi^3}{\sqrt{2\pi} \Psi(\chi)} x \sqrt{1-x^2} + \exp(-\chi^2 (1 - x^2)/2) + + for :math:`0 < x < 1` and :math:`\chi > 0`, where + + .. math:: + + \Psi(\chi) = \Phi(\chi) - \chi \phi(\chi) - 1/2 + + with :math:`\Phi` and :math:`\phi` being the CDF and PDF of a standard + normal distribution, respectively. + + `argus` takes :math:`\chi` as shape a parameter. Details about sampling + from the ARGUS distribution can be found in [2]_. + + %(after_notes)s + + References + ---------- + .. [1] "ARGUS distribution", + https://en.wikipedia.org/wiki/ARGUS_distribution + .. [2] Christoph Baumgarten "Random variate generation by fast numerical + inversion in the varying parameter case." Research in Statistics, + vol. 1, 2023. :doi:`10.1080/27684520.2023.2279060` + + .. versionadded:: 0.19.0 + + %(example)s + """ + def _shape_info(self): + return [_ShapeInfo("chi", False, (0, np.inf), (False, False))] + + def _logpdf(self, x, chi): + # for x = 0 or 1, logpdf returns -np.inf + with np.errstate(divide='ignore'): + y = 1.0 - x*x + A = 3*np.log(chi) - _norm_pdf_logC - np.log(_argus_phi(chi)) + return A + np.log(x) + 0.5*np.log1p(-x*x) - chi**2 * y / 2 + + def _pdf(self, x, chi): + return np.exp(self._logpdf(x, chi)) + + def _cdf(self, x, chi): + return 1.0 - self._sf(x, chi) + + def _sf(self, x, chi): + return _argus_phi(chi * np.sqrt((1 - x)*(1 + x))) / _argus_phi(chi) + + def _rvs(self, chi, size=None, random_state=None): + chi = np.asarray(chi) + if chi.size == 1: + out = self._rvs_scalar(chi, numsamples=size, + random_state=random_state) + else: + shp, bc = _check_shape(chi.shape, size) + numsamples = int(np.prod(shp)) + out = np.empty(size) + it = np.nditer([chi], + flags=['multi_index'], + op_flags=[['readonly']]) + while not it.finished: + idx = tuple((it.multi_index[j] if not bc[j] else slice(None)) + for j in range(-len(size), 0)) + r = self._rvs_scalar(it[0], numsamples=numsamples, + random_state=random_state) + out[idx] = r.reshape(shp) + it.iternext() + + if size == (): + out = out[()] + return out + + def _rvs_scalar(self, chi, numsamples=None, random_state=None): + # if chi <= 1.8: + # use rejection method, see Devroye: + # Non-Uniform Random Variate Generation, 1986, section II.3.2. + # write: PDF f(x) = c * g(x) * h(x), where + # h is [0,1]-valued and g is a density + # we use two ways to write f + # + # Case 1: + # write g(x) = 3*x*sqrt(1-x**2), h(x) = exp(-chi**2 (1-x**2) / 2) + # If X has a distribution with density g its ppf G_inv is given by: + # G_inv(u) = np.sqrt(1 - u**(2/3)) + # + # Case 2: + # g(x) = chi**2 * x * exp(-chi**2 * (1-x**2)/2) / (1 - exp(-chi**2 /2)) + # h(x) = sqrt(1 - x**2), 0 <= x <= 1 + # one can show that + # G_inv(u) = np.sqrt(2*np.log(u*(np.exp(chi**2/2)-1)+1))/chi + # = np.sqrt(1 + 2*np.log(np.exp(-chi**2/2)*(1-u)+u)/chi**2) + # the latter expression is used for precision with small chi + # + # In both cases, the inverse cdf of g can be written analytically, and + # we can apply the rejection method: + # + # REPEAT + # Generate U uniformly distributed on [0, 1] + # Generate X with density g (e.g. via inverse transform sampling: + # X = G_inv(V) with V uniformly distributed on [0, 1]) + # UNTIL X <= h(X) + # RETURN X + # + # We use case 1 for chi <= 0.5 as it maintains precision for small chi + # and case 2 for 0.5 < chi <= 1.8 due to its speed for moderate chi. + # + # if chi > 1.8: + # use relation to the Gamma distribution: if X is ARGUS with parameter + # chi), then Y = chi**2 * (1 - X**2) / 2 has density proportional to + # sqrt(u) * exp(-u) on [0, chi**2 / 2], i.e. a Gamma(3/2) distribution + # conditioned on [0, chi**2 / 2]). Therefore, to sample X from the + # ARGUS distribution, we sample Y from the gamma distribution, keeping + # only samples on [0, chi**2 / 2], and apply the inverse + # transformation X = (1 - 2*Y/chi**2)**(1/2). Since we only + # look at chi > 1.8, gamma(1.5).cdf(chi**2/2) is large enough such + # Y falls in the interval [0, chi**2 / 2] with a high probability: + # stats.gamma(1.5).cdf(1.8**2/2) = 0.644... + # + # The points to switch between the different methods are determined + # by a comparison of the runtime of the different methods. However, + # the runtime is platform-dependent. The implemented values should + # ensure a good overall performance and are supported by an analysis + # of the rejection constants of different methods. + + size1d = tuple(np.atleast_1d(numsamples)) + N = int(np.prod(size1d)) + x = np.zeros(N) + simulated = 0 + chi2 = chi * chi + if chi <= 0.5: + d = -chi2 / 2 + while simulated < N: + k = N - simulated + u = random_state.uniform(size=k) + v = random_state.uniform(size=k) + z = v**(2/3) + # acceptance condition: u <= h(G_inv(v)). This simplifies to + accept = (np.log(u) <= d * z) + num_accept = np.sum(accept) + if num_accept > 0: + # we still need to transform z=v**(2/3) to X = G_inv(v) + rvs = np.sqrt(1 - z[accept]) + x[simulated:(simulated + num_accept)] = rvs + simulated += num_accept + elif chi <= 1.8: + echi = np.exp(-chi2 / 2) + while simulated < N: + k = N - simulated + u = random_state.uniform(size=k) + v = random_state.uniform(size=k) + z = 2 * np.log(echi * (1 - v) + v) / chi2 + # as in case one, simplify u <= h(G_inv(v)) and then transform + # z to the target distribution X = G_inv(v) + accept = (u**2 + z <= 0) + num_accept = np.sum(accept) + if num_accept > 0: + rvs = np.sqrt(1 + z[accept]) + x[simulated:(simulated + num_accept)] = rvs + simulated += num_accept + else: + # conditional Gamma for chi > 1.8 + while simulated < N: + k = N - simulated + g = random_state.standard_gamma(1.5, size=k) + accept = (g <= chi2 / 2) + num_accept = np.sum(accept) + if num_accept > 0: + x[simulated:(simulated + num_accept)] = g[accept] + simulated += num_accept + x = np.sqrt(1 - 2 * x / chi2) + + return np.reshape(x, size1d) + + def _stats(self, chi): + # need to ensure that dtype is float + # otherwise the mask below does not work for integers + chi = np.asarray(chi, dtype=float) + phi = _argus_phi(chi) + m = np.sqrt(np.pi/8) * chi * sc.ive(1, chi**2/4) / phi + # compute second moment, use Taylor expansion for small chi (<= 0.1) + mu2 = np.empty_like(chi) + mask = chi > 0.1 + c = chi[mask] + mu2[mask] = 1 - 3 / c**2 + c * _norm_pdf(c) / phi[mask] + c = chi[~mask] + coef = [-358/65690625, 0, -94/1010625, 0, 2/2625, 0, 6/175, 0, 0.4] + mu2[~mask] = np.polyval(coef, c) + return m, mu2 - m**2, None, None + + +argus = argus_gen(name='argus', longname="An Argus Function", a=0.0, b=1.0) + + +class rv_histogram(rv_continuous): + """ + Generates a distribution given by a histogram. + This is useful to generate a template distribution from a binned + datasample. + + As a subclass of the `rv_continuous` class, `rv_histogram` inherits from it + a collection of generic methods (see `rv_continuous` for the full list), + and implements them based on the properties of the provided binned + datasample. + + Parameters + ---------- + histogram : tuple of array_like + Tuple containing two array_like objects. + The first containing the content of n bins, + the second containing the (n+1) bin boundaries. + In particular, the return value of `numpy.histogram` is accepted. + + density : bool, optional + If False, assumes the histogram is proportional to counts per bin; + otherwise, assumes it is proportional to a density. + For constant bin widths, these are equivalent, but the distinction + is important when bin widths vary (see Notes). + If None (default), sets ``density=True`` for backwards compatibility, + but warns if the bin widths are variable. Set `density` explicitly + to silence the warning. + + .. versionadded:: 1.10.0 + + Notes + ----- + When a histogram has unequal bin widths, there is a distinction between + histograms that are proportional to counts per bin and histograms that are + proportional to probability density over a bin. If `numpy.histogram` is + called with its default ``density=False``, the resulting histogram is the + number of counts per bin, so ``density=False`` should be passed to + `rv_histogram`. If `numpy.histogram` is called with ``density=True``, the + resulting histogram is in terms of probability density, so ``density=True`` + should be passed to `rv_histogram`. To avoid warnings, always pass + ``density`` explicitly when the input histogram has unequal bin widths. + + There are no additional shape parameters except for the loc and scale. + The pdf is defined as a stepwise function from the provided histogram. + The cdf is a linear interpolation of the pdf. + + .. versionadded:: 0.19.0 + + Examples + -------- + + Create a scipy.stats distribution from a numpy histogram + + >>> import scipy.stats + >>> import numpy as np + >>> data = scipy.stats.norm.rvs(size=100000, loc=0, scale=1.5, + ... random_state=123) + >>> hist = np.histogram(data, bins=100) + >>> hist_dist = scipy.stats.rv_histogram(hist, density=False) + + Behaves like an ordinary scipy rv_continuous distribution + + >>> hist_dist.pdf(1.0) + 0.20538577847618705 + >>> hist_dist.cdf(2.0) + 0.90818568543056499 + + PDF is zero above (below) the highest (lowest) bin of the histogram, + defined by the max (min) of the original dataset + + >>> hist_dist.pdf(np.max(data)) + 0.0 + >>> hist_dist.cdf(np.max(data)) + 1.0 + >>> hist_dist.pdf(np.min(data)) + 7.7591907244498314e-05 + >>> hist_dist.cdf(np.min(data)) + 0.0 + + PDF and CDF follow the histogram + + >>> import matplotlib.pyplot as plt + >>> X = np.linspace(-5.0, 5.0, 100) + >>> fig, ax = plt.subplots() + >>> ax.set_title("PDF from Template") + >>> ax.hist(data, density=True, bins=100) + >>> ax.plot(X, hist_dist.pdf(X), label='PDF') + >>> ax.plot(X, hist_dist.cdf(X), label='CDF') + >>> ax.legend() + >>> fig.show() + + """ + _support_mask = rv_continuous._support_mask + + def __init__(self, histogram, *args, density=None, **kwargs): + """ + Create a new distribution using the given histogram + + Parameters + ---------- + histogram : tuple of array_like + Tuple containing two array_like objects. + The first containing the content of n bins, + the second containing the (n+1) bin boundaries. + In particular, the return value of np.histogram is accepted. + density : bool, optional + If False, assumes the histogram is proportional to counts per bin; + otherwise, assumes it is proportional to a density. + For constant bin widths, these are equivalent. + If None (default), sets ``density=True`` for backward + compatibility, but warns if the bin widths are variable. Set + `density` explicitly to silence the warning. + """ + self._histogram = histogram + self._density = density + if len(histogram) != 2: + raise ValueError("Expected length 2 for parameter histogram") + self._hpdf = np.asarray(histogram[0]) + self._hbins = np.asarray(histogram[1]) + if len(self._hpdf) + 1 != len(self._hbins): + raise ValueError("Number of elements in histogram content " + "and histogram boundaries do not match, " + "expected n and n+1.") + self._hbin_widths = self._hbins[1:] - self._hbins[:-1] + bins_vary = not np.allclose(self._hbin_widths, self._hbin_widths[0]) + if density is None and bins_vary: + message = ("Bin widths are not constant. Assuming `density=True`." + "Specify `density` explicitly to silence this warning.") + warnings.warn(message, RuntimeWarning, stacklevel=2) + density = True + elif not density: + self._hpdf = self._hpdf / self._hbin_widths + + self._hpdf = self._hpdf / float(np.sum(self._hpdf * self._hbin_widths)) + self._hcdf = np.cumsum(self._hpdf * self._hbin_widths) + self._hpdf = np.hstack([0.0, self._hpdf, 0.0]) + self._hcdf = np.hstack([0.0, self._hcdf]) + # Set support + kwargs['a'] = self.a = self._hbins[0] + kwargs['b'] = self.b = self._hbins[-1] + super().__init__(*args, **kwargs) + + def _pdf(self, x): + """ + PDF of the histogram + """ + return self._hpdf[np.searchsorted(self._hbins, x, side='right')] + + def _cdf(self, x): + """ + CDF calculated from the histogram + """ + return np.interp(x, self._hbins, self._hcdf) + + def _ppf(self, x): + """ + Percentile function calculated from the histogram + """ + return np.interp(x, self._hcdf, self._hbins) + + def _munp(self, n): + """Compute the n-th non-central moment.""" + integrals = (self._hbins[1:]**(n+1) - self._hbins[:-1]**(n+1)) / (n+1) + return np.sum(self._hpdf[1:-1] * integrals) + + def _entropy(self): + """Compute entropy of distribution""" + hpdf = self._hpdf[1:-1] + res = xpx.apply_where(hpdf > 0.0, hpdf, np.log, fill_value=0.0) + return -np.sum(hpdf * res * self._hbin_widths) + + def _updated_ctor_param(self): + """ + Set the histogram as additional constructor argument + """ + dct = super()._updated_ctor_param() + dct['histogram'] = self._histogram + dct['density'] = self._density + return dct + + +class studentized_range_gen(rv_continuous): + r"""A studentized range continuous random variable. + + %(before_notes)s + + See Also + -------- + t: Student's t distribution + + Notes + ----- + The probability density function for `studentized_range` is: + + .. math:: + + f(x; k, \nu) = \frac{k(k-1)\nu^{\nu/2}}{\Gamma(\nu/2) + 2^{\nu/2-1}} \int_{0}^{\infty} \int_{-\infty}^{\infty} + s^{\nu} e^{-\nu s^2/2} \phi(z) \phi(sx + z) + [\Phi(sx + z) - \Phi(z)]^{k-2} \,dz \,ds + + for :math:`x ≥ 0`, :math:`k > 1`, and :math:`\nu > 0`. + + `studentized_range` takes ``k`` for :math:`k` and ``df`` for :math:`\nu` + as shape parameters. + + When :math:`\nu` exceeds 100,000, an asymptotic approximation (infinite + degrees of freedom) is used to compute the cumulative distribution + function [4]_ and probability distribution function. + + %(after_notes)s + + References + ---------- + + .. [1] "Studentized range distribution", + https://en.wikipedia.org/wiki/Studentized_range_distribution + .. [2] Batista, Ben Dêivide, et al. "Externally Studentized Normal Midrange + Distribution." Ciência e Agrotecnologia, vol. 41, no. 4, 2017, pp. + 378-389., :doi:`10.1590/1413-70542017414047716`. + .. [3] Harter, H. Leon. "Tables of Range and Studentized Range." The Annals + of Mathematical Statistics, vol. 31, no. 4, 1960, pp. 1122-1147. + JSTOR, www.jstor.org/stable/2237810. Accessed 18 Feb. 2021. + .. [4] Lund, R. E., and J. R. Lund. "Algorithm AS 190: Probabilities and + Upper Quantiles for the Studentized Range." Journal of the Royal + Statistical Society. Series C (Applied Statistics), vol. 32, no. 2, + 1983, pp. 204-210. JSTOR, www.jstor.org/stable/2347300. Accessed 18 + Feb. 2021. + + Examples + -------- + >>> import numpy as np + >>> from scipy.stats import studentized_range + >>> import matplotlib.pyplot as plt + >>> fig, ax = plt.subplots(1, 1) + + Display the probability density function (``pdf``): + + >>> k, df = 3, 10 + >>> x = np.linspace(studentized_range.ppf(0.01, k, df), + ... studentized_range.ppf(0.99, k, df), 100) + >>> ax.plot(x, studentized_range.pdf(x, k, df), + ... 'r-', lw=5, alpha=0.6, label='studentized_range pdf') + + Alternatively, the distribution object can be called (as a function) + to fix the shape, location and scale parameters. This returns a "frozen" + RV object holding the given parameters fixed. + + Freeze the distribution and display the frozen ``pdf``: + + >>> rv = studentized_range(k, df) + >>> ax.plot(x, rv.pdf(x), 'k-', lw=2, label='frozen pdf') + + Check accuracy of ``cdf`` and ``ppf``: + + >>> vals = studentized_range.ppf([0.001, 0.5, 0.999], k, df) + >>> np.allclose([0.001, 0.5, 0.999], studentized_range.cdf(vals, k, df)) + True + + Rather than using (``studentized_range.rvs``) to generate random variates, + which is very slow for this distribution, we can approximate the inverse + CDF using an interpolator, and then perform inverse transform sampling + with this approximate inverse CDF. + + This distribution has an infinite but thin right tail, so we focus our + attention on the leftmost 99.9 percent. + + >>> a, b = studentized_range.ppf([0, .999], k, df) + >>> a, b + 0, 7.41058083802274 + + >>> from scipy.interpolate import interp1d + >>> rng = np.random.default_rng() + >>> xs = np.linspace(a, b, 50) + >>> cdf = studentized_range.cdf(xs, k, df) + # Create an interpolant of the inverse CDF + >>> ppf = interp1d(cdf, xs, fill_value='extrapolate') + # Perform inverse transform sampling using the interpolant + >>> r = ppf(rng.uniform(size=1000)) + + And compare the histogram: + + >>> ax.hist(r, density=True, histtype='stepfilled', alpha=0.2) + >>> ax.legend(loc='best', frameon=False) + >>> plt.show() + + """ + + def _argcheck(self, k, df): + return (k > 1) & (df > 0) + + def _shape_info(self): + ik = _ShapeInfo("k", False, (1, np.inf), (False, False)) + idf = _ShapeInfo("df", False, (0, np.inf), (False, False)) + return [ik, idf] + + def _fitstart(self, data): + # Default is k=1, but that is not a valid value of the parameter. + return super()._fitstart(data, args=(2, 1)) + + def _munp(self, K, k, df): + cython_symbol = '_studentized_range_moment' + _a, _b = self._get_support() + # all three of these are used to create a numpy array so they must + # be the same shape. + + def _single_moment(K, k, df): + log_const = _stats._studentized_range_pdf_logconst(k, df) + arg = [K, k, df, log_const] + usr_data = np.array(arg, float).ctypes.data_as(ctypes.c_void_p) + + llc = LowLevelCallable.from_cython(_stats, cython_symbol, usr_data) + + ranges = [(-np.inf, np.inf), (0, np.inf), (_a, _b)] + opts = dict(epsabs=1e-11, epsrel=1e-12) + + return integrate.nquad(llc, ranges=ranges, opts=opts)[0] + + ufunc = np.frompyfunc(_single_moment, 3, 1) + return np.asarray(ufunc(K, k, df), dtype=np.float64)[()] + + def _pdf(self, x, k, df): + + def _single_pdf(q, k, df): + # The infinite form of the PDF is derived from the infinite + # CDF. + if df < 100000: + cython_symbol = '_studentized_range_pdf' + log_const = _stats._studentized_range_pdf_logconst(k, df) + arg = [q, k, df, log_const] + usr_data = np.array(arg, float).ctypes.data_as(ctypes.c_void_p) + ranges = [(-np.inf, np.inf), (0, np.inf)] + + else: + cython_symbol = '_studentized_range_pdf_asymptotic' + arg = [q, k] + usr_data = np.array(arg, float).ctypes.data_as(ctypes.c_void_p) + ranges = [(-np.inf, np.inf)] + + llc = LowLevelCallable.from_cython(_stats, cython_symbol, usr_data) + opts = dict(epsabs=1e-11, epsrel=1e-12) + return integrate.nquad(llc, ranges=ranges, opts=opts)[0] + + ufunc = np.frompyfunc(_single_pdf, 3, 1) + return np.asarray(ufunc(x, k, df), dtype=np.float64)[()] + + def _cdf(self, x, k, df): + + def _single_cdf(q, k, df): + # "When the degrees of freedom V are infinite the probability + # integral takes [on a] simpler form," and a single asymptotic + # integral is evaluated rather than the standard double integral. + # (Lund, Lund, page 205) + if df < 100000: + cython_symbol = '_studentized_range_cdf' + log_const = _stats._studentized_range_cdf_logconst(k, df) + arg = [q, k, df, log_const] + usr_data = np.array(arg, float).ctypes.data_as(ctypes.c_void_p) + ranges = [(-np.inf, np.inf), (0, np.inf)] + + else: + cython_symbol = '_studentized_range_cdf_asymptotic' + arg = [q, k] + usr_data = np.array(arg, float).ctypes.data_as(ctypes.c_void_p) + ranges = [(-np.inf, np.inf)] + + llc = LowLevelCallable.from_cython(_stats, cython_symbol, usr_data) + opts = dict(epsabs=1e-11, epsrel=1e-12) + return integrate.nquad(llc, ranges=ranges, opts=opts)[0] + + ufunc = np.frompyfunc(_single_cdf, 3, 1) + + # clip p-values to ensure they are in [0, 1]. + return np.clip(np.asarray(ufunc(x, k, df), dtype=np.float64)[()], 0, 1) + + +studentized_range = studentized_range_gen(name='studentized_range', a=0, + b=np.inf) + + +class rel_breitwigner_gen(rv_continuous): + r"""A relativistic Breit-Wigner random variable. + + %(before_notes)s + + See Also + -------- + cauchy: Cauchy distribution, also known as the Breit-Wigner distribution. + + Notes + ----- + + The probability density function for `rel_breitwigner` is + + .. math:: + + f(x, \rho) = \frac{k}{(x^2 - \rho^2)^2 + \rho^2} + + where + + .. math:: + k = \frac{2\sqrt{2}\rho^2\sqrt{\rho^2 + 1}} + {\pi\sqrt{\rho^2 + \rho\sqrt{\rho^2 + 1}}} + + The relativistic Breit-Wigner distribution is used in high energy physics + to model resonances [1]_. It gives the uncertainty in the invariant mass, + :math:`M` [2]_, of a resonance with characteristic mass :math:`M_0` and + decay-width :math:`\Gamma`, where :math:`M`, :math:`M_0` and :math:`\Gamma` + are expressed in natural units. In SciPy's parametrization, the shape + parameter :math:`\rho` is equal to :math:`M_0/\Gamma` and takes values in + :math:`(0, \infty)`. + + Equivalently, the relativistic Breit-Wigner distribution is said to give + the uncertainty in the center-of-mass energy :math:`E_{\text{cm}}`. In + natural units, the speed of light :math:`c` is equal to 1 and the invariant + mass :math:`M` is equal to the rest energy :math:`Mc^2`. In the + center-of-mass frame, the rest energy is equal to the total energy [3]_. + + %(after_notes)s + + :math:`\rho = M/\Gamma` and :math:`\Gamma` is the scale parameter. For + example, if one seeks to model the :math:`Z^0` boson with :math:`M_0 + \approx 91.1876 \text{ GeV}` and :math:`\Gamma \approx 2.4952\text{ GeV}` + [4]_ one can set ``rho=91.1876/2.4952`` and ``scale=2.4952``. + + To ensure a physically meaningful result when using the `fit` method, one + should set ``floc=0`` to fix the location parameter to 0. + + References + ---------- + .. [1] Relativistic Breit-Wigner distribution, Wikipedia, + https://en.wikipedia.org/wiki/Relativistic_Breit-Wigner_distribution + .. [2] Invariant mass, Wikipedia, + https://en.wikipedia.org/wiki/Invariant_mass + .. [3] Center-of-momentum frame, Wikipedia, + https://en.wikipedia.org/wiki/Center-of-momentum_frame + .. [4] M. Tanabashi et al. (Particle Data Group) Phys. Rev. D 98, 030001 - + Published 17 August 2018 + + %(example)s + + """ + def _argcheck(self, rho): + return rho > 0 + + def _shape_info(self): + return [_ShapeInfo("rho", False, (0, np.inf), (False, False))] + + def _pdf(self, x, rho): + # C = k / rho**2 + C = np.sqrt( + 2 * (1 + 1/rho**2) / (1 + np.sqrt(1 + 1/rho**2)) + ) * 2 / np.pi + with np.errstate(over='ignore'): + return C / (((x - rho)*(x + rho)/rho)**2 + 1) + + def _cdf(self, x, rho): + # C = k / (2 * rho**2) / np.sqrt(1 + 1/rho**2) + C = np.sqrt(2/(1 + np.sqrt(1 + 1/rho**2)))/np.pi + result = ( + np.sqrt(-1 + 1j/rho) + * np.arctan(x/np.sqrt(-rho*(rho + 1j))) + ) + result = C * 2 * np.imag(result) + # Sometimes above formula produces values greater than 1. + return np.clip(result, None, 1) + + def _munp(self, n, rho): + if n == 0: + return 1. + if n == 1: + # C = k / (2 * rho) + C = np.sqrt( + 2 * (1 + 1/rho**2) / (1 + np.sqrt(1 + 1/rho**2)) + ) / np.pi * rho + return C * (np.pi/2 + np.arctan(rho)) + if n == 2: + # C = pi * k / (4 * rho) + C = np.sqrt( + (1 + 1/rho**2) / (2 * (1 + np.sqrt(1 + 1/rho**2))) + ) * rho + result = (1 - rho * 1j) / np.sqrt(-1 - 1j/rho) + return 2 * C * np.real(result) + else: + return np.inf + + def _stats(self, rho): + # Returning None from stats makes public stats use _munp. + # nan values will be omitted from public stats. Skew and + # kurtosis are actually infinite. + return None, None, np.nan, np.nan + + @inherit_docstring_from(rv_continuous) + def fit(self, data, *args, **kwds): + # Override rv_continuous.fit to better handle case where floc is set. + data, _, floc, fscale = _check_fit_input_parameters( + self, data, args, kwds + ) + + censored = isinstance(data, CensoredData) + if censored: + if data.num_censored() == 0: + # There are no censored values in data, so replace the + # CensoredData instance with a regular array. + data = data._uncensored + censored = False + + if floc is None or censored: + return super().fit(data, *args, **kwds) + + if fscale is None: + # The interquartile range approximates the scale parameter gamma. + # The median approximates rho * gamma. + p25, p50, p75 = np.quantile(data - floc, [0.25, 0.5, 0.75]) + scale_0 = p75 - p25 + rho_0 = p50 / scale_0 + if not args: + args = [rho_0] + if "scale" not in kwds: + kwds["scale"] = scale_0 + else: + M_0 = np.median(data - floc) + rho_0 = M_0 / fscale + if not args: + args = [rho_0] + return super().fit(data, *args, **kwds) + + +rel_breitwigner = rel_breitwigner_gen(a=0.0, name="rel_breitwigner") + + +# Collect names of classes and objects in this module. +pairs = list(globals().copy().items()) +_distn_names, _distn_gen_names = get_distribution_names(pairs, rv_continuous) + +__all__ = _distn_names + _distn_gen_names + ['rv_histogram'] diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_correlation.py b/venv/lib/python3.12/site-packages/scipy/stats/_correlation.py new file mode 100644 index 0000000000000000000000000000000000000000..5c72e5eac9300911c8b6281eceb9ca91d3b03a4c --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_correlation.py @@ -0,0 +1,731 @@ +import numpy as np +import math +from scipy import stats, special +from scipy._external import array_api_extra as xpx +from scipy._lib._array_api import (xp_capabilities, array_namespace, xp_promote, + is_numpy, _share_masks, _count_nonmasked, is_marray) +from scipy.stats._stats_py import (_SimpleNormal, SignificanceResult, _get_pvalue, + _rankdata) +from scipy.stats._axis_nan_policy import _axis_nan_policy_factory +from scipy.stats._stats_mstats_common import (TheilslopesResult, _n_samples_optional_x, + SiegelslopesResult) + + +__all__ = ['chatterjeexi', 'spearmanrho', 'theilslopes', 'siegelslopes'] + + +def _xi_statistic(x, y, y_continuous, xp): + # Compute xi correlation statistic + + # `axis=-1` is guaranteed by _axis_nan_policy decorator + n = x.shape[-1] + + # "Rearrange the data as (X(1), Y(1)), . . . ,(X(n), Y(n)) + # such that X(1) ≤ ··· ≤ X(n)" + j = xp.argsort(x, axis=-1) + j, y = xp.broadcast_arrays(j, y) + y = xp.take_along_axis(y, j, axis=-1) + + # "Let ri be the rank of Y(i), that is, the number of j such that Y(j) ≤ Y(i)" + r = stats.rankdata(y, method='max', axis=-1) + # " additionally define li to be the number of j such that Y(j) ≥ Y(i)" + # Could probably compute this from r, but that can be an enhancement + l = stats.rankdata(-y, method='max', axis=-1) + + num = xp.sum(xp.abs(xp.diff(r, axis=-1)), axis=-1) + if y_continuous: # [1] Eq. 1.1 + statistic = 1 - 3 * num / (n ** 2 - 1) + else: # [1] Eq. 1.2 + den = 2 * xp.sum((n - l) * l, axis=-1) + statistic = 1 - n * num / den + + return statistic, r, l + + +def _xi_std(r, l, y_continuous, xp): + # Compute asymptotic standard deviation of xi under null hypothesis of independence + + # `axis=-1` is guaranteed by _axis_nan_policy decorator + n = r.shape[-1] + + # "Suppose that X and Y are independent and Y is continuous. Then + # √n·ξn(X, Y) → N(0, 2/5) in distribution as n → ∞" + if y_continuous: # [1] Theorem 2.1 + return xp.asarray(math.sqrt(2 / 5) / math.sqrt(n), dtype=r.dtype) + + # "Suppose that X and Y are independent. Then √n·ξn(X, Y) + # converges to N(0, τ²) in distribution as n → ∞ + # [1] Eq. 2.2 and surrounding math + i = xp.arange(1, n + 1, dtype=r.dtype) + u = xp.sort(r, axis=-1) + v = xp.cumulative_sum(u, axis=-1) + an = 1 / n**4 * xp.sum((2*n - 2*i + 1) * u**2, axis=-1) + bn = 1 / n**5 * xp.sum((v + (n - i)*u)**2, axis=-1) + cn = 1 / n**3 * xp.sum((2*n - 2*i + 1) * u, axis=-1) + dn = 1 / n**3 * xp.sum((l * (n - l)), axis=-1) + tau2 = (an - 2*bn + cn**2) / dn**2 + + return xp.sqrt(tau2) / math.sqrt(n) + + +def _chatterjeexi_iv(y_continuous, method): + # Input validation for `chatterjeexi` + # x, y, `axis` input validation taken care of by decorator + + if y_continuous not in {True, False}: + raise ValueError('`y_continuous` must be boolean.') + + if not isinstance(method, stats.PermutationMethod): + method = method.lower() + message = "`method` must be 'asymptotic' or a `PermutationMethod` instance." + if method != 'asymptotic': + raise ValueError(message) + + return y_continuous, method + + +def _unpack(res, _): + return res.statistic, res.pvalue + + +@xp_capabilities(skip_backends=[('dask.array', 'no take_along_axis')]) +@_axis_nan_policy_factory(SignificanceResult, paired=True, n_samples=2, + result_to_tuple=_unpack, n_outputs=2, too_small=1) +def chatterjeexi(x, y, *, axis=0, y_continuous=False, method='asymptotic'): + r"""Compute the xi correlation and perform a test of independence. + + The xi correlation coefficient is a measure of association between two + variables; the value tends to be close to zero when the variables are + independent and close to 1 when there is a strong association. Unlike + other correlation coefficients, the xi correlation is effective even + when the association is not monotonic. + + Parameters + ---------- + x, y : array-like + The samples: corresponding observations of the independent and + dependent variable. The (N-d) arrays must be broadcastable. + axis : int, default: 0 + Axis along which to perform the test. + y_continuous : bool, default: False + Whether `y` is assumed to be drawn from a continuous distribution. + If `y` is drawn from a continuous distribution, results are valid + whether this is assumed or not, but enabling this assumption will + result in faster computation and typically produce similar results. + method : 'asymptotic' or `PermutationMethod` instance, optional + Selects the method used to calculate the *p*-value. + Default is 'asymptotic'. The following options are available. + + * ``'asymptotic'``: compares the standardized test statistic + against the normal distribution. + * `PermutationMethod` instance. In this case, the p-value + is computed using `permutation_test` with the provided + configuration options and other appropriate settings. + + Returns + ------- + res : SignificanceResult + An object containing attributes: + + statistic : float + The xi correlation statistic. + pvalue : float + The associated *p*-value: the probability of a statistic at least as + high as the observed value under the null hypothesis of independence. + + See Also + -------- + scipy.stats.pearsonr, scipy.stats.spearmanr, scipy.stats.kendalltau + + Notes + ----- + There is currently no special handling of ties in `x`; they are broken arbitrarily + by the implementation. [1]_ recommends: "if there are ties among the Xi's, then + choose an increasing rearrangement as above by breaking ties uniformly at random." + This is easily accomplished by adding a small amount of random noise to `x`; see + examples. + + [1]_ notes that the statistic is not symmetric in `x` and `y` *by design*: + "...we may want to understand if :math:`Y` is a function :math:`X`, and not just + if one of the variables is a function of the other." See [1]_ Remark 1. + + References + ---------- + .. [1] Chatterjee, Sourav. "A new coefficient of correlation." Journal of + the American Statistical Association 116.536 (2021): 2009-2022. + :doi:`10.1080/01621459.2020.1758115`. + + Examples + -------- + Generate perfectly correlated data, and observe that the xi correlation is + nearly 1.0. + + >>> import numpy as np + >>> from scipy import stats + >>> rng = np.random.default_rng(348932549825235) + >>> x = rng.uniform(0, 10, size=100) + >>> y = np.sin(x) + >>> res = stats.chatterjeexi(x, y) + >>> res.statistic + np.float64(0.9012901290129013) + + The probability of observing such a high value of the statistic under the + null hypothesis of independence is very low. + + >>> res.pvalue + np.float64(2.2206974648177804e-46) + + As noise is introduced, the correlation coefficient decreases. + + >>> noise = rng.normal(scale=[[0.1], [0.5], [1]], size=(3, 100)) + >>> res = stats.chatterjeexi(x, y + noise, axis=-1) + >>> res.statistic + array([0.79507951, 0.41824182, 0.16651665]) + + Because the distribution of `y` is continuous, it is valid to pass + ``y_continuous=True``. The statistic is identical, and the p-value + (not shown) is only slightly different. + + >>> stats.chatterjeexi(x, y + noise, y_continuous=True, axis=-1).statistic + array([0.79507951, 0.41824182, 0.16651665]) + + Consider a case in which there are ties in `x`. + + >>> x = rng.integers(10, size=1000) + >>> y = rng.integers(10, size=1000) + + [1]_ recommends breaking the ties uniformly at random. + + >>> d = rng.uniform(1e-5, size=x.size) + >>> res = stats.chatterjeexi(x + d, y) + >>> res.statistic + -0.029919991638798438 + + Since this gives a randomized estimate of the statistic, [1]_ also suggests + considering the average over all possibilities of breaking ties. This is + computationally infeasible when there are many ties, but a randomized estimate of + *this* quantity can be obtained by considering many random possibilities of breaking + ties. + + >>> d = rng.uniform(1e-5, size=(9999, x.size)) + >>> res = stats.chatterjeexi(x + d, y, axis=1) + >>> np.mean(res.statistic) + 0.001186895213756626 + + """ + xp = array_namespace(x, y) + + # x, y, `axis` input validation taken care of by decorator + # In fact, `axis` is guaranteed to be -1 + y_continuous, method = _chatterjeexi_iv(y_continuous, method) + x, y = xp_promote(x, y, force_floating=True, xp=xp) + + # A highly negative statistic is possible, e.g. + # x = np.arange(100.), y = (x % 2 == 0) + # Unclear whether we should expose `alternative`, though. + alternative = 'greater' + + if method == 'asymptotic': + xi, r, l = _xi_statistic(x, y, y_continuous, xp=xp) + std = _xi_std(r, l, y_continuous, xp=xp) + norm = _SimpleNormal() + pvalue = _get_pvalue(xi / std, norm, alternative=alternative, xp=xp) + elif isinstance(method, stats.PermutationMethod): + res = stats.permutation_test( + # Could be faster if we just permuted the ranks; for now, keep it simple. + data=(y,), + statistic=lambda y, axis: _xi_statistic(x, y, y_continuous, xp=xp)[0], + alternative=alternative, permutation_type='pairings', **method._asdict(), + axis=-1) # `axis=-1` is guaranteed by _axis_nan_policy decorator + + xi, pvalue = res.statistic, res.pvalue + + xi = xi[()] if xi.ndim == 0 else xi + pvalue = pvalue[()] if pvalue.ndim == 0 else pvalue + return SignificanceResult(xi, pvalue) + + +@xp_capabilities(cpu_only=True, exceptions=['jax.numpy'], marray=True, + skip_backends=[('dask.array', 'not supported by rankdata (take_along_axis)')], + extra_note='Only the default `method` is compatible with MArray input.' +) +@_axis_nan_policy_factory(SignificanceResult, paired=True, n_samples=2, + result_to_tuple=_unpack, n_outputs=2, too_small=1) +def spearmanrho(x, y, /, *, alternative='two-sided', method=None, axis=0): + r"""Calculate a Spearman rho correlation coefficient with associated p-value. + + The Spearman rank-order correlation coefficient is a nonparametric measure + of the monotonicity of the relationship between two datasets. + Like other correlation coefficients, it varies between -1 and +1 with 0 + implying no correlation. Coefficients of -1 or +1 are associated with an exact + monotonic relationship. Positive correlations indicate that as `x` increases, + so does `y`; negative correlations indicate that as `x` increases, `y` decreases. + The p-value is the probability of an uncorrelated system producing datasets + with a Spearman correlation at least as extreme as the one computed from the + observed dataset. + + Parameters + ---------- + x, y : array-like + The samples: corresponding observations of the independent and + dependent variable. The (N-d) arrays must be broadcastable. + alternative : {'two-sided', 'less', 'greater'}, optional + Defines the alternative hypothesis. Default is 'two-sided'. + The following options are available: + + * 'two-sided': the correlation is nonzero + * 'less': the correlation is negative (less than zero) + * 'greater': the correlation is positive (greater than zero) + + method : ResamplingMethod, optional + Defines the method used to compute the p-value. If `method` is an + instance of `PermutationMethod`/`MonteCarloMethod`, the p-value is + computed using + `scipy.stats.permutation_test`/`scipy.stats.monte_carlo_test` with the + provided configuration options and other appropriate settings. + Otherwise, the p-value is computed using an asymptotic approximation of + the null distribution. + axis : int or None, optional + If axis=0 (default), then each column represents a variable, with + observations in the rows. If axis=1, the relationship is transposed: + each row represents a variable, while the columns contain observations. + If axis=None, then both arrays will be raveled. + Like other `scipy.stats` functions, `axis` is interpreted after the + arrays are broadcasted. + + Returns + ------- + res : SignificanceResult + An object containing attributes: + + statistic : floating point array or NumPy scalar + Spearman correlation coefficient + pvalue : floating point array NumPy scalar + The p-value - the probabilitiy of realizing such an extreme statistic + value under the null hypothesis that two samples have no ordinal + correlation. See `alternative` above for alternative hypotheses. + + Warns + ----- + `~scipy.stats.ConstantInputWarning` + Raised if an input is a constant array. The correlation coefficient + is not defined in this case, so ``np.nan`` is returned. + + Notes + ----- + `spearmanrho` was created to make improvements to SciPy's implementation of + the Spearman correlation test without making backward-incompatible changes + to `spearmanr`. Advantages of `spearmanrho` over `spearmanr` include: + + - `spearmanrho` follows standard array broadcasting rules. + - `spearmanrho` is compatible with some non-NumPy arrays. + - `spearmanrho` can compute exact p-values, even in the presence of ties, + when an appropriate instance of `PermutationMethod` is provided via the + `method` argument. + + References + ---------- + .. [1] Zwillinger, D. and Kokoska, S. (2000). CRC Standard + Probability and Statistics Tables and Formulae. Chapman & Hall: New + York. 2000. + Section 14.7 + .. [2] Kendall, M. G. and Stuart, A. (1973). + The Advanced Theory of Statistics, Volume 2: Inference and Relationship. + Griffin. 1973. + Section 31.18 + + Examples + -------- + Univariate samples, approximate p-value. + + >>> import numpy as np + >>> from scipy import stats + >>> x = [1, 2, 3, 4, 5] + >>> y = [5, 6, 7, 8, 7] + >>> res = stats.spearmanrho(x, y) + >>> res.statistic + np.float64(0.8207826816681233) + >>> res.pvalue + np.float64(0.08858700531354405) + + Univariate samples, exact p-value. + + >>> res = stats.spearmanrho(x, y, method=stats.PermutationMethod()) + >>> res.statistic + np.float64(0.8207826816681233) + >>> res.pvalue + np.float64(0.13333333333333333) + + Batch of univariate samples, one vectorized call. + + >>> rng = np.random.default_rng(98145152315484) + >>> x2 = rng.standard_normal((2, 100)) + >>> y2 = rng.standard_normal((2, 100)) + >>> res = stats.spearmanrho(x2, y2, axis=-1) + >>> res.statistic + array([ 0.16585659, -0.12151215]) + >>> res.pvalue + array([0.0991155 , 0.22846869]) + + Bivariate samples using standard broadcasting rules. + + >>> res = stats.spearmanrho(x2[np.newaxis, :], x2[:, np.newaxis], axis=-1) + >>> res.statistic + array([[ 1. , -0.14670267], + [-0.14670267, 1. ]]) + >>> res.pvalue + array([[0. , 0.14526128], + [0.14526128, 0. ]]) + + """ + xp = array_namespace(x, y) + x, y = _share_masks(x, y, xp=xp) + rx = stats.rankdata(x, axis=axis) + ry = stats.rankdata(y, axis=axis) + res = stats.pearsonr(rx, ry, method=method, alternative=alternative, axis=axis) + return SignificanceResult(res.statistic, res.pvalue) + + +@xp_capabilities(skip_backends=[("dask.array", "no take_along_axis"), + ("jax.numpy", "non-concrete boolean indexing")], + marray=True) +@_axis_nan_policy_factory(TheilslopesResult, default_axis=None, n_outputs=4, + n_samples=_n_samples_optional_x, + result_to_tuple=lambda x, _: tuple(x), paired=True, + too_small=1) +def theilslopes(y, x=None, alpha=0.95, method='separate', *, axis=None): + r""" + Computes the Theil-Sen estimator for a set of points (x, y). + + `theilslopes` implements a method for robust linear regression. It + computes the slope as the median of all slopes between paired values. + + Parameters + ---------- + y : array_like + Dependent variable. + x : array_like or None, optional + Independent variable. If None, use ``arange(len(y))`` instead. + alpha : float, optional + Confidence degree between 0 and 1. Default is 95% confidence. + Note that `alpha` is symmetric around 0.5, i.e. both 0.1 and 0.9 are + interpreted as "find the 90% confidence interval". + method : {'joint', 'separate'}, optional + Method to be used for computing estimate for intercept. + Following methods are supported, + + * 'joint': Uses ``np.median(y - slope * x)`` as intercept. + * 'separate': Uses ``np.median(y) - slope * np.median(x)`` + as intercept. + + The default is 'separate'. + + .. versionadded:: 1.8.0 + + axis : int or tuple of ints, default: None + If an int or tuple of ints, the axis or axes of the input along which + to compute the statistic. The statistic of each axis-slice (e.g. row) + of the input will appear in a corresponding element of the output. + If ``None``, the input will be raveled before computing the statistic. + + Returns + ------- + result : ``TheilslopesResult`` instance + The return value is an object with the following attributes: + + slope : float + Theil slope. + intercept : float + Intercept of the Theil line. + low_slope : float + Lower bound of the confidence interval on `slope`. + high_slope : float + Upper bound of the confidence interval on `slope`. + + See Also + -------- + siegelslopes : a similar technique using repeated medians + + Notes + ----- + The implementation of `theilslopes` follows [1]_. The intercept is + not defined in [1]_, and here it is defined as ``median(y) - + slope*median(x)``, which is given in [3]_. Other definitions of + the intercept exist in the literature such as ``median(y - slope*x)`` + in [4]_. The approach to compute the intercept can be determined by the + parameter ``method``. A confidence interval for the intercept is not + given as this question is not addressed in [1]_. + + For compatibility with older versions of SciPy, the return value acts + like a ``namedtuple`` of length 4, with fields ``slope``, ``intercept``, + ``low_slope``, and ``high_slope``, so one can continue to write:: + + slope, intercept, low_slope, high_slope = theilslopes(y, x) + + References + ---------- + .. [1] P.K. Sen, "Estimates of the regression coefficient based on + Kendall's tau", J. Am. Stat. Assoc., Vol. 63, pp. 1379-1389, 1968. + .. [2] H. Theil, "A rank-invariant method of linear and polynomial + regression analysis I, II and III", Nederl. Akad. Wetensch., Proc. + 53:, pp. 386-392, pp. 521-525, pp. 1397-1412, 1950. + .. [3] W.L. Conover, "Practical nonparametric statistics", 2nd ed., + John Wiley and Sons, New York, pp. 493. + .. [4] https://en.wikipedia.org/wiki/Theil%E2%80%93Sen_estimator + + Examples + -------- + >>> import numpy as np + >>> from scipy import stats + >>> import matplotlib.pyplot as plt + + >>> x = np.linspace(-5, 5, num=150) + >>> y = x + np.random.normal(size=x.size) + >>> y[11:15] += 10 # add outliers + >>> y[-5:] -= 7 + + Compute the slope, intercept and 90% confidence interval. For comparison, + also compute the least-squares fit with `linregress`: + + >>> res = stats.theilslopes(y, x, 0.90, method='separate') + >>> lsq_res = stats.linregress(x, y) + + Plot the results. The Theil-Sen regression line is shown in red, with the + dashed red lines illustrating the confidence interval of the slope (note + that the dashed red lines are not the confidence interval of the regression + as the confidence interval of the intercept is not included). The green + line shows the least-squares fit for comparison. + + >>> fig = plt.figure() + >>> ax = fig.add_subplot(111) + >>> ax.plot(x, y, 'b.') + >>> ax.plot(x, res[1] + res[0] * x, 'r-') + >>> ax.plot(x, res[1] + res[2] * x, 'r--') + >>> ax.plot(x, res[1] + res[3] * x, 'r--') + >>> ax.plot(x, lsq_res[1] + lsq_res[0] * x, 'g-') + >>> plt.show() + + """ + return _robust_slopes(y, x=x, alpha=alpha, method=method, pfun='theilslopes') + + +@xp_capabilities(skip_backends=[("dask.array", "no take_along_axis"), + ("jax.numpy", "quantile needs lazy nan_policy")], + marray=True) +@_axis_nan_policy_factory(SiegelslopesResult, default_axis=None, n_outputs=2, + n_samples=_n_samples_optional_x, + result_to_tuple=lambda x, _: tuple(x), paired=True, + too_small=1) +def siegelslopes(y, x=None, method='hierarchical', *, axis=None): + r""" + Computes the Siegel estimator for a set of points (x, y). + + `siegelslopes` implements a method for robust linear regression + using repeated medians (see [1]_) to fit a line to the points (x, y). + The method is robust to outliers with an asymptotic breakdown point + of 50%. + + Parameters + ---------- + y : array_like + Dependent variable. + x : array_like or None, optional + Independent variable. If None, use ``arange(len(y))`` instead. + method : {'hierarchical', 'separate'} + If 'hierarchical', estimate the intercept using the estimated + slope ``slope`` (default option). + If 'separate', estimate the intercept independent of the estimated + slope. See Notes for details. + axis : int or tuple of ints, default: None + If an int or tuple of ints, the axis or axes of the input along which + to compute the statistic. The statistic of each axis-slice (e.g. row) + of the input will appear in a corresponding element of the output. + If ``None``, the input will be raveled before computing the statistic. + + Returns + ------- + result : ``SiegelslopesResult`` instance + The return value is an object with the following attributes: + + slope : float + Estimate of the slope of the regression line. + intercept : float + Estimate of the intercept of the regression line. + + See Also + -------- + theilslopes : a similar technique without repeated medians + + Notes + ----- + With ``n = len(y)``, compute ``m_j`` as the median of + the slopes of the lines from the point ``(x[j], y[j])`` to all other ``n-1`` points. + ``slope`` is then the median of all slopes ``m_j``. + Two ways are given to estimate the intercept in [1]_ which can be chosen + via the parameter ``method``. + The hierarchical approach uses the estimated slope ``slope`` + and computes ``intercept`` as the median of ``y - slope*x``. + The other approach estimates the intercept separately as follows: for + each point ``(x[j], y[j])``, compute the intercepts of all the ``n-1`` + lines through the remaining points and take the median ``i_j``. + ``intercept`` is the median of the ``i_j``. + + The implementation computes `n` times the median of a vector of size `n` + which can be slow for large vectors. There are more efficient algorithms + (see [2]_) which are not implemented here. + + For compatibility with older versions of SciPy, the return value acts + like a ``namedtuple`` of length 2, with fields ``slope`` and + ``intercept``, so one can continue to write:: + + slope, intercept = siegelslopes(y, x) + + References + ---------- + .. [1] A. Siegel, "Robust Regression Using Repeated Medians", + Biometrika, Vol. 69, pp. 242-244, 1982. + + .. [2] A. Stein and M. Werman, "Finding the repeated median regression + line", Proceedings of the Third Annual ACM-SIAM Symposium on + Discrete Algorithms, pp. 409-413, 1992. + + Examples + -------- + >>> import numpy as np + >>> from scipy import stats + >>> import matplotlib.pyplot as plt + + >>> x = np.linspace(-5, 5, num=150) + >>> y = x + np.random.normal(size=x.size) + >>> y[11:15] += 10 # add outliers + >>> y[-5:] -= 7 + + Compute the slope and intercept. For comparison, also compute the + least-squares fit with `linregress`: + + >>> res = stats.siegelslopes(y, x) + >>> lsq_res = stats.linregress(x, y) + + Plot the results. The Siegel regression line is shown in red. The green + line shows the least-squares fit for comparison. + + >>> fig = plt.figure() + >>> ax = fig.add_subplot(111) + >>> ax.plot(x, y, 'b.') + >>> ax.plot(x, res[1] + res[0] * x, 'r-') + >>> ax.plot(x, lsq_res[1] + lsq_res[0] * x, 'g-') + >>> plt.show() + + """ + return _robust_slopes(y, x=x, method=method, pfun='siegelslopes') + + +def _robust_slopes(y, *, x, alpha=None, method, pfun): + other_method = 'joint' if pfun == 'theilslopes' else 'hierarchical' + if method not in {other_method, 'separate'}: + raise ValueError(f"method must be either '{other_method}' or 'separate'. " + f"'{method}' is invalid.") + + xp = array_namespace(y, x) + y, x = xp_promote(y, x, force_floating=True, xp=xp) + x = xp.arange(y.shape[-1], dtype=y.dtype) if x is None else x + y, x = xp.broadcast_arrays(y, x) + x, y = _share_masks(x, y, xp=xp) + + if x.shape[-1] < 2 or y.shape[-1] < 2: # only needed by test_axis_nan_policy + raise ValueError("`x` and `y` must have length at least 2.") + + # Compute sorted slopes only when deltax > 0 + deltax = x[..., :, xp.newaxis] - x[..., xp.newaxis, :] + deltay = y[..., :, xp.newaxis] - y[..., xp.newaxis, :] + + if pfun == 'theilslopes': + i = xp.astype(xp.triu(xp.ones(deltax.shape[-2:]), k=1), xp.bool) + if is_numpy(xp): + deltax, deltay = deltax[..., i], deltay[..., i] + else: + # With array API, mask must be sole index, so we need to broadcast it. + # Indexing ravels the array, so we need to reshape the results. + i = xp.broadcast_to(i, deltax.shape) + deltax, deltay = deltax[i], deltay[i] + deltax = xp.reshape(deltax, (*i.shape[:-2], -1)) + deltay = xp.reshape(deltay, (*i.shape[:-2], -1)) + + # Use `.data` to avoid indexing with masked indices. If masked elements of deltax=0, + # they can safely be set to xp.nan, too - they're masked, after all. + deltax0 = (deltax.data == 0) if is_marray(xp) else (deltax == 0) + deltax = xpx.at(deltax)[deltax0].set(xp.nan) + slopes = deltay / deltax + + if is_marray(xp): + def nanmedian(x, axis): # use mask to ignore NaNs + x_nans_masked = xp.asarray(x.data, mask=x.mask | x._xp.isnan(x.data)) + return stats.quantile(x_nans_masked, 0.5, axis=axis) + else: + def nanmedian(x, axis): # use nan_policy to ignore NaNs + return stats.quantile(x, 0.5, axis=axis, nan_policy='omit') + + def median(x, axis): return stats.quantile(x, 0.5, axis=axis) + + # `theilslopes` is median of all slopes. Indexing with `i` above has already raveled + # all the slopes, so we only need to take the median along the last axis. + # `siegelslope` is a median of medians: we take the median of slopes from point i + # to all other points, then the median of those medians. + # The slope is NaN wherever the two points are the same, and those don't contribute, + # hence the first median omits NaNs. NaNs in the input are propagated by the + # `_axis_nan_policy` decorator. + medslope = (median(nanmedian(slopes, axis=-1), axis=-1) if pfun == 'siegelslopes' + else nanmedian(slopes, axis=-1)) + + if method in {'joint', 'hierarchical'}: + medinter = median(y - medslope[..., np.newaxis] * x, axis=-1) + elif pfun == 'theilslopes': + medinter = median(y, axis=-1) - medslope * median(x, axis=-1) + else: + # Calculate pairwise intercepts given each point (row i) and the slope to each + # other point (column j). Then calculate the median of (row) medians. + intercepts = y[..., :, xp.newaxis] - slopes*x[..., :, xp.newaxis] + medinter = median(nanmedian(intercepts, axis=-1), axis=-1) + + if pfun == 'siegelslopes': + return SiegelslopesResult(slope=medslope[()], intercept=medinter[()]) + + # Now compute confidence intervals + if alpha > 0.5: + alpha = 1. - alpha + + z = float(special.ndtri(alpha / 2.)) + # This implements (2.6) from Sen (1968) + # we don't actually need ranks, so an enhancement could be to have + # `rankdata` return only the third output. In the meantime, use the + # least expensive `method`. + _, _, nxreps = _rankdata(x, method='min', return_ties=True) + _, _, nyreps = _rankdata(y, method='min', return_ties=True) + nt = xp.count_nonzero(xp.isfinite(slopes), axis=-1, keepdims=True) # N in Sen 1968 + nt = xp.asarray(nt, dtype=y.dtype) + ny = _count_nonmasked(y, keepdims=True, axis=-1) # n in Sen 1968 + # Equation 2.6 in Sen (1968): + sigsq = 1/18. * ( + ny * (ny-1) * (2*ny+5) + - xp.sum(nxreps * (nxreps-1) * (2*nxreps + 5), axis=-1, keepdims=True) + - xp.sum(nyreps * (nyreps-1) * (2*nyreps + 5), axis=-1, keepdims=True)) + # Find the confidence interval indices in `slopes` + sigma = xp.sqrt(xp.maximum(sigsq, xp.zeros_like(sigsq))) + Ru = xp.minimum(xp.astype(xp.round((nt - z*sigma)/2.), xp.int64), + xp.astype(nt, xp.int64)-1) + Rl = xp.maximum(xp.astype(xp.round((nt + z*sigma)/2.), xp.int64) - 1, + xp.asarray(0, dtype=xp.int64)) + R = xp.concat((xpx.atleast_nd(Rl, ndim=1), xpx.atleast_nd(Ru, ndim=1)), axis=-1) + slopes = xp.sort(slopes, axis=-1) + delta = xp.take_along_axis(slopes, R, axis=-1) + i_nan = xp.broadcast_to(sigsq < 0, delta.shape) + delta = xpx.at(delta)[i_nan].set(xp.nan) + + slope = medslope[()] if medslope.ndim == 0 else medslope + intercept = medinter[()] if medinter.ndim == 0 else medinter + low_slope = delta[..., 0] + high_slope = delta[..., 1] + low_slope = low_slope[()] if low_slope.ndim == 0 else low_slope + high_slope = high_slope[()] if high_slope.ndim == 0 else high_slope + return TheilslopesResult(slope=slope, intercept=intercept, + low_slope=low_slope, high_slope=high_slope) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_covariance.py b/venv/lib/python3.12/site-packages/scipy/stats/_covariance.py new file mode 100644 index 0000000000000000000000000000000000000000..f2133b9f3278d19fbb94fd6b8ba74876078dc6dd --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_covariance.py @@ -0,0 +1,672 @@ +from functools import cached_property +from types import GenericAlias + +import numpy as np +from scipy import linalg +from scipy.stats import _multivariate + + +__all__ = ["Covariance"] + + +class Covariance: + """ + Representation of a covariance matrix. + + Calculations involving covariance matrices (e.g. data whitening, + multivariate normal function evaluation) are often performed more + efficiently using a decomposition of the covariance matrix instead of the + covariance matrix itself. This class allows the user to construct an + object representing a covariance matrix using any of several + decompositions and perform calculations using a common interface. + + .. note:: + + The `Covariance` class cannot be instantiated directly. Instead, use + one of the factory methods (e.g. `Covariance.from_diagonal`). + + Examples + -------- + The `Covariance` class is used by calling one of its + factory methods to create a `Covariance` object, then pass that + representation of the `Covariance` matrix as a shape parameter of a + multivariate distribution. + + For instance, the multivariate normal distribution can accept an array + representing a covariance matrix: + + >>> from scipy import stats + >>> import numpy as np + >>> d = [1, 2, 3] + >>> A = np.diag(d) # a diagonal covariance matrix + >>> x = [4, -2, 5] # a point of interest + >>> dist = stats.multivariate_normal(mean=[0, 0, 0], cov=A) + >>> dist.pdf(x) + 4.9595685102808205e-08 + + but the calculations are performed in a very generic way that does not + take advantage of any special properties of the covariance matrix. Because + our covariance matrix is diagonal, we can use ``Covariance.from_diagonal`` + to create an object representing the covariance matrix, and + `multivariate_normal` can use this to compute the probability density + function more efficiently. + + >>> cov = stats.Covariance.from_diagonal(d) + >>> dist = stats.multivariate_normal(mean=[0, 0, 0], cov=cov) + >>> dist.pdf(x) + 4.9595685102808205e-08 + + """ + + # generic type compatibility with scipy-stubs + __class_getitem__: classmethod = classmethod(GenericAlias) + + def __init__(self): + message = ("The `Covariance` class cannot be instantiated directly. " + "Please use one of the factory methods " + "(e.g. `Covariance.from_diagonal`).") + raise NotImplementedError(message) + + @staticmethod + def from_diagonal(diagonal): + r""" + Return a representation of a covariance matrix from its diagonal. + + Parameters + ---------- + diagonal : array_like + The diagonal elements of a diagonal matrix. + + Returns + ------- + Covariance + A `Covariance` object representing `diagonal`. + + Notes + ----- + Let the diagonal elements of a diagonal covariance matrix :math:`D` be + stored in the vector :math:`d`. + + When all elements of :math:`d` are strictly positive, whitening of a + data point :math:`x` is performed by computing + :math:`x \cdot d^{-1/2}`, where the inverse square root can be taken + element-wise. + :math:`\log\det{D}` is calculated as :math:`-2 \sum(\log{d})`, + where the :math:`\log` operation is performed element-wise. + + This `Covariance` class supports singular covariance matrices. When + computing ``_log_pdet``, non-positive elements of :math:`d` are + ignored. Whitening is not well defined when the point to be whitened + does not lie in the span of the columns of the covariance matrix. The + convention taken here is to treat the inverse square root of + non-positive elements of :math:`d` as zeros. + + Examples + -------- + Prepare a symmetric positive definite covariance matrix ``A`` and a + data point ``x``. + + >>> import numpy as np + >>> from scipy import stats + >>> rng = np.random.default_rng() + >>> n = 5 + >>> A = np.diag(rng.random(n)) + >>> x = rng.random(size=n) + + Extract the diagonal from ``A`` and create the `Covariance` object. + + >>> d = np.diag(A) + >>> cov = stats.Covariance.from_diagonal(d) + + Compare the functionality of the `Covariance` object against a + reference implementations. + + >>> res = cov.whiten(x) + >>> ref = np.diag(d**-0.5) @ x + >>> np.allclose(res, ref) + True + >>> res = cov.log_pdet + >>> ref = np.linalg.slogdet(A)[-1] + >>> np.allclose(res, ref) + True + + """ + return CovViaDiagonal(diagonal) + + @staticmethod + def from_precision(precision, covariance=None): + r""" + Return a representation of a covariance from its precision matrix. + + Parameters + ---------- + precision : array_like + The precision matrix; that is, the inverse of a square, symmetric, + positive definite covariance matrix. + covariance : array_like, optional + The square, symmetric, positive definite covariance matrix. If not + provided, this may need to be calculated (e.g. to evaluate the + cumulative distribution function of + `scipy.stats.multivariate_normal`) by inverting `precision`. + + Returns + ------- + Covariance + A `Covariance` object representing the covariance matrix + corresponding to `precision`. + + Notes + ----- + Let the covariance matrix be :math:`A`, its precision matrix be + :math:`P = A^{-1}`, and :math:`L` be the lower Cholesky factor such + that :math:`L L^T = P`. + Whitening of a data point :math:`x` is performed by computing + :math:`x^T L`. :math:`\log\det{A}` is calculated as + :math:`-2tr(\log{L})`, where the :math:`\log` operation is performed + element-wise. + + This `Covariance` class does not support singular covariance matrices + because the precision matrix does not exist for a singular covariance + matrix. + + Examples + -------- + Prepare a symmetric positive definite precision matrix ``P`` and a + data point ``x``. (If the precision matrix is not already available, + consider the other factory methods of the `Covariance` class.) + + >>> import numpy as np + >>> from scipy import stats + >>> rng = np.random.default_rng() + >>> n = 5 + >>> P = rng.random(size=(n, n)) + >>> P = P @ P.T # a precision matrix must be positive definite + >>> x = rng.random(size=n) + + Create the `Covariance` object. + + >>> cov = stats.Covariance.from_precision(P) + + Compare the functionality of the `Covariance` object against + reference implementations. + + >>> res = cov.whiten(x) + >>> ref = x @ np.linalg.cholesky(P) + >>> np.allclose(res, ref) + True + >>> res = cov.log_pdet + >>> ref = -np.linalg.slogdet(P)[-1] + >>> np.allclose(res, ref) + True + + """ + return CovViaPrecision(precision, covariance) + + @staticmethod + def from_cholesky(cholesky): + r""" + Representation of a covariance provided via the (lower) Cholesky factor. + + Parameters + ---------- + cholesky : array_like + The lower triangular Cholesky factor of the covariance matrix. + + Returns + ------- + Covariance + A `Covariance` object representing the covariance matrix + corresponding to `cholesky`. + + Notes + ----- + Let the covariance matrix be :math:`A` and :math:`L` be the lower + Cholesky factor such that :math:`L L^T = A`. + Whitening of a data point :math:`x` is performed by computing + :math:`L^{-1} x`. :math:`\log\det{A}` is calculated as + :math:`2tr(\log{L})`, where the :math:`\log` operation is performed + element-wise. + + This `Covariance` class does not support singular covariance matrices + because the Cholesky decomposition does not exist for a singular + covariance matrix. + + Examples + -------- + Prepare a symmetric positive definite covariance matrix ``A`` and a + data point ``x``. + + >>> import numpy as np + >>> from scipy import stats + >>> rng = np.random.default_rng() + >>> n = 5 + >>> A = rng.random(size=(n, n)) + >>> A = A @ A.T # make the covariance symmetric positive definite + >>> x = rng.random(size=n) + + Perform the Cholesky decomposition of ``A`` and create the + `Covariance` object. + + >>> L = np.linalg.cholesky(A) + >>> cov = stats.Covariance.from_cholesky(L) + + Compare the functionality of the `Covariance` object against + reference implementation. + + >>> from scipy.linalg import solve_triangular + >>> res = cov.whiten(x) + >>> ref = solve_triangular(L, x, lower=True) + >>> np.allclose(res, ref) + True + >>> res = cov.log_pdet + >>> ref = np.linalg.slogdet(A)[-1] + >>> np.allclose(res, ref) + True + + """ + return CovViaCholesky(cholesky) + + @staticmethod + def from_eigendecomposition(eigendecomposition): + r""" + Representation of a covariance provided via eigendecomposition. + + Parameters + ---------- + eigendecomposition : sequence + A sequence (nominally a tuple) containing the eigenvalue and + eigenvector arrays as computed by `scipy.linalg.eigh` or + `numpy.linalg.eigh`. + + Returns + ------- + Covariance + A `Covariance` object representing the covariance matrix + corresponding to `eigendecomposition`. + + Notes + ----- + Let the covariance matrix be :math:`A`, let :math:`V` be matrix of + eigenvectors, and let :math:`W` be the diagonal matrix of eigenvalues + such that `V W V^T = A`. + + When all of the eigenvalues are strictly positive, whitening of a + data point :math:`x` is performed by computing + :math:`x^T (V W^{-1/2})`, where the inverse square root can be taken + element-wise. + :math:`\log\det{A}` is calculated as :math:`tr(\log{W})`, + where the :math:`\log` operation is performed element-wise. + + This `Covariance` class supports singular covariance matrices. When + computing ``_log_pdet``, non-positive eigenvalues are ignored. + Whitening is not well defined when the point to be whitened + does not lie in the span of the columns of the covariance matrix. The + convention taken here is to treat the inverse square root of + non-positive eigenvalues as zeros. + + Examples + -------- + Prepare a symmetric positive definite covariance matrix ``A`` and a + data point ``x``. + + >>> import numpy as np + >>> from scipy import stats + >>> rng = np.random.default_rng() + >>> n = 5 + >>> A = rng.random(size=(n, n)) + >>> A = A @ A.T # make the covariance symmetric positive definite + >>> x = rng.random(size=n) + + Perform the eigendecomposition of ``A`` and create the `Covariance` + object. + + >>> w, v = np.linalg.eigh(A) + >>> cov = stats.Covariance.from_eigendecomposition((w, v)) + + Compare the functionality of the `Covariance` object against + reference implementations. + + >>> res = cov.whiten(x) + >>> ref = x @ (v @ np.diag(w**-0.5)) + >>> np.allclose(res, ref) + True + >>> res = cov.log_pdet + >>> ref = np.linalg.slogdet(A)[-1] + >>> np.allclose(res, ref) + True + + """ + return CovViaEigendecomposition(eigendecomposition) + + def whiten(self, x): + """ + Perform a whitening transformation on data. + + "Whitening" ("white" as in "white noise", in which each frequency has + equal magnitude) transforms a set of random variables into a new set of + random variables with unit-diagonal covariance. When a whitening + transform is applied to a sample of points distributed according to + a multivariate normal distribution with zero mean, the covariance of + the transformed sample is approximately the identity matrix. + + Parameters + ---------- + x : array_like + An array of points. The last dimension must correspond with the + dimensionality of the space, i.e., the number of columns in the + covariance matrix. + + Returns + ------- + x_ : array_like + The transformed array of points. + + References + ---------- + .. [1] "Whitening Transformation". Wikipedia. + https://en.wikipedia.org/wiki/Whitening_transformation + .. [2] Novak, Lukas, and Miroslav Vorechovsky. "Generalization of + coloring linear transformation". Transactions of VSB 18.2 + (2018): 31-35. :doi:`10.31490/tces-2018-0013` + + Examples + -------- + >>> import numpy as np + >>> from scipy import stats + >>> rng = np.random.default_rng() + >>> n = 3 + >>> A = rng.random(size=(n, n)) + >>> cov_array = A @ A.T # make matrix symmetric positive definite + >>> precision = np.linalg.inv(cov_array) + >>> cov_object = stats.Covariance.from_precision(precision) + >>> x = rng.multivariate_normal(np.zeros(n), cov_array, size=(10000)) + >>> x_ = cov_object.whiten(x) + >>> np.cov(x_, rowvar=False) # near-identity covariance + array([[0.97862122, 0.00893147, 0.02430451], + [0.00893147, 0.96719062, 0.02201312], + [0.02430451, 0.02201312, 0.99206881]]) + + """ + return self._whiten(np.asarray(x)) + + def colorize(self, x): + """ + Perform a colorizing transformation on data. + + "Colorizing" ("color" as in "colored noise", in which different + frequencies may have different magnitudes) transforms a set of + uncorrelated random variables into a new set of random variables with + the desired covariance. When a coloring transform is applied to a + sample of points distributed according to a multivariate normal + distribution with identity covariance and zero mean, the covariance of + the transformed sample is approximately the covariance matrix used + in the coloring transform. + + Parameters + ---------- + x : array_like + An array of points. The last dimension must correspond with the + dimensionality of the space, i.e., the number of columns in the + covariance matrix. + + Returns + ------- + x_ : array_like + The transformed array of points. + + References + ---------- + .. [1] "Whitening Transformation". Wikipedia. + https://en.wikipedia.org/wiki/Whitening_transformation + .. [2] Novak, Lukas, and Miroslav Vorechovsky. "Generalization of + coloring linear transformation". Transactions of VSB 18.2 + (2018): 31-35. :doi:`10.31490/tces-2018-0013` + + Examples + -------- + >>> import numpy as np + >>> from scipy import stats + >>> rng = np.random.default_rng(1638083107694713882823079058616272161) + >>> n = 3 + >>> A = rng.random(size=(n, n)) + >>> cov_array = A @ A.T # make matrix symmetric positive definite + >>> cholesky = np.linalg.cholesky(cov_array) + >>> cov_object = stats.Covariance.from_cholesky(cholesky) + >>> x = rng.multivariate_normal(np.zeros(n), np.eye(n), size=(10000)) + >>> x_ = cov_object.colorize(x) + >>> cov_data = np.cov(x_, rowvar=False) + >>> np.allclose(cov_data, cov_array, rtol=3e-2) + True + """ + return self._colorize(np.asarray(x)) + + @property + def log_pdet(self): + """ + Log of the pseudo-determinant of the covariance matrix + """ + return np.array(self._log_pdet, dtype=float)[()] + + @property + def rank(self): + """ + Rank of the covariance matrix + """ + return np.array(self._rank, dtype=int)[()] + + @property + def covariance(self): + """ + Explicit representation of the covariance matrix + """ + return self._covariance + + @property + def shape(self): + """ + Shape of the covariance array + """ + return self._shape + + def _validate_matrix(self, A, name): + A = np.atleast_2d(A) + m, n = A.shape[-2:] + if m != n or A.ndim != 2 or not (np.issubdtype(A.dtype, np.integer) or + np.issubdtype(A.dtype, np.floating)): + message = (f"The input `{name}` must be a square, " + "two-dimensional array of real numbers.") + raise ValueError(message) + return A + + def _validate_vector(self, A, name): + A = np.atleast_1d(A) + if A.ndim != 1 or not (np.issubdtype(A.dtype, np.integer) or + np.issubdtype(A.dtype, np.floating)): + message = (f"The input `{name}` must be a one-dimensional array " + "of real numbers.") + raise ValueError(message) + return A + + +class CovViaPrecision(Covariance): + + __class_getitem__ = None # type:ignore[assignment] + + def __init__(self, precision, covariance=None): + precision = self._validate_matrix(precision, 'precision') + if covariance is not None: + covariance = self._validate_matrix(covariance, 'covariance') + message = "`precision.shape` must equal `covariance.shape`." + if precision.shape != covariance.shape: + raise ValueError(message) + + self._chol_P = np.linalg.cholesky(precision) + self._log_pdet = -2*np.log(np.diag(self._chol_P)).sum(axis=-1) + self._rank = precision.shape[-1] # must be full rank if invertible + self._precision = precision + self._cov_matrix = covariance + self._shape = precision.shape + self._allow_singular = False + + def _whiten(self, x): + return x @ self._chol_P + + @cached_property + def _covariance(self): + n = self._shape[-1] + return (linalg.cho_solve((self._chol_P, True), np.eye(n)) + if self._cov_matrix is None else self._cov_matrix) + + def _colorize(self, x): + m = x.T.shape[0] + res = linalg.solve_triangular(self._chol_P.T, x.T.reshape(m, -1), lower=False) + return res.reshape(x.T.shape).T + + +def _dot_diag(x, d): + # If d were a full diagonal matrix, x @ d would always do what we want. + # Special treatment is needed for n-dimensional `d` in which each row + # includes only the diagonal elements of a covariance matrix. + return x * d if x.ndim < 2 else x * np.expand_dims(d, -2) + + +class CovViaDiagonal(Covariance): + + def __init__(self, diagonal): + diagonal = self._validate_vector(diagonal, 'diagonal') + + i_zero = diagonal <= 0 + positive_diagonal = np.array(diagonal, dtype=np.float64) + + positive_diagonal[i_zero] = 1 # ones don't affect determinant + self._log_pdet = np.sum(np.log(positive_diagonal), axis=-1) + + psuedo_reciprocals = 1 / np.sqrt(positive_diagonal) + psuedo_reciprocals[i_zero] = 0 + + self._sqrt_diagonal = np.sqrt(diagonal) + self._LP = psuedo_reciprocals + self._rank = positive_diagonal.shape[-1] - i_zero.sum(axis=-1) + self._covariance = np.apply_along_axis(np.diag, -1, diagonal) + self._i_zero = i_zero + self._shape = self._covariance.shape + self._allow_singular = True + + def _whiten(self, x): + return _dot_diag(x, self._LP) + + def _colorize(self, x): + return _dot_diag(x, self._sqrt_diagonal) + + def _support_mask(self, x): + """ + Check whether x lies in the support of the distribution. + """ + return ~np.any(_dot_diag(x, self._i_zero), axis=-1) + + +class CovViaCholesky(Covariance): + + __class_getitem__ = None # type:ignore[assignment] + + def __init__(self, cholesky): + L = self._validate_matrix(cholesky, 'cholesky') + + self._factor = L + self._log_pdet = 2*np.log(np.diag(self._factor)).sum(axis=-1) + self._rank = L.shape[-1] # must be full rank for cholesky + self._shape = L.shape + self._allow_singular = False + + @cached_property + def _covariance(self): + return self._factor @ self._factor.T + + def _whiten(self, x): + m = x.T.shape[0] + res = linalg.solve_triangular(self._factor, x.T.reshape(m, -1), lower=True) + return res.reshape(x.T.shape).T + + def _colorize(self, x): + return x @ self._factor.T + + +class CovViaEigendecomposition(Covariance): + + __class_getitem__ = None # type:ignore[assignment] + + def __init__(self, eigendecomposition): + eigenvalues, eigenvectors = eigendecomposition + eigenvalues = self._validate_vector(eigenvalues, 'eigenvalues') + eigenvectors = self._validate_matrix(eigenvectors, 'eigenvectors') + message = ("The shapes of `eigenvalues` and `eigenvectors` " + "must be compatible.") + try: + eigenvalues = np.expand_dims(eigenvalues, -2) + eigenvectors, eigenvalues = np.broadcast_arrays(eigenvectors, + eigenvalues) + eigenvalues = eigenvalues[..., 0, :] + except ValueError: + raise ValueError(message) + + i_zero = eigenvalues <= 0 + positive_eigenvalues = np.array(eigenvalues, dtype=np.float64) + + positive_eigenvalues[i_zero] = 1 # ones don't affect determinant + self._log_pdet = np.sum(np.log(positive_eigenvalues), axis=-1) + + psuedo_reciprocals = 1 / np.sqrt(positive_eigenvalues) + psuedo_reciprocals[i_zero] = 0 + + self._LP = eigenvectors * psuedo_reciprocals + self._LA = eigenvectors * np.sqrt(eigenvalues) + self._rank = positive_eigenvalues.shape[-1] - i_zero.sum(axis=-1) + self._w = eigenvalues + self._v = eigenvectors + self._shape = eigenvectors.shape + self._null_basis = eigenvectors * i_zero + # This is only used for `_support_mask`, not to decide whether + # the covariance is singular or not. + self._eps = _multivariate._eigvalsh_to_eps(eigenvalues) * 10**3 + self._allow_singular = True + + def _whiten(self, x): + return x @ self._LP + + def _colorize(self, x): + return x @ self._LA.T + + @cached_property + def _covariance(self): + return (self._v * self._w) @ self._v.T + + def _support_mask(self, x): + """ + Check whether x lies in the support of the distribution. + """ + residual = np.linalg.norm(x @ self._null_basis, axis=-1) + in_support = residual < self._eps + return in_support + + +class CovViaPSD(Covariance): + """ + Representation of a covariance provided via an instance of _PSD + """ + + __class_getitem__ = None # type:ignore[assignment] + + def __init__(self, psd): + self._LP = psd.U + self._log_pdet = psd.log_pdet + self._rank = psd.rank + self._covariance = psd._M + self._shape = psd._M.shape + self._psd = psd + self._allow_singular = False # by default + + def _whiten(self, x): + return x @ self._LP + + def _support_mask(self, x): + return self._psd._support_mask(x) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_crosstab.py b/venv/lib/python3.12/site-packages/scipy/stats/_crosstab.py new file mode 100644 index 0000000000000000000000000000000000000000..dd016a11627968c5fa8361c439c9c55a60a592c2 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_crosstab.py @@ -0,0 +1,198 @@ +import numpy as np +from scipy.sparse import coo_array +from scipy._lib._bunch import _make_tuple_bunch + + +CrosstabResult = _make_tuple_bunch( + "CrosstabResult", ["elements", "count"] +) + + +def crosstab(*args, levels=None, sparse=False): + """ + Return table of counts for each possible unique combination in ``*args``. + + When ``len(args) > 1``, the array computed by this function is + often referred to as a *contingency table* [1]_. + + The arguments must be sequences with the same length. The second return + value, `count`, is an integer array with ``len(args)`` dimensions. If + `levels` is None, the shape of `count` is ``(n0, n1, ...)``, where ``nk`` + is the number of unique elements in ``args[k]``. + + Parameters + ---------- + *args : sequences + A sequence of sequences whose unique aligned elements are to be + counted. The sequences in args must all be the same length. + levels : sequence, optional + If `levels` is given, it must be a sequence that is the same length as + `args`. Each element in `levels` is either a sequence or None. If it + is a sequence, it gives the values in the corresponding sequence in + `args` that are to be counted. If any value in the sequences in `args` + does not occur in the corresponding sequence in `levels`, that value + is ignored and not counted in the returned array `count`. The default + value of `levels` for ``args[i]`` is ``np.unique(args[i])`` + sparse : bool, optional + If True, return a sparse array. The array will be an instance of + the `scipy.sparse.coo_array` class. + Default is False. + + Returns + ------- + res : CrosstabResult + An object containing the following attributes: + + elements : tuple of numpy.ndarrays. + Tuple of length ``len(args)`` containing the arrays of elements + that are counted in `count`. These can be interpreted as the + labels of the corresponding dimensions of `count`. If `levels` was + given, then if ``levels[i]`` is not None, ``elements[i]`` will + hold the values given in ``levels[i]``. + count : numpy.ndarray or scipy.sparse.coo_array + Counts of the unique elements in ``zip(*args)``, stored in an + array. Also known as a *contingency table* when ``len(args) > 1``. + + See Also + -------- + numpy.unique + + Notes + ----- + .. versionadded:: 1.7.0 + + References + ---------- + .. [1] "Contingency table", http://en.wikipedia.org/wiki/Contingency_table + + Examples + -------- + >>> from scipy.stats.contingency import crosstab + + Given the lists `a` and `x`, create a contingency table that counts the + frequencies of the corresponding pairs. + + >>> a = ['A', 'B', 'A', 'A', 'B', 'B', 'A', 'A', 'B', 'B'] + >>> x = ['X', 'X', 'X', 'Y', 'Z', 'Z', 'Y', 'Y', 'Z', 'Z'] + >>> res = crosstab(a, x) + >>> avals, xvals = res.elements + >>> avals + array(['A', 'B'], dtype='>> xvals + array(['X', 'Y', 'Z'], dtype='>> res.count + array([[2, 3, 0], + [1, 0, 4]]) + + So ``('A', 'X')`` occurs twice, ``('A', 'Y')`` occurs three times, etc. + + Higher dimensional contingency tables can be created. + + >>> p = [0, 0, 0, 0, 1, 1, 1, 0, 0, 1] + >>> res = crosstab(a, x, p) + >>> res.count + array([[[2, 0], + [2, 1], + [0, 0]], + [[1, 0], + [0, 0], + [1, 3]]]) + >>> res.count.shape + (2, 3, 2) + + The values to be counted can be set by using the `levels` argument. + It allows the elements of interest in each input sequence to be + given explicitly instead finding the unique elements of the sequence. + + For example, suppose one of the arguments is an array containing the + answers to a survey question, with integer values 1 to 4. Even if the + value 1 does not occur in the data, we want an entry for it in the table. + + >>> q1 = [2, 3, 3, 2, 4, 4, 2, 3, 4, 4, 4, 3, 3, 3, 4] # 1 does not occur. + >>> q2 = [4, 4, 2, 2, 2, 4, 1, 1, 2, 2, 4, 2, 2, 2, 4] # 3 does not occur. + >>> options = [1, 2, 3, 4] + >>> res = crosstab(q1, q2, levels=(options, options)) + >>> res.count + array([[0, 0, 0, 0], + [1, 1, 0, 1], + [1, 4, 0, 1], + [0, 3, 0, 3]]) + + If `levels` is given, but an element of `levels` is None, the unique values + of the corresponding argument are used. For example, + + >>> res = crosstab(q1, q2, levels=(None, options)) + >>> res.elements + [array([2, 3, 4]), [1, 2, 3, 4]] + >>> res.count + array([[1, 1, 0, 1], + [1, 4, 0, 1], + [0, 3, 0, 3]]) + + If we want to ignore the pairs where 4 occurs in ``q2``, we can + give just the values [1, 2] to `levels`, and the 4 will be ignored: + + >>> res = crosstab(q1, q2, levels=(None, [1, 2])) + >>> res.elements + [array([2, 3, 4]), [1, 2]] + >>> res.count + array([[1, 1], + [1, 4], + [0, 3]]) + + Finally, let's repeat the first example, but return a sparse array: + + >>> res = crosstab(a, x, sparse=True) + >>> res.count + + >>> res.count.toarray() + array([[2, 3, 0], + [1, 0, 4]]) + + """ + nargs = len(args) + if nargs == 0: + raise TypeError("At least one input sequence is required.") + + len0 = len(args[0]) + if not all(len(a) == len0 for a in args[1:]): + raise ValueError("All input sequences must have the same length.") + + if levels is None: + # Call np.unique with return_inverse=True on each argument. + actual_levels, indices = zip(*[np.unique(a, return_inverse=True) + for a in args]) + else: + # `levels` is not None... + if len(levels) != nargs: + raise ValueError('len(levels) must equal the number of input ' + 'sequences') + + args = [np.asarray(arg) for arg in args] + mask = np.zeros((nargs, len0), dtype=np.bool_) + inv = np.zeros((nargs, len0), dtype=np.intp) + actual_levels = [] + for k, (levels_list, arg) in enumerate(zip(levels, args)): + if levels_list is None: + levels_list, inv[k, :] = np.unique(arg, return_inverse=True) + mask[k, :] = True + else: + q = arg == np.asarray(levels_list).reshape(-1, 1) + mask[k, :] = np.any(q, axis=0) + qnz = q.T.nonzero() + inv[k, qnz[0]] = qnz[1] + actual_levels.append(levels_list) + + mask_all = mask.all(axis=0) + indices = tuple(inv[:, mask_all]) + + if sparse: + count = coo_array((np.ones(len(indices[0]), dtype=int), indices)) + count.sum_duplicates() + else: + shape = [len(u) for u in actual_levels] + count = np.zeros(shape, dtype=int) + np.add.at(count, indices, 1) + + return CrosstabResult(actual_levels, count) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_discrete_distns.py b/venv/lib/python3.12/site-packages/scipy/stats/_discrete_distns.py new file mode 100644 index 0000000000000000000000000000000000000000..07b02cf7283c33fa1e9d133d7500dc99bff6df27 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_discrete_distns.py @@ -0,0 +1,2105 @@ +# +# Author: Travis Oliphant 2002-2011 with contributions from +# SciPy Developers 2004-2011 +# +from functools import partial +from types import MethodType + +from scipy import special +from scipy.special import entr, logsumexp, betaln, gammaln as gamln +import scipy.special._ufuncs as scu +from scipy.special._spfun_stats import _poisson_binom_pmf, _poisson_binom_cdf +from scipy._lib._util import rng_integers +import scipy._external.array_api_extra as xpx +from scipy.interpolate import interp1d + +from numpy import floor, ceil, log, exp, sqrt, log1p, expm1, tanh, cosh, sinh + +import numpy as np + +from ._distn_infrastructure import (rv_discrete, get_distribution_names, + _vectorize_rvs_over_shapes, + _ShapeInfo, _isintegral, + rv_discrete_frozen) +from ._biasedurn import (_PyFishersNCHypergeometric, + _PyWalleniusNCHypergeometric, + _PyStochasticLib3) + + +class binom_gen(rv_discrete): + r""" + A binomial discrete random variable. + + %(before_notes)s + + See Also + -------- + hypergeom, nbinom, nhypergeom + + Notes + ----- + The probability mass function for `binom` is: + + .. math:: + + f(k) = \binom{n}{k} p^k (1-p)^{n-k} + + for :math:`k \in \{0, 1, \dots, n\}`, :math:`0 \leq p \leq 1` + + `binom` takes :math:`n` and :math:`p` as shape parameters, + where :math:`p` is the probability of a single success + and :math:`1-p` is the probability of a single failure. + + This distribution uses routines from the Boost Math C++ library for + the computation of the ``pmf``, ``cdf``, ``sf``, ``ppf`` and ``isf`` + methods. [1]_ + + %(after_notes)s + + References + ---------- + .. [1] The Boost Developers. "Boost C++ Libraries". https://www.boost.org/. + + %(example)s + """ + def _shape_info(self): + return [_ShapeInfo("n", True, (0, np.inf), (True, False)), + _ShapeInfo("p", False, (0, 1), (True, True))] + + def _rvs(self, n, p, size=None, random_state=None): + if not np.all(n == np.floor(n)): + raise ValueError("`n` must be integral.") + return random_state.binomial(np.asarray(n, dtype=int), p, size) + + def _argcheck(self, n, p): + return (n >= 0) & _isintegral(n) & (p >= 0) & (p <= 1) + + def _get_support(self, n, p): + return self.a, n + + def _logpmf(self, x, n, p): + k = floor(x) + combiln = (gamln(n+1) - (gamln(k+1) + gamln(n-k+1))) + return combiln + special.xlogy(k, p) + special.xlog1py(n-k, -p) + + def _pmf(self, x, n, p): + # binom.pmf(k) = choose(n, k) * p**k * (1-p)**(n-k) + return scu._binom_pmf(x, n, p) + + def _cdf(self, x, n, p): + k = floor(x) + return scu._binom_cdf(k, n, p) + + def _sf(self, x, n, p): + k = floor(x) + return scu._binom_sf(k, n, p) + + def _isf(self, x, n, p): + return scu._binom_isf(x, n, p) + + def _ppf(self, q, n, p): + return scu._binom_ppf(q, n, p) + + def _stats(self, n, p, moments='mv'): + mu = n * p + var = mu - n * np.square(p) + g1, g2 = None, None + if 's' in moments: + pq = p - np.square(p) + npq_sqrt = np.sqrt(n * pq) + t1 = np.reciprocal(npq_sqrt) + t2 = (2.0 * p) / npq_sqrt + g1 = t1 - t2 + if 'k' in moments: + pq = p - np.square(p) + npq = n * pq + t1 = np.reciprocal(npq) + t2 = 6.0/n + g2 = t1 - t2 + return mu, var, g1, g2 + + +binom = binom_gen(name='binom') + + +class bernoulli_gen(binom_gen): + r"""A Bernoulli discrete random variable. + + %(before_notes)s + + Notes + ----- + The probability mass function for `bernoulli` is: + + .. math:: + + f(k) = \begin{cases}1-p &\text{if } k = 0\\ + p &\text{if } k = 1\end{cases} + + for :math:`k` in :math:`\{0, 1\}`, :math:`0 \leq p \leq 1` + + `bernoulli` takes :math:`p` as shape parameter, + where :math:`p` is the probability of a single success + and :math:`1-p` is the probability of a single failure. + + %(after_notes)s + + %(example)s + + """ + def _shape_info(self): + return [_ShapeInfo("p", False, (0, 1), (True, True))] + + def _rvs(self, p, size=None, random_state=None): + return binom_gen._rvs(self, 1, p, size=size, random_state=random_state) + + def _argcheck(self, p): + return (p >= 0) & (p <= 1) + + def _get_support(self, p): + # Overrides binom_gen._get_support!x + return self.a, self.b + + def _logpmf(self, x, p): + return binom._logpmf(x, 1, p) + + def _pmf(self, x, p): + # bernoulli.pmf(k) = 1-p if k = 0 + # = p if k = 1 + return binom._pmf(x, 1, p) + + def _cdf(self, x, p): + return binom._cdf(x, 1, p) + + def _sf(self, x, p): + return binom._sf(x, 1, p) + + def _isf(self, x, p): + return binom._isf(x, 1, p) + + def _ppf(self, q, p): + return binom._ppf(q, 1, p) + + def _stats(self, p): + return binom._stats(1, p) + + def _entropy(self, p): + return entr(p) + entr(1-p) + + +bernoulli = bernoulli_gen(b=1, name='bernoulli') + + +class betabinom_gen(rv_discrete): + r""" + A beta-binomial discrete random variable. + + %(before_notes)s + + See Also + -------- + beta, binom + + Notes + ----- + The beta-binomial distribution is a binomial distribution with a + probability of success `p` that follows a beta distribution. + + The probability mass function for `betabinom` is: + + .. math:: + + f(k) = \binom{n}{k} \frac{B(k + a, n - k + b)}{B(a, b)} + + for :math:`k \in \{0, 1, \dots, n\}`, :math:`n \geq 0`, :math:`a > 0`, + :math:`b > 0`, where :math:`B(a, b)` is the beta function. + + `betabinom` takes :math:`n`, :math:`a`, and :math:`b` as shape parameters. + + %(after_notes)s + + References + ---------- + .. [1] https://en.wikipedia.org/wiki/Beta-binomial_distribution + + .. versionadded:: 1.4.0 + + %(example)s + """ + def _shape_info(self): + return [_ShapeInfo("n", True, (0, np.inf), (True, False)), + _ShapeInfo("a", False, (0, np.inf), (False, False)), + _ShapeInfo("b", False, (0, np.inf), (False, False))] + + def _rvs(self, n, a, b, size=None, random_state=None): + p = random_state.beta(a, b, size) + if not np.all(n == np.floor(n)): + raise ValueError("`n` must be integral.") + return random_state.binomial(np.asarray(n, dtype=int), p, size) + + def _get_support(self, n, a, b): + return 0, n + + def _argcheck(self, n, a, b): + return (n >= 0) & _isintegral(n) & (a > 0) & (b > 0) + + def _logpmf(self, x, n, a, b): + k = floor(x) + combiln = -log(n + 1) - betaln(n - k + 1, k + 1) + return combiln + betaln(k + a, n - k + b) - betaln(a, b) + + def _pmf(self, x, n, a, b): + return exp(self._logpmf(x, n, a, b)) + + def _stats(self, n, a, b, moments='mv'): + e_p = a / (a + b) + e_q = 1 - e_p + mu = n * e_p + var = n * (a + b + n) * e_p * e_q / (a + b + 1) + g1, g2 = None, None + if 's' in moments: + g1 = 1.0 / sqrt(var) + g1 *= (a + b + 2 * n) * (b - a) + g1 /= (a + b + 2) * (a + b) + if 'k' in moments: + g2 = (a + b).astype(e_p.dtype) + g2 *= (a + b - 1 + 6 * n) + g2 += 3 * a * b * (n - 2) + g2 += 6 * n ** 2 + g2 -= 3 * e_p * b * n * (6 - n) + g2 -= 18 * e_p * e_q * n ** 2 + g2 *= (a + b) ** 2 * (1 + a + b) + g2 /= (n * a * b * (a + b + 2) * (a + b + 3) * (a + b + n)) + g2 -= 3 + return mu, var, g1, g2 + + +betabinom = betabinom_gen(name='betabinom') + + +class nbinom_gen(rv_discrete): + r""" + A negative binomial discrete random variable. + + %(before_notes)s + + See Also + -------- + hypergeom, binom, nhypergeom + + Notes + ----- + Negative binomial distribution describes a sequence of i.i.d. Bernoulli + trials, repeated until a predefined, non-random number of successes occurs. + + The probability mass function of the number of failures for `nbinom` is: + + .. math:: + + f(k) = \binom{k+n-1}{n-1} p^n (1-p)^k + + for :math:`k \ge 0`, :math:`0 < p \leq 1` + + `nbinom` takes :math:`n` and :math:`p` as shape parameters where :math:`n` + is the number of successes, :math:`p` is the probability of a single + success, and :math:`1-p` is the probability of a single failure. + + Another common parameterization of the negative binomial distribution is + in terms of the mean number of failures :math:`\mu` to achieve :math:`n` + successes. The mean :math:`\mu` is related to the probability of success + as + + .. math:: + + p = \frac{n}{n + \mu} + + The number of successes :math:`n` may also be specified in terms of a + "dispersion", "heterogeneity", or "aggregation" parameter :math:`\alpha`, + which relates the mean :math:`\mu` to the variance :math:`\sigma^2`, + e.g. :math:`\sigma^2 = \mu + \alpha \mu^2`. Regardless of the convention + used for :math:`\alpha`, + + .. math:: + + p &= \frac{\mu}{\sigma^2} \\ + n &= \frac{\mu^2}{\sigma^2 - \mu} + + This distribution uses routines from the Boost Math C++ library for + the computation of the ``pmf``, ``cdf``, ``sf``, ``ppf``, ``isf`` + and ``stats`` methods. [1]_ + + %(after_notes)s + + References + ---------- + .. [1] The Boost Developers. "Boost C++ Libraries". https://www.boost.org/. + + %(example)s + """ + def _shape_info(self): + return [_ShapeInfo("n", True, (0, np.inf), (True, False)), + _ShapeInfo("p", False, (0, 1), (True, True))] + + def _rvs(self, n, p, size=None, random_state=None): + return random_state.negative_binomial(n, p, size) + + def _argcheck(self, n, p): + return (n > 0) & (p > 0) & (p <= 1) + + def _pmf(self, x, n, p): + # nbinom.pmf(k) = choose(k+n-1, n-1) * p**n * (1-p)**k + return scu._nbinom_pmf(x, n, p) + + def _logpmf(self, x, n, p): + coeff = gamln(n+x) - gamln(x+1) - gamln(n) + return coeff + n*log(p) + special.xlog1py(x, -p) + + def _cdf(self, x, n, p): + k = floor(x) + return scu._nbinom_cdf(k, n, p) + + def _logcdf(self, x, n, p): + k = floor(x) + k, n, p = np.broadcast_arrays(k, n, p) + cdf = self._cdf(k, n, p) + cond = cdf > 0.5 + def f1(k, n, p): + return np.log1p(-special.betainc(k + 1, n, 1 - p)) + + # do calc in place + logcdf = cdf + with np.errstate(divide='ignore'): + logcdf[cond] = f1(k[cond], n[cond], p[cond]) + logcdf[~cond] = np.log(cdf[~cond]) + return logcdf + + def _sf(self, x, n, p): + k = floor(x) + return scu._nbinom_sf(k, n, p) + + def _isf(self, x, n, p): + with np.errstate(over='ignore'): # see gh-17432 + return scu._nbinom_isf(x, n, p) + + def _ppf(self, q, n, p): + with np.errstate(over='ignore'): # see gh-17432 + return scu._nbinom_ppf(q, n, p) + + def _stats(self, n, p): + return ( + scu._nbinom_mean(n, p), + scu._nbinom_variance(n, p), + scu._nbinom_skewness(n, p), + scu._nbinom_kurtosis_excess(n, p), + ) + + +nbinom = nbinom_gen(name='nbinom') + + +class betanbinom_gen(rv_discrete): + r""" + A beta-negative-binomial discrete random variable. + + %(before_notes)s + + See Also + -------- + betabinom : Beta binomial distribution + + Notes + ----- + The beta-negative-binomial distribution is a negative binomial + distribution with a probability of success `p` that follows a + beta distribution. + + The probability mass function for `betanbinom` is: + + .. math:: + + f(k) = \binom{n + k - 1}{k} \frac{B(a + n, b + k)}{B(a, b)} + + for :math:`k \ge 0`, :math:`n \geq 0`, :math:`a > 0`, + :math:`b > 0`, where :math:`B(a, b)` is the beta function. + + `betanbinom` takes :math:`n`, :math:`a`, and :math:`b` as shape parameters. + + %(after_notes)s + + References + ---------- + .. [1] https://en.wikipedia.org/wiki/Beta_negative_binomial_distribution + + .. versionadded:: 1.12.0 + + %(example)s + """ + def _shape_info(self): + return [_ShapeInfo("n", True, (0, np.inf), (True, False)), + _ShapeInfo("a", False, (0, np.inf), (False, False)), + _ShapeInfo("b", False, (0, np.inf), (False, False))] + + def _rvs(self, n, a, b, size=None, random_state=None): + p = random_state.beta(a, b, size) + return random_state.negative_binomial(n, p, size) + + def _argcheck(self, n, a, b): + return (n >= 0) & _isintegral(n) & (a > 0) & (b > 0) + + def _logpmf(self, x, n, a, b): + k = floor(x) + combiln = -np.log(n + k) - betaln(n, k + 1) + return combiln + betaln(a + n, b + k) - betaln(a, b) + + def _pmf(self, x, n, a, b): + return exp(self._logpmf(x, n, a, b)) + + def _stats(self, n, a, b, moments='mv'): + # reference: Wolfram Alpha input + # BetaNegativeBinomialDistribution[a, b, n] + def mean(n, a, b): + return n * b / (a - 1.) + mu = xpx.apply_where(a > 1, (n, a, b), mean, fill_value=np.inf) + def var(n, a, b): + return (n * b * (n + a - 1.) * (a + b - 1.) + / ((a - 2.) * (a - 1.)**2.)) + var = xpx.apply_where(a > 2, (n, a, b), var, fill_value=np.inf) + g1, g2 = None, None + def skew(n, a, b): + return ((2 * n + a - 1.) * (2 * b + a - 1.) + / (a - 3.) / sqrt(n * b * (n + a - 1.) * (b + a - 1.) + / (a - 2.))) + if 's' in moments: + g1 = xpx.apply_where(a > 3, (n, a, b), skew, fill_value=np.inf) + def kurtosis(n, a, b): + term = (a - 2.) + term_2 = ((a - 1.)**2. * (a**2. + a * (6 * b - 1.) + + 6. * (b - 1.) * b) + + 3. * n**2. * ((a + 5.) * b**2. + (a + 5.) + * (a - 1.) * b + 2. * (a - 1.)**2) + + 3 * (a - 1.) * n + * ((a + 5.) * b**2. + (a + 5.) * (a - 1.) * b + + 2. * (a - 1.)**2.)) + denominator = ((a - 4.) * (a - 3.) * b * n + * (a + b - 1.) * (a + n - 1.)) + # Wolfram Alpha uses Pearson kurtosis, so we subtract 3 to get + # scipy's Fisher kurtosis + return term * term_2 / denominator - 3. + if 'k' in moments: + g2 = xpx.apply_where(a > 4, (n, a, b), kurtosis, fill_value=np.inf) + return mu, var, g1, g2 + + +betanbinom = betanbinom_gen(name='betanbinom') + + +class geom_gen(rv_discrete): + r"""A geometric discrete random variable. + + %(before_notes)s + + See Also + -------- + planck + + Notes + ----- + The probability mass function for `geom` is: + + .. math:: + + f(k) = (1-p)^{k-1} p + + for :math:`k \ge 1`, :math:`0 < p \leq 1` + + `geom` takes :math:`p` as shape parameter, + where :math:`p` is the probability of a single success + and :math:`1-p` is the probability of a single failure. + + Note that when drawing random samples, the probability of observations that exceed + ``np.iinfo(np.int64).max`` increases rapidly as $p$ decreases below $10^{-17}$. For + $p < 10^{-20}$, almost all observations would exceed the maximum ``int64``; however, + the output dtype is always ``int64``, so these values are clipped to the maximum. + + %(after_notes)s + + %(example)s + """ + + def _shape_info(self): + return [_ShapeInfo("p", False, (0, 1), (True, True))] + + def _rvs(self, p, size=None, random_state=None): + res = random_state.geometric(p, size=size) + # RandomState.geometric can wrap around to negative values; make behavior + # consistent with Generator.geometric by replacing with maximum integer. + max_int = np.iinfo(res.dtype).max + return np.where(res < 0, max_int, res) + + def _argcheck(self, p): + return (p <= 1) & (p > 0) + + def _pmf(self, k, p): + return np.power(1-p, k-1) * p + + def _logpmf(self, k, p): + return special.xlog1py(k - 1, -p) + log(p) + + def _cdf(self, x, p): + k = floor(x) + return -expm1(log1p(-p)*k) + + def _sf(self, x, p): + return np.exp(self._logsf(x, p)) + + def _logsf(self, x, p): + k = floor(x) + return k*log1p(-p) + + def _ppf(self, q, p): + vals = ceil(log1p(-q) / log1p(-p)) + temp = self._cdf(vals-1, p) + return np.where((temp >= q) & (vals > 0), vals-1, vals) + + def _stats(self, p): + mu = 1.0/p + qr = 1.0-p + var = qr / p / p + g1 = (2.0-p) / sqrt(qr) + g2 = np.polyval([1, -6, 6], p)/(1.0-p) + return mu, var, g1, g2 + + def _entropy(self, p): + return -np.log(p) - np.log1p(-p) * (1.0-p) / p + + +geom = geom_gen(a=1, name='geom', longname="A geometric") + + +class hypergeom_gen(rv_discrete): + r"""A hypergeometric discrete random variable. + + The hypergeometric distribution models drawing objects from a bin. + `M` is the total number of objects, `n` is total number of Type I objects. + The random variate represents the number of Type I objects in `N` drawn + without replacement from the total population. + + %(before_notes)s + + See Also + -------- + nhypergeom, binom, nbinom + + Notes + ----- + The symbols used to denote the shape parameters (`M`, `n`, and `N`) are not + universally accepted. See the Examples for a clarification of the + definitions used here. + + The probability mass function is defined as, + + .. math:: p(k, M, n, N) = \frac{\binom{n}{k} \binom{M - n}{N - k}} + {\binom{M}{N}} + + for :math:`k \in [\max(0, N - M + n), \min(n, N)]`, where the binomial + coefficients are defined as, + + .. math:: \binom{n}{k} \equiv \frac{n!}{k! (n - k)!}. + + This distribution uses routines from the Boost Math C++ library for + the computation of the ``pmf``, ``cdf``, ``sf`` and ``stats`` methods. [1]_ + + %(after_notes)s + + References + ---------- + .. [1] The Boost Developers. "Boost C++ Libraries". https://www.boost.org/. + + Examples + -------- + >>> import numpy as np + >>> from scipy.stats import hypergeom + >>> import matplotlib.pyplot as plt + + Suppose we have a collection of 20 animals, of which 7 are dogs. Then if + we want to know the probability of finding a given number of dogs if we + choose at random 12 of the 20 animals, we can initialize a frozen + distribution and plot the probability mass function: + + >>> [M, n, N] = [20, 7, 12] + >>> rv = hypergeom(M, n, N) + >>> x = np.arange(0, n+1) + >>> pmf_dogs = rv.pmf(x) + + >>> fig = plt.figure() + >>> ax = fig.add_subplot(111) + >>> ax.plot(x, pmf_dogs, 'bo') + >>> ax.vlines(x, 0, pmf_dogs, lw=2) + >>> ax.set_xlabel('# of dogs in our group of chosen animals') + >>> ax.set_ylabel('hypergeom PMF') + >>> plt.show() + + Instead of using a frozen distribution we can also use `hypergeom` + methods directly. To for example obtain the cumulative distribution + function, use: + + >>> prb = hypergeom.cdf(x, M, n, N) + + And to generate random numbers: + + >>> R = hypergeom.rvs(M, n, N, size=10) + """ + def _shape_info(self): + return [_ShapeInfo("M", True, (0, np.inf), (True, False)), + _ShapeInfo("n", True, (0, np.inf), (True, False)), + _ShapeInfo("N", True, (0, np.inf), (True, False))] + + def _rvs(self, M, n, N, size=None, random_state=None): + return random_state.hypergeometric(n, M-n, N, size=size) + + def _get_support(self, M, n, N): + return np.maximum(N-(M-n), 0), np.minimum(n, N) + + def _argcheck(self, M, n, N): + cond = (M > 0) & (n >= 0) & (N >= 0) + cond &= (n <= M) & (N <= M) + cond &= _isintegral(M) & _isintegral(n) & _isintegral(N) + return cond + + def _logpmf(self, k, M, n, N): + tot, good = M, n + bad = tot - good + result = (betaln(good+1, 1) + betaln(bad+1, 1) + betaln(tot-N+1, N+1) - + betaln(k+1, good-k+1) - betaln(N-k+1, bad-N+k+1) - + betaln(tot+1, 1)) + return result + + def _pmf(self, k, M, n, N): + return scu._hypergeom_pmf(k, n, N, M) + + def _cdf(self, k, M, n, N): + return scu._hypergeom_cdf(k, n, N, M) + + def _stats(self, M, n, N): + M, n, N = 1. * M, 1. * n, 1. * N + m = M - n + + # Boost kurtosis_excess doesn't return the same as the value + # computed here. + g2 = M * (M + 1) - 6. * N * (M - N) - 6. * n * m + g2 *= (M - 1) * M * M + g2 += 6. * n * N * (M - N) * m * (5. * M - 6) + g2 /= n * N * (M - N) * m * (M - 2.) * (M - 3.) + return ( + scu._hypergeom_mean(n, N, M), + scu._hypergeom_variance(n, N, M), + scu._hypergeom_skewness(n, N, M), + g2, + ) + + def _entropy(self, M, n, N): + k = np.r_[N - (M - n):min(n, N) + 1] + vals = self.pmf(k, M, n, N) + return np.sum(entr(vals), axis=0) + + def _sf(self, k, M, n, N): + return scu._hypergeom_sf(k, n, N, M) + + def _logsf(self, k, M, n, N): + res = [] + for quant, tot, good, draw in zip(*np.broadcast_arrays(k, M, n, N)): + if (quant + 0.5) * (tot + 0.5) < (good - 0.5) * (draw - 0.5): + # Less terms to sum if we calculate log(1-cdf) + res.append(log1p(-exp(self.logcdf(quant, tot, good, draw)))) + else: + # Integration over probability mass function using logsumexp + k2 = np.arange(quant + 1, draw + 1) + res.append(logsumexp(self._logpmf(k2, tot, good, draw))) + return np.asarray(res) + + def _logcdf(self, k, M, n, N): + res = [] + for quant, tot, good, draw in zip(*np.broadcast_arrays(k, M, n, N)): + if (quant + 0.5) * (tot + 0.5) > (good - 0.5) * (draw - 0.5): + # Less terms to sum if we calculate log(1-sf) + res.append(log1p(-exp(self.logsf(quant, tot, good, draw)))) + else: + # Integration over probability mass function using logsumexp + k2 = np.arange(0, quant + 1) + res.append(logsumexp(self._logpmf(k2, tot, good, draw))) + return np.asarray(res) + + +hypergeom = hypergeom_gen(name='hypergeom') + + +class nhypergeom_gen(rv_discrete): + r"""A negative hypergeometric discrete random variable. + + Consider a box containing :math:`M` balls:, :math:`n` red and + :math:`M-n` blue. We randomly sample balls from the box, one + at a time and *without* replacement, until we have picked :math:`r` + blue balls. `nhypergeom` is the distribution of the number of + red balls :math:`k` we have picked. + + %(before_notes)s + + See Also + -------- + hypergeom, binom, nbinom + + Notes + ----- + The symbols used to denote the shape parameters (`M`, `n`, and `r`) are not + universally accepted. See the Examples for a clarification of the + definitions used here. + + The probability mass function is defined as, + + .. math:: f(k; M, n, r) = \frac{{{k+r-1}\choose{k}}{{M-r-k}\choose{n-k}}} + {{M \choose n}} + + for :math:`k \in [0, n]`, :math:`n \in [0, M]`, :math:`r \in [0, M-n]`, + and the binomial coefficient is: + + .. math:: \binom{n}{k} \equiv \frac{n!}{k! (n - k)!}. + + It is equivalent to observing :math:`k` successes in :math:`k+r-1` + samples with :math:`k+r`'th sample being a failure. The former + can be modelled as a hypergeometric distribution. The probability + of the latter is simply the number of failures remaining + :math:`M-n-(r-1)` divided by the size of the remaining population + :math:`M-(k+r-1)`. This relationship can be shown as: + + .. math:: NHG(k;M,n,r) = HG(k;M,n,k+r-1)\frac{(M-n-(r-1))}{(M-(k+r-1))} + + where :math:`NHG` is probability mass function (PMF) of the + negative hypergeometric distribution and :math:`HG` is the + PMF of the hypergeometric distribution. + + %(after_notes)s + + References + ---------- + .. [1] Negative Hypergeometric Distribution on Wikipedia + https://en.wikipedia.org/wiki/Negative_hypergeometric_distribution + + .. [2] Negative Hypergeometric Distribution from + http://www.math.wm.edu/~leemis/chart/UDR/PDFs/Negativehypergeometric.pdf + + Examples + -------- + >>> import numpy as np + >>> from scipy.stats import nhypergeom + >>> import matplotlib.pyplot as plt + + Suppose we have a collection of 20 animals, of which 7 are dogs. + Then if we want to know the probability of finding a given number + of dogs (successes) in a sample with exactly 12 animals that + aren't dogs (failures), we can initialize a frozen distribution + and plot the probability mass function: + + >>> M, n, r = [20, 7, 12] + >>> rv = nhypergeom(M, n, r) + >>> x = np.arange(0, n+2) + >>> pmf_dogs = rv.pmf(x) + + >>> fig = plt.figure() + >>> ax = fig.add_subplot(111) + >>> ax.plot(x, pmf_dogs, 'bo') + >>> ax.vlines(x, 0, pmf_dogs, lw=2) + >>> ax.set_xlabel('# of dogs in our group with given 12 failures') + >>> ax.set_ylabel('nhypergeom PMF') + >>> plt.show() + + Instead of using a frozen distribution we can also use `nhypergeom` + methods directly. To for example obtain the probability mass + function, use: + + >>> prb = nhypergeom.pmf(x, M, n, r) + + And to generate random numbers: + + >>> R = nhypergeom.rvs(M, n, r, size=10) + + To verify the relationship between `hypergeom` and `nhypergeom`, use: + + >>> from scipy.stats import hypergeom, nhypergeom + >>> M, n, r = 45, 13, 8 + >>> k = 6 + >>> nhypergeom.pmf(k, M, n, r) + 0.06180776620271643 + >>> hypergeom.pmf(k, M, n, k+r-1) * (M - n - (r-1)) / (M - (k+r-1)) + 0.06180776620271644 + """ + + def _shape_info(self): + return [_ShapeInfo("M", True, (0, np.inf), (True, False)), + _ShapeInfo("n", True, (0, np.inf), (True, False)), + _ShapeInfo("r", True, (0, np.inf), (True, False))] + + def _get_support(self, M, n, r): + return 0, n + + def _argcheck(self, M, n, r): + cond = (n >= 0) & (n <= M) & (r >= 0) & (r <= M-n) + cond &= _isintegral(M) & _isintegral(n) & _isintegral(r) + return cond + + def _rvs(self, M, n, r, size=None, random_state=None): + + @_vectorize_rvs_over_shapes + def _rvs1(M, n, r, size, random_state): + # invert cdf by calculating all values in support, scalar M, n, r + a, b = self.support(M, n, r) + ks = np.arange(a, b+1) + cdf = self.cdf(ks, M, n, r) + ppf = interp1d(cdf, ks, kind='next', fill_value='extrapolate') + rvs = ppf(random_state.uniform(size=size)).astype(int) + if size is None: + return rvs.item() + return rvs + + return _rvs1(M, n, r, size=size, random_state=random_state) + + def _logpmf(self, k, M, n, r): + return xpx.apply_where( + (r != 0) | (k != 0), (k, M, n, r), + lambda k, M, n, r: + (-betaln(k+1, r) + betaln(k+r, 1) + - betaln(n-k+1, M-r-n+1) + betaln(M-r-k+1, 1) + + betaln(n+1, M-n+1) - betaln(M+1, 1)), + fill_value=0.0) + + def _pmf(self, k, M, n, r): + # same as the following but numerically more precise + # return comb(k+r-1, k) * comb(M-r-k, n-k) / comb(M, n) + return exp(self._logpmf(k, M, n, r)) + + def _stats(self, M, n, r): + # Promote the datatype to at least float + # mu = rn / (M-n+1) + M, n, r = 1.*M, 1.*n, 1.*r + mu = r*n / (M-n+1) + + var = r*(M+1)*n / ((M-n+1)*(M-n+2)) * (1 - r / (M-n+1)) + + # The skew and kurtosis are mathematically + # intractable so return `None`. See [2]_. + g1, g2 = None, None + return mu, var, g1, g2 + + +nhypergeom = nhypergeom_gen(name='nhypergeom') + + +# FIXME: Fails _cdfvec +class logser_gen(rv_discrete): + r"""A Logarithmic (Log-Series, Series) discrete random variable. + + %(before_notes)s + + Notes + ----- + The probability mass function for `logser` is: + + .. math:: + + f(k) = - \frac{p^k}{k \log(1-p)} + + for :math:`k \ge 1`, :math:`0 < p < 1` + + `logser` takes :math:`p` as shape parameter, + where :math:`p` is the probability of a single success + and :math:`1-p` is the probability of a single failure. + + %(after_notes)s + + %(example)s + + """ + + def _shape_info(self): + return [_ShapeInfo("p", False, (0, 1), (True, True))] + + def _rvs(self, p, size=None, random_state=None): + # looks wrong for p>0.5, too few k=1 + # trying to use generic is worse, no k=1 at all + return random_state.logseries(p, size=size) + + def _argcheck(self, p): + return (p > 0) & (p < 1) + + def _pmf(self, k, p): + # logser.pmf(k) = - p**k / (k*log(1-p)) + return -np.power(p, k) * 1.0 / k / special.log1p(-p) + + def _sf(self, k, p): + tiny = 1e-100 + # Ideally, this is the unregularized beta function with `b=0`. We don't have + # an unregularized beta function (yet, although we could get it from Boost), + # and neither technically support `b=0` - despite the function being accurate + # for `b` super close to zero. See https://github.com/scipy/scipy/issues/3890. + return -special.betainc(k+1, tiny, p) * special.beta(k+1, tiny) / np.log1p(-p) + + def _stats(self, p): + r = special.log1p(-p) + mu = p / (p - 1.0) / r + mu2p = -p / r / (p - 1.0)**2 + var = mu2p - mu*mu + mu3p = -p / r * (1.0+p) / (1.0 - p)**3 + mu3 = mu3p - 3*mu*mu2p + 2*mu**3 + g1 = mu3 / np.power(var, 1.5) + + mu4p = -p / r * ( + 1.0 / (p-1)**2 - 6*p / (p - 1)**3 + 6*p*p / (p-1)**4) + mu4 = mu4p - 4*mu3p*mu + 6*mu2p*mu*mu - 3*mu**4 + g2 = mu4 / var**2 - 3.0 + return mu, var, g1, g2 + + +logser = logser_gen(a=1, name='logser', longname='A logarithmic') + + +class poisson_gen(rv_discrete): + r"""A Poisson discrete random variable. + + %(before_notes)s + + Notes + ----- + The probability mass function for `poisson` is: + + .. math:: + + f(k) = \exp(-\mu) \frac{\mu^k}{k!} + + for :math:`k \ge 0`. + + `poisson` takes :math:`\mu \geq 0` as shape parameter. + When :math:`\mu = 0`, the ``pmf`` method + returns ``1.0`` at quantile :math:`k = 0`. + + %(after_notes)s + + %(example)s + + """ + + def _shape_info(self): + return [_ShapeInfo("mu", False, (0, np.inf), (True, False))] + + # Override rv_discrete._argcheck to allow mu=0. + def _argcheck(self, mu): + return mu >= 0 + + def _rvs(self, mu, size=None, random_state=None): + return random_state.poisson(mu, size) + + def _logpmf(self, k, mu): + Pk = special.xlogy(k, mu) - gamln(k + 1) - mu + return Pk + + def _pmf(self, k, mu): + # poisson.pmf(k) = exp(-mu) * mu**k / k! + return exp(self._logpmf(k, mu)) + + def _cdf(self, x, mu): + k = floor(x) + return special.pdtr(k, mu) + + def _sf(self, x, mu): + k = floor(x) + return special.pdtrc(k, mu) + + def _ppf(self, q, mu): + vals = ceil(special.pdtrik(q, mu)) + vals1 = np.maximum(vals - 1, 0) + temp = special.pdtr(vals1, mu) + return np.where(temp >= q, vals1, vals) + + def _stats(self, mu): + var = mu + tmp = np.asarray(mu) + mu_nonzero = tmp > 0 + g1 = xpx.apply_where(mu_nonzero, tmp, lambda x: sqrt(1.0/x), fill_value=np.inf) + g2 = xpx.apply_where(mu_nonzero, tmp, lambda x: 1.0/x, fill_value=np.inf) + return mu, var, g1, g2 + + +poisson = poisson_gen(name="poisson", longname='A Poisson') + + +class planck_gen(rv_discrete): + r"""A Planck discrete exponential random variable. + + %(before_notes)s + + See Also + -------- + geom + + Notes + ----- + The probability mass function for `planck` is: + + .. math:: + + f(k) = (1-\exp(-\lambda)) \exp(-\lambda k) + + for :math:`k \ge 0` and :math:`\lambda > 0`. + + `planck` takes :math:`\lambda` as shape parameter. The Planck distribution + can be written as a geometric distribution (`geom`) with + :math:`p = 1 - \exp(-\lambda)` shifted by ``loc = -1``. + + %(after_notes)s + + %(example)s + """ + def _shape_info(self): + return [_ShapeInfo("lambda_", False, (0, np.inf), (False, False))] + + def _argcheck(self, lambda_): + return lambda_ > 0 + + def _pmf(self, k, lambda_): + return -expm1(-lambda_)*exp(-lambda_*k) + + def _cdf(self, x, lambda_): + k = floor(x) + return -expm1(-lambda_*(k+1)) + + def _sf(self, x, lambda_): + return exp(self._logsf(x, lambda_)) + + def _logsf(self, x, lambda_): + k = floor(x) + return -lambda_*(k+1) + + def _ppf(self, q, lambda_): + vals = ceil(-1.0/lambda_ * log1p(-q)-1) + vals1 = (vals-1).clip(*(self._get_support(lambda_))) + temp = self._cdf(vals1, lambda_) + return np.where(temp >= q, vals1, vals) + + def _rvs(self, lambda_, size=None, random_state=None): + # use relation to geometric distribution for sampling + p = -expm1(-lambda_) + return random_state.geometric(p, size=size) - 1.0 + + def _stats(self, lambda_): + mu = 1/expm1(lambda_) + var = exp(-lambda_)/(expm1(-lambda_))**2 + g1 = 2*cosh(lambda_/2.0) + g2 = 4+2*cosh(lambda_) + return mu, var, g1, g2 + + def _entropy(self, lambda_): + C = -expm1(-lambda_) + return lambda_*exp(-lambda_)/C - log(C) + + +planck = planck_gen(a=0, name='planck', longname='A discrete exponential ') + + +class boltzmann_gen(rv_discrete): + r"""A Boltzmann (Truncated Discrete Exponential) random variable. + + %(before_notes)s + + Notes + ----- + The probability mass function for `boltzmann` is: + + .. math:: + + f(k) = (1-\exp(-\lambda)) \exp(-\lambda k) / (1-\exp(-\lambda N)) + + for :math:`k = 0,..., N-1`. + + `boltzmann` takes :math:`\lambda > 0` and :math:`N > 0` as shape parameters. + + %(after_notes)s + + %(example)s + + """ + def _shape_info(self): + return [_ShapeInfo("lambda_", False, (0, np.inf), (False, False)), + _ShapeInfo("N", True, (0, np.inf), (False, False))] + + def _argcheck(self, lambda_, N): + return (lambda_ > 0) & (N > 0) & _isintegral(N) + + def _get_support(self, lambda_, N): + return self.a, N - 1 + + def _pmf(self, k, lambda_, N): + # boltzmann.pmf(k) = + # (1-exp(-lambda_)*exp(-lambda_*k)/(1-exp(-lambda_*N)) + fact = (1-exp(-lambda_))/(1-exp(-lambda_*N)) + return fact*exp(-lambda_*k) + + def _cdf(self, x, lambda_, N): + k = floor(x) + return (1-exp(-lambda_*(k+1)))/(1-exp(-lambda_*N)) + + def _ppf(self, q, lambda_, N): + qnew = q*(1-exp(-lambda_*N)) + vals = ceil(-1.0/lambda_ * log(1-qnew)-1) + vals1 = (vals-1).clip(0.0, np.inf) + temp = self._cdf(vals1, lambda_, N) + return np.where(temp >= q, vals1, vals) + + def _stats(self, lambda_, N): + z = exp(-lambda_) + zN = exp(-lambda_*N) + mu = z/(1.0-z)-N*zN/(1-zN) + var = z/(1.0-z)**2 - N*N*zN/(1-zN)**2 + trm = (1-zN)/(1-z) + trm2 = (z*trm**2 - N*N*zN) + g1 = z*(1+z)*trm**3 - N**3*zN*(1+zN) + g1 = g1 / trm2**(1.5) + g2 = z*(1+4*z+z*z)*trm**4 - N**4 * zN*(1+4*zN+zN*zN) + g2 = g2 / trm2 / trm2 + return mu, var, g1, g2 + + +boltzmann = boltzmann_gen(name='boltzmann', a=0, + longname='A truncated discrete exponential ') + + +class randint_gen(rv_discrete): + r"""A uniform discrete random variable. + + %(before_notes)s + + Notes + ----- + The probability mass function for `randint` is: + + .. math:: + + f(k) = \frac{1}{\texttt{high} - \texttt{low}} + + for :math:`k \in \{\texttt{low}, \dots, \texttt{high} - 1\}`. + + `randint` takes :math:`\texttt{low}` and :math:`\texttt{high}` as shape + parameters. + + %(after_notes)s + + Examples + -------- + >>> import numpy as np + >>> from scipy.stats import randint + >>> import matplotlib.pyplot as plt + >>> fig, ax = plt.subplots(1, 1) + + Calculate the first four moments: + + >>> low, high = 7, 31 + >>> mean, var, skew, kurt = randint.stats(low, high, moments='mvsk') + + Display the probability mass function (``pmf``): + + >>> x = np.arange(low - 5, high + 5) + >>> ax.plot(x, randint.pmf(x, low, high), 'bo', ms=8, label='randint pmf') + >>> ax.vlines(x, 0, randint.pmf(x, low, high), colors='b', lw=5, alpha=0.5) + + Alternatively, the distribution object can be called (as a function) to + fix the shape and location. This returns a "frozen" RV object holding the + given parameters fixed. + + Freeze the distribution and display the frozen ``pmf``: + + >>> rv = randint(low, high) + >>> ax.vlines(x, 0, rv.pmf(x), colors='k', linestyles='-', + ... lw=1, label='frozen pmf') + >>> ax.legend(loc='lower center') + >>> plt.show() + + Check the relationship between the cumulative distribution function + (``cdf``) and its inverse, the percent point function (``ppf``): + + >>> q = np.arange(low, high) + >>> p = randint.cdf(q, low, high) + >>> np.allclose(q, randint.ppf(p, low, high)) + True + + Generate random numbers: + + >>> r = randint.rvs(low, high, size=1000) + + """ + + def _shape_info(self): + return [_ShapeInfo("low", True, (-np.inf, np.inf), (False, False)), + _ShapeInfo("high", True, (-np.inf, np.inf), (False, False))] + + def _argcheck(self, low, high): + return (high > low) & _isintegral(low) & _isintegral(high) + + def _get_support(self, low, high): + return low, high-1 + + def _pmf(self, k, low, high): + # randint.pmf(k) = 1./(high - low) + p = np.ones_like(k) / (np.asarray(high, dtype=np.int64) - low) + return np.where((k >= low) & (k < high), p, 0.) + + def _cdf(self, x, low, high): + k = floor(x) + return (k - low + 1.) / (high - low) + + def _ppf(self, q, low, high): + vals = ceil(q * (high - low) + low) - 1 + vals1 = (vals - 1).clip(low, high) + temp = self._cdf(vals1, low, high) + return np.where(temp >= q, vals1, vals) + + def _stats(self, low, high): + m2, m1 = np.asarray(high), np.asarray(low) + mu = (m2 + m1 - 1.0) / 2 + d = m2 - m1 + var = (d*d - 1) / 12.0 + g1 = 0.0 + g2 = -6.0/5.0 * (d*d + 1.0) / (d*d - 1.0) + return mu, var, g1, g2 + + def _rvs(self, low, high, size=None, random_state=None): + """An array of *size* random integers >= ``low`` and < ``high``.""" + if np.asarray(low).size == 1 and np.asarray(high).size == 1: + # no need to vectorize in that case + return rng_integers(random_state, low, high, size=size) + + if size is not None: + # NumPy's RandomState.randint() doesn't broadcast its arguments. + # Use `broadcast_to()` to extend the shapes of low and high + # up to size. Then we can use the numpy.vectorize'd + # randint without needing to pass it a `size` argument. + low = np.broadcast_to(low, size) + high = np.broadcast_to(high, size) + randint = np.vectorize(partial(rng_integers, random_state), + otypes=[np.dtype(int)]) + return randint(low, high) + + def _entropy(self, low, high): + return log(high - low) + + +randint = randint_gen(name='randint', longname='A discrete uniform ' + '(random integer)') + + +# FIXME: problems sampling. +class zipf_gen(rv_discrete): + r"""A Zipf (Zeta) discrete random variable. + + %(before_notes)s + + See Also + -------- + zipfian + + Notes + ----- + The probability mass function for `zipf` is: + + .. math:: + + f(k, a) = \frac{1}{\zeta(a) k^a} + + for :math:`k \ge 1`, :math:`a > 1`. + + `zipf` takes :math:`a > 1` as shape parameter. :math:`\zeta` is the + Riemann zeta function (`scipy.special.zeta`) + + The Zipf distribution is also known as the zeta distribution, which is + a special case of the Zipfian distribution (`zipfian`). + + %(after_notes)s + + References + ---------- + .. [1] "Zeta Distribution", Wikipedia, + https://en.wikipedia.org/wiki/Zeta_distribution + + %(example)s + + Confirm that `zipf` is the large `n` limit of `zipfian`. + + >>> import numpy as np + >>> from scipy.stats import zipf, zipfian + >>> k = np.arange(11) + >>> np.allclose(zipf.pmf(k, a), zipfian.pmf(k, a, n=10000000)) + True + + """ + + def _shape_info(self): + return [_ShapeInfo("a", False, (1, np.inf), (False, False))] + + def _rvs(self, a, size=None, random_state=None): + return random_state.zipf(a, size=size) + + def _argcheck(self, a): + return a > 1 + + def _pmf(self, k, a): + k = k.astype(np.float64) + # zipf.pmf(k, a) = 1/(zeta(a) * k**a) + Pk = 1.0 / special.zeta(a, 1) * k**-a + return Pk + + def _munp(self, n, a): + return xpx.apply_where( + a > n + 1, (a, n), + lambda a, n: special.zeta(a - n, 1) / special.zeta(a, 1), + fill_value=np.inf) + + +zipf = zipf_gen(a=1, name='zipf', longname='A Zipf') + + +class zipfian_gen(rv_discrete): + r"""A Zipfian discrete random variable. + + %(before_notes)s + + See Also + -------- + zipf + + Notes + ----- + The probability mass function for `zipfian` is: + + .. math:: + + f(k, a, n) = \frac{1}{H_{n,a} k^a} + + for :math:`k \in \{1, 2, \dots, n-1, n\}`, :math:`a \ge 0`, + :math:`n \in \{1, 2, 3, \dots\}`. + + `zipfian` takes :math:`a` and :math:`n` as shape parameters. + :math:`H_{n,a}` is the :math:`n`:sup:`th` generalized harmonic + number of order :math:`a`. + + The SciPy implementation of this distribution requires :math:`1 \le n \le 2^{53}`. + For larger values of :math:`n`, the `zipfian` methods (`pmf`, `cdf`, `mean`, etc.) + will return `nan`. + + When :math:`a > 1`, the Zipfian distribution reduces to the Zipf (zeta) + distribution as :math:`n \rightarrow \infty`. + + %(after_notes)s + + References + ---------- + .. [1] "Zipf's Law", Wikipedia, https://en.wikipedia.org/wiki/Zipf's_law + .. [2] Larry Leemis, "Zipf Distribution", Univariate Distribution + Relationships. http://www.math.wm.edu/~leemis/chart/UDR/PDFs/Zipf.pdf + + %(example)s + + Confirm that `zipfian` reduces to `zipf` for large `n`, ``a > 1``. + + >>> import numpy as np + >>> from scipy.stats import zipf, zipfian + >>> k = np.arange(11) + >>> np.allclose(zipfian.pmf(k, a=3.5, n=10000000), zipf.pmf(k, a=3.5)) + True + + """ + + def _shape_info(self): + return [_ShapeInfo("a", False, (0, np.inf), (True, False)), + _ShapeInfo("n", True, (0, np.inf), (False, False))] + + def _argcheck(self, a, n): + # The upper bound on n is for practical numerical reasons. The numerical + # methods for computing the PMF, CDF and SF involve sums over range(1, n+1) + # when a <= 1, so there is no way they can be computed for extremely large + # n--even 2**53 is ridiculously large for those calculations. The bound is + # also required to ensure that the loops compute the sums correctly when + # the inputs are double precision instead of integers. + # + # n may be an integer or a float, but the value must be an integer in the + # range 1 <= n <= 2**53. The expression below clips n to the accepted range + # before attempting to cast to integer to avoid warnings generated by an + # input such as n=1e100. The extra `np.asarray()` wrapper avoids the error + # that arises with an input such as `n=2**100`. + # upper limit changed in gh-24119 because of overflow on windows, linux32 bit + return ((a >= 0) & + (n == np.asarray(np.clip(n, 1, 2**31 - 1)).astype(dtype=np.int64))) + + def _get_support(self, a, n): + return 1, np.floor(n) + + def _pmf(self, k, a, n): + k = np.floor(k) + n = np.floor(n) + return scu._normalized_gen_harmonic(k, k, n, a) + + def _cdf(self, k, a, n): + k = np.floor(k) + n = np.floor(n) + return scu._normalized_gen_harmonic(1, k, n, a) + + def _sf(self, k, a, n): + k = np.floor(k) + n = np.floor(n) + return scu._normalized_gen_harmonic(k + 1, n, n, a) + + def _stats(self, a, n): + n = np.floor(n) + # see http://www.math.wm.edu/~leemis/chart/UDR/PDFs/Zipf.pdf + Hna = scu._gen_harmonic(n, a) + Hna1 = scu._gen_harmonic(n, a-1) + Hna2 = scu._gen_harmonic(n, a-2) + Hna3 = scu._gen_harmonic(n, a-3) + Hna4 = scu._gen_harmonic(n, a-4) + mu1 = Hna1/Hna + mu2n = (Hna2*Hna - Hna1**2) + mu2d = Hna**2 + mu2 = mu2n / mu2d + g1 = (Hna3/Hna - 3*Hna1*Hna2/Hna**2 + 2*Hna1**3/Hna**3)/mu2**(3/2) + g2 = (Hna**3*Hna4 - 4*Hna**2*Hna1*Hna3 + 6*Hna*Hna1**2*Hna2 + - 3*Hna1**4) / mu2n**2 + g2 -= 3 + return mu1, mu2, g1, g2 + + +zipfian = zipfian_gen(a=1, name='zipfian', longname='A Zipfian') + + +class dlaplace_gen(rv_discrete): + r"""A Laplacian discrete random variable. + + %(before_notes)s + + Notes + ----- + The probability mass function for `dlaplace` is: + + .. math:: + + f(k) = \tanh(a/2) \exp(-a |k|) + + for integers :math:`k` and :math:`a > 0`. + + `dlaplace` takes :math:`a` as shape parameter. + + %(after_notes)s + + %(example)s + + """ + + def _shape_info(self): + return [_ShapeInfo("a", False, (0, np.inf), (False, False))] + + def _pmf(self, k, a): + # dlaplace.pmf(k) = tanh(a/2) * exp(-a*abs(k)) + return tanh(a/2.0) * exp(-a * abs(k)) + + def _cdf(self, x, a): + k = floor(x) + + def f1(k, a): + return 1.0 - exp(-a * k) / (exp(a) + 1) + + def f2(k, a): + return exp(a * (k + 1)) / (exp(a) + 1) + + return xpx.apply_where(k >= 0, (k, a), f1, f2) + + def _ppf(self, q, a): + const = 1 + exp(a) + vals = ceil(np.where(q < 1.0 / (1 + exp(-a)), + log(q*const) / a - 1, + -log((1-q) * const) / a)) + vals1 = vals - 1 + return np.where(self._cdf(vals1, a) >= q, vals1, vals) + + def _stats(self, a): + ea = exp(a) + mu2 = 2.*ea/(ea-1.)**2 + mu4 = 2.*ea*(ea**2+10.*ea+1.) / (ea-1.)**4 + return 0., mu2, 0., mu4/mu2**2 - 3. + + def _entropy(self, a): + return a / sinh(a) - log(tanh(a/2.0)) + + def _rvs(self, a, size=None, random_state=None): + # The discrete Laplace is equivalent to the two-sided geometric + # distribution with PMF: + # f(k) = (1 - alpha)/(1 + alpha) * alpha^abs(k) + # Reference: + # https://www.sciencedirect.com/science/ + # article/abs/pii/S0378375804003519 + # Furthermore, the two-sided geometric distribution is + # equivalent to the difference between two iid geometric + # distributions. + # Reference (page 179): + # https://pdfs.semanticscholar.org/61b3/ + # b99f466815808fd0d03f5d2791eea8b541a1.pdf + # Thus, we can leverage the following: + # 1) alpha = e^-a + # 2) probability_of_success = 1 - alpha (Bernoulli trial) + probOfSuccess = -np.expm1(-np.asarray(a)) + x = random_state.geometric(probOfSuccess, size=size) + y = random_state.geometric(probOfSuccess, size=size) + return x - y + + +dlaplace = dlaplace_gen(a=-np.inf, + name='dlaplace', longname='A discrete Laplacian') + + +class poisson_binom_gen(rv_discrete): + r"""A Poisson Binomial discrete random variable. + + %(before_notes)s + + See Also + -------- + binom + + Notes + ----- + The probability mass function for `poisson_binom` is: + + .. math:: + + f(k; p_1, p_2, ..., p_n) = \sum_{A \in F_k} \prod_{i \in A} p_i \prod_{j \in A^C} 1 - p_j + + where :math:`k \in \{0, 1, \dots, n-1, n\}`, :math:`F_k` is the set of all + subsets of :math:`k` integers that can be selected :math:`\{0, 1, \dots, n-1, n\}`, + and :math:`A^C` is the complement of a set :math:`A`. + + `poisson_binom` accepts a single array argument ``p`` for shape parameters + :math:`0 ≤ p_i ≤ 1`, where the last axis corresponds with the index :math:`i` and + any others are for batch dimensions. Broadcasting behaves according to the usual + rules except that the last axis of ``p`` is ignored. Instances of this class do + not support serialization/unserialization. + + %(after_notes)s + + References + ---------- + .. [1] "Poisson binomial distribution", Wikipedia, + https://en.wikipedia.org/wiki/Poisson_binomial_distribution + .. [2] Biscarri, William, Sihai Dave Zhao, and Robert J. Brunner. "A simple and + fast method for computing the Poisson binomial distribution function". + Computational Statistics & Data Analysis 122 (2018) 92-100. + :doi:`10.1016/j.csda.2018.01.007` + + %(example)s + + """ # noqa: E501 + + _parse_args_rvs: MethodType + _parse_args_stats: MethodType + _parse_args: MethodType + + def _shape_info(self): + # message = 'Fitting is not implemented for this distribution." + # raise NotImplementedError(message) + return [] + + def _argcheck(self, *args): + p = np.stack(args, axis=0) + conds = (0 <= p) & (p <= 1) + return np.all(conds, axis=0) + + def _rvs(self, *args, size=None, random_state=None): + # convenient to work along the last axis here to avoid interference with `size` + p = np.stack(args, axis=-1) + # Size passed by the user is the *shape of the returned array*, so it won't + # contain the length of the last axis of p. + size = (p.shape if size is None else + (size, 1) if np.isscalar(size) else tuple(size) + (1,)) + size = np.broadcast_shapes(p.shape, size) + return bernoulli._rvs(p, size=size, random_state=random_state).sum(axis=-1) + + def _get_support(self, *args): + return 0, len(args) + + def _pmf(self, k, *args): + k = np.atleast_1d(k).astype(np.int64) + k, *args = np.broadcast_arrays(k, *args) + p = np.stack(args, dtype=np.float64, axis=-1) + return _poisson_binom_pmf(k, p) + + def _cdf(self, k, *args): + k = np.atleast_1d(k).astype(np.int64) + k, *args = np.broadcast_arrays(k, *args) + p = np.stack(args, dtype=np.float64, axis=-1) + return _poisson_binom_cdf(k, p) + + def _stats(self, *args, **kwds): + p = np.stack(args, axis=0) + mean = np.sum(p, axis=0) + var = np.sum(p * (1-p), axis=0) + return (mean, var, None, None) + + def __call__(self, *args, **kwds): + return poisson_binomial_frozen(self, *args, **kwds) + + +poisson_binom = poisson_binom_gen(name='poisson_binom', longname='A Poisson binomial', + shapes='p') + +# The _parse_args methods don't work with vector-valued shape parameters, so we rewrite +# them. Note that `p` is accepted as an array with the index `i` of `p_i` corresponding +# with the last axis; we return it as a tuple (p_1, p_2, ..., p_n) so that it looks +# like `n` scalar (or arrays of scalar-valued) shape parameters to the infrastructure. + +def _parse_args_rvs(self, p, loc=0, size=None): + return tuple(np.moveaxis(p, -1, 0)), loc, 1.0, size + +def _parse_args_stats(self, p, loc=0, moments='mv'): + return tuple(np.moveaxis(p, -1, 0)), loc, 1.0, moments + +def _parse_args(self, p, loc=0): + return tuple(np.moveaxis(p, -1, 0)), loc, 1.0 + +# The infrastructure manually binds these methods to the instance, so +# we can only override them by manually binding them, too. +_pb_obj, _pb_cls = poisson_binom, poisson_binom_gen # shorter names (for PEP8) +poisson_binom._parse_args_rvs = _parse_args_rvs.__get__(_pb_obj, _pb_cls) +poisson_binom._parse_args_stats = _parse_args_stats.__get__(_pb_obj, _pb_cls) +poisson_binom._parse_args = _parse_args.__get__(_pb_obj, _pb_cls) + +class poisson_binomial_frozen(rv_discrete_frozen): + # copied from rv_frozen; we just need to bind the `_parse_args` methods + def __init__(self, dist, *args, **kwds): # verbatim + self.args = args # verbatim + self.kwds = kwds # verbatim + + # create a new instance # verbatim + self.dist = dist.__class__(**dist._updated_ctor_param()) # verbatim + + # Here is the only modification + self.dist._parse_args_rvs = _parse_args_rvs.__get__(_pb_obj, _pb_cls) + self.dist._parse_args_stats = _parse_args_stats.__get__(_pb_obj, _pb_cls) + self.dist._parse_args = _parse_args.__get__(_pb_obj, _pb_cls) + + shapes, _, _ = self.dist._parse_args(*args, **kwds) # verbatim + self.a, self.b = self.dist._get_support(*shapes) # verbatim + + def expect(self, func=None, lb=None, ub=None, conditional=False, **kwds): + a, loc, scale = self.dist._parse_args(*self.args, **self.kwds) + # Here's the modification: we pass all args (including `loc`) into the `args` + # parameter of `expect` so the shape only goes through `_parse_args` once. + return self.dist.expect(func, self.args, loc, lb, ub, conditional, **kwds) + + +class skellam_gen(rv_discrete): + r"""A Skellam discrete random variable. + + %(before_notes)s + + Notes + ----- + Probability distribution of the difference of two correlated or + uncorrelated Poisson random variables. + + Let :math:`k_1` and :math:`k_2` be two Poisson-distributed r.v. with + expected values :math:`\lambda_1` and :math:`\lambda_2`. Then, + :math:`k_1 - k_2` follows a Skellam distribution with parameters + :math:`\mu_1 = \lambda_1 - \rho \sqrt{\lambda_1 \lambda_2}` and + :math:`\mu_2 = \lambda_2 - \rho \sqrt{\lambda_1 \lambda_2}`, where + :math:`\rho` is the correlation coefficient between :math:`k_1` and + :math:`k_2`. If the two Poisson-distributed r.v. are independent then + :math:`\rho = 0`. + + Parameters :math:`\mu_1` and :math:`\mu_2` must be strictly positive. + + `skellam` takes :math:`\mu_1` and :math:`\mu_2` as shape parameters. + + %(after_notes)s + + References + ---------- + .. [1] Skellam, J. G. "The Frequency Distribution of the Difference + Between Two Poisson Variates Belonging to Different Populations." + *Journal of the Royal Statistical Society* 109, no. 3 (1946): 296-296. + :doi:`10.2307/2981372` + .. [2] "Skellam distribution", Wikipedia, + https://en.wikipedia.org/wiki/Skellam_distribution + + %(example)s + + """ + def _shape_info(self): + return [_ShapeInfo("mu1", False, (0, np.inf), (False, False)), + _ShapeInfo("mu2", False, (0, np.inf), (False, False))] + + def _rvs(self, mu1, mu2, size=None, random_state=None): + n = size + return (random_state.poisson(mu1, n) - + random_state.poisson(mu2, n)) + + def _pmf(self, x, mu1, mu2): + with np.errstate(over='ignore'): # see gh-17432 + px = np.where(x < 0, + scu._ncx2_pdf(2*mu2, 2*(1-x), 2*mu1)*2, + scu._ncx2_pdf(2*mu1, 2*(1+x), 2*mu2)*2) + # ncx2.pdf() returns nan's for extremely low probabilities + return px + + def _cdf(self, x, mu1, mu2): + x = floor(x) + with np.errstate(over='ignore'): # see gh-17432 + px = np.where(x < 0, + special.chndtr(2*mu2, -2*x, 2*mu1), + scu._ncx2_sf(2*mu1, 2*(x+1), 2*mu2)) + return px + + def _stats(self, mu1, mu2): + mean = mu1 - mu2 + var = mu1 + mu2 + g1 = mean / sqrt((var)**3) + g2 = 1 / var + return mean, var, g1, g2 + + +skellam = skellam_gen(a=-np.inf, name="skellam", longname='A Skellam') + + +class yulesimon_gen(rv_discrete): + r"""A Yule-Simon discrete random variable. + + %(before_notes)s + + Notes + ----- + + The probability mass function for the `yulesimon` is: + + .. math:: + + f(k) = \alpha B(k, \alpha+1) + + for :math:`k=1,2,3,...`, where :math:`\alpha>0`. + Here :math:`B` refers to the `scipy.special.beta` function. + + The sampling of random variates is based on pg 553, Section 6.3 of [1]_. + Our notation maps to the referenced logic via :math:`\alpha=a-1`. + + For details see the wikipedia entry [2]_. + + References + ---------- + .. [1] Devroye, Luc. "Non-uniform Random Variate Generation", + (1986) Springer, New York. + + .. [2] https://en.wikipedia.org/wiki/Yule-Simon_distribution + + %(after_notes)s + + %(example)s + + """ + def _shape_info(self): + return [_ShapeInfo("alpha", False, (0, np.inf), (False, False))] + + def _rvs(self, alpha, size=None, random_state=None): + E1 = random_state.standard_exponential(size) + E2 = random_state.standard_exponential(size) + ans = ceil(-E1 / log1p(-exp(-E2 / alpha))) + return ans + + def _pmf(self, x, alpha): + return alpha * special.beta(x, alpha + 1) + + def _argcheck(self, alpha): + return (alpha > 0) + + def _logpmf(self, x, alpha): + return log(alpha) + special.betaln(x, alpha + 1) + + def _cdf(self, x, alpha): + return 1 - x * special.beta(x, alpha + 1) + + def _sf(self, x, alpha): + return x * special.beta(x, alpha + 1) + + def _logsf(self, x, alpha): + return log(x) + special.betaln(x, alpha + 1) + + def _stats(self, alpha): + mu = np.where(alpha <= 1, np.inf, alpha / (alpha - 1)) + mu2 = np.where(alpha > 2, + alpha**2 / ((alpha - 2.0) * (alpha - 1)**2), + np.inf) + mu2 = np.where(alpha <= 1, np.nan, mu2) + g1 = np.where(alpha > 3, + sqrt(alpha - 2) * (alpha + 1)**2 / (alpha * (alpha - 3)), + np.inf) + g1 = np.where(alpha <= 2, np.nan, g1) + g2 = np.where(alpha > 4, + alpha + 3 + ((11 * alpha**3 - 49 * alpha - 22) / + (alpha * (alpha - 4) * (alpha - 3))), + np.inf) + g2 = np.where(alpha <= 2, np.nan, g2) + return mu, mu2, g1, g2 + + +yulesimon = yulesimon_gen(name='yulesimon', a=1) + + +class _nchypergeom_gen(rv_discrete): + r"""A noncentral hypergeometric discrete random variable. + + For subclassing by nchypergeom_fisher_gen and nchypergeom_wallenius_gen. + + """ + + rvs_name = None + dist = None + + def _shape_info(self): + return [_ShapeInfo("M", True, (0, np.inf), (True, False)), + _ShapeInfo("n", True, (0, np.inf), (True, False)), + _ShapeInfo("N", True, (0, np.inf), (True, False)), + _ShapeInfo("odds", False, (0, np.inf), (False, False))] + + def _get_support(self, M, n, N, odds): + N, m1, n = M, n, N # follow Wikipedia notation + m2 = N - m1 + x_min = np.maximum(0, n - m2) + x_max = np.minimum(n, m1) + return x_min, x_max + + def _argcheck(self, M, n, N, odds): + M, n = np.asarray(M), np.asarray(n), + N, odds = np.asarray(N), np.asarray(odds) + cond1 = (~np.isnan(M)) & (M.astype(int) == M) & (M >= 0) + cond2 = (~np.isnan(n)) & (n.astype(int) == n) & (n >= 0) + cond3 = (~np.isnan(N)) & (N.astype(int) == N) & (N >= 0) + cond4 = odds > 0 + cond5 = N <= M + cond6 = n <= M + return cond1 & cond2 & cond3 & cond4 & cond5 & cond6 + + def _rvs(self, M, n, N, odds, size=None, random_state=None): + + @_vectorize_rvs_over_shapes + def _rvs1(M, n, N, odds, size, random_state): + if np.isnan(M) | np.isnan(n) | np.isnan(N): + return np.full(size, np.nan) + length = np.prod(size) + urn = _PyStochasticLib3() + rv_gen = getattr(urn, self.rvs_name) + rvs = rv_gen(N, n, M, odds, length, random_state) + rvs = rvs.reshape(size) + return rvs + + return _rvs1(M, n, N, odds, size=size, random_state=random_state) + + def _pmf(self, x, M, n, N, odds): + + x, M, n, N, odds = np.broadcast_arrays(x, M, n, N, odds) + if x.size == 0: # np.vectorize doesn't work with zero size input + return np.empty_like(x) + + @np.vectorize + def _pmf1(x, M, n, N, odds): + if np.isnan(x) | np.isnan(M) | np.isnan(n) | np.isnan(N): + return np.nan + urn = self.dist(N, n, M, odds, 1e-12) + return urn.probability(x) + + return _pmf1(x, M, n, N, odds) + + def _stats(self, M, n, N, odds, moments='mv'): + + @np.vectorize + def _moments1(M, n, N, odds): + if np.isnan(M) | np.isnan(n) | np.isnan(N): + return np.nan, np.nan + urn = self.dist(N, n, M, odds, 1e-12) + return urn.moments() + + m, v = (_moments1(M, n, N, odds) if ("m" in moments or "v" in moments) + else (None, None)) + s, k = None, None + return m, v, s, k + + +class nchypergeom_fisher_gen(_nchypergeom_gen): + r"""A Fisher's noncentral hypergeometric discrete random variable. + + Fisher's noncentral hypergeometric distribution models drawing objects of + two types from a bin. `M` is the total number of objects, `n` is the + number of Type I objects, and `odds` is the odds ratio: the odds of + selecting a Type I object rather than a Type II object when there is only + one object of each type. + The random variate represents the number of Type I objects drawn if we + take a handful of objects from the bin at once and find out afterwards + that we took `N` objects. + + %(before_notes)s + + See Also + -------- + nchypergeom_wallenius, hypergeom, nhypergeom + + Notes + ----- + Let mathematical symbols :math:`N`, :math:`n`, and :math:`M` correspond + with parameters `N`, `n`, and `M` (respectively) as defined above. + + The probability mass function is defined as + + .. math:: + + p(x; M, n, N, \omega) = + \frac{\binom{n}{x}\binom{M - n}{N-x}\omega^x}{P_0}, + + for + :math:`x \in [x_l, x_u]`, + :math:`M \in {\mathbb N}`, + :math:`n \in [0, M]`, + :math:`N \in [0, M]`, + :math:`\omega > 0`, + where + :math:`x_l = \max(0, N - (M - n))`, + :math:`x_u = \min(N, n)`, + + .. math:: + + P_0 = \sum_{y=x_l}^{x_u} \binom{n}{y}\binom{M - n}{N-y}\omega^y, + + and the binomial coefficients are defined as + + .. math:: \binom{n}{k} \equiv \frac{n!}{k! (n - k)!}. + + `nchypergeom_fisher` uses the BiasedUrn package by Agner Fog with + permission for it to be distributed under SciPy's license. + + The symbols used to denote the shape parameters (`N`, `n`, and `M`) are not + universally accepted; they are chosen for consistency with `hypergeom`. + + Note that Fisher's noncentral hypergeometric distribution is distinct + from Wallenius' noncentral hypergeometric distribution, which models + drawing a pre-determined `N` objects from a bin one by one. + When the odds ratio is unity, however, both distributions reduce to the + ordinary hypergeometric distribution. + + %(after_notes)s + + References + ---------- + .. [1] Agner Fog, "Biased Urn Theory". + https://cran.r-project.org/web/packages/BiasedUrn/vignettes/UrnTheory.pdf + + .. [2] "Fisher's noncentral hypergeometric distribution", Wikipedia, + https://en.wikipedia.org/wiki/Fisher's_noncentral_hypergeometric_distribution + + %(example)s + + """ + + rvs_name = "rvs_fisher" + dist = _PyFishersNCHypergeometric + + +nchypergeom_fisher = nchypergeom_fisher_gen( + name='nchypergeom_fisher', + longname="A Fisher's noncentral hypergeometric") + + +class nchypergeom_wallenius_gen(_nchypergeom_gen): + r"""A Wallenius' noncentral hypergeometric discrete random variable. + + Wallenius' noncentral hypergeometric distribution models drawing objects of + two types from a bin. `M` is the total number of objects, `n` is the + number of Type I objects, and `odds` is the odds ratio: the odds of + selecting a Type I object rather than a Type II object when there is only + one object of each type. + The random variate represents the number of Type I objects drawn if we + draw a pre-determined `N` objects from a bin one by one. + + %(before_notes)s + + See Also + -------- + nchypergeom_fisher, hypergeom, nhypergeom + + Notes + ----- + Let mathematical symbols :math:`N`, :math:`n`, and :math:`M` correspond + with parameters `N`, `n`, and `M` (respectively) as defined above. + + The probability mass function is defined as + + .. math:: + + p(x; N, n, M) = \binom{n}{x} \binom{M - n}{N-x} + \int_0^1 \left(1-t^{\omega/D}\right)^x\left(1-t^{1/D}\right)^{N-x} dt + + for + :math:`x \in [x_l, x_u]`, + :math:`M \in {\mathbb N}`, + :math:`n \in [0, M]`, + :math:`N \in [0, M]`, + :math:`\omega > 0`, + where + :math:`x_l = \max(0, N - (M - n))`, + :math:`x_u = \min(N, n)`, + + .. math:: + + D = \omega(n - x) + ((M - n)-(N-x)), + + and the binomial coefficients are defined as + + .. math:: \binom{n}{k} \equiv \frac{n!}{k! (n - k)!}. + + `nchypergeom_wallenius` uses the BiasedUrn package by Agner Fog with + permission for it to be distributed under SciPy's license. + + The symbols used to denote the shape parameters (`N`, `n`, and `M`) are not + universally accepted; they are chosen for consistency with `hypergeom`. + + Note that Wallenius' noncentral hypergeometric distribution is distinct + from Fisher's noncentral hypergeometric distribution, which models + take a handful of objects from the bin at once, finding out afterwards + that `N` objects were taken. + When the odds ratio is unity, however, both distributions reduce to the + ordinary hypergeometric distribution. + + %(after_notes)s + + References + ---------- + .. [1] Agner Fog, "Biased Urn Theory". + https://cran.r-project.org/web/packages/BiasedUrn/vignettes/UrnTheory.pdf + + .. [2] "Wallenius' noncentral hypergeometric distribution", Wikipedia, + https://en.wikipedia.org/wiki/Wallenius'_noncentral_hypergeometric_distribution + + %(example)s + + """ + + rvs_name = "rvs_wallenius" + dist = _PyWalleniusNCHypergeometric + + +nchypergeom_wallenius = nchypergeom_wallenius_gen( + name='nchypergeom_wallenius', + longname="A Wallenius' noncentral hypergeometric") + + +# Collect names of classes and objects in this module. +pairs = list(globals().copy().items()) +_distn_names, _distn_gen_names = get_distribution_names(pairs, rv_discrete) + +__all__ = _distn_names + _distn_gen_names diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_distn_infrastructure.py b/venv/lib/python3.12/site-packages/scipy/stats/_distn_infrastructure.py new file mode 100644 index 0000000000000000000000000000000000000000..457b9ddb511d4dcba37e375a77c1a8a1dedd6554 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_distn_infrastructure.py @@ -0,0 +1,4274 @@ +# +# Author: Travis Oliphant 2002-2011 with contributions from +# SciPy Developers 2004-2011 +# +from scipy._lib._util import getfullargspec_no_self as _getfullargspec + +import sys +import keyword +import re +import types +import warnings +from itertools import zip_longest + +from scipy._lib import doccer +from scipy._lib._docscrape import FunctionDoc +from ._distr_params import distcont, distdiscrete +from scipy._lib._util import check_random_state +import scipy._external.array_api_extra as xpx + +from scipy.special import comb, entr + + +# for root finding for continuous distribution ppf, and maximum likelihood +# estimation +from scipy import optimize + +# for functions of continuous distributions (e.g. moments, entropy, cdf) +from scipy import integrate + +# to approximate the pdf of a continuous distribution given its cdf +from scipy.stats._finite_differences import _derivative + +# for scipy.stats.entropy. Attempts to import just that function or file +# have cause import problems +from scipy import stats + +from numpy import (arange, putmask, ones, shape, ndarray, zeros, floor, + logical_and, log, sqrt, place, argmax, vectorize, asarray, + nan, inf, isinf, empty) + +import numpy as np +from ._constants import _XMAX, _LOGXMAX +from ._censored_data import CensoredData +from scipy.stats._warnings_errors import FitError + +# These are the docstring parts used for substitution in specific +# distribution docstrings + +docheaders = {'methods': """\nMethods\n-------\n""", + 'notes': """\nNotes\n-----\n""", + 'examples': """\nExamples\n--------\n"""} + +_doc_rvs = """\ +rvs(%(shapes)s, loc=0, scale=1, size=1, random_state=None) + Random variates. +""" +_doc_pdf = """\ +pdf(x, %(shapes)s, loc=0, scale=1) + Probability density function. +""" +_doc_logpdf = """\ +logpdf(x, %(shapes)s, loc=0, scale=1) + Log of the probability density function. +""" +_doc_pmf = """\ +pmf(k, %(shapes)s, loc=0, scale=1) + Probability mass function. +""" +_doc_logpmf = """\ +logpmf(k, %(shapes)s, loc=0, scale=1) + Log of the probability mass function. +""" +_doc_cdf = """\ +cdf(x, %(shapes)s, loc=0, scale=1) + Cumulative distribution function. +""" +_doc_logcdf = """\ +logcdf(x, %(shapes)s, loc=0, scale=1) + Log of the cumulative distribution function. +""" +_doc_sf = """\ +sf(x, %(shapes)s, loc=0, scale=1) + Survival function (also defined as ``1 - cdf``, but `sf` is sometimes more accurate). +""" # noqa: E501 +_doc_logsf = """\ +logsf(x, %(shapes)s, loc=0, scale=1) + Log of the survival function. +""" +_doc_ppf = """\ +ppf(q, %(shapes)s, loc=0, scale=1) + Percent point function (inverse of ``cdf`` --- percentiles). +""" +_doc_isf = """\ +isf(q, %(shapes)s, loc=0, scale=1) + Inverse survival function (inverse of ``sf``). +""" +_doc_moment = """\ +moment(order, %(shapes)s, loc=0, scale=1) + Non-central moment of the specified order. +""" +_doc_stats = """\ +stats(%(shapes)s, loc=0, scale=1, moments='mv') + Mean('m'), variance('v'), skew('s'), and/or kurtosis('k'). +""" +_doc_entropy = """\ +entropy(%(shapes)s, loc=0, scale=1) + (Differential) entropy of the RV. +""" +_doc_fit = """\ +fit(data) + Parameter estimates for generic data. + See `scipy.stats.rv_continuous.fit `__ for detailed documentation of the + keyword arguments. +""" # noqa: E501 +_doc_expect = """\ +expect(func, args=(%(shapes_)s), loc=0, scale=1, lb=None, ub=None, conditional=False, **kwds) + Expected value of a function (of one argument) with respect to the distribution. +""" # noqa: E501 +_doc_expect_discrete = """\ +expect(func, args=(%(shapes_)s), loc=0, lb=None, ub=None, conditional=False) + Expected value of a function (of one argument) with respect to the distribution. +""" +_doc_median = """\ +median(%(shapes)s, loc=0, scale=1) + Median of the distribution. +""" +_doc_mean = """\ +mean(%(shapes)s, loc=0, scale=1) + Mean of the distribution. +""" +_doc_var = """\ +var(%(shapes)s, loc=0, scale=1) + Variance of the distribution. +""" +_doc_std = """\ +std(%(shapes)s, loc=0, scale=1) + Standard deviation of the distribution. +""" +_doc_interval = """\ +interval(confidence, %(shapes)s, loc=0, scale=1) + Confidence interval with equal areas around the median. +""" +_doc_allmethods = ''.join([docheaders['methods'], _doc_rvs, _doc_pdf, + _doc_logpdf, _doc_cdf, _doc_logcdf, _doc_sf, + _doc_logsf, _doc_ppf, _doc_isf, _doc_moment, + _doc_stats, _doc_entropy, _doc_fit, + _doc_expect, _doc_median, + _doc_mean, _doc_var, _doc_std, _doc_interval]) + +_doc_default_longsummary = """\ +As an instance of the `rv_continuous` class, `%(name)s` object inherits from it +a collection of generic methods (see below for the full list), +and completes them with details specific for this particular distribution. +""" + +_doc_default_frozen_note = """ +Alternatively, the object may be called (as a function) to fix the shape, +location, and scale parameters returning a "frozen" continuous RV object: + +rv = %(name)s(%(shapes)s, loc=0, scale=1) + - Frozen RV object with the same methods but holding the given shape, + location, and scale fixed. +""" +_doc_default_example = """\ +Examples +-------- +>>> import numpy as np +>>> from scipy.stats import %(name)s +>>> import matplotlib.pyplot as plt +>>> fig, ax = plt.subplots(1, 1) + +Get the support: + +%(set_vals_stmt)s +>>> lb, ub = %(name)s.support(%(shapes)s) + +Calculate the first four moments: + +>>> mean, var, skew, kurt = %(name)s.stats(%(shapes)s, moments='mvsk') + +Display the probability density function (``pdf``): + +>>> x = np.linspace(%(name)s.ppf(0.01, %(shapes)s), +... %(name)s.ppf(0.99, %(shapes)s), 100) +>>> ax.plot(x, %(name)s.pdf(x, %(shapes)s), +... 'r-', lw=5, alpha=0.6, label='%(name)s pdf') + +Alternatively, the distribution object can be called (as a function) +to fix the shape, location and scale parameters. This returns a "frozen" +RV object holding the given parameters fixed. + +Freeze the distribution and display the frozen ``pdf``: + +>>> rv = %(name)s(%(shapes)s) +>>> ax.plot(x, rv.pdf(x), 'k-', lw=2, label='frozen pdf') + +Check accuracy of ``cdf`` and ``ppf``: + +>>> vals = %(name)s.ppf([0.001, 0.5, 0.999], %(shapes)s) +>>> np.allclose([0.001, 0.5, 0.999], %(name)s.cdf(vals, %(shapes)s)) +True + +Generate random numbers: + +>>> r = %(name)s.rvs(%(shapes)s, size=1000) + +And compare the histogram: + +>>> ax.hist(r, density=True, bins='auto', histtype='stepfilled', alpha=0.2) +>>> ax.set_xlim([x[0], x[-1]]) +>>> ax.legend(loc='best', frameon=False) +>>> plt.show() + +""" + +_doc_default_locscale = """\ +The probability density above is defined in the "standardized" form. To shift +and/or scale the distribution use the ``loc`` and ``scale`` parameters. +Specifically, ``%(name)s.pdf(x, %(shapes)s, loc, scale)`` is identically +equivalent to ``%(name)s.pdf(y, %(shapes)s) / scale`` with +``y = (x - loc) / scale``. Note that shifting the location of a distribution +does not make it a "noncentral" distribution; noncentral generalizations of +some distributions are available in separate classes. +""" + +_doc_default = ''.join([_doc_default_longsummary, + _doc_allmethods, + '\n', + _doc_default_example]) + +_doc_default_before_notes = ''.join([_doc_default_longsummary, + _doc_allmethods]) + +docdict = { + 'rvs': _doc_rvs, + 'pdf': _doc_pdf, + 'logpdf': _doc_logpdf, + 'cdf': _doc_cdf, + 'logcdf': _doc_logcdf, + 'sf': _doc_sf, + 'logsf': _doc_logsf, + 'ppf': _doc_ppf, + 'isf': _doc_isf, + 'stats': _doc_stats, + 'entropy': _doc_entropy, + 'fit': _doc_fit, + 'moment': _doc_moment, + 'expect': _doc_expect, + 'interval': _doc_interval, + 'mean': _doc_mean, + 'std': _doc_std, + 'var': _doc_var, + 'median': _doc_median, + 'allmethods': _doc_allmethods, + 'longsummary': _doc_default_longsummary, + 'frozennote': _doc_default_frozen_note, + 'example': _doc_default_example, + 'default': _doc_default, + 'before_notes': _doc_default_before_notes, + 'after_notes': _doc_default_locscale +} + +# Reuse common content between continuous and discrete docs, change some +# minor bits. +docdict_discrete = docdict.copy() + +docdict_discrete['pmf'] = _doc_pmf +docdict_discrete['logpmf'] = _doc_logpmf +docdict_discrete['expect'] = _doc_expect_discrete +_doc_disc_methods = ['rvs', 'pmf', 'logpmf', 'cdf', 'logcdf', 'sf', 'logsf', + 'ppf', 'isf', 'stats', 'entropy', 'expect', 'median', + 'mean', 'var', 'std', 'interval'] +for obj in _doc_disc_methods: + docdict_discrete[obj] = docdict_discrete[obj].replace(', scale=1', '') + +_doc_disc_methods_err_varname = ['cdf', 'logcdf', 'sf', 'logsf'] +for obj in _doc_disc_methods_err_varname: + docdict_discrete[obj] = docdict_discrete[obj].replace('(x, ', '(k, ') + +docdict_discrete.pop('pdf') +docdict_discrete.pop('logpdf') + +_doc_allmethods = ''.join([docdict_discrete[obj] for obj in _doc_disc_methods]) +docdict_discrete['allmethods'] = docheaders['methods'] + _doc_allmethods + +docdict_discrete['longsummary'] = _doc_default_longsummary.replace( + 'rv_continuous', 'rv_discrete') + +_doc_default_frozen_note = """ +Alternatively, the object may be called (as a function) to fix the shape and +location parameters returning a "frozen" discrete RV object: + +rv = %(name)s(%(shapes)s, loc=0) + - Frozen RV object with the same methods but holding the given shape and + location fixed. +""" +docdict_discrete['frozennote'] = _doc_default_frozen_note + +_doc_default_discrete_example = """\ +Examples +-------- +>>> import numpy as np +>>> from scipy.stats import %(name)s +>>> import matplotlib.pyplot as plt +>>> fig, ax = plt.subplots(1, 1) + +Get the support: + +%(set_vals_stmt)s +>>> lb, ub = %(name)s.support(%(shapes)s) + +Calculate the first four moments: + +>>> mean, var, skew, kurt = %(name)s.stats(%(shapes)s, moments='mvsk') + +Display the probability mass function (``pmf``): + +>>> x = np.arange(%(name)s.ppf(0.01, %(shapes)s), +... %(name)s.ppf(0.99, %(shapes)s)) +>>> ax.plot(x, %(name)s.pmf(x, %(shapes)s), 'bo', ms=8, label='%(name)s pmf') +>>> ax.vlines(x, 0, %(name)s.pmf(x, %(shapes)s), colors='b', lw=5, alpha=0.5) + +Alternatively, the distribution object can be called (as a function) +to fix the shape and location. This returns a "frozen" RV object holding +the given parameters fixed. + +Freeze the distribution and display the frozen ``pmf``: + +>>> rv = %(name)s(%(shapes)s) +>>> ax.vlines(x, 0, rv.pmf(x), colors='k', linestyles='-', lw=1, +... label='frozen pmf') +>>> ax.legend(loc='best', frameon=False) +>>> plt.show() + +Check accuracy of ``cdf`` and ``ppf``: + +>>> prob = %(name)s.cdf(x, %(shapes)s) +>>> np.allclose(x, %(name)s.ppf(prob, %(shapes)s)) +True + +Generate random numbers: + +>>> r = %(name)s.rvs(%(shapes)s, size=1000) +""" + + +_doc_default_discrete_locscale = """\ +The probability mass function above is defined in the "standardized" form. +To shift distribution use the ``loc`` parameter. +Specifically, ``%(name)s.pmf(k, %(shapes)s, loc)`` is identically +equivalent to ``%(name)s.pmf(k - loc, %(shapes)s)``. +""" + +docdict_discrete['example'] = _doc_default_discrete_example +docdict_discrete['after_notes'] = _doc_default_discrete_locscale + +_doc_default_before_notes = ''.join([docdict_discrete['longsummary'], + docdict_discrete['allmethods']]) +docdict_discrete['before_notes'] = _doc_default_before_notes + +_doc_default_disc = ''.join([docdict_discrete['longsummary'], + docdict_discrete['allmethods'], + docdict_discrete['frozennote'], + docdict_discrete['example']]) +docdict_discrete['default'] = _doc_default_disc + +# clean up all the separate docstring elements, we do not need them anymore +for obj in [s for s in dir() if s.startswith('_doc_')]: + exec('del ' + obj) +del obj + + +def _moment(data, n, mu=None): + if mu is None: + mu = data.mean() + return ((data - mu)**n).mean() + + +def _moment_from_stats(n, mu, mu2, g1, g2, moment_func, args): + if (n == 0): + return 1.0 + elif (n == 1): + if mu is None: + val = moment_func(1, *args) + else: + val = mu + elif (n == 2): + if mu2 is None or mu is None: + val = moment_func(2, *args) + else: + val = mu2 + mu*mu + elif (n == 3): + if g1 is None or mu2 is None or mu is None: + val = moment_func(3, *args) + else: + mu3 = g1 * np.power(mu2, 1.5) # 3rd central moment + val = mu3+3*mu*mu2+mu*mu*mu # 3rd non-central moment + elif (n == 4): + if g1 is None or g2 is None or mu2 is None or mu is None: + val = moment_func(4, *args) + else: + mu4 = (g2+3.0)*(mu2**2.0) # 4th central moment + mu3 = g1*np.power(mu2, 1.5) # 3rd central moment + val = mu4+4*mu*mu3+6*mu*mu*mu2+mu*mu*mu*mu + else: + val = moment_func(n, *args) + + return val + + +def _skew(data): + """ + skew is third central moment / variance**(1.5) + """ + data = np.ravel(data) + mu = data.mean() + m2 = ((data - mu)**2).mean() + m3 = ((data - mu)**3).mean() + return m3 / np.power(m2, 1.5) + + +def _kurtosis(data): + """Fisher's excess kurtosis is fourth central moment / variance**2 - 3.""" + data = np.ravel(data) + mu = data.mean() + m2 = ((data - mu)**2).mean() + m4 = ((data - mu)**4).mean() + return m4 / m2**2 - 3 + +def _vectorize_rvs_over_shapes(_rvs1): + """Decorator that vectorizes _rvs method to work on ndarray shapes""" + # _rvs1 must be a _function_ that accepts _scalar_ args as positional + # arguments, `size` and `random_state` as keyword arguments. + # _rvs1 must return a random variate array with shape `size`. If `size` is + # None, _rvs1 must return a scalar. + # When applied to _rvs1, this decorator broadcasts ndarray args + # and loops over them, calling _rvs1 for each set of scalar args. + # For usage example, see _nchypergeom_gen + def _rvs(*args, size, random_state): + _rvs1_size, _rvs1_indices = _check_shape(args[0].shape, size) + + size = np.array(size) + _rvs1_size = np.array(_rvs1_size) + _rvs1_indices = np.array(_rvs1_indices) + + if np.all(_rvs1_indices): # all args are scalars + return _rvs1(*args, size, random_state) + + out = np.empty(size) + + # out.shape can mix dimensions associated with arg_shape and _rvs1_size + # Sort them to arg_shape + _rvs1_size for easy indexing of dimensions + # corresponding with the different sets of scalar args + j0 = np.arange(out.ndim) + j1 = np.hstack((j0[~_rvs1_indices], j0[_rvs1_indices])) + out = np.moveaxis(out, j1, j0) + + for i in np.ndindex(*size[~_rvs1_indices]): + # arg can be squeezed because singleton dimensions will be + # associated with _rvs1_size, not arg_shape per _check_shape + out[i] = _rvs1(*[np.squeeze(arg)[i] for arg in args], + _rvs1_size, random_state) + + return np.moveaxis(out, j0, j1) # move axes back before returning + return _rvs + + +def _fit_determine_optimizer(optimizer): + if not callable(optimizer) and isinstance(optimizer, str): + if not optimizer.startswith('fmin_'): + optimizer = "fmin_"+optimizer + if optimizer == 'fmin_': + optimizer = 'fmin' + try: + optimizer = getattr(optimize, optimizer) + except AttributeError as e: + raise ValueError(f"{optimizer} is not a valid optimizer") from e + return optimizer + +def _isintegral(x): + return x == np.round(x) + +def _sum_finite(x): + """ + For a 1D array x, return a tuple containing the sum of the + finite values of x and the number of nonfinite values. + + This is a utility function used when evaluating the negative + loglikelihood for a distribution and an array of samples. + + Examples + -------- + >>> import numpy as np + >>> from scipy.stats._distn_infrastructure import _sum_finite + >>> tot, nbad = _sum_finite(np.array([-2, -np.inf, 5, 1])) + >>> tot + 4.0 + >>> nbad + 1 + """ + finite_x = np.isfinite(x) + bad_count = finite_x.size - np.count_nonzero(finite_x) + return np.sum(x[finite_x]), bad_count + + +# Frozen RV class +class rv_frozen: + + # generic type compatibility with scipy-stubs + __class_getitem__: classmethod = classmethod(types.GenericAlias) + + def __init__(self, dist, *args, **kwds): + self.args = args + self.kwds = kwds + + # create a new instance + self.dist = dist.__class__(**dist._updated_ctor_param()) + + shapes, _, _ = self.dist._parse_args(*args, **kwds) + self.a, self.b = self.dist._get_support(*shapes) + + @property + def random_state(self): + return self.dist._random_state + + @random_state.setter + def random_state(self, seed): + self.dist._random_state = check_random_state(seed) + + def cdf(self, x): + return self.dist.cdf(x, *self.args, **self.kwds) + + def logcdf(self, x): + return self.dist.logcdf(x, *self.args, **self.kwds) + + def ppf(self, q): + return self.dist.ppf(q, *self.args, **self.kwds) + + def isf(self, q): + return self.dist.isf(q, *self.args, **self.kwds) + + def rvs(self, size=None, random_state=None): + kwds = self.kwds.copy() + kwds.update({'size': size, 'random_state': random_state}) + return self.dist.rvs(*self.args, **kwds) + + def sf(self, x): + return self.dist.sf(x, *self.args, **self.kwds) + + def logsf(self, x): + return self.dist.logsf(x, *self.args, **self.kwds) + + def stats(self, moments='mv'): + kwds = self.kwds.copy() + kwds.update({'moments': moments}) + return self.dist.stats(*self.args, **kwds) + + def median(self): + return self.dist.median(*self.args, **self.kwds) + + def mean(self): + return self.dist.mean(*self.args, **self.kwds) + + def var(self): + return self.dist.var(*self.args, **self.kwds) + + def std(self): + return self.dist.std(*self.args, **self.kwds) + + def moment(self, order=None): + return self.dist.moment(order, *self.args, **self.kwds) + + def entropy(self): + return self.dist.entropy(*self.args, **self.kwds) + + def interval(self, confidence=None): + return self.dist.interval(confidence, *self.args, **self.kwds) + + def expect(self, func=None, lb=None, ub=None, conditional=False, **kwds): + # expect method only accepts shape parameters as positional args + # hence convert self.args, self.kwds, also loc/scale + # See the .expect method docstrings for the meaning of + # other parameters. + a, loc, scale = self.dist._parse_args(*self.args, **self.kwds) + if isinstance(self.dist, rv_discrete): + return self.dist.expect(func, a, loc, lb, ub, conditional, **kwds) + else: + return self.dist.expect(func, a, loc, scale, lb, ub, + conditional, **kwds) + + def support(self): + return self.dist.support(*self.args, **self.kwds) + + +class rv_discrete_frozen(rv_frozen): + + def pmf(self, k): + return self.dist.pmf(k, *self.args, **self.kwds) + + def logpmf(self, k): # No error + return self.dist.logpmf(k, *self.args, **self.kwds) + + +class rv_continuous_frozen(rv_frozen): + + def pdf(self, x): + return self.dist.pdf(x, *self.args, **self.kwds) + + def logpdf(self, x): + return self.dist.logpdf(x, *self.args, **self.kwds) + + +def argsreduce(cond, *args): + """Clean arguments to: + + 1. Ensure all arguments are iterable (arrays of dimension at least one + 2. If cond != True and size > 1, ravel(args[i]) where ravel(condition) is + True, in 1D. + + Return list of processed arguments. + + Examples + -------- + >>> import numpy as np + >>> from scipy.stats._distn_infrastructure import argsreduce + >>> rng = np.random.default_rng() + >>> A = rng.random((4, 5)) + >>> B = 2 + >>> C = rng.random((1, 5)) + >>> cond = np.ones(A.shape) + >>> [A1, B1, C1] = argsreduce(cond, A, B, C) + >>> A1.shape + (4, 5) + >>> B1.shape + (1,) + >>> C1.shape + (1, 5) + >>> cond[2,:] = 0 + >>> [A1, B1, C1] = argsreduce(cond, A, B, C) + >>> A1.shape + (15,) + >>> B1.shape + (1,) + >>> C1.shape + (15,) + + """ + # some distributions assume arguments are iterable. + newargs = np.atleast_1d(*args) + + # np.atleast_1d returns an array if only one argument, or a list of arrays + # if more than one argument. + if not isinstance(newargs, (list | tuple)): + newargs = (newargs,) + + if np.all(cond): + # broadcast arrays with cond + *newargs, cond = np.broadcast_arrays(*newargs, cond) + return [arg.ravel() for arg in newargs] + + s = cond.shape + # np.extract returns flattened arrays, which are not broadcastable together + # unless they are either the same size or size == 1. + return [(arg if np.size(arg) == 1 + else np.extract(cond, np.broadcast_to(arg, s))) + for arg in newargs] + + +parse_arg_template = """ +def _parse_args(self, %(shape_arg_str)s %(locscale_in)s): + return (%(shape_arg_str)s), %(locscale_out)s + +def _parse_args_rvs(self, %(shape_arg_str)s %(locscale_in)s, size=None): + return self._argcheck_rvs(%(shape_arg_str)s %(locscale_out)s, size=size) + +def _parse_args_stats(self, %(shape_arg_str)s %(locscale_in)s, moments='mv'): + return (%(shape_arg_str)s), %(locscale_out)s, moments +""" + + +class rv_generic: + """Class which encapsulates common functionality between rv_discrete + and rv_continuous. + + """ + + def __init__(self, seed=None): + super().__init__() + + # figure out if _stats signature has 'moments' keyword + sig = _getfullargspec(self._stats) + self._stats_has_moments = ((sig.varkw is not None) or + ('moments' in sig.args) or + ('moments' in sig.kwonlyargs)) + self._random_state = check_random_state(seed) + + @property + def random_state(self): + """Get or set the generator object for generating random variates. + + If `random_state` is None (or `np.random`), the + `numpy.random.RandomState` singleton is used. + If `random_state` is an int, a new ``RandomState`` instance is used, + seeded with `random_state`. + If `random_state` is already a ``Generator`` or ``RandomState`` + instance, that instance is used. + + """ + return self._random_state + + @random_state.setter + def random_state(self, seed): + self._random_state = check_random_state(seed) + + def __setstate__(self, state): + try: + self.__dict__.update(state) + # attaches the dynamically created methods on each instance. + # if a subclass overrides rv_generic.__setstate__, or implements + # it's own _attach_methods, then it must make sure that + # _attach_argparser_methods is called. + self._attach_methods() + except ValueError: + # reconstitute an old pickle scipy<1.6, that contains + # (_ctor_param, random_state) as state + self._ctor_param = state[0] + self._random_state = state[1] + self.__init__() + + def _attach_methods(self): + """Attaches dynamically created methods to the rv_* instance. + + This method must be overridden by subclasses, and must itself call + _attach_argparser_methods. This method is called in __init__ in + subclasses, and in __setstate__ + """ + raise NotImplementedError + + def _attach_argparser_methods(self): + """ + Generates the argument-parsing functions dynamically and attaches + them to the instance. + + Should be called from `_attach_methods`, typically in __init__ and + during unpickling (__setstate__) + """ + ns = {} + exec(self._parse_arg_template, ns) + # NB: attach to the instance, not class + for name in ['_parse_args', '_parse_args_stats', '_parse_args_rvs']: + setattr(self, name, types.MethodType(ns[name], self)) + + def _construct_argparser( + self, meths_to_inspect, locscale_in, locscale_out): + """Construct the parser string for the shape arguments. + + This method should be called in __init__ of a class for each + distribution. It creates the `_parse_arg_template` attribute that is + then used by `_attach_argparser_methods` to dynamically create and + attach the `_parse_args`, `_parse_args_stats`, `_parse_args_rvs` + methods to the instance. + + If self.shapes is a non-empty string, interprets it as a + comma-separated list of shape parameters. + + Otherwise inspects the call signatures of `meths_to_inspect` + and constructs the argument-parsing functions from these. + In this case also sets `shapes` and `numargs`. + """ + + if self.shapes: + # sanitize the user-supplied shapes + if not isinstance(self.shapes, str): + raise TypeError('shapes must be a string.') + + shapes = self.shapes.replace(',', ' ').split() + + for field in shapes: + if keyword.iskeyword(field): + raise SyntaxError('keywords cannot be used as shapes.') + if not re.match('^[_a-zA-Z][_a-zA-Z0-9]*$', field): + raise SyntaxError( + 'shapes must be valid python identifiers') + else: + # find out the call signatures (_pdf, _cdf etc), deduce shape + # arguments. Generic methods only have 'self, x', any further args + # are shapes. + shapes_list = [] + for meth in meths_to_inspect: + shapes_args = _getfullargspec(meth) # NB does not contain self + args = shapes_args.args[1:] # peel off 'x', too + + if args: + shapes_list.append(args) + + # *args or **kwargs are not allowed w/automatic shapes + if shapes_args.varargs is not None: + raise TypeError( + '*args are not allowed w/out explicit shapes') + if shapes_args.varkw is not None: + raise TypeError( + '**kwds are not allowed w/out explicit shapes') + if shapes_args.kwonlyargs: + raise TypeError( + 'kwonly args are not allowed w/out explicit shapes') + if shapes_args.defaults is not None: + raise TypeError('defaults are not allowed for shapes') + + if shapes_list: + shapes = shapes_list[0] + + # make sure the signatures are consistent + for item in shapes_list: + if item != shapes: + raise TypeError('Shape arguments are inconsistent.') + else: + shapes = [] + + # have the arguments, construct the method from template + shapes_str = ', '.join(shapes) + ', ' if shapes else '' # NB: not None + dct = dict(shape_arg_str=shapes_str, + locscale_in=locscale_in, + locscale_out=locscale_out, + ) + + # this string is used by _attach_argparser_methods + self._parse_arg_template = parse_arg_template % dct + + self.shapes = ', '.join(shapes) if shapes else None + if not hasattr(self, 'numargs'): + # allows more general subclassing with *args + self.numargs = len(shapes) + + def _construct_doc(self, docdict, shapes_vals=None): + """Construct the instance docstring with string substitutions.""" + if sys.flags.optimize > 1: + # if run with -OO, docstrings are stripped + # see https://docs.python.org/3/using/cmdline.html#cmdoption-OO + return + tempdict = docdict.copy() + tempdict['name'] = self.name or 'distname' + tempdict['shapes'] = self.shapes or '' + + if shapes_vals is None: + shapes_vals = () + try: + vals = ', '.join(f'{val:.3g}' for val in shapes_vals) + except TypeError: + vals = ', '.join(f'{val}' for val in shapes_vals) + tempdict['vals'] = vals + + tempdict['shapes_'] = self.shapes or '' + if self.shapes and self.numargs == 1: + tempdict['shapes_'] += ',' + + if self.shapes: + tempdict['set_vals_stmt'] = f'>>> {self.shapes} = {vals}' + else: + tempdict['set_vals_stmt'] = '' + + if self.shapes is None: + # remove shapes from call parameters if there are none + for item in ['default', 'before_notes']: + tempdict[item] = tempdict[item].replace( + "\n%(shapes)s : array_like\n shape parameters", "") + for i in range(2): + if self.shapes is None: + # necessary because we use %(shapes)s in two forms (w w/o ", ") + self.__doc__ = self.__doc__.replace("%(shapes)s, ", "") + try: + self.__doc__ = doccer.docformat(self.__doc__, tempdict) + except TypeError as e: + raise Exception("Unable to construct docstring for " + f"distribution \"{self.name}\": {repr(e)}") from e + + # correct for empty shapes + self.__doc__ = self.__doc__.replace('(, ', '(').replace(', )', ')') + + def _construct_default_doc(self, longname=None, + docdict=None, discrete='continuous'): + """Construct instance docstring from the default template.""" + if sys.flags.optimize > 1: + # if run with -OO, docstrings are stripped + # see https://docs.python.org/3/using/cmdline.html#cmdoption-OO + return + if longname is None: + longname = 'A' + self.__doc__ = ''.join([f'{longname} {discrete} random variable.', + '\n\n%(before_notes)s\n', docheaders['notes'], + '\n%(example)s']) + self._construct_doc(docdict) + + def freeze(self, *args, **kwds): + """Freeze the distribution for the given arguments. + + Parameters + ---------- + arg1, arg2, arg3,... : array_like + The shape parameter(s) for the distribution. Should include all + the non-optional arguments, may include ``loc`` and ``scale``. + + Returns + ------- + rv_frozen : rv_frozen instance + The frozen distribution. + + """ + if isinstance(self, rv_continuous): + return rv_continuous_frozen(self, *args, **kwds) + else: + return rv_discrete_frozen(self, *args, **kwds) + + def __call__(self, *args, **kwds): + return self.freeze(*args, **kwds) + __call__.__doc__ = freeze.__doc__ + + # The actual calculation functions (no basic checking need be done) + # If these are defined, the others won't be looked at. + # Otherwise, the other set can be defined. + def _stats(self, *args, **kwds): + return None, None, None, None + + # Noncentral moments (also known as the moment about the origin). + # Expressed in LaTeX, munp would be $\mu'_{n}$, i.e. "mu-sub-n-prime". + # The primed mu is a widely used notation for the noncentral moment. + def _munp(self, n, *args): + # Silence floating point warnings from integration. + with np.errstate(all='ignore'): + vals = self.generic_moment(n, *args) + return vals + + def _argcheck_rvs(self, *args, **kwargs): + # Handle broadcasting and size validation of the rvs method. + # Subclasses should not have to override this method. + # The rule is that if `size` is not None, then `size` gives the + # shape of the result (integer values of `size` are treated as + # tuples with length 1; i.e. `size=3` is the same as `size=(3,)`.) + # + # `args` is expected to contain the shape parameters (if any), the + # location and the scale in a flat tuple (e.g. if there are two + # shape parameters `a` and `b`, `args` will be `(a, b, loc, scale)`). + # The only keyword argument expected is 'size'. + size = kwargs.get('size', None) + all_bcast = np.broadcast_arrays(*args) + + def squeeze_left(a): + while a.ndim > 0 and a.shape[0] == 1: + a = a[0] + return a + + # Eliminate trivial leading dimensions. In the convention + # used by numpy's random variate generators, trivial leading + # dimensions are effectively ignored. In other words, when `size` + # is given, trivial leading dimensions of the broadcast parameters + # in excess of the number of dimensions in size are ignored, e.g. + # >>> np.random.normal([[1, 3, 5]], [[[[0.01]]]], size=3) + # array([ 1.00104267, 3.00422496, 4.99799278]) + # If `size` is not given, the exact broadcast shape is preserved: + # >>> np.random.normal([[1, 3, 5]], [[[[0.01]]]]) + # array([[[[ 1.00862899, 3.00061431, 4.99867122]]]]) + # + all_bcast = [squeeze_left(a) for a in all_bcast] + bcast_shape = all_bcast[0].shape + bcast_ndim = all_bcast[0].ndim + + if size is None: + size_ = bcast_shape + else: + size_ = tuple(np.atleast_1d(size)) + + # Check compatibility of size_ with the broadcast shape of all + # the parameters. This check is intended to be consistent with + # how the numpy random variate generators (e.g. np.random.normal, + # np.random.beta) handle their arguments. The rule is that, if size + # is given, it determines the shape of the output. Broadcasting + # can't change the output size. + + # This is the standard broadcasting convention of extending the + # shape with fewer dimensions with enough dimensions of length 1 + # so that the two shapes have the same number of dimensions. + ndiff = bcast_ndim - len(size_) + if ndiff < 0: + bcast_shape = (1,)*(-ndiff) + bcast_shape + elif ndiff > 0: + size_ = (1,)*ndiff + size_ + + # This compatibility test is not standard. In "regular" broadcasting, + # two shapes are compatible if for each dimension, the lengths are the + # same or one of the lengths is 1. Here, the length of a dimension in + # size_ must not be less than the corresponding length in bcast_shape. + ok = all([bcdim == 1 or bcdim == szdim + for (bcdim, szdim) in zip(bcast_shape, size_)]) + if not ok: + raise ValueError("size does not match the broadcast shape of " + f"the parameters. {size}, {size_}, {bcast_shape}") + + param_bcast = all_bcast[:-2] + loc_bcast = all_bcast[-2] + scale_bcast = all_bcast[-1] + + return param_bcast, loc_bcast, scale_bcast, size_ + + # These are the methods you must define (standard form functions) + # NB: generic _pdf, _logpdf, _cdf are different for + # rv_continuous and rv_discrete hence are defined in there + def _argcheck(self, *args): + """Default check for correct values on args and keywords. + + Returns condition array of 1's where arguments are correct and + 0's where they are not. + + """ + cond = 1 + for arg in args: + cond = logical_and(cond, (asarray(arg) > 0)) + return cond + + def _get_support(self, *args, **kwargs): + """Return the support of the (unscaled, unshifted) distribution. + + *Must* be overridden by distributions which have support dependent + upon the shape parameters of the distribution. Any such override + *must not* set or change any of the class members, as these members + are shared amongst all instances of the distribution. + + Parameters + ---------- + arg1, arg2, ... : array_like + The shape parameter(s) for the distribution (see docstring of the + instance object for more information). + + Returns + ------- + a, b : numeric (float, or int or +/-np.inf) + end-points of the distribution's support for the specified + shape parameters. + """ + return self.a, self.b + + def _support_mask(self, x, *args): + a, b = self._get_support(*args) + with np.errstate(invalid='ignore'): + return (a <= x) & (x <= b) + + def _open_support_mask(self, x, *args): + a, b = self._get_support(*args) + with np.errstate(invalid='ignore'): + return (a < x) & (x < b) + + def _rvs(self, *args, size=None, random_state=None): + # This method must handle size being a tuple, and it must + # properly broadcast *args and size. size might be + # an empty tuple, which means a scalar random variate is to be + # generated. + + # Use basic inverse cdf algorithm for RV generation as default. + U = random_state.uniform(size=size) + Y = self._ppf(U, *args) + return Y + + def _logcdf(self, x, *args): + with np.errstate(divide='ignore'): + return log(self._cdf(x, *args)) + + def _sf(self, x, *args): + return 1.0-self._cdf(x, *args) + + def _logsf(self, x, *args): + with np.errstate(divide='ignore'): + return log(self._sf(x, *args)) + + def _ppf(self, q, *args): + return self._ppfvec(q, *args) + + def _isf(self, q, *args): + return self._ppf(1.0-q, *args) # use correct _ppf for subclasses + + # These are actually called, and should not be overwritten if you + # want to keep error checking. + def rvs(self, *args, **kwds): + """Random variates of given type. + + Parameters + ---------- + arg1, arg2, arg3,... : array_like + The shape parameter(s) for the distribution (see docstring of the + instance object for more information). + loc : array_like, optional + Location parameter (default=0). + scale : array_like, optional + Scale parameter (default=1). + size : int or tuple of ints, optional + Defining number of random variates (default is 1). + random_state : {None, int, `numpy.random.Generator`, + `numpy.random.RandomState`}, optional + + If `random_state` is None (or `np.random`), the + `numpy.random.RandomState` singleton is used. + If `random_state` is an int, a new ``RandomState`` instance is + used, seeded with `random_state`. + If `random_state` is already a ``Generator`` or ``RandomState`` + instance, that instance is used. + + Returns + ------- + rvs : ndarray or scalar + Random variates of given `size`. + + """ + discrete = kwds.pop('discrete', None) + rndm = kwds.pop('random_state', None) + args, loc, scale, size = self._parse_args_rvs(*args, **kwds) + cond = logical_and(self._argcheck(*args), (scale >= 0)) + if not np.all(cond): + message = ("Domain error in arguments. The `scale` parameter must " + "be positive for all distributions, and many " + "distributions have restrictions on shape parameters. " + f"Please see the `scipy.stats.{self.name}` " + "documentation for details.") + raise ValueError(message) + + if np.all(scale == 0): + return loc*ones(size, 'd') + + # extra gymnastics needed for a custom random_state + if rndm is not None: + random_state_saved = self._random_state + random_state = check_random_state(rndm) + else: + random_state = self._random_state + + vals = self._rvs(*args, size=size, random_state=random_state) + + vals = vals * scale + loc + + # do not forget to restore the _random_state + if rndm is not None: + self._random_state = random_state_saved + + # Cast to int if discrete + if discrete and not isinstance(self, rv_sample): + if size == (): + vals = int(vals) + else: + vals = vals.astype(np.int64) + + return vals + + def stats(self, *args, **kwds): + """Some statistics of the given RV. + + Parameters + ---------- + arg1, arg2, arg3,... : array_like + The shape parameter(s) for the distribution (see docstring of the + instance object for more information) + loc : array_like, optional + location parameter (default=0) + scale : array_like, optional + scale parameter (default=1) + moments : str, optional + composed of letters ['mvsk'] defining which moments to compute: + 'm' = mean, + 'v' = variance, + 's' = (Fisher's) skew, + 'k' = (Fisher's) kurtosis. + (default is 'mv') + + Returns + ------- + stats : sequence + of requested moments. + + """ + args, loc, scale, moments = self._parse_args_stats(*args, **kwds) + # scale = 1 by construction for discrete RVs + loc, scale = map(asarray, (loc, scale)) + args = tuple(map(asarray, args)) + cond = self._argcheck(*args) & (scale > 0) & (loc == loc) + output = [] + default = np.full(shape(cond), fill_value=self.badvalue) + + # Use only entries that are valid in calculation + if np.any(cond): + goodargs = argsreduce(cond, *(args+(scale, loc))) + scale, loc, goodargs = goodargs[-2], goodargs[-1], goodargs[:-2] + + if self._stats_has_moments: + mu, mu2, g1, g2 = self._stats(*goodargs, + **{'moments': moments}) + else: + mu, mu2, g1, g2 = self._stats(*goodargs) + + if 'm' in moments: + if mu is None: + mu = self._munp(1, *goodargs) + out0 = default.copy() + place(out0, cond, mu * scale + loc) + output.append(out0) + + if 'v' in moments: + if mu2 is None: + mu2p = self._munp(2, *goodargs) + if mu is None: + mu = self._munp(1, *goodargs) + # if mean is inf then var is also inf + with np.errstate(invalid='ignore'): + mu2 = np.where(~np.isinf(mu), mu2p - mu**2, np.inf) + out0 = default.copy() + place(out0, cond, mu2 * scale * scale) + output.append(out0) + + if 's' in moments: + if g1 is None: + mu3p = self._munp(3, *goodargs) + if mu is None: + mu = self._munp(1, *goodargs) + if mu2 is None: + mu2p = self._munp(2, *goodargs) + with np.errstate(invalid='ignore'): + mu2 = mu2p - mu * mu + with np.errstate(invalid='ignore'): + mu3 = (-mu*mu - 3*mu2)*mu + mu3p + g1 = mu3 / np.power(mu2, 1.5) + out0 = default.copy() + place(out0, cond, g1) + output.append(out0) + + if 'k' in moments: + if g2 is None: + mu4p = self._munp(4, *goodargs) + if mu is None: + mu = self._munp(1, *goodargs) + if mu2 is None: + mu2p = self._munp(2, *goodargs) + with np.errstate(invalid='ignore'): + mu2 = mu2p - mu * mu + if g1 is None: + mu3 = None + else: + # (mu2**1.5) breaks down for nan and inf + mu3 = g1 * np.power(mu2, 1.5) + if mu3 is None: + mu3p = self._munp(3, *goodargs) + with np.errstate(invalid='ignore'): + mu3 = (-mu * mu - 3 * mu2) * mu + mu3p + with np.errstate(invalid='ignore'): + mu4 = ((-mu**2 - 6*mu2) * mu - 4*mu3)*mu + mu4p + g2 = mu4 / mu2**2.0 - 3.0 + out0 = default.copy() + place(out0, cond, g2) + output.append(out0) + else: # no valid args + output = [default.copy() for _ in moments] + + output = [out[()] for out in output] + if len(output) == 1: + return output[0] + else: + return tuple(output) + + def entropy(self, *args, **kwds): + """Differential entropy of the RV. + + Parameters + ---------- + arg1, arg2, arg3,... : array_like + The shape parameter(s) for the distribution (see docstring of the + instance object for more information). + loc : array_like, optional + Location parameter (default=0). + scale : array_like, optional + Scale parameter (default=1). + + Notes + ----- + Entropy is defined base `e`: + + >>> import numpy as np + >>> from scipy.stats._distn_infrastructure import rv_discrete + >>> drv = rv_discrete(values=((0, 1), (0.5, 0.5))) + >>> np.allclose(drv.entropy(), np.log(2.0)) + True + + """ + args, loc, scale = self._parse_args(*args, **kwds) + # NB: for discrete distributions scale=1 by construction in _parse_args + loc, scale = map(asarray, (loc, scale)) + args = tuple(map(asarray, args)) + cond0 = self._argcheck(*args) & (scale > 0) & (loc == loc) + output = zeros(shape(cond0), 'd') + place(output, (1-cond0), self.badvalue) + goodargs = argsreduce(cond0, scale, *args) + goodscale = goodargs[0] + goodargs = goodargs[1:] + place(output, cond0, self.vecentropy(*goodargs) + log(goodscale)) + return output[()] + + def moment(self, order, *args, **kwds): + """non-central moment of distribution of specified order. + + Parameters + ---------- + order : int, order >= 1 + Order of moment. + arg1, arg2, arg3,... : float + The shape parameter(s) for the distribution (see docstring of the + instance object for more information). + loc : array_like, optional + location parameter (default=0) + scale : array_like, optional + scale parameter (default=1) + + """ + n = order + shapes, loc, scale = self._parse_args(*args, **kwds) + args = np.broadcast_arrays(*(*shapes, loc, scale)) + *shapes, loc, scale = args + + i0 = np.logical_and(self._argcheck(*shapes), scale > 0) + i1 = np.logical_and(i0, loc == 0) + i2 = np.logical_and(i0, loc != 0) + + args = argsreduce(i0, *shapes, loc, scale) + *shapes, loc, scale = args + + if (floor(n) != n): + raise ValueError("Moment must be an integer.") + if (n < 0): + raise ValueError("Moment must be positive.") + mu, mu2, g1, g2 = None, None, None, None + if (n > 0) and (n < 5): + if self._stats_has_moments: + mdict = {'moments': {1: 'm', 2: 'v', 3: 'vs', 4: 'mvsk'}[n]} + else: + mdict = {} + mu, mu2, g1, g2 = self._stats(*shapes, **mdict) + val = np.empty(loc.shape) # val needs to be indexed by loc + val[...] = _moment_from_stats(n, mu, mu2, g1, g2, self._munp, shapes) + + # Convert to transformed X = L + S*Y + # E[X^n] = E[(L+S*Y)^n] = L^n sum(comb(n, k)*(S/L)^k E[Y^k], k=0...n) + result = zeros(i0.shape) + place(result, ~i0, self.badvalue) + + if i1.any(): + res1 = scale[loc == 0]**n * val[loc == 0] + place(result, i1, res1) + + if i2.any(): + mom = [mu, mu2, g1, g2] + arrs = [i for i in mom if i is not None] + idx = [i for i in range(4) if mom[i] is not None] + if any(idx): + arrs = argsreduce(loc != 0, *arrs) + j = 0 + for i in idx: + mom[i] = arrs[j] + j += 1 + mu, mu2, g1, g2 = mom + args = argsreduce(loc != 0, *shapes, loc, scale, val) + *shapes, loc, scale, val = args + + res2 = zeros(loc.shape, dtype='d') + fac = scale / loc + for k in range(n): + valk = _moment_from_stats(k, mu, mu2, g1, g2, self._munp, + shapes) + res2 += comb(n, k, exact=True)*fac**k * valk + res2 += fac**n * val + res2 *= loc**n + place(result, i2, res2) + + return result[()] + + def median(self, *args, **kwds): + """Median of the distribution. + + Parameters + ---------- + arg1, arg2, arg3,... : array_like + The shape parameter(s) for the distribution (see docstring of the + instance object for more information) + loc : array_like, optional + Location parameter, Default is 0. + scale : array_like, optional + Scale parameter, Default is 1. + + Returns + ------- + median : float + The median of the distribution. + + See Also + -------- + rv_discrete.ppf + Inverse of the CDF + + """ + return self.ppf(0.5, *args, **kwds) + + def mean(self, *args, **kwds): + """Mean of the distribution. + + Parameters + ---------- + arg1, arg2, arg3,... : array_like + The shape parameter(s) for the distribution (see docstring of the + instance object for more information) + loc : array_like, optional + location parameter (default=0) + scale : array_like, optional + scale parameter (default=1) + + Returns + ------- + mean : float + the mean of the distribution + + """ + kwds['moments'] = 'm' + res = self.stats(*args, **kwds) + if isinstance(res, ndarray) and res.ndim == 0: + return res[()] + return res + + def var(self, *args, **kwds): + """Variance of the distribution. + + Parameters + ---------- + arg1, arg2, arg3,... : array_like + The shape parameter(s) for the distribution (see docstring of the + instance object for more information) + loc : array_like, optional + location parameter (default=0) + scale : array_like, optional + scale parameter (default=1) + + Returns + ------- + var : float + the variance of the distribution + + """ + kwds['moments'] = 'v' + res = self.stats(*args, **kwds) + if isinstance(res, ndarray) and res.ndim == 0: + return res[()] + return res + + def std(self, *args, **kwds): + """Standard deviation of the distribution. + + Parameters + ---------- + arg1, arg2, arg3,... : array_like + The shape parameter(s) for the distribution (see docstring of the + instance object for more information) + loc : array_like, optional + location parameter (default=0) + scale : array_like, optional + scale parameter (default=1) + + Returns + ------- + std : float + standard deviation of the distribution + + """ + kwds['moments'] = 'v' + res = sqrt(self.stats(*args, **kwds)) + return res + + def interval(self, confidence, *args, **kwds): + """Confidence interval with equal areas around the median. + + Parameters + ---------- + confidence : array_like of float + Probability that an rv will be drawn from the returned range. + Each value should be in the range [0, 1]. + arg1, arg2, ... : array_like + The shape parameter(s) for the distribution (see docstring of the + instance object for more information). + loc : array_like, optional + location parameter, Default is 0. + scale : array_like, optional + scale parameter, Default is 1. + + Returns + ------- + a, b : ndarray of float + end-points of range that contain ``100 * alpha %`` of the rv's + possible values. + + Notes + ----- + This is implemented as ``ppf([p_tail, 1-p_tail])``, where + ``ppf`` is the inverse cumulative distribution function and + ``p_tail = (1-confidence)/2``. Suppose ``[c, d]`` is the support of a + discrete distribution; then ``ppf([0, 1]) == (c-1, d)``. Therefore, + when ``confidence=1`` and the distribution is discrete, the left end + of the interval will be beyond the support of the distribution. + For discrete distributions, the interval will limit the probability + in each tail to be less than or equal to ``p_tail`` (usually + strictly less). + + """ + alpha = confidence + + alpha = asarray(alpha) + if np.any((alpha > 1) | (alpha < 0)): + raise ValueError("alpha must be between 0 and 1 inclusive") + q1 = (1.0-alpha)/2 + q2 = (1.0+alpha)/2 + a = self.ppf(q1, *args, **kwds) + b = self.ppf(q2, *args, **kwds) + return a, b + + def support(self, *args, **kwargs): + """Support of the distribution. + + Parameters + ---------- + arg1, arg2, ... : array_like + The shape parameter(s) for the distribution (see docstring of the + instance object for more information). + loc : array_like, optional + location parameter, Default is 0. + scale : array_like, optional + scale parameter, Default is 1. + + Returns + ------- + a, b : array_like + end-points of the distribution's support. + + """ + args, loc, scale = self._parse_args(*args, **kwargs) + arrs = np.broadcast_arrays(*args, loc, scale) + args, loc, scale = arrs[:-2], arrs[-2], arrs[-1] + cond = self._argcheck(*args) & (scale > 0) + _a, _b = self._get_support(*args) + if cond.all(): + return _a * scale + loc, _b * scale + loc + elif cond.ndim == 0: + return self.badvalue, self.badvalue + # promote bounds to at least float to fill in the badvalue + _a, _b = np.asarray(_a).astype('d'), np.asarray(_b).astype('d') + out_a, out_b = _a * scale + loc, _b * scale + loc + place(out_a, 1-cond, self.badvalue) + place(out_b, 1-cond, self.badvalue) + return out_a, out_b + + def nnlf(self, theta, x): + """Negative loglikelihood function. + Notes + ----- + This is ``-sum(log pdf(x, theta), axis=0)`` where `theta` are the + parameters (including loc and scale). + """ + loc, scale, args = self._unpack_loc_scale(theta) + if not self._argcheck(*args) or scale <= 0: + return inf + x = (asarray(x)-loc) / scale + n_log_scale = len(x) * log(scale) + if np.any(~self._support_mask(x, *args)): + return inf + return self._nnlf(x, *args) + n_log_scale + + def _nnlf(self, x, *args): + return -np.sum(self._logpxf(x, *args), axis=0) + + def _nlff_and_penalty(self, x, args, log_fitfun): + # negative log fit function + cond0 = ~self._support_mask(x, *args) + n_bad = np.count_nonzero(cond0, axis=0) + if n_bad > 0: + x = argsreduce(~cond0, x)[0] + logff = log_fitfun(x, *args) + finite_logff = np.isfinite(logff) + n_bad += np.sum(~finite_logff, axis=0) + if n_bad > 0: + penalty = n_bad * log(_XMAX) * 100 + return -np.sum(logff[finite_logff], axis=0) + penalty + return -np.sum(logff, axis=0) + + def _penalized_nnlf(self, theta, x): + """Penalized negative loglikelihood function. + i.e., - sum (log pdf(x, theta), axis=0) + penalty + where theta are the parameters (including loc and scale) + """ + loc, scale, args = self._unpack_loc_scale(theta) + if not self._argcheck(*args) or scale <= 0: + return inf + x = asarray((x-loc) / scale) + n_log_scale = len(x) * log(scale) + return self._nlff_and_penalty(x, args, self._logpxf) + n_log_scale + + def _penalized_nlpsf(self, theta, x): + """Penalized negative log product spacing function. + i.e., - sum (log (diff (cdf (x, theta))), axis=0) + penalty + where theta are the parameters (including loc and scale) + Follows reference [1] of scipy.stats.fit + """ + loc, scale, args = self._unpack_loc_scale(theta) + if not self._argcheck(*args) or scale <= 0: + return inf + x = (np.sort(x) - loc)/scale + + def log_psf(x, *args): + x, lj = np.unique(x, return_counts=True) # fast for sorted x + cdf_data = self._cdf(x, *args) if x.size else [] + if not (x.size and 1 - cdf_data[-1] <= 0): + cdf = np.concatenate(([0], cdf_data, [1])) + lj = np.concatenate((lj, [1])) + else: + cdf = np.concatenate(([0], cdf_data)) + # here we could use logcdf w/ logsumexp trick to take differences, + # but in the context of the method, it seems unlikely to matter + return lj * np.log(np.diff(cdf) / lj) + + return self._nlff_and_penalty(x, args, log_psf) + + +class _ShapeInfo: + def __init__(self, name, integrality=False, domain=(-np.inf, np.inf), + inclusive=(True, True)): + self.name = name + self.integrality = integrality + self.endpoints = domain + self.inclusive = inclusive + + domain = list(domain) + if np.isfinite(domain[0]) and not inclusive[0]: + domain[0] = np.nextafter(domain[0], np.inf) + if np.isfinite(domain[1]) and not inclusive[1]: + domain[1] = np.nextafter(domain[1], -np.inf) + self.domain = domain + + +def _get_fixed_fit_value(kwds, names): + """ + Given names such as ``['f0', 'fa', 'fix_a']``, check that there is + at most one non-None value in `kwds` associated with those names. + Return that value, or None if none of the names occur in `kwds`. + As a side effect, all occurrences of those names in `kwds` are + removed. + """ + vals = [(name, kwds.pop(name)) for name in names if name in kwds] + if len(vals) > 1: + repeated = [name for name, val in vals] + raise ValueError("fit method got multiple keyword arguments to " + "specify the same fixed parameter: " + + ', '.join(repeated)) + return vals[0][1] if vals else None + + +# continuous random variables: implement maybe later +# +# hf --- Hazard Function (PDF / SF) +# chf --- Cumulative hazard function (-log(SF)) +# psf --- Probability sparsity function (reciprocal of the pdf) in +# units of percent-point-function (as a function of q). +# Also, the derivative of the percent-point function. + + +class rv_continuous(rv_generic): + """A generic continuous random variable class meant for subclassing. + + `rv_continuous` is a base class to construct specific distribution classes + and instances for continuous random variables. It cannot be used + directly as a distribution. + + Parameters + ---------- + momtype : int, optional + The type of generic moment calculation to use: 0 for pdf, 1 (default) + for ppf. + a : float, optional + Lower bound of the support of the distribution, default is minus + infinity. + b : float, optional + Upper bound of the support of the distribution, default is plus + infinity. + xtol : float, optional + The tolerance for fixed point calculation for generic ppf. + badvalue : float, optional + The value in a result arrays that indicates a value that for which + some argument restriction is violated, default is np.nan. + name : str, optional + The name of the instance. This string is used to construct the default + example for distributions. + longname : str, optional + This string is used as part of the first line of the docstring returned + when a subclass has no docstring of its own. Note: `longname` exists + for backwards compatibility, do not use for new subclasses. + shapes : str, optional + The shape of the distribution. For example ``"m, n"`` for a + distribution that takes two integers as the two shape arguments for all + its methods. If not provided, shape parameters will be inferred from + the signature of the private methods, ``_pdf`` and ``_cdf`` of the + instance. + seed : {None, int, `numpy.random.Generator`, `numpy.random.RandomState`}, optional + If `seed` is None (or `np.random`), the `numpy.random.RandomState` + singleton is used. + If `seed` is an int, a new ``RandomState`` instance is used, + seeded with `seed`. + If `seed` is already a ``Generator`` or ``RandomState`` instance then + that instance is used. + + Attributes + ---------- + a, b : float, optional + Lower/upper bound of the support of the unshifted/unscaled distribution. + This value is unaffected by the `loc` and `scale` parameters. + To calculate the support of the shifted/scaled distribution, + use the `support` method. + + Methods + ------- + rvs + pdf + logpdf + cdf + logcdf + sf + logsf + ppf + isf + moment + stats + entropy + expect + median + mean + std + var + interval + __call__ + fit + fit_loc_scale + nnlf + support + + Notes + ----- + Public methods of an instance of a distribution class (e.g., ``pdf``, + ``cdf``) check their arguments and pass valid arguments to private, + computational methods (``_pdf``, ``_cdf``). For ``pdf(x)``, ``x`` is valid + if it is within the support of the distribution. + Whether a shape parameter is valid is decided by an ``_argcheck`` method + (which defaults to checking that its arguments are strictly positive.) + + **Subclassing** + + New random variables can be defined by subclassing the `rv_continuous` class + and re-defining at least the ``_pdf`` or the ``_cdf`` method (normalized + to location 0 and scale 1). + + If positive argument checking is not correct for your RV + then you will also need to re-define the ``_argcheck`` method. + + For most of the scipy.stats distributions, the support interval doesn't + depend on the shape parameters. ``x`` being in the support interval is + equivalent to ``self.a <= x <= self.b``. If either of the endpoints of + the support do depend on the shape parameters, then + i) the distribution must implement the ``_get_support`` method; and + ii) those dependent endpoints must be omitted from the distribution's + call to the ``rv_continuous`` initializer. + + Correct, but potentially slow defaults exist for the remaining + methods but for speed and/or accuracy you can over-ride:: + + _logpdf, _cdf, _logcdf, _ppf, _rvs, _isf, _sf, _logsf + + The default method ``_rvs`` relies on the inverse of the cdf, ``_ppf``, + applied to a uniform random variate. In order to generate random variates + efficiently, either the default ``_ppf`` needs to be overwritten (e.g. + if the inverse cdf can expressed in an explicit form) or a sampling + method needs to be implemented in a custom ``_rvs`` method. + + If possible, you should override ``_isf``, ``_sf`` or ``_logsf``. + The main reason would be to improve numerical accuracy: for example, + the survival function ``_sf`` is computed as ``1 - _cdf`` which can + result in loss of precision if ``_cdf(x)`` is close to one. + + **Methods that can be overwritten by subclasses** + :: + + _rvs + _pdf + _cdf + _sf + _ppf + _isf + _stats + _munp + _entropy + _argcheck + _get_support + + There are additional (internal and private) generic methods that can + be useful for cross-checking and for debugging, but might work in all + cases when directly called. + + A note on ``shapes``: subclasses need not specify them explicitly. In this + case, `shapes` will be automatically deduced from the signatures of the + overridden methods (`pdf`, `cdf` etc). + If, for some reason, you prefer to avoid relying on introspection, you can + specify ``shapes`` explicitly as an argument to the instance constructor. + + + **Frozen Distributions** + + Normally, you must provide shape parameters (and, optionally, location and + scale parameters to each call of a method of a distribution. + + Alternatively, the object may be called (as a function) to fix the shape, + location, and scale parameters returning a "frozen" continuous RV object: + + rv = generic(, loc=0, scale=1) + `rv_frozen` object with the same methods but holding the given shape, + location, and scale fixed + + **Statistics** + + Statistics are computed using numerical integration by default. + For speed you can redefine this using ``_stats``: + + - take shape parameters and return mu, mu2, g1, g2 + - If you can't compute one of these, return it as None + - Can also be defined with a keyword argument ``moments``, which is a + string composed of "m", "v", "s", and/or "k". + Only the components appearing in string should be computed and + returned in the order "m", "v", "s", or "k" with missing values + returned as None. + + Alternatively, you can override ``_munp``, which takes ``n`` and shape + parameters and returns the n-th non-central moment of the distribution. + + **Deepcopying / Pickling** + + If a distribution or frozen distribution is deepcopied (pickled/unpickled, + etc.), any underlying random number generator is deepcopied with it. An + implication is that if a distribution relies on the singleton RandomState + before copying, it will rely on a copy of that random state after copying, + and ``np.random.seed`` will no longer control the state. + + Examples + -------- + To create a new Gaussian distribution, we would do the following: + + >>> from scipy.stats import rv_continuous + >>> class gaussian_gen(rv_continuous): + ... "Gaussian distribution" + ... def _pdf(self, x): + ... return np.exp(-x**2 / 2.) / np.sqrt(2.0 * np.pi) + >>> gaussian = gaussian_gen(name='gaussian') + + ``scipy.stats`` distributions are *instances*, so here we subclass + `rv_continuous` and create an instance. With this, we now have + a fully functional distribution with all relevant methods automagically + generated by the framework. + + Note that above we defined a standard normal distribution, with zero mean + and unit variance. Shifting and scaling of the distribution can be done + by using ``loc`` and ``scale`` parameters: ``gaussian.pdf(x, loc, scale)`` + essentially computes ``y = (x - loc) / scale`` and + ``gaussian._pdf(y) / scale``. + + """ + + _support: tuple[float | str, float | str] # not required + + def __init__(self, momtype=1, a=None, b=None, xtol=1e-14, + badvalue=None, name=None, longname=None, + shapes=None, seed=None): + + super().__init__(seed) + + # save the ctor parameters, cf generic freeze + self._ctor_param = dict( + momtype=momtype, a=a, b=b, xtol=xtol, + badvalue=badvalue, name=name, longname=longname, + shapes=shapes, seed=seed) + + if badvalue is None: + badvalue = nan + if name is None: + name = 'Distribution' + self.badvalue = badvalue + self.name = name + self.a = a + self.b = b + if a is None: + self.a = -inf + if b is None: + self.b = inf + self.xtol = xtol + self.moment_type = momtype + self.shapes = shapes + + self._construct_argparser(meths_to_inspect=[self._pdf, self._cdf], + locscale_in='loc=0, scale=1', + locscale_out='loc, scale') + self._attach_methods() + + if longname is None: + if name[0] in ['aeiouAEIOU']: + hstr = "An " + else: + hstr = "A " + longname = hstr + name + + if sys.flags.optimize < 2: + # Skip adding docstrings if interpreter is run with -OO + if self.__doc__ is None: + self._construct_default_doc(longname=longname, + docdict=docdict, + discrete='continuous') + else: + dct = dict(distcont) + self._construct_doc(docdict, dct.get(self.name)) + + def __getstate__(self): + dct = self.__dict__.copy() + + # these methods will be remade in __setstate__ + # _random_state attribute is taken care of by rv_generic + attrs = ["_parse_args", "_parse_args_stats", "_parse_args_rvs", + "_cdfvec", "_ppfvec", "vecentropy", "generic_moment"] + [dct.pop(attr, None) for attr in attrs] + return dct + + def _attach_methods(self): + """ + Attaches dynamically created methods to the rv_continuous instance. + """ + # _attach_methods is responsible for calling _attach_argparser_methods + self._attach_argparser_methods() + + # nin correction + self._ppfvec = vectorize(self._ppf_single, otypes='d') + self._ppfvec.nin = self.numargs + 1 + self.vecentropy = vectorize(self._entropy, otypes='d') + self._cdfvec = vectorize(self._cdf_single, otypes='d') + self._cdfvec.nin = self.numargs + 1 + + if self.moment_type == 0: + self.generic_moment = vectorize(self._mom0_sc, otypes='d') + else: + self.generic_moment = vectorize(self._mom1_sc, otypes='d') + # Because of the *args argument of _mom0_sc, vectorize cannot count the + # number of arguments correctly. + self.generic_moment.nin = self.numargs + 1 + + def _updated_ctor_param(self): + """Return the current version of _ctor_param, possibly updated by user. + + Used by freezing. + Keep this in sync with the signature of __init__. + """ + dct = self._ctor_param.copy() + dct['a'] = self.a + dct['b'] = self.b + dct['xtol'] = self.xtol + dct['badvalue'] = self.badvalue + dct['name'] = self.name + dct['shapes'] = self.shapes + return dct + + def _ppf_to_solve(self, x, q, *args): + return self.cdf(*(x, )+args)-q + + def _ppf_single(self, q, *args): + factor = 10. + left, right = self._get_support(*args) + + if np.isinf(left): + left = min(-factor, right) + while self._ppf_to_solve(left, q, *args) > 0.: + left, right = left * factor, left + # left is now such that cdf(left) <= q + # if right has changed, then cdf(right) > q + + if np.isinf(right): + right = max(factor, left) + while self._ppf_to_solve(right, q, *args) < 0.: + left, right = right, right * factor + # right is now such that cdf(right) >= q + + return optimize.brentq(self._ppf_to_solve, + left, right, args=(q,)+args, xtol=self.xtol) + + # moment from definition + def _mom_integ0(self, x, m, *args): + return x**m * self.pdf(x, *args) + + def _mom0_sc(self, m, *args): + _a, _b = self._get_support(*args) + return integrate.quad(self._mom_integ0, _a, _b, + args=(m,)+args)[0] + + # moment calculated using ppf + def _mom_integ1(self, q, m, *args): + return (self.ppf(q, *args))**m + + def _mom1_sc(self, m, *args): + return integrate.quad(self._mom_integ1, 0, 1, args=(m,)+args)[0] + + def _pdf(self, x, *args): + return _derivative(self._cdf, x, dx=1e-5, args=args, order=5) + + # Could also define any of these + def _logpdf(self, x, *args): + p = self._pdf(x, *args) + with np.errstate(divide='ignore'): + return log(p) + + def _logpxf(self, x, *args): + # continuous distributions have PDF, discrete have PMF, but sometimes + # the distinction doesn't matter. This lets us use `_logpxf` for both + # discrete and continuous distributions. + return self._logpdf(x, *args) + + def _cdf_single(self, x, *args): + _a, _b = self._get_support(*args) + return integrate.quad(self._pdf, _a, x, args=args)[0] + + def _cdf(self, x, *args): + return self._cdfvec(x, *args) + + def _logcdf(self, x, *args): + median = self._ppf(0.5, *args) + with np.errstate(divide='ignore'): + return xpx.apply_where( + x < median, (x,) + args, + lambda x, *args: np.log(self._cdf(x, *args)), + lambda x, *args: np.log1p(-self._sf(x, *args))) + + def _logsf(self, x, *args): + median = self._ppf(0.5, *args) + with np.errstate(divide='ignore'): + return xpx.apply_where( + x > median, (x,) + args, + lambda x, *args: np.log(self._sf(x, *args)), + lambda x, *args: np.log1p(-self._cdf(x, *args))) + + # generic _argcheck, _sf, _ppf, _isf, _rvs are defined + # in rv_generic + + def pdf(self, x, *args, **kwds): + """Probability density function at x of the given RV. + + Parameters + ---------- + x : array_like + quantiles + arg1, arg2, arg3,... : array_like + The shape parameter(s) for the distribution (see docstring of the + instance object for more information) + loc : array_like, optional + location parameter (default=0) + scale : array_like, optional + scale parameter (default=1) + + Returns + ------- + pdf : ndarray + Probability density function evaluated at x + + """ + args, loc, scale = self._parse_args(*args, **kwds) + x, loc, scale = map(asarray, (x, loc, scale)) + args = tuple(map(asarray, args)) + dtyp = np.promote_types(x.dtype, np.float64) + x = np.asarray((x - loc)/scale, dtype=dtyp) + cond0 = self._argcheck(*args) & (scale > 0) + cond1 = self._support_mask(x, *args) & (scale > 0) + cond = cond0 & cond1 + output = zeros(shape(cond), dtyp) + putmask(output, (1-cond0)+np.isnan(x), self.badvalue) + if np.any(cond): + goodargs = argsreduce(cond, *((x,)+args+(scale,))) + scale, goodargs = goodargs[-1], goodargs[:-1] + place(output, cond, self._pdf(*goodargs) / scale) + if output.ndim == 0: + return output[()] + return output + + def logpdf(self, x, *args, **kwds): + """Log of the probability density function at x of the given RV. + + This uses a more numerically accurate calculation if available. + + Parameters + ---------- + x : array_like + quantiles + arg1, arg2, arg3,... : array_like + The shape parameter(s) for the distribution (see docstring of the + instance object for more information) + loc : array_like, optional + location parameter (default=0) + scale : array_like, optional + scale parameter (default=1) + + Returns + ------- + logpdf : array_like + Log of the probability density function evaluated at x + + """ + args, loc, scale = self._parse_args(*args, **kwds) + x, loc, scale = map(asarray, (x, loc, scale)) + args = tuple(map(asarray, args)) + dtyp = np.promote_types(x.dtype, np.float64) + x = np.asarray((x - loc)/scale, dtype=dtyp) + cond0 = self._argcheck(*args) & (scale > 0) + cond1 = self._support_mask(x, *args) & (scale > 0) + cond = cond0 & cond1 + output = empty(shape(cond), dtyp) + output.fill(-inf) + putmask(output, (1-cond0)+np.isnan(x), self.badvalue) + if np.any(cond): + goodargs = argsreduce(cond, *((x,)+args+(scale,))) + scale, goodargs = goodargs[-1], goodargs[:-1] + place(output, cond, self._logpdf(*goodargs) - log(scale)) + if output.ndim == 0: + return output[()] + return output + + def cdf(self, x, *args, **kwds): + """ + Cumulative distribution function of the given RV. + + Parameters + ---------- + x : array_like + quantiles + arg1, arg2, arg3,... : array_like + The shape parameter(s) for the distribution (see docstring of the + instance object for more information) + loc : array_like, optional + location parameter (default=0) + scale : array_like, optional + scale parameter (default=1) + + Returns + ------- + cdf : ndarray + Cumulative distribution function evaluated at `x` + + """ + args, loc, scale = self._parse_args(*args, **kwds) + x, loc, scale = map(asarray, (x, loc, scale)) + args = tuple(map(asarray, args)) + _a, _b = self._get_support(*args) + dtyp = np.promote_types(x.dtype, np.float64) + x = np.asarray((x - loc)/scale, dtype=dtyp) + cond0 = self._argcheck(*args) & (scale > 0) + cond1 = self._open_support_mask(x, *args) & (scale > 0) + cond2 = (x >= np.asarray(_b)) & cond0 + cond = cond0 & cond1 + output = zeros(shape(cond), dtyp) + place(output, (1-cond0)+np.isnan(x), self.badvalue) + place(output, cond2, 1.0) + if np.any(cond): # call only if at least 1 entry + goodargs = argsreduce(cond, *((x,)+args)) + place(output, cond, self._cdf(*goodargs)) + if output.ndim == 0: + return output[()] + return output + + def logcdf(self, x, *args, **kwds): + """Log of the cumulative distribution function at x of the given RV. + + Parameters + ---------- + x : array_like + quantiles + arg1, arg2, arg3,... : array_like + The shape parameter(s) for the distribution (see docstring of the + instance object for more information) + loc : array_like, optional + location parameter (default=0) + scale : array_like, optional + scale parameter (default=1) + + Returns + ------- + logcdf : array_like + Log of the cumulative distribution function evaluated at x + + """ + args, loc, scale = self._parse_args(*args, **kwds) + x, loc, scale = map(asarray, (x, loc, scale)) + args = tuple(map(asarray, args)) + _a, _b = self._get_support(*args) + dtyp = np.promote_types(x.dtype, np.float64) + x = np.asarray((x - loc)/scale, dtype=dtyp) + cond0 = self._argcheck(*args) & (scale > 0) + cond1 = self._open_support_mask(x, *args) & (scale > 0) + cond2 = (x >= _b) & cond0 + cond = cond0 & cond1 + output = empty(shape(cond), dtyp) + output.fill(-inf) + place(output, (1-cond0)*(cond1 == cond1)+np.isnan(x), self.badvalue) + place(output, cond2, 0.0) + if np.any(cond): # call only if at least 1 entry + goodargs = argsreduce(cond, *((x,)+args)) + place(output, cond, self._logcdf(*goodargs)) + if output.ndim == 0: + return output[()] + return output + + def sf(self, x, *args, **kwds): + """Survival function (1 - `cdf`) at x of the given RV. + + Parameters + ---------- + x : array_like + quantiles + arg1, arg2, arg3,... : array_like + The shape parameter(s) for the distribution (see docstring of the + instance object for more information) + loc : array_like, optional + location parameter (default=0) + scale : array_like, optional + scale parameter (default=1) + + Returns + ------- + sf : array_like + Survival function evaluated at x + + """ + args, loc, scale = self._parse_args(*args, **kwds) + x, loc, scale = map(asarray, (x, loc, scale)) + args = tuple(map(asarray, args)) + _a, _b = self._get_support(*args) + dtyp = np.promote_types(x.dtype, np.float64) + x = np.asarray((x - loc)/scale, dtype=dtyp) + cond0 = self._argcheck(*args) & (scale > 0) + cond1 = self._open_support_mask(x, *args) & (scale > 0) + cond2 = cond0 & (x <= _a) + cond = cond0 & cond1 + output = zeros(shape(cond), dtyp) + place(output, (1-cond0)+np.isnan(x), self.badvalue) + place(output, cond2, 1.0) + if np.any(cond): + goodargs = argsreduce(cond, *((x,)+args)) + place(output, cond, self._sf(*goodargs)) + if output.ndim == 0: + return output[()] + return output + + def logsf(self, x, *args, **kwds): + """Log of the survival function of the given RV. + + Returns the log of the "survival function," defined as (1 - `cdf`), + evaluated at `x`. + + Parameters + ---------- + x : array_like + quantiles + arg1, arg2, arg3,... : array_like + The shape parameter(s) for the distribution (see docstring of the + instance object for more information) + loc : array_like, optional + location parameter (default=0) + scale : array_like, optional + scale parameter (default=1) + + Returns + ------- + logsf : ndarray + Log of the survival function evaluated at `x`. + + """ + args, loc, scale = self._parse_args(*args, **kwds) + x, loc, scale = map(asarray, (x, loc, scale)) + args = tuple(map(asarray, args)) + _a, _b = self._get_support(*args) + dtyp = np.promote_types(x.dtype, np.float64) + x = np.asarray((x - loc)/scale, dtype=dtyp) + cond0 = self._argcheck(*args) & (scale > 0) + cond1 = self._open_support_mask(x, *args) & (scale > 0) + cond2 = cond0 & (x <= _a) + cond = cond0 & cond1 + output = empty(shape(cond), dtyp) + output.fill(-inf) + place(output, (1-cond0)+np.isnan(x), self.badvalue) + place(output, cond2, 0.0) + if np.any(cond): + goodargs = argsreduce(cond, *((x,)+args)) + place(output, cond, self._logsf(*goodargs)) + if output.ndim == 0: + return output[()] + return output + + def ppf(self, q, *args, **kwds): + """Percent point function (inverse of `cdf`) at q of the given RV. + + Parameters + ---------- + q : array_like + lower tail probability + arg1, arg2, arg3,... : array_like + The shape parameter(s) for the distribution (see docstring of the + instance object for more information) + loc : array_like, optional + location parameter (default=0) + scale : array_like, optional + scale parameter (default=1) + + Returns + ------- + x : array_like + quantile corresponding to the lower tail probability q. + + """ + args, loc, scale = self._parse_args(*args, **kwds) + q, loc, scale = map(asarray, (q, loc, scale)) + args = tuple(map(asarray, args)) + _a, _b = self._get_support(*args) + cond0 = self._argcheck(*args) & (scale > 0) & (loc == loc) + cond1 = (0 < q) & (q < 1) + cond2 = cond0 & (q == 0) + cond3 = cond0 & (q == 1) + cond = cond0 & cond1 + output = np.full(shape(cond), fill_value=self.badvalue) + + lower_bound = _a * scale + loc + upper_bound = _b * scale + loc + place(output, cond2, argsreduce(cond2, lower_bound)[0]) + place(output, cond3, argsreduce(cond3, upper_bound)[0]) + + if np.any(cond): # call only if at least 1 entry + goodargs = argsreduce(cond, *((q,)+args+(scale, loc))) + scale, loc, goodargs = goodargs[-2], goodargs[-1], goodargs[:-2] + place(output, cond, self._ppf(*goodargs) * scale + loc) + if output.ndim == 0: + return output[()] + return output + + def isf(self, q, *args, **kwds): + """Inverse survival function (inverse of `sf`) at q of the given RV. + + Parameters + ---------- + q : array_like + upper tail probability + arg1, arg2, arg3,... : array_like + The shape parameter(s) for the distribution (see docstring of the + instance object for more information) + loc : array_like, optional + location parameter (default=0) + scale : array_like, optional + scale parameter (default=1) + + Returns + ------- + x : ndarray or scalar + Quantile corresponding to the upper tail probability q. + + """ + args, loc, scale = self._parse_args(*args, **kwds) + q, loc, scale = map(asarray, (q, loc, scale)) + args = tuple(map(asarray, args)) + _a, _b = self._get_support(*args) + cond0 = self._argcheck(*args) & (scale > 0) & (loc == loc) + cond1 = (0 < q) & (q < 1) + cond2 = cond0 & (q == 1) + cond3 = cond0 & (q == 0) + cond = cond0 & cond1 + output = np.full(shape(cond), fill_value=self.badvalue) + + lower_bound = _a * scale + loc + upper_bound = _b * scale + loc + place(output, cond2, argsreduce(cond2, lower_bound)[0]) + place(output, cond3, argsreduce(cond3, upper_bound)[0]) + + if np.any(cond): + goodargs = argsreduce(cond, *((q,)+args+(scale, loc))) + scale, loc, goodargs = goodargs[-2], goodargs[-1], goodargs[:-2] + place(output, cond, self._isf(*goodargs) * scale + loc) + if output.ndim == 0: + return output[()] + return output + + def _unpack_loc_scale(self, theta): + try: + loc = theta[-2] + scale = theta[-1] + args = tuple(theta[:-2]) + except IndexError as e: + raise ValueError("Not enough input arguments.") from e + return loc, scale, args + + def _nnlf_and_penalty(self, x, args): + """ + Compute the penalized negative log-likelihood for the + "standardized" data (i.e. already shifted by loc and + scaled by scale) for the shape parameters in `args`. + + `x` can be a 1D numpy array or a CensoredData instance. + """ + if isinstance(x, CensoredData): + # Filter out the data that is not in the support. + xs = x._supported(*self._get_support(*args)) + n_bad = len(x) - len(xs) + i1, i2 = xs._interval.T + terms = [ + # logpdf of the noncensored data. + self._logpdf(xs._uncensored, *args), + # logcdf of the left-censored data. + self._logcdf(xs._left, *args), + # logsf of the right-censored data. + self._logsf(xs._right, *args), + # log of probability of the interval-censored data. + np.log(self._delta_cdf(i1, i2, *args)), + ] + else: + cond0 = ~self._support_mask(x, *args) + n_bad = np.count_nonzero(cond0) + if n_bad > 0: + x = argsreduce(~cond0, x)[0] + terms = [self._logpdf(x, *args)] + + totals, bad_counts = zip(*[_sum_finite(term) for term in terms]) + total = sum(totals) + n_bad += sum(bad_counts) + + return -total + n_bad * _LOGXMAX * 100 + + def _penalized_nnlf(self, theta, x): + """Penalized negative loglikelihood function. + + i.e., - sum (log pdf(x, theta), axis=0) + penalty + where theta are the parameters (including loc and scale) + """ + loc, scale, args = self._unpack_loc_scale(theta) + if not self._argcheck(*args) or scale <= 0: + return inf + if isinstance(x, CensoredData): + x = (x - loc) / scale + n_log_scale = (len(x) - x.num_censored()) * log(scale) + else: + x = (x - loc) / scale + n_log_scale = len(x) * log(scale) + + return self._nnlf_and_penalty(x, args) + n_log_scale + + def _fitstart(self, data, args=None): + """Starting point for fit (shape arguments + loc + scale).""" + if args is None: + args = (1.0,)*self.numargs + loc, scale = self._fit_loc_scale_support(data, *args) + return args + (loc, scale) + + def _reduce_func(self, args, kwds, data=None): + """ + Return the (possibly reduced) function to optimize in order to find MLE + estimates for the .fit method. + """ + # Convert fixed shape parameters to the standard numeric form: e.g. for + # stats.beta, shapes='a, b'. To fix `a`, the caller can give a value + # for `f0`, `fa` or 'fix_a'. The following converts the latter two + # into the first (numeric) form. + shapes = [] + if self.shapes: + shapes = self.shapes.replace(',', ' ').split() + for j, s in enumerate(shapes): + key = 'f' + str(j) + names = [key, 'f' + s, 'fix_' + s] + val = _get_fixed_fit_value(kwds, names) + if val is not None: + kwds[key] = val + + args = list(args) + Nargs = len(args) + fixedn = [] + names = [f'f{n}' for n in range(Nargs - 2)] + ['floc', 'fscale'] + x0 = [] + for n, key in enumerate(names): + if key in kwds: + fixedn.append(n) + args[n] = kwds.pop(key) + else: + x0.append(args[n]) + + methods = {"mle", "mm"} + method = kwds.pop('method', "mle").lower() + if method == "mm": + n_params = len(shapes) + 2 - len(fixedn) + exponents = (np.arange(1, n_params+1))[:, np.newaxis] + data_moments = np.sum(data[None, :]**exponents/len(data), axis=1) + + def objective(theta, x): + return self._moment_error(theta, x, data_moments) + + elif method == "mle": + objective = self._penalized_nnlf + else: + raise ValueError(f"Method '{method}' not available; " + f"must be one of {methods}") + + if len(fixedn) == 0: + func = objective + restore = None + else: + if len(fixedn) == Nargs: + raise ValueError( + "All parameters fixed. There is nothing to optimize.") + + def restore(args, theta): + # Replace with theta for all numbers not in fixedn + # This allows the non-fixed values to vary, but + # we still call self.nnlf with all parameters. + i = 0 + for n in range(Nargs): + if n not in fixedn: + args[n] = theta[i] + i += 1 + return args + + def func(theta, x): + newtheta = restore(args[:], theta) + return objective(newtheta, x) + + return x0, func, restore, args + + def _moment_error(self, theta, x, data_moments): + loc, scale, args = self._unpack_loc_scale(theta) + if not self._argcheck(*args) or scale <= 0: + return inf + + dist_moments = np.array([self.moment(i+1, *args, loc=loc, scale=scale) + for i in range(len(data_moments))]) + if np.any(np.isnan(dist_moments)): + raise ValueError("Method of moments encountered a non-finite " + "distribution moment and cannot continue. " + "Consider trying method='MLE'.") + + return (((data_moments - dist_moments) / + np.maximum(np.abs(data_moments), 1e-8))**2).sum() + + def fit(self, data, *args, **kwds): + r""" + Return estimates of shape (if applicable), location, and scale + parameters from data. The default estimation method is Maximum + Likelihood Estimation (MLE), but Method of Moments (MM) + is also available. + + Starting estimates for the fit are given by input arguments; + for any arguments not provided with starting estimates, + ``self._fitstart(data)`` is called to generate such. + + One can hold some parameters fixed to specific values by passing in + keyword arguments ``f0``, ``f1``, ..., ``fn`` (for shape parameters) + and ``floc`` and ``fscale`` (for location and scale parameters, + respectively). + + Parameters + ---------- + data : array_like or `CensoredData` instance + Data to use in estimating the distribution parameters. + arg1, arg2, arg3,... : floats, optional + Starting value(s) for any shape-characterizing arguments (those not + provided will be determined by a call to ``_fitstart(data)``). + No default value. + **kwds : floats, optional + - `loc`: initial guess of the distribution's location parameter. + - `scale`: initial guess of the distribution's scale parameter. + + Special keyword arguments are recognized as holding certain + parameters fixed: + + - f0, ..., fn : hold respective shape parameters fixed. + Alternatively, shape parameters to fix can be specified by name. + For example, if ``self.shapes == "a, b"``, ``fa`` and ``fix_a`` + are equivalent to ``f0``, and ``fb`` and ``fix_b`` are + equivalent to ``f1``. + + - floc : hold location parameter fixed to specified value. + + - fscale : hold scale parameter fixed to specified value. + + - optimizer : The optimizer to use. The optimizer must take + ``func`` and starting position as the first two arguments, + plus ``args`` (for extra arguments to pass to the + function to be optimized) and ``disp``. + The ``fit`` method calls the optimizer with ``disp=0`` to suppress output. + The optimizer must return the estimated parameters. + + - method : The method to use. The default is "MLE" (Maximum + Likelihood Estimate); "MM" (Method of Moments) + is also available. + + Raises + ------ + TypeError, ValueError + If an input is invalid + `~scipy.stats.FitError` + If fitting fails or the fit produced would be invalid + + Returns + ------- + parameter_tuple : tuple of floats + Estimates for any shape parameters (if applicable), followed by + those for location and scale. For most random variables, shape + statistics will be returned, but there are exceptions (e.g. + ``norm``). + + Notes + ----- + With ``method="MLE"`` (default), the fit is computed by minimizing + the negative log-likelihood function. A large, finite penalty + (rather than infinite negative log-likelihood) is applied for + observations beyond the support of the distribution. + + With ``method="MM"``, the fit is computed by minimizing the L2 norm + of the relative errors between the first *k* raw (about zero) data + moments and the corresponding distribution moments, where *k* is the + number of non-fixed parameters. + More precisely, the objective function is:: + + (((data_moments - dist_moments) + / np.maximum(np.abs(data_moments), 1e-8))**2).sum() + + where the constant ``1e-8`` avoids division by zero in case of + vanishing data moments. Typically, this error norm can be reduced to + zero. + Note that the standard method of moments can produce parameters for + which some data are outside the support of the fitted distribution; + this implementation does nothing to prevent this. + + For either method, + the returned answer is not guaranteed to be globally optimal; it + may only be locally optimal, or the optimization may fail altogether. + If the data contain any of ``np.nan``, ``np.inf``, or ``-np.inf``, + the `fit` method will raise a ``RuntimeError``. + + When passing a ``CensoredData`` instance to ``data``, the log-likelihood + function is defined as: + + .. math:: + + l(\pmb{\theta}; k) & = \sum + \log(f(k_u; \pmb{\theta})) + + \sum + \log(F(k_l; \pmb{\theta})) \\ + & + \sum + \log(1 - F(k_r; \pmb{\theta})) \\ + & + \sum + \log(F(k_{\text{high}, i}; \pmb{\theta}) + - F(k_{\text{low}, i}; \pmb{\theta})) + + where :math:`f` and :math:`F` are the pdf and cdf, respectively, of the + function being fitted, :math:`\pmb{\theta}` is the parameter vector, + :math:`u` are the indices of uncensored observations, + :math:`l` are the indices of left-censored observations, + :math:`r` are the indices of right-censored observations, + subscripts "low"/"high" denote endpoints of interval-censored observations, and + :math:`i` are the indices of interval-censored observations. + + Examples + -------- + + Generate some data to fit: draw random variates from the `beta` + distribution + + >>> import numpy as np + >>> from scipy.stats import beta + >>> a, b = 1., 2. + >>> rng = np.random.default_rng(172786373191770012695001057628748821561) + >>> x = beta.rvs(a, b, size=1000, random_state=rng) + + Now we can fit all four parameters (``a``, ``b``, ``loc`` and + ``scale``): + + >>> a1, b1, loc1, scale1 = beta.fit(x) + >>> a1, b1, loc1, scale1 + (1.0198945204435628, 1.9484708982737828, 4.372241314917588e-05, 0.9979078845964814) + + The fit can be done also using a custom optimizer: + + >>> from scipy.optimize import minimize + >>> def custom_optimizer(func, x0, args=(), disp=0): + ... res = minimize(func, x0, args, method="slsqp", options={"disp": disp}) + ... if res.success: + ... return res.x + ... raise RuntimeError('optimization routine failed') + >>> a1, b1, loc1, scale1 = beta.fit(x, method="MLE", optimizer=custom_optimizer) + >>> a1, b1, loc1, scale1 + (1.0198821087258905, 1.948484145914738, 4.3705304486881485e-05, 0.9979104663953395) + + We can also use some prior knowledge about the dataset: let's keep + ``loc`` and ``scale`` fixed: + + >>> a1, b1, loc1, scale1 = beta.fit(x, floc=0, fscale=1) + >>> loc1, scale1 + (0, 1) + + We can also keep shape parameters fixed by using ``f``-keywords. To + keep the zero-th shape parameter ``a`` equal 1, use ``f0=1`` or, + equivalently, ``fa=1``: + + >>> a1, b1, loc1, scale1 = beta.fit(x, fa=1, floc=0, fscale=1) + >>> a1 + 1 + + Not all distributions return estimates for the shape parameters. + ``norm`` for example just returns estimates for location and scale: + + >>> from scipy.stats import norm + >>> x = norm.rvs(a, b, size=1000, random_state=123) + >>> loc1, scale1 = norm.fit(x) + >>> loc1, scale1 + (0.92087172783841631, 2.0015750750324668) + """ # noqa: E501 + method = kwds.get('method', "mle").lower() + + censored = isinstance(data, CensoredData) + if censored: + if method != 'mle': + raise ValueError('For censored data, the method must' + ' be "MLE".') + if data.num_censored() == 0: + # There are no censored values in data, so replace the + # CensoredData instance with a regular array. + data = data._uncensored + censored = False + + Narg = len(args) + if Narg > self.numargs: + raise TypeError("Too many input arguments.") + + # Check the finiteness of data only if data is not an instance of + # CensoredData. The arrays in a CensoredData instance have already + # been validated. + if not censored: + # Note: `ravel()` is called for backwards compatibility. + data = np.asarray(data).ravel() + if not np.isfinite(data).all(): + raise ValueError("The data contains non-finite values.") + + start = [None]*2 + if (Narg < self.numargs) or not ('loc' in kwds and + 'scale' in kwds): + # get distribution specific starting locations + start = self._fitstart(data) + args += start[Narg:-2] + loc = kwds.pop('loc', start[-2]) + scale = kwds.pop('scale', start[-1]) + args += (loc, scale) + x0, func, restore, args = self._reduce_func(args, kwds, data=data) + optimizer = kwds.pop('optimizer', optimize.fmin) + # convert string to function in scipy.optimize + optimizer = _fit_determine_optimizer(optimizer) + # by now kwds must be empty, since everybody took what they needed + if kwds: + raise TypeError(f"Unknown arguments: {kwds}.") + + # In some cases, method of moments can be done with fsolve/root + # instead of an optimizer, but sometimes no solution exists, + # especially when the user fixes parameters. Minimizing the sum + # of squares of the error generalizes to these cases. + vals = optimizer(func, x0, args=(data,), disp=0) + obj = func(vals, data) + + if restore is not None: + vals = restore(args, vals) + vals = tuple(vals) + + loc, scale, shapes = self._unpack_loc_scale(vals) + if not (np.all(self._argcheck(*shapes)) and scale > 0): + raise FitError("Optimization converged to parameters that are " + "outside the range allowed by the distribution.") + + if method == 'mm': + if not np.isfinite(obj): + raise FitError("Optimization failed: either a data moment " + "or fitted distribution moment is " + "non-finite.") + + return vals + + def _fit_loc_scale_support(self, data, *args): + """Estimate loc and scale parameters from data accounting for support. + + Parameters + ---------- + data : array_like + Data to fit. + arg1, arg2, arg3,... : array_like + The shape parameter(s) for the distribution (see docstring of the + instance object for more information). + + Returns + ------- + Lhat : float + Estimated location parameter for the data. + Shat : float + Estimated scale parameter for the data. + + """ + if isinstance(data, CensoredData): + # For this estimate, "uncensor" the data by taking the + # given endpoints as the data for the left- or right-censored + # data, and the mean for the interval-censored data. + data = data._uncensor() + else: + data = np.asarray(data) + + # Estimate location and scale according to the method of moments. + loc_hat, scale_hat = self.fit_loc_scale(data, *args) + + # Compute the support according to the shape parameters. + self._argcheck(*args) + _a, _b = self._get_support(*args) + a, b = _a, _b + support_width = b - a + + # If the support is empty then return the moment-based estimates. + if support_width <= 0: + return loc_hat, scale_hat + + # Compute the proposed support according to the loc and scale + # estimates. + a_hat = loc_hat + a * scale_hat + b_hat = loc_hat + b * scale_hat + + # Use the moment-based estimates if they are compatible with the data. + data_a = np.min(data) + data_b = np.max(data) + if a_hat < data_a and data_b < b_hat: + return loc_hat, scale_hat + + # Otherwise find other estimates that are compatible with the data. + data_width = data_b - data_a + rel_margin = 0.1 + margin = data_width * rel_margin + + # For a finite interval, both the location and scale + # should have interesting values. + if support_width < np.inf: + loc_hat = (data_a - a) - margin + scale_hat = (data_width + 2 * margin) / support_width + return loc_hat, scale_hat + + # For a one-sided interval, use only an interesting location parameter. + if a > -np.inf: + return (data_a - a) - margin, 1 + elif b < np.inf: + return (data_b - b) + margin, 1 + else: + raise RuntimeError + + def fit_loc_scale(self, data, *args): + """ + Estimate loc and scale parameters from data using 1st and 2nd moments. + + Parameters + ---------- + data : array_like + Data to fit. + arg1, arg2, arg3,... : array_like + The shape parameter(s) for the distribution (see docstring of the + instance object for more information). + + Returns + ------- + Lhat : float + Estimated location parameter for the data. + Shat : float + Estimated scale parameter for the data. + + """ + mu, mu2 = self.stats(*args, **{'moments': 'mv'}) + tmp = asarray(data) + muhat = tmp.mean() + mu2hat = tmp.var() + Shat = sqrt(mu2hat / mu2) + with np.errstate(invalid='ignore'): + Lhat = muhat - Shat*mu + if not np.isfinite(Lhat): + Lhat = 0 + if not (np.isfinite(Shat) and (0 < Shat)): + Shat = 1 + return Lhat, Shat + + def _entropy(self, *args): + def integ(x): + val = self._pdf(x, *args) + return entr(val) + + # upper limit is often inf, so suppress warnings when integrating + _a, _b = self._get_support(*args) + with np.errstate(over='ignore'): + h = integrate.quad(integ, _a, _b)[0] + + if not np.isnan(h): + return h + else: + # try with different limits if integration problems + low, upp = self.ppf([1e-10, 1. - 1e-10], *args) + if np.isinf(_b): + upper = upp + else: + upper = _b + if np.isinf(_a): + lower = low + else: + lower = _a + return integrate.quad(integ, lower, upper)[0] + + def expect(self, func=None, args=(), loc=0, scale=1, lb=None, ub=None, + conditional=False, **kwds): + """Calculate expected value of a function with respect to the + distribution by numerical integration. + + The expected value of a function ``f(x)`` with respect to a + distribution ``dist`` is defined as:: + + ub + E[f(x)] = Integral(f(x) * dist.pdf(x)), + lb + + where ``ub`` and ``lb`` are arguments and ``x`` has the ``dist.pdf(x)`` + distribution. If the bounds ``lb`` and ``ub`` correspond to the + support of the distribution, e.g. ``[-inf, inf]`` in the default + case, then the integral is the unrestricted expectation of ``f(x)``. + Also, the function ``f(x)`` may be defined such that ``f(x)`` is ``0`` + outside a finite interval in which case the expectation is + calculated within the finite range ``[lb, ub]``. + + Parameters + ---------- + func : callable, optional + Function for which integral is calculated. Takes only one argument. + The default is the identity mapping f(x) = x. + args : tuple, optional + Shape parameters of the distribution. + loc : float, optional + Location parameter (default=0). + scale : float, optional + Scale parameter (default=1). + lb, ub : scalar, optional + Lower and upper bound for integration. Default is set to the + support of the distribution. + conditional : bool, optional + If True, the integral is corrected by the conditional probability + of the integration interval. The return value is the expectation + of the function, conditional on being in the given interval. + Default is False. + + Additional keyword arguments are passed to the integration routine. + + Returns + ------- + expect : float + The calculated expected value. + + Notes + ----- + The integration behavior of this function is inherited from + `scipy.integrate.quad`. Neither this function nor + `scipy.integrate.quad` can verify whether the integral exists or is + finite. For example ``cauchy(0).mean()`` returns ``np.nan`` and + ``cauchy(0).expect()`` returns ``0.0``. + + Likewise, the accuracy of results is not verified by the function. + `scipy.integrate.quad` is typically reliable for integrals that are + numerically favorable, but it is not guaranteed to converge + to a correct value for all possible intervals and integrands. This + function is provided for convenience; for critical applications, + check results against other integration methods. + + The function is not vectorized. + + Examples + -------- + + To understand the effect of the bounds of integration consider + + >>> from scipy.stats import expon + >>> expon(1).expect(lambda x: 1, lb=0.0, ub=2.0) + 0.6321205588285578 + + This is close to + + >>> expon(1).cdf(2.0) - expon(1).cdf(0.0) + 0.6321205588285577 + + If ``conditional=True`` + + >>> expon(1).expect(lambda x: 1, lb=0.0, ub=2.0, conditional=True) + 1.0000000000000002 + + The slight deviation from 1 is due to numerical integration. + + The integrand can be treated as a complex-valued function + by passing ``complex_func=True`` to `scipy.integrate.quad` . + + >>> import numpy as np + >>> from scipy.stats import vonmises + >>> res = vonmises(loc=2, kappa=1).expect(lambda x: np.exp(1j*x), + ... complex_func=True) + >>> res + (-0.18576377217422957+0.40590124735052263j) + + >>> np.angle(res) # location of the (circular) distribution + 2.0 + + """ + lockwds = {'loc': loc, + 'scale': scale} + self._argcheck(*args) + _a, _b = self._get_support(*args) + if func is None: + def fun(x, *args): + return x * self.pdf(x, *args, **lockwds) + else: + def fun(x, *args): + return func(x) * self.pdf(x, *args, **lockwds) + if lb is None: + lb = loc + _a * scale + if ub is None: + ub = loc + _b * scale + + cdf_bounds = self.cdf([lb, ub], *args, **lockwds) + invfac = cdf_bounds[1] - cdf_bounds[0] + + kwds['args'] = args + + # split interval to help integrator w/ infinite support; see gh-8928 + alpha = 0.05 # split body from tails at probability mass `alpha` + inner_bounds = np.array([alpha, 1-alpha]) + cdf_inner_bounds = cdf_bounds[0] + invfac * inner_bounds + c, d = loc + self._ppf(cdf_inner_bounds, *args) * scale + + # Do not silence warnings from integration. + lbc = integrate.quad(fun, lb, c, **kwds)[0] + cd = integrate.quad(fun, c, d, **kwds)[0] + dub = integrate.quad(fun, d, ub, **kwds)[0] + vals = (lbc + cd + dub) + + if conditional: + vals /= invfac + return np.array(vals)[()] # make it a numpy scalar like other methods + + def _param_info(self): + shape_info = self._shape_info() + loc_info = _ShapeInfo("loc", False, (-np.inf, np.inf), (False, False)) + scale_info = _ShapeInfo("scale", False, (0, np.inf), (False, False)) + param_info = shape_info + [loc_info, scale_info] + return param_info + + # For now, _delta_cdf is a private method. + def _delta_cdf(self, x1, x2, *args, loc=0, scale=1): + """ + Compute CDF(x2) - CDF(x1). + + Where x1 is greater than the median, compute SF(x1) - SF(x2), + otherwise compute CDF(x2) - CDF(x1). + + This function is only useful if `dist.sf(x, ...)` has an implementation + that is numerically more accurate than `1 - dist.cdf(x, ...)`. + """ + cdf1 = self.cdf(x1, *args, loc=loc, scale=scale) + # Possible optimizations (needs investigation-these might not be + # better): + # * Use xpx.apply_where instead of np.where + # * Instead of cdf1 > 0.5, compare x1 to the median. + result = np.where(cdf1 > 0.5, + (self.sf(x1, *args, loc=loc, scale=scale) + - self.sf(x2, *args, loc=loc, scale=scale)), + self.cdf(x2, *args, loc=loc, scale=scale) - cdf1) + if result.ndim == 0: + result = result[()] + return result + + +# Helpers for the discrete distributions +def _drv2_moment(self, n, *args): + """Non-central moment of discrete distribution.""" + def fun(x): + return np.power(x, n) * self._pmf(x, *args) + + _a, _b = self._get_support(*args) + return _expect(fun, _a, _b, self._ppf(0.5, *args), self.inc) + + +def _drv2_ppfsingle(self, q, *args): # Use basic bisection algorithm + _a, _b = self._get_support(*args) + b = _b + a = _a + + step = 10 + if isinf(b): # Be sure ending point is > q + b = float(max(100*q, 10)) + while 1: + if b >= _b: + qb = 1.0 + break + qb = self._cdf(b, *args) + if (qb < q): + b += step + step *= 2 + else: + break + else: + qb = 1.0 + + step = 10 + if isinf(a): # be sure starting point < q + a = float(min(-100*q, -10)) + while 1: + if a <= _a: + qb = 0.0 + break + qa = self._cdf(a, *args) + if (qa > q): + a -= step + step *= 2 + else: + break + else: + qa = self._cdf(a, *args) + + if np.isinf(a) or np.isinf(b): + message = "Arguments that bracket the requested quantile could not be found." + raise RuntimeError(message) + + # maximum number of bisections within the normal float64s + # maxiter = int(np.log2(finfo.max) - np.log2(finfo.smallest_normal)) + maxiter = 2046 + for i in range(maxiter): + if (qa == q): + return a + if (qb == q): + return b + if b <= a+1: + if qa > q: + return a + else: + return b + c = int((a+b)/2.0) + qc = self._cdf(c, *args) + if (qc < q): + if a != c: + a = c + else: + raise RuntimeError('updating stopped, endless loop') + qa = qc + elif (qc > q): + if b != c: + b = c + else: + raise RuntimeError('updating stopped, endless loop') + qb = qc + else: + return c + + +# Must over-ride one of _pmf or _cdf or pass in +# x_k, p(x_k) lists in initialization + + +class rv_discrete(rv_generic): + """A generic discrete random variable class meant for subclassing. + + `rv_discrete` is a base class to construct specific distribution classes + and instances for discrete random variables. It can also be used + to construct an arbitrary distribution defined by a list of support + points and corresponding probabilities. + + Parameters + ---------- + a : float, optional + Lower bound of the support of the distribution, default: 0 + b : float, optional + Upper bound of the support of the distribution, default: plus infinity + moment_tol : float, optional + The tolerance for the generic calculation of moments. + values : tuple of two array_like, optional + ``(xk, pk)`` where ``xk`` are integers and ``pk`` are the non-zero + probabilities between 0 and 1 with ``sum(pk) = 1``. ``xk`` + and ``pk`` must have the same shape, and ``xk`` must be unique. + inc : int, optional + Increment for the support of the distribution. + Default is 1. (other values have not been tested) + badvalue : float, optional + The value in a result arrays that indicates a value that for which + some argument restriction is violated, default is np.nan. + name : str, optional + The name of the instance. This string is used to construct the default + example for distributions. + longname : str, optional + This string is used as part of the first line of the docstring returned + when a subclass has no docstring of its own. Note: `longname` exists + for backwards compatibility, do not use for new subclasses. + shapes : str, optional + The shape of the distribution. For example "m, n" for a distribution + that takes two integers as the two shape arguments for all its methods + If not provided, shape parameters will be inferred from + the signatures of the private methods, ``_pmf`` and ``_cdf`` of + the instance. + seed : {None, int, `numpy.random.Generator`, `numpy.random.RandomState`}, optional + If `seed` is None (or `np.random`), the `numpy.random.RandomState` + singleton is used. + If `seed` is an int, a new ``RandomState`` instance is used, + seeded with `seed`. + If `seed` is already a ``Generator`` or ``RandomState`` instance then + that instance is used. + + Attributes + ---------- + a, b : float, optional + Lower/upper bound of the support of the unshifted/unscaled distribution. + This value is unaffected by the `loc` and `scale` parameters. + To calculate the support of the shifted/scaled distribution, + use the `support` method. + + Methods + ------- + rvs + pmf + logpmf + cdf + logcdf + sf + logsf + ppf + isf + moment + stats + entropy + expect + median + mean + std + var + interval + __call__ + support + + Notes + ----- + This class is similar to `rv_continuous`. Whether a shape parameter is + valid is decided by an ``_argcheck`` method (which defaults to checking + that its arguments are strictly positive.) + The main differences are as follows. + + - The support of the distribution is a set of integers. + - Instead of the probability density function, ``pdf`` (and the + corresponding private ``_pdf``), this class defines the + *probability mass function*, `pmf` (and the corresponding + private ``_pmf``.) + - There is no ``scale`` parameter. + - The default implementations of methods (e.g. ``_cdf``) are not designed + for distributions with support that is unbounded below (i.e. + ``a=-np.inf``), so they must be overridden. + + To create a new discrete distribution, we would do the following: + + >>> from scipy.stats import rv_discrete + >>> class poisson_gen(rv_discrete): + ... "Poisson distribution" + ... def _pmf(self, k, mu): + ... return exp(-mu) * mu**k / factorial(k) + + and create an instance:: + + >>> poisson = poisson_gen(name="poisson") + + Note that above we defined the Poisson distribution in the standard form. + Shifting the distribution can be done by providing the ``loc`` parameter + to the methods of the instance. For example, ``poisson.pmf(x, mu, loc)`` + delegates the work to ``poisson._pmf(x-loc, mu)``. + + **Discrete distributions from a list of probabilities** + + Alternatively, you can construct an arbitrary discrete rv defined + on a finite set of values ``xk`` with ``Prob{X=xk} = pk`` by using the + ``values`` keyword argument to the `rv_discrete` constructor. + + **Deepcopying / Pickling** + + If a distribution or frozen distribution is deepcopied (pickled/unpickled, + etc.), any underlying random number generator is deepcopied with it. An + implication is that if a distribution relies on the singleton RandomState + before copying, it will rely on a copy of that random state after copying, + and ``np.random.seed`` will no longer control the state. + + Examples + -------- + Custom made discrete distribution: + + >>> import numpy as np + >>> from scipy import stats + >>> xk = np.arange(7) + >>> pk = (0.1, 0.2, 0.3, 0.1, 0.1, 0.0, 0.2) + >>> custm = stats.rv_discrete(name='custm', values=(xk, pk)) + >>> + >>> import matplotlib.pyplot as plt + >>> fig, ax = plt.subplots(1, 1) + >>> ax.plot(xk, custm.pmf(xk), 'ro', ms=12, mec='r') + >>> ax.vlines(xk, 0, custm.pmf(xk), colors='r', lw=4) + >>> plt.show() + + Random number generation: + + >>> R = custm.rvs(size=100) + + """ + def __new__(cls, a=0, b=inf, name=None, badvalue=None, + moment_tol=1e-8, values=None, inc=1, longname=None, + shapes=None, seed=None): + + if values is not None: + # dispatch to a subclass + return super().__new__(rv_sample) + else: + # business as usual + return super().__new__(cls) + + def __init__(self, a=0, b=inf, name=None, badvalue=None, + moment_tol=1e-8, values=None, inc=1, longname=None, + shapes=None, seed=None): + + super().__init__(seed) + + # cf generic freeze + self._ctor_param = dict( + a=a, b=b, name=name, badvalue=badvalue, + moment_tol=moment_tol, values=values, inc=inc, + longname=longname, shapes=shapes, seed=seed) + + if badvalue is None: + badvalue = nan + self.badvalue = badvalue + self.a = a + self.b = b + self.moment_tol = moment_tol + self.inc = inc + self.shapes = shapes + + if values is not None: + raise ValueError("rv_discrete.__init__(..., values != None, ...)") + + self._construct_argparser(meths_to_inspect=[self._pmf, self._cdf], + locscale_in='loc=0', + # scale=1 for discrete RVs + locscale_out='loc, 1') + self._attach_methods() + self._construct_docstrings(name, longname) + + def __getstate__(self): + dct = self.__dict__.copy() + # these methods will be remade in __setstate__ + attrs = ["_parse_args", "_parse_args_stats", "_parse_args_rvs", + "_cdfvec", "_ppfvec", "generic_moment"] + [dct.pop(attr, None) for attr in attrs] + return dct + + def _attach_methods(self): + """Attaches dynamically created methods to the rv_discrete instance.""" + self._cdfvec = vectorize(self._cdf_single, otypes='d') + self.vecentropy = vectorize(self._entropy) + + # _attach_methods is responsible for calling _attach_argparser_methods + self._attach_argparser_methods() + + # nin correction needs to be after we know numargs + # correct nin for generic moment vectorization + _vec_generic_moment = vectorize(_drv2_moment, otypes='d') + _vec_generic_moment.nin = self.numargs + 2 + self.generic_moment = types.MethodType(_vec_generic_moment, self) + + # correct nin for ppf vectorization + _vppf = vectorize(_drv2_ppfsingle, otypes='d') + _vppf.nin = self.numargs + 2 + self._ppfvec = types.MethodType(_vppf, self) + + # now that self.numargs is defined, we can adjust nin + self._cdfvec.nin = self.numargs + 1 + + def _construct_docstrings(self, name, longname): + if name is None: + name = 'Distribution' + self.name = name + + # generate docstring for subclass instances + if longname is None: + if name[0] in ['aeiouAEIOU']: + hstr = "An " + else: + hstr = "A " + longname = hstr + name + + if sys.flags.optimize < 2: + # Skip adding docstrings if interpreter is run with -OO + if self.__doc__ is None: + self._construct_default_doc(longname=longname, + docdict=docdict_discrete, + discrete='discrete') + else: + dct = dict(distdiscrete) + self._construct_doc(docdict_discrete, dct.get(self.name)) + + # discrete RV do not have the scale parameter, remove it + self.__doc__ = self.__doc__.replace( + '\n scale : array_like, ' + 'optional\n scale parameter (default=1)', '') + + def _updated_ctor_param(self): + """Return the current version of _ctor_param, possibly updated by user. + + Used by freezing. + Keep this in sync with the signature of __init__. + """ + dct = self._ctor_param.copy() + dct['a'] = self.a + dct['b'] = self.b + dct['badvalue'] = self.badvalue + dct['moment_tol'] = self.moment_tol + dct['inc'] = self.inc + dct['name'] = self.name + dct['shapes'] = self.shapes + return dct + + def _nonzero(self, k, *args): + return floor(k) == k + + def _pmf(self, k, *args): + return self._cdf(k, *args) - self._cdf(k-1, *args) + + def _logpmf(self, k, *args): + with np.errstate(divide='ignore'): + return log(self._pmf(k, *args)) + + def _logpxf(self, k, *args): + # continuous distributions have PDF, discrete have PMF, but sometimes + # the distinction doesn't matter. This lets us use `_logpxf` for both + # discrete and continuous distributions. + return self._logpmf(k, *args) + + def _unpack_loc_scale(self, theta): + try: + loc = theta[-1] + scale = 1 + args = tuple(theta[:-1]) + except IndexError as e: + raise ValueError("Not enough input arguments.") from e + return loc, scale, args + + def _cdf_single(self, k, *args): + _a, _b = self._get_support(*args) + m = arange(int(_a), k+1) + return np.sum(self._pmf(m, *args), axis=0) + + def _cdf(self, x, *args): + k = floor(x).astype(np.float64) + return self._cdfvec(k, *args) + + # generic _logcdf, _sf, _logsf, _ppf, _isf, _rvs defined in rv_generic + + def rvs(self, *args, **kwargs): + """Random variates of given type. + + Parameters + ---------- + arg1, arg2, arg3,... : array_like + The shape parameter(s) for the distribution (see docstring of the + instance object for more information). + loc : array_like, optional + Location parameter (default=0). + size : int or tuple of ints, optional + Defining number of random variates (Default is 1). Note that `size` + has to be given as keyword, not as positional argument. + random_state : {None, int, `numpy.random.Generator`, + `numpy.random.RandomState`}, optional + + If `random_state` is None (or `np.random`), the + `numpy.random.RandomState` singleton is used. + If `random_state` is an int, a new ``RandomState`` instance is + used, seeded with `random_state`. + If `random_state` is already a ``Generator`` or ``RandomState`` + instance, that instance is used. + + Returns + ------- + rvs : ndarray or scalar + Random variates of given `size`. + + """ + kwargs['discrete'] = True + return super().rvs(*args, **kwargs) + + def pmf(self, k, *args, **kwds): + """Probability mass function at k of the given RV. + + Parameters + ---------- + k : array_like + Quantiles. + arg1, arg2, arg3,... : array_like + The shape parameter(s) for the distribution (see docstring of the + instance object for more information) + loc : array_like, optional + Location parameter (default=0). + + Returns + ------- + pmf : array_like + Probability mass function evaluated at k + + """ + args, loc, _ = self._parse_args(*args, **kwds) + k, loc = map(asarray, (k, loc)) + args = tuple(map(asarray, args)) + _a, _b = self._get_support(*args) + k = asarray(k-loc) + cond0 = self._argcheck(*args) + cond1 = (k >= _a) & (k <= _b) + if not isinstance(self, rv_sample): + cond1 = cond1 & self._nonzero(k, *args) + cond = cond0 & cond1 + output = zeros(shape(cond), 'd') + place(output, (1-cond0) + np.isnan(k), self.badvalue) + if np.any(cond): + goodargs = argsreduce(cond, *((k,)+args)) + place(output, cond, np.clip(self._pmf(*goodargs), 0, 1)) + if output.ndim == 0: + return output[()] + return output + + def logpmf(self, k, *args, **kwds): + """Log of the probability mass function at k of the given RV. + + Parameters + ---------- + k : array_like + Quantiles. + arg1, arg2, arg3,... : array_like + The shape parameter(s) for the distribution (see docstring of the + instance object for more information). + loc : array_like, optional + Location parameter. Default is 0. + + Returns + ------- + logpmf : array_like + Log of the probability mass function evaluated at k. + + """ + args, loc, _ = self._parse_args(*args, **kwds) + k, loc = map(asarray, (k, loc)) + args = tuple(map(asarray, args)) + _a, _b = self._get_support(*args) + k = asarray(k-loc) + cond0 = self._argcheck(*args) + cond1 = (k >= _a) & (k <= _b) + if not isinstance(self, rv_sample): + cond1 = cond1 & self._nonzero(k, *args) + cond = cond0 & cond1 + output = empty(shape(cond), 'd') + output.fill(-inf) + place(output, (1-cond0) + np.isnan(k), self.badvalue) + if np.any(cond): + goodargs = argsreduce(cond, *((k,)+args)) + place(output, cond, self._logpmf(*goodargs)) + if output.ndim == 0: + return output[()] + return output + + def cdf(self, k, *args, **kwds): + """Cumulative distribution function of the given RV. + + Parameters + ---------- + k : array_like, int + Quantiles. + arg1, arg2, arg3,... : array_like + The shape parameter(s) for the distribution (see docstring of the + instance object for more information). + loc : array_like, optional + Location parameter (default=0). + + Returns + ------- + cdf : ndarray + Cumulative distribution function evaluated at `k`. + + """ + args, loc, _ = self._parse_args(*args, **kwds) + k, loc = map(asarray, (k, loc)) + args = tuple(map(asarray, args)) + _a, _b = self._get_support(*args) + k = asarray(k-loc) + cond0 = self._argcheck(*args) + cond1 = (k >= _a) & (k < _b) + cond2 = (k >= _b) + cond3 = np.isneginf(k) + cond = cond0 & cond1 & np.isfinite(k) + + output = zeros(shape(cond), 'd') + place(output, cond2*(cond0 == cond0), 1.0) + place(output, cond3*(cond0 == cond0), 0.0) + place(output, (1-cond0) + np.isnan(k), self.badvalue) + + if np.any(cond): + goodargs = argsreduce(cond, *((k,)+args)) + place(output, cond, np.clip(self._cdf(*goodargs), 0, 1)) + if output.ndim == 0: + return output[()] + return output + + def logcdf(self, k, *args, **kwds): + """Log of the cumulative distribution function at k of the given RV. + + Parameters + ---------- + k : array_like, int + Quantiles. + arg1, arg2, arg3,... : array_like + The shape parameter(s) for the distribution (see docstring of the + instance object for more information). + loc : array_like, optional + Location parameter (default=0). + + Returns + ------- + logcdf : array_like + Log of the cumulative distribution function evaluated at k. + + """ + args, loc, _ = self._parse_args(*args, **kwds) + k, loc = map(asarray, (k, loc)) + args = tuple(map(asarray, args)) + _a, _b = self._get_support(*args) + k = asarray(k-loc) + cond0 = self._argcheck(*args) + cond1 = (k >= _a) & (k < _b) + cond2 = (k >= _b) + cond = cond0 & cond1 + output = empty(shape(cond), 'd') + output.fill(-inf) + place(output, (1-cond0) + np.isnan(k), self.badvalue) + place(output, cond2*(cond0 == cond0), 0.0) + + if np.any(cond): + goodargs = argsreduce(cond, *((k,)+args)) + place(output, cond, self._logcdf(*goodargs)) + if output.ndim == 0: + return output[()] + return output + + def sf(self, k, *args, **kwds): + """Survival function (1 - `cdf`) at k of the given RV. + + Parameters + ---------- + k : array_like + Quantiles. + arg1, arg2, arg3,... : array_like + The shape parameter(s) for the distribution (see docstring of the + instance object for more information). + loc : array_like, optional + Location parameter (default=0). + + Returns + ------- + sf : array_like + Survival function evaluated at k. + + """ + args, loc, _ = self._parse_args(*args, **kwds) + k, loc = map(asarray, (k, loc)) + args = tuple(map(asarray, args)) + _a, _b = self._get_support(*args) + k = asarray(k-loc) + cond0 = self._argcheck(*args) + cond1 = (k >= _a) & (k < _b) + cond2 = ((k < _a) | np.isneginf(k)) & cond0 + cond = cond0 & cond1 & np.isfinite(k) + output = zeros(shape(cond), 'd') + place(output, (1-cond0) + np.isnan(k), self.badvalue) + place(output, cond2, 1.0) + if np.any(cond): + goodargs = argsreduce(cond, *((k,)+args)) + place(output, cond, np.clip(self._sf(*goodargs), 0, 1)) + if output.ndim == 0: + return output[()] + return output + + def logsf(self, k, *args, **kwds): + """Log of the survival function of the given RV. + + Returns the log of the "survival function," defined as 1 - `cdf`, + evaluated at `k`. + + Parameters + ---------- + k : array_like + Quantiles. + arg1, arg2, arg3,... : array_like + The shape parameter(s) for the distribution (see docstring of the + instance object for more information). + loc : array_like, optional + Location parameter (default=0). + + Returns + ------- + logsf : ndarray + Log of the survival function evaluated at `k`. + + """ + args, loc, _ = self._parse_args(*args, **kwds) + k, loc = map(asarray, (k, loc)) + args = tuple(map(asarray, args)) + _a, _b = self._get_support(*args) + k = asarray(k-loc) + cond0 = self._argcheck(*args) + cond1 = (k >= _a) & (k < _b) + cond2 = (k < _a) & cond0 + cond = cond0 & cond1 + output = empty(shape(cond), 'd') + output.fill(-inf) + place(output, (1-cond0) + np.isnan(k), self.badvalue) + place(output, cond2, 0.0) + if np.any(cond): + goodargs = argsreduce(cond, *((k,)+args)) + place(output, cond, self._logsf(*goodargs)) + if output.ndim == 0: + return output[()] + return output + + def ppf(self, q, *args, **kwds): + """Percent point function (inverse of `cdf`) at q of the given RV. + + Parameters + ---------- + q : array_like + Lower tail probability. + arg1, arg2, arg3,... : array_like + The shape parameter(s) for the distribution (see docstring of the + instance object for more information). + loc : array_like, optional + Location parameter (default=0). + + Returns + ------- + k : array_like + Quantile corresponding to the lower tail probability, q. + + Notes + ----- + For discrete distributions, the `cdf` is not strictly invertible. By convention, + this method returns the minimum value `k` for which the `cdf` at `k` is at + least `q`. There is one exception: the `ppf` of ``0`` is ``a-1``, + where ``a`` is the left endpoint of the support. + + """ + args, loc, _ = self._parse_args(*args, **kwds) + q, loc = map(asarray, (q, loc)) + args = tuple(map(asarray, args)) + _a, _b = self._get_support(*args) + cond0 = self._argcheck(*args) & (loc == loc) + cond1 = (q > 0) & (q < 1) + cond2 = (q == 0) & cond0 + cond3 = (q == 1) & cond0 + cond = cond0 & cond1 + output = np.full(shape(cond), fill_value=self.badvalue, dtype='d') + # output type 'd' to handle nin and inf + + place(output, cond2, argsreduce(cond2, _a-1 + loc)[0]) + place(output, cond3, argsreduce(cond3, _b + loc)[0]) + if np.any(cond): + goodargs = argsreduce(cond, *((q,)+args+(loc,))) + loc, goodargs = goodargs[-1], goodargs[:-1] + place(output, cond, self._ppf(*goodargs) + loc) + + if output.ndim == 0: + return output[()] + return output + + def isf(self, q, *args, **kwds): + """Inverse survival function (inverse of `sf`) at q of the given RV. + + Parameters + ---------- + q : array_like + Upper tail probability. + arg1, arg2, arg3,... : array_like + The shape parameter(s) for the distribution (see docstring of the + instance object for more information). + loc : array_like, optional + Location parameter (default=0). + + Returns + ------- + k : ndarray or scalar + Quantile corresponding to the upper tail probability, q. + + Notes + ----- + For discrete distributions, the `sf` is not strictly invertible. By convention, + this method returns the minimum value `k` for which the `sf` at `k` is + no greater than `q`. There is one exception: the `isf` of ``1`` is ``a-1``, + where ``a`` is the left endpoint of the support. + + """ + args, loc, _ = self._parse_args(*args, **kwds) + q, loc = map(asarray, (q, loc)) + args = tuple(map(asarray, args)) + _a, _b = self._get_support(*args) + cond0 = self._argcheck(*args) & (loc == loc) + cond1 = (q > 0) & (q < 1) + cond2 = (q == 1) & cond0 + cond3 = (q == 0) & cond0 + cond = cond0 & cond1 + + # same problem as with ppf; copied from ppf and changed + output = np.full(shape(cond), fill_value=self.badvalue, dtype='d') + # output type 'd' to handle nin and inf + lower_bound = _a - 1 + loc + upper_bound = _b + loc + place(output, cond2, argsreduce(cond2, lower_bound)[0]) + place(output, cond3, argsreduce(cond3, upper_bound)[0]) + + # call place only if at least 1 valid argument + if np.any(cond): + goodargs = argsreduce(cond, *((q,)+args+(loc,))) + loc, goodargs = goodargs[-1], goodargs[:-1] + # PB same as ticket 766 + place(output, cond, self._isf(*goodargs) + loc) + + if output.ndim == 0: + return output[()] + return output + + def _entropy(self, *args): + if hasattr(self, 'pk'): + return stats.entropy(self.pk) + else: + _a, _b = self._get_support(*args) + return _expect(lambda x: entr(self._pmf(x, *args)), + _a, _b, self._ppf(0.5, *args), self.inc) + + def expect(self, func=None, args=(), loc=0, lb=None, ub=None, + conditional=False, maxcount=1000, tolerance=1e-10, chunksize=32): + """ + Calculate expected value of a function with respect to the distribution + for discrete distribution by numerical summation. + + Parameters + ---------- + func : callable, optional + Function for which the expectation value is calculated. + Takes only one argument. + The default is the identity mapping f(k) = k. + args : tuple, optional + Shape parameters of the distribution. + loc : float, optional + Location parameter. + Default is 0. + lb, ub : int, optional + Lower and upper bound for the summation, default is set to the + support of the distribution, inclusive (``lb <= k <= ub``). + conditional : bool, optional + If true then the expectation is corrected by the conditional + probability of the summation interval. The return value is the + expectation of the function, `func`, conditional on being in + the given interval (k such that ``lb <= k <= ub``). + Default is False. + maxcount : int, optional + Maximal number of terms to evaluate (to avoid an endless loop for + an infinite sum). Default is 1000. + tolerance : float, optional + Absolute tolerance for the summation. Default is 1e-10. + chunksize : int, optional + Iterate over the support of a distributions in chunks of this size. + Default is 32. + + Returns + ------- + expect : float + Expected value. + + Notes + ----- + For heavy-tailed distributions, the expected value may or + may not exist, + depending on the function, `func`. If it does exist, but the + sum converges + slowly, the accuracy of the result may be rather low. For instance, for + ``zipf(4)``, accuracy for mean, variance in example is only 1e-5. + increasing `maxcount` and/or `chunksize` may improve the result, + but may also make zipf very slow. + + The function is not vectorized. + + """ + # Although `args` is just the shape parameters, `poisson_binom` needs this + # to split the vector-valued shape into a tuple of separate shapes + args, _, _ = self._parse_args(*args) + + if func is None: + def fun(x): + # loc and args from outer scope + return (x+loc)*self._pmf(x, *args) + else: + def fun(x): + # loc and args from outer scope + return func(x+loc)*self._pmf(x, *args) + # used pmf because _pmf does not check support in randint and there + # might be problems(?) with correct self.a, self.b at this stage maybe + # not anymore, seems to work now with _pmf + + _a, _b = self._get_support(*args) + if lb is None: + lb = _a + else: + lb = lb - loc # convert bound for standardized distribution + if ub is None: + ub = _b + else: + ub = ub - loc # convert bound for standardized distribution + if conditional: + invfac = self.sf(lb-1, *args) - self.sf(ub, *args) + else: + invfac = 1.0 + + if isinstance(self, rv_sample): + res = self._expect(fun, lb, ub) + return res / invfac + + # iterate over the support, starting from the median + x0 = self._ppf(0.5, *args) + res = _expect(fun, lb, ub, x0, self.inc, maxcount, tolerance, chunksize) + return res / invfac + + def _param_info(self): + shape_info = self._shape_info() + loc_info = _ShapeInfo("loc", True, (-np.inf, np.inf), (False, False)) + param_info = shape_info + [loc_info] + return param_info + + # these methods are identical to those of `rv_generic` except that + # they don't accept the `scale` parameter. They need their own copy + # of the methods so we can adjust the documentation. + + def entropy(self, *args, **kwargs): + return super().entropy(*args, **kwargs) + + def interval(self, confidence, *args, **kwargs): + return super().interval(confidence, *args, **kwargs) + + def mean(self, *args, **kwargs): + return super().mean(*args, **kwargs) + + def median(self, *args, **kwargs): + return super().median(*args, **kwargs) + + def moment(self, order, *args, **kwargs): + return super().moment(order, *args, **kwargs) + + def stats(self, *args, **kwargs): + return super().stats(*args, **kwargs) + + def std(self, *args, **kwargs): + return super().std(*args, **kwargs) + + def support(self, *args, **kwargs): + return super().support(*args, **kwargs) + + def var(self, *args, **kwargs): + return super().var(*args, **kwargs) + + +_remove_scale_methods = ['entropy', 'interval', 'mean', 'median', + 'moment', 'stats', 'std', 'support', 'var'] +for method_name in _remove_scale_methods: + # remove `scale` parameter from method documentation + method = getattr(rv_discrete, method_name) + doc = FunctionDoc(method) + doc['Parameters'] = [p for p in doc['Parameters'] if p.name != 'scale'] + doc = str(doc).split("\n", 1)[1].lstrip(" \n") # remove signature + method.__doc__ = doc +del method_name, method, doc # pyrefly:ignore[unbound-name] + + +def _expect(fun, lb, ub, x0, inc, maxcount=1000, tolerance=1e-10, + chunksize=32): + """Helper for computing the expectation value of `fun`.""" + # short-circuit if the support size is small enough + if (ub - lb) <= chunksize: + supp = np.arange(lb, ub+1, inc) + vals = fun(supp) + return np.sum(vals) + + # otherwise, iterate starting from x0 + if x0 < lb: + x0 = lb + if x0 > ub: + x0 = ub + + count, tot = 0, 0. + # iterate over [x0, ub] inclusive + for x in _iter_chunked(x0, ub+1, chunksize=chunksize, inc=inc): + count += x.size + delta = np.sum(fun(x)) + tot += delta + if abs(delta) < tolerance * x.size: + break + if count > maxcount: + warnings.warn('expect(): sum did not converge', + RuntimeWarning, stacklevel=3) + return tot + + # iterate over [lb, x0) + for x in _iter_chunked(x0-1, lb-1, chunksize=chunksize, inc=-inc): + count += x.size + delta = np.sum(fun(x)) + tot += delta + if abs(delta) < tolerance * x.size: + break + if count > maxcount: + warnings.warn('expect(): sum did not converge', + RuntimeWarning, stacklevel=3) + break + + return tot + + +def _iter_chunked(x0, x1, chunksize=4, inc=1): + """Iterate from x0 to x1 in chunks of chunksize and steps inc. + + x0 must be finite, x1 need not be. In the latter case, the iterator is + infinite. + Handles both x0 < x1 and x0 > x1. In the latter case, iterates downwards + (make sure to set inc < 0.) + + >>> from scipy.stats._distn_infrastructure import _iter_chunked + >>> [x for x in _iter_chunked(2, 5, inc=2)] + [array([2, 4])] + >>> [x for x in _iter_chunked(2, 11, inc=2)] + [array([2, 4, 6, 8]), array([10])] + >>> [x for x in _iter_chunked(2, -5, inc=-2)] + [array([ 2, 0, -2, -4])] + >>> [x for x in _iter_chunked(2, -9, inc=-2)] + [array([ 2, 0, -2, -4]), array([-6, -8])] + + """ + if inc == 0: + raise ValueError('Cannot increment by zero.') + if chunksize <= 0: + raise ValueError(f'Chunk size must be positive; got {chunksize}.') + + s = 1 if inc > 0 else -1 + stepsize = abs(chunksize * inc) + + x = np.copy(x0) + while (x - x1) * inc < 0: + delta = min(stepsize, abs(x - x1)) + step = delta * s + supp = np.arange(x, x + step, inc) + x += step + yield supp + + +class rv_sample(rv_discrete): + """A 'sample' discrete distribution defined by the support and values. + + The ctor ignores most of the arguments, only needs the `values` argument. + """ + + def __init__(self, a=0, b=inf, name=None, badvalue=None, + moment_tol=1e-8, values=None, inc=1, longname=None, + shapes=None, seed=None): + + super(rv_discrete, self).__init__(seed) + + if values is None: + raise ValueError("rv_sample.__init__(..., values=None,...)") + + # cf generic freeze + self._ctor_param = dict( + a=a, b=b, name=name, badvalue=badvalue, + moment_tol=moment_tol, values=values, inc=inc, + longname=longname, shapes=shapes, seed=seed) + + if badvalue is None: + badvalue = nan + self.badvalue = badvalue + self.moment_tol = moment_tol + self.inc = inc + self.shapes = shapes + self.vecentropy = self._entropy + + xk, pk = values + + if np.shape(xk) != np.shape(pk): + raise ValueError("xk and pk must have the same shape.") + if np.less(pk, 0.0).any(): + raise ValueError("All elements of pk must be non-negative.") + if not np.allclose(np.sum(pk), 1): + raise ValueError("The sum of provided pk is not 1.") + if not len(set(np.ravel(xk))) == np.size(xk): + raise ValueError("xk may not contain duplicate values.") + + indx = np.argsort(np.ravel(xk)) + self.xk = np.take(np.ravel(xk), indx, 0) + self.pk = np.take(np.ravel(pk), indx, 0) + self.a = self.xk[0] + self.b = self.xk[-1] + + self.qvals = np.cumsum(self.pk, axis=0) + + self.shapes = ' ' # bypass inspection + + self._construct_argparser(meths_to_inspect=[self._pmf], + locscale_in='loc=0', + # scale=1 for discrete RVs + locscale_out='loc, 1') + + self._attach_methods() + + self._construct_docstrings(name, longname) + + def __getstate__(self): + dct = self.__dict__.copy() + + # these methods will be remade in rv_generic.__setstate__, + # which calls rv_generic._attach_methods + attrs = ["_parse_args", "_parse_args_stats", "_parse_args_rvs"] + [dct.pop(attr, None) for attr in attrs] + + return dct + + def _attach_methods(self): + """Attaches dynamically created argparser methods.""" + self._attach_argparser_methods() + + def _get_support(self, *args): + """Return the support of the (unscaled, unshifted) distribution. + + Parameters + ---------- + arg1, arg2, ... : array_like + The shape parameter(s) for the distribution (see docstring of the + instance object for more information). + + Returns + ------- + a, b : numeric (float, or int or +/-np.inf) + end-points of the distribution's support. + """ + return self.a, self.b + + def _pmf(self, x): + return np.select([x == k for k in self.xk], + [np.broadcast_arrays(p, x)[0] for p in self.pk], 0) + + def _cdf(self, x): + xx, xxk = np.broadcast_arrays(x[:, None], self.xk) + indx = np.argmax(xxk > xx, axis=-1) - 1 + return self.qvals[indx] + + def _ppf(self, q): + qq, sqq = np.broadcast_arrays(q[..., None], self.qvals) + indx = argmax(sqq >= qq, axis=-1) + return self.xk[indx] + + def _rvs(self, size=None, random_state=None): + # Need to define it explicitly, otherwise .rvs() with size=None + # fails due to explicit broadcasting in _ppf + U = random_state.uniform(size=size) + if size is None: + U = np.array(U, ndmin=1) + Y = self._ppf(U)[0] + else: + Y = self._ppf(U) + return Y + + def _entropy(self): + return stats.entropy(self.pk) + + def generic_moment(self, n): + n = asarray(n) + return np.sum(self.xk**n[np.newaxis, ...] * self.pk, axis=0) + + def _expect(self, fun, lb, ub, *args, **kwds): + # ignore all args, just do a brute force summation + supp = self.xk[(lb <= self.xk) & (self.xk <= ub)] + vals = fun(supp) + return np.sum(vals) + + +def _check_shape(argshape, size): + """ + This is a utility function used by `_rvs()` in the class geninvgauss_gen. + It compares the tuple argshape to the tuple size. + + Parameters + ---------- + argshape : tuple of integers + Shape of the arguments. + size : tuple of integers or integer + Size argument of rvs(). + + Returns + ------- + The function returns two tuples, scalar_shape and bc. + + scalar_shape : tuple + Shape to which the 1-d array of random variates returned by + _rvs_scalar() is converted when it is copied into the + output array of _rvs(). + + bc : tuple of booleans + bc is a tuple the same length as size. bc[j] is True if the data + associated with that index is generated in one call of _rvs_scalar(). + + """ + scalar_shape = [] + bc = [] + for argdim, sizedim in zip_longest(argshape[::-1], size[::-1], + fillvalue=1): + if sizedim > argdim or (argdim == sizedim == 1): + scalar_shape.append(sizedim) + bc.append(True) + else: + bc.append(False) + return tuple(scalar_shape[::-1]), tuple(bc[::-1]) + + +def get_distribution_names(namespace_pairs, rv_base_class): + """Collect names of statistical distributions and their generators. + + Parameters + ---------- + namespace_pairs : sequence + A snapshot of (name, value) pairs in the namespace of a module. + rv_base_class : class + The base class of random variable generator classes in a module. + + Returns + ------- + distn_names : list of strings + Names of the statistical distributions. + distn_gen_names : list of strings + Names of the generators of the statistical distributions. + Note that these are not simply the names of the statistical + distributions, with a _gen suffix added. + + """ + distn_names = [] + distn_gen_names = [] + for name, value in namespace_pairs: + if name.startswith('_'): + continue + if name.endswith('_gen') and issubclass(value, rv_base_class): + distn_gen_names.append(name) + if isinstance(value, rv_base_class): + distn_names.append(name) + return distn_names, distn_gen_names diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_distr_params.py b/venv/lib/python3.12/site-packages/scipy/stats/_distr_params.py new file mode 100644 index 0000000000000000000000000000000000000000..3234bae358fffd18b70a008a00110c6f3e348530 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_distr_params.py @@ -0,0 +1,300 @@ +""" +Sane parameters for stats.distributions. +""" +import numpy as np + +distcont = [ + ['alpha', (3.5704770516650459,)], + ['anglit', ()], + ['arcsine', ()], + ['argus', (1.0,)], + ['beta', (2.3098496451481823, 0.62687954300963677)], + ['betaprime', (5, 6)], + ['bradford', (0.29891359763170633,)], + ['burr', (10.5, 4.3)], + ['burr12', (10, 4)], + ['cauchy', ()], + ['chi', (78,)], + ['chi2', (55,)], + ['cosine', ()], + ['crystalball', (2.0, 3.0)], + ['dgamma', (1.1023326088288166,)], + ['dpareto_lognorm', (3, 1.2, 1.5, 2)], + ['dweibull', (2.0685080649914673,)], + ['erlang', (10,)], + ['expon', ()], + ['exponnorm', (1.5,)], + ['exponpow', (2.697119160358469,)], + ['exponweib', (2.8923945291034436, 1.9505288745913174)], + ['f', (29, 18)], + ['fatiguelife', (29,)], # correction numargs = 1 + ['fisk', (3.0857548622253179,)], + ['foldcauchy', (4.7164673455831894,)], + ['foldnorm', (1.9521253373555869,)], + ['gamma', (1.9932305483800778,)], + ['gausshyper', (13.763771604130699, 3.1189636648681431, + 2.5145980350183019, 5.1811649903971615)], # veryslow + ['genexpon', (9.1325976465418908, 16.231956600590632, 3.2819552690843983)], + ['genextreme', (-0.1,)], + ['gengamma', (4.4162385429431925, 3.1193091679242761)], + ['gengamma', (4.4162385429431925, -3.1193091679242761)], + ['genhalflogistic', (0.77274727809929322,)], + ['genhyperbolic', (0.5, 1.5, -0.5,)], + ['geninvgauss', (2.3, 1.5)], + ['genlogistic', (0.41192440799679475,)], + ['gennorm', (1.2988442399460265,)], + ['halfgennorm', (0.6748054997000371,)], + ['genpareto', (0.1,)], # use case with finite moments + ['gibrat', ()], + ['gompertz', (0.94743713075105251,)], + ['gumbel_l', ()], + ['gumbel_r', ()], + ['halfcauchy', ()], + ['halflogistic', ()], + ['halfnorm', ()], + ['hypsecant', ()], + ['invgamma', (4.0668996136993067,)], + ['invgauss', (0.14546264555347513,)], + ['invweibull', (10.58,)], + ['irwinhall', (10,)], + ['jf_skew_t', (8, 4)], + ['johnsonsb', (4.3172675099141058, 3.1837781130785063)], + ['johnsonsu', (2.554395574161155, 2.2482281679651965)], + ['kappa4', (0.0, 0.0)], + ['kappa4', (-0.1, 0.1)], + ['kappa4', (0.0, 0.1)], + ['kappa4', (0.1, 0.0)], + ['kappa3', (1.0,)], + ['ksone', (1000,)], # replace 22 by 100 to avoid failing range, ticket 956 + ['kstwo', (10,)], + ['kstwobign', ()], + ['landau', ()], + ['laplace', ()], + ['laplace_asymmetric', (2,)], + ['levy', ()], + ['levy_l', ()], + ['levy_stable', (1.8, -0.5)], + ['loggamma', (0.41411931826052117,)], + ['logistic', ()], + ['loglaplace', (3.2505926592051435,)], + ['lognorm', (0.95368226960575331,)], + ['loguniform', (0.01, 1.25)], + ['lomax', (1.8771398388773268,)], + ['maxwell', ()], + ['mielke', (10.4, 4.6)], + ['moyal', ()], + ['nakagami', (4.9673794866666237,)], + ['ncf', (27, 27, 0.41578441799226107)], + ['nct', (14, 0.24045031331198066)], + ['ncx2', (21, 1.0560465975116415)], + ['norm', ()], + ['norminvgauss', (1.25, 0.5)], + ['pareto', (2.621716532144454,)], + ['pearson3', (0.1,)], + ['pearson3', (-2,)], + ['powerlaw', (1.6591133289905851,)], + ['powerlaw', (0.6591133289905851,)], + ['powerlognorm', (2.1413923530064087, 0.44639540782048337)], + ['powernorm', (4.4453652254590779,)], + ['rayleigh', ()], + ['rdist', (1.6,)], + ['recipinvgauss', (0.63004267809369119,)], + ['reciprocal', (0.01, 1.25)], + ['rel_breitwigner', (36.545206797050334, )], + ['rice', (0.7749725210111873,)], + ['semicircular', ()], + ['skewcauchy', (0.5,)], + ['skewnorm', (4.0,)], + ['studentized_range', (3.0, 10.0)], + ['t', (2.7433514990818093,)], + ['trapezoid', (0.2, 0.8)], + ['triang', (0.15785029824528218,)], + ['truncexpon', (4.6907725456810478,)], + ['truncnorm', (-1.0978730080013919, 2.7306754109031979)], + ['truncnorm', (0.1, 2.)], + ['truncpareto', (1.8, 5.3)], + ['truncpareto', (2, 5)], + ['truncpareto', (-2, 5)], + ['truncweibull_min', (2.5, 0.25, 1.75)], + ['tukeylambda', (3.1321477856738267,)], + ['uniform', ()], + ['vonmises', (3.9939042581071398,)], + ['vonmises_line', (3.9939042581071398,)], + ['wald', ()], + ['weibull_max', (2.8687961709100187,)], + ['weibull_min', (1.7866166930421596,)], + ['wrapcauchy', (0.031071279018614728,)] +] + + +distdiscrete = [ + ['bernoulli',(0.3,)], + ['betabinom', (5, 2.3, 0.63)], + ['betanbinom', (5, 9.3, 1)], + ['binom', (5, 0.4)], + ['boltzmann',(1.4, 19)], + ['dlaplace', (0.8,)], # 0.5 + ['geom', (0.5,)], + ['hypergeom',(30, 12, 6)], + ['hypergeom',(21,3,12)], # numpy.random (3,18,12) numpy ticket:921 + ['hypergeom',(21,18,11)], # numpy.random (18,3,11) numpy ticket:921 + ['nchypergeom_fisher', (140, 80, 60, 0.5)], + ['nchypergeom_wallenius', (140, 80, 60, 0.5)], + ['logser', (0.6,)], # re-enabled, numpy ticket:921 + ['nbinom', (0.4, 0.4)], # from tickets: 583 + ['nbinom', (5, 0.5)], + ['planck', (0.51,)], # 4.1 + ['poisson', (0.6,)], + ['poisson_binom', ([0.1, 0.6, 0.7, 0.8],)], + ['randint', (7, 31)], + ['skellam', (15, 8)], + ['zipf', (6.6,)], + ['zipfian', (0.75, 15)], + ['zipfian', (1.25, 10)], + ['yulesimon', (11.0,)], + ['nhypergeom', (20, 7, 1)] +] + + +invdistdiscrete = [ + # In each of the following, at least one shape parameter is invalid + ['hypergeom', (3, 3, 4)], + ['nhypergeom', (5, 2, 8)], + ['nchypergeom_fisher', (3, 3, 4, 1)], + ['nchypergeom_wallenius', (3, 3, 4, 1)], + ['bernoulli', (1.5, )], + ['binom', (10, 1.5)], + ['betabinom', (10, -0.4, -0.5)], + ['betanbinom', (10, -0.4, -0.5)], + ['boltzmann', (-1, 4)], + ['dlaplace', (-0.5, )], + ['geom', (1.5, )], + ['logser', (1.5, )], + ['nbinom', (10, 1.5)], + ['planck', (-0.5, )], + ['poisson', (-0.5, )], + ['poisson_binom', ([-1, 2, 0.5],)], + ['randint', (5, 2)], + ['skellam', (-5, -2)], + ['zipf', (-2, )], + ['yulesimon', (-2, )], + ['zipfian', (-0.75, 15)] +] + + +invdistcont = [ + # In each of the following, at least one shape parameter is invalid + ['alpha', (-1, )], + ['anglit', ()], + ['arcsine', ()], + ['argus', (-1, )], + ['beta', (-2, 2)], + ['betaprime', (-2, 2)], + ['bradford', (-1, )], + ['burr', (-1, 1)], + ['burr12', (-1, 1)], + ['cauchy', ()], + ['chi', (-1, )], + ['chi2', (-1, )], + ['cosine', ()], + ['crystalball', (-1, 2)], + ['dgamma', (-1, )], + ['dpareto_lognorm', (3, -1.2, 1.5, 2)], + ['dweibull', (-1, )], + ['erlang', (-1, )], + ['expon', ()], + ['exponnorm', (-1, )], + ['exponweib', (1, -1)], + ['exponpow', (-1, )], + ['f', (10, -10)], + ['fatiguelife', (-1, )], + ['fisk', (-1, )], + ['foldcauchy', (-1, )], + ['foldnorm', (-1, )], + ['genlogistic', (-1, )], + ['gennorm', (-1, )], + ['genpareto', (np.inf, )], + ['genexpon', (1, 2, -3)], + ['genextreme', (np.inf, )], + ['genhyperbolic', (0.5, -0.5, -1.5,)], + ['gausshyper', (1, 2, 3, -4)], + ['gamma', (-1, )], + ['gengamma', (-1, 0)], + ['genhalflogistic', (-1, )], + ['geninvgauss', (1, 0)], + ['gibrat', ()], + ['gompertz', (-1, )], + ['gumbel_r', ()], + ['gumbel_l', ()], + ['halfcauchy', ()], + ['halflogistic', ()], + ['halfnorm', ()], + ['halfgennorm', (-1, )], + ['hypsecant', ()], + ['invgamma', (-1, )], + ['invgauss', (-1, )], + ['invweibull', (-1, )], + ['irwinhall', (-1,)], + ['irwinhall', (0,)], + ['irwinhall', (2.5,)], + ['jf_skew_t', (-1, 0)], + ['johnsonsb', (1, -2)], + ['johnsonsu', (1, -2)], + ['kappa4', (np.nan, 0)], + ['kappa3', (-1, )], + ['ksone', (-1, )], + ['kstwo', (-1, )], + ['kstwobign', ()], + ['landau', ()], + ['laplace', ()], + ['laplace_asymmetric', (-1, )], + ['levy', ()], + ['levy_l', ()], + ['levy_stable', (-1, 1)], + ['logistic', ()], + ['loggamma', (-1, )], + ['loglaplace', (-1, )], + ['lognorm', (-1, )], + ['loguniform', (10, 5)], + ['lomax', (-1, )], + ['maxwell', ()], + ['mielke', (1, -2)], + ['moyal', ()], + ['nakagami', (-1, )], + ['ncx2', (-1, 2)], + ['ncf', (10, 20, -1)], + ['nct', (-1, 2)], + ['norm', ()], + ['norminvgauss', (5, -10)], + ['pareto', (-1, )], + ['pearson3', (np.nan, )], + ['powerlaw', (-1, )], + ['powerlognorm', (1, -2)], + ['powernorm', (-1, )], + ['rdist', (-1, )], + ['rayleigh', ()], + ['rice', (-1, )], + ['recipinvgauss', (-1, )], + ['semicircular', ()], + ['skewnorm', (np.inf, )], + ['studentized_range', (-1, 1)], + ['rel_breitwigner', (-2, )], + ['t', (-1, )], + ['trapezoid', (0, 2)], + ['triang', (2, )], + ['truncexpon', (-1, )], + ['truncnorm', (10, 5)], + ['truncpareto', (-1, -5)], + ['truncpareto', (1.8, .5)], + ['truncweibull_min', (-2.5, 0.25, 1.75)], + ['tukeylambda', (np.nan, )], + ['uniform', ()], + ['vonmises', (-1, )], + ['vonmises_line', (-1, )], + ['wald', ()], + ['weibull_min', (-1, )], + ['weibull_max', (-1, )], + ['wrapcauchy', (2, )], + ['reciprocal', (15, 10)], + ['skewcauchy', (2, )] +] diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_distribution_infrastructure.py b/venv/lib/python3.12/site-packages/scipy/stats/_distribution_infrastructure.py new file mode 100644 index 0000000000000000000000000000000000000000..ab05372648e813dc962c4ddcd3fc5171dcab07c0 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_distribution_infrastructure.py @@ -0,0 +1,5764 @@ +import functools +from abc import ABC, abstractmethod +from functools import cached_property +from types import GenericAlias +import inspect +import math + +import numpy as np +from numpy import inf + +from scipy._lib._array_api import xp_capabilities, xp_promote +from scipy._lib._util import _rng_spawn +from scipy._lib._docscrape import ClassDoc, NumpyDocString +from scipy._external import array_api_extra as xpx +from scipy import special, stats +from scipy.special._ufuncs import _log1mexp +from scipy.integrate import tanhsinh as _tanhsinh, nsum +from scipy.optimize import elementwise +from scipy.stats._probability_distribution import _ProbabilityDistribution +from scipy.stats import qmc + +# in case we need to distinguish between None and not specified +# Typically this is used to determine whether the tolerance has been set by the +# user and make a decision about which method to use to evaluate a distribution +# function. Sometimes, the logic does not consider the value of the tolerance, +# only whether this has been defined or not. This is not intended to be the +# best possible logic; the intent is to establish the structure, which can +# be refined in follow-up work. +# See https://github.com/scipy/scipy/pull/21050#discussion_r1714195433. +_null = object() +def _isnull(x): + return type(x) is object or x is None + +__all__ = ['make_distribution', 'Mixture', 'order_statistic', + 'truncate', 'abs', 'exp', 'log'] + +# Could add other policies for broadcasting and edge/out-of-bounds case handling +# For instance, when edge case handling is known not to be needed, it's much +# faster to turn it off, but it might still be nice to have array conversion +# and shaping done so the user doesn't need to be so careful. +_SKIP_ALL = "skip_all" +# Other cache policies would be useful, too. +_NO_CACHE = "no_cache" + +# TODO: +# Test sample dtypes +# Add dtype kwarg (especially for distributions with no parameters) +# Rewrite `test_continuous` without Hypothesis, more modular tests that are easier to +# run individually. +# Add distribution-specific tests - just enough to confirm that distribution is the +# one it is supposed to be; maybe check finite vs undefined vs infinite moments. +# `_tanhsinh` special case when there are no abscissae between the limits +# example: cdf of uniform betweeen 1.0 and np.nextafter(1.0, np.inf) +# implement symmetric distribution (take advantage of symmetry) +# implement composite distribution (for using different implementations depending on +# shape parameters - e.g. t distribution with df = np.inf delegates to normal) +# Add array API support +# Investigate use `median` information throughout, e.g. to improve integration of +# CDF or to provide initial bracket for ICDF. (Only if `_median_formula` is +# provided.) +# Document user tips for faster execution: +# - pass NumPy arrays +# - pass inputs of floating point type (not integers) +# - prefer NumPy scalars or 0d arrays over other size 1 arrays +# - pass no invalid parameters and disable invalid parameter checks (validation_policy) +# - provide a Generator if you're going to do sampling +# Add options for drawing parameters: log-spacing +# Accuracy benchmark suite. Ideally, when all code is array API compatible and mparray +# provides an array-API compatible arbitrary precision array, we can just compare +# results when using NumPy backend against mparray backend. +# Should caches be attributes so we can more easily ensure that they are not +# modified when caching is turned off? +# Make ShiftedScaledDistribution more efficient - only process underlying +# distribution parameters as necessary. +# Reconsider `all_inclusive` - see comment in `contains` method of `_interval` +# `validation_policy` needs testing +# `tol` does not offer very fine-grained control; consider improving +# report accuracy estimates? How? +# reconsider distribution mutability + +# Originally, I planned to filter out invalid distribution parameters; +# distribution implementation functions would always work with "compressed", +# 1D arrays containing only valid distribution parameters. There are two +# problems with this: +# - This essentially requires copying all arrays, even if there is only a +# single invalid parameter combination. This is expensive. Then, to output +# the original size data to the user, we need to "decompress" the arrays +# and fill in the NaNs, so more copying. Unless we branch the code when +# there are no invalid data, these copies happen even in the normal case, +# where there are no invalid parameter combinations. We should not incur +# all this overhead in the normal case. +# - For methods that accept arguments other than distribution parameters, the +# user will pass in arrays that are broadcastable with the original arrays, +# not the compressed arrays. This means that this same sort of invalid +# value detection needs to be repeated every time one of these methods is +# called. +# The much simpler solution is to keep the data uncompressed but to replace +# the invalid parameters and arguments with NaNs (and only if some are +# invalid). With this approach, the copying happens only if/when it is +# needed. Most functions involved in stats distribution calculations don't +# mind NaNs; they just return NaN. The behavior "If x_i is NaN, the result +# is NaN" is explicit in the array API. So this should be fine. +# +# Currently, I am still leaving the parameters and function arguments +# in their broadcasted shapes rather than, say, raveling. The intent +# is to avoid back and forth reshaping. If authors of distributions have +# trouble dealing with N-D arrays, we can reconsider this. +# +# Another important decision is that the *private* methods must accept +# the distribution parameters as inputs rather than relying on these +# cached properties directly (although the public methods typically pass +# the cached values to the private methods). This is because the elementwise +# algorithms for quadrature, differentiation, root-finding, and minimization +# prefer that the input functions are strictly elementwise in the sense +# that the value output for a given input element does not depend on the +# shape of the input or that element's location within the input array. +# When the computation has converged for an element, it is removed from +# the computation entirely. As a result, the shape of the arrays passed to +# the function will almost never be broadcastable with the shape of the +# cached parameter arrays. +# +# I've sprinkled in some optimizations for scalars and same-shape/type arrays +# throughout. The biggest time sinks before were: +# - broadcast_arrays +# - result_dtype +# - is_subdtype +# It is much faster to check whether these are necessary than to do them. + + +class _Domain(ABC): + r""" Representation of the applicable domain of a parameter or variable. + + A `_Domain` object is responsible for storing information about the + domain of a parameter or variable, determining whether a value is within + the domain (`contains`), and providing a text/mathematical representation + of itself (`__str__`). Because the domain of a parameter/variable can have + a complicated relationship with other parameters and variables of a + distribution, `_Domain` itself does not try to represent all possibilities; + in fact, it has no implementation and is meant for subclassing. + + Attributes + ---------- + symbols : dict + A map from special numerical values to symbols for use in `__str__` + + Methods + ------- + contains(x) + Determine whether the argument is contained within the domain (True) + or not (False). Used for input validation. + get_numerical_endpoints() + Gets the numerical values of the domain endpoints, which may have been + defined symbolically or through a callable. + __str__() + Returns a text representation of the domain (e.g. ``[0, b)``). + Used for generating documentation. + + """ + symbols = {np.inf: r"\infty", -np.inf: r"-\infty", np.pi: r"\pi", -np.pi: r"-\pi"} + + # generic type compatibility with scipy-stubs + __class_getitem__: classmethod = classmethod(GenericAlias) + + @abstractmethod + def contains(self, x): + raise NotImplementedError() + + @abstractmethod + def draw(self, n): + raise NotImplementedError() + + @abstractmethod + def get_numerical_endpoints(self, x): + raise NotImplementedError() + + @abstractmethod + def __str__(self): + raise NotImplementedError() + + +class _Interval(_Domain): + r""" Representation of an interval defined by two endpoints. + + Each endpoint may be a finite scalar, positive or negative infinity, or + be given by a single parameter. The domain may include the endpoints or + not. + + This class still does not provide an implementation of the __str__ method, + so it is meant for subclassing (e.g. a subclass for domains on the real + line). + + Attributes + ---------- + symbols : dict + Inherited. A map from special values to symbols for use in `__str__`. + endpoints : 2-tuple of float(s) and/or str(s) and/or callable(s). + A tuple with two values. Each may be either a float (the numerical + value of the endpoints of the domain), a string (the name of the + parameters that will define the endpoint), or a callable taking the + parameters used to define the endpoints of the domain as keyword only + arguments and returning a numerical value for the endpoint. + inclusive : 2-tuple of bools + A tuple with two boolean values; each indicates whether the + corresponding endpoint is included within the domain or not. + + Methods + ------- + define_parameters(*parameters) + Records any parameters used to define the endpoints of the domain + get_numerical_endpoints(parameter_values) + Gets the numerical values of the domain endpoints, which may have been + defined symbolically or through a callable. + contains(item, parameter_values) + Determines whether the argument is contained within the domain + draw(size, rng, proportions, parameter_values) + Draws random values based on the domain. + + """ + def __init__(self, endpoints=(-inf, inf), inclusive=(False, False)): + self.symbols = super().symbols.copy() + a, b = endpoints + self.endpoints = np.asarray(a)[()], np.asarray(b)[()] + self.inclusive = inclusive + + def define_parameters(self, *parameters): + r""" Records any parameters used to define the endpoints of the domain. + + Adds the keyword name of each parameter and its text representation + to the `symbols` attribute as key:value pairs. + For instance, a parameter may be passed into to a distribution's + initializer using the keyword `log_a`, and the corresponding + string representation may be '\log(a)'. To form the text + representation of the domain for use in documentation, the + _Domain object needs to map from the keyword name used in the code + to the string representation. + + Returns None, but updates the `symbols` attribute. + + Parameters + ---------- + *parameters : _Parameter objects + Parameters that may define the endpoints of the domain. + + """ + new_symbols = {param.name: param.symbol for param in parameters} + self.symbols.update(new_symbols) + + def get_numerical_endpoints(self, parameter_values): + r""" Get the numerical values of the domain endpoints. + + Domain endpoints may be defined symbolically or through a callable. + This returns numerical values of the endpoints given numerical values for + any variables. + + Parameters + ---------- + parameter_values : dict + A dictionary that maps between string variable names and numerical + values of parameters, which may define the endpoints. + + Returns + ------- + a, b : ndarray + Numerical values of the endpoints + + """ + a, b = self.endpoints + # If `a` (`b`) is a string - the name of the parameter that defines + # the endpoint of the domain - then corresponding numerical values + # will be found in the `parameter_values` dictionary. + # If a callable, it will be executed with `parameter_values` passed as + # keyword arguments, and it will return the numerical values. + # Otherwise, it is itself the array of numerical values of the endpoint. + try: + if callable(a): + a = a(**parameter_values) + else: + a = np.asarray(parameter_values.get(a, a)) + if callable(b): + b = b(**parameter_values) + else: + b = np.asarray(parameter_values.get(b, b)) + except TypeError as e: + message = ("The endpoints of the distribution are defined by " + "parameters, but their values were not provided. When " + f"using a private method of {self.__class__}, pass " + "all required distribution parameters as keyword " + "arguments.") + raise TypeError(message) from e + # Floating point types are used for even integer parameters. + # Convert to float here to ensure consistency throughout framework. + a, b = xp_promote(a, b, force_floating=True, xp=np) + return a, b + + def contains(self, item, parameter_values=None): + r"""Determine whether the argument is contained within the domain. + + Parameters + ---------- + item : ndarray + The argument + parameter_values : dict + A dictionary that maps between string variable names and numerical + values of parameters, which may define the endpoints. + + Returns + ------- + out : bool + True if `item` is within the domain; False otherwise. + + """ + parameter_values = parameter_values or {} + # if self.all_inclusive: + # # Returning a 0d value here makes things much faster. + # # I'm not sure if it's safe, though. If it causes a bug someday, + # # I guess it wasn't. + # # Even if there is no bug because of the shape, it is incorrect for + # # `contains` to return True when there are invalid (e.g. NaN) + # # parameters. + # return np.asarray(True) + + a, b = self.get_numerical_endpoints(parameter_values) + left_inclusive, right_inclusive = self.inclusive + + in_left = item >= a if left_inclusive else item > a + in_right = item <= b if right_inclusive else item < b + return in_left & in_right + + def draw(self, n, type_, min, max, squeezed_base_shape, rng=None): + r""" Draw random values from the domain. + + Parameters + ---------- + n : int + The number of values to be drawn from the domain. + type_ : str + A string indicating whether the values are + + - strictly within the domain ('in'), + - at one of the two endpoints ('on'), + - strictly outside the domain ('out'), or + - NaN ('nan'). + min, max : ndarray + The endpoints of the domain. + squeezed_based_shape : tuple of ints + See _RealParameter.draw. + rng : np.Generator + The Generator used for drawing random values. + + """ + rng = np.random.default_rng(rng) + + def ints(*args, **kwargs): return rng.integers(*args, **kwargs, endpoint=True) + uniform = rng.uniform if isinstance(self, _RealInterval) else ints + + # get copies of min and max with no nans so that uniform doesn't fail + min_nn, max_nn = min.copy(), max.copy() + i = np.isnan(min_nn) | np.isnan(max_nn) + min_nn[i] = 0 + max_nn[i] = 1 + + shape = (n,) + squeezed_base_shape + + if type_ == 'in': + z = uniform(min_nn, max_nn, size=shape) + + elif type_ == 'on': + z_on_shape = shape + z = np.ones(z_on_shape) + i = rng.random(size=n) < 0.5 + z[i] = min + z[~i] = max + + elif type_ == 'out': + z = min_nn - uniform(1, 5, size=shape) # 1, 5 is arbitary; we just want + zr = max_nn + uniform(1, 5, size=shape) # some numbers outside domain + i = rng.random(size=n) < 0.5 + z[i] = zr[i] + + elif type_ == 'nan': + z = np.full(shape, np.nan) + + return z + + +class _RealInterval(_Interval): + r""" Represents a simply-connected subset of the real line; i.e., an interval + + Completes the implementation of the `_Interval` class for intervals + on the real line. + + Methods + ------- + define_parameters(*parameters) + (Inherited) Records any parameters used to define the endpoints of the + domain. + get_numerical_endpoints(parameter_values) + (Inherited) Gets the numerical values of the domain endpoints, which + may have been defined symbolically. + contains(item, parameter_values) + (Inherited) Determines whether the argument is contained within the + domain + __str__() + Returns a string representation of the domain, e.g. "[a, b)". + """ + + def __str__(self): + a, b = self.endpoints + a, b = self._get_endpoint_str(a, "f1"), self._get_endpoint_str(b, "f2") + left_inclusive, right_inclusive = self.inclusive + left = "[" if left_inclusive else "(" + right = "]" if right_inclusive else ")" + + return f"{left}{a}, {b}{right}" + + def _get_endpoint_str(self, endpoint, funcname): + if callable(endpoint): + if endpoint.__doc__ is not None: + return endpoint.__doc__ + params = inspect.signature(endpoint).parameters.values() + params = [ + p.name for p in params if p.kind == inspect.Parameter.KEYWORD_ONLY + ] + return f"{funcname}({','.join(params)})" + return self.symbols.get(endpoint, f"{endpoint}") + + +class _IntegerInterval(_Interval): + r""" Represents an interval of integers + + Completes the implementation of the `_Interval` class for simple + domains on the integers. + + Methods + ------- + define_parameters(*parameters) + (Inherited) Records any parameters used to define the endpoints of the + domain. + get_numerical_endpoints(parameter_values) + (Inherited) Gets the numerical values of the domain endpoints, which + may have been defined symbolically. + contains(item, parameter_values) + (Overridden) Determines whether the argument is contained within the + domain + draw(n, type_, min, max, squeezed_base_shape, rng=None) + (Inherited) Draws random values based on the domain. + __str__() + Returns a string representation of the domain, e.g. "{a, a+1, ..., b-1, b}". + + """ + def contains(self, item, parameter_values=None): + super_contains = super().contains(item, parameter_values) + integral = (item == np.round(item)) + return super_contains & integral + + def __str__(self): + a, b = self.endpoints + a = self.symbols.get(a, a) + b = self.symbols.get(b, b) + + a_str, b_str = isinstance(a, str), isinstance(b, str) + a_inf = a == r"-\infty" if a_str else np.isinf(a) + b_inf = b == r"\infty" if b_str else np.isinf(b) + + # This doesn't work well for cases where ``a`` is floating point + # number large enough that ``nextafter(a, inf) > a + 1``, and + # similarly for ``b`` and nextafter(b, -inf). There may not be any + # distributions fit for SciPy where we would actually need to handle these + # cases though. + ap1 = f"{a} + 1" if a_str else f"{a + 1}" + bm1 = f"{b} - 1" if b_str else f"{b - 1}" + + if not a_str and not b_str: + gap = b - a + if gap == 3: + return f"\\{{{a}, {ap1}, {bm1}, {b}\\}}" + if gap == 2: + return f"\\{{{a}, {ap1}, {b}\\}}" + if gap == 1: + return f"\\{{{a}, {b}\\}}" + if gap == 0: + return f"\\{{{a}\\}}" + + if not a_inf and b_inf: + ap2 = f"{a} + 2" if a_str else f"{a + 2}" + return f"\\{{{a}, {ap1}, {ap2}, ...\\}}" + if a_inf and not b_inf: + bm2 = f"{b} - 2" if b_str else f"{b - 2}" + return f"\\{{{b}, {bm1}, {bm2}, ...\\}}" + if a_inf and b_inf: + return "\\{..., -2, -1, 0, 1, 2, ...\\}" + + return f"\\{{{a}, {ap1}, ..., {bm1}, {b}\\}}" + + +class _Parameter(ABC): + r""" Representation of a distribution parameter or variable. + + A `_Parameter` object is responsible for storing information about a + parameter or variable, providing input validation/standardization of + values passed for that parameter, providing a text/mathematical + representation of the parameter for the documentation (`__str__`), and + drawing random values of itself for testing and benchmarking. It does + not provide a complete implementation of this functionality and is meant + for subclassing. + + Attributes + ---------- + name : str + The keyword used to pass numerical values of the parameter into the + initializer of the distribution + symbol : str + The text representation of the variable in the documentation. May + include LaTeX. + domain : _Domain + The domain of the parameter for which the distribution is valid. + typical : 2-tuple of floats or strings (consider making a _Domain) + Defines the endpoints of a typical range of values of the parameter. + Used for sampling. + + Methods + ------- + __str__(): + Returns a string description of the variable for use in documentation, + including the keyword used to represent it in code, the symbol used to + represent it mathemtatically, and a description of the valid domain. + draw(size, *, rng, domain, proportions) + Draws random values of the parameter. Proportions of values within + the valid domain, on the endpoints of the domain, outside the domain, + and having value NaN are specified by `proportions`. + validate(x): + Validates and standardizes the argument for use as numerical values + of the parameter. + + """ + + # generic type compatibility with scipy-stubs + __class_getitem__: classmethod = classmethod(GenericAlias) + + def __init__(self, name, *, domain, symbol=None, typical=None): + self.name = name + self.symbol = symbol or name + self.domain = domain + if typical is not None and not isinstance(typical, _Domain): + typical = domain.__class__(typical) + self.typical = typical or domain + + def __str__(self): + r""" String representation of the parameter for use in documentation.""" + return f"`{self.name}` for :math:`{self.symbol} \\in {str(self.domain)}`" + + def draw(self, size=None, *, rng=None, region='domain', proportions=None, + parameter_values=None): + r""" Draw random values of the parameter for use in testing. + + Parameters + ---------- + size : tuple of ints + The shape of the array of valid values to be drawn. + rng : np.Generator + The Generator used for drawing random values. + region : str + The region of the `_Parameter` from which to draw. Default is + "domain" (the *full* domain); alternative is "typical". An + enhancement would give a way to interpolate between the two. + proportions : tuple of numbers + A tuple of four non-negative numbers that indicate the expected + relative proportion of elements that: + + - are strictly within the domain, + - are at one of the two endpoints, + - are strictly outside the domain, and + - are NaN, + + respectively. Default is (1, 0, 0, 0). The number of elements in + each category is drawn from the multinomial distribution with + `np.prod(size)` as the number of trials and `proportions` as the + event probabilities. The values in `proportions` are automatically + normalized to sum to 1. + parameter_values : dict + Map between the names of parameters (that define the endpoints of + `typical`) and numerical values (arrays). + + """ + parameter_values = parameter_values or {} + domain = self.domain + proportions = (1, 0, 0, 0) if proportions is None else proportions + + pvals = proportions / np.sum(proportions) + + a, b = domain.get_numerical_endpoints(parameter_values) + a, b = np.broadcast_arrays(a, b) + + base_shape = a.shape + extended_shape = np.broadcast_shapes(size, base_shape) + n_extended = np.prod(extended_shape) + n_base = np.prod(base_shape) + n = int(n_extended / n_base) if n_extended else 0 + + rng = np.random.default_rng(rng) + n_in, n_on, n_out, n_nan = rng.multinomial(n, pvals) + + # `min` and `max` can have singleton dimensions that correspond with + # non-singleton dimensions in `size`. We need to be careful to avoid + # shuffling results (e.g. a value that was generated for the domain + # [min[i], max[i]] ends up at index j). To avoid this: + # - Squeeze the singleton dimensions out of `min`/`max`. Squeezing is + # often not the right thing to do, but here is equivalent to moving + # all the dimensions that are singleton in `min`/`max` (which may be + # non-singleton in the result) to the left. This is what we want. + # - Now all the non-singleton dimensions of the result are on the left. + # Ravel them to a single dimension of length `n`, which is now along + # the 0th axis. + # - Reshape the 0th axis back to the required dimensions, and move + # these axes back to their original places. + base_shape_padded = ((1,)*(len(extended_shape) - len(base_shape)) + + base_shape) + base_singletons = np.where(np.asarray(base_shape_padded)==1)[0] + new_base_singletons = tuple(range(len(base_singletons))) + # Base singleton dimensions are going to get expanded to these lengths + shape_expansion = np.asarray(extended_shape)[base_singletons] + + # assert(np.prod(shape_expansion) == n) # check understanding + # min = np.reshape(min, base_shape_padded) + # max = np.reshape(max, base_shape_padded) + # min = np.moveaxis(min, base_singletons, new_base_singletons) + # max = np.moveaxis(max, base_singletons, new_base_singletons) + # squeezed_base_shape = max.shape[len(base_singletons):] + # assert np.all(min.reshape(squeezed_base_shape) == min.squeeze()) + # assert np.all(max.reshape(squeezed_base_shape) == max.squeeze()) + + # min = np.maximum(a, _fiinfo(a).min/10) if np.any(np.isinf(a)) else a + # max = np.minimum(b, _fiinfo(b).max/10) if np.any(np.isinf(b)) else b + min = np.asarray(a.squeeze()) + max = np.asarray(b.squeeze()) + squeezed_base_shape = max.shape + + if region == 'typical': + typical = self.typical + a, b = typical.get_numerical_endpoints(parameter_values) + a, b = np.broadcast_arrays(a, b) + min_here = np.asarray(a.squeeze()) + max_here = np.asarray(b.squeeze()) + z_in = typical.draw(n_in, 'in', min_here, max_here, squeezed_base_shape, + rng=rng) + else: + z_in = domain.draw(n_in, 'in', min, max, squeezed_base_shape, rng=rng) + z_on = domain.draw(n_on, 'on', min, max, squeezed_base_shape, rng=rng) + z_out = domain.draw(n_out, 'out', min, max, squeezed_base_shape, rng=rng) + z_nan= domain.draw(n_nan, 'nan', min, max, squeezed_base_shape, rng=rng) + + z = np.concatenate((z_in, z_on, z_out, z_nan), axis=0) + z = rng.permuted(z, axis=0) + + z = np.reshape(z, tuple(shape_expansion) + squeezed_base_shape) + z = np.moveaxis(z, new_base_singletons, base_singletons) + return z + + @abstractmethod + def validate(self, arr): + raise NotImplementedError() + + +class _RealParameter(_Parameter): + r""" Represents a real-valued parameter. + + Implements the remaining methods of _Parameter for real parameters. + All attributes are inherited. + + """ + def validate(self, arr, parameter_values): + r""" Input validation/standardization of numerical values of a parameter. + + Checks whether elements of the argument `arr` are reals, ensuring that + the dtype reflects this. Also produces a logical array that indicates + which elements meet the requirements. + + Parameters + ---------- + arr : ndarray + The argument array to be validated and standardized. + parameter_values : dict + Map of parameter names to parameter value arrays. + + Returns + ------- + arr : ndarray + The argument array that has been validated and standardized + (converted to an appropriate dtype, if necessary). + dtype : NumPy dtype + The appropriate floating point dtype of the parameter. + valid : bool ndarray + Logical array indicating which elements are valid (True) and + which are not (False). The arrays of all distribution parameters + will be broadcasted, and elements for which any parameter value + does not meet the requirements will be replaced with NaN. + + """ + arr = np.asarray(arr) + + valid_dtype = None + # minor optimization - fast track the most common types to avoid + # overhead of np.issubdtype. Checking for `in {...}` doesn't work : / + if arr.dtype == np.float64 or arr.dtype == np.float32: + pass + elif arr.dtype == np.int32 or arr.dtype == np.int64: + arr = np.asarray(arr, dtype=np.float64) + elif np.issubdtype(arr.dtype, np.floating): + pass + elif np.issubdtype(arr.dtype, np.integer): + arr = np.asarray(arr, dtype=np.float64) + else: + message = f"Parameter `{self.name}` must be of real dtype." + raise TypeError(message) + + valid = self.domain.contains(arr, parameter_values) + valid = valid & valid_dtype if valid_dtype is not None else valid + + return arr[()], arr.dtype, valid + + +class _Parameterization: + r""" Represents a parameterization of a distribution. + + Distributions can have multiple parameterizations. A `_Parameterization` + object is responsible for recording the parameters used by the + parameterization, checking whether keyword arguments passed to the + distribution match the parameterization, and performing input validation + of the numerical values of these parameters. + + Attributes + ---------- + parameters : dict + String names (of keyword arguments) and the corresponding _Parameters. + + Methods + ------- + __len__() + Returns the number of parameters in the parameterization. + __str__() + Returns a string representation of the parameterization. + copy + Returns a copy of the parameterization. This is needed for transformed + distributions that add parameters to the parameterization. + matches(parameters) + Checks whether the keyword arguments match the parameterization. + validation(parameter_values) + Input validation / standardization of parameterization. Validates the + numerical values of all parameters. + draw(sizes, rng, proportions) + Draw random values of all parameters of the parameterization for use + in testing. + """ + def __init__(self, *parameters): + self.parameters = {param.name: param for param in parameters} + + def __len__(self): + return len(self.parameters) + + def copy(self): + return _Parameterization(*self.parameters.values()) + + def matches(self, parameters): + r""" Checks whether the keyword arguments match the parameterization. + + Parameters + ---------- + parameters : set + Set of names of parameters passed into the distribution as keyword + arguments. + + Returns + ------- + out : bool + True if the keyword arguments names match the names of the + parameters of this parameterization. + """ + return parameters == set(self.parameters.keys()) + + def validation(self, parameter_values): + r""" Input validation / standardization of parameterization. + + Parameters + ---------- + parameter_values : dict + The keyword arguments passed as parameter values to the + distribution. + + Returns + ------- + all_valid : ndarray + Logical array indicating the elements of the broadcasted arrays + for which all parameter values are valid. + dtype : dtype + The common dtype of the parameter arrays. This will determine + the dtype of the output of distribution methods. + """ + all_valid = True + dtypes = set() # avoid np.result_type if there's only one type + for name, arr in parameter_values.items(): + parameter = self.parameters[name] + arr, dtype, valid = parameter.validate(arr, parameter_values) + dtypes.add(dtype) + all_valid = all_valid & valid + parameter_values[name] = arr + dtype = arr.dtype if len(dtypes)==1 else np.result_type(*list(dtypes)) + + return all_valid, dtype + + def __str__(self): + r"""Returns a string representation of the parameterization.""" + messages = [str(param) for name, param in self.parameters.items()] + return ", ".join(messages) + + def draw(self, sizes=None, rng=None, proportions=None, region='domain'): + r"""Draw random values of all parameters for use in testing. + + Parameters + ---------- + sizes : iterable of shape tuples + The size of the array to be generated for each parameter in the + parameterization. Note that the order of sizes is arbitary; the + size of the array generated for a specific parameter is not + controlled individually as written. + rng : NumPy Generator + The generator used to draw random values. + proportions : tuple + A tuple of four non-negative numbers that indicate the expected + relative proportion of elements that are within the parameter's + domain, are on the boundary of the parameter's domain, are outside + the parameter's domain, and have value NaN. For more information, + see the `draw` method of the _Parameter subclasses. + domain : str + The domain of the `_Parameter` from which to draw. Default is + "domain" (the *full* domain); alternative is "typical". + + Returns + ------- + parameter_values : dict (string: array) + A dictionary of parameter name/value pairs. + """ + # ENH: be smart about the order. The domains of some parameters + # depend on others. If the relationshp is simple (e.g. a < b < c), + # we can draw values in order a, b, c. + parameter_values = {} + + if sizes is None or not len(sizes) or not np.iterable(sizes[0]): + sizes = [sizes]*len(self.parameters) + + for size, param in zip(sizes, self.parameters.values()): + parameter_values[param.name] = param.draw( + size, rng=rng, proportions=proportions, + parameter_values=parameter_values, + region=region + ) + + return parameter_values + + +def _set_invalid_nan(f): + # Wrapper for input / output validation and standardization of distribution + # functions that accept either the quantile or percentile as an argument: + # logpdf, pdf + # logpmf, pmf + # logcdf, cdf + # logccdf, ccdf + # ilogcdf, icdf + # ilogccdf, iccdf + # Arguments that are outside the required range are replaced by NaN before + # passing them into the underlying function. The corresponding outputs + # are replaced by the appropriate value before being returned to the user. + # For example, when the argument of `cdf` exceeds the right end of the + # distribution's support, the wrapper replaces the argument with NaN, + # ignores the output of the underlying function, and returns 1.0. It also + # ensures that output is of the appropriate shape and dtype. + + endpoints = {'icdf': (0, 1), 'iccdf': (0, 1), + 'ilogcdf': (-np.inf, 0), 'ilogccdf': (-np.inf, 0)} + replacements = {'logpdf': (-inf, -inf), 'pdf': (0, 0), + 'logpmf': (-inf, -inf), 'pmf': (0, 0), + '_logcdf1': (-inf, 0), '_logccdf1': (0, -inf), + '_cdf1': (0, 1), '_ccdf1': (1, 0)} + replace_strict = {'pdf', 'logpdf', 'pmf', 'logpmf'} + replace_exact = {'icdf', 'iccdf', 'ilogcdf', 'ilogccdf'} + clip = {'_cdf1', '_ccdf1'} + clip_log = {'_logcdf1', '_logccdf1'} + # relevant to discrete distributions only + replace_non_integral = {'pmf', 'logpmf', 'pdf', 'logpdf'} + + @functools.wraps(f) + def filtered(self, x, *args, **kwargs): + if self.validation_policy == _SKIP_ALL: + return f(self, x, *args, **kwargs) + + method_name = f.__name__ + x = np.asarray(x) + dtype = self._dtype + shape = self._shape + discrete = isinstance(self, DiscreteDistribution) + keep_low_endpoint = discrete and method_name in {'_cdf1', '_logcdf1', + '_ccdf1', '_logccdf1'} + + # Ensure that argument is at least as precise as distribution + # parameters, which are already at least floats. This will avoid issues + # with raising integers to negative integer powers and failure to replace + # invalid integers with NaNs. + if x.dtype != dtype: + dtype = np.result_type(x.dtype, dtype) + x = np.asarray(x, dtype=dtype) + + # Broadcasting is slow. Do it only if necessary. + if not x.shape == shape: + try: + shape = np.broadcast_shapes(x.shape, shape) + x = np.broadcast_to(x, shape) + # Should we broadcast the distribution parameters to this shape, too? + except ValueError as e: + message = ( + f"The argument provided to `{self.__class__.__name__}" + f".{method_name}` cannot be be broadcast to the same " + "shape as the distribution parameters.") + raise ValueError(message) from e + + low, high = endpoints.get(method_name, self.support()) + + # Check for arguments outside of domain. They'll be replaced with NaNs, + # and the result will be set to the appropriate value. + left_inc, right_inc = self._variable.domain.inclusive + mask_low = (x < low if (method_name in replace_strict and left_inc) + or keep_low_endpoint else x <= low) + mask_high = (x > high if (method_name in replace_strict and right_inc) + else x >= high) + mask_invalid = (mask_low | mask_high) + any_invalid = (mask_invalid if mask_invalid.shape == () + else np.any(mask_invalid)) + + # Check for arguments at domain endpoints, whether they + # are part of the domain or not. + any_endpoint = False + if method_name in replace_exact: + mask_low_endpoint = (x == low) + mask_high_endpoint = (x == high) + mask_endpoint = (mask_low_endpoint | mask_high_endpoint) + any_endpoint = (mask_endpoint if mask_endpoint.shape == () + else np.any(mask_endpoint)) + + # Check for non-integral arguments to PMF method + # or PDF of a discrete distribution. + any_non_integral = False + if discrete and method_name in replace_non_integral: + mask_non_integral = (x != np.floor(x)) + any_non_integral = (mask_non_integral if mask_non_integral.shape == () + else np.any(mask_non_integral)) + + # Set out-of-domain arguments to NaN. The result will be set to the + # appropriate value later. + if any_invalid: + x = np.array(x, dtype=dtype, copy=True) + x[mask_invalid] = np.nan + + res = np.asarray(f(self, x, *args, **kwargs)) + + # Ensure that the result is the correct dtype and shape, + # copying (only once) if necessary. + res_needs_copy = False + if res.dtype != dtype: + dtype = np.result_type(dtype, self._dtype) + res_needs_copy = True + + if res.shape != shape: # faster to check first + res = np.broadcast_to(res, self._shape) + res_needs_copy = (res_needs_copy or any_invalid + or any_endpoint or any_non_integral) + + if res_needs_copy: + res = np.array(res, dtype=dtype, copy=True) + + # For non-integral arguments to PMF (and PDF of discrete distribution) + # replace with zero. + if any_non_integral: + zero = -np.inf if method_name in {'logpmf', 'logpdf'} else 0 + res[mask_non_integral & ~np.isnan(res)] = zero + + # For arguments outside the function domain, replace results + if any_invalid: + replace_low, replace_high = ( + replacements.get(method_name, (np.nan, np.nan))) + res[mask_low] = replace_low + res[mask_high] = replace_high + + # For arguments at the endpoints of the domain, replace results + if any_endpoint: + a, b = self.support() + if a.shape != shape: + a = np.array(np.broadcast_to(a, shape), copy=True) + b = np.array(np.broadcast_to(b, shape), copy=True) + + replace_low_endpoint = ( + b[mask_low_endpoint] if method_name.endswith('ccdf') + else a[mask_low_endpoint]) + replace_high_endpoint = ( + a[mask_high_endpoint] if method_name.endswith('ccdf') + else b[mask_high_endpoint]) + + if not keep_low_endpoint: + res[mask_low_endpoint] = replace_low_endpoint + res[mask_high_endpoint] = replace_high_endpoint + + # Clip probabilities to [0, 1] + if method_name in clip: + res = np.clip(res, 0., 1.) + elif method_name in clip_log: + res = res.real # exp(res) > 0 + res = np.clip(res, None, 0.) # exp(res) < 1 + + return res[()] + + return filtered + + +def _set_invalid_nan_property(f): + # Wrapper for input / output validation and standardization of distribution + # functions that represent properties of the distribution itself: + # logentropy, entropy + # median, mode + # moment + # It ensures that the output is of the correct shape and dtype and that + # there are NaNs wherever the distribution parameters were invalid. + + @functools.wraps(f) + def filtered(self, *args, **kwargs): + if self.validation_policy == _SKIP_ALL: + return f(self, *args, **kwargs) + + res = f(self, *args, **kwargs) + if res is None: + # message could be more appropriate + raise NotImplementedError(self._not_implemented) + + res = np.asarray(res) + needs_copy = False + dtype = res.dtype + + if dtype != self._dtype: # this won't work for logmoments (complex) + dtype = np.result_type(dtype, self._dtype) + needs_copy = True + + if res.shape != self._shape: # faster to check first + res = np.broadcast_to(res, self._shape) + needs_copy = needs_copy or self._any_invalid + + if needs_copy: + res = res.astype(dtype=dtype, copy=True) + + if self._any_invalid: + # may be redundant when quadrature is used, but not necessarily + # when formulas are used. + res[self._invalid] = np.nan + + return res[()] + + return filtered + + +def _dispatch(f): + # For each public method (instance function) of a distribution (e.g. ccdf), + # there may be several ways ("method"s) that it can be computed (e.g. a + # formula, as the complement of the CDF, or via numerical integration). + # Each "method" is implemented by a different private method (instance + # function). + # This wrapper calls the appropriate private method based on the public + # method and any specified `method` keyword option. + # - If `method` is specified as a string (by the user), the appropriate + # private method is called. + # - If `method` is None: + # - The appropriate private method for the public method is looked up + # in a cache. + # - If the cache does not have an entry for the public method, the + # appropriate "dispatch " function is called to determine which method + # is most appropriate given the available private methods and + # settings (e.g. tolerance). + + @functools.wraps(f) + def wrapped(self, *args, method=None, **kwargs): + func_name = f.__name__ + method = method or self._method_cache.get(func_name, None) + if callable(method): + pass + elif method is not None: + method = 'logexp' if method == 'log/exp' else method + method_name = func_name.replace('dispatch', method) + method = getattr(self, method_name) + else: + method = f(self, *args, method=method, **kwargs) + if func_name != '_sample_dispatch' and self.cache_policy != _NO_CACHE: + self._method_cache[func_name] = method + + try: + return method(*args, **kwargs) + except KeyError as e: + raise NotImplementedError(self._not_implemented) from e + + return wrapped + + +def _cdf2_input_validation(f): + # Wrapper that does the job of `_set_invalid_nan` when `cdf` or `logcdf` + # is called with two quantile arguments. + # Let's keep it simple; no special cases for speed right now. + # The strategy is a bit different than for 1-arg `cdf` (and other methods + # covered by `_set_invalid_nan`). For 1-arg `cdf`, elements of `x` that + # are outside (or at the edge of) the support get replaced by `nan`, + # and then the results get replaced by the appropriate value (0 or 1). + # We *could* do something similar, dispatching to `_cdf1` in these + # cases. That would be a bit more robust, but it would also be quite + # a bit more complex, since we'd have to do different things when + # `x` and `y` are both out of bounds, when just `x` is out of bounds, + # when just `y` is out of bounds, and when both are out of bounds. + # I'm not going to do that right now. Instead, simply replace values + # outside the support by those at the edge of the support. Here, we also + # omit some of the optimizations that make `_set_invalid_nan` faster for + # simple arguments (e.g. float64 scalars). + + @functools.wraps(f) + def wrapped(self, x, y, *args, **kwargs): + func_name = f.__name__ + + low, high = self.support() + x, y, low, high = np.broadcast_arrays(x, y, low, high) + dtype = np.result_type(x.dtype, y.dtype, self._dtype) + # yes, copy to avoid modifying input arrays + x, y = x.astype(dtype, copy=True), y.astype(dtype, copy=True) + + # Swap arguments to ensure that x < y, and replace + # out-of domain arguments with domain endpoints. We'll + # transform the result later. + i_swap = y < x + x[i_swap], y[i_swap] = y[i_swap], x[i_swap] + i = x < low + x[i] = low[i] + i = y < low + y[i] = low[i] + i = x > high + x[i] = high[i] + i = y > high + y[i] = high[i] + + res = f(self, x, y, *args, **kwargs) + + # Clipping probability to [0, 1] + if func_name in {'_cdf2', '_ccdf2'}: + res = np.clip(res, 0., 1.) + else: + res = np.clip(res, None, 0.) # exp(res) < 1 + + # Transform the result to account for swapped argument order + res = np.asarray(res) + if func_name == '_cdf2': + res[i_swap] *= -1. + elif func_name == '_ccdf2': + res[i_swap] *= -1 + res[i_swap] += 2. + elif func_name == '_logcdf2': + res = np.asarray(res + 0j) if np.any(i_swap) else res + res[i_swap] = res[i_swap] + np.pi*1j + else: + # res[i_swap] is always positive and less than 1, so it's + # safe to ensure that the result is real + res[i_swap] = _logexpxmexpy(np.log(2), res[i_swap]).real + return res[()] + + return wrapped + + +def _fiinfo(x): + if np.issubdtype(x.dtype, np.inexact): + return np.finfo(x.dtype) + else: + return np.iinfo(x) + + +def _logexpxmexpy(x, y): + """ Compute the log of the difference of the exponentials of two arguments. + + Avoids over/underflow, but does not prevent loss of precision otherwise. + """ + return xpx.apply_where(x != y, (x, y), + lambda x, y: special.logsumexp([x, y+np.pi*1j], axis=0), + fill_value=-np.inf) + + +def _guess_bracket(xmin, xmax): + a = np.full_like(xmin, -1.0) + b = np.ones_like(xmax) + + i = np.isfinite(xmin) & np.isfinite(xmax) + a[i] = xmin[i] + b[i] = xmax[i] + + i = np.isfinite(xmin) & ~np.isfinite(xmax) + a[i] = xmin[i] + b[i] = xmin[i] + 1 + + i = np.isfinite(xmax) & ~np.isfinite(xmin) + a[i] = xmax[i] - 1 + b[i] = xmax[i] + + return a, b + + +def _log_real_standardize(x): + """Standardizes the (complex) logarithm of a real number. + + The logarithm of a real number may be represented by a complex number with + imaginary part that is a multiple of pi*1j. Even multiples correspond with + a positive real and odd multiples correspond with a negative real. + + Given a logarithm of a real number `x`, this function returns an equivalent + representation in a standard form: the log of a positive real has imaginary + part `0` and the log of a negative real has imaginary part `pi`. + + """ + shape = x.shape + x = np.atleast_1d(x) + real = np.real(x).astype(x.dtype) + complex = np.imag(x) + y = real + negative = np.exp(complex*1j) < 0.5 + y[negative] = y[negative] + np.pi * 1j + return y.reshape(shape)[()] + + +def _combine_docs(dist_family, *, include_examples=True): + fields = set(NumpyDocString.sections) + fields.remove('index') + if not include_examples: + fields.remove('Examples') + + doc = ClassDoc(dist_family) + superdoc = ClassDoc(UnivariateDistribution) + for field in fields: + if field in {"Methods", "Attributes"}: + doc[field] = superdoc[field] + elif field in {"Summary"}: + pass + elif field == "Extended Summary": + doc[field].append(_generate_domain_support(dist_family)) + elif field == 'Examples': + doc[field] = [_generate_example(dist_family)] + else: + doc[field] += superdoc[field] + return str(doc) + + +def _generate_domain_support(dist_family): + n_parameterizations = len(dist_family._parameterizations) + + domain = f"\nfor :math:`x \\in {dist_family._variable.domain}`.\n" + + if n_parameterizations == 0: + support = """ + This class accepts no distribution parameters. + """ + elif n_parameterizations == 1: + support = f""" + This class accepts one parameterization: + {str(dist_family._parameterizations[0])}. + """ + else: + number = {2: 'two', 3: 'three', 4: 'four', 5: 'five'}[ + n_parameterizations] + parameterizations = [f"- {str(p)}" for p in + dist_family._parameterizations] + parameterizations = "\n".join(parameterizations) + support = f""" + This class accepts {number} parameterizations: + + {parameterizations} + """ + support = "\n".join([line.lstrip() for line in support.split("\n")][1:]) + return domain + support + + +def _generate_example(dist_family): + n_parameters = dist_family._num_parameters(0) + shapes = [()] * n_parameters + rng = np.random.default_rng(615681484984984) + i = 0 + dist = dist_family._draw(shapes, rng=rng, i_parameterization=i) + + rng = np.random.default_rng(2354873452) + name = dist_family.__name__ + if n_parameters: + parameter_names = list(dist._parameterizations[i].parameters) + parameter_values = [round(getattr(dist, name), 2) for name in + parameter_names] + name_values = [f"{name}={value}" for name, value in + zip(parameter_names, parameter_values)] + instantiation = f"{name}({', '.join(name_values)})" + attributes = ", ".join([f"X.{param}" for param in dist._parameters]) + X = dist_family(**dict(zip(parameter_names, parameter_values))) + else: + instantiation = f"{name}()" + X = dist + + p = 0.32 + x = round(X.icdf(p), 2) + y = round(X.icdf(2 * p), 2) # noqa: F841 + + example = f""" + To use the distribution class, it must be instantiated using keyword + parameters corresponding with one of the accepted parameterizations. + + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from scipy import stats + >>> from scipy.stats import {name} + >>> X = {instantiation} + + For convenience, the ``plot`` method can be used to visualize the density + and other functions of the distribution. + + >>> X.plot() + >>> plt.show() + + The support of the underlying distribution is available using the ``support`` + method. + + >>> X.support() + {X.support()} + """ + + if n_parameters: + example += f""" + The numerical values of parameters associated with all parameterizations + are available as attributes. + + >>> {attributes} + {tuple(X._parameters.values())} + """ + + example += f""" + To evaluate the probability density/mass function of the underlying distribution + at argument ``x={x}``: + + >>> x = {x} + >>> X.pdf(x), X.pmf(x) + {X.pdf(x), X.pmf(x)} + + The cumulative distribution function, its complement, and the logarithm + of these functions are evaluated similarly. + + >>> np.allclose(np.exp(X.logccdf(x)), 1 - X.cdf(x)) + True + """ + + # When two-arg CDF is implemented for DiscreteDistribution, consider removing + # the special-casing here. + if issubclass(dist_family, ContinuousDistribution): + example_continuous = f""" + The inverse of these functions with respect to the argument ``x`` is also + available. + + >>> logp = np.log(1 - X.ccdf(x)) + >>> np.allclose(X.ilogcdf(logp), x) + True + + Note that distribution functions and their logarithms also have two-argument + versions for working with the probability mass between two arguments. The + result tends to be more accurate than the naive implementation because it avoids + subtractive cancellation. + + >>> y = {y} + >>> np.allclose(X.ccdf(x, y), 1 - (X.cdf(y) - X.cdf(x))) + True + """ + example += example_continuous + + example += f""" + There are methods for computing measures of central tendency, + dispersion, higher moments, and entropy. + + >>> X.mean(), X.median(), X.mode() + {X.mean(), X.median(), X.mode()} + + >>> X.variance(), X.standard_deviation() + {X.variance(), X.standard_deviation()} + + >>> X.skewness(), X.kurtosis() + {X.skewness(), X.kurtosis()} + + >>> np.allclose(X.moment(order=6, kind='standardized'), + ... X.moment(order=6, kind='central') / X.variance()**3) + True + """ + + # When logentropy is implemented for DiscreteDistribution, remove special-casing + if issubclass(dist_family, ContinuousDistribution): + example += """ + >>> np.allclose(np.exp(X.logentropy()), X.entropy()) + True + """ + else: + example += f""" + >>> X.entropy() + {X.entropy()} + """ + + example += f""" + Pseudo-random samples can be drawn from + the underlying distribution using ``sample``. + + >>> X.sample(shape=(4,)) + {repr(X.sample(shape=(4,)))} # may vary + """ + # remove the indentation due to use of block quote within function; + # eliminate blank first line + example = "\n".join([line.lstrip() for line in example.split("\n")][1:]) + return example + + +class UnivariateDistribution(_ProbabilityDistribution): + r""" Class that represents a continuous statistical distribution. + + Parameters + ---------- + tol : positive float, optional + The desired relative tolerance of calculations. Left unspecified, + calculations may be faster; when provided, calculations may be + more likely to meet the desired accuracy. + validation_policy : {None, "skip_all"} + Specifies the level of input validation to perform. Left unspecified, + input validation is performed to ensure appropriate behavior in edge + case (e.g. parameters out of domain, argument outside of distribution + support, etc.) and improve consistency of output dtype, shape, etc. + Pass ``'skip_all'`` to avoid the computational overhead of these + checks when rough edges are acceptable. + cache_policy : {None, "no_cache"} + Specifies the extent to which intermediate results are cached. Left + unspecified, intermediate results of some calculations (e.g. distribution + support, moments, etc.) are cached to improve performance of future + calculations. Pass ``'no_cache'`` to reduce memory reserved by the class + instance. + + Attributes + ---------- + All parameters are available as attributes. + + Methods + ------- + support + + plot + + sample + + moment + lmoment + + mean + median + mode + + variance + standard_deviation + + skewness + kurtosis + + pdf + logpdf + + cdf + icdf + ccdf + iccdf + + logcdf + ilogcdf + logccdf + ilogccdf + + entropy + logentropy + + See Also + -------- + :ref:`rv_infrastructure` : Tutorial + + Notes + ----- + The following abbreviations are used throughout the documentation. + + - PDF: probability density function + - CDF: cumulative distribution function + - CCDF: complementary CDF + - entropy: differential entropy + - log-*F*: logarithm of *F* (e.g. log-CDF) + - inverse *F*: inverse function of *F* (e.g. inverse CDF) + + The API documentation is written to describe the API, not to serve as + a statistical reference. Effort is made to be correct at the level + required to use the functionality, not to be mathematically rigorous. + For example, continuity and differentiability may be implicitly assumed. + For precise mathematical definitions, consult your preferred mathematical + text. + + """ + __array_priority__ = 1 + _parameterizations = [] # type: ignore[var-annotated] + + ### Initialization + + def __init__(self, *, tol=_null, validation_policy=None, cache_policy=None, + **parameters): + self.tol = tol + self.validation_policy = validation_policy + self.cache_policy = cache_policy + self._not_implemented = ( + f"`{self.__class__.__name__}` does not provide an accurate " + "implementation of the required method. Consider leaving " + "`method` and `tol` unspecified to use another implementation." + ) + self._original_parameters = {} + # We may want to override the `__init__` method with parameters so + # IDEs can suggest parameter names. If there are multiple parameterizations, + # we'll need the default values of parameters to be None; this will + # filter out the parameters that were not actually specified by the user. + parameters = {key: val for key, val in + sorted(parameters.items()) if val is not None} + self._update_parameters(**parameters) + + def _update_parameters(self, *, validation_policy=None, **params): + r""" Update the numerical values of distribution parameters. + + Parameters + ---------- + validation_policy : {None, "skip_all"} + Specifies the level of input validation to perform. Left unspecified, + input validation is performed to ensure appropriate behavior in edge + case (e.g. parameters out of domain, argument outside of distribution + support, etc.) and improve consistency of output dtype, shape, etc. + Pass ``'skip_all'`` to avoid the computational overhead of these + checks when rough edges are acceptable. + + **params : array_like + Desired numerical values of the distribution parameters. Any or all + of the parameters initially used to instantiate the distribution + may be modified. Parameters used in alternative parameterizations + are not accepted. + """ + + parameters = original_parameters = self._original_parameters.copy() + parameters.update(**params) + parameterization = None + self._invalid = np.asarray(False) + self._any_invalid = False + self._shape = tuple() + self._ndim = 0 + self._size = 1 + self._dtype = np.float64 + + if (validation_policy or self.validation_policy) == _SKIP_ALL: + parameters = self._process_parameters(**parameters) + elif not len(self._parameterizations): + if parameters: + message = (f"The `{self.__class__.__name__}` distribution " + "family does not accept parameters, but parameters " + f"`{set(parameters)}` were provided.") + raise ValueError(message) + else: + # This is default behavior, which re-runs all parameter validations + # even when only a single parameter is modified. For many + # distributions, the domain of a parameter doesn't depend on other + # parameters, so parameters could safely be modified without + # re-validating all other parameters. To handle these cases more + # efficiently, we could allow the developer to override this + # behavior. + + # Currently the user can only update the original parameterization. + # Even though that parameterization is already known, + # `_identify_parameterization` is called to produce a nice error + # message if the user passes other values. To be a little more + # efficient, we could detect whether the values passed are + # consistent with the original parameterization rather than finding + # it from scratch. However, we might want other parameterizations + # to be accepted, which would require other changes, so I didn't + # optimize this. + + parameterization = self._identify_parameterization(parameters) + parameters, shape, size, ndim = self._broadcast(parameters) + parameters, invalid, any_invalid, dtype = ( + self._validate(parameterization, parameters)) + parameters = self._process_parameters(**parameters) + + self._invalid = invalid + self._any_invalid = any_invalid + self._shape = shape + self._size = size + self._ndim = ndim + self._dtype = dtype + + self.reset_cache() + self._parameters = parameters + self._parameterization = parameterization + self._original_parameters = original_parameters + for name in self._parameters.keys(): + # Make parameters properties of the class; return values from the instance + if hasattr(self.__class__, name): + continue + setattr(self.__class__, name, property(lambda self_, name_=name: + self_._parameters[name_].copy()[()])) + + def reset_cache(self): + r""" Clear all cached values. + + To improve the speed of some calculations, the distribution's support + and moments are cached. + + This function is called automatically whenever the distribution + parameters are updated. + + """ + # We could offer finer control over what is cleared. + # For simplicity, these will still exist even if cache_policy is + # NO_CACHE; they just won't be populated. This allows caching to be + # turned on and off easily. + self._moment_raw_cache = {} + self._moment_central_cache = {} + self._moment_standardized_cache = {} + self._lmoment_cache = {} + self._support_cache = None + self._method_cache = {} + self._constant_cache = None + + def _identify_parameterization(self, parameters): + # Determine whether a `parameters` dictionary matches is consistent + # with one of the parameterizations of the distribution. If so, + # return that parameterization object; if not, raise an error. + # + # I've come back to this a few times wanting to avoid this explicit + # loop. I've considered several possibilities, but they've all been a + # little unusual. For example, we could override `_eq_` so we can + # use _parameterizations.index() to retrieve the parameterization, + # or the user could put the parameterizations in a dictionary so we + # could look them up with a key (e.g. frozenset of parameter names). + # I haven't been sure enough of these approaches to implement them. + parameter_names_set = set(parameters) + + for parameterization in self._parameterizations: + if parameterization.matches(parameter_names_set): + break + else: + if not parameter_names_set: + message = (f"The `{self.__class__.__name__}` distribution " + "family requires parameters, but none were " + "provided.") + else: + parameter_names = self._get_parameter_str(parameters) + message = (f"The provided parameters `{parameter_names}` " + "do not match a supported parameterization of the " + f"`{self.__class__.__name__}` distribution family.") + raise ValueError(message) + + return parameterization + + def _broadcast(self, parameters): + # Broadcast the distribution parameters to the same shape. If the + # arrays are not broadcastable, raise a meaningful error. + # + # We always make sure that the parameters *are* the same shape + # and not just broadcastable. Users can access parameters as + # attributes, and I think they should see the arrays as the same shape. + # More importantly, arrays should be the same shape before logical + # indexing operations, which are needed in infrastructure code when + # there are invalid parameters, and may be needed in + # distribution-specific code. We don't want developers to need to + # broadcast in implementation functions. + + # It's much faster to check whether broadcasting is necessary than to + # broadcast when it's not necessary. + parameter_vals = [np.asarray(parameter) + for parameter in parameters.values()] + parameter_shapes = set(parameter.shape for parameter in parameter_vals) + if len(parameter_shapes) == 1: + return (parameters, parameter_vals[0].shape, + parameter_vals[0].size, parameter_vals[0].ndim) + + try: + parameter_vals = np.broadcast_arrays(*parameter_vals) + except ValueError as e: + parameter_names = self._get_parameter_str(parameters) + message = (f"The parameters `{parameter_names}` provided to the " + f"`{self.__class__.__name__}` distribution family " + "cannot be broadcast to the same shape.") + raise ValueError(message) from e + return (dict(zip(parameters.keys(), parameter_vals)), + parameter_vals[0].shape, + parameter_vals[0].size, + parameter_vals[0].ndim) + + def _validate(self, parameterization, parameters): + # Broadcasts distribution parameter arrays and converts them to a + # consistent dtype. Replaces invalid parameters with `np.nan`. + # Returns the validated parameters, a boolean mask indicated *which* + # elements are invalid, a boolean scalar indicating whether *any* + # are invalid (to skip special treatments if none are invalid), and + # the common dtype. + valid, dtype = parameterization.validation(parameters) + invalid = ~valid + any_invalid = invalid if invalid.shape == () else np.any(invalid) + # If necessary, make the arrays contiguous and replace invalid with NaN + if any_invalid: + for parameter_name in parameters: + parameters[parameter_name] = np.copy( + parameters[parameter_name]) + parameters[parameter_name][invalid] = np.nan + + return parameters, invalid, any_invalid, dtype + + def _process_parameters(self, **params): + r""" Process and cache distribution parameters for reuse. + + This is intended to be overridden by subclasses. It allows distribution + authors to pre-process parameters for re-use. For instance, when a user + parameterizes a LogUniform distribution with `a` and `b`, it makes + sense to calculate `log(a)` and `log(b)` because these values will be + used in almost all distribution methods. The dictionary returned by + this method is passed to all private methods that calculate functions + of the distribution. + """ + return params + + def _get_parameter_str(self, parameters): + # Get a string representation of the parameters like "{a, b, c}". + return f"{{{', '.join(parameters.keys())}}}" + + def _copy_parameterization(self): + self._parameterizations = self._parameterizations.copy() + for i in range(len(self._parameterizations)): + self._parameterizations[i] = self._parameterizations[i].copy() + + ### Attributes + + @property + def tol(self): + r"""positive float: + The desired relative tolerance of calculations. Left unspecified, + calculations may be faster; when provided, calculations may be + more likely to meet the desired accuracy. + """ + return self._tol + + @tol.setter + def tol(self, tol): + if _isnull(tol): + self._tol = tol + return + + tol = np.asarray(tol) + if (tol.shape != () or not tol > 0 or # catches NaNs + not np.issubdtype(tol.dtype, np.floating)): + message = (f"Attribute `tol` of `{self.__class__.__name__}` must " + "be a positive float, if specified.") + raise ValueError(message) + self._tol = tol[()] + + @property + def cache_policy(self): + r"""{None, "no_cache"}: + Specifies the extent to which intermediate results are cached. Left + unspecified, intermediate results of some calculations (e.g. distribution + support, moments, etc.) are cached to improve performance of future + calculations. Pass ``'no_cache'`` to reduce memory reserved by the class + instance. + """ + return self._cache_policy + + @cache_policy.setter + def cache_policy(self, cache_policy): + cache_policy = str(cache_policy).lower() if cache_policy is not None else None + cache_policies = {None, 'no_cache'} + if cache_policy not in cache_policies: + message = (f"Attribute `cache_policy` of `{self.__class__.__name__}` " + f"must be one of {cache_policies}, if specified.") + raise ValueError(message) + self._cache_policy = cache_policy + + @property + def validation_policy(self): + r"""{None, "skip_all"}: + Specifies the level of input validation to perform. Left unspecified, + input validation is performed to ensure appropriate behavior in edge + case (e.g. parameters out of domain, argument outside of distribution + support, etc.) and improve consistency of output dtype, shape, etc. + Use ``'skip_all'`` to avoid the computational overhead of these + checks when rough edges are acceptable. + """ + return self._validation_policy + + @validation_policy.setter + def validation_policy(self, validation_policy): + validation_policy = (str(validation_policy).lower() + if validation_policy is not None else None) + iv_policies = {None, 'skip_all'} + if validation_policy not in iv_policies: + message = (f"Attribute `validation_policy` of `{self.__class__.__name__}` " + f"must be one of {iv_policies}, if specified.") + raise ValueError(message) + self._validation_policy = validation_policy + + ### Other magic methods + + def __repr__(self): + r""" Returns a string representation of the distribution. + + Includes the name of the distribution family, the names of the + parameters and the `repr` of each of their values. + + + """ + class_name = self.__class__.__name__ + parameters = list(self._original_parameters.items()) + info = [] + with np.printoptions(threshold=10): + str_parameters = [f"{symbol}={repr(value)}" for symbol, value in parameters] + str_parameters = f"{', '.join(str_parameters)}" + info.append(str_parameters) + return f"{class_name}({', '.join(info)})" + + def __str__(self): + class_name = self.__class__.__name__ + parameters = list(self._original_parameters.items()) + info = [] + with np.printoptions(threshold=10): + str_parameters = [f"{symbol}={str(value)}" for symbol, value in parameters] + str_parameters = f"{', '.join(str_parameters)}" + info.append(str_parameters) + return f"{class_name}({', '.join(info)})" + + def __add__(self, loc): + return ShiftedScaledDistribution(self, loc=loc) + + def __sub__(self, loc): + return ShiftedScaledDistribution(self, loc=-loc) + + def __mul__(self, scale): + return ShiftedScaledDistribution(self, scale=scale) + + def __truediv__(self, scale): + return ShiftedScaledDistribution(self, scale=1/scale) + + def __pow__(self, other): + if not np.isscalar(other) or other <= 0 or other != int(other): + message = ("Raising a random variable to the power of an argument is only " + "implemented when the argument is a positive integer.") + raise NotImplementedError(message) + + # Fill in repr_pattern with the repr of self before taking abs. + # Avoids having unnecessary abs in the repr. + with np.printoptions(threshold=10): + repr_pattern = f"({repr(self)})**{repr(other)}" + str_pattern = f"({str(self)})**{str(other)}" + X = abs(self) if other % 2 == 0 else self + + funcs = dict(g=lambda u: u**other, repr_pattern=repr_pattern, + str_pattern=str_pattern, + h=lambda u: np.sign(u) * np.abs(u)**(1 / other), + dh=lambda u: 1/other * np.abs(u)**(1/other - 1)) + + return MonotonicTransformedDistribution(X, **funcs, increasing=True) + + def __radd__(self, other): + return self.__add__(other) + + def __rsub__(self, other): + return self.__neg__().__add__(other) + + def __rmul__(self, other): + return self.__mul__(other) + + def __rtruediv__(self, other): + a, b = self.support() + with np.printoptions(threshold=10): + funcs = dict(g=lambda u: 1 / u, + repr_pattern=f"{repr(other)}/({repr(self)})", + str_pattern=f"{str(other)}/({str(self)})", + h=lambda u: 1 / u, dh=lambda u: 1 / u ** 2) + if np.all(a >= 0) or np.all(b <= 0): + out = MonotonicTransformedDistribution(self, **funcs, increasing=False) + else: + message = ("Division by a random variable is only implemented " + "when the support is either non-negative or non-positive.") + raise NotImplementedError(message) + if np.all(other == 1): + return out + else: + return out * other + + def __rpow__(self, other): + with np.printoptions(threshold=10): + funcs = dict(g=lambda u: other**u, + h=lambda u: np.log(u) / np.log(other), + dh=lambda u: 1 / np.abs(u * np.log(other)), + repr_pattern=f"{repr(other)}**({repr(self)})", + str_pattern=f"{str(other)}**({str(self)})",) + + if not np.isscalar(other) or other <= 0 or other == 1: + message = ("Raising an argument to the power of a random variable is only " + "implemented when the argument is a positive scalar other than " + "1.") + raise NotImplementedError(message) + + if other > 1: + return MonotonicTransformedDistribution(self, **funcs, increasing=True) + else: + return MonotonicTransformedDistribution(self, **funcs, increasing=False) + + def __neg__(self): + return self * -1 + + def __abs__(self): + return FoldedDistribution(self) + + ### Utilities + + ## Input validation + + def _validate_order(self, order, fname='moment', min_order=0): + # Yet another integer validating function. Unlike others in SciPy, it + # Is quite flexible about what is allowed as an integer, and it + # raises a distribution-specific error message to facilitate + # identification of the source of the error. + if self.validation_policy == _SKIP_ALL: + return order + + order = np.asarray(order, dtype=self._dtype)[()] + message = (f"Argument `order` of `{self.__class__.__name__}.{fname}` " + f"must be a finite integer greater than or equal to {min_order}.") + try: + order_int = round(order.item()) + # If this fails for any reason (e.g. it's an array, it's infinite) + # it's not a valid `order`. + except Exception as e: + raise ValueError(message) from e + + if order_int < min_order or order_int != order: + raise ValueError(message) + + return order + + def _validate_kind(self, kind, kinds): + message = (f"Argument `kind` of `{self.__class__.__name__}.moment` " + f"must be one of {set(kinds)}.") + kind = kind.lower() + if kind not in kinds: + raise ValueError(message) + return kind + + def _preserve_type(self, x): + x = np.asarray(x) + if x.dtype != self._dtype: + x = x.astype(self._dtype) + return x[()] + + ## Testing + + @classmethod + def _draw(cls, sizes=None, rng=None, i_parameterization=None, + proportions=None): + r""" Draw a specific (fully-defined) distribution from the family. + + See _Parameterization.draw for documentation details. + """ + rng = np.random.default_rng(rng) + if len(cls._parameterizations) == 0: + return cls() + if i_parameterization is None: + n = cls._num_parameterizations() + i_parameterization = rng.integers(0, max(0, n - 1), endpoint=True) + + parameterization = cls._parameterizations[i_parameterization] + parameters = parameterization.draw(sizes, rng, proportions=proportions, + region='typical') + return cls(**parameters) + + @classmethod + def _num_parameterizations(cls): + # Returns the number of parameterizations accepted by the family. + return len(cls._parameterizations) + + @classmethod + def _num_parameters(cls, i_parameterization=0): + # Returns the number of parameters used in the specified + # parameterization. + return (0 if not cls._num_parameterizations() + else len(cls._parameterizations[i_parameterization])) + + ## Algorithms + + def _quadrature(self, integrand, limits=None, args=(), params=None, log=False): + # Performs numerical integration/summation between limits or over support. + a, b = self._support(**params) if limits is None else limits + rtol = None if _isnull(self.tol) else self.tol + + # For now, we ignore the status, but I want to return the error estimate + if isinstance(self, ContinuousDistribution): + return _tanhsinh(integrand, a, b, args=args, kwargs=params, + log=log, rtol=rtol).integral + else: + res = nsum(integrand, a, b, args=args, kwargs=params, + log=log, tolerances=dict(rtol=rtol)).sum + + # b > a -> sum is zero... unless the parameters are nan. + # (Resolving gh-22321 should fix this in nsum.) + cond = np.isnan(params.popitem()[1]) if params else np.True_ + return np.where(cond, np.nan, res)[()] + + def _solve_bounded(self, f, p, *, bounds=None, params=None, xatol=None): + # Finds the argument of a function that produces the desired output. + xmin, xmax = self._support(**params) if bounds is None else bounds + + def f2(x, _p, **kwargs): # named `_p` to avoid conflict with shape `p` + return f(x, **kwargs) - _p + + xl0, xr0 = _guess_bracket(xmin, xmax) + + res = elementwise.bracket_root(f2, xl0=xl0, xr0=xr0, xmin=xmin, xmax=xmax, + args=(p,), kwargs=params) + # For now, we ignore the status, but I want to use the bracket width + # as an error estimate. + + xrtol = None if _isnull(self.tol) else self.tol + xatol = None if xatol is None else xatol + tolerances = dict(xrtol=xrtol, xatol=xatol, fatol=0, frtol=0) + return elementwise.find_root(f2, res.bracket, args=(p,), + kwargs=params, tolerances=tolerances) + + ## Other + + def _overrides(self, method_name): + # Determines whether a class overrides a specified method. + # Returns True if the method implementation exists and is the same as + # that of the `ContinuousDistribution` class; otherwise returns False. + + # Sometimes we use `_overrides` to check whether a certain method is overridden + # and if so, call it. This begs the questions of why we don't do the more + # obvious thing: restructure so that if the private method is overridden, + # Python will call it instead of the inherited version automatically. The short + # answer is that there are multiple ways a use might wish to evaluate a method, + # and simply overriding the method with a formula is not always the best option. + # For more complete discussion of the considerations, see: + # https://github.com/scipy/scipy/pull/21050#discussion_r1707798901 + method = getattr(self.__class__, method_name, None) + super_method = getattr(UnivariateDistribution, method_name, None) + return method is not super_method + + ### Distribution properties + # The following "distribution properties" are exposed via a public method + # that accepts only options (not distribution parameters or quantile/ + # percentile argument). + # support + # logentropy, entropy, + # median, mode, mean, + # variance, standard_deviation + # skewness, kurtosis + # Common options are: + # method - a string that indicates which method should be used to compute + # the quantity (e.g. a formula or numerical integration). + # Input/output validation is provided by the `_set_invalid_nan_property` + # decorator. These are the methods meant to be called by users. + # + # Each public method calls a private "dispatch" method that + # determines which "method" (strategy for calculating the desired quantity) + # to use by default and, via the `@_dispatch` decorator, calls the + # method and computes the result. + # Dispatch methods always accept: + # method - as passed from the public method + # params - a dictionary of distribution shape parameters passed by + # the public method. + # Dispatch methods accept `params` rather than relying on the state of the + # object because iterative algorithms like `tanhsinh` and `find_root` + # need their callable to follow a strict elementwise protocol: each element + # of the output is determined solely by the values of the inputs at the + # corresponding location. The public methods do not satisfy this protocol + # because they do not accept the parameters as arguments, producing an + # output that generally has a different shape than that of the input. Also, + # by calling "dispatch" methods rather than the public methods, the + # iterative algorithms avoid the overhead of input validation. + # + # Each dispatch method can designate the responsibility of computing + # the required value to any of several "implementation" methods. These + # methods accept only `**params`, the parameter dictionary passed from + # the public method via the dispatch method. We separate the implementation + # methods from the dispatch methods for the sake of simplicity (via + # compartmentalization) and to allow subclasses to override certain + # implementation methods (typically only the "formula" methods). The names + # of implementation methods are combinations of the public method name and + # the name of the "method" (strategy for calculating the desired quantity) + # string. (In fact, the name of the implementation method is calculated + # from these two strings in the `_dispatch` decorator.) Common method + # strings are: + # formula - distribution-specific analytical expressions to be implemented + # by subclasses. + # log/exp - Compute the log of a number and then exponentiate it or vice + # versa. + # quadrature - Compute the value via numerical integration. + # + # The default method (strategy) is determined based on what implementation + # methods are available and the error tolerance of the user. Typically, + # a formula is always used if available. We fall back to "log/exp" if a + # formula for the logarithm or exponential of the quantity is available, + # and we use quadrature otherwise. + + def support(self): + # If this were a `cached_property`, we couldn't update the value + # when the distribution parameters change. + # Caching is important, though, because calls to _support take a few + # microseconds even when `a` and `b` are already the same shape. + if self._support_cache is not None: + return self._support_cache + + a, b = self._support(**self._parameters) + if a.shape != self._shape: + a = np.broadcast_to(a, self._shape) + if b.shape != self._shape: + b = np.broadcast_to(b, self._shape) + + if self._any_invalid: + a, b = np.asarray(a).copy(), np.asarray(b).copy() + a[self._invalid], b[self._invalid] = np.nan, np.nan + a, b = a[()], b[()] + + support = (a, b) + + if self.cache_policy != _NO_CACHE: + self._support_cache = support + + return support + + def _support(self, **params): + # Computes the support given distribution parameters + a, b = self._variable.domain.get_numerical_endpoints(params) + if len(params): + # the parameters should all be of the same dtype and shape at this point + vals = list(params.values()) + shape = vals[0].shape + a = np.broadcast_to(a, shape) if a.shape != shape else a + b = np.broadcast_to(b, shape) if b.shape != shape else b + return self._preserve_type(a), self._preserve_type(b) + + @_set_invalid_nan_property + def logentropy(self, *, method=None): + return self._logentropy_dispatch(method=method, **self._parameters) + 0j + + @_dispatch + def _logentropy_dispatch(self, method=None, **params): + if self._overrides('_logentropy_formula'): + method = self._logentropy_formula + elif self._overrides('_entropy_formula'): + method = self._logentropy_logexp_safe + else: + method = self._logentropy_quadrature + return method + + def _logentropy_formula(self, **params): + raise NotImplementedError(self._not_implemented) + + def _logentropy_logexp(self, **params): + res = np.log(self._entropy_dispatch(**params)+0j) + return _log_real_standardize(res) + + def _logentropy_logexp_safe(self, **params): + out = self._logentropy_logexp(**params) + mask = np.isinf(out.real) + if np.any(mask): + params_mask = {key:val[mask] for key, val in params.items()} + out = np.asarray(out) + out[mask] = self._logentropy_quadrature(**params_mask) + return out[()] + + def _logentropy_quadrature(self, **params): + def logintegrand(x, **params): + logpxf = self._logpxf_dispatch(x, **params) + return logpxf + np.log(0j+logpxf) + res = self._quadrature(logintegrand, params=params, log=True) + return _log_real_standardize(res + np.pi*1j) + + @_set_invalid_nan_property + def entropy(self, *, method=None): + return self._entropy_dispatch(method=method, **self._parameters) + + @_dispatch + def _entropy_dispatch(self, method=None, **params): + if self._overrides('_entropy_formula'): + method = self._entropy_formula + elif self._overrides('_logentropy_formula'): + method = self._entropy_logexp + else: + method = self._entropy_quadrature + return method + + def _entropy_formula(self, **params): + raise NotImplementedError(self._not_implemented) + + def _entropy_logexp(self, **params): + return np.real(np.exp(self._logentropy_dispatch(**params))) + + def _entropy_quadrature(self, **params): + def integrand(x, **params): + pxf = self._pxf_dispatch(x, **params) + logpxf = self._logpxf_dispatch(x, **params) + temp = np.asarray(pxf) + i = (pxf != 0) # 0 * inf -> nan; should be 0 + temp[i] = -pxf[i]*logpxf[i] + return temp + return self._quadrature(integrand, params=params) + + @_set_invalid_nan_property + def median(self, *, method=None): + return self._median_dispatch(method=method, **self._parameters) + + @_dispatch + def _median_dispatch(self, method=None, **params): + if self._overrides('_median_formula'): + method = self._median_formula + else: + method = self._median_icdf + return method + + def _median_formula(self, **params): + raise NotImplementedError(self._not_implemented) + + def _median_icdf(self, **params): + return self._icdf_dispatch(np.asarray(0.5, dtype=self._dtype), **params) + + @_set_invalid_nan_property + def mode(self, *, method=None): + return self._mode_dispatch(method=method, **self._parameters) + + @_dispatch + def _mode_dispatch(self, method=None, **params): + # We could add a method that looks for a critical point with + # differentiation and the root finder + if self._overrides('_mode_formula'): + method = self._mode_formula + else: + method = self._mode_optimization + return method + + def _mode_formula(self, **params): + raise NotImplementedError(self._not_implemented) + + def _mode_optimization(self, xatol=None, **params): + a, b = self._support(**params) + m = self._median_dispatch(**params) + + def f(x, **params): + return -self._pxf_dispatch(x, **params) + + res_b = elementwise.bracket_minimum(f, m, xmin=a, xmax=b, kwargs=params) + res = elementwise.find_minimum(f, res_b.bracket, kwargs=params, + tolerances=dict(xatol=xatol)) + mode = np.asarray(res.x) + + # bracket_minimum fails with status == -1 if the mode is at a boundary. + # We have to consider this as a special case; there's no way to include + # this logic in find_minimum; it has to treat the bracket as invalid. + mode_at_boundary = res.status == -1 + fl, fm, fr = res_b.f_bracket + mode_at_left = mode_at_boundary & (fl <= fm) + mode_at_right = mode_at_boundary & (fr < fm) + mode[mode_at_left] = a[mode_at_left] + mode[mode_at_right] = b[mode_at_right] + return mode[()] + + def mean(self, *, method=None): + return self.moment(1, kind='raw', method=method) + + def variance(self, *, method=None): + return self.moment(2, kind='central', method=method) + + def standard_deviation(self, *, method=None): + return np.sqrt(self.variance(method=method)) + + def skewness(self, *, method=None): + return self.moment(3, kind='standardized', method=method) + + def kurtosis(self, *, method=None, convention='non-excess'): + conventions = {'non-excess', 'excess'} + message = (f'Parameter `convention` of `{self.__class__.__name__}.kurtosis` ' + f"must be one of {conventions}.") + convention = convention.lower() + if convention not in conventions: + raise ValueError(message) + k = self.moment(4, kind='standardized', method=method) + return k - 3 if convention == 'excess' else k + + ### Distribution functions + # The following functions related to the distribution PDF and CDF are + # exposed via a public method that accepts one positional argument - the + # quantile - and keyword options (but not distribution parameters). + # logpdf, pdf + # logcdf, cdf + # logccdf, ccdf + # The `logcdf` and `cdf` functions can also be called with two positional + # arguments - lower and upper quantiles - and they return the probability + # mass (integral of the PDF) between them. The 2-arg versions of `logccdf` + # and `ccdf` return the complement of this quantity. + # All the (1-arg) cumulative distribution functions have inverse + # functions, which accept one positional argument - the percentile. + # ilogcdf, icdf + # ilogccdf, iccdf + # Common keyword options include: + # method - a string that indicates which method should be used to compute + # the quantity (e.g. a formula or numerical integration). + # Tolerance options should be added. + # Input/output validation is provided by the `_set_invalid_nan` + # decorator. These are the methods meant to be called by users. + # + # Each public method calls a private "dispatch" method that + # determines which "method" (strategy for calculating the desired quantity) + # to use by default and, via the `@_dispatch` decorator, calls the + # method and computes the result. + # Each dispatch method can designate the responsibility of computing + # the required value to any of several "implementation" methods. These + # methods accept only `**params`, the parameter dictionary passed from + # the public method via the dispatch method. + # See the note corresponding with the "Distribution Parameters" for more + # information. + + ## Probability Density/Mass Functions + + @_set_invalid_nan + def logpdf(self, x, /, *, method=None): + return self._logpdf_dispatch(x, method=method, **self._parameters) + + @_dispatch + def _logpdf_dispatch(self, x, *, method=None, **params): + if self._overrides('_logpdf_formula'): + method = self._logpdf_formula + elif _isnull(self.tol): # ensure that developers override _logpdf + method = self._logpdf_logexp + return method + + def _logpdf_formula(self, x, **params): + raise NotImplementedError(self._not_implemented) + + def _logpdf_logexp(self, x, **params): + return np.log(self._pdf_dispatch(x, **params)) + + @_set_invalid_nan + def pdf(self, x, /, *, method=None): + return self._pdf_dispatch(x, method=method, **self._parameters) + + @_dispatch + def _pdf_dispatch(self, x, *, method=None, **params): + if self._overrides('_pdf_formula'): + method = self._pdf_formula + else: + method = self._pdf_logexp + return method + + def _pdf_formula(self, x, **params): + raise NotImplementedError(self._not_implemented) + + def _pdf_logexp(self, x, **params): + return np.exp(self._logpdf_dispatch(x, **params)) + + @_set_invalid_nan + def logpmf(self, x, /, *, method=None): + return self._logpmf_dispatch(x, method=method, **self._parameters) + + @_dispatch + def _logpmf_dispatch(self, x, *, method=None, **params): + if self._overrides('_logpmf_formula'): + method = self._logpmf_formula + elif _isnull(self.tol): # ensure that developers override _logpmf + method = self._logpmf_logexp + return method + + def _logpmf_formula(self, x, **params): + raise NotImplementedError(self._not_implemented) + + def _logpmf_logexp(self, x, **params): + with np.errstate(divide='ignore'): + return np.log(self._pmf_dispatch(x, **params)) + + @_set_invalid_nan + def pmf(self, x, /, *, method=None): + return self._pmf_dispatch(x, method=method, **self._parameters) + + @_dispatch + def _pmf_dispatch(self, x, *, method=None, **params): + if self._overrides('_pmf_formula'): + method = self._pmf_formula + else: + method = self._pmf_logexp + return method + + def _pmf_formula(self, x, **params): + raise NotImplementedError(self._not_implemented) + + def _pmf_logexp(self, x, **params): + return np.exp(self._logpmf_dispatch(x, **params)) + + ## Cumulative Distribution Functions + + def logcdf(self, x, y=None, /, *, method=None): + if y is None: + return self._logcdf1(x, method=method) + else: + return self._logcdf2(x, y, method=method) + + @_cdf2_input_validation + def _logcdf2(self, x, y, *, method): + out = self._logcdf2_dispatch(x, y, method=method, **self._parameters) + return (out + 0j) if not np.issubdtype(out.dtype, np.complexfloating) else out + + @_dispatch + def _logcdf2_dispatch(self, x, y, *, method=None, **params): + if self._overrides('_logcdf2_formula'): + method = self._logcdf2_formula + elif (self._overrides('_logcdf_formula') + or self._overrides('_logccdf_formula')): + method = self._logcdf2_subtraction + elif (self._overrides('_cdf_formula') + or self._overrides('_ccdf_formula')): + method = self._logcdf2_logexp_safe + else: + method = self._logcdf2_quadrature + return method + + def _logcdf2_formula(self, x, y, **params): + raise NotImplementedError(self._not_implemented) + + def _logcdf2_subtraction(self, x, y, **params): + flip_sign = x > y # some results will be negative + x, y = np.minimum(x, y), np.maximum(x, y) + logcdf_x = self._logcdf_dispatch(x, **params) + logcdf_y = self._logcdf_dispatch(y, **params) + logccdf_x = self._logccdf_dispatch(x, **params) + logccdf_y = self._logccdf_dispatch(y, **params) + case_left = (logcdf_x < -1) & (logcdf_y < -1) + case_right = (logccdf_x < -1) & (logccdf_y < -1) + case_central = ~(case_left | case_right) + log_mass = _logexpxmexpy(logcdf_y, logcdf_x) + log_mass[case_right] = _logexpxmexpy(logccdf_x, logccdf_y)[case_right] + log_tail = np.logaddexp(logcdf_x, logccdf_y)[case_central] + log_mass[case_central] = _log1mexp(log_tail) + log_mass[flip_sign] += np.pi * 1j + return log_mass[()] if np.any(flip_sign) else log_mass.real[()] + + def _logcdf2_logexp(self, x, y, **params): + expres = self._cdf2_dispatch(x, y, **params) + expres = expres + 0j if np.any(x > y) else expres + return np.log(expres) + + def _logcdf2_logexp_safe(self, x, y, **params): + out = self._logcdf2_logexp(x, y, **params) + mask = np.isinf(out.real) + if np.any(mask): + params_mask = {key: np.broadcast_to(val, mask.shape)[mask] + for key, val in params.items()} + out = np.asarray(out) + out[mask] = self._logcdf2_quadrature(x[mask], y[mask], **params_mask) + return out[()] + + def _logcdf2_quadrature(self, x, y, **params): + logres = self._quadrature(self._logpxf_dispatch, limits=(x, y), + log=True, params=params) + return logres + + @_set_invalid_nan + def _logcdf1(self, x, *, method=None): + return self._logcdf_dispatch(x, method=method, **self._parameters) + + @_dispatch + def _logcdf_dispatch(self, x, *, method=None, **params): + if self._overrides('_logcdf_formula'): + method = self._logcdf_formula + elif self._overrides('_logccdf_formula'): + method = self._logcdf_complement + elif self._overrides('_cdf_formula'): + method = self._logcdf_logexp_safe + else: + method = self._logcdf_quadrature + return method + + def _logcdf_formula(self, x, **params): + raise NotImplementedError(self._not_implemented) + + def _logcdf_complement(self, x, **params): + return _log1mexp(self._logccdf_dispatch(x, **params)) + + def _logcdf_logexp(self, x, **params): + return np.log(self._cdf_dispatch(x, **params)) + + def _logcdf_logexp_safe(self, x, **params): + out = self._logcdf_logexp(x, **params) + mask = np.isinf(out) + if np.any(mask): + params_mask = {key:np.broadcast_to(val, mask.shape)[mask] + for key, val in params.items()} + out = np.asarray(out) + out[mask] = self._logcdf_quadrature(x[mask], **params_mask) + return out[()] + + def _logcdf_quadrature(self, x, **params): + a, _ = self._support(**params) + return self._quadrature(self._logpxf_dispatch, limits=(a, x), + params=params, log=True) + + def cdf(self, x, y=None, /, *, method=None): + if y is None: + return self._cdf1(x, method=method) + else: + return self._cdf2(x, y, method=method) + + @_cdf2_input_validation + def _cdf2(self, x, y, *, method): + return self._cdf2_dispatch(x, y, method=method, **self._parameters) + + @_dispatch + def _cdf2_dispatch(self, x, y, *, method=None, **params): + if self._overrides('_cdf2_formula'): + method = self._cdf2_formula + elif (self._overrides('_logcdf_formula') + or self._overrides('_logccdf_formula')): + method = self._cdf2_logexp + elif self._overrides('_cdf_formula') or self._overrides('_ccdf_formula'): + method = self._cdf2_subtraction_safe + else: + method = self._cdf2_quadrature + return method + + def _cdf2_formula(self, x, y, **params): + raise NotImplementedError(self._not_implemented) + + def _cdf2_logexp(self, x, y, **params): + return np.real(np.exp(self._logcdf2_dispatch(x, y, **params))) + + def _cdf2_subtraction(self, x, y, **params): + # Improvements: + # Lazy evaluation of cdf/ccdf only where needed + # Stack x and y to reduce function calls? + cdf_x = self._cdf_dispatch(x, **params) + cdf_y = self._cdf_dispatch(y, **params) + ccdf_x = self._ccdf_dispatch(x, **params) + ccdf_y = self._ccdf_dispatch(y, **params) + i = (ccdf_x < 0.5) & (ccdf_y < 0.5) + return np.where(i, ccdf_x-ccdf_y, cdf_y-cdf_x) + + def _cdf2_subtraction_safe(self, x, y, **params): + cdf_x = self._cdf_dispatch(x, **params) + cdf_y = self._cdf_dispatch(y, **params) + ccdf_x = self._ccdf_dispatch(x, **params) + ccdf_y = self._ccdf_dispatch(y, **params) + i = (ccdf_x < 0.5) & (ccdf_y < 0.5) + out = np.where(i, ccdf_x-ccdf_y, cdf_y-cdf_x) + + eps = np.finfo(self._dtype).eps + tol = self.tol if not _isnull(self.tol) else np.sqrt(eps) + + cdf_max = np.maximum(cdf_x, cdf_y) + ccdf_max = np.maximum(ccdf_x, ccdf_y) + spacing = np.spacing(np.where(i, ccdf_max, cdf_max)) + mask = np.abs(tol * out) < spacing + + if np.any(mask): + params_mask = {key: np.broadcast_to(val, mask.shape)[mask] + for key, val in params.items()} + out = np.asarray(out) + out[mask] = self._cdf2_quadrature(x[mask], y[mask], **params_mask) + return out[()] + + def _cdf2_quadrature(self, x, y, **params): + return self._quadrature(self._pxf_dispatch, limits=(x, y), params=params) + + @_set_invalid_nan + def _cdf1(self, x, *, method): + return self._cdf_dispatch(x, method=method, **self._parameters) + + @_dispatch + def _cdf_dispatch(self, x, *, method=None, **params): + if self._overrides('_cdf_formula'): + method = self._cdf_formula + elif self._overrides('_logcdf_formula'): + method = self._cdf_logexp + elif self._overrides('_ccdf_formula'): + method = self._cdf_complement_safe + else: + method = self._cdf_quadrature + return method + + def _cdf_formula(self, x, **params): + raise NotImplementedError(self._not_implemented) + + def _cdf_logexp(self, x, **params): + return np.exp(self._logcdf_dispatch(x, **params)) + + def _cdf_complement(self, x, **params): + return 1 - self._ccdf_dispatch(x, **params) + + def _cdf_complement_safe(self, x, **params): + ccdf = self._ccdf_dispatch(x, **params) + out = 1 - ccdf + eps = np.finfo(self._dtype).eps + tol = self.tol if not _isnull(self.tol) else np.sqrt(eps) + mask = tol * out < np.spacing(ccdf) + if np.any(mask): + params_mask = {key: np.broadcast_to(val, mask.shape)[mask] + for key, val in params.items()} + out = np.asarray(out) + out[mask] = self._cdf_quadrature(x[mask], **params_mask) + return out[()] + + def _cdf_quadrature(self, x, **params): + a, _ = self._support(**params) + return self._quadrature(self._pxf_dispatch, limits=(a, x), + params=params) + + def logccdf(self, x, y=None, /, *, method=None): + if y is None: + return self._logccdf1(x, method=method) + else: + return self._logccdf2(x, y, method=method) + + @_cdf2_input_validation + def _logccdf2(self, x, y, *, method): + return self._logccdf2_dispatch(x, y, method=method, **self._parameters) + + @_dispatch + def _logccdf2_dispatch(self, x, y, *, method=None, **params): + # if _logccdf2_formula exists, we could use the complement + # if _ccdf2_formula exists, we could use log/exp + if self._overrides('_logccdf2_formula'): + method = self._logccdf2_formula + else: + method = self._logccdf2_addition + return method + + def _logccdf2_formula(self, x, y, **params): + raise NotImplementedError(self._not_implemented) + + def _logccdf2_addition(self, x, y, **params): + logcdf_x = self._logcdf_dispatch(x, **params) + logccdf_y = self._logccdf_dispatch(y, **params) + return special.logsumexp([logcdf_x, logccdf_y], axis=0) + + @_set_invalid_nan + def _logccdf1(self, x, *, method=None): + return self._logccdf_dispatch(x, method=method, **self._parameters) + + @_dispatch + def _logccdf_dispatch(self, x, method=None, **params): + if self._overrides('_logccdf_formula'): + method = self._logccdf_formula + elif self._overrides('_logcdf_formula'): + method = self._logccdf_complement + elif self._overrides('_ccdf_formula'): + method = self._logccdf_logexp_safe + else: + method = self._logccdf_quadrature + return method + + def _logccdf_formula(self, x, **params): + raise NotImplementedError(self._not_implemented) + + def _logccdf_complement(self, x, **params): + return _log1mexp(self._logcdf_dispatch(x, **params)) + + def _logccdf_logexp(self, x, **params): + return np.log(self._ccdf_dispatch(x, **params)) + + def _logccdf_logexp_safe(self, x, **params): + out = self._logccdf_logexp(x, **params) + mask = np.isinf(out) + if np.any(mask): + params_mask = {key: np.broadcast_to(val, mask.shape)[mask] + for key, val in params.items()} + out = np.asarray(out) + out[mask] = self._logccdf_quadrature(x[mask], **params_mask) + return out[()] + + def _logccdf_quadrature(self, x, **params): + _, b = self._support(**params) + return self._quadrature(self._logpxf_dispatch, limits=(x, b), + params=params, log=True) + + def ccdf(self, x, y=None, /, *, method=None): + if y is None: + return self._ccdf1(x, method=method) + else: + return self._ccdf2(x, y, method=method) + + @_cdf2_input_validation + def _ccdf2(self, x, y, *, method): + return self._ccdf2_dispatch(x, y, method=method, **self._parameters) + + @_dispatch + def _ccdf2_dispatch(self, x, y, *, method=None, **params): + if self._overrides('_ccdf2_formula'): + method = self._ccdf2_formula + else: + method = self._ccdf2_addition + return method + + def _ccdf2_formula(self, x, y, **params): + raise NotImplementedError(self._not_implemented) + + def _ccdf2_addition(self, x, y, **params): + cdf_x = self._cdf_dispatch(x, **params) + ccdf_y = self._ccdf_dispatch(y, **params) + # even if x > y, cdf(x, y) + ccdf(x,y) sums to 1 + return cdf_x + ccdf_y + + @_set_invalid_nan + def _ccdf1(self, x, *, method): + return self._ccdf_dispatch(x, method=method, **self._parameters) + + @_dispatch + def _ccdf_dispatch(self, x, method=None, **params): + if self._overrides('_ccdf_formula'): + method = self._ccdf_formula + elif self._overrides('_logccdf_formula'): + method = self._ccdf_logexp + elif self._overrides('_cdf_formula'): + method = self._ccdf_complement_safe + else: + method = self._ccdf_quadrature + return method + + def _ccdf_formula(self, x, **params): + raise NotImplementedError(self._not_implemented) + + def _ccdf_logexp(self, x, **params): + return np.exp(self._logccdf_dispatch(x, **params)) + + def _ccdf_complement(self, x, **params): + return 1 - self._cdf_dispatch(x, **params) + + def _ccdf_complement_safe(self, x, **params): + cdf = self._cdf_dispatch(x, **params) + out = 1 - cdf + eps = np.finfo(self._dtype).eps + tol = self.tol if not _isnull(self.tol) else np.sqrt(eps) + mask = tol * out < np.spacing(cdf) + if np.any(mask): + params_mask = {key: np.broadcast_to(val, mask.shape)[mask] + for key, val in params.items()} + out = np.asarray(out) + out[mask] = self._ccdf_quadrature(x[mask], **params_mask) + return out[()] + + def _ccdf_quadrature(self, x, **params): + _, b = self._support(**params) + return self._quadrature(self._pxf_dispatch, limits=(x, b), + params=params) + + ## Inverse cumulative distribution functions + + @_set_invalid_nan + def ilogcdf(self, logp, /, *, method=None): + return self._ilogcdf_dispatch(logp, method=method, **self._parameters) + + @_dispatch + def _ilogcdf_dispatch(self, x, method=None, **params): + if self._overrides('_ilogcdf_formula'): + method = self._ilogcdf_formula + elif self._overrides('_ilogccdf_formula'): + method = self._ilogcdf_complement + else: + method = self._ilogcdf_inversion + return method + + def _ilogcdf_formula(self, x, **params): + raise NotImplementedError(self._not_implemented) + + def _ilogcdf_complement(self, x, **params): + return self._ilogccdf_dispatch(_log1mexp(x), **params) + + def _ilogcdf_inversion(self, x, **params): + return self._solve_bounded_continuous(self._logcdf_dispatch, x, params=params) + + @_set_invalid_nan + def icdf(self, p, /, *, method=None): + return self._icdf_dispatch(p, method=method, **self._parameters) + + @_dispatch + def _icdf_dispatch(self, x, method=None, **params): + if self._overrides('_icdf_formula'): + method = self._icdf_formula + elif self._overrides('_iccdf_formula'): + method = self._icdf_complement_safe + else: + method = self._icdf_inversion + return method + + def _icdf_formula(self, x, **params): + raise NotImplementedError(self._not_implemented) + + def _icdf_complement(self, x, **params): + return self._iccdf_dispatch(1 - x, **params) + + def _icdf_complement_safe(self, x, **params): + out = self._icdf_complement(x, **params) + eps = np.finfo(self._dtype).eps + tol = self.tol if not _isnull(self.tol) else np.sqrt(eps) + mask = tol * x < np.spacing(1 - x) + if np.any(mask): + params_mask = {key: np.broadcast_to(val, mask.shape)[mask] + for key, val in params.items()} + out = np.asarray(out) + out[mask] = self._icdf_inversion(x[mask], **params_mask) + return out[()] + + def _icdf_inversion(self, x, **params): + return self._solve_bounded_continuous(self._cdf_dispatch, x, params=params) + + @_set_invalid_nan + def ilogccdf(self, logp, /, *, method=None): + return self._ilogccdf_dispatch(logp, method=method, **self._parameters) + + @_dispatch + def _ilogccdf_dispatch(self, x, method=None, **params): + if self._overrides('_ilogccdf_formula'): + method = self._ilogccdf_formula + elif self._overrides('_ilogcdf_formula'): + method = self._ilogccdf_complement + else: + method = self._ilogccdf_inversion + return method + + def _ilogccdf_formula(self, x, **params): + raise NotImplementedError(self._not_implemented) + + def _ilogccdf_complement(self, x, **params): + return self._ilogcdf_dispatch(_log1mexp(x), **params) + + def _ilogccdf_inversion(self, x, **params): + return self._solve_bounded_continuous(self._logccdf_dispatch, x, params=params) + + @_set_invalid_nan + def iccdf(self, p, /, *, method=None): + return self._iccdf_dispatch(p, method=method, **self._parameters) + + @_dispatch + def _iccdf_dispatch(self, x, method=None, **params): + if self._overrides('_iccdf_formula'): + method = self._iccdf_formula + elif self._overrides('_icdf_formula'): + method = self._iccdf_complement_safe + else: + method = self._iccdf_inversion + return method + + def _iccdf_formula(self, x, **params): + raise NotImplementedError(self._not_implemented) + + def _iccdf_complement(self, x, **params): + return self._icdf_dispatch(1 - x, **params) + + def _iccdf_complement_safe(self, x, **params): + out = self._iccdf_complement(x, **params) + eps = np.finfo(self._dtype).eps + tol = self.tol if not _isnull(self.tol) else np.sqrt(eps) + mask = tol * x < np.spacing(1 - x) + if np.any(mask): + params_mask = {key: np.broadcast_to(val, mask.shape)[mask] + for key, val in params.items()} + out = np.asarray(out) + out[mask] = self._iccdf_inversion(x[mask], **params_mask) + return out[()] + + def _iccdf_inversion(self, x, **params): + return self._solve_bounded_continuous(self._ccdf_dispatch, x, params=params) + + ### Sampling Functions + # The following functions for drawing samples from the distribution are + # exposed via a public method that accepts one positional argument - the + # shape of the sample - and keyword options (but not distribution + # parameters). + # sample + # ~~qmc_sample~~ built into sample now + # + # Common keyword options include: + # method - a string that indicates which method should be used to compute + # the quantity (e.g. a formula or numerical integration). + # rng - the NumPy Generator/SciPy QMCEngine object to used for drawing numbers. + # + # Input/output validation is included in each function, since there is + # little code to be shared. + # These are the methods meant to be called by users. + # + # Each public method calls a private "dispatch" method that + # determines which "method" (strategy for calculating the desired quantity) + # to use by default and, via the `@_dispatch` decorator, calls the + # method and computes the result. + # Each dispatch method can designate the responsibility of sampling to any + # of several "implementation" methods. These methods accept only + # `**params`, the parameter dictionary passed from the public method via + # the "dispatch" method. + # See the note corresponding with the "Distribution Parameters" for more + # information. + + def sample(self, shape=(), *, method=None, rng=None): + # needs output validation to ensure that developer returns correct + # dtype and shape + sample_shape = (shape,) if not np.iterable(shape) else tuple(shape) + full_shape = sample_shape + self._shape + rng = np.random.default_rng(rng) if not isinstance(rng, qmc.QMCEngine) else rng + res = self._sample_dispatch(full_shape, method=method, rng=rng, + **self._parameters) + + return res.astype(self._dtype, copy=False) + + @_dispatch + def _sample_dispatch(self, full_shape, *, method, rng, **params): + # make sure that tests catch if sample is 0d array + if self._overrides('_sample_formula') and not isinstance(rng, qmc.QMCEngine): + method = self._sample_formula + else: + method = self._sample_inverse_transform + return method + + def _sample_formula(self, full_shape, *, rng, **params): + raise NotImplementedError(self._not_implemented) + + def _sample_inverse_transform(self, full_shape, *, rng, **params): + if isinstance(rng, qmc.QMCEngine): + uniform = self._qmc_uniform(full_shape, qrng=rng, **params) + else: + uniform = rng.random(size=full_shape, dtype=self._dtype) + return self._icdf_dispatch(uniform, **params) + + def _qmc_uniform(self, full_shape, *, qrng, **params): + # Generate QMC uniform sample(s) on unit interval with specified shape; + # if `sample_shape != ()`, then each slice along axis 0 is independent. + + sample_shape = full_shape[:len(full_shape)-len(self._shape)] + # Determine the number of independent sequences and the length of each. + n_low_discrepancy = sample_shape[0] if sample_shape else 1 + n_independent = math.prod(full_shape[1:] if sample_shape else full_shape) + + # For each independent sequence, we'll need a new QRNG of the appropriate class + # with its own RNG. (If scramble=False, we don't really need all the separate + # rngs, but I'm not going to add a special code path right now.) + rngs = _rng_spawn(qrng.rng, n_independent) + qrng_class = qrng.__class__ + kwargs = dict(d=1, scramble=qrng.scramble, optimization=qrng._optimization) + if isinstance(qrng, qmc.Sobol): + kwargs['bits'] = qrng.bits + + # Draw uniform low-discrepancy sequences scrambled with each RNG + uniforms = [] + for rng in rngs: + qrng = qrng_class(seed=rng, **kwargs) + uniform = qrng.random(n_low_discrepancy) + uniform = uniform.reshape(n_low_discrepancy if sample_shape else ())[()] + uniforms.append(uniform) + + # Reorder the axes and ensure that the shape is correct + uniform = np.moveaxis(np.stack(uniforms), -1, 0) if uniforms else np.asarray([]) + return uniform.reshape(full_shape) + + ### Moments + # The `moment` method accepts two positional arguments - the order and kind + # (raw, central, or standard) of the moment - and a keyword option: + # method - a string that indicates which method should be used to compute + # the quantity (e.g. a formula or numerical integration). + # Like the distribution properties, input/output validation is provided by + # the `_set_invalid_nan_property` decorator. + # + # Unlike most public methods above, `moment` dispatches to one of three + # private methods - one for each 'kind'. Like most *public* methods above, + # each of these private methods calls a private "dispatch" method that + # determines which "method" (strategy for calculating the desired quantity) + # to use. Also, each dispatch method can designate the responsibility + # computing the moment to one of several "implementation" methods. + # Unlike the dispatch methods above, however, the `@_dispatch` decorator + # is not used, and both logic and method calls are included in the function + # itself. + # Instead of determining which method will be used based solely on the + # implementation methods available and calling only the corresponding + # implementation method, *all* the implementation methods are called + # in sequence until one returns the desired information. When an + # implementation methods cannot provide the requested information, it + # returns the object None (which is distinct from arrays with NaNs or infs, + # which are valid values of moments). + # The reason for this approach is that although formulae for the first + # few moments of a distribution may be found, general formulae that work + # for all orders are not always easy to find. This approach allows the + # developer to write "formula" implementation functions that return the + # desired moment when it is available and None otherwise. + # + # Note that the first implementation method called is a cache. This is + # important because lower-order moments are often needed to compute + # higher moments from formulae, so we eliminate redundant calculations + # when moments of several orders are needed. + + @cached_property + def _moment_methods(self): + return {'cache', 'formula', 'transform', + 'normalize', 'general', 'quadrature'} + + @cached_property + def _lmoment_methods(self): + return {'cache', 'formula', 'general', 'order_statistics', 'quadrature_icdf'} + + @property + def _zero(self): + return self._constants()[0] + + @property + def _one(self): + return self._constants()[1] + + def _constants(self): + if self._constant_cache is not None: + return self._constant_cache + + constants = self._preserve_type([0, 1]) + + if self.cache_policy != _NO_CACHE: + self._constant_cache = constants + + return constants + + @_set_invalid_nan_property + def moment(self, order=1, kind='raw', *, method=None): + kinds = {'raw': self._moment_raw, + 'central': self._moment_central, + 'standardized': self._moment_standardized} + order = self._validate_order(order) + kind = self._validate_kind(kind, kinds) + moment_kind = kinds[kind] + return moment_kind(order, method=method) + + def _moment_raw(self, order=1, *, method=None): + """Raw distribution moment about the origin.""" + # Consider exposing the point about which moments are taken as an + # option. This is easy to support, since `_moment_transform_center` + # does all the work. + methods = self._moment_methods if method is None else {method} + return self._moment_raw_dispatch(order, methods=methods, **self._parameters) + + def _moment_raw_dispatch(self, order, *, methods, **params): + moment = None + + if 'cache' in methods: + moment = self._moment_raw_cache.get(order, None) + + if moment is None and 'formula' in methods: + moment = self._moment_raw_formula(order, **params) + + if moment is None and 'transform' in methods and order > 1: + moment = self._moment_raw_transform(order, **params) + + if moment is None and 'general' in methods: + moment = self._moment_raw_general(order, **params) + + if moment is None and 'quadrature' in methods: + moment = self._moment_from_pxf(order, center=self._zero, **params) + + if moment is None and 'quadrature_icdf' in methods: + moment = self._moment_integrate_icdf(order, center=self._zero, **params) + + if moment is not None and self.cache_policy != _NO_CACHE: + self._moment_raw_cache[order] = moment + + return moment + + def _moment_raw_formula(self, order, **params): + return None + + def _moment_raw_transform(self, order, **params): + central_moments = [] + for i in range(int(order) + 1): + methods = {'cache', 'formula', 'normalize', 'general'} + moment_i = self._moment_central_dispatch(order=i, methods=methods, **params) + if moment_i is None: + return None + central_moments.append(moment_i) + + # Doesn't make sense to get the mean by "transform", since that's + # how we got here. Questionable whether 'quadrature' should be here. + mean_methods = {'cache', 'formula', 'quadrature'} + mean = self._moment_raw_dispatch(self._one, methods=mean_methods, **params) + if mean is None: + return None + + moment = self._moment_transform_center(order, central_moments, mean, self._zero) + return moment + + def _moment_raw_general(self, order, **params): + # This is the only general formula for a raw moment of a probability + # distribution + return self._one if order == 0 else None + + def _moment_central(self, order=1, *, method=None): + """Distribution moment about the mean.""" + methods = self._moment_methods if method is None else {method} + return self._moment_central_dispatch(order, methods=methods, **self._parameters) + + def _moment_central_dispatch(self, order, *, methods, **params): + moment = None + + if 'cache' in methods: + moment = self._moment_central_cache.get(order, None) + + if moment is None and 'formula' in methods: + moment = self._moment_central_formula(order, **params) + + if moment is None and 'transform' in methods: + moment = self._moment_central_transform(order, **params) + + if moment is None and 'normalize' in methods and order > 2: + moment = self._moment_central_normalize(order, **params) + + if moment is None and 'general' in methods: + moment = self._moment_central_general(order, **params) + + if moment is None and 'quadrature' in methods: + mean = self._moment_raw_dispatch(self._one, **params, + methods=self._moment_methods) + moment = self._moment_from_pxf(order, center=mean, **params) + + if moment is None and 'quadrature_icdf' in methods: + mean = self._moment_raw_dispatch(self._one, **params, + methods=self._moment_methods) + moment = self._moment_integrate_icdf(order, center=mean, **params) + + if moment is not None and self.cache_policy != _NO_CACHE: + self._moment_central_cache[order] = moment + + return moment + + def _moment_central_formula(self, order, **params): + return None + + def _moment_central_transform(self, order, **params): + + raw_moments = [] + for i in range(int(order) + 1): + methods = {'cache', 'formula', 'general'} + moment_i = self._moment_raw_dispatch(order=i, methods=methods, **params) + if moment_i is None: + return None + raw_moments.append(moment_i) + + mean_methods = self._moment_methods + mean = self._moment_raw_dispatch(self._one, methods=mean_methods, **params) + + moment = self._moment_transform_center(order, raw_moments, self._zero, mean) + return moment + + def _moment_central_normalize(self, order, **params): + methods = {'cache', 'formula', 'general'} + standard_moment = self._moment_standardized_dispatch(order, **params, + methods=methods) + if standard_moment is None: + return None + var = self._moment_central_dispatch(2, methods=self._moment_methods, **params) + return standard_moment*var**(order/2) + + def _moment_central_general(self, order, **params): + general_central_moments = {0: self._one, 1: self._zero} + return general_central_moments.get(order, None) + + def _moment_standardized(self, order=1, *, method=None): + """Standardized distribution moment.""" + methods = self._moment_methods if method is None else {method} + return self._moment_standardized_dispatch(order, methods=methods, + **self._parameters) + + def _moment_standardized_dispatch(self, order, *, methods, **params): + moment = None + + if 'cache' in methods: + moment = self._moment_standardized_cache.get(order, None) + + if moment is None and 'formula' in methods: + moment = self._moment_standardized_formula(order, **params) + + if moment is None and 'normalize' in methods: + moment = self._moment_standardized_normalize(order, False, **params) + + if moment is None and 'general' in methods: + moment = self._moment_standardized_general(order, **params) + + if moment is None and 'normalize' in methods: + moment = self._moment_standardized_normalize(order, True, **params) + + if moment is not None and self.cache_policy != _NO_CACHE: + self._moment_standardized_cache[order] = moment + + return moment + + def _moment_standardized_formula(self, order, **params): + return None + + def _moment_standardized_normalize(self, order, use_quadrature, **params): + methods = ({'quadrature'} if use_quadrature + else {'cache', 'formula', 'transform'}) + central_moment = self._moment_central_dispatch(order, **params, + methods=methods) + if central_moment is None: + return None + var = self._moment_central_dispatch(2, methods=self._moment_methods, + **params) + return central_moment/var**(order/2) + + def _moment_standardized_general(self, order, **params): + general_standard_moments = {0: self._one, 1: self._zero, 2: self._one} + return general_standard_moments.get(order, None) + + def _moment_from_pxf(self, order, center, **params): + def integrand(x, order, center, **params): + pxf = self._pxf_dispatch(x, **params) + return pxf*(x-center)**order + return self._quadrature(integrand, args=(order, center), params=params) + + def _moment_integrate_icdf(self, order, center, **params): + def integrand(x, order, center, **params): + x = self._icdf_dispatch(x, **params) + return (x-center)**order + return self._quadrature(integrand, limits=(0., 1.), + args=(order, center), params=params) + + def _moment_transform_center(self, order, moment_as, a, b): + a, b, *moment_as = np.broadcast_arrays(a, b, *moment_as) + n = order + i = np.arange(n+1).reshape([-1]+[1]*a.ndim) # orthogonal to other axes + i = self._preserve_type(i) + n_choose_i = special.binom(n, i) + with np.errstate(invalid='ignore'): # can happen with infinite moment + moment_b = np.sum(n_choose_i*moment_as*(a-b)**(n-i), axis=0) + return moment_b + + ### L-Moments + + @_set_invalid_nan_property + def lmoment(self, order=1, *, standardize=True, method=None): + order = self._validate_order(order, fname='lmoment', min_order=1) + return self._lmoment(order, standardize=standardize, method=method) + + def _lmoment(self, order, *, standardize, method): + methods = self._lmoment_methods if method is None else {method} + + lmoment = self._lmoment_dispatch(order, methods=methods, **self._parameters) + if lmoment is None: + return None + + if standardize and order >= 3 and lmoment is not None: + lscale = self._lmoment_dispatch(2, methods=methods, **self._parameters) + if lscale is None: + return None + lmoment = lmoment / lscale + + return lmoment + + def _lmoment_dispatch(self, order, *, methods, **params): + lmoment = None + + if 'cache' in methods: + lmoment = self._lmoment_cache.get(order, None) + + if lmoment is None and 'formula' in methods: + lmoment = self._lmoment_formula(order, **params) + + if lmoment is None and 'general' in methods: + lmoment = self._lmoment_general(order, **params) + + if lmoment is None and 'quadrature_icdf' in methods and ( + self._overrides('_icdf_formula') or self._overrides('_iccdf_formula')): + lmoment = self._lmoment_integrate_icdf(order, **params) + + if lmoment is None and 'order_statistics' in methods: + lmoment = self._lmoment_from_order_statistics(order, **params) + + if lmoment is None and 'quadrature_icdf' in methods: + lmoment = self._lmoment_integrate_icdf(order, **params) + + if lmoment is not None and self.cache_policy != _NO_CACHE: + self._lmoment_cache[order] = lmoment + + return lmoment + + def _lmoment_formula(self, order, **params): + return None + + def _lmoment_general(self, order, **params): + return self.mean() if order == 1 else None + + def _lmoment_from_order_statistics(self, order, **params): + k = np.arange(order) + k = xpx.atleast_nd(k, ndim=self._ndim + 1).T + E = order_statistic(self, r=order-k, n=order).mean() + bc = special.binom(order-1, k) + return np.sum((-1)**k * bc * E, axis=0) / order + + def _lmoment_integrate_icdf(self, order, **params): + def integrand(p, **params): + x = self._icdf_dispatch(p, **params) + P = special.eval_sh_legendre(order - 1, p) + return x * P + return self._quadrature(integrand, limits=(0., 1.), params=params) + + ### Convenience + + def plot(self, x='x', y=None, *, t=None, ax=None): + r"""Plot a function of the distribution. + + Convenience function for quick visualization of the distribution + underlying the random variable. + + Parameters + ---------- + x, y : str, optional + String indicating the quantities to be used as the abscissa and + ordinate (horizontal and vertical coordinates), respectively. + Defaults are ``'x'`` (the domain of the random variable) and either + ``'pdf'`` (the probability density function) (continuous) or + ``'pdf'`` (the probability density function) (discrete). + Valid values are: + 'x', 'pdf', 'pmf', 'cdf', 'ccdf', 'icdf', 'iccdf', 'logpdf', 'logpmf', + 'logcdf', 'logccdf', 'ilogcdf', 'ilogccdf'. + t : 3-tuple of (str, float, float), optional + Tuple indicating the limits within which the quantities are plotted. + The default is ``('cdf', 0.0005, 0.9995)`` if the domain is infinite, + indicating that the central 99.9% of the distribution is to be shown; + otherwise, endpoints of the support are used where they are finite. + Valid values are: + 'x', 'cdf', 'ccdf', 'icdf', 'iccdf', 'logcdf', 'logccdf', + 'ilogcdf', 'ilogccdf'. + ax : `matplotlib.axes`, optional + Axes on which to generate the plot. If not provided, use the + current axes. + + Returns + ------- + ax : `matplotlib.axes` + Axes on which the plot was generated. + The plot can be customized by manipulating this object. + + Examples + -------- + Instantiate a distribution with the desired parameters: + + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from scipy import stats + >>> X = stats.Normal(mu=1., sigma=2.) + + Plot the PDF over the central 99.9% of the distribution. + Compare against a histogram of a random sample. + + >>> ax = X.plot() + >>> sample = X.sample(10000) + >>> ax.hist(sample, density=True, bins=50, alpha=0.5) + >>> plt.show() + + Plot ``logpdf(x)`` as a function of ``x`` in the left tail, + where the log of the CDF is between -10 and ``np.log(0.5)``. + + >>> X.plot('x', 'logpdf', t=('logcdf', -10, np.log(0.5))) + >>> plt.show() + + Plot the PDF of the normal distribution as a function of the + CDF for various values of the scale parameter. + + >>> X = stats.Normal(mu=0., sigma=[0.5, 1., 2]) + >>> X.plot('cdf', 'pdf') + >>> plt.show() + + """ + + # Strategy: given t limits, get quantile limits. Form grid of + # quantiles, compute requested x and y at quantiles, and plot. + # Currently, the grid of quantiles is always linearly spaced. + # Instead of always computing linearly-spaced quantiles, it + # would be better to choose: + # a) quantiles or probabilities + # b) linearly or logarithmically spaced + # based on the specified `t`. + # TODO: + # - smart spacing of points + # - when the parameters of the distribution are an array, + # use the full range of abscissae for all curves + + discrete = isinstance(self, DiscreteDistribution) + t_is_quantile = {'x', 'icdf', 'iccdf', 'ilogcdf', 'ilogccdf'} + t_is_probability = {'cdf', 'ccdf', 'logcdf', 'logccdf'} + valid_t = t_is_quantile.union(t_is_probability) + valid_xy = valid_t.union({'pdf', 'logpdf', 'pmf', 'logpmf'}) + y_default = 'pmf' if discrete else 'pdf' + y = y_default if y is None else y + + ndim = self._ndim + x_name, y_name = x, y + t_name = 'cdf' if t is None else t[0] + + a, b = self.support() + tliml_default = 0 if np.all(np.isfinite(a)) else 0.0005 + tliml = tliml_default if t is None else t[1] + tlimr_default = 1 if np.all(np.isfinite(b)) else 0.9995 + tlimr = tlimr_default if t is None else t[2] + tlim = np.asarray([tliml, tlimr]) + tlim = tlim[:, np.newaxis] if ndim else tlim + + # pdf/logpdf are not valid for `t` because we can't easily invert them + message = (f'Argument `t` of `{self.__class__.__name__}.plot` "' + f'must be one of {valid_t}') + if t_name not in valid_t: + raise ValueError(message) + + message = (f'Argument `x` of `{self.__class__.__name__}.plot` "' + f'must be one of {valid_xy}') + if x_name not in valid_xy: + raise ValueError(message) + + message = (f'Argument `y` of `{self.__class__.__name__}.plot` "' + f'must be one of {valid_xy}') + if y_name not in valid_xy: + raise ValueError(message) + + # This could just be a warning + message = (f'`{self.__class__.__name__}.plot` was called on a random ' + 'variable with at least one invalid shape parameters. When ' + 'a parameter is invalid, no plot can be shown.') + if self._any_invalid: + raise ValueError(message) + + # We could automatically ravel, but do we want to? For now, raise. + message = ("To use `plot`, distribution parameters must be " + "scalars or arrays with one or fewer dimensions.") + if ndim > 1: + raise ValueError(message) + + try: + import matplotlib.pyplot as plt # noqa: F401, E402 + except ModuleNotFoundError as exc: + message = ("`matplotlib` must be installed to use " + f"`{self.__class__.__name__}.plot`.") + raise ModuleNotFoundError(message) from exc + ax = plt.gca() if ax is None else ax + + # get quantile limits given t limits + qlim = tlim if t_name in t_is_quantile else getattr(self, 'i'+t_name)(tlim) + + message = (f"`{self.__class__.__name__}.plot` received invalid input for `t`: " + f"calling {'i'+t_name}({tlim}) produced {qlim}.") + if not np.all(np.isfinite(qlim)): + raise ValueError(message) + + # form quantile grid + if discrete and x_name in t_is_quantile: + # should probably aggregate for large ranges + q = np.arange(np.min(qlim[0]), np.max(qlim[1]) + 1) + q = q[:, np.newaxis] if ndim else q + else: + grid = np.linspace(0, 1, 300) + grid = grid[:, np.newaxis] if ndim else grid + q = qlim[0] + (qlim[1] - qlim[0]) * grid + q = np.round(q) if discrete else q + + # compute requested x and y at quantile grid + x = q if x_name in t_is_quantile else getattr(self, x_name)(q) + y = q if y_name in t_is_quantile else getattr(self, y_name)(q) + + # make plot + x, y = np.broadcast_arrays(x.T, np.atleast_2d(y.T)) + for xi, yi in zip(x, y): # plot is vectorized, but bar/step don't seem to be + if discrete and x_name in t_is_quantile and y_name == 'pmf': + # should this just be a step plot, too? + ax.bar(xi, yi, alpha=np.sqrt(1/y.shape[0])) # alpha heuristic + elif discrete and x_name in t_is_quantile: + values = yi + edges = np.concatenate((xi, [xi[-1]+1])) + ax.stairs(values, edges, baseline=None) + else: + ax.plot(xi, yi) + ax.set_xlabel(f"${x_name}$") + ax.set_ylabel(f"${y_name}$") + ax.set_title(str(self)) + + # only need a legend if distribution has parameters + if len(self._parameters): + label = [] + parameters = self._parameterization.parameters + param_names = list(parameters) + param_arrays = [np.atleast_1d(self._parameters[pname]) + for pname in param_names] + for param_vals in zip(*param_arrays): + assignments = [f"${parameters[name].symbol}$ = {val:.4g}" + for name, val in zip(param_names, param_vals)] + label.append(", ".join(assignments)) + ax.legend(label) + + return ax + + + ### Fitting + # All methods above treat the distribution parameters as fixed, and the + # variable argument may be a quantile or probability. The fitting functions + # are fundamentally different because the quantiles (often observations) + # are considered to be fixed, and the distribution parameters are the + # variables. In a sense, they are like an inverse of the sampling + # functions. + # + # At first glance, it would seem ideal for `fit` to be a classmethod, + # called like `LogUniform.fit(sample=sample)`. + # I tried this. I insisted on it for a while. But if `fit` is a + # classmethod, it cannot call instance methods. If we want to support MLE, + # MPS, MoM, MoLM, then we end up with most of the distribution functions + # above needing to be classmethods, too. All state information, such as + # tolerances and the underlying distribution of `ShiftedScaledDistribution` + # and `OrderStatisticDistribution`, would need to be passed into all + # methods. And I'm not really sure how we would call `fit` as a + # classmethod of a transformed distribution - maybe + # ShiftedScaledDistribution.fit would accept the class of the + # shifted/scaled distribution as an argument? + # + # In any case, it was a conscious decision for the infrastructure to + # treat the parameters as "fixed" and the quantile/percentile arguments + # as "variable". There are a lot of advantages to this structure, and I + # don't think the fact that a few methods reverse the fixed and variable + # quantities should make us question that choice. It can still accommodate + # these methods reasonably efficiently. + + +class ContinuousDistribution(UnivariateDistribution): + def _overrides(self, method_name): + if method_name in {'_logpmf_formula', '_pmf_formula'}: + return True + return super()._overrides(method_name) + + def _pmf_formula(self, x, **params): + return np.zeros_like(x) + + def _logpmf_formula(self, x, **params): + return np.full_like(x, -np.inf) + + def _pxf_dispatch(self, x, *, method=None, **params): + return self._pdf_dispatch(x, method=method, **params) + + def _logpxf_dispatch(self, x, *, method=None, **params): + return self._logpdf_dispatch(x, method=method, **params) + + def _solve_bounded_continuous(self, func, p, params, xatol=None): + return self._solve_bounded(func, p, params=params, xatol=xatol).x + + +class DiscreteDistribution(UnivariateDistribution): + def _overrides(self, method_name): + if method_name in {'_logpdf_formula', '_pdf_formula'}: + return True + return super()._overrides(method_name) + + def _logpdf_formula(self, x, **params): + if params: + p = next(iter(params.values())) + nan_result = np.isnan(x) | np.isnan(p) + else: + nan_result = np.isnan(x) + return np.where(nan_result, np.nan, np.inf) + + def _pdf_formula(self, x, **params): + if params: + p = next(iter(params.values())) + nan_result = np.isnan(x) | np.isnan(p) + else: + nan_result = np.isnan(x) + return np.where(nan_result, np.nan, np.inf) + + def _pxf_dispatch(self, x, *, method=None, **params): + return self._pmf_dispatch(x, method=method, **params) + + def _logpxf_dispatch(self, x, *, method=None, **params): + return self._logpmf_dispatch(x, method=method, **params) + + def _cdf_quadrature(self, x, **params): + return super()._cdf_quadrature(np.floor(x), **params) + + def _logcdf_quadrature(self, x, **params): + return super()._logcdf_quadrature(np.floor(x), **params) + + def _ccdf_quadrature(self, x, **params): + return super()._ccdf_quadrature(np.floor(x + 1), **params) + + def _logccdf_quadrature(self, x, **params): + return super()._logccdf_quadrature(np.floor(x + 1), **params) + + def _cdf2(self, x, y, *, method): + raise NotImplementedError( + "Two argument cdf functions are currently only supported for " + "continuous distributions.") + + def _ccdf2(self, x, y, *, method): + raise NotImplementedError( + "Two argument cdf functions are currently only supported for " + "continuous distributions.") + + def _logcdf2(self, x, y, *, method): + raise NotImplementedError( + "Two argument cdf functions are currently only supported for " + "continuous distributions.") + + def _logccdf2(self, x, y, *, method): + raise NotImplementedError( + "Two argument cdf functions are currently only supported for " + "continuous distributions.") + + def _lmoment(self, order, *, standardize, method): + raise NotImplementedError( + "L-moments are currently available only " + "for continuous distributions.") + + def _solve_bounded_discrete(self, func, p, params, comp): + # We're trying to solve one of these two problems: + # a) find the smallest integer x* within the support s.t. F(x*) >= p + # b) find the smallest integer x* within the support s.t. G(x*) = 1 - F(x*) <= p + # Our approach is to solve a continuous version of the problem that narrows the + # solution down to an integer x s.t. either x* = x or x* = x + 1. At the end, + # we'll choose between them. + + # First, solve func(x) == p where func is a continuous, monotone interpolant + # of either the monotone increasing F or monotone decreasing G. + res = self._solve_bounded(func, p, params=params, xatol=0.9) + # Here, `_solve_bounded` can terminate for one of three reasons: + # 1. `func(res.x) == p` (`fatol = 0` is satisfied), + # 2. `res.xl` and `res.xr` bracket the root and `|res.xr - res.xl| <= xatol`, or + # 3. There is no solution within the support. + # There are several possible strategies for using `res.xl`, `res.x`, and/or + # `res.xr` to find a solution to the original, discrete problem. Here is ours. + + # Consider case 2a. Because F is an increasing function, we know + # that F(xr) >= p (and F(xl) <= p), so F(floor(xr) + 1) >= p. + # F(floor(xr)) *may* be >= p, but we can't know until we evaluate it. + # F(floor(xr) - 1) < p (strictly) because floor(xr) - 1 < xl and F decreases + # monotonically as the argument decreases. So we choose x = floor(xr), and + # later we'll choose between x* = x and x* = x + 1. + x = np.asarray(np.floor(res.bracket[-1])) + # This is also suitable for case 2b. Because G is a *decreasing* function, we + # know that G(xr) <= p (and G(xl) >= p), so G(floor(xr) + 1) <= p. + # G(floor(xr)) *may* be <= p, but we can't know until we evaluate it. + # G(floor(xr) - 1) > p (strictly) because floor(xr) - 1 < xl and G increases + # as the argument decreases. So we would still want to choose x = floor(xr), and + # later we'll choose between x* = x and x* = x + 1. + + # Now we consider case 1a/b. In this case, `res.x` solved the equation + # *exactly*, so the algorithm may have terminated before the bracket is tight + # enough to rely on `res.xr`. If `res.x` happens to be integral, `res.x` is + # the solution to the discrete problem, and floor(res.x) == res.x, so + # floor(res.x) is the solution to the discrete problem. If not: + # a) F(floor(res.x)) < p (strictly) and F(floor(res.x) + 1) > p (strictly). So + # floor(res.x) + 1 is the solution to the discrete problem. + # b) G(floor(res.x)) > p (strictly) and G(floor(res.x) + 1) < p (strictly). So + # floor(res.x) + 1 is again the solution to the discrete problem. + # Either way, we can choose x = res.x, and at the end we'll choose between + # x* = x and x* = x + 1. + mask = res.f_x == 0 + x[mask] = np.floor(res.x[mask]) + + # For case 3, let xmin be the left endpoint of the support, and note that in + # general, F(xmin) > 0 and G(xmin) < 1. Therefore it is possible that: + # a) F(x) > p for all x in the support (e.g. because p ~ 0) + # a) G(x) < p for all x in the support (e.g. because p ~ 1) + # In these cases, `_solve_bounded` would fail to find a root of the continuous + # equation above, but the solution to the original, discrete problem is the left + # endpoint of the support. + # This case is handled before we get to this function; otherwise, + # `_solve_bounded` may spin its wheels for a long time in vain. + + # Now, we choose between x* = x and x* = x + 1: if func(x) satisfies the + # comparison `comp` (>= for cdf, <= for ccdf), the solution is x* = x; + # otherwise the solution must be x* = x + 1. + f = func(x, **params) + x = np.where(comp(f, p), x, x + 1.0) + x[np.isnan(f)] = np.nan # needed? why would func(x) be NaN within support? + + return x + + def _base_discrete_inversion(self, p, func, comp, /, **params): + # For discrete distributions, icdf(p) is defined as the minimum integer x* + # within the support such that F(x*) >= p; iccdf(p) is the minimum integer x* + # within the support such that G(x*) <= p. + + # Identify where the solution is xmin. + # (See rationale in `_solve_bounded_discrete`.) + xmin, xmax = self._support(**params) + p, xmin, _ = np.broadcast_arrays(p, xmin, xmax) + mask = comp(func(xmin, **params), p) + + # Use `apply_where` to perform the inversion only when necessary. + def f1(p, *args): + return self._solve_bounded_discrete( + func, p, params=dict(zip(params.keys(), args)), comp=comp) + + x = xpx.apply_where(~mask, (p, *params.values()), f1, fill_value=xmin) + + # x above may be a finite value even when p is NaN, so the returned value + # should be NaN. We need to handle this as a special case. + x[np.isnan(p)] = np.nan + return x[()] + + def _icdf_inversion(self, x, **params): + return self._base_discrete_inversion(x, self._cdf_dispatch, + np.greater_equal, **params) + + def _ilogcdf_inversion(self, x, **params): + return self._base_discrete_inversion(x, self._logcdf_dispatch, + np.greater_equal, **params) + + def _iccdf_inversion(self, x, **params): + return self._base_discrete_inversion(x, self._ccdf_dispatch, + np.less_equal, **params) + + def _ilogccdf_inversion(self, x, **params): + return self._base_discrete_inversion(x, self._logccdf_dispatch, + np.less_equal, **params) + + def _mode_optimization(self, **params): + # If `x` is the true mode of a unimodal continuous function, we can find + # the mode among integers by rounding in each direction and checking + # which is better. If the difference between `x` and the nearest integer + # is less than `xatol`, the computed value of `x` may end up on the wrong + # side of the nearest integer. Setting `xatol=0.5` guarantees that at most + # three integers need to be checked, the two nearest integers, ``floor(x)`` + # and ``round(x)`` and the nearest integer other than these. + x = super()._mode_optimization(xatol=0.5, **params) + low, high = self.support() + xl, xr = np.floor(x), np.ceil(x) + nearest = np.round(x) + # Clip to stay within support. There will be redundant calculation + # when clipping since `xo` will be one of `xl` or `xr`, but let's + # keep the implementation simple for now. + xo = np.clip(nearest + np.copysign(1, nearest - x), low, high) + x = np.stack([xl, xo, xr]) + idx = np.argmax(self._pmf_dispatch(x, **params), axis=0) + return np.choose(idx, [xl, xo, xr]) + + def _logentropy_quadrature(self, **params): + def logintegrand(x, **params): + logpmf = self._logpmf_dispatch(x, **params) + # Entropy summand is -pmf*log(pmf), so log-entropy summand is + # logpmf + log(logpmf) + pi*j. But pmf is always between 0 and 1, + # so logpmf is always negative, and so log(logpmf) = log(-logpmf) + pi*j. + # The two imaginary components "cancel" each other out (which we would + # expect because each term of the entropy summand is positive). + return np.where(np.isfinite(logpmf), logpmf + np.log(-logpmf), -np.inf) + return self._quadrature(logintegrand, params=params, log=True) + + +# Special case the names of some new-style distributions in `make_distribution` +_distribution_names = { + # Continuous + 'argus': 'ARGUS', + 'betaprime': 'BetaPrime', + 'chi2': 'ChiSquared', + 'crystalball': 'CrystalBall', + 'dgamma': 'DoubleGamma', + 'dweibull': 'DoubleWeibull', + 'expon': 'Exponential', + 'exponnorm': 'ExponentiallyModifiedNormal', + 'exponweib': 'ExponentialWeibull', + 'exponpow': 'ExponentialPower', + 'fatiguelife': 'FatigueLife', + 'foldcauchy': 'FoldedCauchy', + 'foldnorm': 'FoldedNormal', + 'genlogistic': 'GeneralizedLogistic', + 'gennorm': 'GeneralizedNormal', + 'genpareto': 'GeneralizedPareto', + 'genexpon': 'GeneralizedExponential', + 'genextreme': 'GeneralizedExtremeValue', + 'gausshyper': 'GaussHypergeometric', + 'gengamma': 'GeneralizedGamma', + 'genhalflogistic': 'GeneralizedHalfLogistic', + 'geninvgauss': 'GeneralizedInverseGaussian', + 'gumbel_r': 'Gumbel', + 'gumbel_l': 'ReflectedGumbel', + 'halfcauchy': 'HalfCauchy', + 'halflogistic': 'HalfLogistic', + 'halfnorm': 'HalfNormal', + 'halfgennorm': 'HalfGeneralizedNormal', + 'hypsecant': 'HyperbolicSecant', + 'invgamma': 'InverseGammma', + 'invgauss': 'InverseGaussian', + 'invweibull': 'InverseWeibull', + 'irwinhall': 'IrwinHall', + 'jf_skew_t': 'JonesFaddySkewT', + 'johnsonsb': 'JohnsonSB', + 'johnsonsu': 'JohnsonSU', + 'ksone': 'KSOneSided', + 'kstwo': 'KSTwoSided', + 'kstwobign': 'KSTwoSidedAsymptotic', + 'laplace_asymmetric': 'LaplaceAsymmetric', + 'levy_l': 'LevyLeft', + 'levy_stable': 'LevyStable', + 'loggamma': 'ExpGamma', # really the Exponential Gamma Distribution + 'loglaplace': 'LogLaplace', + 'lognorm': 'LogNormal', + 'loguniform': 'LogUniform', + 'ncx2': 'NoncentralChiSquared', + 'nct': 'NoncentralT', + 'norm': 'Normal', + 'norminvgauss': 'NormalInverseGaussian', + 'powerlaw': 'PowerLaw', + 'powernorm': 'PowerNormal', + 'rdist': 'R', + 'rel_breitwigner': 'RelativisticBreitWigner', + 'recipinvgauss': 'ReciprocalInverseGaussian', + 'reciprocal': 'LogUniform', + 'semicircular': 'SemiCircular', + 'skewcauchy': 'SkewCauchy', + 'skewnorm': 'SkewNormal', + 'studentized_range': 'StudentizedRange', + 't': 'StudentT', + 'trapezoid': 'Trapezoidal', + 'triang': 'Triangular', + 'truncexpon': 'TruncatedExponential', + 'truncnorm': 'TruncatedNormal', + 'truncpareto': 'TruncatedPareto', + 'truncweibull_min': 'TruncatedWeibull', + 'tukeylambda': 'TukeyLambda', + 'vonmises_line': 'VonMisesLine', + 'weibull_min': 'Weibull', + 'weibull_max': 'ReflectedWeibull', + 'wrapcauchy': 'WrappedCauchyLine', + # Discrete + 'betabinom': 'BetaBinomial', + 'betanbinom': 'BetaNegativeBinomial', + 'dlaplace': 'LaplaceDiscrete', + 'geom': 'Geometric', + 'hypergeom': 'Hypergeometric', + 'logser': 'LogarithmicSeries', + 'nbinom': 'NegativeBinomial', + 'nchypergeom_fisher': 'NoncentralHypergeometricFisher', + 'nchypergeom_wallenius': 'NoncentralHypergeometricWallenius', + 'nhypergeom': 'NegativeHypergeometric', + 'poisson_binom': 'PoissonBinomial', + 'randint': 'UniformDiscrete', + 'yulesimon': 'YuleSimon', + 'zipf': 'Zeta', +} + + +@xp_capabilities(np_only=True) +def make_distribution(dist): + """Generate a `UnivariateDistribution` class from a compatible object. + + The argument may be an instance of `rv_continuous` or an instance of + another class that satisfies the interface described below. + + The returned value is a `ContinuousDistribution` subclass if the input is an + instance of `rv_continuous` or a `DiscreteDistribution` subclass if the input + is an instance of `rv_discrete`. Like any subclass of `UnivariateDistribution`, + it must be instantiated (i.e. by passing all shape parameters as keyword + arguments) before use. Once instantiated, the resulting object will have the + same interface as any other instance of `UnivariateDistribution`; e.g., + `scipy.stats.Normal`, `scipy.stats.Binomial`. + + .. note:: + + `make_distribution` does not work perfectly with all instances of + `rv_continuous`. Known failures include `levy_stable`, `vonmises`, + `hypergeom`, 'nchypergeom_fisher', 'nchypergeom_wallenius', and + `poisson_binom`. Some methods of some distributions will not support + array shape parameters. + + Parameters + ---------- + dist : `rv_continuous` + Instance of `rv_continuous`, `rv_discrete`, or an instance of any class with + the following attributes: + + __make_distribution_version__ : str + A string containing the version number of SciPy in which this interface + is defined. The preferred interface may change in future SciPy versions, + in which case support for an old interface version may be deprecated + and eventually removed. + parameters : dict or tuple + If a dictionary, each key is the name of a parameter, + and the corresponding value is either a dictionary or tuple. + If the value is a dictionary, it may have the following items, with default + values used for entries which aren't present. + + endpoints : tuple, default: (-inf, inf) + A tuple defining the lower and upper endpoints of the domain of the + parameter; allowable values are floats, the name (string) of another + parameter, or a callable taking parameters as keyword only + arguments and returning the numerical value of an endpoint for + given parameter values. + + inclusive : tuple of bool, default: (False, False) + A tuple specifying whether the endpoints are included within the domain + of the parameter. + + typical : tuple, default: ``endpoints`` + Defining endpoints of a typical range of values of a parameter. Can be + used for sampling parameter values for testing. Behaves like the + ``endpoints`` tuple above, and should define a subinterval of the + domain given by ``endpoints``. + + A tuple value ``(a, b)`` associated to a key in the ``parameters`` + dictionary is equivalent to ``{endpoints: (a, b)}``. + + Custom distributions with multiple parameterizations can be defined by + having the ``parameters`` attribute be a tuple of dictionaries with + the structure described above. In this case, ``dist``\'s class must also + define a method ``process_parameters`` to map between the different + parameterizations. It must take all parameters from all parameterizations + as optional keyword arguments and return a dictionary mapping parameters to + values, filling in values from other parameterizations using values from + the supplied parameterization. See example. + + support : dict or tuple + A dictionary describing the support of the distribution or a tuple + describing the endpoints of the support. This behaves identically to + the values of the parameters dict described above. + + The class **must** also define a ``pdf`` method and **may** define methods + ``logentropy``, ``entropy``, ``median``, ``mode``, ``logpdf``, + ``logcdf``, ``cdf``, ``logccdf``, ``ccdf``, + ``ilogcdf``, ``icdf``, ``ilogccdf``, ``iccdf``, + ``moment``, ``lmoment``, and ``sample``. + If defined, these methods must accept the parameters of the distribution as + keyword arguments and also accept any positional-only arguments accepted by + the corresponding method of `ContinuousDistribution`. + When multiple parameterizations are defined, these methods must accept + all parameters from all parameterizations. The ``moment`` method + must accept the ``order`` and ``kind`` arguments by position or keyword, but + may return ``None`` if a formula is not available for the arguments; in this + case, the infrastructure will fall back to a default implementation. + The ``lmoment`` method must accept the ``order`` argument by position or + keyword and return the specified L-moment (not the L-moment ratio), but may + return ``None`` if a formula is not available for the arguments. The + ``sample`` method must accept ``shape`` by position or keyword, but contrary + to the public method of the same name, the argument it receives will be the + *full* shape of the output array - that is, the shape passed to the public + method prepended to the broadcasted shape of random variable parameters. + + Returns + ------- + CustomDistribution : `UnivariateDistribution` + A subclass of `UnivariateDistribution` corresponding with `dist`. The + initializer requires all shape parameters to be passed as keyword arguments + (using the same names as the instance of `rv_continuous`/`rv_discrete`). + + Notes + ----- + The documentation of `UnivariateDistribution` is not rendered. See below for + an example of how to instantiate the class (i.e. pass all shape parameters of + `dist` to the initializer as keyword arguments). Documentation of all methods + is identical to that of `scipy.stats.Normal`. Use ``help`` on the returned + class or its methods for more information. + + Examples + -------- + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from scipy import stats + >>> from scipy import special + + Create a `ContinuousDistribution` from `scipy.stats.loguniform`. + + >>> LogUniform = stats.make_distribution(stats.loguniform) + >>> X = LogUniform(a=1.0, b=3.0) + >>> np.isclose((X + 0.25).median(), stats.loguniform.ppf(0.5, 1, 3, loc=0.25)) + np.True_ + >>> X.plot() + >>> sample = X.sample(10000, rng=np.random.default_rng()) + >>> plt.hist(sample, density=True, bins=30) + >>> plt.legend(('pdf', 'histogram')) + >>> plt.show() + + Create a custom distribution. + + >>> class MyLogUniform: + ... @property + ... def __make_distribution_version__(self): + ... return "1.16.0" + ... + ... @property + ... def parameters(self): + ... return {'a': {'endpoints': (0, np.inf), + ... 'inclusive': (False, False)}, + ... 'b': {'endpoints': ('a', np.inf), + ... 'inclusive': (False, False)}} + ... + ... @property + ... def support(self): + ... return {'endpoints': ('a', 'b'), 'inclusive': (True, True)} + ... + ... def pdf(self, x, a, b): + ... return 1 / (x * (np.log(b)- np.log(a))) + >>> + >>> MyLogUniform = stats.make_distribution(MyLogUniform()) + >>> Y = MyLogUniform(a=1.0, b=3.0) + >>> np.isclose(Y.cdf(2.), X.cdf(2.)) + np.True_ + + Create a custom distribution with variable support. + + >>> class MyUniformCube: + ... @property + ... def __make_distribution_version__(self): + ... return "1.16.0" + ... + ... @property + ... def parameters(self): + ... return {"a": (-np.inf, np.inf), + ... "b": {'endpoints':('a', np.inf), 'inclusive':(True, False)}} + ... + ... @property + ... def support(self): + ... def left(*, a, b): + ... return a**3 + ... + ... def right(*, a, b): + ... return b**3 + ... return (left, right) + ... + ... def pdf(self, x, *, a, b): + ... return 1 / (3*(b - a)*np.cbrt(x)**2) + ... + ... def cdf(self, x, *, a, b): + ... return (np.cbrt(x) - a) / (b - a) + >>> + >>> MyUniformCube = stats.make_distribution(MyUniformCube()) + >>> X = MyUniformCube(a=-2, b=2) + >>> Y = stats.Uniform(a=-2, b=2)**3 + >>> X.support() + (-8.0, 8.0) + >>> np.isclose(X.cdf(2.1), Y.cdf(2.1)) + np.True_ + + Create a custom distribution with multiple parameterizations. Here we create a + custom version of the beta distribution that has an alternative parameterization + in terms of the mean ``mu`` and a dispersion parameter ``nu``. + + >>> class MyBeta: + ... @property + ... def __make_distribution_version__(self): + ... return "1.16.0" + ... + ... @property + ... def parameters(self): + ... return ({"a": (0, np.inf), "b": (0, np.inf)}, + ... {"mu": (0, 1), "nu": (0, np.inf)}) + ... + ... def process_parameters(self, a=None, b=None, mu=None, nu=None): + ... if a is not None and b is not None: + ... nu = a + b + ... mu = a / nu + ... else: + ... a = mu * nu + ... b = nu - a + ... return dict(a=a, b=b, mu=mu, nu=nu) + ... + ... @property + ... def support(self): + ... return {'endpoints': (0, 1)} + ... + ... def pdf(self, x, a, b, mu, nu): + ... return special._ufuncs._beta_pdf(x, a, b) + ... + ... def cdf(self, x, a, b, mu, nu): + ... return special.betainc(a, b, x) + >>> + >>> MyBeta = stats.make_distribution(MyBeta()) + >>> X = MyBeta(a=2.0, b=2.0) + >>> Y = MyBeta(mu=0.5, nu=4.0) + >>> np.isclose(X.pdf(0.3), Y.pdf(0.3)) + np.True_ + + """ + if dist in {stats.levy_stable, stats.vonmises, stats.hypergeom, + stats.nchypergeom_fisher, stats.nchypergeom_wallenius, + stats.poisson_binom}: + raise NotImplementedError(f"`{dist.name}` is not supported.") + + if isinstance(dist, stats.rv_continuous | stats.rv_discrete): + return _make_distribution_rv_generic(dist) + elif getattr(dist, "__make_distribution_version__", "0.0.0") >= "1.16.0": + return _make_distribution_custom(dist) + else: + message = ("The argument must be an instance of `rv_continuous`, " + "`rv_discrete`, or an instance of a class with attribute " + "`__make_distribution_version__ >= 1.16`.") + raise ValueError(message) + +def _make_distribution_rv_generic(dist): + parameters = [] + names = [] + support = getattr(dist, '_support', (dist.a, dist.b)) + for shape_info in dist._shape_info(): + domain = _RealInterval(endpoints=shape_info.endpoints, + inclusive=shape_info.inclusive) + param = _RealParameter(shape_info.name, domain=domain) + parameters.append(param) + names.append(shape_info.name) + + repr_str = _distribution_names.get(dist.name, dist.name.capitalize()) + if isinstance(dist, stats.rv_continuous): + old_class, new_class = stats.rv_continuous, ContinuousDistribution + else: + old_class, new_class = stats.rv_discrete, DiscreteDistribution + + def _overrides(method_name): + return (getattr(dist.__class__, method_name, None) + is not getattr(old_class, method_name, None)) + + if _overrides("_get_support"): + def left(**parameter_values): + a, _ = dist._get_support(**parameter_values) + return np.asarray(a)[()] + + def right(**parameter_values): + _, b = dist._get_support(**parameter_values) + return np.asarray(b)[()] + + endpoints = (left, right) + else: + endpoints = support + + _x_support = _RealInterval(endpoints=endpoints, inclusive=(True, True)) + _x_param = _RealParameter('x', domain=_x_support, typical=(-1, 1)) + + class CustomDistribution(new_class): + _parameterizations = ([_Parameterization(*parameters)] if parameters + else []) + _variable = _x_param + + __class_getitem__ = None + + def __repr__(self): + s = super().__repr__() + return s.replace('CustomDistribution', repr_str) + + def __str__(self): + s = super().__str__() + return s.replace('CustomDistribution', repr_str) + + def _sample_formula(self, full_shape=(), *, rng=None, **kwargs): + return dist._rvs(size=full_shape, random_state=rng, **kwargs) + + def _moment_raw_formula(self, order, **kwargs): + return dist._munp(int(order), **kwargs) + + def _moment_raw_formula_1(self, order, **kwargs): + if order != 1: + return None + return dist._stats(**kwargs)[0] + + def _moment_central_formula(self, order, **kwargs): + if order != 2: + return None + return dist._stats(**kwargs)[1] + + def _moment_standard_formula(self, order, **kwargs): + if order == 3: + if dist._stats_has_moments: + kwargs['moments'] = 's' + return dist._stats(**kwargs)[int(order - 1)] + elif order == 4: + if dist._stats_has_moments: + kwargs['moments'] = 'k' + k = dist._stats(**kwargs)[int(order - 1)] + return k if k is None else k + 3 + else: + return None + + methods = {'_logpdf': '_logpdf_formula', + '_pdf': '_pdf_formula', + '_logpmf': '_logpmf_formula', + '_pmf': '_pmf_formula', + '_logcdf': '_logcdf_formula', + '_cdf': '_cdf_formula', + '_logsf': '_logccdf_formula', + '_sf': '_ccdf_formula', + '_ppf': '_icdf_formula', + '_isf': '_iccdf_formula', + '_entropy': '_entropy_formula', + '_median': '_median_formula'} + + # These are not desirable overrides for the new infrastructure + skip_override = {'norminvgauss': {'_sf', '_isf'}} + + for old_method, new_method in methods.items(): + if dist.name in skip_override and old_method in skip_override[dist.name]: + continue + # If method of old distribution overrides generic implementation... + method = getattr(dist.__class__, old_method, None) + super_method = getattr(old_class, old_method, None) + if method is not super_method: + # Make it an attribute of the new object with the new name + setattr(CustomDistribution, new_method, getattr(dist, old_method)) + + if _overrides('_munp'): + CustomDistribution._moment_raw_formula = _moment_raw_formula + + if _overrides('_rvs'): + CustomDistribution._sample_formula = _sample_formula + + if _overrides('_stats'): + CustomDistribution._moment_standardized_formula = _moment_standard_formula + if not _overrides('_munp'): + CustomDistribution._moment_raw_formula = _moment_raw_formula_1 + CustomDistribution._moment_central_formula = _moment_central_formula + + support_etc = _combine_docs(CustomDistribution, include_examples=False).lstrip() + docs = [ + f"This class represents `scipy.stats.{dist.name}` as a subclass of " + f"`{new_class}`.", + f"The `repr`/`str` of class instances is `{repr_str}`.", + f"The PDF of the distribution is defined {support_etc}" + ] + CustomDistribution.__doc__ = ("\n".join(docs)) + + return CustomDistribution + + +def _get_domain_info(info): + domain_info = {"endpoints": info} if isinstance(info, tuple) else info + typical = domain_info.pop("typical", None) + return domain_info, typical + + +def _make_distribution_custom(dist): + dist_parameters = ( + dist.parameters if isinstance(dist.parameters, tuple) else (dist.parameters, ) + ) + parameterizations = [] + for parameterization in dist_parameters: + # The attribute name ``parameters`` appears reasonable from a user facing + # perspective, but there is a little tension here with the internal. It's + # important to keep in mind that the ``parameters`` attribute in a + # user-created custom distribution specifies ``_parameterizations`` within + # the infrastructure. + parameters = [] + + for name, info in parameterization.items(): + domain_info, typical = _get_domain_info(info) + domain = _RealInterval(**domain_info) + param = _RealParameter(name, domain=domain, typical=typical) + parameters.append(param) + + if parameters: + parameterizations.append(_Parameterization(*parameters)) + + domain_info, typical = _get_domain_info(dist.support) + _x_support = _RealInterval(**domain_info) + _x_param = _RealParameter('x', domain=_x_support, typical=typical) + repr_str = dist.__class__.__name__ + + class CustomDistribution(ContinuousDistribution): + _parameterizations = parameterizations + _variable = _x_param + + def __repr__(self): + s = super().__repr__() + return s.replace('CustomDistribution', repr_str) + + def __str__(self): + s = super().__str__() + return s.replace('CustomDistribution', repr_str) + + methods = {'sample', 'logentropy', 'entropy', + 'median', 'mode', 'logpdf', 'pdf', + 'logcdf2', 'logcdf', 'cdf2', 'cdf', + 'logccdf2', 'logccdf', 'ccdf2', 'ccdf', + 'ilogcdf', 'icdf', 'ilogccdf', 'iccdf', + 'lmoment'} + + for method in methods: + if hasattr(dist, method): + # Make it an attribute of the new object with the new name + new_method = f"_{method}_formula" + setattr(CustomDistribution, new_method, getattr(dist, method)) + + if hasattr(dist, 'moment'): + def _moment_raw_formula(self, order, **kwargs): + return dist.moment(order, kind='raw', **kwargs) + + def _moment_central_formula(self, order, **kwargs): + return dist.moment(order, kind='central', **kwargs) + + def _moment_standardized_formula(self, order, **kwargs): + return dist.moment(order, kind='standardized', **kwargs) + + CustomDistribution._moment_raw_formula = _moment_raw_formula + CustomDistribution._moment_central_formula = _moment_central_formula + CustomDistribution._moment_standardized_formula = _moment_standardized_formula + + if hasattr(dist, 'process_parameters'): + setattr( + CustomDistribution, + "_process_parameters", + getattr(dist, "process_parameters") + ) + + support_etc = _combine_docs(CustomDistribution, include_examples=False).lstrip() + docs = [ + f"This class represents `{repr_str}` as a subclass of " + "`ContinuousDistribution`.", + f"The PDF of the distribution is defined {support_etc}" + ] + CustomDistribution.__doc__ = ("\n".join(docs)) + + return CustomDistribution + + +# The purpose of making ShiftedScaledDistribution a separate class is for +# a) simplicity of the ContinuousDistribution class and +# b) avoiding the requirement that every distribution accept loc/scale. +# The simplicity of ContinuousDistribution is important, because there are +# several other distribution transformations to be supported; e.g., truncation, +# wrapping, folding, and doubling. We wouldn't want to cram all of this +# into the `ContinuousDistribution` class. Also, the order of the composition +# matters (e.g. truncate then shift/scale or vice versa). It's easier to +# accommodate different orders if the transformation is built up from +# components rather than all built into `ContinuousDistribution`. + +def _shift_scale_distribution_function_2arg(func): + def wrapped(self, x, y, *args, loc, scale, sign, **kwargs): + item = func.__name__ + + f = getattr(self._dist, item) + + # Obviously it's possible to get away with half of the work here. + # Let's focus on correct results first and optimize later. + xt = self._transform(x, loc, scale) + yt = self._transform(y, loc, scale) + fxy = f(xt, yt, *args, **kwargs) + fyx = f(yt, xt, *args, **kwargs) + return np.real_if_close(np.where(sign, fxy, fyx))[()] + + return wrapped + +def _shift_scale_distribution_function(func): + # c is for complementary + citem = {'_logcdf_dispatch': '_logccdf_dispatch', + '_cdf_dispatch': '_ccdf_dispatch', + '_logccdf_dispatch': '_logcdf_dispatch', + '_ccdf_dispatch': '_cdf_dispatch'} + def wrapped(self, x, *args, loc, scale, sign, **kwargs): + item = func.__name__ + + f = getattr(self._dist, item) + cf = getattr(self._dist, citem[item]) + + # Obviously it's possible to get away with half of the work here. + # Let's focus on correct results first and optimize later. + xt = self._transform(x, loc, scale) + fx = f(xt, *args, **kwargs) + cfx = cf(xt, *args, **kwargs) + return np.where(sign, fx, cfx)[()] + + return wrapped + +def _shift_scale_inverse_function(func): + citem = {'_ilogcdf_dispatch': '_ilogccdf_dispatch', + '_icdf_dispatch': '_iccdf_dispatch', + '_ilogccdf_dispatch': '_ilogcdf_dispatch', + '_iccdf_dispatch': '_icdf_dispatch'} + def wrapped(self, p, *args, loc, scale, sign, **kwargs): + item = func.__name__ + + f = getattr(self._dist, item) + cf = getattr(self._dist, citem[item]) + + # Obviously it's possible to get away with half of the work here. + # Let's focus on correct results first and optimize later. + fx = self._itransform(f(p, *args, **kwargs), loc, scale) + cfx = self._itransform(cf(p, *args, **kwargs), loc, scale) + return np.where(sign, fx, cfx)[()] + + return wrapped + + +class TransformedDistribution(ContinuousDistribution): + def __init__(self, X, /, *args, **kwargs): + if not isinstance(X, ContinuousDistribution): + message = "Transformations are currently only supported for continuous RVs." + raise NotImplementedError(message) + self._copy_parameterization() + self._variable = X._variable + self._dist = X + if X._parameterization: + # Add standard distribution parameters to our parameterization + dist_parameters = X._parameterization.parameters + set_params = set(dist_parameters) + if not self._parameterizations: + self._parameterizations.append(_Parameterization()) + for parameterization in self._parameterizations: + if set_params.intersection(parameterization.parameters): + message = (f"One or more of the parameters of {X} has " + "the same name as a parameter of " + f"{self.__class__.__name__}. Name collisions " + "create ambiguities and are not supported.") + raise ValueError(message) + parameterization.parameters.update(dist_parameters) + super().__init__(*args, **kwargs) + + def _overrides(self, method_name): + return (self._dist._overrides(method_name) + or super()._overrides(method_name)) + + def reset_cache(self): + super().reset_cache() + + def _update_parameters(self, *, validation_policy=None, **params): + # maybe broadcast everything before processing? + parameters = {} + # There may be some issues with _original_parameters + # We only want to update with _dist._original_parameters during + # initialization. Afterward that, we want to start with + # self._original_parameters. + parameters.update(self._dist._original_parameters) + parameters.update(params) + super()._update_parameters(validation_policy=validation_policy, **parameters) + + def _process_parameters(self, **params): + return self._dist._process_parameters(**params) + + def __repr__(self): + raise NotImplementedError() + + def __str__(self): + raise NotImplementedError() + + +class TruncatedDistribution(TransformedDistribution): + """Truncated distribution.""" + # TODO: + # - consider avoiding catastropic cancellation by using appropriate tail + # - if the mode of `_dist` is within the support, it's still the mode + # - rejection sampling might be more efficient than inverse transform + + _lb_domain = _RealInterval(endpoints=(-inf, 'ub'), inclusive=(True, False)) + _lb_param = _RealParameter('lb', symbol=r'b_l', + domain=_lb_domain, typical=(0.1, 0.2)) + + _ub_domain = _RealInterval(endpoints=('lb', inf), inclusive=(False, True)) + _ub_param = _RealParameter('ub', symbol=r'b_u', + domain=_ub_domain, typical=(0.8, 0.9)) + + _parameterizations = [_Parameterization(_lb_param, _ub_param), + _Parameterization(_lb_param), + _Parameterization(_ub_param)] + + def __init__(self, X, /, *args, lb=-np.inf, ub=np.inf, **kwargs): + return super().__init__(X, *args, lb=lb, ub=ub, **kwargs) + + def _process_parameters(self, lb=None, ub=None, **params): + lb = lb if lb is not None else np.full_like(lb, -np.inf)[()] + ub = ub if ub is not None else np.full_like(ub, np.inf)[()] + parameters = self._dist._process_parameters(**params) + a, b = self._support(lb=lb, ub=ub, **parameters) + logmass = self._dist._logcdf2_dispatch(a, b, **parameters) + parameters.update(dict(lb=lb, ub=ub, _a=a, _b=b, logmass=logmass)) + return parameters + + def _support(self, lb, ub, **params): + a, b = self._dist._support(**params) + return np.maximum(a, lb), np.minimum(b, ub) + + def _overrides(self, method_name): + return False + + def _logpdf_dispatch(self, x, *args, lb, ub, _a, _b, logmass, **params): + logpdf = self._dist._logpdf_dispatch(x, *args, **params) + return logpdf - logmass + + def _logcdf_dispatch(self, x, *args, lb, ub, _a, _b, logmass, **params): + logcdf = self._dist._logcdf2_dispatch(_a, x, *args, **params) + # of course, if this result is small we could compute with the other tail + return logcdf - logmass + + def _logccdf_dispatch(self, x, *args, lb, ub, _a, _b, logmass, **params): + logccdf = self._dist._logcdf2_dispatch(x, _b, *args, **params) + return logccdf - logmass + + def _logcdf2_dispatch(self, x, y, *args, lb, ub, _a, _b, logmass, **params): + logcdf2 = self._dist._logcdf2_dispatch(x, y, *args, **params) + return logcdf2 - logmass + + def _ilogcdf_dispatch(self, logp, *args, lb, ub, _a, _b, logmass, **params): + log_Fa = self._dist._logcdf_dispatch(_a, *args, **params) + logp_adjusted = np.logaddexp(log_Fa, logp + logmass) + return self._dist._ilogcdf_dispatch(logp_adjusted, *args, **params) + + def _ilogccdf_dispatch(self, logp, *args, lb, ub, _a, _b, logmass, **params): + log_cFb = self._dist._logccdf_dispatch(_b, *args, **params) + logp_adjusted = np.logaddexp(log_cFb, logp + logmass) + return self._dist._ilogccdf_dispatch(logp_adjusted, *args, **params) + + def _icdf_dispatch(self, p, *args, lb, ub, _a, _b, logmass, **params): + Fa = self._dist._cdf_dispatch(_a, *args, **params) + p_adjusted = Fa + p*np.exp(logmass) + return self._dist._icdf_dispatch(p_adjusted, *args, **params) + + def _iccdf_dispatch(self, p, *args, lb, ub, _a, _b, logmass, **params): + cFb = self._dist._ccdf_dispatch(_b, *args, **params) + p_adjusted = cFb + p*np.exp(logmass) + return self._dist._iccdf_dispatch(p_adjusted, *args, **params) + + def __repr__(self): + with np.printoptions(threshold=10): + return (f"truncate({repr(self._dist)}, " + f"lb={repr(self.lb)}, ub={repr(self.ub)})") + + def __str__(self): + with np.printoptions(threshold=10): + return (f"truncate({str(self._dist)}, " + f"lb={str(self.lb)}, ub={str(self.ub)})") + + +@xp_capabilities(np_only=True) +def truncate(X, lb=-np.inf, ub=np.inf): + """Truncate the support of a random variable. + + Given a random variable `X`, `truncate` returns a random variable with + support truncated to the interval between `lb` and `ub`. The underlying + probability density function is normalized accordingly. + + Parameters + ---------- + X : `ContinuousDistribution` + The random variable to be truncated. + lb, ub : float array-like + The lower and upper truncation points, respectively. Must be + broadcastable with one another and the shape of `X`. + + Returns + ------- + X : `ContinuousDistribution` + The truncated random variable. + + References + ---------- + .. [1] "Truncated Distribution". *Wikipedia*. + https://en.wikipedia.org/wiki/Truncated_distribution + + Examples + -------- + Compare against `scipy.stats.truncnorm`, which truncates a standard normal, + *then* shifts and scales it. + + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from scipy import stats + >>> loc, scale, lb, ub = 1, 2, -2, 2 + >>> X = stats.truncnorm(lb, ub, loc, scale) + >>> Y = scale * stats.truncate(stats.Normal(), lb, ub) + loc + >>> x = np.linspace(-3, 5, 300) + >>> plt.plot(x, X.pdf(x), '-', label='X') + >>> plt.plot(x, Y.pdf(x), '--', label='Y') + >>> plt.xlabel('x') + >>> plt.ylabel('PDF') + >>> plt.title('Truncated, then Shifted/Scaled Normal') + >>> plt.legend() + >>> plt.show() + + However, suppose we wish to shift and scale a normal random variable, + then truncate its support to given values. This is straightforward with + `truncate`. + + >>> Z = stats.truncate(scale * stats.Normal() + loc, lb, ub) + >>> Z.plot() + >>> plt.show() + + Furthermore, `truncate` can be applied to any random variable: + + >>> Rayleigh = stats.make_distribution(stats.rayleigh) + >>> W = stats.truncate(Rayleigh(), lb=0.5, ub=3) + >>> W.plot() + >>> plt.show() + + """ + return TruncatedDistribution(X, lb=lb, ub=ub) + + +class ShiftedScaledDistribution(TransformedDistribution): + """Distribution with a standard shift/scale transformation.""" + # Unclear whether infinite loc/scale will work reasonably in all cases + _loc_domain = _RealInterval(endpoints=(-inf, inf), inclusive=(True, True)) + _loc_param = _RealParameter('loc', symbol=r'\mu', + domain=_loc_domain, typical=(1, 2)) + + _scale_domain = _RealInterval(endpoints=(-inf, inf), inclusive=(True, True)) + _scale_param = _RealParameter('scale', symbol=r'\sigma', + domain=_scale_domain, typical=(0.1, 10)) + + _parameterizations = [_Parameterization(_loc_param, _scale_param), + _Parameterization(_loc_param), + _Parameterization(_scale_param)] + + def _process_parameters(self, loc=None, scale=None, **params): + loc = loc if loc is not None else np.zeros_like(scale)[()] + scale = scale if scale is not None else np.ones_like(loc)[()] + sign = scale > 0 + parameters = self._dist._process_parameters(**params) + parameters.update(dict(loc=loc, scale=scale, sign=sign)) + return parameters + + def _transform(self, x, loc, scale, **kwargs): + return (x - loc)/scale + + def _itransform(self, x, loc, scale, **kwargs): + return x * scale + loc + + def _support(self, loc, scale, sign, **params): + # Add shortcut for infinite support? + a, b = self._dist._support(**params) + a, b = self._itransform(a, loc, scale), self._itransform(b, loc, scale) + return np.where(sign, a, b)[()], np.where(sign, b, a)[()] + + def __repr__(self): + with np.printoptions(threshold=10): + result = f"{repr(self.scale)}*{repr(self._dist)}" + if not self.loc.ndim and self.loc < 0: + result += f" - {repr(-self.loc)}" + elif (np.any(self.loc != 0) + or not np.can_cast(self.loc.dtype, self.scale.dtype)): + # We don't want to hide a zero array loc if it can cause + # a type promotion. + result += f" + {repr(self.loc)}" + return result + + def __str__(self): + with np.printoptions(threshold=10): + result = f"{str(self.scale)}*{str(self._dist)}" + if not self.loc.ndim and self.loc < 0: + result += f" - {str(-self.loc)}" + elif (np.any(self.loc != 0) + or not np.can_cast(self.loc.dtype, self.scale.dtype)): + # We don't want to hide a zero array loc if it can cause + # a type promotion. + result += f" + {str(self.loc)}" + return result + + # Here, we override all the `_dispatch` methods rather than the public + # methods or _formula methods. Why not the public methods? + # If we were to override the public methods, then other + # TransformedDistribution classes (which could transform a + # ShiftedScaledDistribution) would need to call the public methods of + # ShiftedScaledDistribution, which would run the input validation again. + # Why not the _formula methods? For distributions that rely on the + # default implementation of methods (e.g. `quadrature`, `inversion`), + # the implementation would "see" the location and scale like other + # distribution parameters, so they could affect the accuracy of the + # calculations. I think it is cleaner if `loc` and `scale` do not affect + # the underlying calculations at all. + + def _entropy_dispatch(self, *args, loc, scale, sign, **params): + return (self._dist._entropy_dispatch(*args, **params) + + np.log(np.abs(scale))) + + def _logentropy_dispatch(self, *args, loc, scale, sign, **params): + lH0 = self._dist._logentropy_dispatch(*args, **params) + lls = np.log(np.log(np.abs(scale))+0j) + return special.logsumexp(np.broadcast_arrays(lH0, lls), axis=0) + + def _median_dispatch(self, *, method, loc, scale, sign, **params): + raw = self._dist._median_dispatch(method=method, **params) + return self._itransform(raw, loc, scale) + + def _mode_dispatch(self, *, method, loc, scale, sign, **params): + raw = self._dist._mode_dispatch(method=method, **params) + return self._itransform(raw, loc, scale) + + def _logpdf_dispatch(self, x, *args, loc, scale, sign, **params): + x = self._transform(x, loc, scale) + logpdf = self._dist._logpdf_dispatch(x, *args, **params) + return logpdf - np.log(np.abs(scale)) + + def _pdf_dispatch(self, x, *args, loc, scale, sign, **params): + x = self._transform(x, loc, scale) + pdf = self._dist._pdf_dispatch(x, *args, **params) + return pdf / np.abs(scale) + + def _logpmf_dispatch(self, x, *args, loc, scale, sign, **params): + x = self._transform(x, loc, scale) + logpmf = self._dist._logpmf_dispatch(x, *args, **params) + return logpmf - np.log(np.abs(scale)) + + def _pmf_dispatch(self, x, *args, loc, scale, sign, **params): + x = self._transform(x, loc, scale) + pmf = self._dist._pmf_dispatch(x, *args, **params) + return pmf / np.abs(scale) + + def _logpxf_dispatch(self, x, *args, loc, scale, sign, **params): + x = self._transform(x, loc, scale) + logpxf = self._dist._logpxf_dispatch(x, *args, **params) + return logpxf - np.log(np.abs(scale)) + + def _pxf_dispatch(self, x, *args, loc, scale, sign, **params): + x = self._transform(x, loc, scale) + pxf = self._dist._pxf_dispatch(x, *args, **params) + return pxf / np.abs(scale) + + @_shift_scale_distribution_function + def _logcdf_dispatch(self, x, *, method=None, **params): + pass + + @_shift_scale_distribution_function + def _cdf_dispatch(self, x, *, method=None, **params): + pass + + @_shift_scale_distribution_function + def _logccdf_dispatch(self, x, *, method=None, **params): + pass + + @_shift_scale_distribution_function + def _ccdf_dispatch(self, x, *, method=None, **params): + pass + + @_shift_scale_distribution_function_2arg + def _logcdf2_dispatch(self, x, y, *, method=None, **params): + pass + + @_shift_scale_distribution_function_2arg + def _cdf2_dispatch(self, x, y, *, method=None, **params): + pass + + @_shift_scale_distribution_function_2arg + def _logccdf2_dispatch(self, x, y, *, method=None, **params): + pass + + @_shift_scale_distribution_function_2arg + def _ccdf2_dispatch(self, x, y, *, method=None, **params): + pass + + @_shift_scale_inverse_function + def _ilogcdf_dispatch(self, x, *, method=None, **params): + pass + + @_shift_scale_inverse_function + def _icdf_dispatch(self, x, *, method=None, **params): + pass + + @_shift_scale_inverse_function + def _ilogccdf_dispatch(self, x, *, method=None, **params): + pass + + @_shift_scale_inverse_function + def _iccdf_dispatch(self, x, *, method=None, **params): + pass + + def _moment_standardized_dispatch(self, order, *, loc, scale, sign, methods, + **params): + res = (self._dist._moment_standardized_dispatch( + order, methods=methods, **params)) + return None if res is None else res * np.sign(scale)**order + + def _moment_central_dispatch(self, order, *, loc, scale, sign, methods, + **params): + res = (self._dist._moment_central_dispatch( + order, methods=methods, **params)) + return None if res is None else res * scale**order + + def _moment_raw_dispatch(self, order, *, loc, scale, sign, methods, + **params): + raw_moments = [] + methods_highest_order = methods + for i in range(int(order) + 1): + methods = (self._moment_methods if i < order + else methods_highest_order) + raw = self._dist._moment_raw_dispatch(i, methods=methods, **params) + if raw is None: + return None + moment_i = raw * scale**i + raw_moments.append(moment_i) + + return self._moment_transform_center( + order, raw_moments, loc, self._zero) + + def _lmoment_dispatch(self, order, *, loc, scale, sign, methods, **params): + res = self._dist._lmoment_dispatch(order, methods=methods, **params) + if res is None: # if a specific method is requested but not available + return None + res = res * np.abs(scale) * np.sign(scale)**order + return res + loc if order == 1 else res + + def _sample_dispatch(self, full_shape, *, + rng, loc, scale, sign, method, **params): + rvs = self._dist._sample_dispatch(full_shape, method=method, rng=rng, **params) + return self._itransform(rvs, loc=loc, scale=scale, sign=sign, **params) + + def __add__(self, loc): + return ShiftedScaledDistribution(self._dist, loc=self.loc + loc, + scale=self.scale) + + def __sub__(self, loc): + return ShiftedScaledDistribution(self._dist, loc=self.loc - loc, + scale=self.scale) + + def __mul__(self, scale): + return ShiftedScaledDistribution(self._dist, + loc=self.loc * scale, + scale=self.scale * scale) + + def __truediv__(self, scale): + return ShiftedScaledDistribution(self._dist, + loc=self.loc / scale, + scale=self.scale / scale) + + +class OrderStatisticDistribution(TransformedDistribution): + r"""Probability distribution of an order statistic. + + An instance of this class represents a random variable that follows the + distribution of the :math:`r^{\text{th}}` order statistic of a + sample of :math:`n` observations of a random variable :math:`X`. + + Parameters + ---------- + dist : `ContinuousDistribution` + The random variable :math:`X`. + n : array_like + The (positive integer) sample size :math:`n`. + r : array_like + The (positive integer) rank of the order statistic :math:`r`, + satisfying :math:`1 \leq r \leq n`. + + + Notes + ----- + If we make :math:`n` observations of a continuous random variable + :math:`X` and sort them in increasing order + :math:`X_{(1)}, \dots, X_{(r)}, \dots, X_{(n)}`, then :math:`X_{(r)}` + is known as the :math:`r^{\text{th}}` order statistic. + + If the PDF, CDF, and CCDF of :math:`X` are denoted by :math:`f`, + :math:`F`, and :math:`G = 1 - F`, respectively, then the PDF of + :math:`X_{(r)}` is given by: + + .. math:: + + f_r(x) = \frac{n!}{(r-1)! (n-r)!} f(x) F(x)^{r-1} G(x)^{n - r} + + The CDF and other methods of the distribution of :math:`X_{(r)}` + are calculated using the fact that :math:`X = F^{-1}(U)`, where :math:`U` is a + standard uniform random variable, together with the fact that the order statistics + of i.i.d. uniform random variables follow a beta distribution + :math:`B(r, n - r + 1)`. + + References + ---------- + .. [1] Order statistic. *Wikipedia*. https://en.wikipedia.org/wiki/Order_statistic + + Examples + -------- + Suppose we are interested in order statistics of samples of size five drawn + from the standard normal distribution. Plot the PDF of the fourth + order statistic and compare with a normalized histogram from simulation. + + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from scipy import stats + >>> from scipy.stats._distribution_infrastructure import OrderStatisticDistribution + >>> + >>> X = stats.Normal() + >>> data = X.sample(shape=(10000, 5)) + >>> sorted_data = np.sort(data, axis=1) + >>> Y = OrderStatisticDistribution(X, r=4, n=5) + >>> + >>> ax = plt.gca() + >>> Y.plot(ax=ax) + >>> ax.hist(sorted_data[:, 3], density=True, bins=30) + >>> plt.show() + + """ + + _r_domain = _IntegerInterval(endpoints=(1, 'n'), inclusive=(True, True)) + _r_param = _RealParameter('r', domain=_r_domain, typical=(1, 2)) + + _n_domain = _IntegerInterval(endpoints=(1, np.inf), inclusive=(True, True)) + _n_param = _RealParameter('n', domain=_n_domain, typical=(1, 4)) + + _r_domain.define_parameters(_n_param) + + _parameterizations = [_Parameterization(_r_param, _n_param)] + + def __init__(self, dist, /, *args, r, n, **kwargs): + super().__init__(dist, *args, r=r, n=n, **kwargs) + + def _support(self, *args, r, n, **kwargs): + return self._dist._support(*args, **kwargs) + + def _process_parameters(self, r=None, n=None, **params): + parameters = self._dist._process_parameters(**params) + parameters.update(dict(r=r, n=n)) + return parameters + + def _overrides(self, method_name): + return method_name in {'_logpdf_formula', '_pdf_formula', + '_cdf_formula', '_ccdf_formula', + '_icdf_formula', '_iccdf_formula'} + + def _logpdf_formula(self, x, r, n, **kwargs): + log_factor = special.betaln(r, n - r + 1) + log_fX = self._dist._logpdf_dispatch(x, **kwargs) + # log-methods sometimes use complex dtype with 0 imaginary component, + # but `_tanhsinh` doesn't accept complex limits of integration; take `real`. + log_FX = self._dist._logcdf_dispatch(x.real, **kwargs) + log_cFX = self._dist._logccdf_dispatch(x.real, **kwargs) + # This can be problematic when (r - 1)|(n-r) = 0 and `log_FX`|log_cFX = -inf + # The PDF in these cases is 0^0, so these should be replaced with log(1)=0 + # return log_fX + (r-1)*log_FX + (n-r)*log_cFX - log_factor + rm1_log_FX = np.where((r - 1 == 0) & np.isneginf(log_FX), 0, (r-1)*log_FX) + nmr_log_cFX = np.where((n - r == 0) & np.isneginf(log_cFX), 0, (n-r)*log_cFX) + return log_fX + rm1_log_FX + nmr_log_cFX - log_factor + + def _pdf_formula(self, x, r, n, **kwargs): + # 1 / factor = factorial(n) / (factorial(r-1) * factorial(n-r)) + factor = special.beta(r, n - r + 1) + fX = self._dist._pdf_dispatch(x, **kwargs) + FX = self._dist._cdf_dispatch(x, **kwargs) + cFX = self._dist._ccdf_dispatch(x, **kwargs) + return fX * FX**(r-1) * cFX**(n-r) / factor + + def _cdf_formula(self, x, r, n, **kwargs): + x_ = self._dist._cdf_dispatch(x, **kwargs) + return special.betainc(r, n-r+1, x_) + + def _ccdf_formula(self, x, r, n, **kwargs): + x_ = self._dist._cdf_dispatch(x, **kwargs) + return special.betaincc(r, n-r+1, x_) + + def _icdf_formula(self, p, r, n, **kwargs): + p_ = special.betaincinv(r, n-r+1, p) + return self._dist._icdf_dispatch(p_, **kwargs) + + def _iccdf_formula(self, p, r, n, **kwargs): + p_ = special.betainccinv(r, n-r+1, p) + return self._dist._icdf_dispatch(p_, **kwargs) + + def __repr__(self): + with np.printoptions(threshold=10): + return (f"order_statistic({repr(self._dist)}, r={repr(self.r)}, " + f"n={repr(self.n)})") + + def __str__(self): + with np.printoptions(threshold=10): + return (f"order_statistic({str(self._dist)}, r={str(self.r)}, " + f"n={str(self.n)})") + + +@xp_capabilities(np_only=True) +def order_statistic(X, /, *, r, n): + r"""Probability distribution of an order statistic. + + Returns a random variable that follows the distribution of the + :math:`r^{\text{th}}` order statistic of a sample of :math:`n` + observations of a random variable :math:`X`. + + Parameters + ---------- + X : `ContinuousDistribution` + The random variable :math:`X`. + r : array_like + The (positive integer) rank of the order statistic :math:`r`, + satisfying ``1 <= r <= n``. + n : array_like + The (positive integer) sample size :math:`n`. + + Returns + ------- + Y : `ContinuousDistribution` + A random variable that follows the distribution of the prescribed + order statistic. + + Notes + ----- + If we make :math:`n` observations of a continuous random variable + :math:`X` and sort them in increasing order + :math:`X_{(1)}, \dots, X_{(r)}, \dots, X_{(n)}`, then :math:`X_{(r)}` + is known as the :math:`r^{\text{th}}` order statistic. + + If the PDF, CDF, and CCDF of :math:`X` are denoted by :math:`f`, + :math:`F`, and :math:`G = 1 - F`, respectively, then the PDF of + :math:`X_{(r)}` is given by: + + .. math:: + + f_r(x) = \frac{n!}{(r-1)! (n-r)!} f(x) F(x)^{r-1} G(x)^{n - r} + + The CDF and other methods of the distribution of :math:`X_{(r)}` + are calculated using the fact that :math:`X = F^{-1}(U)`, where :math:`U` is a + standard uniform random variable, together with the fact that the order statistics + of i.i.d. uniform random variables follow a beta distribution + :math:`B(r, n - r + 1)`. + + References + ---------- + .. [1] Order statistic. *Wikipedia*. https://en.wikipedia.org/wiki/Order_statistic + + Examples + -------- + Suppose we are interested in order statistics of samples of size five drawn + from the standard normal distribution. Plot the PDF of each + order statistic and compare with a normalized histogram from simulation. + + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from scipy import stats + >>> + >>> X = stats.Normal() + >>> data = X.sample(shape=(10000, 5)) + >>> sorted_data = np.sort(data, axis=1) + >>> Y = stats.order_statistic(X, r=[1, 2, 3, 4, 5], n=5) + >>> + >>> ax = plt.gca() + >>> colors = plt.rcParams['axes.prop_cycle'].by_key()['color'] + >>> for i in range(5): + ... y = sorted_data[:, i] + ... ax.hist(y, density=True, bins=30, alpha=0.1, color=colors[i]) + >>> Y.plot(ax=ax) + >>> plt.show() + + """ + r, n = np.asarray(r), np.asarray(n) + if np.any((r != np.floor(r)) | (r < 0)) or np.any((n != np.floor(n)) | (n < 0)): + message = "`r` and `n` must contain only positive integers." + raise ValueError(message) + return OrderStatisticDistribution(X, r=r, n=n) + + +class Mixture(_ProbabilityDistribution): + r"""Representation of a mixture distribution. + + A mixture distribution is the distribution of a random variable + defined in the following way: first, a random variable is selected + from `components` according to the probabilities given by `weights`, then + the selected random variable is realized. + + Parameters + ---------- + components : sequence of `ContinuousDistribution` + The underlying instances of `ContinuousDistribution`. + All must have scalar shape parameters (if any); e.g., the `pdf` evaluated + at a scalar argument must return a scalar. + weights : sequence of floats, optional + The corresponding probabilities of selecting each random variable. + Must be non-negative and sum to one. The default behavior is to weight + all components equally. + + Attributes + ---------- + components : sequence of `ContinuousDistribution` + The underlying instances of `ContinuousDistribution`. + weights : ndarray + The corresponding probabilities of selecting each random variable. + + Methods + ------- + support + + sample + + moment + + mean + median + mode + + variance + standard_deviation + + skewness + kurtosis + + pdf + logpdf + + cdf + icdf + ccdf + iccdf + + logcdf + ilogcdf + logccdf + ilogccdf + + entropy + + Notes + ----- + The following abbreviations are used throughout the documentation. + + - PDF: probability density function + - CDF: cumulative distribution function + - CCDF: complementary CDF + - entropy: differential entropy + - log-*F*: logarithm of *F* (e.g. log-CDF) + - inverse *F*: inverse function of *F* (e.g. inverse CDF) + + References + ---------- + .. [1] Mixture distribution, *Wikipedia*, + https://en.wikipedia.org/wiki/Mixture_distribution + + + Examples + -------- + A mixture of normal distributions: + + >>> import numpy as np + >>> from scipy import stats + >>> import matplotlib.pyplot as plt + >>> X1 = stats.Normal(mu=-2, sigma=1) + >>> X2 = stats.Normal(mu=2, sigma=1) + >>> mixture = stats.Mixture([X1, X2], weights=[0.4, 0.6]) + >>> print(f'mean: {mixture.mean():.2f}, ' + ... f'median: {mixture.median():.2f}, ' + ... f'mode: {mixture.mode():.2f}') + mean: 0.40, median: 1.04, mode: 2.00 + >>> x = np.linspace(-10, 10, 300) + >>> plt.plot(x, mixture.pdf(x)) + >>> plt.title('PDF of normal distribution mixture') + >>> plt.show() + + """ + # Todo: + # Add support for array shapes, weights + + def _input_validation(self, components, weights): + if len(components) == 0: + message = ("`components` must contain at least one random variable.") + raise ValueError(message) + + for var in components: + # will generalize to other kinds of distributions when there + # *are* other kinds of distributions + if not isinstance(var, ContinuousDistribution): + message = ("Each element of `components` must be an instance of " + "`ContinuousDistribution`.") + raise ValueError(message) + if not var._shape == (): + message = "All elements of `components` must have scalar shapes." + raise ValueError(message) + + if weights is None: + return components, weights + + weights = np.asarray(weights) + if weights.shape != (len(components),): + message = "`components` and `weights` must have the same length." + raise ValueError(message) + + if not np.issubdtype(weights.dtype, np.inexact): + message = "`weights` must have floating point dtype." + raise ValueError(message) + + if not np.isclose(np.sum(weights), 1.0): + message = "`weights` must sum to 1.0." + raise ValueError(message) + + if not np.all(weights >= 0): + message = "All `weights` must be non-negative." + raise ValueError(message) + + return components, weights + + def __init__(self, components, *, weights=None): + components, weights = self._input_validation(components, weights) + n = len(components) + dtype = np.result_type(*(var._dtype for var in components)) + self._shape = np.broadcast_shapes(*(var._shape for var in components)) + self._dtype, self._components = dtype, components + self._weights = np.full(n, 1/n, dtype=dtype) if weights is None else weights + self.validation_policy = None + + @property + def components(self): + return list(self._components) + + @property + def weights(self): + return self._weights.copy() + + def _full(self, val, *args): + args = [np.asarray(arg) for arg in args] + dtype = np.result_type(self._dtype, *(arg.dtype for arg in args)) + shape = np.broadcast_shapes(self._shape, *(arg.shape for arg in args)) + return np.full(shape, val, dtype=dtype) + + def _sum(self, fun, *args): + out = self._full(0, *args) + for var, weight in zip(self._components, self._weights): + out += getattr(var, fun)(*args) * weight + return out[()] + + def _logsum(self, fun, *args): + out = self._full(-np.inf, *args) + for var, log_weight in zip(self._components, np.log(self._weights)): + np.logaddexp(out, getattr(var, fun)(*args) + log_weight, out=out) + return out[()] + + def support(self): + a = self._full(np.inf) + b = self._full(-np.inf) + for var in self._components: + a = np.minimum(a, var.support()[0]) + b = np.maximum(b, var.support()[1]) + return a, b + + def _raise_if_method(self, method): + if method is not None: + raise NotImplementedError("`method` not implemented for this distribution.") + + def logentropy(self, *, method=None): + self._raise_if_method(method) + def log_integrand(x): + # `x` passed by `_tanhsinh` will be of complex dtype because + # `log_integrand` returns complex values, but the imaginary + # component is always zero. Extract the real part because + # `logpdf` uses `logaddexp`, which fails for complex input. + return self.logpdf(x.real) + np.log(self.logpdf(x.real) + 0j) + + res = _tanhsinh(log_integrand, *self.support(), log=True).integral + return _log_real_standardize(res + np.pi*1j) + + def entropy(self, *, method=None): + self._raise_if_method(method) + return _tanhsinh(lambda x: -self.pdf(x) * self.logpdf(x), + *self.support()).integral + + def mode(self, *, method=None): + self._raise_if_method(method) + a, b = self.support() + def f(x): return -self.pdf(x) + res = elementwise.bracket_minimum(f, 1., xmin=a, xmax=b) + res = elementwise.find_minimum(f, res.bracket) + return res.x + + def median(self, *, method=None): + self._raise_if_method(method) + return self.icdf(0.5) + + def mean(self, *, method=None): + self._raise_if_method(method) + return self._sum('mean') + + def variance(self, *, method=None): + self._raise_if_method(method) + return self._moment_central(2) + + def standard_deviation(self, *, method=None): + self._raise_if_method(method) + return self.variance()**0.5 + + def skewness(self, *, method=None): + self._raise_if_method(method) + return self._moment_standardized(3) + + def kurtosis(self, *, method=None): + self._raise_if_method(method) + return self._moment_standardized(4) + + def moment(self, order=1, kind='raw', *, method=None): + self._raise_if_method(method) + kinds = {'raw': self._moment_raw, + 'central': self._moment_central, + 'standardized': self._moment_standardized} + order = ContinuousDistribution._validate_order(self, order) + kind = ContinuousDistribution._validate_kind(self, kind, kinds) + moment_kind = kinds[kind] + return moment_kind(order) + + def _moment_raw(self, order): + out = self._full(0) + for var, weight in zip(self._components, self._weights): + out += var.moment(order, kind='raw') * weight + return out[()] + + def _moment_central(self, order): + order = int(order) + out = self._full(0) + for var, weight in zip(self._components, self._weights): + moment_as = [var.moment(order, kind='central') + for order in range(order + 1)] + a, b = var.mean(), self.mean() + moment = var._moment_transform_center(order, moment_as, a, b) + out += moment * weight + return out[()] + + def lmoment(self, order=1, *, standardize=True, method=None): + message = "L-moments are not currently available for mixture distributions." + raise NotImplementedError(message) + + def _moment_standardized(self, order): + return self._moment_central(order) / self.standard_deviation()**order + + def pdf(self, x, /, *, method=None): + self._raise_if_method(method) + return self._sum('pdf', x) + + def logpdf(self, x, /, *, method=None): + self._raise_if_method(method) + return self._logsum('logpdf', x) + + def pmf(self, x, /, *, method=None): + self._raise_if_method(method) + return self._sum('pmf', x) + + def logpmf(self, x, /, *, method=None): + self._raise_if_method(method) + return self._logsum('logpmf', x) + + def cdf(self, x, y=None, /, *, method=None): + self._raise_if_method(method) + args = (x,) if y is None else (x, y) + return self._sum('cdf', *args) + + def logcdf(self, x, y=None, /, *, method=None): + self._raise_if_method(method) + args = (x,) if y is None else (x, y) + return self._logsum('logcdf', *args) + + def ccdf(self, x, y=None, /, *, method=None): + self._raise_if_method(method) + args = (x,) if y is None else (x, y) + return self._sum('ccdf', *args) + + def logccdf(self, x, y=None, /, *, method=None): + self._raise_if_method(method) + args = (x,) if y is None else (x, y) + return self._logsum('logccdf', *args) + + def _invert(self, fun, p): + xmin, xmax = self.support() + fun = getattr(self, fun) + f = lambda x, p: fun(x) - p # noqa: E731 is silly + xl0, xr0 = _guess_bracket(xmin, xmax) + res = elementwise.bracket_root(f, xl0=xl0, xr0=xr0, + xmin=xmin, xmax=xmax, args=(p,)) + return elementwise.find_root(f, res.bracket, args=(p,)).x + + def icdf(self, p, /, *, method=None): + self._raise_if_method(method) + return self._invert('cdf', p) + + def iccdf(self, p, /, *, method=None): + self._raise_if_method(method) + return self._invert('ccdf', p) + + def ilogcdf(self, p, /, *, method=None): + self._raise_if_method(method) + return self._invert('logcdf', p) + + def ilogccdf(self, p, /, *, method=None): + self._raise_if_method(method) + return self._invert('logccdf', p) + + def sample(self, shape=(), *, rng=None, method=None): + self._raise_if_method(method) + rng = np.random.default_rng(rng) + size = np.prod(np.atleast_1d(shape)) + ns = rng.multinomial(size, self._weights) + x = [var.sample(shape=n, rng=rng) for n, var in zip(ns, self._components)] + x = np.reshape(rng.permuted(np.concatenate(x)), shape) + return x[()] + + def __repr__(self): + result = "Mixture(\n" + result += " [\n" + with np.printoptions(threshold=10): + for component in self.components: + result += f" {repr(component)},\n" + result += " ],\n" + result += f" weights={repr(self.weights)},\n" + result += ")" + return result + + def __str__(self): + result = "Mixture(\n" + result += " [\n" + with np.printoptions(threshold=10): + for component in self.components: + result += f" {str(component)},\n" + result += " ],\n" + result += f" weights={str(self.weights)},\n" + result += ")" + return result + + +class MonotonicTransformedDistribution(TransformedDistribution): + r"""Distribution underlying a strictly monotonic function of a random variable + + Given a random variable :math:`X`; a strictly monotonic function + :math:`g(u)`, its inverse :math:`h(u) = g^{-1}(u)`, and the derivative magnitude + :math: `|h'(u)| = \left| \frac{dh(u)}{du} \right|`, define the distribution + underlying the random variable :math:`Y = g(X)`. + + Parameters + ---------- + X : `ContinuousDistribution` + The random variable :math:`X`. + g, h, dh : callable + Elementwise functions representing the mathematical functions + :math:`g(u)`, :math:`h(u)`, and :math:`|h'(u)|` + logdh : callable, optional + Elementwise function representing :math:`\log(h'(u))`. + The default is ``lambda u: np.log(dh(u))``, but providing + a custom implementation may avoid over/underflow. + increasing : bool, optional + Whether the function is strictly increasing (True, default) + or strictly decreasing (False). + repr_pattern : str, optional + A string pattern for determining the __repr__. The __repr__ + for X will be substituted into the position where `***` appears. + For example: + ``"exp(***)"`` for the repr of an exponentially transformed + distribution + The default is ``f"{g.__name__}(***)"``. + str_pattern : str, optional + A string pattern for determining `__str__`. The `__str__` + for X will be substituted into the position where `***` appears. + For example: + ``"exp(***)"`` for the repr of an exponentially transformed + distribution + The default is the value `repr_pattern` takes. + """ + + def __init__(self, X, /, *args, g, h, dh, logdh=None, + increasing=True, repr_pattern=None, + str_pattern=None, **kwargs): + super().__init__(X, *args, **kwargs) + self._g = g + self._h = h + self._dh = dh + self._logdh = (logdh if logdh is not None + else lambda u: np.log(dh(u))) + if increasing: + self._xdf = self._dist._cdf_dispatch + self._cxdf = self._dist._ccdf_dispatch + self._ixdf = self._dist._icdf_dispatch + self._icxdf = self._dist._iccdf_dispatch + self._logxdf = self._dist._logcdf_dispatch + self._logcxdf = self._dist._logccdf_dispatch + self._ilogxdf = self._dist._ilogcdf_dispatch + self._ilogcxdf = self._dist._ilogccdf_dispatch + else: + self._xdf = self._dist._ccdf_dispatch + self._cxdf = self._dist._cdf_dispatch + self._ixdf = self._dist._iccdf_dispatch + self._icxdf = self._dist._icdf_dispatch + self._logxdf = self._dist._logccdf_dispatch + self._logcxdf = self._dist._logcdf_dispatch + self._ilogxdf = self._dist._ilogccdf_dispatch + self._ilogcxdf = self._dist._ilogcdf_dispatch + self._increasing = increasing + self._repr_pattern = repr_pattern or f"{g.__name__}(***)" + self._str_pattern = str_pattern or self._repr_pattern + + def __repr__(self): + with np.printoptions(threshold=10): + return self._repr_pattern.replace("***", repr(self._dist)) + + def __str__(self): + with np.printoptions(threshold=10): + return self._str_pattern.replace("***", str(self._dist)) + + def _overrides(self, method_name): + # Do not use the generic overrides of TransformedDistribution + return False + + def _support(self, **params): + a, b = self._dist._support(**params) + # For reciprocal transformation, we want this zero to become -inf + b = np.where(b==0, np.asarray("-0", dtype=b.dtype), b) + with np.errstate(divide='ignore'): + if self._increasing: + return self._g(a), self._g(b) + else: + return self._g(b), self._g(a) + + def _logpdf_dispatch(self, x, *args, **params): + return self._dist._logpdf_dispatch(self._h(x), *args, **params) + self._logdh(x) + + def _pdf_dispatch(self, x, *args, **params): + return self._dist._pdf_dispatch(self._h(x), *args, **params) * self._dh(x) + + def _logcdf_dispatch(self, x, *args, **params): + return self._logxdf(self._h(x), *args, **params) + + def _cdf_dispatch(self, x, *args, **params): + return self._xdf(self._h(x), *args, **params) + + def _logccdf_dispatch(self, x, *args, **params): + return self._logcxdf(self._h(x), *args, **params) + + def _ccdf_dispatch(self, x, *args, **params): + return self._cxdf(self._h(x), *args, **params) + + def _ilogcdf_dispatch(self, p, *args, **params): + return self._g(self._ilogxdf(p, *args, **params)) + + def _icdf_dispatch(self, p, *args, **params): + return self._g(self._ixdf(p, *args, **params)) + + def _ilogccdf_dispatch(self, p, *args, **params): + return self._g(self._ilogcxdf(p, *args, **params)) + + def _iccdf_dispatch(self, p, *args, **params): + return self._g(self._icxdf(p, *args, **params)) + + def _sample_dispatch(self, full_shape, *, method, rng, **params): + rvs = self._dist._sample_dispatch(full_shape, method=method, rng=rng, **params) + return self._g(rvs) + + +class FoldedDistribution(TransformedDistribution): + r"""Distribution underlying the absolute value of a random variable + + Given a random variable :math:`X`; define the distribution + underlying the random variable :math:`Y = |X|`. + + Parameters + ---------- + X : `ContinuousDistribution` + The random variable :math:`X`. + + Returns + ------- + Y : `ContinuousDistribution` + The random variable :math:`Y = |X|` + + """ + # Many enhancements are possible if distribution is symmetric. Start + # with the general case; enhance later. + + def __init__(self, X, /, *args, **kwargs): + super().__init__(X, *args, **kwargs) + # I think we need to allow `_support` to define whether the endpoints + # are inclusive or not. In the meantime, it's best to ensure that the lower + # endpoint (typically 0 for folded distribution) is inclusive so PDF evaluates + # correctly at that point. + self._variable.domain.inclusive = (True, self._variable.domain.inclusive[1]) + + def _overrides(self, method_name): + # Do not use the generic overrides of TransformedDistribution + return False + + def _support(self, **params): + a, b = self._dist._support(**params) + a_, b_ = np.abs(a), np.abs(b) + a_, b_ = np.minimum(a_, b_), np.maximum(a_, b_) + i = (a < 0) & (b > 0) + a_ = np.asarray(a_) + a_[i] = 0 + return a_[()], b_[()] + + def _logpdf_dispatch(self, x, *args, method=None, **params): + x = np.abs(x) + right = self._dist._logpdf_dispatch(x, *args, method=method, **params) + left = self._dist._logpdf_dispatch(-x, *args, method=method, **params) + left = np.asarray(left) + right = np.asarray(right) + a, b = self._dist._support(**params) + left[-x < a] = -np.inf + right[x > b] = -np.inf + logpdfs = np.stack([left, right]) + return special.logsumexp(logpdfs, axis=0) + + def _pdf_dispatch(self, x, *args, method=None, **params): + x = np.abs(x) + right = self._dist._pdf_dispatch(x, *args, method=method, **params) + left = self._dist._pdf_dispatch(-x, *args, method=method, **params) + left = np.asarray(left) + right = np.asarray(right) + a, b = self._dist._support(**params) + left[-x < a] = 0 + right[x > b] = 0 + return left + right + + def _logcdf_dispatch(self, x, *args, method=None, **params): + x = np.abs(x) + a, b = self._dist._support(**params) + xl = np.maximum(-x, a) + xr = np.minimum(x, b) + return self._dist._logcdf2_dispatch(xl, xr, *args, method=method, **params).real + + def _cdf_dispatch(self, x, *args, method=None, **params): + x = np.abs(x) + a, b = self._dist._support(**params) + xl = np.maximum(-x, a) + xr = np.minimum(x, b) + return self._dist._cdf2_dispatch(xl, xr, *args, **params) + + def _logccdf_dispatch(self, x, *args, method=None, **params): + x = np.abs(x) + a, b = self._dist._support(**params) + xl = np.maximum(-x, a) + xr = np.minimum(x, b) + return self._dist._logccdf2_dispatch(xl, xr, *args, method=method, + **params).real + + def _ccdf_dispatch(self, x, *args, method=None, **params): + x = np.abs(x) + a, b = self._dist._support(**params) + xl = np.maximum(-x, a) + xr = np.minimum(x, b) + return self._dist._ccdf2_dispatch(xl, xr, *args, method=method, **params) + + def _sample_dispatch(self, full_shape, *, method, rng, **params): + rvs = self._dist._sample_dispatch(full_shape, method=method, rng=rng, **params) + return np.abs(rvs) + + def __repr__(self): + with np.printoptions(threshold=10): + return f"abs({repr(self._dist)})" + + def __str__(self): + with np.printoptions(threshold=10): + return f"abs({str(self._dist)})" + + +@xp_capabilities(np_only=True) +def abs(X, /): + r"""Absolute value of a random variable. + + Parameters + ---------- + X : `ContinuousDistribution` + The random variable :math:`X`. + + Returns + ------- + Y : `ContinuousDistribution` + A random variable :math:`Y = |X|`. + + Examples + -------- + Suppose we have a normally distributed random variable :math:`X`: + + >>> import numpy as np + >>> from scipy import stats + >>> X = stats.Normal() + + We wish to have a random variable :math:`Y` distributed according to + the folded normal distribution; that is, a random variable :math:`|X|`. + + >>> Y = stats.abs(X) + + The PDF of the distribution in the left half plane is "folded" over to + the right half plane. Because the normal PDF is symmetric, the resulting + PDF is zero for negative arguments and doubled for positive arguments. + + >>> import matplotlib.pyplot as plt + >>> x = np.linspace(0, 5, 300) + >>> ax = plt.gca() + >>> Y.plot(x='x', y='pdf', t=('x', -1, 5), ax=ax) + >>> plt.plot(x, 2 * X.pdf(x), '--') + >>> plt.legend(('PDF of `Y`', 'Doubled PDF of `X`')) + >>> plt.show() + + """ + return FoldedDistribution(X) + + +@xp_capabilities(np_only=True) +def exp(X, /): + r"""Natural exponential of a random variable. + + Parameters + ---------- + X : `ContinuousDistribution` + The random variable :math:`X`. + + Returns + ------- + Y : `ContinuousDistribution` + A random variable :math:`Y = \exp(X)`. + + Examples + -------- + Suppose we have a normally distributed random variable :math:`X`: + + >>> import numpy as np + >>> from scipy import stats + >>> X = stats.Normal() + + We wish to have a lognormally distributed random variable :math:`Y`, + a random variable whose natural logarithm is :math:`X`. + If :math:`X` is to be the natural logarithm of :math:`Y`, then we + must take :math:`Y` to be the natural exponential of :math:`X`. + + >>> Y = stats.exp(X) + + To demonstrate that ``X`` represents the logarithm of ``Y``, + we plot a normalized histogram of the logarithm of observations of + ``Y`` against the PDF underlying ``X``. + + >>> import matplotlib.pyplot as plt + >>> rng = np.random.default_rng(435383595582522) + >>> y = Y.sample(shape=10000, rng=rng) + >>> ax = plt.gca() + >>> ax.hist(np.log(y), bins=50, density=True) + >>> X.plot(ax=ax) + >>> plt.legend(('PDF of `X`', 'histogram of `log(y)`')) + >>> plt.show() + + """ + return MonotonicTransformedDistribution(X, g=np.exp, h=np.log, dh=lambda u: 1 / u, + logdh=lambda u: -np.log(u)) + + +@xp_capabilities(np_only=True) +def log(X, /): + r"""Natural logarithm of a non-negative random variable. + + Parameters + ---------- + X : `ContinuousDistribution` + The random variable :math:`X` with positive support. + + Returns + ------- + Y : `ContinuousDistribution` + A random variable :math:`Y = \log(X)`. + + Examples + -------- + Suppose we have a gamma distributed random variable :math:`X`: + + >>> import numpy as np + >>> from scipy import stats + >>> Gamma = stats.make_distribution(stats.gamma) + >>> X = Gamma(a=1.0) + + We wish to have an exp-gamma distributed random variable :math:`Y`, + a random variable whose natural exponential is :math:`X`. + If :math:`X` is to be the natural exponential of :math:`Y`, then we + must take :math:`Y` to be the natural logarithm of :math:`X`. + + >>> Y = stats.log(X) + + To demonstrate that ``X`` represents the exponential of ``Y``, + we plot a normalized histogram of the exponential of observations of + ``Y`` against the PDF underlying ``X``. + + >>> import matplotlib.pyplot as plt + >>> rng = np.random.default_rng(435383595582522) + >>> y = Y.sample(shape=10000, rng=rng) + >>> ax = plt.gca() + >>> ax.hist(np.exp(y), bins=50, density=True) + >>> X.plot(ax=ax) + >>> plt.legend(('PDF of `X`', 'histogram of `exp(y)`')) + >>> plt.show() + + """ + if np.any(X.support()[0] < 0): + message = ("The logarithm of a random variable is only implemented when the " + "support is non-negative.") + raise NotImplementedError(message) + return MonotonicTransformedDistribution(X, g=np.log, h=np.exp, dh=np.exp, + logdh=lambda u: u) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_entropy.py b/venv/lib/python3.12/site-packages/scipy/stats/_entropy.py new file mode 100644 index 0000000000000000000000000000000000000000..ab5a874cf4aeaa0bb12e63292cd3f5122658f509 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_entropy.py @@ -0,0 +1,431 @@ +""" +Created on Fri Apr 2 09:06:05 2021 + +@author: matth +""" + +import math +import numpy as np +from scipy import special +from ._axis_nan_policy import _axis_nan_policy_factory +from scipy._lib._array_api import (array_namespace, xp_promote, xp_device, + _masked_apply, _share_masks, xp_capabilities) + +__all__ = ['entropy', 'differential_entropy'] + + +@xp_capabilities(marray=True) +@_axis_nan_policy_factory( + lambda x: x, + n_samples=lambda kwgs: ( + 2 if ("qk" in kwgs and kwgs["qk"] is not None) + else 1 + ), + n_outputs=1, result_to_tuple=lambda x, _: (x,), paired=True, + too_small=-1 # entropy doesn't have too small inputs +) +def entropy(pk: np.typing.ArrayLike, + qk: np.typing.ArrayLike | None = None, + base: float | None = None, + axis: int = 0 + ) -> np.number | np.ndarray: + """ + Calculate the Shannon entropy/relative entropy of given distribution(s). + + If only probabilities `pk` are given, the Shannon entropy is calculated as + ``H = -sum(pk * log(pk))``. + + If `qk` is not None, then compute the relative entropy + ``D = sum(pk * log(pk / qk))``. This quantity is also known + as the Kullback-Leibler divergence. + + This routine will normalize `pk` and `qk` if they don't sum to 1. + + Parameters + ---------- + pk : array_like + Defines the (discrete) distribution. Along each axis-slice of ``pk``, + element ``i`` is the (possibly unnormalized) probability of event + ``i``. + qk : array_like, optional + Sequence against which the relative entropy is computed. Should be in + the same format as `pk`. + base : float, optional + The logarithmic base to use, defaults to ``e`` (natural logarithm). + axis : int, optional + The axis along which the entropy is calculated. Default is 0. + + Returns + ------- + S : {float, array_like} + The calculated entropy. + + Notes + ----- + Informally, the Shannon entropy quantifies the expected uncertainty + inherent in the possible outcomes of a discrete random variable. + For example, + if messages consisting of sequences of symbols from a set are to be + encoded and transmitted over a noiseless channel, then the Shannon entropy + ``H(pk)`` gives a tight lower bound for the average number of units of + information needed per symbol if the symbols occur with frequencies + governed by the discrete distribution `pk` [1]_. The choice of base + determines the choice of units; e.g., ``e`` for nats, ``2`` for bits, etc. + + The relative entropy, ``D(pk|qk)``, quantifies the increase in the average + number of units of information needed per symbol if the encoding is + optimized for the probability distribution `qk` instead of the true + distribution `pk`. Informally, the relative entropy quantifies the expected + excess in surprise experienced if one believes the true distribution is + `qk` when it is actually `pk`. + + A related quantity, the cross entropy ``CE(pk, qk)``, satisfies the + equation ``CE(pk, qk) = H(pk) + D(pk|qk)`` and can also be calculated with + the formula ``CE = -sum(pk * log(qk))``. It gives the average + number of units of information needed per symbol if an encoding is + optimized for the probability distribution `qk` when the true distribution + is `pk`. It is not computed directly by `entropy`, but it can be computed + using two calls to the function (see Examples). + + See [2]_ for more information. + + References + ---------- + .. [1] Shannon, C.E. (1948), A Mathematical Theory of Communication. + Bell System Technical Journal, 27: 379-423. + :doi:`10.1002/j.1538-7305.1948.tb01338.x`. + .. [2] Thomas M. Cover and Joy A. Thomas. 2006. Elements of Information + Theory (Wiley Series in Telecommunications and Signal Processing). + Wiley-Interscience, USA. + + + Examples + -------- + The outcome of a fair coin is the most uncertain: + + >>> import numpy as np + >>> from scipy.stats import entropy + >>> base = 2 # work in units of bits + >>> pk = np.array([1/2, 1/2]) # fair coin + >>> H = entropy(pk, base=base) + >>> H + 1.0 + >>> H == -np.sum(pk * np.log(pk)) / np.log(base) + True + + The outcome of a biased coin is less uncertain: + + >>> qk = np.array([9/10, 1/10]) # biased coin + >>> entropy(qk, base=base) + 0.46899559358928117 + + The relative entropy between the fair coin and biased coin is calculated + as: + + >>> D = entropy(pk, qk, base=base) + >>> D + 0.7369655941662062 + >>> np.isclose(D, np.sum(pk * np.log(pk/qk)) / np.log(base), rtol=4e-16, atol=0) + True + + The cross entropy can be calculated as the sum of the entropy and + relative entropy`: + + >>> CE = entropy(pk, base=base) + entropy(pk, qk, base=base) + >>> CE + 1.736965594166206 + >>> CE == -np.sum(pk * np.log(qk)) / np.log(base) + True + + """ + if base is not None and base <= 0: + raise ValueError("`base` must be a positive number or `None`.") + + xp = array_namespace(pk, qk) + pk, qk = xp_promote(pk, qk, broadcast=True, xp=xp) + + with np.errstate(invalid='ignore'): + if qk is not None: + pk, qk = _share_masks(pk, qk, xp=xp) + qk = qk / xp.sum(qk, axis=axis, keepdims=True) + pk = pk / xp.sum(pk, axis=axis, keepdims=True) + + if qk is None: + vec = special.entr(pk) + else: + vec = _masked_apply(special.rel_entr, args=(pk, qk), xp=xp) + + S = xp.sum(vec, axis=axis) + if base is not None: + S /= math.log(base) + return S + + +def _differential_entropy_is_too_small(samples, kwargs, axis=-1): + values = samples[0] + n = values.shape[axis] + window_length = kwargs.get("window_length") + if window_length is None: + window_length = math.floor(math.sqrt(n) + 0.5) + if not 2 <= 2 * window_length < n: + return True + return False + + +@xp_capabilities() +@_axis_nan_policy_factory( + lambda x: x, n_outputs=1, result_to_tuple=lambda x, _: (x,), + too_small=_differential_entropy_is_too_small +) +def differential_entropy( + values: np.typing.ArrayLike, + *, + window_length: int | None = None, + base: float | None = None, + axis: int = 0, + method: str = "auto", +) -> np.number | np.ndarray: + r"""Given a sample of a distribution, estimate the differential entropy. + + Several estimation methods are available using the `method` parameter. By + default, a method is selected based the size of the sample. + + Parameters + ---------- + values : sequence + Sample from a continuous distribution. + window_length : int, optional + Window length for computing Vasicek estimate. Must be an integer + between 1 and half of the sample size. If ``None`` (the default), it + uses the heuristic value + + .. math:: + \left \lfloor \sqrt{n} + 0.5 \right \rfloor + + where :math:`n` is the sample size. This heuristic was originally + proposed in [2]_ and has become common in the literature. + base : float, optional + The logarithmic base to use, defaults to ``e`` (natural logarithm). + axis : int, optional + The axis along which the differential entropy is calculated. + Default is 0. + method : {'vasicek', 'van es', 'ebrahimi', 'correa', 'auto'}, optional + The method used to estimate the differential entropy from the sample. + Default is ``'auto'``. See Notes for more information. + + Returns + ------- + entropy : float + The calculated differential entropy. + + Notes + ----- + This function will converge to the true differential entropy in the limit + + .. math:: + n \to \infty, \quad m \to \infty, \quad \frac{m}{n} \to 0 + + The optimal choice of ``window_length`` for a given sample size depends on + the (unknown) distribution. Typically, the smoother the density of the + distribution, the larger the optimal value of ``window_length`` [1]_. + + The following options are available for the `method` parameter. + + * ``'vasicek'`` uses the estimator presented in [1]_. This is + one of the first and most influential estimators of differential entropy. + * ``'van es'`` uses the bias-corrected estimator presented in [3]_, which + is not only consistent but, under some conditions, asymptotically normal. + * ``'ebrahimi'`` uses an estimator presented in [4]_, which was shown + in simulation to have smaller bias and mean squared error than + the Vasicek estimator. + * ``'correa'`` uses the estimator presented in [5]_ based on local linear + regression. In a simulation study, it had consistently smaller mean + square error than the Vasiceck estimator, but it is more expensive to + compute. + * ``'auto'`` selects the method automatically (default). Currently, + this selects ``'van es'`` for very small samples (<10), ``'ebrahimi'`` + for moderate sample sizes (11-1000), and ``'vasicek'`` for larger + samples, but this behavior is subject to change in future versions. + + All estimators are implemented as described in [6]_. + + References + ---------- + .. [1] Vasicek, O. (1976). A test for normality based on sample entropy. + Journal of the Royal Statistical Society: + Series B (Methodological), 38(1), 54-59. + .. [2] Crzcgorzewski, P., & Wirczorkowski, R. (1999). Entropy-based + goodness-of-fit test for exponentiality. Communications in + Statistics-Theory and Methods, 28(5), 1183-1202. + .. [3] Van Es, B. (1992). Estimating functionals related to a density by a + class of statistics based on spacings. Scandinavian Journal of + Statistics, 61-72. + .. [4] Ebrahimi, N., Pflughoeft, K., & Soofi, E. S. (1994). Two measures + of sample entropy. Statistics & Probability Letters, 20(3), 225-234. + .. [5] Correa, J. C. (1995). A new estimator of entropy. Communications + in Statistics-Theory and Methods, 24(10), 2439-2449. + .. [6] Noughabi, H. A. (2015). Entropy Estimation Using Numerical Methods. + Annals of Data Science, 2(2), 231-241. + https://link.springer.com/article/10.1007/s40745-015-0045-9 + + Examples + -------- + >>> import numpy as np + >>> from scipy.stats import differential_entropy, norm + + Entropy of a standard normal distribution: + + >>> rng = np.random.default_rng() + >>> values = rng.standard_normal(100) + >>> differential_entropy(values) + 1.3407817436640392 + + Compare with the true entropy: + + >>> float(norm.entropy()) + 1.4189385332046727 + + For several sample sizes between 5 and 1000, compare the accuracy of + the ``'vasicek'``, ``'van es'``, and ``'ebrahimi'`` methods. Specifically, + compare the root mean squared error (over 1000 trials) between the estimate + and the true differential entropy of the distribution. + + >>> from scipy import stats + >>> import matplotlib.pyplot as plt + >>> + >>> + >>> def rmse(res, expected): + ... '''Root mean squared error''' + ... return np.sqrt(np.mean((res - expected)**2)) + >>> + >>> + >>> a, b = np.log10(5), np.log10(1000) + >>> ns = np.round(np.logspace(a, b, 10)).astype(int) + >>> reps = 1000 # number of repetitions for each sample size + >>> expected = stats.expon.entropy() + >>> + >>> method_errors = {'vasicek': [], 'van es': [], 'ebrahimi': []} + >>> for method in method_errors: + ... for n in ns: + ... rvs = stats.expon.rvs(size=(reps, n), random_state=rng) + ... res = stats.differential_entropy(rvs, method=method, axis=-1) + ... error = rmse(res, expected) + ... method_errors[method].append(error) + >>> + >>> for method, errors in method_errors.items(): + ... plt.loglog(ns, errors, label=method) + >>> + >>> plt.legend() + >>> plt.xlabel('sample size') + >>> plt.ylabel('RMSE (1000 trials)') + >>> plt.title('Entropy Estimator Error (Exponential Distribution)') + + """ + xp = array_namespace(values) + values = xp_promote(values, force_floating=True, xp=xp) + values = xp.moveaxis(values, axis, -1) + n = values.shape[-1] # type: ignore[union-attr] + + if window_length is None: + window_length = math.floor(math.sqrt(n) + 0.5) + + if not 2 <= 2 * window_length < n: + raise ValueError( + f"Window length ({window_length}) must be positive and less " + f"than half the sample size ({n}).", + ) + + if base is not None and base <= 0: + raise ValueError("`base` must be a positive number or `None`.") + + sorted_data = xp.sort(values, axis=-1) + + methods = {"vasicek": _vasicek_entropy, + "van es": _van_es_entropy, + "correa": _correa_entropy, + "ebrahimi": _ebrahimi_entropy, + "auto": _vasicek_entropy} + method = method.lower() + if method not in methods: + message = f"`method` must be one of {set(methods)}" + raise ValueError(message) + + if method == "auto": + if n <= 10: + method = 'van es' + elif n <= 1000: + method = 'ebrahimi' + else: + method = 'vasicek' + + res = methods[method](sorted_data, window_length, xp=xp) + + if base is not None: + res /= math.log(base) + + # avoid dtype changes due to data-apis/array-api-compat#152 + # can be removed when data-apis/array-api-compat#152 is resolved + return xp.astype(res, values.dtype) # type: ignore[union-attr] + + +def _pad_along_last_axis(X, m, *, xp): + """Pad the data for computing the rolling window difference.""" + # scales a bit better than method in _vasicek_like_entropy + shape = X.shape[:-1] + (m,) + Xl = xp.broadcast_to(X[..., :1], shape) # :1 vs 0 to maintain shape + Xr = xp.broadcast_to(X[..., -1:], shape) + return xp.concat((Xl, X, Xr), axis=-1) + + +def _vasicek_entropy(X, m, *, xp): + """Compute the Vasicek estimator as described in [6] Eq. 1.3.""" + n = X.shape[-1] + X = _pad_along_last_axis(X, m, xp=xp) + differences = X[..., 2 * m:] - X[..., : -2 * m:] + logs = xp.log(n/(2*m) * differences) + return xp.mean(logs, axis=-1) + + +def _van_es_entropy(X, m, *, xp): + """Compute the van Es estimator as described in [6].""" + # No equation number, but referred to as HVE_mn. + # Typo: there should be a log within the summation. + n = X.shape[-1] + difference = X[..., m:] - X[..., :-m] + term1 = 1/(n-m) * xp.sum(xp.log((n+1)/m * difference), axis=-1) + k = xp.arange(m, n+1, dtype=term1.dtype, device=xp_device(X)) + return term1 + xp.sum(1/k) + math.log(m) - math.log(n+1) + + +def _ebrahimi_entropy(X, m, *, xp): + """Compute the Ebrahimi estimator as described in [6].""" + # No equation number, but referred to as HE_mn + n = X.shape[-1] + X = _pad_along_last_axis(X, m, xp=xp) + + differences = X[..., 2 * m:] - X[..., : -2 * m:] + + i = xp.arange(1, n+1, dtype=X.dtype, device=xp_device(X)) + ci = xp.where(i <= m, 1 + (i - 1)/m, 2.) + ci = xp.where(i >= n - m + 1, 1 + (n - i)/m, ci) + + logs = xp.log(n * differences / (ci * m)) + return xp.mean(logs, axis=-1) + + +def _correa_entropy(X, m, *, xp): + """Compute the Correa estimator as described in [6].""" + # No equation number, but referred to as HC_mn + n = X.shape[-1] + X = _pad_along_last_axis(X, m, xp=xp) + + i = xp.arange(1, n+1, device=xp_device(X)) + dj = xp.arange(-m, m+1, device=xp_device(X))[:, None] + j = i + dj + j0 = j + m - 1 # 0-indexed version of j + + Xibar = xp.mean(X[..., j0], axis=-2, keepdims=True) + difference = X[..., j0] - Xibar + num = xp.sum(difference*dj, axis=-2) # dj is d-i + den = n*xp.sum(difference**2, axis=-2) + return -xp.mean(xp.log(num/den), axis=-1) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_finite_differences.py b/venv/lib/python3.12/site-packages/scipy/stats/_finite_differences.py new file mode 100644 index 0000000000000000000000000000000000000000..6ec91654c4e95f73f32edc5aa357a4e3f80fff1f --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_finite_differences.py @@ -0,0 +1,145 @@ +from numpy import arange, newaxis, hstack, prod, array +from scipy import linalg + + +def _central_diff_weights(Np, ndiv=1): + """ + Return weights for an Np-point central derivative. + + Assumes equally-spaced function points. + + If weights are in the vector w, then + derivative is w[0] * f(x-ho*dx) + ... + w[-1] * f(x+h0*dx) + + Parameters + ---------- + Np : int + Number of points for the central derivative. + ndiv : int, optional + Number of divisions. Default is 1. + + Returns + ------- + w : ndarray + Weights for an Np-point central derivative. Its size is `Np`. + + Notes + ----- + Can be inaccurate for a large number of points. + + Examples + -------- + We can calculate a derivative value of a function. + + >>> def f(x): + ... return 2 * x**2 + 3 + >>> x = 3.0 # derivative point + >>> h = 0.1 # differential step + >>> Np = 3 # point number for central derivative + >>> weights = _central_diff_weights(Np) # weights for first derivative + >>> vals = [f(x + (i - Np/2) * h) for i in range(Np)] + >>> sum(w * v for (w, v) in zip(weights, vals))/h + 11.79999999999998 + + This value is close to the analytical solution: + f'(x) = 4x, so f'(3) = 12 + + References + ---------- + .. [1] https://en.wikipedia.org/wiki/Finite_difference + + """ + if Np < ndiv + 1: + raise ValueError( + "Number of points must be at least the derivative order + 1." + ) + if Np % 2 == 0: + raise ValueError("The number of points must be odd.") + + ho = Np >> 1 + x = arange(-ho, ho + 1.0) + x = x[:, newaxis] + X = x**0.0 + for k in range(1, Np): + X = hstack([X, x**k]) + w = prod(arange(1, ndiv + 1), axis=0) * linalg.inv(X)[ndiv] + return w + + +def _derivative(func, x0, dx=1.0, n=1, args=(), order=3): + """ + Find the nth derivative of a function at a point. + + Given a function, use a central difference formula with spacing `dx` to + compute the nth derivative at `x0`. + + Parameters + ---------- + func : function + Input function. + x0 : float + The point at which the nth derivative is found. + dx : float, optional + Spacing. + n : int, optional + Order of the derivative. Default is 1. + args : tuple, optional + Arguments + order : int, optional + Number of points to use, must be odd. + + Notes + ----- + Decreasing the step size too small can result in round-off error. + + Examples + -------- + >>> def f(x): + ... return x**3 + x**2 + >>> _derivative(f, 1.0, dx=1e-6) + 4.9999999999217337 + + """ + if order < n + 1: + raise ValueError( + "'order' (the number of points used to compute the derivative), " + "must be at least the derivative order 'n' + 1." + ) + if order % 2 == 0: + raise ValueError( + "'order' (the number of points used to compute the derivative) " + "must be odd." + ) + # pre-computed for n=1 and 2 and low-order for speed. + if n == 1: + if order == 3: + weights = array([-1, 0, 1]) / 2.0 + elif order == 5: + weights = array([1, -8, 0, 8, -1]) / 12.0 + elif order == 7: + weights = array([-1, 9, -45, 0, 45, -9, 1]) / 60.0 + elif order == 9: + weights = array([3, -32, 168, -672, 0, 672, -168, 32, -3]) / 840.0 + else: + weights = _central_diff_weights(order, 1) + elif n == 2: + if order == 3: + weights = array([1, -2.0, 1]) + elif order == 5: + weights = array([-1, 16, -30, 16, -1]) / 12.0 + elif order == 7: + weights = array([2, -27, 270, -490, 270, -27, 2]) / 180.0 + elif order == 9: + weights = ( + array([-9, 128, -1008, 8064, -14350, 8064, -1008, 128, -9]) + / 5040.0 + ) + else: + weights = _central_diff_weights(order, 2) + else: + weights = _central_diff_weights(order, n) + val = 0.0 + ho = order >> 1 + for k in range(order): + val += weights[k] * func(x0 + (k - ho) * dx, *args) + return val / prod((dx,) * n, axis=0) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_fit.py b/venv/lib/python3.12/site-packages/scipy/stats/_fit.py new file mode 100644 index 0000000000000000000000000000000000000000..a73e410753dcfc9992e31f9395bbe72e45077749 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_fit.py @@ -0,0 +1,1349 @@ +import warnings +from collections import namedtuple +import numpy as np +from scipy import optimize, stats +from scipy._lib._array_api import xp_capabilities +from scipy._lib._util import check_random_state, _transition_to_rng + + +def _combine_bounds(name, user_bounds, shape_domain, integral): + """Intersection of user-defined bounds and distribution PDF/PMF domain""" + + user_bounds = np.atleast_1d(user_bounds) + + if user_bounds[0] > user_bounds[1]: + message = (f"There are no values for `{name}` on the interval " + f"{list(user_bounds)}.") + raise ValueError(message) + + bounds = (max(user_bounds[0], shape_domain[0]), + min(user_bounds[1], shape_domain[1])) + + if integral and (np.ceil(bounds[0]) > np.floor(bounds[1])): + message = (f"There are no integer values for `{name}` on the interval " + f"defined by the user-provided bounds and the domain " + "of the distribution.") + raise ValueError(message) + elif not integral and (bounds[0] > bounds[1]): + message = (f"There are no values for `{name}` on the interval " + f"defined by the user-provided bounds and the domain " + "of the distribution.") + raise ValueError(message) + + if not np.all(np.isfinite(bounds)): + message = (f"The intersection of user-provided bounds for `{name}` " + f"and the domain of the distribution is not finite. Please " + f"provide finite bounds for shape `{name}` in `bounds`.") + raise ValueError(message) + + return bounds + + +class FitResult: + r"""Result of fitting a discrete or continuous distribution to data + + Attributes + ---------- + params : namedtuple + A namedtuple containing the maximum likelihood estimates of the + shape parameters, location, and (if applicable) scale of the + distribution. + success : bool or None + Whether the optimizer considered the optimization to terminate + successfully or not. + message : str or None + Any status message provided by the optimizer. + + """ + + def __init__(self, dist, data, discrete, res): + self._dist = dist + self._data = data + self.discrete = discrete + self.pxf = getattr(dist, "pmf", None) or getattr(dist, "pdf", None) + + shape_names = [] if dist.shapes is None else dist.shapes.split(", ") + if not discrete: + FitParams = namedtuple('FitParams', shape_names + ['loc', 'scale']) + else: + FitParams = namedtuple('FitParams', shape_names + ['loc']) + + self.params = FitParams(*res.x) + + # Optimizer can report success even when nllf is infinite + if res.success and not np.isfinite(self.nllf()): + res.success = False + res.message = ("Optimization converged to parameter values that " + "are inconsistent with the data.") + self.success = getattr(res, "success", None) + self.message = getattr(res, "message", None) + + def __repr__(self): + keys = ["params", "success", "message"] + m = max(map(len, keys)) + 1 + return '\n'.join([key.rjust(m) + ': ' + repr(getattr(self, key)) + for key in keys if getattr(self, key) is not None]) + + def nllf(self, params=None, data=None): + """Negative log-likelihood function + + Evaluates the negative of the log-likelihood function of the provided + data at the provided parameters. + + Parameters + ---------- + params : tuple, optional + The shape parameters, location, and (if applicable) scale of the + distribution as a single tuple. Default is the maximum likelihood + estimates (``self.params``). + data : array_like, optional + The data for which the log-likelihood function is to be evaluated. + Default is the data to which the distribution was fit. + + Returns + ------- + nllf : float + The negative of the log-likelihood function. + + """ + params = params if params is not None else self.params + data = data if data is not None else self._data + return self._dist.nnlf(theta=params, x=data) + + def plot(self, ax=None, *, plot_type="hist"): + """Visually compare the data against the fitted distribution. + + Available only if `matplotlib` is installed. + + Parameters + ---------- + ax : `matplotlib.axes.Axes` + Axes object to draw the plot onto, otherwise uses the current Axes. + plot_type : {"hist", "qq", "pp", "cdf"} + Type of plot to draw. Options include: + + - "hist": Superposes the PDF/PMF of the fitted distribution + over a normalized histogram of the data. + - "qq": Scatter plot of theoretical quantiles against the + empirical quantiles. Specifically, the x-coordinates are the + values of the fitted distribution PPF evaluated at the + percentiles ``(np.arange(1, n) - 0.5)/n``, where ``n`` is the + number of data points, and the y-coordinates are the sorted + data points. + - "pp": Scatter plot of theoretical percentiles against the + observed percentiles. Specifically, the x-coordinates are the + percentiles ``(np.arange(1, n) - 0.5)/n``, where ``n`` is + the number of data points, and the y-coordinates are the values + of the fitted distribution CDF evaluated at the sorted + data points. + - "cdf": Superposes the CDF of the fitted distribution over the + empirical CDF. Specifically, the x-coordinates of the empirical + CDF are the sorted data points, and the y-coordinates are the + percentiles ``(np.arange(1, n) - 0.5)/n``, where ``n`` is + the number of data points. + + Returns + ------- + ax : `matplotlib.axes.Axes` + The matplotlib Axes object on which the plot was drawn. + + Examples + -------- + >>> import numpy as np + >>> from scipy import stats + >>> import matplotlib.pyplot as plt # matplotlib must be installed + >>> rng = np.random.default_rng() + >>> data = stats.nbinom(5, 0.5).rvs(size=1000, random_state=rng) + >>> bounds = [(0, 30), (0, 1)] + >>> res = stats.fit(stats.nbinom, data, bounds) + >>> ax = res.plot() # save matplotlib Axes object + + The `matplotlib.axes.Axes` object can be used to customize the plot. + See `matplotlib.axes.Axes` documentation for details. + + >>> ax.set_xlabel('number of trials') # customize axis label + >>> ax.get_children()[0].set_linewidth(5) # customize line widths + >>> ax.legend() + >>> plt.show() + """ + try: + import matplotlib # noqa: F401 + except ModuleNotFoundError as exc: + message = "matplotlib must be installed to use method `plot`." + raise ModuleNotFoundError(message) from exc + + plots = {'histogram': self._hist_plot, 'qq': self._qq_plot, + 'pp': self._pp_plot, 'cdf': self._cdf_plot, + 'hist': self._hist_plot} + if plot_type.lower() not in plots: + message = f"`plot_type` must be one of {set(plots.keys())}" + raise ValueError(message) + plot = plots[plot_type.lower()] + + if ax is None: + import matplotlib.pyplot as plt + ax = plt.gca() + + fit_params = np.atleast_1d(self.params) + + return plot(ax=ax, fit_params=fit_params) + + def _hist_plot(self, ax, fit_params): + from matplotlib.ticker import MaxNLocator + + support = self._dist.support(*fit_params) + lb = support[0] if np.isfinite(support[0]) else min(self._data) + ub = support[1] if np.isfinite(support[1]) else max(self._data) + pxf = "PMF" if self.discrete else "PDF" + + if self.discrete: + x = np.arange(lb, ub + 2) + y = self.pxf(x, *fit_params) + ax.vlines(x[:-1], 0, y[:-1], label='Fitted Distribution PMF', + color='C0') + options = dict(density=True, bins=x, align='left', color='C1') + ax.xaxis.set_major_locator(MaxNLocator(integer=True)) + ax.set_xlabel('k') + ax.set_ylabel('PMF') + else: + x = np.linspace(lb, ub, 200) + y = self.pxf(x, *fit_params) + ax.plot(x, y, '--', label='Fitted Distribution PDF', color='C0') + options = dict(density=True, bins=50, align='mid', color='C1') + ax.set_xlabel('x') + ax.set_ylabel('PDF') + + if len(self._data) > 50 or self.discrete: + ax.hist(self._data, label="Histogram of Data", **options) + else: + ax.plot(self._data, np.zeros_like(self._data), "*", + label='Data', color='C1') + + ax.set_title(rf"Fitted $\tt {self._dist.name}$ {pxf} and Histogram") + ax.legend(*ax.get_legend_handles_labels()) + return ax + + def _qp_plot(self, ax, fit_params, qq): + data = np.sort(self._data) + ps = self._plotting_positions(len(self._data)) + + if qq: + qp = "Quantiles" + plot_type = 'Q-Q' + x = self._dist.ppf(ps, *fit_params) + y = data + else: + qp = "Percentiles" + plot_type = 'P-P' + x = ps + y = self._dist.cdf(data, *fit_params) + + ax.plot(x, y, '.', label=f'Fitted Distribution {plot_type}', + color='C0', zorder=1) + xlim = ax.get_xlim() + ylim = ax.get_ylim() + lim = [min(xlim[0], ylim[0]), max(xlim[1], ylim[1])] + if not qq: + lim = max(lim[0], 0), min(lim[1], 1) + + if self.discrete and qq: + q_min, q_max = int(lim[0]), int(lim[1]+1) + q_ideal = np.arange(q_min, q_max) + # q_ideal = np.unique(self._dist.ppf(ps, *fit_params)) + ax.plot(q_ideal, q_ideal, 'o', label='Reference', color='k', + alpha=0.25, markerfacecolor='none', clip_on=True) + elif self.discrete and not qq: + # The intent of this is to match the plot that would be produced + # if x were continuous on [0, 1] and y were cdf(ppf(x)). + # It can be approximated by letting x = np.linspace(0, 1, 1000), + # but this might not look great when zooming in. The vertical + # portions are included to indicate where the transition occurs + # where the data completely obscures the horizontal portions. + p_min, p_max = lim + a, b = self._dist.support(*fit_params) + p_min = max(p_min, 0 if np.isfinite(a) else 1e-3) + p_max = min(p_max, 1 if np.isfinite(b) else 1-1e-3) + q_min, q_max = self._dist.ppf([p_min, p_max], *fit_params) + qs = np.arange(q_min-1, q_max+1) + ps = self._dist.cdf(qs, *fit_params) + ax.step(ps, ps, '-', label='Reference', color='k', alpha=0.25, + clip_on=True) + else: + ax.plot(lim, lim, '-', label='Reference', color='k', alpha=0.25, + clip_on=True) + + ax.set_xlim(lim) + ax.set_ylim(lim) + ax.set_xlabel(rf"Fitted $\tt {self._dist.name}$ Theoretical {qp}") + ax.set_ylabel(f"Data {qp}") + ax.set_title(rf"Fitted $\tt {self._dist.name}$ {plot_type} Plot") + ax.legend(*ax.get_legend_handles_labels()) + ax.set_aspect('equal') + return ax + + def _qq_plot(self, **kwargs): + return self._qp_plot(qq=True, **kwargs) + + def _pp_plot(self, **kwargs): + return self._qp_plot(qq=False, **kwargs) + + def _plotting_positions(self, n, a=.5): + # See https://en.wikipedia.org/wiki/Q%E2%80%93Q_plot#Plotting_positions + k = np.arange(1, n+1) + return (k-a) / (n + 1 - 2*a) + + def _cdf_plot(self, ax, fit_params): + data = np.sort(self._data) + ecdf = self._plotting_positions(len(self._data)) + ls = '--' if len(np.unique(data)) < 30 else '.' + xlabel = 'k' if self.discrete else 'x' + ax.step(data, ecdf, ls, label='Empirical CDF', color='C1', zorder=0) + + xlim = ax.get_xlim() + q = np.linspace(*xlim, 300) + tcdf = self._dist.cdf(q, *fit_params) + + ax.plot(q, tcdf, label='Fitted Distribution CDF', color='C0', zorder=1) + ax.set_xlim(xlim) + ax.set_ylim(0, 1) + ax.set_xlabel(xlabel) + ax.set_ylabel("CDF") + ax.set_title(rf"Fitted $\tt {self._dist.name}$ and Empirical CDF") + handles, labels = ax.get_legend_handles_labels() + ax.legend(handles[::-1], labels[::-1]) + return ax + + +@xp_capabilities(out_of_scope=True) +def fit(dist, data, bounds=None, *, guess=None, method='mle', + optimizer=optimize.differential_evolution): + r"""Fit a discrete or continuous distribution to data. + + Given a distribution, data, and bounds on the parameters of the + distribution, return maximum likelihood estimates of the parameters. + + Parameters + ---------- + dist : `scipy.stats.rv_continuous` or `scipy.stats.rv_discrete` + The object representing the distribution to be fit to the data. + data : 1D array_like + The data to which the distribution is to be fit. If the data contain + any of ``np.nan``, ``np.inf``, or -``np.inf``, the fit method will + raise a ``ValueError``. + bounds : dict or sequence of tuples, optional + If a dictionary, each key is the name of a parameter of the + distribution, and the corresponding value is a tuple containing the + lower and upper bound on that parameter. If the distribution is + defined only for a finite range of values of that parameter, no entry + for that parameter is required; e.g., some distributions have + parameters which must be on the interval [0, 1]. Bounds for parameters + location (``loc``) and scale (``scale``) are optional; by default, + they are fixed to 0 and 1, respectively. + + If a sequence, element *i* is a tuple containing the lower and upper + bound on the *i*\ th parameter of the distribution. In this case, + bounds for *all* distribution shape parameters must be provided. + Optionally, bounds for location and scale may follow the + distribution shape parameters. + + If a shape is to be held fixed (e.g. if it is known), the + lower and upper bounds may be equal. If a user-provided lower or upper + bound is beyond a bound of the domain for which the distribution is + defined, the bound of the distribution's domain will replace the + user-provided value. Similarly, parameters which must be integral + will be constrained to integral values within the user-provided bounds. + guess : dict or array_like, optional + If a dictionary, each key is the name of a parameter of the + distribution, and the corresponding value is a guess for the value + of the parameter. + + If a sequence, element *i* is a guess for the *i*\ th parameter of the + distribution. In this case, guesses for *all* distribution shape + parameters must be provided. + + If `guess` is not provided, guesses for the decision variables will + not be passed to the optimizer. If `guess` is provided, guesses for + any missing parameters will be set at the mean of the lower and + upper bounds. Guesses for parameters which must be integral will be + rounded to integral values, and guesses that lie outside the + intersection of the user-provided bounds and the domain of the + distribution will be clipped. + method : {'mle', 'mse'} + With ``method="mle"`` (default), the fit is computed by minimizing + the negative log-likelihood function. A large, finite penalty + (rather than infinite negative log-likelihood) is applied for + observations beyond the support of the distribution. + With ``method="mse"``, the fit is computed by minimizing + the negative log-product spacing function. The same penalty is applied + for observations beyond the support. We follow the approach of [1]_, + which is generalized for samples with repeated observations. + optimizer : callable, optional + `optimizer` is a callable that accepts the following positional + argument. + + fun : callable + The objective function to be optimized. `fun` accepts one argument + ``x``, candidate shape parameters of the distribution, and returns + the objective function value given ``x``, `dist`, and the provided + `data`. + The job of `optimizer` is to find values of the decision variables + that minimizes `fun`. + + `optimizer` must also accept the following keyword argument. + + bounds : sequence of tuples + The bounds on values of the decision variables; each element will + be a tuple containing the lower and upper bound on a decision + variable. + + If `guess` is provided, `optimizer` must also accept the following + keyword argument. + + x0 : array_like + The guesses for each decision variable. + + If the distribution has any shape parameters that must be integral or + if the distribution is discrete and the location parameter is not + fixed, `optimizer` must also accept the following keyword argument. + + integrality : array_like of bools + For each decision variable, True if the decision variable + must be constrained to integer values and False if the decision + variable is continuous. + + `optimizer` must return an object, such as an instance of + `scipy.optimize.OptimizeResult`, which holds the optimal values of + the decision variables in an attribute ``x``. If attributes + ``fun``, ``status``, or ``message`` are provided, they will be + included in the result object returned by `fit`. + + Returns + ------- + result : `~scipy.stats._result_classes.FitResult` + An object with the following fields. + + params : namedtuple + A namedtuple containing the maximum likelihood estimates of the + shape parameters, location, and (if applicable) scale of the + distribution. + success : bool or None + Whether the optimizer considered the optimization to terminate + successfully or not. + message : str or None + Any status message provided by the optimizer. + + The object has the following method: + + nllf(params=None, data=None) + By default, the negative log-likelihood function at the fitted + `params` for the given `data`. Accepts a tuple containing + alternative shapes, location, and scale of the distribution and + an array of alternative data. + + plot(ax=None) + Superposes the PDF/PMF of the fitted distribution over a normalized + histogram of the data. + + See Also + -------- + rv_continuous, rv_discrete + + Notes + ----- + Optimization is more likely to converge to the maximum likelihood estimate + when the user provides tight bounds containing the maximum likelihood + estimate. For example, when fitting a binomial distribution to data, the + number of experiments underlying each sample may be known, in which case + the corresponding shape parameter ``n`` can be fixed. + + References + ---------- + .. [1] Shao, Yongzhao, and Marjorie G. Hahn. "Maximum product of spacings + method: a unified formulation with illustration of strong + consistency." Illinois Journal of Mathematics 43.3 (1999): 489-499. + + Examples + -------- + Suppose we wish to fit a distribution to the following data. + + >>> import numpy as np + >>> from scipy import stats + >>> rng = np.random.default_rng() + >>> dist = stats.nbinom + >>> shapes = (5, 0.5) + >>> data = dist.rvs(*shapes, size=1000, random_state=rng) + + Suppose we do not know how the data were generated, but we suspect that + it follows a negative binomial distribution with parameters *n* and *p*\. + (See `scipy.stats.nbinom`.) We believe that the parameter *n* was fewer + than 30, and we know that the parameter *p* must lie on the interval + [0, 1]. We record this information in a variable `bounds` and pass + this information to `fit`. + + >>> bounds = [(0, 30), (0, 1)] + >>> res = stats.fit(dist, data, bounds) + + `fit` searches within the user-specified `bounds` for the + values that best match the data (in the sense of maximum likelihood + estimation). In this case, it found shape values similar to those + from which the data were actually generated. + + >>> res.params + FitParams(n=5.0, p=0.5028157644634368, loc=0.0) # may vary + + We can visualize the results by superposing the probability mass function + of the distribution (with the shapes fit to the data) over a normalized + histogram of the data. + + >>> import matplotlib.pyplot as plt # matplotlib must be installed to plot + >>> res.plot() + >>> plt.show() + + Note that the estimate for *n* was exactly integral; this is because + the domain of the `nbinom` PMF includes only integral *n*, and the `nbinom` + object "knows" that. `nbinom` also knows that the shape *p* must be a + value between 0 and 1. In such a case - when the domain of the distribution + with respect to a parameter is finite - we are not required to specify + bounds for the parameter. + + >>> bounds = {'n': (0, 30)} # omit parameter p using a `dict` + >>> res2 = stats.fit(dist, data, bounds) + >>> res2.params + FitParams(n=5.0, p=0.5016492009232932, loc=0.0) # may vary + + If we wish to force the distribution to be fit with *n* fixed at 6, we can + set both the lower and upper bounds on *n* to 6. Note, however, that the + value of the objective function being optimized is typically worse (higher) + in this case. + + >>> bounds = {'n': (6, 6)} # fix parameter `n` + >>> res3 = stats.fit(dist, data, bounds) + >>> res3.params + FitParams(n=6.0, p=0.5486556076755706, loc=0.0) # may vary + >>> res3.nllf() > res.nllf() + True # may vary + + Note that the numerical results of the previous examples are typical, but + they may vary because the default optimizer used by `fit`, + `scipy.optimize.differential_evolution`, is stochastic. However, we can + customize the settings used by the optimizer to ensure reproducibility - + or even use a different optimizer entirely - using the `optimizer` + parameter. + + >>> from scipy.optimize import differential_evolution + >>> rng = np.random.default_rng(767585560716548) + >>> def optimizer(fun, bounds, *, integrality): + ... return differential_evolution(fun, bounds, strategy='best2bin', + ... rng=rng, integrality=integrality) + >>> bounds = [(0, 30), (0, 1)] + >>> res4 = stats.fit(dist, data, bounds, optimizer=optimizer) + >>> res4.params + FitParams(n=5.0, p=0.5015183149259951, loc=0.0) + + """ + # --- Input Validation / Standardization --- # + user_bounds = bounds + user_guess = guess + + # distribution input validation and information collection + if hasattr(dist, "pdf"): # can't use isinstance for types + default_bounds = {'loc': (0, 0), 'scale': (1, 1)} + discrete = False + elif hasattr(dist, "pmf"): + default_bounds = {'loc': (0, 0)} + discrete = True + else: + message = ("`dist` must be an instance of `rv_continuous` " + "or `rv_discrete.`") + raise ValueError(message) + + try: + param_info = dist._param_info() + except AttributeError as e: + message = (f"Distribution `{dist.name}` is not yet supported by " + "`scipy.stats.fit` because shape information has " + "not been defined.") + raise ValueError(message) from e + + # data input validation + data = np.asarray(data) + if data.ndim != 1: + message = "`data` must be exactly one-dimensional." + raise ValueError(message) + if not (np.issubdtype(data.dtype, np.number) + and np.all(np.isfinite(data))): + message = "All elements of `data` must be finite numbers." + raise ValueError(message) + + # bounds input validation and information collection + n_params = len(param_info) + n_shapes = n_params - (1 if discrete else 2) + param_list = [param.name for param in param_info] + param_names = ", ".join(param_list) + shape_names = ", ".join(param_list[:n_shapes]) + + if user_bounds is None: + user_bounds = {} + + if isinstance(user_bounds, dict): + default_bounds.update(user_bounds) + user_bounds = default_bounds + user_bounds_array = np.empty((n_params, 2)) + for i in range(n_params): + param_name = param_info[i].name + user_bound = user_bounds.pop(param_name, None) + if user_bound is None: + user_bound = param_info[i].domain + user_bounds_array[i] = user_bound + if user_bounds: + message = ("Bounds provided for the following unrecognized " + f"parameters will be ignored: {set(user_bounds)}") + warnings.warn(message, RuntimeWarning, stacklevel=2) + + else: + try: + user_bounds = np.asarray(user_bounds, dtype=float) + if user_bounds.size == 0: + user_bounds = np.empty((0, 2)) + except ValueError as e: + message = ("Each element of a `bounds` sequence must be a tuple " + "containing two elements: the lower and upper bound of " + "a distribution parameter.") + raise ValueError(message) from e + if (user_bounds.ndim != 2 or user_bounds.shape[1] != 2): + message = ("Each element of `bounds` must be a tuple specifying " + "the lower and upper bounds of a shape parameter") + raise ValueError(message) + if user_bounds.shape[0] < n_shapes: + message = (f"A `bounds` sequence must contain at least {n_shapes} " + "elements: tuples specifying the lower and upper " + f"bounds of all shape parameters {shape_names}.") + raise ValueError(message) + if user_bounds.shape[0] > n_params: + message = ("A `bounds` sequence may not contain more than " + f"{n_params} elements: tuples specifying the lower and " + "upper bounds of distribution parameters " + f"{param_names}.") + raise ValueError(message) + + user_bounds_array = np.empty((n_params, 2)) + user_bounds_array[n_shapes:] = list(default_bounds.values()) + user_bounds_array[:len(user_bounds)] = user_bounds + + user_bounds = user_bounds_array + validated_bounds = [] + for i in range(n_params): + name = param_info[i].name + user_bound = user_bounds_array[i] + param_domain = param_info[i].domain + integral = param_info[i].integrality + combined = _combine_bounds(name, user_bound, param_domain, integral) + validated_bounds.append(combined) + + bounds = np.asarray(validated_bounds) + integrality = [param.integrality for param in param_info] + + # guess input validation + + if user_guess is None: + guess_array = None + elif isinstance(user_guess, dict): + default_guess = {param.name: np.mean(bound) + for param, bound in zip(param_info, bounds)} + unrecognized = set(user_guess) - set(default_guess) + if unrecognized: + message = ("Guesses provided for the following unrecognized " + f"parameters will be ignored: {unrecognized}") + warnings.warn(message, RuntimeWarning, stacklevel=2) + default_guess.update(user_guess) + + message = ("Each element of `guess` must be a scalar " + "guess for a distribution parameter.") + try: + guess_array = np.asarray([default_guess[param.name] + for param in param_info], dtype=float) + except ValueError as e: + raise ValueError(message) from e + + else: + message = ("Each element of `guess` must be a scalar " + "guess for a distribution parameter.") + try: + user_guess = np.asarray(user_guess, dtype=float) + except ValueError as e: + raise ValueError(message) from e + if user_guess.ndim != 1: + raise ValueError(message) + if user_guess.shape[0] < n_shapes: + message = (f"A `guess` sequence must contain at least {n_shapes} " + "elements: scalar guesses for the distribution shape " + f"parameters {shape_names}.") + raise ValueError(message) + if user_guess.shape[0] > n_params: + message = ("A `guess` sequence may not contain more than " + f"{n_params} elements: scalar guesses for the " + f"distribution parameters {param_names}.") + raise ValueError(message) + + guess_array = np.mean(bounds, axis=1) + guess_array[:len(user_guess)] = user_guess + + if guess_array is not None: + guess_rounded = guess_array.copy() + + guess_rounded[integrality] = np.round(guess_rounded[integrality]) + rounded = np.where(guess_rounded != guess_array)[0] + for i in rounded: + message = (f"Guess for parameter `{param_info[i].name}` " + f"rounded from {guess_array[i]} to {guess_rounded[i]}.") + warnings.warn(message, RuntimeWarning, stacklevel=2) + + guess_clipped = np.clip(guess_rounded, bounds[:, 0], bounds[:, 1]) + clipped = np.where(guess_clipped != guess_rounded)[0] + for i in clipped: + message = (f"Guess for parameter `{param_info[i].name}` " + f"clipped from {guess_rounded[i]} to " + f"{guess_clipped[i]}.") + warnings.warn(message, RuntimeWarning, stacklevel=2) + + guess = guess_clipped + else: + guess = None + + # --- Fitting --- # + def nllf(free_params, data=data): # bind data NOW + with np.errstate(invalid='ignore', divide='ignore'): + return dist._penalized_nnlf(free_params, data) + + def nlpsf(free_params, data=data): # bind data NOW + with np.errstate(invalid='ignore', divide='ignore'): + return dist._penalized_nlpsf(free_params, data) + + methods = {'mle': nllf, 'mse': nlpsf} + objective = methods[method.lower()] + + with np.errstate(invalid='ignore', divide='ignore'): + kwds = {} + if bounds is not None: + kwds['bounds'] = bounds + if np.any(integrality): + kwds['integrality'] = integrality + if guess is not None: + kwds['x0'] = guess + res = optimizer(objective, **kwds) + + return FitResult(dist, data, discrete, res) + + +GoodnessOfFitResult = namedtuple('GoodnessOfFitResult', + ('fit_result', 'statistic', 'pvalue', + 'null_distribution')) + + +@xp_capabilities(out_of_scope=True) +@_transition_to_rng('random_state') +def goodness_of_fit(dist, data, *, known_params=None, fit_params=None, + guessed_params=None, statistic='ad', n_mc_samples=9999, + rng=None): + r""" + Perform a goodness of fit test comparing data to a distribution family. + + Given a distribution family and data, perform a test of the null hypothesis + that the data were drawn from a distribution in that family. Any known + parameters of the distribution may be specified. Remaining parameters of + the distribution will be fit to the data, and the p-value of the test + is computed accordingly. Several statistics for comparing the distribution + to data are available. + + Parameters + ---------- + dist : `scipy.stats.rv_continuous` + The object representing the distribution family under the null + hypothesis. + data : 1D array_like + Finite, uncensored data to be tested. + known_params : dict, optional + A dictionary containing name-value pairs of known distribution + parameters. Monte Carlo samples are randomly drawn from the + null-hypothesized distribution with these values of the parameters. + Before the statistic is evaluated for the observed `data` and each + Monte Carlo sample, only remaining unknown parameters of the + null-hypothesized distribution family are fit to the samples; the + known parameters are held fixed. If all parameters of the distribution + family are known, then the step of fitting the distribution family to + each sample is omitted. + fit_params : dict, optional + A dictionary containing name-value pairs of distribution parameters + that have already been fit to the data, e.g. using `scipy.stats.fit` + or the ``fit`` method of `dist`. Monte Carlo samples are drawn from the + null-hypothesized distribution with these specified values of the + parameter. However, these and all other unknown parameters of the + null-hypothesized distribution family are always fit to the sample, + whether that is the observed `data` or a Monte Carlo sample, before + the statistic is evaluated. + guessed_params : dict, optional + A dictionary containing name-value pairs of distribution parameters + which have been guessed. These parameters are always considered as + free parameters and are fit both to the provided `data` as well as + to the Monte Carlo samples drawn from the null-hypothesized + distribution. The purpose of these `guessed_params` is to be used as + initial values for the numerical fitting procedure. + statistic : {"ad", "ks", "cvm", "filliben"} or callable, optional + The statistic used to compare data to a distribution after fitting + unknown parameters of the distribution family to the data. The + Anderson-Darling ("ad") [1]_, Kolmogorov-Smirnov ("ks") [1]_, + Cramer-von Mises ("cvm") [1]_, and Filliben ("filliben") [7]_ + statistics are available. Alternatively, a callable with signature + ``(dist, data, axis)`` may be supplied to compute the statistic. Here + ``dist`` is a frozen distribution object (potentially with array + parameters), ``data`` is an array of Monte Carlo samples (of + compatible shape), and ``axis`` is the axis of ``data`` along which + the statistic must be computed. + n_mc_samples : int, default: 9999 + The number of Monte Carlo samples drawn from the null hypothesized + distribution to form the null distribution of the statistic. The + sample size of each is the same as the given `data`. + rng : `numpy.random.Generator`, optional + Pseudorandom number generator state. When `rng` is None, a new + `numpy.random.Generator` is created using entropy from the + operating system. Types other than `numpy.random.Generator` are + passed to `numpy.random.default_rng` to instantiate a ``Generator``. + + Returns + ------- + res : GoodnessOfFitResult + An object with the following attributes. + + fit_result : `~scipy.stats._result_classes.FitResult` + An object representing the fit of the provided `dist` to `data`. + This object includes the values of distribution family parameters + that fully define the null-hypothesized distribution, that is, + the distribution from which Monte Carlo samples are drawn. + statistic : float + The value of the statistic comparing provided `data` to the + null-hypothesized distribution. + pvalue : float + The proportion of elements in the null distribution with + statistic values at least as extreme as the statistic value of the + provided `data`. + null_distribution : ndarray + The value of the statistic for each Monte Carlo sample + drawn from the null-hypothesized distribution. + + Notes + ----- + This is a generalized Monte Carlo goodness-of-fit procedure, special cases + of which correspond with various Anderson-Darling tests, Lilliefors' test, + etc. The test is described in [2]_, [3]_, and [4]_ as a parametric + bootstrap test. This is a Monte Carlo test in which parameters that + specify the distribution from which samples are drawn have been estimated + from the data. We describe the test using "Monte Carlo" rather than + "parametric bootstrap" throughout to avoid confusion with the more familiar + nonparametric bootstrap, and describe how the test is performed below. + + *Traditional goodness of fit tests* + + Traditionally, critical values corresponding with a fixed set of + significance levels are pre-calculated using Monte Carlo methods. Users + perform the test by calculating the value of the test statistic only for + their observed `data` and comparing this value to tabulated critical + values. This practice is not very flexible, as tables are not available for + all distributions and combinations of known and unknown parameter values. + Also, results can be inaccurate when critical values are interpolated from + limited tabulated data to correspond with the user's sample size and + fitted parameter values. To overcome these shortcomings, this function + allows the user to perform the Monte Carlo trials adapted to their + particular data. + + *Algorithmic overview* + + In brief, this routine executes the following steps: + + 1. Fit unknown parameters to the given `data`, thereby forming the + "null-hypothesized" distribution, and compute the statistic of + this pair of data and distribution. + 2. Draw random samples from this null-hypothesized distribution. + 3. Fit the unknown parameters to each random sample. + 4. Calculate the statistic between each sample and the distribution that + has been fit to the sample. + 5. Compare the value of the statistic corresponding with `data` from (1) + against the values of the statistic corresponding with the random + samples from (4). The p-value is the proportion of samples with a + statistic value greater than or equal to the statistic of the observed + data. + + In more detail, the steps are as follows. + + First, any unknown parameters of the distribution family specified by + `dist` are fit to the provided `data` using maximum likelihood estimation. + (One exception is the normal distribution with unknown location and scale: + we use the bias-corrected standard deviation ``np.std(data, ddof=1)`` for + the scale as recommended in [1]_.) + These values of the parameters specify a particular member of the + distribution family referred to as the "null-hypothesized distribution", + that is, the distribution from which the data were sampled under the null + hypothesis. The `statistic`, which compares data to a distribution, is + computed between `data` and the null-hypothesized distribution. + + Next, many (specifically `n_mc_samples`) new samples, each containing the + same number of observations as `data`, are drawn from the + null-hypothesized distribution. All unknown parameters of the distribution + family `dist` are fit to *each resample*, and the `statistic` is computed + between each sample and its corresponding fitted distribution. These + values of the statistic form the Monte Carlo null distribution (not to be + confused with the "null-hypothesized distribution" above). + + The p-value of the test is the proportion of statistic values in the Monte + Carlo null distribution that are at least as extreme as the statistic value + of the provided `data`. More precisely, the p-value is given by + + .. math:: + + p = \frac{b + 1} + {m + 1} + + where :math:`b` is the number of statistic values in the Monte Carlo null + distribution that are greater than or equal to the statistic value + calculated for `data`, and :math:`m` is the number of elements in the + Monte Carlo null distribution (`n_mc_samples`). The addition of :math:`1` + to the numerator and denominator can be thought of as including the + value of the statistic corresponding with `data` in the null distribution, + but a more formal explanation is given in [5]_. + + *Limitations* + + The test can be very slow for some distribution families because unknown + parameters of the distribution family must be fit to each of the Monte + Carlo samples, and for most distributions in SciPy, distribution fitting + performed via numerical optimization. + + *Anti-Pattern* + + For this reason, it may be tempting + to treat parameters of the distribution pre-fit to `data` (by the user) + as though they were `known_params`, as specification of all parameters of + the distribution precludes the need to fit the distribution to each Monte + Carlo sample. (This is essentially how the original Kilmogorov-Smirnov + test is performed.) Although such a test can provide evidence against the + null hypothesis, the test is conservative in the sense that small p-values + will tend to (greatly) *overestimate* the probability of making a type I + error (that is, rejecting the null hypothesis although it is true), and the + power of the test is low (that is, it is less likely to reject the null + hypothesis even when the null hypothesis is false). + This is because the Monte Carlo samples are less likely to agree with the + null-hypothesized distribution as well as `data`. This tends to increase + the values of the statistic recorded in the null distribution, so that a + larger number of them exceed the value of statistic for `data`, thereby + inflating the p-value. + + References + ---------- + .. [1] M. A. Stephens (1974). "EDF Statistics for Goodness of Fit and + Some Comparisons." Journal of the American Statistical Association, + Vol. 69, pp. 730-737. + .. [2] W. Stute, W. G. Manteiga, and M. P. Quindimil (1993). + "Bootstrap based goodness-of-fit-tests." Metrika 40.1: 243-256. + .. [3] C. Genest, & B Rémillard. (2008). "Validity of the parametric + bootstrap for goodness-of-fit testing in semiparametric models." + Annales de l'IHP Probabilités et statistiques. Vol. 44. No. 6. + .. [4] I. Kojadinovic and J. Yan (2012). "Goodness-of-fit testing based on + a weighted bootstrap: A fast large-sample alternative to the + parametric bootstrap." Canadian Journal of Statistics 40.3: 480-500. + .. [5] B. Phipson and G. K. Smyth (2010). "Permutation P-values Should + Never Be Zero: Calculating Exact P-values When Permutations Are + Randomly Drawn." Statistical Applications in Genetics and Molecular + Biology 9.1. + .. [6] H. W. Lilliefors (1967). "On the Kolmogorov-Smirnov test for + normality with mean and variance unknown." Journal of the American + statistical Association 62.318: 399-402. + .. [7] Filliben, James J. "The probability plot correlation coefficient + test for normality." Technometrics 17.1 (1975): 111-117. + + Examples + -------- + A well-known test of the null hypothesis that data were drawn from a + given distribution is the Kolmogorov-Smirnov (KS) test, available in SciPy + as `scipy.stats.ks_1samp`. Suppose we wish to test whether the following + data: + + >>> import numpy as np + >>> from scipy import stats + >>> rng = np.random.default_rng(1638083107694713882823079058616272161) + >>> x = stats.uniform.rvs(size=75, random_state=rng) + + were sampled from a normal distribution. To perform a KS test, the + empirical distribution function of the observed data will be compared + against the (theoretical) cumulative distribution function of a normal + distribution. Of course, to do this, the normal distribution under the null + hypothesis must be fully specified. This is commonly done by first fitting + the ``loc`` and ``scale`` parameters of the distribution to the observed + data, then performing the test. + + >>> loc, scale = np.mean(x), np.std(x, ddof=1) + >>> cdf = stats.norm(loc, scale).cdf + >>> stats.ks_1samp(x, cdf) + KstestResult(statistic=0.1119257570456813, + pvalue=0.2827756409939257, + statistic_location=0.7751845155861765, + statistic_sign=-1) + + An advantage of the KS-test is that the p-value - the probability of + obtaining a value of the test statistic under the null hypothesis as + extreme as the value obtained from the observed data - can be calculated + exactly and efficiently. `goodness_of_fit` can only approximate these + results. + + >>> known_params = {'loc': loc, 'scale': scale} + >>> res = stats.goodness_of_fit(stats.norm, x, known_params=known_params, + ... statistic='ks', rng=rng) + >>> res.statistic, res.pvalue + (0.1119257570456813, 0.2788) + + The statistic matches exactly, but the p-value is estimated by forming + a "Monte Carlo null distribution", that is, by explicitly drawing random + samples from `scipy.stats.norm` with the provided parameters and + calculating the stastic for each. The fraction of these statistic values + at least as extreme as ``res.statistic`` approximates the exact p-value + calculated by `scipy.stats.ks_1samp`. + + However, in many cases, we would prefer to test only that the data were + sampled from one of *any* member of the normal distribution family, not + specifically from the normal distribution with the location and scale + fitted to the observed sample. In this case, Lilliefors [6]_ argued that + the KS test is far too conservative (that is, the p-value overstates + the actual probability of rejecting a true null hypothesis) and thus lacks + power - the ability to reject the null hypothesis when the null hypothesis + is actually false. + Indeed, our p-value above is approximately 0.28, which is far too large + to reject the null hypothesis at any common significance level. + + Consider why this might be. Note that in the KS test above, the statistic + always compares data against the CDF of a normal distribution fitted to the + *observed data*. This tends to reduce the value of the statistic for the + observed data, but it is "unfair" when computing the statistic for other + samples, such as those we randomly draw to form the Monte Carlo null + distribution. It is easy to correct for this: whenever we compute the KS + statistic of a sample, we use the CDF of a normal distribution fitted + to *that sample*. The null distribution in this case has not been + calculated exactly and is tyically approximated using Monte Carlo methods + as described above. This is where `goodness_of_fit` excels. + + >>> res = stats.goodness_of_fit(stats.norm, x, statistic='ks', rng=rng) + >>> res.statistic, res.pvalue + (0.1119257570456813, 0.0196) + + Indeed, this p-value is much smaller, and small enough to (correctly) + reject the null hypothesis at common significance levels, including 5% and + 2.5%. + + However, the KS statistic is not very sensitive to all deviations from + normality. The original advantage of the KS statistic was the ability + to compute the null distribution theoretically, but a more sensitive + statistic - resulting in a higher test power - can be used now that we can + approximate the null distribution + computationally. The Anderson-Darling statistic [1]_ tends to be more + sensitive, and critical values of this statistic have been tabulated + for various significance levels and sample sizes using Monte Carlo methods. + + >>> res = stats.anderson(x, 'norm', method='interpolate') + >>> print(res.statistic) + 1.2139573337497467 + >>> print(res.pvalue) + 0.01 + + Here, the observed value of the statistic exceeds the critical value + corresponding with a 1% significance level. This tells us that the p-value + of the observed data is 1% or less, but what is it? `goodness_of_fit` can + estimate it directly. + + >>> res = stats.goodness_of_fit(stats.norm, x, statistic='ad', rng=rng) + >>> res.statistic, res.pvalue + (1.2139573337497467, 0.0034) + + A further advantage is that use of `goodness_of_fit` is not limited to + a particular set of distributions or conditions on which parameters + are known versus which must be estimated from data. Instead, + `goodness_of_fit` can estimate p-values relatively quickly for any + distribution with a sufficiently fast and reliable ``fit`` method. For + instance, here we perform a goodness of fit test using the Cramer-von Mises + statistic against the Rayleigh distribution with known location and unknown + scale. + + >>> rng = np.random.default_rng() + >>> x = stats.chi(df=2.2, loc=0, scale=2).rvs(size=1000, random_state=rng) + >>> res = stats.goodness_of_fit(stats.rayleigh, x, statistic='cvm', + ... known_params={'loc': 0}, rng=rng) + + This executes fairly quickly, but to check the reliability of the ``fit`` + method, we should inspect the fit result. + + >>> res.fit_result # location is as specified, and scale is reasonable + params: FitParams(loc=0.0, scale=2.1026719844231243) + success: True + message: 'The fit was performed successfully.' + >>> import matplotlib.pyplot as plt # matplotlib must be installed to plot + >>> res.fit_result.plot() + >>> plt.show() + + If the distribution is not fit to the observed data as well as possible, + the test may not control the type I error rate, that is, the chance of + rejecting the null hypothesis even when it is true. + + We should also look for extreme outliers in the null distribution that + may be caused by unreliable fitting. These do not necessarily invalidate + the result, but they tend to reduce the test's power. + + >>> _, ax = plt.subplots() + >>> ax.hist(np.log10(res.null_distribution)) + >>> ax.set_xlabel("log10 of CVM statistic under the null hypothesis") + >>> ax.set_ylabel("Frequency") + >>> ax.set_title("Histogram of the Monte Carlo null distribution") + >>> plt.show() + + This plot seems reassuring. + + If ``fit`` method is working reliably, and if the distribution of the test + statistic is not particularly sensitive to the values of the fitted + parameters, then the p-value provided by `goodness_of_fit` is expected to + be a good approximation. + + >>> res.statistic, res.pvalue + (0.2231991510248692, 0.0525) + + """ + args = _gof_iv(dist, data, known_params, fit_params, guessed_params, + statistic, n_mc_samples, rng) + (dist, data, fixed_nhd_params, fixed_rfd_params, guessed_nhd_params, + guessed_rfd_params, statistic, n_mc_samples_int, rng) = args + + # Fit null hypothesis distribution to data + nhd_fit_fun = _get_fit_fun(dist, data, guessed_nhd_params, + fixed_nhd_params) + nhd_vals = nhd_fit_fun(data) + nhd_dist = dist(*nhd_vals) + + def rvs(size): + return nhd_dist.rvs(size=size, random_state=rng) + + # Define statistic + fit_fun = _get_fit_fun(dist, data, guessed_rfd_params, fixed_rfd_params) + if callable(statistic): + compare_fun = statistic + else: + compare_fun = _compare_dict[statistic] + alternative = getattr(compare_fun, 'alternative', 'greater') + + def statistic_fun(data, axis): + # Make things simple by always working along the last axis. + data = np.moveaxis(data, axis, -1) + rfd_vals = fit_fun(data) + rfd_dist = dist(*rfd_vals) + return compare_fun(rfd_dist, data, axis=-1) + + res = stats.monte_carlo_test(data, rvs, statistic_fun, vectorized=True, + n_resamples=n_mc_samples, axis=-1, + alternative=alternative) + opt_res = optimize.OptimizeResult() + opt_res.success = True + opt_res.message = "The fit was performed successfully." + opt_res.x = nhd_vals + # Only continuous distributions for now, hence discrete=False + # There's no fundamental limitation; it's just that we're not using + # stats.fit, discrete distributions don't have `fit` method, and + # we haven't written any vectorized fit functions for a discrete + # distribution yet. + return GoodnessOfFitResult(FitResult(dist, data, False, opt_res), + res.statistic, res.pvalue, + res.null_distribution) + + +def _get_fit_fun(dist, data, guessed_params, fixed_params): + + shape_names = [] if dist.shapes is None else dist.shapes.split(", ") + param_names = shape_names + ['loc', 'scale'] + fparam_names = ['f'+name for name in param_names] + all_fixed = not set(fparam_names).difference(fixed_params) + guessed_shapes = [guessed_params.pop(x, None) + for x in shape_names if x in guessed_params] + + if all_fixed: + def fit_fun(data): + return [fixed_params[name] for name in fparam_names] + # Define statistic, including fitting distribution to data + elif dist in _fit_funs: + def fit_fun(data): + params = _fit_funs[dist](data, **fixed_params) + params = np.asarray(np.broadcast_arrays(*params)) + if params.ndim > 1: + params = params[..., np.newaxis] + return params + else: + def fit_fun_1d(data): + return dist.fit(data, *guessed_shapes, **guessed_params, + **fixed_params) + + def fit_fun(data): + params = np.apply_along_axis(fit_fun_1d, axis=-1, arr=data) + if params.ndim > 1: + params = params.T[..., np.newaxis] + return params + + return fit_fun + + +# Vectorized fitting functions. These are to accept ND `data` in which each +# row (slice along last axis) is a sample to fit and scalar fixed parameters. +# They return a tuple of shape parameter arrays, each of shape data.shape[:-1]. +def _fit_norm(data, floc=None, fscale=None): + loc = floc + scale = fscale + if loc is None and scale is None: + loc = np.mean(data, axis=-1) + scale = np.std(data, ddof=1, axis=-1) + elif loc is None: + loc = np.mean(data, axis=-1) + elif scale is None: + scale = np.sqrt(((data - loc)**2).mean(axis=-1)) + return loc, scale + + +_fit_funs = {stats.norm: _fit_norm} # type: ignore[attr-defined] + + +# Vectorized goodness of fit statistic functions. These accept a frozen +# distribution object and `data` in which each row (slice along last axis) is +# a sample. + + +def _anderson_darling(dist, data, axis): + x = np.sort(data, axis=-1) + n = data.shape[-1] + i = np.arange(1, n+1) + Si = (2*i - 1)/n * (dist.logcdf(x) + dist.logsf(x[..., ::-1])) + S = np.sum(Si, axis=-1) + return -n - S + + +def _compute_dplus(cdfvals): # adapted from _stats_py before gh-17062 + n = cdfvals.shape[-1] + return (np.arange(1.0, n + 1) / n - cdfvals).max(axis=-1) + + +def _compute_dminus(cdfvals): + n = cdfvals.shape[-1] + return (cdfvals - np.arange(0.0, n)/n).max(axis=-1) + + +def _kolmogorov_smirnov(dist, data, axis=-1): + x = np.sort(data, axis=axis) + cdfvals = dist.cdf(x) + cdfvals = np.moveaxis(cdfvals, axis, -1) + Dplus = _compute_dplus(cdfvals) # always works along last axis + Dminus = _compute_dminus(cdfvals) + return np.maximum(Dplus, Dminus) + + +def _corr(X, M): + # Correlation coefficient r, simplified and vectorized as we need it. + # See [7] Equation (2). Lemma 1/2 are only for distributions symmetric + # about 0. + Xm = X.mean(axis=-1, keepdims=True) + Mm = M.mean(axis=-1, keepdims=True) + num = np.sum((X - Xm) * (M - Mm), axis=-1) + den = np.sqrt(np.sum((X - Xm)**2, axis=-1) * np.sum((M - Mm)**2, axis=-1)) + return num/den + + +def _filliben(dist, data, axis): + # [7] Section 8 # 1 + X = np.sort(data, axis=-1) + + # [7] Section 8 # 2 + n = data.shape[-1] + k = np.arange(1, n+1) + # Filliben used an approximation for the uniform distribution order + # statistic medians. + # m = (k - .3175)/(n + 0.365) + # m[-1] = 0.5**(1/n) + # m[0] = 1 - m[-1] + # We can just as easily use the (theoretically) exact values. See e.g. + # https://en.wikipedia.org/wiki/Order_statistic + # "Order statistics sampled from a uniform distribution" + m = stats.beta(k, n + 1 - k).median() + + # [7] Section 8 # 3 + M = dist.ppf(m) + + # [7] Section 8 # 4 + return _corr(X, M) +_filliben.alternative = 'less' # type: ignore[attr-defined] + + +def _cramer_von_mises(dist, data, axis): + x = np.sort(data, axis=-1) + n = data.shape[-1] + cdfvals = dist.cdf(x) + u = (2*np.arange(1, n+1) - 1)/(2*n) + w = 1 / (12*n) + np.sum((u - cdfvals)**2, axis=-1) + return w + + +_compare_dict = {"ad": _anderson_darling, "ks": _kolmogorov_smirnov, + "cvm": _cramer_von_mises, "filliben": _filliben} + + +def _gof_iv(dist, data, known_params, fit_params, guessed_params, statistic, + n_mc_samples, rng): + + if not isinstance(dist, stats.rv_continuous): + message = ("`dist` must be a (non-frozen) instance of " + "`stats.rv_continuous`.") + raise TypeError(message) + + data = np.asarray(data, dtype=float) + if not data.ndim == 1: + message = "`data` must be a one-dimensional array of numbers." + raise ValueError(message) + + # Leave validation of these key/value pairs to the `fit` method, + # but collect these into dictionaries that will be used + known_params = known_params or dict() + fit_params = fit_params or dict() + guessed_params = guessed_params or dict() + + known_params_f = {("f"+key): val for key, val in known_params.items()} + fit_params_f = {("f"+key): val for key, val in fit_params.items()} + + # These are the values of parameters of the null distribution family + # with which resamples are drawn + fixed_nhd_params = known_params_f.copy() + fixed_nhd_params.update(fit_params_f) + + # These are fixed when fitting the distribution family to resamples + fixed_rfd_params = known_params_f.copy() + + # These are used as guesses when fitting the distribution family to + # the original data + guessed_nhd_params = guessed_params.copy() + + # These are used as guesses when fitting the distribution family to + # resamples + guessed_rfd_params = fit_params.copy() + guessed_rfd_params.update(guessed_params) + + if not callable(statistic): + statistic = statistic.lower() + statistics = {'ad', 'ks', 'cvm', 'filliben'} + if statistic not in statistics: + message = f"`statistic` must be one of {statistics}." + raise ValueError(message) + + n_mc_samples_int = int(n_mc_samples) + if n_mc_samples_int != n_mc_samples: + message = "`n_mc_samples` must be an integer." + raise TypeError(message) + + rng = check_random_state(rng) + + return (dist, data, fixed_nhd_params, fixed_rfd_params, guessed_nhd_params, + guessed_rfd_params, statistic, n_mc_samples_int, rng) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_hypotests.py b/venv/lib/python3.12/site-packages/scipy/stats/_hypotests.py new file mode 100644 index 0000000000000000000000000000000000000000..b905654a99a08b2a856923279aa3a112363cb6cf --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_hypotests.py @@ -0,0 +1,2201 @@ +from collections import namedtuple +from dataclasses import dataclass +import math +import numpy as np +import warnings +from itertools import combinations +import scipy.stats +from scipy.optimize import shgo +from . import distributions +from ._common import ConfidenceInterval +from ._continuous_distns import norm +from scipy._lib._array_api import (xp_capabilities, array_namespace, xp_size, + xp_promote, xp_result_type, xp_copy, is_numpy, + is_lazy_array, _count_nonmasked, is_marray, + _masked_apply) +import scipy._external.array_api_extra as xpx +from scipy.special import gamma, kv, gammaln +from scipy.fft import ifft +from ._stats_pythran import _a_ij_Aij_Dij2 +from ._stats_pythran import ( + _concordant_pairs as _P, _discordant_pairs as _Q +) +from ._axis_nan_policy import _axis_nan_policy_factory +from scipy.stats import _stats_py + +__all__ = ['epps_singleton_2samp', 'cramervonmises', 'somersd', + 'barnard_exact', 'boschloo_exact', 'cramervonmises_2samp', + 'tukey_hsd', 'poisson_means_test'] + +Epps_Singleton_2sampResult = namedtuple('Epps_Singleton_2sampResult', + ('statistic', 'pvalue')) + + +@xp_capabilities(skip_backends=[("dask.array", "lazy -> no _axis_nan_policy")]) +@_axis_nan_policy_factory(Epps_Singleton_2sampResult, n_samples=2, too_small=4) +def epps_singleton_2samp(x, y, t=(0.4, 0.8), *, axis=0): + """Compute the Epps-Singleton (ES) test statistic. + + Test the null hypothesis that two samples have the same underlying + probability distribution. + + Parameters + ---------- + x, y : array-like + The two samples of observations to be tested. Input must not have more + than one dimension. Samples can have different lengths, but both + must have at least five observations. + t : array-like, optional + The points (t1, ..., tn) where the empirical characteristic function is + to be evaluated. It should be positive distinct numbers. The default + value (0.4, 0.8) is proposed in [1]_. Input must not have more than + one dimension. + axis : int or tuple of ints, default: 0 + If an int or tuple of ints, the axis or axes of the input along which + to compute the statistic. The statistic of each axis-slice (e.g. row) + of the input will appear in a corresponding element of the output. + If ``None``, the input will be raveled before computing the statistic. + + Returns + ------- + statistic : float + The test statistic. + pvalue : float + The associated p-value based on the asymptotic chi2-distribution. + + See Also + -------- + ks_2samp, anderson_ksamp + + Notes + ----- + Testing whether two samples are generated by the same underlying + distribution is a classical question in statistics. A widely used test is + the Kolmogorov-Smirnov (KS) test which relies on the empirical + distribution function. Epps and Singleton introduce a test based on the + empirical characteristic function in [1]_. + + One advantage of the ES test compared to the KS test is that is does + not assume a continuous distribution. In [1]_, the authors conclude + that the test also has a higher power than the KS test in many + examples. They recommend the use of the ES test for discrete samples as + well as continuous samples with at least 25 observations each, whereas + `anderson_ksamp` is recommended for smaller sample sizes in the + continuous case. + + The p-value is computed from the asymptotic distribution of the test + statistic which follows a `chi2` distribution. If the sample size of both + `x` and `y` is below 25, the small sample correction proposed in [1]_ is + applied to the test statistic. + + The default values of `t` are determined in [1]_ by considering + various distributions and finding good values that lead to a high power + of the test in general. Table III in [1]_ gives the optimal values for + the distributions tested in that study. The values of `t` are scaled by + the semi-interquartile range in the implementation, see [1]_. + + References + ---------- + .. [1] T. W. Epps and K. J. Singleton, "An omnibus test for the two-sample + problem using the empirical characteristic function", Journal of + Statistical Computation and Simulation 26, p. 177--203, 1986. + + .. [2] S. J. Goerg and J. Kaiser, "Nonparametric testing of distributions + - the Epps-Singleton two-sample test using the empirical characteristic + function", The Stata Journal 9(3), p. 454--465, 2009. + + Examples + -------- + Generate random samples ``x`` and ``y``. + + >>> import numpy as np + >>> from scipy import stats + >>> rng = np.random.default_rng(66326777) + >>> x = rng.normal(0, 1, size=100) + >>> y = rng.normal(5, 3, size=100) + + Test the null hypothesis that ``x`` and ``y`` are sampled from the same + distribution. + + >>> res = stats.epps_singleton_2samp(x, y) + >>> res.statistic + np.float64(316.6441136688085) + >>> res.pvalue + np.float64(2.778946959815902e-67) + + The large value of the statistic and small p-value may be taken as evidence that + ``x`` and ``y`` were not sampled from the same distribution. + """ + xp = array_namespace(x, y) + # x and y are converted to arrays by the decorator + # and `axis` is guaranteed to be -1. + x, y = xp_promote(x, y, force_floating=True, xp=xp) + t = xp.asarray(t, dtype=x.dtype) + # check if x and y are valid inputs + nx, ny = x.shape[-1], y.shape[-1] + if (nx < 5) or (ny < 5): # only used by test_axis_nan_policy + raise ValueError('x and y should have at least 5 elements, but len(x) ' + f'= {nx} and len(y) = {ny}.') + n = nx + ny + + # check if t is valid + if t.ndim > 1: + raise ValueError(f't must be 1d, but t.ndim equals {t.ndim}.') + if is_lazy_array(t): + t = xpx.at(t)[t <= 0].set(xp.nan) + elif xp.any(t <= 0): + raise ValueError('t must contain positive elements only.') + + # Previously, non-finite input caused an error in linalg functions. + # To prevent an issue in one slice from halting the calculation, replace non-finite + # values with a harmless one, and replace results with NaN at the end. + i_x = ~xp.isfinite(x) + i_y = ~xp.isfinite(y) + # Ideally we would avoid copying all data here; see + # discussion in data-apis/array-api-extra#506. + x = xp.where(i_x, 1., x) + y = xp.where(i_y, 1., y) + invalid_result = xp.any(i_x, axis=-1) | xp.any(i_y, axis=-1) + + # rescale t with semi-iqr as proposed in [1]; import iqr here to avoid + # circular import + from scipy.stats import iqr + sigma = iqr(xp.concat((x, y), axis=-1), axis=-1, keepdims=True) / 2 + ts = xp.reshape(t, (-1,) + (1,)*x.ndim) / sigma + + # covariance estimation of ES test + gx = xp.concat((xp.cos(ts*x), xp.sin(ts*x)), axis=0) + gy = xp.concat((xp.cos(ts*y), xp.sin(ts*y)), axis=0) + gx, gy = xp.moveaxis(gx, 0, -2), xp.moveaxis(gy, 0, -2) + cov_x = xpx.cov(gx) * (nx-1)/nx # the test uses biased cov-estimate + cov_y = xpx.cov(gy) * (ny-1)/ny + cov_x, cov_y = xp.astype(cov_x, x.dtype), xp.astype(cov_y, y.dtype) + est_cov = (n/nx)*cov_x + (n/ny)*cov_y + est_cov_inv = xp.linalg.pinv(est_cov) + r = xp.asarray(xp.linalg.matrix_rank(est_cov_inv), dtype=est_cov_inv.dtype) + if not is_lazy_array(r) and xp.any(r < 2*xp_size(t)): + warnings.warn('Estimated covariance matrix does not have full rank. ' + 'This indicates a bad choice of the input t and the ' + 'test might not be consistent.', # see p. 183 in [1]_ + stacklevel=2) + + # compute test statistic w distributed asympt. as chisquare with df=r + g_diff = xp.mean(gx, axis=-1, keepdims=True) - xp.mean(gy, axis=-1, keepdims=True) + w = n*xp.matmul(xp.matrix_transpose(g_diff), xp.matmul(est_cov_inv, g_diff)) + w = w[..., 0, 0] + + # apply small-sample correction + if (max(nx, ny) < 25): + corr = 1.0/(1.0 + n**(-0.45) + 10.1*(nx**(-1.7) + ny**(-1.7))) + w *= corr + + chi2 = _stats_py._SimpleChi2(r) + p = _stats_py._get_pvalue(w, chi2, alternative='greater', symmetric=False, xp=xp) + + w = xpx.at(w)[invalid_result].set(xp.nan) + p = xpx.at(p)[invalid_result].set(xp.nan) + w = w[()] if w.ndim == 0 else w + p = p[()] if p.ndim == 0 else p + return Epps_Singleton_2sampResult(w, p) + + +@xp_capabilities(np_only=True) +def poisson_means_test(k1, n1, k2, n2, *, diff=0, alternative='two-sided'): + r""" + Performs the Poisson means test, AKA the "E-test". + + This is a test of the null hypothesis that the difference between means of + two Poisson distributions is `diff`. The samples are provided as the + number of events `k1` and `k2` observed within measurement intervals + (e.g. of time, space, number of observations) of sizes `n1` and `n2`. + + Parameters + ---------- + k1 : int + Number of events observed from distribution 1. + n1 : float + Size of sample from distribution 1. + k2 : int + Number of events observed from distribution 2. + n2 : float + Size of sample from distribution 2. + diff : float, default=0 + The hypothesized difference in means between the distributions + underlying the samples. + alternative : {'two-sided', 'less', 'greater'}, optional + Defines the alternative hypothesis. + The following options are available (default is 'two-sided'): + + * 'two-sided': the difference between distribution means is not + equal to `diff` + * 'less': the difference between distribution means is less than + `diff` + * 'greater': the difference between distribution means is greater + than `diff` + + Returns + ------- + statistic : float + The test statistic (see [1]_ equation 3.3). + pvalue : float + The probability of achieving such an extreme value of the test + statistic under the null hypothesis. + + Notes + ----- + + Let: + + .. math:: X_1 \sim \mbox{Poisson}(\mathtt{n1}\lambda_1) + + be a random variable independent of + + .. math:: X_2 \sim \mbox{Poisson}(\mathtt{n2}\lambda_2) + + and let ``k1`` and ``k2`` be the observed values of :math:`X_1` + and :math:`X_2`, respectively. Then `poisson_means_test` uses the number + of observed events ``k1`` and ``k2`` from samples of size ``n1`` and + ``n2``, respectively, to test the null hypothesis that + + .. math:: + H_0: \lambda_1 - \lambda_2 = \mathtt{diff} + + A benefit of the E-test is that it has good power for small sample sizes, + which can reduce sampling costs [1]_. It has been evaluated and determined + to be more powerful than the comparable C-test, sometimes referred to as + the Poisson exact test. + + References + ---------- + .. [1] Krishnamoorthy, K., & Thomson, J. (2004). A more powerful test for + comparing two Poisson means. Journal of Statistical Planning and + Inference, 119(1), 23-35. + + .. [2] Przyborowski, J., & Wilenski, H. (1940). Homogeneity of results in + testing samples from Poisson series: With an application to testing + clover seed for dodder. Biometrika, 31(3/4), 313-323. + + Examples + -------- + + Suppose that a gardener wishes to test the number of dodder (weed) seeds + in a sack of clover seeds that they buy from a seed company. It has + previously been established that the number of dodder seeds in clover + follows the Poisson distribution. + + A 100 gram sample is drawn from the sack before being shipped to the + gardener. The sample is analyzed, and it is found to contain no dodder + seeds; that is, `k1` is 0. However, upon arrival, the gardener draws + another 100 gram sample from the sack. This time, three dodder seeds are + found in the sample; that is, `k2` is 3. The gardener would like to + know if the difference is significant and not due to chance. The + null hypothesis is that the difference between the two samples is merely + due to chance, or that :math:`\lambda_1 - \lambda_2 = \mathtt{diff}` + where :math:`\mathtt{diff} = 0`. The alternative hypothesis is that the + difference is not due to chance, or :math:`\lambda_1 - \lambda_2 \ne 0`. + The gardener selects a significance level of 5% to reject the null + hypothesis in favor of the alternative [2]_. + + >>> import scipy.stats as stats + >>> res = stats.poisson_means_test(0, 100, 3, 100) + >>> res.statistic, res.pvalue + (-1.7320508075688772, 0.08837900929018157) + + The p-value is .088, indicating a near 9% chance of observing a value of + the test statistic under the null hypothesis. This exceeds 5%, so the + gardener does not reject the null hypothesis as the difference cannot be + regarded as significant at this level. + """ + + _poisson_means_test_iv(k1, n1, k2, n2, diff, alternative) + + # "for a given k_1 and k_2, an estimate of \lambda_2 is given by" [1] (3.4) + lmbd_hat2 = ((k1 + k2) / (n1 + n2) - diff * n1 / (n1 + n2)) + + # "\hat{\lambda_{2k}} may be less than or equal to zero ... and in this + # case the null hypothesis cannot be rejected ... [and] it is not necessary + # to compute the p-value". [1] page 26 below eq. (3.6). + if lmbd_hat2 <= 0: + return _stats_py.SignificanceResult(0, 1) + + # The unbiased variance estimate [1] (3.2) + var = k1 / (n1 ** 2) + k2 / (n2 ** 2) + + # The _observed_ pivot statistic from the input. It follows the + # unnumbered equation following equation (3.3) This is used later in + # comparison with the computed pivot statistics in an indicator function. + t_k1k2 = (k1 / n1 - k2 / n2 - diff) / np.sqrt(var) + + # Equation (3.5) of [1] is lengthy, so it is broken into several parts, + # beginning here. Note that the probability mass function of poisson is + # exp^(-\mu)*\mu^k/k!, so and this is called with shape \mu, here noted + # here as nlmbd_hat*. The strategy for evaluating the double summation in + # (3.5) is to create two arrays of the values of the two products inside + # the summation and then broadcast them together into a matrix, and then + # sum across the entire matrix. + + # Compute constants (as seen in the first and second separated products in + # (3.5).). (This is the shape (\mu) parameter of the poisson distribution.) + nlmbd_hat1 = n1 * (lmbd_hat2 + diff) + nlmbd_hat2 = n2 * lmbd_hat2 + + # Determine summation bounds for tail ends of distribution rather than + # summing to infinity. `x1*` is for the outer sum and `x2*` is the inner + # sum. + x1_lb, x1_ub = distributions.poisson.ppf([1e-10, 1 - 1e-16], nlmbd_hat1) + x2_lb, x2_ub = distributions.poisson.ppf([1e-10, 1 - 1e-16], nlmbd_hat2) + + # Construct arrays to function as the x_1 and x_2 counters on the summation + # in (3.5). `x1` is in columns and `x2` is in rows to allow for + # broadcasting. + x1 = np.arange(x1_lb, x1_ub + 1) + x2 = np.arange(x2_lb, x2_ub + 1)[:, None] + + # These are the two products in equation (3.5) with `prob_x1` being the + # first (left side) and `prob_x2` being the second (right side). (To + # make as clear as possible: the 1st contains a "+ d" term, the 2nd does + # not.) + prob_x1 = distributions.poisson.pmf(x1, nlmbd_hat1) + prob_x2 = distributions.poisson.pmf(x2, nlmbd_hat2) + + # compute constants for use in the "pivot statistic" per the + # unnumbered equation following (3.3). + lmbd_x1 = x1 / n1 + lmbd_x2 = x2 / n2 + lmbds_diff = lmbd_x1 - lmbd_x2 - diff + var_x1x2 = lmbd_x1 / n1 + lmbd_x2 / n2 + + # This is the 'pivot statistic' for use in the indicator of the summation + # (left side of "I[.]"). + with np.errstate(invalid='ignore', divide='ignore'): + t_x1x2 = lmbds_diff / np.sqrt(var_x1x2) + + # `[indicator]` implements the "I[.] ... the indicator function" per + # the paragraph following equation (3.5). + if alternative == 'two-sided': + indicator = np.abs(t_x1x2) >= np.abs(t_k1k2) + elif alternative == 'less': + indicator = t_x1x2 <= t_k1k2 + else: + indicator = t_x1x2 >= t_k1k2 + + # Multiply all combinations of the products together, exclude terms + # based on the `indicator` and then sum. (3.5) + pvalue = np.sum((prob_x1 * prob_x2)[indicator]) + return _stats_py.SignificanceResult(t_k1k2, pvalue) + + +def _poisson_means_test_iv(k1, n1, k2, n2, diff, alternative): + # """check for valid types and values of input to `poisson_mean_test`.""" + if k1 != int(k1) or k2 != int(k2): + raise TypeError('`k1` and `k2` must be integers.') + + count_err = '`k1` and `k2` must be greater than or equal to 0.' + if k1 < 0 or k2 < 0: + raise ValueError(count_err) + + if n1 <= 0 or n2 <= 0: + raise ValueError('`n1` and `n2` must be greater than 0.') + + if diff < 0: + raise ValueError('diff must be greater than or equal to 0.') + + alternatives = {'two-sided', 'less', 'greater'} + if alternative.lower() not in alternatives: + raise ValueError(f"Alternative must be one of '{alternatives}'.") + + +class CramerVonMisesResult: + def __init__(self, statistic, pvalue): + self.statistic = statistic + self.pvalue = pvalue + + def __repr__(self): + return (f"{self.__class__.__name__}(statistic={self.statistic}, " + f"pvalue={self.pvalue})") + + +def _psi1_mod(x, *, xp=None): + """ + psi1 is defined in equation 1.10 in Csörgő, S. and Faraway, J. (1996). + This implements a modified version by excluding the term V(x) / 12 + (here: _cdf_cvm_inf(x) / 12) to avoid evaluating _cdf_cvm_inf(x) + twice in _cdf_cvm. + + Implementation based on MAPLE code of Julian Faraway and R code of the + function pCvM in the package goftest (v1.1.1), permission granted + by Adrian Baddeley. Main difference in the implementation: the code + here keeps adding terms of the series until the terms are small enough. + """ + xp = array_namespace(x) if xp is None else xp + + def _ed2(y): + z = y**2 / 4 + z_ = np.asarray(z) + b = xp.asarray(kv(1/4, z_) + kv(3/4, z_)) + return xp.exp(-z) * (y/2)**(3/2) * b / math.sqrt(np.pi) + + def _ed3(y): + z = y**2 / 4 + z_ = np.asarray(z) + c = xp.exp(-z) / math.sqrt(np.pi) + kv_terms = xp.asarray(2*kv(1/4, z_) + + 3*kv(3/4, z_) - kv(5/4, z_)) + return c * (y/2)**(5/2) * kv_terms + + def _Ak(k, x): + m = 2*k + 1 + sx = 2 * xp.sqrt(x) + y1 = x**(3/4) + y2 = x**(5/4) + + gamma_kp1_2 = float(gamma(k + 1 / 2)) + gamma_kp3_2 = float(gamma(k + 3 / 2)) + + e1 = m * gamma_kp1_2 * _ed2((4 * k + 3)/sx) / (9 * y1) + e2 = gamma_kp1_2 * _ed3((4 * k + 1) / sx) / (72 * y2) + e3 = 2 * (m + 2) * gamma_kp3_2 * _ed3((4 * k + 5) / sx) / (12 * y2) + e4 = 7 * m * gamma_kp1_2 * _ed2((4 * k + 1) / sx) / (144 * y1) + e5 = 7 * m * gamma_kp1_2 * _ed2((4 * k + 5) / sx) / (144 * y1) + + return e1 + e2 + e3 + e4 + e5 + + x = xp.asarray(x) + tot = xp.zeros_like(x) + cond = xp.ones_like(x, dtype=xp.bool) + k = 0 + while xp.any(cond): + gamma_kp1 = float(gamma(k + 1)) + z = -_Ak(k, x[cond]) / (xp.pi * gamma_kp1) + tot = xpx.at(tot)[cond].set(tot[cond] + z) + # For float32 arithmetic, the tolerance may need to be adjusted or the + # algorithm may prove to be unsuitable. + cond = xpx.at(cond)[xp_copy(cond)].set(xp.abs(z) >= 1e-7) + k += 1 + + return tot + + +def _cdf_cvm_inf(x, *, xp=None): + """ + Calculate the cdf of the Cramér-von Mises statistic (infinite sample size). + + See equation 1.2 in Csörgő, S. and Faraway, J. (1996). + + Implementation based on MAPLE code of Julian Faraway and R code of the + function pCvM in the package goftest (v1.1.1), permission granted + by Adrian Baddeley. Main difference in the implementation: the code + here keeps adding terms of the series until the terms are small enough. + + The function is not expected to be accurate for large values of x, say + x > 4, when the cdf is very close to 1. + """ + xp = array_namespace(x) if xp is None else xp + x = xp.asarray(x) + + def term(x, k): + # this expression can be found in [2], second line of (1.3) + u = math.exp(gammaln(k + 0.5) - gammaln(k+1)) / (xp.pi**1.5 * xp.sqrt(x)) + y = 4*k + 1 + q = y**2 / (16*x) + b = xp.asarray(kv(0.25, np.asarray(q)), dtype=u.dtype) # not automatic? + return u * math.sqrt(y) * xp.exp(-q) * b + + tot = xp.zeros_like(x, dtype=x.dtype) + cond = xp.ones_like(x, dtype=xp.bool) + k = 0 + while xp.any(cond): + z = term(x[cond], k) + # tot[cond] = tot[cond] + z + tot = xpx.at(tot)[cond].add(z) + # cond[cond] = np.abs(z) >= 1e-7 + cond = xpx.at(cond)[xp_copy(cond)].set(xp.abs(z) >= 1e-7) # torch needs copy + k += 1 + + return tot + + +def _cdf_cvm(x, n=None, *, xp=None): + """ + Calculate the cdf of the Cramér-von Mises statistic for a finite sample + size n. If N is None, use the asymptotic cdf (n=inf). + + See equation 1.8 in Csörgő, S. and Faraway, J. (1996) for finite samples, + 1.2 for the asymptotic cdf. + + The function is not expected to be accurate for large values of x, say + x > 2, when the cdf is very close to 1 and it might return values > 1 + in that case, e.g. _cdf_cvm(2.0, 12) = 1.0000027556716846. Moreover, it + is not accurate for small values of n, especially close to the bounds of + the distribution's domain, [1/(12*n), n/3], where the value jumps to 0 + and 1, respectively. These are limitations of the approximation by Csörgő + and Faraway (1996) implemented in this function. + """ + xp = array_namespace(x) if xp is None else xp + x = xp.asarray(x) + + if n is None: + y = _cdf_cvm_inf(x, xp=xp) + else: + # support of the test statistic is [12/n, n/3], see 1.1 in [2] + y = xp.zeros_like(x, dtype=x.dtype) + sup = (1./(12*n) < x) & (x < n/3.) + # note: _psi1_mod does not include the term _cdf_cvm_inf(x) / 12 + # therefore, we need to add it here + y = xpx.at(y)[sup].set(_cdf_cvm_inf(x[sup], xp=xp) * (1 + 1./(12*n)) + + _psi1_mod(x[sup], xp=xp) / n) + y = xpx.at(y)[x >= n/3].set(1.) + + return y[()] if y.ndim == 0 else y + + +def _cvm_result_to_tuple(res, _): + return res.statistic, res.pvalue + + +@xp_capabilities(cpu_only=True, # needs special function `kv` + skip_backends=[('dask.array', 'typical dask issues')], + jax_jit=False, # array boolean indices must be concrete + marray=True) +@_axis_nan_policy_factory(CramerVonMisesResult, n_samples=1, too_small=1, + result_to_tuple=_cvm_result_to_tuple) +def cramervonmises(rvs, cdf, args=(), *, axis=0): + r"""Perform the one-sample Cramér-von Mises test for goodness of fit. + + This performs a test of the goodness of fit of a cumulative distribution + function (cdf) :math:`F` compared to the empirical distribution function + :math:`F_n` of observed random variates :math:`X_1, ..., X_n` that are + assumed to be independent and identically distributed ([1]_). + The null hypothesis is that the :math:`X_i` have cumulative distribution + :math:`F`. + + The test statistic :math:`T` is defined as in [1]_, where :math:`\omega^2` + is the Cramér-von Mises criterion and :math:`x_i` are the observed values. + + .. math:: + T = n\omega^2 = + \frac{1}{12n} + \sum_{i=1}^n \left[ \frac{2i-1}{2n} - F(x_i) \right]^2 + + Parameters + ---------- + rvs : array_like + A 1-D array of observed values of the random variables :math:`X_i`. + The sample must contain at least two observations. + cdf : str or callable + The cumulative distribution function :math:`F` to test the + observations against. If a string, it should be the name of a + distribution in `scipy.stats`. If a callable, that callable is used + to calculate the cdf: ``cdf(x, *args) -> float``. + args : tuple, optional + Distribution parameters. These are assumed to be known; see Notes. + axis : int or tuple of ints, default: 0 + If an int or tuple of ints, the axis or axes of the input along which + to compute the statistic. The statistic of each axis-slice (e.g. row) + of the input will appear in a corresponding element of the output. + If ``None``, the input will be raveled before computing the statistic. + + Returns + ------- + res : object with attributes + statistic : float + Cramér-von Mises statistic :math:`T`. + pvalue : float + The p-value. + + See Also + -------- + kstest, cramervonmises_2samp + + Notes + ----- + .. versionadded:: 1.6.0 + + The p-value relies on the approximation given by equation 1.8 in [2]_. + It is important to keep in mind that the p-value is only accurate if + one tests a simple hypothesis, i.e. the parameters of the reference + distribution are known. If the parameters are estimated from the data + (composite hypothesis), the computed p-value is not reliable. + + References + ---------- + .. [1] Cramér-von Mises criterion, Wikipedia, + https://en.wikipedia.org/wiki/Cram%C3%A9r%E2%80%93von_Mises_criterion + .. [2] Csörgő, S. and Faraway, J. (1996). The Exact and Asymptotic + Distribution of Cramér-von Mises Statistics. Journal of the + Royal Statistical Society, pp. 221-234. + + Examples + -------- + + Suppose we wish to test whether data generated by ``scipy.stats.norm.rvs`` + were, in fact, drawn from the standard normal distribution. We choose a + significance level of ``alpha=0.05``. + + >>> import numpy as np + >>> from scipy import stats + >>> rng = np.random.default_rng(165417232101553420507139617764912913465) + >>> x = stats.norm.rvs(size=500, random_state=rng) + >>> res = stats.cramervonmises(x, 'norm') + >>> res.statistic, res.pvalue + (0.1072085112565724, 0.5508482238203407) + + The p-value exceeds our chosen significance level, so we do not + reject the null hypothesis that the observed sample is drawn from the + standard normal distribution. + + Now suppose we wish to check whether the same samples shifted by 2.1 is + consistent with being drawn from a normal distribution with a mean of 2. + + >>> y = x + 2.1 + >>> res = stats.cramervonmises(y, 'norm', args=(2,)) + >>> res.statistic, res.pvalue + (0.8364446265294695, 0.00596286797008283) + + Here we have used the `args` keyword to specify the mean (``loc``) + of the normal distribution to test the data against. This is equivalent + to the following, in which we create a frozen normal distribution with + mean 2.1, then pass its ``cdf`` method as an argument. + + >>> frozen_dist = stats.norm(loc=2) + >>> res = stats.cramervonmises(y, frozen_dist.cdf) + >>> res.statistic, res.pvalue + (0.8364446265294695, 0.00596286797008283) + + In either case, we would reject the null hypothesis that the observed + sample is drawn from a normal distribution with a mean of 2 (and default + variance of 1) because the p-value is less than our chosen + significance level. + + """ + # `_axis_nan_policy` decorator ensures `axis=-1` + xp = array_namespace(rvs) + + if isinstance(cdf, str) and is_numpy(xp): + cdf = getattr(distributions, cdf).cdf + elif isinstance(cdf, str): + message = "`cdf` must be a callable if `rvs` is a non-NumPy array." + raise ValueError(message) + + n_length = rvs.shape[-1] + if n_length <= 1: # only needed for `test_axis_nan_policy.py`; not user-facing + raise ValueError('The sample must contain at least two observations.') + + rvs = xp_promote(rvs, force_floating=True, xp=xp) + vals = xp.sort(rvs, axis=-1) + cdfvals = _masked_apply(cdf, args=(vals, *args), xp=xp) + n_count = xp.asarray(_count_nonmasked(rvs, axis=-1, xp=xp), dtype=rvs.dtype) + + u = (2*xp.arange(1, n_length+1, dtype=rvs.dtype) - 1)/(2*n_count[..., xp.newaxis]) + w = 1/(12*n_count) + xp.sum((u - cdfvals)**2, axis=-1) + + # avoid small negative values that can occur due to the approximation + p = xp.clip(1. - _masked_apply(_cdf_cvm, args=(w, n_count), xp=xp), 0., None) + + return CramerVonMisesResult(statistic=w, pvalue=p) + + +def _get_wilcoxon_distr(n): + """ + Distribution of probability of the Wilcoxon ranksum statistic r_plus (sum + of ranks of positive differences). + Returns an array with the probabilities of all the possible ranks + r = 0, ..., n*(n+1)/2 + """ + c = np.ones(1, dtype=np.float64) + for k in range(1, n + 1): + prev_c = c + c = np.zeros(k * (k + 1) // 2 + 1, dtype=np.float64) + m = len(prev_c) + c[:m] = prev_c * 0.5 + c[-m:] += prev_c * 0.5 + return c + + +def _get_wilcoxon_distr2(n): + """ + Distribution of probability of the Wilcoxon ranksum statistic r_plus (sum + of ranks of positive differences). + Returns an array with the probabilities of all the possible ranks + r = 0, ..., n*(n+1)/2 + This is a slower reference function + References + ---------- + .. [1] 1. Harris T, Hardin JW. Exact Wilcoxon Signed-Rank and Wilcoxon + Mann-Whitney Ranksum Tests. The Stata Journal. 2013;13(2):337-343. + """ + ai = np.arange(1, n+1)[:, None] + t = n*(n+1)/2 + q = 2*t + j = np.arange(q) + theta = 2*np.pi/q*j + phi_sp = np.prod(np.cos(theta*ai), axis=0) + phi_s = np.exp(1j*theta*t) * phi_sp + p = np.real(ifft(phi_s)) + res = np.zeros(int(t)+1) + res[:-1:] = p[::2] + res[0] /= 2 + res[-1] = res[0] + return res + + +def _tau_b(A): + """Calculate Kendall's tau-b and p-value from contingency table.""" + # See [2] 2.2 and 4.2 + + # contingency table must be truly 2D + if A.shape[0] == 1 or A.shape[1] == 1: + return np.nan, np.nan + + NA = A.sum() + PA = _P(A) + QA = _Q(A) + Sri2 = (A.sum(axis=1)**2).sum() + Scj2 = (A.sum(axis=0)**2).sum() + denominator = (NA**2 - Sri2)*(NA**2 - Scj2) + + tau = (PA-QA)/(denominator)**0.5 + + numerator = 4*(_a_ij_Aij_Dij2(A) - (PA - QA)**2 / NA) + s02_tau_b = numerator/denominator + if s02_tau_b == 0: # Avoid divide by zero + return tau, 0 + Z = tau/s02_tau_b**0.5 + p = 2*norm.sf(abs(Z)) # 2-sided p-value + + return tau, p + + +def _somers_d(A, alternative='two-sided'): + """Calculate Somers' D and p-value from contingency table.""" + # See [3] page 1740 + + # contingency table must be truly 2D + if A.shape[0] <= 1 or A.shape[1] <= 1: + return np.nan, np.nan + + NA = A.sum() + NA2 = NA**2 + PA = _P(A) + QA = _Q(A) + Sri2 = (A.sum(axis=1)**2).sum() + + d = (PA - QA)/(NA2 - Sri2) + + S = _a_ij_Aij_Dij2(A) - (PA-QA)**2/NA + + with np.errstate(divide='ignore'): + Z = (PA - QA)/(4*(S))**0.5 + + norm = _stats_py._SimpleNormal() + p = _stats_py._get_pvalue(Z, norm, alternative, xp=np) + + return d, p + + +@dataclass +class SomersDResult: + statistic: float + pvalue: float + table: np.ndarray + + +@xp_capabilities(np_only=True) +def somersd(x, y=None, alternative='two-sided'): + r"""Calculates Somers' D, an asymmetric measure of ordinal association. + + Like Kendall's :math:`\tau`, Somers' :math:`D` is a measure of the + correspondence between two rankings. Both statistics consider the + difference between the number of concordant and discordant pairs in two + rankings :math:`X` and :math:`Y`, and both are normalized such that values + close to 1 indicate strong agreement and values close to -1 indicate + strong disagreement. They differ in how they are normalized. To show the + relationship, Somers' :math:`D` can be defined in terms of Kendall's + :math:`\tau_a`: + + .. math:: + D(Y|X) = \frac{\tau_a(X, Y)}{\tau_a(X, X)} + + Suppose the first ranking :math:`X` has :math:`r` distinct ranks and the + second ranking :math:`Y` has :math:`s` distinct ranks. These two lists of + :math:`n` rankings can also be viewed as an :math:`r \times s` contingency + table in which element :math:`i, j` is the number of rank pairs with rank + :math:`i` in ranking :math:`X` and rank :math:`j` in ranking :math:`Y`. + Accordingly, `somersd` also allows the input data to be supplied as a + single, 2D contingency table instead of as two separate, 1D rankings. + + Note that the definition of Somers' :math:`D` is asymmetric: in general, + :math:`D(Y|X) \neq D(X|Y)`. ``somersd(x, y)`` calculates Somers' + :math:`D(Y|X)`: the "row" variable :math:`X` is treated as an independent + variable, and the "column" variable :math:`Y` is dependent. For Somers' + :math:`D(X|Y)`, swap the input lists or transpose the input table. + + Parameters + ---------- + x : array_like + 1D array of rankings, treated as the (row) independent variable. + Alternatively, a 2D contingency table. + y : array_like, optional + If `x` is a 1D array of rankings, `y` is a 1D array of rankings of the + same length, treated as the (column) dependent variable. + If `x` is 2D, `y` is ignored. + alternative : {'two-sided', 'less', 'greater'}, optional + Defines the alternative hypothesis. Default is 'two-sided'. + The following options are available: + + * 'two-sided': the rank correlation is nonzero + * 'less': the rank correlation is negative (less than zero) + * 'greater': the rank correlation is positive (greater than zero) + + Returns + ------- + res : SomersDResult + A `SomersDResult` object with the following fields: + + statistic : float + The Somers' :math:`D` statistic. + pvalue : float + The p-value for a hypothesis test whose null + hypothesis is an absence of association, :math:`D=0`. + See notes for more information. + table : 2D array + The contingency table formed from rankings `x` and `y` (or the + provided contingency table, if `x` is a 2D array) + + See Also + -------- + kendalltau : Calculates Kendall's tau, another correlation measure. + weightedtau : Computes a weighted version of Kendall's tau. + spearmanr : Calculates a Spearman rank-order correlation coefficient. + pearsonr : Calculates a Pearson correlation coefficient. + + Notes + ----- + This function follows the contingency table approach of [2]_ and + [3]_. *p*-values are computed based on an asymptotic approximation of + the test statistic distribution under the null hypothesis :math:`D=0`. + + Theoretically, hypothesis tests based on Kendall's :math:`tau` and Somers' + :math:`D` should be identical. + However, the *p*-values returned by `kendalltau` are based + on the null hypothesis of *independence* between :math:`X` and :math:`Y` + (i.e. the population from which pairs in :math:`X` and :math:`Y` are + sampled contains equal numbers of all possible pairs), which is more + specific than the null hypothesis :math:`D=0` used here. If the null + hypothesis of independence is desired, it is acceptable to use the + *p*-value returned by `kendalltau` with the statistic returned by + `somersd` and vice versa. For more information, see [2]_. + + Contingency tables are formatted according to the convention used by + SAS and R: the first ranking supplied (``x``) is the "row" variable, and + the second ranking supplied (``y``) is the "column" variable. This is + opposite the convention of Somers' original paper [1]_. + + References + ---------- + .. [1] Robert H. Somers, "A New Asymmetric Measure of Association for + Ordinal Variables", *American Sociological Review*, Vol. 27, No. 6, + pp. 799--811, 1962. + + .. [2] Morton B. Brown and Jacqueline K. Benedetti, "Sampling Behavior of + Tests for Correlation in Two-Way Contingency Tables", *Journal of + the American Statistical Association* Vol. 72, No. 358, pp. + 309--315, 1977. + + .. [3] SAS Institute, Inc., "The FREQ Procedure (Book Excerpt)", + *SAS/STAT 9.2 User's Guide, Second Edition*, SAS Publishing, 2009. + + .. [4] Laerd Statistics, "Somers' d using SPSS Statistics", *SPSS + Statistics Tutorials and Statistical Guides*, + https://statistics.laerd.com/spss-tutorials/somers-d-using-spss-statistics.php, + Accessed July 31, 2020. + + Examples + -------- + We calculate Somers' D for the example given in [4]_, in which a hotel + chain owner seeks to determine the association between hotel room + cleanliness and customer satisfaction. The independent variable, hotel + room cleanliness, is ranked on an ordinal scale: "below average (1)", + "average (2)", or "above average (3)". The dependent variable, customer + satisfaction, is ranked on a second scale: "very dissatisfied (1)", + "moderately dissatisfied (2)", "neither dissatisfied nor satisfied (3)", + "moderately satisfied (4)", or "very satisfied (5)". 189 customers + respond to the survey, and the results are cast into a contingency table + with the hotel room cleanliness as the "row" variable and customer + satisfaction as the "column" variable. + + +-----+-----+-----+-----+-----+-----+ + | | (1) | (2) | (3) | (4) | (5) | + +=====+=====+=====+=====+=====+=====+ + | (1) | 27 | 25 | 14 | 7 | 0 | + +-----+-----+-----+-----+-----+-----+ + | (2) | 7 | 14 | 18 | 35 | 12 | + +-----+-----+-----+-----+-----+-----+ + | (3) | 1 | 3 | 2 | 7 | 17 | + +-----+-----+-----+-----+-----+-----+ + + For example, 27 customers assigned their room a cleanliness ranking of + "below average (1)" and a corresponding satisfaction of "very + dissatisfied (1)". We perform the analysis as follows. + + >>> from scipy.stats import somersd + >>> table = [[27, 25, 14, 7, 0], [7, 14, 18, 35, 12], [1, 3, 2, 7, 17]] + >>> res = somersd(table) + >>> res.statistic + 0.6032766111513396 + >>> res.pvalue + 1.0007091191074533e-27 + + The value of the Somers' D statistic is approximately 0.6, indicating + a positive correlation between room cleanliness and customer satisfaction + in the sample. + The *p*-value is very small, indicating a very small probability of + observing such an extreme value of the statistic under the null + hypothesis that the statistic of the entire population (from which + our sample of 189 customers is drawn) is zero. This supports the + alternative hypothesis that the true value of Somers' D for the population + is nonzero. + + """ + x, y = np.array(x), np.array(y) + if x.ndim == 1: + if x.size != y.size: + raise ValueError("Rankings must be of equal length.") + table = scipy.stats.contingency.crosstab(x, y)[1] + elif x.ndim == 2: + if np.any(x < 0): + raise ValueError("All elements of the contingency table must be " + "non-negative.") + if np.any(x != x.astype(int)): + raise ValueError("All elements of the contingency table must be " + "integer.") + if x.nonzero()[0].size < 2: + raise ValueError("At least two elements of the contingency table " + "must be nonzero.") + table = x + else: + raise ValueError("x must be either a 1D or 2D array") + # The table type is converted to a float to avoid an integer overflow + d, p = _somers_d(table.astype(float), alternative) + + # add alias for consistency with other correlation functions + res = SomersDResult(d, p, table) + res.correlation = d + return res + + +# This could be combined with `_all_partitions` in `_resampling.py` +def _all_partitions(nx, ny): + """ + Partition a set of indices into two fixed-length sets in all possible ways + + Partition a set of indices 0 ... nx + ny - 1 into two sets of length nx and + ny in all possible ways (ignoring order of elements). + """ + z = np.arange(nx+ny) + for c in combinations(z, nx): + x = np.array(c) + mask = np.ones(nx+ny, bool) + mask[x] = False + y = z[mask] + yield x, y + + +def _compute_log_combinations(n): + """Compute all log combination of C(n, k).""" + gammaln_arr = gammaln(np.arange(n + 1) + 1) + return gammaln(n + 1) - gammaln_arr - gammaln_arr[::-1] + + +@dataclass +class BarnardExactResult: + statistic: float + pvalue: float + + +@xp_capabilities(np_only=True) +def barnard_exact(table, alternative="two-sided", pooled=True, n=32): + r"""Perform a Barnard exact test on a 2x2 contingency table. + + Parameters + ---------- + table : array_like of ints + A 2x2 contingency table. Elements should be non-negative integers. + + alternative : {'two-sided', 'less', 'greater'}, optional + Defines the null and alternative hypotheses. Default is 'two-sided'. + Please see explanations in the Notes section below. + + pooled : bool, optional + Whether to compute score statistic with pooled variance (as in + Student's t-test, for example) or unpooled variance (as in Welch's + t-test). Default is ``True``. + + n : int, optional + Number of sampling points used in the construction of the sampling + method. Note that this argument will automatically be converted to + the next higher power of 2 since `scipy.stats.qmc.Sobol` is used to + select sample points. Default is 32. Must be positive. In most cases, + 32 points is enough to reach good precision. More points comes at + performance cost. + + Returns + ------- + ber : BarnardExactResult + A result object with the following attributes. + + statistic : float + The Wald statistic with pooled or unpooled variance, depending + on the user choice of `pooled`. + + pvalue : float + P-value, the probability of obtaining a distribution at least as + extreme as the one that was actually observed, assuming that the + null hypothesis is true. + + See Also + -------- + chi2_contingency : Chi-square test of independence of variables in a + contingency table. + fisher_exact : Fisher exact test on a 2x2 contingency table. + boschloo_exact : Boschloo's exact test on a 2x2 contingency table, + which is a uniformly more powerful alternative to Fisher's exact test. + + Notes + ----- + Barnard's test is an exact test used in the analysis of contingency + tables. It examines the association of two categorical variables, and + is a more powerful alternative than Fisher's exact test + for 2x2 contingency tables. + + Let's define :math:`X_0` a 2x2 matrix representing the observed sample, + where each column stores the binomial experiment, as in the example + below. Let's also define :math:`p_1, p_2` the theoretical binomial + probabilities for :math:`x_{11}` and :math:`x_{12}`. When using + Barnard exact test, we can assert three different null hypotheses : + + - :math:`H_0 : p_1 \geq p_2` versus :math:`H_1 : p_1 < p_2`, + with `alternative` = "less" + + - :math:`H_0 : p_1 \leq p_2` versus :math:`H_1 : p_1 > p_2`, + with `alternative` = "greater" + + - :math:`H_0 : p_1 = p_2` versus :math:`H_1 : p_1 \neq p_2`, + with `alternative` = "two-sided" (default one) + + In order to compute Barnard's exact test, we are using the Wald + statistic [3]_ with pooled or unpooled variance. + Under the default assumption that both variances are equal + (``pooled = True``), the statistic is computed as: + + .. math:: + + T(X) = \frac{ + \hat{p}_1 - \hat{p}_2 + }{ + \sqrt{ + \hat{p}(1 - \hat{p}) + (\frac{1}{c_1} + + \frac{1}{c_2}) + } + } + + with :math:`\hat{p}_1, \hat{p}_2` and :math:`\hat{p}` the estimator of + :math:`p_1, p_2` and :math:`p`, the latter being the combined probability, + given the assumption that :math:`p_1 = p_2`. + + If this assumption is invalid (``pooled = False``), the statistic is: + + .. math:: + + T(X) = \frac{ + \hat{p}_1 - \hat{p}_2 + }{ + \sqrt{ + \frac{\hat{p}_1 (1 - \hat{p}_1)}{c_1} + + \frac{\hat{p}_2 (1 - \hat{p}_2)}{c_2} + } + } + + The p-value is then computed as: + + .. math:: + + \sum + \binom{c_1}{x_{11}} + \binom{c_2}{x_{12}} + \pi^{x_{11} + x_{12}} + (1 - \pi)^{t - x_{11} - x_{12}} + + where the sum is over all 2x2 contingency tables :math:`X` such that: + + * :math:`T(X) \leq T(X_0)` when `alternative` = "less", + * :math:`T(X) \geq T(X_0)` when `alternative` = "greater", or + * :math:`T(X) \geq |T(X_0)|` when `alternative` = "two-sided". + + Above, :math:`c_1, c_2` are the sum of the columns 1 and 2, + and :math:`t` the total (sum of the 4 sample's element). + + The returned p-value is the maximum p-value taken over the nuisance + parameter :math:`\pi`, where :math:`0 \leq \pi \leq 1`. + + This function's complexity is :math:`O(n c_1 c_2)`, where `n` is the + number of sample points. + + References + ---------- + .. [1] Barnard, G. A. "Significance Tests for 2x2 Tables". *Biometrika*. + 34.1/2 (1947): 123-138. :doi:`10.2307/2332517`. + + .. [2] Mehta, Cyrus R., and Pralay Senchaudhuri. "Conditional versus + unconditional exact tests for comparing two binomials." + *Cytel Software Corporation* 675 (2003): 1-5. + + .. [3] "Wald Test". *Wikipedia*. https://en.wikipedia.org/wiki/Wald_test + + Examples + -------- + An example use of Barnard's test is presented in [2]_. + + Consider the following example of a vaccine efficacy study + (Chan, 1998). In a randomized clinical trial of 30 subjects, 15 were + inoculated with a recombinant DNA influenza vaccine and the 15 were + inoculated with a placebo. Twelve of the 15 subjects in the placebo + group (80%) eventually became infected with influenza whereas for the + vaccine group, only 7 of the 15 subjects (47%) became infected. The + data are tabulated as a 2 x 2 table:: + + Vaccine Placebo + Yes 7 12 + No 8 3 + + When working with statistical hypothesis testing, we usually use a + threshold probability or significance level upon which we decide + to reject the null hypothesis :math:`H_0`. Suppose we choose the common + significance level of 5%. + + Our alternative hypothesis is that the vaccine will lower the chance of + becoming infected with the virus; that is, the probability :math:`p_1` of + catching the virus with the vaccine will be *less than* the probability + :math:`p_2` of catching the virus without the vaccine. Therefore, we call + `barnard_exact` with the ``alternative="less"`` option: + + >>> import scipy.stats as stats + >>> res = stats.barnard_exact([[7, 12], [8, 3]], alternative="less") + >>> res.statistic + -1.894 + >>> res.pvalue + 0.03407 + + Under the null hypothesis that the vaccine will not lower the chance of + becoming infected, the probability of obtaining test results at least as + extreme as the observed data is approximately 3.4%. Since this p-value is + less than our chosen significance level, we have evidence to reject + :math:`H_0` in favor of the alternative. + + Suppose we had used Fisher's exact test instead: + + >>> _, pvalue = stats.fisher_exact([[7, 12], [8, 3]], alternative="less") + >>> pvalue + 0.0640 + + With the same threshold significance of 5%, we would not have been able + to reject the null hypothesis in favor of the alternative. As stated in + [2]_, Barnard's test is uniformly more powerful than Fisher's exact test + because Barnard's test does not condition on any margin. Fisher's test + should only be used when both sets of marginals are fixed. + + """ + if n <= 0: + raise ValueError( + "Number of points `n` must be strictly positive, " + f"found {n!r}" + ) + + table = np.asarray(table, dtype=np.int64) + + if not table.shape == (2, 2): + raise ValueError("The input `table` must be of shape (2, 2).") + + if np.any(table < 0): + raise ValueError("All values in `table` must be nonnegative.") + + if 0 in table.sum(axis=0): + # If both values in column are zero, the p-value is 1 and + # the score's statistic is NaN. + return BarnardExactResult(np.nan, 1.0) + + total_col_1, total_col_2 = table.sum(axis=0) + + x1 = np.arange(total_col_1 + 1, dtype=np.int64).reshape(-1, 1) + x2 = np.arange(total_col_2 + 1, dtype=np.int64).reshape(1, -1) + + # We need to calculate the wald statistics for each combination of x1 and + # x2. + p1, p2 = x1 / total_col_1, x2 / total_col_2 + + if pooled: + p = (x1 + x2) / (total_col_1 + total_col_2) + variances = p * (1 - p) * (1 / total_col_1 + 1 / total_col_2) + else: + variances = p1 * (1 - p1) / total_col_1 + p2 * (1 - p2) / total_col_2 + + # To avoid warning when dividing by 0 + with np.errstate(divide="ignore", invalid="ignore"): + wald_statistic = np.divide((p1 - p2), np.sqrt(variances)) + + wald_statistic[p1 == p2] = 0 # Removing NaN values + + wald_stat_obs = wald_statistic[table[0, 0], table[0, 1]] + + if alternative == "two-sided": + index_arr = np.abs(wald_statistic) >= abs(wald_stat_obs) + elif alternative == "less": + index_arr = wald_statistic <= wald_stat_obs + elif alternative == "greater": + index_arr = wald_statistic >= wald_stat_obs + else: + msg = ( + "`alternative` should be one of {'two-sided', 'less', 'greater'}," + f" found {alternative!r}" + ) + raise ValueError(msg) + + x1_sum_x2 = x1 + x2 + + x1_log_comb = _compute_log_combinations(total_col_1) + x2_log_comb = _compute_log_combinations(total_col_2) + x1_sum_x2_log_comb = x1_log_comb[x1] + x2_log_comb[x2] + + result = shgo( + _get_binomial_log_p_value_with_nuisance_param, + args=(x1_sum_x2, x1_sum_x2_log_comb, index_arr), + bounds=((0, 1),), + n=n, + sampling_method="sobol", + ) + + # result.fun is the negative log pvalue and therefore needs to be + # changed before return + p_value = np.clip(np.exp(-result.fun), a_min=0, a_max=1) + return BarnardExactResult(wald_stat_obs, p_value) + + +@dataclass +class BoschlooExactResult: + statistic: float + pvalue: float + + +@xp_capabilities(np_only=True) +def boschloo_exact(table, alternative="two-sided", n=32): + r"""Perform Boschloo's exact test on a 2x2 contingency table. + + Parameters + ---------- + table : array_like of ints + A 2x2 contingency table. Elements should be non-negative integers. + + alternative : {'two-sided', 'less', 'greater'}, optional + Defines the null and alternative hypotheses. Default is 'two-sided'. + Please see explanations in the Notes section below. + + n : int, optional + Number of sampling points used in the construction of the sampling + method. Note that this argument will automatically be converted to + the next higher power of 2 since `scipy.stats.qmc.Sobol` is used to + select sample points. Default is 32. Must be positive. In most cases, + 32 points is enough to reach good precision. More points comes at + performance cost. + + Returns + ------- + ber : BoschlooExactResult + A result object with the following attributes. + + statistic : float + The statistic used in Boschloo's test; that is, the p-value + from Fisher's exact test. + + pvalue : float + P-value, the probability of obtaining a distribution at least as + extreme as the one that was actually observed, assuming that the + null hypothesis is true. + + See Also + -------- + chi2_contingency : Chi-square test of independence of variables in a + contingency table. + fisher_exact : Fisher exact test on a 2x2 contingency table. + barnard_exact : Barnard's exact test, which is a more powerful alternative + than Fisher's exact test for 2x2 contingency tables. + + Notes + ----- + Boschloo's test is an exact test used in the analysis of contingency + tables. It examines the association of two categorical variables, and + is a uniformly more powerful alternative to Fisher's exact test + for 2x2 contingency tables. + + Boschloo's exact test uses the p-value of Fisher's exact test as a + statistic, and Boschloo's p-value is the probability under the null + hypothesis of observing such an extreme value of this statistic. + + Let's define :math:`X_0` a 2x2 matrix representing the observed sample, + where each column stores the binomial experiment, as in the example + below. Let's also define :math:`p_1, p_2` the theoretical binomial + probabilities for :math:`x_{11}` and :math:`x_{12}`. When using + Boschloo exact test, we can assert three different alternative hypotheses: + + - :math:`H_0 : p_1=p_2` versus :math:`H_1 : p_1 < p_2`, + with `alternative` = "less" + + - :math:`H_0 : p_1=p_2` versus :math:`H_1 : p_1 > p_2`, + with `alternative` = "greater" + + - :math:`H_0 : p_1=p_2` versus :math:`H_1 : p_1 \neq p_2`, + with `alternative` = "two-sided" (default) + + There are multiple conventions for computing a two-sided p-value when the + null distribution is asymmetric. Here, we apply the convention that the + p-value of a two-sided test is twice the minimum of the p-values of the + one-sided tests (clipped to 1.0). Note that `fisher_exact` follows a + different convention, so for a given `table`, the statistic reported by + `boschloo_exact` may differ from the p-value reported by `fisher_exact` + when ``alternative='two-sided'``. + + .. versionadded:: 1.7.0 + + References + ---------- + .. [1] R.D. Boschloo. "Raised conditional level of significance for the + 2 x 2-table when testing the equality of two probabilities", + Statistica Neerlandica, 24(1), 1970 + + .. [2] "Boschloo's test", Wikipedia, + https://en.wikipedia.org/wiki/Boschloo%27s_test + + .. [3] Lise M. Saari et al. "Employee attitudes and job satisfaction", + Human Resource Management, 43(4), 395-407, 2004, + :doi:`10.1002/hrm.20032`. + + Examples + -------- + In the following example, we consider the article "Employee + attitudes and job satisfaction" [3]_ + which reports the results of a survey from 63 scientists and 117 college + professors. Of the 63 scientists, 31 said they were very satisfied with + their jobs, whereas 74 of the college professors were very satisfied + with their work. Is this significant evidence that college + professors are happier with their work than scientists? + The following table summarizes the data mentioned above:: + + college professors scientists + Very Satisfied 74 31 + Dissatisfied 43 32 + + When working with statistical hypothesis testing, we usually use a + threshold probability or significance level upon which we decide + to reject the null hypothesis :math:`H_0`. Suppose we choose the common + significance level of 5%. + + Our alternative hypothesis is that college professors are truly more + satisfied with their work than scientists. Therefore, we expect + :math:`p_1` the proportion of very satisfied college professors to be + greater than :math:`p_2`, the proportion of very satisfied scientists. + We thus call `boschloo_exact` with the ``alternative="greater"`` option: + + >>> import scipy.stats as stats + >>> res = stats.boschloo_exact([[74, 31], [43, 32]], alternative="greater") + >>> res.statistic + 0.0483 + >>> res.pvalue + 0.0355 + + Under the null hypothesis that scientists are happier in their work than + college professors, the probability of obtaining test + results at least as extreme as the observed data is approximately 3.55%. + Since this p-value is less than our chosen significance level, we have + evidence to reject :math:`H_0` in favor of the alternative hypothesis. + + """ + hypergeom = distributions.hypergeom + + if n <= 0: + raise ValueError( + "Number of points `n` must be strictly positive," + f" found {n!r}" + ) + + table = np.asarray(table, dtype=np.int64) + + if not table.shape == (2, 2): + raise ValueError("The input `table` must be of shape (2, 2).") + + if np.any(table < 0): + raise ValueError("All values in `table` must be nonnegative.") + + if 0 in table.sum(axis=0): + # If both values in column are zero, the p-value is 1 and + # the score's statistic is NaN. + return BoschlooExactResult(np.nan, np.nan) + + total_col_1, total_col_2 = table.sum(axis=0) + total = total_col_1 + total_col_2 + x1 = np.arange(total_col_1 + 1, dtype=np.int64).reshape(1, -1) + x2 = np.arange(total_col_2 + 1, dtype=np.int64).reshape(-1, 1) + x1_sum_x2 = x1 + x2 + + if alternative == 'less': + pvalues = hypergeom.cdf(x1, total, x1_sum_x2, total_col_1).T + elif alternative == 'greater': + # Same formula as the 'less' case, but with the second column. + pvalues = hypergeom.cdf(x2, total, x1_sum_x2, total_col_2).T + elif alternative == 'two-sided': + boschloo_less = boschloo_exact(table, alternative="less", n=n) + boschloo_greater = boschloo_exact(table, alternative="greater", n=n) + + res = ( + boschloo_less if boschloo_less.pvalue < boschloo_greater.pvalue + else boschloo_greater + ) + + # Two-sided p-value is defined as twice the minimum of the one-sided + # p-values + pvalue = np.clip(2 * res.pvalue, a_min=0, a_max=1) + return BoschlooExactResult(res.statistic, pvalue) + else: + msg = ( + f"`alternative` should be one of {'two-sided', 'less', 'greater'}," + f" found {alternative!r}" + ) + raise ValueError(msg) + + fisher_stat = pvalues[table[0, 0], table[0, 1]] + + # fisher_stat * (1+1e-13) guards us from small numerical error. It is + # equivalent to np.isclose with relative tol of 1e-13 and absolute tol of 0 + # For more throughout explanations, see gh-14178 + index_arr = pvalues <= fisher_stat * (1+1e-13) + + x1, x2, x1_sum_x2 = x1.T, x2.T, x1_sum_x2.T + x1_log_comb = _compute_log_combinations(total_col_1) + x2_log_comb = _compute_log_combinations(total_col_2) + x1_sum_x2_log_comb = x1_log_comb[x1] + x2_log_comb[x2] + + result = shgo( + _get_binomial_log_p_value_with_nuisance_param, + args=(x1_sum_x2, x1_sum_x2_log_comb, index_arr), + bounds=((0, 1),), + n=n, + sampling_method="sobol", + ) + + # result.fun is the negative log pvalue and therefore needs to be + # changed before return + p_value = np.clip(np.exp(-result.fun), a_min=0, a_max=1) + return BoschlooExactResult(fisher_stat, p_value) + + +def _get_binomial_log_p_value_with_nuisance_param( + nuisance_param, x1_sum_x2, x1_sum_x2_log_comb, index_arr +): + r""" + Compute the log pvalue in respect of a nuisance parameter considering + a 2x2 sample space. + + Parameters + ---------- + nuisance_param : float + nuisance parameter used in the computation of the maximisation of + the p-value. Must be between 0 and 1 + + x1_sum_x2 : ndarray + Sum of x1 and x2 inside barnard_exact + + x1_sum_x2_log_comb : ndarray + sum of the log combination of x1 and x2 + + index_arr : ndarray of boolean + + Returns + ------- + p_value : float + Return the maximum p-value considering every nuisance parameter + between 0 and 1 + + Notes + ----- + + Both Barnard's test and Boschloo's test iterate over a nuisance parameter + :math:`\pi \in [0, 1]` to find the maximum p-value. To search this + maxima, this function return the negative log pvalue with respect to the + nuisance parameter passed in params. This negative log p-value is then + used in `shgo` to find the minimum negative pvalue which is our maximum + pvalue. + + Also, to compute the different combination used in the + p-values' computation formula, this function uses `gammaln` which is + more tolerant for large value than `scipy.special.comb`. `gammaln` gives + a log combination. For the little precision loss, performances are + improved a lot. + """ + t1, t2 = x1_sum_x2.shape + n = t1 + t2 - 2 + with np.errstate(divide="ignore", invalid="ignore"): + log_nuisance = np.log( + nuisance_param, + out=np.zeros_like(nuisance_param), + where=nuisance_param >= 0, + ) + log_1_minus_nuisance = np.log( + 1 - nuisance_param, + out=np.zeros_like(nuisance_param), + where=1 - nuisance_param >= 0, + ) + + nuisance_power_x1_x2 = log_nuisance * x1_sum_x2 + nuisance_power_x1_x2[(x1_sum_x2 == 0)[:, :]] = 0 + + nuisance_power_n_minus_x1_x2 = log_1_minus_nuisance * (n - x1_sum_x2) + nuisance_power_n_minus_x1_x2[(x1_sum_x2 == n)[:, :]] = 0 + + tmp_log_values_arr = ( + x1_sum_x2_log_comb + + nuisance_power_x1_x2 + + nuisance_power_n_minus_x1_x2 + ) + + tmp_values_from_index = tmp_log_values_arr[index_arr] + + # To avoid dividing by zero in log function and getting inf value, + # values are centered according to the max + max_value = tmp_values_from_index.max() + + # To have better result's precision, the log pvalue is taken here. + # Indeed, pvalue is included inside [0, 1] interval. Passing the + # pvalue to log makes the interval a lot bigger ([-inf, 0]), and thus + # help us to achieve better precision + with np.errstate(divide="ignore", invalid="ignore"): + log_probs = np.exp(tmp_values_from_index - max_value).sum() + log_pvalue = max_value + np.log( + log_probs, + out=np.full_like(log_probs, -np.inf), + where=log_probs > 0, + ) + + # Since shgo find the minima, minus log pvalue is returned + return -log_pvalue + + +@np.vectorize(otypes=[np.float64]) +def _pval_cvm_2samp_exact(s, m, n): + """ + Compute the exact p-value of the Cramer-von Mises two-sample test + for a given value s of the test statistic. + m and n are the sizes of the samples. + + [1] Y. Xiao, A. Gordon, and A. Yakovlev, "A C++ Program for + the Cramér-Von Mises Two-Sample Test", J. Stat. Soft., + vol. 17, no. 8, pp. 1-15, Dec. 2006. + [2] T. W. Anderson "On the Distribution of the Two-Sample Cramer-von Mises + Criterion," The Annals of Mathematical Statistics, Ann. Math. Statist. + 33(3), 1148-1159, (September, 1962) + """ + + # [1, p. 3] + m, n = int(m), int(n) + lcm = np.lcm(m, n) + # [1, p. 4], below eq. 3 + a = lcm // m + b = lcm // n + # Combine Eq. 9 in [2] with Eq. 2 in [1] and solve for $\zeta$ + # Hint: `s` is $U$ in [2], and $T_2$ in [1] is $T$ in [2] + mn = m * n + zeta = lcm ** 2 * (m + n) * (6 * s - mn * (4 * mn - 1)) // (6 * mn ** 2) + + # bound maximum value that may appear in `gs` (remember both rows!) + zeta_bound = lcm**2 * (m + n) # bound elements in row 1 + combinations = math.comb(m + n, m) # sum of row 2 + max_gs = max(zeta_bound, combinations) + dtype = np.min_scalar_type(max_gs) + + # the frequency table of $g_{u, v}^+$ defined in [1, p. 6] + gs = ([np.array([[0], [1]], dtype=dtype)] + + [np.empty((2, 0), dtype=dtype) for _ in range(m)]) + for u in range(n + 1): + next_gs = [] + tmp = np.empty((2, 0), dtype=dtype) + for v, g in enumerate(gs): + # Calculate g recursively with eq. 11 in [1]. Even though it + # doesn't look like it, this also does 12/13 (all of Algorithm 1). + vi, i0, i1 = np.intersect1d(tmp[0], g[0], return_indices=True) + tmp = np.concatenate([ + np.stack([vi, tmp[1, i0] + g[1, i1]]), + np.delete(tmp, i0, 1), + np.delete(g, i1, 1) + ], 1) + res = (a * v - b * u) ** 2 + tmp[0] += res.astype(dtype) + next_gs.append(tmp) + gs = next_gs + value, freq = gs[m] + return np.float64(np.sum(freq[value >= zeta]) / combinations) + + +def _pval_cvm_2samp_asymptotic(t, N, nx, ny, k, *, xp): + # compute expected value and variance of T (eq. 11 and 14 in [2]) + nx, ny = xp.asarray(nx, dtype=t.dtype), xp.asarray(ny, dtype=t.dtype) + et = (1 + 1 / N) / 6 + vt = (N + 1) * (4 * k * N - 3 * (nx ** 2 + ny ** 2) - 2 * k) + vt = vt / (45 * N ** 2 * 4 * k) + + # computed the normalized statistic (eq. 15 in [2]) + tn = 1 / 6 + (t - et) / xp.sqrt(45 * vt) + + # approximate distribution of tn with limiting distribution + # of the one-sample test statistic + # if tn < 0.003, the _cdf_cvm_inf(tn) < 1.28*1e-18, return 1.0 directly + p = xpx.apply_where(tn >= 0.003, + (tn,), + lambda tn: xp.clip(1. - _cdf_cvm_inf(tn, xp=xp), 0.), + fill_value = 1.) + return p + + +@xp_capabilities(skip_backends=[('dask.array', 'needs rankdata')], + cpu_only=True, jax_jit=False, # due to p-value calculation + marray=True, + extra_note="Only `method='exact'` is compatible with MArray input.") +@_axis_nan_policy_factory(CramerVonMisesResult, n_samples=2, too_small=1, + result_to_tuple=_cvm_result_to_tuple) +def cramervonmises_2samp(x, y, method='auto', *, axis=0): + r"""Perform the two-sample Cramér-von Mises test for goodness of fit. + + This is the two-sample version of the Cramér-von Mises test ([1]_): + for two independent samples :math:`X_1, ..., X_n` and + :math:`Y_1, ..., Y_m`, the null hypothesis is that the samples + come from the same (unspecified) continuous distribution. + + The test statistic :math:`T` is defined as in [1]_: + + .. math:: + T = \frac{nm}{n+m}\omega^2 = + \frac{U}{n m (n+m)} - \frac{4 m n - 1}{6(m+n)} + + where :math:`U` is defined as below, and :math:`\omega^2` is the Cramér-von + Mises criterion. The function :math:`r(\cdot)` here denotes the rank of the + observed values :math:`x_i` and :math:`y_j` within the pooled sample of size + :math:`n + m`, with ties assigned mid-rank values: + + .. math:: + U = n \sum_{i=1}^n (r(x_i)-i)^2 + m \sum_{j=1}^m (r(y_j)-j)^2 + + Parameters + ---------- + x : array_like + A 1-D array of observed values of the random variables :math:`X_i`. + Must contain at least two observations. + y : array_like + A 1-D array of observed values of the random variables :math:`Y_i`. + Must contain at least two observations. + method : {'auto', 'asymptotic', 'exact'}, optional + The method used to compute the p-value, see Notes for details. + The default is 'auto'. + axis : int or tuple of ints, default: 0 + If an int or tuple of ints, the axis or axes of the input along which + to compute the statistic. The statistic of each axis-slice (e.g. row) + of the input will appear in a corresponding element of the output. + If ``None``, the input will be raveled before computing the statistic. + + Returns + ------- + res : object with attributes + statistic : float + Cramér-von Mises statistic :math:`T`. + pvalue : float + The p-value. + + See Also + -------- + cramervonmises, anderson_ksamp, epps_singleton_2samp, ks_2samp + + Notes + ----- + .. versionadded:: 1.7.0 + + The statistic is computed according to equation 9 in [2]_. The + calculation of the p-value depends on the keyword `method`: + + - ``asymptotic``: The p-value is approximated by using the limiting + distribution of the test statistic. + - ``exact``: The exact p-value is computed by enumerating all + possible combinations of the test statistic, see [2]_. + + If ``method='auto'``, the exact approach is used + if both samples contain equal to or less than 20 observations, + otherwise the asymptotic distribution is used. + + If the underlying distribution is not continuous, the p-value is likely to + be conservative (Section 6.2 in [3]_). When ranking the data to compute + the test statistic, midranks are used if there are ties. + + References + ---------- + .. [1] https://en.wikipedia.org/wiki/Cramer-von_Mises_criterion + .. [2] Anderson, T.W. (1962). On the distribution of the two-sample + Cramer-von-Mises criterion. The Annals of Mathematical + Statistics, pp. 1148-1159. + .. [3] Conover, W.J., Practical Nonparametric Statistics, 1971. + + Examples + -------- + + Suppose we wish to test whether two samples generated by + ``scipy.stats.norm.rvs`` have the same distribution. We choose a + significance level of alpha=0.05. + + >>> import numpy as np + >>> from scipy import stats + >>> rng = np.random.default_rng() + >>> x = stats.norm.rvs(size=100, random_state=rng) + >>> y = stats.norm.rvs(size=70, random_state=rng) + >>> res = stats.cramervonmises_2samp(x, y) + >>> res.statistic, res.pvalue + (0.29376470588235293, 0.1412873014573014) + + The p-value exceeds our chosen significance level, so we do not + reject the null hypothesis that the observed samples are drawn from the + same distribution. + + For small sample sizes, one can compute the exact p-values: + + >>> x = stats.norm.rvs(size=7, random_state=rng) + >>> y = stats.t.rvs(df=2, size=6, random_state=rng) + >>> res = stats.cramervonmises_2samp(x, y, method='exact') + >>> res.statistic, res.pvalue + (0.197802197802198, 0.31643356643356646) + + The p-value based on the asymptotic distribution is a good approximation + even though the sample size is small. + + >>> res = stats.cramervonmises_2samp(x, y, method='asymptotic') + >>> res.statistic, res.pvalue + (0.197802197802198, 0.2966041181527128) + + Independent of the method, one would not reject the null hypothesis at the + chosen significance level in this example. + + """ + xp = array_namespace(x, y) + length_x = x.shape[-1] + length_y = y.shape[-1] + count_x = _count_nonmasked(x, axis=-1, xp=xp) + count_y = _count_nonmasked(y, axis=-1, xp=xp) + + if length_x <= 1 or length_y <= 1: # only needed for `test_axis_nan_policy` + raise ValueError('x and y must contain at least two observations.') + if method not in ['auto', 'exact', 'asymptotic']: + raise ValueError('method must be either auto, exact or asymptotic.') + + if is_marray(xp) and method in {'auto', 'asymptotic'}: + message = "Only `method='exact'` is compatible with MArray." + raise ValueError(message) + + if method == 'auto': + if max(count_x, count_y) > 20: + method = 'asymptotic' + else: + method = 'exact' + + # axis=-1 is guaranteed by _axis_nan_policy decorator + xa = xp.sort(x, axis=-1) + ya = xp.sort(y, axis=-1) + + # get ranks of x and y in the pooled sample + z = xp.concat([xa, ya], axis=-1) + # in case of ties, use midrank (see [1]) + r = scipy.stats.rankdata(z, method='average', axis=-1) + dtype = xp_result_type(x, y, force_floating=True, xp=xp) + rx = r[..., :length_x] + ry = r[..., length_x:] + + # compute U (eq. 10 in [2]) + u = (count_x * xp.sum((rx - xp.arange(1, length_x+1, dtype=dtype))**2, axis=-1) + + count_y * xp.sum((ry - xp.arange(1, length_y+1, dtype=dtype))**2, axis=-1)) + + # compute T (eq. 9 in [2]) + k, N = count_x*count_y, count_x + count_y + t = u / (k*N) - (4*k - 1)/(6*N) + + if method == 'exact': + if is_marray(xp): + u, count_x, count_y = u.data, count_x.data, count_y.data + p = _pval_cvm_2samp_exact(np.asarray(u), count_x, count_y) + p = xp.asarray(p, dtype=dtype) + else: + p = _pval_cvm_2samp_asymptotic(t, N, count_x, count_y, k, xp=xp) + + t = t[()] if t.ndim == 0 else t + p = p[()] if p.ndim == 0 else p + return CramerVonMisesResult(statistic=t, pvalue=p) + + +class TukeyHSDResult: + """Result of `scipy.stats.tukey_hsd`. + + Attributes + ---------- + statistic : float ndarray + The computed statistic of the test for each comparison. The element + at index ``(i, j)`` is the statistic for the comparison between groups + ``i`` and ``j``. + pvalue : float ndarray + The associated p-value from the studentized range distribution. The + element at index ``(i, j)`` is the p-value for the comparison + between groups ``i`` and ``j``. + + Notes + ----- + The string representation of this object displays the most recently + calculated confidence interval, and if none have been previously + calculated, it will evaluate ``confidence_interval()``. + + References + ---------- + .. [1] NIST/SEMATECH e-Handbook of Statistical Methods, "7.4.7.1. Tukey's + Method." + https://www.itl.nist.gov/div898/handbook/prc/section4/prc471.htm, + 28 November 2020. + .. [2] P. A. Games and J. F. Howell, "Pairwise Multiple Comparison Procedures + with Unequal N's and/or Variances: A Monte Carlo Study," Journal of + Educational Statistics, vol. 1, no. 2, pp. 113-125, Jun. 1976, + :doi:`10.3102/10769986001002113`. + """ + + def __init__(self, statistic, pvalue, _ntreatments, _df, _stand_err): + self.statistic = statistic + self.pvalue = pvalue + self._ntreatments = _ntreatments + self._df = _df + self._stand_err = _stand_err + self._ci = None + self._ci_cl = None + + def __str__(self): + # Note: `__str__` prints the confidence intervals from the most + # recent call to `confidence_interval`. If it has not been called, + # it will be called with the default CL of .95. + if self._ci is None: + self.confidence_interval(confidence_level=.95) + s = ("Pairwise Group Comparisons" + f" ({self._ci_cl*100:.1f}% Confidence Interval)\n") + s += "Comparison Statistic p-value Lower CI Upper CI\n" + for i, j in np.ndindex(self.pvalue.shape): + if i != j: + s += (f" ({i} - {j}) {self.statistic[i, j]:>10.3f}" + f"{self.pvalue[i, j]:>10.3f}" + f"{self._ci.low[i, j]:>10.3f}" + f"{self._ci.high[i, j]:>10.3f}\n") + return s + + def confidence_interval(self, confidence_level=.95): + """Compute the confidence interval for the specified confidence level. + + Parameters + ---------- + confidence_level : float, optional + Confidence level for the computed confidence interval + of the estimated proportion. Default is .95. + + Returns + ------- + ci : ``ConfidenceInterval`` object + The object has attributes ``low`` and ``high`` that hold the + lower and upper bounds of the confidence intervals for each + comparison. The high and low values are accessible for each + comparison at index ``(i, j)`` between groups ``i`` and ``j``. + + References + ---------- + .. [1] NIST/SEMATECH e-Handbook of Statistical Methods, "7.4.7.1. + Tukey's Method." + https://www.itl.nist.gov/div898/handbook/prc/section4/prc471.htm, + 28 November 2020. + .. [2] P. A. Games and J. F. Howell, "Pairwise Multiple Comparison Procedures + with Unequal N's and/or Variances: A Monte Carlo Study," Journal of + Educational Statistics, vol. 1, no. 2, pp. 113-125, Jun. 1976, + :doi:`10.3102/10769986001002113`. + + Examples + -------- + >>> from scipy.stats import tukey_hsd + >>> group0 = [24.5, 23.5, 26.4, 27.1, 29.9] + >>> group1 = [28.4, 34.2, 29.5, 32.2, 30.1] + >>> group2 = [26.1, 28.3, 24.3, 26.2, 27.8] + >>> result = tukey_hsd(group0, group1, group2) + >>> ci = result.confidence_interval() + >>> ci.low + array([[-3.649159, -8.249159, -3.909159], + [ 0.950841, -3.649159, 0.690841], + [-3.389159, -7.989159, -3.649159]]) + >>> ci.high + array([[ 3.649159, -0.950841, 3.389159], + [ 8.249159, 3.649159, 7.989159], + [ 3.909159, -0.690841, 3.649159]]) + """ + # check to see if the supplied confidence level matches that of the + # previously computed CI. + if (self._ci is not None and self._ci_cl is not None and + confidence_level == self._ci_cl): + return self._ci + + if not 0 < confidence_level < 1: + raise ValueError("Confidence level must be between 0 and 1.") + # determine the critical value of the studentized range using the + # appropriate confidence level, number of treatments, and degrees + # of freedom. See [1] "Confidence limits for Tukey's method" / [2] p.117 + # "H0 was rejected if...". Note that in the cases of unequal sample sizes, + # there will be a criterion for each group comparison. + params = (confidence_level, self._ntreatments, self._df) + srd = distributions.studentized_range.ppf(*params) + # also called maximum critical value, the confidence_radius is the + # studentized range critical value * the square root of mean square + # error over the sample size. + confidence_radius = srd * self._stand_err + # the confidence levels are determined by the + # `mean_differences` +- `confidence_radius` + upper_conf = self.statistic + confidence_radius + lower_conf = self.statistic - confidence_radius + self._ci = ConfidenceInterval(low=lower_conf, high=upper_conf) + self._ci_cl = confidence_level + return self._ci + + +def _tukey_hsd_iv(args, equal_var): + if (len(args)) < 2: + raise ValueError("There must be more than 1 treatment.") + if not isinstance(equal_var, bool): + raise TypeError("Expected a boolean value for 'equal_var'") + args = [np.asarray(arg) for arg in args] + for arg in args: + if arg.ndim != 1: + raise ValueError("Input samples must be one-dimensional.") + if arg.size <= 1: + raise ValueError("Input sample size must be greater than one.") + if np.isinf(arg).any(): + raise ValueError("Input samples must be finite.") + return args + + +@xp_capabilities(np_only=True) +def tukey_hsd(*samples, equal_var=True): + """Perform Tukey's HSD test for equality of means over multiple treatments. + + Tukey's honestly significant difference (HSD) test performs pairwise + comparison of means for a set of samples. Whereas ANOVA (e.g. `f_oneway`) + assesses whether the true means underlying each sample are identical, + Tukey's HSD is a post hoc test used to compare the mean of each sample + to the mean of each other sample. + + The null hypothesis is that the distributions underlying the samples all + have the same mean. The test statistic, which is computed for every + possible pairing of samples, is simply the difference between the sample + means. For each pair, the p-value is the probability under the null + hypothesis (and other assumptions; see notes) of observing such an extreme + value of the statistic, considering that many pairwise comparisons are + being performed. Confidence intervals for the difference between each pair + of means are also available. + + Parameters + ---------- + *samples : array_like + The sample measurements for each group. There must be at least + two arguments. + equal_var : bool, optional + If True (default) and equal sample size, perform Tukey-HSD test [6]. + If True and unequal sample size, perform Tukey-Kramer test [4]_. + If False, perform Games-Howell test [7]_, which does not assume equal variances. + + Returns + ------- + result : `~scipy.stats._result_classes.TukeyHSDResult` instance + The return value is an object with the following attributes: + + statistic : float ndarray + The computed statistic of the test for each comparison. The element + at index ``(i, j)`` is the statistic for the comparison between + groups ``i`` and ``j``. + pvalue : float ndarray + The computed p-value of the test for each comparison. The element + at index ``(i, j)`` is the p-value for the comparison between + groups ``i`` and ``j``. + + The object has the following methods: + + confidence_interval(confidence_level=0.95): + Compute the confidence interval for the specified confidence level. + + See Also + -------- + dunnett : performs comparison of means against a control group. + + Notes + ----- + The use of this test relies on several assumptions. + + 1. The observations are independent within and among groups. + 2. The observations within each group are normally distributed. + 3. The distributions from which the samples are drawn have the same finite + variance. + + The original formulation of the test was for samples of equal size drawn from + populations assumed to have equal variances [6]_. In case of unequal sample sizes, + the test uses the Tukey-Kramer method [4]_. When equal variances are not assumed + (``equal_var=False``), the test uses the Games-Howell method [7]_. + + References + ---------- + .. [1] NIST/SEMATECH e-Handbook of Statistical Methods, "7.4.7.1. Tukey's + Method." + https://www.itl.nist.gov/div898/handbook/prc/section4/prc471.htm, + 28 November 2020. + .. [2] Abdi, Herve & Williams, Lynne. (2021). "Tukey's Honestly Significant + Difference (HSD) Test." + https://personal.utdallas.edu/~herve/abdi-HSD2010-pretty.pdf + .. [3] "One-Way ANOVA Using SAS PROC ANOVA & PROC GLM." SAS + Tutorials, 2007. + https://www.stattutorials.com/SAS/TUTORIAL-PROC-GLM.htm + .. [4] Kramer, Clyde Young. "Extension of Multiple Range Tests to Group + Means with Unequal Numbers of Replications." Biometrics, vol. 12, + no. 3, 1956, pp. 307-310. https://www.jstor.org/stable/3001469 + .. [5] NIST/SEMATECH e-Handbook of Statistical Methods, "7.4.3.3. + The ANOVA table and tests of hypotheses about means" + https://www.itl.nist.gov/div898/handbook/prc/section4/prc433.htm, + 2 June 2021. + .. [6] Tukey, John W. "Comparing Individual Means in the Analysis of + Variance." Biometrics, vol. 5, no. 2, 1949, pp. 99-114. + https://www.jstor.org/stable/3001913 + .. [7] P. A. Games and J. F. Howell, "Pairwise Multiple Comparison Procedures + with Unequal N's and/or Variances: A Monte Carlo Study," Journal of + Educational Statistics, vol. 1, no. 2, pp. 113-125, Jun. 1976. + :doi:`10.3102/10769986001002113`. + + + Examples + -------- + Here are some data comparing the time to relief of three brands of + headache medicine, reported in minutes. Data adapted from [3]_. + + >>> import numpy as np + >>> from scipy.stats import tukey_hsd + >>> group0 = [24.5, 23.5, 26.4, 27.1, 29.9] + >>> group1 = [28.4, 34.2, 29.5, 32.2, 30.1] + >>> group2 = [26.1, 28.3, 24.3, 26.2, 27.8] + + We would like to see if the means between any of the groups are + significantly different. First, visually examine a box and whisker plot. + + >>> import matplotlib.pyplot as plt + >>> fig, ax = plt.subplots(1, 1) + >>> ax.boxplot([group0, group1, group2]) + >>> ax.set_xticklabels(["group0", "group1", "group2"]) # doctest: +SKIP + >>> ax.set_ylabel("mean") # doctest: +SKIP + >>> plt.show() + + From the box and whisker plot, we can see overlap in the interquartile + ranges group 1 to group 2 and group 3, but we can apply the ``tukey_hsd`` + test to determine if the difference between means is significant. We + set a significance level of .05 to reject the null hypothesis. + + >>> res = tukey_hsd(group0, group1, group2) + >>> print(res) + Pairwise Group Comparisons (95.0% Confidence Interval) + Comparison Statistic p-value Lower CI Upper CI + (0 - 1) -4.600 0.014 -8.249 -0.951 + (0 - 2) -0.260 0.980 -3.909 3.389 + (1 - 0) 4.600 0.014 0.951 8.249 + (1 - 2) 4.340 0.020 0.691 7.989 + (2 - 0) 0.260 0.980 -3.389 3.909 + (2 - 1) -4.340 0.020 -7.989 -0.691 + + The null hypothesis is that each group has the same mean. The p-value for + comparisons between ``group0`` and ``group1`` as well as ``group1`` and + ``group2`` do not exceed .05, so we reject the null hypothesis that they + have the same means. The p-value of the comparison between ``group0`` + and ``group2`` exceeds .05, so we accept the null hypothesis that there + is not a significant difference between their means. + + We can also compute the confidence interval associated with our chosen + confidence level. + + >>> group0 = [24.5, 23.5, 26.4, 27.1, 29.9] + >>> group1 = [28.4, 34.2, 29.5, 32.2, 30.1] + >>> group2 = [26.1, 28.3, 24.3, 26.2, 27.8] + >>> result = tukey_hsd(group0, group1, group2) + >>> conf = res.confidence_interval(confidence_level=.99) + >>> for ((i, j), l) in np.ndenumerate(conf.low): + ... # filter out self comparisons + ... if i != j: + ... h = conf.high[i,j] + ... print(f"({i} - {j}) {l:>6.3f} {h:>6.3f}") + (0 - 1) -9.480 0.280 + (0 - 2) -5.140 4.620 + (1 - 0) -0.280 9.480 + (1 - 2) -0.540 9.220 + (2 - 0) -4.620 5.140 + (2 - 1) -9.220 0.540 + """ + args = _tukey_hsd_iv(samples, equal_var) + ntreatments = len(args) + means = np.asarray([np.mean(arg) for arg in args]) + nsamples_treatments = np.asarray([a.size for a in args]) + nobs = np.sum(nsamples_treatments) + vars_ = np.asarray([np.var(arg, ddof=1) for arg in args]) + + if equal_var: + # determine mean square error [5]. Note that this is sometimes called + # mean square error within. + mse = (np.sum(vars_ * (nsamples_treatments - 1)) / (nobs - ntreatments)) + + # The calculation of the standard error differs when treatments differ in + # size. See ("Unequal sample sizes")[1]. + if np.unique(nsamples_treatments).size == 1: + # all input groups are the same length, so only one value needs to be + # calculated [1]. + normalize = 2 / nsamples_treatments[0] + else: + # to compare groups of differing sizes, we must compute a variance + # value for each individual comparison. Use broadcasting to get the + # resulting matrix. [3], verified against [4] (page 308). + normalize = 1 / nsamples_treatments + 1 / nsamples_treatments[None].T + + # the standard error is used in the computation of the tukey criterion and + # finding the p-values. + stand_err = np.sqrt(normalize * mse / 2) + df = nobs - ntreatments + else: + # `stand_err` is the denominator of the Behrens-Fisher statistic ($v$) + # with a factor of $\sqrt{2}$. Compare [7] p.116 "t-solution rejects H0 if...", + # [7] p. 117 "H0 was rejected", and definition of `t_stat` below. + sj2_nj = vars_ / nsamples_treatments + si2_ni = sj2_nj[:, np.newaxis] + stand_err = np.sqrt(si2_ni + sj2_nj) / 2**0.5 + + # `df` is the Welch degree of freedom $\nu$. + # See [7] p. 116 "and the degrees of freedom, $\nu$, are given by...". + njm1 = nsamples_treatments - 1 + nim1 = njm1[:, np.newaxis] + df = (si2_ni + sj2_nj)**2 / (si2_ni**2 / nim1 + sj2_nj**2 / njm1) + + # the mean difference is the test statistic. + mean_differences = means[None].T - means + + # Calculate the t-statistic to use within the survival function of the + # studentized range to get the p-value. + t_stat = np.abs(mean_differences) / stand_err + + params = t_stat, ntreatments, df + pvalues = distributions.studentized_range.sf(*params) + + return TukeyHSDResult(mean_differences, pvalues, ntreatments, + df, stand_err) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_kde.py b/venv/lib/python3.12/site-packages/scipy/stats/_kde.py new file mode 100644 index 0000000000000000000000000000000000000000..a1be45bfa59f7816235886763761dc3a2107c9cd --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_kde.py @@ -0,0 +1,765 @@ +#------------------------------------------------------------------------------- +# +# Define classes for (uni/multi)-variate kernel density estimation. +# +# Currently, only Gaussian kernels are implemented. +# +# Written by: Robert Kern +# +# Date: 2004-08-09 +# +# Modified: 2005-02-10 by Robert Kern. +# Contributed to SciPy +# 2005-10-07 by Robert Kern. +# Some fixes to match the new scipy_core +# +# Copyright 2004-2005 by Enthought, Inc. +# +#------------------------------------------------------------------------------- + +from types import GenericAlias + +# SciPy imports. +from scipy import linalg, special +from scipy._lib._util import check_random_state + +from numpy import (asarray, atleast_2d, reshape, zeros, newaxis, exp, pi, + sqrt, ravel, power, atleast_1d, squeeze, sum, transpose, + ones, cov) +import numpy as np + +# Local imports. +from ._stats import gaussian_kernel_estimate, gaussian_kernel_estimate_log +from ._multivariate import multivariate_normal + +__all__ = ['gaussian_kde'] + + +class gaussian_kde: + """Representation of a kernel-density estimate using Gaussian kernels. + + Kernel density estimation is a way to estimate the probability density + function (PDF) of a random variable in a non-parametric way. + `gaussian_kde` works for both uni-variate and multi-variate data. It + includes automatic bandwidth determination. The estimation works best for + a unimodal distribution; bimodal or multi-modal distributions tend to be + oversmoothed. + + Parameters + ---------- + dataset : array_like + Datapoints to estimate from. In case of univariate data this is a 1-D + array, otherwise a 2-D array with shape (# of dims, # of data). + bw_method : str, scalar or callable, optional + The method used to calculate the bandwidth factor. This can be + 'scott', 'silverman', a scalar constant or a callable. If a scalar, + this will be used directly as `factor`. If a callable, it should + take a `gaussian_kde` instance as only parameter and return a scalar. + If None (default), 'scott' is used. See Notes for more details. + weights : array_like, optional + weights of datapoints. This must be the same shape as dataset. + If None (default), the samples are assumed to be equally weighted + + Attributes + ---------- + dataset : ndarray + The dataset with which `gaussian_kde` was initialized. + d : int + Number of dimensions. + n : int + Number of datapoints. + neff : int + Effective number of datapoints. + + .. versionadded:: 1.2.0 + factor : float + The bandwidth factor obtained from `covariance_factor`. + covariance : ndarray + The kernel covariance matrix; this is the data covariance matrix + multiplied by the square of the bandwidth factor, e.g. + ``np.cov(dataset) * factor**2``. + inv_cov : ndarray + The inverse of `covariance`. + + Methods + ------- + evaluate + __call__ + integrate_gaussian + integrate_box_1d + integrate_box + integrate_kde + pdf + logpdf + resample + set_bandwidth + covariance_factor + marginal + + Notes + ----- + Bandwidth selection strongly influences the estimate obtained from the KDE + (much more so than the actual shape of the kernel). Bandwidth selection + can be done by a "rule of thumb", by cross-validation, by "plug-in + methods" or by other means; see [3]_, [4]_ for reviews. `gaussian_kde` + uses a rule of thumb, the default is Scott's Rule. + + Scott's Rule [1]_, implemented as `scotts_factor`, is:: + + n**(-1./(d+4)), + + with ``n`` the number of data points and ``d`` the number of dimensions. + In the case of unequally weighted points, `scotts_factor` becomes:: + + neff**(-1./(d+4)), + + with ``neff`` the effective number of datapoints. + Silverman's suggestion for *multivariate* data [2]_, implemented as + `silverman_factor`, is:: + + (n * (d + 2) / 4.)**(-1. / (d + 4)). + + or in the case of unequally weighted points:: + + (neff * (d + 2) / 4.)**(-1. / (d + 4)). + + Note that this is not the same as "Silverman's rule of thumb" [6]_, which + may be more robust in the univariate case; see documentation of the + ``set_bandwidth`` method for implementing a custom bandwidth rule. + + Good general descriptions of kernel density estimation can be found in [1]_ + and [2]_, the mathematics for this multi-dimensional implementation can be + found in [1]_. + + With a set of weighted samples, the effective number of datapoints ``neff`` + is defined by:: + + neff = sum(weights)^2 / sum(weights^2) + + as detailed in [5]_. + + `gaussian_kde` does not currently support data that lies in a + lower-dimensional subspace of the space in which it is expressed. For such + data, consider performing principal component analysis / dimensionality + reduction and using `gaussian_kde` with the transformed data. + + References + ---------- + .. [1] D.W. Scott, "Multivariate Density Estimation: Theory, Practice, and + Visualization", John Wiley & Sons, New York, Chicester, 1992. + .. [2] B.W. Silverman, "Density Estimation for Statistics and Data + Analysis", Vol. 26, Monographs on Statistics and Applied Probability, + Chapman and Hall, London, 1986. + .. [3] B.A. Turlach, "Bandwidth Selection in Kernel Density Estimation: A + Review", CORE and Institut de Statistique, Vol. 19, pp. 1-33, 1993. + .. [4] D.M. Bashtannyk and R.J. Hyndman, "Bandwidth selection for kernel + conditional density estimation", Computational Statistics & Data + Analysis, Vol. 36, pp. 279-298, 2001. + .. [5] Gray P. G., 1969, Journal of the Royal Statistical Society. + Series A (General), 132, 272 + .. [6] Kernel density estimation. *Wikipedia.* + https://en.wikipedia.org/wiki/Kernel_density_estimation + + Examples + -------- + Generate some random two-dimensional data: + + >>> import numpy as np + >>> from scipy import stats + >>> def measure(n): + ... "Measurement model, return two coupled measurements." + ... m1 = np.random.normal(size=n) + ... m2 = np.random.normal(scale=0.5, size=n) + ... return m1+m2, m1-m2 + + >>> m1, m2 = measure(2000) + >>> xmin = m1.min() + >>> xmax = m1.max() + >>> ymin = m2.min() + >>> ymax = m2.max() + + Perform a kernel density estimate on the data: + + >>> X, Y = np.mgrid[xmin:xmax:100j, ymin:ymax:100j] + >>> positions = np.vstack([X.ravel(), Y.ravel()]) + >>> values = np.vstack([m1, m2]) + >>> kernel = stats.gaussian_kde(values) + >>> Z = np.reshape(kernel(positions).T, X.shape) + + Plot the results: + + >>> import matplotlib.pyplot as plt + >>> fig, ax = plt.subplots() + >>> ax.imshow(np.rot90(Z), cmap=plt.cm.gist_earth_r, + ... extent=[xmin, xmax, ymin, ymax]) + >>> ax.plot(m1, m2, 'k.', markersize=2) + >>> ax.set_xlim([xmin, xmax]) + >>> ax.set_ylim([ymin, ymax]) + >>> plt.show() + + Compare against manual KDE at a point: + + >>> point = [1, 2] + >>> mean = values.T + >>> cov = kernel.factor**2 * np.cov(values) + >>> X = stats.multivariate_normal(cov=cov) + >>> res = kernel.pdf(point) + >>> ref = X.pdf(point - mean).sum() / len(mean) + >>> np.allclose(res, ref) + True + """ + + # generic type compatibility with scipy-stubs + __class_getitem__: classmethod = classmethod(GenericAlias) + + def __init__(self, dataset, bw_method=None, weights=None): + self.dataset = atleast_2d(asarray(dataset)) + if not self.dataset.size > 1: + raise ValueError("`dataset` input should have multiple elements.") + + self.d, self.n = self.dataset.shape + + if weights is not None: + self._weights = atleast_1d(weights).astype(float) + self._weights /= sum(self._weights) + if self.weights.ndim != 1: + raise ValueError("`weights` input should be one-dimensional.") + if len(self._weights) != self.n: + raise ValueError("`weights` input should be of length n") + self._neff = 1/np.vecdot(self._weights, self._weights) + + # This can be converted to a warning once gh-10205 is resolved + if self.d > self.n: + msg = ("Number of dimensions is greater than number of samples. " + "This results in a singular data covariance matrix, which " + "cannot be treated using the algorithms implemented in " + "`gaussian_kde`. Note that `gaussian_kde` interprets each " + "*column* of `dataset` to be a point; consider transposing " + "the input to `dataset`.") + raise ValueError(msg) + + try: + self.set_bandwidth(bw_method=bw_method) + except linalg.LinAlgError as e: + msg = ("The data appears to lie in a lower-dimensional subspace " + "of the space in which it is expressed. This has resulted " + "in a singular data covariance matrix, which cannot be " + "treated using the algorithms implemented in " + "`gaussian_kde`. Consider performing principal component " + "analysis / dimensionality reduction and using " + "`gaussian_kde` with the transformed data.") + raise linalg.LinAlgError(msg) from e + + def evaluate(self, points): + """Evaluate the estimated pdf on a set of points. + + Parameters + ---------- + points : (# of dimensions, # of points)-array + Alternatively, a (# of dimensions,) vector can be passed in and + treated as a single point. + + Returns + ------- + values : (# of points,)-array + The values at each point. + + Raises + ------ + ValueError : if the dimensionality of the input points is different than + the dimensionality of the KDE. + + """ + points = atleast_2d(asarray(points)) + + d, m = points.shape + if d != self.d: + if d == 1 and m == self.d: + # points was passed in as a row vector + points = reshape(points, (self.d, 1)) + m = 1 + else: + msg = (f"points have dimension {d}, " + f"dataset has dimension {self.d}") + raise ValueError(msg) + + output_dtype, spec = _get_output_dtype(self.covariance, points) + result = gaussian_kernel_estimate[spec]( + self.dataset.T, self.weights[:, None], + points.T, self.cho_cov, output_dtype) + + return result[:, 0] + + __call__ = evaluate + + def integrate_gaussian(self, mean, cov): + """ + Multiply estimated density by a multivariate Gaussian and integrate + over the whole space. + + Parameters + ---------- + mean : aray_like + A 1-D array, specifying the mean of the Gaussian. + cov : array_like + A 2-D array, specifying the covariance matrix of the Gaussian. + + Returns + ------- + result : scalar + The value of the integral. + + Raises + ------ + ValueError + If the mean or covariance of the input Gaussian differs from + the KDE's dimensionality. + + """ + mean = atleast_1d(squeeze(mean)) + cov = atleast_2d(cov) + + if mean.shape != (self.d,): + raise ValueError(f"mean does not have dimension {self.d}") + if cov.shape != (self.d, self.d): + raise ValueError(f"covariance does not have dimension {self.d}") + + # make mean a column vector + mean = mean[:, newaxis] + + sum_cov = self.covariance + cov + + # This will raise LinAlgError if the new cov matrix is not s.p.d + # cho_factor returns (ndarray, bool) where bool is a flag for whether + # or not ndarray is upper or lower triangular + sum_cov_chol = linalg.cho_factor(sum_cov) + + diff = self.dataset - mean + tdiff = linalg.cho_solve(sum_cov_chol, diff) + + sqrt_det = np.prod(np.diagonal(sum_cov_chol[0])) + norm_const = power(2 * pi, sum_cov.shape[0] / 2.0) * sqrt_det + + energies = np.vecdot(diff, tdiff, axis=0) / 2.0 + result = np.vecdot(exp(-energies), self.weights, axis=0) / norm_const + + return result + + def integrate_box_1d(self, low, high): + """ + Computes the integral of a 1D pdf between two bounds. + + Parameters + ---------- + low : scalar + Lower bound of integration. + high : scalar + Upper bound of integration. + + Returns + ------- + value : scalar + The result of the integral. + + Raises + ------ + ValueError + If the KDE is over more than one dimension. + + """ + if self.d != 1: + raise ValueError("integrate_box_1d() only handles 1D pdfs") + + stdev = ravel(sqrt(self.covariance))[0] + + normalized_low = ravel((low - self.dataset) / stdev) + normalized_high = ravel((high - self.dataset) / stdev) + + delta = special.ndtr(normalized_high) - special.ndtr(normalized_low) + value = np.vecdot(self.weights, delta) + return value + + def integrate_box(self, low_bounds, high_bounds, maxpts=None, *, rng=None): + """Computes the integral of a pdf over a rectangular interval. + + Parameters + ---------- + low_bounds : array_like + A 1-D array containing the lower bounds of integration. + high_bounds : array_like + A 1-D array containing the upper bounds of integration. + maxpts : int, optional + The maximum number of points to use for integration. + rng : `numpy.random.Generator`, optional + Pseudorandom number generator state. When `rng` is None, a new + generator is created using entropy from the operating system. Types + other than `numpy.random.Generator` are passed to + `numpy.random.default_rng` to instantiate a ``Generator``. + + Returns + ------- + value : scalar + The result of the integral. + + """ + low, high = low_bounds - self.dataset.T, high_bounds - self.dataset.T + values = multivariate_normal.cdf( + high, lower_limit=low, cov=self.covariance, maxpts=maxpts, + rng=rng + ) + return np.vecdot(values, self.weights, axis=-1) + + def integrate_kde(self, other): + """ + Computes the integral of the product of this kernel density estimate + with another. + + Parameters + ---------- + other : gaussian_kde instance + The other kde. + + Returns + ------- + value : scalar + The result of the integral. + + Raises + ------ + ValueError + If the KDEs have different dimensionality. + + """ + if other.d != self.d: + raise ValueError("KDEs are not the same dimensionality") + + # we want to iterate over the smallest number of points + if other.n < self.n: + small = other + large = self + else: + small = self + large = other + + sum_cov = small.covariance + large.covariance + sum_cov_chol = linalg.cho_factor(sum_cov) + result = 0.0 + for i in range(small.n): + mean = small.dataset[:, i, newaxis] + diff = large.dataset - mean + tdiff = linalg.cho_solve(sum_cov_chol, diff) + + energies = np.vecdot(diff, tdiff, axis=0) / 2.0 + result += np.vecdot(exp(-energies), large.weights, axis=0)*small.weights[i] + + sqrt_det = np.prod(np.diagonal(sum_cov_chol[0])) + norm_const = power(2 * pi, sum_cov.shape[0] / 2.0) * sqrt_det + + result /= norm_const + + return result + + def resample(self, size=None, seed=None): + """Randomly sample a dataset from the estimated pdf. + + Parameters + ---------- + size : int, optional + The number of samples to draw. If not provided, then the size is + the same as the effective number of samples in the underlying + dataset. + seed : {None, int, `numpy.random.Generator`, `numpy.random.RandomState`}, optional + If `seed` is None (or `np.random`), the `numpy.random.RandomState` + singleton is used. + If `seed` is an int, a new ``RandomState`` instance is used, + seeded with `seed`. + If `seed` is already a ``Generator`` or ``RandomState`` instance then + that instance is used. + + Returns + ------- + resample : (self.d, `size`) ndarray + The sampled dataset. + + """ # numpy/numpydoc#87 # noqa: E501 + if size is None: + size = int(self.neff) + + random_state = check_random_state(seed) + norm = transpose(random_state.multivariate_normal( + zeros((self.d,), float), self.covariance, size=size + )) + indices = random_state.choice(self.n, size=size, p=self.weights) + means = self.dataset[:, indices] + + return means + norm + + def scotts_factor(self): + """Compute Scott's factor. + + Returns + ------- + s : float + Scott's factor. + """ + return power(self.neff, -1./(self.d+4)) + + def silverman_factor(self): + """Compute the Silverman factor. + + Returns + ------- + s : float + The silverman factor. + """ + return power(self.neff*(self.d+2.0)/4.0, -1./(self.d+4)) + + # Default method to calculate bandwidth, can be overwritten by subclass + covariance_factor = scotts_factor + covariance_factor.__doc__ = """Computes the bandwidth factor `factor`. + The default is `scotts_factor`. A subclass can overwrite this + method to provide a different method, or set it through a call to + `set_bandwidth`. + + Returns + ------- + factor : float + The bandwidth factor. + """ + + def set_bandwidth(self, bw_method=None): + """Compute the bandwidth factor with given method. + + The new bandwidth calculated after a call to `set_bandwidth` is used + for subsequent evaluations of the estimated density. + + Parameters + ---------- + bw_method : str, scalar or callable, optional + The method used to calculate the bandwidth factor. This can be + 'scott', 'silverman', a scalar constant or a callable. If a + scalar, this will be used directly as `factor`. If a callable, + it should take a `gaussian_kde` instance as only parameter and + return a scalar. If None (default), nothing happens; the current + `covariance_factor` method is kept. + + Notes + ----- + .. versionadded:: 0.11 + + Examples + -------- + >>> import numpy as np + >>> import scipy.stats as stats + >>> x1 = np.array([-7, -5, 1, 4, 5.]) + >>> kde = stats.gaussian_kde(x1) + >>> xs = np.linspace(-10, 10, num=50) + >>> y1 = kde(xs) + >>> kde.set_bandwidth(bw_method='silverman') + >>> y2 = kde(xs) + >>> kde.set_bandwidth(bw_method=kde.factor / 3.) + >>> y3 = kde(xs) + + >>> import matplotlib.pyplot as plt + >>> fig, ax = plt.subplots() + >>> ax.plot(x1, np.full(x1.shape, 1 / (4. * x1.size)), 'bo', + ... label='Data points (rescaled)') + >>> ax.plot(xs, y1, label='Scott (default)') + >>> ax.plot(xs, y2, label='Silverman') + >>> ax.plot(xs, y3, label='Const (1/3 * Silverman)') + >>> ax.legend() + >>> plt.show() + + """ + if bw_method is None: + pass + elif bw_method == 'scott': + self.covariance_factor = self.scotts_factor + elif bw_method == 'silverman': + self.covariance_factor = self.silverman_factor + elif np.isscalar(bw_method) and not isinstance(bw_method, str): + self._bw_method = 'use constant' + self.covariance_factor = lambda: bw_method + elif callable(bw_method): + self._bw_method = bw_method + self.covariance_factor = lambda: self._bw_method(self) + else: + msg = "`bw_method` should be 'scott', 'silverman', a scalar " \ + "or a callable." + raise ValueError(msg) + + self._compute_covariance() + + def _compute_covariance(self): + """Computes the covariance matrix for each Gaussian kernel using + covariance_factor(). + """ + self.factor = self.covariance_factor() + # Cache covariance and Cholesky decomp of covariance + if not hasattr(self, '_data_cho_cov'): + self._data_covariance = atleast_2d(cov(self.dataset, rowvar=1, + bias=False, + aweights=self.weights)) + self._data_cho_cov = linalg.cholesky(self._data_covariance, + lower=True) + + self.covariance = self._data_covariance * self.factor**2 + self.cho_cov = (self._data_cho_cov * self.factor).astype(np.float64) + self.log_det = 2*np.log(np.diag(self.cho_cov + * np.sqrt(2*pi))).sum() + + @property + def inv_cov(self): + # Re-compute from scratch each time because I'm not sure how this is + # used in the wild. (Perhaps users change the `dataset`, since it's + # not a private attribute?) `_compute_covariance` used to recalculate + # all these, so we'll recalculate everything now that this is a + # a property. + self.factor = self.covariance_factor() + self._data_covariance = atleast_2d(cov(self.dataset, rowvar=1, + bias=False, aweights=self.weights)) + return linalg.inv(self._data_covariance) / self.factor**2 + + def pdf(self, x): + """ + Evaluate the estimated pdf on a provided set of points. + + Parameters + ---------- + x : array_like + Points at which to evaluate the pdf. + + Returns + ------- + pdf : ndarray + The pdf evaluated at `x`. + + Notes + ----- + This is an alias for `gaussian_kde.evaluate`. See the ``evaluate`` + docstring for more details. + + """ + return self.evaluate(x) + + def logpdf(self, x): + """ + Evaluate the log of the estimated pdf on a provided set of points. + + Parameters + ---------- + x : array_like + Points at which to evaluate the log-pdf. + + Returns + ------- + logpdf : ndarray + The log-pdf evaluated at `x`. + """ + points = atleast_2d(x) + + d, m = points.shape + if d != self.d: + if d == 1 and m == self.d: + # points was passed in as a row vector + points = reshape(points, (self.d, 1)) + m = 1 + else: + msg = (f"points have dimension {d}, " + f"dataset has dimension {self.d}") + raise ValueError(msg) + + output_dtype, spec = _get_output_dtype(self.covariance, points) + result = gaussian_kernel_estimate_log[spec]( + self.dataset.T, self.weights[:, None], + points.T, self.cho_cov, output_dtype) + + return result[:, 0] + + def marginal(self, dimensions): + """Return a marginal KDE distribution. + + Parameters + ---------- + dimensions : int or 1-d array_like + The dimensions of the multivariate distribution corresponding + with the marginal variables, that is, the indices of the dimensions + that are being retained. The other dimensions are marginalized out. + + Returns + ------- + marginal_kde : gaussian_kde + An object representing the marginal distribution. + + Notes + ----- + .. versionadded:: 1.10.0 + + """ + + dims = np.atleast_1d(dimensions) + + if not np.issubdtype(dims.dtype, np.integer): + msg = ("Elements of `dimensions` must be integers - the indices " + "of the marginal variables being retained.") + raise ValueError(msg) + + n = len(self.dataset) # number of dimensions + original_dims = dims.copy() + + dims[dims < 0] = n + dims[dims < 0] + + if len(np.unique(dims)) != len(dims): + msg = ("All elements of `dimensions` must be unique.") + raise ValueError(msg) + + i_invalid = (dims < 0) | (dims >= n) + if np.any(i_invalid): + msg = (f"Dimensions {original_dims[i_invalid]} are invalid " + f"for a distribution in {n} dimensions.") + raise ValueError(msg) + + dataset = self.dataset[dims] + weights = self.weights + + return gaussian_kde(dataset, bw_method=self.covariance_factor(), + weights=weights) + + @property + def weights(self): + try: + return self._weights + except AttributeError: + self._weights = ones(self.n)/self.n + return self._weights + + @property + def neff(self): + try: + return self._neff + except AttributeError: + self._neff = 1/np.vecdot(self.weights, self.weights) + return self._neff + + +def _get_output_dtype(covariance, points): + """ + Calculates the output dtype and the "spec" (=C type name). + + This was necessary in order to deal with the fused types in the Cython + routine `gaussian_kernel_estimate`. See gh-10824 for details. + """ + output_dtype = np.common_type(covariance, points) + itemsize = np.dtype(output_dtype).itemsize + if itemsize == 4: + spec = 'float' + elif itemsize == 8: + spec = 'double' + elif itemsize in (12, 16): + spec = 'long double' + else: + raise ValueError( + f"{output_dtype} has unexpected item size: {itemsize}" + ) + + return output_dtype, spec diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_ksstats.py b/venv/lib/python3.12/site-packages/scipy/stats/_ksstats.py new file mode 100644 index 0000000000000000000000000000000000000000..f6884bb834bbb060fdec5ed49869517e5b7c941b --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_ksstats.py @@ -0,0 +1,600 @@ +# Compute the two-sided one-sample Kolmogorov-Smirnov Prob(Dn <= d) where: +# D_n = sup_x{|F_n(x) - F(x)|}, +# F_n(x) is the empirical CDF for a sample of size n {x_i: i=1,...,n}, +# F(x) is the CDF of a probability distribution. +# +# Exact methods: +# Prob(D_n >= d) can be computed via a matrix algorithm of Durbin[1] +# or a recursion algorithm due to Pomeranz[2]. +# Marsaglia, Tsang & Wang[3] gave a computation-efficient way to perform +# the Durbin algorithm. +# D_n >= d <==> D_n+ >= d or D_n- >= d (the one-sided K-S statistics), hence +# Prob(D_n >= d) = 2*Prob(D_n+ >= d) - Prob(D_n+ >= d and D_n- >= d). +# For d > 0.5, the latter intersection probability is 0. +# +# Approximate methods: +# For d close to 0.5, ignoring that intersection term may still give a +# reasonable approximation. +# Li-Chien[4] and Korolyuk[5] gave an asymptotic formula extending +# Kolmogorov's initial asymptotic, suitable for large d. (See +# scipy.special.kolmogorov for that asymptotic) +# Pelz-Good[6] used the functional equation for Jacobi theta functions to +# transform the Li-Chien/Korolyuk formula produce a computational formula +# suitable for small d. +# +# Simard and L'Ecuyer[7] provided an algorithm to decide when to use each of +# the above approaches and it is that which is used here. +# +# Other approaches: +# Carvalho[8] optimizes Durbin's matrix algorithm for large values of d. +# Moscovich and Nadler[9] use FFTs to compute the convolutions. + +# References: +# [1] Durbin J (1968). +# "The Probability that the Sample Distribution Function Lies Between Two +# Parallel Straight Lines." +# Annals of Mathematical Statistics, 39, 398-411. +# [2] Pomeranz J (1974). +# "Exact Cumulative Distribution of the Kolmogorov-Smirnov Statistic for +# Small Samples (Algorithm 487)." +# Communications of the ACM, 17(12), 703-704. +# [3] Marsaglia G, Tsang WW, Wang J (2003). +# "Evaluating Kolmogorov's Distribution." +# Journal of Statistical Software, 8(18), 1-4. +# [4] LI-CHIEN, C. (1956). +# "On the exact distribution of the statistics of A. N. Kolmogorov and +# their asymptotic expansion." +# Acta Matematica Sinica, 6, 55-81. +# [5] KOROLYUK, V. S. (1960). +# "Asymptotic analysis of the distribution of the maximum deviation in +# the Bernoulli scheme." +# Theor. Probability Appl., 4, 339-366. +# [6] Pelz W, Good IJ (1976). +# "Approximating the Lower Tail-areas of the Kolmogorov-Smirnov One-sample +# Statistic." +# Journal of the Royal Statistical Society, Series B, 38(2), 152-156. +# [7] Simard, R., L'Ecuyer, P. (2011) +# "Computing the Two-Sided Kolmogorov-Smirnov Distribution", +# Journal of Statistical Software, Vol 39, 11, 1-18. +# [8] Carvalho, Luis (2015) +# "An Improved Evaluation of Kolmogorov's Distribution" +# Journal of Statistical Software, Code Snippets; Vol 65(3), 1-8. +# [9] Amit Moscovich, Boaz Nadler (2017) +# "Fast calculation of boundary crossing probabilities for Poisson +# processes", +# Statistics & Probability Letters, Vol 123, 177-182. + + +import numpy as np +import scipy.special +import scipy.special._ufuncs as scu +from scipy.stats._finite_differences import _derivative + +_E128 = 128 +_EP128 = np.ldexp(np.longdouble(1), _E128) +_EM128 = np.ldexp(np.longdouble(1), -_E128) + +_SQRT2PI = np.sqrt(2 * np.pi) +_LOG_2PI = np.log(2 * np.pi) +_MIN_LOG = -708 +_SQRT3 = np.sqrt(3) +_PI_SQUARED = np.pi ** 2 +_PI_FOUR = np.pi ** 4 +_PI_SIX = np.pi ** 6 + +# [Lifted from _loggamma.pxd.] If B_m are the Bernoulli numbers, +# then Stirling coeffs are B_{2j}/(2j)/(2j-1) for j=8,...1. +_STIRLING_COEFFS = [-2.955065359477124183e-2, 6.4102564102564102564e-3, + -1.9175269175269175269e-3, 8.4175084175084175084e-4, + -5.952380952380952381e-4, 7.9365079365079365079e-4, + -2.7777777777777777778e-3, 8.3333333333333333333e-2] + + +def _log_nfactorial_div_n_pow_n(n): + # Computes n! / n**n + # = (n-1)! / n**(n-1) + # Uses Stirling's approximation, but removes n*log(n) up-front to + # avoid subtractive cancellation. + # = log(n)/2 - n + log(sqrt(2pi)) + sum B_{2j}/(2j)/(2j-1)/n**(2j-1) + rn = 1.0/n + return np.log(n)/2 - n + _LOG_2PI/2 + rn * np.polyval(_STIRLING_COEFFS, rn/n) + + +def _clip_prob(p): + """clips a probability to range 0<=p<=1.""" + return np.clip(p, 0.0, 1.0) + + +def _select_and_clip_prob(cdfprob, sfprob, cdf=True): + """Selects either the CDF or SF, and then clips to range 0<=p<=1.""" + p = np.where(cdf, cdfprob, sfprob) + return _clip_prob(p) + + +def _kolmogn_DMTW(n, d, cdf=True): + r"""Computes the Kolmogorov CDF: Pr(D_n <= d) using the MTW approach to + the Durbin matrix algorithm. + + Durbin (1968); Marsaglia, Tsang, Wang (2003). [1], [3]. + """ + # Write d = (k-h)/n, where k is positive integer and 0 <= h < 1 + # Generate initial matrix H of size m*m where m=(2k-1) + # Compute k-th row of (n!/n^n) * H^n, scaling intermediate results. + # Requires memory O(m^2) and computation O(m^2 log(n)). + # Most suitable for small m. + + if d >= 1.0: + return _select_and_clip_prob(1.0, 0.0, cdf) + nd = n * d + if nd <= 0.5: + return _select_and_clip_prob(0.0, 1.0, cdf) + k = int(np.ceil(nd)) + h = k - nd + m = 2 * k - 1 + + H = np.zeros([m, m]) + + # Initialize: v is first column (and last row) of H + # v[j] = (1-h^(j+1)/(j+1)! (except for v[-1]) + # w[j] = 1/(j)! + # q = k-th row of H (actually i!/n^i*H^i) + intm = np.arange(1, m + 1) + v = 1.0 - h ** intm + w = np.empty(m) + fac = 1.0 + for j in intm: + w[j - 1] = fac + fac /= j # This might underflow. Isn't a problem. + v[j - 1] *= fac + tt = max(2 * h - 1.0, 0)**m - 2*h**m + v[-1] = (1.0 + tt) * fac + + for i in range(1, m): + H[i - 1:, i] = w[:m - i + 1] + H[:, 0] = v + H[-1, :] = np.flip(v, axis=0) + + Hpwr = np.eye(np.shape(H)[0]) # Holds intermediate powers of H + nn = n + expnt = 0 # Scaling of Hpwr + Hexpnt = 0 # Scaling of H + while nn > 0: + if nn % 2: + Hpwr = np.matmul(Hpwr, H) + expnt += Hexpnt + H = np.matmul(H, H) + Hexpnt *= 2 + # Scale as needed. + if np.abs(H[k - 1, k - 1]) > _EP128: + H /= _EP128 + Hexpnt += _E128 + nn = nn // 2 + + p = Hpwr[k - 1, k - 1] + + # Multiply by n!/n^n + for i in range(1, n + 1): + p = i * p / n + if np.abs(p) < _EM128: + p *= _EP128 + expnt -= _E128 + + # unscale + if expnt != 0: + p = np.ldexp(p, expnt) + + return _select_and_clip_prob(p, 1.0-p, cdf) + + +def _pomeranz_compute_j1j2(i, n, ll, ceilf, roundf): + """Compute the endpoints of the interval for row i.""" + if i == 0: + j1, j2 = -ll - ceilf - 1, ll + ceilf - 1 + else: + # i + 1 = 2*ip1div2 + ip1mod2 + ip1div2, ip1mod2 = divmod(i + 1, 2) + if ip1mod2 == 0: # i is odd + if ip1div2 == n + 1: + j1, j2 = n - ll - ceilf - 1, n + ll + ceilf - 1 + else: + j1, j2 = ip1div2 - 1 - ll - roundf - 1, ip1div2 + ll - 1 + ceilf - 1 + else: + j1, j2 = ip1div2 - 1 - ll - 1, ip1div2 + ll + roundf - 1 + + return max(j1 + 2, 0), min(j2, n) + + +def _kolmogn_Pomeranz(n, x, cdf=True): + r"""Computes Pr(D_n <= d) using the Pomeranz recursion algorithm. + + Pomeranz (1974) [2] + """ + + # V is n*(2n+2) matrix. + # Each row is convolution of the previous row and probabilities from a + # Poisson distribution. + # Desired CDF probability is n! V[n-1, 2n+1] (final entry in final row). + # Only two rows are needed at any given stage: + # - Call them V0 and V1. + # - Swap each iteration + # Only a few (contiguous) entries in each row can be non-zero. + # - Keep track of start and end (j1 and j2 below) + # - V0s and V1s track the start in the two rows + # Scale intermediate results as needed. + # Only a few different Poisson distributions can occur + t = n * x + ll = int(np.floor(t)) + f = 1.0 * (t - ll) # fractional part of t + g = min(f, 1.0 - f) + ceilf = (1 if f > 0 else 0) + roundf = (1 if f > 0.5 else 0) + npwrs = 2 * (ll + 1) # Maximum number of powers needed in convolutions + gpower = np.empty(npwrs) # gpower = (g/n)^m/m! + twogpower = np.empty(npwrs) # twogpower = (2g/n)^m/m! + onem2gpower = np.empty(npwrs) # onem2gpower = ((1-2g)/n)^m/m! + # gpower etc are *almost* Poisson probs, just missing normalizing factor. + + gpower[0] = 1.0 + twogpower[0] = 1.0 + onem2gpower[0] = 1.0 + expnt = 0 + g_over_n, two_g_over_n, one_minus_two_g_over_n = g/n, 2*g/n, (1 - 2*g)/n + for m in range(1, npwrs): + gpower[m] = gpower[m - 1] * g_over_n / m + twogpower[m] = twogpower[m - 1] * two_g_over_n / m + onem2gpower[m] = onem2gpower[m - 1] * one_minus_two_g_over_n / m + + V0 = np.zeros([npwrs]) + V1 = np.zeros([npwrs]) + V1[0] = 1 # first row + V0s, V1s = 0, 0 # start indices of the two rows + + j1, j2 = _pomeranz_compute_j1j2(0, n, ll, ceilf, roundf) + for i in range(1, 2 * n + 2): + # Preserve j1, V1, V1s, V0s from last iteration + k1 = j1 + V0, V1 = V1, V0 + V0s, V1s = V1s, V0s + V1.fill(0.0) + j1, j2 = _pomeranz_compute_j1j2(i, n, ll, ceilf, roundf) + if i == 1 or i == 2 * n + 1: + pwrs = gpower + else: + pwrs = (twogpower if i % 2 else onem2gpower) + ln2 = j2 - k1 + 1 + if ln2 > 0: + conv = np.convolve(V0[k1 - V0s:k1 - V0s + ln2], pwrs[:ln2]) + conv_start = j1 - k1 # First index to use from conv + conv_len = j2 - j1 + 1 # Number of entries to use from conv + V1[:conv_len] = conv[conv_start:conv_start + conv_len] + # Scale to avoid underflow. + if 0 < np.max(V1) < _EM128: + V1 *= _EP128 + expnt -= _E128 + V1s = V0s + j1 - k1 + + # multiply by n! + ans = V1[n - V1s] + for m in range(1, n + 1): + if np.abs(ans) > _EP128: + ans *= _EM128 + expnt += _E128 + ans *= m + + # Undo any intermediate scaling + if expnt != 0: + ans = np.ldexp(ans, expnt) + ans = _select_and_clip_prob(ans, 1.0 - ans, cdf) + return ans + + +def _kolmogn_PelzGood(n, x, cdf=True): + """Computes the Pelz-Good approximation to Prob(Dn <= x) with 0<=x<=1. + + Start with Li-Chien, Korolyuk approximation: + Prob(Dn <= x) ~ K0(z) + K1(z)/sqrt(n) + K2(z)/n + K3(z)/n**1.5 + where z = x*sqrt(n). + Transform each K_(z) using Jacobi theta functions into a form suitable + for small z. + Pelz-Good (1976). [6] + """ + if x <= 0.0: + return _select_and_clip_prob(0.0, 1.0, cdf=cdf) + if x >= 1.0: + return _select_and_clip_prob(1.0, 0.0, cdf=cdf) + + z = np.sqrt(n) * x + zsquared, zthree, zfour, zsix = z**2, z**3, z**4, z**6 + + qlog = -_PI_SQUARED / 8 / zsquared + if qlog < _MIN_LOG: # z ~ 0.041743441416853426 + return _select_and_clip_prob(0.0, 1.0, cdf=cdf) + + q = np.exp(qlog) + + # Coefficients of terms in the sums for K1, K2 and K3 + k1a = -zsquared + k1b = _PI_SQUARED / 4 + + k2a = 6 * zsix + 2 * zfour + k2b = (2 * zfour - 5 * zsquared) * _PI_SQUARED / 4 + k2c = _PI_FOUR * (1 - 2 * zsquared) / 16 + + k3d = _PI_SIX * (5 - 30 * zsquared) / 64 + k3c = _PI_FOUR * (-60 * zsquared + 212 * zfour) / 16 + k3b = _PI_SQUARED * (135 * zfour - 96 * zsix) / 4 + k3a = -30 * zsix - 90 * z**8 + + K0to3 = np.zeros(4) + # Use a Horner scheme to evaluate sum c_i q^(i^2) + # Reduces to a sum over odd integers. + maxk = int(np.ceil(16 * z / np.pi)) + for k in range(maxk, 0, -1): + m = 2 * k - 1 + msquared, mfour, msix = m**2, m**4, m**6 + qpower = np.power(q, 8 * k) + coeffs = np.array([1.0, + k1a + k1b*msquared, + k2a + k2b*msquared + k2c*mfour, + k3a + k3b*msquared + k3c*mfour + k3d*msix]) + K0to3 *= qpower + K0to3 += coeffs + K0to3 *= q + K0to3 *= _SQRT2PI + # z**10 > 0 as z > 0.04 + K0to3 /= np.array([z, 6 * zfour, 72 * z**7, 6480 * z**10]) + + # Now do the other sum over the other terms, all integers k + # K_2: (pi^2 k^2) q^(k^2), + # K_3: (3pi^2 k^2 z^2 - pi^4 k^4)*q^(k^2) + # Don't expect much subtractive cancellation so use direct calculation + q = np.exp(-_PI_SQUARED / 2 / zsquared) + ks = np.arange(maxk, 0, -1) + ksquared = ks ** 2 + sqrt3z = _SQRT3 * z + kspi = np.pi * ks + qpwers = q ** ksquared + k2extra = np.sum(ksquared * qpwers) + k2extra *= _PI_SQUARED * _SQRT2PI/(-36 * zthree) + K0to3[2] += k2extra + k3extra = np.sum((sqrt3z + kspi) * (sqrt3z - kspi) * ksquared * qpwers) + k3extra *= _PI_SQUARED * _SQRT2PI/(216 * zsix) + K0to3[3] += k3extra + powers_of_n = np.power(n * 1.0, np.arange(len(K0to3)) / 2.0) + K0to3 /= powers_of_n + + if not cdf: + K0to3 *= -1 + K0to3[0] += 1 + + Ksum = sum(K0to3) + return Ksum + + +def _kolmogn(n, x, cdf=True): + """Computes the CDF(or SF) for the two-sided Kolmogorov-Smirnov statistic. + + x must be of type float, n of type integer. + + Simard & L'Ecuyer (2011) [7]. + """ + if np.isnan(n): + return n # Keep the same type of nan + if int(n) != n or n <= 0: + return np.nan + if x >= 1.0: + return _select_and_clip_prob(1.0, 0.0, cdf=cdf) + if x <= 0.0: + return _select_and_clip_prob(0.0, 1.0, cdf=cdf) + t = n * x + if t <= 1.0: # Ruben-Gambino: 1/2n <= x <= 1/n + if t <= 0.5: + return _select_and_clip_prob(0.0, 1.0, cdf=cdf) + if n <= 140: + prob = np.prod(np.arange(1, n+1) * (1.0/n) * (2*t - 1)) + else: + prob = np.exp(_log_nfactorial_div_n_pow_n(n) + n * np.log(2*t-1)) + return _select_and_clip_prob(prob, 1.0 - prob, cdf=cdf) + if t >= n - 1: # Ruben-Gambino + prob = 2 * (1.0 - x)**n + return _select_and_clip_prob(1 - prob, prob, cdf=cdf) + if x >= 0.5: # Exact: 2 * smirnov + prob = 2 * scipy.special.smirnov(n, x) + return _select_and_clip_prob(1.0 - prob, prob, cdf=cdf) + + nxsquared = t * x + if n <= 140: + if nxsquared <= 0.754693: + prob = _kolmogn_DMTW(n, x, cdf=True) + return _select_and_clip_prob(prob, 1.0 - prob, cdf=cdf) + if nxsquared <= 4: + prob = _kolmogn_Pomeranz(n, x, cdf=True) + return _select_and_clip_prob(prob, 1.0 - prob, cdf=cdf) + # Now use Miller approximation of 2*smirnov + prob = 2 * scipy.special.smirnov(n, x) + return _select_and_clip_prob(1.0 - prob, prob, cdf=cdf) + + # Split CDF and SF as they have different cutoffs on nxsquared. + if not cdf: + if nxsquared >= 370.0: + return 0.0 + if nxsquared >= 2.2: + prob = 2 * scipy.special.smirnov(n, x) + return _clip_prob(prob) + # Fall through and compute the SF as 1.0-CDF + if nxsquared >= 18.0: + cdfprob = 1.0 + elif n <= 100000 and n * x**1.5 <= 1.4: + cdfprob = _kolmogn_DMTW(n, x, cdf=True) + else: + cdfprob = _kolmogn_PelzGood(n, x, cdf=True) + return _select_and_clip_prob(cdfprob, 1.0 - cdfprob, cdf=cdf) + + +def _kolmogn_p(n, x): + """Computes the PDF for the two-sided Kolmogorov-Smirnov statistic. + + x must be of type float, n of type integer. + """ + if np.isnan(n): + return n # Keep the same type of nan + if int(n) != n or n <= 0: + return np.nan + if x >= 1.0 or x <= 0: + return 0 + t = n * x + if t <= 1.0: + # Ruben-Gambino: n!/n^n * (2t-1)^n -> 2 n!/n^n * n^2 * (2t-1)^(n-1) + if t <= 0.5: + return 0.0 + if n <= 140: + prd = np.prod(np.arange(1, n) * (1.0 / n) * (2 * t - 1)) + else: + prd = np.exp(_log_nfactorial_div_n_pow_n(n) + (n-1) * np.log(2 * t - 1)) + return prd * 2 * n**2 + if t >= n - 1: + # Ruben-Gambino : 1-2(1-x)**n -> 2n*(1-x)**(n-1) + return 2 * (1.0 - x) ** (n-1) * n + if x >= 0.5: + return 2 * scipy.stats.ksone.pdf(x, n) + + # Just take a small delta. + # Ideally x +/- delta would stay within [i/n, (i+1)/n] for some integer a. + # as the CDF is a piecewise degree n polynomial. + # It has knots at 1/n, 2/n, ... (n-1)/n + # and is not a C-infinity function at the knots + delta = x / 2.0**16 + delta = min(delta, x - 1.0/n) + delta = min(delta, 0.5 - x) + + def _kk(_x): + return kolmogn(n, _x) + + return _derivative(_kk, x, dx=delta, order=5) + + +def _kolmogni(n, p, q): + """Computes the PPF/ISF of kolmogn. + + n of type integer, n>= 1 + p is the CDF, q the SF, p+q=1 + """ + if np.isnan(n): + return n # Keep the same type of nan + if int(n) != n or n <= 0: + return np.nan + if p <= 0: + return 1.0/n + if q <= 0: + return 1.0 + delta = np.exp((np.log(p) - scipy.special.loggamma(n+1))/n) + if delta <= 1.0/n: + return (delta + 1.0 / n) / 2 + x = -np.expm1(np.log(q/2.0)/n) + if x >= 1 - 1.0/n: + return x + x1 = scu._kolmogci(p)/np.sqrt(n) + x1 = min(x1, 1.0 - 1.0/n) + + def _f(x): + return _kolmogn(n, x) - p + + return scipy.optimize.brentq(_f, 1.0/n, x1, xtol=1e-14) + + +def kolmogn(n, x, cdf=True): + """Computes the CDF for the two-sided Kolmogorov-Smirnov distribution. + + The two-sided Kolmogorov-Smirnov distribution has as its CDF Pr(D_n <= x), + for a sample of size n drawn from a distribution with CDF F(t), where + :math:`D_n &= sup_t |F_n(t) - F(t)|`, and + :math:`F_n(t)` is the Empirical Cumulative Distribution Function of the sample. + + Parameters + ---------- + n : int, array_like + the number of samples + x : float, array_like + The K-S statistic, float between 0 and 1 + cdf : bool, optional + whether to compute the CDF(default=true) or the SF. + + Returns + ------- + cdf : ndarray + CDF (or SF it cdf is False) at the specified locations. + + The return value has shape the result of numpy broadcasting n and x. + """ + it = np.nditer([n, x, cdf, None], flags=['zerosize_ok'], + op_dtypes=[None, np.float64, np.bool_, np.float64]) + for _n, _x, _cdf, z in it: + if np.isnan(_n): + z[...] = _n + continue + if int(_n) != _n: + raise ValueError(f'n is not integral: {_n}') + z[...] = _kolmogn(int(_n), _x, cdf=_cdf) + result = it.operands[-1] + return result + + +def kolmognp(n, x): + """Computes the PDF for the two-sided Kolmogorov-Smirnov distribution. + + Parameters + ---------- + n : int, array_like + the number of samples + x : float, array_like + The K-S statistic, float between 0 and 1 + + Returns + ------- + pdf : ndarray + The PDF at the specified locations + + The return value has shape the result of numpy broadcasting n and x. + """ + it = np.nditer([n, x, None]) + for _n, _x, z in it: + if np.isnan(_n): + z[...] = _n + continue + if int(_n) != _n: + raise ValueError(f'n is not integral: {_n}') + z[...] = _kolmogn_p(int(_n), _x) + result = it.operands[-1] + return result + + +def kolmogni(n, q, cdf=True): + """Computes the PPF(or ISF) for the two-sided Kolmogorov-Smirnov distribution. + + Parameters + ---------- + n : int, array_like + the number of samples + q : float, array_like + Probabilities, float between 0 and 1 + cdf : bool, optional + whether to compute the PPF(default=true) or the ISF. + + Returns + ------- + ppf : ndarray + PPF (or ISF if cdf is False) at the specified locations + + The return value has shape the result of numpy broadcasting n and x. + """ + it = np.nditer([n, q, cdf, None]) + for _n, _q, _cdf, z in it: + if np.isnan(_n): + z[...] = _n + continue + if int(_n) != _n: + raise ValueError(f'n is not integral: {_n}') + _pcdf, _psf = (_q, 1-_q) if _cdf else (1-_q, _q) + z[...] = _kolmogni(int(_n), _pcdf, _psf) + result = it.operands[-1] + return result diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_levy_stable/__init__.py b/venv/lib/python3.12/site-packages/scipy/stats/_levy_stable/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..127c2464cebd4c168e5f01f0c88cfcdd1c9da8ad --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_levy_stable/__init__.py @@ -0,0 +1,1247 @@ +# + +import warnings +from functools import partial + +import numpy as np + +from scipy import optimize +from scipy import integrate +from scipy.integrate._quadrature import _builtincoeffs +from scipy import interpolate +from scipy.interpolate import RectBivariateSpline +import scipy._external.array_api_extra as xpx +import scipy.special as sc +from .._distn_infrastructure import rv_continuous, _ShapeInfo, rv_continuous_frozen +from .._continuous_distns import uniform, expon, _norm_pdf, _norm_cdf +from .levyst import Nolan +from scipy._lib.doccer import inherit_docstring_from + + +__all__ = ["levy_stable", "levy_stable_gen", "pdf_from_cf_with_fft"] + +# Stable distributions are known for various parameterisations +# some being advantageous for numerical considerations and others +# useful due to their location/scale awareness. +# +# Here we follow [NO] convention (see the references in the docstring +# for levy_stable_gen below). +# +# S0 / Z0 / x0 (aka Zoleterav's M) +# S1 / Z1 / x1 +# +# Where S* denotes parameterisation, Z* denotes standardized +# version where gamma = 1, delta = 0 and x* denotes variable. +# +# Scipy's original Stable was a random variate generator. It +# uses S1 and unfortunately is not a location/scale aware. + + +# default numerical integration tolerance +# used for epsrel in piecewise and both epsrel and epsabs in dni +# (epsabs needed in dni since weighted quad requires epsabs > 0) +_QUAD_EPS = 1.2e-14 + + +def _Phi_Z0(alpha, t): + return ( + -np.tan(np.pi * alpha / 2) * (np.abs(t) ** (1 - alpha) - 1) + if alpha != 1 + else -2.0 * np.log(np.abs(t)) / np.pi + ) + + +def _Phi_Z1(alpha, t): + return ( + np.tan(np.pi * alpha / 2) + if alpha != 1 + else -2.0 * np.log(np.abs(t)) / np.pi + ) + + +def _cf(Phi, t, alpha, beta): + """Characteristic function.""" + return np.exp( + -(np.abs(t) ** alpha) * (1 - 1j * beta * np.sign(t) * Phi(alpha, t)) + ) + + +_cf_Z0 = partial(_cf, _Phi_Z0) +_cf_Z1 = partial(_cf, _Phi_Z1) + + +def _pdf_single_value_cf_integrate(Phi, x, alpha, beta, **kwds): + """To improve DNI accuracy convert characteristic function in to real + valued integral using Euler's formula, then exploit cosine symmetry to + change limits to [0, inf). Finally use cosine addition formula to split + into two parts that can be handled by weighted quad pack. + """ + quad_eps = kwds.get("quad_eps", _QUAD_EPS) + + def integrand1(t): + if t == 0: + return 0 + return np.exp(-(t ** alpha)) * ( + np.cos(beta * (t ** alpha) * Phi(alpha, t)) + ) + + def integrand2(t): + if t == 0: + return 0 + return np.exp(-(t ** alpha)) * ( + np.sin(beta * (t ** alpha) * Phi(alpha, t)) + ) + + with np.errstate(invalid="ignore"): + int1, *ret1 = integrate.quad( + integrand1, + 0, + np.inf, + weight="cos", + wvar=x, + limit=1000, + epsabs=quad_eps, + epsrel=quad_eps, + full_output=1, + ) + + int2, *ret2 = integrate.quad( + integrand2, + 0, + np.inf, + weight="sin", + wvar=x, + limit=1000, + epsabs=quad_eps, + epsrel=quad_eps, + full_output=1, + ) + + return (int1 + int2) / np.pi + + +_pdf_single_value_cf_integrate_Z0 = partial( + _pdf_single_value_cf_integrate, _Phi_Z0 +) +_pdf_single_value_cf_integrate_Z1 = partial( + _pdf_single_value_cf_integrate, _Phi_Z1 +) + + +def _nolan_round_x_near_zeta(x0, alpha, zeta, x_tol_near_zeta): + """Round x close to zeta for Nolan's method in [NO].""" + # "8. When |x0-beta*tan(pi*alpha/2)| is small, the + # computations of the density and cumulative have numerical problems. + # The program works around this by setting + # z = beta*tan(pi*alpha/2) when + # |z-beta*tan(pi*alpha/2)| < tol(5)*alpha**(1/alpha). + # (The bound on the right is ad hoc, to get reasonable behavior + # when alpha is small)." + # where tol(5) = 0.5e-2 by default. + # + # We seem to have partially addressed this through re-expression of + # g(theta) here, but it still needs to be used in some extreme cases. + # Perhaps tol(5) = 0.5e-2 could be reduced for our implementation. + if np.abs(x0 - zeta) < x_tol_near_zeta * alpha ** (1 / alpha): + x0 = zeta + return x0 + + +def _nolan_round_difficult_input( + x0, alpha, beta, zeta, x_tol_near_zeta, alpha_tol_near_one +): + """Round difficult input values for Nolan's method in [NO].""" + + # following Nolan's STABLE, + # "1. When 0 < |alpha-1| < 0.005, the program has numerical problems + # evaluating the pdf and cdf. The current version of the program sets + # alpha=1 in these cases. This approximation is not bad in the S0 + # parameterization." + if np.abs(alpha - 1) < alpha_tol_near_one: + alpha = 1.0 + + # "2. When alpha=1 and |beta| < 0.005, the program has numerical + # problems. The current version sets beta=0." + # We seem to have addressed this through re-expression of g(theta) here + + x0 = _nolan_round_x_near_zeta(x0, alpha, zeta, x_tol_near_zeta) + return x0, alpha, beta + + +def _pdf_single_value_piecewise_Z1(x, alpha, beta, **kwds): + # convert from Nolan's S_1 (aka S) to S_0 (aka Zolaterev M) + # parameterization + + zeta = -beta * np.tan(np.pi * alpha / 2.0) + x0 = x + zeta if alpha != 1 else x + + return _pdf_single_value_piecewise_Z0(x0, alpha, beta, **kwds) + + +def _pdf_single_value_piecewise_Z0(x0, alpha, beta, **kwds): + + quad_eps = kwds.get("quad_eps", _QUAD_EPS) + x_tol_near_zeta = kwds.get("piecewise_x_tol_near_zeta", 0.005) + alpha_tol_near_one = kwds.get("piecewise_alpha_tol_near_one", 0.005) + + zeta = -beta * np.tan(np.pi * alpha / 2.0) + x0, alpha, beta = _nolan_round_difficult_input( + x0, alpha, beta, zeta, x_tol_near_zeta, alpha_tol_near_one + ) + + # some other known distribution pdfs / analytical cases + # TODO: add more where possible with test coverage, + # eg https://en.wikipedia.org/wiki/Stable_distribution#Other_analytic_cases + if alpha == 2.0: + # normal + return _norm_pdf(x0 / np.sqrt(2)) / np.sqrt(2) + elif alpha == 0.5 and beta == 1.0: + # levy + # since S(1/2, 1, gamma, delta; ) == + # S(1/2, 1, gamma, gamma + delta; ). + _x = x0 + 1 + if _x <= 0: + return 0 + + return 1 / np.sqrt(2 * np.pi * _x) / _x * np.exp(-1 / (2 * _x)) + elif alpha == 0.5 and beta == 0.0 and x0 != 0: + # analytical solution [HO] + S, C = sc.fresnel([1 / np.sqrt(2 * np.pi * np.abs(x0))]) + arg = 1 / (4 * np.abs(x0)) + return ( + np.sin(arg) * (0.5 - S[0]) + np.cos(arg) * (0.5 - C[0]) + ) / np.sqrt(2 * np.pi * np.abs(x0) ** 3) + elif alpha == 1.0 and beta == 0.0: + # cauchy + return 1 / (1 + x0 ** 2) / np.pi + + return _pdf_single_value_piecewise_post_rounding_Z0( + x0, alpha, beta, quad_eps, x_tol_near_zeta + ) + + +def _pdf_single_value_piecewise_post_rounding_Z0(x0, alpha, beta, quad_eps, + x_tol_near_zeta): + """Calculate pdf using Nolan's methods as detailed in [NO].""" + + _nolan = Nolan(alpha, beta, x0) + zeta = _nolan.zeta + xi = _nolan.xi + c2 = _nolan.c2 + g = _nolan.g + + # round x0 to zeta again if needed. zeta was recomputed and may have + # changed due to floating point differences. + # See https://github.com/scipy/scipy/pull/18133 + x0 = _nolan_round_x_near_zeta(x0, alpha, zeta, x_tol_near_zeta) + # handle Nolan's initial case logic + if x0 == zeta: + return ( + sc.gamma(1 + 1 / alpha) + * np.cos(xi) + / np.pi + / ((1 + zeta ** 2) ** (1 / alpha / 2)) + ) + elif x0 < zeta: + return _pdf_single_value_piecewise_post_rounding_Z0( + -x0, alpha, -beta, quad_eps, x_tol_near_zeta + ) + + # following Nolan, we may now assume + # x0 > zeta when alpha != 1 + # beta != 0 when alpha == 1 + + # spare calculating integral on null set + # use isclose as macos has fp differences + if np.isclose(-xi, np.pi / 2, rtol=1e-014, atol=1e-014): + return 0.0 + + def integrand(theta): + # limit any numerical issues leading to g_1 < 0 near theta limits + g_1 = g(theta) + if not np.isfinite(g_1) or g_1 < 0: + g_1 = 0 + return g_1 * np.exp(-g_1) + + with np.errstate(all="ignore"): + peak = optimize.bisect( + lambda t: g(t) - 1, -xi, np.pi / 2, xtol=quad_eps + ) + + # this integrand can be very peaked, so we need to force + # QUADPACK to evaluate the function inside its support + # + + # lastly, we add additional samples at + # ~exp(-100), ~exp(-10), ~exp(-5), ~exp(-1) + # to improve QUADPACK's detection of rapidly descending tail behavior + # (this choice is fairly ad hoc) + tail_points = [ + optimize.bisect(lambda t: g(t) - exp_height, -xi, np.pi / 2) + for exp_height in [100, 10, 5] + # exp_height = 1 is handled by peak + ] + # Symmetry is used to ensure x > zeta as above. Integration + # becomes numerically unstable so let `quad` handle + # integration points + if beta != -1: + intg_points = [0, peak] + tail_points + else: + intg_points = None + + intg, *ret = integrate.quad( + integrand, + -xi, + np.pi / 2, + points=intg_points, + limit=100, + epsrel=quad_eps, + epsabs=0, + full_output=1, + ) + + return c2 * intg + + +def _cdf_single_value_piecewise_Z1(x, alpha, beta, **kwds): + # convert from Nolan's S_1 (aka S) to S_0 (aka Zolaterev M) + # parameterization + + zeta = -beta * np.tan(np.pi * alpha / 2.0) + x0 = x + zeta if alpha != 1 else x + + return _cdf_single_value_piecewise_Z0(x0, alpha, beta, **kwds) + + +def _cdf_single_value_piecewise_Z0(x0, alpha, beta, **kwds): + + quad_eps = kwds.get("quad_eps", _QUAD_EPS) + x_tol_near_zeta = kwds.get("piecewise_x_tol_near_zeta", 0.005) + alpha_tol_near_one = kwds.get("piecewise_alpha_tol_near_one", 0.005) + + zeta = -beta * np.tan(np.pi * alpha / 2.0) + x0, alpha, beta = _nolan_round_difficult_input( + x0, alpha, beta, zeta, x_tol_near_zeta, alpha_tol_near_one + ) + + # some other known distribution cdfs / analytical cases + # TODO: add more where possible with test coverage, + # eg https://en.wikipedia.org/wiki/Stable_distribution#Other_analytic_cases + if alpha == 2.0: + # normal + return _norm_cdf(x0 / np.sqrt(2)) + elif alpha == 0.5 and beta == 1.0: + # levy + # since S(1/2, 1, gamma, delta; ) == + # S(1/2, 1, gamma, gamma + delta; ). + _x = x0 + 1 + if _x <= 0: + return 0 + + return sc.erfc(np.sqrt(0.5 / _x)) + elif alpha == 1.0 and beta == 0.0: + # cauchy + return 0.5 + np.arctan(x0) / np.pi + + return _cdf_single_value_piecewise_post_rounding_Z0( + x0, alpha, beta, quad_eps, x_tol_near_zeta + ) + + +def _cdf_single_value_piecewise_post_rounding_Z0(x0, alpha, beta, quad_eps, + x_tol_near_zeta): + """Calculate cdf using Nolan's methods as detailed in [NO].""" + _nolan = Nolan(alpha, beta, x0) + zeta = _nolan.zeta + xi = _nolan.xi + c1 = _nolan.c1 + # c2 = _nolan.c2 + c3 = _nolan.c3 + g = _nolan.g + # round x0 to zeta again if needed. zeta was recomputed and may have + # changed due to floating point differences. + # See https://github.com/scipy/scipy/pull/18133 + x0 = _nolan_round_x_near_zeta(x0, alpha, zeta, x_tol_near_zeta) + # handle Nolan's initial case logic + if (alpha == 1 and beta < 0) or x0 < zeta: + # NOTE: Nolan's paper has a typo here! + # He states F(x) = 1 - F(x, alpha, -beta), but this is clearly + # incorrect since F(-infty) would be 1.0 in this case + # Indeed, the alpha != 1, x0 < zeta case is correct here. + return 1 - _cdf_single_value_piecewise_post_rounding_Z0( + -x0, alpha, -beta, quad_eps, x_tol_near_zeta + ) + elif x0 == zeta: + return 0.5 - xi / np.pi + + # following Nolan, we may now assume + # x0 > zeta when alpha != 1 + # beta > 0 when alpha == 1 + + # spare calculating integral on null set + # use isclose as macos has fp differences + if np.isclose(-xi, np.pi / 2, rtol=1e-014, atol=1e-014): + return c1 + + def integrand(theta): + g_1 = g(theta) + return np.exp(-g_1) + + with np.errstate(all="ignore"): + # shrink supports where required + left_support = -xi + right_support = np.pi / 2 + if alpha > 1: + # integrand(t) monotonic 0 to 1 + if integrand(-xi) != 0.0: + res = optimize.minimize( + integrand, + (-xi,), + method="L-BFGS-B", + bounds=[(-xi, np.pi / 2)], + ) + left_support = res.x[0] + else: + # integrand(t) monotonic 1 to 0 + if integrand(np.pi / 2) != 0.0: + res = optimize.minimize( + integrand, + (np.pi / 2,), + method="L-BFGS-B", + bounds=[(-xi, np.pi / 2)], + ) + right_support = res.x[0] + + intg, *ret = integrate.quad( + integrand, + left_support, + right_support, + points=[left_support, right_support], + limit=100, + epsrel=quad_eps, + epsabs=0, + full_output=1, + ) + + return c1 + c3 * intg + + +def _rvs_Z1(alpha, beta, size=None, random_state=None): + """Simulate random variables using Nolan's methods as detailed in [NO]. + """ + + def alpha1func(alpha, beta, TH, aTH, bTH, cosTH, tanTH, W): + return ( + 2 + / np.pi + * ( + (np.pi / 2 + bTH) * tanTH + - beta * np.log((np.pi / 2 * W * cosTH) / (np.pi / 2 + bTH)) + ) + ) + + def beta0func(alpha, beta, TH, aTH, bTH, cosTH, tanTH, W): + return ( + W + / (cosTH / np.tan(aTH) + np.sin(TH)) + * ((np.cos(aTH) + np.sin(aTH) * tanTH) / W) ** (1.0 / alpha) + ) + + def otherwise(alpha, beta, TH, aTH, bTH, cosTH, tanTH, W): + # alpha is not 1 and beta is not 0 + val0 = beta * np.tan(np.pi * alpha / 2) + th0 = np.arctan(val0) / alpha + val3 = W / (cosTH / np.tan(alpha * (th0 + TH)) + np.sin(TH)) + res3 = val3 * ( + ( + np.cos(aTH) + + np.sin(aTH) * tanTH + - val0 * (np.sin(aTH) - np.cos(aTH) * tanTH) + ) + / W + ) ** (1.0 / alpha) + return res3 + + def alphanot1func(alpha, beta, TH, aTH, bTH, cosTH, tanTH, W): + return xpx.apply_where( + beta == 0, (alpha, beta, TH, aTH, bTH, cosTH, tanTH, W), + beta0func, otherwise) + + alpha = np.broadcast_to(alpha, size) + beta = np.broadcast_to(beta, size) + TH = uniform.rvs( + loc=-np.pi / 2.0, scale=np.pi, size=size, random_state=random_state + ) + W = expon.rvs(size=size, random_state=random_state) + aTH = alpha * TH + bTH = beta * TH + cosTH = np.cos(TH) + tanTH = np.tan(TH) + return xpx.apply_where( + alpha == 1, (alpha, beta, TH, aTH, bTH, cosTH, tanTH, W), + alpha1func, alphanot1func) + + +def _fitstart_S0(data): + alpha, beta, delta1, gamma = _fitstart_S1(data) + + # Formulas for mapping parameters in S1 parameterization to + # those in S0 parameterization can be found in [NO]. Note that + # only delta changes. + if alpha != 1: + delta0 = delta1 + beta * gamma * np.tan(np.pi * alpha / 2.0) + else: + delta0 = delta1 + 2 * beta * gamma * np.log(gamma) / np.pi + + return alpha, beta, delta0, gamma + + +def _fitstart_S1(data): + # We follow McCullock 1986 method - Simple Consistent Estimators + # of Stable Distribution Parameters + + # fmt: off + # Table III and IV + nu_alpha_range = [2.439, 2.5, 2.6, 2.7, 2.8, 3, 3.2, 3.5, 4, + 5, 6, 8, 10, 15, 25] + nu_beta_range = [0, 0.1, 0.2, 0.3, 0.5, 0.7, 1] + + # table III - alpha = psi_1(nu_alpha, nu_beta) + alpha_table = np.array([ + [2.000, 2.000, 2.000, 2.000, 2.000, 2.000, 2.000], + [1.916, 1.924, 1.924, 1.924, 1.924, 1.924, 1.924], + [1.808, 1.813, 1.829, 1.829, 1.829, 1.829, 1.829], + [1.729, 1.730, 1.737, 1.745, 1.745, 1.745, 1.745], + [1.664, 1.663, 1.663, 1.668, 1.676, 1.676, 1.676], + [1.563, 1.560, 1.553, 1.548, 1.547, 1.547, 1.547], + [1.484, 1.480, 1.471, 1.460, 1.448, 1.438, 1.438], + [1.391, 1.386, 1.378, 1.364, 1.337, 1.318, 1.318], + [1.279, 1.273, 1.266, 1.250, 1.210, 1.184, 1.150], + [1.128, 1.121, 1.114, 1.101, 1.067, 1.027, 0.973], + [1.029, 1.021, 1.014, 1.004, 0.974, 0.935, 0.874], + [0.896, 0.892, 0.884, 0.883, 0.855, 0.823, 0.769], + [0.818, 0.812, 0.806, 0.801, 0.780, 0.756, 0.691], + [0.698, 0.695, 0.692, 0.689, 0.676, 0.656, 0.597], + [0.593, 0.590, 0.588, 0.586, 0.579, 0.563, 0.513]]).T + # transpose because interpolation with `RectBivariateSpline` is with + # `nu_beta` as `x` and `nu_alpha` as `y` + + # table IV - beta = psi_2(nu_alpha, nu_beta) + beta_table = np.array([ + [0, 2.160, 1.000, 1.000, 1.000, 1.000, 1.000], + [0, 1.592, 3.390, 1.000, 1.000, 1.000, 1.000], + [0, 0.759, 1.800, 1.000, 1.000, 1.000, 1.000], + [0, 0.482, 1.048, 1.694, 1.000, 1.000, 1.000], + [0, 0.360, 0.760, 1.232, 2.229, 1.000, 1.000], + [0, 0.253, 0.518, 0.823, 1.575, 1.000, 1.000], + [0, 0.203, 0.410, 0.632, 1.244, 1.906, 1.000], + [0, 0.165, 0.332, 0.499, 0.943, 1.560, 1.000], + [0, 0.136, 0.271, 0.404, 0.689, 1.230, 2.195], + [0, 0.109, 0.216, 0.323, 0.539, 0.827, 1.917], + [0, 0.096, 0.190, 0.284, 0.472, 0.693, 1.759], + [0, 0.082, 0.163, 0.243, 0.412, 0.601, 1.596], + [0, 0.074, 0.147, 0.220, 0.377, 0.546, 1.482], + [0, 0.064, 0.128, 0.191, 0.330, 0.478, 1.362], + [0, 0.056, 0.112, 0.167, 0.285, 0.428, 1.274]]).T + + # Table V and VII + # These are ordered with decreasing `alpha_range`; so we will need to + # reverse them as required by RectBivariateSpline. + alpha_range = [2, 1.9, 1.8, 1.7, 1.6, 1.5, 1.4, 1.3, 1.2, 1.1, + 1, 0.9, 0.8, 0.7, 0.6, 0.5][::-1] + beta_range = [0, 0.25, 0.5, 0.75, 1] + + # Table V - nu_c = psi_3(alpha, beta) + nu_c_table = np.array([ + [1.908, 1.908, 1.908, 1.908, 1.908], + [1.914, 1.915, 1.916, 1.918, 1.921], + [1.921, 1.922, 1.927, 1.936, 1.947], + [1.927, 1.930, 1.943, 1.961, 1.987], + [1.933, 1.940, 1.962, 1.997, 2.043], + [1.939, 1.952, 1.988, 2.045, 2.116], + [1.946, 1.967, 2.022, 2.106, 2.211], + [1.955, 1.984, 2.067, 2.188, 2.333], + [1.965, 2.007, 2.125, 2.294, 2.491], + [1.980, 2.040, 2.205, 2.435, 2.696], + [2.000, 2.085, 2.311, 2.624, 2.973], + [2.040, 2.149, 2.461, 2.886, 3.356], + [2.098, 2.244, 2.676, 3.265, 3.912], + [2.189, 2.392, 3.004, 3.844, 4.775], + [2.337, 2.634, 3.542, 4.808, 6.247], + [2.588, 3.073, 4.534, 6.636, 9.144]])[::-1].T + # transpose because interpolation with `RectBivariateSpline` is with + # `beta` as `x` and `alpha` as `y` + + # Table VII - nu_zeta = psi_5(alpha, beta) + nu_zeta_table = np.array([ + [0, 0.000, 0.000, 0.000, 0.000], + [0, -0.017, -0.032, -0.049, -0.064], + [0, -0.030, -0.061, -0.092, -0.123], + [0, -0.043, -0.088, -0.132, -0.179], + [0, -0.056, -0.111, -0.170, -0.232], + [0, -0.066, -0.134, -0.206, -0.283], + [0, -0.075, -0.154, -0.241, -0.335], + [0, -0.084, -0.173, -0.276, -0.390], + [0, -0.090, -0.192, -0.310, -0.447], + [0, -0.095, -0.208, -0.346, -0.508], + [0, -0.098, -0.223, -0.380, -0.576], + [0, -0.099, -0.237, -0.424, -0.652], + [0, -0.096, -0.250, -0.469, -0.742], + [0, -0.089, -0.262, -0.520, -0.853], + [0, -0.078, -0.272, -0.581, -0.997], + [0, -0.061, -0.279, -0.659, -1.198]])[::-1].T + # fmt: on + + psi_1 = RectBivariateSpline(nu_beta_range, nu_alpha_range, + alpha_table, kx=1, ky=1, s=0) + + def psi_1_1(nu_beta, nu_alpha): + return psi_1(nu_beta, nu_alpha) \ + if nu_beta > 0 else psi_1(-nu_beta, nu_alpha) + + psi_2 = RectBivariateSpline(nu_beta_range, nu_alpha_range, + beta_table, kx=1, ky=1, s=0) + + def psi_2_1(nu_beta, nu_alpha): + return psi_2(nu_beta, nu_alpha) \ + if nu_beta > 0 else -psi_2(-nu_beta, nu_alpha) + + phi_3 = RectBivariateSpline(beta_range, alpha_range, nu_c_table, + kx=1, ky=1, s=0) + + def phi_3_1(beta, alpha): + return phi_3(beta, alpha) if beta > 0 else phi_3(-beta, alpha) + + phi_5 = RectBivariateSpline(beta_range, alpha_range, nu_zeta_table, + kx=1, ky=1, s=0) + + def phi_5_1(beta, alpha): + return phi_5(beta, alpha) if beta > 0 else -phi_5(-beta, alpha) + + # quantiles + p05 = np.percentile(data, 5) + p50 = np.percentile(data, 50) + p95 = np.percentile(data, 95) + p25 = np.percentile(data, 25) + p75 = np.percentile(data, 75) + + nu_alpha = (p95 - p05) / (p75 - p25) + nu_beta = (p95 + p05 - 2 * p50) / (p95 - p05) + + if nu_alpha >= 2.439: + eps = np.finfo(float).eps + alpha = np.clip(psi_1_1(nu_beta, nu_alpha)[0, 0], eps, 2.) + beta = np.clip(psi_2_1(nu_beta, nu_alpha)[0, 0], -1.0, 1.0) + else: + alpha = 2.0 + beta = np.sign(nu_beta) + c = (p75 - p25) / phi_3_1(beta, alpha)[0, 0] + zeta = p50 + c * phi_5_1(beta, alpha)[0, 0] + delta = zeta-beta*c*np.tan(np.pi*alpha/2.) if alpha != 1. else zeta + + return (alpha, beta, delta, c) + + +class levy_stable_gen(rv_continuous): + r"""A Levy-stable continuous random variable. + + %(before_notes)s + + See Also + -------- + levy, levy_l, cauchy, norm + + Notes + ----- + The distribution for `levy_stable` has characteristic function: + + .. math:: + + \varphi(t, \alpha, \beta, c, \mu) = + e^{it\mu -|ct|^{\alpha}(1-i\beta\operatorname{sign}(t)\Phi)} + + where + + - :math:`\alpha` is the stability parameter (:math:`0 < \alpha \le 2`), + - :math:`\beta` is the skewness parameter (:math:`-1 \le \beta \le 1`), + - :math:`c` is the scale parameter (:math:`c > 0`), + - :math:`\mu` is the location parameter (:math:`-\infty < \mu < \infty`). + + Two parameterizations of :math:`\Phi` are supported. + + The :math:`S_1` parameterization (default): + + .. math:: + + \Phi = \begin{cases} + \tan \left({\frac {\pi \alpha }{2}}\right)&\alpha \neq 1\\ + -{\frac {2}{\pi }}\log |t|&\alpha =1 + \end{cases} + + The :math:`S_0` parameterization: + + .. math:: + + \Phi = \begin{cases} + -\tan \left({\frac {\pi \alpha }{2}}\right)(|ct|^{1-\alpha}-1) + &\alpha \neq 1\\ + -{\frac {2}{\pi }}\log |ct|&\alpha =1 + \end{cases} + + + The probability density function for `levy_stable` is: + + .. math:: + + f(x) = \frac{1}{2\pi}\int_{-\infty}^\infty \varphi(t)e^{-ixt}\,dt + + where :math:`-\infty < x < \infty`. This integral does not have a known + closed form. + + `levy_stable` generalizes several distributions. Where possible, they + should be used instead. Specifically, when the shape parameters + assume the values in the table below, the corresponding equivalent + distribution should be used. + + ========= ======== =========== + ``alpha`` ``beta`` Equivalent + ========= ======== =========== + 1/2 -1 `levy_l` + 1/2 1 `levy` + 1 0 `cauchy` + 2 any `norm` (with ``scale=sqrt(2)``) + ========= ======== =========== + + Evaluation of the pdf uses Nolan's piecewise integration approach with the + Zolotarev :math:`M` parameterization by default. There is also the option + to use direct numerical integration of the standard parameterization of the + characteristic function or to evaluate by taking the FFT of the + characteristic function. + + The default method can changed by setting the class variable + ``levy_stable.pdf_default_method`` to one of 'piecewise' for Nolan's + approach, 'dni' for direct numerical integration, or 'fft-simpson' for the + FFT based approach. For the sake of backwards compatibility, the methods + 'best' and 'zolotarev' are equivalent to 'piecewise' and the method + 'quadrature' is equivalent to 'dni'. + + The parameterization can be changed by setting the class variable + ``levy_stable.parameterization`` to either 'S0' or 'S1'. + The default is 'S1'. + + To improve performance of piecewise and direct numerical integration one + can specify ``levy_stable.quad_eps`` (defaults to 1.2e-14). This is used + as both the absolute and relative quadrature tolerance for direct numerical + integration and as the relative quadrature tolerance for the piecewise + method. One can also specify ``levy_stable.piecewise_x_tol_near_zeta`` + (defaults to 0.005) for how close x is to zeta before it is considered the + same as x [NO]. The exact check is + ``abs(x0 - zeta) < piecewise_x_tol_near_zeta*alpha**(1/alpha)``. One can + also specify ``levy_stable.piecewise_alpha_tol_near_one`` (defaults to + 0.005) for how close alpha is to 1 before being considered equal to 1. + + To increase accuracy of FFT calculation one can specify + ``levy_stable.pdf_fft_grid_spacing`` (defaults to 0.001) and + ``pdf_fft_n_points_two_power`` (defaults to None which means a value is + calculated that sufficiently covers the input range). + + Further control over FFT calculation is available by setting + ``pdf_fft_interpolation_degree`` (defaults to 3) for spline order and + ``pdf_fft_interpolation_level`` for determining the number of points to use + in the Newton-Cotes formula when approximating the characteristic function + (considered experimental). + + Evaluation of the cdf uses Nolan's piecewise integration approach with the + Zolatarev :math:`S_0` parameterization by default. There is also the option + to evaluate through integration of an interpolated spline of the pdf + calculated by means of the FFT method. The settings affecting FFT + calculation are the same as for pdf calculation. The default cdf method can + be changed by setting ``levy_stable.cdf_default_method`` to either + 'piecewise' or 'fft-simpson'. For cdf calculations the Zolatarev method is + superior in accuracy, so FFT is disabled by default. + + Fitting estimate uses quantile estimation method in [MC]. MLE estimation of + parameters in fit method uses this quantile estimate initially. Note that + MLE doesn't always converge if using FFT for pdf calculations; this will be + the case if alpha <= 1 where the FFT approach doesn't give good + approximations. + + Any non-missing value for the attribute + ``levy_stable.pdf_fft_min_points_threshold`` will set + ``levy_stable.pdf_default_method`` to 'fft-simpson' if a valid + default method is not otherwise set. + + + + .. warning:: + + For pdf calculations FFT calculation is considered experimental. + + For cdf calculations FFT calculation is considered experimental. Use + Zolatarev's method instead (default). + + The probability density above is defined in the "standardized" form. To + shift and/or scale the distribution use the ``loc`` and ``scale`` + parameters. + Generally ``%(name)s.pdf(x, %(shapes)s, loc, scale)`` is identically + equivalent to ``%(name)s.pdf(y, %(shapes)s) / scale`` with + ``y = (x - loc) / scale``, except in the ``S1`` parameterization if + ``alpha == 1``. In that case ``%(name)s.pdf(x, %(shapes)s, loc, scale)`` + is identically equivalent to ``%(name)s.pdf(y, %(shapes)s) / scale`` with + ``y = (x - loc - 2 * beta * scale * np.log(scale) / np.pi) / scale``. + See [NO2]_ Definition 1.8 for more information. + Note that shifting the location of a distribution + does not make it a "noncentral" distribution. + + References + ---------- + .. [MC] McCulloch, J., 1986. Simple consistent estimators of stable + distribution parameters. Communications in Statistics - Simulation and + Computation 15, 11091136. + .. [WZ] Wang, Li and Zhang, Ji-Hong, 2008. Simpson's rule based FFT method + to compute densities of stable distribution. + .. [NO] Nolan, J., 1997. Numerical Calculation of Stable Densities and + distributions Functions. + .. [NO2] Nolan, J., 2018. Stable Distributions: Models for Heavy Tailed + Data. + .. [HO] Hopcraft, K. I., Jakeman, E., Tanner, R. M. J., 1999. Lévy random + walks with fluctuating step number and multiscale behavior. + + %(example)s + + """ + # Configurable options as class variables + # (accessible from self by attribute lookup). + parameterization = "S1" + pdf_default_method = "piecewise" + cdf_default_method = "piecewise" + quad_eps = _QUAD_EPS + piecewise_x_tol_near_zeta = 0.005 + piecewise_alpha_tol_near_one = 0.005 + pdf_fft_min_points_threshold = None + pdf_fft_grid_spacing = 0.001 + pdf_fft_n_points_two_power = None + pdf_fft_interpolation_level = 3 + pdf_fft_interpolation_degree = 3 + + def __call__(self, *args, **params): + dist = levy_stable_frozen(self, *args, **params) + dist.parameterization = self.parameterization + return dist + + def _argcheck(self, alpha, beta): + return (alpha > 0) & (alpha <= 2) & (beta <= 1) & (beta >= -1) + + def _shape_info(self): + ialpha = _ShapeInfo("alpha", False, (0, 2), (False, True)) + ibeta = _ShapeInfo("beta", False, (-1, 1), (True, True)) + return [ialpha, ibeta] + + def _parameterization(self): + allowed = ("S0", "S1") + pz = self.parameterization + if pz not in allowed: + raise RuntimeError( + f"Parameterization '{pz}' in supported list: {allowed}" + ) + return pz + + @inherit_docstring_from(rv_continuous) + def rvs(self, *args, **kwds): + X1 = super().rvs(*args, **kwds) + + kwds.pop("discrete", None) + kwds.pop("random_state", None) + (alpha, beta), delta, gamma, size = self._parse_args_rvs(*args, **kwds) + + # shift location for this parameterisation (S1) + X1 = np.where( + alpha == 1.0, X1 + 2 * beta * gamma * np.log(gamma) / np.pi, X1 + ) + + if self._parameterization() == "S0": + return np.where( + alpha == 1.0, + X1 - (beta * 2 * gamma * np.log(gamma) / np.pi), + X1 - gamma * beta * np.tan(np.pi * alpha / 2.0), + ) + elif self._parameterization() == "S1": + return X1 + + def _rvs(self, alpha, beta, size=None, random_state=None): + return _rvs_Z1(alpha, beta, size, random_state) + + @inherit_docstring_from(rv_continuous) + def pdf(self, x, *args, **kwds): + # override base class version to correct + # location for S1 parameterization + if self._parameterization() == "S0": + return super().pdf(x, *args, **kwds) + elif self._parameterization() == "S1": + (alpha, beta), delta, gamma = self._parse_args(*args, **kwds) + if np.all(np.reshape(alpha, (1, -1))[0, :] != 1): + return super().pdf(x, *args, **kwds) + else: + # correct location for this parameterisation + x = np.reshape(x, (1, -1))[0, :] + x, alpha, beta = np.broadcast_arrays(x, alpha, beta) + + data_in = np.dstack((x, alpha, beta))[0] + data_out = np.empty(shape=(len(data_in), 1)) + # group data in unique arrays of alpha, beta pairs + uniq_param_pairs = np.unique(data_in[:, 1:], axis=0) + for pair in uniq_param_pairs: + _alpha, _beta = pair + _delta = ( + delta + 2 * _beta * gamma * np.log(gamma) / np.pi + if _alpha == 1.0 + else delta + ) + data_mask = np.all(data_in[:, 1:] == pair, axis=-1) + _x = data_in[data_mask, 0] + data_out[data_mask] = ( + super() + .pdf(_x, _alpha, _beta, loc=_delta, scale=gamma) + .reshape(len(_x), 1) + ) + output = data_out.T[0] + if output.shape == (1,): + return output[0] + return output + + def _pdf(self, x, alpha, beta): + if self._parameterization() == "S0": + _pdf_single_value_piecewise = _pdf_single_value_piecewise_Z0 + _pdf_single_value_cf_integrate = _pdf_single_value_cf_integrate_Z0 + _cf = _cf_Z0 + elif self._parameterization() == "S1": + _pdf_single_value_piecewise = _pdf_single_value_piecewise_Z1 + _pdf_single_value_cf_integrate = _pdf_single_value_cf_integrate_Z1 + _cf = _cf_Z1 + + x = np.asarray(x).reshape(1, -1)[0, :] + + x, alpha, beta = np.broadcast_arrays(x, alpha, beta) + + data_in = np.dstack((x, alpha, beta))[0] + data_out = np.empty(shape=(len(data_in), 1)) + + pdf_default_method_name = self.pdf_default_method + if pdf_default_method_name in ("piecewise", "best", "zolotarev"): + pdf_single_value_method = _pdf_single_value_piecewise + elif pdf_default_method_name in ("dni", "quadrature"): + pdf_single_value_method = _pdf_single_value_cf_integrate + elif ( + pdf_default_method_name == "fft-simpson" + or self.pdf_fft_min_points_threshold is not None + ): + pdf_single_value_method = None + + pdf_single_value_kwds = { + "quad_eps": self.quad_eps, + "piecewise_x_tol_near_zeta": self.piecewise_x_tol_near_zeta, + "piecewise_alpha_tol_near_one": self.piecewise_alpha_tol_near_one, + } + + fft_grid_spacing = self.pdf_fft_grid_spacing + fft_n_points_two_power = self.pdf_fft_n_points_two_power + fft_interpolation_level = self.pdf_fft_interpolation_level + fft_interpolation_degree = self.pdf_fft_interpolation_degree + + # group data in unique arrays of alpha, beta pairs + uniq_param_pairs = np.unique(data_in[:, 1:], axis=0) + for pair in uniq_param_pairs: + data_mask = np.all(data_in[:, 1:] == pair, axis=-1) + data_subset = data_in[data_mask] + if pdf_single_value_method is not None: + data_out[data_mask] = np.array( + [ + pdf_single_value_method( + _x, _alpha, _beta, **pdf_single_value_kwds + ) + for _x, _alpha, _beta in data_subset + ] + ).reshape(len(data_subset), 1) + else: + warnings.warn( + "Density calculations experimental for FFT method." + + " Use combination of piecewise and dni methods instead.", + RuntimeWarning, stacklevel=3, + ) + _alpha, _beta = pair + _x = data_subset[:, (0,)] + + if _alpha < 1.0: + raise RuntimeError( + "FFT method does not work well for alpha less than 1." + ) + + # need enough points to "cover" _x for interpolation + if fft_grid_spacing is None and fft_n_points_two_power is None: + raise ValueError( + "One of fft_grid_spacing or fft_n_points_two_power " + + "needs to be set." + ) + max_abs_x = np.max(np.abs(_x)) + h = ( + 2 ** (3 - fft_n_points_two_power) * max_abs_x + if fft_grid_spacing is None + else fft_grid_spacing + ) + q = ( + np.ceil(np.log(2 * max_abs_x / h) / np.log(2)) + 2 + if fft_n_points_two_power is None + else int(fft_n_points_two_power) + ) + + # for some parameters, the range of x can be quite + # large, let's choose an arbitrary cut off (8GB) to save on + # computer memory. + MAX_Q = 30 + if q > MAX_Q: + raise RuntimeError( + "fft_n_points_two_power has a maximum " + + f"value of {MAX_Q}" + ) + + density_x, density = pdf_from_cf_with_fft( + lambda t: _cf(t, _alpha, _beta), + h=h, + q=q, + level=fft_interpolation_level, + ) + f = interpolate.InterpolatedUnivariateSpline( + density_x, np.real(density), k=fft_interpolation_degree + ) # patch FFT to use cubic + data_out[data_mask] = f(_x) + + return data_out.T[0] + + @inherit_docstring_from(rv_continuous) + def cdf(self, x, *args, **kwds): + # override base class version to correct + # location for S1 parameterization + # NOTE: this is near identical to pdf() above + if self._parameterization() == "S0": + return super().cdf(x, *args, **kwds) + elif self._parameterization() == "S1": + (alpha, beta), delta, gamma = self._parse_args(*args, **kwds) + if np.all(np.reshape(alpha, (1, -1))[0, :] != 1): + return super().cdf(x, *args, **kwds) + else: + # correct location for this parameterisation + x = np.reshape(x, (1, -1))[0, :] + x, alpha, beta = np.broadcast_arrays(x, alpha, beta) + + data_in = np.dstack((x, alpha, beta))[0] + data_out = np.empty(shape=(len(data_in), 1)) + # group data in unique arrays of alpha, beta pairs + uniq_param_pairs = np.unique(data_in[:, 1:], axis=0) + for pair in uniq_param_pairs: + _alpha, _beta = pair + _delta = ( + delta + 2 * _beta * gamma * np.log(gamma) / np.pi + if _alpha == 1.0 + else delta + ) + data_mask = np.all(data_in[:, 1:] == pair, axis=-1) + _x = data_in[data_mask, 0] + data_out[data_mask] = ( + super() + .cdf(_x, _alpha, _beta, loc=_delta, scale=gamma) + .reshape(len(_x), 1) + ) + output = data_out.T[0] + if output.shape == (1,): + return output[0] + return output + + def _cdf(self, x, alpha, beta): + if self._parameterization() == "S0": + _cdf_single_value_piecewise = _cdf_single_value_piecewise_Z0 + _cf = _cf_Z0 + elif self._parameterization() == "S1": + _cdf_single_value_piecewise = _cdf_single_value_piecewise_Z1 + _cf = _cf_Z1 + + x = np.asarray(x).reshape(1, -1)[0, :] + + x, alpha, beta = np.broadcast_arrays(x, alpha, beta) + + data_in = np.dstack((x, alpha, beta))[0] + data_out = np.empty(shape=(len(data_in), 1)) + + cdf_default_method_name = self.cdf_default_method + if cdf_default_method_name == "piecewise": + cdf_single_value_method = _cdf_single_value_piecewise + elif cdf_default_method_name == "fft-simpson": + cdf_single_value_method = None + + cdf_single_value_kwds = { + "quad_eps": self.quad_eps, + "piecewise_x_tol_near_zeta": self.piecewise_x_tol_near_zeta, + "piecewise_alpha_tol_near_one": self.piecewise_alpha_tol_near_one, + } + + fft_grid_spacing = self.pdf_fft_grid_spacing + fft_n_points_two_power = self.pdf_fft_n_points_two_power + fft_interpolation_level = self.pdf_fft_interpolation_level + fft_interpolation_degree = self.pdf_fft_interpolation_degree + + # group data in unique arrays of alpha, beta pairs + uniq_param_pairs = np.unique(data_in[:, 1:], axis=0) + for pair in uniq_param_pairs: + data_mask = np.all(data_in[:, 1:] == pair, axis=-1) + data_subset = data_in[data_mask] + if cdf_single_value_method is not None: + data_out[data_mask] = np.array( + [ + cdf_single_value_method( + _x, _alpha, _beta, **cdf_single_value_kwds + ) + for _x, _alpha, _beta in data_subset + ] + ).reshape(len(data_subset), 1) + else: + warnings.warn( + "Cumulative density calculations experimental for FFT" + + " method. Use piecewise method instead.", + RuntimeWarning, stacklevel=3, + ) + _alpha, _beta = pair + _x = data_subset[:, (0,)] + + # need enough points to "cover" _x for interpolation + if fft_grid_spacing is None and fft_n_points_two_power is None: + raise ValueError( + "One of fft_grid_spacing or fft_n_points_two_power " + + "needs to be set." + ) + max_abs_x = np.max(np.abs(_x)) + h = ( + 2 ** (3 - fft_n_points_two_power) * max_abs_x + if fft_grid_spacing is None + else fft_grid_spacing + ) + q = ( + np.ceil(np.log(2 * max_abs_x / h) / np.log(2)) + 2 + if fft_n_points_two_power is None + else int(fft_n_points_two_power) + ) + + density_x, density = pdf_from_cf_with_fft( + lambda t: _cf(t, _alpha, _beta), + h=h, + q=q, + level=fft_interpolation_level, + ) + f = interpolate.InterpolatedUnivariateSpline( + density_x, np.real(density), k=fft_interpolation_degree + ) + data_out[data_mask] = np.array( + [f.integral(self.a, float(x_1.squeeze())) for x_1 in _x] + ).reshape(data_out[data_mask].shape) + + return data_out.T[0] + + def _fitstart(self, data): + if self._parameterization() == "S0": + _fitstart = _fitstart_S0 + elif self._parameterization() == "S1": + _fitstart = _fitstart_S1 + return _fitstart(data) + + def _stats(self, alpha, beta): + mu = 0 if alpha > 1 else np.nan + mu2 = 2 if alpha == 2 else np.inf + g1 = 0.0 if alpha == 2.0 else np.nan + g2 = 0.0 if alpha == 2.0 else np.nan + return mu, mu2, g1, g2 + + +# cotes numbers - see sequence from http://oeis.org/A100642 +Cotes_table = np.array( + [[], [1]] + [v[2] for v in _builtincoeffs.values()], dtype=object +) +Cotes = np.array( + [ + np.pad(r, (0, len(Cotes_table) - 1 - len(r)), mode='constant') + for r in Cotes_table + ] +) + + +def pdf_from_cf_with_fft(cf, h=0.01, q=9, level=3): + """Calculates pdf from characteristic function. + + Uses fast Fourier transform with Newton-Cotes integration following [WZ]. + Defaults to using Simpson's method (3-point Newton-Cotes integration). + + Parameters + ---------- + cf : callable + Single argument function from float -> complex expressing a + characteristic function for some distribution. + h : Optional[float] + Step size for Newton-Cotes integration. Default: 0.01 + q : Optional[int] + Use 2**q steps when performing Newton-Cotes integration. + The infinite integral in the inverse Fourier transform will then + be restricted to the interval [-2**q * h / 2, 2**q * h / 2]. Setting + the number of steps equal to a power of 2 allows the fft to be + calculated in O(n*log(n)) time rather than O(n**2). + Default: 9 + level : Optional[int] + Calculate integral using n-point Newton-Cotes integration for + n = level. The 3-point Newton-Cotes formula corresponds to Simpson's + rule. Default: 3 + + Returns + ------- + x_l : ndarray + Array of points x at which pdf is estimated. 2**q equally spaced + points from -pi/h up to but not including pi/h. + density : ndarray + Estimated values of pdf corresponding to cf at points in x_l. + + References + ---------- + .. [WZ] Wang, Li and Zhang, Ji-Hong, 2008. Simpson's rule based FFT method + to compute densities of stable distribution. + """ + n = level + N = 2**q + steps = np.arange(0, N) + L = N * h / 2 + x_l = np.pi * (steps - N / 2) / L + if level > 1: + indices = np.arange(n).reshape(n, 1) + s1 = np.sum( + (-1) ** steps * Cotes[n, indices] * np.fft.fft( + (-1)**steps * cf(-L + h * steps + h * indices / (n - 1)) + ) * np.exp( + 1j * np.pi * indices / (n - 1) + - 2 * 1j * np.pi * indices * steps / + (N * (n - 1)) + ), + axis=0 + ) + else: + s1 = (-1) ** steps * Cotes[n, 0] * np.fft.fft( + (-1) ** steps * cf(-L + h * steps) + ) + density = h * s1 / (2 * np.pi * np.sum(Cotes[n])) + return (x_l, density) + + +levy_stable = levy_stable_gen(name="levy_stable") + + +class levy_stable_frozen(rv_continuous_frozen): + @property + def parameterization(self): + return self.dist.parameterization + + @parameterization.setter + def parameterization(self, value): + self.dist.parameterization = value diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_levy_stable/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/_levy_stable/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..818d521aec8b33f649ede3d4b71294c3673d5291 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/_levy_stable/__pycache__/__init__.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_levy_stable/levyst.cpython-312-x86_64-linux-gnu.so b/venv/lib/python3.12/site-packages/scipy/stats/_levy_stable/levyst.cpython-312-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..7b902130b1d134be8482cd471668210fe05b8237 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/_levy_stable/levyst.cpython-312-x86_64-linux-gnu.so differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_mannwhitneyu.py b/venv/lib/python3.12/site-packages/scipy/stats/_mannwhitneyu.py new file mode 100644 index 0000000000000000000000000000000000000000..def3e264ba8a623b190c787e29bd6aa72ebd1a8b --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_mannwhitneyu.py @@ -0,0 +1,537 @@ +import threading +import numpy as np + +from scipy._lib._array_api import (array_namespace, xp_capabilities, xp_size, + xp_promote, is_lazy_array, is_jax, is_marray, + _count_nonmasked) +from scipy._lib._bunch import _make_tuple_bunch +import scipy._external.array_api_extra as xpx +from scipy import special +from scipy import stats +from scipy.stats._stats_py import _rankdata, _get_pvalue, _SimpleNormal +from scipy.stats._morestats import wilcoxon_result_unpacker, wilcoxon_outputs +from scipy.stats._wilcoxon import _correction_sign +from ._axis_nan_policy import _axis_nan_policy_factory + +class _MWU: + '''Distribution of MWU statistic under the null hypothesis''' + + def __init__(self, n1, n2): + self._reset(n1, n2) + + def set_shapes(self, n1, n2): + n1, n2 = min(n1, n2), max(n1, n2) + if (n1, n2) == (self.n1, self.n2): + return + + self.n1 = n1 + self.n2 = n2 + self.s_array = np.zeros(0, dtype=int) + self.configurations = np.zeros(0, dtype=np.uint64) + + def reset(self): + self._reset(self.n1, self.n2) + + def _reset(self, n1, n2): + self.n1 = None + self.n2 = None + self.set_shapes(n1, n2) + + def pmf(self, k): + + # In practice, `pmf` is never called with k > m*n/2. + # If it were, we'd exploit symmetry here: + # k = np.array(k, copy=True) + # k2 = m*n - k + # i = k2 < k + # k[i] = k2[i] + + pmfs = self.build_u_freqs_array(np.max(k)) + return pmfs[k] + + def cdf(self, k): + '''Cumulative distribution function''' + + # In practice, `cdf` is never called with k > m*n/2. + # If it were, we'd exploit symmetry here rather than in `sf` + pmfs = self.build_u_freqs_array(np.max(k)) + cdfs = np.cumsum(pmfs) + return cdfs[k] + + def sf(self, k): + '''Survival function''' + # Note that both CDF and SF include the PMF at k. The p-value is + # calculated from the SF and should include the mass at k, so this + # is desirable + + # Use the fact that the distribution is symmetric and sum from the left + kc = np.asarray(self.n1*self.n2 - k) # complement of k + i = k < kc + if np.any(i): + kc[i] = k[i] + cdfs = np.asarray(self.cdf(kc)) + cdfs[i] = 1. - cdfs[i] + self.pmf(kc[i]) + else: + cdfs = np.asarray(self.cdf(kc)) + return cdfs[()] + + # build_sigma_array and build_u_freqs_array adapted from code + # by @toobaz with permission. Thanks to @andreasloe for the suggestion. + # See https://github.com/scipy/scipy/pull/4933#issuecomment-1898082691 + def build_sigma_array(self, a): + n1, n2 = self.n1, self.n2 + if a + 1 <= self.s_array.size: + return self.s_array[1:a+1] + + s_array = np.zeros(a + 1, dtype=int) + + for d in np.arange(1, n1 + 1): + # All multiples of d, except 0: + indices = np.arange(d, a + 1, d) + # \epsilon_d = 1: + s_array[indices] += d + + for d in np.arange(n2 + 1, n2 + n1 + 1): + # All multiples of d, except 0: + indices = np.arange(d, a + 1, d) + # \epsilon_d = -1: + s_array[indices] -= d + + # We don't need 0: + self.s_array = s_array + return s_array[1:] + + def build_u_freqs_array(self, maxu): + """ + Build all the array of frequencies for u from 0 to maxu. + Assumptions: + n1 <= n2 + maxu <= n1 * n2 / 2 + """ + n1, n2 = self.n1, self.n2 + total = special.binom(n1 + n2, n1) + + if maxu + 1 <= self.configurations.size: + return self.configurations[:maxu + 1] / total + + s_array = self.build_sigma_array(maxu) + + # Start working with ints, for maximum precision and efficiency: + configurations = np.zeros(maxu + 1, dtype=np.uint64) + configurations_is_uint = True + uint_max = np.iinfo(np.uint64).max + # How many ways to have U=0? 1 + configurations[0] = 1 + + for u in np.arange(1, maxu + 1): + coeffs = s_array[u - 1::-1] + new_val = np.dot(configurations[:u], coeffs) / u + if new_val > uint_max and configurations_is_uint: + # OK, we got into numbers too big for uint64. + # So now we start working with floats. + # By doing this since the beginning, we would have lost precision. + # (And working on python long ints would be unbearably slow) + configurations = configurations.astype(float) + configurations_is_uint = False + configurations[u] = new_val + + self.configurations = configurations + return configurations / total + + +# Maintain state for faster repeat calls to `mannwhitneyu`. +# _MWU() is calculated once per thread and stored as an attribute on +# this thread-local variable inside mannwhitneyu(). +_mwu_state = threading.local() + + +def _get_mwu_z(U, n1, n2, t, continuity=True, *, alternative, xp): + '''Standardized MWU statistic''' + # Follows mannwhitneyu [2] + mu = n1 * n2 / 2 + n = n1 + n2 + + # Tie correction according to [2], "Normal approximation and tie correction" + # "A more computationally-efficient form..." + tie_term = xp.sum(t**3 - t, axis=-1) + s = xp.sqrt(n1*n2/12 * ((n + 1) - tie_term/(n*(n-1)))) + + numerator = U - mu + + # Continuity correction. + # Sign is chosen to always increase the p-value to account for the rest of the + # probability mass _at_ q = U. + if continuity: + sign = _correction_sign(numerator, alternative, xp=xp) + numerator -= 0.5 * sign + + # no problem evaluating the norm SF at an infinity + with np.errstate(divide='ignore', invalid='ignore'): + z = numerator / s + return z + + +def _mwu_input_validation(x, y, use_continuity, alternative, axis, method): + ''' Input validation and standardization for mannwhitneyu ''' + xp = array_namespace(x, y) + + if not is_lazy_array(x) and (xp.any(xp.isnan(x)) or xp.any(xp.isnan(y))): + raise ValueError('`x` and `y` must not contain NaNs.') + if xp_size(x) == 0 or xp_size(y) == 0: + raise ValueError('`x` and `y` must be of nonzero size.') + x, y = xp_promote(x, y, force_floating=True, xp=xp) + + bools = {True, False} + if use_continuity not in bools: + raise ValueError(f'`use_continuity` must be one of {bools}.') + + alternatives = {"two-sided", "less", "greater"} + alternative = alternative.lower() + if alternative not in alternatives: + raise ValueError(f'`alternative` must be one of {alternatives}.') + + axis_int = int(axis) + if axis != axis_int: + raise ValueError('`axis` must be an integer.') + + if not isinstance(method, stats.PermutationMethod): + methods = {"asymptotic", "exact", "auto"} + method = method.lower() + if method not in methods: + raise ValueError(f'`method` must be one of {methods}.') + + if is_marray(xp) and method != "asymptotic": + message = "Only `method='asymptotic'` is compatible with MArrays." + raise ValueError(message) + + output_z = True if method == 'asymptotic' else False + + return x, y, use_continuity, alternative, axis_int, method, output_z, xp + + +def _mwu_choose_method(n1, n2, ties): + """Choose method 'asymptotic' or 'exact' depending on input size, ties""" + + # if both inputs are large, asymptotic is OK + if n1 > 8 and n2 > 8: + return "asymptotic" + + # if there are any ties, asymptotic is preferred + if ties: + return "asymptotic" + + return "exact" + + +MannwhitneyuResult = _make_tuple_bunch('MannwhitneyuResult', ['statistic', 'pvalue']) + + +def mwu_result_object(statistic, pvalue, zstatistic=None): + res = MannwhitneyuResult(statistic, pvalue) + if zstatistic is not None: + res.zstatistic = zstatistic + return res + + +@xp_capabilities( + cpu_only=True, # exact calculation only implemented in NumPy + skip_backends=[('dask.array', 'needs rankdata')], + jax_jit=False, # the exact null distribution is NumPy-only + marray=True, + extra_note=("Only ``method='asymptotic'`` is compatible with MArray input." + "``method='auto'`` is incompatible with JAX arrays.")) +@_axis_nan_policy_factory(mwu_result_object, n_samples=2, + result_to_tuple=wilcoxon_result_unpacker, + n_outputs=wilcoxon_outputs) +def mannwhitneyu(x, y, use_continuity=True, alternative="two-sided", + axis=0, method="auto"): + r'''Perform the Mann-Whitney U rank test on two independent samples. + + The Mann-Whitney U test is a nonparametric test of the null hypothesis + that the distribution underlying sample `x` is the same as the + distribution underlying sample `y`. It is often used as a test of + difference in location between distributions. + + Parameters + ---------- + x, y : array-like + N-d arrays of samples. The arrays must be broadcastable except along + the dimension given by `axis`. + use_continuity : bool, optional + Whether a continuity correction (1/2) should be applied. + Default is True when `method` is ``'asymptotic'``; has no effect + otherwise. + alternative : {'two-sided', 'less', 'greater'}, optional + Defines the alternative hypothesis. Default is 'two-sided'. + Let *SX(u)* and *SY(u)* be the survival functions of the + distributions underlying `x` and `y`, respectively. Then the following + alternative hypotheses are available: + + * 'two-sided': the distributions are not equal, i.e. *SX(u) ≠ SY(u)* for + at least one *u*. + * 'less': the distribution underlying `x` is stochastically less + than the distribution underlying `y`, i.e. *SX(u) < SY(u)* for all *u*. + * 'greater': the distribution underlying `x` is stochastically greater + than the distribution underlying `y`, i.e. *SX(u) > SY(u)* for all *u*. + + Under a more restrictive set of assumptions, the alternative hypotheses + can be expressed in terms of the locations of the distributions; + see [5]_ section 5.1. + axis : int, optional + Axis along which to perform the test. Default is 0. + method : {'auto', 'asymptotic', 'exact'} or `PermutationMethod` instance, optional + Selects the method used to calculate the *p*-value. + Default is 'auto'. The following options are available. + + * ``'asymptotic'``: compares the standardized test statistic + against the normal distribution, correcting for ties. + * ``'exact'``: computes the exact *p*-value by comparing the observed + :math:`U` statistic against the exact distribution of the :math:`U` + statistic under the null hypothesis. No correction is made for ties. + * ``'auto'``: chooses ``'exact'`` when the size of one of the samples + is less than or equal to 8 and there are no ties; + chooses ``'asymptotic'`` otherwise. + * `PermutationMethod` instance. In this case, the p-value + is computed using `permutation_test` with the provided + configuration options and other appropriate settings. + + Returns + ------- + res : MannwhitneyuResult + An object containing attributes: + + statistic : float + The Mann-Whitney U statistic corresponding with sample `x`. See + Notes for the test statistic corresponding with sample `y`. + pvalue : float + The associated *p*-value for the chosen `alternative`. + zstatistic : array_like + When ``method = 'approx'``, this is the normalized z-statistic:: + + z = (U - mn - d) / se + + where ``U`` is `statistic` as defined above, ``mn`` is the mean of the + distribution under the null hypothesis, ``d`` is a continuity + correction, and ``se`` is the standard error. + When ``method != 'asymptotic'``, this attribute is not available. + + Notes + ----- + If ``U1`` is the statistic corresponding with sample `x`, then the + statistic corresponding with sample `y` is + ``U2 = x.shape[axis] * y.shape[axis] - U1``. + + The p-value and standardized z-statistic produced by the Mann-Whitney U test is + identical to that produced by a Wilcoxon *rank sum* test, but different raw + statistics are associated with the tests. Specifically, the rank sum statistic + corresponding with the first sample is + ``R1 = U1 + x.shape[axis] * (x.shape[axis] + 1) / 2``. + + `mannwhitneyu` is for independent samples. For related / paired samples, + consider `scipy.stats.wilcoxon`. + + `method` ``'exact'`` is recommended when there are no ties and when either + sample size is less than 8 [1]_. The implementation follows the algorithm + reported in [3]_. + Note that the exact method is *not* corrected for ties, but + `mannwhitneyu` will not raise errors or warnings if there are ties in the + data. If there are ties and either samples is small (fewer than ~10 + observations), consider passing an instance of `PermutationMethod` + as the `method` to perform a permutation test. + + The Mann-Whitney U test is a non-parametric version of the t-test for + independent samples. When the means of samples from the populations + are normally distributed, consider `scipy.stats.ttest_ind`. + + See Also + -------- + scipy.stats.wilcoxon, scipy.stats.ttest_ind + + References + ---------- + .. [1] H.B. Mann and D.R. Whitney, "On a test of whether one of two random + variables is stochastically larger than the other", The Annals of + Mathematical Statistics, Vol. 18, pp. 50-60, 1947. + .. [2] Mann-Whitney U Test, Wikipedia, + http://en.wikipedia.org/wiki/Mann-Whitney_U_test + .. [3] Andreas Löffler, + "Über eine Partition der nat. Zahlen und ihr Anwendung beim U-Test", + Wiss. Z. Univ. Halle, XXXII'83 pp. 87-89. + .. [4] Rosie Shier, "Statistics: 2.3 The Mann-Whitney U Test", Mathematics + Learning Support Centre, 2004. + .. [5] Michael P. Fay and Michael A. Proschan. "Wilcoxon-Mann-Whitney + or t-test? On assumptions for hypothesis tests and multiple \ + interpretations of decision rules." Statistics surveys, Vol. 4, pp. + 1-39, 2010. https://www.ncbi.nlm.nih.gov/pmc/articles/PMC2857732/ + .. [6] MathWorks. "ranksum - Wilcoxon rank sum test - MATLAB". + https://www.mathworks.com/help/stats/ranksum.html. + + Examples + -------- + We follow the example from [4]_: nine randomly sampled young adults were + diagnosed with type II diabetes at the ages below. + + >>> males = [19, 22, 16, 29, 24] + >>> females = [20, 11, 17, 12] + + We use the Mann-Whitney U test to assess whether there is a statistically + significant difference in the diagnosis age of males and females. + The null hypothesis is that the distribution of male diagnosis ages is + the same as the distribution of female diagnosis ages. We decide + that a confidence level of 95% is required to reject the null hypothesis + in favor of the alternative that the distributions are different. + Since the number of samples is very small and there are no ties in the + data, we can compare the observed test statistic against the *exact* + distribution of the test statistic under the null hypothesis. + + >>> from scipy.stats import mannwhitneyu + >>> U1, p = mannwhitneyu(males, females, method="exact") + >>> print(U1) + 17.0 + + `mannwhitneyu` always reports the statistic associated with the first + sample, which, in this case, is males. This agrees with :math:`U_M = 17` + reported in [4]_. The statistic associated with the second statistic + can be calculated: + + >>> nx, ny = len(males), len(females) + >>> U2 = nx*ny - U1 + >>> print(U2) + 3.0 + + This agrees with :math:`U_F = 3` reported in [4]_. The two-sided + *p*-value can be calculated from either statistic, and the value produced + by `mannwhitneyu` agrees with :math:`p = 0.11` reported in [4]_. + + >>> print(p) + 0.1111111111111111 + + The exact distribution of the test statistic is asymptotically normal, so + the example continues by comparing the exact *p*-value against the + *p*-value produced using the normal approximation. + + >>> _, pnorm = mannwhitneyu(males, females, method="asymptotic") + >>> print(pnorm) + 0.11134688653314041 + + Here `mannwhitneyu`'s reported *p*-value appears to conflict with the + value :math:`p = 0.09` given in [4]_. The reason is that [4]_ + does not apply the continuity correction performed by `mannwhitneyu`; + `mannwhitneyu` reduces the distance between the test statistic and the + mean :math:`\mu = n_x n_y / 2` by 0.5 to correct for the fact that the + discrete statistic is being compared against a continuous distribution. + Here, the :math:`U` statistic used is less than the mean, so we reduce + the distance by adding 0.5 in the numerator. + + >>> import numpy as np + >>> from scipy.stats import norm + >>> U = min(U1, U2) + >>> N = nx + ny + >>> z = (U - nx*ny/2 + 0.5) / np.sqrt(nx*ny * (N + 1)/ 12) + >>> p = 2 * norm.cdf(z) # use CDF to get p-value from smaller statistic + >>> print(p) + 0.11134688653314041 + + If desired, we can disable the continuity correction to get a result + that agrees with that reported in [4]_. + + >>> _, pnorm = mannwhitneyu(males, females, use_continuity=False, + ... method="asymptotic") + >>> print(pnorm) + 0.0864107329737 + + Regardless of whether we perform an exact or asymptotic test, the + probability of the test statistic being as extreme or more extreme by + chance exceeds 5%, so we do not consider the results statistically + significant. + + Suppose that, before seeing the data, we had hypothesized that females + would tend to be diagnosed at a younger age than males. + In that case, it would be natural to provide the female ages as the + first input, and we would have performed a one-sided test using + ``alternative = 'less'``: females are diagnosed at an age that is + stochastically less than that of males. + + >>> res = mannwhitneyu(females, males, alternative="less", method="exact") + >>> print(res) + MannwhitneyuResult(statistic=3.0, pvalue=0.05555555555555555) + + Again, the probability of getting a sufficiently low value of the + test statistic by chance under the null hypothesis is greater than 5%, + so we do not reject the null hypothesis in favor of our alternative. + + If it is reasonable to assume that the means of samples from the + populations are normally distributed, we could have used a t-test to + perform the analysis. + + >>> from scipy.stats import ttest_ind + >>> res = ttest_ind(females, males, alternative="less") + >>> print(res) + TtestResult(statistic=-2.239334696520584, + pvalue=0.030068441095757924, + df=7.0) + + Under this assumption, the *p*-value would be low enough to reject the + null hypothesis in favor of the alternative. + + ''' + + x, y, use_continuity, alternative, axis_int, method, output_z, xp = ( + _mwu_input_validation(x, y, use_continuity, alternative, axis, method)) + + # axis=-1 and arrays broadcasted due to _axis_nan_policy decorator + xy = xp.concat((x, y), axis=-1) + + n1 = _count_nonmasked(x, axis=-1, xp=xp) + n2 = _count_nonmasked(y, axis=-1, xp=xp) + + # Follows [2] + ranks, _, t = _rankdata(xy, 'average', return_ties=True) # method 2, step 1 + ranks = xp.astype(ranks, x.dtype, copy=False) + t = xp.astype(t, x.dtype, copy=False) + R1 = xp.sum(ranks[..., :x.shape[-1]], axis=-1) # method 2, step 2 + U1 = R1 - n1*(n1+1)/2 # method 2, step 3 + U2 = n1 * n2 - U1 # as U1 + U2 = n1 * n2 + + if alternative == "greater": + U, f = U1, 1 # U is the statistic to use for exact p-value, f is a factor + elif alternative == "less": + U, f = U2, 1 # Due to symmetry, it uses SF of U2 rather than CDF of U1 + else: + U, f = xp.maximum(U1, U2), 2 # and will multiply SF by two for two-sided test + + if method == "auto": + if is_jax(xp): + message = "`method='auto'` is incompatible with JAX arrays." + raise ValueError(message) + method = _mwu_choose_method(n1, n2, xp.any(t > 1)) + + if method == "exact": + if not hasattr(_mwu_state, 's'): + _mwu_state.s = _MWU(0, 0) + _mwu_state.s.set_shapes(n1, n2) + p = xpx.lazy_apply(_mwu_state.s.sf, xp.astype(U, xp.int64), + as_numpy=True, dtype=xp.float64) + p = xp.astype(p, x.dtype, copy=False) * f + elif method == "asymptotic": + z = _get_mwu_z(U1, n1, n2, t, continuity=use_continuity, + alternative=alternative, xp=xp) + p = _get_pvalue(z, _SimpleNormal(), alternative, xp=xp) + else: # `PermutationMethod` instance (already validated) + def statistic(x, y, axis): + return mannwhitneyu(x, y, use_continuity=use_continuity, + alternative=alternative, axis=axis, + method="asymptotic").statistic + + res = stats.permutation_test((x, y), statistic, axis=axis, + **method._asdict(), alternative=alternative) + p = res.pvalue + + # Ensure that test statistic is not greater than 1 + # This could happen for exact test when U = m*n/2 + p = xp.clip(p, 0., 1.) + + res = MannwhitneyuResult(U1, p) + if output_z: + res.zstatistic = z[()] if z.ndim == 0 else z + return res diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_mgc.py b/venv/lib/python3.12/site-packages/scipy/stats/_mgc.py new file mode 100644 index 0000000000000000000000000000000000000000..f2ecbe9747acdcb4731a2755c88352896be10d0a --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_mgc.py @@ -0,0 +1,552 @@ +import warnings +import numpy as np + +from scipy._lib._array_api import xp_capabilities +from scipy._lib._util import check_random_state, MapWrapper, rng_integers, _contains_nan +from scipy._lib._bunch import _make_tuple_bunch +from scipy.spatial.distance import cdist +from scipy.ndimage import _measurements + +from ._stats import _local_correlations # type: ignore[import-not-found] +from . import distributions + +__all__ = ['multiscale_graphcorr'] + +# FROM MGCPY: https://github.com/neurodata/mgcpy + + +class _ParallelP: + """Helper function to calculate parallel p-value.""" + + def __init__(self, x, y, random_states): + self.x = x + self.y = y + self.random_states = random_states + + def __call__(self, index): + order = self.random_states[index].permutation(self.y.shape[0]) + permy = self.y[order][:, order] + + # calculate permuted stats, store in null distribution + perm_stat = _mgc_stat(self.x, permy)[0] + + return perm_stat + + +def _perm_test(x, y, stat, reps=1000, workers=-1, random_state=None): + r"""Helper function that calculates the p-value. See below for uses. + + Parameters + ---------- + x, y : ndarray + `x` and `y` have shapes ``(n, p)`` and ``(n, q)``. + stat : float + The sample test statistic. + reps : int, optional + The number of replications used to estimate the null when using the + permutation test. The default is 1000 replications. + workers : int or map-like callable, optional + If `workers` is an int the population is subdivided into `workers` + sections and evaluated in parallel (uses + `multiprocessing.Pool `). Supply `-1` to use all cores + available to the Process. Alternatively supply a map-like callable, + such as `multiprocessing.Pool.map` for evaluating the population in + parallel. This evaluation is carried out as `workers(func, iterable)`. + Requires that `func` be pickleable. + random_state : {None, int, `numpy.random.Generator`, + `numpy.random.RandomState`}, optional + + If `seed` is None (or `np.random`), the `numpy.random.RandomState` + singleton is used. + If `seed` is an int, a new ``RandomState`` instance is used, + seeded with `seed`. + If `seed` is already a ``Generator`` or ``RandomState`` instance then + that instance is used. + + Returns + ------- + pvalue : float + The sample test p-value. + null_dist : list + The approximated null distribution. + + """ + # generate seeds for each rep (change to new parallel random number + # capabilities in numpy >= 1.17+) + random_state = check_random_state(random_state) + random_states = [np.random.RandomState(rng_integers(random_state, 1 << 32, + size=4, dtype=np.uint32)) for _ in range(reps)] + + # parallelizes with specified workers over number of reps and set seeds + parallelp = _ParallelP(x=x, y=y, random_states=random_states) + with MapWrapper(workers) as mapwrapper: + null_dist = np.array(list(mapwrapper(parallelp, range(reps)))) + + # calculate p-value and significant permutation map through list + pvalue = (1 + (null_dist >= stat).sum()) / (1 + reps) + + return pvalue, null_dist + + +def _euclidean_dist(x): + return cdist(x, x) + + +MGCResult = _make_tuple_bunch('MGCResult', + ['statistic', 'pvalue', 'mgc_dict'], []) + + +@xp_capabilities(np_only=True) +def multiscale_graphcorr(x, y, compute_distance=_euclidean_dist, reps=1000, + workers=1, is_twosamp=False, random_state=None): + r"""Computes the Multiscale Graph Correlation (MGC) test statistic. + + Specifically, for each point, MGC finds the :math:`k`-nearest neighbors for + one property (e.g. cloud density), and the :math:`l`-nearest neighbors for + the other property (e.g. grass wetness) [1]_. This pair :math:`(k, l)` is + called the "scale". A priori, however, it is not know which scales will be + most informative. So, MGC computes all distance pairs, and then efficiently + computes the distance correlations for all scales. The local correlations + illustrate which scales are relatively informative about the relationship. + The key, therefore, to successfully discover and decipher relationships + between disparate data modalities is to adaptively determine which scales + are the most informative, and the geometric implication for the most + informative scales. Doing so not only provides an estimate of whether the + modalities are related, but also provides insight into how the + determination was made. This is especially important in high-dimensional + data, where simple visualizations do not reveal relationships to the + unaided human eye. Characterizations of this implementation in particular + have been derived from and benchmarked within in [2]_. + + Parameters + ---------- + x, y : ndarray + If ``x`` and ``y`` have shapes ``(n, p)`` and ``(n, q)`` where `n` is + the number of samples and `p` and `q` are the number of dimensions, + then the MGC independence test will be run. Alternatively, ``x`` and + ``y`` can have shapes ``(n, n)`` if they are distance or similarity + matrices, and ``compute_distance`` must be sent to ``None``. If ``x`` + and ``y`` have shapes ``(n, p)`` and ``(m, p)``, an unpaired + two-sample MGC test will be run. + compute_distance : callable, optional + A function that computes the distance or similarity among the samples + within each data matrix. Set to ``None`` if ``x`` and ``y`` are + already distance matrices. The default uses the euclidean norm metric. + If you are calling a custom function, either create the distance + matrix before-hand or create a function of the form + ``compute_distance(x)`` where `x` is the data matrix for which + pairwise distances are calculated. + reps : int, optional + The number of replications used to estimate the null when using the + permutation test. The default is ``1000``. + workers : int or map-like callable, optional + If ``workers`` is an int the population is subdivided into ``workers`` + sections and evaluated in parallel (uses ``multiprocessing.Pool + ``). Supply ``-1`` to use all cores available to the + Process. Alternatively supply a map-like callable, such as + ``multiprocessing.Pool.map`` for evaluating the p-value in parallel. + This evaluation is carried out as ``workers(func, iterable)``. + Requires that `func` be pickleable. The default is ``1``. + is_twosamp : bool, optional + If `True`, a two sample test will be run. If ``x`` and ``y`` have + shapes ``(n, p)`` and ``(m, p)``, this optional will be overridden and + set to ``True``. Set to ``True`` if ``x`` and ``y`` both have shapes + ``(n, p)`` and a two sample test is desired. The default is ``False``. + Note that this will not run if inputs are distance matrices. + random_state : {None, int, `numpy.random.Generator`, + `numpy.random.RandomState`}, optional + + If `seed` is None (or `np.random`), the `numpy.random.RandomState` + singleton is used. + If `seed` is an int, a new ``RandomState`` instance is used, + seeded with `seed`. + If `seed` is already a ``Generator`` or ``RandomState`` instance then + that instance is used. + + Returns + ------- + res : MGCResult + An object containing attributes: + + statistic : float + The sample MGC test statistic within ``[-1, 1]``. + pvalue : float + The p-value obtained via permutation. + mgc_dict : dict + Contains additional useful results: + + - mgc_map : ndarray + A 2D representation of the latent geometry of the + relationship. + - opt_scale : (int, int) + The estimated optimal scale as a ``(x, y)`` pair. + - null_dist : list + The null distribution derived from the permuted matrices. + + See Also + -------- + pearsonr : Pearson correlation coefficient and p-value for testing + non-correlation. + kendalltau : Calculates Kendall's tau. + spearmanr : Calculates a Spearman rank-order correlation coefficient. + + Notes + ----- + A description of the process of MGC and applications on neuroscience data + can be found in [1]_. It is performed using the following steps: + + #. Two distance matrices :math:`D^X` and :math:`D^Y` are computed and + modified to be mean zero columnwise. This results in two + :math:`n \times n` distance matrices :math:`A` and :math:`B` (the + centering and unbiased modification) [3]_. + + #. For all values :math:`k` and :math:`l` from :math:`1, ..., n`, + + * The :math:`k`-nearest neighbor and :math:`l`-nearest neighbor graphs + are calculated for each property. Here, :math:`G_k (i, j)` indicates + the :math:`k`-smallest values of the :math:`i`-th row of :math:`A` + and :math:`H_l (i, j)` indicates the :math:`l` smallested values of + the :math:`i`-th row of :math:`B` + + * Let :math:`\circ` denotes the entry-wise matrix product, then local + correlations are summed and normalized using the following statistic: + + .. math:: + + c^{kl} = \frac{\sum_{ij} A G_k B H_l} + {\sqrt{\sum_{ij} A^2 G_k \times \sum_{ij} B^2 H_l}} + + #. The MGC test statistic is the smoothed optimal local correlation of + :math:`\{ c^{kl} \}`. Denote the smoothing operation as :math:`R(\cdot)` + (which essentially set all isolated large correlations) as 0 and + connected large correlations the same as before, see [3]_.) MGC is, + + .. math:: + + MGC_n (x, y) = \max_{(k, l)} R \left(c^{kl} \left( x_n, y_n \right) + \right) + + The test statistic returns a value between :math:`(-1, 1)` since it is + normalized. + + The p-value returned is calculated using a permutation test. This process + is completed by first randomly permuting :math:`y` to estimate the null + distribution and then calculating the probability of observing a test + statistic, under the null, at least as extreme as the observed test + statistic. + + MGC requires at least 5 samples to run with reliable results. It can also + handle high-dimensional data sets. + In addition, by manipulating the input data matrices, the two-sample + testing problem can be reduced to the independence testing problem [4]_. + Given sample data :math:`U` and :math:`V` of sizes :math:`p \times n` + :math:`p \times m`, data matrix :math:`X` and :math:`Y` can be created as + follows: + + .. math:: + + X = [U | V] \in \mathcal{R}^{p \times (n + m)} + Y = [0_{1 \times n} | 1_{1 \times m}] \in \mathcal{R}^{(n + m)} + + Then, the MGC statistic can be calculated as normal. This methodology can + be extended to similar tests such as distance correlation [4]_. + + .. versionadded:: 1.4.0 + + References + ---------- + .. [1] Vogelstein, J. T., Bridgeford, E. W., Wang, Q., Priebe, C. E., + Maggioni, M., & Shen, C. (2019). Discovering and deciphering + relationships across disparate data modalities. ELife. + .. [2] Panda, S., Palaniappan, S., Xiong, J., Swaminathan, A., + Ramachandran, S., Bridgeford, E. W., ... Vogelstein, J. T. (2019). + mgcpy: A Comprehensive High Dimensional Independence Testing Python + Package. :arXiv:`1907.02088` + .. [3] Shen, C., Priebe, C.E., & Vogelstein, J. T. (2019). From distance + correlation to multiscale graph correlation. Journal of the American + Statistical Association. + .. [4] Shen, C. & Vogelstein, J. T. (2018). The Exact Equivalence of + Distance and Kernel Methods for Hypothesis Testing. + :arXiv:`1806.05514` + + Examples + -------- + >>> import numpy as np + >>> from scipy.stats import multiscale_graphcorr + >>> x = np.arange(100) + >>> y = x + >>> res = multiscale_graphcorr(x, y) + >>> res.statistic, res.pvalue + (1.0, 0.001) + + To run an unpaired two-sample test, + + >>> x = np.arange(100) + >>> y = np.arange(79) + >>> res = multiscale_graphcorr(x, y) + >>> res.statistic, res.pvalue # doctest: +SKIP + (0.033258146255703246, 0.023) + + or, if shape of the inputs are the same, + + >>> x = np.arange(100) + >>> y = x + >>> res = multiscale_graphcorr(x, y, is_twosamp=True) + >>> res.statistic, res.pvalue # doctest: +SKIP + (-0.008021809890200488, 1.0) + + """ + if not isinstance(x, np.ndarray) or not isinstance(y, np.ndarray): + raise ValueError("x and y must be ndarrays") + + # convert arrays of type (n,) to (n, 1) + if x.ndim == 1: + x = x[:, np.newaxis] + elif x.ndim != 2: + raise ValueError(f"Expected a 2-D array `x`, found shape {x.shape}") + if y.ndim == 1: + y = y[:, np.newaxis] + elif y.ndim != 2: + raise ValueError(f"Expected a 2-D array `y`, found shape {y.shape}") + + nx, px = x.shape + ny, py = y.shape + + # check for NaNs + _contains_nan(x, nan_policy='raise') + _contains_nan(y, nan_policy='raise') + + # check for positive or negative infinity and raise error + if np.sum(np.isinf(x)) > 0 or np.sum(np.isinf(y)) > 0: + raise ValueError("Inputs contain infinities") + + if nx != ny: + if px == py: + # reshape x and y for two sample testing + is_twosamp = True + else: + raise ValueError("Shape mismatch, x and y must have shape [n, p] " + "and [n, q] or have shape [n, p] and [m, p].") + + if nx < 5 or ny < 5: + raise ValueError("MGC requires at least 5 samples to give reasonable " + "results.") + + # convert x and y to float + x = x.astype(np.float64) + y = y.astype(np.float64) + + # check if compute_distance_matrix if a callable() + if not callable(compute_distance) and compute_distance is not None: + raise ValueError("Compute_distance must be a function.") + + # check if number of reps exists, integer, or > 0 (if under 1000 raises + # warning) + if not isinstance(reps, int) or reps < 0: + raise ValueError("Number of reps must be an integer greater than 0.") + elif reps < 1000: + msg = ("The number of replications is low (under 1000), and p-value " + "calculations may be unreliable. Use the p-value result, with " + "caution!") + warnings.warn(msg, RuntimeWarning, stacklevel=2) + + if is_twosamp: + if compute_distance is None: + raise ValueError("Cannot run if inputs are distance matrices") + x, y = _two_sample_transform(x, y) + + if compute_distance is not None: + # compute distance matrices for x and y + x = compute_distance(x) + y = compute_distance(y) + + # calculate MGC stat + stat, stat_dict = _mgc_stat(x, y) + stat_mgc_map = stat_dict["stat_mgc_map"] + opt_scale = stat_dict["opt_scale"] + + # calculate permutation MGC p-value + pvalue, null_dist = _perm_test(x, y, stat, reps=reps, workers=workers, + random_state=random_state) + + # save all stats (other than stat/p-value) in dictionary + mgc_dict = {"mgc_map": stat_mgc_map, + "opt_scale": opt_scale, + "null_dist": null_dist} + + # create result object with alias for backward compatibility + res = MGCResult(stat, pvalue, mgc_dict) + res.stat = stat + return res + + +def _mgc_stat(distx, disty): + r"""Helper function that calculates the MGC stat. See above for use. + + Parameters + ---------- + distx, disty : ndarray + `distx` and `disty` have shapes ``(n, p)`` and ``(n, q)`` or + ``(n, n)`` and ``(n, n)`` + if distance matrices. + + Returns + ------- + stat : float + The sample MGC test statistic within ``[-1, 1]``. + stat_dict : dict + Contains additional useful additional returns containing the following + keys: + + - stat_mgc_map : ndarray + MGC-map of the statistics. + - opt_scale : (float, float) + The estimated optimal scale as a ``(x, y)`` pair. + + """ + # calculate MGC map and optimal scale + stat_mgc_map = _local_correlations(distx, disty, global_corr='mgc') + + m, n = stat_mgc_map.shape + if m == 1 or n == 1: + # the global scale at is the statistic calculated at maximal nearest + # neighbors. There is not enough local scale to search over, so + # default to global scale + stat = stat_mgc_map[m - 1][n - 1] + opt_scale = m * n + else: + samp_size = len(distx) - 1 + + # threshold to find connected region of significant local correlations + sig_connect = _threshold_mgc_map(stat_mgc_map, samp_size) + + # maximum within the significant region + stat, opt_scale = _smooth_mgc_map(sig_connect, stat_mgc_map) + + stat_dict = {"stat_mgc_map": stat_mgc_map, + "opt_scale": opt_scale} + + return stat, stat_dict + + +def _threshold_mgc_map(stat_mgc_map, samp_size): + r""" + Finds a connected region of significance in the MGC-map by thresholding. + + Parameters + ---------- + stat_mgc_map : ndarray + All local correlations within ``[-1,1]``. + samp_size : int + The sample size of original data. + + Returns + ------- + sig_connect : ndarray + A binary matrix with 1's indicating the significant region. + + """ + m, n = stat_mgc_map.shape + + # 0.02 is simply an empirical threshold, this can be set to 0.01 or 0.05 + # with varying levels of performance. Threshold is based on a beta + # approximation. + per_sig = 1 - (0.02 / samp_size) # Percentile to consider as significant + threshold = samp_size * (samp_size - 3)/4 - 1/2 # Beta approximation + threshold = distributions.beta.ppf(per_sig, threshold, threshold) * 2 - 1 + + # the global scale at is the statistic calculated at maximal nearest + # neighbors. Threshold is the maximum on the global and local scales + threshold = max(threshold, stat_mgc_map[m - 1][n - 1]) + + # find the largest connected component of significant correlations + sig_connect = stat_mgc_map > threshold + if np.sum(sig_connect) > 0: + sig_connect, _ = _measurements.label(sig_connect) + _, label_counts = np.unique(sig_connect, return_counts=True) + + # skip the first element in label_counts, as it is count(zeros) + max_label = np.argmax(label_counts[1:]) + 1 + sig_connect = sig_connect == max_label + else: + sig_connect = np.array([[False]]) + + return sig_connect + + +def _smooth_mgc_map(sig_connect, stat_mgc_map): + """Finds the smoothed maximal within the significant region R. + + If area of R is too small it returns the last local correlation. Otherwise, + returns the maximum within significant_connected_region. + + Parameters + ---------- + sig_connect : ndarray + A binary matrix with 1's indicating the significant region. + stat_mgc_map : ndarray + All local correlations within ``[-1, 1]``. + + Returns + ------- + stat : float + The sample MGC statistic within ``[-1, 1]``. + opt_scale: (float, float) + The estimated optimal scale as an ``(x, y)`` pair. + + """ + m, n = stat_mgc_map.shape + + # the global scale at is the statistic calculated at maximal nearest + # neighbors. By default, statistic and optimal scale are global. + stat = stat_mgc_map[m - 1][n - 1] + opt_scale = [m, n] + + if np.linalg.norm(sig_connect) != 0: + # proceed only when the connected region's area is sufficiently large + # 0.02 is simply an empirical threshold, this can be set to 0.01 or 0.05 + # with varying levels of performance + if np.sum(sig_connect) >= np.ceil(0.02 * max(m, n)) * min(m, n): + max_corr = max(stat_mgc_map[sig_connect]) + + # find all scales within significant_connected_region that maximize + # the local correlation + max_corr_index = np.where((stat_mgc_map >= max_corr) & sig_connect) + + if max_corr >= stat: + stat = max_corr + + k, l = max_corr_index + one_d_indices = k * n + l # 2D to 1D indexing + k = np.max(one_d_indices) // n + l = np.max(one_d_indices) % n + opt_scale = [k+1, l+1] # adding 1s to match R indexing + + return stat, opt_scale + + +def _two_sample_transform(u, v): + """Helper function that concatenates x and y for two sample MGC stat. + + See above for use. + + Parameters + ---------- + u, v : ndarray + `u` and `v` have shapes ``(n, p)`` and ``(m, p)``. + + Returns + ------- + x : ndarray + Concatenate `u` and `v` along the ``axis = 0``. `x` thus has shape + ``(2n, p)``. + y : ndarray + Label matrix for `x` where 0 refers to samples that comes from `u` and + 1 refers to samples that come from `v`. `y` thus has shape ``(2n, 1)``. + + """ + nx = u.shape[0] + ny = v.shape[0] + x = np.concatenate([u, v], axis=0) + y = np.concatenate([np.zeros(nx), np.ones(ny)], axis=0).reshape(-1, 1) + return x, y diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_morestats.py b/venv/lib/python3.12/site-packages/scipy/stats/_morestats.py new file mode 100644 index 0000000000000000000000000000000000000000..3ead2e29323cd51229e4e5bb018d8bf52bd87fb6 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_morestats.py @@ -0,0 +1,5086 @@ +import itertools +import math +import warnings +import threading +from collections import namedtuple + +import numpy as np +from numpy import (isscalar, log, around, arange, sort, amin, amax, sqrt, array, + pi, exp, ravel, count_nonzero) + +from scipy import optimize, special, interpolate, stats +from scipy._lib._bunch import _make_tuple_bunch +from scipy._lib._util import _rename_parameter, _contains_nan, _get_nan +from scipy._lib.deprecation import _NoValue +import scipy._external.array_api_extra as xpx + +from scipy._lib._array_api import ( + array_namespace, + is_marray, + xp_capabilities, + is_numpy, + is_jax, + is_dask, + xp_size, + xp_vector_norm, + xp_promote, + xp_result_type, + xp_device, + xp_ravel, + _count_nonmasked, + is_lazy_array, +) + +from ._ansari_swilk_statistics import gscale +from . import _stats_py, _wilcoxon +from ._fit import FitResult +from ._stats_py import (_get_pvalue, SignificanceResult, # noqa:F401 + _SimpleNormal, _SimpleChi2, _SimpleF, _demean) +from .contingency import chi2_contingency +from . import distributions +from ._distn_infrastructure import rv_generic +from ._axis_nan_policy import _axis_nan_policy_factory, _broadcast_arrays + + +__all__ = ['mvsdist', + 'bayes_mvs', 'kstat', 'kstatvar', 'probplot', 'ppcc_max', 'ppcc_plot', + 'boxcox_llf', 'boxcox', 'boxcox_normmax', 'boxcox_normplot', + 'shapiro', 'anderson', 'ansari', 'bartlett', 'levene', + 'fligner', 'mood', 'wilcoxon', 'median_test', + 'circmean', 'circvar', 'circstd', 'anderson_ksamp', + 'yeojohnson_llf', 'yeojohnson', 'yeojohnson_normmax', + 'yeojohnson_normplot', 'directional_stats', + 'false_discovery_control' + ] + + +Mean = namedtuple('Mean', ('statistic', 'minmax')) +Variance = namedtuple('Variance', ('statistic', 'minmax')) +Std_dev = namedtuple('Std_dev', ('statistic', 'minmax')) + + +@xp_capabilities(np_only=True) +def bayes_mvs(data, alpha=0.90): + r""" + Bayesian confidence intervals for the mean, var, and std. + + Parameters + ---------- + data : array_like + Input data, if multi-dimensional it is flattened to 1-D by `bayes_mvs`. + Requires 2 or more data points. + alpha : float, optional + Probability that the returned confidence interval contains + the true parameter. + + Returns + ------- + mean_cntr, var_cntr, std_cntr : tuple + The three results are for the mean, variance and standard deviation, + respectively. Each result is a tuple of the form:: + + (center, (lower, upper)) + + with ``center`` the mean of the conditional pdf of the value given the + data, and ``(lower, upper)`` a confidence interval, centered on the + median, containing the estimate to a probability ``alpha``. + + See Also + -------- + mvsdist + + Notes + ----- + Each tuple of mean, variance, and standard deviation estimates represent + the (center, (lower, upper)) with center the mean of the conditional pdf + of the value given the data and (lower, upper) is a confidence interval + centered on the median, containing the estimate to a probability + ``alpha``. + + Converts data to 1-D and assumes all data has the same mean and variance. + Uses Jeffrey's prior for variance and std. + + Equivalent to ``tuple((x.mean(), x.interval(alpha)) for x in mvsdist(dat))`` + + References + ---------- + T.E. Oliphant, "A Bayesian perspective on estimating mean, variance, and + standard-deviation from data", https://scholarsarchive.byu.edu/facpub/278, + 2006. + + Examples + -------- + First a basic example to demonstrate the outputs: + + >>> from scipy import stats + >>> data = [6, 9, 12, 7, 8, 8, 13] + >>> mean, var, std = stats.bayes_mvs(data) + >>> mean + Mean(statistic=9.0, minmax=(7.103650222612533, 10.896349777387467)) + >>> var + Variance(statistic=10.0, minmax=(3.176724206, 24.45910382)) + >>> std + Std_dev(statistic=2.9724954732045084, + minmax=(1.7823367265645143, 4.945614605014631)) + + Now we generate some normally distributed random data, and get estimates of + mean and standard deviation with 95% confidence intervals for those + estimates: + + >>> n_samples = 100000 + >>> data = stats.norm.rvs(size=n_samples) + >>> res_mean, res_var, res_std = stats.bayes_mvs(data, alpha=0.95) + + >>> import matplotlib.pyplot as plt + >>> fig = plt.figure() + >>> ax = fig.add_subplot(111) + >>> ax.hist(data, bins=100, density=True, label='Histogram of data') + >>> ax.vlines(res_mean.statistic, 0, 0.5, colors='r', label='Estimated mean') + >>> ax.axvspan(res_mean.minmax[0],res_mean.minmax[1], facecolor='r', + ... alpha=0.2, label=r'Estimated mean (95% limits)') + >>> ax.vlines(res_std.statistic, 0, 0.5, colors='g', label='Estimated scale') + >>> ax.axvspan(res_std.minmax[0],res_std.minmax[1], facecolor='g', alpha=0.2, + ... label=r'Estimated scale (95% limits)') + + >>> ax.legend(fontsize=10) + >>> ax.set_xlim([-4, 4]) + >>> ax.set_ylim([0, 0.5]) + >>> plt.show() + + """ + m, v, s = mvsdist(data) + if alpha >= 1 or alpha <= 0: + raise ValueError(f"0 < alpha < 1 is required, but {alpha=} was given.") + + m_res = Mean(m.mean(), m.interval(alpha)) + v_res = Variance(v.mean(), v.interval(alpha)) + s_res = Std_dev(s.mean(), s.interval(alpha)) + + return m_res, v_res, s_res + + +@xp_capabilities(np_only=True) +def mvsdist(data): + """ + 'Frozen' distributions for mean, variance, and standard deviation of data. + + Parameters + ---------- + data : array_like + Input array. Converted to 1-D using ravel. + Requires 2 or more data-points. + + Returns + ------- + mdist : "frozen" distribution object + Distribution object representing the mean of the data. + vdist : "frozen" distribution object + Distribution object representing the variance of the data. + sdist : "frozen" distribution object + Distribution object representing the standard deviation of the data. + + See Also + -------- + bayes_mvs + + Notes + ----- + The return values from ``bayes_mvs(data)`` is equivalent to + ``tuple((x.mean(), x.interval(0.90)) for x in mvsdist(data))``. + + In other words, calling ``.mean()`` and ``.interval(0.90)`` + on the three distribution objects returned from this function will give + the same results that are returned from `bayes_mvs`. + + References + ---------- + T.E. Oliphant, "A Bayesian perspective on estimating mean, variance, and + standard-deviation from data", https://scholarsarchive.byu.edu/facpub/278, + 2006. + + Examples + -------- + >>> from scipy import stats + >>> data = [6, 9, 12, 7, 8, 8, 13] + >>> mean, var, std = stats.mvsdist(data) + + We now have frozen distribution objects "mean", "var" and "std" that we can + examine: + + >>> mean.mean() + 9.0 + >>> mean.interval(0.95) + (6.6120585482655692, 11.387941451734431) + >>> mean.std() + 1.1952286093343936 + + """ + x = ravel(data) + n = len(x) + if n < 2: + raise ValueError("Need at least 2 data-points.") + xbar = x.mean() + C = x.var() + if n > 1000: # gaussian approximations for large n + mdist = distributions.norm(loc=xbar, scale=math.sqrt(C / n)) + sdist = distributions.norm(loc=math.sqrt(C), scale=math.sqrt(C / (2. * n))) + vdist = distributions.norm(loc=C, scale=math.sqrt(2.0 / n) * C) + else: + nm1 = n - 1 + fac = n * C / 2. + val = nm1 / 2. + mdist = distributions.t(nm1, loc=xbar, scale=math.sqrt(C / nm1)) + sdist = distributions.gengamma(val, -2, scale=math.sqrt(fac)) + vdist = distributions.invgamma(val, scale=fac) + return mdist, vdist, sdist + + +@xp_capabilities(marray=True) +@_axis_nan_policy_factory( + lambda x: x, result_to_tuple=lambda x, _: (x,), n_outputs=1, default_axis=None +) +def kstat(data, n=2, *, axis=None): + r""" + Return the `n` th k-statistic ( ``1<=n<=4`` so far). + + The `n` th k-statistic ``k_n`` is the unique symmetric unbiased estimator of the + `n` th cumulant :math:`\kappa_n` [1]_ [2]_. + + Parameters + ---------- + data : array_like + Input array. + n : int, {1, 2, 3, 4}, optional + Default is equal to 2. + axis : int or None, default: None + If an int, the axis of the input along which to compute the statistic. + The statistic of each axis-slice (e.g. row) of the input will appear + in a corresponding element of the output. If ``None``, the input will + be raveled before computing the statistic. + + Returns + ------- + kstat : float + The `n` th k-statistic. + + See Also + -------- + kstatvar : Returns an unbiased estimator of the variance of the k-statistic + moment : Returns the n-th central moment about the mean for a sample. + + Notes + ----- + For a sample size :math:`n`, the first few k-statistics are given by + + .. math:: + + k_1 &= \frac{S_1}{n}, \\ + k_2 &= \frac{nS_2 - S_1^2}{n(n-1)}, \\ + k_3 &= \frac{2S_1^3 - 3nS_1S_2 + n^2S_3}{n(n-1)(n-2)}, \\ + k_4 &= \frac{-6S_1^4 + 12nS_1^2S_2 - 3n(n-1)S_2^2 - 4n(n+1)S_1S_3 + + n^2(n+1)S_4}{n (n-1)(n-2)(n-3)}, + + where + + .. math:: + + S_r \equiv \sum_{i=1}^n X_i^r, + + and :math:`X_i` is the :math:`i` th data point. + + References + ---------- + .. [1] http://mathworld.wolfram.com/k-Statistic.html + + .. [2] http://mathworld.wolfram.com/Cumulant.html + + Examples + -------- + >>> from scipy import stats + >>> from numpy.random import default_rng + >>> rng = default_rng() + + As sample size increases, `n`-th moment and `n`-th k-statistic converge to the + same number (although they aren't identical). In the case of the normal + distribution, they converge to zero. + + >>> for i in range(2,8): + ... x = rng.normal(size=10**i) + ... m, k = stats.moment(x, 3), stats.kstat(x, 3) + ... print(f"{i=}: {m=:.3g}, {k=:.3g}, {(m-k)=:.3g}") + i=2: m=-0.631, k=-0.651, (m-k)=0.0194 # random + i=3: m=0.0282, k=0.0283, (m-k)=-8.49e-05 + i=4: m=-0.0454, k=-0.0454, (m-k)=1.36e-05 + i=6: m=7.53e-05, k=7.53e-05, (m-k)=-2.26e-09 + i=7: m=0.00166, k=0.00166, (m-k)=-4.99e-09 + i=8: m=-2.88e-06 k=-2.88e-06, (m-k)=8.63e-13 + """ + xp = array_namespace(data) + data = xp.asarray(data) + if n > 4 or n < 1: + raise ValueError("k-statistics only supported for 1<=n<=4") + n = int(n) + if axis is None: + data = xp.reshape(data, (-1,)) + axis = 0 + + N = _count_nonmasked(data, axis, xp=xp) + + S = [None] + [xp.sum(data**k, axis=axis) for k in range(1, n + 1)] + if n == 1: + return S[1] * 1.0/N + elif n == 2: + return (N*S[2] - S[1]**2.0) / (N*(N - 1.0)) + elif n == 3: + return (2*S[1]**3 - 3*N*S[1]*S[2] + N*N*S[3]) / (N*(N - 1.0)*(N - 2.0)) + elif n == 4: + return ((-6*S[1]**4 + 12*N*S[1]**2 * S[2] - 3*N*(N-1.0)*S[2]**2 - + 4*N*(N+1)*S[1]*S[3] + N*N*(N+1)*S[4]) / + (N*(N-1.0)*(N-2.0)*(N-3.0))) + else: + raise ValueError("Should not be here.") + + +@xp_capabilities(marray=True) +@_axis_nan_policy_factory( + lambda x: x, result_to_tuple=lambda x, _: (x,), n_outputs=1, default_axis=None +) +def kstatvar(data, n=2, *, axis=None): + r"""Return an unbiased estimator of the variance of the k-statistic. + + See `kstat` and [1]_ for more details about the k-statistic. + + Parameters + ---------- + data : array_like + Input array. + n : int, {1, 2}, optional + Default is equal to 2. + axis : int or None, default: None + If an int, the axis of the input along which to compute the statistic. + The statistic of each axis-slice (e.g. row) of the input will appear + in a corresponding element of the output. If ``None``, the input will + be raveled before computing the statistic. + + Returns + ------- + kstatvar : float + The `n` th k-statistic variance. + + See Also + -------- + kstat : Returns the n-th k-statistic. + moment : Returns the n-th central moment about the mean for a sample. + + Notes + ----- + Unbiased estimators of the variances of the first two k-statistics are given by + + .. math:: + + \mathrm{var}(k_1) &= \frac{k_2}{n}, \\ + \mathrm{var}(k_2) &= \frac{2k_2^2n + (n-1)k_4}{n(n + 1)}. + + References + ---------- + .. [1] http://mathworld.wolfram.com/k-Statistic.html + + Examples + -------- + >>> import numpy as np + >>> from scipy import stats + >>> rng = np.random.default_rng(92366746) + + As the sample size increases, the estimated variance of the k-statistic converges + to zero. + + >>> for n in np.astype(np.logspace(1, 6, 6), int): + ... x = rng.normal(size=n) + ... kvar = stats.kstatvar(x, 1) + ... print(f"{n=:<8}: {kvar=:.3g}") + n=10 : kvar=0.0954 + n=100 : kvar=0.00974 + n=1000 : kvar=0.000962 + n=10000 : kvar=0.0001 + n=100000 : kvar=9.94e-06 + n=1000000 : kvar=9.99e-07 + """ # noqa: E501 + xp = array_namespace(data) + data = xp.asarray(data) + if axis is None: + data = xp.reshape(data, (-1,)) + axis = 0 + N = _count_nonmasked(data, axis, xp=xp) + + if n == 1: + return kstat(data, n=2, axis=axis, _no_deco=True) * 1.0/N + elif n == 2: + k2 = kstat(data, n=2, axis=axis, _no_deco=True) + k4 = kstat(data, n=4, axis=axis, _no_deco=True) + return (2*N*k2**2 + (N-1)*k4) / (N*(N+1)) + else: + raise ValueError("Only n=1 or n=2 supported.") + + +def _calc_uniform_order_statistic_medians(n): + """Approximations of uniform order statistic medians. + + Parameters + ---------- + n : int + Sample size. + + Returns + ------- + v : 1d float array + Approximations of the order statistic medians. + + References + ---------- + .. [1] James J. Filliben, "The Probability Plot Correlation Coefficient + Test for Normality", Technometrics, Vol. 17, pp. 111-117, 1975. + + Examples + -------- + Order statistics of the uniform distribution on the unit interval + are marginally distributed according to beta distributions. + The expectations of these order statistic are evenly spaced across + the interval, but the distributions are skewed in a way that + pushes the medians slightly towards the endpoints of the unit interval: + + >>> import numpy as np + >>> n = 4 + >>> k = np.arange(1, n+1) + >>> from scipy.stats import beta + >>> a = k + >>> b = n-k+1 + >>> beta.mean(a, b) + array([0.2, 0.4, 0.6, 0.8]) + >>> beta.median(a, b) + array([0.15910358, 0.38572757, 0.61427243, 0.84089642]) + + The Filliben approximation uses the exact medians of the smallest + and greatest order statistics, and the remaining medians are approximated + by points spread evenly across a sub-interval of the unit interval: + + >>> from scipy.stats._morestats import _calc_uniform_order_statistic_medians + >>> _calc_uniform_order_statistic_medians(n) + array([0.15910358, 0.38545246, 0.61454754, 0.84089642]) + + This plot shows the skewed distributions of the order statistics + of a sample of size four from a uniform distribution on the unit interval: + + >>> import matplotlib.pyplot as plt + >>> x = np.linspace(0.0, 1.0, num=50, endpoint=True) + >>> pdfs = [beta.pdf(x, a[i], b[i]) for i in range(n)] + >>> plt.figure() + >>> plt.plot(x, pdfs[0], x, pdfs[1], x, pdfs[2], x, pdfs[3]) + + """ + v = np.empty(n, dtype=np.float64) + v[-1] = 0.5**(1.0 / n) + v[0] = 1 - v[-1] + i = np.arange(2, n) + v[1:-1] = (i - 0.3175) / (n + 0.365) + return v + + +def _parse_dist_kw(dist, enforce_subclass=True): + """Parse `dist` keyword. + + Parameters + ---------- + dist : str or stats.distributions instance. + Several functions take `dist` as a keyword, hence this utility + function. + enforce_subclass : bool, optional + If True (default), `dist` needs to be a + `_distn_infrastructure.rv_generic` instance. + It can sometimes be useful to set this keyword to False, if a function + wants to accept objects that just look somewhat like such an instance + (for example, they have a ``ppf`` method). + + """ + if isinstance(dist, rv_generic): + pass + elif isinstance(dist, str): + try: + dist = getattr(distributions, dist) + except AttributeError as e: + raise ValueError(f"{dist} is not a valid distribution name") from e + elif enforce_subclass: + msg = ("`dist` should be a stats.distributions instance or a string " + "with the name of such a distribution.") + raise ValueError(msg) + + return dist + + +def _add_axis_labels_title(plot, xlabel, ylabel, title): + """Helper function to add axes labels and a title to stats plots.""" + try: + if hasattr(plot, 'set_title'): + # Matplotlib Axes instance or something that looks like it + plot.set_title(title) + plot.set_xlabel(xlabel) + plot.set_ylabel(ylabel) + else: + # matplotlib.pyplot module + plot.title(title) + plot.xlabel(xlabel) + plot.ylabel(ylabel) + except Exception: + # Not an MPL object or something that looks (enough) like it. + # Don't crash on adding labels or title + pass + + +@xp_capabilities(np_only=True) +def probplot(x, sparams=(), dist='norm', fit=True, plot=None, rvalue=False): + """ + Calculate quantiles for a probability plot, and optionally show the plot. + + Generates a probability plot of sample data against the quantiles of a + specified theoretical distribution (the normal distribution by default). + `probplot` optionally calculates a best-fit line for the data and plots the + results using Matplotlib or a given plot function. + + Parameters + ---------- + x : array_like + Sample/response data from which `probplot` creates the plot. + sparams : tuple, optional + Distribution-specific shape parameters (shape parameters plus location + and scale). + dist : str or stats.distributions instance, optional + Distribution or distribution function name. The default is 'norm' for a + normal probability plot. Objects that look enough like a + stats.distributions instance (i.e. they have a ``ppf`` method) are also + accepted. + fit : bool, optional + Fit a least-squares regression (best-fit) line to the sample data if + True (default). + plot : object, optional + If given, plots the quantiles. + If given and `fit` is True, also plots the least squares fit. + `plot` is an object that has to have methods "plot" and "text". + The `matplotlib.pyplot` module or a Matplotlib Axes object can be used, + or a custom object with the same methods. + Default is None, which means that no plot is created. + rvalue : bool, optional + If `plot` is provided and `fit` is True, setting `rvalue` to True + includes the coefficient of determination on the plot. + Default is False. + + Returns + ------- + (osm, osr) : tuple of ndarrays + Tuple of theoretical quantiles (osm, or order statistic medians) and + ordered responses (osr). `osr` is simply sorted input `x`. + For details on how `osm` is calculated see the Notes section. + (slope, intercept, r) : tuple of floats, optional + Tuple containing the result of the least-squares fit, if that is + performed by `probplot`. `r` is the square root of the coefficient of + determination. If ``fit=False`` and ``plot=None``, this tuple is not + returned. + + Notes + ----- + Even if `plot` is given, the figure is not shown or saved by `probplot`; + ``plt.show()`` or ``plt.savefig('figname.png')`` should be used after + calling `probplot`. + + `probplot` generates a probability plot, which should not be confused with + a Q-Q or a P-P plot. Statsmodels has more extensive functionality of this + type, see ``statsmodels.api.ProbPlot``. + + The formula used for the theoretical quantiles (horizontal axis of the + probability plot) is Filliben's estimate:: + + quantiles = dist.ppf(val), for + + 0.5**(1/n), for i = n + val = (i - 0.3175) / (n + 0.365), for i = 2, ..., n-1 + 1 - 0.5**(1/n), for i = 1 + + where ``i`` indicates the i-th ordered value and ``n`` is the total number + of values. + + Examples + -------- + >>> import numpy as np + >>> from scipy import stats + >>> import matplotlib.pyplot as plt + >>> nsample = 100 + >>> rng = np.random.default_rng() + + A t distribution with small degrees of freedom: + + >>> ax1 = plt.subplot(221) + >>> x = stats.t.rvs(3, size=nsample, random_state=rng) + >>> res = stats.probplot(x, plot=plt) + + A t distribution with larger degrees of freedom: + + >>> ax2 = plt.subplot(222) + >>> x = stats.t.rvs(25, size=nsample, random_state=rng) + >>> res = stats.probplot(x, plot=plt) + + A mixture of two normal distributions with broadcasting: + + >>> ax3 = plt.subplot(223) + >>> x = stats.norm.rvs(loc=[0,5], scale=[1,1.5], + ... size=(nsample//2,2), random_state=rng).ravel() + >>> res = stats.probplot(x, plot=plt) + + A standard normal distribution: + + >>> ax4 = plt.subplot(224) + >>> x = stats.norm.rvs(loc=0, scale=1, size=nsample, random_state=rng) + >>> res = stats.probplot(x, plot=plt) + + Produce a new figure with a loggamma distribution, using the ``dist`` and + ``sparams`` keywords: + + >>> fig = plt.figure() + >>> ax = fig.add_subplot(111) + >>> x = stats.loggamma.rvs(c=2.5, size=500, random_state=rng) + >>> res = stats.probplot(x, dist=stats.loggamma, sparams=(2.5,), plot=ax) + >>> ax.set_title("Probplot for loggamma dist with shape parameter 2.5") + + Show the results with Matplotlib: + + >>> plt.show() + + """ + x = np.asarray(x) + if x.size == 0: + if fit: + return (x, x), (np.nan, np.nan, 0.0) + else: + return x, x + + osm_uniform = _calc_uniform_order_statistic_medians(len(x)) + dist = _parse_dist_kw(dist, enforce_subclass=False) + if sparams is None: + sparams = () + if isscalar(sparams): + sparams = (sparams,) + if not isinstance(sparams, tuple): + sparams = tuple(sparams) + + osm = dist.ppf(osm_uniform, *sparams) + osr = sort(x) + if fit: + # perform a linear least squares fit. + slope, intercept, r, prob, _ = _stats_py.linregress(osm, osr) + + if plot is not None: + plot.plot(osm, osr, 'bo') + if fit: + plot.plot(osm, slope*osm + intercept, 'r-') + _add_axis_labels_title(plot, xlabel='Theoretical quantiles', + ylabel='Ordered Values', + title='Probability Plot') + + # Add R^2 value to the plot as text + if fit and rvalue: + xmin = amin(osm) + xmax = amax(osm) + ymin = amin(x) + ymax = amax(x) + posx = xmin + 0.70 * (xmax - xmin) + posy = ymin + 0.01 * (ymax - ymin) + plot.text(posx, posy, f"$R^2={r ** 2:1.4f}$") + + if fit: + return (osm, osr), (slope, intercept, r) + else: + return osm, osr + + +@xp_capabilities(np_only=True) +def ppcc_max(x, brack=(0.0, 1.0), dist='tukeylambda'): + """Calculate the shape parameter that maximizes the PPCC. + + The probability plot correlation coefficient (PPCC) plot can be used + to determine the optimal shape parameter for a one-parameter family + of distributions. ``ppcc_max`` returns the shape parameter that would + maximize the probability plot correlation coefficient for the given + data to a one-parameter family of distributions. + + Parameters + ---------- + x : array_like + Input array. + brack : tuple, optional + Triple (a,b,c) where (a>> import numpy as np + >>> from scipy import stats + >>> import matplotlib.pyplot as plt + >>> rng = np.random.default_rng() + >>> c = 2.5 + >>> x = stats.weibull_min.rvs(c, scale=4, size=2000, random_state=rng) + + Generate the PPCC plot for this data with the Weibull distribution. + + >>> fig, ax = plt.subplots(figsize=(8, 6)) + >>> res = stats.ppcc_plot(x, c/2, 2*c, dist='weibull_min', plot=ax) + + We calculate the value where the shape should reach its maximum and a + red line is drawn there. The line should coincide with the highest + point in the PPCC graph. + + >>> cmax = stats.ppcc_max(x, brack=(c/2, 2*c), dist='weibull_min') + >>> ax.axvline(cmax, color='r') + >>> plt.show() + + """ + dist = _parse_dist_kw(dist) + osm_uniform = _calc_uniform_order_statistic_medians(len(x)) + osr = sort(x) + + # this function computes the x-axis values of the probability plot + # and computes a linear regression (including the correlation) + # and returns 1-r so that a minimization function maximizes the + # correlation + def tempfunc(shape, mi, yvals, func): + xvals = func(mi, shape) + r, prob = _stats_py.pearsonr(xvals, yvals) + return 1 - r + + return optimize.brent(tempfunc, brack=brack, + args=(osm_uniform, osr, dist.ppf)) + + +@xp_capabilities(np_only=True) +def ppcc_plot(x, a, b, dist='tukeylambda', plot=None, N=80): + """Calculate and optionally plot probability plot correlation coefficient. + + The probability plot correlation coefficient (PPCC) plot can be used to + determine the optimal shape parameter for a one-parameter family of + distributions. It cannot be used for distributions without shape + parameters + (like the normal distribution) or with multiple shape parameters. + + By default a Tukey-Lambda distribution (`stats.tukeylambda`) is used. A + Tukey-Lambda PPCC plot interpolates from long-tailed to short-tailed + distributions via an approximately normal one, and is therefore + particularly useful in practice. + + Parameters + ---------- + x : array_like + Input array. + a, b : scalar + Lower and upper bounds of the shape parameter to use. + dist : str or stats.distributions instance, optional + Distribution or distribution function name. Objects that look enough + like a stats.distributions instance (i.e. they have a ``ppf`` method) + are also accepted. The default is ``'tukeylambda'``. + plot : object, optional + If given, plots PPCC against the shape parameter. + `plot` is an object that has to have methods "plot" and "text". + The `matplotlib.pyplot` module or a Matplotlib Axes object can be used, + or a custom object with the same methods. + Default is None, which means that no plot is created. + N : int, optional + Number of points on the horizontal axis (equally distributed from + `a` to `b`). + + Returns + ------- + svals : ndarray + The shape values for which `ppcc` was calculated. + ppcc : ndarray + The calculated probability plot correlation coefficient values. + + See Also + -------- + ppcc_max, probplot, boxcox_normplot, tukeylambda + + References + ---------- + J.J. Filliben, "The Probability Plot Correlation Coefficient Test for + Normality", Technometrics, Vol. 17, pp. 111-117, 1975. + + Examples + -------- + First we generate some random data from a Weibull distribution + with shape parameter 2.5, and plot the histogram of the data: + + >>> import numpy as np + >>> from scipy import stats + >>> import matplotlib.pyplot as plt + >>> rng = np.random.default_rng() + >>> c = 2.5 + >>> x = stats.weibull_min.rvs(c, scale=4, size=2000, random_state=rng) + + Take a look at the histogram of the data. + + >>> fig1, ax = plt.subplots(figsize=(9, 4)) + >>> ax.hist(x, bins=50) + >>> ax.set_title('Histogram of x') + >>> plt.show() + + Now we explore this data with a PPCC plot as well as the related + probability plot and Box-Cox normplot. A red line is drawn where we + expect the PPCC value to be maximal (at the shape parameter ``c`` + used above): + + >>> fig2 = plt.figure(figsize=(12, 4)) + >>> ax1 = fig2.add_subplot(1, 3, 1) + >>> ax2 = fig2.add_subplot(1, 3, 2) + >>> ax3 = fig2.add_subplot(1, 3, 3) + >>> res = stats.probplot(x, plot=ax1) + >>> res = stats.boxcox_normplot(x, -4, 4, plot=ax2) + >>> res = stats.ppcc_plot(x, c/2, 2*c, dist='weibull_min', plot=ax3) + >>> ax3.axvline(c, color='r') + >>> plt.show() + + """ + if b <= a: + raise ValueError("`b` has to be larger than `a`.") + + svals = np.linspace(a, b, num=N) + ppcc = np.empty_like(svals) + for k, sval in enumerate(svals): + _, r2 = probplot(x, sval, dist=dist, fit=True) + ppcc[k] = r2[-1] + + if plot is not None: + plot.plot(svals, ppcc, 'x') + _add_axis_labels_title(plot, xlabel='Shape Values', + ylabel='Prob Plot Corr. Coef.', + title=f'({dist}) PPCC Plot') + + return svals, ppcc + + +def _log_mean(logx, axis): + # compute log of mean of x from log(x) + return ( + special.logsumexp(logx, axis=axis, keepdims=True) + - math.log(logx.shape[axis]) + ) + + +def _log_var(logx, xp, axis): + # compute log of variance of x from log(x) + logmean = xp.broadcast_to(_log_mean(logx, axis=axis), logx.shape) + ones = xp.ones_like(logx) + logxmu, _ = special.logsumexp(xp.stack((logx, logmean), axis=0), axis=0, + b=xp.stack((ones, -ones), axis=0), return_sign=True) + return special.logsumexp(2 * logxmu, axis=axis) - math.log(logx.shape[axis]) + + +@xp_capabilities() +def boxcox_llf(lmb, data, *, axis=0, keepdims=False, nan_policy='propagate'): + r"""The boxcox log-likelihood function. + + Parameters + ---------- + lmb : scalar + Parameter for Box-Cox transformation. See `boxcox` for details. + data : array_like + Data to calculate Box-Cox log-likelihood for. If `data` is + multi-dimensional, the log-likelihood is calculated along the first + axis. + axis : int, default: 0 + If an int, the axis of the input along which to compute the statistic. + The statistic of each axis-slice (e.g. row) of the input will appear in a + corresponding element of the output. + If ``None``, the input will be raveled before computing the statistic. + keepdims : bool, default: False + If this is set to True, the axes which are reduced are left + in the result as dimensions with size one. With this option, + the result will broadcast correctly against the input array. + nan_policy : {'propagate', 'omit', 'raise'} + Defines how to handle input NaNs. + + - ``propagate``: if a NaN is present in the axis slice (e.g. row) along + which the statistic is computed, the corresponding entry of the output + will be NaN. + - ``omit``: NaNs will be omitted when performing the calculation. + If insufficient data remains in the axis slice along which the + statistic is computed, the corresponding entry of the output will be + NaN. + - ``raise``: if a NaN is present, a ``ValueError`` will be raised. + + Returns + ------- + llf : float or ndarray + Box-Cox log-likelihood of `data` given `lmb`. A float for 1-D `data`, + an array otherwise. + + See Also + -------- + boxcox, probplot, boxcox_normplot, boxcox_normmax + + Notes + ----- + The Box-Cox log-likelihood function :math:`l` is defined here as + + .. math:: + + l = (\lambda - 1) \sum_i^N \log(x_i) - + \frac{N}{2} \log\left(\sum_i^N (y_i - \bar{y})^2 / N\right), + + where :math:`N` is the number of data points ``data`` and :math:`y` is the Box-Cox + transformed input data. + This corresponds to the *profile log-likelihood* of the original data :math:`x` + with some constant terms dropped. + + Examples + -------- + >>> import numpy as np + >>> from scipy import stats + >>> import matplotlib.pyplot as plt + >>> from mpl_toolkits.axes_grid1.inset_locator import inset_axes + + Generate some random variates and calculate Box-Cox log-likelihood values + for them for a range of ``lmbda`` values: + + >>> rng = np.random.default_rng() + >>> x = stats.loggamma.rvs(5, loc=10, size=1000, random_state=rng) + >>> lmbdas = np.linspace(-2, 10) + >>> llf = np.zeros(lmbdas.shape, dtype=float) + >>> for ii, lmbda in enumerate(lmbdas): + ... llf[ii] = stats.boxcox_llf(lmbda, x) + + Also find the optimal lmbda value with `boxcox`: + + >>> x_most_normal, lmbda_optimal = stats.boxcox(x) + + Plot the log-likelihood as function of lmbda. Add the optimal lmbda as a + horizontal line to check that that's really the optimum: + + >>> fig = plt.figure() + >>> ax = fig.add_subplot(111) + >>> ax.plot(lmbdas, llf, 'b.-') + >>> ax.axhline(stats.boxcox_llf(lmbda_optimal, x), color='r') + >>> ax.set_xlabel('lmbda parameter') + >>> ax.set_ylabel('Box-Cox log-likelihood') + + Now add some probability plots to show that where the log-likelihood is + maximized the data transformed with `boxcox` looks closest to normal: + + >>> locs = [3, 10, 4] # 'lower left', 'center', 'lower right' + >>> for lmbda, loc in zip([-1, lmbda_optimal, 9], locs): + ... xt = stats.boxcox(x, lmbda=lmbda) + ... (osm, osr), (slope, intercept, r_sq) = stats.probplot(xt) + ... ax_inset = inset_axes(ax, width="20%", height="20%", loc=loc) + ... ax_inset.plot(osm, osr, 'c.', osm, slope*osm + intercept, 'k-') + ... ax_inset.set_xticklabels([]) + ... ax_inset.set_yticklabels([]) + ... ax_inset.set_title(r'$\lambda=%1.2f$' % lmbda) + + >>> plt.show() + + """ + # _axis_nan_policy decorator does not currently support these for lazy arrays. + # We want to run tests with lazy backends, so don't pass the arguments explicitly + # unless necessary. + kwargs = {} + if keepdims is not False: + kwargs['keepdims'] = keepdims + if nan_policy != 'propagate': + kwargs['nan_policy'] = nan_policy + return _boxcox_llf(data, lmb=lmb, axis=axis, **kwargs) + + +@_axis_nan_policy_factory(lambda x: x, n_outputs=1, default_axis=0, + result_to_tuple=lambda x, _: (x,)) +def _boxcox_llf(data, axis=0, *, lmb): + xp = array_namespace(data) + dtype = xp_result_type(lmb, data, force_floating=True, xp=xp) + data = xp.asarray(data, dtype=dtype) + N = data.shape[axis] + if N == 0: + return _get_nan(data, xp=xp) + + logdata = xp.log(data) + + # Compute the variance of the transformed data. + if lmb == 0: + logvar = xp.log(xp.var(logdata, axis=axis)) + else: + # Transform without the constant offset 1/lmb. The offset does + # not affect the variance, and the subtraction of the offset can + # lead to loss of precision. + # Division by lmb can be factored out to enhance numerical stability. + logx = lmb * logdata + logvar = _log_var(logx, xp, axis) - 2 * math.log(abs(lmb)) + + res = (lmb - 1) * xp.sum(logdata, axis=axis) - N/2 * logvar + res = xp.astype(res, data.dtype, copy=False) # compensate for NumPy <2.0 + res = res[()] if res.ndim == 0 else res + return res + + +def _boxcox_conf_interval(x, lmax, alpha): + # Need to find the lambda for which + # f(x,lmbda) >= f(x,lmax) - 0.5*chi^2_alpha;1 + fac = 0.5 * distributions.chi2.ppf(1 - alpha, 1) + target = boxcox_llf(lmax, x) - fac + + def rootfunc(lmbda, data, target): + return boxcox_llf(lmbda, data) - target + + # Find positive endpoint of interval in which answer is to be found + newlm = lmax + 0.5 + N = 0 + while (rootfunc(newlm, x, target) > 0.0) and (N < 500): + newlm += 0.1 + N += 1 + + if N == 500: + raise RuntimeError("Could not find endpoint.") + + lmplus = optimize.brentq(rootfunc, lmax, newlm, args=(x, target)) + + # Now find negative interval in the same way + newlm = lmax - 0.5 + N = 0 + while (rootfunc(newlm, x, target) > 0.0) and (N < 500): + newlm -= 0.1 + N += 1 + + if N == 500: + raise RuntimeError("Could not find endpoint.") + + lmminus = optimize.brentq(rootfunc, newlm, lmax, args=(x, target)) + return lmminus, lmplus + + +@xp_capabilities(np_only=True) +def boxcox(x, lmbda=None, alpha=None, optimizer=None, *, nan_policy='propagate'): + r"""Return a dataset transformed by a Box-Cox power transformation. + + Parameters + ---------- + x : ndarray + Input array to be transformed. + + If `lmbda` is not None, this is an alias of + `scipy.special.boxcox`. + Returns nan if ``x < 0``; returns -inf if ``x == 0 and lmbda < 0``. + + If `lmbda` is None, array must be positive, 1-dimensional, and + non-constant. + + lmbda : scalar, optional + If `lmbda` is None (default), find the value of `lmbda` that maximizes + the log-likelihood function and return it as the second output + argument. + + If `lmbda` is not None, do the transformation for that value. + + alpha : float, optional + If `lmbda` is None and `alpha` is not None (default), return the + ``100 * (1-alpha)%`` confidence interval for `lmbda` as the third + output argument. Must be between 0.0 and 1.0. + + If `lmbda` is not None, `alpha` is ignored. + optimizer : callable, optional + If `lmbda` is None, `optimizer` is the scalar optimizer used to find + the value of `lmbda` that minimizes the negative log-likelihood + function. `optimizer` is a callable that accepts one argument: + + fun : callable + The objective function, which evaluates the negative + log-likelihood function at a provided value of `lmbda` + + and returns an object, such as an instance of + `scipy.optimize.OptimizeResult`, which holds the optimal value of + `lmbda` in an attribute `x`. + + See the example in `boxcox_normmax` or the documentation of + `scipy.optimize.minimize_scalar` for more information. + + If `lmbda` is not None, `optimizer` is ignored. + nan_policy : {'propagate', 'omit', 'raise'} + Defines how to handle NaNs in `x`. + + - ``propagate``: if a NaN is present, all outputs will contain NaNs. + - ``omit``: NaNs will be omitted when calculating the optimal `maxlog` and + confidence interval; NaNs in `x` will remain NaNs in the transformed data. + - ``raise``: if a NaN is present, a ``ValueError`` will be raised. + + Returns + ------- + boxcox : ndarray + Box-Cox power transformed array. + maxlog : float, optional + If the `lmbda` parameter is None, the second returned argument is + the `lmbda` that maximizes the log-likelihood function. + (min_ci, max_ci) : tuple of float, optional + If `lmbda` parameter is None and `alpha` is not None, this returned + tuple of floats represents the minimum and maximum confidence limits + given `alpha`. + + See Also + -------- + probplot, boxcox_normplot, boxcox_normmax, boxcox_llf + + Notes + ----- + The Box-Cox transform is given by: + + .. math:: + + y = + \begin{cases} + \frac{x^\lambda - 1}{\lambda}, &\text{for } \lambda \neq 0 \\ + \log(x), &\text{for } \lambda = 0 + \end{cases} + + `boxcox` requires the input data to be positive. Sometimes a Box-Cox + transformation provides a shift parameter to achieve this; `boxcox` does + not. Such a shift parameter is equivalent to adding a positive constant to + `x` before calling `boxcox`. + + The confidence limits returned when `alpha` is provided give the interval + where: + + .. math:: + + l(\hat{\lambda}) - l(\lambda) < \frac{1}{2}\chi^2(1 - \alpha, 1), + + with :math:`l` the log-likelihood function and :math:`\chi^2` the chi-squared + function. + + References + ---------- + G.E.P. Box and D.R. Cox, "An Analysis of Transformations", Journal of the + Royal Statistical Society B, 26, 211-252 (1964). + + Examples + -------- + >>> from scipy import stats + >>> import matplotlib.pyplot as plt + + We generate some random variates from a non-normal distribution and make a + probability plot for it, to show it is non-normal in the tails: + + >>> fig = plt.figure() + >>> ax1 = fig.add_subplot(211) + >>> x = stats.loggamma.rvs(5, size=500) + 5 + >>> prob = stats.probplot(x, dist=stats.norm, plot=ax1) + >>> ax1.set_xlabel('') + >>> ax1.set_title('Probplot against normal distribution') + + We now use `boxcox` to transform the data so it's closest to normal: + + >>> ax2 = fig.add_subplot(212) + >>> xt, _ = stats.boxcox(x) + >>> prob = stats.probplot(xt, dist=stats.norm, plot=ax2) + >>> ax2.set_title('Probplot after Box-Cox transformation') + + >>> plt.show() + + """ + x = np.asarray(x) + + if lmbda is not None: # single transformation + _contains_nan(x, nan_policy, xp_omit_okay=True) # handle nan_policy='raise' + return special.boxcox(x, lmbda) + + if x.ndim != 1: + raise ValueError("Data must be 1-dimensional.") + + if x.size == 0: + return x + + if np.all(x == x[0]): + raise ValueError("Data must not be constant.") + + if np.any(x <= 0): + raise ValueError("Data must be positive.") + + # If lmbda=None, find the lmbda that maximizes the log-likelihood function. + lmax = boxcox_normmax(x, method='mle', optimizer=optimizer, nan_policy=nan_policy) + y = special.boxcox(x, lmax) + + if alpha is None: + return y, lmax + else: + # Find confidence interval + interval = ((lmax, lmax) if np.isnan(lmax) else + _boxcox_conf_interval(x[~np.isnan(x)], lmax, alpha)) + return y, lmax, interval + + +def _boxcox_inv_lmbda(x, y): + # compute lmbda given x and y for Box-Cox transformation + num = special.lambertw(-(x ** (-1 / y)) * np.log(x) / y, k=-1) + return np.real(-num / np.log(x) - 1 / y) + + +class _BigFloat: + def __repr__(self): + return "BIG_FLOAT" + + +_BigFloat_singleton = _BigFloat() + + +@xp_capabilities(np_only=True) +def boxcox_normmax( + x, brack=None, method='pearsonr', optimizer=None, *, ymax=_BigFloat_singleton, + nan_policy='propagate' +): + """Compute optimal Box-Cox transform parameter for input data. + + Parameters + ---------- + x : array_like + Input array. All entries must be positive, finite, real numbers. + brack : 2-tuple, optional, default (-2.0, 2.0) + The starting interval for a downhill bracket search for the default + `optimize.brent` solver. Note that this is in most cases not + critical; the final result is allowed to be outside this bracket. + If `optimizer` is passed, `brack` must be None. + method : str, optional + The method to determine the optimal transform parameter (`boxcox` + ``lmbda`` parameter). Options are: + + 'pearsonr' (default) + Maximizes the Pearson correlation coefficient between + ``y = boxcox(x)`` and the expected values for ``y`` if `x` would be + normally-distributed. + + 'mle' + Maximizes the log-likelihood `boxcox_llf`. This is the method used + in `boxcox`. + + 'all' + Use all optimization methods available, and return all results. + Useful to compare different methods. + optimizer : callable, optional + `optimizer` is a callable that accepts one argument: + + fun : callable + The objective function to be minimized. `fun` accepts one argument, + the Box-Cox transform parameter `lmbda`, and returns the value of + the function (e.g., the negative log-likelihood) at the provided + argument. The job of `optimizer` is to find the value of `lmbda` + that *minimizes* `fun`. + + and returns an object, such as an instance of + `scipy.optimize.OptimizeResult`, which holds the optimal value of + `lmbda` in an attribute `x`. + + See the example below or the documentation of + `scipy.optimize.minimize_scalar` for more information. + ymax : float, optional + The unconstrained optimal transform parameter may cause Box-Cox + transformed data to have extreme magnitude or even overflow. + This parameter constrains MLE optimization such that the magnitude + of the transformed `x` does not exceed `ymax`. The default is + the maximum value of the input dtype. If set to infinity, + `boxcox_normmax` returns the unconstrained optimal lambda. + Ignored when ``method='pearsonr'``. + nan_policy : {'propagate', 'omit', 'raise'} + Defines how to handle input NaNs. + + - ``propagate``: if a NaN is present in the input, the output will be NaN. + - ``omit``: NaNs will be omitted when performing the calculation. + - ``raise``: if a NaN is present, a ``ValueError`` will be raised. + + Returns + ------- + maxlog : float or ndarray + The optimal transform parameter found. An array instead of a scalar + for ``method='all'``. + + See Also + -------- + boxcox, boxcox_llf, boxcox_normplot, scipy.optimize.minimize_scalar + + Examples + -------- + >>> import numpy as np + >>> from scipy import stats + >>> import matplotlib.pyplot as plt + + We can generate some data and determine the optimal ``lmbda`` in various + ways: + + >>> rng = np.random.default_rng() + >>> x = stats.loggamma.rvs(5, size=30, random_state=rng) + 5 + >>> y, lmax_mle = stats.boxcox(x) + >>> lmax_pearsonr = stats.boxcox_normmax(x) + + >>> lmax_mle + 2.217563431465757 + >>> lmax_pearsonr + 2.238318660200961 + >>> stats.boxcox_normmax(x, method='all') + array([2.23831866, 2.21756343]) + + >>> fig = plt.figure() + >>> ax = fig.add_subplot(111) + >>> prob = stats.boxcox_normplot(x, -10, 10, plot=ax) + >>> ax.axvline(lmax_mle, color='r') + >>> ax.axvline(lmax_pearsonr, color='g', ls='--') + + >>> plt.show() + + Alternatively, we can define our own `optimizer` function. Suppose we + are only interested in values of `lmbda` on the interval [6, 7], we + want to use `scipy.optimize.minimize_scalar` with ``method='bounded'``, + and we want to use tighter tolerances when optimizing the log-likelihood + function. To do this, we define a function that accepts positional argument + `fun` and uses `scipy.optimize.minimize_scalar` to minimize `fun` subject + to the provided bounds and tolerances: + + >>> from scipy import optimize + >>> options = {'xatol': 1e-12} # absolute tolerance on `x` + >>> def optimizer(fun): + ... return optimize.minimize_scalar(fun, bounds=(6, 7), + ... method="bounded", options=options) + >>> stats.boxcox_normmax(x, optimizer=optimizer) + 6.000000000 + """ + x = np.asarray(x) + + contains_nan = _contains_nan(x, nan_policy, xp_omit_okay=True) + if contains_nan: + if nan_policy == 'propagate': + NaN = _get_nan(x, xp=np)[()] + return np.asarray([NaN, NaN]) if method == 'all' else NaN + x = x[~np.isnan(x)] # `nan_policy='omit + + if not np.all(np.isfinite(x) & (x >= 0)): + message = ("The `x` argument of `boxcox_normmax` must contain " + "only positive, finite, real numbers.") + raise ValueError(message) + + end_msg = "exceed specified `ymax`." + if ymax is _BigFloat_singleton: + dtype = x.dtype if np.issubdtype(x.dtype, np.floating) else np.float64 + # 10000 is a safety factor because `special.boxcox` overflows prematurely. + ymax = np.finfo(dtype).max / 10000 + end_msg = f"overflow in {dtype}." + elif ymax <= 0: + raise ValueError("`ymax` must be strictly positive") + + # If optimizer is not given, define default 'brent' optimizer. + if optimizer is None: + + # Set default value for `brack`. + if brack is None: + brack = (-2.0, 2.0) + + def _optimizer(func, args): + return optimize.brent(func, args=args, brack=brack) + + # Otherwise check optimizer. + else: + if not callable(optimizer): + raise ValueError("`optimizer` must be a callable") + + if brack is not None: + raise ValueError("`brack` must be None if `optimizer` is given") + + # `optimizer` is expected to return a `OptimizeResult` object, we here + # get the solution to the optimization problem. + def _optimizer(func, args): + def func_wrapped(x): + return func(x, *args) + return getattr(optimizer(func_wrapped), 'x', None) + + def _pearsonr(x): + osm_uniform = _calc_uniform_order_statistic_medians(len(x)) + xvals = distributions.norm.ppf(osm_uniform) + + def _eval_pearsonr(lmbda, xvals, samps): + # This function computes the x-axis values of the probability plot + # and computes a linear regression (including the correlation) and + # returns ``1 - r`` so that a minimization function maximizes the + # correlation. + y = boxcox(samps, lmbda) + yvals = np.sort(y) + r, prob = _stats_py.pearsonr(xvals, yvals) + return 1 - r + + return _optimizer(_eval_pearsonr, args=(xvals, x)) + + def _mle(x): + def _eval_mle(lmb, data): + # function to minimize + return -boxcox_llf(lmb, data) + + return _optimizer(_eval_mle, args=(x,)) + + def _all(x): + maxlog = np.empty(2, dtype=float) + maxlog[0] = _pearsonr(x) + maxlog[1] = _mle(x) + return maxlog + + methods = {'pearsonr': _pearsonr, + 'mle': _mle, + 'all': _all} + if method not in methods.keys(): + raise ValueError(f"Method {method} not recognized.") + + optimfunc = methods[method] + + res = optimfunc(x) + + if res is None: + message = ("The `optimizer` argument of `boxcox_normmax` must return " + "an object containing the optimal `lmbda` in attribute `x`.") + raise ValueError(message) + elif not np.isinf(ymax): # adjust the final lambda + # x > 1, boxcox(x) > 0; x < 1, boxcox(x) < 0 + xmax, xmin = np.max(x), np.min(x) + if xmin >= 1: + x_treme = xmax + elif xmax <= 1: + x_treme = xmin + else: # xmin < 1 < xmax + indicator = special.boxcox(xmax, res) > abs(special.boxcox(xmin, res)) + if isinstance(res, np.ndarray): + indicator = indicator[1] # select corresponds with 'mle' + x_treme = xmax if indicator else xmin + + mask = abs(special.boxcox(x_treme, res)) > ymax + if np.any(mask): + message = ( + f"The optimal lambda is {res}, but the returned lambda is the " + f"constrained optimum to ensure that the maximum or the minimum " + f"of the transformed data does not " + end_msg + ) + warnings.warn(message, stacklevel=2) + + # Return the constrained lambda to ensure the transformation + # does not cause overflow or exceed specified `ymax` + constrained_res = _boxcox_inv_lmbda(x_treme, ymax * np.sign(x_treme - 1)) + + if isinstance(res, np.ndarray): + res[mask] = constrained_res + else: + res = constrained_res + return res + + +def _normplot(method, x, la, lb, plot=None, N=80): + """Compute parameters for a Box-Cox or Yeo-Johnson normality plot, + optionally show it. + + See `boxcox_normplot` or `yeojohnson_normplot` for details. + """ + + if method == 'boxcox': + title = 'Box-Cox Normality Plot' + transform_func = boxcox + else: + title = 'Yeo-Johnson Normality Plot' + transform_func = yeojohnson + + x = np.asarray(x) + if x.size == 0: + return x + + if lb <= la: + raise ValueError("`lb` has to be larger than `la`.") + + if method == 'boxcox' and np.any(x <= 0): + raise ValueError("Data must be positive.") + + lmbdas = np.linspace(la, lb, num=N) + ppcc = lmbdas * 0.0 + for i, val in enumerate(lmbdas): + # Determine for each lmbda the square root of correlation coefficient + # of transformed x + z = transform_func(x, lmbda=val) + _, (_, _, r) = probplot(z, dist='norm', fit=True) + ppcc[i] = r + + if plot is not None: + plot.plot(lmbdas, ppcc, 'x') + _add_axis_labels_title(plot, xlabel='$\\lambda$', + ylabel='Prob Plot Corr. Coef.', + title=title) + + return lmbdas, ppcc + + +@xp_capabilities(np_only=True) +def boxcox_normplot(x, la, lb, plot=None, N=80): + """Compute parameters for a Box-Cox normality plot, optionally show it. + + A Box-Cox normality plot shows graphically what the best transformation + parameter is to use in `boxcox` to obtain a distribution that is close + to normal. + + Parameters + ---------- + x : array_like + Input array. + la, lb : scalar + The lower and upper bounds for the ``lmbda`` values to pass to `boxcox` + for Box-Cox transformations. These are also the limits of the + horizontal axis of the plot if that is generated. + plot : object, optional + If given, plots the quantiles and least squares fit. + `plot` is an object that has to have methods "plot" and "text". + The `matplotlib.pyplot` module or a Matplotlib Axes object can be used, + or a custom object with the same methods. + Default is None, which means that no plot is created. + N : int, optional + Number of points on the horizontal axis (equally distributed from + `la` to `lb`). + + Returns + ------- + lmbdas : ndarray + The ``lmbda`` values for which a Box-Cox transform was done. + ppcc : ndarray + Probability Plot Correlation Coefficient, as obtained from `probplot` + when fitting the Box-Cox transformed input `x` against a normal + distribution. + + See Also + -------- + probplot, boxcox, boxcox_normmax, boxcox_llf, ppcc_max + + Notes + ----- + Even if `plot` is given, the figure is not shown or saved by + `boxcox_normplot`; ``plt.show()`` or ``plt.savefig('figname.png')`` + should be used after calling `probplot`. + + Examples + -------- + >>> from scipy import stats + >>> import matplotlib.pyplot as plt + + Generate some non-normally distributed data, and create a Box-Cox plot: + + >>> x = stats.loggamma.rvs(5, size=500) + 5 + >>> fig = plt.figure() + >>> ax = fig.add_subplot(111) + >>> prob = stats.boxcox_normplot(x, -20, 20, plot=ax) + + Determine and plot the optimal ``lmbda`` to transform ``x`` and plot it in + the same plot: + + >>> _, maxlog = stats.boxcox(x) + >>> ax.axvline(maxlog, color='r') + + >>> plt.show() + + """ + return _normplot('boxcox', x, la, lb, plot, N) + + +@xp_capabilities(np_only=True) +def yeojohnson(x, lmbda=None, *, nan_policy='propagate'): + r"""Return a dataset transformed by a Yeo-Johnson power transformation. + + Parameters + ---------- + x : ndarray + Input array. Should be 1-dimensional. + lmbda : float, optional + If ``lmbda`` is ``None``, find the lambda that maximizes the + log-likelihood function and return it as the second output argument. + Otherwise the transformation is done for the given value. + nan_policy : {'propagate', 'omit', 'raise'} + Defines how to handle NaNs in `x`. + + - ``propagate``: if a NaN is present, all outputs will contain NaNs. + - ``omit``: NaNs will be omitted when calculating the optimal `maxlog`; + NaNs in `x` will remain NaNs in the transformed data. + - ``raise``: if a NaN is present, a ``ValueError`` will be raised. + + Returns + ------- + yeojohnson: ndarray + Yeo-Johnson power transformed array. + maxlog : float, optional + If the `lmbda` parameter is None, the second returned argument is + the lambda that maximizes the log-likelihood function. + + See Also + -------- + probplot, yeojohnson_normplot, yeojohnson_normmax, yeojohnson_llf, boxcox + + Notes + ----- + The Yeo-Johnson transform is given by: + + .. math:: + + y = + \begin{cases} + \frac{(x + 1)^\lambda - 1}{\lambda}, + &\text{for } x \geq 0, \lambda \neq 0 + \\ + \log(x + 1), + &\text{for } x \geq 0, \lambda = 0 + \\ + -\frac{(-x + 1)^{2 - \lambda} - 1}{2 - \lambda}, + &\text{for } x < 0, \lambda \neq 2 + \\ + -\log(-x + 1), + &\text{for } x < 0, \lambda = 2 + \end{cases} + + Unlike `boxcox`, `yeojohnson` does not require the input data to be + positive. + + .. versionadded:: 1.2.0 + + + References + ---------- + I. Yeo and R.A. Johnson, "A New Family of Power Transformations to + Improve Normality or Symmetry", Biometrika 87.4 (2000): + + + Examples + -------- + >>> from scipy import stats + >>> import matplotlib.pyplot as plt + + We generate some random variates from a non-normal distribution and make a + probability plot for it, to show it is non-normal in the tails: + + >>> fig = plt.figure() + >>> ax1 = fig.add_subplot(211) + >>> x = stats.loggamma.rvs(5, size=500) + 5 + >>> prob = stats.probplot(x, dist=stats.norm, plot=ax1) + >>> ax1.set_xlabel('') + >>> ax1.set_title('Probplot against normal distribution') + + We now use `yeojohnson` to transform the data so it's closest to normal: + + >>> ax2 = fig.add_subplot(212) + >>> xt, lmbda = stats.yeojohnson(x) + >>> prob = stats.probplot(xt, dist=stats.norm, plot=ax2) + >>> ax2.set_title('Probplot after Yeo-Johnson transformation') + + >>> plt.show() + + """ + x = np.asarray(x) + if x.size == 0: + return x + + if np.issubdtype(x.dtype, np.complexfloating): + raise ValueError('Yeo-Johnson transformation is not defined for ' + 'complex numbers.') + + if np.issubdtype(x.dtype, np.integer): + x = x.astype(np.float64, copy=False) + + if lmbda is not None: + _contains_nan(x, nan_policy, xp_omit_okay=True) # handle nan_policy='raise' + return _yeojohnson_transform(x, lmbda) + + # if lmbda=None, find the lmbda that maximizes the log-likelihood function. + lmax = yeojohnson_normmax(x, nan_policy=nan_policy) + y = _yeojohnson_transform(x, lmax) + + return y, lmax + + +def _yeojohnson_transform(x, lmbda, xp=None): + """Returns `x` transformed by the Yeo-Johnson power transform with given + parameter `lmbda`. + """ + xp = array_namespace(x) if xp is None else xp + dtype = xp_result_type(x, lmbda, force_floating=True, xp=xp) + eps = xp.finfo(dtype).eps + out = xp.zeros_like(x, dtype=dtype) + pos = x >= 0 # binary mask + + if is_jax(xp): + return xp.select( + [(abs(lmbda) < eps) & pos, (abs(lmbda - 2) < eps) & ~pos, pos], + [xp.log1p(x), -xp.log1p(-x), xp.expm1(lmbda * xp.log1p(x)) / lmbda], + -xp.expm1((2 - lmbda) * xp.log1p(-x)) / (2 - lmbda), + ) + + # when x >= 0 + if abs(lmbda) < eps: + out = xpx.at(out)[pos].set(xp.log1p(x[pos])) + else: # lmbda != 0 + # more stable version of: ((x + 1) ** lmbda - 1) / lmbda + out = xpx.at(out)[pos].set(xp.expm1(lmbda * xp.log1p(x[pos])) / lmbda) + + # when x < 0 + if abs(lmbda - 2) > eps: + out = xpx.at(out)[~pos].set( + -xp.expm1((2 - lmbda) * xp.log1p(-x[~pos])) / (2 - lmbda)) + else: # lmbda == 2 + out = xpx.at(out)[~pos].set(-xp.log1p(-x[~pos])) + + return out + + +@xp_capabilities(skip_backends=[("dask.array", "Dask can't broadcast nan shapes")]) +def yeojohnson_llf(lmb, data, *, axis=0, nan_policy='propagate', keepdims=False): + r"""The Yeo-Johnson log-likelihood function. + + Parameters + ---------- + lmb : scalar + Parameter for Yeo-Johnson transformation. See `yeojohnson` for + details. + data : array_like + Data to calculate Yeo-Johnson log-likelihood for. + axis : int, default: 0 + If an int, the axis of the input along which to compute the statistic. + The statistic of each axis-slice (e.g. row) of the input will appear in a + corresponding element of the output. + If ``None``, the input will be raveled before computing the statistic. + nan_policy : {'propagate', 'omit', 'raise' + Defines how to handle input NaNs. + + - ``propagate``: if a NaN is present in the axis slice (e.g. row) along + which the statistic is computed, the corresponding entry of the output + will be NaN. + - ``omit``: NaNs will be omitted when performing the calculation. + If insufficient data remains in the axis slice along which the + statistic is computed, the corresponding entry of the output will be + NaN. + - ``raise``: if a NaN is present, a ``ValueError`` will be raised. + + keepdims : bool, default: False + If this is set to True, the axes which are reduced are left + in the result as dimensions with size one. With this option, + the result will broadcast correctly against the input array. + + Returns + ------- + llf : float + Yeo-Johnson log-likelihood of `data` given `lmb`. + + See Also + -------- + yeojohnson, probplot, yeojohnson_normplot, yeojohnson_normmax + + Notes + ----- + The Yeo-Johnson log-likelihood function :math:`l` is defined here as + + .. math:: + + l = -\frac{N}{2} \log(\hat{\sigma}^2) + (\lambda - 1) + \sum_i^N \text{sign}(x_i) \log(|x_i| + 1) + + where :math:`N` is the number of data points :math:`x` = ``data`` and + :math:`\hat{\sigma}^2` is the estimated variance of the Yeo-Johnson transformed + input data :math:`x`. + This corresponds to the *profile log-likelihood* of the original data :math:`x` + with some constant terms dropped. + + .. versionadded:: 1.2.0 + + Examples + -------- + >>> import numpy as np + >>> from scipy import stats + >>> import matplotlib.pyplot as plt + >>> from mpl_toolkits.axes_grid1.inset_locator import inset_axes + + Generate some random variates and calculate Yeo-Johnson log-likelihood + values for them for a range of ``lmbda`` values: + + >>> x = stats.loggamma.rvs(5, loc=10, size=1000) + >>> lmbdas = np.linspace(-2, 10) + >>> llf = np.zeros(lmbdas.shape, dtype=float) + >>> for ii, lmbda in enumerate(lmbdas): + ... llf[ii] = stats.yeojohnson_llf(lmbda, x) + + Also find the optimal lmbda value with `yeojohnson`: + + >>> x_most_normal, lmbda_optimal = stats.yeojohnson(x) + + Plot the log-likelihood as function of lmbda. Add the optimal lmbda as a + horizontal line to check that that's really the optimum: + + >>> fig = plt.figure() + >>> ax = fig.add_subplot(111) + >>> ax.plot(lmbdas, llf, 'b.-') + >>> ax.axhline(stats.yeojohnson_llf(lmbda_optimal, x), color='r') + >>> ax.set_xlabel('lmbda parameter') + >>> ax.set_ylabel('Yeo-Johnson log-likelihood') + + Now add some probability plots to show that where the log-likelihood is + maximized the data transformed with `yeojohnson` looks closest to normal: + + >>> locs = [3, 10, 4] # 'lower left', 'center', 'lower right' + >>> for lmbda, loc in zip([-1, lmbda_optimal, 9], locs): + ... xt = stats.yeojohnson(x, lmbda=lmbda) + ... (osm, osr), (slope, intercept, r_sq) = stats.probplot(xt) + ... ax_inset = inset_axes(ax, width="20%", height="20%", loc=loc) + ... ax_inset.plot(osm, osr, 'c.', osm, slope*osm + intercept, 'k-') + ... ax_inset.set_xticklabels([]) + ... ax_inset.set_yticklabels([]) + ... ax_inset.set_title(r'$\lambda=%1.2f$' % lmbda) + + >>> plt.show() + + """ + # _axis_nan_policy decorator does not currently support these for lazy arrays. + # We want to run tests with lazy backends, so don't pass the arguments explicitly + # unless necessary. + kwargs = {} + if keepdims is not False: + kwargs['keepdims'] = keepdims + if nan_policy != 'propagate': + kwargs['nan_policy'] = nan_policy + res = _yeojohnson_llf(data, lmb=lmb, axis=axis, **kwargs) + return res[()] if res.ndim == 0 else res + + +@_axis_nan_policy_factory(lambda x: x, n_outputs=1, default_axis=0, + result_to_tuple=lambda x, _: (x,)) +def _yeojohnson_llf(data, *, lmb, axis=0): + xp = array_namespace(data) + dtype = xp_result_type(lmb, data, force_floating=True, xp=xp) + data = xp.asarray(data, dtype=dtype) + + n = data.shape[axis] + if n == 0: + return _get_nan(data, xp=xp) + eps = xp.finfo(dtype).eps + # Special case all-positive/negative data to avoid overflow and precision loss + pos = data >= 0 # binary mask + + # There exists numerical instability when abs(lmb) or abs(lmb - 2) is very small + if not is_lazy_array(pos) and xp.all(pos): + if abs(lmb) < eps: + logvar = xp.log(xp.var(xp.log1p(data), axis=axis)) + else: + logvar = _log_var(lmb * xp.log1p(data), xp, axis) - 2 * math.log(abs(lmb)) + + elif not is_lazy_array(pos) and xp.all(~pos): + if abs(lmb - 2) < eps: + logvar = xp.log(xp.var(xp.log1p(-data), axis=axis)) + else: + logvar = _log_var((2 - lmb) * xp.log1p(-data), xp, axis) - 2 * math.log( + abs(2 - lmb) + ) + + # overflow/precision loss not reported for mixed data; calculate `logvar` directly + else: # mixed positive and negative data + y = _yeojohnson_transform(data, lmb, xp=xp) + sigma = xp.var(y, axis=axis) + + # Suppress RuntimeWarning raised by np.log when the variance is too low + finite_variance = sigma >= xp.finfo(sigma.dtype).smallest_normal + logvar = xpx.apply_where(finite_variance, (sigma,), xp.log, fill_value=-xp.inf) + + loglike = (-n / 2 * logvar + + (lmb - 1) * xp.sum(xp.sign(data) * xp.log1p(xp.abs(data)), axis=axis)) + + return loglike + + +@xp_capabilities(np_only=True) +def yeojohnson_normmax(x, brack=None, *, nan_policy='propagate'): + """Compute optimal Yeo-Johnson transform parameter. + + Compute optimal Yeo-Johnson transform parameter for input data, using + maximum likelihood estimation. + + Parameters + ---------- + x : array_like + Input array. + brack : 2-tuple, optional + The starting interval for a downhill bracket search with + `optimize.brent`. Note that this is in most cases not critical; the + final result is allowed to be outside this bracket. If None, + `optimize.fminbound` is used with bounds that avoid overflow. + nan_policy : {'propagate', 'omit', 'raise'} + Defines how to handle input NaNs. + + - ``propagate``: if a NaN is present in the input, the output will be NaN. + - ``omit``: NaNs will be omitted when performing the calculation. + - ``raise``: if a NaN is present, a ``ValueError`` will be raised. + + Returns + ------- + maxlog : float + The optimal transform parameter found. + + See Also + -------- + yeojohnson, yeojohnson_llf, yeojohnson_normplot + + Notes + ----- + .. versionadded:: 1.2.0 + + Examples + -------- + >>> import numpy as np + >>> from scipy import stats + >>> import matplotlib.pyplot as plt + + Generate some data and determine optimal ``lmbda`` + + >>> rng = np.random.default_rng() + >>> x = stats.loggamma.rvs(5, size=30, random_state=rng) + 5 + >>> lmax = stats.yeojohnson_normmax(x) + + >>> fig = plt.figure() + >>> ax = fig.add_subplot(111) + >>> prob = stats.yeojohnson_normplot(x, -10, 10, plot=ax) + >>> ax.axvline(lmax, color='r') + + >>> plt.show() + + """ + def _neg_llf(lmbda, data): + llf = np.asarray(yeojohnson_llf(lmbda, data)) + # reject likelihoods that are inf which are likely due to small + # variance in the transformed space + llf[np.isinf(llf)] = -np.inf + return -llf + + with np.errstate(invalid='ignore'): + x = np.asarray(x) + + contains_nan = _contains_nan(x, nan_policy, xp_omit_okay=True) + if contains_nan: + if nan_policy == 'propagate': + return _get_nan(x, xp=np)[()] + x = x[~np.isnan(x)] # `nan_policy='omit + + if not np.all(np.isfinite(x)): + raise ValueError('Yeo-Johnson input must be finite.') + if np.all(x == 0): + return 1.0 + if brack is not None: + return optimize.brent(_neg_llf, brack=brack, args=(x,)) + + dtype = x.dtype if np.issubdtype(x.dtype, np.floating) else np.float64 + # Allow values up to 20 times the maximum observed value to be safely + # transformed without over- or underflow. + log1p_max_x = np.log1p(20 * np.max(np.abs(x))) + # Use half of floating point's exponent range to allow safe computation + # of the variance of the transformed data. + log_eps = np.log(np.finfo(dtype).eps) + log_tiny_float = (np.log(np.finfo(dtype).tiny) - log_eps) / 2 + log_max_float = (np.log(np.finfo(dtype).max) + log_eps) / 2 + # Compute the bounds by approximating the inverse of the Yeo-Johnson + # transform on the smallest and largest floating point exponents, given + # the largest data we expect to observe. See [1] for further details. + # [1] https://github.com/scipy/scipy/pull/18852#issuecomment-1630286174 + lb = log_tiny_float / log1p_max_x + ub = log_max_float / log1p_max_x + # Convert the bounds if all or some of the data is negative. + if np.all(x < 0): + lb, ub = 2 - ub, 2 - lb + elif np.any(x < 0): + lb, ub = max(2 - ub, lb), min(2 - lb, ub) + # Match `optimize.brent`'s tolerance. + tol_brent = 1.48e-08 + return optimize.fminbound(_neg_llf, lb, ub, args=(x,), xtol=tol_brent) + + +@xp_capabilities(np_only=True) +def yeojohnson_normplot(x, la, lb, plot=None, N=80): + """Compute parameters for a Yeo-Johnson normality plot, optionally show it. + + A Yeo-Johnson normality plot shows graphically what the best + transformation parameter is to use in `yeojohnson` to obtain a + distribution that is close to normal. + + Parameters + ---------- + x : array_like + Input array. + la, lb : scalar + The lower and upper bounds for the ``lmbda`` values to pass to + `yeojohnson` for Yeo-Johnson transformations. These are also the + limits of the horizontal axis of the plot if that is generated. + plot : object, optional + If given, plots the quantiles and least squares fit. + `plot` is an object that has to have methods "plot" and "text". + The `matplotlib.pyplot` module or a Matplotlib Axes object can be used, + or a custom object with the same methods. + Default is None, which means that no plot is created. + N : int, optional + Number of points on the horizontal axis (equally distributed from + `la` to `lb`). + + Returns + ------- + lmbdas : ndarray + The ``lmbda`` values for which a Yeo-Johnson transform was done. + ppcc : ndarray + Probability Plot Correlation Coefficient, as obtained from `probplot` + when fitting the Box-Cox transformed input `x` against a normal + distribution. + + See Also + -------- + probplot, yeojohnson, yeojohnson_normmax, yeojohnson_llf, ppcc_max + + Notes + ----- + Even if `plot` is given, the figure is not shown or saved by + `boxcox_normplot`; ``plt.show()`` or ``plt.savefig('figname.png')`` + should be used after calling `probplot`. + + .. versionadded:: 1.2.0 + + Examples + -------- + >>> from scipy import stats + >>> import matplotlib.pyplot as plt + + Generate some non-normally distributed data, and create a Yeo-Johnson plot: + + >>> x = stats.loggamma.rvs(5, size=500) + 5 + >>> fig = plt.figure() + >>> ax = fig.add_subplot(111) + >>> prob = stats.yeojohnson_normplot(x, -20, 20, plot=ax) + + Determine and plot the optimal ``lmbda`` to transform ``x`` and plot it in + the same plot: + + >>> _, maxlog = stats.yeojohnson(x) + >>> ax.axvline(maxlog, color='r') + + >>> plt.show() + + """ + return _normplot('yeojohnson', x, la, lb, plot, N) + + +ShapiroResult = namedtuple('ShapiroResult', ('statistic', 'pvalue')) + + +@xp_capabilities() +@_axis_nan_policy_factory(ShapiroResult, n_samples=1, too_small=2, default_axis=None) +def shapiro(x, *, axis=None): + r"""Perform the Shapiro-Wilk test for normality. + + The Shapiro-Wilk test tests the null hypothesis that the + data was drawn from a normal distribution. + + Parameters + ---------- + x : array_like + Array of sample data. Must contain at least three observations. + axis : int or tuple of ints, default: 0 + If an int or tuple of ints, the axis or axes of the input along which + to compute the statistic. The statistic of each axis-slice (e.g. row) + of the input will appear in a corresponding element of the output. + If ``None``, the input will be raveled before computing the statistic. + + Returns + ------- + statistic : float + The test statistic. + p-value : float + The p-value for the hypothesis test. + + See Also + -------- + anderson : The Anderson-Darling test for normality + kstest : The Kolmogorov-Smirnov test for goodness of fit. + :ref:`hypothesis_shapiro` : Extended example + + Notes + ----- + The algorithm used is described in [4]_, but censoring parameters as + described are not implemented. For N > 5000 the W test statistic is + accurate, but the p-value may not be. + + References + ---------- + .. [1] https://www.itl.nist.gov/div898/handbook/prc/section2/prc213.htm + :doi:`10.18434/M32189` + .. [2] Shapiro, S. S. & Wilk, M.B, "An analysis of variance test for + normality (complete samples)", Biometrika, 1965, Vol. 52, + pp. 591-611, :doi:`10.2307/2333709` + .. [3] Razali, N. M. & Wah, Y. B., "Power comparisons of Shapiro-Wilk, + Kolmogorov-Smirnov, Lilliefors and Anderson-Darling tests", Journal + of Statistical Modeling and Analytics, 2011, Vol. 2, pp. 21-33. + .. [4] Royston, P. "A toolkit for testing for non-normality in complete and + censored samples." Journal of the Royal Statistical Society: Series D + (The Statistician) 42.1 (1993): 37-43. + + Examples + -------- + + >>> import numpy as np + >>> from scipy import stats + >>> rng = np.random.default_rng() + >>> x = stats.norm.rvs(loc=5, scale=3, size=100, random_state=rng) + >>> shapiro_test = stats.shapiro(x) + >>> shapiro_test + ShapiroResult(statistic=0.9813305735588074, pvalue=0.16855233907699585) + >>> shapiro_test.statistic + 0.9813305735588074 + >>> shapiro_test.pvalue + 0.16855233907699585 + + For a more detailed example, see :ref:`hypothesis_shapiro`. + """ + # `x` is an array and axis=-1 due to _axis_nan_policy decorator + xp = array_namespace(x) + + N = x.shape[-1] + if N < 3: + raise ValueError("Data must be at least length 3.") + + y = xp.sort(x, axis=-1) + y -= x[..., N//2:N//2+1] # subtract the median (or a nearby value); see gh-15777 + + y = xp_promote(y, force_floating=True, xp=xp) + w, pw = _swilk(y, xp=xp) + if N > 5000: + warnings.warn("scipy.stats.shapiro: For N > 5000, computed p-value " + f"may not be accurate. Current N is {N}.", + stacklevel=2) + + # `w` and `pw` are always Python floats, which are double precision. + # We want to ensure that they are NumPy floats, so until dtypes are + # respected, we can explicitly convert each to float64 (faster than + # `np.array([w, pw])`). + return ShapiroResult(w[()], pw[()]) + + +def _swilk_w(y, a, *, xp): + # calculate Shapiro-Wilk statistic given sorted sample and weights + # Follows [4] Section 2.1 + num = xp.vecdot(a, y, axis=-1) ** 2 + y_ = _demean(y, mean=xp.mean(y, axis=-1, keepdims=True), axis=-1, xp=xp) + den = xp.vecdot(y_, y_, axis=-1) + return num / den + + +def _swilk(y, *, xp): + # calculate Shapiro-Wilk statistic and p-value from sorted sample + n = y.shape[-1] + + if n == 3: + # [2] Table 5 gives the first four digits + c = math.sqrt(2) / 2 + a = xp.asarray([-c, 0, c]) + # [2] Corollary 4; discussed in https://github.com/scipy/scipy/issues/18322 + W = xp.clip(_swilk_w(y, a, xp=xp), 0.75, 1.) + pvalue = xp.clip(1. - 6/np.pi * xp.acos(xp.sqrt(W)), 0., 1.) + return W, pvalue + + # Follows [4] section 2.2 + # could calculate half the coefficients and get the rest by antisymmetry + i = xp.arange(1, n + 1, dtype=y.dtype) + m = special.ndtri((i - 3 / 8) / (n + 1 / 4)) + u = n**(-0.5) + mTm = xp.vecdot(m, m) + c = mTm**(-0.5) * m + mnm1, mn = m[..., -2], m[..., -1] + cnm1, cn = c[..., -2], c[..., -1] + an = (cn + 0.221157*u - 0.147981*u**2 + - 2.071190*u**3 + 4.434685*u**4 - 2.706056*u**5) + anm1 = (cnm1 + 0.042981*u - 0.293762*u**2 + - 1.752461*u**3 + 5.682633*u**4 - 3.582633*u**5) + phi = ((mTm - 2*mn**2) / (1 - 2*an**2) if n <= 5 + else (mTm - 2*mn**2 - 2*mnm1**2) / (1 - 2*an**2 - 2*anm1**2)) + a = phi**(-0.5) * m + if n > 5: + a = xpx.at(a)[..., -2].set(anm1) + a = xpx.at(a)[..., 1].set(-anm1) + a = xpx.at(a)[..., -1].set(an) + a = xpx.at(a)[..., -0].set(-an) + W = _swilk_w(y, a, xp=xp) + + # Follows [4] Table 1 + if n <= 11: + u = n + gamma = -2.273 + 0.459*u + mu = 0.5440 - 0.39978*u + 0.025054*u**2 - 0.0006714*u**3 + sigma = math.exp(1.3822 - 0.77857*u + 0.062767*u**2 - 0.0020322*u**3) + gW = -xp.log(gamma - xp.log(1 - W)) + else: + u = math.log(n) + mu = -1.5861 - 0.31082*u - 0.083751*u**2 + 0.0038915*u**3 + sigma = math.exp(-0.4803 - 0.082676*u + 0.0030302*u**2) + gW = xp.log(1 - W) + + z = (gW - mu) / sigma + pvalue = special.ndtr(-z) + return W, pvalue + + +# Values from [8] +_Avals_norm = array([0.561, 0.631, 0.752, 0.873, 1.035]) +_Avals_expon = array([0.916, 1.062, 1.321, 1.591, 1.959]) +# From Stephens, M A, "Goodness of Fit for the Extreme Value Distribution", +# Biometrika, Vol. 64, Issue 3, Dec. 1977, pp 583-588. +_Avals_gumbel = array([0.474, 0.637, 0.757, 0.877, 1.038]) +# From Stephens, M A, "Tests of Fit for the Logistic Distribution Based +# on the Empirical Distribution Function.", Biometrika, +# Vol. 66, Issue 3, Dec. 1979, pp 591-595. +_Avals_logistic = array([0.426, 0.563, 0.660, 0.769, 0.906, 1.010]) +# From Richard A. Lockhart and Michael A. Stephens "Estimation and Tests of +# Fit for the Three-Parameter Weibull Distribution" +# Journal of the Royal Statistical Society.Series B(Methodological) +# Vol. 56, No. 3 (1994), pp. 491-500, table 1. Keys are c*100 +_Avals_weibull = [[0.292, 0.395, 0.467, 0.522, 0.617, 0.711, 0.836, 0.931], + [0.295, 0.399, 0.471, 0.527, 0.623, 0.719, 0.845, 0.941], + [0.298, 0.403, 0.476, 0.534, 0.631, 0.728, 0.856, 0.954], + [0.301, 0.408, 0.483, 0.541, 0.640, 0.738, 0.869, 0.969], + [0.305, 0.414, 0.490, 0.549, 0.650, 0.751, 0.885, 0.986], + [0.309, 0.421, 0.498, 0.559, 0.662, 0.765, 0.902, 1.007], + [0.314, 0.429, 0.508, 0.570, 0.676, 0.782, 0.923, 1.030], + [0.320, 0.438, 0.519, 0.583, 0.692, 0.802, 0.947, 1.057], + [0.327, 0.448, 0.532, 0.598, 0.711, 0.824, 0.974, 1.089], + [0.334, 0.469, 0.547, 0.615, 0.732, 0.850, 1.006, 1.125], + [0.342, 0.472, 0.563, 0.636, 0.757, 0.879, 1.043, 1.167]] +_Avals_weibull = np.array(_Avals_weibull) +_cvals_weibull = np.linspace(0, 0.5, 11) +_get_As_weibull = interpolate.interp1d(_cvals_weibull, _Avals_weibull.T, + kind='linear', bounds_error=False, + fill_value=_Avals_weibull[-1]) + + +def _weibull_fit_check(params, x): + # Refine the fit returned by `weibull_min.fit` to ensure that the first + # order necessary conditions are satisfied. If not, raise an error. + # Here, use `m` for the shape parameter to be consistent with [7] + # and avoid confusion with `c` as defined in [7]. + n = len(x) + m, u, s = params + + def dnllf_dm(m, u): + # Partial w.r.t. shape w/ optimal scale. See [7] Equation 5. + xu = x-u + return (1/m - (xu**m*np.log(xu)).sum()/(xu**m).sum() + + np.log(xu).sum()/n) + + def dnllf_du(m, u): + # Partial w.r.t. loc w/ optimal scale. See [7] Equation 6. + xu = x-u + return (m-1)/m*(xu**-1).sum() - n*(xu**(m-1)).sum()/(xu**m).sum() + + def get_scale(m, u): + # Partial w.r.t. scale solved in terms of shape and location. + # See [7] Equation 7. + return ((x-u)**m/n).sum()**(1/m) + + def dnllf(params): + # Partial derivatives of the NLLF w.r.t. parameters, i.e. + # first order necessary conditions for MLE fit. + return [dnllf_dm(*params), dnllf_du(*params)] + + suggestion = ("Maximum likelihood estimation is known to be challenging " + "for the three-parameter Weibull distribution. Consider " + "performing a custom goodness-of-fit test using " + "`scipy.stats.monte_carlo_test`.") + + if np.allclose(u, np.min(x)) or m < 1: + # The critical values provided by [7] don't seem to control the + # Type I error rate in this case. Error out. + message = ("Maximum likelihood estimation has converged to " + "a solution in which the location is equal to the minimum " + "of the data, the shape parameter is less than 2, or both. " + "The table of critical values in [7] does not " + "include this case. " + suggestion) + raise ValueError(message) + + try: + # Refine the MLE / verify that first-order necessary conditions are + # satisfied. If so, the critical values provided in [7] seem reliable. + with np.errstate(over='raise', invalid='raise'): + res = optimize.root(dnllf, params[:-1]) + + message = ("Solution of MLE first-order conditions failed: " + f"{res.message}. `anderson` cannot continue. " + suggestion) + if not res.success: + raise ValueError(message) + + except (FloatingPointError, ValueError) as e: + message = ("An error occurred while fitting the Weibull distribution " + "to the data, so `anderson` cannot continue. " + suggestion) + raise ValueError(message) from e + + m, u = res.x + s = get_scale(m, u) + return m, u, s + + +AndersonResult = _make_tuple_bunch('AndersonResult', + ['statistic', 'critical_values', + 'significance_level'], ['fit_result']) + + +_anderson_warning_message = ( +"""As of SciPy 1.17, users must choose a p-value calculation method by providing the +`method` parameter. `method='interpolate'` interpolates the p-value from pre-calculated +tables; `method` may also be an instance of `MonteCarloMethod` to approximate the +p-value via Monte Carlo simulation. When `method` is specified, the result object will +include a `pvalue` attribute and not attributes `critical_value`, `significance_level`, +or `fit_result`. Beginning in 1.19.0, these other attributes will no longer be +available, and a p-value will always be computed according to one of the available +`method` options.""".replace('\n', ' ')) + + +@xp_capabilities(np_only=True) +def anderson(x, dist='norm', *, method=None): + """Anderson-Darling test for data coming from a particular distribution. + + The Anderson-Darling test tests the null hypothesis that a sample is + drawn from a population that follows a particular distribution. + For the Anderson-Darling test, the critical values depend on + which distribution is being tested against. This function works + for normal, exponential, logistic, weibull_min, or Gumbel (Extreme Value + Type I) distributions. + + Parameters + ---------- + x : array_like + Array of sample data. + dist : {'norm', 'expon', 'logistic', 'gumbel', 'gumbel_l', 'gumbel_r', 'extreme1', 'weibull_min'}, optional + The type of distribution to test against. The default is 'norm'. + The names 'extreme1', 'gumbel_l' and 'gumbel' are synonyms for the + same distribution. + method : str or instance of `MonteCarloMethod` + Defines the method used to compute the p-value. + If `method` is ``"interpolated"``, the p-value is interpolated from + pre-calculated tables. + If `method` is an instance of `MonteCarloMethod`, the p-value is computed using + `scipy.stats.monte_carlo_test` with the provided configuration options and other + appropriate settings. + + .. versionadded:: 1.17.0 + If `method` is not specified, `anderson` will emit a ``FutureWarning`` + specifying that the user must opt into a p-value calculation method. + When `method` is specified, the object returned will include a ``pvalue`` + attribute, but no ``critical_value``, ``significance_level``, or + ``fit_result`` attributes. Beginning in 1.19.0, these other attributes will + no longer be available, and a p-value will always be computed according to + one of the available `method` options. + + Returns + ------- + result : AndersonResult + If `method` is provided, this is an object with the following attributes: + + statistic : float + The Anderson-Darling test statistic. + pvalue: float + The p-value corresponding with the test statistic, calculated according to + the specified `method`. + + If `method` is unspecified, this is an object with the following attributes: + + statistic : float + The Anderson-Darling test statistic. + critical_values : list + The critical values for this distribution. + significance_level : list + The significance levels for the corresponding critical values + in percents. The function returns critical values for a + differing set of significance levels depending on the + distribution that is being tested against. + fit_result : `~scipy.stats._result_classes.FitResult` + An object containing the results of fitting the distribution to + the data. + + .. deprecated:: 1.17.0 + The tuple-unpacking behavior of the return object and attributes + ``critical_values``, ``significance_level``, and ``fit_result`` are + deprecated. Beginning in SciPy 1.19.0, these features will no longer be + available, and the object returned will have attributes ``statistic`` and + ``pvalue``. + + See Also + -------- + kstest : The Kolmogorov-Smirnov test for goodness-of-fit. + + Notes + ----- + Critical values provided when `method` is unspecified are for the following + significance levels: + + normal/exponential + 15%, 10%, 5%, 2.5%, 1% + logistic + 25%, 10%, 5%, 2.5%, 1%, 0.5% + gumbel_l / gumbel_r + 25%, 10%, 5%, 2.5%, 1% + weibull_min + 50%, 25%, 15%, 10%, 5%, 2.5%, 1%, 0.5% + + If the returned statistic is larger than these critical values then + for the corresponding significance level, the null hypothesis that + the data come from the chosen distribution can be rejected. + The returned statistic is referred to as 'A2' in the references. + + For `weibull_min`, maximum likelihood estimation is known to be + challenging. If the test returns successfully, then the first order + conditions for a maximum likelihood estimate have been verified and + the critical values correspond relatively well to the significance levels, + provided that the sample is sufficiently large (>10 observations [7]). + However, for some data - especially data with no left tail - `anderson` + is likely to result in an error message. In this case, consider + performing a custom goodness of fit test using + `scipy.stats.monte_carlo_test`. + + References + ---------- + .. [1] https://www.itl.nist.gov/div898/handbook/prc/section2/prc213.htm + .. [2] Stephens, M. A. (1974). EDF Statistics for Goodness of Fit and + Some Comparisons, Journal of the American Statistical Association, + Vol. 69, pp. 730-737. + .. [3] Stephens, M. A. (1976). Asymptotic Results for Goodness-of-Fit + Statistics with Unknown Parameters, Annals of Statistics, Vol. 4, + pp. 357-369. + .. [4] Stephens, M. A. (1977). Goodness of Fit for the Extreme Value + Distribution, Biometrika, Vol. 64, pp. 583-588. + .. [5] Stephens, M. A. (1977). Goodness of Fit with Special Reference + to Tests for Exponentiality , Technical Report No. 262, + Department of Statistics, Stanford University, Stanford, CA. + .. [6] Stephens, M. A. (1979). Tests of Fit for the Logistic Distribution + Based on the Empirical Distribution Function, Biometrika, Vol. 66, + pp. 591-595. + .. [7] Richard A. Lockhart and Michael A. Stephens "Estimation and Tests of + Fit for the Three-Parameter Weibull Distribution" + Journal of the Royal Statistical Society.Series B(Methodological) + Vol. 56, No. 3 (1994), pp. 491-500, Table 0. + .. [8] D'Agostino, Ralph B. (1986). "Tests for the Normal Distribution". + In: Goodness-of-Fit Techniques. Ed. by Ralph B. D'Agostino and + Michael A. Stephens. New York: Marcel Dekker, pp. 122-141. ISBN: + 0-8247-7487-6. + + Examples + -------- + Test the null hypothesis that a random sample was drawn from a normal + distribution (with unspecified mean and standard deviation). + + >>> import numpy as np + >>> from scipy.stats import anderson + >>> rng = np.random.default_rng(9781234521) + >>> data = rng.random(size=35) + >>> res = anderson(data, dist='norm', method='interpolate') + >>> res.statistic + np.float64(0.9887620209957291) + >>> res.pvalue + np.float64(0.012111200538380142) + + The p-value is approximately 0.012,, so the null hypothesis may be rejected + at a significance level of 2.5%, but not at a significance level of 1%. + + """ # numpy/numpydoc#87 # noqa: E501 + dist = dist.lower() + if dist in {'extreme1', 'gumbel'}: + dist = 'gumbel_l' + dists = {'norm', 'expon', 'gumbel_l', + 'gumbel_r', 'logistic', 'weibull_min'} + + if dist not in dists: + raise ValueError(f"Invalid distribution; dist must be in {dists}.") + y = sort(x) + xbar = np.mean(x, axis=0) + N = len(y) + if dist == 'norm': + s = np.std(x, ddof=1, axis=0) + w = (y - xbar) / s + fit_params = xbar, s + logcdf = distributions.norm.logcdf(w) + logsf = distributions.norm.logsf(w) + sig = array([15, 10, 5, 2.5, 1]) + critical = around(_Avals_norm / (1.0 + 0.75/N + 2.25/N/N), 3) + elif dist == 'expon': + w = y / xbar + fit_params = 0, xbar + logcdf = distributions.expon.logcdf(w) + logsf = distributions.expon.logsf(w) + sig = array([15, 10, 5, 2.5, 1]) + critical = around(_Avals_expon / (1.0 + 0.6/N), 3) + elif dist == 'logistic': + def rootfunc(ab, xj, N): + a, b = ab + tmp = (xj - a) / b + tmp2 = exp(tmp) + val = [np.sum(1.0/(1+tmp2), axis=0) - 0.5*N, + np.sum(tmp*(1.0-tmp2)/(1+tmp2), axis=0) + N] + return array(val) + + sol0 = array([xbar, np.std(x, ddof=1, axis=0)]) + sol = optimize.fsolve(rootfunc, sol0, args=(x, N), xtol=1e-5) + w = (y - sol[0]) / sol[1] + fit_params = sol + logcdf = distributions.logistic.logcdf(w) + logsf = distributions.logistic.logsf(w) + sig = array([25, 10, 5, 2.5, 1, 0.5]) + critical = around(_Avals_logistic / (1.0 + 0.25/N), 3) + elif dist == 'gumbel_r': + xbar, s = distributions.gumbel_r.fit(x) + w = (y - xbar) / s + fit_params = xbar, s + logcdf = distributions.gumbel_r.logcdf(w) + logsf = distributions.gumbel_r.logsf(w) + sig = array([25, 10, 5, 2.5, 1]) + critical = around(_Avals_gumbel / (1.0 + 0.2/sqrt(N)), 3) + elif dist == 'gumbel_l': + xbar, s = distributions.gumbel_l.fit(x) + w = (y - xbar) / s + fit_params = xbar, s + logcdf = distributions.gumbel_l.logcdf(w) + logsf = distributions.gumbel_l.logsf(w) + sig = array([25, 10, 5, 2.5, 1]) + critical = around(_Avals_gumbel / (1.0 + 0.2/sqrt(N)), 3) + elif dist == 'weibull_min': + message = ("Critical values of the test statistic are given for the " + "asymptotic distribution. These may not be accurate for " + "samples with fewer than 10 observations. Consider using " + "`scipy.stats.monte_carlo_test`.") + if N < 10: + warnings.warn(message, stacklevel=2) + # [7] writes our 'c' as 'm', and they write `c = 1/m`. Use their names. + m, loc, scale = distributions.weibull_min.fit(y) + m, loc, scale = _weibull_fit_check((m, loc, scale), y) + fit_params = m, loc, scale + logcdf = stats.weibull_min(*fit_params).logcdf(y) + logsf = stats.weibull_min(*fit_params).logsf(y) + c = 1 / m # m and c are as used in [7] + sig = array([0.5, 0.75, 0.85, 0.9, 0.95, 0.975, 0.99, 0.995]) + critical = _get_As_weibull(c) + # Goodness-of-fit tests should only be used to provide evidence + # _against_ the null hypothesis. Be conservative and round up. + critical = np.round(critical + 0.0005, decimals=3) + + i = arange(1, N + 1) + A2 = -N - np.sum((2*i - 1.0) / N * (logcdf + logsf[::-1]), axis=0) + + # FitResult initializer expects an optimize result, so let's work with it + message = '`anderson` successfully fit the distribution to the data.' + res = optimize.OptimizeResult(success=True, message=message) + res.x = np.array(fit_params) + fit_result = FitResult(getattr(distributions, dist), y, + discrete=False, res=res) + + if method is None: + warnings.warn(_anderson_warning_message, FutureWarning, stacklevel=2) + return AndersonResult(A2, critical, sig, fit_result=fit_result) + + if method == 'interpolate': + sig = 1 - sig if dist == 'weibull_min' else sig / 100 + pvalue = np.interp(A2, critical, sig) + elif isinstance(method, stats.MonteCarloMethod): + pvalue = _anderson_simulate_pvalue(x, dist, method) + else: + message = ("`method` must be either 'interpolate' or " + "an instance of `MonteCarloMethod`.") + raise ValueError(message) + return SignificanceResult(statistic=A2, pvalue=pvalue) + + +def _anderson_simulate_pvalue(x, dist, method): + message = ("The `___` attribute of a `MonteCarloMethod` object passed as the " + "`method` parameter of `scipy.stats.anderson` is ignored.") + + method = method._asdict() + if method.pop('rvs', False): + warnings.warn(message.replace('___', 'rvs'), UserWarning, stacklevel=3) + if method.pop('batch', False): + warnings.warn(message.replace('___', 'batch'), UserWarning, stacklevel=3) + method['n_mc_samples'] = method.pop('n_resamples') + + kwargs= {'known_params': {'loc': 0}} if dist == 'expon' else {} + dist = getattr(stats, dist) + res = stats.goodness_of_fit(dist, x, statistic='ad', **kwargs, **method) + return res.pvalue + + +def _anderson_ksamp_continuous(samples, Z, Zstar, k, n, N): + """Compute A2akN equation 3 of Scholz & Stephens. + + Parameters + ---------- + samples : sequence of 1-D array_like + Array of sample arrays. + Z : array_like + Sorted array of all observations. + Zstar : array_like + Sorted array of unique observations. Unused. + k : int + Number of samples. + n : array_like + Number of observations in each sample. + N : int + Total number of observations. + + Returns + ------- + A2KN : float + The A2KN statistics of Scholz and Stephens 1987. + + """ + A2kN = 0. + + j = np.arange(1, N) + for i in arange(0, k): + s = np.sort(samples[i]) + Mij = s.searchsorted(Z[:-1], side='right') + inner = (N*Mij - j*n[i])**2 / (j * (N - j)) + A2kN += inner.sum() / n[i] + return A2kN / N + + +def _anderson_ksamp_midrank(samples, Z, Zstar, k, n, N): + """Compute A2akN equation 7 of Scholz and Stephens. + + Parameters + ---------- + samples : sequence of 1-D array_like + Array of sample arrays. + Z : array_like + Sorted array of all observations. + Zstar : array_like + Sorted array of unique observations. + k : int + Number of samples. + n : array_like + Number of observations in each sample. + N : int + Total number of observations. + + Returns + ------- + A2aKN : float + The A2aKN statistics of Scholz and Stephens 1987. + + """ + A2akN = 0. + Z_ssorted_left = Z.searchsorted(Zstar, 'left') + if N == Zstar.size: + lj = 1. + else: + lj = Z.searchsorted(Zstar, 'right') - Z_ssorted_left + Bj = Z_ssorted_left + lj / 2. + for i in arange(0, k): + s = np.sort(samples[i]) + s_ssorted_right = s.searchsorted(Zstar, side='right') + Mij = s_ssorted_right.astype(float) + fij = s_ssorted_right - s.searchsorted(Zstar, 'left') + Mij -= fij / 2. + inner = lj / float(N) * (N*Mij - Bj*n[i])**2 / (Bj*(N - Bj) - N*lj/4.) + A2akN += inner.sum() / n[i] + A2akN *= (N - 1.) / N + return A2akN + + +def _anderson_ksamp_right(samples, Z, Zstar, k, n, N): + """Compute A2akN equation 6 of Scholz & Stephens. + + Parameters + ---------- + samples : sequence of 1-D array_like + Array of sample arrays. + Z : array_like + Sorted array of all observations. + Zstar : array_like + Sorted array of unique observations. + k : int + Number of samples. + n : array_like + Number of observations in each sample. + N : int + Total number of observations. + + Returns + ------- + A2KN : float + The A2KN statistics of Scholz and Stephens 1987. + + """ + A2kN = 0. + lj = Z.searchsorted(Zstar[:-1], 'right') - Z.searchsorted(Zstar[:-1], + 'left') + Bj = lj.cumsum() + for i in arange(0, k): + s = np.sort(samples[i]) + Mij = s.searchsorted(Zstar[:-1], side='right') + inner = lj / float(N) * (N * Mij - Bj * n[i])**2 / (Bj * (N - Bj)) + A2kN += inner.sum() / n[i] + return A2kN + + +Anderson_ksampResult = _make_tuple_bunch( + 'Anderson_ksampResult', + ['statistic', 'critical_values', 'pvalue'], [] +) + + +@xp_capabilities(np_only=True) +def anderson_ksamp(samples, midrank=_NoValue, *, variant=_NoValue, method=None): + """The Anderson-Darling test for k-samples. + + The k-sample Anderson-Darling test is a modification of the + one-sample Anderson-Darling test. It tests the null hypothesis + that k-samples are drawn from the same population without having + to specify the distribution function of that population. The + critical values depend on the number of samples. + + Parameters + ---------- + samples : sequence of 1-D array_like + Array of sample data in arrays. + midrank : bool, optional + Variant of Anderson-Darling test which is computed. Default + (True) is the midrank test applicable to continuous and + discrete populations. If False, the right side empirical + distribution is used. + + .. deprecated:: 1.17.0 + Use parameter `variant` instead. + variant : {'midrank', 'right', 'continuous'} + Variant of Anderson-Darling test to be computed. ``'midrank'`` is applicable + to both continuous and discrete populations. ``'discrete'`` and ``'continuous'`` + perform alternative versions of the test for discrete and continuous + populations, respectively. + When `variant` is specified, the return object will not be unpackable as a + tuple, and only attributes ``statistic`` and ``pvalue`` will be present. + method : PermutationMethod, optional + Defines the method used to compute the p-value. If `method` is an + instance of `PermutationMethod`, the p-value is computed using + `scipy.stats.permutation_test` with the provided configuration options + and other appropriate settings. Otherwise, the p-value is interpolated + from tabulated values. + + Returns + ------- + res : Anderson_ksampResult + An object containing attributes: + + statistic : float + Normalized k-sample Anderson-Darling test statistic. + critical_values : array + The critical values for significance levels 25%, 10%, 5%, 2.5%, 1%, + 0.5%, 0.1%. + + .. deprecated:: 1.17.0 + Present only when `variant` is unspecified. + + pvalue : float + The approximate p-value of the test. If `method` is not + provided, the value is floored / capped at 0.1% / 25%. + + Raises + ------ + ValueError + If fewer than 2 samples are provided, a sample is empty, or no + distinct observations are in the samples. + + See Also + -------- + ks_2samp : 2 sample Kolmogorov-Smirnov test + anderson : 1 sample Anderson-Darling test + + Notes + ----- + [1]_ defines three versions of the k-sample Anderson-Darling test: + one for continuous distributions and two for discrete + distributions, in which ties between samples may occur. The + default of this routine is to compute the version based on the + midrank empirical distribution function. This test is applicable + to continuous and discrete data. If `variant` is set to ``'discrete'``, the + right side empirical distribution is used for a test for discrete + data; if `variant` is ``'continuous'``, the same test statistic and p-value are + computed for data with no ties, but with less computation. According to [1]_, + the two discrete test statistics differ only slightly if a few collisions due + to round-off errors occur in the test not adjusted for ties between samples. + + The critical values corresponding to the significance levels from 0.01 + to 0.25 are taken from [1]_. p-values are floored / capped + at 0.1% / 25%. Since the range of critical values might be extended in + future releases, it is recommended not to test ``p == 0.25``, but rather + ``p >= 0.25`` (analogously for the lower bound). + + .. versionadded:: 0.14.0 + + References + ---------- + .. [1] Scholz, F. W and Stephens, M. A. (1987), K-Sample + Anderson-Darling Tests, Journal of the American Statistical + Association, Vol. 82, pp. 918-924. + + Examples + -------- + >>> import numpy as np + >>> from scipy import stats + >>> rng = np.random.default_rng(44925884305279435) + >>> res = stats.anderson_ksamp([rng.normal(size=50), rng.normal(loc=0.5, size=30)], + ... variant='midrank') + >>> res.statistic, res.pvalue + (3.4444310693448936, 0.013106682406720973) + + The null hypothesis that the two random samples come from the same + distribution can be rejected at the 5% level because the returned + p-value is less than 0.05, but not at the 1% level. + + >>> samples = [rng.normal(size=50), rng.normal(size=30), + ... rng.normal(size=20)] + >>> res = stats.anderson_ksamp(samples, variant='continuous') + >>> res.statistic, res.pvalue + (-0.6309662273193832, 0.25) + + As we might expect, the null hypothesis cannot be rejected here for three samples + from an identical distribution. The reported p-value (25%) has been capped at the + maximum value for which pre-computed p-values are available. + + In such cases where the p-value is capped or when sample sizes are + small, a permutation test may be more accurate. + + >>> method = stats.PermutationMethod(n_resamples=9999, random_state=rng) + >>> res = stats.anderson_ksamp(samples, variant='continuous', method=method) + >>> res.pvalue + 0.699 + + """ + k = len(samples) + if (k < 2): + raise ValueError("anderson_ksamp needs at least two samples") + + samples = list(map(np.asarray, samples)) + Z = np.sort(np.hstack(samples)) + N = Z.size + Zstar = np.unique(Z) + if Zstar.size < 2: + raise ValueError("anderson_ksamp needs more than one distinct " + "observation") + + n = np.array([sample.size for sample in samples]) + if np.any(n == 0): + raise ValueError("anderson_ksamp encountered sample without " + "observations") + + if variant == _NoValue or midrank != _NoValue: + message = ("Parameter `variant` has been introduced to replace `midrank`; " + "`midrank` will be removed in SciPy 1.19.0. Specify `variant` to " + "silence this warning. Note that the returned object will no longer " + "be unpackable as a tuple, and `critical_values` will be omitted.") + warnings.warn(message, category=UserWarning, stacklevel=2) + + return_critical_values = False + if variant == _NoValue: + return_critical_values = True + variant = 'midrank' if midrank else 'right' + + if variant == 'midrank': + A2kN_fun = _anderson_ksamp_midrank + elif variant == 'right': + A2kN_fun = _anderson_ksamp_right + elif variant == 'continuous': + A2kN_fun = _anderson_ksamp_continuous + else: + message = "`variant` must be one of 'midrank', 'right', or 'continuous'." + raise ValueError(message) + + A2kN = A2kN_fun(samples, Z, Zstar, k, n, N) + + def statistic(*samples): + return A2kN_fun(samples, Z, Zstar, k, n, N) + + if method is not None: + res = stats.permutation_test(samples, statistic, **method._asdict(), + alternative='greater') + + H = (1. / n).sum() + hs_cs = (1. / arange(N - 1, 1, -1)).cumsum() + h = hs_cs[-1] + 1 + g = (hs_cs / arange(2, N)).sum() + + a = (4*g - 6) * (k - 1) + (10 - 6*g)*H + b = (2*g - 4)*k**2 + 8*h*k + (2*g - 14*h - 4)*H - 8*h + 4*g - 6 + c = (6*h + 2*g - 2)*k**2 + (4*h - 4*g + 6)*k + (2*h - 6)*H + 4*h + d = (2*h + 6)*k**2 - 4*h*k + sigmasq = (a*N**3 + b*N**2 + c*N + d) / ((N - 1.) * (N - 2.) * (N - 3.)) + m = k - 1 + A2 = (A2kN - m) / math.sqrt(sigmasq) + + # The b_i values are the interpolation coefficients from Table 2 + # of Scholz and Stephens 1987 + b0 = np.array([0.675, 1.281, 1.645, 1.96, 2.326, 2.573, 3.085]) + b1 = np.array([-0.245, 0.25, 0.678, 1.149, 1.822, 2.364, 3.615]) + b2 = np.array([-0.105, -0.305, -0.362, -0.391, -0.396, -0.345, -0.154]) + critical = b0 + b1 / math.sqrt(m) + b2 / m + + sig = np.array([0.25, 0.1, 0.05, 0.025, 0.01, 0.005, 0.001]) + + if A2 < critical.min() and method is None: + p = sig.max() + msg = (f"p-value capped: true value larger than {p}. Consider " + "specifying `method` " + "(e.g. `method=stats.PermutationMethod()`.)") + warnings.warn(msg, stacklevel=2) + elif A2 > critical.max() and method is None: + p = sig.min() + msg = (f"p-value floored: true value smaller than {p}. Consider " + "specifying `method` " + "(e.g. `method=stats.PermutationMethod()`.)") + warnings.warn(msg, stacklevel=2) + elif method is None: + # interpolation of probit of significance level + pf = np.polyfit(critical, log(sig), 2) + p = math.exp(np.polyval(pf, A2)) + else: + p = res.pvalue if method is not None else p + + if return_critical_values: + # create result object with alias for backward compatibility + res = Anderson_ksampResult(A2, critical, p) + res.significance_level = p + else: + res = SignificanceResult(statistic=A2, pvalue=p) + + return res + + + +AnsariResult = namedtuple('AnsariResult', ('statistic', 'pvalue')) + + +class _ABW: + """Distribution of Ansari-Bradley W-statistic under the null hypothesis.""" + # TODO: calculate exact distribution considering ties + # We could avoid summing over more than half the frequencies, + # but initially it doesn't seem worth the extra complexity + + def __init__(self): + """Minimal initializer.""" + self.m = None + self.n = None + self.astart = None + self.total = None + self.freqs = None + + def _recalc(self, n, m): + """When necessary, recalculate exact distribution.""" + if n != self.n or m != self.m: + self.n, self.m = n, m + # distribution is NOT symmetric when m + n is odd + # n is len(x), m is len(y), and ratio of scales is defined x/y + astart, a1, _ = gscale(n, m) + self.astart = astart # minimum value of statistic + # Exact distribution of test statistic under null hypothesis + # expressed as frequencies/counts/integers to maintain precision. + # Stored as floats to avoid overflow of sums. + self.freqs = a1.astype(np.float64) + self.total = self.freqs.sum() # could calculate from m and n + # probability mass is self.freqs / self.total; + + def pmf(self, k, n, m): + """Probability mass function.""" + self._recalc(n, m) + # The convention here is that PMF at k = 12.5 is the same as at k = 12, + # -> use `floor` in case of ties. + ind = np.floor(k - self.astart).astype(int) + return self.freqs[ind] / self.total + + def cdf(self, k, n, m): + """Cumulative distribution function.""" + self._recalc(n, m) + # Null distribution derived without considering ties is + # approximate. Round down to avoid Type I error. + ind = np.ceil(k - self.astart).astype(int) + return self.freqs[:ind+1].sum() / self.total + + def sf(self, k, n, m): + """Survival function.""" + self._recalc(n, m) + # Null distribution derived without considering ties is + # approximate. Round down to avoid Type I error. + ind = np.floor(k - self.astart).astype(int) + return self.freqs[ind:].sum() / self.total + + +# Maintain state for faster repeat calls to ansari w/ method='exact' +# _ABW() is calculated once per thread and stored as an attribute on +# this thread-local variable inside ansari(). +_abw_state = threading.local() + + +@xp_capabilities(cpu_only=True, skip_backends=[('dask.array', 'no rankdata')]) +@_axis_nan_policy_factory(AnsariResult, n_samples=2) +def ansari(x, y, alternative='two-sided', *, axis=0, method='auto'): + """Perform the Ansari-Bradley test for equal scale parameters. + + The Ansari-Bradley test ([1]_, [2]_) is a non-parametric test + for the equality of the scale parameter of the distributions + from which two samples were drawn. The null hypothesis states that + the ratio of the scale of the distribution underlying `x` to the scale + of the distribution underlying `y` is 1. + + Parameters + ---------- + x, y : array_like + Arrays of sample data. + alternative : {'two-sided', 'less', 'greater'}, optional + Defines the alternative hypothesis. Default is 'two-sided'. + The following options are available: + + * 'two-sided': the ratio of scales is not equal to 1. + * 'less': the ratio of scales is less than 1. + * 'greater': the ratio of scales is greater than 1. + + .. versionadded:: 1.7.0 + axis : int or tuple of ints, default: 0 + If an int or tuple of ints, the axis or axes of the input along which + to compute the statistic. The statistic of each axis-slice (e.g. row) + of the input will appear in a corresponding element of the output. + If ``None``, the input will be raveled before computing the statistic. + method : {'auto', 'asymptotic', 'exact'} or `PermutationMethod` instance, optional + Selects the method used to calculate the *p*-value. + Default is 'auto'. The following options are available. + + * ``'asymptotic'``: compares the standardized test statistic + against the normal distribution, correcting for ties. + * ``'exact'``: computes the exact *p*-value by comparing the observed + statistic against the exact distribution of the statistic under the + null hypothesis. No correction is made for ties. + * ``'auto'``: chooses ``'exact'`` when the size of both + samples is less than or equal to 55 and there are no ties; + chooses ``'asymptotic'`` otherwise. + * `PermutationMethod` instance. In this case, the p-value + is computed using `permutation_test` with the provided + configuration options and other appropriate settings. + + Returns + ------- + statistic : float + The Ansari-Bradley test statistic. + pvalue : float + The p-value of the hypothesis test. + + See Also + -------- + fligner : A non-parametric test for the equality of k variances + mood : A non-parametric test for the equality of two scale parameters + + Notes + ----- + The p-value given is exact when the sample sizes are both less than + 55 and there are no ties, otherwise a normal approximation for the + p-value is used. + + References + ---------- + .. [1] Ansari, A. R. and Bradley, R. A. (1960) Rank-sum tests for + dispersions, Annals of Mathematical Statistics, 31, 1174-1189. + .. [2] Sprent, Peter and N.C. Smeeton. Applied nonparametric + statistical methods. 3rd ed. Chapman and Hall/CRC. 2001. + Section 5.8.2. + .. [3] Nathaniel E. Helwig "Nonparametric Dispersion and Equality + Tests" at http://users.stat.umn.edu/~helwig/notes/npde-Notes.pdf + + Examples + -------- + >>> import numpy as np + >>> from scipy.stats import ansari + >>> rng = np.random.default_rng() + + For these examples, we'll create three random data sets. The first + two, with sizes 35 and 25, are drawn from a normal distribution with + mean 0 and standard deviation 2. The third data set has size 25 and + is drawn from a normal distribution with standard deviation 1.25. + + >>> x1 = rng.normal(loc=0, scale=2, size=35) + >>> x2 = rng.normal(loc=0, scale=2, size=25) + >>> x3 = rng.normal(loc=0, scale=1.25, size=25) + + First we apply `ansari` to `x1` and `x2`. These samples are drawn + from the same distribution, so we expect the Ansari-Bradley test + should not lead us to conclude that the scales of the distributions + are different. + + >>> ansari(x1, x2) + AnsariResult(statistic=541.0, pvalue=0.9762532927399098) + + With a p-value close to 1, we cannot conclude that there is a + significant difference in the scales (as expected). + + Now apply the test to `x1` and `x3`: + + >>> ansari(x1, x3) + AnsariResult(statistic=425.0, pvalue=0.0003087020407974518) + + The probability of observing such an extreme value of the statistic + under the null hypothesis of equal scales is only 0.03087%. We take this + as evidence against the null hypothesis in favor of the alternative: + the scales of the distributions from which the samples were drawn + are not equal. + + We can use the `alternative` parameter to perform a one-tailed test. + In the above example, the scale of `x1` is greater than `x3` and so + the ratio of scales of `x1` and `x3` is greater than 1. This means + that the p-value when ``alternative='greater'`` should be near 0 and + hence we should be able to reject the null hypothesis: + + >>> ansari(x1, x3, alternative='greater') + AnsariResult(statistic=425.0, pvalue=0.0001543510203987259) + + As we can see, the p-value is indeed quite low. Use of + ``alternative='less'`` should thus yield a large p-value: + + >>> ansari(x1, x3, alternative='less') + AnsariResult(statistic=425.0, pvalue=0.9998643258449039) + + """ + xp = array_namespace(x, y) + dtype = xp_result_type(x, y, force_floating=True, xp=xp) + + if alternative not in {'two-sided', 'greater', 'less'}: + raise ValueError("'alternative' must be 'two-sided'," + " 'greater', or 'less'.") + methods = {'auto', 'asymptotic', 'exact'} + if str(method) not in methods and not isinstance(method, stats.PermutationMethod): + message = (f"`method` must be one of {methods} or " + "an instance of `PermutationMethod`.") + raise ValueError(message) + if is_lazy_array(x) and method == 'auto': + message = ("`method` must be 'exact', 'asymptotic', or an instance of " + "`PermutationMethod` when used with lazy arrays.") + raise ValueError(message) + + if not hasattr(_abw_state, 'a'): + _abw_state.a = _ABW() + + # _axis_nan_policy decorator guarantees that axis=-1 + n = x.shape[-1] + m = y.shape[-1] + if m < 1: # needed by test_axis_nan_policy; not user-facing + raise ValueError("Not enough other observations.") + if n < 1: + raise ValueError("Not enough test observations.") + + N = m + n + xy = xp.concat([x, y], axis=-1) # combine + rank, _, t = _stats_py._rankdata(xy, method='average', return_ties=True) + symrank = xp.minimum(rank, N - rank + 1) + AB = xp.sum(symrank[..., :n], axis=-1) + repeats = xp.any(t > 1) # in theory we could branch for each slice separately + + if method == "auto": + method = 'exact' if ((m < 55) and (n < 55) and not repeats) else 'asymptotic' + + if method == 'exact': + # np.vectorize converts to NumPy here, and we convert back to the result + # type before returning + cdf = np.vectorize(_abw_state.a.cdf, otypes=[np.float64]) + sf = np.vectorize(_abw_state.a.sf, otypes=[np.float64]) + def get_ansari_pvalue(AB): + if alternative == 'two-sided': + pval = 2.0 * np.minimum(cdf(AB, n, m), sf(AB, n, m)) + elif alternative == 'greater': + # AB statistic is _smaller_ when ratio of scales is larger, + # so this is the opposite of the usual calculation + pval = cdf(AB, n, m) + else: + pval = sf(AB, n, m) + return pval + + pval = xpx.lazy_apply(get_ansari_pvalue, AB, shape=AB.shape) + pval = xp.clip(xp.asarray(pval, dtype=dtype), max=1.0) + AB = AB[()] if AB.ndim == 0 else AB + pval = pval[()] if pval.ndim == 0 else pval + return AnsariResult(AB, pval) + + elif isinstance(method, stats.PermutationMethod): + def statistic(x, y, axis): + return ansari(x, y, axis=axis, method="asymptotic").statistic + alternative = dict(less='greater', greater='less').get(alternative, 'two-sided') + res = stats.permutation_test((x, y), statistic, axis=axis, + **method._asdict(), alternative=alternative) + return AnsariResult(AB, res.pvalue) + + mnAB = (n * (N + 1.0) ** 2 / 4.0 / N) if N % 2 else (n * (N + 2.0) / 4.0) + + if is_lazy_array(repeats) or repeats: # adjust variance estimates + # compute np.sum(tj * rj**2,axis=0) + fac = xp.sum(symrank**2, axis=-1) + if N % 2: # N odd + varAB = m * n * (16*N*fac - (N+1)**4) / (16.0 * N**2 * (N-1)) + else: # N even + varAB = m * n * (16*fac - N*(N+2)**2) / (16.0 * N * (N-1)) + else: + # otherwise compute normal approximation + if N % 2: # N odd + varAB = n * m * (N + 1.0) * (3 + N ** 2) / (48.0 * N ** 2) + else: + varAB = m * n * (N + 2) * (N - 2.0) / 48 / (N - 1.0) + varAB = xp.asarray(varAB, dtype=dtype) + + # Small values of AB indicate larger dispersion for the x sample. + # Large values of AB indicate larger dispersion for the y sample. + # This is opposite to the way we define the ratio of scales. see [1]_. + z = (mnAB - AB) / xp.sqrt(varAB) + pvalue = _get_pvalue(z, _SimpleNormal(), alternative, xp=xp) + AB = AB[()] if AB.ndim == 0 else AB + pvalue = pvalue[()] if pvalue.ndim == 0 else pvalue + return AnsariResult(AB, pvalue) + + +BartlettResult = namedtuple('BartlettResult', ('statistic', 'pvalue')) + +@xp_capabilities(marray=True) +@_axis_nan_policy_factory(BartlettResult, n_samples=None) +def bartlett(*samples, axis=0): + r"""Perform Bartlett's test for equal variances. + + Bartlett's test tests the null hypothesis that all input samples + are from populations with equal variances. For samples + from significantly non-normal populations, Levene's test + `levene` is more robust. + + Parameters + ---------- + *samples : array_like + arrays of sample data. Only 1d arrays are accepted, they may have + different lengths. + + Returns + ------- + statistic : float + The test statistic. + pvalue : float + The p-value of the test. + + See Also + -------- + fligner : A non-parametric test for the equality of k variances + levene : A robust parametric test for equality of k variances + :ref:`hypothesis_bartlett` : Extended example + + Notes + ----- + Conover et al. (1981) examine many of the existing parametric and + nonparametric tests by extensive simulations and they conclude that the + tests proposed by Fligner and Killeen (1976) and Levene (1960) appear to be + superior in terms of robustness of departures from normality and power + ([3]_). + + References + ---------- + .. [1] https://www.itl.nist.gov/div898/handbook/eda/section3/eda357.htm + .. [2] Snedecor, George W. and Cochran, William G. (1989), Statistical + Methods, Eighth Edition, Iowa State University Press. + .. [3] Park, C. and Lindsay, B. G. (1999). Robust Scale Estimation and + Hypothesis Testing based on Quadratic Inference Function. Technical + Report #99-03, Center for Likelihood Studies, Pennsylvania State + University. + .. [4] Bartlett, M. S. (1937). Properties of Sufficiency and Statistical + Tests. Proceedings of the Royal Society of London. Series A, + Mathematical and Physical Sciences, Vol. 160, No.901, pp. 268-282. + + Examples + -------- + + Test whether the lists `a`, `b` and `c` come from populations + with equal variances. + + >>> import numpy as np + >>> from scipy import stats + >>> a = [8.88, 9.12, 9.04, 8.98, 9.00, 9.08, 9.01, 8.85, 9.06, 8.99] + >>> b = [8.88, 8.95, 9.29, 9.44, 9.15, 9.58, 8.36, 9.18, 8.67, 9.05] + >>> c = [8.95, 9.12, 8.95, 8.85, 9.03, 8.84, 9.07, 8.98, 8.86, 8.98] + >>> stat, p = stats.bartlett(a, b, c) + >>> p + 1.1254782518834628e-05 + + The very small p-value suggests that the populations do not have equal + variances. + + This is not surprising, given that the sample variance of `b` is much + larger than that of `a` and `c`: + + >>> [np.var(x, ddof=1) for x in [a, b, c]] + [0.007054444444444413, 0.13073888888888888, 0.008890000000000002] + + For a more detailed example, see :ref:`hypothesis_bartlett`. + """ + xp = array_namespace(*samples) + + k = len(samples) + if k < 2: + raise ValueError("Must enter at least two input sample vectors.") + + if axis is None: + samples = [xp_ravel(sample) for sample in samples] + else: + samples = _broadcast_arrays(samples, axis=axis, xp=xp) + samples = [xp.moveaxis(sample, axis, -1) for sample in samples] + + Ni = [xp.asarray(_count_nonmasked(sample, axis=-1, xp=xp), + dtype=sample.dtype, device=xp_device(sample)) + for sample in samples] + Ni = [xp.broadcast_to(N, samples[0].shape[:-1]) for N in Ni] + ssq = [xp.var(sample, correction=1, axis=-1) for sample in samples] + Ni = [arr[xp.newaxis, ...] for arr in Ni] + ssq = [arr[xp.newaxis, ...] for arr in ssq] + Ni = xp.concat(Ni, axis=0) + Ni = xpx.at(Ni)[Ni == 0].set(xp.nan) + ssq = xp.concat(ssq, axis=0) + dtype = Ni.dtype + Ntot = xp.sum(Ni, axis=0) + spsq = xp.sum((Ni - 1)*ssq, axis=0, dtype=dtype) / (Ntot - k) + numer = ((Ntot - k) * xp.log(spsq) + - xp.sum((Ni - 1)*xp.log(ssq), axis=0, dtype=dtype)) + denom = (1 + 1/(3*(k - 1)) + * ((xp.sum(1/(Ni - 1), axis=0)) - 1/(Ntot - k))) + T = numer / denom + + chi2 = _SimpleChi2(xp.asarray(k-1, dtype=dtype, device=xp_device(T))) + pvalue = _get_pvalue(T, chi2, alternative='greater', symmetric=False, xp=xp) + + T = xp.clip(T, min=0., max=xp.inf) + T = T[()] if T.ndim == 0 else T + pvalue = pvalue[()] if pvalue.ndim == 0 else pvalue + + return BartlettResult(T, pvalue) + + +LeveneResult = namedtuple('LeveneResult', ('statistic', 'pvalue')) + + +@xp_capabilities(cpu_only=True, exceptions=['cupy'], marray=True) +@_axis_nan_policy_factory(LeveneResult, n_samples=None) +def levene(*samples, center='median', proportiontocut=0.05, axis=0): + r"""Perform Levene test for equal variances. + + The Levene test tests the null hypothesis that all input samples + are from populations with equal variances. Levene's test is an + alternative to Bartlett's test `bartlett` in the case where + there are significant deviations from normality. + + Parameters + ---------- + *samples : array_like + The sample data, possibly with different lengths. + center : {'mean', 'median', 'trimmed'}, optional + Which statistics to use to center data points within each sample. Default + is 'median'. + proportiontocut : float, optional + When `center` is 'trimmed', this gives the proportion of data points + to cut from each end. (See `scipy.stats.trim_mean`.) + Default is 0.05. + axis : int or tuple of ints, default: 0 + If an int or tuple of ints, the axis or axes of the input along which + to compute the statistic. The statistic of each axis-slice (e.g. row) + of the input will appear in a corresponding element of the output. + If ``None``, the input will be raveled before computing the statistic. + + Returns + ------- + statistic : float + The test statistic. + pvalue : float + The p-value for the test. + + See Also + -------- + fligner : A non-parametric test for the equality of k variances + bartlett : A parametric test for equality of k variances in normal samples + :ref:`hypothesis_levene` : Extended example + + Notes + ----- + Three variations of Levene's test are possible. The possibilities + and their recommended usages are: + + * 'median' : Recommended for skewed (non-normal) distributions> + * 'mean' : Recommended for symmetric, moderate-tailed distributions. + * 'trimmed' : Recommended for heavy-tailed distributions. + + The test version using the mean was proposed in the original article + of Levene ([2]_) while the median and trimmed mean have been studied by + Brown and Forsythe ([3]_), sometimes also referred to as Brown-Forsythe + test. + + References + ---------- + .. [1] https://www.itl.nist.gov/div898/handbook/eda/section3/eda35a.htm + .. [2] Levene, H. (1960). In Contributions to Probability and Statistics: + Essays in Honor of Harold Hotelling, I. Olkin et al. eds., + Stanford University Press, pp. 278-292. + .. [3] Brown, M. B. and Forsythe, A. B. (1974), Journal of the American + Statistical Association, 69, 364-367 + + Examples + -------- + + Test whether the lists `a`, `b` and `c` come from populations + with equal variances. + + >>> import numpy as np + >>> from scipy import stats + >>> a = [8.88, 9.12, 9.04, 8.98, 9.00, 9.08, 9.01, 8.85, 9.06, 8.99] + >>> b = [8.88, 8.95, 9.29, 9.44, 9.15, 9.58, 8.36, 9.18, 8.67, 9.05] + >>> c = [8.95, 9.12, 8.95, 8.85, 9.03, 8.84, 9.07, 8.98, 8.86, 8.98] + >>> stat, p = stats.levene(a, b, c) + >>> p + 0.002431505967249681 + + The small p-value suggests that the populations do not have equal + variances. + + This is not surprising, given that the sample variance of `b` is much + larger than that of `a` and `c`: + + >>> [np.var(x, ddof=1) for x in [a, b, c]] + [0.007054444444444413, 0.13073888888888888, 0.008890000000000002] + + For a more detailed example, see :ref:`hypothesis_levene`. + """ + xp = array_namespace(*samples) + + if center not in ['mean', 'median', 'trimmed']: + raise ValueError("center must be 'mean', 'median' or 'trimmed'.") + + k = len(samples) + if k < 2: + raise ValueError("Must provide at least two samples.") + + if center == 'median': + + def func(x): + return (xp.median(x, axis=-1, keepdims=True) + if (is_numpy(xp) or is_dask(xp)) + else stats.quantile(x, 0.5, axis=-1, keepdims=True)) + + elif center == 'mean': + + def func(x): + return xp.mean(x, axis=-1, keepdims=True) + + else: # center == 'trimmed' + + def func(x): + # keepdims=True doesn't currently work for Dask + return _stats_py.trim_mean(x, proportiontocut, axis=-1)[..., xp.newaxis] + + Nis = [_count_nonmasked(sample, axis=-1, keepdims=True, xp=xp) + for sample in samples] + Ycis = [func(sample) for sample in samples] + Ntot = sum(Nis) + + # compute Zij's + Zijs = [xp.abs(sample - Yc) for sample, Yc in zip(samples, Ycis)] + + # compute Zbari + Zbaris = [xp.mean(Zij, axis=-1, keepdims=True) for Zij in Zijs] + Zbar = sum(Ni*Zbari for Ni, Zbari in zip(Nis, Zbaris)) / Ntot + + # compute numerator and denominator + dfd = (Ntot - k) + numer = dfd * sum(Ni * (Zbari - Zbar)**2 + for Ni, Zbari in zip(Nis, Zbaris)) + dfn = (k - 1.0) + denom = dfn * sum(xp.sum((Zij - Zbari)**2, axis=-1, keepdims=True) + for Zij, Zbari in zip(Zijs, Zbaris)) + + W = numer / denom + W = xp.squeeze(W, axis=-1) + dfd = xp.squeeze(dfd, axis=-1) if is_marray(xp) else dfd + dfn, dfd = xp.asarray(dfn, dtype=W.dtype), xp.asarray(dfd, dtype=W.dtype) + pval = _get_pvalue(W, _SimpleF(dfn, dfd), 'greater', xp=xp) + W = W[()] if W.ndim == 0 else W + pval = pval[()] if pval.ndim == 0 else pval + return LeveneResult(W, pval) + + +FlignerResult = namedtuple('FlignerResult', ('statistic', 'pvalue')) + + +@xp_capabilities(skip_backends=[('dask.array', 'no rankdata')], + marray=True) +@_axis_nan_policy_factory(FlignerResult, n_samples=None) +def fligner(*samples, center='median', proportiontocut=0.05, axis=0): + r"""Perform Fligner-Killeen test for equality of variance. + + Fligner's test tests the null hypothesis that all input samples + are from populations with equal variances. Fligner-Killeen's test is + distribution free when populations are identical [2]_. + + Parameters + ---------- + *samples : array_like + Arrays of sample data. Need not be the same length. + center : {'mean', 'median', 'trimmed'}, optional + Which statistics to use to center data points within each sample. Default + is 'median'. + proportiontocut : float, optional + When `center` is 'trimmed', this gives the proportion of data points + to cut from each end. (See `scipy.stats.trim_mean`.) + Default is 0.05. + axis : int or tuple of ints, default: 0 + If an int or tuple of ints, the axis or axes of the input along which + to compute the statistic. The statistic of each axis-slice (e.g. row) + of the input will appear in a corresponding element of the output. + If ``None``, the input will be raveled before computing the statistic. + + Returns + ------- + statistic : float + The test statistic. + pvalue : float + The p-value for the hypothesis test. + + See Also + -------- + bartlett : A parametric test for equality of k variances in normal samples + levene : A robust parametric test for equality of k variances + :ref:`hypothesis_fligner` : Extended example + + Notes + ----- + As with Levene's test there are three variants of Fligner's test that + differ by the measure of central tendency used in the test. See `levene` + for more information. + + Conover et al. (1981) examine many of the existing parametric and + nonparametric tests by extensive simulations and they conclude that the + tests proposed by Fligner and Killeen (1976) and Levene (1960) appear to be + superior in terms of robustness of departures from normality and power + [3]_. + + References + ---------- + .. [1] Qu, A., Lindsay, B. G., and Li, B. (2000). Improving generalized + estimating equations using quadratic inference functions. + Biometrika, 87(4), 823-836. + :doi:`10.1093/biomet/87.4.823` + .. [2] Fligner, M.A. and Killeen, T.J. (1976). Distribution-free two-sample + tests for scale. Journal of the American Statistical Association. + 71(353), 210-213. + .. [3] Conover, W. J., Johnson, M. E. and Johnson M. M. (1981). A + comparative study of tests for homogeneity of variances, with + applications to the outer continental shelf bidding data. + Technometrics, 23(4), 351-361. + + Examples + -------- + + >>> import numpy as np + >>> from scipy import stats + + Test whether the lists `a`, `b` and `c` come from populations + with equal variances. + + >>> a = [8.88, 9.12, 9.04, 8.98, 9.00, 9.08, 9.01, 8.85, 9.06, 8.99] + >>> b = [8.88, 8.95, 9.29, 9.44, 9.15, 9.58, 8.36, 9.18, 8.67, 9.05] + >>> c = [8.95, 9.12, 8.95, 8.85, 9.03, 8.84, 9.07, 8.98, 8.86, 8.98] + >>> stat, p = stats.fligner(a, b, c) + >>> p + 0.00450826080004775 + + The small p-value suggests that the populations do not have equal + variances. + + This is not surprising, given that the sample variance of `b` is much + larger than that of `a` and `c`: + + >>> [np.var(x, ddof=1) for x in [a, b, c]] + [0.007054444444444413, 0.13073888888888888, 0.008890000000000002] + + For a more detailed example, see :ref:`hypothesis_fligner`. + """ + xp = array_namespace(*samples) + + if center not in ['mean', 'median', 'trimmed']: + raise ValueError("center must be 'mean', 'median' or 'trimmed'.") + + k = len(samples) + if k < 2: + raise ValueError("Must provide at least two samples.") + + samples = xp_promote(*samples, force_floating=True, xp=xp) + dtype = samples[0].dtype + + # Handle empty input + for sample in samples: + if sample.size == 0: + NaN = _get_nan(*samples, xp=xp) + return FlignerResult(NaN, NaN) + + if center == 'median': + + def func(x): + return (xp.median(x, axis=-1, keepdims=True) + if (is_numpy(xp) or is_dask(xp)) + else stats.quantile(x, 0.5, axis=-1, keepdims=True)) + + elif center == 'mean': + + def func(x): + return xp.mean(x, axis=-1, keepdims=True) + + else: # center == 'trimmed' + + def func(x): + # keepdims=True doesn't currently work for lazy arrays + return _stats_py.trim_mean(x, proportiontocut, axis=-1)[..., xp.newaxis] + + lengths = [sample.shape[-1] for sample in samples] + ni = [_count_nonmasked(sample, axis=-1, keepdims=True, xp=xp) + for sample in samples] + N = sum(ni) + ni = [xp.squeeze(ni_, axis=-1) for ni_ in ni] if is_marray(xp) else ni + + # Implementation follows [3] pg 355 F-K. + Xibar = [func(sample) for sample in samples] + Xij_Xibar = [xp.abs(sample - Xibar_) for sample, Xibar_ in zip(samples, Xibar)] + Xij_Xibar = xp.concat(Xij_Xibar, axis=-1) + ranks = stats.rankdata(Xij_Xibar, method='average', axis=-1) + a_Ni = special.ndtri(ranks / (2*(N + 1.0)) + 0.5) + + # [3] Equation 2.1 + splits = list(itertools.accumulate(lengths, initial=0)) + Ai = [a_Ni[..., i:j] for i, j in zip(splits[:-1], splits[1:])] + Aibar = [xp.mean(Ai_, axis=-1) for Ai_ in Ai] + abar = xp.mean(a_Ni, axis=-1) + V2 = xp.var(a_Ni, axis=-1, correction=1) + statistic = sum(ni_ * (Aibar_ - abar)**2 for ni_, Aibar_ in zip(ni, Aibar)) / V2 + + chi2 = _SimpleChi2(xp.asarray(k-1, dtype=dtype)) + pval = _get_pvalue(statistic, chi2, alternative='greater', symmetric=False, xp=xp) + return FlignerResult(statistic, pval) + + +def _mood_statistic_with_ties(x, y, t, m, n, N, xp): + # First equation of "Mood's Squared Rank Test", Mielke pg 313 + E_0_T = m * (N * N - 1) / 12 + + # m, n, N, t, and S are defined in the second paragraph of Mielke pg 312 + # The only difference is that our `t` has zeros interspersed with the relevant + # numbers to keep the array rectangular, but these terms add nothing to the sum. + S = xp.cumulative_sum(t, include_initial=True, axis=-1) + S_i, S_i_m1 = S[..., 1:], S[..., :-1] + # Second equation of "Mood's Squared Rank Test", Mielke pg 313 + varM = (m * n * (N + 1.0) * (N**2 - 4) / 180 + - m * n / (180 * N * (N - 1)) + * xp.sum(t * (t ** 2 - 1) * (t ** 2 - 4 + (15 * (N - S_i - S_i_m1) ** 2)), + axis=-1)) + + # There is a formula for Phi (`phi` in code) in terms of t, S, and Psi(I) at the + # bottom of Mielke pg 312. Psi(I) = [I - (N+1)/2]^2 is defined (with a mistake in + # the location of the ^2) at the beginning of "Mood's Squared Rank Test" (pg 313). + # To vectorize this calculation, let c = (N + 1) / 2, so Psi(I) = I^2 - 2*c*I + c^2. + # We sum each of these three parts of Psi separately using formulas for sums from a + # to b (inclusive) of terms I^2, I, and 1 where I takes on successive integers. + def sum_I2(a, b=None): + return (a * (a + 1) * (2 * a + 1) / 6 if b is None + else sum_I2(b) - sum_I2(a) + a**2) + + def sum_I(a, b=None): + return (a * (a + 1) / 2 if b is None + else sum_I(b) - sum_I(a) + a) + + def sum_1(a, b): + return (b - a) + 1 + + with np.errstate(invalid='ignore', divide='ignore'): + sum_I2 = sum_I2(S_i_m1 + 1, S_i) + sum_I = sum_I(S_i_m1 + 1, S_i) + sum_1 = sum_1(S_i_m1 + 1, S_i) + c = (N + 1) / 2 + phi = (sum_I2 - 2*c*sum_I + sum_1*c**2) / t + + phi = xpx.at(phi)[t == 0].set(0.) # where t = 0 we get NaNs; eliminate them + + # Mielke pg 312 defines `a` as the count of elements in sample `x` for each of the + # unique values in the combined sample. The tricky thing is getting these to line + # up with the locations of nonzero elements in `t`/`phi`. + x = xp.sort(x, axis=-1) + xy = xp.concat((x, y), axis=-1) + i = xp.argsort(xy, stable=True, axis=-1) + _, _, a = _stats_py._rankdata(x, method='average', return_ties=True) + + zeros = xp.zeros(a.shape[:-1] + (n,), dtype=a.dtype) + a = xp.concat((a, zeros), axis=-1) + a = xp.take_along_axis(a, i, axis=-1) + + # Mielke pg 312 defines test statistic `T` as the inner product `a` and `phi` + T = xp.vecdot(a, phi, axis=-1) + + return (T - E_0_T) / xp.sqrt(varM) + + +def _mood_statistic_no_ties(r, m, n, N, xp): + rx = r[..., :m] + M = xp.sum((rx - (N + 1.0) / 2) ** 2, axis=-1) + E_0_T = m * (N * N - 1.0) / 12 + varM = m * n * (N + 1.0) * (N + 2) * (N - 2) / 180 + return (M - E_0_T) / math.sqrt(varM) + + +def _mood_too_small(samples, kwargs, axis=-1): + x, y = samples + m = x.shape[axis] + n = y.shape[axis] + N = m + n + return N < 3 + + +@xp_capabilities(skip_backends=[('dask.array', 'no rankdata')]) +@_axis_nan_policy_factory(SignificanceResult, n_samples=2, too_small=_mood_too_small) +def mood(x, y, axis=0, alternative="two-sided"): + """Perform Mood's test for equal scale parameters. + + Mood's two-sample test for scale parameters is a non-parametric + test for the null hypothesis that two samples are drawn from the + same distribution with the same scale parameter. + + Parameters + ---------- + x, y : array_like + Arrays of sample data. There must be at least three observations + total. + axis : int, optional + The axis along which the samples are tested. `x` and `y` can be of + different length along `axis`. + If `axis` is None, `x` and `y` are flattened and the test is done on + all values in the flattened arrays. + alternative : {'two-sided', 'less', 'greater'}, optional + Defines the alternative hypothesis. Default is 'two-sided'. + The following options are available: + + * 'two-sided': the scales of the distributions underlying `x` and `y` + are different. + * 'less': the scale of the distribution underlying `x` is less than + the scale of the distribution underlying `y`. + * 'greater': the scale of the distribution underlying `x` is greater + than the scale of the distribution underlying `y`. + + .. versionadded:: 1.7.0 + + Returns + ------- + res : SignificanceResult + An object containing attributes: + + statistic : scalar or ndarray + The z-score for the hypothesis test. For 1-D inputs a scalar is + returned. + pvalue : scalar ndarray + The p-value for the hypothesis test. + + See Also + -------- + fligner : A non-parametric test for the equality of k variances + ansari : A non-parametric test for the equality of 2 variances + bartlett : A parametric test for equality of k variances in normal samples + levene : A parametric test for equality of k variances + + Notes + ----- + The data are assumed to be drawn from probability distributions ``f(x)`` + and ``f(x/s) / s`` respectively, for some probability density function f. + The null hypothesis is that ``s == 1``. + + For multi-dimensional arrays, if the inputs are of shapes + ``(n0, n1, n2, n3)`` and ``(n0, m1, n2, n3)``, then if ``axis=1``, the + resulting z and p values will have shape ``(n0, n2, n3)``. Note that + ``n1`` and ``m1`` don't have to be equal, but the other dimensions do. + + References + ---------- + [1] Mielke, Paul W. "Note on Some Squared Rank Tests with Existing Ties." + Technometrics, vol. 9, no. 2, 1967, pp. 312-14. JSTOR, + :doi:`10.2307/1266427`. Accessed 18 May 2022. + + Examples + -------- + >>> import numpy as np + >>> from scipy import stats + >>> rng = np.random.default_rng() + >>> x2 = rng.standard_normal((2, 45, 6, 7)) + >>> x1 = rng.standard_normal((2, 30, 6, 7)) + >>> res = stats.mood(x1, x2, axis=1) + >>> res.pvalue.shape + (2, 6, 7) + + Find the number of points where the difference in scale is not significant: + + >>> (res.pvalue > 0.1).sum() + 78 + + Perform the test with different scales: + + >>> x1 = rng.standard_normal((2, 30)) + >>> x2 = rng.standard_normal((2, 35)) * 10.0 + >>> stats.mood(x1, x2, axis=1) + SignificanceResult(statistic=array([-5.76174136, -6.12650783]), + pvalue=array([8.32505043e-09, 8.98287869e-10])) + + """ + xp = array_namespace(x, y) + x, y = xp_promote(x, y, force_floating=True, xp=xp) + + # _axis_nan_policy decorator ensures axis=-1 + xy = xp.concat((x, y), axis=-1) + + m = x.shape[-1] + n = y.shape[-1] + N = m + n + + if m == 0 or n == 0 or N < 3: # only needed for test_axis_nan_policy + NaN = _get_nan(x, y, xp=xp) + return SignificanceResult(NaN, NaN) + + # determine if any of the samples contain ties + # `a` represents ties within `x`; `t` represents ties within `xy` + r, _, t = _stats_py._rankdata(xy, method='average', return_ties=True) + + if is_lazy_array(t) or xp.any(t > 1): + z = _mood_statistic_with_ties(x, y, t, m, n, N, xp=xp) + else: + z = _mood_statistic_no_ties(r, m, n, N, xp=xp) + + pval = _get_pvalue(z, _SimpleNormal(), alternative, xp=xp) + + z = z[()] if z.ndim == 0 else z + pval = pval[()] if pval.ndim == 0 else pval + return SignificanceResult(z, pval) + + +WilcoxonResult = _make_tuple_bunch('WilcoxonResult', ['statistic', 'pvalue']) + + +def wilcoxon_result_unpacker(res, _): + if hasattr(res, 'zstatistic'): + return res.statistic, res.pvalue, res.zstatistic + else: + return res.statistic, res.pvalue + + +def wilcoxon_result_object(statistic, pvalue, zstatistic=None): + res = WilcoxonResult(statistic, pvalue) + if zstatistic is not None: + res.zstatistic = zstatistic + return res + + +def wilcoxon_outputs(kwds): + method = kwds.get('method', 'auto') + if method == 'asymptotic': + return 3 + return 2 + + +@xp_capabilities(skip_backends=[("dask.array", "no rankdata")], + cpu_only=True, # null distribution is CPU only + marray=True, + extra_note=("Only ``method='asymptotic'``/``zero_method='zsplit'`` is " + "compatible with MArray input. " + "``method='auto'`` is incompatible with JAX arrays.")) +@_rename_parameter("mode", "method") +@_axis_nan_policy_factory( + wilcoxon_result_object, paired=True, + n_samples=lambda kwds: 2 if kwds.get('y', None) is not None else 1, + result_to_tuple=wilcoxon_result_unpacker, n_outputs=wilcoxon_outputs, +) +def wilcoxon(x, y=None, zero_method="wilcox", correction=False, + alternative="two-sided", method='auto', *, axis=0): + """Calculate the Wilcoxon signed-rank test. + + The Wilcoxon signed-rank test tests the null hypothesis that two + related paired samples come from the same distribution. In particular, + it tests whether the distribution of the differences ``x - y`` is symmetric + about zero. It is a non-parametric version of the paired T-test. + + Parameters + ---------- + x : array_like + Either the first set of measurements (in which case ``y`` is the second + set of measurements), or the differences between two sets of + measurements (in which case ``y`` is not to be specified.) Must be + one-dimensional. + y : array_like, optional + Either the second set of measurements (if ``x`` is the first set of + measurements), or not specified (if ``x`` is the differences between + two sets of measurements.) Must be one-dimensional. + + .. warning:: + When `y` is provided, `wilcoxon` calculates the test statistic + based on the ranks of the absolute values of ``d = x - y``. + Roundoff error in the subtraction can result in elements of ``d`` + being assigned different ranks even when they would be tied with + exact arithmetic. Rather than passing `x` and `y` separately, + consider computing the difference ``x - y``, rounding as needed to + ensure that only truly unique elements are numerically distinct, + and passing the result as `x`, leaving `y` at the default (None). + + zero_method : {"wilcox", "pratt", "zsplit"}, optional + There are different conventions for handling pairs of observations + with equal values ("zero-differences", or "zeros"). + + * "wilcox": Discards all zero-differences (default); see [4]_. + * "pratt": Includes zero-differences in the ranking process, + but drops the ranks of the zeros (more conservative); see [3]_. + In this case, the normal approximation is adjusted as in [5]_. + * "zsplit": Includes zero-differences in the ranking process and + splits the zero rank between positive and negative ones. + + correction : bool, optional + If True, apply continuity correction by adjusting the Wilcoxon rank + statistic by 0.5 towards the mean value when computing the + z-statistic if a normal approximation is used. Default is False. + alternative : {"two-sided", "greater", "less"}, optional + Defines the alternative hypothesis. Default is 'two-sided'. + In the following, let ``d`` represent the difference between the paired + samples: ``d = x - y`` if both ``x`` and ``y`` are provided, or + ``d = x`` otherwise. + + * 'two-sided': the distribution underlying ``d`` is not symmetric + about zero. + * 'less': the distribution underlying ``d`` is stochastically less + than a distribution symmetric about zero. + * 'greater': the distribution underlying ``d`` is stochastically + greater than a distribution symmetric about zero. + + method : {"auto", "exact", "asymptotic"} or `PermutationMethod` instance, optional + Method to calculate the p-value, see Notes. Default is "auto". + + axis : int or None, default: 0 + If an int, the axis of the input along which to compute the statistic. + The statistic of each axis-slice (e.g. row) of the input will appear + in a corresponding element of the output. If ``None``, the input will + be raveled before computing the statistic. + + Returns + ------- + result : WilcoxonResult + An object with the following attributes. + + statistic : array_like + If `alternative` is "two-sided", the sum of the ranks of the + differences above or below zero, whichever is smaller. + Otherwise the sum of the ranks of the differences above zero. + pvalue : array_like + The p-value for the test depending on `alternative` and `method`. + zstatistic : array_like + When ``method = 'asymptotic'``, this is the normalized z-statistic:: + + z = (T - mn - d) / se + + where ``T`` is `statistic` as defined above, ``mn`` is the mean of the + distribution under the null hypothesis, ``d`` is a continuity + correction, and ``se`` is the standard error. + When ``method != 'asymptotic'``, this attribute is not available. + + See Also + -------- + kruskal, mannwhitneyu + + Notes + ----- + In the following, let ``d`` represent the difference between the paired + samples: ``d = x - y`` if both ``x`` and ``y`` are provided, or ``d = x`` + otherwise. Assume that all elements of ``d`` are independent and + identically distributed observations, and all are distinct and nonzero. + + - When ``len(d)`` is sufficiently large, the null distribution of the + normalized test statistic (`zstatistic` above) is approximately normal, + and ``method = 'asymptotic'`` can be used to compute the p-value. + - When ``len(d)`` is small, the normal approximation may not be accurate, + and ``method='exact'`` is preferred (at the cost of additional + execution time). + - The default, ``method='auto'``, selects between the two: + ``method='exact'`` is used when ``len(d) <= 50``, and + ``method='asymptotic'`` is used otherwise. + + The presence of "ties" (i.e. not all elements of ``d`` are unique) or + "zeros" (i.e. elements of ``d`` are zero) changes the null distribution + of the test statistic, and ``method='exact'`` no longer calculates + the exact p-value. If ``method='asymptotic'``, the z-statistic is adjusted + for more accurate comparison against the standard normal, but still, + for finite sample sizes, the standard normal is only an approximation of + the true null distribution of the z-statistic. For such situations, the + `method` parameter also accepts instances of `PermutationMethod`. In this + case, the p-value is computed using `permutation_test` with the provided + configuration options and other appropriate settings. + + The presence of ties and zeros affects the resolution of ``method='auto'`` + accordingly: exhasutive permutations are performed when ``len(d) <= 13``, + and the asymptotic method is used otherwise. Note that they asymptotic + method may not be very accurate even for ``len(d) > 14``; the threshold + was chosen as a compromise between execution time and accuracy under the + constraint that the results must be deterministic. Consider providing an + instance of `PermutationMethod` method manually, choosing the + ``n_resamples`` parameter to balance time constraints and accuracy + requirements. + + Please also note that in the edge case that all elements of ``d`` are zero, + the p-value relying on the normal approximaton cannot be computed (NaN) + if ``zero_method='wilcox'`` or ``zero_method='pratt'``. + + References + ---------- + .. [1] https://en.wikipedia.org/wiki/Wilcoxon_signed-rank_test + .. [2] Conover, W.J., Practical Nonparametric Statistics, 1971. + .. [3] Pratt, J.W., Remarks on Zeros and Ties in the Wilcoxon Signed + Rank Procedures, Journal of the American Statistical Association, + Vol. 54, 1959, pp. 655-667. :doi:`10.1080/01621459.1959.10501526` + .. [4] Wilcoxon, F., Individual Comparisons by Ranking Methods, + Biometrics Bulletin, Vol. 1, 1945, pp. 80-83. :doi:`10.2307/3001968` + .. [5] Cureton, E.E., The Normal Approximation to the Signed-Rank + Sampling Distribution When Zero Differences are Present, + Journal of the American Statistical Association, Vol. 62, 1967, + pp. 1068-1069. :doi:`10.1080/01621459.1967.10500917` + + Examples + -------- + In [4]_, the differences in height between cross- and self-fertilized + corn plants is given as follows: + + >>> d = [6, 8, 14, 16, 23, 24, 28, 29, 41, -48, 49, 56, 60, -67, 75] + + Cross-fertilized plants appear to be higher. To test the null + hypothesis that there is no height difference, we can apply the + two-sided test: + + >>> from scipy.stats import wilcoxon + >>> res = wilcoxon(d) + >>> res.statistic, res.pvalue + (24.0, 0.041259765625) + + Hence, we would reject the null hypothesis at a confidence level of 5%, + concluding that there is a difference in height between the groups. + To confirm that the median of the differences can be assumed to be + positive, we use: + + >>> res = wilcoxon(d, alternative='greater') + >>> res.statistic, res.pvalue + (96.0, 0.0206298828125) + + This shows that the null hypothesis that the median is negative can be + rejected at a confidence level of 5% in favor of the alternative that + the median is greater than zero. The p-values above are exact. Using the + normal approximation gives very similar values: + + >>> res = wilcoxon(d, method='asymptotic') + >>> res.statistic, res.pvalue + (24.0, 0.04088813291185591) + + Note that the statistic changed to 96 in the one-sided case (the sum + of ranks of positive differences) whereas it is 24 in the two-sided + case (the minimum of sum of ranks above and below zero). + + In the example above, the differences in height between paired plants are + provided to `wilcoxon` directly. Alternatively, `wilcoxon` accepts two + samples of equal length, calculates the differences between paired + elements, then performs the test. Consider the samples ``x`` and ``y``: + + >>> import numpy as np + >>> x = np.array([0.5, 0.825, 0.375, 0.5]) + >>> y = np.array([0.525, 0.775, 0.325, 0.55]) + >>> res = wilcoxon(x, y, alternative='greater') + >>> res + WilcoxonResult(statistic=5.0, pvalue=0.5625) + + Note that had we calculated the differences by hand, the test would have + produced different results: + + >>> d = [-0.025, 0.05, 0.05, -0.05] + >>> ref = wilcoxon(d, alternative='greater') + >>> ref + WilcoxonResult(statistic=6.0, pvalue=0.5) + + The substantial difference is due to roundoff error in the results of + ``x-y``: + + >>> d - (x-y) + array([2.08166817e-17, 6.93889390e-17, 1.38777878e-17, 4.16333634e-17]) + + Even though we expected all the elements of ``(x-y)[1:]`` to have the same + magnitude ``0.05``, they have slightly different magnitudes in practice, + and therefore are assigned different ranks in the test. Before performing + the test, consider calculating ``d`` and adjusting it as necessary to + ensure that theoretically identically values are not numerically distinct. + For example: + + >>> d2 = np.around(x - y, decimals=3) + >>> wilcoxon(d2, alternative='greater') + WilcoxonResult(statistic=6.0, pvalue=0.5) + + """ + # replace approx by asymptotic to ensure backwards compatability + if method == "approx": + method = "asymptotic" + return _wilcoxon._wilcoxon_nd(x, y, zero_method, correction, alternative, + method, axis) + + +MedianTestResult = _make_tuple_bunch( + 'MedianTestResult', + ['statistic', 'pvalue', 'median', 'table'], [] +) + + +@xp_capabilities(np_only=True) +def median_test(*samples, ties='below', correction=True, lambda_=1, + nan_policy='propagate'): + """Perform a Mood's median test. + + Test that two or more samples come from populations with the same median. + + Let ``n = len(samples)`` be the number of samples. The "grand median" of + all the data is computed, and a contingency table is formed by + classifying the values in each sample as being above or below the grand + median. The contingency table, along with `correction` and `lambda_`, + are passed to `scipy.stats.chi2_contingency` to compute the test statistic + and p-value. + + Parameters + ---------- + *samples : array_like + The set of samples. There must be at least two samples. + Each sample must be a one-dimensional sequence containing at least + one value. The samples are not required to have the same length. + ties : str, optional + Determines how values equal to the grand median are classified in + the contingency table. The string must be one of:: + + "below": + Values equal to the grand median are counted as "below". + "above": + Values equal to the grand median are counted as "above". + "ignore": + Values equal to the grand median are not counted. + + The default is "below". + correction : bool, optional + If True, *and* there are just two samples, apply Yates' correction + for continuity when computing the test statistic associated with + the contingency table. Default is True. + lambda_ : float or str, optional + By default, the statistic computed in this test is Pearson's + chi-squared statistic. `lambda_` allows a statistic from the + Cressie-Read power divergence family to be used instead. See + `power_divergence` for details. + Default is 1 (Pearson's chi-squared statistic). + nan_policy : {'propagate', 'raise', 'omit'}, optional + Defines how to handle when input contains nan. 'propagate' returns nan, + 'raise' throws an error, 'omit' performs the calculations ignoring nan + values. Default is 'propagate'. + + Returns + ------- + res : MedianTestResult + An object containing attributes: + + statistic : float + The test statistic. The statistic that is returned is determined + by `lambda_`. The default is Pearson's chi-squared statistic. + pvalue : float + The p-value of the test. + median : float + The grand median. + table : ndarray + The contingency table. The shape of the table is (2, n), where + n is the number of samples. The first row holds the counts of the + values above the grand median, and the second row holds the counts + of the values below the grand median. The table allows further + analysis with, for example, `scipy.stats.chi2_contingency`, or with + `scipy.stats.fisher_exact` if there are two samples, without having + to recompute the table. If ``nan_policy`` is "propagate" and there + are nans in the input, the return value for ``table`` is ``None``. + + See Also + -------- + kruskal : Compute the Kruskal-Wallis H-test for independent samples. + mannwhitneyu : Computes the Mann-Whitney rank test on samples x and y. + + Notes + ----- + .. versionadded:: 0.15.0 + + References + ---------- + .. [1] Mood, A. M., Introduction to the Theory of Statistics. McGraw-Hill + (1950), pp. 394-399. + .. [2] Zar, J. H., Biostatistical Analysis, 5th ed. Prentice Hall (2010). + See Sections 8.12 and 10.15. + + Examples + -------- + A biologist runs an experiment in which there are three groups of plants. + Group 1 has 16 plants, group 2 has 15 plants, and group 3 has 17 plants. + Each plant produces a number of seeds. The seed counts for each group + are:: + + Group 1: 10 14 14 18 20 22 24 25 31 31 32 39 43 43 48 49 + Group 2: 28 30 31 33 34 35 36 40 44 55 57 61 91 92 99 + Group 3: 0 3 9 22 23 25 25 33 34 34 40 45 46 48 62 67 84 + + The following code applies Mood's median test to these samples. + + >>> g1 = [10, 14, 14, 18, 20, 22, 24, 25, 31, 31, 32, 39, 43, 43, 48, 49] + >>> g2 = [28, 30, 31, 33, 34, 35, 36, 40, 44, 55, 57, 61, 91, 92, 99] + >>> g3 = [0, 3, 9, 22, 23, 25, 25, 33, 34, 34, 40, 45, 46, 48, 62, 67, 84] + >>> from scipy.stats import median_test + >>> res = median_test(g1, g2, g3) + + The median is + + >>> res.median + 34.0 + + and the contingency table is + + >>> res.table + array([[ 5, 10, 7], + [11, 5, 10]]) + + `p` is too large to conclude that the medians are not the same: + + >>> res.pvalue + 0.12609082774093244 + + The "G-test" can be performed by passing ``lambda_="log-likelihood"`` to + `median_test`. + + >>> res = median_test(g1, g2, g3, lambda_="log-likelihood") + >>> res.pvalue + 0.12224779737117837 + + The median occurs several times in the data, so we'll get a different + result if, for example, ``ties="above"`` is used: + + >>> res = median_test(g1, g2, g3, ties="above") + >>> res.pvalue + 0.063873276069553273 + + >>> res.table + array([[ 5, 11, 9], + [11, 4, 8]]) + + This example demonstrates that if the data set is not large and there + are values equal to the median, the p-value can be sensitive to the + choice of `ties`. + + """ + if len(samples) < 2: + raise ValueError('median_test requires two or more samples.') + + ties_options = ['below', 'above', 'ignore'] + if ties not in ties_options: + raise ValueError(f"invalid 'ties' option '{ties}'; 'ties' must be one " + f"of: {str(ties_options)[1:-1]}") + + data = [np.asarray(sample) for sample in samples] + + # Validate the sizes and shapes of the arguments. + for k, d in enumerate(data): + if d.size == 0: + raise ValueError(f"Sample {k + 1} is empty. All samples must " + f"contain at least one value.") + if d.ndim != 1: + raise ValueError(f"Sample {k + 1} has {d.ndim} dimensions. " + f"All samples must be one-dimensional sequences.") + + cdata = np.concatenate(data) + contains_nan = _contains_nan(cdata, nan_policy) + if nan_policy == 'propagate' and contains_nan: + return MedianTestResult(np.nan, np.nan, np.nan, None) + + if contains_nan: + grand_median = np.median(cdata[~np.isnan(cdata)]) + else: + grand_median = np.median(cdata) + # When the minimum version of numpy supported by scipy is 1.9.0, + # the above if/else statement can be replaced by the single line: + # grand_median = np.nanmedian(cdata) + + # Create the contingency table. + table = np.zeros((2, len(data)), dtype=np.int64) + for k, sample in enumerate(data): + sample = sample[~np.isnan(sample)] + + nabove = count_nonzero(sample > grand_median) + nbelow = count_nonzero(sample < grand_median) + nequal = sample.size - (nabove + nbelow) + table[0, k] += nabove + table[1, k] += nbelow + if ties == "below": + table[1, k] += nequal + elif ties == "above": + table[0, k] += nequal + + # Check that no row or column of the table is all zero. + # Such a table can not be given to chi2_contingency, because it would have + # a zero in the table of expected frequencies. + rowsums = table.sum(axis=1) + if rowsums[0] == 0: + raise ValueError(f"All values are below the grand median ({grand_median}).") + if rowsums[1] == 0: + raise ValueError(f"All values are above the grand median ({grand_median}).") + if ties == "ignore": + # We already checked that each sample has at least one value, but it + # is possible that all those values equal the grand median. If `ties` + # is "ignore", that would result in a column of zeros in `table`. We + # check for that case here. + zero_cols = np.nonzero((table == 0).all(axis=0))[0] + if len(zero_cols) > 0: + raise ValueError( + f"All values in sample {zero_cols[0] + 1} are equal to the grand " + f"median ({grand_median!r}), so they are ignored, resulting in an " + f"empty sample." + ) + + stat, p, dof, expected = chi2_contingency(table, lambda_=lambda_, + correction=correction) + return MedianTestResult(stat, p, grand_median, table) + + +def _circfuncs_common(samples, period, xp=None): + xp = array_namespace(samples) if xp is None else xp + + samples = xp_promote(samples, force_floating=True, xp=xp) + + # Recast samples as radians that range between 0 and 2 pi and calculate + # the sine and cosine + scaled_samples = samples * ((2.0 * pi) / period) + sin_samp = xp.sin(scaled_samples) + cos_samp = xp.cos(scaled_samples) + + return samples, sin_samp, cos_samp + + +@xp_capabilities(marray=True) +@_axis_nan_policy_factory( + lambda x: x, n_outputs=1, default_axis=None, + result_to_tuple=lambda x, _: (x,) +) +def circmean(samples, high=2*pi, low=0, axis=None, nan_policy='propagate'): + r"""Compute the circular mean of a sample of angle observations. + + Given :math:`n` angle observations :math:`x_1, \cdots, x_n` measured in + radians, their *circular mean* is defined by ([1]_, Eq. 2.2.4) + + .. math:: + + \mathrm{Arg} \left( \frac{1}{n} \sum_{k=1}^n e^{i x_k} \right) + + where :math:`i` is the imaginary unit and :math:`\mathop{\mathrm{Arg}} z` + gives the principal value of the argument of complex number :math:`z`, + restricted to the range :math:`[0,2\pi]` by default. :math:`z` in the + above expression is known as the `mean resultant vector`. + + Parameters + ---------- + samples : array_like + Input array of angle observations. The value of a full angle is + equal to ``(high - low)``. + high : float, optional + Upper boundary of the principal value of an angle. Default is ``2*pi``. + low : float, optional + Lower boundary of the principal value of an angle. Default is ``0``. + + Returns + ------- + circmean : float + Circular mean, restricted to the range ``[low, high]``. + + If the mean resultant vector is zero, an input-dependent, + implementation-defined number between ``[low, high]`` is returned. + If the input array is empty, ``np.nan`` is returned. + + See Also + -------- + circstd : Circular standard deviation. + circvar : Circular variance. + + References + ---------- + .. [1] Mardia, K. V. and Jupp, P. E. *Directional Statistics*. + John Wiley & Sons, 1999. + + Examples + -------- + For readability, all angles are printed out in degrees. + + >>> import numpy as np + >>> from scipy.stats import circmean + >>> import matplotlib.pyplot as plt + >>> angles = np.deg2rad(np.array([20, 30, 330])) + >>> circmean = circmean(angles) + >>> np.rad2deg(circmean) + 7.294976657784009 + + >>> mean = angles.mean() + >>> np.rad2deg(mean) + 126.66666666666666 + + Plot and compare the circular mean against the arithmetic mean. + + >>> plt.plot(np.cos(np.linspace(0, 2*np.pi, 500)), + ... np.sin(np.linspace(0, 2*np.pi, 500)), + ... c='k') + >>> plt.scatter(np.cos(angles), np.sin(angles), c='k') + >>> plt.scatter(np.cos(circmean), np.sin(circmean), c='b', + ... label='circmean') + >>> plt.scatter(np.cos(mean), np.sin(mean), c='r', label='mean') + >>> plt.legend() + >>> plt.axis('equal') + >>> plt.show() + + """ + xp = array_namespace(samples) + # Needed for non-NumPy arrays to get appropriate NaN result + # Apparently atan2(0, 0) is 0, even though it is mathematically undefined + if xp_size(samples) == 0: + return xp.mean(samples, axis=axis) + period = high - low + samples, sin_samp, cos_samp = _circfuncs_common(samples, period, xp=xp) + sin_sum = xp.sum(sin_samp, axis=axis) + cos_sum = xp.sum(cos_samp, axis=axis) + res = xp.atan2(sin_sum, cos_sum) + + res = res[()] if res.ndim == 0 else res + return (res * (period / (2.0 * pi)) - low) % period + low + + +@xp_capabilities(marray=True) +@_axis_nan_policy_factory( + lambda x: x, n_outputs=1, default_axis=None, + result_to_tuple=lambda x, _: (x,) +) +def circvar(samples, high=2*pi, low=0, axis=None, nan_policy='propagate'): + r"""Compute the circular variance of a sample of angle observations. + + Given :math:`n` angle observations :math:`x_1, \cdots, x_n` measured in + radians, their *circular variance* is defined by ([2]_, Eq. 2.3.3) + + .. math:: + + 1 - \left| \frac{1}{n} \sum_{k=1}^n e^{i x_k} \right| + + where :math:`i` is the imaginary unit and :math:`|z|` gives the length + of the complex number :math:`z`. :math:`|z|` in the above expression + is known as the `mean resultant length`. + + Parameters + ---------- + samples : array_like + Input array of angle observations. The value of a full angle is + equal to ``(high - low)``. + high : float, optional + Upper boundary of the principal value of an angle. Default is ``2*pi``. + low : float, optional + Lower boundary of the principal value of an angle. Default is ``0``. + + Returns + ------- + circvar : float + Circular variance. The returned value is in the range ``[0, 1]``, + where ``0`` indicates no variance and ``1`` indicates large variance. + + If the input array is empty, ``np.nan`` is returned. + + See Also + -------- + circmean : Circular mean. + circstd : Circular standard deviation. + + Notes + ----- + In the limit of small angles, the circular variance is close to + half the 'linear' variance if measured in radians. + + References + ---------- + .. [1] Fisher, N.I. *Statistical analysis of circular data*. Cambridge + University Press, 1993. + .. [2] Mardia, K. V. and Jupp, P. E. *Directional Statistics*. + John Wiley & Sons, 1999. + + Examples + -------- + >>> import numpy as np + >>> from scipy.stats import circvar + >>> import matplotlib.pyplot as plt + >>> samples_1 = np.array([0.072, -0.158, 0.077, 0.108, 0.286, + ... 0.133, -0.473, -0.001, -0.348, 0.131]) + >>> samples_2 = np.array([0.111, -0.879, 0.078, 0.733, 0.421, + ... 0.104, -0.136, -0.867, 0.012, 0.105]) + >>> circvar_1 = circvar(samples_1) + >>> circvar_2 = circvar(samples_2) + + Plot the samples. + + >>> fig, (left, right) = plt.subplots(ncols=2) + >>> for image in (left, right): + ... image.plot(np.cos(np.linspace(0, 2*np.pi, 500)), + ... np.sin(np.linspace(0, 2*np.pi, 500)), + ... c='k') + ... image.axis('equal') + ... image.axis('off') + >>> left.scatter(np.cos(samples_1), np.sin(samples_1), c='k', s=15) + >>> left.set_title(f"circular variance: {np.round(circvar_1, 2)!r}") + >>> right.scatter(np.cos(samples_2), np.sin(samples_2), c='k', s=15) + >>> right.set_title(f"circular variance: {np.round(circvar_2, 2)!r}") + >>> plt.show() + + """ + xp = array_namespace(samples) + period = high - low + samples, sin_samp, cos_samp = _circfuncs_common(samples, period, xp=xp) + sin_mean = xp.mean(sin_samp, axis=axis) + cos_mean = xp.mean(cos_samp, axis=axis) + hypotenuse = (sin_mean**2. + cos_mean**2.)**0.5 + # hypotenuse can go slightly above 1 due to rounding errors + R = xp.clip(hypotenuse, max=1.) + + res = 1. - R + return res + + +@xp_capabilities(marray=True) +@_axis_nan_policy_factory( + lambda x: x, n_outputs=1, default_axis=None, + result_to_tuple=lambda x, _: (x,) +) +def circstd(samples, high=2*pi, low=0, axis=None, nan_policy='propagate', *, + normalize=False): + r""" + Compute the circular standard deviation of a sample of angle observations. + + Given :math:`n` angle observations :math:`x_1, \cdots, x_n` measured in + radians, their `circular standard deviation` is defined by + ([2]_, Eq. 2.3.11) + + .. math:: + + \sqrt{ -2 \log \left| \frac{1}{n} \sum_{k=1}^n e^{i x_k} \right| } + + where :math:`i` is the imaginary unit and :math:`|z|` gives the length + of the complex number :math:`z`. :math:`|z|` in the above expression + is known as the `mean resultant length`. + + Parameters + ---------- + samples : array_like + Input array of angle observations. The value of a full angle is + equal to ``(high - low)``. + high : float, optional + Upper boundary of the principal value of an angle. Default is ``2*pi``. + low : float, optional + Lower boundary of the principal value of an angle. Default is ``0``. + axis : int or None, default: None + If an int, the axis of the input along which to compute the statistic. + The statistic of each axis-slice (e.g. row) of the input will appear in a + corresponding element of the output. + If ``None``, the input will be raveled before computing the statistic. + nan_policy : {'propagate', 'omit', 'raise'} + Defines how to handle input NaNs. + + - ``propagate``: if a NaN is present in the axis slice (e.g. row) along + which the statistic is computed, the corresponding entry of the output + will be NaN. + - ``omit``: NaNs will be omitted when performing the calculation. + If insufficient data remains in the axis slice along which the + statistic is computed, the corresponding entry of the output will be + NaN. + - ``raise``: if a NaN is present, a ``ValueError`` will be raised. + + normalize : bool, optional + If ``False`` (the default), the return value is computed from the + above formula with the input scaled by ``(2*pi)/(high-low)`` and + the output scaled (back) by ``(high-low)/(2*pi)``. If ``True``, + the output is not scaled and is returned directly. + + Returns + ------- + circstd : float + Circular standard deviation, optionally normalized. + + If the input array is empty, ``np.nan`` is returned. + + See Also + -------- + circmean : Circular mean. + circvar : Circular variance. + + Notes + ----- + In the limit of small angles, the circular standard deviation is close + to the 'linear' standard deviation if ``normalize`` is ``False``. + + References + ---------- + .. [1] Mardia, K. V. (1972). 2. In *Statistics of Directional Data* + (pp. 18-24). Academic Press. :doi:`10.1016/C2013-0-07425-7`. + .. [2] Mardia, K. V. and Jupp, P. E. *Directional Statistics*. + John Wiley & Sons, 1999. + + Examples + -------- + >>> import numpy as np + >>> from scipy.stats import circstd + >>> import matplotlib.pyplot as plt + >>> samples_1 = np.array([0.072, -0.158, 0.077, 0.108, 0.286, + ... 0.133, -0.473, -0.001, -0.348, 0.131]) + >>> samples_2 = np.array([0.111, -0.879, 0.078, 0.733, 0.421, + ... 0.104, -0.136, -0.867, 0.012, 0.105]) + >>> circstd_1 = circstd(samples_1) + >>> circstd_2 = circstd(samples_2) + + Plot the samples. + + >>> fig, (left, right) = plt.subplots(ncols=2) + >>> for image in (left, right): + ... image.plot(np.cos(np.linspace(0, 2*np.pi, 500)), + ... np.sin(np.linspace(0, 2*np.pi, 500)), + ... c='k') + ... image.axis('equal') + ... image.axis('off') + >>> left.scatter(np.cos(samples_1), np.sin(samples_1), c='k', s=15) + >>> left.set_title(f"circular std: {np.round(circstd_1, 2)!r}") + >>> right.plot(np.cos(np.linspace(0, 2*np.pi, 500)), + ... np.sin(np.linspace(0, 2*np.pi, 500)), + ... c='k') + >>> right.scatter(np.cos(samples_2), np.sin(samples_2), c='k', s=15) + >>> right.set_title(f"circular std: {np.round(circstd_2, 2)!r}") + >>> plt.show() + + """ + xp = array_namespace(samples) + period = high - low + samples, sin_samp, cos_samp = _circfuncs_common(samples, period, xp=xp) + sin_mean = xp.mean(sin_samp, axis=axis) # [1] (2.2.3) + cos_mean = xp.mean(cos_samp, axis=axis) # [1] (2.2.3) + hypotenuse = (sin_mean**2. + cos_mean**2.)**0.5 + # hypotenuse can go slightly above 1 due to rounding errors + R = xp.clip(hypotenuse, max=1.) # [1] (2.2.4) + + res = (-2*xp.log(R))**0.5+0.0 # torch.pow returns -0.0 if R==1 + if not normalize: + res *= (high-low)/(2.*pi) # [1] (2.3.14) w/ (2.3.7) + return res + + +class DirectionalStats: + def __init__(self, mean_direction, mean_resultant_length): + self.mean_direction = mean_direction + self.mean_resultant_length = mean_resultant_length + + def __repr__(self): + return (f"DirectionalStats(mean_direction={self.mean_direction}," + f" mean_resultant_length={self.mean_resultant_length})") + + +@xp_capabilities(marray=True) +def directional_stats(samples, *, axis=0, normalize=True): + """ + Computes sample statistics for directional data. + + Computes the directional mean (also called the mean direction vector) and + mean resultant length of a sample of vectors. + + The directional mean is a measure of "preferred direction" of vector data. + It is analogous to the sample mean, but it is for use when the length of + the data is irrelevant (e.g. unit vectors). + + The mean resultant length is a value between 0 and 1 used to quantify the + dispersion of directional data: the smaller the mean resultant length, the + greater the dispersion. Several definitions of directional variance + involving the mean resultant length are given in [1]_ and [2]_. + + Parameters + ---------- + samples : array_like + Input array. Must be at least two-dimensional, and the last axis of the + input must correspond with the dimensionality of the vector space. + When the input is exactly two dimensional, this means that each row + of the data is a vector observation. + axis : int, default: 0 + Axis along which the directional mean is computed. + normalize : bool, default: True + If True, normalize the input to ensure that each observation is a + unit vector. It the observations are already unit vectors, consider + setting this to False to avoid unnecessary computation. + + Returns + ------- + res : DirectionalStats + An object containing attributes: + + mean_direction : ndarray + Directional mean. + mean_resultant_length : ndarray + The mean resultant length [1]_. + + See Also + -------- + circmean: circular mean; i.e. directional mean for 2D *angles* + circvar: circular variance; i.e. directional variance for 2D *angles* + + Notes + ----- + This uses a definition of directional mean from [1]_. + Assuming the observations are unit vectors, the calculation is as follows. + + .. code-block:: python + + mean = samples.mean(axis=0) + mean_resultant_length = np.linalg.norm(mean) + mean_direction = mean / mean_resultant_length + + This definition is appropriate for *directional* data (i.e. vector data + for which the magnitude of each observation is irrelevant) but not + for *axial* data (i.e. vector data for which the magnitude and *sign* of + each observation is irrelevant). + + Several definitions of directional variance involving the mean resultant + length ``R`` have been proposed, including ``1 - R`` [1]_, ``1 - R**2`` + [2]_, and ``2 * (1 - R)`` [2]_. Rather than choosing one, this function + returns ``R`` as attribute `mean_resultant_length` so the user can compute + their preferred measure of dispersion. + + References + ---------- + .. [1] Mardia, Jupp. (2000). *Directional Statistics* + (p. 163). Wiley. + + .. [2] https://en.wikipedia.org/wiki/Directional_statistics + + Examples + -------- + >>> import numpy as np + >>> from scipy.stats import directional_stats + >>> data = np.array([[3, 4], # first observation, 2D vector space + ... [6, -8]]) # second observation + >>> dirstats = directional_stats(data) + >>> dirstats.mean_direction + array([1., 0.]) + + In contrast, the regular sample mean of the vectors would be influenced + by the magnitude of each observation. Furthermore, the result would not be + a unit vector. + + >>> data.mean(axis=0) + array([4.5, -2.]) + + An exemplary use case for `directional_stats` is to find a *meaningful* + center for a set of observations on a sphere, e.g. geographical locations. + + >>> data = np.array([[0.8660254, 0.5, 0.], + ... [0.8660254, -0.5, 0.]]) + >>> dirstats = directional_stats(data) + >>> dirstats.mean_direction + array([1., 0., 0.]) + + The regular sample mean on the other hand yields a result which does not + lie on the surface of the sphere. + + >>> data.mean(axis=0) + array([0.8660254, 0., 0.]) + + The function also returns the mean resultant length, which + can be used to calculate a directional variance. For example, using the + definition ``Var(z) = 1 - R`` from [2]_ where ``R`` is the + mean resultant length, we can calculate the directional variance of the + vectors in the above example as: + + >>> 1 - dirstats.mean_resultant_length + 0.13397459716167093 + """ + xp = array_namespace(samples) + samples = xp.asarray(samples) + + if samples.ndim < 2: + raise ValueError("samples must at least be two-dimensional. " + f"Instead samples has shape: {tuple(samples.shape)}") + samples = xp.moveaxis(samples, axis, 0) + + if is_marray(xp): + _xp = array_namespace(samples.mask) + mask = _xp.any(samples.mask, axis=-1, keepdims=True) + samples = xp.asarray(samples.data, mask=mask) + + if normalize: + vectornorms = xp_vector_norm(samples, axis=-1, keepdims=True, xp=xp) + samples = samples/vectornorms + mean = xp.mean(samples, axis=0) + mean_resultant_length = xp_vector_norm(mean, axis=-1, keepdims=True, xp=xp) + mean_direction = mean / mean_resultant_length + mrl = xp.squeeze(mean_resultant_length, axis=-1) + mean_resultant_length = mrl[()] if mrl.ndim == 0 else mrl + return DirectionalStats(mean_direction, mean_resultant_length) + + +@xp_capabilities(skip_backends=[('dask.array', "no take_along_axis")]) +def false_discovery_control(ps, *, axis=0, method='bh'): + """Adjust p-values to control the false discovery rate. + + The false discovery rate (FDR) is the expected proportion of rejected null + hypotheses that are actually true. + If the null hypothesis is rejected when the *adjusted* p-value falls below + a specified level, the false discovery rate is controlled at that level. + + Parameters + ---------- + ps : 1D array_like + The p-values to adjust. Elements must be real numbers between 0 and 1. + axis : int + The axis along which to perform the adjustment. The adjustment is + performed independently along each axis-slice. If `axis` is None, `ps` + is raveled before performing the adjustment. + method : {'bh', 'by'} + The false discovery rate control procedure to apply: ``'bh'`` is for + Benjamini-Hochberg [1]_ (Eq. 1), ``'by'`` is for Benjaminini-Yekutieli + [2]_ (Theorem 1.3). The latter is more conservative, but it is + guaranteed to control the FDR even when the p-values are not from + independent tests. + + Returns + ------- + ps_adusted : array_like + The adjusted p-values. If the null hypothesis is rejected where these + fall below a specified level, the false discovery rate is controlled + at that level. + + See Also + -------- + combine_pvalues + statsmodels.stats.multitest.multipletests + + Notes + ----- + In multiple hypothesis testing, false discovery control procedures tend to + offer higher power than familywise error rate control procedures (e.g. + Bonferroni correction [1]_). + + If the p-values correspond with independent tests (or tests with + "positive regression dependencies" [2]_), rejecting null hypotheses + corresponding with Benjamini-Hochberg-adjusted p-values below :math:`q` + controls the false discovery rate at a level less than or equal to + :math:`q m_0 / m`, where :math:`m_0` is the number of true null hypotheses + and :math:`m` is the total number of null hypotheses tested. The same is + true even for dependent tests when the p-values are adjusted accorded to + the more conservative Benjaminini-Yekutieli procedure. + + The adjusted p-values produced by this function are comparable to those + produced by the R function ``p.adjust`` and the statsmodels function + `statsmodels.stats.multitest.multipletests`. Please consider the latter + for more advanced methods of multiple comparison correction. + + References + ---------- + .. [1] Benjamini, Yoav, and Yosef Hochberg. "Controlling the false + discovery rate: a practical and powerful approach to multiple + testing." Journal of the Royal statistical society: series B + (Methodological) 57.1 (1995): 289-300. + + .. [2] Benjamini, Yoav, and Daniel Yekutieli. "The control of the false + discovery rate in multiple testing under dependency." Annals of + statistics (2001): 1165-1188. + + .. [3] TileStats. FDR - Benjamini-Hochberg explained - Youtube. + https://www.youtube.com/watch?v=rZKa4tW2NKs. + + .. [4] Neuhaus, Karl-Ludwig, et al. "Improved thrombolysis in acute + myocardial infarction with front-loaded administration of alteplase: + results of the rt-PA-APSAC patency study (TAPS)." Journal of the + American College of Cardiology 19.5 (1992): 885-891. + + Examples + -------- + We follow the example from [1]_. + + Thrombolysis with recombinant tissue-type plasminogen activator (rt-PA) + and anisoylated plasminogen streptokinase activator (APSAC) in + myocardial infarction has been proved to reduce mortality. [4]_ + investigated the effects of a new front-loaded administration of rt-PA + versus those obtained with a standard regimen of APSAC, in a randomized + multicentre trial in 421 patients with acute myocardial infarction. + + There were four families of hypotheses tested in the study, the last of + which was "cardiac and other events after the start of thrombolitic + treatment". FDR control may be desired in this family of hypotheses + because it would not be appropriate to conclude that the front-loaded + treatment is better if it is merely equivalent to the previous treatment. + + The p-values corresponding with the 15 hypotheses in this family were + + >>> ps = [0.0001, 0.0004, 0.0019, 0.0095, 0.0201, 0.0278, 0.0298, 0.0344, + ... 0.0459, 0.3240, 0.4262, 0.5719, 0.6528, 0.7590, 1.000] + + If the chosen significance level is 0.05, we may be tempted to reject the + null hypotheses for the tests corresponding with the first nine p-values, + as the first nine p-values fall below the chosen significance level. + However, this would ignore the problem of "multiplicity": if we fail to + correct for the fact that multiple comparisons are being performed, we + are more likely to incorrectly reject true null hypotheses. + + One approach to the multiplicity problem is to control the family-wise + error rate (FWER), that is, the rate at which the null hypothesis is + rejected when it is actually true. A common procedure of this kind is the + Bonferroni correction [1]_. We begin by multiplying the p-values by the + number of hypotheses tested. + + >>> import numpy as np + >>> np.array(ps) * len(ps) + array([1.5000e-03, 6.0000e-03, 2.8500e-02, 1.4250e-01, 3.0150e-01, + 4.1700e-01, 4.4700e-01, 5.1600e-01, 6.8850e-01, 4.8600e+00, + 6.3930e+00, 8.5785e+00, 9.7920e+00, 1.1385e+01, 1.5000e+01]) + + To control the FWER at 5%, we reject only the hypotheses corresponding + with adjusted p-values less than 0.05. In this case, only the hypotheses + corresponding with the first three p-values can be rejected. According to + [1]_, these three hypotheses concerned "allergic reaction" and "two + different aspects of bleeding." + + An alternative approach is to control the false discovery rate: the + expected fraction of rejected null hypotheses that are actually true. The + advantage of this approach is that it typically affords greater power: an + increased rate of rejecting the null hypothesis when it is indeed false. To + control the false discovery rate at 5%, we apply the Benjamini-Hochberg + p-value adjustment. + + >>> from scipy import stats + >>> stats.false_discovery_control(ps) + array([0.0015 , 0.003 , 0.0095 , 0.035625 , 0.0603 , + 0.06385714, 0.06385714, 0.0645 , 0.0765 , 0.486 , + 0.58118182, 0.714875 , 0.75323077, 0.81321429, 1. ]) + + Now, the first *four* adjusted p-values fall below 0.05, so we would reject + the null hypotheses corresponding with these *four* p-values. Rejection + of the fourth null hypothesis was particularly important to the original + study as it led to the conclusion that the new treatment had a + "substantially lower in-hospital mortality rate." + + For simplicity of exposition, the p-values in the example above were given in + sorted order, but this is not required; `false_discovery_control` returns + adjusted p-values in order corresponding with the input `ps`. + + >>> stats.false_discovery_control([0.5, 0.6, 0.1, 0.001]) + array([0.6 , 0.6 , 0.2 , 0.004]) + + """ + xp = array_namespace(ps) + + # Input Validation and Special Cases + ps = xp.asarray(ps) + + if not xp.isdtype(ps.dtype, ("integral", "real floating")): + raise ValueError("`ps` must contain only real numbers.") + + if is_lazy_array(ps): + ps = xp.where((ps < 0.) | (ps > 1.), xp.nan, ps) + else: + if not xp.all(ps == xp.clip(ps, 0., 1.)): + raise ValueError("All values in `ps` must lie between 0. and 1.") + + methods = {'bh', 'by'} + if method.lower() not in methods: + raise ValueError(f"Unrecognized `method` '{method}'." + f"Method must be one of {methods}.") + method = method.lower() + + if axis is None: + axis = 0 + ps = xp_ravel(ps) + + axis = np.asarray(axis)[()] # use of NumPy for input validation is OK + if not np.issubdtype(axis.dtype, np.integer) or axis.size != 1: + raise ValueError("`axis` must be an integer or `None`") + axis = int(axis) + + if xp_size(ps) <= 1 or ps.shape[axis] <= 1: + return ps[()] if ps.ndim == 0 else ps + + ps = xp.moveaxis(ps, axis, -1) + m = ps.shape[-1] + + # Main Algorithm + # Equivalent to the ideas of [1] and [2], except that this adjusts the + # p-values as described in [3]. The results are similar to those produced + # by R's p.adjust. + + # "Let [ps] be the ordered observed p-values..." + order = xp.argsort(ps, axis=-1) + ps = xp.take_along_axis(ps, order, axis=-1) # this copies ps + + # Equation 1 of [1] rearranged to reject when p is less than specified q + i = xp.arange(1, m+1, dtype=ps.dtype, device=xp_device(ps)) + # ps *= m / i + ps = xpx.at(ps)[...].multiply(m / i) + + # Theorem 1.3 of [2] + if method == 'by': + # ps *= np.sum(1 / i) + ps = xpx.at(ps)[...].multiply(xp.sum(1 / i)) + + # accounts for rejecting all null hypotheses i for i < k, where k is + # defined in Eq. 1 of either [1] or [2]. See [3]. Starting with the index j + # of the second to last element, we replace element j with element j+1 if + # the latter is smaller. + if is_numpy(xp): + np.minimum.accumulate(ps[..., ::-1], out=ps[..., ::-1], axis=-1) + else: + n = ps.shape[-1] + for j in range(n-2, -1, -1): + # ps[..., j] = xp.minimum(ps[..., j], ps[..., j+1]) + ps = xpx.at(ps)[..., j].set(xp.minimum(ps[..., j], ps[..., j+1])) + + # Restore original order of axes and data + ps = _reorder_along_axis(ps, order, axis=-1, xp=xp) + ps = xp.moveaxis(ps, -1, axis) + + return xp.clip(ps, 0., 1.) + + +def _reorder_along_axis(x, i, *, axis, xp): + if is_jax(xp): + return xp.put_along_axis(x, i, values=x, axis=axis, inplace=False) + if hasattr(xp, 'put_along_axis'): + xp.put_along_axis(x, i, values=x.copy(), axis=axis) + return x + else: + return xp.take_along_axis(x, xp.argsort(i, axis=-1), axis=-1) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_mstats_basic.py b/venv/lib/python3.12/site-packages/scipy/stats/_mstats_basic.py new file mode 100644 index 0000000000000000000000000000000000000000..96517b32ae2a8865403d2a02ac9a64fbd3ea5805 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_mstats_basic.py @@ -0,0 +1,3833 @@ +""" +An extension of scipy.stats._stats_py to support masked arrays + +""" +# Original author (2007): Pierre GF Gerard-Marchant + + +__all__ = ['argstoarray', + 'count_tied_groups', + 'describe', + 'f_oneway', 'find_repeats','friedmanchisquare', + 'kendalltau','kendalltau_seasonal','kruskal','kruskalwallis', + 'ks_twosamp', 'ks_2samp', 'kurtosis', 'kurtosistest', + 'ks_1samp', 'kstest', + 'linregress', + 'mannwhitneyu', 'meppf','mode','moment','mquantiles','msign', + 'normaltest', + 'obrientransform', + 'pearsonr','plotting_positions','pointbiserialr', + 'rankdata', + 'scoreatpercentile','sem', + 'sen_seasonal_slopes','skew','skewtest','spearmanr', + 'siegelslopes', 'theilslopes', + 'tmax','tmean','tmin','trim','trimboth', + 'trimtail','trima','trimr','trimmed_mean','trimmed_std', + 'trimmed_stde','trimmed_var','tsem','ttest_1samp','ttest_onesamp', + 'ttest_ind','ttest_rel','tvar', + 'variation', + 'winsorize', + 'brunnermunzel', + ] + +import numpy as np +from numpy import ndarray +import numpy.ma as ma +from numpy.ma import masked, nomask +import math + +import itertools +import warnings +from collections import namedtuple + +from . import distributions +from scipy._lib._util import _rename_parameter, _contains_nan, _dedent_for_py313 +from scipy._lib._bunch import _make_tuple_bunch +import scipy.special as special +import scipy.stats._stats_py +import scipy.stats._stats_py as _stats_py +from scipy.stats._stats_pythran import siegelslopes as siegelslopes_pythran +from ._stats_mstats_common import _find_repeats, TheilslopesResult, SiegelslopesResult + + +def _chk_asarray(a, axis): + # Always returns a masked array, raveled for axis=None + a = ma.asanyarray(a) + if axis is None: + a = ma.ravel(a) + outaxis = 0 + else: + outaxis = axis + return a, outaxis + + +def _chk2_asarray(a, b, axis): + a = ma.asanyarray(a) + b = ma.asanyarray(b) + if axis is None: + a = ma.ravel(a) + b = ma.ravel(b) + outaxis = 0 + else: + outaxis = axis + return a, b, outaxis + + +def _chk_size(a, b): + a = ma.asanyarray(a) + b = ma.asanyarray(b) + (na, nb) = (a.size, b.size) + if na != nb: + raise ValueError("The size of the input array should match!" + f" ({na} <> {nb})") + return (a, b, na) + + +def _ttest_finish(df, t, alternative): + """Common code between all 3 t-test functions.""" + # We use ``stdtr`` directly here to preserve masked arrays + + if alternative == 'less': + pval = special._ufuncs.stdtr(df, t) + elif alternative == 'greater': + pval = special._ufuncs.stdtr(df, -t) + elif alternative == 'two-sided': + pval = special._ufuncs.stdtr(df, -np.abs(t))*2 + else: + raise ValueError("alternative must be " + "'less', 'greater' or 'two-sided'") + + if t.ndim == 0: + t = t[()] + if pval.ndim == 0: + pval = pval[()] + + return t, pval + + +def argstoarray(*args): + """ + Constructs a 2D array from a group of sequences. + + Sequences are filled with missing values to match the length of the longest + sequence. + + Parameters + ---------- + *args : sequences + Group of sequences. + + Returns + ------- + argstoarray : MaskedArray + A ( `m` x `n` ) masked array, where `m` is the number of arguments and + `n` the length of the longest argument. + + Notes + ----- + `numpy.ma.vstack` has identical behavior, but is called with a sequence + of sequences. + + Examples + -------- + A 2D masked array constructed from a group of sequences is returned. + + >>> from scipy.stats.mstats import argstoarray + >>> argstoarray([1, 2, 3], [4, 5, 6]) + masked_array( + data=[[1.0, 2.0, 3.0], + [4.0, 5.0, 6.0]], + mask=[[False, False, False], + [False, False, False]], + fill_value=1e+20) + + The returned masked array filled with missing values when the lengths of + sequences are different. + + >>> argstoarray([1, 3], [4, 5, 6]) + masked_array( + data=[[1.0, 3.0, --], + [4.0, 5.0, 6.0]], + mask=[[False, False, True], + [False, False, False]], + fill_value=1e+20) + + """ + if len(args) == 1 and not isinstance(args[0], ndarray): + output = ma.asarray(args[0]) + if output.ndim != 2: + raise ValueError("The input should be 2D") + else: + n = len(args) + m = max([len(k) for k in args]) + output = ma.array(np.empty((n,m), dtype=float), mask=True) + for (k,v) in enumerate(args): + output[k,:len(v)] = v + + output[np.logical_not(np.isfinite(output._data))] = masked + return output + + +def find_repeats(arr): + """Find repeats in arr and return a tuple (repeats, repeat_count). + + The input is cast to float64. Masked values are discarded. + + Parameters + ---------- + arr : sequence + Input array. The array is flattened if it is not 1D. + + Returns + ------- + repeats : ndarray + Array of repeated values. + counts : ndarray + Array of counts. + + Examples + -------- + >>> from scipy.stats import mstats + >>> mstats.find_repeats([2, 1, 2, 3, 2, 2, 5]) + (array([2.]), array([4])) + + In the above example, 2 repeats 4 times. + + >>> mstats.find_repeats([[10, 20, 1, 2], [5, 5, 4, 4]]) + (array([4., 5.]), array([2, 2])) + + In the above example, both 4 and 5 repeat 2 times. + + """ + # Make sure we get a copy. ma.compressed promises a "new array", but can + # actually return a reference. + compr = np.asarray(ma.compressed(arr), dtype=np.float64) + need_copy = np.may_share_memory(compr, arr) + if need_copy: + compr = compr.copy() + return _find_repeats(compr) + + +def count_tied_groups(x, use_missing=False): + """ + Counts the number of tied values. + + Parameters + ---------- + x : sequence + Sequence of data on which to counts the ties + use_missing : bool, optional + Whether to consider missing values as tied. + + Returns + ------- + count_tied_groups : dict + Returns a dictionary (nb of ties: nb of groups). + + Examples + -------- + >>> from scipy.stats import mstats + >>> import numpy as np + >>> z = [0, 0, 0, 2, 2, 2, 3, 3, 4, 5, 6] + >>> mstats.count_tied_groups(z) + {2: 1, 3: 2} + + In the above example, the ties were 0 (3x), 2 (3x) and 3 (2x). + + >>> z = np.ma.array([0, 0, 1, 2, 2, 2, 3, 3, 4, 5, 6]) + >>> mstats.count_tied_groups(z) + {2: 2, 3: 1} + >>> z[[1,-1]] = np.ma.masked + >>> mstats.count_tied_groups(z, use_missing=True) + {2: 2, 3: 1} + + """ + nmasked = ma.getmask(x).sum() + # We need the copy as find_repeats will overwrite the initial data + data = ma.compressed(x).copy() + (ties, counts) = find_repeats(data) + nties = {} + if len(ties): + nties = dict(zip(np.unique(counts), itertools.repeat(1))) + nties.update(dict(zip(*find_repeats(counts)))) + + if nmasked and use_missing: + try: + nties[nmasked] += 1 + except KeyError: + nties[nmasked] = 1 + + return nties + + +def rankdata(data, axis=None, use_missing=False): + """Returns the rank (also known as order statistics) of each data point + along the given axis. + + If some values are tied, their rank is averaged. + If some values are masked, their rank is set to 0 if use_missing is False, + or set to the average rank of the unmasked values if use_missing is True. + + Parameters + ---------- + data : sequence + Input data. The data is transformed to a masked array + axis : {None,int}, optional + Axis along which to perform the ranking. + If None, the array is first flattened. An exception is raised if + the axis is specified for arrays with a dimension larger than 2 + use_missing : bool, optional + Whether the masked values have a rank of 0 (False) or equal to the + average rank of the unmasked values (True). + + """ # numpydoc ignore=RT01 + def _rank1d(data, use_missing=False): + n = data.count() + rk = np.empty(data.size, dtype=float) + idx = data.argsort() + rk[idx[:n]] = np.arange(1,n+1) + + if use_missing: + rk[idx[n:]] = (n+1)/2. + else: + rk[idx[n:]] = 0 + + repeats = find_repeats(data.copy()) + for r in repeats[0]: + condition = (data == r).filled(False) + rk[condition] = rk[condition].mean() + return rk + + data = ma.array(data, copy=False) + if axis is None: + if data.ndim > 1: + return _rank1d(data.ravel(), use_missing).reshape(data.shape) + else: + return _rank1d(data, use_missing) + else: + return ma.apply_along_axis(_rank1d,axis,data,use_missing).view(ndarray) + + +ModeResult = namedtuple('ModeResult', ('mode', 'count')) + + +def mode(a, axis=0): + """ + Returns an array of the modal (most common) value in the passed array. + + Parameters + ---------- + a : array_like + n-dimensional array of which to find mode(s). + axis : int or None, optional + Axis along which to operate. Default is 0. If None, compute over + the whole array `a`. + + Returns + ------- + mode : ndarray + Array of modal values. + count : ndarray + Array of counts for each mode. + + Notes + ----- + For more details, see `scipy.stats.mode`. + + Examples + -------- + >>> import numpy as np + >>> from scipy import stats + >>> from scipy.stats import mstats + >>> m_arr = np.ma.array([1, 1, 0, 0, 0, 0], mask=[0, 0, 1, 1, 1, 0]) + >>> mstats.mode(m_arr) # note that most zeros are masked + ModeResult(mode=array([1.]), count=array([2.])) + + """ + return _mode(a, axis=axis, keepdims=True) + + +def _mode(a, axis=0, keepdims=True): + # Don't want to expose `keepdims` from the public `mstats.mode` + a, axis = _chk_asarray(a, axis) + + def _mode1D(a): + (rep,cnt) = find_repeats(a) + if not cnt.ndim: + return (0, 0) + elif cnt.size: + return (rep[cnt.argmax()], cnt.max()) + else: + return (a.min(), 1) + + if axis is None: + output = _mode1D(ma.ravel(a)) + output = (ma.array(output[0]), ma.array(output[1])) + else: + output = ma.apply_along_axis(_mode1D, axis, a) + if keepdims is None or keepdims: + newshape = list(a.shape) + newshape[axis] = 1 + slices = [slice(None)] * output.ndim + slices[axis] = 0 + modes = output[tuple(slices)].reshape(newshape) + slices[axis] = 1 + counts = output[tuple(slices)].reshape(newshape) + output = (modes, counts) + else: + output = np.moveaxis(output, axis, 0) + + return ModeResult(*output) + + +def _betai(a, b, x): + x = np.asanyarray(x) + x = ma.where(x < 1.0, x, 1.0) # if x > 1 then return 1.0 + return special.betainc(a, b, x) + + +def msign(x): + """Returns the sign of x, or 0 if x is masked.""" # numpydoc ignore=RT01 + return ma.filled(np.sign(x), 0) + + +def pearsonr(x, y): + r""" + Pearson correlation coefficient and p-value for testing non-correlation. + + The Pearson correlation coefficient [1]_ measures the linear relationship + between two datasets. The calculation of the p-value relies on the + assumption that each dataset is normally distributed. (See Kowalski [3]_ + for a discussion of the effects of non-normality of the input on the + distribution of the correlation coefficient.) Like other correlation + coefficients, this one varies between -1 and +1 with 0 implying no + correlation. Correlations of -1 or +1 imply an exact linear relationship. + + Parameters + ---------- + x : (N,) array_like + Input array. + y : (N,) array_like + Input array. + + Returns + ------- + r : float + Pearson's correlation coefficient. + p-value : float + Two-tailed p-value. + + Warns + ----- + `~scipy.stats.ConstantInputWarning` + Raised if an input is a constant array. The correlation coefficient + is not defined in this case, so ``np.nan`` is returned. + + `~scipy.stats.NearConstantInputWarning` + Raised if an input is "nearly" constant. The array ``x`` is considered + nearly constant if ``norm(x - mean(x)) < 1e-13 * abs(mean(x))``. + Numerical errors in the calculation ``x - mean(x)`` in this case might + result in an inaccurate calculation of r. + + See Also + -------- + spearmanr : Spearman rank-order correlation coefficient. + kendalltau : Kendall's tau, a correlation measure for ordinal data. + + Notes + ----- + The correlation coefficient is calculated as follows: + + .. math:: + + r = \frac{\sum (x - m_x) (y - m_y)} + {\sqrt{\sum (x - m_x)^2 \sum (y - m_y)^2}} + + where :math:`m_x` is the mean of the vector x and :math:`m_y` is + the mean of the vector y. + + Under the assumption that x and y are drawn from + independent normal distributions (so the population correlation coefficient + is 0), the probability density function of the sample correlation + coefficient r is ([1]_, [2]_): + + .. math:: + + f(r) = \frac{{(1-r^2)}^{n/2-2}}{\mathrm{B}(\frac{1}{2},\frac{n}{2}-1)} + + where n is the number of samples, and B is the beta function. This + is sometimes referred to as the exact distribution of r. This is + the distribution that is used in `pearsonr` to compute the p-value. + The distribution is a beta distribution on the interval [-1, 1], + with equal shape parameters a = b = n/2 - 1. In terms of SciPy's + implementation of the beta distribution, the distribution of r is:: + + dist = scipy.stats.beta(n/2 - 1, n/2 - 1, loc=-1, scale=2) + + The p-value returned by `pearsonr` is a two-sided p-value. The p-value + roughly indicates the probability of an uncorrelated system + producing datasets that have a Pearson correlation at least as extreme + as the one computed from these datasets. More precisely, for a + given sample with correlation coefficient r, the p-value is + the probability that abs(r') of a random sample x' and y' drawn from + the population with zero correlation would be greater than or equal + to abs(r). In terms of the object ``dist`` shown above, the p-value + for a given r and length n can be computed as:: + + p = 2*dist.cdf(-abs(r)) + + When n is 2, the above continuous distribution is not well-defined. + One can interpret the limit of the beta distribution as the shape + parameters a and b approach a = b = 0 as a discrete distribution with + equal probability masses at r = 1 and r = -1. More directly, one + can observe that, given the data x = [x1, x2] and y = [y1, y2], and + assuming x1 != x2 and y1 != y2, the only possible values for r are 1 + and -1. Because abs(r') for any sample x' and y' with length 2 will + be 1, the two-sided p-value for a sample of length 2 is always 1. + + References + ---------- + .. [1] "Pearson correlation coefficient", Wikipedia, + https://en.wikipedia.org/wiki/Pearson_correlation_coefficient + .. [2] Student, "Probable error of a correlation coefficient", + Biometrika, Volume 6, Issue 2-3, 1 September 1908, pp. 302-310. + .. [3] C. J. Kowalski, "On the Effects of Non-Normality on the Distribution + of the Sample Product-Moment Correlation Coefficient" + Journal of the Royal Statistical Society. Series C (Applied + Statistics), Vol. 21, No. 1 (1972), pp. 1-12. + + Examples + -------- + >>> import numpy as np + >>> from scipy import stats + >>> from scipy.stats import mstats + >>> mstats.pearsonr([1, 2, 3, 4, 5], [10, 9, 2.5, 6, 4]) + (-0.7426106572325057, 0.1505558088534455) + + There is a linear dependence between x and y if y = a + b*x + e, where + a,b are constants and e is a random error term, assumed to be independent + of x. For simplicity, assume that x is standard normal, a=0, b=1 and let + e follow a normal distribution with mean zero and standard deviation s>0. + + >>> s = 0.5 + >>> x = stats.norm.rvs(size=500) + >>> e = stats.norm.rvs(scale=s, size=500) + >>> y = x + e + >>> mstats.pearsonr(x, y) + (0.9029601878969703, 8.428978827629898e-185) # may vary + + This should be close to the exact value given by + + >>> 1/np.sqrt(1 + s**2) + 0.8944271909999159 + + For s=0.5, we observe a high level of correlation. In general, a large + variance of the noise reduces the correlation, while the correlation + approaches one as the variance of the error goes to zero. + + It is important to keep in mind that no correlation does not imply + independence unless (x, y) is jointly normal. Correlation can even be zero + when there is a very simple dependence structure: if X follows a + standard normal distribution, let y = abs(x). Note that the correlation + between x and y is zero. Indeed, since the expectation of x is zero, + cov(x, y) = E[x*y]. By definition, this equals E[x*abs(x)] which is zero + by symmetry. The following lines of code illustrate this observation: + + >>> y = np.abs(x) + >>> mstats.pearsonr(x, y) + (-0.016172891856853524, 0.7182823678751942) # may vary + + A non-zero correlation coefficient can be misleading. For example, if X has + a standard normal distribution, define y = x if x < 0 and y = 0 otherwise. + A simple calculation shows that corr(x, y) = sqrt(2/Pi) = 0.797..., + implying a high level of correlation: + + >>> y = np.where(x < 0, x, 0) + >>> mstats.pearsonr(x, y) + (0.8537091583771509, 3.183461621422181e-143) # may vary + + This is unintuitive since there is no dependence of x and y if x is larger + than zero which happens in about half of the cases if we sample x and y. + """ + (x, y, n) = _chk_size(x, y) + (x, y) = (x.ravel(), y.ravel()) + # Get the common mask and the total nb of unmasked elements + m = ma.mask_or(ma.getmask(x), ma.getmask(y)) + n -= m.sum() + df = n-2 + if df < 0: + return (masked, masked) + + return scipy.stats._stats_py.pearsonr( + ma.masked_array(x, mask=m).compressed(), + ma.masked_array(y, mask=m).compressed()) + + +def spearmanr(x, y=None, use_ties=True, axis=None, nan_policy='propagate', + alternative='two-sided'): + """ + Calculates a Spearman rank-order correlation coefficient and the p-value + to test for non-correlation. + + The Spearman correlation is a nonparametric measure of the linear + relationship between two datasets. Unlike the Pearson correlation, the + Spearman correlation does not assume that both datasets are normally + distributed. Like other correlation coefficients, this one varies + between -1 and +1 with 0 implying no correlation. Correlations of -1 or + +1 imply a monotonic relationship. Positive correlations imply that + as `x` increases, so does `y`. Negative correlations imply that as `x` + increases, `y` decreases. + + Missing values are discarded pair-wise: if a value is missing in `x`, the + corresponding value in `y` is masked. + + The p-value roughly indicates the probability of an uncorrelated system + producing datasets that have a Spearman correlation at least as extreme + as the one computed from these datasets. The p-values are not entirely + reliable but are probably reasonable for datasets larger than 500 or so. + + Parameters + ---------- + x, y : 1D or 2D array_like, y is optional + One or two 1-D or 2-D arrays containing multiple variables and + observations. When these are 1-D, each represents a vector of + observations of a single variable. For the behavior in the 2-D case, + see under ``axis``, below. + use_ties : bool, optional + DO NOT USE. Does not do anything, keyword is only left in place for + backwards compatibility reasons. + axis : int or None, optional + If axis=0 (default), then each column represents a variable, with + observations in the rows. If axis=1, the relationship is transposed: + each row represents a variable, while the columns contain observations. + If axis=None, then both arrays will be raveled. + nan_policy : {'propagate', 'raise', 'omit'}, optional + Defines how to handle when input contains nan. 'propagate' returns nan, + 'raise' throws an error, 'omit' performs the calculations ignoring nan + values. Default is 'propagate'. + alternative : {'two-sided', 'less', 'greater'}, optional + Defines the alternative hypothesis. Default is 'two-sided'. + The following options are available: + + * 'two-sided': the correlation is nonzero + * 'less': the correlation is negative (less than zero) + * 'greater': the correlation is positive (greater than zero) + + .. versionadded:: 1.7.0 + + Returns + ------- + res : SignificanceResult + An object containing attributes: + + statistic : float or ndarray (2-D square) + Spearman correlation matrix or correlation coefficient (if only 2 + variables are given as parameters). Correlation matrix is square + with length equal to total number of variables (columns or rows) in + ``a`` and ``b`` combined. + pvalue : float + The p-value for a hypothesis test whose null hypothesis + is that two sets of data are linearly uncorrelated. See + `alternative` above for alternative hypotheses. `pvalue` has the + same shape as `statistic`. + + References + ---------- + [CRCProbStat2000] section 14.7 + + """ + if not use_ties: + raise ValueError("`use_ties=False` is not supported in SciPy >= 1.2.0") + + # Always returns a masked array, raveled if axis=None + x, axisout = _chk_asarray(x, axis) + if y is not None: + # Deal only with 2-D `x` case. + y, _ = _chk_asarray(y, axis) + if axisout == 0: + x = ma.column_stack((x, y)) + else: + x = ma.vstack((x, y)) + + if axisout == 1: + # To simplify the code that follow (always use `n_obs, n_vars` shape) + x = x.T + + if nan_policy == 'omit': + x = ma.masked_invalid(x) + + def _spearmanr_2cols(x): + # Mask the same observations for all variables, and then drop those + # observations (can't leave them masked, rankdata is weird). + x = ma.mask_rowcols(x, axis=0) + x = x[~x.mask.any(axis=1), :] + + # If either column is entirely NaN or Inf + if not np.any(x.data): + res = scipy.stats._stats_py.SignificanceResult(np.nan, np.nan) + res.correlation = np.nan + return res + + m = ma.getmask(x) + n_obs = x.shape[0] + dof = n_obs - 2 - int(m.sum(axis=0)[0]) + if dof < 0: + raise ValueError("The input must have at least 3 entries!") + + # Gets the ranks and rank differences + x_ranked = rankdata(x, axis=0) + rs = ma.corrcoef(x_ranked, rowvar=False).data + + # rs can have elements equal to 1, so avoid zero division warnings + with np.errstate(divide='ignore'): + # clip the small negative values possibly caused by rounding + # errors before taking the square root + t = rs * np.sqrt((dof / ((rs+1.0) * (1.0-rs))).clip(0)) + + t, prob = _ttest_finish(dof, t, alternative) + + # For backwards compatibility, return scalars when comparing 2 columns + if rs.shape == (2, 2): + res = scipy.stats._stats_py.SignificanceResult(rs[1, 0], + prob[1, 0]) + res.correlation = rs[1, 0] + return res + else: + res = scipy.stats._stats_py.SignificanceResult(rs, prob) + res.correlation = rs + return res + + # Need to do this per pair of variables, otherwise the dropped observations + # in a third column mess up the result for a pair. + n_vars = x.shape[1] + if n_vars == 2: + return _spearmanr_2cols(x) + else: + rs = np.ones((n_vars, n_vars), dtype=float) + prob = np.zeros((n_vars, n_vars), dtype=float) + for var1 in range(n_vars - 1): + for var2 in range(var1+1, n_vars): + result = _spearmanr_2cols(x[:, [var1, var2]]) + rs[var1, var2] = result.correlation + rs[var2, var1] = result.correlation + prob[var1, var2] = result.pvalue + prob[var2, var1] = result.pvalue + + res = scipy.stats._stats_py.SignificanceResult(rs, prob) + res.correlation = rs + return res + + +def _kendall_p_exact(n, c, alternative='two-sided'): + + # Use the fact that distribution is symmetric: always calculate a CDF in + # the left tail. + # This will be the one-sided p-value if `c` is on the side of + # the null distribution predicted by the alternative hypothesis. + # The two-sided p-value will be twice this value. + # If `c` is on the other side of the null distribution, we'll need to + # take the complement and add back the probability mass at `c`. + in_right_tail = (c >= (n*(n-1))//2 - c) + alternative_greater = (alternative == 'greater') + c = int(min(c, (n*(n-1))//2 - c)) + + # Exact p-value, see Maurice G. Kendall, "Rank Correlation Methods" + # (4th Edition), Charles Griffin & Co., 1970. + if n <= 0: + raise ValueError(f'n ({n}) must be positive') + elif c < 0 or 4*c > n*(n-1): + raise ValueError(f'c ({c}) must satisfy 0 <= 4c <= n(n-1) = {n*(n-1)}.') + elif n == 1: + prob = 1.0 + p_mass_at_c = 1 + elif n == 2: + prob = 1.0 + p_mass_at_c = 0.5 + elif c == 0: + prob = 2.0/math.factorial(n) if n < 171 else 0.0 + p_mass_at_c = prob/2 + elif c == 1: + prob = 2.0/math.factorial(n-1) if n < 172 else 0.0 + p_mass_at_c = (n-1)/math.factorial(n) + elif 4*c == n*(n-1) and alternative == 'two-sided': + # I'm sure there's a simple formula for p_mass_at_c in this + # case, but I don't know it. Use generic formula for one-sided p-value. + prob = 1.0 + elif n < 171: + new = np.zeros(c+1) + new[0:2] = 1.0 + for j in range(3,n+1): + new = np.cumsum(new) + if j <= c: + new[j:] -= new[:c+1-j] + prob = 2.0*np.sum(new)/math.factorial(n) + p_mass_at_c = new[-1]/math.factorial(n) + else: + new = np.zeros(c+1) + new[0:2] = 1.0 + for j in range(3, n+1): + new = np.cumsum(new)/j + if j <= c: + new[j:] -= new[:c+1-j] + prob = np.sum(new) + p_mass_at_c = new[-1]/2 + + if alternative != 'two-sided': + # if the alternative hypothesis and alternative agree, + # one-sided p-value is half the two-sided p-value + if in_right_tail == alternative_greater: + prob /= 2 + else: + prob = 1 - prob/2 + p_mass_at_c + + prob = np.clip(prob, 0, 1) + + return prob + + +def kendalltau(x, y, use_ties=True, use_missing=False, method='auto', + alternative='two-sided'): + """ + Computes Kendall's rank correlation tau on two variables *x* and *y*. + + Parameters + ---------- + x : sequence + First data list (for example, time). + y : sequence + Second data list. + use_ties : {True, False}, optional + Whether ties correction should be performed. + use_missing : {False, True}, optional + Whether missing data should be allocated a rank of 0 (False) or the + average rank (True) + method : {'auto', 'asymptotic', 'exact'}, optional + Defines which method is used to calculate the p-value [1]_. + 'asymptotic' uses a normal approximation valid for large samples. + 'exact' computes the exact p-value, but can only be used if no ties + are present. As the sample size increases, the 'exact' computation + time may grow and the result may lose some precision. + 'auto' is the default and selects the appropriate + method based on a trade-off between speed and accuracy. + alternative : {'two-sided', 'less', 'greater'}, optional + Defines the alternative hypothesis. Default is 'two-sided'. + The following options are available: + + * 'two-sided': the rank correlation is nonzero + * 'less': the rank correlation is negative (less than zero) + * 'greater': the rank correlation is positive (greater than zero) + + Returns + ------- + res : SignificanceResult + An object containing attributes: + + statistic : float + The tau statistic. + pvalue : float + The p-value for a hypothesis test whose null hypothesis is + an absence of association, tau = 0. + + References + ---------- + .. [1] Maurice G. Kendall, "Rank Correlation Methods" (4th Edition), + Charles Griffin & Co., 1970. + + """ + (x, y, n) = _chk_size(x, y) + (x, y) = (x.flatten(), y.flatten()) + m = ma.mask_or(ma.getmask(x), ma.getmask(y)) + if m is not nomask: + x = ma.array(x, mask=m, copy=True) + y = ma.array(y, mask=m, copy=True) + # need int() here, otherwise numpy defaults to 32 bit + # integer on all Windows architectures, causing overflow. + # int() will keep it infinite precision. + n -= int(m.sum()) + + if n < 2: + res = scipy.stats._stats_py.SignificanceResult(np.nan, np.nan) + res.correlation = np.nan + return res + + rx = ma.masked_equal(rankdata(x, use_missing=use_missing), 0) + ry = ma.masked_equal(rankdata(y, use_missing=use_missing), 0) + idx = rx.argsort() + (rx, ry) = (rx[idx], ry[idx]) + C = np.sum([((ry[i+1:] > ry[i]) * (rx[i+1:] > rx[i])).filled(0).sum() + for i in range(len(ry)-1)], dtype=float) + D = np.sum([((ry[i+1:] < ry[i])*(rx[i+1:] > rx[i])).filled(0).sum() + for i in range(len(ry)-1)], dtype=float) + xties = count_tied_groups(x) + yties = count_tied_groups(y) + if use_ties: + corr_x = np.sum([v*k*(k-1) for (k,v) in xties.items()], dtype=float) + corr_y = np.sum([v*k*(k-1) for (k,v) in yties.items()], dtype=float) + denom = ma.sqrt((n*(n-1)-corr_x)/2. * (n*(n-1)-corr_y)/2.) + else: + denom = n*(n-1)/2. + tau = (C-D) / denom + + if method == 'exact' and (xties or yties): + raise ValueError("Ties found, exact method cannot be used.") + + if method == 'auto': + if (not xties and not yties) and (n <= 33 or min(C, n*(n-1)/2.0-C) <= 1): + method = 'exact' + else: + method = 'asymptotic' + + if not xties and not yties and method == 'exact': + prob = _kendall_p_exact(n, C, alternative) + + elif method == 'asymptotic': + var_s = n*(n-1)*(2*n+5) + if use_ties: + var_s -= np.sum([v*k*(k-1)*(2*k+5)*1. for (k,v) in xties.items()]) + var_s -= np.sum([v*k*(k-1)*(2*k+5)*1. for (k,v) in yties.items()]) + v1 = (np.sum([v*k*(k-1) for (k, v) in xties.items()], dtype=float) * + np.sum([v*k*(k-1) for (k, v) in yties.items()], dtype=float)) + v1 /= 2.*n*(n-1) + if n > 2: + v2 = np.sum([v*k*(k-1)*(k-2) for (k,v) in xties.items()], + dtype=float) * \ + np.sum([v*k*(k-1)*(k-2) for (k,v) in yties.items()], + dtype=float) + v2 /= 9.*n*(n-1)*(n-2) + else: + v2 = 0 + else: + v1 = v2 = 0 + + var_s /= 18. + var_s += (v1 + v2) + z = (C-D)/np.sqrt(var_s) + prob = scipy.stats._stats_py._get_pvalue(z, distributions.norm, alternative) + else: + raise ValueError("Unknown method "+str(method)+" specified, please " + "use auto, exact or asymptotic.") + + res = scipy.stats._stats_py.SignificanceResult(tau[()], prob[()]) + res.correlation = tau + return res + + +def kendalltau_seasonal(x): + """ + Computes a multivariate Kendall's rank correlation tau, for seasonal data. + + Parameters + ---------- + x : 2-D ndarray + Array of seasonal data, with seasons in columns. + + """ # numpydoc ignore=RT01 + x = ma.array(x, subok=True, copy=False, ndmin=2) + (n,m) = x.shape + n_p = x.count(0) + + S_szn = sum(msign(x[i:]-x[i]).sum(0) for i in range(n)) + S_tot = S_szn.sum() + + n_tot = x.count() + ties = count_tied_groups(x.compressed()) + corr_ties = sum(v*k*(k-1) for (k,v) in ties.items()) + denom_tot = ma.sqrt(1.*n_tot*(n_tot-1)*(n_tot*(n_tot-1)-corr_ties))/2. + + R = rankdata(x, axis=0, use_missing=True) + K = ma.empty((m,m), dtype=int) + covmat = ma.empty((m,m), dtype=float) + denom_szn = ma.empty(m, dtype=float) + for j in range(m): + ties_j = count_tied_groups(x[:,j].compressed()) + corr_j = sum(v*k*(k-1) for (k,v) in ties_j.items()) + cmb = n_p[j]*(n_p[j]-1) + for k in range(j,m,1): + K[j,k] = sum(msign((x[i:,j]-x[i,j])*(x[i:,k]-x[i,k])).sum() + for i in range(n)) + covmat[j,k] = (K[j,k] + 4*(R[:,j]*R[:,k]).sum() - + n*(n_p[j]+1)*(n_p[k]+1))/3. + K[k,j] = K[j,k] + covmat[k,j] = covmat[j,k] + + denom_szn[j] = ma.sqrt(cmb*(cmb-corr_j)) / 2. + + var_szn = covmat.diagonal() + + z_szn = msign(S_szn) * (abs(S_szn)-1) / ma.sqrt(var_szn) + z_tot_ind = msign(S_tot) * (abs(S_tot)-1) / ma.sqrt(var_szn.sum()) + z_tot_dep = msign(S_tot) * (abs(S_tot)-1) / ma.sqrt(covmat.sum()) + + prob_szn = special.erfc(abs(z_szn.data)/np.sqrt(2)) + prob_tot_ind = special.erfc(abs(z_tot_ind)/np.sqrt(2)) + prob_tot_dep = special.erfc(abs(z_tot_dep)/np.sqrt(2)) + + chi2_tot = (z_szn*z_szn).sum() + chi2_trd = m * z_szn.mean()**2 + output = {'seasonal tau': S_szn/denom_szn, + 'global tau': S_tot/denom_tot, + 'global tau (alt)': S_tot/denom_szn.sum(), + 'seasonal p-value': prob_szn, + 'global p-value (indep)': prob_tot_ind, + 'global p-value (dep)': prob_tot_dep, + 'chi2 total': chi2_tot, + 'chi2 trend': chi2_trd, + } + return output + + +PointbiserialrResult = namedtuple('PointbiserialrResult', ('correlation', + 'pvalue')) + + +def pointbiserialr(x, y): + """Calculates a point biserial correlation coefficient and its p-value. + + Parameters + ---------- + x : array_like of bools + Input array. + y : array_like + Input array. + + Returns + ------- + correlation : float + R value + pvalue : float + 2-tailed p-value + + Notes + ----- + Missing values are considered pair-wise: if a value is missing in x, + the corresponding value in y is masked. + + For more details on `pointbiserialr`, see `scipy.stats.pointbiserialr`. + + """ + x = ma.fix_invalid(x, copy=True).astype(bool) + y = ma.fix_invalid(y, copy=True).astype(float) + # Get rid of the missing data + m = ma.mask_or(ma.getmask(x), ma.getmask(y)) + if m is not nomask: + unmask = np.logical_not(m) + x = x[unmask] + y = y[unmask] + + n = len(x) + # phat is the fraction of x values that are True + phat = x.sum() / float(n) + y0 = y[~x] # y-values where x is False + y1 = y[x] # y-values where x is True + y0m = y0.mean() + y1m = y1.mean() + + rpb = (y1m - y0m)*np.sqrt(phat * (1-phat)) / y.std() + + df = n-2 + t = rpb*ma.sqrt(df/(1.0-rpb**2)) + prob = _betai(0.5*df, 0.5, df/(df+t*t)) + + return PointbiserialrResult(rpb, prob) + + +def linregress(x, y=None): + r""" + Calculate a linear least-squares regression for two sets of measurements. + + Parameters + ---------- + x, y : array_like + Two sets of measurements. Both arrays should have the same length N. If + only `x` is given (and ``y=None``), then it must be a two-dimensional + array where one dimension has length 2. The two sets of measurements + are then found by splitting the array along the length-2 dimension. In + the case where ``y=None`` and `x` is a 2xN array, ``linregress(x)`` is + equivalent to ``linregress(x[0], x[1])``. + + Returns + ------- + result : ``LinregressResult`` instance + The return value is an object with the following attributes: + + slope : float + Slope of the regression line. + intercept : float + Intercept of the regression line. + rvalue : float + The Pearson correlation coefficient. The square of ``rvalue`` + is equal to the coefficient of determination. + pvalue : float + The p-value for a hypothesis test whose null hypothesis is + that the slope is zero, using Wald Test with t-distribution of + the test statistic. See `alternative` above for alternative + hypotheses. + stderr : float + Standard error of the estimated slope (gradient), under the + assumption of residual normality. + intercept_stderr : float + Standard error of the estimated intercept, under the assumption + of residual normality. + + See Also + -------- + scipy.optimize.curve_fit : + Use non-linear least squares to fit a function to data. + scipy.optimize.leastsq : + Minimize the sum of squares of a set of equations. + + Notes + ----- + Missing values are considered pair-wise: if a value is missing in `x`, + the corresponding value in `y` is masked. + + For compatibility with older versions of SciPy, the return value acts + like a ``namedtuple`` of length 5, with fields ``slope``, ``intercept``, + ``rvalue``, ``pvalue`` and ``stderr``, so one can continue to write:: + + slope, intercept, r, p, se = linregress(x, y) + + With that style, however, the standard error of the intercept is not + available. To have access to all the computed values, including the + standard error of the intercept, use the return value as an object + with attributes, e.g.:: + + result = linregress(x, y) + print(result.intercept, result.intercept_stderr) + + Examples + -------- + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from scipy import stats + >>> rng = np.random.default_rng() + + Generate some data: + + >>> x = rng.random(10) + >>> y = 1.6*x + rng.random(10) + + Perform the linear regression: + + >>> res = stats.mstats.linregress(x, y) + + Coefficient of determination (R-squared): + + >>> print(f"R-squared: {res.rvalue**2:.6f}") + R-squared: 0.717533 + + Plot the data along with the fitted line: + + >>> plt.plot(x, y, 'o', label='original data') + >>> plt.plot(x, res.intercept + res.slope*x, 'r', label='fitted line') + >>> plt.legend() + >>> plt.show() + + Calculate 95% confidence interval on slope and intercept: + + >>> # Two-sided inverse Students t-distribution + >>> # p - probability, df - degrees of freedom + >>> from scipy.stats import t + >>> tinv = lambda p, df: abs(t.ppf(p/2, df)) + + >>> ts = tinv(0.05, len(x)-2) + >>> print(f"slope (95%): {res.slope:.6f} +/- {ts*res.stderr:.6f}") + slope (95%): 1.453392 +/- 0.743465 + >>> print(f"intercept (95%): {res.intercept:.6f}" + ... f" +/- {ts*res.intercept_stderr:.6f}") + intercept (95%): 0.616950 +/- 0.544475 + + """ + if y is None: + x = ma.array(x) + if x.shape[0] == 2: + x, y = x + elif x.shape[1] == 2: + x, y = x.T + else: + raise ValueError("If only `x` is given as input, " + "it has to be of shape (2, N) or (N, 2), " + f"provided shape was {x.shape}") + else: + x = ma.array(x) + y = ma.array(y) + + x = x.flatten() + y = y.flatten() + + if np.amax(x) == np.amin(x) and len(x) > 1: + raise ValueError("Cannot calculate a linear regression " + "if all x values are identical") + + m = ma.mask_or(ma.getmask(x), ma.getmask(y), shrink=False) + if m is not nomask: + x = ma.array(x, mask=m) + y = ma.array(y, mask=m) + if np.any(~m): + result = _stats_py.linregress(x.data[~m], y.data[~m]) + else: + # All data is masked + result = _stats_py.LinregressResult(slope=None, intercept=None, + rvalue=None, pvalue=None, + stderr=None, + intercept_stderr=None) + else: + result = _stats_py.linregress(x.data, y.data) + + return result + + +def _theilslopes(y, x=None, alpha=0.95, method='separate'): + r""" + Computes the Theil-Sen estimator for a set of points (x, y). + + `theilslopes` implements a method for robust linear regression. It + computes the slope as the median of all slopes between paired values. + + Parameters + ---------- + y : array_like + Dependent variable. + x : array_like or None, optional + Independent variable. If None, use ``arange(len(y))`` instead. + alpha : float, optional + Confidence degree between 0 and 1. Default is 95% confidence. + Note that `alpha` is symmetric around 0.5, i.e. both 0.1 and 0.9 are + interpreted as "find the 90% confidence interval". + method : {'joint', 'separate'}, optional + Method to be used for computing estimate for intercept. + Following methods are supported, + + * 'joint': Uses np.median(y - slope * x) as intercept. + * 'separate': Uses np.median(y) - slope * np.median(x) + as intercept. + + The default is 'separate'. + + .. versionadded:: 1.8.0 + + Returns + ------- + result : ``TheilslopesResult`` instance + The return value is an object with the following attributes: + + slope : float + Theil slope. + intercept : float + Intercept of the Theil line. + low_slope : float + Lower bound of the confidence interval on `slope`. + high_slope : float + Upper bound of the confidence interval on `slope`. + + See Also + -------- + siegelslopes : a similar technique using repeated medians + + Notes + ----- + The implementation of `theilslopes` follows [1]_. The intercept is + not defined in [1]_, and here it is defined as ``median(y) - + slope*median(x)``, which is given in [3]_. Other definitions of + the intercept exist in the literature such as ``median(y - slope*x)`` + in [4]_. The approach to compute the intercept can be determined by the + parameter ``method``. A confidence interval for the intercept is not + given as this question is not addressed in [1]_. + + For compatibility with older versions of SciPy, the return value acts + like a ``namedtuple`` of length 4, with fields ``slope``, ``intercept``, + ``low_slope``, and ``high_slope``, so one can continue to write:: + + slope, intercept, low_slope, high_slope = theilslopes(y, x) + + References + ---------- + .. [1] P.K. Sen, "Estimates of the regression coefficient based on + Kendall's tau", J. Am. Stat. Assoc., Vol. 63, pp. 1379-1389, 1968. + .. [2] H. Theil, "A rank-invariant method of linear and polynomial + regression analysis I, II and III", Nederl. Akad. Wetensch., Proc. + 53:, pp. 386-392, pp. 521-525, pp. 1397-1412, 1950. + .. [3] W.L. Conover, "Practical nonparametric statistics", 2nd ed., + John Wiley and Sons, New York, pp. 493. + .. [4] https://en.wikipedia.org/wiki/Theil%E2%80%93Sen_estimator + + Examples + -------- + >>> import numpy as np + >>> from scipy import stats + >>> import matplotlib.pyplot as plt + + >>> x = np.linspace(-5, 5, num=150) + >>> y = x + np.random.normal(size=x.size) + >>> y[11:15] += 10 # add outliers + >>> y[-5:] -= 7 + + Compute the slope, intercept and 90% confidence interval. For comparison, + also compute the least-squares fit with `linregress`: + + >>> res = stats.theilslopes(y, x, 0.90, method='separate') + >>> lsq_res = stats.linregress(x, y) + + Plot the results. The Theil-Sen regression line is shown in red, with the + dashed red lines illustrating the confidence interval of the slope (note + that the dashed red lines are not the confidence interval of the regression + as the confidence interval of the intercept is not included). The green + line shows the least-squares fit for comparison. + + >>> fig = plt.figure() + >>> ax = fig.add_subplot(111) + >>> ax.plot(x, y, 'b.') + >>> ax.plot(x, res[1] + res[0] * x, 'r-') + >>> ax.plot(x, res[1] + res[2] * x, 'r--') + >>> ax.plot(x, res[1] + res[3] * x, 'r--') + >>> ax.plot(x, lsq_res[1] + lsq_res[0] * x, 'g-') + >>> plt.show() + + """ + if method not in ['joint', 'separate']: + raise ValueError("method must be either 'joint' or 'separate'." + f"'{method}' is invalid.") + # We copy both x and y so we can use _find_repeats. + y = np.array(y, dtype=float, copy=True).ravel() + if x is None: + x = np.arange(len(y), dtype=float) + else: + x = np.array(x, dtype=float, copy=True).ravel() + if len(x) != len(y): + raise ValueError("Array shapes are incompatible for broadcasting.") + if len(x) < 2: + raise ValueError("`x` and `y` must have length at least 2.") + + # Compute sorted slopes only when deltax > 0 + deltax = x[:, np.newaxis] - x + deltay = y[:, np.newaxis] - y + slopes = deltay[deltax > 0] / deltax[deltax > 0] + if not slopes.size: + msg = "All `x` coordinates are identical." + warnings.warn(msg, RuntimeWarning, stacklevel=2) + slopes.sort() + medslope = np.median(slopes) + if method == 'joint': + medinter = np.median(y - medslope * x) + else: + medinter = np.median(y) - medslope * np.median(x) + # Now compute confidence intervals + if alpha > 0.5: + alpha = 1. - alpha + + z = distributions.norm.ppf(alpha / 2.) + # This implements (2.6) from Sen (1968) + _, nxreps = _find_repeats(x) + _, nyreps = _find_repeats(y) + nt = len(slopes) # N in Sen (1968) + ny = len(y) # n in Sen (1968) + # Equation 2.6 in Sen (1968): + sigsq = 1/18. * (ny * (ny-1) * (2*ny+5) - + sum(k * (k-1) * (2*k + 5) for k in nxreps) - + sum(k * (k-1) * (2*k + 5) for k in nyreps)) + # Find the confidence interval indices in `slopes` + try: + sigma = np.sqrt(sigsq) + Ru = min(int(np.round((nt - z*sigma)/2.)), len(slopes)-1) + Rl = max(int(np.round((nt + z*sigma)/2.)) - 1, 0) + delta = slopes[[Rl, Ru]] + except (ValueError, IndexError): + delta = (np.nan, np.nan) + + return TheilslopesResult(slope=medslope, intercept=medinter, + low_slope=delta[0], high_slope=delta[1]) + + +def theilslopes(y, x=None, alpha=0.95, method='separate'): + r""" + Computes the Theil-Sen estimator for a set of points (x, y). + + `theilslopes` implements a method for robust linear regression. It + computes the slope as the median of all slopes between paired values. + + Parameters + ---------- + y : array_like + Dependent variable. + x : array_like or None, optional + Independent variable. If None, use ``arange(len(y))`` instead. + alpha : float, optional + Confidence degree between 0 and 1. Default is 95% confidence. + Note that `alpha` is symmetric around 0.5, i.e. both 0.1 and 0.9 are + interpreted as "find the 90% confidence interval". + method : {'joint', 'separate'}, optional + Method to be used for computing estimate for intercept. + Following methods are supported, + + * 'joint': Uses np.median(y - slope * x) as intercept. + * 'separate': Uses np.median(y) - slope * np.median(x) + as intercept. + + The default is 'separate'. + + .. versionadded:: 1.8.0 + + Returns + ------- + result : ``TheilslopesResult`` instance + The return value is an object with the following attributes: + + slope : float + Theil slope. + intercept : float + Intercept of the Theil line. + low_slope : float + Lower bound of the confidence interval on `slope`. + high_slope : float + Upper bound of the confidence interval on `slope`. + + See Also + -------- + siegelslopes : a similar technique using repeated medians + + + Notes + ----- + For more details on `theilslopes`, see `scipy.stats.theilslopes`. + + """ + y = ma.asarray(y).flatten() + if x is None: + x = ma.arange(len(y), dtype=float) + else: + x = ma.asarray(x).flatten() + if len(x) != len(y): + raise ValueError(f"Incompatible lengths ! ({len(y)}<>{len(x)})") + + m = ma.mask_or(ma.getmask(x), ma.getmask(y)) + y._mask = x._mask = m + # Disregard any masked elements of x or y + y = y.compressed() + x = x.compressed().astype(float) + # We now have unmasked arrays so can use `scipy.stats.theilslopes` + return _theilslopes(y, x, alpha=alpha, method=method) + + +def _siegelslopes(y, x=None, method="hierarchical"): + if method not in ['hierarchical', 'separate']: + raise ValueError("method can only be 'hierarchical' or 'separate'") + y = np.asarray(y).ravel() + if x is None: + x = np.arange(len(y), dtype=float) + else: + x = np.asarray(x, dtype=float).ravel() + if len(x) != len(y): + raise ValueError("Array shapes are incompatible for broadcasting.") + if len(x) < 2: + raise ValueError("`x` and `y` must have length at least 2.") + + dtype = np.result_type(x, y, np.float32) # use at least float32 + y, x = y.astype(dtype), x.astype(dtype) + medslope, medinter = siegelslopes_pythran(y, x, method) + medslope, medinter = np.asarray(medslope)[()], np.asarray(medinter)[()] + return SiegelslopesResult(slope=medslope, intercept=medinter) + + +def siegelslopes(y, x=None, method="hierarchical"): + r""" + Computes the Siegel estimator for a set of points (x, y). + + `siegelslopes` implements a method for robust linear regression + using repeated medians to fit a line to the points (x, y). + The method is robust to outliers with an asymptotic breakdown point + of 50%. + + Parameters + ---------- + y : array_like + Dependent variable. + x : array_like or None, optional + Independent variable. If None, use ``arange(len(y))`` instead. + method : {'hierarchical', 'separate'} + If 'hierarchical', estimate the intercept using the estimated + slope ``slope`` (default option). + If 'separate', estimate the intercept independent of the estimated + slope. See Notes for details. + + Returns + ------- + result : ``SiegelslopesResult`` instance + The return value is an object with the following attributes: + + slope : float + Estimate of the slope of the regression line. + intercept : float + Estimate of the intercept of the regression line. + + See Also + -------- + theilslopes : a similar technique without repeated medians + + Notes + ----- + For more details on `siegelslopes`, see `scipy.stats.siegelslopes`. + + """ + y = ma.asarray(y).ravel() + if x is None: + x = ma.arange(len(y), dtype=float) + else: + x = ma.asarray(x).ravel() + if len(x) != len(y): + raise ValueError(f"Incompatible lengths ! ({len(y)}<>{len(x)})") + + m = ma.mask_or(ma.getmask(x), ma.getmask(y)) + y._mask = x._mask = m + # Disregard any masked elements of x or y + y = y.compressed() + x = x.compressed().astype(float) + # We now have unmasked arrays so can use `scipy.stats.siegelslopes` + return _siegelslopes(y, x, method=method) + + +SenSeasonalSlopesResult = _make_tuple_bunch('SenSeasonalSlopesResult', + ['intra_slope', 'inter_slope']) + + +def sen_seasonal_slopes(x): + r""" + Computes seasonal Theil-Sen and Kendall slope estimators. + + The seasonal generalization of Sen's slope computes the slopes between all + pairs of values within a "season" (column) of a 2D array. It returns an + array containing the median of these "within-season" slopes for each + season (the Theil-Sen slope estimator of each season), and it returns the + median of the within-season slopes across all seasons (the seasonal Kendall + slope estimator). + + Parameters + ---------- + x : 2D array_like + Each column of `x` contains measurements of the dependent variable + within a season. The independent variable (usually time) of each season + is assumed to be ``np.arange(x.shape[0])``. + + Returns + ------- + result : ``SenSeasonalSlopesResult`` instance + The return value is an object with the following attributes: + + intra_slope : ndarray + For each season, the Theil-Sen slope estimator: the median of + within-season slopes. + inter_slope : float + The seasonal Kendall slope estimator: the median of within-season + slopes *across all* seasons. + + See Also + -------- + theilslopes : the analogous function for non-seasonal data + scipy.stats.theilslopes : non-seasonal slopes for non-masked arrays + + Notes + ----- + The slopes :math:`d_{ijk}` within season :math:`i` are: + + .. math:: + + d_{ijk} = \frac{x_{ij} - x_{ik}} + {j - k} + + for pairs of distinct integer indices :math:`j, k` of :math:`x`. + + Element :math:`i` of the returned `intra_slope` array is the median of the + :math:`d_{ijk}` over all :math:`j < k`; this is the Theil-Sen slope + estimator of season :math:`i`. The returned `inter_slope` value, better + known as the seasonal Kendall slope estimator, is the median of the + :math:`d_{ijk}` over all :math:`i, j, k`. + + References + ---------- + .. [1] Hirsch, Robert M., James R. Slack, and Richard A. Smith. + "Techniques of trend analysis for monthly water quality data." + *Water Resources Research* 18.1 (1982): 107-121. + + Examples + -------- + Suppose we have 100 observations of a dependent variable for each of four + seasons: + + >>> import numpy as np + >>> rng = np.random.default_rng() + >>> x = rng.random(size=(100, 4)) + + We compute the seasonal slopes as: + + >>> from scipy import stats + >>> intra_slope, inter_slope = stats.mstats.sen_seasonal_slopes(x) + + If we define a function to compute all slopes between observations within + a season: + + >>> def dijk(yi): + ... n = len(yi) + ... x = np.arange(n) + ... dy = yi - yi[:, np.newaxis] + ... dx = x - x[:, np.newaxis] + ... # we only want unique pairs of distinct indices + ... mask = np.triu(np.ones((n, n), dtype=bool), k=1) + ... return dy[mask]/dx[mask] + + then element ``i`` of ``intra_slope`` is the median of ``dijk[x[:, i]]``: + + >>> i = 2 + >>> np.allclose(np.median(dijk(x[:, i])), intra_slope[i]) + True + + and ``inter_slope`` is the median of the values returned by ``dijk`` for + all seasons: + + >>> all_slopes = np.concatenate([dijk(x[:, i]) for i in range(x.shape[1])]) + >>> np.allclose(np.median(all_slopes), inter_slope) + True + + Because the data are randomly generated, we would expect the median slopes + to be nearly zero both within and across all seasons, and indeed they are: + + >>> intra_slope.data + array([ 0.00124504, -0.00277761, -0.00221245, -0.00036338]) + >>> inter_slope + -0.0010511779872922058 + + """ + x = ma.array(x, subok=True, copy=False, ndmin=2) + (n,_) = x.shape + # Get list of slopes per season + szn_slopes = ma.vstack([(x[i+1:]-x[i])/np.arange(1,n-i)[:,None] + for i in range(n)]) + szn_medslopes = ma.median(szn_slopes, axis=0) + medslope = ma.median(szn_slopes, axis=None) + return SenSeasonalSlopesResult(szn_medslopes, medslope) + + +Ttest_1sampResult = namedtuple('Ttest_1sampResult', ('statistic', 'pvalue')) + + +def ttest_1samp(a, popmean, axis=0, alternative='two-sided'): + """ + Calculates the T-test for the mean of ONE group of scores. + + Parameters + ---------- + a : array_like + sample observation + popmean : float or array_like + expected value in null hypothesis, if array_like than it must have the + same shape as `a` excluding the axis dimension + axis : int or None, optional + Axis along which to compute test. If None, compute over the whole + array `a`. + alternative : {'two-sided', 'less', 'greater'}, optional + Defines the alternative hypothesis. + The following options are available (default is 'two-sided'): + + * 'two-sided': the mean of the underlying distribution of the sample + is different than the given population mean (`popmean`) + * 'less': the mean of the underlying distribution of the sample is + less than the given population mean (`popmean`) + * 'greater': the mean of the underlying distribution of the sample is + greater than the given population mean (`popmean`) + + .. versionadded:: 1.7.0 + + Returns + ------- + statistic : float or array + t-statistic + pvalue : float or array + The p-value + + Notes + ----- + For more details on `ttest_1samp`, see `scipy.stats.ttest_1samp`. + + """ + a, axis = _chk_asarray(a, axis) + if a.size == 0: + return (np.nan, np.nan) + + x = a.mean(axis=axis) + v = a.var(axis=axis, ddof=1) + n = a.count(axis=axis) + # force df to be an array for masked division not to throw a warning + df = ma.asanyarray(n - 1.0) + svar = ((n - 1.0) * v) / df + with np.errstate(divide='ignore', invalid='ignore'): + t = (x - popmean) / ma.sqrt(svar / n) + + t, prob = _ttest_finish(df, t, alternative) + return Ttest_1sampResult(t, prob) + + +ttest_onesamp = ttest_1samp + + +Ttest_indResult = namedtuple('Ttest_indResult', ('statistic', 'pvalue')) + + +def ttest_ind(a, b, axis=0, equal_var=True, alternative='two-sided'): + """ + Calculates the T-test for the means of TWO INDEPENDENT samples of scores. + + Parameters + ---------- + a, b : array_like + The arrays must have the same shape, except in the dimension + corresponding to `axis` (the first, by default). + axis : int or None, optional + Axis along which to compute test. If None, compute over the whole + arrays, `a`, and `b`. + equal_var : bool, optional + If True, perform a standard independent 2 sample test that assumes equal + population variances. + If False, perform Welch's t-test, which does not assume equal population + variance. + + .. versionadded:: 0.17.0 + alternative : {'two-sided', 'less', 'greater'}, optional + Defines the alternative hypothesis. + The following options are available (default is 'two-sided'): + + * 'two-sided': the means of the distributions underlying the samples + are unequal. + * 'less': the mean of the distribution underlying the first sample + is less than the mean of the distribution underlying the second + sample. + * 'greater': the mean of the distribution underlying the first + sample is greater than the mean of the distribution underlying + the second sample. + + .. versionadded:: 1.7.0 + + Returns + ------- + statistic : float or array + The calculated t-statistic. + pvalue : float or array + The p-value. + + Notes + ----- + For more details on `ttest_ind`, see `scipy.stats.ttest_ind`. + + """ + a, b, axis = _chk2_asarray(a, b, axis) + + if a.size == 0 or b.size == 0: + return Ttest_indResult(np.nan, np.nan) + + (x1, x2) = (a.mean(axis), b.mean(axis)) + (v1, v2) = (a.var(axis=axis, ddof=1), b.var(axis=axis, ddof=1)) + (n1, n2) = (a.count(axis), b.count(axis)) + + if equal_var: + # force df to be an array for masked division not to throw a warning + df = ma.asanyarray(n1 + n2 - 2.0) + svar = ((n1-1)*v1+(n2-1)*v2) / df + denom = ma.sqrt(svar*(1.0/n1 + 1.0/n2)) # n-D computation here! + else: + vn1 = v1/n1 + vn2 = v2/n2 + with np.errstate(divide='ignore', invalid='ignore'): + df = (vn1 + vn2)**2 / (vn1**2 / (n1 - 1) + vn2**2 / (n2 - 1)) + + # If df is undefined, variances are zero. + # It doesn't matter what df is as long as it is not NaN. + df = np.where(np.isnan(df), 1, df) + denom = ma.sqrt(vn1 + vn2) + + with np.errstate(divide='ignore', invalid='ignore'): + t = (x1-x2) / denom + + t, prob = _ttest_finish(df, t, alternative) + return Ttest_indResult(t, prob) + + +Ttest_relResult = namedtuple('Ttest_relResult', ('statistic', 'pvalue')) + + +def ttest_rel(a, b, axis=0, alternative='two-sided'): + """ + Calculates the T-test on TWO RELATED samples of scores, a and b. + + Parameters + ---------- + a, b : array_like + The arrays must have the same shape. + axis : int or None, optional + Axis along which to compute test. If None, compute over the whole + arrays, `a`, and `b`. + alternative : {'two-sided', 'less', 'greater'}, optional + Defines the alternative hypothesis. + The following options are available (default is 'two-sided'): + + * 'two-sided': the means of the distributions underlying the samples + are unequal. + * 'less': the mean of the distribution underlying the first sample + is less than the mean of the distribution underlying the second + sample. + * 'greater': the mean of the distribution underlying the first + sample is greater than the mean of the distribution underlying + the second sample. + + .. versionadded:: 1.7.0 + + Returns + ------- + statistic : float or array + t-statistic + pvalue : float or array + two-tailed p-value + + Notes + ----- + For more details on `ttest_rel`, see `scipy.stats.ttest_rel`. + + """ + a, b, axis = _chk2_asarray(a, b, axis) + if len(a) != len(b): + raise ValueError('unequal length arrays') + + if a.size == 0 or b.size == 0: + return Ttest_relResult(np.nan, np.nan) + + n = a.count(axis) + df = ma.asanyarray(n-1.0) + d = (a-b).astype('d') + dm = d.mean(axis) + v = d.var(axis=axis, ddof=1) + denom = ma.sqrt(v / n) + with np.errstate(divide='ignore', invalid='ignore'): + t = dm / denom + + t, prob = _ttest_finish(df, t, alternative) + return Ttest_relResult(t, prob) + + +MannwhitneyuResult = namedtuple('MannwhitneyuResult', ('statistic', + 'pvalue')) + + +def mannwhitneyu(x,y, use_continuity=True): + """ + Computes the Mann-Whitney statistic + + Missing values in `x` and/or `y` are discarded. + + Parameters + ---------- + x : sequence + Input + y : sequence + Input + use_continuity : {True, False}, optional + Whether a continuity correction (1/2.) should be taken into account. + + Returns + ------- + statistic : float + The minimum of the Mann-Whitney statistics + pvalue : float + Approximate two-sided p-value assuming a normal distribution. + + """ + x = ma.asarray(x).compressed().view(ndarray) + y = ma.asarray(y).compressed().view(ndarray) + ranks = rankdata(np.concatenate([x,y])) + (nx, ny) = (len(x), len(y)) + nt = nx + ny + U = ranks[:nx].sum() - nx*(nx+1)/2. + U = max(U, nx*ny - U) + u = nx*ny - U + + mu = (nx*ny)/2. + sigsq = (nt**3 - nt)/12. + ties = count_tied_groups(ranks) + sigsq -= sum(v*(k**3-k) for (k,v) in ties.items())/12. + sigsq *= nx*ny/float(nt*(nt-1)) + + if use_continuity: + z = (U - 1/2. - mu) / ma.sqrt(sigsq) + else: + z = (U - mu) / ma.sqrt(sigsq) + + prob = special.erfc(abs(z)/np.sqrt(2)) + return MannwhitneyuResult(u, prob) + + +KruskalResult = namedtuple('KruskalResult', ('statistic', 'pvalue')) + + +def kruskal(*samples): + """ + Compute the Kruskal-Wallis H-test for independent samples + + Parameters + ---------- + *samples : array_like + Two or more arrays with the sample measurements can be given as + arguments. + + Returns + ------- + statistic : float + The Kruskal-Wallis H statistic, corrected for ties + pvalue : float + The p-value for the test using the assumption that H has a chi + square distribution + + Notes + ----- + For more details on `kruskal`, see `scipy.stats.kruskal`. + + Examples + -------- + >>> from scipy.stats.mstats import kruskal + + Random samples from three different brands of batteries were tested + to see how long the charge lasted. Results were as follows: + + >>> a = [6.3, 5.4, 5.7, 5.2, 5.0] + >>> b = [6.9, 7.0, 6.1, 7.9] + >>> c = [7.2, 6.9, 6.1, 6.5] + + Test the hypothesis that the distribution functions for all of the brands' + durations are identical. Use 5% level of significance. + + >>> kruskal(a, b, c) + KruskalResult(statistic=7.113812154696133, pvalue=0.028526948491942164) + + The null hypothesis is rejected at the 5% level of significance + because the returned p-value is less than the critical value of 5%. + + """ + output = argstoarray(*samples) + ranks = ma.masked_equal(rankdata(output, use_missing=False), 0) + sumrk = ranks.sum(-1) + ngrp = ranks.count(-1) + ntot = ranks.count() + H = 12./(ntot*(ntot+1)) * (sumrk**2/ngrp).sum() - 3*(ntot+1) + # Tie correction + ties = count_tied_groups(ranks) + T = 1. - sum(v*(k**3-k) for (k,v) in ties.items())/float(ntot**3-ntot) + if T == 0: + raise ValueError('All numbers are identical in kruskal') + + H /= T + df = len(output) - 1 + prob = distributions.chi2.sf(H, df) + return KruskalResult(H, prob) + + +kruskalwallis = kruskal + + +@_rename_parameter("mode", "method") +def ks_1samp(x, cdf, args=(), alternative="two-sided", method='auto'): + """ + Computes the Kolmogorov-Smirnov test on one sample of masked values. + + Missing values in `x` are discarded. + + Parameters + ---------- + x : array_like + a 1-D array of observations of random variables. + cdf : str or callable + If a string, it should be the name of a distribution in `scipy.stats`. + If a callable, that callable is used to calculate the cdf. + args : tuple, sequence, optional + Distribution parameters, used if `cdf` is a string. + alternative : {'two-sided', 'less', 'greater'}, optional + Indicates the alternative hypothesis. Default is 'two-sided'. + method : {'auto', 'exact', 'asymp'}, optional + Defines the method used for calculating the p-value. + The following options are available (default is 'auto'): + + * 'auto' : use 'exact' for small size arrays, 'asymp' for large + * 'exact' : use approximation to exact distribution of test statistic + * 'asymp' : use asymptotic distribution of test statistic + + Returns + ------- + d : float + Value of the Kolmogorov Smirnov test + p : float + Corresponding p-value. + + """ + alternative = {'t': 'two-sided', 'g': 'greater', 'l': 'less'}.get( + alternative.lower()[0], alternative) + return scipy.stats._stats_py.ks_1samp( + x, cdf, args=args, alternative=alternative, method=method) + + +@_rename_parameter("mode", "method") +def ks_2samp(data1, data2, alternative="two-sided", method='auto'): + """ + Computes the Kolmogorov-Smirnov test on two samples. + + Missing values in `x` and/or `y` are discarded. + + Parameters + ---------- + data1 : array_like + First data set + data2 : array_like + Second data set + alternative : {'two-sided', 'less', 'greater'}, optional + Indicates the alternative hypothesis. Default is 'two-sided'. + method : {'auto', 'exact', 'asymp'}, optional + Defines the method used for calculating the p-value. + The following options are available (default is 'auto'): + + * 'auto' : use 'exact' for small size arrays, 'asymp' for large + * 'exact' : use approximation to exact distribution of test statistic + * 'asymp' : use asymptotic distribution of test statistic + + Returns + ------- + d : float + Value of the Kolmogorov Smirnov test + p : float + Corresponding p-value. + + """ + # Ideally this would be accomplished by + # ks_2samp = scipy.stats._stats_py.ks_2samp + # but the circular dependencies between _mstats_basic and stats prevent that. + alternative = {'t': 'two-sided', 'g': 'greater', 'l': 'less'}.get( + alternative.lower()[0], alternative) + return scipy.stats._stats_py.ks_2samp(data1, data2, + alternative=alternative, + method=method) + + +ks_twosamp = ks_2samp + + +@_rename_parameter("mode", "method") +def kstest(data1, data2, args=(), alternative='two-sided', method='auto'): + """ + Performs the Kolmogorov-Smirnov test for goodness of fit. + + Parameters + ---------- + data1 : array_like + data2 : str, callable or array_like + args : tuple, sequence, optional + Distribution parameters, used if `data1` or `data2` are strings. + alternative : str, as documented in stats.kstest + method : str, as documented in stats.kstest + + Returns + ------- + tuple of (K-S statistic, probability) + + """ # numpydoc ignore=RT03 + return scipy.stats._stats_py.kstest(data1, data2, args, + alternative=alternative, method=method) + + +def trima(a, limits=None, inclusive=(True,True)): + """ + Trims an array by masking the data outside some given limits. + + Returns a masked version of the input array. + + Parameters + ---------- + a : array_like + Input array. + limits : {None, tuple}, optional + Tuple of (lower limit, upper limit) in absolute values. + Values of the input array lower (greater) than the lower (upper) limit + will be masked. A limit is None indicates an open interval. + inclusive : (bool, bool) tuple, optional + Tuple of (lower flag, upper flag), indicating whether values exactly + equal to the lower (upper) limit are allowed. + + Examples + -------- + >>> from scipy.stats.mstats import trima + >>> import numpy as np + + >>> a = np.arange(10) + + The interval is left-closed and right-open, i.e., `[2, 8)`. + Trim the array by keeping only values in the interval. + + >>> trima(a, limits=(2, 8), inclusive=(True, False)) + masked_array(data=[--, --, 2, 3, 4, 5, 6, 7, --, --], + mask=[ True, True, False, False, False, False, False, False, + True, True], + fill_value=999999) + + """ # numpydoc ignore=RT01 + a = ma.asarray(a) + a.unshare_mask() + if (limits is None) or (limits == (None, None)): + return a + + (lower_lim, upper_lim) = limits + (lower_in, upper_in) = inclusive + condition = False + if lower_lim is not None: + if lower_in: + condition |= (a < lower_lim) + else: + condition |= (a <= lower_lim) + + if upper_lim is not None: + if upper_in: + condition |= (a > upper_lim) + else: + condition |= (a >= upper_lim) + + a[condition.filled(True)] = masked + return a + + +def trimr(a, limits=None, inclusive=(True, True), axis=None): + """ + Trims an array by masking some proportion of the data on each end. + Returns a masked version of the input array. + + Parameters + ---------- + a : sequence + Input array. + limits : {None, tuple}, optional + Tuple of the percentages to cut on each side of the array, with respect + to the number of unmasked data, as floats between 0. and 1. + Noting n the number of unmasked data before trimming, the + (n*limits[0])th smallest data and the (n*limits[1])th largest data are + masked, and the total number of unmasked data after trimming is + n*(1.-sum(limits)). The value of one limit can be set to None to + indicate an open interval. + inclusive : {(True,True) tuple}, optional + Tuple of flags indicating whether the number of data being masked on + the left (right) end should be truncated (True) or rounded (False) to + integers. + axis : {None,int}, optional + Axis along which to trim. If None, the whole array is trimmed, but its + shape is maintained. + + """ # numpydoc ignore=RT01 + def _trimr1D(a, low_limit, up_limit, low_inclusive, up_inclusive): + n = a.count() + idx = a.argsort() + if low_limit: + if low_inclusive: + lowidx = int(low_limit*n) + else: + lowidx = int(np.round(low_limit*n)) + a[idx[:lowidx]] = masked + if up_limit is not None: + if up_inclusive: + upidx = n - int(n*up_limit) + else: + upidx = n - int(np.round(n*up_limit)) + a[idx[upidx:]] = masked + return a + + a = ma.asarray(a) + a.unshare_mask() + if limits is None: + return a + + # Check the limits + (lolim, uplim) = limits + errmsg = "The proportion to cut from the %s should be between 0. and 1." + if lolim is not None: + if lolim > 1. or lolim < 0: + raise ValueError(errmsg % 'beginning' + f"(got {lolim})") + if uplim is not None: + if uplim > 1. or uplim < 0: + raise ValueError(errmsg % 'end' + f"(got {uplim})") + + (loinc, upinc) = inclusive + + if axis is None: + shp = a.shape + return _trimr1D(a.ravel(),lolim,uplim,loinc,upinc).reshape(shp) + else: + return ma.apply_along_axis(_trimr1D, axis, a, lolim,uplim,loinc,upinc) + + +trimdoc = _dedent_for_py313(""" + Parameters + ---------- + a : sequence + Input array + limits : {None, tuple}, optional + If `relative` is False, tuple (lower limit, upper limit) in absolute values. + Values of the input array lower (greater) than the lower (upper) limit are + masked. + + If `relative` is True, tuple (lower percentage, upper percentage) to cut + on each side of the array, with respect to the number of unmasked data. + + Noting n the number of unmasked data before trimming, the (n*limits[0])th + smallest data and the (n*limits[1])th largest data are masked, and the + total number of unmasked data after trimming is n*(1.-sum(limits)) + In each case, the value of one limit can be set to None to indicate an + open interval. + + If limits is None, no trimming is performed + inclusive : {(bool, bool) tuple}, optional + If `relative` is False, tuple indicating whether values exactly equal + to the absolute limits are allowed. + If `relative` is True, tuple indicating whether the number of data + being masked on each side should be rounded (True) or truncated + (False). + relative : bool, optional + Whether to consider the limits as absolute values (False) or proportions + to cut (True). + axis : int, optional + Axis along which to trim.""") + + +def trim(a, limits=None, inclusive=(True,True), relative=False, axis=None): + """ + Trims an array by masking the data outside some given limits. + + Returns a masked version of the input array. + + %s + + Examples + -------- + >>> from scipy.stats.mstats import trim + >>> z = [ 1, 2, 3, 4, 5, 6, 7, 8, 9,10] + >>> print(trim(z,(3,8))) + [-- -- 3 4 5 6 7 8 -- --] + >>> print(trim(z,(0.1,0.2),relative=True)) + [-- 2 3 4 5 6 7 8 -- --] + + """ # numpydoc ignore=RT01 + if relative: + return trimr(a, limits=limits, inclusive=inclusive, axis=axis) + else: + return trima(a, limits=limits, inclusive=inclusive) + + +if trim.__doc__: + trim.__doc__ = trim.__doc__ % trimdoc + + +def trimboth(data, proportiontocut=0.2, inclusive=(True,True), axis=None): + """ + Trims the smallest and largest data values. + + Trims the `data` by masking the ``int(proportiontocut * n)`` smallest and + ``int(proportiontocut * n)`` largest values of data along the given axis, + where n is the number of unmasked values before trimming. + + Parameters + ---------- + data : ndarray + Data to trim. + proportiontocut : float, optional + Percentage of trimming (as a float between 0 and 1). + If n is the number of unmasked values before trimming, the number of + values after trimming is ``(1 - 2*proportiontocut) * n``. + Default is 0.2. + inclusive : {(bool, bool) tuple}, optional + Tuple indicating whether the number of data being masked on each side + should be rounded (True) or truncated (False). + axis : int, optional + Axis along which to perform the trimming. + If None, the input array is first flattened. + + """ # numpydoc ignore=RT01 + return trimr(data, limits=(proportiontocut,proportiontocut), + inclusive=inclusive, axis=axis) + + +def trimtail(data, proportiontocut=0.2, tail='left', inclusive=(True,True), + axis=None): + """ + Trims the data by masking values from one tail. + + Parameters + ---------- + data : array_like + Data to trim. + proportiontocut : float, optional + Percentage of trimming. If n is the number of unmasked values + before trimming, the number of values after trimming is + ``(1 - proportiontocut) * n``. Default is 0.2. + tail : {'left','right'}, optional + If 'left' the `proportiontocut` lowest values will be masked. + If 'right' the `proportiontocut` highest values will be masked. + Default is 'left'. + inclusive : {(bool, bool) tuple}, optional + Tuple indicating whether the number of data being masked on each side + should be rounded (True) or truncated (False). Default is + (True, True). + axis : int, optional + Axis along which to perform the trimming. + If None, the input array is first flattened. Default is None. + + Returns + ------- + trimtail : ndarray + Returned array of same shape as `data` with masked tail values. + + """ + tail = str(tail).lower()[0] + if tail == 'l': + limits = (proportiontocut,None) + elif tail == 'r': + limits = (None, proportiontocut) + else: + raise TypeError("The tail argument should be in ('left','right')") + + return trimr(data, limits=limits, axis=axis, inclusive=inclusive) + + +trim1 = trimtail + + +def trimmed_mean(a, limits=(0.1,0.1), inclusive=(1,1), relative=True, + axis=None): + """Returns the trimmed mean of the data along the given axis. + + %s + + """ # numpydoc ignore=RT01 + if (not isinstance(limits,tuple)) and isinstance(limits,float): + limits = (limits, limits) + if relative: + return trimr(a,limits=limits,inclusive=inclusive,axis=axis).mean(axis=axis) + else: + return trima(a,limits=limits,inclusive=inclusive).mean(axis=axis) + + +if trimmed_mean.__doc__: + trimmed_mean.__doc__ = trimmed_mean.__doc__ % trimdoc + + +def trimmed_var(a, limits=(0.1,0.1), inclusive=(1,1), relative=True, + axis=None, ddof=0): + """Returns the trimmed variance of the data along the given axis. + + %s + ddof : {0,integer}, optional + Means Delta Degrees of Freedom. The denominator used during computations + is (n-ddof). DDOF=0 corresponds to a biased estimate, DDOF=1 to an un- + biased estimate of the variance. + + """ # numpydoc ignore=RT01 + if (not isinstance(limits,tuple)) and isinstance(limits,float): + limits = (limits, limits) + if relative: + out = trimr(a,limits=limits, inclusive=inclusive,axis=axis) + else: + out = trima(a,limits=limits,inclusive=inclusive) + + return out.var(axis=axis, ddof=ddof) + + +if trimmed_var.__doc__: + trimmed_var.__doc__ = trimmed_var.__doc__ % trimdoc + + +def trimmed_std(a, limits=(0.1,0.1), inclusive=(1,1), relative=True, + axis=None, ddof=0): + """Returns the trimmed standard deviation of the data along the given axis. + + %s + ddof : {0,integer}, optional + Means Delta Degrees of Freedom. The denominator used during computations + is (n-ddof). DDOF=0 corresponds to a biased estimate, DDOF=1 to an un- + biased estimate of the variance. + + """ # numpydoc ignore=RT01 + if (not isinstance(limits,tuple)) and isinstance(limits,float): + limits = (limits, limits) + if relative: + out = trimr(a,limits=limits,inclusive=inclusive,axis=axis) + else: + out = trima(a,limits=limits,inclusive=inclusive) + return out.std(axis=axis,ddof=ddof) + + +if trimmed_std.__doc__: + trimmed_std.__doc__ = trimmed_std.__doc__ % trimdoc + + +def trimmed_stde(a, limits=(0.1,0.1), inclusive=(1,1), axis=None): + """ + Returns the standard error of the trimmed mean along the given axis. + + Parameters + ---------- + a : sequence + Input array + limits : {(0.1,0.1), tuple of float}, optional + tuple (lower percentage, upper percentage) to cut on each side of the + array, with respect to the number of unmasked data. + + If n is the number of unmasked data before trimming, the values + smaller than ``n * limits[0]`` and the values larger than + ``n * `limits[1]`` are masked, and the total number of unmasked + data after trimming is ``n * (1.-sum(limits))``. In each case, + the value of one limit can be set to None to indicate an open interval. + If `limits` is None, no trimming is performed. + inclusive : {(bool, bool) tuple} optional + Tuple indicating whether the number of data being masked on each side + should be rounded (True) or truncated (False). + axis : int, optional + Axis along which to trim. + + Returns + ------- + trimmed_stde : scalar or ndarray + + """ # numpydoc ignore=RT03 + def _trimmed_stde_1D(a, low_limit, up_limit, low_inclusive, up_inclusive): + "Returns the standard error of the trimmed mean for a 1D input data." + n = a.count() + idx = a.argsort() + if low_limit: + if low_inclusive: + lowidx = int(low_limit*n) + else: + lowidx = np.round(low_limit*n) + a[idx[:lowidx]] = masked + if up_limit is not None: + if up_inclusive: + upidx = n - int(n*up_limit) + else: + upidx = n - np.round(n*up_limit) + a[idx[upidx:]] = masked + a[idx[:lowidx]] = a[idx[lowidx]] + a[idx[upidx:]] = a[idx[upidx-1]] + winstd = a.std(ddof=1) + return winstd / ((1-low_limit-up_limit)*np.sqrt(len(a))) + + a = ma.array(a, copy=True, subok=True) + a.unshare_mask() + if limits is None: + return a.std(axis=axis,ddof=1)/ma.sqrt(a.count(axis)) + if (not isinstance(limits,tuple)) and isinstance(limits,float): + limits = (limits, limits) + + # Check the limits + (lolim, uplim) = limits + errmsg = "The proportion to cut from the %s should be between 0. and 1." + if lolim is not None: + if lolim > 1. or lolim < 0: + raise ValueError(errmsg % 'beginning' + f"(got {lolim})") + if uplim is not None: + if uplim > 1. or uplim < 0: + raise ValueError(errmsg % 'end' + f"(got {uplim})") + + (loinc, upinc) = inclusive + if (axis is None): + return _trimmed_stde_1D(a.ravel(),lolim,uplim,loinc,upinc) + else: + if a.ndim > 2: + raise ValueError(f"Array 'a' must be at most two dimensional, " + f"but got a.ndim = {a.ndim}") + return ma.apply_along_axis(_trimmed_stde_1D, axis, a, + lolim,uplim,loinc,upinc) + + +def _mask_to_limits(a, limits, inclusive): + """Mask an array for values outside of given limits. + + This is primarily a utility function. + + Parameters + ---------- + a : array + limits : (float or None, float or None) + A tuple consisting of the (lower limit, upper limit). Values in the + input array less than the lower limit or greater than the upper limit + will be masked out. None implies no limit. + inclusive : (bool, bool) + A tuple consisting of the (lower flag, upper flag). These flags + determine whether values exactly equal to lower or upper are allowed. + + Returns + ------- + A MaskedArray. + + Raises + ------ + A ValueError if there are no values within the given limits. + """ + lower_limit, upper_limit = limits + lower_include, upper_include = inclusive + am = ma.MaskedArray(a) + if lower_limit is not None: + if lower_include: + am = ma.masked_less(am, lower_limit) + else: + am = ma.masked_less_equal(am, lower_limit) + + if upper_limit is not None: + if upper_include: + am = ma.masked_greater(am, upper_limit) + else: + am = ma.masked_greater_equal(am, upper_limit) + + if am.count() == 0: + raise ValueError("No array values within given limits") + + return am + + +def tmean(a, limits=None, inclusive=(True, True), axis=None): + """ + Compute the trimmed mean. + + Parameters + ---------- + a : array_like + Array of values. + limits : None or (lower limit, upper limit), optional + Values in the input array less than the lower limit or greater than the + upper limit will be ignored. When limits is None (default), then all + values are used. Either of the limit values in the tuple can also be + None representing a half-open interval. + inclusive : (bool, bool), optional + A tuple consisting of the (lower flag, upper flag). These flags + determine whether values exactly equal to the lower or upper limits + are included. The default value is (True, True). + axis : int or None, optional + Axis along which to operate. If None, compute over the + whole array. Default is None. + + Returns + ------- + tmean : float + + Notes + ----- + For more details on `tmean`, see `scipy.stats.tmean`. + + Examples + -------- + >>> import numpy as np + >>> from scipy.stats import mstats + >>> a = np.array([[6, 8, 3, 0], + ... [3, 9, 1, 2], + ... [8, 7, 8, 2], + ... [5, 6, 0, 2], + ... [4, 5, 5, 2]]) + ... + ... + >>> mstats.tmean(a, (2,5)) + 3.3 + >>> mstats.tmean(a, (2,5), axis=0) + masked_array(data=[4.0, 5.0, 4.0, 2.0], + mask=[False, False, False, False], + fill_value=1e+20) + + """ # numpydoc ignore=RT03 + return trima(a, limits=limits, inclusive=inclusive).mean(axis=axis) + + +def tvar(a, limits=None, inclusive=(True, True), axis=0, ddof=1): + """ + Compute the trimmed variance + + This function computes the sample variance of an array of values, + while ignoring values which are outside of given `limits`. + + Parameters + ---------- + a : array_like + Array of values. + limits : None or (lower limit, upper limit), optional + Values in the input array less than the lower limit or greater than the + upper limit will be ignored. When limits is None, then all values are + used. Either of the limit values in the tuple can also be None + representing a half-open interval. The default value is None. + inclusive : (bool, bool), optional + A tuple consisting of the (lower flag, upper flag). These flags + determine whether values exactly equal to the lower or upper limits + are included. The default value is (True, True). + axis : int or None, optional + Axis along which to operate. If None, compute over the + whole array. Default is zero. + ddof : int, optional + Delta degrees of freedom. Default is 1. + + Returns + ------- + tvar : float + Trimmed variance. + + Notes + ----- + For more details on `tvar`, see `scipy.stats.tvar`. + + """ + a = a.astype(float).ravel() + if limits is None: + n = (~a.mask).sum() # todo: better way to do that? + return np.ma.var(a) * n/(n-1.) + am = _mask_to_limits(a, limits=limits, inclusive=inclusive) + + return np.ma.var(am, axis=axis, ddof=ddof) + + +def tmin(a, lowerlimit=None, axis=0, inclusive=True): + """ + Compute the trimmed minimum + + Parameters + ---------- + a : array_like + array of values + lowerlimit : None or float, optional + Values in the input array less than the given limit will be ignored. + When lowerlimit is None, then all values are used. The default value + is None. + axis : int or None, optional + Axis along which to operate. Default is 0. If None, compute over the + whole array `a`. + inclusive : {True, False}, optional + This flag determines whether values exactly equal to the lower limit + are included. The default value is True. + + Returns + ------- + tmin : float, int or ndarray + + Notes + ----- + For more details on `tmin`, see `scipy.stats.tmin`. + + Examples + -------- + >>> import numpy as np + >>> from scipy.stats import mstats + >>> a = np.array([[6, 8, 3, 0], + ... [3, 2, 1, 2], + ... [8, 1, 8, 2], + ... [5, 3, 0, 2], + ... [4, 7, 5, 2]]) + ... + >>> mstats.tmin(a, 5) + masked_array(data=[5, 7, 5, --], + mask=[False, False, False, True], + fill_value=999999) + + """ # numpydoc ignore=RT03 + a, axis = _chk_asarray(a, axis) + am = trima(a, (lowerlimit, None), (inclusive, False)) + return ma.minimum.reduce(am, axis) + + +def tmax(a, upperlimit=None, axis=0, inclusive=True): + """ + Compute the trimmed maximum + + This function computes the maximum value of an array along a given axis, + while ignoring values larger than a specified upper limit. + + Parameters + ---------- + a : array_like + array of values + upperlimit : None or float, optional + Values in the input array greater than the given limit will be ignored. + When upperlimit is None, then all values are used. The default value + is None. + axis : int or None, optional + Axis along which to operate. Default is 0. If None, compute over the + whole array `a`. + inclusive : {True, False}, optional + This flag determines whether values exactly equal to the upper limit + are included. The default value is True. + + Returns + ------- + tmax : float, int or ndarray + + Notes + ----- + For more details on `tmax`, see `scipy.stats.tmax`. + + Examples + -------- + >>> import numpy as np + >>> from scipy.stats import mstats + >>> a = np.array([[6, 8, 3, 0], + ... [3, 9, 1, 2], + ... [8, 7, 8, 2], + ... [5, 6, 0, 2], + ... [4, 5, 5, 2]]) + ... + ... + >>> mstats.tmax(a, 4) + masked_array(data=[4, --, 3, 2], + mask=[False, True, False, False], + fill_value=999999) + + """ # numpydoc ignore=RT03 + a, axis = _chk_asarray(a, axis) + am = trima(a, (None, upperlimit), (False, inclusive)) + return ma.maximum.reduce(am, axis) + + +def tsem(a, limits=None, inclusive=(True, True), axis=0, ddof=1): + """ + Compute the trimmed standard error of the mean. + + This function finds the standard error of the mean for given + values, ignoring values outside the given `limits`. + + Parameters + ---------- + a : array_like + array of values + limits : None or (lower limit, upper limit), optional + Values in the input array less than the lower limit or greater than the + upper limit will be ignored. When limits is None, then all values are + used. Either of the limit values in the tuple can also be None + representing a half-open interval. The default value is None. + inclusive : (bool, bool), optional + A tuple consisting of the (lower flag, upper flag). These flags + determine whether values exactly equal to the lower or upper limits + are included. The default value is (True, True). + axis : int or None, optional + Axis along which to operate. If None, compute over the + whole array. Default is zero. + ddof : int, optional + Delta degrees of freedom. Default is 1. + + Returns + ------- + tsem : float + + Notes + ----- + For more details on `tsem`, see `scipy.stats.tsem`. + + """ # numpydoc ignore=RT03 + a = ma.asarray(a).ravel() + if limits is None: + n = float(a.count()) + return a.std(axis=axis, ddof=ddof)/ma.sqrt(n) + + am = trima(a.ravel(), limits, inclusive) + sd = np.sqrt(am.var(axis=axis, ddof=ddof)) + return sd / np.sqrt(am.count()) + + +def winsorize(a, limits=None, inclusive=(True, True), inplace=False, + axis=None, nan_policy='propagate'): + """Returns a Winsorized version of the input array. + + The (limits[0])th lowest values are set to the (limits[0])th percentile, + and the (limits[1])th highest values are set to the (1 - limits[1])th + percentile. + Masked values are skipped. + + + Parameters + ---------- + a : sequence + Input array. + limits : {None, tuple of float}, optional + Tuple of the percentages to cut on each side of the array, with respect + to the number of unmasked data, as floats between 0. and 1. + Noting n the number of unmasked data before trimming, the + (n*limits[0])th smallest data and the (n*limits[1])th largest data are + masked, and the total number of unmasked data after trimming + is n*(1.-sum(limits)) The value of one limit can be set to None to + indicate an open interval. + inclusive : {(True, True) tuple}, optional + Tuple indicating whether the number of data being masked on each side + should be truncated (True) or rounded (False). + inplace : {False, True}, optional + Whether to winsorize in place (True) or to use a copy (False) + axis : {None, int}, optional + Axis along which to trim. If None, the whole array is trimmed, but its + shape is maintained. + nan_policy : {'propagate', 'raise', 'omit'}, optional + Defines how to handle when input contains nan. + The following options are available (default is 'propagate'): + + * 'propagate': allows nan values and may overwrite or propagate them + * 'raise': throws an error + * 'omit': performs the calculations ignoring nan values + + Notes + ----- + This function is applied to reduce the effect of possibly spurious outliers + by limiting the extreme values. + + Examples + -------- + >>> import numpy as np + >>> from scipy.stats.mstats import winsorize + + A shuffled array contains integers from 1 to 10. + + >>> a = np.array([10, 4, 9, 8, 5, 3, 7, 2, 1, 6]) + + The 10% of the lowest value (i.e., ``1``) and the 20% of the highest + values (i.e., ``9`` and ``10``) are replaced. + + >>> winsorize(a, limits=[0.1, 0.2]) + masked_array(data=[8, 4, 8, 8, 5, 3, 7, 2, 2, 6], + mask=False, + fill_value=999999) + + """ # numpydoc ignore=RT01 + def _winsorize1D(a, low_limit, up_limit, low_include, up_include, + contains_nan, nan_policy): + n = a.count() + idx = a.argsort() + if contains_nan: + nan_count = np.count_nonzero(np.isnan(a)) + if low_limit: + if low_include: + lowidx = int(low_limit * n) + else: + lowidx = np.round(low_limit * n).astype(int) + if contains_nan and nan_policy == 'omit': + lowidx = min(lowidx, n-nan_count-1) + a[idx[:lowidx]] = a[idx[lowidx]] + if up_limit is not None: + if up_include: + upidx = n - int(n * up_limit) + else: + upidx = n - np.round(n * up_limit).astype(int) + if contains_nan and nan_policy == 'omit': + a[idx[upidx:-nan_count]] = a[idx[upidx - 1]] + else: + a[idx[upidx:]] = a[idx[upidx - 1]] + return a + + contains_nan = _contains_nan(a, nan_policy) + # We are going to modify a: better make a copy + a = ma.array(a, copy=np.logical_not(inplace)) + + if limits is None: + return a + if (not isinstance(limits, tuple)) and isinstance(limits, float): + limits = (limits, limits) + + # Check the limits + (lolim, uplim) = limits + errmsg = "The proportion to cut from the %s should be between 0. and 1." + if lolim is not None: + if lolim > 1. or lolim < 0: + raise ValueError(errmsg % 'beginning' + f"(got {lolim})") + if uplim is not None: + if uplim > 1. or uplim < 0: + raise ValueError(errmsg % 'end' + f"(got {uplim})") + + (loinc, upinc) = inclusive + + if axis is None: + shp = a.shape + return _winsorize1D(a.ravel(), lolim, uplim, loinc, upinc, + contains_nan, nan_policy).reshape(shp) + else: + return ma.apply_along_axis(_winsorize1D, axis, a, lolim, uplim, loinc, + upinc, contains_nan, nan_policy) + + +def moment(a, moment=1, axis=0): + """ + Calculates the nth moment about the mean for a sample. + + Parameters + ---------- + a : array_like + data + moment : int, optional + order of central moment that is returned + axis : int or None, optional + Axis along which the central moment is computed. Default is 0. + If None, compute over the whole array `a`. + + Returns + ------- + n-th central moment : ndarray or float + The appropriate moment along the given axis or over all values if axis + is None. The denominator for the moment calculation is the number of + observations, no degrees of freedom correction is done. + + Notes + ----- + For more details about `moment`, see `scipy.stats.moment`. + + """ + a, axis = _chk_asarray(a, axis) + if a.size == 0: + moment_shape = list(a.shape) + del moment_shape[axis] + dtype = a.dtype.type if a.dtype.kind in 'fc' else np.float64 + # empty array, return nan(s) with shape matching `moment` + out_shape = (moment_shape if np.isscalar(moment) + else [len(moment)] + moment_shape) + if len(out_shape) == 0: + return dtype(np.nan) + else: + return ma.array(np.full(out_shape, np.nan, dtype=dtype)) + + # for array_like moment input, return a value for each. + if not np.isscalar(moment): + mean = a.mean(axis, keepdims=True) + mmnt = [_moment(a, i, axis, mean=mean) for i in moment] + return ma.array(mmnt) + else: + return _moment(a, moment, axis) + + +# Moment with optional pre-computed mean, equal to a.mean(axis, keepdims=True) +def _moment(a, moment, axis, *, mean=None): + if np.abs(moment - np.round(moment)) > 0: + raise ValueError("All moment parameters must be integers") + + if moment == 0 or moment == 1: + # By definition the zeroth moment about the mean is 1, and the first + # moment is 0. + shape = list(a.shape) + del shape[axis] + dtype = a.dtype.type if a.dtype.kind in 'fc' else np.float64 + + if len(shape) == 0: + return dtype(1.0 if moment == 0 else 0.0) + else: + return (ma.ones(shape, dtype=dtype) if moment == 0 + else ma.zeros(shape, dtype=dtype)) + else: + # Exponentiation by squares: form exponent sequence + n_list = [moment] + current_n = moment + while current_n > 2: + if current_n % 2: + current_n = (current_n-1)/2 + else: + current_n /= 2 + n_list.append(current_n) + + # Starting point for exponentiation by squares + mean = a.mean(axis, keepdims=True) if mean is None else mean + a_zero_mean = a - mean + if n_list[-1] == 1: + s = a_zero_mean.copy() + else: + s = a_zero_mean**2 + + # Perform multiplications + for n in n_list[-2::-1]: + s = s**2 + if n % 2: + s *= a_zero_mean + return s.mean(axis) + + +def variation(a, axis=0, ddof=0): + """ + Compute the coefficient of variation. + + The coefficient of variation is the standard deviation divided by the + mean. This function is equivalent to:: + + np.std(x, axis=axis, ddof=ddof) / np.mean(x) + + The default for ``ddof`` is 0, but many definitions of the coefficient + of variation use the square root of the unbiased sample variance + for the sample standard deviation, which corresponds to ``ddof=1``. + + Parameters + ---------- + a : array_like + Input array. + axis : int or None, optional + Axis along which to calculate the coefficient of variation. Default + is 0. If None, compute over the whole array `a`. + ddof : int, optional + Delta degrees of freedom. Default is 0. + + Returns + ------- + variation : ndarray + The calculated variation along the requested axis. + + Notes + ----- + For more details about `variation`, see `scipy.stats.variation`. + + Examples + -------- + >>> import numpy as np + >>> from scipy.stats.mstats import variation + >>> a = np.array([2,8,4]) + >>> variation(a) + 0.5345224838248487 + >>> b = np.array([2,8,3,4]) + >>> c = np.ma.masked_array(b, mask=[0,0,1,0]) + >>> variation(c) + 0.5345224838248487 + + In the example above, it can be seen that this works the same as + `scipy.stats.variation` except 'stats.mstats.variation' ignores masked + array elements. + + """ + a, axis = _chk_asarray(a, axis) + return a.std(axis, ddof=ddof)/a.mean(axis) + + +def skew(a, axis=0, bias=True): + """ + Computes the skewness of a data set. + + Parameters + ---------- + a : ndarray + data + axis : int or None, optional + Axis along which skewness is calculated. Default is 0. + If None, compute over the whole array `a`. + bias : bool, optional + If False, then the calculations are corrected for statistical bias. + + Returns + ------- + skewness : ndarray + The skewness of values along an axis, returning 0 where all values are + equal. + + Notes + ----- + For more details about `skew`, see `scipy.stats.skew`. + + """ + a, axis = _chk_asarray(a,axis) + mean = a.mean(axis, keepdims=True) + m2 = _moment(a, 2, axis, mean=mean) + m3 = _moment(a, 3, axis, mean=mean) + zero = (m2 <= (np.finfo(m2.dtype).resolution * mean.squeeze(axis))**2) + with np.errstate(all='ignore'): + vals = ma.where(zero, 0, m3 / m2**1.5) + + if not bias and zero is not ma.masked and m2 is not ma.masked: + n = a.count(axis) + can_correct = ~zero & (n > 2) + if can_correct.any(): + n = np.extract(can_correct, n) + m2 = np.extract(can_correct, m2) + m3 = np.extract(can_correct, m3) + nval = ma.sqrt((n-1.0)*n)/(n-2.0)*m3/m2**1.5 + np.place(vals, can_correct, nval) + return vals + + +def kurtosis(a, axis=0, fisher=True, bias=True): + """ + Computes the kurtosis (Fisher or Pearson) of a dataset. + + Kurtosis is the fourth central moment divided by the square of the + variance. If Fisher's definition is used, then 3.0 is subtracted from + the result to give 0.0 for a normal distribution. + + If bias is False then the kurtosis is calculated using k statistics to + eliminate bias coming from biased moment estimators + + Use `kurtosistest` to see if result is close enough to normal. + + Parameters + ---------- + a : array + data for which the kurtosis is calculated + axis : int or None, optional + Axis along which the kurtosis is calculated. Default is 0. + If None, compute over the whole array `a`. + fisher : bool, optional + If True, Fisher's definition is used (normal ==> 0.0). If False, + Pearson's definition is used (normal ==> 3.0). + bias : bool, optional + If False, then the calculations are corrected for statistical bias. + + Returns + ------- + kurtosis : array + The kurtosis of values along an axis. If all values are equal, + return -3 for Fisher's definition and 0 for Pearson's definition. + + Notes + ----- + For more details about `kurtosis`, see `scipy.stats.kurtosis`. + + """ + a, axis = _chk_asarray(a, axis) + mean = a.mean(axis, keepdims=True) + m2 = _moment(a, 2, axis, mean=mean) + m4 = _moment(a, 4, axis, mean=mean) + zero = (m2 <= (np.finfo(m2.dtype).resolution * mean.squeeze(axis))**2) + with np.errstate(all='ignore'): + vals = ma.where(zero, 0, m4 / m2**2.0) + + if not bias and zero is not ma.masked and m2 is not ma.masked: + n = a.count(axis) + can_correct = ~zero & (n > 3) + if can_correct.any(): + n = np.extract(can_correct, n) + m2 = np.extract(can_correct, m2) + m4 = np.extract(can_correct, m4) + nval = 1.0/(n-2)/(n-3)*((n*n-1.0)*m4/m2**2.0-3*(n-1)**2.0) + np.place(vals, can_correct, nval+3.0) + if fisher: + return vals - 3 + else: + return vals + + +DescribeResult = namedtuple('DescribeResult', ('nobs', 'minmax', 'mean', + 'variance', 'skewness', + 'kurtosis')) + + +def describe(a, axis=0, ddof=0, bias=True): + """ + Computes several descriptive statistics of the passed array. + + Parameters + ---------- + a : array_like + Data array + axis : int or None, optional + Axis along which to calculate statistics. Default 0. If None, + compute over the whole array `a`. + ddof : int, optional + degree of freedom (default 0); note that default ddof is different + from the same routine in stats.describe + bias : bool, optional + If False, then the skewness and kurtosis calculations are corrected for + statistical bias. + + Returns + ------- + nobs : int + (size of the data (discarding missing values) + + minmax : (int, int) + min, max + + mean : float + arithmetic mean + + variance : float + unbiased variance + + skewness : float + biased skewness + + kurtosis : float + biased kurtosis + + Examples + -------- + >>> import numpy as np + >>> from scipy.stats.mstats import describe + >>> ma = np.ma.array(range(6), mask=[0, 0, 0, 1, 1, 1]) + >>> describe(ma) + DescribeResult(nobs=np.int64(3), minmax=(masked_array(data=0, + mask=False, + fill_value=999999), masked_array(data=2, + mask=False, + fill_value=999999)), mean=np.float64(1.0), + variance=np.float64(0.6666666666666666), + skewness=masked_array(data=0., mask=False, fill_value=1e+20), + kurtosis=np.float64(-1.5)) + + """ + a, axis = _chk_asarray(a, axis) + n = a.count(axis) + mm = (ma.minimum.reduce(a, axis=axis), ma.maximum.reduce(a, axis=axis)) + m = a.mean(axis) + v = a.var(axis, ddof=ddof) + sk = skew(a, axis, bias=bias) + kurt = kurtosis(a, axis, bias=bias) + + return DescribeResult(n, mm, m, v, sk, kurt) + + +def stde_median(data, axis=None): + """Returns the McKean-Schrader estimate of the standard error of the sample + median along the given axis. masked values are discarded. + + Parameters + ---------- + data : ndarray + Data to trim. + axis : {None,int}, optional + Axis along which to perform the trimming. + If None, the input array is first flattened. + + """ + def _stdemed_1D(data): + data = np.sort(data.compressed()) + n = len(data) + z = 2.5758293035489004 + k = int(np.round((n+1)/2. - z * np.sqrt(n/4.),0)) + return ((data[n-k] - data[k-1])/(2.*z)) + + data = ma.array(data, copy=False, subok=True) + if (axis is None): + return _stdemed_1D(data) + else: + if data.ndim > 2: + raise ValueError(f"Array 'data' must be at most two dimensional, " + f"but got data.ndim = {data.ndim}") + return ma.apply_along_axis(_stdemed_1D, axis, data) + + +SkewtestResult = namedtuple('SkewtestResult', ('statistic', 'pvalue')) + + +def skewtest(a, axis=0, alternative='two-sided'): + """ + Tests whether the skew is different from the normal distribution. + + Parameters + ---------- + a : array_like + The data to be tested + axis : int or None, optional + Axis along which statistics are calculated. Default is 0. + If None, compute over the whole array `a`. + alternative : {'two-sided', 'less', 'greater'}, optional + Defines the alternative hypothesis. Default is 'two-sided'. + The following options are available: + + * 'two-sided': the skewness of the distribution underlying the sample + is different from that of the normal distribution (i.e. 0) + * 'less': the skewness of the distribution underlying the sample + is less than that of the normal distribution + * 'greater': the skewness of the distribution underlying the sample + is greater than that of the normal distribution + + .. versionadded:: 1.7.0 + + Returns + ------- + statistic : array_like + The computed z-score for this test. + pvalue : array_like + A p-value for the hypothesis test + + Notes + ----- + For more details about `skewtest`, see `scipy.stats.skewtest`. + + """ + a, axis = _chk_asarray(a, axis) + if axis is None: + a = a.ravel() + axis = 0 + b2 = skew(a,axis) + n = a.count(axis) + if np.min(n) < 8: + raise ValueError(f"skewtest is not valid with less than 8 samples; " + f"{np.min(n)} samples were given.") + + y = b2 * ma.sqrt(((n+1)*(n+3)) / (6.0*(n-2))) + beta2 = (3.0*(n*n+27*n-70)*(n+1)*(n+3)) / ((n-2.0)*(n+5)*(n+7)*(n+9)) + W2 = -1 + ma.sqrt(2*(beta2-1)) + delta = 1/ma.sqrt(0.5*ma.log(W2)) + alpha = ma.sqrt(2.0/(W2-1)) + y = ma.where(y == 0, 1, y) + Z = delta*ma.log(y/alpha + ma.sqrt((y/alpha)**2+1)) + pvalue = scipy.stats._stats_py._get_pvalue(Z, distributions.norm, alternative) + + return SkewtestResult(Z[()], pvalue[()]) + + +KurtosistestResult = namedtuple('KurtosistestResult', ('statistic', 'pvalue')) + + +def kurtosistest(a, axis=0, alternative='two-sided'): + """ + Tests whether a dataset has normal kurtosis + + Parameters + ---------- + a : array_like + array of the sample data + axis : int or None, optional + Axis along which to compute test. Default is 0. If None, + compute over the whole array `a`. + alternative : {'two-sided', 'less', 'greater'}, optional + Defines the alternative hypothesis. + The following options are available (default is 'two-sided'): + + * 'two-sided': the kurtosis of the distribution underlying the sample + is different from that of the normal distribution + * 'less': the kurtosis of the distribution underlying the sample + is less than that of the normal distribution + * 'greater': the kurtosis of the distribution underlying the sample + is greater than that of the normal distribution + + .. versionadded:: 1.7.0 + + Returns + ------- + statistic : array_like + The computed z-score for this test. + pvalue : array_like + The p-value for the hypothesis test + + Notes + ----- + For more details about `kurtosistest`, see `scipy.stats.kurtosistest`. + + """ + a, axis = _chk_asarray(a, axis) + n = a.count(axis=axis) + if np.min(n) < 5: + raise ValueError(f"kurtosistest requires at least 5 observations; " + f"{np.min(n)} observations were given.") + if np.min(n) < 20: + warnings.warn(f"kurtosistest only valid for n>=20 ... continuing " + f"anyway, n={np.min(n)}", stacklevel=2) + + b2 = kurtosis(a, axis, fisher=False) + E = 3.0*(n-1) / (n+1) + varb2 = 24.0*n*(n-2.)*(n-3) / ((n+1)*(n+1.)*(n+3)*(n+5)) + x = (b2-E)/ma.sqrt(varb2) + sqrtbeta1 = 6.0*(n*n-5*n+2)/((n+7)*(n+9)) * np.sqrt((6.0*(n+3)*(n+5)) / + (n*(n-2)*(n-3))) + A = 6.0 + 8.0/sqrtbeta1 * (2.0/sqrtbeta1 + np.sqrt(1+4.0/(sqrtbeta1**2))) + term1 = 1 - 2./(9.0*A) + denom = 1 + x*ma.sqrt(2/(A-4.0)) + if np.ma.isMaskedArray(denom): + # For multi-dimensional array input + denom[denom == 0.0] = masked + elif denom == 0.0: + denom = masked + + term2 = np.ma.where(denom > 0, ma.power((1-2.0/A)/denom, 1/3.0), + -ma.power(-(1-2.0/A)/denom, 1/3.0)) + Z = (term1 - term2) / np.sqrt(2/(9.0*A)) + pvalue = scipy.stats._stats_py._get_pvalue(Z, distributions.norm, alternative) + + return KurtosistestResult(Z[()], pvalue[()]) + + +NormaltestResult = namedtuple('NormaltestResult', ('statistic', 'pvalue')) + + +def normaltest(a, axis=0): + """ + Tests whether a sample differs from a normal distribution. + + Parameters + ---------- + a : array_like + The array containing the data to be tested. + axis : int or None, optional + Axis along which to compute test. Default is 0. If None, + compute over the whole array `a`. + + Returns + ------- + statistic : float or array + ``s^2 + k^2``, where ``s`` is the z-score returned by `skewtest` and + ``k`` is the z-score returned by `kurtosistest`. + pvalue : float or array + A 2-sided chi squared probability for the hypothesis test. + + Notes + ----- + For more details about `normaltest`, see `scipy.stats.normaltest`. + + """ + a, axis = _chk_asarray(a, axis) + s, _ = skewtest(a, axis) + k, _ = kurtosistest(a, axis) + k2 = s*s + k*k + + return NormaltestResult(k2, distributions.chi2.sf(k2, 2)) + + +def mquantiles(a, prob=(.25, .5, .75), alphap=.4, betap=.4, axis=None, + limit=()): + """ + Computes empirical quantiles for a data array. + + Samples quantile are defined by ``Q(p) = (1-gamma)*x[j] + gamma*x[j+1]``, + where ``x[j]`` is the j-th order statistic, and gamma is a function of + ``j = floor(n*p + m)``, ``m = alphap + p*(1 - alphap - betap)`` and + ``g = n*p + m - j``. + + Reinterpreting the above equations to compare to **R** lead to the + equation: ``p(k) = (k - alphap)/(n + 1 - alphap - betap)`` + + Typical values of (alphap,betap) are: + + - (0,1) : ``p(k) = k/n`` : linear interpolation of cdf + (**R** type 4) + - (.5,.5) : ``p(k) = (k - 1/2.)/n`` : piecewise linear function + (**R** type 5) + - (0,0) : ``p(k) = k/(n+1)`` : + (**R** type 6) + - (1,1) : ``p(k) = (k-1)/(n-1)``: p(k) = mode[F(x[k])]. + (**R** type 7, **R** default) + - (1/3,1/3): ``p(k) = (k-1/3)/(n+1/3)``: Then p(k) ~ median[F(x[k])]. + The resulting quantile estimates are approximately median-unbiased + regardless of the distribution of x. + (**R** type 8) + - (3/8,3/8): ``p(k) = (k-3/8)/(n+1/4)``: Blom. + The resulting quantile estimates are approximately unbiased + if x is normally distributed + (**R** type 9) + - (.4,.4) : approximately quantile unbiased (Cunnane) + - (.35,.35): APL, used with PWM + + Parameters + ---------- + a : array_like + Input data, as a sequence or array of dimension at most 2. + prob : array_like, optional + List of quantiles to compute. + alphap : float, optional + Plotting positions parameter, default is 0.4. + betap : float, optional + Plotting positions parameter, default is 0.4. + axis : int, optional + Axis along which to perform the trimming. + If None (default), the input array is first flattened. + limit : tuple, optional + Tuple of (lower, upper) values. + Values of `a` outside this open interval are ignored. + + Returns + ------- + mquantiles : MaskedArray + An array containing the calculated quantiles. + + Notes + ----- + This formulation is very similar to **R** except the calculation of + ``m`` from ``alphap`` and ``betap``, where in **R** ``m`` is defined + with each type. + + References + ---------- + .. [1] *R* statistical software: https://www.r-project.org/ + .. [2] *R* ``quantile`` function: + http://stat.ethz.ch/R-manual/R-devel/library/stats/html/quantile.html + + Examples + -------- + >>> import numpy as np + >>> from scipy.stats.mstats import mquantiles + >>> a = np.array([6., 47., 49., 15., 42., 41., 7., 39., 43., 40., 36.]) + >>> mquantiles(a) + array([ 19.2, 40. , 42.8]) + + Using a 2D array, specifying axis and limit. + + >>> data = np.array([[ 6., 7., 1.], + ... [ 47., 15., 2.], + ... [ 49., 36., 3.], + ... [ 15., 39., 4.], + ... [ 42., 40., -999.], + ... [ 41., 41., -999.], + ... [ 7., -999., -999.], + ... [ 39., -999., -999.], + ... [ 43., -999., -999.], + ... [ 40., -999., -999.], + ... [ 36., -999., -999.]]) + >>> print(mquantiles(data, axis=0, limit=(0, 50))) + [[19.2 14.6 1.45] + [40. 37.5 2.5 ] + [42.8 40.05 3.55]] + + >>> data[:, 2] = -999. + >>> print(mquantiles(data, axis=0, limit=(0, 50))) + [[19.200000000000003 14.6 --] + [40.0 37.5 --] + [42.800000000000004 40.05 --]] + + """ + def _quantiles1D(data,m,p): + x = np.sort(data.compressed()) + n = len(x) + if n == 0: + return ma.array(np.empty(len(p), dtype=float), mask=True) + elif n == 1: + return ma.array(np.resize(x, p.shape), mask=nomask) + aleph = (n*p + m) + k = np.floor(aleph.clip(1, n-1)).astype(int) + gamma = (aleph-k).clip(0,1) + return (1.-gamma)*x[(k-1).tolist()] + gamma*x[k.tolist()] + + data = ma.array(a, copy=False) + if data.ndim > 2: + raise TypeError("Array should be 2D at most !") + + if limit: + condition = (limit[0] < data) & (data < limit[1]) + data[~condition.filled(True)] = masked + + p = np.atleast_1d(np.asarray(prob)) + m = alphap + p*(1.-alphap-betap) + # Computes quantiles along axis (or globally) + if (axis is None): + return _quantiles1D(data, m, p) + + return ma.apply_along_axis(_quantiles1D, axis, data, m, p) + + +def scoreatpercentile(data, per, limit=(), alphap=.4, betap=.4): + """Calculate the score at the given 'per' percentile of the + sequence a. For example, the score at per=50 is the median. + + This function is a shortcut to mquantile + + """ # numpydoc ignore=RT01 + if (per < 0) or (per > 100.): + raise ValueError(f"The percentile should be between 0. and 100. ! (got {per})") + + return mquantiles(data, prob=[per/100.], alphap=alphap, betap=betap, + limit=limit, axis=0).squeeze() + + +def plotting_positions(data, alpha=0.4, beta=0.4): + """ + Returns plotting positions (or empirical percentile points) for the data. + + Plotting positions are defined as ``(i-alpha)/(n+1-alpha-beta)``, where: + + - i is the rank order statistics + - n is the number of unmasked values along the given axis + - `alpha` and `beta` are two parameters. + + Typical values for `alpha` and `beta` are: + + - (0,1) : ``p(k) = k/n``, linear interpolation of cdf (R, type 4) + - (.5,.5) : ``p(k) = (k-1/2.)/n``, piecewise linear function + (R, type 5) + - (0,0) : ``p(k) = k/(n+1)``, Weibull (R type 6) + - (1,1) : ``p(k) = (k-1)/(n-1)``, in this case, + ``p(k) = mode[F(x[k])]``. That's R default (R type 7) + - (1/3,1/3): ``p(k) = (k-1/3)/(n+1/3)``, then + ``p(k) ~ median[F(x[k])]``. + The resulting quantile estimates are approximately median-unbiased + regardless of the distribution of x. (R type 8) + - (3/8,3/8): ``p(k) = (k-3/8)/(n+1/4)``, Blom. + The resulting quantile estimates are approximately unbiased + if x is normally distributed (R type 9) + - (.4,.4) : approximately quantile unbiased (Cunnane) + - (.35,.35): APL, used with PWM + - (.3175, .3175): used in scipy.stats.probplot + + Parameters + ---------- + data : array_like + Input data, as a sequence or array of dimension at most 2. + alpha : float, optional + Plotting positions parameter. Default is 0.4. + beta : float, optional + Plotting positions parameter. Default is 0.4. + + Returns + ------- + positions : MaskedArray + The calculated plotting positions. + + """ + data = ma.array(data, copy=False).reshape(1,-1) + n = data.count() + plpos = np.empty(data.size, dtype=float) + plpos[n:] = 0 + plpos[data.argsort(axis=None)[:n]] = ((np.arange(1, n+1) - alpha) / + (n + 1.0 - alpha - beta)) + return ma.array(plpos, mask=data._mask) + + +meppf = plotting_positions + + +def obrientransform(*args): + """ + Computes a transform on input data (any number of columns). Used to + test for homogeneity of variance prior to running one-way stats. Each + array in ``*args`` is one level of a factor. If an `f_oneway()` run on + the transformed data and found significant, variances are unequal. From + Maxwell and Delaney, p.112. + + Returns: transformed data for use in an ANOVA + """ # numpydoc ignore=RT01 + data = argstoarray(*args).T + v = data.var(axis=0,ddof=1) + m = data.mean(0) + n = data.count(0).astype(float) + # result = ((N-1.5)*N*(a-m)**2 - 0.5*v*(n-1))/((n-1)*(n-2)) + data -= m + data **= 2 + data *= (n-1.5)*n + data -= 0.5*v*(n-1) + data /= (n-1.)*(n-2.) + if not ma.allclose(v,data.mean(0)): + raise ValueError("Lack of convergence in obrientransform.") + + return data + + +def sem(a, axis=0, ddof=1): + """ + Calculates the standard error of the mean of the input array. + + Also sometimes called standard error of measurement. + + Parameters + ---------- + a : array_like + An array containing the values for which the standard error is + returned. + axis : int or None, optional + If axis is None, ravel `a` first. If axis is an integer, this will be + the axis over which to operate. Defaults to 0. + ddof : int, optional + Delta degrees-of-freedom. How many degrees of freedom to adjust + for bias in limited samples relative to the population estimate + of variance. Defaults to 1. + + Returns + ------- + s : ndarray or float + The standard error of the mean in the sample(s), along the input axis. + + Notes + ----- + The default value for `ddof` changed in scipy 0.15.0 to be consistent with + `scipy.stats.sem` as well as with the most common definition used (like in + the R documentation). + + Examples + -------- + Find standard error along the first axis: + + >>> import numpy as np + >>> from scipy import stats + >>> a = np.arange(20).reshape(5,4) + >>> print(stats.mstats.sem(a)) + [2.8284271247461903 2.8284271247461903 2.8284271247461903 + 2.8284271247461903] + + Find standard error across the whole array, using n degrees of freedom: + + >>> print(stats.mstats.sem(a, axis=None, ddof=0)) + 1.2893796958227628 + + """ + a, axis = _chk_asarray(a, axis) + n = a.count(axis=axis) + s = a.std(axis=axis, ddof=ddof) / ma.sqrt(n) + return s + + +F_onewayResult = namedtuple('F_onewayResult', ('statistic', 'pvalue')) + + +def f_oneway(*args): + """ + Performs a 1-way ANOVA, returning an F-value and probability given + any number of groups. From Heiman, pp.394-7. + + Usage: ``f_oneway(*args)``, where ``*args`` is 2 or more arrays, + one per treatment group. + + Returns + ------- + statistic : float + The computed F-value of the test. + pvalue : float + The associated p-value from the F-distribution. + + """ + # Construct a single array of arguments: each row is a group + data = argstoarray(*args) + ngroups = len(data) + ntot = data.count() + sstot = (data**2).sum() - (data.sum())**2/float(ntot) + ssbg = (data.count(-1) * (data.mean(-1)-data.mean())**2).sum() + sswg = sstot-ssbg + dfbg = ngroups-1 + dfwg = ntot - ngroups + msb = ssbg/float(dfbg) + msw = sswg/float(dfwg) + f = msb/msw + prob = special.fdtrc(dfbg, dfwg, f) # equivalent to stats.f.sf + + return F_onewayResult(f, prob) + + +FriedmanchisquareResult = namedtuple('FriedmanchisquareResult', + ('statistic', 'pvalue')) + + +def friedmanchisquare(*args): + """Friedman Chi-Square is a non-parametric, one-way within-subjects ANOVA. + This function calculates the Friedman Chi-square test for repeated measures + and returns the result, along with the associated probability value. + + Each input is considered a given group. Ideally, the number of treatments + among each group should be equal. If this is not the case, only the first + n treatments are taken into account, where n is the number of treatments + of the smallest group. + If a group has some missing values, the corresponding treatments are masked + in the other groups. + The test statistic is corrected for ties. + + Masked values in one group are propagated to the other groups. + + Returns + ------- + statistic : float + the test statistic. + pvalue : float + the associated p-value. + + """ + data = argstoarray(*args).astype(float) + k = len(data) + if k < 3: + raise ValueError(f"Less than 3 groups ({k}): the Friedman test " + f"is NOT appropriate.") + + ranked = ma.masked_values(rankdata(data, axis=0), 0) + if ranked._mask is not nomask: + ranked = ma.mask_cols(ranked) + ranked = ranked.compressed().reshape(k,-1).view(ndarray) + else: + ranked = ranked._data + (k,n) = ranked.shape + # Ties correction + repeats = [find_repeats(row) for row in ranked.T] + ties = np.array([y for x, y in repeats if x.size > 0]) + tie_correction = 1 - (ties**3-ties).sum()/float(n*(k**3-k)) + + ssbg = np.sum((ranked.sum(-1) - n*(k+1)/2.)**2) + chisq = ssbg * 12./(n*k*(k+1)) * 1./tie_correction + + return FriedmanchisquareResult(chisq, + distributions.chi2.sf(chisq, k-1)) + + +BrunnerMunzelResult = namedtuple('BrunnerMunzelResult', ('statistic', 'pvalue')) + + +def brunnermunzel(x, y, alternative="two-sided", distribution="t"): + """ + Compute the Brunner-Munzel test on samples x and y. + + Any missing values in `x` and/or `y` are discarded. + + The Brunner-Munzel test is a nonparametric test of the null hypothesis that + when values are taken one by one from each group, the probabilities of + getting large values in both groups are equal. + Unlike the Wilcoxon-Mann-Whitney's U test, this does not require the + assumption of equivariance of two groups. Note that this does not assume + the distributions are same. This test works on two independent samples, + which may have different sizes. + + Parameters + ---------- + x, y : array_like + Array of samples, should be one-dimensional. + alternative : 'less', 'two-sided', or 'greater', optional + Whether to get the p-value for the one-sided hypothesis ('less' + or 'greater') or for the two-sided hypothesis ('two-sided'). + Defaults value is 'two-sided' . + distribution : 't' or 'normal', optional + Whether to get the p-value by t-distribution or by standard normal + distribution. + Defaults value is 't' . + + Returns + ------- + statistic : float + The Brunner-Munzer W statistic. + pvalue : float + p-value assuming a t distribution. One-sided or + two-sided, depending on the choice of `alternative` and `distribution`. + + See Also + -------- + mannwhitneyu : Mann-Whitney rank test on two samples. + + Notes + ----- + For more details on `brunnermunzel`, see `scipy.stats.brunnermunzel`. + + Examples + -------- + >>> from scipy.stats.mstats import brunnermunzel + >>> import numpy as np + >>> x1 = [1, 2, np.nan, np.nan, 1, 1, 1, 1, 1, 1, 2, 4, 1, 1] + >>> x2 = [3, 3, 4, 3, 1, 2, 3, 1, 1, 5, 4] + >>> brunnermunzel(x1, x2) + BrunnerMunzelResult(statistic=1.4723186918922935, pvalue=0.15479415300426624) # may vary + + """ # noqa: E501 + x = ma.asarray(x).compressed().view(ndarray) + y = ma.asarray(y).compressed().view(ndarray) + nx = len(x) + ny = len(y) + if nx == 0 or ny == 0: + return BrunnerMunzelResult(np.nan, np.nan) + rankc = rankdata(np.concatenate((x,y))) + rankcx = rankc[0:nx] + rankcy = rankc[nx:nx+ny] + rankcx_mean = np.mean(rankcx) + rankcy_mean = np.mean(rankcy) + rankx = rankdata(x) + ranky = rankdata(y) + rankx_mean = np.mean(rankx) + ranky_mean = np.mean(ranky) + + Sx = np.sum(np.power(rankcx - rankx - rankcx_mean + rankx_mean, 2.0)) + Sx /= nx - 1 + Sy = np.sum(np.power(rankcy - ranky - rankcy_mean + ranky_mean, 2.0)) + Sy /= ny - 1 + + wbfn = nx * ny * (rankcy_mean - rankcx_mean) + wbfn /= (nx + ny) * np.sqrt(nx * Sx + ny * Sy) + + if distribution == "t": + df_numer = np.power(nx * Sx + ny * Sy, 2.0) + df_denom = np.power(nx * Sx, 2.0) / (nx - 1) + df_denom += np.power(ny * Sy, 2.0) / (ny - 1) + df = df_numer / df_denom + p = distributions.t.cdf(wbfn, df) + elif distribution == "normal": + p = distributions.norm.cdf(wbfn) + else: + raise ValueError( + "distribution should be 't' or 'normal'") + + if alternative == "greater": + pass + elif alternative == "less": + p = 1 - p + elif alternative == "two-sided": + p = 2 * np.min([p, 1-p]) + else: + raise ValueError( + "alternative should be 'less', 'greater' or 'two-sided'") + + return BrunnerMunzelResult(wbfn, p) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_mstats_extras.py b/venv/lib/python3.12/site-packages/scipy/stats/_mstats_extras.py new file mode 100644 index 0000000000000000000000000000000000000000..d7b5484fb930edadb0538576e0cb736c82957e7a --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_mstats_extras.py @@ -0,0 +1,520 @@ +""" +Additional statistics functions with support for masked arrays. + +""" + +# Original author (2007): Pierre GF Gerard-Marchant + + +__all__ = ['compare_medians_ms', + 'hdquantiles', 'hdmedian', 'hdquantiles_sd', + 'idealfourths', + 'median_cihs','mjci','mquantiles_cimj', + 'rsh', + 'trimmed_mean_ci',] + + +import numpy as np +from numpy import float64, ndarray + +import numpy.ma as ma +from numpy.ma import MaskedArray + +from . import _mstats_basic as mstats + +from scipy.stats.distributions import norm, beta, t, binom + + +def hdquantiles(data, prob=(.25, .5, .75), axis=None, var=False,): + """ + Computes quantile estimates with the Harrell-Davis method. + + The quantile estimates are calculated as a weighted linear combination + of order statistics. + + Parameters + ---------- + data : array_like + Data array. + prob : sequence, optional + Sequence of probabilities at which to compute the quantiles. + axis : int or None, optional + Axis along which to compute the quantiles. If None, use a flattened + array. + var : bool, optional + Whether to return the variance of the estimate. + + Returns + ------- + hdquantiles : MaskedArray + A (p,) array of quantiles (if `var` is False), or a (2,p) array of + quantiles and variances (if `var` is True), where ``p`` is the + number of quantiles. + + See Also + -------- + hdquantiles_sd + + Examples + -------- + >>> import numpy as np + >>> from scipy.stats.mstats import hdquantiles + >>> + >>> # Sample data + >>> data = np.array([1.2, 2.5, 3.7, 4.0, 5.1, 6.3, 7.0, 8.2, 9.4]) + >>> + >>> # Probabilities at which to compute quantiles + >>> probabilities = [0.25, 0.5, 0.75] + >>> + >>> # Compute Harrell-Davis quantile estimates + >>> quantile_estimates = hdquantiles(data, prob=probabilities) + >>> + >>> # Display the quantile estimates + >>> for i, quantile in enumerate(probabilities): + ... print(f"{int(quantile * 100)}th percentile: {quantile_estimates[i]}") + 25th percentile: 3.1505820231763066 # may vary + 50th percentile: 5.194344084883956 + 75th percentile: 7.430626414674935 + + """ + def _hd_1D(data,prob,var): + "Computes the HD quantiles for a 1D array. Returns nan for invalid data." + xsorted = np.squeeze(np.sort(data.compressed().view(ndarray))) + # Don't use length here, in case we have a numpy scalar + n = xsorted.size + + hd = np.empty((2,len(prob)), float64) + if n < 2: + hd.flat = np.nan + if var: + return hd + return hd[0] + + v = np.arange(n+1) / float(n) + betacdf = beta.cdf + for (i,p) in enumerate(prob): + _w = betacdf(v, (n+1)*p, (n+1)*(1-p)) + w = _w[1:] - _w[:-1] + hd_mean = np.dot(w, xsorted) + hd[0,i] = hd_mean + # + hd[1,i] = np.dot(w, (xsorted-hd_mean)**2) + # + hd[0, prob == 0] = xsorted[0] + hd[0, prob == 1] = xsorted[-1] + if var: + hd[1, prob == 0] = hd[1, prob == 1] = np.nan + return hd + return hd[0] + # Initialization & checks + data = ma.array(data, copy=False, dtype=float64) + p = np.atleast_1d(np.asarray(prob)) + # Computes quantiles along axis (or globally) + if (axis is None) or (data.ndim == 1): + result = _hd_1D(data, p, var) + else: + if data.ndim > 2: + raise ValueError(f"Array 'data' must be at most two dimensional, " + f"but got data.ndim = {data.ndim}") + result = ma.apply_along_axis(_hd_1D, axis, data, p, var) + + return ma.fix_invalid(result, copy=False) + + +def hdmedian(data, axis=-1, var=False): + """ + Returns the Harrell-Davis estimate of the median along the given axis. + + Parameters + ---------- + data : ndarray + Data array. + axis : int, optional + Axis along which to compute the quantiles. If None, use a flattened + array. + var : bool, optional + Whether to return the variance of the estimate. + + Returns + ------- + hdmedian : MaskedArray + The median values. If ``var=True``, the variance is returned inside + the masked array. E.g. for a 1-D array the shape change from (1,) to + (2,). + + """ + result = hdquantiles(data,[0.5], axis=axis, var=var) + return result.squeeze() + + +def hdquantiles_sd(data, prob=(.25, .5, .75), axis=None): + """ + The standard error of the Harrell-Davis quantile estimates by jackknife. + + Parameters + ---------- + data : array_like + Data array. + prob : sequence, optional + Sequence of quantiles to compute. + axis : int, optional + Axis along which to compute the quantiles. If None, use a flattened + array. + + Returns + ------- + hdquantiles_sd : MaskedArray + Standard error of the Harrell-Davis quantile estimates. + + See Also + -------- + hdquantiles + + """ + def _hdsd_1D(data, prob): + "Computes the std error for 1D arrays." + xsorted = np.sort(data.compressed()) + n = len(xsorted) + + hdsd = np.empty(len(prob), float64) + if n < 2: + hdsd.flat = np.nan + + vv = np.arange(n) / float(n-1) + betacdf = beta.cdf + + for (i,p) in enumerate(prob): + _w = betacdf(vv, n*p, n*(1-p)) + w = _w[1:] - _w[:-1] + # cumulative sum of weights and data points if + # ith point is left out for jackknife + mx_ = np.zeros_like(xsorted) + mx_[1:] = np.cumsum(w * xsorted[:-1]) + # similar but from the right + mx_[:-1] += np.cumsum(w[::-1] * xsorted[:0:-1])[::-1] + hdsd[i] = np.sqrt(mx_.var() * (n - 1)) + return hdsd + + # Initialization & checks + data = ma.array(data, copy=False, dtype=float64) + p = np.atleast_1d(np.asarray(prob)) + # Computes quantiles along axis (or globally) + if (axis is None): + result = _hdsd_1D(data, p) + else: + if data.ndim > 2: + raise ValueError(f"Array 'data' must be at most two dimensional, " + f"but got data.ndim = {data.ndim}") + result = ma.apply_along_axis(_hdsd_1D, axis, data, p) + + return ma.fix_invalid(result, copy=False).ravel() + + +def trimmed_mean_ci(data, limits=(0.2,0.2), inclusive=(True,True), + alpha=0.05, axis=None): + """ + Selected confidence interval of the trimmed mean along the given axis. + + Parameters + ---------- + data : array_like + Input data. + limits : {None, tuple}, optional + None or a two item tuple. + Tuple of the percentages to cut on each side of the array, with respect + to the number of unmasked data, as floats between 0. and 1. If ``n`` + is the number of unmasked data before trimming, then + (``n * limits[0]``)th smallest data and (``n * limits[1]``)th + largest data are masked. The total number of unmasked data after + trimming is ``n * (1. - sum(limits))``. + The value of one limit can be set to None to indicate an open interval. + + Defaults to (0.2, 0.2). + inclusive : (2,) tuple of boolean, optional + If relative==False, tuple indicating whether values exactly equal to + the absolute limits are allowed. + If relative==True, tuple indicating whether the number of data being + masked on each side should be rounded (True) or truncated (False). + + Defaults to (True, True). + alpha : float, optional + Confidence level of the intervals. + + Defaults to 0.05. + axis : int, optional + Axis along which to cut. If None, uses a flattened version of `data`. + + Defaults to None. + + Returns + ------- + trimmed_mean_ci : (2,) ndarray + The lower and upper confidence intervals of the trimmed data. + + """ + data = ma.array(data, copy=False) + trimmed = mstats.trimr(data, limits=limits, inclusive=inclusive, axis=axis) + tmean = trimmed.mean(axis) + tstde = mstats.trimmed_stde(data,limits=limits,inclusive=inclusive,axis=axis) + df = trimmed.count(axis) - 1 + tppf = t.ppf(1-alpha/2.,df) + return np.array((tmean - tppf*tstde, tmean+tppf*tstde)) + + +def mjci(data, prob=(0.25, 0.5, 0.75), axis=None): + """ + Returns the Maritz-Jarrett estimators of the standard error of selected + experimental quantiles of the data. + + Parameters + ---------- + data : ndarray + Data array. + prob : sequence, optional + Sequence of quantiles to compute. + axis : int or None, optional + Axis along which to compute the quantiles. If None, use a flattened + array. + + """ # numpydoc ignore=RT01 + def _mjci_1D(data, p): + data = np.sort(data.compressed()) + n = data.size + prob = (np.array(p) * n + 0.5).astype(int) + betacdf = beta.cdf + + mj = np.empty(len(prob), float64) + x = np.arange(1,n+1, dtype=float64) / n + y = x - 1./n + for (i,m) in enumerate(prob): + W = betacdf(x,m-1,n-m) - betacdf(y,m-1,n-m) + C1 = np.dot(W,data) + C2 = np.dot(W,data**2) + mj[i] = np.sqrt(C2 - C1**2) + return mj + + data = ma.array(data, copy=False) + if data.ndim > 2: + raise ValueError(f"Array 'data' must be at most two dimensional, " + f"but got data.ndim = {data.ndim}") + + p = np.atleast_1d(np.asarray(prob)) + # Computes quantiles along axis (or globally) + if (axis is None): + return _mjci_1D(data, p) + else: + return ma.apply_along_axis(_mjci_1D, axis, data, p) + + +def mquantiles_cimj(data, prob=(0.25, 0.50, 0.75), alpha=0.05, axis=None): + """ + Computes the alpha confidence interval for the selected quantiles of the + data, with Maritz-Jarrett estimators. + + Parameters + ---------- + data : ndarray + Data array. + prob : sequence, optional + Sequence of quantiles to compute. + alpha : float, optional + Confidence level of the intervals. + axis : int or None, optional + Axis along which to compute the quantiles. + If None, use a flattened array. + + Returns + ------- + ci_lower : ndarray + The lower boundaries of the confidence interval. Of the same length as + `prob`. + ci_upper : ndarray + The upper boundaries of the confidence interval. Of the same length as + `prob`. + + """ + alpha = min(alpha, 1 - alpha) + z = norm.ppf(1 - alpha/2.) + xq = mstats.mquantiles(data, prob, alphap=0, betap=0, axis=axis) + smj = mjci(data, prob, axis=axis) + return (xq - z * smj, xq + z * smj) + + +def median_cihs(data, alpha=0.05, axis=None): + """ + Computes the alpha-level confidence interval for the median of the data. + + Uses the Hettmasperger-Sheather method. + + Parameters + ---------- + data : array_like + Input data. Masked values are discarded. The input should be 1D only, + or `axis` should be set to None. + alpha : float, optional + Confidence level of the intervals. + axis : int or None, optional + Axis along which to compute the quantiles. If None, use a flattened + array. + + Returns + ------- + median_cihs + Alpha level confidence interval. + + """ + def _cihs_1D(data, alpha): + data = np.sort(data.compressed()) + n = len(data) + alpha = min(alpha, 1-alpha) + k = int(binom._ppf(alpha/2., n, 0.5)) + gk = binom.cdf(n-k,n,0.5) - binom.cdf(k-1,n,0.5) + if gk < 1-alpha: + k -= 1 + gk = binom.cdf(n-k,n,0.5) - binom.cdf(k-1,n,0.5) + gkk = binom.cdf(n-k-1,n,0.5) - binom.cdf(k,n,0.5) + I = (gk - 1 + alpha)/(gk - gkk) + lambd = (n-k) * I / float(k + (n-2*k)*I) + lims = (lambd*data[k] + (1-lambd)*data[k-1], + lambd*data[n-k-1] + (1-lambd)*data[n-k]) + return lims + data = ma.array(data, copy=False) + # Computes quantiles along axis (or globally) + if (axis is None): + result = _cihs_1D(data, alpha) + else: + if data.ndim > 2: + raise ValueError(f"Array 'data' must be at most two dimensional, " + f"but got data.ndim = {data.ndim}") + result = ma.apply_along_axis(_cihs_1D, axis, data, alpha) + + return result + + +def compare_medians_ms(group_1, group_2, axis=None): + """ + Compares the medians from two independent groups along the given axis. + + The comparison is performed using the McKean-Schrader estimate of the + standard error of the medians. + + Parameters + ---------- + group_1 : array_like + First dataset. Has to be of size >=7. + group_2 : array_like + Second dataset. Has to be of size >=7. + axis : int, optional + Axis along which the medians are estimated. If None, the arrays are + flattened. If `axis` is not None, then `group_1` and `group_2` + should have the same shape. + + Returns + ------- + compare_medians_ms : {float, ndarray} + If `axis` is None, then returns a float, otherwise returns a 1-D + ndarray of floats with a length equal to the length of `group_1` + along `axis`. + + References + ---------- + .. [1] McKean, Joseph W., and Ronald M. Schrader. "A comparison of methods + for studentizing the sample median." Communications in + Statistics-Simulation and Computation 13.6 (1984): 751-773. + + Examples + -------- + + >>> from scipy import stats + >>> a = [1, 2, 3, 4, 5, 6, 7] + >>> b = [8, 9, 10, 11, 12, 13, 14] + >>> stats.mstats.compare_medians_ms(a, b, axis=None) + 1.0693225866553746e-05 + + The function is vectorized to compute along a given axis. + + >>> import numpy as np + >>> rng = np.random.default_rng() + >>> x = rng.random(size=(3, 7)) + >>> y = rng.random(size=(3, 8)) + >>> stats.mstats.compare_medians_ms(x, y, axis=1) + array([0.36908985, 0.36092538, 0.2765313 ]) + """ + (med_1, med_2) = (ma.median(group_1,axis=axis), ma.median(group_2,axis=axis)) + (std_1, std_2) = (mstats.stde_median(group_1, axis=axis), + mstats.stde_median(group_2, axis=axis)) + W = np.abs(med_1 - med_2) / ma.sqrt(std_1**2 + std_2**2) + return 1 - norm.cdf(W) + + +def idealfourths(data, axis=None): + """ + Returns an estimate of the lower and upper quartiles. + + Uses the ideal fourths algorithm. + + Parameters + ---------- + data : array_like + Input array. + axis : int, optional + Axis along which the quartiles are estimated. If None, the arrays are + flattened. + + Returns + ------- + idealfourths : {list of floats, masked array} + Returns the two internal values that divide `data` into four parts + using the ideal fourths algorithm either along the flattened array + (if `axis` is None) or along `axis` of `data`. + + """ + def _idf(data): + x = data.compressed() + n = len(x) + if n < 3: + return [np.nan,np.nan] + (j,h) = divmod(n/4. + 5/12.,1) + j = int(j) + qlo = (1-h)*x[j-1] + h*x[j] + k = n - j + qup = (1-h)*x[k] + h*x[k-1] + return [qlo, qup] + data = ma.sort(data, axis=axis).view(MaskedArray) + if (axis is None): + return _idf(data) + else: + return ma.apply_along_axis(_idf, axis, data) + + +def rsh(data, points=None): + """ + Evaluates Rosenblatt's shifted histogram estimators for each data point. + + Rosenblatt's estimator is a centered finite-difference approximation to the + derivative of the empirical cumulative distribution function. + + Parameters + ---------- + data : sequence + Input data, should be 1-D. Masked values are ignored. + points : sequence or None, optional + Sequence of points where to evaluate Rosenblatt shifted histogram. + If None, use the data. + + """ # numpydoc ignore=RT01 + data = ma.array(data, copy=False) + if points is None: + points = data + else: + points = np.atleast_1d(np.asarray(points)) + + if data.ndim != 1: + raise AttributeError("The input array should be 1D only !") + + n = data.count() + r = idealfourths(data, axis=None) + h = 1.2 * (r[-1]-r[0]) / n**(1./5) + nhi = (data[:,None] <= points[None,:] + h).sum(0) + nlo = (data[:,None] < points[None,:] - h).sum(0) + return (nhi-nlo) / (2.*n*h) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_multicomp.py b/venv/lib/python3.12/site-packages/scipy/stats/_multicomp.py new file mode 100644 index 0000000000000000000000000000000000000000..f7251be0912025e81afee9169e781e9b16ff5ce7 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_multicomp.py @@ -0,0 +1,457 @@ +import warnings +from collections.abc import Sequence +from dataclasses import dataclass, field +from typing import TYPE_CHECKING, Literal + +import numpy as np + +from scipy import stats +from scipy.optimize import minimize_scalar +from scipy.stats._common import ConfidenceInterval +from scipy.stats._qmc import check_random_state +from scipy.stats._stats_py import _var +from scipy._lib._array_api import xp_capabilities +from scipy._lib._util import _transition_to_rng, DecimalNumber, SeedType + + +if TYPE_CHECKING: + import numpy.typing as npt + + +__all__ = [ + 'dunnett' +] + + +@dataclass +class DunnettResult: + """Result object returned by `scipy.stats.dunnett`. + + Attributes + ---------- + statistic : float ndarray + The computed statistic of the test for each comparison. The element + at index ``i`` is the statistic for the comparison between + groups ``i`` and the control. + pvalue : float ndarray + The computed p-value of the test for each comparison. The element + at index ``i`` is the p-value for the comparison between + group ``i`` and the control. + """ + statistic: np.ndarray + pvalue: np.ndarray + _alternative: Literal['two-sided', 'less', 'greater'] = field(repr=False) + _rho: np.ndarray = field(repr=False) + _df: int = field(repr=False) + _std: float = field(repr=False) + _mean_samples: np.ndarray = field(repr=False) + _mean_control: np.ndarray = field(repr=False) + _n_samples: np.ndarray = field(repr=False) + _n_control: int = field(repr=False) + _rng: SeedType = field(repr=False) + _ci: ConfidenceInterval | None = field(default=None, repr=False) + _ci_cl: DecimalNumber | None = field(default=None, repr=False) + + @classmethod + def __class_getitem__(cls, arg, /): + # generic type compatibility with scipy-stubs + from types import GenericAlias + return GenericAlias(cls, arg) + + def __str__(self): + # Note: `__str__` prints the confidence intervals from the most + # recent call to `confidence_interval`. If it has not been called, + # it will be called with the default CL of .95. + if self._ci is None: + self.confidence_interval(confidence_level=.95) + s = ( + "Dunnett's test" + f" ({self._ci_cl*100:.1f}% Confidence Interval)\n" + "Comparison Statistic p-value Lower CI Upper CI\n" + ) + for i in range(self.pvalue.size): + s += (f" (Sample {i} - Control) {self.statistic[i]:>10.3f}" + f"{self.pvalue[i]:>10.3f}" + f"{self._ci.low[i]:>10.3f}" + f"{self._ci.high[i]:>10.3f}\n") + + return s + + def _allowance( + self, confidence_level: DecimalNumber = 0.95, tol: DecimalNumber = 1e-3 + ) -> float: + """Allowance. + + It is the quantity to add/subtract from the observed difference + between the means of observed groups and the mean of the control + group. The result gives confidence limits. + + Parameters + ---------- + confidence_level : float, optional + Confidence level for the computed confidence interval. + Default is .95. + tol : float, optional + A tolerance for numerical optimization: the allowance will produce + a confidence within ``10*tol*(1 - confidence_level)`` of the + specified level, or a warning will be emitted. Tight tolerances + may be impractical due to noisy evaluation of the objective. + Default is 1e-3. + + Returns + ------- + allowance : float + Allowance around the mean. + """ + alpha = 1 - confidence_level + + def pvalue_from_stat(statistic): + statistic = np.array(statistic) + sf = _pvalue_dunnett( + rho=self._rho, df=self._df, + statistic=statistic, alternative=self._alternative, + rng=self._rng + ) + return abs(sf - alpha)/alpha + + # Evaluation of `pvalue_from_stat` is noisy due to the use of RQMC to + # evaluate `multivariate_t.cdf`. `minimize_scalar` is not designed + # to tolerate a noisy objective function and may fail to find the + # minimum accurately. We mitigate this possibility with the validation + # step below, but implementation of a noise-tolerant root finder or + # minimizer would be a welcome enhancement. See gh-18150. + res = minimize_scalar(pvalue_from_stat, method='brent', tol=tol) + critical_value = res.x + + # validation + # tol*10 because tol=1e-3 means we tolerate a 1% change at most + if res.success is False or res.fun >= tol*10: + warnings.warn( + "Computation of the confidence interval did not converge to " + "the desired level. The confidence level corresponding with " + f"the returned interval is approximately {alpha*(1+res.fun)}.", + stacklevel=3 + ) + + # From [1] p. 1101 between (1) and (3) + allowance = critical_value*self._std*np.sqrt( + 1/self._n_samples + 1/self._n_control + ) + return abs(allowance) + + def confidence_interval( + self, confidence_level: DecimalNumber = 0.95 + ) -> ConfidenceInterval: + """Compute the confidence interval for the specified confidence level. + + Parameters + ---------- + confidence_level : float, optional + Confidence level for the computed confidence interval. + Default is .95. + + Returns + ------- + ci : ``ConfidenceInterval`` object + The object has attributes ``low`` and ``high`` that hold the + lower and upper bounds of the confidence intervals for each + comparison. The high and low values are accessible for each + comparison at index ``i`` for each group ``i``. + + """ + # check to see if the supplied confidence level matches that of the + # previously computed CI. + if (self._ci is not None) and (confidence_level == self._ci_cl): + return self._ci + + if not (0 < confidence_level < 1): + raise ValueError("Confidence level must be between 0 and 1.") + + allowance = self._allowance(confidence_level=confidence_level) + diff_means = self._mean_samples - self._mean_control + + low = diff_means-allowance + high = diff_means+allowance + + if self._alternative == 'greater': + high = [np.inf] * len(diff_means) + elif self._alternative == 'less': + low = [-np.inf] * len(diff_means) + + self._ci_cl = confidence_level + self._ci = ConfidenceInterval( + low=low, + high=high + ) + return self._ci + + +@xp_capabilities(np_only=True) +@_transition_to_rng('random_state', replace_doc=False) +def dunnett( + *samples: "npt.ArrayLike", # noqa: D417 + control: "npt.ArrayLike", + alternative: Literal['two-sided', 'less', 'greater'] = "two-sided", + rng: SeedType = None +) -> DunnettResult: + """Dunnett's test: multiple comparisons of means against a control group. + + This is an implementation of Dunnett's original, single-step test as + described in [1]_. + + Parameters + ---------- + *samples : 1D array_like + The sample measurements for each experimental group. + control : 1D array_like + The sample measurements for the control group. + alternative : {'two-sided', 'less', 'greater'}, optional + Defines the alternative hypothesis. + + The null hypothesis is that the means of the distributions underlying + the samples and control are equal. The following alternative + hypotheses are available (default is 'two-sided'): + + * 'two-sided': the means of the distributions underlying the samples + and control are unequal. + * 'less': the means of the distributions underlying the samples + are less than the mean of the distribution underlying the control. + * 'greater': the means of the distributions underlying the + samples are greater than the mean of the distribution underlying + the control. + rng : `numpy.random.Generator`, optional + Pseudorandom number generator state. When `rng` is None, a new + `numpy.random.Generator` is created using entropy from the + operating system. Types other than `numpy.random.Generator` are + passed to `numpy.random.default_rng` to instantiate a ``Generator``. + + .. versionchanged:: 1.15.0 + + As part of the `SPEC-007 `_ + transition from use of `numpy.random.RandomState` to + `numpy.random.Generator`, this keyword was changed from `random_state` to + `rng`. For an interim period, both keywords will continue to work, although + only one may be specified at a time. After the interim period, function + calls using the `random_state` keyword will emit warnings. Following a + deprecation period, the `random_state` keyword will be removed. + + Returns + ------- + res : `~scipy.stats._result_classes.DunnettResult` + An object containing attributes: + + statistic : float ndarray + The computed statistic of the test for each comparison. The element + at index ``i`` is the statistic for the comparison between + groups ``i`` and the control. + pvalue : float ndarray + The computed p-value of the test for each comparison. The element + at index ``i`` is the p-value for the comparison between + group ``i`` and the control. + + And the following method: + + confidence_interval(confidence_level=0.95) : + Compute the difference in means of the groups + with the control +- the allowance. + + See Also + -------- + tukey_hsd : performs pairwise comparison of means. + :ref:`hypothesis_dunnett` : Extended example + + Notes + ----- + Like the independent-sample t-test, Dunnett's test [1]_ is used to make + inferences about the means of distributions from which samples were drawn. + However, when multiple t-tests are performed at a fixed significance level, + the "family-wise error rate" - the probability of incorrectly rejecting the + null hypothesis in at least one test - will exceed the significance level. + Dunnett's test is designed to perform multiple comparisons while + controlling the family-wise error rate. + + Dunnett's test compares the means of multiple experimental groups + against a single control group. Tukey's Honestly Significant Difference Test + is another multiple-comparison test that controls the family-wise error + rate, but `tukey_hsd` performs *all* pairwise comparisons between groups. + When pairwise comparisons between experimental groups are not needed, + Dunnett's test is preferable due to its higher power. + + The use of this test relies on several assumptions. + + 1. The observations are independent within and among groups. + 2. The observations within each group are normally distributed. + 3. The distributions from which the samples are drawn have the same finite + variance. + + References + ---------- + .. [1] Dunnett, Charles W. (1955) "A Multiple Comparison Procedure for + Comparing Several Treatments with a Control." Journal of the American + Statistical Association, 50:272, 1096-1121, + :doi:`10.1080/01621459.1955.10501294` + .. [2] Thomson, M. L., & Short, M. D. (1969). Mucociliary function in + health, chronic obstructive airway disease, and asbestosis. Journal + of applied physiology, 26(5), 535-539. + :doi:`10.1152/jappl.1969.26.5.535` + + Examples + -------- + We'll use data from [2]_, Table 1. The null hypothesis is that the means of + the distributions underlying the samples and control are equal. + + First, we test that the means of the distributions underlying the samples + and control are unequal (``alternative='two-sided'``, the default). + + >>> import numpy as np + >>> from scipy.stats import dunnett + >>> samples = [[3.8, 2.7, 4.0, 2.4], [2.8, 3.4, 3.7, 2.2, 2.0]] + >>> control = [2.9, 3.0, 2.5, 2.6, 3.2] + >>> res = dunnett(*samples, control=control) + >>> res.statistic + array([ 0.90874545, -0.05007117]) + >>> res.pvalue + array([0.58325114, 0.99819341]) + + Now, we test that the means of the distributions underlying the samples are + greater than the mean of the distribution underlying the control. + + >>> res = dunnett(*samples, control=control, alternative='greater') + >>> res.statistic + array([ 0.90874545, -0.05007117]) + >>> res.pvalue + array([0.30230596, 0.69115597]) + + For a more detailed example, see :ref:`hypothesis_dunnett`. + """ + samples_, control_, rng = _iv_dunnett( + samples=samples, control=control, + alternative=alternative, rng=rng + ) + + rho, df, n_group, n_samples, n_control = _params_dunnett( + samples=samples_, control=control_ + ) + + statistic, std, mean_control, mean_samples = _statistic_dunnett( + samples_, control_, df, n_samples, n_control + ) + + pvalue = _pvalue_dunnett( + rho=rho, df=df, statistic=statistic, alternative=alternative, rng=rng + ) + + return DunnettResult( + statistic=statistic, pvalue=pvalue, + _alternative=alternative, + _rho=rho, _df=df, _std=std, + _mean_samples=mean_samples, + _mean_control=mean_control, + _n_samples=n_samples, + _n_control=n_control, + _rng=rng + ) + + +def _iv_dunnett( + samples: Sequence["npt.ArrayLike"], + control: "npt.ArrayLike", + alternative: Literal['two-sided', 'less', 'greater'], + rng: SeedType +) -> tuple[list[np.ndarray], np.ndarray, SeedType]: + """Input validation for Dunnett's test.""" + rng = check_random_state(rng) + + if alternative not in {'two-sided', 'less', 'greater'}: + raise ValueError( + "alternative must be 'less', 'greater' or 'two-sided'" + ) + + ndim_msg = "Control and samples groups must be 1D arrays" + n_obs_msg = "Control and samples groups must have at least 1 observation" + + control = np.asarray(control) + samples_ = [np.asarray(sample) for sample in samples] + + # samples checks + samples_control: list[np.ndarray] = samples_ + [control] + for sample in samples_control: + if sample.ndim > 1: + raise ValueError(ndim_msg) + + if sample.size < 1: + raise ValueError(n_obs_msg) + + return samples_, control, rng + + +def _params_dunnett( + samples: list[np.ndarray], control: np.ndarray +) -> tuple[np.ndarray, int, int, np.ndarray, int]: + """Specific parameters for Dunnett's test. + + Degree of freedom is the number of observations minus the number of groups + including the control. + """ + n_samples = np.array([sample.size for sample in samples]) + + # From [1] p. 1100 d.f. = (sum N)-(p+1) + n_sample = n_samples.sum() + n_control = control.size + n = n_sample + n_control + n_groups = len(samples) + df = n - n_groups - 1 + + # From [1] p. 1103 rho_ij = 1/sqrt((N0/Ni+1)(N0/Nj+1)) + rho = n_control/n_samples + 1 + rho = 1/np.sqrt(rho[:, None] * rho[None, :]) + np.fill_diagonal(rho, 1) + + return rho, df, n_groups, n_samples, n_control + + +def _statistic_dunnett( + samples: list[np.ndarray], control: np.ndarray, df: int, + n_samples: np.ndarray, n_control: int +) -> tuple[np.ndarray, float, np.ndarray, np.ndarray]: + """Statistic of Dunnett's test. + + Computation based on the original single-step test from [1]. + """ + mean_control = np.mean(control) + mean_samples = np.array([np.mean(sample) for sample in samples]) + all_samples = [control] + samples + all_means = np.concatenate([[mean_control], mean_samples]) + + # Variance estimate s^2 from [1] Eq. 1 + s2 = np.sum([_var(sample, mean=mean)*sample.size + for sample, mean in zip(all_samples, all_means)]) / df + std = np.sqrt(s2) + + # z score inferred from [1] unlabeled equation after Eq. 1 + z = (mean_samples - mean_control) / np.sqrt(1/n_samples + 1/n_control) + + return z / std, std, mean_control, mean_samples + + +def _pvalue_dunnett( + rho: np.ndarray, df: int, statistic: np.ndarray, + alternative: Literal['two-sided', 'less', 'greater'], + rng: SeedType = None +) -> np.ndarray: + """pvalue from the multivariate t-distribution. + + Critical values come from the multivariate student-t distribution. + """ + statistic = statistic.reshape(-1, 1) + + mvt = stats.multivariate_t(shape=rho, df=df, seed=rng) + if alternative == "two-sided": + statistic = abs(statistic) + pvalue = 1 - mvt.cdf(statistic, lower_limit=-statistic) + elif alternative == "greater": + pvalue = 1 - mvt.cdf(statistic, lower_limit=-np.inf) + else: + pvalue = 1 - mvt.cdf(np.inf, lower_limit=statistic) + + return np.atleast_1d(pvalue) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_multivariate.py b/venv/lib/python3.12/site-packages/scipy/stats/_multivariate.py new file mode 100644 index 0000000000000000000000000000000000000000..51e5c8dc8f5272381d17e8edc33662a635628bba --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_multivariate.py @@ -0,0 +1,8054 @@ +# +# Author: Joris Vankerschaver 2013 +# +import math +import threading +import types +import numpy as np +import scipy.linalg +from scipy._lib import doccer +from scipy.special import (gammaln, psi, multigammaln, xlogy, entr, betaln, + ive, loggamma) +from scipy import special +import scipy._external.array_api_extra as xpx +from scipy._lib._util import check_random_state +from scipy.linalg.blas import get_blas_funcs +from ._continuous_distns import norm, invgamma +from ._discrete_distns import binom +from . import _covariance, _rcont +from ._qmvnt import _qmvt, _qmvn, _qauto +from ._morestats import directional_stats +from scipy.optimize import root_scalar + +__all__ = ['multivariate_normal', + 'matrix_normal', + 'dirichlet', + 'dirichlet_multinomial', + 'wishart', + 'invwishart', + 'multinomial', + 'special_ortho_group', + 'ortho_group', + 'random_correlation', + 'unitary_group', + 'multivariate_t', + 'multivariate_hypergeom', + 'random_table', + 'uniform_direction', + 'vonmises_fisher', + 'normal_inverse_gamma', + 'matrix_t'] + +_LOG_2PI = np.log(2 * np.pi) +_LOG_2 = np.log(2) +_LOG_PI = np.log(np.pi) +MVN_LOCK = threading.Lock() + + +_doc_random_state = """\ +seed : {None, int, np.random.RandomState, np.random.Generator}, optional + Used for drawing random variates. + If `seed` is `None`, the `~np.random.RandomState` singleton is used. + If `seed` is an int, a new ``RandomState`` instance is used, seeded + with seed. + If `seed` is already a ``RandomState`` or ``Generator`` instance, + then that object is used. + Default is `None`. +""" + + +def _squeeze_output(out): + """ + Remove single-dimensional entries from array and convert to scalar, + if necessary. + """ + out = out.squeeze() + if out.ndim == 0: + out = out[()] + return out + + +def _eigvalsh_to_eps(spectrum, cond=None, rcond=None): + """Determine which eigenvalues are "small" given the spectrum. + + This is for compatibility across various linear algebra functions + that should agree about whether or not a Hermitian matrix is numerically + singular and what is its numerical matrix rank. + This is designed to be compatible with scipy.linalg.pinvh. + + Parameters + ---------- + spectrum : 1d ndarray + Array of eigenvalues of a Hermitian matrix. + cond, rcond : float, optional + Cutoff for small eigenvalues. + Singular values smaller than rcond * largest_eigenvalue are + considered zero. + If None or -1, suitable machine precision is used. + + Returns + ------- + eps : float + Magnitude cutoff for numerical negligibility. + + """ + if rcond is not None: + cond = rcond + if cond in [None, -1]: + t = spectrum.dtype.char.lower() + factor = {'f': 1E3, 'd': 1E6} + cond = factor[t] * np.finfo(t).eps + eps = cond * np.max(abs(spectrum)) + return eps + + +def _pinv_1d(v, eps=1e-5): + """A helper function for computing the pseudoinverse. + + Parameters + ---------- + v : iterable of numbers + This may be thought of as a vector of eigenvalues or singular values. + eps : float + Values with magnitude no greater than eps are considered negligible. + + Returns + ------- + v_pinv : 1d float ndarray + A vector of pseudo-inverted numbers. + + """ + return np.array([0 if abs(x) <= eps else 1/x for x in v], dtype=float) + + +def _validate_marginal_input(dimensions, multivariate_dims): + """Determine if input dimensions can be marginalized. + + Parameters + ---------- + dimensions : float, ndarray + Input dimensions to be marginalized + multivariate_dims : int + Number of dimensions of multivariate distribution. + + Returns + ------- + dims : ndarray + Array of indices to marginalize + """ + dims = np.copy(dimensions) + dims = np.atleast_1d(dims) + + if len(dims) == 0: + msg = "Cannot marginalize all dimensions." + raise ValueError(msg) + + if not np.issubdtype(dims.dtype, np.integer): + msg = ("Elements of `dimensions` must be integers - the indices " + "of the marginal variables being retained.") + raise ValueError(msg) + + original_dims = np.copy(dims) + + dims[dims < 0] += multivariate_dims + + if len(np.unique(dims)) != len(dims): + msg = "All elements of `dimensions` must be unique." + raise ValueError(msg) + + i_invalid = (dims < 0) | (dims >= multivariate_dims) + if np.any(i_invalid): + msg = (f"Dimensions {original_dims[i_invalid]} are invalid " + f"for a distribution in {multivariate_dims} dimensions.") + raise ValueError(msg) + + return dims + + +class _PSD: + """ + Compute coordinated functions of a symmetric positive semidefinite matrix. + + This class addresses two issues. Firstly it allows the pseudoinverse, + the logarithm of the pseudo-determinant, and the rank of the matrix + to be computed using one call to eigh instead of three. + Secondly it allows these functions to be computed in a way + that gives mutually compatible results. + All of the functions are computed with a common understanding as to + which of the eigenvalues are to be considered negligibly small. + The functions are designed to coordinate with scipy.linalg.pinvh() + but not necessarily with np.linalg.det() or with np.linalg.matrix_rank(). + + Parameters + ---------- + M : array_like + Symmetric positive semidefinite matrix (2-D). + cond, rcond : float, optional + Cutoff for small eigenvalues. + Singular values smaller than rcond * largest_eigenvalue are + considered zero. + If None or -1, suitable machine precision is used. + lower : bool, optional + Whether the pertinent array data is taken from the lower + or upper triangle of M. (Default: lower) + check_finite : bool, optional + Whether to check that the input matrices contain only finite + numbers. Disabling may give a performance gain, but may result + in problems (crashes, non-termination) if the inputs do contain + infinities or NaNs. + allow_singular : bool, optional + Whether to allow a singular matrix. (Default: True) + + Notes + ----- + The arguments are similar to those of scipy.linalg.pinvh(). + + """ + + def __init__(self, M, cond=None, rcond=None, lower=True, + check_finite=True, allow_singular=True): + self._M = np.asarray(M) + + # Compute the symmetric eigendecomposition. + # Note that eigh takes care of array conversion, chkfinite, + # and assertion that the matrix is square. + s, u = scipy.linalg.eigh(M, lower=lower, check_finite=check_finite) + + eps = _eigvalsh_to_eps(s, cond, rcond) + if np.min(s) < -eps: + msg = "The input matrix must be symmetric positive semidefinite." + raise ValueError(msg) + d = s[s > eps] + if len(d) < len(s) and not allow_singular: + msg = ("When `allow_singular is False`, the input matrix must be " + "symmetric positive definite.") + raise np.linalg.LinAlgError(msg) + s_pinv = _pinv_1d(s, eps) + U = np.multiply(u, np.sqrt(s_pinv)) + + # Save the eigenvector basis, and tolerance for testing support + self.eps = 1e3*eps + self.V = u[:, s <= eps] + + # Initialize the eagerly precomputed attributes. + self.rank = len(d) + self.U = U + self.log_pdet = np.sum(np.log(d)) + + # Initialize attributes to be lazily computed. + self._pinv = None + + def _support_mask(self, x): + """ + Check whether x lies in the support of the distribution. + """ + residual = np.linalg.norm(x @ self.V, axis=-1) + in_support = residual < self.eps + return in_support + + @property + def pinv(self): + if self._pinv is None: + self._pinv = np.dot(self.U, self.U.T) + return self._pinv + + +class multi_rv_generic: + """ + Class which encapsulates common functionality between all multivariate + distributions. + """ + + def __init__(self, seed=None): + super().__init__() + self._random_state = check_random_state(seed) + + @property + def random_state(self): + """ Get or set the Generator object for generating random variates. + + If `seed` is None (or `np.random`), the `numpy.random.RandomState` + singleton is used. + If `seed` is an int, a new ``RandomState`` instance is used, + seeded with `seed`. + If `seed` is already a ``Generator`` or ``RandomState`` instance then + that instance is used. + + """ + return self._random_state + + @random_state.setter + def random_state(self, seed): + self._random_state = check_random_state(seed) + + def _get_random_state(self, random_state): + if random_state is not None: + return check_random_state(random_state) + else: + return self._random_state + + +class multi_rv_frozen: + """ + Class which encapsulates common functionality between all frozen + multivariate distributions. + """ + + # generic type compatibility with scipy-stubs + __class_getitem__: classmethod = classmethod(types.GenericAlias) + + @property + def random_state(self): + return self._dist._random_state + + @random_state.setter + def random_state(self, seed): + self._dist._random_state = check_random_state(seed) + + +_mvn_doc_default_callparams = """\ +mean : array_like, default: ``[0]`` + Mean of the distribution. +cov : array_like or `Covariance`, default: ``[1]`` + Symmetric positive (semi)definite covariance matrix of the distribution. +allow_singular : bool, default: ``False`` + Whether to allow a singular covariance matrix. This is ignored if `cov` is + a `Covariance` object. +""" + +_mvn_doc_callparams_note = """\ +Setting the parameter `mean` to `None` is equivalent to having `mean` +be the zero-vector. The parameter `cov` can be a scalar, in which case +the covariance matrix is the identity times that value, a vector of +diagonal entries for the covariance matrix, a two-dimensional array_like, +or a `Covariance` object. +""" + +_mvn_doc_frozen_callparams = "" + +_mvn_doc_frozen_callparams_note = """\ +See class definition for a detailed description of parameters.""" + +mvn_docdict_params = { + '_mvn_doc_default_callparams': _mvn_doc_default_callparams, + '_mvn_doc_callparams_note': _mvn_doc_callparams_note, + '_doc_random_state': _doc_random_state +} + +mvn_docdict_noparams = { + '_mvn_doc_default_callparams': _mvn_doc_frozen_callparams, + '_mvn_doc_callparams_note': _mvn_doc_frozen_callparams_note, + '_doc_random_state': _doc_random_state +} + + +class multivariate_normal_gen(multi_rv_generic): + r""" + A multivariate normal random variable. + + The `mean` keyword specifies the mean. The `cov` keyword specifies the + covariance matrix. + + Parameters + ---------- + %(_mvn_doc_default_callparams)s + %(_doc_random_state)s + + Methods + ------- + pdf(x, mean=None, cov=1, allow_singular=False) + Probability density function. + logpdf(x, mean=None, cov=1, allow_singular=False) + Log of the probability density function. + cdf(x, mean=None, cov=1, allow_singular=False, maxpts=1000000*dim, abseps=1e-5, releps=1e-5, lower_limit=None) + Cumulative distribution function. + logcdf(x, mean=None, cov=1, allow_singular=False, maxpts=1000000*dim, abseps=1e-5, releps=1e-5) + Log of the cumulative distribution function. + rvs(mean=None, cov=1, size=1, random_state=None) + Draw random samples from a multivariate normal distribution. + entropy(mean=None, cov=1) + Compute the differential entropy of the multivariate normal. + marginal(dimensions, mean=None, cov=1, allow_singular=False) + Return a marginal multivariate normal distribution. + fit(x, fix_mean=None, fix_cov=None) + Fit a multivariate normal distribution to data. + + Notes + ----- + %(_mvn_doc_callparams_note)s + + The covariance matrix `cov` may be an instance of a subclass of + `Covariance`, e.g. `scipy.stats.CovViaPrecision`. If so, `allow_singular` + is ignored. + + Otherwise, `cov` must be a symmetric positive semidefinite + matrix when `allow_singular` is True; it must be (strictly) positive + definite when `allow_singular` is False. + Symmetry is not checked; only the lower triangular portion is used. + The determinant and inverse of `cov` are computed + as the pseudo-determinant and pseudo-inverse, respectively, so + that `cov` does not need to have full rank. + + The probability density function for `multivariate_normal` is + + .. math:: + + f(x) = \frac{1}{\sqrt{(2 \pi)^k \det \Sigma}} + \exp\left( -\frac{1}{2} (x - \mu)^T \Sigma^{-1} (x - \mu) \right), + + where :math:`\mu` is the mean, :math:`\Sigma` the covariance matrix, + :math:`k` the rank of :math:`\Sigma`. In case of singular :math:`\Sigma`, + SciPy extends this definition according to [1]_. + + .. versionadded:: 0.14.0 + + References + ---------- + .. [1] Multivariate Normal Distribution - Degenerate Case, Wikipedia, + https://en.wikipedia.org/wiki/Multivariate_normal_distribution#Degenerate_case + + Examples + -------- + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from scipy.stats import multivariate_normal + + >>> x = np.linspace(0, 5, 10, endpoint=False) + >>> y = multivariate_normal.pdf(x, mean=2.5, cov=0.5); y + array([ 0.00108914, 0.01033349, 0.05946514, 0.20755375, 0.43939129, + 0.56418958, 0.43939129, 0.20755375, 0.05946514, 0.01033349]) + >>> fig1 = plt.figure() + >>> ax = fig1.add_subplot(111) + >>> ax.plot(x, y) + >>> plt.show() + + Alternatively, the object may be called (as a function) to fix the mean + and covariance parameters, returning a "frozen" multivariate normal + random variable: + + >>> rv = multivariate_normal(mean=None, cov=1, allow_singular=False) + >>> # Frozen object with the same methods but holding the given + >>> # mean and covariance fixed. + + The input quantiles can be any shape of array, as long as the last + axis labels the components. This allows us for instance to + display the frozen pdf for a non-isotropic random variable in 2D as + follows: + + >>> x, y = np.mgrid[-1:1:.01, -1:1:.01] + >>> pos = np.dstack((x, y)) + >>> rv = multivariate_normal([0.5, -0.2], [[2.0, 0.3], [0.3, 0.5]]) + >>> fig2 = plt.figure() + >>> ax2 = fig2.add_subplot(111) + >>> ax2.contourf(x, y, rv.pdf(pos)) + """ # noqa: E501 + + def __init__(self, seed=None): + super().__init__(seed) + self.__doc__ = doccer.docformat(self.__doc__, mvn_docdict_params) + + def __call__(self, mean=None, cov=1, allow_singular=False, seed=None, **kwds): + """Create a frozen multivariate normal distribution. + + See `multivariate_normal_frozen` for more information. + """ + return multivariate_normal_frozen(mean, cov, + allow_singular=allow_singular, + seed=seed, **kwds) + + def _process_parameters(self, mean, cov, allow_singular=True): + """ + Infer dimensionality from mean or covariance matrix, ensure that + mean and covariance are full vector resp. matrix. + """ + if isinstance(cov, _covariance.Covariance): + return self._process_parameters_Covariance(mean, cov) + else: + # Before `Covariance` classes were introduced, + # `multivariate_normal` accepted plain arrays as `cov` and used the + # following input validation. To avoid disturbing the behavior of + # `multivariate_normal` when plain arrays are used, we use the + # original input validation here. + dim, mean, cov = self._process_parameters_psd(None, mean, cov) + # After input validation, some methods then processed the arrays + # with a `_PSD` object and used that to perform computation. + # To avoid branching statements in each method depending on whether + # `cov` is an array or `Covariance` object, we always process the + # array with `_PSD`, and then use wrapper that satisfies the + # `Covariance` interface, `CovViaPSD`. + psd = _PSD(cov, allow_singular=allow_singular) + cov_object = _covariance.CovViaPSD(psd) + return dim, mean, cov_object + + def _process_parameters_Covariance(self, mean, cov): + dim = cov.shape[-1] + mean = np.array([0.]) if mean is None else mean + message = (f"`cov` represents a covariance matrix in {dim} dimensions," + f"and so `mean` must be broadcastable to shape {(dim,)}") + try: + mean = np.broadcast_to(mean, dim) + except ValueError as e: + raise ValueError(message) from e + return dim, mean, cov + + def _process_parameters_psd(self, dim, mean, cov): + # Try to infer dimensionality + if dim is None: + if mean is None: + if cov is None: + dim = 1 + else: + cov = np.asarray(cov, dtype=float) + if cov.ndim < 2: + dim = 1 + else: + dim = cov.shape[0] + else: + mean = np.asarray(mean, dtype=float) + dim = mean.size + else: + if not np.isscalar(dim): + raise ValueError("Dimension of random variable must be " + "a scalar.") + + # Check input sizes and return full arrays for mean and cov if + # necessary + if mean is None: + mean = np.zeros(dim) + mean = np.asarray(mean, dtype=float) + + if cov is None: + cov = 1.0 + cov = np.asarray(cov, dtype=float) + + if dim == 1: + mean = mean.reshape(1) + cov = cov.reshape(1, 1) + + if mean.ndim != 1 or mean.shape[0] != dim: + raise ValueError(f"Array 'mean' must be a vector of length {dim}.") + if cov.ndim == 0: + cov = cov * np.eye(dim) + elif cov.ndim == 1: + cov = np.diag(cov) + elif cov.ndim == 2 and cov.shape != (dim, dim): + rows, cols = cov.shape + if rows != cols: + msg = ("Array 'cov' must be square if it is two dimensional," + f" but cov.shape = {str(cov.shape)}.") + else: + msg = (f"Dimension mismatch: array 'cov' is of shape {cov.shape}, " + f"but 'mean' is a vector of length {len(mean)}.") + raise ValueError(msg) + elif cov.ndim > 2: + raise ValueError(f"Array 'cov' must be at most two-dimensional, " + f"but cov.ndim = {cov.ndim}") + + return dim, mean, cov + + def _process_quantiles(self, x, dim): + """ + Adjust quantiles array so that last axis labels the components of + each data point. + """ + x = np.asarray(x, dtype=float) + + if x.ndim == 0: + x = x[np.newaxis] + elif x.ndim == 1: + if dim == 1: + x = x[:, np.newaxis] + else: + x = x[np.newaxis, :] + + return x + + def _logpdf(self, x, mean, cov_object): + """Log of the multivariate normal probability density function. + + Parameters + ---------- + x : ndarray + Points at which to evaluate the log of the probability + density function + mean : ndarray + Mean of the distribution + cov_object : Covariance + An object representing the Covariance matrix + + Notes + ----- + As this function does no argument checking, it should not be + called directly; use 'logpdf' instead. + + """ + log_det_cov, rank = cov_object.log_pdet, cov_object.rank + dev = x - mean + if dev.ndim > 1: + log_det_cov = log_det_cov[..., np.newaxis] + rank = rank[..., np.newaxis] + maha = np.sum(np.square(cov_object.whiten(dev)), axis=-1) + return -0.5 * (rank * _LOG_2PI + log_det_cov + maha) + + def logpdf(self, x, mean=None, cov=1, allow_singular=False): + """Log of the multivariate normal probability density function. + + Parameters + ---------- + x : array_like + Quantiles, with the last axis of `x` denoting the components. + %(_mvn_doc_default_callparams)s + + Returns + ------- + pdf : ndarray or scalar + Log of the probability density function evaluated at `x` + + Notes + ----- + %(_mvn_doc_callparams_note)s + + """ + params = self._process_parameters(mean, cov, allow_singular) + dim, mean, cov_object = params + x = self._process_quantiles(x, dim) + out = self._logpdf(x, mean, cov_object) + if np.any(cov_object.rank < dim): + out_of_bounds = ~cov_object._support_mask(x-mean) + out[out_of_bounds] = -np.inf + return _squeeze_output(out) + + def pdf(self, x, mean=None, cov=1, allow_singular=False): + """Multivariate normal probability density function. + + Parameters + ---------- + x : array_like + Quantiles, with the last axis of `x` denoting the components. + %(_mvn_doc_default_callparams)s + + Returns + ------- + pdf : ndarray or scalar + Probability density function evaluated at `x` + + Notes + ----- + %(_mvn_doc_callparams_note)s + + """ + params = self._process_parameters(mean, cov, allow_singular) + dim, mean, cov_object = params + x = self._process_quantiles(x, dim) + out = np.exp(self._logpdf(x, mean, cov_object)) + if np.any(cov_object.rank < dim): + out_of_bounds = ~cov_object._support_mask(x-mean) + out[out_of_bounds] = 0.0 + return _squeeze_output(out) + + def _cdf(self, x, mean, cov, maxpts, abseps, releps, lower_limit, rng): + """Multivariate normal cumulative distribution function. + + Parameters + ---------- + x : ndarray + Points at which to evaluate the cumulative distribution function. + mean : ndarray + Mean of the distribution + cov : array_like + Covariance matrix of the distribution + maxpts : int + The maximum number of points to use for integration + abseps : float + Absolute error tolerance + releps : float + Relative error tolerance + lower_limit : array_like, optional + Lower limit of integration of the cumulative distribution function. + Default is negative infinity. Must be broadcastable with `x`. + rng : Generator + an instance of ``np.random.Generator``, which is used internally + for QMC integration. + + Notes + ----- + As this function does no argument checking, it should not be + called directly; use 'cdf' instead. + + + .. versionadded:: 1.0.0 + + """ + lower = (np.full(mean.shape, -np.inf) + if lower_limit is None else lower_limit) + # In 2d, _mvn.mvnun accepts input in which `lower` bound elements + # are greater than `x`. Not so in other dimensions. Fix this by + # ensuring that lower bounds are indeed lower when passed, then + # set signs of resulting CDF manually. + b, a = np.broadcast_arrays(x, lower) + b, a = b - mean, a - mean # _qmvn only accepts zero mean + i_swap = b < a + signs = (-1)**(i_swap.sum(axis=-1)) # odd # of swaps -> negative + a, b = a.copy(), b.copy() + a[i_swap], b[i_swap] = b[i_swap], a[i_swap] + n = x.shape[-1] + limits = np.concatenate((a, b), axis=-1) + + # qmvn expects 1-d arguments, so process points sequentially + # XXX: if cov.ndim == 2 and limits.ndim == 1, can avoid apply_along_axis + def func1d(limits): + # res0 = _qmvn(maxpts, cov, limits[:n], limits[n:], rng)[0] + res = _qauto(_qmvn, cov, limits[:n], limits[n:], + rng, error=abseps, limit=maxpts, n_batches=10) + return np.squeeze(res[0]) + + out = np.apply_along_axis(func1d, -1, limits) * signs + return _squeeze_output(out) + + def logcdf(self, x, mean=None, cov=1, allow_singular=False, maxpts=None, + abseps=1e-5, releps=1e-5, *, lower_limit=None, rng=None): + """Log of the multivariate normal cumulative distribution function. + + Parameters + ---------- + x : array_like + Quantiles, with the last axis of `x` denoting the components. + %(_mvn_doc_default_callparams)s + maxpts : int, optional + The maximum number of points to use for integration + (default ``1000000*dim``) + abseps : float, optional + Absolute error tolerance (default 1e-5) + releps : float, optional + Relative error tolerance (default 1e-5) + lower_limit : array_like, optional + Lower limit of integration of the cumulative distribution function. + Default is negative infinity. Must be broadcastable with `x`. + rng : Generator, optional + an instance of ``np.random.Generator``, which is used internally + for QMC integration. + + Returns + ------- + cdf : ndarray or scalar + Log of the cumulative distribution function evaluated at `x` + + Notes + ----- + %(_mvn_doc_callparams_note)s + + .. versionadded:: 1.0.0 + + """ + params = self._process_parameters(mean, cov, allow_singular) + dim, mean, cov_object = params + cov = cov_object.covariance + x = self._process_quantiles(x, dim) + if not maxpts: + maxpts = 1000000 * dim + + rng = self._get_random_state(rng) + cdf = self._cdf(x, mean, cov, maxpts, abseps, releps, lower_limit, rng) + # the log of a negative real is complex, and cdf can be negative + # if lower limit is greater than upper limit + cdf = cdf + 0j if np.any(cdf < 0) else cdf + out = np.log(cdf) + return out + + def cdf(self, x, mean=None, cov=1, allow_singular=False, maxpts=None, + abseps=1e-5, releps=1e-5, *, lower_limit=None, rng=None): + """Multivariate normal cumulative distribution function. + + Parameters + ---------- + x : array_like + Quantiles, with the last axis of `x` denoting the components. + %(_mvn_doc_default_callparams)s + maxpts : int, optional + The maximum number of points to use for integration + (default ``1000000*dim``) + abseps : float, optional + Absolute error tolerance (default 1e-5) + releps : float, optional + Relative error tolerance (default 1e-5) + lower_limit : array_like, optional + Lower limit of integration of the cumulative distribution function. + Default is negative infinity. Must be broadcastable with `x`. + rng : Generator, optional + an instance of ``np.random.Generator``, which is used internally + for QMC integration. + + Returns + ------- + cdf : ndarray or scalar + Cumulative distribution function evaluated at `x` + + Notes + ----- + %(_mvn_doc_callparams_note)s + + .. versionadded:: 1.0.0 + + """ + params = self._process_parameters(mean, cov, allow_singular) + dim, mean, cov_object = params + cov = cov_object.covariance + x = self._process_quantiles(x, dim) + if not maxpts: + maxpts = 1000000 * dim + rng = self._get_random_state(rng) + out = self._cdf(x, mean, cov, maxpts, abseps, releps, lower_limit, rng) + return out + + def rvs(self, mean=None, cov=1, size=1, random_state=None): + """Draw random samples from a multivariate normal distribution. + + Parameters + ---------- + %(_mvn_doc_default_callparams)s + size : int, optional + Number of samples to draw (default 1). + %(_doc_random_state)s + + Returns + ------- + rvs : ndarray or scalar + Random variates of size (`size`, `N`), where `N` is the + dimension of the random variable. + + Notes + ----- + %(_mvn_doc_callparams_note)s + + """ + dim, mean, cov_object = self._process_parameters(mean, cov) + random_state = self._get_random_state(random_state) + + if isinstance(cov_object, _covariance.CovViaPSD): + cov = cov_object.covariance + out = random_state.multivariate_normal(mean, cov, size) + out = _squeeze_output(out) + else: + size = size or tuple() + if not np.iterable(size): + size = (size,) + shape = tuple(size) + (cov_object.shape[-1],) + x = random_state.normal(size=shape) + out = mean + cov_object.colorize(x) + return out + + def entropy(self, mean=None, cov=1): + """Compute the differential entropy of the multivariate normal. + + Parameters + ---------- + %(_mvn_doc_default_callparams)s + + Returns + ------- + h : scalar + Entropy of the multivariate normal distribution + + Notes + ----- + %(_mvn_doc_callparams_note)s + + """ + dim, mean, cov_object = self._process_parameters(mean, cov) + return 0.5 * (cov_object.rank * (_LOG_2PI + 1) + cov_object.log_pdet) + + def fit(self, x, fix_mean=None, fix_cov=None): + """Fit a multivariate normal distribution to data. + + Parameters + ---------- + x : ndarray (m, n) + Data the distribution is fitted to. Must have two axes. + The first axis of length `m` represents the number of vectors + the distribution is fitted to. The second axis of length `n` + determines the dimensionality of the fitted distribution. + fix_mean : ndarray(n, ) + Fixed mean vector. Must have length `n`. + fix_cov: ndarray (n, n) + Fixed covariance matrix. Must have shape ``(n, n)``. + + Returns + ------- + mean : ndarray (n, ) + Maximum likelihood estimate of the mean vector + cov : ndarray (n, n) + Maximum likelihood estimate of the covariance matrix + + """ + # input validation for data to be fitted + x = np.asarray(x) + if x.ndim != 2: + raise ValueError("`x` must be two-dimensional.") + + n_vectors, dim = x.shape + + # parameter estimation + # reference: https://home.ttic.edu/~shubhendu/Slides/Estimation.pdf + if fix_mean is not None: + # input validation for `fix_mean` + fix_mean = np.atleast_1d(fix_mean) + if fix_mean.shape != (dim, ): + msg = ("`fix_mean` must be a one-dimensional array the same " + "length as the dimensionality of the vectors `x`.") + raise ValueError(msg) + mean = fix_mean + else: + mean = x.mean(axis=0) + + if fix_cov is not None: + # input validation for `fix_cov` + fix_cov = np.atleast_2d(fix_cov) + # validate shape + if fix_cov.shape != (dim, dim): + msg = ("`fix_cov` must be a two-dimensional square array " + "of same side length as the dimensionality of the " + "vectors `x`.") + raise ValueError(msg) + # validate positive semidefiniteness + # a trimmed down copy from _PSD + s, u = scipy.linalg.eigh(fix_cov, lower=True, check_finite=True) + eps = _eigvalsh_to_eps(s) + if np.min(s) < -eps: + msg = "`fix_cov` must be symmetric positive semidefinite." + raise ValueError(msg) + cov = fix_cov + else: + centered_data = x - mean + cov = centered_data.T @ centered_data / n_vectors + return mean, cov + + def marginal(self, dimensions, mean=None, cov=1, allow_singular=False): + """Return a marginal multivariate normal distribution. + + Parameters + ---------- + dimensions : int or 1-d array_like + The dimensions of the multivariate distribution corresponding + with the marginal variables, that is, the indices of the dimensions + that are being retained. The other dimensions are marginalized out. + %(_mvn_doc_default_callparams)s + + Returns + ------- + marginal_multivariate_normal : multivariate_normal_frozen + An object representing the marginal distribution. + + Notes + ----- + %(_mvn_doc_callparams_note)s + """ + params = self._process_parameters(mean, cov, allow_singular) + n, mean, cov_object = params + dims = _validate_marginal_input(dimensions, n) + + mean = mean[dims] + cov = cov_object.covariance[np.ix_(dims, dims)] + + return multivariate_normal_frozen(mean, cov, allow_singular) + +multivariate_normal = multivariate_normal_gen() + + +class multivariate_normal_frozen(multi_rv_frozen): + __class_getitem__ = None # pyrefly:ignore[bad-assignment] + + def __init__(self, mean=None, cov=1, allow_singular=False, seed=None, + maxpts=None, abseps=1e-5, releps=1e-5): + """Create a frozen multivariate normal distribution. + + Parameters + ---------- + mean : array_like, default: ``[0]`` + Mean of the distribution. + cov : array_like, default: ``[1]`` + Symmetric positive (semi)definite covariance matrix of the + distribution. + allow_singular : bool, default: ``False`` + Whether to allow a singular covariance matrix. + seed : {None, int, `numpy.random.Generator`, `numpy.random.RandomState`}, optional + If `seed` is None (or `np.random`), the `numpy.random.RandomState` + singleton is used. + If `seed` is an int, a new ``RandomState`` instance is used, + seeded with `seed`. + If `seed` is already a ``Generator`` or ``RandomState`` instance + then that instance is used. + maxpts : int, optional + The maximum number of points to use for integration of the + cumulative distribution function (default ``1000000*dim``) + abseps : float, optional + Absolute error tolerance for the cumulative distribution function + (default 1e-5) + releps : float, optional + Relative error tolerance for the cumulative distribution function + (default 1e-5) + + Examples + -------- + When called with the default parameters, this will create a 1D random + variable with mean 0 and covariance 1: + + >>> from scipy.stats import multivariate_normal + >>> r = multivariate_normal() + >>> r.mean + array([ 0.]) + >>> r.cov + array([[1.]]) + + """ # numpy/numpydoc#87 # noqa: E501 + self._dist = multivariate_normal_gen(seed) + self.dim, self.mean, self.cov_object = ( + self._dist._process_parameters(mean, cov, allow_singular)) + self.allow_singular = allow_singular or self.cov_object._allow_singular + if not maxpts: + maxpts = 1000000 * self.dim + self.maxpts = maxpts + self.abseps = abseps + self.releps = releps + + @property + def cov(self): + return self.cov_object.covariance + + def logpdf(self, x): + x = self._dist._process_quantiles(x, self.dim) + out = self._dist._logpdf(x, self.mean, self.cov_object) + if np.any(self.cov_object.rank < self.dim): + out_of_bounds = ~self.cov_object._support_mask(x-self.mean) + out[out_of_bounds] = -np.inf + return _squeeze_output(out) + + def pdf(self, x): + return np.exp(self.logpdf(x)) + + def logcdf(self, x, *, lower_limit=None, rng=None): + cdf = self.cdf(x, lower_limit=lower_limit, rng=rng) + # the log of a negative real is complex, and cdf can be negative + # if lower limit is greater than upper limit + cdf = cdf + 0j if np.any(cdf < 0) else cdf + out = np.log(cdf) + return out + + def cdf(self, x, *, lower_limit=None, rng=None): + x = self._dist._process_quantiles(x, self.dim) + rng = self._dist._get_random_state(rng) + out = self._dist._cdf(x, self.mean, self.cov_object.covariance, + self.maxpts, self.abseps, self.releps, + lower_limit, rng) + return _squeeze_output(out) + + def rvs(self, size=1, random_state=None): + return self._dist.rvs(self.mean, self.cov_object, size, random_state) + + def entropy(self): + """Computes the differential entropy of the multivariate normal. + + Returns + ------- + h : scalar + Entropy of the multivariate normal distribution + + """ + log_pdet = self.cov_object.log_pdet + rank = self.cov_object.rank + return 0.5 * (rank * (_LOG_2PI + 1) + log_pdet) + + def marginal(self, dimensions): + return self._dist.marginal(dimensions, self.mean, + self.cov_object, self.allow_singular) + +# Set frozen generator docstrings from corresponding docstrings in +# multivariate_normal_gen and fill in default strings in class docstrings +for name in ['logpdf', 'pdf', 'logcdf', 'cdf', 'rvs']: + method = multivariate_normal_gen.__dict__[name] + method_frozen = multivariate_normal_frozen.__dict__[name] + method_frozen.__doc__ = doccer.docformat(method.__doc__, + mvn_docdict_noparams) + method.__doc__ = doccer.docformat(method.__doc__, mvn_docdict_params) + +_matnorm_doc_default_callparams = """\ +mean : array_like, optional + Mean of the distribution (default: `None`) +rowcov : array_like, optional + Among-row covariance matrix of the distribution (default: ``1``) +colcov : array_like, optional + Among-column covariance matrix of the distribution (default: ``1``) +""" + +_matnorm_doc_callparams_note = """\ +If `mean` is set to `None` then a matrix of zeros is used for the mean. +The dimensions of this matrix are inferred from the shape of `rowcov` and +`colcov`, if these are provided, or set to ``1`` if ambiguous. + +`rowcov` and `colcov` can be two-dimensional array_likes specifying the +covariance matrices directly. Alternatively, a one-dimensional array will +be be interpreted as the entries of a diagonal matrix, and a scalar or +zero-dimensional array will be interpreted as this value times the +identity matrix. +""" + +_matnorm_doc_frozen_callparams = "" + +_matnorm_doc_frozen_callparams_note = """\ +See class definition for a detailed description of parameters.""" + +matnorm_docdict_params = { + '_matnorm_doc_default_callparams': _matnorm_doc_default_callparams, + '_matnorm_doc_callparams_note': _matnorm_doc_callparams_note, + '_doc_random_state': _doc_random_state +} + +matnorm_docdict_noparams = { + '_matnorm_doc_default_callparams': _matnorm_doc_frozen_callparams, + '_matnorm_doc_callparams_note': _matnorm_doc_frozen_callparams_note, + '_doc_random_state': _doc_random_state +} + + +class matrix_normal_gen(multi_rv_generic): + r""" + A matrix normal random variable. + + The `mean` keyword specifies the mean. The `rowcov` keyword specifies the + among-row covariance matrix. The 'colcov' keyword specifies the + among-column covariance matrix. + + Parameters + ---------- + %(_matnorm_doc_default_callparams)s + %(_doc_random_state)s + + Methods + ------- + pdf(X, mean=None, rowcov=1, colcov=1) + Probability density function. + logpdf(X, mean=None, rowcov=1, colcov=1) + Log of the probability density function. + rvs(mean=None, rowcov=1, colcov=1, size=1, random_state=None) + Draw random samples. + entropy(rowcol=1, colcov=1) + Differential entropy. + + Notes + ----- + %(_matnorm_doc_callparams_note)s + + The covariance matrices specified by `rowcov` and `colcov` must be + (symmetric) positive definite. If the samples in `X` are + :math:`m \times n`, then `rowcov` must be :math:`m \times m` and + `colcov` must be :math:`n \times n`. `mean` must be the same shape as `X`. + + The probability density function for `matrix_normal` is + + .. math:: + + f(X) = (2 \pi)^{-\frac{mn}{2}}|U|^{-\frac{n}{2}} |V|^{-\frac{m}{2}} + \exp\left( -\frac{1}{2} \mathrm{Tr}\left[ U^{-1} (X-M) V^{-1} + (X-M)^T \right] \right), + + where :math:`M` is the mean, :math:`U` the among-row covariance matrix, + :math:`V` the among-column covariance matrix. + + The `allow_singular` behaviour of the `multivariate_normal` + distribution is not currently supported. Covariance matrices must be + full rank. + + The `matrix_normal` distribution is closely related to the + `multivariate_normal` distribution. Specifically, :math:`\mathrm{Vec}(X)` + (the vector formed by concatenating the columns of :math:`X`) has a + multivariate normal distribution with mean :math:`\mathrm{Vec}(M)` + and covariance :math:`V \otimes U` (where :math:`\otimes` is the Kronecker + product). Sampling and pdf evaluation are + :math:`\mathcal{O}(m^3 + n^3 + m^2 n + m n^2)` for the matrix normal, but + :math:`\mathcal{O}(m^3 n^3)` for the equivalent multivariate normal, + making this equivalent form algorithmically inefficient. + + .. versionadded:: 0.17.0 + + References + ---------- + .. [1] "Matrix normal distribution", Wikipedia, + https://en.wikipedia.org/wiki/Matrix_normal_distribution + + Examples + -------- + + >>> import numpy as np + >>> from scipy.stats import matrix_normal + + >>> M = np.arange(6).reshape(3,2); M + array([[0, 1], + [2, 3], + [4, 5]]) + >>> U = np.diag([1,2,3]); U + array([[1, 0, 0], + [0, 2, 0], + [0, 0, 3]]) + >>> V = 0.3*np.identity(2); V + array([[ 0.3, 0. ], + [ 0. , 0.3]]) + >>> X = M + 0.1; X + array([[ 0.1, 1.1], + [ 2.1, 3.1], + [ 4.1, 5.1]]) + >>> matrix_normal.pdf(X, mean=M, rowcov=U, colcov=V) + 0.023410202050005054 + + >>> # Equivalent multivariate normal + >>> from scipy.stats import multivariate_normal + >>> vectorised_X = X.T.flatten() + >>> equiv_mean = M.T.flatten() + >>> equiv_cov = np.kron(V,U) + >>> multivariate_normal.pdf(vectorised_X, mean=equiv_mean, cov=equiv_cov) + 0.023410202050005054 + + Alternatively, the object may be called (as a function) to fix the mean + and covariance parameters, returning a "frozen" matrix normal + random variable: + + >>> rv = matrix_normal(mean=None, rowcov=1, colcov=1) + >>> # Frozen object with the same methods but holding the given + >>> # mean and covariance fixed. + """ + + def __init__(self, seed=None): + super().__init__(seed) + self.__doc__ = doccer.docformat(self.__doc__, matnorm_docdict_params) + + def __call__(self, mean=None, rowcov=1, colcov=1, seed=None): + """Create a frozen matrix normal distribution. + + See `matrix_normal_frozen` for more information. + + """ + return matrix_normal_frozen(mean, rowcov, colcov, seed=seed) + + def _process_parameters(self, mean, rowcov, colcov): + """ + Infer dimensionality from mean or covariance matrices. Handle + defaults. Ensure compatible dimensions. + """ + + # Process mean + if mean is not None: + mean = np.asarray(mean, dtype=float) + meanshape = mean.shape + if len(meanshape) != 2: + raise ValueError("Array `mean` must be two dimensional.") + if np.any(meanshape == 0): + raise ValueError("Array `mean` has invalid shape.") + + # Process among-row covariance + rowcov = np.asarray(rowcov, dtype=float) + if rowcov.ndim == 0: + if mean is not None: + rowcov = rowcov * np.identity(meanshape[0]) + else: + rowcov = rowcov * np.identity(1) + elif rowcov.ndim == 1: + rowcov = np.diag(rowcov) + rowshape = rowcov.shape + if len(rowshape) != 2: + raise ValueError("`rowcov` must be a scalar or a 2D array.") + if rowshape[0] != rowshape[1]: + raise ValueError("Array `rowcov` must be square.") + if rowshape[0] == 0: + raise ValueError("Array `rowcov` has invalid shape.") + numrows = rowshape[0] + + # Process among-column covariance + colcov = np.asarray(colcov, dtype=float) + if colcov.ndim == 0: + if mean is not None: + colcov = colcov * np.identity(meanshape[1]) + else: + colcov = colcov * np.identity(1) + elif colcov.ndim == 1: + colcov = np.diag(colcov) + colshape = colcov.shape + if len(colshape) != 2: + raise ValueError("`colcov` must be a scalar or a 2D array.") + if colshape[0] != colshape[1]: + raise ValueError("Array `colcov` must be square.") + if colshape[0] == 0: + raise ValueError("Array `colcov` has invalid shape.") + numcols = colshape[0] + + # Ensure mean and covariances compatible + if mean is not None: + if meanshape[0] != numrows: + raise ValueError("Arrays `mean` and `rowcov` must have the " + "same number of rows.") + if meanshape[1] != numcols: + raise ValueError("Arrays `mean` and `colcov` must have the " + "same number of columns.") + else: + mean = np.zeros((numrows, numcols)) + + dims = (numrows, numcols) + + return dims, mean, rowcov, colcov + + def _process_quantiles(self, X, dims): + """ + Adjust quantiles array so that last two axes labels the components of + each data point. + """ + X = np.asarray(X, dtype=float) + if X.ndim == 2: + X = X[np.newaxis, :] + if X.shape[-2:] != dims: + raise ValueError("The shape of array `X` is not compatible " + "with the distribution parameters.") + return X + + def _logpdf(self, dims, X, mean, row_prec_rt, log_det_rowcov, + col_prec_rt, log_det_colcov): + """Log of the matrix normal probability density function. + + Parameters + ---------- + dims : tuple + Dimensions of the matrix variates + X : ndarray + Points at which to evaluate the log of the probability + density function + mean : ndarray + Mean of the distribution + row_prec_rt : ndarray + A decomposition such that np.dot(row_prec_rt, row_prec_rt.T) + is the inverse of the among-row covariance matrix + log_det_rowcov : float + Logarithm of the determinant of the among-row covariance matrix + col_prec_rt : ndarray + A decomposition such that np.dot(col_prec_rt, col_prec_rt.T) + is the inverse of the among-column covariance matrix + log_det_colcov : float + Logarithm of the determinant of the among-column covariance matrix + + Notes + ----- + As this function does no argument checking, it should not be + called directly; use 'logpdf' instead. + + """ + numrows, numcols = dims + roll_dev = np.moveaxis(X-mean, -1, 0) + scale_dev = np.tensordot(col_prec_rt.T, + np.dot(roll_dev, row_prec_rt), 1) + maha = np.sum(np.sum(np.square(scale_dev), axis=-1), axis=0) + return -0.5 * (numrows*numcols*_LOG_2PI + numcols*log_det_rowcov + + numrows*log_det_colcov + maha) + + def logpdf(self, X, mean=None, rowcov=1, colcov=1): + """Log of the matrix normal probability density function. + + Parameters + ---------- + X : array_like + Quantiles, with the last two axes of `X` denoting the components. + %(_matnorm_doc_default_callparams)s + + Returns + ------- + logpdf : ndarray + Log of the probability density function evaluated at `X` + + Notes + ----- + %(_matnorm_doc_callparams_note)s + + """ + dims, mean, rowcov, colcov = self._process_parameters(mean, rowcov, + colcov) + X = self._process_quantiles(X, dims) + rowpsd = _PSD(rowcov, allow_singular=False) + colpsd = _PSD(colcov, allow_singular=False) + out = self._logpdf(dims, X, mean, rowpsd.U, rowpsd.log_pdet, colpsd.U, + colpsd.log_pdet) + return _squeeze_output(out) + + def pdf(self, X, mean=None, rowcov=1, colcov=1): + """Matrix normal probability density function. + + Parameters + ---------- + X : array_like + Quantiles, with the last two axes of `X` denoting the components. + %(_matnorm_doc_default_callparams)s + + Returns + ------- + pdf : ndarray + Probability density function evaluated at `X` + + Notes + ----- + %(_matnorm_doc_callparams_note)s + + """ + return np.exp(self.logpdf(X, mean, rowcov, colcov)) + + def rvs(self, mean=None, rowcov=1, colcov=1, size=1, random_state=None): + """Draw random samples from a matrix normal distribution. + + Parameters + ---------- + %(_matnorm_doc_default_callparams)s + size : int, optional + Number of samples to draw (default 1). + %(_doc_random_state)s + + Returns + ------- + rvs : ndarray or scalar + Random variates of size (`size`, `dims`), where `dims` is the + dimension of the random matrices. + + Notes + ----- + %(_matnorm_doc_callparams_note)s + + """ + size = int(size) + dims, mean, rowcov, colcov = self._process_parameters(mean, rowcov, + colcov) + rowchol = scipy.linalg.cholesky(rowcov, lower=True) + colchol = scipy.linalg.cholesky(colcov, lower=True) + random_state = self._get_random_state(random_state) + # We aren't generating standard normal variates with size=(size, + # dims[0], dims[1]) directly to ensure random variates remain backwards + # compatible. See https://github.com/scipy/scipy/pull/12312 for more + # details. + std_norm = random_state.standard_normal( + size=(dims[1], size, dims[0]) + ).transpose(1, 2, 0) + out = mean + np.einsum('jp,ipq,kq->ijk', + rowchol, std_norm, colchol, + optimize=True) + if size == 1: + out = out.reshape(mean.shape) + return out + + def entropy(self, rowcov=1, colcov=1): + """Log of the matrix normal probability density function. + + Parameters + ---------- + rowcov : array_like, optional + Among-row covariance matrix of the distribution (default: ``1``) + colcov : array_like, optional + Among-column covariance matrix of the distribution (default: ``1``) + + Returns + ------- + entropy : float + Entropy of the distribution + + Notes + ----- + %(_matnorm_doc_callparams_note)s + + """ + dummy_mean = np.zeros((rowcov.shape[0], colcov.shape[0])) + dims, _, rowcov, colcov = self._process_parameters(dummy_mean, + rowcov, + colcov) + rowpsd = _PSD(rowcov, allow_singular=False) + colpsd = _PSD(colcov, allow_singular=False) + + return self._entropy(dims, rowpsd.log_pdet, colpsd.log_pdet) + + def _entropy(self, dims, row_cov_logdet, col_cov_logdet): + n, p = dims + return (0.5 * n * p * (1 + _LOG_2PI) + 0.5 * p * row_cov_logdet + + 0.5 * n * col_cov_logdet) + + +matrix_normal = matrix_normal_gen() + + +class matrix_normal_frozen(multi_rv_frozen): + """ + Create a frozen matrix normal distribution. + + Parameters + ---------- + %(_matnorm_doc_default_callparams)s + seed : {None, int, `numpy.random.Generator`, `numpy.random.RandomState`}, optional + If `seed` is `None` the `~np.random.RandomState` singleton is used. + If `seed` is an int, a new ``RandomState`` instance is used, seeded + with seed. + If `seed` is already a ``RandomState`` or ``Generator`` instance, + then that object is used. + Default is `None`. + + Examples + -------- + >>> import numpy as np + >>> from scipy.stats import matrix_normal + + >>> distn = matrix_normal(mean=np.zeros((3,3))) + >>> X = distn.rvs(); X + array([[-0.02976962, 0.93339138, -0.09663178], + [ 0.67405524, 0.28250467, -0.93308929], + [-0.31144782, 0.74535536, 1.30412916]]) + >>> distn.pdf(X) + 2.5160642368346784e-05 + >>> distn.logpdf(X) + -10.590229595124615 + """ + __class_getitem__ = None # pyrefly:ignore[bad-assignment] + + def __init__(self, mean=None, rowcov=1, colcov=1, seed=None): + self._dist = matrix_normal_gen(seed) + self.dims, self.mean, self.rowcov, self.colcov = \ + self._dist._process_parameters(mean, rowcov, colcov) + self.rowpsd = _PSD(self.rowcov, allow_singular=False) + self.colpsd = _PSD(self.colcov, allow_singular=False) + + def logpdf(self, X): + X = self._dist._process_quantiles(X, self.dims) + out = self._dist._logpdf(self.dims, X, self.mean, self.rowpsd.U, + self.rowpsd.log_pdet, self.colpsd.U, + self.colpsd.log_pdet) + return _squeeze_output(out) + + def pdf(self, X): + return np.exp(self.logpdf(X)) + + def rvs(self, size=1, random_state=None): + return self._dist.rvs(self.mean, self.rowcov, self.colcov, size, + random_state) + + def entropy(self): + return self._dist._entropy(self.dims, self.rowpsd.log_pdet, + self.colpsd.log_pdet) + +# Set frozen generator docstrings from corresponding docstrings in +# matrix_normal_gen and fill in default strings in class docstrings +for name in ['logpdf', 'pdf', 'rvs', 'entropy']: + method = matrix_normal_gen.__dict__[name] + method_frozen = matrix_normal_frozen.__dict__[name] + method_frozen.__doc__ = doccer.docformat(method.__doc__, + matnorm_docdict_noparams) + method.__doc__ = doccer.docformat(method.__doc__, matnorm_docdict_params) + + +_matt_doc_default_callparams = """\ +mean : array_like, optional + Mean of the distribution (default: `None`) +row_spread : array_like, optional + Row-wise 2nd order raw central moment matrix (default: ``1``) +col_spread : array_like, optional + Column-wise 2nd order raw central moment matrix (default: ``1``) +df : scalar, optional + Degrees of freedom (default: ``1``) +""" + +_matt_doc_callparams_note = """\ +If `mean` is set to `None` then a matrix of zeros is used for the mean. +The dimensions of this matrix are inferred from the shape of `row_spread` and +`col_spread`, if these are provided, or set to ``1`` if ambiguous. + +`row_spread` and `col_spread` can be two-dimensional array_likes specifying the +spread matrices directly. Alternatively, a one-dimensional array will +be be interpreted as the entries of a diagonal matrix, and a scalar or +zero-dimensional array will be interpreted as this value times the +identity matrix. +""" + +_matt_doc_frozen_callparams = "" + +_matt_doc_frozen_callparams_note = """\ +See class definition for a detailed description of parameters.""" + +matrix_t_docdict_params = { + "_matt_doc_default_callparams": _matt_doc_default_callparams, + "_matt_doc_callparams_note": _matt_doc_callparams_note, + "_doc_random_state": _doc_random_state, +} + +matrix_t_docdict_noparams = { + "_matt_doc_default_callparams": _matt_doc_frozen_callparams, + "_matt_doc_callparams_note": _matt_doc_frozen_callparams_note, + "_doc_random_state": _doc_random_state, +} + + +class matrix_t_gen(multi_rv_generic): + r""" + A matrix t-random variable. + + The `mean` keyword specifies the mean. + The `row_spread` keyword specifies the row-wise spread matrix. + The `col_spread` keyword specifies the column-wise spread matrix. + + Parameters + ---------- + %(_matt_doc_default_callparams)s + %(_doc_random_state)s + + Methods + ------- + pdf(x, mean=None, row_spread=None, col_spread=None) + Probability density function. + logpdf(x, mean=None, row_spread=None, col_spread=None) + Log of the probability density function. + rvs(mean=None, row_spread=1, col_spread=1, df=1, size=1, random_state=None) + Draw random samples. + + Notes + ----- + %(_matt_doc_callparams_note)s + + The spread matrices specified by `row_spread` and `col_spread` must be + (symmetric) positive definite. If the samples in `X` have shape `(m,n)` + then `row_spread` must have shape `(m,m)` and `col_spread` must have shape `(n,n)`. + Spread matrices must be full rank. + + The probability density function for `matrix_t` is + + .. math:: + + f(X \vert \mathrm{M}, \Sigma, \Omega, \mathrm{df}) = + \frac{ + \Gamma_n \left( + \frac{\mathrm{df} + m + n - 1}{2} + \right) + \left( + \det \left( + I_n + (X - \mathrm{M})^T \Sigma^{-1} (X - \mathrm{M}) \Omega^{-1} + \right) + \right)^{ -\frac{\mathrm{df} + m + n - 1}{2} } + }{ + \Gamma_n \left( + \frac{\mathrm{df} + n - 1}{2} + \right) + \pi^{mn / 2} + \left( \det \Sigma \right)^{n/2} + \left( \det \Omega \right)^{m/2} + } + + or, alternatively, + + .. math:: + + f(X \vert \mathrm{M}, \Sigma, \Omega, \mathrm{df}) = + \frac{ + \Gamma_m \left( + \frac{\mathrm{df} + m + n - 1}{2} + \right) + \left( + \det \left( + I_m + \Sigma^{-1} (X - \mathrm{M}) \Omega^{-1} (X - \mathrm{M})^T + \right) + \right)^{ -\frac{\mathrm{df} + m + n - 1}{2} } + }{ + \Gamma_m \left( + \frac{\mathrm{df} + n - 1}{2} + \right) + \pi^{mn / 2} + \left( \det \Sigma \right)^{n/2} + \left( \det \Omega \right)^{m/2} + } + + where :math:`\mathrm{M}` is the mean, + :math:`\Sigma` is the row-wise spread matrix, + :math:`\Omega` is the column-wise matrix, + :math:`\mathrm{df}` is the degrees of freedom, + and :math:`\Gamma_n` is the multivariate gamma function. + + These equivalent formulations come from the identity + + .. math:: + + \det\left( I_m + A B \right) = \det\left( I_n + B A \right) + + for :math:`m \times n` arrays :math:`A` and :math:`B^T` + and the fact that + :math:`\gamma_n(\mathrm{df} + m) / \gamma_n(\mathrm{df})` + is equal to + :math:`\gamma_m(\mathrm{df} + n) / \gamma_m(\mathrm{df})`, + where + + .. math:: + + \gamma_m(\mathrm{df}) = 2^{m(m-1)/2} + \Gamma_m\left( (\mathrm{df} + m - 1) / 2 \right) + + denotes a normalized multivariate gamma function. + + When :math:`\mathrm{df} = 1` this distribution is known as the matrix + variate Cauchy. + + .. versionadded:: 1.17.0 + + References + ---------- + .. [1] Gupta, A.K., & Nagar, D.K. (2000). Matrix Variate Distributions (1st ed.). + Chapman and Hall/CRC. + + Examples + -------- + + >>> import numpy as np + >>> from scipy.stats import matrix_t + >>> M = np.arange(6).reshape(3,2) + >>> M + array([[0, 1], + [2, 3], + [4, 5]]) + >>> Sigma = np.diag([1,2,3]) + >>> Sigma + array([[1, 0, 0], + [0, 2, 0], + [0, 0, 3]]) + >>> Omega = 0.3*np.identity(2) + >>> Omega + array([[ 0.3, 0. ], + [ 0. , 0.3]]) + >>> X = M + 0.1 + >>> X + array([[ 0.1, 1.1], + [ 2.1, 3.1], + [ 4.1, 5.1]]) + >>> df = 3 + >>> matrix_t.pdf(X, mean=M, row_spread=Sigma, col_spread=Omega, df=df) + 0.9972880280135796 + + Alternatively, the object may be called (as a function) to fix the mean + and spread parameters, returning a "frozen" matrix t + random variable: + + >>> rv = matrix_t(mean=None, row_spread=1, col_spread=1, df=1) + >>> # Frozen object with the same methods but holding the given + >>> # mean and spreads and degrees of freedom fixed. + """ + + def __init__(self, seed=None): + super().__init__(seed) + self.__doc__ = scipy._lib.doccer.docformat( + self.__doc__, matrix_t_docdict_params + ) + + def __call__(self, mean=None, row_spread=1, col_spread=1, df=None, seed=None): + """Create a frozen matrix t distribution. + + See `matrix_t_frozen` for more information. + """ + return matrix_t_frozen(mean, row_spread, col_spread, df, seed) + + def _process_parameters(self, mean, row_spread, col_spread, df): + """ + Infer dimensionality from mean or covariance matrices. + Handle defaults. Ensure conformality. + + Parameters + ---------- + mean : ndarray, shape (m,n) + Mean of the distribution + row_spread : ndarray, shape (m,m) + Row-wise spread matrix + col_spread : ndarray, shape (n,n) + Column-wise spread matrix + df : float + Degrees of freedom + """ + + # Process mean + if mean is not None: + mean = np.asarray(mean, dtype=float) + meanshape = mean.shape + if 0 in meanshape: + raise ValueError("Array `mean` has invalid shape.") + if len(meanshape) != 2: + raise ValueError("Array `mean` must be 2D.") + + # Process row-wise spread + row_spread = np.asarray(row_spread, dtype=float) + if row_spread.ndim == 0: + if mean is not None: + row_spread = row_spread * np.identity(meanshape[0]) + else: + row_spread = row_spread * np.identity(1) + elif row_spread.ndim == 1: + row_spread = np.diag(row_spread) + rowshape = row_spread.shape + if 0 in rowshape: + raise ValueError("Array `row_spread` has invalid shape.") + if len(rowshape) != 2: + raise ValueError("Array `row_spread` must be a scalar or a 2D array.") + if rowshape[0] != rowshape[1]: + raise ValueError("Array `row_spread` must be square.") + numrows = rowshape[0] + + # Process column-wise spread + col_spread = np.asarray(col_spread, dtype=float) + if col_spread.ndim == 0: + if mean is not None: + col_spread = col_spread * np.identity(meanshape[1]) + else: + col_spread = col_spread * np.identity(1) + elif col_spread.ndim == 1: + col_spread = np.diag(col_spread) + colshape = col_spread.shape + if 0 in colshape: + raise ValueError("Array `col_spread` has invalid shape.") + if len(colshape) != 2: + raise ValueError("Array `col_spread` must be a scalar or a 2D array.") + if colshape[0] != colshape[1]: + raise ValueError("Array `col_spread` must be square.") + numcols = colshape[0] + + # Ensure mean and spreads are conformal + if mean is not None: + if meanshape[0] != numrows: + raise ValueError( + "Arrays `mean` and `row_spread` must have the same number of rows." + ) + if meanshape[1] != numcols: + raise ValueError( + "Arrays `mean` and `col_spread` must have the same number " + "of columns." + ) + else: + mean = np.zeros((numrows, numcols)) + + dims = (numrows, numcols) + + if df is None: + df = 1 # default to matrix variate Cauchy + elif not np.isscalar(df): + raise ValueError("Degrees of freedom must be a scalar.") + elif df <= 0: + raise ValueError("Degrees of freedom must be positive.") + + return dims, mean, row_spread, col_spread, df + + def _process_quantiles(self, X, dims): + """ + Adjust quantiles array so that last two axes labels the component of + each data point. + """ + X = np.asarray(X, dtype=float) + if X.ndim == 2: + X = X[np.newaxis, :] + if X.shape[-2:] != dims: + raise ValueError( + "The shape of array `X` is not conformal with " + "the distribution parameters." + ) + return X + + def _logpdf( + self, + dims, + X, + mean, + df, + invrow_spread, + invcol_spread, + logdetrow_spread, + logdetcol_spread, + ): + """ + Log of the matrix t probability density function. + + Parameters + ---------- + dims : tuple + Dimensions of the matrix variates + X : ndarray, shape (m,n) (equal to `dims`) + Points at which to evaluate the log of the probability density function + mean : ndarray, shape (m,n) + Mean of the distribution + df : float + Degrees-of-freedom parameter + invrow_spread : ndarray, shape (m,m) + Inverse of the row-wise spread matrix + invcol_spread : ndarray, shape (n,n) + Inverse of the column-wise spread matrix + logdetrow_spread : float + Log-determinant of the row-wise spread matrix + detcol_spread : float + Log-determinant of the column-wise spread matrix + + Notes + ----- + As this function does no argument checking, it should not be + called directly; use `logpdf` instead. + """ + m, n = dims + X_shape = X.shape + if X.ndim > 3: + X = X.reshape(-1, m, n) + X_centered = X - mean[np.newaxis, ...] + det_arg = np.identity(n) + np.einsum( + "nij,njk,nkl,nlp->nip", + X_centered.transpose(0, 2, 1), + invrow_spread[np.newaxis, ...], + X_centered, + invcol_spread[np.newaxis, ...], + optimize=True, + ) + _, logdet = np.linalg.slogdet(det_arg) + log_d_mn = -((df + m + n - 1) / 2) * logdet + log_f_mn = ( + scipy.special.multigammaln((df + m + n - 1) / 2, n) + - scipy.special.multigammaln((df + n - 1) / 2, n) + - (m * n / 2) * _LOG_PI + - (n / 2) * logdetrow_spread + - (m / 2) * logdetcol_spread + ) + retval = log_d_mn + log_f_mn + if len(X_shape) > 3: + retval = retval.reshape(X_shape[:-2]) + return retval + + def logpdf(self, X, mean=None, row_spread=1, col_spread=1, df=1): + """Log of the matrix normal probability density function. + + Parameters + ---------- + X : array_like + Quantiles, with the last two axes of `X` denoting the components. + %(_matt_doc_default_callparams)s + + Returns + ------- + logpdf : ndarray + Log of the probability density function evaluated at `X` + + Notes + ----- + %(_matt_doc_callparams_note)s + + Examples + ------- + + >>> import numpy as np + >>> from scipy.stats import matrix_t + >>> M = np.arange(6).reshape(3,2); M + array([[0, 1], + [2, 3], + [4, 5]]) + >>> Sigma = np.diag([1,2,3]); Sigma + array([[1, 0, 0], + [0, 2, 0], + [0, 0, 3]]) + >>> Omega = 0.3*np.identity(2); Omega + array([[ 0.3, 0. ], + [ 0. , 0.3]]) + >>> X = M + 0.1; X + array([[ 0.1, 1.1], + [ 2.1, 3.1], + [ 4.1, 5.1]]) + >>> df = 3; df + 3 + >>> matrix_t.logpdf(X, mean=M, row_spread=Sigma, col_spread=Omega, df=df) + -0.002715656044664061 + """ + dims, mean, row_spread, col_spread, df = self._process_parameters( + mean, row_spread, col_spread, df + ) + X = self._process_quantiles(X, dims) + rowpsd = _PSD(row_spread, allow_singular=False) + colpsd = _PSD(col_spread, allow_singular=False) + invrow_spread = rowpsd.pinv + invcol_spread = colpsd.pinv + logdetrow_spread = rowpsd.log_pdet + logdetcol_spread = colpsd.log_pdet + out = self._logpdf( + dims, + X, + mean, + df, + invrow_spread, + invcol_spread, + logdetrow_spread, + logdetcol_spread, + ) + return _squeeze_output(out) + + def pdf(self, X, mean=None, row_spread=1, col_spread=1, df=1): + """Matrix t probability density function. + + Parameters + ---------- + X : array_like + Quantiles, with the last two axes of `X` denoting the components. + %(_matt_doc_default_callparams)s + + Returns + ------- + pdf : ndarray + Probability density function evaluated at `X` + + Notes + ----- + %(_matt_doc_callparams_note)s + + Examples + -------- + + >>> import numpy as np + >>> from scipy.stats import matrix_t + >>> M = np.arange(6).reshape(3,2); M + array([[0, 1], + [2, 3], + [4, 5]]) + >>> Sigma = np.diag([1,2,3]); Sigma + array([[1, 0, 0], + [0, 2, 0], + [0, 0, 3]]) + >>> Omega = 0.3*np.identity(2); Omega + array([[ 0.3, 0. ], + [ 0. , 0.3]]) + >>> X = M + 0.1; X + array([[ 0.1, 1.1], + [ 2.1, 3.1], + [ 4.1, 5.1]]) + >>> df = 3; df + 3 + >>> matrix_t.logpdf(X, mean=M, row_spread=Sigma, col_spread=Omega, df=df) + 0.9972880280135796 + """ + return np.exp(self.logpdf(X, mean, row_spread, col_spread, df)) + + def rvs( + self, mean=None, row_spread=1, col_spread=1, df=1, size=1, random_state=None + ) -> np.ndarray: + """Draw random samples from a matrix t distribution. + + Parameters + ---------- + %(_matt_doc_default_callparams)s + size : int, optional + Number of samples to draw (default 1). + %(_doc_random_state)s + + Returns + ------- + rvs : ndarray or scalar + Random variates of size (`size`, `dims`), where `dims` is the + dimension of the random matrices. + + Notes + ----- + %(_matt_doc_callparams_note)s + + This method takes advantage of the two equivalent expressions of the + probability density function. It samples a Cholesky factor of a + random variate of the appropriate inverse Wishart distribution using + the smaller of the row/column dimensions. + """ + size = int(size) + dims, mean, row_spread, col_spread, df = self._process_parameters( + mean, row_spread, col_spread, df + ) + random_state = self._get_random_state(random_state) + # see scipy.stats.matrix_normal.rvs + std_norm = random_state.standard_normal( + size=(dims[1], size, dims[0]) + ).transpose(1, 2, 0) + if dims[0] <= dims[1]: + rowchol = _cholesky_invwishart_rvs(df, row_spread, size, random_state) + colchol = scipy.linalg.cholesky(col_spread, lower=True)[np.newaxis, ...] + else: + rowchol = scipy.linalg.cholesky(row_spread, lower=True)[np.newaxis, ...] + colchol = _cholesky_invwishart_rvs(df, col_spread, size, random_state) + t_raw = np.einsum("ijp,ipq,ikq->ijk", rowchol, std_norm, colchol, optimize=True) + t_centered = mean[np.newaxis, ...] + t_raw + if size == 1: + t_centered = t_centered.reshape(mean.shape) + return t_centered + + +matrix_t = matrix_t_gen() + + +class matrix_t_frozen: + def __init__(self, mean, row_spread, col_spread, df, seed=None): + self._dist = matrix_t_gen(seed) + self.dims, self.mean, self.row_spread, self.col_spread, self.df = ( + self._dist._process_parameters(mean, row_spread, col_spread, df) + ) + self._random_state = np.random.RandomState(seed) + self.rowpsd = _PSD(self.row_spread, allow_singular=False) + self.colpsd = _PSD(self.col_spread, allow_singular=False) + + def logpdf(self, X): + X = self._dist._process_quantiles(X, self.dims) + rowpsd = _PSD(self.row_spread, allow_singular=False) + colpsd = _PSD(self.col_spread, allow_singular=False) + invrow_spread = rowpsd.pinv + invcol_spread = colpsd.pinv + logdetrow_spread = rowpsd.log_pdet + logdetcol_spread = colpsd.log_pdet + out = self._dist._logpdf( + self.dims, + X, + self.mean, + self.df, + invrow_spread, + invcol_spread, + logdetrow_spread, + logdetcol_spread, + ) + return _squeeze_output(out) + + def pdf(self, X): + return np.exp(self.logpdf(X)) + + def rvs(self, size=1, random_state=None): + return self._dist.rvs( + self.mean, self.row_spread, self.col_spread, self.df, size, random_state + ) + + +# Set frozen generator docstrings from corresponding docstrings in +# matrix_t_gen and fill in default strings in class docstrings +for name in ["logpdf", "pdf", "rvs"]: + method = matrix_t_gen.__dict__[name] + method_frozen = matrix_t_frozen.__dict__[name] + method_frozen.__doc__ = scipy._lib.doccer.docformat( + method.__doc__, matrix_t_docdict_noparams + ) + method.__doc__ = scipy._lib.doccer.docformat( + method.__doc__, matrix_t_docdict_params + ) + + + +def _cholesky_invwishart_rvs( + df: float, scale: np.ndarray, size: int, random_state: np.random.Generator +) -> np.ndarray: + r"""Samples the lower Cholesky factor of a matrix following an inverse + Wishart distribution. + + Notes + ----- + Intended to be used *as a step in the process* for computing random variates + of a matrix t distribution :math:`\mathcal{T}_{m,n}` by appealing to its + alternative form as a matrix mixture + .. math:: + \mathcal{T}_{m,n}( \mathrm{df}, \mathrm{M}, \Sigma, \Omega ) + = \mathcal{N}_{m,n}( + \mathrm{M}, + \mathcal{W}^{-1}_m(\mathrm{df} + m - 1, \Sigma), + \Omega + ) + = \mathcal{N}_{m,n}( + \mathrm{M}, + \Sigma, + \mathcal{W}^{-1}_n(\mathrm{df} - n + 1, \Omega) + ) + where :math:`\mathcal{N}_{m,n}` is a matrix normal distribution + and :math:`\mathcal{W}^{-1}_d` is an inverse Wishart distribution. + Accordingly, the degrees of freedom adjustment + :math:`\mathrm{df} \to \mathrm{df} + d - 1` + occurrs in the scope of this function. + """ + df_iw = df + scale.shape[0] - 1 + iw_samples = scipy.stats.invwishart.rvs(df_iw, scale, size, random_state) + if size == 1: + iw_samples = iw_samples[np.newaxis, ...] + chol_samples = np.empty_like(iw_samples) + for idx in range(size): + chol_samples[idx] = scipy.linalg.cholesky( + iw_samples[idx], lower=True, check_finite=False + ).reshape(iw_samples.shape[1:]) + return chol_samples.reshape((size, *scale.shape)) + + +_dirichlet_doc_default_callparams = """\ +alpha : array_like + The concentration parameters. The number of entries determines the + dimensionality of the distribution. +""" +_dirichlet_doc_frozen_callparams = "" + +_dirichlet_doc_frozen_callparams_note = """\ +See class definition for a detailed description of parameters.""" + +dirichlet_docdict_params = { + '_dirichlet_doc_default_callparams': _dirichlet_doc_default_callparams, + '_doc_random_state': _doc_random_state +} + +dirichlet_docdict_noparams = { + '_dirichlet_doc_default_callparams': _dirichlet_doc_frozen_callparams, + '_doc_random_state': _doc_random_state +} + + +def _dirichlet_check_parameters(alpha): + alpha = np.asarray(alpha) + if np.min(alpha) <= 0: + raise ValueError("All parameters must be greater than 0") + elif alpha.ndim != 1: + raise ValueError("Parameter vector 'a' must be one dimensional, " + f"but a.shape = {alpha.shape}.") + return alpha + + +def _dirichlet_check_input(alpha, x): + x = np.asarray(x) + + if x.shape[0] + 1 != alpha.shape[0] and x.shape[0] != alpha.shape[0]: + raise ValueError("Vector 'x' must have either the same number " + "of entries as, or one entry fewer than, " + f"parameter vector 'a', but alpha.shape = {alpha.shape} " + f"and x.shape = {x.shape}.") + + if x.shape[0] != alpha.shape[0]: + xk = np.array([1 - np.sum(x, 0)]) + if xk.ndim == 1: + x = np.append(x, xk) + elif xk.ndim == 2: + x = np.vstack((x, xk)) + else: + raise ValueError("The input must be one dimensional or a two " + "dimensional matrix containing the entries.") + + if np.min(x) < 0: + raise ValueError("Each entry in 'x' must be greater than or equal " + "to zero.") + + if np.max(x) > 1: + raise ValueError("Each entry in 'x' must be smaller or equal one.") + + # Check x_i > 0 or alpha_i > 1 + xeq0 = (x == 0) + alphalt1 = (alpha < 1) + if x.shape != alpha.shape: + alphalt1 = np.repeat(alphalt1, x.shape[-1], axis=-1).reshape(x.shape) + chk = np.logical_and(xeq0, alphalt1) + + if np.sum(chk): + raise ValueError("Each entry in 'x' must be greater than zero if its " + "alpha is less than one.") + + if (np.abs(np.sum(x, 0) - 1.0) > 10e-10).any(): + raise ValueError("The input vector 'x' must lie within the normal " + f"simplex. but np.sum(x, 0) = {np.sum(x, 0)}.") + + return x + + +def _lnB(alpha): + r"""Internal helper function to compute the log of the useful quotient. + + .. math:: + + B(\alpha) = \frac{\prod_{i=1}{K}\Gamma(\alpha_i)} + {\Gamma\left(\sum_{i=1}^{K} \alpha_i \right)} + + Parameters + ---------- + %(_dirichlet_doc_default_callparams)s + + Returns + ------- + B : scalar + Helper quotient, internal use only + + """ + return np.sum(gammaln(alpha)) - gammaln(np.sum(alpha)) + + +class dirichlet_gen(multi_rv_generic): + r""" + A Dirichlet random variable. + + The ``alpha`` keyword specifies the concentration parameters of the + distribution. + + .. versionadded:: 0.15.0 + + Parameters + ---------- + %(_dirichlet_doc_default_callparams)s + %(_doc_random_state)s + + Methods + ------- + pdf(x, alpha) + Probability density function. + logpdf(x, alpha) + Log of the probability density function. + rvs(alpha, size=1, random_state=None) + Draw random samples from a Dirichlet distribution. + mean(alpha) + The mean of the Dirichlet distribution + var(alpha) + The variance of the Dirichlet distribution + cov(alpha) + The covariance of the Dirichlet distribution + entropy(alpha) + Compute the differential entropy of the Dirichlet distribution. + + Notes + ----- + Each :math:`\alpha` entry must be positive. The distribution has only + support on the simplex defined by + + .. math:: + \sum_{i=1}^{K} x_i = 1 + + where :math:`0 < x_i < 1`. + + If the quantiles don't lie within the simplex, a ValueError is raised. + + The probability density function for `dirichlet` is + + .. math:: + + f(x) = \frac{1}{\mathrm{B}(\boldsymbol\alpha)} \prod_{i=1}^K x_i^{\alpha_i - 1} + + where + + .. math:: + + \mathrm{B}(\boldsymbol\alpha) = \frac{\prod_{i=1}^K \Gamma(\alpha_i)} + {\Gamma\bigl(\sum_{i=1}^K \alpha_i\bigr)} + + and :math:`\boldsymbol\alpha=(\alpha_1,\ldots,\alpha_K)`, the + concentration parameters and :math:`K` is the dimension of the space + where :math:`x` takes values. + + Note that the `dirichlet` interface is somewhat inconsistent. + The array returned by the rvs function is transposed + with respect to the format expected by the pdf and logpdf. + + Examples + -------- + >>> import numpy as np + >>> from scipy.stats import dirichlet + + Generate a dirichlet random variable + + >>> quantiles = np.array([0.2, 0.2, 0.6]) # specify quantiles + >>> alpha = np.array([0.4, 5, 15]) # specify concentration parameters + >>> dirichlet.pdf(quantiles, alpha) + 0.2843831684937255 + + The same PDF but following a log scale + + >>> dirichlet.logpdf(quantiles, alpha) + -1.2574327653159187 + + Once we specify the dirichlet distribution + we can then calculate quantities of interest + + >>> dirichlet.mean(alpha) # get the mean of the distribution + array([0.01960784, 0.24509804, 0.73529412]) + >>> dirichlet.var(alpha) # get variance + array([0.00089829, 0.00864603, 0.00909517]) + >>> dirichlet.entropy(alpha) # calculate the differential entropy + -4.3280162474082715 + + We can also return random samples from the distribution + + >>> dirichlet.rvs(alpha, size=1, random_state=1) + array([[0.00766178, 0.24670518, 0.74563305]]) + >>> dirichlet.rvs(alpha, size=2, random_state=2) + array([[0.01639427, 0.1292273 , 0.85437844], + [0.00156917, 0.19033695, 0.80809388]]) + + Alternatively, the object may be called (as a function) to fix + concentration parameters, returning a "frozen" Dirichlet + random variable: + + >>> rv = dirichlet(alpha) + >>> # Frozen object with the same methods but holding the given + >>> # concentration parameters fixed. + """ + + def __init__(self, seed=None): + super().__init__(seed) + self.__doc__ = doccer.docformat(self.__doc__, dirichlet_docdict_params) + + def __call__(self, alpha, seed=None): + return dirichlet_frozen(alpha, seed=seed) + + def _logpdf(self, x, alpha): + """Log of the Dirichlet probability density function. + + Parameters + ---------- + x : ndarray + Points at which to evaluate the log of the probability + density function + %(_dirichlet_doc_default_callparams)s + + Notes + ----- + As this function does no argument checking, it should not be + called directly; use 'logpdf' instead. + + """ + lnB = _lnB(alpha) + return - lnB + np.sum((xlogy(alpha - 1, x.T)).T, 0) + + def logpdf(self, x, alpha): + """Log of the Dirichlet probability density function. + + Parameters + ---------- + x : array_like + Quantiles, with the last axis of `x` denoting the components. + %(_dirichlet_doc_default_callparams)s + + Returns + ------- + pdf : ndarray or scalar + Log of the probability density function evaluated at `x`. + + """ + alpha = _dirichlet_check_parameters(alpha) + x = _dirichlet_check_input(alpha, x) + + out = self._logpdf(x, alpha) + return _squeeze_output(out) + + def pdf(self, x, alpha): + """The Dirichlet probability density function. + + Parameters + ---------- + x : array_like + Quantiles, with the last axis of `x` denoting the components. + %(_dirichlet_doc_default_callparams)s + + Returns + ------- + pdf : ndarray or scalar + The probability density function evaluated at `x`. + + """ + alpha = _dirichlet_check_parameters(alpha) + x = _dirichlet_check_input(alpha, x) + + out = np.exp(self._logpdf(x, alpha)) + return _squeeze_output(out) + + def mean(self, alpha): + """Mean of the Dirichlet distribution. + + Parameters + ---------- + %(_dirichlet_doc_default_callparams)s + + Returns + ------- + mu : ndarray or scalar + Mean of the Dirichlet distribution. + + """ + alpha = _dirichlet_check_parameters(alpha) + + out = alpha / (np.sum(alpha)) + return _squeeze_output(out) + + def var(self, alpha): + """Variance of the Dirichlet distribution. + + Parameters + ---------- + %(_dirichlet_doc_default_callparams)s + + Returns + ------- + v : ndarray or scalar + Variance of the Dirichlet distribution. + + """ + + alpha = _dirichlet_check_parameters(alpha) + + alpha0 = np.sum(alpha) + out = (alpha * (alpha0 - alpha)) / ((alpha0 * alpha0) * (alpha0 + 1)) + return _squeeze_output(out) + + def cov(self, alpha): + """Covariance matrix of the Dirichlet distribution. + + Parameters + ---------- + %(_dirichlet_doc_default_callparams)s + + Returns + ------- + cov : ndarray + The covariance matrix of the distribution. + """ + + alpha = _dirichlet_check_parameters(alpha) + alpha0 = np.sum(alpha) + a = alpha / alpha0 + + cov = (np.diag(a) - np.outer(a, a)) / (alpha0 + 1) + return _squeeze_output(cov) + + def entropy(self, alpha): + """ + Differential entropy of the Dirichlet distribution. + + Parameters + ---------- + %(_dirichlet_doc_default_callparams)s + + Returns + ------- + h : scalar + Entropy of the Dirichlet distribution + + """ + + alpha = _dirichlet_check_parameters(alpha) + + alpha0 = np.sum(alpha) + lnB = _lnB(alpha) + K = alpha.shape[0] + + out = lnB + (alpha0 - K) * scipy.special.psi(alpha0) - np.sum( + (alpha - 1) * scipy.special.psi(alpha)) + return _squeeze_output(out) + + def rvs(self, alpha, size=1, random_state=None): + """ + Draw random samples from a Dirichlet distribution. + + Parameters + ---------- + %(_dirichlet_doc_default_callparams)s + size : int, optional + Number of samples to draw (default 1). + %(_doc_random_state)s + + Returns + ------- + rvs : ndarray or scalar + Random variates of size (`size`, `N`), where `N` is the + dimension of the random variable. + + """ + alpha = _dirichlet_check_parameters(alpha) + random_state = self._get_random_state(random_state) + return random_state.dirichlet(alpha, size=size) + + +dirichlet = dirichlet_gen() + + +class dirichlet_frozen(multi_rv_frozen): + __class_getitem__ = None # pyrefly:ignore[bad-assignment] + + def __init__(self, alpha, seed=None): + self.alpha = _dirichlet_check_parameters(alpha) + self._dist = dirichlet_gen(seed) + + def logpdf(self, x): + return self._dist.logpdf(x, self.alpha) + + def pdf(self, x): + return self._dist.pdf(x, self.alpha) + + def mean(self): + return self._dist.mean(self.alpha) + + def var(self): + return self._dist.var(self.alpha) + + def cov(self): + return self._dist.cov(self.alpha) + + def entropy(self): + return self._dist.entropy(self.alpha) + + def rvs(self, size=1, random_state=None): + return self._dist.rvs(self.alpha, size, random_state) + + +# Set frozen generator docstrings from corresponding docstrings in +# multivariate_normal_gen and fill in default strings in class docstrings +for name in ['logpdf', 'pdf', 'rvs', 'mean', 'var', 'cov', 'entropy']: + method = dirichlet_gen.__dict__[name] + method_frozen = dirichlet_frozen.__dict__[name] + method_frozen.__doc__ = doccer.docformat( + method.__doc__, dirichlet_docdict_noparams) + method.__doc__ = doccer.docformat(method.__doc__, dirichlet_docdict_params) + + +_wishart_doc_default_callparams = """\ +df : int + Degrees of freedom, must be greater than or equal to dimension of the + scale matrix +scale : array_like + Symmetric positive definite scale matrix of the distribution +""" + +_wishart_doc_callparams_note = "" + +_wishart_doc_frozen_callparams = "" + +_wishart_doc_frozen_callparams_note = """\ +See class definition for a detailed description of parameters.""" + +wishart_docdict_params = { + '_doc_default_callparams': _wishart_doc_default_callparams, + '_doc_callparams_note': _wishart_doc_callparams_note, + '_doc_random_state': _doc_random_state +} + +wishart_docdict_noparams = { + '_doc_default_callparams': _wishart_doc_frozen_callparams, + '_doc_callparams_note': _wishart_doc_frozen_callparams_note, + '_doc_random_state': _doc_random_state +} + + +class wishart_gen(multi_rv_generic): + r""" + A Wishart random variable. + + The `df` keyword specifies the degrees of freedom. The `scale` keyword + specifies the scale matrix, which must be symmetric and positive definite. + In this context, the scale matrix is often interpreted in terms of a + multivariate normal precision matrix (the inverse of the covariance + matrix). These arguments must satisfy the relationship + ``df > scale.ndim - 1``, but see notes on using the `rvs` method with + ``df < scale.ndim``. + + Parameters + ---------- + %(_doc_default_callparams)s + %(_doc_random_state)s + + Methods + ------- + pdf(x, df, scale) + Probability density function. + logpdf(x, df, scale) + Log of the probability density function. + rvs(df, scale, size=1, random_state=None) + Draw random samples from a Wishart distribution. + entropy() + Compute the differential entropy of the Wishart distribution. + + Raises + ------ + scipy.linalg.LinAlgError + If the scale matrix `scale` is not positive definite. + + See Also + -------- + invwishart, chi2 + + Notes + ----- + %(_doc_callparams_note)s + + The scale matrix `scale` must be a symmetric positive definite + matrix. Singular matrices, including the symmetric positive semi-definite + case, are not supported. Symmetry is not checked; only the lower triangular + portion is used. + + The Wishart distribution is often denoted + + .. math:: + + W_p(\nu, \Sigma) + + where :math:`\nu` is the degrees of freedom and :math:`\Sigma` is the + :math:`p \times p` scale matrix. + + The probability density function for `wishart` has support over positive + definite matrices :math:`S`; if :math:`S \sim W_p(\nu, \Sigma)`, then + its PDF is given by: + + .. math:: + + f(S) = \frac{|S|^{\frac{\nu - p - 1}{2}}}{2^{ \frac{\nu p}{2} } + |\Sigma|^\frac{\nu}{2} \Gamma_p \left ( \frac{\nu}{2} \right )} + \exp\left( -tr(\Sigma^{-1} S) / 2 \right) + + If :math:`S \sim W_p(\nu, \Sigma)` (Wishart) then + :math:`S^{-1} \sim W_p^{-1}(\nu, \Sigma^{-1})` (inverse Wishart). + + If the scale matrix is 1-dimensional and equal to one, then the Wishart + distribution :math:`W_1(\nu, 1)` collapses to the :math:`\chi^2(\nu)` + distribution. + + The algorithm [2]_ implemented by the `rvs` method may + produce numerically singular matrices with :math:`p - 1 < \nu < p`; the + user may wish to check for this condition and generate replacement samples + as necessary. + + + .. versionadded:: 0.16.0 + + References + ---------- + .. [1] M.L. Eaton, "Multivariate Statistics: A Vector Space Approach", + Wiley, 1983. + .. [2] W.B. Smith and R.R. Hocking, "Algorithm AS 53: Wishart Variate + Generator", Applied Statistics, vol. 21, pp. 341-345, 1972. + + Examples + -------- + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from scipy.stats import wishart, chi2 + >>> x = np.linspace(1e-5, 8, 100) + >>> w = wishart.pdf(x, df=3, scale=1); w[:5] + array([ 0.00126156, 0.10892176, 0.14793434, 0.17400548, 0.1929669 ]) + >>> c = chi2.pdf(x, 3); c[:5] + array([ 0.00126156, 0.10892176, 0.14793434, 0.17400548, 0.1929669 ]) + >>> plt.plot(x, w) + >>> plt.show() + + The input quantiles can be any shape of array, as long as the last + axis labels the components. + + Alternatively, the object may be called (as a function) to fix the degrees + of freedom and scale parameters, returning a "frozen" Wishart random + variable: + + >>> rv = wishart(df=1, scale=1) + >>> # Frozen object with the same methods but holding the given + >>> # degrees of freedom and scale fixed. + """ + + def __init__(self, seed=None): + super().__init__(seed) + self.__doc__ = doccer.docformat(self.__doc__, wishart_docdict_params) + + def __call__(self, df=None, scale=None, seed=None): + """Create a frozen Wishart distribution. + + See `wishart_frozen` for more information. + """ + return wishart_frozen(df, scale, seed) + + def _process_parameters(self, df, scale): + if scale is None: + scale = 1.0 + scale = np.asarray(scale, dtype=float) + + if scale.ndim == 0: + scale = scale[np.newaxis, np.newaxis] + elif scale.ndim == 1: + scale = np.diag(scale) + elif scale.ndim == 2 and not scale.shape[0] == scale.shape[1]: + raise ValueError("Array 'scale' must be square if it is two dimensional," + f" but scale.scale = {str(scale.shape)}.") + elif scale.ndim > 2: + raise ValueError(f"Array 'scale' must be at most two-dimensional, " + f"but scale.ndim = {scale.ndim}") + + dim = scale.shape[0] + + if df is None: + df = dim + elif not np.isscalar(df): + raise ValueError("Degrees of freedom must be a scalar.") + elif df <= dim - 1: + raise ValueError("Degrees of freedom must be greater than the " + "dimension of scale matrix minus 1.") + + return dim, df, scale + + def _process_quantiles(self, x, dim): + """ + Adjust quantiles array so that last axis labels the components of + each data point. + """ + x = np.asarray(x, dtype=float) + + if x.ndim == 0: + x = x * np.eye(dim)[:, :, np.newaxis] + if x.ndim == 1: + if dim == 1: + x = x[np.newaxis, np.newaxis, :] + else: + x = np.diag(x)[:, :, np.newaxis] + elif x.ndim == 2: + if not x.shape[0] == x.shape[1]: + raise ValueError( + "Quantiles must be square if they are two dimensional," + f" but x.shape = {str(x.shape)}.") + x = x[:, :, np.newaxis] + elif x.ndim == 3: + if not x.shape[0] == x.shape[1]: + raise ValueError( + "Quantiles must be square in the first two dimensions " + f"if they are three dimensional, but x.shape = {str(x.shape)}.") + elif x.ndim > 3: + raise ValueError(f"Quantiles must be at most two-dimensional with an " + f"additional dimension for multiple components, " + f"but x.ndim = {x.ndim}") + + # Now we have 3-dim array; should have shape [dim, dim, *] + if not x.shape[0:2] == (dim, dim): + raise ValueError('Quantiles have incompatible dimensions: should' + f' be {(dim, dim)}, got {x.shape[0:2]}.') + + return x + + def _process_size(self, size): + size = np.asarray(size) + + if size.ndim == 0: + size = size[np.newaxis] + elif size.ndim > 1: + raise ValueError('Size must be an integer or tuple of integers;' + ' thus must have dimension <= 1.' + f' Got size.ndim = {str(tuple(size))}') + n = size.prod() + shape = tuple(size) + + return n, shape + + def _logpdf(self, x, dim, df, scale, log_det_scale, C): + """Log of the Wishart probability density function. + + Parameters + ---------- + x : ndarray + Points at which to evaluate the log of the probability + density function + dim : int + Dimension of the scale matrix + df : int + Degrees of freedom + scale : ndarray + Scale matrix + log_det_scale : float + Logarithm of the determinant of the scale matrix + C : ndarray + Cholesky factorization of the scale matrix, lower triangular. + + Notes + ----- + As this function does no argument checking, it should not be + called directly; use 'logpdf' instead. + + """ + # log determinant of x + # Note: x has components along the last axis, so that x.T has + # components alone the 0-th axis. Then since det(A) = det(A'), this + # gives us a 1-dim vector of determinants + + # Retrieve tr(scale^{-1} x) + log_det_x = np.empty(x.shape[-1]) + scale_inv_x = np.empty(x.shape) + tr_scale_inv_x = np.empty(x.shape[-1]) + for i in range(x.shape[-1]): + _, log_det_x[i] = self._cholesky_logdet(x[:, :, i]) + scale_inv_x[:, :, i] = scipy.linalg.cho_solve((C, True), x[:, :, i]) + tr_scale_inv_x[i] = scale_inv_x[:, :, i].trace() + + # Log PDF + out = ((0.5 * (df - dim - 1) * log_det_x - 0.5 * tr_scale_inv_x) - + (0.5 * df * dim * _LOG_2 + 0.5 * df * log_det_scale + + multigammaln(0.5*df, dim))) + + return out + + def logpdf(self, x, df, scale): + """Log of the Wishart probability density function. + + Parameters + ---------- + x : array_like + Quantiles, with the last axis of `x` denoting the components. + Each quantile must be a symmetric positive definite matrix. + %(_doc_default_callparams)s + + Returns + ------- + pdf : ndarray + Log of the probability density function evaluated at `x` + + Notes + ----- + %(_doc_callparams_note)s + + """ + dim, df, scale = self._process_parameters(df, scale) + x = self._process_quantiles(x, dim) + + # Cholesky decomposition of scale, get log(det(scale)) + C, log_det_scale = self._cholesky_logdet(scale) + + out = self._logpdf(x, dim, df, scale, log_det_scale, C) + return _squeeze_output(out) + + def pdf(self, x, df, scale): + """Wishart probability density function. + + Parameters + ---------- + x : array_like + Quantiles, with the last axis of `x` denoting the components. + Each quantile must be a symmetric positive definite matrix. + %(_doc_default_callparams)s + + Returns + ------- + pdf : ndarray + Probability density function evaluated at `x` + + Notes + ----- + %(_doc_callparams_note)s + + """ + return np.exp(self.logpdf(x, df, scale)) + + def _mean(self, dim, df, scale): + """Mean of the Wishart distribution. + + Parameters + ---------- + dim : int + Dimension of the scale matrix + %(_doc_default_callparams)s + + Notes + ----- + As this function does no argument checking, it should not be + called directly; use 'mean' instead. + + """ + return df * scale + + def mean(self, df, scale): + """Mean of the Wishart distribution. + + Parameters + ---------- + %(_doc_default_callparams)s + + Returns + ------- + mean : float + The mean of the distribution + """ + dim, df, scale = self._process_parameters(df, scale) + out = self._mean(dim, df, scale) + return _squeeze_output(out) + + def _mode(self, dim, df, scale): + """Mode of the Wishart distribution. + + Parameters + ---------- + dim : int + Dimension of the scale matrix + %(_doc_default_callparams)s + + Notes + ----- + As this function does no argument checking, it should not be + called directly; use 'mode' instead. + + """ + if df >= dim + 1: + out = (df-dim-1) * scale + else: + out = None + return out + + def mode(self, df, scale): + """Mode of the Wishart distribution + + Only valid if the degrees of freedom are greater than the dimension of + the scale matrix. + + Parameters + ---------- + %(_doc_default_callparams)s + + Returns + ------- + mode : float or None + The Mode of the distribution + """ + dim, df, scale = self._process_parameters(df, scale) + out = self._mode(dim, df, scale) + return _squeeze_output(out) if out is not None else out + + def _var(self, dim, df, scale): + """Variance of the Wishart distribution. + + Parameters + ---------- + dim : int + Dimension of the scale matrix + %(_doc_default_callparams)s + + Notes + ----- + As this function does no argument checking, it should not be + called directly; use 'var' instead. + + """ + var = scale**2 + diag = scale.diagonal() # 1 x dim array + var += np.outer(diag, diag) + var *= df + return var + + def var(self, df, scale): + """Variance of the Wishart distribution. + + Parameters + ---------- + %(_doc_default_callparams)s + + Returns + ------- + var : float + The variance of the distribution + """ + dim, df, scale = self._process_parameters(df, scale) + out = self._var(dim, df, scale) + return _squeeze_output(out) + + def _standard_rvs(self, n, shape, dim, df, random_state): + """ + Parameters + ---------- + n : int + Number of variates to generate + shape : iterable + Shape of the variates to generate + dim : int + Dimension of the scale matrix + df : int + Degrees of freedom + random_state : {None, int, `numpy.random.Generator`, + `numpy.random.RandomState`}, optional + + If `seed` is None (or `np.random`), the `numpy.random.RandomState` + singleton is used. + If `seed` is an int, a new ``RandomState`` instance is used, + seeded with `seed`. + If `seed` is already a ``Generator`` or ``RandomState`` instance + then that instance is used. + + Notes + ----- + As this function does no argument checking, it should not be + called directly; use 'rvs' instead. + + """ + # Random normal variates for off-diagonal elements + n_tril = dim * (dim-1) // 2 + covariances = random_state.normal( + size=n*n_tril).reshape(shape+(n_tril,)) + + # Random chi-square variates for diagonal elements + variances = (np.r_[[random_state.chisquare(df-(i+1)+1, size=n)**0.5 + for i in range(dim)]].reshape((dim,) + + shape[::-1]).T) + + # Create the A matri(ces) - lower triangular + A = np.zeros(shape + (dim, dim)) + + # Input the covariances + size_idx = tuple([slice(None, None, None)]*len(shape)) + tril_idx = np.tril_indices(dim, k=-1) + A[size_idx + tril_idx] = covariances + + # Input the variances + diag_idx = np.diag_indices(dim) + A[size_idx + diag_idx] = variances + + return A + + def _rvs(self, n, shape, dim, df, C, random_state): + """Draw random samples from a Wishart distribution. + + Parameters + ---------- + n : int + Number of variates to generate + shape : iterable + Shape of the variates to generate + dim : int + Dimension of the scale matrix + df : int + Degrees of freedom + C : ndarray + Cholesky factorization of the scale matrix, lower triangular. + %(_doc_random_state)s + + Notes + ----- + As this function does no argument checking, it should not be + called directly; use 'rvs' instead. + + """ + random_state = self._get_random_state(random_state) + # Calculate the matrices A, which are actually lower triangular + # Cholesky factorizations of a matrix B such that B ~ W(df, I) + A = self._standard_rvs(n, shape, dim, df, random_state) + + # Calculate SA = C A A' C', where SA ~ W(df, scale) + # Note: this is the product of a (lower) (lower) (lower)' (lower)' + # or, denoting B = AA', it is C B C' where C is the lower + # triangular Cholesky factorization of the scale matrix. + # this appears to conflict with the instructions in [1]_, which + # suggest that it should be D' B D where D is the lower + # triangular factorization of the scale matrix. However, it is + # meant to refer to the Bartlett (1933) representation of a + # Wishart random variate as L A A' L' where L is lower triangular + # so it appears that understanding D' to be upper triangular + # is either a typo in or misreading of [1]_. + for index in np.ndindex(shape): + CA = np.dot(C, A[index]) + A[index] = np.dot(CA, CA.T) + + return A + + def rvs(self, df, scale, size=1, random_state=None): + """Draw random samples from a Wishart distribution. + + Parameters + ---------- + %(_doc_default_callparams)s + size : int or iterable of integers, optional + Number of samples to draw (default 1). + %(_doc_random_state)s + + Returns + ------- + rvs : ndarray + Random variates of shape (`size`) + (``dim``, ``dim``), where + ``dim`` is the dimension of the scale matrix. + + Notes + ----- + %(_doc_callparams_note)s + + """ + n, shape = self._process_size(size) + dim, df, scale = self._process_parameters(df, scale) + + # Cholesky decomposition of scale + C = scipy.linalg.cholesky(scale, lower=True) + + out = self._rvs(n, shape, dim, df, C, random_state) + + return _squeeze_output(out) + + def _entropy(self, dim, df, log_det_scale): + """Compute the differential entropy of the Wishart. + + Parameters + ---------- + dim : int + Dimension of the scale matrix + df : int + Degrees of freedom + log_det_scale : float + Logarithm of the determinant of the scale matrix + + Notes + ----- + As this function does no argument checking, it should not be + called directly; use 'entropy' instead. + + """ + return ( + 0.5 * (dim+1) * log_det_scale + + 0.5 * dim * (dim+1) * _LOG_2 + + multigammaln(0.5*df, dim) - + 0.5 * (df - dim - 1) * np.sum( + [psi(0.5*(df + 1 - (i+1))) for i in range(dim)] + ) + + 0.5 * df * dim + ) + + def entropy(self, df, scale): + """Compute the differential entropy of the Wishart. + + Parameters + ---------- + %(_doc_default_callparams)s + + Returns + ------- + h : scalar + Entropy of the Wishart distribution + + Notes + ----- + %(_doc_callparams_note)s + + """ + dim, df, scale = self._process_parameters(df, scale) + _, log_det_scale = self._cholesky_logdet(scale) + return self._entropy(dim, df, log_det_scale) + + def _cholesky_logdet(self, scale): + """Compute Cholesky decomposition and determine (log(det(scale)). + + Parameters + ---------- + scale : ndarray + Scale matrix. + + Returns + ------- + c_decomp : ndarray + The Cholesky decomposition of `scale`. + logdet : scalar + The log of the determinant of `scale`. + + Notes + ----- + This computation of ``logdet`` is equivalent to + ``np.linalg.slogdet(scale)``. It is ~2x faster though. + + """ + c_decomp = scipy.linalg.cholesky(scale, lower=True) + logdet = 2 * np.sum(np.log(c_decomp.diagonal())) + return c_decomp, logdet + + +wishart = wishart_gen() + + +class wishart_frozen(multi_rv_frozen): + """Create a frozen Wishart distribution. + + Parameters + ---------- + df : array_like + Degrees of freedom of the distribution + scale : array_like + Scale matrix of the distribution + seed : {None, int, `numpy.random.Generator`, `numpy.random.RandomState`}, optional + If `seed` is None (or `np.random`), the `numpy.random.RandomState` + singleton is used. + If `seed` is an int, a new ``RandomState`` instance is used, + seeded with `seed`. + If `seed` is already a ``Generator`` or ``RandomState`` instance then + that instance is used. + + """ + __class_getitem__ = None # pyrefly:ignore[bad-assignment] + + def __init__(self, df, scale, seed=None): + self._dist = wishart_gen(seed) + self.dim, self.df, self.scale = self._dist._process_parameters( + df, scale) + self.C, self.log_det_scale = self._dist._cholesky_logdet(self.scale) + + def logpdf(self, x): + x = self._dist._process_quantiles(x, self.dim) + + out = self._dist._logpdf(x, self.dim, self.df, self.scale, + self.log_det_scale, self.C) + return _squeeze_output(out) + + def pdf(self, x): + return np.exp(self.logpdf(x)) + + def mean(self): + out = self._dist._mean(self.dim, self.df, self.scale) + return _squeeze_output(out) + + def mode(self): + out = self._dist._mode(self.dim, self.df, self.scale) + return _squeeze_output(out) if out is not None else out + + def var(self): + out = self._dist._var(self.dim, self.df, self.scale) + return _squeeze_output(out) + + def rvs(self, size=1, random_state=None): + n, shape = self._dist._process_size(size) + out = self._dist._rvs(n, shape, self.dim, self.df, + self.C, random_state) + return _squeeze_output(out) + + def entropy(self): + return self._dist._entropy(self.dim, self.df, self.log_det_scale) + + +# Set frozen generator docstrings from corresponding docstrings in +# Wishart and fill in default strings in class docstrings +for name in ['logpdf', 'pdf', 'mean', 'mode', 'var', 'rvs', 'entropy']: + method = wishart_gen.__dict__[name] + method_frozen = wishart_frozen.__dict__[name] + method_frozen.__doc__ = doccer.docformat( + method.__doc__, wishart_docdict_noparams) + method.__doc__ = doccer.docformat(method.__doc__, wishart_docdict_params) + + +class invwishart_gen(wishart_gen): + r""" + An inverse Wishart random variable. + + The `df` keyword specifies the degrees of freedom. The `scale` keyword + specifies the scale matrix, which must be symmetric and positive definite. + In this context, the scale matrix is often interpreted in terms of a + multivariate normal covariance matrix. + + Parameters + ---------- + %(_doc_default_callparams)s + %(_doc_random_state)s + + Methods + ------- + pdf(x, df, scale) + Probability density function. + logpdf(x, df, scale) + Log of the probability density function. + rvs(df, scale, size=1, random_state=None) + Draw random samples from an inverse Wishart distribution. + entropy(df, scale) + Differential entropy of the distribution. + + Raises + ------ + scipy.linalg.LinAlgError + If the scale matrix `scale` is not positive definite. + + See Also + -------- + wishart + + Notes + ----- + %(_doc_callparams_note)s + + The scale matrix `scale` must be a symmetric positive definite + matrix. Singular matrices, including the symmetric positive semi-definite + case, are not supported. Symmetry is not checked; only the lower triangular + portion is used. + + The inverse Wishart distribution is often denoted + + .. math:: + + W_p^{-1}(\nu, \Psi) + + where :math:`\nu` is the degrees of freedom and :math:`\Psi` is the + :math:`p \times p` scale matrix. + + The probability density function for `invwishart` has support over positive + definite matrices :math:`S`; if :math:`S \sim W^{-1}_p(\nu, \Sigma)`, + then its PDF is given by: + + .. math:: + + f(S) = \frac{|\Sigma|^\frac{\nu}{2}}{2^{ \frac{\nu p}{2} } + |S|^{\frac{\nu + p + 1}{2}} \Gamma_p \left(\frac{\nu}{2} \right)} + \exp\left( -tr(\Sigma S^{-1}) / 2 \right) + + If :math:`S \sim W_p^{-1}(\nu, \Psi)` (inverse Wishart) then + :math:`S^{-1} \sim W_p(\nu, \Psi^{-1})` (Wishart). + + If the scale matrix is 1-dimensional and equal to one, then the inverse + Wishart distribution :math:`W_1(\nu, 1)` collapses to the + inverse Gamma distribution with parameters shape = :math:`\frac{\nu}{2}` + and scale = :math:`\frac{1}{2}`. + + Instead of inverting a randomly generated Wishart matrix as described in [2], + here the algorithm in [4] is used to directly generate a random inverse-Wishart + matrix without inversion. + + .. versionadded:: 0.16.0 + + References + ---------- + .. [1] M.L. Eaton, "Multivariate Statistics: A Vector Space Approach", + Wiley, 1983. + .. [2] M.C. Jones, "Generating Inverse Wishart Matrices", Communications + in Statistics - Simulation and Computation, vol. 14.2, pp.511-514, + 1985. + .. [3] Gupta, M. and Srivastava, S. "Parametric Bayesian Estimation of + Differential Entropy and Relative Entropy". Entropy 12, 818 - 843. + 2010. + .. [4] S.D. Axen, "Efficiently generating inverse-Wishart matrices and + their Cholesky factors", :arXiv:`2310.15884v1`. 2023. + + Examples + -------- + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from scipy.stats import invwishart, invgamma + >>> x = np.linspace(0.01, 1, 100) + >>> iw = invwishart.pdf(x, df=6, scale=1) + >>> iw[:3] + array([ 1.20546865e-15, 5.42497807e-06, 4.45813929e-03]) + >>> ig = invgamma.pdf(x, 6/2., scale=1./2) + >>> ig[:3] + array([ 1.20546865e-15, 5.42497807e-06, 4.45813929e-03]) + >>> plt.plot(x, iw) + >>> plt.show() + + The input quantiles can be any shape of array, as long as the last + axis labels the components. + + Alternatively, the object may be called (as a function) to fix the degrees + of freedom and scale parameters, returning a "frozen" inverse Wishart + random variable: + + >>> rv = invwishart(df=1, scale=1) + >>> # Frozen object with the same methods but holding the given + >>> # degrees of freedom and scale fixed. + """ + + def __init__(self, seed=None): + super().__init__(seed) + self.__doc__ = doccer.docformat(self.__doc__, wishart_docdict_params) + + def __call__(self, df=None, scale=None, seed=None): + """Create a frozen inverse Wishart distribution. + + See `invwishart_frozen` for more information. + + """ + return invwishart_frozen(df, scale, seed) + + def _logpdf(self, x, dim, df, log_det_scale, C): + """Log of the inverse Wishart probability density function. + + Parameters + ---------- + x : ndarray + Points at which to evaluate the log of the probability + density function. + dim : int + Dimension of the scale matrix + df : int + Degrees of freedom + log_det_scale : float + Logarithm of the determinant of the scale matrix + C : ndarray + Cholesky factorization of the scale matrix, lower triangular. + + Notes + ----- + As this function does no argument checking, it should not be + called directly; use 'logpdf' instead. + + """ + # Retrieve tr(scale x^{-1}) + log_det_x = np.empty(x.shape[-1]) + tr_scale_x_inv = np.empty(x.shape[-1]) + trsm = get_blas_funcs(('trsm'), (x,)) + if dim > 1: + for i in range(x.shape[-1]): + Cx, log_det_x[i] = self._cholesky_logdet(x[:, :, i]) + A = trsm(1., Cx, C, side=0, lower=True) + tr_scale_x_inv[i] = np.linalg.norm(A)**2 + else: + log_det_x[:] = np.log(x[0, 0]) + tr_scale_x_inv[:] = C[0, 0]**2 / x[0, 0] + + # Log PDF + out = ((0.5 * df * log_det_scale - 0.5 * tr_scale_x_inv) - + (0.5 * df * dim * _LOG_2 + 0.5 * (df + dim + 1) * log_det_x) - + multigammaln(0.5*df, dim)) + + return out + + def logpdf(self, x, df, scale): + """Log of the inverse Wishart probability density function. + + Parameters + ---------- + x : array_like + Quantiles, with the last axis of `x` denoting the components. + Each quantile must be a symmetric positive definite matrix. + %(_doc_default_callparams)s + + Returns + ------- + pdf : ndarray + Log of the probability density function evaluated at `x` + + Notes + ----- + %(_doc_callparams_note)s + + """ + dim, df, scale = self._process_parameters(df, scale) + x = self._process_quantiles(x, dim) + C, log_det_scale = self._cholesky_logdet(scale) + out = self._logpdf(x, dim, df, log_det_scale, C) + return _squeeze_output(out) + + def pdf(self, x, df, scale): + """Inverse Wishart probability density function. + + Parameters + ---------- + x : array_like + Quantiles, with the last axis of `x` denoting the components. + Each quantile must be a symmetric positive definite matrix. + %(_doc_default_callparams)s + + Returns + ------- + pdf : ndarray + Probability density function evaluated at `x` + + Notes + ----- + %(_doc_callparams_note)s + + """ + return np.exp(self.logpdf(x, df, scale)) + + def _mean(self, dim, df, scale): + """Mean of the inverse Wishart distribution. + + Parameters + ---------- + dim : int + Dimension of the scale matrix + %(_doc_default_callparams)s + + Notes + ----- + As this function does no argument checking, it should not be + called directly; use 'mean' instead. + + """ + if df > dim + 1: + out = scale / (df - dim - 1) + else: + out = None + return out + + def mean(self, df, scale): + """Mean of the inverse Wishart distribution. + + Only valid if the degrees of freedom are greater than the dimension of + the scale matrix plus one. + + Parameters + ---------- + %(_doc_default_callparams)s + + Returns + ------- + mean : float or None + The mean of the distribution + + """ + dim, df, scale = self._process_parameters(df, scale) + out = self._mean(dim, df, scale) + return _squeeze_output(out) if out is not None else out + + def _mode(self, dim, df, scale): + """Mode of the inverse Wishart distribution. + + Parameters + ---------- + dim : int + Dimension of the scale matrix + %(_doc_default_callparams)s + + Notes + ----- + As this function does no argument checking, it should not be + called directly; use 'mode' instead. + + """ + return scale / (df + dim + 1) + + def mode(self, df, scale): + """Mode of the inverse Wishart distribution. + + Parameters + ---------- + %(_doc_default_callparams)s + + Returns + ------- + mode : float + The Mode of the distribution + + """ + dim, df, scale = self._process_parameters(df, scale) + out = self._mode(dim, df, scale) + return _squeeze_output(out) + + def _var(self, dim, df, scale): + """Variance of the inverse Wishart distribution. + + Parameters + ---------- + dim : int + Dimension of the scale matrix + %(_doc_default_callparams)s + + Notes + ----- + As this function does no argument checking, it should not be + called directly; use 'var' instead. + + """ + if df > dim + 3: + var = (df - dim + 1) * scale**2 + diag = scale.diagonal() # 1 x dim array + var += (df - dim - 1) * np.outer(diag, diag) + var /= (df - dim) * (df - dim - 1)**2 * (df - dim - 3) + else: + var = None + return var + + def var(self, df, scale): + """Variance of the inverse Wishart distribution. + + Only valid if the degrees of freedom are greater than the dimension of + the scale matrix plus three. + + Parameters + ---------- + %(_doc_default_callparams)s + + Returns + ------- + var : float + The variance of the distribution + """ + dim, df, scale = self._process_parameters(df, scale) + out = self._var(dim, df, scale) + return _squeeze_output(out) if out is not None else out + + def _inv_standard_rvs(self, n, shape, dim, df, random_state): + """ + Parameters + ---------- + n : int + Number of variates to generate + shape : iterable + Shape of the variates to generate + dim : int + Dimension of the scale matrix + df : int + Degrees of freedom + random_state : {None, int, `numpy.random.Generator`, + `numpy.random.RandomState`}, optional + + If `seed` is None (or `np.random`), the `numpy.random.RandomState` + singleton is used. + If `seed` is an int, a new ``RandomState`` instance is used, + seeded with `seed`. + If `seed` is already a ``Generator`` or ``RandomState`` instance + then that instance is used. + + Returns + ------- + A : ndarray + Random variates of shape (`shape`) + (``dim``, ``dim``). + Each slice `A[..., :, :]` is lower-triangular, and its + inverse is the lower Cholesky factor of a draw from + `invwishart(df, np.eye(dim))`. + + Notes + ----- + As this function does no argument checking, it should not be + called directly; use 'rvs' instead. + + """ + A = np.zeros(shape + (dim, dim)) + + # Random normal variates for off-diagonal elements + tri_rows, tri_cols = np.tril_indices(dim, k=-1) + n_tril = dim * (dim-1) // 2 + A[..., tri_rows, tri_cols] = random_state.normal( + size=(*shape, n_tril), + ) + + # Random chi variates for diagonal elements + rows = np.arange(dim) + chi_dfs = (df - dim + 1) + rows + A[..., rows, rows] = random_state.chisquare( + df=chi_dfs, size=(*shape, dim), + )**0.5 + + return A + + def _rvs(self, n, shape, dim, df, C, random_state): + """Draw random samples from an inverse Wishart distribution. + + Parameters + ---------- + n : int + Number of variates to generate + shape : iterable + Shape of the variates to generate + dim : int + Dimension of the scale matrix + df : int + Degrees of freedom + C : ndarray + Cholesky factorization of the scale matrix, lower triangular. + %(_doc_random_state)s + + Notes + ----- + As this function does no argument checking, it should not be + called directly; use 'rvs' instead. + + """ + random_state = self._get_random_state(random_state) + # Get random draws A such that inv(A) ~ iW(df, I) + A = self._inv_standard_rvs(n, shape, dim, df, random_state) + + # Calculate SA = (CA)'^{-1} (CA)^{-1} ~ iW(df, scale) + trsm = get_blas_funcs(('trsm'), (A,)) + trmm = get_blas_funcs(('trmm'), (A,)) + + for index in np.ndindex(A.shape[:-2]): + if dim > 1: + # Calculate CA + # Get CA = C A^{-1} via triangular solver + CA = trsm(1., A[index], C, side=1, lower=True) + # get SA + A[index] = trmm(1., CA, CA, side=1, lower=True, trans_a=True) + else: + A[index][0, 0] = (C[0, 0] / A[index][0, 0])**2 + + return A + + def rvs(self, df, scale, size=1, random_state=None): + """Draw random samples from an inverse Wishart distribution. + + Parameters + ---------- + %(_doc_default_callparams)s + size : int or iterable of integers, optional + Number of samples to draw (default 1). + %(_doc_random_state)s + + Returns + ------- + rvs : ndarray + Random variates of shape (`size`) + (``dim``, ``dim``), where + ``dim`` is the dimension of the scale matrix. + + Notes + ----- + %(_doc_callparams_note)s + + """ + n, shape = self._process_size(size) + dim, df, scale = self._process_parameters(df, scale) + + # Cholesky decomposition of scale + C = scipy.linalg.cholesky(scale, lower=True) + + out = self._rvs(n, shape, dim, df, C, random_state) + + return _squeeze_output(out) + + def _entropy(self, dim, df, log_det_scale): + # reference: eq. (17) from ref. 3 + psi_eval_points = [0.5 * (df - dim + i) for i in range(1, dim + 1)] + psi_eval_points = np.asarray(psi_eval_points) + return multigammaln(0.5 * df, dim) + 0.5 * dim * df + \ + 0.5 * (dim + 1) * (log_det_scale - _LOG_2) - \ + 0.5 * (df + dim + 1) * \ + psi(psi_eval_points, out=psi_eval_points).sum() + + def entropy(self, df, scale): + dim, df, scale = self._process_parameters(df, scale) + _, log_det_scale = self._cholesky_logdet(scale) + return self._entropy(dim, df, log_det_scale) + + +invwishart = invwishart_gen() + + +class invwishart_frozen(multi_rv_frozen): + __class_getitem__ = None # pyrefly:ignore[bad-assignment] + + def __init__(self, df, scale, seed=None): + """Create a frozen inverse Wishart distribution. + + Parameters + ---------- + df : array_like + Degrees of freedom of the distribution + scale : array_like + Scale matrix of the distribution + seed : {None, int, `numpy.random.Generator`}, optional + If `seed` is None the `numpy.random.Generator` singleton is used. + If `seed` is an int, a new ``Generator`` instance is used, + seeded with `seed`. + If `seed` is already a ``Generator`` instance then that instance is + used. + + """ + self._dist = invwishart_gen(seed) + self.dim, self.df, self.scale = self._dist._process_parameters( + df, scale + ) + + # Get the determinant via Cholesky factorization + self.C = scipy.linalg.cholesky(self.scale, lower=True) + self.log_det_scale = 2 * np.sum(np.log(self.C.diagonal())) + + def logpdf(self, x): + x = self._dist._process_quantiles(x, self.dim) + out = self._dist._logpdf(x, self.dim, self.df, + self.log_det_scale, self.C) + return _squeeze_output(out) + + def pdf(self, x): + return np.exp(self.logpdf(x)) + + def mean(self): + out = self._dist._mean(self.dim, self.df, self.scale) + return _squeeze_output(out) if out is not None else out + + def mode(self): + out = self._dist._mode(self.dim, self.df, self.scale) + return _squeeze_output(out) + + def var(self): + out = self._dist._var(self.dim, self.df, self.scale) + return _squeeze_output(out) if out is not None else out + + def rvs(self, size=1, random_state=None): + n, shape = self._dist._process_size(size) + + out = self._dist._rvs(n, shape, self.dim, self.df, + self.C, random_state) + + return _squeeze_output(out) + + def entropy(self): + return self._dist._entropy(self.dim, self.df, self.log_det_scale) + + +# Set frozen generator docstrings from corresponding docstrings in +# inverse Wishart and fill in default strings in class docstrings +for name in ['logpdf', 'pdf', 'mean', 'mode', 'var', 'rvs']: + method = invwishart_gen.__dict__[name] + method_frozen = wishart_frozen.__dict__[name] + method_frozen.__doc__ = doccer.docformat( + method.__doc__, wishart_docdict_noparams) + method.__doc__ = doccer.docformat(method.__doc__, wishart_docdict_params) + +_multinomial_doc_default_callparams = """\ +n : int + Number of trials +p : array_like + Probability of a trial falling into each category; should sum to 1 +""" + +_multinomial_doc_callparams_note = """\ +`n` should be a nonnegative integer. Each element of `p` should be in the +interval :math:`[0,1]` and the elements should sum to 1. If they do not sum to +1, the last element of the `p` array is not used and is replaced with the +remaining probability left over from the earlier elements. +""" + +_multinomial_doc_frozen_callparams = "" + +_multinomial_doc_frozen_callparams_note = """\ +See class definition for a detailed description of parameters.""" + +multinomial_docdict_params = { + '_doc_default_callparams': _multinomial_doc_default_callparams, + '_doc_callparams_note': _multinomial_doc_callparams_note, + '_doc_random_state': _doc_random_state +} + +multinomial_docdict_noparams = { + '_doc_default_callparams': _multinomial_doc_frozen_callparams, + '_doc_callparams_note': _multinomial_doc_frozen_callparams_note, + '_doc_random_state': _doc_random_state +} + + +class multinomial_gen(multi_rv_generic): + r""" + A multinomial random variable. + + Parameters + ---------- + %(_doc_default_callparams)s + %(_doc_random_state)s + + Methods + ------- + pmf(x, n, p) + Probability mass function. + logpmf(x, n, p) + Log of the probability mass function. + rvs(n, p, size=1, random_state=None) + Draw random samples from a multinomial distribution. + entropy(n, p) + Compute the entropy of the multinomial distribution. + cov(n, p) + Compute the covariance matrix of the multinomial distribution. + + See Also + -------- + scipy.stats.binom : The binomial distribution. + numpy.random.Generator.multinomial : Sampling from the multinomial distribution. + scipy.stats.multivariate_hypergeom : + The multivariate hypergeometric distribution. + + Notes + ----- + %(_doc_callparams_note)s + + The probability mass function for `multinomial` is + + .. math:: + + f(x) = \frac{n!}{x_1! \cdots x_k!} p_1^{x_1} \cdots p_k^{x_k}, + + supported on :math:`x=(x_1, \ldots, x_k)` where each :math:`x_i` is a + nonnegative integer and their sum is :math:`n`. + + .. versionadded:: 0.19.0 + + Examples + -------- + + >>> from scipy.stats import multinomial + >>> rv = multinomial(8, [0.3, 0.2, 0.5]) + >>> rv.pmf([1, 3, 4]) + 0.042000000000000072 + + The multinomial distribution for :math:`k=2` is identical to the + corresponding binomial distribution (tiny numerical differences + notwithstanding): + + >>> from scipy.stats import binom + >>> multinomial.pmf([3, 4], n=7, p=[0.4, 0.6]) + 0.29030399999999973 + >>> binom.pmf(3, 7, 0.4) + 0.29030400000000012 + + The functions ``pmf``, ``logpmf``, ``entropy``, and ``cov`` support + broadcasting, under the convention that the vector parameters (``x`` and + ``p``) are interpreted as if each row along the last axis is a single + object. For instance: + + >>> multinomial.pmf([[3, 4], [3, 5]], n=[7, 8], p=[.3, .7]) + array([0.2268945, 0.25412184]) + + Here, ``x.shape == (2, 2)``, ``n.shape == (2,)``, and ``p.shape == (2,)``, + but following the rules mentioned above they behave as if the rows + ``[3, 4]`` and ``[3, 5]`` in ``x`` and ``[.3, .7]`` in ``p`` were a single + object, and as if we had ``x.shape = (2,)``, ``n.shape = (2,)``, and + ``p.shape = ()``. To obtain the individual elements without broadcasting, + we would do this: + + >>> multinomial.pmf([3, 4], n=7, p=[.3, .7]) + 0.2268945 + >>> multinomial.pmf([3, 5], 8, p=[.3, .7]) + 0.25412184 + + This broadcasting also works for ``cov``, where the output objects are + square matrices of size ``p.shape[-1]``. For example: + + >>> multinomial.cov([4, 5], [[.3, .7], [.4, .6]]) + array([[[ 0.84, -0.84], + [-0.84, 0.84]], + [[ 1.2 , -1.2 ], + [-1.2 , 1.2 ]]]) + + In this example, ``n.shape == (2,)`` and ``p.shape == (2, 2)``, and + following the rules above, these broadcast as if ``p.shape == (2,)``. + Thus the result should also be of shape ``(2,)``, but since each output is + a :math:`2 \times 2` matrix, the result in fact has shape ``(2, 2, 2)``, + where ``result[0]`` is equal to ``multinomial.cov(n=4, p=[.3, .7])`` and + ``result[1]`` is equal to ``multinomial.cov(n=5, p=[.4, .6])``. + + Alternatively, the object may be called (as a function) to fix the `n` and + `p` parameters, returning a "frozen" multinomial random variable: + + >>> rv = multinomial(n=7, p=[.3, .7]) + >>> # Frozen object with the same methods but holding the given + >>> # degrees of freedom and scale fixed. + """ + + def __init__(self, seed=None): + super().__init__(seed) + self.__doc__ = \ + doccer.docformat(self.__doc__, multinomial_docdict_params) + + def __call__(self, n, p, seed=None): + """Create a frozen multinomial distribution. + + See `multinomial_frozen` for more information. + """ + return multinomial_frozen(n, p, seed) + + def _process_parameters(self, n, p): + """Returns: n_, p_, npcond. + + n_ and p_ are arrays of the correct shape; npcond is a boolean array + flagging values out of the domain. + """ + eps = np.finfo(np.result_type(np.asarray(p), np.float32)).eps * 10 + p = np.array(p, dtype=np.float64, copy=True) + + # true for bad p + pcond = np.abs(1 - p.sum(axis=-1)) > eps + pcond |= np.any(p < 0, axis=-1) + pcond |= np.any(p > 1, axis=-1) + + n = np.array(n, dtype=int, copy=True) + + # true for bad n + ncond = n < 0 + + return n, p, ncond | pcond + + def _process_quantiles(self, x, n, p): + """Returns: x_, xcond. + + x_ is an int array; xcond is a boolean array flagging values out of the + domain. + """ + xx = np.asarray(x, dtype=int) + + if xx.ndim == 0: + raise ValueError("x must be an array.") + + if xx.size != 0 and not xx.shape[-1] == p.shape[-1]: + raise ValueError(f"Size of each quantile should be size of p: " + f"received {xx.shape[-1]}, but expected " + f"{p.shape[-1]}.") + + # true for x out of the domain + cond = np.any(xx != x, axis=-1) + cond |= np.any(xx < 0, axis=-1) + cond = cond | (np.sum(xx, axis=-1) != n) + + return xx, cond + + def _checkresult(self, result, cond, bad_value): + result = np.asarray(result) + + if cond.ndim != 0: + result[cond] = bad_value + elif cond: + if result.ndim == 0: + return bad_value + result[...] = bad_value + return result + + def _logpmf(self, x, n, p): + return gammaln(n+1) + np.sum(xlogy(x, p) - gammaln(x+1), axis=-1) + + def logpmf(self, x, n, p): + """Log of the Multinomial probability mass function. + + Parameters + ---------- + x : array_like + Quantiles, with the last axis of `x` denoting the components. + %(_doc_default_callparams)s + + Returns + ------- + logpmf : ndarray or scalar + Log of the probability mass function evaluated at `x` + + Notes + ----- + %(_doc_callparams_note)s + """ + n, p, npcond = self._process_parameters(n, p) + x, xcond = self._process_quantiles(x, n, p) + + result = self._logpmf(x, n, p) + + # replace values for which x was out of the domain; broadcast + # xcond to the right shape + xcond_ = xcond | np.zeros(npcond.shape, dtype=np.bool_) + result = self._checkresult(result, xcond_, -np.inf) + + # replace values bad for n or p; broadcast npcond to the right shape + npcond_ = npcond | np.zeros(xcond.shape, dtype=np.bool_) + return self._checkresult(result, npcond_, np.nan) + + def pmf(self, x, n, p): + """Multinomial probability mass function. + + Parameters + ---------- + x : array_like + Quantiles, with the last axis of `x` denoting the components. + %(_doc_default_callparams)s + + Returns + ------- + pmf : ndarray or scalar + Probability density function evaluated at `x` + + Notes + ----- + %(_doc_callparams_note)s + """ + return np.exp(self.logpmf(x, n, p)) + + def mean(self, n, p): + """Mean of the Multinomial distribution. + + Parameters + ---------- + %(_doc_default_callparams)s + + Returns + ------- + mean : float + The mean of the distribution + """ + n, p, npcond = self._process_parameters(n, p) + result = n[..., np.newaxis]*p + return self._checkresult(result, npcond, np.nan) + + def cov(self, n, p): + """Covariance matrix of the multinomial distribution. + + Parameters + ---------- + %(_doc_default_callparams)s + + Returns + ------- + cov : ndarray + The covariance matrix of the distribution + """ + n, p, npcond = self._process_parameters(n, p) + + nn = n[..., np.newaxis, np.newaxis] + result = nn * np.einsum('...j,...k->...jk', -p, p) + + # change the diagonal + for i in range(p.shape[-1]): + result[..., i, i] += n*p[..., i] + + return self._checkresult(result, npcond, np.nan) + + def entropy(self, n, p): + r"""Compute the entropy of the multinomial distribution. + + The entropy is computed using this expression: + + .. math:: + + f(x) = - \log n! - n\sum_{i=1}^k p_i \log p_i + + \sum_{i=1}^k \sum_{x=0}^n \binom n x p_i^x(1-p_i)^{n-x} \log x! + + Parameters + ---------- + %(_doc_default_callparams)s + + Returns + ------- + h : scalar + Entropy of the Multinomial distribution + + Notes + ----- + %(_doc_callparams_note)s + """ + n, p, npcond = self._process_parameters(n, p) + + x = np.r_[1:np.max(n)+1] + + term1 = n*np.sum(entr(p), axis=-1) + term1 -= gammaln(n+1) + + n = n[..., np.newaxis] + new_axes_needed = max(p.ndim, n.ndim) - x.ndim + 1 + new_shape = x.shape + (1,)*new_axes_needed + x = x.reshape(new_shape) + + term2 = np.sum(binom.pmf(x, n, p)*gammaln(x+1), + axis=(-1, -1-new_axes_needed)) + + return self._checkresult(term1 + term2, npcond, np.nan) + + def rvs(self, n, p, size=None, random_state=None): + """Draw random samples from a Multinomial distribution. + + Parameters + ---------- + %(_doc_default_callparams)s + size : int or iterable of integers, optional + Number of samples to draw (default 1). + %(_doc_random_state)s + + Returns + ------- + rvs : ndarray or scalar + Random variates of shape (`size`, `len(p)`) + + Notes + ----- + %(_doc_callparams_note)s + """ + n, p, npcond = self._process_parameters(n, p) + if np.any(npcond): + message = ("`multinomial.rvs` requires `n > 0`, `(p > 0).all()`, and" + "`p.sum() == 1`.") + raise ValueError(message) + random_state = self._get_random_state(random_state) + return random_state.multinomial(n, p, size) + + +multinomial = multinomial_gen() + + +class multinomial_frozen(multi_rv_frozen): + r"""Create a frozen Multinomial distribution. + + Parameters + ---------- + n : int + number of trials + p: array_like + probability of a trial falling into each category; should sum to 1 + seed : {None, int, `numpy.random.Generator`, `numpy.random.RandomState`}, optional + If `seed` is None (or `np.random`), the `numpy.random.RandomState` + singleton is used. + If `seed` is an int, a new ``RandomState`` instance is used, + seeded with `seed`. + If `seed` is already a ``Generator`` or ``RandomState`` instance then + that instance is used. + """ + def __init__(self, n, p, seed=None): + self._dist = multinomial_gen(seed) + self.n, self.p, self.npcond = self._dist._process_parameters(n, p) + + # monkey patch self._dist + def _process_parameters(n, p): + return self.n, self.p, self.npcond + + self._dist._process_parameters = _process_parameters + + def logpmf(self, x): + return self._dist.logpmf(x, self.n, self.p) + + def pmf(self, x): + return self._dist.pmf(x, self.n, self.p) + + def mean(self): + return self._dist.mean(self.n, self.p) + + def cov(self): + return self._dist.cov(self.n, self.p) + + def entropy(self): + return self._dist.entropy(self.n, self.p) + + def rvs(self, size=1, random_state=None): + return self._dist.rvs(self.n, self.p, size, random_state) + + +# Set frozen generator docstrings from corresponding docstrings in +# multinomial and fill in default strings in class docstrings +for name in ['logpmf', 'pmf', 'mean', 'cov', 'rvs']: + method = multinomial_gen.__dict__[name] + method_frozen = multinomial_frozen.__dict__[name] + method_frozen.__doc__ = doccer.docformat( + method.__doc__, multinomial_docdict_noparams) + method.__doc__ = doccer.docformat(method.__doc__, + multinomial_docdict_params) + + +class special_ortho_group_gen(multi_rv_generic): + r""" + A Special Orthogonal matrix (SO(N)) random variable. + + Return a random rotation matrix, drawn from the Haar distribution + (the only uniform distribution on SO(N)) with a determinant of +1. + + The `dim` keyword specifies the dimension N. + + Parameters + ---------- + dim : scalar + Dimension of matrices + seed : {None, int, np.random.RandomState, np.random.Generator}, optional + Used for drawing random variates. + If `seed` is `None`, the `~np.random.RandomState` singleton is used. + If `seed` is an int, a new ``RandomState`` instance is used, seeded + with seed. + If `seed` is already a ``RandomState`` or ``Generator`` instance, + then that object is used. + Default is `None`. + + Methods + ------- + rvs(dim=None, size=1, random_state=None) + Draw random samples from SO(N). + + See Also + -------- + ortho_group, scipy.spatial.transform.Rotation.random + + Notes + ----- + The ``rvs`` method returns a random rotation matrix drawn from the Haar + distribution, the only uniform distribution on SO(N). The algorithm generates + a Haar-distributed orthogonal matrix in O(N) using the ``rvs`` method of + `ortho_group`, then adjusts the matrix to ensure that the determinant is +1. + + For a random rotation in three dimensions, see + `scipy.spatial.transform.Rotation.random`. + + Examples + -------- + >>> import numpy as np + >>> from scipy.stats import special_ortho_group + >>> x = special_ortho_group.rvs(3) + + >>> np.dot(x, x.T) + array([[ 1.00000000e+00, 1.13231364e-17, -2.86852790e-16], + [ 1.13231364e-17, 1.00000000e+00, -1.46845020e-16], + [ -2.86852790e-16, -1.46845020e-16, 1.00000000e+00]]) + + >>> import scipy.linalg + >>> scipy.linalg.det(x) + 1.0 + + This generates one random matrix from SO(3). It is orthogonal and + has a determinant of 1. + + Alternatively, the object may be called (as a function) to fix the `dim` + parameter, returning a "frozen" special_ortho_group random variable: + + >>> rv = special_ortho_group(5) + >>> # Frozen object with the same methods but holding the + >>> # dimension parameter fixed. + """ + + def __init__(self, seed=None): + super().__init__(seed) + self.__doc__ = doccer.docformat(self.__doc__) + + def __call__(self, dim=None, seed=None): + """Create a frozen SO(N) distribution. + + See `special_ortho_group_frozen` for more information. + """ + return special_ortho_group_frozen(dim, seed=seed) + + def _process_parameters(self, dim): + """Dimension N must be specified; it cannot be inferred.""" + if dim is None or not np.isscalar(dim) or dim < 0 or dim != int(dim): + raise ValueError("""Dimension of rotation must be specified, + and must be a scalar nonnegative integer.""") + + return dim + + def rvs(self, dim, size=1, random_state=None): + """Draw random samples from SO(N). + + Parameters + ---------- + dim : int + Dimension of rotation space (N). + size : int, optional + Number of samples to draw (default 1). + + Returns + ------- + rvs : ndarray or scalar + Random size N-dimensional matrices, dimension (size, dim, dim) + + """ + random_state = self._get_random_state(random_state) + + q = ortho_group.rvs(dim, size, random_state) + dets = np.linalg.det(q) + if dim: + q[..., 0, :] /= dets[..., np.newaxis] + return q + +special_ortho_group = special_ortho_group_gen() + + +class special_ortho_group_frozen(multi_rv_frozen): + __class_getitem__ = None # pyrefly:ignore[bad-assignment] + + def __init__(self, dim=None, seed=None): + """Create a frozen SO(N) distribution. + + Parameters + ---------- + dim : scalar + Dimension of matrices + seed : {None, int, `numpy.random.Generator`, `numpy.random.RandomState`}, optional + If `seed` is None (or `np.random`), the `numpy.random.RandomState` + singleton is used. + If `seed` is an int, a new ``RandomState`` instance is used, + seeded with `seed`. + If `seed` is already a ``Generator`` or ``RandomState`` instance + then that instance is used. + + Examples + -------- + >>> from scipy.stats import special_ortho_group + >>> g = special_ortho_group(5) + >>> x = g.rvs() + + """ # numpy/numpydoc#87 # noqa: E501 + self._dist = special_ortho_group_gen(seed) + self.dim = self._dist._process_parameters(dim) + + def rvs(self, size=1, random_state=None): + return self._dist.rvs(self.dim, size, random_state) + + +class ortho_group_gen(multi_rv_generic): + r""" + An Orthogonal matrix (O(N)) random variable. + + Return a random orthogonal matrix, drawn from the O(N) Haar + distribution (the only uniform distribution on O(N)). + + The `dim` keyword specifies the dimension N. + + Parameters + ---------- + dim : scalar + Dimension of matrices + seed : {None, int, np.random.RandomState, np.random.Generator}, optional + Used for drawing random variates. + If `seed` is `None`, the `~np.random.RandomState` singleton is used. + If `seed` is an int, a new ``RandomState`` instance is used, seeded + with seed. + If `seed` is already a ``RandomState`` or ``Generator`` instance, + then that object is used. + Default is `None`. + + Methods + ------- + rvs(dim=None, size=1, random_state=None) + Draw random samples from O(N). + + See Also + -------- + special_ortho_group + + Notes + ----- + This class is closely related to `special_ortho_group`. + + Some care is taken to avoid numerical error, as per the paper by Mezzadri. + + References + ---------- + .. [1] F. Mezzadri, "How to generate random matrices from the classical + compact groups", :arXiv:`math-ph/0609050v2`. + + Examples + -------- + >>> import numpy as np + >>> from scipy.stats import ortho_group + >>> x = ortho_group.rvs(3) + + >>> np.dot(x, x.T) + array([[ 1.00000000e+00, 1.13231364e-17, -2.86852790e-16], + [ 1.13231364e-17, 1.00000000e+00, -1.46845020e-16], + [ -2.86852790e-16, -1.46845020e-16, 1.00000000e+00]]) + + >>> import scipy.linalg + >>> np.fabs(scipy.linalg.det(x)) + 1.0 + + This generates one random matrix from O(3). It is orthogonal and + has a determinant of +1 or -1. + + Alternatively, the object may be called (as a function) to fix the `dim` + parameter, returning a "frozen" ortho_group random variable: + + >>> rv = ortho_group(5) + >>> # Frozen object with the same methods but holding the + >>> # dimension parameter fixed. + """ + + def __init__(self, seed=None): + super().__init__(seed) + self.__doc__ = doccer.docformat(self.__doc__) + + def __call__(self, dim=None, seed=None): + """Create a frozen O(N) distribution. + + See `ortho_group_frozen` for more information. + """ + return ortho_group_frozen(dim, seed=seed) + + def _process_parameters(self, dim): + """Dimension N must be specified; it cannot be inferred.""" + if dim is None or not np.isscalar(dim) or dim < 0 or dim != int(dim): + raise ValueError("Dimension of rotation must be specified," + "and must be a scalar nonnegative integer.") + + return dim + + def rvs(self, dim, size=1, random_state=None): + """Draw random samples from O(N). + + Parameters + ---------- + dim : int + Dimension of rotation space (N). + size : int, optional + Number of samples to draw (default 1). + + Returns + ------- + rvs : ndarray or scalar + Random size N-dimensional matrices, dimension (size, dim, dim) + + """ + random_state = self._get_random_state(random_state) + + size = int(size) + + dim = self._process_parameters(dim) + + size = (size,) if size > 1 else () + z = random_state.normal(size=size + (dim, dim)) + q, r = np.linalg.qr(z) + # The last two dimensions are the rows and columns of R matrices. + # Extract the diagonals. Note that this eliminates a dimension. + d = r.diagonal(offset=0, axis1=-2, axis2=-1) + # Add back a dimension for proper broadcasting: we're dividing + # each row of each R matrix by the diagonal of the R matrix. + q *= (d/abs(d))[..., np.newaxis, :] # to broadcast properly + return q + + +ortho_group = ortho_group_gen() + + +class ortho_group_frozen(multi_rv_frozen): + __class_getitem__ = None # pyrefly:ignore[bad-assignment] + + def __init__(self, dim=None, seed=None): + """Create a frozen O(N) distribution. + + Parameters + ---------- + dim : scalar + Dimension of matrices + seed : {None, int, `numpy.random.Generator`, `numpy.random.RandomState`}, optional + If `seed` is None (or `np.random`), the `numpy.random.RandomState` + singleton is used. + If `seed` is an int, a new ``RandomState`` instance is used, + seeded with `seed`. + If `seed` is already a ``Generator`` or ``RandomState`` instance + then that instance is used. + + Examples + -------- + >>> from scipy.stats import ortho_group + >>> g = ortho_group(5) + >>> x = g.rvs() + + """ # numpy/numpydoc#87 # noqa: E501 + self._dist = ortho_group_gen(seed) + self.dim = self._dist._process_parameters(dim) + + def rvs(self, size=1, random_state=None): + return self._dist.rvs(self.dim, size, random_state) + + +class random_correlation_gen(multi_rv_generic): + r""" + A random correlation matrix. + + Return a random correlation matrix, given a vector of eigenvalues. + The returned matrix is symmetric positive semidefinite with unit diagonal. + + The `eigs` keyword specifies the eigenvalues of the correlation matrix, + and implies the dimension. + + Parameters + ---------- + eigs : 1d ndarray + Eigenvalues of correlation matrix. All eigenvalues need to be non-negative and + need to sum to the number of eigenvalues. + seed : {None, int, `numpy.random.Generator`, `numpy.random.RandomState`}, optional + If `seed` is None (or `np.random`), the `numpy.random.RandomState` + singleton is used. + If `seed` is an int, a new ``RandomState`` instance is used, + seeded with `seed`. + If `seed` is already a ``Generator`` or ``RandomState`` instance + then that instance is used. + tol : float, optional + Tolerance for input parameter checks + diag_tol : float, optional + Tolerance for deviation of the diagonal of the resulting + matrix. Default: 1e-7 + + Methods + ------- + rvs(eigs=None, random_state=None) + Draw random correlation matrices, all with eigenvalues eigs. + + Returns + ------- + rvs : ndarray or scalar + Random size N-dimensional matrices, dimension (size, dim, dim), + each having eigenvalues eigs. + + Raises + ------ + RuntimeError + Floating point error prevented generating a valid correlation + matrix. + + Notes + ----- + + Generates a random correlation matrix following a numerically stable + algorithm spelled out by Davies & Higham. This algorithm uses a single O(N) + similarity transformation to construct a symmetric positive semi-definite + matrix, and applies a series of Givens rotations to scale it to have ones + on the diagonal. + + References + ---------- + + .. [1] Davies, Philip I; Higham, Nicholas J; "Numerically stable generation + of correlation matrices and their factors", BIT 2000, Vol. 40, + No. 4, pp. 640 651 + + Examples + -------- + >>> import numpy as np + >>> from scipy.stats import random_correlation + >>> rng = np.random.default_rng() + >>> x = random_correlation.rvs((.5, .8, 1.2, 1.5), random_state=rng) + >>> x + array([[ 1. , -0.02423399, 0.03130519, 0.4946965 ], + [-0.02423399, 1. , 0.20334736, 0.04039817], + [ 0.03130519, 0.20334736, 1. , 0.02694275], + [ 0.4946965 , 0.04039817, 0.02694275, 1. ]]) + >>> import scipy.linalg + >>> e, v = scipy.linalg.eigh(x) + >>> e + array([ 0.5, 0.8, 1.2, 1.5]) + """ + + def __init__(self, seed=None): + super().__init__(seed) + self.__doc__ = doccer.docformat(self.__doc__) + + def __call__(self, eigs, seed=None, tol=1e-13, diag_tol=1e-7): + """Create a frozen random correlation matrix. + + See `random_correlation_frozen` for more information. + """ + return random_correlation_frozen(eigs, seed=seed, tol=tol, + diag_tol=diag_tol) + + def _process_parameters(self, eigs, tol): + eigs = np.asarray(eigs, dtype=float) + dim = eigs.size + + if eigs.ndim != 1 or eigs.shape[0] != dim or dim <= 1: + raise ValueError("Array 'eigs' must be a vector of length " + "greater than 1.") + + if np.fabs(np.sum(eigs) - dim) > tol: + raise ValueError("Sum of eigenvalues must equal dimensionality.") + + for x in eigs: + if x < -tol: + raise ValueError("All eigenvalues must be non-negative.") + + return dim, eigs + + def _givens_to_1(self, aii, ajj, aij): + """Computes a 2x2 Givens matrix to put 1's on the diagonal. + + The input matrix is a 2x2 symmetric matrix M = [ aii aij ; aij ajj ]. + + The output matrix g is a 2x2 anti-symmetric matrix of the form + [ c s ; -s c ]; the elements c and s are returned. + + Applying the output matrix to the input matrix (as b=g.T M g) + results in a matrix with bii=1, provided tr(M) - det(M) >= 1 + and floating point issues do not occur. Otherwise, some other + valid rotation is returned. When tr(M)==2, also bjj=1. + + """ + aiid = aii - 1. + ajjd = ajj - 1. + + if ajjd == 0: + # ajj==1, so swap aii and ajj to avoid division by zero + return 0., 1. + + dd = math.sqrt(max(aij**2 - aiid*ajjd, 0)) + + # The choice of t should be chosen to avoid cancellation [1] + t = (aij + math.copysign(dd, aij)) / ajjd + c = 1. / math.sqrt(1. + t*t) + if c == 0: + # Underflow + s = 1.0 + else: + s = c*t + return c, s + + def _to_corr(self, m): + """ + Given a psd matrix m, rotate to put one's on the diagonal, turning it + into a correlation matrix. This also requires the trace equal the + dimensionality. Note: modifies input matrix + """ + # Check requirements for in-place Givens + if not (m.flags.c_contiguous and m.dtype == np.float64 and + m.shape[0] == m.shape[1]): + raise ValueError() + + drot = get_blas_funcs('rot', dtype=np.float64, ilp64='preferred') + + d = m.shape[0] + for i in range(d-1): + if m[i, i] == 1: + continue + elif m[i, i] > 1: + for j in range(i+1, d): + if m[j, j] < 1: + break + else: + for j in range(i+1, d): + if m[j, j] > 1: + break + + c, s = self._givens_to_1(m[i, i], m[j, j], m[i, j]) + + # Use BLAS to apply Givens rotations in-place. Equivalent to: + # g = np.eye(d) + # g[i, i] = g[j,j] = c + # g[j, i] = -s; g[i, j] = s + # m = np.dot(g.T, np.dot(m, g)) + mv = m.ravel() + drot(mv, mv, c, -s, n=d, + offx=i*d, incx=1, offy=j*d, incy=1, + overwrite_x=True, overwrite_y=True) + drot(mv, mv, c, -s, n=d, + offx=i, incx=d, offy=j, incy=d, + overwrite_x=True, overwrite_y=True) + + return m + + def rvs(self, eigs, random_state=None, tol=1e-13, diag_tol=1e-7): + """Draw random correlation matrices. + + Parameters + ---------- + eigs : 1d ndarray + Eigenvalues of correlation matrix + tol : float, optional + Tolerance for input parameter checks + diag_tol : float, optional + Tolerance for deviation of the diagonal of the resulting + matrix. Default: 1e-7 + + Raises + ------ + RuntimeError + Floating point error prevented generating a valid correlation + matrix. + + Returns + ------- + rvs : ndarray or scalar + Random size N-dimensional matrices, dimension (size, dim, dim), + each having eigenvalues eigs. + + """ + dim, eigs = self._process_parameters(eigs, tol=tol) + + random_state = self._get_random_state(random_state) + + m = ortho_group.rvs(dim, random_state=random_state) + m = np.dot(np.dot(m, np.diag(eigs)), m.T) # Set the trace of m + m = self._to_corr(m) # Carefully rotate to unit diagonal + + # Check diagonal + if abs(m.diagonal() - 1).max() > diag_tol: + raise RuntimeError("Failed to generate a valid correlation matrix") + + return m + + +random_correlation = random_correlation_gen() + + +class random_correlation_frozen(multi_rv_frozen): + __class_getitem__ = None # pyrefly:ignore[bad-assignment] + + def __init__(self, eigs, seed=None, tol=1e-13, diag_tol=1e-7): + """Create a frozen random correlation matrix distribution. + + Parameters + ---------- + eigs : 1d ndarray + Eigenvalues of correlation matrix + seed : {None, int, `numpy.random.Generator`, `numpy.random.RandomState`}, optional + If `seed` is None (or `np.random`), the `numpy.random.RandomState` + singleton is used. + If `seed` is an int, a new ``RandomState`` instance is used, + seeded with `seed`. + If `seed` is already a ``Generator`` or ``RandomState`` instance + then that instance is used. + tol : float, optional + Tolerance for input parameter checks + diag_tol : float, optional + Tolerance for deviation of the diagonal of the resulting + matrix. Default: 1e-7 + + Raises + ------ + RuntimeError + Floating point error prevented generating a valid correlation + matrix. + + Returns + ------- + rvs : ndarray or scalar + Random size N-dimensional matrices, dimension (size, dim, dim), + each having eigenvalues eigs. + """ # numpy/numpydoc#87 # noqa: E501 + + self._dist = random_correlation_gen(seed) + self.tol = tol + self.diag_tol = diag_tol + _, self.eigs = self._dist._process_parameters(eigs, tol=self.tol) + + def rvs(self, random_state=None): + return self._dist.rvs(self.eigs, random_state=random_state, + tol=self.tol, diag_tol=self.diag_tol) + + +class unitary_group_gen(multi_rv_generic): + r""" + A matrix-valued U(N) random variable. + + Return a random unitary matrix. + + The `dim` keyword specifies the dimension N. + + Parameters + ---------- + dim : scalar + Dimension of matrices. + seed : {None, int, np.random.RandomState, np.random.Generator}, optional + Used for drawing random variates. + If `seed` is `None`, the `~np.random.RandomState` singleton is used. + If `seed` is an int, a new ``RandomState`` instance is used, seeded + with seed. + If `seed` is already a ``RandomState`` or ``Generator`` instance, + then that object is used. + Default is `None`. + + Methods + ------- + rvs(dim=None, size=1, random_state=None) + Draw random samples from U(N). + + See Also + -------- + ortho_group + + Notes + ----- + This class is similar to `ortho_group`. + + References + ---------- + .. [1] F. Mezzadri, "How to generate random matrices from the classical + compact groups", :arXiv:`math-ph/0609050v2`. + + Examples + -------- + >>> import numpy as np + >>> from scipy.stats import unitary_group + >>> x = unitary_group.rvs(3) + + >>> np.dot(x, x.conj().T) + array([[ 1.00000000e+00, 1.13231364e-17, -2.86852790e-16], + [ 1.13231364e-17, 1.00000000e+00, -1.46845020e-16], + [ -2.86852790e-16, -1.46845020e-16, 1.00000000e+00]]) # may vary + + This generates one random matrix from U(3). The dot product confirms that + it is unitary up to machine precision. + + Alternatively, the object may be called (as a function) to fix the `dim` + parameter, return a "frozen" unitary_group random variable: + + >>> rv = unitary_group(5) + """ + + def __init__(self, seed=None): + super().__init__(seed) + self.__doc__ = doccer.docformat(self.__doc__) + + def __call__(self, dim=None, seed=None): + """Create a frozen (U(N)) n-dimensional unitary matrix distribution. + + See `unitary_group_frozen` for more information. + """ + return unitary_group_frozen(dim, seed=seed) + + def _process_parameters(self, dim): + """Dimension N must be specified; it cannot be inferred.""" + if dim is None or not np.isscalar(dim) or dim < 0 or dim != int(dim): + raise ValueError("Dimension of rotation must be specified," + "and must be a scalar nonnegative integer.") + + return dim + + def rvs(self, dim, size=1, random_state=None): + """Draw random samples from U(N). + + Parameters + ---------- + dim : int + Dimension of space (N). + size : int, optional + Number of samples to draw (default 1). + + Returns + ------- + rvs : ndarray or scalar + Random size N-dimensional matrices, dimension (size, dim, dim) + + """ + random_state = self._get_random_state(random_state) + + size = int(size) + + dim = self._process_parameters(dim) + + size = (size,) if size > 1 else () + z = 1/math.sqrt(2)*(random_state.normal(size=size + (dim, dim)) + + 1j*random_state.normal(size=size + (dim, dim))) + q, r = np.linalg.qr(z) + # The last two dimensions are the rows and columns of R matrices. + # Extract the diagonals. Note that this eliminates a dimension. + d = r.diagonal(offset=0, axis1=-2, axis2=-1) + # Add back a dimension for proper broadcasting: we're dividing + # each row of each R matrix by the diagonal of the R matrix. + q *= (d/abs(d))[..., np.newaxis, :] # to broadcast properly + return q + + +unitary_group = unitary_group_gen() + + +class unitary_group_frozen(multi_rv_frozen): + __class_getitem__ = None # pyrefly:ignore[bad-assignment] + + def __init__(self, dim=None, seed=None): + """Create a frozen (U(N)) n-dimensional unitary matrix distribution. + + Parameters + ---------- + dim : scalar + Dimension of matrices + seed : {None, int, `numpy.random.Generator`, `numpy.random.RandomState`}, optional + If `seed` is None (or `np.random`), the `numpy.random.RandomState` + singleton is used. + If `seed` is an int, a new ``RandomState`` instance is used, + seeded with `seed`. + If `seed` is already a ``Generator`` or ``RandomState`` instance + then that instance is used. + + Examples + -------- + >>> from scipy.stats import unitary_group + >>> x = unitary_group(3) + >>> x.rvs() + + """ # numpy/numpydoc#87 # noqa: E501 + self._dist = unitary_group_gen(seed) + self.dim = self._dist._process_parameters(dim) + + def rvs(self, size=1, random_state=None): + return self._dist.rvs(self.dim, size, random_state) + + +_mvt_doc_default_callparams = """\ +loc : array_like, optional + Location of the distribution. (default ``0``) +shape : array_like, optional + Positive semidefinite matrix of the distribution. (default ``1``) +df : float, optional + Degrees of freedom of the distribution; must be greater than zero. + If ``np.inf`` then results are multivariate normal. The default is ``1``. +allow_singular : bool, optional + Whether to allow a singular matrix. (default ``False``) +""" + +_mvt_doc_callparams_note = """\ +Setting the parameter `loc` to ``None`` is equivalent to having `loc` +be the zero-vector. The parameter `shape` can be a scalar, in which case +the shape matrix is the identity times that value, a vector of +diagonal entries for the shape matrix, or a two-dimensional array_like. +""" + +_mvt_doc_frozen_callparams_note = """\ +See class definition for a detailed description of parameters.""" + +mvt_docdict_params = { + '_mvt_doc_default_callparams': _mvt_doc_default_callparams, + '_mvt_doc_callparams_note': _mvt_doc_callparams_note, + '_doc_random_state': _doc_random_state +} + +mvt_docdict_noparams = { + '_mvt_doc_default_callparams': "", + '_mvt_doc_callparams_note': _mvt_doc_frozen_callparams_note, + '_doc_random_state': _doc_random_state +} + + +class multivariate_t_gen(multi_rv_generic): + r""" + A multivariate t-distributed random variable. + + The `loc` parameter specifies the location. The `shape` parameter specifies + the positive semidefinite shape matrix. The `df` parameter specifies the + degrees of freedom. + + In addition to calling the methods below, the object itself may be called + as a function to fix the location, shape matrix, and degrees of freedom + parameters, returning a "frozen" multivariate t-distribution random. + + Parameters + ---------- + %(_mvt_doc_default_callparams)s + %(_doc_random_state)s + + Methods + ------- + pdf(x, loc=None, shape=1, df=1, allow_singular=False) + Probability density function. + logpdf(x, loc=None, shape=1, df=1, allow_singular=False) + Log of the probability density function. + cdf(x, loc=None, shape=1, df=1, allow_singular=False, *, + maxpts=None, lower_limit=None, random_state=None) + Cumulative distribution function. + rvs(loc=None, shape=1, df=1, size=1, random_state=None) + Draw random samples from a multivariate t-distribution. + entropy(loc=None, shape=1, df=1) + Differential entropy of a multivariate t-distribution. + marginal(dimensions, loc=None, shape=1, df=1, allow_singular=False) + Return a marginal multivariate t-distribution. + + Notes + ----- + %(_mvt_doc_callparams_note)s + The matrix `shape` must be a (symmetric) positive semidefinite matrix. The + determinant and inverse of `shape` are computed as the pseudo-determinant + and pseudo-inverse, respectively, so that `shape` does not need to have + full rank. + + The probability density function for `multivariate_t` is + + .. math:: + + f(x) = \frac{\Gamma((\nu + p)/2)}{\Gamma(\nu/2)\nu^{p/2}\pi^{p/2}|\Sigma|^{1/2}} + \left[1 + \frac{1}{\nu} (\mathbf{x} - \boldsymbol{\mu})^{\top} + \boldsymbol{\Sigma}^{-1} + (\mathbf{x} - \boldsymbol{\mu}) \right]^{-(\nu + p)/2}, + + where :math:`p` is the dimension of :math:`\mathbf{x}`, + :math:`\boldsymbol{\mu}` is the :math:`p`-dimensional location, + :math:`\boldsymbol{\Sigma}` the :math:`p \times p`-dimensional shape + matrix, and :math:`\nu` is the degrees of freedom. + + .. versionadded:: 1.6.0 + + References + ---------- + .. [1] Arellano-Valle et al. "Shannon Entropy and Mutual Information for + Multivariate Skew-Elliptical Distributions". Scandinavian Journal + of Statistics. Vol. 40, issue 1. + + Examples + -------- + The object may be called (as a function) to fix the `loc`, `shape`, + `df`, and `allow_singular` parameters, returning a "frozen" + multivariate_t random variable: + + >>> import numpy as np + >>> from scipy.stats import multivariate_t + >>> rv = multivariate_t([1.0, -0.5], [[2.1, 0.3], [0.3, 1.5]], df=2) + >>> # Frozen object with the same methods but holding the given location, + >>> # scale, and degrees of freedom fixed. + + Create a contour plot of the PDF. + + >>> import matplotlib.pyplot as plt + >>> x, y = np.mgrid[-1:3:.01, -2:1.5:.01] + >>> pos = np.dstack((x, y)) + >>> fig, ax = plt.subplots(1, 1) + >>> ax.set_aspect('equal') + >>> plt.contourf(x, y, rv.pdf(pos)) + """ + + def __init__(self, seed=None): + """Initialize a multivariate t-distributed random variable. + + Parameters + ---------- + seed : Random state. + + """ + super().__init__(seed) + self.__doc__ = doccer.docformat(self.__doc__, mvt_docdict_params) + self._random_state = check_random_state(seed) + + def __call__(self, loc=None, shape=1, df=1, allow_singular=False, + seed=None): + """Create a frozen multivariate t-distribution. + + See `multivariate_t_frozen` for parameters. + """ + if df == np.inf: + return multivariate_normal_frozen(mean=loc, cov=shape, + allow_singular=allow_singular, + seed=seed) + return multivariate_t_frozen(loc=loc, shape=shape, df=df, + allow_singular=allow_singular, seed=seed) + + def pdf(self, x, loc=None, shape=1, df=1, allow_singular=False): + """Multivariate t-distribution probability density function. + + Parameters + ---------- + x : array_like + Points at which to evaluate the probability density function. + %(_mvt_doc_default_callparams)s + + Returns + ------- + pdf : Probability density function evaluated at `x`. + + Examples + -------- + >>> from scipy.stats import multivariate_t + >>> x = [0.4, 5] + >>> loc = [0, 1] + >>> shape = [[1, 0.1], [0.1, 1]] + >>> df = 7 + >>> multivariate_t.pdf(x, loc, shape, df) + 0.00075713 + + """ + dim, loc, shape, df = self._process_parameters(loc, shape, df) + x = self._process_quantiles(x, dim) + shape_info = _PSD(shape, allow_singular=allow_singular) + logpdf = self._logpdf(x, loc, shape_info.U, shape_info.log_pdet, df, + dim, shape_info.rank) + return np.exp(logpdf) + + def logpdf(self, x, loc=None, shape=1, df=1): + """Log of the multivariate t-distribution probability density function. + + Parameters + ---------- + x : array_like + Points at which to evaluate the log of the probability density + function. + %(_mvt_doc_default_callparams)s + + Returns + ------- + logpdf : Log of the probability density function evaluated at `x`. + + Examples + -------- + >>> from scipy.stats import multivariate_t + >>> x = [0.4, 5] + >>> loc = [0, 1] + >>> shape = [[1, 0.1], [0.1, 1]] + >>> df = 7 + >>> multivariate_t.logpdf(x, loc, shape, df) + -7.1859802 + + See Also + -------- + pdf : Probability density function. + + """ + dim, loc, shape, df = self._process_parameters(loc, shape, df) + x = self._process_quantiles(x, dim) + shape_info = _PSD(shape) + cov_object = _covariance.CovViaPSD(shape_info) + + return self._logpdf(x, loc, shape_info.U, shape_info.log_pdet, df, dim, + shape_info.rank, cov_object) + + def _logpdf(self, x, loc, prec_U, log_pdet, df, dim, rank, cov_object=None): + """Utility method `pdf`, `logpdf` for parameters. + + Parameters + ---------- + x : ndarray + Points at which to evaluate the log of the probability density + function. + loc : ndarray + Location of the distribution. + prec_U : ndarray + A decomposition such that `np.dot(prec_U, prec_U.T)` is the inverse + of the shape matrix. + log_pdet : float + Logarithm of the determinant of the shape matrix. + df : float + Degrees of freedom of the distribution. + dim : int + Dimension of the quantiles x. + rank : int + Rank of the shape matrix. + + Notes + ----- + As this function does no argument checking, it should not be called + directly; use 'logpdf' instead. + + """ + if df == np.inf: + return multivariate_normal._logpdf(x, loc, cov_object) + + dev = x - loc + maha = np.square(np.dot(dev, prec_U)).sum(axis=-1) + + t = 0.5 * (df + dim) + A = gammaln(t) + B = gammaln(0.5 * df) + C = dim/2. * np.log(df * np.pi) + D = 0.5 * log_pdet + E = -t * np.log(1 + (1./df) * maha) + + return _squeeze_output(A - B - C - D + E) + + def _cdf(self, x, loc, shape, df, dim, maxpts=None, lower_limit=None, + random_state=None): + + # All of this - random state validation, maxpts, apply_along_axis, + # etc. needs to go in this private method unless we want + # frozen distribution's `cdf` method to duplicate it or call `cdf`, + # which would require re-processing parameters + if random_state is not None: + rng = check_random_state(random_state) + else: + rng = self._random_state + + if not maxpts: + maxpts = 1000 * dim + + x = self._process_quantiles(x, dim) + lower_limit = (np.full(loc.shape, -np.inf) + if lower_limit is None else lower_limit) + + # remove the mean + x, lower_limit = x - loc, lower_limit - loc + + b, a = np.broadcast_arrays(x, lower_limit) + i_swap = b < a + signs = (-1)**(i_swap.sum(axis=-1)) # odd # of swaps -> negative + a, b = a.copy(), b.copy() + a[i_swap], b[i_swap] = b[i_swap], a[i_swap] + n = x.shape[-1] + limits = np.concatenate((a, b), axis=-1) + + def func1d(limits): + a, b = limits[:n], limits[n:] + return _qmvt(maxpts, df, shape, a, b, rng)[0] + + res = np.apply_along_axis(func1d, -1, limits) * signs + # Fixing the output shape for existing distributions is a separate + # issue. For now, let's keep this consistent with pdf. + return _squeeze_output(res) + + def cdf(self, x, loc=None, shape=1, df=1, allow_singular=False, *, + maxpts=None, lower_limit=None, random_state=None): + """Multivariate t-distribution cumulative distribution function. + + Parameters + ---------- + x : array_like + Points at which to evaluate the cumulative distribution function. + %(_mvt_doc_default_callparams)s + maxpts : int, optional + Maximum number of points to use for integration. The default is + 1000 times the number of dimensions. + lower_limit : array_like, optional + Lower limit of integration of the cumulative distribution function. + Default is negative infinity. Must be broadcastable with `x`. + %(_doc_random_state)s + + Returns + ------- + cdf : ndarray or scalar + Cumulative distribution function evaluated at `x`. + + Examples + -------- + >>> from scipy.stats import multivariate_t + >>> x = [0.4, 5] + >>> loc = [0, 1] + >>> shape = [[1, 0.1], [0.1, 1]] + >>> df = 7 + >>> multivariate_t.cdf(x, loc, shape, df) + 0.64798491 + + """ + dim, loc, shape, df = self._process_parameters(loc, shape, df) + shape = _PSD(shape, allow_singular=allow_singular)._M + + return self._cdf(x, loc, shape, df, dim, maxpts, + lower_limit, random_state) + + def _entropy(self, dim, df=1, shape=1): + if df == np.inf: + return multivariate_normal(None, cov=shape).entropy() + + shape_info = _PSD(shape) + shape_term = 0.5 * shape_info.log_pdet + + def regular(dim, df): + halfsum = 0.5 * (dim + df) + half_df = 0.5 * df + return ( + -gammaln(halfsum) + gammaln(half_df) + + 0.5 * dim * np.log(df * np.pi) + halfsum + * (psi(halfsum) - psi(half_df)) + + shape_term + ) + + def asymptotic(dim, df): + # Formula from Wolfram Alpha: + # "asymptotic expansion -gammaln((m+d)/2) + gammaln(d/2) + (m*log(d*pi))/2 + # + ((m+d)/2) * (digamma((m+d)/2) - digamma(d/2))" + return ( + dim * norm._entropy() + dim / df + - dim * (dim - 2) * df**-2.0 / 4 + + dim**2 * (dim - 2) * df**-3.0 / 6 + + dim * (-3 * dim**3 + 8 * dim**2 - 8) * df**-4.0 / 24 + + dim**2 * (3 * dim**3 - 10 * dim**2 + 16) * df**-5.0 / 30 + + shape_term + )[()] + + # preserves ~12 digits accuracy up to at least `dim=1e5`. See gh-18465. + threshold = dim * 100 * 4 / (np.log(dim) + 1) + return xpx.apply_where(df >= threshold, (dim, df), asymptotic, regular) + + def entropy(self, loc=None, shape=1, df=1): + """Calculate the differential entropy of a multivariate + t-distribution. + + Parameters + ---------- + %(_mvt_doc_default_callparams)s + + Returns + ------- + h : float + Differential entropy + + """ + dim, loc, shape, df = self._process_parameters(None, shape, df) + return self._entropy(dim, df, shape) + + def rvs(self, loc=None, shape=1, df=1, size=1, random_state=None): + """Draw random samples from a multivariate t-distribution. + + Parameters + ---------- + %(_mvt_doc_default_callparams)s + size : int, optional + Number of samples to draw (default 1). + %(_doc_random_state)s + + Returns + ------- + rvs : ndarray or scalar + Random variates of size (`size`, `P`), where `P` is the + dimension of the random variable. + + Examples + -------- + >>> from scipy.stats import multivariate_t + >>> x = [0.4, 5] + >>> loc = [0, 1] + >>> shape = [[1, 0.1], [0.1, 1]] + >>> df = 7 + >>> multivariate_t.rvs(loc, shape, df) + array([[0.93477495, 3.00408716]]) + + """ + # For implementation details, see equation (3): + # + # Hofert, "On Sampling from the Multivariatet Distribution", 2013 + # http://rjournal.github.io/archive/2013-2/hofert.pdf + # + dim, loc, shape, df = self._process_parameters(loc, shape, df) + if random_state is not None: + rng = check_random_state(random_state) + else: + rng = self._random_state + + if np.isinf(df): + x = np.ones(size) + else: + x = rng.chisquare(df, size=size) / df + + z = rng.multivariate_normal(np.zeros(dim), shape, size=size) + samples = loc + z / np.sqrt(x)[..., None] + return _squeeze_output(samples) + + def _process_quantiles(self, x, dim): + """ + Adjust quantiles array so that last axis labels the components of + each data point. + """ + x = np.asarray(x, dtype=float) + if x.ndim == 0: + x = x[np.newaxis] + elif x.ndim == 1: + if dim == 1: + x = x[:, np.newaxis] + else: + x = x[np.newaxis, :] + return x + + def _process_parameters(self, loc, shape, df): + """ + Infer dimensionality from location array and shape matrix, handle + defaults, and ensure compatible dimensions. + """ + if loc is None and shape is None: + loc = np.asarray(0, dtype=float) + shape = np.asarray(1, dtype=float) + dim = 1 + elif loc is None: + shape = np.asarray(shape, dtype=float) + if shape.ndim < 2: + dim = 1 + else: + dim = shape.shape[0] + loc = np.zeros(dim) + elif shape is None: + loc = np.asarray(loc, dtype=float) + dim = loc.size + shape = np.eye(dim) + else: + shape = np.asarray(shape, dtype=float) + loc = np.asarray(loc, dtype=float) + dim = loc.size + + if dim == 1: + loc = loc.reshape(1) + shape = shape.reshape(1, 1) + + if loc.ndim != 1 or loc.shape[0] != dim: + raise ValueError(f"Array 'loc' must be a vector of length {dim}.") + if shape.ndim == 0: + shape = shape * np.eye(dim) + elif shape.ndim == 1: + shape = np.diag(shape) + elif shape.ndim == 2 and shape.shape != (dim, dim): + rows, cols = shape.shape + if rows != cols: + msg = ("Array 'cov' must be square if it is two dimensional," + f" but cov.shape = {str(shape.shape)}.") + else: + msg = ("Dimension mismatch: array 'cov' is of shape %s," + " but 'loc' is a vector of length %d.") + msg = msg % (str(shape.shape), len(loc)) + raise ValueError(msg) + elif shape.ndim > 2: + raise ValueError(f"Array 'cov' must be at most two-dimensional, " + f"but cov.ndim = {shape.ndim}") + + # Process degrees of freedom. + if df is None: + df = 1 + elif df <= 0: + raise ValueError("'df' must be greater than zero.") + elif np.isnan(df): + raise ValueError("'df' is 'nan' but must be greater than zero or 'np.inf'.") + + return dim, loc, shape, df + + def marginal(self, dimensions, loc=None, shape=1, df=1, allow_singular=False): + """Return a marginal multivariate t-distribution. + + Parameters + ---------- + dimensions : int or 1-d array_like + The dimensions of the multivariate t corresponding + with the marginal variables, that is, the indices of the dimensions + that are being retained. The other dimensions are marginalized out. + %(_mvt_doc_default_callparams)s + + Returns + ------- + marginal_multivariate_t : multivariate_t_frozen + An object representing the marginal t-distribution. + + Notes + ----- + %(_mvt_doc_frozen_callparams_note)s + """ + params = self._process_parameters(loc, shape, df) + n, loc, shape, df = params + dims = _validate_marginal_input(dimensions, n) + + loc = loc[dims] + shape = shape[np.ix_(dims, dims)] + + return multivariate_t_frozen(loc, shape, df, allow_singular) + + +class multivariate_t_frozen(multi_rv_frozen): + __class_getitem__ = None # pyrefly:ignore[bad-assignment] + + def __init__(self, loc=None, shape=1, df=1, allow_singular=False, + seed=None): + """Create a frozen multivariate t distribution. + + Parameters + ---------- + %(_mvt_doc_default_callparams)s + + Examples + -------- + >>> import numpy as np + >>> from scipy.stats import multivariate_t + >>> loc = np.zeros(3) + >>> shape = np.eye(3) + >>> df = 10 + >>> dist = multivariate_t(loc, shape, df) + >>> dist.rvs() + array([[ 0.81412036, -1.53612361, 0.42199647]]) + >>> dist.pdf([1, 1, 1]) + array([0.01237803]) + + """ + self._dist = multivariate_t_gen(seed) + dim, loc, shape, df = self._dist._process_parameters(loc, shape, df) + self.dim, self.loc, self.shape, self.df = dim, loc, shape, df + self.shape_info = _PSD(shape, allow_singular=allow_singular) + self.allow_singular = allow_singular + + def logpdf(self, x): + x = self._dist._process_quantiles(x, self.dim) + U = self.shape_info.U + log_pdet = self.shape_info.log_pdet + return self._dist._logpdf(x, self.loc, U, log_pdet, self.df, self.dim, + self.shape_info.rank) + + def cdf(self, x, *, maxpts=None, lower_limit=None, random_state=None): + x = self._dist._process_quantiles(x, self.dim) + return self._dist._cdf(x, self.loc, self.shape, self.df, self.dim, + maxpts, lower_limit, random_state) + + def pdf(self, x): + return np.exp(self.logpdf(x)) + + def rvs(self, size=1, random_state=None): + return self._dist.rvs(loc=self.loc, + shape=self.shape, + df=self.df, + size=size, + random_state=random_state) + + def entropy(self): + return self._dist._entropy(self.dim, self.df, self.shape) + + def marginal(self, dimensions): + return self._dist.marginal(dimensions, self.loc, + self.shape, self.df, self.allow_singular) + +multivariate_t = multivariate_t_gen() + + +# Set frozen generator docstrings from corresponding docstrings in +# multivariate_t_gen and fill in default strings in class docstrings +for name in ['logpdf', 'pdf', 'rvs', 'cdf', 'entropy']: + method = multivariate_t_gen.__dict__[name] + method_frozen = multivariate_t_frozen.__dict__[name] + method_frozen.__doc__ = doccer.docformat(method.__doc__, + mvt_docdict_noparams) + method.__doc__ = doccer.docformat(method.__doc__, mvt_docdict_params) + + +_mhg_doc_default_callparams = """\ +m : array_like + The number of each type of object in the population. + That is, :math:`m[i]` is the number of objects of + type :math:`i`. +n : array_like + The number of samples taken from the population. +""" + +_mhg_doc_callparams_note = """\ +`m` must be an array of positive integers. If the quantile +:math:`i` contains values out of the range :math:`[0, m_i]` +where :math:`m_i` is the number of objects of type :math:`i` +in the population or if the parameters are inconsistent with one +another (e.g. ``x.sum() != n``), methods return the appropriate +value (e.g. ``0`` for ``pmf``). If `m` or `n` contain negative +values, the result will contain ``nan`` there. +""" + +_mhg_doc_frozen_callparams = "" + +_mhg_doc_frozen_callparams_note = """\ +See class definition for a detailed description of parameters.""" + +mhg_docdict_params = { + '_doc_default_callparams': _mhg_doc_default_callparams, + '_doc_callparams_note': _mhg_doc_callparams_note, + '_doc_random_state': _doc_random_state +} + +mhg_docdict_noparams = { + '_doc_default_callparams': _mhg_doc_frozen_callparams, + '_doc_callparams_note': _mhg_doc_frozen_callparams_note, + '_doc_random_state': _doc_random_state +} + + +class multivariate_hypergeom_gen(multi_rv_generic): + r""" + A multivariate hypergeometric random variable. + + Parameters + ---------- + %(_doc_default_callparams)s + %(_doc_random_state)s + + Methods + ------- + pmf(x, m, n) + Probability mass function. + logpmf(x, m, n) + Log of the probability mass function. + rvs(m, n, size=1, random_state=None) + Draw random samples from a multivariate hypergeometric + distribution. + mean(m, n) + Mean of the multivariate hypergeometric distribution. + var(m, n) + Variance of the multivariate hypergeometric distribution. + cov(m, n) + Compute the covariance matrix of the multivariate + hypergeometric distribution. + + See Also + -------- + scipy.stats.hypergeom : The hypergeometric distribution. + scipy.stats.multinomial : The multinomial distribution. + + Notes + ----- + %(_doc_callparams_note)s + + The probability mass function for `multivariate_hypergeom` is + + .. math:: + + P(X_1 = x_1, X_2 = x_2, \ldots, X_k = x_k) = \frac{\binom{m_1}{x_1} + \binom{m_2}{x_2} \cdots \binom{m_k}{x_k}}{\binom{M}{n}}, \\ \quad + (x_1, x_2, \ldots, x_k) \in \mathbb{N}^k \text{ with } + \sum_{i=1}^k x_i = n + + where :math:`m_i` are the number of objects of type :math:`i`, :math:`M` + is the total number of objects in the population (sum of all the + :math:`m_i`), and :math:`n` is the size of the sample to be taken + from the population. + + .. versionadded:: 1.6.0 + + References + ---------- + .. [1] The Multivariate Hypergeometric Distribution, + http://www.randomservices.org/random/urn/MultiHypergeometric.html + .. [2] Thomas J. Sargent and John Stachurski, 2020, + Multivariate Hypergeometric Distribution + https://python.quantecon.org/multi_hyper.html + + Examples + -------- + To evaluate the probability mass function of the multivariate + hypergeometric distribution, with a dichotomous population of size + :math:`10` and :math:`20`, at a sample of size :math:`12` with + :math:`8` objects of the first type and :math:`4` objects of the + second type, use: + + >>> from scipy.stats import multivariate_hypergeom + >>> multivariate_hypergeom.pmf(x=[8, 4], m=[10, 20], n=12) + 0.0025207176631464523 + + The `multivariate_hypergeom` distribution is identical to the + corresponding `hypergeom` distribution (tiny numerical differences + notwithstanding) when only two types (good and bad) of objects + are present in the population as in the example above. Consider + another example for a comparison with the hypergeometric distribution: + + >>> from scipy.stats import hypergeom + >>> multivariate_hypergeom.pmf(x=[3, 1], m=[10, 5], n=4) + 0.4395604395604395 + >>> hypergeom.pmf(k=3, M=15, n=4, N=10) + 0.43956043956044005 + + The functions ``pmf``, ``logpmf``, ``mean``, ``var``, ``cov``, and ``rvs`` + support broadcasting, under the convention that the vector parameters + (``x``, ``m``, and ``n``) are interpreted as if each row along the last + axis is a single object. For instance, we can combine the previous two + calls to `multivariate_hypergeom` as + + >>> multivariate_hypergeom.pmf(x=[[8, 4], [3, 1]], m=[[10, 20], [10, 5]], + ... n=[12, 4]) + array([0.00252072, 0.43956044]) + + This broadcasting also works for ``cov``, where the output objects are + square matrices of size ``m.shape[-1]``. For example: + + >>> multivariate_hypergeom.cov(m=[[7, 9], [10, 15]], n=[8, 12]) + array([[[ 1.05, -1.05], + [-1.05, 1.05]], + [[ 1.56, -1.56], + [-1.56, 1.56]]]) + + That is, ``result[0]`` is equal to + ``multivariate_hypergeom.cov(m=[7, 9], n=8)`` and ``result[1]`` is equal + to ``multivariate_hypergeom.cov(m=[10, 15], n=12)``. + + Alternatively, the object may be called (as a function) to fix the `m` + and `n` parameters, returning a "frozen" multivariate hypergeometric + random variable. + + >>> rv = multivariate_hypergeom(m=[10, 20], n=12) + >>> rv.pmf(x=[8, 4]) + 0.0025207176631464523 + """ + def __init__(self, seed=None): + super().__init__(seed) + self.__doc__ = doccer.docformat(self.__doc__, mhg_docdict_params) + + def __call__(self, m, n, seed=None): + """Create a frozen multivariate_hypergeom distribution. + + See `multivariate_hypergeom_frozen` for more information. + """ + return multivariate_hypergeom_frozen(m, n, seed=seed) + + def _process_parameters(self, m, n): + m = np.asarray(m) + n = np.asarray(n) + if m.size == 0: + m = m.astype(int) + if n.size == 0: + n = n.astype(int) + if not np.issubdtype(m.dtype, np.integer): + raise TypeError("'m' must an array of integers.") + if not np.issubdtype(n.dtype, np.integer): + raise TypeError("'n' must an array of integers.") + if m.ndim == 0: + raise ValueError("'m' must be an array with" + " at least one dimension.") + + # check for empty arrays + if m.size != 0: + n = n[..., np.newaxis] + + m, n = np.broadcast_arrays(m, n) + + # check for empty arrays + if m.size != 0: + n = n[..., 0] + + mcond = m < 0 + + M = m.sum(axis=-1) + + ncond = (n < 0) | (n > M) + return M, m, n, mcond, ncond, np.any(mcond, axis=-1) | ncond + + def _process_quantiles(self, x, M, m, n): + x = np.asarray(x) + if not np.issubdtype(x.dtype, np.integer): + raise TypeError("'x' must an array of integers.") + if x.ndim == 0: + raise ValueError("'x' must be an array with" + " at least one dimension.") + if not x.shape[-1] == m.shape[-1]: + raise ValueError(f"Size of each quantile must be size of 'm': " + f"received {x.shape[-1]}, " + f"but expected {m.shape[-1]}.") + + # check for empty arrays + if m.size != 0: + n = n[..., np.newaxis] + M = M[..., np.newaxis] + + x, m, n, M = np.broadcast_arrays(x, m, n, M) + + # check for empty arrays + if m.size != 0: + n, M = n[..., 0], M[..., 0] + + xcond = (x < 0) | (x > m) + return (x, M, m, n, xcond, + np.any(xcond, axis=-1) | (x.sum(axis=-1) != n)) + + def _checkresult(self, result, cond, bad_value): + result = np.asarray(result) + if cond.ndim != 0: + result[cond] = bad_value + elif cond: + return bad_value + if result.ndim == 0: + return result[()] + return result + + def _logpmf(self, x, M, m, n, mxcond, ncond): + # This equation of the pmf comes from the relation, + # n combine r = beta(n+1, 1) / beta(r+1, n-r+1) + num = np.zeros_like(m, dtype=np.float64) + den = np.zeros_like(n, dtype=np.float64) + m, x = m[~mxcond], x[~mxcond] + M, n = M[~ncond], n[~ncond] + num[~mxcond] = (betaln(m+1, 1) - betaln(x+1, m-x+1)) + den[~ncond] = (betaln(M+1, 1) - betaln(n+1, M-n+1)) + num[mxcond] = np.nan + den[ncond] = np.nan + num = num.sum(axis=-1) + return num - den + + def logpmf(self, x, m, n): + """Log of the multivariate hypergeometric probability mass function. + + Parameters + ---------- + x : array_like + Quantiles, with the last axis of `x` denoting the components. + %(_doc_default_callparams)s + + Returns + ------- + logpmf : ndarray or scalar + Log of the probability mass function evaluated at `x` + + Notes + ----- + %(_doc_callparams_note)s + """ + M, m, n, mcond, ncond, mncond = self._process_parameters(m, n) + (x, M, m, n, xcond, + xcond_reduced) = self._process_quantiles(x, M, m, n) + mxcond = mcond | xcond + ncond = ncond | np.zeros(n.shape, dtype=np.bool_) + + result = self._logpmf(x, M, m, n, mxcond, ncond) + + # replace values for which x was out of the domain; broadcast + # xcond to the right shape + xcond_ = xcond_reduced | np.zeros(mncond.shape, dtype=np.bool_) + result = self._checkresult(result, xcond_, -np.inf) + + # replace values bad for n or m; broadcast + # mncond to the right shape + mncond_ = mncond | np.zeros(xcond_reduced.shape, dtype=np.bool_) + return self._checkresult(result, mncond_, np.nan) + + def pmf(self, x, m, n): + """Multivariate hypergeometric probability mass function. + + Parameters + ---------- + x : array_like + Quantiles, with the last axis of `x` denoting the components. + %(_doc_default_callparams)s + + Returns + ------- + pmf : ndarray or scalar + Probability density function evaluated at `x` + + Notes + ----- + %(_doc_callparams_note)s + """ + out = np.exp(self.logpmf(x, m, n)) + return out + + def mean(self, m, n): + """Mean of the multivariate hypergeometric distribution. + + Parameters + ---------- + %(_doc_default_callparams)s + + Returns + ------- + mean : array_like or scalar + The mean of the distribution + """ + M, m, n, _, _, mncond = self._process_parameters(m, n) + # check for empty arrays + if m.size != 0: + M, n = M[..., np.newaxis], n[..., np.newaxis] + cond = (M == 0) + M = np.ma.masked_array(M, mask=cond) + mu = n*(m/M) + if m.size != 0: + mncond = (mncond[..., np.newaxis] | + np.zeros(mu.shape, dtype=np.bool_)) + return self._checkresult(mu, mncond, np.nan) + + def var(self, m, n): + """Variance of the multivariate hypergeometric distribution. + + Parameters + ---------- + %(_doc_default_callparams)s + + Returns + ------- + array_like + The variances of the components of the distribution. This is + the diagonal of the covariance matrix of the distribution + """ + M, m, n, _, _, mncond = self._process_parameters(m, n) + # check for empty arrays + if m.size != 0: + M, n = M[..., np.newaxis], n[..., np.newaxis] + cond = (M == 0) & (M-1 == 0) + M = np.ma.masked_array(M, mask=cond) + output = n * m/M * (M-m)/M * (M-n)/(M-1) + if m.size != 0: + mncond = (mncond[..., np.newaxis] | + np.zeros(output.shape, dtype=np.bool_)) + return self._checkresult(output, mncond, np.nan) + + def cov(self, m, n): + """Covariance matrix of the multivariate hypergeometric distribution. + + Parameters + ---------- + %(_doc_default_callparams)s + + Returns + ------- + cov : array_like + The covariance matrix of the distribution + """ + # see [1]_ for the formula and [2]_ for implementation + # cov( x_i,x_j ) = -n * (M-n)/(M-1) * (K_i*K_j) / (M**2) + M, m, n, _, _, mncond = self._process_parameters(m, n) + # check for empty arrays + if m.size != 0: + M = M[..., np.newaxis, np.newaxis] + n = n[..., np.newaxis, np.newaxis] + cond = (M == 0) & (M-1 == 0) + M = np.ma.masked_array(M, mask=cond) + output = (-n * (M-n)/(M-1) * + np.einsum("...i,...j->...ij", m, m) / (M**2)) + # check for empty arrays + if m.size != 0: + M, n = M[..., 0, 0], n[..., 0, 0] + cond = cond[..., 0, 0] + dim = m.shape[-1] + # diagonal entries need to be computed differently + for i in range(dim): + output[..., i, i] = (n * (M-n) * m[..., i]*(M-m[..., i])) + output[..., i, i] = output[..., i, i] / (M-1) + output[..., i, i] = output[..., i, i] / (M**2) + if m.size != 0: + mncond = (mncond[..., np.newaxis, np.newaxis] | + np.zeros(output.shape, dtype=np.bool_)) + return self._checkresult(output, mncond, np.nan) + + def rvs(self, m, n, size=None, random_state=None): + """Draw random samples from a multivariate hypergeometric distribution. + + Parameters + ---------- + %(_doc_default_callparams)s + size : int or iterable of integers, optional + Number of samples to draw. Default is ``None``, in which case a + single variate is returned as an array with shape ``m.shape``. + %(_doc_random_state)s + + Returns + ------- + rvs : array_like + Random variates of shape ``size`` or ``m.shape`` + (if ``size=None``). + + Notes + ----- + %(_doc_callparams_note)s + + Also note that NumPy's `multivariate_hypergeometric` sampler is not + used as it doesn't support broadcasting. + """ + M, m, n, _, _, _ = self._process_parameters(m, n) + + random_state = self._get_random_state(random_state) + + if size is not None and isinstance(size, int): + size = (size, ) + + if size is None: + rvs = np.empty(m.shape, dtype=m.dtype) + else: + rvs = np.empty(size + (m.shape[-1], ), dtype=m.dtype) + rem = M + + # This sampler has been taken from numpy gh-13794 + # https://github.com/numpy/numpy/pull/13794 + for c in range(m.shape[-1] - 1): + rem = rem - m[..., c] + n0mask = n == 0 + rvs[..., c] = (~n0mask * + random_state.hypergeometric(m[..., c], + rem + n0mask, + n + n0mask, + size=size)) + n = n - rvs[..., c] + rvs[..., m.shape[-1] - 1] = n + + return rvs + + +multivariate_hypergeom = multivariate_hypergeom_gen() + + +class multivariate_hypergeom_frozen(multi_rv_frozen): + def __init__(self, m, n, seed=None): + self._dist = multivariate_hypergeom_gen(seed) + (self.M, self.m, self.n, + self.mcond, self.ncond, + self.mncond) = self._dist._process_parameters(m, n) + + # monkey patch self._dist + def _process_parameters(m, n): + return (self.M, self.m, self.n, + self.mcond, self.ncond, + self.mncond) + self._dist._process_parameters = _process_parameters + + def logpmf(self, x): + return self._dist.logpmf(x, self.m, self.n) + + def pmf(self, x): + return self._dist.pmf(x, self.m, self.n) + + def mean(self): + return self._dist.mean(self.m, self.n) + + def var(self): + return self._dist.var(self.m, self.n) + + def cov(self): + return self._dist.cov(self.m, self.n) + + def rvs(self, size=1, random_state=None): + return self._dist.rvs(self.m, self.n, + size=size, + random_state=random_state) + + +# Set frozen generator docstrings from corresponding docstrings in +# multivariate_hypergeom and fill in default strings in class docstrings +for name in ['logpmf', 'pmf', 'mean', 'var', 'cov', 'rvs']: + method = multivariate_hypergeom_gen.__dict__[name] + method_frozen = multivariate_hypergeom_frozen.__dict__[name] + method_frozen.__doc__ = doccer.docformat( + method.__doc__, mhg_docdict_noparams) + method.__doc__ = doccer.docformat(method.__doc__, + mhg_docdict_params) + + +class random_table_gen(multi_rv_generic): + r""" + Contingency tables from independent samples with fixed marginal sums. + + This is the distribution of random tables with given row and column vector + sums. This distribution represents the set of random tables under the null + hypothesis that rows and columns are independent. It is used in hypothesis + tests of independence. + + Because of assumed independence, the expected frequency of each table + element can be computed from the row and column sums, so that the + distribution is completely determined by these two vectors. + + Parameters + ---------- + %(_doc_row_col)s + %(_doc_random_state)s + + Methods + ------- + logpmf(x) + Log-probability of table `x` to occur in the distribution. + pmf(x) + Probability of table `x` to occur in the distribution. + mean(row, col) + Mean table. + rvs(row, col, size=None, method=None, random_state=None) + Draw random tables with given row and column vector sums. + + Notes + ----- + %(_doc_row_col_note)s + + Random elements from the distribution are generated either with Boyett's + [1]_ or Patefield's algorithm [2]_. Boyett's algorithm has + O(N) time and space complexity, where N is the total sum of entries in the + table. Patefield's algorithm has O(K x log(N)) time complexity, where K is + the number of cells in the table and requires only a small constant work + space. By default, the `rvs` method selects the fastest algorithm based on + the input, but you can specify the algorithm with the keyword `method`. + Allowed values are "boyett" and "patefield". + + .. versionadded:: 1.10.0 + + References + ---------- + .. [1] J. Boyett, AS 144 Appl. Statist. 28 (1979) 329-332 + .. [2] W.M. Patefield, AS 159 Appl. Statist. 30 (1981) 91-97 + + Examples + -------- + >>> from scipy.stats import random_table + + >>> row = [1, 5] + >>> col = [2, 3, 1] + >>> random_table.mean(row, col) + array([[0.33333333, 0.5 , 0.16666667], + [1.66666667, 2.5 , 0.83333333]]) + + Alternatively, the object may be called (as a function) to fix the row + and column vector sums, returning a "frozen" distribution. + + >>> dist = random_table(row, col) + >>> dist.rvs(random_state=123) + array([[1, 0, 0], + [1, 3, 1]]) + """ + + def __init__(self, seed=None): + super().__init__(seed) + + def __call__(self, row, col, *, seed=None): + """Create a frozen distribution of tables with given marginals. + + See `random_table_frozen` for more information. + """ + return random_table_frozen(row, col, seed=seed) + + def logpmf(self, x, row, col): + """Log-probability of table to occur in the distribution. + + Parameters + ---------- + %(_doc_x)s + %(_doc_row_col)s + + Returns + ------- + logpmf : ndarray or scalar + Log of the probability mass function evaluated at `x`. + + Notes + ----- + %(_doc_row_col_note)s + + If row and column marginals of `x` do not match `row` and `col`, + negative infinity is returned. + + Examples + -------- + >>> from scipy.stats import random_table + >>> import numpy as np + + >>> x = [[1, 5, 1], [2, 3, 1]] + >>> row = np.sum(x, axis=1) + >>> col = np.sum(x, axis=0) + >>> random_table.logpmf(x, row, col) + -1.6306401200847027 + + Alternatively, the object may be called (as a function) to fix the row + and column vector sums, returning a "frozen" distribution. + + >>> d = random_table(row, col) + >>> d.logpmf(x) + -1.6306401200847027 + """ + r, c, n = self._process_parameters(row, col) + x = np.asarray(x) + + if x.ndim < 2: + raise ValueError("`x` must be at least two-dimensional") + + dtype_is_int = np.issubdtype(x.dtype, np.integer) + with np.errstate(invalid='ignore'): + if not dtype_is_int and not np.all(x.astype(int) == x): + raise ValueError("`x` must contain only integral values") + + # x does not contain NaN if we arrive here + if np.any(x < 0): + raise ValueError("`x` must contain only non-negative values") + + r2 = np.sum(x, axis=-1) + c2 = np.sum(x, axis=-2) + + if r2.shape[-1] != len(r): + raise ValueError("shape of `x` must agree with `row`") + + if c2.shape[-1] != len(c): + raise ValueError("shape of `x` must agree with `col`") + + res = np.empty(x.shape[:-2]) + + mask = np.all(r2 == r, axis=-1) & np.all(c2 == c, axis=-1) + + def lnfac(x): + return gammaln(x + 1) + + res[mask] = (np.sum(lnfac(r), axis=-1) + np.sum(lnfac(c), axis=-1) + - lnfac(n) - np.sum(lnfac(x[mask]), axis=(-1, -2))) + res[~mask] = -np.inf + + return res[()] + + def pmf(self, x, row, col): + """Probability of table to occur in the distribution. + + Parameters + ---------- + %(_doc_x)s + %(_doc_row_col)s + + Returns + ------- + pmf : ndarray or scalar + Probability mass function evaluated at `x`. + + Notes + ----- + %(_doc_row_col_note)s + + If row and column marginals of `x` do not match `row` and `col`, + zero is returned. + + Examples + -------- + >>> from scipy.stats import random_table + >>> import numpy as np + + >>> x = [[1, 5, 1], [2, 3, 1]] + >>> row = np.sum(x, axis=1) + >>> col = np.sum(x, axis=0) + >>> random_table.pmf(x, row, col) + 0.19580419580419592 + + Alternatively, the object may be called (as a function) to fix the row + and column vector sums, returning a "frozen" distribution. + + >>> d = random_table(row, col) + >>> d.pmf(x) + 0.19580419580419592 + """ + return np.exp(self.logpmf(x, row, col)) + + def mean(self, row, col): + """Mean of distribution of conditional tables. + %(_doc_mean_params)s + + Returns + ------- + mean: ndarray + Mean of the distribution. + + Notes + ----- + %(_doc_row_col_note)s + + Examples + -------- + >>> from scipy.stats import random_table + + >>> row = [1, 5] + >>> col = [2, 3, 1] + >>> random_table.mean(row, col) + array([[0.33333333, 0.5 , 0.16666667], + [1.66666667, 2.5 , 0.83333333]]) + + Alternatively, the object may be called (as a function) to fix the row + and column vector sums, returning a "frozen" distribution. + + >>> d = random_table(row, col) + >>> d.mean() + array([[0.33333333, 0.5 , 0.16666667], + [1.66666667, 2.5 , 0.83333333]]) + """ + r, c, n = self._process_parameters(row, col) + return np.outer(r, c) / n + + def rvs(self, row, col, *, size=None, method=None, random_state=None): + """Draw random tables with fixed column and row marginals. + + Parameters + ---------- + %(_doc_row_col)s + size : int, optional + Number of samples to draw (default 1). + method : str, optional + Which method to use, "boyett" or "patefield". If None (default), + selects the fastest method for this input. + %(_doc_random_state)s + + Returns + ------- + rvs : ndarray + Random 2D tables of shape (`size`, `len(row)`, `len(col)`). + + Notes + ----- + %(_doc_row_col_note)s + + Examples + -------- + >>> from scipy.stats import random_table + + >>> row = [1, 5] + >>> col = [2, 3, 1] + >>> random_table.rvs(row, col, random_state=123) + array([[1., 0., 0.], + [1., 3., 1.]]) + + Alternatively, the object may be called (as a function) to fix the row + and column vector sums, returning a "frozen" distribution. + + >>> d = random_table(row, col) + >>> d.rvs(random_state=123) + array([[1., 0., 0.], + [1., 3., 1.]]) + """ + r, c, n = self._process_parameters(row, col) + size, shape = self._process_size_shape(size, r, c) + + random_state = self._get_random_state(random_state) + meth = self._process_rvs_method(method, r, c, n) + + return meth(r, c, n, size, random_state).reshape(shape) + + @staticmethod + def _process_parameters(row, col): + """ + Check that row and column vectors are one-dimensional, that they do + not contain negative or non-integer entries, and that the sums over + both vectors are equal. + """ + r = np.array(row, dtype=np.int64, copy=True) + c = np.array(col, dtype=np.int64, copy=True) + + if np.ndim(r) != 1: + raise ValueError("`row` must be one-dimensional") + if np.ndim(c) != 1: + raise ValueError("`col` must be one-dimensional") + + if np.any(r < 0): + raise ValueError("each element of `row` must be non-negative") + if np.any(c < 0): + raise ValueError("each element of `col` must be non-negative") + + n = np.sum(r) + if n != np.sum(c): + raise ValueError("sums over `row` and `col` must be equal") + + if not np.all(r == np.asarray(row)): + raise ValueError("each element of `row` must be an integer") + if not np.all(c == np.asarray(col)): + raise ValueError("each element of `col` must be an integer") + + return r, c, n + + @staticmethod + def _process_size_shape(size, r, c): + """ + Compute the number of samples to be drawn and the shape of the output + """ + shape = (len(r), len(c)) + + if size is None: + return 1, shape + + size = np.atleast_1d(size) + if not np.issubdtype(size.dtype, np.integer) or np.any(size < 0): + raise ValueError("`size` must be a non-negative integer or `None`") + + return np.prod(size), tuple(size) + shape + + @classmethod + def _process_rvs_method(cls, method, r, c, n): + known_methods = { + None: cls._rvs_select(r, c, n), + "boyett": cls._rvs_boyett, + "patefield": cls._rvs_patefield, + } + try: + return known_methods[method] + except KeyError: + raise ValueError(f"'{method}' not recognized, " + f"must be one of {set(known_methods)}") + + @classmethod + def _rvs_select(cls, r, c, n): + fac = 1.0 # benchmarks show that this value is about 1 + k = len(r) * len(c) # number of cells + # n + 1 guards against failure if n == 0 + if n > fac * np.log(n + 1) * k: + return cls._rvs_patefield + return cls._rvs_boyett + + @staticmethod + def _rvs_boyett(row, col, ntot, size, random_state): + return _rcont.rvs_rcont1(row, col, ntot, size, random_state) + + @staticmethod + def _rvs_patefield(row, col, ntot, size, random_state): + return _rcont.rvs_rcont2(row, col, ntot, size, random_state) + + +random_table = random_table_gen() + + +class random_table_frozen(multi_rv_frozen): + __class_getitem__ = None # pyrefly:ignore[bad-assignment] + + def __init__(self, row, col, *, seed=None): + self._dist = random_table_gen(seed) + self._params = self._dist._process_parameters(row, col) + + # monkey patch self._dist + def _process_parameters(r, c): + return self._params + self._dist._process_parameters = _process_parameters + + def logpmf(self, x): + return self._dist.logpmf(x, None, None) + + def pmf(self, x): + return self._dist.pmf(x, None, None) + + def mean(self): + return self._dist.mean(None, None) + + def rvs(self, size=None, method=None, random_state=None): + # optimisations are possible here + return self._dist.rvs(None, None, size=size, method=method, + random_state=random_state) + + +_ctab_doc_row_col = """\ +row : array_like + Sum of table entries in each row. +col : array_like + Sum of table entries in each column.""" + +_ctab_doc_x = """\ +x : array-like + Two-dimensional table of non-negative integers, or a + multi-dimensional array with the last two dimensions + corresponding with the tables.""" + +_ctab_doc_row_col_note = """\ +The row and column vectors must be one-dimensional, not empty, +and each sum up to the same value. They cannot contain negative +or noninteger entries.""" + +_ctab_doc_mean_params = f""" +Parameters +---------- +{_ctab_doc_row_col}""" + +_ctab_doc_row_col_note_frozen = """\ +See class definition for a detailed description of parameters.""" + +_ctab_docdict = { + "_doc_random_state": _doc_random_state, + "_doc_row_col": _ctab_doc_row_col, + "_doc_x": _ctab_doc_x, + "_doc_mean_params": _ctab_doc_mean_params, + "_doc_row_col_note": _ctab_doc_row_col_note, +} + +_ctab_docdict_frozen = _ctab_docdict.copy() +_ctab_docdict_frozen.update({ + "_doc_row_col": "", + "_doc_mean_params": "", + "_doc_row_col_note": _ctab_doc_row_col_note_frozen, +}) + + +def _docfill(obj, docdict, template=None): + obj.__doc__ = doccer.docformat(template or obj.__doc__, docdict) + + +# Set frozen generator docstrings from corresponding docstrings in +# random_table and fill in default strings in class docstrings +_docfill(random_table_gen, _ctab_docdict) +for name in ['logpmf', 'pmf', 'mean', 'rvs']: + method = random_table_gen.__dict__[name] + method_frozen = random_table_frozen.__dict__[name] + _docfill(method_frozen, _ctab_docdict_frozen, method.__doc__) + _docfill(method, _ctab_docdict) + + +class uniform_direction_gen(multi_rv_generic): + r""" + A vector-valued uniform direction. + + Return a random direction (unit vector). The `dim` keyword specifies + the dimensionality of the space. + + Parameters + ---------- + dim : scalar + Dimension of directions. + seed : {None, int, `numpy.random.Generator`, + `numpy.random.RandomState`}, optional + + Used for drawing random variates. + If `seed` is `None`, the `~np.random.RandomState` singleton is used. + If `seed` is an int, a new ``RandomState`` instance is used, seeded + with seed. + If `seed` is already a ``RandomState`` or ``Generator`` instance, + then that object is used. + Default is `None`. + + Methods + ------- + rvs(dim=None, size=1, random_state=None) + Draw random directions. + + Notes + ----- + This distribution generates unit vectors uniformly distributed on + the surface of a hypersphere. These can be interpreted as random + directions. + For example, if `dim` is 3, 3D vectors from the surface of :math:`S^2` + will be sampled. + + References + ---------- + .. [1] Marsaglia, G. (1972). "Choosing a Point from the Surface of a + Sphere". Annals of Mathematical Statistics. 43 (2): 645-646. + + Examples + -------- + >>> import numpy as np + >>> from scipy.stats import uniform_direction + >>> x = uniform_direction.rvs(3) + >>> np.linalg.norm(x) + 1. + + This generates one random direction, a vector on the surface of + :math:`S^2`. + + Alternatively, the object may be called (as a function) to return a frozen + distribution with fixed `dim` parameter. Here, + we create a `uniform_direction` with ``dim=3`` and draw 5 observations. + The samples are then arranged in an array of shape 5x3. + + >>> rng = np.random.default_rng() + >>> uniform_sphere_dist = uniform_direction(3) + >>> unit_vectors = uniform_sphere_dist.rvs(5, random_state=rng) + >>> unit_vectors + array([[ 0.56688642, -0.1332634 , -0.81294566], + [-0.427126 , -0.74779278, 0.50830044], + [ 0.3793989 , 0.92346629, 0.05715323], + [ 0.36428383, -0.92449076, -0.11231259], + [-0.27733285, 0.94410968, -0.17816678]]) + """ + + def __init__(self, seed=None): + super().__init__(seed) + self.__doc__ = doccer.docformat(self.__doc__) + + def __call__(self, dim=None, seed=None): + """Create a frozen n-dimensional uniform direction distribution. + + See `uniform_direction` for more information. + """ + return uniform_direction_frozen(dim, seed=seed) + + def _process_parameters(self, dim): + """Dimension N must be specified; it cannot be inferred.""" + if dim is None or not np.isscalar(dim) or dim < 1 or dim != int(dim): + raise ValueError("Dimension of vector must be specified, " + "and must be an integer greater than 0.") + + return int(dim) + + def rvs(self, dim, size=None, random_state=None): + """Draw random samples from S(N-1). + + Parameters + ---------- + dim : int + Dimension of space (N). + size : int or tuple of ints, optional + Given a shape of, for example, (m,n,k), m*n*k samples are + generated, and packed in an m-by-n-by-k arrangement. + Because each sample is N-dimensional, the output shape + is (m,n,k,N). If no shape is specified, a single (N-D) + sample is returned. + random_state : {None, int, `numpy.random.Generator`, + `numpy.random.RandomState`}, optional + + Pseudorandom number generator state used to generate resamples. + + If `random_state` is ``None`` (or `np.random`), the + `numpy.random.RandomState` singleton is used. + If `random_state` is an int, a new ``RandomState`` instance is + used, seeded with `random_state`. + If `random_state` is already a ``Generator`` or ``RandomState`` + instance then that instance is used. + + Returns + ------- + rvs : ndarray + Random direction vectors + + """ + random_state = self._get_random_state(random_state) + if size is None: + size = np.array([], dtype=int) + size = np.atleast_1d(size) + + dim = self._process_parameters(dim) + + samples = _sample_uniform_direction(dim, size, random_state) + return samples + + +uniform_direction = uniform_direction_gen() + + +class uniform_direction_frozen(multi_rv_frozen): + def __init__(self, dim=None, seed=None): + """Create a frozen n-dimensional uniform direction distribution. + + Parameters + ---------- + dim : int + Dimension of matrices + seed : {None, int, `numpy.random.Generator`, + `numpy.random.RandomState`}, optional + + If `seed` is None (or `np.random`), the `numpy.random.RandomState` + singleton is used. + If `seed` is an int, a new ``RandomState`` instance is used, + seeded with `seed`. + If `seed` is already a ``Generator`` or ``RandomState`` instance + then that instance is used. + + Examples + -------- + >>> from scipy.stats import uniform_direction + >>> x = uniform_direction(3) + >>> x.rvs() + + """ + self._dist = uniform_direction_gen(seed) + self.dim = self._dist._process_parameters(dim) + + def rvs(self, size=None, random_state=None): + return self._dist.rvs(self.dim, size, random_state) + + +def _sample_uniform_direction(dim, size, random_state): + """ + Private method to generate uniform directions + Reference: Marsaglia, G. (1972). "Choosing a Point from the Surface of a + Sphere". Annals of Mathematical Statistics. 43 (2): 645-646. + """ + samples_shape = np.append(size, dim) + samples = random_state.standard_normal(samples_shape) + samples /= np.linalg.norm(samples, axis=-1, keepdims=True) + return samples + + +_dirichlet_mn_doc_default_callparams = """\ +alpha : array_like + The concentration parameters. The number of entries along the last axis + determines the dimensionality of the distribution. Each entry must be + strictly positive. +n : int or array_like + The number of trials. Each element must be a non-negative integer. +""" + +_dirichlet_mn_doc_frozen_callparams = "" + +_dirichlet_mn_doc_frozen_callparams_note = """\ +See class definition for a detailed description of parameters.""" + +dirichlet_mn_docdict_params = { + '_dirichlet_mn_doc_default_callparams': _dirichlet_mn_doc_default_callparams, + '_doc_random_state': _doc_random_state +} + +dirichlet_mn_docdict_noparams = { + '_dirichlet_mn_doc_default_callparams': _dirichlet_mn_doc_frozen_callparams, + '_doc_random_state': _doc_random_state +} + + +def _dirichlet_multinomial_check_parameters(alpha, n, x=None): + + alpha = np.asarray(alpha) + n = np.asarray(n) + + if x is not None: + # Ensure that `x` and `alpha` are arrays. If the shapes are + # incompatible, NumPy will raise an appropriate error. + try: + x, alpha = np.broadcast_arrays(x, alpha) + except ValueError as e: + msg = "`x` and `alpha` must be broadcastable." + raise ValueError(msg) from e + + x_int = np.floor(x) + if np.any(x < 0) or np.any(x != x_int): + raise ValueError("`x` must contain only non-negative integers.") + x = x_int + + if np.any(alpha <= 0): + raise ValueError("`alpha` must contain only positive values.") + + n_int = np.floor(n) + if np.any(n < 0) or np.any(n != n_int): + raise ValueError("`n` must be a non-negative integer.") + n = n_int + + sum_alpha = np.sum(alpha, axis=-1) + sum_alpha, n = np.broadcast_arrays(sum_alpha, n) + + return (alpha, sum_alpha, n) if x is None else (alpha, sum_alpha, n, x) + + +class dirichlet_multinomial_gen(multi_rv_generic): + r""" + A Dirichlet multinomial random variable. + + The Dirichlet multinomial distribution is a compound probability + distribution: it is the multinomial distribution with number of trials + `n` and class probabilities ``p`` randomly sampled from a Dirichlet + distribution with concentration parameters ``alpha``. + + Parameters + ---------- + %(_dirichlet_mn_doc_default_callparams)s + %(_doc_random_state)s + + Methods + ------- + logpmf(x, alpha, n): + Log of the probability mass function. + pmf(x, alpha, n): + Probability mass function. + mean(alpha, n): + Mean of the Dirichlet multinomial distribution. + var(alpha, n): + Variance of the Dirichlet multinomial distribution. + cov(alpha, n): + The covariance of the Dirichlet multinomial distribution. + + See Also + -------- + scipy.stats.dirichlet : The dirichlet distribution. + scipy.stats.multinomial : The multinomial distribution. + + References + ---------- + .. [1] Dirichlet-multinomial distribution, Wikipedia, + https://www.wikipedia.org/wiki/Dirichlet-multinomial_distribution + + Examples + -------- + >>> from scipy.stats import dirichlet_multinomial + + Get the PMF + + >>> n = 6 # number of trials + >>> alpha = [3, 4, 5] # concentration parameters + >>> x = [1, 2, 3] # counts + >>> dirichlet_multinomial.pmf(x, alpha, n) + 0.08484162895927604 + + If the sum of category counts does not equal the number of trials, + the probability mass is zero. + + >>> dirichlet_multinomial.pmf(x, alpha, n=7) + 0.0 + + Get the log of the PMF + + >>> dirichlet_multinomial.logpmf(x, alpha, n) + -2.4669689491013327 + + Get the mean + + >>> dirichlet_multinomial.mean(alpha, n) + array([1.5, 2. , 2.5]) + + Get the variance + + >>> dirichlet_multinomial.var(alpha, n) + array([1.55769231, 1.84615385, 2.01923077]) + + Get the covariance + + >>> dirichlet_multinomial.cov(alpha, n) + array([[ 1.55769231, -0.69230769, -0.86538462], + [-0.69230769, 1.84615385, -1.15384615], + [-0.86538462, -1.15384615, 2.01923077]]) + + Alternatively, the object may be called (as a function) to fix the + `alpha` and `n` parameters, returning a "frozen" Dirichlet multinomial + random variable. + + >>> dm = dirichlet_multinomial(alpha, n) + >>> dm.pmf(x) + 0.08484162895927579 + + All methods are fully vectorized. Each element of `x` and `alpha` is + a vector (along the last axis), each element of `n` is an + integer (scalar), and the result is computed element-wise. + + >>> x = [[1, 2, 3], [4, 5, 6]] + >>> alpha = [[1, 2, 3], [4, 5, 6]] + >>> n = [6, 15] + >>> dirichlet_multinomial.pmf(x, alpha, n) + array([0.06493506, 0.02626937]) + + >>> dirichlet_multinomial.cov(alpha, n).shape # both covariance matrices + (2, 3, 3) + + Broadcasting according to standard NumPy conventions is supported. Here, + we have four sets of concentration parameters (each a two element vector) + for each of three numbers of trials (each a scalar). + + >>> alpha = [[3, 4], [4, 5], [5, 6], [6, 7]] + >>> n = [[6], [7], [8]] + >>> dirichlet_multinomial.mean(alpha, n).shape + (3, 4, 2) + """ + def __init__(self, seed=None): + super().__init__(seed) + self.__doc__ = doccer.docformat(self.__doc__, + dirichlet_mn_docdict_params) + + def __call__(self, alpha, n, seed=None): + return dirichlet_multinomial_frozen(alpha, n, seed=seed) + + def logpmf(self, x, alpha, n): + """The log of the probability mass function. + + Parameters + ---------- + x: ndarray + Category counts (non-negative integers). Must be broadcastable + with shape parameter ``alpha``. If multidimensional, the last axis + must correspond with the categories. + %(_dirichlet_mn_doc_default_callparams)s + + Returns + ------- + out: ndarray or scalar + Log of the probability mass function. + + """ + + a, Sa, n, x = _dirichlet_multinomial_check_parameters(alpha, n, x) + + out = np.asarray(loggamma(Sa) + loggamma(n + 1) - loggamma(n + Sa)) + out += (loggamma(x + a) - (loggamma(a) + loggamma(x + 1))).sum(axis=-1) + np.place(out, n != x.sum(axis=-1), -np.inf) + return out[()] + + def pmf(self, x, alpha, n): + """Probability mass function for a Dirichlet multinomial distribution. + + Parameters + ---------- + x: ndarray + Category counts (non-negative integers). Must be broadcastable + with shape parameter ``alpha``. If multidimensional, the last axis + must correspond with the categories. + %(_dirichlet_mn_doc_default_callparams)s + + Returns + ------- + out: ndarray or scalar + Probability mass function. + + """ + return np.exp(self.logpmf(x, alpha, n)) + + def mean(self, alpha, n): + """Mean of a Dirichlet multinomial distribution. + + Parameters + ---------- + %(_dirichlet_mn_doc_default_callparams)s + + Returns + ------- + out: ndarray + Mean of a Dirichlet multinomial distribution. + + """ + a, Sa, n = _dirichlet_multinomial_check_parameters(alpha, n) + n, Sa = n[..., np.newaxis], Sa[..., np.newaxis] + return n * a / Sa + + def var(self, alpha, n): + """The variance of the Dirichlet multinomial distribution. + + Parameters + ---------- + %(_dirichlet_mn_doc_default_callparams)s + + Returns + ------- + out: array_like + The variances of the components of the distribution. This is + the diagonal of the covariance matrix of the distribution. + + """ + a, Sa, n = _dirichlet_multinomial_check_parameters(alpha, n) + n, Sa = n[..., np.newaxis], Sa[..., np.newaxis] + return n * a / Sa * (1 - a/Sa) * (n + Sa) / (1 + Sa) + + def cov(self, alpha, n): + """Covariance matrix of a Dirichlet multinomial distribution. + + Parameters + ---------- + %(_dirichlet_mn_doc_default_callparams)s + + Returns + ------- + out : array_like + The covariance matrix of the distribution. + + """ + a, Sa, n = _dirichlet_multinomial_check_parameters(alpha, n) + var = dirichlet_multinomial.var(a, n) + + n, Sa = n[..., np.newaxis, np.newaxis], Sa[..., np.newaxis, np.newaxis] + aiaj = a[..., :, np.newaxis] * a[..., np.newaxis, :] + cov = -n * aiaj / Sa ** 2 * (n + Sa) / (1 + Sa) + + ii = np.arange(cov.shape[-1]) + cov[..., ii, ii] = var + return cov + + +dirichlet_multinomial = dirichlet_multinomial_gen() + + +class dirichlet_multinomial_frozen(multi_rv_frozen): + def __init__(self, alpha, n, seed=None): + alpha, Sa, n = _dirichlet_multinomial_check_parameters(alpha, n) + self.alpha = alpha + self.n = n + self._dist = dirichlet_multinomial_gen(seed) + + def logpmf(self, x): + return self._dist.logpmf(x, self.alpha, self.n) + + def pmf(self, x): + return self._dist.pmf(x, self.alpha, self.n) + + def mean(self): + return self._dist.mean(self.alpha, self.n) + + def var(self): + return self._dist.var(self.alpha, self.n) + + def cov(self): + return self._dist.cov(self.alpha, self.n) + + +# Set frozen generator docstrings from corresponding docstrings in +# dirichlet_multinomial and fill in default strings in class docstrings. +for name in ['logpmf', 'pmf', 'mean', 'var', 'cov']: + method = dirichlet_multinomial_gen.__dict__[name] + method_frozen = dirichlet_multinomial_frozen.__dict__[name] + method_frozen.__doc__ = doccer.docformat( + method.__doc__, dirichlet_mn_docdict_noparams) + method.__doc__ = doccer.docformat(method.__doc__, + dirichlet_mn_docdict_params) + + +class vonmises_fisher_gen(multi_rv_generic): + r""" + A von Mises-Fisher variable. + + The `mu` keyword specifies the mean direction vector. The `kappa` keyword + specifies the concentration parameter. + + Parameters + ---------- + mu : array_like + Mean direction of the distribution. Must be a one-dimensional unit + vector of norm 1. + kappa : float + Concentration parameter. Must be positive. + seed : {None, int, np.random.RandomState, np.random.Generator}, optional + Used for drawing random variates. + If `seed` is `None`, the `~np.random.RandomState` singleton is used. + If `seed` is an int, a new ``RandomState`` instance is used, seeded + with seed. + If `seed` is already a ``RandomState`` or ``Generator`` instance, + then that object is used. + Default is `None`. + + Methods + ------- + pdf(x, mu=None, kappa=1) + Probability density function. + logpdf(x, mu=None, kappa=1) + Log of the probability density function. + rvs(mu=None, kappa=1, size=1, random_state=None) + Draw random samples from a von Mises-Fisher distribution. + entropy(mu=None, kappa=1) + Compute the differential entropy of the von Mises-Fisher distribution. + fit(data) + Fit a von Mises-Fisher distribution to data. + + See Also + -------- + scipy.stats.vonmises : Von-Mises Fisher distribution in 2D on a circle + uniform_direction : uniform distribution on the surface of a hypersphere + + Notes + ----- + The von Mises-Fisher distribution is a directional distribution on the + surface of the unit hypersphere. The probability density + function of a unit vector :math:`\mathbf{x}` is + + .. math:: + + f(\mathbf{x}) = \frac{\kappa^{d/2-1}}{(2\pi)^{d/2}I_{d/2-1}(\kappa)} + \exp\left(\kappa \mathbf{\mu}^T\mathbf{x}\right), + + where :math:`\mathbf{\mu}` is the mean direction, :math:`\kappa` the + concentration parameter, :math:`d` the dimension and :math:`I` the + modified Bessel function of the first kind. As :math:`\mu` represents + a direction, it must be a unit vector or in other words, a point + on the hypersphere: :math:`\mathbf{\mu}\in S^{d-1}`. :math:`\kappa` is a + concentration parameter, which means that it must be positive + (:math:`\kappa>0`) and that the distribution becomes more narrow with + increasing :math:`\kappa`. In that sense, the reciprocal value + :math:`1/\kappa` resembles the variance parameter of the normal + distribution. + + The von Mises-Fisher distribution often serves as an analogue of the + normal distribution on the sphere. Intuitively, for unit vectors, a + useful distance measure is given by the angle :math:`\alpha` between + them. This is exactly what the scalar product + :math:`\mathbf{\mu}^T\mathbf{x}=\cos(\alpha)` in the + von Mises-Fisher probability density function describes: the angle + between the mean direction :math:`\mathbf{\mu}` and the vector + :math:`\mathbf{x}`. The larger the angle between them, the smaller the + probability to observe :math:`\mathbf{x}` for this particular mean + direction :math:`\mathbf{\mu}`. + + In dimensions 2 and 3, specialized algorithms are used for fast sampling + [2]_, [3]_. For dimensions of 4 or higher the rejection sampling algorithm + described in [4]_ is utilized. This implementation is partially based on + the geomstats package [5]_, [6]_. + + .. versionadded:: 1.11 + + References + ---------- + .. [1] Von Mises-Fisher distribution, Wikipedia, + https://en.wikipedia.org/wiki/Von_Mises%E2%80%93Fisher_distribution + .. [2] Mardia, K., and Jupp, P. Directional statistics. Wiley, 2000. + .. [3] J. Wenzel. Numerically stable sampling of the von Mises Fisher + distribution on S2. + https://www.mitsuba-renderer.org/~wenzel/files/vmf.pdf + .. [4] Wood, A. Simulation of the von mises fisher distribution. + Communications in statistics-simulation and computation 23, + 1 (1994), 157-164. :doi:`10.1080/03610919408813161`. + .. [5] geomstats, Github. MIT License. Accessed: 06.01.2023. + https://github.com/geomstats/geomstats + .. [6] Miolane, N. et al. Geomstats: A Python Package for Riemannian + Geometry in Machine Learning. Journal of Machine Learning Research + 21 (2020). http://jmlr.org/papers/v21/19-027.html + + Examples + -------- + **Visualization of the probability density** + + Plot the probability density in three dimensions for increasing + concentration parameter. The density is calculated by the ``pdf`` + method. + + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from scipy.stats import vonmises_fisher + >>> from matplotlib.colors import Normalize + >>> n_grid = 100 + >>> u = np.linspace(0, np.pi, n_grid) + >>> v = np.linspace(0, 2 * np.pi, n_grid) + >>> u_grid, v_grid = np.meshgrid(u, v) + >>> vertices = np.stack([np.cos(v_grid) * np.sin(u_grid), + ... np.sin(v_grid) * np.sin(u_grid), + ... np.cos(u_grid)], + ... axis=2) + >>> x = np.outer(np.cos(v), np.sin(u)) + >>> y = np.outer(np.sin(v), np.sin(u)) + >>> z = np.outer(np.ones_like(u), np.cos(u)) + >>> def plot_vmf_density(ax, x, y, z, vertices, mu, kappa): + ... vmf = vonmises_fisher(mu, kappa) + ... pdf_values = vmf.pdf(vertices) + ... pdfnorm = Normalize(vmin=pdf_values.min(), vmax=pdf_values.max()) + ... ax.plot_surface(x, y, z, rstride=1, cstride=1, + ... facecolors=plt.cm.viridis(pdfnorm(pdf_values)), + ... linewidth=0) + ... ax.set_aspect('equal') + ... ax.view_init(azim=-130, elev=0) + ... ax.axis('off') + ... ax.set_title(rf"$\kappa={kappa}$") + >>> fig, axes = plt.subplots(nrows=1, ncols=3, figsize=(9, 4), + ... subplot_kw={"projection": "3d"}) + >>> left, middle, right = axes + >>> mu = np.array([-np.sqrt(0.5), -np.sqrt(0.5), 0]) + >>> plot_vmf_density(left, x, y, z, vertices, mu, 5) + >>> plot_vmf_density(middle, x, y, z, vertices, mu, 20) + >>> plot_vmf_density(right, x, y, z, vertices, mu, 100) + >>> plt.subplots_adjust(top=1, bottom=0.0, left=0.0, right=1.0, wspace=0.) + >>> plt.show() + + As we increase the concentration parameter, the points are getting more + clustered together around the mean direction. + + **Sampling** + + Draw 5 samples from the distribution using the ``rvs`` method resulting + in a 5x3 array. + + >>> rng = np.random.default_rng() + >>> mu = np.array([0, 0, 1]) + >>> samples = vonmises_fisher(mu, 20).rvs(5, random_state=rng) + >>> samples + array([[ 0.3884594 , -0.32482588, 0.86231516], + [ 0.00611366, -0.09878289, 0.99509023], + [-0.04154772, -0.01637135, 0.99900239], + [-0.14613735, 0.12553507, 0.98126695], + [-0.04429884, -0.23474054, 0.97104814]]) + + These samples are unit vectors on the sphere :math:`S^2`. To verify, + let us calculate their euclidean norms: + + >>> np.linalg.norm(samples, axis=1) + array([1., 1., 1., 1., 1.]) + + Plot 20 observations drawn from the von Mises-Fisher distribution for + increasing concentration parameter :math:`\kappa`. The red dot highlights + the mean direction :math:`\mu`. + + >>> def plot_vmf_samples(ax, x, y, z, mu, kappa): + ... vmf = vonmises_fisher(mu, kappa) + ... samples = vmf.rvs(20) + ... ax.plot_surface(x, y, z, rstride=1, cstride=1, linewidth=0, + ... alpha=0.2) + ... ax.scatter(samples[:, 0], samples[:, 1], samples[:, 2], c='k', s=5) + ... ax.scatter(mu[0], mu[1], mu[2], c='r', s=30) + ... ax.set_aspect('equal') + ... ax.view_init(azim=-130, elev=0) + ... ax.axis('off') + ... ax.set_title(rf"$\kappa={kappa}$") + >>> mu = np.array([-np.sqrt(0.5), -np.sqrt(0.5), 0]) + >>> fig, axes = plt.subplots(nrows=1, ncols=3, + ... subplot_kw={"projection": "3d"}, + ... figsize=(9, 4)) + >>> left, middle, right = axes + >>> plot_vmf_samples(left, x, y, z, mu, 5) + >>> plot_vmf_samples(middle, x, y, z, mu, 20) + >>> plot_vmf_samples(right, x, y, z, mu, 100) + >>> plt.subplots_adjust(top=1, bottom=0.0, left=0.0, + ... right=1.0, wspace=0.) + >>> plt.show() + + The plots show that with increasing concentration :math:`\kappa` the + resulting samples are centered more closely around the mean direction. + + **Fitting the distribution parameters** + + The distribution can be fitted to data using the ``fit`` method returning + the estimated parameters. As a toy example let's fit the distribution to + samples drawn from a known von Mises-Fisher distribution. + + >>> mu, kappa = np.array([0, 0, 1]), 20 + >>> samples = vonmises_fisher(mu, kappa).rvs(1000, random_state=rng) + >>> mu_fit, kappa_fit = vonmises_fisher.fit(samples) + >>> mu_fit, kappa_fit + (array([0.01126519, 0.01044501, 0.99988199]), 19.306398751730995) + + We see that the estimated parameters `mu_fit` and `kappa_fit` are + very close to the ground truth parameters. + """ + def __init__(self, seed=None): + super().__init__(seed) + + def __call__(self, mu=None, kappa=1, seed=None): + """Create a frozen von Mises-Fisher distribution. + + See `vonmises_fisher_frozen` for more information. + """ + return vonmises_fisher_frozen(mu, kappa, seed=seed) + + def _process_parameters(self, mu, kappa): + """ + Infer dimensionality from mu and ensure that mu is a one-dimensional + unit vector and kappa positive. + """ + mu = np.asarray(mu) + if mu.ndim > 1: + raise ValueError("'mu' must have one-dimensional shape.") + if not np.allclose(np.linalg.norm(mu), 1.): + raise ValueError("'mu' must be a unit vector of norm 1.") + if not mu.size > 1: + raise ValueError("'mu' must have at least two entries.") + kappa_error_msg = "'kappa' must be a positive scalar." + if not np.isscalar(kappa) or kappa < 0: + raise ValueError(kappa_error_msg) + if float(kappa) == 0.: + raise ValueError("For 'kappa=0' the von Mises-Fisher distribution " + "becomes the uniform distribution on the sphere " + "surface. Consider using " + "'scipy.stats.uniform_direction' instead.") + dim = mu.size + + return dim, mu, kappa + + def _check_data_vs_dist(self, x, dim): + if x.shape[-1] != dim: + raise ValueError("The dimensionality of the last axis of 'x' must " + "match the dimensionality of the " + "von Mises Fisher distribution.") + if not np.allclose(np.linalg.norm(x, axis=-1), 1.): + msg = "'x' must be unit vectors of norm 1 along last dimension." + raise ValueError(msg) + + def _log_norm_factor(self, dim, kappa): + # normalization factor is given by + # c = kappa**(dim/2-1)/((2*pi)**(dim/2)*I[dim/2-1](kappa)) + # = kappa**(dim/2-1)*exp(-kappa) / + # ((2*pi)**(dim/2)*I[dim/2-1](kappa)*exp(-kappa) + # = kappa**(dim/2-1)*exp(-kappa) / + # ((2*pi)**(dim/2)*ive[dim/2-1](kappa) + # Then the log is given by + # log c = 1/2*(dim -1)*log(kappa) - kappa - -1/2*dim*ln(2*pi) - + # ive[dim/2-1](kappa) + halfdim = 0.5 * dim + return (0.5 * (dim - 2)*np.log(kappa) - halfdim * _LOG_2PI - + np.log(ive(halfdim - 1, kappa)) - kappa) + + def _logpdf(self, x, dim, mu, kappa): + """Log of the von Mises-Fisher probability density function. + + As this function does no argument checking, it should not be + called directly; use 'logpdf' instead. + + """ + x = np.asarray(x) + self._check_data_vs_dist(x, dim) + dotproducts = np.einsum('i,...i->...', mu, x) + return self._log_norm_factor(dim, kappa) + kappa * dotproducts + + def logpdf(self, x, mu=None, kappa=1): + """Log of the von Mises-Fisher probability density function. + + Parameters + ---------- + x : array_like + Points at which to evaluate the log of the probability + density function. The last axis of `x` must correspond + to unit vectors of the same dimensionality as the distribution. + mu : array_like, default: None + Mean direction of the distribution. Must be a one-dimensional unit + vector of norm 1. + kappa : float, default: 1 + Concentration parameter. Must be positive. + + Returns + ------- + logpdf : ndarray or scalar + Log of the probability density function evaluated at `x`. + + """ + dim, mu, kappa = self._process_parameters(mu, kappa) + return self._logpdf(x, dim, mu, kappa) + + def pdf(self, x, mu=None, kappa=1): + """Von Mises-Fisher probability density function. + + Parameters + ---------- + x : array_like + Points at which to evaluate the probability + density function. The last axis of `x` must correspond + to unit vectors of the same dimensionality as the distribution. + mu : array_like + Mean direction of the distribution. Must be a one-dimensional unit + vector of norm 1. + kappa : float + Concentration parameter. Must be positive. + + Returns + ------- + pdf : ndarray or scalar + Probability density function evaluated at `x`. + + """ + dim, mu, kappa = self._process_parameters(mu, kappa) + return np.exp(self._logpdf(x, dim, mu, kappa)) + + def _rvs_2d(self, mu, kappa, size, random_state): + """ + In 2D, the von Mises-Fisher distribution reduces to the + von Mises distribution which can be efficiently sampled by numpy. + This method is much faster than the general rejection + sampling based algorithm. + + """ + mean_angle = np.arctan2(mu[1], mu[0]) + angle_samples = random_state.vonmises(mean_angle, kappa, size=size) + samples = np.stack([np.cos(angle_samples), np.sin(angle_samples)], + axis=-1) + return samples + + def _rvs_3d(self, kappa, size, random_state): + """ + Generate samples from a von Mises-Fisher distribution + with mu = [1, 0, 0] and kappa. Samples then have to be + rotated towards the desired mean direction mu. + This method is much faster than the general rejection + sampling based algorithm. + Reference: https://www.mitsuba-renderer.org/~wenzel/files/vmf.pdf + + """ + if size is None: + sample_size = 1 + else: + sample_size = size + + # compute x coordinate acc. to equation from section 3.1 + x = random_state.random(sample_size) + x = 1. + np.log(x + (1. - x) * np.exp(-2 * kappa))/kappa + + # (y, z) are random 2D vectors that only have to be + # normalized accordingly. Then (x, y z) follow a VMF distribution + temp = np.sqrt(1. - np.square(x)) + uniformcircle = _sample_uniform_direction(2, sample_size, random_state) + samples = np.stack([x, temp * uniformcircle[..., 0], + temp * uniformcircle[..., 1]], + axis=-1) + if size is None: + samples = np.squeeze(samples) + return samples + + def _rejection_sampling(self, dim, kappa, size, random_state): + """ + Generate samples from an n-dimensional von Mises-Fisher distribution + with mu = [1, 0, ..., 0] and kappa via rejection sampling. + Samples then have to be rotated towards the desired mean direction mu. + Reference: :doi:`10.1080/03610919408813161`. + """ + dim_minus_one = dim - 1 + # calculate number of requested samples + if size is not None: + if not np.iterable(size): + size = (size, ) + n_samples = math.prod(size) + else: + n_samples = 1 + # calculate envelope for rejection sampler (eq. 4) + sqrt = np.sqrt(4 * kappa ** 2. + dim_minus_one ** 2) + envelop_param = (-2 * kappa + sqrt) / dim_minus_one + if envelop_param == 0: + # the regular formula suffers from loss of precision for high + # kappa. This can only be detected by checking for 0 here. + # Workaround: expansion for sqrt variable + # https://www.wolframalpha.com/input?i=sqrt%284*x%5E2%2Bd%5E2%29 + # e = (-2 * k + sqrt(k**2 + d**2)) / d + # ~ (-2 * k + 2 * k + d**2/(4 * k) - d**4/(64 * k**3)) / d + # = d/(4 * k) - d**3/(64 * k**3) + envelop_param = (dim_minus_one/4 * kappa**-1. + - dim_minus_one**3/64 * kappa**-3.) + # reference step 0 + node = (1. - envelop_param) / (1. + envelop_param) + # t = ln(1 - ((1-x)/(1+x))**2) + # = ln(4 * x / (1+x)**2) + # = ln(4) + ln(x) - 2*log1p(x) + correction = (kappa * node + dim_minus_one + * (np.log(4) + np.log(envelop_param) + - 2 * np.log1p(envelop_param))) + n_accepted = 0 + x = np.zeros((n_samples, )) + halfdim = 0.5 * dim_minus_one + # main loop + while n_accepted < n_samples: + # generate candidates acc. to reference step 1 + sym_beta = random_state.beta(halfdim, halfdim, + size=n_samples - n_accepted) + coord_x = (1 - (1 + envelop_param) * sym_beta) / ( + 1 - (1 - envelop_param) * sym_beta) + # accept or reject: reference step 2 + # reformulation for numerical stability: + # t = ln(1 - (1-x)/(1+x) * y) + # = ln((1 + x - y +x*y)/(1 +x)) + accept_tol = random_state.random(n_samples - n_accepted) + criterion = ( + kappa * coord_x + + dim_minus_one * (np.log((1 + envelop_param - coord_x + + coord_x * envelop_param) / (1 + envelop_param))) + - correction) > np.log(accept_tol) + accepted_iter = np.sum(criterion) + x[n_accepted:n_accepted + accepted_iter] = coord_x[criterion] + n_accepted += accepted_iter + # concatenate x and remaining coordinates: step 3 + coord_rest = _sample_uniform_direction(dim_minus_one, n_accepted, + random_state) + coord_rest = np.einsum( + '...,...i->...i', np.sqrt(1 - x ** 2), coord_rest) + samples = np.concatenate([x[..., None], coord_rest], axis=1) + # reshape output to (size, dim) + if size is not None: + samples = samples.reshape(size + (dim, )) + else: + samples = np.squeeze(samples) + return samples + + def _rotate_samples(self, samples, mu, dim): + """A QR decomposition is used to find the rotation that maps the + north pole (1, 0,...,0) to the vector mu. This rotation is then + applied to all samples. + + Parameters + ---------- + samples: array_like, shape = [..., n] + mu : array-like, shape=[n, ] + Point to parametrise the rotation. + + Returns + ------- + samples : rotated samples + + """ + base_point = np.zeros((dim, )) + base_point[0] = 1. + embedded = np.concatenate([mu[None, :], np.zeros((dim - 1, dim))]) + rotmatrix, _ = np.linalg.qr(np.transpose(embedded)) + if np.allclose(np.matmul(rotmatrix, base_point[:, None])[:, 0], mu): + rotsign = 1 + else: + rotsign = -1 + + # apply rotation + samples = np.einsum('ij,...j->...i', rotmatrix, samples) * rotsign + return samples + + def _rvs(self, dim, mu, kappa, size, random_state): + if dim == 2: + samples = self._rvs_2d(mu, kappa, size, random_state) + elif dim == 3: + samples = self._rvs_3d(kappa, size, random_state) + else: + samples = self._rejection_sampling(dim, kappa, size, + random_state) + + if dim != 2: + samples = self._rotate_samples(samples, mu, dim) + return samples + + def rvs(self, mu=None, kappa=1, size=1, random_state=None): + """Draw random samples from a von Mises-Fisher distribution. + + Parameters + ---------- + mu : array_like + Mean direction of the distribution. Must be a one-dimensional unit + vector of norm 1. + kappa : float + Concentration parameter. Must be positive. + size : int or tuple of ints, optional + Given a shape of, for example, (m,n,k), m*n*k samples are + generated, and packed in an m-by-n-by-k arrangement. + Because each sample is N-dimensional, the output shape + is (m,n,k,N). If no shape is specified, a single (N-D) + sample is returned. + random_state : {None, int, np.random.RandomState, np.random.Generator}, + optional + Used for drawing random variates. + If `seed` is `None`, the `~np.random.RandomState` singleton is used. + If `seed` is an int, a new ``RandomState`` instance is used, seeded + with seed. + If `seed` is already a ``RandomState`` or ``Generator`` instance, + then that object is used. + Default is `None`. + + Returns + ------- + rvs : ndarray + Random variates of shape (`size`, `N`), where `N` is the + dimension of the distribution. + + """ + dim, mu, kappa = self._process_parameters(mu, kappa) + random_state = self._get_random_state(random_state) + samples = self._rvs(dim, mu, kappa, size, random_state) + return samples + + def _entropy(self, dim, kappa): + halfdim = 0.5 * dim + return (-self._log_norm_factor(dim, kappa) - kappa * + ive(halfdim, kappa) / ive(halfdim - 1, kappa)) + + def entropy(self, mu=None, kappa=1): + """Compute the differential entropy of the von Mises-Fisher + distribution. + + Parameters + ---------- + mu : array_like, default: None + Mean direction of the distribution. Must be a one-dimensional unit + vector of norm 1. + kappa : float, default: 1 + Concentration parameter. Must be positive. + + Returns + ------- + h : scalar + Entropy of the von Mises-Fisher distribution. + + """ + dim, _, kappa = self._process_parameters(mu, kappa) + return self._entropy(dim, kappa) + + def fit(self, x): + """Fit the von Mises-Fisher distribution to data. + + Parameters + ---------- + x : array-like + Data the distribution is fitted to. Must be two dimensional. + The second axis of `x` must be unit vectors of norm 1 and + determine the dimensionality of the fitted + von Mises-Fisher distribution. + + Returns + ------- + mu : ndarray + Estimated mean direction. + kappa : float + Estimated concentration parameter. + + """ + # validate input data + x = np.asarray(x) + if x.ndim != 2: + raise ValueError("'x' must be two dimensional.") + if not np.allclose(np.linalg.norm(x, axis=-1), 1.): + msg = "'x' must be unit vectors of norm 1 along last dimension." + raise ValueError(msg) + dim = x.shape[-1] + + # mu is simply the directional mean + dirstats = directional_stats(x) + mu = dirstats.mean_direction + r = dirstats.mean_resultant_length + + # kappa is the solution to the equation: + # r = I[dim/2](kappa) / I[dim/2 -1](kappa) + # = I[dim/2](kappa) * exp(-kappa) / I[dim/2 -1](kappa) * exp(-kappa) + # = ive(dim/2, kappa) / ive(dim/2 -1, kappa) + + halfdim = 0.5 * dim + + def solve_for_kappa(kappa): + bessel_vals = ive([halfdim, halfdim - 1], kappa) + return bessel_vals[0]/bessel_vals[1] - r + + root_res = root_scalar(solve_for_kappa, method="brentq", + bracket=(1e-8, 1e9)) + kappa = root_res.root + return mu, kappa + + +vonmises_fisher = vonmises_fisher_gen() + + +class vonmises_fisher_frozen(multi_rv_frozen): + def __init__(self, mu=None, kappa=1, seed=None): + """Create a frozen von Mises-Fisher distribution. + + Parameters + ---------- + mu : array_like, default: None + Mean direction of the distribution. + kappa : float, default: 1 + Concentration parameter. Must be positive. + seed : {None, int, `numpy.random.Generator`, + `numpy.random.RandomState`}, optional + If `seed` is None (or `np.random`), the `numpy.random.RandomState` + singleton is used. + If `seed` is an int, a new ``RandomState`` instance is used, + seeded with `seed`. + If `seed` is already a ``Generator`` or ``RandomState`` instance + then that instance is used. + + """ + self._dist = vonmises_fisher_gen(seed) + self.dim, self.mu, self.kappa = ( + self._dist._process_parameters(mu, kappa) + ) + + def logpdf(self, x): + """ + Parameters + ---------- + x : array_like + Points at which to evaluate the log of the probability + density function. The last axis of `x` must correspond + to unit vectors of the same dimensionality as the distribution. + + Returns + ------- + logpdf : ndarray or scalar + Log of probability density function evaluated at `x`. + + """ + return self._dist._logpdf(x, self.dim, self.mu, self.kappa) + + def pdf(self, x): + """ + Parameters + ---------- + x : array_like + Points at which to evaluate the log of the probability + density function. The last axis of `x` must correspond + to unit vectors of the same dimensionality as the distribution. + + Returns + ------- + pdf : ndarray or scalar + Probability density function evaluated at `x`. + + """ + return np.exp(self.logpdf(x)) + + def rvs(self, size=1, random_state=None): + """Draw random variates from the Von Mises-Fisher distribution. + + Parameters + ---------- + size : int or tuple of ints, optional + Given a shape of, for example, (m,n,k), m*n*k samples are + generated, and packed in an m-by-n-by-k arrangement. + Because each sample is N-dimensional, the output shape + is (m,n,k,N). If no shape is specified, a single (N-D) + sample is returned. + random_state : {None, int, `numpy.random.Generator`, + `numpy.random.RandomState`}, optional + If `seed` is None (or `np.random`), the `numpy.random.RandomState` + singleton is used. + If `seed` is an int, a new ``RandomState`` instance is used, + seeded with `seed`. + If `seed` is already a ``Generator`` or ``RandomState`` instance + then that instance is used. + + Returns + ------- + rvs : ndarray or scalar + Random variates of size (`size`, `N`), where `N` is the + dimension of the distribution. + + """ + random_state = self._dist._get_random_state(random_state) + return self._dist._rvs(self.dim, self.mu, self.kappa, size, + random_state) + + def entropy(self): + """ + Calculate the differential entropy of the von Mises-Fisher + distribution. + + Returns + ------- + h: float + Entropy of the Von Mises-Fisher distribution. + + """ + return self._dist._entropy(self.dim, self.kappa) + + +class normal_inverse_gamma_gen(multi_rv_generic): + r""" + Normal-inverse-gamma distribution. + + The normal-inverse-gamma distribution is the conjugate prior of a normal + distribution with unknown mean and variance. + + Parameters + ---------- + mu, lmbda, a, b : array_like + Shape parameters of the distribution. See notes. + seed : {None, int, np.random.RandomState, np.random.Generator}, optional + Used for drawing random variates. + If `seed` is `None`, the `~np.random.RandomState` singleton is used. + If `seed` is an int, a new ``RandomState`` instance is used, seeded + with seed. + If `seed` is already a ``RandomState`` or ``Generator`` instance, + then that object is used. + Default is `None`. + + Methods + ------- + pdf(x, s2, mu=0, lmbda=1, a=1, b=1) + Probability density function. + logpdf(x, s2, mu=0, lmbda=1, a=1, b=1) + Log of the probability density function. + mean(mu=0, lmbda=1, a=1, b=1) + Distribution mean. + var(mu=0, lmbda=1, a=1, b=1) + Distribution variance. + rvs(mu=0, lmbda=1, a=1, b=1, size=None, random_state=None) + Draw random samples. + + See Also + -------- + norm + invgamma + + Notes + ----- + + The probability density function of `normal_inverse_gamma` is: + + .. math:: + + f(x, \sigma^2; \mu, \lambda, \alpha, \beta) = + \frac{\sqrt{\lambda}}{\sqrt{2 \pi \sigma^2}} + \frac{\beta^\alpha}{\Gamma(\alpha)} + \left( \frac{1}{\sigma^2} \right)^{\alpha + 1} + \exp \left(- \frac{2 \beta + \lambda (x - \mu)^2} {2 \sigma^2} \right) + + where all parameters are real and finite, and :math:`\sigma^2 > 0`, + :math:`\lambda > 0`, :math:`\alpha > 0`, and :math:`\beta > 0`. + + Methods ``normal_inverse_gamma.pdf`` and ``normal_inverse_gamma.logpdf`` + accept `x` and `s2` for arguments :math:`x` and :math:`\sigma^2`. + All methods accept `mu`, `lmbda`, `a`, and `b` for shape parameters + :math:`\mu`, :math:`\lambda`, :math:`\alpha`, and :math:`\beta`, + respectively. + + .. versionadded:: 1.15 + + References + ---------- + .. [1] Normal-inverse-gamma distribution, Wikipedia, + https://en.wikipedia.org/wiki/Normal-inverse-gamma_distribution + + Examples + -------- + Suppose we wish to investigate the relationship between the + normal-inverse-gamma distribution and the inverse gamma distribution. + + >>> import numpy as np + >>> from scipy import stats + >>> import matplotlib.pyplot as plt + >>> rng = np.random.default_rng(527484872345) + >>> mu, lmbda, a, b = 0, 1, 20, 20 + >>> norm_inv_gamma = stats.normal_inverse_gamma(mu, lmbda, a, b) + >>> inv_gamma = stats.invgamma(a, scale=b) + + One approach is to compare the distribution of the `s2` elements of + random variates against the PDF of an inverse gamma distribution. + + >>> _, s2 = norm_inv_gamma.rvs(size=10000, random_state=rng) + >>> bins = np.linspace(s2.min(), s2.max(), 50) + >>> plt.hist(s2, bins=bins, density=True, label='Frequency density') + >>> s2 = np.linspace(s2.min(), s2.max(), 300) + >>> plt.plot(s2, inv_gamma.pdf(s2), label='PDF') + >>> plt.xlabel(r'$\sigma^2$') + >>> plt.ylabel('Frequency density / PMF') + >>> plt.show() + + Similarly, we can compare the marginal distribution of `s2` against + an inverse gamma distribution. + + >>> from scipy.integrate import quad_vec + >>> from scipy import integrate + >>> s2 = np.linspace(0.5, 3, 6) + >>> res = quad_vec(lambda x: norm_inv_gamma.pdf(x, s2), -np.inf, np.inf)[0] + >>> np.allclose(res, inv_gamma.pdf(s2)) + True + + The sample mean is comparable to the mean of the distribution. + + >>> x, s2 = norm_inv_gamma.rvs(size=10000, random_state=rng) + >>> x.mean(), s2.mean() + (np.float64(-0.005254750127304425), np.float64(1.050438111436508)) + >>> norm_inv_gamma.mean() + (np.float64(0.0), np.float64(1.0526315789473684)) + + Similarly, for the variance: + + >>> x.var(ddof=1), s2.var(ddof=1) + (np.float64(1.0546150578185023), np.float64(0.061829865266330754)) + >>> norm_inv_gamma.var() + (np.float64(1.0526315789473684), np.float64(0.061557402277623886)) + """ + def rvs(self, mu=0, lmbda=1, a=1, b=1, size=None, random_state=None): + """Draw random samples from the distribution. + + Parameters + ---------- + mu, lmbda, a, b : array_like, optional + Shape parameters. `lmbda`, `a`, and `b` must be greater + than zero. + size : int or tuple of ints, optional + Shape of samples to draw. + random_state : {None, int, np.random.RandomState, np.random.Generator}, optional + Used for drawing random variates. + If `random_state` is `None`, the `~np.random.RandomState` singleton is used. + If `random_state` is an int, a new ``RandomState`` instance is used, seeded + with `random_state`. + If `random_state` is already a ``RandomState`` or ``Generator`` instance, + then that object is used. + Default is `None`. + + Returns + ------- + x, s2 : ndarray + Random variates. + + """ + random_state = self._get_random_state(random_state) + s2 = invgamma(a, scale=b).rvs(size=size, random_state=random_state) + scale = (s2 / lmbda)**0.5 + x = norm(loc=mu, scale=scale).rvs(size=size, random_state=random_state) + dtype = np.result_type(1.0, mu, lmbda, a, b) + return x.astype(dtype), s2.astype(dtype) + + def _logpdf(self, x, s2, mu, lmbda, a, b): + t1 = 0.5 * (np.log(lmbda) - np.log(2 * np.pi * s2)) + t2 = a*np.log(b) - special.gammaln(a).astype(a.dtype) + t3 = -(a + 1) * np.log(s2) + t4 = -(2*b + lmbda*(x - mu)**2) / (2*s2) + return t1 + t2 + t3 + t4 + + def logpdf(self, x, s2, mu=0, lmbda=1, a=1, b=1): + """Log of the probability density function. + + Parameters + ---------- + x, s2 : array_like + Arguments. `s2` must be greater than zero. + mu, lmbda, a, b : array_like, optional + Shape parameters. `lmbda`, `a`, and `b` must be greater + than zero. + + Returns + ------- + logpdf : ndarray or scalar + Log of the probability density function. + + """ + invalid, args = self._process_parameters_pdf(x, s2, mu, lmbda, a, b) + s2 = args[1] + # Keep it simple for now; lazyselect later, perhaps. + with np.errstate(all='ignore'): + logpdf = np.asarray(self._logpdf(*args)) + logpdf[s2 <= 0] = -np.inf + logpdf[invalid] = np.nan + return logpdf[()] + + def _pdf(self, x, s2, mu, lmbda, a, b): + t1 = np.sqrt(lmbda / (2 * np.pi * s2)) + t2 = b**a / special.gamma(a).astype(a.dtype) + t3 = (1 / s2)**(a + 1) + t4 = np.exp(-(2*b + lmbda*(x - mu)**2) / (2*s2)) + return t1 * t2 * t3 * t4 + + def pdf(self, x, s2, mu=0, lmbda=1, a=1, b=1): + """The probability density function. + + Parameters + ---------- + x, s2 : array_like + Arguments. `s2` must be greater than zero. + mu, lmbda, a, b : array_like, optional + Shape parameters. `lmbda`, `a`, and `b` must be greater + than zero. + + Returns + ------- + logpdf : ndarray or scalar + The probability density function. + + """ + invalid, args = self._process_parameters_pdf(x, s2, mu, lmbda, a, b) + s2 = args[1] + # Keep it simple for now; lazyselect later, perhaps. + with np.errstate(all='ignore'): + pdf = np.asarray(self._pdf(*args)) + pdf[s2 <= 0] = 0 + pdf[invalid] = np.nan + return pdf[()] + + def mean(self, mu=0, lmbda=1, a=1, b=1): + """The mean of the distribution. + + Parameters + ---------- + mu, lmbda, a, b : array_like, optional + Shape parameters. `lmbda` and `b` must be greater + than zero, and `a` must be greater than one. + + Returns + ------- + x, s2 : ndarray + The mean of the distribution. + + """ + invalid, args = self._process_shapes(mu, lmbda, a, b) + mu, lmbda, a, b = args + invalid |= ~(a > 1) + mean_x = np.asarray(mu).copy() + mean_s2 = np.asarray(b / (a - 1)) + mean_x[invalid] = np.nan + mean_s2[invalid] = np.nan + return mean_x[()], mean_s2[()] + + def var(self, mu=0, lmbda=1, a=1, b=1): + """The variance of the distribution. + + Parameters + ---------- + mu, lmbda, a, b : array_like, optional + Shape parameters. `lmbda` and `b` must be greater + than zero, and `a` must be greater than two. + + Returns + ------- + x, s2 : ndarray + The variance of the distribution. + + """ + invalid, args = self._process_shapes(mu, lmbda, a, b) + mu, lmbda, a, b = args + invalid_x = invalid | ~(a > 1) + invalid_s2 = invalid | ~(a > 2) + var_x = b / ((a - 1) * lmbda) + var_s2 = b**2 / ((a - 1)**2 * (a - 2)) + var_x, var_s2 = np.asarray(var_x), np.asarray(var_s2) + var_x[invalid_x] = np.nan + var_s2[invalid_s2] = np.nan + return var_x[()], var_s2[()] + + def _process_parameters_pdf(self, x, s2, mu, lmbda, a, b): + args = np.broadcast_arrays(x, s2, mu, lmbda, a, b) + dtype = np.result_type(1.0, *(arg.dtype for arg in args)) + args = [arg.astype(dtype, copy=False) for arg in args] + x, s2, mu, lmbda, a, b = args + invalid = ~((lmbda > 0) & (a > 0) & (b > 0)) + return invalid, args + + def _process_shapes(self, mu, lmbda, a, b): + args = np.broadcast_arrays(mu, lmbda, a, b) + dtype = np.result_type(1.0, *(arg.dtype for arg in args)) + args = [arg.astype(dtype, copy=False) for arg in args] + mu, lmbda, a, b = args + invalid = ~((lmbda > 0) & (a > 0) & (b > 0)) + return invalid, args + + def __call__(self, mu=0, lmbda=1, a=1, b=1, seed=None): + return normal_inverse_gamma_frozen(mu, lmbda, a, b, seed=seed) + + +normal_inverse_gamma = normal_inverse_gamma_gen() + + +class normal_inverse_gamma_frozen(multi_rv_frozen): + + def __init__(self, mu=0, lmbda=1, a=1, b=1, seed=None): + self._dist = normal_inverse_gamma_gen(seed) + self._shapes = mu, lmbda, a, b + + def logpdf(self, x, s2): + return self._dist.logpdf(x, s2, *self._shapes) + + def pdf(self, x, s2): + return self._dist.pdf(x, s2, *self._shapes) + + def mean(self): + return self._dist.mean(*self._shapes) + + def var(self): + return self._dist.var(*self._shapes) + + def rvs(self, size=None, random_state=None): + return self._dist.rvs(*self._shapes, size=size, random_state=random_state) + + +# Set frozen generator docstrings from corresponding docstrings in +# normal_inverse_gamma_gen and fill in default strings in class docstrings +for name in ['logpdf', 'pdf', 'mean', 'var', 'rvs']: + method = normal_inverse_gamma_gen.__dict__[name] + method_frozen = normal_inverse_gamma_frozen.__dict__[name] + method_frozen.__doc__ = doccer.docformat(method.__doc__, + mvn_docdict_noparams) + method.__doc__ = doccer.docformat(method.__doc__, mvn_docdict_params) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_new_distributions.py b/venv/lib/python3.12/site-packages/scipy/stats/_new_distributions.py new file mode 100644 index 0000000000000000000000000000000000000000..8393e7718b7300cc4aed4f620f31c8d44c0848ce --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_new_distributions.py @@ -0,0 +1,551 @@ +import sys + +import numpy as np +from numpy import inf + +from scipy._external import array_api_extra as xpx +from scipy import special +from scipy.special import _ufuncs as scu +from scipy.stats._distribution_infrastructure import ( + ContinuousDistribution, DiscreteDistribution, _RealInterval, _IntegerInterval, + _RealParameter, _Parameterization, _combine_docs) + +__all__ = ['Normal', 'Logistic', 'Uniform', 'Binomial'] + + +class Normal(ContinuousDistribution): + r"""Normal distribution with prescribed mean and standard deviation. + + The probability density function of the normal distribution is: + + .. math:: + + f(x) = \frac{1}{\sigma \sqrt{2 \pi}} \exp { + \left( -\frac{1}{2}\left( \frac{x - \mu}{\sigma} \right)^2 \right)} + + """ + # `ShiftedScaledDistribution` allows this to be generated automatically from + # an instance of `StandardNormal`, but the normal distribution is so frequently + # used that it's worth a bit of code duplication to get better performance. + _mu_domain = _RealInterval(endpoints=(-inf, inf)) + _sigma_domain = _RealInterval(endpoints=(0, inf)) + _x_support = _RealInterval(endpoints=(-inf, inf)) + + _mu_param = _RealParameter('mu', symbol=r'\mu', domain=_mu_domain, + typical=(-1, 1)) + _sigma_param = _RealParameter('sigma', symbol=r'\sigma', domain=_sigma_domain, + typical=(0.5, 1.5)) + _x_param = _RealParameter('x', domain=_x_support, typical=(-1, 1)) + + _parameterizations = [_Parameterization(_mu_param, _sigma_param)] + + _variable = _x_param + _normalization = 1/np.sqrt(2*np.pi) + _log_normalization = np.log(2*np.pi)/2 + + def __new__(cls, mu=None, sigma=None, **kwargs): + if mu is None and sigma is None: + return super().__new__(StandardNormal) + return super().__new__(cls) + + def __init__(self, *, mu=0., sigma=1., **kwargs): + super().__init__(mu=mu, sigma=sigma, **kwargs) + + def _logpdf_formula(self, x, *, mu, sigma, **kwargs): + return StandardNormal._logpdf_formula(self, (x - mu)/sigma) - np.log(sigma) + + def _pdf_formula(self, x, *, mu, sigma, **kwargs): + return StandardNormal._pdf_formula(self, (x - mu)/sigma) / sigma + + def _logcdf_formula(self, x, *, mu, sigma, **kwargs): + return StandardNormal._logcdf_formula(self, (x - mu)/sigma) + + def _cdf_formula(self, x, *, mu, sigma, **kwargs): + return StandardNormal._cdf_formula(self, (x - mu)/sigma) + + def _logccdf_formula(self, x, *, mu, sigma, **kwargs): + return StandardNormal._logccdf_formula(self, (x - mu)/sigma) + + def _ccdf_formula(self, x, *, mu, sigma, **kwargs): + return StandardNormal._ccdf_formula(self, (x - mu)/sigma) + + def _icdf_formula(self, x, *, mu, sigma, **kwargs): + return StandardNormal._icdf_formula(self, x) * sigma + mu + + def _ilogcdf_formula(self, x, *, mu, sigma, **kwargs): + return StandardNormal._ilogcdf_formula(self, x) * sigma + mu + + def _iccdf_formula(self, x, *, mu, sigma, **kwargs): + return StandardNormal._iccdf_formula(self, x) * sigma + mu + + def _ilogccdf_formula(self, x, *, mu, sigma, **kwargs): + return StandardNormal._ilogccdf_formula(self, x) * sigma + mu + + def _entropy_formula(self, *, mu, sigma, **kwargs): + return StandardNormal._entropy_formula(self) + np.log(abs(sigma)) + + def _logentropy_formula(self, *, mu, sigma, **kwargs): + lH0 = StandardNormal._logentropy_formula(self) + with np.errstate(divide='ignore'): + # sigma = 1 -> log(sigma) = 0 -> log(log(sigma)) = -inf + # Silence the unnecessary runtime warning + lls = np.log(np.log(abs(sigma))+0j) + return special.logsumexp(np.broadcast_arrays(lH0, lls), axis=0) + + def _median_formula(self, *, mu, sigma, **kwargs): + return mu + + def _mode_formula(self, *, mu, sigma, **kwargs): + return mu + + def _moment_raw_formula(self, order, *, mu, sigma, **kwargs): + if order == 0: + return np.ones_like(mu) + elif order == 1: + return mu + else: + return None + _moment_raw_formula.orders = [0, 1] # type: ignore[attr-defined] + + def _moment_central_formula(self, order, *, mu, sigma, **kwargs): + if order == 0: + return np.ones_like(mu) + elif order % 2: + return np.zeros_like(mu) + else: + # exact is faster (and obviously more accurate) for reasonable orders + return sigma**order * special.factorial2(int(order) - 1, exact=True) + + def _lmoment_formula(self, order, *, mu, sigma, **kwargs): + lscale = sigma / np.sqrt(np.pi) + lkurtosis = 30*np.arctan(np.sqrt(2))/np.pi - 9 + lmoments = {1: mu, 2: lscale, 3: 0, 4: lkurtosis * lscale} + return lmoments.get(order, None) + + def _sample_formula(self, full_shape, rng, *, mu, sigma, **kwargs): + return rng.normal(loc=mu, scale=sigma, size=full_shape)[()] + + +def _log_diff(log_p, log_q): + return special.logsumexp([log_p, log_q+np.pi*1j], axis=0) + + +class StandardNormal(Normal): + r"""Standard normal distribution. + + The probability density function of the standard normal distribution is: + + .. math:: + + f(x) = \frac{1}{\sqrt{2 \pi}} \exp \left( -\frac{1}{2} x^2 \right) + + """ + _x_support = _RealInterval(endpoints=(-inf, inf)) + _x_param = _RealParameter('x', domain=_x_support, typical=(-5, 5)) + _variable = _x_param + _parameterizations = [] + _normalization = 1/np.sqrt(2*np.pi) + _log_normalization = np.log(2*np.pi)/2 + mu = np.float64(0.) + sigma = np.float64(1.) + + def __init__(self, **kwargs): + ContinuousDistribution.__init__(self, **kwargs) + + def _logpdf_formula(self, x, **kwargs): + return -(self._log_normalization + x**2/2) + + def _pdf_formula(self, x, **kwargs): + return self._normalization * np.exp(-x**2/2) + + def _logcdf_formula(self, x, **kwargs): + return special.log_ndtr(x) + + def _cdf_formula(self, x, **kwargs): + return special.ndtr(x) + + def _logccdf_formula(self, x, **kwargs): + return special.log_ndtr(-x) + + def _ccdf_formula(self, x, **kwargs): + return special.ndtr(-x) + + def _icdf_formula(self, x, **kwargs): + return special.ndtri(x) + + def _ilogcdf_formula(self, x, **kwargs): + return special.ndtri_exp(x) + + def _iccdf_formula(self, x, **kwargs): + return -special.ndtri(x) + + def _ilogccdf_formula(self, x, **kwargs): + return -special.ndtri_exp(x) + + def _entropy_formula(self, **kwargs): + return (1 + np.log(2*np.pi))/2 + + def _logentropy_formula(self, **kwargs): + return np.log1p(np.log(2*np.pi)) - np.log(2) + + def _median_formula(self, **kwargs): + return 0 + + def _mode_formula(self, **kwargs): + return 0 + + def _moment_raw_formula(self, order, **kwargs): + raw_moments = {0: 1, 1: 0, 2: 1, 3: 0, 4: 3, 5: 0} + return raw_moments.get(order, None) + + def _moment_central_formula(self, order, **kwargs): + return self._moment_raw_formula(order, **kwargs) + + def _moment_standardized_formula(self, order, **kwargs): + return self._moment_raw_formula(order, **kwargs) + + def _lmoment_formula(self, order, **kwargs): + return super()._lmoment_formula(order, mu=0., sigma=1., **kwargs) + + def _sample_formula(self, full_shape, rng, **kwargs): + return rng.normal(size=full_shape)[()] + + +class Logistic(ContinuousDistribution): + r"""Standard logistic distribution. + + The probability density function of the standard logistic distribution is: + + .. math:: + + f(x) = \frac{1}{\left( e^{x / 2} + e^{-x / 2} \right)^2} + + """ + _x_support = _RealInterval(endpoints=(-inf, inf)) + _variable = _x_param = _RealParameter('x', domain=_x_support, typical=(-9, 9)) + _parameterizations = () # type:ignore[assignment] + + _scale = np.pi / np.sqrt(3) + + def _logpdf_formula(self, x, **kwargs): + y = -np.abs(x) + return y - 2 * special.log1p(np.exp(y)) + + def _pdf_formula(self, x, **kwargs): + # f(x) = sech(x / 2)**2 / 4 + return (.5 / np.cosh(x / 2))**2 + + def _logcdf_formula(self, x, **kwargs): + return special.log_expit(x) + + def _cdf_formula(self, x, **kwargs): + return special.expit(x) + + def _logccdf_formula(self, x, **kwargs): + return special.log_expit(-x) + + def _ccdf_formula(self, x, **kwargs): + return special.expit(-x) + + def _icdf_formula(self, x, **kwargs): + return special.logit(x) + + def _iccdf_formula(self, x, **kwargs): + return -special.logit(x) + + def _entropy_formula(self, **kwargs): + return 2.0 + + def _logentropy_formula(self, **kwargs): + return np.log(2) + + def _median_formula(self, **kwargs): + return 0 + + def _mode_formula(self, **kwargs): + return 0 + + def _moment_raw_formula(self, order, **kwargs): + n = int(order) + if n % 2: + return 0.0 + return np.pi**n * abs((2**n - 2) * float(special.bernoulli(n)[-1])) + + def _moment_central_formula(self, order, **kwargs): + return self._moment_raw_formula(order, **kwargs) + + def _moment_standardized_formula(self, order, **kwargs): + return self._moment_raw_formula(order, **kwargs) / self._scale**order + + def _lmoment_formula(self, order, **kwargs): + lmoments = {1: 0, 2: 1, 3: 0, 4: 1/6} + return lmoments.get(order, None) + + def _sample_formula(self, full_shape, rng, **kwargs): + return rng.logistic(size=full_shape)[()] + + +# currently for testing only +class _LogUniform(ContinuousDistribution): + r"""Log-uniform distribution. + + The probability density function of the log-uniform distribution is: + + .. math:: + + f(x; a, b) = \frac{1} + {x (\log(b) - \log(a))} + + If :math:`\log(X)` is a random variable that follows a uniform distribution + between :math:`\log(a)` and :math:`\log(b)`, then :math:`X` is log-uniformly + distributed with shape parameters :math:`a` and :math:`b`. + + """ + + _a_domain = _RealInterval(endpoints=(0, inf)) + _b_domain = _RealInterval(endpoints=('a', inf)) + _log_a_domain = _RealInterval(endpoints=(-inf, inf)) + _log_b_domain = _RealInterval(endpoints=('log_a', inf)) + _x_support = _RealInterval(endpoints=('a', 'b'), inclusive=(True, True)) + + _a_param = _RealParameter('a', domain=_a_domain, typical=(1e-3, 0.9)) + _b_param = _RealParameter('b', domain=_b_domain, typical=(1.1, 1e3)) + _log_a_param = _RealParameter('log_a', symbol=r'\log(a)', + domain=_log_a_domain, typical=(-3, -0.1)) + _log_b_param = _RealParameter('log_b', symbol=r'\log(b)', + domain=_log_b_domain, typical=(0.1, 3)) + _x_param = _RealParameter('x', domain=_x_support, typical=('a', 'b')) + + _b_domain.define_parameters(_a_param) + _log_b_domain.define_parameters(_log_a_param) + _x_support.define_parameters(_a_param, _b_param) + + _parameterizations = [_Parameterization(_log_a_param, _log_b_param), + _Parameterization(_a_param, _b_param)] + _variable = _x_param + + def __init__(self, *, a=None, b=None, log_a=None, log_b=None, **kwargs): + super().__init__(a=a, b=b, log_a=log_a, log_b=log_b, **kwargs) + + def _process_parameters(self, a=None, b=None, log_a=None, log_b=None, **kwargs): + a = np.exp(log_a) if a is None else a + b = np.exp(log_b) if b is None else b + log_a = np.log(a) if log_a is None else log_a + log_b = np.log(b) if log_b is None else log_b + kwargs.update(dict(a=a, b=b, log_a=log_a, log_b=log_b)) + return kwargs + + def _pdf_formula(self, x, *, log_a, log_b, **kwargs): + return ((log_b - log_a)*x)**-1 + + def _moment_raw_formula(self, order, log_a, log_b, **kwargs): + if order == 0: + return self._one + t1 = self._one / (log_b - log_a) / order + t2 = np.real(np.exp(_log_diff(order * log_b, order * log_a))) + return t1 * t2 + + +class Uniform(ContinuousDistribution): + r"""Uniform distribution. + + The probability density function of the uniform distribution is: + + .. math:: + + f(x; a, b) = \frac{1} + {b - a} + + """ + + _a_domain = _RealInterval(endpoints=(-inf, inf)) + _b_domain = _RealInterval(endpoints=('a', inf)) + _x_support = _RealInterval(endpoints=('a', 'b'), inclusive=(True, True)) + + _a_param = _RealParameter('a', domain=_a_domain, typical=(1e-3, 0.9)) + _b_param = _RealParameter('b', domain=_b_domain, typical=(1.1, 1e3)) + _x_param = _RealParameter('x', domain=_x_support, typical=('a', 'b')) + + _b_domain.define_parameters(_a_param) + _x_support.define_parameters(_a_param, _b_param) + + _parameterizations = [_Parameterization(_a_param, _b_param)] + _variable = _x_param + + def __init__(self, *, a=None, b=None, **kwargs): + super().__init__(a=a, b=b, **kwargs) + + def _process_parameters(self, a=None, b=None, ab=None, **kwargs): + ab = b - a + kwargs.update(dict(a=a, b=b, ab=ab)) + return kwargs + + def _logpdf_formula(self, x, *, ab, **kwargs): + return np.where(np.isnan(x), np.nan, -np.log(ab)) + + def _pdf_formula(self, x, *, ab, **kwargs): + return np.where(np.isnan(x), np.nan, 1/ab) + + def _logcdf_formula(self, x, *, a, ab, **kwargs): + with np.errstate(divide='ignore'): + return np.log(x - a) - np.log(ab) + + def _cdf_formula(self, x, *, a, ab, **kwargs): + return (x - a) / ab + + def _logccdf_formula(self, x, *, b, ab, **kwargs): + with np.errstate(divide='ignore'): + return np.log(b - x) - np.log(ab) + + def _ccdf_formula(self, x, *, b, ab, **kwargs): + return (b - x) / ab + + def _icdf_formula(self, p, *, a, ab, **kwargs): + return a + ab*p + + def _iccdf_formula(self, p, *, b, ab, **kwargs): + return b - ab*p + + def _entropy_formula(self, *, ab, **kwargs): + return np.log(ab) + + def _mode_formula(self, *, a, b, ab, **kwargs): + return a + 0.5*ab + + def _median_formula(self, *, a, b, ab, **kwargs): + return a + 0.5*ab + + def _moment_raw_formula(self, order, a, b, ab, **kwargs): + np1 = order + 1 + return (b**np1 - a**np1) / (np1 * ab) + + def _moment_central_formula(self, order, ab, **kwargs): + return ab**2/12 if order == 2 else None + + _moment_central_formula.orders = [2] # type: ignore[attr-defined] + + def _lmoment_formula(self, order, *, a, b, ab, **kwargs): + lmoments = {1: 0.5*(a + b), 2: ab / 6} + return lmoments.get(order, np.zeros_like(ab)) + + def _sample_formula(self, full_shape, rng, a, b, ab, **kwargs): + try: + return rng.uniform(a, b, size=full_shape)[()] + except OverflowError: # happens when there are NaNs + return rng.uniform(0, 1, size=full_shape)*ab + a + + +class _Gamma(ContinuousDistribution): + # Gamma distribution for testing only + _a_domain = _RealInterval(endpoints=(0, inf)) + _x_support = _RealInterval(endpoints=(0, inf), inclusive=(False, False)) + + _a_param = _RealParameter('a', domain=_a_domain, typical=(0.1, 10)) + _x_param = _RealParameter('x', domain=_x_support, typical=(0.1, 10)) + + _parameterizations = [_Parameterization(_a_param)] + _variable = _x_param + + def _pdf_formula(self, x, *, a, **kwargs): + return x ** (a - 1) * np.exp(-x) / special.gamma(a) + + +class Binomial(DiscreteDistribution): + r"""Binomial distribution with prescribed success probability and number of trials + + The probability density function of the binomial distribution is: + + .. math:: + + f(x) = {n \choose x} p^x (1 - p)^{n-x} + + """ + _n_domain = _IntegerInterval(endpoints=(0, inf), inclusive=(False, False)) + _p_domain = _RealInterval(endpoints=(0, 1), inclusive=(False, False)) + _x_support = _IntegerInterval(endpoints=(0, 'n'), inclusive=(True, True)) + + _n_param = _RealParameter('n', domain=_n_domain, typical=(10, 20)) + _p_param = _RealParameter('p', domain=_p_domain, typical=(0.25, 0.75)) + _x_param = _RealParameter('x', domain=_x_support, typical=(0, 10)) + + _parameterizations = [_Parameterization(_n_param, _p_param)] + _variable = _x_param + + def __init__(self, *, n, p, **kwargs): + super().__init__(n=n, p=p, **kwargs) + + def _pmf_formula(self, x, *, n, p, **kwargs): + return scu._binom_pmf(x, n, p) + + def _logpmf_formula(self, x, *, n, p, **kwargs): + # This implementation is from the ``scipy.stats.binom`` and could be improved + # by using a more numerically sound implementation of the absolute value of + # the binomial coefficient. + combiln = ( + special.gammaln(n+1) - (special.gammaln(x+1) + special.gammaln(n-x+1)) + ) + return combiln + special.xlogy(x, p) + special.xlog1py(n-x, -p) + + def _cdf_formula(self, x, *, n, p, **kwargs): + return scu._binom_cdf(x, n, p) + + def _logcdf_formula(self, x, *, n, p, **kwargs): + # todo: add this strategy to infrastructure more generally, but allow dist + # author to specify threshold other than median in case median is expensive + median = self._icdf_formula(0.5, n=n, p=p) + return xpx.apply_where(x < median, (x, n, p), + lambda *args: np.log(scu._binom_cdf(*args)), + lambda *args: np.log1p(-scu._binom_sf(*args)) + ) + + def _ccdf_formula(self, x, *, n, p, **kwargs): + return scu._binom_sf(x, n, p) + + def _logccdf_formula(self, x, *, n, p, **kwargs): + median = self._icdf_formula(0.5, n=n, p=p) + return xpx.apply_where(x < median, (x, n, p), + lambda *args: np.log1p(-scu._binom_cdf(*args)), + lambda *args: np.log(scu._binom_sf(*args)) + ) + + def _icdf_formula(self, x, *, n, p, **kwargs): + return scu._binom_ppf(x, n, p) + + def _iccdf_formula(self, x, *, n, p, **kwargs): + return scu._binom_isf(x, n, p) + + def _mode_formula(self, *, n, p, **kwargs): + # https://en.wikipedia.org/wiki/Binomial_distribution#Mode + mode = np.floor((n+1)*p) + mode = np.where(p == 1, mode - 1, mode) + return mode[()] + + def _moment_raw_formula(self, order, *, n, p, **kwargs): + # https://en.wikipedia.org/wiki/Binomial_distribution#Higher_moments + if order == 1: + return n*p + if order == 2: + return n*p*(1 - p + n*p) + return None + _moment_raw_formula.orders = [1, 2] # type: ignore[attr-defined] + + def _moment_central_formula(self, order, *, n, p, **kwargs): + # https://en.wikipedia.org/wiki/Binomial_distribution#Higher_moments + if order == 1: + return np.zeros_like(n) + if order == 2: + return n*p*(1 - p) + if order == 3: + return n*p*(1 - p)*(1 - 2*p) + if order == 4: + return n*p*(1 - p)*(1 + (3*n - 6)*p*(1 - p)) + return None + _moment_central_formula.orders = [1, 2, 3, 4] # type: ignore[attr-defined] + + +# Distribution classes need only define the summary and beginning of the extended +# summary portion of the class documentation. All other documentation, including +# examples, is generated automatically. +_module = sys.modules[__name__].__dict__ +for dist_name in __all__: + _module[dist_name].__doc__ = _combine_docs(_module[dist_name]) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_odds_ratio.py b/venv/lib/python3.12/site-packages/scipy/stats/_odds_ratio.py new file mode 100644 index 0000000000000000000000000000000000000000..bc593f5adc9a700c618c721b6c37c801809d868b --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_odds_ratio.py @@ -0,0 +1,466 @@ +import numpy as np + +from scipy.special import ndtri +from scipy.optimize import brentq +from ._discrete_distns import nchypergeom_fisher +from ._common import ConfidenceInterval + + +def _sample_odds_ratio(table): + """ + Given a table [[a, b], [c, d]], compute a*d/(b*c). + + Return nan if the numerator and denominator are 0. + Return inf if just the denominator is 0. + """ + # table must be a 2x2 numpy array. + if table[1, 0] > 0 and table[0, 1] > 0: + oddsratio = table[0, 0] * table[1, 1] / (table[1, 0] * table[0, 1]) + elif table[0, 0] == 0 or table[1, 1] == 0: + oddsratio = np.nan + else: + oddsratio = np.inf + return oddsratio + + +def _solve(func): + """ + Solve func(nc) = 0. func must be an increasing function. + """ + # We could just as well call the variable `x` instead of `nc`, but we + # always call this function with functions for which nc (the noncentrality + # parameter) is the variable for which we are solving. + nc = 1.0 + value = func(nc) + if value == 0: + return nc + + # Multiplicative factor by which to increase or decrease nc when + # searching for a bracketing interval. + factor = 2.0 + # Find a bracketing interval. + if value > 0: + nc /= factor + while func(nc) > 0: + nc /= factor + lo = nc + hi = factor*nc + else: + nc *= factor + while func(nc) < 0: + nc *= factor + lo = nc/factor + hi = nc + + # lo and hi bracket the solution for nc. + nc = brentq(func, lo, hi, xtol=1e-13) + return nc + + +def _nc_hypergeom_mean_inverse(x, M, n, N): + """ + For the given noncentral hypergeometric parameters x, M, n,and N + (table[0,0], total, row 0 sum and column 0 sum, resp., of a 2x2 + contingency table), find the noncentrality parameter of Fisher's + noncentral hypergeometric distribution whose mean is x. + """ + nc = _solve(lambda nc: nchypergeom_fisher.mean(M, n, N, nc) - x) + return nc + + +def _hypergeom_params_from_table(table): + # The notation M, n and N is consistent with stats.hypergeom and + # stats.nchypergeom_fisher. + x = table[0, 0] + M = table.sum() + n = table[0].sum() + N = table[:, 0].sum() + return x, M, n, N + + +def _ci_upper(table, alpha): + """ + Compute the upper end of the confidence interval. + """ + if _sample_odds_ratio(table) == np.inf: + return np.inf + + x, M, n, N = _hypergeom_params_from_table(table) + + # nchypergeom_fisher.cdf is a decreasing function of nc, so we negate + # it in the lambda expression. + nc = _solve(lambda nc: -nchypergeom_fisher.cdf(x, M, n, N, nc) + alpha) + return nc + + +def _ci_lower(table, alpha): + """ + Compute the lower end of the confidence interval. + """ + if _sample_odds_ratio(table) == 0: + return 0 + + x, M, n, N = _hypergeom_params_from_table(table) + + nc = _solve(lambda nc: nchypergeom_fisher.sf(x - 1, M, n, N, nc) - alpha) + return nc + + +def _conditional_oddsratio(table): + """ + Conditional MLE of the odds ratio for the 2x2 contingency table. + """ + x, M, n, N = _hypergeom_params_from_table(table) + # Get the bounds of the support. The support of the noncentral + # hypergeometric distribution with parameters M, n, and N is the same + # for all values of the noncentrality parameter, so we can use 1 here. + lo, hi = nchypergeom_fisher.support(M, n, N, 1) + + # Check if x is at one of the extremes of the support. If so, we know + # the odds ratio is either 0 or inf. + if x == lo: + # x is at the low end of the support. + return 0 + if x == hi: + # x is at the high end of the support. + return np.inf + + nc = _nc_hypergeom_mean_inverse(x, M, n, N) + return nc + + +def _conditional_oddsratio_ci(table, confidence_level=0.95, + alternative='two-sided'): + """ + Conditional exact confidence interval for the odds ratio. + """ + if alternative == 'two-sided': + alpha = 0.5*(1 - confidence_level) + lower = _ci_lower(table, alpha) + upper = _ci_upper(table, alpha) + elif alternative == 'less': + lower = 0.0 + upper = _ci_upper(table, 1 - confidence_level) + else: + # alternative == 'greater' + lower = _ci_lower(table, 1 - confidence_level) + upper = np.inf + + return lower, upper + + +def _sample_odds_ratio_ci(table, confidence_level=0.95, + alternative='two-sided'): + oddsratio = _sample_odds_ratio(table) + log_or = np.log(oddsratio) + se = np.sqrt((1/table).sum()) + if alternative == 'less': + z = ndtri(confidence_level) + loglow = -np.inf + loghigh = log_or + z*se + elif alternative == 'greater': + z = ndtri(confidence_level) + loglow = log_or - z*se + loghigh = np.inf + else: + # alternative is 'two-sided' + z = ndtri(0.5*confidence_level + 0.5) + loglow = log_or - z*se + loghigh = log_or + z*se + + return np.exp(loglow), np.exp(loghigh) + + +class OddsRatioResult: + """ + Result of `scipy.stats.contingency.odds_ratio`. See the + docstring for `odds_ratio` for more details. + + Attributes + ---------- + statistic : float + The computed odds ratio. + + * If `kind` is ``'sample'``, this is sample (or unconditional) + estimate, given by + ``table[0, 0]*table[1, 1]/(table[0, 1]*table[1, 0])``. + * If `kind` is ``'conditional'``, this is the conditional + maximum likelihood estimate for the odds ratio. It is + the noncentrality parameter of Fisher's noncentral + hypergeometric distribution with the same hypergeometric + parameters as `table` and whose mean is ``table[0, 0]``. + + Methods + ------- + confidence_interval : + Confidence interval for the odds ratio. + """ + + def __init__(self, _table, _kind, statistic): + # for now, no need to make _table and _kind public, since this sort of + # information is returned in very few `scipy.stats` results + self._table = _table + self._kind = _kind + self.statistic = statistic + + def __repr__(self): + return f"OddsRatioResult(statistic={self.statistic})" + + def confidence_interval(self, confidence_level=0.95, + alternative='two-sided'): + """ + Confidence interval for the odds ratio. + + Parameters + ---------- + confidence_level: float + Desired confidence level for the confidence interval. + The value must be given as a fraction between 0 and 1. + Default is 0.95 (meaning 95%). + + alternative : {'two-sided', 'less', 'greater'}, optional + The alternative hypothesis of the hypothesis test to which the + confidence interval corresponds. That is, suppose the null + hypothesis is that the true odds ratio equals ``OR`` and the + confidence interval is ``(low, high)``. Then the following options + for `alternative` are available (default is 'two-sided'): + + * 'two-sided': the true odds ratio is not equal to ``OR``. There + is evidence against the null hypothesis at the chosen + `confidence_level` if ``high < OR`` or ``low > OR``. + * 'less': the true odds ratio is less than ``OR``. The ``low`` end + of the confidence interval is 0, and there is evidence against + the null hypothesis at the chosen `confidence_level` if + ``high < OR``. + * 'greater': the true odds ratio is greater than ``OR``. The + ``high`` end of the confidence interval is ``np.inf``, and there + is evidence against the null hypothesis at the chosen + `confidence_level` if ``low > OR``. + + Returns + ------- + ci : ``ConfidenceInterval`` instance + The confidence interval, represented as an object with + attributes ``low`` and ``high``. + + Notes + ----- + When `kind` is ``'conditional'``, the limits of the confidence + interval are the conditional "exact confidence limits" as described + by Fisher [1]_. The conditional odds ratio and confidence interval are + also discussed in Section 4.1.2 of the text by Sahai and Khurshid [2]_. + + When `kind` is ``'sample'``, the confidence interval is computed + under the assumption that the logarithm of the odds ratio is normally + distributed with standard error given by:: + + se = sqrt(1/a + 1/b + 1/c + 1/d) + + where ``a``, ``b``, ``c`` and ``d`` are the elements of the + contingency table. (See, for example, [2]_, section 3.1.3.2, + or [3]_, section 2.3.3). + + References + ---------- + .. [1] R. A. Fisher (1935), The logic of inductive inference, + Journal of the Royal Statistical Society, Vol. 98, No. 1, + pp. 39-82. + .. [2] H. Sahai and A. Khurshid (1996), Statistics in Epidemiology: + Methods, Techniques, and Applications, CRC Press LLC, Boca + Raton, Florida. + .. [3] Alan Agresti, An Introduction to Categorical Data Analysis + (second edition), Wiley, Hoboken, NJ, USA (2007). + """ + if alternative not in ['two-sided', 'less', 'greater']: + raise ValueError("`alternative` must be 'two-sided', 'less' or " + "'greater'.") + + if confidence_level < 0 or confidence_level > 1: + raise ValueError('confidence_level must be between 0 and 1') + + if self._kind == 'conditional': + ci = self._conditional_odds_ratio_ci(confidence_level, alternative) + else: + ci = self._sample_odds_ratio_ci(confidence_level, alternative) + return ci + + def _conditional_odds_ratio_ci(self, confidence_level=0.95, + alternative='two-sided'): + """ + Confidence interval for the conditional odds ratio. + """ + + table = self._table + if 0 in table.sum(axis=0) or 0 in table.sum(axis=1): + # If both values in a row or column are zero, the p-value is 1, + # the odds ratio is NaN and the confidence interval is (0, inf). + ci = (0, np.inf) + else: + ci = _conditional_oddsratio_ci(table, + confidence_level=confidence_level, + alternative=alternative) + return ConfidenceInterval(low=ci[0], high=ci[1]) + + def _sample_odds_ratio_ci(self, confidence_level=0.95, + alternative='two-sided'): + """ + Confidence interval for the sample odds ratio. + """ + if confidence_level < 0 or confidence_level > 1: + raise ValueError('confidence_level must be between 0 and 1') + + table = self._table + if 0 in table.sum(axis=0) or 0 in table.sum(axis=1): + # If both values in a row or column are zero, the p-value is 1, + # the odds ratio is NaN and the confidence interval is (0, inf). + ci = (0, np.inf) + else: + ci = _sample_odds_ratio_ci(table, + confidence_level=confidence_level, + alternative=alternative) + return ConfidenceInterval(low=ci[0], high=ci[1]) + + +def odds_ratio(table, *, kind='conditional'): + r""" + Compute the odds ratio for a 2x2 contingency table. + + Parameters + ---------- + table : array_like of ints + A 2x2 contingency table. Elements must be non-negative integers. + kind : str, optional + Which kind of odds ratio to compute, either the sample + odds ratio (``kind='sample'``) or the conditional odds ratio + (``kind='conditional'``). Default is ``'conditional'``. + + Returns + ------- + result : `~scipy.stats._result_classes.OddsRatioResult` instance + The returned object has two computed attributes: + + statistic : float + * If `kind` is ``'sample'``, this is sample (or unconditional) + estimate, given by + ``table[0, 0]*table[1, 1]/(table[0, 1]*table[1, 0])``. + * If `kind` is ``'conditional'``, this is the conditional + maximum likelihood estimate for the odds ratio. It is + the noncentrality parameter of Fisher's noncentral + hypergeometric distribution with the same hypergeometric + parameters as `table` and whose mean is ``table[0, 0]``. + + The object has the method `confidence_interval` that computes + the confidence interval of the odds ratio. + + See Also + -------- + scipy.stats.fisher_exact + relative_risk + :ref:`hypothesis_odds_ratio` : Extended example + + Notes + ----- + The conditional odds ratio was discussed by Fisher (see "Example 1" + of [1]_). Texts that cover the odds ratio include [2]_ and [3]_. + + .. versionadded:: 1.10.0 + + References + ---------- + .. [1] R. A. Fisher (1935), The logic of inductive inference, + Journal of the Royal Statistical Society, Vol. 98, No. 1, + pp. 39-82. + .. [2] Breslow NE, Day NE (1980). Statistical methods in cancer research. + Volume I - The analysis of case-control studies. IARC Sci Publ. + (32):5-338. PMID: 7216345. (See section 4.2.) + .. [3] H. Sahai and A. Khurshid (1996), Statistics in Epidemiology: + Methods, Techniques, and Applications, CRC Press LLC, Boca + Raton, Florida. + + Examples + -------- + In epidemiology, individuals are classified as "exposed" or + "unexposed" to some factor or treatment. If the occurrence of some + illness is under study, those who have the illness are often + classified as "cases", and those without it are "noncases". The + counts of the occurrences of these classes gives a contingency + table:: + + exposed unexposed + cases a b + noncases c d + + The sample odds ratio may be written ``(a/c) / (b/d)``. ``a/c`` can + be interpreted as the odds of a case occurring in the exposed group, + and ``b/d`` as the odds of a case occurring in the unexposed group. + The sample odds ratio is the ratio of these odds. If the odds ratio + is greater than 1, it suggests that there is a positive association + between being exposed and being a case. + + Interchanging the rows or columns of the contingency table inverts + the odds ratio, so it is important to understand the meaning of labels + given to the rows and columns of the table when interpreting the + odds ratio. + + Consider a hypothetical example where it is hypothesized that exposure to a + certain chemical is associated with increased occurrence of a certain + disease. Suppose we have the following table for a collection of 410 people:: + + exposed unexposed + cases 7 15 + noncases 58 472 + + The question we ask is "Is exposure to the chemical associated with + increased risk of the disease?" + + Compute the odds ratio: + + >>> from scipy.stats.contingency import odds_ratio + >>> res = odds_ratio([[7, 15], [58, 472]]) + >>> res.statistic + 3.7836687705553493 + + For this sample, the odds of getting the disease for those who have been + exposed to the chemical are almost 3.8 times that of those who have not been + exposed. + + We can compute the 95% confidence interval for the odds ratio: + + >>> res.confidence_interval(confidence_level=0.95) + ConfidenceInterval(low=1.2514829132266785, high=10.363493716701269) + + The 95% confidence interval for the conditional odds ratio is approximately + (1.25, 10.4). + + For a more detailed example, see :ref:`hypothesis_odds_ratio`. + """ + if kind not in ['conditional', 'sample']: + raise ValueError("`kind` must be 'conditional' or 'sample'.") + + c = np.asarray(table) + + if c.shape != (2, 2): + raise ValueError(f"Invalid shape {c.shape}. The input `table` must be " + "of shape (2, 2).") + + if not np.issubdtype(c.dtype, np.integer): + raise ValueError("`table` must be an array of integers, but got " + f"type {c.dtype}") + c = c.astype(np.int64) + + if np.any(c < 0): + raise ValueError("All values in `table` must be nonnegative.") + + if 0 in c.sum(axis=0) or 0 in c.sum(axis=1): + # If both values in a row or column are zero, the p-value is NaN and + # the odds ratio is NaN. + result = OddsRatioResult(_table=c, _kind=kind, statistic=np.nan) + return result + + if kind == 'sample': + oddsratio = _sample_odds_ratio(c) + else: # kind is 'conditional' + oddsratio = _conditional_oddsratio(c) + + result = OddsRatioResult(_table=c, _kind=kind, statistic=oddsratio) + return result diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_page_trend_test.py b/venv/lib/python3.12/site-packages/scipy/stats/_page_trend_test.py new file mode 100644 index 0000000000000000000000000000000000000000..27f8dda8c043eafb0c0380f0cd3269afd30046e9 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_page_trend_test.py @@ -0,0 +1,488 @@ +from dataclasses import dataclass +from itertools import permutations +import math +import threading + +import numpy as np + +from ._continuous_distns import norm +from scipy._lib._array_api import xp_capabilities +import scipy.stats + + +@dataclass +class PageTrendTestResult: + statistic: float + pvalue: float + method: str + + +@xp_capabilities(np_only=True) +def page_trend_test(data, ranked=False, predicted_ranks=None, method='auto'): + r""" + Perform Page's Test, a measure of trend in observations between treatments. + + Page's Test (also known as Page's :math:`L` test) is useful when: + + * there are :math:`n \geq 3` treatments, + * :math:`m \geq 2` subjects are observed for each treatment, and + * the observations are hypothesized to have a particular order. + + Specifically, the test considers the null hypothesis that + + .. math:: + + m_1 = m_2 = m_3 \cdots = m_n, + + where :math:`m_j` is the mean of the observed quantity under treatment + :math:`j`, against the alternative hypothesis that + + .. math:: + + m_1 \leq m_2 \leq m_3 \leq \cdots \leq m_n, + + where at least one inequality is strict. + + As noted by [4]_, Page's :math:`L` test has greater statistical power than + the Friedman test against the alternative that there is a difference in + trend, as Friedman's test only considers a difference in the means of the + observations without considering their order. Whereas Spearman :math:`\rho` + considers the correlation between the ranked observations of two variables + (e.g. the airspeed velocity of a swallow vs. the weight of the coconut it + carries), Page's :math:`L` is concerned with a trend in an observation + (e.g. the airspeed velocity of a swallow) across several distinct + treatments (e.g. carrying each of five coconuts of different weight) even + as the observation is repeated with multiple subjects (e.g. one European + swallow and one African swallow). + + Parameters + ---------- + data : array-like + A :math:`m \times n` array; the element in row :math:`i` and + column :math:`j` is the observation corresponding with subject + :math:`i` and treatment :math:`j`. By default, the columns are + assumed to be arranged in order of increasing predicted mean. + + ranked : bool, optional + By default, `data` is assumed to be observations rather than ranks; + it will be ranked with `scipy.stats.rankdata` along ``axis=1``. If + `data` is provided in the form of ranks, pass argument ``True``. + + predicted_ranks : array-like, optional + The predicted ranks of the column means. If not specified, + the columns are assumed to be arranged in order of increasing + predicted mean, so the default `predicted_ranks` are + :math:`[1, 2, \dots, n-1, n]`. + + method : {'auto', 'asymptotic', 'exact'}, optional + Selects the method used to calculate the *p*-value. The following + options are available. + + * 'auto': selects between 'exact' and 'asymptotic' to + achieve reasonably accurate results in reasonable time (default) + * 'asymptotic': compares the standardized test statistic against + the normal distribution + * 'exact': computes the exact *p*-value by comparing the observed + :math:`L` statistic against those realized by all possible + permutations of ranks (under the null hypothesis that each + permutation is equally likely) + + Returns + ------- + res : PageTrendTestResult + An object containing attributes: + + statistic : float + Page's :math:`L` test statistic. + pvalue : float + The associated *p*-value + method : {'asymptotic', 'exact'} + The method used to compute the *p*-value + + See Also + -------- + rankdata, friedmanchisquare, spearmanr + + Notes + ----- + As noted in [1]_, "the :math:`n` 'treatments' could just as well represent + :math:`n` objects or events or performances or persons or trials ranked." + Similarly, the :math:`m` 'subjects' could equally stand for :math:`m` + "groupings by ability or some other control variable, or judges doing + the ranking, or random replications of some other sort." + + The procedure for calculating the :math:`L` statistic, adapted from + [1]_, is: + + 1. "Predetermine with careful logic the appropriate hypotheses + concerning the predicted ordering of the experimental results. + If no reasonable basis for ordering any treatments is known, the + :math:`L` test is not appropriate." + 2. "As in other experiments, determine at what level of confidence + you will reject the null hypothesis that there is no agreement of + experimental results with the monotonic hypothesis." + 3. "Cast the experimental material into a two-way table of :math:`n` + columns (treatments, objects ranked, conditions) and :math:`m` + rows (subjects, replication groups, levels of control variables)." + 4. "When experimental observations are recorded, rank them across each + row", e.g. ``ranks = scipy.stats.rankdata(data, axis=1)``. + 5. "Add the ranks in each column", e.g. + ``colsums = np.sum(ranks, axis=0)``. + 6. "Multiply each sum of ranks by the predicted rank for that same + column", e.g. ``products = predicted_ranks * colsums``. + 7. "Sum all such products", e.g. ``L = products.sum()``. + + [1]_ continues by suggesting use of the standardized statistic + + .. math:: + + \chi_L^2 = \frac{\left[12L-3mn(n+1)^2\right]^2}{mn^2(n^2-1)(n+1)} + + "which is distributed approximately as chi-square with 1 degree of + freedom. The ordinary use of :math:`\chi^2` tables would be + equivalent to a two-sided test of agreement. If a one-sided test + is desired, *as will almost always be the case*, the probability + discovered in the chi-square table should be *halved*." + + However, this standardized statistic does not distinguish between the + observed values being well correlated with the predicted ranks and being + _anti_-correlated with the predicted ranks. Instead, we follow [2]_ + and calculate the standardized statistic + + .. math:: + + \Lambda = \frac{L - E_0}{\sqrt{V_0}}, + + where :math:`E_0 = \frac{1}{4} mn(n+1)^2` and + :math:`V_0 = \frac{1}{144} mn^2(n+1)(n^2-1)`, "which is asymptotically + normal under the null hypothesis". + + The *p*-value for ``method='exact'`` is generated by comparing the observed + value of :math:`L` against the :math:`L` values generated for all + :math:`(n!)^m` possible permutations of ranks. The calculation is performed + using the recursive method of [5]. + + The *p*-values are not adjusted for the possibility of ties. When + ties are present, the reported ``'exact'`` *p*-values may be somewhat + larger (i.e. more conservative) than the true *p*-value [2]_. The + ``'asymptotic'`` *p*-values, however, tend to be smaller (i.e. less + conservative) than the ``'exact'`` *p*-values. + + References + ---------- + .. [1] Ellis Batten Page, "Ordered hypotheses for multiple treatments: + a significant test for linear ranks", *Journal of the American + Statistical Association* 58(301), p. 216--230, 1963. + + .. [2] Markus Neuhauser, *Nonparametric Statistical Test: A computational + approach*, CRC Press, p. 150--152, 2012. + + .. [3] Statext LLC, "Page's L Trend Test - Easy Statistics", *Statext - + Statistics Study*, https://www.statext.com/practice/PageTrendTest03.php, + Accessed July 12, 2020. + + .. [4] "Page's Trend Test", *Wikipedia*, WikimediaFoundation, + https://en.wikipedia.org/wiki/Page%27s_trend_test, + Accessed July 12, 2020. + + .. [5] Robert E. Odeh, "The exact distribution of Page's L-statistic in + the two-way layout", *Communications in Statistics - Simulation and + Computation*, 6(1), p. 49--61, 1977. + + Examples + -------- + We use the example from [3]_: 10 students are asked to rate three + teaching methods - tutorial, lecture, and seminar - on a scale of 1-5, + with 1 being the lowest and 5 being the highest. We have decided that + a confidence level of 99% is required to reject the null hypothesis in + favor of our alternative: that the seminar will have the highest ratings + and the tutorial will have the lowest. Initially, the data have been + tabulated with each row representing an individual student's ratings of + the three methods in the following order: tutorial, lecture, seminar. + + >>> table = [[3, 4, 3], + ... [2, 2, 4], + ... [3, 3, 5], + ... [1, 3, 2], + ... [2, 3, 2], + ... [2, 4, 5], + ... [1, 2, 4], + ... [3, 4, 4], + ... [2, 4, 5], + ... [1, 3, 4]] + + Because the tutorial is hypothesized to have the lowest ratings, the + column corresponding with tutorial rankings should be first; the seminar + is hypothesized to have the highest ratings, so its column should be last. + Since the columns are already arranged in this order of increasing + predicted mean, we can pass the table directly into `page_trend_test`. + + >>> from scipy.stats import page_trend_test + >>> res = page_trend_test(table) + >>> res + PageTrendTestResult(statistic=133.5, pvalue=0.0018191161948127822, + method='exact') + + This *p*-value indicates that there is a 0.1819% chance that + the :math:`L` statistic would reach such an extreme value under the null + hypothesis. Because 0.1819% is less than 1%, we have evidence to reject + the null hypothesis in favor of our alternative at a 99% confidence level. + + The value of the :math:`L` statistic is 133.5. To check this manually, + we rank the data such that high scores correspond with high ranks, settling + ties with an average rank: + + >>> from scipy.stats import rankdata + >>> ranks = rankdata(table, axis=1) + >>> ranks + array([[1.5, 3. , 1.5], + [1.5, 1.5, 3. ], + [1.5, 1.5, 3. ], + [1. , 3. , 2. ], + [1.5, 3. , 1.5], + [1. , 2. , 3. ], + [1. , 2. , 3. ], + [1. , 2.5, 2.5], + [1. , 2. , 3. ], + [1. , 2. , 3. ]]) + + We add the ranks within each column, multiply the sums by the + predicted ranks, and sum the products. + + >>> import numpy as np + >>> m, n = ranks.shape + >>> predicted_ranks = np.arange(1, n+1) + >>> L = (predicted_ranks * np.sum(ranks, axis=0)).sum() + >>> res.statistic == L + True + + As presented in [3]_, the asymptotic approximation of the *p*-value is the + survival function of the normal distribution evaluated at the standardized + test statistic: + + >>> from scipy.stats import norm + >>> E0 = (m*n*(n+1)**2)/4 + >>> V0 = (m*n**2*(n+1)*(n**2-1))/144 + >>> Lambda = (L-E0)/np.sqrt(V0) + >>> p = norm.sf(Lambda) + >>> p + 0.0012693433690751756 + + This does not precisely match the *p*-value reported by `page_trend_test` + above. The asymptotic distribution is not very accurate, nor conservative, + for :math:`m \leq 12` and :math:`n \leq 8`, so `page_trend_test` chose to + use ``method='exact'`` based on the dimensions of the table and the + recommendations in Page's original paper [1]_. To override + `page_trend_test`'s choice, provide the `method` argument. + + >>> res = page_trend_test(table, method="asymptotic") + >>> res + PageTrendTestResult(statistic=133.5, pvalue=0.0012693433690751756, + method='asymptotic') + + If the data are already ranked, we can pass in the ``ranks`` instead of + the ``table`` to save computation time. + + >>> res = page_trend_test(ranks, # ranks of data + ... ranked=True, # data is already ranked + ... ) + >>> res + PageTrendTestResult(statistic=133.5, pvalue=0.0018191161948127822, + method='exact') + + Suppose the raw data had been tabulated in an order different from the + order of predicted means, say lecture, seminar, tutorial. + + >>> table = np.asarray(table)[:, [1, 2, 0]] + + Since the arrangement of this table is not consistent with the assumed + ordering, we can either rearrange the table or provide the + `predicted_ranks`. Remembering that the lecture is predicted + to have the middle rank, the seminar the highest, and tutorial the lowest, + we pass: + + >>> res = page_trend_test(table, # data as originally tabulated + ... predicted_ranks=[2, 3, 1], # our predicted order + ... ) + >>> res + PageTrendTestResult(statistic=133.5, pvalue=0.0018191161948127822, + method='exact') + + """ + if not hasattr(_pagel_state, 'state'): + _pagel_state.state = _PageL() + + # Possible values of the method parameter and the corresponding function + # used to evaluate the p value + methods = {"asymptotic": _l_p_asymptotic, + "exact": _l_p_exact, + "auto": None} + if method not in methods: + raise ValueError(f"`method` must be in {set(methods)}") + + ranks = np.asarray(data) + if ranks.ndim != 2: # TODO: relax this to accept 3d arrays? + raise ValueError("`data` must be a 2d array.") + + m, n = ranks.shape + if m < 2 or n < 3: + raise ValueError("Page's L is only appropriate for data with two " + "or more rows and three or more columns.") + + if np.any(np.isnan(data)): + raise ValueError("`data` contains NaNs, which cannot be ranked " + "meaningfully") + + # ensure NumPy array and rank the data if it's not already ranked + if ranked: + # Only a basic check on whether data is ranked. Checking that the data + # is properly ranked could take as much time as ranking it. + if not (ranks.min() >= 1 and ranks.max() <= ranks.shape[1]): + raise ValueError("`data` is not properly ranked. Rank the data or " + "pass `ranked=False`.") + else: + ranks = scipy.stats.rankdata(data, axis=-1) + + # generate predicted ranks if not provided, ensure valid NumPy array + if predicted_ranks is None: + predicted_ranks = np.arange(1, n+1) + else: + predicted_ranks = np.asarray(predicted_ranks) + if (predicted_ranks.ndim < 1 or + (set(predicted_ranks) != set(range(1, n+1)) or + len(predicted_ranks) != n)): + raise ValueError(f"`predicted_ranks` must include each integer " + f"from 1 to {n} (the number of columns in " + f"`data`) exactly once.") + + if not isinstance(ranked, bool): + raise TypeError("`ranked` must be boolean.") + + # Calculate the L statistic + L = _l_vectorized(ranks, predicted_ranks) + + # Calculate the p-value + if method == "auto": + method = _choose_method(ranks) + p_fun = methods[method] # get the function corresponding with the method + p = p_fun(L, m, n) + + page_result = PageTrendTestResult(statistic=L, pvalue=p, method=method) + return page_result + + +def _choose_method(ranks): + '''Choose method for computing p-value automatically''' + m, n = ranks.shape + if n > 8 or (m > 12 and n > 3) or m > 20: # as in [1], [4] + method = "asymptotic" + else: + method = "exact" + return method + + +def _l_vectorized(ranks, predicted_ranks): + '''Calculate's Page's L statistic for each page of a 3d array''' + colsums = ranks.sum(axis=-2, keepdims=True) + products = predicted_ranks * colsums + Ls = products.sum(axis=-1) + Ls = Ls[0] if Ls.size == 1 else Ls.ravel() + return Ls + + +def _l_p_asymptotic(L, m, n): + '''Calculate the p-value of Page's L from the asymptotic distribution''' + # Using [1] as a reference, the asymptotic p-value would be calculated as: + # chi_L = (12*L - 3*m*n*(n+1)**2)**2/(m*n**2*(n**2-1)*(n+1)) + # p = chi2.sf(chi_L, df=1, loc=0, scale=1)/2 + # but this is insensitive to the direction of the hypothesized ranking + + # See [2] page 151 + E0 = (m*n*(n+1)**2)/4 + V0 = (m*n**2*(n+1)*(n**2-1))/144 + Lambda = (L-E0)/np.sqrt(V0) + # This is a one-sided "greater" test - calculate the probability that the + # L statistic under H0 would be greater than the observed L statistic + p = norm.sf(Lambda) + return p + + +def _l_p_exact(L, m, n): + '''Calculate the p-value of Page's L exactly''' + # [1] uses m, n; [5] uses n, k. + # Switch convention here because exact calculation code references [5]. + L, n, k = int(L), int(m), int(n) + _pagel_state.state.set_k(k) + return _pagel_state.state.sf(L, n) + + +class _PageL: + '''Maintains state between `page_trend_test` executions''' + + def __init__(self): + '''Lightweight initialization''' + self.all_pmfs = {} + + def set_k(self, k): + '''Calculate lower and upper limits of L for single row''' + self.k = k + # See [5] top of page 52 + self.a, self.b = (k*(k+1)*(k+2))//6, (k*(k+1)*(2*k+1))//6 + + def sf(self, l, n): + '''Survival function of Page's L statistic''' + ps = [self.pmf(l, n) for l in range(l, n*self.b + 1)] + return np.sum(ps) + + def p_l_k_1(self): + '''Relative frequency of each L value over all possible single rows''' + + # See [5] Equation (6) + ranks = range(1, self.k+1) + # generate all possible rows of length k + rank_perms = np.array(list(permutations(ranks))) + # compute Page's L for all possible rows + Ls = (ranks*rank_perms).sum(axis=1) + # count occurrences of each L value + counts = np.histogram(Ls, np.arange(self.a-0.5, self.b+1.5))[0] + # factorial(k) is number of possible permutations + return counts/math.factorial(self.k) + + def pmf(self, l, n): + '''Recursive function to evaluate p(l, k, n); see [5] Equation 1''' + + if n not in self.all_pmfs: + self.all_pmfs[n] = {} + if self.k not in self.all_pmfs[n]: + self.all_pmfs[n][self.k] = {} + + # Cache results to avoid repeating calculation. Initially this was + # written with lru_cache, but this seems faster? Also, we could add + # an option to save this for future lookup. + if l in self.all_pmfs[n][self.k]: + return self.all_pmfs[n][self.k][l] + + if n == 1: + ps = self.p_l_k_1() # [5] Equation 6 + ls = range(self.a, self.b+1) + # not fast, but we'll only be here once + self.all_pmfs[n][self.k] = {l: p for l, p in zip(ls, ps)} + return self.all_pmfs[n][self.k][l] + + p = 0 + low = max(l-(n-1)*self.b, self.a) # [5] Equation 2 + high = min(l-(n-1)*self.a, self.b) + + # [5] Equation 1 + for t in range(low, high+1): + p1 = self.pmf(l-t, n-1) + p2 = self.pmf(t, 1) + p += p1*p2 + self.all_pmfs[n][self.k][l] = p + return p + + +# Maintain state for faster repeat calls to page_trend_test w/ method='exact' +# _PageL() is calculated once per thread and stored as an attribute on +# this thread-local variable inside page_trend_test(). +_pagel_state = threading.local() diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_probability_distribution.py b/venv/lib/python3.12/site-packages/scipy/stats/_probability_distribution.py new file mode 100644 index 0000000000000000000000000000000000000000..71ed8f48920f3031a88cf1285fae25e4bb28eefa --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_probability_distribution.py @@ -0,0 +1,2091 @@ +# Temporary file separated from _distribution_infrastructure.py +# to simplify the diff during PR review. +from abc import ABC, abstractmethod +from types import GenericAlias + +class _ProbabilityDistribution(ABC): + + # generic type compatibility with scipy-stubs + __class_getitem__: classmethod = classmethod(GenericAlias) + + @abstractmethod + def support(self): + r"""Support of the random variable + + The support of a random variable is set of all possible outcomes; + i.e., the subset of the domain of argument :math:`x` for which + the probability density function :math:`f(x)` is nonzero. + + This function returns lower and upper bounds of the support. + + Returns + ------- + out : tuple of Array + The lower and upper bounds of the support. + + See Also + -------- + pdf + + References + ---------- + .. [1] Support (mathematics), *Wikipedia*, + https://en.wikipedia.org/wiki/Support_(mathematics) + + Notes + ----- + Suppose a continuous probability distribution has support ``(l, r)``. + The following table summarizes the value returned by several + methods when the argument is outside the support. + + +----------------+---------------------+---------------------+ + | Method | Value for ``x < l`` | Value for ``x > r`` | + +================+=====================+=====================+ + | ``pdf(x)`` | 0 | 0 | + +----------------+---------------------+---------------------+ + | ``logpdf(x)`` | -inf | -inf | + +----------------+---------------------+---------------------+ + | ``cdf(x)`` | 0 | 1 | + +----------------+---------------------+---------------------+ + | ``logcdf(x)`` | -inf | 0 | + +----------------+---------------------+---------------------+ + | ``ccdf(x)`` | 1 | 0 | + +----------------+---------------------+---------------------+ + | ``logccdf(x)`` | 0 | -inf | + +----------------+---------------------+---------------------+ + + For discrete distributions, the same table is applicable with + ``pmf`` and ``logpmf`` substituted for ``pdf`` and ``logpdf``. + + For the ``cdf`` and related methods of continuous distributions, the + inequality need not be strict; i.e. the tabulated value is returned + when the method is evaluated *at* the corresponding boundary. + + The following table summarizes the value returned by the inverse + methods for arguments ``0`` and ``1``, whether the distribution + is continuous or discrete. + + +-------------+-----------+-----------+ + | Method | ``x = 0`` | ``x = 1`` | + +=============+===========+===========+ + | ``icdf(x)`` | ``l`` | ``r`` | + +-------------+-----------+-----------+ + | ``icdf(x)`` | ``r`` | ``l`` | + +-------------+-----------+-----------+ + + For the inverse log-functions, the same values are returned + for ``x = log(0)`` and ``x = log(1)``. All inverse functions return + ``nan`` when evaluated at an argument outside the domain ``0`` to ``1``. + + Examples + -------- + Instantiate a distribution with the desired parameters: + + >>> from scipy import stats + >>> X = stats.Uniform(a=-0.5, b=0.5) + + Retrieve the support of the distribution: + + >>> X.support() + (-0.5, 0.5) + + For a distribution with infinite support, + + >>> X = stats.Normal() + >>> X.support() + (-inf, inf) + + Due to underflow, the numerical value returned by the PDF may be zero + even for arguments within the support, even if the true value is + nonzero. In such cases, the log-PDF may be useful. + + >>> X.pdf([-100., 100.]) + array([0., 0.]) + >>> X.logpdf([-100., 100.]) + array([-5000.91893853, -5000.91893853]) + + Use cases for the log-CDF and related methods are analogous. + + """ + raise NotImplementedError() + + @abstractmethod + def sample(self, shape, *, method, rng): + r"""Random sample from the distribution. + + Parameters + ---------- + shape : tuple of ints, default: () + The shape of the sample to draw. If the parameters of the distribution + underlying the random variable are arrays of shape ``param_shape``, + the output array will be of shape ``shape + param_shape``. + method : {None, 'formula', 'inverse_transform'} + The strategy used to produce the sample. By default (``None``), + the infrastructure chooses between the following options, + listed in order of precedence. + + - ``'formula'``: an implementation specific to the distribution + - ``'inverse_transform'``: generate a uniformly distributed sample and + return the inverse CDF at these arguments. + + Not all `method` options are available for all distributions. + If the selected `method` is not available, a `NotImplementedError`` + will be raised. + rng : `numpy.random.Generator` or `scipy.stats.qmc.QMCEngine`, optional + Pseudo- or quasi-random number generator state. When `rng` is None, + a new `numpy.random.Generator` is created using entropy from the + operating system. Types other than `numpy.random.Generator` and + `scipy.stats.qmc.QMCEngine` are passed to `numpy.random.default_rng` + to instantiate a ``Generator``. + + If `rng` is an instance of `scipy.stats.qmc.QMCEngine` configured to use + scrambling and `shape` is not empty, then each slice along the zeroth + axis of the result is a "quasi-independent", low-discrepancy sequence; + that is, they are distinct sequences that can be treated as statistically + independent for most practical purposes. Separate calls to `sample` + produce new quasi-independent, low-discrepancy sequences. The dimensionality + (``d``) of the provided ``QMCEngine`` is ignored; new instances of the same + class and configuration options (``scramble``, ``optimization``, and + ``bits``, where applicable) will be created with ``d=1``. + + References + ---------- + .. [1] Sampling (statistics), *Wikipedia*, + https://en.wikipedia.org/wiki/Sampling_(statistics) + + Examples + -------- + Instantiate a distribution with the desired parameters: + + >>> import numpy as np + >>> from scipy import stats + >>> X = stats.Uniform(a=0., b=1.) + + Generate a pseudorandom sample: + + >>> x = X.sample((1000, 1)) + >>> octiles = (np.arange(8) + 1) / 8 + >>> np.count_nonzero(x <= octiles, axis=0) + array([ 148, 263, 387, 516, 636, 751, 865, 1000]) # may vary + + >>> X = stats.Uniform(a=np.zeros((3, 1)), b=np.ones(2)) + >>> X.a.shape, + (3, 2) + >>> x = X.sample(shape=(5, 4)) + >>> x.shape + (5, 4, 3, 2) + + """ + raise NotImplementedError() + + @abstractmethod + def moment(self, order, kind, *, method): + r"""Raw, central, or standard moment of positive integer order. + + In terms of probability density function :math:`f(x)` and support + :math:`\chi`, the "raw" moment (about the origin) of order :math:`n` of + a continuous random variable :math:`X` is: + + .. math:: + + \mu'_n(X) = \int_{\chi} x^n f(x) dx + + The "central" moment is the raw moment taken about the mean, + :math:`\mu = \mu'_1`: + + .. math:: + + \mu_n(X) = \int_{\chi} (x - \mu) ^n f(x) dx + + The "standardized" moment is the central moment normalized by the + :math:`n^\text{th}` power of the standard deviation + :math:`\sigma = \sqrt{\mu_2}` to produce a scale invariant quantity: + + .. math:: + + \tilde{\mu}_n(X) = \frac{\mu_n(X)} + {\sigma^n} + + The definitions for discrete random variables are analogous, with + sums over the support replacing the integrals. + + Parameters + ---------- + order : int + The integer order of the moment; i.e. :math:`n` in the formulae above. + kind : {'raw', 'central', 'standardized'} + Whether to return the raw (default), central, or standardized moment + defined above. + method : {None, 'formula', 'general', 'transform', 'normalize', 'quadrature', 'cache'} + The strategy used to evaluate the moment. By default (``None``), + the infrastructure chooses between the following options, + listed in order of precedence. + + - ``'cache'``: use the value of the moment most recently calculated + via another method + - ``'formula'``: use a formula for the moment itself + - ``'general'``: use a general result that is true for all distributions + with finite moments; for instance, the zeroth raw moment is + identically 1 + - ``'transform'``: transform a raw moment to a central moment or + vice versa (see Notes) + - ``'normalize'``: normalize a central moment to get a standardized + or vice versa + - ``'quadrature'``: numerically integrate (or, in the discrete case, sum) + according to the definition + + Not all `method` options are available for all orders, kinds, and + distributions. If the selected `method` is not available, a + ``NotImplementedError`` will be raised. + + Returns + ------- + out : array + The moment of the random variable of the specified order and kind. + + See Also + -------- + pdf + mean + variance + standard_deviation + skewness + kurtosis + lmoment + + Notes + ----- + Not all distributions have finite moments of all orders; moments of some + orders may be undefined or infinite. If a formula for the moment is not + specifically implemented for the chosen distribution, SciPy will attempt + to compute the moment via a generic method, which may yield a finite + result where none exists. This is not a critical bug, but an opportunity + for an enhancement. + + The definition of a raw moment in the summary is specific to the raw moment + about the origin. The raw moment about any point :math:`a` is: + + .. math:: + + E[(X-a)^n] = \int_{\chi} (x-a)^n f(x) dx + + In this notation, a raw moment about the origin is :math:`\mu'_n = E[x^n]`, + and a central moment is :math:`\mu_n = E[(x-\mu)^n]`, where :math:`\mu` + is the first raw moment; i.e. the mean. + + The ``'transform'`` method takes advantage of the following relationships + between moments taken about different points :math:`a` and :math:`b`. + + .. math:: + + E[(X-b)^n] = \sum_{i=0}^n E[(X-a)^i] {n \choose i} (a - b)^{n-i} + + For instance, to transform the raw moment to the central moment, we let + :math:`b = \mu` and :math:`a = 0`. + + The distribution infrastructure provides flexibility for distribution + authors to implement separate formulas for raw moments, central moments, + and standardized moments of any order. By default, the moment of the + desired order and kind is evaluated from the formula if such a formula + is available; if not, the infrastructure uses any formulas that are + available rather than resorting directly to numerical integration. + For instance, if formulas for the first three raw moments are + available and the third standardized moments is desired, the + infrastructure will evaluate the raw moments and perform the transforms + and standardization required. The decision tree is somewhat complex, + but the strategy for obtaining a moment of a given order and kind + (possibly as an intermediate step due to the recursive nature of the + transform formula above) roughly follows this order of priority: + + #. Use cache (if order of same moment and kind has been calculated) + #. Use formula (if available) + #. Transform between raw and central moment and/or normalize to convert + between central and standardized moments (if efficient) + #. Use a generic result true for most distributions (if available) + #. Use quadrature + + References + ---------- + .. [1] Moment, *Wikipedia*, + https://en.wikipedia.org/wiki/Moment_(mathematics) + + Examples + -------- + Instantiate a distribution with the desired parameters: + + >>> from scipy import stats + >>> X = stats.Normal(mu=1., sigma=2.) + + Evaluate the first raw moment: + + >>> X.moment(order=1, kind='raw') + 1.0 + >>> X.moment(order=1, kind='raw') == X.mean() == X.mu + True + + Evaluate the second central moment: + + >>> X.moment(order=2, kind='central') + 4.0 + >>> X.moment(order=2, kind='central') == X.variance() == X.sigma**2 + True + + Evaluate the fourth standardized moment: + + >>> X.moment(order=4, kind='standardized') + 3.0 + >>> X.moment(order=4, kind='standardized') == X.kurtosis(convention='non-excess') + True + + """ # noqa:E501 + raise NotImplementedError() + + @abstractmethod + def lmoment(self, order, *, standardize, method): + r"""L-moment or L-moment ratio of positive integer order. + + The L-moment of order :math:`n` of a continuous random variable :math:`X` is: + + .. math:: + + \lambda_n(X) = \frac{1}{n} \sum_{k=0}^{n-1} (-1)^{k} {{n-1}\choose k} E[X_{(n-k)}] + + where :math:`X_{(1)}, \dots, X_{(r)}, \dots, X_{(n)}` are the order statistics + of an independent sample of size :math:`n`. + + The L-moment can also be expressed in terms of the random variable's inverse + cumulative distribution function :math:`F^{-1}` and the shifted Legendre + polynomial :math:`\widetilde{P}_{n-1}`: + + .. math:: + + \lambda_n(X) = \int_0^1 F^{-1}(p) \widetilde{P}_{n-1}(p) dp + + For order :math:`n \geq 3`, the "standardized" L-moment, known as the L-moment + ratio, is the L-moment normalized by the L-moment of order 2, resulting in a + scale invariant quantity: + + .. math:: + + \tau_n(X) = \frac{\lambda_n(X)} + {\lambda_2(X)} + + Parameters + ---------- + order : int + The positive integer order of the L-moment; i.e. :math:`n` in the formulae + above. + standardize : bool, default: True + Whether to return L-moment ratios for orders 3 and higher. L-moment ratios + are analogous to standardized conventional moments: they are the + non-standardized L-moments divided by the L-moment of order 2. + method : {None, 'formula', 'general', 'order_statistics', 'quadrature_icdf', 'cache'} + The strategy used to evaluate the L-moment. By default (``None``), + the infrastructure chooses between the following options, + listed in order of precedence. + + - ``'cache'``: use the value of the L-moment most recently calculated + via another method + - ``'formula'``: use a formula specific to the distribution. + - ``'general'``: use a general result that is true for all distributions + with finite L-moments; for instance, the first L-moment is identically + equal to the mean. + - ``'quadrature_icdf'``: numerically integrate according to the definition + in terms of the inverse cumulative distribution function. + - ``'order_statistics'``: compute according to the definition in terms of + order statistics. + + Not all `method` options are available for all orders and distributions. + If the selected `method` is not available, a ``NotImplementedError`` will + be raised. + + Returns + ------- + out : array + The L-moment of the random variable of the specified order. + + See Also + -------- + moment + order_statistic + + Notes + ----- + L-moments are only defined for distributions with finite mean. If a formula for + the L-moment is not specifically implemented for the chosen distribution, SciPy + will attempt to compute the moment via a generic method, which may yield a + finite result where none exists. This is not a critical bug, but an opportunity + for an enhancement. + + SciPy offers only basic capabilities for working with L-moments. For more advanced + features, consider the ``lmo`` package [2]_. + + References + ---------- + .. [1] L-moment, *Wikipedia*, + https://en.wikipedia.org/wiki/L-moment + .. [2] @jorenham, *Lmo*, https://github.com/jorenham/Lmo/ + + Examples + -------- + Instantiate a distribution with the desired parameters: + + >>> import numpy as np + >>> from scipy import stats + >>> X = stats.Normal(mu=1., sigma=2.) + + Evaluate the first L-moment: + + >>> X.lmoment(order=1) + 1.0 + >>> X.lmoment(order=1) == X.mean() == X.mu + True + + Evaluate the second L-moment: + + >>> X.lmoment(order=2) + np.float64(1.1283791670955123) + >>> np.allclose(X.lmoment(order=2), X.sigma / np.sqrt(np.pi)) + True + + Evaluate the fourth L-moment ratio, that is, the L-kurtosis: + + >>> X.lmoment(order=4) + np.float64(0.12260171954089069) + >>> X.lmoment(order=4) == X.lmoment(order=4, standardize=False) / X.lmoment(order=2) + True + + """ # noqa:E501 + raise NotImplementedError() + + @abstractmethod + def mean(self, *, method): + r"""Mean (raw first moment about the origin) + + Parameters + ---------- + method : {None, 'formula', 'transform', 'quadrature', 'cache'} + Method used to calculate the raw first moment. Not + all methods are available for all distributions. See + `moment` for details. + + See Also + -------- + moment + median + mode + + References + ---------- + .. [1] Mean, *Wikipedia*, + https://en.wikipedia.org/wiki/Mean#Mean_of_a_probability_distribution + + Examples + -------- + Instantiate a distribution with the desired parameters: + + >>> from scipy import stats + >>> X = stats.Normal(mu=1., sigma=2.) + + Evaluate the variance: + + >>> X.mean() + 1.0 + >>> X.mean() == X.moment(order=1, kind='raw') == X.mu + True + + """ + raise NotImplementedError() + + @abstractmethod + def median(self, *, method): + r"""Median (50th percentile) + + If a continuous random variable :math:`X` has probability :math:`0.5` of + taking on a value less than :math:`m`, then :math:`m` is the median. + + More generally, a median is a value :math:`m` for which: + + .. math:: + + P(X ≤ m) ≤ 0.5 ≥ P(X ≥ m) + + For discrete random variables, the median may not be unique, in which + case the smallest value satisfying the definition is reported. + + Parameters + ---------- + method : {None, 'formula', 'icdf'} + The strategy used to evaluate the median. + By default (``None``), the infrastructure chooses between the + following options, listed in order of precedence. + + - ``'formula'``: use a formula for the median + - ``'icdf'``: evaluate the inverse CDF of 0.5 + + Not all `method` options are available for all distributions. + If the selected `method` is not available, a ``NotImplementedError`` + will be raised. + + Returns + ------- + out : array + The median + + See Also + -------- + mean + mode + icdf + + References + ---------- + .. [1] Median, *Wikipedia*, + https://en.wikipedia.org/wiki/Median#Probability_distributions + + Examples + -------- + Instantiate a distribution with the desired parameters: + + >>> from scipy import stats + >>> X = stats.Uniform(a=0., b=10.) + + Compute the median: + + >>> X.median() + np.float64(5.0) + >>> X.median() == X.icdf(0.5) == X.iccdf(0.5) + True + + """ + raise NotImplementedError() + + @abstractmethod + def mode(self, *, method): + r"""Mode (most likely value) + + Informally, the mode is a value that a random variable has the highest + probability (density) of assuming. That is, the mode is the element of + the support :math:`\chi` that maximizes the probability density (or mass, + for discrete random variables) function :math:`f(x)`: + + .. math:: + + \text{mode} = \arg\max_{x \in \chi} f(x) + + Parameters + ---------- + method : {None, 'formula', 'optimization'} + The strategy used to evaluate the mode. + By default (``None``), the infrastructure chooses between the + following options, listed in order of precedence. + + - ``'formula'``: use a formula for the median + - ``'optimization'``: numerically maximize the PDF/PMF + + Not all `method` options are available for all distributions. + If the selected `method` is not available, a ``NotImplementedError`` + will be raised. + + Returns + ------- + out : array + The mode + + See Also + -------- + mean + median + pdf + + Notes + ----- + For some distributions + + #. the mode is not unique (e.g. the uniform distribution); + #. the PDF has one or more singularities, and it is debatable whether + a singularity is considered to be in the domain and called the mode + (e.g. the gamma distribution with shape parameter less than 1); and/or + #. the probability density function may have one or more local maxima + that are not a global maximum (e.g. mixture distributions). + + In such cases, `mode` will + + #. return a single value, + #. consider the mode to occur at a singularity, and/or + #. return a local maximum which may or may not be a global maximum. + + If a formula for the mode is not specifically implemented for the + chosen distribution, SciPy will attempt to compute the mode + numerically, which may not meet the user's preferred definition of a + mode. In such cases, the user is encouraged to subclass the + distribution and override ``mode``. + + References + ---------- + .. [1] Mode (statistics), *Wikipedia*, + https://en.wikipedia.org/wiki/Mode_(statistics) + + Examples + -------- + Instantiate a distribution with the desired parameters: + + >>> from scipy import stats + >>> X = stats.Normal(mu=1., sigma=2.) + + Evaluate the mode: + + >>> X.mode() + 1.0 + + If the mode is not uniquely defined, ``mode`` nonetheless returns a + single value. + + >>> X = stats.Uniform(a=0., b=1.) + >>> X.mode() + 0.5 + + If this choice does not satisfy your requirements, subclass the + distribution and override ``mode``: + + >>> class BetterUniform(stats.Uniform): + ... def mode(self): + ... return self.b + >>> X = BetterUniform(a=0., b=1.) + >>> X.mode() + 1.0 + + """ + raise NotImplementedError() + + @abstractmethod + def variance(self, *, method): + r"""Variance (central second moment) + + Parameters + ---------- + method : {None, 'formula', 'transform', 'normalize', 'quadrature', 'cache'} + Method used to calculate the central second moment. Not + all methods are available for all distributions. See + `moment` for details. + + See Also + -------- + moment + standard_deviation + mean + + References + ---------- + .. [1] Variance, *Wikipedia*, + https://en.wikipedia.org/wiki/Variance#Absolutely_continuous_random_variable + + Examples + -------- + Instantiate a distribution with the desired parameters: + + >>> from scipy import stats + >>> X = stats.Normal(mu=1., sigma=2.) + + Evaluate the variance: + + >>> X.variance() + 4.0 + >>> X.variance() == X.moment(order=2, kind='central') == X.sigma**2 + True + + """ + raise NotImplementedError() + + @abstractmethod + def standard_deviation(self, *, method): + r"""Standard deviation (square root of the second central moment) + + Parameters + ---------- + method : {None, 'formula', 'transform', 'normalize', 'quadrature', 'cache'} + Method used to calculate the central second moment. Not + all methods are available for all distributions. See + `moment` for details. + + See Also + -------- + variance + mean + moment + + References + ---------- + .. [1] Standard deviation, *Wikipedia*, + https://en.wikipedia.org/wiki/Standard_deviation#Definition_of_population_values + + Examples + -------- + Instantiate a distribution with the desired parameters: + + >>> from scipy import stats + >>> X = stats.Normal(mu=1., sigma=2.) + + Evaluate the standard deviation: + + >>> X.standard_deviation() + 2.0 + >>> X.standard_deviation() == X.moment(order=2, kind='central')**0.5 == X.sigma + True + + """ + raise NotImplementedError() + + @abstractmethod + def skewness(self, *, method): + r"""Skewness (standardized third moment) + + Parameters + ---------- + method : {None, 'formula', 'general', 'transform', 'normalize', 'cache'} + Method used to calculate the standardized third moment. Not + all methods are available for all distributions. See + `moment` for details. + + See Also + -------- + moment + mean + variance + + References + ---------- + .. [1] Skewness, *Wikipedia*, + https://en.wikipedia.org/wiki/Skewness + + Examples + -------- + Instantiate a distribution with the desired parameters: + + >>> from scipy import stats + >>> X = stats.Normal(mu=1., sigma=2.) + + Evaluate the skewness: + + >>> X.skewness() + 0.0 + >>> X.skewness() == X.moment(order=3, kind='standardized') + True + + """ + raise NotImplementedError() + + @abstractmethod + def kurtosis(self, *, method): + r"""Kurtosis (standardized fourth moment) + + By default, this is the standardized fourth moment, also known as the + "non-excess" or "Pearson" kurtosis (e.g. the kurtosis of the normal + distribution is 3). The "excess" or "Fisher" kurtosis (the standardized + fourth moment minus 3) is available via the `convention` parameter. + + Parameters + ---------- + method : {None, 'formula', 'general', 'transform', 'normalize', 'cache'} + Method used to calculate the standardized fourth moment. Not + all methods are available for all distributions. See + `moment` for details. + convention : {'non-excess', 'excess'} + Two distinct conventions are available: + + - ``'non-excess'``: the standardized fourth moment (Pearson's kurtosis) + - ``'excess'``: the standardized fourth moment minus 3 (Fisher's kurtosis) + + The default is ``'non-excess'``. + + See Also + -------- + moment + mean + variance + + References + ---------- + .. [1] Kurtosis, *Wikipedia*, + https://en.wikipedia.org/wiki/Kurtosis + + Examples + -------- + Instantiate a distribution with the desired parameters: + + >>> from scipy import stats + >>> X = stats.Normal(mu=1., sigma=2.) + + Evaluate the kurtosis: + + >>> X.kurtosis() + 3.0 + >>> (X.kurtosis() + ... == X.kurtosis(convention='excess') + 3. + ... == X.moment(order=4, kind='standardized')) + True + + """ + raise NotImplementedError() + + @abstractmethod + def pdf(self, x, /, *, method): + r"""Probability density function + + The probability density function ("PDF"), denoted :math:`f(x)`, is the + probability *per unit length* that the random variable will assume the + value :math:`x`. Mathematically, it can be defined as the derivative + of the cumulative distribution function :math:`F(x)`: + + .. math:: + + f(x) = \frac{d}{dx} F(x) + + `pdf` accepts `x` for :math:`x`. + + Parameters + ---------- + x : array_like + The argument of the PDF. + method : {None, 'formula', 'logexp'} + The strategy used to evaluate the PDF. By default (``None``), the + infrastructure chooses between the following options, listed in + order of precedence. + + - ``'formula'``: use a formula for the PDF itself + - ``'logexp'``: evaluate the log-PDF and exponentiate + + Not all `method` options are available for all distributions. + If the selected `method` is not available, a ``NotImplementedError`` + will be raised. + + Returns + ------- + out : array + The PDF evaluated at the argument `x`. + + See Also + -------- + cdf + logpdf + + Notes + ----- + Suppose a continuous probability distribution has support :math:`[l, r]`. + By definition of the support, the PDF evaluates to its minimum value + of :math:`0` outside the support; i.e. for :math:`x < l` or + :math:`x > r`. The maximum of the PDF may be less than or greater than + :math:`1`; since the value is a probability *density*, only its integral + over the support must equal :math:`1`. + + For discrete distributions, `pdf` returns ``inf`` at supported points + and ``0`` elsewhere. + + References + ---------- + .. [1] Probability density function, *Wikipedia*, + https://en.wikipedia.org/wiki/Probability_density_function + + Examples + -------- + Instantiate a distribution with the desired parameters: + + >>> from scipy import stats + >>> X = stats.Uniform(a=-1., b=1.) + + Evaluate the PDF at the desired argument: + + >>> X.pdf(0.25) + 0.5 + + """ + raise NotImplementedError() + + @abstractmethod + def logpdf(self, x, /, *, method): + r"""Log of the probability density function + + The probability density function ("PDF"), denoted :math:`f(x)`, is the + probability *per unit length* that the random variable will assume the + value :math:`x`. Mathematically, it can be defined as the derivative + of the cumulative distribution function :math:`F(x)`: + + .. math:: + + f(x) = \frac{d}{dx} F(x) + + `logpdf` computes the logarithm of the probability density function + ("log-PDF"), :math:`\log(f(x))`, but it may be numerically favorable + compared to the naive implementation (computing :math:`f(x)` and + taking the logarithm). + + `logpdf` accepts `x` for :math:`x`. + + Parameters + ---------- + x : array_like + The argument of the log-PDF. + method : {None, 'formula', 'logexp'} + The strategy used to evaluate the log-PDF. By default (``None``), the + infrastructure chooses between the following options, listed in order + of precedence. + + - ``'formula'``: use a formula for the log-PDF itself + - ``'logexp'``: evaluate the PDF and takes its logarithm + + Not all `method` options are available for all distributions. + If the selected `method` is not available, a ``NotImplementedError`` + will be raised. + + Returns + ------- + out : array + The log-PDF evaluated at the argument `x`. + + See Also + -------- + pdf + logcdf + + Notes + ----- + Suppose a continuous probability distribution has support :math:`[l, r]`. + By definition of the support, the log-PDF evaluates to its minimum value + of :math:`-\infty` (i.e. :math:`\log(0)`) outside the support; i.e. for + :math:`x < l` or :math:`x > r`. The maximum of the log-PDF may be less + than or greater than :math:`\log(1) = 0` because the maximum of the PDF + can be any positive real. + + For distributions with infinite support, it is common for `pdf` to return + a value of ``0`` when the argument is theoretically within the support; + this can occur because the true value of the PDF is too small to be + represented by the chosen dtype. The log-PDF, however, will often be finite + (not ``-inf``) over a much larger domain. Consequently, it may be preferred + to work with the logarithms of probabilities and probability densities to + avoid underflow. + + For discrete distributions, `logpdf` returns ``inf`` at supported points and + ``-inf`` (``log(0)``) elsewhere. + + References + ---------- + .. [1] Probability density function, *Wikipedia*, + https://en.wikipedia.org/wiki/Probability_density_function + + Examples + -------- + Instantiate a distribution with the desired parameters: + + >>> import numpy as np + >>> from scipy import stats + >>> X = stats.Uniform(a=-1.0, b=1.0) + + Evaluate the log-PDF at the desired argument: + + >>> X.logpdf(0.5) + -0.6931471805599453 + >>> np.allclose(X.logpdf(0.5), np.log(X.pdf(0.5))) + True + + """ + raise NotImplementedError() + + def pmf(self, x, /, *, method=None): + r"""Probability mass function + + The probability mass function ("PMF"), denoted :math:`f(x)`, is the + probability that the random variable :math:`X` will assume the value :math:`x`. + + .. math:: + + f(x) = P(X = x) + + `pmf` accepts `x` for :math:`x`. + + Parameters + ---------- + x : array_like + The argument of the PMF. + method : {None, 'formula', 'logexp'} + The strategy used to evaluate the PMF. By default (``None``), the + infrastructure chooses between the following options, listed in + order of precedence. + + - ``'formula'``: use a formula for the PMF itself + - ``'logexp'``: evaluate the log-PMF and exponentiate + + Not all `method` options are available for all distributions. + If the selected `method` is not available, a ``NotImplementedError`` + will be raised. + + Returns + ------- + out : array + The PMF evaluated at the argument `x`. + + See Also + -------- + cdf + logpmf + + Notes + ----- + Suppose a discrete probability distribution has support over the integers + :math:`{l, l+1, ..., r-1, r}`. + By definition of the support, the PMF evaluates to its minimum value + of :math:`0` for non-integral :math:`x` and for :math:`x` outside the support; + i.e. for :math:`x < l` or :math:`x > r`. + + For continuous distributions, `pmf` returns ``0`` at all real arguments. + + References + ---------- + .. [1] Probability mass function, *Wikipedia*, + https://en.wikipedia.org/wiki/Probability_mass_function + + Examples + -------- + Instantiate a distribution with the desired parameters: + + >>> from scipy import stats + >>> X = stats.Binomial(n=10, p=0.5) + + Evaluate the PMF at the desired argument: + + >>> X.pmf(5) + np.float64(0.24609375) + + """ + raise NotImplementedError() + + def logpmf(self, x, /, *, method=None): + r"""Log of the probability mass function + + The probability mass function ("PMF"), denoted :math:`f(x)`, is the + probability that the random variable :math:`X` will assume the value :math:`x`. + + .. math:: + + f(x) = \frac{d}{dx} F(x) + + `logpmf` computes the logarithm of the probability mass function + ("log-PMF"), :math:`\log(f(x))`, but it may be numerically favorable + compared to the naive implementation (computing :math:`f(x)` and + taking the logarithm). + + `logpmf` accepts `x` for :math:`x`. + + Parameters + ---------- + x : array_like + The argument of the log-PMF. + method : {None, 'formula', 'logexp'} + The strategy used to evaluate the log-PMF. By default (``None``), the + infrastructure chooses between the following options, listed in order + of precedence. + + - ``'formula'``: use a formula for the log-PMF itself + - ``'logexp'``: evaluate the PMF and takes its logarithm + + Not all `method` options are available for all distributions. + If the selected `method` is not available, a ``NotImplementedError`` + will be raised. + + Returns + ------- + out : array + The log-PMF evaluated at the argument `x`. + + See Also + -------- + pmf + logcdf + + Notes + ----- + Suppose a discrete probability distribution has support over the integers + :math:`{l, l+1, ..., r-1, r}`. + By definition of the support, the log-PMF evaluates to its minimum value + of :math:`-\infty` (i.e. :math:`\log(0)`) for non-integral :math:`x` and + for :math:`x` outside the support; i.e. for :math:`x < l` or :math:`x > r`. + + For distributions with infinite support, it is common for `pmf` to return + a value of ``0`` when the argument is theoretically within the support; + this can occur because the true value of the PMF is too small to be + represented by the chosen dtype. The log-PMF, however, will often be finite + (not ``-inf``) over a much larger domain. Consequently, it may be preferred + to work with the logarithms of probabilities and probability densities to + avoid underflow. + + References + ---------- + .. [1] Probability density function, *Wikipedia*, + https://en.wikipedia.org/wiki/Probability_density_function + + Examples + -------- + Instantiate a distribution with the desired parameters: + + >>> import numpy as np + >>> from scipy import stats + >>> X = stats.Binomial(n=10, p=0.5) + + Evaluate the log-PMF at the desired argument: + + >>> X.logpmf(5) + np.float64(-1.4020427180880297) + >>> np.allclose(X.logpmf(5), np.log(X.pmf(5))) + True + + """ + raise NotImplementedError() + + @abstractmethod + def cdf(self, x, y, /, *, method): + r"""Cumulative distribution function + + The cumulative distribution function ("CDF"), denoted :math:`F(x)`, is + the probability the random variable :math:`X` will assume a value + less than or equal to :math:`x`: + + .. math:: + + F(x) = P(X ≤ x) + + A two-argument variant of this function is also defined as the + probability the random variable :math:`X` will assume a value between + :math:`x` and :math:`y`. + + .. math:: + + F(x, y) = P(x ≤ X ≤ y) + + `cdf` accepts `x` for :math:`x` and `y` for :math:`y`. + + Parameters + ---------- + x, y : array_like + The arguments of the CDF. `x` is required; `y` is optional. + method : {None, 'formula', 'logexp', 'complement', 'quadrature', 'subtraction'} + The strategy used to evaluate the CDF. + By default (``None``), the one-argument form of the function + chooses between the following options, listed in order of precedence. + + - ``'formula'``: use a formula for the CDF itself + - ``'logexp'``: evaluate the log-CDF and exponentiate + - ``'complement'``: evaluate the CCDF and take the complement + - ``'quadrature'``: numerically integrate the PDF (or, in the discrete + case, sum the PMF) + + In place of ``'complement'``, the two-argument form accepts: + + - ``'subtraction'``: compute the CDF at each argument and take + the difference. + + Not all `method` options are available for all distributions. + If the selected `method` is not available, a ``NotImplementedError`` + will be raised. + + Returns + ------- + out : array + The CDF evaluated at the provided argument(s). + + See Also + -------- + logcdf + ccdf + + Notes + ----- + Suppose a continuous probability distribution has support :math:`[l, r]`. + The CDF :math:`F(x)` is related to the probability density function + :math:`f(x)` by: + + .. math:: + + F(x) = \int_l^x f(u) du + + The two argument version is: + + .. math:: + + F(x, y) = \int_x^y f(u) du = F(y) - F(x) + + The CDF evaluates to its minimum value of :math:`0` for :math:`x ≤ l` + and its maximum value of :math:`1` for :math:`x ≥ r`. + + Suppose a discrete probability distribution has support :math:`[l, r]`. + The CDF :math:`F(x)` is related to the probability mass function + :math:`f(x)` by: + + .. math:: + + F(x) = \sum_{u=l}^{\lfloor x \rfloor} f(u) + + The CDF evaluates to its minimum value of :math:`0` for :math:`x < l` + and its maximum value of :math:`1` for :math:`x ≥ r`. + + The CDF is also known simply as the "distribution function". + + References + ---------- + .. [1] Cumulative distribution function, *Wikipedia*, + https://en.wikipedia.org/wiki/Cumulative_distribution_function + + Examples + -------- + Instantiate a distribution with the desired parameters: + + >>> from scipy import stats + >>> X = stats.Uniform(a=-0.5, b=0.5) + + Evaluate the CDF at the desired argument: + + >>> X.cdf(0.25) + 0.75 + + Evaluate the cumulative probability between two arguments: + + >>> X.cdf(-0.25, 0.25) == X.cdf(0.25) - X.cdf(-0.25) + True + + """ # noqa: E501 + raise NotImplementedError() + + @abstractmethod + def icdf(self, p, /, *, method): + r"""Inverse of the cumulative distribution function. + + For monotonic continuous distributions, the inverse of the cumulative + distribution function ("inverse CDF"), denoted :math:`F^{-1}(p)`, is the + argument :math:`x` for which the cumulative distribution function + :math:`F(x)` evaluates to :math:`p`. + + .. math:: + + F^{-1}(p) = x \quad \text{s.t.} \quad F(x) = p + + When a strict "inverse" of the cumulative distribution function does not + exist (e.g. discrete random variables), the "inverse CDF" is defined by + convention as the smallest value within the support :math:`\chi` for which + :math:`F(x)` is at least :math:`p`. + + .. math:: + + F^{-1}(p) = \min_\chi \quad \text{s.t.} \quad F(x) ≥ p + + `icdf` accepts `p` for :math:`p \in [0, 1]`. + + Parameters + ---------- + p : array_like + The argument of the inverse CDF. + method : {None, 'formula', 'complement', 'inversion'} + The strategy used to evaluate the inverse CDF. + By default (``None``), the infrastructure chooses between the + following options, listed in order of precedence. + + - ``'formula'``: use a formula for the inverse CDF itself + - ``'complement'``: evaluate the inverse CCDF at the + complement of `p` + - ``'inversion'``: solve numerically for the argument at which the + CDF is equal to `p` + + Not all `method` options are available for all distributions. + If the selected `method` is not available, a ``NotImplementedError`` + will be raised. + + Returns + ------- + out : array + The inverse CDF evaluated at the provided argument. + + See Also + -------- + cdf + ilogcdf + + Notes + ----- + Suppose a probability distribution has support :math:`[l, r]`. The + inverse CDF returns its minimum value of :math:`l` at :math:`p = 0` + and its maximum value of :math:`r` at :math:`p = 1`. Because the CDF + has range :math:`[0, 1]`, the inverse CDF is only defined on the + domain :math:`[0, 1]`; for :math:`p < 0` and :math:`p > 1`, `icdf` + returns ``nan``. + + The inverse CDF is also known as the quantile function, percentile function, + and percent-point function. + + References + ---------- + .. [1] Quantile function, *Wikipedia*, + https://en.wikipedia.org/wiki/Quantile_function + + Examples + -------- + Instantiate a distribution with the desired parameters: + + >>> import numpy as np + >>> from scipy import stats + >>> X = stats.Uniform(a=-0.5, b=0.5) + + Evaluate the inverse CDF at the desired argument: + + >>> X.icdf(0.25) + -0.25 + >>> np.allclose(X.cdf(X.icdf(0.25)), 0.25) + True + + This function returns NaN when the argument is outside the domain. + + >>> X.icdf([-0.1, 0, 1, 1.1]) + array([ nan, -0.5, 0.5, nan]) + + """ + raise NotImplementedError() + + @abstractmethod + def ccdf(self, x, y, /, *, method): + r"""Complementary cumulative distribution function + + The complementary cumulative distribution function ("CCDF"), denoted + :math:`G(x)`, is the complement of the cumulative distribution function + :math:`F(x)`; i.e., probability the random variable :math:`X` will + assume a value greater than :math:`x`: + + .. math:: + + G(x) = 1 - F(x) = P(X > x) + + A two-argument variant of this function is: + + .. math:: + + G(x, y) = 1 - F(x, y) = P(X < x \text{ or } X > y) + + `ccdf` accepts `x` for :math:`x` and `y` for :math:`y`. + + Parameters + ---------- + x, y : array_like + The arguments of the CCDF. `x` is required; `y` is optional. + method : {None, 'formula', 'logexp', 'complement', 'quadrature', 'addition'} + The strategy used to evaluate the CCDF. + By default (``None``), the infrastructure chooses between the + following options, listed in order of precedence. + + - ``'formula'``: use a formula for the CCDF itself + - ``'logexp'``: evaluate the log-CCDF and exponentiate + - ``'complement'``: evaluate the CDF and take the complement + - ``'quadrature'``: numerically integrate the PDF (or, in the discrete + case, sum the PMF) + + The two-argument form chooses between: + + - ``'formula'``: use a formula for the CCDF itself + - ``'addition'``: compute the CDF at `x` and the CCDF at `y`, then add + + Not all `method` options are available for all distributions. + If the selected `method` is not available, a ``NotImplementedError`` + will be raised. + + Returns + ------- + out : array + The CCDF evaluated at the provided argument(s). + + See Also + -------- + cdf + logccdf + + Notes + ----- + Suppose a continuous probability distribution has support :math:`[l, r]`. + The CCDF :math:`G(x)` is related to the probability density function + :math:`f(x)` by: + + .. math:: + + G(x) = \int_x^r f(u) du + + The two argument version is: + + .. math:: + + G(x, y) = \int_l^x f(u) du + \int_y^r f(u) du + + The CCDF returns its minimum value of :math:`0` for :math:`x ≥ r` + and its maximum value of :math:`1` for :math:`x ≤ l`. + + Suppose a discrete probability distribution has support :math:`[l, r]`. + The CCDF :math:`G(x)` is related to the probability mass function + :math:`f(x)` by: + + .. math:: + + G(x) = \sum_{u=\lfloor x + 1 \rfloor}^{r} f(u) + + The CCDF evaluates to its minimum value of :math:`0` for :math:`x ≥ r` + and its maximum value of :math:`1` for :math:`x < l`. + + The CCDF is also known as the "survival function". + + References + ---------- + .. [1] Cumulative distribution function, *Wikipedia*, + https://en.wikipedia.org/wiki/Cumulative_distribution_function#Derived_functions + + Examples + -------- + Instantiate a distribution with the desired parameters: + + >>> import numpy as np + >>> from scipy import stats + >>> X = stats.Uniform(a=-0.5, b=0.5) + + Evaluate the CCDF at the desired argument: + + >>> X.ccdf(0.25) + 0.25 + >>> np.allclose(X.ccdf(0.25), 1-X.cdf(0.25)) + True + + Evaluate the complement of the cumulative probability between two arguments: + + >>> X.ccdf(-0.25, 0.25) == X.cdf(-0.25) + X.ccdf(0.25) + True + + """ # noqa: E501 + raise NotImplementedError() + + @abstractmethod + def iccdf(self, p, /, *, method): + r"""Inverse complementary cumulative distribution function. + + The inverse complementary cumulative distribution function ("inverse CCDF"), + denoted :math:`G^{-1}(p)`, is the argument :math:`x` for which the + complementary cumulative distribution function :math:`G(x)` evaluates to + :math:`p`. + + .. math:: + + G^{-1}(p) = x \quad \text{s.t.} \quad G(x) = p + + When a strict "inverse" of the complementary cumulative distribution function + does not exist (e.g. discrete random variables), the "inverse CCDF" is defined + by convention as the smallest value within the support :math:`\chi` for which + :math:`G(x)` is no greater than :math:`p`. + + .. math:: + + G^{-1}(p) = \min_\chi \quad \text{s.t.} \quad G(x) ≤ p + + `iccdf` accepts `p` for :math:`p \in [0, 1]`. + + Parameters + ---------- + p : array_like + The argument of the inverse CCDF. + method : {None, 'formula', 'complement', 'inversion'} + The strategy used to evaluate the inverse CCDF. + By default (``None``), the infrastructure chooses between the + following options, listed in order of precedence. + + - ``'formula'``: use a formula for the inverse CCDF itself + - ``'complement'``: evaluate the inverse CDF at the + complement of `p` + - ``'inversion'``: solve numerically for the argument at which the + CCDF is equal to `p` + + Not all `method` options are available for all distributions. + If the selected `method` is not available, a ``NotImplementedError`` + will be raised. + + Returns + ------- + out : array + The inverse CCDF evaluated at the provided argument. + + Notes + ----- + Suppose a probability distribution has support :math:`[l, r]`. The + inverse CCDF returns its minimum value of :math:`l` at :math:`p = 1` + and its maximum value of :math:`r` at :math:`p = 0`. Because the CCDF + has range :math:`[0, 1]`, the inverse CCDF is only defined on the + domain :math:`[0, 1]`; for :math:`p < 0` and :math:`p > 1`, ``iccdf`` + returns ``nan``. + + See Also + -------- + icdf + ilogccdf + + Examples + -------- + Instantiate a distribution with the desired parameters: + + >>> import numpy as np + >>> from scipy import stats + >>> X = stats.Uniform(a=-0.5, b=0.5) + + Evaluate the inverse CCDF at the desired argument: + + >>> X.iccdf(0.25) + 0.25 + >>> np.allclose(X.iccdf(0.25), X.icdf(1-0.25)) + True + + This function returns NaN when the argument is outside the domain. + + >>> X.iccdf([-0.1, 0, 1, 1.1]) + array([ nan, 0.5, -0.5, nan]) + + """ + raise NotImplementedError() + + @abstractmethod + def logcdf(self, x, y, /, *, method): + r"""Log of the cumulative distribution function + + The cumulative distribution function ("CDF"), denoted :math:`F(x)`, is + the probability the random variable :math:`X` will assume a value + less than or equal to :math:`x`: + + .. math:: + + F(x) = P(X ≤ x) + + A two-argument variant of this function is also defined as the + probability the random variable :math:`X` will assume a value between + :math:`x` and :math:`y`. + + .. math:: + + F(x, y) = P(x ≤ X ≤ y) + + `logcdf` computes the logarithm of the cumulative distribution function + ("log-CDF"), :math:`\log(F(x))`/:math:`\log(F(x, y))`, but it may be + numerically favorable compared to the naive implementation (computing + the CDF and taking the logarithm). + + `logcdf` accepts `x` for :math:`x` and `y` for :math:`y`. + + Parameters + ---------- + x, y : array_like + The arguments of the log-CDF. `x` is required; `y` is optional. + method : {None, 'formula', 'logexp', 'complement', 'quadrature', 'subtraction'} + The strategy used to evaluate the log-CDF. + By default (``None``), the one-argument form of the function + chooses between the following options, listed in order of precedence. + + - ``'formula'``: use a formula for the log-CDF itself + - ``'logexp'``: evaluate the CDF and take the logarithm + - ``'complement'``: evaluate the log-CCDF and take the + logarithmic complement (see Notes) + - ``'quadrature'``: numerically log-integrate the log-PDF (or, in the + discrete case, log-sum the log-PMF) + + In place of ``'complement'``, the two-argument form accepts: + + - ``'subtraction'``: compute the log-CDF at each argument and take + the logarithmic difference (see Notes) + + Not all `method` options are available for all distributions. + If the selected `method` is not available, a ``NotImplementedError`` + will be raised. + + Returns + ------- + out : array + The log-CDF evaluated at the provided argument(s). + + See Also + -------- + cdf + logccdf + + Notes + ----- + Suppose a continuous probability distribution has support :math:`[l, r]`. + The log-CDF evaluates to its minimum value of :math:`\log(0) = -\infty` + for :math:`x ≤ l` and its maximum value of :math:`\log(1) = 0` for + :math:`x ≥ r`. An analogous statement can be made for discrete distributions, + but the inequality governing the minimum value is strict. + + For distributions with infinite support, it is common for + `cdf` to return a value of ``0`` when the argument + is theoretically within the support; this can occur because the true value + of the CDF is too small to be represented by the chosen dtype. `logcdf`, + however, will often return a finite (not ``-inf``) result over a much larger + domain. Similarly, `logcdf` may provided a strictly negative result with + arguments for which `cdf` would return ``1.0``. Consequently, it may be + preferred to work with the logarithms of probabilities to avoid underflow + and related limitations of floating point numbers. + + The "logarithmic complement" of a number :math:`z` is mathematically + equivalent to :math:`\log(1-\exp(z))`, but it is computed to avoid loss + of precision when :math:`\exp(z)` is nearly :math:`0` or :math:`1`. + Similarly, the term "logarithmic difference" of :math:`w` and :math:`z` + is used here to mean :math:`\log(\exp(w)-\exp(z))`. + + If ``y < x``, the CDF is negative, and therefore the log-CCDF + is complex with imaginary part :math:`\pi`. For + consistency, the result of this function always has complex dtype + when `y` is provided, regardless of the value of the imaginary part. + + References + ---------- + .. [1] Cumulative distribution function, *Wikipedia*, + https://en.wikipedia.org/wiki/Cumulative_distribution_function + + Examples + -------- + Instantiate a distribution with the desired parameters: + + >>> import numpy as np + >>> from scipy import stats + >>> X = stats.Uniform(a=-0.5, b=0.5) + + Evaluate the log-CDF at the desired argument: + + >>> X.logcdf(0.25) + -0.287682072451781 + >>> np.allclose(X.logcdf(0.), np.log(X.cdf(0.))) + True + + """ # noqa: E501 + raise NotImplementedError() + + @abstractmethod + def ilogcdf(self, logp, /, *, method): + r"""Inverse of the logarithm of the cumulative distribution function. + + The inverse of the logarithm of the cumulative distribution function + ("inverse log-CDF") is the argument :math:`x` for which the logarithm + of the cumulative distribution function :math:`\log(F(x))` evaluates + to :math:`\log(p)`. + + Mathematically, it is equivalent to :math:`F^{-1}(\exp(y))`, where + :math:`y = \log(p)`, but it may be numerically favorable compared to + the naive implementation (computing :math:`p = \exp(y)`, then + :math:`F^{-1}(p)`). + + `ilogcdf` accepts `logp` for :math:`\log(p) ≤ 0`. + + Parameters + ---------- + logp : array_like + The argument of the inverse log-CDF. + method : {None, 'formula', 'complement', 'inversion'} + The strategy used to evaluate the inverse log-CDF. + By default (``None``), the infrastructure chooses between the + following options, listed in order of precedence. + + - ``'formula'``: use a formula for the inverse log-CDF itself + - ``'complement'``: evaluate the inverse log-CCDF at the + logarithmic complement of `logp` (see Notes) + - ``'inversion'``: solve numerically for the argument at which the + log-CDF is equal to `logp` + + Not all `method` options are available for all distributions. + If the selected `method` is not available, a ``NotImplementedError`` + will be raised. + + Returns + ------- + out : array + The inverse log-CDF evaluated at the provided argument. + + See Also + -------- + icdf + logcdf + + Notes + ----- + Suppose a probability distribution has support :math:`[l, r]`. + The inverse log-CDF returns its minimum value of :math:`l` at + :math:`\log(p) = \log(0) = -\infty` and its maximum value of :math:`r` at + :math:`\log(p) = \log(1) = 0`. Because the log-CDF has range + :math:`[-\infty, 0]`, the inverse log-CDF is only defined on the + negative reals; for :math:`\log(p) > 0`, `ilogcdf` returns ``nan``. + + Occasionally, it is needed to find the argument of the CDF for which + the resulting probability is very close to ``0`` or ``1`` - too close to + represent accurately with floating point arithmetic. In many cases, + however, the *logarithm* of this resulting probability may be + represented in floating point arithmetic, in which case this function + may be used to find the argument of the CDF for which the *logarithm* + of the resulting probability is :math:`y = \log(p)`. + + The "logarithmic complement" of a number :math:`z` is mathematically + equivalent to :math:`\log(1-\exp(z))`, but it is computed to avoid loss + of precision when :math:`\exp(z)` is nearly :math:`0` or :math:`1`. + + Examples + -------- + Instantiate a distribution with the desired parameters: + + >>> import numpy as np + >>> from scipy import stats + >>> X = stats.Uniform(a=-0.5, b=0.5) + + Evaluate the inverse log-CDF at the desired argument: + + >>> X.ilogcdf(-0.25) + 0.2788007830714034 + >>> np.allclose(X.ilogcdf(-0.25), X.icdf(np.exp(-0.25))) + True + + """ + raise NotImplementedError() + + @abstractmethod + def logccdf(self, x, y, /, *, method): + r"""Log of the complementary cumulative distribution function + + The complementary cumulative distribution function ("CCDF"), denoted + :math:`G(x)` is the complement of the cumulative distribution function + :math:`F(x)`; i.e., probability the random variable :math:`X` will + assume a value greater than :math:`x`: + + .. math:: + + G(x) = 1 - F(x) = P(X > x) + + A two-argument variant of this function is: + + .. math:: + + G(x, y) = 1 - F(x, y) = P(X < x \quad \text{or} \quad X > y) + + `logccdf` computes the logarithm of the complementary cumulative + distribution function ("log-CCDF"), :math:`\log(G(x))`/:math:`\log(G(x, y))`, + but it may be numerically favorable compared to the naive implementation + (computing the CDF and taking the logarithm). + + `logccdf` accepts `x` for :math:`x` and `y` for :math:`y`. + + Parameters + ---------- + x, y : array_like + The arguments of the log-CCDF. `x` is required; `y` is optional. + method : {None, 'formula', 'logexp', 'complement', 'quadrature', 'addition'} + The strategy used to evaluate the log-CCDF. + By default (``None``), the one-argument form of the function + chooses between the following options, listed in order of precedence. + + - ``'formula'``: use a formula for the log CCDF itself + - ``'logexp'``: evaluate the CCDF and take the logarithm + - ``'complement'``: evaluate the log-CDF and take the + logarithmic complement (see Notes) + - ``'quadrature'``: numerically log-integrate the log-PDF (or, in the + discrete case, log-sum the log-PMF) + + The two-argument form chooses between: + + - ``'formula'``: use a formula for the log CCDF itself + - ``'addition'``: compute the log-CDF at `x` and the log-CCDF at `y`, + then take the logarithmic sum (see Notes) + + Not all `method` options are available for all distributions. + If the selected `method` is not available, a ``NotImplementedError`` + will be raised. + + Returns + ------- + out : array + The log-CCDF evaluated at the provided argument(s). + + See Also + -------- + ccdf + logcdf + + Notes + ----- + Suppose a continuous probability distribution has support :math:`[l, r]`. + The log-CCDF returns its minimum value of :math:`\log(0)=-\infty` for + :math:`x ≥ r` and its maximum value of :math:`\log(1) = 0` for + :math:`x ≤ l`. An analogous statement can be made for discrete distributions, + but the inequality governing the maximum value is strict. + + For distributions with infinite support, it is common for + `ccdf` to return a value of ``0`` when the argument + is theoretically within the support; this can occur because the true value + of the CCDF is too small to be represented by the chosen dtype. The log + of the CCDF, however, will often be finite (not ``-inf``) over a much larger + domain. Similarly, `logccdf` may provided a strictly negative result with + arguments for which `ccdf` would return ``1.0``. Consequently, it may be + preferred to work with the logarithms of probabilities to avoid underflow + and related limitations of floating point numbers. + + The "logarithmic complement" of a number :math:`z` is mathematically + equivalent to :math:`\log(1-\exp(z))`, but it is computed to avoid loss + of precision when :math:`\exp(z)` is nearly :math:`0` or :math:`1`. + Similarly, the term "logarithmic sum" of :math:`w` and :math:`z` + is used here to mean the :math:`\log(\exp(w)+\exp(z))`, AKA + :math:`\text{LogSumExp}(w, z)`. + + References + ---------- + .. [1] Cumulative distribution function, *Wikipedia*, + https://en.wikipedia.org/wiki/Cumulative_distribution_function#Derived_functions + + Examples + -------- + Instantiate a distribution with the desired parameters: + + >>> import numpy as np + >>> from scipy import stats + >>> X = stats.Uniform(a=-0.5, b=0.5) + + Evaluate the log-CCDF at the desired argument: + + >>> X.logccdf(0.25) + -1.3862943611198906 + >>> np.allclose(X.logccdf(0.), np.log(X.ccdf(0.))) + True + + """ # noqa: E501 + raise NotImplementedError() + + @abstractmethod + def ilogccdf(self, logp, /, *, method): + r"""Inverse of the log of the complementary cumulative distribution function. + + The inverse of the logarithm of the complementary cumulative distribution + function ("inverse log-CCDF") is the argument :math:`x` for which the logarithm + of the complementary cumulative distribution function :math:`\log(G(x))` + evaluates to :math:`\log(p)`. + + Mathematically, it is equivalent to :math:`G^{-1}(\exp(y))`, where + :math:`y = \log(p)`, but it may be numerically favorable compared to the naive + implementation (computing :math:`p = \exp(y)`, then :math:`G^{-1}(p)`). + + `ilogccdf` accepts `logp` for :math:`\log(p) ≤ 0`. + + Parameters + ---------- + x : array_like + The argument of the inverse log-CCDF. + method : {None, 'formula', 'complement', 'inversion'} + The strategy used to evaluate the inverse log-CCDF. + By default (``None``), the infrastructure chooses between the + following options, listed in order of precedence. + + - ``'formula'``: use a formula for the inverse log-CCDF itself + - ``'complement'``: evaluate the inverse log-CDF at the + logarithmic complement of `x` (see Notes) + - ``'inversion'``: solve numerically for the argument at which the + log-CCDF is equal to `x` + + Not all `method` options are available for all distributions. + If the selected `method` is not available, a ``NotImplementedError`` + will be raised. + + Returns + ------- + out : array + The inverse log-CCDF evaluated at the provided argument. + + Notes + ----- + Suppose a probability distribution has support :math:`[l, r]`. The + inverse log-CCDF returns its minimum value of :math:`l` at + :math:`\log(p) = \log(1) = 0` and its maximum value of :math:`r` at + :math:`\log(p) = \log(0) = -\infty`. Because the log-CCDF has range + :math:`[-\infty, 0]`, the inverse log-CDF is only defined on the + negative reals; for :math:`\log(p) > 0`, `ilogccdf` returns ``nan``. + + Occasionally, it is needed to find the argument of the CCDF for which + the resulting probability is very close to ``0`` or ``1`` - too close to + represent accurately with floating point arithmetic. In many cases, + however, the *logarithm* of this resulting probability may be + represented in floating point arithmetic, in which case this function + may be used to find the argument of the CCDF for which the *logarithm* + of the resulting probability is :math:`y = \log(p)`. + + The "logarithmic complement" of a number :math:`z` is mathematically + equivalent to :math:`\log(1-\exp(z))`, but it is computed to avoid loss + of precision when :math:`\exp(z)` is nearly :math:`0` or :math:`1`. + + See Also + -------- + iccdf + ilogccdf + + Examples + -------- + Instantiate a distribution with the desired parameters: + + >>> import numpy as np + >>> from scipy import stats + >>> X = stats.Uniform(a=-0.5, b=0.5) + + Evaluate the inverse log-CCDF at the desired argument: + + >>> X.ilogccdf(-0.25) + -0.2788007830714034 + >>> np.allclose(X.ilogccdf(-0.25), X.iccdf(np.exp(-0.25))) + True + + """ + raise NotImplementedError() + + @abstractmethod + def logentropy(self, *, method): + r"""Logarithm of the differential entropy + + In terms of probability density function :math:`f(x)` and support + :math:`\chi`, the differential entropy (or simply "entropy") of a + continuous random variable :math:`X` is: + + .. math:: + + h(X) = - \int_{\chi} f(x) \log f(x) dx + + The definition for a discrete random variable is analogous, with the PMF + replacing the PDF and a sum over the support replacing the integral. + + `logentropy` computes the logarithm of the differential entropy + ("log-entropy"), :math:`\log(h(X))`, but it may be numerically favorable + compared to the naive implementation (computing :math:`h(X)` then + taking the logarithm). + + Parameters + ---------- + method : {None, 'formula', 'logexp', 'quadrature} + The strategy used to evaluate the log-entropy. By default + (``None``), the infrastructure chooses between the following options, + listed in order of precedence. + + - ``'formula'``: use a formula for the log-entropy itself + - ``'logexp'``: evaluate the entropy and take the logarithm + - ``'quadrature'``: numerically log-integrate (or, in the discrete + case, log-sum) the logarithm of the entropy integrand (summand) + + Not all `method` options are available for all distributions. + If the selected `method` is not available, a ``NotImplementedError`` + will be raised. + + Returns + ------- + out : array + The log-entropy. + + See Also + -------- + entropy + logpdf + + Notes + ----- + The differential entropy of a continuous distribution can be negative. + In this case, the log-entropy is complex with imaginary part :math:`\pi`. + For consistency, the result of this function always has complex dtype, + regardless of the value of the imaginary part. + + References + ---------- + .. [1] Differential entropy, *Wikipedia*, + https://en.wikipedia.org/wiki/Differential_entropy + + Examples + -------- + Instantiate a distribution with the desired parameters: + + >>> import numpy as np + >>> from scipy import stats + >>> X = stats.Uniform(a=-1., b=1.) + + Evaluate the log-entropy: + + >>> X.logentropy() + (-0.3665129205816642+0j) + >>> np.allclose(np.exp(X.logentropy()), X.entropy()) + True + + For a random variable with negative entropy, the log-entropy has an + imaginary part equal to `np.pi`. + + >>> X = stats.Uniform(a=-.1, b=.1) + >>> X.entropy(), X.logentropy() + (-1.6094379124341007, (0.4758849953271105+3.141592653589793j)) + + """ + raise NotImplementedError() + + @abstractmethod + def entropy(self, *, method): + r"""Differential entropy + + In terms of probability density function :math:`f(x)` and support + :math:`\chi`, the differential entropy (or simply "entropy") of a + continuous random variable :math:`X` is: + + .. math:: + + h(X) = - \int_{\chi} f(x) \log f(x) dx + + The definition for a discrete random variable is analogous, with the + PMF replacing the PDF and a sum over the support replacing the integral. + + Parameters + ---------- + method : {None, 'formula', 'logexp', 'quadrature'} + The strategy used to evaluate the entropy. By default (``None``), + the infrastructure chooses between the following options, listed + in order of precedence. + + - ``'formula'``: use a formula for the entropy itself + - ``'logexp'``: evaluate the log-entropy and exponentiate + - ``'quadrature'``: numerically integrate (or, in the discrete + case, sum) the entropy integrand (summand) + + + Not all `method` options are available for all distributions. + If the selected `method` is not available, a ``NotImplementedError`` + will be raised. + + Returns + ------- + out : array + The entropy of the random variable. + + See Also + -------- + logentropy + pdf + + Notes + ----- + This function calculates the entropy using the natural logarithm; i.e. + the logarithm with base :math:`e`. Consequently, the value is expressed + in (dimensionless) "units" of nats. To convert the entropy to different + units (i.e. corresponding with a different base), divide the result by + the natural logarithm of the desired base. + + References + ---------- + .. [1] Differential entropy, *Wikipedia*, + https://en.wikipedia.org/wiki/Differential_entropy + + Examples + -------- + Instantiate a distribution with the desired parameters: + + >>> from scipy import stats + >>> X = stats.Uniform(a=-1., b=1.) + + Evaluate the entropy: + + >>> X.entropy() + 0.6931471805599454 + + """ + raise NotImplementedError() diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_qmc.py b/venv/lib/python3.12/site-packages/scipy/stats/_qmc.py new file mode 100644 index 0000000000000000000000000000000000000000..3358bc68222035ddc02a5f7977041bd313f3d0a9 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_qmc.py @@ -0,0 +1,2951 @@ +"""Quasi-Monte Carlo engines and helpers.""" +import copy +import math +import numbers +import os +import warnings +from abc import ABC, abstractmethod +from functools import partial +from typing import ( + ClassVar, + Literal, + overload, + TYPE_CHECKING, +) +from collections.abc import Callable + +import numpy as np + +from scipy._lib._util import DecimalNumber, GeneratorType, IntNumber, SeedType + +if TYPE_CHECKING: + import numpy.typing as npt + +import scipy.stats as stats +from scipy._lib._util import rng_integers, _rng_spawn, _transition_to_rng +from scipy.sparse.csgraph import minimum_spanning_tree +from scipy.spatial import distance, Voronoi +from scipy.special import gammainc +from ._sobol import ( + _initialize_v, _cscramble, _fill_p_cumulative, _draw, _fast_forward, + _categorize, _MAXDIM +) +from ._qmc_cy import ( + _cy_wrapper_centered_discrepancy, + _cy_wrapper_wrap_around_discrepancy, + _cy_wrapper_mixture_discrepancy, + _cy_wrapper_l2_star_discrepancy, + _cy_wrapper_update_discrepancy, + _cy_van_der_corput_scrambled, + _cy_van_der_corput, +) + + +__all__ = ['scale', 'discrepancy', 'geometric_discrepancy', 'update_discrepancy', + 'QMCEngine', 'Sobol', 'Halton', 'LatinHypercube', 'PoissonDisk', + 'MultinomialQMC', 'MultivariateNormalQMC'] + + +@overload +def check_random_state(seed: IntNumber | None = ...) -> np.random.Generator: + ... + + +@overload +def check_random_state(seed: GeneratorType) -> GeneratorType: + ... + + +# Based on scipy._lib._util.check_random_state +# This is going to be removed at the end of the SPEC 7 transition, +# so I'll just leave the argument name `seed` alone +def check_random_state(seed=None): + """Turn `seed` into a `numpy.random.Generator` instance. + + Parameters + ---------- + seed : {None, int, `numpy.random.Generator`, `numpy.random.RandomState`}, optional + If `seed` is an int or None, a new `numpy.random.Generator` is + created using ``np.random.default_rng(seed)``. + If `seed` is already a ``Generator`` or ``RandomState`` instance, then + the provided instance is used. + + Returns + ------- + seed : {`numpy.random.Generator`, `numpy.random.RandomState`} + Random number generator. + + """ + if seed is None or isinstance(seed, numbers.Integral | np.integer): + return np.random.default_rng(seed) + elif isinstance(seed, np.random.RandomState | np.random.Generator): + return seed + else: + raise ValueError(f'{seed!r} cannot be used to seed a' + ' numpy.random.Generator instance') + + +def scale( + sample: "npt.ArrayLike", + l_bounds: "npt.ArrayLike", + u_bounds: "npt.ArrayLike", + *, + reverse: bool = False +) -> np.ndarray: + r"""Sample scaling from unit hypercube to different bounds. + + To convert a sample from :math:`[0, 1)` to :math:`[a, b), b>a`, + with :math:`a` the lower bounds and :math:`b` the upper bounds. + The following transformation is used: + + .. math:: + + (b - a) \cdot \text{sample} + a + + Parameters + ---------- + sample : array_like (n, d) + Sample to scale. + l_bounds, u_bounds : array_like (d,) + Lower and upper bounds (resp. :math:`a`, :math:`b`) of transformed + data. If `reverse` is True, range of the original data to transform + to the unit hypercube. + reverse : bool, optional + Reverse the transformation from different bounds to the unit hypercube. + Default is False. + + Returns + ------- + sample : array_like (n, d) + Scaled sample. + + Examples + -------- + Transform 3 samples in the unit hypercube to bounds: + + >>> from scipy.stats import qmc + >>> l_bounds = [-2, 0] + >>> u_bounds = [6, 5] + >>> sample = [[0.5 , 0.75], + ... [0.5 , 0.5], + ... [0.75, 0.25]] + >>> sample_scaled = qmc.scale(sample, l_bounds, u_bounds) + >>> sample_scaled + array([[2. , 3.75], + [2. , 2.5 ], + [4. , 1.25]]) + + And convert back to the unit hypercube: + + >>> sample_ = qmc.scale(sample_scaled, l_bounds, u_bounds, reverse=True) + >>> sample_ + array([[0.5 , 0.75], + [0.5 , 0.5 ], + [0.75, 0.25]]) + + """ + sample = np.asarray(sample) + + # Checking bounds and sample + if not sample.ndim == 2: + raise ValueError('Sample is not a 2D array') + + lower, upper = _validate_bounds( + l_bounds=l_bounds, u_bounds=u_bounds, d=sample.shape[1] + ) + + if not reverse: + # Checking that sample is within the hypercube + if (sample.max() > 1.) or (sample.min() < 0.): + raise ValueError('Sample is not in unit hypercube') + + return sample * (upper - lower) + lower + else: + # Checking that sample is within the bounds + if not (np.all(sample >= lower) and np.all(sample <= upper)): + raise ValueError('Sample is out of bounds') + + return (sample - lower) / (upper - lower) + + +def _ensure_in_unit_hypercube(sample: "npt.ArrayLike") -> np.ndarray: + """Ensure that sample is a 2D array and is within a unit hypercube + + Parameters + ---------- + sample : array_like (n, d) + A 2D array of points. + + Returns + ------- + np.ndarray + The array interpretation of the input sample + + Raises + ------ + ValueError + If the input is not a 2D array or contains points outside of + a unit hypercube. + """ + sample = np.asarray(sample, dtype=np.float64, order="C") + + if not sample.ndim == 2: + raise ValueError("Sample is not a 2D array") + + if (sample.max() > 1.) or (sample.min() < 0.): + raise ValueError("Sample is not in unit hypercube") + + return sample + + +def discrepancy( + sample: "npt.ArrayLike", + *, + iterative: bool = False, + method: Literal["CD", "WD", "MD", "L2-star"] = "CD", + workers: IntNumber = 1) -> float: + """Discrepancy of a given sample. + + Parameters + ---------- + sample : array_like (n, d) + The sample to compute the discrepancy from. + iterative : bool, optional + Must be False if not using it for updating the discrepancy. + Default is False. Refer to the notes for more details. + method : str, optional + Type of discrepancy, can be ``CD``, ``WD``, ``MD`` or ``L2-star``. + Refer to the notes for more details. Default is ``CD``. + workers : int, optional + Number of workers to use for parallel processing. If -1 is given all + CPU threads are used. Default is 1. + + Returns + ------- + discrepancy : float + Discrepancy. + + See Also + -------- + geometric_discrepancy + + Notes + ----- + The discrepancy is a uniformity criterion used to assess the space filling + of a number of samples in a hypercube. A discrepancy quantifies the + distance between the continuous uniform distribution on a hypercube and the + discrete uniform distribution on :math:`n` distinct sample points. + + The lower the value is, the better the coverage of the parameter space is. + + For a collection of subsets of the hypercube, the discrepancy is the + difference between the fraction of sample points in one of those + subsets and the volume of that subset. There are different definitions of + discrepancy corresponding to different collections of subsets. Some + versions take a root mean square difference over subsets instead of + a maximum. + + A measure of uniformity is reasonable if it satisfies the following + criteria [1]_: + + 1. It is invariant under permuting factors and/or runs. + 2. It is invariant under rotation of the coordinates. + 3. It can measure not only uniformity of the sample over the hypercube, + but also the projection uniformity of the sample over non-empty + subset of lower dimension hypercubes. + 4. There is some reasonable geometric meaning. + 5. It is easy to compute. + 6. It satisfies the Koksma-Hlawka-like inequality. + 7. It is consistent with other criteria in experimental design. + + Four methods are available: + + * ``CD``: Centered Discrepancy - subspace involves a corner of the + hypercube + * ``WD``: Wrap-around Discrepancy - subspace can wrap around bounds + * ``MD``: Mixture Discrepancy - mix between CD/WD covering more criteria + * ``L2-star``: L2-star discrepancy - like CD BUT variant to rotation + + Methods ``CD``, ``WD``, and ``MD`` implement the right hand side of equations + 9, 10, and 18 of [2]_, respectively; the square root is not taken. On the + other hand, ``L2-star`` computes the quantity given by equation 10 of + [3]_ as implemented by subsequent equations; the square root is taken. + + Lastly, using ``iterative=True``, it is possible to compute the + discrepancy as if we had :math:`n+1` samples. This is useful if we want + to add a point to a sampling and check the candidate which would give the + lowest discrepancy. Then you could just update the discrepancy with + each candidate using `update_discrepancy`. This method is faster than + computing the discrepancy for a large number of candidates. + + References + ---------- + .. [1] Fang et al. "Design and modeling for computer experiments". + Computer Science and Data Analysis Series, 2006. + .. [2] Zhou Y.-D. et al. "Mixture discrepancy for quasi-random point sets." + Journal of Complexity, 29 (3-4) , pp. 283-301, 2013. + .. [3] T. T. Warnock. "Computational investigations of low discrepancy + point sets." Applications of Number Theory to Numerical + Analysis, Academic Press, pp. 319-343, 1972. + + Examples + -------- + Calculate the quality of the sample using the discrepancy: + + >>> import numpy as np + >>> from scipy.stats import qmc + >>> space = np.array([[1, 3], [2, 6], [3, 2], [4, 5], [5, 1], [6, 4]]) + >>> l_bounds = [0.5, 0.5] + >>> u_bounds = [6.5, 6.5] + >>> space = qmc.scale(space, l_bounds, u_bounds, reverse=True) + >>> space + array([[0.08333333, 0.41666667], + [0.25 , 0.91666667], + [0.41666667, 0.25 ], + [0.58333333, 0.75 ], + [0.75 , 0.08333333], + [0.91666667, 0.58333333]]) + >>> qmc.discrepancy(space) + 0.008142039609053464 + + We can also compute iteratively the ``CD`` discrepancy by using + ``iterative=True``. + + >>> disc_init = qmc.discrepancy(space[:-1], iterative=True) + >>> disc_init + 0.04769081147119336 + >>> qmc.update_discrepancy(space[-1], space[:-1], disc_init) + 0.008142039609053513 + + """ + sample = _ensure_in_unit_hypercube(sample) + + workers = _validate_workers(workers) + + methods = { + "CD": _cy_wrapper_centered_discrepancy, + "WD": _cy_wrapper_wrap_around_discrepancy, + "MD": _cy_wrapper_mixture_discrepancy, + "L2-star": _cy_wrapper_l2_star_discrepancy, + } + + if method in methods: + return methods[method](sample, iterative, workers=workers) + else: + raise ValueError(f"{method!r} is not a valid method. It must be one of" + f" {set(methods)!r}") + + +def geometric_discrepancy( + sample: "npt.ArrayLike", + method: Literal["mindist", "mst"] = "mindist", + metric: str = "euclidean") -> float: + """Discrepancy of a given sample based on its geometric properties. + + Parameters + ---------- + sample : array_like (n, d) + The sample to compute the discrepancy from. + method : {"mindist", "mst"}, optional + The method to use. One of ``mindist`` for minimum distance (default) + or ``mst`` for minimum spanning tree. + metric : str or callable, optional + The distance metric to use. See the documentation + for `scipy.spatial.distance.pdist` for the available metrics and + the default. + + Returns + ------- + discrepancy : float + Discrepancy (higher values correspond to greater sample uniformity). + + See Also + -------- + discrepancy + + Notes + ----- + The discrepancy can serve as a simple measure of quality of a random sample. + This measure is based on the geometric properties of the distribution of points + in the sample, such as the minimum distance between any pair of points, or + the mean edge length in a minimum spanning tree. + + The higher the value is, the better the coverage of the parameter space is. + Note that this is different from `scipy.stats.qmc.discrepancy`, where lower + values correspond to higher quality of the sample. + + Also note that when comparing different sampling strategies using this function, + the sample size must be kept constant. + + It is possible to calculate two metrics from the minimum spanning tree: + the mean edge length and the standard deviation of edges lengths. Using + both metrics offers a better picture of uniformity than either metric alone, + with higher mean and lower standard deviation being preferable (see [1]_ + for a brief discussion). This function currently only calculates the mean + edge length. + + References + ---------- + .. [1] Franco J. et al. "Minimum Spanning Tree: A new approach to assess the quality + of the design of computer experiments." Chemometrics and Intelligent Laboratory + Systems, 97 (2), pp. 164-169, 2009. + + Examples + -------- + Calculate the quality of the sample using the minimum euclidean distance + (the defaults): + + >>> import numpy as np + >>> from scipy.stats import qmc + >>> rng = np.random.default_rng(191468432622931918890291693003068437394) + >>> sample = qmc.LatinHypercube(d=2, rng=rng).random(50) + >>> qmc.geometric_discrepancy(sample) + 0.03708161435687876 + + Calculate the quality using the mean edge length in the minimum + spanning tree: + + >>> qmc.geometric_discrepancy(sample, method='mst') + 0.1105149978798376 + + Display the minimum spanning tree and the points with + the smallest distance: + + >>> import matplotlib.pyplot as plt + >>> from matplotlib.lines import Line2D + >>> from scipy.sparse.csgraph import minimum_spanning_tree + >>> from scipy.spatial.distance import pdist, squareform + >>> dist = pdist(sample) + >>> mst = minimum_spanning_tree(squareform(dist)) + >>> edges = np.where(mst.toarray() > 0) + >>> edges = np.asarray(edges).T + >>> min_dist = np.min(dist) + >>> min_idx = np.argwhere(squareform(dist) == min_dist)[0] + >>> fig, ax = plt.subplots(figsize=(10, 5)) + >>> _ = ax.set(aspect='equal', xlabel=r'$x_1$', ylabel=r'$x_2$', + ... xlim=[0, 1], ylim=[0, 1]) + >>> for edge in edges: + ... ax.plot(sample[edge, 0], sample[edge, 1], c='k') + >>> ax.scatter(sample[:, 0], sample[:, 1]) + >>> ax.add_patch(plt.Circle(sample[min_idx[0]], min_dist, color='red', fill=False)) + >>> markers = [ + ... Line2D([0], [0], marker='o', lw=0, label='Sample points'), + ... Line2D([0], [0], color='k', label='Minimum spanning tree'), + ... Line2D([0], [0], marker='o', lw=0, markerfacecolor='w', markeredgecolor='r', + ... label='Minimum point-to-point distance'), + ... ] + >>> ax.legend(handles=markers, loc='center left', bbox_to_anchor=(1, 0.5)); + >>> plt.show() + + """ + sample = _ensure_in_unit_hypercube(sample) + if sample.shape[0] < 2: + raise ValueError("Sample must contain at least two points") + + distances = distance.pdist(sample, metric=metric) # type: ignore[call-overload] + + if np.any(distances == 0.0): + warnings.warn("Sample contains duplicate points.", stacklevel=2) + + if method == "mindist": + return np.min(distances[distances.nonzero()]) + elif method == "mst": + fully_connected_graph = distance.squareform(distances) + mst = minimum_spanning_tree(fully_connected_graph) + distances = mst[mst.nonzero()] + # TODO consider returning both the mean and the standard deviation + # see [1] for a discussion + return np.mean(distances) + else: + raise ValueError(f"{method!r} is not a valid method. " + f"It must be one of {{'mindist', 'mst'}}") + + +def update_discrepancy( + x_new: "npt.ArrayLike", + sample: "npt.ArrayLike", + initial_disc: DecimalNumber) -> float: + """Update the centered discrepancy with a new sample. + + Parameters + ---------- + x_new : array_like (1, d) + The new sample to add in `sample`. + sample : array_like (n, d) + The initial sample. + initial_disc : float + Centered discrepancy of the `sample`. + + Returns + ------- + discrepancy : float + Centered discrepancy of the sample composed of `x_new` and `sample`. + + Examples + -------- + We can also compute iteratively the discrepancy by using + ``iterative=True``. + + >>> import numpy as np + >>> from scipy.stats import qmc + >>> space = np.array([[1, 3], [2, 6], [3, 2], [4, 5], [5, 1], [6, 4]]) + >>> l_bounds = [0.5, 0.5] + >>> u_bounds = [6.5, 6.5] + >>> space = qmc.scale(space, l_bounds, u_bounds, reverse=True) + >>> disc_init = qmc.discrepancy(space[:-1], iterative=True) + >>> disc_init + 0.04769081147119336 + >>> qmc.update_discrepancy(space[-1], space[:-1], disc_init) + 0.008142039609053513 + + """ + sample = np.asarray(sample, dtype=np.float64, order="C") + x_new = np.asarray(x_new, dtype=np.float64, order="C") + + # Checking that sample is within the hypercube and 2D + if not sample.ndim == 2: + raise ValueError('Sample is not a 2D array') + + if (sample.max() > 1.) or (sample.min() < 0.): + raise ValueError('Sample is not in unit hypercube') + + # Checking that x_new is within the hypercube and 1D + if not x_new.ndim == 1: + raise ValueError('x_new is not a 1D array') + + if not (np.all(x_new >= 0) and np.all(x_new <= 1)): + raise ValueError('x_new is not in unit hypercube') + + if x_new.shape[0] != sample.shape[1]: + raise ValueError("x_new and sample must be broadcastable") + + return _cy_wrapper_update_discrepancy(x_new, sample, initial_disc) + + +def _perturb_discrepancy(sample: np.ndarray, i1: int, i2: int, k: int, + disc: float): + """Centered discrepancy after an elementary perturbation of a LHS. + + An elementary perturbation consists of an exchange of coordinates between + two points: ``sample[i1, k] <-> sample[i2, k]``. By construction, + this operation conserves the LHS properties. + + Parameters + ---------- + sample : array_like (n, d) + The sample (before permutation) to compute the discrepancy from. + i1 : int + The first line of the elementary permutation. + i2 : int + The second line of the elementary permutation. + k : int + The column of the elementary permutation. + disc : float + Centered discrepancy of the design before permutation. + + Returns + ------- + discrepancy : float + Centered discrepancy of the design after permutation. + + References + ---------- + .. [1] Jin et al. "An efficient algorithm for constructing optimal design + of computer experiments", Journal of Statistical Planning and + Inference, 2005. + + """ + n = sample.shape[0] + + z_ij = sample - 0.5 + + # Eq (19) + c_i1j = (1. / n ** 2. + * np.prod(0.5 * (2. + abs(z_ij[i1, :]) + + abs(z_ij) - abs(z_ij[i1, :] - z_ij)), axis=1)) + c_i2j = (1. / n ** 2. + * np.prod(0.5 * (2. + abs(z_ij[i2, :]) + + abs(z_ij) - abs(z_ij[i2, :] - z_ij)), axis=1)) + + # Eq (20) + c_i1i1 = (1. / n ** 2 * np.prod(1 + abs(z_ij[i1, :])) + - 2. / n * np.prod(1. + 0.5 * abs(z_ij[i1, :]) + - 0.5 * z_ij[i1, :] ** 2)) + c_i2i2 = (1. / n ** 2 * np.prod(1 + abs(z_ij[i2, :])) + - 2. / n * np.prod(1. + 0.5 * abs(z_ij[i2, :]) + - 0.5 * z_ij[i2, :] ** 2)) + + # Eq (22), typo in the article in the denominator i2 -> i1 + num = (2 + abs(z_ij[i2, k]) + abs(z_ij[:, k]) + - abs(z_ij[i2, k] - z_ij[:, k])) + denum = (2 + abs(z_ij[i1, k]) + abs(z_ij[:, k]) + - abs(z_ij[i1, k] - z_ij[:, k])) + gamma = num / denum + + # Eq (23) + c_p_i1j = gamma * c_i1j + # Eq (24) + c_p_i2j = c_i2j / gamma + + alpha = (1 + abs(z_ij[i2, k])) / (1 + abs(z_ij[i1, k])) + beta = (2 - abs(z_ij[i2, k])) / (2 - abs(z_ij[i1, k])) + + g_i1 = np.prod(1. + abs(z_ij[i1, :])) + g_i2 = np.prod(1. + abs(z_ij[i2, :])) + h_i1 = np.prod(1. + 0.5 * abs(z_ij[i1, :]) - 0.5 * (z_ij[i1, :] ** 2)) + h_i2 = np.prod(1. + 0.5 * abs(z_ij[i2, :]) - 0.5 * (z_ij[i2, :] ** 2)) + + # Eq (25), typo in the article g is missing + c_p_i1i1 = ((g_i1 * alpha) / (n ** 2) - 2. * alpha * beta * h_i1 / n) + # Eq (26), typo in the article n ** 2 + c_p_i2i2 = ((g_i2 / ((n ** 2) * alpha)) - (2. * h_i2 / (n * alpha * beta))) + + # Eq (26) + sum_ = c_p_i1j - c_i1j + c_p_i2j - c_i2j + + mask = np.ones(n, dtype=bool) + mask[[i1, i2]] = False + sum_ = sum(sum_[mask]) + + disc_ep = (disc + c_p_i1i1 - c_i1i1 + c_p_i2i2 - c_i2i2 + 2 * sum_) + + return disc_ep + + +def primes_from_2_to(n: int) -> np.ndarray: + """Prime numbers from 2 to *n*. + + Parameters + ---------- + n : int + Sup bound with ``n >= 6``. + + Returns + ------- + primes : list(int) + Primes in ``2 <= p < n``. + + Notes + ----- + Taken from [1]_ by P.T. Roy, written consent given on 23.04.2021 + by the original author, Bruno Astrolino, for free use in SciPy under + the 3-clause BSD. + + References + ---------- + .. [1] `StackOverflow `_. + + """ + sieve = np.ones(n // 3 + (n % 6 == 2), dtype=bool) + for i in range(1, int(n ** 0.5) // 3 + 1): + k = 3 * i + 1 | 1 + sieve[k * k // 3::2 * k] = False + sieve[k * (k - 2 * (i & 1) + 4) // 3::2 * k] = False + return np.r_[2, 3, ((3 * np.nonzero(sieve)[0][1:] + 1) | 1)] + + +def n_primes(n: IntNumber) -> list[int] | np.ndarray: + """List of the n-first prime numbers. + + Parameters + ---------- + n : int + Number of prime numbers wanted. + + Returns + ------- + primes : list(int) + List of primes. + + """ + primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, + 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, + 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, + 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, + 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, + 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, + 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, + 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, + 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, + 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, + 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, + 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, + 953, 967, 971, 977, 983, 991, 997][:n] + + if len(primes) < n: + big_number = 2000 + while 'Not enough primes': + primes = primes_from_2_to(big_number)[:n] # type: ignore + if len(primes) == n: + break + big_number += 1000 + + return primes + + +def _van_der_corput_permutations( + base: IntNumber, *, rng: SeedType = None +) -> np.ndarray: + """Permutations for scrambling a Van der Corput sequence. + + Parameters + ---------- + base : int + Base of the sequence. + rng : `numpy.random.Generator`, optional + Pseudorandom number generator state. When `rng` is None, a new + `numpy.random.Generator` is created using entropy from the + operating system. Types other than `numpy.random.Generator` are + passed to `numpy.random.default_rng` to instantiate a ``Generator``. + + .. versionchanged:: 1.15.0 + + As part of the `SPEC-007 `_ + transition from use of `numpy.random.RandomState` to + `numpy.random.Generator`, this keyword was changed from `seed` to + `rng`. During the transition, the behavior documented above is not + accurate; see `check_random_state` for actual behavior. After the + transition, this admonition can be removed. + + Returns + ------- + permutations : array_like + Permutation indices. + + Notes + ----- + In Algorithm 1 of Owen 2017, a permutation of `np.arange(base)` is + created for each positive integer `k` such that ``1 - base**-k < 1`` + using floating-point arithmetic. For double precision floats, the + condition ``1 - base**-k < 1`` can also be written as ``base**-k > + 2**-54``, which makes it more apparent how many permutations we need + to create. + """ + rng = check_random_state(rng) + count = math.ceil(54 / math.log2(base)) - 1 + permutations = np.repeat(np.arange(base)[None], count, axis=0) + for perm in permutations: + rng.shuffle(perm) + + return permutations + + +def van_der_corput( + n: IntNumber, + base: IntNumber = 2, + *, + start_index: IntNumber = 0, + scramble: bool = False, + permutations: "npt.ArrayLike | None" = None, + rng: SeedType = None, + workers: IntNumber = 1) -> np.ndarray: + """Van der Corput sequence. + + Pseudo-random number generator based on a b-adic expansion. + + Scrambling uses permutations of the remainders (see [1]_). Multiple + permutations are applied to construct a point. The sequence of + permutations has to be the same for all points of the sequence. + + Parameters + ---------- + n : int + Number of element of the sequence. + base : int, optional + Base of the sequence. Default is 2. + start_index : int, optional + Index to start the sequence from. Default is 0. + scramble : bool, optional + If True, use Owen scrambling. Otherwise no scrambling is done. + Default is True. + permutations : array_like, optional + Permutations used for scrambling. + rng : `numpy.random.Generator`, optional + Pseudorandom number generator state. When `rng` is None, a new + `numpy.random.Generator` is created using entropy from the + operating system. Types other than `numpy.random.Generator` are + passed to `numpy.random.default_rng` to instantiate a ``Generator``. + workers : int, optional + Number of workers to use for parallel processing. If -1 is + given all CPU threads are used. Default is 1. + + Returns + ------- + sequence : list (n,) + Sequence of Van der Corput. + + References + ---------- + .. [1] A. B. Owen. "A randomized Halton algorithm in R", + :arxiv:`1706.02808`, 2017. + + """ + if base < 2: + raise ValueError("'base' must be at least 2") + + if scramble: + if permutations is None: + permutations = _van_der_corput_permutations( + base=base, rng=rng + ) + else: + permutations = np.asarray(permutations) + + permutations = permutations.astype(np.int64) + return _cy_van_der_corput_scrambled(n, base, start_index, + permutations, workers) + + else: + return _cy_van_der_corput(n, base, start_index, workers) + + +class QMCEngine(ABC): + """A generic Quasi-Monte Carlo sampler class meant for subclassing. + + QMCEngine is a base class to construct a specific Quasi-Monte Carlo + sampler. It cannot be used directly as a sampler. + + Parameters + ---------- + d : int + Dimension of the parameter space. + optimization : {None, "random-cd", "lloyd"}, optional + Whether to use an optimization scheme to improve the quality after + sampling. Note that this is a post-processing step that does not + guarantee that all properties of the sample will be conserved. + Default is None. + + * ``random-cd``: random permutations of coordinates to lower the + centered discrepancy. The best sample based on the centered + discrepancy is constantly updated. Centered discrepancy-based + sampling shows better space-filling robustness toward 2D and 3D + subprojections compared to using other discrepancy measures. + * ``lloyd``: Perturb samples using a modified Lloyd-Max algorithm. + The process converges to equally spaced samples. + + .. versionadded:: 1.10.0 + + rng : `numpy.random.Generator`, optional + Pseudorandom number generator state. When `rng` is None, a new + `numpy.random.Generator` is created using entropy from the + operating system. Types other than `numpy.random.Generator` are + passed to `numpy.random.default_rng` to instantiate a ``Generator``. + + .. versionchanged:: 1.15.0 + + As part of the `SPEC-007 `_ + transition from use of `numpy.random.RandomState` to + `numpy.random.Generator`, this keyword was changed from `seed` to + `rng`. For an interim period, both keywords will continue to work, although + only one may be specified at a time. After the interim period, function + calls using the `seed` keyword will emit warnings. Following a + deprecation period, the `seed` keyword will be removed. + + Notes + ----- + By convention samples are distributed over the half-open interval + ``[0, 1)``. Instances of the class can access the attributes: ``d`` for + the dimension; and ``rng`` for the random number generator. + + **Subclassing** + + When subclassing `QMCEngine` to create a new sampler, ``__init__`` and + ``random`` must be redefined. + + * ``__init__(d, rng=None)``: at least fix the dimension. If the sampler + does not take advantage of a ``rng`` (deterministic methods like + Halton), this parameter can be omitted. + * ``_random(n, *, workers=1)``: draw ``n`` from the engine. ``workers`` + is used for parallelism. See `Halton` for example. + + Optionally, two other methods can be overwritten by subclasses: + + * ``reset``: Reset the engine to its original state. + * ``fast_forward``: If the sequence is deterministic (like Halton + sequence), then ``fast_forward(n)`` is skipping the ``n`` first draw. + + Examples + -------- + To create a random sampler based on ``np.random.random``, we would do the + following: + + >>> from scipy.stats import qmc + >>> class RandomEngine(qmc.QMCEngine): + ... def __init__(self, d, rng=None): + ... super().__init__(d=d, rng=rng) + ... + ... + ... def _random(self, n=1, *, workers=1): + ... return self.rng.random((n, self.d)) + ... + ... + ... def reset(self): + ... super().__init__(d=self.d, rng=self.rng_seed) + ... return self + ... + ... + ... def fast_forward(self, n): + ... self.random(n) + ... return self + + After subclassing `QMCEngine` to define the sampling strategy we want to + use, we can create an instance to sample from. + + >>> engine = RandomEngine(2) + >>> engine.random(5) + array([[0.22733602, 0.31675834], # random + [0.79736546, 0.67625467], + [0.39110955, 0.33281393], + [0.59830875, 0.18673419], + [0.67275604, 0.94180287]]) + + We can also reset the state of the generator and resample again. + + >>> _ = engine.reset() + >>> engine.random(5) + array([[0.22733602, 0.31675834], # random + [0.79736546, 0.67625467], + [0.39110955, 0.33281393], + [0.59830875, 0.18673419], + [0.67275604, 0.94180287]]) + + """ + + @abstractmethod + @_transition_to_rng('seed', replace_doc=False) + def __init__( + self, + d: IntNumber, + *, + optimization: Literal["random-cd", "lloyd"] | None = None, + rng: SeedType = None + ) -> None: + self._initialize(d, optimization=optimization, rng=rng) + + # During SPEC 7 transition: + # `__init__` has to be wrapped with @_transition_to_rng decorator + # because it is public. Subclasses previously called `__init__` + # directly, but this was problematic because arguments passed to + # subclass `__init__` as `seed` would get passed to superclass + # `__init__` as `rng`, rejecting `RandomState` arguments. + def _initialize( + self, + d: IntNumber, + *, + optimization: Literal["random-cd", "lloyd"] | None = None, + rng: SeedType = None + ) -> None: + if not np.issubdtype(type(d), np.integer) or d < 0: + raise ValueError('d must be a non-negative integer value') + + self.d = d + + if isinstance(rng, np.random.Generator): + # Spawn a Generator that we can own and reset. + self.rng = _rng_spawn(rng, 1)[0] + else: + # Create our instance of Generator, does not need spawning + # Also catch RandomState which cannot be spawned + self.rng = check_random_state(rng) + self.rng_seed = copy.deepcopy(self.rng) + + self.num_generated = 0 + + config = { + # random-cd + "n_nochange": 100, + "n_iters": 10_000, + "rng": self.rng, + + # lloyd + "tol": 1e-5, + "maxiter": 10, + "qhull_options": None, + } + self._optimization = optimization + self.optimization_method = _select_optimizer(optimization, config) + + @abstractmethod + def _random( + self, n: IntNumber = 1, *, workers: IntNumber = 1 + ) -> np.ndarray: + ... + + def random( + self, n: IntNumber = 1, *, workers: IntNumber = 1 + ) -> np.ndarray: + """Draw `n` in the half-open interval ``[0, 1)``. + + Parameters + ---------- + n : int, optional + Number of samples to generate in the parameter space. + Default is 1. + workers : int, optional + Only supported with `Halton`. + Number of workers to use for parallel processing. If -1 is + given all CPU threads are used. Default is 1. It becomes faster + than one worker for `n` greater than :math:`10^3`. + + Returns + ------- + sample : array_like (n, d) + QMC sample. + + """ + sample = self._random(n, workers=workers) + if self.optimization_method is not None: + sample = self.optimization_method(sample) + + self.num_generated += n + return sample + + def integers( + self, + l_bounds: "npt.ArrayLike", + *, + u_bounds: "npt.ArrayLike | None" = None, + n: IntNumber = 1, + endpoint: bool = False, + workers: IntNumber = 1 + ) -> np.ndarray: + r""" + Draw `n` integers from `l_bounds` (inclusive) to `u_bounds` + (exclusive), or if endpoint=True, `l_bounds` (inclusive) to + `u_bounds` (inclusive). + + Parameters + ---------- + l_bounds : int or array-like of ints + Lowest (signed) integers to be drawn (unless ``u_bounds=None``, + in which case this parameter is 0 and this value is used for + `u_bounds`). + u_bounds : int or array-like of ints, optional + If provided, one above the largest (signed) integer to be drawn + (see above for behavior if ``u_bounds=None``). + If array-like, must contain integer values. + n : int, optional + Number of samples to generate in the parameter space. + Default is 1. + endpoint : bool, optional + If true, sample from the interval ``[l_bounds, u_bounds]`` instead + of the default ``[l_bounds, u_bounds)``. Defaults is False. + workers : int, optional + Number of workers to use for parallel processing. If -1 is + given all CPU threads are used. Only supported when using `Halton` + Default is 1. + + Returns + ------- + sample : array_like (n, d) + QMC sample. + + Notes + ----- + It is safe to just use the same ``[0, 1)`` to integer mapping + with QMC that you would use with MC. You still get unbiasedness, + a strong law of large numbers, an asymptotically infinite variance + reduction and a finite sample variance bound. + + To convert a sample from :math:`[0, 1)` to :math:`[a, b), b>a`, + with :math:`a` the lower bounds and :math:`b` the upper bounds, + the following transformation is used: + + .. math:: + + \text{floor}((b - a) \cdot \text{sample} + a) + + """ + if u_bounds is None: + u_bounds = l_bounds + l_bounds = 0 + + u_bounds = np.atleast_1d(u_bounds) + l_bounds = np.atleast_1d(l_bounds) + + if endpoint: + u_bounds = u_bounds + 1 + + if (not np.issubdtype(l_bounds.dtype, np.integer) or + not np.issubdtype(u_bounds.dtype, np.integer)): + message = ("'u_bounds' and 'l_bounds' must be integers or" + " array-like of integers") + raise ValueError(message) + + if isinstance(self, Halton): + sample = self.random(n=n, workers=workers) + else: + sample = self.random(n=n) + + sample = scale(sample, l_bounds=l_bounds, u_bounds=u_bounds) + sample = np.floor(sample).astype(np.int64) + + return sample + + def reset(self) -> "QMCEngine": + """Reset the engine to base state. + + Returns + ------- + engine : QMCEngine + Engine reset to its base state. + + """ + rng = copy.deepcopy(self.rng_seed) + self.rng = check_random_state(rng) + self.num_generated = 0 + return self + + def fast_forward(self, n: IntNumber) -> "QMCEngine": + """Fast-forward the sequence by `n` positions. + + Parameters + ---------- + n : int + Number of points to skip in the sequence. + + Returns + ------- + engine : QMCEngine + Engine reset to its base state. + + """ + self.random(n=n) + return self + + +class Halton(QMCEngine): + """Halton sequence. + + Pseudo-random number generator that generalize the Van der Corput sequence + for multiple dimensions. The Halton sequence uses the base-two Van der + Corput sequence for the first dimension, base-three for its second and + base-:math:`p` for its :math:`n`-dimension, with :math:`p` the + :math:`n`'th prime. + + Parameters + ---------- + d : int + Dimension of the parameter space. + scramble : bool, optional + If True, use random scrambling from [2]_. Otherwise no scrambling + is done. + Default is True. + optimization : {None, "random-cd", "lloyd"}, optional + Whether to use an optimization scheme to improve the quality after + sampling. Note that this is a post-processing step that does not + guarantee that all properties of the sample will be conserved. + Default is None. + + * ``random-cd``: random permutations of coordinates to lower the + centered discrepancy. The best sample based on the centered + discrepancy is constantly updated. Centered discrepancy-based + sampling shows better space-filling robustness toward 2D and 3D + subprojections compared to using other discrepancy measures. + * ``lloyd``: Perturb samples using a modified Lloyd-Max algorithm. + The process converges to equally spaced samples. + + .. versionadded:: 1.10.0 + + rng : `numpy.random.Generator`, optional + Pseudorandom number generator state. When `rng` is None, a new + `numpy.random.Generator` is created using entropy from the + operating system. Types other than `numpy.random.Generator` are + passed to `numpy.random.default_rng` to instantiate a ``Generator``. + + .. versionchanged:: 1.15.0 + + As part of the `SPEC-007 `_ + transition from use of `numpy.random.RandomState` to + `numpy.random.Generator`, this keyword was changed from `seed` to + `rng`. For an interim period, both keywords will continue to work, although + only one may be specified at a time. After the interim period, function + calls using the `seed` keyword will emit warnings. Following a + deprecation period, the `seed` keyword will be removed. + + Notes + ----- + The Halton sequence has severe striping artifacts for even modestly + large dimensions. These can be ameliorated by scrambling. Scrambling + also supports replication-based error estimates and extends + applicability to unbounded integrands. + + References + ---------- + .. [1] Halton, "On the efficiency of certain quasi-random sequences of + points in evaluating multi-dimensional integrals", Numerische + Mathematik, 1960. + .. [2] A. B. Owen. "A randomized Halton algorithm in R", + :arxiv:`1706.02808`, 2017. + + Examples + -------- + Generate samples from a low discrepancy sequence of Halton. + + >>> from scipy.stats import qmc + >>> sampler = qmc.Halton(d=2, scramble=False) + >>> sample = sampler.random(n=5) + >>> sample + array([[0. , 0. ], + [0.5 , 0.33333333], + [0.25 , 0.66666667], + [0.75 , 0.11111111], + [0.125 , 0.44444444]]) + + Compute the quality of the sample using the discrepancy criterion. + + >>> qmc.discrepancy(sample) + 0.088893711419753 + + If some wants to continue an existing design, extra points can be obtained + by calling again `random`. Alternatively, you can skip some points like: + + >>> _ = sampler.fast_forward(5) + >>> sample_continued = sampler.random(n=5) + >>> sample_continued + array([[0.3125 , 0.37037037], + [0.8125 , 0.7037037 ], + [0.1875 , 0.14814815], + [0.6875 , 0.48148148], + [0.4375 , 0.81481481]]) + + Finally, samples can be scaled to bounds. + + >>> l_bounds = [0, 2] + >>> u_bounds = [10, 5] + >>> qmc.scale(sample_continued, l_bounds, u_bounds) + array([[3.125 , 3.11111111], + [8.125 , 4.11111111], + [1.875 , 2.44444444], + [6.875 , 3.44444444], + [4.375 , 4.44444444]]) + + """ + @_transition_to_rng('seed', replace_doc=False) + def __init__( + self, d: IntNumber, *, scramble: bool = True, + optimization: Literal["random-cd", "lloyd"] | None = None, + rng: SeedType = None + ) -> None: + # Used in `scipy.integrate.qmc_quad` + self._init_quad = {'d': d, 'scramble': True, + 'optimization': optimization} + super()._initialize(d=d, optimization=optimization, rng=rng) + + # important to have ``type(bdim) == int`` for performance reason + self.base = [int(bdim) for bdim in n_primes(d)] + self.scramble = scramble + + self._initialize_permutations() + + def _initialize_permutations(self) -> None: + """Initialize permutations for all Van der Corput sequences. + + Permutations are only needed for scrambling. + """ + self._permutations: list = [None] * len(self.base) + if self.scramble: + for i, bdim in enumerate(self.base): + permutations = _van_der_corput_permutations( + base=bdim, rng=self.rng + ) + + self._permutations[i] = permutations + + def _random( + self, n: IntNumber = 1, *, workers: IntNumber = 1 + ) -> np.ndarray: + """Draw `n` in the half-open interval ``[0, 1)``. + + Parameters + ---------- + n : int, optional + Number of samples to generate in the parameter space. Default is 1. + workers : int, optional + Number of workers to use for parallel processing. If -1 is + given all CPU threads are used. Default is 1. It becomes faster + than one worker for `n` greater than :math:`10^3`. + + Returns + ------- + sample : array_like (n, d) + QMC sample. + + """ + workers = _validate_workers(workers) + # Generate a sample using a Van der Corput sequence per dimension. + sample = [van_der_corput(n, bdim, start_index=self.num_generated, + scramble=self.scramble, + permutations=self._permutations[i], + workers=workers) + for i, bdim in enumerate(self.base)] + + return np.array(sample).T.reshape(n, self.d) + + +class LatinHypercube(QMCEngine): + r"""Latin hypercube sampling (LHS). + + A Latin hypercube sample [1]_ generates :math:`n` points in + :math:`[0,1)^{d}`. Each univariate marginal distribution is stratified, + placing exactly one point in :math:`[j/n, (j+1)/n)` for + :math:`j=0,1,...,n-1`. They are still applicable when :math:`n << d`. + + Parameters + ---------- + d : int + Dimension of the parameter space. + scramble : bool, optional + When False, center samples within cells of a multi-dimensional grid. + Otherwise, samples are randomly placed within cells of the grid. + + .. note:: + Setting ``scramble=False`` does not ensure deterministic output. + For that, use the `rng` parameter. + + Default is True. + + .. versionadded:: 1.10.0 + + optimization : {None, "random-cd", "lloyd"}, optional + Whether to use an optimization scheme to improve the quality after + sampling. Note that this is a post-processing step that does not + guarantee that all properties of the sample will be conserved. + Default is None. + + * ``random-cd``: random permutations of coordinates to lower the + centered discrepancy. The best sample based on the centered + discrepancy is constantly updated. Centered discrepancy-based + sampling shows better space-filling robustness toward 2D and 3D + subprojections compared to using other discrepancy measures. + * ``lloyd``: Perturb samples using a modified Lloyd-Max algorithm. + The process converges to equally spaced samples. + + .. versionadded:: 1.8.0 + .. versionchanged:: 1.10.0 + Add ``lloyd``. + + strength : {1, 2}, optional + Strength of the LHS. ``strength=1`` produces a plain LHS while + ``strength=2`` produces an orthogonal array based LHS of strength 2 + [7]_, [8]_. In that case, only ``n=p**2`` points can be sampled, + with ``p`` a prime number. It also constrains ``d <= p + 1``. + Default is 1. + + .. versionadded:: 1.8.0 + + rng : `numpy.random.Generator`, optional + Pseudorandom number generator state. When `rng` is None, a new + `numpy.random.Generator` is created using entropy from the + operating system. Types other than `numpy.random.Generator` are + passed to `numpy.random.default_rng` to instantiate a ``Generator``. + + .. versionchanged:: 1.15.0 + + As part of the `SPEC-007 `_ + transition from use of `numpy.random.RandomState` to + `numpy.random.Generator`, this keyword was changed from `seed` to + `rng`. For an interim period, both keywords will continue to work, although + only one may be specified at a time. After the interim period, function + calls using the `seed` keyword will emit warnings. Following a + deprecation period, the `seed` keyword will be removed. + + See Also + -------- + :ref:`quasi-monte-carlo` + + Notes + ----- + + When LHS is used for integrating a function :math:`f` over :math:`n`, + LHS is extremely effective on integrands that are nearly additive [2]_. + With a LHS of :math:`n` points, the variance of the integral is always + lower than plain MC on :math:`n-1` points [3]_. There is a central limit + theorem for LHS on the mean and variance of the integral [4]_, but not + necessarily for optimized LHS due to the randomization. + + :math:`A` is called an orthogonal array of strength :math:`t` if in each + n-row-by-t-column submatrix of :math:`A`: all :math:`p^t` possible + distinct rows occur the same number of times. The elements of :math:`A` + are in the set :math:`\{0, 1, ..., p-1\}`, also called symbols. + The constraint that :math:`p` must be a prime number is to allow modular + arithmetic. Increasing strength adds some symmetry to the sub-projections + of a sample. With strength 2, samples are symmetric along the diagonals of + 2D sub-projections. This may be undesirable, but on the other hand, the + sample dispersion is improved. + + Strength 1 (plain LHS) brings an advantage over strength 0 (MC) and + strength 2 is a useful increment over strength 1. Going to strength 3 is + a smaller increment and scrambled QMC like Sobol', Halton are more + performant [7]_. + + To create a LHS of strength 2, the orthogonal array :math:`A` is + randomized by applying a random, bijective map of the set of symbols onto + itself. For example, in column 0, all 0s might become 2; in column 1, + all 0s might become 1, etc. + Then, for each column :math:`i` and symbol :math:`j`, we add a plain, + one-dimensional LHS of size :math:`p` to the subarray where + :math:`A^i = j`. The resulting matrix is finally divided by :math:`p`. + + References + ---------- + .. [1] Mckay et al., "A Comparison of Three Methods for Selecting Values + of Input Variables in the Analysis of Output from a Computer Code." + Technometrics, 1979. + .. [2] M. Stein, "Large sample properties of simulations using Latin + hypercube sampling." Technometrics 29, no. 2: 143-151, 1987. + .. [3] A. B. Owen, "Monte Carlo variance of scrambled net quadrature." + SIAM Journal on Numerical Analysis 34, no. 5: 1884-1910, 1997 + .. [4] Loh, W.-L. "On Latin hypercube sampling." The annals of statistics + 24, no. 5: 2058-2080, 1996. + .. [5] Fang et al. "Design and modeling for computer experiments". + Computer Science and Data Analysis Series, 2006. + .. [6] Damblin et al., "Numerical studies of space filling designs: + optimization of Latin Hypercube Samples and subprojection properties." + Journal of Simulation, 2013. + .. [7] A. B. Owen , "Orthogonal arrays for computer experiments, + integration and visualization." Statistica Sinica, 1992. + .. [8] B. Tang, "Orthogonal Array-Based Latin Hypercubes." + Journal of the American Statistical Association, 1993. + .. [9] Seaholm, Susan K. et al. (1988). Latin hypercube sampling and the + sensitivity analysis of a Monte Carlo epidemic model. Int J Biomed + Comput, 23(1-2), 97-112. :doi:`10.1016/0020-7101(88)90067-0` + + Examples + -------- + Generate samples from a Latin hypercube generator. + + >>> from scipy.stats import qmc + >>> sampler = qmc.LatinHypercube(d=2) + >>> sample = sampler.random(n=5) + >>> sample + array([[0.1545328 , 0.53664833], # random + [0.84052691, 0.06474907], + [0.52177809, 0.93343721], + [0.68033825, 0.36265316], + [0.26544879, 0.61163943]]) + + Compute the quality of the sample using the discrepancy criterion. + + >>> qmc.discrepancy(sample) + 0.0196... # random + + Samples can be scaled to bounds. + + >>> l_bounds = [0, 2] + >>> u_bounds = [10, 5] + >>> qmc.scale(sample, l_bounds, u_bounds) + array([[1.54532796, 3.609945 ], # random + [8.40526909, 2.1942472 ], + [5.2177809 , 4.80031164], + [6.80338249, 3.08795949], + [2.65448791, 3.83491828]]) + + Below are other examples showing alternative ways to construct LHS with + even better coverage of the space. + + Using a base LHS as a baseline. + + >>> sampler = qmc.LatinHypercube(d=2) + >>> sample = sampler.random(n=5) + >>> qmc.discrepancy(sample) + 0.0196... # random + + Use the `optimization` keyword argument to produce a LHS with + lower discrepancy at higher computational cost. + + >>> sampler = qmc.LatinHypercube(d=2, optimization="random-cd") + >>> sample = sampler.random(n=5) + >>> qmc.discrepancy(sample) + 0.0176... # random + + Use the `strength` keyword argument to produce an orthogonal array based + LHS of strength 2. In this case, the number of sample points must be the + square of a prime number. + + >>> sampler = qmc.LatinHypercube(d=2, strength=2) + >>> sample = sampler.random(n=9) + >>> qmc.discrepancy(sample) + 0.00526... # random + + Options could be combined to produce an optimized centered + orthogonal array based LHS. After optimization, the result would not + be guaranteed to be of strength 2. + + **Real-world example** + + In [9]_, a Latin Hypercube sampling (LHS) strategy was used to sample a + parameter space to study the importance of each parameter of an epidemic + model. Such analysis is also called a sensitivity analysis. + + Since the dimensionality of the problem is high (6), it is computationally + expensive to cover the space. When numerical experiments are costly, QMC + enables analysis that may not be possible if using a grid. + + The six parameters of the model represented the probability of illness, + the probability of withdrawal, and four contact probabilities. The + authors assumed uniform distributions for all parameters and generated + 50 samples. + + Using `scipy.stats.qmc.LatinHypercube` to replicate the protocol, + the first step is to create a sample in the unit hypercube: + + >>> from scipy.stats import qmc + >>> sampler = qmc.LatinHypercube(d=6) + >>> sample = sampler.random(n=50) + + Then the sample can be scaled to the appropriate bounds: + + >>> l_bounds = [0.000125, 0.01, 0.0025, 0.05, 0.47, 0.7] + >>> u_bounds = [0.000375, 0.03, 0.0075, 0.15, 0.87, 0.9] + >>> sample_scaled = qmc.scale(sample, l_bounds, u_bounds) + + Such a sample was used to run the model 50 times, and a polynomial + response surface was constructed. This allowed the authors to study the + relative importance of each parameter across the range of possibilities + of every other parameter. + + In this computer experiment, they showed a 14-fold reduction in the + number of samples required to maintain an error below 2% on their + response surface when compared to a grid sampling. + + """ + + @_transition_to_rng('seed', replace_doc=False) + def __init__( + self, d: IntNumber, *, + scramble: bool = True, + strength: int = 1, + optimization: Literal["random-cd", "lloyd"] | None = None, + rng: SeedType = None + ) -> None: + # Used in `scipy.integrate.qmc_quad` + self._init_quad = {'d': d, 'scramble': True, 'strength': strength, + 'optimization': optimization} + super()._initialize(d=d, rng=rng, optimization=optimization) + self.scramble = scramble + + lhs_method_strength = { + 1: self._random_lhs, + 2: self._random_oa_lhs + } + + try: + self.lhs_method: Callable = lhs_method_strength[strength] + except KeyError as exc: + message = (f"{strength!r} is not a valid strength. It must be one" + f" of {set(lhs_method_strength)!r}") + raise ValueError(message) from exc + + def _random( + self, n: IntNumber = 1, *, workers: IntNumber = 1 + ) -> np.ndarray: + lhs = self.lhs_method(n) + return lhs + + def _random_lhs(self, n: IntNumber = 1) -> np.ndarray: + """Base LHS algorithm.""" + if not self.scramble: + samples: np.ndarray | float = 0.5 + else: + samples = self.rng.uniform(size=(n, self.d)) + + perms = np.tile(np.arange(1, n + 1), (int(self.d), 1)) + for i in range(self.d): + self.rng.shuffle(perms[i, :]) + perms = perms.T + + samples = (perms - samples) / n + return samples + + def _random_oa_lhs(self, n: IntNumber = 4) -> np.ndarray: + """Orthogonal array based LHS of strength 2.""" + p = np.sqrt(n).astype(int) + n_row = p**2 + n_col = p + 1 + + primes = primes_from_2_to(p + 1) + if p not in primes or n != n_row: + raise ValueError( + "n is not the square of a prime number. Close" + f" values are {primes[-2:]**2}" + ) + if self.d > p + 1: + raise ValueError("n is too small for d. Must be n > (d-1)**2") + + oa_sample = np.zeros(shape=(n_row, n_col), dtype=int) + + # OA of strength 2 + arrays = np.tile(np.arange(p), (2, 1)) + oa_sample[:, :2] = np.stack(np.meshgrid(*arrays), + axis=-1).reshape(-1, 2) + for p_ in range(1, p): + oa_sample[:, 2+p_-1] = np.mod(oa_sample[:, 0] + + p_*oa_sample[:, 1], p) + + # scramble the OA + oa_sample_ = np.empty(shape=(n_row, n_col), dtype=int) + for j in range(n_col): + perms = self.rng.permutation(p) + oa_sample_[:, j] = perms[oa_sample[:, j]] + + oa_sample = oa_sample_ + # following is making a scrambled OA into an OA-LHS + oa_lhs_sample = np.zeros(shape=(n_row, n_col)) + lhs_engine = LatinHypercube(d=1, scramble=self.scramble, strength=1, + rng=self.rng) # type: QMCEngine + for j in range(n_col): + for k in range(p): + idx = oa_sample[:, j] == k + lhs = lhs_engine.random(p).flatten() + oa_lhs_sample[:, j][idx] = lhs + oa_sample[:, j][idx] + + oa_lhs_sample /= p + + return oa_lhs_sample[:, :self.d] + + +class Sobol(QMCEngine): + """Engine for generating (scrambled) Sobol' sequences. + + Sobol' sequences are low-discrepancy, quasi-random numbers. Points + can be drawn using two methods: + + * `random_base2`: safely draw :math:`n=2^m` points. This method + guarantees the balance properties of the sequence. + * `random`: draw an arbitrary number of points from the + sequence. See warning below. + + Parameters + ---------- + d : int + Dimensionality of the sequence. Max dimensionality is 21201. + scramble : bool, optional + If True, use LMS+shift scrambling. Otherwise, no scrambling is done. + Default is True. + bits : int, optional + Number of bits of the generator. Control the maximum number of points + that can be generated, which is ``2**bits``. Maximal value is 64. + It does not correspond to the return type, which is always + ``np.float64`` to prevent points from repeating themselves. + Default is None, which for backward compatibility, corresponds to 30. + + .. versionadded:: 1.9.0 + optimization : {None, "random-cd", "lloyd"}, optional + Whether to use an optimization scheme to improve the quality after + sampling. Note that this is a post-processing step that does not + guarantee that all properties of the sample will be conserved. + Default is None. + + * ``random-cd``: random permutations of coordinates to lower the + centered discrepancy. The best sample based on the centered + discrepancy is constantly updated. Centered discrepancy-based + sampling shows better space-filling robustness toward 2D and 3D + subprojections compared to using other discrepancy measures. + * ``lloyd``: Perturb samples using a modified Lloyd-Max algorithm. + The process converges to equally spaced samples. + + .. versionadded:: 1.10.0 + + rng : `numpy.random.Generator`, optional + Pseudorandom number generator state. When `rng` is None, a new + `numpy.random.Generator` is created using entropy from the + operating system. Types other than `numpy.random.Generator` are + passed to `numpy.random.default_rng` to instantiate a ``Generator``. + + .. versionchanged:: 1.15.0 + + As part of the `SPEC-007 `_ + transition from use of `numpy.random.RandomState` to + `numpy.random.Generator`, this keyword was changed from `seed` to + `rng`. For an interim period, both keywords will continue to work, although + only one may be specified at a time. After the interim period, function + calls using the `seed` keyword will emit warnings. Following a + deprecation period, the `seed` keyword will be removed. + + Notes + ----- + Sobol' sequences [1]_ provide :math:`n=2^m` low discrepancy points in + :math:`[0,1)^{d}`. Scrambling them [3]_ makes them suitable for singular + integrands, provides a means of error estimation, and can improve their + rate of convergence. The scrambling strategy which is implemented is a + (left) linear matrix scramble (LMS) followed by a digital random shift + (LMS+shift) [2]_. + + There are many versions of Sobol' sequences depending on their + 'direction numbers'. This code uses direction numbers from [4]_. Hence, + the maximum number of dimension is 21201. The direction numbers have been + precomputed with search criterion 6 and can be retrieved at + https://web.maths.unsw.edu.au/~fkuo/sobol/. + + .. warning:: + + Sobol' sequences are a quadrature rule and they lose their balance + properties if one uses a sample size that is not a power of 2, or skips + the first point, or thins the sequence [5]_. + + If :math:`n=2^m` points are not enough then one should take :math:`2^M` + points for :math:`M>m`. When scrambling, the number R of independent + replicates does not have to be a power of 2. + + Sobol' sequences are generated to some number :math:`B` of bits. + After :math:`2^B` points have been generated, the sequence would + repeat. Hence, an error is raised. + The number of bits can be controlled with the parameter `bits`. + + References + ---------- + .. [1] I. M. Sobol', "The distribution of points in a cube and the accurate + evaluation of integrals." Zh. Vychisl. Mat. i Mat. Phys., 7:784-802, + 1967. + .. [2] J. Matousek, "On the L2-discrepancy for anchored boxes." + J. of Complexity 14, 527-556, 1998. + .. [3] Art B. Owen, "Scrambling Sobol and Niederreiter-Xing points." + Journal of Complexity, 14(4):466-489, December 1998. + .. [4] S. Joe and F. Y. Kuo, "Constructing sobol sequences with better + two-dimensional projections." SIAM Journal on Scientific Computing, + 30(5):2635-2654, 2008. + .. [5] Art B. Owen, "On dropping the first Sobol' point." + :arxiv:`2008.08051`, 2020. + + Examples + -------- + Generate samples from a low discrepancy sequence of Sobol'. + + >>> from scipy.stats import qmc + >>> sampler = qmc.Sobol(d=2, scramble=False) + >>> sample = sampler.random_base2(m=3) + >>> sample + array([[0. , 0. ], + [0.5 , 0.5 ], + [0.75 , 0.25 ], + [0.25 , 0.75 ], + [0.375, 0.375], + [0.875, 0.875], + [0.625, 0.125], + [0.125, 0.625]]) + + Compute the quality of the sample using the discrepancy criterion. + + >>> qmc.discrepancy(sample) + 0.013882107204860938 + + To continue an existing design, extra points can be obtained + by calling again `random_base2`. Alternatively, you can skip some + points like: + + >>> _ = sampler.reset() + >>> _ = sampler.fast_forward(4) + >>> sample_continued = sampler.random_base2(m=2) + >>> sample_continued + array([[0.375, 0.375], + [0.875, 0.875], + [0.625, 0.125], + [0.125, 0.625]]) + + Finally, samples can be scaled to bounds. + + >>> l_bounds = [0, 2] + >>> u_bounds = [10, 5] + >>> qmc.scale(sample_continued, l_bounds, u_bounds) + array([[3.75 , 3.125], + [8.75 , 4.625], + [6.25 , 2.375], + [1.25 , 3.875]]) + + """ + + MAXDIM: ClassVar[int] = _MAXDIM + + @_transition_to_rng('seed', replace_doc=False) + def __init__( + self, d: IntNumber, *, scramble: bool = True, + bits: IntNumber | None = None, rng: SeedType = None, + optimization: Literal["random-cd", "lloyd"] | None = None + ) -> None: + # Used in `scipy.integrate.qmc_quad` + self._init_quad = {'d': d, 'scramble': True, 'bits': bits, + 'optimization': optimization} + + super()._initialize(d=d, optimization=optimization, rng=rng) + if d > self.MAXDIM: + raise ValueError( + f"Maximum supported dimensionality is {self.MAXDIM}." + ) + + self.bits = bits + self.dtype_i: type + self.scramble = scramble + + if self.bits is None: + self.bits = 30 + + if self.bits <= 32: + self.dtype_i = np.uint32 + elif 32 < self.bits <= 64: + self.dtype_i = np.uint64 + else: + raise ValueError("Maximum supported 'bits' is 64") + + self.maxn = 2**self.bits + + # v is d x maxbit matrix + self._sv: np.ndarray = np.zeros((d, self.bits), dtype=self.dtype_i) + _initialize_v(self._sv, dim=d, bits=self.bits) + + if not scramble: + self._shift: np.ndarray = np.zeros(d, dtype=self.dtype_i) + else: + # scramble self._shift and self._sv + self._scramble() + + self._quasi = self._shift.copy() + + # normalization constant with the largest possible number + # calculate in Python to not overflow int with 2**64 + self._scale = 1.0 / 2 ** self.bits + + self._first_point = (self._quasi * self._scale).reshape(1, -1) + # explicit casting to float64 + self._first_point = self._first_point.astype(np.float64) + + def _scramble(self) -> None: + """Scramble the sequence using LMS+shift.""" + # Generate shift vector + self._shift = np.dot( + rng_integers(self.rng, 2, size=(self.d, self.bits), + dtype=self.dtype_i), + # pyrefly:ignore[no-matching-overload] + 2 ** np.arange(self.bits, dtype=self.dtype_i), + ) + # Generate lower triangular matrices (stacked across dimensions) + ltm = np.tril(rng_integers(self.rng, 2, + size=(self.d, self.bits, self.bits), + dtype=self.dtype_i)) + _cscramble( + dim=self.d, bits=self.bits, # type: ignore[arg-type] + ltm=ltm, sv=self._sv + ) + + def _random( + self, n: IntNumber = 1, *, workers: IntNumber = 1 + ) -> np.ndarray: + """Draw next point(s) in the Sobol' sequence. + + Parameters + ---------- + n : int, optional + Number of samples to generate in the parameter space. Default is 1. + + Returns + ------- + sample : array_like (n, d) + Sobol' sample. + + """ + sample: np.ndarray = np.empty((n, self.d), dtype=np.float64) + + if n == 0: + return sample + + total_n = self.num_generated + n + if total_n > self.maxn: + msg = ( + f"At most 2**{self.bits}={self.maxn} distinct points can be " + f"generated. {self.num_generated} points have been previously " + f"generated, then: n={self.num_generated}+{n}={total_n}. " + ) + if self.bits != 64: + msg += "Consider increasing `bits`." + raise ValueError(msg) + + if self.num_generated == 0: + # verify n is 2**n + if not (n & (n - 1) == 0): + warnings.warn("The balance properties of Sobol' points require" + " n to be a power of 2.", stacklevel=3) + + if n == 1: + sample = self._first_point + else: + _draw( + n=n - 1, num_gen=self.num_generated, dim=self.d, + scale=self._scale, sv=self._sv, quasi=self._quasi, + sample=sample + ) + sample = np.concatenate( + [self._first_point, sample] + )[:n] + else: + _draw( + n=n, num_gen=self.num_generated - 1, dim=self.d, + scale=self._scale, sv=self._sv, quasi=self._quasi, + sample=sample + ) + + return sample + + def random_base2(self, m: IntNumber) -> np.ndarray: + """Draw point(s) from the Sobol' sequence. + + This function draws :math:`n=2^m` points in the parameter space + ensuring the balance properties of the sequence. + + Parameters + ---------- + m : int + Logarithm in base 2 of the number of samples; i.e., n = 2^m. + + Returns + ------- + sample : array_like (n, d) + Sobol' sample. + + """ + n = 2 ** m + + total_n = self.num_generated + n + if not (total_n & (total_n - 1) == 0): + raise ValueError('The balance properties of Sobol\' points require ' + f'n to be a power of 2. {self.num_generated} points ' + 'have been previously generated, then: ' + f'n={self.num_generated}+2**{m}={total_n}. ' + 'If you still want to do this, the function ' + '\'Sobol.random()\' can be used.' + ) + + return self.random(n) + + def reset(self) -> "Sobol": + """Reset the engine to base state. + + Returns + ------- + engine : Sobol + Engine reset to its base state. + + """ + super().reset() + self._quasi = self._shift.copy() + return self + + def fast_forward(self, n: IntNumber) -> "Sobol": + """Fast-forward the sequence by `n` positions. + + Parameters + ---------- + n : int + Number of points to skip in the sequence. + + Returns + ------- + engine : Sobol + The fast-forwarded engine. + + """ + if self.num_generated == 0: + _fast_forward( + n=n - 1, num_gen=self.num_generated, dim=self.d, + sv=self._sv, quasi=self._quasi + ) + else: + _fast_forward( + n=n, num_gen=self.num_generated - 1, dim=self.d, + sv=self._sv, quasi=self._quasi + ) + self.num_generated += n + return self + + +class PoissonDisk(QMCEngine): + """Poisson disk sampling. + + Parameters + ---------- + d : int + Dimension of the parameter space. + radius : float + Minimal distance to keep between points when sampling new candidates. + hypersphere : {"volume", "surface"}, optional + Sampling strategy to generate potential candidates to be added in the + final sample. Default is "volume". + + * ``volume``: original Bridson algorithm as described in [1]_. + New candidates are sampled *within* the hypersphere. + * ``surface``: only sample the surface of the hypersphere. + ncandidates : int + Number of candidates to sample per iteration. More candidates result + in a denser sampling as more candidates can be accepted per iteration. + optimization : {None, "random-cd", "lloyd"}, optional + Whether to use an optimization scheme to improve the quality after + sampling. Note that this is a post-processing step that does not + guarantee that all properties of the sample will be conserved. + Default is None. + + * ``random-cd``: random permutations of coordinates to lower the + centered discrepancy. The best sample based on the centered + discrepancy is constantly updated. Centered discrepancy-based + sampling shows better space-filling robustness toward 2D and 3D + subprojections compared to using other discrepancy measures. + * ``lloyd``: Perturb samples using a modified Lloyd-Max algorithm. + The process converges to equally spaced samples. + + .. versionadded:: 1.10.0 + + rng : `numpy.random.Generator`, optional + Pseudorandom number generator state. When `rng` is None, a new + `numpy.random.Generator` is created using entropy from the + operating system. Types other than `numpy.random.Generator` are + passed to `numpy.random.default_rng` to instantiate a ``Generator``. + + .. versionchanged:: 1.15.0 + + As part of the `SPEC-007 `_ + transition from use of `numpy.random.RandomState` to + `numpy.random.Generator`, this keyword was changed from `seed` to + `rng`. For an interim period, both keywords will continue to work, although + only one may be specified at a time. After the interim period, function + calls using the `seed` keyword will emit warnings. Following a + deprecation period, the `seed` keyword will be removed. + + l_bounds, u_bounds : array_like (d,) + Lower and upper bounds of target sample data. + + Notes + ----- + Poisson disk sampling is an iterative sampling strategy. Starting from + a seed sample, `ncandidates` are sampled in the hypersphere + surrounding the seed. Candidates below a certain `radius` or outside the + domain are rejected. New samples are added in a pool of sample seed. The + process stops when the pool is empty or when the number of required + samples is reached. + + The maximum number of point that a sample can contain is directly linked + to the `radius`. As the dimension of the space increases, a higher radius + spreads the points further and help overcome the curse of dimensionality. + See the :ref:`quasi monte carlo tutorial ` for more + details. + + .. warning:: + + The algorithm is more suitable for low dimensions and sampling size + due to its iterative nature and memory requirements. + Selecting a small radius with a high dimension would + mean that the space could contain more samples than using lower + dimension or a bigger radius. + + Some code taken from [2]_, written consent given on 31.03.2021 + by the original author, Shamis, for free use in SciPy under + the 3-clause BSD. + + References + ---------- + .. [1] Robert Bridson, "Fast Poisson Disk Sampling in Arbitrary + Dimensions." SIGGRAPH, 2007. + .. [2] `StackOverflow `__. + + Examples + -------- + Generate a 2D sample using a `radius` of 0.2. + + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from matplotlib.collections import PatchCollection + >>> from scipy.stats import qmc + >>> + >>> rng = np.random.default_rng() + >>> radius = 0.2 + >>> engine = qmc.PoissonDisk(d=2, radius=radius, rng=rng) + >>> sample = engine.random(20) + + Visualizing the 2D sample and showing that no points are closer than + `radius`. ``radius/2`` is used to visualize non-intersecting circles. + If two samples are exactly at `radius` from each other, then their circle + of radius ``radius/2`` will touch. + + >>> fig, ax = plt.subplots() + >>> _ = ax.scatter(sample[:, 0], sample[:, 1]) + >>> circles = [plt.Circle((xi, yi), radius=radius/2, fill=False) + ... for xi, yi in sample] + >>> collection = PatchCollection(circles, match_original=True) + >>> ax.add_collection(collection) + >>> _ = ax.set(aspect='equal', xlabel=r'$x_1$', ylabel=r'$x_2$', + ... xlim=[0, 1], ylim=[0, 1]) + >>> plt.show() + + Such visualization can be seen as circle packing: how many circle can + we put in the space. It is an np-hard problem. The method `fill_space` + can be used to add samples until no more samples can be added. This is + a hard problem and parameters may need to be adjusted manually. Beware of + the dimension: as the dimensionality increases, the number of samples + required to fill the space increases exponentially + (curse-of-dimensionality). + + """ + + @_transition_to_rng('seed', replace_doc=False) + def __init__( + self, + d: IntNumber, + *, + radius: DecimalNumber = 0.05, + hypersphere: Literal["volume", "surface"] = "volume", + ncandidates: IntNumber = 30, + optimization: Literal["random-cd", "lloyd"] | None = None, + rng: SeedType = None, + l_bounds: "npt.ArrayLike | None" = None, + u_bounds: "npt.ArrayLike | None" = None, + ) -> None: + # Used in `scipy.integrate.qmc_quad` + self._init_quad = {'d': d, 'radius': radius, + 'hypersphere': hypersphere, + 'ncandidates': ncandidates, + 'optimization': optimization} + super()._initialize(d=d, optimization=optimization, rng=rng) + + hypersphere_sample = { + "volume": self._hypersphere_volume_sample, + "surface": self._hypersphere_surface_sample + } + + try: + self.hypersphere_method = hypersphere_sample[hypersphere] + except KeyError as exc: + message = ( + f"{hypersphere!r} is not a valid hypersphere sampling" + f" method. It must be one of {set(hypersphere_sample)!r}") + raise ValueError(message) from exc + + # size of the sphere from which the samples are drawn relative to the + # size of a disk (radius) + # for the surface sampler, all new points are almost exactly 1 radius + # away from at least one existing sample +eps to avoid rejection + self.radius_factor = 2 if hypersphere == "volume" else 1.001 + self.radius = radius + self.radius_squared = self.radius**2 + + # sample to generate per iteration in the hypersphere around center + self.ncandidates = ncandidates + + if u_bounds is None: + u_bounds = np.ones(d) + if l_bounds is None: + l_bounds = np.zeros(d) + self.l_bounds, self.u_bounds = _validate_bounds( + l_bounds=l_bounds, u_bounds=u_bounds, d=int(d) + ) + + with np.errstate(divide='ignore'): + self.cell_size = self.radius / np.sqrt(self.d) + self.grid_size = ( + np.ceil((self.u_bounds - self.l_bounds) / self.cell_size) + ).astype(int) + + self._initialize_grid_pool() + + def _initialize_grid_pool(self): + """Sampling pool and sample grid.""" + self.sample_pool = [] + # Positions of cells + # n-dim value for each grid cell + self.sample_grid = np.empty( + np.append(self.grid_size, self.d), + dtype=np.float32 + ) + # Initialise empty cells with NaNs + self.sample_grid.fill(np.nan) + + def _random( + self, n: IntNumber = 1, *, workers: IntNumber = 1 + ) -> np.ndarray: + """Draw `n` in the interval ``[l_bounds, u_bounds]``. + + Note that it can return fewer samples if the space is full. + See the note section of the class. + + Parameters + ---------- + n : int, optional + Number of samples to generate in the parameter space. Default is 1. + + Returns + ------- + sample : array_like (n, d) + QMC sample. + + """ + if n == 0 or self.d == 0: + return np.empty((n, self.d)) + + def in_limits(sample: np.ndarray) -> bool: + for i in range(self.d): + if (sample[i] > self.u_bounds[i] or sample[i] < self.l_bounds[i]): + return False + return True + + def in_neighborhood(candidate: np.ndarray, n: int = 2) -> bool: + """ + Check if there are samples closer than ``radius_squared`` to the + `candidate` sample. + """ + indices = ((candidate - self.l_bounds) / self.cell_size).astype(int) + ind_min = np.maximum(indices - n, 0) + ind_max = np.minimum(indices + n + 1, self.grid_size) + + # Check if the center cell is empty + if not np.isnan(self.sample_grid[tuple(indices)][0]): + return True + + a = [slice(ind_min[i], ind_max[i]) for i in range(self.d)] + + # guards against: invalid value encountered in less as we are + # comparing with nan and returns False. Which is wanted. + with np.errstate(invalid='ignore'): + if np.any( + np.sum( + np.square(candidate - self.sample_grid[tuple(a)]), + axis=self.d + ) < self.radius_squared + ): + return True + + return False + + def add_sample(candidate: np.ndarray) -> None: + self.sample_pool.append(candidate) + indices = ((candidate - self.l_bounds) / self.cell_size).astype(int) + self.sample_grid[tuple(indices)] = candidate + curr_sample.append(candidate) + + curr_sample: list[np.ndarray] = [] + + if len(self.sample_pool) == 0: + # the pool is being initialized with a single random sample + add_sample(self.rng.uniform(self.l_bounds, self.u_bounds)) + num_drawn = 1 + else: + num_drawn = 0 + + # exhaust sample pool to have up to n sample + while len(self.sample_pool) and num_drawn < n: + # select a sample from the available pool + idx_center = rng_integers(self.rng, len(self.sample_pool)) + center = self.sample_pool[idx_center] + del self.sample_pool[idx_center] + + # generate candidates around the center sample + candidates = self.hypersphere_method( + center, self.radius * self.radius_factor, self.ncandidates + ) + + # keep candidates that satisfy some conditions + for candidate in candidates: + if in_limits(candidate) and not in_neighborhood(candidate): + add_sample(candidate) + + num_drawn += 1 + if num_drawn >= n: + break + + self.num_generated += num_drawn + return np.array(curr_sample) + + def fill_space(self) -> np.ndarray: + """Draw ``n`` samples in the interval ``[l_bounds, u_bounds]``. + + Unlike `random`, this method will try to add points until + the space is full. Depending on ``candidates`` (and to a lesser extent + other parameters), some empty areas can still be present in the sample. + + .. warning:: + + This can be extremely slow in high dimensions or if the + ``radius`` is very small-with respect to the dimensionality. + + Returns + ------- + sample : array_like (n, d) + QMC sample. + + """ + return self.random(np.inf) # type: ignore[arg-type] + + def reset(self) -> "PoissonDisk": + """Reset the engine to base state. + + Returns + ------- + engine : PoissonDisk + Engine reset to its base state. + + """ + super().reset() + self._initialize_grid_pool() + return self + + def _hypersphere_volume_sample( + self, center: np.ndarray, radius: DecimalNumber, + candidates: IntNumber = 1 + ) -> np.ndarray: + """Uniform sampling within hypersphere.""" + # should remove samples within r/2 + x = self.rng.standard_normal(size=(candidates, self.d)) + ssq = np.sum(x**2, axis=1) + fr = radius * gammainc(self.d/2, ssq/2)**(1/self.d) / np.sqrt(ssq) + fr_tiled = np.tile(fr.reshape(-1, 1), (1, int(self.d))) + p = center + np.multiply(x, fr_tiled) + return p + + def _hypersphere_surface_sample( + self, center: np.ndarray, radius: DecimalNumber, + candidates: IntNumber = 1 + ) -> np.ndarray: + """Uniform sampling on the hypersphere's surface.""" + vec = self.rng.standard_normal(size=(candidates, self.d)) + vec /= np.linalg.norm(vec, axis=1)[:, None] + p = center + np.multiply(vec, radius) + return p + + +class MultivariateNormalQMC: + r"""QMC sampling from a multivariate Normal :math:`N(\mu, \Sigma)`. + + Parameters + ---------- + mean : array_like (d,) + The mean vector. Where ``d`` is the dimension. + cov : array_like (d, d), optional + The covariance matrix. If omitted, use `cov_root` instead. + If both `cov` and `cov_root` are omitted, use the identity matrix. + cov_root : array_like (d, d'), optional + A root decomposition of the covariance matrix, where ``d'`` may be less + than ``d`` if the covariance is not full rank. If omitted, use `cov`. + inv_transform : bool, optional + If True, use inverse transform instead of Box-Muller. Default is True. + engine : QMCEngine, optional + Quasi-Monte Carlo engine sampler. If None, `Sobol` is used. + rng : `numpy.random.Generator`, optional + Pseudorandom number generator state. When `rng` is None, a new + `numpy.random.Generator` is created using entropy from the + operating system. Types other than `numpy.random.Generator` are + passed to `numpy.random.default_rng` to instantiate a ``Generator``. + + .. versionchanged:: 1.15.0 + + As part of the `SPEC-007 `_ + transition from use of `numpy.random.RandomState` to + `numpy.random.Generator`, this keyword was changed from `seed` to + `rng`. For an interim period, both keywords will continue to work, although + only one may be specified at a time. After the interim period, function + calls using the `seed` keyword will emit warnings. Following a + deprecation period, the `seed` keyword will be removed. + + Examples + -------- + >>> import matplotlib.pyplot as plt + >>> from scipy.stats import qmc + >>> dist = qmc.MultivariateNormalQMC(mean=[0, 5], cov=[[1, 0], [0, 1]]) + >>> sample = dist.random(512) + >>> _ = plt.scatter(sample[:, 0], sample[:, 1]) + >>> plt.show() + + """ + + @_transition_to_rng('seed', replace_doc=False) + def __init__( + self, + mean: "npt.ArrayLike", + cov: "npt.ArrayLike | None" = None, + *, + cov_root: "npt.ArrayLike | None" = None, + inv_transform: bool = True, + engine: QMCEngine | None = None, + rng: SeedType = None, + ) -> None: + mean = np.asarray(np.atleast_1d(mean)) + d = mean.shape[0] + if cov is not None: + # covariance matrix provided + cov = np.asarray(np.atleast_2d(cov)) + # check for square/symmetric cov matrix and mean vector has the + # same d + if not mean.shape[0] == cov.shape[0]: + raise ValueError("Dimension mismatch between mean and " + "covariance.") + if not np.allclose(cov, cov.transpose()): + raise ValueError("Covariance matrix is not symmetric.") + # compute Cholesky decomp; if it fails, do the eigen decomposition + try: + cov_root = np.linalg.cholesky(cov).transpose() + except np.linalg.LinAlgError: + eigval, eigvec = np.linalg.eigh(cov) + if not np.all(eigval >= -1.0e-8): + raise ValueError("Covariance matrix not PSD.") + eigval = np.clip(eigval, 0.0, None) + cov_root = (eigvec * np.sqrt(eigval)).transpose() + elif cov_root is not None: + # root decomposition provided + cov_root = np.atleast_2d(cov_root) + if not mean.shape[0] == cov_root.shape[0]: + raise ValueError("Dimension mismatch between mean and " + "covariance.") + else: + # corresponds to identity covariance matrix + cov_root = None + + self._inv_transform = inv_transform + + if not inv_transform: + # to apply Box-Muller, we need an even number of dimensions + engine_dim = 2 * math.ceil(d / 2) + else: + engine_dim = d + if engine is None: + # Need this during SPEC 7 transition to prevent `RandomState` + # from being passed via `rng`. + kwarg = "seed" if isinstance(rng, np.random.RandomState) else "rng" + kwargs = {kwarg: rng} + self.engine: QMCEngine = Sobol( + d=engine_dim, scramble=True, bits=30, **kwargs + ) + elif isinstance(engine, QMCEngine): + if engine.d != engine_dim: + raise ValueError("Dimension of `engine` must be consistent" + " with dimensions of mean and covariance." + " If `inv_transform` is False, it must be" + " an even number.") + self.engine = engine + else: + raise ValueError("`engine` must be an instance of " + "`scipy.stats.qmc.QMCEngine` or `None`.") + + self._mean = mean + self._corr_matrix = cov_root + + self._d = d + + def random(self, n: IntNumber = 1) -> np.ndarray: + """Draw `n` QMC samples from the multivariate Normal. + + Parameters + ---------- + n : int, optional + Number of samples to generate in the parameter space. Default is 1. + + Returns + ------- + sample : array_like (n, d) + Sample. + + """ + base_samples = self._standard_normal_samples(n) + return self._correlate(base_samples) + + def _correlate(self, base_samples: np.ndarray) -> np.ndarray: + if self._corr_matrix is not None: + return base_samples @ self._corr_matrix + self._mean + else: + # avoid multiplying with identity here + return base_samples + self._mean + + def _standard_normal_samples(self, n: IntNumber = 1) -> np.ndarray: + """Draw `n` QMC samples from the standard Normal :math:`N(0, I_d)`. + + Parameters + ---------- + n : int, optional + Number of samples to generate in the parameter space. Default is 1. + + Returns + ------- + sample : array_like (n, d) + Sample. + + """ + # get base samples + samples = self.engine.random(n) + if self._inv_transform: + # apply inverse transform + # (values to close to 0/1 result in inf values) + return stats.norm.ppf(0.5 + (1 - 1e-10) * (samples - 0.5)) # type: ignore[attr-defined] # noqa: E501 + else: + # apply Box-Muller transform (note: indexes starting from 1) + even = np.arange(0, samples.shape[-1], 2) + Rs = np.sqrt(-2 * np.log(samples[:, even])) + thetas = 2 * math.pi * samples[:, 1 + even] + cos = np.cos(thetas) + sin = np.sin(thetas) + transf_samples = np.stack([Rs * cos, Rs * sin], + -1).reshape(n, -1) + # make sure we only return the number of dimension requested + return transf_samples[:, : self._d] + + +class MultinomialQMC: + r"""QMC sampling from a multinomial distribution. + + Parameters + ---------- + pvals : array_like (k,) + Vector of probabilities of size ``k``, where ``k`` is the number + of categories. Elements must be non-negative and sum to 1. + n_trials : int + Number of trials. + engine : QMCEngine, optional + Quasi-Monte Carlo engine sampler. If None, `Sobol` is used. + rng : `numpy.random.Generator`, optional + Pseudorandom number generator state. When `rng` is None, a new + `numpy.random.Generator` is created using entropy from the + operating system. Types other than `numpy.random.Generator` are + passed to `numpy.random.default_rng` to instantiate a ``Generator``. + + .. versionchanged:: 1.15.0 + + As part of the `SPEC-007 `_ + transition from use of `numpy.random.RandomState` to + `numpy.random.Generator`, this keyword was changed from `seed` to + `rng`. For an interim period, both keywords will continue to work, although + only one may be specified at a time. After the interim period, function + calls using the `seed` keyword will emit warnings. Following a + deprecation period, the `seed` keyword will be removed. + + Examples + -------- + Let's define 3 categories and for a given sample, the sum of the trials + of each category is 8. The number of trials per category is determined + by the `pvals` associated to each category. + Then, we sample this distribution 64 times. + + >>> import matplotlib.pyplot as plt + >>> from scipy.stats import qmc + >>> dist = qmc.MultinomialQMC( + ... pvals=[0.2, 0.4, 0.4], n_trials=10, engine=qmc.Halton(d=1) + ... ) + >>> sample = dist.random(64) + + We can plot the sample and verify that the median of number of trials + for each category is following the `pvals`. That would be + ``pvals * n_trials = [2, 4, 4]``. + + >>> fig, ax = plt.subplots() + >>> ax.yaxis.get_major_locator().set_params(integer=True) + >>> _ = ax.boxplot(sample) + >>> ax.set(xlabel="Categories", ylabel="Trials") + >>> plt.show() + + """ + + @_transition_to_rng('seed', replace_doc=False) + def __init__( + self, + pvals: "npt.ArrayLike", + n_trials: IntNumber, + *, + engine: QMCEngine | None = None, + rng: SeedType = None, + ) -> None: + self.pvals = np.atleast_1d(np.asarray(pvals)) + if np.min(pvals) < 0: + raise ValueError('Elements of pvals must be non-negative.') + if not np.isclose(np.sum(pvals), 1): + raise ValueError('Elements of pvals must sum to 1.') + self.n_trials = n_trials + if engine is None: + # Need this during SPEC 7 transition to prevent `RandomState` + # from being passed via `rng`. + kwarg = "seed" if isinstance(rng, np.random.RandomState) else "rng" + kwargs = {kwarg: rng} + self.engine: QMCEngine = Sobol(d=1, scramble=True, bits=30, **kwargs) + elif isinstance(engine, QMCEngine): + if engine.d != 1: + raise ValueError("Dimension of `engine` must be 1.") + self.engine = engine + else: + raise ValueError("`engine` must be an instance of " + "`scipy.stats.qmc.QMCEngine` or `None`.") + + def random(self, n: IntNumber = 1) -> np.ndarray: + """Draw `n` QMC samples from the multinomial distribution. + + Parameters + ---------- + n : int, optional + Number of samples to generate in the parameter space. Default is 1. + + Returns + ------- + samples : array_like (n, pvals) + Sample. + + """ + sample = np.empty((n, len(self.pvals))) + for i in range(n): + base_draws = self.engine.random(self.n_trials).ravel() + p_cumulative = np.empty_like(self.pvals, dtype=float) + _fill_p_cumulative(np.array(self.pvals, dtype=float), p_cumulative) + sample_ = np.zeros_like(self.pvals, dtype=np.intp) + _categorize(base_draws, p_cumulative, sample_) + sample[i] = sample_ + return sample + + +def _select_optimizer( + optimization: Literal["random-cd", "lloyd"] | None, config: dict +) -> Callable | None: + """A factory for optimization methods.""" + optimization_method: dict[str, Callable] = { + "random-cd": _random_cd, + "lloyd": _lloyd_centroidal_voronoi_tessellation + } + + optimizer: partial | None + if optimization is not None: + try: + optimizer_ = optimization_method[optimization.lower()] + except KeyError as exc: + message = (f"{optimization!r} is not a valid optimization" + f" method. It must be one of" + f" {set(optimization_method)!r}") + raise ValueError(message) from exc + + # config + optimizer = partial(optimizer_, **config) + else: + optimizer = None + + return optimizer + + +def _random_cd( + best_sample: np.ndarray, n_iters: int, n_nochange: int, rng: GeneratorType, + **kwargs: dict +) -> np.ndarray: + """Optimal LHS on CD. + + Create a base LHS and do random permutations of coordinates to + lower the centered discrepancy. + Because it starts with a normal LHS, it also works with the + `scramble` keyword argument. + + Two stopping criterion are used to stop the algorithm: at most, + `n_iters` iterations are performed; or if there is no improvement + for `n_nochange` consecutive iterations. + """ + del kwargs # only use keywords which are defined, needed by factory + + n, d = best_sample.shape + + if d == 0 or n == 0: + return np.empty((n, d)) + + if d == 1 or n == 1: + # discrepancy measures are invariant under permuting factors and runs + return best_sample + + best_disc = discrepancy(best_sample) + + bounds = ([0, d - 1], + [0, n - 1], + [0, n - 1]) + + n_nochange_ = 0 + n_iters_ = 0 + while n_nochange_ < n_nochange and n_iters_ < n_iters: + n_iters_ += 1 + + col = rng_integers(rng, *bounds[0], endpoint=True) # type: ignore[misc] + row_1 = rng_integers(rng, *bounds[1], endpoint=True) # type: ignore[misc] + row_2 = rng_integers(rng, *bounds[2], endpoint=True) # type: ignore[misc] + disc = _perturb_discrepancy(best_sample, + row_1, row_2, col, + best_disc) + if disc < best_disc: + best_sample[row_1, col], best_sample[row_2, col] = ( + best_sample[row_2, col], best_sample[row_1, col]) + + best_disc = disc + n_nochange_ = 0 + else: + n_nochange_ += 1 + + return best_sample + + +def _l1_norm(sample: np.ndarray) -> float: + return distance.pdist(sample, 'cityblock').min() + + +def _lloyd_iteration( + sample: np.ndarray, + decay: float, + qhull_options: str +) -> np.ndarray: + """Lloyd-Max algorithm iteration. + + Based on the implementation of Stéfan van der Walt: + + https://github.com/stefanv/lloyd + + which is: + + Copyright (c) 2021-04-21 Stéfan van der Walt + https://github.com/stefanv/lloyd + MIT License + + Parameters + ---------- + sample : array_like (n, d) + The sample to iterate on. + decay : float + Relaxation decay. A positive value would move the samples toward + their centroid, and negative value would move them away. + 1 would move the samples to their centroid. + qhull_options : str + Additional options to pass to Qhull. See Qhull manual + for details. (Default: "Qbb Qc Qz Qj Qx" for ndim > 4 and + "Qbb Qc Qz Qj" otherwise.) + + Returns + ------- + sample : array_like (n, d) + The sample after an iteration of Lloyd's algorithm. + + """ + new_sample = np.empty_like(sample) + + voronoi = Voronoi(sample, qhull_options=qhull_options) + + for ii, idx in enumerate(voronoi.point_region): + # the region is a series of indices into self.voronoi.vertices + # remove samples at infinity, designated by index -1 + region = [i for i in voronoi.regions[idx] if i != -1] + + # get the vertices for this region + verts = voronoi.vertices[region] + + # clipping would be wrong, we need to intersect + # verts = np.clip(verts, 0, 1) + + # move samples towards centroids: + # Centroid in n-D is the mean for uniformly distributed nodes + # of a geometry. + centroid = np.mean(verts, axis=0) + new_sample[ii] = sample[ii] + (centroid - sample[ii]) * decay + + # only update sample to centroid within the region + is_valid = np.all(np.logical_and(new_sample >= 0, new_sample <= 1), axis=1) + sample[is_valid] = new_sample[is_valid] + + return sample + + +def _lloyd_centroidal_voronoi_tessellation( + sample: "npt.ArrayLike", + *, + tol: DecimalNumber = 1e-5, + maxiter: IntNumber = 10, + qhull_options: str | None = None, + **kwargs: dict +) -> np.ndarray: + """Approximate Centroidal Voronoi Tessellation. + + Perturb samples in N-dimensions using Lloyd-Max algorithm. + + Parameters + ---------- + sample : array_like (n, d) + The sample to iterate on. With ``n`` the number of samples and ``d`` + the dimension. Samples must be in :math:`[0, 1]^d`, with ``d>=2``. + tol : float, optional + Tolerance for termination. If the min of the L1-norm over the samples + changes less than `tol`, it stops the algorithm. Default is 1e-5. + maxiter : int, optional + Maximum number of iterations. It will stop the algorithm even if + `tol` is above the threshold. + Too many iterations tend to cluster the samples as a hypersphere. + Default is 10. + qhull_options : str, optional + Additional options to pass to Qhull. See Qhull manual + for details. (Default: "Qbb Qc Qz Qj Qx" for ndim > 4 and + "Qbb Qc Qz Qj" otherwise.) + + Returns + ------- + sample : array_like (n, d) + The sample after being processed by Lloyd-Max algorithm. + + Notes + ----- + Lloyd-Max algorithm is an iterative process with the purpose of improving + the dispersion of samples. For given sample: (i) compute a Voronoi + Tessellation; (ii) find the centroid of each Voronoi cell; (iii) move the + samples toward the centroid of their respective cell. See [1]_, [2]_. + + A relaxation factor is used to control how fast samples can move at each + iteration. This factor is starting at 2 and ending at 1 after `maxiter` + following an exponential decay. + + The process converges to equally spaced samples. It implies that measures + like the discrepancy could suffer from too many iterations. On the other + hand, L1 and L2 distances should improve. This is especially true with + QMC methods which tend to favor the discrepancy over other criteria. + + .. note:: + + The current implementation does not intersect the Voronoi Tessellation + with the boundaries. This implies that for a low number of samples, + empirically below 20, no Voronoi cell is touching the boundaries. + Hence, samples cannot be moved close to the boundaries. + + Further improvements could consider the samples at infinity so that + all boundaries are segments of some Voronoi cells. This would fix + the computation of the centroid position. + + .. warning:: + + The Voronoi Tessellation step is expensive and quickly becomes + intractable with dimensions as low as 10 even for a sample + of size as low as 1000. + + .. versionadded:: 1.9.0 + + References + ---------- + .. [1] Lloyd. "Least Squares Quantization in PCM". + IEEE Transactions on Information Theory, 1982. + .. [2] Max J. "Quantizing for minimum distortion". + IEEE Transactions on Information Theory, 1960. + + Examples + -------- + >>> import numpy as np + >>> from scipy.spatial import distance + >>> from scipy.stats._qmc import _lloyd_centroidal_voronoi_tessellation + >>> rng = np.random.default_rng() + >>> sample = rng.random((128, 2)) + + .. note:: + + The samples need to be in :math:`[0, 1]^d`. `scipy.stats.qmc.scale` + can be used to scale the samples from their + original bounds to :math:`[0, 1]^d`. And back to their original bounds. + + Compute the quality of the sample using the L1 criterion. + + >>> def l1_norm(sample): + ... return distance.pdist(sample, 'cityblock').min() + + >>> l1_norm(sample) + 0.00161... # random + + Now process the sample using Lloyd's algorithm and check the improvement + on the L1. The value should increase. + + >>> sample = _lloyd_centroidal_voronoi_tessellation(sample) + >>> l1_norm(sample) + 0.0278... # random + + """ + del kwargs # only use keywords which are defined, needed by factory + + sample = np.asarray(sample).copy() + + if not sample.ndim == 2: + raise ValueError('`sample` is not a 2D array') + + if not sample.shape[1] >= 2: + raise ValueError('`sample` dimension is not >= 2') + + # Checking that sample is within the hypercube + if (sample.max() > 1.) or (sample.min() < 0.): + raise ValueError('`sample` is not in unit hypercube') + + if qhull_options is None: + qhull_options = 'Qbb Qc Qz QJ' + + if sample.shape[1] >= 5: + qhull_options += ' Qx' + + # Fit an exponential to be 2 at 0 and 1 at `maxiter`. + # The decay is used for relaxation. + # analytical solution for y=exp(-maxiter/x) - 0.1 + root = -maxiter / np.log(0.1) + decay = [np.exp(-x / root)+0.9 for x in range(maxiter)] + + l1_old = _l1_norm(sample=sample) + for i in range(maxiter): + sample = _lloyd_iteration( + sample=sample, decay=decay[i], + qhull_options=qhull_options, + ) + + l1_new = _l1_norm(sample=sample) + + if abs(l1_new - l1_old) < tol: + break + else: + l1_old = l1_new + + return sample + + +def _validate_workers(workers: IntNumber = 1) -> IntNumber: + """Validate `workers` based on platform and value. + + Parameters + ---------- + workers : int, optional + Number of workers to use for parallel processing. If -1 is + given all CPU threads are used. Default is 1. + + Returns + ------- + Workers : int + Number of CPU used by the algorithm + + """ + workers = int(workers) + if workers == -1: + workers = os.cpu_count() # type: ignore[assignment] + if workers is None: + raise NotImplementedError( + "Cannot determine the number of cpus using os.cpu_count(), " + "cannot use -1 for the number of workers" + ) + elif workers <= 0: + raise ValueError(f"Invalid number of workers: {workers}, must be -1 " + "or > 0") + + return workers + + +def _validate_bounds( + l_bounds: "npt.ArrayLike", u_bounds: "npt.ArrayLike", d: int +) -> tuple[np.ndarray, np.ndarray]: + """Bounds input validation. + + Parameters + ---------- + l_bounds, u_bounds : array_like (d,) + Lower and upper bounds. + d : int + Dimension to use for broadcasting. + + Returns + ------- + l_bounds, u_bounds : array_like (d,) + Lower and upper bounds. + + """ + try: + lower = np.broadcast_to(l_bounds, d) + upper = np.broadcast_to(u_bounds, d) + except ValueError as exc: + msg = ("'l_bounds' and 'u_bounds' must be broadcastable and respect" + " the sample dimension") + raise ValueError(msg) from exc + + if not np.all(lower < upper): + raise ValueError("Bounds are not consistent 'l_bounds' < 'u_bounds'") + + return lower, upper diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_qmc_cy.cpython-312-x86_64-linux-gnu.so b/venv/lib/python3.12/site-packages/scipy/stats/_qmc_cy.cpython-312-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..668ea797d154ea1c664febcd147cc1daafbbe31f --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_qmc_cy.cpython-312-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:13d71387da10a610c119621119f26a981474737dc105153d24248ba566285fda +size 130088 diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_qmc_cy.pyi b/venv/lib/python3.12/site-packages/scipy/stats/_qmc_cy.pyi new file mode 100644 index 0000000000000000000000000000000000000000..1006385a43179478a9a4a32ae5f825aa5b8b35c4 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_qmc_cy.pyi @@ -0,0 +1,54 @@ +import numpy as np +from scipy._lib._util import DecimalNumber, IntNumber + + +def _cy_wrapper_centered_discrepancy( + sample: np.ndarray, + iterative: bool, + workers: IntNumber, +) -> float: ... + + +def _cy_wrapper_wrap_around_discrepancy( + sample: np.ndarray, + iterative: bool, + workers: IntNumber, +) -> float: ... + + +def _cy_wrapper_mixture_discrepancy( + sample: np.ndarray, + iterative: bool, + workers: IntNumber, +) -> float: ... + + +def _cy_wrapper_l2_star_discrepancy( + sample: np.ndarray, + iterative: bool, + workers: IntNumber, +) -> float: ... + + +def _cy_wrapper_update_discrepancy( + x_new_view: np.ndarray, + sample_view: np.ndarray, + initial_disc: DecimalNumber, +) -> float: ... + + +def _cy_van_der_corput( + n: IntNumber, + base: IntNumber, + start_index: IntNumber, + workers: IntNumber, +) -> np.ndarray: ... + + +def _cy_van_der_corput_scrambled( + n: IntNumber, + base: IntNumber, + start_index: IntNumber, + permutations: np.ndarray, + workers: IntNumber, +) -> np.ndarray: ... diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_qmvnt.py b/venv/lib/python3.12/site-packages/scipy/stats/_qmvnt.py new file mode 100644 index 0000000000000000000000000000000000000000..9f942da89e0a03c456b4ee0011b711ba217b4024 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_qmvnt.py @@ -0,0 +1,475 @@ +# Integration of multivariate normal and t distributions. + +# Adapted from the MATLAB original implementations by Dr. Alan Genz. + +# http://www.math.wsu.edu/faculty/genz/software/software.html + +# Copyright (C) 2013, Alan Genz, All rights reserved. +# Python implementation is copyright (C) 2022, Robert Kern, All rights +# reserved. + +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided the following conditions are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# 3. The contributor name(s) may not be used to endorse or promote +# products derived from this software without specific prior +# written permission. +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +import math +import numpy as np + +from scipy.fft import fft, ifft +from scipy.special import ndtr as phi, ndtri as phinv +from scipy.special._ufuncs import _bivariate_normal_cdf as _bvnu +from scipy.stats._qmc import primes_from_2_to + +from ._qmvnt_cy import _qmvn_inner, _qmvt_inner + + +def _factorize_int(n): + """Return a sorted list of the unique prime factors of a positive integer. + """ + # NOTE: There are lots faster ways to do this, but this isn't terrible. + factors = set() + for p in primes_from_2_to(int(np.sqrt(n)) + 1): + while not (n % p): + factors.add(p) + n //= p + if n == 1: + break + if n != 1: + factors.add(n) + return sorted(factors) + + +def _primitive_root(p): + """Compute a primitive root of the prime number `p`. + + Used in the CBC lattice construction. + + References + ---------- + .. [1] https://en.wikipedia.org/wiki/Primitive_root_modulo_n + """ + # p is prime + pm = p - 1 + factors = _factorize_int(pm) + n = len(factors) + r = 2 + k = 0 + while k < n: + d = pm // factors[k] + # pow() doesn't like numpy scalar types. + rd = pow(int(r), int(d), int(p)) + if rd == 1: + r += 1 + k = 0 + else: + k += 1 + return r + + +def _cbc_lattice(n_dim, n_qmc_samples): + """Compute a QMC lattice generator using a Fast CBC construction. + + Parameters + ---------- + n_dim : int > 0 + The number of dimensions for the lattice. + n_qmc_samples : int > 0 + The desired number of QMC samples. This will be rounded down to the + nearest prime to enable the CBC construction. + + Returns + ------- + q : float array : shape=(n_dim,) + The lattice generator vector. All values are in the open interval + ``(0, 1)``. + actual_n_qmc_samples : int + The prime number of QMC samples that must be used with this lattice, + no more, no less. + + References + ---------- + .. [1] Nuyens, D. and Cools, R. "Fast Component-by-Component Construction, + a Reprise for Different Kernels", In H. Niederreiter and D. Talay, + editors, Monte-Carlo and Quasi-Monte Carlo Methods 2004, + Springer-Verlag, 2006, 371-385. + """ + # Round down to the nearest prime number. + primes = primes_from_2_to(n_qmc_samples + 1) + n_qmc_samples = primes[-1] + + bt = np.ones(n_dim) + gm = np.hstack([1.0, 0.8 ** np.arange(n_dim - 1)]) + q = 1 + w = 0 + z = np.arange(1, n_dim + 1) + m = (n_qmc_samples - 1) // 2 + g = _primitive_root(n_qmc_samples) + # Slightly faster way to compute perm[j] = pow(g, j, n_qmc_samples) + # Shame that we don't have modulo pow() implemented as a ufunc. + perm = np.ones(m, dtype=int) + for j in range(m - 1): + perm[j + 1] = (g * perm[j]) % n_qmc_samples + perm = np.minimum(n_qmc_samples - perm, perm) + pn = perm / n_qmc_samples + c = pn * pn - pn + 1.0 / 6 + fc = fft(c) + for s in range(1, n_dim): + reordered = np.hstack([ + c[:w+1][::-1], + c[w+1:m][::-1], + ]) + q = q * (bt[s-1] + gm[s-1] * reordered) + w = ifft(fc * fft(q)).real.argmin() + z[s] = perm[w] + q = z / n_qmc_samples + return q, n_qmc_samples + + +def _qauto(func, covar, low, high, rng, error=1e-3, limit=10_000, **kwds): + """Automatically rerun the integration to get the required error bound. + + Parameters + ---------- + func : callable + Either :func:`_qmvn` or :func:`_qmvt`. + covar, low, high : array + As specified in :func:`_qmvn` and :func:`_qmvt`. + rng : Generator, optional + default_rng(), yada, yada + error : float > 0 + The desired error bound. + limit : int > 0: + The rough limit of the number of integration points to consider. The + integration will stop looping once this limit has been *exceeded*. + **kwds : + Other keyword arguments to pass to `func`. When using :func:`_qmvt`, be + sure to include ``nu=`` as one of these. + + Returns + ------- + prob : float + The estimated probability mass within the bounds. + est_error : float + 3 times the standard error of the batch estimates. + n_samples : int + The number of integration points actually used. + """ + n = len(covar) + n_samples = 0 + if n == 1: + prob = phi(high / covar**0.5) - phi(low / covar**0.5) + # More or less + est_error = 1e-15 + elif n == 2: + prob = _bvn(low, high, covar) + est_error = 1e-15 + else: + mi = min(limit, n * 1000) + prob = 0.0 + est_error = 1.0 + ei = 0.0 + while est_error > error and n_samples < limit: + mi = round(np.sqrt(2) * mi) + pi, ei, ni = func(mi, covar, low, high, rng=rng, **kwds) + n_samples += ni + wt = 1.0 / (1 + (ei / est_error)**2) + prob += wt * (pi - prob) + est_error = np.sqrt(wt) * ei + return prob, est_error, n_samples + + +def _qmvn(m, covar, low, high, rng, lattice='cbc', n_batches=10): + """Multivariate normal integration over box bounds. + + Parameters + ---------- + m : int > n_batches + The number of points to sample. This number will be divided into + `n_batches` batches that apply random offsets of the sampling lattice + for each batch in order to estimate the error. + covar : (n, n) float array + Possibly singular, positive semidefinite symmetric covariance matrix. + low, high : (n,) float array + The low and high integration bounds. + rng : Generator, optional + default_rng(), yada, yada + lattice : 'cbc' or callable + The type of lattice rule to use to construct the integration points. + n_batches : int > 0, optional + The number of QMC batches to apply. + + Returns + ------- + prob : float + The estimated probability mass within the bounds. + est_error : float + 3 times the standard error of the batch estimates. + """ + cho, lo, hi = _permuted_cholesky(covar, low, high) + if not cho.flags.c_contiguous: + # qmvn_inner expects contiguous buffers + cho = cho.copy() + + n = cho.shape[0] + q, n_qmc_samples = _cbc_lattice(n - 1, max(m // n_batches, 1)) + rndm = rng.random(size=(n_batches, n)) + + prob, est_error, n_samples = _qmvn_inner( + q, rndm, int(n_qmc_samples), int(n_batches), cho, lo, hi + ) + return prob, est_error, n_samples + + +# Note: this function is not currently used or tested by any SciPy code. It is +# included in this file to facilitate the resolution of gh-8367, gh-16142, and +# possibly gh-14286, but must be reviewed and tested before use. +def _mvn_qmc_integrand(covar, low, high, use_tent=False): + """Transform the multivariate normal integration into a QMC integrand over + a unit hypercube. + + The dimensionality of the resulting hypercube integration domain is one + less than the dimensionality of the original integrand. Note that this + transformation subsumes the integration bounds in order to account for + infinite bounds. The QMC integration one does with the returned integrand + should be on the unit hypercube. + + Parameters + ---------- + covar : (n, n) float array + Possibly singular, positive semidefinite symmetric covariance matrix. + low, high : (n,) float array + The low and high integration bounds. + use_tent : bool, optional + If True, then use tent periodization. Only helpful for lattice rules. + + Returns + ------- + integrand : Callable[[NDArray], NDArray] + The QMC-integrable integrand. It takes an + ``(n_qmc_samples, ndim_integrand)`` array of QMC samples in the unit + hypercube and returns the ``(n_qmc_samples,)`` evaluations of at these + QMC points. + ndim_integrand : int + The dimensionality of the integrand. Equal to ``n-1``. + """ + cho, lo, hi = _permuted_cholesky(covar, low, high) + n = cho.shape[0] + ndim_integrand = n - 1 + ct = cho[0, 0] + c = phi(lo[0] / ct) + d = phi(hi[0] / ct) + ci = c + dci = d - ci + + def integrand(*zs): + ndim_qmc = len(zs) + n_qmc_samples = len(np.atleast_1d(zs[0])) + assert ndim_qmc == ndim_integrand + y = np.zeros((ndim_qmc, n_qmc_samples)) + c = np.full(n_qmc_samples, ci) + dc = np.full(n_qmc_samples, dci) + pv = dc.copy() + for i in range(1, n): + if use_tent: + # Tent periodization transform. + x = abs(2 * zs[i-1] - 1) + else: + x = zs[i-1] + y[i - 1, :] = phinv(c + x * dc) + s = cho[i, :i] @ y[:i, :] + ct = cho[i, i] + c = phi((lo[i] - s) / ct) + d = phi((hi[i] - s) / ct) + dc = d - c + pv = pv * dc + return pv + + return integrand, ndim_integrand + + +def _qmvt(m, nu, covar, low, high, rng, lattice='cbc', n_batches=10): + """Multivariate t integration over box bounds. + + Parameters + ---------- + m : int > n_batches + The number of points to sample. This number will be divided into + `n_batches` batches that apply random offsets of the sampling lattice + for each batch in order to estimate the error. + nu : float >= 0 + The shape parameter of the multivariate t distribution. + covar : (n, n) float array + Possibly singular, positive semidefinite symmetric covariance matrix. + low, high : (n,) float array + The low and high integration bounds. + rng : Generator, optional + default_rng(), yada, yada + lattice : 'cbc' or callable + The type of lattice rule to use to construct the integration points. + n_batches : int > 0, optional + The number of QMC batches to apply. + + Returns + ------- + prob : float + The estimated probability mass within the bounds. + est_error : float + 3 times the standard error of the batch estimates. + n_samples : int + The number of samples actually used. + """ + sn = max(1.0, np.sqrt(nu)) + low = np.asarray(low, dtype=np.float64) + high = np.asarray(high, dtype=np.float64) + cho, lo, hi = _permuted_cholesky(covar, low / sn, high / sn) + n = cho.shape[0] + q, n_qmc_samples = _cbc_lattice(n, max(m // n_batches, 1)) + rndm = rng.random(size=(n_batches, n)) + prob, est_error, n_samples = _qmvt_inner( + q, rndm, int(n_qmc_samples), int(n_batches), cho, lo, hi, float(nu) + ) + return prob, est_error, n_samples + + +def _permuted_cholesky(covar, low, high, tol=1e-10): + """Compute a scaled, permuted Cholesky factor, with integration bounds. + + The scaling and permuting of the dimensions accomplishes part of the + transformation of the original integration problem into a more numerically + tractable form. The lower-triangular Cholesky factor will then be used in + the subsequent integration. The integration bounds will be scaled and + permuted as well. + + Parameters + ---------- + covar : (n, n) float array + Possibly singular, positive semidefinite symmetric covariance matrix. + low, high : (n,) float array + The low and high integration bounds. + tol : float, optional + The singularity tolerance. + + Returns + ------- + cho : (n, n) float array + Lower Cholesky factor, scaled and permuted. + new_low, new_high : (n,) float array + The scaled and permuted low and high integration bounds. + """ + # Make copies for outputting. + cho = np.array(covar, dtype=np.float64) + new_lo = np.array(low, dtype=np.float64) + new_hi = np.array(high, dtype=np.float64) + n = cho.shape[0] + if cho.shape != (n, n): + raise ValueError("expected a square symmetric array") + if new_lo.shape != (n,) or new_hi.shape != (n,): + raise ValueError( + "expected integration boundaries the same dimensions " + "as the covariance matrix" + ) + # Scale by the sqrt of the diagonal. + dc = np.sqrt(np.maximum(np.diag(cho), 0.0)) + # But don't divide by 0. + dc[dc == 0.0] = 1.0 + new_lo /= dc + new_hi /= dc + cho /= dc + cho /= dc[:, np.newaxis] + + y = np.zeros(n) + sqtp = np.sqrt(2 * np.pi) + for k in range(n): + epk = (k + 1) * tol + im = k + ck = 0.0 + dem = 1.0 + s = 0.0 + lo_m = 0.0 + hi_m = 0.0 + for i in range(k, n): + if cho[i, i] > tol: + ci = np.sqrt(cho[i, i]) + if i > 0: + s = cho[i, :k] @ y[:k] + lo_i = (new_lo[i] - s) / ci + hi_i = (new_hi[i] - s) / ci + de = phi(hi_i) - phi(lo_i) + if de <= dem: + ck = ci + dem = de + lo_m = lo_i + hi_m = hi_i + im = i + if im > k: + # Swap im and k + cho[im, im] = cho[k, k] + _swap_slices(cho, np.s_[im, :k], np.s_[k, :k]) + _swap_slices(cho, np.s_[im + 1:, im], np.s_[im + 1:, k]) + _swap_slices(cho, np.s_[k + 1:im, k], np.s_[im, k + 1:im]) + _swap_slices(new_lo, k, im) + _swap_slices(new_hi, k, im) + if ck > epk: + cho[k, k] = ck + cho[k, k + 1:] = 0.0 + for i in range(k + 1, n): + cho[i, k] /= ck + cho[i, k + 1:i + 1] -= cho[i, k] * cho[k + 1:i + 1, k] + if abs(dem) > tol: + y[k] = ((np.exp(-lo_m * lo_m / 2) - np.exp(-hi_m * hi_m / 2)) / + (sqtp * dem)) + else: + y[k] = (lo_m + hi_m) / 2 + if lo_m < -10: + y[k] = hi_m + elif hi_m > 10: + y[k] = lo_m + cho[k, :k + 1] /= ck + new_lo[k] /= ck + new_hi[k] /= ck + else: + cho[k:, k] = 0.0 + y[k] = (new_lo[k] + new_hi[k]) / 2 + return cho, new_lo, new_hi + + +def _swap_slices(x, slc1, slc2): + t = x[slc1].copy() + x[slc1] = x[slc2].copy() + x[slc2] = t + + +def _bvn(a, b, A): + # covariance matrix is written [[s1**2, rho*s1*s2], [rho*s1*s2, s2**2]] + # e.g. https://en.wikipedia.org/wiki/Multivariate_normal_distribution + # therefore, s12 = rho*s1*s2 -> rho = s12/(s1*s2) + s1 = math.sqrt(A[0, 0]) + s2 = math.sqrt(A[1, 1]) + s12 = A[0, 1] + r = s12 / (s1 * s2) + # the x and y coordinates seem to be normalized by the standard devs + xl, xu = a[0] / s1, b[0] / s1 + yl, yu = a[1] / s2, b[1] / s2 + p = _bvnu(xl, yl, r) - _bvnu(xu, yl, r) - _bvnu(xl, yu, r) + _bvnu(xu, yu, r) + p = max( 0., min( p, 1. ) ) + return p diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_qmvnt_cy.cpython-312-x86_64-linux-gnu.so b/venv/lib/python3.12/site-packages/scipy/stats/_qmvnt_cy.cpython-312-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..6750cbd6955d404fd94cd65107bbac35f06cedfc --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_qmvnt_cy.cpython-312-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f24c941ac05f683264bbc14ff0cc4da1468bb75a2d788f40b5fc68c484e7aec9 +size 126432 diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_quantile.py b/venv/lib/python3.12/site-packages/scipy/stats/_quantile.py new file mode 100644 index 0000000000000000000000000000000000000000..cd3756f23138ae04cfabb7b47dfaa24a47c4c10a --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_quantile.py @@ -0,0 +1,816 @@ +import math +import numpy as np +from scipy.special import betainc +from scipy._lib._array_api import ( + xp_capabilities, + xp_ravel, + array_namespace, + xp_promote, + xp_device, + xp_size, + _count_nonmasked, + is_torch, + is_lazy_array, +) +import scipy._external.array_api_extra as xpx +from scipy.stats._axis_nan_policy import _broadcast_arrays, _contains_nan + + +def _quantile_iv(x, p, method, axis, nan_policy, keepdims, weights, fun='quantile'): + xp = array_namespace(x, p, weights) + + if fun == "quantile": + methods = {'inverted_cdf', 'averaged_inverted_cdf', 'closest_observation', + 'hazen', 'interpolated_inverted_cdf', 'linear', + 'median_unbiased', 'normal_unbiased', 'weibull', + 'harrell-davis', '_lower', '_midpoint', '_higher', '_nearest', + 'round_outward', 'round_inward', 'round_nearest'} + allowed_types = 'real floating' + def mask_fun(p): return (p > 1) | (p < 0) | xp.isnan(p) + var2_name = 'p' + var2_type_msg = '`p` must have real floating dtype.' + else: + methods = set(_estimated_cdf_methods) + allowed_types = ('integral', 'real floating') + mask_fun = xp.isnan + var2_name = 'y' + var2_type_msg = '`y` must have real dtype.' + + if not xp.isdtype(xp.asarray(x).dtype, ('integral', 'real floating')): + raise ValueError("`x` must have real dtype.") + + if not xp.isdtype(xp.asarray(p).dtype, allowed_types): + raise ValueError(var2_type_msg) + + if not (weights is None + or xp.isdtype(xp.asarray(weights).dtype, ('integral', 'real floating'))): + raise ValueError("`weights` must have real dtype.") + + x, p, weights = xp_promote(x, p, weights, force_floating=True, xp=xp) + p = xp.asarray(p, device=xp_device(x)) + dtype = x.dtype + + axis_none = axis is None + ndim = max(x.ndim, p.ndim) + if axis_none: + x = xp_ravel(x) + p = xp_ravel(p) + axis = 0 + elif np.iterable(axis) or int(axis) != axis: + message = "`axis` must be an integer or None." + raise ValueError(message) + elif (axis >= ndim) or (axis < -ndim): + message = "`axis` is not compatible with the shapes of the inputs." + raise ValueError(message) + axis = int(axis) + + if method not in methods: + message = f"`method` must be one of {methods}" + raise ValueError(message) + + no_weights = {'_lower', '_midpoint', '_higher', '_nearest', 'harrell-davis', + 'round_nearest', 'round_inward', 'round_outward'} + if weights is not None and method in no_weights: + message = f"`method='{method}'` does not support `weights`." + raise ValueError(message) + + contains_nans = _contains_nan(x, nan_policy, xp_omit_okay=True, xp=xp) + + if keepdims not in {None, True, False}: + message = "If specified, `keepdims` must be True or False." + raise ValueError(message) + + # If data has length zero along `axis`, the result will be an array of NaNs just + # as if the data had length 1 along axis and were filled with NaNs. This is treated + # naturally below whether `nan_policy` is `'propagate'` or `'omit'`. + if x.shape[axis] == 0 and fun == 'quantile': + shape = list(x.shape) + shape[axis] = 1 + x = xp.full(shape, xp.nan, dtype=dtype, device=xp_device(x)) + + if weights is None: + y = xp.sort(x, axis=axis, stable=False) + y, p = _broadcast_arrays((y, p), axis=axis) + n_zero_weight = None + else: + x, weights = xp.broadcast_arrays(x, weights) + i_zero_weight = (weights == 0) + n_zero_weight = xp.count_nonzero(i_zero_weight, axis=axis, keepdims=True) + x = xpx.at(x)[i_zero_weight].set(xp.inf, copy=True) + i_y = xp.argsort(x, axis=axis, stable=False) + y = xp.take_along_axis(x, i_y, axis=axis) + weights = xp.take_along_axis(weights, i_y, axis=axis) + y, p, weights, i_y, n_zero_weight = _broadcast_arrays( + (y, p, weights, i_y, n_zero_weight), axis=axis) + + if (keepdims is False) and (p.shape[axis] != 1): + message = ("`keepdims` may be False only if the length of " + f"`{var2_name}` along `axis` is 1.") + raise ValueError(message) + keepdims = (p.shape[axis] != 1) if keepdims is None else keepdims + + y = xp.moveaxis(y, axis, -1) + p = xp.moveaxis(p, axis, -1) + weights = weights if weights is None else xp.moveaxis(weights, axis, -1) + n_zero_weight = (n_zero_weight if n_zero_weight is None + else xp.moveaxis(n_zero_weight, axis, -1)) + + n = _count_nonmasked(y, -1, xp=xp, keepdims=True) + n = n if n_zero_weight is None else n - n_zero_weight + + nan_out = None + if is_lazy_array(y) or contains_nans: + nans = xp.isnan(y) + + # Note that if length along `axis` were 0 to begin with, + # it is now length 1 and filled with NaNs. + if nan_policy == 'propagate': + nan_out = xp.any(nans, axis=-1, keepdims=True) + else: # 'omit' + n_int = n - xp.count_nonzero(nans, axis=-1, keepdims=True) + n = xp.astype(n_int, dtype) + # NaNs are produced only if slice is empty after removing NaNs + nan_out = n == 0 + n = xpx.at(n, nan_out).set(y.shape[-1]) # avoids pytorch/pytorch#146211 + + if (is_lazy_array(nans) or xp.any(nans)) and (method == 'harrell-davis'): + y = xp.asarray(y, copy=True) # ensure writable + y = xpx.at(y, nans).set(0) # any non-nan will prevent NaN from propagating + if is_lazy_array(nan_out) or xp.any(nan_out): + y = xp.asarray(y, copy=True) # ensure writable + y = xpx.at(y, xp.broadcast_to(nan_out, y.shape)).set(xp.nan) + + n = xp.asarray(n, dtype=dtype, device=xp_device(y)) + + # apparently xpx.at is accepting nan_out as a mask even though it doesn't have the + # same number of dimensions as `y`, yet it still appears to work correctly? + # should refactor for clarity, and p_mask that gets returned here should probably + # get a different name - `nan_out` would be more appropriate! + p_mask = mask_fun(p) if nan_out is None else mask_fun(p) | nan_out + if is_lazy_array(p_mask) or xp.any(p_mask): + p = xp.where(p_mask, 0.5, p) # these get NaN-ed out at the end + + return (y, p, method, axis, nan_policy, keepdims, + n, axis_none, ndim, p_mask, weights, xp) + + +@xp_capabilities(skip_backends=[("dask.array", "No take_along_axis yet.")], + marray=True, + extra_note=("Use of `weights` is incompatible with MArray. " + "``method='harrell-davis'`` is CPU-only.")) +def quantile(x, p, *, method='linear', axis=0, nan_policy='propagate', keepdims=None, + weights=None): + """ + Compute the p-th quantile of the data along the specified axis. + + Parameters + ---------- + x : array_like of real numbers + Data array. + p : array_like of float + Probability or sequence of probabilities of the quantiles to compute. + Values must be between 0 and 1 (inclusive). + While `numpy.quantile` can only compute quantiles according to the Cartesian + product of the first two arguments, this function enables calculation of + quantiles at different probabilities for each axis slice by following + broadcasting rules like those of `scipy.stats` reducing functions. + See `axis`, `keepdims`, and the examples. + method : str, default: 'linear' + The method to use for estimating the quantile. + The available options, numbered as they appear in [1]_, are: + + 1. 'inverted_cdf' + 2. 'averaged_inverted_cdf' + 3. 'closest_observation' + 4. 'interpolated_inverted_cdf' + 5. 'hazen' + 6. 'weibull' + 7. 'linear' (default) + 8. 'median_unbiased' + 9. 'normal_unbiased' + + 'harrell-davis' is also available to compute the quantile estimate + according to [2]_. + + 'round_outward', 'round_inward', and 'round_nearest' are available for use + in trimming and winsorizing data. + + See Notes for details. + axis : int or None, default: 0 + Axis along which the quantiles are computed. + ``None`` ravels both `x` and `p` before performing the calculation, + without checking whether the original shapes were compatible. + As in other `scipy.stats` functions, a positive integer `axis` is resolved + after prepending 1s to the shape of `x` or `p` as needed until the two arrays + have the same dimensionality. When providing `x` and `p` with different + dimensionality, consider using negative `axis` integers for clarity. + nan_policy : str, default: 'propagate' + Defines how to handle NaNs in the input data `x`. + + - ``propagate``: if a NaN is present in the axis slice (e.g. row) along + which the statistic is computed, the corresponding slice of the output + will contain NaN(s). + - ``omit``: NaNs will be omitted when performing the calculation. + If insufficient data remains in the axis slice along which the + statistic is computed, the corresponding slice of the output will + contain NaN(s). + - ``raise``: if a NaN is present, a ``ValueError`` will be raised. + + If NaNs are present in `p`, a ``ValueError`` will be raised. + keepdims : bool, optional + Consider the case in which `x` is 1-D and `p` is a scalar: the quantile + is a reducing statistic, and the default behavior is to return a scalar. + If `keepdims` is set to True, the axis will not be reduced away, and the + result will be a 1-D array with one element. + + The general case is more subtle, since multiple quantiles may be + requested for each axis-slice of `x`. For instance, if both `x` and `p` + are 1-D and ``p.size > 1``, no axis can be reduced away; there must be an + axis to contain the number of quantiles given by ``p.size``. Therefore: + + - By default, the axis will be reduced away if possible (i.e. if there is + exactly one element of `p` per axis-slice of `x`). + - If `keepdims` is set to True, the axis will not be reduced away. + - If `keepdims` is set to False, the axis will be reduced away + if possible, and an error will be raised otherwise. + + weights : array_like of finite, non-negative real numbers + Frequency weights; e.g., for counting number weights, + ``quantile(x, p, weights=weights)`` is equivalent to + ``quantile(np.repeat(x, weights), p)``. Values other than finite counting + numbers are accepted, but may not have valid statistical interpretations. + Not compatible with ``method='harrell-davis'`` or those that begin with + ``'round_'``. + + Returns + ------- + quantile : scalar or ndarray + The resulting quantile(s). The dtype is the result dtype of `x` and `p`. + + See Also + -------- + numpy.quantile + estimated_cdf + :ref:`outliers` + + Notes + ----- + Given a sample `x` from an underlying distribution, `quantile` provides a + nonparametric estimate of the inverse cumulative distribution function. + + By default, this is done by interpolating between adjacent elements in + ``y``, a sorted copy of `x`:: + + (1-g)*y[j] + g*y[j+1] + + where the index ``j`` and coefficient ``g`` are the integral and + fractional components of ``p * (n-1)``, and ``n`` is the number of + elements in the sample. + + This is a special case of Equation 1 of H&F [1]_. More generally, + + - ``j = (p*n + m - 1) // 1``, and + - ``g = (p*n + m - 1) % 1``, + + where ``m`` may be defined according to several different conventions. + The preferred convention may be selected using the ``method`` parameter: + + =============================== =============== =============== + ``method`` number in H&F ``m`` + =============================== =============== =============== + ``interpolated_inverted_cdf`` 4 ``0`` + ``hazen`` 5 ``1/2`` + ``weibull`` 6 ``p`` + ``linear`` (default) 7 ``1 - p`` + ``median_unbiased`` 8 ``p/3 + 1/3`` + ``normal_unbiased`` 9 ``p/4 + 3/8`` + =============================== =============== =============== + + Note that indices ``j`` and ``j + 1`` are clipped to the range ``0`` to + ``n - 1`` when the results of the formula would be outside the allowed + range of non-negative indices. When ``j`` is clipped to zero, ``g`` is + set to zero as well. The ``-1`` in the formulas for ``j`` and ``g`` + accounts for Python's 0-based indexing. + + The table above includes only the estimators from [1]_ that are continuous + functions of probability `p` (estimators 4-9). SciPy also provides the + three discontinuous estimators from [1]_ (estimators 1-3), where ``j`` is + defined as above, ``m`` is defined as follows, and ``g`` is ``0`` when + ``index = p*n + m - 1`` is less than ``0`` and otherwise is defined below. + + 1. ``inverted_cdf``: ``m = 0`` and ``g = int(index - j > 0)`` + 2. ``averaged_inverted_cdf``: ``m = 0`` and + ``g = (1 + int(index - j > 0)) / 2`` + 3. ``closest_observation``: ``m = -1/2`` and + ``g = 1 - int((index == j) & (j%2 == 1))`` + + Note that for methods ``inverted_cdf`` and ``averaged_inverted_cdf``, only the + relative proportions of tied observations (and relative weights) affect the + results; for all other methods, the total number of observations (and absolute + weights) matter. + + A different strategy for computing quantiles from [2]_, ``method='harrell-davis'``, + uses a weighted combination of all elements. The weights are computed as: + + .. math:: + + w_{n, i} = I_{i/n}(a, b) - I_{(i - 1)/n}(a, b) + + where :math:`n` is the number of elements in the sample, + :math:`i` are the indices :math:`1, 2, ..., n-1, n` of the sorted elements, + :math:`a = p (n + 1)`, :math:`b = (1 - p)(n + 1)`, + :math:`p` is the probability of the quantile, and + :math:`I` is the regularized, lower incomplete beta function + (`scipy.special.betainc`). + + ``method='round_nearest'`` is equivalent to indexing ``y[j]``, where:: + + j = int(np.round(p*n) if p < 0.5 else np.round(n*p - 1)) + + This is useful when winsorizing data: replacing ``p*n`` of the most extreme + observations with the next most extreme observation. ``method='round_outward'`` + adjusts the direction of rounding to winsorize fewer elements:: + + j = int(np.floor(p*n) if p < 0.5 else np.ceil(n*p - 1)) + + and ``method='round_inward'`` rounds to winsorize more elements:: + + j = int(np.ceil(p*n) if p < 0.5 else np.floor(n*p - 1)) + + These methods are also useful for trimming data: removing ``p*n`` of the most + extreme observations. See :ref:`outliers` for example applications. + + Examples + -------- + >>> import numpy as np + >>> from scipy import stats + >>> x = np.asarray([[10, 8, 7, 5, 4], + ... [0, 1, 2, 3, 5]]) + + Take the median of each row. + + >>> stats.quantile(x, 0.5, axis=-1) + array([7., 2.]) + + Take a different quantile for each row. + + >>> stats.quantile(x, [[0.25], [0.75]], axis=-1, keepdims=True) + array([[5.], + [3.]]) + + Take multiple quantiles for each row. + + >>> stats.quantile(x, [0.25, 0.75], axis=-1) + array([[5., 8.], + [1., 3.]]) + + Take different quantiles for each row. + + >>> p = np.asarray([[0.25, 0.75], + ... [0.5, 1.0]]) + >>> stats.quantile(x, p, axis=-1) + array([[5., 8.], + [2., 5.]]) + + Take different quantiles for each column. + + >>> stats.quantile(x.T, p.T, axis=0) + array([[5., 2.], + [8., 5.]]) + + References + ---------- + .. [1] R. J. Hyndman and Y. Fan, + "Sample quantiles in statistical packages," + The American Statistician, 50(4), pp. 361-365, 1996 + .. [2] Harrell, Frank E., and C. E. Davis. + "A new distribution-free quantile estimator." + Biometrika 69.3 (1982): 635-640. + + """ + # Input validation / standardization + + temp = _quantile_iv(x, p, method, axis, nan_policy, keepdims, weights) + (y, p, method, axis, nan_policy, keepdims, + n, axis_none, ndim, p_mask, weights, xp) = temp + + if method in {'inverted_cdf', 'averaged_inverted_cdf', 'closest_observation', + 'hazen', 'interpolated_inverted_cdf', 'linear', + 'median_unbiased', 'normal_unbiased', 'weibull'}: + res = _quantile_hf(y, p, n, method, weights, xp) + elif method in {'harrell-davis'}: + res = _quantile_hd(y, p, n, xp) + elif method in {'_lower', '_midpoint', '_higher', '_nearest'}: + res = _quantile_bc(y, p, n, method, xp) + else: # method.startswith('round'): + res = _quantile_winsor(y, p, n, method, xp) + + return _post_quantile(res, p_mask, axis, axis_none, ndim, keepdims, xp) + + +def _post_quantile(res, p_mask, axis, axis_none, ndim, keepdims, xp): + res = xpx.at(res, p_mask).set(xp.nan) + + # Reshape per axis/keepdims + if axis_none and keepdims: + shape = (1,)*(ndim - 1) + res.shape + res = xp.reshape(res, shape) + axis = -1 + + res = xp.moveaxis(res, -1, axis) + + if not keepdims: + res = xp.squeeze(res, axis=axis) + + return res[()] if res.ndim == 0 else res + + +def _quantile_hf(y, p, n, method, weights, xp): + ms = dict(inverted_cdf=0, averaged_inverted_cdf=0, closest_observation=-0.5, + interpolated_inverted_cdf=0, hazen=0.5, weibull=p, linear=1 - p, + median_unbiased=p/3 + 1/3, normal_unbiased=p/4 + 3/8) + m = ms[method] + + if weights is None: + jg = p * n + m + jp1 = jg // 1 + j = jp1 - 1 + else: + cumulative_weights = xp.cumulative_sum(weights, axis=-1) + n_int = xp.asarray(n, dtype=xp.int64) + n_int = xp.broadcast_to(n_int, cumulative_weights.shape[:-1] + (1,)) + total_weight = xp.take_along_axis(cumulative_weights, n_int-1, axis=-1) + jg = p * total_weight + m + jp1 = _xp_searchsorted(cumulative_weights, jg, side='right') + j = _xp_searchsorted(cumulative_weights, jg-1, side='right') + j, jp1 = xp.astype(j, y.dtype), xp.astype(jp1, y.dtype) + + g = jg % 1 + if method == 'inverted_cdf': + g = xp.astype((g > 0), jg.dtype) + elif method == 'averaged_inverted_cdf': + g = (1 + xp.astype((g > 0), jg.dtype)) / 2 + elif method == 'closest_observation': + g = (1 - xp.astype((g == 0) & (j % 2 == 1), jg.dtype)) + if method in {'inverted_cdf', 'averaged_inverted_cdf', 'closest_observation'}: + g = xp.asarray(g) + g = xpx.at(g, jg < 0).set(0) + + g = xpx.at(g)[j < 0].set(0) + j = xp.clip(j, 0., n - 1) + jp1 = xp.clip(jp1, 0., n - 1) + + return ((1 - g) * xp.take_along_axis(y, xp.astype(j, xp.int64), axis=-1) + + g * xp.take_along_axis(y, xp.astype(jp1, xp.int64), axis=-1)) + + +def _quantile_hd(y, p, n, xp): + # RE axis handling: We need to perform a reducing operation over rows of `y` for + # each element in the corresponding row of `p` (a la Cartesian product). Strategy: + # move rows of `p` to an axis at the front that is orthogonal to all the rest, + # perform the reducing operating over the last axis, then move the front axis back + # to the end. + p = xp.moveaxis(p, -1, 0)[..., xp.newaxis] + a = p * (n + 1) + b = (1 - p) * (n + 1) + i = xp.arange(y.shape[-1] + 1, dtype=y.dtype, device=xp_device(y)) + w = betainc(a, b, i / n) + w = w[..., 1:] - w[..., :-1] + w = xpx.at(w, xp.isnan(w)).set(0) + res = xp.vecdot(w, y, axis=-1) + return xp.moveaxis(res, 0, -1) + + +def _quantile_winsor(y, p, n, method, xp): + ops = dict(round_outward=(xp.floor, xp.ceil), + round_inward=(xp.ceil, xp.floor), + round_nearest=(xp.round, xp.round)) + op_left, op_right = ops[method] + j = xp.where(p < 0.5, op_left(p*n), op_right(n*p - 1)) + return xp.take_along_axis(y, xp.astype(j, xp.int64), axis=-1) + + +def _quantile_bc(y, p, n, method, xp): + # Methods retained for backward compatibility. NumPy documentation is not + # quite right about what these methods do: if `p * (n - 1)` is integral, + # that is used as the index. See numpy/numpy#28910. + ij = p * (n - 1) + if method == '_midpoint': + return (xp.take_along_axis(y, xp.astype(xp.floor(ij), xp.int64), axis=-1) + + xp.take_along_axis(y, xp.astype(xp.ceil(ij), xp.int64), axis=-1)) / 2 + elif method == '_lower': + k = xp.floor(ij) + elif method == '_higher': + k = xp.ceil(ij) + elif method == '_nearest': + k = xp.round(ij) + return xp.take_along_axis(y, xp.astype(k, xp.int64), axis=-1) + + +@xp_capabilities(skip_backends=[("dask.array", "No take_along_axis yet.")]) +def _xp_searchsorted(x, y, *, side='left', xp=None): + # Vectorized xp.searchsorted. Search is performed along last axis. The shape of the + # output is that of `y`, broadcasting the batch dimensions with those of `x` if + # necessary. + xp = array_namespace(x, y) if xp is None else xp + xp_default_int = xp.asarray(1).dtype + y_0d = xp.asarray(y).ndim == 0 + x, y = _broadcast_arrays((x, y), axis=-1, xp=xp) + x_1d = x.ndim <= 1 + + if x_1d or is_torch(xp): + y = xp.reshape(y, ()) if (y_0d and x_1d) else y + out = xp.searchsorted(x, y, side=side) + out = xp.astype(out, xp_default_int, copy=False) + return out + + a = xp.full(y.shape, 0, device=xp_device(x)) + + if x.shape[-1] == 0: + return a + + n = xp.count_nonzero(~xp.isnan(x), axis=-1, keepdims=True) + b = xp.broadcast_to(n, y.shape) + + compare = xp.less_equal if side == 'left' else xp.less + + # while xp.any(b - a > 1): + # refactored to for loop with ~log2(n) iterations for JAX JIT + for i in range(int(math.log2(x.shape[-1])) + 1): + c = (a + b) // 2 + x0 = xp.take_along_axis(x, c, axis=-1) + j = compare(y, x0) + b = xp.where(j, c, b) + a = xp.where(j, a, c) + + out = xp.where(compare(y, xp.min(x, axis=-1, keepdims=True)), 0, b) + out = xp.where(xp.isnan(y), x.shape[-1], out) if side == 'right' else out + out = xp.astype(out, xp_default_int, copy=False) + return out + + +@xp_capabilities(skip_backends=[("dask.array", "No take_along_axis yet.")], + marray=True) +def estimated_cdf(x, y, *, method='linear', + axis=0, nan_policy='propagate', keepdims=None): + """ + Estimate the CDF of the distribution underlying a sample. + + Parameters + ---------- + x : array_like of real numbers + Data array. + y : array_like of real numbers + Points at which to evaluate the estimated cdf. + See `axis`, `keepdims`, and the examples for broadcasting behavior. + method : str, default: 'linear' + The method to use for estimating the cumulative distribution function. + The available options, numbered as they appear in [1]_, are: + + 1. 'inverted_cdf' (AKA *the* empirical cumulative distribution function) + 2. 'averaged_inverted_cdf' + 3. 'closest_observation' + 4. 'interpolated_inverted_cdf' + 5. 'hazen' + 6. 'weibull' + 7. 'linear' (default) + 8. 'median_unbiased' + 9. 'normal_unbiased' + + See Notes for details. + axis : int or None, default: 0 + Axis along which samples in `x` are given in ND case. + ``None`` ravels both `x` and `y` before performing the calculation, + without checking whether the original shapes were compatible. + As in other `scipy.stats` functions, a positive integer `axis` is resolved + after prepending 1s to the shape of `x` or `y` as needed until the two arrays + have the same dimensionality. When providing `x` and `y` with different + dimensionality, consider using negative `axis` integers for clarity. + nan_policy : str, default: 'propagate' + Defines how to handle NaNs in the input data `x`. + + - ``propagate``: if a NaN is present in the axis slice (e.g. row) along + which the statistic is computed, the corresponding slice of the output + will contain NaN(s). + - ``omit``: NaNs will be omitted when performing the calculation. + If insufficient data remains in the axis slice along which the + statistic is computed, the corresponding slice of the output will + contain NaN(s). + - ``raise``: if a NaN is present, a ``ValueError`` will be raised. + + If NaNs are present in `y`, the corresponding entries in the output will be NaN. + keepdims : bool, optional + Consider the case in which `x` is 1-D and `y` is a scalar: the estimated + cumulative distribution function at a point is a reducing statistic, and the + default behavior is to return a scalar. + If `keepdims` is set to True, the axis will not be reduced away, and the + result will be a 1-D array with one element. + + The general case is more subtle, since multiple values of `y` may be + specified for each axis-slice of `x`. For instance, if both `x` and `y` + are 1-D and ``y.size > 1``, no axis can be reduced away; there must be an + axis to contain the number of values given by ``y.size``. Therefore: + + - By default, the axis will be reduced away if possible (i.e. if there is + exactly one element of `y` per axis-slice of `x`). + - If `keepdims` is set to True, the axis will not be reduced away. + - If `keepdims` is set to False, the axis will be reduced away + if possible, and an error will be raised otherwise. + + Returns + ------- + probability : scalar or ndarray + The resulting probabilities(s). The dtype is the result dtype of `x`, `y`, and + the Python ``float`` type. + + See Also + -------- + quantile + + Notes + ----- + Given a sample `x` from an underlying distribution, `estimated_cdf` provides a + nonparametric estimate of the cumulative distribution function. + + By default, this is done by computing the "fractional index" ``p`` at which ``y`` + would appear within ``z``, a sorted copy of `x`:: + + p = 1 / (n - 1) * (j + ( y - z[j]) + / (z[j+1] - z[j])) + + where the index ``j`` is that of the largest element of ``z`` that does not exceed + ``y``, and ``n`` is the number of elements in the sample. Note that if ``y`` is an + element of ``z``, then ``j`` is the index such that ``y = z[j]``, and the formula + simplifies to the intuitive ``j / (n - 1)``. The full formula linearly interpolates + between ``j / (n - 1)`` and ``(j + 1) / (n - 1)``. + + This is a special case of the more general:: + + p = (j + (y - z[j]) / (z[j+1] - z[j] + 1 - m) / n + + where ``m`` may be defined according to several different conventions. + The preferred convention may be selected using the ``method`` parameter: + + =============================== =============== =============== + ``method`` number in H&F ``m`` + =============================== =============== =============== + ``interpolated_inverted_cdf`` 4 ``0`` + ``hazen`` 5 ``1/2`` + ``weibull`` 6 ``p`` + ``linear`` (default) 7 ``1 - p`` + ``median_unbiased`` 8 ``p/3 + 1/3`` + ``normal_unbiased`` 9 ``p/4 + 3/8`` + =============================== =============== =============== + + Note that indices ``j`` and ``j + 1`` are clipped to the range ``0`` to + ``n - 1`` when the results of the formula would be outside the allowed + range of non-negative indices, and resulting ``p`` is clipped to the range + ``0`` to ``1``. + + The table above includes only the estimators from [1]_ that are continuous + functions of probability `p` (estimators 4-9). SciPy also provides the + three discontinuous estimators from [1]_ (estimators 1-3), where ``j`` is + defined as above, ``m`` is defined as follows, and ``g`` is ``0`` when + ``index = p*n + m - 1`` is less than ``0`` and otherwise is defined below. + + 1. ``inverted_cdf``: ``m = 0`` and ``g = int(index - j > 0)`` + 2. ``averaged_inverted_cdf``: ``m = 0`` and + ``g = (1 + int(index - j > 0)) / 2`` + 3. ``closest_observation``: ``m = -1/2`` and + ``g = 1 - int((index == j) & (j%2 == 1))`` + + When all the data in ``x`` are unique, `estimated_cdf` and `quantile` are are + inverses of one another within a certain domain. + Although `quantile` with ``method='linear'`` is invertible over the whole domain + of ``p`` from ``0`` to ``1``, this is not true of other methods. + + References + ---------- + .. [1] R. J. Hyndman and Y. Fan, + "Sample quantiles in statistical packages," + The American Statistician, 50(4), pp. 361-365, 1996 + + Examples + -------- + >>> import numpy as np + >>> from scipy import stats + >>> x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + + Estimate the cumulative distribution function of one sample at a single point. + + >>> stats.estimated_cdf(x, 5, axis=-1) + np.float64(0.5) + + Estimate the cumulative distribution function of one sample at two points. + + >>> stats.estimated_cdf(x, [2.5, 7.5], axis=-1) + array([0.25, 0.75]) + + Estimate the cumulative distribution function of two samples at different points. + + >>> x = np.stack((np.arange(0, 11), np.arange(10, 21))) + >>> stats.estimated_cdf(x, [[2.5], [17.5]], axis=-1, keepdims=True) + array([[0.25], + [0.75]]) + + Estimate the cumulative distribution function at many points for each of two + samples. + + >>> import matplotlib.pyplot as plt + >>> rng = np.random.default_rng(6110515095) + >>> x = stats.Normal(mu=[-1, 1]).sample(10000) + >>> y = np.linspace(-4, 4, 5000)[:, np.newaxis] + >>> p = stats.estimated_cdf(x, y, axis=0) + >>> plt.plot(y, p) + >>> plt.show() + + Note that the `quantile` and `estimated_cdf` functions are inverses of one another + within a certain domain. + + >>> p = np.linspace(0, 1, 300) + >>> x = rng.standard_normal(300) + >>> y = stats.quantile(x, p) + >>> p2 = stats.estimated_cdf(x, y) + >>> np.testing.assert_allclose(p2, p) + >>> y2 = stats.quantile(x, p2) + >>> np.testing.assert_allclose(y2, y) + + However, the domain over which `quantile` can be inverted by `estimated_cdf` depends + on the `method` used. This is most noticeable when there are few observations in the + sample. + + >>> x = np.asarray([0, 1]) + >>> y_linear = stats.quantile(x, p, method='linear') + >>> y_weibull = stats.quantile(x, p, method='weibull') + >>> y_iicdf = stats.quantile(x, p, method='interpolated_inverted_cdf') + >>> plt.plot(p, y_linear, p, y_weibull, p, y_iicdf) + >>> plt.legend(['linear', 'weibull', 'iicdf']) + >>> plt.xlabel('p') + >>> plt.ylabel('y = quantile(x, p)') + >>> plt.show() + + For example, in the case above, `quantile` is only invertible from + ``p = 0.5`` to ``p = 1.0`` with ``method = 'interpolated_inverted_cdf'``. This is a + fundamental characteristic of the methods, not a shortcoming of `estimated_cdf`. + + """ + temp = _quantile_iv(x, y, method, axis, nan_policy, keepdims, weights=None, + fun='estimated_cdf') + x, y, method, axis, nan_policy, keepdims, n, axis_none, ndim, y_mask, _, xp = temp + + if xp_size(x) == 0: + res = xp.full_like(y, xp.nan) + else: + res = _estimated_cdf_hf(x, y, n, method, xp) + + return _post_quantile(res, y_mask, axis, axis_none, ndim, keepdims, xp) + + +_estimated_cdf_discontinuous_methods = dict( + inverted_cdf=0.0, + averaged_inverted_cdf=0.0, + closest_observation=0.5, +) + + +_estimated_cdf_continuous_methods = dict( + interpolated_inverted_cdf=(0, 1), + hazen=(0.5, 0.5), + weibull=(0, 0), + linear=(1, 1), + median_unbiased=(1 / 3, 1 / 3), + normal_unbiased=(3 / 8, 3 / 8), +) + + +_estimated_cdf_methods = (set(_estimated_cdf_continuous_methods).union( + set(_estimated_cdf_discontinuous_methods))) + + +def _estimated_cdf_hf(x, y, n, method, xp): + n_int = xp.astype(n, xp.int64) + j_max = n_int - 1 + j_min = xp.minimum(j_max, xp.asarray(1, dtype=j_max.dtype)) + jp1 = _xp_searchsorted(x, y, side='right') + + if method in _estimated_cdf_discontinuous_methods: + dp = _estimated_cdf_discontinuous_methods[method] + p = (xp.astype(jp1, x.dtype)+dp)/n + + else: + jp1 = xp.clip(jp1, j_min, j_max) + j = xp.clip(jp1-1, 0) + xj = xp.take_along_axis(x, j, axis=-1) + xjp1 = xp.take_along_axis(x, jp1, axis=-1) + with np.errstate(divide='ignore', invalid='ignore'): # refactor to apply_where? + delta = xp.where((xjp1 > xj) & xp.isfinite(xj), (y - xj) / (xjp1 - xj), 1.) + + a, b = _estimated_cdf_continuous_methods[method] + p = (xp.astype(jp1, x.dtype) + delta - a) / (n + 1 - a - b) + + xmin = x[..., :1] + xmax = (x[..., -1:] if n.shape == () else xp.take_along_axis(x, j_max)) + p = xpx.at(p)[y < xmin].set(0.) + p = xpx.at(p)[y > xmax].set(1.) + + return xp.clip(p, 0., 1.) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_rcont/__init__.py b/venv/lib/python3.12/site-packages/scipy/stats/_rcont/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..25728ead5f7ea277e6e94c359d5a5603b99eeb38 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_rcont/__init__.py @@ -0,0 +1,4 @@ +# +from .rcont import rvs_rcont1, rvs_rcont2 + +__all__ = ["rvs_rcont1", "rvs_rcont2"] diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_rcont/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/_rcont/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5bb5b6821e3451099d908984fc89a5365c19520c Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/_rcont/__pycache__/__init__.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_rcont/rcont.cpython-312-x86_64-linux-gnu.so b/venv/lib/python3.12/site-packages/scipy/stats/_rcont/rcont.cpython-312-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..fa504763a219bb263466d5e5f6c79acbd9205322 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_rcont/rcont.cpython-312-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cac8d420bff9e2bf24fd12b85bd3ab6d123a8fe13af3aa475cef7ce775205154 +size 107912 diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_relative_risk.py b/venv/lib/python3.12/site-packages/scipy/stats/_relative_risk.py new file mode 100644 index 0000000000000000000000000000000000000000..51525fd28adb37c72b12106450e4178c786091b2 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_relative_risk.py @@ -0,0 +1,263 @@ +import operator +from dataclasses import dataclass +import numpy as np +from scipy.special import ndtri +from ._common import ConfidenceInterval + + +def _validate_int(n, bound, name): + msg = f'{name} must be an integer not less than {bound}, but got {n!r}' + try: + n = operator.index(n) + except TypeError: + raise TypeError(msg) from None + if n < bound: + raise ValueError(msg) + return n + + +@dataclass +class RelativeRiskResult: + """ + Result of `scipy.stats.contingency.relative_risk`. + + Attributes + ---------- + relative_risk : float + This is:: + + (exposed_cases/exposed_total) / (control_cases/control_total) + + exposed_cases : int + The number of "cases" (i.e. occurrence of disease or other event + of interest) among the sample of "exposed" individuals. + exposed_total : int + The total number of "exposed" individuals in the sample. + control_cases : int + The number of "cases" among the sample of "control" or non-exposed + individuals. + control_total : int + The total number of "control" individuals in the sample. + + Methods + ------- + confidence_interval : + Compute the confidence interval for the relative risk estimate. + """ + + relative_risk: float + exposed_cases: int + exposed_total: int + control_cases: int + control_total: int + + def confidence_interval(self, confidence_level=0.95): + """ + Compute the confidence interval for the relative risk. + + The confidence interval is computed using the Katz method + (i.e. "Method C" of [1]_; see also [2]_, section 3.1.2). + + Parameters + ---------- + confidence_level : float, optional + The confidence level to use for the confidence interval. + Default is 0.95. + + Returns + ------- + ci : ConfidenceInterval instance + The return value is an object with attributes ``low`` and + ``high`` that hold the confidence interval. + + References + ---------- + .. [1] D. Katz, J. Baptista, S. P. Azen and M. C. Pike, "Obtaining + confidence intervals for the risk ratio in cohort studies", + Biometrics, 34, 469-474 (1978). + .. [2] Hardeo Sahai and Anwer Khurshid, Statistics in Epidemiology, + CRC Press LLC, Boca Raton, FL, USA (1996). + + + Examples + -------- + >>> from scipy.stats.contingency import relative_risk + >>> result = relative_risk(exposed_cases=10, exposed_total=75, + ... control_cases=12, control_total=225) + >>> result.relative_risk + 2.5 + >>> result.confidence_interval() + ConfidenceInterval(low=1.1261564003469628, high=5.549850800541033) + """ + if not 0 <= confidence_level <= 1: + raise ValueError('confidence_level must be in the interval ' + '[0, 1].') + + # Handle edge cases where either exposed_cases or control_cases + # is zero. We follow the convention of the R function riskratio + # from the epitools library. + if self.exposed_cases == 0 and self.control_cases == 0: + # relative risk is nan. + return ConfidenceInterval(low=np.nan, high=np.nan) + elif self.exposed_cases == 0: + # relative risk is 0. + return ConfidenceInterval(low=0.0, high=np.nan) + elif self.control_cases == 0: + # relative risk is inf + return ConfidenceInterval(low=np.nan, high=np.inf) + + alpha = 1 - confidence_level + z = ndtri(1 - alpha/2) + rr = self.relative_risk + + # Estimate of the variance of log(rr) is + # var(log(rr)) = 1/exposed_cases - 1/exposed_total + + # 1/control_cases - 1/control_total + # and the standard error is the square root of that. + se = np.sqrt(1/self.exposed_cases - 1/self.exposed_total + + 1/self.control_cases - 1/self.control_total) + delta = z*se + katz_lo = rr*np.exp(-delta) + katz_hi = rr*np.exp(delta) + return ConfidenceInterval(low=katz_lo, high=katz_hi) + + +def relative_risk(exposed_cases, exposed_total, control_cases, control_total): + """ + Compute the relative risk (also known as the risk ratio). + + This function computes the relative risk associated with a 2x2 + contingency table ([1]_, section 2.2.3; [2]_, section 3.1.2). Instead + of accepting a table as an argument, the individual numbers that are + used to compute the relative risk are given as separate parameters. + This is to avoid the ambiguity of which row or column of the contingency + table corresponds to the "exposed" cases and which corresponds to the + "control" cases. Unlike, say, the odds ratio, the relative risk is not + invariant under an interchange of the rows or columns. + + Parameters + ---------- + exposed_cases : nonnegative int + The number of "cases" (i.e. occurrence of disease or other event + of interest) among the sample of "exposed" individuals. + exposed_total : positive int + The total number of "exposed" individuals in the sample. + control_cases : nonnegative int + The number of "cases" among the sample of "control" or non-exposed + individuals. + control_total : positive int + The total number of "control" individuals in the sample. + + Returns + ------- + result : instance of `~scipy.stats._result_classes.RelativeRiskResult` + The object has the float attribute ``relative_risk``, which is:: + + rr = (exposed_cases/exposed_total) / (control_cases/control_total) + + The object also has the method ``confidence_interval`` to compute + the confidence interval of the relative risk for a given confidence + level. + + See Also + -------- + odds_ratio + + Notes + ----- + The R package epitools has the function `riskratio`, which accepts + a table with the following layout:: + + disease=0 disease=1 + exposed=0 (ref) n00 n01 + exposed=1 n10 n11 + + With a 2x2 table in the above format, the estimate of the CI is + computed by `riskratio` when the argument method="wald" is given, + or with the function `riskratio.wald`. + + For example, in a test of the incidence of lung cancer among a + sample of smokers and nonsmokers, the "exposed" category would + correspond to "is a smoker" and the "disease" category would + correspond to "has or had lung cancer". + + To pass the same data to ``relative_risk``, use:: + + relative_risk(n11, n10 + n11, n01, n00 + n01) + + .. versionadded:: 1.7.0 + + References + ---------- + .. [1] Alan Agresti, An Introduction to Categorical Data Analysis + (second edition), Wiley, Hoboken, NJ, USA (2007). + .. [2] Hardeo Sahai and Anwer Khurshid, Statistics in Epidemiology, + CRC Press LLC, Boca Raton, FL, USA (1996). + + Examples + -------- + >>> from scipy.stats.contingency import relative_risk + + This example is from Example 3.1 of [2]_. The results of a heart + disease study are summarized in the following table:: + + High CAT Low CAT Total + -------- ------- ----- + CHD 27 44 71 + No CHD 95 443 538 + + Total 122 487 609 + + CHD is coronary heart disease, and CAT refers to the level of + circulating catecholamine. CAT is the "exposure" variable, and + high CAT is the "exposed" category. So the data from the table + to be passed to ``relative_risk`` is:: + + exposed_cases = 27 + exposed_total = 122 + control_cases = 44 + control_total = 487 + + >>> result = relative_risk(27, 122, 44, 487) + >>> result.relative_risk + 2.4495156482861398 + + Find the confidence interval for the relative risk. + + >>> result.confidence_interval(confidence_level=0.95) + ConfidenceInterval(low=1.5836990926700116, high=3.7886786315466354) + + The interval does not contain 1, so the data supports the statement + that high CAT is associated with greater risk of CHD. + """ + # Relative risk is a trivial calculation. The nontrivial part is in the + # `confidence_interval` method of the RelativeRiskResult class. + + exposed_cases = _validate_int(exposed_cases, 0, "exposed_cases") + exposed_total = _validate_int(exposed_total, 1, "exposed_total") + control_cases = _validate_int(control_cases, 0, "control_cases") + control_total = _validate_int(control_total, 1, "control_total") + + if exposed_cases > exposed_total: + raise ValueError('exposed_cases must not exceed exposed_total.') + if control_cases > control_total: + raise ValueError('control_cases must not exceed control_total.') + + if exposed_cases == 0 and control_cases == 0: + # relative risk is 0/0. + rr = np.nan + elif exposed_cases == 0: + # relative risk is 0/nonzero + rr = 0.0 + elif control_cases == 0: + # relative risk is nonzero/0. + rr = np.inf + else: + p1 = exposed_cases / exposed_total + p2 = control_cases / control_total + rr = p1 / p2 + return RelativeRiskResult(relative_risk=rr, + exposed_cases=exposed_cases, + exposed_total=exposed_total, + control_cases=control_cases, + control_total=control_total) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_resampling.py b/venv/lib/python3.12/site-packages/scipy/stats/_resampling.py new file mode 100644 index 0000000000000000000000000000000000000000..3977e3718809ff6772dbee247c506f1257c9a624 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_resampling.py @@ -0,0 +1,2425 @@ +import warnings +import numpy as np +from itertools import combinations, permutations, product, accumulate +from collections.abc import Sequence +from dataclasses import dataclass, field +import inspect +import math + +from scipy._lib._util import (check_random_state, _rename_parameter, rng_integers, + _transition_to_rng) +from scipy._lib._array_api import ( + array_namespace, + is_numpy, + is_array_api_strict, + xp_capabilities, + xp_result_type, + xp_size, + xp_device, + xp_swapaxes, + is_lazy_array, +) +from scipy._external import array_api_extra as xpx +from scipy.special import ndtr, ndtri +from scipy import stats + +from ._common import ConfidenceInterval +from ._axis_nan_policy import _broadcast_concatenate, _broadcast_arrays +from ._warnings_errors import DegenerateDataWarning + +__all__ = ['bootstrap', 'monte_carlo_test', 'permutation_test'] + + +def _vectorize_statistic(statistic): + """Vectorize an n-sample statistic""" + # This is a little cleaner than np.nditer at the expense of some data + # copying: concatenate samples together, then use np.apply_along_axis + def stat_nd(*data, axis=0): + lengths = [sample.shape[axis] for sample in data] + split_indices = np.cumsum(lengths)[:-1] + z = _broadcast_concatenate(data, axis) + + # move working axis to position 0 so that new dimensions in the output + # of `statistic` are _prepended_. ("This axis is removed, and replaced + # with new dimensions...") + z = np.moveaxis(z, axis, 0) + + def stat_1d(z): + data = np.split(z, split_indices) + return statistic(*data) + + return np.apply_along_axis(stat_1d, 0, z)[()] + return stat_nd + + +def _jackknife_resample(sample, batch=None, *, xp): + """Jackknife resample the sample. Only one-sample stats for now.""" + n = sample.shape[-1] + batch_nominal = batch or n + + for k in range(0, n, batch_nominal): + # col_start:col_end are the observations to remove + batch_actual = min(batch_nominal, n-k) + + # jackknife - each row leaves out one observation + j = np.ones((batch_actual, n), dtype=bool) + np.fill_diagonal(j[:, k:k+batch_actual], False) + i = np.arange(n) + i = np.broadcast_to(i, (batch_actual, n)) + i = i[j].reshape((batch_actual, n-1)) + + i = xp.asarray(i, device=xp_device(sample)) + yield _get_from_last_axis(sample, i, xp=xp) + + +def _bootstrap_resample(sample, n_resamples=None, rng=None, *, xp): + """Bootstrap resample the sample.""" + n = sample.shape[-1] + + # bootstrap - each row is a random resample of original observations + i = rng_integers(rng, 0, n, (n_resamples, n)) + + i = xp.asarray(i, device=xp_device(sample)) + return _get_from_last_axis(sample, i, xp=xp) + + +def _get_from_last_axis(sample, i, xp): + if not is_array_api_strict(xp): + return sample[..., i] + + # Equivalent to `sample[..., i]` as used in `bootstrap`. Assumes i.ndim <=2. + if i.ndim == 2: + sample = xp.expand_dims(sample, axis=-2) + sample, i = _broadcast_arrays((sample, i), axis=-1, xp=xp) + return xp.take_along_axis(sample, i, axis=-1) + + +def _percentile_of_score(a, score, axis, xp): + """Vectorized, simplified `scipy.stats.percentileofscore`. + Uses logic of the 'mean' value of percentileofscore's kind parameter. + + Unlike `stats.percentileofscore`, the percentile returned is a fraction + in [0, 1]. + """ + B = a.shape[axis] + nonzeros = (xp.count_nonzero(a < score, axis=axis) + + xp.count_nonzero(a <= score, axis=axis)) + return xp.astype(nonzeros, score.dtype) / (2 * B) + + +def _bca_interval(data, statistic, axis, alpha, theta_hat_b, batch, xp): + """Bias-corrected and accelerated interval.""" + # closely follows [1] 14.3 and 15.4 (Eq. 15.36) + + # calculate z0_hat + theta_hat = xp.expand_dims(statistic(*data, axis=axis), axis=-1) + percentile = _percentile_of_score(theta_hat_b, theta_hat, axis=-1, xp=xp) + percentile = xp.asarray(percentile, dtype=theta_hat.dtype, + device=xp_device(theta_hat)) + z0_hat = ndtri(percentile) + + # calculate a_hat + theta_hat_ji = [] # j is for sample of data, i is for jackknife resample + for j, sample in enumerate(data): + # _jackknife_resample will add an axis prior to the last axis that + # corresponds with the different jackknife resamples. Do the same for + # each sample of the data to ensure broadcastability. We need to + # create a copy of the list containing the samples anyway, so do this + # in the loop to simplify the code. This is not the bottleneck... + samples = [xp.expand_dims(sample, axis=-2) for sample in data] + theta_hat_i = [] + for jackknife_sample in _jackknife_resample(sample, batch, xp=xp): + samples[j] = jackknife_sample + broadcasted = _broadcast_arrays(samples, axis=-1, xp=xp) + theta_hat_i.append(statistic(*broadcasted, axis=-1)) + theta_hat_ji.append(theta_hat_i) + + theta_hat_ji = [xp.concat(theta_hat_i, axis=-1) + for theta_hat_i in theta_hat_ji] + + # CuPy array dtype is unstable in 1.13.6 when divided by large Python int: + # (cp.ones(1, dtype=cp.float32)/10000).dtype # float32 + # (cp.ones(1, dtype=cp.float32)/100000).dtype # float64 + # Solution for now is to make the divisor a Python float + # Looks like this will be fixed in 1.14.0 + n_j = [float(theta_hat_i.shape[-1]) for theta_hat_i in theta_hat_ji] + + theta_hat_j_dot = [xp.mean(theta_hat_i, axis=-1, keepdims=True) + for theta_hat_i in theta_hat_ji] + + U_ji = [(n - 1) * (theta_hat_dot - theta_hat_i) + for theta_hat_dot, theta_hat_i, n + in zip(theta_hat_j_dot, theta_hat_ji, n_j)] + + nums = [xp.sum(U_i**3, axis=-1)/n**3 for U_i, n in zip(U_ji, n_j)] + dens = [xp.sum(U_i**2, axis=-1)/n**2 for U_i, n in zip(U_ji, n_j)] + a_hat = 1/6 * sum(nums) / sum(dens)**(3/2) + + # calculate alpha_1, alpha_2 + # `float` because dtype of ndtri output (float64) should not promote other vals + z_alpha = float(ndtri(alpha)) + z_1alpha = -z_alpha + num1 = z0_hat + z_alpha + alpha_1 = ndtr(z0_hat + num1/(1 - a_hat*num1)) + num2 = z0_hat + z_1alpha + alpha_2 = ndtr(z0_hat + num2/(1 - a_hat*num2)) + return alpha_1, alpha_2, a_hat # return a_hat for testing + + +def _bootstrap_iv(data, statistic, vectorized, paired, axis, confidence_level, + alternative, n_resamples, batch, method, bootstrap_result, + rng): + """Input validation and standardization for `bootstrap`.""" + xp = array_namespace(*data) + + if vectorized not in {True, False, None}: + raise ValueError("`vectorized` must be `True`, `False`, or `None`.") + + if vectorized is None: + vectorized = 'axis' in inspect.signature(statistic).parameters + + if not vectorized: + if not is_numpy(xp): + message = (f"When using array library {xp.__name__}, `func` must be " + "vectorized and accept argument `axis`.") + raise TypeError(message) + + statistic = _vectorize_statistic(statistic) + + axis_int = int(axis) + if axis != axis_int: + raise ValueError("`axis` must be an integer.") + + n_samples = 0 + try: + n_samples = len(data) + except TypeError: + raise ValueError("`data` must be a sequence of samples.") + + if n_samples == 0: + raise ValueError("`data` must contain at least one sample.") + + data = _broadcast_arrays(data, axis_int, xp=xp) + + data_iv = [] + for sample in data: + if sample.shape[axis_int] <= 1: + raise ValueError("each sample in `data` must contain two or more " + "observations along `axis`.") + sample = xp.moveaxis(sample, axis_int, -1) + data_iv.append(sample) + + if paired not in {True, False}: + raise ValueError("`paired` must be `True` or `False`.") + + if paired: + n = data_iv[0].shape[-1] + for sample in data_iv[1:]: + if sample.shape[-1] != n: + message = ("When `paired is True`, all samples must have the " + "same length along `axis`") + raise ValueError(message) + + # to generate the bootstrap distribution for paired-sample statistics, + # resample the indices of the observations + def statistic(i, axis=-1, data=data_iv, unpaired_statistic=statistic): + # data = [sample[..., i] for sample in data] + data = [_get_from_last_axis(sample, i, xp=xp) for sample in data] + return unpaired_statistic(*data, axis=axis) + + data_iv = [xp.arange(n)] + + confidence_level_float = float(confidence_level) + + alternative = alternative.lower() + alternatives = {'two-sided', 'less', 'greater'} + if alternative not in alternatives: + raise ValueError(f"`alternative` must be one of {alternatives}") + + n_resamples_int = int(n_resamples) + if n_resamples != n_resamples_int or n_resamples_int < 0: + raise ValueError("`n_resamples` must be a non-negative integer.") + + if batch is None: + batch_iv = batch + else: + batch_iv = int(batch) + if batch != batch_iv or batch_iv <= 0: + raise ValueError("`batch` must be a positive integer or None.") + + methods = {'percentile', 'basic', 'bca'} + method = method.lower() + if method not in methods: + raise ValueError(f"`method` must be in {methods}") + + message = "`bootstrap_result` must have attribute `bootstrap_distribution'" + if (bootstrap_result is not None + and not hasattr(bootstrap_result, "bootstrap_distribution")): + raise ValueError(message) + + message = ("Either `bootstrap_result.bootstrap_distribution.size` or " + "`n_resamples` must be positive.") + if ((not bootstrap_result or + not xp_size(bootstrap_result.bootstrap_distribution)) + and n_resamples_int == 0): + raise ValueError(message) + + rng = check_random_state(rng) + + return (data_iv, statistic, vectorized, paired, axis_int, + confidence_level_float, alternative, n_resamples_int, batch_iv, + method, bootstrap_result, rng, xp) + + +@dataclass +class BootstrapResult: + """Result object returned by `scipy.stats.bootstrap`. + + Attributes + ---------- + confidence_interval : ConfidenceInterval + The bootstrap confidence interval as an instance of + `collections.namedtuple` with attributes `low` and `high`. + bootstrap_distribution : ndarray + The bootstrap distribution, that is, the value of `statistic` for + each resample. The last dimension corresponds with the resamples + (e.g. ``res.bootstrap_distribution.shape[-1] == n_resamples``). + standard_error : float or ndarray + The bootstrap standard error, that is, the sample standard + deviation of the bootstrap distribution. + + """ + confidence_interval: ConfidenceInterval + bootstrap_distribution: np.ndarray + standard_error: float | np.ndarray + + +@xp_capabilities(skip_backends=[("dask.array", "Dask doesn't have take_along_axis.")], + jax_jit=False) # a few failed assertions - not sure what's going on +@_transition_to_rng('random_state') +def bootstrap(data, statistic, *, n_resamples=9999, batch=None, + vectorized=None, paired=False, axis=0, confidence_level=0.95, + alternative='two-sided', method='BCa', bootstrap_result=None, + rng=None): + r""" + Compute a two-sided bootstrap confidence interval of a statistic. + + When `method` is ``'percentile'`` and `alternative` is ``'two-sided'``, + a bootstrap confidence interval is computed according to the following + procedure. + + 1. Resample the data: for each sample in `data` and for each of + `n_resamples`, take a random sample of the original sample + (with replacement) of the same size as the original sample. + + 2. Compute the bootstrap distribution of the statistic: for each set of + resamples, compute the test statistic. + + 3. Determine the confidence interval: find the interval of the bootstrap + distribution that is + + - symmetric about the median and + - contains `confidence_level` of the resampled statistic values. + + While the ``'percentile'`` method is the most intuitive, it is rarely + used in practice. Two more common methods are available, ``'basic'`` + ('reverse percentile') and ``'BCa'`` ('bias-corrected and accelerated'); + they differ in how step 3 is performed. + + If the samples in `data` are taken at random from their respective + distributions :math:`n` times, the confidence interval returned by + `bootstrap` will contain the true value of the statistic for those + distributions approximately `confidence_level`:math:`\, \times \, n` times. + + Parameters + ---------- + data : sequence of array-like + Each element of `data` is a sample containing scalar observations from an + underlying distribution. Elements of `data` must be broadcastable to the + same shape (with the possible exception of the dimension specified by `axis`). + statistic : callable + Statistic for which the confidence interval is to be calculated. + `statistic` must be a callable that accepts ``len(data)`` samples + as separate arguments and returns the resulting statistic. + If `vectorized` is set ``True``, + `statistic` must also accept a keyword argument `axis` and be + vectorized to compute the statistic along the provided `axis`. + n_resamples : int, default: ``9999`` + The number of resamples performed to form the bootstrap distribution + of the statistic. + batch : int, optional + The number of resamples to process in each vectorized call to + `statistic`. Memory usage is O( `batch` * ``n`` ), where ``n`` is the + sample size. Default is ``None``, in which case ``batch = n_resamples`` + (or ``batch = max(n_resamples, n)`` for ``method='BCa'``). + vectorized : bool, optional + If `vectorized` is set ``False``, `statistic` will not be passed + keyword argument `axis` and is expected to calculate the statistic + only for 1D samples. If ``True``, `statistic` will be passed keyword + argument `axis` and is expected to calculate the statistic along `axis` + when passed an ND sample array. If ``None`` (default), `vectorized` + will be set ``True`` if ``axis`` is a parameter of `statistic`. Use of + a vectorized statistic typically reduces computation time. + paired : bool, default: ``False`` + Whether the statistic treats corresponding elements of the samples + in `data` as paired. If True, `bootstrap` resamples an array of + *indices* and uses the same indices for all arrays in `data`; otherwise, + `bootstrap` independently resamples the elements of each array. + axis : int, default: ``0`` + The axis of the samples in `data` along which the `statistic` is + calculated. + confidence_level : float, default: ``0.95`` + The confidence level of the confidence interval. + alternative : {'two-sided', 'less', 'greater'}, default: ``'two-sided'`` + Choose ``'two-sided'`` (default) for a two-sided confidence interval, + ``'less'`` for a one-sided confidence interval with the lower bound + at ``-np.inf``, and ``'greater'`` for a one-sided confidence interval + with the upper bound at ``np.inf``. The other bound of the one-sided + confidence intervals is the same as that of a two-sided confidence + interval with `confidence_level` twice as far from 1.0; e.g. the upper + bound of a 95% ``'less'`` confidence interval is the same as the upper + bound of a 90% ``'two-sided'`` confidence interval. + method : {'percentile', 'basic', 'bca'}, default: ``'BCa'`` + Whether to return the 'percentile' bootstrap confidence interval + (``'percentile'``), the 'basic' (AKA 'reverse') bootstrap confidence + interval (``'basic'``), or the bias-corrected and accelerated bootstrap + confidence interval (``'BCa'``). + bootstrap_result : BootstrapResult, optional + Provide the result object returned by a previous call to `bootstrap` + to include the previous bootstrap distribution in the new bootstrap + distribution. This can be used, for example, to change + `confidence_level`, change `method`, or see the effect of performing + additional resampling without repeating computations. + rng : `numpy.random.Generator`, optional + Pseudorandom number generator state. When `rng` is None, a new + `numpy.random.Generator` is created using entropy from the + operating system. Types other than `numpy.random.Generator` are + passed to `numpy.random.default_rng` to instantiate a ``Generator``. + + Returns + ------- + res : BootstrapResult + An object with attributes: + + confidence_interval : ConfidenceInterval + The bootstrap confidence interval as an instance of + `collections.namedtuple` with attributes `low` and `high`. + bootstrap_distribution : ndarray + The bootstrap distribution, that is, the value of `statistic` for + each resample. The last dimension corresponds with the resamples + (e.g. ``res.bootstrap_distribution.shape[-1] == n_resamples``). + standard_error : float or ndarray + The bootstrap standard error, that is, the sample standard + deviation of the bootstrap distribution. + + Warns + ----- + `~scipy.stats.DegenerateDataWarning` + Generated when ``method='BCa'`` and the bootstrap distribution is + degenerate (e.g. all elements are identical). + + Notes + ----- + Elements of the confidence interval may be NaN for ``method='BCa'`` if + the bootstrap distribution is degenerate (e.g. all elements are identical). + In this case, consider using another `method` or inspecting `data` for + indications that other analysis may be more appropriate (e.g. all + observations are identical). + + References + ---------- + .. [1] B. Efron and R. J. Tibshirani, An Introduction to the Bootstrap, + Chapman & Hall/CRC, Boca Raton, FL, USA (1993) + .. [2] Nathaniel E. Helwig, "Bootstrap Confidence Intervals", + http://users.stat.umn.edu/~helwig/notes/bootci-Notes.pdf + .. [3] Bootstrapping (statistics), Wikipedia, + https://en.wikipedia.org/wiki/Bootstrapping_%28statistics%29 + + Examples + -------- + Suppose we have sampled data from an unknown distribution. + + >>> import numpy as np + >>> rng = np.random.default_rng() + >>> from scipy.stats import norm + >>> dist = norm(loc=2, scale=4) # our "unknown" distribution + >>> data = dist.rvs(size=100, random_state=rng) + + We are interested in the standard deviation of the distribution. + + >>> std_true = dist.std() # the true value of the statistic + >>> print(std_true) + 4.0 + >>> std_sample = np.std(data) # the sample statistic + >>> print(std_sample) + 3.9460644295563863 + + The bootstrap is used to approximate the variability we would expect if we + were to repeatedly sample from the unknown distribution and calculate the + statistic of the sample each time. It does this by repeatedly resampling + values *from the original sample* with replacement and calculating the + statistic of each resample. This results in a "bootstrap distribution" of + the statistic. + + >>> import matplotlib.pyplot as plt + >>> from scipy.stats import bootstrap + >>> data = (data,) # samples must be in a sequence + >>> res = bootstrap(data, np.std, confidence_level=0.9, rng=rng) + >>> fig, ax = plt.subplots() + >>> ax.hist(res.bootstrap_distribution, bins=25) + >>> ax.set_title('Bootstrap Distribution') + >>> ax.set_xlabel('statistic value') + >>> ax.set_ylabel('frequency') + >>> plt.show() + + The standard error quantifies this variability. It is calculated as the + standard deviation of the bootstrap distribution. + + >>> res.standard_error + 0.24427002125829136 + >>> res.standard_error == np.std(res.bootstrap_distribution, ddof=1) + True + + The bootstrap distribution of the statistic is often approximately normal + with scale equal to the standard error. + + >>> x = np.linspace(3, 5) + >>> pdf = norm.pdf(x, loc=std_sample, scale=res.standard_error) + >>> fig, ax = plt.subplots() + >>> ax.hist(res.bootstrap_distribution, bins=25, density=True) + >>> ax.plot(x, pdf) + >>> ax.set_title('Normal Approximation of the Bootstrap Distribution') + >>> ax.set_xlabel('statistic value') + >>> ax.set_ylabel('pdf') + >>> plt.show() + + This suggests that we could construct a 90% confidence interval on the + statistic based on quantiles of this normal distribution. + + >>> norm.interval(0.9, loc=std_sample, scale=res.standard_error) + (3.5442759991341726, 4.3478528599786) + + Due to central limit theorem, this normal approximation is accurate for a + variety of statistics and distributions underlying the samples; however, + the approximation is not reliable in all cases. Because `bootstrap` is + designed to work with arbitrary underlying distributions and statistics, + it uses more advanced techniques to generate an accurate confidence + interval. + + >>> print(res.confidence_interval) + ConfidenceInterval(low=3.57655333533867, high=4.382043696342881) + + If we sample from the original distribution 100 times and form a bootstrap + confidence interval for each sample, the confidence interval + contains the true value of the statistic approximately 90% of the time. + + >>> n_trials = 100 + >>> ci_contains_true_std = 0 + >>> for i in range(n_trials): + ... data = (dist.rvs(size=100, random_state=rng),) + ... res = bootstrap(data, np.std, confidence_level=0.9, + ... n_resamples=999, rng=rng) + ... ci = res.confidence_interval + ... if ci[0] < std_true < ci[1]: + ... ci_contains_true_std += 1 + >>> print(ci_contains_true_std) + 88 + + Rather than writing a loop, we can also determine the confidence intervals + for all 100 samples at once. + + >>> data = (dist.rvs(size=(n_trials, 100), random_state=rng),) + >>> res = bootstrap(data, np.std, axis=-1, confidence_level=0.9, + ... n_resamples=999, rng=rng) + >>> ci_l, ci_u = res.confidence_interval + + Here, `ci_l` and `ci_u` contain the confidence interval for each of the + ``n_trials = 100`` samples. + + >>> print(ci_l[:5]) + [3.86401283 3.33304394 3.52474647 3.54160981 3.80569252] + >>> print(ci_u[:5]) + [4.80217409 4.18143252 4.39734707 4.37549713 4.72843584] + + And again, approximately 90% contain the true value, ``std_true = 4``. + + >>> print(np.sum((ci_l < std_true) & (std_true < ci_u))) + 93 + + `bootstrap` can also be used to estimate confidence intervals of + multi-sample statistics. For example, to get a confidence interval + for the difference between means, we write a function that accepts + two sample arguments and returns only the statistic. The use of the + ``axis`` argument ensures that all mean calculations are perform in + a single vectorized call, which is faster than looping over pairs + of resamples in Python. + + >>> def my_statistic(sample1, sample2, axis=-1): + ... mean1 = np.mean(sample1, axis=axis) + ... mean2 = np.mean(sample2, axis=axis) + ... return mean1 - mean2 + + Here, we use the 'percentile' method with the default 95% confidence level. + + >>> sample1 = norm.rvs(scale=1, size=100, random_state=rng) + >>> sample2 = norm.rvs(scale=2, size=100, random_state=rng) + >>> data = (sample1, sample2) + >>> res = bootstrap(data, my_statistic, method='basic', rng=rng) + >>> print(my_statistic(sample1, sample2)) + 0.16661030792089523 + >>> print(res.confidence_interval) + ConfidenceInterval(low=-0.29087973240818693, high=0.6371338699912273) + + The bootstrap estimate of the standard error is also available. + + >>> print(res.standard_error) + 0.238323948262459 + + Paired-sample statistics work, too. For example, consider the Pearson + correlation coefficient. + + >>> from scipy.stats import pearsonr + >>> n = 100 + >>> x = np.linspace(0, 10, n) + >>> y = x + rng.uniform(size=n) + >>> print(pearsonr(x, y)[0]) # element 0 is the statistic + 0.9954306665125647 + + We wrap `pearsonr` so that it returns only the statistic, ensuring + that we use the `axis` argument because it is available. + + >>> def my_statistic(x, y, axis=-1): + ... return pearsonr(x, y, axis=axis)[0] + + We call `bootstrap` using ``paired=True``. + + >>> res = bootstrap((x, y), my_statistic, paired=True, rng=rng) + >>> print(res.confidence_interval) + ConfidenceInterval(low=0.9941504301315878, high=0.996377412215445) + + The result object can be passed back into `bootstrap` to perform additional + resampling: + + >>> len(res.bootstrap_distribution) + 9999 + >>> res = bootstrap((x, y), my_statistic, paired=True, + ... n_resamples=1000, rng=rng, + ... bootstrap_result=res) + >>> len(res.bootstrap_distribution) + 10999 + + or to change the confidence interval options: + + >>> res2 = bootstrap((x, y), my_statistic, paired=True, + ... n_resamples=0, rng=rng, bootstrap_result=res, + ... method='percentile', confidence_level=0.9) + >>> np.testing.assert_equal(res2.bootstrap_distribution, + ... res.bootstrap_distribution) + >>> res.confidence_interval + ConfidenceInterval(low=0.9941574828235082, high=0.9963781698210212) + + without repeating computation of the original bootstrap distribution. + + """ + # Input validation + args = _bootstrap_iv(data, statistic, vectorized, paired, axis, + confidence_level, alternative, n_resamples, batch, + method, bootstrap_result, rng) + (data, statistic, vectorized, paired, axis, confidence_level, + alternative, n_resamples, batch, method, bootstrap_result, + rng, xp) = args + + theta_hat_b = ([] if bootstrap_result is None + else [bootstrap_result.bootstrap_distribution]) + + batch_nominal = batch or n_resamples or 1 + + for k in range(0, n_resamples, batch_nominal): + batch_actual = min(batch_nominal, n_resamples-k) + # Generate resamples + resampled_data = [] + for sample in data: + resample = _bootstrap_resample(sample, n_resamples=batch_actual, + rng=rng, xp=xp) + resampled_data.append(resample) + + # Compute bootstrap distribution of statistic + theta_hat_b.append(statistic(*resampled_data, axis=-1)) + theta_hat_b = xp.concat(theta_hat_b, axis=-1) + + # Calculate percentile interval + alpha = ((1 - confidence_level)/2 if alternative == 'two-sided' + else (1 - confidence_level)) + if method == 'bca': + interval = _bca_interval(data, statistic, axis=-1, alpha=alpha, + theta_hat_b=theta_hat_b, batch=batch, xp=xp)[:2] + else: + alpha = xp.asarray(alpha, dtype=theta_hat_b.dtype, + device=xp_device(theta_hat_b)) + interval = alpha, 1 - alpha + + # Calculate confidence interval of statistic + interval = xp.stack(interval, axis=-1) + ci = stats.quantile(theta_hat_b, interval, axis=-1) + if not is_lazy_array(ci) and xp.any(xp.isnan(ci)): + msg = ( + "The BCa confidence interval cannot be calculated. " + "This problem is known to occur when the distribution " + "is degenerate or the statistic is np.min." + ) + warnings.warn(DegenerateDataWarning(msg), stacklevel=2) + + ci_l = ci[..., 0] + ci_u = ci[..., 1] + + if method == 'basic': # see [3] + theta_hat = statistic(*data, axis=-1) + ci_l, ci_u = 2*theta_hat - ci_u, 2*theta_hat - ci_l + + if alternative == 'less': + ci_l = xp.full_like(ci_l, -xp.inf) + elif alternative == 'greater': + ci_u = xp.full_like(ci_u, xp.inf) + + standard_error = xp.std(theta_hat_b, correction=1, axis=-1) + + ci_l = ci_l[()] if ci_l.ndim == 0 else ci_l + ci_u = ci_u[()] if ci_u.ndim == 0 else ci_u + standard_error = standard_error[()] if standard_error.ndim == 0 else standard_error + + return BootstrapResult(confidence_interval=ConfidenceInterval(ci_l, ci_u), + bootstrap_distribution=theta_hat_b, + standard_error=standard_error) + + +def _monte_carlo_test_iv(data, rvs, statistic, vectorized, n_resamples, + batch, alternative, axis): + """Input validation for `monte_carlo_test`.""" + axis_int = int(axis) + if axis != axis_int: + raise ValueError("`axis` must be an integer.") + + if vectorized not in {True, False, None}: + raise ValueError("`vectorized` must be `True`, `False`, or `None`.") + + if not isinstance(rvs, Sequence): + rvs = (rvs,) + data = (data,) + for rvs_i in rvs: + if not callable(rvs_i): + raise TypeError("`rvs` must be callable or sequence of callables.") + + # At this point, `data` should be a sequence + # If it isn't, the user passed a sequence for `rvs` but not `data` + message = "If `rvs` is a sequence, `len(rvs)` must equal `len(data)`." + try: + len(data) + except TypeError as e: + raise ValueError(message) from e + if not len(rvs) == len(data): + raise ValueError(message) + + if not callable(statistic): + raise TypeError("`statistic` must be callable.") + + if vectorized is None: + try: + signature = inspect.signature(statistic).parameters + except ValueError as e: + message = (f"Signature inspection of {statistic=} failed; " + "pass `vectorize` explicitly.") + raise ValueError(message) from e + vectorized = 'axis' in signature + + xp = array_namespace(*data) + dtype = xp_result_type(*data, force_floating=True, xp=xp) + + if not vectorized: + if is_numpy(xp): + statistic_vectorized = _vectorize_statistic(statistic) + else: + message = ("`statistic` must be vectorized (i.e. support an `axis` " + f"argument) when `data` contains {xp.__name__} arrays.") + raise ValueError(message) + else: + statistic_vectorized = statistic + + data = _broadcast_arrays(data, axis, xp=xp) + data_iv = [] + for sample in data: + sample = xp.broadcast_to(sample, (1,)) if sample.ndim == 0 else sample + sample = xp.moveaxis(sample, axis_int, -1) + data_iv.append(sample) + + n_resamples_int = int(n_resamples) + if n_resamples != n_resamples_int or n_resamples_int <= 0: + raise ValueError("`n_resamples` must be a positive integer.") + + if batch is None: + batch_iv = batch + else: + batch_iv = int(batch) + if batch != batch_iv or batch_iv <= 0: + raise ValueError("`batch` must be a positive integer or None.") + + alternatives = {'two-sided', 'greater', 'less'} + alternative = alternative.lower() + if alternative not in alternatives: + raise ValueError(f"`alternative` must be in {alternatives}") + + return (data_iv, rvs, statistic_vectorized, vectorized, n_resamples_int, + batch_iv, alternative, axis_int, dtype, xp) + + +@dataclass +class MonteCarloTestResult: + """Result object returned by `scipy.stats.monte_carlo_test`. + + Attributes + ---------- + statistic : float or ndarray + The observed test statistic of the sample. + pvalue : float or ndarray + The p-value for the given alternative. + null_distribution : ndarray + The values of the test statistic generated under the null + hypothesis. + """ + statistic: float | np.ndarray + pvalue: float | np.ndarray + null_distribution: np.ndarray + + +@xp_capabilities() +@_rename_parameter('sample', 'data') +def monte_carlo_test(data, rvs, statistic, *, vectorized=None, + n_resamples=9999, batch=None, alternative="two-sided", + axis=0): + r"""Perform a Monte Carlo hypothesis test. + + `data` contains a sample or a sequence of one or more samples. `rvs` + specifies the distribution(s) of the sample(s) in `data` under the null + hypothesis. The value of `statistic` for the given `data` is compared + against a Monte Carlo null distribution: the value of the statistic for + each of `n_resamples` sets of samples generated using `rvs`. This gives + the p-value, the probability of observing such an extreme value of the + test statistic under the null hypothesis. + + Parameters + ---------- + data : array-like or sequence of array-like + An array or sequence of arrays of observations. + rvs : callable or tuple of callables + A callable or sequence of callables that generates random variates + under the null hypothesis. Each element of `rvs` must be a callable + that accepts keyword argument ``size`` (e.g. ``rvs(size=(m, n))``) and + returns an N-d array sample of that shape. If `rvs` is a sequence, the + number of callables in `rvs` must match the number of samples in + `data`, i.e. ``len(rvs) == len(data)``. If `rvs` is a single callable, + `data` is treated as a single sample. + statistic : callable + Statistic for which the p-value of the hypothesis test is to be + calculated. `statistic` must be a callable that accepts a sample + (e.g. ``statistic(sample)``) or ``len(rvs)`` separate samples (e.g. + ``statistic(samples1, sample2)`` if `rvs` contains two callables and + `data` contains two samples) and returns the resulting statistic. + If `vectorized` is set ``True``, `statistic` must also accept a keyword + argument `axis` and be vectorized to compute the statistic along the + provided `axis` of the samples in `data`. + vectorized : bool, optional + If `vectorized` is set ``False``, `statistic` will not be passed + keyword argument `axis` and is expected to calculate the statistic + only for 1D samples. If ``True``, `statistic` will be passed keyword + argument `axis` and is expected to calculate the statistic along `axis` + when passed ND sample arrays. If ``None`` (default), `vectorized` + will be set ``True`` if ``axis`` is a parameter of `statistic`. Use of + a vectorized statistic typically reduces computation time. + n_resamples : int, default: 9999 + Number of samples drawn from each of the callables of `rvs`. + Equivalently, the number statistic values under the null hypothesis + used as the Monte Carlo null distribution. + batch : int, optional + The number of Monte Carlo samples to process in each call to + `statistic`. Memory usage is O( `batch` * ``sample.size[axis]`` ). Default + is ``None``, in which case `batch` equals `n_resamples`. + alternative : {'two-sided', 'less', 'greater'} + The alternative hypothesis for which the p-value is calculated. + For each alternative, the p-value is defined as follows. + + - ``'greater'`` : the percentage of the null distribution that is + greater than or equal to the observed value of the test statistic. + - ``'less'`` : the percentage of the null distribution that is + less than or equal to the observed value of the test statistic. + - ``'two-sided'`` : twice the smaller of the p-values above. + + axis : int, default: 0 + The axis of `data` (or each sample within `data`) over which to + calculate the statistic. + + Returns + ------- + res : MonteCarloTestResult + An object with attributes: + + statistic : float or ndarray + The test statistic of the observed `data`. + pvalue : float or ndarray + The p-value for the given alternative. + null_distribution : ndarray + The values of the test statistic generated under the null + hypothesis. + + .. warning:: + The p-value is calculated by counting the elements of the null + distribution that are as extreme or more extreme than the observed + value of the statistic. Due to the use of finite precision arithmetic, + some statistic functions return numerically distinct values when the + theoretical values would be exactly equal. In some cases, this could + lead to a large error in the calculated p-value. `monte_carlo_test` + guards against this by considering elements in the null distribution + that are "close" (within a relative tolerance of 100 times the + floating point epsilon of inexact dtypes) to the observed + value of the test statistic as equal to the observed value of the + test statistic. However, the user is advised to inspect the null + distribution to assess whether this method of comparison is + appropriate, and if not, calculate the p-value manually. + + References + ---------- + + .. [1] B. Phipson and G. K. Smyth. "Permutation P-values Should Never Be + Zero: Calculating Exact P-values When Permutations Are Randomly Drawn." + Statistical Applications in Genetics and Molecular Biology 9.1 (2010). + + Examples + -------- + + Suppose we wish to test whether a small sample has been drawn from a normal + distribution. We decide that we will use the skew of the sample as a + test statistic, and we will consider a p-value of 0.05 to be statistically + significant. + + >>> import numpy as np + >>> from scipy import stats + >>> def statistic(x, axis): + ... return stats.skew(x, axis) + + After collecting our data, we calculate the observed value of the test + statistic. + + >>> rng = np.random.default_rng() + >>> x = stats.skewnorm.rvs(a=1, size=50, random_state=rng) + >>> statistic(x, axis=0) + 0.12457412450240658 + + To determine the probability of observing such an extreme value of the + skewness by chance if the sample were drawn from the normal distribution, + we can perform a Monte Carlo hypothesis test. The test will draw many + samples at random from their normal distribution, calculate the skewness + of each sample, and compare our original skewness against this + distribution to determine an approximate p-value. + + >>> from scipy.stats import monte_carlo_test + >>> # because our statistic is vectorized, we pass `vectorized=True` + >>> rvs = lambda size: stats.norm.rvs(size=size, random_state=rng) + >>> res = monte_carlo_test(x, rvs, statistic, vectorized=True) + >>> print(res.statistic) + 0.12457412450240658 + >>> print(res.pvalue) + 0.7012 + + The probability of obtaining a test statistic less than or equal to the + observed value under the null hypothesis is ~70%. This is greater than + our chosen threshold of 5%, so we cannot consider this to be significant + evidence against the null hypothesis. + + Note that this p-value essentially matches that of + `scipy.stats.skewtest`, which relies on an asymptotic distribution of a + test statistic based on the sample skewness. + + >>> stats.skewtest(x).pvalue + 0.6892046027110614 + + This asymptotic approximation is not valid for small sample sizes, but + `monte_carlo_test` can be used with samples of any size. + + >>> x = stats.skewnorm.rvs(a=1, size=7, random_state=rng) + >>> # stats.skewtest(x) would produce an error due to small sample + >>> res = monte_carlo_test(x, rvs, statistic, vectorized=True) + + The Monte Carlo distribution of the test statistic is provided for + further investigation. + + >>> import matplotlib.pyplot as plt + >>> fig, ax = plt.subplots() + >>> ax.hist(res.null_distribution, bins=50) + >>> ax.set_title("Monte Carlo distribution of test statistic") + >>> ax.set_xlabel("Value of Statistic") + >>> ax.set_ylabel("Frequency") + >>> plt.show() + + """ + args = _monte_carlo_test_iv(data, rvs, statistic, vectorized, + n_resamples, batch, alternative, axis) + (data, rvs, statistic, vectorized, n_resamples, + batch, alternative, axis, dtype, xp) = args + + # Some statistics return plain floats; ensure they're at least a NumPy float + observed = xp.asarray(statistic(*data, axis=-1)) + observed = observed[()] if observed.ndim == 0 else observed + + n_observations = [sample.shape[-1] for sample in data] + batch_nominal = batch or n_resamples + null_distribution = [] + for k in range(0, n_resamples, batch_nominal): + batch_actual = min(batch_nominal, n_resamples - k) + resamples = [rvs_i(size=(batch_actual, n_observations_i)) + for rvs_i, n_observations_i in zip(rvs, n_observations)] + null_distribution.append(statistic(*resamples, axis=-1)) + null_distribution = xp.concat(null_distribution) + null_distribution = xp.reshape(null_distribution, (-1,) + (1,)*observed.ndim) + + # relative tolerance for detecting numerically distinct but + # theoretically equal values in the null distribution + eps = (0 if not xp.isdtype(observed.dtype, ('real floating')) + else xp.finfo(observed.dtype).eps*100) + gamma = xp.abs(eps * observed) + + def less(null_distribution, observed): + cmps = null_distribution <= observed + gamma + cmps = xp.asarray(cmps, dtype=dtype) + pvalues = (xp.sum(cmps, axis=0, dtype=dtype) + 1.) / (n_resamples + 1.) + return pvalues + + def greater(null_distribution, observed): + cmps = null_distribution >= observed - gamma + cmps = xp.asarray(cmps, dtype=dtype) + pvalues = (xp.sum(cmps, axis=0, dtype=dtype) + 1.) / (n_resamples + 1.) + return pvalues + + def two_sided(null_distribution, observed): + pvalues_less = less(null_distribution, observed) + pvalues_greater = greater(null_distribution, observed) + pvalues = xp.minimum(pvalues_less, pvalues_greater) * 2 + return pvalues + + compare = {"less": less, + "greater": greater, + "two-sided": two_sided} + + pvalues = compare[alternative](null_distribution, observed) + pvalues = xp.clip(pvalues, 0., 1.) + + return MonteCarloTestResult(observed, pvalues, null_distribution) + + +@dataclass +class PowerResult: + """Result object returned by `scipy.stats.power`. + + Attributes + ---------- + power : float or ndarray + The estimated power. + pvalues : float or ndarray + The simulated p-values. + """ + power: float | np.ndarray + pvalues: float | np.ndarray + + +def _wrap_kwargs(fun): + """Wrap callable to accept arbitrary kwargs and ignore unused ones""" + + try: + keys = set(inspect.signature(fun).parameters.keys()) + except ValueError: + # NumPy Generator methods can't be inspected + keys = {'size'} + + # Set keys=keys/fun=fun to avoid late binding gotcha + def wrapped_rvs_i(*args, keys=keys, fun=fun, **all_kwargs): + kwargs = {key: val for key, val in all_kwargs.items() + if key in keys} + return fun(*args, **kwargs) + return wrapped_rvs_i + + +def _power_iv(rvs, test, n_observations, significance, vectorized, + n_resamples, batch, kwargs): + """Input validation for `monte_carlo_test`.""" + if vectorized not in {True, False, None}: + raise ValueError("`vectorized` must be `True`, `False`, or `None`.") + + if not isinstance(rvs, Sequence): + rvs = (rvs,) + n_observations = (n_observations,) + for rvs_i in rvs: + if not callable(rvs_i): + raise TypeError("`rvs` must be callable or sequence of callables.") + + if not len(rvs) == len(n_observations): + message = ("If `rvs` is a sequence, `len(rvs)` " + "must equal `len(n_observations)`.") + raise ValueError(message) + + kwargs = dict() if kwargs is None else kwargs + if not isinstance(kwargs, dict): + raise TypeError("`kwargs` must be a dictionary that maps keywords to arrays.") + + vals = kwargs.values() + keys = kwargs.keys() + + xp = array_namespace(*n_observations, significance, *vals) + + significance = xp.asarray(significance) + if not xp.isdtype(significance.dtype, "real floating"): + raise ValueError("`significance` must be of floating point dtype.") + + if is_lazy_array(significance): + significance = xp.where((significance < 0.) | (significance > 1.), + xp.nan, significance) + elif xp.min(significance) < 0. or xp.max(significance) > 1.: + raise ValueError("All elements of `significance` must be between 0. and 1.") + + # Wrap callables to ignore unused keyword arguments + wrapped_rvs = [_wrap_kwargs(rvs_i) for rvs_i in rvs] + + # Broadcast, then ravel nobs/kwarg combinations. In the end, + # `nobs` and `vals` have shape (# of combinations, number of variables) + # todo: find a better way to do this without combining arrays + tmp = xp.stack(xp.broadcast_arrays(*n_observations, *vals)) + shape = tmp.shape + if tmp.ndim == 1: + tmp = xp.expand_dims(tmp, axis=0) + else: + tmp = xp.reshape(tmp, (shape[0], -1)).T + nobs, vals = tmp[:, :len(rvs)], tmp[:, len(rvs):] + integer_dtype = xp_result_type(*n_observations, xp=xp) + nobs = xp.astype(nobs, integer_dtype) + + if not callable(test): + raise TypeError("`test` must be callable.") + + if vectorized is None: + vectorized = 'axis' in inspect.signature(test).parameters + + test_vectorized = test + if not vectorized: + if not is_numpy(xp): + message = (f"When using array library {xp.__name__}, `test` must be " + "be vectorized and accept argument `axis`.") + raise TypeError(message) + + test_vectorized = _vectorize_statistic(test) + + # Wrap `test` function to ignore unused kwargs + test_vectorized = _wrap_kwargs(test_vectorized) + + n_resamples_int = int(n_resamples) + if n_resamples != n_resamples_int or n_resamples_int <= 0: + raise ValueError("`n_resamples` must be a positive integer.") + + if batch is None: + batch_iv = batch + else: + batch_iv = int(batch) + if batch != batch_iv or batch_iv <= 0: + raise ValueError("`batch` must be a positive integer or None.") + + return (wrapped_rvs, test_vectorized, nobs, significance, vectorized, + n_resamples_int, batch_iv, vals, keys, shape[1:], xp) + + +@xp_capabilities(skip_backends=[('dask.array', 'just because')], + jax_jit=False) # some problem with batch looping +def power(test, rvs, n_observations, *, significance=0.01, vectorized=None, + n_resamples=10000, batch=None, kwargs=None): + r"""Simulate the power of a hypothesis test under an alternative hypothesis. + + Parameters + ---------- + test : callable + Hypothesis test for which the power is to be simulated. + `test` must be a callable that accepts a sample (e.g. ``test(sample)``) + or ``len(rvs)`` separate samples (e.g. ``test(samples1, sample2)`` if + `rvs` contains two callables and `n_observations` contains two values) + and returns the p-value of the test. + If `vectorized` is set to ``True``, `test` must also accept a keyword + argument `axis` and be vectorized to perform the test along the + provided `axis` of the samples. + Any callable from `scipy.stats` with an `axis` argument that returns an + object with a `pvalue` attribute is also acceptable. + rvs : callable or tuple of callables + A callable or sequence of callables that generate(s) random variates + under the alternative hypothesis. Each element of `rvs` must accept + keyword argument ``size`` (e.g. ``rvs(size=(m, n))``) and return an + N-d array of that shape. If `rvs` is a sequence, the number of callables + in `rvs` must match the number of elements of `n_observations`, i.e. + ``len(rvs) == len(n_observations)``. If `rvs` is a single callable, + `n_observations` is treated as a single element. + n_observations : tuple of ints or tuple of int arrays + If a sequence of ints, each is the sizes of a sample to be passed to `test`. + If a sequence of integer arrays, the power is simulated for each + set of corresponding sample sizes. See Examples. + significance : float or array_like of floats, default: 0.01 + The threshold for significance; i.e., the p-value below which the + hypothesis test results will be considered as evidence against the null + hypothesis. Equivalently, the acceptable rate of Type I error under + the null hypothesis. If an array, the power is simulated for each + significance threshold. + vectorized : bool, optional + If `vectorized` is set to ``False``, `test` will not be passed keyword + argument `axis` and is expected to perform the test only for 1D samples. + If ``True``, `test` will be passed keyword argument `axis` and is + expected to perform the test along `axis` when passed N-D sample arrays. + If ``None`` (default), `vectorized` will be set ``True`` if ``axis`` is + a parameter of `test`. Use of a vectorized test typically reduces + computation time. + n_resamples : int, default: 10000 + Number of samples drawn from each of the callables of `rvs`. + Equivalently, the number tests performed under the alternative + hypothesis to approximate the power. + batch : int, optional + The number of samples to process in each call to `test`. Memory usage is + proportional to the product of `batch` and the largest sample size. Default + is ``None``, in which case `batch` equals `n_resamples`. + kwargs : dict, optional + Keyword arguments to be passed to `rvs` and/or `test` callables. + Introspection is used to determine which keyword arguments may be + passed to each callable. + The value corresponding with each keyword must be an array. + Arrays must be broadcastable with one another and with each array in + `n_observations`. The power is simulated for each set of corresponding + sample sizes and arguments. See Examples. + + Returns + ------- + res : PowerResult + An object with attributes: + + power : float or ndarray + The estimated power against the alternative. + pvalues : ndarray + The p-values observed under the alternative hypothesis. + + Notes + ----- + The power is simulated as follows: + + - Draw many random samples (or sets of samples), each of the size(s) + specified by `n_observations`, under the alternative specified by + `rvs`. + - For each sample (or set of samples), compute the p-value according to + `test`. These p-values are recorded in the ``pvalues`` attribute of + the result object. + - Compute the proportion of p-values that are less than the `significance` + level. This is the power recorded in the ``power`` attribute of the + result object. + + Suppose that `significance` is an array with shape ``shape1``, the elements + of `kwargs` and `n_observations` are mutually broadcastable to shape ``shape2``, + and `test` returns an array of p-values of shape ``shape3``. Then the result + object ``power`` attribute will be of shape ``shape1 + shape2 + shape3``, and + the ``pvalues`` attribute will be of shape ``shape2 + shape3 + (n_resamples,)``. + + Examples + -------- + Suppose we wish to simulate the power of the independent sample t-test + under the following conditions: + + - The first sample has 10 observations drawn from a normal distribution + with mean 0. + - The second sample has 12 observations drawn from a normal distribution + with mean 1.0. + - The threshold on p-values for significance is 0.05. + + >>> import numpy as np + >>> from scipy import stats + >>> rng = np.random.default_rng(2549598345528) + >>> + >>> test = stats.ttest_ind + >>> n_observations = (10, 12) + >>> rvs1 = rng.normal + >>> rvs2 = lambda size: rng.normal(loc=1, size=size) + >>> rvs = (rvs1, rvs2) + >>> res = stats.power(test, rvs, n_observations, significance=0.05) + >>> res.power + 0.6116 + + With samples of size 10 and 12, respectively, the power of the t-test + with a significance threshold of 0.05 is approximately 60% under the chosen + alternative. We can investigate the effect of sample size on the power + by passing sample size arrays. + + >>> import matplotlib.pyplot as plt + >>> nobs_x = np.arange(5, 21) + >>> nobs_y = nobs_x + >>> n_observations = (nobs_x, nobs_y) + >>> res = stats.power(test, rvs, n_observations, significance=0.05) + >>> ax = plt.subplot() + >>> ax.plot(nobs_x, res.power) + >>> ax.set_xlabel('Sample Size') + >>> ax.set_ylabel('Simulated Power') + >>> ax.set_title('Simulated Power of `ttest_ind` with Equal Sample Sizes') + >>> plt.show() + + Alternatively, we can investigate the impact that effect size has on the power. + In this case, the effect size is the location of the distribution underlying + the second sample. + + >>> n_observations = (10, 12) + >>> loc = np.linspace(0, 1, 20) + >>> rvs2 = lambda size, loc: rng.normal(loc=loc, size=size) + >>> rvs = (rvs1, rvs2) + >>> res = stats.power(test, rvs, n_observations, significance=0.05, + ... kwargs={'loc': loc}) + >>> ax = plt.subplot() + >>> ax.plot(loc, res.power) + >>> ax.set_xlabel('Effect Size') + >>> ax.set_ylabel('Simulated Power') + >>> ax.set_title('Simulated Power of `ttest_ind`, Varying Effect Size') + >>> plt.show() + + We can also use `power` to estimate the Type I error rate (also referred to by the + ambiguous term "size") of a test and assess whether it matches the nominal level. + For example, the null hypothesis of `jarque_bera` is that the sample was drawn from + a distribution with the same skewness and kurtosis as the normal distribution. To + estimate the Type I error rate, we can consider the null hypothesis to be a true + *alternative* hypothesis and calculate the power. + + >>> test = stats.jarque_bera + >>> n_observations = 10 + >>> rvs = rng.normal + >>> significance = np.linspace(0.0001, 0.1, 1000) + >>> res = stats.power(test, rvs, n_observations, significance=significance) + >>> size = res.power + + As shown below, the Type I error rate of the test is far below the nominal level + for such a small sample, as mentioned in its documentation. + + >>> ax = plt.subplot() + >>> ax.plot(significance, size) + >>> ax.plot([0, 0.1], [0, 0.1], '--') + >>> ax.set_xlabel('nominal significance level') + >>> ax.set_ylabel('estimated test size (Type I error rate)') + >>> ax.set_title('Estimated test size vs nominal significance level') + >>> ax.set_aspect('equal', 'box') + >>> ax.legend(('`ttest_1samp`', 'ideal test')) + >>> plt.show() + + As one might expect from such a conservative test, the power is quite low with + respect to some alternatives. For example, the power of the test under the + alternative that the sample was drawn from the Laplace distribution may not + be much greater than the Type I error rate. + + >>> rvs = rng.laplace + >>> significance = np.linspace(0.0001, 0.1, 1000) + >>> res = stats.power(test, rvs, n_observations, significance=0.05) + >>> print(res.power) + 0.0587 + + This is not a mistake in SciPy's implementation; it is simply due to the fact + that the null distribution of the test statistic is derived under the assumption + that the sample size is large (i.e. approaches infinity), and this asymptotic + approximation is not accurate for small samples. In such cases, resampling + and Monte Carlo methods (e.g. `permutation_test`, `goodness_of_fit`, + `monte_carlo_test`) may be more appropriate. + + """ + tmp = _power_iv(rvs, test, n_observations, significance, + vectorized, n_resamples, batch, kwargs) + (rvs, test, nobs, significance, + vectorized, n_resamples, batch, args, kwds, shape, xp) = tmp + + batch_nominal = batch or n_resamples + pvalues = [] # results of various nobs/kwargs combinations + for i in range(nobs.shape[0]): + nobs_i, args_i = nobs[i, ...], args[i, ...] + kwargs_i = dict(zip(kwds, args_i)) + pvalues_i = [] # results of batches; fixed nobs/kwargs combination + for k in range(0, n_resamples, batch_nominal): + batch_actual = min(batch_nominal, n_resamples - k) + resamples = [rvs_j(size=(batch_actual, int(nobs_ij)), **kwargs_i) + for rvs_j, nobs_ij in zip(rvs, nobs_i)] + res = test(*resamples, **kwargs_i, axis=-1) + p = getattr(res, 'pvalue', res) + pvalues_i.append(p) + # Concatenate results from batches + pvalues_i = xp.concat(pvalues_i, axis=-1) + pvalues.append(pvalues_i) + # `test` can return result with array of p-values + shape += pvalues_i.shape[:-1] + # Concatenate results from various nobs/kwargs combinations + pvalues = xp.concat(pvalues, axis=0) + # nobs/kwargs arrays were raveled to single axis; unravel + pvalues = xp.reshape(pvalues, shape + (-1,)) + if significance.ndim > 0: + newdims = tuple(range(significance.ndim, pvalues.ndim + significance.ndim)) + significance = xpx.expand_dims(significance, axis=newdims) + + float_dtype = xp_result_type(significance, pvalues, xp=xp) + powers = xp.mean(xp.astype(pvalues < significance, float_dtype), axis=-1) + + return PowerResult(power=powers, pvalues=pvalues) + + +@dataclass +class PermutationTestResult: + """Result object returned by `scipy.stats.permutation_test`. + + Attributes + ---------- + statistic : float or ndarray + The observed test statistic of the data. + pvalue : float or ndarray + The p-value for the given alternative. + null_distribution : ndarray + The values of the test statistic generated under the null + hypothesis. + """ + statistic: float | np.ndarray + pvalue: float | np.ndarray + null_distribution: np.ndarray + + +def _all_partitions_concatenated(ns): + """ + Generate all partitions of indices of groups of given sizes, concatenated + + `ns` is an iterable of ints. + """ + def all_partitions(z, n): + for c in combinations(z, n): + x0 = set(c) + x1 = z - x0 + yield [x0, x1] + + def all_partitions_n(z, ns): + if len(ns) == 0: + yield [z] + return + for c in all_partitions(z, ns[0]): + for d in all_partitions_n(c[1], ns[1:]): + yield c[0:1] + d + + z = set(range(np.sum(ns))) + for partitioning in all_partitions_n(z, ns[:]): + x = np.concatenate([list(partition) + for partition in partitioning]).astype(int) + yield x + + +def _batch_generator(iterable, batch): + """A generator that yields batches of elements from an iterable""" + iterator = iter(iterable) + if batch <= 0: + raise ValueError("`batch` must be positive.") + z = [item for i, item in zip(range(batch), iterator)] + while z: # we don't want StopIteration without yielding an empty list + yield z + z = [item for i, item in zip(range(batch), iterator)] + + +def _pairings_permutations_gen(n_permutations, n_samples, n_obs_sample, batch, + rng): + # Returns a generator that yields arrays of size + # `(batch, n_samples, n_obs_sample)`. + # Each row is an independent permutation of indices 0 to `n_obs_sample`. + batch = min(batch, n_permutations) + + if hasattr(rng, 'permuted'): + def batched_perm_generator(): + indices = np.arange(n_obs_sample) + indices = np.tile(indices, (batch, n_samples, 1)) + for k in range(0, n_permutations, batch): + batch_actual = min(batch, n_permutations-k) + # Don't permute in place, otherwise results depend on `batch` + permuted_indices = rng.permuted(indices, axis=-1) + yield permuted_indices[:batch_actual] + else: # RandomState and early Generators don't have `permuted` + def batched_perm_generator(): + for k in range(0, n_permutations, batch): + batch_actual = min(batch, n_permutations-k) + size = (batch_actual, n_samples, n_obs_sample) + x = rng.random(size=size) + yield np.argsort(x, axis=-1)[:batch_actual] + + return batched_perm_generator() + + +def _calculate_null_both(data, statistic, n_permutations, batch, + rng=None, *, xp): + """ + Calculate null distribution for independent sample tests. + """ + # compute number of permutations + # (distinct partitions of data into samples of these sizes) + n_obs_i = [sample.shape[-1] for sample in data] # observations per sample + n_obs_ic = list(accumulate(n_obs_i, initial=0)) + n_obs = n_obs_ic[-1] # total number of observations + n_max = math.prod([math.comb(n, k) for n, k in zip(n_obs_ic[1:], n_obs_ic[:-1])]) + + # perm_generator is an iterator that produces permutations of indices + # from 0 to n_obs. We'll concatenate the samples, use these indices to + # permute the data, then split the samples apart again. + if n_permutations >= n_max: + exact_test = True + n_permutations = n_max + perm_generator = _all_partitions_concatenated(n_obs_i) + else: + exact_test = False + # Neither RandomState.permutation nor Generator.permutation + # can permute axis-slices independently. If this feature is + # added in the future, batches of the desired size should be + # generated in a single call. + perm_generator = (rng.permutation(n_obs) + for i in range(n_permutations)) + + batch = batch or int(n_permutations) + null_distribution = [] + + # First, concatenate all the samples. In batches, permute samples with + # indices produced by the `perm_generator`, split them into new samples of + # the original sizes, compute the statistic for each batch, and add these + # statistic values to the null distribution. + data = xp.concat(data, axis=-1) + for indices in _batch_generator(perm_generator, batch=batch): + # Creating a tensor from a list of numpy.ndarrays is extremely slow... + indices = np.asarray(indices) + indices = xp.asarray(indices) + + # `indices` is 2D: each row is a permutation of the indices. + # We use it to index `data` along its last axis, which corresponds + # with observations. + # After indexing, the second to last axis of `data_batch` corresponds + # with permutations, and the last axis corresponds with observations. + # data_batch = data[..., indices] + data_batch = _get_from_last_axis(data, indices, xp=xp) + + # Move the permutation axis to the front: we'll concatenate a list + # of batched statistic values along this zeroth axis to form the + # null distribution. + data_batch = xp.moveaxis(data_batch, -2, 0) + # data_batch = np.split(data_batch, n_obs_ic[:-1], axis=-1) + data_batch = [data_batch[..., i:j] for i, j in zip(n_obs_ic[:-1], n_obs_ic[1:])] + null_distribution.append(statistic(*data_batch, axis=-1)) + null_distribution = xp.concat(null_distribution, axis=0) + + return null_distribution, n_permutations, exact_test + + +def _calculate_null_pairings(data, statistic, n_permutations, batch, + rng=None, *, xp): + """ + Calculate null distribution for association tests. + """ + n_samples = len(data) + + # compute number of permutations (factorial(n) permutations of each sample) + n_obs_sample = data[0].shape[-1] # observations per sample; same for each + n_max = math.factorial(n_obs_sample)**n_samples + + # `perm_generator` is an iterator that produces a list of permutations of + # indices from 0 to n_obs_sample, one for each sample. + if n_permutations >= n_max: + exact_test = True + n_permutations = n_max + batch = batch or int(n_permutations) + # Cartesian product of the sets of all permutations of indices + perm_generator = product(*(permutations(range(n_obs_sample)) + for i in range(n_samples))) + batched_perm_generator = _batch_generator(perm_generator, batch=batch) + else: + exact_test = False + batch = batch or int(n_permutations) + # Separate random permutations of indices for each sample. + # Again, it would be nice if RandomState/Generator.permutation + # could permute each axis-slice separately. + args = n_permutations, n_samples, n_obs_sample, batch, rng + batched_perm_generator = _pairings_permutations_gen(*args) + + null_distribution = [] + + for indices in batched_perm_generator: + indices = xp.asarray(indices) + + # `indices` is 3D: the zeroth axis is for permutations, the next is + # for samples, and the last is for observations. Swap the first two + # to make the zeroth axis correspond with samples, as it does for + # `data`. + indices = xp_swapaxes(indices, 0, 1, xp=xp) + + # When we're done, `data_batch` will be a list of length `n_samples`. + # Each element will be a batch of random permutations of one sample. + # The zeroth axis of each batch will correspond with permutations, + # and the last will correspond with observations. (This makes it + # easy to pass into `statistic`.) + data_batch = [None]*n_samples + for i in range(n_samples): + # data_batch[i] = data[i][..., indices[i]] + data_batch[i] = _get_from_last_axis(data[i], indices[i, ...], xp=xp) + data_batch[i] = xp.moveaxis(data_batch[i], -2, 0) + + null_distribution.append(statistic(*data_batch, axis=-1)) + null_distribution = xp.concat(null_distribution, axis=0) + + return null_distribution, n_permutations, exact_test + + +def _calculate_null_samples(data, statistic, n_permutations, batch, + rng=None, *, xp): + """ + Calculate null distribution for paired-sample tests. + """ + n_samples = len(data) + + # By convention, the meaning of the "samples" permutations type for + # data with only one sample is to flip the sign of the observations. + # Achieve this by adding a second sample - the negative of the original. + if n_samples == 1: + data = [data[0], -data[0]] + + # The "samples" permutation strategy is the same as the "pairings" + # strategy except the roles of samples and observations are flipped. + # So swap these axes, then we'll use the function for the "pairings" + # strategy to do all the work! + data = xp.stack(data, axis=0) + data = xp_swapaxes(data, 0, -1, xp=xp) + + # (Of course, the user's statistic doesn't know what we've done here, + # so we need to pass it what it's expecting.) + def statistic_wrapped(*data, axis): + # can we do this without converting back and forth between + # array and list? + data = xp.stack(data, axis=0) + data = xp_swapaxes(data, 0, -1, xp=xp) + if n_samples == 1: + data = data[0:1, ...] + data = [data[i, ...] for i in range(data.shape[0])] + return statistic(*data, axis=axis) + + data = [data[i, ...] for i in range(data.shape[0])] + return _calculate_null_pairings(data, statistic_wrapped, n_permutations, + batch, rng, xp=xp) + + +def _permutation_test_iv(data, statistic, permutation_type, vectorized, + n_resamples, batch, alternative, axis, rng): + """Input validation for `permutation_test`.""" + axis_int = int(axis) + if axis != axis_int: + raise ValueError("`axis` must be an integer.") + + permutation_types = {'samples', 'pairings', 'independent'} + permutation_type = permutation_type.lower() + if permutation_type not in permutation_types: + raise ValueError(f"`permutation_type` must be in {permutation_types}.") + + if vectorized not in {True, False, None}: + raise ValueError("`vectorized` must be `True`, `False`, or `None`.") + + if vectorized is None: + vectorized = 'axis' in inspect.signature(statistic).parameters + + message = "`data` must be a tuple containing at least two samples" + try: + if len(data) < 2 and permutation_type == 'independent': + raise ValueError(message) + except TypeError: + raise TypeError(message) + + xp = array_namespace(*data) + + if not vectorized: + if not is_numpy(xp): + message = (f"When using array library {xp.__name__}, `func` must be " + "vectorized and accept argument `axis`.") + raise TypeError(message) + + statistic = _vectorize_statistic(statistic) + + data = _broadcast_arrays(data, axis, xp=xp) + data_iv = [] + for sample in data: + sample = xpx.atleast_nd(sample, ndim=1) + if sample.shape[axis] <= 1: + raise ValueError("each sample in `data` must contain two or more " + "observations along `axis`.") + sample = xp.moveaxis(sample, axis_int, -1) + data_iv.append(sample) + + n_resamples_int = (int(n_resamples) if not math.isinf(n_resamples) + else xp.inf) + if n_resamples != n_resamples_int or n_resamples_int <= 0: + raise ValueError("`n_resamples` must be a positive integer.") + + if batch is None: + batch_iv = batch + else: + batch_iv = int(batch) + if batch != batch_iv or batch_iv <= 0: + raise ValueError("`batch` must be a positive integer or None.") + + alternatives = {'two-sided', 'greater', 'less'} + alternative = alternative.lower() + if alternative not in alternatives: + raise ValueError(f"`alternative` must be in {alternatives}") + + rng = check_random_state(rng) + + float_dtype = xp_result_type(*data_iv, force_floating=True, xp=xp) + + return (data_iv, statistic, permutation_type, vectorized, n_resamples_int, + batch_iv, alternative, axis_int, rng, float_dtype, xp) + + +@xp_capabilities(skip_backends=[('dask.array', "lacks required indexing capabilities")]) +@_transition_to_rng('random_state') +def permutation_test(data, statistic, *, permutation_type='independent', + vectorized=None, n_resamples=9999, batch=None, + alternative="two-sided", axis=0, rng=None): + r""" + Performs a permutation test of a given statistic on provided data. + + For independent sample statistics, the null hypothesis is that the data are + randomly sampled from the same distribution. + For paired sample statistics, two null hypothesis can be tested: + that the data are paired at random or that the data are assigned to samples + at random. + + Parameters + ---------- + data : iterable of array-like + Contains the samples, each of which is an array of observations. + Dimensions of sample arrays must be compatible for broadcasting except + along `axis`. + statistic : callable + Statistic for which the p-value of the hypothesis test is to be + calculated. `statistic` must be a callable that accepts samples + as separate arguments (e.g. ``statistic(*data)``) and returns the + resulting statistic. + If `vectorized` is set ``True``, `statistic` must also accept a keyword + argument `axis` and be vectorized to compute the statistic along the + provided `axis` of the sample arrays. + permutation_type : {'independent', 'samples', 'pairings'}, optional + The type of permutations to be performed, in accordance with the + null hypothesis. The first two permutation types are for paired sample + statistics, in which all samples contain the same number of + observations and observations with corresponding indices along `axis` + are considered to be paired; the third is for independent sample + statistics. + + - ``'samples'`` : observations are assigned to different samples + but remain paired with the same observations from other samples. + This permutation type is appropriate for paired sample hypothesis + tests such as the Wilcoxon signed-rank test and the paired t-test. + - ``'pairings'`` : observations are paired with different observations, + but they remain within the same sample. This permutation type is + appropriate for association/correlation tests with statistics such + as Spearman's :math:`\rho`, Kendall's :math:`\tau`, and Pearson's + :math:`r`. + - ``'independent'`` (default) : observations are assigned to different + samples. Samples may contain different numbers of observations. This + permutation type is appropriate for independent sample hypothesis + tests such as the Mann-Whitney :math:`U` test and the independent + sample t-test. + + Please see the Notes section below for more detailed descriptions + of the permutation types. + + vectorized : bool, optional + If `vectorized` is set ``False``, `statistic` will not be passed + keyword argument `axis` and is expected to calculate the statistic + only for 1D samples. If ``True``, `statistic` will be passed keyword + argument `axis` and is expected to calculate the statistic along `axis` + when passed an ND sample array. If ``None`` (default), `vectorized` + will be set ``True`` if ``axis`` is a parameter of `statistic`. Use + of a vectorized statistic typically reduces computation time. + n_resamples : int or np.inf, default: 9999 + Number of random permutations (resamples) used to approximate the null + distribution. If greater than or equal to the number of distinct + permutations, the exact null distribution will be computed. + Note that the number of distinct permutations grows very rapidly with + the sizes of samples, so exact tests are feasible only for very small + data sets. + batch : int, optional + The number of permutations to process in each call to `statistic`. + Memory usage is O( `batch` * ``n`` ), where ``n`` is the total size + of all samples, regardless of the value of `vectorized`. Default is + ``None``, in which case ``batch`` is the number of permutations. + alternative : {'two-sided', 'less', 'greater'}, optional + The alternative hypothesis for which the p-value is calculated. + For each alternative, the p-value is defined for exact tests as + follows. + + - ``'greater'`` : the percentage of the null distribution that is + greater than or equal to the observed value of the test statistic. + - ``'less'`` : the percentage of the null distribution that is + less than or equal to the observed value of the test statistic. + - ``'two-sided'`` (default) : twice the smaller of the p-values above. + + Note that p-values for randomized tests are calculated according to the + conservative (over-estimated) approximation suggested in [2]_ and [3]_ + rather than the unbiased estimator suggested in [4]_. That is, when + calculating the proportion of the randomized null distribution that is + as extreme as the observed value of the test statistic, the values in + the numerator and denominator are both increased by one. An + interpretation of this adjustment is that the observed value of the + test statistic is always included as an element of the randomized + null distribution. + The convention used for two-sided p-values is not universal; + the observed test statistic and null distribution are returned in + case a different definition is preferred. + + axis : int, default: 0 + The axis of the (broadcasted) samples over which to calculate the + statistic. If samples have a different number of dimensions, + singleton dimensions are prepended to samples with fewer dimensions + before `axis` is considered. + rng : `numpy.random.Generator`, optional + Pseudorandom number generator state. When `rng` is None, a new + `numpy.random.Generator` is created using entropy from the + operating system. Types other than `numpy.random.Generator` are + passed to `numpy.random.default_rng` to instantiate a ``Generator``. + + Returns + ------- + res : PermutationTestResult + An object with attributes: + + statistic : float or ndarray + The observed test statistic of the data. + pvalue : float or ndarray + The p-value for the given alternative. + null_distribution : ndarray + The values of the test statistic generated under the null + hypothesis. + + Notes + ----- + + The three types of permutation tests supported by this function are + described below. + + **Unpaired statistics** (``permutation_type='independent'``): + + The null hypothesis associated with this permutation type is that all + observations are sampled from the same underlying distribution and that + they have been assigned to one of the samples at random. + + Suppose ``data`` contains two samples; e.g. ``a, b = data``. + When ``1 < n_resamples < binom(n, k)``, where + + * ``k`` is the number of observations in ``a``, + * ``n`` is the total number of observations in ``a`` and ``b``, and + * ``binom(n, k)`` is the binomial coefficient (``n`` choose ``k``), + + the data are pooled (concatenated), randomly assigned to either the first + or second sample, and the statistic is calculated. This process is + performed repeatedly, `permutation` times, generating a distribution of the + statistic under the null hypothesis. The statistic of the original + data is compared to this distribution to determine the p-value. + + When ``n_resamples >= binom(n, k)``, an exact test is performed: the data + are *partitioned* between the samples in each distinct way exactly once, + and the exact null distribution is formed. + Note that for a given partitioning of the data between the samples, + only one ordering/permutation of the data *within* each sample is + considered. For statistics that do not depend on the order of the data + within samples, this dramatically reduces computational cost without + affecting the shape of the null distribution (because the frequency/count + of each value is affected by the same factor). + + For ``a = [a1, a2, a3, a4]`` and ``b = [b1, b2, b3]``, an example of this + permutation type is ``x = [b3, a1, a2, b2]`` and ``y = [a4, b1, a3]``. + Because only one ordering/permutation of the data *within* each sample + is considered in an exact test, a resampling like ``x = [b3, a1, b2, a2]`` + and ``y = [a4, a3, b1]`` would *not* be considered distinct from the + example above. + + ``permutation_type='independent'`` does not support one-sample statistics, + but it can be applied to statistics with more than two samples. In this + case, if ``n`` is an array of the number of observations within each + sample, the number of distinct partitions is:: + + np.prod([binom(sum(n[i:]), sum(n[i+1:])) for i in range(len(n)-1)]) + + **Paired statistics, permute pairings** (``permutation_type='pairings'``): + + The null hypothesis associated with this permutation type is that + observations within each sample are drawn from the same underlying + distribution and that pairings with elements of other samples are + assigned at random. + + Suppose ``data`` contains only one sample; e.g. ``a, = data``, and we + wish to consider all possible pairings of elements of ``a`` with elements + of a second sample, ``b``. Let ``n`` be the number of observations in + ``a``, which must also equal the number of observations in ``b``. + + When ``1 < n_resamples < factorial(n)``, the elements of ``a`` are + randomly permuted. The user-supplied statistic accepts one data argument, + say ``a_perm``, and calculates the statistic considering ``a_perm`` and + ``b``. This process is performed repeatedly, `permutation` times, + generating a distribution of the statistic under the null hypothesis. + The statistic of the original data is compared to this distribution to + determine the p-value. + + When ``n_resamples >= factorial(n)``, an exact test is performed: + ``a`` is permuted in each distinct way exactly once. Therefore, the + `statistic` is computed for each unique pairing of samples between ``a`` + and ``b`` exactly once. + + For ``a = [a1, a2, a3]`` and ``b = [b1, b2, b3]``, an example of this + permutation type is ``a_perm = [a3, a1, a2]`` while ``b`` is left + in its original order. + + ``permutation_type='pairings'`` supports ``data`` containing any number + of samples, each of which must contain the same number of observations. + All samples provided in ``data`` are permuted *independently*. Therefore, + if ``m`` is the number of samples and ``n`` is the number of observations + within each sample, then the number of permutations in an exact test is:: + + factorial(n)**m + + Note that if a two-sample statistic, for example, does not inherently + depend on the order in which observations are provided - only on the + *pairings* of observations - then only one of the two samples should be + provided in ``data``. This dramatically reduces computational cost without + affecting the shape of the null distribution (because the frequency/count + of each value is affected by the same factor). + + **Paired statistics, permute samples** (``permutation_type='samples'``): + + The null hypothesis associated with this permutation type is that + observations within each pair are drawn from the same underlying + distribution and that the sample to which they are assigned is random. + + Suppose ``data`` contains two samples; e.g. ``a, b = data``. + Let ``n`` be the number of observations in ``a``, which must also equal + the number of observations in ``b``. + + When ``1 < n_resamples < 2**n``, the elements of ``a`` are ``b`` are + randomly swapped between samples (maintaining their pairings) and the + statistic is calculated. This process is performed repeatedly, + `permutation` times, generating a distribution of the statistic under the + null hypothesis. The statistic of the original data is compared to this + distribution to determine the p-value. + + When ``n_resamples >= 2**n``, an exact test is performed: the observations + are assigned to the two samples in each distinct way (while maintaining + pairings) exactly once. + + For ``a = [a1, a2, a3]`` and ``b = [b1, b2, b3]``, an example of this + permutation type is ``x = [b1, a2, b3]`` and ``y = [a1, b2, a3]``. + + ``permutation_type='samples'`` supports ``data`` containing any number + of samples, each of which must contain the same number of observations. + If ``data`` contains more than one sample, paired observations within + ``data`` are exchanged between samples *independently*. Therefore, if ``m`` + is the number of samples and ``n`` is the number of observations within + each sample, then the number of permutations in an exact test is:: + + factorial(m)**n + + Several paired-sample statistical tests, such as the Wilcoxon signed rank + test and paired-sample t-test, can be performed considering only the + *difference* between two paired elements. Accordingly, if ``data`` contains + only one sample, then the null distribution is formed by independently + changing the *sign* of each observation. + + .. warning:: + The p-value is calculated by counting the elements of the null + distribution that are as extreme or more extreme than the observed + value of the statistic. Due to the use of finite precision arithmetic, + some statistic functions return numerically distinct values when the + theoretical values would be exactly equal. In some cases, this could + lead to a large error in the calculated p-value. `permutation_test` + guards against this by considering elements in the null distribution + that are "close" (within a relative tolerance of 100 times the + floating point epsilon of inexact dtypes) to the observed + value of the test statistic as equal to the observed value of the + test statistic. However, the user is advised to inspect the null + distribution to assess whether this method of comparison is + appropriate, and if not, calculate the p-value manually. See example + below. + + References + ---------- + + .. [1] R. A. Fisher. The Design of Experiments, 6th Ed (1951). + .. [2] B. Phipson and G. K. Smyth. "Permutation P-values Should Never Be + Zero: Calculating Exact P-values When Permutations Are Randomly Drawn." + Statistical Applications in Genetics and Molecular Biology 9.1 (2010). + .. [3] M. D. Ernst. "Permutation Methods: A Basis for Exact Inference". + Statistical Science (2004). + .. [4] B. Efron and R. J. Tibshirani. An Introduction to the Bootstrap + (1993). + + Examples + -------- + + Suppose we wish to test whether two samples are drawn from the same + distribution. Assume that the underlying distributions are unknown to us, + and that before observing the data, we hypothesized that the mean of the + first sample would be less than that of the second sample. We decide that + we will use the difference between the sample means as a test statistic, + and we will consider a p-value of 0.05 to be statistically significant. + + For efficiency, we write the function defining the test statistic in a + vectorized fashion: the samples ``x`` and ``y`` can be ND arrays, and the + statistic will be calculated for each axis-slice along `axis`. + + >>> import numpy as np + >>> def statistic(x, y, axis): + ... return np.mean(x, axis=axis) - np.mean(y, axis=axis) + + After collecting our data, we calculate the observed value of the test + statistic. + + >>> from scipy.stats import norm + >>> rng = np.random.default_rng() + >>> x = norm.rvs(size=5, random_state=rng) + >>> y = norm.rvs(size=6, loc = 3, random_state=rng) + >>> statistic(x, y, 0) + -3.5411688580987266 + + Indeed, the test statistic is negative, suggesting that the true mean of + the distribution underlying ``x`` is less than that of the distribution + underlying ``y``. To determine the probability of this occurring by chance + if the two samples were drawn from the same distribution, we perform + a permutation test. + + >>> from scipy.stats import permutation_test + >>> # because our statistic is vectorized, we pass `vectorized=True` + >>> # `n_resamples=np.inf` indicates that an exact test is to be performed + >>> res = permutation_test((x, y), statistic, vectorized=True, + ... n_resamples=np.inf, alternative='less') + >>> print(res.statistic) + -3.5411688580987266 + >>> print(res.pvalue) + 0.004329004329004329 + + The probability of obtaining a test statistic less than or equal to the + observed value under the null hypothesis is 0.4329%. This is less than our + chosen threshold of 5%, so we consider this to be significant evidence + against the null hypothesis in favor of the alternative. + + Because the size of the samples above was small, `permutation_test` could + perform an exact test. For larger samples, we resort to a randomized + permutation test. + + >>> x = norm.rvs(size=100, random_state=rng) + >>> y = norm.rvs(size=120, loc=0.2, random_state=rng) + >>> res = permutation_test((x, y), statistic, n_resamples=9999, + ... vectorized=True, alternative='less', + ... rng=rng) + >>> print(res.statistic) + -0.4230459671240913 + >>> print(res.pvalue) + 0.0015 + + The approximate probability of obtaining a test statistic less than or + equal to the observed value under the null hypothesis is 0.0225%. This is + again less than our chosen threshold of 5%, so again we have significant + evidence to reject the null hypothesis in favor of the alternative. + + For large samples and number of permutations, the result is comparable to + that of the corresponding asymptotic test, the independent sample t-test. + + >>> from scipy.stats import ttest_ind + >>> res_asymptotic = ttest_ind(x, y, alternative='less') + >>> print(res_asymptotic.pvalue) + 0.0014669545224902675 + + The permutation distribution of the test statistic is provided for + further investigation. + + >>> import matplotlib.pyplot as plt + >>> plt.hist(res.null_distribution, bins=50) + >>> plt.title("Permutation distribution of test statistic") + >>> plt.xlabel("Value of Statistic") + >>> plt.ylabel("Frequency") + >>> plt.show() + + Inspection of the null distribution is essential if the statistic suffers + from inaccuracy due to limited machine precision. Consider the following + case: + + >>> from scipy.stats import pearsonr + >>> x = [1, 2, 4, 3] + >>> y = [2, 4, 6, 8] + >>> def statistic(x, y, axis=-1): + ... return pearsonr(x, y, axis=axis).statistic + >>> res = permutation_test((x, y), statistic, vectorized=True, + ... permutation_type='pairings', + ... alternative='greater') + >>> r, pvalue, null = res.statistic, res.pvalue, res.null_distribution + + In this case, some elements of the null distribution differ from the + observed value of the correlation coefficient ``r`` due to numerical noise. + We manually inspect the elements of the null distribution that are nearly + the same as the observed value of the test statistic. + + >>> r + 0.7999999999999999 + >>> unique = np.unique(null) + >>> unique + array([-1. , -1. , -0.8, -0.8, -0.8, -0.6, -0.4, -0.4, -0.2, -0.2, -0.2, + 0. , 0.2, 0.2, 0.2, 0.4, 0.4, 0.6, 0.8, 0.8, 0.8, 1. , + 1. ]) # may vary + >>> unique[np.isclose(r, unique)].tolist() + [0.7999999999999998, 0.7999999999999999, 0.8] # may vary + + If `permutation_test` were to perform the comparison naively, the + elements of the null distribution with value ``0.7999999999999998`` would + not be considered as extreme or more extreme as the observed value of the + statistic, so the calculated p-value would be too small. + + >>> incorrect_pvalue = np.count_nonzero(null >= r) / len(null) + >>> incorrect_pvalue + 0.14583333333333334 # may vary + + Instead, `permutation_test` treats elements of the null distribution that + are within ``max(1e-14, abs(r)*1e-14)`` of the observed value of the + statistic ``r`` to be equal to ``r``. + + >>> correct_pvalue = np.count_nonzero(null >= r - 1e-14) / len(null) + >>> correct_pvalue + 0.16666666666666666 + >>> res.pvalue == correct_pvalue + True + + This method of comparison is expected to be accurate in most practical + situations, but the user is advised to assess this by inspecting the + elements of the null distribution that are close to the observed value + of the statistic. Also, consider the use of statistics that can be + calculated using exact arithmetic (e.g. integer statistics). + + """ + args = _permutation_test_iv(data, statistic, permutation_type, vectorized, + n_resamples, batch, alternative, axis, + rng) + (data, statistic, permutation_type, vectorized, n_resamples, batch, + alternative, axis, rng, float_dtype, xp) = args + + observed = statistic(*data, axis=-1) + + null_calculators = {"pairings": _calculate_null_pairings, + "samples": _calculate_null_samples, + "independent": _calculate_null_both} + null_calculator_args = (data, statistic, n_resamples, + batch, rng) + calculate_null = null_calculators[permutation_type] + null_distribution, n_resamples, exact_test = ( + calculate_null(*null_calculator_args, xp=xp)) + + # See References [2] and [3] + adjustment = 0 if exact_test else 1 + + # relative tolerance for detecting numerically distinct but + # theoretically equal values in the null distribution + eps = (0 if not xp.isdtype(observed.dtype, 'real floating') + else xp.finfo(observed.dtype).eps*100) + gamma = xp.abs(eps * observed) + + def less(null_distribution, observed): + cmps = null_distribution <= observed + gamma + count = xp.count_nonzero(cmps, axis=0) + adjustment + pvalues = xp.astype(count, float_dtype) / (n_resamples + adjustment) + return pvalues + + def greater(null_distribution, observed): + cmps = null_distribution >= observed - gamma + count = xp.count_nonzero(cmps, axis=0) + adjustment + pvalues = xp.astype(count, float_dtype) / (n_resamples + adjustment) + return pvalues + + def two_sided(null_distribution, observed): + pvalues_less = less(null_distribution, observed) + pvalues_greater = greater(null_distribution, observed) + pvalues = xp.minimum(pvalues_less, pvalues_greater) * 2 + return pvalues + + compare = {"less": less, + "greater": greater, + "two-sided": two_sided} + + pvalues = compare[alternative](null_distribution, observed) + pvalues = xpx.at(pvalues)[xp.isnan(observed)].set(xp.nan) + pvalues = xp.clip(pvalues, 0., 1.) + + return PermutationTestResult(observed, pvalues, null_distribution) + + +@dataclass +class ResamplingMethod: + """Configuration information for a statistical resampling method. + + Instances of this class can be passed into the `method` parameter of some + hypothesis test functions to perform a resampling or Monte Carlo version + of the hypothesis test. + + Attributes + ---------- + n_resamples : int + The number of resamples to perform or Monte Carlo samples to draw. + batch : int, optional + The number of resamples to process in each vectorized call to + the statistic. Batch sizes >>1 tend to be faster when the statistic + is vectorized, but memory usage scales linearly with the batch size. + Default is ``None``, which processes all resamples in a single batch. + + """ + n_resamples: int = 9999 + batch: int = None # type: ignore[assignment] + + +@dataclass +class MonteCarloMethod(ResamplingMethod): + """Configuration information for a Monte Carlo hypothesis test. + + Instances of this class can be passed into the `method` parameter of some + hypothesis test functions to perform a Monte Carlo version of the + hypothesis tests. + + Parameters + ---------- + n_resamples : int, optional + The number of Monte Carlo samples to draw. Default is 9999. + batch : int, optional + The number of Monte Carlo samples to process in each vectorized call to + the statistic. Batch sizes >>1 tend to be faster when the statistic + is vectorized, but memory usage scales linearly with the batch size. + Default is ``None``, which processes all samples in a single batch. + rvs : callable or tuple of callables, optional + A callable or sequence of callables that generates random variates + under the null hypothesis. Each element of `rvs` must be a callable + that accepts keyword argument ``size`` (e.g. ``rvs(size=(m, n))``) and + returns an N-d array sample of that shape. If `rvs` is a sequence, the + number of callables in `rvs` must match the number of samples passed + to the hypothesis test in which the `MonteCarloMethod` is used. Default + is ``None``, in which case the hypothesis test function chooses values + to match the standard version of the hypothesis test. For example, + the null hypothesis of `scipy.stats.pearsonr` is typically that the + samples are drawn from the standard normal distribution, so + ``rvs = (rng.normal, rng.normal)`` where + ``rng = np.random.default_rng()``. + rng : `numpy.random.Generator`, optional + Pseudorandom number generator state. When `rng` is None, a new + `numpy.random.Generator` is created using entropy from the + operating system. Types other than `numpy.random.Generator` are + passed to `numpy.random.default_rng` to instantiate a ``Generator``. + + """ + rvs: object = None + rng: object = None + + def __init__(self, n_resamples=9999, batch=None, rvs=None, rng=None): + if (rvs is not None) and (rng is not None): + message = 'Use of `rvs` and `rng` are mutually exclusive.' + raise ValueError(message) + + self.n_resamples = n_resamples + self.batch = batch + self.rvs = rvs + self.rng = rng + + def _asdict(self): + # `dataclasses.asdict` deepcopies; we don't want that. + return dict(n_resamples=self.n_resamples, batch=self.batch, + rvs=self.rvs, rng=self.rng) + + +_rs_deprecation = ("Use of attribute `random_state` is deprecated and replaced by " + "`rng`. Support for `random_state` will be removed in SciPy 1.19.0. " + "To silence this warning and ensure consistent behavior in SciPy " + "1.19.0, control the RNG using attribute `rng`. Values set using " + "attribute `rng` will be validated by `np.random.default_rng`, so " + "the behavior corresponding with a given value may change compared " + "to use of `random_state`. For example, 1) `None` will result in " + "unpredictable random numbers, 2) an integer will result in a " + "different stream of random numbers, (with the same distribution), " + "and 3) `np.random` or `RandomState` instances will result in an " + "error. See the documentation of `default_rng` for more " + "information.") + + +@dataclass +class PermutationMethod(ResamplingMethod): + """Configuration information for a permutation hypothesis test. + + Instances of this class can be passed into the `method` parameter of some + hypothesis test functions to perform a permutation version of the + hypothesis tests. + + Parameters + ---------- + n_resamples : int, optional + The number of resamples to perform. Default is 9999. + batch : int, optional + The number of resamples to process in each vectorized call to + the statistic. Batch sizes >>1 tend to be faster when the statistic + is vectorized, but memory usage scales linearly with the batch size. + Default is ``None``, which processes all resamples in a single batch. + rng : `numpy.random.Generator`, optional + Pseudorandom number generator used to perform resampling. + + If `rng` is passed by keyword to the initializer or the `rng` attribute is used + directly, types other than `numpy.random.Generator` are passed to + `numpy.random.default_rng` to instantiate a ``Generator`` before use. + If `rng` is already a ``Generator`` instance, then the provided instance is + used. Specify `rng` for repeatable behavior. + + If this argument is passed by position, if `random_state` is passed by keyword + into the initializer, or if the `random_state` attribute is used directly, + legacy behavior for `random_state` applies: + + - If `random_state` is None (or `numpy.random`), the `numpy.random.RandomState` + singleton is used. + - If `random_state` is an int, a new ``RandomState`` instance is used, + seeded with `random_state`. + - If `random_state` is already a ``Generator`` or ``RandomState`` instance then + that instance is used. + + .. versionchanged:: 1.15.0 + + As part of the `SPEC-007 `_ + transition from use of `numpy.random.RandomState` to + `numpy.random.Generator`, this attribute name was changed from + `random_state` to `rng`. For an interim period, both names will continue to + work, although only one may be specified at a time. After the interim + period, uses of `random_state` will emit warnings. The behavior of both + `random_state` and `rng` are outlined above, but only `rng` should be used + in new code. + + """ + rng: object # type: ignore[misc] + _rng: object = field(init=False, repr=False, default=None) # type: ignore[assignment] + + @property + def random_state(self): + # Uncomment in SciPy 1.17.0 + # warnings.warn(_rs_deprecation, DeprecationWarning, stacklevel=2) + return self._random_state + + @random_state.setter + def random_state(self, val): + # Uncomment in SciPy 1.17.0 + # warnings.warn(_rs_deprecation, DeprecationWarning, stacklevel=2) + self._random_state = val + + @property # type: ignore[no-redef] + def rng(self): # noqa: F811 + return self._rng + + def __init__(self, n_resamples=9999, batch=None, random_state=None, *, rng=None): + # Uncomment in SciPy 1.17.0 + # warnings.warn(_rs_deprecation.replace('attribute', 'argument'), + # DeprecationWarning, stacklevel=2) + self._rng = rng + self._random_state = random_state + super().__init__(n_resamples=n_resamples, batch=batch) + + def _asdict(self): + # `dataclasses.asdict` deepcopies; we don't want that. + d = dict(n_resamples=self.n_resamples, batch=self.batch) + if self.rng is not None: + d['rng'] = self.rng + if self.random_state is not None: + d['random_state'] = self.random_state + return d + + +@dataclass +class BootstrapMethod(ResamplingMethod): + """Configuration information for a bootstrap confidence interval. + + Instances of this class can be passed into the `method` parameter of some + confidence interval methods to generate a bootstrap confidence interval. + + Parameters + ---------- + n_resamples : int, optional + The number of resamples to perform. Default is 9999. + batch : int, optional + The number of resamples to process in each vectorized call to + the statistic. Batch sizes >>1 tend to be faster when the statistic + is vectorized, but memory usage scales linearly with the batch size. + Default is ``None``, which processes all resamples in a single batch. + rng : `numpy.random.Generator`, optional + Pseudorandom number generator used to perform resampling. + + If `rng` is passed by keyword to the initializer or the `rng` attribute is used + directly, types other than `numpy.random.Generator` are passed to + `numpy.random.default_rng` to instantiate a ``Generator`` before use. + If `rng` is already a ``Generator`` instance, then the provided instance is + used. Specify `rng` for repeatable behavior. + + If this argument is passed by position, if `random_state` is passed by keyword + into the initializer, or if the `random_state` attribute is used directly, + legacy behavior for `random_state` applies: + + - If `random_state` is None (or `numpy.random`), the `numpy.random.RandomState` + singleton is used. + - If `random_state` is an int, a new ``RandomState`` instance is used, + seeded with `random_state`. + - If `random_state` is already a ``Generator`` or ``RandomState`` instance then + that instance is used. + + .. versionchanged:: 1.15.0 + + As part of the `SPEC-007 `_ + transition from use of `numpy.random.RandomState` to + `numpy.random.Generator`, this attribute name was changed from + `random_state` to `rng`. For an interim period, both names will continue to + work, although only one may be specified at a time. After the interim + period, uses of `random_state` will emit warnings. The behavior of both + `random_state` and `rng` are outlined above, but only `rng` should be used + in new code. + + method : {'BCa', 'percentile', 'basic'} + Whether to use the 'percentile' bootstrap ('percentile'), the 'basic' + (AKA 'reverse') bootstrap ('basic'), or the bias-corrected and + accelerated bootstrap ('BCa', default). + + """ + rng: object # type: ignore[misc] + _rng: object = field(init=False, repr=False, default=None) # type: ignore[assignment] + method: str = 'BCa' + + @property + def random_state(self): + # Uncomment in SciPy 1.17.0 + # warnings.warn(_rs_deprecation, DeprecationWarning, stacklevel=2) + return self._random_state + + @random_state.setter + def random_state(self, val): + # Uncomment in SciPy 1.17.0 + # warnings.warn(_rs_deprecation, DeprecationWarning, stacklevel=2) + self._random_state = val + + @property # type: ignore[no-redef] + def rng(self): # noqa: F811 + return self._rng + + def __init__(self, n_resamples=9999, batch=None, random_state=None, + method='BCa', *, rng=None): + # Uncomment in SciPy 1.17.0 + # warnings.warn(_rs_deprecation.replace('attribute', 'argument'), + # DeprecationWarning, stacklevel=2) + self._rng = rng # don't validate with `default_rng` + self._random_state = random_state + self.method = method + super().__init__(n_resamples=n_resamples, batch=batch) + + def _asdict(self): + # `dataclasses.asdict` deepcopies; we don't want that. + d = dict(n_resamples=self.n_resamples, batch=self.batch, + method=self.method) + if self.rng is not None: + d['rng'] = self.rng + if self.random_state is not None: + d['random_state'] = self.random_state + return d diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_result_classes.py b/venv/lib/python3.12/site-packages/scipy/stats/_result_classes.py new file mode 100644 index 0000000000000000000000000000000000000000..975af9310efb0c9a414439fd8d531fb95c988951 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_result_classes.py @@ -0,0 +1,40 @@ +# This module exists only to allow Sphinx to generate docs +# for the result objects returned by some functions in stats +# _without_ adding them to the main stats documentation page. + +""" +Result classes +-------------- + +.. currentmodule:: scipy.stats._result_classes + +.. autosummary:: + :toctree: generated/ + + RelativeRiskResult + BinomTestResult + TukeyHSDResult + DunnettResult + PearsonRResult + FitResult + OddsRatioResult + TtestResult + ECDFResult + EmpiricalDistributionFunction + +""" + +__all__ = ['BinomTestResult', 'RelativeRiskResult', 'TukeyHSDResult', + 'PearsonRResult', 'FitResult', 'OddsRatioResult', + 'TtestResult', 'DunnettResult', 'ECDFResult', + 'EmpiricalDistributionFunction'] + + +from ._binomtest import BinomTestResult +from ._odds_ratio import OddsRatioResult +from ._relative_risk import RelativeRiskResult +from ._hypotests import TukeyHSDResult +from ._multicomp import DunnettResult +from ._stats_py import PearsonRResult, TtestResult +from ._fit import FitResult +from ._survival import ECDFResult, EmpiricalDistributionFunction diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_sampling.py b/venv/lib/python3.12/site-packages/scipy/stats/_sampling.py new file mode 100644 index 0000000000000000000000000000000000000000..3c0abd14bb9e7f14bb3ae98cb13bcb2b6d8144c7 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_sampling.py @@ -0,0 +1,1319 @@ +import math +import numbers +import numpy as np +from scipy import stats +from scipy import special as sc +from ._qmc import (check_random_state as check_random_state_qmc, + Halton, QMCEngine) +from ._unuran.unuran_wrapper import NumericalInversePolynomial +from scipy._lib._util import check_random_state + + +__all__ = ['FastGeneratorInversion', 'RatioUniforms'] + + +# define pdfs and other helper functions to create the generators + +def argus_pdf(x, chi): + # approach follows Baumgarten/Hoermann: Generating ARGUS random variates + # for chi > 5, use relationship of the ARGUS distribution to Gamma(1.5) + if chi <= 5: + y = 1 - x * x + return x * math.sqrt(y) * math.exp(-0.5 * chi**2 * y) + return math.sqrt(x) * math.exp(-x) + + +def argus_gamma_trf(x, chi): + if chi <= 5: + return x + return np.sqrt(1.0 - 2 * x / chi**2) + + +def argus_gamma_inv_trf(x, chi): + if chi <= 5: + return x + return 0.5 * chi**2 * (1 - x**2) + + +def betaprime_pdf(x, a, b): + if x > 0: + logf = (a - 1) * math.log(x) - (a + b) * math.log1p(x) - sc.betaln(a, b) + return math.exp(logf) + else: + # return pdf at x == 0 separately to avoid runtime warnings + if a > 1: + return 0 + elif a < 1: + return np.inf + else: + return 1 / sc.beta(a, b) + + +def beta_valid_params(a, b): + return (min(a, b) >= 0.1) and (max(a, b) <= 700) + + +def gamma_pdf(x, a): + if x > 0: + return math.exp(-math.lgamma(a) + (a - 1.0) * math.log(x) - x) + else: + return 0 if a >= 1 else np.inf + + +def invgamma_pdf(x, a): + if x > 0: + return math.exp(-(a + 1.0) * math.log(x) - math.lgamma(a) - 1 / x) + else: + return 0 if a >= 1 else np.inf + + +def burr_pdf(x, cc, dd): + # note: we use np.exp instead of math.exp, otherwise an overflow + # error can occur in the setup, e.g., for parameters + # 1.89128135, 0.30195177, see test test_burr_overflow + if x > 0: + lx = math.log(x) + return np.exp(-(cc + 1) * lx - (dd + 1) * math.log1p(np.exp(-cc * lx))) + else: + return 0 + + +def burr12_pdf(x, cc, dd): + if x > 0: + lx = math.log(x) + logterm = math.log1p(math.exp(cc * lx)) + return math.exp((cc - 1) * lx - (dd + 1) * logterm + math.log(cc * dd)) + else: + return 0 + + +def chi_pdf(x, a): + if x > 0: + return math.exp( + (a - 1) * math.log(x) + - 0.5 * (x * x) + - (a / 2 - 1) * math.log(2) + - math.lgamma(0.5 * a) + ) + else: + return 0 if a >= 1 else np.inf + + +def chi2_pdf(x, df): + if x > 0: + return math.exp( + (df / 2 - 1) * math.log(x) + - 0.5 * x + - (df / 2) * math.log(2) + - math.lgamma(0.5 * df) + ) + else: + return 0 if df >= 1 else np.inf + + +def alpha_pdf(x, a): + if x > 0: + return math.exp(-2.0 * math.log(x) - 0.5 * (a - 1.0 / x) ** 2) + return 0.0 + + +def bradford_pdf(x, c): + if 0 <= x <= 1: + return 1.0 / (1.0 + c * x) + return 0.0 + + +def crystalball_pdf(x, b, m): + if x > -b: + return math.exp(-0.5 * x * x) + return math.exp(m * math.log(m / b) - 0.5 * b * b - m * math.log(m / b - b - x)) + + +def weibull_min_pdf(x, c): + if x > 0: + return c * math.exp((c - 1) * math.log(x) - x**c) + return 0.0 + + +def weibull_max_pdf(x, c): + if x < 0: + return c * math.exp((c - 1) * math.log(-x) - ((-x) ** c)) + return 0.0 + + +def invweibull_pdf(x, c): + if x > 0: + return c * math.exp(-(c + 1) * math.log(x) - x ** (-c)) + return 0.0 + + +def wald_pdf(x): + if x > 0: + return math.exp(-((x - 1) ** 2) / (2 * x)) / math.sqrt(x**3) + return 0.0 + + +def geninvgauss_mode(p, b): + if p > 1: # equivalent mode formulas numerical more stable versions + return (math.sqrt((1 - p) ** 2 + b**2) - (1 - p)) / b + return b / (math.sqrt((1 - p) ** 2 + b**2) + (1 - p)) + + +def geninvgauss_pdf(x, p, b): + m = geninvgauss_mode(p, b) + lfm = (p - 1) * math.log(m) - 0.5 * b * (m + 1 / m) + if x > 0: + return math.exp((p - 1) * math.log(x) - 0.5 * b * (x + 1 / x) - lfm) + return 0.0 + + +def invgauss_mode(mu): + return 1.0 / (math.sqrt(1.5 * 1.5 + 1 / (mu * mu)) + 1.5) + + +def invgauss_pdf(x, mu): + m = invgauss_mode(mu) + lfm = -1.5 * math.log(m) - (m - mu) ** 2 / (2 * m * mu**2) + if x > 0: + return math.exp(-1.5 * math.log(x) - (x - mu) ** 2 / (2 * x * mu**2) - lfm) + return 0.0 + + +def powerlaw_pdf(x, a): + if x > 0: + return x ** (a - 1) + return 0.0 + + +# Define a dictionary: for a given distribution (keys), another dictionary +# (values) specifies the parameters for NumericalInversePolynomial (PINV). +# The keys of the latter dictionary are: +# - pdf: the pdf of the distribution (callable). The signature of the pdf +# is float -> float (i.e., the function does not have to be vectorized). +# If possible, functions like log or exp from the module math should be +# preferred over functions from numpy since the PINV setup will be faster +# in that case. +# - check_pinv_params: callable f that returns true if the shape parameters +# (args) are recommended parameters for PINV (i.e., the u-error does +# not exceed the default tolerance) +# - center: scalar if the center does not depend on args, otherwise +# callable that returns the center as a function of the shape parameters +# - rvs_transform: a callable that can be used to transform the rvs that +# are distributed according to the pdf to the target distribution +# (as an example, see the entry for the beta distribution) +# - rvs_transform_inv: the inverse of rvs_transform (it is required +# for the transformed ppf) +# - mirror_uniform: bool or a callable that returns true or false +# depending on the shape parameters. If True, the ppf is applied +# to 1-u instead of u to generate rvs, where u is a uniform rv. +# While both u and 1-u are uniform, it can be required to use 1-u +# to compute the u-error correctly. This is only relevant for the argus +# distribution. +# The only required keys are "pdf" and "check_pinv_params". +# All other keys are optional. + +PINV_CONFIG = { + "alpha": { + "pdf": alpha_pdf, + "check_pinv_params": lambda a: 1.0e-11 <= a < 2.1e5, + "center": lambda a: 0.25 * (math.sqrt(a * a + 8.0) - a), + }, + "anglit": { + "pdf": lambda x: math.cos(2 * x) + 1.0e-13, + # +1.e-13 is necessary, otherwise PINV has strange problems as + # f(upper border) is very close to 0 + "center": 0, + }, + "argus": { + "pdf": argus_pdf, + "center": lambda chi: 0.7 if chi <= 5 else 0.5, + "check_pinv_params": lambda chi: 1e-20 < chi < 901, + "rvs_transform": argus_gamma_trf, + "rvs_transform_inv": argus_gamma_inv_trf, + "mirror_uniform": lambda chi: chi > 5, + }, + "beta": { + "pdf": betaprime_pdf, + "center": lambda a, b: max(0.1, (a - 1) / (b + 1)), + "check_pinv_params": beta_valid_params, + "rvs_transform": lambda x, *args: x / (1 + x), + "rvs_transform_inv": lambda x, *args: x / (1 - x) if x < 1 else np.inf, + }, + "betaprime": { + "pdf": betaprime_pdf, + "center": lambda a, b: max(0.1, (a - 1) / (b + 1)), + "check_pinv_params": beta_valid_params, + }, + "bradford": { + "pdf": bradford_pdf, + "check_pinv_params": lambda a: 1.0e-6 <= a <= 1e9, + "center": 0.5, + }, + "burr": { + "pdf": burr_pdf, + "center": lambda a, b: (2 ** (1 / b) - 1) ** (-1 / a), + "check_pinv_params": lambda a, b: (min(a, b) >= 0.3) and (max(a, b) <= 50), + }, + "burr12": { + "pdf": burr12_pdf, + "center": lambda a, b: (2 ** (1 / b) - 1) ** (1 / a), + "check_pinv_params": lambda a, b: (min(a, b) >= 0.2) and (max(a, b) <= 50), + }, + "cauchy": { + "pdf": lambda x: 1 / (1 + (x * x)), + "center": 0, + }, + "chi": { + "pdf": chi_pdf, + "check_pinv_params": lambda df: 0.05 <= df <= 1.0e6, + "center": lambda a: math.sqrt(a), + }, + "chi2": { + "pdf": chi2_pdf, + "check_pinv_params": lambda df: 0.07 <= df <= 1e6, + "center": lambda a: a, + }, + "cosine": { + "pdf": lambda x: 1 + math.cos(x), + "center": 0, + }, + "crystalball": { + "pdf": crystalball_pdf, + "check_pinv_params": lambda b, m: (0.01 <= b <= 5.5) + and (1.1 <= m <= 75.1), + "center": 0.0, + }, + "expon": { + "pdf": lambda x: math.exp(-x), + "center": 1.0, + }, + "gamma": { + "pdf": gamma_pdf, + "check_pinv_params": lambda a: 0.04 <= a <= 1e6, + "center": lambda a: a, + }, + "gennorm": { + "pdf": lambda x, b: math.exp(-abs(x) ** b), + "check_pinv_params": lambda b: 0.081 <= b <= 45.0, + "center": 0.0, + }, + "geninvgauss": { + "pdf": geninvgauss_pdf, + "check_pinv_params": lambda p, b: (abs(p) <= 1200.0) + and (1.0e-10 <= b <= 1200.0), + "center": geninvgauss_mode, + }, + "gumbel_l": { + "pdf": lambda x: math.exp(x - math.exp(x)), + "center": -0.6, + }, + "gumbel_r": { + "pdf": lambda x: math.exp(-x - math.exp(-x)), + "center": 0.6, + }, + "hypsecant": { + "pdf": lambda x: 1.0 / (math.exp(x) + math.exp(-x)), + "center": 0.0, + }, + "invgamma": { + "pdf": invgamma_pdf, + "check_pinv_params": lambda a: 0.04 <= a <= 1e6, + "center": lambda a: 1 / a, + }, + "invgauss": { + "pdf": invgauss_pdf, + "check_pinv_params": lambda mu: 1.0e-10 <= mu <= 1.0e9, + "center": invgauss_mode, + }, + "invweibull": { + "pdf": invweibull_pdf, + "check_pinv_params": lambda a: 0.12 <= a <= 512, + "center": 1.0, + }, + "laplace": { + "pdf": lambda x: math.exp(-abs(x)), + "center": 0.0, + }, + "logistic": { + "pdf": lambda x: math.exp(-x) / (1 + math.exp(-x)) ** 2, + "center": 0.0, + }, + "maxwell": { + "pdf": lambda x: x * x * math.exp(-0.5 * x * x), + "center": 1.41421, + }, + "moyal": { + "pdf": lambda x: math.exp(-(x + math.exp(-x)) / 2), + "center": 1.2, + }, + "norm": { + "pdf": lambda x: math.exp(-x * x / 2), + "center": 0.0, + }, + "pareto": { + "pdf": lambda x, b: x ** -(b + 1), + "center": lambda b: b / (b - 1) if b > 2 else 1.5, + "check_pinv_params": lambda b: 0.08 <= b <= 400000, + }, + "powerlaw": { + "pdf": powerlaw_pdf, + "center": 1.0, + "check_pinv_params": lambda a: 0.06 <= a <= 1.0e5, + }, + "t": { + "pdf": lambda x, df: (1 + x * x / df) ** (-0.5 * (df + 1)), + "check_pinv_params": lambda a: 0.07 <= a <= 1e6, + "center": 0.0, + }, + "rayleigh": { + "pdf": lambda x: x * math.exp(-0.5 * (x * x)), + "center": 1.0, + }, + "semicircular": { + "pdf": lambda x: math.sqrt(1.0 - (x * x)), + "center": 0, + }, + "wald": { + "pdf": wald_pdf, + "center": 1.0, + }, + "weibull_max": { + "pdf": weibull_max_pdf, + "check_pinv_params": lambda a: 0.25 <= a <= 512, + "center": -1.0, + }, + "weibull_min": { + "pdf": weibull_min_pdf, + "check_pinv_params": lambda a: 0.25 <= a <= 512, + "center": 1.0, + }, +} + + +def _validate_qmc_input(qmc_engine, d, seed): + # Input validation for `qmc_engine` and `d` + # Error messages for invalid `d` are raised by QMCEngine + # we could probably use a stats.qmc.check_qrandom_state + if isinstance(qmc_engine, QMCEngine): + if d is not None and qmc_engine.d != d: + message = "`d` must be consistent with dimension of `qmc_engine`." + raise ValueError(message) + d = qmc_engine.d if d is None else d + elif qmc_engine is None: + d = 1 if d is None else d + qmc_engine = Halton(d, seed=seed) + else: + message = ( + "`qmc_engine` must be an instance of " + "`scipy.stats.qmc.QMCEngine` or `None`." + ) + raise ValueError(message) + + return qmc_engine, d + + +class CustomDistPINV: + def __init__(self, pdf, args): + self._pdf = lambda x: pdf(x, *args) + + def pdf(self, x): + return self._pdf(x) + + +class FastGeneratorInversion: + """ + Fast sampling by numerical inversion of the CDF for a large class of + continuous distributions in `scipy.stats`. + + Parameters + ---------- + dist : rv_frozen object + Frozen distribution object from `scipy.stats`. The list of supported + distributions can be found in the Notes section. The shape parameters, + `loc` and `scale` used to create the distributions must be scalars. + For example, for the Gamma distribution with shape parameter `p`, + `p` has to be a float, and for the beta distribution with shape + parameters (a, b), both a and b have to be floats. + domain : tuple of floats, optional + If one wishes to sample from a truncated/conditional distribution, + the domain has to be specified. + The default is None. In that case, the random variates are not + truncated, and the domain is inferred from the support of the + distribution. + ignore_shape_range : bool, optional + If False, shape parameters that are outside of the valid range + of values to ensure that the numerical accuracy (see Notes) is + high, raise a ValueError. If True, any shape parameters that are valid + for the distribution are accepted. This can be useful for testing. + The default is False. + random_state : {None, int, `numpy.random.Generator`, + `numpy.random.RandomState`}, optional + + A NumPy random number generator or seed for the underlying NumPy + random number generator used to generate the stream of uniform + random numbers. + If `random_state` is None, it uses ``self.random_state``. + If `random_state` is an int, + ``np.random.default_rng(random_state)`` is used. + If `random_state` is already a ``Generator`` or ``RandomState`` + instance then that instance is used. + + Attributes + ---------- + loc : float + The location parameter. + random_state : {`numpy.random.Generator`, `numpy.random.RandomState`} + The random state used in relevant methods like `rvs` (unless + another `random_state` is passed as an argument to these methods). + scale : float + The scale parameter. + + Methods + ------- + cdf + evaluate_error + ppf + qrvs + rvs + support + + Notes + ----- + The class creates an object for continuous distributions specified + by `dist`. The method `rvs` uses a generator from + `scipy.stats.sampling` that is created when the object is instantiated. + In addition, the methods `qrvs` and `ppf` are added. + `qrvs` generate samples based on quasi-random numbers from + `scipy.stats.qmc`. `ppf` is the PPF based on the + numerical inversion method in [1]_ (`NumericalInversePolynomial`) that is + used to generate random variates. + + Supported distributions (`distname`) are: + ``alpha``, ``anglit``, ``argus``, ``beta``, ``betaprime``, ``bradford``, + ``burr``, ``burr12``, ``cauchy``, ``chi``, ``chi2``, ``cosine``, + ``crystalball``, ``expon``, ``gamma``, ``gennorm``, ``geninvgauss``, + ``gumbel_l``, ``gumbel_r``, ``hypsecant``, ``invgamma``, ``invgauss``, + ``invweibull``, ``laplace``, ``logistic``, ``maxwell``, ``moyal``, + ``norm``, ``pareto``, ``powerlaw``, ``t``, ``rayleigh``, ``semicircular``, + ``wald``, ``weibull_max``, ``weibull_min``. + + `rvs` relies on the accuracy of the numerical inversion. If very extreme + shape parameters are used, the numerical inversion might not work. However, + for all implemented distributions, the admissible shape parameters have + been tested, and an error will be raised if the user supplies values + outside of the allowed range. The u-error should not exceed 1e-10 for all + valid parameters. Note that warnings might be raised even if parameters + are within the valid range when the object is instantiated. + To check numerical accuracy, the method `evaluate_error` can be used. + + Note that all implemented distributions are also part of `scipy.stats`, and + the object created by `FastGeneratorInversion` relies on methods like + `ppf`, `cdf` and `pdf` from `rv_frozen`. The main benefit of using this + class can be summarized as follows: Once the generator to sample random + variates is created in the setup step, sampling and evaluation of + the PPF using `ppf` are very fast, + and performance is essentially independent of the distribution. Therefore, + a substantial speed-up can be achieved for many distributions if large + numbers of random variates are required. It is important to know that this + fast sampling is achieved by inversion of the CDF. Thus, one uniform + random variate is transformed into a non-uniform variate, which is an + advantage for several simulation methods, e.g., when + the variance reduction methods of common random variates or + antithetic variates are be used ([2]_). + + In addition, inversion makes it possible to + + - to use a QMC generator from `scipy.stats.qmc` (method `qrvs`), + - to generate random variates truncated to an interval. For example, if + + one aims to sample standard normal random variates from + the interval (2, 4), this can be easily achieved by using the parameter + `domain`. + + The location and scale that are initially defined by `dist` + can be reset without having to rerun the setup + step to create the generator that is used for sampling. The relation + of the distribution `Y` with `loc` and `scale` to the standard + distribution `X` (i.e., ``loc=0`` and ``scale=1``) is given by + ``Y = loc + scale * X``. + + References + ---------- + .. [1] Derflinger, Gerhard, Wolfgang Hörmann, and Josef Leydold. + "Random variate generation by numerical inversion when only the + density is known." ACM Transactions on Modeling and Computer + Simulation (TOMACS) 20.4 (2010): 1-25. + .. [2] Hörmann, Wolfgang, Josef Leydold and Gerhard Derflinger. + "Automatic nonuniform random number generation." + Springer, 2004. + + Examples + -------- + >>> import numpy as np + >>> from scipy import stats + >>> from scipy.stats.sampling import FastGeneratorInversion + + Let's start with a simple example to illustrate the main features: + + >>> gamma_frozen = stats.gamma(1.5) + >>> gamma_dist = FastGeneratorInversion(gamma_frozen) + >>> r = gamma_dist.rvs(size=1000) + + The mean should be approximately equal to the shape parameter 1.5: + + >>> r.mean() + 1.52423591130436 # may vary + + Similarly, we can draw a sample based on quasi-random numbers: + + >>> r = gamma_dist.qrvs(size=1000) + >>> r.mean() + 1.4996639255942914 # may vary + + Compare the PPF against approximation `ppf`. + + >>> q = [0.001, 0.2, 0.5, 0.8, 0.999] + >>> np.max(np.abs(gamma_frozen.ppf(q) - gamma_dist.ppf(q))) + 4.313394796895409e-08 + + To confirm that the numerical inversion is accurate, we evaluate the + approximation error (u-error), which should be below 1e-10 (for more + details, refer to the documentation of `evaluate_error`): + + >>> gamma_dist.evaluate_error() + (7.446320551265581e-11, nan) # may vary + + Note that the location and scale can be changed without instantiating a + new generator: + + >>> gamma_dist.loc = 2 + >>> gamma_dist.scale = 3 + >>> r = gamma_dist.rvs(size=1000) + + The mean should be approximately 2 + 3*1.5 = 6.5. + + >>> r.mean() + 6.399549295242894 # may vary + + Let us also illustrate how truncation can be applied: + + >>> trunc_norm = FastGeneratorInversion(stats.norm(), domain=(3, 4)) + >>> r = trunc_norm.rvs(size=1000) + >>> 3 < r.min() < r.max() < 4 + True + + Check the mean: + + >>> r.mean() + 3.250433367078603 # may vary + + >>> stats.norm.expect(lb=3, ub=4, conditional=True) + 3.260454285589997 + + In this particular, case, `scipy.stats.truncnorm` could also be used to + generate truncated normal random variates. + + """ + + def __init__( + self, + dist, + *, + domain=None, + ignore_shape_range=False, + random_state=None, + ): + + if isinstance(dist, stats.distributions.rv_frozen): + distname = dist.dist.name + if distname not in PINV_CONFIG.keys(): + raise ValueError( + f"Distribution '{distname}' is not supported." + f"It must be one of {list(PINV_CONFIG.keys())}" + ) + else: + raise ValueError("`dist` must be a frozen distribution object") + + loc = dist.kwds.get("loc", 0) + scale = dist.kwds.get("scale", 1) + args = dist.args + if not np.isscalar(loc): + raise ValueError("loc must be scalar.") + if not np.isscalar(scale): + raise ValueError("scale must be scalar.") + + self._frozendist = getattr(stats, distname)( + *args, + loc=loc, + scale=scale, + ) + self._distname = distname + + nargs = np.broadcast_arrays(args)[0].size + nargs_expected = self._frozendist.dist.numargs + if nargs != nargs_expected: + raise ValueError( + f"Each of the {nargs_expected} shape parameters must be a " + f"scalar, but {nargs} values are provided." + ) + + self.random_state = random_state + + if domain is None: + self._domain = self._frozendist.support() + self._p_lower = 0.0 + self._p_domain = 1.0 + else: + self._domain = domain + self._p_lower = self._frozendist.cdf(self._domain[0]) + _p_domain = self._frozendist.cdf(self._domain[1]) - self._p_lower + self._p_domain = _p_domain + self._set_domain_adj() + self._ignore_shape_range = ignore_shape_range + + # the domain to be passed to NumericalInversePolynomial + # define a separate variable since in case of a transformation, + # domain_pinv will not be the same as self._domain + self._domain_pinv = self._domain + + # get information about the distribution from the config to set up + # the generator + dist = self._process_config(distname, args) + + if self._rvs_transform_inv is not None: + d0 = self._rvs_transform_inv(self._domain[0], *args) + d1 = self._rvs_transform_inv(self._domain[1], *args) + if d0 > d1: + # swap values if transformation if decreasing + d0, d1 = d1, d0 + # only update _domain_pinv and not _domain + # _domain refers to the original distribution, _domain_pinv + # to the transformed distribution + self._domain_pinv = d0, d1 + + # self._center has been set by the call self._process_config + # check if self._center is inside the transformed domain + # _domain_pinv, otherwise move it to the endpoint that is closer + if self._center is not None: + if self._center < self._domain_pinv[0]: + self._center = self._domain_pinv[0] + elif self._center > self._domain_pinv[1]: + self._center = self._domain_pinv[1] + + self._rng = NumericalInversePolynomial( + dist, + random_state=self.random_state, + domain=self._domain_pinv, + center=self._center, + ) + + @property + def random_state(self): + return self._random_state + + @random_state.setter + def random_state(self, random_state): + self._random_state = check_random_state_qmc(random_state) + + @property + def loc(self): + return self._frozendist.kwds.get("loc", 0) + + @loc.setter + def loc(self, loc): + if not np.isscalar(loc): + raise ValueError("loc must be scalar.") + self._frozendist.kwds["loc"] = loc + # update the adjusted domain that depends on loc and scale + self._set_domain_adj() + + @property + def scale(self): + return self._frozendist.kwds.get("scale", 0) + + @scale.setter + def scale(self, scale): + if not np.isscalar(scale): + raise ValueError("scale must be scalar.") + self._frozendist.kwds["scale"] = scale + # update the adjusted domain that depends on loc and scale + self._set_domain_adj() + + def _set_domain_adj(self): + """ Adjust the domain based on loc and scale. """ + loc = self.loc + scale = self.scale + lb = self._domain[0] * scale + loc + ub = self._domain[1] * scale + loc + self._domain_adj = (lb, ub) + + def _process_config(self, distname, args): + cfg = PINV_CONFIG[distname] + if "check_pinv_params" in cfg: + if not self._ignore_shape_range: + if not cfg["check_pinv_params"](*args): + msg = ("No generator is defined for the shape parameters " + f"{args}. Use ignore_shape_range to proceed " + "with the selected values.") + raise ValueError(msg) + + if "center" in cfg.keys(): + if not np.isscalar(cfg["center"]): + self._center = cfg["center"](*args) + else: + self._center = cfg["center"] + else: + self._center = None + self._rvs_transform = cfg.get("rvs_transform", None) + self._rvs_transform_inv = cfg.get("rvs_transform_inv", None) + _mirror_uniform = cfg.get("mirror_uniform", None) + if _mirror_uniform is None: + self._mirror_uniform = False + else: + self._mirror_uniform = _mirror_uniform(*args) + + return CustomDistPINV(cfg["pdf"], args) + + def rvs(self, size=None): + """ + Sample from the distribution by inversion. + + Parameters + ---------- + size : int or tuple, optional + The shape of samples. Default is ``None`` in which case a scalar + sample is returned. + + Returns + ------- + rvs : array_like + A NumPy array of random variates. + + Notes + ----- + Random variates are generated by numerical inversion of the CDF, i.e., + `ppf` computed by `NumericalInversePolynomial` when the class + is instantiated. Note that the + default ``rvs`` method of the rv_continuous class is + overwritten. Hence, a different stream of random numbers is generated + even if the same seed is used. + """ + # note: we cannot use self._rng.rvs directly in case + # self._mirror_uniform is true + u = self.random_state.uniform(size=size) + if self._mirror_uniform: + u = 1 - u + r = self._rng.ppf(u) + if self._rvs_transform is not None: + r = self._rvs_transform(r, *self._frozendist.args) + return self.loc + self.scale * r + + def ppf(self, q): + """ + Very fast PPF (inverse CDF) of the distribution which + is a very close approximation of the exact PPF values. + + Parameters + ---------- + q : array_like + Array with probabilities. + + Returns + ------- + ppf : array_like + Quantiles corresponding to the values in `q`. + + Notes + ----- + The evaluation of the PPF is very fast but it may have a large + relative error in the far tails. The numerical precision of the PPF + is controlled by the q-error, that is, + ``max |q - CDF(PPF(q))|`` where the max is taken over points in + the interval [0,1], see `evaluate_error`. + + Note that this PPF is designed to generate random samples. + """ + q = np.asarray(q) + if self._mirror_uniform: + x = self._rng.ppf(1 - q) + else: + x = self._rng.ppf(q) + if self._rvs_transform is not None: + x = self._rvs_transform(x, *self._frozendist.args) + return self.scale * x + self.loc + + def qrvs(self, size=None, d=None, qmc_engine=None): + """ + Quasi-random variates of the given distribution. + + The `qmc_engine` is used to draw uniform quasi-random variates, and + these are converted to quasi-random variates of the given distribution + using inverse transform sampling. + + Parameters + ---------- + size : int, tuple of ints, or None; optional + Defines shape of random variates array. Default is ``None``. + d : int or None, optional + Defines dimension of uniform quasi-random variates to be + transformed. Default is ``None``. + qmc_engine : scipy.stats.qmc.QMCEngine(d=1), optional + Defines the object to use for drawing + quasi-random variates. Default is ``None``, which uses + `scipy.stats.qmc.Halton(1)`. + + Returns + ------- + rvs : ndarray or scalar + Quasi-random variates. See Notes for shape information. + + Notes + ----- + The shape of the output array depends on `size`, `d`, and `qmc_engine`. + The intent is for the interface to be natural, but the detailed rules + to achieve this are complicated. + + - If `qmc_engine` is ``None``, a `scipy.stats.qmc.Halton` instance is + created with dimension `d`. If `d` is not provided, ``d=1``. + - If `qmc_engine` is not ``None`` and `d` is ``None``, `d` is + determined from the dimension of the `qmc_engine`. + - If `qmc_engine` is not ``None`` and `d` is not ``None`` but the + dimensions are inconsistent, a ``ValueError`` is raised. + - After `d` is determined according to the rules above, the output + shape is ``tuple_shape + d_shape``, where: + + - ``tuple_shape = tuple()`` if `size` is ``None``, + - ``tuple_shape = (size,)`` if `size` is an ``int``, + - ``tuple_shape = size`` if `size` is a sequence, + - ``d_shape = tuple()`` if `d` is ``None`` or `d` is 1, and + - ``d_shape = (d,)`` if `d` is greater than 1. + + The elements of the returned array are part of a low-discrepancy + sequence. If `d` is 1, this means that none of the samples are truly + independent. If `d` > 1, each slice ``rvs[..., i]`` will be of a + quasi-independent sequence; see `scipy.stats.qmc.QMCEngine` for + details. Note that when `d` > 1, the samples returned are still those + of the provided univariate distribution, not a multivariate + generalization of that distribution. + + """ + qmc_engine, d = _validate_qmc_input(qmc_engine, d, self.random_state) + # mainly copied from unuran_wrapper.pyx.templ + # `rvs` is flexible about whether `size` is an int or tuple, so this + # should be, too. + try: + if size is None: + tuple_size = (1,) + else: + tuple_size = tuple(size) + except TypeError: + tuple_size = (size,) + # we do not use rng.qrvs directly since we need to be + # able to apply the ppf to 1 - u + N = 1 if size is None else np.prod(size) + u = qmc_engine.random(N) + if self._mirror_uniform: + u = 1 - u + qrvs = self._ppf(u) + if self._rvs_transform is not None: + qrvs = self._rvs_transform(qrvs, *self._frozendist.args) + if size is None: + qrvs = qrvs.squeeze()[()] + else: + if d == 1: + qrvs = qrvs.reshape(tuple_size) + else: + qrvs = qrvs.reshape(tuple_size + (d,)) + return self.loc + self.scale * qrvs + + def evaluate_error(self, size=100000, random_state=None, x_error=False): + """ + Evaluate the numerical accuracy of the inversion (u- and x-error). + + Parameters + ---------- + size : int, optional + The number of random points over which the error is estimated. + Default is ``100000``. + random_state : {None, int, `numpy.random.Generator`, + `numpy.random.RandomState`}, optional + + A NumPy random number generator or seed for the underlying NumPy + random number generator used to generate the stream of uniform + random numbers. + If `random_state` is None, use ``self.random_state``. + If `random_state` is an int, + ``np.random.default_rng(random_state)`` is used. + If `random_state` is already a ``Generator`` or ``RandomState`` + instance then that instance is used. + x_error : bool + If ``True``, return the x-error, as per the notes section. If ``False``, + returns ``NaN``. + + Returns + ------- + u_error, x_error : tuple of floats + A NumPy array of random variates. + + Notes + ----- + The numerical precision of the inverse CDF `ppf` is controlled by + the u-error. It is computed as follows: + ``max |u - CDF(PPF(u))|`` where the max is taken `size` random + points in the interval [0,1]. `random_state` determines the random + sample. Note that if `ppf` was exact, the u-error would be zero. + + The x-error measures the direct distance between the exact PPF + and `ppf`. If ``x_error`` is set to ``True`, it is + computed as the maximum of the minimum of the relative and absolute + x-error: + ``max(min(x_error_abs[i], x_error_rel[i]))`` where + ``x_error_abs[i] = |PPF(u[i]) - PPF_fast(u[i])|``, + ``x_error_rel[i] = max |(PPF(u[i]) - PPF_fast(u[i])) / PPF(u[i])|``. + Note that it is important to consider the relative x-error in the case + that ``PPF(u)`` is close to zero or very large. + + By default, only the u-error is evaluated and the x-error is set to + ``np.nan``. Note that the evaluation of the x-error will be very slow + if the implementation of the PPF is slow. + + Further information about these error measures can be found in [1]_. + + References + ---------- + .. [1] Derflinger, Gerhard, Wolfgang Hörmann, and Josef Leydold. + "Random variate generation by numerical inversion when only the + density is known." ACM Transactions on Modeling and Computer + Simulation (TOMACS) 20.4 (2010): 1-25. + + Examples + -------- + + >>> import numpy as np + >>> from scipy import stats + >>> from scipy.stats.sampling import FastGeneratorInversion + + Create an object for the normal distribution: + + >>> d_norm_frozen = stats.norm() + >>> d_norm = FastGeneratorInversion(d_norm_frozen) + + To confirm that the numerical inversion is accurate, we evaluate the + approximation error (u-error and x-error). + + >>> u_error, x_error = d_norm.evaluate_error(x_error=True) + + The u-error should be below 1e-10: + + >>> u_error + 8.785783212061915e-11 # may vary + + Compare the PPF against approximation `ppf`: + + >>> q = [0.001, 0.2, 0.4, 0.6, 0.8, 0.999] + >>> diff = np.abs(d_norm_frozen.ppf(q) - d_norm.ppf(q)) + >>> x_error_abs = np.max(diff) + >>> x_error_abs + 1.2937954707581412e-08 + + This is the absolute x-error evaluated at the points q. The relative + error is given by + + >>> x_error_rel = np.max(diff / np.abs(d_norm_frozen.ppf(q))) + >>> x_error_rel + 4.186725600453555e-09 + + The x_error computed above is derived in a very similar way over a + much larger set of random values q. At each value q[i], the minimum + of the relative and absolute error is taken. The final value is then + derived as the maximum of these values. In our example, we get the + following value: + + >>> x_error + 4.507068014335139e-07 # may vary + + """ + if not isinstance(size, numbers.Integral | np.integer): + raise ValueError("size must be an integer.") + # urng will be used to draw the samples for testing the error + # it must not interfere with self.random_state. therefore, do not + # call self.rvs, but draw uniform random numbers and apply + # self.ppf (note: like in rvs, consider self._mirror_uniform) + urng = check_random_state_qmc(random_state) + u = urng.uniform(size=size) + if self._mirror_uniform: + u = 1 - u + x = self.ppf(u) + uerr = np.max(np.abs(self._cdf(x) - u)) + if not x_error: + return uerr, np.nan + ppf_u = self._ppf(u) + x_error_abs = np.abs(self.ppf(u)-ppf_u) + x_error_rel = x_error_abs / np.abs(ppf_u) + x_error_combined = np.array([x_error_abs, x_error_rel]).min(axis=0) + return uerr, np.max(x_error_combined) + + def support(self): + """Support of the distribution. + + Returns + ------- + a, b : float + end-points of the distribution's support. + + Notes + ----- + + Note that the support of the distribution depends on `loc`, + `scale` and `domain`. + + Examples + -------- + + >>> from scipy import stats + >>> from scipy.stats.sampling import FastGeneratorInversion + + Define a truncated normal distribution: + + >>> d_norm = FastGeneratorInversion(stats.norm(), domain=(0, 1)) + >>> d_norm.support() + (0, 1) + + Shift the distribution: + + >>> d_norm.loc = 2.5 + >>> d_norm.support() + (2.5, 3.5) + + """ + return self._domain_adj + + def _cdf(self, x): + """Cumulative distribution function (CDF) + + Parameters + ---------- + x : array_like + The values where the CDF is evaluated + + Returns + ------- + y : ndarray + CDF evaluated at x + + """ + y = self._frozendist.cdf(x) + if self._p_domain == 1.0: + return y + return np.clip((y - self._p_lower) / self._p_domain, 0, 1) + + def _ppf(self, q): + """Percent point function (inverse of `cdf`) + + Parameters + ---------- + q : array_like + lower tail probability + + Returns + ------- + x : array_like + quantile corresponding to the lower tail probability q. + + """ + if self._p_domain == 1.0: + return self._frozendist.ppf(q) + x = self._frozendist.ppf(self._p_domain * np.array(q) + self._p_lower) + return np.clip(x, self._domain_adj[0], self._domain_adj[1]) + + +class RatioUniforms: + """ + Generate random samples from a probability density function using the + ratio-of-uniforms method. + + Parameters + ---------- + pdf : callable + A function with signature `pdf(x)` that is proportional to the + probability density function of the distribution. + umax : float + The upper bound of the bounding rectangle in the u-direction. + vmin : float + The lower bound of the bounding rectangle in the v-direction. + vmax : float + The upper bound of the bounding rectangle in the v-direction. + c : float, optional + Shift parameter of ratio-of-uniforms method, see Notes. Default is 0. + random_state : {None, int, `numpy.random.Generator`, + `numpy.random.RandomState`}, optional + + If `seed` is None (or `np.random`), the `numpy.random.RandomState` + singleton is used. + If `seed` is an int, a new ``RandomState`` instance is used, + seeded with `seed`. + If `seed` is already a ``Generator`` or ``RandomState`` instance then + that instance is used. + + Methods + ------- + rvs + + Notes + ----- + Given a univariate probability density function `pdf` and a constant `c`, + define the set ``A = {(u, v) : 0 < u <= sqrt(pdf(v/u + c))}``. + If ``(U, V)`` is a random vector uniformly distributed over ``A``, + then ``V/U + c`` follows a distribution according to `pdf`. + + The above result (see [1]_, [2]_) can be used to sample random variables + using only the PDF, i.e. no inversion of the CDF is required. Typical + choices of `c` are zero or the mode of `pdf`. The set ``A`` is a subset of + the rectangle ``R = [0, umax] x [vmin, vmax]`` where + + - ``umax = sup sqrt(pdf(x))`` + - ``vmin = inf (x - c) sqrt(pdf(x))`` + - ``vmax = sup (x - c) sqrt(pdf(x))`` + + In particular, these values are finite if `pdf` is bounded and + ``x**2 * pdf(x)`` is bounded (i.e. subquadratic tails). + One can generate ``(U, V)`` uniformly on ``R`` and return + ``V/U + c`` if ``(U, V)`` are also in ``A`` which can be directly + verified. + + The algorithm is not changed if one replaces `pdf` by k * `pdf` for any + constant k > 0. Thus, it is often convenient to work with a function + that is proportional to the probability density function by dropping + unnecessary normalization factors. + + Intuitively, the method works well if ``A`` fills up most of the + enclosing rectangle such that the probability is high that ``(U, V)`` + lies in ``A`` whenever it lies in ``R`` as the number of required + iterations becomes too large otherwise. To be more precise, note that + the expected number of iterations to draw ``(U, V)`` uniformly + distributed on ``R`` such that ``(U, V)`` is also in ``A`` is given by + the ratio ``area(R) / area(A) = 2 * umax * (vmax - vmin) / area(pdf)``, + where `area(pdf)` is the integral of `pdf` (which is equal to one if the + probability density function is used but can take on other values if a + function proportional to the density is used). The equality holds since + the area of ``A`` is equal to ``0.5 * area(pdf)`` (Theorem 7.1 in [1]_). + If the sampling fails to generate a single random variate after 50000 + iterations (i.e. not a single draw is in ``A``), an exception is raised. + + If the bounding rectangle is not correctly specified (i.e. if it does not + contain ``A``), the algorithm samples from a distribution different from + the one given by `pdf`. It is therefore recommended to perform a + test such as `~scipy.stats.kstest` as a check. + + References + ---------- + .. [1] L. Devroye, "Non-Uniform Random Variate Generation", + Springer-Verlag, 1986. + + .. [2] W. Hoermann and J. Leydold, "Generating generalized inverse Gaussian + random variates", Statistics and Computing, 24(4), p. 547--557, 2014. + + .. [3] A.J. Kinderman and J.F. Monahan, "Computer Generation of Random + Variables Using the Ratio of Uniform Deviates", + ACM Transactions on Mathematical Software, 3(3), p. 257--260, 1977. + + Examples + -------- + >>> import numpy as np + >>> from scipy import stats + + >>> from scipy.stats.sampling import RatioUniforms + >>> rng = np.random.default_rng() + + Simulate normally distributed random variables. It is easy to compute the + bounding rectangle explicitly in that case. For simplicity, we drop the + normalization factor of the density. + + >>> f = lambda x: np.exp(-x**2 / 2) + >>> v = np.sqrt(f(np.sqrt(2))) * np.sqrt(2) + >>> umax = np.sqrt(f(0)) + >>> gen = RatioUniforms(f, umax=umax, vmin=-v, vmax=v, random_state=rng) + >>> r = gen.rvs(size=2500) + + The K-S test confirms that the random variates are indeed normally + distributed (normality is not rejected at 5% significance level): + + >>> stats.kstest(r, 'norm')[1] + 0.250634764150542 + + The exponential distribution provides another example where the bounding + rectangle can be determined explicitly. + + >>> gen = RatioUniforms(lambda x: np.exp(-x), umax=1, vmin=0, + ... vmax=2*np.exp(-1), random_state=rng) + >>> r = gen.rvs(1000) + >>> stats.kstest(r, 'expon')[1] + 0.21121052054580314 + + """ + + def __init__(self, pdf, *, umax, vmin, vmax, c=0, random_state=None): + if vmin >= vmax: + raise ValueError("vmin must be smaller than vmax.") + + if umax <= 0: + raise ValueError("umax must be positive.") + + self._pdf = pdf + self._umax = umax + self._vmin = vmin + self._vmax = vmax + self._c = c + self._rng = check_random_state(random_state) + + def rvs(self, size=1): + """Sampling of random variates. + + Parameters + ---------- + size : int or tuple of ints, optional + Number of random variates to be generated (default is 1). + + Returns + ------- + rvs : ndarray + The random variates distributed according to the probability + distribution defined by the pdf. + + """ + size1d = tuple(np.atleast_1d(size)) + N = np.prod(size1d) # number of rvs needed, reshape upon return + + # start sampling using ratio of uniforms method + x = np.zeros(N) + simulated, i = 0, 1 + + # loop until N rvs have been generated: expected runtime is finite. + # to avoid infinite loop, raise exception if not a single rv has been + # generated after 50000 tries. even if the expected number of iterations + # is 1000, the probability of this event is (1-1/1000)**50000 + # which is of order 10e-22 + while simulated < N: + k = N - simulated + # simulate uniform rvs on [0, umax] and [vmin, vmax] + u1 = self._umax * self._rng.uniform(size=k) + v1 = self._rng.uniform(self._vmin, self._vmax, size=k) + # apply rejection method + rvs = v1 / u1 + self._c + accept = (u1**2 <= self._pdf(rvs)) + num_accept = np.sum(accept) + if num_accept > 0: + x[simulated:(simulated + num_accept)] = rvs[accept] + simulated += num_accept + + if (simulated == 0) and (i*N >= 50000): + msg = ( + f"Not a single random variate could be generated in {i*N} " + "attempts. The ratio of uniforms method does not appear " + "to work for the provided parameters. Please check the " + "pdf and the bounds." + ) + raise RuntimeError(msg) + i += 1 + + return np.reshape(x, size1d) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_sensitivity_analysis.py b/venv/lib/python3.12/site-packages/scipy/stats/_sensitivity_analysis.py new file mode 100644 index 0000000000000000000000000000000000000000..529c1e6120a63ff65313928ee87b4ee1e881a73c --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_sensitivity_analysis.py @@ -0,0 +1,718 @@ +import inspect +from dataclasses import dataclass +from typing import TYPE_CHECKING, Any +from collections.abc import Callable + +import numpy as np + +from scipy.stats._common import ConfidenceInterval +from scipy.stats._qmc import check_random_state +from scipy.stats._resampling import BootstrapResult +from scipy.stats import qmc, bootstrap +from scipy._lib._array_api import xp_capabilities +from scipy._lib._util import _transition_to_rng + + +if TYPE_CHECKING: + import numpy.typing as npt + from scipy._lib._util import DecimalNumber, IntNumber + + +__all__ = [ + 'sobol_indices' +] + + +def f_ishigami(x: "npt.ArrayLike") -> "npt.NDArray[np.inexact[Any]]": + r"""Ishigami function. + + .. math:: + + Y(\mathbf{x}) = \sin x_1 + 7 \sin^2 x_2 + 0.1 x_3^4 \sin x_1 + + with :math:`\mathbf{x} \in [-\pi, \pi]^3`. + + Parameters + ---------- + x : array_like ([x1, x2, x3], n) + + Returns + ------- + f : array_like (n,) + Function evaluation. + + References + ---------- + .. [1] Ishigami, T. and T. Homma. "An importance quantification technique + in uncertainty analysis for computer models." IEEE, + :doi:`10.1109/ISUMA.1990.151285`, 1990. + """ + x = np.atleast_2d(x) + f_eval = ( + np.sin(x[0]) + + 7 * np.sin(x[1])**2 + + 0.1 * (x[2]**4) * np.sin(x[0]) + ) + return f_eval + + +def sample_A_B( + n, + dists, + rng=None +): + """Sample two matrices A and B. + + Uses a Sobol' sequence with 2`d` columns to have 2 uncorrelated matrices. + This is more efficient than using 2 random draw of Sobol'. + See sec. 5 from [1]_. + + Output shape is (d, n). + + References + ---------- + .. [1] Saltelli, A., P. Annoni, I. Azzini, F. Campolongo, M. Ratto, and + S. Tarantola. "Variance based sensitivity analysis of model + output. Design and estimator for the total sensitivity index." + Computer Physics Communications, 181(2):259-270, + :doi:`10.1016/j.cpc.2009.09.018`, 2010. + """ + d = len(dists) + A_B = qmc.Sobol(d=2*d, seed=rng, bits=64).random(n).T + A_B = A_B.reshape(2, d, -1) + try: + for d_, dist in enumerate(dists): + A_B[:, d_] = dist.ppf(A_B[:, d_]) + except AttributeError as exc: + message = "Each distribution in `dists` must have method `ppf`." + raise ValueError(message) from exc + return A_B + + +def sample_AB(A: np.ndarray, B: np.ndarray) -> np.ndarray: + """AB matrix. + + AB: rows of B into A. Shape (d, d, n). + + - Copy A into d "pages" + - In the first page, replace 1st rows of A with 1st row of B. + ... + - In the dth page, replace dth row of A with dth row of B. + - return the stack of pages + + """ + d, n = A.shape + AB = np.tile(A, (d, 1, 1)) + i = np.arange(d) + AB[i, i] = B[i] + return AB + + +def saltelli_2010( + f_A: np.ndarray, f_B: np.ndarray, f_AB: np.ndarray +) -> tuple[np.ndarray, np.ndarray]: + r"""Saltelli2010 formulation. + + .. math:: + + S_i = \frac{1}{N} \sum_{j=1}^N + f(\mathbf{B})_j (f(\mathbf{AB}^{(i)})_j - f(\mathbf{A})_j) + + .. math:: + + S_{T_i} = \frac{1}{N} \sum_{j=1}^N + (f(\mathbf{A})_j - f(\mathbf{AB}^{(i)})_j)^2 + + Parameters + ---------- + f_A, f_B : array_like (s, n) + Function values at A and B, respectively + f_AB : array_like (d, s, n) + Function values at each of the AB pages + + Returns + ------- + s, st : array_like (s, d) + First order and total order Sobol' indices. + + References + ---------- + .. [1] Saltelli, A., P. Annoni, I. Azzini, F. Campolongo, M. Ratto, and + S. Tarantola. "Variance based sensitivity analysis of model + output. Design and estimator for the total sensitivity index." + Computer Physics Communications, 181(2):259-270, + :doi:`10.1016/j.cpc.2009.09.018`, 2010. + """ + # Empirical variance calculated using output from A and B which are + # independent. Output of AB is not independent and cannot be used + var = np.var([f_A, f_B], axis=(0, -1)) + + # We divide by the variance to have a ratio of variance + # this leads to eq. 2 + s = np.mean(f_B * (f_AB - f_A), axis=-1) / var # Table 2 (b) + st = 0.5 * np.mean((f_A - f_AB) ** 2, axis=-1) / var # Table 2 (f) + + return s.T, st.T + + +@dataclass +class BootstrapSobolResult: + first_order: BootstrapResult + total_order: BootstrapResult + + +@dataclass +class SobolResult: + first_order: np.ndarray + total_order: np.ndarray + _indices_method: Callable + _f_A: np.ndarray + _f_B: np.ndarray + _f_AB: np.ndarray + _A: np.ndarray | None = None + _B: np.ndarray | None = None + _AB: np.ndarray | None = None + _bootstrap_result: BootstrapResult | None = None + + def bootstrap( + self, + confidence_level: "DecimalNumber" = 0.95, + n_resamples: "IntNumber" = 999 + ) -> BootstrapSobolResult: + """Bootstrap Sobol' indices to provide confidence intervals. + + Parameters + ---------- + confidence_level : float, default: ``0.95`` + The confidence level of the confidence intervals. + n_resamples : int, default: ``999`` + The number of resamples performed to form the bootstrap + distribution of the indices. + + Returns + ------- + res : BootstrapSobolResult + Bootstrap result containing the confidence intervals and the + bootstrap distribution of the indices. + + An object with attributes: + + first_order : BootstrapResult + Bootstrap result of the first order indices. + total_order : BootstrapResult + Bootstrap result of the total order indices. + See `BootstrapResult` for more details. + + """ + def statistic(idx): + f_A_ = self._f_A[:, idx] + f_B_ = self._f_B[:, idx] + f_AB_ = self._f_AB[..., idx] + return self._indices_method(f_A_, f_B_, f_AB_) + + n = self._f_A.shape[1] + + res = bootstrap( + [np.arange(n)], statistic=statistic, method="BCa", + n_resamples=n_resamples, + confidence_level=confidence_level, + bootstrap_result=self._bootstrap_result + ) + self._bootstrap_result = res + + first_order = BootstrapResult( + confidence_interval=ConfidenceInterval( + res.confidence_interval.low[0], res.confidence_interval.high[0] + ), + bootstrap_distribution=res.bootstrap_distribution[0], + standard_error=res.standard_error[0], + ) + total_order = BootstrapResult( + confidence_interval=ConfidenceInterval( + res.confidence_interval.low[1], res.confidence_interval.high[1] + ), + bootstrap_distribution=res.bootstrap_distribution[1], + standard_error=res.standard_error[1], + ) + + return BootstrapSobolResult( + first_order=first_order, total_order=total_order + ) + + +@xp_capabilities(np_only=True) +@_transition_to_rng('random_state', replace_doc=False) +def sobol_indices( + *, + func, + n, + dists=None, + method='saltelli_2010', + rng=None +): + r"""Global sensitivity indices of Sobol'. + + Parameters + ---------- + func : callable or dict(str, array_like) + If `func` is a callable, function to compute the Sobol' indices from. + Its signature must be:: + + func(x: ArrayLike) -> ArrayLike + + with ``x`` of shape ``(d, n)`` and output of shape ``(s, n)`` where: + + - ``d`` is the input dimensionality of `func` + (number of input variables), + - ``s`` is the output dimensionality of `func` + (number of output variables), and + - ``n`` is the number of samples (see `n` below). + + Function evaluation values must be finite. + + If `func` is a dictionary, contains the function evaluations from three + different arrays. Keys must be: ``f_A``, ``f_B`` and ``f_AB``. + ``f_A`` and ``f_B`` should have a shape ``(s, n)`` and ``f_AB`` + should have a shape ``(d, s, n)``. + This is an advanced feature and misuse can lead to wrong analysis. + n : int + Number of samples used to generate the matrices ``A`` and ``B``. + Must be a power of 2. The total number of points at which `func` is + evaluated will be ``n*(d+2)``. + dists : list(distributions), optional + List of each parameter's distribution. The distribution of parameters + depends on the application and should be carefully chosen. + Parameters are assumed to be independently distributed, meaning there + is no constraint nor relationship between their values. + + Distributions must be an instance of a class with a ``ppf`` + method. + + Must be specified if `func` is a callable, and ignored otherwise. + method : Callable or str, default: 'saltelli_2010' + Method used to compute the first and total Sobol' indices. + + If a callable, its signature must be:: + + func(f_A: np.ndarray, f_B: np.ndarray, f_AB: np.ndarray) + -> tuple[np.ndarray, np.ndarray] + + with ``f_A, f_B`` of shape ``(s, n)`` and ``f_AB`` of shape + ``(d, s, n)``. + These arrays contain the function evaluations from three different sets + of samples. + The output is a tuple of the first and total indices with + shape ``(s, d)``. + This is an advanced feature and misuse can lead to wrong analysis. + rng : `numpy.random.Generator`, optional + Pseudorandom number generator state. When `rng` is None, a new + `numpy.random.Generator` is created using entropy from the + operating system. Types other than `numpy.random.Generator` are + passed to `numpy.random.default_rng` to instantiate a ``Generator``. + + .. versionchanged:: 1.15.0 + + As part of the `SPEC-007 `_ + transition from use of `numpy.random.RandomState` to + `numpy.random.Generator`, this keyword was changed from `random_state` to + `rng`. For an interim period, both keywords will continue to work, although + only one may be specified at a time. After the interim period, function + calls using the `random_state` keyword will emit warnings. Following a + deprecation period, the `random_state` keyword will be removed. + + Returns + ------- + res : SobolResult + An object with attributes: + + first_order : ndarray of shape (s, d) + First order Sobol' indices. + total_order : ndarray of shape (s, d) + Total order Sobol' indices. + + And method: + + bootstrap(confidence_level: float, n_resamples: int) + -> BootstrapSobolResult + + A method providing confidence intervals on the indices. + See `scipy.stats.bootstrap` for more details. + + The bootstrapping is done on both first and total order indices, + and they are available in `BootstrapSobolResult` as attributes + ``first_order`` and ``total_order``. + + Notes + ----- + The Sobol' method [1]_, [2]_ is a variance-based Sensitivity Analysis which + obtains the contribution of each parameter to the variance of the + quantities of interest (QoIs; i.e., the outputs of `func`). + Respective contributions can be used to rank the parameters and + also gauge the complexity of the model by computing the + model's effective (or mean) dimension. + + .. note:: + + Parameters are assumed to be independently distributed. Each + parameter can still follow any distribution. In fact, the distribution + is very important and should match the real distribution of the + parameters. + + It uses a functional decomposition of the variance of the function to + explore + + .. math:: + + \mathbb{V}(Y) = \sum_{i}^{d} \mathbb{V}_i (Y) + \sum_{i= 2**12``. The more complex the model is, + the more samples will be needed. + + Even for a purely additive model, the indices may not sum to 1 due + to numerical noise. + + References + ---------- + .. [1] Sobol, I. M.. "Sensitivity analysis for nonlinear mathematical + models." Mathematical Modeling and Computational Experiment, 1:407-414, + 1993. + .. [2] Sobol, I. M. (2001). "Global sensitivity indices for nonlinear + mathematical models and their Monte Carlo estimates." Mathematics + and Computers in Simulation, 55(1-3):271-280, + :doi:`10.1016/S0378-4754(00)00270-6`, 2001. + .. [3] Saltelli, A. "Making best use of model evaluations to + compute sensitivity indices." Computer Physics Communications, + 145(2):280-297, :doi:`10.1016/S0010-4655(02)00280-1`, 2002. + .. [4] Saltelli, A., M. Ratto, T. Andres, F. Campolongo, J. Cariboni, + D. Gatelli, M. Saisana, and S. Tarantola. "Global Sensitivity Analysis. + The Primer." 2007. + .. [5] Saltelli, A., P. Annoni, I. Azzini, F. Campolongo, M. Ratto, and + S. Tarantola. "Variance based sensitivity analysis of model + output. Design and estimator for the total sensitivity index." + Computer Physics Communications, 181(2):259-270, + :doi:`10.1016/j.cpc.2009.09.018`, 2010. + .. [6] Ishigami, T. and T. Homma. "An importance quantification technique + in uncertainty analysis for computer models." IEEE, + :doi:`10.1109/ISUMA.1990.151285`, 1990. + + Examples + -------- + The following is an example with the Ishigami function [6]_ + + .. math:: + + Y(\mathbf{x}) = \sin x_1 + 7 \sin^2 x_2 + 0.1 x_3^4 \sin x_1, + + with :math:`\mathbf{x} \in [-\pi, \pi]^3`. This function exhibits strong + non-linearity and non-monotonicity. + + Remember, Sobol' indices assumes that samples are independently + distributed. In this case we use a uniform distribution on each marginals. + + >>> import numpy as np + >>> from scipy.stats import sobol_indices, uniform + >>> rng = np.random.default_rng() + >>> def f_ishigami(x): + ... f_eval = ( + ... np.sin(x[0]) + ... + 7 * np.sin(x[1])**2 + ... + 0.1 * (x[2]**4) * np.sin(x[0]) + ... ) + ... return f_eval + >>> indices = sobol_indices( + ... func=f_ishigami, n=1024, + ... dists=[ + ... uniform(loc=-np.pi, scale=2*np.pi), + ... uniform(loc=-np.pi, scale=2*np.pi), + ... uniform(loc=-np.pi, scale=2*np.pi) + ... ], + ... rng=rng + ... ) + >>> indices.first_order + array([0.31637954, 0.43781162, 0.00318825]) + >>> indices.total_order + array([0.56122127, 0.44287857, 0.24229595]) + + Confidence interval can be obtained using bootstrapping. + + >>> boot = indices.bootstrap() + + Then, this information can be easily visualized. + + >>> import matplotlib.pyplot as plt + >>> fig, axs = plt.subplots(1, 2, figsize=(9, 4)) + >>> _ = axs[0].errorbar( + ... [1, 2, 3], indices.first_order, fmt='o', + ... yerr=[ + ... indices.first_order - boot.first_order.confidence_interval.low, + ... boot.first_order.confidence_interval.high - indices.first_order + ... ], + ... ) + >>> axs[0].set_ylabel("First order Sobol' indices") + >>> axs[0].set_xlabel('Input parameters') + >>> axs[0].set_xticks([1, 2, 3]) + >>> _ = axs[1].errorbar( + ... [1, 2, 3], indices.total_order, fmt='o', + ... yerr=[ + ... indices.total_order - boot.total_order.confidence_interval.low, + ... boot.total_order.confidence_interval.high - indices.total_order + ... ], + ... ) + >>> axs[1].set_ylabel("Total order Sobol' indices") + >>> axs[1].set_xlabel('Input parameters') + >>> axs[1].set_xticks([1, 2, 3]) + >>> plt.tight_layout() + >>> plt.show() + + .. note:: + + By default, `scipy.stats.uniform` has support ``[0, 1]``. + Using the parameters ``loc`` and ``scale``, one obtains the uniform + distribution on ``[loc, loc + scale]``. + + This result is particularly interesting because the first order index + :math:`S_{x_3} = 0` whereas its total order is :math:`S_{T_{x_3}} = 0.244`. + This means that higher order interactions with :math:`x_3` are responsible + for the difference. Almost 25% of the observed variance + on the QoI is due to the correlations between :math:`x_3` and :math:`x_1`, + although :math:`x_3` by itself has no impact on the QoI. + + The following gives a visual explanation of Sobol' indices on this + function. Let's generate 1024 samples in :math:`[-\pi, \pi]^3` and + calculate the value of the output. + + >>> from scipy.stats import qmc + >>> n_dim = 3 + >>> p_labels = ['$x_1$', '$x_2$', '$x_3$'] + >>> sample = qmc.Sobol(d=n_dim, seed=rng).random(1024) + >>> sample = qmc.scale( + ... sample=sample, + ... l_bounds=[-np.pi, -np.pi, -np.pi], + ... u_bounds=[np.pi, np.pi, np.pi] + ... ) + >>> output = f_ishigami(sample.T) + + Now we can do scatter plots of the output with respect to each parameter. + This gives a visual way to understand how each parameter impacts the + output of the function. + + >>> fig, ax = plt.subplots(1, n_dim, figsize=(12, 4)) + >>> for i in range(n_dim): + ... xi = sample[:, i] + ... ax[i].scatter(xi, output, marker='+') + ... ax[i].set_xlabel(p_labels[i]) + >>> ax[0].set_ylabel('Y') + >>> plt.tight_layout() + >>> plt.show() + + Now Sobol' goes a step further: + by conditioning the output value by given values of the parameter + (black lines), the conditional output mean is computed. It corresponds to + the term :math:`\mathbb{E}(Y|x_i)`. Taking the variance of this term gives + the numerator of the Sobol' indices. + + >>> mini = np.min(output) + >>> maxi = np.max(output) + >>> n_bins = 10 + >>> bins = np.linspace(-np.pi, np.pi, num=n_bins, endpoint=False) + >>> dx = bins[1] - bins[0] + >>> fig, ax = plt.subplots(1, n_dim, figsize=(12, 4)) + >>> for i in range(n_dim): + ... xi = sample[:, i] + ... ax[i].scatter(xi, output, marker='+') + ... ax[i].set_xlabel(p_labels[i]) + ... for bin_ in bins: + ... idx = np.where((bin_ <= xi) & (xi <= bin_ + dx)) + ... xi_ = xi[idx] + ... y_ = output[idx] + ... ave_y_ = np.mean(y_) + ... ax[i].plot([bin_ + dx/2] * 2, [mini, maxi], c='k') + ... ax[i].scatter(bin_ + dx/2, ave_y_, c='r') + >>> ax[0].set_ylabel('Y') + >>> plt.tight_layout() + >>> plt.show() + + Looking at :math:`x_3`, the variance + of the mean is zero leading to :math:`S_{x_3} = 0`. But we can further + observe that the variance of the output is not constant along the parameter + values of :math:`x_3`. This heteroscedasticity is explained by higher order + interactions. Moreover, a heteroscedasticity is also noticeable on + :math:`x_1` leading to an interaction between :math:`x_3` and :math:`x_1`. + On :math:`x_2`, the variance seems to be constant and thus null interaction + with this parameter can be supposed. + + This case is fairly simple to analyse visually---although it is only a + qualitative analysis. Nevertheless, when the number of input parameters + increases such analysis becomes unrealistic as it would be difficult to + conclude on high-order terms. Hence the benefit of using Sobol' indices. + + """ + rng = check_random_state(rng) + + n_ = int(n) + if not (n_ & (n_ - 1) == 0) or n != n_: + raise ValueError( + "The balance properties of Sobol' points require 'n' " + "to be a power of 2." + ) + n = n_ + + if not callable(method): + indices_methods = { + "saltelli_2010": saltelli_2010, + } + try: + method = method.lower() # type: ignore[assignment] + indices_method_ = indices_methods[method] + except KeyError as exc: + message = ( + f"{method!r} is not a valid 'method'. It must be one of" + f" {set(indices_methods)!r} or a callable." + ) + raise ValueError(message) from exc + else: + indices_method_ = method + sig = inspect.signature(indices_method_) + + if set(sig.parameters) != {'f_A', 'f_B', 'f_AB'}: + message = ( + "If 'method' is a callable, it must have the following" + f" signature: {inspect.signature(saltelli_2010)}" + ) + raise ValueError(message) + + def indices_method(f_A, f_B, f_AB): + """Wrap indices method to ensure proper output dimension. + + 1D when single output, 2D otherwise. + """ + return np.squeeze(indices_method_(f_A=f_A, f_B=f_B, f_AB=f_AB)) + + if callable(func): + if dists is None: + raise ValueError( + "'dists' must be defined when 'func' is a callable." + ) + + def wrapped_func(x): + return np.atleast_2d(func(x)) + + A, B = sample_A_B(n=n, dists=dists, rng=rng) + AB = sample_AB(A=A, B=B) + + f_A = wrapped_func(A) + + if f_A.shape[1] != n: + raise ValueError( + "'func' output should have a shape ``(s, -1)`` with ``s`` " + "the number of output." + ) + + def funcAB(AB): + d, d, n = AB.shape + AB = np.moveaxis(AB, 0, -1).reshape(d, n*d) + f_AB = wrapped_func(AB) + return np.moveaxis(f_AB.reshape((-1, n, d)), -1, 0) + + f_B = wrapped_func(B) + f_AB = funcAB(AB) + else: + message = ( + "When 'func' is a dictionary, it must contain the following " + "keys: 'f_A', 'f_B' and 'f_AB'." + "'f_A' and 'f_B' should have a shape ``(s, n)`` and 'f_AB' " + "should have a shape ``(d, s, n)``." + ) + try: + f_A, f_B, f_AB = map(lambda arr: arr.copy(), np.atleast_2d( + func['f_A'], func['f_B'], func['f_AB'] + )) + except KeyError as exc: + raise ValueError(message) from exc + + if f_A.shape[1] != n or f_A.shape != f_B.shape or \ + f_AB.shape == f_A.shape or f_AB.shape[-1] % n != 0: + raise ValueError(message) + + # Normalization by mean + # Sobol', I. and Levitan, Y. L. (1999). On the use of variance reducing + # multipliers in monte carlo computations of a global sensitivity index. + # Computer Physics Communications, 117(1) :52-61. + mean = np.mean([f_A, f_B], axis=(0, -1)).reshape(-1, 1) + f_A -= mean + f_B -= mean + f_AB -= mean + + # Compute indices + # Filter warnings for constant output as var = 0 + with np.errstate(divide='ignore', invalid='ignore'): + first_order, total_order = indices_method(f_A=f_A, f_B=f_B, f_AB=f_AB) + + # null variance means null indices + first_order[~np.isfinite(first_order)] = 0 + total_order[~np.isfinite(total_order)] = 0 + + res = dict( + first_order=first_order, + total_order=total_order, + _indices_method=indices_method, + _f_A=f_A, + _f_B=f_B, + _f_AB=f_AB + ) + + if callable(func): + res.update( + dict( + _A=A, + _B=B, + _AB=AB, + ) + ) + + return SobolResult(**res) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_sobol.cpython-312-x86_64-linux-gnu.so b/venv/lib/python3.12/site-packages/scipy/stats/_sobol.cpython-312-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..5f7bf1850e616490ad4d74a43210a6e02b302299 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_sobol.cpython-312-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f26c7d98b4befa24ddfdf90406b3d7b33cff94f60b473d33a0af82ee329c9a77 +size 184960 diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_sobol.pyi b/venv/lib/python3.12/site-packages/scipy/stats/_sobol.pyi new file mode 100644 index 0000000000000000000000000000000000000000..7ca5e3a9c1a142b25ac26401e9ab1cb6726c877f --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_sobol.pyi @@ -0,0 +1,54 @@ +import numpy as np +from scipy._lib._util import IntNumber +from typing import Literal + +def _initialize_v( + v : np.ndarray, + dim : IntNumber, + bits: IntNumber +) -> None: ... + +def _cscramble ( + dim : IntNumber, + bits: IntNumber, + ltm : np.ndarray, + sv: np.ndarray +) -> None: ... + +def _fill_p_cumulative( + p: np.ndarray, + p_cumulative: np.ndarray +) -> None: ... + +def _draw( + n : IntNumber, + num_gen: IntNumber, + dim: IntNumber, + scale: float, + sv: np.ndarray, + quasi: np.ndarray, + sample: np.ndarray + ) -> None: ... + +def _fast_forward( + n: IntNumber, + num_gen: IntNumber, + dim: IntNumber, + sv: np.ndarray, + quasi: np.ndarray + ) -> None: ... + +def _categorize( + draws: np.ndarray, + p_cumulative: np.ndarray, + result: np.ndarray + ) -> None: ... + +_MAXDIM: Literal[21201] +_MAXDEG: Literal[18] + +def _test_find_index( + p_cumulative: np.ndarray, + size: int, + value: float + ) -> int: ... diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_sobol_direction_numbers.npz b/venv/lib/python3.12/site-packages/scipy/stats/_sobol_direction_numbers.npz new file mode 100644 index 0000000000000000000000000000000000000000..44f1f1e9ebd1eb188289ca9adb8027855c1a23b6 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_sobol_direction_numbers.npz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4859931147d42ce465b8605cb277f957d98b839d03194fdf06579357906d193b +size 589334 diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_stats.cpython-312-x86_64-linux-gnu.so b/venv/lib/python3.12/site-packages/scipy/stats/_stats.cpython-312-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..0d99a2a0098d2912fd938b0417230804463c47b8 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_stats.cpython-312-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ae458e9e7341aa4b88d2bb2225def5108b5fecc6092a9dee86634f8504d89d3a +size 497368 diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_stats.pxd b/venv/lib/python3.12/site-packages/scipy/stats/_stats.pxd new file mode 100644 index 0000000000000000000000000000000000000000..e01565f75fe232446e4b8b0b50fdf645c8506108 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_stats.pxd @@ -0,0 +1,10 @@ +# destined to be used in a LowLevelCallable + +cdef double _geninvgauss_pdf(double x, void *user_data) noexcept nogil +cdef double _studentized_range_cdf(int n, double[2] x, void *user_data) noexcept nogil +cdef double _studentized_range_cdf_asymptotic(double z, void *user_data) noexcept nogil +cdef double _studentized_range_pdf(int n, double[2] x, void *user_data) noexcept nogil +cdef double _studentized_range_pdf_asymptotic(double z, void *user_data) noexcept nogil +cdef double _studentized_range_moment(int n, double[3] x_arg, void *user_data) noexcept nogil +cdef double _genhyperbolic_pdf(double x, void *user_data) noexcept nogil +cdef double _genhyperbolic_logpdf(double x, void *user_data) noexcept nogil diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_stats_mstats_common.py b/venv/lib/python3.12/site-packages/scipy/stats/_stats_mstats_common.py new file mode 100644 index 0000000000000000000000000000000000000000..c272fcc1a31a6f296cb54c28859ea51b3ca03362 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_stats_mstats_common.py @@ -0,0 +1,34 @@ +import numpy as np +from .._lib._bunch import _make_tuple_bunch + +__all__ = ['_find_repeats'] + +# This is not a namedtuple for backwards compatibility. See PR #12983 +TheilslopesResult = _make_tuple_bunch('TheilslopesResult', + ['slope', 'intercept', + 'low_slope', 'high_slope']) +SiegelslopesResult = _make_tuple_bunch('SiegelslopesResult', + ['slope', 'intercept']) + + +def _n_samples_optional_x(kwargs): + return 2 if kwargs.get('x', None) is not None else 1 + + +def _find_repeats(arr): + # This function assumes it may clobber its input. + if len(arr) == 0: + return np.array(0, np.float64), np.array(0, np.intp) + + # XXX This cast was previously needed for the Fortran implementation, + # should we ditch it? + arr = np.asarray(arr, np.float64).ravel() + arr.sort() + + # Taken from NumPy 1.9's np.unique. + change = np.concatenate(([True], arr[1:] != arr[:-1])) + unique = arr[change] + change_idx = np.concatenate(np.nonzero(change) + ([arr.size],)) + freq = np.diff(change_idx) + atleast2 = freq > 1 + return unique[atleast2], freq[atleast2] diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_stats_py.py b/venv/lib/python3.12/site-packages/scipy/stats/_stats_py.py new file mode 100644 index 0000000000000000000000000000000000000000..bf10af5738d8c2fa4ec7ea33cf0deca9e9bc2a19 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_stats_py.py @@ -0,0 +1,11250 @@ +# Copyright 2002 Gary Strangman. All rights reserved +# Copyright 2002-2016 The SciPy Developers +# +# The original code from Gary Strangman was heavily adapted for +# use in SciPy by Travis Oliphant. The original code came with the +# following disclaimer: +# +# This software is provided "as-is". There are no expressed or implied +# warranties of any kind, including, but not limited to, the warranties +# of merchantability and fitness for a given application. In no event +# shall Gary Strangman be liable for any direct, indirect, incidental, +# special, exemplary or consequential damages (including, but not limited +# to, loss of use, data or profits, or business interruption) however +# caused and on any theory of liability, whether in contract, strict +# liability or tort (including negligence or otherwise) arising in any way +# out of the use of this software, even if advised of the possibility of +# such damage. + +""" +A collection of basic statistical functions for Python. + +References +---------- +.. [CRCProbStat2000] Zwillinger, D. and Kokoska, S. (2000). CRC Standard + Probability and Statistics Tables and Formulae. Chapman & Hall: New + York. 2000. + +""" +import math +import itertools +import operator +import warnings +from collections import namedtuple +from collections.abc import Sequence + +import numpy as np +from numpy import array, asarray, ma + +from scipy import sparse +from scipy.spatial.distance import cdist + +from scipy.optimize import milp, LinearConstraint +from scipy.optimize.elementwise import find_root +from scipy._lib._util import _get_nan, _rename_parameter, _contains_nan + +import scipy.special as special +# Import unused here but needs to stay until end of deprecation periode +# See https://github.com/scipy/scipy/issues/15765#issuecomment-1875564522 +from scipy import linalg # noqa: F401 +from . import distributions +from . import _mstats_basic as mstats_basic + +from ._stats import _kendall_dis, _toint64, _weightedrankedtau + +from dataclasses import dataclass, field +from ._stats_pythran import _compute_outer_prob_inside_method +from ._resampling import (MonteCarloMethod, PermutationMethod, BootstrapMethod, + monte_carlo_test, permutation_test, bootstrap,) +from ._axis_nan_policy import (_axis_nan_policy_factory, _broadcast_shapes, + _broadcast_arrays, + _broadcast_array_shapes_remove_axis, SmallSampleWarning, + too_small_1d_not_omit, too_small_1d_omit, + too_small_nd_not_omit, too_small_nd_omit) +from ._binomtest import _binary_search_for_binom_tst as _binary_search, _SimpleBinomial +from scipy._lib._bunch import _make_tuple_bunch +from scipy import stats +from scipy._lib._array_api import ( + _asarray, + array_namespace, + is_lazy_array, + is_dask, + is_numpy, + is_cupy, + is_jax, + is_marray, + xp_size, + xp_vector_norm, + xp_promote, + xp_result_type, + xp_capabilities, + xp_ravel, + _count_nonmasked, + _share_masks, + _masked_apply, + xp_swapaxes, + xp_device, +) +import scipy._external.array_api_extra as xpx +from scipy.stats._quantile import _xp_searchsorted + + +# Functions/classes in other files should be added in `__init__.py`, not here +__all__ = ['gmean', 'hmean', 'pmean', 'mode', 'tmean', 'tvar', + 'tmin', 'tmax', 'tstd', 'tsem', 'moment', + 'skew', 'kurtosis', 'describe', 'skewtest', 'kurtosistest', + 'normaltest', 'jarque_bera', + 'scoreatpercentile', 'percentileofscore', + 'cumfreq', 'relfreq', 'obrientransform', + 'sem', 'zmap', 'zscore', 'gzscore', 'iqr', 'gstd', + 'median_abs_deviation', + 'sigmaclip', 'trimboth', 'trim1', 'trim_mean', + 'f_oneway', 'pearsonr', 'fisher_exact', + 'spearmanr', 'pointbiserialr', + 'kendalltau', 'weightedtau', + 'linregress', 'ttest_1samp', + 'ttest_ind', 'ttest_ind_from_stats', 'ttest_rel', + 'kstest', 'ks_1samp', 'ks_2samp', + 'chisquare', 'power_divergence', + 'tiecorrect', 'ranksums', 'kruskal', 'friedmanchisquare', + 'rankdata', 'combine_pvalues', 'quantile_test', + 'wasserstein_distance', 'wasserstein_distance_nd', 'energy_distance', + 'brunnermunzel', 'alexandergovern', + 'expectile', 'lmoment'] + + +def _chk_asarray(a, axis, *, xp=None): + if xp is None: + xp = array_namespace(a) + + if axis is None: + a = xp.reshape(a, (-1,)) + outaxis = 0 + else: + a = xp.asarray(a) + outaxis = axis + + if a.ndim == 0: + a = xp.reshape(a, (-1,)) + + return a, outaxis + + +SignificanceResult = _make_tuple_bunch('SignificanceResult', + ['statistic', 'pvalue'], []) +# Let's call a SignificanceResult with legacy :correlation" attribute a +# "CorrelationResult". Don't add to `extra_field_names`- shouldn't be in repr. + + +def _pack_CorrelationResult(statistic, pvalue, correlation): + res = SignificanceResult(statistic, pvalue) + res.correlation = correlation + return res + + +def _unpack_CorrelationResult(res, _): + return res.statistic, res.pvalue, res.correlation + + +# note that `weights` are paired with `x` +@xp_capabilities(marray=True) +@_axis_nan_policy_factory( + lambda x: x, n_samples=1, n_outputs=1, too_small=0, paired=True, + result_to_tuple=lambda x, _: (x,), kwd_samples=['weights']) +def gmean(a, axis=0, dtype=None, weights=None): + r"""Compute the weighted geometric mean along the specified axis. + + The weighted geometric mean of the array :math:`a_i` associated to weights + :math:`w_i` is: + + .. math:: + + \exp \left( \frac{ \sum_{i=1}^n w_i \ln a_i }{ \sum_{i=1}^n w_i } + \right) \, , + + and, with equal weights, it gives: + + .. math:: + + \sqrt[n]{ \prod_{i=1}^n a_i } \, . + + Parameters + ---------- + a : array_like + Input array or object that can be converted to an array. + axis : int or None, optional + Axis along which the geometric mean is computed. Default is 0. + If None, compute over the whole array `a`. + dtype : dtype, optional + (Floating point) dtype to which numerical arguments are cast before performing + the calculation. If `dtype` is not specified, it defaults to the floating point + result dtype of the numerical arguments. + weights : array_like, optional + The `weights` array must be broadcastable to the same shape as `a`. + Default is None, which gives each value a weight of 1.0. + + Returns + ------- + gmean : ndarray + See `dtype` parameter above. + + See Also + -------- + numpy.mean : Arithmetic average + numpy.average : Weighted average + hmean : Harmonic mean + + Notes + ----- + The sample geometric mean is the exponential of the mean of the natural + logarithms of the observations. + Negative observations will produce NaNs in the output because the *natural* + logarithm (as opposed to the *complex* logarithm) is defined only for + non-negative reals. + + References + ---------- + .. [1] "Weighted Geometric Mean", *Wikipedia*, + https://en.wikipedia.org/wiki/Weighted_geometric_mean. + .. [2] Grossman, J., Grossman, M., Katz, R., "Averages: A New Approach", + Archimedes Foundation, 1983 + + Examples + -------- + >>> from scipy.stats import gmean + >>> gmean([1, 4]) + 2.0 + >>> gmean([1, 2, 3, 4, 5, 6, 7]) + 3.3800151591412964 + >>> gmean([1, 4, 7], weights=[3, 1, 3]) + 2.80668351922014 + + """ + xp = array_namespace(a, weights) + dtype = (xp_result_type(a, weights, force_floating=True, xp=xp) + if dtype is None else dtype) + a = xp.asarray(a, dtype=dtype) + + if weights is not None: + weights = xp.asarray(weights, dtype=dtype) + + with np.errstate(divide='ignore'): + log_a = xp.log(a) + + return xp.exp(_xp_mean(log_a, axis=axis, weights=weights)) + + +@xp_capabilities(marray=True) +@_axis_nan_policy_factory( + lambda x: x, n_samples=1, n_outputs=1, too_small=0, paired=True, + result_to_tuple=lambda x, _: (x,), kwd_samples=['weights']) +def hmean(a, axis=0, dtype=None, *, weights=None): + r"""Calculate the weighted harmonic mean along the specified axis. + + The weighted harmonic mean of the array :math:`a_i` associated to weights + :math:`w_i` is: + + .. math:: + + \frac{ \sum_{i=1}^n w_i }{ \sum_{i=1}^n \frac{w_i}{a_i} } \, , + + and, with equal weights, it gives: + + .. math:: + + \frac{ n }{ \sum_{i=1}^n \frac{1}{a_i} } \, . + + Parameters + ---------- + a : array_like + Input array, masked array or object that can be converted to an array. + axis : int or None, optional + Axis along which the harmonic mean is computed. Default is 0. + If None, compute over the whole array `a`. + dtype : dtype, optional + (Floating point) dtype to which numerical arguments are cast before performing + the calculation. If `dtype` is not specified, it defaults to the floating point + result dtype of the numerical arguments. + weights : array_like, optional + The weights array can either be 1-D (in which case its length must be + the size of `a` along the given `axis`) or of the same shape as `a`. + Default is None, which gives each value a weight of 1.0. + + .. versionadded:: 1.9 + + Returns + ------- + hmean : ndarray + See `dtype` parameter above. + + See Also + -------- + numpy.mean : Arithmetic average + numpy.average : Weighted average + gmean : Geometric mean + + Notes + ----- + The sample harmonic mean is the reciprocal of the mean of the reciprocals + of the observations. + + The harmonic mean is computed over a single dimension of the input + array, axis=0 by default, or all values in the array if axis=None. + float64 intermediate and return values are used for integer inputs. + + The harmonic mean is only defined if all observations are non-negative; + otherwise, the result is NaN. + + References + ---------- + .. [1] "Weighted Harmonic Mean", *Wikipedia*, + https://en.wikipedia.org/wiki/Harmonic_mean#Weighted_harmonic_mean + .. [2] Ferger, F., "The nature and use of the harmonic mean", Journal of + the American Statistical Association, vol. 26, pp. 36-40, 1931 + + Examples + -------- + >>> from scipy.stats import hmean + >>> hmean([1, 4]) + 1.6000000000000001 + >>> hmean([1, 2, 3, 4, 5, 6, 7]) + 2.6997245179063363 + >>> hmean([1, 4, 7], weights=[3, 1, 3]) + 1.9029126213592233 + + """ + xp = array_namespace(a, weights) + dtype = (xp_result_type(a, weights, force_floating=True, xp=xp) + if dtype is None else dtype) + a = xp.asarray(a, dtype=dtype) + + if weights is not None: + weights = xp.asarray(weights, dtype=dtype) + + negative_mask = a < 0 + a = xp.where(negative_mask, xp.nan, a) + + if not is_lazy_array(negative_mask) and xp.any(negative_mask): + message = ("The harmonic mean is only defined if all elements are " + "non-negative; otherwise, the result is NaN.") + warnings.warn(message, RuntimeWarning, stacklevel=2) + + with np.errstate(divide='ignore'): + return 1.0 / _xp_mean(1.0 / a, axis=axis, weights=weights) + + +@xp_capabilities(marray=True) +@_axis_nan_policy_factory( + lambda x: x, n_samples=1, n_outputs=1, too_small=0, paired=True, + result_to_tuple=lambda x, _: (x,), kwd_samples=['weights']) +def pmean(a, p, *, axis=0, dtype=None, weights=None): + r"""Calculate the weighted power mean along the specified axis. + + The weighted power mean of the array :math:`a_i` associated to weights + :math:`w_i` is: + + .. math:: + + \left( \frac{ \sum_{i=1}^n w_i a_i^p }{ \sum_{i=1}^n w_i } + \right)^{ 1 / p } \, , + + and, with equal weights, it gives: + + .. math:: + + \left( \frac{ 1 }{ n } \sum_{i=1}^n a_i^p \right)^{ 1 / p } \, . + + When ``p=0``, it returns the geometric mean. + + This mean is also called generalized mean or Hölder mean, and must not be + confused with the Kolmogorov generalized mean, also called + quasi-arithmetic mean or generalized f-mean [3]_. + + Parameters + ---------- + a : array_like + Input array, masked array or object that can be converted to an array. + p : int or float + Exponent. Must be finite. + axis : int or None, optional + Axis along which the power mean is computed. Default is 0. + If None, compute over the whole array `a`. + dtype : dtype, optional + (Floating point) dtype to which numerical arguments are cast before performing + the calculation. If `dtype` is not specified, it defaults to the floating point + result dtype of the numerical arguments. + weights : array_like, optional + The weights array can either be 1-D (in which case its length must be + the size of `a` along the given `axis`) or of the same shape as `a`. + Default is None, which gives each value a weight of 1.0. + + Returns + ------- + pmean : ndarray, see `dtype` parameter above. + Output array containing the power mean values. + + See Also + -------- + numpy.average : Weighted average + gmean : Geometric mean + hmean : Harmonic mean + + Notes + ----- + The power mean is computed over a single dimension of the input + array, ``axis=0`` by default, or all values in the array if ``axis=None``. + float64 intermediate and return values are used for integer inputs. + + The power mean is only defined if all observations are non-negative; + otherwise, the result is NaN. + + .. versionadded:: 1.9 + + References + ---------- + .. [1] "Generalized Mean", *Wikipedia*, + https://en.wikipedia.org/wiki/Generalized_mean + .. [2] Norris, N., "Convexity properties of generalized mean value + functions", The Annals of Mathematical Statistics, vol. 8, + pp. 118-120, 1937 + .. [3] Bullen, P.S., Handbook of Means and Their Inequalities, 2003 + + Examples + -------- + >>> from scipy.stats import pmean, hmean, gmean + >>> pmean([1, 4], 1.3) + 2.639372938300652 + >>> pmean([1, 2, 3, 4, 5, 6, 7], 1.3) + 4.157111214492084 + >>> pmean([1, 4, 7], -2, weights=[3, 1, 3]) + 1.4969684896631954 + + For p=-1, power mean is equal to harmonic mean: + + >>> pmean([1, 4, 7], -1, weights=[3, 1, 3]) + 1.9029126213592233 + >>> hmean([1, 4, 7], weights=[3, 1, 3]) + 1.9029126213592233 + + For p=0, power mean is defined as the geometric mean: + + >>> pmean([1, 4, 7], 0, weights=[3, 1, 3]) + 2.80668351922014 + >>> gmean([1, 4, 7], weights=[3, 1, 3]) + 2.80668351922014 + + """ + if not isinstance(p, int | float): + raise ValueError("Power mean only defined for exponent of type int or " + "float.") + if p == 0: + return gmean(a, axis=axis, dtype=dtype, weights=weights) + elif math.isinf(p): + message = "Power mean only implemented for finite `p`" + raise NotImplementedError(message) + + xp = array_namespace(a, weights) + dtype = (xp_result_type(a, p, weights, force_floating=True, xp=xp) + if dtype is None else dtype) + a = xp.asarray(a, dtype=dtype) + + if weights is not None: + weights = xp.asarray(weights, dtype=dtype) + + negative_mask = a < 0 + a = xp.where(negative_mask, np.nan, a) + + if not is_lazy_array(negative_mask) and xp.any(negative_mask): + message = ("The power mean is only defined if all elements are " + "non-negative; otherwise, the result is NaN.") + warnings.warn(message, RuntimeWarning, stacklevel=2) + + # Linearized approximation for small p to avoid numerical issues; see gh-23407 + p_threshold = 2e-6 + if abs(p) <= p_threshold: + return _linearized_pmean(a, p, axis=axis, weights=weights, xp=xp) + + with np.errstate(divide='ignore', invalid='ignore'): + return _xp_mean(a**float(p), axis=axis, weights=weights)**(1/p) + + +ModeResult = namedtuple('ModeResult', ('mode', 'count')) + + +def _mode_result(mode, count): + # When a slice is empty, `_axis_nan_policy` automatically produces + # NaN for `mode` and `count`. This is a reasonable convention for `mode`, + # but `count` should not be NaN; it should be zero. + xp = array_namespace(mode, count) + i = xp.isnan(count) + if i.shape == (): + count = xp.asarray(0, dtype=count.dtype)[()] if i else count + else: + count = xpx.at(count)[i].set(0) + if is_numpy(xp) and mode.ndim > 0: + # When a) xp is NumPy, b) array is multidimensional, c) NaNs are present, + # and d) nan_policy='omit', `_axis_nan_policy` decorator uses + # `np.apply_along_axis` to compute mode/count along each axis-slice separately. + # The result is a single array with the result dtype of `mode` and `count`. + # We need to change `count` back to an integer. + count = xp.astype(count, xp.asarray(0).dtype) + return ModeResult(mode, count) + + +@xp_capabilities(skip_backends=[('dask.array', "can't compute chunk size"), + ('cupy', "data-apis/array-api-compat#312")], + jax_jit=False, # would delegate, but jax-ml/jax#34486 + marray=True, + extra_note=("Values in MArray input must be real numbers " + "that cast safely to floating point.")) +@_axis_nan_policy_factory(_mode_result, override={'nan_propagation': False}) +def mode(a, axis=0, nan_policy='propagate', keepdims=False): + r"""Return an array of the modal (most common) value in the passed array. + + If there is more than one such value, only one is returned. + The bin-count for the modal bins is also returned. + + Parameters + ---------- + a : array_like + Numeric, n-dimensional array of which to find mode(s). + axis : int or None, optional + Axis along which to operate. Default is 0. If None, compute over + the whole array `a`. + nan_policy : {'propagate', 'raise', 'omit'}, optional + Defines how to handle when input contains nan. + The following options are available (default is 'propagate'): + + * 'propagate': treats nan as it would treat any other value + * 'raise': throws an error + * 'omit': performs the calculations ignoring nan values + + keepdims : bool, optional + If set to ``False``, the `axis` over which the statistic is taken + is consumed (eliminated from the output array). If set to ``True``, + the `axis` is retained with size one, and the result will broadcast + correctly against the input array. + + Returns + ------- + mode : ndarray + Array of modal values. + count : ndarray + Array of counts for each mode. + + Notes + ----- + The mode is calculated using `numpy.unique`. + In NumPy versions 1.21 and after, all NaNs - even those with different + binary representations - are treated as equivalent and counted as separate + instances of the same value. + + By convention, the mode of an empty array is NaN, and the associated count + is zero. + + Examples + -------- + >>> import numpy as np + >>> a = np.array([[3, 0, 3, 7], + ... [3, 2, 6, 2], + ... [1, 7, 2, 8], + ... [3, 0, 6, 1], + ... [3, 2, 5, 5]]) + >>> from scipy import stats + >>> stats.mode(a, keepdims=True) + ModeResult(mode=array([[3, 0, 6, 1]]), count=array([[4, 2, 2, 1]])) + + To get mode of whole array, specify ``axis=None``: + + >>> stats.mode(a, axis=None, keepdims=True) + ModeResult(mode=[[3]], count=[[5]]) + >>> stats.mode(a, axis=None, keepdims=False) + ModeResult(mode=3, count=5) + + """ + xp = array_namespace(a) + + # `axis`, `nan_policy`, and `keepdims` are handled by `_axis_nan_policy` + if not xp.isdtype(a.dtype, 'numeric'): + message = ("Argument `a` is not recognized as numeric. " + "Support for input that cannot be coerced to a numeric " + "array was deprecated in SciPy 1.9.0 and removed in SciPy " + "1.11.0. Please consider `np.unique`.") + raise TypeError(message) + + if xp_size(a) == 0: + NaN = _get_nan(a, xp=xp) + return ModeResult(*xp.asarray([NaN, 0], dtype=NaN.dtype)) + + if a.ndim == 1 and not is_marray(xp): + vals, cnts = xp.unique_counts(a) + # in contrast with np.unique, `unique_counts` treats all NaNs as distinct, + # but we have always grouped them. Replace `cnts` corresponding with NaNs + # with the number of NaNs. + mask = xp.isnan(vals) + cnts = xpx.at(cnts)[mask].set(xp.count_nonzero(mask)) + modes, counts = vals[xp.argmax(cnts)], xp.max(cnts) + default_int = xp.asarray(1).dtype # fail slow CI job failed - incorrect dtype + counts = xp.astype(counts, default_int, copy=False) + modes = modes[()] if modes.ndim == 0 else modes + counts = counts[()] if counts.ndim == 0 else counts + return ModeResult(modes, counts) + + # `axis` is always -1 after the `_axis_nan_policy` decorator + y = xp.sort(a, axis=-1) + + # Get boolean array of elements that are different from the previous element + if is_marray(xp): + # Treat masked elements as NaNs and *don't* count NaNs as equal. Two downsides: + # - integer arrays are converted to floating point, so some very large distinct + # integers will no longer be distinct. + # - NaNs in the original data *will* be treated as distinct. + y_data = xp_promote(y.data, force_floating=True, xp=y._xp) + y_data = y._xp.where(y.mask, y._xp.nan, y_data) + i = xp.concat([xp.ones(y.shape[:-1] + (1,), dtype=xp.bool), + (y_data[..., :-1] != y_data[..., 1:])], axis=-1) + else: + i = xp.concat([xp.ones(y.shape[:-1] + (1,), dtype=xp.bool), + (y[..., :-1] != y[..., 1:]) & ~xp.isnan(y[..., :-1])], axis=-1) + # Get linear integer indices of these elements in a raveled array + indices = xp.arange(xp_size(y), device=xp_device(y))[xp_ravel(i)] + # The difference between integer indices is the number of repeats + append = xp.full(indices.shape[:-1] + (1,), xp_size(y), dtype=indices.dtype) + counts = xp.diff(indices, append=append) + # Now we form an array of `counts` corresponding with each element of `y`... + counts = xp.reshape(xp.repeat(counts, counts), y.shape) + # ... so we can get the argmax of *each slice* separately. + k = xp.argmax(counts, axis=-1, keepdims=True) + # Extract the corresponding element/count, and eliminate the reduced dimension + modes = xp.take_along_axis(y, k, axis=-1)[..., 0] + counts = xp.take_along_axis(counts, k, axis=-1)[..., 0] + modes = modes[()] if modes.ndim == 0 else modes + counts = counts[()] if counts.ndim == 0 else counts + return ModeResult(modes, counts) + + +def _put_val_to_limits(a, limits, inclusive, val=np.nan, xp=None): + """Replace elements outside limits with a value. + + This is primarily a utility function. + + Parameters + ---------- + a : array + limits : (float or None, float or None) + A tuple consisting of the (lower limit, upper limit). Elements in the + input array less than the lower limit or greater than the upper limit + will be replaced with `val`. None implies no limit. + inclusive : (bool, bool) + A tuple consisting of the (lower flag, upper flag). These flags + determine whether values exactly equal to lower or upper are allowed. + val : float, default: NaN + The value with which extreme elements of the array are replaced. + + """ + xp = array_namespace(a) if xp is None else xp + mask = xp.zeros_like(a, dtype=xp.bool) + if limits is None: + return a, mask + lower_limit, upper_limit = limits + lower_include, upper_include = inclusive + if lower_limit is not None: + mask = mask | ((a < lower_limit) if lower_include else a <= lower_limit) + if upper_limit is not None: + mask = mask | ((a > upper_limit) if upper_include else a >= upper_limit) + lazy = is_lazy_array(mask) + if not lazy and xp.all(mask): + raise ValueError("No array values within given limits") + if lazy or xp.any(mask): + a = xp.where(mask, val, a) + return a, mask + + +@xp_capabilities(marray=True) +@_axis_nan_policy_factory( + lambda x: x, n_outputs=1, default_axis=None, + result_to_tuple=lambda x, _: (x,) +) +def tmean(a, limits=None, inclusive=(True, True), axis=None): + """Compute the trimmed mean. + + This function finds the arithmetic mean of given values, ignoring values + outside the given `limits`. + + Parameters + ---------- + a : array_like + Array of values. + limits : None or (lower limit, upper limit), optional + Values in the input array less than the lower limit or greater than the + upper limit will be ignored. When limits is None (default), then all + values are used. Either of the limit values in the tuple can also be + None representing a half-open interval. + inclusive : (bool, bool), optional + A tuple consisting of the (lower flag, upper flag). These flags + determine whether values exactly equal to the lower or upper limits + are included. The default value is (True, True). + axis : int or None, optional + Axis along which to compute test. Default is None. + + Returns + ------- + tmean : ndarray + Trimmed mean. + + See Also + -------- + trim_mean : Returns mean after trimming a proportion from both tails. + + Examples + -------- + >>> import numpy as np + >>> from scipy import stats + >>> x = np.arange(20) + >>> stats.tmean(x) + 9.5 + >>> stats.tmean(x, (3,17)) + 10.0 + + """ + xp = array_namespace(a) + a, mask = _put_val_to_limits(a, limits, inclusive, val=0., xp=xp) + # explicit dtype specification required due to data-apis/array-api-compat#152 + sum = xp.sum(a, axis=axis, dtype=a.dtype) + n = xp.sum(xp.asarray(~mask, dtype=a.dtype, device=xp_device(a)), axis=axis, + dtype=a.dtype) + mean = xpx.apply_where(n != 0, (sum, n), operator.truediv, fill_value=xp.nan) + return mean[()] if mean.ndim == 0 else mean + + +@xp_capabilities(marray=True) +@_axis_nan_policy_factory( + lambda x: x, n_outputs=1, result_to_tuple=lambda x, _: (x,) +) +def tvar(a, limits=None, inclusive=(True, True), axis=0, ddof=1): + """Compute the trimmed variance. + + This function computes the sample variance of an array of values, + while ignoring values which are outside of given `limits`. + + Parameters + ---------- + a : array_like + Array of values. + limits : None or (lower limit, upper limit), optional + Values in the input array less than the lower limit or greater than the + upper limit will be ignored. When limits is None, then all values are + used. Either of the limit values in the tuple can also be None + representing a half-open interval. The default value is None. + inclusive : (bool, bool), optional + A tuple consisting of the (lower flag, upper flag). These flags + determine whether values exactly equal to the lower or upper limits + are included. The default value is (True, True). + axis : int or None, optional + Axis along which to operate. Default is 0. If None, compute over the + whole array `a`. + ddof : int, optional + Delta degrees of freedom. Default is 1. + + Returns + ------- + tvar : float + Trimmed variance. + + Notes + ----- + `tvar` computes the unbiased sample variance, i.e. it uses a correction + factor ``n / (n - 1)``. + + Examples + -------- + >>> import numpy as np + >>> from scipy import stats + >>> x = np.arange(20) + >>> stats.tvar(x) + 35.0 + >>> stats.tvar(x, (3,17)) + 20.0 + + """ + xp = array_namespace(a) + a, _ = _put_val_to_limits(a, limits, inclusive, xp=xp) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", SmallSampleWarning) + # Currently, this behaves like nan_policy='omit' for alternative array + # backends, but nan_policy='propagate' will be handled for other backends + # by the axis_nan_policy decorator shortly. + return _xp_var(a, correction=ddof, axis=axis, nan_policy='omit', xp=xp) + + +@xp_capabilities(marray=True) +@_axis_nan_policy_factory( + lambda x: x, n_outputs=1, result_to_tuple=lambda x, _: (x,) +) +def tmin(a, lowerlimit=None, axis=0, inclusive=True, nan_policy='propagate'): + """Compute the trimmed minimum. + + This function finds the minimum value of an array `a` along the + specified axis, but only considering values greater than a specified + lower limit. + + Parameters + ---------- + a : array_like + Array of values. + lowerlimit : None or float, optional + Values in the input array less than the given limit will be ignored. + When lowerlimit is None, then all values are used. The default value + is None. + axis : int or None, optional + Axis along which to operate. Default is 0. If None, compute over the + whole array `a`. + inclusive : {True, False}, optional + This flag determines whether values exactly equal to the lower limit + are included. The default value is True. + + Returns + ------- + tmin : float, int or ndarray + Trimmed minimum. + + Examples + -------- + >>> import numpy as np + >>> from scipy import stats + >>> x = np.arange(20) + >>> stats.tmin(x) + 0 + + >>> stats.tmin(x, 13) + 13 + + >>> stats.tmin(x, 13, inclusive=False) + 14 + + """ + xp = array_namespace(a) + + max_ = xp.iinfo(a.dtype).max if xp.isdtype(a.dtype, 'integral') else xp.inf + a, mask = _put_val_to_limits(a, (lowerlimit, None), (inclusive, None), + val=max_, xp=xp) + + res = xp.min(a, axis=axis) + invalid = xp.all(mask, axis=axis) # All elements are below lowerlimit + + # For eager backends, output dtype is data-dependent + if is_lazy_array(invalid) or xp.any(invalid): + # Possible loss of precision for int types + res = xp_promote(res, force_floating=True, xp=xp) + res = xp.where(invalid, xp.nan, res) + + return res[()] if res.ndim == 0 else res + + +@xp_capabilities(marray=True) +@_axis_nan_policy_factory( + lambda x: x, n_outputs=1, result_to_tuple=lambda x, _: (x,) +) +def tmax(a, upperlimit=None, axis=0, inclusive=True, nan_policy='propagate'): + """Compute the trimmed maximum. + + This function computes the maximum value of an array along a given axis, + while ignoring values larger than a specified upper limit. + + Parameters + ---------- + a : array_like + Array of values. + upperlimit : None or float, optional + Values in the input array greater than the given limit will be ignored. + When upperlimit is None, then all values are used. The default value + is None. + axis : int or None, optional + Axis along which to operate. Default is 0. If None, compute over the + whole array `a`. + inclusive : {True, False}, optional + This flag determines whether values exactly equal to the upper limit + are included. The default value is True. + + Returns + ------- + tmax : float, int or ndarray + Trimmed maximum. + + Examples + -------- + >>> import numpy as np + >>> from scipy import stats + >>> x = np.arange(20) + >>> stats.tmax(x) + 19 + + >>> stats.tmax(x, 13) + 13 + + >>> stats.tmax(x, 13, inclusive=False) + 12 + + """ + xp = array_namespace(a) + + min_ = xp.iinfo(a.dtype).min if xp.isdtype(a.dtype, 'integral') else -xp.inf + a, mask = _put_val_to_limits(a, (None, upperlimit), (None, inclusive), + val=min_, xp=xp) + + res = xp.max(a, axis=axis) + invalid = xp.all(mask, axis=axis) # All elements are above upperlimit + + # For eager backends, output dtype is data-dependent + if is_lazy_array(invalid) or xp.any(invalid): + # Possible loss of precision for int types + res = xp_promote(res, force_floating=True, xp=xp) + res = xp.where(invalid, xp.nan, res) + + return res[()] if res.ndim == 0 else res + + +@xp_capabilities(marray=True) +@_axis_nan_policy_factory( + lambda x: x, n_outputs=1, result_to_tuple=lambda x, _: (x,) +) +def tstd(a, limits=None, inclusive=(True, True), axis=0, ddof=1): + """Compute the trimmed sample standard deviation. + + This function finds the sample standard deviation of given values, + ignoring values outside the given `limits`. + + Parameters + ---------- + a : array_like + Array of values. + limits : None or (lower limit, upper limit), optional + Values in the input array less than the lower limit or greater than the + upper limit will be ignored. When limits is None, then all values are + used. Either of the limit values in the tuple can also be None + representing a half-open interval. The default value is None. + inclusive : (bool, bool), optional + A tuple consisting of the (lower flag, upper flag). These flags + determine whether values exactly equal to the lower or upper limits + are included. The default value is (True, True). + axis : int or None, optional + Axis along which to operate. Default is 0. If None, compute over the + whole array `a`. + ddof : int, optional + Delta degrees of freedom. Default is 1. + + Returns + ------- + tstd : float + Trimmed sample standard deviation. + + Notes + ----- + `tstd` computes the unbiased sample standard deviation, i.e. it uses a + correction factor ``n / (n - 1)``. + + Examples + -------- + >>> import numpy as np + >>> from scipy import stats + >>> x = np.arange(20) + >>> stats.tstd(x) + 5.9160797830996161 + >>> stats.tstd(x, (3,17)) + 4.4721359549995796 + + """ + return tvar(a, limits, inclusive, axis, ddof, _no_deco=True)**0.5 + + +@xp_capabilities(marray=True) +@_axis_nan_policy_factory( + lambda x: x, n_outputs=1, result_to_tuple=lambda x, _: (x,) +) +def tsem(a, limits=None, inclusive=(True, True), axis=0, ddof=1): + """Compute the trimmed standard error of the mean. + + This function finds the standard error of the mean for given + values, ignoring values outside the given `limits`. + + Parameters + ---------- + a : array_like + Array of values. + limits : None or (lower limit, upper limit), optional + Values in the input array less than the lower limit or greater than the + upper limit will be ignored. When limits is None, then all values are + used. Either of the limit values in the tuple can also be None + representing a half-open interval. The default value is None. + inclusive : (bool, bool), optional + A tuple consisting of the (lower flag, upper flag). These flags + determine whether values exactly equal to the lower or upper limits + are included. The default value is (True, True). + axis : int or None, optional + Axis along which to operate. Default is 0. If None, compute over the + whole array `a`. + ddof : int, optional + Delta degrees of freedom. Default is 1. + + Returns + ------- + tsem : float + Trimmed standard error of the mean. + + Notes + ----- + `tsem` uses unbiased sample standard deviation, i.e. it uses a + correction factor ``n / (n - 1)``. + + Examples + -------- + >>> import numpy as np + >>> from scipy import stats + >>> x = np.arange(20) + >>> stats.tsem(x) + 1.3228756555322954 + >>> stats.tsem(x, (3,17)) + 1.1547005383792515 + + """ + xp = array_namespace(a) + a, _ = _put_val_to_limits(a, limits, inclusive, xp=xp) + + with warnings.catch_warnings(): + warnings.simplefilter("ignore", SmallSampleWarning) + # Currently, this behaves like nan_policy='omit' for alternative array + # backends, but nan_policy='propagate' will be handled for other backends + # by the axis_nan_policy decorator shortly. + sd = _xp_var(a, correction=ddof, axis=axis, nan_policy='omit', xp=xp)**0.5 + + not_nan = xp.astype(~xp.isnan(a), a.dtype) + n_obs = xp.sum(not_nan, axis=axis, dtype=sd.dtype) + return sd / n_obs**0.5 + + +##################################### +# MOMENTS # +##################################### + + +def _moment_outputs(kwds, default_order=1): + order = np.atleast_1d(kwds.get('order', default_order)) + message = "`order` must be a scalar or a non-empty 1D array." + if order.size == 0 or order.ndim > 1: + raise ValueError(message) + return len(order) + + +def _moment_result_object(*args): + if len(args) == 1: + return args[0] + xp = array_namespace(*args) + return xp.stack(args) + + +# When `order` is array-like with size > 1, moment produces an *array* +# rather than a tuple, but the zeroth dimension is to be treated like +# separate outputs. It is important to make the distinction between +# separate outputs when adding the reduced axes back (`keepdims=True`). +def _moment_tuple(x, n_out): + return tuple(x[i, ...] for i in range(x.shape[0])) if n_out > 1 else (x,) + + +# `moment` fits into the `_axis_nan_policy` pattern, but it is a bit unusual +# because the number of outputs is variable. Specifically, +# `result_to_tuple=lambda x: (x,)` may be surprising for a function that +# can produce more than one output, but it is intended here. +# When `moment is called to produce the output: +# - `result_to_tuple` packs the returned array into a single-element tuple, +# - `_moment_result_object` extracts and returns that single element. +# However, when the input array is empty, `moment` is never called. Instead, +# - `_check_empty_inputs` is used to produce an empty array with the +# appropriate dimensions. +# - A list comprehension creates the appropriate number of copies of this +# array, depending on `n_outputs`. +# - This list - which may have multiple elements - is passed into +# `_moment_result_object`. +# - If there is a single output, `_moment_result_object` extracts and returns +# the single output from the list. +# - If there are multiple outputs, and therefore multiple elements in the list, +# `_moment_result_object` converts the list of arrays to a single array and +# returns it. +# Currently, this leads to a slight inconsistency: when the input array is +# empty, there is no distinction between the `moment` function being called +# with parameter `order=1` and `order=[1]`; the latter *should* produce +# the same as the former but with a singleton zeroth dimension. +@xp_capabilities(skip_backends=[('dask.array', 'needs axis_nan_policy decorator')], + marray=True) +@_rename_parameter('moment', 'order') +@_axis_nan_policy_factory( # noqa: E302 + _moment_result_object, n_samples=1, result_to_tuple=_moment_tuple, + n_outputs=_moment_outputs +) +def moment(a, order=1, axis=0, nan_policy='propagate', *, center=None): + r"""Calculate the nth moment about the mean for a sample. + + A moment is a specific quantitative measure of the shape of a set of + points. It is often used to calculate coefficients of skewness and kurtosis + due to its close relationship with them. + + Parameters + ---------- + a : array_like + Input array. + order : int or 1-D array_like of ints, optional + Order of central moment that is returned. Default is 1. + axis : int or None, optional + Axis along which the central moment is computed. Default is 0. + If None, compute over the whole array `a`. + nan_policy : {'propagate', 'raise', 'omit'}, optional + Defines how to handle when input contains nan. + The following options are available (default is 'propagate'): + + * 'propagate': returns nan + * 'raise': throws an error + * 'omit': performs the calculations ignoring nan values + + center : float or None, optional + The point about which moments are taken. This can be the sample mean, + the origin, or any other be point. If `None` (default) compute the + center as the sample mean. + + Returns + ------- + n-th moment about the `center` : ndarray or float + The appropriate moment along the given axis or over all values if axis + is None. The denominator for the moment calculation is the number of + observations, no degrees of freedom correction is done. + + See Also + -------- + kurtosis, skew, describe + + Notes + ----- + The k-th moment of a data sample is: + + .. math:: + + m_k = \frac{1}{n} \sum_{i = 1}^n (x_i - c)^k + + Where `n` is the number of samples, and `c` is the center around which the + moment is calculated. This function uses exponentiation by squares [1]_ for + efficiency. + + Note that, if `a` is an empty array (``a.size == 0``), array `moment` with + one element (`moment.size == 1`) is treated the same as scalar `moment` + (``np.isscalar(moment)``). This might produce arrays of unexpected shape. + + References + ---------- + .. [1] https://eli.thegreenplace.net/2009/03/21/efficient-integer-exponentiation-algorithms + + Examples + -------- + >>> from scipy.stats import moment + >>> moment([1, 2, 3, 4, 5], order=1) + 0.0 + >>> moment([1, 2, 3, 4, 5], order=2) + 2.0 + + """ + xp = array_namespace(a) + a, center, order = xp_promote(a, center, order, force_floating=True, xp=xp) + + if not is_lazy_array(order) and xp.any(order != xp.round(order)): + raise ValueError("All elements of `order` must be integral.") + + # _axis_nan_policy decorator ensures that axis=-1 + if order.ndim > 0: + order = xp.reshape(order, (-1,) + (1,)*a.ndim) + return _moment(a, order, axis=-1, center=center) + else: + res = _moment(a, order, axis=-1, center=center) + return res[()] if res.ndim == 0 else res + + +def _demean(a, mean, axis, *, xp, precision_warning=True): + # subtracts `mean` from `a` and returns the result, + # warning if there is catastrophic cancellation. `mean` + # must be the mean of `a` along axis with `keepdims=True`. + # Used in e.g. `_moment`, `_zscore`, `_xp_var`. See gh-15905. + a_zero_mean = a - mean + + if (xp_size(a_zero_mean) == 0 or not precision_warning + or is_lazy_array(a_zero_mean)): + return a_zero_mean + + eps = xp.finfo(mean.dtype).eps * 10 + + with np.errstate(divide='ignore', invalid='ignore'): + rel_diff = xp.max(xp.abs(a_zero_mean), axis=axis, + keepdims=True) / xp.abs(mean) + + n = _count_nonmasked(a, axis, xp=xp) + with np.errstate(invalid='ignore'): + device = {'device': xp_device(a)} + precision_loss = xp.any(xp.asarray(rel_diff < eps, **device) + & xp.asarray(n > 1, **device)) + + if precision_loss: + message = ("Precision loss occurred in moment calculation due to " + "catastrophic cancellation. This occurs when the data " + "are nearly identical. Results may be unreliable.") + warnings.warn(message, RuntimeWarning, stacklevel=5) + return a_zero_mean + + +def _moment(a, order, axis, *, center=None, xp=None): + """Vectorized calculation of raw moment about specified center + + When `center` is None, the mean is computed and used as the center; + otherwise, the provided value is used as the center. + """ + xp = array_namespace(a) if xp is None else xp + + order = xp.asarray(order, dtype=a.dtype, device=xp_device(a)) + order_0 = order == 0 + order_1 = (order == 1) & (center is None) + center = xp.mean(a, axis=axis, keepdims=True) if center is None else center + a_zero_mean = _demean(a, center, axis, xp=xp) + res = xp.mean(a_zero_mean**order, axis=axis, keepdims=True) + if a.shape[-1] > 0 and (is_lazy_array(res) + or xp.any(order_0) or xp.any(order_1)): + res = xp.where(order_0, xp.ones_like(res), res) + res = xp.where(order_1, xp.zeros_like(res), res) + + return xp.squeeze(res, axis=axis) + + +def _var(x, axis=0, ddof=0, mean=None, xp=None): + # Calculate variance of sample, warning if precision is lost + xp = array_namespace(x) if xp is None else xp + var = _moment(x, 2, axis, center=mean, xp=xp) + if ddof != 0: + n = _count_nonmasked(x, axis, xp=xp) + n = xp.asarray(n, dtype=x.dtype, device=xp_device(x)) + var *= (n / (n-ddof)) # to avoid error on division by zero + return var + + +@xp_capabilities(marray=True) +@_axis_nan_policy_factory( + lambda x: x, result_to_tuple=lambda x, _: (x,), n_outputs=1 +) +# nan_policy handled by `_axis_nan_policy`, but needs to be left +# in signature to preserve use as a positional argument +def skew(a, axis=0, bias=True, nan_policy='propagate'): + r"""Compute the sample skewness of a data set. + + For normally distributed data, the skewness should be about zero. For + unimodal continuous distributions, a skewness value greater than zero means + that there is more weight in the right tail of the distribution. The + function `skewtest` can be used to determine if the skewness value + is close enough to zero, statistically speaking. + + Parameters + ---------- + a : ndarray + Input array. + axis : int or None, optional + Axis along which skewness is calculated. Default is 0. + If None, compute over the whole array `a`. + bias : bool, optional + If False, then the calculations are corrected for statistical bias. + nan_policy : {'propagate', 'raise', 'omit'}, optional + Defines how to handle when input contains nan. + The following options are available (default is 'propagate'): + + * 'propagate': returns nan + * 'raise': throws an error + * 'omit': performs the calculations ignoring nan values + + Returns + ------- + skewness : ndarray + The skewness of values along an axis, returning NaN where all values + are equal. + + Notes + ----- + The sample skewness is computed as the Fisher-Pearson coefficient + of skewness, i.e. + + .. math:: + + g_1=\frac{m_3}{m_2^{3/2}} + + where + + .. math:: + + m_i=\frac{1}{N}\sum_{n=1}^N(x[n]-\bar{x})^i + + is the biased sample :math:`i\texttt{th}` central moment, and + :math:`\bar{x}` is + the sample mean. If ``bias`` is False, the calculations are + corrected for bias and the value computed is the adjusted + Fisher-Pearson standardized moment coefficient, i.e. + + .. math:: + + G_1=\frac{k_3}{k_2^{3/2}}= + \frac{\sqrt{N(N-1)}}{N-2}\frac{m_3}{m_2^{3/2}}. + + References + ---------- + .. [1] Zwillinger, D. and Kokoska, S. (2000). CRC Standard + Probability and Statistics Tables and Formulae. Chapman & Hall: New + York. 2000. + Section 2.2.24.1 + + Examples + -------- + >>> from scipy.stats import skew + >>> skew([1, 2, 3, 4, 5]) + 0.0 + >>> skew([2, 8, 0, 4, 1, 9, 9, 0]) + 0.2650554122698573 + + """ + xp = array_namespace(a) + a, axis = _chk_asarray(a, axis, xp=xp) + n = _count_nonmasked(a, axis, xp=xp) + + mean = xp.mean(a, axis=axis, keepdims=True) + mean_reduced = xp.squeeze(mean, axis=axis) # needed later + m2 = _moment(a, 2, axis, center=mean, xp=xp) + m3 = _moment(a, 3, axis, center=mean, xp=xp) + with np.errstate(all='ignore'): + eps = xp.finfo(m2.dtype).eps + zero = m2 <= (eps * mean_reduced)**2 + vals = xp.where(zero, xp.nan, m3 / m2**1.5) + if not bias: + can_correct = ~zero & (n > 2) + if is_lazy_array(can_correct) or xp.any(can_correct): + nval = ((n - 1.0) * n)**0.5 / (n - 2.0) * m3 / m2**1.5 + vals = xp.where(can_correct, nval, vals) + + return vals[()] if vals.ndim == 0 else vals + + +@xp_capabilities(marray=True) +@_axis_nan_policy_factory( + lambda x: x, result_to_tuple=lambda x, _: (x,), n_outputs=1 +) +# nan_policy handled by `_axis_nan_policy`, but needs to be left +# in signature to preserve use as a positional argument +def kurtosis(a, axis=0, fisher=True, bias=True, nan_policy='propagate'): + """Compute the kurtosis (Fisher or Pearson) of a dataset. + + Kurtosis is the fourth central moment divided by the square of the + variance. If Fisher's definition is used, then 3.0 is subtracted from + the result to give 0.0 for a normal distribution. + + If bias is False then the kurtosis is calculated using k statistics to + eliminate bias coming from biased moment estimators + + Use `kurtosistest` to see if result is close enough to normal. + + Parameters + ---------- + a : array + Data for which the kurtosis is calculated. + axis : int or None, optional + Axis along which the kurtosis is calculated. Default is 0. + If None, compute over the whole array `a`. + fisher : bool, optional + If True, Fisher's definition is used (normal ==> 0.0). If False, + Pearson's definition is used (normal ==> 3.0). + bias : bool, optional + If False, then the calculations are corrected for statistical bias. + nan_policy : {'propagate', 'raise', 'omit'}, optional + Defines how to handle when input contains nan. 'propagate' returns nan, + 'raise' throws an error, 'omit' performs the calculations ignoring nan + values. Default is 'propagate'. + + Returns + ------- + kurtosis : array + The kurtosis of values along an axis, returning NaN where all values + are equal. + + References + ---------- + .. [1] Zwillinger, D. and Kokoska, S. (2000). CRC Standard + Probability and Statistics Tables and Formulae. Chapman & Hall: New + York. 2000. + + Examples + -------- + In Fisher's definition, the kurtosis of the normal distribution is zero. + In the following example, the kurtosis is close to zero, because it was + calculated from the dataset, not from the continuous distribution. + + >>> import numpy as np + >>> from scipy.stats import norm, kurtosis + >>> data = norm.rvs(size=1000, random_state=3) + >>> kurtosis(data) + -0.06928694200380558 + + The distribution with a higher kurtosis has a heavier tail. + The zero valued kurtosis of the normal distribution in Fisher's definition + can serve as a reference point. + + >>> import matplotlib.pyplot as plt + >>> import scipy.stats as stats + >>> from scipy.stats import kurtosis + + >>> x = np.linspace(-5, 5, 100) + >>> ax = plt.subplot() + >>> distnames = ['laplace', 'norm', 'uniform'] + + >>> for distname in distnames: + ... if distname == 'uniform': + ... dist = getattr(stats, distname)(loc=-2, scale=4) + ... else: + ... dist = getattr(stats, distname) + ... data = dist.rvs(size=1000) + ... kur = kurtosis(data, fisher=True) + ... y = dist.pdf(x) + ... ax.plot(x, y, label="{}, {}".format(distname, round(kur, 3))) + ... ax.legend() + + The Laplace distribution has a heavier tail than the normal distribution. + The uniform distribution (which has negative kurtosis) has the thinnest + tail. + + """ + xp = array_namespace(a) + a, axis = _chk_asarray(a, axis, xp=xp) + + n = _count_nonmasked(a, axis, xp=xp) + mean = xp.mean(a, axis=axis, keepdims=True) + mean_reduced = xp.squeeze(mean, axis=axis) # needed later + m2 = _moment(a, 2, axis, center=mean, xp=xp) + m4 = _moment(a, 4, axis, center=mean, xp=xp) + with np.errstate(all='ignore'): + zero = m2 <= (xp.finfo(m2.dtype).eps * mean_reduced)**2 + vals = xp.where(zero, xp.nan, m4 / m2**2.0) + + if not bias: + can_correct = ~zero & (n > 3) + if is_lazy_array(can_correct) or xp.any(can_correct): + nval = 1.0/(n-2)/(n-3) * ((n**2-1.0)*m4/m2**2.0 - 3*(n-1)**2.0) + vals = xp.where(can_correct, nval + 3.0, vals) + + vals = vals - 3 if fisher else vals + return vals[()] if vals.ndim == 0 else vals + + +DescribeResult = namedtuple('DescribeResult', + ('nobs', 'minmax', 'mean', 'variance', 'skewness', + 'kurtosis')) + + +@xp_capabilities(marray=True) +def describe(a, axis=0, ddof=1, bias=True, nan_policy='propagate'): + """Compute several descriptive statistics of the passed array. + + Parameters + ---------- + a : array_like + Input data. + axis : int or None, optional + Axis along which statistics are calculated. Default is 0. + If None, compute over the whole array `a`. + ddof : int, optional + Delta degrees of freedom (only for variance). Default is 1. + bias : bool, optional + If False, then the skewness and kurtosis calculations are corrected + for statistical bias. + nan_policy : {'propagate', 'raise', 'omit'}, optional + Defines how to handle when input contains nan. + The following options are available (default is 'propagate'): + + * 'propagate': returns nan + * 'raise': throws an error + * 'omit': performs the calculations ignoring nan values + + Returns + ------- + nobs : int or ndarray of ints + Number of observations (length of data along `axis`). + When 'omit' is chosen as nan_policy, the length along each axis + slice is counted separately. + minmax: tuple of ndarrays or floats + Minimum and maximum value of `a` along the given axis. + mean : ndarray or float + Arithmetic mean of `a` along the given axis. + variance : ndarray or float + Unbiased variance of `a` along the given axis; denominator is number + of observations minus one. + skewness : ndarray or float + Skewness of `a` along the given axis, based on moment calculations + with denominator equal to the number of observations, i.e. no degrees + of freedom correction. + kurtosis : ndarray or float + Kurtosis (Fisher) of `a` along the given axis. The kurtosis is + normalized so that it is zero for the normal distribution. No + degrees of freedom are used. + + Raises + ------ + ValueError + If size of `a` is 0. + + See Also + -------- + skew, kurtosis + + Examples + -------- + >>> import numpy as np + >>> from scipy import stats + >>> a = np.arange(10) + >>> stats.describe(a) + DescribeResult(nobs=10, minmax=(0, 9), mean=4.5, + variance=9.166666666666666, skewness=0.0, + kurtosis=-1.2242424242424244) + >>> b = [[1, 2], [3, 4]] + >>> stats.describe(b) + DescribeResult(nobs=2, minmax=(array([1, 2]), array([3, 4])), + mean=array([2., 3.]), variance=array([2., 2.]), + skewness=array([0., 0.]), kurtosis=array([-2., -2.])) + + """ + xp = array_namespace(a) + a, axis = _chk_asarray(a, axis, xp=xp) + + contains_nan = _contains_nan(a, nan_policy) + + # Test nan_policy before the implicit call to bool(contains_nan) + # to avoid raising on lazy xps on the default nan_policy='propagate' + if nan_policy == 'omit' and contains_nan: + # only NumPy gets here; `_contains_nan` raises error for the rest + a = ma.masked_invalid(a) + return mstats_basic.describe(a, axis, ddof, bias) + + if xp_size(a) == 0: + raise ValueError("The input must not be empty.") + + # use xp.astype when data-apis/array-api-compat#226 is resolved + n = xp.asarray(_count_nonmasked(a, axis, xp=xp), dtype=xp.int64, + device=xp_device(a)) + n = n[()] if n.ndim == 0 else n + mm = (xp.min(a, axis=axis), xp.max(a, axis=axis)) + a = xp_promote(a, force_floating=True, xp=xp) + m = xp.mean(a, axis=axis) + v = _var(a, axis=axis, ddof=ddof, xp=xp) + v = v[()] if v.ndim == 0 else v + sk = skew(a, axis, bias=bias) + kurt = kurtosis(a, axis, bias=bias) + + return DescribeResult(n, mm, m, v, sk, kurt) + +##################################### +# NORMALITY TESTS # +##################################### + + +def _get_pvalue(statistic, distribution, alternative, symmetric=True, xp=None): + """Get p-value given the statistic, (continuous) distribution, and alternative""" + xp = array_namespace(statistic) if xp is None else xp + + if alternative == 'less': + pvalue = distribution.cdf(statistic) + elif alternative == 'greater': + pvalue = distribution.sf(statistic) + elif alternative == 'two-sided': + pvalue = 2 * (distribution.sf(xp.abs(statistic)) if symmetric + else xp.minimum(distribution.cdf(statistic), + distribution.sf(statistic))) + else: + message = "`alternative` must be 'less', 'greater', or 'two-sided'." + raise ValueError(message) + + return pvalue + + +SkewtestResult = namedtuple('SkewtestResult', ('statistic', 'pvalue')) + + +@xp_capabilities(marray=True) +@_axis_nan_policy_factory(SkewtestResult, n_samples=1, too_small=7) +# nan_policy handled by `_axis_nan_policy`, but needs to be left +# in signature to preserve use as a positional argument +def skewtest(a, axis=0, nan_policy='propagate', alternative='two-sided'): + r"""Test whether the skew is different from the normal distribution. + + This function tests the null hypothesis that the skewness of + the population that the sample was drawn from is the same + as that of a corresponding normal distribution. + + Parameters + ---------- + a : array + The data to be tested. Must contain at least eight observations. + axis : int or None, optional + Axis along which statistics are calculated. Default is 0. + If None, compute over the whole array `a`. + nan_policy : {'propagate', 'raise', 'omit'}, optional + Defines how to handle when input contains nan. + The following options are available (default is 'propagate'): + + * 'propagate': returns nan + * 'raise': throws an error + * 'omit': performs the calculations ignoring nan values + + alternative : {'two-sided', 'less', 'greater'}, optional + Defines the alternative hypothesis. Default is 'two-sided'. + The following options are available: + + * 'two-sided': the skewness of the distribution underlying the sample + is different from that of the normal distribution (i.e. 0) + * 'less': the skewness of the distribution underlying the sample + is less than that of the normal distribution + * 'greater': the skewness of the distribution underlying the sample + is greater than that of the normal distribution + + .. versionadded:: 1.7.0 + + Returns + ------- + statistic : float + The computed z-score for this test. + pvalue : float + The p-value for the hypothesis test. + + See Also + -------- + :ref:`hypothesis_skewtest` : Extended example + + Notes + ----- + The sample size must be at least 8. + + References + ---------- + .. [1] R. B. D'Agostino, A. J. Belanger and R. B. D'Agostino Jr., + "A suggestion for using powerful and informative tests of + normality", American Statistician 44, pp. 316-321, 1990. + + Examples + -------- + + >>> from scipy.stats import skewtest + >>> skewtest([1, 2, 3, 4, 5, 6, 7, 8]) + SkewtestResult(statistic=1.0108048609177787, pvalue=0.3121098361421897) + >>> skewtest([2, 8, 0, 4, 1, 9, 9, 0]) + SkewtestResult(statistic=0.44626385374196975, pvalue=0.6554066631275459) + >>> skewtest([1, 2, 3, 4, 5, 6, 7, 8000]) + SkewtestResult(statistic=3.571773510360407, pvalue=0.0003545719905823133) + >>> skewtest([100, 100, 100, 100, 100, 100, 100, 101]) + SkewtestResult(statistic=3.5717766638478072, pvalue=0.000354567720281634) + >>> skewtest([1, 2, 3, 4, 5, 6, 7, 8], alternative='less') + SkewtestResult(statistic=1.0108048609177787, pvalue=0.8439450819289052) + >>> skewtest([1, 2, 3, 4, 5, 6, 7, 8], alternative='greater') + SkewtestResult(statistic=1.0108048609177787, pvalue=0.15605491807109484) + + For a more detailed example, see :ref:`hypothesis_skewtest`. + """ + xp = array_namespace(a) + a, axis = _chk_asarray(a, axis, xp=xp) + + b2 = skew(a, axis, _no_deco=True) + + n = xp.asarray(_count_nonmasked(a, axis), dtype=b2.dtype, device=xp_device(a)) + n = xpx.at(n, n < 8).set(xp.nan) + + with np.errstate(divide='ignore', invalid='ignore'): + y = b2 * xp.sqrt(((n + 1) * (n + 3)) / (6.0 * (n - 2))) + beta2 = (3.0 * (n**2 + 27*n - 70) * (n+1) * (n+3) / + ((n-2.0) * (n+5) * (n+7) * (n+9))) + W2 = -1 + xp.sqrt(2 * (beta2 - 1)) + delta = 1 / xp.sqrt(0.5 * xp.log(W2)) + alpha = xp.sqrt(2.0 / (W2 - 1)) + y = xp.where(y == 0, 1., y) + Z = delta * xp.log(y / alpha + xp.sqrt((y / alpha)**2 + 1)) + pvalue = _get_pvalue(Z, _SimpleNormal(), alternative, xp=xp) + + Z = Z[()] if Z.ndim == 0 else Z + pvalue = pvalue[()] if pvalue.ndim == 0 else pvalue + return SkewtestResult(Z, pvalue) + + +KurtosistestResult = namedtuple('KurtosistestResult', ('statistic', 'pvalue')) + + +@xp_capabilities(marray=True) +@_axis_nan_policy_factory(KurtosistestResult, n_samples=1, too_small=4) +def kurtosistest(a, axis=0, nan_policy='propagate', alternative='two-sided'): + r"""Test whether a dataset has normal kurtosis. + + This function tests the null hypothesis that the kurtosis + of the population from which the sample was drawn is that + of the normal distribution. + + Parameters + ---------- + a : array + Array of the sample data. Must contain at least five observations. + axis : int or None, optional + Axis along which to compute test. Default is 0. If None, + compute over the whole array `a`. + nan_policy : {'propagate', 'raise', 'omit'}, optional + Defines how to handle when input contains nan. + The following options are available (default is 'propagate'): + + * 'propagate': returns nan + * 'raise': throws an error + * 'omit': performs the calculations ignoring nan values + + alternative : {'two-sided', 'less', 'greater'}, optional + Defines the alternative hypothesis. + The following options are available (default is 'two-sided'): + + * 'two-sided': the kurtosis of the distribution underlying the sample + is different from that of the normal distribution + * 'less': the kurtosis of the distribution underlying the sample + is less than that of the normal distribution + * 'greater': the kurtosis of the distribution underlying the sample + is greater than that of the normal distribution + + .. versionadded:: 1.7.0 + + Returns + ------- + statistic : float + The computed z-score for this test. + pvalue : float + The p-value for the hypothesis test. + + See Also + -------- + :ref:`hypothesis_kurtosistest` : Extended example + + Notes + ----- + Valid only for n>20. This function uses the method described in [1]_. + + References + ---------- + .. [1] F. J. Anscombe, W. J. Glynn, "Distribution of the kurtosis + statistic b2 for normal samples", Biometrika, vol. 70, pp. 227-234, 1983. + + Examples + -------- + + >>> import numpy as np + >>> from scipy.stats import kurtosistest + >>> kurtosistest(list(range(20))) + KurtosistestResult(statistic=-1.7058104152122062, pvalue=0.08804338332528348) + >>> kurtosistest(list(range(20)), alternative='less') + KurtosistestResult(statistic=-1.7058104152122062, pvalue=0.04402169166264174) + >>> kurtosistest(list(range(20)), alternative='greater') + KurtosistestResult(statistic=-1.7058104152122062, pvalue=0.9559783083373583) + >>> rng = np.random.default_rng() + >>> s = rng.normal(0, 1, 1000) + >>> kurtosistest(s) + KurtosistestResult(statistic=-1.475047944490622, pvalue=0.14019965402996987) + + For a more detailed example, see :ref:`hypothesis_kurtosistest`. + """ + xp = array_namespace(a) + a, axis = _chk_asarray(a, axis, xp=xp) + + b2 = kurtosis(a, axis, fisher=False, _no_deco=True) + + n = xp.asarray(_count_nonmasked(a, axis), dtype=b2.dtype, device=xp_device(a)) + n = xpx.at(n, n < 5).set(xp.nan) + + E = 3.0*(n-1) / (n+1) + varb2 = 24.0*n*(n-2)*(n-3) / ((n+1)*(n+1.)*(n+3)*(n+5)) # [1]_ Eq. 1 + x = (b2-E) / varb2**0.5 # [1]_ Eq. 4 + # [1]_ Eq. 2: + sqrtbeta1 = 6.0*(n*n-5*n+2)/((n+7)*(n+9)) * ((6.0*(n+3)*(n+5)) + / (n*(n-2)*(n-3)))**0.5 + # [1]_ Eq. 3: + A = 6.0 + 8.0/sqrtbeta1 * (2.0/sqrtbeta1 + (1+4.0/(sqrtbeta1**2))**0.5) + term1 = 1 - 2/(9.0*A) + denom = 1 + x * (2/(A-4.0))**0.5 + term2 = xp.sign(denom) * xp.where(denom == 0.0, xp.nan, + ((1-2.0/A)/xp.abs(denom))**(1/3)) + if not is_lazy_array(denom) and xp.any(denom == 0): + msg = ("Test statistic not defined in some cases due to division by " + "zero. Return nan in that case...") + warnings.warn(msg, RuntimeWarning, stacklevel=2) + + Z = (term1 - term2) / (2/(9.0*A))**0.5 # [1]_ Eq. 5 + pvalue = _get_pvalue(Z, _SimpleNormal(), alternative, xp=xp) + + Z = Z[()] if Z.ndim == 0 else Z + pvalue = pvalue[()] if pvalue.ndim == 0 else pvalue + return KurtosistestResult(Z, pvalue) + + +NormaltestResult = namedtuple('NormaltestResult', ('statistic', 'pvalue')) + + +@xp_capabilities(marray=True) +@_axis_nan_policy_factory(NormaltestResult, n_samples=1, too_small=7) +def normaltest(a, axis=0, nan_policy='propagate'): + r"""Test whether a sample differs from a normal distribution. + + This function tests the null hypothesis that a sample comes + from a normal distribution. It is based on D'Agostino and + Pearson's [1]_, [2]_ test that combines skew and kurtosis to + produce an omnibus test of normality. + + Parameters + ---------- + a : array_like + The array containing the sample to be tested. Must contain + at least eight observations. + axis : int or None, optional + Axis along which to compute test. Default is 0. If None, + compute over the whole array `a`. + nan_policy : {'propagate', 'raise', 'omit'}, optional + Defines how to handle when input contains nan. + The following options are available (default is 'propagate'): + + * 'propagate': returns nan + * 'raise': throws an error + * 'omit': performs the calculations ignoring nan values + + Returns + ------- + statistic : float or array + ``s^2 + k^2``, where ``s`` is the z-score returned by `skewtest` and + ``k`` is the z-score returned by `kurtosistest`. + pvalue : float or array + A 2-sided chi squared probability for the hypothesis test. + + See Also + -------- + :ref:`hypothesis_normaltest` : Extended example + + References + ---------- + .. [1] D'Agostino, R. B. (1971), "An omnibus test of normality for + moderate and large sample size", Biometrika, 58, 341-348 + .. [2] D'Agostino, R. and Pearson, E. S. (1973), "Tests for departure from + normality", Biometrika, 60, 613-622 + + Examples + -------- + + >>> import numpy as np + >>> from scipy import stats + >>> rng = np.random.default_rng() + >>> pts = 1000 + >>> a = rng.normal(0, 1, size=pts) + >>> b = rng.normal(2, 1, size=pts) + >>> x = np.concatenate((a, b)) + >>> res = stats.normaltest(x) + >>> res.statistic + 53.619... # random + >>> res.pvalue + 2.273917413209226e-12 # random + + For a more detailed example, see :ref:`hypothesis_normaltest`. + """ + xp = array_namespace(a) + + s, _ = skewtest(a, axis, _no_deco=True) + k, _ = kurtosistest(a, axis, _no_deco=True) + statistic = s*s + k*k + + chi2 = _SimpleChi2(xp.asarray(2., dtype=statistic.dtype, device=xp_device(a))) + pvalue = _get_pvalue(statistic, chi2, alternative='greater', symmetric=False, xp=xp) + + statistic = statistic[()] if statistic.ndim == 0 else statistic + pvalue = pvalue[()] if pvalue.ndim == 0 else pvalue + + return NormaltestResult(statistic, pvalue) + + +@xp_capabilities(marray=True) +@_axis_nan_policy_factory(SignificanceResult, default_axis=None) +def jarque_bera(x, *, axis=None): + r"""Perform the Jarque-Bera goodness of fit test on sample data. + + The Jarque-Bera test tests whether the sample data has the skewness and + kurtosis matching a normal distribution. + + Note that this test only works for a large enough number of data samples + (>2000) as the test statistic asymptotically has a Chi-squared distribution + with 2 degrees of freedom. + + Parameters + ---------- + x : array_like + Observations of a random variable. + axis : int or None, default: 0 + If an int, the axis of the input along which to compute the statistic. + The statistic of each axis-slice (e.g. row) of the input will appear in + a corresponding element of the output. + If ``None``, the input will be raveled before computing the statistic. + + Returns + ------- + result : SignificanceResult + An object with the following attributes: + + statistic : float + The test statistic. + pvalue : float + The p-value for the hypothesis test. + + See Also + -------- + :ref:`hypothesis_jarque_bera` : Extended example + + References + ---------- + .. [1] Jarque, C. and Bera, A. (1980) "Efficient tests for normality, + homoscedasticity and serial independence of regression residuals", + 6 Econometric Letters 255-259. + + Examples + -------- + + >>> import numpy as np + >>> from scipy import stats + >>> rng = np.random.default_rng() + >>> x = rng.normal(0, 1, 100000) + >>> jarque_bera_test = stats.jarque_bera(x) + >>> jarque_bera_test + Jarque_beraResult(statistic=3.3415184718131554, pvalue=0.18810419594996775) + >>> jarque_bera_test.statistic + 3.3415184718131554 + >>> jarque_bera_test.pvalue + 0.18810419594996775 + + For a more detailed example, see :ref:`hypothesis_jarque_bera`. + """ + xp = array_namespace(x) + x, axis = _chk_asarray(x, axis, xp=xp) + + mu = _xp_mean(x, axis=axis, keepdims=True) + diffx = x - mu + s = skew(diffx, axis=axis, _no_deco=True) + k = kurtosis(diffx, axis=axis, _no_deco=True) + + n = xp.asarray(_count_nonmasked(x, axis), dtype=mu.dtype, device=xp_device(x)) + statistic = n / 6 * (s**2 + k**2 / 4) + + chi2 = _SimpleChi2(xp.asarray(2., dtype=mu.dtype, device=xp_device(x))) + pvalue = _get_pvalue(statistic, chi2, alternative='greater', symmetric=False, xp=xp) + + statistic = statistic[()] if statistic.ndim == 0 else statistic + pvalue = pvalue[()] if pvalue.ndim == 0 else pvalue + + return SignificanceResult(statistic, pvalue) + + +##################################### +# FREQUENCY FUNCTIONS # +##################################### + + +@xp_capabilities(np_only=True) +def scoreatpercentile(a, per, limit=(), interpolation_method='fraction', + axis=None): + """Calculate the score at a given percentile of the input sequence. + + For example, the score at ``per=50`` is the median. If the desired quantile + lies between two data points, we interpolate between them, according to + the value of `interpolation`. If the parameter `limit` is provided, it + should be a tuple (lower, upper) of two values. + + Parameters + ---------- + a : array_like + A 1-D array of values from which to extract score. + per : array_like + Percentile(s) at which to extract score. Values should be in range + [0,100]. + limit : tuple, optional + Tuple of two scalars, the lower and upper limits within which to + compute the percentile. Values of `a` outside + this (closed) interval will be ignored. + interpolation_method : {'fraction', 'lower', 'higher'}, optional + Specifies the interpolation method to use, + when the desired quantile lies between two data points `i` and `j` + The following options are available (default is 'fraction'): + + * 'fraction': ``i + (j - i) * fraction`` where ``fraction`` is the + fractional part of the index surrounded by ``i`` and ``j`` + * 'lower': ``i`` + * 'higher': ``j`` + + axis : int, optional + Axis along which the percentiles are computed. Default is None. If + None, compute over the whole array `a`. + + Returns + ------- + score : float or ndarray + Score at percentile(s). + + See Also + -------- + percentileofscore, numpy.percentile + + Notes + ----- + This function will become obsolete in the future. + For NumPy 1.9 and higher, `numpy.percentile` provides all the functionality + that `scoreatpercentile` provides. And it's significantly faster. + Therefore it's recommended to use `numpy.percentile` for users that have + numpy >= 1.9. + + Examples + -------- + >>> import numpy as np + >>> from scipy import stats + >>> a = np.arange(100) + >>> stats.scoreatpercentile(a, 50) + 49.5 + + """ + # adapted from NumPy's percentile function. When we require numpy >= 1.8, + # the implementation of this function can be replaced by np.percentile. + a = np.asarray(a) + if a.size == 0: + # empty array, return nan(s) with shape matching `per` + if np.isscalar(per): + return np.nan + else: + return np.full(np.asarray(per).shape, np.nan, dtype=np.float64) + + if limit: + a = a[(limit[0] <= a) & (a <= limit[1])] + + sorted_ = np.sort(a, axis=axis) + if axis is None: + axis = 0 + + return _compute_qth_percentile(sorted_, per, interpolation_method, axis) + + +# handle sequence of per's without calling sort multiple times +def _compute_qth_percentile(sorted_, per, interpolation_method, axis): + if not np.isscalar(per): + score = [_compute_qth_percentile(sorted_, i, + interpolation_method, axis) + for i in per] + return np.array(score) + + if not (0 <= per <= 100): + raise ValueError("percentile must be in the range [0, 100]") + + indexer = [slice(None)] * sorted_.ndim + idx = per / 100. * (sorted_.shape[axis] - 1) + + if int(idx) != idx: + # round fractional indices according to interpolation method + if interpolation_method == 'lower': + idx = int(np.floor(idx)) + elif interpolation_method == 'higher': + idx = int(np.ceil(idx)) + elif interpolation_method == 'fraction': + pass # keep idx as fraction and interpolate + else: + raise ValueError("interpolation_method can only be 'fraction', " + "'lower' or 'higher'") + + i = int(idx) + if i == idx: + indexer[axis] = slice(i, i + 1) + weights = array(1) + sumval = 1.0 + else: + indexer[axis] = slice(i, i + 2) + j = i + 1 + weights = array([(j - idx), (idx - i)], float) + wshape = [1] * sorted_.ndim + wshape[axis] = 2 + weights = weights.reshape(wshape) + sumval = weights.sum() + + # Use np.add.reduce (== np.sum but a little faster) to coerce data type + return np.add.reduce(sorted_[tuple(indexer)] * weights, axis=axis) / sumval + + +@xp_capabilities(np_only=True) +def percentileofscore(a, score, kind='rank', nan_policy='propagate'): + """Compute the percentile rank of a score relative to a list of scores. + + A `percentileofscore` of, for example, 80% means that 80% of the + scores in `a` are below the given score. In the case of gaps or + ties, the exact definition depends on the optional keyword, `kind`. + + Parameters + ---------- + a : array_like + A 1-D array to which `score` is compared. + score : float or array_like + A float score or array of scores for which to compute the percentile(s). + kind : {'rank', 'weak', 'strict', 'mean'}, optional + Specifies the interpretation of the resulting score. + The following options are available (default is 'rank'): + + * 'rank': Average percentage ranking of score. In case of multiple + matches, average the percentage rankings of all matching scores. + * 'weak': This kind corresponds to the definition of a cumulative + distribution function. A percentileofscore of 80% means that 80% + of values are less than or equal to the provided score. + * 'strict': Similar to "weak", except that only values that are + strictly less than the given score are counted. + * 'mean': The average of the "weak" and "strict" scores, often used + in testing. See https://en.wikipedia.org/wiki/Percentile_rank + + nan_policy : {'propagate', 'raise', 'omit'}, optional + Specifies how to treat `nan` values in `a`. + The following options are available (default is 'propagate'): + + * 'propagate': returns nan (for each value in `score`). + * 'raise': throws an error + * 'omit': performs the calculations ignoring nan values + + Returns + ------- + pcos : float or array-like + Percentile-position(s) of `score` (0-100) relative to `a`. + + See Also + -------- + numpy.percentile + scipy.stats.scoreatpercentile, scipy.stats.rankdata + + Examples + -------- + Three-quarters of the given values lie below a given score: + + >>> import numpy as np + >>> from scipy import stats + >>> stats.percentileofscore([1, 2, 3, 4], 3) + 75.0 + + With multiple matches, note how the scores of the two matches, 0.6 + and 0.8 respectively, are averaged: + + >>> stats.percentileofscore([1, 2, 3, 3, 4], 3) + 70.0 + + Only 2/5 values are strictly less than 3: + + >>> stats.percentileofscore([1, 2, 3, 3, 4], 3, kind='strict') + 40.0 + + But 4/5 values are less than or equal to 3: + + >>> stats.percentileofscore([1, 2, 3, 3, 4], 3, kind='weak') + 80.0 + + The average between the weak and the strict scores is: + + >>> stats.percentileofscore([1, 2, 3, 3, 4], 3, kind='mean') + 60.0 + + Score arrays (of any dimensionality) are supported: + + >>> stats.percentileofscore([1, 2, 3, 3, 4], [2, 3]) + array([40., 70.]) + + The inputs can be infinite: + + >>> stats.percentileofscore([-np.inf, 0, 1, np.inf], [1, 2, np.inf]) + array([75., 75., 100.]) + + If `a` is empty, then the resulting percentiles are all `nan`: + + >>> stats.percentileofscore([], [1, 2]) + array([nan, nan]) + """ + + a = np.asarray(a) + score = np.asarray(score) + + if a.ndim != 1: + raise ValueError("`a` must be 1-dimensional.") + + n = len(a) + + # Nan treatment + cna = _contains_nan(a, nan_policy) + cns = _contains_nan(score, nan_policy) + + if cns: + # If a score is nan, then the output should be nan + # (also if nan_policy is "omit", because it only applies to `a`) + score = ma.masked_where(np.isnan(score), score) + + if cna: + if nan_policy == "omit": + # Don't count nans + a = ma.masked_where(np.isnan(a), a) + n = a.count() + + if nan_policy == "propagate": + # All outputs should be nans + n = 0 + + # Cannot compare to empty list ==> nan + if n == 0: + perct = np.full_like(score, np.nan, dtype=np.float64) + + else: + # Prepare broadcasting + score = score[..., None] + + def count(x): + return np.count_nonzero(x, -1) + + # Main computations/logic + if kind == 'rank': + left = count(a < score) + right = count(a <= score) + plus1 = left < right + perct = (left + right + plus1) * (50.0 / n) + elif kind == 'strict': + perct = count(a < score) * (100.0 / n) + elif kind == 'weak': + perct = count(a <= score) * (100.0 / n) + elif kind == 'mean': + left = count(a < score) + right = count(a <= score) + perct = (left + right) * (50.0 / n) + else: + raise ValueError( + "kind can only be 'rank', 'strict', 'weak' or 'mean'") + + # Re-insert nan values + perct = ma.filled(perct, np.nan) + + if perct.ndim == 0: + return perct[()] + return perct + + +HistogramResult = namedtuple('HistogramResult', + ('count', 'lowerlimit', 'binsize', 'extrapoints')) + + +def _histogram(a, numbins=10, defaultlimits=None, weights=None, *, + density=False, cumulative=False): + """Create a histogram. + + Separate the range into several bins and return the number of instances + in each bin. + + Parameters + ---------- + a : array_like + Array of scores which will be put into bins. + numbins : int, optional + The number of bins to use for the histogram. Default is 10. + defaultlimits : tuple (lower, upper), optional + The lower and upper values for the range of the histogram. + If no value is given, a range slightly larger than the range of the + values in a is used. Specifically ``(a.min() - s, a.max() + s)``, + where ``s = (1/2)(a.max() - a.min()) / (numbins - 1)``. + weights : array_like, optional + The weights for each value in `a`. Default is None, which gives each + value a weight of 1.0 + + Returns + ------- + count : ndarray + Number of points (or sum of weights) in each bin. + lowerlimit : float + Lowest value of histogram, the lower limit of the first bin. + binsize : float + The size of the bins (all bins have the same size). + extrapoints : int + The number of points outside the range of the histogram. + + See Also + -------- + numpy.histogram + + Notes + ----- + This histogram is based on numpy's histogram but has a larger range by + default if default limits is not set. + + """ + xp = array_namespace(a) + a = xp_ravel(a) + a, weights = xp_promote(a, weights, force_floating=True, xp=xp) + + if defaultlimits is None: + if xp_size(a) == 0: + # handle empty arrays. Undetermined range, so use 0-1. + defaultlimits = xp.asarray(0., dtype=a.dtype), xp.asarray(1., dtype=a.dtype) + else: + # no range given, so use values in `a` + data_min = xp.min(a) + data_max = xp.max(a) + # Have bins extend past min and max values slightly + s = (data_max - data_min) / (2. * (numbins - 1.)) + defaultlimits = (data_min - s, data_max + s) + else: + if not (np.iterable(defaultlimits) and len(defaultlimits)==2 + and defaultlimits[0] < defaultlimits[1]): + message = ('If specified, `defaultreallimits` must be given as an iterable ' + 'in the order (lower limit, upper limit).') + raise ValueError(message) + if not (xp.isdtype(a.dtype, 'real floating') + and (weights is None or xp.isdtype(weights.dtype, 'real floating'))): + message = '`a` and (if specified) `weights` must have real dtype.' + raise ValueError(message) + if weights is not None and not is_lazy_array(weights) and xp.any(weights < 0): + message = 'All `weights` must be non-negative.' + raise ValueError(message) + + bin_edges = xp.linspace(*defaultlimits, numbins+1, dtype=a.dtype) + if weights is None: + indices = xp.searchsorted(xp.sort(a), bin_edges, side='left') + hist = xp.diff(indices) + n_right_limit = xp.count_nonzero(a == bin_edges[-1]) + hist = xpx.at(hist)[-1].add(xp.astype(n_right_limit, hist.dtype)) + else: + i = xp.argsort(a) + weights, a = weights[i], a[i] + cumulative_weights = xp.cumulative_sum(weights, include_initial=True) + indices = xp.searchsorted(a, bin_edges, side='left') + hist = xp.diff(xp.take_along_axis(cumulative_weights, indices, axis=-1)) + hist = xpx.at(hist)[-1].add(xp.sum(xp.where(a == bin_edges[-1], weights, 0.))) + + # fixed width for bins is assumed, as numpy's histogram gives + # fixed width bins for int values for 'bins' + binsize = bin_edges[1] - bin_edges[0] + # calculate number of extra points + binnedpoints = (xp.sum(hist) if weights is None + else xp.count_nonzero((bin_edges[0] <= a) & (a <= bin_edges[-1]))) + extrapoints = a.shape[0] - binnedpoints + + lowerlimit = xp.asarray(defaultlimits[0], dtype=a.dtype)[()] + + hist = xp.asarray(hist, dtype=a.dtype) + if density: + hist = hist / (a.shape[0] if weights is None else cumulative_weights[-1]) + if cumulative: + hist = xp.cumulative_sum(hist, axis=0) + + return HistogramResult(hist, lowerlimit, binsize, extrapoints) + + +CumfreqResult = namedtuple('CumfreqResult', + ('cumcount', 'lowerlimit', 'binsize', + 'extrapoints')) + + +@xp_capabilities(skip_backends=[('dask.array', 'fails tests')]) +def cumfreq(a, numbins=10, defaultreallimits=None, weights=None): + """Return a cumulative frequency histogram, using the histogram function. + + A cumulative histogram is a mapping that counts the cumulative number of + observations in all of the bins up to the specified bin. + + Parameters + ---------- + a : array_like + Input array. + numbins : int, optional + The number of bins to use for the histogram. Default is 10. + defaultreallimits : tuple (lower, upper), optional + The lower and upper values for the range of the histogram. + If no value is given, a range slightly larger than the range of the + values in `a` is used. Specifically ``(a.min() - s, a.max() + s)``, + where ``s = (1/2)(a.max() - a.min()) / (numbins - 1)``. + weights : array_like, optional + The weights for each value in `a`. Default is None, which gives each + value a weight of 1.0 + + Returns + ------- + cumcount : ndarray + Binned values of cumulative frequency. + lowerlimit : float + Lower real limit + binsize : float + Width of each bin. + extrapoints : int + Extra points. + + Examples + -------- + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from scipy import stats + >>> rng = np.random.default_rng() + >>> x = [1, 4, 2, 1, 3, 1] + >>> res = stats.cumfreq(x, numbins=4, defaultreallimits=(1.5, 5)) + >>> res.cumcount + array([ 1., 2., 3., 3.]) + >>> res.extrapoints + 3 + + Create a normal distribution with 1000 random values + + >>> samples = stats.norm.rvs(size=1000, random_state=rng) + + Calculate cumulative frequencies + + >>> res = stats.cumfreq(samples, numbins=25) + + Calculate space of values for x + + >>> x = res.lowerlimit + np.linspace(0, res.binsize*res.cumcount.size, + ... res.cumcount.size + 1) + + Plot histogram and cumulative histogram + + >>> fig = plt.figure(figsize=(10, 4)) + >>> ax1 = fig.add_subplot(1, 2, 1) + >>> ax2 = fig.add_subplot(1, 2, 2) + >>> ax1.hist(samples, bins=25) + >>> ax1.set_title('Histogram') + >>> ax2.bar(x[:-1], res.cumcount, width=res.binsize, align='edge') + >>> ax2.set_title('Cumulative histogram') + >>> ax2.set_xlim([x.min(), x.max()]) + + >>> plt.show() + + """ + h, l, b, e = _histogram(a, numbins, defaultreallimits, weights=weights, + cumulative=True) + return CumfreqResult(h, l, b, e) + + +RelfreqResult = namedtuple('RelfreqResult', + ('frequency', 'lowerlimit', 'binsize', + 'extrapoints')) + + +@xp_capabilities(skip_backends=[('dask.array', 'fails tests')]) +def relfreq(a, numbins=10, defaultreallimits=None, weights=None): + """Return a relative frequency histogram, using the histogram function. + + A relative frequency histogram is a mapping of the number of + observations in each of the bins relative to the total of observations. + + Parameters + ---------- + a : array_like + Input array. + numbins : int, optional + The number of bins to use for the histogram. Default is 10. + defaultreallimits : tuple (lower, upper), optional + The lower and upper values for the range of the histogram. + If no value is given, a range slightly larger than the range of the + values in a is used. Specifically ``(a.min() - s, a.max() + s)``, + where ``s = (1/2)(a.max() - a.min()) / (numbins - 1)``. + weights : array_like, optional + The weights for each value in `a`. Default is None, which gives each + value a weight of 1.0 + + Returns + ------- + frequency : ndarray + Binned values of relative frequency. + lowerlimit : float + Lower real limit. + binsize : float + Width of each bin. + extrapoints : int + Extra points. + + Examples + -------- + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from scipy import stats + >>> rng = np.random.default_rng() + >>> a = np.array([2, 4, 1, 2, 3, 2]) + >>> res = stats.relfreq(a, numbins=4) + >>> res.frequency + array([ 0.16666667, 0.5 , 0.16666667, 0.16666667]) + >>> np.sum(res.frequency) # relative frequencies should add up to 1 + 1.0 + + Create a normal distribution with 1000 random values + + >>> samples = stats.norm.rvs(size=1000, random_state=rng) + + Calculate relative frequencies + + >>> res = stats.relfreq(samples, numbins=25) + + Calculate space of values for x + + >>> x = res.lowerlimit + np.linspace(0, res.binsize*res.frequency.size, + ... res.frequency.size) + + Plot relative frequency histogram + + >>> fig = plt.figure(figsize=(5, 4)) + >>> ax = fig.add_subplot(1, 1, 1) + >>> ax.bar(x, res.frequency, width=res.binsize) + >>> ax.set_title('Relative frequency histogram') + >>> ax.set_xlim([x.min(), x.max()]) + + >>> plt.show() + + """ + h, l, b, e = _histogram(a, numbins, defaultreallimits, weights=weights, + density=True) + return RelfreqResult(h, l, b, e) + + +##################################### +# VARIABILITY FUNCTIONS # +##################################### + +@xp_capabilities() +def obrientransform(*samples, nan_policy='propagate'): + """Compute the O'Brien transform on input data (any number of arrays). + + Used to test for homogeneity of variance prior to running one-way stats. + Each array in ``*samples`` is one level of a factor. + Significant results of `f_oneway` on the transformed data suggest that the + variances of the underlying distributions are unequal. + See Maxwell and Delaney [1]_, p.112. + + Parameters + ---------- + *samples : array_like + Any number of arrays. + nan_policy : {'propagate', 'omit', 'raise'} + Defines how to handle input NaNs. + + - ``propagate``: if a NaN is present in a sample, all elements of the + transformed sample will be NaN. + - ``omit``: NaNs will be omitted when computing reducing statistics for the + transform, but NaNs in the sample will remain NaNs in the transformed sample. + - ``raise``: if a NaN is present, a ``ValueError`` will be raised. + + Returns + ------- + obrientransform : tuple of arrays + Transformed arrays for use in ANOVA. + + Raises + ------ + ValueError + If the mean of the transformed data is not equal to the original + variance, indicating a lack of convergence in the O'Brien transform. + + References + ---------- + .. [1] S. E. Maxwell and H. D. Delaney, "Designing Experiments and + Analyzing Data: A Model Comparison Perspective", Wadsworth, 1990. + + Examples + -------- + We'll test the following data sets for differences in their variance. + + >>> x = [10, 11, 13, 9, 7, 12, 12, 9, 10] + >>> y = [13, 21, 5, 10, 8, 14, 10, 12, 7, 15] + + Apply the O'Brien transform to the data. + + >>> from scipy.stats import obrientransform + >>> tx, ty = obrientransform(x, y) + + Use `scipy.stats.f_oneway` to apply a one-way ANOVA test to the + transformed data. + + >>> from scipy.stats import f_oneway + >>> F, p = f_oneway(tx, ty) + >>> p + 0.1314139477040335 + + If we require that ``p < 0.05`` for significance, we cannot conclude + that the variances are different. + + """ + xp = array_namespace(*samples) + n_samples = len(samples) + samples = xp_promote(*samples, force_floating=True, xp=xp) + samples = (samples,) if n_samples == 1 else samples + return tuple(_xp_obrientransform_one_sample(sample, xp=xp, nan_policy=nan_policy) + for sample in samples) + + +def _xp_obrientransform_one_sample(a, *, xp, nan_policy): + _contains_nan(a, nan_policy, xp_omit_okay=True) # handle `nan_policy='raise'` + n = xp.asarray(xp.count_nonzero(~xp.isnan(a)), dtype=a.dtype) + mu = _xp_mean(a, nan_policy=nan_policy) + sq = (a - mu)**2 + sumsq = _xp_mean(sq, nan_policy=nan_policy) * n + return ((n - 1.5) * n * sq - 0.5 * sumsq) / ((n - 1) * (n - 2)) + + +@xp_capabilities(marray=True) +@_axis_nan_policy_factory( + lambda x: x, result_to_tuple=lambda x, _: (x,), n_outputs=1, too_small=1 +) +def sem(a, axis=0, ddof=1, nan_policy='propagate'): + """Compute standard error of the mean. + + Calculate the standard error of the mean (or standard error of + measurement) of the values in the input array. + + Parameters + ---------- + a : array_like + An array containing the values for which the standard error is + returned. Must contain at least two observations. + axis : int or None, optional + Axis along which to operate. Default is 0. If None, compute over + the whole array `a`. + ddof : int, optional + Delta degrees-of-freedom. How many degrees of freedom to adjust + for bias in limited samples relative to the population estimate + of variance. Defaults to 1. + nan_policy : {'propagate', 'raise', 'omit'}, optional + Defines how to handle when input contains nan. + The following options are available (default is 'propagate'): + + * 'propagate': returns nan + * 'raise': throws an error + * 'omit': performs the calculations ignoring nan values + + Returns + ------- + s : ndarray or float + The standard error of the mean in the sample(s), along the input axis. + + Notes + ----- + The default value for `ddof` is different to the default (0) used by other + ddof containing routines, such as np.std and np.nanstd. + + Examples + -------- + Find standard error along the first axis: + + >>> import numpy as np + >>> from scipy import stats + >>> a = np.arange(20).reshape(5,4) + >>> stats.sem(a) + array([ 2.8284, 2.8284, 2.8284, 2.8284]) + + Find standard error across the whole array, using n degrees of freedom: + + >>> stats.sem(a, axis=None, ddof=0) + 1.2893796958227628 + + """ + xp = array_namespace(a) + if axis is None: + a = xp.reshape(a, (-1,)) + axis = 0 + a = xpx.atleast_nd(xp.asarray(a), ndim=1, xp=xp) + n = _count_nonmasked(a, axis, xp=xp) + s = xp.std(a, axis=axis, correction=ddof) / n**0.5 + return s + + +def _isconst(x): + """ + Check if all values in x are the same. nans are ignored. + + x must be a 1d array. + + The return value is a 1d array with length 1, so it can be used + in np.apply_along_axis. + """ + y = x[~np.isnan(x)] + if y.size == 0: + return np.array([True]) + else: + return (y[0] == y).all(keepdims=True) + + +@xp_capabilities(marray=True) +def zscore(a, axis=0, ddof=0, nan_policy='propagate'): + """ + Compute the z score. + + Compute the z score of each value in the sample, relative to the + sample mean and standard deviation. + + Parameters + ---------- + a : array_like + An array like object containing the sample data. + axis : int or None, optional + Axis along which to operate. Default is 0. If None, compute over + the whole array `a`. + ddof : int, optional + Degrees of freedom correction in the calculation of the + standard deviation. Default is 0. + nan_policy : {'propagate', 'raise', 'omit'}, optional + Defines how to handle when input contains nan. 'propagate' returns nan, + 'raise' throws an error, 'omit' performs the calculations ignoring nan + values. Default is 'propagate'. Note that when the value is 'omit', + nans in the input also propagate to the output, but they do not affect + the z-scores computed for the non-nan values. + + Returns + ------- + zscore : array_like + The z-scores, standardized by mean and standard deviation of + input array `a`. + + See Also + -------- + numpy.mean : Arithmetic average + numpy.std : Arithmetic standard deviation + scipy.stats.gzscore : Geometric standard score + + Notes + ----- + This function preserves ndarray subclasses, and works also with + matrices and masked arrays (it uses `asanyarray` instead of + `asarray` for parameters). + + References + ---------- + .. [1] "Standard score", *Wikipedia*, + https://en.wikipedia.org/wiki/Standard_score. + .. [2] Huck, S. W., Cross, T. L., Clark, S. B, "Overcoming misconceptions + about Z-scores", Teaching Statistics, vol. 8, pp. 38-40, 1986 + + Examples + -------- + >>> import numpy as np + >>> a = np.array([ 0.7972, 0.0767, 0.4383, 0.7866, 0.8091, + ... 0.1954, 0.6307, 0.6599, 0.1065, 0.0508]) + >>> from scipy import stats + >>> stats.zscore(a) + array([ 1.1273, -1.247 , -0.0552, 1.0923, 1.1664, -0.8559, 0.5786, + 0.6748, -1.1488, -1.3324]) + + Computing along a specified axis, using n-1 degrees of freedom + (``ddof=1``) to calculate the standard deviation: + + >>> b = np.array([[ 0.3148, 0.0478, 0.6243, 0.4608], + ... [ 0.7149, 0.0775, 0.6072, 0.9656], + ... [ 0.6341, 0.1403, 0.9759, 0.4064], + ... [ 0.5918, 0.6948, 0.904 , 0.3721], + ... [ 0.0921, 0.2481, 0.1188, 0.1366]]) + >>> stats.zscore(b, axis=1, ddof=1) + array([[-0.19264823, -1.28415119, 1.07259584, 0.40420358], + [ 0.33048416, -1.37380874, 0.04251374, 1.00081084], + [ 0.26796377, -1.12598418, 1.23283094, -0.37481053], + [-0.22095197, 0.24468594, 1.19042819, -1.21416216], + [-0.82780366, 1.4457416 , -0.43867764, -0.1792603 ]]) + + An example with ``nan_policy='omit'``: + + >>> x = np.array([[25.11, 30.10, np.nan, 32.02, 43.15], + ... [14.95, 16.06, 121.25, 94.35, 29.81]]) + >>> stats.zscore(x, axis=1, nan_policy='omit') + array([[-1.13490897, -0.37830299, nan, -0.08718406, 1.60039602], + [-0.91611681, -0.89090508, 1.4983032 , 0.88731639, -0.5785977 ]]) + """ + return zmap(a, a, axis=axis, ddof=ddof, nan_policy=nan_policy) + + +@xp_capabilities(marray=True) +def gzscore(a, *, axis=0, ddof=0, nan_policy='propagate'): + """ + Compute the geometric standard score. + + Compute the geometric z score of each strictly positive value in the + sample, relative to the geometric mean and standard deviation. + Mathematically the geometric z score can be evaluated as:: + + gzscore = log(a/gmu) / log(gsigma) + + where ``gmu`` (resp. ``gsigma``) is the geometric mean (resp. standard + deviation). + + Parameters + ---------- + a : array_like + Sample data. + axis : int or None, optional + Axis along which to operate. Default is 0. If None, compute over + the whole array `a`. + ddof : int, optional + Degrees of freedom correction in the calculation of the + standard deviation. Default is 0. + nan_policy : {'propagate', 'raise', 'omit'}, optional + Defines how to handle when input contains nan. 'propagate' returns nan, + 'raise' throws an error, 'omit' performs the calculations ignoring nan + values. Default is 'propagate'. Note that when the value is 'omit', + nans in the input also propagate to the output, but they do not affect + the geometric z scores computed for the non-nan values. + + Returns + ------- + gzscore : array_like + The geometric z scores, standardized by geometric mean and geometric + standard deviation of input array `a`. + + See Also + -------- + gmean : Geometric mean + gstd : Geometric standard deviation + zscore : Standard score + + Notes + ----- + This function preserves ndarray subclasses, and works also with + matrices and masked arrays (it uses ``asanyarray`` instead of + ``asarray`` for parameters). + + .. versionadded:: 1.8 + + References + ---------- + .. [1] "Geometric standard score", *Wikipedia*, + https://en.wikipedia.org/wiki/Geometric_standard_deviation#Geometric_standard_score. + + Examples + -------- + Draw samples from a log-normal distribution: + + >>> import numpy as np + >>> from scipy.stats import zscore, gzscore + >>> import matplotlib.pyplot as plt + + >>> rng = np.random.default_rng() + >>> mu, sigma = 3., 1. # mean and standard deviation + >>> x = rng.lognormal(mu, sigma, size=500) + + Display the histogram of the samples: + + >>> fig, ax = plt.subplots() + >>> ax.hist(x, 50) + >>> plt.show() + + Display the histogram of the samples standardized by the classical zscore. + Distribution is rescaled but its shape is unchanged. + + >>> fig, ax = plt.subplots() + >>> ax.hist(zscore(x), 50) + >>> plt.show() + + Demonstrate that the distribution of geometric zscores is rescaled and + quasinormal: + + >>> fig, ax = plt.subplots() + >>> ax.hist(gzscore(x), 50) + >>> plt.show() + + """ + xp = array_namespace(a) + a = xp_promote(a, force_floating=True, xp=xp) + log = ma.log if isinstance(a, ma.MaskedArray) else xp.log + return zscore(log(a), axis=axis, ddof=ddof, nan_policy=nan_policy) + + +@xp_capabilities(marray=True) +def zmap(scores, compare, axis=0, ddof=0, nan_policy='propagate'): + """ + Calculate the relative z-scores. + + Return an array of z-scores, i.e., scores that are standardized to + zero mean and unit variance, where mean and variance are calculated + from the comparison array. + + Parameters + ---------- + scores : array_like + The input for which z-scores are calculated. + compare : array_like + The input from which the mean and standard deviation of the + normalization are taken; assumed to have the same dimension as + `scores`. + axis : int or None, optional + Axis over which mean and variance of `compare` are calculated. + Default is 0. If None, compute over the whole array `scores`. + ddof : int, optional + Degrees of freedom correction in the calculation of the + standard deviation. Default is 0. + nan_policy : {'propagate', 'raise', 'omit'}, optional + Defines how to handle the occurrence of nans in `compare`. + 'propagate' returns nan, 'raise' raises an exception, 'omit' + performs the calculations ignoring nan values. Default is + 'propagate'. Note that when the value is 'omit', nans in `scores` + also propagate to the output, but they do not affect the z-scores + computed for the non-nan values. + + Returns + ------- + zscore : array_like + Z-scores, in the same shape as `scores`. + + Notes + ----- + This function preserves ndarray subclasses, and works also with + matrices and masked arrays (it uses `asanyarray` instead of + `asarray` for parameters). + + Examples + -------- + >>> from scipy.stats import zmap + >>> a = [0.5, 2.0, 2.5, 3] + >>> b = [0, 1, 2, 3, 4] + >>> zmap(a, b) + array([-1.06066017, 0. , 0.35355339, 0.70710678]) + + """ + # The docstring explicitly states that it preserves subclasses. + # Let's table deprecating that and just get the array API version + # working. + + like_zscore = (scores is compare) + xp = array_namespace(scores, compare) + scores, compare = xp_promote(scores, compare, force_floating=True, xp=xp) + + with warnings.catch_warnings(): + if like_zscore: # zscore should not emit SmallSampleWarning + warnings.simplefilter('ignore', SmallSampleWarning) + + mn = _xp_mean(compare, axis=axis, keepdims=True, nan_policy=nan_policy) + std = _xp_var(compare, axis=axis, correction=ddof, + keepdims=True, nan_policy=nan_policy)**0.5 + + with np.errstate(invalid='ignore', divide='ignore'): + z = _demean(scores, mn, axis, xp=xp, precision_warning=False) / std + + # If we know that scores and compare are identical, we can infer that + # some slices should have NaNs. + if like_zscore: + eps = xp.finfo(z.dtype).eps + zero = std <= xp.abs(eps * mn) + zero = xp.broadcast_to(zero, z.shape) + z = xpx.at(z, zero).set(xp.nan) + + return z + + +@xp_capabilities(marray=True) +def gstd(a, axis=0, ddof=1, *, keepdims=False, nan_policy='propagate'): + r""" + Calculate the geometric standard deviation of an array. + + The geometric standard deviation describes the spread of a set of numbers + where the geometric mean is preferred. It is a multiplicative factor, and + so a dimensionless quantity. + + It is defined as the exponential of the standard deviation of the + natural logarithms of the observations. + + Parameters + ---------- + a : array_like + An array containing finite, strictly positive, real numbers. + axis : int, tuple or None, optional + Axis along which to operate. Default is 0. If None, compute over + the whole array `a`. + ddof : int, optional + Degree of freedom correction in the calculation of the + geometric standard deviation. Default is 1. + keepdims : bool, optional + If this is set to ``True``, the axes which are reduced are left + in the result as dimensions with length one. With this option, + the result will broadcast correctly against the input array. + nan_policy : {'propagate', 'omit', 'raise'}, default: 'propagate' + Defines how to handle input NaNs. + + - ``propagate``: if a NaN is present in the axis slice (e.g. row) along + which the statistic is computed, the corresponding entry of the output + will be NaN. + - ``omit``: NaNs will be omitted when performing the calculation. + If insufficient data remains in the axis slice along which the + statistic is computed, the corresponding entry of the output will be + NaN. + - ``raise``: if a NaN is present, a ``ValueError`` will be raised. + + Returns + ------- + gstd : ndarray or float + An array of the geometric standard deviation. If `axis` is None or `a` + is a 1d array a float is returned. + + See Also + -------- + gmean : Geometric mean + numpy.std : Standard deviation + gzscore : Geometric standard score + + Notes + ----- + Mathematically, the sample geometric standard deviation :math:`s_G` can be + defined in terms of the natural logarithms of the observations + :math:`y_i = \log(x_i)`: + + .. math:: + + s_G = \exp(s), \quad s = \sqrt{\frac{1}{n - d} \sum_{i=1}^n (y_i - \bar y)^2} + + where :math:`n` is the number of observations, :math:`d` is the adjustment `ddof` + to the degrees of freedom, and :math:`\bar y` denotes the mean of the natural + logarithms of the observations. Note that the default ``ddof=1`` is different from + the default value used by similar functions, such as `numpy.std` and `numpy.var`. + + When an observation is infinite, the geometric standard deviation is + NaN (undefined). Non-positive observations will also produce NaNs in the + output because the *natural* logarithm (as opposed to the *complex* + logarithm) is defined and finite only for positive reals. + The geometric standard deviation is sometimes confused with the exponential + of the standard deviation, ``exp(std(a))``. Instead, the geometric standard + deviation is ``exp(std(log(a)))``. + + References + ---------- + .. [1] "Geometric standard deviation", *Wikipedia*, + https://en.wikipedia.org/wiki/Geometric_standard_deviation. + .. [2] Kirkwood, T. B., "Geometric means and measures of dispersion", + Biometrics, vol. 35, pp. 908-909, 1979 + + Examples + -------- + Find the geometric standard deviation of a log-normally distributed sample. + Note that the standard deviation of the distribution is one; on a + log scale this evaluates to approximately ``exp(1)``. + + >>> import numpy as np + >>> from scipy.stats import gstd + >>> rng = np.random.default_rng() + >>> sample = rng.lognormal(mean=0, sigma=1, size=1000) + >>> gstd(sample) + 2.810010162475324 + + Compute the geometric standard deviation of a multidimensional array and + of a given axis. + + >>> a = np.arange(1, 25).reshape(2, 3, 4) + >>> gstd(a, axis=None) + 2.2944076136018947 + >>> gstd(a, axis=2) + array([[1.82424757, 1.22436866, 1.13183117], + [1.09348306, 1.07244798, 1.05914985]]) + >>> gstd(a, axis=(1,2)) + array([2.12939215, 1.22120169]) + + """ + xp = array_namespace(a) + a = xp_promote(a, force_floating=True, xp=xp) + + kwargs = dict(axis=axis, correction=ddof, keepdims=keepdims, nan_policy=nan_policy) + with np.errstate(invalid='ignore', divide='ignore'): + res = xp.exp(_xp_var(xp.log(a), **kwargs)**0.5) + + if not is_lazy_array(a) and xp.any(a <= 0): + message = ("The geometric standard deviation is only defined if all elements " + "are greater than or equal to zero; otherwise, the result is NaN.") + warnings.warn(message, RuntimeWarning, stacklevel=2) + + return res + +# Private dictionary initialized only once at module level +# See https://en.wikipedia.org/wiki/Robust_measures_of_scale +_scale_conversions = {'normal': float(special.erfinv(0.5) * 2.0 * math.sqrt(2.0))} + + +@xp_capabilities(skip_backends=[('dask.array', 'no quantile (take_along_axis)')], + marray=True) +@_axis_nan_policy_factory( + lambda x: x, result_to_tuple=lambda x, _: (x,), n_outputs=1, + default_axis=None, override={'nan_propagation': False} +) +def iqr(x, axis=None, rng=(25, 75), scale=1.0, nan_policy='propagate', + interpolation='linear', keepdims=False): + r""" + Compute the interquartile range of the data along the specified axis. + + The interquartile range (IQR) is the difference between the 75th and + 25th percentile of the data. It is a measure of the dispersion + similar to standard deviation or variance, but is much more robust + against outliers [2]_. + + The ``rng`` parameter allows this function to compute other + percentile ranges than the actual IQR. For example, setting + ``rng=(0, 100)`` is equivalent to `numpy.ptp`. + + The IQR of an empty array is `np.nan`. + + .. versionadded:: 0.18.0 + + Parameters + ---------- + x : array_like + Input array or object that can be converted to an array. + axis : int or sequence of int, optional + Axis along which the range is computed. The default is to + compute the IQR for the entire array. + rng : Two-element sequence containing floats in range of [0,100] optional + Percentiles over which to compute the range. Each must be + between 0 and 100, inclusive. The default is the true IQR: + ``(25, 75)``. The order of the elements is not important. + scale : scalar or str or array_like of reals, optional + The numerical value of scale will be divided out of the final + result. The following string value is also recognized: + + * 'normal' : Scale by + :math:`2 \sqrt{2} erf^{-1}(\frac{1}{2}) \approx 1.349`. + + The default is 1.0. + Array-like `scale` of real dtype is also allowed, as long + as it broadcasts correctly to the output such that + ``out / scale`` is a valid operation. The output dimensions + depend on the input array, `x`, the `axis` argument, and the + `keepdims` flag. + nan_policy : {'propagate', 'raise', 'omit'}, optional + Defines how to handle when input contains nan. + The following options are available (default is 'propagate'): + + * 'propagate': returns nan + * 'raise': throws an error + * 'omit': performs the calculations ignoring nan values + + interpolation : str, optional + + Specifies the interpolation method to use when the percentile + boundaries lie between two data points ``i`` and ``j``. + The following options are available (default is 'linear'): + + * 'linear': ``i + (j - i)*fraction``, where ``fraction`` is the + fractional part of the index surrounded by ``i`` and ``j``. + * 'lower': ``i``. + * 'higher': ``j``. + * 'nearest': ``i`` or ``j`` whichever is nearest. + * 'midpoint': ``(i + j)/2``. + + For NumPy >= 1.22.0, the additional options provided by the ``method`` + keyword of `numpy.percentile` are also valid. + + keepdims : bool, optional + If this is set to True, the reduced axes are left in the + result as dimensions with size one. With this option, the result + will broadcast correctly against the original array `x`. + + Returns + ------- + iqr : scalar or ndarray + If ``axis=None``, a scalar is returned. If the input contains + integers or floats of smaller precision than ``np.float64``, then the + output data-type is ``np.float64``. Otherwise, the output data-type is + the same as that of the input. + + See Also + -------- + numpy.std, numpy.var + + References + ---------- + .. [1] "Interquartile range" https://en.wikipedia.org/wiki/Interquartile_range + .. [2] "Robust measures of scale" https://en.wikipedia.org/wiki/Robust_measures_of_scale + .. [3] "Quantile" https://en.wikipedia.org/wiki/Quantile + + Examples + -------- + >>> import numpy as np + >>> from scipy.stats import iqr + >>> x = np.array([[10, 7, 4], [3, 2, 1]]) + >>> x + array([[10, 7, 4], + [ 3, 2, 1]]) + >>> iqr(x) + 4.0 + >>> iqr(x, axis=0) + array([ 3.5, 2.5, 1.5]) + >>> iqr(x, axis=1) + array([ 3., 1.]) + >>> iqr(x, axis=1, keepdims=True) + array([[ 3.], + [ 1.]]) + + """ + xp = array_namespace(x) + + # An error may be raised here, so fail-fast, before doing lengthy + # computations, even though `scale` is not used until later + if isinstance(scale, str): + scale_key = scale.lower() + if scale_key not in _scale_conversions: + raise ValueError(f"{scale} not a valid scale for `iqr`") + scale = _scale_conversions[scale_key] + + if len(rng) != 2: + raise TypeError("`rng` must be a two element sequence.") + + if np.isnan(rng).any(): # OK to use NumPy; this shouldn't be an array + raise ValueError("`rng` must not contain NaNs.") + + rng = (rng[0]/100, rng[1]/100) if rng[0] < rng[1] else (rng[1]/100, rng[0]/100) + + if rng[0] < 0 or rng[1] > 1: + raise ValueError("Elements of `rng` must be in the range [0, 100].") + + if interpolation in {'lower', 'midpoint', 'higher', 'nearest'}: + interpolation = '_' + interpolation + + rng = xp.asarray(rng, dtype=xp_result_type(x, force_floating=True, xp=xp)) + pct = stats.quantile(x, rng, axis=-1, method=interpolation, keepdims=True) + out = pct[..., 1:2] - pct[..., 0:1] + + if scale != 1.0: + out /= scale + + out = out if keepdims else xp.squeeze(out, axis=-1) + return out[()] if out.ndim == 0 else out + + +def _mad_1d(x, center, nan_policy): + # Median absolute deviation for 1-d array x. + # This is a helper function for `median_abs_deviation`; it assumes its + # arguments have been validated already. In particular, x must be a + # 1-d numpy array, center must be callable, and if nan_policy is not + # 'propagate', it is assumed to be 'omit', because 'raise' is handled + # in `median_abs_deviation`. + # No warning is generated if x is empty or all nan. + isnan = np.isnan(x) + if isnan.any(): + if nan_policy == 'propagate': + return np.nan + x = x[~isnan] + if x.size == 0: + # MAD of an empty array is nan. + return np.nan + # Edge cases have been handled, so do the basic MAD calculation. + med = center(x) + mad = np.median(np.abs(x - med)) + return mad + + +@xp_capabilities(skip_backends=[('dask.array', 'not supported by `quantile`')], + marray=True) +@_axis_nan_policy_factory( + lambda x: x, result_to_tuple=lambda x, _: (x,), n_outputs=1, default_axis=0 +) +def median_abs_deviation(x, axis=0, center=None, scale=1.0, + nan_policy='propagate'): + r""" + Compute the median absolute deviation of the data along the given axis. + + The median absolute deviation (MAD, [1]_) computes the median over the + absolute deviations from the median. It is a measure of dispersion + similar to the standard deviation but more robust to outliers [2]_. + + The MAD of an empty array is ``np.nan``. + + .. versionadded:: 1.5.0 + + Parameters + ---------- + x : array_like + Input array or object that can be converted to an array. + axis : int or None, optional + Axis along which the range is computed. Default is 0. If None, compute + the MAD over the entire array. + center : callable, optional + A function that will return the central value. The default is to use + np.median. Any user defined function used will need to have the + function signature ``func(arr, axis)``. + scale : scalar or str, optional + The numerical value of scale will be divided out of the final + result. The default is 1.0. The string "normal" is also accepted, + and results in `scale` being the inverse of the standard normal + quantile function at 0.75, which is approximately 0.67449. + Array-like scale is also allowed, as long as it broadcasts correctly + to the output such that ``out / scale`` is a valid operation. The + output dimensions depend on the input array, `x`, and the `axis` + argument. + nan_policy : {'propagate', 'raise', 'omit'}, optional + Defines how to handle when input contains nan. + The following options are available (default is 'propagate'): + + * 'propagate': returns nan + * 'raise': throws an error + * 'omit': performs the calculations ignoring nan values + + Returns + ------- + mad : scalar or ndarray + If ``axis=None``, a scalar is returned. If the input contains + integers or floats of smaller precision than ``np.float64``, then the + output data-type is ``np.float64``. Otherwise, the output data-type is + the same as that of the input. + + See Also + -------- + numpy.std, numpy.var, numpy.median, scipy.stats.iqr, scipy.stats.tmean, + scipy.stats.tstd, scipy.stats.tvar + + Notes + ----- + The `center` argument only affects the calculation of the central value + around which the MAD is calculated. That is, passing in ``center=np.mean`` + will calculate the MAD around the mean - it will not calculate the *mean* + absolute deviation. + + The input array may contain `inf`, but if `center` returns `inf`, the + corresponding MAD for that data will be `nan`. + + References + ---------- + .. [1] "Median absolute deviation", + https://en.wikipedia.org/wiki/Median_absolute_deviation + .. [2] "Robust measures of scale", + https://en.wikipedia.org/wiki/Robust_measures_of_scale + + Examples + -------- + When comparing the behavior of `median_abs_deviation` with ``np.std``, + the latter is affected when we change a single value of an array to have an + outlier value while the MAD hardly changes: + + >>> import numpy as np + >>> from scipy import stats + >>> x = stats.norm.rvs(size=100, scale=1, random_state=123456) + >>> x.std() + 0.9973906394005013 + >>> stats.median_abs_deviation(x) + 0.82832610097857 + >>> x[0] = 345.6 + >>> x.std() + 34.42304872314415 + >>> stats.median_abs_deviation(x) + 0.8323442311590675 + + Axis handling example: + + >>> x = np.array([[10, 7, 4], [3, 2, 1]]) + >>> x + array([[10, 7, 4], + [ 3, 2, 1]]) + >>> stats.median_abs_deviation(x) + array([3.5, 2.5, 1.5]) + >>> stats.median_abs_deviation(x, axis=None) + 2.0 + + Scale normal example: + + >>> x = stats.norm.rvs(size=1000000, scale=2, random_state=123456) + >>> stats.median_abs_deviation(x) + 1.3487398527041636 + >>> stats.median_abs_deviation(x, scale='normal') + 1.9996446978061115 + + """ + xp = array_namespace(x) + xp_median = (xp.median if is_numpy(xp) + else lambda x, axis: stats.quantile(x, 0.5, axis=axis)) + center = (xp_median if center is None else center) + + if not callable(center): + raise TypeError("The argument 'center' must be callable. The given " + f"value {repr(center)} is not callable.") + + # An error may be raised here, so fail-fast, before doing lengthy + # computations, even though `scale` is not used until later + if isinstance(scale, str): + if scale.lower() == 'normal': + scale = 0.6744897501960817 # special.ndtri(0.75) + else: + raise ValueError(f"{scale} is not a valid scale value.") + + # Wrap the call to center() in expand_dims() so it acts like + # keepdims=True was used. + med = xp.expand_dims(center(x, axis=axis), axis=axis) + mad = xp_median(xp.abs(x - med), axis=axis) + + return mad / scale + + +##################################### +# TRIMMING FUNCTIONS # +##################################### + + +SigmaclipResult = namedtuple('SigmaclipResult', ('clipped', 'lower', 'upper')) + + +@xp_capabilities(skip_backends=[('dask.array', "doesn't know array size")], + jax_jit=False) +def sigmaclip(a, low=4., high=4., *, nan_policy='propagate'): + """Perform iterative sigma-clipping of array elements. + + Starting from the full sample, all elements outside the critical range are + removed, i.e. all elements of the input array `c` that satisfy either of + the following conditions:: + + c < mean(c) - std(c)*low + c > mean(c) + std(c)*high + + The iteration continues with the updated sample until no + elements are outside the (updated) range. + + Parameters + ---------- + a : array_like + Data array, will be raveled if not 1-D. + low : float, optional + Lower bound factor of sigma clipping. Default is 4. + high : float, optional + Upper bound factor of sigma clipping. Default is 4. + nan_policy : {'propagate', 'omit', 'raise'} + Defines how to handle input NaNs. + + - ``propagate``: if a NaN is present in the input, the clipped array will be + empty, and the upper and lower thresholds will be NaN. + - ``omit``: NaNs will be omitted when performing the calculation. + - ``raise``: if a NaN is present, a ``ValueError`` will be raised. + + Returns + ------- + clipped : ndarray + Input array with clipped elements removed. + lower : float + Lower threshold value use for clipping. + upper : float + Upper threshold value use for clipping. + + Notes + ----- + This function iteratively *removes* observations. Once observations are + removed, they are not re-added in subsequent iterations. Consequently, + although it is often the case that ``clipped`` is identical to + ``a[(a >= lower) & (a <= upper)]``, this property is not guaranteed to be + satisfied; ``clipped`` may have fewer elements. + + Examples + -------- + >>> import numpy as np + >>> from scipy.stats import sigmaclip + >>> a = np.concatenate((np.linspace(9.5, 10.5, 31), + ... np.linspace(0, 20, 5))) + >>> fact = 1.5 + >>> c, low, upp = sigmaclip(a, fact, fact) + >>> c + array([ 9.96666667, 10. , 10.03333333, 10. ]) + >>> c.var(), c.std() + (0.00055555555555555165, 0.023570226039551501) + >>> low, c.mean() - fact*c.std(), c.min() + (9.9646446609406727, 9.9646446609406727, 9.9666666666666668) + >>> upp, c.mean() + fact*c.std(), c.max() + (10.035355339059327, 10.035355339059327, 10.033333333333333) + + >>> a = np.concatenate((np.linspace(9.5, 10.5, 11), + ... np.linspace(-100, -50, 3))) + >>> c, low, upp = sigmaclip(a, 1.8, 1.8) + >>> (c == np.linspace(9.5, 10.5, 11)).all() + True + + """ + xp = array_namespace(a) + c = xp_ravel(xp.asarray(a)) + contains_nan = _contains_nan(c, nan_policy, xp_omit_okay=True) + if contains_nan: + if nan_policy == 'propagate': + NaN = _get_nan(c, xp=xp) + clipped = xp.empty_like(c[0:0]) + return SigmaclipResult(clipped, NaN, NaN) + elif nan_policy == 'omit': + c = c[~xp.isnan(c)] + + delta = 1 + while delta: + c_std = xp.std(c) + c_mean = xp.mean(c) + size = xp_size(c) + critlower = c_mean - c_std * low + critupper = c_mean + c_std * high + c = c[(c >= critlower) & (c <= critupper)] + delta = size - xp_size(c) + + return SigmaclipResult(c, critlower, critupper) + + +@xp_capabilities(np_only=True) +def trimboth(a, proportiontocut, axis=0): + """Slice off a proportion of items from both ends of an array. + + Slice off the passed proportion of items from both ends of the passed + array (i.e., with `proportiontocut` = 0.1, slices leftmost 10% **and** + rightmost 10% of scores). The trimmed values are the lowest and + highest ones. + Slice off less if proportion results in a non-integer slice index (i.e. + conservatively slices off `proportiontocut`). + + Parameters + ---------- + a : array_like + Data to trim. + proportiontocut : float + Proportion (in range 0-1) of total data set to trim of each end. + axis : int or None, optional + Axis along which to trim data. Default is 0. If None, compute over + the whole array `a`. + + Returns + ------- + out : ndarray + Trimmed version of array `a`. The order of the trimmed content + is undefined. + + See Also + -------- + trim_mean + + Examples + -------- + Create an array of 10 values and trim 10% of those values from each end: + + >>> import numpy as np + >>> from scipy import stats + >>> a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + >>> stats.trimboth(a, 0.1) + array([1, 3, 2, 4, 5, 6, 7, 8]) + + Note that the elements of the input array are trimmed by value, but the + output array is not necessarily sorted. + + The proportion to trim is rounded down to the nearest integer. For + instance, trimming 25% of the values from each end of an array of 10 + values will return an array of 6 values: + + >>> b = np.arange(10) + >>> stats.trimboth(b, 1/4).shape + (6,) + + Multidimensional arrays can be trimmed along any axis or across the entire + array: + + >>> c = [2, 4, 6, 8, 0, 1, 3, 5, 7, 9] + >>> d = np.array([a, b, c]) + >>> stats.trimboth(d, 0.4, axis=0).shape + (1, 10) + >>> stats.trimboth(d, 0.4, axis=1).shape + (3, 2) + >>> stats.trimboth(d, 0.4, axis=None).shape + (6,) + + """ + a = np.asarray(a) + + if a.size == 0: + return a + + if axis is None: + a = a.ravel() + axis = 0 + + nobs = a.shape[axis] + lowercut = int(proportiontocut * nobs) + uppercut = nobs - lowercut + if (lowercut >= uppercut): + raise ValueError("Proportion too big.") + + atmp = np.partition(a, (lowercut, uppercut - 1), axis) + + sl = [slice(None)] * atmp.ndim + sl[axis] = slice(lowercut, uppercut) + return atmp[tuple(sl)] + + +@xp_capabilities(np_only=True) +def trim1(a, proportiontocut, tail='right', axis=0): + """Slice off a proportion from ONE end of the passed array distribution. + + If `proportiontocut` = 0.1, slices off 'leftmost' or 'rightmost' + 10% of scores. The lowest or highest values are trimmed (depending on + the tail). + Slice off less if proportion results in a non-integer slice index + (i.e. conservatively slices off `proportiontocut` ). + + Parameters + ---------- + a : array_like + Input array. + proportiontocut : float + Fraction to cut off of 'left' or 'right' of distribution. + tail : {'left', 'right'}, optional + Defaults to 'right'. + axis : int or None, optional + Axis along which to trim data. Default is 0. If None, compute over + the whole array `a`. + + Returns + ------- + trim1 : ndarray + Trimmed version of array `a`. The order of the trimmed content is + undefined. + + Examples + -------- + Create an array of 10 values and trim 20% of its lowest values: + + >>> import numpy as np + >>> from scipy import stats + >>> a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + >>> stats.trim1(a, 0.2, 'left') + array([2, 4, 3, 5, 6, 7, 8, 9]) + + Note that the elements of the input array are trimmed by value, but the + output array is not necessarily sorted. + + The proportion to trim is rounded down to the nearest integer. For + instance, trimming 25% of the values from an array of 10 values will + return an array of 8 values: + + >>> b = np.arange(10) + >>> stats.trim1(b, 1/4).shape + (8,) + + Multidimensional arrays can be trimmed along any axis or across the entire + array: + + >>> c = [2, 4, 6, 8, 0, 1, 3, 5, 7, 9] + >>> d = np.array([a, b, c]) + >>> stats.trim1(d, 0.8, axis=0).shape + (1, 10) + >>> stats.trim1(d, 0.8, axis=1).shape + (3, 2) + >>> stats.trim1(d, 0.8, axis=None).shape + (6,) + + """ + a = np.asarray(a) + if axis is None: + a = a.ravel() + axis = 0 + + nobs = a.shape[axis] + + # avoid possible corner case + if proportiontocut >= 1: + return [] + + if tail.lower() == 'right': + lowercut = 0 + uppercut = nobs - int(proportiontocut * nobs) + + elif tail.lower() == 'left': + lowercut = int(proportiontocut * nobs) + uppercut = nobs + + atmp = np.partition(a, (lowercut, uppercut - 1), axis) + + sl = [slice(None)] * atmp.ndim + sl[axis] = slice(lowercut, uppercut) + return atmp[tuple(sl)] + + +@xp_capabilities(marray=True) +@_axis_nan_policy_factory(lambda x: x, result_to_tuple=lambda x, _: (x,), n_outputs=1) +def trim_mean(a, proportiontocut, axis=0): + """Return mean of array after trimming a specified fraction of extreme values. + + Removes the specified proportion of elements from *each* end of the + sorted array, then computes the mean of the remaining elements. + + Parameters + ---------- + a : array_like + Input array. + proportiontocut : float + Fraction of the most positive and most negative elements to remove. + When the specified proportion does not result in an integer number of + elements, the number of elements to trim is rounded down. + axis : int or None, default: 0 + Axis along which the trimmed means are computed. + If None, compute over the raveled array. + + Returns + ------- + trim_mean : ndarray + Mean of trimmed array. + + See Also + -------- + trimboth : Remove a proportion of elements from each end of an array. + tmean : Compute the mean after trimming values outside specified limits. + + Notes + ----- + For 1-D array `a`, `trim_mean` is approximately equivalent to the following + calculation:: + + import numpy as np + a = np.sort(a) + m = int(proportiontocut * len(a)) + np.mean(a[m: len(a) - m]) + + Examples + -------- + >>> import numpy as np + >>> from scipy import stats + >>> x = [1, 2, 3, 5] + >>> stats.trim_mean(x, 0.25) + 2.5 + + When the specified proportion does not result in an integer number of + elements, the number of elements to trim is rounded down. + + >>> stats.trim_mean(x, 0.24999) == np.mean(x) + True + + Use `axis` to specify the axis along which the calculation is performed. + + >>> x2 = [[1, 2, 3, 5], + ... [10, 20, 30, 50]] + >>> stats.trim_mean(x2, 0.25) + array([ 5.5, 11. , 16.5, 27.5]) + >>> stats.trim_mean(x2, 0.25, axis=1) + array([ 2.5, 25. ]) + + """ + xp = array_namespace(a) + + a = xp.asarray(a) + + if xp_size(a) == 0: + return _get_nan(a, xp=xp) + + if axis is None: + a = xp_ravel(a) + axis = 0 + + nobs = _count_nonmasked(a, axis=axis, keepdims=True, xp=xp) + lowercut = proportiontocut * nobs + nobs, lowercut = ((nobs, int(lowercut)) if not is_marray(xp) + else (xp.astype(nobs, xp.int64), xp.astype(lowercut, xp.int64))) + uppercut = nobs - lowercut + if not is_marray(xp) and (lowercut > uppercut): + raise ValueError("Proportion too big.") + + atmp = (np.partition(a, (lowercut, uppercut - 1), axis) if is_numpy(xp) + else xp.sort(a, axis=axis)) + + if is_marray(xp): + indices = xp.arange(a.shape[-1]) # axis_nan_policy decorator -> axis=-1 + mask = (indices < lowercut) | (indices >= (nobs - lowercut)) | atmp.mask + trimmed = xp.asarray(atmp.data, mask=mask.data) + else: + sl = [slice(None)] * atmp.ndim + sl[axis] = slice(lowercut, uppercut) + trimmed = atmp[tuple(sl)] + + trimmed = xp_promote(trimmed, force_floating=True, xp=xp) + return xp.mean(trimmed, axis=axis) + + +F_onewayResult = namedtuple('F_onewayResult', ('statistic', 'pvalue')) + + +def _f_oneway_is_too_small(samples, kwargs=None, axis=-1): + message = f"At least two samples are required; got {len(samples)}." + if len(samples) < 2: + raise TypeError(message) + + # Check this after forming alldata, so shape errors are detected + # and reported before checking for 0 length inputs. + if any(sample.shape[axis] == 0 for sample in samples): + return True + + # Must have at least one group with length greater than 1. + if all(sample.shape[axis] == 1 for sample in samples): + msg = ('all input arrays have length 1. f_oneway requires that at ' + 'least one input has length greater than 1.') + warnings.warn(SmallSampleWarning(msg), stacklevel=2) + return True + + return False + + +# Torch GPU need fdtrc +@xp_capabilities(cpu_only=True, exceptions=['cupy'], marray=True) +@_axis_nan_policy_factory( + F_onewayResult, n_samples=None, too_small=_f_oneway_is_too_small) +def f_oneway(*samples, axis=0, equal_var=True): + """Perform one-way ANOVA. + + The one-way ANOVA tests the null hypothesis that two or more groups have + the same population mean. The test is applied to samples from two or + more groups, possibly with differing sizes. + + Parameters + ---------- + *samples : array_like + The sample measurements for each group. There must be at least + two arguments. If the arrays are multidimensional, then all the + dimensions of the array must be the same except for `axis`. + axis : int, optional + Axis of the input arrays along which the test is applied. + Default is 0. + equal_var : bool, optional + If True (default), perform a standard one-way ANOVA test that + assumes equal population variances [2]_. + If False, perform Welch's ANOVA test, which does not assume + equal population variances [4]_. + + .. versionadded:: 1.16.0 + + Returns + ------- + statistic : float + The computed F statistic of the test. + pvalue : float + The associated p-value from the F distribution. + + Warns + ----- + `~scipy.stats.ConstantInputWarning` + Emitted if all values within each of the input arrays are identical. + In this case the F statistic is either infinite or isn't defined, + so ``np.inf`` or ``np.nan`` is returned. + + RuntimeWarning + Emitted if the length of any input array is 0, or if all the input + arrays have length 1. ``np.nan`` is returned for the F statistic + and the p-value in these cases. + + Notes + ----- + The ANOVA test has important assumptions that must be satisfied in order + for the associated p-value to be valid. + + 1. The samples are independent. + 2. Each sample is from a normally distributed population. + 3. The population standard deviations of the groups are all equal. This + property is known as homoscedasticity. + + If these assumptions are not true for a given set of data, it may still + be possible to use the Kruskal-Wallis H-test (`scipy.stats.kruskal`) or + the Alexander-Govern test (`scipy.stats.alexandergovern`) although with + some loss of power. + + The length of each group must be at least one, and there must be at + least one group with length greater than one. If these conditions + are not satisfied, a warning is generated and (``np.nan``, ``np.nan``) + is returned. + + If all values in each group are identical, and there exist at least two + groups with different values, the function generates a warning and + returns (``np.inf``, 0). + + If all values in all groups are the same, function generates a warning + and returns (``np.nan``, ``np.nan``). + + The algorithm is from Heiman [2]_, pp.394-7. + + References + ---------- + .. [1] R. Lowry, "Concepts and Applications of Inferential Statistics", + Chapter 14, 2014, http://vassarstats.net/textbook/ + + .. [2] G.W. Heiman, "Understanding research methods and statistics: An + integrated introduction for psychology", Houghton, Mifflin and + Company, 2001. + + .. [3] J.H. McDonald, "Handbook of Biological Statistics", + One-way ANOVA, 2014. + http://www.biostathandbook.com/onewayanova.html + + .. [4] B. L. Welch, "On the Comparison of Several Mean Values: + An Alternative Approach", Biometrika, vol. 38, no. 3/4, + pp. 330-336, 1951. :doi:`10.2307/2332579`. + + .. [5] J.H. McDonald, R. Seed and R.K. Koehn, "Allozymes and + morphometric characters of three species of Mytilus in + the Northern and Southern Hemispheres", + Marine Biology, vol. 111, pp. 323-333, 1991. + + Examples + -------- + >>> import numpy as np + >>> from scipy.stats import f_oneway + + Here are some data [3]_ on a shell measurement (the length of the anterior + adductor muscle scar, standardized by dividing by length) in the mussel + Mytilus trossulus from five locations: Tillamook, Oregon; Newport, Oregon; + Petersburg, Alaska; Magadan, Russia; and Tvarminne, Finland, taken from a + much larger data set used in [5]_. + + >>> tillamook = [0.0571, 0.0813, 0.0831, 0.0976, 0.0817, 0.0859, 0.0735, + ... 0.0659, 0.0923, 0.0836] + >>> newport = [0.0873, 0.0662, 0.0672, 0.0819, 0.0749, 0.0649, 0.0835, + ... 0.0725] + >>> petersburg = [0.0974, 0.1352, 0.0817, 0.1016, 0.0968, 0.1064, 0.105] + >>> magadan = [0.1033, 0.0915, 0.0781, 0.0685, 0.0677, 0.0697, 0.0764, + ... 0.0689] + >>> tvarminne = [0.0703, 0.1026, 0.0956, 0.0973, 0.1039, 0.1045] + >>> f_oneway(tillamook, newport, petersburg, magadan, tvarminne) + F_onewayResult(statistic=7.121019471642447, pvalue=0.0002812242314534544) + + `f_oneway` accepts multidimensional input arrays. When the inputs + are multidimensional and `axis` is not given, the test is performed + along the first axis of the input arrays. For the following data, the + test is performed three times, once for each column. + + >>> a = np.array([[9.87, 9.03, 6.81], + ... [7.18, 8.35, 7.00], + ... [8.39, 7.58, 7.68], + ... [7.45, 6.33, 9.35], + ... [6.41, 7.10, 9.33], + ... [8.00, 8.24, 8.44]]) + >>> b = np.array([[6.35, 7.30, 7.16], + ... [6.65, 6.68, 7.63], + ... [5.72, 7.73, 6.72], + ... [7.01, 9.19, 7.41], + ... [7.75, 7.87, 8.30], + ... [6.90, 7.97, 6.97]]) + >>> c = np.array([[3.31, 8.77, 1.01], + ... [8.25, 3.24, 3.62], + ... [6.32, 8.81, 5.19], + ... [7.48, 8.83, 8.91], + ... [8.59, 6.01, 6.07], + ... [3.07, 9.72, 7.48]]) + >>> F = f_oneway(a, b, c) + >>> F.statistic + array([1.75676344, 0.03701228, 3.76439349]) + >>> F.pvalue + array([0.20630784, 0.96375203, 0.04733157]) + + Welch ANOVA will be performed if `equal_var` is False. + + """ + xp = array_namespace(*samples) + samples = xp_promote(*samples, force_floating=True, xp=xp) + + if len(samples) < 2: + raise TypeError('at least two inputs are required;' + f' got {len(samples)}.') + + # ANOVA on N groups, each in its own array + num_groups = len(samples) + + # axis is guaranteed to be -1 by the _axis_nan_policy decorator + alldata = xp.concat(samples, axis=-1) + bign = _count_nonmasked(alldata, axis=-1, xp=xp) + + # Check if the inputs are too small (for testing _axis_nan_policy decorator) + if _f_oneway_is_too_small(samples): + NaN = _get_nan(*samples, xp=xp) + return F_onewayResult(NaN, NaN) + + # Check if all values within each group are identical, and if the common + # value in at least one group is different from that in another group. + # Based on https://github.com/scipy/scipy/issues/11669 + + # If axis=0, say, and the groups have shape (n0, ...), (n1, ...), ..., + # then is_const is a boolean array with shape (num_groups, ...). + # It is True if the values within the groups along the axis slice are + # identical. In the typical case where each input array is 1-d, is_const is + # a 1-d array with length num_groups. + is_const = xp.concat([xp.all(xp.diff(sample, axis=-1) == 0, axis=-1, keepdims=True) + for sample in samples], axis=-1) + + # all_const is a boolean array with shape (...) (see previous comment). + # It is True if the values within each group along the axis slice are + # the same (e.g. [[3, 3, 3], [5, 5, 5, 5], [4, 4, 4]]). + all_const = xp.all(is_const, axis=-1) + + # all_same_const is True if all the values in the groups along the axis=0 + # slice are the same (e.g. [[3, 3, 3], [3, 3, 3, 3], [3, 3, 3]]). + all_same_const = xp.all(xp.diff(alldata, axis=-1) == 0, axis=-1) + + if not isinstance(equal_var, bool): + raise TypeError("Expected a boolean value for 'equal_var'") + + if equal_var: + # Determine the mean of the data, and subtract that from all inputs to a + # variance (via sum_of_sq / sq_of_sum) calculation. Variance is invariant + # to a shift in location, and centering all data around zero vastly + # improves numerical stability. + offset = xp.mean(alldata, axis=-1, keepdims=True) + alldata = alldata - offset + + normalized_ss = xp.sum(alldata, axis=-1)**2. / bign + + sstot = xp.vecdot(alldata, alldata, axis=-1) - normalized_ss + + ssbn = 0 + for sample in samples: + smo_ss = xp.sum(sample - offset, axis=-1)**2. + ssbn = ssbn + smo_ss / _count_nonmasked(sample, axis=-1, xp=xp) + + # Naming: variables ending in bn/b are for "between treatments", wn/w are + # for "within treatments" + ssbn = ssbn - normalized_ss + sswn = sstot - ssbn + dfbn = num_groups - 1 + dfwn = bign - num_groups + msb = ssbn / dfbn + msw = sswn / dfwn + with np.errstate(divide='ignore', invalid='ignore'): + f = msb / msw + dfn, dfd = dfbn, dfwn + + else: + # calculate basic statistics for each sample + # Beginning of second paragraph [4] page 1: + # "As a particular case $y_t$ may be the means ... of samples + y_t = xp.stack([xp.mean(sample, axis=-1) for sample in samples]) + # "... of $n_t$ observations..." + if is_marray(xp): + n_t = xp.stack([_count_nonmasked(sample, axis=-1, xp=xp) + for sample in samples]) + n_t = xp.asarray(n_t, dtype=n_t.dtype) + else: + n_t = xp.asarray([sample.shape[-1] for sample in samples], dtype=y_t.dtype) + n_t = xp.reshape(n_t, (-1,) + (1,) * (y_t.ndim - 1)) + # "... from $k$ different normal populations..." + k = len(samples) + # "The separate samples provide estimates $s_t^2$ of the $\sigma_t^2$." + s_t2 = xp.stack([xp.var(sample, axis=-1, correction=1) for sample in samples]) + + # calculate weight by number of data and variance + # "we have $\lambda_t = 1 / n_t$ ... where w_t = 1 / {\lambda_t s_t^2}$" + w_t = n_t / s_t2 + # sum of w_t + s_w_t = xp.sum(w_t, axis=0) + + # calculate adjusted grand mean + # "... and $\hat{y} = \sum w_t y_t / \sum w_t$. When all..." + axis_zero = -w_t.ndim + y_hat = xp.vecdot(w_t, y_t, axis=axis_zero) / xp.sum(w_t, axis=0) + + # adjust f statistic + # ref.[4] p.334 eq.29 + numerator = xp.vecdot(w_t, (y_t - y_hat)**2, axis=axis_zero) / (k - 1) + denominator = ( + 1 + 2 * (k - 2) / (k**2 - 1) * + xp.vecdot(1 / (n_t - 1), (1 - w_t / s_w_t)**2, axis=axis_zero) + ) + f = numerator / denominator + + # degree of freedom 1 + # ref.[4] p.334 eq.30 + hat_f1 = k - 1 + + # adjusted degree of freedom 2 + # ref.[4] p.334 eq.30 + hat_f2 = ( + (k**2 - 1) / + (3 * xp.vecdot(1 / (n_t - 1), (1 - w_t / s_w_t)**2, axis=axis_zero)) + ) + + dfn, dfd = hat_f1, hat_f2 + + # Fix any f values that should be inf or nan because the corresponding + # inputs were constant. + f = xpx.at(f)[all_const].set(xp.inf) + f = xpx.at(f)[all_same_const].set(xp.nan) + + # calculate p value + # ref.[4] p.334 eq.28 + prob = special.fdtrc(dfn, dfd, f) + + f, prob = (f[()], prob[()]) if f.ndim == 0 else (f, prob) + return F_onewayResult(f, prob) + + +@dataclass +class AlexanderGovernResult: + statistic: float + pvalue: float + + +@xp_capabilities(marray=True) +@_axis_nan_policy_factory( + AlexanderGovernResult, n_samples=None, + result_to_tuple=lambda x, _: (x.statistic, x.pvalue), + too_small=1 +) +def alexandergovern(*samples, nan_policy='propagate', axis=0): + """Performs the Alexander Govern test. + + The Alexander-Govern approximation tests the equality of k independent + means in the face of heterogeneity of variance. The test is applied to + samples from two or more groups, possibly with differing sizes. + + Parameters + ---------- + *samples : array_like + The sample measurements for each group. There must be at least + two samples, and each sample must contain at least two observations. + nan_policy : {'propagate', 'raise', 'omit'}, optional + Defines how to handle when input contains nan. + The following options are available (default is 'propagate'): + + * 'propagate': returns nan + * 'raise': throws an error + * 'omit': performs the calculations ignoring nan values + + Returns + ------- + res : AlexanderGovernResult + An object with attributes: + + statistic : float + The computed A statistic of the test. + pvalue : float + The associated p-value from the chi-squared distribution. + + Warns + ----- + `~scipy.stats.ConstantInputWarning` + Raised if an input is a constant array. The statistic is not defined + in this case, so ``np.nan`` is returned. + + See Also + -------- + f_oneway : one-way ANOVA + + Notes + ----- + The use of this test relies on several assumptions. + + 1. The samples are independent. + 2. Each sample is from a normally distributed population. + 3. Unlike `f_oneway`, this test does not assume on homoscedasticity, + instead relaxing the assumption of equal variances. + + Input samples must be finite, one dimensional, and with size greater than + one. + + References + ---------- + .. [1] Alexander, Ralph A., and Diane M. Govern. "A New and Simpler + Approximation for ANOVA under Variance Heterogeneity." Journal + of Educational Statistics, vol. 19, no. 2, 1994, pp. 91-101. + https://www.jstor.org/stable/1165140 + + Examples + -------- + >>> from scipy.stats import alexandergovern + + Here are some data on annual percentage rate of interest charged on + new car loans at nine of the largest banks in four American cities + taken from the National Institute of Standards and Technology's + ANOVA dataset. + + We use `alexandergovern` to test the null hypothesis that all cities + have the same mean APR against the alternative that the cities do not + all have the same mean APR. We decide that a significance level of 5% + is required to reject the null hypothesis in favor of the alternative. + + >>> atlanta = [13.75, 13.75, 13.5, 13.5, 13.0, 13.0, 13.0, 12.75, 12.5] + >>> chicago = [14.25, 13.0, 12.75, 12.5, 12.5, 12.4, 12.3, 11.9, 11.9] + >>> houston = [14.0, 14.0, 13.51, 13.5, 13.5, 13.25, 13.0, 12.5, 12.5] + >>> memphis = [15.0, 14.0, 13.75, 13.59, 13.25, 12.97, 12.5, 12.25, + ... 11.89] + >>> alexandergovern(atlanta, chicago, houston, memphis) + AlexanderGovernResult(statistic=4.65087071883494, + pvalue=0.19922132490385214) + + The p-value is 0.1992, indicating a nearly 20% chance of observing + such an extreme value of the test statistic under the null hypothesis. + This exceeds 5%, so we do not reject the null hypothesis in favor of + the alternative. + + """ + xp = array_namespace(*samples) + samples = _alexandergovern_input_validation(samples, nan_policy, axis, xp=xp) + + # The following formula numbers reference the equation described on + # page 92 by Alexander, Govern. Formulas 5, 6, and 7 describe other + # tests that serve as the basis for equation (8) but are not needed + # to perform the test. + + # precalculate mean and length of each sample + lengths = [_count_nonmasked(sample, axis=-1, xp=xp) for sample in samples] + means = xp.stack([_xp_mean(sample, axis=-1) for sample in samples]) + + # (1) determine standard error of the mean for each sample + se2 = [(_xp_var(sample, correction=1, axis=-1) / length) + for sample, length in zip(samples, lengths)] + standard_errors_squared = xp.stack(se2) + standard_errors = standard_errors_squared**0.5 + + # Special case: statistic is NaN when variance is zero + eps = xp.finfo(standard_errors.dtype).eps + zero = standard_errors <= xp.abs(eps * means) + NaN = xp.asarray(xp.nan, dtype=standard_errors.dtype) + standard_errors = xp.where(zero, NaN, standard_errors) + + # (2) define a weight for each sample + inv_sq_se = 1 / standard_errors_squared + weights = inv_sq_se / xp.sum(inv_sq_se, axis=0, keepdims=True) + + # (3) determine variance-weighted estimate of the common mean + # Consider replacing with vecdot when data-apis/array-api#910 is resolved + var_w = xp.sum(weights * means, axis=0, keepdims=True) + + # (4) determine one-sample t statistic for each group + t_stats = _demean(means, var_w, axis=0, xp=xp) / standard_errors + + # calculate parameters to be used in transformation + if is_marray(xp): + v = xp.stack(lengths) - 1 + else: + v = xp.asarray(lengths, dtype=t_stats.dtype) - 1 + # align along 0th axis, which corresponds with separate samples + v = xp.reshape(v, (-1,) + (1,)*(t_stats.ndim-1)) + a = v - .5 + b = 48 * a**2 + c = (a * xp.log(1 + (t_stats ** 2)/v))**.5 + + # (8) perform a normalizing transformation on t statistic + z = (c + ((c**3 + 3*c)/b) - + ((4*c**7 + 33*c**5 + 240*c**3 + 855*c) / + (b**2*10 + 8*b*c**4 + 1000*b))) + + # (9) calculate statistic + A = xp.vecdot(z, z, axis=-z.ndim) + A = A[()] if A.ndim == 0 else A # data-apis/array-api-compat#355 + + # "[the p value is determined from] central chi-square random deviates + # with k - 1 degrees of freedom". Alexander, Govern (94) + df = xp.asarray(len(samples) - 1, dtype=A.dtype) + chi2 = _SimpleChi2(df) + p = _get_pvalue(A, chi2, alternative='greater', symmetric=False, xp=xp) + return AlexanderGovernResult(A, p) + + +def _alexandergovern_input_validation(samples, nan_policy, axis, xp): + if len(samples) < 2: + raise TypeError(f"2 or more inputs required, got {len(samples)}") + + for sample in samples: + if sample.shape[axis] <= 1: + raise ValueError("Input sample size must be greater than one.") + + samples = [xp.moveaxis(sample, axis, -1) for sample in samples] + + return samples + + +def _pearsonr_fisher_ci(r, n, confidence_level, alternative): + """ + Compute the confidence interval for Pearson's R. + + Fisher's transformation is used to compute the confidence interval + (https://en.wikipedia.org/wiki/Fisher_transformation). + """ + xp = array_namespace(r) + + ones = xp.ones_like(r) + n = xp.asarray(n, dtype=r.dtype, device=xp_device(r)) + confidence_level = xp.asarray(confidence_level, dtype=r.dtype, device=xp_device(r)) + + with np.errstate(divide='ignore', invalid='ignore'): + zr = xp.atanh(r) + se = xp.sqrt(1 / (n - 3)) + + if alternative == "two-sided": + h = special.ndtri(0.5 + confidence_level/2) + zlo = zr - h*se + zhi = zr + h*se + rlo = xp.tanh(zlo) + rhi = xp.tanh(zhi) + elif alternative == "less": + h = special.ndtri(confidence_level) + zhi = zr + h*se + rhi = xp.tanh(zhi) + rlo = -ones + else: + # alternative == "greater": + h = special.ndtri(confidence_level) + zlo = zr - h*se + rlo = xp.tanh(zlo) + rhi = ones + + mask = (n <= 3) + if mask.ndim == 0: + # This is Array API legal, but Dask doesn't like it. + mask = xp.broadcast_to(mask, rlo.shape) + + rlo = xpx.at(rlo)[mask].set(-1) + rhi = xpx.at(rhi)[mask].set(1) + + rlo = rlo[()] if rlo.ndim == 0 else rlo + rhi = rhi[()] if rhi.ndim == 0 else rhi + return ConfidenceInterval(low=rlo, high=rhi) + + +def _pearsonr_bootstrap_ci(confidence_level, method, x, y, alternative, axis): + """ + Compute the confidence interval for Pearson's R using the bootstrap. + """ + def statistic(x, y, axis): + statistic, _ = pearsonr(x, y, axis=axis) + return statistic + + res = bootstrap((x, y), statistic, confidence_level=confidence_level, axis=axis, + paired=True, alternative=alternative, **method._asdict()) + # for one-sided confidence intervals, bootstrap gives +/- inf on one side + res.confidence_interval = np.clip(res.confidence_interval, -1, 1) + + return ConfidenceInterval(*res.confidence_interval) + + +ConfidenceInterval = namedtuple('ConfidenceInterval', ['low', 'high']) + +PearsonRResultBase = _make_tuple_bunch('PearsonRResultBase', + ['statistic', 'pvalue'], []) + + +class PearsonRResult(PearsonRResultBase): + """ + Result of `scipy.stats.pearsonr` + + Attributes + ---------- + statistic : float + Pearson product-moment correlation coefficient. + pvalue : float + The p-value associated with the chosen alternative. + + Methods + ------- + confidence_interval + Computes the confidence interval of the correlation + coefficient `statistic` for the given confidence level. + + """ + def __init__(self, statistic, pvalue, alternative, n, x, y, axis): + super().__init__(statistic, pvalue) + self._alternative = alternative + self._n = n + self._x = x + self._y = y + self._axis = axis + + # add alias for consistency with other correlation functions + self.correlation = statistic + + def confidence_interval(self, confidence_level=0.95, method=None): + """ + The confidence interval for the correlation coefficient. + + Compute the confidence interval for the correlation coefficient + ``statistic`` with the given confidence level. + + If `method` is not provided, + The confidence interval is computed using the Fisher transformation + F(r) = arctanh(r) [1]_. When the sample pairs are drawn from a + bivariate normal distribution, F(r) approximately follows a normal + distribution with standard error ``1/sqrt(n - 3)``, where ``n`` is the + length of the original samples along the calculation axis. When + ``n <= 3``, this approximation does not yield a finite, real standard + error, so we define the confidence interval to be -1 to 1. + + If `method` is an instance of `BootstrapMethod`, the confidence + interval is computed using `scipy.stats.bootstrap` with the provided + configuration options and other appropriate settings. In some cases, + confidence limits may be NaN due to a degenerate resample, and this is + typical for very small samples (~6 observations). + + Parameters + ---------- + confidence_level : float + The confidence level for the calculation of the correlation + coefficient confidence interval. Default is 0.95. + + method : BootstrapMethod, optional + Defines the method used to compute the confidence interval. See + method description for details. + + .. versionadded:: 1.11.0 + + Returns + ------- + ci : namedtuple + The confidence interval is returned in a ``namedtuple`` with + fields `low` and `high`. + + References + ---------- + .. [1] "Pearson correlation coefficient", Wikipedia, + https://en.wikipedia.org/wiki/Pearson_correlation_coefficient + """ + if isinstance(method, BootstrapMethod): + xp = array_namespace(self._x) + message = ('`method` must be `None` if `pearsonr` ' + 'arguments were not NumPy arrays.') + if not is_numpy(xp): + raise ValueError(message) + + ci = _pearsonr_bootstrap_ci(confidence_level, method, self._x, self._y, + self._alternative, self._axis) + elif method is None: + ci = _pearsonr_fisher_ci(self.statistic, self._n, confidence_level, + self._alternative) + else: + message = ('`method` must be an instance of `BootstrapMethod` ' + 'or None.') + raise ValueError(message) + return ci + + +# Missing special.betainc on torch +@xp_capabilities(cpu_only=True, exceptions=['cupy', 'jax.numpy'], marray=True, + extra_note='Only the default `method` is compatible with MArray input.') +def pearsonr(x, y, *, alternative='two-sided', method=None, axis=0): + r""" + Pearson correlation coefficient and p-value for testing non-correlation. + + The Pearson correlation coefficient [1]_ measures the linear relationship + between two datasets. Like other correlation + coefficients, this one varies between -1 and +1 with 0 implying no + correlation. Correlations of -1 or +1 imply an exact linear relationship. + Positive correlations imply that as x increases, so does y. Negative + correlations imply that as x increases, y decreases. + + This function also performs a test of the null hypothesis that the + distributions underlying the samples are uncorrelated and normally + distributed. (See Kowalski [3]_ + for a discussion of the effects of non-normality of the input on the + distribution of the correlation coefficient.) + The p-value roughly indicates the probability of an uncorrelated system + producing datasets that have a Pearson correlation at least as extreme + as the one computed from these datasets. + + Parameters + ---------- + x : array_like + Input array. + y : array_like + Input array. + alternative : {'two-sided', 'greater', 'less'}, optional + Defines the alternative hypothesis. Default is 'two-sided'. + The following options are available: + + * 'two-sided': the correlation is nonzero + * 'less': the correlation is negative (less than zero) + * 'greater': the correlation is positive (greater than zero) + + .. versionadded:: 1.9.0 + method : ResamplingMethod, optional + Defines the method used to compute the p-value. If `method` is an + instance of `PermutationMethod`/`MonteCarloMethod`, the p-value is + computed using + `scipy.stats.permutation_test`/`scipy.stats.monte_carlo_test` with the + provided configuration options and other appropriate settings. + Otherwise, the p-value is computed as documented in the notes. + + .. versionadded:: 1.11.0 + axis : int or None, default + Axis along which to perform the calculation. Default is 0. + If None, ravel both arrays before performing the calculation. + + .. versionadded:: 1.14.0 + + Returns + ------- + result : `~scipy.stats._result_classes.PearsonRResult` + An object with the following attributes: + + statistic : float + Pearson product-moment correlation coefficient. + pvalue : float + The p-value associated with the chosen alternative. + + The object has the following method: + + confidence_interval(confidence_level, method) + This computes the confidence interval of the correlation + coefficient `statistic` for the given confidence level. + The confidence interval is returned in a ``namedtuple`` with + fields `low` and `high`. If `method` is not provided, the + confidence interval is computed using the Fisher transformation + [1]_. If `method` is an instance of `BootstrapMethod`, the + confidence interval is computed using `scipy.stats.bootstrap` with + the provided configuration options and other appropriate settings. + In some cases, confidence limits may be NaN due to a degenerate + resample, and this is typical for very small samples (~6 + observations). + + Raises + ------ + ValueError + If `x` and `y` do not have length at least 2. + + Warns + ----- + `~scipy.stats.ConstantInputWarning` + Raised if an input is a constant array. The correlation coefficient + is not defined in this case, so ``np.nan`` is returned. + + `~scipy.stats.NearConstantInputWarning` + Raised if an input is "nearly" constant. The array ``x`` is considered + nearly constant if ``norm(x - mean(x)) < 1e-13 * abs(mean(x))``. + Numerical errors in the calculation ``x - mean(x)`` in this case might + result in an inaccurate calculation of r. + + See Also + -------- + spearmanr : Spearman rank-order correlation coefficient. + kendalltau : Kendall's tau, a correlation measure for ordinal data. + :ref:`hypothesis_pearsonr` : Extended example + + Notes + ----- + The correlation coefficient is calculated as follows: + + .. math:: + + r = \frac{\sum (x - m_x) (y - m_y)} + {\sqrt{\sum (x - m_x)^2 \sum (y - m_y)^2}} + + where :math:`m_x` is the mean of the vector x and :math:`m_y` is + the mean of the vector y. + + Under the assumption that x and y are drawn from + independent normal distributions (so the population correlation coefficient + is 0), the probability density function of the sample correlation + coefficient r is ([1]_, [2]_): + + .. math:: + f(r) = \frac{{(1-r^2)}^{n/2-2}}{\mathrm{B}(\frac{1}{2},\frac{n}{2}-1)} + + where n is the number of samples, and B is the beta function. This + is sometimes referred to as the exact distribution of r. This is + the distribution that is used in `pearsonr` to compute the p-value when + the `method` parameter is left at its default value (None). + The distribution is a beta distribution on the interval [-1, 1], + with equal shape parameters a = b = n/2 - 1. In terms of SciPy's + implementation of the beta distribution, the distribution of r is:: + + dist = scipy.stats.beta(n/2 - 1, n/2 - 1, loc=-1, scale=2) + + The default p-value returned by `pearsonr` is a two-sided p-value. For a + given sample with correlation coefficient r, the p-value is + the probability that abs(r') of a random sample x' and y' drawn from + the population with zero correlation would be greater than or equal + to abs(r). In terms of the object ``dist`` shown above, the p-value + for a given r and length n can be computed as:: + + p = 2*dist.cdf(-abs(r)) + + When n is 2, the above continuous distribution is not well-defined. + One can interpret the limit of the beta distribution as the shape + parameters a and b approach a = b = 0 as a discrete distribution with + equal probability masses at r = 1 and r = -1. More directly, one + can observe that, given the data x = [x1, x2] and y = [y1, y2], and + assuming x1 != x2 and y1 != y2, the only possible values for r are 1 + and -1. Because abs(r') for any sample x' and y' with length 2 will + be 1, the two-sided p-value for a sample of length 2 is always 1. + + For backwards compatibility, the object that is returned also behaves + like a tuple of length two that holds the statistic and the p-value. + + References + ---------- + .. [1] "Pearson correlation coefficient", Wikipedia, + https://en.wikipedia.org/wiki/Pearson_correlation_coefficient + .. [2] Student, "Probable error of a correlation coefficient", + Biometrika, Volume 6, Issue 2-3, 1 September 1908, pp. 302-310. + .. [3] C. J. Kowalski, "On the Effects of Non-Normality on the Distribution + of the Sample Product-Moment Correlation Coefficient" + Journal of the Royal Statistical Society. Series C (Applied + Statistics), Vol. 21, No. 1 (1972), pp. 1-12. + + Examples + -------- + >>> import numpy as np + >>> from scipy import stats + >>> x, y = [1, 2, 3, 4, 5, 6, 7], [10, 9, 2.5, 6, 4, 3, 2] + >>> res = stats.pearsonr(x, y) + >>> res + PearsonRResult(statistic=-0.828503883588428, pvalue=0.021280260007523286) + + To perform an exact permutation version of the test: + + >>> rng = np.random.default_rng(7796654889291491997) + >>> method = stats.PermutationMethod(n_resamples=np.inf, random_state=rng) + >>> stats.pearsonr(x, y, method=method) + PearsonRResult(statistic=-0.828503883588428, pvalue=0.028174603174603175) + + To perform the test under the null hypothesis that the data were drawn from + *uniform* distributions: + + >>> method = stats.MonteCarloMethod(rvs=(rng.uniform, rng.uniform)) + >>> stats.pearsonr(x, y, method=method) + PearsonRResult(statistic=-0.828503883588428, pvalue=0.0188) + + To produce an asymptotic 90% confidence interval: + + >>> res.confidence_interval(confidence_level=0.9) + ConfidenceInterval(low=-0.9644331982722841, high=-0.3460237473272273) + + And for a bootstrap confidence interval: + + >>> method = stats.BootstrapMethod(method='BCa', rng=rng) + >>> res.confidence_interval(confidence_level=0.9, method=method) + ConfidenceInterval(low=-0.9983163756488651, high=-0.22771001702132443) # may vary + + If N-dimensional arrays are provided, multiple tests are performed in a + single call according to the same conventions as most `scipy.stats` functions: + + >>> rng = np.random.default_rng(2348246935601934321) + >>> x = rng.standard_normal((8, 15)) + >>> y = rng.standard_normal((8, 15)) + >>> stats.pearsonr(x, y, axis=0).statistic.shape # between corresponding columns + (15,) + >>> stats.pearsonr(x, y, axis=1).statistic.shape # between corresponding rows + (8,) + + To perform all pairwise comparisons between slices of the arrays, + use standard NumPy broadcasting techniques. For instance, to compute the + correlation between all pairs of rows: + + >>> stats.pearsonr(x[:, np.newaxis, :], y, axis=-1).statistic.shape + (8, 8) + + There is a linear dependence between x and y if y = a + b*x + e, where + a,b are constants and e is a random error term, assumed to be independent + of x. For simplicity, assume that x is standard normal, a=0, b=1 and let + e follow a normal distribution with mean zero and standard deviation s>0. + + >>> rng = np.random.default_rng() + >>> s = 0.5 + >>> x = stats.norm.rvs(size=500, random_state=rng) + >>> e = stats.norm.rvs(scale=s, size=500, random_state=rng) + >>> y = x + e + >>> stats.pearsonr(x, y).statistic + 0.9001942438244763 + + This should be close to the exact value given by + + >>> 1/np.sqrt(1 + s**2) + 0.8944271909999159 + + For s=0.5, we observe a high level of correlation. In general, a large + variance of the noise reduces the correlation, while the correlation + approaches one as the variance of the error goes to zero. + + It is important to keep in mind that no correlation does not imply + independence unless (x, y) is jointly normal. Correlation can even be zero + when there is a very simple dependence structure: if X follows a + standard normal distribution, let y = abs(x). Note that the correlation + between x and y is zero. Indeed, since the expectation of x is zero, + cov(x, y) = E[x*y]. By definition, this equals E[x*abs(x)] which is zero + by symmetry. The following lines of code illustrate this observation: + + >>> y = np.abs(x) + >>> stats.pearsonr(x, y) + PearsonRResult(statistic=-0.05444919272687482, pvalue=0.22422294836207743) + + A non-zero correlation coefficient can be misleading. For example, if X has + a standard normal distribution, define y = x if x < 0 and y = 0 otherwise. + A simple calculation shows that corr(x, y) = sqrt(2/Pi) = 0.797..., + implying a high level of correlation: + + >>> y = np.where(x < 0, x, 0) + >>> stats.pearsonr(x, y) + PearsonRResult(statistic=0.861985781588, pvalue=4.813432002751103e-149) + + This is unintuitive since there is no dependence of x and y if x is larger + than zero which happens in about half of the cases if we sample x and y. + + For a more detailed example, see :ref:`hypothesis_pearsonr`. + + """ + xp = array_namespace(x, y) + x, y = xp_promote(x, y, force_floating=True, xp=xp) + dtype = x.dtype + + if not is_numpy(xp) and method is not None: + method = 'invalid' + + if axis is None: + x = xp.reshape(x, (-1,)) + y = xp.reshape(y, (-1,)) + axis = -1 + + axis_int = int(axis) + if axis_int != axis: + raise ValueError('`axis` must be an integer.') + axis = axis_int + + try: + np.broadcast_shapes(x.shape, y.shape) + # For consistency with other `stats` functions, we need to + # match the dimensionalities before looking at `axis`. + # (Note: this is not the NEP 5 / gufunc order of operations; + # see TestPearsonr::test_different_dimensionality for more information.) + ndim = max(x.ndim, y.ndim) + x = xp.reshape(x, (1,) * (ndim - x.ndim) + x.shape) + y = xp.reshape(y, (1,) * (ndim - y.ndim) + y.shape) + + except (ValueError, RuntimeError) as e: + message = '`x` and `y` must be broadcastable.' + raise ValueError(message) from e + + if x.shape[axis] != y.shape[axis]: + raise ValueError('`x` and `y` must have the same length along `axis`.') + + if x.shape[axis] < 2: + raise ValueError('`x` and `y` must have length at least 2.') + + x, y = _share_masks(x, y, xp=xp) + n = xp.asarray(_count_nonmasked(x, axis=axis), dtype=x.dtype) + + x = xp.moveaxis(x, axis, -1) + y = xp.moveaxis(y, axis, -1) + axis = -1 + + if xp.isdtype(dtype, "complex floating"): + raise ValueError('This function does not support complex data') + + x = xp.astype(x, dtype, copy=False) + y = xp.astype(y, dtype, copy=False) + threshold = xp.finfo(dtype).eps ** 0.75 + + # If an input is constant, the correlation coefficient is not defined. + if is_marray(xp): + # sort to ensure that we are comparing to a non-masked element + const_x = xp.all(x == xp.sort(x, axis=-1)[..., 0:1], axis=-1) + const_y = xp.all(y == xp.sort(y, axis=-1)[..., 0:1], axis=-1) + else: + const_x = xp.all(x == x[..., 0:1], axis=-1) + const_y = xp.all(y == y[..., 0:1], axis=-1) + const_xy = const_x | const_y + + any_const_xy = xp.any(const_xy) + lazy = is_lazy_array(const_xy) + if not lazy and any_const_xy: + msg = ("An input array is constant; the correlation coefficient " + "is not defined.") + warnings.warn(stats.ConstantInputWarning(msg), stacklevel=2) + if lazy or any_const_xy: + x = xp.where(const_x[..., xp.newaxis], xp.nan, x) + y = xp.where(const_y[..., xp.newaxis], xp.nan, y) + + if isinstance(method, PermutationMethod): + def statistic(y, axis): + statistic, _ = pearsonr(x, y, axis=axis, alternative=alternative) + return statistic + + res = permutation_test((y,), statistic, permutation_type='pairings', + axis=axis, alternative=alternative, **method._asdict()) + + return PearsonRResult(statistic=res.statistic, pvalue=res.pvalue, n=n, + alternative=alternative, x=x, y=y, axis=axis) + elif isinstance(method, MonteCarloMethod): + def statistic(x, y, axis): + statistic, _ = pearsonr(x, y, axis=axis, alternative=alternative) + return statistic + + # `monte_carlo_test` accepts an `rvs` tuple of callables, not an `rng` + # If the user specified an `rng`, replace it with the appropriate callables + method = method._asdict() + if (rng := method.pop('rng', None)) is not None: # goo-goo g'joob + rng = np.random.default_rng(rng) + method['rvs'] = rng.normal, rng.normal + + res = monte_carlo_test((x, y,), statistic=statistic, axis=axis, + alternative=alternative, **method) + + return PearsonRResult(statistic=res.statistic, pvalue=res.pvalue, n=n, + alternative=alternative, x=x, y=y, axis=axis) + elif method == 'invalid': + message = '`method` must be `None` if arguments are not NumPy arrays.' + raise ValueError(message) + elif method is not None: + message = ('`method` must be an instance of `PermutationMethod`, ' + '`MonteCarloMethod`, or None.') + raise ValueError(message) + + xmean = xp.mean(x, axis=axis, keepdims=True) + ymean = xp.mean(y, axis=axis, keepdims=True) + xm = x - xmean + ym = y - ymean + + # scipy.linalg.norm(xm) avoids premature overflow when xm is e.g. + # [-5e210, 5e210, 3e200, -3e200] + # but not when `axis` is provided, so scale manually. scipy.linalg.norm + # also raises an error with NaN input rather than returning NaN, so + # use np.linalg.norm. + xmax = xp.max(xp.abs(xm), axis=axis, keepdims=True) + ymax = xp.max(xp.abs(ym), axis=axis, keepdims=True) + with np.errstate(invalid='ignore', divide='ignore'): + normxm = xmax * xp_vector_norm(xm/xmax, axis=axis, keepdims=True) + normym = ymax * xp_vector_norm(ym/ymax, axis=axis, keepdims=True) + + if not lazy: + nconst_x = xp.any(normxm < threshold*xp.abs(xmean), axis=axis) + nconst_y = xp.any(normym < threshold*xp.abs(ymean), axis=axis) + nconst_xy = nconst_x | nconst_y + if xp.any(nconst_xy & (~const_xy)): + # If all the values in x (likewise y) are very close to the mean, + # the loss of precision that occurs in the subtraction xm = x - xmean + # might result in large errors in r. + msg = ("An input array is nearly constant; the computed " + "correlation coefficient may be inaccurate.") + warnings.warn(stats.NearConstantInputWarning(msg), stacklevel=2) + + with np.errstate(invalid='ignore', divide='ignore'): + r = xp.vecdot(xm / normxm, ym / normym, axis=axis) + + # Presumably, if abs(r) > 1, then it is only some small artifact of + # floating point arithmetic. + r = xp.clip(r, -1., 1.) + r = xpx.at(r, const_xy).set(xp.nan) + + # As explained in the docstring, the distribution of `r` under the null + # hypothesis is the beta distribution on (-1, 1) with a = b = n/2 - 1. + ab = xp.asarray(n/2 - 1, dtype=dtype, device=xp_device(x)) + dist = _SimpleBeta(ab, ab, loc=-1, scale=2) + pvalue = _get_pvalue(r, dist, alternative, xp=xp) + + mask = (n == 2) # return exactly 1.0 or -1.0 values for n == 2 case as promised + # data-apis/array-api-extra#196 + mxp = array_namespace(r._meta) if is_dask(xp) else xp + def special_case(r): + return mxp.where(mxp.isnan(r), mxp.nan, mxp.ones_like(r)) + r = xpx.apply_where(mask, r, mxp.round, fill_value=r) + pvalue = xpx.apply_where(mask, (r,), special_case, fill_value=pvalue) + + r = r[()] if r.ndim == 0 else r + pvalue = pvalue[()] if pvalue.ndim == 0 else pvalue + return PearsonRResult(statistic=r, pvalue=pvalue, n=n, + alternative=alternative, x=x, y=y, axis=axis) + + +@xp_capabilities(np_only=True) +def fisher_exact(table, alternative=None, *, method=None): + """Perform a Fisher exact test on a contingency table. + + For a 2x2 table, + the null hypothesis is that the true odds ratio of the populations + underlying the observations is one, and the observations were sampled + from these populations under a condition: the marginals of the + resulting table must equal those of the observed table. + The statistic is the unconditional maximum likelihood estimate of the odds + ratio, and the p-value is the probability under the null hypothesis of + obtaining a table at least as extreme as the one that was actually + observed. + + For other table sizes, or if `method` is provided, the null hypothesis + is that the rows and columns of the tables have fixed sums and are + independent; i.e., the table was sampled from a `scipy.stats.random_table` + distribution with the observed marginals. The statistic is the + probability mass of this distribution evaluated at `table`, and the + p-value is the percentage of the population of tables with statistic at + least as extreme (small) as that of `table`. There is only one alternative + hypothesis available: the rows and columns are not independent. + + There are other possible choices of statistic and two-sided + p-value definition associated with Fisher's exact test; please see the + Notes for more information. + + Parameters + ---------- + table : array_like of ints + A contingency table. Elements must be non-negative integers. + alternative : {'two-sided', 'less', 'greater'}, optional + Defines the alternative hypothesis for 2x2 tables; unused for other + table sizes. + The following options are available (default is 'two-sided'): + + * 'two-sided': the odds ratio of the underlying population is not one + * 'less': the odds ratio of the underlying population is less than one + * 'greater': the odds ratio of the underlying population is greater + than one + + See the Notes for more details. + + method : ResamplingMethod, optional + Defines the method used to compute the p-value. + If `method` is an instance of `PermutationMethod`/`MonteCarloMethod`, + the p-value is computed using + `scipy.stats.permutation_test`/`scipy.stats.monte_carlo_test` with the + provided configuration options and other appropriate settings. + Note that if `method` is an instance of `MonteCarloMethod`, the ``rvs`` + attribute must be left unspecified; Monte Carlo samples are always drawn + using the ``rvs`` method of `scipy.stats.random_table`. + Otherwise, the p-value is computed as documented in the notes. + + .. versionadded:: 1.15.0 + + Returns + ------- + res : SignificanceResult + An object containing attributes: + + statistic : float + For a 2x2 table with default `method`, this is the odds ratio - the + prior odds ratio not a posterior estimate. In all other cases, this + is the probability density of obtaining the observed table under the + null hypothesis of independence with marginals fixed. + pvalue : float + The probability under the null hypothesis of obtaining a + table at least as extreme as the one that was actually observed. + + Raises + ------ + ValueError + If `table` is not two-dimensional or has negative entries. + + See Also + -------- + chi2_contingency : Chi-square test of independence of variables in a + contingency table. This can be used as an alternative to + `fisher_exact` when the numbers in the table are large. + contingency.odds_ratio : Compute the odds ratio (sample or conditional + MLE) for a 2x2 contingency table. + barnard_exact : Barnard's exact test, which is a more powerful alternative + than Fisher's exact test for 2x2 contingency tables. + boschloo_exact : Boschloo's exact test, which is a more powerful + alternative than Fisher's exact test for 2x2 contingency tables. + :ref:`hypothesis_fisher_exact` : Extended example + + Notes + ----- + *Null hypothesis and p-values* + + The null hypothesis is that the true odds ratio of the populations + underlying the observations is one, and the observations were sampled at + random from these populations under a condition: the marginals of the + resulting table must equal those of the observed table. Equivalently, + the null hypothesis is that the input table is from the hypergeometric + distribution with parameters (as used in `hypergeom`) + ``M = a + b + c + d``, ``n = a + b`` and ``N = a + c``, where the + input table is ``[[a, b], [c, d]]``. This distribution has support + ``max(0, N + n - M) <= x <= min(N, n)``, or, in terms of the values + in the input table, ``min(0, a - d) <= x <= a + min(b, c)``. ``x`` + can be interpreted as the upper-left element of a 2x2 table, so the + tables in the distribution have form:: + + [ x n - x ] + [N - x M - (n + N) + x] + + For example, if:: + + table = [6 2] + [1 4] + + then the support is ``2 <= x <= 7``, and the tables in the distribution + are:: + + [2 6] [3 5] [4 4] [5 3] [6 2] [7 1] + [5 0] [4 1] [3 2] [2 3] [1 4] [0 5] + + The probability of each table is given by the hypergeometric distribution + ``hypergeom.pmf(x, M, n, N)``. For this example, these are (rounded to + three significant digits):: + + x 2 3 4 5 6 7 + p 0.0163 0.163 0.408 0.326 0.0816 0.00466 + + These can be computed with:: + + >>> import numpy as np + >>> from scipy.stats import hypergeom + >>> table = np.array([[6, 2], [1, 4]]) + >>> M = table.sum() + >>> n = table[0].sum() + >>> N = table[:, 0].sum() + >>> start, end = hypergeom.support(M, n, N) + >>> hypergeom.pmf(np.arange(start, end+1), M, n, N) + array([0.01631702, 0.16317016, 0.40792541, 0.32634033, 0.08158508, + 0.004662 ]) + + The two-sided p-value is the probability that, under the null hypothesis, + a random table would have a probability equal to or less than the + probability of the input table. For our example, the probability of + the input table (where ``x = 6``) is 0.0816. The x values where the + probability does not exceed this are 2, 6 and 7, so the two-sided p-value + is ``0.0163 + 0.0816 + 0.00466 ~= 0.10256``:: + + >>> from scipy.stats import fisher_exact + >>> res = fisher_exact(table, alternative='two-sided') + >>> res.pvalue + 0.10256410256410257 + + The one-sided p-value for ``alternative='greater'`` is the probability + that a random table has ``x >= a``, which in our example is ``x >= 6``, + or ``0.0816 + 0.00466 ~= 0.08626``:: + + >>> res = fisher_exact(table, alternative='greater') + >>> res.pvalue + 0.08624708624708627 + + This is equivalent to computing the survival function of the + distribution at ``x = 5`` (one less than ``x`` from the input table, + because we want to include the probability of ``x = 6`` in the sum):: + + >>> hypergeom.sf(5, M, n, N) + 0.08624708624708627 + + For ``alternative='less'``, the one-sided p-value is the probability + that a random table has ``x <= a``, (i.e. ``x <= 6`` in our example), + or ``0.0163 + 0.163 + 0.408 + 0.326 + 0.0816 ~= 0.9949``:: + + >>> res = fisher_exact(table, alternative='less') + >>> res.pvalue + 0.9953379953379957 + + This is equivalent to computing the cumulative distribution function + of the distribution at ``x = 6``: + + >>> hypergeom.cdf(6, M, n, N) + 0.9953379953379957 + + *Odds ratio* + + The calculated odds ratio is different from the value computed by the + R function ``fisher.test``. This implementation returns the "sample" + or "unconditional" maximum likelihood estimate, while ``fisher.test`` + in R uses the conditional maximum likelihood estimate. To compute the + conditional maximum likelihood estimate of the odds ratio, use + `scipy.stats.contingency.odds_ratio`. + + References + ---------- + .. [1] Fisher, Sir Ronald A, "The Design of Experiments: + Mathematics of a Lady Tasting Tea." ISBN 978-0-486-41151-4, 1935. + .. [2] "Fisher's exact test", + https://en.wikipedia.org/wiki/Fisher's_exact_test + + Examples + -------- + + >>> from scipy.stats import fisher_exact + >>> res = fisher_exact([[8, 2], [1, 5]]) + >>> res.statistic + 20.0 + >>> res.pvalue + 0.034965034965034975 + + For tables with shape other than ``(2, 2)``, provide an instance of + `scipy.stats.MonteCarloMethod` or `scipy.stats.PermutationMethod` for the + `method` parameter: + + >>> import numpy as np + >>> from scipy.stats import MonteCarloMethod + >>> rng = np.random.default_rng(4507195762371367) + >>> method = MonteCarloMethod(rng=rng) + >>> fisher_exact([[8, 2, 3], [1, 5, 4]], method=method) + SignificanceResult(statistic=np.float64(0.005782), pvalue=np.float64(0.0603)) + + For a more detailed example, see :ref:`hypothesis_fisher_exact`. + """ + hypergeom = distributions.hypergeom + # int32 is not enough for the algorithm + c = np.asarray(table, dtype=np.int64) + if not c.ndim == 2: + raise ValueError("The input `table` must have two dimensions.") + + if np.any(c < 0): + raise ValueError("All values in `table` must be nonnegative.") + + if not c.shape == (2, 2) or method is not None: + return _fisher_exact_rxc(c, alternative, method) + alternative = 'two-sided' if alternative is None else alternative + + if 0 in c.sum(axis=0) or 0 in c.sum(axis=1): + # If both values in a row or column are zero, the p-value is 1 and + # the odds ratio is NaN. + return SignificanceResult(np.nan, 1.0) + + if c[1, 0] > 0 and c[0, 1] > 0: + oddsratio = c[0, 0] * c[1, 1] / (c[1, 0] * c[0, 1]) + else: + oddsratio = np.inf + + n1 = c[0, 0] + c[0, 1] + n2 = c[1, 0] + c[1, 1] + n = c[0, 0] + c[1, 0] + + def pmf(x): + return hypergeom.pmf(x, n1 + n2, n1, n) + + if alternative == 'less': + pvalue = hypergeom.cdf(c[0, 0], n1 + n2, n1, n) + elif alternative == 'greater': + # Same formula as the 'less' case, but with the second column. + pvalue = hypergeom.cdf(c[0, 1], n1 + n2, n1, c[0, 1] + c[1, 1]) + elif alternative == 'two-sided': + mode = int((n + 1) * (n1 + 1) / (n1 + n2 + 2)) + pexact = hypergeom.pmf(c[0, 0], n1 + n2, n1, n) + pmode = hypergeom.pmf(mode, n1 + n2, n1, n) + + epsilon = 1e-14 + gamma = 1 + epsilon + + if np.abs(pexact - pmode) / np.maximum(pexact, pmode) <= epsilon: + return SignificanceResult(oddsratio, 1.) + + elif c[0, 0] < mode: + plower = hypergeom.cdf(c[0, 0], n1 + n2, n1, n) + if hypergeom.pmf(n, n1 + n2, n1, n) > pexact * gamma: + return SignificanceResult(oddsratio, plower) + + guess = _binary_search(lambda x: -pmf(x), -pexact * gamma, mode, n, xp=np) + pvalue = plower + hypergeom.sf(guess, n1 + n2, n1, n) + else: + pupper = hypergeom.sf(c[0, 0] - 1, n1 + n2, n1, n) + if hypergeom.pmf(0, n1 + n2, n1, n) > pexact * gamma: + return SignificanceResult(oddsratio, pupper) + + guess = _binary_search(pmf, pexact * gamma, 0, mode, xp=np) + pvalue = pupper + hypergeom.cdf(guess, n1 + n2, n1, n) + else: + msg = "`alternative` should be one of {'two-sided', 'less', 'greater'}" + raise ValueError(msg) + + pvalue = min(pvalue, 1.0) + + return SignificanceResult(oddsratio, pvalue) + + +def _fisher_exact_rxc(table, alternative, method): + if alternative is not None: + message = ('`alternative` must be the default (None) unless ' + '`table` has shape `(2, 2)` and `method is None`.') + raise ValueError(message) + + if table.size == 0: + raise ValueError("`table` must have at least one row and one column.") + + if table.shape[0] == 1 or table.shape[1] == 1 or np.all(table == 0): + # Only one such table with those marginals + return SignificanceResult(1.0, 1.0) + + if method is None: + method = stats.MonteCarloMethod() + + if isinstance(method, stats.PermutationMethod): + res = _fisher_exact_permutation_method(table, method) + elif isinstance(method, stats.MonteCarloMethod): + res = _fisher_exact_monte_carlo_method(table, method) + else: + message = (f'`{method=}` not recognized; if provided, `method` must be an ' + 'instance of `PermutationMethod` or `MonteCarloMethod`.') + raise ValueError(message) + + return SignificanceResult(np.clip(res.statistic, None, 1.0), res.pvalue) + + +def _fisher_exact_permutation_method(table, method): + x, y = _untabulate(table) + colsums = np.sum(table, axis=0) + rowsums = np.sum(table, axis=1) + X = stats.random_table(rowsums, colsums) + + # `permutation_test` with `permutation_type='pairings' permutes the order of `x`, + # which pairs observations in `x` with different observations in `y`. + def statistic(x): + # crosstab the resample and compute the statistic + table = stats.contingency.crosstab(x, y)[1] + return X.pmf(table) + + # tables with *smaller* probability mass are considered to be more extreme + return stats.permutation_test((x,), statistic, permutation_type='pairings', + alternative='less', **method._asdict()) + + +def _fisher_exact_monte_carlo_method(table, method): + method = method._asdict() + + if method.pop('rvs', None) is not None: + message = ('If the `method` argument of `fisher_exact` is an ' + 'instance of `MonteCarloMethod`, its `rvs` attribute ' + 'must be unspecified. Use the `MonteCarloMethod` `rng` argument ' + 'to control the random state.') + raise ValueError(message) + rng = np.random.default_rng(method.pop('rng', None)) + + # `random_table.rvs` produces random contingency tables with the given marginals + # under the null hypothesis of independence + shape = table.shape + colsums = np.sum(table, axis=0) + rowsums = np.sum(table, axis=1) + totsum = np.sum(table) + X = stats.random_table(rowsums, colsums, seed=rng) + + def rvs(size): + n_resamples = size[0] + return X.rvs(size=n_resamples).reshape(size) + + # axis signals to `monte_carlo_test` that statistic is vectorized, but we know + # how it will pass the table(s), so we don't need to use `axis` explicitly. + def statistic(table, axis): + shape_ = (-1,) + shape if table.size > totsum else shape + return X.pmf(table.reshape(shape_)) + + # tables with *smaller* probability mass are considered to be more extreme + return stats.monte_carlo_test(table.ravel(), rvs, statistic, + alternative='less', **method) + + +def _untabulate(table): + # converts a contingency table to paired samples indicating the + # correspondence between row and column indices + r, c = table.shape + x, y = [], [] + for i in range(r): + for j in range(c): + x.append([i] * table[i, j]) + y.append([j] * table[i, j]) + return np.concatenate(x), np.concatenate(y) + + +@xp_capabilities(np_only=True) +def spearmanr(a, b=None, axis=0, nan_policy='propagate', + alternative='two-sided'): + r"""Calculate a Spearman correlation coefficient with associated p-value. + + The Spearman rank-order correlation coefficient is a nonparametric measure + of the monotonicity of the relationship between two datasets. + Like other correlation coefficients, + this one varies between -1 and +1 with 0 implying no correlation. + Correlations of -1 or +1 imply an exact monotonic relationship. Positive + correlations imply that as x increases, so does y. Negative correlations + imply that as x increases, y decreases. + + The p-value roughly indicates the probability of an uncorrelated system + producing datasets that have a Spearman correlation at least as extreme + as the one computed from these datasets. Although calculation of the + p-value does not make strong assumptions about the distributions underlying + the samples, it is only accurate for very large samples (>500 + observations). For smaller sample sizes, consider a permutation test (see + Examples section below). + + Parameters + ---------- + a, b : 1D or 2D array_like, b is optional + One or two 1-D or 2-D arrays containing multiple variables and + observations. When these are 1-D, each represents a vector of + observations of a single variable. For the behavior in the 2-D case, + see under ``axis``, below. + Both arrays need to have the same length in the ``axis`` dimension. + axis : int or None, optional + If axis=0 (default), then each column represents a variable, with + observations in the rows. If axis=1, the relationship is transposed: + each row represents a variable, while the columns contain observations. + If axis=None, then both arrays will be raveled. + nan_policy : {'propagate', 'raise', 'omit'}, optional + Defines how to handle when input contains nan. + The following options are available (default is 'propagate'): + + * 'propagate': returns nan + * 'raise': throws an error + * 'omit': performs the calculations ignoring nan values + + alternative : {'two-sided', 'less', 'greater'}, optional + Defines the alternative hypothesis. Default is 'two-sided'. + The following options are available: + + * 'two-sided': the correlation is nonzero + * 'less': the correlation is negative (less than zero) + * 'greater': the correlation is positive (greater than zero) + + .. versionadded:: 1.7.0 + + Returns + ------- + res : SignificanceResult + An object containing attributes: + + statistic : float or ndarray (2-D square) + Spearman correlation matrix or correlation coefficient (if only 2 + variables are given as parameters). Correlation matrix is square + with length equal to total number of variables (columns or rows) in + ``a`` and ``b`` combined. + pvalue : float + The p-value for a hypothesis test whose null hypothesis + is that two samples have no ordinal correlation. See + `alternative` above for alternative hypotheses. `pvalue` has the + same shape as `statistic`. + + Raises + ------ + ValueError + If `axis` is not 0, 1 or None, or if the number of dimensions of `a` + is greater than 2, or if `b` is None and the number of dimensions of + `a` is less than 2. + + Warns + ----- + `~scipy.stats.ConstantInputWarning` + Raised if an input is a constant array. The correlation coefficient + is not defined in this case, so ``np.nan`` is returned. + + See Also + -------- + :ref:`hypothesis_spearmanr` : Extended example + + References + ---------- + .. [1] Zwillinger, D. and Kokoska, S. (2000). CRC Standard + Probability and Statistics Tables and Formulae. Chapman & Hall: New + York. 2000. + Section 14.7 + .. [2] Kendall, M. G. and Stuart, A. (1973). + The Advanced Theory of Statistics, Volume 2: Inference and Relationship. + Griffin. 1973. + Section 31.18 + + Examples + -------- + + >>> import numpy as np + >>> from scipy import stats + >>> res = stats.spearmanr([1, 2, 3, 4, 5], [5, 6, 7, 8, 7]) + >>> res.statistic + 0.8207826816681233 + >>> res.pvalue + 0.08858700531354381 + + >>> rng = np.random.default_rng() + >>> x2n = rng.standard_normal((100, 2)) + >>> y2n = rng.standard_normal((100, 2)) + >>> res = stats.spearmanr(x2n) + >>> res.statistic, res.pvalue + (-0.07960396039603959, 0.4311168705769747) + + >>> res = stats.spearmanr(x2n[:, 0], x2n[:, 1]) + >>> res.statistic, res.pvalue + (-0.07960396039603959, 0.4311168705769747) + + >>> res = stats.spearmanr(x2n, y2n) + >>> res.statistic + array([[ 1. , -0.07960396, -0.08314431, 0.09662166], + [-0.07960396, 1. , -0.14448245, 0.16738074], + [-0.08314431, -0.14448245, 1. , 0.03234323], + [ 0.09662166, 0.16738074, 0.03234323, 1. ]]) + >>> res.pvalue + array([[0. , 0.43111687, 0.41084066, 0.33891628], + [0.43111687, 0. , 0.15151618, 0.09600687], + [0.41084066, 0.15151618, 0. , 0.74938561], + [0.33891628, 0.09600687, 0.74938561, 0. ]]) + + >>> res = stats.spearmanr(x2n.T, y2n.T, axis=1) + >>> res.statistic + array([[ 1. , -0.07960396, -0.08314431, 0.09662166], + [-0.07960396, 1. , -0.14448245, 0.16738074], + [-0.08314431, -0.14448245, 1. , 0.03234323], + [ 0.09662166, 0.16738074, 0.03234323, 1. ]]) + + >>> res = stats.spearmanr(x2n, y2n, axis=None) + >>> res.statistic, res.pvalue + (0.044981624540613524, 0.5270803651336189) + + >>> res = stats.spearmanr(x2n.ravel(), y2n.ravel()) + >>> res.statistic, res.pvalue + (0.044981624540613524, 0.5270803651336189) + + >>> rng = np.random.default_rng() + >>> xint = rng.integers(10, size=(100, 2)) + >>> res = stats.spearmanr(xint) + >>> res.statistic, res.pvalue + (0.09800224850707953, 0.3320271757932076) + + For small samples, consider performing a permutation test instead of + relying on the asymptotic p-value. Note that to calculate the null + distribution of the statistic (for all possibly pairings between + observations in sample ``x`` and ``y``), only one of the two inputs needs + to be permuted. + + >>> x = [1.76405235, 0.40015721, 0.97873798, + ... 2.2408932, 1.86755799, -0.97727788] + >>> y = [2.71414076, 0.2488, 0.87551913, + ... 2.6514917, 2.01160156, 0.47699563] + + >>> def statistic(x): # permute only `x` + ... return stats.spearmanr(x, y).statistic + >>> res_exact = stats.permutation_test((x,), statistic, + ... permutation_type='pairings') + >>> res_asymptotic = stats.spearmanr(x, y) + >>> res_exact.pvalue, res_asymptotic.pvalue # asymptotic pvalue is too low + (0.10277777777777777, 0.07239650145772594) + + For a more detailed example, see :ref:`hypothesis_spearmanr`. + """ + if axis is not None and axis > 1: + raise ValueError("spearmanr only handles 1-D or 2-D arrays, " + f"supplied axis argument {axis}, please use only " + "values 0, 1 or None for axis") + + a, axisout = _chk_asarray(a, axis) + if a.ndim > 2: + raise ValueError("spearmanr only handles 1-D or 2-D arrays") + + if b is None: + if a.ndim < 2: + raise ValueError("`spearmanr` needs at least 2 " + "variables to compare") + else: + # Concatenate a and b, so that we now only have to handle the case + # of a 2-D `a`. + b, _ = _chk_asarray(b, axis) + if axisout == 0: + a = np.column_stack((a, b)) + else: + a = np.vstack((a, b)) + + n_vars = a.shape[1 - axisout] + n_obs = a.shape[axisout] + if n_obs <= 1: + # Handle empty arrays or single observations. + res = SignificanceResult(np.nan, np.nan) + res.correlation = np.nan + return res + + warn_msg = ("An input array is constant; the correlation coefficient " + "is not defined.") + + constant_axis = False + if axisout == 0: + constant_columns = np.all(a == a[0, :], axis=0) + constant_axis = np.any(constant_columns) + if constant_axis: + # If an input is constant, the correlation coefficient + # is not defined. + warnings.warn(stats.ConstantInputWarning(warn_msg), stacklevel=2) + else: # case when axisout == 1 b/c a is 2 dim only + constant_rows = np.all(a.T == a.T[0, :], axis=0) + constant_axis = np.any(constant_rows) + if constant_axis: + # If an input is constant, the correlation coefficient + # is not defined. + warnings.warn(stats.ConstantInputWarning(warn_msg), stacklevel=2) + + a_contains_nan = _contains_nan(a, nan_policy) + variable_has_nan = np.zeros(n_vars, dtype=bool) + if a_contains_nan: + if nan_policy == 'omit': + return mstats_basic.spearmanr(a, axis=axis, nan_policy=nan_policy, + alternative=alternative) + elif nan_policy == 'propagate': + if a.ndim == 1 or n_vars <= 2: + res = SignificanceResult(np.nan, np.nan) + res.correlation = np.nan + return res + else: + # Keep track of variables with NaNs, set the outputs to NaN + # only for those variables + variable_has_nan = np.isnan(a).any(axis=axisout) + + a_ranked = np.apply_along_axis(rankdata, axisout, a) + + if constant_axis: + with np.errstate(invalid='ignore'): + rs = np.corrcoef(a_ranked, rowvar=axisout) + else: + rs = np.corrcoef(a_ranked, rowvar=axisout) + + dof = n_obs - 2 # degrees of freedom + + # rs can have elements equal to 1, so avoid zero division warnings + with np.errstate(divide='ignore'): + # clip the small negative values possibly caused by rounding + # errors before taking the square root + t = rs * np.sqrt((dof/((rs+1.0)*(1.0-rs))).clip(0)) + + dist = _SimpleStudentT(dof) + prob = _get_pvalue(t, dist, alternative, xp=np) + + # For backwards compatibility, return scalars when comparing 2 columns + if rs.shape == (2, 2): + res = SignificanceResult(rs[1, 0], prob[1, 0]) + res.correlation = rs[1, 0] + return res + else: + rs[variable_has_nan, :] = np.nan + rs[:, variable_has_nan] = np.nan + res = SignificanceResult(rs[()], prob[()]) + res.correlation = rs + return res + + +@xp_capabilities(cpu_only=True, exceptions=['cupy', 'jax.numpy'], marray=True) +@_axis_nan_policy_factory(_pack_CorrelationResult, n_samples=2, + result_to_tuple=_unpack_CorrelationResult, paired=True, + too_small=1, n_outputs=3) +def pointbiserialr(x, y, *, axis=0): + r"""Calculate a point biserial correlation coefficient and its p-value. + + The point biserial correlation is used to measure the relationship + between a binary variable, x, and a continuous variable, y. Like other + correlation coefficients, this one varies between -1 and +1 with 0 + implying no correlation. Correlations of -1 or +1 imply a determinative + relationship. + + This function may be computed using a shortcut formula but produces the + same result as `pearsonr`. + + Parameters + ---------- + x : array_like of bools + Input array. + y : array_like + Input array. + axis : int or None, default: 0 + Axis along which to perform the calculation. Default is 0. + If None, ravel both arrays before performing the calculation. + + Returns + ------- + res: SignificanceResult + An object containing attributes: + + statistic : float + The R value. + pvalue : float + The two-sided p-value. + + Notes + ----- + `pointbiserialr` uses a t-test with ``n-1`` degrees of freedom. + It is equivalent to `pearsonr`. + + The value of the point-biserial correlation can be calculated from: + + .. math:: + + r_{pb} = \frac{\overline{Y_1} - \overline{Y_0}} + {s_y} + \sqrt{\frac{N_0 N_1} + {N (N - 1)}} + + Where :math:`\overline{Y_{0}}` and :math:`\overline{Y_{1}}` are means + of the metric observations coded 0 and 1 respectively; :math:`N_{0}` and + :math:`N_{1}` are number of observations coded 0 and 1 respectively; + :math:`N` is the total number of observations and :math:`s_{y}` is the + standard deviation of all the metric observations. + + A value of :math:`r_{pb}` that is significantly different from zero is + completely equivalent to a significant difference in means between the two + groups. Thus, an independent groups t Test with :math:`N-2` degrees of + freedom may be used to test whether :math:`r_{pb}` is nonzero. The + relation between the t-statistic for comparing two independent groups and + :math:`r_{pb}` is given by: + + .. math:: + + t = \sqrt{N - 2}\frac{r_{pb}}{\sqrt{1 - r^{2}_{pb}}} + + References + ---------- + .. [1] J. Lev, "The Point Biserial Coefficient of Correlation", Ann. Math. + Statist., Vol. 20, no.1, pp. 125-126, 1949. + + .. [2] R.F. Tate, "Correlation Between a Discrete and a Continuous + Variable. Point-Biserial Correlation.", Ann. Math. Statist., Vol. 25, + np. 3, pp. 603-607, 1954. + + .. [3] D. Kornbrot "Point Biserial Correlation", In Wiley StatsRef: + Statistics Reference Online (eds N. Balakrishnan, et al.), 2014. + :doi:`10.1002/9781118445112.stat06227` + + Examples + -------- + >>> import numpy as np + >>> from scipy import stats + >>> a = np.array([0, 0, 0, 1, 1, 1, 1]) + >>> b = np.arange(7) + >>> stats.pointbiserialr(a, b) + (0.8660254037844386, 0.011724811003954652) + >>> stats.pearsonr(a, b) + (0.86602540378443871, 0.011724811003954626) + >>> np.corrcoef(a, b) + array([[ 1. , 0.8660254], + [ 0.8660254, 1. ]]) + + """ + rpb, prob = pearsonr(x, y, axis=axis) + # create result object with alias for backward compatibility + res = SignificanceResult(rpb, prob) + res.correlation = rpb + return res + + +@xp_capabilities(cpu_only=True, jax_jit=False, allow_dask_compute=2, + marray=True, extra_note=("`nan_policy='propagate'` is " + "incompatible with MArray input.")) +def kendalltau(x, y, *, nan_policy='propagate', method='auto', variant='b', + alternative='two-sided', axis=None, keepdims=False): + r"""Calculate Kendall's tau, a correlation measure for ordinal data. + + Kendall's tau is a measure of the correspondence between two rankings. + Values close to 1 indicate strong agreement, and values close to -1 + indicate strong disagreement. This implements two variants of Kendall's + tau: tau-b (the default) and tau-c (also known as Stuart's tau-c). These + differ only in how they are normalized to lie within the range -1 to 1; + the hypothesis tests (their p-values) are identical. Kendall's original + tau-a is not implemented separately because both tau-b and tau-c reduce + to tau-a in the absence of ties. + + Although a naive implementation has O(n^2) complexity, this implementation + uses a Fenwick tree to do the computation in O(n log(n)) complexity. + + Parameters + ---------- + x, y : array_like + Arrays of rankings, of the same shape. If arrays are not 1-D, they + will be flattened to 1-D. + nan_policy : {'propagate', 'raise', 'omit'}, optional + Defines how to handle when input contains nan. + The following options are available (default is 'propagate'): + + * 'propagate': returns nan + * 'raise': throws an error + * 'omit': performs the calculations ignoring nan values + + method : {'auto', 'asymptotic', 'exact'}, optional + Defines which method is used to calculate the p-value [5]_. + The following options are available (default is 'auto'): + + * 'auto': selects the appropriate method based on a trade-off + between speed and accuracy + * 'asymptotic': uses a normal approximation valid for large samples + * 'exact': computes the exact p-value, but can only be used if no ties + are present. As the sample size increases, the 'exact' computation + time may grow and the result may lose some precision. + + variant : {'b', 'c'}, optional + Defines which variant of Kendall's tau is returned. Default is 'b'. + alternative : {'two-sided', 'less', 'greater'}, optional + Defines the alternative hypothesis. Default is 'two-sided'. + The following options are available: + + * 'two-sided': the rank correlation is nonzero + * 'less': the rank correlation is negative (less than zero) + * 'greater': the rank correlation is positive (greater than zero) + axis : int or None, default: None + If an int, the axis of the input along which to compute the statistic. + The statistic of each axis-slice (e.g. row) of the input will appear in a + corresponding element of the output. + If ``None``, the input will be raveled before computing the statistic. + keepdims : bool, optional + If this is set to ``True``, the axes which are reduced are left + in the result as dimensions with length one. With this option, + the result will broadcast correctly against the input array. + + Returns + ------- + res : SignificanceResult + An object containing attributes: + + statistic : float + The tau statistic. + pvalue : float + The p-value for a hypothesis test whose null hypothesis is + an absence of association, tau = 0. + + Raises + ------ + ValueError + If `nan_policy` is 'omit' and `variant` is not 'b' or + if `method` is 'exact' and there are ties between `x` and `y`. + + See Also + -------- + spearmanr : Calculates a Spearman rank-order correlation coefficient. + theilslopes : Computes the Theil-Sen estimator for a set of points (x, y). + weightedtau : Computes a weighted version of Kendall's tau. + :ref:`hypothesis_kendalltau` : Extended example + + Notes + ----- + The definition of Kendall's tau that is used is [2]_:: + + tau_b = (P - Q) / sqrt((P + Q + T) * (P + Q + U)) + + tau_c = 2 (P - Q) / (n**2 * (m - 1) / m) + + where P is the number of concordant pairs, Q the number of discordant + pairs, T the number of tied pairs only in `x`, and U the number of tied pairs only + in `y`. If a tie occurs for the same pair in both `x` and `y`, it is not + added to either T or U. n is the total number of samples, and m is the + number of unique values in either `x` or `y`, whichever is smaller. + + References + ---------- + .. [1] Maurice G. Kendall, "A New Measure of Rank Correlation", Biometrika + Vol. 30, No. 1/2, pp. 81-93, 1938. + .. [2] Maurice G. Kendall, "The treatment of ties in ranking problems", + Biometrika Vol. 33, No. 3, pp. 239-251. 1945. + .. [3] Gottfried E. Noether, "Elements of Nonparametric Statistics", John + Wiley & Sons, 1967. + .. [4] Peter M. Fenwick, "A new data structure for cumulative frequency + tables", Software: Practice and Experience, Vol. 24, No. 3, + pp. 327-336, 1994. + .. [5] Maurice G. Kendall, "Rank Correlation Methods" (4th Edition), + Charles Griffin & Co., 1970. + + Examples + -------- + + >>> from scipy import stats + >>> x1 = [12, 2, 1, 12, 2] + >>> x2 = [1, 4, 7, 1, 0] + >>> res = stats.kendalltau(x1, x2) + >>> res.statistic + -0.47140452079103173 + >>> res.pvalue + 0.2827454599327748 + + For a more detailed example, see :ref:`hypothesis_kendalltau`. + """ + xp = array_namespace(x, y) + x, y = xp_promote(x, y, force_floating=True, xp=xp) + dtype = x.dtype + if is_marray(xp): + mask_x, mask_y = np.asarray(x.mask), np.asarray(y.mask) + x, y = np.asarray(x.data).copy(), np.asarray(y.data).copy() + unmasked_nans = np.any(np.isnan(x[~mask_x])) | np.any(np.isnan(y[~mask_y])) + if unmasked_nans: + if nan_policy == 'propagate': + message = "`nan_policy='propagate'` is incompatible with MArray input." + elif nan_policy == 'raise': + message = "The input contains nan values." + raise ValueError(message) + nan_policy = 'omit' + x[mask_x], y[mask_y] = np.nan, np.nan + else: + x, y = _asarray(x, subok=True, xp=np), _asarray(y, subok=True, xp=np) + res = _kendalltau(x, y, nan_policy=nan_policy, method=method, variant=variant, + alternative=alternative, axis=axis, keepdims=keepdims) + vals = res.statistic, res.pvalue, res.statistic + vals = (xp.asarray(val, dtype=dtype)[()] if val.ndim == 0 + else xp.asarray(val, dtype=dtype) for val in vals) + return _pack_CorrelationResult(*vals) + + +@_axis_nan_policy_factory(_pack_CorrelationResult, default_axis=None, n_samples=2, + result_to_tuple=_unpack_CorrelationResult, paired=True, + too_small=1, n_outputs=3) +def _kendalltau(x, y, *, method='auto', variant='b', alternative='two-sided'): + x = np.asarray(x).ravel() + y = np.asarray(y).ravel() + + if x.size != y.size: + raise ValueError("Array shapes are incompatible for broadcasting.") + elif not x.size or not y.size: + # Return NaN if arrays are empty + NaN = _get_nan(x, y) + res = SignificanceResult(NaN, NaN) + res.correlation = NaN + return res + + def count_rank_tie(ranks): + cnt = np.bincount(ranks).astype('int64', copy=False) + cnt = cnt[cnt > 1] + # Python ints to avoid overflow down the line + return (int((cnt * (cnt - 1) // 2).sum()), + int((cnt * (cnt - 1.) * (cnt - 2)).sum()), + int((cnt * (cnt - 1.) * (2*cnt + 5)).sum())) + + size = x.size + perm = np.argsort(y) # sort on y and convert y to dense ranks + x, y = x[perm], y[perm] + y = np.r_[True, y[1:] != y[:-1]].cumsum(dtype=np.intp) + + # stable sort on x and convert x to dense ranks + perm = np.argsort(x, kind='mergesort') + x, y = x[perm], y[perm] + x = np.r_[True, x[1:] != x[:-1]].cumsum(dtype=np.intp) + + dis = _kendall_dis(x, y) # discordant pairs + + obs = np.r_[True, (x[1:] != x[:-1]) | (y[1:] != y[:-1]), True] + cnt = np.diff(np.nonzero(obs)[0]).astype('int64', copy=False) + + ntie = int((cnt * (cnt - 1) // 2).sum()) # joint ties + xtie, x0, x1 = count_rank_tie(x) # ties in x, stats + ytie, y0, y1 = count_rank_tie(y) # ties in y, stats + + tot = (size * (size - 1)) // 2 + + if xtie == tot or ytie == tot: + NaN = _get_nan(x, y) + res = SignificanceResult(NaN, NaN) + res.correlation = NaN + return res + + # Note that tot = con + dis + (xtie - ntie) + (ytie - ntie) + ntie + # = con + dis + xtie + ytie - ntie + con_minus_dis = tot - xtie - ytie + ntie - 2 * dis + if variant == 'b': + tau = con_minus_dis / np.sqrt(tot - xtie) / np.sqrt(tot - ytie) + elif variant == 'c': + minclasses = min(len(set(x)), len(set(y))) + tau = 2*con_minus_dis / (size**2 * (minclasses-1)/minclasses) + else: + raise ValueError(f"Unknown `variant` '{variant}' specified. " + "`variant` must be 'b' or 'c'.") + + # Limit range to fix computational errors + tau = np.minimum(1., max(-1., tau)) + + # The p-value calculation is the same for all variants since the p-value + # depends only on con_minus_dis. + if method == 'exact' and (xtie != 0 or ytie != 0): + raise ValueError("Ties found; exact method cannot be used.") + + if method == 'auto': + if (xtie == 0 and ytie == 0) and (size <= 33 or + min(dis, tot-dis) <= 1): + method = 'exact' + else: + method = 'asymptotic' + + if xtie == 0 and ytie == 0 and method == 'exact': + pvalue = mstats_basic._kendall_p_exact(size, tot-dis, alternative) + elif method == 'asymptotic': + # con_minus_dis is approx normally distributed with this variance [3]_ + m = size * (size - 1.) + var = ((m * (2*size + 5) - x1 - y1) / 18 + + (2 * xtie * ytie) / m + x0 * y0 / (9 * m * (size - 2))) + z = con_minus_dis / np.sqrt(var) + pvalue = _get_pvalue(z, _SimpleNormal(), alternative, xp=np) + else: + raise ValueError(f"Unknown `method` '{method}' specified. `method` must be" + "'auto', 'exact' or 'asymptotic'.") + + # create result object with alias for backward compatibility + res = SignificanceResult(tau[()], pvalue[()]) + res.correlation = tau[()] + return res + + +def _weightedtau_n_samples(kwargs): + rank = kwargs.get('rank', False) + return 2 if (isinstance(rank, bool) or rank is None) else 3 + + +@xp_capabilities(np_only=True) +@_axis_nan_policy_factory(_pack_CorrelationResult, default_axis=None, + n_samples=_weightedtau_n_samples, + result_to_tuple=_unpack_CorrelationResult, paired=True, + too_small=1, n_outputs=3, override={'nan_propagation': False}) +def weightedtau(x, y, rank=True, weigher=None, additive=True): + r"""Compute a weighted version of Kendall's :math:`\tau`. + + The weighted :math:`\tau` is a weighted version of Kendall's + :math:`\tau` in which exchanges of high weight are more influential than + exchanges of low weight. The default parameters compute the additive + hyperbolic version of the index, :math:`\tau_\mathrm h`, which has + been shown to provide the best balance between important and + unimportant elements [1]_. + + The weighting is defined by means of a rank array, which assigns a + nonnegative rank to each element (higher importance ranks being + associated with smaller values, e.g., 0 is the highest possible rank), + and a weigher function, which assigns a weight based on the rank to + each element. The weight of an exchange is then the sum or the product + of the weights of the ranks of the exchanged elements. The default + parameters compute :math:`\tau_\mathrm h`: an exchange between + elements with rank :math:`r` and :math:`s` (starting from zero) has + weight :math:`1/(r+1) + 1/(s+1)`. + + Specifying a rank array is meaningful only if you have in mind an + external criterion of importance. If, as it usually happens, you do + not have in mind a specific rank, the weighted :math:`\tau` is + defined by averaging the values obtained using the decreasing + lexicographical rank by (`x`, `y`) and by (`y`, `x`). This is the + behavior with default parameters. Note that the convention used + here for ranking (lower values imply higher importance) is opposite + to that used by other SciPy statistical functions. + + Parameters + ---------- + x, y : array_like + Arrays of scores, of the same shape. If arrays are not 1-D, they will + be flattened to 1-D. + rank : array_like of ints or bool, optional + A nonnegative rank assigned to each element. If it is None, the + decreasing lexicographical rank by (`x`, `y`) will be used: elements of + higher rank will be those with larger `x`-values, using `y`-values to + break ties (in particular, swapping `x` and `y` will give a different + result). If it is False, the element indices will be used + directly as ranks. The default is True, in which case this + function returns the average of the values obtained using the + decreasing lexicographical rank by (`x`, `y`) and by (`y`, `x`). + weigher : callable, optional + The weigher function. Must map nonnegative integers (zero + representing the most important element) to a nonnegative weight. + The default, None, provides hyperbolic weighing, that is, + rank :math:`r` is mapped to weight :math:`1/(r+1)`. + additive : bool, optional + If True, the weight of an exchange is computed by adding the + weights of the ranks of the exchanged elements; otherwise, the weights + are multiplied. The default is True. + + Returns + ------- + res: SignificanceResult + An object containing attributes: + + statistic : float + The weighted :math:`\tau` correlation index. + pvalue : float + Presently ``np.nan``, as the null distribution of the statistic is + unknown (even in the additive hyperbolic case). + + See Also + -------- + kendalltau : Calculates Kendall's tau. + spearmanr : Calculates a Spearman rank-order correlation coefficient. + theilslopes : Computes the Theil-Sen estimator for a set of points (x, y). + + Notes + ----- + This function uses an :math:`O(n \log n)`, mergesort-based algorithm + [1]_ that is a weighted extension of Knight's algorithm for Kendall's + :math:`\tau` [2]_. It can compute Shieh's weighted :math:`\tau` [3]_ + between rankings without ties (i.e., permutations) by setting + `additive` and `rank` to False, as the definition given in [1]_ is a + generalization of Shieh's. + + NaNs are considered the smallest possible score. + + .. versionadded:: 0.19.0 + + References + ---------- + .. [1] Sebastiano Vigna, "A weighted correlation index for rankings with + ties", Proceedings of the 24th international conference on World + Wide Web, pp. 1166-1176, ACM, 2015. + .. [2] W.R. Knight, "A Computer Method for Calculating Kendall's Tau with + Ungrouped Data", Journal of the American Statistical Association, + Vol. 61, No. 314, Part 1, pp. 436-439, 1966. + .. [3] Grace S. Shieh. "A weighted Kendall's tau statistic", Statistics & + Probability Letters, Vol. 39, No. 1, pp. 17-24, 1998. + + Examples + -------- + >>> import numpy as np + >>> from scipy import stats + >>> x = [12, 2, 1, 12, 2] + >>> y = [1, 4, 7, 1, 0] + >>> res = stats.weightedtau(x, y) + >>> res.statistic + -0.56694968153682723 + >>> res.pvalue + nan + >>> res = stats.weightedtau(x, y, additive=False) + >>> res.statistic + -0.62205716951801038 + + NaNs are considered the smallest possible score: + + >>> x = [12, 2, 1, 12, 2] + >>> y = [1, 4, 7, 1, np.nan] + >>> res = stats.weightedtau(x, y) + >>> res.statistic + -0.56694968153682723 + + This is exactly Kendall's tau: + + >>> x = [12, 2, 1, 12, 2] + >>> y = [1, 4, 7, 1, 0] + >>> res = stats.weightedtau(x, y, weigher=lambda x: 1) + >>> res.statistic + -0.47140452079103173 + + >>> x = [12, 2, 1, 12, 2] + >>> y = [1, 4, 7, 1, 0] + >>> stats.weightedtau(x, y, rank=None) + SignificanceResult(statistic=-0.4157652301037516, pvalue=nan) + >>> stats.weightedtau(y, x, rank=None) + SignificanceResult(statistic=-0.7181341329699028, pvalue=nan) + + """ + x = np.asarray(x).ravel() + y = np.asarray(y).ravel() + NaN = _get_nan(x, y) + + if x.size != y.size: + raise ValueError("Array shapes are incompatible for broadcasting.") + if not x.size: + # Return NaN if arrays are empty + res = SignificanceResult(NaN, NaN) + res.correlation = NaN + return res + + # If there are NaNs we apply _toint64() + if np.isnan(np.sum(x)): + x = _toint64(x) + if np.isnan(np.sum(y)): + y = _toint64(y) + + # Reduce to ranks unsupported types + if x.dtype != y.dtype: + if x.dtype != np.int64: + x = _toint64(x) + if y.dtype != np.int64: + y = _toint64(y) + else: + if x.dtype not in (np.int32, np.int64, np.float32, np.float64): + x = _toint64(x) + y = _toint64(y) + + if rank is True: + tau = np.asarray( + _weightedrankedtau(x, y, None, weigher, additive) + + _weightedrankedtau(y, x, None, weigher, additive) + )[()] / 2 + res = SignificanceResult(tau, NaN) + res.correlation = tau + return res + + if rank is False: + rank = np.arange(x.size, dtype=np.intp) + elif rank is not None: + rank = np.asarray(rank).ravel() + rank = _toint64(rank).astype(np.intp) + if rank.size != x.size: + raise ValueError( + "All inputs to `weightedtau` must be of the same size, " + f"found x-size {x.size} and rank-size {rank.size}" + ) + + tau = np.asarray(_weightedrankedtau(x, y, rank, weigher, additive))[()] + res = SignificanceResult(tau, NaN) + res.correlation = tau + return res + + +##################################### +# INFERENTIAL STATISTICS # +##################################### + +TtestResultBase = _make_tuple_bunch('TtestResultBase', + ['statistic', 'pvalue'], ['df']) + + +class TtestResult(TtestResultBase): + """ + Result of a t-test. + + See the documentation of the particular t-test function for more + information about the definition of the statistic and meaning of + the confidence interval. + + Attributes + ---------- + statistic : float or array + The t-statistic of the sample. + pvalue : float or array + The p-value associated with the given alternative. + df : float or array + The number of degrees of freedom used in calculation of the + t-statistic; this is one less than the size of the sample + (``a.shape[axis]-1`` if there are no masked elements or omitted NaNs). + + Methods + ------- + confidence_interval + Computes a confidence interval around the population statistic + for the given confidence level. + The confidence interval is returned in a ``namedtuple`` with + fields `low` and `high`. + + """ + + def __init__(self, statistic, pvalue, df, # public + alternative, standard_error, estimate, # private + statistic_np=None, xp=None): # private + super().__init__(statistic, pvalue, df=df) + self._alternative = alternative + self._standard_error = standard_error # denominator of t-statistic + self._estimate = estimate # point estimate of sample mean + self._statistic_np = statistic if statistic_np is None else statistic_np + self._dtype = statistic.dtype + self._xp = array_namespace(statistic, pvalue) if xp is None else xp + + + def confidence_interval(self, confidence_level=0.95): + """ + Parameters + ---------- + confidence_level : float + The confidence level for the calculation of the population mean + confidence interval. Default is 0.95. + + Returns + ------- + ci : namedtuple + The confidence interval is returned in a ``namedtuple`` with + fields `low` and `high`. + + """ + low, high = _t_confidence_interval(self.df, self._statistic_np, + confidence_level, self._alternative, + self._dtype, self._xp) + low = low * self._standard_error + self._estimate + high = high * self._standard_error + self._estimate + return ConfidenceInterval(low=low, high=high) + + +def pack_TtestResult(statistic, pvalue, df, alternative, standard_error, + estimate): + xp = array_namespace(statistic, pvalue) + # Due to behavior of `_axis_nan_policy` decorator, `alternative` can be any number + # of dimensions, but there is at most one unique non-NaN value. + # `_xp_mean` with `nan_policy='omit'` is a JIT-compatible way to extract it. + alternative = xp.asarray(alternative) + alternative = (_xp_mean(alternative, axis=None, nan_policy='omit', warn=False) + if xp_size(alternative) != 0 else xp.nan) + return TtestResult(statistic, pvalue, df=df, alternative=alternative, + standard_error=standard_error, estimate=estimate) + + +def unpack_TtestResult(res, _): + return (res.statistic, res.pvalue, res.df, res._alternative, + res._standard_error, res._estimate) + + +@xp_capabilities(cpu_only=True, exceptions=["cupy", "jax.numpy"], marray=True, + extra_note=("The ``confidence_interval`` method of the output object " + "is incompatible with JAX JIT.")) +@_axis_nan_policy_factory(pack_TtestResult, default_axis=0, n_samples=2, + result_to_tuple=unpack_TtestResult, n_outputs=6) +# nan_policy handled by `_axis_nan_policy`, but needs to be left +# in signature to preserve use as a positional argument +def ttest_1samp(a, popmean, axis=0, nan_policy="propagate", alternative="two-sided"): + """Calculate the T-test for the mean of ONE group of scores. + + This is a test for the null hypothesis that the expected value + (mean) of a sample of independent observations `a` is equal to the given + population mean, `popmean`. + + Parameters + ---------- + a : array_like + Sample observations. + popmean : float or array_like + Expected value in null hypothesis. If array_like, then its length along + `axis` must equal 1, and it must otherwise be broadcastable with `a`. + axis : int or None, optional + Axis along which to compute test; default is 0. If None, compute over + the whole array `a`. + nan_policy : {'propagate', 'raise', 'omit'}, optional + Defines how to handle when input contains nan. + The following options are available (default is 'propagate'): + + * 'propagate': returns nan + * 'raise': throws an error + * 'omit': performs the calculations ignoring nan values + + alternative : {'two-sided', 'less', 'greater'}, optional + Defines the alternative hypothesis. + The following options are available (default is 'two-sided'): + + * 'two-sided': the mean of the underlying distribution of the sample + is different than the given population mean (`popmean`) + * 'less': the mean of the underlying distribution of the sample is + less than the given population mean (`popmean`) + * 'greater': the mean of the underlying distribution of the sample is + greater than the given population mean (`popmean`) + + Returns + ------- + result : `~scipy.stats._result_classes.TtestResult` + An object with the following attributes: + + statistic : float or array + The t-statistic. + pvalue : float or array + The p-value associated with the given alternative. + df : float or array + The number of degrees of freedom used in calculation of the + t-statistic; this is one less than the size of the sample + (``a.shape[axis]``). + + .. versionadded:: 1.10.0 + + The object also has the following method: + + confidence_interval(confidence_level=0.95) + Computes a confidence interval around the population + mean for the given confidence level. + The confidence interval is returned in a ``namedtuple`` with + fields `low` and `high`. + + .. versionadded:: 1.10.0 + + Notes + ----- + The statistic is calculated as ``(np.mean(a) - popmean)/se``, where + ``se`` is the standard error. Therefore, the statistic will be positive + when the sample mean is greater than the population mean and negative when + the sample mean is less than the population mean. + + Examples + -------- + Suppose we wish to test the null hypothesis that the mean of a population + is equal to 0.5. We choose a confidence level of 99%; that is, we will + reject the null hypothesis in favor of the alternative if the p-value is + less than 0.01. + + When testing random variates from the standard uniform distribution, which + has a mean of 0.5, we expect the data to be consistent with the null + hypothesis most of the time. + + >>> import numpy as np + >>> from scipy import stats + >>> rng = np.random.default_rng() + >>> rvs = stats.uniform.rvs(size=50, random_state=rng) + >>> stats.ttest_1samp(rvs, popmean=0.5) + TtestResult(statistic=2.456308468440, pvalue=0.017628209047638, df=49) + + As expected, the p-value of 0.017 is not below our threshold of 0.01, so + we cannot reject the null hypothesis. + + When testing data from the standard *normal* distribution, which has a mean + of 0, we would expect the null hypothesis to be rejected. + + >>> rvs = stats.norm.rvs(size=50, random_state=rng) + >>> stats.ttest_1samp(rvs, popmean=0.5) + TtestResult(statistic=-7.433605518875, pvalue=1.416760157221e-09, df=49) + + Indeed, the p-value is lower than our threshold of 0.01, so we reject the + null hypothesis in favor of the default "two-sided" alternative: the mean + of the population is *not* equal to 0.5. + + However, suppose we were to test the null hypothesis against the + one-sided alternative that the mean of the population is *greater* than + 0.5. Since the mean of the standard normal is less than 0.5, we would not + expect the null hypothesis to be rejected. + + >>> stats.ttest_1samp(rvs, popmean=0.5, alternative='greater') + TtestResult(statistic=-7.433605518875, pvalue=0.99999999929, df=49) + + Unsurprisingly, with a p-value greater than our threshold, we would not + reject the null hypothesis. + + Note that when working with a confidence level of 99%, a true null + hypothesis will be rejected approximately 1% of the time. + + >>> rvs = stats.uniform.rvs(size=(100, 50), random_state=rng) + >>> res = stats.ttest_1samp(rvs, popmean=0.5, axis=1) + >>> np.sum(res.pvalue < 0.01) + 1 + + Indeed, even though all 100 samples above were drawn from the standard + uniform distribution, which *does* have a population mean of 0.5, we would + mistakenly reject the null hypothesis for one of them. + + `ttest_1samp` can also compute a confidence interval around the population + mean. + + >>> rvs = stats.norm.rvs(size=50, random_state=rng) + >>> res = stats.ttest_1samp(rvs, popmean=0) + >>> ci = res.confidence_interval(confidence_level=0.95) + >>> ci + ConfidenceInterval(low=-0.3193887540880017, high=0.2898583388980972) + + The bounds of the 95% confidence interval are the + minimum and maximum values of the parameter `popmean` for which the + p-value of the test would be 0.05. + + >>> res = stats.ttest_1samp(rvs, popmean=ci.low) + >>> np.testing.assert_allclose(res.pvalue, 0.05) + >>> res = stats.ttest_1samp(rvs, popmean=ci.high) + >>> np.testing.assert_allclose(res.pvalue, 0.05) + + Under certain assumptions about the population from which a sample + is drawn, the confidence interval with confidence level 95% is expected + to contain the true population mean in 95% of sample replications. + + >>> rvs = stats.norm.rvs(size=(50, 1000), loc=1, random_state=rng) + >>> res = stats.ttest_1samp(rvs, popmean=0) + >>> ci = res.confidence_interval() + >>> contains_pop_mean = (ci.low < 1) & (ci.high > 1) + >>> contains_pop_mean.sum() + 953 + + """ + xp = array_namespace(a) + a, popmean = xp_promote(a, popmean, force_floating=True, xp=xp) + a, axis = _chk_asarray(a, axis, xp=xp) + + n = _count_nonmasked(a, axis) + df = n - 1 + + if a.shape[axis] == 0: + # This is really only needed for *testing* _axis_nan_policy decorator + # It won't happen when the decorator is used. + NaN = _get_nan(a) + return TtestResult(NaN, NaN, df=NaN, alternative=NaN, + standard_error=NaN, estimate=NaN) + + mean = xp.mean(a, axis=axis) + try: + popmean = xp.asarray(popmean) + popmean = xp.squeeze(popmean, axis=axis) if popmean.ndim > 0 else popmean + except ValueError as e: + raise ValueError("`popmean.shape[axis]` must equal 1.") from e + d = mean - popmean + v = _var(a, axis=axis, ddof=1) + denom = xp.sqrt(v / n) + + with np.errstate(divide='ignore', invalid='ignore'): + t = xp.divide(d, denom) + t = t[()] if t.ndim == 0 else t + + dist = _SimpleStudentT(xp.asarray(df, dtype=t.dtype, device=xp_device(a))) + prob = _get_pvalue(t, dist, alternative, xp=xp) + prob = prob[()] if prob.ndim == 0 else prob + + # when nan_policy='omit', `df` can be different for different axis-slices + df = xp.broadcast_to(xp.asarray(df, device=xp_device(a)), t.shape) + df = df[()] if df.ndim == 0 else df + # _axis_nan_policy decorator doesn't play well with strings + alternative_num = {"less": -1, "two-sided": 0, "greater": 1}[alternative] + return TtestResult(t, prob, df=df, alternative=alternative_num, + standard_error=denom, estimate=mean, + statistic_np=xp.asarray(t), xp=xp) + + +def _t_confidence_interval(df, t, confidence_level, alternative, dtype=None, xp=None): + # Input validation on `alternative` is already done + # We just need IV on confidence_level + dtype = t.dtype if dtype is None else dtype + xp = array_namespace(t) if xp is None else xp + + if confidence_level < 0 or confidence_level > 1: + message = "`confidence_level` must be a number between 0 and 1." + raise ValueError(message) + + confidence_level = xp.asarray(confidence_level, dtype=dtype, device=xp_device(t)) + inf = xp.asarray(xp.inf, dtype=dtype) + + if alternative < 0: # 'less' + p = confidence_level + low, high = xp.broadcast_arrays(-inf, special.stdtrit(df, p)) + elif alternative > 0: # 'greater' + p = 1 - confidence_level + low, high = xp.broadcast_arrays(special.stdtrit(df, p), inf) + elif alternative == 0: # 'two-sided' + tail_probability = (1 - confidence_level)/2 + p = xp.stack((tail_probability, 1-tail_probability)) + # axis of p must be the zeroth and orthogonal to all the rest + p = xp.reshape(p, tuple([2] + [1]*xp.asarray(df, device=xp_device(t)).ndim)) + ci = special.stdtrit(df, p) + low, high = ci[0, ...], ci[1, ...] + else: # alternative is NaN when input is empty (see _axis_nan_policy) + nan = xp.asarray(xp.nan, device=xp_device(t)) + p, nans = xp.broadcast_arrays(t, nan) + low, high = nans, nans + + low = xp.asarray(low, dtype=dtype) + low = low[()] if low.ndim == 0 else low + high = xp.asarray(high, dtype=dtype) + high = high[()] if high.ndim == 0 else high + return low, high + + +def _ttest_ind_from_stats(mean1, mean2, denom, df, alternative, xp=None): + xp = array_namespace(mean1, mean2, denom) if xp is None else xp + + d = mean1 - mean2 + with np.errstate(divide='ignore', invalid='ignore'): + t = xp.divide(d, denom) + + dist = _SimpleStudentT(xp.asarray(df, dtype=t.dtype, device=xp_device(t))) + prob = _get_pvalue(t, dist, alternative, xp=xp) + prob = prob[()] if prob.ndim == 0 else prob + + t = t[()] if t.ndim == 0 else t + prob = prob[()] if prob.ndim == 0 else prob + return t, prob + + +def _unequal_var_ttest_denom(v1, n1, v2, n2, xp=None): + xp = array_namespace(v1, v2) if xp is None else xp + vn1 = v1 / n1 + vn2 = v2 / n2 + with np.errstate(divide='ignore', invalid='ignore'): + df = (vn1 + vn2)**2 / (vn1**2 / (n1 - 1) + vn2**2 / (n2 - 1)) + + # If df is undefined, variances are zero (assumes n1 > 0 & n2 > 0). + # Hence it doesn't matter what df is as long as it's not NaN. + df = xp.where(xp.isnan(df), 1., df) + denom = xp.sqrt(vn1 + vn2) + return df, denom + + +def _equal_var_ttest_denom(v1, n1, v2, n2, xp=None): + xp = array_namespace(v1, v2) if xp is None else xp + + # If there is a single observation in one sample, this formula for pooled + # variance breaks down because the variance of that sample is undefined. + # The pooled variance is still defined, though, because the (n-1) in the + # numerator should cancel with the (n-1) in the denominator, leaving only + # the sum of squared differences from the mean: zero. + v1 = xp.where(xp.asarray(n1 == 1), 0., v1) + v2 = xp.where(xp.asarray(n2 == 1), 0., v2) + + df = n1 + n2 - 2.0 + svar = ((n1 - 1) * v1 + (n2 - 1) * v2) / df + denom = xp.sqrt(svar * (1.0 / n1 + 1.0 / n2)) + df = xp.asarray(df, dtype=denom.dtype) + return df, denom + + +Ttest_indResult = namedtuple('Ttest_indResult', ('statistic', 'pvalue')) + + +@xp_capabilities(cpu_only=True, exceptions=["cupy", "jax.numpy"], marray=True) +def ttest_ind_from_stats(mean1, std1, nobs1, mean2, std2, nobs2, + equal_var=True, alternative="two-sided"): + r""" + T-test for means of two independent samples from descriptive statistics. + + This is a test for the null hypothesis that two independent + samples have identical average (expected) values. + + Parameters + ---------- + mean1 : array_like + The mean(s) of sample 1. + std1 : array_like + The corrected sample standard deviation of sample 1 (i.e. ``ddof=1``). + nobs1 : array_like + The number(s) of observations of sample 1. + mean2 : array_like + The mean(s) of sample 2. + std2 : array_like + The corrected sample standard deviation of sample 2 (i.e. ``ddof=1``). + nobs2 : array_like + The number(s) of observations of sample 2. + equal_var : bool, optional + If True (default), perform a standard independent 2 sample test + that assumes equal population variances [1]_. + If False, perform Welch's t-test, which does not assume equal + population variance [2]_. + alternative : {'two-sided', 'less', 'greater'}, optional + Defines the alternative hypothesis. + The following options are available (default is 'two-sided'): + + * 'two-sided': the means of the distributions are unequal. + * 'less': the mean of the first distribution is less than the + mean of the second distribution. + * 'greater': the mean of the first distribution is greater than the + mean of the second distribution. + + .. versionadded:: 1.6.0 + + Returns + ------- + statistic : float or array + The calculated t-statistics. + pvalue : float or array + The two-tailed p-value. + + See Also + -------- + scipy.stats.ttest_ind + + Notes + ----- + The statistic is calculated as ``(mean1 - mean2)/se``, where ``se`` is the + standard error. Therefore, the statistic will be positive when `mean1` is + greater than `mean2` and negative when `mean1` is less than `mean2`. + + This method does not check whether any of the elements of `std1` or `std2` + are negative. If any elements of the `std1` or `std2` parameters are + negative in a call to this method, this method will return the same result + as if it were passed ``numpy.abs(std1)`` and ``numpy.abs(std2)``, + respectively, instead; no exceptions or warnings will be emitted. + + References + ---------- + .. [1] https://en.wikipedia.org/wiki/T-test#Independent_two-sample_t-test + + .. [2] https://en.wikipedia.org/wiki/Welch%27s_t-test + + Examples + -------- + Suppose we have the summary data for two samples, as follows (with the + Sample Variance being the corrected sample variance):: + + Sample Sample + Size Mean Variance + Sample 1 13 15.0 87.5 + Sample 2 11 12.0 39.0 + + Apply the t-test to this data (with the assumption that the population + variances are equal): + + >>> import numpy as np + >>> from scipy.stats import ttest_ind_from_stats + >>> ttest_ind_from_stats(mean1=15.0, std1=np.sqrt(87.5), nobs1=13, + ... mean2=12.0, std2=np.sqrt(39.0), nobs2=11) + Ttest_indResult(statistic=0.9051358093310269, pvalue=0.3751996797581487) + + For comparison, here is the data from which those summary statistics + were taken. With this data, we can compute the same result using + `scipy.stats.ttest_ind`: + + >>> a = np.array([1, 3, 4, 6, 11, 13, 15, 19, 22, 24, 25, 26, 26]) + >>> b = np.array([2, 4, 6, 9, 11, 13, 14, 15, 18, 19, 21]) + >>> from scipy.stats import ttest_ind + >>> ttest_ind(a, b) + TtestResult(statistic=0.905135809331027, + pvalue=0.3751996797581486, + df=22.0) + + Suppose we instead have binary data and would like to apply a t-test to + compare the proportion of 1s in two independent groups:: + + Number of Sample Sample + Size ones Mean Variance + Sample 1 150 30 0.2 0.161073 + Sample 2 200 45 0.225 0.175251 + + The sample mean :math:`\hat{p}` is the proportion of ones in the sample + and the variance for a binary observation is estimated by + :math:`\hat{p}(1-\hat{p})`. + + >>> ttest_ind_from_stats(mean1=0.2, std1=np.sqrt(0.161073), nobs1=150, + ... mean2=0.225, std2=np.sqrt(0.175251), nobs2=200) + Ttest_indResult(statistic=-0.5627187905196761, pvalue=0.5739887114209541) + + For comparison, we could compute the t statistic and p-value using + arrays of 0s and 1s and `scipy.stat.ttest_ind`, as above. + + >>> group1 = np.array([1]*30 + [0]*(150-30)) + >>> group2 = np.array([1]*45 + [0]*(200-45)) + >>> ttest_ind(group1, group2) + TtestResult(statistic=-0.5627179589855622, + pvalue=0.573989277115258, + df=348.0) + + """ + xp = array_namespace(mean1, std1, mean2, std2) + + mean1, std1, nobs1, mean2, std2, nobs2 = xp_promote( + mean1, std1, nobs1, mean2, std2, nobs2, force_floating=True, xp=xp) + + if equal_var: + df, denom = _equal_var_ttest_denom(std1**2, nobs1, std2**2, nobs2, xp=xp) + else: + df, denom = _unequal_var_ttest_denom(std1**2, nobs1, std2**2, nobs2, xp=xp) + + res = _ttest_ind_from_stats(mean1, mean2, denom, df, alternative) + return Ttest_indResult(*res) + + +@xp_capabilities(cpu_only=True, exceptions=["cupy", "jax.numpy"], marray=True, + extra_note=("`trim` and `method` are incompatible with MArray input." + "The ``confidence_interval`` method of the output object " + "is incompatible with JAX JIT.")) +@_axis_nan_policy_factory(pack_TtestResult, default_axis=0, n_samples=2, + result_to_tuple=unpack_TtestResult, n_outputs=6) +def ttest_ind(a, b, *, axis=0, equal_var=True, nan_policy='propagate', + alternative="two-sided", trim=0, method=None): + """ + Calculate the T-test for the means of *two independent* samples of scores. + + This is a test for the null hypothesis that 2 independent samples + have identical average (expected) values. This test assumes that the + populations have identical variances by default. + + Parameters + ---------- + a, b : array_like + The arrays must have the same shape, except in the dimension + corresponding to `axis` (the first, by default). + axis : int or None, optional + Axis along which to compute test. If None, compute over the whole + arrays, `a`, and `b`. + equal_var : bool, optional + If True (default), perform a standard independent 2 sample test + that assumes equal population variances [1]_. + If False, perform Welch's t-test, which does not assume equal + population variance [2]_. + + .. versionadded:: 0.11.0 + + nan_policy : {'propagate', 'raise', 'omit'}, optional + Defines how to handle when input contains nan. + The following options are available (default is 'propagate'): + + * 'propagate': returns nan + * 'raise': throws an error + * 'omit': performs the calculations ignoring nan values + + The 'omit' option is not currently available for one-sided asymptotic tests. + + alternative : {'two-sided', 'less', 'greater'}, optional + Defines the alternative hypothesis. + The following options are available (default is 'two-sided'): + + * 'two-sided': the means of the distributions underlying the samples + are unequal. + * 'less': the mean of the distribution underlying the first sample + is less than the mean of the distribution underlying the second + sample. + * 'greater': the mean of the distribution underlying the first + sample is greater than the mean of the distribution underlying + the second sample. + + trim : float, optional + If nonzero, performs a trimmed (Yuen's) t-test. + Defines the fraction of elements to be trimmed from each end of the + input samples. If 0 (default), no elements will be trimmed from either + side. The number of trimmed elements from each tail is the floor of the + trim times the number of elements. Valid range is [0, .5). + method : ResamplingMethod, optional + Defines the method used to compute the p-value. If `method` is an + instance of `PermutationMethod`/`MonteCarloMethod`, the p-value is + computed using + `scipy.stats.permutation_test`/`scipy.stats.monte_carlo_test` with the + provided configuration options and other appropriate settings. + Otherwise, the p-value is computed by comparing the test statistic + against a theoretical t-distribution. + + .. versionadded:: 1.15.0 + + Returns + ------- + result : `~scipy.stats._result_classes.TtestResult` + An object with the following attributes: + + statistic : float or ndarray + The t-statistic. + pvalue : float or ndarray + The p-value associated with the given alternative. + df : float or ndarray + The number of degrees of freedom used in calculation of the + t-statistic. + + .. versionadded:: 1.11.0 + + The object also has the following method: + + confidence_interval(confidence_level=0.95) + Computes a confidence interval around the difference in + population means for the given confidence level. + The confidence interval is returned in a ``namedtuple`` with + fields ``low`` and ``high``. + + .. versionadded:: 1.11.0 + + Notes + ----- + Suppose we observe two independent samples, e.g. flower petal lengths, and + we are considering whether the two samples were drawn from the same + population (e.g. the same species of flower or two species with similar + petal characteristics) or two different populations. + + The t-test quantifies the difference between the arithmetic means + of the two samples. The p-value quantifies the probability of observing + as or more extreme values assuming the null hypothesis, that the + samples are drawn from populations with the same population means, is true. + A p-value larger than a chosen threshold (e.g. 5% or 1%) indicates that + our observation is not so unlikely to have occurred by chance. Therefore, + we do not reject the null hypothesis of equal population means. + If the p-value is smaller than our threshold, then we have evidence + against the null hypothesis of equal population means. + + By default, the p-value is determined by comparing the t-statistic of the + observed data against a theoretical t-distribution. + + It is also possible to compute the test statistic using a permutation test by + passing ``method=scipy.stats.PermutationMethod(n_resamples=permutations)``, + where ``permutations`` is the desired number of "permutations" to use in + forming the null distribution. When ``1 < permutations < binom(n, k)``, where + + * ``k`` is the number of observations in `a`, + * ``n`` is the total number of observations in `a` and `b`, and + * ``binom(n, k)`` is the binomial coefficient (``n`` choose ``k``), + + the data are pooled (concatenated), randomly assigned to either group `a` + or `b`, and the t-statistic is calculated. This process is performed + repeatedly (``permutations`` times), generating a distribution of the + t-statistic under the null hypothesis, and the t-statistic of the observed + data is compared to this distribution to determine the p-value. + Specifically, the p-value reported is the "achieved significance level" + (ASL) as defined in 4.4 of [3]_. Note that there are other ways of + estimating p-values using randomized permutation tests; for other + options, see the more general `permutation_test`. + + When ``permutations >= binom(n, k)``, an exact test is performed: the data + are partitioned between the groups in each distinct way exactly once. + + The permutation test can be computationally expensive and not necessarily + more accurate than the analytical test, but it does not make strong + assumptions about the shape of the underlying distribution. + + Use of trimming is commonly referred to as the trimmed t-test. At times + called Yuen's t-test, this is an extension of Welch's t-test, with the + difference being the use of winsorized means in calculation of the variance + and the trimmed sample size in calculation of the statistic. Trimming is + recommended if the underlying distribution is long-tailed or contaminated + with outliers [4]_. + + The statistic is calculated as ``(np.mean(a) - np.mean(b))/se``, where + ``se`` is the standard error. Therefore, the statistic will be positive + when the sample mean of `a` is greater than the sample mean of `b` and + negative when the sample mean of `a` is less than the sample mean of + `b`. + + References + ---------- + .. [1] https://en.wikipedia.org/wiki/T-test#Independent_two-sample_t-test + + .. [2] https://en.wikipedia.org/wiki/Welch%27s_t-test + + .. [3] B. Efron and T. Hastie. + Computer Age Statistical Inference. (2016). + + .. [4] Yuen, Karen K. "The Two-Sample Trimmed t for Unequal Population + Variances." Biometrika, vol. 61, no. 1, 1974, pp. 165-170. + https://www.jstor.org/stable/2334299 + + .. [5] Yuen, Karen K., and W. J. Dixon. "The Approximate Behaviour and + Performance of the Two-Sample Trimmed t." Biometrika, vol. 60, + no. 2, 1973, pp. 369-374. + https://www.jstor.org/stable/2334550 + + Examples + -------- + >>> import numpy as np + >>> from scipy import stats + >>> rng = np.random.default_rng() + + Test with sample with identical means: + + >>> rvs1 = stats.norm.rvs(loc=5, scale=10, size=500, random_state=rng) + >>> rvs2 = stats.norm.rvs(loc=5, scale=10, size=500, random_state=rng) + >>> stats.ttest_ind(rvs1, rvs2) + TtestResult(statistic=-0.4390847099199348, + pvalue=0.6606952038870015, + df=998.0) + >>> stats.ttest_ind(rvs1, rvs2, equal_var=False) + TtestResult(statistic=-0.4390847099199348, + pvalue=0.6606952553131064, + df=997.4602304121448) + + `ttest_ind` underestimates p for unequal variances: + + >>> rvs3 = stats.norm.rvs(loc=5, scale=20, size=500, random_state=rng) + >>> stats.ttest_ind(rvs1, rvs3) + TtestResult(statistic=-1.6370984482905417, + pvalue=0.1019251574705033, + df=998.0) + >>> stats.ttest_ind(rvs1, rvs3, equal_var=False) + TtestResult(statistic=-1.637098448290542, + pvalue=0.10202110497954867, + df=765.1098655246868) + + When ``n1 != n2``, the equal variance t-statistic is no longer equal to the + unequal variance t-statistic: + + >>> rvs4 = stats.norm.rvs(loc=5, scale=20, size=100, random_state=rng) + >>> stats.ttest_ind(rvs1, rvs4) + TtestResult(statistic=-1.9481646859513422, + pvalue=0.05186270935842703, + df=598.0) + >>> stats.ttest_ind(rvs1, rvs4, equal_var=False) + TtestResult(statistic=-1.3146566100751664, + pvalue=0.1913495266513811, + df=110.41349083985212) + + T-test with different means, variance, and n: + + >>> rvs5 = stats.norm.rvs(loc=8, scale=20, size=100, random_state=rng) + >>> stats.ttest_ind(rvs1, rvs5) + TtestResult(statistic=-2.8415950600298774, + pvalue=0.0046418707568707885, + df=598.0) + >>> stats.ttest_ind(rvs1, rvs5, equal_var=False) + TtestResult(statistic=-1.8686598649188084, + pvalue=0.06434714193919686, + df=109.32167496550137) + + Take these two samples, one of which has an extreme tail. + + >>> a = (56, 128.6, 12, 123.8, 64.34, 78, 763.3) + >>> b = (1.1, 2.9, 4.2) + + Use the `trim` keyword to perform a trimmed (Yuen) t-test. For example, + using 20% trimming, ``trim=.2``, the test will reduce the impact of one + (``np.floor(trim*len(a))``) element from each tail of sample `a`. It will + have no effect on sample `b` because ``np.floor(trim*len(b))`` is 0. + + >>> stats.ttest_ind(a, b, trim=.2) + TtestResult(statistic=3.4463884028073513, + pvalue=0.01369338726499547, + df=6.0) + """ + xp = array_namespace(a, b) + + a, b = xp_promote(a, b, force_floating=True, xp=xp) + + if axis is None: + a, b, axis = xp_ravel(a), xp_ravel(b), 0 + + if not (0 <= trim < .5): + raise ValueError("Trimming percentage should be 0 <= `trim` < .5.") + + if not isinstance(method, PermutationMethod | MonteCarloMethod | None): + message = ("`method` must be an instance of `PermutationMethod`, an instance " + "of `MonteCarloMethod`, or None (default).") + raise ValueError(message) + + result_shape = _broadcast_array_shapes_remove_axis((a, b), axis=axis) + NaN = _get_nan(a, b, shape=result_shape, xp=xp) + if xp_size(a) == 0 or xp_size(b) == 0: + return TtestResult(NaN, NaN, df=NaN, alternative=NaN, + standard_error=NaN, estimate=NaN) + + alternative_nums = {"less": -1, "two-sided": 0, "greater": 1} + + n1 = _count_nonmasked(a, axis) + n2 = _count_nonmasked(b, axis) + + if trim == 0: + with np.errstate(divide='ignore', invalid='ignore'): + v1 = _var(a, axis, ddof=1, xp=xp) + v2 = _var(b, axis, ddof=1, xp=xp) + + m1 = xp.mean(a, axis=axis) + m2 = xp.mean(b, axis=axis) + else: + v1, m1, n1 = _ttest_trim_var_mean_len(a, trim, axis, xp=xp) + v2, m2, n2 = _ttest_trim_var_mean_len(b, trim, axis, xp=xp) + + if equal_var: + df, denom = _equal_var_ttest_denom(v1, n1, v2, n2, xp=xp) + else: + df, denom = _unequal_var_ttest_denom(v1, n1, v2, n2, xp=xp) + + if method is None: + t, prob = _ttest_ind_from_stats(m1, m2, denom, df, alternative) + else: + # nan_policy is taken care of by axis_nan_policy decorator + ttest_kwargs = dict(equal_var=equal_var, trim=trim) + t, prob = _ttest_resampling(a, b, axis, alternative, + ttest_kwargs, method, xp=xp) + + # when nan_policy='omit', `df` can be different for different axis-slices + df = xp.broadcast_to(df, t.shape) + df = df[()] if df.ndim ==0 else df + estimate = m1 - m2 + + return TtestResult(t, prob, df=df, alternative=alternative_nums[alternative], + standard_error=denom, estimate=estimate) + + +def _ttest_resampling(x, y, axis, alternative, ttest_kwargs, method, *, xp): + def statistic(x, y, axis): + x, y = xp.asarray(x), xp.asarray(y) + return ttest_ind(x, y, axis=axis, **ttest_kwargs).statistic + + test = (permutation_test if isinstance(method, PermutationMethod) + else monte_carlo_test) + method = method._asdict() + + if test is monte_carlo_test: + # `monte_carlo_test` accepts an `rvs` tuple of callables, not an `rng` + # If the user specified an `rng`, replace it with the default callables + if (rng := method.pop('rng', None)) is not None: + rng = np.random.default_rng(rng) + method['rvs'] = rng.normal, rng.normal + + res = test((x, y,), statistic=statistic, axis=axis, + alternative=alternative, **method) + + return res.statistic, res.pvalue + + +def _ttest_trim_var_mean_len(a, trim, axis, *, xp): + """Variance, mean, and length of winsorized input along specified axis""" + # for use with `ttest_ind` when trimming. + # further calculations in this test assume that the inputs are sorted. + # From [4] Section 1 "Let x_1, ..., x_n be n ordered observations..." + a = xp.sort(a, axis=axis) + + # `g` is the number of elements to be replaced on each tail, converted + # from a percentage amount of trimming + n = a.shape[axis] + g = int(n * trim) + + # Calculate the Winsorized variance of the input samples according to + # specified `g` + v = _calculate_winsorized_variance(a, g, axis, xp=xp) + + # the total number of elements in the trimmed samples + n -= 2 * g + + # calculate the g-times trimmed mean, as defined in [4] (1-1) + m = trim_mean(a, trim, axis=axis) + return v, m, n + + +def _calculate_winsorized_variance(a, g, axis, *, xp): + """Calculates g-times winsorized variance along specified axis""" + # it is expected that the input `a` is sorted along the correct axis + if g == 0: + return _var(a, ddof=1, axis=axis, xp=xp) + # move the intended axis to the end that way it is easier to manipulate + a_win = xp.moveaxis(a, axis, -1) + + # save where NaNs are for later use. + nans_indices = xp.any(xp.isnan(a_win), axis=-1) + + # Winsorization and variance calculation are done in one step in [4] + # (1-3), but here winsorization is done first; replace the left and + # right sides with the repeating value. This can be see in effect in ( + # 1-3) in [4], where the leftmost and rightmost tails are replaced with + # `(g + 1) * x_{g + 1}` on the left and `(g + 1) * x_{n - g}` on the + # right. Zero-indexing turns `g + 1` to `g`, and `n - g` to `- g - 1` in + # array indexing. + a_win = xpx.at(a_win)[..., :g].set(a_win[..., g:g+1]) + a_win = xpx.at(a_win)[..., -g:].set(a_win[..., -g - 1:-g]) + + # Determine the variance. In [4], the degrees of freedom is expressed as + # `h - 1`, where `h = n - 2g` (unnumbered equations in Section 1, end of + # page 369, beginning of page 370). This is converted to NumPy's format, + # `n - ddof` for use with `np.var`. The result is converted to an + # array to accommodate indexing later. + var_win = xp.asarray(_var(a_win, ddof=(2 * g + 1), axis=-1, xp=xp)) + + # with `nan_policy='propagate'`, NaNs may be completely trimmed out + # because they were sorted into the tail of the array. In these cases, + # replace computed variances with `np.nan`. + var_win = xpx.at(var_win)[nans_indices].set(xp.nan) + return var_win + + +@xp_capabilities(cpu_only=True, exceptions=["cupy", "jax.numpy"], marray=True, + extra_note=("The ``confidence_interval`` method of the output object" + "is incompatible with JAX JIT.")) +@_axis_nan_policy_factory(pack_TtestResult, default_axis=0, n_samples=2, + result_to_tuple=unpack_TtestResult, n_outputs=6, + paired=True) +def ttest_rel(a, b, axis=0, nan_policy='propagate', alternative="two-sided"): + """Calculate the t-test on TWO RELATED samples of scores, a and b. + + This is a test for the null hypothesis that two related or + repeated samples have identical average (expected) values. + + Parameters + ---------- + a, b : array_like + The arrays must have the same shape. + axis : int or None, optional + Axis along which to compute test. If None, compute over the whole + arrays, `a`, and `b`. + nan_policy : {'propagate', 'raise', 'omit'}, optional + Defines how to handle when input contains nan. + The following options are available (default is 'propagate'): + + * 'propagate': returns nan + * 'raise': throws an error + * 'omit': performs the calculations ignoring nan values + + alternative : {'two-sided', 'less', 'greater'}, optional + Defines the alternative hypothesis. + The following options are available (default is 'two-sided'): + + * 'two-sided': the means of the distributions underlying the samples + are unequal. + * 'less': the mean of the distribution underlying the first sample + is less than the mean of the distribution underlying the second + sample. + * 'greater': the mean of the distribution underlying the first + sample is greater than the mean of the distribution underlying + the second sample. + + .. versionadded:: 1.6.0 + + Returns + ------- + result : `~scipy.stats._result_classes.TtestResult` + An object with the following attributes: + + statistic : float or array + The t-statistic. + pvalue : float or array + The p-value associated with the given alternative. + df : float or array + The number of degrees of freedom used in calculation of the + t-statistic; this is one less than the size of the sample + (``a.shape[axis]``). + + .. versionadded:: 1.10.0 + + The object also has the following method: + + confidence_interval(confidence_level=0.95) + Computes a confidence interval around the difference in + population means for the given confidence level. + The confidence interval is returned in a ``namedtuple`` with + fields `low` and `high`. + + .. versionadded:: 1.10.0 + + Notes + ----- + Examples for use are scores of the same set of student in + different exams, or repeated sampling from the same units. The + test measures whether the average score differs significantly + across samples (e.g. exams). If we observe a large p-value, for + example greater than 0.05 or 0.1 then we cannot reject the null + hypothesis of identical average scores. If the p-value is smaller + than the threshold, e.g. 1%, 5% or 10%, then we reject the null + hypothesis of equal averages. Small p-values are associated with + large t-statistics. + + The t-statistic is calculated as ``np.mean(a - b)/se``, where ``se`` is the + standard error. Therefore, the t-statistic will be positive when the sample + mean of ``a - b`` is greater than zero and negative when the sample mean of + ``a - b`` is less than zero. + + References + ---------- + https://en.wikipedia.org/wiki/T-test#Dependent_t-test_for_paired_samples + + Examples + -------- + >>> import numpy as np + >>> from scipy import stats + >>> rng = np.random.default_rng() + + >>> rvs1 = stats.norm.rvs(loc=5, scale=10, size=500, random_state=rng) + >>> rvs2 = (stats.norm.rvs(loc=5, scale=10, size=500, random_state=rng) + ... + stats.norm.rvs(scale=0.2, size=500, random_state=rng)) + >>> stats.ttest_rel(rvs1, rvs2) + TtestResult(statistic=-0.4549717054410304, pvalue=0.6493274702088672, df=499) + >>> rvs3 = (stats.norm.rvs(loc=8, scale=10, size=500, random_state=rng) + ... + stats.norm.rvs(scale=0.2, size=500, random_state=rng)) + >>> stats.ttest_rel(rvs1, rvs3) + TtestResult(statistic=-5.879467544540889, pvalue=7.540777129099917e-09, df=499) + + """ + return ttest_1samp(a - b, popmean=0., axis=axis, alternative=alternative, + _no_deco=True) + + +# Map from names to lambda_ values used in power_divergence(). +_power_div_lambda_names = { + "pearson": 1, + "log-likelihood": 0, + "freeman-tukey": -0.5, + "mod-log-likelihood": -1, + "neyman": -2, + "cressie-read": 2/3, +} + + +Power_divergenceResult = namedtuple('Power_divergenceResult', + ('statistic', 'pvalue')) + + +def _pd_nsamples(kwargs): + return 2 if kwargs.get('f_exp', None) is not None else 1 + + +@xp_capabilities(marray=True) +@_axis_nan_policy_factory(Power_divergenceResult, paired=True, n_samples=_pd_nsamples, + too_small=-1) +def power_divergence(f_obs, f_exp=None, ddof=0, axis=0, lambda_=None): + """Cressie-Read power divergence statistic and goodness of fit test. + + This function tests the null hypothesis that the categorical data + has the given frequencies, using the Cressie-Read power divergence + statistic. + + Parameters + ---------- + f_obs : array_like + Observed frequencies in each category. + f_exp : array_like, optional + Expected frequencies in each category. By default the categories are + assumed to be equally likely. + ddof : int, optional + "Delta degrees of freedom": adjustment to the degrees of freedom + for the p-value. The p-value is computed using a chi-squared + distribution with ``k - 1 - ddof`` degrees of freedom, where `k` + is the number of observed frequencies. The default value of `ddof` + is 0. + axis : int or None, optional + The axis of the broadcast result of `f_obs` and `f_exp` along which to + apply the test. If axis is None, all values in `f_obs` are treated + as a single data set. Default is 0. + lambda_ : float or str, optional + The power in the Cressie-Read power divergence statistic. The default + is 1. For convenience, `lambda_` may be assigned one of the following + strings, in which case the corresponding numerical value is used: + + * ``"pearson"`` (value 1) + Pearson's chi-squared statistic. In this case, the function is + equivalent to `chisquare`. + * ``"log-likelihood"`` (value 0) + Log-likelihood ratio. Also known as the G-test [3]_. + * ``"freeman-tukey"`` (value -1/2) + Freeman-Tukey statistic. + * ``"mod-log-likelihood"`` (value -1) + Modified log-likelihood ratio. + * ``"neyman"`` (value -2) + Neyman's statistic. + * ``"cressie-read"`` (value 2/3) + The power recommended in [5]_. + + Returns + ------- + res: Power_divergenceResult + An object containing attributes: + + statistic : float or ndarray + The Cressie-Read power divergence test statistic. The value is + a float if `axis` is None or if` `f_obs` and `f_exp` are 1-D. + pvalue : float or ndarray + The p-value of the test. The value is a float if `ddof` and the + return value `stat` are scalars. + + See Also + -------- + chisquare + + Notes + ----- + This test is invalid when the observed or expected frequencies in each + category are too small. A typical rule is that all of the observed + and expected frequencies should be at least 5. + + Also, the sum of the observed and expected frequencies must be the same + for the test to be valid; `power_divergence` raises an error if the sums + do not agree within a relative tolerance of ``eps**0.5``, where ``eps`` + is the precision of the input dtype. + + When `lambda_` is less than zero, the formula for the statistic involves + dividing by `f_obs`, so a warning or error may be generated if any value + in `f_obs` is 0. + + Similarly, a warning or error may be generated if any value in `f_exp` is + zero when `lambda_` >= 0. + + The default degrees of freedom, k-1, are for the case when no parameters + of the distribution are estimated. If p parameters are estimated by + efficient maximum likelihood then the correct degrees of freedom are + k-1-p. If the parameters are estimated in a different way, then the + dof can be between k-1-p and k-1. However, it is also possible that + the asymptotic distribution is not a chisquare, in which case this + test is not appropriate. + + References + ---------- + .. [1] Lowry, Richard. "Concepts and Applications of Inferential + Statistics". Chapter 8. + https://web.archive.org/web/20171015035606/http://faculty.vassar.edu/lowry/ch8pt1.html + .. [2] "Chi-squared test", https://en.wikipedia.org/wiki/Chi-squared_test + .. [3] "G-test", https://en.wikipedia.org/wiki/G-test + .. [4] Sokal, R. R. and Rohlf, F. J. "Biometry: the principles and + practice of statistics in biological research", New York: Freeman + (1981) + .. [5] Cressie, N. and Read, T. R. C., "Multinomial Goodness-of-Fit + Tests", J. Royal Stat. Soc. Series B, Vol. 46, No. 3 (1984), + pp. 440-464. + + Examples + -------- + (See `chisquare` for more examples.) + + When just `f_obs` is given, it is assumed that the expected frequencies + are uniform and given by the mean of the observed frequencies. Here we + perform a G-test (i.e. use the log-likelihood ratio statistic): + + >>> import numpy as np + >>> from scipy.stats import power_divergence + >>> power_divergence([16, 18, 16, 14, 12, 12], lambda_='log-likelihood') + (2.006573162632538, 0.84823476779463769) + + The expected frequencies can be given with the `f_exp` argument: + + >>> power_divergence([16, 18, 16, 14, 12, 12], + ... f_exp=[16, 16, 16, 16, 16, 8], + ... lambda_='log-likelihood') + (3.3281031458963746, 0.6495419288047497) + + When `f_obs` is 2-D, by default the test is applied to each column. + + >>> obs = np.array([[16, 18, 16, 14, 12, 12], [32, 24, 16, 28, 20, 24]]).T + >>> obs.shape + (6, 2) + >>> power_divergence(obs, lambda_="log-likelihood") + (array([ 2.00657316, 6.77634498]), array([ 0.84823477, 0.23781225])) + + By setting ``axis=None``, the test is applied to all data in the array, + which is equivalent to applying the test to the flattened array. + + >>> power_divergence(obs, axis=None) + (23.31034482758621, 0.015975692534127565) + >>> power_divergence(obs.ravel()) + (23.31034482758621, 0.015975692534127565) + + `ddof` is the change to make to the default degrees of freedom. + + >>> power_divergence([16, 18, 16, 14, 12, 12], ddof=1) + (2.0, 0.73575888234288467) + + The calculation of the p-values is done by broadcasting the + test statistic with `ddof`. + + >>> power_divergence([16, 18, 16, 14, 12, 12], ddof=[0,1,2]) + (2.0, array([ 0.84914504, 0.73575888, 0.5724067 ])) + + `f_obs` and `f_exp` are also broadcast. In the following, `f_obs` has + shape (6,) and `f_exp` has shape (2, 6), so the result of broadcasting + `f_obs` and `f_exp` has shape (2, 6). To compute the desired chi-squared + statistics, we must use ``axis=1``: + + >>> power_divergence([16, 18, 16, 14, 12, 12], + ... f_exp=[[16, 16, 16, 16, 16, 8], + ... [8, 20, 20, 16, 12, 12]], + ... axis=1) + (array([ 3.5 , 9.25]), array([ 0.62338763, 0.09949846])) + + """ + return _power_divergence(f_obs, f_exp=f_exp, ddof=ddof, axis=axis, lambda_=lambda_) + + +def _power_divergence(f_obs, f_exp, ddof, axis, lambda_, sum_check=True): + xp = array_namespace(f_obs, f_exp, ddof) + f_obs, f_exp, ddof = xp_promote(f_obs, f_exp, ddof, + force_floating=True, xp=xp) + + # Convert the input argument `lambda_` to a numerical value. + if isinstance(lambda_, str): + if lambda_ not in _power_div_lambda_names: + names = repr(list(_power_div_lambda_names.keys()))[1:-1] + raise ValueError(f"invalid string for lambda_: {lambda_!r}. " + f"Valid strings are {names}") + lambda_ = _power_div_lambda_names[lambda_] + elif lambda_ is None: + lambda_ = 1 + + if f_exp is not None: + # not sure why we force to float64, but not going to touch it + f_obs_float = xp.asarray(f_obs, dtype=xp.float64) + bshape = _broadcast_shapes((f_obs_float.shape, f_exp.shape)) + f_obs_float = xp.broadcast_to(f_obs_float, bshape) + f_exp = xp.broadcast_to(f_exp, bshape) + f_obs_float, f_exp = _share_masks(f_obs_float, f_exp, xp=xp) + + if sum_check: + dtype_res = xp.result_type(f_obs.dtype, f_exp.dtype) + rtol = xp.finfo(dtype_res).eps**0.5 # to pass existing tests + with np.errstate(invalid='ignore'): + f_obs_sum = xp.sum(f_obs_float, axis=axis, keepdims=True) + f_exp_sum = xp.sum(f_exp, axis=axis, keepdims=True) + relative_diff = (xp.abs(f_obs_sum - f_exp_sum) / + xp.minimum(f_obs_sum, f_exp_sum)) + diff_gt_tol = xp.any(relative_diff > rtol, axis=axis, keepdims=True) + + if not is_lazy_array(diff_gt_tol) and xp.any(diff_gt_tol): + msg = (f"For each axis slice, the sum of the observed " + f"frequencies must agree with the sum of the " + f"expected frequencies to a relative tolerance " + f"of {rtol}, but the percent differences are:\n" + f"{relative_diff}") + raise ValueError(msg) + elif is_lazy_array(diff_gt_tol): + diff_gt_tol = xp.broadcast_to(diff_gt_tol, f_obs.shape) + f_obs = xpx.at(f_obs)[diff_gt_tol].set(xp.nan) + + else: + # Avoid warnings with the edge case of a data set with length 0 + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + f_exp = xp.mean(f_obs, axis=axis, keepdims=True) + + # `terms` is the array of terms that are summed along `axis` to create + # the test statistic. We use some specialized code for a few special + # cases of lambda_. + if lambda_ == 1: + # Pearson's chi-squared statistic + terms = (f_obs - f_exp)**2 / f_exp + elif lambda_ == 0: + # Log-likelihood ratio (i.e. G-test) + terms = 2.0 * special.xlogy(f_obs, f_obs / f_exp) + elif lambda_ == -1: + # Modified log-likelihood ratio + terms = 2.0 * special.xlogy(f_exp, f_exp / f_obs) + else: + # General Cressie-Read power divergence. + terms = f_obs * ((f_obs / f_exp)**lambda_ - 1) + terms /= 0.5 * lambda_ * (lambda_ + 1) + + stat = xp.sum(terms, axis=axis) + + num_obs = xp.asarray(_count_nonmasked(terms, axis), device=xp_device(terms), + dtype=f_obs.dtype) + + df = num_obs - 1 - ddof + chi2 = _SimpleChi2(df) + pvalue = _get_pvalue(stat, chi2 , alternative='greater', symmetric=False, xp=xp) + + stat = stat[()] if stat.ndim == 0 else stat + pvalue = pvalue[()] if pvalue.ndim == 0 else pvalue + + return Power_divergenceResult(stat, pvalue) + + + +@xp_capabilities(marray=True) +@_axis_nan_policy_factory(Power_divergenceResult, paired=True, n_samples=_pd_nsamples, + too_small=-1) +def chisquare(f_obs, f_exp=None, ddof=0, axis=0, *, sum_check=True): + """Perform Pearson's chi-squared test. + + Pearson's chi-squared test [1]_ is a goodness-of-fit test for a multinomial + distribution with given probabilities; that is, it assesses the null hypothesis + that the observed frequencies (counts) are obtained by independent + sampling of *N* observations from a categorical distribution with given + expected frequencies. + + Parameters + ---------- + f_obs : array_like + Observed frequencies in each category. + f_exp : array_like, optional + Expected frequencies in each category. By default, the categories are + assumed to be equally likely. + ddof : int, optional + "Delta degrees of freedom": adjustment to the degrees of freedom + for the p-value. The p-value is computed using a chi-squared + distribution with ``k - 1 - ddof`` degrees of freedom, where ``k`` + is the number of categories. The default value of `ddof` is 0. + axis : int or None, optional + The axis of the broadcast result of `f_obs` and `f_exp` along which to + apply the test. If axis is None, all values in `f_obs` are treated + as a single data set. Default is 0. + sum_check : bool, optional + Whether to perform a check that ``sum(f_obs) - sum(f_exp) == 0``. If True, + (default) raise an error (or, for lazy backends, return NaN) when the relative + difference exceeds the square root of the precision of the data type. + See Notes for rationale and possible exceptions. + + Returns + ------- + res: Power_divergenceResult + An object containing attributes: + + statistic : float or ndarray + The chi-squared test statistic. The value is a float if `axis` is + None or `f_obs` and `f_exp` are 1-D. + pvalue : float or ndarray + The p-value of the test. The value is a float if `ddof` and the + result attribute `statistic` are scalars. + + See Also + -------- + scipy.stats.power_divergence + scipy.stats.fisher_exact : Fisher exact test on a 2x2 contingency table. + scipy.stats.barnard_exact : An unconditional exact test. An alternative + to chi-squared test for small sample sizes. + :ref:`hypothesis_chisquare` : Extended example + + Notes + ----- + This test is invalid when the observed or expected frequencies in each + category are too small. A typical rule is that all of the observed + and expected frequencies should be at least 5. According to [2]_, the + total number of observations is recommended to be greater than 13, + otherwise exact tests (such as Barnard's Exact test) should be used + because they do not overreject. + + The default degrees of freedom, k-1, are for the case when no parameters + of the distribution are estimated. If p parameters are estimated by + efficient maximum likelihood then the correct degrees of freedom are + k-1-p. If the parameters are estimated in a different way, then the + dof can be between k-1-p and k-1. However, it is also possible that + the asymptotic distribution is not chi-square, in which case this test + is not appropriate. + + For Pearson's chi-squared test, the total observed and expected counts must match + for the p-value to accurately reflect the probability of observing such an extreme + value of the statistic under the null hypothesis. + This function may be used to perform other statistical tests that do not require + the total counts to be equal. For instance, to test the null hypothesis that + ``f_obs[i]`` is Poisson-distributed with expectation ``f_exp[i]``, set ``ddof=-1`` + and ``sum_check=False``. This test follows from the fact that a Poisson random + variable with mean and variance ``f_exp[i]`` is approximately normal with the + same mean and variance; the chi-squared statistic standardizes, squares, and sums + the observations; and the sum of ``n`` squared standard normal variables follows + the chi-squared distribution with ``n`` degrees of freedom. + + References + ---------- + .. [1] "Pearson's chi-squared test". + *Wikipedia*. https://en.wikipedia.org/wiki/Pearson%27s_chi-squared_test + .. [2] Pearson, Karl. "On the criterion that a given system of deviations from the probable + in the case of a correlated system of variables is such that it can be reasonably + supposed to have arisen from random sampling", Philosophical Magazine. Series 5. 50 + (1900), pp. 157-175. + + Examples + -------- + When only the mandatory `f_obs` argument is given, it is assumed that the + expected frequencies are uniform and given by the mean of the observed + frequencies: + + >>> import numpy as np + >>> from scipy.stats import chisquare + >>> chisquare([16, 18, 16, 14, 12, 12]) + Power_divergenceResult(statistic=2.0, pvalue=0.84914503608460956) + + The optional `f_exp` argument gives the expected frequencies. + + >>> chisquare([16, 18, 16, 14, 12, 12], f_exp=[16, 16, 16, 16, 16, 8]) + Power_divergenceResult(statistic=3.5, pvalue=0.62338762774958223) + + When `f_obs` is 2-D, by default the test is applied to each column. + + >>> obs = np.array([[16, 18, 16, 14, 12, 12], [32, 24, 16, 28, 20, 24]]).T + >>> obs.shape + (6, 2) + >>> chisquare(obs) + Power_divergenceResult(statistic=array([2. , 6.66666667]), pvalue=array([0.84914504, 0.24663415])) + + By setting ``axis=None``, the test is applied to all data in the array, + which is equivalent to applying the test to the flattened array. + + >>> chisquare(obs, axis=None) + Power_divergenceResult(statistic=23.31034482758621, pvalue=0.015975692534127565) + >>> chisquare(obs.ravel()) + Power_divergenceResult(statistic=23.310344827586206, pvalue=0.01597569253412758) + + `ddof` is the change to make to the default degrees of freedom. + + >>> chisquare([16, 18, 16, 14, 12, 12], ddof=1) + Power_divergenceResult(statistic=2.0, pvalue=0.7357588823428847) + + The calculation of the p-values is done by broadcasting the + chi-squared statistic with `ddof`. + + >>> chisquare([16, 18, 16, 14, 12, 12], ddof=[0, 1, 2]) + Power_divergenceResult(statistic=2.0, pvalue=array([0.84914504, 0.73575888, 0.5724067 ])) + + `f_obs` and `f_exp` are also broadcast. In the following, `f_obs` has + shape (6,) and `f_exp` has shape (2, 6), so the result of broadcasting + `f_obs` and `f_exp` has shape (2, 6). To compute the desired chi-squared + statistics, we use ``axis=1``: + + >>> chisquare([16, 18, 16, 14, 12, 12], + ... f_exp=[[16, 16, 16, 16, 16, 8], [8, 20, 20, 16, 12, 12]], + ... axis=1) + Power_divergenceResult(statistic=array([3.5 , 9.25]), pvalue=array([0.62338763, 0.09949846])) + + For a more detailed example, see :ref:`hypothesis_chisquare`. + """ # noqa: E501 + return _power_divergence(f_obs, f_exp=f_exp, ddof=ddof, axis=axis, + lambda_="pearson", sum_check=sum_check) + + +KstestResult = _make_tuple_bunch('KstestResult', ['statistic', 'pvalue'], + ['statistic_location', 'statistic_sign']) + + +def _compute_d(cdfvals, x, sign, xp=None): + """Computes D+/D- as used in the Kolmogorov-Smirnov test. + + Vectorized along the last axis. + + Parameters + ---------- + cdfvals : array_like + Sorted array of CDF values between 0 and 1 + x: array_like + Sorted array of the stochastic variable itself + sign: int + Indicates whether to compute D+ (+1) or D- (-1). + + Returns + ------- + D : float or array + The maximum distance of the CDF values below/above (D+/D-) Uniform(0, 1). + loc_max : float or array + The location at which the maximum is reached. + """ + xp = array_namespace(cdfvals, x) if xp is None else xp + length = cdfvals.shape[-1] + n = _count_nonmasked(cdfvals, axis=-1, keepdims=True) + D = (xp.arange(1.0, length + 1, dtype=x.dtype) / n - cdfvals if sign == +1 + else (cdfvals - xp.arange(0.0, length, dtype=x.dtype) / n)) + amax = xp.argmax(D, axis=-1, keepdims=True) + loc_max = xp.squeeze(xp.take_along_axis(x, amax, axis=-1), axis=-1) + D = xp.squeeze(xp.take_along_axis(D, amax, axis=-1), axis=-1) + return D[()] if D.ndim == 0 else D, loc_max[()] if loc_max.ndim == 0 else loc_max + + +def _tuple_to_KstestResult(statistic, pvalue, + statistic_location, statistic_sign): + return KstestResult(statistic, pvalue, + statistic_location=statistic_location, + statistic_sign=statistic_sign) + + +def _KstestResult_to_tuple(res, _): + return *res, res.statistic_location, res.statistic_sign + + +# JAX JIT / Torch GPU / CuPy need null distribution +@xp_capabilities(cpu_only=True, jax_jit=False, marray=True, + skip_backends=[('dask.array', 'needs take_along_axis')]) +@_axis_nan_policy_factory(_tuple_to_KstestResult, n_samples=1, n_outputs=4, + result_to_tuple=_KstestResult_to_tuple) +@_rename_parameter("mode", "method") +def ks_1samp(x, cdf, args=(), alternative='two-sided', method='auto', *, axis=0): + """ + Performs the one-sample Kolmogorov-Smirnov test for goodness of fit. + + This test compares the underlying distribution F(x) of a sample + against a given continuous distribution G(x). See Notes for a description + of the available null and alternative hypotheses. + + Parameters + ---------- + x : array_like + a 1-D array of observations of iid random variables. + cdf : callable + callable used to calculate the cdf. + args : tuple, sequence, optional + Distribution parameters, used with `cdf`. + alternative : {'two-sided', 'less', 'greater'}, optional + Defines the null and alternative hypotheses. Default is 'two-sided'. + Please see explanations in the Notes below. + method : {'auto', 'exact', 'approx', 'asymp'}, optional + Defines the distribution used for calculating the p-value. + The following options are available (default is 'auto'): + + * 'auto' : selects one of the other options. + * 'exact' : uses the exact distribution of test statistic. + * 'approx' : approximates the two-sided probability with twice + the one-sided probability + * 'asymp': uses asymptotic distribution of test statistic + + axis : int or tuple of ints, default: 0 + If an int or tuple of ints, the axis or axes of the input along which + to compute the statistic. The statistic of each axis-slice (e.g. row) + of the input will appear in a corresponding element of the output. + If ``None``, the input will be raveled before computing the statistic. + + Returns + ------- + res: KstestResult + An object containing attributes: + + statistic : float + KS test statistic, either D+, D-, or D (the maximum of the two) + pvalue : float + One-tailed or two-tailed p-value. + statistic_location : float + Value of `x` corresponding with the KS statistic; i.e., the + distance between the empirical distribution function and the + hypothesized cumulative distribution function is measured at this + observation. + statistic_sign : int + +1 if the KS statistic is the maximum positive difference between + the empirical distribution function and the hypothesized cumulative + distribution function (D+); -1 if the KS statistic is the maximum + negative difference (D-). + + + See Also + -------- + ks_2samp, kstest + + Notes + ----- + There are three options for the null and corresponding alternative + hypothesis that can be selected using the `alternative` parameter. + + - `two-sided`: The null hypothesis is that the two distributions are + identical, F(x)=G(x) for all x; the alternative is that they are not + identical. + + - `less`: The null hypothesis is that F(x) >= G(x) for all x; the + alternative is that F(x) < G(x) for at least one x. + + - `greater`: The null hypothesis is that F(x) <= G(x) for all x; the + alternative is that F(x) > G(x) for at least one x. + + Note that the alternative hypotheses describe the *CDFs* of the + underlying distributions, not the observed values. For example, + suppose x1 ~ F and x2 ~ G. If F(x) > G(x) for all x, the values in + x1 tend to be less than those in x2. + + Examples + -------- + Suppose we wish to test the null hypothesis that a sample is distributed + according to the standard normal. + We choose a confidence level of 95%; that is, we will reject the null + hypothesis in favor of the alternative if the p-value is less than 0.05. + + When testing uniformly distributed data, we would expect the + null hypothesis to be rejected. + + >>> import numpy as np + >>> from scipy import stats + >>> rng = np.random.default_rng() + >>> stats.ks_1samp(stats.uniform.rvs(size=100, random_state=rng), + ... stats.norm.cdf) + KstestResult(statistic=0.5001899973268688, + pvalue=1.1616392184763533e-23, + statistic_location=0.00047625268963724654, + statistic_sign=-1) + + Indeed, the p-value is lower than our threshold of 0.05, so we reject the + null hypothesis in favor of the default "two-sided" alternative: the data + are *not* distributed according to the standard normal. + + When testing random variates from the standard normal distribution, we + expect the data to be consistent with the null hypothesis most of the time. + + >>> x = stats.norm.rvs(size=100, random_state=rng) + >>> stats.ks_1samp(x, stats.norm.cdf) + KstestResult(statistic=0.05345882212970396, + pvalue=0.9227159037744717, + statistic_location=-1.2451343873745018, + statistic_sign=1) + + As expected, the p-value of 0.92 is not below our threshold of 0.05, so + we cannot reject the null hypothesis. + + Suppose, however, that the random variates are distributed according to + a normal distribution that is shifted toward greater values. In this case, + the cumulative density function (CDF) of the underlying distribution tends + to be *less* than the CDF of the standard normal. Therefore, we would + expect the null hypothesis to be rejected with ``alternative='less'``: + + >>> x = stats.norm.rvs(size=100, loc=0.5, random_state=rng) + >>> stats.ks_1samp(x, stats.norm.cdf, alternative='less') + KstestResult(statistic=0.17482387821055168, + pvalue=0.001913921057766743, + statistic_location=0.3713830565352756, + statistic_sign=-1) + + and indeed, with p-value smaller than our threshold, we reject the null + hypothesis in favor of the alternative. + + """ + # `_axis_nan_policy` decorator ensures `axis=-1` + xp = array_namespace(x) + mode = method + if mode not in ['auto', 'exact', 'approx', 'asymp']: + raise ValueError( + f"Invalid value for method: {mode!r}. " + f"Must be one of 'auto', 'exact', 'approx', 'asymp'." + ) + + alternative = {'t': 'two-sided', 'g': 'greater', 'l': 'less'}.get( + alternative.lower()[0], alternative) + if alternative not in ['two-sided', 'greater', 'less']: + raise ValueError(f"Unexpected value {alternative=}") + + x = xp.sort(x, axis=-1) + x = xp_promote(x, force_floating=True, xp=xp) + N = _count_nonmasked(x, axis=-1, xp=xp) + cdfvals = _masked_apply(cdf, args=(x, *args), xp=xp) + + ones = xp.ones(x.shape[:-1], dtype=xp.int8) + ones = ones[()] if ones.ndim == 0 else ones + + if alternative == 'greater': + Dplus, d_location = _compute_d(cdfvals, x, +1) + pvalue = _masked_apply(distributions.ksone.sf, args=(Dplus, N), xp=xp) + pvalue = xp.asarray(pvalue, dtype=x.dtype) + pvalue = pvalue[()] if pvalue.ndim == 0 else pvalue + Dplus = xp.asarray(Dplus) if is_marray(xp) else Dplus + return KstestResult(Dplus, pvalue, + statistic_location=d_location, + statistic_sign=ones) + + if alternative == 'less': + Dminus, d_location = _compute_d(cdfvals, x, -1) + pvalue = _masked_apply(distributions.ksone.sf, args=(Dminus, N), xp=xp) + pvalue = xp.asarray(pvalue, dtype=x.dtype) + pvalue = pvalue[()] if pvalue.ndim == 0 else pvalue + Dminus = xp.asarray(Dminus) if is_marray(xp) else Dminus + return KstestResult(Dminus, pvalue, + statistic_location=d_location, + statistic_sign=-ones) + + # alternative == 'two-sided': + Dplus, dplus_location = _compute_d(cdfvals, x, +1) + Dminus, dminus_location = _compute_d(cdfvals, x, -1) + i_plus = Dplus > Dminus + D = xp.where(i_plus, Dplus, Dminus) + d_location = xp.where(i_plus, dplus_location, dminus_location) + d_sign = xp.where(i_plus, ones, -ones) + if D.ndim == 0: + D, d_location, d_sign = D[()], d_location[()], d_sign[()] + + if mode == 'auto': # Always select exact + mode = 'exact' + if mode == 'exact': + prob = _masked_apply(distributions.kstwo.sf, args=(D, N), xp=xp) + elif mode == 'asymp': + prob = _masked_apply(distributions.kstwobign.sf, args=(D * N**0.5,), xp=xp) + else: + # mode == 'approx' + prob = 2 * _masked_apply(distributions.ksone.sf, args=(D, N), xp=xp) + prob = xp.clip(xp.asarray(prob, dtype=x.dtype), 0., 1.) + return KstestResult(D, prob, + statistic_location=d_location, + statistic_sign=d_sign) + + +Ks_2sampResult = KstestResult + + +def _compute_prob_outside_square(n, h): + """ + Compute the proportion of paths that pass outside the two diagonal lines. + + Parameters + ---------- + n : int + n > 0 + h : int + 0 <= h <= n + + Returns + ------- + p : float + The proportion of paths that pass outside the lines x-y = +/-h. + + """ + # Compute Pr(D_{n,n} >= h/n) + # Prob = 2 * ( binom(2n, n-h) - binom(2n, n-2a) + binom(2n, n-3a) - ... ) + # / binom(2n, n) + # This formulation exhibits subtractive cancellation. + # Instead divide each term by binom(2n, n), then factor common terms + # and use a Horner-like algorithm + # P = 2 * A0 * (1 - A1*(1 - A2*(1 - A3*(1 - A4*(...))))) + + P = 0.0 + k = int(np.floor(n / h)) + while k >= 0: + p1 = 1.0 + # Each of the Ai terms has numerator and denominator with + # h simple terms. + for j in range(h): + p1 = (n - k * h - j) * p1 / (n + k * h + j + 1) + P = p1 * (1.0 - P) + k -= 1 + return 2 * P + + +def _count_paths_outside_method(m, n, g, h): + """Count the number of paths that pass outside the specified diagonal. + + Parameters + ---------- + m : int + m > 0 + n : int + n > 0 + g : int + g is greatest common divisor of m and n + h : int + 0 <= h <= lcm(m,n) + + Returns + ------- + p : float + The number of paths that go low. + The calculation may overflow - check for a finite answer. + + Notes + ----- + Count the integer lattice paths from (0, 0) to (m, n), which at some + point (x, y) along the path, satisfy: + m*y <= n*x - h*g + The paths make steps of size +1 in either positive x or positive y + directions. + + We generally follow Hodges' treatment of Drion/Gnedenko/Korolyuk. + Hodges, J.L. Jr., + "The Significance Probability of the Smirnov Two-Sample Test," + Arkiv fiur Matematik, 3, No. 43 (1958), 469-86. + + """ + # Compute #paths which stay lower than x/m-y/n = h/lcm(m,n) + # B(x, y) = #{paths from (0,0) to (x,y) without + # previously crossing the boundary} + # = binom(x, y) - #{paths which already reached the boundary} + # Multiply by the number of path extensions going from (x, y) to (m, n) + # Sum. + + # Probability is symmetrical in m, n. Computation below assumes m >= n. + if m < n: + m, n = n, m + mg = m // g + ng = n // g + + # Not every x needs to be considered. + # xj holds the list of x values to be checked. + # Wherever n*x/m + ng*h crosses an integer + lxj = n + (mg-h)//mg + xj = [(h + mg * j + ng-1)//ng for j in range(lxj)] + # B is an array just holding a few values of B(x,y), the ones needed. + # B[j] == B(x_j, j) + if lxj == 0: + return special.binom(m + n, n) + B = np.zeros(lxj) + B[0] = 1 + # Compute the B(x, y) terms + for j in range(1, lxj): + Bj = special.binom(xj[j] + j, j) + for i in range(j): + bin = special.binom(xj[j] - xj[i] + j - i, j-i) + Bj -= bin * B[i] + B[j] = Bj + # Compute the number of path extensions... + num_paths = 0 + for j in range(lxj): + bin = special.binom((m-xj[j]) + (n - j), n-j) + term = B[j] * bin + num_paths += term + return num_paths + + +def _attempt_exact_2kssamp(n1, n2, g, d, alternative): + """Attempts to compute the exact 2sample probability. + + n1, n2 are the sample sizes + g is the gcd(n1, n2) + d is the computed max difference in ECDFs + + Returns (success, d, probability) + """ + lcm = (n1 // g) * n2 + h = int(np.round(d * lcm)) + d = h * 1.0 / lcm + if h == 0: + return True, d, 1.0 + saw_fp_error, prob = False, np.nan + try: + with np.errstate(invalid="raise", over="raise"): + if alternative == 'two-sided': + if n1 == n2: + prob = _compute_prob_outside_square(n1, h) + else: + prob = _compute_outer_prob_inside_method(n1, n2, g, h) + else: + if n1 == n2: + # prob = binom(2n, n-h) / binom(2n, n) + # Evaluating in that form incurs roundoff errors + # from special.binom. Instead calculate directly + jrange = np.arange(h) + prob = np.prod((n1 - jrange) / (n1 + jrange + 1.0)) + else: + with np.errstate(over='raise'): + num_paths = _count_paths_outside_method(n1, n2, g, h) + bin = special.binom(n1 + n2, n1) + if num_paths > bin or np.isinf(bin): + saw_fp_error = True + else: + prob = num_paths / bin + + except (FloatingPointError, OverflowError): + saw_fp_error = True + + if saw_fp_error: + return False, d, np.nan + if not (0 <= prob <= 1): + return False, d, prob + return True, d, prob + + +@xp_capabilities(skip_backends=[('dask.array', 'no rankdata')], + jax_jit=False, cpu_only=True, # null distribution is NumPy-only + marray=True) +@_axis_nan_policy_factory(_tuple_to_KstestResult, n_samples=2, n_outputs=4, + result_to_tuple=_KstestResult_to_tuple) +@_rename_parameter("mode", "method") +def ks_2samp(data1, data2, alternative='two-sided', method='auto', *, axis=0): + """ + Performs the two-sample Kolmogorov-Smirnov test for goodness of fit. + + This test compares the underlying continuous distributions F(x) and G(x) + of two independent samples. See Notes for a description of the available + null and alternative hypotheses. + + Parameters + ---------- + data1, data2 : array_like, 1-Dimensional + Two arrays of sample observations assumed to be drawn from a continuous + distribution, sample sizes can be different. + alternative : {'two-sided', 'less', 'greater'}, optional + Defines the null and alternative hypotheses. Default is 'two-sided'. + Please see explanations in the Notes below. + method : {'auto', 'exact', 'asymp'}, optional + Defines the method used for calculating the p-value. + The following options are available (default is 'auto'): + + * 'auto' : use 'exact' for small size arrays, 'asymp' for large + * 'exact' : use exact distribution of test statistic + * 'asymp' : use asymptotic distribution of test statistic + + axis : int or tuple of ints, default: 0 + If an int or tuple of ints, the axis or axes of the input along which + to compute the statistic. The statistic of each axis-slice (e.g. row) + of the input will appear in a corresponding element of the output. + If ``None``, the input will be raveled before computing the statistic. + + Returns + ------- + res: KstestResult + An object containing attributes: + + statistic : float + KS test statistic. + pvalue : float + One-tailed or two-tailed p-value. + statistic_location : float + Value from `data1` or `data2` corresponding with the KS statistic; + i.e., the distance between the empirical distribution functions is + measured at this observation. + statistic_sign : int + +1 if the empirical distribution function of `data1` exceeds + the empirical distribution function of `data2` at + `statistic_location`, otherwise -1. + + See Also + -------- + kstest, ks_1samp, epps_singleton_2samp, anderson_ksamp + + Notes + ----- + There are three options for the null and corresponding alternative + hypothesis that can be selected using the `alternative` parameter. + + - `less`: The null hypothesis is that F(x) >= G(x) for all x; the + alternative is that F(x) < G(x) for at least one x. The statistic + is the magnitude of the minimum (most negative) difference between the + empirical distribution functions of the samples. + + - `greater`: The null hypothesis is that F(x) <= G(x) for all x; the + alternative is that F(x) > G(x) for at least one x. The statistic + is the maximum (most positive) difference between the empirical + distribution functions of the samples. + + - `two-sided`: The null hypothesis is that the two distributions are + identical, F(x)=G(x) for all x; the alternative is that they are not + identical. The statistic is the maximum absolute difference between the + empirical distribution functions of the samples. + + Note that the alternative hypotheses describe the *CDFs* of the + underlying distributions, not the observed values of the data. For example, + suppose x1 ~ F and x2 ~ G. If F(x) > G(x) for all x, the values in + x1 tend to be less than those in x2. + + If the KS statistic is large, then the p-value will be small, and this may + be taken as evidence against the null hypothesis in favor of the + alternative. + + If ``method='exact'``, `ks_2samp` attempts to compute an exact p-value, + that is, the probability under the null hypothesis of obtaining a test + statistic value as extreme as the value computed from the data. + If ``method='asymp'``, the asymptotic Kolmogorov-Smirnov distribution is + used to compute an approximate p-value. + If ``method='auto'``, an exact p-value computation is attempted if both + sample sizes are less than 10000; otherwise, the asymptotic method is used. + In any case, if an exact p-value calculation is attempted and fails, a + warning will be emitted, and the asymptotic p-value will be returned. + + The 'two-sided' 'exact' computation computes the complementary probability + and then subtracts from 1. As such, the minimum probability it can return + is about 1e-16. While the algorithm itself is exact, numerical + errors may accumulate for large sample sizes. It is most suited to + situations in which one of the sample sizes is only a few thousand. + + We generally follow Hodges' treatment of Drion/Gnedenko/Korolyuk [1]_. + + References + ---------- + .. [1] Hodges, J.L. Jr., "The Significance Probability of the Smirnov + Two-Sample Test," Arkiv fiur Matematik, 3, No. 43 (1958), 469-486. + + Examples + -------- + Suppose we wish to test the null hypothesis that two samples were drawn + from the same distribution. + We choose a confidence level of 95%; that is, we will reject the null + hypothesis in favor of the alternative if the p-value is less than 0.05. + + If the first sample were drawn from a uniform distribution and the second + were drawn from the standard normal, we would expect the null hypothesis + to be rejected. + + >>> import numpy as np + >>> from scipy import stats + >>> rng = np.random.default_rng() + >>> sample1 = stats.uniform.rvs(size=100, random_state=rng) + >>> sample2 = stats.norm.rvs(size=110, random_state=rng) + >>> stats.ks_2samp(sample1, sample2) + KstestResult(statistic=0.5454545454545454, + pvalue=7.37417839555191e-15, + statistic_location=-0.014071496412861274, + statistic_sign=-1) + + + Indeed, the p-value is lower than our threshold of 0.05, so we reject the + null hypothesis in favor of the default "two-sided" alternative: the data + were *not* drawn from the same distribution. + + When both samples are drawn from the same distribution, we expect the data + to be consistent with the null hypothesis most of the time. + + >>> sample1 = stats.norm.rvs(size=105, random_state=rng) + >>> sample2 = stats.norm.rvs(size=95, random_state=rng) + >>> stats.ks_2samp(sample1, sample2) + KstestResult(statistic=0.10927318295739348, + pvalue=0.5438289009927495, + statistic_location=-0.1670157701848795, + statistic_sign=-1) + + As expected, the p-value of 0.54 is not below our threshold of 0.05, so + we cannot reject the null hypothesis. + + Suppose, however, that the first sample were drawn from + a normal distribution shifted toward greater values. In this case, + the cumulative density function (CDF) of the underlying distribution tends + to be *less* than the CDF underlying the second sample. Therefore, we would + expect the null hypothesis to be rejected with ``alternative='less'``: + + >>> sample1 = stats.norm.rvs(size=105, loc=0.5, random_state=rng) + >>> stats.ks_2samp(sample1, sample2, alternative='less') + KstestResult(statistic=0.4055137844611529, + pvalue=3.5474563068855554e-08, + statistic_location=-0.13249370614972575, + statistic_sign=-1) + + and indeed, with p-value smaller than our threshold, we reject the null + hypothesis in favor of the alternative. + + """ + # because of the _axis_nan_policy decorator, we can assume the arrays + # are broadcasted and `axis=-1` + mode = method + + if mode not in ['auto', 'exact', 'asymp']: + raise ValueError(f'Invalid value for mode: {mode}') + alternative = {'t': 'two-sided', 'g': 'greater', 'l': 'less'}.get( + alternative.lower()[0], alternative) + if alternative not in ['two-sided', 'less', 'greater']: + raise ValueError(f'Invalid value for alternative: {alternative}') + MAX_AUTO_N = 10000 # 'auto' will attempt to be exact if n1,n2 <= MAX_AUTO_N + + xp = array_namespace(data1, data2) + data1 = xp.sort(data1, axis=-1) + data2 = xp.sort(data2, axis=-1) + n1 = data1.shape[-1] + n2 = data2.shape[-1] + if min(n1, n2) == 0: + raise ValueError('Data passed to ks_2samp must not be empty') + + data_all = xp.concat((data1, data2), axis=-1) + batch_shape = data_all.shape[:-1] + dtype = xp_result_type(data1, data2, force_floating=True, xp=xp) + + if is_marray(xp): + # Previously, we used this algorithm for all backends: + n1 = xp.astype(_count_nonmasked(data1, axis=-1), dtype) + n2 = xp.astype(_count_nonmasked(data2, axis=-1), dtype) + cdf1 = xp.astype(_xp_searchsorted(data1, data_all, side='right'), dtype) + cdf2 = xp.astype(_xp_searchsorted(data2, data_all, side='right'), dtype) + cddiffs = cdf1/n1[..., xp.newaxis] - cdf2/n2[..., xp.newaxis] + # but the switch from `xp.searchsorted` to `_xp_searchsorted` would come with a + # pretty steep performance hit, so the algorithm below is faster. + # When `xp.searchsorted` is vectorized, we can revert. + else: + n = n1 + n2 + + # We want the ECDF of each sample evaluated at *all* the points in the pooled + # sample. The values each ECDF can assume are given by: + cdf1_vals = xp.broadcast_to(xp.linspace(0, 1, n1 + 1, dtype=dtype), + batch_shape + (n1 + 1,)) + cdf2_vals = xp.broadcast_to(xp.linspace(0, 1, n2 + 1, dtype=dtype), + batch_shape + (n2 + 1,)) + # Now we "just" need to know how many times each of these values *will* be + # assumed when we evaluate the ECDFs at all points in the pooled sample. + # These counts are given by the differences between consecutive ("min" or "max") + # ranks corresponding with the observations in the (sorted) samples. + ranks, data_all, _ = _rankdata(data_all, method='min', return_sorted=True) + ranks = xp.astype(ranks, xp.asarray(1).dtype) # default int type + one = xp.ones((*ranks.shape[:-1], 1), dtype=ranks.dtype, + device=xp_device(ranks)) + cdf1_counts = xp.diff(ranks[..., :n1], prepend=one, append=n + one, axis=-1) + cdf2_counts = xp.diff(ranks[..., -n2:], prepend=one, append=n + one, axis=-1) + # Repeat isn't vectorized - in general, this would produce a ragged array. + # However, in our case, the sum of repeats for each slice is the same, so we can + # do a vectorized repeat by raveling, repeating, then restoring the shape. + cdf1 = xp.repeat(xp_ravel(cdf1_vals), xp_ravel(cdf1_counts), axis=-1) + cdf2 = xp.repeat(xp_ravel(cdf2_vals), xp_ravel(cdf2_counts), axis=-1) + cddiffs = xp.reshape(cdf1 - cdf2, ranks.shape[:-1] + (-1,)) + + # Identify the location of the statistic + argminS = xp.argmin(cddiffs, axis=-1, keepdims=True) + argmaxS = xp.argmax(cddiffs, axis=-1, keepdims=True) + loc_minS = xp.squeeze(xp.take_along_axis(data_all, argminS, axis=-1), axis=-1) + loc_maxS = xp.squeeze(xp.take_along_axis(data_all, argmaxS, axis=-1), axis=-1) + + # Ensure sign of minS is not negative. + minS = -xp.squeeze(xp.take_along_axis(cddiffs, argminS, axis=-1), axis=-1) + maxS = xp.squeeze(xp.take_along_axis(cddiffs, argmaxS, axis=-1), axis=-1) + minS = xp.clip(minS, 0., 1.) + + if alternative == 'less': + selector = xp.ones(minS.shape, dtype=xp.bool) + elif alternative == 'two-sided': + selector = minS > maxS + else: + selector = xp.zeros(minS.shape, dtype=xp.bool) + + d = xp.where(selector, minS, maxS) + d_location = xp.where(selector, loc_minS, loc_maxS) + one = xp.asarray(1, dtype=xp.int8) + d_sign = xp.where(selector, -one, one) + + if is_marray(xp): + d = d.data # converted to NumPy below + n1, n2 = np.asarray(n1.data, dtype=int), np.asarray(n2.data, dtype=int) + prob = _ks_2samp_prob(np.asarray(d), n1, n2, mode, MAX_AUTO_N, alternative) + dtype = xp_result_type(data1, data2, force_floating=True, xp=xp) + prob = xp.asarray(prob, dtype=dtype) + d = xp.asarray(d, dtype=dtype) + if d.ndim == 0: + d, prob, d_location, d_sign = d[()], prob[()], d_location[()], d_sign[()] + return KstestResult(d, prob, statistic_location=d_location, statistic_sign=d_sign) + + +@np.vectorize(excluded={3, 4, 5}, otypes=[np.float64]) +def _ks_2samp_prob(d, n1, n2, mode, MAX_AUTO_N, alternative): + # math.gcd used to return an `int`, but now that these return arrays, NumPy can + # return an int32. Unless we want the pythranized functions to be compiled for + # int32, this needs to produce an int64. + g = np.gcd(n1, n2).astype(np.int64) + n1g = n1 // g + n2g = n2 // g + prob = -np.inf + if mode == 'auto': + mode = 'exact' if max(n1, n2) <= MAX_AUTO_N else 'asymp' + elif mode == 'exact': + # If lcm(n1, n2) is too big, switch from exact to asymp + if n1g >= np.iinfo(np.int32).max / n2g: + mode = 'asymp' + warnings.warn( + f"Exact ks_2samp calculation not possible with samples sizes " + f"{n1} and {n2}. Switching to 'asymp'.", RuntimeWarning, + stacklevel=3) + + if mode == 'exact': + success, d, prob = _attempt_exact_2kssamp(n1, n2, g, d, alternative) + if not success: + mode = 'asymp' + warnings.warn(f"ks_2samp: Exact calculation unsuccessful. " + f"Switching to method={mode}.", RuntimeWarning, + stacklevel=3) + + if mode == 'asymp': + # The product n1*n2 is large. Use Smirnov's asymptotic formula. + # Ensure float to avoid overflow in multiplication + # sorted because the one-sided formula is not symmetric in n1, n2 + m, n = sorted([float(n1), float(n2)], reverse=True) + en = m * n / (m + n) + if alternative == 'two-sided': + prob = distributions.kstwo.sf(d, np.round(en)) + else: + z = np.sqrt(en) * d + # Use Hodges' suggested approximation Eqn 5.3 + # Requires m to be the larger of (n1, n2) + expt = -2 * z**2 - 2 * z * (m + 2*n)/np.sqrt(m*n*(m+n))/3.0 + prob = np.exp(expt) + + prob = np.clip(prob, 0, 1) + return prob + + +def _parse_kstest_args(data1, data2, args, N): + # kstest allows many different variations of arguments. + # Pull out the parsing into a separate function + # (xvals, yvals, ) # 2sample + # (xvals, cdf function,..) + # (xvals, name of distribution, ...) + # (name of distribution, name of distribution, ...) + + # Returns xvals, yvals, cdf + # where cdf is a cdf function, or None + # and yvals is either an array_like of values, or None + # and xvals is array_like. + rvsfunc, cdf = None, None + if isinstance(data1, str): + rvsfunc = getattr(distributions, data1).rvs + elif callable(data1): + rvsfunc = data1 + + if isinstance(data2, str): + special_distributions = {'norm': special.ndtr} + cdf = special_distributions.get(data2, getattr(distributions, data2).cdf) + data2 = None + elif callable(data2): + cdf = data2 + data2 = None + + xp = array_namespace(data1, data2, *args) + data1 = xp.sort(rvsfunc(*args, size=N) if rvsfunc else data1) + return data1, data2, cdf + + +def _kstest_n_samples(kwargs): + cdf = kwargs['cdf'] + return 1 if (isinstance(cdf, str) or callable(cdf)) else 2 + + +@xp_capabilities(skip_backends=[('dask.array', 'no rankdata')], + jax_jit=False, cpu_only=True, # see ks_1samp/ks_2samp + marray=True) +@_axis_nan_policy_factory(_tuple_to_KstestResult, n_samples=_kstest_n_samples, + n_outputs=4, result_to_tuple=_KstestResult_to_tuple) +@_rename_parameter("mode", "method") +def kstest(rvs, cdf, args=(), N=20, alternative='two-sided', method='auto', *, + axis=0): + """ + Performs the (one-sample or two-sample) Kolmogorov-Smirnov test for + goodness of fit. + + The one-sample test compares the underlying distribution F(x) of a sample + against a given distribution G(x). The two-sample test compares the + underlying distributions of two independent samples. Both tests are valid + only for continuous distributions. + + Parameters + ---------- + rvs : str, array_like, or callable + If an array, it should be a 1-D array of observations of random + variables. + If a callable, it should be a function to generate random variables; + it is required to have a keyword argument `size`. + If a string, it should be the name of a distribution in `scipy.stats`, + which will be used to generate random variables. + cdf : str, array_like or callable + If array_like, it should be a 1-D array of observations of random + variables, and the two-sample test is performed + (and rvs must be array_like). + If a callable, that callable is used to calculate the cdf. + If a string, it should be the name of a distribution in `scipy.stats`, + which will be used as the cdf function. + args : tuple, sequence, optional + Distribution parameters, used if `rvs` or `cdf` are strings or + callables. + N : int, optional + Sample size if `rvs` is string or callable. Default is 20. + alternative : {'two-sided', 'less', 'greater'}, optional + Defines the null and alternative hypotheses. Default is 'two-sided'. + Please see explanations in the Notes below. + method : {'auto', 'exact', 'approx', 'asymp'}, optional + Defines the distribution used for calculating the p-value. + The following options are available (default is 'auto'): + + * 'auto' : selects one of the other options. + * 'exact' : uses the exact distribution of test statistic. + * 'approx' : approximates the two-sided probability with twice the + one-sided probability + * 'asymp': uses asymptotic distribution of test statistic + + Returns + ------- + res: KstestResult + An object containing attributes: + + statistic : float + KS test statistic, either D+, D-, or D (the maximum of the two) + pvalue : float + One-tailed or two-tailed p-value. + statistic_location : float + In a one-sample test, this is the value of `rvs` + corresponding with the KS statistic; i.e., the distance between + the empirical distribution function and the hypothesized cumulative + distribution function is measured at this observation. + + In a two-sample test, this is the value from `rvs` or `cdf` + corresponding with the KS statistic; i.e., the distance between + the empirical distribution functions is measured at this + observation. + statistic_sign : int + In a one-sample test, this is +1 if the KS statistic is the + maximum positive difference between the empirical distribution + function and the hypothesized cumulative distribution function + (D+); it is -1 if the KS statistic is the maximum negative + difference (D-). + + In a two-sample test, this is +1 if the empirical distribution + function of `rvs` exceeds the empirical distribution + function of `cdf` at `statistic_location`, otherwise -1. + + See Also + -------- + ks_1samp, ks_2samp + + Notes + ----- + There are three options for the null and corresponding alternative + hypothesis that can be selected using the `alternative` parameter. + + - `two-sided`: The null hypothesis is that the two distributions are + identical, F(x)=G(x) for all x; the alternative is that they are not + identical. + + - `less`: The null hypothesis is that F(x) >= G(x) for all x; the + alternative is that F(x) < G(x) for at least one x. + + - `greater`: The null hypothesis is that F(x) <= G(x) for all x; the + alternative is that F(x) > G(x) for at least one x. + + Note that the alternative hypotheses describe the *CDFs* of the + underlying distributions, not the observed values. For example, + suppose x1 ~ F and x2 ~ G. If F(x) > G(x) for all x, the values in + x1 tend to be less than those in x2. + + + Examples + -------- + Suppose we wish to test the null hypothesis that a sample is distributed + according to the standard normal. + We choose a confidence level of 95%; that is, we will reject the null + hypothesis in favor of the alternative if the p-value is less than 0.05. + + When testing uniformly distributed data, we would expect the + null hypothesis to be rejected. + + >>> import numpy as np + >>> from scipy import stats + >>> rng = np.random.default_rng() + >>> stats.kstest(stats.uniform.rvs(size=100, random_state=rng), + ... stats.norm.cdf) + KstestResult(statistic=0.5001899973268688, + pvalue=1.1616392184763533e-23, + statistic_location=0.00047625268963724654, + statistic_sign=-1) + + Indeed, the p-value is lower than our threshold of 0.05, so we reject the + null hypothesis in favor of the default "two-sided" alternative: the data + are *not* distributed according to the standard normal. + + When testing random variates from the standard normal distribution, we + expect the data to be consistent with the null hypothesis most of the time. + + >>> x = stats.norm.rvs(size=100, random_state=rng) + >>> stats.kstest(x, stats.norm.cdf) + KstestResult(statistic=0.05345882212970396, + pvalue=0.9227159037744717, + statistic_location=-1.2451343873745018, + statistic_sign=1) + + + As expected, the p-value of 0.92 is not below our threshold of 0.05, so + we cannot reject the null hypothesis. + + Suppose, however, that the random variates are distributed according to + a normal distribution that is shifted toward greater values. In this case, + the cumulative density function (CDF) of the underlying distribution tends + to be *less* than the CDF of the standard normal. Therefore, we would + expect the null hypothesis to be rejected with ``alternative='less'``: + + >>> x = stats.norm.rvs(size=100, loc=0.5, random_state=rng) + >>> stats.kstest(x, stats.norm.cdf, alternative='less') + KstestResult(statistic=0.17482387821055168, + pvalue=0.001913921057766743, + statistic_location=0.3713830565352756, + statistic_sign=-1) + + and indeed, with p-value smaller than our threshold, we reject the null + hypothesis in favor of the alternative. + + For convenience, the previous test can be performed using the name of the + distribution as the second argument. + + >>> stats.kstest(x, "norm", alternative='less') + KstestResult(statistic=0.17482387821055168, + pvalue=0.001913921057766743, + statistic_location=0.3713830565352756, + statistic_sign=-1) + + The examples above have all been one-sample tests identical to those + performed by `ks_1samp`. Note that `kstest` can also perform two-sample + tests identical to those performed by `ks_2samp`. For example, when two + samples are drawn from the same distribution, we expect the data to be + consistent with the null hypothesis most of the time. + + >>> sample1 = stats.laplace.rvs(size=105, random_state=rng) + >>> sample2 = stats.laplace.rvs(size=95, random_state=rng) + >>> stats.kstest(sample1, sample2) + KstestResult(statistic=0.11779448621553884, + pvalue=0.4494256912629795, + statistic_location=0.6138814275424155, + statistic_sign=1) + + As expected, the p-value of 0.45 is not below our threshold of 0.05, so + we cannot reject the null hypothesis. + + """ + # to not break compatibility with existing code + if alternative == 'two_sided': + alternative = 'two-sided' + if alternative not in ['two-sided', 'greater', 'less']: + raise ValueError(f"Unexpected alternative: {alternative}") + xvals, yvals, cdf = _parse_kstest_args(rvs, cdf, args, N) + if cdf: + return ks_1samp(xvals, cdf, args=args, alternative=alternative, + method=method, axis=axis, _no_deco=True) + return ks_2samp(xvals, yvals, alternative=alternative, method=method, + axis=axis, _no_deco=True) + + +@xp_capabilities(np_only=True) +def tiecorrect(rankvals): + """Tie correction factor for Mann-Whitney U and Kruskal-Wallis H tests. + + Parameters + ---------- + rankvals : array_like + A 1-D sequence of ranks. Typically this will be the array + returned by `~scipy.stats.rankdata`. + + Returns + ------- + factor : float + Correction factor for U or H. + + See Also + -------- + rankdata : Assign ranks to the data + mannwhitneyu : Mann-Whitney rank test + kruskal : Kruskal-Wallis H test + + References + ---------- + .. [1] Siegel, S. (1956) Nonparametric Statistics for the Behavioral + Sciences. New York: McGraw-Hill. + + Examples + -------- + >>> from scipy.stats import tiecorrect, rankdata + >>> tiecorrect([1, 2.5, 2.5, 4]) + 0.9 + >>> ranks = rankdata([1, 3, 2, 4, 5, 7, 2, 8, 4]) + >>> ranks + array([ 1. , 4. , 2.5, 5.5, 7. , 8. , 2.5, 9. , 5.5]) + >>> tiecorrect(ranks) + 0.9833333333333333 + + """ + arr = np.sort(rankvals) + idx = np.nonzero(np.r_[True, arr[1:] != arr[:-1], True])[0] + cnt = np.diff(idx).astype(np.float64) + + size = np.float64(arr.size) + return 1.0 if size < 2 else 1.0 - (cnt**3 - cnt).sum() / (size**3 - size) + + +RanksumsResult = namedtuple('RanksumsResult', ('statistic', 'pvalue')) + + +@xp_capabilities(out_of_scope=True) # legacy +@_axis_nan_policy_factory(RanksumsResult, n_samples=2) +def ranksums(x, y, alternative='two-sided'): + """Compute the Wilcoxon rank-sum statistic for two samples. + + .. legacy:: function + + This function includes a subset of the features of `mannwhitneyu`. + Its statistic and p-value can be reproduced using `mannwhitneyu` with + options ``use_continuity=False`` and ``method='asymptotic'``. + Prefer `mannwhitneyu` for use in new code. + + The Wilcoxon rank-sum test tests the null hypothesis that two sets + of measurements are drawn from the same distribution. The alternative + hypothesis is that values in one sample are more likely to be + larger than the values in the other sample. + + This test should be used to compare two samples from continuous + distributions. It does not handle ties between measurements + in x and y. For tie-handling and an optional continuity correction + see `scipy.stats.mannwhitneyu`. + + Parameters + ---------- + x, y : array_like + The data from the two samples. + alternative : {'two-sided', 'less', 'greater'}, optional + Defines the alternative hypothesis. Default is 'two-sided'. + The following options are available: + + * 'two-sided': one of the distributions (underlying `x` or `y`) is + stochastically greater than the other. + * 'less': the distribution underlying `x` is stochastically less + than the distribution underlying `y`. + * 'greater': the distribution underlying `x` is stochastically greater + than the distribution underlying `y`. + + .. versionadded:: 1.7.0 + + Returns + ------- + statistic : float + The test statistic under the large-sample approximation that the + rank sum statistic is normally distributed. + pvalue : float + The p-value of the test. + + See Also + -------- + scipy.stats.mannwhitneyu + + References + ---------- + .. [1] https://en.wikipedia.org/wiki/Wilcoxon_rank-sum_test + + Examples + -------- + We can test the hypothesis that two independent unequal-sized samples are + drawn from the same distribution with computing the Wilcoxon rank-sum + statistic. + + >>> import numpy as np + >>> from scipy import stats + >>> rng = np.random.default_rng(6004253334) + >>> sample1 = rng.uniform(-1, 1, 200) + >>> sample2 = rng.uniform(-0.5, 1.5, 300) # a shifted distribution + >>> stats.ranksums(sample1, sample2) + RanksumsResult(statistic=np.float64(-7.576201867), + pvalue=np.float64(3.5581802537469756e-14)) + >>> stats.ranksums(sample1, sample2, alternative='less') + RanksumsResult(statistic=np.float64(-7.576201867066302), + pvalue=np.float64(1.7790901268734878e-14)) + >>> stats.ranksums(sample1, sample2, alternative='greater') + RanksumsResult(statistic=np.float64(-7.576201867066302), + pvalue=np.float64(0.9999999999999822)) + + The p-value of less than ``0.05`` indicates that this test rejects the + hypothesis at the 5% significance level. + + Note that identical hypothesis tests (and more accurate ones) can be performed + using `mannwhitneyu`. Prefer `mannwhitneyu` for use in new code. + + >>> res = stats.mannwhitneyu(sample1, sample2, alternative='greater', + ... method='asymptotic', use_continuity = False) + >>> res.zstatistic + np.float64(-7.576201867066302) + >>> res.pvalue + np.float64(0.9999999999999822) + """ + x, y = map(np.asarray, (x, y)) + n1 = len(x) + n2 = len(y) + alldata = np.concatenate((x, y)) + ranked = rankdata(alldata) + x = ranked[:n1] + s = np.sum(x, axis=0) + expected = n1 * (n1+n2+1) / 2.0 + z = (s - expected) / np.sqrt(n1*n2*(n1+n2+1)/12.0) + pvalue = _get_pvalue(z, _SimpleNormal(), alternative, xp=np) + + return RanksumsResult(z[()], pvalue[()]) + + +KruskalResult = namedtuple('KruskalResult', ('statistic', 'pvalue')) + + +@xp_capabilities(skip_backends=[('dask.array', 'no rankdata')], + marray=True) +@_axis_nan_policy_factory(KruskalResult, n_samples=None) +def kruskal(*samples, nan_policy='propagate', axis=0): + """Compute the Kruskal-Wallis H-test for independent samples. + + The Kruskal-Wallis H-test tests the null hypothesis that the population + median of all of the groups are equal. It is a non-parametric version of + ANOVA. The test works on 2 or more independent samples, which may have + different sizes. Note that rejecting the null hypothesis does not + indicate which of the groups differs. Post hoc comparisons between + groups are required to determine which groups are different. + + Parameters + ---------- + *samples : array_like + Two or more arrays with the sample measurements can be given as + arguments. Samples must be one-dimensional. + nan_policy : {'propagate', 'raise', 'omit'}, optional + Defines how to handle when input contains nan. + The following options are available (default is 'propagate'): + + * 'propagate': returns nan + * 'raise': throws an error + * 'omit': performs the calculations ignoring nan values + + axis : int or tuple of ints, default: 0 + If an int or tuple of ints, the axis or axes of the input along which + to compute the statistic. The statistic of each axis-slice (e.g. row) + of the input will appear in a corresponding element of the output. + If ``None``, the input will be raveled before computing the statistic. + + Returns + ------- + statistic : float + The Kruskal-Wallis H statistic, corrected for ties. + pvalue : float + The p-value for the test using the assumption that H has a chi + square distribution. The p-value returned is the survival function of + the chi square distribution evaluated at H. + + See Also + -------- + f_oneway : 1-way ANOVA. + mannwhitneyu : Mann-Whitney rank test on two samples. + friedmanchisquare : Friedman test for repeated measurements. + + Notes + ----- + Due to the assumption that H has a chi square distribution, the number + of samples in each group must not be too small. A typical rule is + that each sample must have at least 5 measurements. + + References + ---------- + .. [1] W. H. Kruskal & W. W. Wallis, "Use of Ranks in + One-Criterion Variance Analysis", Journal of the American Statistical + Association, Vol. 47, Issue 260, pp. 583-621, 1952. + .. [2] https://en.wikipedia.org/wiki/Kruskal-Wallis_one-way_analysis_of_variance + + Examples + -------- + >>> from scipy import stats + >>> x = [1, 3, 5, 7, 9] + >>> y = [2, 4, 6, 8, 10] + >>> stats.kruskal(x, y) + KruskalResult(statistic=0.2727272727272734, pvalue=0.6015081344405895) + + >>> x = [1, 1, 1] + >>> y = [2, 2, 2] + >>> z = [2, 2] + >>> stats.kruskal(x, y, z) + KruskalResult(statistic=7.0, pvalue=0.0301973834223185) + + """ + xp = array_namespace(*samples) + samples = xp_promote(*samples, force_floating=True, xp=xp) + + num_groups = len(samples) + if num_groups < 2: + raise ValueError("Need at least two groups in stats.kruskal()") + + lengths = [sample.shape[-1] for sample in samples] + if any(lengths) < 1: # Only needed for `test_axis_nan_policy` + raise ValueError("Inputs must not be empty.") + + alldata = xp.concat(samples, axis=-1) + ranked, _, t = _rankdata(alldata, method='average', return_ties=True) + counts = [xp.asarray(_count_nonmasked(sample, -1), dtype=t.dtype) + for sample in samples] + totaln = sum(counts) + ties = 1 - xp.sum(t**3 - t, axis=-1) / (totaln**3 - totaln) # tiecorrect(ranked) + + # Compute sum^2/count for each group and sum + j = list(itertools.accumulate(lengths, initial=0)) + ssbn = sum(xp.sum(ranked[..., j[i]:j[i + 1]], axis=-1)**2 / counts[i] + for i in range(num_groups)) + + h = 12.0 / (totaln * (totaln + 1)) * ssbn - 3 * (totaln + 1) + df = xp.asarray(num_groups - 1, dtype=h.dtype) + h /= ties + + chi2 = _SimpleChi2(df) + pvalue = _get_pvalue(h, chi2, alternative='greater', symmetric=False, xp=np) + return KruskalResult(h, pvalue) + + +FriedmanchisquareResult = namedtuple('FriedmanchisquareResult', + ('statistic', 'pvalue')) + + +@xp_capabilities(skip_backends=[("dask.array", "no rankdata")], + marray=True) +@_axis_nan_policy_factory(FriedmanchisquareResult, n_samples=None, paired=True) +def friedmanchisquare(*samples, axis=0): + """Compute the Friedman test for repeated samples. + + The Friedman test tests the null hypothesis that repeated samples of + the same individuals have the same distribution. It is often used + to test for consistency among samples obtained in different ways. + For example, if two sampling techniques are used on the same set of + individuals, the Friedman test can be used to determine if the two + sampling techniques are consistent. + + Parameters + ---------- + *samples : array_like + Arrays of observations. All of the arrays must have the same number + of elements. At least three samples must be given. + axis : int or tuple of ints, default: 0 + If an int or tuple of ints, the axis or axes of the input along which + to compute the statistic. The statistic of each axis-slice (e.g. row) + of the input will appear in a corresponding element of the output. + If ``None``, the input will be raveled before computing the statistic. + + Returns + ------- + statistic : float + The test statistic, correcting for ties. + pvalue : float + The associated p-value assuming that the test statistic has a chi + squared distribution. + + See Also + -------- + :ref:`hypothesis_friedmanchisquare` : Extended example + + Notes + ----- + Due to the assumption that the test statistic has a chi squared + distribution, the p-value is only reliable for n > 10 and more than + 6 repeated samples. + + References + ---------- + .. [1] https://en.wikipedia.org/wiki/Friedman_test + .. [2] Demsar, J. (2006). Statistical comparisons of classifiers over + multiple data sets. Journal of Machine Learning Research, 7, 1-30. + + Examples + -------- + + >>> import numpy as np + >>> rng = np.random.default_rng(seed=18) + >>> x = rng.random((6, 10)) + >>> from scipy.stats import friedmanchisquare + >>> res = friedmanchisquare(x[0], x[1], x[2], x[3], x[4], x[5]) + >>> res.statistic, res.pvalue + (11.428571428571416, 0.043514520866727614) + + The p-value is less than 0.05; however, as noted above, the results may not + be reliable since we have a small number of repeated samples. + + For a more detailed example, see :ref:`hypothesis_friedmanchisquare`. + """ + k = len(samples) + if k < 3: + raise ValueError('At least 3 samples must be given ' + f'for Friedman test, got {k}.') + + xp = array_namespace(*samples) + samples = xp_promote(*samples, force_floating=True, xp=xp) + dtype = samples[0].dtype + samples = _share_masks(*samples, xp=xp) # paired-sample test + + n = samples[0].shape[-1] + if n == 0: # only for `test_axis_nan_policy`; user doesn't see this + raise ValueError("One or more sample arguments is too small.") + + # Rank data + # axis-slices are aligned with axis -1 by decorator; stack puts samples along axis 0 + # The transpose flips this so we can work with axis-slices along -1. This is a + # reducing statistic, so both axes 0 and -1 are consumed. + data = xp_swapaxes(xp.stack(samples), 0, -1) + data, _, t = _rankdata(data, method='average', return_ties=True) + + # Handle ties + ties = xp.sum(t * (t*t - 1), axis=(0, -1)) + count = xp.asarray(_count_nonmasked(samples[0], axis=-1), dtype=ties.dtype) + c = 1 - ties / (k*(k*k - 1)*count) + + ssbn = xp.sum(xp.sum(data, axis=0)**2, axis=-1) + statistic = (12.0 / (k*count*(k+1)) * ssbn - 3*count*(k+1)) / c + + chi2 = _SimpleChi2(xp.asarray(k - 1, dtype=dtype)) + pvalue = _get_pvalue(statistic, chi2, alternative='greater', symmetric=False, xp=xp) + return FriedmanchisquareResult(statistic, pvalue) + + +BrunnerMunzelResult = namedtuple('BrunnerMunzelResult', + ('statistic', 'pvalue')) + + +@xp_capabilities(cpu_only=True, # torch GPU can't use `stdtr` + marray=True, + skip_backends=[('dask.array', 'needs rankdata')]) +@_axis_nan_policy_factory(BrunnerMunzelResult, n_samples=2) +def brunnermunzel(x, y, alternative="two-sided", distribution="t", + nan_policy='propagate', *, axis=0): + """Compute the Brunner-Munzel test on samples x and y. + + The Brunner-Munzel test is a nonparametric test of the null hypothesis that + when values are taken one by one from each group, the probabilities of + getting large values in both groups are equal. + Unlike the Wilcoxon-Mann-Whitney's U test, this does not require the + assumption of equivariance of two groups. Note that this does not assume + the distributions are same. This test works on two independent samples, + which may have different sizes. + + Parameters + ---------- + x, y : array_like + Array of samples, should be one-dimensional. + alternative : {'two-sided', 'less', 'greater'}, optional + Defines the alternative hypothesis. + The following options are available (default is 'two-sided'): + + * 'two-sided' + * 'less': one-sided + * 'greater': one-sided + + distribution : {'t', 'normal'}, optional + Defines how to get the p-value. + The following options are available (default is 't'): + + * 't': get the p-value by t-distribution + * 'normal': get the p-value by standard normal distribution. + + nan_policy : {'propagate', 'raise', 'omit'}, optional + Defines how to handle when input contains nan. + The following options are available (default is 'propagate'): + + * 'propagate': returns nan + * 'raise': throws an error + * 'omit': performs the calculations ignoring nan values + + axis : int or None, default=0 + If an int, the axis of the input along which to compute the statistic. + The statistic of each axis-slice (e.g. row) of the input will appear + in a corresponding element of the output. If None, the input will be + raveled before computing the statistic. + + Returns + ------- + statistic : float + The Brunner-Munzer W statistic. + pvalue : float + p-value assuming an t distribution. One-sided or + two-sided, depending on the choice of `alternative` and `distribution`. + + See Also + -------- + mannwhitneyu : Mann-Whitney rank test on two samples. + + Notes + ----- + Brunner and Munzel recommended to estimate the p-value by t-distribution + when the size of data is 50 or less. If the size is lower than 10, it would + be better to use permuted Brunner Munzel test (see [2]_). + + References + ---------- + .. [1] Brunner, E. and Munzel, U. "The nonparametric Benhrens-Fisher + problem: Asymptotic theory and a small-sample approximation". + Biometrical Journal. Vol. 42(2000): 17-25. + .. [2] Neubert, K. and Brunner, E. "A studentized permutation test for the + non-parametric Behrens-Fisher problem". Computational Statistics and + Data Analysis. Vol. 51(2007): 5192-5204. + + Examples + -------- + >>> from scipy import stats + >>> x1 = [1,2,1,1,1,1,1,1,1,1,2,4,1,1] + >>> x2 = [3,3,4,3,1,2,3,1,1,5,4] + >>> w, p_value = stats.brunnermunzel(x1, x2) + >>> w + 3.1374674823029505 + >>> p_value + 0.0057862086661515377 + + """ + # _axis_nan_policy decorator ensures we can work along the last axis + xp = array_namespace(x, y) + length_x = x.shape[-1] + nx = _count_nonmasked(x, axis=-1) + ny = _count_nonmasked(y, axis=-1) + + rankc = rankdata(xp.concat((x, y), axis=axis), axis=-1) + rankcx = rankc[..., :length_x] + rankcy = rankc[..., length_x:] + rankcx_mean = xp.mean(rankcx, axis=-1, keepdims=True) + rankcy_mean = xp.mean(rankcy, axis=-1, keepdims=True) + rankx = rankdata(x, axis=-1) + ranky = rankdata(y, axis=-1) + rankx_mean = xp.mean(rankx, axis=-1, keepdims=True) + ranky_mean = xp.mean(ranky, axis=-1, keepdims=True) + + temp_x = rankcx - rankx - rankcx_mean + rankx_mean + Sx = xp.vecdot(temp_x, temp_x, axis=-1) + Sx /= nx - 1 + temp_y = rankcy - ranky - rankcy_mean + ranky_mean + Sy = xp.vecdot(temp_y, temp_y, axis=-1) + Sy /= ny - 1 + + rankcx_mean = xp.squeeze(rankcx_mean, axis=-1) + rankcy_mean = xp.squeeze(rankcy_mean, axis=-1) + wbfn = nx * ny * (rankcy_mean - rankcx_mean) + wbfn /= (nx + ny) * xp.sqrt(nx * Sx + ny * Sy) + + if distribution == "t": + df_numer = xp.pow(nx * Sx + ny * Sy, 2.0) + df_denom = xp.pow(nx * Sx, 2.0) / (nx - 1) + df_denom += xp.pow(ny * Sy, 2.0) / (ny - 1) + df = df_numer / df_denom + + if not is_lazy_array(df_numer) and not is_lazy_array(df_denom) and ( + xp.any(df_numer == 0) and xp.any(df_denom == 0)): + message = ("p-value cannot be estimated with `distribution='t' " + "because degrees of freedom parameter is undefined " + "(0/0). Try using `distribution='normal'") + warnings.warn(message, RuntimeWarning, stacklevel=2) + + distribution = _SimpleStudentT(df) + elif distribution == "normal": + distribution = _SimpleNormal() + else: + raise ValueError( + "distribution should be 't' or 'normal'") + + p = _get_pvalue(-wbfn, distribution, alternative, xp=xp) + + return BrunnerMunzelResult(wbfn, p) + + +@xp_capabilities(cpu_only=True, exceptions=['cupy', 'jax.numpy'], + reason='Delegation for `special.stdtr` only implemented for CuPy and JAX.', + marray=True) +@_axis_nan_policy_factory(SignificanceResult, kwd_samples=['weights'], paired=True) +def combine_pvalues(pvalues, method='fisher', weights=None, *, axis=0): + """ + Combine p-values from independent tests that bear upon the same hypothesis. + + These methods are intended only for combining p-values from hypothesis + tests based upon continuous distributions. + + Each method assumes that under the null hypothesis, the p-values are + sampled independently and uniformly from the interval [0, 1]. A test + statistic (different for each method) is computed and a combined + p-value is calculated based upon the distribution of this test statistic + under the null hypothesis. + + Parameters + ---------- + pvalues : array_like + Array of p-values assumed to come from independent tests based on + continuous distributions. + method : {'fisher', 'pearson', 'tippett', 'stouffer', 'mudholkar_george'} + + Name of method to use to combine p-values. + + The available methods are (see Notes for details): + + * 'fisher': Fisher's method (Fisher's combined probability test) + * 'pearson': Pearson's method + * 'mudholkar_george': Mudholkar's and George's method + * 'tippett': Tippett's method + * 'stouffer': Stouffer's Z-score method + + weights : array_like, optional + Optional array of weights used only for Stouffer's Z-score method. + Ignored by other methods. + + Returns + ------- + res : SignificanceResult + An object containing attributes: + + statistic : float + The statistic calculated by the specified method. + pvalue : float + The combined p-value. + + Examples + -------- + Suppose we wish to combine p-values from four independent tests + of the same null hypothesis using Fisher's method (default). + + >>> from scipy.stats import combine_pvalues + >>> pvalues = [0.1, 0.05, 0.02, 0.3] + >>> combine_pvalues(pvalues) + SignificanceResult(statistic=20.828626352604235, pvalue=0.007616871850449092) + + When the individual p-values carry different weights, consider Stouffer's + method. + + >>> weights = [1, 2, 3, 4] + >>> res = combine_pvalues(pvalues, method='stouffer', weights=weights) + >>> res.pvalue + 0.009578891494533616 + + Notes + ----- + If this function is applied to tests with a discrete statistics such as + any rank test or contingency-table test, it will yield systematically + wrong results, e.g. Fisher's method will systematically overestimate the + p-value [1]_. This problem becomes less severe for large sample sizes + when the discrete distributions become approximately continuous. + + The differences between the methods can be best illustrated by their + statistics and what aspects of a combination of p-values they emphasise + when considering significance [2]_. For example, methods emphasising large + p-values are more sensitive to strong false and true negatives; conversely + methods focussing on small p-values are sensitive to positives. + + * The statistics of Fisher's method (also known as Fisher's combined + probability test) [3]_ is :math:`-2\\sum_i \\log(p_i)`, which is + equivalent (as a test statistics) to the product of individual p-values: + :math:`\\prod_i p_i`. Under the null hypothesis, this statistics follows + a :math:`\\chi^2` distribution. This method emphasises small p-values. + * Pearson's method uses :math:`-2\\sum_i\\log(1-p_i)`, which is equivalent + to :math:`\\prod_i \\frac{1}{1-p_i}` [2]_. + It thus emphasises large p-values. + * Mudholkar and George compromise between Fisher's and Pearson's method by + averaging their statistics [4]_. Their method emphasises extreme + p-values, both close to 1 and 0. + * Stouffer's method [5]_ uses Z-scores and the statistic: + :math:`\\sum_i \\Phi^{-1} (p_i)`, where :math:`\\Phi` is the CDF of the + standard normal distribution. The advantage of this method is that it is + straightforward to introduce weights, which can make Stouffer's method + more powerful than Fisher's method when the p-values are from studies + of different size [6]_ [7]_. + * Tippett's method uses the smallest p-value as a statistic. + (Mind that this minimum is not the combined p-value.) + + Fisher's method may be extended to combine p-values from dependent tests + [8]_. Extensions such as Brown's method and Kost's method are not currently + implemented. + + .. versionadded:: 0.15.0 + + References + ---------- + .. [1] Kincaid, W. M., "The Combination of Tests Based on Discrete + Distributions." Journal of the American Statistical Association 57, + no. 297 (1962), 10-19. + .. [2] Heard, N. and Rubin-Delanchey, P. "Choosing between methods of + combining p-values." Biometrika 105.1 (2018): 239-246. + .. [3] https://en.wikipedia.org/wiki/Fisher%27s_method + .. [4] George, E. O., and G. S. Mudholkar. "On the convolution of logistic + random variables." Metrika 30.1 (1983): 1-13. + .. [5] https://en.wikipedia.org/wiki/Fisher%27s_method#Relation_to_Stouffer.27s_Z-score_method + .. [6] Whitlock, M. C. "Combining probability from independent tests: the + weighted Z-method is superior to Fisher's approach." Journal of + Evolutionary Biology 18, no. 5 (2005): 1368-1373. + .. [7] Zaykin, Dmitri V. "Optimally weighted Z-test is a powerful method + for combining probabilities in meta-analysis." Journal of + Evolutionary Biology 24, no. 8 (2011): 1836-1841. + .. [8] https://en.wikipedia.org/wiki/Extensions_of_Fisher%27s_method + + """ + xp = array_namespace(pvalues, weights) + pvalues, weights = xp_promote(pvalues, weights, broadcast=True, + force_floating=True, xp=xp) + + if xp_size(pvalues) == 0: + # This is really only needed for *testing* _axis_nan_policy decorator + # It won't happen when the decorator is used. + NaN = _get_nan(pvalues) + return SignificanceResult(NaN, NaN) + + n = _count_nonmasked(pvalues, axis) + n = xp.asarray(n, dtype=pvalues.dtype, device=xp_device(pvalues)) + + if method == 'fisher': + statistic = -2 * xp.sum(xp.log(pvalues), axis=axis) + chi2 = _SimpleChi2(2*n) + pval = _get_pvalue(statistic, chi2, alternative='greater', + symmetric=False, xp=xp) + elif method == 'pearson': + statistic = 2 * xp.sum(xp.log1p(-pvalues), axis=axis) + chi2 = _SimpleChi2(2*n) + pval = _get_pvalue(-statistic, chi2, alternative='less', symmetric=False, xp=xp) + elif method == 'mudholkar_george': + normalizing_factor = xp.sqrt(3/n)/xp.pi + statistic = (-xp.sum(xp.log(pvalues), axis=axis) + + xp.sum(xp.log1p(-pvalues), axis=axis)) + nu = 5*n + 4 + approx_factor = xp.sqrt(nu / (nu - 2)) + t = _SimpleStudentT(nu) + pval = _get_pvalue(statistic * normalizing_factor * approx_factor, t, + alternative="greater", xp=xp) + elif method == 'tippett': + statistic = xp.min(pvalues, axis=axis) + beta = _SimpleBeta(xp.ones_like(n), n) + pval = _get_pvalue(statistic, beta, alternative='less', symmetric=False, xp=xp) + elif method == 'stouffer': + if weights is None: + weights = xp.ones_like(pvalues, dtype=pvalues.dtype) + pvalues, weights = _share_masks(pvalues, weights, xp=xp) + + norm = _SimpleNormal() + Zi = norm.isf(pvalues) + # Consider `vecdot` when data-apis/array-api#910 is resolved + statistic = (xp.sum(weights * Zi, axis=axis) + / xp_vector_norm(weights, axis=axis)) + pval = _get_pvalue(statistic, norm, alternative="greater", xp=xp) + + else: + raise ValueError( + f"Invalid method {method!r}. Valid methods are 'fisher', " + "'pearson', 'mudholkar_george', 'tippett', and 'stouffer'" + ) + + return SignificanceResult(statistic, pval) + + +@dataclass +class QuantileTestResult: + r""" + Result of `scipy.stats.quantile_test`. + + Attributes + ---------- + statistic: float + The statistic used to calculate the p-value; either ``T1``, the + number of observations less than or equal to the hypothesized quantile, + or ``T2``, the number of observations strictly less than the + hypothesized quantile. Two test statistics are required to handle the + possibility the data was generated from a discrete or mixed + distribution. + + statistic_type : int + ``1`` or ``2`` depending on which of ``T1`` or ``T2`` was used to + calculate the p-value respectively. ``T1`` corresponds to the + ``"greater"`` alternative hypothesis and ``T2`` to the ``"less"``. For + the ``"two-sided"`` case, the statistic type that leads to smallest + p-value is used. For significant tests, ``statistic_type = 1`` means + there is evidence that the population quantile is significantly greater + than the hypothesized value and ``statistic_type = 2`` means there is + evidence that it is significantly less than the hypothesized value. + + pvalue : float + The p-value of the hypothesis test. + """ + statistic: float + statistic_type: int + pvalue: float + _alternative: list[str] = field(repr=False) + _x : np.ndarray = field(repr=False) + _p : float = field(repr=False) + _statistic : float = field(repr=False) + _statistic_type : int = field(repr=False) + _pvalue : float = field(repr=False) + _axis : int = field(repr=False) + _axis_none : bool = field(repr=False) + _keepdims : bool = field(repr=False) + _ndim : int = field(repr=False) + _nan_out : bool = field(repr=False) + _xp : bool = field(repr=False) + + + def confidence_interval(self, confidence_level=0.95): + """ + Compute the confidence interval of the quantile. + + Parameters + ---------- + confidence_level : float, default: 0.95 + Confidence level for the computed confidence interval + of the quantile. Default is 0.95. + + Returns + ------- + ci : ``ConfidenceInterval`` object + The object has attributes ``low`` and ``high`` that hold the + lower and upper bounds of the confidence interval. + + Examples + -------- + >>> import numpy as np + >>> import scipy.stats as stats + >>> p = 0.75 # quantile of interest + >>> q = 0 # hypothesized value of the quantile + >>> x = np.exp(np.arange(0, 1.01, 0.01)) + >>> res = stats.quantile_test(x, q=q, p=p, alternative='less') + >>> lb, ub = res.confidence_interval() + >>> lb, ub + (-inf, 2.293318740264183) + >>> res = stats.quantile_test(x, q=q, p=p, alternative='two-sided') + >>> lb, ub = res.confidence_interval(0.9) + >>> lb, ub + (1.9542373206359396, 2.293318740264183) + """ + + xp = self._xp + alternative = self._alternative + p = self._p + x = xp.sort(self._x, axis=-1) + n = x.shape[-1] + bd = _SimpleBinomial(n=n, p=p) + shape = p.shape + + if confidence_level <= 0 or confidence_level >= 1: + message = "`confidence_level` must be a number between 0 and 1." + raise ValueError(message) + + if n == 0: + zeros = xp.zeros(p.shape, dtype=x.dtype) + low, high = zeros, zeros + elif alternative == 'less': + p = 1 - confidence_level + low = xp.full(shape, -xp.inf) + high_index = xp.astype(bd.isf(p), xp.int64) + valid_index = high_index < n + high_index = xpx.at(high_index)[~valid_index].set(0) + x_high = xp.take_along_axis(x, high_index, axis=-1) + high = xp.where(valid_index, x_high, xp.nan) + elif alternative == 'greater': + p = 1 - confidence_level + low_index = xp.astype(bd.ppf(p), xp.int64) - 1 + valid_index = low_index >= 0 + low_index = xpx.at(low_index)[~valid_index].set(0) + x_low = xp.take_along_axis(x, low_index, axis=-1) + low = xp.where(valid_index, x_low, xp.nan) + high = xp.full(shape, xp.inf) + elif alternative == 'two-sided': + p = (1 - confidence_level) / 2 + low_index = xp.astype(bd.ppf(p), xp.int64) - 1 + valid_index = low_index >= 0 + low_index = xpx.at(low_index)[~valid_index].set(0) + x_low = xp.take_along_axis(x, low_index, axis=-1) + low = xp.where(valid_index, x_low, xp.nan) + high_index = xp.astype(bd.isf(p), xp.int64) + valid_index = high_index < n + high_index = xpx.at(high_index)[~valid_index].set(0) + x_high = xp.take_along_axis(x, high_index, axis=-1) + high = xp.where(valid_index, x_high, xp.nan) + + args = (self._axis, self._axis_none, self._keepdims, + self._ndim, self._nan_out, self._xp) + low = _quantile_test_postprocess(low, *args) + high = _quantile_test_postprocess(high, *args) + return ConfidenceInterval(low, high) + + +def quantile_test_iv(x, q, p, alternative, axis, keepdims): + xp = array_namespace(x, q, p) + dtype = xp_result_type(x, q, p, force_floating=True, xp=xp) + + x = xpx.atleast_nd(x, ndim=1, xp=xp) + message = '`x` must be an array of numbers.' + if not xp.isdtype(x.dtype, 'numeric'): + raise ValueError(message) + + q = xp.asarray(q) + message = "`q` must be a scalar or array of numbers." + if not xp.isdtype(q.dtype, 'numeric'): + raise ValueError(message) + + p = xp.asarray(p) + message = "`p` must be a scalar or array of floats." + if not xp.isdtype(p.dtype, 'real floating'): + raise ValueError(message) + + axis_none = axis is None + ndim = max(x.ndim, p.ndim) + if axis_none: + x = xp_ravel(x) + q = xp_ravel(q) + p = xp_ravel(p) + axis = 0 + elif np.iterable(axis) or int(axis) != axis: + message = "`axis` must be an integer or None." + raise ValueError(message) + elif (axis >= ndim) or (axis < -ndim): + message = "`axis` is not compatible with the shapes of the inputs." + raise ValueError(message) + axis = int(axis) + + alternatives = {'two-sided', 'less', 'greater'} + message = f"`alternative` must be one of {alternatives}" + if alternative not in alternatives: + raise ValueError(message) + + if keepdims not in {None, True, False}: + message = "If specified, `keepdims` must be True or False." + raise ValueError(message) + + x = xp.sort(x, axis=axis) + q, p = xp.broadcast_arrays(q, p) # length along axis matters for q and p + x, q, p = _broadcast_arrays((x, q, p), axis=axis, xp=xp) + + if (keepdims is False) and (p.shape[axis] != 1): + message = ("`keepdims` may be False only if `p` and `q` are scalars or the " + "length of `p` and `q` along `axis` is 1.") + raise ValueError(message) + keepdims = (p.shape[axis] != 1) if keepdims is None else keepdims + + x = xp.moveaxis(x, axis, -1) + q = xp.moveaxis(q, axis, -1) + p = xp.moveaxis(p, axis, -1) + + nan_out = ((p >= 1) | (p <= 0) | xp.isnan(p) | xp.isnan(q) + | xp.any(xp.isnan(x), axis=-1, keepdims=True)) + if is_lazy_array(nan_out) or xp.any(nan_out): + # These get NaN-ed out at the end. In the meantime, we want them + # to pass through calculations without warnings or errors + q = xpx.at(q, nan_out).set(0.0, copy=True) + p = xpx.at(p, nan_out).set(0.5, copy=True) + + return x, q, p, alternative, axis, keepdims, axis_none, ndim, nan_out, dtype, xp + + +def _quantile_test_postprocess(res, axis, axis_none, keepdims, ndim, nan_out, xp): + # Reshape per axis/keepdims + + res = xpx.at(res)[nan_out].set(xp.nan) + + if axis_none and keepdims: + shape = (1,)*(ndim - 1) + res.shape + res = xp.reshape(res, shape) + axis = -1 + + res = xp.moveaxis(res, -1, axis) + + if not keepdims: + res = xp.squeeze(res, axis=axis) + return res[()] if res.ndim == 0 else res + + +@xp_capabilities(skip_backends=[("dask.array", "no `take_along_axis`")], cpu_only=True, + reason="binomial distribution ufuncs only available for NumPy") +def quantile_test(x, *, q=0.0, p=0.5, alternative='two-sided', axis=0, keepdims=None): + r""" + Perform a quantile test and compute a confidence interval of the quantile. + + This function tests the null hypothesis that `q` is the value of the + quantile associated with probability `p` of the population underlying + sample `x`. For example, with default parameters, it tests that the + median of the population underlying `x` is zero. The function returns an + object including the test statistic, a p-value, and a method for computing + the confidence interval around the quantile. + + Parameters + ---------- + x : array_like + A one-dimensional sample. + q : float, default: 0 + The hypothesized value of the quantile. + p : float, default: 0.5 + The probability associated with the quantile; i.e. the proportion of + the population less than `q` is `p`. Must be strictly between 0 and 1. + alternative : {'two-sided', 'less', 'greater'}, optional + Defines the alternative hypothesis. + The following options are available (default is 'two-sided'): + + * 'two-sided': the quantile associated with the probability `p` + is not `q`. + * 'less': the quantile associated with the probability `p` is less + than `q`. + * 'greater': the quantile associated with the probability `p` is + greater than `q`. + axis : int or None, default: 0 + Axis of `x` along which the test is performed. + ``None`` ravels `x`, `q`, and `p` before performing the calculation, + without checking whether the original shapes were compatible. + As in other `scipy.stats` functions, a positive integer `axis` is resolved + after prepending 1s to the shape of `x`, `q`, and `p` as needed until the arrays + have the same dimensionality. When providing `x`, `q`, and `p` with different + dimensionality, consider using negative `axis` integers for clarity. + keepdims : bool, optional + Consider the case in which `x` is 1-D and `p` and `q` are scalars: the test + computes a reducing statistic, and the default behavior is to return a scalar. + If `keepdims` is set to True, the axis will not be reduced away, and the + result will be a 1-D array with one element. + + The general case is more subtle, since multiple tests may be + requested for each axis-slice of `x`. For instance, if `x`, `q`, and `p` + are 1-D and ``p.size == q.size > 1``, no axis can be reduced away; there must be + an axis to contain the results. Therefore: + + - By default, the axis will be reduced away if possible (i.e. if there is + exactly one element of `p` and `q` per axis-slice of `x`). + - If `keepdims` is set to True, the axis will not be reduced away. + - If `keepdims` is set to False, the axis will be reduced away + if possible, and an error will be raised otherwise. + + Returns + ------- + result : QuantileTestResult + An object with the following attributes: + + statistic : float + One of two test statistics that may be used in the quantile test. + The first test statistic, ``T1``, is the number of observations in + `x` that are less than or equal to the hypothesized quantile + `q`. The second test statistic, ``T2``, is the number of + observations in `x` that are strictly less than the hypothesized + quantile `q`. + + When ``alternative = 'greater'``, ``T1`` is used to calculate the + p-value and ``statistic`` is set to ``T1``. + + When ``alternative = 'less'``, ``T2`` is used to calculate the + p-value and ``statistic`` is set to ``T2``. + + When ``alternative = 'two-sided'``, both ``T1`` and ``T2`` are + considered, and the one that leads to the smallest p-value is used. + + statistic_type : float + Either ``1.`` or ``2.`` depending on which of ``T1`` or ``T2`` was + used to calculate the p-value. + + pvalue : float + The p-value associated with the given alternative. + + The object also has the following method: + + confidence_interval(confidence_level=0.95) + Computes a confidence interval around the the + population quantile associated with the probability `p`. The + confidence interval is returned in a ``namedtuple`` with + fields `low` and `high`. Values are `nan` when there are + not enough observations to compute the confidence interval at + the desired confidence. + + Notes + ----- + This test and its method for computing confidence intervals are + non-parametric. They are valid if and only if the observations are i.i.d. + + The implementation of the test follows Conover [1]_. Two test statistics + are considered. + + ``T1``: The number of observations in `x` less than or equal to `q`. + + ``T1 = (x <= q).sum()`` + + ``T2``: The number of observations in `x` strictly less than `q`. + + ``T2 = (x < q).sum()`` + + The use of two test statistics is necessary to handle the possibility that + `x` was generated from a discrete or mixed distribution. + + The null hypothesis for the test is: + + H0: The :math:`p^{\mathrm{th}}` population quantile is `q`. + + and the null distribution for each test statistic is + :math:`\mathrm{binom}\left(n, p\right)`. When ``alternative='less'``, + the alternative hypothesis is: + + H1: The :math:`p^{\mathrm{th}}` population quantile is less than `q`. + + and the p-value is the probability that the binomial random variable + + .. math:: + Y \sim \mathrm{binom}\left(n, p\right) + + is greater than or equal to the observed value ``T2``. + + When ``alternative='greater'``, the alternative hypothesis is: + + H1: The :math:`p^{\mathrm{th}}` population quantile is greater than `q` + + and the p-value is the probability that the binomial random variable Y + is less than or equal to the observed value ``T1``. + + When ``alternative='two-sided'``, the alternative hypothesis is + + H1: `q` is not the :math:`p^{\mathrm{th}}` population quantile. + + and the p-value is twice the smaller of the p-values for the ``'less'`` + and ``'greater'`` cases. Both of these p-values can exceed 0.5 for the same + data, so the value is clipped into the interval :math:`[0, 1]`. + + The approach for confidence intervals is attributed to Thompson [2]_ and + later proven to be applicable to any i.i.d. sample [3]_. The + computation is based on the observation that the probability of a quantile + :math:`q` to be larger than any observations :math:`x_m (1\leq m \leq N)` + can be computed as + + .. math:: + + \mathbb{P}(x_m \leq q) = 1 - \sum_{k=0}^{m-1} \binom{N}{k} + q^k(1-q)^{N-k} + + By default, confidence intervals are computed for a 95% confidence level. + A common interpretation of a 95% confidence intervals is that if i.i.d. + samples are drawn repeatedly from the same population and confidence + intervals are formed each time, the confidence interval will contain the + true value of the specified quantile in approximately 95% of trials. + + A similar function is available in the QuantileNPCI R package [4]_. The + foundation is the same, but it computes the confidence interval bounds by + doing interpolations between the sample values, whereas this function uses + only sample values as bounds. Thus, ``quantile_test.confidence_interval`` + returns more conservative intervals (i.e., larger). + + The same computation of confidence intervals for quantiles is included in + the confintr package [5]_. + + Two-sided confidence intervals are not guaranteed to be optimal; i.e., + there may exist a tighter interval that may contain the quantile of + interest with probability larger than the confidence level. + Without further assumption on the nature of the + underlying distribution, the one-sided intervals are optimally tight. + + References + ---------- + .. [1] W. J. Conover. Practical Nonparametric Statistics, 3rd Ed. 1999. + .. [2] W. R. Thompson, "On Confidence Ranges for the Median and Other + Expectation Distributions for Populations of Unknown Distribution + Form," The Annals of Mathematical Statistics, vol. 7, no. 3, + pp. 122-128, 1936, Accessed: Sep. 18, 2019. [Online]. Available: + https://www.jstor.org/stable/2957563. + .. [3] H. A. David and H. N. Nagaraja, "Order Statistics in Nonparametric + Inference" in Order Statistics, John Wiley & Sons, Ltd, 2005, pp. + 159-170. :doi:`10.1002/0471722162.ch7`. + .. [4] N. Hutson, A. Hutson, L. Yan, "QuantileNPCI: Nonparametric + Confidence Intervals for Quantiles," R package, + https://cran.r-project.org/package=QuantileNPCI + .. [5] M. Mayer, "confintr: Confidence Intervals," R package, + https://cran.r-project.org/package=confintr + + + Examples + -------- + + Suppose we wish to test the null hypothesis that the median of a population + is equal to 0.5. We choose a confidence level of 99%; that is, we will + reject the null hypothesis in favor of the alternative if the p-value is + less than 0.01. + + When testing random variates from the standard uniform distribution, which + has a median of 0.5, we expect the data to be consistent with the null + hypothesis most of the time. + + >>> import numpy as np + >>> from scipy import stats + >>> rng = np.random.default_rng(6981396440634228121) + >>> rvs = stats.uniform.rvs(size=100, random_state=rng) + >>> stats.quantile_test(rvs, q=0.5, p=0.5) + QuantileTestResult(statistic=45, statistic_type=1, pvalue=0.36820161732669576) + + As expected, the p-value is not below our threshold of 0.01, so + we cannot reject the null hypothesis. + + When testing data from the standard *normal* distribution, which has a + median of 0, we would expect the null hypothesis to be rejected. + + >>> rvs = stats.norm.rvs(size=100, random_state=rng) + >>> stats.quantile_test(rvs, q=0.5, p=0.5) + QuantileTestResult(statistic=67, statistic_type=2, pvalue=0.0008737198369123724) + + Indeed, the p-value is lower than our threshold of 0.01, so we reject the + null hypothesis in favor of the default "two-sided" alternative: the median + of the population is *not* equal to 0.5. + + However, suppose we were to test the null hypothesis against the + one-sided alternative that the median of the population is *greater* than + 0.5. Since the median of the standard normal is less than 0.5, we would not + expect the null hypothesis to be rejected. + + >>> stats.quantile_test(rvs, q=0.5, p=0.5, alternative='greater') + QuantileTestResult(statistic=67, statistic_type=1, pvalue=0.9997956114162866) + + Unsurprisingly, with a p-value greater than our threshold, we would not + reject the null hypothesis in favor of the chosen alternative. + + The quantile test can be used for any quantile, not only the median. For + example, we can test whether the third quartile of the distribution + underlying the sample is greater than 0.6. + + >>> rvs = stats.uniform.rvs(size=100, random_state=rng) + >>> stats.quantile_test(rvs, q=0.6, p=0.75, alternative='greater') + QuantileTestResult(statistic=64, statistic_type=1, pvalue=0.00940696592998271) + + The p-value is lower than the threshold. We reject the null hypothesis in + favor of the alternative: the third quartile of the distribution underlying + our sample is greater than 0.6. + + `quantile_test` can also compute confidence intervals for any quantile. + + >>> rvs = stats.norm.rvs(size=100, random_state=rng) + >>> res = stats.quantile_test(rvs, q=0.6, p=0.75) + >>> ci = res.confidence_interval(confidence_level=0.95) + >>> ci + ConfidenceInterval(low=0.284491604437432, high=0.8912531024914844) + + When testing a one-sided alternative, the confidence interval contains + all observations such that if passed as `q`, the p-value of the + test would be greater than 0.05, and therefore the null hypothesis + would not be rejected. For example: + + >>> rvs.sort() + >>> q, p, alpha = 0.6, 0.75, 0.95 + >>> res = stats.quantile_test(rvs, q=q, p=p, alternative='less') + >>> ci = res.confidence_interval(confidence_level=alpha) + >>> for x in rvs[rvs <= ci.high]: + ... res = stats.quantile_test(rvs, q=x, p=p, alternative='less') + ... assert res.pvalue > 1-alpha + >>> for x in rvs[rvs > ci.high]: + ... res = stats.quantile_test(rvs, q=x, p=p, alternative='less') + ... assert res.pvalue < 1-alpha + + Also, if a 95% confidence interval is repeatedly generated for random + samples, the confidence interval will contain the true quantile value in + approximately 95% of replications. + + >>> dist = stats.rayleigh() # our "unknown" distribution + >>> p = 0.2 + >>> true_stat = dist.ppf(p) # the true value of the statistic + >>> n_trials = 1000 + >>> quantile_ci_contains_true_stat = 0 + >>> for i in range(n_trials): + ... data = dist.rvs(size=100, random_state=rng) + ... res = stats.quantile_test(data, p=p) + ... ci = res.confidence_interval(0.95) + ... if ci[0] < true_stat < ci[1]: + ... quantile_ci_contains_true_stat += 1 + >>> quantile_ci_contains_true_stat >= 950 + True + + This works with any distribution and any quantile, as long as the observations + are i.i.d. + """ + # Implementation carefully follows [1] 3.2 + # "H0: the p*th quantile of X is x*" + # To facilitate comparison with [1], we'll use variable names that + # best match Conover's notation + X, x_star, p_star, H1, axis, keepdims, axis_none, ndim, nan_out, dtype, xp = ( + quantile_test_iv(x, q, p, alternative, axis, keepdims)) + # `axis` is the original `axis`; the working axis is -1. + + # "We will use two test statistics in this test. Let T1 equal " + # "the number of observations less than or equal to x*, and " + # "let T2 equal the number of observations less than x*." + if X.shape[-1] > 0: + T1 = _xp_searchsorted(X, x_star, side='right', xp=xp) + T2 = _xp_searchsorted(X, x_star, side='left', xp=xp) + T1, T2 = xp.astype(T1, dtype), xp.astype(T2, dtype) + else: + nan_out = xp.ones_like(nan_out) + T = xp.zeros(x_star.shape, dtype=dtype) + T1, T2 = T, T + + # "The null distribution of the test statistics T1 and T2 is " + # "the binomial distribution, with parameters n = sample size, and " + # "p = p* as given in the null hypothesis.... Y has the binomial " + # "distribution with parameters n and p*." + n = X.shape[-1] + Y = _SimpleBinomial(n=n, p=p_star) + + # "H1: the p* population quantile is less than x*" + if H1 == 'less': + # "The p-value is the probability that a binomial random variable Y " + # "is greater than *or equal to* the observed value of T2...using p=p*" + pvalue = Y.sf(T2-1) # Y.pmf(T2) + Y.sf(T2) + statistic = xp.broadcast_to(T2, pvalue.shape) + statistic_type = xp.full_like(statistic, 2.) + # "H1: the p* population quantile is greater than x*" + elif H1 == 'greater': + # "The p-value is the probability that a binomial random variable Y " + # "is less than or equal to the observed value of T1... using p = p*" + pvalue = Y.cdf(T1) + statistic = xp.broadcast_to(T1, pvalue.shape) + statistic_type = xp.full_like(statistic, 1.) + # "H1: x* is not the p*th population quantile" + elif H1 == 'two-sided': + # "The p-value is twice the smaller of the probabilities that a + # binomial random variable Y is less than or equal to the observed + # value of T1 or greater than or equal to the observed value of T2 + # using p=p*." + # Note: both one-sided p-values can exceed 0.5 for the same data, so + # `clip` + p_min = xp.minimum(Y.cdf(T1), Y.sf(T2 - 1)) + pvalue = xp.clip(2*p_min, 0., 1.) + i2 = Y.cdf(T1) > Y.sf(T2 - 1) + statistic = xp.where(i2, T2, T1) + statistic_type = xp.where(i2, xp.full_like(T2, 2.), xp.full_like(T1, 1.)) + + statistic = xp.astype(statistic, dtype) + statistic_type = xp.astype(statistic_type, dtype) + pvalue = xp.astype(pvalue, dtype) + X = xp.astype(X, dtype) + + _statistic, _statistic_type, _pvalue = statistic, statistic_type, pvalue + args = axis, axis_none, keepdims, ndim, nan_out, xp + statistic = _quantile_test_postprocess(statistic, *args) + statistic_type = _quantile_test_postprocess(statistic_type, *args) + pvalue = _quantile_test_postprocess(pvalue, *args) + + return QuantileTestResult( + statistic=statistic, + statistic_type=statistic_type, + pvalue=pvalue, + _alternative=H1, + _x=X, + _p=p_star, + _statistic=statistic, + _statistic_type=_statistic_type, + _pvalue=_pvalue, + _axis=axis, + _axis_none=axis_none, + _keepdims=keepdims, + _ndim=ndim, + _nan_out=nan_out, + _xp=xp, + ) + + +##################################### +# STATISTICAL DISTANCES # +##################################### + + +@xp_capabilities(np_only=True) +def wasserstein_distance_nd(u_values, v_values, u_weights=None, v_weights=None): + r""" + Compute the Wasserstein-1 distance between two N-D discrete distributions. + + The Wasserstein distance, also called the Earth mover's distance or the + optimal transport distance, is a similarity metric between two probability + distributions [1]_. In the discrete case, the Wasserstein distance can be + understood as the cost of an optimal transport plan to convert one + distribution into the other. The cost is calculated as the product of the + amount of probability mass being moved and the distance it is being moved. + A brief and intuitive introduction can be found at [2]_. + + .. versionadded:: 1.13.0 + + Parameters + ---------- + u_values : 2d array_like + A sample from a probability distribution or the support (set of all + possible values) of a probability distribution. Each element along + axis 0 is an observation or possible value, and axis 1 represents the + dimensionality of the distribution; i.e., each row is a vector + observation or possible value. + + v_values : 2d array_like + A sample from or the support of a second distribution. + + u_weights, v_weights : 1d array_like, optional + Weights or counts corresponding with the sample or probability masses + corresponding with the support values. Sum of elements must be positive + and finite. If unspecified, each value is assigned the same weight. + + Returns + ------- + distance : float + The computed distance between the distributions. + + Notes + ----- + Given two probability mass functions, :math:`u` + and :math:`v`, the first Wasserstein distance between the distributions + using the Euclidean norm is: + + .. math:: + + l_1 (u, v) = \inf_{\pi \in \Gamma (u, v)} \int \| x-y \|_2 \mathrm{d} \pi (x, y) + + where :math:`\Gamma (u, v)` is the set of (probability) distributions on + :math:`\mathbb{R}^n \times \mathbb{R}^n` whose marginals are :math:`u` and + :math:`v` on the first and second factors respectively. For a given value + :math:`x`, :math:`u(x)` gives the probability of :math:`u` at position + :math:`x`, and the same for :math:`v(x)`. + + This is also called the optimal transport problem or the Monge problem. + Let the finite point sets :math:`\{x_i\}` and :math:`\{y_j\}` denote + the support set of probability mass function :math:`u` and :math:`v` + respectively. The Monge problem can be expressed as follows, + + Let :math:`\Gamma` denote the transport plan, :math:`D` denote the + distance matrix and, + + .. math:: + + x = \text{vec}(\Gamma) \\ + c = \text{vec}(D) \\ + b = \begin{bmatrix} + u\\ + v\\ + \end{bmatrix} + + The :math:`\text{vec}()` function denotes the Vectorization function + that transforms a matrix into a column vector by vertically stacking + the columns of the matrix. + The transport plan :math:`\Gamma` is a matrix :math:`[\gamma_{ij}]` in + which :math:`\gamma_{ij}` is a positive value representing the amount of + probability mass transported from :math:`u(x_i)` to :math:`v(y_i)`. + Summing over the rows of :math:`\Gamma` should give the source distribution + :math:`u` : :math:`\sum_j \gamma_{ij} = u(x_i)` holds for all :math:`i` + and summing over the columns of :math:`\Gamma` should give the target + distribution :math:`v`: :math:`\sum_i \gamma_{ij} = v(y_j)` holds for all + :math:`j`. + The distance matrix :math:`D` is a matrix :math:`[d_{ij}]`, in which + :math:`d_{ij} = d(x_i, y_j)`. + + Given :math:`\Gamma`, :math:`D`, :math:`b`, the Monge problem can be + transformed into a linear programming problem by + taking :math:`A x = b` as constraints and :math:`z = c^T x` as minimization + target (sum of costs) , where matrix :math:`A` has the form + + .. math:: + + \begin{array} {rrrr|rrrr|r|rrrr} + 1 & 1 & \dots & 1 & 0 & 0 & \dots & 0 & \dots & 0 & 0 & \dots & + 0 \cr + 0 & 0 & \dots & 0 & 1 & 1 & \dots & 1 & \dots & 0 & 0 &\dots & + 0 \cr + \vdots & \vdots & \ddots & \vdots & \vdots & \vdots & \ddots + & \vdots & \vdots & \vdots & \vdots & \ddots & \vdots \cr + 0 & 0 & \dots & 0 & 0 & 0 & \dots & 0 & \dots & 1 & 1 & \dots & + 1 \cr \hline + + 1 & 0 & \dots & 0 & 1 & 0 & \dots & \dots & \dots & 1 & 0 & \dots & + 0 \cr + 0 & 1 & \dots & 0 & 0 & 1 & \dots & \dots & \dots & 0 & 1 & \dots & + 0 \cr + \vdots & \vdots & \ddots & \vdots & \vdots & \vdots & \ddots & + \vdots & \vdots & \vdots & \vdots & \ddots & \vdots \cr + 0 & 0 & \dots & 1 & 0 & 0 & \dots & 1 & \dots & 0 & 0 & \dots & 1 + \end{array} + + By solving the dual form of the above linear programming problem (with + solution :math:`y^*`), the Wasserstein distance :math:`l_1 (u, v)` can + be computed as :math:`b^T y^*`. + + The above solution is inspired by Vincent Herrmann's blog [3]_ . For a + more thorough explanation, see [4]_ . + + The input distributions can be empirical, therefore coming from samples + whose values are effectively inputs of the function, or they can be seen as + generalized functions, in which case they are weighted sums of Dirac delta + functions located at the specified values. + + References + ---------- + .. [1] "Wasserstein metric", + https://en.wikipedia.org/wiki/Wasserstein_metric + .. [2] Lili Weng, "What is Wasserstein distance?", Lil'log, + https://lilianweng.github.io/posts/2017-08-20-gan/#what-is-wasserstein-distance. + .. [3] Hermann, Vincent. "Wasserstein GAN and the Kantorovich-Rubinstein + Duality". https://vincentherrmann.github.io/blog/wasserstein/. + .. [4] Peyré, Gabriel, and Marco Cuturi. "Computational optimal + transport." Center for Research in Economics and Statistics + Working Papers 2017-86 (2017). + + See Also + -------- + wasserstein_distance: Compute the Wasserstein-1 distance between two + 1D discrete distributions. + + Examples + -------- + Compute the Wasserstein distance between two three-dimensional samples, + each with two observations. + + >>> from scipy.stats import wasserstein_distance_nd + >>> wasserstein_distance_nd([[0, 2, 3], [1, 2, 5]], [[3, 2, 3], [4, 2, 5]]) + 3.0 + + Compute the Wasserstein distance between two two-dimensional distributions + with three and two weighted observations, respectively. + + >>> wasserstein_distance_nd([[0, 2.75], [2, 209.3], [0, 0]], + ... [[0.2, 0.322], [4.5, 25.1808]], + ... [0.4, 5.2, 0.114], [0.8, 1.5]) + 174.15840245217169 + """ + m, n = len(u_values), len(v_values) + u_values = asarray(u_values) + v_values = asarray(v_values) + + if u_values.ndim > 2 or v_values.ndim > 2: + raise ValueError('Invalid input values. The inputs must have either ' + 'one or two dimensions.') + # if dimensions are not equal throw error + if u_values.ndim != v_values.ndim: + raise ValueError('Invalid input values. Dimensions of inputs must be ' + 'equal.') + # if data is 1D then call the cdf_distance function + if u_values.ndim == 1 and v_values.ndim == 1: + return _cdf_distance(1, u_values, v_values, u_weights, v_weights) + + u_values, u_weights = _validate_distribution(u_values, u_weights) + v_values, v_weights = _validate_distribution(v_values, v_weights) + # if number of columns is not equal throw error + if u_values.shape[1] != v_values.shape[1]: + raise ValueError('Invalid input values. If two-dimensional, ' + '`u_values` and `v_values` must have the same ' + 'number of columns.') + + # if data contains np.inf then return inf or nan + if np.any(np.isinf(u_values)) ^ np.any(np.isinf(v_values)): + return np.inf + elif np.any(np.isinf(u_values)) and np.any(np.isinf(v_values)): + return np.nan + + # create constraints + A_upper_part = sparse.block_diag((sparse.coo_array(np.ones((1, n))), ) * m) + A_lower_part = sparse.hstack((sparse.eye_array(n), ) * m) + # sparse constraint matrix of size (m + n)*(m * n) + A = sparse.vstack((A_upper_part, A_lower_part)) + + # get cost matrix + D = cdist(u_values, v_values) + cost = D.ravel() + + # create the minimization target + p_u = np.full(m, 1/m) if u_weights is None else u_weights/np.sum(u_weights) + p_v = np.full(n, 1/n) if v_weights is None else v_weights/np.sum(v_weights) + b = np.concatenate((p_u, p_v), axis=0) + + # solving LP + constraints = LinearConstraint(A=A.T, ub=cost) + opt_res = milp(c=-b, constraints=constraints, bounds=(-np.inf, np.inf)) + return -opt_res.fun + + +@xp_capabilities(np_only=True) +def wasserstein_distance(u_values, v_values, u_weights=None, v_weights=None): + r""" + Compute the Wasserstein-1 distance between two 1D discrete distributions. + + The Wasserstein distance, also called the Earth mover's distance or the + optimal transport distance, is a similarity metric between two probability + distributions [1]_. In the discrete case, the Wasserstein distance can be + understood as the cost of an optimal transport plan to convert one + distribution into the other. The cost is calculated as the product of the + amount of probability mass being moved and the distance it is being moved. + A brief and intuitive introduction can be found at [2]_. + + .. versionadded:: 1.0.0 + + Parameters + ---------- + u_values : 1d array_like + A sample from a probability distribution or the support (set of all + possible values) of a probability distribution. Each element is an + observation or possible value. + + v_values : 1d array_like + A sample from or the support of a second distribution. + + u_weights, v_weights : 1d array_like, optional + Weights or counts corresponding with the sample or probability masses + corresponding with the support values. Sum of elements must be positive + and finite. If unspecified, each value is assigned the same weight. + + Returns + ------- + distance : float + The computed distance between the distributions. + + Notes + ----- + Given two 1D probability mass functions, :math:`u` and :math:`v`, the first + Wasserstein distance between the distributions is: + + .. math:: + + l_1 (u, v) = \inf_{\pi \in \Gamma (u, v)} \int_{\mathbb{R} \times + \mathbb{R}} |x-y| \mathrm{d} \pi (x, y) + + where :math:`\Gamma (u, v)` is the set of (probability) distributions on + :math:`\mathbb{R} \times \mathbb{R}` whose marginals are :math:`u` and + :math:`v` on the first and second factors respectively. For a given value + :math:`x`, :math:`u(x)` gives the probability of :math:`u` at position + :math:`x`, and the same for :math:`v(x)`. + + If :math:`U` and :math:`V` are the respective CDFs of :math:`u` and + :math:`v`, this distance also equals to: + + .. math:: + + l_1(u, v) = \int_{-\infty}^{+\infty} |U-V| + + See [3]_ for a proof of the equivalence of both definitions. + + The input distributions can be empirical, therefore coming from samples + whose values are effectively inputs of the function, or they can be seen as + generalized functions, in which case they are weighted sums of Dirac delta + functions located at the specified values. + + References + ---------- + .. [1] "Wasserstein metric", https://en.wikipedia.org/wiki/Wasserstein_metric + .. [2] Lili Weng, "What is Wasserstein distance?", Lil'log, + https://lilianweng.github.io/posts/2017-08-20-gan/#what-is-wasserstein-distance. + .. [3] Ramdas, Garcia, Cuturi "On Wasserstein Two Sample Testing and Related + Families of Nonparametric Tests" (2015). :arXiv:`1509.02237`. + + See Also + -------- + wasserstein_distance_nd: Compute the Wasserstein-1 distance between two N-D + discrete distributions. + + Examples + -------- + >>> from scipy.stats import wasserstein_distance + >>> wasserstein_distance([0, 1, 3], [5, 6, 8]) + 5.0 + >>> wasserstein_distance([0, 1], [0, 1], [3, 1], [2, 2]) + 0.25 + >>> wasserstein_distance([3.4, 3.9, 7.5, 7.8], [4.5, 1.4], + ... [1.4, 0.9, 3.1, 7.2], [3.2, 3.5]) + 4.0781331438047861 + + """ + return _cdf_distance(1, u_values, v_values, u_weights, v_weights) + + +@xp_capabilities(np_only=True) +def energy_distance(u_values, v_values, u_weights=None, v_weights=None): + r"""Compute the energy distance between two 1D distributions. + + .. versionadded:: 1.0.0 + + Parameters + ---------- + u_values, v_values : array_like + Values observed in the (empirical) distribution. + u_weights, v_weights : array_like, optional + Weight for each value. If unspecified, each value is assigned the same + weight. + `u_weights` (resp. `v_weights`) must have the same length as + `u_values` (resp. `v_values`). If the weight sum differs from 1, it + must still be positive and finite so that the weights can be normalized + to sum to 1. + + Returns + ------- + distance : float + The computed distance between the distributions. + + Notes + ----- + The energy distance between two distributions :math:`u` and :math:`v`, whose + respective CDFs are :math:`U` and :math:`V`, equals to: + + .. math:: + + D(u, v) = \left( 2\mathbb E|X - Y| - \mathbb E|X - X'| - + \mathbb E|Y - Y'| \right)^{1/2} + + where :math:`X` and :math:`X'` (resp. :math:`Y` and :math:`Y'`) are + independent random variables whose probability distribution is :math:`u` + (resp. :math:`v`). + + Sometimes the square of this quantity is referred to as the "energy + distance" (e.g. in [2]_, [4]_), but as noted in [1]_ and [3]_, only the + definition above satisfies the axioms of a distance function (metric). + + As shown in [2]_, for one-dimensional real-valued variables, the energy + distance is linked to the non-distribution-free version of the Cramér-von + Mises distance: + + .. math:: + + D(u, v) = \sqrt{2} l_2(u, v) = \left( 2 \int_{-\infty}^{+\infty} (U-V)^2 + \right)^{1/2} + + Note that the common Cramér-von Mises criterion uses the distribution-free + version of the distance. See [2]_ (section 2), for more details about both + versions of the distance. + + The input distributions can be empirical, therefore coming from samples + whose values are effectively inputs of the function, or they can be seen as + generalized functions, in which case they are weighted sums of Dirac delta + functions located at the specified values. + + References + ---------- + .. [1] Rizzo, Szekely "Energy distance." Wiley Interdisciplinary Reviews: + Computational Statistics, 8(1):27-38 (2015). + .. [2] Szekely "E-statistics: The energy of statistical samples." Bowling + Green State University, Department of Mathematics and Statistics, + Technical Report 02-16 (2002). + .. [3] "Energy distance", https://en.wikipedia.org/wiki/Energy_distance + .. [4] Bellemare, Danihelka, Dabney, Mohamed, Lakshminarayanan, Hoyer, + Munos "The Cramer Distance as a Solution to Biased Wasserstein + Gradients" (2017). :arXiv:`1705.10743`. + + Examples + -------- + >>> from scipy.stats import energy_distance + >>> energy_distance([0], [2]) + 2.0000000000000004 + >>> energy_distance([0, 8], [0, 8], [3, 1], [2, 2]) + 1.0000000000000002 + >>> energy_distance([0.7, 7.4, 2.4, 6.8], [1.4, 8. ], + ... [2.1, 4.2, 7.4, 8. ], [7.6, 8.8]) + 0.88003340976158217 + + """ + return np.sqrt(2) * _cdf_distance(2, u_values, v_values, + u_weights, v_weights) + + +def _cdf_distance(p, u_values, v_values, u_weights=None, v_weights=None): + r""" + Compute, between two one-dimensional distributions :math:`u` and + :math:`v`, whose respective CDFs are :math:`U` and :math:`V`, the + statistical distance that is defined as: + + .. math:: + + l_p(u, v) = \left( \int_{-\infty}^{+\infty} |U-V|^p \right)^{1/p} + + p is a positive parameter; p = 1 gives the Wasserstein distance, p = 2 + gives the energy distance. + + Parameters + ---------- + u_values, v_values : array_like + Values observed in the (empirical) distribution. + u_weights, v_weights : array_like, optional + Weight for each value. If unspecified, each value is assigned the same + weight. + `u_weights` (resp. `v_weights`) must have the same length as + `u_values` (resp. `v_values`). If the weight sum differs from 1, it + must still be positive and finite so that the weights can be normalized + to sum to 1. + + Returns + ------- + distance : float + The computed distance between the distributions. + + Notes + ----- + The input distributions can be empirical, therefore coming from samples + whose values are effectively inputs of the function, or they can be seen as + generalized functions, in which case they are weighted sums of Dirac delta + functions located at the specified values. + + References + ---------- + .. [1] Bellemare, Danihelka, Dabney, Mohamed, Lakshminarayanan, Hoyer, + Munos "The Cramer Distance as a Solution to Biased Wasserstein + Gradients" (2017). :arXiv:`1705.10743`. + + """ + u_values, u_weights = _validate_distribution(u_values, u_weights) + v_values, v_weights = _validate_distribution(v_values, v_weights) + + u_sorter = np.argsort(u_values) + v_sorter = np.argsort(v_values) + + all_values = np.concatenate((u_values, v_values)) + all_values.sort(kind='mergesort') + + # Compute the differences between pairs of successive values of u and v. + deltas = np.diff(all_values) + + # Get the respective positions of the values of u and v among the values of + # both distributions. + u_cdf_indices = u_values[u_sorter].searchsorted(all_values[:-1], 'right') + v_cdf_indices = v_values[v_sorter].searchsorted(all_values[:-1], 'right') + + # Calculate the CDFs of u and v using their weights, if specified. + if u_weights is None: + u_cdf = u_cdf_indices / u_values.size + else: + u_sorted_cumweights = np.concatenate(([0], + np.cumsum(u_weights[u_sorter]))) + u_cdf = u_sorted_cumweights[u_cdf_indices] / u_sorted_cumweights[-1] + + if v_weights is None: + v_cdf = v_cdf_indices / v_values.size + else: + v_sorted_cumweights = np.concatenate(([0], + np.cumsum(v_weights[v_sorter]))) + v_cdf = v_sorted_cumweights[v_cdf_indices] / v_sorted_cumweights[-1] + + # Compute the value of the integral based on the CDFs. + # If p = 1 or p = 2, we avoid using np.power, which introduces an overhead + # of about 15%. + if p == 1: + return np.vecdot(np.abs(u_cdf - v_cdf), deltas) + if p == 2: + return np.sqrt(np.vecdot(np.square(u_cdf - v_cdf), deltas)) + return np.power(np.vecdot(np.power(np.abs(u_cdf - v_cdf), p), deltas), 1/p) + + +def _validate_distribution(values, weights): + """ + Validate the values and weights from a distribution input of `cdf_distance` + and return them as ndarray objects. + + Parameters + ---------- + values : array_like + Values observed in the (empirical) distribution. + weights : array_like + Weight for each value. + + Returns + ------- + values : ndarray + Values as ndarray. + weights : ndarray + Weights as ndarray. + + """ + # Validate the value array. + values = np.asarray(values, dtype=float) + if len(values) == 0: + raise ValueError("Distribution can't be empty.") + + # Validate the weight array, if specified. + if weights is not None: + weights = np.asarray(weights, dtype=float) + if len(weights) != len(values): + raise ValueError('Value and weight array-likes for the same ' + 'empirical distribution must be of the same size.') + if np.any(weights < 0): + raise ValueError('All weights must be non-negative.') + if not 0 < np.sum(weights) < np.inf: + raise ValueError('Weight array-like sum must be positive and ' + 'finite. Set as None for an equal distribution of ' + 'weight.') + + return values, weights + + return values, None + + +@xp_capabilities(skip_backends=[("dask.array", "no `take_along_axis`")], + marray=True) +def rankdata(a, method='average', *, axis=None, nan_policy='propagate'): + """Assign ranks to data, dealing with ties appropriately. + + By default (``axis=None``), the data array is first flattened, and a flat + array of ranks is returned. Separately reshape the rank array to the + shape of the data array if desired (see Examples). + + Ranks begin at 1. The `method` argument controls how ranks are assigned + to equal values. See [1]_ for further discussion of ranking methods. + + Parameters + ---------- + a : array_like + The array of values to be ranked. + method : {'average', 'min', 'max', 'dense', 'ordinal'}, optional + The method used to assign ranks to tied elements. + The following methods are available (default is 'average'): + + * 'average': The average of the ranks that would have been assigned to + all the tied values is assigned to each value. + * 'min': The minimum of the ranks that would have been assigned to all + the tied values is assigned to each value. (This is also + referred to as "competition" ranking.) + * 'max': The maximum of the ranks that would have been assigned to all + the tied values is assigned to each value. + * 'dense': Like 'min', but the rank of the next highest element is + assigned the rank immediately after those assigned to the tied + elements. + * 'ordinal': All values are given a distinct rank, corresponding to + the order that the values occur in `a`. + + axis : {None, int}, optional + Axis along which to perform the ranking. If ``None``, the data array + is first flattened. + nan_policy : {'propagate', 'omit', 'raise'}, optional + Defines how to handle when input contains nan. + The following options are available (default is 'propagate'): + + * 'propagate': propagates nans through the rank calculation + * 'omit': performs the calculations ignoring nan values + * 'raise': raises an error + + .. note:: + + When `nan_policy` is 'propagate', the output is an array of *all* + nans because ranks relative to nans in the input are undefined. + When `nan_policy` is 'omit', nans in `a` are ignored when ranking + the other values, and the corresponding locations of the output + are nan. + + .. versionadded:: 1.10 + + Returns + ------- + ranks : ndarray + An array of size equal to the size of `a`, containing rank + scores. The dtype is the result dtype of `a` and a Python float. + + References + ---------- + .. [1] "Ranking", https://en.wikipedia.org/wiki/Ranking + + Examples + -------- + >>> import numpy as np + >>> from scipy.stats import rankdata + >>> rankdata([0, 2, 3, 2]) + array([1. , 2.5, 4. , 2.5]) + >>> rankdata([0, 2, 3, 2], method='min') + array([1., 2., 4., 2.]) + >>> rankdata([0, 2, 3, 2], method='max') + array([1., 3., 4., 3.]) + >>> rankdata([0, 2, 3, 2], method='dense') + array([1., 2., 3., 2.]) + >>> rankdata([0, 2, 3, 2], method='ordinal') + array([1., 2., 4., 3.]) + >>> rankdata([[0, 2], [3, 2]]).reshape(2, 2) + array([[1. , 2.5], + [4. , 2.5]]) + >>> rankdata([[0, 2, 2], [3, 2, 5]], axis=1) + array([[1. , 2.5, 2.5], + [2. , 1. , 3. ]]) + >>> rankdata([0, 2, 3, np.nan, -2, np.nan], nan_policy="propagate") + array([nan, nan, nan, nan, nan, nan]) + >>> rankdata([0, 2, 3, np.nan, -2, np.nan], nan_policy="omit") + array([ 2., 3., 4., nan, 1., nan]) + + """ + methods = ('average', 'min', 'max', 'dense', 'ordinal') + if method not in methods: + raise ValueError(f'unknown method "{method}"') + + xp = array_namespace(a) + x = xp.asarray(a) + + if axis is None: + x = xp_ravel(x) + axis = -1 + + if xp_size(x) == 0: + dtype = xp_result_type(x, force_floating=True, xp=xp) + return xp.empty_like(x, dtype=dtype) + + contains_nan = _contains_nan(x, nan_policy) + + x = xp_swapaxes(x, axis, -1, xp=xp) + ranks, _, _ = _rankdata(x, method, xp=xp) + + # JIT won't allow use of `contains_nan` for control flow here, so we always have to + # run this with JIT. + if is_lazy_array(x) or contains_nan: + i_nan = (xp.isnan(x) if nan_policy == 'omit' + else xp.any(xp.isnan(x), axis=-1, keepdims=True)) + i_nan = xp.broadcast_to(i_nan, ranks.shape) + ranks = xpx.at(ranks)[i_nan].set(xp.nan) + + ranks = xp_swapaxes(ranks, axis, -1, xp=xp) + return ranks + + +def _order_ranks(ranks, j, *, xp): + # Reorder ascending order `ranks` according to `j` + xp = array_namespace(ranks) if xp is None else xp + if is_numpy(xp) or is_cupy(xp): + ordered_ranks = xp.empty(j.shape, dtype=ranks.dtype) + xp.put_along_axis(ordered_ranks, j, ranks, axis=-1) + else: + # `put_along_axis` not in array API (data-apis/array-api#177) + # so argsort the argsort and take_along_axis... + j_inv = xp.argsort(j, axis=-1, stable=True) + ordered_ranks = xp.take_along_axis(ranks, j_inv, axis=-1) + return ordered_ranks + + +def _rankdata(x, method, return_sorted=False, return_ties=False, xp=None): + # Rank data `x` by desired `method`. For methods other than 'ordinal': + # - `return_sorted=True` ensures that the second output is sorted `x` + # - `return_ties=True` ensures that the third output is tie data + # Otherwise, the second/third output will be None. + xp = array_namespace(x) if xp is None else xp + dtype = xp_result_type(x, force_floating=True, xp=xp) + + if is_jax(xp): + import jax.scipy.stats as jax_stats + ranks = jax_stats.rankdata(x, method=method, axis=-1) + ranks = xp.astype(ranks, dtype, copy=False) + y = xp.sort(x, axis=-1) if (return_ties or return_sorted) else None + t = None + if return_ties: + max_ranks = jax_stats.rankdata(y, method='max', axis=-1) + t = xp.diff(max_ranks, axis=-1, prepend=0) + t = xp.astype(t, dtype, copy=False) + return ranks, y, t + + if is_marray(xp): + data, mask = x.data, x.mask + uxp = array_namespace(data) + data = uxp.where(mask, uxp.nan, data) + ranks, sorted, ties = _rankdata(data, method, return_sorted, return_ties, uxp) + ranks = xp.asarray(ranks, mask=mask) + mask_sorted = uxp.isnan(sorted) if (return_sorted or return_ties) else None + sorted = xp.asarray(sorted, mask=mask_sorted) if return_sorted else None + ties = xp.asarray(ties, mask=mask_sorted) if return_ties else None + return ranks, sorted, ties + + shape = x.shape + + # Get sort order + j = xp.argsort(x, axis=-1, stable=True) + ordinal_ranks = xp.broadcast_to(xp.arange(1, shape[-1]+1, dtype=dtype), shape) + + # Ordinal ranks is very easy because ties don't matter. We're done. + if method == 'ordinal': + ranks = _order_ranks(ordinal_ranks, j, xp=xp) + return (ranks, None, None) + + # Sort array + y = xp.take_along_axis(x, j, axis=-1) + # Logical indices of unique elements + i = xp.concat([xp.ones(shape[:-1] + (1,), dtype=xp.bool), + y[..., :-1] != y[..., 1:]], axis=-1) + + # Integer indices of unique elements + indices = xp.arange(xp_size(y))[xp.reshape(i, (-1,))] # i gets raveled + # Counts of unique elements + counts = xp.diff(indices, append=xp.asarray([xp_size(y)], dtype=indices.dtype)) + + # Compute `'min'`, `'max'`, and `'mid'` ranks of unique elements + if method == 'min': + ranks = ordinal_ranks[i] + elif method == 'max': + ranks = ordinal_ranks[i] + xp.astype(counts, dtype) - 1 + elif method == 'average': + # array API doesn't promote integers to floats + ranks = ordinal_ranks[i] + (xp.astype(counts, dtype) - 1)/2 + elif method == 'dense': + ranks = xp.cumulative_sum(xp.astype(i, dtype, copy=False), axis=-1)[i] + + if not is_cupy(xp): + ranks = xp.reshape(xp.repeat(ranks, counts), shape) + else: + # workaround for cupy.repeat not accepting arrays for repeats + # (ref https://github.com/cupy/cupy/issues/3849). + # The cumulative sum over i will + # increment every time a new unique rank appears, giving the correct + # indices to replicate xp.repeats. + ranks = xp.reshape( + ranks[xp.cumulative_sum(xp.astype(xp.reshape(i, (-1,)), xp.int64)) - 1], + shape, + ) + ranks = _order_ranks(ranks, j, xp=xp) + + t = None + if return_ties: + # Tie information is returned in a format that is useful to functions that + # rely on this (private) function. Example: + # >>> x = np.asarray([3, 2, 1, 2, 2, 2, 1]) + # >>> _, t = _rankdata(x, 'average', return_ties=True) + # >>> t # array([2., 0., 4., 0., 0., 0., 1.]) # two 1s, four 2s, and one 3 + # Unlike ranks, tie counts are *not* reordered to correspond with the order of + # the input; e.g. the number of appearances of the lowest rank element comes + # first. This is a useful format because: + # - The shape of the result is the shape of the input. Different slices can + # have different numbers of tied elements but not result in a ragged array. + # - Functions that use `t` usually don't need to which each element of the + # original array is associated with each tie count; they perform a reduction + # over the tie counts onnly. The tie counts are naturally computed in a + # sorted order, so this does not unnecessarily reorder them. + # - One exception is `wilcoxon`, which needs the number of zeros. Zeros always + # have the lowest rank, so it is easy to find them at the zeroth index. + t = xp.zeros(shape, dtype=dtype) + t = xpx.at(t)[i].set(xp.astype(counts, dtype, copy=False)) + + return ranks, y, t + + +@xp_capabilities(marray=True, + skip_backends=[('dask.array', 'uses `optimize.elementwise.find_root`'), + ('array_api_strict', 'uses `optimize.elementwise.find_root`'), + ('jax.numpy', 'uses `optimize.elementwise.find_root`')]) +@_axis_nan_policy_factory( + lambda x: x, n_outputs=1, result_to_tuple=lambda x, _: (x,), + kwd_samples=['weights'], paired=True +) +def expectile(a, alpha=0.5, *, weights=None, axis=None): + r"""Compute the expectile at the specified level. + + Expectiles are a generalization of the expectation in the same way as + quantiles are a generalization of the median. The expectile at level + `alpha = 0.5` is the mean (average). See Notes for more details. + + Parameters + ---------- + a : array_like + Array containing numbers whose expectile is desired. + alpha : float, default: 0.5 + The level of the expectile; e.g., ``alpha=0.5`` gives the mean. + weights : array_like, optional + An array of weights associated with the values in `a`. + The `weights` must be broadcastable to the same shape as `a`. + Default is None, which gives each value a weight of 1.0. + An integer valued weight element acts like repeating the corresponding + observation in `a` that many times. See Notes for more details. + axis : int or tuple of ints, default: None + If an int or tuple of ints, the axis or axes of the input along which + to compute the statistic. The statistic of each axis-slice (e.g. row) + of the input will appear in a corresponding element of the output. + If ``None``, the input will be raveled before computing the statistic. + + Returns + ------- + expectile : ndarray + The empirical expectile at level `alpha`. + + See Also + -------- + numpy.mean : Arithmetic average + numpy.quantile : Quantile + + Notes + ----- + In general, the expectile at level :math:`\alpha` of a random variable + :math:`X` with cumulative distribution function (CDF) :math:`F` is given + by the unique solution :math:`t` of: + + .. math:: + + \alpha E((X - t)_+) = (1 - \alpha) E((t - X)_+) \,. + + Here, :math:`(x)_+ = \max(0, x)` is the positive part of :math:`x`. + This equation can be equivalently written as: + + .. math:: + + \alpha \int_t^\infty (x - t)\mathrm{d}F(x) + = (1 - \alpha) \int_{-\infty}^t (t - x)\mathrm{d}F(x) \,. + + The empirical expectile at level :math:`\alpha` (`alpha`) of a sample + :math:`a_i` (the array `a`) is defined by plugging in the empirical CDF of + `a`. Given sample or case weights :math:`w` (the array `weights`), it + reads :math:`F_a(x) = \frac{1}{\sum_i w_i} \sum_i w_i 1_{a_i \leq x}` + with indicator function :math:`1_{A}`. This leads to the definition of the + empirical expectile at level `alpha` as the unique solution :math:`t` of: + + .. math:: + + \alpha \sum_{i=1}^n w_i (a_i - t)_+ = + (1 - \alpha) \sum_{i=1}^n w_i (t - a_i)_+ \,. + + For :math:`\alpha=0.5`, this simplifies to the weighted average. + Furthermore, the larger :math:`\alpha`, the larger the value of the + expectile. + + As a final remark, the expectile at level :math:`\alpha` can also be + written as a minimization problem. One often used choice is + + .. math:: + + \operatorname{argmin}_t + E(\lvert 1_{t\geq X} - \alpha\rvert(t - X)^2) \,. + + References + ---------- + .. [1] W. K. Newey and J. L. Powell (1987), "Asymmetric Least Squares + Estimation and Testing," Econometrica, 55, 819-847. + .. [2] T. Gneiting (2009). "Making and Evaluating Point Forecasts," + Journal of the American Statistical Association, 106, 746 - 762. + :doi:`10.48550/arXiv.0912.0902` + + Examples + -------- + >>> import numpy as np + >>> from scipy.stats import expectile + >>> a = [1, 4, 2, -1] + >>> expectile(a, alpha=0.5) == np.mean(a) + True + >>> expectile(a, alpha=0.2) + 0.42857142857142855 + >>> expectile(a, alpha=0.8) + 2.5714285714285716 + >>> weights = [1, 3, 1, 1] + >>> expectile(a, alpha=0.8, weights=weights) + 3.3333333333333335 + """ + xp = array_namespace(a, weights) + a, weights = xp_promote(a, weights, force_floating=True, xp=xp) + + if alpha < 0 or alpha > 1: + raise ValueError( + "The expectile level alpha must be in the range [0, 1]." + ) + + if a.shape[-1] == 0: # Only for *testing* _axis_nan_policy decorator + return _get_nan(a, xp=xp) + + # for simplicity, ensure that shape is (batch size, sample size) and at least 2d + shape = a.shape # remember original shape, though, to ensure correct output shape + # `reshape` can't infer shape element -1 when array is size 0, so take `prod` + a = xp.reshape(a, (math.prod(shape[:-1]), shape[-1])) + weights = weights if weights is None else xp.reshape(weights, (-1, shape[-1])) + + # This is the empirical equivalent of Eq. (13) with identification + # function from Table 9 (omitting a factor of 2) in [2] (their y is our + # data a, their x is our t) + def first_order(t, i): + ai = a[i, ...].T + wi = weights if weights is None else weights[i, ...].T + return _xp_mean(xp.abs(xp.astype(ai <= t, ai.dtype) - alpha) * (t - ai), + weights=wi, axis=0) + + # axis is -1 per the decorator. Use mean to produce NaNs of the correct shape/type. + x0 = xp.min(a, axis=-1) + x1 = xp.max(a, axis=-1) + + # Note that the expectile is the unique solution, so no worries about + # finding a wrong root. + i = xp.arange(a.shape[0]) + res = find_root(first_order, (x0, x1), args=(i,)) + res = xp.reshape(res.x, shape[:-1]) + return res[()] if res.ndim == 0 else res + + +def _lmoment_iv(sample, order, axis, sorted, standardize, xp): + # input validation/standardization for `lmoment` + sample = xp_promote(sample, force_floating=True, xp=xp) + + message = "`sample` must be an array of real numbers." + if not xp.isdtype(sample.dtype, "real floating"): + raise ValueError(message) + + message = "`order` must be a scalar or a non-empty array of positive integers." + order = xp.arange(1, 5) if order is None else xp.asarray(order) + if (not xp.isdtype(order.dtype, "integral") or order.size == 0 or order.ndim > 1 + or (not is_lazy_array(order) and xp.any(order <= 0))): + raise ValueError(message) + + # input validation of non-array types can still be performed with NumPy + axis = np.asarray(axis)[()] + message = "`axis` must be an integer." + if not np.issubdtype(axis.dtype, np.integer) or axis.ndim != 0: + raise ValueError(message) + axis = int(axis) + + sorted = np.asarray(sorted)[()] + message = "`sorted` must be True or False." + if not np.issubdtype(sorted.dtype, np.bool_) or sorted.ndim != 0: + raise ValueError(message) + sorted = bool(sorted) + + standardize = np.asarray(standardize)[()] + message = "`standardize` must be True or False." + if not np.issubdtype(standardize.dtype, np.bool_) or standardize.ndim != 0: + raise ValueError(message) + standardize = bool(standardize) + + sample = xp.moveaxis(sample, axis, -1) + sample = xp.sort(sample, axis=-1) if not sorted else sample + + return sample, order, axis, sorted, standardize + + +def _br(x, *, r=0, xp): + n = x.shape[-1] + x = xp.expand_dims(x, axis=-2) + x = xp.broadcast_to(x, x.shape[:-2] + (r.shape[0], n)) + x = xp.triu(x) + j = xp.arange(n, dtype=x.dtype) + n = xp.asarray(n, dtype=x.dtype)[()] + binom_j_r = xpx.lazy_apply(special.binom, j, r[:, xp.newaxis]) + binom_nm1_r = xpx.lazy_apply(special.binom, n-1, r) + return xp.vecdot(binom_j_r, x, axis=-1) / binom_nm1_r / n + + +def _prk(r, k): + # Writen to match [1] Equation 27 closely to facilitate review. + # This does not protect against overflow, so improvements to + # robustness would be a welcome follow-up. + binom_r_k = xpx.lazy_apply(special.binom, r, k) + binom_rpk_k = xpx.lazy_apply(special.binom, r+k, k) + return (-1)**(r-k)*binom_r_k*binom_rpk_k + + +@xp_capabilities(skip_backends=[('dask.array', "too many issues")], + cpu_only=True, # torch doesn't have `binom` + exceptions=('cupy', 'jax.numpy'), + extra_note="Lazy backends do not support `order` greater than 4.") +@_axis_nan_policy_factory( # noqa: E302 + _moment_result_object, n_samples=1, result_to_tuple=_moment_tuple, + n_outputs=lambda kwds: _moment_outputs(kwds, [1, 2, 3, 4]) +) +def lmoment(sample, order=None, *, axis=0, sorted=False, standardize=True): + r"""Compute L-moments of a sample from a continuous distribution. + + The L-moments of a probability distribution are summary statistics with + uses similar to those of conventional moments, but they are defined in + terms of the expected values of order statistics. + Sample L-moments are defined analogously to population L-moments, and + they can serve as estimators of population L-moments. They tend to be less + sensitive to extreme observations than conventional moments. + + Parameters + ---------- + sample : array_like + The real-valued sample whose L-moments are desired. + order : array_like, optional + The (positive integer) orders of the desired L-moments. + Must be a scalar or non-empty 1D array. Default is [1, 2, 3, 4]. + axis : int or None, default=0 + If an int, the axis of the input along which to compute the statistic. + The statistic of each axis-slice (e.g. row) of the input will appear + in a corresponding element of the output. If None, the input will be + raveled before computing the statistic. + sorted : bool, default=False + Whether `sample` is already sorted in increasing order along `axis`. + If False (default), `sample` will be sorted. + standardize : bool, default=True + Whether to return L-moment ratios for orders 3 and higher. + L-moment ratios are analogous to standardized conventional + moments: they are the non-standardized L-moments divided + by the L-moment of order 2. + + Returns + ------- + lmoments : ndarray + The sample L-moments of order `order`. + + See Also + -------- + moment + + Notes + ----- + SciPy offers only basic capabilities for working with L-moments. For more advanced + features, consider the ``lmo`` package [4]_. + + References + ---------- + .. [1] D. Bilkova. "L-Moments and TL-Moments as an Alternative Tool of + Statistical Data Analysis". Journal of Applied Mathematics and + Physics. 2014. :doi:`10.4236/jamp.2014.210104` + .. [2] J. R. M. Hosking. "L-Moments: Analysis and Estimation of Distributions + Using Linear Combinations of Order Statistics". Journal of the Royal + Statistical Society. 1990. :doi:`10.1111/j.2517-6161.1990.tb01775.x` + .. [3] "L-moment". *Wikipedia*. https://en.wikipedia.org/wiki/L-moment. + .. [4] @jorenham, *Lmo*, https://github.com/jorenham/Lmo/ + + Examples + -------- + >>> import numpy as np + >>> from scipy import stats + >>> rng = np.random.default_rng(328458568356392) + >>> sample = rng.exponential(size=100000) + >>> stats.lmoment(sample) + array([1.00124272, 0.50111437, 0.3340092 , 0.16755338]) + + Note that the first four standardized population L-moments of the standard + exponential distribution are 1, 1/2, 1/3, and 1/6; the sample L-moments + provide reasonable estimates. + + """ + xp = array_namespace(sample) + args = _lmoment_iv(sample, order, axis, sorted, standardize, xp=xp) + sample, order, axis, sorted, standardize = args + + n_moments = 4 if is_lazy_array(order) else int(xp.max(order)) + k = xp.arange(n_moments, dtype=sample.dtype) + prk = _prk(xpx.expand_dims(k, axis=tuple(range(1, sample.ndim+1))), k) + bk = _br(sample, r=k, xp=xp) + + n = sample.shape[-1] + if n < bk.shape[-1]: + bk = xpx.at(bk)[..., n:].set(0) # remove NaNs due to n_moments > n + + lmoms = xp.vecdot(prk, bk, axis=-1) + if standardize and n_moments > 2: + lmoms = xpx.at(lmoms)[2:, ...].divide(lmoms[1, ...]) + + if n < lmoms.shape[0]: + lmoms = xpx.at(lmoms)[n:, ...].set(xp.nan) # add NaNs where appropriate + # return lmoms[order-1] # strict can't handle fancy indexing plus ellipses + return xp.take(lmoms, order - 1, axis=0) if order.ndim == 1 else lmoms[order - 1] + + +LinregressResult = _make_tuple_bunch('LinregressResult', + ['slope', 'intercept', 'rvalue', + 'pvalue', 'stderr'], + extra_field_names=['intercept_stderr']) + + +def _pack_LinregressResult(slope, intercept, rvalue, pvalue, stderr, intercept_stderr): + return LinregressResult(slope, intercept, rvalue, pvalue, stderr, + intercept_stderr=intercept_stderr) + + +def _unpack_LinregressResult(res, _): + return tuple(res) + (res.intercept_stderr,) + + +@xp_capabilities(skip_backends=[('dask.array', '`dtype` inference failed....')], + cpu_only=True, exceptions=['cupy', 'jax.numpy'], + marray=True) +@_axis_nan_policy_factory(_pack_LinregressResult, n_samples=2, + result_to_tuple=_unpack_LinregressResult, paired=True, + too_small=1, n_outputs=6) +def linregress(x, y, alternative='two-sided', *, axis=0): + """ + Calculate a linear least-squares regression for two sets of measurements. + + Parameters + ---------- + x, y : array_like + Two sets of measurements. Both arrays should have the same length N. + alternative : {'two-sided', 'less', 'greater'}, optional + Defines the alternative hypothesis. Default is 'two-sided'. + The following options are available: + + * 'two-sided': the slope of the regression line is nonzero + * 'less': the slope of the regression line is less than zero + * 'greater': the slope of the regression line is greater than zero + + .. versionadded:: 1.7.0 + axis : int or None, default: 0 + If an int, the axis of the input along which to compute the statistic. + The statistic of each axis-slice (e.g. row) of the input will appear in a + corresponding element of the output. + If ``None``, the input will be raveled before computing the statistic. + + Returns + ------- + result : ``LinregressResult`` instance + The return value is an object with the following attributes: + + slope : float + Slope of the regression line. + intercept : float + Intercept of the regression line. + rvalue : float + The Pearson correlation coefficient. The square of ``rvalue`` + is equal to the coefficient of determination. + pvalue : float + The p-value for a hypothesis test whose null hypothesis is + that the slope is zero, using Wald Test with t-distribution of + the test statistic. See `alternative` above for alternative + hypotheses. + stderr : float + Standard error of the estimated slope (gradient), under the + assumption of residual normality. + intercept_stderr : float + Standard error of the estimated intercept, under the assumption + of residual normality. + + See Also + -------- + scipy.optimize.curve_fit : + Use non-linear least squares to fit a function to data. + scipy.optimize.leastsq : + Minimize the sum of squares of a set of equations. + + Notes + ----- + For compatibility with older versions of SciPy, the return value acts + like a ``namedtuple`` of length 5, with fields ``slope``, ``intercept``, + ``rvalue``, ``pvalue`` and ``stderr``, so one can continue to write:: + + slope, intercept, r, p, se = linregress(x, y) + + With that style, however, the standard error of the intercept is not + available. To have access to all the computed values, including the + standard error of the intercept, use the return value as an object + with attributes, e.g.:: + + result = linregress(x, y) + print(result.intercept, result.intercept_stderr) + + Examples + -------- + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from scipy import stats + >>> rng = np.random.default_rng() + + Generate some data: + + >>> x = rng.random(10) + >>> y = 1.6*x + rng.random(10) + + Perform the linear regression: + + >>> res = stats.linregress(x, y) + + Coefficient of determination (R-squared): + + >>> print(f"R-squared: {res.rvalue**2:.6f}") + R-squared: 0.717533 + + Plot the data along with the fitted line: + + >>> plt.plot(x, y, 'o', label='original data') + >>> plt.plot(x, res.intercept + res.slope*x, 'r', label='fitted line') + >>> plt.legend() + >>> plt.show() + + Calculate 95% confidence interval on slope and intercept: + + >>> # Two-sided inverse Students t-distribution + >>> # p - probability, df - degrees of freedom + >>> from scipy.stats import t + >>> tinv = lambda p, df: abs(t.ppf(p/2, df)) + + >>> ts = tinv(0.05, len(x)-2) + >>> print(f"slope (95%): {res.slope:.6f} +/- {ts*res.stderr:.6f}") + slope (95%): 1.453392 +/- 0.743465 + >>> print(f"intercept (95%): {res.intercept:.6f}" + ... f" +/- {ts*res.intercept_stderr:.6f}") + intercept (95%): 0.616950 +/- 0.544475 + + """ + xp = array_namespace(x, y) + x, y = xp_promote(x, y, force_floating=True, xp=xp) + + TINY = 1.0e-20 + + # _axis_nan_policy decorator ensures that `axis=-1` + x, y = _share_masks(x, y, xp=xp) + n = _count_nonmasked(x, axis=-1, keepdims=False, xp=xp) + xmean = xp.mean(x, axis=-1, keepdims=True) + ymean = xp.mean(y, axis=-1, keepdims=True) + + # Average sums of square differences from the mean + # ssxm = mean( (x-mean(x))^2 ) + # ssxym = mean( (x-mean(x)) * (y-mean(y)) ) + x_ = _demean(x, xmean, axis=-1, xp=xp) + y_ = _demean(y, ymean, axis=-1, xp=xp, precision_warning=False) + xmean = xp.squeeze(xmean, axis=-1) + ymean = xp.squeeze(ymean, axis=-1) + + ssxm = xp.vecdot(x_, x_, axis=-1) / n + ssym = xp.vecdot(y_, y_, axis=-1) / n + ssxym = xp.vecdot(x_, y_, axis=-1) / n + + # R-value + # r = ssxym / sqrt( ssxm * ssym ) + degenerate = (ssxm == 0.0) | (ssym == 0.0) + NaN = xp.asarray(xp.nan, dtype=ssxym.dtype) + r = xpx.apply_where( + ~degenerate, + (ssxym, ssxm, ssym), + lambda ssxym, ssxm, ssym: xp.clip(ssxym / xp.sqrt(ssxm * ssym), -1.0, 1.0), + lambda ssxym, ssxm, ssym: xp.where(ssxym==0, NaN, 0.0) + ) + + slope = ssxym / ssxm + intercept = ymean - slope*xmean + with np.errstate(invalid='ignore', divide='ignore'): + df = n - 2 # Number of degrees of freedom + # n-2 degrees of freedom because 2 has been used up + # to estimate the mean and standard deviation + t = r * xp.sqrt(df / ((1.0 - r + TINY)*(1.0 + r + TINY))) + + dist = _SimpleStudentT(xp.asarray(df, dtype=t.dtype)) + prob = _get_pvalue(t, dist, alternative, xp=xp) + prob = prob[()] if prob.ndim == 0 else prob + + slope_stderr = xp.sqrt((1 - r**2) * ssym / ssxm / df) + + # Also calculate the standard error of the intercept + # The following relationship is used: + # ssxm = mean( (x-mean(x))^2 ) + # = ssx - sx*sx + # = mean( x^2 ) - mean(x)^2 + intercept_stderr = slope_stderr * xp.sqrt(ssxm + xmean**2) + + outputs = slope, intercept, r, prob, slope_stderr, intercept_stderr + outputs = (output[()] if output.ndim == 0 else output for output in outputs) + slope, intercept, r, prob, slope_stderr, intercept_stderr = outputs + + return LinregressResult(slope=slope, intercept=intercept, rvalue=r, + pvalue=prob, stderr=slope_stderr, + intercept_stderr=intercept_stderr) + +def _linearized_pmean(a, p, *, axis=None, weights=None, xp=None): + # pmean linearized as a function of p about p = 0; see gh-23407 + M0 = gmean(a, axis=axis, weights=weights) + + loga = xp.log(a) + + ln_avg = _xp_mean(loga, axis=axis, weights=weights) + ln2_avg = _xp_mean(loga * loga, axis=axis, weights=weights) + + return M0 * (1 + 0.5 * p * (ln2_avg - ln_avg * ln_avg)) + + +@xp_capabilities() +def _xp_mean(x, /, *, axis=None, weights=None, keepdims=False, nan_policy='propagate', + dtype=None, warn=True, xp=None): + r"""Compute the arithmetic mean along the specified axis. + + Parameters + ---------- + x : real array + Array containing real numbers whose mean is desired. + axis : int or tuple of ints, default: None + If an int or tuple of ints, the axis or axes of the input along which + to compute the statistic. The statistic of each axis-slice (e.g. row) + of the input will appear in a corresponding element of the output. + If ``None``, the input will be raveled before computing the statistic. + weights : real array, optional + If specified, an array of weights associated with the values in `x`; + otherwise ``1``. If `weights` and `x` do not have the same shape, the + arrays will be broadcasted before performing the calculation. See + Notes for details. + keepdims : bool, optional + If this is set to ``True``, the axes which are reduced are left + in the result as dimensions with length one. With this option, + the result will broadcast correctly against the input array. + nan_policy : {'propagate', 'omit', 'raise'}, default: 'propagate' + Defines how to handle input NaNs. + + - ``propagate``: if a NaN is present in the axis slice (e.g. row) along + which the statistic is computed, the corresponding entry of the output + will be NaN. + - ``omit``: NaNs will be omitted when performing the calculation. + If insufficient data remains in the axis slice along which the + statistic is computed, the corresponding entry of the output will be + NaN. + - ``raise``: if a NaN is present, a ``ValueError`` will be raised. + + dtype : dtype, optional + Type to use in computing the mean. For integer inputs, the default is + the default float type of the array library; for floating point inputs, + the dtype is that of the input. + + Returns + ------- + out : array + The mean of each slice + + Notes + ----- + Let :math:`x_i` represent element :math:`i` of data `x` and let :math:`w_i` + represent the corresponding element of `weights` after broadcasting. Then the + (weighted) mean :math:`\bar{x}_w` is given by: + + .. math:: + + \bar{x}_w = \frac{ \sum_{i=0}^{n-1} w_i x_i } + { \sum_{i=0}^{n-1} w_i } + + where :math:`n` is the number of elements along a slice. Note that this simplifies + to the familiar :math:`(\sum_i x_i) / n` when the weights are all ``1`` (default). + + The behavior of this function with respect to weights is somewhat different + from that of `np.average`. For instance, + `np.average` raises an error when `axis` is not specified and the shapes of `x` + and the `weights` array are not the same; `xp_mean` simply broadcasts the two. + Also, `np.average` raises an error when weights sum to zero along a slice; + `xp_mean` computes the appropriate result. The intent is for this function's + interface to be consistent with the rest of `scipy.stats`. + + Note that according to the formula, including NaNs with zero weights is not + the same as *omitting* NaNs with ``nan_policy='omit'``; in the former case, + the NaNs will continue to propagate through the calculation whereas in the + latter case, the NaNs are excluded entirely. + + """ + # ensure that `x` and `weights` are array-API compatible arrays of identical shape + xp = array_namespace(x) if xp is None else xp + x = _asarray(x, dtype=dtype, subok=True) + weights = xp.asarray(weights, dtype=dtype) if weights is not None else weights + + # to ensure that this matches the behavior of decorated functions when one of the + # arguments has size zero, it's easiest to call a similar decorated function. + if is_numpy(xp) and (xp_size(x) == 0 + or (weights is not None and xp_size(weights) == 0)): + return gmean(x, weights=weights, axis=axis, keepdims=keepdims) + + x, weights = xp_promote(x, weights, broadcast=True, force_floating=True, xp=xp) + if weights is not None: + x, weights = _share_masks(x, weights, xp=xp) + + # handle the special case of zero-sized arrays + message = (too_small_1d_not_omit if (x.ndim == 1 or axis is None) + else too_small_nd_not_omit) + if xp_size(x) == 0: + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + res = xp.mean(x, axis=axis, keepdims=keepdims) + if warn and xp_size(res) != 0: + warnings.warn(message, SmallSampleWarning, stacklevel=2) + return res + + contains_nan = _contains_nan(x, nan_policy, xp_omit_okay=True, xp=xp) + if weights is not None: + contains_nan_w = _contains_nan(weights, nan_policy, xp_omit_okay=True, xp=xp) + contains_nan = contains_nan | contains_nan_w + + # Handle `nan_policy='omit'` by giving zero weight to NaNs, whether they + # appear in `x` or `weights`. Emit warning if there is an all-NaN slice. + # Test nan_policy before the implicit call to bool(contains_nan) + # to avoid raising on lazy xps on the default nan_policy='propagate' + lazy = is_lazy_array(x) + if nan_policy == 'omit' and (lazy or contains_nan): + nan_mask = xp.isnan(x) + if weights is not None: + nan_mask |= xp.isnan(weights) + if warn and not lazy and xp.any(xp.all(nan_mask, axis=axis)): + message = (too_small_1d_omit if (x.ndim == 1 or axis is None) + else too_small_nd_omit) + warnings.warn(message, SmallSampleWarning, stacklevel=2) + weights = xp.ones_like(x) if weights is None else weights + x = xp.where(nan_mask, 0., x) + weights = xp.where(nan_mask, 0., weights) + + # Perform the mean calculation itself + if weights is None: + return xp.mean(x, axis=axis, keepdims=keepdims) + + # consider using `vecdot` if `axis` tuple support is added (data-apis/array-api#910) + norm = xp.sum(weights, axis=axis) + wsum = xp.sum(x * weights, axis=axis) + with np.errstate(divide='ignore', invalid='ignore'): + res = wsum/norm + + # Respect `keepdims` and convert NumPy 0-D arrays to scalars + if keepdims: + + if axis is None: + final_shape = (1,) * len(x.shape) + else: + # axis can be a scalar or sequence + axes = (axis,) if not isinstance(axis, Sequence) else axis + final_shape = list(x.shape) + for i in axes: + final_shape[i] = 1 + + res = xp.reshape(res, tuple(final_shape)) + + return res[()] if res.ndim == 0 else res + + +@xp_capabilities() +def _xp_var(x, /, *, axis=None, correction=0, keepdims=False, nan_policy='propagate', + dtype=None, xp=None): + # an array-api compatible function for variance with scipy.stats interface + # and features (e.g. `nan_policy`). + xp = array_namespace(x) if xp is None else xp + x = _asarray(x, subok=True) + + # use `_xp_mean` instead of `xp.var` for desired warning behavior + # it would be nice to combine this with `_var`, which uses `_moment` + # and therefore warns when precision is lost, but that does not support + # `axis` tuples or keepdims. Eventually, `_axis_nan_policy` will simplify + # `axis` tuples and implement `keepdims` for non-NumPy arrays; then it will + # be easy. + kwargs = dict(axis=axis, nan_policy=nan_policy, dtype=dtype, xp=xp) + mean = _xp_mean(x, keepdims=True, **kwargs) + x = _asarray(x, dtype=mean.dtype, subok=True) + x_mean = _demean(x, mean, axis, xp=xp) + x_mean_conj = (xp.conj(x_mean) if xp.isdtype(x_mean.dtype, 'complex floating') + else x_mean) # crossref data-apis/array-api#824 + var = _xp_mean(x_mean * x_mean_conj, keepdims=keepdims, **kwargs) + + if correction != 0: + n = _count_nonmasked(x, axis, xp=xp, keepdims=keepdims) + # Or two lines with ternaries : ) + # axis = range(x.ndim) if axis is None else axis + # n = math.prod(x.shape[i] for i in axis) if iterable(axis) else x.shape[axis] + + n = xp.asarray(n, dtype=var.dtype, device=xp_device(x)) + + if nan_policy == 'omit': + nan_mask = xp.astype(xp.isnan(x), var.dtype) + n = n - xp.sum(nan_mask, axis=axis, keepdims=keepdims) + + # Produce NaNs silently when n - correction <= 0 + nc = n - correction + factor = xpx.apply_where(nc > 0, (n, nc), operator.truediv, fill_value=xp.nan) + var *= factor + + return var[()] if var.ndim == 0 else var + + +class _SimpleNormal: + # A very simple, array-API compatible normal distribution for use in + # hypothesis tests. May be replaced by new infrastructure Normal + # distribution in due time. + + def cdf(self, x): + return special.ndtr(x) + + def sf(self, x): + return special.ndtr(-x) + + def isf(self, x): + return -special.ndtri(x) + + +class _SimpleChi2: + # A very simple, array-API compatible chi-squared distribution for use in + # hypothesis tests. May be replaced by new infrastructure chi-squared + # distribution in due time. + def __init__(self, df): + self.df = df + + def cdf(self, x): + return special.chdtr(self.df, x) + + def sf(self, x): + return special.chdtrc(self.df, x) + + +class _SimpleBeta: + # A very simple, array-API compatible beta distribution for use in + # hypothesis tests. May be replaced by new infrastructure beta + # distribution in due time. + def __init__(self, a, b, *, loc=None, scale=None): + self.a = a + self.b = b + self.loc = loc + self.scale = scale + + def cdf(self, x): + if self.loc is not None or self.scale is not None: + loc = 0 if self.loc is None else self.loc + scale = 1 if self.scale is None else self.scale + return special.betainc(self.a, self.b, (x - loc)/scale) + return special.betainc(self.a, self.b, x) + + def sf(self, x): + if self.loc is not None or self.scale is not None: + loc = 0 if self.loc is None else self.loc + scale = 1 if self.scale is None else self.scale + return special.betaincc(self.a, self.b, (x - loc)/scale) + return special.betaincc(self.a, self.b, x) + + +class _SimpleStudentT: + # A very simple, array-API compatible t distribution for use in + # hypothesis tests. May be replaced by new infrastructure t + # distribution in due time. + def __init__(self, df): + self.df = df + + def cdf(self, t): + return special.stdtr(self.df, t) + + def sf(self, t): + return special.stdtr(self.df, -t) + + +class _SimpleF: + # A very simple, array-API compatible F distribution for use in + # hypothesis tests. + def __init__(self, dfn, dfd): + self.dfn = dfn + self.dfd = dfd + + def cdf(self, x): + return special.fdtr(self.dfn, self.dfd, x) + + def sf(self, x): + return special.fdtrc(self.dfn, self.dfd, x) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_stats_pythran.cpython-312-x86_64-linux-gnu.so b/venv/lib/python3.12/site-packages/scipy/stats/_stats_pythran.cpython-312-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..46ab388331867a5e2dedb593a52a9ca5ef93d221 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_stats_pythran.cpython-312-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:14320c5a7e42ed3a39a3606acdeb108aae23f0038307e35ae28090808239c530 +size 136360 diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_survival.py b/venv/lib/python3.12/site-packages/scipy/stats/_survival.py new file mode 100644 index 0000000000000000000000000000000000000000..70a84806a95ae82b2968bb9cf5ee37f48808c59c --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_survival.py @@ -0,0 +1,686 @@ +from dataclasses import dataclass, field +from typing import TYPE_CHECKING, Literal +import warnings + +import numpy as np +from scipy import special, interpolate, stats +from scipy._lib._array_api import xp_capabilities +from scipy.stats._censored_data import CensoredData +from scipy.stats._common import ConfidenceInterval + +if TYPE_CHECKING: + import numpy.typing as npt + + +__all__ = ['ecdf', 'logrank'] + + +@dataclass +class EmpiricalDistributionFunction: + """An empirical distribution function produced by `scipy.stats.ecdf` + + Attributes + ---------- + quantiles : ndarray + The unique values of the sample from which the + `EmpiricalDistributionFunction` was estimated. + probabilities : ndarray + The point estimates of the cumulative distribution function (CDF) or + its complement, the survival function (SF), corresponding with + `quantiles`. + """ + quantiles: np.ndarray + probabilities: np.ndarray + # Exclude these from __str__ + _n: np.ndarray = field(repr=False) # number "at risk" + _d: np.ndarray = field(repr=False) # number of "deaths" + _sf: np.ndarray = field(repr=False) # survival function for var estimate + _kind: str = field(repr=False) # type of function: "cdf" or "sf" + + def __init__(self, q, p, n, d, kind): + self.probabilities = p + self.quantiles = q + self._n = n + self._d = d + self._sf = p if kind == 'sf' else 1 - p + self._kind = kind + + f0 = 1 if kind == 'sf' else 0 # leftmost function value + f1 = 1 - f0 + # fill_value can't handle edge cases at infinity + x = np.insert(q, [0, len(q)], [-np.inf, np.inf]) + y = np.insert(p, [0, len(p)], [f0, f1]) + # `or` conditions handle the case of empty x, points + self._f = interpolate.interp1d(x, y, kind='previous', + assume_sorted=True) + + def evaluate(self, x): + """Evaluate the empirical CDF/SF function at the input. + + Parameters + ---------- + x : ndarray + Argument to the CDF/SF + + Returns + ------- + y : ndarray + The CDF/SF evaluated at the input + """ + return self._f(x) + + def plot(self, ax=None, **matplotlib_kwargs): + """Plot the empirical distribution function + + Available only if ``matplotlib`` is installed. + + Parameters + ---------- + ax : matplotlib.axes.Axes + Axes object to draw the plot onto, otherwise uses the current Axes. + + **matplotlib_kwargs : dict, optional + Keyword arguments passed directly to `matplotlib.axes.Axes.step`. + Unless overridden, ``where='post'``. + + Returns + ------- + lines : list of `matplotlib.lines.Line2D` + Objects representing the plotted data + """ + try: + import matplotlib # noqa: F401 + except ModuleNotFoundError as exc: + message = "matplotlib must be installed to use method `plot`." + raise ModuleNotFoundError(message) from exc + + if ax is None: + import matplotlib.pyplot as plt + ax = plt.gca() + + kwargs = {'where': 'post'} + kwargs.update(matplotlib_kwargs) + + delta = np.ptp(self.quantiles)*0.05 # how far past sample edge to plot + q = self.quantiles + q = [q[0] - delta] + list(q) + [q[-1] + delta] + + return ax.step(q, self.evaluate(q), **kwargs) + + def confidence_interval(self, confidence_level=0.95, *, method='linear'): + """Compute a confidence interval around the CDF/SF point estimate + + Parameters + ---------- + confidence_level : float, default: 0.95 + Confidence level for the computed confidence interval + + method : str, {"linear", "log-log"} + Method used to compute the confidence interval. Options are + "linear" for the conventional Greenwood confidence interval + (default) and "log-log" for the "exponential Greenwood", + log-negative-log-transformed confidence interval. + + Returns + ------- + ci : ``ConfidenceInterval`` + An object with attributes ``low`` and ``high``, instances of + `~scipy.stats._result_classes.EmpiricalDistributionFunction` that + represent the lower and upper bounds (respectively) of the + confidence interval. + + Notes + ----- + Confidence intervals are computed according to the Greenwood formula + (``method='linear'``) or the more recent "exponential Greenwood" + formula (``method='log-log'``) as described in [1]_. The conventional + Greenwood formula can result in lower confidence limits less than 0 + and upper confidence limits greater than 1; these are clipped to the + unit interval. NaNs may be produced by either method; these are + features of the formulas. + + References + ---------- + .. [1] Sawyer, Stanley. "The Greenwood and Exponential Greenwood + Confidence Intervals in Survival Analysis." + https://www.math.wustl.edu/~sawyer/handouts/greenwood.pdf + + """ + message = ("Confidence interval bounds do not implement a " + "`confidence_interval` method.") + if self._n is None: + raise NotImplementedError(message) + + methods = {'linear': self._linear_ci, + 'log-log': self._loglog_ci} + + message = f"`method` must be one of {set(methods)}." + if method.lower() not in methods: + raise ValueError(message) + + message = "`confidence_level` must be a scalar between 0 and 1." + confidence_level = np.asarray(confidence_level)[()] + if confidence_level.shape or not (0 <= confidence_level <= 1): + raise ValueError(message) + + method_fun = methods[method.lower()] + low, high = method_fun(confidence_level) + + message = ("The confidence interval is undefined at some observations." + " This is a feature of the mathematical formula used, not" + " an error in its implementation.") + if np.any(np.isnan(low) | np.isnan(high)): + warnings.warn(message, RuntimeWarning, stacklevel=2) + + low, high = np.clip(low, 0, 1), np.clip(high, 0, 1) + low = EmpiricalDistributionFunction(self.quantiles, low, None, None, + self._kind) + high = EmpiricalDistributionFunction(self.quantiles, high, None, None, + self._kind) + return ConfidenceInterval(low, high) + + def _linear_ci(self, confidence_level): + sf, d, n = self._sf, self._d, self._n + # When n == d, Greenwood's formula divides by zero. + # When s != 0, this can be ignored: var == inf, and CI is [0, 1] + # When s == 0, this results in NaNs. Produce an informative warning. + with np.errstate(divide='ignore', invalid='ignore'): + var = sf ** 2 * np.cumsum(d / (n * (n - d))) + + se = np.sqrt(var) + z = special.ndtri(1 / 2 + confidence_level / 2) + + z_se = z * se + low = self.probabilities - z_se + high = self.probabilities + z_se + + return low, high + + def _loglog_ci(self, confidence_level): + sf, d, n = self._sf, self._d, self._n + + with np.errstate(divide='ignore', invalid='ignore'): + var = 1 / np.log(sf) ** 2 * np.cumsum(d / (n * (n - d))) + + se = np.sqrt(var) + z = special.ndtri(1 / 2 + confidence_level / 2) + + with np.errstate(divide='ignore'): + lnl_points = np.log(-np.log(sf)) + + z_se = z * se + low = np.exp(-np.exp(lnl_points + z_se)) + high = np.exp(-np.exp(lnl_points - z_se)) + if self._kind == "cdf": + low, high = 1-high, 1-low + + return low, high + + +@dataclass +class ECDFResult: + """ Result object returned by `scipy.stats.ecdf` + + Attributes + ---------- + cdf : `~scipy.stats._result_classes.EmpiricalDistributionFunction` + An object representing the empirical cumulative distribution function. + sf : `~scipy.stats._result_classes.EmpiricalDistributionFunction` + An object representing the complement of the empirical cumulative + distribution function. + """ + cdf: EmpiricalDistributionFunction + sf: EmpiricalDistributionFunction + + def __init__(self, q, cdf, sf, n, d): + self.cdf = EmpiricalDistributionFunction(q, cdf, n, d, "cdf") + self.sf = EmpiricalDistributionFunction(q, sf, n, d, "sf") + + +def _iv_CensoredData( + sample: "npt.ArrayLike | CensoredData", param_name: str = "sample" +) -> CensoredData: + """Attempt to convert `sample` to `CensoredData`.""" + if not isinstance(sample, CensoredData): + try: # takes care of input standardization/validation + sample = CensoredData(uncensored=sample) + except ValueError as e: + message = str(e).replace('uncensored', param_name) + raise type(e)(message) from e + return sample + + +@xp_capabilities(np_only=True) +def ecdf(sample: "npt.ArrayLike | CensoredData") -> ECDFResult: + """Empirical cumulative distribution function of a sample. + + The empirical cumulative distribution function (ECDF) is a step function + estimate of the CDF of the distribution underlying a sample. This function + returns objects representing both the empirical distribution function and + its complement, the empirical survival function. + + Parameters + ---------- + sample : 1D array_like or `scipy.stats.CensoredData` + Besides array_like, instances of `scipy.stats.CensoredData` containing + uncensored and right-censored observations are supported. Currently, + other instances of `scipy.stats.CensoredData` will result in a + ``NotImplementedError``. + + Returns + ------- + res : `~scipy.stats._result_classes.ECDFResult` + An object with the following attributes. + + cdf : `~scipy.stats._result_classes.EmpiricalDistributionFunction` + An object representing the empirical cumulative distribution + function. + sf : `~scipy.stats._result_classes.EmpiricalDistributionFunction` + An object representing the empirical survival function. + + The `cdf` and `sf` attributes themselves have the following attributes. + + quantiles : ndarray + The unique values in the sample that defines the empirical CDF/SF. + probabilities : ndarray + The point estimates of the probabilities corresponding with + `quantiles`. + + And the following methods: + + evaluate(x) : + Evaluate the CDF/SF at the argument. + + plot(ax) : + Plot the CDF/SF on the provided axes. + + confidence_interval(confidence_level=0.95) : + Compute the confidence interval around the CDF/SF at the values in + `quantiles`. + + Notes + ----- + When each observation of the sample is a precise measurement, the ECDF + steps up by ``1/len(sample)`` at each of the observations [1]_. + + When observations are lower bounds, upper bounds, or both upper and lower + bounds, the data is said to be "censored", and `sample` may be provided as + an instance of `scipy.stats.CensoredData`. + + For right-censored data, the ECDF is given by the Kaplan-Meier estimator + [2]_; other forms of censoring are not supported at this time. + + Confidence intervals are computed according to the Greenwood formula or the + more recent "Exponential Greenwood" formula as described in [4]_. + + References + ---------- + .. [1] Conover, William Jay. Practical nonparametric statistics. Vol. 350. + John Wiley & Sons, 1999. + + .. [2] Kaplan, Edward L., and Paul Meier. "Nonparametric estimation from + incomplete observations." Journal of the American statistical + association 53.282 (1958): 457-481. + + .. [3] Goel, Manish Kumar, Pardeep Khanna, and Jugal Kishore. + "Understanding survival analysis: Kaplan-Meier estimate." + International journal of Ayurveda research 1.4 (2010): 274. + + .. [4] Sawyer, Stanley. "The Greenwood and Exponential Greenwood Confidence + Intervals in Survival Analysis." + https://www.math.wustl.edu/~sawyer/handouts/greenwood.pdf + + Examples + -------- + **Uncensored Data** + + As in the example from [1]_ page 79, five boys were selected at random from + those in a single high school. Their one-mile run times were recorded as + follows. + + >>> sample = [6.23, 5.58, 7.06, 6.42, 5.20] # one-mile run times (minutes) + + The empirical distribution function, which approximates the distribution + function of one-mile run times of the population from which the boys were + sampled, is calculated as follows. + + >>> from scipy import stats + >>> res = stats.ecdf(sample) + >>> res.cdf.quantiles + array([5.2 , 5.58, 6.23, 6.42, 7.06]) + >>> res.cdf.probabilities + array([0.2, 0.4, 0.6, 0.8, 1. ]) + + To plot the result as a step function: + + >>> import matplotlib.pyplot as plt + >>> ax = plt.subplot() + >>> res.cdf.plot(ax) + >>> ax.set_xlabel('One-Mile Run Time (minutes)') + >>> ax.set_ylabel('Empirical CDF') + >>> plt.show() + + **Right-censored Data** + + As in the example from [1]_ page 91, the lives of ten car fanbelts were + tested. Five tests concluded because the fanbelt being tested broke, but + the remaining tests concluded for other reasons (e.g. the study ran out of + funding, but the fanbelt was still functional). The mileage driven + with the fanbelts were recorded as follows. + + >>> broken = [77, 47, 81, 56, 80] # in thousands of miles driven + >>> unbroken = [62, 60, 43, 71, 37] + + Precise survival times of the fanbelts that were still functional at the + end of the tests are unknown, but they are known to exceed the values + recorded in ``unbroken``. Therefore, these observations are said to be + "right-censored", and the data is represented using + `scipy.stats.CensoredData`. + + >>> sample = stats.CensoredData(uncensored=broken, right=unbroken) + + The empirical survival function is calculated as follows. + + >>> res = stats.ecdf(sample) + >>> res.sf.quantiles + array([37., 43., 47., 56., 60., 62., 71., 77., 80., 81.]) + >>> res.sf.probabilities + array([1. , 1. , 0.875, 0.75 , 0.75 , 0.75 , 0.75 , 0.5 , 0.25 , 0. ]) + + To plot the result as a step function: + + >>> ax = plt.subplot() + >>> res.sf.plot(ax) + >>> ax.set_xlabel('Fanbelt Survival Time (thousands of miles)') + >>> ax.set_ylabel('Empirical SF') + >>> plt.show() + + """ + sample = _iv_CensoredData(sample) + + if sample.num_censored() == 0: + res = _ecdf_uncensored(sample._uncensor()) + elif sample.num_censored() == sample._right.size: + res = _ecdf_right_censored(sample) + else: + # Support additional censoring options in follow-up PRs + message = ("Currently, only uncensored and right-censored data is " + "supported.") + raise NotImplementedError(message) + + t, cdf, sf, n, d = res + return ECDFResult(t, cdf, sf, n, d) + + +def _ecdf_uncensored(sample): + sample = np.sort(sample) + x, counts = np.unique(sample, return_counts=True) + + # [1].81 "the fraction of [observations] that are less than or equal to x + events = np.cumsum(counts) + n = sample.size + cdf = events / n + + # [1].89 "the relative frequency of the sample that exceeds x in value" + sf = 1 - cdf + + at_risk = np.concatenate(([n], n - events[:-1])) + return x, cdf, sf, at_risk, counts + + +def _ecdf_right_censored(sample): + # It is conventional to discuss right-censored data in terms of + # "survival time", "death", and "loss" (e.g. [2]). We'll use that + # terminology here. + # This implementation was influenced by the references cited and also + # https://www.youtube.com/watch?v=lxoWsVco_iM + # https://en.wikipedia.org/wiki/Kaplan%E2%80%93Meier_estimator + # In retrospect it is probably most easily compared against [3]. + # Ultimately, the data needs to be sorted, so this implementation is + # written to avoid a separate call to `unique` after sorting. In hope of + # better performance on large datasets, it also computes survival + # probabilities at unique times only rather than at each observation. + tod = sample._uncensored # time of "death" + tol = sample._right # time of "loss" + times = np.concatenate((tod, tol)) + died = np.asarray([1]*tod.size + [0]*tol.size) + + # sort by times + i = np.argsort(times) + times = times[i] + died = died[i] + at_risk = np.arange(times.size, 0, -1) + + # logical indices of unique times + j = np.diff(times, prepend=-np.inf, append=np.inf) > 0 + j_l = j[:-1] # first instances of unique times + j_r = j[1:] # last instances of unique times + + # get number at risk and deaths at each unique time + t = times[j_l] # unique times + n = at_risk[j_l] # number at risk at each unique time + cd = np.cumsum(died)[j_r] # cumulative deaths up to/including unique times + d = np.diff(cd, prepend=0) # deaths at each unique time + + # compute survival function + sf = np.cumprod((n - d) / n) + cdf = 1 - sf + return t, cdf, sf, n, d + + +@dataclass +class LogRankResult: + """Result object returned by `scipy.stats.logrank`. + + Attributes + ---------- + statistic : float ndarray + The computed statistic (defined below). Its magnitude is the + square root of the magnitude returned by most other logrank test + implementations. + pvalue : float ndarray + The computed p-value of the test. + """ + statistic: np.ndarray + pvalue: np.ndarray + + +@xp_capabilities(np_only=True) +def logrank( + x: "npt.ArrayLike | CensoredData", + y: "npt.ArrayLike | CensoredData", + alternative: Literal['two-sided', 'less', 'greater'] = "two-sided" +) -> LogRankResult: + r"""Compare the survival distributions of two samples via the logrank test. + + Parameters + ---------- + x, y : array_like or CensoredData + Samples to compare based on their empirical survival functions. + alternative : {'two-sided', 'less', 'greater'}, optional + Defines the alternative hypothesis. + + The null hypothesis is that the survival distributions of the two + groups, say *X* and *Y*, are identical. + + The following alternative hypotheses [4]_ are available (default is + 'two-sided'): + + * 'two-sided': the survival distributions of the two groups are not + identical. + * 'less': survival of group *X* is favored: the group *X* failure rate + function is less than the group *Y* failure rate function at some + times. + * 'greater': survival of group *Y* is favored: the group *X* failure + rate function is greater than the group *Y* failure rate function at + some times. + + Returns + ------- + res : `~scipy.stats._result_classes.LogRankResult` + An object containing attributes: + + statistic : float ndarray + The computed statistic (defined below). Its magnitude is the + square root of the magnitude returned by most other logrank test + implementations. + pvalue : float ndarray + The computed p-value of the test. + + See Also + -------- + scipy.stats.ecdf + + Notes + ----- + The logrank test [1]_ compares the observed number of events to + the expected number of events under the null hypothesis that the two + samples were drawn from the same distribution. The statistic is + + .. math:: + + Z_i = \frac{\sum_{j=1}^J(O_{i,j}-E_{i,j})}{\sqrt{\sum_{j=1}^J V_{i,j}}} + \rightarrow \mathcal{N}(0,1) + + where + + .. math:: + + E_{i,j} = O_j \frac{N_{i,j}}{N_j}, + \qquad + V_{i,j} = E_{i,j} \left(\frac{N_j-O_j}{N_j}\right) + \left(\frac{N_j-N_{i,j}}{N_j-1}\right), + + :math:`i` denotes the group (i.e. it may assume values :math:`x` or + :math:`y`, or it may be omitted to refer to the combined sample) + :math:`j` denotes the time (at which an event occurred), + :math:`N` is the number of subjects at risk just before an event occurred, + and :math:`O` is the observed number of events at that time. + + The ``statistic`` :math:`Z_x` returned by `logrank` is the (signed) square + root of the statistic returned by many other implementations. Under the + null hypothesis, :math:`Z_x**2` is asymptotically distributed according to + the chi-squared distribution with one degree of freedom. Consequently, + :math:`Z_x` is asymptotically distributed according to the standard normal + distribution. The advantage of using :math:`Z_x` is that the sign + information (i.e. whether the observed number of events tends to be less + than or greater than the number expected under the null hypothesis) is + preserved, allowing `scipy.stats.logrank` to offer one-sided alternative + hypotheses. + + References + ---------- + .. [1] Mantel N. "Evaluation of survival data and two new rank order + statistics arising in its consideration." + Cancer Chemotherapy Reports, 50(3):163-170, PMID: 5910392, 1966 + .. [2] Bland, Altman, "The logrank test", BMJ, 328:1073, + :doi:`10.1136/bmj.328.7447.1073`, 2004 + .. [3] "Logrank test", Wikipedia, + https://en.wikipedia.org/wiki/Logrank_test + .. [4] Brown, Mark. "On the choice of variance for the log rank test." + Biometrika 71.1 (1984): 65-74. + .. [5] Klein, John P., and Melvin L. Moeschberger. Survival analysis: + techniques for censored and truncated data. Vol. 1230. New York: + Springer, 2003. + + Examples + -------- + Reference [2]_ compared the survival times of patients with two different + types of recurrent malignant gliomas. The samples below record the time + (number of weeks) for which each patient participated in the study. The + `scipy.stats.CensoredData` class is used because the data is + right-censored: the uncensored observations correspond with observed deaths + whereas the censored observations correspond with the patient leaving the + study for another reason. + + >>> from scipy import stats + >>> x = stats.CensoredData( + ... uncensored=[6, 13, 21, 30, 37, 38, 49, 50, + ... 63, 79, 86, 98, 202, 219], + ... right=[31, 47, 80, 82, 82, 149] + ... ) + >>> y = stats.CensoredData( + ... uncensored=[10, 10, 12, 13, 14, 15, 16, 17, 18, 20, 24, 24, + ... 25, 28,30, 33, 35, 37, 40, 40, 46, 48, 76, 81, + ... 82, 91, 112, 181], + ... right=[34, 40, 70] + ... ) + + We can calculate and visualize the empirical survival functions + of both groups as follows. + + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> ax = plt.subplot() + >>> ecdf_x = stats.ecdf(x) + >>> ecdf_x.sf.plot(ax, label='Astrocytoma') + >>> ecdf_y = stats.ecdf(y) + >>> ecdf_y.sf.plot(ax, label='Glioblastoma') + >>> ax.set_xlabel('Time to death (weeks)') + >>> ax.set_ylabel('Empirical SF') + >>> plt.legend() + >>> plt.show() + + Visual inspection of the empirical survival functions suggests that the + survival times tend to be different between the two groups. To formally + assess whether the difference is significant at the 1% level, we use the + logrank test. + + >>> res = stats.logrank(x=x, y=y) + >>> res.statistic + -2.73799 + >>> res.pvalue + 0.00618 + + The p-value is less than 1%, so we can consider the data to be evidence + against the null hypothesis in favor of the alternative that there is a + difference between the two survival functions. + + """ + # Input validation. `alternative` IV handled in `_get_pvalue` below. + x = _iv_CensoredData(sample=x, param_name='x') + y = _iv_CensoredData(sample=y, param_name='y') + + # Combined sample. (Under H0, the two groups are identical.) + xy = CensoredData( + uncensored=np.concatenate((x._uncensored, y._uncensored)), + right=np.concatenate((x._right, y._right)) + ) + + # Extract data from the combined sample + res = ecdf(xy) + idx = res.sf._d.astype(bool) # indices of observed events + times_xy = res.sf.quantiles[idx] # unique times of observed events + at_risk_xy = res.sf._n[idx] # combined number of subjects at risk + deaths_xy = res.sf._d[idx] # combined number of events + + # Get the number at risk within each sample. + # First compute the number at risk in group X at each of the `times_xy`. + # Could use `interpolate_1d`, but this is more compact. + res_x = ecdf(x) + i = np.searchsorted(res_x.sf.quantiles, times_xy) + at_risk_x = np.append(res_x.sf._n, 0)[i] # 0 at risk after last time + # Subtract from the combined number at risk to get number at risk in Y + at_risk_y = at_risk_xy - at_risk_x + + # Compute the variance. + num = at_risk_x * at_risk_y * deaths_xy * (at_risk_xy - deaths_xy) + den = at_risk_xy**2 * (at_risk_xy - 1) + # Note: when `at_risk_xy == 1`, we would have `at_risk_xy - 1 == 0` in the + # numerator and denominator. Simplifying the fraction symbolically, we + # would always find the overall quotient to be zero, so don't compute it. + i = at_risk_xy > 1 + sum_var = np.sum(num[i]/den[i]) + + # Get the observed and expected number of deaths in group X + n_died_x = x._uncensored.size + sum_exp_deaths_x = np.sum(at_risk_x * (deaths_xy/at_risk_xy)) + + # Compute the statistic. This is the square root of that in references. + statistic = (n_died_x - sum_exp_deaths_x)/np.sqrt(sum_var) + + # Equivalent to chi2(df=1).sf(statistic**2) when alternative='two-sided' + norm = stats._stats_py._SimpleNormal() + pvalue = stats._stats_py._get_pvalue(statistic, norm, alternative, xp=np) + + return LogRankResult(statistic=statistic[()], pvalue=pvalue[()]) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_tukeylambda_stats.py b/venv/lib/python3.12/site-packages/scipy/stats/_tukeylambda_stats.py new file mode 100644 index 0000000000000000000000000000000000000000..d3cda54fa2801f7659419b3c9c68d15ed80fc13e --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_tukeylambda_stats.py @@ -0,0 +1,199 @@ +import numpy as np +from numpy import poly1d +from scipy.special import beta + + +# The following code was used to generate the Pade coefficients for the +# Tukey Lambda variance function. Version 0.17 of mpmath was used. +#--------------------------------------------------------------------------- +# import mpmath as mp +# +# mp.mp.dps = 60 +# +# one = mp.mpf(1) +# two = mp.mpf(2) +# +# def mpvar(lam): +# if lam == 0: +# v = mp.pi**2 / three +# else: +# v = (two / lam**2) * (one / (one + two*lam) - +# mp.beta(lam + one, lam + one)) +# return v +# +# t = mp.taylor(mpvar, 0, 8) +# p, q = mp.pade(t, 4, 4) +# print("p =", [mp.fp.mpf(c) for c in p]) +# print("q =", [mp.fp.mpf(c) for c in q]) +#--------------------------------------------------------------------------- + +# Pade coefficients for the Tukey Lambda variance function. +_tukeylambda_var_pc = [3.289868133696453, 0.7306125098871127, + -0.5370742306855439, 0.17292046290190008, + -0.02371146284628187] +_tukeylambda_var_qc = [1.0, 3.683605511659861, 4.184152498888124, + 1.7660926747377275, 0.2643989311168465] + +# numpy.poly1d instances for the numerator and denominator of the +# Pade approximation to the Tukey Lambda variance. +_tukeylambda_var_p = poly1d(_tukeylambda_var_pc[::-1]) +_tukeylambda_var_q = poly1d(_tukeylambda_var_qc[::-1]) + + +def tukeylambda_variance(lam): + """Variance of the Tukey Lambda distribution. + + Parameters + ---------- + lam : array_like + The lambda values at which to compute the variance. + + Returns + ------- + v : ndarray + The variance. For lam < -0.5, the variance is not defined, so + np.nan is returned. For lam = 0.5, np.inf is returned. + + Notes + ----- + In an interval around lambda=0, this function uses the [4,4] Pade + approximation to compute the variance. Otherwise it uses the standard + formula (https://en.wikipedia.org/wiki/Tukey_lambda_distribution). The + Pade approximation is used because the standard formula has a removable + discontinuity at lambda = 0, and does not produce accurate numerical + results near lambda = 0. + """ + lam = np.asarray(lam) + shp = lam.shape + lam = np.atleast_1d(lam).astype(np.float64) + + # For absolute values of lam less than threshold, use the Pade + # approximation. + threshold = 0.075 + + # Play games with masks to implement the conditional evaluation of + # the distribution. + # lambda < -0.5: var = nan + low_mask = lam < -0.5 + # lambda == -0.5: var = inf + neghalf_mask = lam == -0.5 + # abs(lambda) < threshold: use Pade approximation + small_mask = np.abs(lam) < threshold + # else the "regular" case: use the explicit formula. + reg_mask = ~(low_mask | neghalf_mask | small_mask) + + # Get the 'lam' values for the cases where they are needed. + small = lam[small_mask] + reg = lam[reg_mask] + + # Compute the function for each case. + v = np.empty_like(lam) + v[low_mask] = np.nan + v[neghalf_mask] = np.inf + if small.size > 0: + # Use the Pade approximation near lambda = 0. + v[small_mask] = _tukeylambda_var_p(small) / _tukeylambda_var_q(small) + if reg.size > 0: + v[reg_mask] = (2.0 / reg**2) * (1.0 / (1.0 + 2 * reg) - + beta(reg + 1, reg + 1)) + v = v.reshape(shp) + return v + + +# The following code was used to generate the Pade coefficients for the +# Tukey Lambda kurtosis function. Version 0.17 of mpmath was used. +#--------------------------------------------------------------------------- +# import mpmath as mp +# +# mp.mp.dps = 60 +# +# one = mp.mpf(1) +# two = mp.mpf(2) +# three = mp.mpf(3) +# four = mp.mpf(4) +# +# def mpkurt(lam): +# if lam == 0: +# k = mp.mpf(6)/5 +# else: +# numer = (one/(four*lam+one) - four*mp.beta(three*lam+one, lam+one) + +# three*mp.beta(two*lam+one, two*lam+one)) +# denom = two*(one/(two*lam+one) - mp.beta(lam+one,lam+one))**2 +# k = numer / denom - three +# return k +# +# # There is a bug in mpmath 0.17: when we use the 'method' keyword of the +# # taylor function and we request a degree 9 Taylor polynomial, we actually +# # get degree 8. +# t = mp.taylor(mpkurt, 0, 9, method='quad', radius=0.01) +# t = [mp.chop(c, tol=1e-15) for c in t] +# p, q = mp.pade(t, 4, 4) +# print("p =", [mp.fp.mpf(c) for c in p]) +# print("q =", [mp.fp.mpf(c) for c in q]) +#--------------------------------------------------------------------------- + +# Pade coefficients for the Tukey Lambda kurtosis function. +_tukeylambda_kurt_pc = [1.2, -5.853465139719495, -22.653447381131077, + 0.20601184383406815, 4.59796302262789] +_tukeylambda_kurt_qc = [1.0, 7.171149192233599, 12.96663094361842, + 0.43075235247853005, -2.789746758009912] + +# numpy.poly1d instances for the numerator and denominator of the +# Pade approximation to the Tukey Lambda kurtosis. +_tukeylambda_kurt_p = poly1d(_tukeylambda_kurt_pc[::-1]) +_tukeylambda_kurt_q = poly1d(_tukeylambda_kurt_qc[::-1]) + + +def tukeylambda_kurtosis(lam): + """Kurtosis of the Tukey Lambda distribution. + + Parameters + ---------- + lam : array_like + The lambda values at which to compute the variance. + + Returns + ------- + v : ndarray + The variance. For lam < -0.25, the variance is not defined, so + np.nan is returned. For lam = 0.25, np.inf is returned. + + """ + lam = np.asarray(lam) + shp = lam.shape + lam = np.atleast_1d(lam).astype(np.float64) + + # For absolute values of lam less than threshold, use the Pade + # approximation. + threshold = 0.055 + + # Use masks to implement the conditional evaluation of the kurtosis. + # lambda < -0.25: kurtosis = nan + low_mask = lam < -0.25 + # lambda == -0.25: kurtosis = inf + negqrtr_mask = lam == -0.25 + # lambda near 0: use Pade approximation + small_mask = np.abs(lam) < threshold + # else the "regular" case: use the explicit formula. + reg_mask = ~(low_mask | negqrtr_mask | small_mask) + + # Get the 'lam' values for the cases where they are needed. + small = lam[small_mask] + reg = lam[reg_mask] + + # Compute the function for each case. + k = np.empty_like(lam) + k[low_mask] = np.nan + k[negqrtr_mask] = np.inf + if small.size > 0: + k[small_mask] = _tukeylambda_kurt_p(small) / _tukeylambda_kurt_q(small) + if reg.size > 0: + numer = (1.0 / (4 * reg + 1) - 4 * beta(3 * reg + 1, reg + 1) + + 3 * beta(2 * reg + 1, 2 * reg + 1)) + denom = 2 * (1.0/(2 * reg + 1) - beta(reg + 1, reg + 1))**2 + k[reg_mask] = numer / denom - 3 + + # The return value will be a numpy array; resetting the shape ensures that + # if `lam` was a scalar, the return value is a 0-d array. + k = k.reshape(shp) + return k diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_unuran/__init__.py b/venv/lib/python3.12/site-packages/scipy/stats/_unuran/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_unuran/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/_unuran/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..98207cc972e668589fb5848bc3052ccd27c4138f Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/_unuran/__pycache__/__init__.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_unuran/unuran_wrapper.cpython-312-x86_64-linux-gnu.so b/venv/lib/python3.12/site-packages/scipy/stats/_unuran/unuran_wrapper.cpython-312-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..9d27c1c06ffe4f4f1662f9128b5518478755c235 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_unuran/unuran_wrapper.cpython-312-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6fc277c78c018ca5d7ef03d0ae46357e6af4aebc3137a3d0c005f9fe8c09f1b +size 1335008 diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_unuran/unuran_wrapper.pyi b/venv/lib/python3.12/site-packages/scipy/stats/_unuran/unuran_wrapper.pyi new file mode 100644 index 0000000000000000000000000000000000000000..1ef1bd443b7dbf6a1d673fdc976c04536606c8d3 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_unuran/unuran_wrapper.pyi @@ -0,0 +1,179 @@ +import numpy as np +from typing import (overload, NamedTuple, Protocol) +from collections.abc import Callable +import numpy.typing as npt +from scipy._lib._util import SeedType +import scipy.stats as stats + + +ArrayLike0D = bool | int | float | complex | str | bytes | np.generic + + +__all__: list[str] + + +class UNURANError(RuntimeError): + ... + + +class Method: + @overload + def rvs(self, size: None = ...) -> float | int: ... # type: ignore[overload-overlap] + @overload + def rvs(self, size: int | tuple[int, ...] = ...) -> np.ndarray: ... + def set_random_state(self, random_state: SeedType) -> None: ... + + +class TDRDist(Protocol): + @property + def pdf(self) -> Callable[..., float]: ... + @property + def dpdf(self) -> Callable[..., float]: ... + @property + def support(self) -> tuple[float, float]: ... + + +class TransformedDensityRejection(Method): + def __init__(self, + dist: TDRDist, + *, + mode: None | float = ..., + center: None | float = ..., + domain: None | tuple[float, float] = ..., + c: float = ..., + construction_points: int | npt.ArrayLike = ..., + use_dars: bool = ..., + max_squeeze_hat_ratio: float = ..., + random_state: SeedType = ...) -> None: ... + @property + def squeeze_hat_ratio(self) -> float: ... + @property + def squeeze_area(self) -> float: ... + @overload + def ppf_hat(self, u: ArrayLike0D) -> float: ... # type: ignore[overload-overlap] + @overload + def ppf_hat(self, u: npt.ArrayLike) -> np.ndarray: ... + + +class SROUDist(Protocol): + @property + def pdf(self) -> Callable[..., float]: ... + @property + def support(self) -> tuple[float, float]: ... + + +class SimpleRatioUniforms(Method): + def __init__(self, + dist: SROUDist, + *, + mode: None | float = ..., + pdf_area: float = ..., + domain: None | tuple[float, float] = ..., + cdf_at_mode: float = ..., + random_state: SeedType = ...) -> None: ... + + +class UError(NamedTuple): + max_error: float + mean_absolute_error: float + +class PINVDist(Protocol): + @property + def pdf(self) -> Callable[..., float]: ... + @property + def cdf(self) -> Callable[..., float]: ... + @property + def logpdf(self) -> Callable[..., float]: ... + + +class NumericalInversePolynomial(Method): + def __init__(self, + dist: PINVDist, + *, + mode: None | float = ..., + center: None | float = ..., + domain: None | tuple[float, float] = ..., + order: int = ..., + u_resolution: float = ..., + random_state: SeedType = ...) -> None: ... + @property + def intervals(self) -> int: ... + @overload + def ppf(self, u: ArrayLike0D) -> float: ... # type: ignore[overload-overlap] + @overload + def ppf(self, u: npt.ArrayLike) -> np.ndarray: ... + @overload + def cdf(self, x: ArrayLike0D) -> float: ... # type: ignore[overload-overlap] + @overload + def cdf(self, x: npt.ArrayLike) -> np.ndarray: ... + def u_error(self, sample_size: int = ...) -> UError: ... + def qrvs(self, + size: None | int | tuple[int, ...] = ..., + d: None | int = ..., + qmc_engine: None | stats.qmc.QMCEngine = ...) -> npt.ArrayLike: ... + + +class HINVDist(Protocol): + @property + def pdf(self) -> Callable[..., float]: ... + @property + def cdf(self) -> Callable[..., float]: ... + @property + def support(self) -> tuple[float, float]: ... + + +class NumericalInverseHermite(Method): + def __init__(self, + dist: HINVDist, + *, + domain: None | tuple[float, float] = ..., + order: int= ..., + u_resolution: float = ..., + construction_points: None | npt.ArrayLike = ..., + max_intervals: int = ..., + random_state: SeedType = ...) -> None: ... + @property + def intervals(self) -> int: ... + @overload + def ppf(self, u: ArrayLike0D) -> float: ... # type: ignore[overload-overlap] + @overload + def ppf(self, u: npt.ArrayLike) -> np.ndarray: ... + def qrvs(self, + size: None | int | tuple[int, ...] = ..., + d: None | int = ..., + qmc_engine: None | stats.qmc.QMCEngine = ...) -> npt.ArrayLike: ... + def u_error(self, sample_size: int = ...) -> UError: ... + + +class DAUDist(Protocol): + @property + def pmf(self) -> Callable[..., float]: ... + @property + def support(self) -> tuple[float, float]: ... + +class DiscreteAliasUrn(Method): + def __init__(self, + dist: npt.ArrayLike | DAUDist, + *, + domain: None | tuple[float, float] = ..., + urn_factor: float = ..., + random_state: SeedType = ...) -> None: ... + + +class DGTDist(Protocol): + @property + def pmf(self) -> Callable[..., float]: ... + @property + def support(self) -> tuple[float, float]: ... + +class DiscreteGuideTable(Method): + def __init__(self, + dist: npt.ArrayLike | DGTDist, + *, + domain: None | tuple[float, float] = ..., + guide_factor: float = ..., + random_state: SeedType = ...) -> None: ... + @overload + def ppf(self, u: ArrayLike0D) -> float: ... # type: ignore[overload-overlap] + @overload + def ppf(self, u: npt.ArrayLike) -> np.ndarray: ... diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_variation.py b/venv/lib/python3.12/site-packages/scipy/stats/_variation.py new file mode 100644 index 0000000000000000000000000000000000000000..c991a70158a226a1b0fb6a1a0fce6f1ec4a67400 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_variation.py @@ -0,0 +1,133 @@ +import warnings +import numpy as np + +from scipy._lib._array_api import ( + array_namespace, + xp_capabilities, + xp_device, + _count_nonmasked, + xp_promote, +) +import scipy._external.array_api_extra as xpx + +from ._axis_nan_policy import _axis_nan_policy_factory + + +@xp_capabilities(marray=True) +@_axis_nan_policy_factory( + lambda x: x, n_outputs=1, result_to_tuple=lambda x, _: (x,) +) +def variation(a, axis=0, nan_policy='propagate', ddof=0, *, keepdims=False): + """ + Compute the coefficient of variation. + + The coefficient of variation is the standard deviation divided by the + mean. This function is equivalent to:: + + np.std(x, axis=axis, ddof=ddof) / np.mean(x) + + The default for ``ddof`` is 0, but many definitions of the coefficient + of variation use the square root of the unbiased sample variance + for the sample standard deviation, which corresponds to ``ddof=1``. + + The function does not take the absolute value of the mean of the data, + so the return value is negative if the mean is negative. + + Parameters + ---------- + a : array_like + Input array. + axis : int or None, optional + Axis along which to calculate the coefficient of variation. + Default is 0. If None, compute over the whole array `a`. + nan_policy : {'propagate', 'raise', 'omit'}, optional + Defines how to handle when input contains ``nan``. + The following options are available: + + * 'propagate': return ``nan`` + * 'raise': raise an exception + * 'omit': perform the calculation with ``nan`` values omitted + + The default is 'propagate'. + ddof : int, optional + Gives the "Delta Degrees Of Freedom" used when computing the + standard deviation. The divisor used in the calculation of the + standard deviation is ``N - ddof``, where ``N`` is the number of + elements. `ddof` must be less than ``N``; if it isn't, the result + will be ``nan`` or ``inf``, depending on ``N`` and the values in + the array. By default `ddof` is zero for backwards compatibility, + but it is recommended to use ``ddof=1`` to ensure that the sample + standard deviation is computed as the square root of the unbiased + sample variance. + + Returns + ------- + variation : ndarray + The calculated variation along the requested axis. + + Notes + ----- + There are several edge cases that are handled without generating a + warning: + + * If both the mean and the standard deviation are zero, ``nan`` + is returned. + * If the mean is zero and the standard deviation is nonzero, ``inf`` + is returned. + * If the input has length zero (either because the array has zero + length, or all the input values are ``nan`` and ``nan_policy`` is + ``'omit'``), ``nan`` is returned. + * If the input contains ``inf``, ``nan`` is returned. + + References + ---------- + .. [1] Zwillinger, D. and Kokoska, S. (2000). CRC Standard + Probability and Statistics Tables and Formulae. Chapman & Hall: New + York. 2000. + + Examples + -------- + >>> import numpy as np + >>> from scipy.stats import variation + >>> variation([1, 2, 3, 4, 5], ddof=1) + 0.5270462766947299 + + Compute the variation along a given dimension of an array that contains + a few ``nan`` values: + + >>> x = np.array([[ 10.0, np.nan, 11.0, 19.0, 23.0, 29.0, 98.0], + ... [ 29.0, 30.0, 32.0, 33.0, 35.0, 56.0, 57.0], + ... [np.nan, np.nan, 12.0, 13.0, 16.0, 16.0, 17.0]]) + >>> variation(x, axis=1, ddof=1, nan_policy='omit') + array([1.05109361, 0.31428986, 0.146483 ]) + + """ + xp = array_namespace(a) + a = xp_promote(a, force_floating=True, xp=xp) + + # `nan_policy` and `keepdims` are handled by `_axis_nan_policy` + if axis is None: + a = xp.reshape(a, (-1,)) + axis = 0 + + n = xp.asarray(_count_nonmasked(a, axis=axis), dtype=a.dtype, device=xp_device(a)) + + with (np.errstate(divide='ignore', invalid='ignore'), warnings.catch_warnings()): + warnings.simplefilter("ignore") + mean_a = xp.mean(a, axis=axis) + std_a = xp.std(a, axis=axis) + correction = (n / (n - ddof))**0.5 # we may need uncorrected std below + result = std_a * correction / mean_a + + def special_case(std_a, mean_a): + # xref data-apis/array-api-extra#196 + mxp = array_namespace(std_a, mean_a) + # `_xp_inf` is a workaround for torch.copysign not accepting a scalar yet, + # xref data-apis/array-api-compat#271 + _xp_inf = mxp.asarray(mxp.inf, dtype=mean_a.dtype, device=xp_device(mean_a)) + return mxp.where(std_a > 0, mxp.copysign(_xp_inf, mean_a), mxp.nan) + + result = xpx.apply_where((ddof == n), (std_a, mean_a), + special_case, fill_value=result) + + return result[()] if result.ndim == 0 else result diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_warnings_errors.py b/venv/lib/python3.12/site-packages/scipy/stats/_warnings_errors.py new file mode 100644 index 0000000000000000000000000000000000000000..57bc8fcabd7a0f382960046cd253bbc74d4b76e4 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_warnings_errors.py @@ -0,0 +1,62 @@ +# Warnings + + +class DegenerateDataWarning(RuntimeWarning): + """Warns when data is degenerate and results may not be reliable. + + Parameters + ---------- + msg : str, optional + Warning message. + """ + def __init__(self, msg=None): + if msg is None: + msg = ("Degenerate data encountered; results may not be reliable.") + self.args = (msg,) + + +class ConstantInputWarning(DegenerateDataWarning): + """Warns when all values in data are exactly equal. + + Parameters + ---------- + msg : str, optional + Warning message. + """ + def __init__(self, msg=None): + if msg is None: + msg = ("All values in data are exactly equal; " + "results may not be reliable.") + self.args = (msg,) + + +class NearConstantInputWarning(DegenerateDataWarning): + """Warns when all values in data are nearly equal. + + Parameters + ---------- + msg : str, optional + Warning message. + """ + def __init__(self, msg=None): + if msg is None: + msg = ("All values in data are nearly equal; " + "results may not be reliable.") + self.args = (msg,) + + +# Errors + + +class FitError(RuntimeError): + """Represents an error condition when fitting a distribution to data. + + Parameters + ---------- + msg : str, optional + Error message. + """ + def __init__(self, msg=None): + if msg is None: + msg = ("An error occurred when fitting a distribution to data.") + self.args = (msg,) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/_wilcoxon.py b/venv/lib/python3.12/site-packages/scipy/stats/_wilcoxon.py new file mode 100644 index 0000000000000000000000000000000000000000..a4437ba01a80deb0a09d36b4f3fca54ae5da960e --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/_wilcoxon.py @@ -0,0 +1,284 @@ +import numpy as np + +from scipy import stats +from ._stats_py import _get_pvalue, _rankdata, _SimpleNormal +from . import _morestats +from ._axis_nan_policy import _broadcast_arrays +from ._hypotests import _get_wilcoxon_distr +from scipy._lib._util import _get_nan +from scipy._lib._array_api import (array_namespace, xp_promote, xp_size, is_jax, + is_marray, _count_nonmasked) +import scipy._external.array_api_extra as xpx + + +class WilcoxonDistribution: + + def __init__(self, n): + n = np.asarray(n).astype(int, copy=False) + self.n = n + self._dists = {ni: _get_wilcoxon_distr(ni) for ni in np.unique(n)} + + def _cdf1(self, k, n): + pmfs = self._dists[n] + return pmfs[:k + 1].sum() + + def _cdf(self, k, n): + return np.vectorize(self._cdf1, otypes=[float])(k, n) + + def _sf1(self, k, n): + pmfs = self._dists[n] + return pmfs[k:].sum() + + def _sf(self, k, n): + return np.vectorize(self._sf1, otypes=[float])(k, n) + + def mean(self): + return self.n * (self.n + 1) / 4 + + def _prep(self, k): + k = np.asarray(k).astype(int, copy=False) + mn = self.mean() + out = np.empty(k.shape, dtype=np.float64) + return k, mn, out + + def cdf(self, k): + k, mn, out = self._prep(k) + return xpx.apply_where( + k <= mn, (k, self.n), + self._cdf, + lambda k, n: 1 - self._sf(k+1, n))[()] + + def sf(self, k): + k, mn, out = self._prep(k) + return xpx.apply_where( + k <= mn, (k, self.n), + self._sf, + lambda k, n: 1 - self._cdf(k-1, n))[()] + + +def _wilcoxon_iv(x, y, zero_method, correction, alternative, method, axis): + xp = array_namespace(x, y) + x, y = xp_promote(x, y, force_floating=True, xp=xp) + + axis = np.asarray(axis)[()] # OK to use NumPy for input validation + message = "`axis` must be an integer." + if not np.issubdtype(axis.dtype, np.integer) or axis.ndim != 0: + raise ValueError(message) + axis = int(axis) + + message = '`axis` must be compatible with the shape(s) of `x` (and `y`)' + AxisError = getattr(np, 'AxisError', None) or np.exceptions.AxisError + try: + if y is None: + d = x + else: + x, y = _broadcast_arrays((x, y), axis=axis, xp=xp) + d = x - y + d = xp.moveaxis(d, axis, -1) + except AxisError as e: + raise AxisError(message) from e + + message = "`x` and `y` must have the same length along `axis`." + if y is not None and x.shape[axis] != y.shape[axis]: + raise ValueError(message) + + message = "`x` (and `y`, if provided) must be an array of real numbers." + if not xp.isdtype(d.dtype, "real floating"): + raise ValueError(message) + + zero_method = str(zero_method).lower() + zero_methods = {"wilcox", "pratt", "zsplit"} + message = f"`zero_method` must be one of {zero_methods}." + if zero_method not in zero_methods: + raise ValueError(message) + + corrections = {True, False} + message = f"`correction` must be one of {corrections}." + if correction not in corrections: + raise ValueError(message) + + alternative = str(alternative).lower() + alternatives = {"two-sided", "less", "greater"} + message = f"`alternative` must be one of {alternatives}." + if alternative not in alternatives: + raise ValueError(message) + + if not isinstance(method, stats.PermutationMethod): + methods = {"auto", "asymptotic", "exact"} + message = (f"`method` must be one of {methods} or " + "an instance of `stats.PermutationMethod`.") + if method not in methods: + raise ValueError(message) + output_z = True if method == 'asymptotic' else False + + if is_jax(xp) and str(method) in {"auto", "exact"}: + message = ("When using `wilcoxon` with `jax.numpy` arrays, `method` must be " + "either 'asymptotic' or an instance of `stats.PermutationMethod`.") + raise ValueError(message) + + if is_marray(xp) and (method != "asymptotic" or zero_method != 'zsplit'): + message = ("Only `method='asymptotic'`/`zero_method='zsplit'` is compatible " + "with MArrays.") + raise ValueError(message) + + # For small samples, we decide later whether to perform an exact test or a + # permutation test. The reason is that the presence of ties is not + # known at the input validation stage. + n_zero = xp.count_nonzero(d == 0, axis=None) + if method == "auto" and d.shape[-1] > 50: + method = "asymptotic" + + return d, zero_method, correction, alternative, method, axis, output_z, n_zero, xp + + +def _wilcoxon_statistic(d, method, zero_method='wilcox', *, xp): + dtype = d.dtype + i_zeros = (d == 0) + + if zero_method == 'wilcox': + # Wilcoxon's method for treating zeros was to remove them from + # the calculation. We do this by replacing 0s with NaNs, which + # are ignored anyway. + # Copy required for array-api-strict. See data-apis/array-api-extra#506. + d = xpx.at(d)[i_zeros].set(xp.nan, copy=True) + + i_nan = xp.isnan(d) + n_nan = xp.astype(xp.count_nonzero(i_nan, axis=-1), dtype) + count = _count_nonmasked(d, axis=-1) - n_nan + + r, _, t = _rankdata(xp.abs(d), 'average', return_ties=True, xp=xp) + r, t = xp.astype(r, dtype, copy=False), xp.astype(t, dtype, copy=False) + + r_plus = xp.sum(xp.astype(d > 0, dtype) * r, axis=-1) + r_minus = xp.sum(xp.astype(d < 0, dtype) * r, axis=-1) + + has_ties = xp.any(t == 0) + + if zero_method == "zsplit": + # The "zero-split" method for treating zeros is to add half their contribution + # to r_plus and half to r_minus. + # See gh-2263 for the origin of this method. + r_zero_2 = xp.sum(xp.astype(i_zeros, dtype) * r, axis=-1) / 2 + r_plus = xpx.at(r_plus)[...].add(r_zero_2) + r_minus = xpx.at(r_minus)[...].add(r_zero_2) + + mn = count * (count + 1.) * 0.25 + se = count * (count + 1.) * (2. * count + 1.) + + if zero_method == "pratt": + # Pratt's method for treating zeros was just to modify the z-statistic. + + # normal approximation needs to be adjusted, see Cureton (1967) + n_zero = xp.astype(xp.count_nonzero(i_zeros, axis=-1), dtype) + mn = xpx.at(mn)[...].subtract(n_zero * (n_zero + 1.) * 0.25) + se = xpx.at(se)[...].subtract(n_zero * (n_zero + 1.) * (2. * n_zero + 1.)) + + # zeros are not to be included in tie-correction. + # any tie counts corresponding with zeros are in the 0th column + # t[xp.any(i_zeros, axis=-1), 0] = 0 + t_i_zeros = xp.zeros_like(i_zeros) + t_i_zeros = xpx.at(t_i_zeros)[..., 0].set(xp.any(i_zeros, axis=-1)) + t = xpx.at(t)[t_i_zeros].set(0.) + + tie_correct = xp.sum(t**3 - t, axis=-1) + se = xp.sqrt((se - tie_correct/2) / 24) + + # se = 0 means that no non-zero values are left in d. we only need z + # if method is asymptotic. however, if method="auto", the switch to + # asymptotic might only happen after the statistic is calculated, so z + # needs to be computed. in all other cases, avoid division by zero warning + # (z is not needed anyways) + if method in ["asymptotic", "auto"]: + z = (r_plus - mn) / se + else: + z = xp.nan + + return r_plus, r_minus, se, z, count, has_ties + + +def _correction_sign(z, alternative, xp): + if alternative == 'greater': + return 1 + elif alternative == 'less': + return -1 + else: + return xp.sign(z) + + +def _wilcoxon_nd(x, y=None, zero_method='wilcox', correction=True, + alternative='two-sided', method='auto', axis=0): + + temp = _wilcoxon_iv(x, y, zero_method, correction, alternative, method, axis) + d, zero_method, correction, alternative, method, axis, output_z, n_zero, xp = temp + + if xp_size(d) == 0: + NaN = _get_nan(d, xp=xp) + res = _morestats.WilcoxonResult(statistic=NaN, pvalue=NaN) + if method == 'asymptotic': + res.zstatistic = NaN + return res + + r_plus, r_minus, se, z, count, has_ties = _wilcoxon_statistic( + d, method, zero_method, xp=xp + ) + + # we only know if there are ties after computing the statistic and not + # at the input validation stage. if the original method was auto and + # the decision was to use an exact test, we override this to + # a permutation test now (since method='exact' is not exact in the + # presence of ties) + if method == "auto": + if not (has_ties or n_zero > 0): + method = "exact" + elif d.shape[-1] <= 13: + # the possible outcomes to be simulated by the permutation test + # are 2**n, where n is the sample size. + # if n <= 13, the p-value is deterministic since 2**13 is less + # than 9999, the default number of n_resamples + method = stats.PermutationMethod() + else: + # if there are ties and the sample size is too large to + # run a deterministic permutation test, fall back to asymptotic + method = "asymptotic" + + if method == 'asymptotic': + if correction: + sign = _correction_sign(z, alternative, xp=xp) + z = xpx.at(z)[...].subtract(sign * 0.5 / se) + p = _get_pvalue(z, _SimpleNormal(), alternative, xp=xp) + elif method == 'exact': + dist = WilcoxonDistribution(count) + # The null distribution in `dist` is exact only if there are no ties + # or zeros. If there are ties or zeros, the statistic can be non- + # integral, but the null distribution is only defined for integral + # values of the statistic. Therefore, we're conservative: round + # non-integral statistic up before computing CDF and down before + # computing SF. This preserves symmetry w.r.t. alternatives and + # order of the input arguments. See gh-19872. + r_plus_np = np.asarray(r_plus) + if alternative == 'less': + p = dist.cdf(np.ceil(r_plus_np)) + elif alternative == 'greater': + p = dist.sf(np.floor(r_plus_np)) + else: + p = 2 * np.minimum(dist.sf(np.floor(r_plus_np)), + dist.cdf(np.ceil(r_plus_np))) + p = np.clip(p, 0, 1) + p = xp.asarray(p, dtype=d.dtype) + else: # `PermutationMethod` instance (already validated) + p = stats.permutation_test( + # permutation_test always uses `axis=-1` as `_wilcoxon_statistic` assumes + (d,), lambda d, axis: _wilcoxon_statistic(d, method, zero_method, xp=xp)[0], + permutation_type='samples', **method._asdict(), + alternative=alternative, axis=-1).pvalue + + # for backward compatibility... + statistic = xp.minimum(r_plus, r_minus) if alternative=='two-sided' else r_plus + z = -xp.abs(z) if (alternative == 'two-sided' and method == 'asymptotic') else z + + statistic = statistic[()] if statistic.ndim == 0 else statistic + p = p[()] if p.ndim == 0 else p + res = _morestats.WilcoxonResult(statistic=statistic, pvalue=p) + if output_z: + res.zstatistic = z[()] if z.ndim == 0 else z + return res diff --git a/venv/lib/python3.12/site-packages/scipy/stats/biasedurn.py b/venv/lib/python3.12/site-packages/scipy/stats/biasedurn.py new file mode 100644 index 0000000000000000000000000000000000000000..2b1c9f1cf2b3b39acdcaeda97a90e8c11c589d89 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/biasedurn.py @@ -0,0 +1,16 @@ +# This file is not meant for public use and will be removed in SciPy v2.0.0. + +from scipy._lib.deprecation import _sub_module_deprecation + + +__all__: list[str] = [] + + +def __dir__(): + return __all__ + + +def __getattr__(name): + return _sub_module_deprecation(sub_package="stats", module="biasedurn", + private_modules=["_biasedurn"], all=__all__, + attribute=name) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/contingency.py b/venv/lib/python3.12/site-packages/scipy/stats/contingency.py new file mode 100644 index 0000000000000000000000000000000000000000..c45112f3f1d7ae346681732d75943c75e6ef3fad --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/contingency.py @@ -0,0 +1,526 @@ +""" +Contingency table functions (:mod:`scipy.stats.contingency`) +============================================================ + +Functions for creating and analyzing contingency tables. + +.. currentmodule:: scipy.stats.contingency + +.. autosummary:: + :toctree: generated/ + + chi2_contingency + relative_risk + odds_ratio + crosstab + association + + expected_freq + margins + +""" + + +from functools import reduce +import math +import numpy as np +from ._stats_py import power_divergence, _untabulate +from ._relative_risk import relative_risk +from ._crosstab import crosstab +from ._odds_ratio import odds_ratio +from scipy._lib._array_api import xp_capabilities +from scipy._lib._bunch import _make_tuple_bunch +from scipy import stats + + +__all__ = ['margins', 'expected_freq', 'chi2_contingency', 'crosstab', + 'association', 'relative_risk', 'odds_ratio'] + + +@xp_capabilities(np_only=True) +def margins(a): + """Return a list of the marginal sums of the array `a`. + + Parameters + ---------- + a : ndarray + The array for which to compute the marginal sums. + + Returns + ------- + margsums : list of ndarrays + A list of length `a.ndim`. `margsums[k]` is the result + of summing `a` over all axes except `k`; it has the same + number of dimensions as `a`, but the length of each axis + except axis `k` will be 1. + + Examples + -------- + >>> import numpy as np + >>> from scipy.stats.contingency import margins + + >>> a = np.arange(12).reshape(2, 6) + >>> a + array([[ 0, 1, 2, 3, 4, 5], + [ 6, 7, 8, 9, 10, 11]]) + >>> m0, m1 = margins(a) + >>> m0 + array([[15], + [51]]) + >>> m1 + array([[ 6, 8, 10, 12, 14, 16]]) + + >>> b = np.arange(24).reshape(2,3,4) + >>> m0, m1, m2 = margins(b) + >>> m0 + array([[[ 66]], + [[210]]]) + >>> m1 + array([[[ 60], + [ 92], + [124]]]) + >>> m2 + array([[[60, 66, 72, 78]]]) + """ + margsums = [] + ranged = list(range(a.ndim)) + for k in ranged: + marg = np.apply_over_axes(np.sum, a, [j for j in ranged if j != k]) + margsums.append(marg) + return margsums + + +@xp_capabilities(np_only=True) +def expected_freq(observed): + """ + Compute the expected frequencies from a contingency table. + + Given an n-dimensional contingency table of observed frequencies, + compute the expected frequencies for the table based on the marginal + sums under the assumption that the groups associated with each + dimension are independent. + + Parameters + ---------- + observed : array_like + The table of observed frequencies. (While this function can handle + a 1-D array, that case is trivial. Generally `observed` is at + least 2-D.) + + Returns + ------- + expected : ndarray of float64 + The expected frequencies, based on the marginal sums of the table. + Same shape as `observed`. + + Examples + -------- + >>> import numpy as np + >>> from scipy.stats.contingency import expected_freq + >>> observed = np.array([[10, 10, 20],[20, 20, 20]]) + >>> expected_freq(observed) + array([[ 12., 12., 16.], + [ 18., 18., 24.]]) + + """ + # Typically `observed` is an integer array. If `observed` has a large + # number of dimensions or holds large values, some of the following + # computations may overflow, so we first switch to floating point. + observed = np.asarray(observed, dtype=np.float64) + + # Create a list of the marginal sums. + margsums = margins(observed) + + # Create the array of expected frequencies. The shapes of the + # marginal sums returned by apply_over_axes() are just what we + # need for broadcasting in the following product. + d = observed.ndim + expected = reduce(np.multiply, margsums) / observed.sum() ** (d - 1) + return expected + + +Chi2ContingencyResult = _make_tuple_bunch( + 'Chi2ContingencyResult', + ['statistic', 'pvalue', 'dof', 'expected_freq'], [] +) + + +@xp_capabilities(np_only=True) +def chi2_contingency(observed, correction=True, lambda_=None, *, method=None): + """Chi-square test of independence of variables in a contingency table. + + This function computes the chi-square statistic and p-value for the + hypothesis test of independence of the observed frequencies in the + contingency table [1]_ `observed`. The expected frequencies are computed + based on the marginal sums under the assumption of independence; see + `scipy.stats.contingency.expected_freq`. The number of degrees of + freedom is (expressed using numpy functions and attributes):: + + dof = observed.size - sum(observed.shape) + observed.ndim - 1 + + + Parameters + ---------- + observed : array_like + The contingency table. The table contains the observed frequencies + (i.e. number of occurrences) in each category. In the two-dimensional + case, the table is often described as an "R x C table". + correction : bool, optional + If True, *and* the degrees of freedom is 1, apply Yates' correction + for continuity. The effect of the correction is to adjust each + observed value by 0.5 towards the corresponding expected value. + lambda_ : float or str, optional + By default, the statistic computed in this test is Pearson's + chi-squared statistic [2]_. `lambda_` allows a statistic from the + Cressie-Read power divergence family [3]_ to be used instead. See + `scipy.stats.power_divergence` for details. + method : ResamplingMethod, optional + Defines the method used to compute the p-value. Compatible only with + ``correction=False``, default `lambda_`, and two-way tables. + If `method` is an instance of `scipy.stats.PermutationMethod` or + `scipy.stats.MonteCarloMethod`, the p-value is computed using + `scipy.stats.permutation_test`/`scipy.stats.monte_carlo_test` with the + provided configuration options and other appropriate settings. + Otherwise, the p-value is computed as documented in the notes. + Note that if `method` is an instance of `scipy.stats.MonteCarloMethod`, the + ``rvs`` attribute must be left unspecified; Monte Carlo samples are always drawn + using the ``rvs`` method of `scipy.stats.random_table`. + + .. versionadded:: 1.15.0 + + + Returns + ------- + res : Chi2ContingencyResult + An object containing attributes: + + statistic : float + The test statistic. + pvalue : float + The p-value of the test. + dof : int + The degrees of freedom. NaN if `method` is not ``None``. + expected_freq : ndarray, same shape as `observed` + The expected frequencies, based on the marginal sums of the table. + + See Also + -------- + scipy.stats.contingency.expected_freq + scipy.stats.fisher_exact + scipy.stats.chisquare + scipy.stats.power_divergence + scipy.stats.barnard_exact + scipy.stats.boschloo_exact + :ref:`hypothesis_chi2_contingency` : Extended example + + Notes + ----- + An often quoted guideline for the validity of this calculation is that + the test should be used only if the observed and expected frequencies + in each cell are at least 5. + + This is a test for the independence of different categories of a + population. The test is only meaningful when the dimension of + `observed` is two or more. Applying the test to a one-dimensional + table will always result in `expected` equal to `observed` and a + chi-square statistic equal to 0. + + This function does not handle masked arrays, because the calculation + does not make sense with missing values. + + Like `scipy.stats.chisquare`, this function computes a chi-square + statistic; the convenience this function provides is to figure out the + expected frequencies and degrees of freedom from the given contingency + table. If these were already known, and if the Yates' correction was not + required, one could use `scipy.stats.chisquare`. That is, if one calls:: + + res = chi2_contingency(obs, correction=False) + + then the following is true:: + + (res.statistic, res.pvalue) == stats.chisquare(obs.ravel(), + f_exp=ex.ravel(), + ddof=obs.size - 1 - dof) + + The `lambda_` argument was added in version 0.13.0 of scipy. + + References + ---------- + .. [1] "Contingency table", + https://en.wikipedia.org/wiki/Contingency_table + .. [2] "Pearson's chi-squared test", + https://en.wikipedia.org/wiki/Pearson%27s_chi-squared_test + .. [3] Cressie, N. and Read, T. R. C., "Multinomial Goodness-of-Fit + Tests", J. Royal Stat. Soc. Series B, Vol. 46, No. 3 (1984), + pp. 440-464. + + Examples + -------- + A two-way example (2 x 3): + + >>> import numpy as np + >>> from scipy.stats import chi2_contingency + >>> obs = np.array([[10, 10, 20], [20, 20, 20]]) + >>> res = chi2_contingency(obs) + >>> res.statistic + 2.7777777777777777 + >>> res.pvalue + 0.24935220877729619 + >>> res.dof + 2 + >>> res.expected_freq + array([[ 12., 12., 16.], + [ 18., 18., 24.]]) + + Perform the test using the log-likelihood ratio (i.e. the "G-test") + instead of Pearson's chi-squared statistic. + + >>> res = chi2_contingency(obs, lambda_="log-likelihood") + >>> res.statistic + 2.7688587616781319 + >>> res.pvalue + 0.25046668010954165 + + A four-way example (2 x 2 x 2 x 2): + + >>> obs = np.array( + ... [[[[12, 17], + ... [11, 16]], + ... [[11, 12], + ... [15, 16]]], + ... [[[23, 15], + ... [30, 22]], + ... [[14, 17], + ... [15, 16]]]]) + >>> res = chi2_contingency(obs) + >>> res.statistic + 8.7584514426741897 + >>> res.pvalue + 0.64417725029295503 + + When the sum of the elements in a two-way table is small, the p-value + produced by the default asymptotic approximation may be inaccurate. + Consider passing a `PermutationMethod` or `MonteCarloMethod` as the + `method` parameter with `correction=False`. + + >>> from scipy.stats import PermutationMethod + >>> obs = np.asarray([[12, 3], + ... [17, 16]]) + >>> res = chi2_contingency(obs, correction=False) + >>> ref = chi2_contingency(obs, correction=False, method=PermutationMethod()) + >>> res.pvalue, ref.pvalue + (0.0614122539870913, 0.1074) # may vary + + For a more detailed example, see :ref:`hypothesis_chi2_contingency`. + + """ + observed = np.asarray(observed) + if np.any(observed < 0): + raise ValueError("All values in `observed` must be nonnegative.") + if observed.size == 0: + raise ValueError("No data; `observed` has size 0.") + + expected = expected_freq(observed) + if np.any(expected == 0): + # Include one of the positions where expected is zero in + # the exception message. + zeropos = list(zip(*np.nonzero(expected == 0)))[0] + raise ValueError("The internally computed table of expected " + f"frequencies has a zero element at {zeropos}.") + + if method is not None: + return _chi2_resampling_methods(observed, expected, correction, lambda_, method) + + # The degrees of freedom + dof = expected.size - sum(expected.shape) + expected.ndim - 1 + + if dof == 0: + # Degenerate case; this occurs when `observed` is 1D (or, more + # generally, when it has only one nontrivial dimension). In this + # case, we also have observed == expected, so chi2 is 0. + chi2 = 0.0 + p = 1.0 + else: + if dof == 1 and correction: + # Adjust `observed` according to Yates' correction for continuity. + # Magnitude of correction no bigger than difference; see gh-13875 + diff = expected - observed + direction = np.sign(diff) + magnitude = np.minimum(0.5, np.abs(diff)) + observed = observed + magnitude * direction + + chi2, p = power_divergence(observed, expected, + ddof=observed.size - 1 - dof, axis=None, + lambda_=lambda_) + + return Chi2ContingencyResult(chi2, p, dof, expected) + + +def _chi2_resampling_methods(observed, expected, correction, lambda_, method): + + if observed.ndim != 2: + message = 'Use of `method` is only compatible with two-way tables.' + raise ValueError(message) + + if correction: + message = f'`{correction=}` is not compatible with `{method=}.`' + raise ValueError(message) + + if lambda_ is not None: + message = f'`{lambda_=}` is not compatible with `{method=}.`' + raise ValueError(message) + + if isinstance(method, stats.PermutationMethod): + res = _chi2_permutation_method(observed, expected, method) + elif isinstance(method, stats.MonteCarloMethod): + res = _chi2_monte_carlo_method(observed, expected, method) + else: + message = (f'`{method=}` not recognized; if provided, `method` must be an ' + 'instance of `PermutationMethod` or `MonteCarloMethod`.') + raise ValueError(message) + + return Chi2ContingencyResult(res.statistic, res.pvalue, np.nan, expected) + + +def _chi2_permutation_method(observed, expected, method): + x, y = _untabulate(observed) + # `permutation_test` with `permutation_type='pairings' permutes the order of `x`, + # which pairs observations in `x` with different observations in `y`. + def statistic(x): + # crosstab the resample and compute the statistic + table = crosstab(x, y)[1] + return np.sum((table - expected)**2/expected) + + return stats.permutation_test((x,), statistic, permutation_type='pairings', + alternative='greater', **method._asdict()) + + +def _chi2_monte_carlo_method(observed, expected, method): + method = method._asdict() + + if method.pop('rvs', None) is not None: + message = ('If the `method` argument of `chi2_contingency` is an ' + 'instance of `MonteCarloMethod`, its `rvs` attribute ' + 'must be unspecified. Use the `MonteCarloMethod` `rng` argument ' + 'to control the random state.') + raise ValueError(message) + rng = np.random.default_rng(method.pop('rng', None)) + + # `random_table.rvs` produces random contingency tables with the given marginals + # under the null hypothesis of independence + rowsums, colsums = stats.contingency.margins(observed) + X = stats.random_table(rowsums.ravel(), colsums.ravel(), seed=rng) + def rvs(size): + n_resamples = size[0] + return X.rvs(size=n_resamples).reshape(size) + + expected = expected.ravel() + def statistic(table, axis): + return np.sum((table - expected)**2/expected, axis=axis) + + return stats.monte_carlo_test(observed.ravel(), rvs, statistic, + alternative='greater', **method) + + +@xp_capabilities(np_only=True) +def association(observed, method="cramer", correction=False, lambda_=None): + """Calculates degree of association between two nominal variables. + + The function provides the option for computing one of three measures of + association between two nominal variables from the data given in a 2d + contingency table: Tschuprow's T, Pearson's Contingency Coefficient + and Cramer's V. + + Parameters + ---------- + observed : array-like + The array of observed values + method : {"cramer", "tschuprow", "pearson"} (default = "cramer") + The association test statistic. + correction : bool, optional + Inherited from `scipy.stats.contingency.chi2_contingency()` + lambda_ : float or str, optional + Inherited from `scipy.stats.contingency.chi2_contingency()` + + Returns + ------- + statistic : float + Value of the test statistic + + Notes + ----- + Cramer's V, Tschuprow's T and Pearson's Contingency Coefficient, all + measure the degree to which two nominal or ordinal variables are related, + or the level of their association. This differs from correlation, although + many often mistakenly consider them equivalent. Correlation measures in + what way two variables are related, whereas, association measures how + related the variables are. As such, association does not subsume + independent variables, and is rather a test of independence. A value of + 1.0 indicates perfect association, and 0.0 means the variables have no + association. + + Both the Cramer's V and Tschuprow's T are extensions of the phi + coefficient. Moreover, due to the close relationship between the + Cramer's V and Tschuprow's T the returned values can often be similar + or even equivalent. They are likely to diverge more as the array shape + diverges from a 2x2. + + References + ---------- + .. [1] "Tschuprow's T", + https://en.wikipedia.org/wiki/Tschuprow's_T + .. [2] Tschuprow, A. A. (1939) + Principles of the Mathematical Theory of Correlation; + translated by M. Kantorowitsch. W. Hodge & Co. + .. [3] "Cramer's V", https://en.wikipedia.org/wiki/Cramer's_V + .. [4] "Nominal Association: Phi and Cramer's V", + http://www.people.vcu.edu/~pdattalo/702SuppRead/MeasAssoc/NominalAssoc.html + .. [5] Gingrich, Paul, "Association Between Variables", + http://uregina.ca/~gingrich/ch11a.pdf + + Examples + -------- + An example with a 4x2 contingency table: + + >>> import numpy as np + >>> from scipy.stats.contingency import association + >>> obs4x2 = np.array([[100, 150], [203, 322], [420, 700], [320, 210]]) + + Pearson's contingency coefficient + + >>> association(obs4x2, method="pearson") + 0.18303298140595667 + + Cramer's V + + >>> association(obs4x2, method="cramer") + 0.18617813077483678 + + Tschuprow's T + + >>> association(obs4x2, method="tschuprow") + 0.14146478765062995 + """ + arr = np.asarray(observed) + if not np.issubdtype(arr.dtype, np.integer): + raise ValueError("`observed` must be an integer array.") + + if len(arr.shape) != 2: + raise ValueError("method only accepts 2d arrays") + + chi2_stat = chi2_contingency(arr, correction=correction, + lambda_=lambda_) + + phi2 = chi2_stat.statistic / arr.sum() + n_rows, n_cols = arr.shape + if method == "cramer": + value = phi2 / min(n_cols - 1, n_rows - 1) + elif method == "tschuprow": + value = phi2 / math.sqrt((n_rows - 1) * (n_cols - 1)) + elif method == 'pearson': + value = phi2 / (1 + phi2) + else: + raise ValueError("Invalid argument value: 'method' argument must " + "be 'cramer', 'tschuprow', or 'pearson'") + + return math.sqrt(value) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/distributions.py b/venv/lib/python3.12/site-packages/scipy/stats/distributions.py new file mode 100644 index 0000000000000000000000000000000000000000..ac9c37aa98c9545b2616c8d32e8f676d8d49289e --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/distributions.py @@ -0,0 +1,24 @@ +# +# Author: Travis Oliphant 2002-2011 with contributions from +# SciPy Developers 2004-2011 +# +# NOTE: To look at history using `git blame`, use `git blame -M -C -C` +# instead of `git blame -Lxxx,+x`. +# +from ._distn_infrastructure import (rv_discrete, rv_continuous, rv_frozen) # noqa: F401 + +from . import _continuous_distns +from . import _discrete_distns + +from ._continuous_distns import * # noqa: F403 +from ._levy_stable import levy_stable +from ._discrete_distns import * # noqa: F403 +from ._entropy import entropy + +# For backwards compatibility e.g. pymc expects distributions.__all__. +__all__ = ['rv_discrete', 'rv_continuous', 'rv_histogram', 'entropy'] # noqa: F405 + +# Add only the distribution names, not the *_gen names. +__all__ += _continuous_distns._distn_names +__all__ += ['levy_stable'] +__all__ += _discrete_distns._distn_names diff --git a/venv/lib/python3.12/site-packages/scipy/stats/kde.py b/venv/lib/python3.12/site-packages/scipy/stats/kde.py new file mode 100644 index 0000000000000000000000000000000000000000..4401da5a30f4452ab394232d3928493d0e3b77ec --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/kde.py @@ -0,0 +1,18 @@ +# This file is not meant for public use and will be removed in SciPy v2.0.0. +# Use the `scipy.stats` namespace for importing the functions +# included below. + +from scipy._lib.deprecation import _sub_module_deprecation + + +__all__ = ["gaussian_kde"] # noqa: F822 + + +def __dir__(): + return __all__ + + +def __getattr__(name): + return _sub_module_deprecation(sub_package="stats", module="kde", + private_modules=["_kde"], all=__all__, + attribute=name) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/morestats.py b/venv/lib/python3.12/site-packages/scipy/stats/morestats.py new file mode 100644 index 0000000000000000000000000000000000000000..76040ea0ca5251cabbdda7fb7936af4b281b3212 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/morestats.py @@ -0,0 +1,27 @@ +# This file is not meant for public use and will be removed in SciPy v2.0.0. +# Use the `scipy.stats` namespace for importing the functions +# included below. + +from scipy._lib.deprecation import _sub_module_deprecation + + +__all__ = [ # noqa: F822 + 'mvsdist', + 'bayes_mvs', 'kstat', 'kstatvar', 'probplot', 'ppcc_max', 'ppcc_plot', + 'boxcox_llf', 'boxcox', 'boxcox_normmax', 'boxcox_normplot', + 'shapiro', 'anderson', 'ansari', 'bartlett', 'levene', + 'fligner', 'mood', 'wilcoxon', 'median_test', + 'circmean', 'circvar', 'circstd', 'anderson_ksamp', + 'yeojohnson_llf', 'yeojohnson', 'yeojohnson_normmax', + 'yeojohnson_normplot', 'chi2_contingency', 'distributions', +] + + +def __dir__(): + return __all__ + + +def __getattr__(name): + return _sub_module_deprecation(sub_package="stats", module="morestats", + private_modules=["_morestats"], all=__all__, + attribute=name) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/mstats.py b/venv/lib/python3.12/site-packages/scipy/stats/mstats.py new file mode 100644 index 0000000000000000000000000000000000000000..88016af71803dc5c4ebadba168f22cdcd8273dbb --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/mstats.py @@ -0,0 +1,140 @@ +""" +=================================================================== +Statistical functions for masked arrays (:mod:`scipy.stats.mstats`) +=================================================================== + +.. currentmodule:: scipy.stats.mstats + +This module contains a large number of statistical functions that can +be used with masked arrays. + +Most of these functions are similar to those in `scipy.stats` but might +have small differences in the API or in the algorithm used. Since this +is a relatively new package, some API changes are still possible. + +Summary statistics +================== + +.. autosummary:: + :toctree: generated/ + + describe + gmean + hmean + kurtosis + mode + mquantiles + hdmedian + hdquantiles + hdquantiles_sd + idealfourths + plotting_positions + meppf + moment + skew + tmean + tvar + tmin + tmax + tsem + variation + find_repeats + sem + trimmed_mean + trimmed_mean_ci + trimmed_std + trimmed_var + +Frequency statistics +==================== + +.. autosummary:: + :toctree: generated/ + + scoreatpercentile + +Correlation functions +===================== + +.. autosummary:: + :toctree: generated/ + + f_oneway + pearsonr + spearmanr + pointbiserialr + kendalltau + kendalltau_seasonal + linregress + siegelslopes + theilslopes + sen_seasonal_slopes + +Statistical tests +================= + +.. autosummary:: + :toctree: generated/ + + ttest_1samp + ttest_onesamp + ttest_ind + ttest_rel + chisquare + kstest + ks_2samp + ks_1samp + ks_twosamp + mannwhitneyu + rankdata + kruskal + kruskalwallis + friedmanchisquare + brunnermunzel + skewtest + kurtosistest + normaltest + +Transformations +=============== + +.. autosummary:: + :toctree: generated/ + + obrientransform + trim + trima + trimmed_stde + trimr + trimtail + trimboth + winsorize + zmap + zscore + +Other +===== + +.. autosummary:: + :toctree: generated/ + + argstoarray + count_tied_groups + msign + compare_medians_ms + median_cihs + mjci + mquantiles_cimj + rsh + +""" +from . import _mstats_basic +from . import _mstats_extras +from ._mstats_basic import * # noqa: F403 +from ._mstats_extras import * # noqa: F403 +# Functions that support masked array input in stats but need to be kept in the +# mstats namespace for backwards compatibility: +from scipy.stats import gmean, hmean, zmap, zscore, chisquare + +__all__ = _mstats_basic.__all__ + _mstats_extras.__all__ +__all__ += ['gmean', 'hmean', 'zmap', 'zscore', 'chisquare'] diff --git a/venv/lib/python3.12/site-packages/scipy/stats/mstats_basic.py b/venv/lib/python3.12/site-packages/scipy/stats/mstats_basic.py new file mode 100644 index 0000000000000000000000000000000000000000..19cc67a6acdfa054ffa2b29b6e774dd7aafda263 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/mstats_basic.py @@ -0,0 +1,42 @@ +# This file is not meant for public use and will be removed in SciPy v2.0.0. +# Use the `scipy.stats` namespace for importing the functions +# included below. + +from scipy._lib.deprecation import _sub_module_deprecation + + +__all__ = [ # noqa: F822 + 'argstoarray', + 'count_tied_groups', + 'describe', + 'f_oneway', 'find_repeats','friedmanchisquare', + 'kendalltau','kendalltau_seasonal','kruskal','kruskalwallis', + 'ks_twosamp', 'ks_2samp', 'kurtosis', 'kurtosistest', + 'ks_1samp', 'kstest', + 'linregress', + 'mannwhitneyu', 'meppf','mode','moment','mquantiles','msign', + 'normaltest', + 'obrientransform', + 'pearsonr','plotting_positions','pointbiserialr', + 'rankdata', + 'scoreatpercentile','sem', + 'sen_seasonal_slopes','skew','skewtest','spearmanr', + 'siegelslopes', 'theilslopes', + 'tmax','tmean','tmin','trim','trimboth', + 'trimtail','trima','trimr','trimmed_mean','trimmed_std', + 'trimmed_stde','trimmed_var','tsem','ttest_1samp','ttest_onesamp', + 'ttest_ind','ttest_rel','tvar', + 'variation', + 'winsorize', + 'brunnermunzel', +] + + +def __dir__(): + return __all__ + + +def __getattr__(name): + return _sub_module_deprecation(sub_package="stats", module="mstats_basic", + private_modules=["_mstats_basic"], all=__all__, + attribute=name, correct_module="mstats") diff --git a/venv/lib/python3.12/site-packages/scipy/stats/mstats_extras.py b/venv/lib/python3.12/site-packages/scipy/stats/mstats_extras.py new file mode 100644 index 0000000000000000000000000000000000000000..fec695329cf2c2d58a4918cc99e209c0650c3ea6 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/mstats_extras.py @@ -0,0 +1,25 @@ +# This file is not meant for public use and will be removed in SciPy v2.0.0. +# Use the `scipy.stats` namespace for importing the functions +# included below. + +from scipy._lib.deprecation import _sub_module_deprecation + + +__all__ = [ # noqa: F822 + 'compare_medians_ms', + 'hdquantiles', 'hdmedian', 'hdquantiles_sd', + 'idealfourths', + 'median_cihs','mjci','mquantiles_cimj', + 'rsh', + 'trimmed_mean_ci', +] + + +def __dir__(): + return __all__ + + +def __getattr__(name): + return _sub_module_deprecation(sub_package="stats", module="mstats_extras", + private_modules=["_mstats_extras"], all=__all__, + attribute=name, correct_module="mstats") diff --git a/venv/lib/python3.12/site-packages/scipy/stats/mvn.py b/venv/lib/python3.12/site-packages/scipy/stats/mvn.py new file mode 100644 index 0000000000000000000000000000000000000000..65da9e20f6a4e6d24c1cb206c59821730fb6ab83 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/mvn.py @@ -0,0 +1,17 @@ +# This file is not meant for public use and will be removed in SciPy v2.0.0. +# Use the `scipy.stats` namespace for importing the functions +# included below. + +from scipy._lib.deprecation import _sub_module_deprecation + +__all__: list[str] = [] + + +def __dir__(): + return __all__ + + +def __getattr__(name): + return _sub_module_deprecation(sub_package="stats", module="mvn", + private_modules=["_mvn"], all=__all__, + attribute=name) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/qmc.py b/venv/lib/python3.12/site-packages/scipy/stats/qmc.py new file mode 100644 index 0000000000000000000000000000000000000000..a8a08343cf4c759938b31c29e32aaa644bf6e0fd --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/qmc.py @@ -0,0 +1,236 @@ +r""" +==================================================== +Quasi-Monte Carlo submodule (:mod:`scipy.stats.qmc`) +==================================================== + +.. currentmodule:: scipy.stats.qmc + +This module provides Quasi-Monte Carlo generators and associated helper +functions. + + +Quasi-Monte Carlo +================= + +Engines +------- + +.. autosummary:: + :toctree: generated/ + + QMCEngine + Sobol + Halton + LatinHypercube + PoissonDisk + MultinomialQMC + MultivariateNormalQMC + +Helpers +------- + +.. autosummary:: + :toctree: generated/ + + discrepancy + geometric_discrepancy + update_discrepancy + scale + + +Introduction to Quasi-Monte Carlo +================================= + +Quasi-Monte Carlo (QMC) methods [1]_, [2]_, [3]_ provide an +:math:`n \times d` array of numbers in :math:`[0,1]`. They can be used in +place of :math:`n` points from the :math:`U[0,1]^{d}` distribution. Compared to +random points, QMC points are designed to have fewer gaps and clumps. This is +quantified by discrepancy measures [4]_. From the Koksma-Hlawka +inequality [5]_ we know that low discrepancy reduces a bound on +integration error. Averaging a function :math:`f` over :math:`n` QMC points +can achieve an integration error close to :math:`O(n^{-1})` for well +behaved functions [2]_. + +Most QMC constructions are designed for special values of :math:`n` +such as powers of 2 or large primes. Changing the sample +size by even one can degrade their performance, even their +rate of convergence [6]_. For instance :math:`n=100` points may give less +accuracy than :math:`n=64` if the method was designed for :math:`n=2^m`. + +Some QMC constructions are extensible in :math:`n`: we can find +another special sample size :math:`n' > n` and often an infinite +sequence of increasing special sample sizes. Some QMC +constructions are extensible in :math:`d`: we can increase the dimension, +possibly to some upper bound, and typically without requiring +special values of :math:`d`. Some QMC methods are extensible in +both :math:`n` and :math:`d`. + +QMC points are deterministic. That makes it hard to estimate the accuracy of +integrals estimated by averages over QMC points. Randomized QMC (RQMC) [7]_ +points are constructed so that each point is individually :math:`U[0,1]^{d}` +while collectively the :math:`n` points retain their low discrepancy. +One can make :math:`R` independent replications of RQMC points to +see how stable a computation is. From :math:`R` independent values, +a t-test (or bootstrap t-test [8]_) then gives approximate confidence +intervals on the mean value. Some RQMC methods produce a +root mean squared error that is actually :math:`o(1/n)` and smaller than +the rate seen in unrandomized QMC. An intuitive explanation is +that the error is a sum of many small ones and random errors +cancel in a way that deterministic ones do not. RQMC also +has advantages on integrands that are singular or, for other +reasons, fail to be Riemann integrable. + +(R)QMC cannot beat Bahkvalov's curse of dimension (see [9]_). For +any random or deterministic method, there are worst case functions +that will give it poor performance in high dimensions. A worst +case function for QMC might be 0 at all n points but very +large elsewhere. Worst case analyses get very pessimistic +in high dimensions. (R)QMC can bring a great improvement over +MC when the functions on which it is used are not worst case. +For instance (R)QMC can be especially effective on integrands +that are well approximated by sums of functions of +some small number of their input variables at a time [10]_, [11]_. +That property is often a surprising finding about those functions. + +Also, to see an improvement over IID MC, (R)QMC requires a bit of smoothness of +the integrand, roughly the mixed first order derivative in each direction, +:math:`\partial^d f/\partial x_1 \cdots \partial x_d`, must be integral. +For instance, a function that is 1 inside the hypersphere and 0 outside of it +has infinite variation in the sense of Hardy and Krause for any dimension +:math:`d = 2`. + +Scrambled nets are a kind of RQMC that have some valuable robustness +properties [12]_. If the integrand is square integrable, they give variance +:math:`var_{SNET} = o(1/n)`. There is a finite upper bound on +:math:`var_{SNET} / var_{MC}` that holds simultaneously for every square +integrable integrand. Scrambled nets satisfy a strong law of large numbers +for :math:`f` in :math:`L^p` when :math:`p>1`. In some +special cases there is a central limit theorem [13]_. For smooth enough +integrands they can achieve RMSE nearly :math:`O(n^{-3})`. See [12]_ +for references about these properties. + +The main kinds of QMC methods are lattice rules [14]_ and digital +nets and sequences [2]_, [15]_. The theories meet up in polynomial +lattice rules [16]_ which can produce digital nets. Lattice rules +require some form of search for good constructions. For digital +nets there are widely used default constructions. + +The most widely used QMC methods are Sobol' sequences [17]_. +These are digital nets. They are extensible in both :math:`n` and :math:`d`. +They can be scrambled. The special sample sizes are powers +of 2. Another popular method are Halton sequences [18]_. +The constructions resemble those of digital nets. The earlier +dimensions have much better equidistribution properties than +later ones. There are essentially no special sample sizes. +They are not thought to be as accurate as Sobol' sequences. +They can be scrambled. The nets of Faure [19]_ are also widely +used. All dimensions are equally good, but the special sample +sizes grow rapidly with dimension :math:`d`. They can be scrambled. +The nets of Niederreiter and Xing [20]_ have the best asymptotic +properties but have not shown good empirical performance [21]_. + +Higher order digital nets are formed by a digit interleaving process +in the digits of the constructed points. They can achieve higher +levels of asymptotic accuracy given higher smoothness conditions on :math:`f` +and they can be scrambled [22]_. There is little or no empirical work +showing the improved rate to be attained. + +Using QMC is like using the entire period of a small random +number generator. The constructions are similar and so +therefore are the computational costs [23]_. + +(R)QMC is sometimes improved by passing the points through +a baker's transformation (tent function) prior to using them. +That function has the form :math:`1-2|x-1/2|`. As :math:`x` goes from 0 to +1, this function goes from 0 to 1 and then back. It is very +useful to produce a periodic function for lattice rules [14]_, +and sometimes it improves the convergence rate [24]_. + +It is not straightforward to apply QMC methods to Markov +chain Monte Carlo (MCMC). We can think of MCMC as using +:math:`n=1` point in :math:`[0,1]^{d}` for very large :math:`d`, with +ergodic results corresponding to :math:`d \to \infty`. One proposal is +in [25]_ and under strong conditions an improved rate of convergence +has been shown [26]_. + +Returning to Sobol' points: there are many versions depending +on what are called direction numbers. Those are the result of +searches and are tabulated. A very widely used set of direction +numbers come from [27]_. It is extensible in dimension up to +:math:`d=21201`. + +References +---------- +.. [1] Owen, Art B. "Monte Carlo Book: the Quasi-Monte Carlo parts." 2019. +.. [2] Niederreiter, Harald. "Random number generation and quasi-Monte Carlo + methods." Society for Industrial and Applied Mathematics, 1992. +.. [3] Dick, Josef, Frances Y. Kuo, and Ian H. Sloan. "High-dimensional + integration: the quasi-Monte Carlo way." Acta Numerica no. 22: 133, 2013. +.. [4] Aho, A. V., C. Aistleitner, T. Anderson, K. Appel, V. Arnol'd, N. + Aronszajn, D. Asotsky et al. "W. Chen et al.(eds.), "A Panorama of + Discrepancy Theory", Sringer International Publishing, + Switzerland: 679, 2014. +.. [5] Hickernell, Fred J. "Koksma-Hlawka Inequality." Wiley StatsRef: + Statistics Reference Online, 2014. +.. [6] Owen, Art B. "On dropping the first Sobol' point." :arxiv:`2008.08051`, + 2020. +.. [7] L'Ecuyer, Pierre, and Christiane Lemieux. "Recent advances in randomized + quasi-Monte Carlo methods." In Modeling uncertainty, pp. 419-474. Springer, + New York, NY, 2002. +.. [8] DiCiccio, Thomas J., and Bradley Efron. "Bootstrap confidence + intervals." Statistical science: 189-212, 1996. +.. [9] Dimov, Ivan T. "Monte Carlo methods for applied scientists." World + Scientific, 2008. +.. [10] Caflisch, Russel E., William J. Morokoff, and Art B. Owen. "Valuation + of mortgage backed securities using Brownian bridges to reduce effective + dimension." Journal of Computational Finance: no. 1 27-46, 1997. +.. [11] Sloan, Ian H., and Henryk Wozniakowski. "When are quasi-Monte Carlo + algorithms efficient for high dimensional integrals?." Journal of Complexity + 14, no. 1 (1998): 1-33. +.. [12] Owen, Art B., and Daniel Rudolf, "A strong law of large numbers for + scrambled net integration." SIAM Review, to appear. +.. [13] Loh, Wei-Liem. "On the asymptotic distribution of scrambled net + quadrature." The Annals of Statistics 31, no. 4: 1282-1324, 2003. +.. [14] Sloan, Ian H. and S. Joe. "Lattice methods for multiple integration." + Oxford University Press, 1994. +.. [15] Dick, Josef, and Friedrich Pillichshammer. "Digital nets and sequences: + discrepancy theory and quasi-Monte Carlo integration." Cambridge University + Press, 2010. +.. [16] Dick, Josef, F. Kuo, Friedrich Pillichshammer, and I. Sloan. + "Construction algorithms for polynomial lattice rules for multivariate + integration." Mathematics of computation 74, no. 252: 1895-1921, 2005. +.. [17] Sobol', Il'ya Meerovich. "On the distribution of points in a cube and + the approximate evaluation of integrals." Zhurnal Vychislitel'noi Matematiki + i Matematicheskoi Fiziki 7, no. 4: 784-802, 1967. +.. [18] Halton, John H. "On the efficiency of certain quasi-random sequences of + points in evaluating multi-dimensional integrals." Numerische Mathematik 2, + no. 1: 84-90, 1960. +.. [19] Faure, Henri. "Discrepance de suites associees a un systeme de + numeration (en dimension s)." Acta arithmetica 41, no. 4: 337-351, 1982. +.. [20] Niederreiter, Harold, and Chaoping Xing. "Low-discrepancy sequences and + global function fields with many rational places." Finite Fields and their + applications 2, no. 3: 241-273, 1996. +.. [21] Hong, Hee Sun, and Fred J. Hickernell. "Algorithm 823: Implementing + scrambled digital sequences." ACM Transactions on Mathematical Software + (TOMS) 29, no. 2: 95-109, 2003. +.. [22] Dick, Josef. "Higher order scrambled digital nets achieve the optimal + rate of the root mean square error for smooth integrands." The Annals of + Statistics 39, no. 3: 1372-1398, 2011. +.. [23] Niederreiter, Harald. "Multidimensional numerical integration using + pseudorandom numbers." In Stochastic Programming 84 Part I, pp. 17-38. + Springer, Berlin, Heidelberg, 1986. +.. [24] Hickernell, Fred J. "Obtaining O (N-2+e) Convergence for Lattice + Quadrature Rules." In Monte Carlo and Quasi-Monte Carlo Methods 2000, + pp. 274-289. Springer, Berlin, Heidelberg, 2002. +.. [25] Owen, Art B., and Seth D. Tribble. "A quasi-Monte Carlo Metropolis + algorithm." Proceedings of the National Academy of Sciences 102, + no. 25: 8844-8849, 2005. +.. [26] Chen, Su. "Consistency and convergence rate of Markov chain quasi Monte + Carlo with examples." PhD diss., Stanford University, 2011. +.. [27] Joe, Stephen, and Frances Y. Kuo. "Constructing Sobol sequences with + better two-dimensional projections." SIAM Journal on Scientific Computing + 30, no. 5: 2635-2654, 2008. + +""" +from ._qmc import * # noqa: F403 +from ._qmc import __all__ # noqa: F401 diff --git a/venv/lib/python3.12/site-packages/scipy/stats/sampling.py b/venv/lib/python3.12/site-packages/scipy/stats/sampling.py new file mode 100644 index 0000000000000000000000000000000000000000..12174d9dfb3cb93fa33811ed4b5d233817512e36 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/sampling.py @@ -0,0 +1,73 @@ +""" +====================================================== +Random Number Generators (:mod:`scipy.stats.sampling`) +====================================================== + +.. currentmodule:: scipy.stats.sampling + +This module contains a collection of random number generators to sample +from univariate continuous and discrete distributions. It uses the +implementation of a C library called "UNU.RAN". The only exception is +RatioUniforms, which is a pure Python implementation of the +Ratio-of-Uniforms method. + +Generators Wrapped +================== + +For continuous distributions +---------------------------- + +.. autosummary:: + :toctree: generated/ + + NumericalInverseHermite + NumericalInversePolynomial + TransformedDensityRejection + SimpleRatioUniforms + RatioUniforms + +For discrete distributions +-------------------------- + +.. autosummary:: + :toctree: generated/ + + DiscreteAliasUrn + DiscreteGuideTable + +Warnings / Errors used in :mod:`scipy.stats.sampling` +----------------------------------------------------- + +.. autosummary:: + :toctree: generated/ + + UNURANError + + +Generators for pre-defined distributions +======================================== + +To easily apply the above methods for some of the continuous distributions +in :mod:`scipy.stats`, the following functionality can be used: + +.. autosummary:: + :toctree: generated/ + + FastGeneratorInversion + +""" +from ._sampling import FastGeneratorInversion, RatioUniforms # noqa: F401 +from ._unuran.unuran_wrapper import ( # noqa: F401 + TransformedDensityRejection, + DiscreteAliasUrn, + DiscreteGuideTable, + NumericalInversePolynomial, + NumericalInverseHermite, + SimpleRatioUniforms, + UNURANError +) + +__all__ = ["NumericalInverseHermite", "NumericalInversePolynomial", + "TransformedDensityRejection", "SimpleRatioUniforms", + "RatioUniforms", "DiscreteAliasUrn", "DiscreteGuideTable", + "UNURANError", "FastGeneratorInversion"] diff --git a/venv/lib/python3.12/site-packages/scipy/stats/stats.py b/venv/lib/python3.12/site-packages/scipy/stats/stats.py new file mode 100644 index 0000000000000000000000000000000000000000..d0589a9907d3cd181739135a3e6fd1d14d18dc50 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/stats.py @@ -0,0 +1,43 @@ +# This file is not meant for public use and will be removed in SciPy v2.0.0. +# Use the `scipy.stats` namespace for importing the functions +# included below. + +from scipy._lib.deprecation import _sub_module_deprecation + + +__all__ = [ # noqa: F822 + 'gmean', 'hmean', 'pmean', 'mode', 'tmean', 'tvar', + 'tmin', 'tmax', 'tstd', 'tsem', 'moment', + 'skew', 'kurtosis', 'describe', 'skewtest', 'kurtosistest', + 'normaltest', 'jarque_bera', + 'scoreatpercentile', 'percentileofscore', + 'cumfreq', 'relfreq', 'obrientransform', + 'sem', 'zmap', 'zscore', 'gzscore', 'iqr', 'gstd', + 'median_abs_deviation', + 'sigmaclip', 'trimboth', 'trim1', 'trim_mean', + 'f_oneway', + 'pearsonr', 'fisher_exact', + 'spearmanr', 'pointbiserialr', + 'kendalltau', 'weightedtau', 'multiscale_graphcorr', + 'linregress', 'siegelslopes', 'theilslopes', 'ttest_1samp', + 'ttest_ind', 'ttest_ind_from_stats', 'ttest_rel', + 'kstest', 'ks_1samp', 'ks_2samp', + 'chisquare', 'power_divergence', + 'tiecorrect', 'ranksums', 'kruskal', 'friedmanchisquare', + 'rankdata', + 'combine_pvalues', 'wasserstein_distance', 'energy_distance', + 'brunnermunzel', 'alexandergovern', 'distributions', + 'mstats_basic', +] + + +def __dir__(): + return __all__ + + +def __getattr__(name): + return _sub_module_deprecation( + sub_package="stats", module="stats", + private_modules=["_stats_py", "_mgc", "_correlation"], + all=__all__, + attribute=name) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/__init__.py b/venv/lib/python3.12/site-packages/scipy/stats/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c2cd82a3d6a3dc0672ed9bdb2a4db863312b169c Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/__init__.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/common_tests.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/common_tests.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8497c31a3f005e399a83a022483cd2f4e5c817ee Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/common_tests.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_axis_nan_policy.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_axis_nan_policy.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2b70bfd966fd2fc44d25e234511ba30abb9a7ccf Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_axis_nan_policy.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_binned_statistic.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_binned_statistic.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3f0648a3ac73a7523d769ef14dc6f36966eff7d5 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_binned_statistic.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_censored_data.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_censored_data.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3c24635c059b337254abbea3283179fb81a4b9ee Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_censored_data.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_contingency.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_contingency.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..22719c35a5376458e30f31980d310588c5bc4732 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_contingency.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_continued_fraction.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_continued_fraction.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7e0091c6f9cd609204b060566f93abc3159d8212 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_continued_fraction.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_continuous.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_continuous.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4209f43c9b019fbe4a160039a0f0269826cf446d --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_continuous.cpython-312.pyc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:520dc83bb6abcc02e251aa4d3006de0684205d52430f7042d682d91da971eca5 +size 152416 diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_continuous_basic.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_continuous_basic.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..09f045b36900617b2b0d55176b7e10fbc7afbbc6 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_continuous_basic.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_continuous_fit_censored.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_continuous_fit_censored.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a0622f2abb4020837a3a1ded007a1f6b5fdbf18e Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_continuous_fit_censored.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_correlation.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_correlation.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..421739061fe5a4fcbd18a3813c05d42c6a1f39fb Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_correlation.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_crosstab.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_crosstab.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..831a340ee4bf40f2c8626c6edc3ef5c8aa60bb3d Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_crosstab.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_device_dtype.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_device_dtype.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..56ed4d45af86f81c0a765712e93119a505913ec3 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_device_dtype.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_discrete_basic.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_discrete_basic.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..db2231a9096bc4ac25859311497634c2a438b951 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_discrete_basic.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_discrete_distns.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_discrete_distns.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6bb463986346c400e5f20f808e2bec7a11fee6e3 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_discrete_distns.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_distributions.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_distributions.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4c1095cab1f685693a259d320ea1112be02ac2bd --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_distributions.cpython-312.pyc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ac251d27159c170a7d6ed1e85d54255298c238b8fc5de2c4d674bab65e6c99eb +size 567618 diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_entropy.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_entropy.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9a4cf9b3deca33813a65f654a32fe84f48f0f19c Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_entropy.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_fast_gen_inversion.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_fast_gen_inversion.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ba090d3162f3c52f29b7d88e3384215467abc676 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_fast_gen_inversion.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_fit.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_fit.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..20f335e266c95bc4f61300c08312508fdae16310 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_fit.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_hypotests.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_hypotests.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2760278247e2acae670157630baf5468aee9992e --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_hypotests.cpython-312.pyc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:301ab0beca9ab3aeb0f1895eeeaaaeb5d245e5358a6185b0c25df0549565ff0a +size 125971 diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_kdeoth.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_kdeoth.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..012f82e7445432c50220dca9a6fe0a3058db3473 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_kdeoth.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_marray.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_marray.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5a25ed13228dfd0e2b2d32bc00c98d2584f0ed24 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_marray.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_mgc.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_mgc.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4e3d3ba52a8aecacb020d9494dac6ca20caae049 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_mgc.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_morestats.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_morestats.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1cdd8cc3243cde0ea542fe7e0be516f302462ae3 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_morestats.cpython-312.pyc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:167356ae886459a6c58336fa0c750dd0ce6ea4fb8beb4673d6eba82d6df0a003 +size 246313 diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_mstats_basic.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_mstats_basic.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4b9d2f928d4770933b7f7cc920967065078c81c1 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_mstats_basic.cpython-312.pyc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8511e46d158344d451f22eda71ef4575b1b21732d7063d0fed01049bd2b630c5 +size 138481 diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_mstats_extras.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_mstats_extras.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2be02bce87af036957e05d7b02a92f01518dad7d Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_mstats_extras.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_multicomp.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_multicomp.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1c4c6023f4d3498a79a57442b969a2e399441b79 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_multicomp.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_multivariate.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_multivariate.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d491eb87ac6d76522a3cb0cac5712fc7616b23ea --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_multivariate.cpython-312.pyc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:37fe57ab4bcf9de9e87a22e1514dfc867790d2e9416c17d175114d60c0079ceb +size 286513 diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_new_distributions.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_new_distributions.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3180b99e34d18195c591f880e6decd568be2ee47 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_new_distributions.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_odds_ratio.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_odds_ratio.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8c7927d29f661c18a5c40080daa235b907201e95 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_odds_ratio.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_qmc.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_qmc.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3f8ee846950c036f6a5bd0139f26cce0af36b0a3 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_qmc.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_quantile.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_quantile.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7815437b2a3643c83c304a194fdb0e4c8a084171 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_quantile.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_rank.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_rank.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..40469b20b7af3b791bd1c73b6f72a52353a88f66 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_rank.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_relative_risk.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_relative_risk.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bbf564d1018227ecef34efadc05542a83a3cf021 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_relative_risk.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_resampling.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_resampling.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..57b5dca86f12cd6230a88f2f0996aa6bde8b6cd8 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_resampling.cpython-312.pyc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:849e055ddc1b8de2ccee5788e26b24f287640ce263349d5bd3ce4fb7eb6c0420 +size 132637 diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_sampling.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_sampling.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8a2e9b6429df40f0f075538df9f4c915f1b554d0 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_sampling.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_sensitivity_analysis.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_sensitivity_analysis.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3342d00c349e2e2784cd4f03d894fb6716bfa299 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_sensitivity_analysis.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_stats.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_stats.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bd973762fba8ba70c256593fbd984bc2ca2bf64a --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_stats.cpython-312.pyc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4b005537dd468c7ecfaa7f2fc3fcd4ea909bd7ab4cedde785e9ba7e0ea3b377b +size 640785 diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_survival.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_survival.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..56e361190a79f5f323869126ba4c7b6117d4cfd8 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_survival.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_tukeylambda_stats.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_tukeylambda_stats.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1f27523b02ffe35f01c701ebc3a7fa5e335b0d98 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_tukeylambda_stats.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_variation.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_variation.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3201e5d0f40f0267284d9b77d97e9484aa2d6ad9 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/tests/__pycache__/test_variation.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/common_tests.py b/venv/lib/python3.12/site-packages/scipy/stats/tests/common_tests.py new file mode 100644 index 0000000000000000000000000000000000000000..981bfc3061e3158116a9a2c9f448eb1eb9ff2c61 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/common_tests.py @@ -0,0 +1,347 @@ +import pickle + +import numpy as np +import numpy.testing as npt +from numpy.testing import assert_allclose, assert_equal +from pytest import raises as assert_raises + +import numpy.ma.testutils as ma_npt + +from scipy._lib._util import getfullargspec_no_self as _getfullargspec +from scipy._lib._array_api_no_0d import xp_assert_equal +from scipy import stats + + +def check_named_results(res, attributes, ma=False, xp=None): + for i, attr in enumerate(attributes): + if ma: + ma_npt.assert_equal(res[i], getattr(res, attr)) + elif xp is not None: + xp_assert_equal(res[i], getattr(res, attr)) + else: + npt.assert_equal(res[i], getattr(res, attr)) + + +def check_normalization(distfn, args, distname): + norm_moment = distfn.moment(0, *args) + npt.assert_allclose(norm_moment, 1.0) + + if distname == "rv_histogram_instance": + atol, rtol = 1e-5, 0 + else: + atol, rtol = 1e-7, 1e-7 + + normalization_expect = distfn.expect(lambda x: 1, args=args) + npt.assert_allclose(normalization_expect, 1.0, atol=atol, rtol=rtol, + err_msg=distname, verbose=True) + + _a, _b = distfn.support(*args) + normalization_cdf = distfn.cdf(_b, *args) + npt.assert_allclose(normalization_cdf, 1.0) + + +def check_moment(distfn, arg, m, v, msg): + m1 = distfn.moment(1, *arg) + m2 = distfn.moment(2, *arg) + if not np.isinf(m): + npt.assert_almost_equal(m1, m, decimal=10, + err_msg=msg + ' - 1st moment') + else: # or np.isnan(m1), + npt.assert_(np.isinf(m1), + msg + f' - 1st moment -infinite, m1={str(m1)}') + + if not np.isinf(v): + npt.assert_almost_equal(m2 - m1 * m1, v, decimal=10, + err_msg=msg + ' - 2ndt moment') + else: # or np.isnan(m2), + npt.assert_(np.isinf(m2), msg + f' - 2nd moment -infinite, {m2=}') + + +def check_mean_expect(distfn, arg, m, msg): + if np.isfinite(m): + m1 = distfn.expect(lambda x: x, arg) + npt.assert_almost_equal(m1, m, decimal=5, + err_msg=msg + ' - 1st moment (expect)') + + +def check_var_expect(distfn, arg, m, v, msg): + dist_looser_tolerances = {"rv_histogram_instance" , "ksone"} + kwargs = {'rtol': 5e-6} if msg in dist_looser_tolerances else {} + if np.isfinite(v): + m2 = distfn.expect(lambda x: x*x, arg) + npt.assert_allclose(m2, v + m*m, **kwargs) + + +def check_skew_expect(distfn, arg, m, v, s, msg): + if np.isfinite(s): + m3e = distfn.expect(lambda x: np.power(x-m, 3), arg) + npt.assert_almost_equal(m3e, s * np.power(v, 1.5), + decimal=5, err_msg=msg + ' - skew') + else: + npt.assert_(np.isnan(s)) + + +def check_kurt_expect(distfn, arg, m, v, k, msg): + if np.isfinite(k): + m4e = distfn.expect(lambda x: np.power(x-m, 4), arg) + npt.assert_allclose(m4e, (k + 3.) * np.power(v, 2), + atol=1e-5, rtol=1e-5, + err_msg=msg + ' - kurtosis') + elif not np.isposinf(k): + npt.assert_(np.isnan(k)) + + +def check_munp_expect(dist, args, msg): + # If _munp is overridden, test a higher moment. (Before gh-18634, some + # distributions had issues with moments 5 and higher.) + if dist._munp.__func__ != stats.rv_continuous._munp: + res = dist.moment(5, *args) # shouldn't raise an error + ref = dist.expect(lambda x: x ** 5, args, lb=-np.inf, ub=np.inf) + if not np.isfinite(res): # could be valid; automated test can't know + return + # loose tolerance, mostly to see whether _munp returns *something* + assert_allclose(res, ref, atol=1e-10, rtol=1e-4, + err_msg=msg + ' - higher moment / _munp') + + +def check_entropy(distfn, arg, msg): + ent = distfn.entropy(*arg) + npt.assert_(not np.isnan(ent), msg + 'test Entropy is nan') + + +def check_private_entropy(distfn, args, superclass): + # compare a generic _entropy with the distribution-specific implementation + npt.assert_allclose(distfn._entropy(*args), + superclass._entropy(distfn, *args)) + + +def check_entropy_vect_scale(distfn, arg): + # check 2-d + sc = np.asarray([[1, 2], [3, 4]]) + v_ent = distfn.entropy(*arg, scale=sc) + s_ent = [distfn.entropy(*arg, scale=s) for s in sc.ravel()] + s_ent = np.asarray(s_ent).reshape(v_ent.shape) + assert_allclose(v_ent, s_ent, atol=1e-14) + + # check invalid value, check cast + sc = [1, 2, -3] + v_ent = distfn.entropy(*arg, scale=sc) + s_ent = [distfn.entropy(*arg, scale=s) for s in sc] + s_ent = np.asarray(s_ent).reshape(v_ent.shape) + assert_allclose(v_ent, s_ent, atol=1e-14) + + +def check_edge_support(distfn, args): + # Make sure that x=self.a and self.b are handled correctly. + x = distfn.support(*args) + if isinstance(distfn, stats.rv_discrete): + x = x[0]-1, x[1] + + npt.assert_equal(distfn.cdf(x, *args), [0.0, 1.0]) + npt.assert_equal(distfn.sf(x, *args), [1.0, 0.0]) + + if distfn.name not in ('skellam', 'dlaplace'): + # with a = -inf, log(0) generates warnings + npt.assert_equal(distfn.logcdf(x, *args), [-np.inf, 0.0]) + npt.assert_equal(distfn.logsf(x, *args), [0.0, -np.inf]) + + npt.assert_equal(distfn.ppf([0.0, 1.0], *args), x) + npt.assert_equal(distfn.isf([0.0, 1.0], *args), x[::-1]) + + # out-of-bounds for isf & ppf + npt.assert_(np.isnan(distfn.isf([-1, 2], *args)).all()) + npt.assert_(np.isnan(distfn.ppf([-1, 2], *args)).all()) + + +def check_named_args(distfn, x, shape_args, defaults, meths): + ## Check calling w/ named arguments. + + # check consistency of shapes, numargs and _parse signature + signature = _getfullargspec(distfn._parse_args) + npt.assert_(signature.varargs is None) + npt.assert_(signature.varkw is None) + npt.assert_(not signature.kwonlyargs) + npt.assert_(list(signature.defaults) == list(defaults)) + + shape_argnames = signature.args[:-len(defaults)] # a, b, loc=0, scale=1 + if distfn.shapes: + shapes_ = distfn.shapes.replace(',', ' ').split() + else: + shapes_ = '' + npt.assert_(len(shapes_) == distfn.numargs) + npt.assert_(len(shapes_) == len(shape_argnames)) + + # check calling w/ named arguments + shape_args = list(shape_args) + + vals = [meth(x, *shape_args) for meth in meths] + npt.assert_(np.all(np.isfinite(vals))) + + names, a, k = shape_argnames[:], shape_args[:], {} + while names: + k.update({names.pop(): a.pop()}) + v = [meth(x, *a, **k) for meth in meths] + npt.assert_array_equal(vals, v) + if 'n' not in k.keys(): + # `n` is first parameter of moment(), so can't be used as named arg + npt.assert_equal(distfn.moment(1, *a, **k), + distfn.moment(1, *shape_args)) + + # unknown arguments should not go through: + k.update({'kaboom': 42}) + assert_raises(TypeError, distfn.cdf, x, **k) + + +def check_random_state_property(distfn, args): + # check the random_state attribute of a distribution *instance* + + # baseline: this relies on the global state + np.random.seed(1234) # valid use of np.random.seed + distfn.random_state = None + r0 = distfn.rvs(*args, size=8) + + # use an explicit instance-level random_state + distfn.random_state = 1234 + r1 = distfn.rvs(*args, size=8) + npt.assert_equal(r0, r1) + + distfn.random_state = np.random.RandomState(1234) + r2 = distfn.rvs(*args, size=8) + npt.assert_equal(r0, r2) + + # check that np.random.Generator can be used (numpy >= 1.17) + if hasattr(np.random, 'default_rng'): + # obtain a np.random.Generator object + rng = np.random.default_rng(1234) + distfn.rvs(*args, size=1, random_state=rng) + + # can override the instance-level random_state for an individual .rvs call + distfn.random_state = 2 + orig_state = distfn.random_state.get_state() + + r3 = distfn.rvs(*args, size=8, random_state=np.random.RandomState(1234)) + npt.assert_equal(r0, r3) + + # ... and that does not alter the instance-level random_state! + npt.assert_equal(distfn.random_state.get_state(), orig_state) + + +def check_meth_dtype(distfn, arg, meths): + q0 = [0.25, 0.5, 0.75] + x0 = distfn.ppf(q0, *arg) + x_cast = [x0.astype(tp) for tp in (np.long, np.float16, np.float32, + np.float64)] + + for x in x_cast: + # casting may have clipped the values, exclude those + distfn._argcheck(*arg) + x = x[(distfn.a < x) & (x < distfn.b)] + for meth in meths: + val = meth(x, *arg) + npt.assert_(val.dtype == np.float64) + + +def check_ppf_dtype(distfn, arg): + q0 = np.asarray([0.25, 0.5, 0.75]) + q_cast = [q0.astype(tp) for tp in (np.float16, np.float32, np.float64)] + for q in q_cast: + for meth in [distfn.ppf, distfn.isf]: + val = meth(q, *arg) + npt.assert_(val.dtype == np.float64) + + +def check_cmplx_deriv(distfn, arg): + # Distributions allow complex arguments. + def deriv(f, x, *arg): + x = np.asarray(x) + h = 1e-10 + return (f(x + h*1j, *arg)/h).imag + + x0 = distfn.ppf([0.25, 0.51, 0.75], *arg) + x_cast = [x0.astype(tp) for tp in (np.long, np.float16, np.float32, + np.float64)] + + for x in x_cast: + # casting may have clipped the values, exclude those + distfn._argcheck(*arg) + x = x[(distfn.a < x) & (x < distfn.b)] + + pdf, cdf, sf = distfn.pdf(x, *arg), distfn.cdf(x, *arg), distfn.sf(x, *arg) + assert_allclose(deriv(distfn.cdf, x, *arg), pdf, rtol=1e-5) + assert_allclose(deriv(distfn.logcdf, x, *arg), pdf/cdf, rtol=1e-5) + + assert_allclose(deriv(distfn.sf, x, *arg), -pdf, rtol=1e-5) + assert_allclose(deriv(distfn.logsf, x, *arg), -pdf/sf, rtol=1e-5) + + assert_allclose(deriv(distfn.logpdf, x, *arg), + deriv(distfn.pdf, x, *arg) / distfn.pdf(x, *arg), + rtol=1e-5) + + +def check_pickling(distfn, args): + # check that a distribution instance pickles and unpickles + # pay special attention to the random_state property + + # save the random_state (restore later) + rndm = distfn.random_state + + # check unfrozen + distfn.random_state = 1234 + distfn.rvs(*args, size=8) + s = pickle.dumps(distfn) + r0 = distfn.rvs(*args, size=8) + + unpickled = pickle.loads(s) + r1 = unpickled.rvs(*args, size=8) + npt.assert_equal(r0, r1) + + # also smoke test some methods + medians = [distfn.ppf(0.5, *args), unpickled.ppf(0.5, *args)] + npt.assert_equal(medians[0], medians[1]) + npt.assert_equal(distfn.cdf(medians[0], *args), + unpickled.cdf(medians[1], *args)) + + # check frozen pickling/unpickling with rvs + frozen_dist = distfn(*args) + pkl = pickle.dumps(frozen_dist) + unpickled = pickle.loads(pkl) + + r0 = frozen_dist.rvs(size=8) + r1 = unpickled.rvs(size=8) + npt.assert_equal(r0, r1) + + # check pickling/unpickling of .fit method + if hasattr(distfn, "fit"): + fit_function = distfn.fit + pickled_fit_function = pickle.dumps(fit_function) + unpickled_fit_function = pickle.loads(pickled_fit_function) + assert fit_function.__name__ == unpickled_fit_function.__name__ == "fit" + + # restore the random_state + distfn.random_state = rndm + + +def check_freezing(distfn, args): + # regression test for gh-11089: freezing a distribution fails + # if loc and/or scale are specified + if isinstance(distfn, stats.rv_continuous): + locscale = {'loc': 1, 'scale': 2} + else: + locscale = {'loc': 1} + + rv = distfn(*args, **locscale) + assert rv.a == distfn(*args).a + assert rv.b == distfn(*args).b + + +def check_rvs_broadcast(distfunc, distname, allargs, shape, shape_only, otype): + rng = np.random.RandomState(123) + sample = distfunc.rvs(*allargs, random_state=rng) + assert_equal(sample.shape, shape, f"{distname}: rvs failed to broadcast") + if not shape_only: + rvs = np.vectorize( + lambda *allargs: distfunc.rvs(*allargs, random_state=rng), + otypes=otype) + rng = np.random.RandomState(123) + expected = rvs(*allargs) + assert_allclose(sample, expected, rtol=1e-13) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/data/__pycache__/_mvt.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/tests/data/__pycache__/_mvt.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6b0b4187eec21e548e31c7ee748734a4c866e957 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/tests/data/__pycache__/_mvt.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/data/__pycache__/fisher_exact_results_from_r.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/tests/data/__pycache__/fisher_exact_results_from_r.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ed3f16ba70bedfc34c5e97454ec6a79bf4ed2f54 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/tests/data/__pycache__/fisher_exact_results_from_r.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/data/_mvt.py b/venv/lib/python3.12/site-packages/scipy/stats/tests/data/_mvt.py new file mode 100644 index 0000000000000000000000000000000000000000..c346d0daded4b6e734718742cc8950b84ed333f7 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/data/_mvt.py @@ -0,0 +1,171 @@ +import math +import numpy as np +from scipy import special +from scipy.stats._qmc import primes_from_2_to + + +def _primes(n): + # Defined to facilitate comparison between translation and source + # In Matlab, primes(10.5) -> first four primes, primes(11.5) -> first five + return primes_from_2_to(math.ceil(n)) + + +def _gaminv(a, b): + # Defined to facilitate comparison between translation and source + # Matlab's `gaminv` is like `special.gammaincinv` but args are reversed + return special.gammaincinv(b, a) + + +def _qsimvtv(m, nu, sigma, a, b, rng): + """Estimates the multivariate t CDF using randomized QMC + + Parameters + ---------- + m : int + The number of points + nu : float + Degrees of freedom + sigma : ndarray + A 2D positive semidefinite covariance matrix + a : ndarray + Lower integration limits + b : ndarray + Upper integration limits. + rng : Generator + Pseudorandom number generator + + Returns + ------- + p : float + The estimated CDF. + e : float + An absolute error estimate. + + """ + # _qsimvtv is a Python translation of the Matlab function qsimvtv, + # semicolons and all. + # + # This function uses an algorithm given in the paper + # "Comparison of Methods for the Numerical Computation of + # Multivariate t Probabilities", in + # J. of Computational and Graphical Stat., 11(2002), pp. 950-971, by + # Alan Genz and Frank Bretz + # + # The primary references for the numerical integration are + # "On a Number-Theoretical Integration Method" + # H. Niederreiter, Aequationes Mathematicae, 8(1972), pp. 304-11. + # and + # "Randomization of Number Theoretic Methods for Multiple Integration" + # R. Cranley & T.N.L. Patterson, SIAM J Numer Anal, 13(1976), pp. 904-14. + # + # Alan Genz is the author of this function and following Matlab functions. + # Alan Genz, WSU Math, PO Box 643113, Pullman, WA 99164-3113 + # Email : alangenz@wsu.edu + # + # Copyright (C) 2013, Alan Genz, All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided the following conditions are met: + # 1. Redistributions of source code must retain the above copyright + # notice, this list of conditions and the following disclaimer. + # 2. Redistributions in binary form must reproduce the above copyright + # notice, this list of conditions and the following disclaimer in + # the documentation and/or other materials provided with the + # distribution. + # 3. The contributor name(s) may not be used to endorse or promote + # products derived from this software without specific prior + # written permission. + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + # OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF USE + # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + # Initialization + sn = max(1, math.sqrt(nu)); ch, az, bz = _chlrps(sigma, a/sn, b/sn) + n = len(sigma); N = 10; P = math.ceil(m/N); on = np.ones(P); p = 0; e = 0 + ps = np.sqrt(_primes(5*n*math.log(n+4)/4)); q = ps[:, np.newaxis] # Richtmyer gens. + + # Randomization loop for ns samples + c = None; dc = None + for S in range(N): + vp = on.copy(); s = np.zeros((n, P)) + for i in range(n): + x = np.abs(2*np.mod(q[i]*np.arange(1, P+1) + rng.random(), 1)-1) # periodizing transform + if i == 0: + r = on + if nu > 0: + r = np.sqrt(2*_gaminv(x, nu/2)) + else: + y = _Phinv(c + x*dc) + s[i:] += ch[i:, i-1:i] * y + si = s[i, :]; c = on.copy(); ai = az[i]*r - si; d = on.copy(); bi = bz[i]*r - si + c[ai <= -9] = 0; tl = abs(ai) < 9; c[tl] = _Phi(ai[tl]) + d[bi <= -9] = 0; tl = abs(bi) < 9; d[tl] = _Phi(bi[tl]) + dc = d - c; vp = vp * dc + d = (np.mean(vp) - p)/(S + 1); p = p + d; e = (S - 1)*e/(S + 1) + d**2 + e = math.sqrt(e) # error estimate is 3 times std error with N samples. + return p, e + + +# Standard statistical normal distribution functions +def _Phi(z): + return special.ndtr(z) + + +def _Phinv(p): + return special.ndtri(p) + + +def _chlrps(R, a, b): + """ + Computes permuted and scaled lower Cholesky factor c for R which may be + singular, also permuting and scaling integration limit vectors a and b. + """ + ep = 1e-10 # singularity tolerance + eps = np.finfo(R.dtype).eps + + n = len(R); c = R.copy(); ap = a.copy(); bp = b.copy(); d = np.sqrt(np.maximum(np.diag(c), 0)) + for i in range(n): + if d[i] > 0: + c[:, i] /= d[i]; c[i, :] /= d[i] + ap[i] /= d[i]; bp[i] /= d[i] + y = np.zeros((n, 1)); sqtp = math.sqrt(2*math.pi) + + for k in range(n): + im = k; ckk = 0; dem = 1; s = 0 + for i in range(k, n): + if c[i, i] > eps: + cii = math.sqrt(max(c[i, i], 0)) + if i > 0: s = c[i, :k] @ y[:k] + ai = (ap[i]-s)/cii; bi = (bp[i]-s)/cii; de = _Phi(bi)-_Phi(ai) + if de <= dem: + ckk = cii; dem = de; am = ai; bm = bi; im = i + if im > k: + ap[[im, k]] = ap[[k, im]]; bp[[im, k]] = bp[[k, im]]; c[im, im] = c[k, k] + t = c[im, :k].copy(); c[im, :k] = c[k, :k]; c[k, :k] = t + t = c[im+1:, im].copy(); c[im+1:, im] = c[im+1:, k]; c[im+1:, k] = t + t = c[k+1:im, k].copy(); c[k+1:im, k] = c[im, k+1:im].T; c[im, k+1:im] = t.T + if ckk > ep*(k+1): + c[k, k] = ckk; c[k, k+1:] = 0 + for i in range(k+1, n): + c[i, k] = c[i, k]/ckk; c[i, k+1:i+1] = c[i, k+1:i+1] - c[i, k]*c[k+1:i+1, k].T + if abs(dem) > ep: + y[k] = (np.exp(-am**2/2) - np.exp(-bm**2/2)) / (sqtp*dem) + else: + y[k] = (am + bm) / 2 + if am < -10: + y[k] = bm + elif bm > 10: + y[k] = am + c[k, :k+1] /= ckk; ap[k] /= ckk; bp[k] /= ckk + else: + c[k:, k] = 0; y[k] = (ap[k] + bp[k])/2 + pass + return c, ap, bp diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/data/fisher_exact_results_from_r.py b/venv/lib/python3.12/site-packages/scipy/stats/tests/data/fisher_exact_results_from_r.py new file mode 100644 index 0000000000000000000000000000000000000000..b7dd8936018eae2f74cc6f5966235a86fa821793 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/data/fisher_exact_results_from_r.py @@ -0,0 +1,607 @@ +# DO NOT EDIT THIS FILE! +# This file was generated by the R script +# generate_fisher_exact_results_from_r.R +# The script was run with R version 3.6.2 (2019-12-12) at 2020-11-09 06:16:09 + + +from collections import namedtuple +import numpy as np + + +Inf = np.inf + +Parameters = namedtuple('Parameters', + ['table', 'confidence_level', 'alternative']) +RResults = namedtuple('RResults', + ['pvalue', 'conditional_odds_ratio', + 'conditional_odds_ratio_ci']) +data = [ + (Parameters(table=[[100, 2], [1000, 5]], + confidence_level=0.95, + alternative='two.sided'), + RResults(pvalue=0.1300759363430016, + conditional_odds_ratio=0.25055839934223, + conditional_odds_ratio_ci=(0.04035202926536294, + 2.662846672960251))), + (Parameters(table=[[2, 7], [8, 2]], + confidence_level=0.95, + alternative='two.sided'), + RResults(pvalue=0.02301413756522116, + conditional_odds_ratio=0.0858623513573622, + conditional_odds_ratio_ci=(0.004668988338943325, + 0.895792956493601))), + (Parameters(table=[[5, 1], [10, 10]], + confidence_level=0.95, + alternative='two.sided'), + RResults(pvalue=0.1973244147157191, + conditional_odds_ratio=4.725646047336587, + conditional_odds_ratio_ci=(0.4153910882532168, + 259.2593661129417))), + (Parameters(table=[[5, 15], [20, 20]], + confidence_level=0.95, + alternative='two.sided'), + RResults(pvalue=0.09580440012477633, + conditional_odds_ratio=0.3394396617440851, + conditional_odds_ratio_ci=(0.08056337526385809, + 1.22704788545557))), + (Parameters(table=[[5, 16], [16, 25]], + confidence_level=0.95, + alternative='two.sided'), + RResults(pvalue=0.2697004098849359, + conditional_odds_ratio=0.4937791394540491, + conditional_odds_ratio_ci=(0.1176691231650079, + 1.787463657995973))), + (Parameters(table=[[10, 5], [10, 1]], + confidence_level=0.95, + alternative='two.sided'), + RResults(pvalue=0.1973244147157192, + conditional_odds_ratio=0.2116112781158479, + conditional_odds_ratio_ci=(0.003857141267422399, + 2.407369893767229))), + (Parameters(table=[[10, 5], [10, 0]], + confidence_level=0.95, + alternative='two.sided'), + RResults(pvalue=0.06126482213438735, + conditional_odds_ratio=0, + conditional_odds_ratio_ci=(0, + 1.451643573543705))), + (Parameters(table=[[5, 0], [1, 4]], + confidence_level=0.95, + alternative='two.sided'), + RResults(pvalue=0.04761904761904762, + conditional_odds_ratio=Inf, + conditional_odds_ratio_ci=(1.024822256141754, + Inf))), + (Parameters(table=[[0, 5], [1, 4]], + confidence_level=0.95, + alternative='two.sided'), + RResults(pvalue=1, + conditional_odds_ratio=0, + conditional_odds_ratio_ci=(0, + 39.00054996869288))), + (Parameters(table=[[5, 1], [0, 4]], + confidence_level=0.95, + alternative='two.sided'), + RResults(pvalue=0.04761904761904761, + conditional_odds_ratio=Inf, + conditional_odds_ratio_ci=(1.024822256141754, + Inf))), + (Parameters(table=[[0, 1], [3, 2]], + confidence_level=0.95, + alternative='two.sided'), + RResults(pvalue=1, + conditional_odds_ratio=0, + conditional_odds_ratio_ci=(0, + 39.00054996869287))), + (Parameters(table=[[200, 7], [8, 300]], + confidence_level=0.95, + alternative='two.sided'), + RResults(pvalue=2.005657880389071e-122, + conditional_odds_ratio=977.7866978606228, + conditional_odds_ratio_ci=(349.2595113327733, + 3630.382605689872))), + (Parameters(table=[[28, 21], [6, 1957]], + confidence_level=0.95, + alternative='two.sided'), + RResults(pvalue=5.728437460831947e-44, + conditional_odds_ratio=425.2403028434684, + conditional_odds_ratio_ci=(152.4166024390096, + 1425.700792178893))), + (Parameters(table=[[190, 800], [200, 900]], + confidence_level=0.95, + alternative='two.sided'), + RResults(pvalue=0.574111858126088, + conditional_odds_ratio=1.068697577856801, + conditional_odds_ratio_ci=(0.8520462587912048, + 1.340148950273938))), + (Parameters(table=[[100, 2], [1000, 5]], + confidence_level=0.99, + alternative='two.sided'), + RResults(pvalue=0.1300759363430016, + conditional_odds_ratio=0.25055839934223, + conditional_odds_ratio_ci=(0.02502345007115455, + 6.304424772117853))), + (Parameters(table=[[2, 7], [8, 2]], + confidence_level=0.99, + alternative='two.sided'), + RResults(pvalue=0.02301413756522116, + conditional_odds_ratio=0.0858623513573622, + conditional_odds_ratio_ci=(0.001923034001462487, + 1.53670836950172))), + (Parameters(table=[[5, 1], [10, 10]], + confidence_level=0.99, + alternative='two.sided'), + RResults(pvalue=0.1973244147157191, + conditional_odds_ratio=4.725646047336587, + conditional_odds_ratio_ci=(0.2397970951413721, + 1291.342011095509))), + (Parameters(table=[[5, 15], [20, 20]], + confidence_level=0.99, + alternative='two.sided'), + RResults(pvalue=0.09580440012477633, + conditional_odds_ratio=0.3394396617440851, + conditional_odds_ratio_ci=(0.05127576113762925, + 1.717176678806983))), + (Parameters(table=[[5, 16], [16, 25]], + confidence_level=0.99, + alternative='two.sided'), + RResults(pvalue=0.2697004098849359, + conditional_odds_ratio=0.4937791394540491, + conditional_odds_ratio_ci=(0.07498546954483619, + 2.506969905199901))), + (Parameters(table=[[10, 5], [10, 1]], + confidence_level=0.99, + alternative='two.sided'), + RResults(pvalue=0.1973244147157192, + conditional_odds_ratio=0.2116112781158479, + conditional_odds_ratio_ci=(0.0007743881879531337, + 4.170192301163831))), + (Parameters(table=[[10, 5], [10, 0]], + confidence_level=0.99, + alternative='two.sided'), + RResults(pvalue=0.06126482213438735, + conditional_odds_ratio=0, + conditional_odds_ratio_ci=(0, + 2.642491011905582))), + (Parameters(table=[[5, 0], [1, 4]], + confidence_level=0.99, + alternative='two.sided'), + RResults(pvalue=0.04761904761904762, + conditional_odds_ratio=Inf, + conditional_odds_ratio_ci=(0.496935393325443, + Inf))), + (Parameters(table=[[0, 5], [1, 4]], + confidence_level=0.99, + alternative='two.sided'), + RResults(pvalue=1, + conditional_odds_ratio=0, + conditional_odds_ratio_ci=(0, + 198.019801980198))), + (Parameters(table=[[5, 1], [0, 4]], + confidence_level=0.99, + alternative='two.sided'), + RResults(pvalue=0.04761904761904761, + conditional_odds_ratio=Inf, + conditional_odds_ratio_ci=(0.496935393325443, + Inf))), + (Parameters(table=[[0, 1], [3, 2]], + confidence_level=0.99, + alternative='two.sided'), + RResults(pvalue=1, + conditional_odds_ratio=0, + conditional_odds_ratio_ci=(0, + 198.019801980198))), + (Parameters(table=[[200, 7], [8, 300]], + confidence_level=0.99, + alternative='two.sided'), + RResults(pvalue=2.005657880389071e-122, + conditional_odds_ratio=977.7866978606228, + conditional_odds_ratio_ci=(270.0334165523604, + 5461.333333326708))), + (Parameters(table=[[28, 21], [6, 1957]], + confidence_level=0.99, + alternative='two.sided'), + RResults(pvalue=5.728437460831947e-44, + conditional_odds_ratio=425.2403028434684, + conditional_odds_ratio_ci=(116.7944750275836, + 1931.995993191814))), + (Parameters(table=[[190, 800], [200, 900]], + confidence_level=0.99, + alternative='two.sided'), + RResults(pvalue=0.574111858126088, + conditional_odds_ratio=1.068697577856801, + conditional_odds_ratio_ci=(0.7949398282935892, + 1.436229679394333))), + (Parameters(table=[[100, 2], [1000, 5]], + confidence_level=0.95, + alternative='less'), + RResults(pvalue=0.1300759363430016, + conditional_odds_ratio=0.25055839934223, + conditional_odds_ratio_ci=(0, + 1.797867027270803))), + (Parameters(table=[[2, 7], [8, 2]], + confidence_level=0.95, + alternative='less'), + RResults(pvalue=0.0185217259520665, + conditional_odds_ratio=0.0858623513573622, + conditional_odds_ratio_ci=(0, + 0.6785254803404526))), + (Parameters(table=[[5, 1], [10, 10]], + confidence_level=0.95, + alternative='less'), + RResults(pvalue=0.9782608695652173, + conditional_odds_ratio=4.725646047336587, + conditional_odds_ratio_ci=(0, + 127.8497388102893))), + (Parameters(table=[[5, 15], [20, 20]], + confidence_level=0.95, + alternative='less'), + RResults(pvalue=0.05625775074399956, + conditional_odds_ratio=0.3394396617440851, + conditional_odds_ratio_ci=(0, + 1.032332939718425))), + (Parameters(table=[[5, 16], [16, 25]], + confidence_level=0.95, + alternative='less'), + RResults(pvalue=0.1808979350599346, + conditional_odds_ratio=0.4937791394540491, + conditional_odds_ratio_ci=(0, + 1.502407513296985))), + (Parameters(table=[[10, 5], [10, 1]], + confidence_level=0.95, + alternative='less'), + RResults(pvalue=0.1652173913043479, + conditional_odds_ratio=0.2116112781158479, + conditional_odds_ratio_ci=(0, + 1.820421051562392))), + (Parameters(table=[[10, 5], [10, 0]], + confidence_level=0.95, + alternative='less'), + RResults(pvalue=0.0565217391304348, + conditional_odds_ratio=0, + conditional_odds_ratio_ci=(0, + 1.06224603077045))), + (Parameters(table=[[5, 0], [1, 4]], + confidence_level=0.95, + alternative='less'), + RResults(pvalue=1, + conditional_odds_ratio=Inf, + conditional_odds_ratio_ci=(0, + Inf))), + (Parameters(table=[[0, 5], [1, 4]], + confidence_level=0.95, + alternative='less'), + RResults(pvalue=0.5, + conditional_odds_ratio=0, + conditional_odds_ratio_ci=(0, + 19.00192394479939))), + (Parameters(table=[[5, 1], [0, 4]], + confidence_level=0.95, + alternative='less'), + RResults(pvalue=1, + conditional_odds_ratio=Inf, + conditional_odds_ratio_ci=(0, + Inf))), + (Parameters(table=[[0, 1], [3, 2]], + confidence_level=0.95, + alternative='less'), + RResults(pvalue=0.4999999999999999, + conditional_odds_ratio=0, + conditional_odds_ratio_ci=(0, + 19.00192394479939))), + (Parameters(table=[[200, 7], [8, 300]], + confidence_level=0.95, + alternative='less'), + RResults(pvalue=1, + conditional_odds_ratio=977.7866978606228, + conditional_odds_ratio_ci=(0, + 3045.460216525746))), + (Parameters(table=[[28, 21], [6, 1957]], + confidence_level=0.95, + alternative='less'), + RResults(pvalue=1, + conditional_odds_ratio=425.2403028434684, + conditional_odds_ratio_ci=(0, + 1186.440170942579))), + (Parameters(table=[[190, 800], [200, 900]], + confidence_level=0.95, + alternative='less'), + RResults(pvalue=0.7416227010368963, + conditional_odds_ratio=1.068697577856801, + conditional_odds_ratio_ci=(0, + 1.293551891610822))), + (Parameters(table=[[100, 2], [1000, 5]], + confidence_level=0.99, + alternative='less'), + RResults(pvalue=0.1300759363430016, + conditional_odds_ratio=0.25055839934223, + conditional_odds_ratio_ci=(0, + 4.375946050832565))), + (Parameters(table=[[2, 7], [8, 2]], + confidence_level=0.99, + alternative='less'), + RResults(pvalue=0.0185217259520665, + conditional_odds_ratio=0.0858623513573622, + conditional_odds_ratio_ci=(0, + 1.235282118191202))), + (Parameters(table=[[5, 1], [10, 10]], + confidence_level=0.99, + alternative='less'), + RResults(pvalue=0.9782608695652173, + conditional_odds_ratio=4.725646047336587, + conditional_odds_ratio_ci=(0, + 657.2063583945989))), + (Parameters(table=[[5, 15], [20, 20]], + confidence_level=0.99, + alternative='less'), + RResults(pvalue=0.05625775074399956, + conditional_odds_ratio=0.3394396617440851, + conditional_odds_ratio_ci=(0, + 1.498867660683128))), + (Parameters(table=[[5, 16], [16, 25]], + confidence_level=0.99, + alternative='less'), + RResults(pvalue=0.1808979350599346, + conditional_odds_ratio=0.4937791394540491, + conditional_odds_ratio_ci=(0, + 2.186159386716762))), + (Parameters(table=[[10, 5], [10, 1]], + confidence_level=0.99, + alternative='less'), + RResults(pvalue=0.1652173913043479, + conditional_odds_ratio=0.2116112781158479, + conditional_odds_ratio_ci=(0, + 3.335351451901569))), + (Parameters(table=[[10, 5], [10, 0]], + confidence_level=0.99, + alternative='less'), + RResults(pvalue=0.0565217391304348, + conditional_odds_ratio=0, + conditional_odds_ratio_ci=(0, + 2.075407697450433))), + (Parameters(table=[[5, 0], [1, 4]], + confidence_level=0.99, + alternative='less'), + RResults(pvalue=1, + conditional_odds_ratio=Inf, + conditional_odds_ratio_ci=(0, + Inf))), + (Parameters(table=[[0, 5], [1, 4]], + confidence_level=0.99, + alternative='less'), + RResults(pvalue=0.5, + conditional_odds_ratio=0, + conditional_odds_ratio_ci=(0, + 99.00009507969122))), + (Parameters(table=[[5, 1], [0, 4]], + confidence_level=0.99, + alternative='less'), + RResults(pvalue=1, + conditional_odds_ratio=Inf, + conditional_odds_ratio_ci=(0, + Inf))), + (Parameters(table=[[0, 1], [3, 2]], + confidence_level=0.99, + alternative='less'), + RResults(pvalue=0.4999999999999999, + conditional_odds_ratio=0, + conditional_odds_ratio_ci=(0, + 99.00009507969123))), + (Parameters(table=[[200, 7], [8, 300]], + confidence_level=0.99, + alternative='less'), + RResults(pvalue=1, + conditional_odds_ratio=977.7866978606228, + conditional_odds_ratio_ci=(0, + 4503.078257659934))), + (Parameters(table=[[28, 21], [6, 1957]], + confidence_level=0.99, + alternative='less'), + RResults(pvalue=1, + conditional_odds_ratio=425.2403028434684, + conditional_odds_ratio_ci=(0, + 1811.766127544222))), + (Parameters(table=[[190, 800], [200, 900]], + confidence_level=0.99, + alternative='less'), + RResults(pvalue=0.7416227010368963, + conditional_odds_ratio=1.068697577856801, + conditional_odds_ratio_ci=(0, + 1.396522811516685))), + (Parameters(table=[[100, 2], [1000, 5]], + confidence_level=0.95, + alternative='greater'), + RResults(pvalue=0.979790445314723, + conditional_odds_ratio=0.25055839934223, + conditional_odds_ratio_ci=(0.05119649909830196, + Inf))), + (Parameters(table=[[2, 7], [8, 2]], + confidence_level=0.95, + alternative='greater'), + RResults(pvalue=0.9990149169715733, + conditional_odds_ratio=0.0858623513573622, + conditional_odds_ratio_ci=(0.007163749169069961, + Inf))), + (Parameters(table=[[5, 1], [10, 10]], + confidence_level=0.95, + alternative='greater'), + RResults(pvalue=0.1652173913043478, + conditional_odds_ratio=4.725646047336587, + conditional_odds_ratio_ci=(0.5493234651081089, + Inf))), + (Parameters(table=[[5, 15], [20, 20]], + confidence_level=0.95, + alternative='greater'), + RResults(pvalue=0.9849086665340765, + conditional_odds_ratio=0.3394396617440851, + conditional_odds_ratio_ci=(0.1003538933958604, + Inf))), + (Parameters(table=[[5, 16], [16, 25]], + confidence_level=0.95, + alternative='greater'), + RResults(pvalue=0.9330176609214881, + conditional_odds_ratio=0.4937791394540491, + conditional_odds_ratio_ci=(0.146507416280863, + Inf))), + (Parameters(table=[[10, 5], [10, 1]], + confidence_level=0.95, + alternative='greater'), + RResults(pvalue=0.9782608695652174, + conditional_odds_ratio=0.2116112781158479, + conditional_odds_ratio_ci=(0.007821681994077808, + Inf))), + (Parameters(table=[[10, 5], [10, 0]], + confidence_level=0.95, + alternative='greater'), + RResults(pvalue=1, + conditional_odds_ratio=0, + conditional_odds_ratio_ci=(0, + Inf))), + (Parameters(table=[[5, 0], [1, 4]], + confidence_level=0.95, + alternative='greater'), + RResults(pvalue=0.02380952380952382, + conditional_odds_ratio=Inf, + conditional_odds_ratio_ci=(1.487678929918272, + Inf))), + (Parameters(table=[[0, 5], [1, 4]], + confidence_level=0.95, + alternative='greater'), + RResults(pvalue=1, + conditional_odds_ratio=0, + conditional_odds_ratio_ci=(0, + Inf))), + (Parameters(table=[[5, 1], [0, 4]], + confidence_level=0.95, + alternative='greater'), + RResults(pvalue=0.0238095238095238, + conditional_odds_ratio=Inf, + conditional_odds_ratio_ci=(1.487678929918272, + Inf))), + (Parameters(table=[[0, 1], [3, 2]], + confidence_level=0.95, + alternative='greater'), + RResults(pvalue=1, + conditional_odds_ratio=0, + conditional_odds_ratio_ci=(0, + Inf))), + (Parameters(table=[[200, 7], [8, 300]], + confidence_level=0.95, + alternative='greater'), + RResults(pvalue=2.005657880388915e-122, + conditional_odds_ratio=977.7866978606228, + conditional_odds_ratio_ci=(397.784359748113, + Inf))), + (Parameters(table=[[28, 21], [6, 1957]], + confidence_level=0.95, + alternative='greater'), + RResults(pvalue=5.728437460831983e-44, + conditional_odds_ratio=425.2403028434684, + conditional_odds_ratio_ci=(174.7148056880929, + Inf))), + (Parameters(table=[[190, 800], [200, 900]], + confidence_level=0.95, + alternative='greater'), + RResults(pvalue=0.2959825901308897, + conditional_odds_ratio=1.068697577856801, + conditional_odds_ratio_ci=(0.8828406663967776, + Inf))), + (Parameters(table=[[100, 2], [1000, 5]], + confidence_level=0.99, + alternative='greater'), + RResults(pvalue=0.979790445314723, + conditional_odds_ratio=0.25055839934223, + conditional_odds_ratio_ci=(0.03045407081240429, + Inf))), + (Parameters(table=[[2, 7], [8, 2]], + confidence_level=0.99, + alternative='greater'), + RResults(pvalue=0.9990149169715733, + conditional_odds_ratio=0.0858623513573622, + conditional_odds_ratio_ci=(0.002768053063547901, + Inf))), + (Parameters(table=[[5, 1], [10, 10]], + confidence_level=0.99, + alternative='greater'), + RResults(pvalue=0.1652173913043478, + conditional_odds_ratio=4.725646047336587, + conditional_odds_ratio_ci=(0.2998184792279909, + Inf))), + (Parameters(table=[[5, 15], [20, 20]], + confidence_level=0.99, + alternative='greater'), + RResults(pvalue=0.9849086665340765, + conditional_odds_ratio=0.3394396617440851, + conditional_odds_ratio_ci=(0.06180414342643172, + Inf))), + (Parameters(table=[[5, 16], [16, 25]], + confidence_level=0.99, + alternative='greater'), + RResults(pvalue=0.9330176609214881, + conditional_odds_ratio=0.4937791394540491, + conditional_odds_ratio_ci=(0.09037094010066403, + Inf))), + (Parameters(table=[[10, 5], [10, 1]], + confidence_level=0.99, + alternative='greater'), + RResults(pvalue=0.9782608695652174, + conditional_odds_ratio=0.2116112781158479, + conditional_odds_ratio_ci=(0.001521592095430679, + Inf))), + (Parameters(table=[[10, 5], [10, 0]], + confidence_level=0.99, + alternative='greater'), + RResults(pvalue=1, + conditional_odds_ratio=0, + conditional_odds_ratio_ci=(0, + Inf))), + (Parameters(table=[[5, 0], [1, 4]], + confidence_level=0.99, + alternative='greater'), + RResults(pvalue=0.02380952380952382, + conditional_odds_ratio=Inf, + conditional_odds_ratio_ci=(0.6661157890359722, + Inf))), + (Parameters(table=[[0, 5], [1, 4]], + confidence_level=0.99, + alternative='greater'), + RResults(pvalue=1, + conditional_odds_ratio=0, + conditional_odds_ratio_ci=(0, + Inf))), + (Parameters(table=[[5, 1], [0, 4]], + confidence_level=0.99, + alternative='greater'), + RResults(pvalue=0.0238095238095238, + conditional_odds_ratio=Inf, + conditional_odds_ratio_ci=(0.6661157890359725, + Inf))), + (Parameters(table=[[0, 1], [3, 2]], + confidence_level=0.99, + alternative='greater'), + RResults(pvalue=1, + conditional_odds_ratio=0, + conditional_odds_ratio_ci=(0, + Inf))), + (Parameters(table=[[200, 7], [8, 300]], + confidence_level=0.99, + alternative='greater'), + RResults(pvalue=2.005657880388915e-122, + conditional_odds_ratio=977.7866978606228, + conditional_odds_ratio_ci=(297.9619252357688, + Inf))), + (Parameters(table=[[28, 21], [6, 1957]], + confidence_level=0.99, + alternative='greater'), + RResults(pvalue=5.728437460831983e-44, + conditional_odds_ratio=425.2403028434684, + conditional_odds_ratio_ci=(130.3213490295859, + Inf))), + (Parameters(table=[[190, 800], [200, 900]], + confidence_level=0.99, + alternative='greater'), + RResults(pvalue=0.2959825901308897, + conditional_odds_ratio=1.068697577856801, + conditional_odds_ratio_ci=(0.8176272148267533, + Inf))), +] diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/data/jf_skew_t_gamlss_pdf_data.npy b/venv/lib/python3.12/site-packages/scipy/stats/tests/data/jf_skew_t_gamlss_pdf_data.npy new file mode 100644 index 0000000000000000000000000000000000000000..721749bcd853fa5c5efe5a1f5ba6e105658395dc --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/data/jf_skew_t_gamlss_pdf_data.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:254d2dee4a4d547b9331c60243c6fcfcaffd26c8b104d08d4f6045a7645b3bba +size 4064 diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/data/levy_stable/stable-Z1-cdf-sample-data.npy b/venv/lib/python3.12/site-packages/scipy/stats/tests/data/levy_stable/stable-Z1-cdf-sample-data.npy new file mode 100644 index 0000000000000000000000000000000000000000..adda664a7b5442fc0977ddbaa572c864ddd31f08 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/data/levy_stable/stable-Z1-cdf-sample-data.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cf18c1f2d65a232bf2c7121282df31bf2a8be827afafc4ed810ed37457ee898a +size 183728 diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/data/levy_stable/stable-Z1-pdf-sample-data.npy b/venv/lib/python3.12/site-packages/scipy/stats/tests/data/levy_stable/stable-Z1-pdf-sample-data.npy new file mode 100644 index 0000000000000000000000000000000000000000..6c41166721b891a801cdc6828804c6da7233d625 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/data/levy_stable/stable-Z1-pdf-sample-data.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fee99512bab4ccc6569b47b924e4b034e1cdbab5624fafc7e120648bd5f7a128 +size 183688 diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/data/levy_stable/stable-loc-scale-sample-data.npy b/venv/lib/python3.12/site-packages/scipy/stats/tests/data/levy_stable/stable-loc-scale-sample-data.npy new file mode 100644 index 0000000000000000000000000000000000000000..0a1460e407521836a9b73a081609af4ccdb6deae --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/data/levy_stable/stable-loc-scale-sample-data.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f3c719edd5431fb9e7b9ecb6d19e3ca7a9095298bd19f226685b0fca40f0c073 +size 9328 diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/data/nist_anova/AtmWtAg.dat b/venv/lib/python3.12/site-packages/scipy/stats/tests/data/nist_anova/AtmWtAg.dat new file mode 100644 index 0000000000000000000000000000000000000000..30537565fe8c47f74da0e63a39f4b46600f7768f --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/data/nist_anova/AtmWtAg.dat @@ -0,0 +1,108 @@ +NIST/ITL StRD +Dataset Name: AtmWtAg (AtmWtAg.dat) + + +File Format: ASCII + Certified Values (lines 41 to 47) + Data (lines 61 to 108) + + +Procedure: Analysis of Variance + + +Reference: Powell, L.J., Murphy, T.J. and Gramlich, J.W. (1982). + "The Absolute Isotopic Abundance & Atomic Weight + of a Reference Sample of Silver". + NBS Journal of Research, 87, pp. 9-19. + + +Data: 1 Factor + 2 Treatments + 24 Replicates/Cell + 48 Observations + 7 Constant Leading Digits + Average Level of Difficulty + Observed Data + + +Model: 3 Parameters (mu, tau_1, tau_2) + y_{ij} = mu + tau_i + epsilon_{ij} + + + + + + +Certified Values: + +Source of Sums of Mean +Variation df Squares Squares F Statistic + + +Between Instrument 1 3.63834187500000E-09 3.63834187500000E-09 1.59467335677930E+01 +Within Instrument 46 1.04951729166667E-08 2.28155932971014E-10 + + Certified R-Squared 2.57426544538321E-01 + + Certified Residual + Standard Deviation 1.51048314446410E-05 + + + + + + + + + + + +Data: Instrument AgWt + 1 107.8681568 + 1 107.8681465 + 1 107.8681572 + 1 107.8681785 + 1 107.8681446 + 1 107.8681903 + 1 107.8681526 + 1 107.8681494 + 1 107.8681616 + 1 107.8681587 + 1 107.8681519 + 1 107.8681486 + 1 107.8681419 + 1 107.8681569 + 1 107.8681508 + 1 107.8681672 + 1 107.8681385 + 1 107.8681518 + 1 107.8681662 + 1 107.8681424 + 1 107.8681360 + 1 107.8681333 + 1 107.8681610 + 1 107.8681477 + 2 107.8681079 + 2 107.8681344 + 2 107.8681513 + 2 107.8681197 + 2 107.8681604 + 2 107.8681385 + 2 107.8681642 + 2 107.8681365 + 2 107.8681151 + 2 107.8681082 + 2 107.8681517 + 2 107.8681448 + 2 107.8681198 + 2 107.8681482 + 2 107.8681334 + 2 107.8681609 + 2 107.8681101 + 2 107.8681512 + 2 107.8681469 + 2 107.8681360 + 2 107.8681254 + 2 107.8681261 + 2 107.8681450 + 2 107.8681368 diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/data/nist_anova/SiRstv.dat b/venv/lib/python3.12/site-packages/scipy/stats/tests/data/nist_anova/SiRstv.dat new file mode 100644 index 0000000000000000000000000000000000000000..18ea8971fd7a4d67800dafe98ac5ea5acef53025 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/data/nist_anova/SiRstv.dat @@ -0,0 +1,85 @@ +NIST/ITL StRD +Dataset Name: SiRstv (SiRstv.dat) + + +File Format: ASCII + Certified Values (lines 41 to 47) + Data (lines 61 to 85) + + +Procedure: Analysis of Variance + + +Reference: Ehrstein, James and Croarkin, M. Carroll. + Unpublished NIST dataset. + + +Data: 1 Factor + 5 Treatments + 5 Replicates/Cell + 25 Observations + 3 Constant Leading Digits + Lower Level of Difficulty + Observed Data + + +Model: 6 Parameters (mu,tau_1, ... , tau_5) + y_{ij} = mu + tau_i + epsilon_{ij} + + + + + + + + +Certified Values: + +Source of Sums of Mean +Variation df Squares Squares F Statistic + +Between Instrument 4 5.11462616000000E-02 1.27865654000000E-02 1.18046237440255E+00 +Within Instrument 20 2.16636560000000E-01 1.08318280000000E-02 + + Certified R-Squared 1.90999039051129E-01 + + Certified Residual + Standard Deviation 1.04076068334656E-01 + + + + + + + + + + + + +Data: Instrument Resistance + 1 196.3052 + 1 196.1240 + 1 196.1890 + 1 196.2569 + 1 196.3403 + 2 196.3042 + 2 196.3825 + 2 196.1669 + 2 196.3257 + 2 196.0422 + 3 196.1303 + 3 196.2005 + 3 196.2889 + 3 196.0343 + 3 196.1811 + 4 196.2795 + 4 196.1748 + 4 196.1494 + 4 196.1485 + 4 195.9885 + 5 196.2119 + 5 196.1051 + 5 196.1850 + 5 196.0052 + 5 196.2090 diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/data/nist_anova/SmLs01.dat b/venv/lib/python3.12/site-packages/scipy/stats/tests/data/nist_anova/SmLs01.dat new file mode 100644 index 0000000000000000000000000000000000000000..945b24bf35422152a5faba73ed054ab78fda1bdf --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/data/nist_anova/SmLs01.dat @@ -0,0 +1,249 @@ +NIST/ITL StRD +Dataset Name: SmLs01 (SmLs01.dat) + + +File Format: ASCII + Certified Values (lines 41 to 47) + Data (lines 61 to 249) + + +Procedure: Analysis of Variance + + +Reference: Simon, Stephen D. and Lesage, James P. (1989). + "Assessing the Accuracy of ANOVA Calculations in + Statistical Software". + Computational Statistics & Data Analysis, 8, pp. 325-332. + + +Data: 1 Factor + 9 Treatments + 21 Replicates/Cell + 189 Observations + 1 Constant Leading Digit + Lower Level of Difficulty + Generated Data + + +Model: 10 Parameters (mu,tau_1, ... , tau_9) + y_{ij} = mu + tau_i + epsilon_{ij} + + + + + + +Certified Values: + +Source of Sums of Mean +Variation df Squares Squares F Statistic + +Between Treatment 8 1.68000000000000E+00 2.10000000000000E-01 2.10000000000000E+01 +Within Treatment 180 1.80000000000000E+00 1.00000000000000E-02 + + Certified R-Squared 4.82758620689655E-01 + + Certified Residual + Standard Deviation 1.00000000000000E-01 + + + + + + + + + + + + +Data: Treatment Response + 1 1.4 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 2 1.3 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 3 1.5 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 4 1.3 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 5 1.5 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 6 1.3 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 7 1.5 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 8 1.3 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 9 1.5 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/data/nist_anova/SmLs02.dat b/venv/lib/python3.12/site-packages/scipy/stats/tests/data/nist_anova/SmLs02.dat new file mode 100644 index 0000000000000000000000000000000000000000..ee76633a660a48225064bbb86a25f6a2f36c6d9a --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/data/nist_anova/SmLs02.dat @@ -0,0 +1,1869 @@ +NIST/ITL StRD +Dataset Name: SmLs02 (SmLs02.dat) + + +File Format: ASCII + Certified Values (lines 41 to 47) + Data (lines 61 to 1869) + + +Procedure: Analysis of Variance + + +Reference: Simon, Stephen D. and Lesage, James P. (1989). + "Assessing the Accuracy of ANOVA Calculations in + Statistical Software". + Computational Statistics & Data Analysis, 8, pp. 325-332. + + +Data: 1 Factor + 9 Treatments + 201 Replicates/Cell + 1809 Observations + 1 Constant Leading Digit + Lower Level of Difficulty + Generated Data + + +Model: 10 Parameters (mu,tau_1, ... , tau_9) + y_{ij} = mu + tau_i + epsilon_{ij} + + + + + + +Certified Values: + +Source of Sums of Mean +Variation df Squares Squares F Statistic + +Between Treatment 8 1.60800000000000E+01 2.01000000000000E+00 2.01000000000000E+02 +Within Treatment 1800 1.80000000000000E+01 1.00000000000000E-02 + + Certified R-Squared 4.71830985915493E-01 + + Certified Residual + Standard Deviation 1.00000000000000E-01 + + + + + + + + + + + + +Data: Treatment Response + 1 1.4 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 2 1.3 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 3 1.5 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 4 1.3 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 5 1.5 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 6 1.3 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 7 1.5 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 8 1.3 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 9 1.5 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/data/nist_anova/SmLs03.dat b/venv/lib/python3.12/site-packages/scipy/stats/tests/data/nist_anova/SmLs03.dat new file mode 100644 index 0000000000000000000000000000000000000000..55dfa2313ffb152709c58b47c0058567b710d903 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/data/nist_anova/SmLs03.dat @@ -0,0 +1,18069 @@ +NIST/ITL StRD +Dataset Name: SmLs03 (SmLs03.dat) + + +File Format: ASCII + Certified Values (lines 41 to 47) + Data (lines 61 to 18069) + + +Procedure: Analysis of Variance + + +Reference: Simon, Stephen D. and Lesage, James P. (1989). + "Assessing the Accuracy of ANOVA Calculations in + Statistical Software". + Computational Statistics & Data Analysis, 8, pp. 325-332. + + +Data: 1 Factor + 9 Treatments + 2001 Replicates/Cell + 18009 Observations + 1 Constant Leading Digit + Lower Level of Difficulty + Generated Data + + +Model: 10 Parameters (mu,tau_1, ... , tau_9) + y_{ij} = mu + tau_i + epsilon_{ij} + + + + + + +Certified Values: + +Source of Sums of Mean +Variation df Squares Squares F Statistic + +Between Treatment 8 1.60080000000000E+02 2.00100000000000E+01 2.00100000000000E+03 +Within Treatment 18000 1.80000000000000E+02 1.00000000000000E-02 + + Certified R-Squared 4.70712773465067E-01 + + Certified Residual + Standard Deviation 1.00000000000000E-01 + + + + + + + + + + + + +Data: Treatment Response + 1 1.4 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 1 1.3 + 1 1.5 + 2 1.3 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 2 1.2 + 2 1.4 + 3 1.5 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 3 1.4 + 3 1.6 + 4 1.3 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 4 1.2 + 4 1.4 + 5 1.5 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 5 1.4 + 5 1.6 + 6 1.3 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 6 1.2 + 6 1.4 + 7 1.5 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 7 1.4 + 7 1.6 + 8 1.3 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 8 1.2 + 8 1.4 + 9 1.5 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 + 9 1.4 + 9 1.6 diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/data/nist_anova/SmLs04.dat b/venv/lib/python3.12/site-packages/scipy/stats/tests/data/nist_anova/SmLs04.dat new file mode 100644 index 0000000000000000000000000000000000000000..6a2a9fc935a56989b166de9b23f3df3bc4f64879 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/data/nist_anova/SmLs04.dat @@ -0,0 +1,249 @@ +NIST/ITL StRD +Dataset Name: SmLs04 (SmLs04.dat) + + +File Format: ASCII + Certified Values (lines 41 to 47) + Data (lines 61 to 249) + + +Procedure: Analysis of Variance + + +Reference: Simon, Stephen D. and Lesage, James P. (1989). + "Assessing the Accuracy of ANOVA Calculations in + Statistical Software". + Computational Statistics & Data Analysis, 8, pp. 325-332. + + +Data: 1 Factor + 9 Treatments + 21 Replicates/Cell + 189 Observations + 7 Constant Leading Digits + Average Level of Difficulty + Generated Data + + +Model: 10 Parameters (mu,tau_1, ... , tau_9) + y_{ij} = mu + tau_i + epsilon_{ij} + + + + + + +Certified Values: + +Source of Sums of Mean +Variation df Squares Squares F Statistic + +Between Treatment 8 1.68000000000000E+00 2.10000000000000E-01 2.10000000000000E+01 +Within Treatment 180 1.80000000000000E+00 1.00000000000000E-02 + + Certified R-Squared 4.82758620689655E-01 + + Certified Residual + Standard Deviation 1.00000000000000E-01 + + + + + + + + + + + + +Data: Treatment Response + 1 1000000.4 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 2 1000000.3 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 3 1000000.5 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 4 1000000.3 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 5 1000000.5 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 6 1000000.3 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 7 1000000.5 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 8 1000000.3 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 9 1000000.5 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/data/nist_anova/SmLs05.dat b/venv/lib/python3.12/site-packages/scipy/stats/tests/data/nist_anova/SmLs05.dat new file mode 100644 index 0000000000000000000000000000000000000000..fe11c40b5f51aefc81d4d1501a74e627f2b2d992 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/data/nist_anova/SmLs05.dat @@ -0,0 +1,1869 @@ +NIST/ITL StRD +Dataset Name: SmLs05 (SmLs05.dat) + + +File Format: ASCII + Certified Values (lines 41 to 47) + Data (lines 61 to 1869) + + +Procedure: Analysis of Variance + + +Reference: Simon, Stephen D. and Lesage, James P. (1989). + "Assessing the Accuracy of ANOVA Calculations in + Statistical Software". + Computational Statistics & Data Analysis, 8, pp. 325-332. + + +Data: 1 Factor + 9 Treatments + 201 Replicates/Cell + 1809 Observations + 7 Constant Leading Digits + Average Level of Difficulty + Generated Data + + +Model: 10 Parameters (mu,tau_1, ... , tau_9) + y_{ij} = mu + tau_i + epsilon_{ij} + + + + + + +Certified Values: + +Source of Sums of Mean +Variation df Squares Squares F Statistic + +Between Treatment 8 1.60800000000000E+01 2.01000000000000E+00 2.01000000000000E+02 +Within Treatment 1800 1.80000000000000E+01 1.00000000000000E-02 + + Certified R-Squared 4.71830985915493E-01 + + Certified Residual + Standard Deviation 1.00000000000000E-01 + + + + + + + + + + + + +Data: Treatment Response + 1 1000000.4 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 2 1000000.3 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 3 1000000.5 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 4 1000000.3 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 5 1000000.5 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 6 1000000.3 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 7 1000000.5 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 8 1000000.3 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 9 1000000.5 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/data/nist_anova/SmLs06.dat b/venv/lib/python3.12/site-packages/scipy/stats/tests/data/nist_anova/SmLs06.dat new file mode 100644 index 0000000000000000000000000000000000000000..602e4fbdaa26bbb8d95ce78d1f48dbbfa883e7e9 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/data/nist_anova/SmLs06.dat @@ -0,0 +1,18069 @@ +NIST/ITL StRD +Dataset Name: SmLs06 (SmLs06.dat) + + +File Format: ASCII + Certified Values (lines 41 to 47) + Data (lines 61 to 18069) + + +Procedure: Analysis of Variance + + +Reference: Simon, Stephen D. and Lesage, James P. (1989). + "Assessing the Accuracy of ANOVA Calculations in + Statistical Software". + Computational Statistics & Data Analysis, 8, pp. 325-332. + + +Data: 1 Factor + 9 Treatments + 2001 Replicates/Cell + 18009 Observations + 7 Constant Leading Digits + Average Level of Difficulty + Generated Data + + +Model: 10 Parameters (mu,tau_1, ... , tau_9) + y_{ij} = mu + tau_i + epsilon_{ij} + + + + + + +Certified Values: + +Source of Sums of Mean +Variation df Squares Squares F Statistic + +Between Treatment 8 1.60080000000000E+02 2.00100000000000E+01 2.00100000000000E+03 +Within Treatment 18000 1.80000000000000E+02 1.00000000000000E-02 + + Certified R-Squared 4.70712773465067E-01 + + Certified Residual + Standard Deviation 1.00000000000000E-01 + + + + + + + + + + + + +Data: Treatment Response + 1 1000000.4 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 1 1000000.3 + 1 1000000.5 + 2 1000000.3 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 2 1000000.2 + 2 1000000.4 + 3 1000000.5 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 3 1000000.4 + 3 1000000.6 + 4 1000000.3 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 4 1000000.2 + 4 1000000.4 + 5 1000000.5 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 5 1000000.4 + 5 1000000.6 + 6 1000000.3 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 6 1000000.2 + 6 1000000.4 + 7 1000000.5 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 7 1000000.4 + 7 1000000.6 + 8 1000000.3 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 8 1000000.2 + 8 1000000.4 + 9 1000000.5 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 + 9 1000000.4 + 9 1000000.6 diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/data/nist_anova/SmLs07.dat b/venv/lib/python3.12/site-packages/scipy/stats/tests/data/nist_anova/SmLs07.dat new file mode 100644 index 0000000000000000000000000000000000000000..deeac955e65ffaf55838568baa54951efaf2662b --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/data/nist_anova/SmLs07.dat @@ -0,0 +1,249 @@ +NIST/ITL StRD +Dataset Name: SmLs07 (SmLs07.dat) + + +File Format: ASCII + Certified Values (lines 41 to 47) + Data (lines 61 to 249) + + +Procedure: Analysis of Variance + + +Reference: Simon, Stephen D. and Lesage, James P. (1989). + "Assessing the Accuracy of ANOVA Calculations in + Statistical Software". + Computational Statistics & Data Analysis, 8, pp. 325-332. + + +Data: 1 Factor + 9 Treatments + 21 Replicates/Cell + 189 Observations + 13 Constant Leading Digits + Higher Level of Difficulty + Generated Data + + +Model: 10 Parameters (mu,tau_1, ... , tau_9) + y_{ij} = mu + tau_i + epsilon_{ij} + + + + + + +Certified Values: + +Source of Sums of Mean +Variation df Squares Squares F Statistic + +Between Treatment 8 1.68000000000000E+00 2.10000000000000E-01 2.10000000000000E+01 +Within Treatment 180 1.80000000000000E+00 1.00000000000000E-02 + + Certified R-Squared 4.82758620689655E-01 + + Certified Residual + Standard Deviation 1.00000000000000E-01 + + + + + + + + + + + + +Data: Treatment Response + 1 1000000000000.4 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 2 1000000000000.3 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 3 1000000000000.5 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 4 1000000000000.3 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 5 1000000000000.5 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 6 1000000000000.3 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 7 1000000000000.5 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 8 1000000000000.3 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 9 1000000000000.5 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/data/nist_anova/SmLs08.dat b/venv/lib/python3.12/site-packages/scipy/stats/tests/data/nist_anova/SmLs08.dat new file mode 100644 index 0000000000000000000000000000000000000000..c5ee643fb8c6ef849ab8e34352bc60f15c715a45 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/data/nist_anova/SmLs08.dat @@ -0,0 +1,1869 @@ +NIST/ITL StRD +Dataset Name: SmLs08 (SmLs08.dat) + + +File Format: ASCII + Certified Values (lines 41 to 47) + Data (lines 61 to 1869) + + +Procedure: Analysis of Variance + + +Reference: Simon, Stephen D. and Lesage, James P. (1989). + "Assessing the Accuracy of ANOVA Calculations in + Statistical Software". + Computational Statistics & Data Analysis, 8, pp. 325-332. + + +Data: 1 Factor + 9 Treatments + 201 Replicates/Cell + 1809 Observations + 13 Constant Leading Digits + Higher Level of Difficulty + Generated Data + + +Model: 10 Parameters (mu,tau_1, ... , tau_9) + y_{ij} = mu + tau_i + epsilon_{ij} + + + + + + +Certified Values: + +Source of Sums of Mean +Variation df Squares Squares F Statistic + +Between Treatment 8 1.60800000000000E+01 2.01000000000000E+00 2.01000000000000E+02 +Within Treatment 1800 1.80000000000000E+01 1.00000000000000E-02 + + Certified R-Squared 4.71830985915493E-01 + + Certified Residual + Standard Deviation 1.00000000000000E-01 + + + + + + + + + + + + +Data: Treatment Response + 1 1000000000000.4 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 2 1000000000000.3 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 3 1000000000000.5 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 4 1000000000000.3 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 5 1000000000000.5 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 6 1000000000000.3 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 7 1000000000000.5 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 8 1000000000000.3 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 9 1000000000000.5 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/data/nist_anova/SmLs09.dat b/venv/lib/python3.12/site-packages/scipy/stats/tests/data/nist_anova/SmLs09.dat new file mode 100644 index 0000000000000000000000000000000000000000..887905e355a2a13801f1b004187631f2301f7eef --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/data/nist_anova/SmLs09.dat @@ -0,0 +1,18069 @@ +NIST/ITL StRD +Dataset Name: SmLs09 (SmLs09.dat) + + +File Format: ASCII + Certified Values (lines 41 to 47) + Data (lines 61 to 18069) + + +Procedure: Analysis of Variance + + +Reference: Simon, Stephen D. and Lesage, James P. (1989). + "Assessing the Accuracy of ANOVA Calculations in + Statistical Software". + Computational Statistics & Data Analysis, 8, pp. 325-332. + + +Data: 1 Factor + 9 Treatments + 2001 Replicates/Cell + 18009 Observations + 13 Constant Leading Digits + Higher Level of Difficulty + Generated Data + + +Model: 10 Parameters (mu,tau_1, ... , tau_9) + y_{ij} = mu + tau_i + epsilon_{ij} + + + + + + +Certified Values: + +Source of Sums of Mean +Variation df Squares Squares F Statistic + +Between Treatment 8 1.60080000000000E+02 2.00100000000000E+01 2.00100000000000E+03 +Within Treatment 18000 1.80000000000000E+02 1.00000000000000E-02 + + Certified R-Squared 4.70712773465067E-01 + + Certified Residual + Standard Deviation 1.00000000000000E-01 + + + + + + + + + + + + +Data: Treatment Response + 1 1000000000000.4 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 1 1000000000000.3 + 1 1000000000000.5 + 2 1000000000000.3 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 2 1000000000000.2 + 2 1000000000000.4 + 3 1000000000000.5 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 3 1000000000000.4 + 3 1000000000000.6 + 4 1000000000000.3 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 4 1000000000000.2 + 4 1000000000000.4 + 5 1000000000000.5 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 5 1000000000000.4 + 5 1000000000000.6 + 6 1000000000000.3 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 6 1000000000000.2 + 6 1000000000000.4 + 7 1000000000000.5 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 7 1000000000000.4 + 7 1000000000000.6 + 8 1000000000000.3 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 8 1000000000000.2 + 8 1000000000000.4 + 9 1000000000000.5 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 + 9 1000000000000.4 + 9 1000000000000.6 diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/data/nist_linregress/Norris.dat b/venv/lib/python3.12/site-packages/scipy/stats/tests/data/nist_linregress/Norris.dat new file mode 100644 index 0000000000000000000000000000000000000000..4bf8ed911cae75824b27e5f5d5e444e17fa8eae8 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/data/nist_linregress/Norris.dat @@ -0,0 +1,97 @@ +NIST/ITL StRD +Dataset Name: Norris (Norris.dat) + +File Format: ASCII + Certified Values (lines 31 to 46) + Data (lines 61 to 96) + +Procedure: Linear Least Squares Regression + +Reference: Norris, J., NIST. + Calibration of Ozone Monitors. + +Data: 1 Response Variable (y) + 1 Predictor Variable (x) + 36 Observations + Lower Level of Difficulty + Observed Data + +Model: Linear Class + 2 Parameters (B0,B1) + + y = B0 + B1*x + e + + + + Certified Regression Statistics + + Standard Deviation + Parameter Estimate of Estimate + + B0 -0.262323073774029 0.232818234301152 + B1 1.00211681802045 0.429796848199937E-03 + + Residual + Standard Deviation 0.884796396144373 + + R-Squared 0.999993745883712 + + + Certified Analysis of Variance Table + +Source of Degrees of Sums of Mean +Variation Freedom Squares Squares F Statistic + +Regression 1 4255954.13232369 4255954.13232369 5436385.54079785 +Residual 34 26.6173985294224 0.782864662630069 + + + + + + + + + + + + + +Data: y x + 0.1 0.2 + 338.8 337.4 + 118.1 118.2 + 888.0 884.6 + 9.2 10.1 + 228.1 226.5 + 668.5 666.3 + 998.5 996.3 + 449.1 448.6 + 778.9 777.0 + 559.2 558.2 + 0.3 0.4 + 0.1 0.6 + 778.1 775.5 + 668.8 666.9 + 339.3 338.0 + 448.9 447.5 + 10.8 11.6 + 557.7 556.0 + 228.3 228.1 + 998.0 995.8 + 888.8 887.6 + 119.6 120.2 + 0.3 0.3 + 0.6 0.3 + 557.6 556.8 + 339.3 339.1 + 888.0 887.2 + 998.5 999.0 + 778.9 779.0 + 10.2 11.1 + 117.6 118.3 + 228.9 229.2 + 668.4 669.1 + 449.2 448.9 + 0.2 0.5 + diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/data/rel_breitwigner_pdf_sample_data_ROOT.npy b/venv/lib/python3.12/site-packages/scipy/stats/tests/data/rel_breitwigner_pdf_sample_data_ROOT.npy new file mode 100644 index 0000000000000000000000000000000000000000..80dde74dcda9a23dcdbf9a2f677eb9c98337b0a7 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/data/rel_breitwigner_pdf_sample_data_ROOT.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eef4dc702dd8c6e31c18c74e1f81284c3e9ca2ab50282de39c9ad30b7bb8e76d +size 38624 diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/data/studentized_range_mpmath_ref.json b/venv/lib/python3.12/site-packages/scipy/stats/tests/data/studentized_range_mpmath_ref.json new file mode 100644 index 0000000000000000000000000000000000000000..bb971286cf85b28738a80bacececfb90c2566782 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/data/studentized_range_mpmath_ref.json @@ -0,0 +1,1499 @@ +{ + "COMMENT": "!!!!!! THIS FILE WAS AUTOGENERATED BY RUNNING `python studentized_range_mpmath_ref.py` !!!!!!", + "moment_data": [ + { + "src_case": { + "m": 0, + "k": 3, + "v": 10, + "expected_atol": 1e-09, + "expected_rtol": 1e-09 + }, + "mp_result": 1.0 + }, + { + "src_case": { + "m": 1, + "k": 3, + "v": 10, + "expected_atol": 1e-09, + "expected_rtol": 1e-09 + }, + "mp_result": 1.8342745127927962 + }, + { + "src_case": { + "m": 2, + "k": 3, + "v": 10, + "expected_atol": 1e-09, + "expected_rtol": 1e-09 + }, + "mp_result": 4.567483357831711 + }, + { + "src_case": { + "m": 3, + "k": 3, + "v": 10, + "expected_atol": 1e-09, + "expected_rtol": 1e-09 + }, + "mp_result": 14.412156886227011 + }, + { + "src_case": { + "m": 4, + "k": 3, + "v": 10, + "expected_atol": 1e-09, + "expected_rtol": 1e-09 + }, + "mp_result": 56.012250366720444 + } + ], + "cdf_data": [ + { + "src_case": { + "q": 0.1, + "k": 3, + "v": 3, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.0027502772229359594 + }, + { + "src_case": { + "q": 0.1, + "k": 10, + "v": 10, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 2.8544145010066327e-12 + }, + { + "src_case": { + "q": 0.1, + "k": 3, + "v": 10, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.0027520560662338336 + }, + { + "src_case": { + "q": 0.1, + "k": 10, + "v": 100, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 9.39089126131273e-13 + }, + { + "src_case": { + "q": 0.1, + "k": 3, + "v": 20, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.002752437649536182 + }, + { + "src_case": { + "q": 0.1, + "k": 10, + "v": 50, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 1.0862189999210748e-12 + }, + { + "src_case": { + "q": 0.1, + "k": 3, + "v": 120, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.002752755744313648 + }, + { + "src_case": { + "q": 0.1, + "k": 3, + "v": 100, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.0027527430186246545 + }, + { + "src_case": { + "q": 0.1, + "k": 3, + "v": 50, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.002752666667812431 + }, + { + "src_case": { + "q": 0.1, + "k": 20, + "v": 10, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 2.505275157135514e-24 + }, + { + "src_case": { + "q": 0.1, + "k": 20, + "v": 20, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 3.8546698113384126e-25 + }, + { + "src_case": { + "q": 0.1, + "k": 10, + "v": 3, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 1.7362668562706085e-11 + }, + { + "src_case": { + "q": 0.1, + "k": 20, + "v": 50, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 5.571947730052616e-26 + }, + { + "src_case": { + "q": 0.1, + "k": 20, + "v": 100, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 2.032619249089036e-27 + }, + { + "src_case": { + "q": 0.1, + "k": 20, + "v": 3, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 9.539763646681808e-22 + }, + { + "src_case": { + "q": 0.1, + "k": 10, + "v": 20, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 1.618313512511099e-12 + }, + { + "src_case": { + "q": 0.1, + "k": 20, + "v": 120, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 4.919231733354114e-28 + }, + { + "src_case": { + "q": 0.1, + "k": 10, + "v": 120, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 9.159348906295542e-13 + }, + { + "src_case": { + "q": 1, + "k": 3, + "v": 3, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.22331624289542043 + }, + { + "src_case": { + "q": 1, + "k": 3, + "v": 50, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.2395624637676257 + }, + { + "src_case": { + "q": 1, + "k": 3, + "v": 10, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.23510918942128056 + }, + { + "src_case": { + "q": 1, + "k": 3, + "v": 20, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.23786536230099864 + }, + { + "src_case": { + "q": 1, + "k": 10, + "v": 50, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.000651656693149116 + }, + { + "src_case": { + "q": 1, + "k": 3, + "v": 100, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.2401356460422021 + }, + { + "src_case": { + "q": 1, + "k": 10, + "v": 3, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.003971273224673166 + }, + { + "src_case": { + "q": 1, + "k": 10, + "v": 20, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.0008732969319364606 + }, + { + "src_case": { + "q": 1, + "k": 3, + "v": 120, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.24023154593376422 + }, + { + "src_case": { + "q": 1, + "k": 10, + "v": 10, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.001300816146573152 + }, + { + "src_case": { + "q": 1, + "k": 20, + "v": 50, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 1.5682573722040226e-07 + }, + { + "src_case": { + "q": 1, + "k": 10, + "v": 100, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.0005841098057517027 + }, + { + "src_case": { + "q": 1, + "k": 20, + "v": 3, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 9.2267674885784e-05 + }, + { + "src_case": { + "q": 1, + "k": 10, + "v": 120, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.0005731712496327297 + }, + { + "src_case": { + "q": 1, + "k": 20, + "v": 10, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 2.746798012658064e-06 + }, + { + "src_case": { + "q": 1, + "k": 20, + "v": 20, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 5.807700350854172e-07 + }, + { + "src_case": { + "q": 1, + "k": 20, + "v": 100, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 9.147637957472628e-08 + }, + { + "src_case": { + "q": 1, + "k": 20, + "v": 120, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 8.306675539750552e-08 + }, + { + "src_case": { + "q": 4, + "k": 3, + "v": 3, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.8711786295203324 + }, + { + "src_case": { + "q": 4, + "k": 3, + "v": 50, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.9818862781476212 + }, + { + "src_case": { + "q": 4, + "k": 3, + "v": 10, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.9566506502400175 + }, + { + "src_case": { + "q": 4, + "k": 3, + "v": 120, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.9849546621386962 + }, + { + "src_case": { + "q": 4, + "k": 3, + "v": 20, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.9731488893573804 + }, + { + "src_case": { + "q": 4, + "k": 10, + "v": 50, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.8450530667988544 + }, + { + "src_case": { + "q": 4, + "k": 10, + "v": 3, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.6164875232404174 + }, + { + "src_case": { + "q": 4, + "k": 3, + "v": 100, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.9845292772767739 + }, + { + "src_case": { + "q": 4, + "k": 10, + "v": 20, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.8079691517949077 + }, + { + "src_case": { + "q": 4, + "k": 10, + "v": 10, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.7573606942645745 + }, + { + "src_case": { + "q": 4, + "k": 10, + "v": 100, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.8587525248147736 + }, + { + "src_case": { + "q": 4, + "k": 10, + "v": 120, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.8611036193280976 + }, + { + "src_case": { + "q": 4, + "k": 20, + "v": 3, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.46523135355387657 + }, + { + "src_case": { + "q": 4, + "k": 20, + "v": 50, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.6318042819232383 + }, + { + "src_case": { + "q": 4, + "k": 20, + "v": 10, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.5574947140294286 + }, + { + "src_case": { + "q": 4, + "k": 20, + "v": 20, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.5970517763141937 + }, + { + "src_case": { + "q": 4, + "k": 20, + "v": 120, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.6493671527818267 + }, + { + "src_case": { + "q": 4, + "k": 20, + "v": 100, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.6466699776044968 + }, + { + "src_case": { + "q": 10, + "k": 3, + "v": 3, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.9881335633712994 + }, + { + "src_case": { + "q": 10, + "k": 3, + "v": 50, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.9999999861266821 + }, + { + "src_case": { + "q": 10, + "k": 3, + "v": 10, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.999908236635449 + }, + { + "src_case": { + "q": 10, + "k": 3, + "v": 20, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.9999978467928313 + }, + { + "src_case": { + "q": 10, + "k": 3, + "v": 120, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.9999999996690216 + }, + { + "src_case": { + "q": 10, + "k": 3, + "v": 100, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.9999999993640496 + }, + { + "src_case": { + "q": 10, + "k": 10, + "v": 3, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.9570401457077894 + }, + { + "src_case": { + "q": 10, + "k": 10, + "v": 50, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.9999997977351971 + }, + { + "src_case": { + "q": 10, + "k": 10, + "v": 10, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.9991738325963548 + }, + { + "src_case": { + "q": 10, + "k": 10, + "v": 20, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.9999730883609333 + }, + { + "src_case": { + "q": 10, + "k": 10, + "v": 100, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.9999999905199205 + }, + { + "src_case": { + "q": 10, + "k": 10, + "v": 120, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.9999999950566264 + }, + { + "src_case": { + "q": 10, + "k": 20, + "v": 3, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.9312318042339768 + }, + { + "src_case": { + "q": 10, + "k": 20, + "v": 50, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.9999991743904675 + }, + { + "src_case": { + "q": 10, + "k": 20, + "v": 10, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.9977643922032399 + }, + { + "src_case": { + "q": 10, + "k": 20, + "v": 20, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.9999054426012515 + }, + { + "src_case": { + "q": 10, + "k": 20, + "v": 100, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.9999999602948055 + }, + { + "src_case": { + "q": 10, + "k": 20, + "v": 120, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.9999999792458618 + } + ], + "pdf_data": [ + { + "src_case": { + "q": 0.1, + "k": 3, + "v": 3, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.05487847613526332 + }, + { + "src_case": { + "q": 0.1, + "k": 10, + "v": 10, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 2.564099684606509e-10 + }, + { + "src_case": { + "q": 0.1, + "k": 3, + "v": 10, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.05494947290360002 + }, + { + "src_case": { + "q": 0.1, + "k": 10, + "v": 100, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 8.442593793786411e-11 + }, + { + "src_case": { + "q": 0.1, + "k": 3, + "v": 20, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.054964710604860405 + }, + { + "src_case": { + "q": 0.1, + "k": 10, + "v": 50, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 9.764441961563576e-11 + }, + { + "src_case": { + "q": 0.1, + "k": 3, + "v": 100, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.05497690690332341 + }, + { + "src_case": { + "q": 0.1, + "k": 3, + "v": 50, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.05497385731702228 + }, + { + "src_case": { + "q": 0.1, + "k": 20, + "v": 10, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 4.758021225803992e-22 + }, + { + "src_case": { + "q": 0.1, + "k": 3, + "v": 120, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.054977415200879516 + }, + { + "src_case": { + "q": 0.1, + "k": 20, + "v": 3, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 1.8004731453548083e-19 + }, + { + "src_case": { + "q": 0.1, + "k": 10, + "v": 3, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 1.5564176176604816e-09 + }, + { + "src_case": { + "q": 0.1, + "k": 20, + "v": 50, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 9.342768070688728e-24 + }, + { + "src_case": { + "q": 0.1, + "k": 10, + "v": 20, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 1.454372265306114e-10 + }, + { + "src_case": { + "q": 0.1, + "k": 20, + "v": 100, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 3.9138464398429654e-25 + }, + { + "src_case": { + "q": 0.1, + "k": 20, + "v": 20, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 5.266341131767418e-23 + }, + { + "src_case": { + "q": 0.1, + "k": 10, + "v": 120, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 8.234556126446594e-11 + }, + { + "src_case": { + "q": 0.1, + "k": 20, + "v": 120, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 9.32929780487562e-26 + }, + { + "src_case": { + "q": 1, + "k": 3, + "v": 3, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.36083736990527154 + }, + { + "src_case": { + "q": 1, + "k": 3, + "v": 50, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.4137959132282269 + }, + { + "src_case": { + "q": 1, + "k": 3, + "v": 20, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.4080239698771056 + }, + { + "src_case": { + "q": 1, + "k": 3, + "v": 10, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.398772020275752 + }, + { + "src_case": { + "q": 1, + "k": 3, + "v": 120, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.4160873922094346 + }, + { + "src_case": { + "q": 1, + "k": 3, + "v": 100, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.4157583991350054 + }, + { + "src_case": { + "q": 1, + "k": 10, + "v": 50, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.005210720148451848 + }, + { + "src_case": { + "q": 1, + "k": 10, + "v": 3, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.02575314059867804 + }, + { + "src_case": { + "q": 1, + "k": 10, + "v": 10, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.009782573637596617 + }, + { + "src_case": { + "q": 1, + "k": 10, + "v": 20, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.006818708302379005 + }, + { + "src_case": { + "q": 1, + "k": 10, + "v": 100, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.0047089182958790715 + }, + { + "src_case": { + "q": 1, + "k": 10, + "v": 120, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.004627085294166373 + }, + { + "src_case": { + "q": 1, + "k": 20, + "v": 3, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.0010886280311369462 + }, + { + "src_case": { + "q": 1, + "k": 20, + "v": 50, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 2.630674470916427e-06 + }, + { + "src_case": { + "q": 1, + "k": 20, + "v": 10, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 4.121713278199428e-05 + }, + { + "src_case": { + "q": 1, + "k": 20, + "v": 20, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 9.319506007252685e-06 + }, + { + "src_case": { + "q": 1, + "k": 20, + "v": 100, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 1.5585754418789747e-06 + }, + { + "src_case": { + "q": 1, + "k": 20, + "v": 120, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 1.4190335899441991e-06 + }, + { + "src_case": { + "q": 4, + "k": 3, + "v": 3, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.07185383302009114 + }, + { + "src_case": { + "q": 4, + "k": 3, + "v": 10, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.050268901219386576 + }, + { + "src_case": { + "q": 4, + "k": 3, + "v": 50, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.03321056847176124 + }, + { + "src_case": { + "q": 4, + "k": 3, + "v": 20, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.04044172384981084 + }, + { + "src_case": { + "q": 4, + "k": 3, + "v": 100, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.030571365659999617 + }, + { + "src_case": { + "q": 4, + "k": 3, + "v": 120, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.030120779149073032 + }, + { + "src_case": { + "q": 4, + "k": 10, + "v": 3, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.17501664247670937 + }, + { + "src_case": { + "q": 4, + "k": 10, + "v": 10, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.22374394725370736 + }, + { + "src_case": { + "q": 4, + "k": 10, + "v": 50, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.23246597521020534 + }, + { + "src_case": { + "q": 4, + "k": 10, + "v": 20, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.23239043677504484 + }, + { + "src_case": { + "q": 4, + "k": 10, + "v": 100, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.23057775622748988 + }, + { + "src_case": { + "q": 4, + "k": 10, + "v": 120, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.23012666145240815 + }, + { + "src_case": { + "q": 4, + "k": 20, + "v": 3, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.2073676639537027 + }, + { + "src_case": { + "q": 4, + "k": 20, + "v": 10, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.3245990542431859 + }, + { + "src_case": { + "q": 10, + "k": 3, + "v": 3, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.0033733228559870584 + }, + { + "src_case": { + "q": 10, + "k": 3, + "v": 10, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 7.728665739003835e-05 + }, + { + "src_case": { + "q": 4, + "k": 20, + "v": 20, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.38244500549096866 + }, + { + "src_case": { + "q": 4, + "k": 20, + "v": 100, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.45434978340834464 + }, + { + "src_case": { + "q": 4, + "k": 20, + "v": 50, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.43334135870667473 + }, + { + "src_case": { + "q": 10, + "k": 3, + "v": 100, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 2.159522630228393e-09 + }, + { + "src_case": { + "q": 4, + "k": 20, + "v": 120, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.45807877248528855 + }, + { + "src_case": { + "q": 10, + "k": 3, + "v": 50, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 3.5303467191175695e-08 + }, + { + "src_case": { + "q": 10, + "k": 3, + "v": 20, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 3.121281850105421e-06 + }, + { + "src_case": { + "q": 10, + "k": 3, + "v": 120, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 1.1901591191700855e-09 + }, + { + "src_case": { + "q": 10, + "k": 10, + "v": 10, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.0006784051704217357 + }, + { + "src_case": { + "q": 10, + "k": 10, + "v": 3, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.011845582636101885 + }, + { + "src_case": { + "q": 10, + "k": 10, + "v": 20, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 3.844183552674918e-05 + }, + { + "src_case": { + "q": 10, + "k": 10, + "v": 100, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 3.215093171597309e-08 + }, + { + "src_case": { + "q": 10, + "k": 10, + "v": 50, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 5.125792577534542e-07 + }, + { + "src_case": { + "q": 10, + "k": 10, + "v": 120, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 1.7759015355532446e-08 + }, + { + "src_case": { + "q": 10, + "k": 20, + "v": 10, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.0017957646258393628 + }, + { + "src_case": { + "q": 10, + "k": 20, + "v": 3, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.018534407764819284 + }, + { + "src_case": { + "q": 10, + "k": 20, + "v": 20, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 0.00013316083413164858 + }, + { + "src_case": { + "q": 10, + "k": 20, + "v": 50, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 2.082489228991225e-06 + }, + { + "src_case": { + "q": 10, + "k": 20, + "v": 100, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 1.3444226792257012e-07 + }, + { + "src_case": { + "q": 10, + "k": 20, + "v": 120, + "expected_atol": 1e-11, + "expected_rtol": 1e-11 + }, + "mp_result": 7.446912854228521e-08 + } + ] +} \ No newline at end of file diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/test_axis_nan_policy.py b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_axis_nan_policy.py new file mode 100644 index 0000000000000000000000000000000000000000..2b7f1bccccbe41d1cd40dd7479159f31282be151 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_axis_nan_policy.py @@ -0,0 +1,1495 @@ +# Many scipy.stats functions support `axis` and `nan_policy` parameters. +# When the two are combined, it can be tricky to get all the behavior just +# right. This file contains a suite of common tests for scipy.stats functions +# that support `axis` and `nan_policy` and additional tests for some associated +# functions in stats._util. + +from itertools import product, combinations_with_replacement, permutations +import os +import re +import pickle +import pytest +import warnings + +import numpy as np +from numpy.testing import assert_allclose, assert_equal +from scipy import stats, special +from scipy.stats._axis_nan_policy import (_masked_arrays_2_sentinel_arrays, + SmallSampleWarning, + too_small_nd_omit, too_small_nd_not_omit, + too_small_1d_omit, too_small_1d_not_omit) +from scipy._lib._util import AxisError +from scipy._lib._array_api import make_xp_test_case +from scipy.conftest import skip_xp_invalid_arg + + +SCIPY_XSLOW = int(os.environ.get('SCIPY_XSLOW', '0')) + + +def _using_accelerate(): + config = np.show_config('dicts') + return config['Build Dependencies']['blas']['name'].lower() == 'accelerate' + + +RTOL = 1e-6 if _using_accelerate() else 1e-15 + + +tolerance_overrides = {stats.epps_singleton_2samp: 1e-10} + + +def unpack_ttest_result(res): + low, high = res.confidence_interval() + return (res.statistic, res.pvalue, res.df, res._standard_error, + res._estimate, low, high) + + +def _get_ttest_ci(ttest): + # get a function that returns the CI bounds of provided `ttest` + def ttest_ci(*args, **kwargs): + res = ttest(*args, **kwargs) + return res.confidence_interval() + return ttest_ci + + +def xp_mean_1samp(*args, **kwargs): + kwargs.pop('_no_deco', None) + return stats._stats_py._xp_mean(*args, **kwargs) + + +def xp_mean_2samp(*args, **kwargs): + kwargs.pop('_no_deco', None) + weights = args[1] + return stats._stats_py._xp_mean(args[0], *args[2:], weights=weights, **kwargs) + + +def xp_var(*args, **kwargs): + kwargs.pop('_no_deco', None) + return stats._stats_py._xp_var(*args, **kwargs) + + +def gstd(*args, **kwargs): + kwargs.pop('_no_deco', None) + return stats.gstd(*args, **kwargs) + + +def combine_pvalues_weighted(*args, **kwargs): + return stats.combine_pvalues(args[0], *args[2:], weights=args[1], + method='stouffer', **kwargs) + + +def weightedtau_weighted(x, y, rank, **kwargs): + axis = kwargs.get('axis', 0) + nan_policy = kwargs.get('nan_policy', 'propagate') + rank = stats.rankdata(rank, axis=axis, nan_policy=nan_policy) + return stats.weightedtau(x, y, rank, **kwargs) + + +def boxcox_llf(data, lmb, axis=0, _no_deco=False, **kwargs): + if _no_deco: + return stats._morestats._boxcox_llf(data, lmb=lmb, axis=axis) + return stats.boxcox_llf(lmb, data, axis=axis, **kwargs) + + +def yeojohnson_llf(data, lmb, axis=0, _no_deco=False, **kwargs): + if _no_deco: + return stats._morestats._yeojohnson_llf(data, lmb=lmb, axis=axis) + return stats.yeojohnson_llf(lmb, data, axis=axis, **kwargs) + + +def kendalltau(*args, _no_deco=False, **kwargs): + if _no_deco: + return stats._stats_py._kendalltau(*args, _no_deco=_no_deco, **kwargs) + return stats.kendalltau(*args, **kwargs) + + +axis_nan_policy_cases = [ + # function, args, kwds, number of samples, number of outputs, + # ... paired, unpacker function + # args, kwds typically aren't needed; just showing that they work + (stats.kruskal, tuple(), dict(), 3, 2, False, None), # 4 samples is slow + (stats.ranksums, ('less',), dict(), 2, 2, False, None), + (stats.mannwhitneyu, tuple(), {'method': 'asymptotic'}, 2, 3, False, + lambda res: (res.statistic, res.pvalue, res.zstatistic)), + (stats.mannwhitneyu, tuple(), {'method': 'auto'}, 2, 2, False, None), + (stats.wilcoxon, ('pratt',), {'mode': 'auto'}, 2, 2, True, + lambda res: (res.statistic, res.pvalue)), + (stats.wilcoxon, tuple(), dict(), 1, 2, True, + lambda res: (res.statistic, res.pvalue)), + (stats.wilcoxon, tuple(), {'method': 'asymptotic'}, 1, 3, True, + lambda res: (res.statistic, res.pvalue, res.zstatistic)), + (stats.gmean, tuple(), dict(), 1, 1, False, lambda x: (x,)), + (stats.hmean, tuple(), dict(), 1, 1, False, lambda x: (x,)), + (stats.pmean, (1.42,), dict(), 1, 1, False, lambda x: (x,)), + (stats.trim_mean, (0.05,), dict(), 1, 1, False, lambda x: (x,)), + (stats.sem, tuple(), dict(), 1, 1, False, lambda x: (x,)), + (stats.iqr, tuple(), dict(), 1, 1, False, lambda x: (x,)), + (stats.kurtosis, tuple(), dict(), 1, 1, False, lambda x: (x,)), + (stats.skew, tuple(), dict(), 1, 1, False, lambda x: (x,)), + (stats.kstat, tuple(), dict(), 1, 1, False, lambda x: (x,)), + (stats.kstatvar, tuple(), dict(), 1, 1, False, lambda x: (x,)), + (stats.moment, tuple(), dict(), 1, 1, False, lambda x: (x,)), + (stats.moment, tuple(), dict(order=[1, 2]), 1, 2, False, None), + (stats.jarque_bera, tuple(), dict(), 1, 2, False, None), + (stats.ttest_1samp, (np.array([0]),), dict(), 1, 7, False, + unpack_ttest_result), + (stats.ttest_rel, tuple(), dict(), 2, 7, True, unpack_ttest_result), + (stats.ttest_ind, tuple(), dict(), 2, 7, False, unpack_ttest_result), + (_get_ttest_ci(stats.ttest_1samp), (0,), dict(), 1, 2, False, None), + (_get_ttest_ci(stats.ttest_rel), tuple(), dict(), 2, 2, True, None), + (_get_ttest_ci(stats.ttest_ind), tuple(), dict(), 2, 2, False, None), + (stats.mode, tuple(), dict(), 1, 2, True, lambda x: (x.mode, x.count)), + (stats.differential_entropy, tuple(), dict(), 1, 1, False, lambda x: (x,)), + (stats.variation, tuple(), dict(), 1, 1, False, lambda x: (x,)), + (stats.friedmanchisquare, tuple(), dict(), 3, 2, True, None), + (stats.brunnermunzel, tuple(), dict(distribution='normal'), 2, 2, False, None), + (stats.mood, tuple(), {}, 2, 2, False, None), + (stats.shapiro, tuple(), {}, 1, 2, False, None), + (stats.ks_1samp, (special.ndtr,), dict(), 1, 4, False, + lambda res: (*res, res.statistic_location, res.statistic_sign)), + (stats.ks_1samp, (special.ndtr,), dict(alternative='greater'), 1, 4, False, + lambda res: (*res, res.statistic_location, res.statistic_sign)), + (stats.ks_1samp, (special.ndtr,), dict(alternative='less'), 1, 4, False, + lambda res: (*res, res.statistic_location, res.statistic_sign)), + (stats.ks_2samp, tuple(), dict(), 2, 4, False, + lambda res: (*res, res.statistic_location, res.statistic_sign)), + (stats.kstest, (special.ndtr,), dict(), 1, 4, False, + lambda res: (*res, res.statistic_location, res.statistic_sign)), + (stats.kstest, tuple(), dict(), 2, 4, False, + lambda res: (*res, res.statistic_location, res.statistic_sign)), + (stats.levene, tuple(), {}, 2, 2, False, None), + (stats.fligner, tuple(), {'center': 'trimmed', 'proportiontocut': 0.01}, + 2, 2, False, None), + (stats.ansari, tuple(), {}, 2, 2, False, None), + (stats.entropy, tuple(), dict(), 1, 1, False, lambda x: (x,)), + (stats.entropy, tuple(), dict(), 2, 1, True, lambda x: (x,)), + (stats.skewtest, tuple(), dict(), 1, 2, False, None), + (stats.kurtosistest, tuple(), dict(), 1, 2, False, None), + (stats.normaltest, tuple(), dict(), 1, 2, False, None), + (stats.cramervonmises, (special.ndtr,), dict(), 1, 2, False, + lambda res: (res.statistic, res.pvalue)), + (stats.cramervonmises_2samp, tuple(), dict(method='asymptotic'), 2, 2, False, + lambda res: (res.statistic, res.pvalue)), + (stats.epps_singleton_2samp, tuple(), dict(), 2, 2, False, None), + (stats.bartlett, tuple(), {}, 2, 2, False, None), + (stats.tmean, tuple(), {}, 1, 1, False, lambda x: (x,)), + (stats.tvar, tuple(), {}, 1, 1, False, lambda x: (x,)), + (stats.tmin, tuple(), {}, 1, 1, False, lambda x: (x,)), + (stats.tmax, tuple(), {}, 1, 1, False, lambda x: (x,)), + (stats.tstd, tuple(), {}, 1, 1, False, lambda x: (x,)), + (stats.tsem, tuple(), {}, 1, 1, False, lambda x: (x,)), + (stats.circmean, tuple(), dict(), 1, 1, False, lambda x: (x,)), + (stats.circvar, tuple(), dict(), 1, 1, False, lambda x: (x,)), + (stats.circstd, tuple(), dict(), 1, 1, False, lambda x: (x,)), + (stats.f_oneway, tuple(), {}, 2, 2, False, None), + (stats.f_oneway, tuple(), {'equal_var': False}, 2, 2, False, None), + (stats.alexandergovern, tuple(), {}, 2, 2, False, + lambda res: (res.statistic, res.pvalue)), + (stats.combine_pvalues, tuple(), {}, 1, 2, False, None), + (stats.lmoment, tuple(), dict(), 1, 4, False, lambda x: tuple(x)), + (combine_pvalues_weighted, tuple(), {}, 2, 2, True, None), + (xp_mean_1samp, tuple(), dict(), 1, 1, False, lambda x: (x,)), + (xp_mean_2samp, tuple(), dict(), 2, 1, True, lambda x: (x,)), + (xp_var, tuple(), dict(), 1, 1, False, lambda x: (x,)), + (stats.chatterjeexi, tuple(), dict(), 2, 2, True, + lambda res: (res.statistic, res.pvalue)), + (stats.spearmanrho, tuple(), dict(), 2, 2, True, + lambda res: (res.statistic, res.pvalue)), + (stats.pointbiserialr, tuple(), dict(), 2, 3, True, + lambda res: (res.statistic, res.pvalue, res.correlation)), + (kendalltau, tuple(), dict(), 2, 3, True, + lambda res: (res.statistic, res.pvalue, res.correlation)), + (stats.weightedtau, tuple(), dict(), 2, 3, True, + lambda res: (res.statistic, res.pvalue, res.correlation)), + (weightedtau_weighted, tuple(), dict(), 3, 3, True, + lambda res: (res.statistic, res.pvalue, res.correlation)), + (stats.linregress, tuple(), dict(), 2, 6, True, + lambda res: tuple(res) + (res.intercept_stderr,)), + (stats.theilslopes, tuple(), dict(), 2, 4, True, tuple), + (stats.theilslopes, tuple(), dict(), 1, 4, True, tuple), + (stats.theilslopes, tuple(), dict(method='joint'), 2, 4, True, tuple), + (stats.siegelslopes, tuple(), dict(), 2, 2, True, tuple), + (stats.siegelslopes, tuple(), dict(), 1, 2, True, tuple), + (stats.siegelslopes, tuple(), dict(method='hierarchical'), 2, 2, True, tuple), + (gstd, tuple(), dict(), 1, 1, False, lambda x: (x,)), + (stats.power_divergence, tuple(), dict(), 1, 2, False, None), + (stats.chisquare, tuple(), dict(), 1, 2, False, None), + (stats.median_abs_deviation, tuple(), dict(), 1, 1, False, lambda x: (x,)), + (boxcox_llf, tuple(), dict(lmb=1.5), 1, 1, False, lambda x: (x,)), + (yeojohnson_llf, tuple(), dict(lmb=1.5), 1, 1, False, lambda x: (x,)), + (stats.expectile, (0.4,), dict(), 1, 1, False, lambda x: (x,)), +] + +# If the message is one of those expected, put nans in +# appropriate places of `statistics` and `pvalues` +too_small_messages = {"Degrees of freedom <= 0 for slice", + "x and y should have at least 5 elements", + "Data must be at least length 3", + "The sample must contain at least two", + "x and y must contain at least two", + "division by zero", + "Mean of empty slice", + "Data passed to ks_2samp must not be empty", + "Not enough test observations", + "Not enough other observations", + "Not enough observations.", + "At least one observation is required", + "zero-size array to reduction operation maximum", + "`x` and `y` must be of nonzero size.", + "The exact distribution of the Wilcoxon test", + "Data input must not be empty", + "Window length (0) must be positive and less", + "Window length (1) must be positive and less", + "Window length (2) must be positive and less", + "`skewtest` requires at least", + "`kurtosistest` requires at least", + "attempt to get argmax of an empty sequence", + "No array values within given limits", + "Input sample size must be greater than one.", + "At least one slice along `axis` has zero length", + "One or more sample arguments is too small", + "invalid value encountered", + "divide by zero encountered", + "`x` and `y` must have length at least 2.", + "Inputs must not be empty.", + "All `x` coordinates are identical.", +} + +# If the message is one of these, results of the function may be inaccurate, +# but NaNs are not to be placed +inaccuracy_messages = {"Precision loss occurred in moment calculation", + "Sample size too small for normal approximation."} + +# For some functions, nan_policy='propagate' should not just return NaNs +override_propagate_funcs = {stats.mode, weightedtau_weighted, stats.weightedtau} + +# For some functions, empty arrays produce non-NaN results +empty_special_case_funcs = {stats.entropy, stats.chisquare, stats.power_divergence} + +# Some functions don't follow the usual "too small" warning rules +too_small_special_case_funcs = {stats.entropy, stats.chisquare, stats.power_divergence} + +def _mixed_data_generator(n_samples, n_repetitions, axis, rng, + paired=False): + # generate random samples to check the response of hypothesis tests to + # samples with different (but broadcastable) shapes and various + # nan patterns (e.g. all nans, some nans, no nans) along axis-slices + + data = [] + for i in range(n_samples): + n_patterns = 6 # number of distinct nan patterns + n_obs = 20 if paired else 20 + i # observations per axis-slice + x = np.ones((n_repetitions, n_patterns, n_obs)) * np.nan + + for j in range(n_repetitions): + samples = x[j, :, :] + + # case 0: axis-slice with all nans (0 reals) + # cases 1-3: axis-slice with 1-3 reals (the rest nans) + # case 4: axis-slice with mostly (all but two) reals + # case 5: axis slice with all reals + for k, n_reals in enumerate([0, 1, 2, 3, n_obs-2, n_obs]): + # for cases 1-3, need paired nansw to be in the same place + indices = rng.permutation(n_obs)[:n_reals] + samples[k, indices] = rng.random(size=n_reals) + + # permute the axis-slices just to show that order doesn't matter + samples[:] = rng.permutation(samples, axis=0) + + # For multi-sample tests, we want to test broadcasting and check + # that nan policy works correctly for each nan pattern for each input. + # This takes care of both simultaneously. + new_shape = [n_repetitions] + [1]*n_samples + [n_obs] + new_shape[1 + i] = 6 + x = x.reshape(new_shape) + + x = np.moveaxis(x, -1, axis) + data.append(x) + return data + + +def _homogeneous_data_generator(n_samples, n_repetitions, axis, rng, + paired=False, all_nans=True): + # generate random samples to check the response of hypothesis tests to + # samples with different (but broadcastable) shapes and homogeneous + # data (all nans or all finite) + data = [] + for i in range(n_samples): + n_obs = 20 if paired else 20 + i # observations per axis-slice + shape = [n_repetitions] + [1]*n_samples + [n_obs] + shape[1 + i] = 2 + x = np.ones(shape) * np.nan if all_nans else rng.random(shape) + x = np.moveaxis(x, -1, axis) + data.append(x) + return data + + +def nan_policy_1d(hypotest, data1d, unpacker, *args, n_outputs=2, + nan_policy='raise', paired=False, _no_deco=True, **kwds): + # Reference implementation for how `nan_policy` should work for 1d samples + + if nan_policy == 'raise': + for sample in data1d: + if np.any(np.isnan(sample)): + raise ValueError("The input contains nan values") + + elif (nan_policy == 'propagate' + and hypotest not in override_propagate_funcs): + # For all hypothesis tests tested, returning nans is the right thing. + # But many hypothesis tests don't propagate correctly (e.g. they treat + # np.nan the same as np.inf, which doesn't make sense when ranks are + # involved) so override that behavior here. + for sample in data1d: + if np.any(np.isnan(sample)): + return np.full(n_outputs, np.nan) + + elif nan_policy == 'omit': + # manually omit nans (or pairs in which at least one element is nan) + if not paired: + data1d = [sample[~np.isnan(sample)] for sample in data1d] + else: + nan_mask = np.isnan(data1d[0]) + for sample in data1d[1:]: + nan_mask = np.logical_or(nan_mask, np.isnan(sample)) + data1d = [sample[~nan_mask] for sample in data1d] + + return unpacker(hypotest(*data1d, *args, _no_deco=_no_deco, **kwds)) + + +# These three warnings are intentional +# For `wilcoxon` when the sample size < 50 +@pytest.mark.filterwarnings('ignore:Sample size too small for normal:UserWarning') +# `kurtosistest` and `normaltest` when sample size < 20 +@pytest.mark.filterwarnings('ignore:`kurtosistest` p-value may be:UserWarning') +# `foneway` +@pytest.mark.filterwarnings('ignore:all input arrays have length 1.:RuntimeWarning') + +# The rest of these may or may not be desirable. They need further investigation +# to determine whether the function's decorator should define `too_small. +# `bartlett`, `tvar`, `tstd`, `tsem` +@pytest.mark.filterwarnings('ignore:Degrees of freedom <= 0 for slice:RuntimeWarning') +# kstat, kstatvar, ttest_1samp, ttest_rel, ttest_ind, ttest_ci, brunnermunzel +# mood, levene, fligner, bartlett +@pytest.mark.filterwarnings('ignore:Invalid value encountered in:RuntimeWarning') +# kstatvar, ttest_1samp, ttest_rel, ttest_ci, brunnermunzel, levene, bartlett +@pytest.mark.filterwarnings('ignore:divide by zero encountered:RuntimeWarning') + +@pytest.mark.parametrize(("hypotest", "args", "kwds", "n_samples", "n_outputs", + "paired", "unpacker"), axis_nan_policy_cases) +@pytest.mark.parametrize(("nan_policy"), ("propagate", "omit", "raise")) +@pytest.mark.parametrize(("axis"), (1,)) +@pytest.mark.parametrize(("data_generator"), ("mixed",)) +def test_axis_nan_policy_fast(hypotest, args, kwds, n_samples, n_outputs, + paired, unpacker, nan_policy, axis, + data_generator): + if hypotest in {stats.kruskal} and not SCIPY_XSLOW: + pytest.skip("Too slow.") + _axis_nan_policy_test(hypotest, args, kwds, n_samples, n_outputs, paired, + unpacker, nan_policy, axis, data_generator) + + +if SCIPY_XSLOW: + # Takes O(1 min) to run, and even skipping with the `xslow` decorator takes + # about 3 sec because this is >3,000 tests. So ensure pytest doesn't see + # them at all unless `SCIPY_XSLOW` is defined. + + # These three warnings are intentional + # For `wilcoxon` when the sample size < 50 + @pytest.mark.filterwarnings('ignore:Sample size too small for normal:UserWarning') + # `kurtosistest` and `normaltest` when sample size < 20 + @pytest.mark.filterwarnings('ignore:`kurtosistest` p-value may be:UserWarning') + # `foneway` + @pytest.mark.filterwarnings('ignore:all input arrays have length 1.:RuntimeWarning') + + # The rest of these may or may not be desirable. They need further investigation + # to determine whether the function's decorator should define `too_small. + # `bartlett`, `tvar`, `tstd`, `tsem` + @pytest.mark.filterwarnings('ignore:Degrees of freedom <= 0 for:RuntimeWarning') + # kstat, kstatvar, ttest_1samp, ttest_rel, ttest_ind, ttest_ci, brunnermunzel + # mood, levene, fligner, bartlett + @pytest.mark.filterwarnings('ignore:Invalid value encountered in:RuntimeWarning') + # kstatvar, ttest_1samp, ttest_rel, ttest_ci, brunnermunzel, levene, bartlett + @pytest.mark.filterwarnings('ignore:divide by zero encountered:RuntimeWarning') + + @pytest.mark.parametrize(("hypotest", "args", "kwds", "n_samples", "n_outputs", + "paired", "unpacker"), axis_nan_policy_cases) + @pytest.mark.parametrize(("nan_policy"), ("propagate", "omit", "raise")) + @pytest.mark.parametrize(("axis"), list(range(-3, 3))) + @pytest.mark.parametrize(("data_generator"), + ("all_nans", "all_finite", "mixed")) + def test_axis_nan_policy_full(hypotest, args, kwds, n_samples, n_outputs, + paired, unpacker, nan_policy, axis, + data_generator): + _axis_nan_policy_test(hypotest, args, kwds, n_samples, n_outputs, paired, + unpacker, nan_policy, axis, data_generator) + + +def _axis_nan_policy_test(hypotest, args, kwds, n_samples, n_outputs, paired, + unpacker, nan_policy, axis, data_generator): + # Tests the 1D and vectorized behavior of hypothesis tests against a + # reference implementation (nan_policy_1d with np.ndenumerate) + + # Some hypothesis tests return a non-iterable that needs an `unpacker` to + # extract the statistic and p-value. For those that don't: + if not unpacker: + def unpacker(res): + return res + + rng = np.random.default_rng(0) + + # Generate multi-dimensional test data with all important combinations + # of patterns of nans along `axis` + n_repetitions = 3 # number of repetitions of each pattern + data_gen_kwds = {'n_samples': n_samples, 'n_repetitions': n_repetitions, + 'axis': axis, 'rng': rng, 'paired': paired} + if data_generator == 'mixed': + inherent_size = 6 # number of distinct types of patterns + data = _mixed_data_generator(**data_gen_kwds) + elif data_generator == 'all_nans': + inherent_size = 2 # hard-coded in _homogeneous_data_generator + data_gen_kwds['all_nans'] = True + data = _homogeneous_data_generator(**data_gen_kwds) + elif data_generator == 'all_finite': + inherent_size = 2 # hard-coded in _homogeneous_data_generator + data_gen_kwds['all_nans'] = False + data = _homogeneous_data_generator(**data_gen_kwds) + + output_shape = [n_repetitions] + [inherent_size]*n_samples + + # To generate reference behavior to compare against, loop over the axis- + # slices in data. Make indexing easier by moving `axis` to the end and + # broadcasting all samples to the same shape. + data_b = [np.moveaxis(sample, axis, -1) for sample in data] + data_b = [np.broadcast_to(sample, output_shape + [sample.shape[-1]]) + for sample in data_b] + res_1d = np.zeros(output_shape + [n_outputs]) + + for i, _ in np.ndenumerate(np.zeros(output_shape)): + data1d = [sample[i] for sample in data_b] + contains_nan = any([np.isnan(sample).any() for sample in data1d]) + + # Take care of `nan_policy='raise'`. + # Afterward, the 1D part of the test is over + message = "The input contains nan values" + if nan_policy == 'raise' and contains_nan: + with pytest.raises(ValueError, match=message): + nan_policy_1d(hypotest, data1d, unpacker, *args, + n_outputs=n_outputs, + nan_policy=nan_policy, + paired=paired, _no_deco=True, **kwds) + + with pytest.raises(ValueError, match=message): + hypotest(*data1d, *args, nan_policy=nan_policy, **kwds) + + continue + + # Take care of `nan_policy='propagate'` and `nan_policy='omit'` + + # Get results of simple reference implementation + try: + res_1da = nan_policy_1d(hypotest, data1d, unpacker, *args, + n_outputs=n_outputs, + nan_policy=nan_policy, + paired=paired, _no_deco=True, **kwds) + except (ValueError, RuntimeWarning, ZeroDivisionError, UserWarning) as ea: + ea_str = str(ea) + if any([str(ea_str).startswith(msg) for msg in too_small_messages]): + res_1da = np.full(n_outputs, np.nan) + else: + raise + + # Get results of public function with 1D slices + # Should warn for all slices + if (nan_policy == 'omit' and data_generator == "all_nans" + and hypotest not in too_small_special_case_funcs): + with pytest.warns(SmallSampleWarning, match=too_small_1d_omit): + res = hypotest(*data1d, *args, nan_policy=nan_policy, **kwds) + # warning depends on slice + elif (nan_policy == 'omit' and data_generator == "mixed" + and hypotest not in too_small_special_case_funcs): + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", too_small_1d_omit, SmallSampleWarning) + res = hypotest(*data1d, *args, nan_policy=nan_policy, **kwds) + # shouldn't complain if there are no NaNs + else: + res = hypotest(*data1d, *args, nan_policy=nan_policy, **kwds) + res_1db = unpacker(res) + + assert_allclose(res_1db, res_1da, rtol=RTOL) + res_1d[i] = res_1db + + res_1d = np.moveaxis(res_1d, -1, 0) + + # Perform a vectorized call to the hypothesis test. + + # If `nan_policy == 'raise'`, check that it raises the appropriate error. + # Test is done, so return + if nan_policy == 'raise' and not data_generator == "all_finite": + message = 'The input contains nan values' + with pytest.raises(ValueError, match=message): + hypotest(*data, axis=axis, nan_policy=nan_policy, *args, **kwds) + return + + # If `nan_policy == 'omit', we might be left with a small sample. + # Check for the appropriate warning. + if (nan_policy == 'omit' and data_generator in {"all_nans", "mixed"} + and hypotest not in too_small_special_case_funcs): + with pytest.warns(SmallSampleWarning, match=too_small_nd_omit): + res = hypotest(*data, axis=axis, nan_policy=nan_policy, *args, **kwds) + else: # otherwise, there should be no warning + res = hypotest(*data, axis=axis, nan_policy=nan_policy, *args, **kwds) + + # Compare against the output against looping over 1D slices + res_nd = unpacker(res) + + rtol = max(tolerance_overrides.get(hypotest, RTOL), RTOL) + assert_allclose(res_nd, res_1d, rtol=rtol) + +# nan should not raise an exception in np.mean() +# but does on some mips64el systems, triggering failure in some test cases +# see https://github.com/scipy/scipy/issues/22360 +# and https://github.com/numpy/numpy/issues/23158 +def skip_nan_unexpected_exception(): + try: + # should not raise an exception + with np.errstate(all='raise'): + x = np.asarray([1, 2, np.nan]) + np.mean(x) + except Exception as e: + pytest.skip(f"nan raises unexpected {e.__class__.__name__} in numpy") + +@pytest.mark.parametrize(("hypotest", "args", "kwds", "n_samples", "n_outputs", + "paired", "unpacker"), axis_nan_policy_cases) +@pytest.mark.parametrize(("nan_policy"), ("propagate", "omit", "raise")) +@pytest.mark.parametrize(("data_generator"), + ("all_nans", "all_finite", "mixed", "empty")) +def test_axis_nan_policy_axis_is_None(hypotest, args, kwds, n_samples, + n_outputs, paired, unpacker, nan_policy, + data_generator): + # check for correct behavior when `axis=None` + if not unpacker: + def unpacker(res): + return res + + # skip if nan emits unexpected RuntimeWarning in np.mean() + # seen on mips64el, https://github.com/scipy/scipy/issues/22360 + # Only affects nan_policy "mixed-propagate" for selected hypotests + if data_generator=="mixed" and nan_policy=="propagate": + # only skip affected hypotests + if hypotest.__name__ in ["iqr", "ttest_ci", + "xp_mean_1samp", "xp_mean_2samp", "xp_var", + "weightedtau", "weightedtau_weighted"]: + skip_nan_unexpected_exception() + # all_nans-propagate-ttest_ci is also affected, via scalar multiply + if (data_generator=="all_nans" and nan_policy=="propagate" + and hypotest.__name__=="ttest_ci"): + skip_nan_unexpected_exception() + # mixed-omit-xp_var is also affected, via subtract + if (data_generator=="mixed" and nan_policy=="omit" + and hypotest.__name__=="xp_var"): + skip_nan_unexpected_exception() + + rng = np.random.default_rng(0) + + if data_generator == "empty": + data = [rng.random((2, 0)) for i in range(n_samples)] + else: + data = [rng.random((2, 20)) for i in range(n_samples)] + + if data_generator == "mixed": + masks = [rng.random((2, 20)) > 0.9 for i in range(n_samples)] + for sample, mask in zip(data, masks): + sample[mask] = np.nan + elif data_generator == "all_nans": + data = [sample * np.nan for sample in data] + + data_raveled = [sample.ravel() for sample in data] + + if nan_policy == 'raise' and data_generator not in {"all_finite", "empty"}: + message = 'The input contains nan values' + + # check for correct behavior whether or not data is 1d to begin with + with pytest.raises(ValueError, match=message): + hypotest(*data, axis=None, nan_policy=nan_policy, + *args, **kwds) + with pytest.raises(ValueError, match=message): + hypotest(*data_raveled, axis=None, nan_policy=nan_policy, + *args, **kwds) + + return + + # behavior of reference implementation with 1d input, public function with 1d + # input, and public function with Nd input and `axis=None` should be consistent. + # This means: + # - If the reference version raises an error or emits a warning, it's because + # the sample is too small, so check that the public function emits an + # appropriate "too small" warning + # - Any results returned by the three versions should be the same. + with warnings.catch_warnings(): # treat warnings as errors + warnings.simplefilter("error") + + ea_str, eb_str, ec_str = None, None, None + try: + res1da = nan_policy_1d(hypotest, data_raveled, unpacker, *args, + n_outputs=n_outputs, nan_policy=nan_policy, + paired=paired, _no_deco=True, **kwds) + except (RuntimeWarning, ValueError, ZeroDivisionError, UserWarning) as ea: + res1da = None + ea_str = str(ea) + + try: + res1db = hypotest(*data_raveled, *args, nan_policy=nan_policy, **kwds) + except SmallSampleWarning as eb: + eb_str = str(eb) + + try: + res1dc = hypotest(*data, *args, axis=None, nan_policy=nan_policy, **kwds) + except SmallSampleWarning as ec: + ec_str = str(ec) + + if ea_str or eb_str or ec_str: # *if* there is some sort of error or warning + # If the reference implemented generated an error or warning, make sure the + # message was one of the expected "too small" messages. Note that some + # functions don't complain at all without the decorator; that's OK, too. + ok_msg = any([str(ea_str).startswith(msg) for msg in too_small_messages]) + assert (ea_str is None) or ok_msg + + # make sure the wrapped function emits the *intended* warning + desired_warnings = {too_small_1d_omit, too_small_1d_not_omit} + assert str(eb_str) in desired_warnings + assert str(ec_str) in desired_warnings + + with warnings.catch_warnings(): # ignore warnings to get return value + warnings.simplefilter("ignore") + res1db = hypotest(*data_raveled, *args, nan_policy=nan_policy, **kwds) + res1dc = hypotest(*data, *args, axis=None, nan_policy=nan_policy, **kwds) + + # Make sure any results returned by reference/public function are identical + # and all attributes are *NumPy* scalars + res1db, res1dc = unpacker(res1db), unpacker(res1dc) + # changed from 1e-15 solely to appease macosx-x86_64+Accelerate + assert_allclose(res1dc, res1db, rtol=2e-14) + all_results = list(res1db) + list(res1dc) + + if res1da is not None: + # changed from 1e-15 solely to appease macosx-x86_64+Accelerate + assert_allclose(res1db, res1da, rtol=2e-14) + all_results += list(res1da) + + for item in all_results: + assert np.issubdtype(item.dtype, np.number) + assert np.isscalar(item) + + +# Test keepdims for: +# - Axis negative, positive, None, and tuple +# - 1D with no NaNs +# - 1D with NaN propagation +# - Zero-sized output +# We're working on making `stats` quieter, but that's not what this test +# is about. For now, we expect all sorts of warnings here due to small samples. +@pytest.mark.filterwarnings('ignore::UserWarning') +@pytest.mark.filterwarnings('ignore::RuntimeWarning') +@pytest.mark.parametrize("nan_policy", ("omit", "propagate")) +@pytest.mark.parametrize(("hypotest", "args", "kwds", "n_samples", "n_outputs", + "paired", "unpacker"), axis_nan_policy_cases) +@pytest.mark.parametrize( + ("sample_shape", "axis_cases"), + (((2, 3, 3, 4), (None, 0, -1, (0, 2), (1, -1), (3, 1, 2, 0))), + ((10, ), (0, -1)), + ((20, 0), (0, 1))) +) +def test_keepdims(hypotest, args, kwds, n_samples, n_outputs, paired, unpacker, + sample_shape, axis_cases, nan_policy): + small_sample_raises = {stats.skewtest, stats.kurtosistest, stats.normaltest, + stats.differential_entropy, stats.epps_singleton_2samp, + stats.shapiro} + if sample_shape == (2, 3, 3, 4) and hypotest in small_sample_raises: + pytest.skip("Sample too small; test raises error.") + if hypotest in {weightedtau_weighted}: + pytest.skip("`rankdata` used in testing doesn't support axis tuple.") + # test if keepdims parameter works correctly + if not unpacker: + def unpacker(res): + return res + rng = np.random.default_rng(0) + data = [rng.random(sample_shape) for _ in range(n_samples)] + nan_data = [sample.copy() for sample in data] + nan_mask = [rng.random(sample_shape) < 0.2 for _ in range(n_samples)] + for sample, mask in zip(nan_data, nan_mask): + sample[mask] = np.nan + for axis in axis_cases: + expected_shape = list(sample_shape) + if axis is None: + expected_shape = np.ones(len(sample_shape)) + else: + if isinstance(axis, int): + expected_shape[axis] = 1 + else: + for ax in axis: + expected_shape[ax] = 1 + expected_shape = tuple(expected_shape) + res = unpacker(hypotest(*data, *args, axis=axis, keepdims=True, + **kwds)) + res_base = unpacker(hypotest(*data, *args, axis=axis, keepdims=False, + **kwds)) + nan_res = unpacker(hypotest(*nan_data, *args, axis=axis, + keepdims=True, nan_policy=nan_policy, + **kwds)) + nan_res_base = unpacker(hypotest(*nan_data, *args, axis=axis, + keepdims=False, + nan_policy=nan_policy, **kwds)) + for r, r_base, rn, rn_base in zip(res, res_base, nan_res, + nan_res_base): + assert r.shape == expected_shape + r = np.squeeze(r, axis=axis) + assert_allclose(r, r_base, atol=1e-16) + assert rn.shape == expected_shape + rn = np.squeeze(rn, axis=axis) + # ideally assert_equal, but `combine_pvalues` failed on 32-bit build + assert_allclose(rn, rn_base, atol=1e-16) + + +@pytest.mark.parametrize(("fun", "nsamp"), + [(stats.kstat, 1), + (stats.kstatvar, 1)]) +def test_hypotest_back_compat_no_axis(fun, nsamp): + m, n = 8, 9 + + rng = np.random.default_rng(0) + x = rng.random((nsamp, m, n)) + res = fun(*x) + res2 = fun(*x, _no_deco=True) + res3 = fun([xi.ravel() for xi in x]) + assert_equal(res, res2) + assert_equal(res, res3) + + +@pytest.mark.parametrize(("axis"), (0, 1, 2)) +def test_axis_nan_policy_decorated_positional_axis(axis): + # Test for correct behavior of function decorated with + # _axis_nan_policy_decorator whether `axis` is provided as positional or + # keyword argument + + shape = (8, 9, 10) + rng = np.random.default_rng(0) + x = rng.random(shape) + y = rng.random(shape) + res1 = stats.mannwhitneyu(x, y, True, 'two-sided', axis) + res2 = stats.mannwhitneyu(x, y, True, 'two-sided', axis=axis) + assert_equal(res1, res2) + + message = "mannwhitneyu() got multiple values for argument 'axis'" + with pytest.raises(TypeError, match=re.escape(message)): + stats.mannwhitneyu(x, y, True, 'two-sided', axis, axis=axis) + + +def test_axis_nan_policy_decorated_positional_args(): + # Test for correct behavior of function decorated with + # _axis_nan_policy_decorator when function accepts *args + + shape = (3, 8, 9, 10) + rng = np.random.default_rng(0) + x = rng.random(shape) + x[0, 0, 0, 0] = np.nan + stats.kruskal(*x) + + message = "kruskal() got an unexpected keyword argument 'samples'" + with pytest.raises(TypeError, match=re.escape(message)): + stats.kruskal(samples=x) + + with pytest.raises(TypeError, match=re.escape(message)): + stats.kruskal(*x, samples=x) + + +def test_axis_nan_policy_decorated_keyword_samples(): + # Test for correct behavior of function decorated with + # _axis_nan_policy_decorator whether samples are provided as positional or + # keyword arguments + + shape = (2, 8, 9, 10) + rng = np.random.default_rng(0) + x = rng.random(shape) + x[0, 0, 0, 0] = np.nan + res1 = stats.mannwhitneyu(*x) + res2 = stats.mannwhitneyu(x=x[0], y=x[1]) + assert_equal(res1, res2) + + message = "mannwhitneyu() got multiple values for argument" + with pytest.raises(TypeError, match=re.escape(message)): + stats.mannwhitneyu(*x, x=x[0], y=x[1]) + + +@pytest.mark.parametrize(("hypotest", "args", "kwds", "n_samples", "n_outputs", + "paired", "unpacker"), axis_nan_policy_cases) +def test_axis_nan_policy_decorated_pickled(hypotest, args, kwds, n_samples, + n_outputs, paired, unpacker): + if "ttest_ci" in hypotest.__name__: + pytest.skip("Can't pickle functions defined within functions.") + + rng = np.random.default_rng(0) + + # Some hypothesis tests return a non-iterable that needs an `unpacker` to + # extract the statistic and p-value. For those that don't: + if not unpacker: + def unpacker(res): + return res + + data = rng.uniform(size=(n_samples, 2, 30)) + pickled_hypotest = pickle.dumps(hypotest) + unpickled_hypotest = pickle.loads(pickled_hypotest) + res1 = unpacker(hypotest(*data, *args, axis=-1, **kwds)) + res2 = unpacker(unpickled_hypotest(*data, *args, axis=-1, **kwds)) + assert_allclose(res1, res2, rtol=1e-12) + + +def test_check_empty_inputs(): + # Test that _check_empty_inputs is doing its job, at least for single- + # sample inputs. (Multi-sample functionality is tested below.) + # If the input sample is not empty, it should return None. + # If the input sample is empty, it should return an array of NaNs or an + # empty array of appropriate shape. np.mean is used as a reference for the + # output because, like the statistics calculated by these functions, + # it works along and "consumes" `axis` but preserves the other axes. + for i in range(5): + for combo in combinations_with_replacement([0, 1, 2], i): + for axis in range(len(combo)): + samples = (np.zeros(combo),) + output = stats._axis_nan_policy._check_empty_inputs(samples, + axis) + if output is not None: + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", "Mean of empty slice", RuntimeWarning) + warnings.filterwarnings( + "ignore", "invalid value encountered", RuntimeWarning) + reference = samples[0].mean(axis=axis) + np.testing.assert_equal(output, reference) + + +def _check_arrays_broadcastable(arrays, axis): + # https://numpy.org/doc/stable/user/basics.broadcasting.html + # "When operating on two arrays, NumPy compares their shapes element-wise. + # It starts with the trailing (i.e. rightmost) dimensions and works its + # way left. + # Two dimensions are compatible when + # 1. they are equal, or + # 2. one of them is 1 + # ... + # Arrays do not need to have the same number of dimensions." + # (Clarification: if the arrays are compatible according to the criteria + # above and an array runs out of dimensions, it is still compatible.) + # Below, we follow the rules above except ignoring `axis` + + n_dims = max([arr.ndim for arr in arrays]) + if axis is not None: + # convert to negative axis + axis = (-n_dims + axis) if axis >= 0 else axis + + for dim in range(1, n_dims+1): # we'll index from -1 to -n_dims, inclusive + if -dim == axis: + continue # ignore lengths along `axis` + + dim_lengths = set() + for arr in arrays: + if dim <= arr.ndim and arr.shape[-dim] != 1: + dim_lengths.add(arr.shape[-dim]) + + if len(dim_lengths) > 1: + return False + return True + + +@pytest.mark.slow +@pytest.mark.parametrize(("hypotest", "args", "kwds", "n_samples", "n_outputs", + "paired", "unpacker"), axis_nan_policy_cases) +def test_empty(hypotest, args, kwds, n_samples, n_outputs, paired, unpacker): + # test for correct output shape when at least one input is empty + if hypotest in {stats.kruskal, stats.friedmanchisquare} and not SCIPY_XSLOW: + pytest.skip("Too slow.") + + if hypotest in override_propagate_funcs: + reason = "Doesn't follow the usual pattern. Tested separately." + pytest.skip(reason=reason) + + if unpacker is None: + unpacker = lambda res: (res[0], res[1]) # noqa: E731 + + def small_data_generator(n_samples, n_dims): + + def small_sample_generator(n_dims): + # return all possible "small" arrays in up to n_dim dimensions + for i in n_dims: + # "small" means with size along dimension either 0 or 1 + for combo in combinations_with_replacement([0, 1, 2], i): + yield np.zeros(combo) + + # yield all possible combinations of small samples + gens = [small_sample_generator(n_dims) for i in range(n_samples)] + yield from product(*gens) + + n_dims = [1, 2, 3] + for samples in small_data_generator(n_samples, n_dims): + + # this test is only for arrays of zero size + if not any(sample.size == 0 for sample in samples): + continue + + max_axis = max(sample.ndim for sample in samples) + + # need to test for all valid values of `axis` parameter, too + for axis in range(-max_axis, max_axis): + + try: + # After broadcasting, all arrays are the same shape, so + # the shape of the output should be the same as a single- + # sample statistic. Use np.mean as a reference. + concat = stats._axis_nan_policy._broadcast_concatenate(samples, axis, + paired=paired) + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", "Mean of empty slice", RuntimeWarning) + warnings.filterwarnings( + "ignore", "invalid value encountered", RuntimeWarning) + expected = np.mean(concat, axis=axis) * np.nan + mask = np.isnan(expected) + expected = [np.asarray(expected.copy()) for i in range(n_outputs)] + + if hypotest in empty_special_case_funcs: + empty_val = hypotest(*([[]]*len(samples)), *args, **kwds) + empty_val = list(unpacker(empty_val)) + for i in range(n_outputs): + expected[i][mask] = empty_val[i] + + if expected[0].size and hypotest not in too_small_special_case_funcs: + message = (too_small_1d_not_omit if max_axis == 1 + else too_small_nd_not_omit) + with pytest.warns(SmallSampleWarning, match=message): + res = hypotest(*samples, *args, axis=axis, **kwds) + else: + with warnings.catch_warnings(): + # f_oneway special case + msg = "all input arrays have length 1" + warnings.filterwarnings("ignore", msg, SmallSampleWarning) + res = hypotest(*samples, *args, axis=axis, **kwds) + res = unpacker(res) + + for i in range(n_outputs): + assert_equal(res[i], expected[i]) + + except ValueError: + # confirm that the arrays truly are not broadcastable + assert not _check_arrays_broadcastable(samples, + None if paired else axis) + + # confirm that _both_ `_broadcast_concatenate` and `hypotest` + # produce this information. + message = "Array shapes are incompatible for broadcasting." + with pytest.raises(ValueError, match=message): + stats._axis_nan_policy._broadcast_concatenate(samples, axis, paired) + with pytest.raises(ValueError, match=message): + hypotest(*samples, *args, axis=axis, **kwds) + + +def paired_non_broadcastable_cases(): + for case in axis_nan_policy_cases: + hypotest, args, kwds, n_samples, n_outputs, paired, unpacker = case + if n_samples == 1: # broadcasting only needed with >1 sample + continue + yield case + + +@pytest.mark.parametrize("axis", [0, 1]) +@pytest.mark.parametrize(("hypotest", "args", "kwds", "n_samples", "n_outputs", + "paired", "unpacker"), + list(paired_non_broadcastable_cases())) +def test_non_broadcastable(hypotest, args, kwds, n_samples, n_outputs, paired, + unpacker, axis): + # test for correct error message when shapes are not broadcastable + rng = np.random.default_rng(91359824598245) + get_samples = True + while get_samples: + samples = [rng.random(size=rng.integers(2, 100, size=2)) + for i in range(n_samples)] + # if samples are broadcastable, try again + get_samples = _check_arrays_broadcastable(samples, axis=axis) + + message = "Array shapes are incompatible for broadcasting." + with pytest.raises(ValueError, match=message): + hypotest(*samples, *args, axis=axis, **kwds) + + if not paired: # there's another test for paired-sample statistics + return + + # Previously, paired sample statistics did not raise an error + # message when the shapes were broadcastable except along `axis` + # https://github.com/scipy/scipy/pull/19578#pullrequestreview-1766857165 + shape = rng.integers(2, 10, size=2) + most_samples = [rng.random(size=shape) for i in range(n_samples-1)] + shape = list(shape) + shape[axis] += 1 + other_sample = rng.random(size=shape) + with pytest.raises(ValueError, match=message): + hypotest(other_sample, *most_samples, *args, axis=axis, **kwds) + +def test_masked_array_2_sentinel_array(): + # prepare arrays + rng = np.random.default_rng(805715284) + A = rng.random((10, 11, 12)) + B = rng.random(12) + mask = A < 0.5 + A = np.ma.masked_array(A, mask) + + # set arbitrary elements to special values + # (these values might have been considered for use as sentinel values) + max_float = np.finfo(np.float64).max + max_float2 = np.nextafter(max_float, -np.inf) + max_float3 = np.nextafter(max_float2, -np.inf) + A[3, 4, 1] = np.nan + A[4, 5, 2] = np.inf + A[5, 6, 3] = max_float + B[8] = np.nan + B[7] = np.inf + B[6] = max_float2 + + # convert masked A to array with sentinel value, don't modify B + out_arrays, sentinel = _masked_arrays_2_sentinel_arrays([A, B]) + A_out, B_out = out_arrays + + # check that good sentinel value was chosen (according to intended logic) + assert (sentinel != max_float) and (sentinel != max_float2) + assert sentinel == max_float3 + + # check that output arrays are as intended + A_reference = A.data + A_reference[A.mask] = sentinel + np.testing.assert_array_equal(A_out, A_reference) + assert B_out is B + + +@skip_xp_invalid_arg +def test_masked_dtype(): + # When _masked_arrays_2_sentinel_arrays was first added, it always + # upcast the arrays to np.float64. After gh16662, check expected promotion + # and that the expected sentinel is found. + + # these are important because the max of the promoted dtype is the first + # candidate to be the sentinel value + max16 = np.iinfo(np.int16).max + max128c = np.finfo(np.complex128).max + + # a is a regular array, b has masked elements, and c has no masked elements + a = np.array([1, 2, max16], dtype=np.int16) + b = np.ma.array([1, 2, 1], dtype=np.int8, mask=[0, 1, 0]) + c = np.ma.array([1, 2, 1], dtype=np.complex128, mask=[0, 0, 0]) + + # check integer masked -> sentinel conversion + out_arrays, sentinel = _masked_arrays_2_sentinel_arrays([a, b]) + a_out, b_out = out_arrays + assert sentinel == max16-1 # not max16 because max16 was in the data + assert b_out.dtype == np.int16 # check expected promotion + assert_allclose(b_out, [b[0], sentinel, b[-1]]) # check sentinel placement + assert a_out is a # not a masked array, so left untouched + assert not isinstance(b_out, np.ma.MaskedArray) # b became regular array + + # similarly with complex + out_arrays, sentinel = _masked_arrays_2_sentinel_arrays([b, c]) + b_out, c_out = out_arrays + assert sentinel == max128c # max128c was not in the data + assert b_out.dtype == np.complex128 # b got promoted + assert_allclose(b_out, [b[0], sentinel, b[-1]]) # check sentinel placement + assert not isinstance(b_out, np.ma.MaskedArray) # b became regular array + assert not isinstance(c_out, np.ma.MaskedArray) # c became regular array + + # Also, check edge case when a sentinel value cannot be found in the data + min8, max8 = np.iinfo(np.int8).min, np.iinfo(np.int8).max + a = np.arange(min8, max8+1, dtype=np.int8) # use all possible values + mask1 = np.zeros_like(a, dtype=bool) + mask0 = np.zeros_like(a, dtype=bool) + + # a masked value can be used as the sentinel + mask1[1] = True + a1 = np.ma.array(a, mask=mask1) + out_arrays, sentinel = _masked_arrays_2_sentinel_arrays([a1]) + assert sentinel == min8+1 + + # unless it's the smallest possible; skipped for simiplicity (see code) + mask0[0] = True + a0 = np.ma.array(a, mask=mask0) + message = "This function replaces masked elements with sentinel..." + with pytest.raises(ValueError, match=message): + _masked_arrays_2_sentinel_arrays([a0]) + + # test that dtype is preserved in functions + a = np.ma.array([1, 2, 3], mask=[0, 1, 0], dtype=np.float32) + assert stats.gmean(a).dtype == np.float32 + + +@skip_xp_invalid_arg +def test_masked_stat_1d(): + # basic test of _axis_nan_policy_factory with 1D masked sample + males = [19, 22, 16, 29, 24] + females = [20, 11, 17, 12] + res = stats.mannwhitneyu(males, females) + + # same result when extra nan is omitted + females2 = [20, 11, 17, np.nan, 12] + res2 = stats.mannwhitneyu(males, females2, nan_policy='omit') + np.testing.assert_array_equal(res2, res) + + # same result when extra element is masked + females3 = [20, 11, 17, 1000, 12] + mask3 = [False, False, False, True, False] + females3 = np.ma.masked_array(females3, mask=mask3) + res3 = stats.mannwhitneyu(males, females3) + np.testing.assert_array_equal(res3, res) + + # same result when extra nan is omitted and additional element is masked + females4 = [20, 11, 17, np.nan, 1000, 12] + mask4 = [False, False, False, False, True, False] + females4 = np.ma.masked_array(females4, mask=mask4) + res4 = stats.mannwhitneyu(males, females4, nan_policy='omit') + np.testing.assert_array_equal(res4, res) + + # same result when extra elements, including nan, are masked + females5 = [20, 11, 17, np.nan, 1000, 12] + mask5 = [False, False, False, True, True, False] + females5 = np.ma.masked_array(females5, mask=mask5) + res5 = stats.mannwhitneyu(males, females5, nan_policy='propagate') + res6 = stats.mannwhitneyu(males, females5, nan_policy='raise') + np.testing.assert_array_equal(res5, res) + np.testing.assert_array_equal(res6, res) + + +@pytest.mark.filterwarnings('ignore:After omitting NaNs...') +@pytest.mark.filterwarnings('ignore:One or more axis-slices of one...') +@skip_xp_invalid_arg +@pytest.mark.parametrize(("axis"), list(range(-3, 3))) +def test_masked_stat_3d(axis): + # basic test of _axis_nan_policy_factory with 3D masked sample + rng = np.random.default_rng(3679428403) + a = rng.random((3, 4, 5)) + b = rng.random((4, 5)) + c = rng.random((4, 1)) + + mask_a = a < 0.1 + mask_c = [False, False, False, True] + a_masked = np.ma.masked_array(a, mask=mask_a) + c_masked = np.ma.masked_array(c, mask=mask_c) + + a_nans = a.copy() + a_nans[mask_a] = np.nan + c_nans = c.copy() + c_nans[mask_c] = np.nan + + res = stats.kruskal(a_nans, b, c_nans, nan_policy='omit', axis=axis) + res2 = stats.kruskal(a_masked, b, c_masked, axis=axis) + np.testing.assert_array_equal(res, res2) + + +@pytest.mark.filterwarnings('ignore:After omitting NaNs...') +@pytest.mark.filterwarnings('ignore:One or more axis-slices of one...') +@skip_xp_invalid_arg +def test_mixed_mask_nan_1(): + # targeted test of _axis_nan_policy_factory with 2D masked sample: + # omitting samples with masks and nan_policy='omit' are equivalent + # also checks paired-sample sentinel value removal + m, n = 3, 20 + axis = -1 + + rng = np.random.RandomState(0) + a = rng.rand(m, n) + b = rng.rand(m, n) + mask_a1 = rng.rand(m, n) < 0.2 + mask_a2 = rng.rand(m, n) < 0.1 + mask_b1 = rng.rand(m, n) < 0.15 + mask_b2 = rng.rand(m, n) < 0.15 + mask_a1[2, :] = True + + a_nans = a.copy() + b_nans = b.copy() + a_nans[mask_a1 | mask_a2] = np.nan + b_nans[mask_b1 | mask_b2] = np.nan + + a_masked1 = np.ma.masked_array(a, mask=mask_a1) + b_masked1 = np.ma.masked_array(b, mask=mask_b1) + a_masked1[mask_a2] = np.nan + b_masked1[mask_b2] = np.nan + + a_masked2 = np.ma.masked_array(a, mask=mask_a2) + b_masked2 = np.ma.masked_array(b, mask=mask_b2) + a_masked2[mask_a1] = np.nan + b_masked2[mask_b1] = np.nan + + a_masked3 = np.ma.masked_array(a, mask=(mask_a1 | mask_a2)) + b_masked3 = np.ma.masked_array(b, mask=(mask_b1 | mask_b2)) + + res = stats.wilcoxon(a_nans, b_nans, nan_policy='omit', axis=axis) + res1 = stats.wilcoxon(a_masked1, b_masked1, nan_policy='omit', axis=axis) + res2 = stats.wilcoxon(a_masked2, b_masked2, nan_policy='omit', axis=axis) + res3 = stats.wilcoxon(a_masked3, b_masked3, nan_policy='raise', axis=axis) + res4 = stats.wilcoxon(a_masked3, b_masked3, + nan_policy='propagate', axis=axis) + + np.testing.assert_array_equal(res1, res) + np.testing.assert_array_equal(res2, res) + np.testing.assert_array_equal(res3, res) + np.testing.assert_array_equal(res4, res) + + +@pytest.mark.filterwarnings('ignore:After omitting NaNs...') +@pytest.mark.filterwarnings('ignore:One or more axis-slices of one...') +@skip_xp_invalid_arg +def test_mixed_mask_nan_2(): + # targeted test of _axis_nan_policy_factory with 2D masked sample: + # check for expected interaction between masks and nans + + # Cases here are + # [mixed nan/mask, all nans, all masked, + # unmasked nan, masked nan, unmasked non-nan] + a = [[1, np.nan, 2], [np.nan, np.nan, np.nan], [1, 2, 3], + [1, np.nan, 3], [1, np.nan, 3], [1, 2, 3]] + mask = [[1, 0, 1], [0, 0, 0], [1, 1, 1], + [0, 0, 0], [0, 1, 0], [0, 0, 0]] + a_masked = np.ma.masked_array(a, mask=mask) + b = [[4, 5, 6]] + ref1 = stats.ranksums([1, 3], [4, 5, 6]) + ref2 = stats.ranksums([1, 2, 3], [4, 5, 6]) + + # nan_policy = 'omit' + # all elements are removed from first three rows + # middle element is removed from fourth and fifth rows + # no elements removed from last row + res = stats.ranksums(a_masked, b, nan_policy='omit', axis=-1) + stat_ref = [np.nan, np.nan, np.nan, + ref1.statistic, ref1.statistic, ref2.statistic] + p_ref = [np.nan, np.nan, np.nan, + ref1.pvalue, ref1.pvalue, ref2.pvalue] + np.testing.assert_array_equal(res.statistic, stat_ref) + np.testing.assert_array_equal(res.pvalue, p_ref) + + # nan_policy = 'propagate' + # nans propagate in first, second, and fourth row + # all elements are removed by mask from third row + # middle element is removed from fifth row + # no elements removed from last row + res = stats.ranksums(a_masked, b, nan_policy='propagate', axis=-1) + stat_ref = [np.nan, np.nan, np.nan, + np.nan, ref1.statistic, ref2.statistic] + p_ref = [np.nan, np.nan, np.nan, + np.nan, ref1.pvalue, ref2.pvalue] + np.testing.assert_array_equal(res.statistic, stat_ref) + np.testing.assert_array_equal(res.pvalue, p_ref) + + +def test_axis_None_vs_tuple(): + # `axis` `None` should be equivalent to tuple with all axes + shape = (3, 8, 9, 10) + rng = np.random.default_rng(0) + x = rng.random(shape) + res = stats.kruskal(*x, axis=None) + res2 = stats.kruskal(*x, axis=(0, 1, 2)) + np.testing.assert_array_equal(res, res2) + + +def test_axis_None_vs_tuple_with_broadcasting(): + # `axis` `None` should be equivalent to tuple with all axes, + # which should be equivalent to raveling the arrays before passing them + rng = np.random.default_rng(0) + x = rng.random((5, 1)) + y = rng.random((1, 5)) + x2, y2 = np.broadcast_arrays(x, y) + + res0 = stats.mannwhitneyu(x.ravel(), y.ravel()) + res1 = stats.mannwhitneyu(x, y, axis=None) + res2 = stats.mannwhitneyu(x, y, axis=(0, 1)) + res3 = stats.mannwhitneyu(x2.ravel(), y2.ravel()) + + assert res1 == res0 + assert res2 == res0 + assert res3 != res0 + + +@pytest.mark.parametrize(("axis"), + list(permutations(range(-3, 3), 2)) + [(-4, 1)]) +def test_other_axis_tuples(axis): + # Check that _axis_nan_policy_factory treats all `axis` tuples as expected + rng = np.random.default_rng(0) + shape_x = (4, 5, 6) + shape_y = (1, 6) + x = rng.random(shape_x) + y = rng.random(shape_y) + axis_original = axis + + # convert axis elements to positive + axis = tuple([(i if i >= 0 else 3 + i) for i in axis]) + axis = sorted(axis) + + if len(set(axis)) != len(axis): + message = "`axis` must contain only distinct elements" + with pytest.raises(AxisError, match=re.escape(message)): + stats.mannwhitneyu(x, y, axis=axis_original) + return + + if axis[0] < 0 or axis[-1] > 2: + message = "`axis` is out of bounds for array of dimension 3" + with pytest.raises(AxisError, match=re.escape(message)): + stats.mannwhitneyu(x, y, axis=axis_original) + return + + res = stats.mannwhitneyu(x, y, axis=axis_original) + + # reference behavior + not_axis = {0, 1, 2} - set(axis) # which axis is not part of `axis` + not_axis = next(iter(not_axis)) # take it out of the set + + x2 = x + shape_y_broadcasted = [1, 1, 6] + shape_y_broadcasted[not_axis] = shape_x[not_axis] + y2 = np.broadcast_to(y, shape_y_broadcasted) + + m = x2.shape[not_axis] + x2 = np.moveaxis(x2, axis, (1, 2)) + y2 = np.moveaxis(y2, axis, (1, 2)) + x2 = np.reshape(x2, (m, -1)) + y2 = np.reshape(y2, (m, -1)) + res2 = stats.mannwhitneyu(x2, y2, axis=1) + + np.testing.assert_array_equal(res, res2) + + +@pytest.mark.filterwarnings('ignore:After omitting NaNs...') +@pytest.mark.filterwarnings('ignore:One or more axis-slices of one...') +@skip_xp_invalid_arg +@pytest.mark.parametrize( + ("weighted_fun_name, unpacker"), + [ + ("gmean", lambda x: x), + ("hmean", lambda x: x), + ("pmean", lambda x: x), + ("combine_pvalues", lambda x: (x.pvalue, x.statistic)), + ], +) +def test_mean_mixed_mask_nan_weights(weighted_fun_name, unpacker): + # targeted test of _axis_nan_policy_factory with 2D masked sample: + # omitting samples with masks and nan_policy='omit' are equivalent + # also checks paired-sample sentinel value removal + + if weighted_fun_name == 'pmean': + def weighted_fun(a, **kwargs): + return stats.pmean(a, p=0.42, **kwargs) + else: + weighted_fun = getattr(stats, weighted_fun_name) + + def func(*args, **kwargs): + return unpacker(weighted_fun(*args, **kwargs)) + + m, n = 3, 20 + axis = -1 + + rng = np.random.default_rng(6541968121) + a = rng.uniform(size=(m, n)) + b = rng.uniform(size=(m, n)) + mask_a1 = rng.uniform(size=(m, n)) < 0.2 + mask_a2 = rng.uniform(size=(m, n)) < 0.1 + mask_b1 = rng.uniform(size=(m, n)) < 0.15 + mask_b2 = rng.uniform(size=(m, n)) < 0.15 + mask_a1[2, :] = True + + a_nans = a.copy() + b_nans = b.copy() + a_nans[mask_a1 | mask_a2] = np.nan + b_nans[mask_b1 | mask_b2] = np.nan + + a_masked1 = np.ma.masked_array(a, mask=mask_a1) + b_masked1 = np.ma.masked_array(b, mask=mask_b1) + a_masked1[mask_a2] = np.nan + b_masked1[mask_b2] = np.nan + + a_masked2 = np.ma.masked_array(a, mask=mask_a2) + b_masked2 = np.ma.masked_array(b, mask=mask_b2) + a_masked2[mask_a1] = np.nan + b_masked2[mask_b1] = np.nan + + a_masked3 = np.ma.masked_array(a, mask=(mask_a1 | mask_a2)) + b_masked3 = np.ma.masked_array(b, mask=(mask_b1 | mask_b2)) + + with warnings.catch_warnings(): + message = 'invalid value encountered' + warnings.filterwarnings("ignore", message, RuntimeWarning) + res = func(a_nans, weights=b_nans, nan_policy="omit", axis=axis) + res1 = func(a_masked1, weights=b_masked1, nan_policy="omit", axis=axis) + res2 = func(a_masked2, weights=b_masked2, nan_policy="omit", axis=axis) + res3 = func(a_masked3, weights=b_masked3, nan_policy="raise", axis=axis) + res4 = func(a_masked3, weights=b_masked3, nan_policy="propagate", axis=axis) + + np.testing.assert_array_equal(res1, res) + np.testing.assert_array_equal(res2, res) + np.testing.assert_array_equal(res3, res) + np.testing.assert_array_equal(res4, res) + + +def test_raise_invalid_args_g17713(): + # other cases are handled in: + # test_axis_nan_policy_decorated_positional_axis - multiple values for arg + # test_axis_nan_policy_decorated_positional_args - unexpected kwd arg + message = "got an unexpected keyword argument" + with pytest.raises(TypeError, match=message): + stats.gmean([1, 2, 3], invalid_arg=True) + + message = " got multiple values for argument" + with pytest.raises(TypeError, match=message): + stats.gmean([1, 2, 3], a=True) + + message = "missing 1 required positional argument" + with pytest.raises(TypeError, match=message): + stats.gmean() + + message = "takes from 1 to 4 positional arguments but 5 were given" + with pytest.raises(TypeError, match=message): + stats.gmean([1, 2, 3], 0, float, [1, 1, 1], 10) + + +@pytest.mark.parametrize('dtype', [np.int16, np.float32, np.complex128]) +def test_array_like_input(dtype): + # Check that `_axis_nan_policy`-decorated functions work with custom + # containers that are coercible to numeric arrays + + class ArrLike: + def __init__(self, x, dtype): + self._x = x + self._dtype = dtype + + def __array__(self, dtype=None, copy=None): + return np.asarray(x, dtype=self._dtype) + + def __iter__(self): + # I don't know of a canonical way to determine whether an object should + # be coerced to a NumPy array or not. Currently, `xp_promote` checks + # whether they are iterable, and if so uses `_asarray` with whatever + # `xp` is. So for this to get coerced, it needs to be iterable. + return iter(self._x) + + x = [1]*2 + [3, 4, 5] + res = stats.mode(ArrLike(x, dtype=dtype)) + assert res.mode == 1 + assert res.count == 2 + + +@make_xp_test_case(stats._axis_nan_policy._broadcast_concatenate) +def test__broadcast_concatenate(xp): + # test that _broadcast_concatenate properly broadcasts arrays along all + # axes except `axis`, then concatenates along axis + rng = np.random.default_rng(7544340069) + a = rng.random((5, 4, 4, 3, 1, 6)) + b = rng.random((4, 1, 8, 2, 6)) + arrays = (xp.asarray(a), xp.asarray(b)) + c = stats._axis_nan_policy._broadcast_concatenate(arrays, axis=-3, xp=xp) + # broadcast manually as an independent check + a = np.tile(a, (1, 1, 1, 1, 2, 1)) + b = np.tile(b[None, ...], (5, 1, 4, 1, 1, 1)) + for index in product(*(range(i) for i in c.shape)): + i, j, k, l, m, n = index + if l < a.shape[-3]: + assert a[i, j, k, l, m, n] == c[i, j, k, l, m, n] + else: + assert b[i, j, k, l - a.shape[-3], m, n] == c[i, j, k, l, m, n] diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/test_binned_statistic.py b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_binned_statistic.py new file mode 100644 index 0000000000000000000000000000000000000000..a1fc499123f8f0eed07a767f2d9e361a84452bcb --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_binned_statistic.py @@ -0,0 +1,602 @@ +import numpy as np +from numpy.testing import assert_allclose +import pytest +from pytest import raises as assert_raises +from scipy.stats import (binned_statistic, binned_statistic_2d, + binned_statistic_dd) +from scipy._lib._util import check_random_state + +from .common_tests import check_named_results + + +class TestBinnedStatistic: + + @classmethod + def setup_class(cls): + rng = check_random_state(9865) + cls.x = rng.uniform(size=100) + cls.y = rng.uniform(size=100) + cls.v = rng.uniform(size=100) + cls.X = rng.uniform(size=(100, 3)) + cls.w = rng.uniform(size=100) + cls.u = rng.uniform(size=100) + 1e6 + + def test_1d_count(self): + x = self.x + v = self.v + + count1, edges1, bc = binned_statistic(x, v, 'count', bins=10) + count2, edges2 = np.histogram(x, bins=10) + + assert_allclose(count1, count2) + assert_allclose(edges1, edges2) + + def test_gh5927(self): + # smoke test for gh5927 - binned_statistic was using `is` for string + # comparison + x = self.x + v = self.v + statistics = ['mean', 'median', 'count', 'sum'] + for statistic in statistics: + binned_statistic(x, v, statistic, bins=10) + + def test_big_number_std(self): + # tests for numerical stability of std calculation + # see issue gh-10126 for more + x = self.x + u = self.u + stat1, edges1, bc = binned_statistic(x, u, 'std', bins=10) + stat2, edges2, bc = binned_statistic(x, u, np.std, bins=10) + + assert_allclose(stat1, stat2) + + def test_empty_bins_std(self): + # tests that std returns gives nan for empty bins + x = self.x + u = self.u + print(binned_statistic(x, u, 'count', bins=1000)) + stat1, edges1, bc = binned_statistic(x, u, 'std', bins=1000) + stat2, edges2, bc = binned_statistic(x, u, np.std, bins=1000) + + assert_allclose(stat1, stat2) + + def test_1d_result_attributes(self): + x = self.x + v = self.v + + res = binned_statistic(x, v, 'count', bins=10) + attributes = ('statistic', 'bin_edges', 'binnumber') + check_named_results(res, attributes) + + def test_1d_sum(self): + x = self.x + v = self.v + + sum1, edges1, bc = binned_statistic(x, v, 'sum', bins=10) + sum2, edges2 = np.histogram(x, bins=10, weights=v) + + assert_allclose(sum1, sum2) + assert_allclose(edges1, edges2) + + def test_1d_mean(self): + x = self.x + v = self.v + + stat1, edges1, bc = binned_statistic(x, v, 'mean', bins=10) + stat2, edges2, bc = binned_statistic(x, v, np.mean, bins=10) + + assert_allclose(stat1, stat2) + assert_allclose(edges1, edges2) + + def test_1d_std(self): + x = self.x + v = self.v + + stat1, edges1, bc = binned_statistic(x, v, 'std', bins=10) + stat2, edges2, bc = binned_statistic(x, v, np.std, bins=10) + + assert_allclose(stat1, stat2) + assert_allclose(edges1, edges2) + + def test_1d_min(self): + x = self.x + v = self.v + + stat1, edges1, bc = binned_statistic(x, v, 'min', bins=10) + stat2, edges2, bc = binned_statistic(x, v, np.min, bins=10) + + assert_allclose(stat1, stat2) + assert_allclose(edges1, edges2) + + def test_1d_max(self): + x = self.x + v = self.v + + stat1, edges1, bc = binned_statistic(x, v, 'max', bins=10) + stat2, edges2, bc = binned_statistic(x, v, np.max, bins=10) + + assert_allclose(stat1, stat2) + assert_allclose(edges1, edges2) + + def test_1d_median(self): + x = self.x + v = self.v + + stat1, edges1, bc = binned_statistic(x, v, 'median', bins=10) + stat2, edges2, bc = binned_statistic(x, v, np.median, bins=10) + + assert_allclose(stat1, stat2) + assert_allclose(edges1, edges2) + + def test_1d_bincode(self): + x = self.x[:20] + v = self.v[:20] + + count1, edges1, bc = binned_statistic(x, v, 'count', bins=3) + bc2 = np.array([3, 2, 1, 3, 2, 3, 3, 3, 3, 1, 1, 3, 3, 1, 2, 3, 1, + 1, 2, 1]) + + bcount = [(bc == i).sum() for i in np.unique(bc)] + + assert_allclose(bc, bc2) + assert_allclose(bcount, count1) + + def test_1d_range_keyword(self): + # Regression test for gh-3063, range can be (min, max) or [(min, max)] + rng = np.random.default_rng(6823616729) + x = np.arange(30) + data = rng.random(30) + + mean, bins, _ = binned_statistic(x[:15], data[:15]) + mean_range, bins_range, _ = binned_statistic(x, data, range=[(0, 14)]) + mean_range2, bins_range2, _ = binned_statistic(x, data, range=(0, 14)) + + assert_allclose(mean, mean_range) + assert_allclose(bins, bins_range) + assert_allclose(mean, mean_range2) + assert_allclose(bins, bins_range2) + + def test_1d_multi_values(self): + x = self.x + v = self.v + w = self.w + + stat1v, edges1v, bc1v = binned_statistic(x, v, 'mean', bins=10) + stat1w, edges1w, bc1w = binned_statistic(x, w, 'mean', bins=10) + stat2, edges2, bc2 = binned_statistic(x, [v, w], 'mean', bins=10) + + assert_allclose(stat2[0], stat1v) + assert_allclose(stat2[1], stat1w) + assert_allclose(edges1v, edges2) + assert_allclose(bc1v, bc2) + + def test_2d_count(self): + x = self.x + y = self.y + v = self.v + + count1, binx1, biny1, bc = binned_statistic_2d( + x, y, v, 'count', bins=5) + count2, binx2, biny2 = np.histogram2d(x, y, bins=5) + + assert_allclose(count1, count2) + assert_allclose(binx1, binx2) + assert_allclose(biny1, biny2) + + def test_2d_result_attributes(self): + x = self.x + y = self.y + v = self.v + + res = binned_statistic_2d(x, y, v, 'count', bins=5) + attributes = ('statistic', 'x_edge', 'y_edge', 'binnumber') + check_named_results(res, attributes) + + def test_2d_sum(self): + x = self.x + y = self.y + v = self.v + + sum1, binx1, biny1, bc = binned_statistic_2d(x, y, v, 'sum', bins=5) + sum2, binx2, biny2 = np.histogram2d(x, y, bins=5, weights=v) + + assert_allclose(sum1, sum2) + assert_allclose(binx1, binx2) + assert_allclose(biny1, biny2) + + def test_2d_mean(self): + x = self.x + y = self.y + v = self.v + + stat1, binx1, biny1, bc = binned_statistic_2d(x, y, v, 'mean', bins=5) + stat2, binx2, biny2, bc = binned_statistic_2d(x, y, v, np.mean, bins=5) + + assert_allclose(stat1, stat2) + assert_allclose(binx1, binx2) + assert_allclose(biny1, biny2) + + def test_2d_mean_unicode(self): + x = self.x + y = self.y + v = self.v + stat1, binx1, biny1, bc = binned_statistic_2d( + x, y, v, 'mean', bins=5) + stat2, binx2, biny2, bc = binned_statistic_2d(x, y, v, np.mean, bins=5) + assert_allclose(stat1, stat2) + assert_allclose(binx1, binx2) + assert_allclose(biny1, biny2) + + def test_2d_std(self): + x = self.x + y = self.y + v = self.v + + stat1, binx1, biny1, bc = binned_statistic_2d(x, y, v, 'std', bins=5) + stat2, binx2, biny2, bc = binned_statistic_2d(x, y, v, np.std, bins=5) + + assert_allclose(stat1, stat2) + assert_allclose(binx1, binx2) + assert_allclose(biny1, biny2) + + def test_2d_min(self): + x = self.x + y = self.y + v = self.v + + stat1, binx1, biny1, bc = binned_statistic_2d(x, y, v, 'min', bins=5) + stat2, binx2, biny2, bc = binned_statistic_2d(x, y, v, np.min, bins=5) + + assert_allclose(stat1, stat2) + assert_allclose(binx1, binx2) + assert_allclose(biny1, biny2) + + def test_2d_max(self): + x = self.x + y = self.y + v = self.v + + stat1, binx1, biny1, bc = binned_statistic_2d(x, y, v, 'max', bins=5) + stat2, binx2, biny2, bc = binned_statistic_2d(x, y, v, np.max, bins=5) + + assert_allclose(stat1, stat2) + assert_allclose(binx1, binx2) + assert_allclose(biny1, biny2) + + def test_2d_median(self): + x = self.x + y = self.y + v = self.v + + stat1, binx1, biny1, bc = binned_statistic_2d( + x, y, v, 'median', bins=5) + stat2, binx2, biny2, bc = binned_statistic_2d( + x, y, v, np.median, bins=5) + + assert_allclose(stat1, stat2) + assert_allclose(binx1, binx2) + assert_allclose(biny1, biny2) + + def test_2d_bincode(self): + x = self.x[:20] + y = self.y[:20] + v = self.v[:20] + + count1, binx1, biny1, bc = binned_statistic_2d( + x, y, v, 'count', bins=3) + bc2 = np.array([17, 11, 6, 16, 11, 17, 18, 17, 17, 7, 6, 18, 16, + 6, 11, 16, 6, 6, 11, 8]) + + bcount = [(bc == i).sum() for i in np.unique(bc)] + + assert_allclose(bc, bc2) + count1adj = count1[count1.nonzero()] + assert_allclose(bcount, count1adj) + + def test_2d_multi_values(self): + x = self.x + y = self.y + v = self.v + w = self.w + + stat1v, binx1v, biny1v, bc1v = binned_statistic_2d( + x, y, v, 'mean', bins=8) + stat1w, binx1w, biny1w, bc1w = binned_statistic_2d( + x, y, w, 'mean', bins=8) + stat2, binx2, biny2, bc2 = binned_statistic_2d( + x, y, [v, w], 'mean', bins=8) + + assert_allclose(stat2[0], stat1v) + assert_allclose(stat2[1], stat1w) + assert_allclose(binx1v, binx2) + assert_allclose(biny1w, biny2) + assert_allclose(bc1v, bc2) + + def test_2d_binnumbers_unraveled(self): + x = self.x + y = self.y + v = self.v + + stat, edgesx, bcx = binned_statistic(x, v, 'mean', bins=20) + stat, edgesy, bcy = binned_statistic(y, v, 'mean', bins=10) + + stat2, edgesx2, edgesy2, bc2 = binned_statistic_2d( + x, y, v, 'mean', bins=(20, 10), expand_binnumbers=True) + + bcx3 = np.searchsorted(edgesx, x, side='right') + bcy3 = np.searchsorted(edgesy, y, side='right') + + # `numpy.searchsorted` is non-inclusive on right-edge, compensate + bcx3[x == x.max()] -= 1 + bcy3[y == y.max()] -= 1 + + assert_allclose(bcx, bc2[0]) + assert_allclose(bcy, bc2[1]) + assert_allclose(bcx3, bc2[0]) + assert_allclose(bcy3, bc2[1]) + + def test_dd_count(self): + X = self.X + v = self.v + + count1, edges1, bc = binned_statistic_dd(X, v, 'count', bins=3) + count2, edges2 = np.histogramdd(X, bins=3) + + assert_allclose(count1, count2) + assert_allclose(edges1, edges2) + + def test_dd_result_attributes(self): + X = self.X + v = self.v + + res = binned_statistic_dd(X, v, 'count', bins=3) + attributes = ('statistic', 'bin_edges', 'binnumber') + check_named_results(res, attributes) + + def test_dd_sum(self): + X = self.X + v = self.v + + sum1, edges1, bc = binned_statistic_dd(X, v, 'sum', bins=3) + sum2, edges2 = np.histogramdd(X, bins=3, weights=v) + sum3, edges3, bc = binned_statistic_dd(X, v, np.sum, bins=3) + + assert_allclose(sum1, sum2) + assert_allclose(edges1, edges2) + assert_allclose(sum1, sum3) + assert_allclose(edges1, edges3) + + def test_dd_mean(self): + X = self.X + v = self.v + + stat1, edges1, bc = binned_statistic_dd(X, v, 'mean', bins=3) + stat2, edges2, bc = binned_statistic_dd(X, v, np.mean, bins=3) + + assert_allclose(stat1, stat2) + assert_allclose(edges1, edges2) + + def test_dd_std(self): + X = self.X + v = self.v + + stat1, edges1, bc = binned_statistic_dd(X, v, 'std', bins=3) + stat2, edges2, bc = binned_statistic_dd(X, v, np.std, bins=3) + + assert_allclose(stat1, stat2) + assert_allclose(edges1, edges2) + + def test_dd_min(self): + X = self.X + v = self.v + + stat1, edges1, bc = binned_statistic_dd(X, v, 'min', bins=3) + stat2, edges2, bc = binned_statistic_dd(X, v, np.min, bins=3) + + assert_allclose(stat1, stat2) + assert_allclose(edges1, edges2) + + def test_dd_max(self): + X = self.X + v = self.v + + stat1, edges1, bc = binned_statistic_dd(X, v, 'max', bins=3) + stat2, edges2, bc = binned_statistic_dd(X, v, np.max, bins=3) + + assert_allclose(stat1, stat2) + assert_allclose(edges1, edges2) + + def test_dd_median(self): + X = self.X + v = self.v + + stat1, edges1, bc = binned_statistic_dd(X, v, 'median', bins=3) + stat2, edges2, bc = binned_statistic_dd(X, v, np.median, bins=3) + + assert_allclose(stat1, stat2) + assert_allclose(edges1, edges2) + + def test_dd_bincode(self): + X = self.X[:20] + v = self.v[:20] + + count1, edges1, bc = binned_statistic_dd(X, v, 'count', bins=3) + bc2 = np.array([63, 33, 86, 83, 88, 67, 57, 33, 42, 41, 82, 83, 92, + 32, 36, 91, 43, 87, 81, 81]) + + bcount = [(bc == i).sum() for i in np.unique(bc)] + + assert_allclose(bc, bc2) + count1adj = count1[count1.nonzero()] + assert_allclose(bcount, count1adj) + + def test_dd_multi_values(self): + X = self.X + v = self.v + w = self.w + + for stat in ["count", "sum", "mean", "std", "min", "max", "median", + np.std]: + stat1v, edges1v, bc1v = binned_statistic_dd(X, v, stat, bins=8) + stat1w, edges1w, bc1w = binned_statistic_dd(X, w, stat, bins=8) + stat2, edges2, bc2 = binned_statistic_dd(X, [v, w], stat, bins=8) + assert_allclose(stat2[0], stat1v) + assert_allclose(stat2[1], stat1w) + assert_allclose(edges1v, edges2) + assert_allclose(edges1w, edges2) + assert_allclose(bc1v, bc2) + + def test_dd_binnumbers_unraveled(self): + X = self.X + v = self.v + + stat, edgesx, bcx = binned_statistic(X[:, 0], v, 'mean', bins=15) + stat, edgesy, bcy = binned_statistic(X[:, 1], v, 'mean', bins=20) + stat, edgesz, bcz = binned_statistic(X[:, 2], v, 'mean', bins=10) + + stat2, edges2, bc2 = binned_statistic_dd( + X, v, 'mean', bins=(15, 20, 10), expand_binnumbers=True) + + assert_allclose(bcx, bc2[0]) + assert_allclose(bcy, bc2[1]) + assert_allclose(bcz, bc2[2]) + + def test_dd_binned_statistic_result(self): + # NOTE: tests the reuse of bin_edges from previous call + rng = np.random.default_rng(8111360615) + x = rng.random((10000, 3)) + v = rng.random(10000) + bins = np.linspace(0, 1, 10) + bins = (bins, bins, bins) + + result = binned_statistic_dd(x, v, 'mean', bins=bins) + stat = result.statistic + + result = binned_statistic_dd(x, v, 'mean', + binned_statistic_result=result) + stat2 = result.statistic + + assert_allclose(stat, stat2) + + def test_dd_zero_dedges(self): + rng = np.random.default_rng(1132724173) + x = rng.random((10000, 3)) + v = rng.random(10000) + bins = np.linspace(0, 1, 10) + bins = np.append(bins, 1) + bins = (bins, bins, bins) + with assert_raises(ValueError, match='difference is numerically 0'): + binned_statistic_dd(x, v, 'mean', bins=bins) + + def test_dd_range_errors(self): + # Test that descriptive exceptions are raised as appropriate for bad + # values of the `range` argument. (See gh-12996) + with assert_raises(ValueError, + match='In range, start must be <= stop'): + binned_statistic_dd([self.y], self.v, + range=[[1, 0]]) + with assert_raises( + ValueError, + match='In dimension 1 of range, start must be <= stop'): + binned_statistic_dd([self.x, self.y], self.v, + range=[[1, 0], [0, 1]]) + with assert_raises( + ValueError, + match='In dimension 2 of range, start must be <= stop'): + binned_statistic_dd([self.x, self.y], self.v, + range=[[0, 1], [1, 0]]) + with assert_raises( + ValueError, + match='range given for 1 dimensions; 2 required'): + binned_statistic_dd([self.x, self.y], self.v, + range=[[0, 1]]) + + def test_binned_statistic_float32(self): + X = np.array([0, 0.42358226], dtype=np.float32) + stat, _, _ = binned_statistic(X, None, 'count', bins=5) + assert_allclose(stat, np.array([1, 0, 0, 0, 1], dtype=np.float64)) + + def test_gh14332(self): + # Test the wrong output when the `sample` is close to bin edge + x = [] + size = 20 + for i in range(size): + x += [1-0.1**i] + + bins = np.linspace(0,1,11) + sum1, edges1, bc = binned_statistic_dd(x, np.ones(len(x)), + bins=[bins], statistic='sum') + sum2, edges2 = np.histogram(x, bins=bins) + + assert_allclose(sum1, sum2) + assert_allclose(edges1[0], edges2) + + @pytest.mark.parametrize("dtype", [np.float64, np.complex128]) + @pytest.mark.parametrize("statistic", [np.mean, np.median, np.sum, np.std, + np.min, np.max, 'count', + lambda x: (x**2).sum(), + lambda x: (x**2).sum() * 1j]) + def test_dd_all(self, dtype, statistic): + def ref_statistic(x): + return len(x) if statistic == 'count' else statistic(x) + + rng = np.random.default_rng(3704743126639371) + n = 10 + x = rng.random(size=n) + i = x >= 0.5 + v = rng.random(size=n) + if dtype is np.complex128: + v = v + rng.random(size=n)*1j + + stat, _, _ = binned_statistic_dd(x, v, statistic, bins=2) + ref = np.array([ref_statistic(v[~i]), ref_statistic(v[i])]) + assert_allclose(stat, ref) + assert stat.dtype == np.result_type(ref.dtype, np.float64) + + @pytest.mark.parametrize('bins', [10, (10, 10, 10), [np.arange(10)]*3]) + def test_nan_gh17154(self, bins): + # gh-17154 reported that `binned_statistic_dd` raised an error when `samples` + # contains NaNs and argued that this was an inappropriate default. gh-23751 + # decided that it was indeed an appropriate choice, and went further, ensuring + # that the same error would be raised regardless of how `bins` is specified. + + # Generate a samples (with NaNs) and values + rng = np.random.default_rng(529847529874529829) + samples = rng.standard_normal(size=(100, 3)) + values = rng.standard_normal(100) + i = rng.random(samples.shape) < 0.05 + samples[i] = np.nan + + message = "The sample on which `values` is to be binned..." + with pytest.raises(ValueError, match=message): + binned_statistic_dd(samples, values) + + def test_inf_gh17154(self): + # While addressing gh-17154, it seemed that `inf`s should not be ignored + # entirely; rather, values corresponding with `inf`s can always be in outlier + # bins. + + # Generate a samples (with infs) and values + rng = np.random.default_rng(529847529874529829) + samples = rng.standard_normal(size=(100, 3)) + values = rng.standard_normal(100) + i_pinf = rng.random(samples.shape) < 0.05 + samples[i_pinf] = np.inf + i_ninf = rng.random(samples.shape) < 0.05 + samples[i_ninf] = -np.inf + + # Compute results + res = binned_statistic_dd(samples, values) + + # Compute reference values (with bins computed ignoring infs) + i_inf = i_pinf | i_ninf + temp = samples.copy() + temp[i_inf] = 0 + low = np.min(temp, axis=0) + high = np.max(temp, axis=0) + ref = binned_statistic_dd(samples, values, range=list(zip(low, high))) + + # Compare results against reference values + np.testing.assert_allclose(res[0], ref[0]) + np.testing.assert_allclose(res[1], ref[1]) + np.testing.assert_allclose(res[2], ref[2]) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/test_censored_data.py b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_censored_data.py new file mode 100644 index 0000000000000000000000000000000000000000..ae71dcfaccf899645051287f8944131ec48a1eee --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_censored_data.py @@ -0,0 +1,152 @@ +# Tests for the CensoredData class. + +import pytest +import numpy as np +from numpy.testing import assert_equal, assert_array_equal +from scipy.stats import CensoredData + + +class TestCensoredData: + + def test_basic(self): + uncensored = [1] + left = [0] + right = [2, 5] + interval = [[2, 3]] + data = CensoredData(uncensored, left=left, right=right, + interval=interval) + assert_equal(data._uncensored, uncensored) + assert_equal(data._left, left) + assert_equal(data._right, right) + assert_equal(data._interval, interval) + + udata = data._uncensor() + assert_equal(udata, np.concatenate((uncensored, left, right, + np.mean(interval, axis=1)))) + + def test_right_censored(self): + x = np.array([0, 3, 2.5]) + is_censored = np.array([0, 1, 0], dtype=bool) + data = CensoredData.right_censored(x, is_censored) + assert_equal(data._uncensored, x[~is_censored]) + assert_equal(data._right, x[is_censored]) + assert_equal(data._left, []) + assert_equal(data._interval, np.empty((0, 2))) + + def test_left_censored(self): + x = np.array([0, 3, 2.5]) + is_censored = np.array([0, 1, 0], dtype=bool) + data = CensoredData.left_censored(x, is_censored) + assert_equal(data._uncensored, x[~is_censored]) + assert_equal(data._left, x[is_censored]) + assert_equal(data._right, []) + assert_equal(data._interval, np.empty((0, 2))) + + def test_interval_censored_basic(self): + a = [0.5, 2.0, 3.0, 5.5] + b = [1.0, 2.5, 3.5, 7.0] + data = CensoredData.interval_censored(low=a, high=b) + assert_array_equal(data._interval, np.array(list(zip(a, b)))) + assert data._uncensored.shape == (0,) + assert data._left.shape == (0,) + assert data._right.shape == (0,) + + def test_interval_censored_mixed(self): + # This is actually a mix of uncensored, left-censored, right-censored + # and interval-censored data. Check that when the `interval_censored` + # class method is used, the data is correctly separated into the + # appropriate arrays. + a = [0.5, -np.inf, -13.0, 2.0, 1.0, 10.0, -1.0] + b = [0.5, 2500.0, np.inf, 3.0, 1.0, 11.0, np.inf] + data = CensoredData.interval_censored(low=a, high=b) + assert_array_equal(data._interval, [[2.0, 3.0], [10.0, 11.0]]) + assert_array_equal(data._uncensored, [0.5, 1.0]) + assert_array_equal(data._left, [2500.0]) + assert_array_equal(data._right, [-13.0, -1.0]) + + def test_interval_to_other_types(self): + # The interval parameter can represent uncensored and + # left- or right-censored data. Test the conversion of such + # an example to the canonical form in which the different + # types have been split into the separate arrays. + interval = np.array([[0, 1], # interval-censored + [2, 2], # not censored + [3, 3], # not censored + [9, np.inf], # right-censored + [8, np.inf], # right-censored + [-np.inf, 0], # left-censored + [1, 2]]) # interval-censored + data = CensoredData(interval=interval) + assert_equal(data._uncensored, [2, 3]) + assert_equal(data._left, [0]) + assert_equal(data._right, [9, 8]) + assert_equal(data._interval, [[0, 1], [1, 2]]) + + def test_empty_arrays(self): + data = CensoredData(uncensored=[], left=[], right=[], interval=[]) + assert data._uncensored.shape == (0,) + assert data._left.shape == (0,) + assert data._right.shape == (0,) + assert data._interval.shape == (0, 2) + assert len(data) == 0 + + def test_invalid_constructor_args(self): + with pytest.raises(ValueError, match='must be a one-dimensional'): + CensoredData(uncensored=[[1, 2, 3]]) + with pytest.raises(ValueError, match='must be a one-dimensional'): + CensoredData(left=[[1, 2, 3]]) + with pytest.raises(ValueError, match='must be a one-dimensional'): + CensoredData(right=[[1, 2, 3]]) + with pytest.raises(ValueError, match='must be a two-dimensional'): + CensoredData(interval=[[1, 2, 3]]) + + with pytest.raises(ValueError, match='must not contain nan'): + CensoredData(uncensored=[1, np.nan, 2]) + with pytest.raises(ValueError, match='must not contain nan'): + CensoredData(left=[1, np.nan, 2]) + with pytest.raises(ValueError, match='must not contain nan'): + CensoredData(right=[1, np.nan, 2]) + with pytest.raises(ValueError, match='must not contain nan'): + CensoredData(interval=[[1, np.nan], [2, 3]]) + + with pytest.raises(ValueError, + match='both values must not be infinite'): + CensoredData(interval=[[1, 3], [2, 9], [np.inf, np.inf]]) + + with pytest.raises(ValueError, + match='left value must not exceed the right'): + CensoredData(interval=[[1, 0], [2, 2]]) + + @pytest.mark.parametrize('func', [CensoredData.left_censored, + CensoredData.right_censored]) + def test_invalid_left_right_censored_args(self, func): + with pytest.raises(ValueError, + match='`x` must be one-dimensional'): + func([[1, 2, 3]], [0, 1, 1]) + with pytest.raises(ValueError, + match='`censored` must be one-dimensional'): + func([1, 2, 3], [[0, 1, 1]]) + with pytest.raises(ValueError, match='`x` must not contain'): + func([1, 2, np.nan], [0, 1, 1]) + with pytest.raises(ValueError, match='must have the same length'): + func([1, 2, 3], [0, 0, 1, 1]) + + def test_invalid_censored_args(self): + with pytest.raises(ValueError, + match='`low` must be a one-dimensional'): + CensoredData.interval_censored(low=[[3]], high=[4, 5]) + with pytest.raises(ValueError, + match='`high` must be a one-dimensional'): + CensoredData.interval_censored(low=[3], high=[[4, 5]]) + with pytest.raises(ValueError, match='`low` must not contain'): + CensoredData.interval_censored([1, 2, np.nan], [0, 1, 1]) + with pytest.raises(ValueError, match='must have the same length'): + CensoredData.interval_censored([1, 2, 3], [0, 0, 1, 1]) + + def test_count_censored(self): + x = [1, 2, 3] + # data1 has no censored data. + data1 = CensoredData(x) + assert data1.num_censored() == 0 + data2 = CensoredData(uncensored=[2.5], left=[10], interval=[[0, 1]]) + assert data2.num_censored() == 2 diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/test_contingency.py b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_contingency.py new file mode 100644 index 0000000000000000000000000000000000000000..aa83e05d7da6b3d21d5515a332344c2347f0d7eb --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_contingency.py @@ -0,0 +1,294 @@ +import numpy as np +from numpy.testing import (assert_equal, assert_array_equal, + assert_array_almost_equal, assert_approx_equal, + assert_allclose) +import pytest +from pytest import raises as assert_raises +from scipy import stats +from scipy.special import xlogy +from scipy.stats.contingency import (margins, expected_freq, + chi2_contingency, association) + + +def test_margins(): + a = np.array([1]) + m = margins(a) + assert_equal(len(m), 1) + m0 = m[0] + assert_array_equal(m0, np.array([1])) + + a = np.array([[1]]) + m0, m1 = margins(a) + expected0 = np.array([[1]]) + expected1 = np.array([[1]]) + assert_array_equal(m0, expected0) + assert_array_equal(m1, expected1) + + a = np.arange(12).reshape(2, 6) + m0, m1 = margins(a) + expected0 = np.array([[15], [51]]) + expected1 = np.array([[6, 8, 10, 12, 14, 16]]) + assert_array_equal(m0, expected0) + assert_array_equal(m1, expected1) + + a = np.arange(24).reshape(2, 3, 4) + m0, m1, m2 = margins(a) + expected0 = np.array([[[66]], [[210]]]) + expected1 = np.array([[[60], [92], [124]]]) + expected2 = np.array([[[60, 66, 72, 78]]]) + assert_array_equal(m0, expected0) + assert_array_equal(m1, expected1) + assert_array_equal(m2, expected2) + + +def test_expected_freq(): + assert_array_equal(expected_freq([1]), np.array([1.0])) + + observed = np.array([[[2, 0], [0, 2]], [[0, 2], [2, 0]], [[1, 1], [1, 1]]]) + e = expected_freq(observed) + assert_array_equal(e, np.ones_like(observed)) + + observed = np.array([[10, 10, 20], [20, 20, 20]]) + e = expected_freq(observed) + correct = np.array([[12., 12., 16.], [18., 18., 24.]]) + assert_array_almost_equal(e, correct) + + +class TestChi2Contingency: + def test_chi2_contingency_trivial(self): + # Some very simple tests for chi2_contingency. + + # A trivial case + obs = np.array([[1, 2], [1, 2]]) + chi2, p, dof, expected = chi2_contingency(obs, correction=False) + assert_equal(chi2, 0.0) + assert_equal(p, 1.0) + assert_equal(dof, 1) + assert_array_equal(obs, expected) + + # A *really* trivial case: 1-D data. + obs = np.array([1, 2, 3]) + chi2, p, dof, expected = chi2_contingency(obs, correction=False) + assert_equal(chi2, 0.0) + assert_equal(p, 1.0) + assert_equal(dof, 0) + assert_array_equal(obs, expected) + + def test_chi2_contingency_R(self): + # Some test cases that were computed independently, using R. + + # Rcode = \ + # """ + # # Data vector. + # data <- c( + # 12, 34, 23, 4, 47, 11, + # 35, 31, 11, 34, 10, 18, + # 12, 32, 9, 18, 13, 19, + # 12, 12, 14, 9, 33, 25 + # ) + # + # # Create factor tags:r=rows, c=columns, t=tiers + # r <- factor(gl(4, 2*3, 2*3*4, labels=c("r1", "r2", "r3", "r4"))) + # c <- factor(gl(3, 1, 2*3*4, labels=c("c1", "c2", "c3"))) + # t <- factor(gl(2, 3, 2*3*4, labels=c("t1", "t2"))) + # + # # 3-way Chi squared test of independence + # s = summary(xtabs(data~r+c+t)) + # print(s) + # """ + # Routput = \ + # """ + # Call: xtabs(formula = data ~ r + c + t) + # Number of cases in table: 478 + # Number of factors: 3 + # Test for independence of all factors: + # Chisq = 102.17, df = 17, p-value = 3.514e-14 + # """ + obs = np.array( + [[[12, 34, 23], + [35, 31, 11], + [12, 32, 9], + [12, 12, 14]], + [[4, 47, 11], + [34, 10, 18], + [18, 13, 19], + [9, 33, 25]]]) + chi2, p, dof, expected = chi2_contingency(obs) + assert_approx_equal(chi2, 102.17, significant=5) + assert_approx_equal(p, 3.514e-14, significant=4) + assert_equal(dof, 17) + + # Rcode = \ + # """ + # # Data vector. + # data <- c( + # # + # 12, 17, + # 11, 16, + # # + # 11, 12, + # 15, 16, + # # + # 23, 15, + # 30, 22, + # # + # 14, 17, + # 15, 16 + # ) + # + # # Create factor tags:r=rows, c=columns, d=depths(?), t=tiers + # r <- factor(gl(2, 2, 2*2*2*2, labels=c("r1", "r2"))) + # c <- factor(gl(2, 1, 2*2*2*2, labels=c("c1", "c2"))) + # d <- factor(gl(2, 4, 2*2*2*2, labels=c("d1", "d2"))) + # t <- factor(gl(2, 8, 2*2*2*2, labels=c("t1", "t2"))) + # + # # 4-way Chi squared test of independence + # s = summary(xtabs(data~r+c+d+t)) + # print(s) + # """ + # Routput = \ + # """ + # Call: xtabs(formula = data ~ r + c + d + t) + # Number of cases in table: 262 + # Number of factors: 4 + # Test for independence of all factors: + # Chisq = 8.758, df = 11, p-value = 0.6442 + # """ + obs = np.array( + [[[[12, 17], + [11, 16]], + [[11, 12], + [15, 16]]], + [[[23, 15], + [30, 22]], + [[14, 17], + [15, 16]]]]) + chi2, p, dof, expected = chi2_contingency(obs) + assert_approx_equal(chi2, 8.758, significant=4) + assert_approx_equal(p, 0.6442, significant=4) + assert_equal(dof, 11) + + def test_chi2_contingency_g(self): + c = np.array([[15, 60], [15, 90]]) + g, p, dof, e = chi2_contingency(c, lambda_='log-likelihood', + correction=False) + assert_allclose(g, 2*xlogy(c, c/e).sum()) + + g, p, dof, e = chi2_contingency(c, lambda_='log-likelihood', + correction=True) + c_corr = c + np.array([[-0.5, 0.5], [0.5, -0.5]]) + assert_allclose(g, 2*xlogy(c_corr, c_corr/e).sum()) + + c = np.array([[10, 12, 10], [12, 10, 10]]) + g, p, dof, e = chi2_contingency(c, lambda_='log-likelihood') + assert_allclose(g, 2*xlogy(c, c/e).sum()) + + def test_chi2_contingency_bad_args(self): + # Test that "bad" inputs raise a ValueError. + + # Negative value in the array of observed frequencies. + obs = np.array([[-1, 10], [1, 2]]) + assert_raises(ValueError, chi2_contingency, obs) + + # The zeros in this will result in zeros in the array + # of expected frequencies. + obs = np.array([[0, 1], [0, 1]]) + assert_raises(ValueError, chi2_contingency, obs) + + # A degenerate case: `observed` has size 0. + obs = np.empty((0, 8)) + assert_raises(ValueError, chi2_contingency, obs) + + def test_chi2_contingency_yates_gh13875(self): + # Magnitude of Yates' continuity correction should not exceed difference + # between expected and observed value of the statistic; see gh-13875 + observed = np.array([[1573, 3], [4, 0]]) + p = chi2_contingency(observed)[1] + assert_allclose(p, 1, rtol=1e-12) + + @pytest.mark.parametrize("correction", [False, True]) + def test_result(self, correction): + obs = np.array([[1, 2], [1, 2]]) + res = chi2_contingency(obs, correction=correction) + assert_equal((res.statistic, res.pvalue, res.dof, res.expected_freq), res) + + @pytest.mark.slow + def test_exact_permutation(self): + table = np.arange(4).reshape(2, 2) + ref_statistic = chi2_contingency(table, correction=False).statistic + ref_pvalue = stats.fisher_exact(table).pvalue + method = stats.PermutationMethod(n_resamples=50000) + res = chi2_contingency(table, correction=False, method=method) + assert_equal(res.statistic, ref_statistic) + assert_allclose(res.pvalue, ref_pvalue, rtol=1e-15) + + @pytest.mark.slow + @pytest.mark.parametrize('method', (stats.PermutationMethod, + stats.MonteCarloMethod)) + def test_resampling_randomized(self, method): + rng = np.random.default_rng(2592340925) + # need to have big sum for asymptotic approximation to be good + rows = [300, 1000, 800] + cols = [200, 400, 800, 700] + table = stats.random_table(rows, cols, seed=rng).rvs() + res = chi2_contingency(table, correction=False, method=method(rng=rng)) + ref = chi2_contingency(table, correction=False) + assert_equal(res.statistic, ref.statistic) + assert_allclose(res.pvalue, ref.pvalue, atol=5e-3) + assert_equal(res.dof, np.nan) + assert_equal(res.expected_freq, ref.expected_freq) + + def test_resampling_invalid_args(self): + table = np.arange(8).reshape(2, 2, 2) + + method = stats.PermutationMethod() + message = "Use of `method` is only compatible with two-way tables." + with pytest.raises(ValueError, match=message): + chi2_contingency(table, correction=False, method=method) + + table = np.arange(4).reshape(2, 2) + + method = stats.PermutationMethod() + message = "`correction=True` is not compatible with..." + with pytest.raises(ValueError, match=message): + chi2_contingency(table, method=method) + + method = stats.MonteCarloMethod() + message = "`lambda_=2` is not compatible with..." + with pytest.raises(ValueError, match=message): + chi2_contingency(table, correction=False, lambda_=2, method=method) + + method = 'herring' + message = "`method='herring'` not recognized; if provided, `method`..." + with pytest.raises(ValueError, match=message): + chi2_contingency(table, correction=False, method=method) + + method = stats.MonteCarloMethod(rvs=stats.norm.rvs) + message = "If the `method` argument of `chi2_contingency` is..." + with pytest.raises(ValueError, match=message): + chi2_contingency(table, correction=False, method=method) + + +def test_bad_association_args(): + # Invalid Test Statistic + assert_raises(ValueError, association, [[1, 2], [3, 4]], "X") + # Invalid array shape + assert_raises(ValueError, association, [[[1, 2]], [[3, 4]]], "cramer") + # chi2_contingency exception + assert_raises(ValueError, association, [[-1, 10], [1, 2]], 'cramer') + # Invalid Array Item Data Type + assert_raises(ValueError, association, + np.array([[1, 2], ["dd", 4]], dtype=object), 'cramer') + + +@pytest.mark.parametrize('stat, expected', + [('cramer', 0.09222412010290792), + ('tschuprow', 0.0775509319944633), + ('pearson', 0.12932925727138758)]) +def test_assoc(stat, expected): + # 2d Array + obs1 = np.array([[12, 13, 14, 15, 16], + [17, 16, 18, 19, 11], + [9, 15, 14, 12, 11]]) + a = association(observed=obs1, method=stat) + assert_allclose(a, expected) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/test_continued_fraction.py b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_continued_fraction.py new file mode 100644 index 0000000000000000000000000000000000000000..e6911aa4956cc66a95eeb1e87aedb04782e2440b --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_continued_fraction.py @@ -0,0 +1,168 @@ +import math + +import pytest +import numpy as np + +from scipy._lib._array_api import array_namespace +from scipy._lib._array_api_no_0d import xp_assert_close, xp_assert_less, xp_assert_equal +from scipy.stats._continued_fraction import _continued_fraction + + +@pytest.mark.skip_xp_backends('array_api_strict', reason='No fancy indexing assignment') +@pytest.mark.skip_xp_backends('jax.numpy', reason="Don't support mutation") +# dask doesn't like lines like this +# n = int(xp.real(xp_ravel(n))[0]) +# (at some point in here the shape becomes nan) +@pytest.mark.skip_xp_backends('dask.array', reason="dask has issues with the shapes") +@pytest.mark.uses_xp_capabilities(False, reason="private") +class TestContinuedFraction: + rng = np.random.default_rng(5895448232066142650) + p = rng.uniform(1, 10, size=10) + + def a1(self, n, x=1.5): + if n == 0: + y = 0*x + elif n == 1: + y = x + else: + y = -x**2 + return y + + def b1(self, n, x=1.5): + if n == 0: + y = 0*x + else: + one = x/x # gets array of correct type, dtype, and shape + y = one * (2*n - 1) + return y + + def log_a1(self, n, x): + xp = array_namespace(x) + if n == 0: + y = xp.full_like(x, -xp.asarray(math.inf, dtype=x.dtype)) + elif n == 1: + y = xp.log(x) + else: + y = 2 * xp.log(x) + math.pi * 1j + return y + + def log_b1(self, n, x): + xp = array_namespace(x) + if n == 0: + y = xp.full_like(x, -xp.asarray(math.inf, dtype=x.dtype)) + else: + one = x - x # gets array of correct type, dtype, and shape + y = one + math.log(2 * n - 1) + return y + + def test_input_validation(self, xp): + a1 = self.a1 + b1 = self.b1 + + message = '`a` and `b` must be callable.' + with pytest.raises(ValueError, match=message): + _continued_fraction(1, b1) + with pytest.raises(ValueError, match=message): + _continued_fraction(a1, 1) + + message = r'`eps` and `tiny` must be \(or represent the logarithm of\)...' + with pytest.raises(ValueError, match=message): + _continued_fraction(a1, b1, tolerances={'eps': -10}) + with pytest.raises(ValueError, match=message): + _continued_fraction(a1, b1, tolerances={'eps': np.nan}) + with pytest.raises(ValueError, match=message): + _continued_fraction(a1, b1, tolerances={'eps': 1+1j}, log=True) + with pytest.raises(ValueError, match=message): + _continued_fraction(a1, b1, tolerances={'tiny': 0}) + with pytest.raises(ValueError, match=message): + _continued_fraction(a1, b1, tolerances={'tiny': np.inf}) + with pytest.raises(ValueError, match=message): + _continued_fraction(a1, b1, tolerances={'tiny': np.inf}, log=True) + # this should not raise + kwargs = dict(args=xp.asarray(1.5+0j), log=True, maxiter=0) + _continued_fraction(a1, b1, tolerances={'eps': -10}, **kwargs) + _continued_fraction(a1, b1, tolerances={'tiny': -10}, **kwargs) + + message = '`maxiter` must be a non-negative integer.' + with pytest.raises(ValueError, match=message): + _continued_fraction(a1, b1, maxiter=-1) + + message = '`log` must be boolean.' + with pytest.raises(ValueError, match=message): + _continued_fraction(a1, b1, log=2) + + @pytest.mark.parametrize('dtype', ['float32', 'float64', 'complex64', 'complex128']) + @pytest.mark.parametrize('shape', [(), (1,), (3,), (3, 2)]) + def test_basic(self, shape, dtype, xp): + np_dtype = getattr(np, dtype) + xp_dtype = getattr(xp, dtype) + rng = np.random.default_rng(2435908729190400) + + x = rng.random(shape).astype(np_dtype) + x = x + rng.random(shape).astype(np_dtype)*1j if dtype.startswith('c') else x + x = xp.asarray(x, dtype=xp_dtype) + + res = _continued_fraction(self.a1, self.b1, args=(x,)) + ref = xp.tan(x) + xp_assert_close(res.f, ref) + + @pytest.mark.skip_xp_backends('torch', reason='pytorch/pytorch#136063') + @pytest.mark.parametrize('dtype', ['float32', 'float64']) + @pytest.mark.parametrize('shape', [(), (1,), (3,), (3, 2)]) + def test_log(self, shape, dtype, xp): + np_dtype = getattr(np, dtype) + rng = np.random.default_rng(2435908729190400) + x = rng.random(shape).astype(np_dtype) + x = xp.asarray(x) + + res = _continued_fraction(self.log_a1, self.log_b1, args=(x + 0j,), log=True) + ref = xp.tan(x) + xp_assert_close(xp.exp(xp.real(res.f)), ref) + + def test_maxiter(self, xp): + rng = np.random.default_rng(2435908729190400) + x = xp.asarray(rng.random(), dtype=xp.float64) + ref = xp.tan(x) + + res1 = _continued_fraction(self.a1, self.b1, args=(x,), maxiter=3) + assert res1.nit == 3 + + res2 = _continued_fraction(self.a1, self.b1, args=(x,), maxiter=6) + assert res2.nit == 6 + + xp_assert_less(xp.abs(res2.f - ref), xp.abs(res1.f - ref)) + + def test_eps(self, xp): + x = xp.asarray(1.5, dtype=xp.float64) # x = 1.5 is the default defined above + ref = xp.tan(x) + res1 = _continued_fraction(self.a1, self.b1, args=(x,), + tolerances={'eps': 1e-6}) + res2 = _continued_fraction(self.a1, self.b1, args=(x,)) + xp_assert_less(res1.nit, res2.nit) + xp_assert_less(xp.abs(res2.f - ref), xp.abs(res1.f - ref)) + + def test_feval(self, xp): + def a(n, x): + a.nfev += 1 + return n * x + + def b(n, x): + b.nfev += 1 + return n * x + + a.nfev, b.nfev = 0, 0 + + res = _continued_fraction(a, b, args=(xp.asarray(1.),)) + assert res.nfev == a.nfev == b.nfev == res.nit + 1 + + def test_status(self, xp): + x = xp.asarray([1, 10, np.nan], dtype=xp.float64) + res = _continued_fraction(self.a1, self.b1, args=(x,), maxiter=15) + xp_assert_equal(res.success, xp.asarray([True, False, False])) + xp_assert_equal(res.status, xp.asarray([0, -2, -3], dtype=xp.int32)) + + def test_special_cases(self, xp): + one = xp.asarray(1) + res = _continued_fraction(lambda x: one, lambda x: one, maxiter=0) + xp_assert_close(res.f, xp.asarray(1.)) + assert res.nit == res.nfev - 1 == 0 diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/test_continuous.py b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_continuous.py new file mode 100644 index 0000000000000000000000000000000000000000..898b7bcdf6b5ae4d72404fe98a3fd32ea8a90564 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_continuous.py @@ -0,0 +1,2328 @@ +import itertools as it +import os +import pickle +from copy import deepcopy + +import numpy as np +from numpy import inf +import pytest +from numpy.testing import assert_allclose, assert_equal +from hypothesis import strategies, given, reproduce_failure, settings # noqa: F401 +import hypothesis.extra.numpy as npst + +from scipy import special +from scipy import stats +from scipy.stats._fit import _kolmogorov_smirnov +from scipy.stats._ksstats import kolmogn +from scipy.stats import qmc +from scipy.stats._distr_params import distcont, distdiscrete +from scipy.stats._distribution_infrastructure import ( + _Domain, _RealInterval, _Parameter, _Parameterization, _RealParameter, + ContinuousDistribution, ShiftedScaledDistribution, _fiinfo, + _generate_domain_support, Mixture, _logexpxmexpy) +from scipy.stats._new_distributions import StandardNormal, _LogUniform, _Gamma +from scipy.stats._new_distributions import DiscreteDistribution +from scipy.stats import Normal, Logistic, Uniform, Binomial + + +class Test_RealInterval: + rng = np.random.default_rng(349849812549824) + + def test_iv(self): + domain = _RealInterval(endpoints=('a', 'b')) + message = "The endpoints of the distribution are defined..." + with pytest.raises(TypeError, match=message): + domain.get_numerical_endpoints(dict) + + @pytest.mark.parametrize('x', [rng.uniform(10, 10, size=(2, 3, 4)), + -np.inf, np.pi]) + def test_contains_simple(self, x): + # Test `contains` when endpoints are defined by constants + a, b = -np.inf, np.pi + domain = _RealInterval(endpoints=(a, b), inclusive=(False, True)) + assert_equal(domain.contains(x), (a < x) & (x <= b)) + + @pytest.mark.slow + @given(shapes=npst.mutually_broadcastable_shapes(num_shapes=3, min_side=0), + inclusive_a=strategies.booleans(), + inclusive_b=strategies.booleans(), + data=strategies.data()) + def test_contains(self, shapes, inclusive_a, inclusive_b, data): + # Test `contains` when endpoints are defined by parameters + input_shapes, result_shape = shapes + shape_a, shape_b, shape_x = input_shapes + + # Without defining min and max values, I spent forever trying to set + # up a valid test without overflows or similar just drawing arrays. + a_elements = dict(allow_nan=False, allow_infinity=False, + min_value=-1e3, max_value=1) + b_elements = dict(allow_nan=False, allow_infinity=False, + min_value=2, max_value=1e3) + a = data.draw(npst.arrays(npst.floating_dtypes(), + shape_a, elements=a_elements)) + b = data.draw(npst.arrays(npst.floating_dtypes(), + shape_b, elements=b_elements)) + # ensure some points are to the left, some to the right, and some + # are exactly on the boundary + d = b - a + x = np.concatenate([np.linspace(a-d, a, 10), + np.linspace(a, b, 10), + np.linspace(b, b+d, 10)]) + # Domain is defined by two parameters, 'a' and 'b' + domain = _RealInterval(endpoints=('a', 'b'), + inclusive=(inclusive_a, inclusive_b)) + domain.define_parameters(_RealParameter('a', domain=_RealInterval()), + _RealParameter('b', domain=_RealInterval())) + # Check that domain and string evaluation give the same result + res = domain.contains(x, dict(a=a, b=b)) + + # Apparently, `np.float16([2]) < np.float32(2.0009766)` is False + # but `np.float16([2]) < np.float32([2.0009766])` is True + # dtype = np.result_type(a.dtype, b.dtype, x.dtype) + # a, b, x = a.astype(dtype), b.astype(dtype), x.astype(dtype) + # unclear whether we should be careful about this, since it will be + # fixed with NEP50. Just do what makes the test pass. + left_comparison = '<=' if inclusive_a else '<' + right_comparison = '<=' if inclusive_b else '<' + ref = eval(f'(a {left_comparison} x) & (x {right_comparison} b)') + assert_equal(res, ref) + + @pytest.mark.parametrize("inclusive", list(it.product([True, False], repeat=2))) + @pytest.mark.parametrize("a,b", [(0, 1), (3, 1)]) + def test_contains_function_endpoints(self, inclusive, a, b): + # Test `contains` when endpoints are defined by functions. + endpoints = (lambda a, b: (a - b) / 2, lambda a, b: (a + b) / 2) + domain = _RealInterval(endpoints=endpoints, inclusive=inclusive) + x = np.asarray([(a - 2*b)/2, (a - b)/2, a/2, (a + b)/2, (a + 2*b)/2]) + res = domain.contains(x, dict(a=a, b=b)) + + numerical_endpoints = ((a - b) / 2, (a + b) / 2) + assert numerical_endpoints == domain.get_numerical_endpoints(dict(a=a, b=b)) + alpha, beta = numerical_endpoints + + above_left = alpha <= x if inclusive[0] else alpha < x + below_right = x <= beta if inclusive[1] else x < beta + ref = above_left & below_right + assert_equal(res, ref) + + + @pytest.mark.parametrize('case', [ + (-np.inf, np.pi, False, True, r"(-\infty, \pi]"), + ('a', 5, True, False, "[a, 5)") + ]) + def test_str(self, case): + domain = _RealInterval(endpoints=case[:2], inclusive=case[2:4]) + assert str(domain) == case[4] + + @pytest.mark.slow + @given(a=strategies.one_of( + strategies.decimals(allow_nan=False), + strategies.characters(whitelist_categories="L"), # type: ignore[arg-type] + strategies.sampled_from(list(_Domain.symbols))), + b=strategies.one_of( + strategies.decimals(allow_nan=False), + strategies.characters(whitelist_categories="L"), # type: ignore[arg-type] + strategies.sampled_from(list(_Domain.symbols))), + inclusive_a=strategies.booleans(), + inclusive_b=strategies.booleans(), + ) + def test_str2(self, a, b, inclusive_a, inclusive_b): + # I wrote this independently from the implementation of __str__, but + # I imagine it looks pretty similar to __str__. + a = _Domain.symbols.get(a, a) + b = _Domain.symbols.get(b, b) + left_bracket = '[' if inclusive_a else '(' + right_bracket = ']' if inclusive_b else ')' + domain = _RealInterval(endpoints=(a, b), + inclusive=(inclusive_a, inclusive_b)) + ref = f"{left_bracket}{a}, {b}{right_bracket}" + assert str(domain) == ref + + def test_symbols_gh22137(self): + # `symbols` was accidentally shared between instances originally + # Check that this is no longer the case + domain1 = _RealInterval(endpoints=(0, 1)) + domain2 = _RealInterval(endpoints=(0, 1)) + assert domain1.symbols is not domain2.symbols + + +def draw_distribution_from_family(family, data, rng, proportions, min_side=0): + # If the distribution has parameters, choose a parameterization and + # draw broadcastable shapes for the parameter arrays. + n_parameterizations = family._num_parameterizations() + if n_parameterizations > 0: + i = data.draw(strategies.integers(0, max_value=n_parameterizations-1)) + n_parameters = family._num_parameters(i) + shapes, result_shape = data.draw( + npst.mutually_broadcastable_shapes(num_shapes=n_parameters, + min_side=min_side)) + dist = family._draw(shapes, rng=rng, proportions=proportions, + i_parameterization=i) + else: + dist = family._draw(rng=rng) + result_shape = tuple() + + # Draw a broadcastable shape for the arguments, and draw values for the + # arguments. + x_shape = data.draw(npst.broadcastable_shapes(result_shape, + min_side=min_side)) + x = dist._variable.draw(x_shape, parameter_values=dist._parameters, + proportions=proportions, rng=rng, region='typical') + x_result_shape = np.broadcast_shapes(x_shape, result_shape) + y_shape = data.draw(npst.broadcastable_shapes(x_result_shape, + min_side=min_side)) + y = dist._variable.draw(y_shape, parameter_values=dist._parameters, + proportions=proportions, rng=rng, region='typical') + xy_result_shape = np.broadcast_shapes(y_shape, x_result_shape) + p_domain = _RealInterval((0, 1), (True, True)) + p_var = _RealParameter('p', domain=p_domain) + p = p_var.draw(x_shape, proportions=proportions, rng=rng) + with np.errstate(divide='ignore', invalid='ignore'): + logp = np.log(p) + + return dist, x, y, p, logp, result_shape, x_result_shape, xy_result_shape + + +continuous_families = [ + StandardNormal, + Normal, + Logistic, + Uniform, + _LogUniform +] + +discrete_families = [ + Binomial, +] + +families = continuous_families + discrete_families + + +class TestDistributions: + @pytest.mark.fail_slow(60) # need to break up check_moment_funcs + @settings(max_examples=20) + @pytest.mark.parametrize('family', families) + @given(data=strategies.data(), seed=strategies.integers(min_value=0)) + def test_support_moments_sample(self, family, data, seed): + rng = np.random.default_rng(seed) + + # relative proportions of valid, endpoint, out of bounds, and NaN params + proportions = (0.7, 0.1, 0.1, 0.1) + tmp = draw_distribution_from_family(family, data, rng, proportions) + dist, x, y, p, logp, result_shape, x_result_shape, xy_result_shape = tmp + sample_shape = data.draw(npst.array_shapes(min_dims=0, min_side=0, + max_side=20)) + + with np.errstate(invalid='ignore', divide='ignore'): + check_support(dist) + check_moment_funcs(dist, result_shape) # this needs to get split up + check_lmoment_funcs(dist, result_shape) + check_sample_shape_NaNs(dist, 'sample', sample_shape, result_shape, rng) + qrng = qmc.Halton(d=1, seed=rng) + check_sample_shape_NaNs(dist, 'sample', sample_shape, result_shape, qrng) + + @pytest.mark.fail_slow(10) + @pytest.mark.parametrize('family', families) + @pytest.mark.parametrize('func, methods, arg', + [('entropy', {'log/exp', 'quadrature'}, None), + ('logentropy', {'log/exp', 'quadrature'}, None), + ('median', {'icdf'}, None), + ('mode', {'optimization'}, None), + ('mean', {'cache'}, None), + ('variance', {'cache'}, None), + ('skewness', {'cache'}, None), + ('kurtosis', {'cache'}, None), + ('pdf', {'log/exp'}, 'x'), + ('logpdf', {'log/exp'}, 'x'), + ('logcdf', {'log/exp', 'complement', 'quadrature'}, 'x'), + ('cdf', {'log/exp', 'complement', 'quadrature'}, 'x'), + ('logccdf', {'log/exp', 'complement', 'quadrature'}, 'x'), + ('ccdf', {'log/exp', 'complement', 'quadrature'}, 'x'), + ('ilogccdf', {'complement', 'inversion'}, 'logp'), + ('iccdf', {'complement', 'inversion'}, 'p'), + ]) + @settings(max_examples=20) + @given(data=strategies.data(), seed=strategies.integers(min_value=0)) + def test_funcs(self, family, data, seed, func, methods, arg): + if family == Uniform and func == 'mode': + pytest.skip("Mode is not unique; `method`s disagree.") + + rng = np.random.default_rng(seed) + + # relative proportions of valid, endpoint, out of bounds, and NaN params + proportions = (0.7, 0.1, 0.1, 0.1) + tmp = draw_distribution_from_family(family, data, rng, proportions) + dist, x, y, p, logp, result_shape, x_result_shape, xy_result_shape = tmp + + args = {'x': x, 'p': p, 'logp': p} + with np.errstate(invalid='ignore', divide='ignore', over='ignore'): + if arg is None: + check_dist_func(dist, func, None, result_shape, methods) + elif arg in args: + check_dist_func(dist, func, args[arg], x_result_shape, methods) + + if func == 'variance': + assert_allclose(dist.standard_deviation()**2, dist.variance()) + + # invalid and divide are to be expected; maybe look into over + with np.errstate(invalid='ignore', divide='ignore', over='ignore'): + if not isinstance(dist, ShiftedScaledDistribution): + if func == 'cdf': + methods = {'quadrature'} + check_cdf2(dist, False, x, y, xy_result_shape, methods) + check_cdf2(dist, True, x, y, xy_result_shape, methods) + elif func == 'ccdf': + methods = {'addition'} + check_ccdf2(dist, False, x, y, xy_result_shape, methods) + check_ccdf2(dist, True, x, y, xy_result_shape, methods) + + def test_plot(self): + try: + import matplotlib.pyplot as plt + except ImportError: + return + + X = Uniform(a=0., b=1.) + ax = X.plot() + assert ax == plt.gca() + + @pytest.mark.parametrize('method_name', ['cdf', 'ccdf']) + def test_complement_safe(self, method_name): + X = stats.Normal(mu=1, sigma=2) + X.tol = 1e-12 + p = np.asarray([1e-4, 1e-3]) + func = getattr(X, method_name) + ifunc = getattr(X, 'i'+method_name) + x = ifunc(p, method='formula') + p1 = func(x, method='complement_safe') + p2 = func(x, method='complement') + assert_equal(p1[1], p2[1]) + assert p1[0] != p2[0] + assert_allclose(p1[0], p[0], rtol=X.tol) + + @pytest.mark.parametrize('method_name', ['cdf', 'ccdf']) + def test_icomplement_safe(self, method_name): + X = stats.Normal(mu=1, sigma=2) + X.tol = 1e-12 + p = np.asarray([1e-4, 1e-3]) + func = getattr(X, method_name) + ifunc = getattr(X, 'i'+method_name) + x1 = ifunc(p, method='complement_safe') + x2 = ifunc(p, method='complement') + assert_equal(x1[1], x2[1]) + assert x1[0] != x2[0] + assert_allclose(func(x1[0]), p[0], rtol=X.tol) + + def test_subtraction_safe(self): + X = stats.Normal() + X.tol = 1e-12 + + # Regular subtraction is fine in either tail (and of course, across tails) + x = [-11, -10, 10, 11] + y = [-10, -11, 11, 10] + p0 = X.cdf(x, y, method='quadrature') + p1 = X.cdf(x, y, method='subtraction_safe') + p2 = X.cdf(x, y, method='subtraction') + assert_equal(p2, p1) + assert_allclose(p1, p0, rtol=X.tol) + + # Safe subtraction is needed in special cases + x = np.asarray([-1e-20, -1e-21, 1e-20, 1e-21, -1e-20]) + y = np.asarray([-1e-21, -1e-20, 1e-21, 1e-20, 1e-20]) + + + p0 = X.pdf(0)*(y-x) + p1 = X.cdf(x, y, method='subtraction_safe') + p2 = X.cdf(x, y, method='subtraction') + assert_equal(p2, 0) + assert_allclose(p1, p0, rtol=X.tol) + + def test_logentropy_safe(self): + # simulate an `entropy` calculation over/underflowing with extreme parameters + class _Normal(stats.Normal): + def _entropy_formula(self, **params): + out = np.asarray(super()._entropy_formula(**params)) + out[0] = 0 + out[-1] = np.inf + return out + + X = _Normal(sigma=[1, 2, 3]) + with np.errstate(divide='ignore'): + res1 = X.logentropy(method='logexp_safe') + res2 = X.logentropy(method='logexp') + ref = X.logentropy(method='quadrature') + i_fl = [0, -1] # first and last + assert np.isinf(res2[i_fl]).all() + assert res1[1] == res2[1] + # quadrature happens to be perfectly accurate on some platforms + # assert res1[1] != ref[1] + assert_equal(res1[i_fl], ref[i_fl]) + + def test_logcdf2_safe(self): + # test what happens when 2-arg `cdf` underflows + X = stats.Normal(sigma=[1, 2, 3]) + x = [-301, 1, 300] + y = [-300, 2, 301] + with np.errstate(divide='ignore'): + res1 = X.logcdf(x, y, method='logexp_safe') + res2 = X.logcdf(x, y, method='logexp') + ref = X.logcdf(x, y, method='quadrature') + i_fl = [0, -1] # first and last + assert np.isinf(res2[i_fl]).all() + assert res1[1] == res2[1] + # quadrature happens to be perfectly accurate on some platforms + # assert res1[1] != ref[1] + assert_equal(res1[i_fl], ref[i_fl]) + + @pytest.mark.parametrize('method_name', ['logcdf', 'logccdf']) + def test_logexp_safe(self, method_name): + # test what happens when `cdf`/`ccdf` underflows + X = stats.Normal(sigma=2) + x = [-301, 1] if method_name == 'logcdf' else [301, 1] + func = getattr(X, method_name) + with np.errstate(divide='ignore'): + res1 = func(x, method='logexp_safe') + res2 = func(x, method='logexp') + ref = func(x, method='quadrature') + assert res1[0] == ref[0] + assert res1[0] != res2[0] + assert res1[1] == res2[1] + assert res1[1] != ref[1] + +def check_sample_shape_NaNs(dist, fname, sample_shape, result_shape, rng): + full_shape = sample_shape + result_shape + if fname == 'sample': + sample_method = dist.sample + + methods = {'inverse_transform'} + if dist._overrides(f'_{fname}_formula') and not isinstance(rng, qmc.QMCEngine): + methods.add('formula') + + for method in methods: + res = sample_method(sample_shape, method=method, rng=rng) + valid_parameters = np.broadcast_to(get_valid_parameters(dist), + res.shape) + assert_equal(res.shape, full_shape) + np.testing.assert_equal(res.dtype, dist._dtype) + + if full_shape == (): + # NumPy random makes a distinction between a 0d array and a scalar. + # In stats, we consistently turn 0d arrays into scalars, so + # maintain that behavior here. (With Array API arrays, this will + # change.) + assert np.isscalar(res) + assert np.all(np.isfinite(res[valid_parameters])) + assert_equal(res[~valid_parameters], np.nan) + + sample1 = sample_method(sample_shape, method=method, rng=42) + sample2 = sample_method(sample_shape, method=method, rng=42) + if not isinstance(dist, DiscreteDistribution): + # The idea is that it's very unlikely that the random sample + # for a randomly chosen seed will match that for seed 42, + # but it is not so unlikely if `dist` is a discrete distribution. + assert not np.any(np.equal(res, sample1)) + assert_equal(sample1, sample2) + + +def check_support(dist): + a, b = dist.support() + check_nans_and_edges(dist, 'support', None, a) + check_nans_and_edges(dist, 'support', None, b) + assert a.shape == dist._shape + assert b.shape == dist._shape + assert a.dtype == dist._dtype + assert b.dtype == dist._dtype + + +def check_dist_func(dist, fname, arg, result_shape, methods): + # Check that all computation methods of all distribution functions agree + # with one another, effectively testing the correctness of the generic + # computation methods and confirming the consistency of specific + # distributions with their pdf/logpdf. + + args = tuple() if arg is None else (arg,) + methods = methods.copy() + + if "cache" in methods: + # If "cache" is specified before the value has been evaluated, it + # raises an error. After the value is evaluated, it will succeed. + with pytest.raises(NotImplementedError): + getattr(dist, fname)(*args, method="cache") + + ref = getattr(dist, fname)(*args) + check_nans_and_edges(dist, fname, arg, ref) + + # Remove this after fixing `draw` + tol_override = {'atol': 1e-15} + # Mean can be 0, which makes logmean -inf. + if fname in {'logmean', 'mean', 'logskewness', 'skewness'}: + tol_override = {'atol': 1e-15} + elif fname in {'mode'}: + # can only expect about half of machine precision for optimization + # because math + tol_override = {'atol': 1e-6} + elif fname in {'logcdf'}: # gh-22276 + tol_override = {'rtol': 2e-7} + + if dist._overrides(f'_{fname}_formula'): + methods.add('formula') + + np.testing.assert_equal(ref.shape, result_shape) + # Until we convert to array API, let's do the familiar thing: + # 0d things are scalars, not arrays + if result_shape == tuple(): + assert np.isscalar(ref) + + for method in methods: + res = getattr(dist, fname)(*args, method=method) + if 'log' in fname: + np.testing.assert_allclose(np.exp(res), np.exp(ref), + **tol_override) + else: + np.testing.assert_allclose(res, ref, **tol_override) + + # for now, make sure dtypes are consistent; later, we can check whether + # they are correct. + np.testing.assert_equal(res.dtype, ref.dtype) + np.testing.assert_equal(res.shape, result_shape) + if result_shape == tuple(): + assert np.isscalar(res) + +def check_cdf2(dist, log, x, y, result_shape, methods): + # Specialized test for 2-arg cdf since the interface is a bit different + # from the other methods. Here, we'll use 1-arg cdf as a reference, and + # since we have already checked 1-arg cdf in `check_nans_and_edges`, this + # checks the equivalent of both `check_dist_func` and + # `check_nans_and_edges`. + methods = methods.copy() + + if log: + if dist._overrides('_logcdf2_formula'): + methods.add('formula') + if dist._overrides('_logcdf_formula') or dist._overrides('_logccdf_formula'): + methods.add('subtraction') + if (dist._overrides('_cdf_formula') + or dist._overrides('_ccdf_formula')): + methods.add('log/exp') + else: + if dist._overrides('_cdf2_formula'): + methods.add('formula') + if dist._overrides('_cdf_formula') or dist._overrides('_ccdf_formula'): + methods.add('subtraction') + if (dist._overrides('_logcdf_formula') + or dist._overrides('_logccdf_formula')): + methods.add('log/exp') + + ref = dist.cdf(y) - dist.cdf(x) + np.testing.assert_equal(ref.shape, result_shape) + + if result_shape == tuple(): + assert np.isscalar(ref) + + for method in methods: + if isinstance(dist, DiscreteDistribution): + message = ("Two argument cdf functions are currently only supported for " + "continuous distributions.") + with pytest.raises(NotImplementedError, match=message): + res = (np.exp(dist.logcdf(x, y, method=method)) if log + else dist.cdf(x, y, method=method)) + continue + res = (np.exp(dist.logcdf(x, y, method=method)) if log + else dist.cdf(x, y, method=method)) + np.testing.assert_allclose(res, ref, atol=1e-14) + if log: + np.testing.assert_equal(res.dtype, (ref + 0j).dtype) + else: + np.testing.assert_equal(res.dtype, ref.dtype) + np.testing.assert_equal(res.shape, result_shape) + if result_shape == tuple(): + assert np.isscalar(res) + + +def check_ccdf2(dist, log, x, y, result_shape, methods): + # Specialized test for 2-arg ccdf since the interface is a bit different + # from the other methods. Could be combined with check_cdf2 above, but + # writing it separately is simpler. + methods = methods.copy() + + if dist._overrides(f'_{"log" if log else ""}ccdf2_formula'): + methods.add('formula') + + ref = dist.cdf(x) + dist.ccdf(y) + np.testing.assert_equal(ref.shape, result_shape) + + if result_shape == tuple(): + assert np.isscalar(ref) + + for method in methods: + message = ("Two argument cdf functions are currently only supported for " + "continuous distributions.") + if isinstance(dist, DiscreteDistribution): + with pytest.raises(NotImplementedError, match=message): + res = (np.exp(dist.logccdf(x, y, method=method)) if log + else dist.ccdf(x, y, method=method)) + continue + res = (np.exp(dist.logccdf(x, y, method=method)) if log + else dist.ccdf(x, y, method=method)) + np.testing.assert_allclose(res, ref, atol=1e-14) + np.testing.assert_equal(res.dtype, ref.dtype) + np.testing.assert_equal(res.shape, result_shape) + if result_shape == tuple(): + assert np.isscalar(res) + + +def check_nans_and_edges(dist, fname, arg, res): + + valid_parameters = get_valid_parameters(dist) + if fname in {'icdf', 'iccdf'}: + arg_domain = _RealInterval(endpoints=(0, 1), inclusive=(True, True)) + elif fname in {'ilogcdf', 'ilogccdf'}: + arg_domain = _RealInterval(endpoints=(-inf, 0), inclusive=(True, True)) + else: + arg_domain = dist._variable.domain + + classified_args = classify_arg(dist, arg, arg_domain) + valid_parameters, *classified_args = np.broadcast_arrays(valid_parameters, + *classified_args) + valid_arg, endpoint_arg, outside_arg, nan_arg = classified_args + all_valid = valid_arg & valid_parameters + + # Check NaN pattern and edge cases + assert_equal(res[~valid_parameters], np.nan) + assert_equal(res[nan_arg], np.nan) + + a, b = dist.support() + a = np.broadcast_to(a, res.shape) + b = np.broadcast_to(b, res.shape) + + outside_arg_minus = (outside_arg == -1) & valid_parameters + outside_arg_plus = (outside_arg == 1) & valid_parameters + endpoint_arg_minus = (endpoint_arg == -1) & valid_parameters + endpoint_arg_plus = (endpoint_arg == 1) & valid_parameters + + is_discrete = isinstance(dist, DiscreteDistribution) + # Writing this independently of how the are set in the distribution + # infrastructure. That is very compact; this is very verbose. + if fname in {'logpdf'}: + assert_equal(res[outside_arg_minus], -np.inf) + assert_equal(res[outside_arg_plus], -np.inf) + ref = -np.inf if not is_discrete else np.inf + assert_equal(res[endpoint_arg_minus & ~valid_arg], ref) + assert_equal(res[endpoint_arg_plus & ~valid_arg], ref) + elif fname in {'pdf'}: + assert_equal(res[outside_arg_minus], 0) + assert_equal(res[outside_arg_plus], 0) + ref = 0 if not is_discrete else np.inf + assert_equal(res[endpoint_arg_minus & ~valid_arg], ref) + assert_equal(res[endpoint_arg_plus & ~valid_arg], ref) + elif fname in {'logcdf'} and not is_discrete: + assert_equal(res[outside_arg_minus], -inf) + assert_equal(res[outside_arg_plus], 0) + assert_equal(res[endpoint_arg_minus], -inf) + assert_equal(res[endpoint_arg_plus], 0) + elif fname in {'cdf'} and not is_discrete: + assert_equal(res[outside_arg_minus], 0) + assert_equal(res[outside_arg_plus], 1) + assert_equal(res[endpoint_arg_minus], 0) + assert_equal(res[endpoint_arg_plus], 1) + elif fname in {'logccdf'} and not is_discrete: + assert_equal(res[outside_arg_minus], 0) + assert_equal(res[outside_arg_plus], -inf) + assert_equal(res[endpoint_arg_minus], 0) + assert_equal(res[endpoint_arg_plus], -inf) + elif fname in {'ccdf'} and not is_discrete: + assert_equal(res[outside_arg_minus], 1) + assert_equal(res[outside_arg_plus], 0) + assert_equal(res[endpoint_arg_minus], 1) + assert_equal(res[endpoint_arg_plus], 0) + elif fname in {'ilogcdf', 'icdf'} and not is_discrete: + assert_equal(res[outside_arg == -1], np.nan) + assert_equal(res[outside_arg == 1], np.nan) + assert_equal(res[endpoint_arg == -1], a[endpoint_arg == -1]) + assert_equal(res[endpoint_arg == 1], b[endpoint_arg == 1]) + elif fname in {'ilogccdf', 'iccdf'} and not is_discrete: + assert_equal(res[outside_arg == -1], np.nan) + assert_equal(res[outside_arg == 1], np.nan) + assert_equal(res[endpoint_arg == -1], b[endpoint_arg == -1]) + assert_equal(res[endpoint_arg == 1], a[endpoint_arg == 1]) + + exclude = {'logmean', 'mean', 'logskewness', 'skewness', 'support'} + if isinstance(dist, DiscreteDistribution): + exclude.update({'pdf', 'logpdf'}) + + if fname not in exclude: + assert np.isfinite(res[all_valid & (endpoint_arg == 0)]).all() + + +def check_moment_funcs(dist, result_shape): + # Check that all computation methods of all distribution functions agree + # with one another, effectively testing the correctness of the generic + # computation methods and confirming the consistency of specific + # distributions with their pdf/logpdf. + + atol = 1e-9 # make this tighter (e.g. 1e-13) after fixing `draw` + + def check(order, kind, method=None, ref=None, success=True): + if success: + res = dist.moment(order, kind, method=method) + assert_allclose(res, ref, atol=atol*10**order) + assert res.shape == ref.shape + else: + with pytest.raises(NotImplementedError): + dist.moment(order, kind, method=method) + + def has_formula(order, kind): + formula_name = f'_moment_{kind}_formula' + overrides = dist._overrides(formula_name) + if not overrides: + return False + formula = getattr(dist, formula_name) + orders = getattr(formula, 'orders', set(range(6))) + return order in orders + + dist.reset_cache() + + ### Check Raw Moments ### + for i in range(6): + check(i, 'raw', 'cache', success=False) # not cached yet + ref = dist.moment(i, 'raw', method='quadrature') + check_nans_and_edges(dist, 'moment', None, ref) + assert ref.shape == result_shape + check(i, 'raw','cache', ref, success=True) # cached now + check(i, 'raw', 'formula', ref, success=has_formula(i, 'raw')) + check(i, 'raw', 'general', ref, success=(i == 0)) + if dist.__class__ == stats.Normal: + check(i, 'raw', 'quadrature_icdf', ref, success=True) + + + # Clearing caches to better check their behavior + dist.reset_cache() + + # If we have central or standard moment formulas, or if there are + # values in their cache, we can use method='transform' + dist.moment(0, 'central') # build up the cache + dist.moment(1, 'central') + for i in range(2, 6): + ref = dist.moment(i, 'raw', method='quadrature') + check(i, 'raw', 'transform', ref, + success=has_formula(i, 'central') or has_formula(i, 'standardized')) + dist.moment(i, 'central') # build up the cache + check(i, 'raw', 'transform', ref) + + dist.reset_cache() + + ### Check Central Moments ### + + for i in range(6): + check(i, 'central', 'cache', success=False) + ref = dist.moment(i, 'central', method='quadrature') + assert ref.shape == result_shape + check(i, 'central', 'cache', ref, success=True) + check(i, 'central', 'formula', ref, success=has_formula(i, 'central')) + check(i, 'central', 'general', ref, success=i <= 1) + if dist.__class__ == stats.Normal: + check(i, 'central', 'quadrature_icdf', ref, success=True) + if not (dist.__class__ == stats.Uniform and i == 5): + # Quadrature is not super accurate for 5th central moment when the + # support is really big. Skip this one failing test. We need to come + # up with a better system of skipping individual failures w/ hypothesis. + check(i, 'central', 'transform', ref, + success=has_formula(i, 'raw') or (i <= 1)) + if not has_formula(i, 'raw'): + dist.moment(i, 'raw') + check(i, 'central', 'transform', ref) + + variance = dist.variance() + dist.reset_cache() + + # If we have standard moment formulas, or if there are + # values in their cache, we can use method='normalize' + dist.moment(0, 'standardized') # build up the cache + dist.moment(1, 'standardized') + dist.moment(2, 'standardized') + for i in range(3, 6): + ref = dist.moment(i, 'central', method='quadrature') + check(i, 'central', 'normalize', ref, + success=has_formula(i, 'standardized') and not np.any(variance == 0)) + dist.moment(i, 'standardized') # build up the cache + check(i, 'central', 'normalize', ref, success=not np.any(variance == 0)) + + ### Check Standardized Moments ### + + var = dist.moment(2, 'central', method='quadrature') + dist.reset_cache() + + for i in range(6): + check(i, 'standardized', 'cache', success=False) + ref = dist.moment(i, 'central', method='quadrature') / var ** (i / 2) + assert ref.shape == result_shape + check(i, 'standardized', 'formula', ref, + success=has_formula(i, 'standardized')) + check(i, 'standardized', 'general', ref, success=i <= 2) + check(i, 'standardized', 'normalize', ref) + + dist.reset_cache() + + +def check_lmoment_funcs(dist, result_shape): + # Perform consistency check for L-moments similar to check_moment_funcs above + + if not isinstance(dist, ContinuousDistribution): + message = "L-moments are currently available only for continuous..." + with pytest.raises(NotImplementedError, match=message): + dist.lmoment(1) + return + + atol = 2e-9 + + def check(order, standardize=False, method=None, ref=None, success=True): + if success: + res = dist.lmoment(order, standardize=standardize, method=method) + assert_allclose(res, ref, atol=atol) + assert res.shape == ref.shape + else: + with pytest.raises(NotImplementedError): + dist.lmoment(order, standardize=standardize, method=method) + + ### Check L-Moments ### + + standardize = False + for i in range(1, 6): + check(i, standardize, 'cache', success=standardize) # not cached yet + ref = dist.lmoment(i, standardize=standardize, method='order_statistics') + check_nans_and_edges(dist, 'lmoment', None, ref) + assert ref.shape == result_shape + check(i, standardize, 'cache', ref, success=True) # cached now + check(i, standardize, 'formula', ref, + success=dist._overrides('_lmoment_formula') + and (i < 5 or dist.__class__.__name__ == "Uniform")) + check(i, standardize, 'general', ref, success=(i == 1)) + if dist._overrides('_icdf_formula'): + check(i, standardize, 'quadrature_icdf', ref, success=True) + + standardize=True + for i in range(3, 6): + ref = dist.lmoment(i, standardize=standardize, method='order_statistics') + assert ref.shape == result_shape + check(i, standardize, 'formula', ref, + success=dist._overrides('_lmoment_formula') + and (i < 5 or dist.__class__.__name__ == "Uniform")) + check(i, standardize, 'general', ref, success=False) + if dist._overrides('_icdf_formula'): + check(i, standardize, 'quadrature_icdf', ref, success=True) + + +@pytest.mark.parametrize('family', (Normal,)) +@pytest.mark.parametrize('x_shape', [tuple(), (2, 3)]) +@pytest.mark.parametrize('dist_shape', [tuple(), (4, 1)]) +@pytest.mark.parametrize('fname', ['sample']) +@pytest.mark.parametrize('rng_type', [np.random.Generator, qmc.Halton, qmc.Sobol]) +def test_sample_against_cdf(family, dist_shape, x_shape, fname, rng_type): + rng = np.random.default_rng(842582438235635) + num_parameters = family._num_parameters() + + if dist_shape and num_parameters == 0: + pytest.skip("Distribution can't have a shape without parameters.") + + dist = family._draw(dist_shape, rng) + + n = 1024 + sample_size = (n,) + x_shape + sample_array_shape = sample_size + dist_shape + + if fname == 'sample': + sample_method = dist.sample + + if rng_type != np.random.Generator: + rng = rng_type(d=1, seed=rng) + x = sample_method(sample_size, rng=rng) + assert x.shape == sample_array_shape + + # probably should give `axis` argument to ks_1samp, review that separately + statistic = _kolmogorov_smirnov(dist, x, axis=0) + pvalue = kolmogn(x.shape[0], statistic, cdf=False) + p_threshold = 0.01 + num_pvalues = pvalue.size + num_small_pvalues = np.sum(pvalue < p_threshold) + assert num_small_pvalues < p_threshold * num_pvalues + + +def get_valid_parameters(dist): + # Given a distribution, return a logical array that is true where all + # distribution parameters are within their respective domains. The code + # here is probably quite similar to that used to form the `_invalid` + # attribute of the distribution, but this was written about a week later + # without referring to that code, so it is a somewhat independent check. + + # Get all parameter values and `_Parameter` objects + parameter_values = dist._parameters + parameters = {} + for parameterization in dist._parameterizations: + parameters.update(parameterization.parameters) + + all_valid = np.ones(dist._shape, dtype=bool) + for name, value in parameter_values.items(): + if name not in parameters: # cached value not part of parameterization + continue + parameter = parameters[name] + + # Check that the numerical endpoints and inclusivity attribute + # agree with the `contains` method about which parameter values are + # within the domain. + a, b = parameter.domain.get_numerical_endpoints( + parameter_values=parameter_values) + a_included, b_included = parameter.domain.inclusive + valid = (a <= value) if a_included else a < value + valid &= (value <= b) if b_included else value < b + assert_equal(valid, parameter.domain.contains( + value, parameter_values=parameter_values)) + + # Form `all_valid` mask that is True where *all* parameters are valid + all_valid &= valid + + # Check that the `all_valid` mask formed here is the complement of the + # `dist._invalid` mask stored by the infrastructure + assert_equal(~all_valid, dist._invalid) + + return all_valid + +def classify_arg(dist, arg, arg_domain): + if arg is None: + valid_args = np.ones(dist._shape, dtype=bool) + endpoint_args = np.zeros(dist._shape, dtype=bool) + outside_args = np.zeros(dist._shape, dtype=bool) + nan_args = np.zeros(dist._shape, dtype=bool) + return valid_args, endpoint_args, outside_args, nan_args + + a, b = arg_domain.get_numerical_endpoints( + parameter_values=dist._parameters) + + a, b, arg = np.broadcast_arrays(a, b, arg) + a_included, b_included = arg_domain.inclusive + + inside = (a <= arg) if a_included else a < arg + inside &= (arg <= b) if b_included else arg < b + # TODO: add `supported` method and check here + on = np.zeros(a.shape, dtype=int) + on[a == arg] = -1 + on[b == arg] = 1 + outside = np.zeros(a.shape, dtype=int) + outside[(arg < a) if a_included else arg <= a] = -1 + outside[(b < arg) if b_included else b <= arg] = 1 + nan = np.isnan(arg) + + return inside, on, outside, nan + + +def test_input_validation(): + class Test(ContinuousDistribution): + _variable = _RealParameter('x', domain=_RealInterval()) + + message = ("The `Test` distribution family does not accept parameters, " + "but parameters `{'a'}` were provided.") + with pytest.raises(ValueError, match=message): + Test(a=1, ) + + message = "Attribute `tol` of `Test` must be a positive float, if specified." + with pytest.raises(ValueError, match=message): + Test(tol=np.asarray([])) + with pytest.raises(ValueError, match=message): + Test(tol=[1, 2, 3]) + with pytest.raises(ValueError, match=message): + Test(tol=np.nan) + with pytest.raises(ValueError, match=message): + Test(tol=-1) + + message = ("Argument `order` of `Test.moment` must be a " + "finite integer greater than or equal to 0.") + with pytest.raises(ValueError, match=message): + Test().moment(-1) + with pytest.raises(ValueError, match=message): + Test().moment(np.inf) + + message = ("Argument `order` of `Test.lmoment` must be a " + "finite integer greater than or equal to 1.") + with pytest.raises(ValueError, match=message): + Test().lmoment(0) + + message = "Argument `kind` of `Test.moment` must be one of..." + with pytest.raises(ValueError, match=message): + Test().moment(2, kind='coconut') + + class Test2(ContinuousDistribution): + _p1 = _RealParameter('c', domain=_RealInterval()) + _p2 = _RealParameter('d', domain=_RealInterval()) + _parameterizations = [_Parameterization(_p1, _p2)] + _variable = _RealParameter('x', domain=_RealInterval()) + + message = ("The provided parameters `{a}` do not match a supported " + "parameterization of the `Test2` distribution family.") + with pytest.raises(ValueError, match=message): + Test2(a=1) + + message = ("The `Test2` distribution family requires parameters, but none " + "were provided.") + with pytest.raises(ValueError, match=message): + Test2() + + message = ("The parameters `{c, d}` provided to the `Test2` " + "distribution family cannot be broadcast to the same shape.") + with pytest.raises(ValueError, match=message): + Test2(c=[1, 2], d=[1, 2, 3]) + + message = ("The argument provided to `Test2.pdf` cannot be be broadcast to " + "the same shape as the distribution parameters.") + with pytest.raises(ValueError, match=message): + dist = Test2(c=[1, 2, 3], d=[1, 2, 3]) + dist.pdf([1, 2]) + + message = "Parameter `c` must be of real dtype." + with pytest.raises(TypeError, match=message): + Test2(c=[1, object()], d=[1, 2]) + + message = "Parameter `convention` of `Test2.kurtosis` must be one of..." + with pytest.raises(ValueError, match=message): + dist = Test2(c=[1, 2, 3], d=[1, 2, 3]) + dist.kurtosis(convention='coconut') + + +def test_rng_deepcopy_pickle(): + # test behavior of `rng` attribute and copy behavior + kwargs = dict(a=[-1, 2], b=10) + dist1 = Uniform(**kwargs) + dist2 = deepcopy(dist1) + dist3 = pickle.loads(pickle.dumps(dist1)) + + res1, res2, res3 = dist1.sample(), dist2.sample(), dist3.sample() + assert np.all(res2 != res1) + assert np.all(res3 != res1) + + res1, res2, res3 = dist1.sample(rng=42), dist2.sample(rng=42), dist3.sample(rng=42) + assert np.all(res2 == res1) + assert np.all(res3 == res1) + + +class TestAttributes: + def test_cache_policy(self): + dist = StandardNormal(cache_policy="no_cache") + # make error message more appropriate + message = "`StandardNormal` does not provide an accurate implementation of the " + with pytest.raises(NotImplementedError, match=message): + dist.mean(method='cache') + mean = dist.mean() + with pytest.raises(NotImplementedError, match=message): + dist.mean(method='cache') + + # add to enum + dist.cache_policy = None + with pytest.raises(NotImplementedError, match=message): + dist.mean(method='cache') + mean = dist.mean() # method is 'formula' by default + cached_mean = dist.mean(method='cache') + assert_equal(cached_mean, mean) + + # cache is not cleared when used in a TransformedDistribution + dist2 = stats.exp(dist) + assert_equal(dist.mean(method='cache'), cached_mean) + dist2.reset_cache() + assert_equal(dist.mean(method='cache'), cached_mean) + + # cache is overridden by latest evaluation + quadrature_mean = dist.mean(method='quadrature') + cached_mean = dist.mean(method='cache') + assert_equal(cached_mean, quadrature_mean) + assert not np.all(mean == quadrature_mean) + + # We can turn the cache off, and it won't change, but the old cache is + # still available + dist.cache_policy = "no_cache" + mean = dist.mean(method='formula') + cached_mean = dist.mean(method='cache') + assert_equal(cached_mean, quadrature_mean) + assert not np.all(mean == quadrature_mean) + + dist.reset_cache() + with pytest.raises(NotImplementedError, match=message): + dist.mean(method='cache') + + message = "Attribute `cache_policy` of `StandardNormal`..." + with pytest.raises(ValueError, match=message): + dist.cache_policy = "invalid" + + def test_tol(self): + x = 3. + X = stats.Normal() + + message = "Attribute `tol` of `StandardNormal` must..." + with pytest.raises(ValueError, match=message): + X.tol = -1. + with pytest.raises(ValueError, match=message): + X.tol = (0.1,) + with pytest.raises(ValueError, match=message): + X.tol = np.nan + + X1 = stats.Normal(tol=1e-1) + X2 = stats.Normal(tol=1e-12) + ref = X.cdf(x) + res1 = X1.cdf(x, method='quadrature') + res2 = X2.cdf(x, method='quadrature') + assert_allclose(res1, ref, rtol=X1.tol) + assert_allclose(res2, ref, rtol=X2.tol) + assert abs(res1 - ref) > abs(res2 - ref) + + p = 0.99 + X1.tol, X2.tol = X2.tol, X1.tol + ref = X.icdf(p) + res1 = X1.icdf(p, method='inversion') + res2 = X2.icdf(p, method='inversion') + assert_allclose(res1, ref, rtol=X1.tol) + assert_allclose(res2, ref, rtol=X2.tol) + assert abs(res2 - ref) > abs(res1 - ref) + + def test_iv_policy(self): + X = Uniform(a=0, b=1) + assert X.pdf(2) == 0 + + X.validation_policy = 'skip_all' + assert X.pdf(np.asarray(2.)) == 1 + + # Tests _set_invalid_nan + a, b = np.asarray(1.), np.asarray(0.) # invalid parameters + X = Uniform(a=a, b=b, validation_policy='skip_all') + assert X.pdf(np.asarray(2.)) == -1 + + # Tests _set_invalid_nan_property + class MyUniform(Uniform): + def _entropy_formula(self, *args, **kwargs): + return 'incorrect' + + def _moment_raw_formula(self, order, **params): + return 'incorrect' + + X = MyUniform(a=a, b=b, validation_policy='skip_all') + assert X.entropy() == 'incorrect' + + # Tests _validate_order_kind + assert X.moment(kind='raw', order=-1) == 'incorrect' + + # Test input validation + message = "Attribute `validation_policy` of `MyUniform`..." + with pytest.raises(ValueError, match=message): + X.validation_policy = "invalid" + + def test_shapes(self): + X = stats.Normal(mu=1, sigma=2) + Y = stats.Normal(mu=[2], sigma=3) + + # Check that attributes are available as expected + assert X.mu == 1 + assert X.sigma == 2 + assert Y.mu[0] == 2 + assert Y.sigma[0] == 3 + + # Trying to set an attribute raises + # message depends on Python version + with pytest.raises(AttributeError): + X.mu = 2 + + # Trying to mutate an attribute really mutates a copy + Y.mu[0] = 10 + assert Y.mu[0] == 2 + + +class TestMakeDistribution: + @pytest.mark.parametrize('i, distdata', list(enumerate(distcont + distdiscrete))) + def test_rv_generic(self, i, distdata): + distname = distdata[0] + + slow = {'argus', 'exponpow', 'exponweib', 'genexpon', 'gompertz', + 'johnsonsb', 'kappa4', 'ksone', 'kstwo', 'kstwobign', 'norminvgauss', + 'powerlognorm', 'powernorm', 'recipinvgauss', 'studentized_range', + 'vonmises_line'} # continuous + if not int(os.environ.get('SCIPY_XSLOW', '0')) and distname in slow: + pytest.skip('Skipping as XSLOW') + + if distname in { # skip these distributions + 'levy_stable', # private methods seem to require >= 1d args + 'vonmises', # circular distribution; shouldn't work + 'poisson_binom', # vector shape parameter + 'hypergeom', # distribution functions need interpolation + 'nchypergeom_fisher', # distribution functions need interpolation + 'nchypergeom_wallenius', # distribution functions need interpolation + }: + return + + # skip single test, mostly due to slight disagreement + custom_tolerances = {'ksone': 1e-5, 'kstwo': 1e-5} # discontinuous PDF + skip_entropy = {'kstwobign', 'pearson3'} # tolerance issue + skip_skewness = {'exponpow', 'ksone', 'nchypergeom_wallenius'} # tolerance + skip_kurtosis = {'chi', 'exponpow', 'invgamma', # tolerance + 'johnsonsb', 'ksone', 'kstwo', # tolerance + 'nchypergeom_wallenius'} # tolerance + skip_logccdf = {'arcsine', 'skewcauchy', 'trapezoid', 'triang', # tolerance + 'dlaplace'} # slight tolerance issue, but only for array shape + skip_raw = {2: {'alpha', 'foldcauchy', 'halfcauchy', 'levy', 'levy_l'}, + 3: {'pareto'}, # stats.pareto is just wrong + 4: {'invgamma'}} # tolerance issue + skip_standardized = {'exponpow', 'ksone'} # tolerances + + dist = getattr(stats, distname) + params = dict(zip(dist.shapes.split(', '), distdata[1])) if dist.shapes else {} + rng = np.random.default_rng(7548723590230982) + CustomDistribution = stats.make_distribution(dist) + params = {key: np.asarray([val, val]) for key, val in params.items()} + X = CustomDistribution(**params) + Y = dist(**params) + x = X.sample(shape=10, rng=rng) + p = X.cdf(x) + rtol = custom_tolerances.get(distname, 1e-7) + atol = 1e-12 + + with np.errstate(divide='ignore', invalid='ignore'): + m, v, s, k = Y.stats('mvsk') + assert_allclose(X.support(), Y.support()) + if distname not in skip_entropy: + assert_allclose(X.entropy(), Y.entropy(), rtol=rtol) + if isinstance(Y, stats.rv_discrete): + # some continuous distributions have trouble with `logentropy` because + # it uses complex numbers + assert_allclose(np.exp(X.logentropy()), Y.entropy(), rtol=rtol) + assert_allclose(X.median(), Y.median(), rtol=rtol) + assert_allclose(X.mean(), m, rtol=rtol, atol=atol) + assert_allclose(X.variance(), v, rtol=rtol, atol=atol) + if distname not in skip_skewness: + assert_allclose(X.skewness(), s, rtol=rtol, atol=atol) + if distname not in skip_kurtosis: + assert_allclose(X.kurtosis(convention='excess'), k, + rtol=rtol, atol=atol) + if isinstance(dist, stats.rv_continuous): + assert_allclose(X.logpdf(x), Y.logpdf(x), rtol=rtol) + assert_allclose(X.pdf(x), Y.pdf(x), rtol=rtol) + else: + assert_allclose(X.logpmf(x), Y.logpmf(x), rtol=rtol) + assert_allclose(X.pmf(x), Y.pmf(x), rtol=rtol) + assert_allclose(X.logcdf(x), Y.logcdf(x), rtol=rtol) + assert_allclose(X.cdf(x), Y.cdf(x), rtol=rtol) + if distname not in skip_logccdf: + assert_allclose(X.logccdf(x), Y.logsf(x), rtol=rtol) + assert_allclose(X.ccdf(x), Y.sf(x), rtol=rtol) + + # old infrastructure convention for ppf(p=0) and isf(p=1) is different than + # new infrastructure. Adjust reference values accordingly. + a, _ = Y.support() + a, p = np.broadcast_arrays(a, p) + ref_ppf = Y.ppf(p) + ref_ppf[p == 0] = a[p == 0] + ref_isf = Y.isf(p) + ref_isf[p == 1] = a[p == 1] + + assert_allclose(X.icdf(p), ref_ppf, rtol=rtol) + assert_allclose(X.iccdf(p), ref_isf, rtol=rtol) + + for order in range(5): + if distname not in skip_raw.get(order, {}): + assert_allclose(X.moment(order, kind='raw'), + Y.moment(order), rtol=rtol, atol=atol) + for order in range(3, 4): + if distname not in skip_standardized: + assert_allclose(X.moment(order, kind='standardized'), + Y.stats('mvsk'[order-1]), rtol=rtol, atol=atol) + if isinstance(dist, stats.rv_continuous): + # For discrete distributions, these won't agree at the far left end + # of the support, and the new infrastructure is slow there (for now). + seed = 845298245687345 + assert_allclose(X.sample(shape=10, rng=seed), + Y.rvs(size=p.shape, + random_state=np.random.default_rng(seed)), + rtol=rtol) + + def test_custom(self): + rng = np.random.default_rng(7548723590230982) + + class MyLogUniform: + @property + def __make_distribution_version__(self): + return "1.16.0" + + @property + def parameters(self): + return {'a': {'endpoints': (0, np.inf), 'inclusive': (False, False)}, + 'b': {'endpoints': ('a', np.inf), 'inclusive': (False, False)}} + + @property + def support(self): + return {'endpoints': ('a', 'b')} + + def pdf(self, x, a, b): + return 1 / (x * (np.log(b) - np.log(a))) + + def sample(self, shape, *, a, b, rng=None): + p = rng.uniform(size=shape) + return np.exp(np.log(a) + p * (np.log(b) - np.log(a))) + + def moment(self, order, kind='raw', *, a, b): + if order == 1 and kind == 'raw': + # quadrature is perfectly accurate here; add 1e-10 error so we + # can tell the difference between the two + return (b - a) / np.log(b/a) + 1e-10 + + def lmoment(self, order, *, a, b): + s = np.log(b/a) + l1 = (b - a) / s + l2 = ((s - 2) * b + (s + 2) * a) / s**2 + l3 = ((s**2 - 6*s + 12) * b - (s**2 + 6*s + 12) * a) / s ** 3 + l4 = ((s**3 - 12*s**2 + 60*s - 120) * b + + (s**3 + 12*s**2 + 60*s + 120) * a) / s ** 4 + ls = {1: l1, 2: l2, 3: l3, 4: l4} + return ls.get(int(order), None) + + LogUniform = stats.make_distribution(MyLogUniform()) + + X = LogUniform(a=1., b=np.e) + Y = stats.exp(Uniform(a=0., b=1.)) + + assert str(X) == f"MyLogUniform(a=1.0, b={np.e})" + assert repr(X) == f"MyLogUniform(a=np.float64(1.0), b=np.float64({np.e}))" + + x = X.sample(shape=10, rng=rng) + p = X.cdf(x) + + assert_allclose(X.support(), Y.support()) + assert_allclose(X.entropy(), Y.entropy()) + assert_allclose(X.median(), Y.median()) + assert_allclose(X.logpdf(x), Y.logpdf(x)) + assert_allclose(X.pdf(x), Y.pdf(x)) + assert_allclose(X.logcdf(x), Y.logcdf(x)) + assert_allclose(X.cdf(x), Y.cdf(x)) + assert_allclose(X.logccdf(x), Y.logccdf(x)) + assert_allclose(X.ccdf(x), Y.ccdf(x)) + assert_allclose(X.icdf(p), Y.icdf(p)) + assert_allclose(X.iccdf(p), Y.iccdf(p)) + for kind in ['raw', 'central', 'standardized']: + for order in range(5): + assert_allclose(X.moment(order, kind=kind), + Y.moment(order, kind=kind)) + for standardize in [False, True]: + for order in range(1, 5): + if standardize and order < 3: + continue + assert_allclose(X.lmoment(order, standardize=standardize), + Y.lmoment(order, standardize=standardize)) + + # Confirm that the `sample` and `moment` methods are overriden as expected + sample_formula = X.sample(shape=10, rng=0, method='formula') + sample_inverse = X.sample(shape=10, rng=0, method='inverse_transform') + assert_allclose(sample_formula, sample_inverse) + assert not np.all(sample_formula == sample_inverse) + + assert_allclose(X.mean(method='formula'), X.mean(method='quadrature')) + assert not X.mean(method='formula') == X.mean(method='quadrature') + + assert_allclose(X.lmoment(method='formula'), + X.lmoment(method='quadrature_icdf')) + assert not X.lmoment(method='formula') == X.lmoment(method='quadrature_icdf') + + # pdf and cdf formulas below can warn on boundary of support in some cases. + # See https://github.com/scipy/scipy/pull/22560#discussion_r1962763840. + @pytest.mark.slow + @pytest.mark.filterwarnings("ignore::RuntimeWarning") + @pytest.mark.parametrize("c", [-1, 0, 1, np.asarray([-2.1, -1., 0., 1., 2.1])]) + def test_custom_variable_support(self, c): + rng = np.random.default_rng(7548723590230982) + + class MyGenExtreme: + @property + def __make_distribution_version__(self): + return "1.16.0" + + @property + def parameters(self): + return { + 'c': {'endpoints': (-np.inf, np.inf), 'inclusive': (False, False)}, + 'mu': {'endpoints': (-np.inf, np.inf), 'inclusive': (False, False)}, + 'sigma': {'endpoints': (0, np.inf), 'inclusive': (False, False)} + } + + @property + def support(self): + def left(*, c, mu, sigma): + c, mu, sigma = np.broadcast_arrays(c, mu, sigma) + result = np.empty_like(c) + result[c >= 0] = -np.inf + result[c < 0] = mu[c < 0] + sigma[c < 0] / c[c < 0] + return result[()] + + def right(*, c, mu, sigma): + c, mu, sigma = np.broadcast_arrays(c, mu, sigma) + result = np.empty_like(c) + result[c <= 0] = np.inf + result[c > 0] = mu[c > 0] + sigma[c > 0] / c[c > 0] + return result[()] + + return {"endpoints": (left, right), "inclusive": (False, False)} + + def pdf(self, x, *, c, mu, sigma): + x, c, mu, sigma = np.broadcast_arrays(x, c, mu, sigma) + t = np.empty_like(x) + mask = (c == 0) + t[mask] = np.exp(-(x[mask] - mu[mask])/sigma[mask]) + t[~mask] = ( + 1 - c[~mask]*(x[~mask] - mu[~mask])/sigma[~mask] + )**(1/c[~mask]) + result = 1/sigma * t**(1 - c)*np.exp(-t) + return result[()] + + def cdf(self, x, *, c, mu, sigma): + x, c, mu, sigma = np.broadcast_arrays(x, c, mu, sigma) + t = np.empty_like(x) + mask = (c == 0) + t[mask] = np.exp(-(x[mask] - mu[mask])/sigma[mask]) + t[~mask] = ( + 1 - c[~mask]*(x[~mask] - mu[~mask])/sigma[~mask] + )**(1/c[~mask]) + return np.exp(-t)[()] + + GenExtreme1 = stats.make_distribution(MyGenExtreme()) + GenExtreme2 = stats.make_distribution(stats.genextreme) + + X1 = GenExtreme1(c=c, mu=0, sigma=1) + X2 = GenExtreme2(c=c) + + x = X1.sample(shape=10, rng=rng) + p = X1.cdf(x) + + assert_allclose(X1.support(), X2.support()) + assert_allclose(X1.entropy(), X2.entropy(), rtol=5e-6) + assert_allclose(X1.median(), X2.median()) + assert_allclose(X1.logpdf(x), X2.logpdf(x)) + assert_allclose(X1.pdf(x), X2.pdf(x)) + assert_allclose(X1.logcdf(x), X2.logcdf(x)) + assert_allclose(X1.cdf(x), X2.cdf(x)) + assert_allclose(X1.logccdf(x), X2.logccdf(x)) + assert_allclose(X1.ccdf(x), X2.ccdf(x)) + assert_allclose(X1.icdf(p), X2.icdf(p)) + assert_allclose(X1.iccdf(p), X2.iccdf(p)) + + @pytest.mark.slow + @pytest.mark.parametrize("a", [0.5, np.asarray([0.5, 1.0, 2.0, 4.0, 8.0])]) + @pytest.mark.parametrize("b", [0.5, np.asarray([0.5, 1.0, 2.0, 4.0, 8.0])]) + def test_custom_multiple_parameterizations(self, a, b): + rng = np.random.default_rng(7548723590230982) + class MyBeta: + @property + def __make_distribution_version__(self): + return "1.16.0" + + @property + def parameters(self): + return ( + {"a": (0, np.inf), "b": (0, np.inf)}, + {"mu": (0, 1), "nu": (0, np.inf)}, + ) + + def process_parameters(self, a=None, b=None, mu=None, nu=None): + if a is not None and b is not None and mu is None and nu is None: + nu = a + b + mu = a / nu + else: + a = mu * nu + b = nu - a + return {"a": a, "b": b, "mu": mu, "nu": nu} + + @property + def support(self): + return {'endpoints': (0, 1)} + + def pdf(self, x, a, b, mu, nu): + return special._ufuncs._beta_pdf(x, a, b) + + def cdf(self, x, a, b, mu, nu): + return special.betainc(a, b, x) + + Beta = stats.make_distribution(stats.beta) + MyBeta = stats.make_distribution(MyBeta()) + + mu = a / (a + b) + nu = a + b + + X = MyBeta(a=a, b=b) + Y = MyBeta(mu=mu, nu=nu) + Z = Beta(a=a, b=b) + + x = Z.sample(shape=10, rng=rng) + p = Z.cdf(x) + + assert_allclose(X.support(), Z.support()) + assert_allclose(X.median(), Z.median()) + assert_allclose(X.pdf(x), Z.pdf(x)) + assert_allclose(X.cdf(x), Z.cdf(x)) + assert_allclose(X.ccdf(x), Z.ccdf(x)) + assert_allclose(X.icdf(p), Z.icdf(p)) + assert_allclose(X.iccdf(p), Z.iccdf(p)) + + assert_allclose(Y.support(), Z.support()) + assert_allclose(Y.median(), Z.median()) + assert_allclose(Y.pdf(x), Z.pdf(x)) + assert_allclose(Y.cdf(x), Z.cdf(x)) + assert_allclose(Y.ccdf(x), Z.ccdf(x)) + assert_allclose(Y.icdf(p), Z.icdf(p)) + assert_allclose(Y.iccdf(p), Z.iccdf(p)) + + def test_input_validation(self): + message = '`levy_stable` is not supported.' + with pytest.raises(NotImplementedError, match=message): + stats.make_distribution(stats.levy_stable) + + message = '`vonmises` is not supported.' + with pytest.raises(NotImplementedError, match=message): + stats.make_distribution(stats.vonmises) + + message = "The argument must be an instance of..." + with pytest.raises(ValueError, match=message): + stats.make_distribution(object()) + + def test_repr_str_docs(self): + from scipy.stats._distribution_infrastructure import _distribution_names + for dist in _distribution_names.keys(): + assert hasattr(stats, dist) + + dist = stats.make_distribution(stats.gamma) + assert str(dist(a=2)) == "Gamma(a=2.0)" + assert repr(dist(a=2)) == "Gamma(a=np.float64(2.0))" + assert 'Gamma' in dist.__doc__ + + dist = stats.make_distribution(stats.halfgennorm) + assert str(dist(beta=2)) == "HalfGeneralizedNormal(beta=2.0)" + assert repr(dist(beta=2)) == "HalfGeneralizedNormal(beta=np.float64(2.0))" + assert 'HalfGeneralizedNormal' in dist.__doc__ + + @pytest.mark.slow # just in case + @settings(max_examples=20) # no need for more + @given(data=strategies.data()) + def test_draw_distribution(self, data): + # `draw_distribution_from_family` is a private function right now, but we will + # want that functionality to be public someday. It was broken for custom + # distributions because the `typical` parameter of the support was ignored. + # Check that this is resolved. + rng = np.random.default_rng(8465652168548465121) + u_typical = tuple(np.sort(rng.standard_normal(2))) + s_typical = tuple(np.sort(rng.random(2)*2)) + x_typical = tuple(np.sort(rng.standard_normal(2))) + + class MyNormal: + __make_distribution_version__ = "1.16.0" + parameters = {'u': {'endpoints': (-np.inf, np.inf), 'typical': u_typical}, + 's': {'endpoints': (0, np.inf), 'typical': s_typical}} + support = {'endpoints': (-np.inf, np.inf), 'typical': x_typical} + + def pdf(self, x, u, s): + return 1 / np.sqrt(2*np.pi) / s * np.exp(-((x-u)/s)**2/2) + + family = stats.make_distribution(MyNormal()) + proportions = (1.0, 0., 0., 0.) + tmp = draw_distribution_from_family(family, data, rng, proportions, min_side=1) + dist, x, y, p, logp, result_shape, x_result_shape, xy_result_shape = tmp + assert u_typical[0] < np.min(dist.u) and np.max(dist.u) < u_typical[1] + assert s_typical[0] < np.min(dist.s) and np.max(dist.s) < s_typical[1] + assert x_typical[0] < np.min(x) and np.max(x) < x_typical[1] + + @pytest.mark.parametrize('p', [None, ()]) + def test_no_parameters(self, p): + # To create a distribution without parameters, it is natural to try an empty + # dictionary (since a dictionary with entries is valid if there is only + # one parameterization), but that didn't work; only an empty tuple (no + # parameterizations) worked originally. Check that this is resolved. + class MyStandardNormal: + __make_distribution_version__ = "1.16.0" + parameters = {} if p is None else p + support = {'endpoints': (-np.inf, np.inf)} + + def pdf(self, x): + return 1 / np.sqrt(2*np.pi) * np.exp(-x**2/2) + + StandardNormal = stats.make_distribution(MyStandardNormal()) + X = StandardNormal() + assert X.support() == (-np.inf, np.inf) + + +class TestTransforms: + + def test_ContinuousDistribution_only(self): + X = stats.Binomial(n=10, p=0.5) + # This is applied at the top level TransformedDistribution, + # so testing one subclass is enough + message = "Transformations are currently only supported for continuous RVs." + with pytest.raises(NotImplementedError, match=message): + stats.exp(X) + + def test_truncate(self): + rng = np.random.default_rng(81345982345826) + lb = rng.random((3, 1)) + ub = rng.random((3, 1)) + lb, ub = np.minimum(lb, ub), np.maximum(lb, ub) + + Y = stats.truncate(Normal(), lb=lb, ub=ub) + Y0 = stats.truncnorm(lb, ub) + + y = Y0.rvs((3, 10), random_state=rng) + p = Y0.cdf(y) + + assert_allclose(Y.logentropy(), np.log(Y0.entropy() + 0j)) + assert_allclose(Y.entropy(), Y0.entropy()) + assert_allclose(Y.median(), Y0.ppf(0.5)) + assert_allclose(Y.mean(), Y0.mean()) + assert_allclose(Y.variance(), Y0.var()) + assert_allclose(Y.standard_deviation(), np.sqrt(Y0.var())) + assert_allclose(Y.skewness(), Y0.stats('s')) + assert_allclose(Y.kurtosis(), Y0.stats('k') + 3) + assert_allclose(Y.support(), Y0.support()) + assert_allclose(Y.pdf(y), Y0.pdf(y)) + assert_allclose(Y.cdf(y), Y0.cdf(y)) + assert_allclose(Y.ccdf(y), Y0.sf(y)) + assert_allclose(Y.icdf(p), Y0.ppf(p)) + assert_allclose(Y.iccdf(p), Y0.isf(p)) + assert_allclose(Y.logpdf(y), Y0.logpdf(y)) + assert_allclose(Y.logcdf(y), Y0.logcdf(y)) + assert_allclose(Y.logccdf(y), Y0.logsf(y)) + assert_allclose(Y.ilogcdf(np.log(p)), Y0.ppf(p)) + assert_allclose(Y.ilogccdf(np.log(p)), Y0.isf(p)) + sample = Y.sample(10) + assert np.all((sample > lb) & (sample < ub)) + + @pytest.mark.fail_slow(10) + @given(data=strategies.data(), seed=strategies.integers(min_value=0)) + def test_loc_scale(self, data, seed): + # Need tests with negative scale + rng = np.random.default_rng(seed) + + class TransformedNormal(ShiftedScaledDistribution): + def __init__(self, *args, **kwargs): + super().__init__(StandardNormal(), *args, **kwargs) + + tmp = draw_distribution_from_family( + TransformedNormal, data, rng, proportions=(1, 0, 0, 0), min_side=1) + dist, x, y, p, logp, result_shape, x_result_shape, xy_result_shape = tmp + + loc = dist.loc + scale = dist.scale + dist0 = StandardNormal() + dist_ref = stats.norm(loc=loc, scale=scale) + dist_ref_lmoment = Normal(mu=loc, sigma=scale) + + x0 = (x - loc) / scale + y0 = (y - loc) / scale + + a, b = dist.support() + a0, b0 = dist0.support() + assert_allclose(a, a0 + loc) + assert_allclose(b, b0 + loc) + + with np.errstate(invalid='ignore', divide='ignore'): + assert_allclose(np.exp(dist.logentropy()), dist.entropy()) + assert_allclose(dist.entropy(), dist_ref.entropy()) + assert_allclose(dist.median(), dist0.median() + loc) + assert_allclose(dist.mode(), dist0.mode() + loc) + assert_allclose(dist.mean(), dist0.mean() + loc) + assert_allclose(dist.variance(), dist0.variance() * scale**2) + assert_allclose(dist.standard_deviation(), dist.variance()**0.5) + assert_allclose(dist.skewness(), dist0.skewness() * np.sign(scale)) + assert_allclose(dist.kurtosis(), dist0.kurtosis()) + assert_allclose(dist.logpdf(x), dist0.logpdf(x0) - np.log(scale)) + assert_allclose(dist.pdf(x), dist0.pdf(x0) / scale) + assert_allclose(dist.logcdf(x), dist0.logcdf(x0)) + assert_allclose(dist.cdf(x), dist0.cdf(x0)) + assert_allclose(dist.logccdf(x), dist0.logccdf(x0)) + assert_allclose(dist.ccdf(x), dist0.ccdf(x0)) + assert_allclose(dist.logcdf(x, y), dist0.logcdf(x0, y0)) + assert_allclose(dist.cdf(x, y), dist0.cdf(x0, y0)) + assert_allclose(dist.logccdf(x, y), dist0.logccdf(x0, y0)) + assert_allclose(dist.ccdf(x, y), dist0.ccdf(x0, y0)) + assert_allclose(dist.ilogcdf(logp), dist0.ilogcdf(logp)*scale + loc) + assert_allclose(dist.icdf(p), dist0.icdf(p)*scale + loc) + assert_allclose(dist.ilogccdf(logp), dist0.ilogccdf(logp)*scale + loc) + assert_allclose(dist.iccdf(p), dist0.iccdf(p)*scale + loc) + for i in range(1, 5): + assert_allclose(dist.moment(i, 'raw'), dist_ref.moment(i)) + assert_allclose(dist.moment(i, 'central'), + dist0.moment(i, 'central') * scale**i) + assert_allclose(dist.moment(i, 'standardized'), + dist0.moment(i, 'standardized') * np.sign(scale)**i) + for i in range(1, 5): + assert_allclose(dist.lmoment(i), dist_ref_lmoment.lmoment(i), atol=1e-8) + if i >= 3: + assert_allclose(dist.lmoment(i, standardize=True), + dist_ref_lmoment.lmoment(i, standardize=True), + atol=1e-8) + + # Transform back to the original distribution using all arithmetic + # operations; check that it behaves as expected. + dist = (dist - 2*loc) + loc + dist = dist/scale**2 * scale + z = np.zeros(dist._shape) # compact broadcasting + + a, b = dist.support() + a0, b0 = dist0.support() + assert_allclose(a, a0 + z) + assert_allclose(b, b0 + z) + + with np.errstate(invalid='ignore', divide='ignore'): + assert_allclose(dist.logentropy(), dist0.logentropy() + z) + assert_allclose(dist.entropy(), dist0.entropy() + z) + assert_allclose(dist.median(), dist0.median() + z) + assert_allclose(dist.mode(), dist0.mode() + z) + assert_allclose(dist.mean(), dist0.mean() + z) + assert_allclose(dist.variance(), dist0.variance() + z) + assert_allclose(dist.standard_deviation(), dist0.standard_deviation() + z) + assert_allclose(dist.skewness(), dist0.skewness() + z) + assert_allclose(dist.kurtosis(), dist0.kurtosis() + z) + assert_allclose(dist.logpdf(x), dist0.logpdf(x)+z) + assert_allclose(dist.pdf(x), dist0.pdf(x) + z) + assert_allclose(dist.logcdf(x), dist0.logcdf(x) + z) + assert_allclose(dist.cdf(x), dist0.cdf(x) + z) + assert_allclose(dist.logccdf(x), dist0.logccdf(x) + z) + assert_allclose(dist.ccdf(x), dist0.ccdf(x) + z) + assert_allclose(dist.ilogcdf(logp), dist0.ilogcdf(logp) + z) + assert_allclose(dist.icdf(p), dist0.icdf(p) + z) + assert_allclose(dist.ilogccdf(logp), dist0.ilogccdf(logp) + z) + assert_allclose(dist.iccdf(p), dist0.iccdf(p) + z) + for i in range(1, 5): + assert_allclose(dist.moment(i, 'raw'), dist0.moment(i, 'raw')) + assert_allclose(dist.moment(i, 'central'), dist0.moment(i, 'central')) + assert_allclose(dist.moment(i, 'standardized'), + dist0.moment(i, 'standardized')) + for i in range(1, 5): + assert_allclose(dist.lmoment(i), dist0.lmoment(i)) + if i >= 3: + assert_allclose(dist.lmoment(i, standardize=True), + dist0.lmoment(i, standardize=True)) + + # These are tough to compare because of the way the shape works + # rng = np.random.default_rng(seed) + # rng0 = np.random.default_rng(seed) + # assert_allclose(dist.sample(x_result_shape, rng=rng), + # dist0.sample(x_result_shape, rng=rng0) * scale + loc) + # Should also try to test fit, plot? + + @pytest.mark.fail_slow(5) + @pytest.mark.parametrize('exp_pow', ['exp', 'pow']) + def test_exp_pow(self, exp_pow): + rng = np.random.default_rng(81345982345826) + mu = rng.random((3, 1)) + sigma = rng.random((3, 1)) + + X = Normal()*sigma + mu + if exp_pow == 'exp': + Y = stats.exp(X) + else: + Y = np.e ** X + Y0 = stats.lognorm(sigma, scale=np.exp(mu)) + + y = Y0.rvs((3, 10), random_state=rng) + p = Y0.cdf(y) + + assert_allclose(Y.logentropy(), np.log(Y0.entropy())) + assert_allclose(Y.entropy(), Y0.entropy()) + assert_allclose(Y.median(), Y0.ppf(0.5)) + assert_allclose(Y.mean(), Y0.mean()) + assert_allclose(Y.variance(), Y0.var()) + assert_allclose(Y.standard_deviation(), np.sqrt(Y0.var())) + assert_allclose(Y.skewness(), Y0.stats('s')) + assert_allclose(Y.kurtosis(), Y0.stats('k') + 3) + assert_allclose(Y.support(), Y0.support()) + assert_allclose(Y.pdf(y), Y0.pdf(y)) + assert_allclose(Y.cdf(y), Y0.cdf(y)) + assert_allclose(Y.ccdf(y), Y0.sf(y)) + assert_allclose(Y.icdf(p), Y0.ppf(p)) + assert_allclose(Y.iccdf(p), Y0.isf(p)) + assert_allclose(Y.logpdf(y), Y0.logpdf(y)) + assert_allclose(Y.logcdf(y), Y0.logcdf(y)) + assert_allclose(Y.logccdf(y), Y0.logsf(y)) + assert_allclose(Y.ilogcdf(np.log(p)), Y0.ppf(p)) + assert_allclose(Y.ilogccdf(np.log(p)), Y0.isf(p)) + seed = 3984593485 + assert_allclose(Y.sample(rng=seed), np.exp(X.sample(rng=seed))) + + + @pytest.mark.fail_slow(10) + @pytest.mark.parametrize('scale', [1, 2, -1]) + @pytest.mark.xfail_on_32bit("`scale=-1` fails on 32-bit; needs investigation") + def test_reciprocal(self, scale): + rng = np.random.default_rng(81345982345826) + a = rng.random((3, 1)) + + # Separate sign from scale. It's easy to scale the resulting + # RV with negative scale; we want to test the ability to divide + # by a RV with negative support + sign, scale = np.sign(scale), abs(scale) + + # Reference distribution + InvGamma = stats.make_distribution(stats.invgamma) + Y0 = sign * scale * InvGamma(a=a) + + # Test distribution + X = _Gamma(a=a) if sign > 0 else -_Gamma(a=a) + Y = scale / X + + y = Y0.sample(shape=(3, 10), rng=rng) + p = Y0.cdf(y) + logp = np.log(p) + + assert_allclose(Y.logentropy(), np.log(Y0.entropy())) + assert_allclose(Y.entropy(), Y0.entropy()) + assert_allclose(Y.median(), Y0.median()) + # moments are not finite + assert_allclose(Y.support(), Y0.support()) + assert_allclose(Y.pdf(y), Y0.pdf(y)) + assert_allclose(Y.cdf(y), Y0.cdf(y)) + assert_allclose(Y.ccdf(y), Y0.ccdf(y)) + assert_allclose(Y.icdf(p), Y0.icdf(p)) + assert_allclose(Y.iccdf(p), Y0.iccdf(p)) + assert_allclose(Y.logpdf(y), Y0.logpdf(y)) + assert_allclose(Y.logcdf(y), Y0.logcdf(y)) + assert_allclose(Y.logccdf(y), Y0.logccdf(y)) + with np.errstate(divide='ignore', invalid='ignore'): + assert_allclose(Y.ilogcdf(logp), Y0.ilogcdf(logp)) + assert_allclose(Y.ilogccdf(logp), Y0.ilogccdf(logp)) + seed = 3984593485 + assert_allclose(Y.sample(rng=seed), scale/(X.sample(rng=seed))) + + @pytest.mark.fail_slow(5) + def test_log(self): + rng = np.random.default_rng(81345982345826) + a = rng.random((3, 1)) + + X = _Gamma(a=a) + Y0 = stats.loggamma(a) + Y = stats.log(X) + y = Y0.rvs((3, 10), random_state=rng) + p = Y0.cdf(y) + + assert_allclose(Y.logentropy(), np.log(Y0.entropy())) + assert_allclose(Y.entropy(), Y0.entropy()) + assert_allclose(Y.median(), Y0.ppf(0.5)) + assert_allclose(Y.mean(), Y0.mean()) + assert_allclose(Y.variance(), Y0.var()) + assert_allclose(Y.standard_deviation(), np.sqrt(Y0.var())) + assert_allclose(Y.skewness(), Y0.stats('s')) + assert_allclose(Y.kurtosis(), Y0.stats('k') + 3) + assert_allclose(Y.support(), Y0.support()) + assert_allclose(Y.pdf(y), Y0.pdf(y)) + assert_allclose(Y.cdf(y), Y0.cdf(y)) + assert_allclose(Y.ccdf(y), Y0.sf(y)) + assert_allclose(Y.icdf(p), Y0.ppf(p)) + assert_allclose(Y.iccdf(p), Y0.isf(p)) + assert_allclose(Y.logpdf(y), Y0.logpdf(y)) + assert_allclose(Y.logcdf(y), Y0.logcdf(y)) + assert_allclose(Y.logccdf(y), Y0.logsf(y)) + with np.errstate(invalid='ignore'): + assert_allclose(Y.ilogcdf(np.log(p)), Y0.ppf(p)) + assert_allclose(Y.ilogccdf(np.log(p)), Y0.isf(p)) + seed = 3984593485 + assert_allclose(Y.sample(rng=seed), np.log(X.sample(rng=seed))) + + def test_monotonic_transforms(self): + # Some tests of monotonic transforms that are better to be grouped or + # don't fit well above + + X = Uniform(a=1, b=2) + X_str = "Uniform(a=1.0, b=2.0)" + + assert str(stats.log(X)) == f"log({X_str})" + assert str(1 / X) == f"1/({X_str})" + assert str(stats.exp(X)) == f"exp({X_str})" + + X = Uniform(a=-1, b=2) + message = "Division by a random variable is only implemented when the..." + with pytest.raises(NotImplementedError, match=message): + 1 / X + message = "The logarithm of a random variable is only implemented when the..." + with pytest.raises(NotImplementedError, match=message): + stats.log(X) + message = "Raising an argument to the power of a random variable is only..." + with pytest.raises(NotImplementedError, match=message): + (-2) ** X + with pytest.raises(NotImplementedError, match=message): + 1 ** X + with pytest.raises(NotImplementedError, match=message): + [0.5, 1.5] ** X + + message = "Raising a random variable to the power of an argument is only" + with pytest.raises(NotImplementedError, match=message): + X ** (-2) + with pytest.raises(NotImplementedError, match=message): + X ** 0 + with pytest.raises(NotImplementedError, match=message): + X ** [0.5, 1.5] + + def test_arithmetic_operators(self): + rng = np.random.default_rng(2348923495832349834) + + a, b, loc, scale = 0.294, 1.34, 0.57, 1.16 + + x = rng.uniform(-3, 3, 100) + Y = _LogUniform(a=a, b=b) + + X = scale*Y + loc + assert_allclose(X.cdf(x), Y.cdf((x - loc) / scale)) + X = loc + Y*scale + assert_allclose(X.cdf(x), Y.cdf((x - loc) / scale)) + + X = Y/scale - loc + assert_allclose(X.cdf(x), Y.cdf((x + loc) * scale)) + X = loc -_LogUniform(a=a, b=b)/scale + assert_allclose(X.cdf(x), Y.ccdf((-x + loc)*scale)) + + def test_abs(self): + rng = np.random.default_rng(81345982345826) + loc = rng.random((3, 1)) + + Y = stats.abs(Normal() + loc) + Y0 = stats.foldnorm(loc) + + y = Y0.rvs((3, 10), random_state=rng) + p = Y0.cdf(y) + + assert_allclose(Y.logentropy(), np.log(Y0.entropy() + 0j)) + assert_allclose(Y.entropy(), Y0.entropy()) + assert_allclose(Y.median(), Y0.ppf(0.5)) + assert_allclose(Y.mean(), Y0.mean()) + assert_allclose(Y.variance(), Y0.var()) + assert_allclose(Y.standard_deviation(), np.sqrt(Y0.var())) + assert_allclose(Y.skewness(), Y0.stats('s')) + assert_allclose(Y.kurtosis(), Y0.stats('k') + 3) + assert_allclose(Y.support(), Y0.support()) + assert_allclose(Y.pdf(y), Y0.pdf(y)) + assert_allclose(Y.cdf(y), Y0.cdf(y)) + assert_allclose(Y.ccdf(y), Y0.sf(y)) + assert_allclose(Y.icdf(p), Y0.ppf(p)) + assert_allclose(Y.iccdf(p), Y0.isf(p)) + assert_allclose(Y.logpdf(y), Y0.logpdf(y)) + assert_allclose(Y.logcdf(y), Y0.logcdf(y)) + assert_allclose(Y.logccdf(y), Y0.logsf(y)) + assert_allclose(Y.ilogcdf(np.log(p)), Y0.ppf(p)) + assert_allclose(Y.ilogccdf(np.log(p)), Y0.isf(p)) + sample = Y.sample(10) + assert np.all(sample > 0) + + def test_abs_finite_support(self): + # The original implementation of `FoldedDistribution` might evaluate + # the private distribution methods outside the support. Check that this + # is resolved. + Weibull = stats.make_distribution(stats.weibull_min) + X = Weibull(c=2) + Y = abs(-X) + assert_equal(X.logpdf(1), Y.logpdf(1)) + assert_equal(X.pdf(1), Y.pdf(1)) + assert_equal(X.logcdf(1), Y.logcdf(1)) + assert_equal(X.cdf(1), Y.cdf(1)) + assert_equal(X.logccdf(1), Y.logccdf(1)) + assert_equal(X.ccdf(1), Y.ccdf(1)) + + def test_pow(self): + rng = np.random.default_rng(81345982345826) + + Y = Normal()**2 + Y0 = stats.chi2(df=1) + + y = Y0.rvs(10, random_state=rng) + p = Y0.cdf(y) + + assert_allclose(Y.logentropy(), np.log(Y0.entropy() + 0j), rtol=1e-6) + assert_allclose(Y.entropy(), Y0.entropy(), rtol=1e-6) + assert_allclose(Y.median(), Y0.median()) + assert_allclose(Y.mean(), Y0.mean()) + assert_allclose(Y.variance(), Y0.var()) + assert_allclose(Y.standard_deviation(), np.sqrt(Y0.var())) + assert_allclose(Y.skewness(), Y0.stats('s')) + assert_allclose(Y.kurtosis(), Y0.stats('k') + 3) + assert_allclose(Y.support(), Y0.support()) + assert_allclose(Y.pdf(y), Y0.pdf(y)) + assert_allclose(Y.cdf(y), Y0.cdf(y)) + assert_allclose(Y.ccdf(y), Y0.sf(y)) + assert_allclose(Y.icdf(p), Y0.ppf(p)) + assert_allclose(Y.iccdf(p), Y0.isf(p)) + assert_allclose(Y.logpdf(y), Y0.logpdf(y)) + assert_allclose(Y.logcdf(y), Y0.logcdf(y)) + assert_allclose(Y.logccdf(y), Y0.logsf(y)) + assert_allclose(Y.ilogcdf(np.log(p)), Y0.ppf(p)) + assert_allclose(Y.ilogccdf(np.log(p)), Y0.isf(p)) + sample = Y.sample(10) + assert np.all(sample > 0) + +class TestOrderStatistic: + @pytest.mark.fail_slow(20) # Moments require integration + def test_order_statistic(self): + rng = np.random.default_rng(7546349802439582) + X = Uniform(a=0, b=1) + n = 5 + r = np.asarray([[1], [3], [5]]) + Y = stats.order_statistic(X, n=n, r=r) + Y0 = stats.beta(r, n + 1 - r) + + y = Y0.rvs((3, 10), random_state=rng) + p = Y0.cdf(y) + + # log methods need some attention before merge + assert_allclose(np.exp(Y.logentropy()), Y0.entropy()) + assert_allclose(Y.entropy(), Y0.entropy()) + assert_allclose(Y.mean(), Y0.mean()) + assert_allclose(Y.variance(), Y0.var()) + assert_allclose(Y.skewness(), Y0.stats('s'), atol=1e-15) + assert_allclose(Y.kurtosis(), Y0.stats('k') + 3, atol=1e-15) + assert_allclose(Y.median(), Y0.ppf(0.5)) + assert_allclose(Y.support(), Y0.support()) + assert_allclose(Y.pdf(y), Y0.pdf(y)) + assert_allclose(Y.cdf(y, method='formula'), Y.cdf(y, method='quadrature')) + assert_allclose(Y.ccdf(y, method='formula'), Y.ccdf(y, method='quadrature')) + assert_allclose(Y.icdf(p, method='formula'), Y.icdf(p, method='inversion')) + assert_allclose(Y.iccdf(p, method='formula'), Y.iccdf(p, method='inversion')) + assert_allclose(Y.logpdf(y), Y0.logpdf(y)) + assert_allclose(Y.logcdf(y), Y0.logcdf(y)) + assert_allclose(Y.logccdf(y), Y0.logsf(y)) + with np.errstate(invalid='ignore', divide='ignore'): + assert_allclose(Y.ilogcdf(np.log(p),), Y0.ppf(p)) + assert_allclose(Y.ilogccdf(np.log(p)), Y0.isf(p)) + + message = "`r` and `n` must contain only positive integers." + with pytest.raises(ValueError, match=message): + stats.order_statistic(X, n=n, r=-1) + with pytest.raises(ValueError, match=message): + stats.order_statistic(X, n=-1, r=r) + with pytest.raises(ValueError, match=message): + stats.order_statistic(X, n=n, r=1.5) + with pytest.raises(ValueError, match=message): + stats.order_statistic(X, n=1.5, r=r) + + def test_support_gh22037(self): + # During review of gh-22037, it was noted that the `support` of + # an `OrderStatisticDistribution` returned incorrect results; + # this was resolved by overriding `_support`. + Uniform = stats.make_distribution(stats.uniform) + X = Uniform() + Y = X*5 + 2 + Z = stats.order_statistic(Y, r=3, n=5) + assert_allclose(Z.support(), Y.support()) + + def test_composition_gh22037(self): + # During review of gh-22037, it was noted that an error was + # raised when creating an `OrderStatisticDistribution` from + # a `TruncatedDistribution`. This was resolved by overriding + # `_update_parameters`. + Normal = stats.make_distribution(stats.norm) + TruncatedNormal = stats.make_distribution(stats.truncnorm) + a, b = [-2, -1], 1 + r, n = 3, [[4], [5]] + x = [[[-0.3]], [[0.1]]] + X1 = Normal() + Y1 = stats.truncate(X1, a, b) + Z1 = stats.order_statistic(Y1, r=r, n=n) + X2 = TruncatedNormal(a=a, b=b) + Z2 = stats.order_statistic(X2, r=r, n=n) + np.testing.assert_allclose(Z1.cdf(x), Z2.cdf(x)) + + +class TestFullCoverage: + # Adds tests just to get to 100% test coverage; this way it's more obvious + # if new lines are untested. + def test_Domain(self): + with pytest.raises(NotImplementedError): + _Domain.contains(None, 1.) + with pytest.raises(NotImplementedError): + _Domain.get_numerical_endpoints(None, 1.) + with pytest.raises(NotImplementedError): + _Domain.__str__(None) + + def test_Parameter(self): + with pytest.raises(NotImplementedError): + _Parameter.validate(None, 1.) + + @pytest.mark.parametrize(("dtype_in", "dtype_out"), + [(np.float16, np.float16), + (np.int16, np.float64)]) + def test_RealParameter_uncommon_dtypes(self, dtype_in, dtype_out): + domain = _RealInterval((-1, 1)) + parameter = _RealParameter('x', domain=domain) + + x = np.asarray([0.5, 2.5], dtype=dtype_in) + arr, dtype, valid = parameter.validate(x, parameter_values={}) + assert_equal(arr, x) + assert dtype == dtype_out + assert_equal(valid, [True, False]) + + def test_ContinuousDistribution_set_invalid_nan(self): + # Exercise code paths when formula returns wrong shape and dtype + # We could consider making this raise an error to force authors + # to return the right shape and dytpe, but this would need to be + # configurable. + class TestDist(ContinuousDistribution): + _variable = _RealParameter('x', domain=_RealInterval(endpoints=(0., 1.))) + def _logpdf_formula(self, x, *args, **kwargs): + return 0 + + X = TestDist() + dtype = np.float32 + X._dtype = dtype + x = np.asarray([0.5], dtype=dtype) + assert X.logpdf(x).dtype == dtype + + def test_fiinfo(self): + assert _fiinfo(np.float64(1.)).max == np.finfo(np.float64).max + assert _fiinfo(np.int64(1)).max == np.iinfo(np.int64).max + + def test_generate_domain_support(self): + msg = _generate_domain_support(StandardNormal) + assert "accepts no distribution parameters" in msg + + msg = _generate_domain_support(Normal) + assert "accepts one parameterization" in msg + + msg = _generate_domain_support(_LogUniform) + assert "accepts two parameterizations" in msg + + def test_ContinuousDistribution__repr__(self): + X = Uniform(a=0, b=1) + assert repr(X) == "Uniform(a=np.float64(0.0), b=np.float64(1.0))" + assert repr(X*3 + 2) == ( + "np.float64(3.0)*Uniform(a=np.float64(0.0), b=np.float64(1.0))" + " + np.float64(2.0)" + ) + + X = Uniform(a=np.zeros(4), b=1) + assert repr(X) == "Uniform(a=array([0., 0., 0., 0.]), b=1)" + + X = Uniform(a=np.zeros(4, dtype=np.float32), b=np.ones(4, dtype=np.float32)) + assert repr(X) == ( + "Uniform(a=array([0., 0., 0., 0.], dtype=float32)," + " b=array([1., 1., 1., 1.], dtype=float32))" + ) + + +class TestReprs: + U = Uniform(a=0, b=1) + V = Uniform(a=np.float32(0.0), b=np.float32(1.0)) + X = Normal(mu=-1, sigma=1) + Y = Normal(mu=1, sigma=1) + Z = Normal(mu=np.zeros(1000), sigma=1) + + @pytest.mark.parametrize( + "dist", + [ + U, + U - np.array([1.0, 2.0]), + V, + np.ones(2, dtype=np.float32)*V + np.zeros(2, dtype=np.float64), + 3*U + 2, + U**4, + (3*U + 2)**4, + (3*U + 2)**3, + 2**U, + 2**(3*U + 1), + 1 / (1 + U), + stats.order_statistic(U, r=3, n=5), + stats.truncate(U, 0.2, 0.8), + stats.Mixture([X, Y], weights=[0.3, 0.7]), + abs(U), + stats.exp(U), + stats.log(1 + U), + np.array([1.0, 2.0])*U + np.array([2.0, 3.0]), + ] + ) + def test_executable(self, dist): + # Test that reprs actually evaluate to proper distribution + # provided relevant imports are made. + from numpy import array # noqa: F401 + from numpy import float32 # noqa: F401 + from scipy.stats import abs, exp, log, order_statistic, truncate # noqa: F401 + from scipy.stats import Mixture, Normal # noqa: F401 + from scipy.stats._new_distributions import Uniform # noqa: F401 + new_dist = eval(repr(dist)) + # A basic check that the distributions are the same + sample1 = dist.sample(shape=10, rng=1234) + sample2 = new_dist.sample(shape=10, rng=1234) + assert_equal(sample1, sample2) + assert sample1.dtype is sample2.dtype + + @pytest.mark.parametrize( + "dist", + [ + Z, + np.full(1000, 2.0) * X + 1.0, + 2.0 * X + np.full(1000, 1.0), + np.full(1000, 2.0) * X + 1.0, + stats.truncate(Z, -1, 1), + stats.truncate(Z, -np.ones(1000), np.ones(1000)), + stats.order_statistic(X, r=np.arange(1, 1000), n=1000), + Z**2, + 1.0 / (1 + stats.exp(Z)), + 2**Z, + ] + ) + def test_not_too_long(self, dist): + # Tests that array summarization is working to ensure reprs aren't too long. + # None of the reprs above will be executable. + assert len(repr(dist)) < 250 + + +class MixedDist(ContinuousDistribution): + _variable = _RealParameter('x', domain=_RealInterval(endpoints=(-np.inf, np.inf))) + def _pdf_formula(self, x, *args, **kwargs): + return (0.4 * 1/(1.1 * np.sqrt(2*np.pi)) * np.exp(-0.5*((x+0.25)/1.1)**2) + + 0.6 * 1/(0.9 * np.sqrt(2*np.pi)) * np.exp(-0.5*((x-0.5)/0.9)**2)) + + +class TestMixture: + def test_input_validation(self): + message = "`components` must contain at least one random variable." + with pytest.raises(ValueError, match=message): + Mixture([]) + + message = "Each element of `components` must be an instance..." + with pytest.raises(ValueError, match=message): + Mixture((1, 2, 3)) + + message = "All elements of `components` must have scalar shapes." + with pytest.raises(ValueError, match=message): + Mixture([Normal(mu=[1, 2]), Normal()]) + + message = "`components` and `weights` must have the same length." + with pytest.raises(ValueError, match=message): + Mixture([Normal()], weights=[0.5, 0.5]) + + message = "`weights` must have floating point dtype." + with pytest.raises(ValueError, match=message): + Mixture([Normal()], weights=[1]) + + message = "`weights` must have floating point dtype." + with pytest.raises(ValueError, match=message): + Mixture([Normal()], weights=[1]) + + message = "`weights` must sum to 1.0." + with pytest.raises(ValueError, match=message): + Mixture([Normal(), Normal()], weights=[0.5, 1.0]) + + message = "All `weights` must be non-negative." + with pytest.raises(ValueError, match=message): + Mixture([Normal(), Normal()], weights=[1.5, -0.5]) + + @pytest.mark.parametrize('shape', [(), (10,)]) + def test_basic(self, shape): + rng = np.random.default_rng(582348972387243524) + X = Mixture((Normal(mu=-0.25, sigma=1.1), Normal(mu=0.5, sigma=0.9)), + weights=(0.4, 0.6)) + Y = MixedDist() + x = rng.random(shape) + + def assert_allclose(res, ref, **kwargs): + if shape == (): + assert np.isscalar(res) + np.testing.assert_allclose(res, ref, **kwargs) + + assert_allclose(X.logentropy(), Y.logentropy()) + assert_allclose(X.entropy(), Y.entropy()) + assert_allclose(X.mode(), Y.mode()) + assert_allclose(X.median(), Y.median()) + assert_allclose(X.mean(), Y.mean()) + assert_allclose(X.variance(), Y.variance()) + assert_allclose(X.standard_deviation(), Y.standard_deviation()) + assert_allclose(X.skewness(), Y.skewness()) + assert_allclose(X.kurtosis(), Y.kurtosis()) + assert_allclose(X.logpdf(x), Y.logpdf(x)) + assert_allclose(X.pdf(x), Y.pdf(x)) + assert_allclose(X.logcdf(x), Y.logcdf(x)) + assert_allclose(X.cdf(x), Y.cdf(x)) + assert_allclose(X.logccdf(x), Y.logccdf(x)) + assert_allclose(X.ccdf(x), Y.ccdf(x)) + assert_allclose(X.ilogcdf(x), Y.ilogcdf(x)) + assert_allclose(X.icdf(x), Y.icdf(x)) + assert_allclose(X.ilogccdf(x), Y.ilogccdf(x)) + assert_allclose(X.iccdf(x), Y.iccdf(x)) + for kind in ['raw', 'central', 'standardized']: + for order in range(5): + assert_allclose(X.moment(order, kind=kind), + Y.moment(order, kind=kind), + atol=1e-15) + message = "L-moments are not currently available..." + with pytest.raises(NotImplementedError, match=message): + X.lmoment(1) + + # weak test of `sample` + shape = (10, 20, 5) + y = X.sample(shape, rng=rng) + assert y.shape == shape + assert stats.ks_1samp(y.ravel(), X.cdf).pvalue > 0.05 + + def test_default_weights(self): + a = 1.1 + Gamma = stats.make_distribution(stats.gamma) + X = Gamma(a=a) + Y = stats.Mixture((X, -X)) + x = np.linspace(-4, 4, 300) + assert_allclose(Y.pdf(x), stats.dgamma(a=a).pdf(x)) + + def test_properties(self): + components = [Normal(mu=-0.25, sigma=1.1), Normal(mu=0.5, sigma=0.9)] + weights = (0.4, 0.6) + X = Mixture(components, weights=weights) + + # Replacing properties doesn't work + # Different version of Python have different messages + with pytest.raises(AttributeError): + X.components = 10 + with pytest.raises(AttributeError): + X.weights = 10 + + # Mutation doesn't work + X.components[0] = components[1] + assert X.components[0] == components[0] + X.weights[0] = weights[1] + assert X.weights[0] == weights[0] + + def test_inverse(self): + # Originally, inverse relied on the mean to start the bracket search. + # This didn't work for distributions with non-finite mean. Check that + # this is resolved. + rng = np.random.default_rng(24358934657854237863456) + Cauchy = stats.make_distribution(stats.cauchy) + X0 = Cauchy() + X = stats.Mixture([X0, X0]) + p = rng.random(size=10) + np.testing.assert_allclose(X.icdf(p), X0.icdf(p)) + np.testing.assert_allclose(X.iccdf(p), X0.iccdf(p)) + np.testing.assert_allclose(X.ilogcdf(p), X0.ilogcdf(p)) + np.testing.assert_allclose(X.ilogccdf(p), X0.ilogccdf(p)) + + +def test_zipfian_distribution_wrapper(): + # Regression test for gh-23678: calling the cdf method at the end + # point of the Zipfian distribution would generate a warning. + Zipfian = stats.make_distribution(stats.zipfian) + zdist = Zipfian(a=0.75, n=15) + # This should not generate any warnings. + assert_equal(zdist.cdf(15), 1.0) + + +class Test_logexpxmexpy: + # Regression tests for some cases that the simplest version of `_logexpmexpy` + # did not originally handle correctly. + + def test_x_equals_y(self): + # Test x - x == 0 in log-space + x = np.asarray(2.) + assert_equal(_logexpxmexpy(x, x), -np.inf) + + def test_y_neg_inf(self): + # Test x - 0 == x in log-space + x, y = np.asarray(2.), np.asarray(-inf) + assert_equal(_logexpxmexpy(x, y), x) + + def test_nan(self): + # operations involving NaNs should not produce warnings + x = np.asarray(np.nan) + assert_equal(_logexpxmexpy(x, x), x) + + +def test_gh_25180(): + lu = _LogUniform(log_a=np.float64(-0.28915434544814245), + log_b=np.float64(0.3085224670197856)) + actual = lu.mode(method='optimization') + assert np.isfinite(actual) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/test_continuous_basic.py b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_continuous_basic.py new file mode 100644 index 0000000000000000000000000000000000000000..f2a8ee50c83d8149b8a551cfcd068683c2babe3f --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_continuous_basic.py @@ -0,0 +1,1065 @@ +import sys +import warnings +import platform + +import numpy as np +import numpy.testing as npt +import pytest +from pytest import raises as assert_raises +from scipy.integrate import IntegrationWarning +import itertools + +from scipy import stats +from .common_tests import (check_normalization, check_moment, + check_mean_expect, + check_var_expect, check_skew_expect, + check_kurt_expect, check_entropy, + check_private_entropy, check_entropy_vect_scale, + check_edge_support, check_named_args, + check_random_state_property, + check_meth_dtype, check_ppf_dtype, + check_cmplx_deriv, + check_pickling, check_rvs_broadcast, + check_freezing, check_munp_expect,) +from scipy.stats._distr_params import distcont +from scipy.stats._distn_infrastructure import rv_continuous_frozen + +""" +Test all continuous distributions. + +Parameters were chosen for those distributions that pass the +Kolmogorov-Smirnov test. This provides safe parameters for each +distributions so that we can perform further testing of class methods. + +These tests currently check only/mostly for serious errors and exceptions, +not for numerically exact results. +""" + +# Note that you need to add new distributions you want tested +# to _distr_params + +DECIMAL = 5 # specify the precision of the tests # increased from 0 to 5 +_IS_32BIT = (sys.maxsize < 2**32) + +# Sets of tests to skip. +# Entries sorted by speed (very slow to slow). +# xslow took > 1s; slow took > 0.5s + +xslow_test_cont_basic = {'studentized_range', 'kstwo', 'ksone', 'vonmises', 'kappa4', + 'recipinvgauss', 'vonmises_line', 'gausshyper', + 'rel_breitwigner', 'norminvgauss'} +slow_test_cont_basic = {'crystalball', 'powerlognorm', 'pearson3'} + +# test_moments is already marked slow +xslow_test_moments = {'studentized_range', 'ksone', 'vonmises', 'vonmises_line', + 'recipinvgauss', 'kstwo', 'kappa4'} + +slow_fit_mle = {'exponweib', 'genexpon', 'genhyperbolic', 'johnsonsb', + 'kappa4', 'powerlognorm', 'tukeylambda'} +xslow_fit_mle = {'gausshyper', 'ncf', 'ncx2', 'recipinvgauss', 'vonmises_line'} +xfail_fit_mle = {'ksone', 'kstwo', 'truncpareto', 'irwinhall'} +skip_fit_mle = {'levy_stable', 'studentized_range'} # far too slow (>10min) +slow_fit_mm = {'chi2', 'expon', 'lognorm', 'loguniform', 'powerlaw', 'reciprocal'} +xslow_fit_mm = {'argus', 'beta', 'exponpow', 'gausshyper', 'gengamma', + 'genhalflogistic', 'geninvgauss', 'gompertz', + 'johnsonsb', 'kstwobign', 'ncx2', 'norminvgauss', 'trapezoid', + 'truncnorm', 'truncweibull_min', 'wrapcauchy'} +xfail_fit_mm = {'alpha', 'betaprime', 'bradford', 'burr', 'burr12', 'cauchy', + 'crystalball', 'dpareto_lognorm', 'exponweib', 'f', 'fisk', + 'foldcauchy', 'genextreme', 'genpareto', 'halfcauchy', 'invgamma', + 'irwinhall', 'jf_skew_t', 'johnsonsu', 'kappa3', 'kappa4', 'landau', + 'levy', 'levy_l', 'loglaplace', 'lomax', 'mielke', 'ncf', 'nct', + 'pareto', 'powerlognorm', 'powernorm', 'rel_breitwigner', + 'skewcauchy', 't', 'truncexpon', 'truncpareto', + 'tukeylambda', 'vonmises', 'vonmises_line'} +skip_fit_mm = {'genexpon', 'genhyperbolic', 'ksone', 'kstwo', 'levy_stable', + 'recipinvgauss', 'studentized_range'} # far too slow (>10min) + +# These distributions fail the complex derivative test below. +# Here 'fail' mean produce wrong results and/or raise exceptions, depending +# on the implementation details of corresponding special functions. +# cf https://github.com/scipy/scipy/pull/4979 for a discussion. +fails_cmplx = {'argus', 'beta', 'betaprime', 'cauchy', 'chi', 'chi2', 'cosine', + 'dgamma', 'dpareto_lognorm', 'dweibull', 'erlang', 'f', 'foldcauchy', + 'gamma', 'gausshyper', 'gengamma', 'genhyperbolic', + 'geninvgauss', 'gennorm', 'genpareto', + 'halfcauchy', 'halfgennorm', 'invgamma', 'irwinhall', 'jf_skew_t', + 'ksone', 'kstwo', 'kstwobign', 'landau', 'levy_l', 'loggamma', + 'logistic', 'loguniform', 'maxwell', 'nakagami', + 'ncf', 'nct', 'ncx2', 'norminvgauss', 'pearson3', + 'powerlaw', 'rdist', 'reciprocal', 'rice', + 'skewnorm', 't', 'truncpareto', 'truncweibull_min', + 'tukeylambda', 'vonmises', 'vonmises_line', + 'rv_histogram_instance', 'truncnorm', 'studentized_range', + 'johnsonsb', 'halflogistic', 'rel_breitwigner'} + +# Slow test_method_with_lists +slow_with_lists = {'studentized_range'} + + +# rv_histogram instances, with uniform and non-uniform bins; +# stored as (dist, arg) tuples for cases_test_cont_basic +# and cases_test_moments. +histogram_test_instances = [] +case1 = {'a': [1, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6, + 6, 6, 6, 7, 7, 7, 8, 8, 9], 'bins': 8} # equal width bins +case2 = {'a': [1, 1], 'bins': [0, 1, 10]} # unequal width bins +for case, density in itertools.product([case1, case2], [True, False]): + _hist = np.histogram(**case, density=density) + _rv_hist = stats.rv_histogram(_hist, density=density) + histogram_test_instances.append((_rv_hist, tuple())) + + +def cases_test_cont_basic(): + for distname, arg in distcont[:] + histogram_test_instances: + if distname == 'levy_stable': # fails; tested separately + continue + if distname in slow_test_cont_basic: + yield pytest.param(distname, arg, marks=pytest.mark.slow) + elif distname in xslow_test_cont_basic: + yield pytest.param(distname, arg, marks=pytest.mark.xslow) + else: + yield distname, arg + + +@pytest.mark.parametrize('distname,arg', list(cases_test_cont_basic())) +@pytest.mark.parametrize('sn', [500]) +def test_cont_basic(distname, arg, sn, num_parallel_threads): + try: + distfn = getattr(stats, distname) + except TypeError: + distfn = distname + distname = 'rv_histogram_instance' + + rng = np.random.default_rng(7654565) + rvs = distfn.rvs(size=sn, *arg, random_state=rng) + m, v = distfn.stats(*arg) + + if distname not in {'laplace_asymmetric'}: + # TODO: multiple checks in this function are not robust, tweaking the + # seed above will make different distributions fail. + check_sample_meanvar_(m, v, rvs, rng) + check_cdf_ppf(distfn, arg, distname) + check_sf_isf(distfn, arg, distname) + check_cdf_sf(distfn, arg, distname) + check_ppf_isf(distfn, arg, distname) + check_pdf(distfn, arg, distname) + check_pdf_logpdf(distfn, arg, distname) + check_pdf_logpdf_at_endpoints(distfn, arg, distname) + check_cdf_logcdf(distfn, arg, distname) + check_sf_logsf(distfn, arg, distname) + check_ppf_broadcast(distfn, arg, distname) + + alpha = 0.01 + if distname == 'rv_histogram_instance': + check_distribution_rvs(distfn.cdf, arg, alpha, rvs) + elif distname != 'geninvgauss': + # skip kstest for geninvgauss since cdf is too slow; see test for + # rv generation in TestGenInvGauss in test_distributions.py + check_distribution_rvs(distname, arg, alpha, rvs) + + locscale_defaults = (0, 1) + meths = [distfn.pdf, distfn.logpdf, distfn.cdf, distfn.logcdf, + distfn.logsf] + # make sure arguments are within support + spec_x = {'weibull_max': -0.5, 'levy_l': -0.5, + 'pareto': 1.5, 'truncpareto': 3.2, 'tukeylambda': 0.3, + 'rv_histogram_instance': 5.0} + x = spec_x.get(distname, 0.5) + if distname == 'invweibull': + arg = (1,) + elif distname == 'ksone': + arg = (3,) + + check_named_args(distfn, x, arg, locscale_defaults, meths) + if num_parallel_threads == 1: + check_random_state_property(distfn, arg) + + if distname in ['rel_breitwigner'] and _IS_32BIT: + # gh18414 + pytest.skip("fails on Linux 32-bit") + else: + check_pickling(distfn, arg) + check_freezing(distfn, arg) + + # Entropy + if distname not in ['kstwobign', 'kstwo', 'ncf']: + check_entropy(distfn, arg, distname) + + if distfn.numargs == 0: + check_vecentropy(distfn, arg) + + if (distfn.__class__._entropy != stats.rv_continuous._entropy + and distname != 'vonmises'): + check_private_entropy(distfn, arg, stats.rv_continuous) + + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", "The occurrence of roundoff error", IntegrationWarning) + warnings.filterwarnings("ignore", "Extremely bad integrand", IntegrationWarning) + warnings.filterwarnings("ignore", "invalid value", RuntimeWarning) + check_entropy_vect_scale(distfn, arg) + + check_retrieving_support(distfn, arg) + check_edge_support(distfn, arg) + + check_meth_dtype(distfn, arg, meths) + check_ppf_dtype(distfn, arg) + + # complex special functions known to fail on sparc64 (gh-22577) + if distname not in fails_cmplx and platform.machine() != 'sparc64': + check_cmplx_deriv(distfn, arg) + + if distname != 'truncnorm': + check_ppf_private(distfn, arg, distname) + + +def cases_test_cont_basic_fit(): + slow = pytest.mark.slow + xslow = pytest.mark.xslow + fail = pytest.mark.skip(reason="Test fails and may be slow.") + skip = pytest.mark.skip(reason="Test too slow to run to completion (>10m).") + + for distname, arg in distcont[:] + histogram_test_instances: + for method in ["MLE", "MM"]: + for fix_args in [True, False]: + if method == 'MLE' and distname in slow_fit_mle: + yield pytest.param(distname, arg, method, fix_args, marks=slow) + continue + if method == 'MLE' and distname in xslow_fit_mle: + yield pytest.param(distname, arg, method, fix_args, marks=xslow) + continue + if method == 'MLE' and distname in xfail_fit_mle: + yield pytest.param(distname, arg, method, fix_args, marks=fail) + continue + if method == 'MLE' and distname in skip_fit_mle: + yield pytest.param(distname, arg, method, fix_args, marks=skip) + continue + if method == 'MM' and distname in slow_fit_mm: + yield pytest.param(distname, arg, method, fix_args, marks=slow) + continue + if method == 'MM' and distname in xslow_fit_mm: + yield pytest.param(distname, arg, method, fix_args, marks=xslow) + continue + if method == 'MM' and distname in xfail_fit_mm: + yield pytest.param(distname, arg, method, fix_args, marks=fail) + continue + if method == 'MM' and distname in skip_fit_mm: + yield pytest.param(distname, arg, method, fix_args, marks=skip) + continue + + yield distname, arg, method, fix_args + + +def test_cont_basic_fit_cases(): + # Distribution names should not be in multiple MLE or MM sets + assert (len(xslow_fit_mle.union(xfail_fit_mle).union(skip_fit_mle)) == + len(xslow_fit_mle) + len(xfail_fit_mle) + len(skip_fit_mle)) + assert (len(xslow_fit_mm.union(xfail_fit_mm).union(skip_fit_mm)) == + len(xslow_fit_mm) + len(xfail_fit_mm) + len(skip_fit_mm)) + + +@pytest.mark.parametrize('distname, arg, method, fix_args', + list(cases_test_cont_basic_fit())) +@pytest.mark.parametrize('n_fit_samples', [200]) +def test_cont_basic_fit(distname, arg, n_fit_samples, method, fix_args): + try: + distfn = getattr(stats, distname) + except TypeError: + distfn = distname + + rng = np.random.RandomState(765456) + rvs = distfn.rvs(size=n_fit_samples, *arg, random_state=rng) + if fix_args: + check_fit_args_fix(distfn, arg, rvs, method) + else: + check_fit_args(distfn, arg, rvs, method) + +@pytest.mark.parametrize('distname,arg', list(cases_test_cont_basic())) +def test_rvs_scalar(distname, arg): + # rvs should return a scalar when given scalar arguments (gh-12428) + try: + distfn = getattr(stats, distname) + except TypeError: + distfn = distname + distname = 'rv_histogram_instance' + + assert np.isscalar(distfn.rvs(*arg)) + assert np.isscalar(distfn.rvs(*arg, size=())) + assert np.isscalar(distfn.rvs(*arg, size=None)) + + +@pytest.mark.thread_unsafe(reason="global rng") +def test_levy_stable_random_state_property(): + # levy_stable only implements rvs(), so it is skipped in the + # main loop in test_cont_basic(). Here we apply just the test + # check_random_state_property to levy_stable. + check_random_state_property(stats.levy_stable, (0.5, 0.1)) + + +def cases_test_moments(): + fail_normalization = set() + fail_higher = {'ncf'} + fail_moment = {'johnsonsu'} # generic `munp` is inaccurate for johnsonsu + + for distname, arg in distcont[:] + histogram_test_instances: + if distname == 'levy_stable': + continue + + if distname in xslow_test_moments: + yield pytest.param(distname, arg, True, True, True, True, + marks=pytest.mark.xslow(reason="too slow")) + continue + + cond1 = distname not in fail_normalization + cond2 = distname not in fail_higher + cond3 = distname not in fail_moment + + marks = list() + # Currently unused, `marks` can be used to add a timeout to a test of + # a specific distribution. For example, this shows how a timeout could + # be added for the 'skewnorm' distribution: + # + # marks = list() + # if distname == 'skewnorm': + # marks.append(pytest.mark.timeout(300)) + + yield pytest.param(distname, arg, cond1, cond2, cond3, + False, marks=marks) + + if not cond1 or not cond2 or not cond3: + # Run the distributions that have issues twice, once skipping the + # not_ok parts, once with the not_ok parts but marked as knownfail + yield pytest.param(distname, arg, True, True, True, True, + marks=[pytest.mark.xfail] + marks) + + +@pytest.mark.slow +@pytest.mark.parametrize('distname,arg,normalization_ok,higher_ok,moment_ok,' + 'is_xfailing', + list(cases_test_moments())) +def test_moments(distname, arg, normalization_ok, higher_ok, moment_ok, + is_xfailing): + try: + distfn = getattr(stats, distname) + except TypeError: + distfn = distname + distname = 'rv_histogram_instance' + + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", + "The integral is probably divergent, or slowly convergent.", + IntegrationWarning, + ) + warnings.filterwarnings( + "ignore", + "The maximum number of subdivisions.", + IntegrationWarning + ) + warnings.filterwarnings( + "ignore", + "The algorithm does not converge.", + IntegrationWarning + ) + + if is_xfailing: + warnings.simplefilter("ignore", IntegrationWarning) + + m, v, s, k = distfn.stats(*arg, moments='mvsk') + + with np.errstate(all="ignore"): + if normalization_ok: + check_normalization(distfn, arg, distname) + + if higher_ok: + check_mean_expect(distfn, arg, m, distname) + check_skew_expect(distfn, arg, m, v, s, distname) + check_var_expect(distfn, arg, m, v, distname) + check_kurt_expect(distfn, arg, m, v, k, distname) + check_munp_expect(distfn, arg, distname) + + check_loc_scale(distfn, arg, m, v, distname) + + if moment_ok: + check_moment(distfn, arg, m, v, distname) + + +@pytest.mark.parametrize('dist,shape_args', distcont) +def test_rvs_broadcast(dist, shape_args): + if dist in ['gausshyper', 'studentized_range']: + pytest.skip("too slow") + + if dist in ['rel_breitwigner'] and _IS_32BIT: + # gh18414 + pytest.skip("fails on Linux 32-bit") + + # If shape_only is True, it means the _rvs method of the + # distribution uses more than one random number to generate a random + # variate. That means the result of using rvs with broadcasting or + # with a nontrivial size will not necessarily be the same as using the + # numpy.vectorize'd version of rvs(), so we can only compare the shapes + # of the results, not the values. + # Whether or not a distribution is in the following list is an + # implementation detail of the distribution, not a requirement. If + # the implementation the rvs() method of a distribution changes, this + # test might also have to be changed. + shape_only = dist in ['argus', 'betaprime', 'dgamma', 'dpareto_lognorm', 'dweibull', + 'exponnorm', 'genhyperbolic', 'geninvgauss', 'landau', + 'levy_stable', 'nct', 'norminvgauss', 'rice', + 'skewnorm', 'semicircular', 'gennorm', 'loggamma'] + + distfunc = getattr(stats, dist) + loc = np.zeros(2) + scale = np.ones((3, 1)) + nargs = distfunc.numargs + allargs = [] + bshape = [3, 2] + # Generate shape parameter arguments... + for k in range(nargs): + shp = (k + 4,) + (1,)*(k + 2) + allargs.append(shape_args[k]*np.ones(shp)) + bshape.insert(0, k + 4) + allargs.extend([loc, scale]) + # bshape holds the expected shape when loc, scale, and the shape + # parameters are all broadcast together. + + check_rvs_broadcast(distfunc, dist, allargs, bshape, shape_only, 'd') + + +# Expected values of the SF, CDF, PDF were computed using +# mpmath with mpmath.mp.dps = 50 and output at 20: +# +# def ks(x, n): +# x = mpmath.mpf(x) +# logp = -mpmath.power(6.0*n*x+1.0, 2)/18.0/n +# sf, cdf = mpmath.exp(logp), -mpmath.expm1(logp) +# pdf = (6.0*n*x+1.0) * 2 * sf/3 +# print(mpmath.nstr(sf, 20), mpmath.nstr(cdf, 20), mpmath.nstr(pdf, 20)) +# +# Tests use 1/n < x < 1-1/n and n > 1e6 to use the asymptotic computation. +# Larger x has a smaller sf. +@pytest.mark.parametrize('x,n,sf,cdf,pdf,rtol', + [(2.0e-5, 1000000000, + 0.44932297307934442379, 0.55067702692065557621, + 35946.137394996276407, 5e-15), + (2.0e-9, 1000000000, + 0.99999999061111115519, 9.3888888448132728224e-9, + 8.6666665852962971765, 5e-14), + (5.0e-4, 1000000000, + 7.1222019433090374624e-218, 1.0, + 1.4244408634752704094e-211, 5e-14)]) +def test_gh17775_regression(x, n, sf, cdf, pdf, rtol): + # Regression test for gh-17775. In scipy 1.9.3 and earlier, + # these test would fail. + # + # KS one asymptotic sf ~ e^(-(6nx+1)^2 / 18n) + # Given a large 32-bit integer n, 6n will overflow in the c implementation. + # Example of broken behaviour: + # ksone.sf(2.0e-5, 1000000000) == 0.9374359693473666 + ks = stats.ksone + vals = np.array([ks.sf(x, n), ks.cdf(x, n), ks.pdf(x, n)]) + expected = np.array([sf, cdf, pdf]) + npt.assert_allclose(vals, expected, rtol=rtol) + # The sf+cdf must sum to 1.0. + npt.assert_equal(vals[0] + vals[1], 1.0) + # Check inverting the (potentially very small) sf (uses a lower tolerance) + npt.assert_allclose([ks.isf(sf, n)], [x], rtol=1e-8) + + +def test_rvs_gh2069_regression(): + # Regression tests for gh-2069. In scipy 0.17 and earlier, + # these tests would fail. + # + # A typical example of the broken behavior: + # >>> norm.rvs(loc=np.zeros(5), scale=np.ones(5)) + # array([-2.49613705, -2.49613705, -2.49613705, -2.49613705, -2.49613705]) + rng = np.random.RandomState(123) + vals = stats.norm.rvs(loc=np.zeros(5), scale=1, random_state=rng) + d = np.diff(vals) + npt.assert_(np.all(d != 0), "All the values are equal, but they shouldn't be!") + vals = stats.norm.rvs(loc=0, scale=np.ones(5), random_state=rng) + d = np.diff(vals) + npt.assert_(np.all(d != 0), "All the values are equal, but they shouldn't be!") + vals = stats.norm.rvs(loc=np.zeros(5), scale=np.ones(5), random_state=rng) + d = np.diff(vals) + npt.assert_(np.all(d != 0), "All the values are equal, but they shouldn't be!") + vals = stats.norm.rvs(loc=np.array([[0], [0]]), scale=np.ones(5), + random_state=rng) + d = np.diff(vals.ravel()) + npt.assert_(np.all(d != 0), "All the values are equal, but they shouldn't be!") + + assert_raises(ValueError, stats.norm.rvs, [[0, 0], [0, 0]], + [[1, 1], [1, 1]], 1) + assert_raises(ValueError, stats.gamma.rvs, [2, 3, 4, 5], 0, 1, (2, 2)) + assert_raises(ValueError, stats.gamma.rvs, [1, 1, 1, 1], [0, 0, 0, 0], + [[1], [2]], (4,)) + + +def test_nomodify_gh9900_regression(): + # Regression test for gh-9990 + # Prior to gh-9990, calls to stats.truncnorm._cdf() use what ever was + # set inside the stats.truncnorm instance during stats.truncnorm.cdf(). + # This could cause issues with multi-threaded code. + # Since then, the calls to cdf() are not permitted to modify the global + # stats.truncnorm instance. + tn = stats.truncnorm + # Use the right-half truncated normal + # Check that the cdf and _cdf return the same result. + npt.assert_almost_equal(tn.cdf(1, 0, np.inf), + 0.6826894921370859) + npt.assert_almost_equal(tn._cdf([1], [0], [np.inf]), + 0.6826894921370859) + + # Now use the left-half truncated normal + npt.assert_almost_equal(tn.cdf(-1, -np.inf, 0), + 0.31731050786291415) + npt.assert_almost_equal(tn._cdf([-1], [-np.inf], [0]), + 0.31731050786291415) + + # Check that the right-half truncated normal _cdf hasn't changed + npt.assert_almost_equal(tn._cdf([1], [0], [np.inf]), + 0.6826894921370859) # Not 1.6826894921370859 + npt.assert_almost_equal(tn.cdf(1, 0, np.inf), + 0.6826894921370859) + + # Check that the left-half truncated normal _cdf hasn't changed + npt.assert_almost_equal(tn._cdf([-1], [-np.inf], [0]), + 0.31731050786291415) # Not -0.6826894921370859 + npt.assert_almost_equal(tn.cdf(1, -np.inf, 0), + 1) # Not 1.6826894921370859 + npt.assert_almost_equal(tn.cdf(-1, -np.inf, 0), + 0.31731050786291415) # Not -0.6826894921370859 + + +def test_broadcast_gh9990_regression(): + # Regression test for gh-9990 + # The x-value 7 only lies within the support of 4 of the supplied + # distributions. Prior to 9990, one array passed to + # stats.reciprocal._cdf would have 4 elements, but an array + # previously stored by stats.reciprocal_argcheck() would have 6, leading + # to a broadcast error. + a = np.array([1, 2, 3, 4, 5, 6]) + b = np.array([8, 16, 1, 32, 1, 48]) + ans = [stats.reciprocal.cdf(7, _a, _b) for _a, _b in zip(a,b)] + npt.assert_array_almost_equal(stats.reciprocal.cdf(7, a, b), ans) + + ans = [stats.reciprocal.cdf(1, _a, _b) for _a, _b in zip(a,b)] + npt.assert_array_almost_equal(stats.reciprocal.cdf(1, a, b), ans) + + ans = [stats.reciprocal.cdf(_a, _a, _b) for _a, _b in zip(a,b)] + npt.assert_array_almost_equal(stats.reciprocal.cdf(a, a, b), ans) + + ans = [stats.reciprocal.cdf(_b, _a, _b) for _a, _b in zip(a,b)] + npt.assert_array_almost_equal(stats.reciprocal.cdf(b, a, b), ans) + + +def test_broadcast_gh7933_regression(): + # Check broadcast works + stats.truncnorm.logpdf( + np.array([3.0, 2.0, 1.0]), + a=(1.5 - np.array([6.0, 5.0, 4.0])) / 3.0, + b=np.inf, + loc=np.array([6.0, 5.0, 4.0]), + scale=3.0 + ) + + +def test_gh2002_regression(): + # Add a check that broadcast works in situations where only some + # x-values are compatible with some of the shape arguments. + x = np.r_[-2:2:101j] + a = np.r_[-np.ones(50), np.ones(51)] + expected = [stats.truncnorm.pdf(_x, _a, np.inf) for _x, _a in zip(x, a)] + ans = stats.truncnorm.pdf(x, a, np.inf) + npt.assert_array_almost_equal(ans, expected) + + +def test_gh1320_regression(): + # Check that the first example from gh-1320 now works. + c = 2.62 + stats.genextreme.ppf(0.5, np.array([[c], [c + 0.5]])) + # The other examples in gh-1320 appear to have stopped working + # some time ago. + # ans = stats.genextreme.moment(2, np.array([c, c + 0.5])) + # expected = np.array([25.50105963, 115.11191437]) + # stats.genextreme.moment(5, np.array([[c], [c + 0.5]])) + # stats.genextreme.moment(5, np.array([c, c + 0.5])) + + +def test_method_of_moments(): + # example from https://en.wikipedia.org/wiki/Method_of_moments_(statistics) + x = [0, 0, 0, 0, 1] + a = 1/5 - 2*np.sqrt(3)/5 + b = 1/5 + 2*np.sqrt(3)/5 + # force use of method of moments (uniform.fit is overridden) + loc, scale = super(type(stats.uniform), stats.uniform).fit(x, method="MM") + npt.assert_almost_equal(loc, a, decimal=4) + npt.assert_almost_equal(loc+scale, b, decimal=4) + + +def check_sample_meanvar_(popmean, popvar, sample, rng): + if np.isfinite(popmean): + check_sample_mean(sample, popmean) + if np.isfinite(popvar): + check_sample_var(sample, popvar, rng) + + +def check_sample_mean(sample, popmean): + # Checks for unlikely difference between sample mean and population mean + prob = stats.ttest_1samp(sample, popmean).pvalue + assert prob > 0.01 + + +def check_sample_var(sample, popvar, rng): + # check that population mean lies within the CI bootstrapped from the + # sample. This used to be a chi-squared test for variance, but there were + # too many false positives + res = stats.bootstrap( + (sample,), + lambda x, axis: x.var(ddof=1, axis=axis), + confidence_level=0.995, + rng=rng, + ) + conf = res.confidence_interval + low, high = conf.low, conf.high + assert low <= popvar <= high + + +def check_cdf_ppf(distfn, arg, msg): + values = [0.001, 0.5, 0.999] + npt.assert_almost_equal(distfn.cdf(distfn.ppf(values, *arg), *arg), + values, decimal=DECIMAL, err_msg=msg + + ' - cdf-ppf roundtrip') + + +def check_sf_isf(distfn, arg, msg): + npt.assert_almost_equal(distfn.sf(distfn.isf([0.1, 0.5, 0.9], *arg), *arg), + [0.1, 0.5, 0.9], decimal=DECIMAL, err_msg=msg + + ' - sf-isf roundtrip') + + +def check_cdf_sf(distfn, arg, msg): + npt.assert_almost_equal(distfn.cdf([0.1, 0.9], *arg), + 1.0 - distfn.sf([0.1, 0.9], *arg), + decimal=DECIMAL, err_msg=msg + + ' - cdf-sf relationship') + + +def check_ppf_isf(distfn, arg, msg): + p = np.array([0.1, 0.9]) + npt.assert_almost_equal(distfn.isf(p, *arg), distfn.ppf(1-p, *arg), + decimal=DECIMAL, err_msg=msg + + ' - ppf-isf relationship') + + +def check_pdf(distfn, arg, msg): + # compares pdf at median with numerical derivative of cdf + median = distfn.ppf(0.5, *arg) + eps = 1e-6 + pdfv = distfn.pdf(median, *arg) + if (pdfv < 1e-4) or (pdfv > 1e4): + # avoid checking a case where pdf is close to zero or + # huge (singularity) + median = median + 0.1 + pdfv = distfn.pdf(median, *arg) + cdfdiff = (distfn.cdf(median + eps, *arg) - + distfn.cdf(median - eps, *arg))/eps/2.0 + # replace with better diff and better test (more points), + # actually, this works pretty well + msg += ' - cdf-pdf relationship' + npt.assert_almost_equal(pdfv, cdfdiff, decimal=DECIMAL, err_msg=msg) + + +def check_pdf_logpdf(distfn, args, msg): + # compares pdf at several points with the log of the pdf + points = np.array([0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8]) + vals = distfn.ppf(points, *args) + vals = vals[np.isfinite(vals)] + pdf = distfn.pdf(vals, *args) + logpdf = distfn.logpdf(vals, *args) + pdf = pdf[(pdf != 0) & np.isfinite(pdf)] + logpdf = logpdf[np.isfinite(logpdf)] + msg += " - logpdf-log(pdf) relationship" + npt.assert_almost_equal(np.log(pdf), logpdf, decimal=7, err_msg=msg) + + +def check_pdf_logpdf_at_endpoints(distfn, args, msg): + # compares pdf with the log of the pdf at the (finite) end points + points = np.array([0, 1]) + vals = distfn.ppf(points, *args) + vals = vals[np.isfinite(vals)] + pdf = distfn.pdf(vals, *args) + logpdf = distfn.logpdf(vals, *args) + pdf = pdf[(pdf != 0) & np.isfinite(pdf)] + logpdf = logpdf[np.isfinite(logpdf)] + msg += " - logpdf-log(pdf) relationship" + npt.assert_almost_equal(np.log(pdf), logpdf, decimal=7, err_msg=msg) + + +def check_sf_logsf(distfn, args, msg): + # compares sf at several points with the log of the sf + points = np.array([0.0, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 1.0]) + vals = distfn.ppf(points, *args) + vals = vals[np.isfinite(vals)] + sf = distfn.sf(vals, *args) + logsf = distfn.logsf(vals, *args) + sf = sf[sf != 0] + logsf = logsf[np.isfinite(logsf)] + msg += " - logsf-log(sf) relationship" + npt.assert_almost_equal(np.log(sf), logsf, decimal=7, err_msg=msg) + + +def check_cdf_logcdf(distfn, args, msg): + # compares cdf at several points with the log of the cdf + points = np.array([0, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 1.0]) + vals = distfn.ppf(points, *args) + vals = vals[np.isfinite(vals)] + cdf = distfn.cdf(vals, *args) + logcdf = distfn.logcdf(vals, *args) + cdf = cdf[cdf != 0] + logcdf = logcdf[np.isfinite(logcdf)] + msg += " - logcdf-log(cdf) relationship" + npt.assert_almost_equal(np.log(cdf), logcdf, decimal=7, err_msg=msg) + + +def check_ppf_broadcast(distfn, arg, msg): + # compares ppf for multiple argsets. + num_repeats = 5 + args = [] * num_repeats + if arg: + args = [np.array([_] * num_repeats) for _ in arg] + + median = distfn.ppf(0.5, *arg) + medians = distfn.ppf(0.5, *args) + msg += " - ppf multiple" + npt.assert_almost_equal(medians, [median] * num_repeats, decimal=7, err_msg=msg) + + +def check_distribution_rvs(dist, args, alpha, rvs): + # dist is either a cdf function or name of a distribution in scipy.stats. + # args are the args for scipy.stats.dist(*args) + # alpha is a significance level, ~0.01 + # rvs is array_like of random variables + # test from scipy.stats.tests + # this version reuses existing random variables + D, pval = stats.kstest(rvs, dist, args=args, N=1000) + if (pval < alpha): + # The rvs passed in failed the K-S test, which _could_ happen + # but is unlikely if alpha is small enough. + # Repeat the test with a new sample of rvs. + # Generate 1000 rvs, perform a K-S test that the new sample of rvs + # are distributed according to the distribution. + D, pval = stats.kstest(dist, dist, args=args, N=1000) + npt.assert_(pval > alpha, "D = " + str(D) + "; pval = " + str(pval) + + "; alpha = " + str(alpha) + "\nargs = " + str(args)) + + +def check_vecentropy(distfn, args): + npt.assert_equal(distfn.vecentropy(*args), distfn._entropy(*args)) + + +def check_loc_scale(distfn, arg, m, v, msg): + # Make `loc` and `scale` arrays to catch bugs like gh-13580 where + # `loc` and `scale` arrays improperly broadcast with shapes. + loc, scale = np.array([10.0, 20.0]), np.array([10.0, 20.0]) + mt, vt = distfn.stats(*arg, loc=loc, scale=scale) + npt.assert_allclose(m*scale + loc, mt) + npt.assert_allclose(v*scale*scale, vt) + + +def check_ppf_private(distfn, arg, msg): + # fails by design for truncnorm self.nb not defined + ppfs = distfn._ppf(np.array([0.1, 0.5, 0.9]), *arg) + npt.assert_(not np.any(np.isnan(ppfs)), msg + 'ppf private is nan') + + +def check_retrieving_support(distfn, args): + loc, scale = 1, 2 + supp = distfn.support(*args) + supp_loc_scale = distfn.support(*args, loc=loc, scale=scale) + npt.assert_almost_equal(np.array(supp)*scale + loc, + np.array(supp_loc_scale)) + + +def check_fit_args(distfn, arg, rvs, method): + with np.errstate(all='ignore'), warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=RuntimeWarning, + message="The shape parameter of the erlang") + warnings.filterwarnings("ignore", category=RuntimeWarning, + message="floating point number truncated") + vals = distfn.fit(rvs, method=method) + vals2 = distfn.fit(rvs, optimizer='powell', method=method) + # Only check the length of the return; accuracy tested in test_fit.py + npt.assert_(len(vals) == 2+len(arg)) + npt.assert_(len(vals2) == 2+len(arg)) + + +def check_fit_args_fix(distfn, arg, rvs, method): + with np.errstate(all='ignore'), warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=RuntimeWarning, + message="The shape parameter of the erlang") + + vals = distfn.fit(rvs, floc=0, method=method) + vals2 = distfn.fit(rvs, fscale=1, method=method) + npt.assert_(len(vals) == 2+len(arg)) + npt.assert_(vals[-2] == 0) + npt.assert_(vals2[-1] == 1) + npt.assert_(len(vals2) == 2+len(arg)) + if len(arg) > 0: + vals3 = distfn.fit(rvs, f0=arg[0], method=method) + npt.assert_(len(vals3) == 2+len(arg)) + npt.assert_(vals3[0] == arg[0]) + if len(arg) > 1: + vals4 = distfn.fit(rvs, f1=arg[1], method=method) + npt.assert_(len(vals4) == 2+len(arg)) + npt.assert_(vals4[1] == arg[1]) + if len(arg) > 2: + vals5 = distfn.fit(rvs, f2=arg[2], method=method) + npt.assert_(len(vals5) == 2+len(arg)) + npt.assert_(vals5[2] == arg[2]) + + +def cases_test_methods_with_lists(): + for distname, arg in distcont: + if distname in slow_with_lists: + yield pytest.param(distname, arg, marks=pytest.mark.slow) + else: + yield distname, arg + + +@pytest.mark.parametrize('method', ['pdf', 'logpdf', 'cdf', 'logcdf', + 'sf', 'logsf', 'ppf', 'isf']) +@pytest.mark.parametrize('distname, args', list(cases_test_methods_with_lists())) +def test_methods_with_lists(method, distname, args): + # Test that the continuous distributions can accept Python lists + # as arguments. + dist = getattr(stats, distname) + f = getattr(dist, method) + if distname == 'invweibull' and method.startswith('log'): + x = [1.5, 2] + else: + x = [0.1, 0.2] + + shape2 = [[a]*2 for a in args] + loc = [0, 0.1] + scale = [1, 1.01] + result = f(x, *shape2, loc=loc, scale=scale) + npt.assert_allclose(result, + [f(*v) for v in zip(x, *shape2, loc, scale)], + rtol=1e-14, atol=5e-14) + + +def test_burr_fisk_moment_gh13234_regression(): + vals0 = stats.burr.moment(1, 5, 4) + assert isinstance(vals0, float) + + vals1 = stats.fisk.moment(1, 8) + assert isinstance(vals1, float) + + +def test_moments_with_array_gh12192_regression(): + # array loc and scalar scale + vals0 = stats.norm.moment(order=1, loc=np.array([1, 2, 3]), scale=1) + expected0 = np.array([1., 2., 3.]) + npt.assert_equal(vals0, expected0) + + # array loc and invalid scalar scale + vals1 = stats.norm.moment(order=1, loc=np.array([1, 2, 3]), scale=-1) + expected1 = np.array([np.nan, np.nan, np.nan]) + npt.assert_equal(vals1, expected1) + + # array loc and array scale with invalid entries + vals2 = stats.norm.moment(order=1, loc=np.array([1, 2, 3]), + scale=[-3, 1, 0]) + expected2 = np.array([np.nan, 2., np.nan]) + npt.assert_equal(vals2, expected2) + + # (loc == 0) & (scale < 0) + vals3 = stats.norm.moment(order=2, loc=0, scale=-4) + expected3 = np.nan + npt.assert_equal(vals3, expected3) + assert isinstance(vals3, expected3.__class__) + + # array loc with 0 entries and scale with invalid entries + vals4 = stats.norm.moment(order=2, loc=[1, 0, 2], scale=[3, -4, -5]) + expected4 = np.array([10., np.nan, np.nan]) + npt.assert_equal(vals4, expected4) + + # all(loc == 0) & (array scale with invalid entries) + vals5 = stats.norm.moment(order=2, loc=[0, 0, 0], scale=[5., -2, 100.]) + expected5 = np.array([25., np.nan, 10000.]) + npt.assert_equal(vals5, expected5) + + # all( (loc == 0) & (scale < 0) ) + vals6 = stats.norm.moment(order=2, loc=[0, 0, 0], scale=[-5., -2, -100.]) + expected6 = np.array([np.nan, np.nan, np.nan]) + npt.assert_equal(vals6, expected6) + + # scalar args, loc, and scale + vals7 = stats.chi.moment(order=2, df=1, loc=0, scale=0) + expected7 = np.nan + npt.assert_equal(vals7, expected7) + assert isinstance(vals7, expected7.__class__) + + # array args, scalar loc, and scalar scale + vals8 = stats.chi.moment(order=2, df=[1, 2, 3], loc=0, scale=0) + expected8 = np.array([np.nan, np.nan, np.nan]) + npt.assert_equal(vals8, expected8) + + # array args, array loc, and array scale + vals9 = stats.chi.moment(order=2, df=[1, 2, 3], loc=[1., 0., 2.], + scale=[1., -3., 0.]) + expected9 = np.array([3.59576912, np.nan, np.nan]) + npt.assert_allclose(vals9, expected9, rtol=1e-8) + + # (n > 4), all(loc != 0), and all(scale != 0) + vals10 = stats.norm.moment(5, [1., 2.], [1., 2.]) + expected10 = np.array([26., 832.]) + npt.assert_allclose(vals10, expected10, rtol=1e-13) + + # test broadcasting and more + a = [-1.1, 0, 1, 2.2, np.pi] + b = [-1.1, 0, 1, 2.2, np.pi] + loc = [-1.1, 0, np.sqrt(2)] + scale = [-2.1, 0, 1, 2.2, np.pi] + + a = np.array(a).reshape((-1, 1, 1, 1)) + b = np.array(b).reshape((-1, 1, 1)) + loc = np.array(loc).reshape((-1, 1)) + scale = np.array(scale) + + vals11 = stats.beta.moment(order=2, a=a, b=b, loc=loc, scale=scale) + + a, b, loc, scale = np.broadcast_arrays(a, b, loc, scale) + + for i in np.ndenumerate(a): + with np.errstate(invalid='ignore', divide='ignore'): + i = i[0] # just get the index + # check against same function with scalar input + expected = stats.beta.moment(order=2, a=a[i], b=b[i], + loc=loc[i], scale=scale[i]) + np.testing.assert_equal(vals11[i], expected) + + +def test_broadcasting_in_moments_gh12192_regression(): + vals0 = stats.norm.moment(order=1, loc=np.array([1, 2, 3]), scale=[[1]]) + expected0 = np.array([[1., 2., 3.]]) + npt.assert_equal(vals0, expected0) + assert vals0.shape == expected0.shape + + vals1 = stats.norm.moment(order=1, loc=np.array([[1], [2], [3]]), + scale=[1, 2, 3]) + expected1 = np.array([[1., 1., 1.], [2., 2., 2.], [3., 3., 3.]]) + npt.assert_equal(vals1, expected1) + assert vals1.shape == expected1.shape + + vals2 = stats.chi.moment(order=1, df=[1., 2., 3.], loc=0., scale=1.) + expected2 = np.array([0.79788456, 1.25331414, 1.59576912]) + npt.assert_allclose(vals2, expected2, rtol=1e-8) + assert vals2.shape == expected2.shape + + vals3 = stats.chi.moment(order=1, df=[[1.], [2.], [3.]], loc=[0., 1., 2.], + scale=[-1., 0., 3.]) + expected3 = np.array([[np.nan, np.nan, 4.39365368], + [np.nan, np.nan, 5.75994241], + [np.nan, np.nan, 6.78730736]]) + npt.assert_allclose(vals3, expected3, rtol=1e-8) + assert vals3.shape == expected3.shape + + +@pytest.mark.slow +def test_kappa3_array_gh13582(): + # https://github.com/scipy/scipy/pull/15140#issuecomment-994958241 + shapes = [0.5, 1.5, 2.5, 3.5, 4.5] + moments = 'mvsk' + res = np.array([[stats.kappa3.stats(shape, moments=moment) + for shape in shapes] for moment in moments]) + res2 = np.array(stats.kappa3.stats(shapes, moments=moments)) + npt.assert_allclose(res, res2) + + +@pytest.mark.xslow +def test_kappa4_array_gh13582(): + h = np.array([-0.5, 2.5, 3.5, 4.5, -3]) + k = np.array([-0.5, 1, -1.5, 0, 3.5]) + moments = 'mvsk' + res = np.array([[stats.kappa4.stats(h[i], k[i], moments=moment) + for i in range(5)] for moment in moments]) + res2 = np.array(stats.kappa4.stats(h, k, moments=moments)) + npt.assert_allclose(res, res2) + + # https://github.com/scipy/scipy/pull/15250#discussion_r775112913 + h = np.array([-1, -1/4, -1/4, 1, -1, 0]) + k = np.array([1, 1, 1/2, -1/3, -1, 0]) + res = np.array([[stats.kappa4.stats(h[i], k[i], moments=moment) + for i in range(6)] for moment in moments]) + res2 = np.array(stats.kappa4.stats(h, k, moments=moments)) + npt.assert_allclose(res, res2) + + # https://github.com/scipy/scipy/pull/15250#discussion_r775115021 + h = np.array([-1, -0.5, 1]) + k = np.array([-1, -0.5, 0, 1])[:, None] + res2 = np.array(stats.kappa4.stats(h, k, moments=moments)) + assert res2.shape == (4, 4, 3) + + +def test_frozen_attributes(monkeypatch): + # gh-14827 reported that all frozen distributions had both pmf and pdf + # attributes; continuous should have pdf and discrete should have pmf. + message = "'rv_continuous_frozen' object has no attribute" + with pytest.raises(AttributeError, match=message): + stats.norm().pmf + with pytest.raises(AttributeError, match=message): + stats.norm().logpmf + monkeypatch.setattr(stats.norm, "pmf", "herring", raising=False) + frozen_norm = stats.norm() + assert isinstance(frozen_norm, rv_continuous_frozen) + assert not hasattr(frozen_norm, "pmf") + + +def test_skewnorm_pdf_gh16038(): + rng = np.random.default_rng(0) + x, a = -np.inf, 0 + npt.assert_equal(stats.skewnorm.pdf(x, a), stats.norm.pdf(x)) + x, a = rng.random(size=(3, 3)), rng.random(size=(3, 3)) + mask = rng.random(size=(3, 3)) < 0.5 + a[mask] = 0 + x_norm = x[mask] + res = stats.skewnorm.pdf(x, a) + npt.assert_equal(res[mask], stats.norm.pdf(x_norm)) + npt.assert_equal(res[~mask], stats.skewnorm.pdf(x[~mask], a[~mask])) + + +# for scalar input, these functions should return scalar output +scalar_out = [['rvs', []], ['pdf', [0]], ['logpdf', [0]], ['cdf', [0]], + ['logcdf', [0]], ['sf', [0]], ['logsf', [0]], ['ppf', [0]], + ['isf', [0]], ['moment', [1]], ['entropy', []], ['expect', []], + ['median', []], ['mean', []], ['std', []], ['var', []]] +scalars_out = [['interval', [0.95]], ['support', []], ['stats', ['mv']]] + + +@pytest.mark.parametrize('case', scalar_out + scalars_out) +def test_scalar_for_scalar(case): + # Some rv_continuous functions returned 0d array instead of NumPy scalar + # Guard against regression + method_name, args = case + method = getattr(stats.norm(), method_name) + res = method(*args) + if case in scalar_out: + assert isinstance(res, np.number) + else: + assert isinstance(res[0], np.number) + assert isinstance(res[1], np.number) + + +def test_scalar_for_scalar2(): + # test methods that are not attributes of frozen distributions + res = stats.norm.fit([1, 2, 3]) + assert isinstance(res[0], np.number) + assert isinstance(res[1], np.number) + res = stats.norm.fit_loc_scale([1, 2, 3]) + assert isinstance(res[0], np.number) + assert isinstance(res[1], np.number) + res = stats.norm.nnlf((0, 1), [1, 2, 3]) + assert isinstance(res, np.number) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/test_continuous_fit_censored.py b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_continuous_fit_censored.py new file mode 100644 index 0000000000000000000000000000000000000000..4508b49712e5bc8975bf2f9b2681ccc6504b0ae0 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_continuous_fit_censored.py @@ -0,0 +1,683 @@ +# Tests for fitting specific distributions to censored data. + +import numpy as np +from numpy.testing import assert_allclose + +from scipy.optimize import fmin +from scipy.stats import (CensoredData, beta, cauchy, chi2, expon, gamma, + gumbel_l, gumbel_r, invgauss, invweibull, laplace, + logistic, lognorm, nct, ncx2, norm, weibull_max, + weibull_min) + + +# In some tests, we'll use this optimizer for improved accuracy. +def optimizer(func, x0, args=(), disp=0): + return fmin(func, x0, args=args, disp=disp, xtol=1e-12, ftol=1e-12) + + +def test_beta(): + """ + Test fitting beta shape parameters to interval-censored data. + + Calculation in R: + + > library(fitdistrplus) + > data <- data.frame(left=c(0.10, 0.50, 0.75, 0.80), + + right=c(0.20, 0.55, 0.90, 0.95)) + > result = fitdistcens(data, 'beta', control=list(reltol=1e-14)) + + > result + Fitting of the distribution ' beta ' on censored data by maximum likelihood + Parameters: + estimate + shape1 1.419941 + shape2 1.027066 + > result$sd + shape1 shape2 + 0.9914177 0.6866565 + """ + data = CensoredData(interval=[[0.10, 0.20], + [0.50, 0.55], + [0.75, 0.90], + [0.80, 0.95]]) + + # For this test, fit only the shape parameters; loc and scale are fixed. + a, b, loc, scale = beta.fit(data, floc=0, fscale=1, optimizer=optimizer) + + assert_allclose(a, 1.419941, rtol=5e-6) + assert_allclose(b, 1.027066, rtol=5e-6) + assert loc == 0 + assert scale == 1 + + +def test_cauchy_right_censored(): + """ + Test fitting the Cauchy distribution to right-censored data. + + Calculation in R, with two values not censored [1, 10] and + one right-censored value [30]. + + > library(fitdistrplus) + > data <- data.frame(left=c(1, 10, 30), right=c(1, 10, NA)) + > result = fitdistcens(data, 'cauchy', control=list(reltol=1e-14)) + > result + Fitting of the distribution ' cauchy ' on censored data by maximum + likelihood + Parameters: + estimate + location 7.100001 + scale 7.455866 + """ + data = CensoredData(uncensored=[1, 10], right=[30]) + loc, scale = cauchy.fit(data, optimizer=optimizer) + assert_allclose(loc, 7.10001, rtol=5e-6) + assert_allclose(scale, 7.455866, rtol=5e-6) + + +def test_cauchy_mixed(): + """ + Test fitting the Cauchy distribution to data with mixed censoring. + + Calculation in R, with: + * two values not censored [1, 10], + * one left-censored [1], + * one right-censored [30], and + * one interval-censored [[4, 8]]. + + > library(fitdistrplus) + > data <- data.frame(left=c(NA, 1, 4, 10, 30), right=c(1, 1, 8, 10, NA)) + > result = fitdistcens(data, 'cauchy', control=list(reltol=1e-14)) + > result + Fitting of the distribution ' cauchy ' on censored data by maximum + likelihood + Parameters: + estimate + location 4.605150 + scale 5.900852 + """ + data = CensoredData(uncensored=[1, 10], left=[1], right=[30], + interval=[[4, 8]]) + loc, scale = cauchy.fit(data, optimizer=optimizer) + assert_allclose(loc, 4.605150, rtol=5e-6) + assert_allclose(scale, 5.900852, rtol=5e-6) + + +def test_chi2_mixed(): + """ + Test fitting just the shape parameter (df) of chi2 to mixed data. + + Calculation in R, with: + * two values not censored [1, 10], + * one left-censored [1], + * one right-censored [30], and + * one interval-censored [[4, 8]]. + + > library(fitdistrplus) + > data <- data.frame(left=c(NA, 1, 4, 10, 30), right=c(1, 1, 8, 10, NA)) + > result = fitdistcens(data, 'chisq', control=list(reltol=1e-14)) + > result + Fitting of the distribution ' chisq ' on censored data by maximum + likelihood + Parameters: + estimate + df 5.060329 + """ + data = CensoredData(uncensored=[1, 10], left=[1], right=[30], + interval=[[4, 8]]) + df, loc, scale = chi2.fit(data, floc=0, fscale=1, optimizer=optimizer) + assert_allclose(df, 5.060329, rtol=5e-6) + assert loc == 0 + assert scale == 1 + + +def test_expon_right_censored(): + """ + For the exponential distribution with loc=0, the exact solution for + fitting n uncensored points x[0]...x[n-1] and m right-censored points + x[n]..x[n+m-1] is + + scale = sum(x)/n + + That is, divide the sum of all the values (not censored and + right-censored) by the number of uncensored values. (See, for example, + https://en.wikipedia.org/wiki/Censoring_(statistics)#Likelihood.) + + The second derivative of the log-likelihood function is + + n/scale**2 - 2*sum(x)/scale**3 + + from which the estimate of the standard error can be computed. + + ----- + + Calculation in R, for reference only. The R results are not + used in the test. + + > library(fitdistrplus) + > dexps <- function(x, scale) { + + return(dexp(x, 1/scale)) + + } + > pexps <- function(q, scale) { + + return(pexp(q, 1/scale)) + + } + > left <- c(1, 2.5, 3, 6, 7.5, 10, 12, 12, 14.5, 15, + + 16, 16, 20, 20, 21, 22) + > right <- c(1, 2.5, 3, 6, 7.5, 10, 12, 12, 14.5, 15, + + NA, NA, NA, NA, NA, NA) + > result = fitdistcens(data, 'exps', start=list(scale=mean(data$left)), + + control=list(reltol=1e-14)) + > result + Fitting of the distribution ' exps ' on censored data by maximum likelihood + Parameters: + estimate + scale 19.85 + > result$sd + scale + 6.277119 + """ + # This data has 10 uncensored values and 6 right-censored values. + obs = [1, 2.5, 3, 6, 7.5, 10, 12, 12, 14.5, 15, 16, 16, 20, 20, 21, 22] + cens = [False]*10 + [True]*6 + data = CensoredData.right_censored(obs, cens) + + loc, scale = expon.fit(data, floc=0, optimizer=optimizer) + + assert loc == 0 + # Use the analytical solution to compute the expected value. This + # is the sum of the observed values divided by the number of uncensored + # values. + n = len(data) - data.num_censored() + total = data._uncensored.sum() + data._right.sum() + expected = total / n + assert_allclose(scale, expected, 1e-8) + + +def test_gamma_right_censored(): + """ + Fit gamma shape and scale to data with one right-censored value. + + Calculation in R: + + > library(fitdistrplus) + > data <- data.frame(left=c(2.5, 2.9, 3.8, 9.1, 9.3, 12.0, 23.0, 25.0), + + right=c(2.5, 2.9, 3.8, 9.1, 9.3, 12.0, 23.0, NA)) + > result = fitdistcens(data, 'gamma', start=list(shape=1, scale=10), + + control=list(reltol=1e-13)) + > result + Fitting of the distribution ' gamma ' on censored data by maximum + likelihood + Parameters: + estimate + shape 1.447623 + scale 8.360197 + > result$sd + shape scale + 0.7053086 5.1016531 + """ + # The last value is right-censored. + x = CensoredData.right_censored([2.5, 2.9, 3.8, 9.1, 9.3, 12.0, 23.0, + 25.0], + [0]*7 + [1]) + + a, loc, scale = gamma.fit(x, floc=0, optimizer=optimizer) + + assert_allclose(a, 1.447623, rtol=5e-6) + assert loc == 0 + assert_allclose(scale, 8.360197, rtol=5e-6) + + +def test_gumbel(): + """ + Fit gumbel_l and gumbel_r to censored data. + + This R calculation should match gumbel_r. + + > library(evd) + > library(fitdistrplus) + > data = data.frame(left=c(0, 2, 3, 9, 10, 10), + + right=c(1, 2, 3, 9, NA, NA)) + > result = fitdistcens(data, 'gumbel', + + control=list(reltol=1e-14), + + start=list(loc=4, scale=5)) + > result + Fitting of the distribution ' gumbel ' on censored data by maximum + likelihood + Parameters: + estimate + loc 4.487853 + scale 4.843640 + """ + # First value is interval-censored. Last two are right-censored. + uncensored = np.array([2, 3, 9]) + right = np.array([10, 10]) + interval = np.array([[0, 1]]) + data = CensoredData(uncensored, right=right, interval=interval) + loc, scale = gumbel_r.fit(data, optimizer=optimizer) + assert_allclose(loc, 4.487853, rtol=5e-6) + assert_allclose(scale, 4.843640, rtol=5e-6) + + # Negate the data and reverse the intervals, and test with gumbel_l. + data2 = CensoredData(-uncensored, left=-right, + interval=-interval[:, ::-1]) + # Fitting gumbel_l to data2 should give the same result as above, but + # with loc negated. + loc2, scale2 = gumbel_l.fit(data2, optimizer=optimizer) + assert_allclose(loc2, -4.487853, rtol=5e-6) + assert_allclose(scale2, 4.843640, rtol=5e-6) + + +def test_invgauss(): + """ + Fit just the shape parameter of invgauss to data with one value + left-censored and one value right-censored. + + Calculation in R; using a fixed dispersion parameter amounts to fixing + the scale to be 1. + + > library(statmod) + > library(fitdistrplus) + > left <- c(NA, 0.4813096, 0.5571880, 0.5132463, 0.3801414, 0.5904386, + + 0.4822340, 0.3478597, 3, 0.7191797, 1.5810902, 0.4442299) + > right <- c(0.15, 0.4813096, 0.5571880, 0.5132463, 0.3801414, 0.5904386, + + 0.4822340, 0.3478597, NA, 0.7191797, 1.5810902, 0.4442299) + > data <- data.frame(left=left, right=right) + > result = fitdistcens(data, 'invgauss', control=list(reltol=1e-12), + + fix.arg=list(dispersion=1), start=list(mean=3)) + > result + Fitting of the distribution ' invgauss ' on censored data by maximum + likelihood + Parameters: + estimate + mean 0.853469 + Fixed parameters: + value + dispersion 1 + > result$sd + mean + 0.247636 + + Here's the R calculation with the dispersion as a free parameter to + be fit. + + > result = fitdistcens(data, 'invgauss', control=list(reltol=1e-12), + + start=list(mean=3, dispersion=1)) + > result + Fitting of the distribution ' invgauss ' on censored data by maximum + likelihood + Parameters: + estimate + mean 0.8699819 + dispersion 1.2261362 + + The parametrization of the inverse Gaussian distribution in the + `statmod` package is not the same as in SciPy (see + https://arxiv.org/abs/1603.06687 + for details). The translation from R to SciPy is + + scale = 1/dispersion + mu = mean * dispersion + + > 1/result$estimate['dispersion'] # 1/dispersion + dispersion + 0.8155701 + > result$estimate['mean'] * result$estimate['dispersion'] + mean + 1.066716 + + Those last two values are the SciPy scale and shape parameters. + """ + # One point is left-censored, and one is right-censored. + x = [0.4813096, 0.5571880, 0.5132463, 0.3801414, + 0.5904386, 0.4822340, 0.3478597, 0.7191797, + 1.5810902, 0.4442299] + data = CensoredData(uncensored=x, left=[0.15], right=[3]) + + # Fit only the shape parameter. + mu, loc, scale = invgauss.fit(data, floc=0, fscale=1, optimizer=optimizer) + + assert_allclose(mu, 0.853469, rtol=5e-5) + assert loc == 0 + assert scale == 1 + + # Fit the shape and scale. + mu, loc, scale = invgauss.fit(data, floc=0, optimizer=optimizer) + + assert_allclose(mu, 1.066716, rtol=5e-5) + assert loc == 0 + assert_allclose(scale, 0.8155701, rtol=5e-5) + + +def test_invweibull(): + """ + Fit invweibull to censored data. + + Here is the calculation in R. The 'frechet' distribution from the evd + package matches SciPy's invweibull distribution. The `loc` parameter + is fixed at 0. + + > library(evd) + > library(fitdistrplus) + > data = data.frame(left=c(0, 2, 3, 9, 10, 10), + + right=c(1, 2, 3, 9, NA, NA)) + > result = fitdistcens(data, 'frechet', + + control=list(reltol=1e-14), + + start=list(loc=4, scale=5)) + > result + Fitting of the distribution ' frechet ' on censored data by maximum + likelihood + Parameters: + estimate + scale 2.7902200 + shape 0.6379845 + Fixed parameters: + value + loc 0 + """ + # In the R data, the first value is interval-censored, and the last + # two are right-censored. The rest are not censored. + data = CensoredData(uncensored=[2, 3, 9], right=[10, 10], + interval=[[0, 1]]) + c, loc, scale = invweibull.fit(data, floc=0, optimizer=optimizer) + assert_allclose(c, 0.6379845, rtol=5e-6) + assert loc == 0 + assert_allclose(scale, 2.7902200, rtol=5e-6) + + +def test_laplace(): + """ + Fir the Laplace distribution to left- and right-censored data. + + Calculation in R: + + > library(fitdistrplus) + > dlaplace <- function(x, location=0, scale=1) { + + return(0.5*exp(-abs((x - location)/scale))/scale) + + } + > plaplace <- function(q, location=0, scale=1) { + + z <- (q - location)/scale + + s <- sign(z) + + f <- -s*0.5*exp(-abs(z)) + (s+1)/2 + + return(f) + + } + > left <- c(NA, -41.564, 50.0, 15.7384, 50.0, 10.0452, -2.0684, + + -19.5399, 50.0, 9.0005, 27.1227, 4.3113, -3.7372, + + 25.3111, 14.7987, 34.0887, 50.0, 42.8496, 18.5862, + + 32.8921, 9.0448, -27.4591, NA, 19.5083, -9.7199) + > right <- c(-50.0, -41.564, NA, 15.7384, NA, 10.0452, -2.0684, + + -19.5399, NA, 9.0005, 27.1227, 4.3113, -3.7372, + + 25.3111, 14.7987, 34.0887, NA, 42.8496, 18.5862, + + 32.8921, 9.0448, -27.4591, -50.0, 19.5083, -9.7199) + > data <- data.frame(left=left, right=right) + > result <- fitdistcens(data, 'laplace', start=list(location=10, scale=10), + + control=list(reltol=1e-13)) + > result + Fitting of the distribution ' laplace ' on censored data by maximum + likelihood + Parameters: + estimate + location 14.79870 + scale 30.93601 + > result$sd + location scale + 0.1758864 7.0972125 + """ + # The value -50 is left-censored, and the value 50 is right-censored. + obs = np.array([-50.0, -41.564, 50.0, 15.7384, 50.0, 10.0452, -2.0684, + -19.5399, 50.0, 9.0005, 27.1227, 4.3113, -3.7372, + 25.3111, 14.7987, 34.0887, 50.0, 42.8496, 18.5862, + 32.8921, 9.0448, -27.4591, -50.0, 19.5083, -9.7199]) + x = obs[(obs != -50.0) & (obs != 50)] + left = obs[obs == -50.0] + right = obs[obs == 50.0] + data = CensoredData(uncensored=x, left=left, right=right) + loc, scale = laplace.fit(data, loc=10, scale=10, optimizer=optimizer) + assert_allclose(loc, 14.79870, rtol=5e-6) + assert_allclose(scale, 30.93601, rtol=5e-6) + + +def test_logistic(): + """ + Fit the logistic distribution to left-censored data. + + Calculation in R: + > library(fitdistrplus) + > left = c(13.5401, 37.4235, 11.906 , 13.998 , NA , 0.4023, NA , + + 10.9044, 21.0629, 9.6985, NA , 12.9016, 39.164 , 34.6396, + + NA , 20.3665, 16.5889, 18.0952, 45.3818, 35.3306, 8.4949, + + 3.4041, NA , 7.2828, 37.1265, 6.5969, 17.6868, 17.4977, + + 16.3391, 36.0541) + > right = c(13.5401, 37.4235, 11.906 , 13.998 , 0. , 0.4023, 0. , + + 10.9044, 21.0629, 9.6985, 0. , 12.9016, 39.164 , 34.6396, + + 0. , 20.3665, 16.5889, 18.0952, 45.3818, 35.3306, 8.4949, + + 3.4041, 0. , 7.2828, 37.1265, 6.5969, 17.6868, 17.4977, + + 16.3391, 36.0541) + > data = data.frame(left=left, right=right) + > result = fitdistcens(data, 'logis', control=list(reltol=1e-14)) + > result + Fitting of the distribution ' logis ' on censored data by maximum + likelihood + Parameters: + estimate + location 14.633459 + scale 9.232736 + > result$sd + location scale + 2.931505 1.546879 + """ + # Values that are zero are left-censored; the true values are less than 0. + x = np.array([13.5401, 37.4235, 11.906, 13.998, 0.0, 0.4023, 0.0, 10.9044, + 21.0629, 9.6985, 0.0, 12.9016, 39.164, 34.6396, 0.0, 20.3665, + 16.5889, 18.0952, 45.3818, 35.3306, 8.4949, 3.4041, 0.0, + 7.2828, 37.1265, 6.5969, 17.6868, 17.4977, 16.3391, + 36.0541]) + data = CensoredData.left_censored(x, censored=(x == 0)) + loc, scale = logistic.fit(data, optimizer=optimizer) + assert_allclose(loc, 14.633459, rtol=5e-7) + assert_allclose(scale, 9.232736, rtol=5e-6) + + +def test_lognorm(): + """ + Ref: https://math.montana.edu/jobo/st528/documents/relc.pdf + + The data is the locomotive control time to failure example that starts + on page 8. That's the 8th page in the PDF; the page number shown in + the text is 270). + The document includes SAS output for the data. + """ + # These are the uncensored measurements. There are also 59 right-censored + # measurements where the lower bound is 135. + miles_to_fail = [22.5, 37.5, 46.0, 48.5, 51.5, 53.0, 54.5, 57.5, 66.5, + 68.0, 69.5, 76.5, 77.0, 78.5, 80.0, 81.5, 82.0, 83.0, + 84.0, 91.5, 93.5, 102.5, 107.0, 108.5, 112.5, 113.5, + 116.0, 117.0, 118.5, 119.0, 120.0, 122.5, 123.0, 127.5, + 131.0, 132.5, 134.0] + + data = CensoredData.right_censored(miles_to_fail + [135]*59, + [0]*len(miles_to_fail) + [1]*59) + sigma, loc, scale = lognorm.fit(data, floc=0) + + assert loc == 0 + # Convert the lognorm parameters to the mu and sigma of the underlying + # normal distribution. + mu = np.log(scale) + # The expected results are from the 17th page of the PDF document + # (labeled page 279), in the SAS output on the right side of the page. + assert_allclose(mu, 5.1169, rtol=5e-4) + assert_allclose(sigma, 0.7055, rtol=5e-3) + + +def test_nct(): + """ + Test fitting the noncentral t distribution to censored data. + + Calculation in R: + + > library(fitdistrplus) + > data <- data.frame(left=c(1, 2, 3, 5, 8, 10, 25, 25), + + right=c(1, 2, 3, 5, 8, 10, NA, NA)) + > result = fitdistcens(data, 't', control=list(reltol=1e-14), + + start=list(df=1, ncp=2)) + > result + Fitting of the distribution ' t ' on censored data by maximum likelihood + Parameters: + estimate + df 0.5432336 + ncp 2.8893565 + + """ + data = CensoredData.right_censored([1, 2, 3, 5, 8, 10, 25, 25], + [0, 0, 0, 0, 0, 0, 1, 1]) + # Fit just the shape parameter df and nc; loc and scale are fixed. + with np.errstate(over='ignore'): # remove context when gh-14901 is closed + df, nc, loc, scale = nct.fit(data, floc=0, fscale=1, + optimizer=optimizer) + assert_allclose(df, 0.5432336, rtol=5e-6) + assert_allclose(nc, 2.8893565, rtol=5e-6) + assert loc == 0 + assert scale == 1 + + +def test_ncx2(): + """ + Test fitting the shape parameters (df, ncp) of ncx2 to mixed data. + + Calculation in R, with + * 5 not censored values [2.7, 0.2, 6.5, 0.4, 0.1], + * 1 interval-censored value [[0.6, 1.0]], and + * 2 right-censored values [8, 8]. + + > library(fitdistrplus) + > data <- data.frame(left=c(2.7, 0.2, 6.5, 0.4, 0.1, 0.6, 8, 8), + + right=c(2.7, 0.2, 6.5, 0.4, 0.1, 1.0, NA, NA)) + > result = fitdistcens(data, 'chisq', control=list(reltol=1e-14), + + start=list(df=1, ncp=2)) + > result + Fitting of the distribution ' chisq ' on censored data by maximum + likelihood + Parameters: + estimate + df 1.052871 + ncp 2.362934 + """ + data = CensoredData(uncensored=[2.7, 0.2, 6.5, 0.4, 0.1], right=[8, 8], + interval=[[0.6, 1.0]]) + with np.errstate(over='ignore'): # remove context when gh-14901 is closed + df, ncp, loc, scale = ncx2.fit(data, floc=0, fscale=1, + optimizer=optimizer) + assert_allclose(df, 1.052871, rtol=5e-6) + assert_allclose(ncp, 2.362934, rtol=5e-6) + assert loc == 0 + assert scale == 1 + + +def test_norm(): + """ + Test fitting the normal distribution to interval-censored data. + + Calculation in R: + + > library(fitdistrplus) + > data <- data.frame(left=c(0.10, 0.50, 0.75, 0.80), + + right=c(0.20, 0.55, 0.90, 0.95)) + > result = fitdistcens(data, 'norm', control=list(reltol=1e-14)) + + > result + Fitting of the distribution ' norm ' on censored data by maximum likelihood + Parameters: + estimate + mean 0.5919990 + sd 0.2868042 + > result$sd + mean sd + 0.1444432 0.1029451 + """ + data = CensoredData(interval=[[0.10, 0.20], + [0.50, 0.55], + [0.75, 0.90], + [0.80, 0.95]]) + + loc, scale = norm.fit(data, optimizer=optimizer) + + assert_allclose(loc, 0.5919990, rtol=5e-6) + assert_allclose(scale, 0.2868042, rtol=5e-6) + + +def test_weibull_censored1(): + # Ref: http://www.ams.sunysb.edu/~zhu/ams588/Lecture_3_likelihood.pdf + + # Survival times; '*' indicates right-censored. + s = "3,5,6*,8,10*,11*,15,20*,22,23,27*,29,32,35,40,26,28,33*,21,24*" + + times, cens = zip(*[(float(t[0]), len(t) == 2) + for t in [w.split('*') for w in s.split(',')]]) + data = CensoredData.right_censored(times, cens) + + c, loc, scale = weibull_min.fit(data, floc=0) + + # Expected values are from the reference. + assert_allclose(c, 2.149, rtol=1e-3) + assert loc == 0 + assert_allclose(scale, 28.99, rtol=1e-3) + + # Flip the sign of the data, and make the censored values + # left-censored. We should get the same parameters when we fit + # weibull_max to the flipped data. + data2 = CensoredData.left_censored(-np.array(times), cens) + + c2, loc2, scale2 = weibull_max.fit(data2, floc=0) + + assert_allclose(c2, 2.149, rtol=1e-3) + assert loc2 == 0 + assert_allclose(scale2, 28.99, rtol=1e-3) + + +def test_weibull_min_sas1(): + # Data and SAS results from + # https://support.sas.com/documentation/cdl/en/qcug/63922/HTML/default/ + # viewer.htm#qcug_reliability_sect004.htm + + text = """ + 450 0 460 1 1150 0 1150 0 1560 1 + 1600 0 1660 1 1850 1 1850 1 1850 1 + 1850 1 1850 1 2030 1 2030 1 2030 1 + 2070 0 2070 0 2080 0 2200 1 3000 1 + 3000 1 3000 1 3000 1 3100 0 3200 1 + 3450 0 3750 1 3750 1 4150 1 4150 1 + 4150 1 4150 1 4300 1 4300 1 4300 1 + 4300 1 4600 0 4850 1 4850 1 4850 1 + 4850 1 5000 1 5000 1 5000 1 6100 1 + 6100 0 6100 1 6100 1 6300 1 6450 1 + 6450 1 6700 1 7450 1 7800 1 7800 1 + 8100 1 8100 1 8200 1 8500 1 8500 1 + 8500 1 8750 1 8750 0 8750 1 9400 1 + 9900 1 10100 1 10100 1 10100 1 11500 1 + """ + + life, cens = np.array([int(w) for w in text.split()]).reshape(-1, 2).T + life = life/1000.0 + + data = CensoredData.right_censored(life, cens) + + c, loc, scale = weibull_min.fit(data, floc=0, optimizer=optimizer) + assert_allclose(c, 1.0584, rtol=1e-4) + assert_allclose(scale, 26.2968, rtol=1e-5) + assert loc == 0 + + +def test_weibull_min_sas2(): + # http://support.sas.com/documentation/cdl/en/ormpug/67517/HTML/default/ + # viewer.htm#ormpug_nlpsolver_examples06.htm + + # The last two values are right-censored. + days = np.array([143, 164, 188, 188, 190, 192, 206, 209, 213, 216, 220, + 227, 230, 234, 246, 265, 304, 216, 244]) + + data = CensoredData.right_censored(days, [0]*(len(days) - 2) + [1]*2) + + c, loc, scale = weibull_min.fit(data, 1, loc=100, scale=100, + optimizer=optimizer) + + assert_allclose(c, 2.7112, rtol=5e-4) + assert_allclose(loc, 122.03, rtol=5e-4) + assert_allclose(scale, 108.37, rtol=5e-4) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/test_correlation.py b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_correlation.py new file mode 100644 index 0000000000000000000000000000000000000000..fdc8abb068dd92b71aeb3081a895045c24faebe0 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_correlation.py @@ -0,0 +1,423 @@ +import pytest +import numpy as np +from numpy.testing import assert_allclose + +from scipy.conftest import skip_xp_invalid_arg +import scipy._external.array_api_extra as xpx +from scipy._lib._array_api import (make_xp_test_case, xp_default_dtype, is_jax, is_cupy, + eager_warns, xp_result_type, is_array_api_strict) +from scipy._lib._array_api_no_0d import xp_assert_close, xp_assert_equal +from scipy import stats +from scipy.stats._axis_nan_policy import SmallSampleWarning + + +lazy_xp_modules = [stats] + + +@make_xp_test_case(stats.chatterjeexi) +class TestChatterjeeXi: + @pytest.mark.parametrize('case', [ + dict(y_cont=True, statistic=-0.303030303030303, pvalue=0.9351329808526656), + dict(y_cont=False, statistic=0.07407407407407396, pvalue=0.3709859367123997)]) + @pytest.mark.parametrize('dtype', ['float32', 'float64', None]) + def test_against_R_XICOR(self, case, dtype, xp): + # Test against R package XICOR, e.g. + # library(XICOR) + # options(digits=16) + # x = c(0.11027287231363914, 0.8154770102474279, 0.7073943466920335, + # 0.6651317324378386, 0.6905752850115503, 0.06115250587536558, + # 0.5209906494474178, 0.3155763519785274, 0.18405731803625924, + # 0.8613557911541495) + # y = c(0.8402081904493103, 0.5946972833914318, 0.23481606164114155, + # 0.49754786197715384, 0.9146460831206026, 0.5848057749217579, + # 0.7620801065573549, 0.31410063302647495, 0.7935620302236199, + # 0.5423085761365468) + # xicor(x, y, ties=FALSE, pvalue=TRUE) + dtype = xp_default_dtype(xp) if dtype is None else getattr(xp, dtype) + rng = np.random.default_rng(25982435982346983) + x = rng.random(size=10) + y = (rng.random(size=10) if case['y_cont'] + else rng.integers(0, 5, size=10)) + + x, y = xp.asarray(x, dtype=dtype), xp.asarray(y, dtype=dtype) + res = stats.chatterjeexi(x, y, y_continuous=case['y_cont']) + + xp_assert_close(res.statistic, xp.asarray(case['statistic'], dtype=dtype)) + xp_assert_close(res.pvalue, xp.asarray(case['pvalue'], dtype=dtype)) + + @pytest.mark.parametrize('y_continuous', (False, True)) + def test_permutation_asymptotic(self, y_continuous): + # XICOR doesn't seem to perform the permutation test as advertised, so + # compare the result of a permutation test against an asymptotic test. + rng = np.random.default_rng(2524579827426) + n = np.floor(rng.uniform(100, 150)).astype(int) + shape = (2, n) + x = rng.random(size=shape) + y = (rng.random(size=shape) if y_continuous + else rng.integers(0, 10, size=shape)) + method = stats.PermutationMethod(rng=rng) + res = stats.chatterjeexi(x, y, method=method, + y_continuous=y_continuous, axis=-1) + ref = stats.chatterjeexi(x, y, y_continuous=y_continuous, axis=-1) + np.testing.assert_allclose(res.statistic, ref.statistic, rtol=1e-15) + np.testing.assert_allclose(res.pvalue, ref.pvalue, rtol=2e-2) + + def test_input_validation(self, xp): + rng = np.random.default_rng(25932435798274926) + x, y = rng.random(size=(2, 10)) + x, y = xp.asarray(x), xp.asarray(y) + + message = 'Array shapes are incompatible for broadcasting.|Incompatible shapes' + with pytest.raises((ValueError, TypeError), match=message): + stats.chatterjeexi(x, y[:-1]) + + if not (is_jax(xp) or is_cupy(xp)): + # jax misses out on some input validation from _axis_nan_policy decorator + # This also fails with cupy for reasons that need to be investigated. + message = '...axis 10 is out of bounds for array...|out of range' + with pytest.raises((ValueError, IndexError), match=message): + stats.chatterjeexi(x, y, axis=10) + + message = '`y_continuous` must be boolean.' + with pytest.raises(ValueError, match=message): + stats.chatterjeexi(x, y, y_continuous='a herring') + + message = "`method` must be 'asymptotic' or" + with pytest.raises(ValueError, match=message): + stats.chatterjeexi(x, y, method='ekki ekii') + + def test_special_cases(self, xp): + message = 'One or more sample arguments is too small...' + with pytest.warns(SmallSampleWarning, match=message): + res = stats.chatterjeexi(xp.asarray([1]), xp.asarray([2])) + + assert xp.isnan(res.statistic) + assert xp.isnan(res.pvalue) + + +@make_xp_test_case(stats.spearmanrho) +class TestSpearmanRho: + @pytest.mark.parametrize('alternative, statistic, pvalue', [ + ('two-sided', -0.2727272727272727, 0.4458383415428), + ('greater', -0.2727272727272727, 0.7770808292286), + ('less', -0.2727272727272727, 0.2229191707714), + ]) + @pytest.mark.parametrize('dtype', ['float32', 'float64', None]) + def test_against_R_cor_test(self, alternative, statistic, pvalue, dtype, xp): + # Test against R cor.test, e.g. + # options(digits=16) + # x = c(0.11027287231363914, 0.8154770102474279, 0.7073943466920335, + # 0.6651317324378386, 0.6905752850115503, 0.06115250587536558, + # 0.5209906494474178, 0.3155763519785274, 0.18405731803625924, + # 0.8613557911541495) + # y = c(0.8402081904493103, 0.5946972833914318, 0.23481606164114155, + # 0.49754786197715384, 0.9146460831206026, 0.5848057749217579, + # 0.7620801065573549, 0.31410063302647495, 0.7935620302236199, + # 0.5423085761365468) + # cor.test(x, y, method='spearman', alternative='t', exact=FALSE) + dtype = xp_default_dtype(xp) if dtype is None else getattr(xp, dtype) + rng = np.random.default_rng(25982435982346983) + x = rng.random(size=10) + y = rng.random(size=10) + + x, y = xp.asarray(x, dtype=dtype), xp.asarray(y, dtype=dtype) + res = stats.spearmanrho(x, y, alternative=alternative) + + xp_assert_close(res.statistic, xp.asarray(statistic, dtype=dtype)) + xp_assert_close(res.pvalue, xp.asarray(pvalue, dtype=dtype)) + + + @pytest.mark.parametrize('alternative, statistic, pvalue', [ + ('two-sided', -0.4857142857142857, 0.3555555555556), + ('greater', -0.4857142857142857, 0.8513888888889), + ('less', -0.4857142857142857, 0.1777777777778), + ]) + def test_against_R_cor_test_exact(self, alternative, statistic, pvalue): + xp = np # will test for multiple backends when gh-23772 merges + # Test against R cor.test exact=TRUE, e.g. + # options(digits=16) + # x = c(0.11027287231363914, 0.8154770102474279, 0.7073943466920335, + # 0.6651317324378386, 0.6905752850115503, 0.06115250587536558) + # y = c(0.5209906494474178, 0.3155763519785274, 0.18405731803625924, + # 0.8613557911541495, 0.8402081904493103, 0.5946972833914318) + # cor.test(x, y, method='spearman', alternative='t', exact=TRUE) + rng = np.random.default_rng(25982435982346983) + x = rng.random(size=6) + y = rng.random(size=6) + x, y = xp.asarray(x), xp.asarray(y) + + method = stats.PermutationMethod() + res = stats.spearmanrho(x, y, method=method, alternative=alternative) + + xp_assert_close(res.statistic, xp.asarray(statistic)) + xp_assert_close(res.pvalue, xp.asarray(pvalue)) + + + @pytest.mark.parametrize('alternative', ('two-sided', 'greater', 'less')) + @pytest.mark.parametrize('n', [9, 99, 999]) + def test_against_scipy_spearmanr(self, alternative, n, xp): + rng = np.random.default_rng(5982435982346983) + x = rng.integers(n//2, size=n) + y = rng.integers(n//2, size=n) + dtype = xp_default_dtype(xp) + + ref = stats.spearmanr(x, y, alternative=alternative) + ref_statistic = xp.asarray(ref.statistic, dtype=dtype) + ref_pvalue = xp.asarray(ref.pvalue, dtype=dtype) + + x = xp.asarray(x, dtype=dtype) + y = xp.asarray(y, dtype=dtype) + res = stats.spearmanrho(x, y, alternative=alternative) + + xp_assert_close(res.statistic, ref_statistic) + xp_assert_close(res.pvalue, ref_pvalue) + + + @pytest.mark.parametrize('axis', [-1, 0, 1]) + def test_other_backends_nd(self, axis, xp): + # NumPy n-d behavior is tested by `test_axis_nan_policy`; + # check other backends against it. + rng = np.random.default_rng(8243598346983259) + shape = (8, 9, 10) + x = rng.standard_normal(size=shape) + y = rng.standard_normal(size=shape) + res = stats.spearmanrho(xp.asarray(x), xp.asarray(y), axis=axis) + ref = stats.spearmanrho(x, y, axis=axis) + xp_assert_close(res.statistic, xp.asarray(ref.statistic), atol=1e-16) + xp_assert_close(res.pvalue, xp.asarray(ref.pvalue), atol=1e-16) + + + def test_input_validation(self, xp): + rng = np.random.default_rng(25932435798274926) + x, y = rng.random(size=(2, 10)) + x, y = xp.asarray(x), xp.asarray(y) + + msg = 'incompatible for broadcasting|Incompatible shapes|must be broadcastable' + with pytest.raises((ValueError, TypeError), match=msg): + stats.spearmanrho(x, y[:-1]) + + if not is_jax(xp): + # jax misses out on some input validation from _axis_nan_policy decorator + message = '...axis 10 is out of bounds for array...|out of range' + with pytest.raises((ValueError, IndexError), match=message): + stats.spearmanrho(x, y, axis=10) + + message = "`alternative` must be 'less', 'greater', or 'two-sided'." + with pytest.raises(ValueError, match=message): + stats.spearmanrho(x, y, alternative='alternative') + + message = ("`method` must be...") + with pytest.raises(ValueError, match=message): + stats.spearmanrho(x, y, method='method') + + def test_special_cases(self, xp): + def check_nan(res): + assert xp.isnan(res.statistic) + assert xp.isnan(res.pvalue) + + message = 'One or more sample arguments is too small...' + with eager_warns(SmallSampleWarning, match=message, xp=xp): + res = stats.spearmanrho(xp.asarray([1]), xp.asarray([2])) + check_nan(res) + + x = xp.asarray([1, 1, 1, 1, 1]) + y = xp.asarray([1, 2, 3, 4, 5]) + message = 'An input array is constant; the correlation coefficient...' + with eager_warns(stats.ConstantInputWarning, match=message, xp=xp): + res = stats.spearmanrho(x, y) + check_nan(res) + with eager_warns(stats.ConstantInputWarning, match=message, xp=xp): + res = stats.spearmanrho(y, x) + check_nan(res) + + +class RobustSlopesTest: + # 'iv, mask, warnings, consistency, gh19678, mstats' + def test_input_validation(self, xp): + pfun = getattr(stats, self.pfun) + other_method = 'joint' if pfun == stats.theilslopes else 'hierarchical' + msg = (f"method must be either '{other_method}' or 'separate'. " + "'joint_separate' is invalid.") + with pytest.raises(ValueError, match=msg): + pfun(xp.asarray([0, 1, 1]), method='joint_separate') + + @skip_xp_invalid_arg + @pytest.mark.parametrize("method", ['separate', 'other']) + def test_mask(self, method): + pfun = getattr(stats, self.pfun) + if method == 'other': + method = 'joint' if self.pfun =='theilslopes' else 'hierarchical' + + # Test for correct masking. + mask = np.asarray([False, False, True, False]) + y = np.ma.array([0, 1, 100, 1], mask=mask) + res = pfun(y, method=method) + ref = pfun(y[~mask], method=method) + + assert_allclose(res.slope, ref.slope) + assert_allclose(res.intercept, ref.intercept) + + def test_degenerate(self, xp): + # Test with degenerate input; see gh-15943 + pfun = getattr(stats, self.pfun) + res = pfun(xp.asarray([0, 1]), xp.asarray([0, 0])) + assert xp.all(xp.isnan(xp.stack(res))) + res = pfun(xp.asarray([0, 0, 0]), xp.asarray([0, 1, 0])) + xp_assert_equal(res[0], xp.asarray(0.)) + xp_assert_equal(res[1], xp.asarray(0.)) + if pfun == stats.theilslopes: + xp_assert_equal(res[2], xp.asarray(xp.nan)) + xp_assert_equal(res[3], xp.asarray(xp.nan)) + + def test_namedtuple_consistency(self, xp): + """ + Simple test to ensure tuple backwards-compatibility of the returned object. + """ + pfun = getattr(stats, self.pfun) + + y = xp.asarray([1, 2, 4]) + x = xp.asarray([4, 6, 8]) + + result = pfun(y, x) + + # note both returned values are distinct here + xp_assert_equal(result[0], result.slope) + xp_assert_equal(result[1], result.intercept) + if pfun == stats.theilslopes: + xp_assert_equal(result[2], result.low_slope) + xp_assert_equal(result[3], result.high_slope) + + def test_gh19678_uint8(self, xp): + # `theilslopes` returned unexpected results when `y` was an unsigned type. + # Check that this is resolved. + pfun = getattr(stats, self.pfun) + rng = np.random.default_rng(2549824598234528) + y = xp.asarray(rng.integers(0, 255, size=10, dtype=np.uint8)) + res = pfun(y, y) + xp_assert_close(res.slope, xp.asarray(1.)) + + @pytest.mark.parametrize("method", ['separate', 'other']) + @pytest.mark.parametrize("case_number", [0, 1, 2, 3]) + def test_against_mstats(self, method, case_number, xp): + pfun = getattr(stats, self.pfun) + if method == 'other': + method = 'joint' if self.pfun =='theilslopes' else 'hierarchical' + + rng = np.random.default_rng(349824598234528554) + match case_number: + case 0: # no x + x = None + y = rng.random(100) + case 1: # no ties + x = rng.random(100) + y = rng.random(100) + case 2: # no x ties + x = rng.random(100) + y = rng.integers(50, size=100) + case 3: # ties in x and y + x = rng.integers(25, size=100) + y = rng.integers(50, size=100) + + ref = pfun(y, x, method=method) + + y, x = xp.asarray(y), x if x is None else xp.asarray(x) + if (is_array_api_strict(xp) and xp.isdtype(y.dtype, 'integral') + and xp.isdtype(x.dtype, 'real floating')): + # array API strict doesn't allow mixed type promotion, so promote for it + y = xp.astype(y, xp.float64) + + dtype = xp_result_type(y, x, force_floating=True, xp=xp) + + res = pfun(y, x, method=method, axis=-1) + + assert res.slope != 0 # ensure randomly generated test case isn't trivial + xp_assert_close(res.slope, xp.asarray(ref.slope, dtype=dtype)) + xp_assert_close(res.intercept, xp.asarray(ref.intercept, dtype=dtype)) + if pfun == stats.theilslopes: + xp_assert_close(res.low_slope, xp.asarray(ref.low_slope, dtype=dtype)) + xp_assert_close(res.high_slope, xp.asarray(ref.high_slope, dtype=dtype)) + + +@make_xp_test_case(stats.theilslopes) +class TestTheilslopes(RobustSlopesTest): + pfun = 'theilslopes' + def test_theilslopes(self, xp): + # Test for basic slope and intercept. + y = xp.asarray([0, 1, 1]) + slope, intercept, lower, upper = stats.theilslopes(y) + xp_assert_close(slope, xp.asarray(0.5)) + xp_assert_close(intercept, xp.asarray(0.5)) + + slope, intercept, lower, upper = stats.theilslopes(y, method='joint') + xp_assert_close(slope, xp.asarray(0.5)) + xp_assert_close(intercept, xp.asarray(0.0)) + + # Test of confidence intervals from example in Sen (1968). + x = xp.asarray([1, 2, 3, 4, 10, 12, 18]) + y = xp.asarray([9, 15, 19, 20, 45, 55, 78]) + slope, intercept, lower, upper = stats.theilslopes(y, x, 0.07) + xp_assert_close(slope, xp.asarray(4.0)) + xp_assert_close(intercept, xp.asarray(4.0)) + xp_assert_close(upper, xp.asarray(4.38), rtol=5e-3) + xp_assert_close(lower, xp.asarray(3.71), rtol=5e-3) + + slope, intercept, lower, upper = stats.theilslopes(y, x, 0.07, + method='joint') + xp_assert_close(slope, xp.asarray(4.0)) + xp_assert_close(intercept, xp.asarray(6.0)) + xp_assert_close(upper, xp.asarray(4.38), rtol=5e-3) + xp_assert_close(lower, xp.asarray(3.71), rtol=5e-3) + + +@make_xp_test_case(stats.siegelslopes) +class TestSiegelslopes(RobustSlopesTest): + pfun = 'siegelslopes' + def test_siegelslopes(self, xp): + # method should be exact for straight line + y = 2 * xp.arange(10.) + 0.5 + slope, intercept = stats.siegelslopes(y) + xp_assert_close(slope, xp.asarray(2.0)) + xp_assert_close(intercept, xp.asarray(0.5)) + slope, intercept = stats.siegelslopes(y, method='separate') + xp_assert_close(slope, xp.asarray(2.0)) + xp_assert_close(intercept, xp.asarray(0.5)) + + x = 2 * xp.arange(10.) + y = 5 * x - 3.0 + slope, intercept = stats.siegelslopes(y, x) + xp_assert_close(slope, xp.asarray(5.0)) + xp_assert_close(intercept, xp.asarray(-3.0)) + slope, intercept = stats.siegelslopes(y, x, method='separate') + xp_assert_close(slope, xp.asarray(5.0)) + xp_assert_close(intercept, xp.asarray(-3.0)) + + # method is robust to outliers: breakdown point of 50% + y = xpx.at(y)[:4].set(1000.) + xp_assert_close(slope, xp.asarray(5.0)) + xp_assert_close(intercept, xp.asarray(-3.0)) + + # if there are no outliers, results should be comparable to linregress + x = np.arange(10.) + y = -2.3 + 0.3 * x + stats.norm.rvs(size=10, random_state=231) + slope_ols, intercept_ols, _, _, _ = stats.linregress(x, y) + + y, x = xp.asarray(y), xp.asarray(x) + slope, intercept = stats.siegelslopes(y, x) + xp_assert_close(slope, xp.asarray(slope_ols), rtol=0.1) + xp_assert_close(intercept, xp.asarray(intercept_ols), rtol=0.1) + + slope, intercept = stats.siegelslopes(y, x, method='separate') + xp_assert_close(slope, xp.asarray(slope_ols), rtol=0.1) + xp_assert_close(intercept, xp.asarray(intercept_ols), rtol=0.1) + + def test_method(self, xp): + # Test that distinguishes between the methods. Reference values generated with + # SciPy 1.17 (results unchanged since at least 1.12, so assumed "correct") to + # detect *changes*. + y = xp.asarray([2, 1, 3, 0, 4]) + slope, intercept = stats.siegelslopes(y, method='separate') + xp_assert_close(slope, xp.asarray(0.25)) + xp_assert_close(intercept, xp.asarray(1.75)) + slope, intercept = stats.siegelslopes(y, method='hierarchical') + xp_assert_close(slope, xp.asarray(0.25)) # always the same between the methods + xp_assert_close(intercept, xp.asarray(2.0)) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/test_crosstab.py b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_crosstab.py new file mode 100644 index 0000000000000000000000000000000000000000..42dc525045b6e455c0bf498adcac5faabe2106ff --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_crosstab.py @@ -0,0 +1,118 @@ +import pytest +import numpy as np +from numpy.testing import assert_array_equal, assert_equal +from scipy.stats.contingency import crosstab + + +@pytest.mark.parametrize('sparse', [False, True]) +def test_crosstab_basic(sparse): + a = [0, 0, 9, 9, 0, 0, 9] + b = [2, 1, 3, 1, 2, 3, 3] + expected_avals = [0, 9] + expected_bvals = [1, 2, 3] + expected_count = np.array([[1, 2, 1], + [1, 0, 2]]) + (avals, bvals), count = crosstab(a, b, sparse=sparse) + assert_array_equal(avals, expected_avals) + assert_array_equal(bvals, expected_bvals) + if sparse: + assert_array_equal(count.toarray(), expected_count) + else: + assert_array_equal(count, expected_count) + + +@pytest.mark.parametrize('sparse', [False, True]) +def test_crosstab_basic_1d(sparse): + # Verify that a single input sequence works as expected. + x = [1, 2, 3, 1, 2, 3, 3] + expected_xvals = [1, 2, 3] + expected_count = np.array([2, 2, 3]) + (xvals,), count = crosstab(x, sparse=sparse) + assert_array_equal(xvals, expected_xvals) + if sparse: + assert_array_equal(count.toarray(), expected_count) + else: + assert_array_equal(count, expected_count) + + +@pytest.mark.parametrize('sparse', [False, True]) +def test_crosstab_basic_3d(sparse): + # Verify the function for three input sequences. + a = 'a' + b = 'b' + x = [0, 0, 9, 9, 0, 0, 9, 9] + y = [a, a, a, a, b, b, b, a] + z = [1, 2, 3, 1, 2, 3, 3, 1] + expected_xvals = [0, 9] + expected_yvals = [a, b] + expected_zvals = [1, 2, 3] + expected_count = np.array([[[1, 1, 0], + [0, 1, 1]], + [[2, 0, 1], + [0, 0, 1]]]) + (xvals, yvals, zvals), count = crosstab(x, y, z, sparse=sparse) + assert_array_equal(xvals, expected_xvals) + assert_array_equal(yvals, expected_yvals) + assert_array_equal(zvals, expected_zvals) + if sparse: + assert_array_equal(count.toarray(), expected_count) + else: + assert_array_equal(count, expected_count) + + +@pytest.mark.parametrize('sparse', [False, True]) +def test_crosstab_levels(sparse): + a = [0, 0, 9, 9, 0, 0, 9] + b = [1, 2, 3, 1, 2, 3, 3] + expected_avals = [0, 9] + expected_bvals = [0, 1, 2, 3] + expected_count = np.array([[0, 1, 2, 1], + [0, 1, 0, 2]]) + (avals, bvals), count = crosstab(a, b, levels=[None, [0, 1, 2, 3]], + sparse=sparse) + assert_array_equal(avals, expected_avals) + assert_array_equal(bvals, expected_bvals) + if sparse: + assert_array_equal(count.toarray(), expected_count) + else: + assert_array_equal(count, expected_count) + + +@pytest.mark.parametrize('sparse', [False, True]) +def test_crosstab_extra_levels(sparse): + # The pair of values (-1, 3) will be ignored, because we explicitly + # request the counted `a` values to be [0, 9]. + a = [0, 0, 9, 9, 0, 0, 9, -1] + b = [1, 2, 3, 1, 2, 3, 3, 3] + expected_avals = [0, 9] + expected_bvals = [0, 1, 2, 3] + expected_count = np.array([[0, 1, 2, 1], + [0, 1, 0, 2]]) + (avals, bvals), count = crosstab(a, b, levels=[[0, 9], [0, 1, 2, 3]], + sparse=sparse) + assert_array_equal(avals, expected_avals) + assert_array_equal(bvals, expected_bvals) + if sparse: + assert_array_equal(count.toarray(), expected_count) + else: + assert_array_equal(count, expected_count) + + +def test_validation_at_least_one(): + with pytest.raises(TypeError, match='At least one'): + crosstab() + + +def test_validation_same_lengths(): + with pytest.raises(ValueError, match='must have the same length'): + crosstab([1, 2], [1, 2, 3, 4]) + + +def test_validation_len_levels_matches_args(): + with pytest.raises(ValueError, match='number of input sequences'): + crosstab([0, 1, 1], [8, 8, 9], levels=([0, 1, 2, 3],)) + + +def test_result(): + res = crosstab([0, 1], [1, 2]) + assert_equal((res.elements, res.count), res) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/test_device_dtype.py b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_device_dtype.py new file mode 100644 index 0000000000000000000000000000000000000000..c20bfc1104a7c3b0dcd2ee6484aed9169380eeef --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_device_dtype.py @@ -0,0 +1,248 @@ +import pytest +import numpy as np +from scipy import stats + +from scipy._lib._array_api import xp_device, is_array_api_strict, is_torch +from scipy.stats._stats_py import _xp_mean, _xp_var + +skip_xp_backends = pytest.mark.skip_xp_backends + +dtypes = ['float32', 'float64'] + + +def get_arrays(n_arrays, *, dtype=np.float64, xp=np, shape=(30,), device=None, + seed=84912165484321): + rng = np.random.default_rng(seed) + + datas = [] + for i in range(n_arrays): + data = 10*rng.random(size=shape) + if xp.isdtype(dtype, 'complex floating'): + data = data * 10j*rng.standard_normal(size=shape) + data = xp.asarray(data, dtype=dtype, device=device) + datas.append(data) + + return datas + + +@pytest.mark.parametrize('fun, kwargs', [(stats.gmean, {}), + (stats.hmean, {}), + (stats.pmean, {'p': 2}), + (_xp_mean, {}), + ]) +@pytest.mark.parametrize('dtype', dtypes) +@pytest.mark.uses_xp_capabilities(False, reason="not used in this test yet") +def test_xmean(fun, kwargs, dtype, xp, devices): + dtype = getattr(xp, dtype) + for device in devices: + x, weights = get_arrays(2, device=device, dtype=dtype, xp=xp) + res = fun(x, weights=weights, **kwargs) + assert xp_device(res) == xp_device(x) + assert x.dtype == dtype + + +@skip_xp_backends('array_api_strict', + reason="special functions don't work with 'device1'") +@pytest.mark.parametrize('nargs', [1, 2]) +@pytest.mark.parametrize('dtype', dtypes) +@pytest.mark.uses_xp_capabilities(False, reason="not used in this test yet") +def test_entropy(nargs, dtype, xp, devices): + dtype = getattr(xp, dtype) + for device in devices: + args = get_arrays(nargs, device=device, dtype=dtype, xp=xp) + res = stats.entropy(*args) + assert xp_device(res) == xp_device(args[0]) + assert res.dtype == dtype + + +@pytest.mark.parametrize('dtype', dtypes) +@pytest.mark.uses_xp_capabilities(False, reason="not used in this test yet") +def test_directional_stats(dtype, xp, devices): + dtype = getattr(xp, dtype) + for device in devices: + x = get_arrays(1, shape=(30, 3), device=device, dtype=dtype, xp=xp)[0] + res = stats.directional_stats(x) + assert xp_device(res.mean_direction) == xp_device(x) + assert res.mean_direction.dtype == dtype + assert xp_device(res.mean_resultant_length) == xp_device(x) + assert res.mean_resultant_length.dtype == dtype + + +@pytest.mark.parametrize('method', [ + "vasicek", + "van es", + pytest.param( + "correa", + marks=[ + skip_xp_backends("array_api_strict", reason="Invalid fancy indexing"), + skip_xp_backends("dask.array", reason="Invalid fancy indexing"), + ], + ), + "ebrahimi", + "auto", +]) +@pytest.mark.parametrize('dtype', dtypes) +@pytest.mark.uses_xp_capabilities(False, reason="not used in this test yet") +def test_differential_entropy(method, dtype, xp, devices): + dtype = getattr(xp, dtype) + for device in devices: + values = get_arrays(1, device=device, dtype=dtype, xp=xp)[0] + res = stats.differential_entropy(values, method=method) + assert xp_device(res) == xp_device(values) + assert values.dtype == dtype + + +@skip_xp_backends('dask.array', reason='no take_along_axis') +@pytest.mark.parametrize('method', ['inverted_cdf', 'averaged_inverted_cdf', + 'closest_observation', 'interpolated_inverted_cdf', + 'hazen', 'weibull', 'linear', 'median_unbiased', + 'normal_unbiased', 'harrell-davis']) +@pytest.mark.parametrize('dtype', dtypes) +@pytest.mark.uses_xp_capabilities(False, reason="not used in this test yet") +def test_quantile(method, dtype, xp, devices): + if (is_array_api_strict(xp) or is_torch(xp)) and method == 'harrell-davis': + pytest.skip("'harrell-davis' not currently supported on GPU.") + + dtype = getattr(xp, dtype) + for device in devices: + values = get_arrays(1, device=device, dtype=dtype, xp=xp)[0] + res = stats.quantile(values, 0.5, method=method) + assert xp_device(res) == xp_device(values) + assert values.dtype == dtype + + +@pytest.mark.parametrize('dtype', dtypes) +@pytest.mark.uses_xp_capabilities(False, reason="not used in this test yet") +def test_boxcox_llf(dtype, xp, devices): + dtype = getattr(xp, dtype) + for device in devices: + data = get_arrays(1, device=device, dtype=dtype, xp=xp)[0] + res = stats.boxcox_llf(1, data) + assert xp_device(res) == xp_device(data) + assert data.dtype == dtype + + +@pytest.mark.parametrize('fun, kwargs', + [(stats.moment, {'order': 2}), + (stats.skew, {}), + (stats.skew, {'bias': False}), + (stats.kurtosis, {}), + (stats.kurtosis, {'bias': False}), + (stats.sem, {}), + (stats.kstat, {'n': 1}), + (stats.kstat, {'n': 2}), + (stats.kstat, {'n': 3}), + (stats.kstat, {'n': 4}), + (stats.kstatvar, {'n': 1}), + (stats.kstatvar, {'n': 2}), + (stats.circmean, {}), + (stats.circvar, {}), + (stats.circstd, {}), + (_xp_var, {}), + (stats.gstd, {}), + (stats.variation, {}), + (stats.tmean, {'limits': (0.1, 0.9)}), + (stats.tvar, {'limits': (0.1, 0.9)}), + (stats.tmin, {'lowerlimit': 0.1}), + (stats.tmax, {'upperlimit': 0.9}), + (stats.tstd, {'limits': (0.1, 0.9)}), + (stats.tsem, {'limits': (0.1, 0.9)}), + ]) +@pytest.mark.parametrize('dtype', dtypes) +@pytest.mark.uses_xp_capabilities(False, reason="not used in this test yet") +def test_one_in_one_out(fun, kwargs, dtype, xp, devices): + dtype = getattr(xp, dtype) + for device in devices: + array = get_arrays(1, device=device, dtype=dtype, xp=xp)[0] + res = fun(array, **kwargs) + assert xp_device(res) == xp_device(array) + assert res.dtype == dtype + + +@pytest.mark.parametrize('dtype', dtypes) +@pytest.mark.uses_xp_capabilities(False, reason="not used in this test yet") +def test_describe(dtype, xp, devices): + dtype = getattr(xp, dtype) + for device in devices: + array = get_arrays(1, shape=10, dtype=dtype, device=device, xp=xp)[0] + res = stats.describe(array, axis=-1) + + assert xp_device(res.nobs) == xp_device(array) + assert xp_device(res.minmax[0]) == xp_device(array) + assert xp_device(res.minmax[1]) == xp_device(array) + assert xp_device(res.variance) == xp_device(array) + assert xp_device(res.skewness) == xp_device(array) + assert xp_device(res.kurtosis) == xp_device(array) + + assert res.minmax[0].dtype == dtype + assert res.minmax[1].dtype == dtype + assert res.variance.dtype == dtype + assert res.skewness.dtype == dtype + assert res.kurtosis.dtype == dtype + + +@pytest.mark.parametrize('fun', [stats.zscore, stats.gzscore, stats.zmap]) +@pytest.mark.parametrize('dtype', dtypes) +@pytest.mark.uses_xp_capabilities(False, reason="not used in this test yet") +def test_zscore(fun, dtype, xp, devices): + dtype = getattr(xp, dtype) + for device in devices: + n = 2 if (fun == stats.zmap) else 1 + arrays = get_arrays(n, device=device, dtype=dtype, xp=xp) + res = fun(*arrays) + assert xp_device(res) == xp_device(arrays[0]) + assert res.dtype == dtype + + +@skip_xp_backends('array_api_strict', reason="special/_support_alternative_backends") +@skip_xp_backends(cpu_only=True, exceptions=['cupy', 'jax.numpy']) +@pytest.mark.parametrize('f_name', ['ttest_1samp', 'ttest_rel', 'ttest_ind', 'skewtest', + 'kurtosistest', 'normaltest', 'jarque_bera', + 'bartlett', 'pearsonr', 'chisquare', + 'power_divergence']) +@pytest.mark.parametrize('dtype', dtypes) +@pytest.mark.uses_xp_capabilities(False, reason="not used in this test yet") +def test_hypothesis_tests(f_name, dtype, xp, devices): + dtype = getattr(xp, dtype) + for device in devices: + f = getattr(stats, f_name) + + n = 2 if f_name in {'ttest_1samp', 'ttest_rel', 'ttest_ind', 'bartlett', + 'pearsonr', 'chisquare'} else 1 + + arrays = get_arrays(n, xp=xp, dtype=dtype, device=device) + if f_name == 'ttest_1samp': + arrays[1] = xp.mean(arrays[1]) + if f_name == 'chisquare': + arrays[1] = xp.sum(arrays[0]) * arrays[1] / xp.sum(arrays[1]) + + res = f(*arrays) + assert xp_device(res.statistic) == xp_device(arrays[0]) + assert xp_device(res.pvalue) == xp_device(arrays[0]) + assert res.statistic.dtype == dtype + assert res.pvalue.dtype == dtype + + if f_name in {'ttest_1samp', 'ttest_rel', 'ttest_ind', 'pearsonr'}: + res_ci = res.confidence_interval() + assert xp_device(res_ci.low) == xp_device(arrays[0]) + assert xp_device(res_ci.high) == xp_device(arrays[0]) + assert res_ci.low.dtype == dtype + assert res_ci.high.dtype == dtype + + +@skip_xp_backends('array_api_strict', + reason="special functions don't work with 'device1'") +@skip_xp_backends(cpu_only=True, exceptions=['cupy', 'jax.numpy']) +@pytest.mark.parametrize('method', ['fisher', 'pearson', 'tippett', 'stouffer', + 'mudholkar_george']) +@pytest.mark.parametrize('dtype', dtypes) +@pytest.mark.uses_xp_capabilities(False, reason="not used in this test yet") +def test_combine_pvalues(method, dtype, xp, devices): + dtype = getattr(xp, dtype) + for device in devices: + pvalues = get_arrays(1, xp=xp, dtype=dtype, device=device)[0] / 10 + res = stats.combine_pvalues(pvalues, method=method) + assert xp_device(res.statistic) == xp_device(pvalues) + assert xp_device(res.pvalue) == xp_device(pvalues) + assert res.statistic.dtype == dtype + assert res.pvalue.dtype == dtype diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/test_discrete_basic.py b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_discrete_basic.py new file mode 100644 index 0000000000000000000000000000000000000000..8e67c60a21d193ee3f304b80f6660110fb95b177 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_discrete_basic.py @@ -0,0 +1,625 @@ +import warnings + +import numpy.testing as npt +from numpy.testing import assert_allclose + +import numpy as np +import pytest + +from scipy import stats +from scipy.special import _ufuncs +from .common_tests import (check_normalization, check_moment, + check_mean_expect, + check_var_expect, check_skew_expect, + check_kurt_expect, check_entropy, + check_private_entropy, check_edge_support, + check_named_args, check_random_state_property, + check_pickling, check_rvs_broadcast, + check_freezing,) +from scipy.stats._distr_params import distdiscrete, invdistdiscrete +from scipy.stats._distn_infrastructure import rv_discrete_frozen + +vals = ([1, 2, 3, 4], [0.1, 0.2, 0.3, 0.4]) +distdiscrete += [[stats.rv_discrete(values=vals), ()]] + +# For these distributions, test_discrete_basic only runs with test mode full +distslow = {'nhypergeom'} + +# Override number of ULPs adjustment for `check_cdf_ppf` +roundtrip_cdf_ppf_exceptions = {'nbinom': 30} + +def cases_test_discrete_basic(): + seen = set() + for distname, arg in distdiscrete: + if distname in distslow: + yield pytest.param(distname, arg, distname, marks=pytest.mark.slow) + else: + yield distname, arg, distname not in seen + seen.add(distname) + + +@pytest.mark.parametrize('distname,arg,first_case', list(cases_test_discrete_basic())) +def test_discrete_basic(distname, arg, first_case, num_parallel_threads): + if (isinstance(distname, str) and distname.startswith('nchypergeom') + and num_parallel_threads > 1): + pytest.skip(reason='nchypergeom has a global random generator') + + try: + distfn = getattr(stats, distname) + except TypeError: + distfn = distname + distname = 'sample distribution' + rng = np.random.RandomState(9765456) + rvs = distfn.rvs(*arg, size=2000, random_state=rng) + supp = np.unique(rvs) + m, v = distfn.stats(*arg) + check_cdf_ppf(distfn, arg, supp, distname + ' cdf_ppf') + + check_pmf_cdf(distfn, arg, distname) + check_oth(distfn, arg, supp, distname + ' oth') + check_edge_support(distfn, arg) + + alpha = 0.01 + check_discrete_chisquare(distfn, arg, rvs, alpha, + distname + ' chisquare') + + if first_case: + locscale_defaults = (0,) + meths = [distfn.pmf, distfn.logpmf, distfn.cdf, distfn.logcdf, + distfn.logsf] + # make sure arguments are within support + # for some distributions, this needs to be overridden + spec_k = {'randint': 11, 'hypergeom': 4, 'bernoulli': 0, + 'nchypergeom_wallenius': 6} + k = spec_k.get(distname, 1) + check_named_args(distfn, k, arg, locscale_defaults, meths) + if distname != 'sample distribution': + check_scale_docstring(distfn) + if num_parallel_threads == 1: + check_random_state_property(distfn, arg) + if distname not in {'poisson_binom'}: # can't be pickled + check_pickling(distfn, arg) + check_freezing(distfn, arg) + + # Entropy + check_entropy(distfn, arg, distname) + if distfn.__class__._entropy != stats.rv_discrete._entropy: + check_private_entropy(distfn, arg, stats.rv_discrete) + + +@pytest.mark.parametrize('distname,arg', distdiscrete) +def test_moments(distname, arg): + try: + distfn = getattr(stats, distname) + except TypeError: + distfn = distname + distname = 'sample distribution' + m, v, s, k = distfn.stats(*arg, moments='mvsk') + check_normalization(distfn, arg, distname) + + # compare `stats` and `moment` methods + check_moment(distfn, arg, m, v, distname) + check_mean_expect(distfn, arg, m, distname) + check_var_expect(distfn, arg, m, v, distname) + check_skew_expect(distfn, arg, m, v, s, distname) + with warnings.catch_warnings(): + if distname in ['zipf', 'betanbinom']: + warnings.simplefilter("ignore", RuntimeWarning) + check_kurt_expect(distfn, arg, m, v, k, distname) + + # frozen distr moments + check_moment_frozen(distfn, arg, m, 1) + check_moment_frozen(distfn, arg, v+m*m, 2) + + +@pytest.mark.parametrize('dist,shape_args', distdiscrete) +def test_rvs_broadcast(dist, shape_args): + # If shape_only is True, it means the _rvs method of the + # distribution uses more than one random number to generate a random + # variate. That means the result of using rvs with broadcasting or + # with a nontrivial size will not necessarily be the same as using the + # numpy.vectorize'd version of rvs(), so we can only compare the shapes + # of the results, not the values. + # Whether or not a distribution is in the following list is an + # implementation detail of the distribution, not a requirement. If + # the implementation the rvs() method of a distribution changes, this + # test might also have to be changed. + shape_only = dist in ['betabinom', 'betanbinom', 'skellam', 'yulesimon', + 'dlaplace', 'nchypergeom_fisher', + 'nchypergeom_wallenius', 'poisson_binom'] + try: + distfunc = getattr(stats, dist) + except TypeError: + distfunc = dist + dist = f'rv_discrete(values=({dist.xk!r}, {dist.pk!r}))' + loc = np.zeros(2) + nargs = distfunc.numargs + allargs = [] + bshape = [] + + if dist == 'poisson_binom': + # normal rules apply except the last axis of `p` is ignored + p = np.full((3, 1, 10), 0.5) + allargs = (p, loc) + bshape = (3, 2) + check_rvs_broadcast(distfunc, dist, allargs, + bshape, shape_only, [np.dtype(int)]) + return + + # Generate shape parameter arguments... + for k in range(nargs): + shp = (k + 3,) + (1,)*(k + 1) + param_val = shape_args[k] + allargs.append(np.full(shp, param_val)) + bshape.insert(0, shp[0]) + allargs.append(loc) + bshape.append(loc.size) + # bshape holds the expected shape when loc, scale, and the shape + # parameters are all broadcast together. + check_rvs_broadcast( + distfunc, dist, allargs, bshape, shape_only, [np.dtype(int)] + ) + + +@pytest.mark.parametrize('dist,args', distdiscrete) +def test_ppf_with_loc(dist, args): + try: + distfn = getattr(stats, dist) + except TypeError: + distfn = dist + #check with a negative, no and positive relocation. + rng = np.random.default_rng(5108587887) + + re_locs = [rng.integers(-10, -1), 0, rng.integers(1, 10)] + _a, _b = distfn.support(*args) + for loc in re_locs: + npt.assert_array_equal( + [_a-1+loc, _b+loc], + [distfn.ppf(0.0, *args, loc=loc), distfn.ppf(1.0, *args, loc=loc)] + ) + + +@pytest.mark.parametrize('dist, args', distdiscrete) +def test_isf_with_loc(dist, args): + try: + distfn = getattr(stats, dist) + except TypeError: + distfn = dist + # check with a negative, no and positive relocation. + rng = np.random.default_rng(4030503535) + re_locs = [rng.integers(-10, -1), 0, rng.integers(1, 10)] + _a, _b = distfn.support(*args) + for loc in re_locs: + expected = _b + loc, _a - 1 + loc + res = distfn.isf(0., *args, loc=loc), distfn.isf(1., *args, loc=loc) + npt.assert_array_equal(expected, res) + # test broadcasting behaviour + re_locs = [rng.integers(-10, -1, size=(5, 3)), + np.zeros((5, 3)), + rng.integers(1, 10, size=(5, 3))] + _a, _b = distfn.support(*args) + for loc in re_locs: + expected = _b + loc, _a - 1 + loc + res = distfn.isf(0., *args, loc=loc), distfn.isf(1., *args, loc=loc) + npt.assert_array_equal(expected, res) + + +def check_cdf_ppf(distfn, arg, supp, msg): + # supp is assumed to be an array of integers in the support of distfn + # (but not necessarily all the integers in the support). + # This test assumes that the PMF of any value in the support of the + # distribution is greater than 1e-8. + + # cdf is a step function, and ppf(q) = min{k : cdf(k) >= q, k integer} + cdf_supp = distfn.cdf(supp, *arg) + # In very rare cases, the finite precision calculation of ppf(cdf(supp)) + # can produce an array in which an element is off by one. We nudge the + # CDF values down by a few ULPs help to avoid this. + n_ulps = roundtrip_cdf_ppf_exceptions.get(distfn.name, 15) + cdf_supp0 = cdf_supp - n_ulps*np.spacing(cdf_supp) + npt.assert_array_equal(distfn.ppf(cdf_supp0, *arg), + supp, msg + '-roundtrip') + # Repeat the same calculation, but with the CDF values decreased by 1e-8. + npt.assert_array_equal(distfn.ppf(distfn.cdf(supp, *arg) - 1e-8, *arg), + supp, msg + '-roundtrip') + + if not hasattr(distfn, 'xk'): + _a, _b = distfn.support(*arg) + supp1 = supp[supp < _b] + npt.assert_array_equal(distfn.ppf(distfn.cdf(supp1, *arg) + 1e-8, *arg), + supp1 + distfn.inc, msg + ' ppf-cdf-next') + + +def check_pmf_cdf(distfn, arg, distname): + if hasattr(distfn, 'xk'): + index = distfn.xk + else: + startind = int(distfn.ppf(0.01, *arg) - 1) + index = list(range(startind, startind + 10)) + cdfs = distfn.cdf(index, *arg) + pmfs_cum = distfn.pmf(index, *arg).cumsum() + + atol, rtol = 1e-10, 1e-10 + if distname == 'skellam': # ncx2 accuracy + atol, rtol = 1e-5, 1e-5 + npt.assert_allclose(cdfs - cdfs[0], pmfs_cum - pmfs_cum[0], + atol=atol, rtol=rtol) + + # also check that pmf at non-integral k is zero + k = np.asarray(index) + k_shifted = k[:-1] + np.diff(k)/2 + npt.assert_equal(distfn.pmf(k_shifted, *arg), 0) + + # better check frozen distributions, and also when loc != 0 + loc = 0.5 + dist = distfn(loc=loc, *arg) + npt.assert_allclose(dist.pmf(k[1:] + loc), np.diff(dist.cdf(k + loc))) + npt.assert_equal(dist.pmf(k_shifted + loc), 0) + + +def check_moment_frozen(distfn, arg, m, k): + npt.assert_allclose(distfn(*arg).moment(k), m, + atol=1e-10, rtol=1e-10) + + +def check_oth(distfn, arg, supp, msg): + # checking other methods of distfn + npt.assert_allclose(distfn.sf(supp, *arg), 1. - distfn.cdf(supp, *arg), + atol=1e-10, rtol=1e-10) + + q = np.linspace(0.01, 0.99, 20) + npt.assert_allclose(distfn.isf(q, *arg), distfn.ppf(1. - q, *arg), + atol=1e-10, rtol=1e-10) + + median_sf = distfn.isf(0.5, *arg) + npt.assert_(distfn.sf(median_sf - 1, *arg) > 0.5) + npt.assert_(distfn.cdf(median_sf + 1, *arg) > 0.5) + + +def check_discrete_chisquare(distfn, arg, rvs, alpha, msg): + """Perform chisquare test for random sample of a discrete distribution + + Parameters + ---------- + distname : str + name of distribution function + arg : sequence + parameters of distribution + alpha : float + significance level, threshold for p-value + + Returns + ------- + result : bool + 0 if test passes, 1 if test fails + + """ + wsupp = 0.05 + + # construct intervals with minimum mass `wsupp`. + # intervals are left-half-open as in a cdf difference + _a, _b = distfn.support(*arg) + lo = int(max(_a, -1000)) + high = int(min(_b, 1000)) + 1 + distsupport = range(lo, high) + last = 0 + distsupp = [lo] + distmass = [] + for ii in distsupport: + current = distfn.cdf(ii, *arg) + if current - last >= wsupp - 1e-14: + distsupp.append(ii) + distmass.append(current - last) + last = current + if current > (1 - wsupp): + break + if distsupp[-1] < _b: + distsupp.append(_b) + distmass.append(1 - last) + distsupp = np.array(distsupp) + distmass = np.array(distmass) + + # convert intervals to right-half-open as required by histogram + histsupp = distsupp + 1e-8 + histsupp[0] = _a + + # find sample frequencies and perform chisquare test + freq, hsupp = np.histogram(rvs, histsupp) + chis, pval = stats.chisquare(np.array(freq), len(rvs)*distmass) + + npt.assert_( + pval > alpha, + f'chisquare - test for {msg} at arg = {str(arg)} with pval = {str(pval)}' + ) + + +def check_scale_docstring(distfn): + if distfn.__doc__ is not None: + # Docstrings can be stripped if interpreter is run with -OO + npt.assert_('scale' not in distfn.__doc__) + + +@pytest.mark.parametrize('method', ['pmf', 'logpmf', 'cdf', 'logcdf', + 'sf', 'logsf', 'ppf', 'isf']) +@pytest.mark.parametrize('distname, args', distdiscrete) +def test_methods_with_lists(method, distname, args): + # Test that the discrete distributions can accept Python lists + # as arguments. + try: + dist = getattr(stats, distname) + except TypeError: + return + dist_method = getattr(dist, method) + if method in ['ppf', 'isf']: + z = [0.1, 0.2] + else: + z = [0, 1] + p2 = [[p]*2 for p in args] + loc = [0, 1] + result = dist_method(z, *p2, loc=loc) + npt.assert_allclose(result, + [dist_method(*v) for v in zip(z, *p2, loc)], + rtol=1e-15, atol=1e-15) + + +@pytest.mark.parametrize('distname, args', invdistdiscrete) +def test_cdf_gh13280_regression(distname, args): + # Test for nan output when shape parameters are invalid + dist = getattr(stats, distname) + x = np.arange(-2, 15) + vals = dist.cdf(x, *args) + expected = np.nan + npt.assert_equal(vals, expected) + + +def cases_test_discrete_integer_shapes(): + # distributions parameters that are only allowed to be integral when + # fitting, but are allowed to be real as input to PDF, etc. + integrality_exceptions = {'nbinom': {'n'}, 'betanbinom': {'n'}} + + seen = set() + for distname, shapes in distdiscrete: + if distname in seen: + continue + seen.add(distname) + + try: + dist = getattr(stats, distname) + except TypeError: + continue + + shape_info = dist._shape_info() + + for i, shape in enumerate(shape_info): + if (shape.name in integrality_exceptions.get(distname, set()) or + not shape.integrality): + continue + + yield distname, shape.name, shapes + + +@pytest.mark.parametrize('distname, shapename, shapes', + list(cases_test_discrete_integer_shapes())) +def test_integer_shapes(distname, shapename, shapes): + dist = getattr(stats, distname) + shape_info = dist._shape_info() + shape_names = [shape.name for shape in shape_info] + i = shape_names.index(shapename) # this element of params must be integral + + shapes_copy = list(shapes) + + valid_shape = shapes[i] + invalid_shape = valid_shape - 0.5 # arbitrary non-integral value + new_valid_shape = valid_shape - 1 + shapes_copy[i] = [[valid_shape], [invalid_shape], [new_valid_shape]] + + a, b = dist.support(*shapes) + x = np.round(np.linspace(a, b, 5)) + + pmf = dist.pmf(x, *shapes_copy) + assert not np.any(np.isnan(pmf[0, :])) + assert np.all(np.isnan(pmf[1, :])) + assert not np.any(np.isnan(pmf[2, :])) + + +def test_frozen_attributes(monkeypatch): + # gh-14827 reported that all frozen distributions had both pmf and pdf + # attributes; continuous should have pdf and discrete should have pmf. + message = "'rv_discrete_frozen' object has no attribute" + with pytest.raises(AttributeError, match=message): + stats.binom(10, 0.5).pdf + with pytest.raises(AttributeError, match=message): + stats.binom(10, 0.5).logpdf + monkeypatch.setattr(stats.binom, "pdf", "herring", raising=False) + frozen_binom = stats.binom(10, 0.5) + assert isinstance(frozen_binom, rv_discrete_frozen) + assert not hasattr(frozen_binom, "pdf") + + +@pytest.mark.parametrize('distname, shapes', distdiscrete) +def test_interval(distname, shapes): + # gh-11026 reported that `interval` returns incorrect values when + # `confidence=1`. The values were not incorrect, but it was not intuitive + # that the left end of the interval should extend beyond the support of the + # distribution. Confirm that this is the behavior for all distributions. + if isinstance(distname, str): + dist = getattr(stats, distname) + else: + dist = distname + a, b = dist.support(*shapes) + npt.assert_equal(dist.ppf([0, 1], *shapes), (a-1, b)) + npt.assert_equal(dist.isf([1, 0], *shapes), (a-1, b)) + npt.assert_equal(dist.interval(1, *shapes), (a-1, b)) + + +@pytest.mark.xfail_on_32bit("Sensible to machine precision") +def test_rv_sample(): + # Thoroughly test rv_sample and check that gh-3758 is resolved + + # Generate a random discrete distribution + rng = np.random.default_rng(98430143469) + xk = np.sort(rng.random(10) * 10) + pk = rng.random(10) + pk /= np.sum(pk) + dist = stats.rv_discrete(values=(xk, pk)) + + # Generate points to the left and right of xk + xk_left = (np.array([0] + xk[:-1].tolist()) + xk)/2 + xk_right = (np.array(xk[1:].tolist() + [xk[-1]+1]) + xk)/2 + + # Generate points to the left and right of cdf + cdf2 = np.cumsum(pk) + cdf2_left = (np.array([0] + cdf2[:-1].tolist()) + cdf2)/2 + cdf2_right = (np.array(cdf2[1:].tolist() + [1]) + cdf2)/2 + + # support - leftmost and rightmost xk + a, b = dist.support() + assert_allclose(a, xk[0]) + assert_allclose(b, xk[-1]) + + # pmf - supported only on the xk + assert_allclose(dist.pmf(xk), pk) + assert_allclose(dist.pmf(xk_right), 0) + assert_allclose(dist.pmf(xk_left), 0) + + # logpmf is log of the pmf; log(0) = -np.inf + with np.errstate(divide='ignore'): + assert_allclose(dist.logpmf(xk), np.log(pk)) + assert_allclose(dist.logpmf(xk_right), -np.inf) + assert_allclose(dist.logpmf(xk_left), -np.inf) + + # cdf - the cumulative sum of the pmf + assert_allclose(dist.cdf(xk), cdf2) + assert_allclose(dist.cdf(xk_right), cdf2) + assert_allclose(dist.cdf(xk_left), [0]+cdf2[:-1].tolist()) + + with np.errstate(divide='ignore'): + assert_allclose(dist.logcdf(xk), np.log(dist.cdf(xk)), + atol=1e-15) + assert_allclose(dist.logcdf(xk_right), np.log(dist.cdf(xk_right)), + atol=1e-15) + assert_allclose(dist.logcdf(xk_left), np.log(dist.cdf(xk_left)), + atol=1e-15) + + # sf is 1-cdf + assert_allclose(dist.sf(xk), 1-dist.cdf(xk)) + assert_allclose(dist.sf(xk_right), 1-dist.cdf(xk_right)) + assert_allclose(dist.sf(xk_left), 1-dist.cdf(xk_left)) + + with np.errstate(divide='ignore'): + assert_allclose(dist.logsf(xk), np.log(dist.sf(xk)), + atol=1e-15) + assert_allclose(dist.logsf(xk_right), np.log(dist.sf(xk_right)), + atol=1e-15) + assert_allclose(dist.logsf(xk_left), np.log(dist.sf(xk_left)), + atol=1e-15) + + # ppf + assert_allclose(dist.ppf(cdf2), xk) + assert_allclose(dist.ppf(cdf2_left), xk) + assert_allclose(dist.ppf(cdf2_right)[:-1], xk[1:]) + assert_allclose(dist.ppf(0), a - 1) + assert_allclose(dist.ppf(1), b) + + # isf + sf2 = dist.sf(xk) + assert_allclose(dist.isf(sf2), xk) + assert_allclose(dist.isf(1-cdf2_left), dist.ppf(cdf2_left)) + assert_allclose(dist.isf(1-cdf2_right), dist.ppf(cdf2_right)) + assert_allclose(dist.isf(0), b) + assert_allclose(dist.isf(1), a - 1) + + # interval is (ppf(alpha/2), isf(alpha/2)) + ps = np.linspace(0.01, 0.99, 10) + int2 = dist.ppf(ps/2), dist.isf(ps/2) + assert_allclose(dist.interval(1-ps), int2) + assert_allclose(dist.interval(0), dist.median()) + assert_allclose(dist.interval(1), (a-1, b)) + + # median is simply ppf(0.5) + med2 = dist.ppf(0.5) + assert_allclose(dist.median(), med2) + + # all four stats (mean, var, skew, and kurtosis) from the definitions + mean2 = np.sum(xk*pk) + var2 = np.sum((xk - mean2)**2 * pk) + skew2 = np.sum((xk - mean2)**3 * pk) / var2**(3/2) + kurt2 = np.sum((xk - mean2)**4 * pk) / var2**2 - 3 + assert_allclose(dist.mean(), mean2) + assert_allclose(dist.std(), np.sqrt(var2)) + assert_allclose(dist.var(), var2) + assert_allclose(dist.stats(moments='mvsk'), (mean2, var2, skew2, kurt2)) + + # noncentral moment against definition + mom3 = np.sum((xk**3) * pk) + assert_allclose(dist.moment(3), mom3) + + # expect - check against moments + assert_allclose(dist.expect(lambda x: 1), 1) + assert_allclose(dist.expect(), mean2) + assert_allclose(dist.expect(lambda x: x**3), mom3) + + # entropy is the negative of the expected value of log(p) + with np.errstate(divide='ignore'): + assert_allclose(-dist.expect(lambda x: dist.logpmf(x)), dist.entropy()) + + # RVS is just ppf of uniform random variates + rng = np.random.default_rng(98430143469) + rvs = dist.rvs(size=100, random_state=rng) + rng = np.random.default_rng(98430143469) + rvs0 = dist.ppf(rng.random(size=100)) + assert_allclose(rvs, rvs0) + +def test__pmf_float_input(): + # gh-21272 + # test that `rvs()` can be computed when `_pmf` requires float input + + class rv_exponential(stats.rv_discrete): + def _pmf(self, i): + return (2/3)*3**(1 - i) + + rv = rv_exponential(a=0.0, b=float('inf')) + rvs = rv.rvs(random_state=42) # should not crash due to integer input to `_pmf` + assert_allclose(rvs, 0) + + +def test_gh18919_ppf_array_args(): + # gh-18919 reported incorrect results for ppf and isf of discrete distributions when + # arguments were arrays and first argument (`q`) had elements at the boundaries of + # the support. + q = [[0.5, 1.0, 0.5], + [1.0, 0.5, 1.0], + [0.5, 1.0, 0.5]] + + n = [[45, 46, 47], + [48, 49, 50], + [51, 52, 53]] + + p = 0.5 + + ref = _ufuncs._binom_ppf(q, n, p) + res = stats.binom.ppf(q, n, p) + np.testing.assert_allclose(res, ref) + +@pytest.mark.parametrize("dist", [stats.binom, stats.boltzmann]) +def test_gh18919_ppf_isf_array_args2(dist): + # a more general version of the test above. Requires that arguments are broadcasted + # by the infrastructure. + rng = np.random.default_rng(34873457824358729823) + q = rng.random(size=(30, 1, 1, 1)) + n = rng.integers(10, 30, size=(10, 1, 1)) + p = rng.random(size=(4, 1)) + loc = rng.integers(5, size=(3,)) + + q[rng.random(size=30) > 0.7] = 0 + q[rng.random(size=30) > 0.7] = 1 + + args = (q, n, p) if dist == stats.binom else (q, p, n) + + res = dist.ppf(*args, loc=loc) + ref = np.vectorize(dist.ppf)(*args) + loc + np.testing.assert_allclose(res, ref) + + res = dist.isf(*args, loc=loc) + ref = np.vectorize(dist.isf)(*args) + loc + np.testing.assert_allclose(res, ref) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/test_discrete_distns.py b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_discrete_distns.py new file mode 100644 index 0000000000000000000000000000000000000000..8ba8da84e2dca928426a3b5283d85f5cf29be07d --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_discrete_distns.py @@ -0,0 +1,747 @@ +import pytest +import itertools +import warnings + +from scipy import stats +from scipy.stats import (betabinom, betanbinom, hypergeom, nhypergeom, + bernoulli, boltzmann, skellam, zipf, zipfian, binom, + nbinom, nchypergeom_fisher, nchypergeom_wallenius, + randint, poisson_binom) + +import numpy as np +from numpy.testing import ( + assert_almost_equal, assert_equal, assert_allclose +) +from scipy.special import binom as special_binom +from scipy.optimize import root_scalar +from scipy.integrate import quad + + +# The expected values were computed with Wolfram Alpha, using +# the expression CDF[HypergeometricDistribution[N, n, M], k]. +@pytest.mark.parametrize('k, M, n, N, expected, rtol', + [(3, 10, 4, 5, + 0.9761904761904762, 1e-15), + (107, 10000, 3000, 215, + 0.9999999997226765, 1e-15), + (10, 10000, 3000, 215, + 2.681682217692179e-21, 5e-11)]) +def test_hypergeom_cdf(k, M, n, N, expected, rtol): + p = hypergeom.cdf(k, M, n, N) + assert_allclose(p, expected, rtol=rtol) + + +# The expected values were computed with Wolfram Alpha, using +# the expression SurvivalFunction[HypergeometricDistribution[N, n, M], k]. +@pytest.mark.parametrize('k, M, n, N, expected, rtol', + [(25, 10000, 3000, 215, + 0.9999999999052958, 1e-15), + (125, 10000, 3000, 215, + 1.4416781705752128e-18, 5e-11)]) +def test_hypergeom_sf(k, M, n, N, expected, rtol): + p = hypergeom.sf(k, M, n, N) + assert_allclose(p, expected, rtol=rtol) + + +def test_hypergeom_logpmf(): + # symmetries test + # f(k,N,K,n) = f(n-k,N,N-K,n) = f(K-k,N,K,N-n) = f(k,N,n,K) + k = 5 + N = 50 + K = 10 + n = 5 + logpmf1 = hypergeom.logpmf(k, N, K, n) + logpmf2 = hypergeom.logpmf(n - k, N, N - K, n) + logpmf3 = hypergeom.logpmf(K - k, N, K, N - n) + logpmf4 = hypergeom.logpmf(k, N, n, K) + assert_almost_equal(logpmf1, logpmf2, decimal=12) + assert_almost_equal(logpmf1, logpmf3, decimal=12) + assert_almost_equal(logpmf1, logpmf4, decimal=12) + + # test related distribution + # Bernoulli distribution if n = 1 + k = 1 + N = 10 + K = 7 + n = 1 + hypergeom_logpmf = hypergeom.logpmf(k, N, K, n) + bernoulli_logpmf = bernoulli.logpmf(k, K/N) + assert_almost_equal(hypergeom_logpmf, bernoulli_logpmf, decimal=12) + + +def test_nhypergeom_pmf(): + # test with hypergeom + M, n, r = 45, 13, 8 + k = 6 + NHG = nhypergeom.pmf(k, M, n, r) + HG = hypergeom.pmf(k, M, n, k+r-1) * (M - n - (r-1)) / (M - (k+r-1)) + assert_allclose(HG, NHG, rtol=1e-10) + + +def test_nhypergeom_pmfcdf(): + # test pmf and cdf with arbitrary values. + M = 8 + n = 3 + r = 4 + support = np.arange(n+1) + pmf = nhypergeom.pmf(support, M, n, r) + cdf = nhypergeom.cdf(support, M, n, r) + assert_allclose(pmf, [1/14, 3/14, 5/14, 5/14], rtol=1e-13) + assert_allclose(cdf, [1/14, 4/14, 9/14, 1.0], rtol=1e-13) + + +def test_nhypergeom_r0(): + # test with `r = 0`. + M = 10 + n = 3 + r = 0 + pmf = nhypergeom.pmf([[0, 1, 2, 0], [1, 2, 0, 3]], M, n, r) + assert_allclose(pmf, [[1, 0, 0, 1], [0, 0, 1, 0]], rtol=1e-13) + + +def test_nhypergeom_rvs_shape(): + # Check that when given a size with more dimensions than the + # dimensions of the broadcast parameters, rvs returns an array + # with the correct shape. + x = nhypergeom.rvs(22, [7, 8, 9], [[12], [13]], size=(5, 1, 2, 3)) + assert x.shape == (5, 1, 2, 3) + + +def test_nhypergeom_accuracy(): + # Check that nhypergeom.rvs post-gh-13431 gives the same values as + # inverse transform sampling + rng = np.random.RandomState(0) + x = nhypergeom.rvs(22, 7, 11, size=100, random_state=rng) + rng = np.random.RandomState(0) + p = rng.uniform(size=100) + y = nhypergeom.ppf(p, 22, 7, 11) + assert_equal(x, y) + + +def test_boltzmann_upper_bound(): + k = np.arange(-3, 5) + + N = 1 + p = boltzmann.pmf(k, 0.123, N) + expected = k == 0 + assert_equal(p, expected) + + lam = np.log(2) + N = 3 + p = boltzmann.pmf(k, lam, N) + expected = [0, 0, 0, 4/7, 2/7, 1/7, 0, 0] + assert_allclose(p, expected, rtol=1e-13) + + c = boltzmann.cdf(k, lam, N) + expected = [0, 0, 0, 4/7, 6/7, 1, 1, 1] + assert_allclose(c, expected, rtol=1e-13) + + +def test_betabinom_a_and_b_unity(): + # test limiting case that betabinom(n, 1, 1) is a discrete uniform + # distribution from 0 to n + n = 20 + k = np.arange(n + 1) + p = betabinom(n, 1, 1).pmf(k) + expected = np.repeat(1 / (n + 1), n + 1) + assert_almost_equal(p, expected) + + +@pytest.mark.parametrize('dtypes', list(itertools.product(*[(int, float)]*3))) +def test_betabinom_stats_a_and_b_integers_gh18026(dtypes): + # gh-18026 reported that `betabinom` kurtosis calculation fails when some + # parameters are integers. Check that this is resolved. + n_type, a_type, b_type = dtypes + n, a, b = n_type(10), a_type(2), b_type(3) + assert_allclose(betabinom.stats(n, a, b, moments='k'), -0.6904761904761907) + + +def test_betabinom_bernoulli(): + # test limiting case that betabinom(1, a, b) = bernoulli(a / (a + b)) + a = 2.3 + b = 0.63 + k = np.arange(2) + p = betabinom(1, a, b).pmf(k) + expected = bernoulli(a / (a + b)).pmf(k) + assert_almost_equal(p, expected) + + +def test_issue_10317(): + alpha, n, p = 0.9, 10, 1 + assert_equal(nbinom.interval(confidence=alpha, n=n, p=p), (0, 0)) + + +def test_issue_11134(): + alpha, n, p = 0.95, 10, 0 + assert_equal(binom.interval(confidence=alpha, n=n, p=p), (0, 0)) + + +def test_issue_7406(): + rng = np.random.default_rng(4763112764) + assert_equal(binom.ppf(rng.random(10), 0, 0.5), 0) + + # Also check that endpoints (q=0, q=1) are correct + assert_equal(binom.ppf(0, 0, 0.5), -1) + assert_equal(binom.ppf(1, 0, 0.5), 0) + + +def test_issue_5122(): + rng = np.random.default_rng(8312492117) + p = 0 + n = rng.integers(100, size=10) + + x = 0 + ppf = binom.ppf(x, n, p) + assert_equal(ppf, -1) + + x = np.linspace(0.01, 0.99, 10) + ppf = binom.ppf(x, n, p) + assert_equal(ppf, 0) + + x = 1 + ppf = binom.ppf(x, n, p) + assert_equal(ppf, n) + + +def test_issue_1603(): + assert_equal(binom(1000, np.logspace(-3, -100)).ppf(0.01), 0) + + +def test_issue_5503(): + p = 0.5 + x = np.logspace(3, 14, 12) + assert_allclose(binom.cdf(x, 2*x, p), 0.5, atol=1e-2) + + +@pytest.mark.parametrize('x, n, p, cdf_desired', [ + (300, 1000, 3/10, 0.51559351981411995636), + (3000, 10000, 3/10, 0.50493298381929698016), + (30000, 100000, 3/10, 0.50156000591726422864), + (300000, 1000000, 3/10, 0.50049331906666960038), + (3000000, 10000000, 3/10, 0.50015600124585261196), + (30000000, 100000000, 3/10, 0.50004933192735230102), + (30010000, 100000000, 3/10, 0.98545384016570790717), + (29990000, 100000000, 3/10, 0.01455017177985268670), + (29950000, 100000000, 3/10, 5.02250963487432024943e-28), + (300_000_000, 1_000_000_000, 3/10, 0.50001560012523938), + (3_000_000_000, 10_000_000_000, 3/10, 0.50000493319275) +]) +def test_issue_5503pt2(x, n, p, cdf_desired): + assert_allclose(binom.cdf(x, n, p), cdf_desired) + + +def test_issue_5503pt3(): + # From Wolfram Alpha: CDF[BinomialDistribution[1e12, 1e-12], 2] + assert_allclose(binom.cdf(2, 10**12, 10**-12), 0.91969860292869777384) + + +def test_issue_6682(): + # Reference value from R: + # options(digits=16) + # print(pnbinom(250, 50, 32/63, lower.tail=FALSE)) + assert_allclose(nbinom.sf(250, 50, 32./63.), 1.460458510976452e-35) + + +def test_issue_19747(): + # test that negative k does not raise an error in nbinom.logcdf + result = nbinom.logcdf([5, -1, 1], 5, 0.5) + reference = [-0.47313352, -np.inf, -2.21297293] + assert_allclose(result, reference) + + +def test_boost_divide_by_zero_issue_15101(): + n = 1000 + p = 0.01 + k = 996 + assert_allclose(binom.pmf(k, n, p), 0.0) + + +def test_skellam_gh11474(): + # test issue reported in gh-11474 caused by `cdfchn` + mu = [1, 10, 100, 1000, 5000, 5050, 5100, 5250, 6000] + cdf = skellam.cdf(0, mu, mu) + # generated in R + # library(skellam) + # options(digits = 16) + # mu = c(1, 10, 100, 1000, 5000, 5050, 5100, 5250, 6000) + # pskellam(0, mu, mu, TRUE) + cdf_expected = [0.6542541612768356, 0.5448901559424127, 0.5141135799745580, + 0.5044605891382528, 0.5019947363350450, 0.5019848365953181, + 0.5019750827993392, 0.5019466621805060, 0.5018209330219539] + assert_allclose(cdf, cdf_expected) + + +class TestZipfian: + def test_zipfian_asymptotic(self): + # test limiting case that zipfian(a, n) -> zipf(a) as n-> oo + a = 6.5 + N = 10000000 + k = np.arange(1, 21) + assert_allclose(zipfian.pmf(k, a, N), zipf.pmf(k, a)) + assert_allclose(zipfian.cdf(k, a, N), zipf.cdf(k, a)) + assert_allclose(zipfian.sf(k, a, N), zipf.sf(k, a)) + assert_allclose(zipfian.stats(a, N, moments='msvk'), + zipf.stats(a, moments='msvk')) + + def test_zipfian_continuity(self): + # test that zipfian(0.999999, n) ~ zipfian(1.000001, n) + # (a = 1 switches between methods of calculating harmonic sum) + alt1, agt1 = 0.99999999, 1.00000001 + N = 30 + k = np.arange(1, N + 1) + assert_allclose(zipfian.pmf(k, alt1, N), zipfian.pmf(k, agt1, N), + rtol=5e-7) + assert_allclose(zipfian.cdf(k, alt1, N), zipfian.cdf(k, agt1, N), + rtol=5e-7) + assert_allclose(zipfian.sf(k, alt1, N), zipfian.sf(k, agt1, N), + rtol=5e-7) + assert_allclose(zipfian.stats(alt1, N, moments='msvk'), + zipfian.stats(agt1, N, moments='msvk'), rtol=5e-7) + + def test_zipfian_R(self): + # test against R VGAM package + # library(VGAM) + # k <- c(13, 16, 1, 4, 4, 8, 10, 19, 5, 7) + # a <- c(1.56712977, 3.72656295, 5.77665117, 9.12168729, 5.79977172, + # 4.92784796, 9.36078764, 4.3739616 , 7.48171872, 4.6824154) + # n <- c(70, 80, 48, 65, 83, 89, 50, 30, 20, 20) + # pmf <- dzipf(k, N = n, shape = a) + # cdf <- pzipf(k, N = n, shape = a) + # print(pmf) + # print(cdf) + rng = np.random.RandomState(0) + k = rng.randint(1, 20, size=10) + a = rng.rand(10)*10 + 1 + n = rng.randint(1, 100, size=10) + pmf = [8.076972e-03, 2.950214e-05, 9.799333e-01, 3.216601e-06, + 3.158895e-04, 3.412497e-05, 4.350472e-10, 2.405773e-06, + 5.860662e-06, 1.053948e-04] + cdf = [0.8964133, 0.9998666, 0.9799333, 0.9999995, 0.9998584, + 0.9999458, 1.0000000, 0.9999920, 0.9999977, 0.9998498] + # skip the first point; zipUC is not accurate for low a, n + assert_allclose(zipfian.pmf(k, a, n)[1:], pmf[1:], rtol=1e-6) + assert_allclose(zipfian.cdf(k, a, n)[1:], cdf[1:], rtol=5e-5) + + rng = np.random.RandomState(0) + naive_tests = np.vstack((np.logspace(-2, 1, 10), + rng.randint(2, 40, 10))).T + + @pytest.mark.parametrize("a, n", list(naive_tests)) + def test_zipfian_naive(self, a, n): + # test against bare-bones implementation + + @np.vectorize + def Hns(n, s): + """Naive implementation of harmonic sum""" + return (1/np.arange(1, n+1)**s).sum() + + @np.vectorize + def pzip(k, a, n): + """Naive implementation of zipfian pmf""" + if k < 1 or k > n: + return 0. + else: + return 1 / k**a / Hns(n, a) + + k = np.arange(n+1) + pmf = pzip(k, a, n) + cdf = np.cumsum(pmf) + mean = np.average(k, weights=pmf) + var = np.average((k - mean)**2, weights=pmf) + std = var**0.5 + skew = np.average(((k-mean)/std)**3, weights=pmf) + kurtosis = np.average(((k-mean)/std)**4, weights=pmf) - 3 + assert_allclose(zipfian.pmf(k, a, n), pmf) + assert_allclose(zipfian.cdf(k, a, n), cdf) + assert_allclose(zipfian.stats(a, n, moments="mvsk"), + [mean, var, skew, kurtosis]) + + def test_pmf_integer_k(self): + k = np.arange(0, 1000) + k_int32 = k.astype(np.int32) + dist = zipfian(111, 22) + pmf = dist.pmf(k) + pmf_k_int32 = dist.pmf(k_int32) + assert_equal(pmf, pmf_k_int32) + + # Reference values were computed with mpmath. + @pytest.mark.parametrize( + 'k, a, n, ref', + [(3, 1 + 1e-12, 10, 0.11380571738244807), + (995, 1 + 1e-9, 1000, 0.0001342634472310051)] + ) + def test_pmf_against_mpmath(self, k, a, n, ref): + p = zipfian.pmf(k, a, n) + assert_allclose(p, ref, 5e-16) + + # Reference values were computed with mpmath. + @pytest.mark.parametrize( + 'k, a, n, ref', + [(4990, 1.25, 5000, 5.780138225335147e-05), + (9998, 3.5, 10000, 1.775352757966365e-14), + (50000, 3.5, 100000, 5.227803621367486e-13), + (10, 6.0, 100, 1.523108153557902e-06), + (95, 6.0, 100, 5.572438078308601e-12)] + ) + def test_sf_against_mpmath(self, k, a, n, ref): + sf = zipfian.sf(k, a, n) + assert_allclose(sf, ref, rtol=8e-15) + + # Reference values were computed with mpmath. + @pytest.mark.parametrize( + 'a, n, ref', + [(1 + 1e-9, 100, 19.27756356649707), + (1 + 1e-7, 100000, 8271.194454731552), + (1.001, 3, 1.6360225521183804)] + ) + def test_mean_against_mpmath(self, a, n, ref): + m = zipfian.mean(a, n) + assert_allclose(m, ref, rtol=8e-15) + + def test_ridiculously_large_n(self): + # This should return nan with no errors or warnings. + a = 2.5 + n = 1e100 + p = zipfian.pmf(10, a, n) + assert_equal(p, np.nan) + + +class TestNCH: + def setup_method(self): + rng = np.random.default_rng(7162434334) + shape = (2, 4, 3) + max_m = 100 + m1 = rng.integers(1, max_m, size=shape) # red balls + m2 = rng.integers(1, max_m, size=shape) # white balls + N = m1 + m2 # total balls + n = randint.rvs(0, N, size=N.shape, random_state=rng) # number of draws + xl = np.maximum(0, n-m2) # lower bound of support + xu = np.minimum(n, m1) # upper bound of support + x = randint.rvs(xl, xu, size=xl.shape, random_state=rng) + odds = rng.random(x.shape)*2 + self.x, self.N, self.m1, self.n, self.odds = x, N, m1, n, odds + + # test output is more readable when function names (strings) are passed + @pytest.mark.parametrize('dist_name', + ['nchypergeom_fisher', 'nchypergeom_wallenius']) + def test_nch_hypergeom(self, dist_name): + # Both noncentral hypergeometric distributions reduce to the + # hypergeometric distribution when odds = 1 + dists = {'nchypergeom_fisher': nchypergeom_fisher, + 'nchypergeom_wallenius': nchypergeom_wallenius} + dist = dists[dist_name] + x, N, m1, n = self.x, self.N, self.m1, self.n + assert_allclose(dist.pmf(x, N, m1, n, odds=1), + hypergeom.pmf(x, N, m1, n)) + + def test_nchypergeom_fisher_naive(self): + # test against a very simple implementation + x, N, m1, n, odds = self.x, self.N, self.m1, self.n, self.odds + + @np.vectorize + def pmf_mean_var(x, N, m1, n, w): + # simple implementation of nchypergeom_fisher pmf + m2 = N - m1 + xl = np.maximum(0, n-m2) + xu = np.minimum(n, m1) + + def f(x): + t1 = special_binom(m1, x) + t2 = special_binom(m2, n - x) + return t1 * t2 * w**x + + def P(k): + return sum(f(y)*y**k for y in range(xl, xu + 1)) + + P0 = P(0) + P1 = P(1) + P2 = P(2) + pmf = f(x) / P0 + mean = P1 / P0 + var = P2 / P0 - (P1 / P0)**2 + return pmf, mean, var + + pmf, mean, var = pmf_mean_var(x, N, m1, n, odds) + assert_allclose(nchypergeom_fisher.pmf(x, N, m1, n, odds), pmf) + assert_allclose(nchypergeom_fisher.stats(N, m1, n, odds, moments='m'), + mean) + assert_allclose(nchypergeom_fisher.stats(N, m1, n, odds, moments='v'), + var) + + def test_nchypergeom_wallenius_naive(self): + # test against a very simple implementation + + rng = np.random.RandomState(2) + shape = (2, 4, 3) + max_m = 100 + m1 = rng.randint(1, max_m, size=shape) + m2 = rng.randint(1, max_m, size=shape) + N = m1 + m2 + n = randint.rvs(0, N, size=N.shape, random_state=rng) + xl = np.maximum(0, n-m2) + xu = np.minimum(n, m1) + x = randint.rvs(xl, xu, size=xl.shape, random_state=rng) + w = rng.rand(*x.shape)*2 + + def support(N, m1, n, w): + m2 = N - m1 + xl = np.maximum(0, n-m2) + xu = np.minimum(n, m1) + return xl, xu + + @np.vectorize + def mean(N, m1, n, w): + m2 = N - m1 + xl, xu = support(N, m1, n, w) + + def fun(u): + return u/m1 + (1 - (n-u)/m2)**w - 1 + + return root_scalar(fun, bracket=(xl, xu)).root + + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=RuntimeWarning, + message="invalid value encountered in mean") + assert_allclose(nchypergeom_wallenius.mean(N, m1, n, w), + mean(N, m1, n, w), rtol=2e-2) + + @np.vectorize + def variance(N, m1, n, w): + m2 = N - m1 + u = mean(N, m1, n, w) + a = u * (m1 - u) + b = (n-u)*(u + m2 - n) + return N*a*b / ((N-1) * (m1*b + m2*a)) + + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=RuntimeWarning, + message="invalid value encountered in mean") + assert_allclose( + nchypergeom_wallenius.stats(N, m1, n, w, moments='v'), + variance(N, m1, n, w), + rtol=5e-2 + ) + + @np.vectorize + def pmf(x, N, m1, n, w): + m2 = N - m1 + xl, xu = support(N, m1, n, w) + + def integrand(t): + D = w*(m1 - x) + (m2 - (n-x)) + res = (1-t**(w/D))**x * (1-t**(1/D))**(n-x) + return res + + def f(x): + t1 = special_binom(m1, x) + t2 = special_binom(m2, n - x) + the_integral = quad(integrand, 0, 1, + epsrel=1e-16, epsabs=1e-16) + return t1 * t2 * the_integral[0] + + return f(x) + + pmf0 = pmf(x, N, m1, n, w) + pmf1 = nchypergeom_wallenius.pmf(x, N, m1, n, w) + + atol, rtol = 1e-6, 1e-6 + i = np.abs(pmf1 - pmf0) < atol + rtol*np.abs(pmf0) + assert i.sum() > np.prod(shape) / 2 # works at least half the time + + # for those that fail, discredit the naive implementation + for N, m1, n, w in zip(N[~i], m1[~i], n[~i], w[~i]): + # get the support + m2 = N - m1 + xl, xu = support(N, m1, n, w) + x = np.arange(xl, xu + 1) + + # calculate sum of pmf over the support + # the naive implementation is very wrong in these cases + assert pmf(x, N, m1, n, w).sum() < .5 + assert_allclose(nchypergeom_wallenius.pmf(x, N, m1, n, w).sum(), 1) + + def test_wallenius_against_mpmath(self): + # precompute data with mpmath since naive implementation above + # is not reliable. See source code in gh-13330. + M = 50 + n = 30 + N = 20 + odds = 2.25 + # Expected results, computed with mpmath. + sup = np.arange(21) + pmf = np.array([3.699003068656875e-20, + 5.89398584245431e-17, + 2.1594437742911123e-14, + 3.221458044649955e-12, + 2.4658279241205077e-10, + 1.0965862603981212e-08, + 3.057890479665704e-07, + 5.622818831643761e-06, + 7.056482841531681e-05, + 0.000618899425358671, + 0.003854172932571669, + 0.01720592676256026, + 0.05528844897093792, + 0.12772363313574242, + 0.21065898367825722, + 0.24465958845359234, + 0.1955114898110033, + 0.10355390084949237, + 0.03414490375225675, + 0.006231989845775931, + 0.0004715577304677075]) + mean = 14.808018384813426 + var = 2.6085975877923717 + + # nchypergeom_wallenius.pmf returns 0 for pmf(0) and pmf(1), and pmf(2) + # has only three digits of accuracy (~ 2.1511e-14). + assert_allclose(nchypergeom_wallenius.pmf(sup, M, n, N, odds), pmf, + rtol=1e-13, atol=1e-13) + assert_allclose(nchypergeom_wallenius.mean(M, n, N, odds), + mean, rtol=1e-13) + assert_allclose(nchypergeom_wallenius.var(M, n, N, odds), + var, rtol=1e-11) + + @pytest.mark.parametrize('dist_name', + ['nchypergeom_fisher', 'nchypergeom_wallenius']) + def test_rvs_shape(self, dist_name): + # Check that when given a size with more dimensions than the + # dimensions of the broadcast parameters, rvs returns an array + # with the correct shape. + dists = {'nchypergeom_fisher': nchypergeom_fisher, + 'nchypergeom_wallenius': nchypergeom_wallenius} + dist = dists[dist_name] + x = dist.rvs(50, 30, [[10], [20]], [0.5, 1.0, 2.0], size=(5, 1, 2, 3)) + assert x.shape == (5, 1, 2, 3) + + +@pytest.mark.parametrize("mu, q, expected", + [[10, 120, -1.240089881791596e-38], + [1500, 0, -86.61466680572661]]) +def test_nbinom_11465(mu, q, expected): + # test nbinom.logcdf at extreme tails + size = 20 + n, p = size, size/(size+mu) + # In R: + # options(digits=16) + # pnbinom(mu=10, size=20, q=120, log.p=TRUE) + assert_allclose(nbinom.logcdf(q, n, p), expected) + + +def test_gh_17146(): + # Check that discrete distributions return PMF of zero at non-integral x. + # See gh-17146. + x = np.linspace(0, 1, 11) + p = 0.8 + pmf = bernoulli(p).pmf(x) + i = (x % 1 == 0) + assert_allclose(pmf[-1], p) + assert_allclose(pmf[0], 1-p) + assert_equal(pmf[~i], 0) + + +class TestBetaNBinom: + @pytest.mark.parametrize('x, n, a, b, ref', + [[5, 5e6, 5, 20, 1.1520944824139114e-107], + [100, 50, 5, 20, 0.002855762954310226], + [10000, 1000, 5, 20, 1.9648515726019154e-05]]) + def test_betanbinom_pmf(self, x, n, a, b, ref): + # test that PMF stays accurate in the distribution tails + # reference values computed with mpmath + # from mpmath import mp + # mp.dps = 500 + # def betanbinom_pmf(k, n, a, b): + # k = mp.mpf(k) + # a = mp.mpf(a) + # b = mp.mpf(b) + # n = mp.mpf(n) + # return float(mp.binomial(n + k - mp.one, k) + # * mp.beta(a + n, b + k) / mp.beta(a, b)) + assert_allclose(betanbinom.pmf(x, n, a, b), ref, rtol=1e-10) + + + @pytest.mark.parametrize('n, a, b, ref', + [[10000, 5000, 50, 0.12841520515722202], + [10, 9, 9, 7.9224400871459695], + [100, 1000, 10, 1.5849602176622748]]) + def test_betanbinom_kurtosis(self, n, a, b, ref): + # reference values were computed via mpmath + # from mpmath import mp + # def kurtosis_betanegbinom(n, a, b): + # n = mp.mpf(n) + # a = mp.mpf(a) + # b = mp.mpf(b) + # four = mp.mpf(4.) + # mean = n * b / (a - mp.one) + # var = (n * b * (n + a - 1.) * (a + b - 1.) + # / ((a - 2.) * (a - 1.)**2.)) + # def f(k): + # return (mp.binomial(n + k - mp.one, k) + # * mp.beta(a + n, b + k) / mp.beta(a, b) + # * (k - mean)**four) + # fourth_moment = mp.nsum(f, [0, mp.inf]) + # return float(fourth_moment/var**2 - 3.) + assert_allclose(betanbinom.stats(n, a, b, moments="k"), + ref, rtol=3e-15) + + +class TestZipf: + def test_gh20692(self): + # test that int32 data for k generates same output as double + k = np.arange(0, 1000) + k_int32 = k.astype(np.int32) + dist = zipf(9) + pmf = dist.pmf(k) + pmf_k_int32 = dist.pmf(k_int32) + assert_equal(pmf, pmf_k_int32) + + +def test_gh20048(): + # gh-20048 reported an infinite loop in _drv2_ppfsingle + # check that the one identified is resolved + class test_dist_gen(stats.rv_discrete): + def _cdf(self, k): + return min(k / 100, 0.99) + + test_dist = test_dist_gen(b=np.inf) + + message = "Arguments that bracket..." + with pytest.raises(RuntimeError, match=message): + test_dist.ppf(0.999) + + +class TestPoissonBinomial: + def test_pmf(self): + # Test pmf against R `poisbinom` to confirm that this is indeed the Poisson + # binomial distribution. Consistency of other methods and all other behavior + # should be covered by generic tests. (If not, please add a generic test.) + # Like many other distributions, no special attempt is made to be more + # accurate than the usual formulas provide, so we use default tolerances. + # + # library(poisbinom) + # options(digits=16) + # k = c(0, 1, 2, 3, 4) + # p = c(0.9480654803913988, 0.052428488100509374, + # 0.25863527358887417, 0.057764076043633206) + # dpoisbinom(k, p) + rng = np.random.default_rng(259823598254) + n = rng.integers(10) # 4 + k = np.arange(n + 1) + p = rng.random(n) # [0.9480654803913988, 0.052428488100509374, + # 0.25863527358887417, 0.057764076043633206] + res = poisson_binom.pmf(k, p) + ref = [0.0343763443678060318, 0.6435428452689714307, 0.2936345519235536994, + 0.0277036647503902354, 0.0007425936892786034] + assert_allclose(res, ref) + + +class TestRandInt: + def test_gh19759(self): + # test zero PMF values within the support reported by gh-19759 + a = -354 + max_range = abs(a) + all_b_1 = [a + 2 ** 31 + i for i in range(max_range)] + res = randint.pmf(325, a, all_b_1) + assert (res > 0).all() + ref = 1 / (np.asarray(all_b_1, dtype=np.float64) - a) + assert_allclose(res, ref) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/test_distributions.py b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_distributions.py new file mode 100644 index 0000000000000000000000000000000000000000..a355a702807438358bd88c00de026008055dbc51 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_distributions.py @@ -0,0 +1,10606 @@ +""" +Test functions for stats module +""" +import warnings +import re +import sys +import pickle +import threading +from pathlib import Path +import os +import json +import platform + +from numpy.testing import (assert_equal, assert_array_equal, + assert_almost_equal, assert_array_almost_equal, + assert_allclose, assert_, + assert_array_less, assert_array_max_ulp) +import pytest +from pytest import raises as assert_raises + +import numpy as np +from numpy import typecodes, array +from numpy.lib.recfunctions import rec_append_fields +from scipy import special +from scipy._lib._util import check_random_state +from scipy.integrate import (IntegrationWarning, quad, trapezoid, + cumulative_trapezoid) +import scipy.stats as stats +from scipy.stats._distn_infrastructure import argsreduce +from scipy.stats._constants import _XMAX +import scipy.stats.distributions + +from scipy.special import xlogy, polygamma, entr +from scipy.stats._distr_params import distcont, invdistcont +from .test_discrete_basic import distdiscrete, invdistdiscrete +from scipy.stats._continuous_distns import FitDataError, _argus_phi +from scipy.optimize import root, fmin, differential_evolution +from itertools import product + +# python -OO strips docstrings +DOCSTRINGS_STRIPPED = sys.flags.optimize > 1 + +# Failing on macOS 11, Intel CPUs. See gh-14901 +MACOS_INTEL = (sys.platform == 'darwin') and (platform.machine() == 'x86_64') + + +# distributions to skip while testing the fix for the support method +# introduced in gh-13294. These distributions are skipped as they +# always return a non-nan support for every parametrization. +skip_test_support_gh13294_regression = ['tukeylambda', 'pearson3'] + + +def _assert_hasattr(a, b, msg=None): + if msg is None: + msg = f'{a} does not have attribute {b}' + assert_(hasattr(a, b), msg=msg) + + +def test_api_regression(): + # https://github.com/scipy/scipy/issues/3802 + _assert_hasattr(scipy.stats.distributions, 'f_gen') + + +def test_distributions_submodule(): + actual = set(scipy.stats.distributions.__all__) + continuous = [dist[0] for dist in distcont] # continuous dist names + discrete = [dist[0] for dist in distdiscrete] # discrete dist names + other = ['rv_discrete', 'rv_continuous', 'rv_histogram', + 'entropy'] + expected = continuous + discrete + other + + # need to remove, e.g., + # + expected = set(filter(lambda s: not str(s).startswith('<'), expected)) + + assert actual == expected + + +class TestVonMises: + def setup_method(self): + self.rng = np.random.default_rng(6320571663) + + @pytest.mark.parametrize('k', [0.1, 1, 101]) + @pytest.mark.parametrize('x', [0, 1, np.pi, 10, 100]) + def test_vonmises_periodic(self, k, x): + def check_vonmises_pdf_periodic(k, L, s, x): + vm = stats.vonmises(k, loc=L, scale=s) + assert_almost_equal(vm.pdf(x), vm.pdf(x % (2 * np.pi * s))) + + def check_vonmises_cdf_periodic(k, L, s, x): + vm = stats.vonmises(k, loc=L, scale=s) + assert_almost_equal(vm.cdf(x) % 1, + vm.cdf(x % (2 * np.pi * s)) % 1) + + check_vonmises_pdf_periodic(k, 0, 1, x) + check_vonmises_pdf_periodic(k, 1, 1, x) + check_vonmises_pdf_periodic(k, 0, 10, x) + + check_vonmises_cdf_periodic(k, 0, 1, x) + check_vonmises_cdf_periodic(k, 1, 1, x) + check_vonmises_cdf_periodic(k, 0, 10, x) + + def test_vonmises_line_support(self): + assert_equal(stats.vonmises_line.a, -np.pi) + assert_equal(stats.vonmises_line.b, np.pi) + + def test_vonmises_numerical(self): + vm = stats.vonmises(800) + assert_almost_equal(vm.cdf(0), 0.5) + + # Expected values of the vonmises PDF were computed using + # mpmath with 50 digits of precision: + # + # def vmpdf_mp(x, kappa): + # x = mpmath.mpf(x) + # kappa = mpmath.mpf(kappa) + # num = mpmath.exp(kappa*mpmath.cos(x)) + # den = 2 * mpmath.pi * mpmath.besseli(0, kappa) + # return num/den + + @pytest.mark.parametrize('x, kappa, expected_pdf', + [(0.1, 0.01, 0.16074242744907072), + (0.1, 25.0, 1.7515464099118245), + (0.1, 800, 0.2073272544458798), + (2.0, 0.01, 0.15849003875385817), + (2.0, 25.0, 8.356882934278192e-16), + (2.0, 800, 0.0)]) + def test_vonmises_pdf(self, x, kappa, expected_pdf): + pdf = stats.vonmises.pdf(x, kappa) + assert_allclose(pdf, expected_pdf, rtol=1e-15) + + # Expected values of the vonmises entropy were computed using + # mpmath with 50 digits of precision: + # + # def vonmises_entropy(kappa): + # kappa = mpmath.mpf(kappa) + # return (-kappa * mpmath.besseli(1, kappa) / + # mpmath.besseli(0, kappa) + mpmath.log(2 * mpmath.pi * + # mpmath.besseli(0, kappa))) + # >>> float(vonmises_entropy(kappa)) + + @pytest.mark.parametrize('kappa, expected_entropy', + [(1, 1.6274014590199897), + (5, 0.6756431570114528), + (100, -0.8811275441649473), + (1000, -2.03468891852547), + (2000, -2.3813876496587847)]) + def test_vonmises_entropy(self, kappa, expected_entropy): + entropy = stats.vonmises.entropy(kappa) + assert_allclose(entropy, expected_entropy, rtol=1e-13) + + def test_vonmises_rvs_gh4598(self): + # check that random variates wrap around as discussed in gh-4598 + seed = 30899520 + rng1 = np.random.default_rng(seed) + rng2 = np.random.default_rng(seed) + rng3 = np.random.default_rng(seed) + rvs1 = stats.vonmises(1, loc=0, scale=1).rvs(random_state=rng1) + rvs2 = stats.vonmises(1, loc=2*np.pi, scale=1).rvs(random_state=rng2) + rvs3 = stats.vonmises(1, loc=0, + scale=(2*np.pi/abs(rvs1)+1)).rvs(random_state=rng3) + assert_allclose(rvs1, rvs2, atol=1e-15) + assert_allclose(rvs1, rvs3, atol=1e-15) + + # Expected values of the vonmises LOGPDF were computed + # using wolfram alpha: + # kappa * cos(x) - log(2*pi*I0(kappa)) + @pytest.mark.parametrize('x, kappa, expected_logpdf', + [(0.1, 0.01, -1.8279520246003170), + (0.1, 25.0, 0.5604990605420549), + (0.1, 800, -1.5734567947337514), + (2.0, 0.01, -1.8420635346185686), + (2.0, 25.0, -34.7182759850871489), + (2.0, 800, -1130.4942582548682739)]) + def test_vonmises_logpdf(self, x, kappa, expected_logpdf): + logpdf = stats.vonmises.logpdf(x, kappa) + assert_allclose(logpdf, expected_logpdf, rtol=1e-15) + + def test_vonmises_expect(self): + """ + Test that the vonmises expectation values are + computed correctly. This test checks that the + numeric integration estimates the correct normalization + (1) and mean angle (loc). These expectations are + independent of the chosen 2pi interval. + """ + rng = np.random.default_rng(6762668991392531563) + + loc, kappa, lb = rng.random(3) * 10 + res = stats.vonmises(loc=loc, kappa=kappa).expect(lambda x: 1) + assert_allclose(res, 1) + assert np.issubdtype(res.dtype, np.floating) + + bounds = lb, lb + 2 * np.pi + res = stats.vonmises(loc=loc, kappa=kappa).expect(lambda x: 1, *bounds) + assert_allclose(res, 1) + assert np.issubdtype(res.dtype, np.floating) + + bounds = lb, lb + 2 * np.pi + res = stats.vonmises(loc=loc, kappa=kappa).expect(lambda x: np.exp(1j*x), + *bounds, complex_func=1) + assert_allclose(np.angle(res), loc % (2*np.pi)) + assert np.issubdtype(res.dtype, np.complexfloating) + + @pytest.mark.xslow + @pytest.mark.parametrize("rvs_loc", [0, 2]) + @pytest.mark.parametrize("rvs_shape", [1, 100, 1e8]) + @pytest.mark.parametrize('fix_loc', [True, False]) + @pytest.mark.parametrize('fix_shape', [True, False]) + def test_fit_MLE_comp_optimizer(self, rvs_loc, rvs_shape, + fix_loc, fix_shape): + if fix_shape and fix_loc: + pytest.skip("Nothing to fit.") + + rng = np.random.default_rng(6762668991392531563) + data = stats.vonmises.rvs(rvs_shape, size=1000, loc=rvs_loc, + random_state=rng) + + kwds = {'fscale': 1} + if fix_loc: + kwds['floc'] = rvs_loc + if fix_shape: + kwds['f0'] = rvs_shape + + _assert_less_or_close_loglike(stats.vonmises, data, + stats.vonmises.nnlf, **kwds) + + @pytest.mark.slow + def test_vonmises_fit_bad_floc(self): + data = [-0.92923506, -0.32498224, 0.13054989, -0.97252014, 2.79658071, + -0.89110948, 1.22520295, 1.44398065, 2.49163859, 1.50315096, + 3.05437696, -2.73126329, -3.06272048, 1.64647173, 1.94509247, + -1.14328023, 0.8499056, 2.36714682, -1.6823179, -0.88359996] + data = np.asarray(data) + loc = -0.5 * np.pi + kappa_fit, loc_fit, scale_fit = stats.vonmises.fit(data, floc=loc) + assert kappa_fit == np.finfo(float).tiny + _assert_less_or_close_loglike(stats.vonmises, data, + stats.vonmises.nnlf, fscale=1, floc=loc) + + @pytest.mark.parametrize('sign', [-1, 1]) + def test_vonmises_fit_unwrapped_data(self, sign): + rng = np.random.default_rng(6762668991392531563) + data = stats.vonmises(loc=sign*0.5*np.pi, kappa=10).rvs(100000, + random_state=rng) + shifted_data = data + 4*np.pi + kappa_fit, loc_fit, scale_fit = stats.vonmises.fit(data) + kappa_fit_shifted, loc_fit_shifted, _ = stats.vonmises.fit(shifted_data) + assert_allclose(loc_fit, loc_fit_shifted) + assert_allclose(kappa_fit, kappa_fit_shifted) + assert scale_fit == 1 + assert -np.pi < loc_fit < np.pi + + def test_vonmises_kappa_0_gh18166(self): + # Check that kappa = 0 is supported. + dist = stats.vonmises(0) + assert_allclose(dist.pdf(0), 1 / (2 * np.pi), rtol=1e-15) + assert_allclose(dist.cdf(np.pi/2), 0.75, rtol=1e-15) + assert_allclose(dist.sf(-np.pi/2), 0.75, rtol=1e-15) + assert_allclose(dist.ppf(0.9), np.pi*0.8, rtol=1e-15) + assert_allclose(dist.mean(), 0, atol=1e-15) + assert_allclose(dist.expect(), 0, atol=1e-15) + assert np.all(np.abs(dist.rvs(size=10, random_state=self.rng)) <= np.pi) + + def test_vonmises_fit_equal_data(self): + # When all data are equal, expect kappa = 1e16. + kappa, loc, scale = stats.vonmises.fit([0]) + assert kappa == 1e16 and loc == 0 and scale == 1 + + def test_vonmises_fit_bounds(self): + # For certain input data, the root bracket is violated numerically. + # Test that this situation is handled. The input data below are + # crafted to trigger the bound violation for the current choice of + # bounds and the specific way the bounds and the objective function + # are computed. + + # Test that no exception is raised when the lower bound is violated. + scipy.stats.vonmises.fit([0, 3.7e-08], floc=0) + + # Test that no exception is raised when the upper bound is violated. + scipy.stats.vonmises.fit([np.pi/2*(1-4.86e-9)], floc=0) + + +def _assert_less_or_close_loglike(dist, data, func=None, maybe_identical=False, + **kwds): + """ + This utility function checks that the negative log-likelihood function + (or `func`) of the result computed using dist.fit() is less than or equal + to the result computed using the generic fit method. Because of + normal numerical imprecision, the "equality" check is made using + `np.allclose` with a relative tolerance of 1e-15. + """ + if func is None: + func = dist.nnlf + + mle_analytical = dist.fit(data, **kwds) + numerical_opt = super(type(dist), dist).fit(data, **kwds) + + # Sanity check that the analytical MLE is actually executed. + # Due to floating point arithmetic, the generic MLE is unlikely + # to produce the exact same result as the analytical MLE. + if not maybe_identical: + assert np.any(mle_analytical != numerical_opt) + + ll_mle_analytical = func(mle_analytical, data) + ll_numerical_opt = func(numerical_opt, data) + assert (ll_mle_analytical <= ll_numerical_opt or + np.allclose(ll_mle_analytical, ll_numerical_opt, rtol=1e-15)) + + # Ideally we'd check that shapes are correctly fixed, too, but that is + # complicated by the many ways of fixing them (e.g. f0, fix_a, fa). + if 'floc' in kwds: + assert mle_analytical[-2] == kwds['floc'] + if 'fscale' in kwds: + assert mle_analytical[-1] == kwds['fscale'] + + +def assert_fit_warnings(dist): + param = ['floc', 'fscale'] + if dist.shapes: + nshapes = len(dist.shapes.split(",")) + param += ['f0', 'f1', 'f2'][:nshapes] + all_fixed = dict(zip(param, np.arange(len(param)))) + data = [1, 2, 3] + with pytest.raises(RuntimeError, + match="All parameters fixed. There is nothing " + "to optimize."): + dist.fit(data, **all_fixed) + with pytest.raises(ValueError, + match="The data contains non-finite values"): + dist.fit([np.nan]) + with pytest.raises(ValueError, + match="The data contains non-finite values"): + dist.fit([np.inf]) + with pytest.raises(TypeError, match="Unknown keyword arguments:"): + dist.fit(data, extra_keyword=2) + with pytest.raises(TypeError, match="Too many positional arguments."): + dist.fit(data, *[1]*(len(param) - 1)) + + +@pytest.mark.parametrize('dist', + ['alpha', 'betaprime', + 'fatiguelife', 'invgamma', 'invgauss', 'invweibull', + 'johnsonsb', 'levy', 'levy_l', 'lognorm', 'gibrat', + 'powerlognorm', 'rayleigh', 'wald']) +def test_support(dist): + """gh-6235""" + dct = dict(distcont) + args = dct[dist] + + dist = getattr(stats, dist) + + assert_almost_equal(dist.pdf(dist.a, *args), 0) + assert_equal(dist.logpdf(dist.a, *args), -np.inf) + assert_almost_equal(dist.pdf(dist.b, *args), 0) + assert_equal(dist.logpdf(dist.b, *args), -np.inf) + + +class TestRandInt: + def setup_method(self): + self.rng = np.random.default_rng(8826485737) + + def test_rvs(self): + vals = stats.randint.rvs(5, 30, size=100, random_state=self.rng) + assert_(np.all(vals < 30) & np.all(vals >= 5)) + assert_(len(vals) == 100) + vals = stats.randint.rvs(5, 30, size=(2, 50), random_state=self.rng) + assert_(np.shape(vals) == (2, 50)) + assert_(vals.dtype.char in typecodes['AllInteger']) + val = stats.randint.rvs(15, 46, random_state=self.rng) + assert_((val >= 15) & (val < 46)) + assert_(isinstance(val, np.ScalarType), msg=repr(type(val))) + val = stats.randint(15, 46).rvs(3, random_state=self.rng) + assert_(val.dtype.char in typecodes['AllInteger']) + + def test_pdf(self): + k = np.r_[0:36] + out = np.where((k >= 5) & (k < 30), 1.0/(30-5), 0) + vals = stats.randint.pmf(k, 5, 30) + assert_array_almost_equal(vals, out) + + def test_cdf(self): + x = np.linspace(0, 36, 100) + k = np.floor(x) + out = np.select([k >= 30, k >= 5], [1.0, (k-5.0+1)/(30-5.0)], 0) + vals = stats.randint.cdf(x, 5, 30) + assert_array_almost_equal(vals, out, decimal=12) + + +class TestBinom: + def setup_method(self): + self.rng = np.random.default_rng(1778595878) + + def test_rvs(self): + vals = stats.binom.rvs(10, 0.75, size=(2, 50), random_state=self.rng) + assert_(np.all(vals >= 0) & np.all(vals <= 10)) + assert_(np.shape(vals) == (2, 50)) + assert_(vals.dtype.char in typecodes['AllInteger']) + val = stats.binom.rvs(10, 0.75, random_state=self.rng) + assert_(isinstance(val, int)) + val = stats.binom(10, 0.75).rvs(3, random_state=self.rng) + assert_(isinstance(val, np.ndarray)) + assert_(val.dtype.char in typecodes['AllInteger']) + + def test_pmf(self): + # regression test for Ticket #1842 + vals1 = stats.binom.pmf(100, 100, 1) + vals2 = stats.binom.pmf(0, 100, 0) + assert_allclose(vals1, 1.0, rtol=1e-15, atol=0) + assert_allclose(vals2, 1.0, rtol=1e-15, atol=0) + + def test_entropy(self): + # Basic entropy tests. + b = stats.binom(2, 0.5) + expected_p = np.array([0.25, 0.5, 0.25]) + expected_h = -sum(xlogy(expected_p, expected_p)) + h = b.entropy() + assert_allclose(h, expected_h) + + b = stats.binom(2, 0.0) + h = b.entropy() + assert_equal(h, 0.0) + + b = stats.binom(2, 1.0) + h = b.entropy() + assert_equal(h, 0.0) + + def test_warns_p0(self): + # no spurious warnings are generated for p=0; gh-3817 + with warnings.catch_warnings(): + warnings.simplefilter("error", RuntimeWarning) + assert_equal(stats.binom(n=2, p=0).mean(), 0) + assert_equal(stats.binom(n=2, p=0).std(), 0) + + def test_ppf_p1(self): + # Check that gh-17388 is resolved: PPF == n when p = 1 + n = 4 + assert stats.binom.ppf(q=0.3, n=n, p=1.0) == n + + def test_pmf_poisson(self): + # Check that gh-17146 is resolved: binom -> poisson + n = 1541096362225563.0 + p = 1.0477878413173978e-18 + x = np.arange(3) + res = stats.binom.pmf(x, n=n, p=p) + ref = stats.poisson.pmf(x, n * p) + assert_allclose(res, ref, atol=1e-16) + + def test_pmf_cdf(self): + # Check that gh-17809 is resolved: binom.pmf(0) ~ binom.cdf(0) + n = 25.0 * 10 ** 21 + p = 1.0 * 10 ** -21 + r = 0 + res = stats.binom.pmf(r, n, p) + ref = stats.binom.cdf(r, n, p) + assert_allclose(res, ref, atol=1e-16) + + def test_pmf_gh15101(self): + # Check that gh-15101 is resolved (no divide warnings when p~1, n~oo) + res = stats.binom.pmf(3, 2000, 0.999) + assert_allclose(res, 0, atol=1e-16) + + +class TestArcsine: + + def test_endpoints(self): + # Regression test for gh-13697. The following calculation + # should not generate a warning. + p = stats.arcsine.pdf([0, 1]) + assert_equal(p, [np.inf, np.inf]) + + +class TestBernoulli: + def setup_method(self): + self.rng = np.random.default_rng(7836792223) + + def test_rvs(self): + vals = stats.bernoulli.rvs(0.75, size=(2, 50), random_state=self.rng) + assert_(np.all(vals >= 0) & np.all(vals <= 1)) + assert_(np.shape(vals) == (2, 50)) + assert_(vals.dtype.char in typecodes['AllInteger']) + val = stats.bernoulli.rvs(0.75, random_state=self.rng) + assert_(isinstance(val, int)) + val = stats.bernoulli(0.75).rvs(3, random_state=self.rng) + assert_(isinstance(val, np.ndarray)) + assert_(val.dtype.char in typecodes['AllInteger']) + + def test_entropy(self): + # Simple tests of entropy. + b = stats.bernoulli(0.25) + expected_h = -0.25*np.log(0.25) - 0.75*np.log(0.75) + h = b.entropy() + assert_allclose(h, expected_h) + + b = stats.bernoulli(0.0) + h = b.entropy() + assert_equal(h, 0.0) + + b = stats.bernoulli(1.0) + h = b.entropy() + assert_equal(h, 0.0) + + +class TestBradford: + # gh-6216 + def test_cdf_ppf(self): + c = 0.1 + x = np.logspace(-20, -4) + q = stats.bradford.cdf(x, c) + xx = stats.bradford.ppf(q, c) + assert_allclose(x, xx) + + +class TestCauchy: + + def test_pdf_no_overflow_warning(self): + # The argument is large enough that x**2 will overflow to + # infinity and 1/(1 + x**2) will be 0. This should not + # trigger a warning. + p = stats.cauchy.pdf(1e200) + assert p == 0.0 + + # Reference values were computed with mpmath. + @pytest.mark.parametrize( + 'x, ref', + [(0.0, -1.1447298858494002), + (5e-324, -1.1447298858494002), + (1e-34, -1.1447298858494002), + (2.2e-16, -1.1447298858494002), + (2e-8, -1.1447298858494006), + (5e-4, -1.144730135849369), + (0.1, -1.1546802167025683), + (1.5, -2.3233848821910463), + (2e18, -85.42408759475494), + (1e200, -922.1787670834676), + (_XMAX, -1420.7101556726175)]) + def test_logpdf(self, x, ref): + logp = stats.cauchy.logpdf([x, -x]) + assert_allclose(logp, [ref, ref], rtol=1e-15) + + # Reference values were computed with mpmath. + @pytest.mark.parametrize( + 'x, ref', + [(-5e15, 6.366197723675814e-17), + (-5, 0.06283295818900118), + (-1, 0.25), + (0, 0.5), + (1, 0.75), + (5, 0.9371670418109989), + (5e15, 0.9999999999999999)] + ) + @pytest.mark.parametrize( + 'method, sgn', + [(stats.cauchy.cdf, 1), + (stats.cauchy.sf, -1)] + ) + def test_cdf_sf(self, x, ref, method, sgn): + p = method(sgn*x) + assert_allclose(p, ref, rtol=1e-15) + + # Reference values were computed with mpmath. + @pytest.mark.parametrize('x, ref', + [(4e250, -7.957747154594767e-252), + (1e25, -3.1830988618379063e-26), + (10.0, -0.03223967552667532), + (0.0, -0.6931471805599453), + (-10.0, -3.4506339556469654), + (-7e45, -106.70696921963678), + (-3e225, -520.3249880981778)]) + def test_logcdf_logsf(self, x, ref): + logcdf = stats.cauchy.logcdf(x) + assert_allclose(logcdf, ref, rtol=5e-15) + logsf = stats.cauchy.logsf(-x) + assert_allclose(logsf, ref, rtol=5e-15) + + # Reference values were computed with mpmath. + @pytest.mark.parametrize( + 'p, ref', + [(1e-20, -3.1830988618379067e+19), + (1e-9, -318309886.1837906), + (0.25, -1.0), + (0.50, 0.0), + (0.75, 1.0), + (0.999999, 318309.88617359026), + (0.999999999999, 318316927901.77966)] + ) + @pytest.mark.parametrize( + 'method, sgn', + [(stats.cauchy.ppf, 1), + (stats.cauchy.isf, -1)]) + def test_ppf_isf(self, p, ref, method, sgn): + x = sgn*method(p) + assert_allclose(x, ref, rtol=1e-15) + + +class TestChi: + + # "Exact" value of chi.sf(10, 4), as computed by Wolfram Alpha with + # 1 - CDF[ChiDistribution[4], 10] + CHI_SF_10_4 = 9.83662422461598e-21 + # "Exact" value of chi.mean(df=1000) as computed by Wolfram Alpha with + # Mean[ChiDistribution[1000]] + CHI_MEAN_1000 = 31.614871896980 + + def test_sf(self): + s = stats.chi.sf(10, 4) + assert_allclose(s, self.CHI_SF_10_4, rtol=1e-15) + + def test_isf(self): + x = stats.chi.isf(self.CHI_SF_10_4, 4) + assert_allclose(x, 10, rtol=1e-15) + + def test_logcdf(self): + x = 10.0 + df = 15 + logcdf = stats.chi.logcdf(x, df) + # Reference value computed with mpath. + assert_allclose(logcdf, -1.304704343625153e-14, rtol=5e-15) + + def test_logsf(self): + x = 0.01 + df = 15 + logsf = stats.chi.logsf(x, df) + # Reference value computed with mpath. + assert_allclose(logsf, -3.936060782678026e-37, rtol=5e-15) + + # reference value for 1e14 was computed via mpmath + # from mpmath import mp + # mp.dps = 500 + # df = mp.mpf(1e14) + # float(mp.rf(mp.mpf(0.5) * df, mp.mpf(0.5)) * mp.sqrt(2.)) + + @pytest.mark.parametrize('df, ref', + [(1e3, CHI_MEAN_1000), + (1e14, 9999999.999999976)]) + def test_mean(self, df, ref): + assert_allclose(stats.chi.mean(df), ref, rtol=1e-12) + + # Entropy references values were computed with the following mpmath code + # from mpmath import mp + # mp.dps = 50 + # def chi_entropy_mpmath(df): + # df = mp.mpf(df) + # half_df = 0.5 * df + # entropy = mp.log(mp.gamma(half_df)) + 0.5 * \ + # (df - mp.log(2) - (df - mp.one) * mp.digamma(half_df)) + # return float(entropy) + + @pytest.mark.parametrize('df, ref', + [(1e-4, -9989.7316027504), + (1, 0.7257913526447274), + (1e3, 1.0721981095025448), + (1e10, 1.0723649429080335), + (1e100, 1.0723649429247002)]) + def test_entropy(self, df, ref): + assert_allclose(stats.chi(df).entropy(), ref, rtol=1e-15) + + +class TestCrystalBall: + + def test_pdf(self): + """ + All values are calculated using the independent implementation of the + ROOT framework (see https://root.cern.ch/). + Corresponding ROOT code is given in the comments. + """ + X = np.linspace(-5.0, 5.0, 21)[:-1] + + # for (double x = -5.0; x < 5.0; x += 0.5) { + # cout << setprecision(16) + # << ROOT::Math::crystalball_pdf(x, 1.0, 2.0, 1.0) + # << ", "; + # } + calculated = stats.crystalball.pdf(X, beta=1.0, m=2.0) + expected = np.array([0.02028666423671257, 0.02414280702550917, + 0.02921279650086611, 0.03606518086526679, + 0.04564499453260328, 0.05961795204258388, + 0.08114665694685029, 0.1168511860034644, + 0.1825799781304131, 0.2656523006609301, + 0.3010234935475763, 0.2656523006609301, + 0.1825799781304131, 0.09772801991305094, + 0.0407390997601359, 0.01322604925508607, + 0.003344068947749631, 0.0006584862184997063, + 0.0001009821322058648, 1.206059579124873e-05]) + assert_allclose(expected, calculated, rtol=1e-14) + + # for (double x = -5.0; x < 5.0; x += 0.5) { + # cout << setprecision(16) + # << ROOT::Math::crystalball_pdf(x, 2.0, 3.0, 1.0) + # << ", "; + # } + calculated = stats.crystalball.pdf(X, beta=2.0, m=3.0) + expected = np.array([0.00196480373120913, 0.0027975428126005, + 0.004175923965164595, 0.006631212592830816, + 0.01145873536041165, 0.022380342500804, + 0.05304970074264653, 0.1272596164638828, + 0.237752264003024, 0.3459275029304401, + 0.3919872148188981, 0.3459275029304401, + 0.237752264003024, 0.1272596164638828, + 0.05304970074264653, 0.01722271623872227, + 0.004354584612458383, 0.0008574685508575863, + 0.000131497061187334, 1.570508433595375e-05]) + assert_allclose(expected, calculated, rtol=1e-14) + + # for (double x = -5.0; x < 5.0; x += 0.5) { + # cout << setprecision(16) + # << ROOT::Math::crystalball_pdf(x, 2.0, 3.0, 2.0, 0.5) + # << ", "; + # } + calculated = stats.crystalball.pdf(X, beta=2.0, m=3.0, loc=0.5, scale=2.0) + expected = np.array([0.007859214924836521, 0.011190171250402, + 0.01670369586065838, 0.02652485037132326, + 0.04238659020399594, 0.06362980823194138, + 0.08973241216601403, 0.118876132001512, + 0.1479437366093383, 0.17296375146522, + 0.1899635180461471, 0.1959936074094491, + 0.1899635180461471, 0.17296375146522, + 0.1479437366093383, 0.118876132001512, + 0.08973241216601403, 0.06362980823194138, + 0.04238659020399594, 0.02652485037132326]) + assert_allclose(expected, calculated, rtol=1e-14) + + def test_cdf(self): + """ + All values are calculated using the independent implementation of the + ROOT framework (see https://root.cern.ch/). + Corresponding ROOT code is given in the comments. + """ + X = np.linspace(-5.0, 5.0, 21)[:-1] + + # for (double x = -5.0; x < 5.0; x += 0.5) { + # cout << setprecision(16) + # << ROOT::Math::crystalball_cdf(x, 1.0, 2.0, 1.0) + # << ", "; + # } + calculated = stats.crystalball.cdf(X, beta=1.0, m=2.0) + expected = np.array([0.1217199854202754, 0.1327854386403005, + 0.1460639825043305, 0.1622933138937006, + 0.1825799781304132, 0.2086628321490436, + 0.2434399708405509, 0.292127965008661, + 0.3651599562608263, 0.4782542338198316, + 0.6227229998727213, 0.7671917659256111, + 0.8802860434846165, 0.9495903590367718, + 0.9828337969321823, 0.9953144721881936, + 0.9989814290402977, 0.9998244687978383, + 0.9999761023377818, 0.9999974362721522]) + assert_allclose(expected, calculated, rtol=1e-13) + + # for (double x = -5.0; x < 5.0; x += 0.5) { + # cout << setprecision(16) + # << ROOT::Math::crystalball_cdf(x, 2.0, 3.0, 1.0) + # << ", "; + # } + calculated = stats.crystalball.cdf(X, beta=2.0, m=3.0) + expected = np.array([0.004420808395220632, 0.005595085625200946, + 0.007307866939038177, 0.009946818889246312, + 0.01432341920051472, 0.02238034250080412, + 0.03978727555698502, 0.08307626432678494, + 0.1733230597116304, 0.3205923321191123, + 0.508716882020547, 0.6968414319219818, + 0.8441107043294638, 0.934357499714309, + 0.9776464884841091, 0.9938985925142876, + 0.9986736357721329, 0.9997714265214375, + 0.9999688809071239, 0.9999966615611068]) + assert_allclose(expected, calculated, rtol=1e-13) + + # for (double x = -5.0; x < 5.0; x += 0.5) { + # cout << setprecision(16) + # << ROOT::Math::crystalball_cdf(x, 2.0, 3.0, 2.0, 0.5); + # << ", "; + # } + calculated = stats.crystalball.cdf(X, beta=2.0, m=3.0, loc=0.5, scale=2.0) + expected = np.array([0.0176832335808822, 0.02238034250080412, + 0.02923146775615237, 0.03978727555698502, + 0.05679453901646225, 0.08307626432678494, + 0.1212416644828466, 0.1733230597116304, + 0.2401101486313661, 0.3205923321191123, + 0.4117313791289429, 0.508716882020547, + 0.6057023849121512, 0.6968414319219818, + 0.7773236154097279, 0.8441107043294638, + 0.8961920995582476, 0.934357499714309, + 0.9606392250246318, 0.9776464884841091]) + assert_allclose(expected, calculated, rtol=1e-13) + + # Reference value computed with ROOT, e.g. + # cout << setprecision(16) + # << ROOT::Math::crystalball_cdf_c(12.0, 1.0, 2.0, 1.0) + # << endl; + @pytest.mark.parametrize( + 'x, beta, m, rootref', + [(12.0, 1.0, 2.0, 1.340451684048897e-33), + (9.0, 4.0, 1.25, 1.12843537145273e-19), + (20, 0.1, 1.001, 6.929038716892384e-93), + (-4.5, 2.0, 3.0, 0.9944049143747991), + (-30.0, 0.5, 5.0, 0.9976994814571858), + (-1e50, 1.5, 1.1, 0.9999951099570382)] + ) + def test_sf(self, x, beta, m, rootref): + sf = stats.crystalball.sf(x, beta=beta, m=m) + assert_allclose(sf, rootref, rtol=1e-13) + + def test_moments(self): + """ + All values are calculated using the pdf formula and the integrate function + of Mathematica + """ + # The Last two (alpha, n) pairs test the special case n == alpha**2 + beta = np.array([2.0, 1.0, 3.0, 2.0, 3.0]) + m = np.array([3.0, 3.0, 2.0, 4.0, 9.0]) + + # The distribution should be correctly normalised + expected_0th_moment = np.array([1.0, 1.0, 1.0, 1.0, 1.0]) + calculated_0th_moment = stats.crystalball._munp(0, beta, m) + assert_allclose(expected_0th_moment, calculated_0th_moment, rtol=0.001) + + # calculated using wolframalpha.com + # e.g. for beta = 2 and m = 3 we calculate the norm like this: + # integrate exp(-x^2/2) from -2 to infinity + + # integrate (3/2)^3*exp(-2^2/2)*(3/2-2-x)^(-3) from -infinity to -2 + norm = np.array([2.5511, 3.01873, 2.51065, 2.53983, 2.507410455]) + + a = np.array([-0.21992, -3.03265, np.inf, -0.135335, -0.003174]) + expected_1th_moment = a / norm + calculated_1th_moment = stats.crystalball._munp(1, beta, m) + assert_allclose(expected_1th_moment, calculated_1th_moment, rtol=0.001) + + a = np.array([np.inf, np.inf, np.inf, 3.2616, 2.519908]) + expected_2th_moment = a / norm + calculated_2th_moment = stats.crystalball._munp(2, beta, m) + assert_allclose(expected_2th_moment, calculated_2th_moment, rtol=0.001) + + a = np.array([np.inf, np.inf, np.inf, np.inf, -0.0577668]) + expected_3th_moment = a / norm + calculated_3th_moment = stats.crystalball._munp(3, beta, m) + assert_allclose(expected_3th_moment, calculated_3th_moment, rtol=0.001) + + a = np.array([np.inf, np.inf, np.inf, np.inf, 7.78468]) + expected_4th_moment = a / norm + calculated_4th_moment = stats.crystalball._munp(4, beta, m) + assert_allclose(expected_4th_moment, calculated_4th_moment, rtol=0.001) + + a = np.array([np.inf, np.inf, np.inf, np.inf, -1.31086]) + expected_5th_moment = a / norm + calculated_5th_moment = stats.crystalball._munp(5, beta, m) + assert_allclose(expected_5th_moment, calculated_5th_moment, rtol=0.001) + + def test_entropy(self): + # regression test for gh-13602 + cb = stats.crystalball(2, 3) + res1 = cb.entropy() + # -20000 and 30 are negative and positive infinity, respectively + lo, hi, N = -20000, 30, 200000 + x = np.linspace(lo, hi, N) + res2 = trapezoid(entr(cb.pdf(x)), x) + assert_allclose(res1, res2, rtol=1e-7) + + +class TestNBinom: + def setup_method(self): + self.rng = np.random.default_rng(5861367021) + + def test_rvs(self): + vals = stats.nbinom.rvs(10, 0.75, size=(2, 50), random_state=self.rng) + assert_(np.all(vals >= 0)) + assert_(np.shape(vals) == (2, 50)) + assert_(vals.dtype.char in typecodes['AllInteger']) + val = stats.nbinom.rvs(10, 0.75, random_state=self.rng) + assert_(isinstance(val, int)) + val = stats.nbinom(10, 0.75).rvs(3, random_state=self.rng) + assert_(isinstance(val, np.ndarray)) + assert_(val.dtype.char in typecodes['AllInteger']) + + def test_pmf(self): + # regression test for ticket 1779 + assert_allclose(np.exp(stats.nbinom.logpmf(700, 721, 0.52)), + stats.nbinom.pmf(700, 721, 0.52)) + # logpmf(0,1,1) shouldn't return nan (regression test for gh-4029) + val = scipy.stats.nbinom.logpmf(0, 1, 1) + assert_equal(val, 0) + + def test_logcdf_gh16159(self): + # check that gh16159 is resolved. + vals = stats.nbinom.logcdf([0, 5, 0, 5], n=4.8, p=0.45) + ref = np.log(stats.nbinom.cdf([0, 5, 0, 5], n=4.8, p=0.45)) + assert_allclose(vals, ref) + + +class TestGenInvGauss: + def setup_method(self): + self.rng = np.random.default_rng(6473281180) + + @pytest.mark.slow + def test_rvs_with_mode_shift(self): + # ratio_unif w/ mode shift + gig = stats.geninvgauss(2.3, 1.5) + _, p = stats.kstest(gig.rvs(size=1500, random_state=self.rng), gig.cdf) + assert_equal(p > 0.05, True) + + @pytest.mark.slow + def test_rvs_without_mode_shift(self): + # ratio_unif w/o mode shift + gig = stats.geninvgauss(0.9, 0.75) + _, p = stats.kstest(gig.rvs(size=1500, random_state=self.rng), gig.cdf) + assert_equal(p > 0.05, True) + + @pytest.mark.slow + def test_rvs_new_method(self): + # new algorithm of Hoermann / Leydold + gig = stats.geninvgauss(0.1, 0.2) + _, p = stats.kstest(gig.rvs(size=1500, random_state=self.rng), gig.cdf) + assert_equal(p > 0.05, True) + + @pytest.mark.slow + def test_rvs_p_zero(self): + def my_ks_check(p, b): + gig = stats.geninvgauss(p, b) + rvs = gig.rvs(size=1500, random_state=self.rng) + return stats.kstest(rvs, gig.cdf)[1] > 0.05 + # boundary cases when p = 0 + assert_equal(my_ks_check(0, 0.2), True) # new algo + assert_equal(my_ks_check(0, 0.9), True) # ratio_unif w/o shift + assert_equal(my_ks_check(0, 1.5), True) # ratio_unif with shift + + def test_rvs_negative_p(self): + # if p negative, return inverse + assert_equal( + stats.geninvgauss(-1.5, 2).rvs(size=10, random_state=1234), + 1 / stats.geninvgauss(1.5, 2).rvs(size=10, random_state=1234)) + + def test_invgauss(self): + # test that invgauss is special case + ig = stats.geninvgauss.rvs(size=1500, p=-0.5, b=1, random_state=1464878613) + assert_equal(stats.kstest(ig, 'invgauss', args=[1])[1] > 0.15, True) + # test pdf and cdf + mu, x = 100, np.linspace(0.01, 1, 10) + pdf_ig = stats.geninvgauss.pdf(x, p=-0.5, b=1 / mu, scale=mu) + assert_allclose(pdf_ig, stats.invgauss(mu).pdf(x)) + cdf_ig = stats.geninvgauss.cdf(x, p=-0.5, b=1 / mu, scale=mu) + assert_allclose(cdf_ig, stats.invgauss(mu).cdf(x)) + + def test_pdf_R(self): + # test against R package GIGrvg + # x <- seq(0.01, 5, length.out = 10) + # GIGrvg::dgig(x, 0.5, 1, 1) + vals_R = np.array([2.081176820e-21, 4.488660034e-01, 3.747774338e-01, + 2.693297528e-01, 1.905637275e-01, 1.351476913e-01, + 9.636538981e-02, 6.909040154e-02, 4.978006801e-02, + 3.602084467e-02]) + x = np.linspace(0.01, 5, 10) + assert_allclose(vals_R, stats.geninvgauss.pdf(x, 0.5, 1)) + + def test_pdf_zero(self): + # pdf at 0 is 0, needs special treatment to avoid 1/x in pdf + assert_equal(stats.geninvgauss.pdf(0, 0.5, 0.5), 0) + # if x is large and p is moderate, make sure that pdf does not + # overflow because of x**(p-1); exp(-b*x) forces pdf to zero + assert_equal(stats.geninvgauss.pdf(2e6, 50, 2), 0) + + +class TestGenHyperbolic: + + def test_pdf_r(self): + # test against R package GeneralizedHyperbolic + # x <- seq(-10, 10, length.out = 10) + # GeneralizedHyperbolic::dghyp( + # x = x, lambda = 2, alpha = 2, beta = 1, delta = 1.5, mu = 0.5 + # ) + vals_R = np.array([ + 2.94895678275316e-13, 1.75746848647696e-10, 9.48149804073045e-08, + 4.17862521692026e-05, 0.0103947630463822, 0.240864958986839, + 0.162833527161649, 0.0374609592899472, 0.00634894847327781, + 0.000941920705790324 + ]) + + lmbda, alpha, beta = 2, 2, 1 + mu, delta = 0.5, 1.5 + args = (lmbda, alpha*delta, beta*delta) + + gh = stats.genhyperbolic(*args, loc=mu, scale=delta) + x = np.linspace(-10, 10, 10) + + assert_allclose(gh.pdf(x), vals_R, atol=0, rtol=1e-13) + + def test_cdf_r(self): + # test against R package GeneralizedHyperbolic + # q <- seq(-10, 10, length.out = 10) + # GeneralizedHyperbolic::pghyp( + # q = q, lambda = 2, alpha = 2, beta = 1, delta = 1.5, mu = 0.5 + # ) + vals_R = np.array([ + 1.01881590921421e-13, 6.13697274983578e-11, 3.37504977637992e-08, + 1.55258698166181e-05, 0.00447005453832497, 0.228935323956347, + 0.755759458895243, 0.953061062884484, 0.992598013917513, + 0.998942646586662 + ]) + + lmbda, alpha, beta = 2, 2, 1 + mu, delta = 0.5, 1.5 + args = (lmbda, alpha*delta, beta*delta) + + gh = stats.genhyperbolic(*args, loc=mu, scale=delta) + x = np.linspace(-10, 10, 10) + + assert_allclose(gh.cdf(x), vals_R, atol=0, rtol=1e-6) + + # The reference values were computed by implementing the PDF with mpmath + # and integrating it with mp.quad. The values were computed with + # mp.dps=250, and then again with mp.dps=400 to ensure the full 64 bit + # precision was computed. + @pytest.mark.parametrize( + 'x, p, a, b, loc, scale, ref', + [(-15, 2, 3, 1.5, 0.5, 1.5, 4.770036428808252e-20), + (-15, 10, 1.5, 0.25, 1, 5, 0.03282964575089294), + (-15, 10, 1.5, 1.375, 0, 1, 3.3711159600215594e-23), + (-15, 0.125, 1.5, 1.49995, 0, 1, 4.729401428898605e-23), + (-1, 0.125, 1.5, 1.49995, 0, 1, 0.0003565725914786859), + (5, -0.125, 1.5, 1.49995, 0, 1, 0.2600651974023352), + (5, -0.125, 1000, 999, 0, 1, 5.923270556517253e-28), + (20, -0.125, 1000, 999, 0, 1, 0.23452293711665634), + (40, -0.125, 1000, 999, 0, 1, 0.9999648749561968), + (60, -0.125, 1000, 999, 0, 1, 0.9999999999975475)] + ) + def test_cdf_mpmath(self, x, p, a, b, loc, scale, ref): + cdf = stats.genhyperbolic.cdf(x, p, a, b, loc=loc, scale=scale) + assert_allclose(cdf, ref, rtol=5e-12) + + # The reference values were computed by implementing the PDF with mpmath + # and integrating it with mp.quad. The values were computed with + # mp.dps=250, and then again with mp.dps=400 to ensure the full 64 bit + # precision was computed. + @pytest.mark.parametrize( + 'x, p, a, b, loc, scale, ref', + [(0, 1e-6, 12, -1, 0, 1, 0.38520358671350524), + (-1, 3, 2.5, 2.375, 1, 3, 0.9999901774267577), + (-20, 3, 2.5, 2.375, 1, 3, 1.0), + (25, 2, 3, 1.5, 0.5, 1.5, 8.593419916523976e-10), + (300, 10, 1.5, 0.25, 1, 5, 6.137415609872158e-24), + (60, -0.125, 1000, 999, 0, 1, 2.4524915075944173e-12), + (75, -0.125, 1000, 999, 0, 1, 2.9435194886214633e-18)] + ) + def test_sf_mpmath(self, x, p, a, b, loc, scale, ref): + sf = stats.genhyperbolic.sf(x, p, a, b, loc=loc, scale=scale) + assert_allclose(sf, ref, rtol=5e-12) + + def test_moments_r(self): + # test against R package GeneralizedHyperbolic + # sapply(1:4, + # function(x) GeneralizedHyperbolic::ghypMom( + # order = x, lambda = 2, alpha = 2, + # beta = 1, delta = 1.5, mu = 0.5, + # momType = 'raw') + # ) + + vals_R = [2.36848366948115, 8.4739346779246, + 37.8870502710066, 205.76608511485] + + lmbda, alpha, beta = 2, 2, 1 + mu, delta = 0.5, 1.5 + args = (lmbda, alpha*delta, beta*delta) + + vals_us = [ + stats.genhyperbolic(*args, loc=mu, scale=delta).moment(i) + for i in range(1, 5) + ] + + assert_allclose(vals_us, vals_R, atol=0, rtol=1e-13) + + def test_rvs(self): + # Kolmogorov-Smirnov test to ensure alignment + # of analytical and empirical cdfs + + lmbda, alpha, beta = 2, 2, 1 + mu, delta = 0.5, 1.5 + args = (lmbda, alpha*delta, beta*delta) + + gh = stats.genhyperbolic(*args, loc=mu, scale=delta) + _, p = stats.kstest(gh.rvs(size=1500, random_state=1234), gh.cdf) + + assert_equal(p > 0.05, True) + + def test_pdf_t(self): + # Test Against T-Student with 1 - 30 df + df = np.linspace(1, 30, 10) + + # in principle alpha should be zero in practice for big lmbdas + # alpha cannot be too small else pdf does not integrate + alpha, beta = np.float_power(df, 2)*np.finfo(np.float32).eps, 0 + mu, delta = 0, np.sqrt(df) + args = (-df/2, alpha, beta) + + gh = stats.genhyperbolic(*args, loc=mu, scale=delta) + x = np.linspace(gh.ppf(0.01), gh.ppf(0.99), 50)[:, np.newaxis] + + assert_allclose( + gh.pdf(x), stats.t.pdf(x, df), + atol=0, rtol=1e-6 + ) + + def test_pdf_cauchy(self): + # Test Against Cauchy distribution + + # in principle alpha should be zero in practice for big lmbdas + # alpha cannot be too small else pdf does not integrate + lmbda, alpha, beta = -0.5, np.finfo(np.float32).eps, 0 + mu, delta = 0, 1 + args = (lmbda, alpha, beta) + + gh = stats.genhyperbolic(*args, loc=mu, scale=delta) + x = np.linspace(gh.ppf(0.01), gh.ppf(0.99), 50)[:, np.newaxis] + + assert_allclose( + gh.pdf(x), stats.cauchy.pdf(x), + atol=0, rtol=1e-6 + ) + + def test_pdf_laplace(self): + # Test Against Laplace with location param [-10, 10] + loc = np.linspace(-10, 10, 10) + + # in principle delta should be zero in practice for big loc delta + # cannot be too small else pdf does not integrate + delta = np.finfo(np.float32).eps + + lmbda, alpha, beta = 1, 1, 0 + args = (lmbda, alpha*delta, beta*delta) + + # ppf does not integrate for scale < 5e-4 + # therefore using simple linspace to define the support + gh = stats.genhyperbolic(*args, loc=loc, scale=delta) + x = np.linspace(-20, 20, 50)[:, np.newaxis] + + assert_allclose( + gh.pdf(x), stats.laplace.pdf(x, loc=loc, scale=1), + atol=0, rtol=1e-11 + ) + + def test_pdf_norminvgauss(self): + # Test Against NIG with varying alpha/beta/delta/mu + + alpha, beta, delta, mu = ( + np.linspace(1, 20, 10), + np.linspace(0, 19, 10)*np.float_power(-1, range(10)), + np.linspace(1, 1, 10), + np.linspace(-100, 100, 10) + ) + + lmbda = - 0.5 + args = (lmbda, alpha * delta, beta * delta) + + gh = stats.genhyperbolic(*args, loc=mu, scale=delta) + x = np.linspace(gh.ppf(0.01), gh.ppf(0.99), 50)[:, np.newaxis] + + assert_allclose( + gh.pdf(x), stats.norminvgauss.pdf( + x, a=alpha, b=beta, loc=mu, scale=delta), + atol=0, rtol=1e-13 + ) + + +class TestHypSecant: + + # Reference values were computed with the mpmath expression + # float((2/mp.pi)*mp.atan(mp.exp(-x))) + # and mp.dps = 50. + @pytest.mark.parametrize('x, reference', + [(30, 5.957247804324683e-14), + (50, 1.2278802891647964e-22)]) + def test_sf(self, x, reference): + sf = stats.hypsecant.sf(x) + assert_allclose(sf, reference, rtol=5e-15) + + # Reference values were computed with the mpmath expression + # float(-mp.log(mp.tan((mp.pi/2)*p))) + # and mp.dps = 50. + @pytest.mark.parametrize('p, reference', + [(1e-6, 13.363927852673998), + (1e-12, 27.179438410639094)]) + def test_isf(self, p, reference): + x = stats.hypsecant.isf(p) + assert_allclose(x, reference, rtol=5e-15) + + def test_logcdf_logsf(self): + x = 50.0 + # Reference value was computed with mpmath. + ref = -1.2278802891647964e-22 + logcdf = stats.hypsecant.logcdf(x) + assert_allclose(logcdf, ref, rtol=5e-15) + logsf = stats.hypsecant.logsf(-x) + assert_allclose(logsf, ref, rtol=5e-15) + + +class TestNormInvGauss: + def test_cdf_R(self): + # test pdf and cdf vals against R + # require("GeneralizedHyperbolic") + # x_test <- c(-7, -5, 0, 8, 15) + # r_cdf <- GeneralizedHyperbolic::pnig(x_test, mu = 0, a = 1, b = 0.5) + # r_pdf <- GeneralizedHyperbolic::dnig(x_test, mu = 0, a = 1, b = 0.5) + r_cdf = np.array([8.034920282e-07, 2.512671945e-05, 3.186661051e-01, + 9.988650664e-01, 9.999848769e-01]) + x_test = np.array([-7, -5, 0, 8, 15]) + vals_cdf = stats.norminvgauss.cdf(x_test, a=1, b=0.5) + assert_allclose(vals_cdf, r_cdf, atol=1e-9) + + def test_pdf_R(self): + # values from R as defined in test_cdf_R + r_pdf = np.array([1.359600783e-06, 4.413878805e-05, 4.555014266e-01, + 7.450485342e-04, 8.917889931e-06]) + x_test = np.array([-7, -5, 0, 8, 15]) + vals_pdf = stats.norminvgauss.pdf(x_test, a=1, b=0.5) + assert_allclose(vals_pdf, r_pdf, atol=1e-9) + + @pytest.mark.parametrize('x, a, b, sf, rtol', + [(-1, 1, 0, 0.8759652211005315, 1e-13), + (25, 1, 0, 1.1318690184042579e-13, 1e-4), + (1, 5, -1.5, 0.002066711134653577, 1e-12), + (10, 5, -1.5, 2.308435233930669e-29, 1e-9)]) + def test_sf_isf_mpmath(self, x, a, b, sf, rtol): + # Reference data generated with `reference_distributions.NormInvGauss`, + # e.g. `NormInvGauss(alpha=1, beta=0).sf(-1)` with mp.dps = 50 + s = stats.norminvgauss.sf(x, a, b) + assert_allclose(s, sf, rtol=rtol) + i = stats.norminvgauss.isf(sf, a, b) + assert_allclose(i, x, rtol=rtol) + + def test_sf_isf_mpmath_vectorized(self): + x = [-1, 25] + a = [1, 1] + b = 0 + sf = [0.8759652211005315, 1.1318690184042579e-13] # see previous test + s = stats.norminvgauss.sf(x, a, b) + assert_allclose(s, sf, rtol=1e-13, atol=1e-16) + i = stats.norminvgauss.isf(sf, a, b) + # Not perfect, but better than it was. See gh-13338. + assert_allclose(i, x, rtol=1e-6) + + def test_gh8718(self): + # Add test that gh-13338 resolved gh-8718 + dst = stats.norminvgauss(1, 0) + x = np.arange(0, 20, 2) + sf = dst.sf(x) + isf = dst.isf(sf) + assert_allclose(isf, x) + + def test_stats(self): + a, b = 1, 0.5 + gamma = np.sqrt(a**2 - b**2) + v_stats = (b / gamma, a**2 / gamma**3, 3.0 * b / (a * np.sqrt(gamma)), + 3.0 * (1 + 4 * b**2 / a**2) / gamma) + assert_equal(v_stats, stats.norminvgauss.stats(a, b, moments='mvsk')) + + def test_ppf(self): + a, b = 1, 0.5 + x_test = np.array([0.001, 0.5, 0.999]) + vals = stats.norminvgauss.ppf(x_test, a, b) + assert_allclose(x_test, stats.norminvgauss.cdf(vals, a, b)) + + @pytest.mark.parametrize('a', [0.1, 1.0, 10.0]) + @pytest.mark.parametrize('b_a', [-0.9, -0.5, -0.1, 0.1, 0.5, 0.9]) + def test_gh23196_tails_cdf_sf(self, a, b_a): + # gh-23196 reported that the norminvgauss CDF would drop to zero in the far + # right tail. The SF had a similar problem, dropping to zero at the far left. + # Check that this is resolved by checking that values in the deep tails are + # close to 1.0. + b = b_a * a + res = stats.norminvgauss(a=a, b=b).cdf(300) + np.testing.assert_allclose(res, 1, atol=1e-2) + res = stats.norminvgauss(a=a, b=b).sf(-300) + np.testing.assert_allclose(res, 1, atol=1e-2) + + @pytest.mark.parametrize("x, a, b, ref", [ + (100, 1.0, 0.0, 1.0), # originally reported in gh-23196 + (1500, 0.1, 0.09, 0.9999999999418577), # suggested in gh-24567 + ]) + def test_gh23196_tails_cdf_sf_accuracy(self, x, a, b, ref): + # Spot check the accuracy of the CDF/SF in the deep tails. + # library(GeneralizedHyperbolic) + # options(digits=16) + # pnig(1500, alpha=0.1, beta=0.09) + rtol = 1e-14 + np.testing.assert_allclose(stats.norminvgauss(a=a, b=b).cdf(x), ref, rtol=rtol) + np.testing.assert_allclose(stats.norminvgauss(a=a, b=-b).sf(-x), ref, rtol=rtol) + + +class TestGeom: + def setup_method(self): + self.rng = np.random.default_rng(7672986002) + + def test_rvs(self): + vals = stats.geom.rvs(0.75, size=(2, 50), random_state=self.rng) + assert_(np.all(vals >= 0)) + assert_(np.shape(vals) == (2, 50)) + assert_(vals.dtype.char in typecodes['AllInteger']) + val = stats.geom.rvs(0.75, random_state=self.rng) + assert_(isinstance(val, int)) + val = stats.geom(0.75).rvs(3, random_state=self.rng) + assert_(isinstance(val, np.ndarray)) + assert_(val.dtype.char in typecodes['AllInteger']) + + def test_rvs_9313(self): + # previously, RVS were converted to `np.int32` on some platforms, + # causing overflow for moderately large integer output (gh-9313). + # Check that this is resolved to the extent possible w/ `np.int64`. + rvs = stats.geom.rvs(np.exp(-35), size=5, random_state=self.rng) + assert rvs.dtype == np.int64 + assert np.all(rvs > np.iinfo(np.int32).max) + + def test_pmf(self): + vals = stats.geom.pmf([1, 2, 3], 0.5) + assert_array_almost_equal(vals, [0.5, 0.25, 0.125]) + + def test_logpmf(self): + # regression test for ticket 1793 + vals1 = np.log(stats.geom.pmf([1, 2, 3], 0.5)) + vals2 = stats.geom.logpmf([1, 2, 3], 0.5) + assert_allclose(vals1, vals2, rtol=1e-15, atol=0) + + # regression test for gh-4028 + val = stats.geom.logpmf(1, 1) + assert_equal(val, 0.0) + + def test_cdf_sf(self): + vals = stats.geom.cdf([1, 2, 3], 0.5) + vals_sf = stats.geom.sf([1, 2, 3], 0.5) + expected = array([0.5, 0.75, 0.875]) + assert_array_almost_equal(vals, expected) + assert_array_almost_equal(vals_sf, 1-expected) + + def test_logcdf_logsf(self): + vals = stats.geom.logcdf([1, 2, 3], 0.5) + vals_sf = stats.geom.logsf([1, 2, 3], 0.5) + expected = array([0.5, 0.75, 0.875]) + assert_array_almost_equal(vals, np.log(expected)) + assert_array_almost_equal(vals_sf, np.log1p(-expected)) + + def test_ppf(self): + vals = stats.geom.ppf([0.5, 0.75, 0.875], 0.5) + expected = array([1.0, 2.0, 3.0]) + assert_array_almost_equal(vals, expected) + + def test_ppf_underflow(self): + # this should not underflow + assert_allclose(stats.geom.ppf(1e-20, 1e-20), 1.0, atol=1e-14) + + def test_entropy_gh18226(self): + # gh-18226 reported that `geom.entropy` produced a warning and + # inaccurate output for small p. Check that this is resolved. + h = stats.geom(0.0146).entropy() + assert_allclose(h, 5.219397961962308, rtol=1e-15) + + def test_rvs_gh18372(self): + # gh-18372 reported that `geom.rvs` could produce negative numbers, + # with `RandomState` PRNG, but the support is positive integers. + # Check that this is resolved. + random_state = np.random.RandomState(294582935) + assert (stats.geom.rvs(1e-30, size=10, random_state=random_state) > 0).all() + + +class TestPlanck: + + def test_sf(self): + vals = stats.planck.sf([1, 2, 3], 5.) + expected = array([4.5399929762484854e-05, + 3.0590232050182579e-07, + 2.0611536224385579e-09]) + assert_array_almost_equal(vals, expected) + + def test_logsf(self): + vals = stats.planck.logsf([1000., 2000., 3000.], 1000.) + expected = array([-1001000., -2001000., -3001000.]) + assert_array_almost_equal(vals, expected) + + +class TestGennorm: + def test_laplace(self): + # test against Laplace (special case for beta=1) + points = [1, 2, 3] + pdf1 = stats.gennorm.pdf(points, 1) + pdf2 = stats.laplace.pdf(points) + assert_almost_equal(pdf1, pdf2) + + def test_norm(self): + # test against normal (special case for beta=2) + points = [1, 2, 3] + pdf1 = stats.gennorm.pdf(points, 2) + pdf2 = stats.norm.pdf(points, scale=2**-.5) + assert_almost_equal(pdf1, pdf2) + + def test_rvs(self): + # 0 < beta < 1 + dist = stats.gennorm(0.5) + rng = np.random.default_rng(2204049394) + rvs = dist.rvs(size=1000, random_state=rng) + assert stats.kstest(rvs, dist.cdf).pvalue > 0.1 + # beta = 1 + dist = stats.gennorm(1) + rvs = dist.rvs(size=1000, random_state=rng) + rvs_laplace = stats.laplace.rvs(size=1000, random_state=rng) + assert stats.ks_2samp(rvs, rvs_laplace).pvalue > 0.1 + # beta = 2 + dist = stats.gennorm(2) + dist.random_state = rng + rvs = dist.rvs(size=1000, random_state=rng) + rvs_norm = stats.norm.rvs(scale=1/2**0.5, size=1000, random_state=rng) + assert stats.ks_2samp(rvs, rvs_norm).pvalue > 0.1 + + def test_rvs_broadcasting(self): + dist = stats.gennorm([[0.5, 1.], [2., 5.]]) + rng = np.random.default_rng(2204049394) + rvs = dist.rvs(size=[1000, 2, 2], random_state=rng) + assert stats.kstest(rvs[:, 0, 0], stats.gennorm(0.5).cdf)[1] > 0.1 + assert stats.kstest(rvs[:, 0, 1], stats.gennorm(1.0).cdf)[1] > 0.1 + assert stats.kstest(rvs[:, 1, 0], stats.gennorm(2.0).cdf)[1] > 0.1 + assert stats.kstest(rvs[:, 1, 1], stats.gennorm(5.0).cdf)[1] > 0.1 + + +class TestGibrat: + + # sfx is sf(x). The values were computed with mpmath: + # + # from mpmath import mp + # mp.dps = 100 + # def gibrat_sf(x): + # return 1 - mp.ncdf(mp.log(x)) + # + # E.g. + # + # >>> float(gibrat_sf(1.5)) + # 0.3425678305148459 + # + @pytest.mark.parametrize('x, sfx', [(1.5, 0.3425678305148459), + (5000, 8.173334352522493e-18)]) + def test_sf_isf(self, x, sfx): + assert_allclose(stats.gibrat.sf(x), sfx, rtol=2e-14) + assert_allclose(stats.gibrat.isf(sfx), x, rtol=2e-14) + + +class TestGompertz: + + def test_gompertz_accuracy(self): + # Regression test for gh-4031 + p = stats.gompertz.ppf(stats.gompertz.cdf(1e-100, 1), 1) + assert_allclose(p, 1e-100) + + # sfx is sf(x). The values were computed with mpmath: + # + # from mpmath import mp + # mp.dps = 100 + # def gompertz_sf(x, c): + # return mp.exp(-c*mp.expm1(x)) + # + # E.g. + # + # >>> float(gompertz_sf(1, 2.5)) + # 0.013626967146253437 + # + @pytest.mark.parametrize('x, c, sfx', [(1, 2.5, 0.013626967146253437), + (3, 2.5, 1.8973243273704087e-21), + (0.05, 5, 0.7738668242570479), + (2.25, 5, 3.707795833465481e-19)]) + def test_sf_isf(self, x, c, sfx): + assert_allclose(stats.gompertz.sf(x, c), sfx, rtol=1e-14) + assert_allclose(stats.gompertz.isf(sfx, c), x, rtol=1e-14) + + def test_logcdf(self): + x = 8.0 + c = 0.1 + # Reference value computed with mpmath. + ref = -3.820049516821143e-130 + logcdf = stats.gompertz.logcdf(x, c) + assert_allclose(logcdf, ref, rtol=5e-15) + + def test_logsf(self): + x = 3e-80 + c = 12 + # Reference value computed with mpmath. + ref = -3.6e-79 + logsf = stats.gompertz.logsf(x, c) + assert_allclose(logsf, ref, rtol=5e-15) + + # reference values were computed with mpmath + # from mpmath import mp + # mp.dps = 100 + # def gompertz_entropy(c): + # c = mp.mpf(c) + # return float(mp.one - mp.log(c) - mp.exp(c)*mp.e1(c)) + + @pytest.mark.parametrize('c, ref', [(1e-4, 1.5762523017634573), + (1, 0.4036526376768059), + (1000, -5.908754280976161), + (1e10, -22.025850930040455)]) + def test_entropy(self, c, ref): + assert_allclose(stats.gompertz.entropy(c), ref, rtol=1e-14) + + +class TestFoldNorm: + + # reference values were computed with mpmath with 50 digits of precision + # from mpmath import mp + # mp.dps = 50 + # mp.mpf(0.5) * (mp.erf((x - c)/mp.sqrt(2)) + mp.erf((x + c)/mp.sqrt(2))) + + @pytest.mark.parametrize('x, c, ref', [(1e-4, 1e-8, 7.978845594730578e-05), + (1e-4, 1e-4, 7.97884555483635e-05)]) + def test_cdf(self, x, c, ref): + assert_allclose(stats.foldnorm.cdf(x, c), ref, rtol=1e-15) + + +class TestHalfNorm: + + # sfx is sf(x). The values were computed with mpmath: + # + # from mpmath import mp + # mp.dps = 100 + # def halfnorm_sf(x): + # return 2*(1 - mp.ncdf(x)) + # + # E.g. + # + # >>> float(halfnorm_sf(1)) + # 0.3173105078629141 + # + @pytest.mark.parametrize('x, sfx', [(1, 0.3173105078629141), + (10, 1.523970604832105e-23)]) + def test_sf_isf(self, x, sfx): + assert_allclose(stats.halfnorm.sf(x), sfx, rtol=1e-14) + assert_allclose(stats.halfnorm.isf(sfx), x, rtol=1e-14) + + # reference values were computed via mpmath + # from mpmath import mp + # mp.dps = 100 + # def halfnorm_cdf_mpmath(x): + # x = mp.mpf(x) + # return float(mp.erf(x/mp.sqrt(2.))) + + @pytest.mark.parametrize('x, ref', [(1e-40, 7.978845608028653e-41), + (1e-18, 7.978845608028654e-19), + (8, 0.9999999999999988)]) + def test_cdf(self, x, ref): + assert_allclose(stats.halfnorm.cdf(x), ref, rtol=1e-15) + + @pytest.mark.parametrize("rvs_loc", [1e-5, 1e10]) + @pytest.mark.parametrize("rvs_scale", [1e-2, 100, 1e8]) + @pytest.mark.parametrize('fix_loc', [True, False]) + @pytest.mark.parametrize('fix_scale', [True, False]) + def test_fit_MLE_comp_optimizer(self, rvs_loc, rvs_scale, + fix_loc, fix_scale): + + rng = np.random.default_rng(6762668991392531563) + data = stats.halfnorm.rvs(loc=rvs_loc, scale=rvs_scale, size=1000, + random_state=rng) + + if fix_loc and fix_scale: + error_msg = ("All parameters fixed. There is nothing to " + "optimize.") + with pytest.raises(RuntimeError, match=error_msg): + stats.halflogistic.fit(data, floc=rvs_loc, fscale=rvs_scale) + return + + kwds = {} + if fix_loc: + kwds['floc'] = rvs_loc + if fix_scale: + kwds['fscale'] = rvs_scale + + # Numerical result may equal analytical result if the initial guess + # computed from moment condition is already optimal. + _assert_less_or_close_loglike(stats.halfnorm, data, **kwds, + maybe_identical=True) + + def test_fit_error(self): + # `floc` bigger than the minimal data point + with pytest.raises(FitDataError): + stats.halfnorm.fit([1, 2, 3], floc=2) + + +class TestHalfCauchy: + + @pytest.mark.parametrize("rvs_loc", [1e-5, 1e10]) + @pytest.mark.parametrize("rvs_scale", [1e-2, 1e8]) + @pytest.mark.parametrize('fix_loc', [True, False]) + @pytest.mark.parametrize('fix_scale', [True, False]) + def test_fit_MLE_comp_optimizer(self, rvs_loc, rvs_scale, + fix_loc, fix_scale): + + rng = np.random.default_rng(6762668991392531563) + data = stats.halfnorm.rvs(loc=rvs_loc, scale=rvs_scale, size=1000, + random_state=rng) + + if fix_loc and fix_scale: + error_msg = ("All parameters fixed. There is nothing to " + "optimize.") + with pytest.raises(RuntimeError, match=error_msg): + stats.halfcauchy.fit(data, floc=rvs_loc, fscale=rvs_scale) + return + + kwds = {} + if fix_loc: + kwds['floc'] = rvs_loc + if fix_scale: + kwds['fscale'] = rvs_scale + + _assert_less_or_close_loglike(stats.halfcauchy, data, **kwds) + + def test_fit_error(self): + # `floc` bigger than the minimal data point + with pytest.raises(FitDataError): + stats.halfcauchy.fit([1, 2, 3], floc=2) + + +class TestHalfLogistic: + # survival function reference values were computed with mpmath + # from mpmath import mp + # mp.dps = 50 + # def sf_mpmath(x): + # x = mp.mpf(x) + # return float(mp.mpf(2.)/(mp.exp(x) + mp.one)) + + @pytest.mark.parametrize('x, ref', [(100, 7.440151952041672e-44), + (200, 2.767793053473475e-87)]) + def test_sf(self, x, ref): + assert_allclose(stats.halflogistic.sf(x), ref, rtol=1e-15) + + # inverse survival function reference values were computed with mpmath + # from mpmath import mp + # mp.dps = 200 + # def isf_mpmath(x): + # halfx = mp.mpf(x)/2 + # return float(-mp.log(halfx/(mp.one - halfx))) + + @pytest.mark.parametrize('q, ref', [(7.440151952041672e-44, 100), + (2.767793053473475e-87, 200), + (1-1e-9, 1.999999943436137e-09), + (1-1e-15, 1.9984014443252818e-15)]) + def test_isf(self, q, ref): + assert_allclose(stats.halflogistic.isf(q), ref, rtol=1e-15) + + def test_logcdf(self): + x = 30.0 + # Reference value computed with mpmath. + ref = -1.871524593768035e-13 + logcdf = stats.halflogistic.logcdf(x) + assert_allclose(logcdf, ref, rtol=5e-15) + + def test_logsf(self): + x = 2e-14 + # Reference value computed with mpmath. + ref = -1.000000000000005e-14 + logsf = stats.halflogistic.logsf(x) + assert_allclose(logsf, ref, rtol=5e-15) + + @pytest.mark.parametrize("rvs_loc", [1e-5, 1e10]) + @pytest.mark.parametrize("rvs_scale", [1e-2, 100, 1e8]) + @pytest.mark.parametrize('fix_loc', [True, False]) + @pytest.mark.parametrize('fix_scale', [True, False]) + def test_fit_MLE_comp_optimizer(self, rvs_loc, rvs_scale, + fix_loc, fix_scale): + + rng = np.random.default_rng(6762668991392531563) + data = stats.halflogistic.rvs(loc=rvs_loc, scale=rvs_scale, size=1000, + random_state=rng) + + kwds = {} + if fix_loc and fix_scale: + error_msg = ("All parameters fixed. There is nothing to " + "optimize.") + with pytest.raises(RuntimeError, match=error_msg): + stats.halflogistic.fit(data, floc=rvs_loc, fscale=rvs_scale) + return + + if fix_loc: + kwds['floc'] = rvs_loc + if fix_scale: + kwds['fscale'] = rvs_scale + + # Numerical result may equal analytical result if the initial guess + # computed from moment condition is already optimal. + _assert_less_or_close_loglike(stats.halflogistic, data, **kwds, + maybe_identical=True) + + def test_fit_bad_floc(self): + msg = r" Maximum likelihood estimation with 'halflogistic' requires" + with assert_raises(FitDataError, match=msg): + stats.halflogistic.fit([0, 2, 4], floc=1) + + +class TestHalfgennorm: + def test_expon(self): + # test against exponential (special case for beta=1) + points = [1, 2, 3] + pdf1 = stats.halfgennorm.pdf(points, 1) + pdf2 = stats.expon.pdf(points) + assert_almost_equal(pdf1, pdf2) + + def test_halfnorm(self): + # test against half normal (special case for beta=2) + points = [1, 2, 3] + pdf1 = stats.halfgennorm.pdf(points, 2) + pdf2 = stats.halfnorm.pdf(points, scale=2**-.5) + assert_almost_equal(pdf1, pdf2) + + def test_gennorm(self): + # test against generalized normal + points = [1, 2, 3] + pdf1 = stats.halfgennorm.pdf(points, .497324) + pdf2 = stats.gennorm.pdf(points, .497324) + assert_almost_equal(pdf1, 2*pdf2) + + # Reference values (e.g. for n=3, beta=1.25) + # from mpmath import mp + # mp.dps = 100 + # n = 3 + # beta = mp.mpf(1.25) + # ref = float(mp.rf(1/beta, n/beta)) + @pytest.mark.parametrize('n, beta, ref', + [(0, 2.25, 1), + (1, 0.5, 6.0), + (2, 12.5, 0.3155489903298712), + (3, 0.125, 1.631515605987683e+30)]) + def test_munp(self, n, beta, ref): + munp = stats.halfgennorm._munp(n, beta) + assert_allclose(munp, ref, rtol=5e-15) + + # Reference values (e.g. for beta=0.02) + # from mpmath import mp + # mp.dps = 100 + # beta = mp.mpf(0.02) + # ref = float(mp.rf(1/beta, 1/beta)) + @pytest.mark.parametrize('beta, ref', + [(0.02, 1.5342593781274747e+93), + (0.125, 259459200.0), + (0.5, 6.0), + (10.0, 0.48256057149575027), + (125.0, 0.49777435261046765), + (15000.0, 0.49998076533051666)]) + def test_mean(self, beta, ref): + m = stats.halfgennorm.mean(beta) + assert_allclose(m, ref, rtol=1e-14) + + # Reference values (e.g. for beta=0.02) + # from mpmath import mp + # mp.dps = 100 + # beta = mp.mpf(0.02) + # mu = mp.rf(1/beta, 1/beta) + # # var = E(X**2) - E(X)**2: + # ref = float(mp.rf(1/beta, 2/beta) - mu**2) + @pytest.mark.parametrize('beta, ref', + [(0.02, 6.261772482175519e+197), + (0.125, 5.062049324107776e+18), + (0.5, 84.0), + (10.0, 0.08159018176717249), + (125.0, 0.0826270885971827), + (15000.0, 0.08332692433644018)]) + def test_var(self, beta, ref): + v = stats.halfgennorm.var(beta) + assert_allclose(v, ref, rtol=1.5e-14) + + +class TestLaplaceasymmetric: + def test_laplace(self): + # test against Laplace (special case for kappa=1) + points = np.array([1, 2, 3]) + pdf1 = stats.laplace_asymmetric.pdf(points, 1) + pdf2 = stats.laplace.pdf(points) + assert_allclose(pdf1, pdf2) + + def test_asymmetric_laplace_pdf(self): + # test asymmetric Laplace + points = np.array([1, 2, 3]) + kappa = 2 + kapinv = 1/kappa + pdf1 = stats.laplace_asymmetric.pdf(points, kappa) + pdf2 = stats.laplace_asymmetric.pdf(points*(kappa**2), kapinv) + assert_allclose(pdf1, pdf2) + + def test_asymmetric_laplace_log_10_16(self): + # test asymmetric Laplace + points = np.array([-np.log(16), np.log(10)]) + kappa = 2 + pdf1 = stats.laplace_asymmetric.pdf(points, kappa) + cdf1 = stats.laplace_asymmetric.cdf(points, kappa) + sf1 = stats.laplace_asymmetric.sf(points, kappa) + pdf2 = np.array([1/10, 1/250]) + cdf2 = np.array([1/5, 1 - 1/500]) + sf2 = np.array([4/5, 1/500]) + ppf1 = stats.laplace_asymmetric.ppf(cdf2, kappa) + ppf2 = points + isf1 = stats.laplace_asymmetric.isf(sf2, kappa) + isf2 = points + assert_allclose(np.concatenate((pdf1, cdf1, sf1, ppf1, isf1)), + np.concatenate((pdf2, cdf2, sf2, ppf2, isf2))) + + +class TestTruncnorm: + def setup_method(self): + self.rng = np.random.default_rng(3255963201) + + @pytest.mark.parametrize("a, b, ref", + [(0, 100, 0.7257913526447274), + (0.6, 0.7, -2.3027610681852573), + (1e-06, 2e-06, -13.815510557964274)]) + def test_entropy(self, a, b, ref): + # All reference values were calculated with mpmath: + # import numpy as np + # from mpmath import mp + # mp.dps = 50 + # def entropy_trun(a, b): + # a, b = mp.mpf(a), mp.mpf(b) + # Z = mp.ncdf(b) - mp.ncdf(a) + # + # def pdf(x): + # return mp.npdf(x) / Z + # + # res = -mp.quad(lambda t: pdf(t) * mp.log(pdf(t)), [a, b]) + # return np.float64(res) + assert_allclose(stats.truncnorm.entropy(a, b), ref, rtol=1e-10) + + @pytest.mark.parametrize("a, b, ref", + [(1e-11, 10000000000.0, 0.725791352640738), + (1e-100, 1e+100, 0.7257913526447274), + (-1e-100, 1e+100, 0.7257913526447274), + (-1e+100, 1e+100, 1.4189385332046727)]) + def test_extreme_entropy(self, a, b, ref): + # The reference values were calculated with mpmath + # import numpy as np + # from mpmath import mp + # mp.dps = 50 + # def trunc_norm_entropy(a, b): + # a, b = mp.mpf(a), mp.mpf(b) + # Z = mp.ncdf(b) - mp.ncdf(a) + # A = mp.log(mp.sqrt(2 * mp.pi * mp.e) * Z) + # B = (a * mp.npdf(a) - b * mp.npdf(b)) / (2 * Z) + # return np.float64(A + B) + assert_allclose(stats.truncnorm.entropy(a, b), ref, rtol=1e-14) + + def test_ppf_ticket1131(self): + vals = stats.truncnorm.ppf([-0.5, 0, 1e-4, 0.5, 1-1e-4, 1, 2], -1., 1., + loc=[3]*7, scale=2) + expected = np.array([np.nan, 1, 1.00056419, 3, 4.99943581, 5, np.nan]) + assert_array_almost_equal(vals, expected) + + def test_isf_ticket1131(self): + vals = stats.truncnorm.isf([-0.5, 0, 1e-4, 0.5, 1-1e-4, 1, 2], -1., 1., + loc=[3]*7, scale=2) + expected = np.array([np.nan, 5, 4.99943581, 3, 1.00056419, 1, np.nan]) + assert_array_almost_equal(vals, expected) + + def test_gh_2477_small_values(self): + # Check a case that worked in the original issue. + low, high = -11, -10 + x = stats.truncnorm.rvs(low, high, 0, 1, size=10, random_state=self.rng) + assert_(low < x.min() < x.max() < high) + # Check a case that failed in the original issue. + low, high = 10, 11 + x = stats.truncnorm.rvs(low, high, 0, 1, size=10, random_state=self.rng) + assert_(low < x.min() < x.max() < high) + + def test_gh_2477_large_values(self): + # Check a case that used to fail because of extreme tailness. + low, high = 100, 101 + x = stats.truncnorm.rvs(low, high, 0, 1, size=10, random_state=self.rng) + assert_(low <= x.min() <= x.max() <= high), str([low, high, x]) + + # Check some additional extreme tails + low, high = 1000, 1001 + x = stats.truncnorm.rvs(low, high, 0, 1, size=10, random_state=self.rng) + assert_(low < x.min() < x.max() < high) + + low, high = 10000, 10001 + x = stats.truncnorm.rvs(low, high, 0, 1, size=10, random_state=self.rng) + assert_(low < x.min() < x.max() < high) + + low, high = -10001, -10000 + x = stats.truncnorm.rvs(low, high, 0, 1, size=10, random_state=self.rng) + assert_(low < x.min() < x.max() < high) + + def test_gh_9403_nontail_values(self): + for low, high in [[3, 4], [-4, -3]]: + xvals = np.array([-np.inf, low, high, np.inf]) + xmid = (high+low)/2.0 + cdfs = stats.truncnorm.cdf(xvals, low, high) + sfs = stats.truncnorm.sf(xvals, low, high) + pdfs = stats.truncnorm.pdf(xvals, low, high) + expected_cdfs = np.array([0, 0, 1, 1]) + expected_sfs = np.array([1.0, 1.0, 0.0, 0.0]) + expected_pdfs = np.array([0, 3.3619772, 0.1015229, 0]) + if low < 0: + expected_pdfs = np.array([0, 0.1015229, 3.3619772, 0]) + assert_almost_equal(cdfs, expected_cdfs) + assert_almost_equal(sfs, expected_sfs) + assert_almost_equal(pdfs, expected_pdfs) + assert_almost_equal(np.log(expected_pdfs[1]/expected_pdfs[2]), + low + 0.5) + pvals = np.array([0, 0.5, 1.0]) + ppfs = stats.truncnorm.ppf(pvals, low, high) + expected_ppfs = np.array([low, np.sign(low)*3.1984741, high]) + assert_almost_equal(ppfs, expected_ppfs) + + if low < 0: + assert_almost_equal(stats.truncnorm.sf(xmid, low, high), + 0.8475544278436675) + assert_almost_equal(stats.truncnorm.cdf(xmid, low, high), + 0.1524455721563326) + else: + assert_almost_equal(stats.truncnorm.cdf(xmid, low, high), + 0.8475544278436675) + assert_almost_equal(stats.truncnorm.sf(xmid, low, high), + 0.1524455721563326) + pdf = stats.truncnorm.pdf(xmid, low, high) + assert_almost_equal(np.log(pdf/expected_pdfs[2]), (xmid+0.25)/2) + + def test_gh_9403_medium_tail_values(self): + for low, high in [[39, 40], [-40, -39]]: + xvals = np.array([-np.inf, low, high, np.inf]) + xmid = (high+low)/2.0 + cdfs = stats.truncnorm.cdf(xvals, low, high) + sfs = stats.truncnorm.sf(xvals, low, high) + pdfs = stats.truncnorm.pdf(xvals, low, high) + expected_cdfs = np.array([0, 0, 1, 1]) + expected_sfs = np.array([1.0, 1.0, 0.0, 0.0]) + expected_pdfs = np.array([0, 3.90256074e+01, 2.73349092e-16, 0]) + if low < 0: + expected_pdfs = np.array([0, 2.73349092e-16, + 3.90256074e+01, 0]) + assert_almost_equal(cdfs, expected_cdfs) + assert_almost_equal(sfs, expected_sfs) + assert_almost_equal(pdfs, expected_pdfs) + assert_almost_equal(np.log(expected_pdfs[1]/expected_pdfs[2]), + low + 0.5) + pvals = np.array([0, 0.5, 1.0]) + ppfs = stats.truncnorm.ppf(pvals, low, high) + expected_ppfs = np.array([low, np.sign(low)*39.01775731, high]) + assert_almost_equal(ppfs, expected_ppfs) + cdfs = stats.truncnorm.cdf(ppfs, low, high) + assert_almost_equal(cdfs, pvals) + + if low < 0: + assert_almost_equal(stats.truncnorm.sf(xmid, low, high), + 0.9999999970389126) + assert_almost_equal(stats.truncnorm.cdf(xmid, low, high), + 2.961048103554866e-09) + else: + assert_almost_equal(stats.truncnorm.cdf(xmid, low, high), + 0.9999999970389126) + assert_almost_equal(stats.truncnorm.sf(xmid, low, high), + 2.961048103554866e-09) + pdf = stats.truncnorm.pdf(xmid, low, high) + assert_almost_equal(np.log(pdf/expected_pdfs[2]), (xmid+0.25)/2) + + xvals = np.linspace(low, high, 11) + xvals2 = -xvals[::-1] + assert_almost_equal(stats.truncnorm.cdf(xvals, low, high), + stats.truncnorm.sf(xvals2, -high, -low)[::-1]) + assert_almost_equal(stats.truncnorm.sf(xvals, low, high), + stats.truncnorm.cdf(xvals2, -high, -low)[::-1]) + assert_almost_equal(stats.truncnorm.pdf(xvals, low, high), + stats.truncnorm.pdf(xvals2, -high, -low)[::-1]) + + def test_cdf_tail_15110_14753(self): + # Check accuracy issues reported in gh-14753 and gh-155110 + # Ground truth values calculated using Wolfram Alpha, e.g. + # (CDF[NormalDistribution[0,1],83/10]-CDF[NormalDistribution[0,1],8])/ + # (1 - CDF[NormalDistribution[0,1],8]) + assert_allclose(stats.truncnorm(13., 15.).cdf(14.), + 0.9999987259565643) + assert_allclose(stats.truncnorm(8, np.inf).cdf(8.3), + 0.9163220907327540) + + # Test data for the truncnorm stats() method. + # The data in each row is: + # a, b, mean, variance, skewness, excess kurtosis. Generated using + # https://gist.github.com/WarrenWeckesser/636b537ee889679227d53543d333a720 + _truncnorm_stats_data = [ + [-30, 30, + 0.0, 1.0, 0.0, 0.0], + [-10, 10, + 0.0, 1.0, 0.0, -1.4927521335810455e-19], + [-3, 3, + 0.0, 0.9733369246625415, 0.0, -0.17111443639774404], + [-2, 2, + 0.0, 0.7737413035499232, 0.0, -0.6344632828703505], + [0, np.inf, + 0.7978845608028654, + 0.3633802276324187, + 0.995271746431156, + 0.8691773036059741], + [-np.inf, 0, + -0.7978845608028654, + 0.3633802276324187, + -0.995271746431156, + 0.8691773036059741], + [-1, 3, + 0.282786110727154, + 0.6161417353578293, + 0.5393018494027877, + -0.20582065135274694], + [-3, 1, + -0.282786110727154, + 0.6161417353578293, + -0.5393018494027877, + -0.20582065135274694], + [-10, -9, + -9.108456288012409, + 0.011448805821636248, + -1.8985607290949496, + 5.0733461105025075], + ] + _truncnorm_stats_data = np.array(_truncnorm_stats_data) + + @pytest.mark.parametrize("case", _truncnorm_stats_data) + def test_moments(self, case): + a, b, m0, v0, s0, k0 = case + m, v, s, k = stats.truncnorm.stats(a, b, moments='mvsk') + assert_allclose([m, v, s, k], [m0, v0, s0, k0], atol=1e-17) + + def test_9902_moments(self): + m, v = stats.truncnorm.stats(0, np.inf, moments='mv') + assert_almost_equal(m, 0.79788456) + assert_almost_equal(v, 0.36338023) + + def test_gh_1489_trac_962_rvs(self): + # Check the original example. + low, high = 10, 15 + x = stats.truncnorm.rvs(low, high, 0, 1, size=10, random_state=self.rng) + assert_(low < x.min() < x.max() < high) + + def test_gh_11299_rvs(self): + # Arose from investigating gh-11299 + # Test multiple shape parameters simultaneously. + low = [-10, 10, -np.inf, -5, -np.inf, -np.inf, -45, -45, 40, -10, 40] + high = [-5, 11, 5, np.inf, 40, -40, 40, -40, 45, np.inf, np.inf] + x = stats.truncnorm.rvs(low, high, size=(5, len(low)), random_state=self.rng) + assert np.shape(x) == (5, len(low)) + assert_(np.all(low <= x.min(axis=0))) + assert_(np.all(x.max(axis=0) <= high)) + + def test_rvs_Generator(self): + # check that rvs can use a Generator + if hasattr(np.random, "default_rng"): + stats.truncnorm.rvs(-10, -5, size=5, random_state=self.rng) + + def test_logcdf_gh17064(self): + # regression test for gh-17064 - avoid roundoff error for logcdfs ~0 + a = np.array([-np.inf, -np.inf, -8, -np.inf, 10]) + b = np.array([np.inf, np.inf, 8, 10, np.inf]) + x = np.array([10, 7.5, 7.5, 9, 20]) + expected = [-7.619853024160525e-24, -3.190891672910947e-14, + -3.128682067168231e-14, -1.1285122074235991e-19, + -3.61374964828753e-66] + assert_allclose(stats.truncnorm(a, b).logcdf(x), expected) + assert_allclose(stats.truncnorm(-b, -a).logsf(-x), expected) + + def test_moments_gh18634(self): + # gh-18634 reported that moments 5 and higher didn't work; check that + # this is resolved + res = stats.truncnorm(-2, 3).moment(5) + # From Mathematica: + # Moment[TruncatedDistribution[{-2, 3}, NormalDistribution[]], 5] + ref = 1.645309620208361 + assert_allclose(res, ref) + + +class TestGenLogistic: + + # Expected values computed with mpmath with 50 digits of precision. + @pytest.mark.parametrize('x, expected', [(-1000, -1499.5945348918917), + (-125, -187.09453489189184), + (0, -1.3274028432916989), + (100, -99.59453489189184), + (1000, -999.5945348918918)]) + def test_logpdf(self, x, expected): + c = 1.5 + logp = stats.genlogistic.logpdf(x, c) + assert_allclose(logp, expected, rtol=1e-13) + + # Expected values computed with mpmath with 50 digits of precision + # from mpmath import mp + # mp.dps = 50 + # def entropy_mp(c): + # c = mp.mpf(c) + # return float(-mp.log(c)+mp.one+mp.digamma(c + mp.one) + mp.euler) + + @pytest.mark.parametrize('c, ref', [(1e-100, 231.25850929940458), + (1e-4, 10.21050485336338), + (1e8, 1.577215669901533), + (1e100, 1.5772156649015328)]) + def test_entropy(self, c, ref): + assert_allclose(stats.genlogistic.entropy(c), ref, rtol=5e-15) + + # Expected values computed with mpmath with 50 digits of precision + # from mpmath import mp + # mp.dps = 1000 + # + # def genlogistic_cdf_mp(x, c): + # x = mp.mpf(x) + # c = mp.mpf(c) + # return (mp.one + mp.exp(-x)) ** (-c) + # + # def genlogistic_sf_mp(x, c): + # return mp.one - genlogistic_cdf_mp(x, c) + # + # x, c, ref = 100, 0.02, -7.440151952041672e-466 + # print(float(mp.log(genlogistic_cdf_mp(x, c)))) + # ppf/isf reference values generated by passing in `ref` (`q` is produced) + + @pytest.mark.parametrize('x, c, ref', [(200, 10, 1.3838965267367375e-86), + (500, 20, 1.424915281348257e-216)]) + def test_sf(self, x, c, ref): + assert_allclose(stats.genlogistic.sf(x, c), ref, rtol=1e-14) + + @pytest.mark.parametrize('q, c, ref', [(0.01, 200, 9.898441467379765), + (0.001, 2, 7.600152115573173)]) + def test_isf(self, q, c, ref): + assert_allclose(stats.genlogistic.isf(q, c), ref, rtol=5e-16) + + @pytest.mark.parametrize('q, c, ref', [(0.5, 200, 5.6630969187064615), + (0.99, 20, 7.595630231412436)]) + def test_ppf(self, q, c, ref): + assert_allclose(stats.genlogistic.ppf(q, c), ref, rtol=5e-16) + + @pytest.mark.parametrize('x, c, ref', [(100, 0.02, -7.440151952041672e-46), + (50, 20, -3.857499695927835e-21)]) + def test_logcdf(self, x, c, ref): + assert_allclose(stats.genlogistic.logcdf(x, c), ref, rtol=1e-15) + + +class TestHypergeom: + def setup_method(self): + self.rng = np.random.default_rng(1765545342) + + def test_rvs(self): + vals = stats.hypergeom.rvs(20, 10, 3, size=(2, 50), random_state=self.rng) + assert np.all(vals >= 0) & np.all(vals <= 3) + assert np.shape(vals) == (2, 50) + assert vals.dtype.char in typecodes['AllInteger'] + val = stats.hypergeom.rvs(20, 3, 10, random_state=self.rng) + assert isinstance(val, int) + val = stats.hypergeom(20, 3, 10).rvs(3, random_state=self.rng) + assert isinstance(val, np.ndarray) + assert val.dtype.char in typecodes['AllInteger'] + + def test_precision(self): + # comparison number from mpmath + M = 2500 + n = 50 + N = 500 + tot = M + good = n + hgpmf = stats.hypergeom.pmf(2, tot, good, N) + assert_almost_equal(hgpmf, 0.0010114963068932233, 11) + + def test_args(self): + # test correct output for corner cases of arguments + # see gh-2325 + assert_almost_equal(stats.hypergeom.pmf(0, 2, 1, 0), 1.0, 11) + assert_almost_equal(stats.hypergeom.pmf(1, 2, 1, 0), 0.0, 11) + + assert_almost_equal(stats.hypergeom.pmf(0, 2, 0, 2), 1.0, 11) + assert_almost_equal(stats.hypergeom.pmf(1, 2, 1, 0), 0.0, 11) + + def test_cdf_above_one(self): + # for some values of parameters, hypergeom cdf was >1, see gh-2238 + assert_(0 <= stats.hypergeom.cdf(30, 13397950, 4363, 12390) <= 1.0) + + def test_precision2(self): + # Test hypergeom precision for large numbers. See #1218. + # Results compared with those from R. + oranges = 9.9e4 + pears = 1.1e5 + fruits_eaten = np.array([3, 3.8, 3.9, 4, 4.1, 4.2, 5]) * 1e4 + quantile = 2e4 + res = [stats.hypergeom.sf(quantile, oranges + pears, oranges, eaten) + for eaten in fruits_eaten] + expected = np.array([0, 1.904153e-114, 2.752693e-66, 4.931217e-32, + 8.265601e-11, 0.1237904, 1]) + assert_allclose(res, expected, atol=0, rtol=5e-7) + + # Test with array_like first argument + quantiles = [1.9e4, 2e4, 2.1e4, 2.15e4] + res2 = stats.hypergeom.sf(quantiles, oranges + pears, oranges, 4.2e4) + expected2 = [1, 0.1237904, 6.511452e-34, 3.277667e-69] + assert_allclose(res2, expected2, atol=0, rtol=5e-7) + + def test_entropy(self): + # Simple tests of entropy. + hg = stats.hypergeom(4, 1, 1) + h = hg.entropy() + expected_p = np.array([0.75, 0.25]) + expected_h = -np.sum(xlogy(expected_p, expected_p)) + assert_allclose(h, expected_h) + + hg = stats.hypergeom(1, 1, 1) + h = hg.entropy() + assert_equal(h, 0.0) + + def test_logsf(self): + # Test logsf for very large numbers. See issue #4982 + # Results compare with those from R (v3.2.0): + # phyper(k, n, M-n, N, lower.tail=FALSE, log.p=TRUE) + # -2239.771 + + k = 1e4 + M = 1e7 + n = 1e6 + N = 5e4 + + result = stats.hypergeom.logsf(k, M, n, N) + expected = -2239.771 # From R + assert_almost_equal(result, expected, decimal=3) + + k = 1 + M = 1600 + n = 600 + N = 300 + + result = stats.hypergeom.logsf(k, M, n, N) + expected = -2.566567e-68 # From R + assert_almost_equal(result, expected, decimal=15) + + def test_logcdf(self): + # Test logcdf for very large numbers. See issue #8692 + # Results compare with those from R (v3.3.2): + # phyper(k, n, M-n, N, lower.tail=TRUE, log.p=TRUE) + # -5273.335 + + k = 1 + M = 1e7 + n = 1e6 + N = 5e4 + + result = stats.hypergeom.logcdf(k, M, n, N) + expected = -5273.335 # From R + assert_almost_equal(result, expected, decimal=3) + + # Same example as in issue #8692 + k = 40 + M = 1600 + n = 50 + N = 300 + + result = stats.hypergeom.logcdf(k, M, n, N) + expected = -7.565148879229e-23 # From R + assert_almost_equal(result, expected, decimal=15) + + k = 125 + M = 1600 + n = 250 + N = 500 + + result = stats.hypergeom.logcdf(k, M, n, N) + expected = -4.242688e-12 # From R + assert_almost_equal(result, expected, decimal=15) + + # test broadcasting robustness based on reviewer + # concerns in PR 9603; using an array version of + # the example from issue #8692 + k = np.array([40, 40, 40]) + M = 1600 + n = 50 + N = 300 + + result = stats.hypergeom.logcdf(k, M, n, N) + expected = np.full(3, -7.565148879229e-23) # filled from R result + assert_almost_equal(result, expected, decimal=15) + + def test_mean_gh18511(self): + # gh-18511 reported that the `mean` was incorrect for large arguments; + # check that this is resolved + M = 390_000 + n = 370_000 + N = 12_000 + + hm = stats.hypergeom.mean(M, n, N) + rm = n / M * N + assert_allclose(hm, rm) + + @pytest.mark.xslow + def test_sf_gh18506(self): + # gh-18506 reported that `sf` was incorrect for large population; + # check that this is resolved + n = 10 + N = 10**5 + i = np.arange(5, 15) + population_size = 10.**i + p = stats.hypergeom.sf(n - 1, population_size, N, n) + assert np.all(p > 0) + assert np.all(np.diff(p) < 0) + + +class TestLoggamma: + def setup_method(self): + self.rng = np.random.default_rng(8638464332) + + # Expected cdf values were computed with mpmath. For given x and c, + # x = mpmath.mpf(x) + # c = mpmath.mpf(c) + # cdf = mpmath.gammainc(c, 0, mpmath.exp(x), + # regularized=True) + @pytest.mark.parametrize('x, c, cdf', + [(1, 2, 0.7546378854206702), + (-1, 14, 6.768116452566383e-18), + (-745.1, 0.001, 0.4749605142005238), + (-800, 0.001, 0.44958802911019136), + (-725, 0.1, 3.4301205868273265e-32), + (-740, 0.75, 1.0074360436599631e-241)]) + def test_cdf_ppf(self, x, c, cdf): + p = stats.loggamma.cdf(x, c) + assert_allclose(p, cdf, rtol=1e-13) + y = stats.loggamma.ppf(cdf, c) + assert_allclose(y, x, rtol=1e-13) + + # Expected sf values were computed with mpmath. For given x and c, + # x = mpmath.mpf(x) + # c = mpmath.mpf(c) + # sf = mpmath.gammainc(c, mpmath.exp(x), mpmath.inf, + # regularized=True) + @pytest.mark.parametrize('x, c, sf', + [(4, 1.5, 1.6341528919488565e-23), + (6, 100, 8.23836829202024e-74), + (-800, 0.001, 0.5504119708898086), + (-743, 0.0025, 0.8437131370024089)]) + def test_sf_isf(self, x, c, sf): + s = stats.loggamma.sf(x, c) + assert_allclose(s, sf, rtol=1e-13) + y = stats.loggamma.isf(sf, c) + assert_allclose(y, x, rtol=1e-13) + + def test_logpdf(self): + # Test logpdf with x=-500, c=2. ln(gamma(2)) = 0, and + # exp(-500) ~= 7e-218, which is far smaller than the ULP + # of c*x=-1000, so logpdf(-500, 2) = c*x - exp(x) - ln(gamma(2)) + # should give -1000.0. + lp = stats.loggamma.logpdf(-500, 2) + assert_allclose(lp, -1000.0, rtol=1e-14) + + def test_logcdf(self): + x = 4.0 + c = 4.5 + logcdf = stats.loggamma.logcdf(x, c) + # Reference value computed with mpmath. + ref = -2.1429747073164531e-19 + assert_allclose(logcdf, ref, rtol=5e-15) + + def test_logsf(self): + x = -25.0 + c = 3.5 + logsf = stats.loggamma.logsf(x, c) + # Reference value computed with mpmath. + ref = -8.58200139319556e-40 + assert_allclose(logsf, ref, rtol=5e-15) + + def test_stats(self): + # The following precomputed values are from the table in section 2.2 + # of "A Statistical Study of Log-Gamma Distribution", by Ping Shing + # Chan (thesis, McMaster University, 1993). + table = np.array([ + # c, mean, var, skew, exc. kurt. + 0.5, -1.9635, 4.9348, -1.5351, 4.0000, + 1.0, -0.5772, 1.6449, -1.1395, 2.4000, + 12.0, 2.4427, 0.0869, -0.2946, 0.1735, + ]).reshape(-1, 5) + for c, mean, var, skew, kurt in table: + computed = stats.loggamma.stats(c, moments='msvk') + assert_array_almost_equal(computed, [mean, var, skew, kurt], + decimal=4) + + @pytest.mark.parametrize('c', [0.1, 0.001]) + def test_rvs(self, c): + # Regression test for gh-11094. + x = stats.loggamma.rvs(c, size=100000, random_state=self.rng) + # Before gh-11094 was fixed, the case with c=0.001 would + # generate many -inf values. + assert np.isfinite(x).all() + # Crude statistical test. About half the values should be + # less than the median and half greater than the median. + med = stats.loggamma.median(c) + btest = stats.binomtest(np.count_nonzero(x < med), len(x)) + ci = btest.proportion_ci(confidence_level=0.999) + assert ci.low < 0.5 < ci.high + + @pytest.mark.parametrize("c, ref", + [(1e-8, 19.420680753952364), + (1, 1.5772156649015328), + (1e4, -3.186214986116763), + (1e10, -10.093986931748889), + (1e100, -113.71031611649761)]) + def test_entropy(self, c, ref): + + # Reference values were calculated with mpmath + # from mpmath import mp + # mp.dps = 500 + # def loggamma_entropy_mpmath(c): + # c = mp.mpf(c) + # return float(mp.log(mp.gamma(c)) + c * (mp.one - mp.digamma(c))) + + assert_allclose(stats.loggamma.entropy(c), ref, rtol=1e-14) + + +class TestJohnsonsu: + # reference values were computed via mpmath + # from mpmath import mp + # mp.dps = 50 + # def johnsonsu_sf(x, a, b): + # x = mp.mpf(x) + # a = mp.mpf(a) + # b = mp.mpf(b) + # return float(mp.ncdf(-(a + b * mp.log(x + mp.sqrt(x*x + 1))))) + # Order is x, a, b, sf, isf tol + # (Can't expect full precision when the ISF input is very nearly 1) + cases = [(-500, 1, 1, 0.9999999982660072, 1e-8), + (2000, 1, 1, 7.426351000595343e-21, 5e-14), + (100000, 1, 1, 4.046923979269977e-40, 5e-14)] + + @pytest.mark.parametrize("case", cases) + def test_sf_isf(self, case): + x, a, b, sf, tol = case + assert_allclose(stats.johnsonsu.sf(x, a, b), sf, rtol=5e-14) + assert_allclose(stats.johnsonsu.isf(sf, a, b), x, rtol=tol) + + +class TestJohnsonb: + # reference values were computed via mpmath + # from mpmath import mp + # mp.dps = 50 + # def johnsonb_sf(x, a, b): + # x = mp.mpf(x) + # a = mp.mpf(a) + # b = mp.mpf(b) + # return float(mp.ncdf(-(a + b * mp.log(x/(mp.one - x))))) + # Order is x, a, b, sf, isf atol + # (Can't expect full precision when the ISF input is very nearly 1) + cases = [(1e-4, 1, 1, 0.9999999999999999, 1e-7), + (0.9999, 1, 1, 8.921114313932308e-25, 5e-14), + (0.999999, 1, 1, 5.815197487181902e-50, 5e-14)] + + @pytest.mark.parametrize("case", cases) + def test_sf_isf(self, case): + x, a, b, sf, tol = case + assert_allclose(stats.johnsonsb.sf(x, a, b), sf, rtol=5e-14) + assert_allclose(stats.johnsonsb.isf(sf, a, b), x, atol=tol) + + +class TestLogistic: + def setup_method(self): + self.rng = np.random.default_rng(2807014525) + + # gh-6226 + def test_cdf_ppf(self): + x = np.linspace(-20, 20) + y = stats.logistic.cdf(x) + xx = stats.logistic.ppf(y) + assert_allclose(x, xx) + + def test_sf_isf(self): + x = np.linspace(-20, 20) + y = stats.logistic.sf(x) + xx = stats.logistic.isf(y) + assert_allclose(x, xx) + + def test_extreme_values(self): + # p is chosen so that 1 - (1 - p) == p in double precision + p = 9.992007221626409e-16 + desired = 34.53957599234088 + assert_allclose(stats.logistic.ppf(1 - p), desired) + assert_allclose(stats.logistic.isf(p), desired) + + def test_logpdf_basic(self): + logp = stats.logistic.logpdf([-15, 0, 10]) + # Expected values computed with mpmath with 50 digits of precision. + expected = [-15.000000611804547, + -1.3862943611198906, + -10.000090797798434] + assert_allclose(logp, expected, rtol=1e-13) + + def test_logpdf_extreme_values(self): + logp = stats.logistic.logpdf([800, -800]) + # For such large arguments, logpdf(x) = -abs(x) when computed + # with 64 bit floating point. + assert_equal(logp, [-800, -800]) + + @pytest.mark.parametrize("loc_rvs,scale_rvs", [(0.4484955, 0.10216821), + (0.62918191, 0.74367064)]) + def test_fit(self, loc_rvs, scale_rvs): + data = stats.logistic.rvs(size=100, loc=loc_rvs, scale=scale_rvs, + random_state=self.rng) + + # test that result of fit method is the same as optimization + def func(input, data): + a, b = input + n = len(data) + x1 = np.sum(np.exp((data - a) / b) / + (1 + np.exp((data - a) / b))) - n / 2 + x2 = np.sum(((data - a) / b) * + ((np.exp((data - a) / b) - 1) / + (np.exp((data - a) / b) + 1))) - n + return x1, x2 + + expected_solution = root(func, stats.logistic._fitstart(data), args=( + data,)).x + fit_method = stats.logistic.fit(data) + + # other than computational variances, the fit method and the solution + # to this system of equations are equal + assert_allclose(fit_method, expected_solution, atol=1e-30) + + def test_fit_comp_optimizer(self): + data = stats.logistic.rvs(size=100, loc=0.5, scale=2, random_state=self.rng) + _assert_less_or_close_loglike(stats.logistic, data) + _assert_less_or_close_loglike(stats.logistic, data, floc=1) + _assert_less_or_close_loglike(stats.logistic, data, fscale=1) + + @pytest.mark.parametrize('testlogcdf', [True, False]) + def test_logcdfsf_tails(self, testlogcdf): + # Test either logcdf or logsf. By symmetry, we can use the same + # expected values for both by switching the sign of x for logsf. + x = np.array([-10000, -800, 17, 50, 500]) + if testlogcdf: + y = stats.logistic.logcdf(x) + else: + y = stats.logistic.logsf(-x) + # The expected values were computed with mpmath. + expected = [-10000.0, -800.0, -4.139937633089748e-08, + -1.9287498479639178e-22, -7.124576406741286e-218] + assert_allclose(y, expected, rtol=2e-15) + + def test_fit_gh_18176(self): + # logistic.fit returned `scale < 0` for this data. Check that this has + # been fixed. + data = np.array([-459, 37, 43, 45, 45, 48, 54, 55, 58] + + [59] * 3 + [61] * 9) + # If scale were negative, NLLF would be infinite, so this would fail + _assert_less_or_close_loglike(stats.logistic, data) + + +class TestLogser: + def setup_method(self): + self.rng = np.random.default_rng(187505461) + + def test_rvs(self): + vals = stats.logser.rvs(0.75, size=(2, 50), random_state=self.rng) + assert np.all(vals >= 1) + assert np.shape(vals) == (2, 50) + assert vals.dtype.char in typecodes['AllInteger'] + val = stats.logser.rvs(0.75, random_state=self.rng) + assert isinstance(val, int) + val = stats.logser(0.75).rvs(3, random_state=self.rng) + assert isinstance(val, np.ndarray) + assert val.dtype.char in typecodes['AllInteger'] + + def test_pmf_small_p(self): + m = stats.logser.pmf(4, 1e-20) + # The expected value was computed using mpmath: + # >>> import mpmath + # >>> mpmath.mp.dps = 64 + # >>> k = 4 + # >>> p = mpmath.mpf('1e-20') + # >>> float(-(p**k)/k/mpmath.log(1-p)) + # 2.5e-61 + # It is also clear from noticing that for very small p, + # log(1-p) is approximately -p, and the formula becomes + # p**(k-1) / k + assert_allclose(m, 2.5e-61) + + def test_mean_small_p(self): + m = stats.logser.mean(1e-8) + # The expected mean was computed using mpmath: + # >>> import mpmath + # >>> mpmath.dps = 60 + # >>> p = mpmath.mpf('1e-8') + # >>> float(-p / ((1 - p)*mpmath.log(1 - p))) + # 1.000000005 + assert_allclose(m, 1.000000005) + + def test_sf(self): + p = [[0.5], [1e-5], [1 - 1e-5]] + k = [1, 10, 100, 1000] + # Reference values from Wolfram Alpha, e.g. + # SurvivalFunction[LogSeriesDistribution[99999/100000], 1000] + # 0.35068668662799737584735036958139157462633608106500173019897861351038286634 + ref = [[0.2786524795555183, 0.00011876901682721189, + 1.1159788564768581e-32, 1.3437300083506688e-304], + [5.000008333375e-06, 9.090946969973778e-52, 0, 0], + [0.9131419722083134, 0.745601735620566, + 0.5495169511115096, 0.3506866866279974]] + res = stats.logser.sf(k, p) + np.testing.assert_allclose(res, ref, atol=1e-300) + + +class TestGumbel_r_l: + @pytest.fixture(scope='function') + def rng(self): + return np.random.default_rng(1234) + + @pytest.mark.parametrize("dist", [stats.gumbel_r, stats.gumbel_l]) + @pytest.mark.parametrize("loc_rvs", [-1, 0, 1]) + @pytest.mark.parametrize("scale_rvs", [.1, 1, 5]) + @pytest.mark.parametrize('fix_loc, fix_scale', + ([True, False], [False, True])) + def test_fit_comp_optimizer(self, dist, loc_rvs, scale_rvs, + fix_loc, fix_scale, rng): + data = dist.rvs(size=100, loc=loc_rvs, scale=scale_rvs, + random_state=rng) + + kwds = dict() + # the fixed location and scales are arbitrarily modified to not be + # close to the true value. + if fix_loc: + kwds['floc'] = loc_rvs * 2 + if fix_scale: + kwds['fscale'] = scale_rvs * 2 + + # test that the gumbel_* fit method is better than super method + _assert_less_or_close_loglike(dist, data, **kwds) + + @pytest.mark.parametrize("dist, sgn", [(stats.gumbel_r, 1), + (stats.gumbel_l, -1)]) + def test_fit(self, dist, sgn): + z = sgn*np.array([3, 3, 3, 3, 3, 3, 3, 3.00000001]) + loc, scale = dist.fit(z) + # The expected values were computed with mpmath with 60 digits + # of precision. + assert_allclose(loc, sgn*3.0000000001667906) + assert_allclose(scale, 1.2495222465145514e-09, rtol=1e-6) + + +class TestPareto: + def test_stats(self): + # Check the stats() method with some simple values. Also check + # that the calculations do not trigger RuntimeWarnings. + with warnings.catch_warnings(): + warnings.simplefilter("error", RuntimeWarning) + + m, v, s, k = stats.pareto.stats(0.5, moments='mvsk') + assert_equal(m, np.inf) + assert_equal(v, np.inf) + assert_equal(s, np.nan) + assert_equal(k, np.nan) + + m, v, s, k = stats.pareto.stats(1.0, moments='mvsk') + assert_equal(m, np.inf) + assert_equal(v, np.inf) + assert_equal(s, np.nan) + assert_equal(k, np.nan) + + m, v, s, k = stats.pareto.stats(1.5, moments='mvsk') + assert_equal(m, 3.0) + assert_equal(v, np.inf) + assert_equal(s, np.nan) + assert_equal(k, np.nan) + + m, v, s, k = stats.pareto.stats(2.0, moments='mvsk') + assert_equal(m, 2.0) + assert_equal(v, np.inf) + assert_equal(s, np.nan) + assert_equal(k, np.nan) + + m, v, s, k = stats.pareto.stats(2.5, moments='mvsk') + assert_allclose(m, 2.5 / 1.5) + assert_allclose(v, 2.5 / (1.5*1.5*0.5)) + assert_equal(s, np.nan) + assert_equal(k, np.nan) + + m, v, s, k = stats.pareto.stats(3.0, moments='mvsk') + assert_allclose(m, 1.5) + assert_allclose(v, 0.75) + assert_equal(s, np.nan) + assert_equal(k, np.nan) + + m, v, s, k = stats.pareto.stats(3.5, moments='mvsk') + assert_allclose(m, 3.5 / 2.5) + assert_allclose(v, 3.5 / (2.5*2.5*1.5)) + assert_allclose(s, (2*4.5/0.5)*np.sqrt(1.5/3.5)) + assert_equal(k, np.nan) + + m, v, s, k = stats.pareto.stats(4.0, moments='mvsk') + assert_allclose(m, 4.0 / 3.0) + assert_allclose(v, 4.0 / 18.0) + assert_allclose(s, 2*(1+4.0)/(4.0-3) * np.sqrt((4.0-2)/4.0)) + assert_equal(k, np.nan) + + m, v, s, k = stats.pareto.stats(4.5, moments='mvsk') + assert_allclose(m, 4.5 / 3.5) + assert_allclose(v, 4.5 / (3.5*3.5*2.5)) + assert_allclose(s, (2*5.5/1.5) * np.sqrt(2.5/4.5)) + assert_allclose(k, 6*(4.5**3 + 4.5**2 - 6*4.5 - 2)/(4.5*1.5*0.5)) + + def test_sf(self): + x = 1e9 + b = 2 + scale = 1.5 + p = stats.pareto.sf(x, b, loc=0, scale=scale) + expected = (scale/x)**b # 2.25e-18 + assert_allclose(p, expected) + + @pytest.mark.filterwarnings("ignore:invalid value encountered in " + "double_scalars") + @pytest.mark.parametrize("rvs_shape", [1, 2]) + @pytest.mark.parametrize("rvs_loc", [0, 2]) + @pytest.mark.parametrize("rvs_scale", [1, 5]) + def test_fit(self, rvs_shape, rvs_loc, rvs_scale): + rng = np.random.default_rng(1234) + data = stats.pareto.rvs(size=100, b=rvs_shape, scale=rvs_scale, + loc=rvs_loc, random_state=rng) + + # shape can still be fixed with multiple names + shape_mle_analytical1 = stats.pareto.fit(data, floc=0, f0=1.04)[0] + shape_mle_analytical2 = stats.pareto.fit(data, floc=0, fix_b=1.04)[0] + shape_mle_analytical3 = stats.pareto.fit(data, floc=0, fb=1.04)[0] + assert (shape_mle_analytical1 == shape_mle_analytical2 == + shape_mle_analytical3 == 1.04) + + # data can be shifted with changes to `loc` + data = stats.pareto.rvs(size=100, b=rvs_shape, scale=rvs_scale, + loc=(rvs_loc + 2), random_state=rng) + shape_mle_a, loc_mle_a, scale_mle_a = stats.pareto.fit(data, floc=2) + assert_equal(scale_mle_a + 2, data.min()) + + data_shift = data - 2 + ndata = data_shift.shape[0] + assert_equal(shape_mle_a, + ndata / np.sum(np.log(data_shift/data_shift.min()))) + assert_equal(loc_mle_a, 2) + + @pytest.mark.parametrize("rvs_shape", [.1, 2]) + @pytest.mark.parametrize("rvs_loc", [0, 2]) + @pytest.mark.parametrize("rvs_scale", [1, 5]) + @pytest.mark.parametrize('fix_shape, fix_loc, fix_scale', + [p for p in product([True, False], repeat=3) + if False in p]) + @np.errstate(invalid="ignore") + def test_fit_MLE_comp_optimizer(self, rvs_shape, rvs_loc, rvs_scale, + fix_shape, fix_loc, fix_scale): + rng = np.random.default_rng(1234) + data = stats.pareto.rvs(size=100, b=rvs_shape, scale=rvs_scale, + loc=rvs_loc, random_state=rng) + + kwds = {} + if fix_shape: + kwds['f0'] = rvs_shape + if fix_loc: + kwds['floc'] = rvs_loc + if fix_scale: + kwds['fscale'] = rvs_scale + + _assert_less_or_close_loglike(stats.pareto, data, **kwds) + + @np.errstate(invalid="ignore") + def test_fit_known_bad_seed(self): + # Tests a known seed and set of parameters that would produce a result + # would violate the support of Pareto if the fit method did not check + # the constraint `fscale + floc < min(data)`. + shape, location, scale = 1, 0, 1 + data = stats.pareto.rvs(shape, location, scale, size=100, + random_state=np.random.default_rng(2535619)) + _assert_less_or_close_loglike(stats.pareto, data) + + def test_fit_warnings(self): + assert_fit_warnings(stats.pareto) + # `floc` that causes invalid negative data + assert_raises(FitDataError, stats.pareto.fit, [1, 2, 3], floc=2) + # `floc` and `fscale` combination causes invalid data + assert_raises(FitDataError, stats.pareto.fit, [5, 2, 3], floc=1, + fscale=3) + + def test_negative_data(self): + rng = np.random.default_rng(1234) + data = stats.pareto.rvs(loc=-130, b=1, size=100, random_state=rng) + assert_array_less(data, 0) + # The purpose of this test is to make sure that no runtime warnings are + # raised for all negative data, not the output of the fit method. Other + # methods test the output but have to silence warnings from the super + # method. + _ = stats.pareto.fit(data) + + +class TestGenpareto: + def test_ab(self): + # c >= 0: a, b = [0, inf] + for c in [1., 0.]: + c = np.asarray(c) + a, b = stats.genpareto._get_support(c) + assert_equal(a, 0.) + assert_(np.isposinf(b)) + + # c < 0: a=0, b=1/|c| + c = np.asarray(-2.) + a, b = stats.genpareto._get_support(c) + assert_allclose([a, b], [0., 0.5]) + + def test_c0(self): + # with c=0, genpareto reduces to the exponential distribution + # rv = stats.genpareto(c=0.) + rv = stats.genpareto(c=0.) + x = np.linspace(0, 10., 30) + assert_allclose(rv.pdf(x), stats.expon.pdf(x)) + assert_allclose(rv.cdf(x), stats.expon.cdf(x)) + assert_allclose(rv.sf(x), stats.expon.sf(x)) + + q = np.linspace(0., 1., 10) + assert_allclose(rv.ppf(q), stats.expon.ppf(q)) + + def test_cm1(self): + # with c=-1, genpareto reduces to the uniform distr on [0, 1] + rv = stats.genpareto(c=-1.) + x = np.linspace(0, 10., 30) + assert_allclose(rv.pdf(x), stats.uniform.pdf(x)) + assert_allclose(rv.cdf(x), stats.uniform.cdf(x)) + assert_allclose(rv.sf(x), stats.uniform.sf(x)) + + q = np.linspace(0., 1., 10) + assert_allclose(rv.ppf(q), stats.uniform.ppf(q)) + + # logpdf(1., c=-1) should be zero + assert_allclose(rv.logpdf(1), 0) + + def test_x_inf(self): + # make sure x=inf is handled gracefully + rv = stats.genpareto(c=0.1) + assert_allclose([rv.pdf(np.inf), rv.cdf(np.inf)], [0., 1.]) + assert_(np.isneginf(rv.logpdf(np.inf))) + + rv = stats.genpareto(c=0.) + assert_allclose([rv.pdf(np.inf), rv.cdf(np.inf)], [0., 1.]) + assert_(np.isneginf(rv.logpdf(np.inf))) + + rv = stats.genpareto(c=-1.) + assert_allclose([rv.pdf(np.inf), rv.cdf(np.inf)], [0., 1.]) + assert_(np.isneginf(rv.logpdf(np.inf))) + + def test_c_continuity(self): + # pdf is continuous at c=0, -1 + x = np.linspace(0, 10, 30) + for c in [0, -1]: + pdf0 = stats.genpareto.pdf(x, c) + for dc in [1e-14, -1e-14]: + pdfc = stats.genpareto.pdf(x, c + dc) + assert_allclose(pdf0, pdfc, atol=1e-12) + + cdf0 = stats.genpareto.cdf(x, c) + for dc in [1e-14, 1e-14]: + cdfc = stats.genpareto.cdf(x, c + dc) + assert_allclose(cdf0, cdfc, atol=1e-12) + + def test_c_continuity_ppf(self): + q = np.r_[np.logspace(1e-12, 0.01, base=0.1), + np.linspace(0.01, 1, 30, endpoint=False), + 1. - np.logspace(1e-12, 0.01, base=0.1)] + for c in [0., -1.]: + ppf0 = stats.genpareto.ppf(q, c) + for dc in [1e-14, -1e-14]: + ppfc = stats.genpareto.ppf(q, c + dc) + assert_allclose(ppf0, ppfc, atol=1e-12) + + def test_c_continuity_isf(self): + q = np.r_[np.logspace(1e-12, 0.01, base=0.1), + np.linspace(0.01, 1, 30, endpoint=False), + 1. - np.logspace(1e-12, 0.01, base=0.1)] + for c in [0., -1.]: + isf0 = stats.genpareto.isf(q, c) + for dc in [1e-14, -1e-14]: + isfc = stats.genpareto.isf(q, c + dc) + assert_allclose(isf0, isfc, atol=1e-12) + + def test_cdf_ppf_roundtrip(self): + # this should pass with machine precision. hat tip @pbrod + q = np.r_[np.logspace(1e-12, 0.01, base=0.1), + np.linspace(0.01, 1, 30, endpoint=False), + 1. - np.logspace(1e-12, 0.01, base=0.1)] + for c in [1e-8, -1e-18, 1e-15, -1e-15]: + assert_allclose(stats.genpareto.cdf(stats.genpareto.ppf(q, c), c), + q, atol=1e-15) + + def test_logsf(self): + logp = stats.genpareto.logsf(1e10, .01, 0, 1) + assert_allclose(logp, -1842.0680753952365) + + # Values in 'expected_stats' are + # [mean, variance, skewness, excess kurtosis]. + @pytest.mark.parametrize( + 'c, expected_stats', + [(0, [1, 1, 2, 6]), + (1/4, [4/3, 32/9, 10/np.sqrt(2), np.nan]), + (1/9, [9/8, (81/64)*(9/7), (10/9)*np.sqrt(7), 754/45]), + (-1, [1/2, 1/12, 0, -6/5])]) + def test_stats(self, c, expected_stats): + result = stats.genpareto.stats(c, moments='mvsk') + assert_allclose(result, expected_stats, rtol=1e-13, atol=1e-15) + + def test_var(self): + # Regression test for gh-11168. + v = stats.genpareto.var(1e-8) + assert_allclose(v, 1.000000040000001, rtol=1e-13) + + +class TestPearson3: + def setup_method(self): + self.rng = np.random.default_rng(2775995570) + + def test_rvs(self): + vals = stats.pearson3.rvs(0.1, size=(2, 50), random_state=self.rng) + assert np.shape(vals) == (2, 50) + assert vals.dtype.char in typecodes['AllFloat'] + val = stats.pearson3.rvs(0.5, random_state=self.rng) + assert isinstance(val, float) + val = stats.pearson3(0.5).rvs(3, random_state=self.rng) + assert isinstance(val, np.ndarray) + assert val.dtype.char in typecodes['AllFloat'] + assert len(val) == 3 + + def test_pdf(self): + vals = stats.pearson3.pdf(2, [0.0, 0.1, 0.2]) + assert_allclose(vals, np.array([0.05399097, 0.05555481, 0.05670246]), + atol=1e-6) + vals = stats.pearson3.pdf(-3, 0.1) + assert_allclose(vals, np.array([0.00313791]), atol=1e-6) + vals = stats.pearson3.pdf([-3, -2, -1, 0, 1], 0.1) + assert_allclose(vals, np.array([0.00313791, 0.05192304, 0.25028092, + 0.39885918, 0.23413173]), atol=1e-6) + + def test_cdf(self): + vals = stats.pearson3.cdf(2, [0.0, 0.1, 0.2]) + assert_allclose(vals, np.array([0.97724987, 0.97462004, 0.97213626]), + atol=1e-6) + vals = stats.pearson3.cdf(-3, 0.1) + assert_allclose(vals, [0.00082256], atol=1e-6) + vals = stats.pearson3.cdf([-3, -2, -1, 0, 1], 0.1) + assert_allclose(vals, [8.22563821e-04, 1.99860448e-02, 1.58550710e-01, + 5.06649130e-01, 8.41442111e-01], atol=1e-6) + + def test_negative_cdf_bug_11186(self): + # incorrect CDFs for negative skews in gh-11186; fixed in gh-12640 + # Also check vectorization w/ negative, zero, and positive skews + skews = [-3, -1, 0, 0.5] + x_eval = 0.5 + neg_inf = -30 # avoid RuntimeWarning caused by np.log(0) + cdfs = stats.pearson3.cdf(x_eval, skews) + int_pdfs = [quad(stats.pearson3(skew).pdf, neg_inf, x_eval)[0] + for skew in skews] + assert_allclose(cdfs, int_pdfs) + + def test_return_array_bug_11746(self): + # pearson3.moment was returning size 0 or 1 array instead of float + # The first moment is equal to the loc, which defaults to zero + moment = stats.pearson3.moment(1, 2) + assert_equal(moment, 0) + assert isinstance(moment, np.number) + + moment = stats.pearson3.moment(1, 0.000001) + assert_equal(moment, 0) + assert isinstance(moment, np.number) + + def test_ppf_bug_17050(self): + # incorrect PPF for negative skews were reported in gh-17050 + # Check that this is fixed (even in the array case) + skews = [-3, -1, 0, 0.5] + x_eval = 0.5 + res = stats.pearson3.ppf(stats.pearson3.cdf(x_eval, skews), skews) + assert_allclose(res, x_eval) + + # Negation of the skew flips the distribution about the origin, so + # the following should hold + skew = np.array([[-0.5], [1.5]]) + x = np.linspace(-2, 2) + assert_allclose(stats.pearson3.pdf(x, skew), + stats.pearson3.pdf(-x, -skew)) + assert_allclose(stats.pearson3.cdf(x, skew), + stats.pearson3.sf(-x, -skew)) + assert_allclose(stats.pearson3.ppf(x, skew), + -stats.pearson3.isf(x, -skew)) + + def test_sf(self): + # reference values were computed via the reference distribution, e.g. + # mp.dps = 50; Pearson3(skew=skew).sf(x). Check positive, negative, + # and zero skew due to branching. + skew = [0.1, 0.5, 1.0, -0.1] + x = [5.0, 10.0, 50.0, 8.0] + ref = [1.64721926440872e-06, 8.271911573556123e-11, + 1.3149506021756343e-40, 2.763057937820296e-21] + assert_allclose(stats.pearson3.sf(x, skew), ref, rtol=2e-14) + assert_allclose(stats.pearson3.sf(x, 0), stats.norm.sf(x), rtol=2e-14) + + +class TestKappa4: + def test_cdf_genpareto(self): + # h = 1 and k != 0 is generalized Pareto + x = [0.0, 0.1, 0.2, 0.5] + h = 1.0 + for k in [-1.9, -1.0, -0.5, -0.2, -0.1, 0.1, 0.2, 0.5, 1.0, + 1.9]: + vals = stats.kappa4.cdf(x, h, k) + # shape parameter is opposite what is expected + vals_comp = stats.genpareto.cdf(x, -k) + assert_allclose(vals, vals_comp) + + def test_cdf_genextreme(self): + # h = 0 and k != 0 is generalized extreme value + x = np.linspace(-5, 5, 10) + h = 0.0 + k = np.linspace(-3, 3, 10) + vals = stats.kappa4.cdf(x, h, k) + vals_comp = stats.genextreme.cdf(x, k) + assert_allclose(vals, vals_comp) + + def test_cdf_expon(self): + # h = 1 and k = 0 is exponential + x = np.linspace(0, 10, 10) + h = 1.0 + k = 0.0 + vals = stats.kappa4.cdf(x, h, k) + vals_comp = stats.expon.cdf(x) + assert_allclose(vals, vals_comp) + + def test_cdf_gumbel_r(self): + # h = 0 and k = 0 is gumbel_r + x = np.linspace(-5, 5, 10) + h = 0.0 + k = 0.0 + vals = stats.kappa4.cdf(x, h, k) + vals_comp = stats.gumbel_r.cdf(x) + assert_allclose(vals, vals_comp) + + def test_cdf_logistic(self): + # h = -1 and k = 0 is logistic + x = np.linspace(-5, 5, 10) + h = -1.0 + k = 0.0 + vals = stats.kappa4.cdf(x, h, k) + vals_comp = stats.logistic.cdf(x) + assert_allclose(vals, vals_comp) + + def test_cdf_uniform(self): + # h = 1 and k = 1 is uniform + x = np.linspace(-5, 5, 10) + h = 1.0 + k = 1.0 + vals = stats.kappa4.cdf(x, h, k) + vals_comp = stats.uniform.cdf(x) + assert_allclose(vals, vals_comp) + + def test_integers_ctor(self): + # regression test for gh-7416: _argcheck fails for integer h and k + # in numpy 1.12 + stats.kappa4(1, 2) + + +class TestPoisson: + def setup_method(self): + self.rng = np.random.default_rng(9799340796) + + def test_pmf_basic(self): + # Basic case + ln2 = np.log(2) + vals = stats.poisson.pmf([0, 1, 2], ln2) + expected = [0.5, ln2/2, ln2**2/4] + assert_allclose(vals, expected) + + def test_mu0(self): + # Edge case: mu=0 + vals = stats.poisson.pmf([0, 1, 2], 0) + expected = [1, 0, 0] + assert_array_equal(vals, expected) + + interval = stats.poisson.interval(0.95, 0) + assert_equal(interval, (0, 0)) + + def test_rvs(self): + vals = stats.poisson.rvs(0.5, size=(2, 50), random_state=self.rng) + assert np.all(vals >= 0) + assert np.shape(vals) == (2, 50) + assert vals.dtype.char in typecodes['AllInteger'] + val = stats.poisson.rvs(0.5, random_state=self.rng) + assert isinstance(val, int) + val = stats.poisson(0.5).rvs(3, random_state=self.rng) + assert isinstance(val, np.ndarray) + assert val.dtype.char in typecodes['AllInteger'] + + def test_stats(self): + mu = 16.0 + result = stats.poisson.stats(mu, moments='mvsk') + assert_allclose(result, [mu, mu, np.sqrt(1.0/mu), 1.0/mu]) + + mu = np.array([0.0, 1.0, 2.0]) + result = stats.poisson.stats(mu, moments='mvsk') + expected = (mu, mu, [np.inf, 1, 1/np.sqrt(2)], [np.inf, 1, 0.5]) + assert_allclose(result, expected) + + +class TestKSTwo: + + def test_cdf(self): + for n in [1, 2, 3, 10, 100, 1000]: + # Test x-values: + # 0, 1/2n, where the cdf should be 0 + # 1/n, where the cdf should be n!/n^n + # 0.5, where the cdf should match ksone.cdf + # 1-1/n, where cdf = 1-2/n^n + # 1, where cdf == 1 + # (E.g. Exact values given by Eqn 1 in Simard / L'Ecuyer) + x = np.array([0, 0.5/n, 1/n, 0.5, 1-1.0/n, 1]) + v1 = (1.0/n)**n + lg = scipy.special.gammaln(n+1) + elg = (np.exp(lg) if v1 != 0 else 0) + expected = np.array([0, 0, v1 * elg, + 1 - 2*stats.ksone.sf(0.5, n), + max(1 - 2*v1, 0.0), + 1.0]) + vals_cdf = stats.kstwo.cdf(x, n) + assert_allclose(vals_cdf, expected) + + def test_sf(self): + x = np.linspace(0, 1, 11) + for n in [1, 2, 3, 10, 100, 1000]: + # Same x values as in test_cdf, and use sf = 1 - cdf + x = np.array([0, 0.5/n, 1/n, 0.5, 1-1.0/n, 1]) + v1 = (1.0/n)**n + lg = scipy.special.gammaln(n+1) + elg = (np.exp(lg) if v1 != 0 else 0) + expected = np.array([1.0, 1.0, + 1 - v1 * elg, + 2*stats.ksone.sf(0.5, n), + min(2*v1, 1.0), 0]) + vals_sf = stats.kstwo.sf(x, n) + assert_allclose(vals_sf, expected) + + def test_cdf_sqrtn(self): + # For fixed a, cdf(a/sqrt(n), n) -> kstwobign(a) as n->infinity + # cdf(a/sqrt(n), n) is an increasing function of n (and a) + # Check that the function is indeed increasing (allowing for some + # small floating point and algorithm differences.) + x = np.linspace(0, 2, 11)[1:] + ns = [50, 100, 200, 400, 1000, 2000] + for _x in x: + xn = _x / np.sqrt(ns) + probs = stats.kstwo.cdf(xn, ns) + diffs = np.diff(probs) + assert_array_less(diffs, 1e-8) + + def test_cdf_sf(self): + x = np.linspace(0, 1, 11) + for n in [1, 2, 3, 10, 100, 1000]: + vals_cdf = stats.kstwo.cdf(x, n) + vals_sf = stats.kstwo.sf(x, n) + assert_array_almost_equal(vals_cdf, 1 - vals_sf) + + def test_cdf_sf_sqrtn(self): + x = np.linspace(0, 1, 11) + for n in [1, 2, 3, 10, 100, 1000]: + xn = x / np.sqrt(n) + vals_cdf = stats.kstwo.cdf(xn, n) + vals_sf = stats.kstwo.sf(xn, n) + assert_array_almost_equal(vals_cdf, 1 - vals_sf) + + def test_ppf_of_cdf(self): + x = np.linspace(0, 1, 11) + for n in [1, 2, 3, 10, 100, 1000]: + xn = x[x > 0.5/n] + vals_cdf = stats.kstwo.cdf(xn, n) + # CDFs close to 1 are better dealt with using the SF + cond = (0 < vals_cdf) & (vals_cdf < 0.99) + vals = stats.kstwo.ppf(vals_cdf, n) + assert_allclose(vals[cond], xn[cond], rtol=1e-4) + + def test_isf_of_sf(self): + x = np.linspace(0, 1, 11) + for n in [1, 2, 3, 10, 100, 1000]: + xn = x[x > 0.5/n] + vals_isf = stats.kstwo.isf(xn, n) + cond = (0 < vals_isf) & (vals_isf < 1.0) + vals = stats.kstwo.sf(vals_isf, n) + assert_allclose(vals[cond], xn[cond], rtol=1e-4) + + def test_ppf_of_cdf_sqrtn(self): + x = np.linspace(0, 1, 11) + for n in [1, 2, 3, 10, 100, 1000]: + xn = (x / np.sqrt(n))[x > 0.5/n] + vals_cdf = stats.kstwo.cdf(xn, n) + cond = (0 < vals_cdf) & (vals_cdf < 1.0) + vals = stats.kstwo.ppf(vals_cdf, n) + assert_allclose(vals[cond], xn[cond]) + + def test_isf_of_sf_sqrtn(self): + x = np.linspace(0, 1, 11) + for n in [1, 2, 3, 10, 100, 1000]: + xn = (x / np.sqrt(n))[x > 0.5/n] + vals_sf = stats.kstwo.sf(xn, n) + # SFs close to 1 are better dealt with using the CDF + cond = (0 < vals_sf) & (vals_sf < 0.95) + vals = stats.kstwo.isf(vals_sf, n) + assert_allclose(vals[cond], xn[cond]) + + def test_ppf(self): + probs = np.linspace(0, 1, 11)[1:] + for n in [1, 2, 3, 10, 100, 1000]: + xn = stats.kstwo.ppf(probs, n) + vals_cdf = stats.kstwo.cdf(xn, n) + assert_allclose(vals_cdf, probs) + + def test_simard_lecuyer_table1(self): + # Compute the cdf for values near the mean of the distribution. + # The mean u ~ log(2)*sqrt(pi/(2n)) + # Compute for x in [u/4, u/3, u/2, u, 2u, 3u] + # This is the computation of Table 1 of Simard, R., L'Ecuyer, P. (2011) + # "Computing the Two-Sided Kolmogorov-Smirnov Distribution". + # Except that the values below are not from the published table, but + # were generated using an independent SageMath implementation of + # Durbin's algorithm (with the exponentiation and scaling of + # Marsaglia/Tsang/Wang's version) using 500 bit arithmetic. + # Some of the values in the published table have relative + # errors greater than 1e-4. + ns = [10, 50, 100, 200, 500, 1000] + ratios = np.array([1.0/4, 1.0/3, 1.0/2, 1, 2, 3]) + expected = np.array([ + [1.92155292e-08, 5.72933228e-05, 2.15233226e-02, 6.31566589e-01, + 9.97685592e-01, 9.99999942e-01], + [2.28096224e-09, 1.99142563e-05, 1.42617934e-02, 5.95345542e-01, + 9.96177701e-01, 9.99998662e-01], + [1.00201886e-09, 1.32673079e-05, 1.24608594e-02, 5.86163220e-01, + 9.95866877e-01, 9.99998240e-01], + [4.93313022e-10, 9.52658029e-06, 1.12123138e-02, 5.79486872e-01, + 9.95661824e-01, 9.99997964e-01], + [2.37049293e-10, 6.85002458e-06, 1.01309221e-02, 5.73427224e-01, + 9.95491207e-01, 9.99997750e-01], + [1.56990874e-10, 5.71738276e-06, 9.59725430e-03, 5.70322692e-01, + 9.95409545e-01, 9.99997657e-01] + ]) + for idx, n in enumerate(ns): + x = ratios * np.log(2) * np.sqrt(np.pi/2/n) + vals_cdf = stats.kstwo.cdf(x, n) + assert_allclose(vals_cdf, expected[idx], rtol=1e-5) + + +class TestZipf: + def setup_method(self): + self.rng = np.random.default_rng(2444103536) + + def test_rvs(self): + vals = stats.zipf.rvs(1.5, size=(2, 50), random_state=self.rng) + assert np.all(vals >= 1) + assert np.shape(vals) == (2, 50) + assert vals.dtype.char in typecodes['AllInteger'] + val = stats.zipf.rvs(1.5, random_state=self.rng) + assert isinstance(val, int) + val = stats.zipf(1.5).rvs(3, random_state=self.rng) + assert isinstance(val, np.ndarray) + assert val.dtype.char in typecodes['AllInteger'] + + def test_moments(self): + # n-th moment is finite iff a > n + 1 + m, v = stats.zipf.stats(a=2.8) + assert_(np.isfinite(m)) + assert_equal(v, np.inf) + + s, k = stats.zipf.stats(a=4.8, moments='sk') + assert_(not np.isfinite([s, k]).all()) + + +class TestDLaplace: + def setup_method(self): + self.rng = np.random.default_rng(460403075) + + def test_rvs(self): + vals = stats.dlaplace.rvs(1.5, size=(2, 50), random_state=self.rng) + assert np.shape(vals) == (2, 50) + assert vals.dtype.char in typecodes['AllInteger'] + val = stats.dlaplace.rvs(1.5, random_state=self.rng) + assert isinstance(val, int) + val = stats.dlaplace(1.5).rvs(3, random_state=self.rng) + assert isinstance(val, np.ndarray) + assert val.dtype.char in typecodes['AllInteger'] + assert stats.dlaplace.rvs(0.8, random_state=self.rng) is not None + + def test_stats(self): + # compare the explicit formulas w/ direct summation using pmf + a = 1. + dl = stats.dlaplace(a) + m, v, s, k = dl.stats('mvsk') + + N = 37 + xx = np.arange(-N, N+1) + pp = dl.pmf(xx) + m2, m4 = np.sum(pp*xx**2), np.sum(pp*xx**4) + assert_equal((m, s), (0, 0)) + assert_allclose((v, k), (m2, m4/m2**2 - 3.), atol=1e-14, rtol=1e-8) + + def test_stats2(self): + a = np.log(2.) + dl = stats.dlaplace(a) + m, v, s, k = dl.stats('mvsk') + assert_equal((m, s), (0., 0.)) + assert_allclose((v, k), (4., 3.25)) + + +class TestInvgauss: + def setup_method(self): + self.rng = np.random.default_rng(5422839947) + + @pytest.mark.parametrize("rvs_mu,rvs_loc,rvs_scale", + [(2, 0, 1), (4.635, 4.362, 6.303)]) + def test_fit(self, rvs_mu, rvs_loc, rvs_scale): + data = stats.invgauss.rvs(size=100, mu=rvs_mu, + loc=rvs_loc, scale=rvs_scale, random_state=self.rng) + # Analytical MLEs are calculated with formula when `floc` is fixed + mu, loc, scale = stats.invgauss.fit(data, floc=rvs_loc) + + data = data - rvs_loc + mu_temp = np.mean(data) + scale_mle = len(data) / (np.sum(data**(-1) - mu_temp**(-1))) + mu_mle = mu_temp/scale_mle + + # `mu` and `scale` match analytical formula + assert_allclose(mu_mle, mu, atol=1e-15, rtol=1e-15) + assert_allclose(scale_mle, scale, atol=1e-15, rtol=1e-15) + assert_equal(loc, rvs_loc) + data = stats.invgauss.rvs(size=100, mu=rvs_mu, + loc=rvs_loc, scale=rvs_scale, random_state=self.rng) + # fixed parameters are returned + mu, loc, scale = stats.invgauss.fit(data, floc=rvs_loc - 1, + fscale=rvs_scale + 1) + assert_equal(rvs_scale + 1, scale) + assert_equal(rvs_loc - 1, loc) + + # shape can still be fixed with multiple names + shape_mle1 = stats.invgauss.fit(data, fmu=1.04)[0] + shape_mle2 = stats.invgauss.fit(data, fix_mu=1.04)[0] + shape_mle3 = stats.invgauss.fit(data, f0=1.04)[0] + assert shape_mle1 == shape_mle2 == shape_mle3 == 1.04 + + @pytest.mark.parametrize("rvs_mu,rvs_loc,rvs_scale", + [(2, 0, 1), (6.311, 3.225, 4.520)]) + def test_fit_MLE_comp_optimizer(self, rvs_mu, rvs_loc, rvs_scale): + rng = np.random.RandomState(1234) + data = stats.invgauss.rvs(size=100, mu=rvs_mu, + loc=rvs_loc, scale=rvs_scale, random_state=rng) + + super_fit = super(type(stats.invgauss), stats.invgauss).fit + # fitting without `floc` uses superclass fit method + super_fitted = super_fit(data) + invgauss_fit = stats.invgauss.fit(data) + assert_equal(super_fitted, invgauss_fit) + + # fitting with `fmu` is uses superclass fit method + super_fitted = super_fit(data, floc=0, fmu=2) + invgauss_fit = stats.invgauss.fit(data, floc=0, fmu=2) + assert_equal(super_fitted, invgauss_fit) + + # fixed `floc` uses analytical formula and provides better fit than + # super method + _assert_less_or_close_loglike(stats.invgauss, data, floc=rvs_loc) + + # fixed `floc` not resulting in invalid data < 0 uses analytical + # formulas and provides a better fit than the super method + assert np.all((data - (rvs_loc - 1)) > 0) + _assert_less_or_close_loglike(stats.invgauss, data, floc=rvs_loc - 1) + + # fixed `floc` to an arbitrary number, 0, still provides a better fit + # than the super method + _assert_less_or_close_loglike(stats.invgauss, data, floc=0) + + # fixed `fscale` to an arbitrary number still provides a better fit + # than the super method + _assert_less_or_close_loglike(stats.invgauss, data, floc=rvs_loc, + fscale=self.rng.random(1)[0]) + + def test_fit_raise_errors(self): + assert_fit_warnings(stats.invgauss) + # FitDataError is raised when negative invalid data + with pytest.raises(FitDataError): + stats.invgauss.fit([1, 2, 3], floc=2) + + def test_cdf_sf(self): + # Regression tests for gh-13614. + # Ground truth from R's statmod library (pinvgauss), e.g. + # library(statmod) + # options(digits=15) + # mu = c(4.17022005e-04, 7.20324493e-03, 1.14374817e-06, + # 3.02332573e-03, 1.46755891e-03) + # print(pinvgauss(5, mu, 1)) + + # make sure a finite value is returned when mu is very small. see + # GH-13614 + mu = [4.17022005e-04, 7.20324493e-03, 1.14374817e-06, + 3.02332573e-03, 1.46755891e-03] + expected = [1, 1, 1, 1, 1] + actual = stats.invgauss.cdf(0.4, mu=mu) + assert_equal(expected, actual) + + # test if the function can distinguish small left/right tail + # probabilities from zero. + cdf_actual = stats.invgauss.cdf(0.001, mu=1.05) + assert_allclose(cdf_actual, 4.65246506892667e-219) + sf_actual = stats.invgauss.sf(110, mu=1.05) + assert_allclose(sf_actual, 4.12851625944048e-25) + + # test if x does not cause numerical issues when mu is very small + # and x is close to mu in value. + + # slightly smaller than mu + actual = stats.invgauss.cdf(0.00009, 0.0001) + assert_allclose(actual, 2.9458022894924e-26) + + # slightly bigger than mu + actual = stats.invgauss.cdf(0.000102, 0.0001) + assert_allclose(actual, 0.976445540507925) + + def test_logcdf_logsf(self): + # Regression tests for improvements made in gh-13616. + # Ground truth from R's statmod library (pinvgauss), e.g. + # library(statmod) + # options(digits=15) + # print(pinvgauss(0.001, 1.05, 1, log.p=TRUE, lower.tail=FALSE)) + + # test if logcdf and logsf can compute values too small to + # be represented on the unlogged scale. See: gh-13616 + logcdf = stats.invgauss.logcdf(0.0001, mu=1.05) + assert_allclose(logcdf, -5003.87872590367) + logcdf = stats.invgauss.logcdf(110, 1.05) + assert_allclose(logcdf, -4.12851625944087e-25) + logsf = stats.invgauss.logsf(0.001, mu=1.05) + assert_allclose(logsf, -4.65246506892676e-219) + logsf = stats.invgauss.logsf(110, 1.05) + assert_allclose(logsf, -56.1467092416426) + + # from mpmath import mp + # mp.dps = 100 + # mu = mp.mpf(1e-2) + # ref = (1/2 * mp.log(2 * mp.pi * mp.e * mu**3) + # - 3/2* mp.exp(2/mu) * mp.e1(2/mu)) + @pytest.mark.parametrize("mu, ref", [(2e-8, -25.172361826883957), + (1e-3, -8.943444010642972), + (1e-2, -5.4962796152622335), + (1e8, 3.3244822568873476), + (1e100, 3.32448280139689)]) + def test_entropy(self, mu, ref): + assert_allclose(stats.invgauss.entropy(mu), ref, rtol=5e-14) + + def test_mu_inf_gh13666(self): + # invgauss methods should return correct result when mu=inf + # invgauss as mu -> oo is invgamma with shape and scale 0.5; + # see gh-13666 and gh-22496 + dist = stats.invgauss(mu=np.inf) + dist0 = stats.invgamma(0.5, scale=0.5) + x, p = 1., 0.5 + assert_allclose(dist.logpdf(x), dist0.logpdf(x)) + assert_allclose(dist.pdf(x), dist0.pdf(x)) + assert_allclose(dist.logcdf(x), dist0.logcdf(x)) + assert_allclose(dist.cdf(x), dist0.cdf(x)) + assert_allclose(dist.logsf(x), dist0.logsf(x)) + assert_allclose(dist.sf(x), dist0.sf(x)) + assert_allclose(dist.ppf(p), dist0.ppf(p)) + assert_allclose(dist.isf(p), dist0.isf(p)) + + +class TestLandau: + @pytest.mark.parametrize('name', ['pdf', 'cdf', 'sf', 'ppf', 'isf']) + def test_landau_levy_agreement(self, name): + # Test PDF to confirm that this is the Landau distribution + # Test other methods with tighter tolerance than generic tests + # Levy entropy is slow and inaccurate, and RVS is tested generically + if name in {'ppf', 'isf'}: + x = np.linspace(0.1, 0.9, 5), + else: + x = np.linspace(-2, 5, 10), + + landau_method = getattr(stats.landau, name) + levy_method = getattr(stats.levy_stable, name) + res = landau_method(*x) + ref = levy_method(*x, 1, 1) + assert_allclose(res, ref, rtol=1e-14) + + def test_moments(self): + # I would test these against Levy above, but Levy says variance is infinite. + assert_equal(stats.landau.stats(moments='mvsk'), (np.nan,)*4) + assert_equal(stats.landau.moment(5), np.nan) + + +class TestLaplace: + @pytest.mark.parametrize("rvs_loc", [-5, 0, 1, 2]) + @pytest.mark.parametrize("rvs_scale", [1, 2, 3, 10]) + def test_fit(self, rvs_loc, rvs_scale): + # tests that various inputs follow expected behavior + # for a variety of `loc` and `scale`. + rng = np.random.RandomState(1234) + data = stats.laplace.rvs(size=100, loc=rvs_loc, scale=rvs_scale, + random_state=rng) + + # MLE estimates are given by + loc_mle = np.median(data) + scale_mle = np.sum(np.abs(data - loc_mle)) / len(data) + + # standard outputs should match analytical MLE formulas + loc, scale = stats.laplace.fit(data) + assert_allclose(loc, loc_mle, atol=1e-15, rtol=1e-15) + assert_allclose(scale, scale_mle, atol=1e-15, rtol=1e-15) + + # fixed parameter should use analytical formula for other + loc, scale = stats.laplace.fit(data, floc=loc_mle) + assert_allclose(scale, scale_mle, atol=1e-15, rtol=1e-15) + loc, scale = stats.laplace.fit(data, fscale=scale_mle) + assert_allclose(loc, loc_mle) + + # test with non-mle fixed parameter + # create scale with non-median loc + loc = rvs_loc * 2 + scale_mle = np.sum(np.abs(data - loc)) / len(data) + + # fixed loc to non median, scale should match + # scale calculation with modified loc + loc, scale = stats.laplace.fit(data, floc=loc) + assert_equal(scale_mle, scale) + + # fixed scale created with non median loc, + # loc output should still be the data median. + loc, scale = stats.laplace.fit(data, fscale=scale_mle) + assert_equal(loc_mle, loc) + + # error raised when both `floc` and `fscale` are fixed + assert_raises(RuntimeError, stats.laplace.fit, data, floc=loc_mle, + fscale=scale_mle) + + # error is raised with non-finite values + assert_raises(ValueError, stats.laplace.fit, [np.nan]) + assert_raises(ValueError, stats.laplace.fit, [np.inf]) + + @pytest.mark.parametrize("rvs_loc,rvs_scale", [(-5, 10), + (10, 5), + (0.5, 0.2)]) + def test_fit_MLE_comp_optimizer(self, rvs_loc, rvs_scale): + rng = np.random.RandomState(1234) + data = stats.laplace.rvs(size=1000, loc=rvs_loc, scale=rvs_scale, + random_state=rng) + + # the log-likelihood function for laplace is given by + def ll(loc, scale, data): + return -1 * (- (len(data)) * np.log(2*scale) - + (1/scale)*np.sum(np.abs(data - loc))) + + # test that the objective function result of the analytical MLEs is + # less than or equal to that of the numerically optimized estimate + loc, scale = stats.laplace.fit(data) + loc_opt, scale_opt = super(type(stats.laplace), + stats.laplace).fit(data) + ll_mle = ll(loc, scale, data) + ll_opt = ll(loc_opt, scale_opt, data) + assert ll_mle < ll_opt or np.allclose(ll_mle, ll_opt, + atol=1e-15, rtol=1e-15) + + def test_fit_simple_non_random_data(self): + data = np.array([1.0, 1.0, 3.0, 5.0, 8.0, 14.0]) + # with `floc` fixed to 6, scale should be 4. + loc, scale = stats.laplace.fit(data, floc=6) + assert_allclose(scale, 4, atol=1e-15, rtol=1e-15) + # with `fscale` fixed to 6, loc should be 4. + loc, scale = stats.laplace.fit(data, fscale=6) + assert_allclose(loc, 4, atol=1e-15, rtol=1e-15) + + def test_sf_cdf_extremes(self): + # These calculations should not generate warnings. + x = 1000 + p0 = stats.laplace.cdf(-x) + # The exact value is smaller than can be represented with + # 64 bit floating point, so the expected result is 0. + assert p0 == 0.0 + # The closest 64 bit floating point representation of the + # exact value is 1.0. + p1 = stats.laplace.cdf(x) + assert p1 == 1.0 + + p0 = stats.laplace.sf(x) + # The exact value is smaller than can be represented with + # 64 bit floating point, so the expected result is 0. + assert p0 == 0.0 + # The closest 64 bit floating point representation of the + # exact value is 1.0. + p1 = stats.laplace.sf(-x) + assert p1 == 1.0 + + def test_sf(self): + x = 200 + p = stats.laplace.sf(x) + assert_allclose(p, np.exp(-x)/2, rtol=1e-13) + + def test_isf(self): + p = 1e-25 + x = stats.laplace.isf(p) + assert_allclose(x, -np.log(2*p), rtol=1e-13) + + def test_logcdf_logsf(self): + x = 40 + # Reference value computed with mpmath. + ref = -2.1241771276457944e-18 + logcdf = stats.laplace.logcdf(x) + assert_allclose(logcdf, ref) + logsf = stats.laplace.logsf(-x) + assert_allclose(logsf, ref, rtol=5e-15) + + +class TestLogLaplace: + + def test_sf(self): + # reference values were computed via the reference distribution, e.g. + # mp.dps = 100; LogLaplace(c=c).sf(x). + c = np.array([2.0, 3.0, 5.0]) + x = np.array([1e-5, 1e10, 1e15]) + ref = [0.99999999995, 5e-31, 5e-76] + assert_allclose(stats.loglaplace.sf(x, c), ref, rtol=1e-15) + + def test_isf(self): + # reference values were computed via the reference distribution, e.g. + # mp.dps = 100; LogLaplace(c=c).isf(q). + c = 3.25 + q = [0.8, 0.1, 1e-10, 1e-20, 1e-40] + ref = [0.7543222539245642, 1.6408455124660906, 964.4916294395846, + 1151387.578354072, 1640845512466.0906] + assert_allclose(stats.loglaplace.isf(q, c), ref, rtol=1e-14) + + @pytest.mark.parametrize('r', [1, 2, 3, 4]) + def test_moments_stats(self, r): + mom = 'mvsk'[r - 1] + c = np.arange(0.5, r + 0.5, 0.5) + + # r-th non-central moment is infinite if |r| >= c. + assert_allclose(stats.loglaplace.moment(r, c), np.inf) + + # r-th non-central moment is non-finite (inf or nan) if r >= c. + assert not np.any(np.isfinite(stats.loglaplace.stats(c, moments=mom))) + + @pytest.mark.parametrize("c", [0.5, 1.0, 2.0]) + @pytest.mark.parametrize("loc, scale", [(-1.2, 3.45)]) + @pytest.mark.parametrize("fix_c", [True, False]) + @pytest.mark.parametrize("fix_scale", [True, False]) + def test_fit_analytic_mle(self, c, loc, scale, fix_c, fix_scale): + # Test that the analytical MLE produces no worse result than the + # generic (numerical) MLE. + + rng = np.random.default_rng(6762668991392531563) + data = stats.loglaplace.rvs(c, loc=loc, scale=scale, size=100, + random_state=rng) + + kwds = {'floc': loc} + if fix_c: + kwds['fc'] = c + if fix_scale: + kwds['fscale'] = scale + nfree = 3 - len(kwds) + + if nfree == 0: + error_msg = "All parameters fixed. There is nothing to optimize." + with pytest.raises((RuntimeError, ValueError), match=error_msg): + stats.loglaplace.fit(data, **kwds) + return + + _assert_less_or_close_loglike(stats.loglaplace, data, **kwds) + + +class TestPowerlaw: + + # In the following data, `sf` was computed with mpmath. + @pytest.mark.parametrize('x, a, sf', + [(0.25, 2.0, 0.9375), + (0.99609375, 1/256, 1.528855235208108e-05)]) + def test_sf(self, x, a, sf): + assert_allclose(stats.powerlaw.sf(x, a), sf, rtol=1e-15) + + @pytest.fixture(scope='function') + def rng(self): + return np.random.default_rng(1234) + + @pytest.mark.parametrize("rvs_shape", [.1, .5, .75, 1, 2]) + @pytest.mark.parametrize("rvs_loc", [-1, 0, 1]) + @pytest.mark.parametrize("rvs_scale", [.1, 1, 5]) + @pytest.mark.parametrize('fix_shape, fix_loc, fix_scale', + [p for p in product([True, False], repeat=3) + if False in p]) + def test_fit_MLE_comp_optimizer(self, rvs_shape, rvs_loc, rvs_scale, + fix_shape, fix_loc, fix_scale, rng): + data = stats.powerlaw.rvs(size=250, a=rvs_shape, loc=rvs_loc, + scale=rvs_scale, random_state=rng) + + kwds = dict() + if fix_shape: + kwds['f0'] = rvs_shape + if fix_loc: + kwds['floc'] = np.nextafter(data.min(), -np.inf) + if fix_scale: + kwds['fscale'] = rvs_scale + + # Numerical result may equal analytical result if some code path + # of the analytical routine makes use of numerical optimization. + _assert_less_or_close_loglike(stats.powerlaw, data, **kwds, + maybe_identical=True) + + def test_problem_case(self): + # An observed problem with the test method indicated that some fixed + # scale values could cause bad results, this is now corrected. + a = 2.50002862645130604506 + location = 0.0 + scale = 35.249023299873095 + + data = stats.powerlaw.rvs(a=a, loc=location, scale=scale, size=100, + random_state=np.random.default_rng(5)) + + kwds = {'fscale': np.ptp(data) * 2} + + _assert_less_or_close_loglike(stats.powerlaw, data, **kwds) + + def test_fit_warnings(self): + assert_fit_warnings(stats.powerlaw) + # test for error when `fscale + floc <= np.max(data)` is not satisfied + msg = r" Maximum likelihood estimation with 'powerlaw' requires" + with assert_raises(FitDataError, match=msg): + stats.powerlaw.fit([1, 2, 4], floc=0, fscale=3) + + # test for error when `data - floc >= 0` is not satisfied + msg = r" Maximum likelihood estimation with 'powerlaw' requires" + with assert_raises(FitDataError, match=msg): + stats.powerlaw.fit([1, 2, 4], floc=2) + + # test for fixed location not less than `min(data)`. + msg = r" Maximum likelihood estimation with 'powerlaw' requires" + with assert_raises(FitDataError, match=msg): + stats.powerlaw.fit([1, 2, 4], floc=1) + + # test for when fixed scale is less than or equal to range of data + msg = r"Negative or zero `fscale` is outside" + with assert_raises(ValueError, match=msg): + stats.powerlaw.fit([1, 2, 4], fscale=-3) + + # test for when fixed scale is less than or equal to range of data + msg = r"`fscale` must be greater than the range of data." + with assert_raises(ValueError, match=msg): + stats.powerlaw.fit([1, 2, 4], fscale=3) + + def test_minimum_data_zero_gh17801(self): + # gh-17801 reported an overflow error when the minimum value of the + # data is zero. Check that this problem is resolved. + data = [0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 5, 6] + dist = stats.powerlaw + with np.errstate(over='ignore'): + _assert_less_or_close_loglike(dist, data) + + +class TestPowerLogNorm: + + # reference values were computed via mpmath + # from mpmath import mp + # mp.dps = 80 + # def powerlognorm_sf_mp(x, c, s): + # x = mp.mpf(x) + # c = mp.mpf(c) + # s = mp.mpf(s) + # return mp.ncdf(-mp.log(x) / s)**c + # + # def powerlognormal_cdf_mp(x, c, s): + # return mp.one - powerlognorm_sf_mp(x, c, s) + # + # x, c, s = 100, 20, 1 + # print(float(powerlognorm_sf_mp(x, c, s))) + + @pytest.mark.parametrize("x, c, s, ref", + [(100, 20, 1, 1.9057100820561928e-114), + (1e-3, 20, 1, 0.9999999999507617), + (1e-3, 0.02, 1, 0.9999999999999508), + (1e22, 0.02, 1, 6.50744044621611e-12)]) + def test_sf(self, x, c, s, ref): + assert_allclose(stats.powerlognorm.sf(x, c, s), ref, rtol=1e-13) + + # reference values were computed via mpmath using the survival + # function above (passing in `ref` and getting `q`). + @pytest.mark.parametrize("q, c, s, ref", + [(0.9999999587870905, 0.02, 1, 0.01), + (6.690376686108851e-233, 20, 1, 1000)]) + def test_isf(self, q, c, s, ref): + assert_allclose(stats.powerlognorm.isf(q, c, s), ref, rtol=5e-11) + + @pytest.mark.parametrize("x, c, s, ref", + [(1e25, 0.02, 1, 0.9999999999999963), + (1e-6, 0.02, 1, 2.054921078040843e-45), + (1e-6, 200, 1, 2.0549210780408428e-41), + (0.3, 200, 1, 0.9999999999713368)]) + def test_cdf(self, x, c, s, ref): + assert_allclose(stats.powerlognorm.cdf(x, c, s), ref, rtol=3e-14) + + # reference values were computed via mpmath + # from mpmath import mp + # mp.dps = 50 + # def powerlognorm_pdf_mpmath(x, c, s): + # x = mp.mpf(x) + # c = mp.mpf(c) + # s = mp.mpf(s) + # res = (c/(x * s) * mp.npdf(mp.log(x)/s) * + # mp.ncdf(-mp.log(x)/s)**(c - mp.one)) + # return float(res) + + @pytest.mark.parametrize("x, c, s, ref", + [(1e22, 0.02, 1, 6.5954987852335016e-34), + (1e20, 1e-3, 1, 1.588073750563988e-22), + (1e40, 1e-3, 1, 1.3179391812506349e-43)]) + def test_pdf(self, x, c, s, ref): + assert_allclose(stats.powerlognorm.pdf(x, c, s), ref, rtol=3e-12) + + +class TestPowerNorm: + + # survival function references were computed with mpmath via + # from mpmath import mp + # x = mp.mpf(x) + # c = mp.mpf(x) + # float(mp.ncdf(-x)**c) + + @pytest.mark.parametrize("x, c, ref", + [(9, 1, 1.1285884059538405e-19), + (20, 2, 7.582445786569958e-178), + (100, 0.02, 3.330957891903866e-44), + (200, 0.01, 1.3004759092324774e-87)]) + def test_sf(self, x, c, ref): + assert_allclose(stats.powernorm.sf(x, c), ref, rtol=1e-13) + + # inverse survival function references were computed with mpmath via + # from mpmath import mp + # def isf_mp(q, c): + # q = mp.mpf(q) + # c = mp.mpf(c) + # arg = q**(mp.one / c) + # return float(-mp.sqrt(2) * mp.erfinv(mp.mpf(2.) * arg - mp.one)) + + @pytest.mark.parametrize("q, c, ref", + [(1e-5, 20, -0.15690800666514138), + (0.99999, 100, -5.19933666203545), + (0.9999, 0.02, -2.576676052143387), + (5e-2, 0.02, 17.089518110222244), + (1e-18, 2, 5.9978070150076865), + (1e-50, 5, 6.361340902404057)]) + def test_isf(self, q, c, ref): + assert_allclose(stats.powernorm.isf(q, c), ref, rtol=5e-12) + + # CDF reference values were computed with mpmath via + # from mpmath import mp + # def cdf_mp(x, c): + # x = mp.mpf(x) + # c = mp.mpf(c) + # return float(mp.one - mp.ncdf(-x)**c) + + @pytest.mark.parametrize("x, c, ref", + [(-12, 9, 1.598833900869911e-32), + (2, 9, 0.9999999999999983), + (-20, 9, 2.4782617067456103e-88), + (-5, 0.02, 5.733032242841443e-09), + (-20, 0.02, 5.507248237212467e-91)]) + def test_cdf(self, x, c, ref): + assert_allclose(stats.powernorm.cdf(x, c), ref, rtol=5e-14) + + +class TestInvGamma: + def test_invgamma_inf_gh_1866(self): + # invgamma's moments are only finite for a>n + # specific numbers checked w/ boost 1.54 + with warnings.catch_warnings(): + warnings.simplefilter('error', RuntimeWarning) + mvsk = stats.invgamma.stats(a=19.31, moments='mvsk') + expected = [0.05461496450, 0.0001723162534, 1.020362676, + 2.055616582] + assert_allclose(mvsk, expected) + + a = [1.1, 3.1, 5.6] + mvsk = stats.invgamma.stats(a=a, moments='mvsk') + expected = ([10., 0.476190476, 0.2173913043], # mmm + [np.inf, 0.2061430632, 0.01312749422], # vvv + [np.nan, 41.95235392, 2.919025532], # sss + [np.nan, np.nan, 24.51923076]) # kkk + for x, y in zip(mvsk, expected): + assert_almost_equal(x, y) + + def test_cdf_ppf(self): + # gh-6245 + x = np.logspace(-2.6, 0) + y = stats.invgamma.cdf(x, 1) + xx = stats.invgamma.ppf(y, 1) + assert_allclose(x, xx) + + def test_sf_isf(self): + # gh-6245 + if sys.maxsize > 2**32: + x = np.logspace(2, 100) + else: + # Invgamme roundtrip on 32-bit systems has relative accuracy + # ~1e-15 until x=1e+15, and becomes inf above x=1e+18 + x = np.logspace(2, 18) + + y = stats.invgamma.sf(x, 1) + xx = stats.invgamma.isf(y, 1) + assert_allclose(x, xx, rtol=1.0) + + def test_logcdf(self): + x = 1e7 + a = 2.25 + # Reference value computed with mpmath. + ref = -6.97567687425534e-17 + logcdf = stats.invgamma.logcdf(x, a) + assert_allclose(logcdf, ref, rtol=5e-15) + + def test_logsf(self): + x = 0.01 + a = 3.5 + # Reference value computed with mpmath. + ref = -1.147781224014262e-39 + logsf = stats.invgamma.logsf(x, a) + assert_allclose(logsf, ref, rtol=5e-15) + + @pytest.mark.parametrize("a, ref", + [(100000000.0, -26.21208257605721), + (1e+100, -343.9688254159022)]) + def test_large_entropy(self, a, ref): + # The reference values were calculated with mpmath: + # from mpmath import mp + # mp.dps = 500 + + # def invgamma_entropy(a): + # a = mp.mpf(a) + # h = a + mp.loggamma(a) - (mp.one + a) * mp.digamma(a) + # return float(h) + assert_allclose(stats.invgamma.entropy(a), ref, rtol=1e-15) + + +class TestF: + def test_endpoints(self): + # Compute the pdf at the left endpoint dst.a. + data = [[stats.f, (2, 1), 1.0]] + for _f, _args, _correct in data: + ans = _f.pdf(_f.a, *_args) + + ans = [_f.pdf(_f.a, *_args) for _f, _args, _ in data] + correct = [_correct_ for _f, _args, _correct_ in data] + assert_array_almost_equal(ans, correct) + + def test_f_moments(self): + # n-th moment of F distributions is only finite for n < dfd / 2 + m, v, s, k = stats.f.stats(11, 6.5, moments='mvsk') + assert_(np.isfinite(m)) + assert_(np.isfinite(v)) + assert_(np.isfinite(s)) + assert_(not np.isfinite(k)) + + def test_moments_warnings(self): + # no warnings should be generated for dfd = 2, 4, 6, 8 (div by zero) + with warnings.catch_warnings(): + warnings.simplefilter('error', RuntimeWarning) + stats.f.stats(dfn=[11]*4, dfd=[2, 4, 6, 8], moments='mvsk') + + def test_stats_broadcast(self): + dfn = np.array([[3], [11]]) + dfd = np.array([11, 12]) + m, v, s, k = stats.f.stats(dfn=dfn, dfd=dfd, moments='mvsk') + m2 = [dfd / (dfd - 2)]*2 + assert_allclose(m, m2) + v2 = 2 * dfd**2 * (dfn + dfd - 2) / dfn / (dfd - 2)**2 / (dfd - 4) + assert_allclose(v, v2) + s2 = ((2*dfn + dfd - 2) * np.sqrt(8*(dfd - 4)) / + ((dfd - 6) * np.sqrt(dfn*(dfn + dfd - 2)))) + assert_allclose(s, s2) + k2num = 12 * (dfn * (5*dfd - 22) * (dfn + dfd - 2) + + (dfd - 4) * (dfd - 2)**2) + k2den = dfn * (dfd - 6) * (dfd - 8) * (dfn + dfd - 2) + k2 = k2num / k2den + assert_allclose(k, k2) + + +class TestStudentT: + def test_rvgeneric_std(self): + # Regression test for #1191 + assert_array_almost_equal(stats.t.std([5, 6]), [1.29099445, 1.22474487]) + + def test_moments_t(self): + # regression test for #8786 + assert_equal(stats.t.stats(df=1, moments='mvsk'), + (np.inf, np.nan, np.nan, np.nan)) + assert_equal(stats.t.stats(df=1.01, moments='mvsk'), + (0.0, np.inf, np.nan, np.nan)) + assert_equal(stats.t.stats(df=2, moments='mvsk'), + (0.0, np.inf, np.nan, np.nan)) + assert_equal(stats.t.stats(df=2.01, moments='mvsk'), + (0.0, 2.01/(2.01-2.0), np.nan, np.inf)) + assert_equal(stats.t.stats(df=3, moments='sk'), (np.nan, np.inf)) + assert_equal(stats.t.stats(df=3.01, moments='sk'), (0.0, np.inf)) + assert_equal(stats.t.stats(df=4, moments='sk'), (0.0, np.inf)) + assert_allclose(stats.t.stats(df=4.01, moments='sk'), (0.0, 6.0/(4.01 - 4.0)), + rtol=1e-14) + + def test_t_entropy(self): + df = [1, 2, 25, 100] + # Expected values were computed with mpmath. + expected = [2.5310242469692907, 1.9602792291600821, + 1.459327578078393, 1.4289633653182439] + assert_allclose(stats.t.entropy(df), expected, rtol=1e-13) + + @pytest.mark.parametrize("v, ref", + [(100, 1.4289633653182439), + (1e+100, 1.4189385332046727)]) + def test_t_extreme_entropy(self, v, ref): + # Reference values were calculated with mpmath: + # from mpmath import mp + # mp.dps = 500 + # + # def t_entropy(v): + # v = mp.mpf(v) + # C = (v + mp.one) / 2 + # A = C * (mp.digamma(C) - mp.digamma(v / 2)) + # B = 0.5 * mp.log(v) + mp.log(mp.beta(v / 2, mp.one / 2)) + # h = A + B + # return float(h) + assert_allclose(stats.t.entropy(v), ref, rtol=1e-14) + + @pytest.mark.parametrize("methname", ["pdf", "logpdf", "cdf", + "ppf", "sf", "isf"]) + @pytest.mark.parametrize("df_infmask", [[0, 0], [1, 1], [0, 1], + [[0, 1, 0], [1, 1, 1]], + [[1, 0], [0, 1]], + [[0], [1]]]) + def test_t_inf_df(self, methname, df_infmask): + df_infmask = np.asarray(df_infmask, dtype=bool) + rng = np.random.default_rng(5442451539) + df = rng.uniform(0, 10, size=df_infmask.shape) + x = rng.standard_normal(df_infmask.shape) + df[df_infmask] = np.inf + t_dist = stats.t(df=df, loc=3, scale=1) + t_dist_ref = stats.t(df=df[~df_infmask], loc=3, scale=1) + norm_dist = stats.norm(loc=3, scale=1) + t_meth = getattr(t_dist, methname) + t_meth_ref = getattr(t_dist_ref, methname) + norm_meth = getattr(norm_dist, methname) + res = t_meth(x) + assert_allclose(res[df_infmask], norm_meth(x[df_infmask]), rtol=5e-15) + assert_equal(res[~df_infmask], t_meth_ref(x[~df_infmask])) + + @pytest.mark.parametrize("df_infmask", [[0, 0], [1, 1], [0, 1], + [[0, 1, 0], [1, 1, 1]], + [[1, 0], [0, 1]], + [[0], [1]]]) + def test_t_inf_df_stats_entropy(self, df_infmask): + df_infmask = np.asarray(df_infmask, dtype=bool) + rng = np.random.default_rng(5442451539) + df = rng.uniform(0, 10, size=df_infmask.shape) + df[df_infmask] = np.inf + res = stats.t.stats(df=df, loc=3, scale=1, moments='mvsk') + res_ex_inf = stats.norm.stats(loc=3, scale=1, moments='mvsk') + res_ex_noinf = stats.t.stats(df=df[~df_infmask], loc=3, scale=1, + moments='mvsk') + for i in range(4): + assert_equal(res[i][df_infmask], res_ex_inf[i]) + assert_equal(res[i][~df_infmask], res_ex_noinf[i]) + + res = stats.t.entropy(df=df, loc=3, scale=1) + res_ex_inf = stats.norm.entropy(loc=3, scale=1) + res_ex_noinf = stats.t.entropy(df=df[~df_infmask], loc=3, scale=1) + assert_equal(res[df_infmask], res_ex_inf) + assert_equal(res[~df_infmask], res_ex_noinf) + + def test_logpdf_pdf(self): + # reference values were computed via the reference distribution, e.g. + # mp.dps = 500; StudentT(df=df).logpdf(x), StudentT(df=df).pdf(x) + x = [1, 1e3, 10, 1] + df = [1e100, 1e50, 1e20, 1] + logpdf_ref = [-1.4189385332046727, -500000.9189385332, + -50.918938533204674, -1.8378770664093456] + pdf_ref = [0.24197072451914334, 0, + 7.69459862670642e-23, 0.15915494309189535] + assert_allclose(stats.t.logpdf(x, df), logpdf_ref, rtol=1e-14) + assert_allclose(stats.t.pdf(x, df), pdf_ref, rtol=1e-14) + + # Reference values were computed with mpmath, and double-checked with + # Wolfram Alpha. + @pytest.mark.parametrize('x, df, ref', + [(-75.0, 15, -46.76036184546812), + (0, 15, -0.6931471805599453), + (75.0, 15, -4.9230344937641665e-21)]) + def test_logcdf_logsf(self, x, df, ref): + logcdf = stats.t.logcdf(x, df) + assert_allclose(logcdf, ref, rtol=5e-15) + # The reference value is logcdf(x, df) == logsf(-x, df). + logsf = stats.t.logsf(-x, df) + assert_allclose(logsf, ref, rtol=5e-15) + + +class TestRvDiscrete: + def setup_method(self): + self.rng = np.random.default_rng(333348228) + + def test_rvs(self): + states = [-1, 0, 1, 2, 3, 4] + probability = [0.0, 0.3, 0.4, 0.0, 0.3, 0.0] + samples = 1000 + r = stats.rv_discrete(name='sample', values=(states, probability)) + x = r.rvs(size=samples, random_state=self.rng) + assert isinstance(x, np.ndarray) + + for s, p in zip(states, probability): + assert abs(sum(x == s)/float(samples) - p) < 0.05 + + x = r.rvs(random_state=self.rng) + assert np.issubdtype(type(x), np.integer) + + def test_entropy(self): + # Basic tests of entropy. + pvals = np.array([0.25, 0.45, 0.3]) + p = stats.rv_discrete(values=([0, 1, 2], pvals)) + expected_h = -sum(xlogy(pvals, pvals)) + h = p.entropy() + assert_allclose(h, expected_h) + + p = stats.rv_discrete(values=([0, 1, 2], [1.0, 0, 0])) + h = p.entropy() + assert_equal(h, 0.0) + + def test_pmf(self): + xk = [1, 2, 4] + pk = [0.5, 0.3, 0.2] + rv = stats.rv_discrete(values=(xk, pk)) + + x = [[1., 4.], + [3., 2]] + assert_allclose(rv.pmf(x), + [[0.5, 0.2], + [0., 0.3]], atol=1e-14) + + def test_cdf(self): + xk = [1, 2, 4] + pk = [0.5, 0.3, 0.2] + rv = stats.rv_discrete(values=(xk, pk)) + + x_values = [-2, 1., 1.1, 1.5, 2.0, 3.0, 4, 5] + expected = [0, 0.5, 0.5, 0.5, 0.8, 0.8, 1, 1] + assert_allclose(rv.cdf(x_values), expected, atol=1e-14) + + # also check scalar arguments + assert_allclose([rv.cdf(xx) for xx in x_values], + expected, atol=1e-14) + + def test_ppf(self): + xk = [1, 2, 4] + pk = [0.5, 0.3, 0.2] + rv = stats.rv_discrete(values=(xk, pk)) + + q_values = [0.1, 0.5, 0.6, 0.8, 0.9, 1.] + expected = [1, 1, 2, 2, 4, 4] + assert_allclose(rv.ppf(q_values), expected, atol=1e-14) + + # also check scalar arguments + assert_allclose([rv.ppf(q) for q in q_values], + expected, atol=1e-14) + + def test_cdf_ppf_next(self): + # copied and special cased from test_discrete_basic + vals = ([1, 2, 4, 7, 8], [0.1, 0.2, 0.3, 0.3, 0.1]) + rv = stats.rv_discrete(values=vals) + + assert_array_equal(rv.ppf(rv.cdf(rv.xk[:-1]) + 1e-8), + rv.xk[1:]) + + def test_multidimension(self): + xk = np.arange(12).reshape((3, 4)) + pk = np.array([[0.1, 0.1, 0.15, 0.05], + [0.1, 0.1, 0.05, 0.05], + [0.1, 0.1, 0.05, 0.05]]) + rv = stats.rv_discrete(values=(xk, pk)) + + assert_allclose(rv.expect(), np.sum(rv.xk * rv.pk), atol=1e-14) + + def test_bad_input(self): + xk = [1, 2, 3] + pk = [0.5, 0.5] + assert_raises(ValueError, stats.rv_discrete, **dict(values=(xk, pk))) + + pk = [1, 2, 3] + assert_raises(ValueError, stats.rv_discrete, **dict(values=(xk, pk))) + + xk = [1, 2, 3] + pk = [0.5, 1.2, -0.7] + assert_raises(ValueError, stats.rv_discrete, **dict(values=(xk, pk))) + + xk = [1, 2, 3, 4, 5] + pk = [0.3, 0.3, 0.3, 0.3, -0.2] + assert_raises(ValueError, stats.rv_discrete, **dict(values=(xk, pk))) + + xk = [1, 1] + pk = [0.5, 0.5] + assert_raises(ValueError, stats.rv_discrete, **dict(values=(xk, pk))) + + def test_shape_rv_sample(self): + # tests added for gh-9565 + + # mismatch of 2d inputs + xk, pk = np.arange(4).reshape((2, 2)), np.full((2, 3), 1/6) + assert_raises(ValueError, stats.rv_discrete, **dict(values=(xk, pk))) + + # same number of elements, but shapes not compatible + xk, pk = np.arange(6).reshape((3, 2)), np.full((2, 3), 1/6) + assert_raises(ValueError, stats.rv_discrete, **dict(values=(xk, pk))) + + # same shapes => no error + xk, pk = np.arange(6).reshape((3, 2)), np.full((3, 2), 1/6) + assert_equal(stats.rv_discrete(values=(xk, pk)).pmf(0), 1/6) + + def test_expect1(self): + xk = [1, 2, 4, 6, 7, 11] + pk = [0.1, 0.2, 0.2, 0.2, 0.2, 0.1] + rv = stats.rv_discrete(values=(xk, pk)) + + assert_allclose(rv.expect(), np.sum(rv.xk * rv.pk), atol=1e-14) + + def test_expect2(self): + # rv_sample should override _expect. Bug report from + # https://stackoverflow.com/questions/63199792 + y = [200.0, 300.0, 400.0, 500.0, 600.0, 700.0, 800.0, 900.0, 1000.0, + 1100.0, 1200.0, 1300.0, 1400.0, 1500.0, 1600.0, 1700.0, 1800.0, + 1900.0, 2000.0, 2100.0, 2200.0, 2300.0, 2400.0, 2500.0, 2600.0, + 2700.0, 2800.0, 2900.0, 3000.0, 3100.0, 3200.0, 3300.0, 3400.0, + 3500.0, 3600.0, 3700.0, 3800.0, 3900.0, 4000.0, 4100.0, 4200.0, + 4300.0, 4400.0, 4500.0, 4600.0, 4700.0, 4800.0] + + py = [0.0004, 0.0, 0.0033, 0.006500000000000001, 0.0, 0.0, + 0.004399999999999999, 0.6862, 0.0, 0.0, 0.0, + 0.00019999999999997797, 0.0006000000000000449, + 0.024499999999999966, 0.006400000000000072, + 0.0043999999999999595, 0.019499999999999962, + 0.03770000000000007, 0.01759999999999995, 0.015199999999999991, + 0.018100000000000005, 0.04500000000000004, 0.0025999999999999357, + 0.0, 0.0041000000000001036, 0.005999999999999894, + 0.0042000000000000925, 0.0050000000000000044, + 0.0041999999999999815, 0.0004999999999999449, + 0.009199999999999986, 0.008200000000000096, + 0.0, 0.0, 0.0046999999999999265, 0.0019000000000000128, + 0.0006000000000000449, 0.02510000000000001, 0.0, + 0.007199999999999984, 0.0, 0.012699999999999934, 0.0, 0.0, + 0.008199999999999985, 0.005600000000000049, 0.0] + + rv = stats.rv_discrete(values=(y, py)) + + # check the mean + assert_allclose(rv.expect(), rv.mean(), atol=1e-14) + assert_allclose(rv.expect(), + sum(v * w for v, w in zip(y, py)), atol=1e-14) + + # also check the second moment + assert_allclose(rv.expect(lambda x: x**2), + sum(v**2 * w for v, w in zip(y, py)), atol=1e-14) + + +class TestSkewCauchy: + def test_cauchy(self): + x = np.linspace(-5, 5, 100) + assert_array_almost_equal(stats.skewcauchy.pdf(x, a=0), + stats.cauchy.pdf(x)) + assert_array_almost_equal(stats.skewcauchy.cdf(x, a=0), + stats.cauchy.cdf(x)) + assert_array_almost_equal(stats.skewcauchy.ppf(x, a=0), + stats.cauchy.ppf(x)) + + def test_skewcauchy_R(self): + # options(digits=16) + # library(sgt) + # # lmbda, x contain the values generated for a, x below + # lmbda <- c(0.0976270078546495, 0.430378732744839, 0.2055267521432877, + # 0.0897663659937937, -0.15269040132219, 0.2917882261333122, + # -0.12482557747462, 0.7835460015641595, 0.9273255210020589, + # -0.2331169623484446) + # x <- c(2.917250380826646, 0.2889491975290444, 0.6804456109393229, + # 4.25596638292661, -4.289639418021131, -4.1287070029845925, + # -4.797816025596743, 3.32619845547938, 2.7815675094985046, + # 3.700121482468191) + # pdf = dsgt(x, mu=0, lambda=lambda, sigma=1, q=1/2, mean.cent=FALSE, + # var.adj = sqrt(2)) + # cdf = psgt(x, mu=0, lambda=lambda, sigma=1, q=1/2, mean.cent=FALSE, + # var.adj = sqrt(2)) + # qsgt(cdf, mu=0, lambda=lambda, sigma=1, q=1/2, mean.cent=FALSE, + # var.adj = sqrt(2)) + + rng = np.random.RandomState(0) + a = rng.rand(10) * 2 - 1 + x = rng.rand(10) * 10 - 5 + pdf = [0.039473975217333909, 0.305829714049903223, 0.24140158118994162, + 0.019585772402693054, 0.021436553695989482, 0.00909817103867518, + 0.01658423410016873, 0.071083288030394126, 0.103250045941454524, + 0.013110230778426242] + cdf = [0.87426677718213752, 0.37556468910780882, 0.59442096496538066, + 0.91304659850890202, 0.09631964100300605, 0.03829624330921733, + 0.08245240578402535, 0.72057062945510386, 0.62826415852515449, + 0.95011308463898292] + assert_allclose(stats.skewcauchy.pdf(x, a), pdf) + assert_allclose(stats.skewcauchy.cdf(x, a), cdf) + assert_allclose(stats.skewcauchy.ppf(cdf, a), x) + + +class TestJFSkewT: + def test_compare_t(self): + # Verify that jf_skew_t with a=b recovers the t distribution with 2a + # degrees of freedom + a = b = 5 + df = a * 2 + x = [-1.0, 0.0, 1.0, 2.0] + q = [0.0, 0.1, 0.25, 0.75, 0.90, 1.0] + + jf = stats.jf_skew_t(a, b) + t = stats.t(df) + + assert_allclose(jf.pdf(x), t.pdf(x)) + assert_allclose(jf.cdf(x), t.cdf(x)) + assert_allclose(jf.ppf(q), t.ppf(q)) + assert_allclose(jf.stats('mvsk'), t.stats('mvsk')) + + @pytest.fixture + def gamlss_pdf_data(self): + """Sample data points computed using the `ST5` distribution from the + GAMLSS package in R. The pdf has been calculated for (a,b)=(2,3), + (a,b)=(8,4), and (a,b)=(12,13) for x in `np.linspace(-10, 10, 41)`. + + N.B. the `ST5` distribution in R uses an alternative parameterization + in terms of nu and tau, where: + - nu = (a - b) / (a * b * (a + b)) ** 0.5 + - tau = 2 / (a + b) + """ + data = np.load( + Path(__file__).parent / "data/jf_skew_t_gamlss_pdf_data.npy" + ) + return np.rec.fromarrays(data, names="x,pdf,a,b") + + @pytest.mark.parametrize("a,b", [(2, 3), (8, 4), (12, 13)]) + def test_compare_with_gamlss_r(self, gamlss_pdf_data, a, b): + """Compare the pdf with a table of reference values. The table of + reference values was produced using R, where the Jones and Faddy skew + t distribution is available in the GAMLSS package as `ST5`. + """ + data = gamlss_pdf_data[ + (gamlss_pdf_data["a"] == a) & (gamlss_pdf_data["b"] == b) + ] + x, pdf = data["x"], data["pdf"] + assert_allclose(pdf, stats.jf_skew_t(a, b).pdf(x), rtol=1e-12) + + +# Test data for TestSkewNorm.test_noncentral_moments() +# The expected noncentral moments were computed by Wolfram Alpha. +# In Wolfram Alpha, enter +# SkewNormalDistribution[0, 1, a] moment +# with `a` replaced by the desired shape parameter. In the results, there +# should be a table of the first four moments. Click on "More" to get more +# moments. The expected moments start with the first moment (order = 1). +_skewnorm_noncentral_moments = [ + (2, [2*np.sqrt(2/(5*np.pi)), + 1, + 22/5*np.sqrt(2/(5*np.pi)), + 3, + 446/25*np.sqrt(2/(5*np.pi)), + 15, + 2682/25*np.sqrt(2/(5*np.pi)), + 105, + 107322/125*np.sqrt(2/(5*np.pi))]), + (0.1, [np.sqrt(2/(101*np.pi)), + 1, + 302/101*np.sqrt(2/(101*np.pi)), + 3, + (152008*np.sqrt(2/(101*np.pi)))/10201, + 15, + (107116848*np.sqrt(2/(101*np.pi)))/1030301, + 105, + (97050413184*np.sqrt(2/(101*np.pi)))/104060401]), + (-3, [-3/np.sqrt(5*np.pi), + 1, + -63/(10*np.sqrt(5*np.pi)), + 3, + -2529/(100*np.sqrt(5*np.pi)), + 15, + -30357/(200*np.sqrt(5*np.pi)), + 105, + -2428623/(2000*np.sqrt(5*np.pi)), + 945, + -242862867/(20000*np.sqrt(5*np.pi)), + 10395, + -29143550277/(200000*np.sqrt(5*np.pi)), + 135135]), +] + + +class TestSkewNorm: + def setup_method(self): + self.rng = check_random_state(1234) + + def test_normal(self): + # When the skewness is 0 the distribution is normal + x = np.linspace(-5, 5, 100) + assert_array_almost_equal(stats.skewnorm.pdf(x, a=0), + stats.norm.pdf(x)) + + def test_rvs(self): + rng = check_random_state(1234) + shape = (3, 4, 5) + x = stats.skewnorm.rvs(a=0.75, size=shape, random_state=rng) + assert_equal(shape, x.shape) + + x = stats.skewnorm.rvs(a=-3, size=shape, random_state=rng) + assert_equal(shape, x.shape) + + def test_moments(self): + rng = check_random_state(1234) + X = stats.skewnorm.rvs(a=4, size=int(1e6), loc=5, scale=2, + random_state=rng) + expected = [np.mean(X), np.var(X), stats.skew(X), stats.kurtosis(X)] + computed = stats.skewnorm.stats(a=4, loc=5, scale=2, moments='mvsk') + assert_array_almost_equal(computed, expected, decimal=2) + + X = stats.skewnorm.rvs(a=-4, size=int(1e6), loc=5, scale=2, + random_state=rng) + expected = [np.mean(X), np.var(X), stats.skew(X), stats.kurtosis(X)] + computed = stats.skewnorm.stats(a=-4, loc=5, scale=2, moments='mvsk') + assert_array_almost_equal(computed, expected, decimal=2) + + def test_pdf_large_x(self): + # Triples are [x, a, logpdf(x, a)]. These values were computed + # using Log[PDF[SkewNormalDistribution[0, 1, a], x]] in Wolfram Alpha. + logpdfvals = [ + [40, -1, -1604.834233366398515598970], + [40, -1/2, -1004.142946723741991369168], + [40, 0, -800.9189385332046727417803], + [40, 1/2, -800.2257913526447274323631], + [-40, -1/2, -800.2257913526447274323631], + [-2, 1e7, -2.000000000000199559727173e14], + [2, -1e7, -2.000000000000199559727173e14], + ] + for x, a, logpdfval in logpdfvals: + logp = stats.skewnorm.logpdf(x, a) + assert_allclose(logp, logpdfval, rtol=1e-8) + + def test_cdf_large_x(self): + # Regression test for gh-7746. + # The x values are large enough that the closest 64 bit floating + # point representation of the exact CDF is 1.0. + p = stats.skewnorm.cdf([10, 20, 30], -1) + assert_allclose(p, np.ones(3), rtol=1e-14) + p = stats.skewnorm.cdf(25, 2.5) + assert_allclose(p, 1.0, rtol=1e-14) + + def test_cdf_sf_small_values(self): + # Triples are [x, a, cdf(x, a)]. These values were computed + # using CDF[SkewNormalDistribution[0, 1, a], x] in Wolfram Alpha. + cdfvals = [ + [-8, 1, 3.870035046664392611e-31], + [-4, 2, 8.1298399188811398e-21], + [-2, 5, 1.55326826787106273e-26], + [-9, -1, 2.257176811907681295e-19], + [-10, -4, 1.523970604832105213e-23], + ] + for x, a, cdfval in cdfvals: + p = stats.skewnorm.cdf(x, a) + assert_allclose(p, cdfval, rtol=1e-8) + # For the skew normal distribution, sf(-x, -a) = cdf(x, a). + p = stats.skewnorm.sf(-x, -a) + assert_allclose(p, cdfval, rtol=1e-8) + + @pytest.mark.parametrize('a, moments', _skewnorm_noncentral_moments) + def test_noncentral_moments(self, a, moments): + for order, expected in enumerate(moments, start=1): + mom = stats.skewnorm.moment(order, a) + assert_allclose(mom, expected, rtol=1e-14) + + def test_fit(self): + rng = np.random.default_rng(4609813989115202851) + + a, loc, scale = -2, 3.5, 0.5 # arbitrary, valid parameters + dist = stats.skewnorm(a, loc, scale) + rvs = dist.rvs(size=100, random_state=rng) + + # test that MLE still honors guesses and fixed parameters + a2, loc2, scale2 = stats.skewnorm.fit(rvs, -1.5, floc=3) + a3, loc3, scale3 = stats.skewnorm.fit(rvs, -1.6, floc=3) + assert loc2 == loc3 == 3 # fixed parameter is respected + assert a2 != a3 # different guess -> (slightly) different outcome + # quality of fit is tested elsewhere + + # test that MoM honors fixed parameters, accepts (but ignores) guesses + a4, loc4, scale4 = stats.skewnorm.fit(rvs, 3, fscale=3, method='mm') + assert scale4 == 3 + # because scale was fixed, only the mean and skewness will be matched + dist4 = stats.skewnorm(a4, loc4, scale4) + res = dist4.stats(moments='ms') + ref = np.mean(rvs), stats.skew(rvs) + assert_allclose(res, ref) + + # Test behavior when skew of data is beyond maximum of skewnorm + rvs2 = stats.pareto.rvs(1, size=100, random_state=rng) + + # MLE still works + res = stats.skewnorm.fit(rvs2) + assert np.all(np.isfinite(res)) + + # MoM fits variance and skewness + a5, loc5, scale5 = stats.skewnorm.fit(rvs2, method='mm') + assert np.isinf(a5) + # distribution infrastructure doesn't allow infinite shape parameters + # into _stats; it just bypasses it and produces NaNs. Calculate + # moments manually. + m, v = np.mean(rvs2), np.var(rvs2) + assert_allclose(m, loc5 + scale5 * np.sqrt(2/np.pi)) + assert_allclose(v, scale5**2 * (1 - 2 / np.pi)) + + # test that MLE and MoM behave as expected under sign changes + a6p, loc6p, scale6p = stats.skewnorm.fit(rvs, method='mle') + a6m, loc6m, scale6m = stats.skewnorm.fit(-rvs, method='mle') + assert_allclose([a6m, loc6m, scale6m], [-a6p, -loc6p, scale6p]) + a7p, loc7p, scale7p = stats.skewnorm.fit(rvs, method='mm') + a7m, loc7m, scale7m = stats.skewnorm.fit(-rvs, method='mm') + assert_allclose([a7m, loc7m, scale7m], [-a7p, -loc7p, scale7p]) + + def test_fit_gh19332(self): + # When the skewness of the data was high, `skewnorm.fit` fell back on + # generic `fit` behavior with a bad guess of the skewness parameter. + # Test that this is improved; `skewnorm.fit` is now better at finding + # the global optimum when the sample is highly skewed. See gh-19332. + x = np.array([-5, -1, 1 / 100_000] + 12 * [1] + [5]) + + params = stats.skewnorm.fit(x) + res = stats.skewnorm.nnlf(params, x) + + # Compare overridden fit against generic fit. + # res should be about 32.01, and generic fit is worse at 32.64. + # In case the generic fit improves, remove this assertion (see gh-19333). + params_super = stats.skewnorm.fit(x, superfit=True) + ref = stats.skewnorm.nnlf(params_super, x) + assert res < ref - 0.5 + + # Compare overridden fit against stats.fit + rng = np.random.default_rng(9842356982345693637) + bounds = {'a': (-5, 5), 'loc': (-10, 10), 'scale': (1e-16, 10)} + + def optimizer(fun, bounds): + return differential_evolution(fun, bounds, rng=rng) + + fit_result = stats.fit(stats.skewnorm, x, bounds, optimizer=optimizer) + np.testing.assert_allclose(params, fit_result.params, rtol=1e-4) + + def test_ppf(self): + # gh-20124 reported that Boost's ppf was wrong for high skewness + # Reference value was calculated using + # N[InverseCDF[SkewNormalDistribution[0, 1, 500], 1/100], 14] in Wolfram Alpha. + assert_allclose(stats.skewnorm.ppf(0.01, 500), 0.012533469508013, rtol=1e-13) + + +class TestExpon: + def test_zero(self): + assert_equal(stats.expon.pdf(0), 1) + + def test_tail(self): # Regression test for ticket 807 + assert_equal(stats.expon.cdf(1e-18), 1e-18) + assert_equal(stats.expon.isf(stats.expon.sf(40)), 40) + + def test_nan_raises_error(self): + # see gh-issue 10300 + x = np.array([1.6483, 2.7169, 2.4667, 1.1791, 3.5433, np.nan]) + assert_raises(ValueError, stats.expon.fit, x) + + def test_inf_raises_error(self): + # see gh-issue 10300 + x = np.array([1.6483, 2.7169, 2.4667, 1.1791, 3.5433, np.inf]) + assert_raises(ValueError, stats.expon.fit, x) + + +class TestNorm: + def test_nan_raises_error(self): + # see gh-issue 10300 + x = np.array([1.6483, 2.7169, 2.4667, 1.1791, 3.5433, np.nan]) + assert_raises(ValueError, stats.norm.fit, x) + + def test_inf_raises_error(self): + # see gh-issue 10300 + x = np.array([1.6483, 2.7169, 2.4667, 1.1791, 3.5433, np.inf]) + assert_raises(ValueError, stats.norm.fit, x) + + def test_bad_keyword_arg(self): + x = [1, 2, 3] + assert_raises(TypeError, stats.norm.fit, x, plate="shrimp") + + @pytest.mark.parametrize('loc', [0, 1]) + def test_delta_cdf(self, loc): + # The expected value is computed with mpmath: + # >>> import mpmath + # >>> mpmath.mp.dps = 60 + # >>> float(mpmath.ncdf(12) - mpmath.ncdf(11)) + # 1.910641809677555e-28 + expected = 1.910641809677555e-28 + delta = stats.norm._delta_cdf(11+loc, 12+loc, loc=loc) + assert_allclose(delta, expected, rtol=1e-13) + delta = stats.norm._delta_cdf(-(12+loc), -(11+loc), loc=-loc) + assert_allclose(delta, expected, rtol=1e-13) + + +class TestUniform: + """gh-10300""" + def test_nan_raises_error(self): + # see gh-issue 10300 + x = np.array([1.6483, 2.7169, 2.4667, 1.1791, 3.5433, np.nan]) + assert_raises(ValueError, stats.uniform.fit, x) + + def test_inf_raises_error(self): + # see gh-issue 10300 + x = np.array([1.6483, 2.7169, 2.4667, 1.1791, 3.5433, np.inf]) + assert_raises(ValueError, stats.uniform.fit, x) + + +class TestExponNorm: + def test_moments(self): + # Some moment test cases based on non-loc/scaled formula + def get_moms(lam, sig, mu): + # See wikipedia for these formulae + # where it is listed as an exponentially modified gaussian + opK2 = 1.0 + 1 / (lam*sig)**2 + exp_skew = 2 / (lam * sig)**3 * opK2**(-1.5) + exp_kurt = 6.0 * (1 + (lam * sig)**2)**(-2) + return [mu + 1/lam, sig*sig + 1.0/(lam*lam), exp_skew, exp_kurt] + + mu, sig, lam = 0, 1, 1 + K = 1.0 / (lam * sig) + sts = stats.exponnorm.stats(K, loc=mu, scale=sig, moments='mvsk') + assert_almost_equal(sts, get_moms(lam, sig, mu)) + mu, sig, lam = -3, 2, 0.1 + K = 1.0 / (lam * sig) + sts = stats.exponnorm.stats(K, loc=mu, scale=sig, moments='mvsk') + assert_almost_equal(sts, get_moms(lam, sig, mu)) + mu, sig, lam = 0, 3, 1 + K = 1.0 / (lam * sig) + sts = stats.exponnorm.stats(K, loc=mu, scale=sig, moments='mvsk') + assert_almost_equal(sts, get_moms(lam, sig, mu)) + mu, sig, lam = -5, 11, 3.5 + K = 1.0 / (lam * sig) + sts = stats.exponnorm.stats(K, loc=mu, scale=sig, moments='mvsk') + assert_almost_equal(sts, get_moms(lam, sig, mu)) + + def test_nan_raises_error(self): + # see gh-issue 10300 + x = np.array([1.6483, 2.7169, 2.4667, 1.1791, 3.5433, np.nan]) + assert_raises(ValueError, stats.exponnorm.fit, x, floc=0, fscale=1) + + def test_inf_raises_error(self): + # see gh-issue 10300 + x = np.array([1.6483, 2.7169, 2.4667, 1.1791, 3.5433, np.inf]) + assert_raises(ValueError, stats.exponnorm.fit, x, floc=0, fscale=1) + + def test_extremes_x(self): + # Test for extreme values against overflows + assert_almost_equal(stats.exponnorm.pdf(-900, 1), 0.0) + assert_almost_equal(stats.exponnorm.pdf(+900, 1), 0.0) + assert_almost_equal(stats.exponnorm.pdf(-900, 0.01), 0.0) + assert_almost_equal(stats.exponnorm.pdf(+900, 0.01), 0.0) + + # Expected values for the PDF were computed with mpmath, with + # the following function, and with mpmath.mp.dps = 50. + # + # def exponnorm_stdpdf(x, K): + # x = mpmath.mpf(x) + # K = mpmath.mpf(K) + # t1 = mpmath.exp(1/(2*K**2) - x/K) + # erfcarg = -(x - 1/K)/mpmath.sqrt(2) + # t2 = mpmath.erfc(erfcarg) + # return t1 * t2 / (2*K) + # + @pytest.mark.parametrize('x, K, expected', + [(20, 0.01, 6.90010764753618e-88), + (1, 0.01, 0.24438994313247364), + (-1, 0.01, 0.23955149623472075), + (-20, 0.01, 4.6004708690125477e-88), + (10, 1, 7.48518298877006e-05), + (10, 10000, 9.990005048283775e-05)]) + def test_std_pdf(self, x, K, expected): + assert_allclose(stats.exponnorm.pdf(x, K), expected, rtol=5e-12) + + # Expected values for the CDF were computed with mpmath using + # the following function and with mpmath.mp.dps = 60: + # + # def mp_exponnorm_cdf(x, K, loc=0, scale=1): + # x = mpmath.mpf(x) + # K = mpmath.mpf(K) + # loc = mpmath.mpf(loc) + # scale = mpmath.mpf(scale) + # z = (x - loc)/scale + # return (mpmath.ncdf(z) + # - mpmath.exp((1/(2*K) - z)/K)*mpmath.ncdf(z - 1/K)) + # + @pytest.mark.parametrize('x, K, scale, expected', + [[0, 0.01, 1, 0.4960109760186432], + [-5, 0.005, 1, 2.7939945412195734e-07], + [-1e4, 0.01, 100, 0.0], + [-1e4, 0.01, 1000, 6.920401854427357e-24], + [5, 0.001, 1, 0.9999997118542392]]) + def test_cdf_small_K(self, x, K, scale, expected): + p = stats.exponnorm.cdf(x, K, scale=scale) + if expected == 0.0: + assert p == 0.0 + else: + assert_allclose(p, expected, rtol=1e-13) + + # Expected values for the SF were computed with mpmath using + # the following function and with mpmath.mp.dps = 60: + # + # def mp_exponnorm_sf(x, K, loc=0, scale=1): + # x = mpmath.mpf(x) + # K = mpmath.mpf(K) + # loc = mpmath.mpf(loc) + # scale = mpmath.mpf(scale) + # z = (x - loc)/scale + # return (mpmath.ncdf(-z) + # + mpmath.exp((1/(2*K) - z)/K)*mpmath.ncdf(z - 1/K)) + # + @pytest.mark.parametrize('x, K, scale, expected', + [[10, 0.01, 1, 8.474702916146657e-24], + [2, 0.005, 1, 0.02302280664231312], + [5, 0.005, 0.5, 8.024820681931086e-24], + [10, 0.005, 0.5, 3.0603340062892486e-89], + [20, 0.005, 0.5, 0.0], + [-3, 0.001, 1, 0.9986545205566117]]) + def test_sf_small_K(self, x, K, scale, expected): + p = stats.exponnorm.sf(x, K, scale=scale) + if expected == 0.0: + assert p == 0.0 + else: + assert_allclose(p, expected, rtol=5e-13) + + @pytest.mark.parametrize('K', [1e-9, 1e-12, 1e-15]) + def test_extremely_small_K(self, K): + # see gh-issue 24551 + # test whether distribution approaches standard normal for K -> 0 + x = np.array([-10.5, -3.0, -1.0, 1.0, 3.0, 10.5]) + dist = stats.exponnorm(K) + dist_norm = stats.norm() + assert_allclose(dist.logpdf(x), dist_norm.logpdf(x)) + assert_allclose(dist.cdf(x), dist_norm.cdf(x)) + assert_allclose(dist.sf(x), dist_norm.sf(x)) + +class TestGenExpon: + def test_pdf_unity_area(self): + from scipy.integrate import simpson + # PDF should integrate to one + p = stats.genexpon.pdf(np.arange(0, 10, 0.01), 0.5, 0.5, 2.0) + assert_almost_equal(simpson(p, dx=0.01), 1, 1) + + def test_cdf_bounds(self): + # CDF should always be positive + cdf = stats.genexpon.cdf(np.arange(0, 10, 0.01), 0.5, 0.5, 2.0) + assert np.all((0 <= cdf) & (cdf <= 1)) + + # The values of p in the following data were computed with mpmath. + # E.g. the script + # from mpmath import mp + # mp.dps = 80 + # x = mp.mpf('15.0') + # a = mp.mpf('1.0') + # b = mp.mpf('2.0') + # c = mp.mpf('1.5') + # print(float(mp.exp((-a-b)*x + (b/c)*-mp.expm1(-c*x)))) + # prints + # 1.0859444834514553e-19 + @pytest.mark.parametrize('x, p, a, b, c', + [(15, 1.0859444834514553e-19, 1, 2, 1.5), + (0.25, 0.7609068232534623, 0.5, 2, 3), + (0.25, 0.09026661397565876, 9.5, 2, 0.5), + (0.01, 0.9753038265071597, 2.5, 0.25, 0.5), + (3.25, 0.0001962824553094492, 2.5, 0.25, 0.5), + (0.125, 0.9508674287164001, 0.25, 5, 0.5)]) + def test_sf_isf(self, x, p, a, b, c): + sf = stats.genexpon.sf(x, a, b, c) + assert_allclose(sf, p, rtol=2e-14) + isf = stats.genexpon.isf(p, a, b, c) + assert_allclose(isf, x, rtol=2e-14) + + # The values of p in the following data were computed with mpmath. + @pytest.mark.parametrize('x, p, a, b, c', + [(0.25, 0.2390931767465377, 0.5, 2, 3), + (0.25, 0.9097333860243412, 9.5, 2, 0.5), + (0.01, 0.0246961734928403, 2.5, 0.25, 0.5), + (3.25, 0.9998037175446906, 2.5, 0.25, 0.5), + (0.125, 0.04913257128359998, 0.25, 5, 0.5)]) + def test_cdf_ppf(self, x, p, a, b, c): + cdf = stats.genexpon.cdf(x, a, b, c) + assert_allclose(cdf, p, rtol=2e-14) + ppf = stats.genexpon.ppf(p, a, b, c) + assert_allclose(ppf, x, rtol=2e-14) + + +class TestTruncexpon: + + def test_sf_isf(self): + # reference values were computed via the reference distribution, e.g. + # mp.dps = 50; TruncExpon(b=b).sf(x) + b = [20, 100] + x = [19.999999, 99.999999] + ref = [2.0611546593828472e-15, 3.7200778266671455e-50] + assert_allclose(stats.truncexpon.sf(x, b), ref, rtol=1.5e-10) + assert_allclose(stats.truncexpon.isf(ref, b), x, rtol=1e-12) + + +class TestExponpow: + def test_tail(self): + assert_almost_equal(stats.exponpow.cdf(1e-10, 2.), 1e-20) + assert_almost_equal(stats.exponpow.isf(stats.exponpow.sf(5, .8), .8), + 5) + + +class TestSkellam: + def test_pmf(self): + # comparison to R + k = np.arange(-10, 15) + mu1, mu2 = 10, 5 + skpmfR = np.array( + [4.2254582961926893e-005, 1.1404838449648488e-004, + 2.8979625801752660e-004, 6.9177078182101231e-004, + 1.5480716105844708e-003, 3.2412274963433889e-003, + 6.3373707175123292e-003, 1.1552351566696643e-002, + 1.9606152375042644e-002, 3.0947164083410337e-002, + 4.5401737566767360e-002, 6.1894328166820688e-002, + 7.8424609500170578e-002, 9.2418812533573133e-002, + 1.0139793148019728e-001, 1.0371927988298846e-001, + 9.9076583077406091e-002, 8.8546660073089561e-002, + 7.4187842052486810e-002, 5.8392772862200251e-002, + 4.3268692953013159e-002, 3.0248159818374226e-002, + 1.9991434305603021e-002, 1.2516877303301180e-002, + 7.4389876226229707e-003]) + + assert_almost_equal(stats.skellam.pmf(k, mu1, mu2), skpmfR, decimal=15) + + def test_cdf(self): + # comparison to R, only 5 decimals + k = np.arange(-10, 15) + mu1, mu2 = 10, 5 + skcdfR = np.array( + [6.4061475386192104e-005, 1.7810985988267694e-004, + 4.6790611790020336e-004, 1.1596768997212152e-003, + 2.7077485103056847e-003, 5.9489760066490718e-003, + 1.2286346724161398e-002, 2.3838698290858034e-002, + 4.3444850665900668e-002, 7.4392014749310995e-002, + 1.1979375231607835e-001, 1.8168808048289900e-001, + 2.6011268998306952e-001, 3.5253150251664261e-001, + 4.5392943399683988e-001, 5.5764871387982828e-001, + 6.5672529695723436e-001, 7.4527195703032389e-001, + 8.1945979908281064e-001, 8.7785257194501087e-001, + 9.2112126489802404e-001, 9.5136942471639818e-001, + 9.7136085902200120e-001, 9.8387773632530240e-001, + 9.9131672394792536e-001]) + + assert_almost_equal(stats.skellam.cdf(k, mu1, mu2), skcdfR, decimal=5) + + def test_extreme_mu2(self): + # check that crash reported by gh-17916 large mu2 is resolved + x, mu1, mu2 = 0, 1, 4820232647677555.0 + assert_allclose(stats.skellam.pmf(x, mu1, mu2), 0, atol=1e-16) + assert_allclose(stats.skellam.cdf(x, mu1, mu2), 1, atol=1e-16) + + +class TestLognorm: + def test_pdf(self): + # Regression test for Ticket #1471: avoid nan with 0/0 situation + # Also make sure there are no warnings at x=0, cf gh-5202 + with warnings.catch_warnings(): + warnings.simplefilter('error', RuntimeWarning) + pdf = stats.lognorm.pdf([0, 0.5, 1], 1) + assert_array_almost_equal(pdf, [0.0, 0.62749608, 0.39894228]) + + def test_logcdf(self): + # Regression test for gh-5940: sf et al would underflow too early + x2, mu, sigma = 201.68, 195, 0.149 + assert_allclose(stats.lognorm.sf(x2-mu, s=sigma), + stats.norm.sf(np.log(x2-mu)/sigma)) + assert_allclose(stats.lognorm.logsf(x2-mu, s=sigma), + stats.norm.logsf(np.log(x2-mu)/sigma)) + + @pytest.fixture(scope='function') + def rng(self): + return np.random.default_rng(1234) + + @pytest.mark.parametrize("rvs_shape", [.1, 2]) + @pytest.mark.parametrize("rvs_loc", [-2, 0, 2]) + @pytest.mark.parametrize("rvs_scale", [.2, 1, 5]) + @pytest.mark.parametrize('fix_shape, fix_loc, fix_scale', + [e for e in product((False, True), repeat=3) + if False in e]) + @np.errstate(invalid="ignore") + def test_fit_MLE_comp_optimizer(self, rvs_shape, rvs_loc, rvs_scale, + fix_shape, fix_loc, fix_scale, rng): + data = stats.lognorm.rvs(size=100, s=rvs_shape, scale=rvs_scale, + loc=rvs_loc, random_state=rng) + + kwds = {} + if fix_shape: + kwds['f0'] = rvs_shape + if fix_loc: + kwds['floc'] = rvs_loc + if fix_scale: + kwds['fscale'] = rvs_scale + + # Numerical result may equal analytical result if some code path + # of the analytical routine makes use of numerical optimization. + _assert_less_or_close_loglike(stats.lognorm, data, **kwds, + maybe_identical=True) + + def test_isf(self): + # reference values were computed via the reference distribution, e.g. + # mp.dps = 100; + # LogNormal(s=s).isf(q=0.1, guess=0) + # LogNormal(s=s).isf(q=2e-10, guess=100) + s = 0.954 + q = [0.1, 2e-10, 5e-20, 6e-40] + ref = [3.3960065375794937, 390.07632793595974, 5830.5020828128445, + 287872.84087457904] + assert_allclose(stats.lognorm.isf(q, s), ref, rtol=1e-14) + + +class TestBeta: + def test_logpdf(self): + # Regression test for Ticket #1326: avoid nan with 0*log(0) situation + logpdf = stats.beta.logpdf(0, 1, 0.5) + assert_almost_equal(logpdf, -0.69314718056) + logpdf = stats.beta.logpdf(0, 0.5, 1) + assert_almost_equal(logpdf, np.inf) + + def test_logpdf_ticket_1866(self): + alpha, beta = 267, 1472 + x = np.array([0.2, 0.5, 0.6]) + b = stats.beta(alpha, beta) + assert_allclose(b.logpdf(x).sum(), -1201.699061824062) + assert_allclose(b.pdf(x), np.exp(b.logpdf(x))) + + def test_fit_bad_keyword_args(self): + x = [0.1, 0.5, 0.6] + assert_raises(TypeError, stats.beta.fit, x, floc=0, fscale=1, + plate="shrimp") + + def test_fit_duplicated_fixed_parameter(self): + # At most one of 'f0', 'fa' or 'fix_a' can be given to the fit method. + # More than one raises a ValueError. + x = [0.1, 0.5, 0.6] + assert_raises(ValueError, stats.beta.fit, x, fa=0.5, fix_a=0.5) + + @pytest.mark.skipif(MACOS_INTEL, reason="Overflow, see gh-14901") + def test_issue_12635(self): + # Confirm that Boost's beta distribution resolves gh-12635. + # Check against R: + # options(digits=16) + # p = 0.9999999999997369 + # a = 75.0 + # b = 66334470.0 + # print(qbeta(p, a, b)) + p, a, b = 0.9999999999997369, 75.0, 66334470.0 + assert_allclose(stats.beta.ppf(p, a, b), 2.343620802982393e-06) + + @pytest.mark.skipif(MACOS_INTEL, reason="Overflow, see gh-14901") + def test_issue_12794(self): + # Confirm that Boost's beta distribution resolves gh-12794. + # Check against R. + # options(digits=16) + # p = 1e-11 + # count_list = c(10,100,1000) + # print(qbeta(1-p, count_list + 1, 100000 - count_list)) + inv_R = np.array([0.0004944464889611935, + 0.0018360586912635726, + 0.0122663919942518351]) + count_list = np.array([10, 100, 1000]) + p = 1e-11 + inv = stats.beta.isf(p, count_list + 1, 100000 - count_list) + assert_allclose(inv, inv_R) + res = stats.beta.sf(inv, count_list + 1, 100000 - count_list) + assert_allclose(res, p) + + @pytest.mark.skipif(MACOS_INTEL, reason="Overflow, see gh-14901") + def test_issue_12796(self): + # Confirm that Boost's beta distribution succeeds in the case + # of gh-12796 + alpha_2 = 5e-6 + count_ = np.arange(1, 20) + nobs = 100000 + q, a, b = 1 - alpha_2, count_ + 1, nobs - count_ + inv = stats.beta.ppf(q, a, b) + res = stats.beta.cdf(inv, a, b) + assert_allclose(res, 1 - alpha_2) + + def test_endpoints(self): + # Confirm that boost's beta distribution returns inf at x=1 + # when b<1 + a, b = 1, 0.5 + assert_equal(stats.beta.pdf(1, a, b), np.inf) + + # Confirm that boost's beta distribution returns inf at x=0 + # when a<1 + a, b = 0.2, 3 + assert_equal(stats.beta.pdf(0, a, b), np.inf) + + # Confirm that boost's beta distribution returns 5 at x=0 + # when a=1, b=5 + a, b = 1, 5 + assert_equal(stats.beta.pdf(0, a, b), 5) + assert_equal(stats.beta.pdf(1e-310, a, b), 5) + + # Confirm that boost's beta distribution returns 5 at x=1 + # when a=5, b=1 + a, b = 5, 1 + assert_equal(stats.beta.pdf(1, a, b), 5) + assert_equal(stats.beta.pdf(1-1e-310, a, b), 5) + + def test_boost_eval_issue_14606(self): + q, a, b = 0.995, 1.0e11, 1.0e13 + stats.beta.ppf(q, a, b) + + @pytest.mark.parametrize('method', [stats.beta.ppf, stats.beta.isf]) + @pytest.mark.parametrize('a, b', [(1e-310, 12.5), (12.5, 1e-310)]) + def test_beta_ppf_with_subnormal_a_b(self, method, a, b): + # Regression test for gh-17444: beta.ppf(p, a, b) and beta.isf(p, a, b) + # would result in a segmentation fault if either a or b was subnormal. + p = 0.9 + # Depending on the version of Boost that we have vendored and + # our setting of the Boost double promotion policy, the call + # `stats.beta.ppf(p, a, b)` might raise an OverflowError or + # return a value. We'll accept either behavior (and not care about + # the value), because our goal here is to verify that the call does + # not trigger a segmentation fault. + try: + method(p, a, b) + except OverflowError: + # The OverflowError exception occurs with Boost 1.80 or earlier + # when Boost's double promotion policy is false; see + # https://github.com/boostorg/math/issues/882 + # and + # https://github.com/boostorg/math/pull/883 + # Once we have vendored the fixed version of Boost, we can drop + # this try-except wrapper and just call the function. + pass + + # Reference values computed with mpmath. + @pytest.mark.parametrize('x, a, b, ref', + [(0.999, 1.5, 2.5, -6.439838145196121e-08), + (2e-9, 3.25, 2.5, -63.13030939685114)]) + def test_logcdf(self, x, a, b, ref): + logcdf = stats.beta.logcdf(x, a, b) + assert_allclose(logcdf, ref, rtol=5e-15) + + # Reference values computed with mpmath. + @pytest.mark.parametrize('x, a, b, ref', + [(2e-9, 1.5, 2.5, -3.0368535131140806e-13), + (0.998, 3.25, 2.5, -13.309796070871489)]) + def test_logsf(self, x, a, b, ref): + logsf = stats.beta.logsf(x, a, b) + assert_allclose(logsf, ref, 5e-15) + + # entropy accuracy was confirmed using the following mpmath function + # from mpmath import mp + # mp.dps = 50 + # def beta_entropy_mpmath(a, b): + # a = mp.mpf(a) + # b = mp.mpf(b) + # entropy = mp.log(mp.beta(a, b)) - (a - 1) * mp.digamma(a) -\ + # (b - 1) * mp.digamma(b) + (a + b -2) * mp.digamma(a + b) + # return float(entropy) + + @pytest.mark.parametrize('a, b, ref', + [(0.5, 0.5, -0.24156447527049044), + (0.001, 1, -992.0922447210179), + (1, 10000, -8.210440371976183), + (100000, 100000, -5.377247470132859)]) + def test_entropy(self, a, b, ref): + assert_allclose(stats.beta(a, b).entropy(), ref) + + @pytest.mark.parametrize( + "a, b, ref, tol", + [ + (1, 10, -1.4025850929940458, 1e-14), + (10, 20, -1.0567887388936708, 1e-13), + (4e6, 4e6+20, -7.221686009678741, 1e-9), + (5e6, 5e6+10, -7.333257022834638, 1e-8), + (1e10, 1e10+20, -11.133707703130474, 1e-11), + (1e50, 1e50+20, -57.185409562486385, 1e-15), + (2, 1e10, -21.448635265288925, 1e-11), + (2, 1e20, -44.47448619497938, 1e-14), + (2, 1e50, -113.55203898480075, 1e-14), + (5, 1e10, -20.87226777401971, 1e-10), + (5, 1e20, -43.89811870326017, 1e-14), + (5, 1e50, -112.97567149308153, 1e-14), + (10, 1e10, -20.489796752909477, 1e-9), + (10, 1e20, -43.51564768139993, 1e-14), + (10, 1e50, -112.59320047122131, 1e-14), + (1e20, 2, -44.47448619497938, 1e-14), + (1e20, 5, -43.89811870326017, 1e-14), + (1e50, 10, -112.59320047122131, 1e-14), + ] + ) + def test_extreme_entropy(self, a, b, ref, tol): + # Reference values were calculated with mpmath: + # from mpmath import mp + # mp.dps = 500 + # + # def beta_entropy_mpmath(a, b): + # a = mp.mpf(a) + # b = mp.mpf(b) + # entropy = ( + # mp.log(mp.beta(a, b)) - (a - 1) * mp.digamma(a) + # - (b - 1) * mp.digamma(b) + (a + b - 2) * mp.digamma(a + b) + # ) + # return float(entropy) + assert_allclose(stats.beta(a, b).entropy(), ref, rtol=tol) + + def test_entropy_broadcasting(self): + # gh-23127 reported that the entropy method of the beta + # distribution did not broadcast correctly. + Beta = stats.make_distribution(stats.beta) + a = np.asarray([5e6, 100, 1e9, 10]) + b = np.asarray([5e6, 1e9, 100, 20]) + res = Beta(a=a, b=b).entropy() + ref = np.asarray([Beta(a=a[0], b=b[0]).entropy(), + Beta(a=a[1], b=b[1]).entropy(), + Beta(a=a[2], b=b[2]).entropy(), + Beta(a=a[3], b=b[3]).entropy()]) + assert_allclose(res, ref) + + +class TestBetaPrime: + # the test values are used in test_cdf_gh_17631 / test_ppf_gh_17631 + # They are computed with mpmath. Example: + # from mpmath import mp + # mp.dps = 50 + # a, b = mp.mpf(0.05), mp.mpf(0.1) + # x = mp.mpf(1e22) + # float(mp.betainc(a, b, 0.0, x/(1+x), regularized=True)) + # note: we use the values computed by the cdf to test whether + # ppf(cdf(x)) == x (up to a small tolerance) + # since the ppf can be very sensitive to small variations of the input, + # it can be required to generate the test case for the ppf separately, + # see self.test_ppf + cdf_vals = [ + (1e22, 100.0, 0.05, 0.8973027435427167), + (1e10, 100.0, 0.05, 0.5911548582766262), + (1e8, 0.05, 0.1, 0.9467768090820048), + (1e8, 100.0, 0.05, 0.4852944858726726), + (1e-10, 0.05, 0.1, 0.21238845427095), + (1e-10, 1.5, 1.5, 1.697652726007973e-15), + (1e-10, 0.05, 100.0, 0.40884514172337383), + (1e-22, 0.05, 0.1, 0.053349567649287326), + (1e-22, 1.5, 1.5, 1.6976527263135503e-33), + (1e-22, 0.05, 100.0, 0.10269725645728331), + (1e-100, 0.05, 0.1, 6.7163126421919795e-06), + (1e-100, 1.5, 1.5, 1.6976527263135503e-150), + (1e-100, 0.05, 100.0, 1.2928818587561651e-05), + ] + + def test_logpdf(self): + alpha, beta = 267, 1472 + x = np.array([0.2, 0.5, 0.6]) + b = stats.betaprime(alpha, beta) + assert_(np.isfinite(b.logpdf(x)).all()) + assert_allclose(b.pdf(x), np.exp(b.logpdf(x))) + + def test_cdf(self): + # regression test for gh-4030: Implementation of + # scipy.stats.betaprime.cdf() + x = stats.betaprime.cdf(0, 0.2, 0.3) + assert_equal(x, 0.0) + + alpha, beta = 267, 1472 + x = np.array([0.2, 0.5, 0.6]) + cdfs = stats.betaprime.cdf(x, alpha, beta) + assert_(np.isfinite(cdfs).all()) + + # check the new cdf implementation vs generic one: + gen_cdf = stats.rv_continuous._cdf_single + cdfs_g = [gen_cdf(stats.betaprime, val, alpha, beta) for val in x] + assert_allclose(cdfs, cdfs_g, atol=0, rtol=2e-12) + + # The expected values for test_ppf() were computed with mpmath, e.g. + # + # from mpmath import mp + # mp.dps = 125 + # p = 0.01 + # a, b = 1.25, 2.5 + # x = mp.findroot(lambda t: mp.betainc(a, b, x1=0, x2=t/(1+t), + # regularized=True) - p, + # x0=(0.01, 0.011), method='secant') + # print(float(x)) + # + # prints + # + # 0.01080162700956614 + # + @pytest.mark.parametrize( + 'p, a, b, expected', + [(0.010, 1.25, 2.5, 0.01080162700956614), + (1e-12, 1.25, 2.5, 1.0610141996279122e-10), + (1e-18, 1.25, 2.5, 1.6815941817974941e-15), + (1e-17, 0.25, 7.0, 1.0179194531881782e-69), + (0.375, 0.25, 7.0, 0.002036820346115211), + (0.9978811466052919, 0.05, 0.1, 1.0000000000001218e22),] + ) + def test_ppf(self, p, a, b, expected): + x = stats.betaprime.ppf(p, a, b) + assert_allclose(x, expected, rtol=1e-14) + + @pytest.mark.parametrize('x, a, b, p', cdf_vals) + def test_ppf_gh_17631(self, x, a, b, p): + assert_allclose(stats.betaprime.ppf(p, a, b), x, rtol=2e-14) + + def test__ppf(self): + # Verify that _ppf supports scalar arrays. + a = np.array(1.0) + b = np.array(1.0) + p = np.array(0.5) + assert_allclose(stats.betaprime._ppf(p, a, b), 1.0, rtol=5e-16) + + @pytest.mark.parametrize( + 'x, a, b, expected', + cdf_vals + [ + (1e10, 1.5, 1.5, 0.9999999999999983), + (1e10, 0.05, 0.1, 0.9664184367890859), + (1e22, 0.05, 0.1, 0.9978811466052919), + ]) + def test_cdf_gh_17631(self, x, a, b, expected): + assert_allclose(stats.betaprime.cdf(x, a, b), expected, rtol=1e-14) + + @pytest.mark.parametrize( + 'x, a, b, expected', + [(1e50, 0.05, 0.1, 0.9999966641709545), + (1e50, 100.0, 0.05, 0.995925162631006)]) + def test_cdf_extreme_tails(self, x, a, b, expected): + # for even more extreme values, we only get a few correct digits + # results are still < 1 + y = stats.betaprime.cdf(x, a, b) + assert y < 1.0 + assert_allclose(y, expected, rtol=2e-5) + + def test_sf(self): + # reference values were computed via the reference distribution, + # e.g. + # mp.dps = 50 + # a, b = 5, 3 + # x = 1e10 + # BetaPrime(a=a, b=b).sf(x); returns 3.4999999979e-29 + a = [5, 4, 2, 0.05, 0.05, 0.05, 0.05, 100.0, 100.0, 0.05, 0.05, + 0.05, 1.5, 1.5] + b = [3, 2, 1, 0.1, 0.1, 0.1, 0.1, 0.05, 0.05, 100.0, 100.0, + 100.0, 1.5, 1.5] + x = [1e10, 1e20, 1e30, 1e22, 1e-10, 1e-22, 1e-100, 1e22, 1e10, + 1e-10, 1e-22, 1e-100, 1e10, 1e-10] + ref = [3.4999999979e-29, 9.999999999994357e-40, 1.9999999999999998e-30, + 0.0021188533947081017, 0.78761154572905, 0.9466504323507127, + 0.9999932836873578, 0.10269725645728331, 0.40884514172337383, + 0.5911548582766262, 0.8973027435427167, 0.9999870711814124, + 1.6976527260079727e-15, 0.9999999999999983] + sf_values = stats.betaprime.sf(x, a, b) + assert_allclose(sf_values, ref, rtol=1e-12) + + def test_logcdf(self): + x = 800 + a = 0.5 + b = 5.0 + ref = -7.467307556554531e-16 + logcdf = stats.betaprime.logcdf(x, a, b) + assert_allclose(logcdf, ref, rtol=5e-15) + + def test_logsf(self): + x = 1e-8 + a = 4.5 + b = 0.5 + ref = -2.5868992866500915e-37 + logsf = stats.betaprime.logsf(x, a, b) + assert_allclose(logsf, ref, rtol=5e-15) + + + def test_fit_stats_gh18274(self): + # gh-18274 reported spurious warning emitted when fitting `betaprime` + # to data. Some of these were emitted by stats, too. Check that the + # warnings are no longer emitted. + stats.betaprime.fit([0.1, 0.25, 0.3, 1.2, 1.6], floc=0, fscale=1) + stats.betaprime(a=1, b=1).stats('mvsk') + + def test_moment_gh18634(self): + # Testing for gh-18634 revealed that `betaprime` raised a + # NotImplementedError for higher moments. Check that this is + # resolved. Parameters are arbitrary but lie on either side of the + # moment order (5) to test both branches of `xpx.apply_where`. + # Reference values produced with Mathematica, e.g. + # `Moment[BetaPrimeDistribution[2,7],5]` + ref = [np.inf, 0.867096912929055] + res = stats.betaprime(2, [4.2, 7.1]).moment(5) + assert_allclose(res, ref) + + +class TestGamma: + def test_pdf(self): + # a few test cases to compare with R + pdf = stats.gamma.pdf(90, 394, scale=1./5) + assert_almost_equal(pdf, 0.002312341) + + pdf = stats.gamma.pdf(3, 10, scale=1./5) + assert_almost_equal(pdf, 0.1620358) + + def test_logpdf(self): + # Regression test for Ticket #1326: cornercase avoid nan with 0*log(0) + # situation + logpdf = stats.gamma.logpdf(0, 1) + assert_almost_equal(logpdf, 0) + + def test_fit_bad_keyword_args(self): + x = [0.1, 0.5, 0.6] + assert_raises(TypeError, stats.gamma.fit, x, floc=0, plate="shrimp") + + def test_isf(self): + # Test cases for when the probability is very small. See gh-13664. + # The expected values can be checked with mpmath. With mpmath, + # the survival function sf(x, k) can be computed as + # + # mpmath.gammainc(k, x, mpmath.inf, regularized=True) + # + # Here we have: + # + # >>> mpmath.mp.dps = 60 + # >>> float(mpmath.gammainc(1, 39.14394658089878, mpmath.inf, + # ... regularized=True)) + # 9.99999999999999e-18 + # >>> float(mpmath.gammainc(100, 330.6557590436547, mpmath.inf, + # regularized=True)) + # 1.000000000000028e-50 + # + assert np.isclose(stats.gamma.isf(1e-17, 1), + 39.14394658089878, atol=1e-14) + assert np.isclose(stats.gamma.isf(1e-50, 100), + 330.6557590436547, atol=1e-13) + + def test_logcdf(self): + x = 80 + a = 7 + ref = -7.096510270453943e-27 + logcdf = stats.gamma.logcdf(x, a) + assert_allclose(logcdf, ref, rtol=5e-15) + + def test_logsf(self): + x = 0.001 + a = 3.0 + ref = -1.6654171666664883e-10 + logsf = stats.gamma.logsf(x, a) + assert_allclose(logsf, ref, rtol=5e-15) + + @pytest.mark.parametrize('scale', [1.0, 5.0]) + def test_delta_cdf(self, scale): + # Expected value computed with mpmath: + # + # >>> import mpmath + # >>> mpmath.mp.dps = 150 + # >>> cdf1 = mpmath.gammainc(3, 0, 245, regularized=True) + # >>> cdf2 = mpmath.gammainc(3, 0, 250, regularized=True) + # >>> float(cdf2 - cdf1) + # 1.1902609356171962e-102 + # + delta = stats.gamma._delta_cdf(scale*245, scale*250, 3, scale=scale) + assert_allclose(delta, 1.1902609356171962e-102, rtol=1e-13) + + @pytest.mark.parametrize('a, ref, rtol', + [(1e-4, -9990.366610819761, 1e-15), + (2, 1.5772156649015328, 1e-15), + (100, 3.7181819485047463, 1e-13), + (1e4, 6.024075385026086, 1e-15), + (1e18, 22.142204370151084, 1e-15), + (1e100, 116.54819318290696, 1e-15)]) + def test_entropy(self, a, ref, rtol): + # expected value computed with mpmath: + # from mpmath import mp + # mp.dps = 500 + # def gamma_entropy_reference(x): + # x = mp.mpf(x) + # return float(mp.digamma(x) * (mp.one - x) + x + mp.loggamma(x)) + + assert_allclose(stats.gamma.entropy(a), ref, rtol=rtol) + + @pytest.mark.parametrize("a", [1e-2, 1, 1e2]) + @pytest.mark.parametrize("loc", [1e-2, 0, 1e2]) + @pytest.mark.parametrize('scale', [1e-2, 1, 1e2]) + @pytest.mark.parametrize('fix_a', [True, False]) + @pytest.mark.parametrize('fix_loc', [True, False]) + @pytest.mark.parametrize('fix_scale', [True, False]) + def test_fit_mm(self, a, loc, scale, fix_a, fix_loc, fix_scale): + rng = np.random.default_rng(6762668991392531563) + data = stats.gamma.rvs(a, loc=loc, scale=scale, size=100, + random_state=rng) + + kwds = {} + if fix_a: + kwds['fa'] = a + if fix_loc: + kwds['floc'] = loc + if fix_scale: + kwds['fscale'] = scale + nfree = 3 - len(kwds) + + if nfree == 0: + error_msg = "All parameters fixed. There is nothing to optimize." + with pytest.raises(ValueError, match=error_msg): + stats.gamma.fit(data, method='mm', **kwds) + return + + theta = stats.gamma.fit(data, method='mm', **kwds) + dist = stats.gamma(*theta) + if nfree >= 1: + assert_allclose(dist.mean(), np.mean(data)) + if nfree >= 2: + assert_allclose(dist.moment(2), np.mean(data**2)) + if nfree >= 3: + assert_allclose(dist.moment(3), np.mean(data**3)) + + +def test_pdf_overflow_gh19616(): + # Confirm that gh19616 (intermediate over/underflows in PDF) is resolved + # Reference value from R GeneralizedHyperbolic library + # library(GeneralizedHyperbolic) + # options(digits=16) + # jitter = 1e-3 + # dnig(1, a=2**0.5 / jitter**2, b=1 / jitter**2) + jitter = 1e-3 + Z = stats.norminvgauss(2**0.5 / jitter**2, 1 / jitter**2, loc=0, scale=1) + assert_allclose(Z.pdf(1.0), 282.0948446666433) + + +class TestDgamma: + def test_pdf(self): + rng = np.random.default_rng(3791303244302340058) + size = 10 # number of points to check + x = rng.normal(scale=10, size=size) + a = rng.uniform(high=10, size=size) + res = stats.dgamma.pdf(x, a) + ref = stats.gamma.pdf(np.abs(x), a) / 2 + assert_allclose(res, ref) + + dist = stats.dgamma(a) + # There was an intermittent failure with assert_equal on Linux - 32 bit + assert_allclose(dist.pdf(x), res, rtol=5e-16) + + # mpmath was used to compute the expected values. + # For x < 0, cdf(x, a) is mp.gammainc(a, -x, mp.inf, regularized=True)/2 + # For x > 0, cdf(x, a) is (1 + mp.gammainc(a, 0, x, regularized=True))/2 + # E.g. + # from mpmath import mp + # mp.dps = 50 + # print(float(mp.gammainc(1, 20, mp.inf, regularized=True)/2)) + # prints + # 1.030576811219279e-09 + @pytest.mark.parametrize('x, a, expected', + [(-20, 1, 1.030576811219279e-09), + (-40, 1, 2.1241771276457944e-18), + (-50, 5, 2.7248509914602648e-17), + (-25, 0.125, 5.333071920958156e-14), + (5, 1, 0.9966310265004573)]) + def test_cdf_ppf_sf_isf_tail(self, x, a, expected): + cdf = stats.dgamma.cdf(x, a) + assert_allclose(cdf, expected, rtol=5e-15) + ppf = stats.dgamma.ppf(expected, a) + assert_allclose(ppf, x, rtol=5e-15) + sf = stats.dgamma.sf(-x, a) + assert_allclose(sf, expected, rtol=5e-15) + isf = stats.dgamma.isf(expected, a) + assert_allclose(isf, -x, rtol=5e-15) + + @pytest.mark.parametrize("a, ref", + [(1.5, 2.0541199559354117), + (1.3, 1.9357296377121247), + (1.1, 1.7856502333412134)]) + def test_entropy(self, a, ref): + # The reference values were calculated with mpmath: + # def entropy_dgamma(a): + # def pdf(x): + # A = mp.one / (mp.mpf(2.) * mp.gamma(a)) + # B = mp.fabs(x) ** (a - mp.one) + # C = mp.exp(-mp.fabs(x)) + # h = A * B * C + # return h + # + # return -mp.quad(lambda t: pdf(t) * mp.log(pdf(t)), + # [-mp.inf, mp.inf]) + assert_allclose(stats.dgamma.entropy(a), ref, rtol=1e-14) + + @pytest.mark.parametrize("a, ref", + [(1e-100, -1e+100), + (1e-10, -9999999975.858217), + (1e-5, -99987.37111657023), + (1e4, 6.717222565586032), + (1000000000000000.0, 19.38147391121996), + (1e+100, 117.2413403634669)]) + def test_entropy_entreme_values(self, a, ref): + # The reference values were calculated with mpmath: + # from mpmath import mp + # mp.dps = 500 + # def second_dgamma(a): + # a = mp.mpf(a) + # x_1 = a + mp.log(2) + mp.loggamma(a) + # x_2 = (mp.one - a) * mp.digamma(a) + # h = x_1 + x_2 + # return h + assert_allclose(stats.dgamma.entropy(a), ref, rtol=1e-10) + + def test_entropy_array_input(self): + x = np.array([1, 5, 1e20, 1e-5]) + y = stats.dgamma.entropy(x) + for i in range(len(y)): + assert y[i] == stats.dgamma.entropy(x[i]) + + +class TestChi2: + + # regression tests after precision improvements, ticket:1041, not verified + def test_precision(self): + assert_almost_equal(stats.chi2.pdf(1000, 1000), 8.919133934753128e-003, + decimal=14) + assert_almost_equal(stats.chi2.pdf(100, 100), 0.028162503162596778, + decimal=14) + + # Reference values computed with mpmath. + @pytest.mark.parametrize( + 'x, df, ref', + [(750.0, 3, -3.0172957781136564e-162), + (120.0, 15, -1.8924849646375648e-18), + (15.0, 13, -0.36723446372517876)] + ) + def test_logcdf(self, x, df, ref): + logcdf = stats.chi2.logcdf(x, df) + assert_allclose(logcdf, ref, rtol=5e-15) + + # Reference values computed with mpmath. + @pytest.mark.parametrize( + 'x, df, ref', + [(1e-4, 15, -3.936060782678026e-37), + (1.5, 40, -6.384797888313517e-22), + (3.0, 10, -0.018750635779926784)] + ) + def test_logsf(self, x, df, ref): + logsf = stats.chi2.logsf(x, df) + assert_allclose(logsf, ref, rtol=5e-15) + + def test_ppf(self): + # Expected values computed with mpmath. + df = 4.8 + x = stats.chi2.ppf(2e-47, df) + assert_allclose(x, 1.098472479575179840604902808e-19, rtol=1e-10) + x = stats.chi2.ppf(0.5, df) + assert_allclose(x, 4.15231407598589358660093156, rtol=1e-10) + + df = 13 + x = stats.chi2.ppf(2e-77, df) + assert_allclose(x, 1.0106330688195199050507943e-11, rtol=1e-10) + x = stats.chi2.ppf(0.1, df) + assert_allclose(x, 7.041504580095461859307179763, rtol=1e-10) + + # Entropy references values were computed with the following mpmath code + # from mpmath import mp + # mp.dps = 50 + # def chisq_entropy_mpmath(df): + # df = mp.mpf(df) + # half_df = 0.5 * df + # entropy = (half_df + mp.log(2) + mp.log(mp.gamma(half_df)) + + # (mp.one - half_df) * mp.digamma(half_df)) + # return float(entropy) + + @pytest.mark.parametrize('df, ref', + [(1e-4, -19988.980448690163), + (1, 0.7837571104739337), + (100, 4.061397128938114), + (251, 4.525577254045129), + (1e15, 19.034900320939986)]) + def test_entropy(self, df, ref): + assert_allclose(stats.chi2(df).entropy(), ref, rtol=1e-13) + + def test_regression_ticket_1326(self): + # adjust to avoid nan with 0*log(0) + assert_almost_equal(stats.chi2.pdf(0.0, 2), 0.5, 14) + + +class TestGumbelL: + def setup_method(self): + self.rng = np.random.default_rng(3922444007) + + # gh-6228 + def test_cdf_ppf(self): + x = np.linspace(-100, -4) + y = stats.gumbel_l.cdf(x) + xx = stats.gumbel_l.ppf(y) + assert_allclose(x, xx) + + def test_logcdf_logsf(self): + x = np.linspace(-100, -4) + y = stats.gumbel_l.logcdf(x) + z = stats.gumbel_l.logsf(x) + u = np.exp(y) + v = -special.expm1(z) + assert_allclose(u, v) + + def test_sf_isf(self): + x = np.linspace(-20, 5) + y = stats.gumbel_l.sf(x) + xx = stats.gumbel_l.isf(y) + assert_allclose(x, xx) + + @pytest.mark.parametrize('loc', [-1, 1]) + def test_fit_fixed_param(self, loc): + # ensure fixed location is correctly reflected from `gumbel_r.fit` + # See comments at end of gh-12737. + data = stats.gumbel_l.rvs(size=100, loc=loc, random_state=self.rng) + fitted_loc, _ = stats.gumbel_l.fit(data, floc=loc) + assert_equal(fitted_loc, loc) + + +class TestGumbelR: + + def test_sf(self): + # Expected value computed with mpmath: + # >>> import mpmath + # >>> mpmath.mp.dps = 40 + # >>> float(mpmath.mp.one - mpmath.exp(-mpmath.exp(-50))) + # 1.9287498479639178e-22 + assert_allclose(stats.gumbel_r.sf(50), 1.9287498479639178e-22, + rtol=1e-14) + + def test_isf(self): + # Expected value computed with mpmath: + # >>> import mpmath + # >>> mpmath.mp.dps = 40 + # >>> float(-mpmath.log(-mpmath.log(mpmath.mp.one - 1e-17))) + # 39.14394658089878 + assert_allclose(stats.gumbel_r.isf(1e-17), 39.14394658089878, + rtol=1e-14) + + +class TestLevyStable: + def setup_method(self): + self.rng = np.random.default_rng(7195199371) + + @pytest.fixture(autouse=True) + def reset_levy_stable_params(self): + """Setup default parameters for levy_stable generator""" + stats.levy_stable.parameterization = "S1" + stats.levy_stable.cdf_default_method = "piecewise" + stats.levy_stable.pdf_default_method = "piecewise" + stats.levy_stable.quad_eps = stats._levy_stable._QUAD_EPS + + @pytest.fixture + def nolan_pdf_sample_data(self): + """Sample data points for pdf computed with Nolan's stablec + + See - http://fs2.american.edu/jpnolan/www/stable/stable.html + + There's a known limitation of Nolan's executable for alpha < 0.2. + + The data table loaded below is generated from Nolan's stablec + with the following parameter space: + + alpha = 0.1, 0.2, ..., 2.0 + beta = -1.0, -0.9, ..., 1.0 + p = 0.01, 0.05, 0.1, 0.25, 0.35, 0.5, + and the equivalent for the right tail + + Typically inputs for stablec: + + stablec.exe << + 1 # pdf + 1 # Nolan S equivalent to S0 in scipy + .25,2,.25 # alpha + -1,-1,0 # beta + -10,10,1 # x + 1,0 # gamma, delta + 2 # output file + """ + data = np.load( + Path(__file__).parent / + 'data/levy_stable/stable-Z1-pdf-sample-data.npy' + ) + data = np.rec.fromarrays(data.T, names='x,p,alpha,beta,pct') + return data + + @pytest.fixture + def nolan_cdf_sample_data(self): + """Sample data points for cdf computed with Nolan's stablec + + See - http://fs2.american.edu/jpnolan/www/stable/stable.html + + There's a known limitation of Nolan's executable for alpha < 0.2. + + The data table loaded below is generated from Nolan's stablec + with the following parameter space: + + alpha = 0.1, 0.2, ..., 2.0 + beta = -1.0, -0.9, ..., 1.0 + p = 0.01, 0.05, 0.1, 0.25, 0.35, 0.5, + + and the equivalent for the right tail + + Ideally, Nolan's output for CDF values should match the percentile + from where they have been sampled from. Even more so as we extract + percentile x positions from stablec too. However, we note at places + Nolan's stablec will produce absolute errors in order of 1e-5. We + compare against his calculations here. In future, once we less + reliant on Nolan's paper we might switch to comparing directly at + percentiles (those x values being produced from some alternative + means). + + Typically inputs for stablec: + + stablec.exe << + 2 # cdf + 1 # Nolan S equivalent to S0 in scipy + .25,2,.25 # alpha + -1,-1,0 # beta + -10,10,1 # x + 1,0 # gamma, delta + 2 # output file + """ + data = np.load( + Path(__file__).parent / + 'data/levy_stable/stable-Z1-cdf-sample-data.npy' + ) + data = np.rec.fromarrays(data.T, names='x,p,alpha,beta,pct') + return data + + @pytest.fixture + def nolan_loc_scale_sample_data(self): + """Sample data where loc, scale are different from 0, 1 + + Data extracted in similar way to pdf/cdf above using + Nolan's stablec but set to an arbitrary location scale of + (2, 3) for various important parameters alpha, beta and for + parameterisations S0 and S1. + """ + data = np.load( + Path(__file__).parent / + 'data/levy_stable/stable-loc-scale-sample-data.npy' + ) + return data + + @pytest.mark.slow + @pytest.mark.parametrize( + "sample_size", [ + pytest.param(50), pytest.param(1500, marks=pytest.mark.slow) + ] + ) + @pytest.mark.parametrize("parameterization", ["S0", "S1"]) + @pytest.mark.parametrize( + "alpha,beta", [(1.0, 0), (1.0, -0.5), (1.5, 0), (1.9, 0.5)] + ) + @pytest.mark.parametrize("gamma,delta", [(1, 0), (3, 2)]) + def test_rvs( + self, + parameterization, + alpha, + beta, + gamma, + delta, + sample_size, + ): + stats.levy_stable.parameterization = parameterization + ls = stats.levy_stable( + alpha=alpha, beta=beta, scale=gamma, loc=delta + ) + _, p = stats.kstest( + ls.rvs(size=sample_size, random_state=self.rng), ls.cdf + ) + assert p > 0.05 + + @pytest.mark.xslow + @pytest.mark.parametrize('beta', [0.5, 1]) + def test_rvs_alpha1(self, beta): + """Additional test cases for rvs for alpha equal to 1.""" + alpha = 1.0 + loc = 0.5 + scale = 1.5 + x = stats.levy_stable.rvs(alpha, beta, loc=loc, scale=scale, + size=5000, random_state=self.rng) + stat, p = stats.kstest(x, 'levy_stable', + args=(alpha, beta, loc, scale)) + assert p > 0.01 + + def test_fit(self): + # construct data to have percentiles that match + # example in McCulloch 1986. + x = [ + -.05413, -.05413, 0., 0., 0., 0., .00533, .00533, .00533, .00533, + .00533, .03354, .03354, .03354, .03354, .03354, .05309, .05309, + .05309, .05309, .05309 + ] + alpha1, beta1, loc1, scale1 = stats.levy_stable._fitstart(x) + assert_allclose(alpha1, 1.48, rtol=0, atol=0.01) + assert_almost_equal(beta1, -.22, 2) + assert_almost_equal(scale1, 0.01717, 4) + assert_almost_equal( + loc1, 0.00233, 2 + ) # to 2 dps due to rounding error in McCulloch86 + + # cover alpha=2 scenario + x2 = x + [.05309, .05309, .05309, .05309, .05309] + alpha2, beta2, loc2, scale2 = stats.levy_stable._fitstart(x2) + assert_equal(alpha2, 2) + assert_equal(beta2, -1) + assert_almost_equal(scale2, .02503, 4) + assert_almost_equal(loc2, .03354, 4) + + @pytest.mark.xfail(reason="Unknown problem with fitstart.") + @pytest.mark.parametrize( + "alpha,beta,delta,gamma", + [ + (1.5, 0.4, 2, 3), + (1.0, 0.4, 2, 3), + ] + ) + @pytest.mark.parametrize( + "parametrization", ["S0", "S1"] + ) + def test_fit_rvs(self, alpha, beta, delta, gamma, parametrization): + """Test that fit agrees with rvs for each parametrization.""" + stats.levy_stable.parametrization = parametrization + data = stats.levy_stable.rvs( + alpha, beta, loc=delta, scale=gamma, size=10000, random_state=self.rng + ) + fit = stats.levy_stable._fitstart(data) + alpha_obs, beta_obs, delta_obs, gamma_obs = fit + assert_allclose( + [alpha, beta, delta, gamma], + [alpha_obs, beta_obs, delta_obs, gamma_obs], + rtol=0.01, + ) + + def test_fit_beta_flip(self): + # Confirm that sign of beta affects loc, not alpha or scale. + x = np.array([1, 1, 3, 3, 10, 10, 10, 30, 30, 100, 100]) + alpha1, beta1, loc1, scale1 = stats.levy_stable._fitstart(x) + alpha2, beta2, loc2, scale2 = stats.levy_stable._fitstart(-x) + assert_equal(beta1, 1) + assert loc1 != 0 + assert_almost_equal(alpha2, alpha1) + assert_almost_equal(beta2, -beta1) + assert_almost_equal(loc2, -loc1) + assert_almost_equal(scale2, scale1) + + def test_fit_delta_shift(self): + # Confirm that loc slides up and down if data shifts. + SHIFT = 1 + x = np.array([1, 1, 3, 3, 10, 10, 10, 30, 30, 100, 100]) + alpha1, beta1, loc1, scale1 = stats.levy_stable._fitstart(-x) + alpha2, beta2, loc2, scale2 = stats.levy_stable._fitstart(-x + SHIFT) + assert_almost_equal(alpha2, alpha1) + assert_almost_equal(beta2, beta1) + assert_almost_equal(loc2, loc1 + SHIFT) + assert_almost_equal(scale2, scale1) + + def test_fit_loc_extrap(self): + # Confirm that loc goes out of sample for alpha close to 1. + x = [1, 1, 3, 3, 10, 10, 10, 30, 30, 140, 140] + alpha1, beta1, loc1, scale1 = stats.levy_stable._fitstart(x) + assert alpha1 < 1, f"Expected alpha < 1, got {alpha1}" + assert loc1 < min(x), f"Expected loc < {min(x)}, got {loc1}" + + x2 = [1, 1, 3, 3, 10, 10, 10, 30, 30, 130, 130] + alpha2, beta2, loc2, scale2 = stats.levy_stable._fitstart(x2) + assert alpha2 > 1, f"Expected alpha > 1, got {alpha2}" + assert loc2 > max(x2), f"Expected loc > {max(x2)}, got {loc2}" + + @pytest.mark.slow + @pytest.mark.parametrize( + "pct_range,alpha_range,beta_range", [ + pytest.param( + [.01, .5, .99], + [.1, 1, 2], + [-1, 0, .8], + ), + pytest.param( + [.01, .05, .5, .95, .99], + [.1, .5, 1, 1.5, 2], + [-.9, -.5, 0, .3, .6, 1], + marks=pytest.mark.slow + ), + pytest.param( + [.01, .05, .1, .25, .35, .5, .65, .75, .9, .95, .99], + np.linspace(0.1, 2, 20), + np.linspace(-1, 1, 21), + marks=pytest.mark.xslow, + ), + ] + ) + def test_pdf_nolan_samples( + self, nolan_pdf_sample_data, pct_range, alpha_range, beta_range + ): + """Test pdf values against Nolan's stablec.exe output""" + data = nolan_pdf_sample_data + + # some tests break on linux 32 bit + uname = platform.uname() + is_linux_32 = uname.system == 'Linux' and uname.machine == 'i686' + platform_desc = "/".join( + [uname.system, uname.machine, uname.processor]) + + # fmt: off + # There are a number of cases which fail on some but not all platforms. + # These are excluded by the filters below. TODO: Rewrite tests so that + # the now filtered out test cases are still run but marked in pytest as + # expected to fail. + tests = [ + [ + 'dni', 1e-7, lambda r: ( + np.isin(r['pct'], pct_range) & + np.isin(r['alpha'], alpha_range) & + np.isin(r['beta'], beta_range) & + ~( + ( + (r['beta'] == 0) & + (r['pct'] == 0.5) + ) | + ( + (r['beta'] >= 0.9) & + (r['alpha'] >= 1.6) & + (r['pct'] == 0.5) + ) | + ( + (r['alpha'] <= 0.4) & + np.isin(r['pct'], [.01, .99]) + ) | + ( + (r['alpha'] <= 0.3) & + np.isin(r['pct'], [.05, .95]) + ) | + ( + (r['alpha'] <= 0.2) & + np.isin(r['pct'], [.1, .9]) + ) | + ( + (r['alpha'] == 0.1) & + np.isin(r['pct'], [.25, .75]) & + np.isin(np.abs(r['beta']), [.5, .6, .7]) + ) | + ( + (r['alpha'] == 0.1) & + np.isin(r['pct'], [.5]) & + np.isin(np.abs(r['beta']), [.1]) + ) | + ( + (r['alpha'] == 0.1) & + np.isin(r['pct'], [.35, .65]) & + np.isin(np.abs(r['beta']), [-.4, -.3, .3, .4, .5]) + ) | + ( + (r['alpha'] == 0.2) & + (r['beta'] == 0.5) & + (r['pct'] == 0.25) + ) | + ( + (r['alpha'] == 0.2) & + (r['beta'] == -0.3) & + (r['pct'] == 0.65) + ) | + ( + (r['alpha'] == 0.2) & + (r['beta'] == 0.3) & + (r['pct'] == 0.35) + ) | + ( + (r['alpha'] == 1.) & + np.isin(r['pct'], [.5]) & + np.isin(np.abs(r['beta']), [.1, .2, .3, .4]) + ) | + ( + (r['alpha'] == 1.) & + np.isin(r['pct'], [.35, .65]) & + np.isin(np.abs(r['beta']), [.8, .9, 1.]) + ) | + ( + (r['alpha'] == 1.) & + np.isin(r['pct'], [.01, .99]) & + np.isin(np.abs(r['beta']), [-.1, .1]) + ) | + # various points ok but too sparse to list + (r['alpha'] >= 1.1) + ) + ) + ], + # piecewise generally good accuracy + [ + 'piecewise', 1e-11, lambda r: ( + np.isin(r['pct'], pct_range) & + np.isin(r['alpha'], alpha_range) & + np.isin(r['beta'], beta_range) & + (r['alpha'] > 0.2) & + (r['alpha'] != 1.) + ) + ], + # for alpha = 1. for linux 32 bit optimize.bisect + # has some issues for .01 and .99 percentile + [ + 'piecewise', 1e-11, lambda r: ( + (r['alpha'] == 1.) & + (not is_linux_32) & + np.isin(r['pct'], pct_range) & + (1. in alpha_range) & + np.isin(r['beta'], beta_range) + ) + ], + # for small alpha very slightly reduced accuracy + [ + 'piecewise', 2.5e-10, lambda r: ( + np.isin(r['pct'], pct_range) & + np.isin(r['alpha'], alpha_range) & + np.isin(r['beta'], beta_range) & + (r['alpha'] <= 0.2) + ) + ], + # fft accuracy reduces as alpha decreases + [ + 'fft-simpson', 1e-5, lambda r: ( + (r['alpha'] >= 1.9) & + np.isin(r['pct'], pct_range) & + np.isin(r['alpha'], alpha_range) & + np.isin(r['beta'], beta_range) + ), + ], + [ + 'fft-simpson', 1e-6, lambda r: ( + np.isin(r['pct'], pct_range) & + np.isin(r['alpha'], alpha_range) & + np.isin(r['beta'], beta_range) & + (r['alpha'] > 1) & + (r['alpha'] < 1.9) + ) + ], + # fft relative errors for alpha < 1, will raise if enabled + # ['fft-simpson', 1e-4, lambda r: r['alpha'] == 0.9], + # ['fft-simpson', 1e-3, lambda r: r['alpha'] == 0.8], + # ['fft-simpson', 1e-2, lambda r: r['alpha'] == 0.7], + # ['fft-simpson', 1e-1, lambda r: r['alpha'] == 0.6], + ] + # fmt: on + for ix, (default_method, rtol, + filter_func) in enumerate(tests): + stats.levy_stable.pdf_default_method = default_method + subdata = data[filter_func(data) + ] if filter_func is not None else data + msg = "Density calculations experimental for FFT method" + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", msg, RuntimeWarning) + # occurs in FFT methods only + p = stats.levy_stable.pdf( + subdata['x'], + subdata['alpha'], + subdata['beta'], + scale=1, + loc=0 + ) + with np.errstate(over="ignore"): + subdata2 = rec_append_fields( + subdata, + ['calc', 'abserr', 'relerr'], + [ + p, + np.abs(p - subdata['p']), + np.abs(p - subdata['p']) / np.abs(subdata['p']) + ] + ) + failures = subdata2[ + (subdata2['relerr'] >= rtol) | + np.isnan(p) + ] + message = ( + f"pdf test {ix} failed with method '{default_method}' " + f"[platform: {platform_desc}]\n{failures.dtype.names}\n{failures}" + ) + assert_allclose( + p, + subdata['p'], + rtol, + err_msg=message, + verbose=False + ) + + @pytest.mark.parametrize( + "pct_range,alpha_range,beta_range", [ + pytest.param( + [.01, .5, .99], + [.1, 1, 2], + [-1, 0, .8], + ), + pytest.param( + [.01, .05, .5, .95, .99], + [.1, .5, 1, 1.5, 2], + [-.9, -.5, 0, .3, .6, 1], + marks=pytest.mark.slow + ), + pytest.param( + [.01, .05, .1, .25, .35, .5, .65, .75, .9, .95, .99], + np.linspace(0.1, 2, 20), + np.linspace(-1, 1, 21), + marks=pytest.mark.xslow, + ), + ] + ) + def test_cdf_nolan_samples( + self, nolan_cdf_sample_data, pct_range, alpha_range, beta_range + ): + """ Test cdf values against Nolan's stablec.exe output.""" + data = nolan_cdf_sample_data + tests = [ + # piecewise generally good accuracy + [ + 'piecewise', 2e-12, lambda r: ( + np.isin(r['pct'], pct_range) & + np.isin(r['alpha'], alpha_range) & + np.isin(r['beta'], beta_range) & + ~( + ( + (r['alpha'] == 1.) & + np.isin(r['beta'], [-0.3, -0.2, -0.1]) & + (r['pct'] == 0.01) + ) | + ( + (r['alpha'] == 1.) & + np.isin(r['beta'], [0.1, 0.2, 0.3]) & + (r['pct'] == 0.99) + ) + ) + ) + ], + # for some points with alpha=1, Nolan's STABLE clearly + # loses accuracy + [ + 'piecewise', 5e-2, lambda r: ( + np.isin(r['pct'], pct_range) & + np.isin(r['alpha'], alpha_range) & + np.isin(r['beta'], beta_range) & + ( + (r['alpha'] == 1.) & + np.isin(r['beta'], [-0.3, -0.2, -0.1]) & + (r['pct'] == 0.01) + ) | + ( + (r['alpha'] == 1.) & + np.isin(r['beta'], [0.1, 0.2, 0.3]) & + (r['pct'] == 0.99) + ) + ) + ], + # fft accuracy poor, very poor alpha < 1 + [ + 'fft-simpson', 1e-5, lambda r: ( + np.isin(r['pct'], pct_range) & + np.isin(r['alpha'], alpha_range) & + np.isin(r['beta'], beta_range) & + (r['alpha'] > 1.7) + ) + ], + [ + 'fft-simpson', 1e-4, lambda r: ( + np.isin(r['pct'], pct_range) & + np.isin(r['alpha'], alpha_range) & + np.isin(r['beta'], beta_range) & + (r['alpha'] > 1.5) & + (r['alpha'] <= 1.7) + ) + ], + [ + 'fft-simpson', 1e-3, lambda r: ( + np.isin(r['pct'], pct_range) & + np.isin(r['alpha'], alpha_range) & + np.isin(r['beta'], beta_range) & + (r['alpha'] > 1.3) & + (r['alpha'] <= 1.5) + ) + ], + [ + 'fft-simpson', 1e-2, lambda r: ( + np.isin(r['pct'], pct_range) & + np.isin(r['alpha'], alpha_range) & + np.isin(r['beta'], beta_range) & + (r['alpha'] > 1.0) & + (r['alpha'] <= 1.3) + ) + ], + ] + for ix, (default_method, rtol, + filter_func) in enumerate(tests): + stats.levy_stable.cdf_default_method = default_method + subdata = data[filter_func(data) + ] if filter_func is not None else data + with warnings.catch_warnings(): + warnings.filterwarnings( + 'ignore', + ('Cumulative density calculations experimental for FFT' + ' method. Use piecewise method instead.'), + RuntimeWarning) + p = stats.levy_stable.cdf( + subdata['x'], + subdata['alpha'], + subdata['beta'], + scale=1, + loc=0 + ) + with np.errstate(over="ignore"): + subdata2 = rec_append_fields( + subdata, + ['calc', 'abserr', 'relerr'], + [ + p, + np.abs(p - subdata['p']), + np.abs(p - subdata['p']) / np.abs(subdata['p']) + ] + ) + failures = subdata2[ + (subdata2['relerr'] >= rtol) | + np.isnan(p) + ] + message = (f"cdf test {ix} failed with method '{default_method}'\n" + f"{failures.dtype.names}\n{failures}") + assert_allclose( + p, + subdata['p'], + rtol, + err_msg=message, + verbose=False + ) + + @pytest.mark.parametrize("param", [0, 1]) + @pytest.mark.parametrize("case", ["pdf", "cdf"]) + def test_location_scale( + self, nolan_loc_scale_sample_data, param, case + ): + """Tests for pdf and cdf where loc, scale are different from 0, 1 + """ + + uname = platform.uname() + is_linux_32 = uname.system == 'Linux' and "32bit" in platform.architecture()[0] + # Test seems to be unstable (see gh-17839 for a bug report on Debian + # i386), so skip it. + if is_linux_32 and case == 'pdf': + pytest.skip("Test unstable on some platforms; see gh-17839, 17859") + + data = nolan_loc_scale_sample_data + # We only test against piecewise as location/scale transforms + # are same for other methods. + stats.levy_stable.cdf_default_method = "piecewise" + stats.levy_stable.pdf_default_method = "piecewise" + + subdata = data[data["param"] == param] + stats.levy_stable.parameterization = f"S{param}" + + assert case in ["pdf", "cdf"] + function = ( + stats.levy_stable.pdf if case == "pdf" else stats.levy_stable.cdf + ) + + v1 = function( + subdata['x'], subdata['alpha'], subdata['beta'], scale=2, loc=3 + ) + assert_allclose(v1, subdata[case], 1e-5) + + @pytest.mark.parametrize( + "method,decimal_places", + [ + ['dni', 4], + ['piecewise', 4], + ] + ) + def test_pdf_alpha_equals_one_beta_non_zero(self, method, decimal_places): + """ sample points extracted from Tables and Graphs of Stable + Probability Density Functions - Donald R Holt - 1973 - p 187. + """ + xs = np.array( + [0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4] + ) + density = np.array( + [ + .3183, .3096, .2925, .2622, .1591, .1587, .1599, .1635, .0637, + .0729, .0812, .0955, .0318, .0390, .0458, .0586, .0187, .0236, + .0285, .0384 + ] + ) + betas = np.array( + [ + 0, .25, .5, 1, 0, .25, .5, 1, 0, .25, .5, 1, 0, .25, .5, 1, 0, + .25, .5, 1 + ] + ) + with np.errstate(all='ignore'), warnings.catch_warnings(): + warnings.filterwarnings("ignore", + category=RuntimeWarning, + message="Density calculation unstable.*" + ) + stats.levy_stable.pdf_default_method = method + # stats.levy_stable.fft_grid_spacing = 0.0001 + pdf = stats.levy_stable.pdf(xs, 1, betas, scale=1, loc=0) + assert_almost_equal( + pdf, density, decimal_places, method + ) + + @pytest.mark.parametrize( + "params,expected", + [ + [(1.48, -.22, 0, 1), (0, np.inf, np.nan, np.nan)], + [(2, .9, 10, 1.5), (10, 4.5, 0, 0)] + ] + ) + def test_stats(self, params, expected): + observed = stats.levy_stable.stats( + params[0], params[1], loc=params[2], scale=params[3], + moments='mvsk' + ) + assert_almost_equal(observed, expected) + + @pytest.mark.parametrize('alpha', [0.25, 0.5, 0.75]) + @pytest.mark.parametrize( + 'function,beta,points,expected', + [ + ( + stats.levy_stable.cdf, + 1.0, + np.linspace(-25, 0, 10), + 0.0, + ), + ( + stats.levy_stable.pdf, + 1.0, + np.linspace(-25, 0, 10), + 0.0, + ), + ( + stats.levy_stable.cdf, + -1.0, + np.linspace(0, 25, 10), + 1.0, + ), + ( + stats.levy_stable.pdf, + -1.0, + np.linspace(0, 25, 10), + 0.0, + ) + ] + ) + def test_distribution_outside_support( + self, alpha, function, beta, points, expected + ): + """Ensure the pdf/cdf routines do not return nan outside support. + + This distribution's support becomes truncated in a few special cases: + support is [mu, infty) if alpha < 1 and beta = 1 + support is (-infty, mu] if alpha < 1 and beta = -1 + Otherwise, the support is all reals. Here, mu is zero by default. + """ + assert 0 < alpha < 1 + assert_almost_equal( + function(points, alpha=alpha, beta=beta), + np.full(len(points), expected) + ) + + @pytest.mark.parametrize( + 'x,alpha,beta,expected', + # Reference values from Matlab + # format long + # alphas = [1.7720732804618808, 1.9217001522410235, 1.5654806051633634, + # 1.7420803447784388, 1.5748002527689913]; + # betas = [0.5059373136902996, -0.8779442746685926, -0.4016220341911392, + # -0.38180029468259247, -0.25200194914153684]; + # x0s = [0, 1e-4, -1e-4]; + # for x0 = x0s + # disp("x0 = " + x0) + # for ii = 1:5 + # alpha = alphas(ii); + # beta = betas(ii); + # pd = makedist('Stable','alpha',alpha,'beta',beta,'gam',1,'delta',0); + # % we need to adjust x. It is the same as x = 0 In scipy. + # x = x0 - beta * tan(pi * alpha / 2); + # disp(pd.pdf(x)) + # end + # end + [ + (0, 1.7720732804618808, 0.5059373136902996, 0.278932636798268), + (0, 1.9217001522410235, -0.8779442746685926, 0.281054757202316), + (0, 1.5654806051633634, -0.4016220341911392, 0.271282133194204), + (0, 1.7420803447784388, -0.38180029468259247, 0.280202199244247), + (0, 1.5748002527689913, -0.25200194914153684, 0.280136576218665), + ] + ) + def test_x_equal_zeta( + self, x, alpha, beta, expected + ): + """Test pdf for x equal to zeta. + + With S1 parametrization: x0 = x + zeta if alpha != 1 So, for x = 0, x0 + will be close to zeta. + + When case "x equal zeta" is not handled properly and quad_eps is not + low enough: - pdf may be less than 0 - logpdf is nan + + The points from the parametrize block are found randomly so that PDF is + less than 0. + + Reference values taken from MATLAB + https://www.mathworks.com/help/stats/stable-distribution.html + """ + stats.levy_stable.quad_eps = 1.2e-11 + + assert_almost_equal( + stats.levy_stable.pdf(x, alpha=alpha, beta=beta), + expected, + ) + + @pytest.mark.xfail + @pytest.mark.parametrize( + # See comment for test_x_equal_zeta for script for reference values + 'x,alpha,beta,expected', + [ + (1e-4, 1.7720732804618808, 0.5059373136902996, 0.278929165340670), + (1e-4, 1.9217001522410235, -0.8779442746685926, 0.281056564327953), + (1e-4, 1.5654806051633634, -0.4016220341911392, 0.271252432161167), + (1e-4, 1.7420803447784388, -0.38180029468259247, 0.280205311264134), + (1e-4, 1.5748002527689913, -0.25200194914153684, 0.280140965235426), + (-1e-4, 1.7720732804618808, 0.5059373136902996, 0.278936106741754), + (-1e-4, 1.9217001522410235, -0.8779442746685926, 0.281052948629429), + (-1e-4, 1.5654806051633634, -0.4016220341911392, 0.271275394392385), + (-1e-4, 1.7420803447784388, -0.38180029468259247, 0.280199085645099), + (-1e-4, 1.5748002527689913, -0.25200194914153684, 0.280132185432842), + ] + ) + def test_x_near_zeta( + self, x, alpha, beta, expected + ): + """Test pdf for x near zeta. + + With S1 parametrization: x0 = x + zeta if alpha != 1 So, for x = 0, x0 + will be close to zeta. + + When case "x near zeta" is not handled properly and quad_eps is not + low enough: - pdf may be less than 0 - logpdf is nan + + The points from the parametrize block are found randomly so that PDF is + less than 0. + + Reference values taken from MATLAB + https://www.mathworks.com/help/stats/stable-distribution.html + """ + stats.levy_stable.quad_eps = 1.2e-11 + + assert_almost_equal( + stats.levy_stable.pdf(x, alpha=alpha, beta=beta), + expected, + ) + + @pytest.fixture + def levy_stable_lock(self): + return threading.Lock() + + def test_frozen_parameterization_gh20821(self, levy_stable_lock): + # gh-20821 reported that frozen distributions ignore the parameterization. + # Check that this is resolved and that the frozen distribution's + # parameterization can be changed independently of stats.levy_stable + rng = np.random.default_rng + shapes = dict(alpha=1.9, beta=0.1, loc=0.0, scale=1.0) + unfrozen = stats.levy_stable + frozen = stats.levy_stable(**shapes) + + with levy_stable_lock: + unfrozen.parameterization = "S0" + frozen.parameterization = "S1" + unfrozen_a = unfrozen.rvs(**shapes, size=10, random_state=rng(329823498)) + frozen_a = frozen.rvs(size=10, random_state=rng(329823498)) + assert not np.any(frozen_a == unfrozen_a) + + unfrozen.parameterization = "S1" + frozen.parameterization = "S0" + unfrozen_b = unfrozen.rvs(**shapes, size=10, random_state=rng(329823498)) + frozen_b = frozen.rvs(size=10, random_state=rng(329823498)) + assert_equal(frozen_b, unfrozen_a) + assert_equal(unfrozen_b, frozen_a) + + def test_frozen_parameterization_gh20821b(self, levy_stable_lock): + # Check that the parameterization of the frozen distribution is that of + # the unfrozen distribution at the time of freezing + rng = np.random.default_rng + shapes = dict(alpha=1.9, beta=0.1, loc=0.0, scale=1.0) + unfrozen = stats.levy_stable + + with levy_stable_lock: + unfrozen.parameterization = "S0" + frozen = stats.levy_stable(**shapes) + unfrozen_a = unfrozen.rvs(**shapes, size=10, random_state=rng(329823498)) + frozen_a = frozen.rvs(size=10, random_state=rng(329823498)) + assert_equal(frozen_a, unfrozen_a) + + unfrozen.parameterization = "S1" + frozen = stats.levy_stable(**shapes) + unfrozen_b = unfrozen.rvs(**shapes, size=10, random_state=rng(329823498)) + frozen_b = frozen.rvs(size=10, random_state=rng(329823498)) + assert_equal(frozen_b, unfrozen_b) + + @pytest.mark.parametrize( + "param, alpha, beta, xs, expected", + [ + (0, 1.6, 1, [-15, -14, -13], [-187.1444263, -157.1411663, -130.3526688]), + (1, 1.6, 1, [-15, -14, -13], [-165.0203129, -137.3671717, -112.8223545]), + (0, 1.4, -1, [13, 14, 15], [-367.1943293, -464.7049565, -579.4399286]), + (1, 1.4, -1, [13, 14, 15], [-258.1216497, -334.6139857, -426.0724094]), + ], + ) + @pytest.mark.xfail_on_32bit("`bisect` fails to recognize valid bracket") + def test_logpdf_tail(self, param, alpha, beta, xs, expected): + # gh-20636 + # Expected values computed from Mathematica. Mathematica command is: + # Log[PDF[StableDistribution[0, 1.6, 1, 0, 1], -15.0]] // InputForm + # Test levy_stable.logpdf accuracy in the tails when beta=1 or beta=-1 + # for both S0/S1 parameterizations. + # Testing logpdf instead of pdf as more compact. + stats.levy_stable.parameterization = f"S{param}" + logpdfs = stats.levy_stable(alpha=alpha, beta=beta).logpdf(xs) + assert_allclose(logpdfs, expected, rtol=1e-5) + +class TestArrayArgument: # test for ticket:992 + def setup_method(self): + self.rng = np.random.default_rng(7556981556) + + def test_noexception(self): + rvs = stats.norm.rvs(loc=(np.arange(5)), scale=np.ones(5), + size=(10, 5), random_state=self.rng) + assert_equal(rvs.shape, (10, 5)) + + +class TestDocstring: + def test_docstrings(self): + # See ticket #761 + if stats.rayleigh.__doc__ is not None: + assert_("rayleigh" in stats.rayleigh.__doc__.lower()) + if stats.bernoulli.__doc__ is not None: + assert_("bernoulli" in stats.bernoulli.__doc__.lower()) + + def test_no_name_arg(self): + # If name is not given, construction shouldn't fail. See #1508. + stats.rv_continuous() + stats.rv_discrete() + + +def test_args_reduce(): + a = array([1, 3, 2, 1, 2, 3, 3]) + b, c = argsreduce(a > 1, a, 2) + + assert_array_equal(b, [3, 2, 2, 3, 3]) + assert_array_equal(c, [2]) + + b, c = argsreduce(2 > 1, a, 2) + assert_array_equal(b, a) + assert_array_equal(c, [2] * np.size(a)) + + b, c = argsreduce(a > 0, a, 2) + assert_array_equal(b, a) + assert_array_equal(c, [2] * np.size(a)) + + +class TestFitMethod: + # fitting assumes continuous parameters + skip = ['ncf', 'ksone', 'kstwo', 'irwinhall'] + + def setup_method(self): + self.rng = np.random.default_rng(4522425749) + + # skip these b/c deprecated, or only loc and scale arguments + fitSkipNonFinite = ['expon', 'norm', 'uniform', 'irwinhall'] + + @pytest.mark.parametrize('dist,args', distcont) + def test_fit_w_non_finite_data_values(self, dist, args): + """gh-10300""" + if dist in self.fitSkipNonFinite: + pytest.skip(f"{dist} fit known to fail or deprecated") + x = np.array([1.6483, 2.7169, 2.4667, 1.1791, 3.5433, np.nan]) + y = np.array([1.6483, 2.7169, 2.4667, 1.1791, 3.5433, np.inf]) + distfunc = getattr(stats, dist) + assert_raises(ValueError, distfunc.fit, x, fscale=1) + assert_raises(ValueError, distfunc.fit, y, fscale=1) + + def test_fix_fit_2args_lognorm(self): + # Regression test for #1551. + with np.errstate(all='ignore'): + x = stats.lognorm.rvs(0.25, 0., 20.0, size=20, random_state=self.rng) + expected_shape = np.sqrt(((np.log(x) - np.log(20))**2).mean()) + assert_allclose(np.array(stats.lognorm.fit(x, floc=0, fscale=20)), + [expected_shape, 0, 20], atol=1e-8) + + def test_fix_fit_norm(self): + x = np.arange(1, 6) + + loc, scale = stats.norm.fit(x) + assert_almost_equal(loc, 3) + assert_almost_equal(scale, np.sqrt(2)) + + loc, scale = stats.norm.fit(x, floc=2) + assert_equal(loc, 2) + assert_equal(scale, np.sqrt(3)) + + loc, scale = stats.norm.fit(x, fscale=2) + assert_almost_equal(loc, 3) + assert_equal(scale, 2) + + def test_fix_fit_gamma(self): + x = np.arange(1, 6) + meanlog = np.log(x).mean() + + # A basic test of gamma.fit with floc=0. + floc = 0 + a, loc, scale = stats.gamma.fit(x, floc=floc) + s = np.log(x.mean()) - meanlog + assert_almost_equal(np.log(a) - special.digamma(a), s, decimal=5) + assert_equal(loc, floc) + assert_almost_equal(scale, x.mean()/a, decimal=8) + + # Regression tests for gh-2514. + # The problem was that if `floc=0` was given, any other fixed + # parameters were ignored. + f0 = 1 + floc = 0 + a, loc, scale = stats.gamma.fit(x, f0=f0, floc=floc) + assert_equal(a, f0) + assert_equal(loc, floc) + assert_almost_equal(scale, x.mean()/a, decimal=8) + + f0 = 2 + floc = 0 + a, loc, scale = stats.gamma.fit(x, f0=f0, floc=floc) + assert_equal(a, f0) + assert_equal(loc, floc) + assert_almost_equal(scale, x.mean()/a, decimal=8) + + # loc and scale fixed. + floc = 0 + fscale = 2 + a, loc, scale = stats.gamma.fit(x, floc=floc, fscale=fscale) + assert_equal(loc, floc) + assert_equal(scale, fscale) + c = meanlog - np.log(fscale) + assert_almost_equal(special.digamma(a), c) + + def test_fix_fit_beta(self): + # Test beta.fit when both floc and fscale are given. + + def mlefunc(a, b, x): + # Zeros of this function are critical points of + # the maximum likelihood function. + n = len(x) + s1 = np.log(x).sum() + s2 = np.log(1-x).sum() + psiab = special.psi(a + b) + func = [s1 - n * (-psiab + special.psi(a)), + s2 - n * (-psiab + special.psi(b))] + return func + + # Basic test with floc and fscale given. + x = np.array([0.125, 0.25, 0.5]) + a, b, loc, scale = stats.beta.fit(x, floc=0, fscale=1) + assert_equal(loc, 0) + assert_equal(scale, 1) + assert_allclose(mlefunc(a, b, x), [0, 0], atol=1e-6) + + # Basic test with f0, floc and fscale given. + # This is also a regression test for gh-2514. + x = np.array([0.125, 0.25, 0.5]) + a, b, loc, scale = stats.beta.fit(x, f0=2, floc=0, fscale=1) + assert_equal(a, 2) + assert_equal(loc, 0) + assert_equal(scale, 1) + da, db = mlefunc(a, b, x) + assert_allclose(db, 0, atol=1e-5) + + # Same floc and fscale values as above, but reverse the data + # and fix b (f1). + x2 = 1 - x + a2, b2, loc2, scale2 = stats.beta.fit(x2, f1=2, floc=0, fscale=1) + assert_equal(b2, 2) + assert_equal(loc2, 0) + assert_equal(scale2, 1) + da, db = mlefunc(a2, b2, x2) + assert_allclose(da, 0, atol=1e-5) + # a2 of this test should equal b from above. + assert_almost_equal(a2, b) + + # Check for detection of data out of bounds when floc and fscale + # are given. + assert_raises(ValueError, stats.beta.fit, x, floc=0.5, fscale=1) + y = np.array([0, .5, 1]) + assert_raises(ValueError, stats.beta.fit, y, floc=0, fscale=1) + assert_raises(ValueError, stats.beta.fit, y, floc=0, fscale=1, f0=2) + assert_raises(ValueError, stats.beta.fit, y, floc=0, fscale=1, f1=2) + + # Check that attempting to fix all the parameters raises a ValueError. + assert_raises(ValueError, stats.beta.fit, y, f0=0, f1=1, + floc=2, fscale=3) + + def test_expon_fit(self): + x = np.array([2, 2, 4, 4, 4, 4, 4, 8]) + + loc, scale = stats.expon.fit(x) + assert_equal(loc, 2) # x.min() + assert_equal(scale, 2) # x.mean() - x.min() + + loc, scale = stats.expon.fit(x, fscale=3) + assert_equal(loc, 2) # x.min() + assert_equal(scale, 3) # fscale + + loc, scale = stats.expon.fit(x, floc=0) + assert_equal(loc, 0) # floc + assert_equal(scale, 4) # x.mean() - loc + + def test_lognorm_fit(self): + x = np.array([1.5, 3, 10, 15, 23, 59]) + lnxm1 = np.log(x - 1) + + shape, loc, scale = stats.lognorm.fit(x, floc=1) + assert_allclose(shape, lnxm1.std(), rtol=1e-12) + assert_equal(loc, 1) + assert_allclose(scale, np.exp(lnxm1.mean()), rtol=1e-12) + + shape, loc, scale = stats.lognorm.fit(x, floc=1, fscale=6) + assert_allclose(shape, np.sqrt(((lnxm1 - np.log(6))**2).mean()), + rtol=1e-12) + assert_equal(loc, 1) + assert_equal(scale, 6) + + shape, loc, scale = stats.lognorm.fit(x, floc=1, fix_s=0.75) + assert_equal(shape, 0.75) + assert_equal(loc, 1) + assert_allclose(scale, np.exp(lnxm1.mean()), rtol=1e-12) + + def test_uniform_fit(self): + x = np.array([1.0, 1.1, 1.2, 9.0]) + + loc, scale = stats.uniform.fit(x) + assert_equal(loc, x.min()) + assert_equal(scale, np.ptp(x)) + + loc, scale = stats.uniform.fit(x, floc=0) + assert_equal(loc, 0) + assert_equal(scale, x.max()) + + loc, scale = stats.uniform.fit(x, fscale=10) + assert_equal(loc, 0) + assert_equal(scale, 10) + + assert_raises(ValueError, stats.uniform.fit, x, floc=2.0) + assert_raises(ValueError, stats.uniform.fit, x, fscale=5.0) + + @pytest.mark.xslow + @pytest.mark.parametrize("method", ["MLE", "MM"]) + def test_fshapes(self, method): + # take a beta distribution, with shapes='a, b', and make sure that + # fa is equivalent to f0, and fb is equivalent to f1 + a, b = 3., 4. + x = stats.beta.rvs(a, b, size=100, random_state=self.rng) + res_1 = stats.beta.fit(x, f0=3., method=method) + res_2 = stats.beta.fit(x, fa=3., method=method) + assert_allclose(res_1, res_2, atol=1e-12, rtol=1e-12) + + res_2 = stats.beta.fit(x, fix_a=3., method=method) + assert_allclose(res_1, res_2, atol=1e-12, rtol=1e-12) + + res_3 = stats.beta.fit(x, f1=4., method=method) + res_4 = stats.beta.fit(x, fb=4., method=method) + assert_allclose(res_3, res_4, atol=1e-12, rtol=1e-12) + + res_4 = stats.beta.fit(x, fix_b=4., method=method) + assert_allclose(res_3, res_4, atol=1e-12, rtol=1e-12) + + # cannot specify both positional and named args at the same time + assert_raises(ValueError, stats.beta.fit, x, fa=1, f0=2, method=method) + + # check that attempting to fix all parameters raises a ValueError + assert_raises(ValueError, stats.beta.fit, x, fa=0, f1=1, + floc=2, fscale=3, method=method) + + # check that specifying floc, fscale and fshapes works for + # beta and gamma which override the generic fit method + res_5 = stats.beta.fit(x, fa=3., floc=0, fscale=1, method=method) + aa, bb, ll, ss = res_5 + assert_equal([aa, ll, ss], [3., 0, 1]) + + # gamma distribution + a = 3. + data = stats.gamma.rvs(a, size=100, random_state=self.rng) + aa, ll, ss = stats.gamma.fit(data, fa=a, method=method) + assert_equal(aa, a) + + @pytest.mark.parametrize("method", ["MLE", "MM"]) + def test_extra_params(self, method): + # unknown parameters should raise rather than be silently ignored + dist = stats.exponnorm + data = dist.rvs(K=2, size=100, random_state=self.rng) + dct = dict(enikibeniki=-101) + assert_raises(TypeError, dist.fit, data, **dct, method=method) + + +class TestFrozen: + # Test that a frozen distribution gives the same results as the original + # object. + # + # Only tested for the normal distribution (with loc and scale specified) + # and for the gamma distribution (with a shape parameter specified). + def test_norm(self): + dist = stats.norm + frozen = stats.norm(loc=10.0, scale=3.0) + + result_f = frozen.pdf(20.0) + result = dist.pdf(20.0, loc=10.0, scale=3.0) + assert_equal(result_f, result) + + result_f = frozen.cdf(20.0) + result = dist.cdf(20.0, loc=10.0, scale=3.0) + assert_equal(result_f, result) + + result_f = frozen.ppf(0.25) + result = dist.ppf(0.25, loc=10.0, scale=3.0) + assert_equal(result_f, result) + + result_f = frozen.isf(0.25) + result = dist.isf(0.25, loc=10.0, scale=3.0) + assert_equal(result_f, result) + + result_f = frozen.sf(10.0) + result = dist.sf(10.0, loc=10.0, scale=3.0) + assert_equal(result_f, result) + + result_f = frozen.median() + result = dist.median(loc=10.0, scale=3.0) + assert_equal(result_f, result) + + result_f = frozen.mean() + result = dist.mean(loc=10.0, scale=3.0) + assert_equal(result_f, result) + + result_f = frozen.var() + result = dist.var(loc=10.0, scale=3.0) + assert_equal(result_f, result) + + result_f = frozen.std() + result = dist.std(loc=10.0, scale=3.0) + assert_equal(result_f, result) + + result_f = frozen.entropy() + result = dist.entropy(loc=10.0, scale=3.0) + assert_equal(result_f, result) + + result_f = frozen.moment(2) + result = dist.moment(2, loc=10.0, scale=3.0) + assert_equal(result_f, result) + + assert_equal(frozen.a, dist.a) + assert_equal(frozen.b, dist.b) + + def test_gamma(self): + a = 2.0 + dist = stats.gamma + frozen = stats.gamma(a) + + result_f = frozen.pdf(20.0) + result = dist.pdf(20.0, a) + assert_equal(result_f, result) + + result_f = frozen.cdf(20.0) + result = dist.cdf(20.0, a) + assert_equal(result_f, result) + + result_f = frozen.ppf(0.25) + result = dist.ppf(0.25, a) + assert_equal(result_f, result) + + result_f = frozen.isf(0.25) + result = dist.isf(0.25, a) + assert_equal(result_f, result) + + result_f = frozen.sf(10.0) + result = dist.sf(10.0, a) + assert_equal(result_f, result) + + result_f = frozen.median() + result = dist.median(a) + assert_equal(result_f, result) + + result_f = frozen.mean() + result = dist.mean(a) + assert_equal(result_f, result) + + result_f = frozen.var() + result = dist.var(a) + assert_equal(result_f, result) + + result_f = frozen.std() + result = dist.std(a) + assert_equal(result_f, result) + + result_f = frozen.entropy() + result = dist.entropy(a) + assert_equal(result_f, result) + + result_f = frozen.moment(2) + result = dist.moment(2, a) + assert_equal(result_f, result) + + assert_equal(frozen.a, frozen.dist.a) + assert_equal(frozen.b, frozen.dist.b) + + def test_regression_ticket_1293(self): + # Create a frozen distribution. + frozen = stats.lognorm(1) + # Call one of its methods that does not take any keyword arguments. + m1 = frozen.moment(2) + # Now call a method that takes a keyword argument. + frozen.stats(moments='mvsk') + # Call moment(2) again. + # After calling stats(), the following was raising an exception. + # So this test passes if the following does not raise an exception. + m2 = frozen.moment(2) + # The following should also be true, of course. But it is not + # the focus of this test. + assert_equal(m1, m2) + + def test_ab(self): + # test that the support of a frozen distribution + # (i) remains frozen even if it changes for the original one + # (ii) is actually correct if the shape parameters are such that + # the values of [a, b] are not the default [0, inf] + # take a genpareto as an example where the support + # depends on the value of the shape parameter: + # for c > 0: a, b = 0, inf + # for c < 0: a, b = 0, -1/c + + c = -0.1 + rv = stats.genpareto(c=c) + a, b = rv.dist._get_support(c) + assert_equal([a, b], [0., 10.]) + + c = 0.1 + stats.genpareto.pdf(0, c=c) + assert_equal(rv.dist._get_support(c), [0, np.inf]) + + c = -0.1 + rv = stats.genpareto(c=c) + a, b = rv.dist._get_support(c) + assert_equal([a, b], [0., 10.]) + + c = 0.1 + stats.genpareto.pdf(0, c) # this should NOT change genpareto.b + assert_equal((rv.dist.a, rv.dist.b), stats.genpareto._get_support(c)) + + rv1 = stats.genpareto(c=0.1) + assert_(rv1.dist is not rv.dist) + + # c >= 0: a, b = [0, inf] + for c in [1., 0.]: + c = np.asarray(c) + rv = stats.genpareto(c=c) + a, b = rv.a, rv.b + assert_equal(a, 0.) + assert_(np.isposinf(b)) + + # c < 0: a=0, b=1/|c| + c = np.asarray(-2.) + a, b = stats.genpareto._get_support(c) + assert_allclose([a, b], [0., 0.5]) + + def test_rv_frozen_in_namespace(self): + # Regression test for gh-3522 + assert_(hasattr(stats.distributions, 'rv_frozen')) + + def test_random_state(self): + # only check that the random_state attribute exists, + frozen = stats.norm() + assert_(hasattr(frozen, 'random_state')) + + # ... that it can be set, + frozen.random_state = 42 + assert_equal(frozen.random_state.get_state(), + np.random.RandomState(42).get_state()) + + # ... and that .rvs method accepts it as an argument + rndm = np.random.RandomState(1234) + frozen.rvs(size=8, random_state=rndm) + + def test_pickling(self): + # test that a frozen instance pickles and unpickles + # (this method is a clone of common_tests.check_pickling) + beta = stats.beta(2.3098496451481823, 0.62687954300963677) + poiss = stats.poisson(3.) + sample = stats.rv_discrete(values=([0, 1, 2, 3], + [0.1, 0.2, 0.3, 0.4])) + + for distfn in [beta, poiss, sample]: + distfn.random_state = 1234 + distfn.rvs(size=8) + s = pickle.dumps(distfn) + r0 = distfn.rvs(size=8) + + unpickled = pickle.loads(s) + r1 = unpickled.rvs(size=8) + assert_equal(r0, r1) + + # also smoke test some methods + medians = [distfn.ppf(0.5), unpickled.ppf(0.5)] + assert_equal(medians[0], medians[1]) + assert_equal(distfn.cdf(medians[0]), + unpickled.cdf(medians[1])) + + def test_expect(self): + # smoke test the expect method of the frozen distribution + # only take a gamma w/loc and scale and poisson with loc specified + def func(x): + return x + + gm = stats.gamma(a=2, loc=3, scale=4) + with np.errstate(invalid="ignore", divide="ignore"): + gm_val = gm.expect(func, lb=1, ub=2, conditional=True) + gamma_val = stats.gamma.expect(func, args=(2,), loc=3, scale=4, + lb=1, ub=2, conditional=True) + assert_allclose(gm_val, gamma_val) + + p = stats.poisson(3, loc=4) + p_val = p.expect(func) + poisson_val = stats.poisson.expect(func, args=(3,), loc=4) + assert_allclose(p_val, poisson_val) + + +class TestExpect: + # Test for expect method. + # + # Uses normal distribution and beta distribution for finite bounds, and + # hypergeom for discrete distribution with finite support + def test_norm(self): + v = stats.norm.expect(lambda x: (x-5)*(x-5), loc=5, scale=2) + assert_almost_equal(v, 4, decimal=14) + + m = stats.norm.expect(lambda x: (x), loc=5, scale=2) + assert_almost_equal(m, 5, decimal=14) + + lb = stats.norm.ppf(0.05, loc=5, scale=2) + ub = stats.norm.ppf(0.95, loc=5, scale=2) + prob90 = stats.norm.expect(lambda x: 1, loc=5, scale=2, lb=lb, ub=ub) + assert_almost_equal(prob90, 0.9, decimal=14) + + prob90c = stats.norm.expect(lambda x: 1, loc=5, scale=2, lb=lb, ub=ub, + conditional=True) + assert_almost_equal(prob90c, 1., decimal=14) + + def test_beta(self): + # case with finite support interval + v = stats.beta.expect(lambda x: (x-19/3.)*(x-19/3.), args=(10, 5), + loc=5, scale=2) + assert_almost_equal(v, 1./18., decimal=13) + + m = stats.beta.expect(lambda x: x, args=(10, 5), loc=5., scale=2.) + assert_almost_equal(m, 19/3., decimal=13) + + ub = stats.beta.ppf(0.95, 10, 10, loc=5, scale=2) + lb = stats.beta.ppf(0.05, 10, 10, loc=5, scale=2) + prob90 = stats.beta.expect(lambda x: 1., args=(10, 10), loc=5., + scale=2., lb=lb, ub=ub, conditional=False) + assert_almost_equal(prob90, 0.9, decimal=13) + + prob90c = stats.beta.expect(lambda x: 1, args=(10, 10), loc=5, + scale=2, lb=lb, ub=ub, conditional=True) + assert_almost_equal(prob90c, 1., decimal=13) + + def test_hypergeom(self): + # test case with finite bounds + + # without specifying bounds + m_true, v_true = stats.hypergeom.stats(20, 10, 8, loc=5.) + m = stats.hypergeom.expect(lambda x: x, args=(20, 10, 8), loc=5.) + assert_almost_equal(m, m_true, decimal=13) + + v = stats.hypergeom.expect(lambda x: (x-9.)**2, args=(20, 10, 8), + loc=5.) + assert_almost_equal(v, v_true, decimal=14) + + # with bounds, bounds equal to shifted support + v_bounds = stats.hypergeom.expect(lambda x: (x-9.)**2, + args=(20, 10, 8), + loc=5., lb=5, ub=13) + assert_almost_equal(v_bounds, v_true, decimal=14) + + # drop boundary points + prob_true = 1-stats.hypergeom.pmf([5, 13], 20, 10, 8, loc=5).sum() + prob_bounds = stats.hypergeom.expect(lambda x: 1, args=(20, 10, 8), + loc=5., lb=6, ub=12) + assert_almost_equal(prob_bounds, prob_true, decimal=13) + + # conditional + prob_bc = stats.hypergeom.expect(lambda x: 1, args=(20, 10, 8), loc=5., + lb=6, ub=12, conditional=True) + assert_almost_equal(prob_bc, 1, decimal=14) + + # check simple integral + prob_b = stats.hypergeom.expect(lambda x: 1, args=(20, 10, 8), + lb=0, ub=8) + assert_almost_equal(prob_b, 1, decimal=13) + + def test_poisson(self): + # poisson, use lower bound only + prob_bounds = stats.poisson.expect(lambda x: 1, args=(2,), lb=3, + conditional=False) + prob_b_true = 1-stats.poisson.cdf(2, 2) + assert_almost_equal(prob_bounds, prob_b_true, decimal=14) + + prob_lb = stats.poisson.expect(lambda x: 1, args=(2,), lb=2, + conditional=True) + assert_almost_equal(prob_lb, 1, decimal=14) + + def test_genhalflogistic(self): + # genhalflogistic, changes upper bound of support in _argcheck + # regression test for gh-2622 + halflog = stats.genhalflogistic + # check consistency when calling expect twice with the same input + res1 = halflog.expect(args=(1.5,)) + halflog.expect(args=(0.5,)) + res2 = halflog.expect(args=(1.5,)) + assert_almost_equal(res1, res2, decimal=14) + + def test_rice_overflow(self): + # rice.pdf(999, 0.74) was inf since special.i0 silently overflows + # check that using i0e fixes it + assert_(np.isfinite(stats.rice.pdf(999, 0.74))) + + assert_(np.isfinite(stats.rice.expect(lambda x: 1, args=(0.74,)))) + assert_(np.isfinite(stats.rice.expect(lambda x: 2, args=(0.74,)))) + assert_(np.isfinite(stats.rice.expect(lambda x: 3, args=(0.74,)))) + + def test_logser(self): + # test a discrete distribution with infinite support and loc + p, loc = 0.3, 3 + res_0 = stats.logser.expect(lambda k: k, args=(p,)) + # check against the correct answer (sum of a geom series) + assert_allclose(res_0, + p / (p - 1.) / np.log(1. - p), atol=1e-15) + + # now check it with `loc` + res_l = stats.logser.expect(lambda k: k, args=(p,), loc=loc) + assert_allclose(res_l, res_0 + loc, atol=1e-15) + + def test_skellam(self): + # Use a discrete distribution w/ bi-infinite support. Compute two first + # moments and compare to known values (cf skellam.stats) + p1, p2 = 18, 22 + m1 = stats.skellam.expect(lambda x: x, args=(p1, p2)) + m2 = stats.skellam.expect(lambda x: x**2, args=(p1, p2)) + assert_allclose(m1, p1 - p2, atol=1e-12) + assert_allclose(m2 - m1**2, p1 + p2, atol=1e-12) + + def test_randint(self): + # Use a discrete distribution w/ parameter-dependent support, which + # is larger than the default chunksize + lo, hi = 0, 113 + res = stats.randint.expect(lambda x: x, (lo, hi)) + assert_allclose(res, + sum(_ for _ in range(lo, hi)) / (hi - lo), atol=1e-15) + + def test_zipf(self): + # Test that there is no infinite loop even if the sum diverges + with pytest.warns(RuntimeWarning): + stats.zipf.expect(lambda x: x**2, (2,)) + + def test_discrete_kwds(self): + # check that discrete expect accepts keywords to control the summation + n0 = stats.poisson.expect(lambda x: 1, args=(2,)) + n1 = stats.poisson.expect(lambda x: 1, args=(2,), + maxcount=1001, chunksize=32, tolerance=1e-8) + assert_almost_equal(n0, n1, decimal=14) + + def test_moment(self): + # test the .moment() method: compute a higher moment and compare to + # a known value + def poiss_moment5(mu): + return mu**5 + 10*mu**4 + 25*mu**3 + 15*mu**2 + mu + + for mu in [5, 7]: + m5 = stats.poisson.moment(5, mu) + assert_allclose(m5, poiss_moment5(mu), rtol=1e-10) + + def test_challenging_cases_gh8928(self): + # Several cases where `expect` failed to produce a correct result were + # reported in gh-8928. Check that these cases have been resolved. + assert_allclose(stats.norm.expect(loc=36, scale=1.0), 36) + assert_allclose(stats.norm.expect(loc=40, scale=1.0), 40) + assert_allclose(stats.norm.expect(loc=10, scale=0.1), 10) + assert_allclose(stats.gamma.expect(args=(148,)), 148) + assert_allclose(stats.logistic.expect(loc=85), 85) + + def test_lb_ub_gh15855(self): + # Make sure changes to `expect` made in gh15855 treat lb/ub correctly + dist = stats.uniform + ref = dist.mean(loc=10, scale=5) # 12.5 + # moment over whole distribution + assert_allclose(dist.expect(loc=10, scale=5), ref) + # moment over whole distribution, lb and ub outside of support + assert_allclose(dist.expect(loc=10, scale=5, lb=9, ub=16), ref) + # moment over 60% of distribution, [lb, ub] centered within support + assert_allclose(dist.expect(loc=10, scale=5, lb=11, ub=14), ref*0.6) + # moment over truncated distribution, essentially + assert_allclose(dist.expect(loc=10, scale=5, lb=11, ub=14, + conditional=True), ref) + # moment over 40% of distribution, [lb, ub] not centered within support + assert_allclose(dist.expect(loc=10, scale=5, lb=11, ub=13), 12*0.4) + # moment with lb > ub + assert_allclose(dist.expect(loc=10, scale=5, lb=13, ub=11), -12*0.4) + # moment with lb > ub, conditional + assert_allclose(dist.expect(loc=10, scale=5, lb=13, ub=11, + conditional=True), 12) + + +class TestNct: + def test_nc_parameter(self): + # Parameter values c<=0 were not enabled (gh-2402). + # For negative values c and for c=0 results of rv.cdf(0) below were nan + rv = stats.nct(5, 0) + assert_equal(rv.cdf(0), 0.5) + rv = stats.nct(5, -1) + assert_almost_equal(rv.cdf(0), 0.841344746069, decimal=10) + + def test_broadcasting(self): + res = stats.nct.pdf(5, np.arange(4, 7)[:, None], + np.linspace(0.1, 1, 4)) + expected = array([[0.00321886, 0.00557466, 0.00918418, 0.01442997], + [0.00217142, 0.00395366, 0.00683888, 0.01126276], + [0.00153078, 0.00291093, 0.00525206, 0.00900815]]) + assert_allclose(res, expected, rtol=1e-5) + + def test_variance_gh_issue_2401(self): + # Computation of the variance of a non-central t-distribution resulted + # in a TypeError: ufunc 'isinf' not supported for the input types, + # and the inputs could not be safely coerced to any supported types + # according to the casting rule 'safe' + rv = stats.nct(4, 0) + assert_equal(rv.var(), 2.0) + + def test_nct_inf_moments(self): + # n-th moment of nct only exists for df > n + m, v, s, k = stats.nct.stats(df=0.9, nc=0.3, moments='mvsk') + assert_equal([m, v, s, k], [np.nan, np.nan, np.nan, np.nan]) + + m, v, s, k = stats.nct.stats(df=1.9, nc=0.3, moments='mvsk') + assert_(np.isfinite(m)) + assert_equal([v, s, k], [np.nan, np.nan, np.nan]) + + m, v, s, k = stats.nct.stats(df=3.1, nc=0.3, moments='mvsk') + assert_(np.isfinite([m, v, s]).all()) + assert_equal(k, np.nan) + + def test_nct_stats_large_df_values(self): + # previously gamma function was used which lost precision at df=345 + # cf. https://github.com/scipy/scipy/issues/12919 for details + nct_mean_df_1000 = stats.nct.mean(1000, 2) + nct_stats_df_1000 = stats.nct.stats(1000, 2) + # These expected values were computed with mpmath. They were also + # verified with the Wolfram Alpha expressions: + # Mean[NoncentralStudentTDistribution[1000, 2]] + # Var[NoncentralStudentTDistribution[1000, 2]] + expected_stats_df_1000 = [2.0015015641422464, 1.0040115288163005] + assert_allclose(nct_mean_df_1000, expected_stats_df_1000[0], + rtol=1e-10) + assert_allclose(nct_stats_df_1000, expected_stats_df_1000, + rtol=1e-10) + # and a bigger df value + nct_mean = stats.nct.mean(100000, 2) + nct_stats = stats.nct.stats(100000, 2) + # These expected values were computed with mpmath. + expected_stats = [2.0000150001562518, 1.0000400011500288] + assert_allclose(nct_mean, expected_stats[0], rtol=1e-10) + assert_allclose(nct_stats, expected_stats, rtol=1e-9) + + def test_cdf_large_nc(self): + # gh-17916 reported a crash with large `nc` values + assert_allclose(stats.nct.cdf(2, 2, float(2**16)), 0) + + # PDF reference values were computed with mpmath + # with 100 digits of precision + + # def nct_pdf(x, df, nc): + # x = mp.mpf(x) + # n = mp.mpf(df) + # nc = mp.mpf(nc) + + # x2 = x*x + # ncx2 = nc*nc*x2 + # fac1 = n + x2 + # trm1 = (n/2.*mp.log(n) + mp.loggamma(n + mp.one) + # - (n * mp.log(2.) + nc*nc/2 + (n/2)*mp.log(fac1) + # + mp.loggamma(n/2))) + # Px = mp.exp(trm1) + # valF = ncx2 / (2*fac1) + # trm1 = (mp.sqrt(2)*nc*x*mp.hyp1f1(n/2+1, 1.5, valF) + # / (fac1*mp.gamma((n+1)/2))) + # trm2 = (mp.hyp1f1((n+1)/2, 0.5, valF) + # / (mp.sqrt(fac1)*mp.gamma(n/2 + mp.one))) + # Px *= trm1+trm2 + # return float(Px) + + @pytest.mark.parametrize("x, df, nc, expected", [ + (10000, 10, 16, 3.394646922945872e-30), + (-10, 8, 16, 4.282769500264159e-70) + ]) + def test_pdf_large_nc(self, x, df, nc, expected): + # gh-#20693 reported zero values for large `nc` values + assert_allclose(stats.nct.pdf(x, df, nc), expected, rtol=1e-12) + + +class TestRecipInvGauss: + + def test_pdf_endpoint(self): + p = stats.recipinvgauss.pdf(0, 0.6) + assert p == 0.0 + + def test_logpdf_endpoint(self): + logp = stats.recipinvgauss.logpdf(0, 0.6) + assert logp == -np.inf + + def test_cdf_small_x(self): + # The expected value was computer with mpmath: + # + # import mpmath + # + # mpmath.mp.dps = 100 + # + # def recipinvgauss_cdf_mp(x, mu): + # x = mpmath.mpf(x) + # mu = mpmath.mpf(mu) + # trm1 = 1/mu - x + # trm2 = 1/mu + x + # isqx = 1/mpmath.sqrt(x) + # return (mpmath.ncdf(-isqx*trm1) + # - mpmath.exp(2/mu)*mpmath.ncdf(-isqx*trm2)) + # + p = stats.recipinvgauss.cdf(0.05, 0.5) + expected = 6.590396159501331e-20 + assert_allclose(p, expected, rtol=1e-14) + + def test_sf_large_x(self): + # The expected value was computed with mpmath; see test_cdf_small. + p = stats.recipinvgauss.sf(80, 0.5) + expected = 2.699819200556787e-18 + assert_allclose(p, expected, 5e-15) + + +class TestRice: + def setup_method(self): + self.rng = np.random.default_rng(666822542) + + def test_rice_zero_b(self): + # rice distribution should work with b=0, cf gh-2164 + x = [0.2, 1., 5.] + assert_(np.isfinite(stats.rice.pdf(x, b=0.)).all()) + assert_(np.isfinite(stats.rice.logpdf(x, b=0.)).all()) + assert_(np.isfinite(stats.rice.cdf(x, b=0.)).all()) + assert_(np.isfinite(stats.rice.logcdf(x, b=0.)).all()) + + q = [0.1, 0.1, 0.5, 0.9] + assert_(np.isfinite(stats.rice.ppf(q, b=0.)).all()) + + mvsk = stats.rice.stats(0, moments='mvsk') + assert_(np.isfinite(mvsk).all()) + + # furthermore, pdf is continuous as b\to 0 + # rice.pdf(x, b\to 0) = x exp(-x^2/2) + O(b^2) + # see e.g. Abramovich & Stegun 9.6.7 & 9.6.10 + b = 1e-8 + assert_allclose(stats.rice.pdf(x, 0), stats.rice.pdf(x, b), + atol=b, rtol=0) + + def test_rice_rvs(self): + rvs = stats.rice.rvs + assert_equal(rvs(b=3., random_state=self.rng).size, 1) + assert_equal(rvs(b=3., size=(3, 5), random_state=self.rng).shape, (3, 5)) + + def test_rice_gh9836(self): + # test that gh-9836 is resolved; previously jumped to 1 at the end + + cdf = stats.rice.cdf(np.arange(10, 160, 10), np.arange(10, 160, 10)) + # Generated in R + # library(VGAM) + # options(digits=16) + # x = seq(10, 150, 10) + # print(price(x, sigma=1, vee=x)) + cdf_exp = [0.4800278103504522, 0.4900233218590353, 0.4933500379379548, + 0.4950128317658719, 0.4960103776798502, 0.4966753655438764, + 0.4971503395812474, 0.4975065620443196, 0.4977836197921638, + 0.4980052636649550, 0.4981866072661382, 0.4983377260666599, + 0.4984655952615694, 0.4985751970541413, 0.4986701850071265] + assert_allclose(cdf, cdf_exp) + + probabilities = np.arange(0.1, 1, 0.1) + ppf = stats.rice.ppf(probabilities, 500/4, scale=4) + # Generated in R + # library(VGAM) + # options(digits=16) + # p = seq(0.1, .9, by = .1) + # print(qrice(p, vee = 500, sigma = 4)) + ppf_exp = [494.8898762347361, 496.6495690858350, 497.9184315188069, + 499.0026277378915, 500.0159999146250, 501.0293721352668, + 502.1135684981884, 503.3824312270405, 505.1421247157822] + assert_allclose(ppf, ppf_exp) + + ppf = scipy.stats.rice.ppf(0.5, np.arange(10, 150, 10)) + # Generated in R + # library(VGAM) + # options(digits=16) + # b <- seq(10, 140, 10) + # print(qrice(0.5, vee = b, sigma = 1)) + ppf_exp = [10.04995862522287, 20.02499480078302, 30.01666512465732, + 40.01249934924363, 50.00999966676032, 60.00833314046875, + 70.00714273568241, 80.00624991862573, 90.00555549840364, + 100.00499995833597, 110.00454542324384, 120.00416664255323, + 130.00384613488120, 140.00357141338748] + assert_allclose(ppf, ppf_exp) + + +class TestErlang: + def setup_method(self): + self.rng = np.random.default_rng(2792245532) + + def test_erlang_runtimewarning(self): + # erlang should generate a RuntimeWarning if a non-integer + # shape parameter is used. + with warnings.catch_warnings(): + warnings.simplefilter("error", RuntimeWarning) + + # The non-integer shape parameter 1.3 should trigger a + # RuntimeWarning + assert_raises(RuntimeWarning, stats.erlang.rvs, 1.3, loc=0, + scale=1, size=4, random_state=self.rng) + + # Calling the fit method with `f0` set to an integer should + # *not* trigger a RuntimeWarning. It should return the same + # values as gamma.fit(...). + data = [0.5, 1.0, 2.0, 4.0] + result_erlang = stats.erlang.fit(data, f0=1) + result_gamma = stats.gamma.fit(data, f0=1) + assert_allclose(result_erlang, result_gamma, rtol=1e-3) + + def test_gh_pr_10949_argcheck(self): + assert_equal(stats.erlang.pdf(0.5, a=[1, -1]), + stats.gamma.pdf(0.5, a=[1, -1])) + + +class TestRayleigh: + def setup_method(self): + self.rng = np.random.default_rng(7186715712) + + # gh-6227 + def test_logpdf(self): + y = stats.rayleigh.logpdf(50) + assert_allclose(y, -1246.0879769945718) + + def test_logsf(self): + y = stats.rayleigh.logsf(50) + assert_allclose(y, -1250) + + @pytest.mark.parametrize("rvs_loc,rvs_scale", [(0.85373171, 0.86932204), + (0.20558821, 0.61621008)]) + def test_fit(self, rvs_loc, rvs_scale): + data = stats.rayleigh.rvs(size=250, loc=rvs_loc, + scale=rvs_scale, random_state=self.rng) + + def scale_mle(data, floc): + return (np.sum((data - floc) ** 2) / (2 * len(data))) ** .5 + + # when `floc` is provided, `scale` is found with an analytical formula + scale_expect = scale_mle(data, rvs_loc) + loc, scale = stats.rayleigh.fit(data, floc=rvs_loc) + assert_equal(loc, rvs_loc) + assert_equal(scale, scale_expect) + + # when `fscale` is fixed, superclass fit is used to determine `loc`. + loc, scale = stats.rayleigh.fit(data, fscale=.6) + assert_equal(scale, .6) + + # with both parameters free, one dimensional optimization is done + # over a new function that takes into account the dependent relation + # of `scale` to `loc`. + loc, scale = stats.rayleigh.fit(data) + # test that `scale` is defined by its relation to `loc` + assert_equal(scale, scale_mle(data, loc)) + + @pytest.mark.parametrize("rvs_loc,rvs_scale", [[0.74, 0.01], + [0.08464463, 0.12069025]]) + def test_fit_comparison_super_method(self, rvs_loc, rvs_scale): + # test that the objective function result of the analytical MLEs is + # less than or equal to that of the numerically optimized estimate + data = stats.rayleigh.rvs(size=250, loc=rvs_loc, + scale=rvs_scale, random_state=self.rng) + _assert_less_or_close_loglike(stats.rayleigh, data) + + def test_fit_warnings(self): + assert_fit_warnings(stats.rayleigh) + + def test_fit_gh17088(self): + # `rayleigh.fit` could return a location that was inconsistent with + # the data. See gh-17088. + rng = np.random.default_rng(456) + loc, scale, size = 50, 600, 500 + rvs = stats.rayleigh.rvs(loc, scale, size=size, random_state=rng) + loc_fit, _ = stats.rayleigh.fit(rvs) + assert loc_fit < np.min(rvs) + loc_fit, scale_fit = stats.rayleigh.fit(rvs, fscale=scale) + assert loc_fit < np.min(rvs) + assert scale_fit == scale + + +class TestExponWeib: + + def test_pdf_logpdf(self): + # Regression test for gh-3508. + x = 0.1 + a = 1.0 + c = 100.0 + p = stats.exponweib.pdf(x, a, c) + logp = stats.exponweib.logpdf(x, a, c) + # Expected values were computed with mpmath. + assert_allclose([p, logp], + [1.0000000000000054e-97, -223.35075402042244]) + + def test_a_is_1(self): + # For issue gh-3508. + # Check that when a=1, the pdf and logpdf methods of exponweib are the + # same as those of weibull_min. + x = np.logspace(-4, -1, 4) + a = 1 + c = 100 + + p = stats.exponweib.pdf(x, a, c) + expected = stats.weibull_min.pdf(x, c) + assert_allclose(p, expected) + + logp = stats.exponweib.logpdf(x, a, c) + expected = stats.weibull_min.logpdf(x, c) + assert_allclose(logp, expected) + + def test_a_is_1_c_is_1(self): + # When a = 1 and c = 1, the distribution is exponential. + x = np.logspace(-8, 1, 10) + a = 1 + c = 1 + + p = stats.exponweib.pdf(x, a, c) + expected = stats.expon.pdf(x) + assert_allclose(p, expected) + + logp = stats.exponweib.logpdf(x, a, c) + expected = stats.expon.logpdf(x) + assert_allclose(logp, expected) + + # Reference values were computed with mpmath, e.g: + # + # from mpmath import mp + # + # def mp_sf(x, a, c): + # x = mp.mpf(x) + # a = mp.mpf(a) + # c = mp.mpf(c) + # return -mp.powm1(-mp.expm1(-x**c)), a) + # + # mp.dps = 100 + # print(float(mp_sf(1, 2.5, 0.75))) + # + # prints + # + # 0.6823127476985246 + # + @pytest.mark.parametrize( + 'x, a, c, ref', + [(1, 2.5, 0.75, 0.6823127476985246), + (50, 2.5, 0.75, 1.7056666054719663e-08), + (125, 2.5, 0.75, 1.4534393150714602e-16), + (250, 2.5, 0.75, 1.2391389689773512e-27), + (250, 0.03125, 0.75, 1.548923711221689e-29), + (3, 0.03125, 3.0, 5.873527551689983e-14), + (2e80, 10.0, 0.02, 2.9449084156902135e-17)] + ) + def test_sf(self, x, a, c, ref): + sf = stats.exponweib.sf(x, a, c) + assert_allclose(sf, ref, rtol=1e-14) + + # Reference values were computed with mpmath, e.g. + # + # from mpmath import mp + # + # def mp_isf(p, a, c): + # p = mp.mpf(p) + # a = mp.mpf(a) + # c = mp.mpf(c) + # return (-mp.log(-mp.expm1(mp.log1p(-p)/a)))**(1/c) + # + # mp.dps = 100 + # print(float(mp_isf(0.25, 2.5, 0.75))) + # + # prints + # + # 2.8946008178158924 + # + @pytest.mark.parametrize( + 'p, a, c, ref', + [(0.25, 2.5, 0.75, 2.8946008178158924), + (3e-16, 2.5, 0.75, 121.77966713102938), + (1e-12, 1, 2, 5.256521769756932), + (2e-13, 0.03125, 3, 2.953915059484589), + (5e-14, 10.0, 0.02, 7.57094886384687e+75)] + ) + def test_isf(self, p, a, c, ref): + isf = stats.exponweib.isf(p, a, c) + assert_allclose(isf, ref, rtol=5e-14) + + # Reference values computed with mpmath. + @pytest.mark.parametrize('x, a, c, ref', + [(2, 3, 8, -1.9848783170128456e-111), + (1000, 0.5, 0.75, -2.946296827524972e-78)]) + def test_logcdf(self, x, a, c, ref): + logcdf = stats.exponweib.logcdf(x, a, c) + assert_allclose(logcdf, ref, rtol=5e-15) + + # Reference values computed with mpmath. + @pytest.mark.parametrize('x, a, c, ref', + [(1e-65, 1.5, 1.25, -1.333521432163324e-122), + (2e-10, 2, 10, -1.0485760000000007e-194)]) + def test_logsf(self, x, a, c, ref): + logsf = stats.exponweib.logsf(x, a, c) + assert_allclose(logsf, ref, rtol=5e-15) + + +class TestFatigueLife: + + def test_sf_tail(self): + # Expected value computed with mpmath: + # import mpmath + # mpmath.mp.dps = 80 + # x = mpmath.mpf(800.0) + # c = mpmath.mpf(2.5) + # s = float(1 - mpmath.ncdf(1/c * (mpmath.sqrt(x) + # - 1/mpmath.sqrt(x)))) + # print(s) + # Output: + # 6.593376447038406e-30 + s = stats.fatiguelife.sf(800.0, 2.5) + assert_allclose(s, 6.593376447038406e-30, rtol=1e-13) + + def test_isf_tail(self): + # See test_sf_tail for the mpmath code. + p = 6.593376447038406e-30 + q = stats.fatiguelife.isf(p, 2.5) + assert_allclose(q, 800.0, rtol=1e-13) + + +class TestWeibull: + + def test_logpdf(self): + # gh-6217 + y = stats.weibull_min.logpdf(0, 1) + assert_equal(y, 0) + + def test_with_maxima_distrib(self): + # Tests for weibull_min and weibull_max. + # The expected values were computed using the symbolic algebra + # program 'maxima' with the package 'distrib', which has + # 'pdf_weibull' and 'cdf_weibull'. The mapping between the + # scipy and maxima functions is as follows: + # ----------------------------------------------------------------- + # scipy maxima + # --------------------------------- ------------------------------ + # weibull_min.pdf(x, a, scale=b) pdf_weibull(x, a, b) + # weibull_min.logpdf(x, a, scale=b) log(pdf_weibull(x, a, b)) + # weibull_min.cdf(x, a, scale=b) cdf_weibull(x, a, b) + # weibull_min.logcdf(x, a, scale=b) log(cdf_weibull(x, a, b)) + # weibull_min.sf(x, a, scale=b) 1 - cdf_weibull(x, a, b) + # weibull_min.logsf(x, a, scale=b) log(1 - cdf_weibull(x, a, b)) + # + # weibull_max.pdf(x, a, scale=b) pdf_weibull(-x, a, b) + # weibull_max.logpdf(x, a, scale=b) log(pdf_weibull(-x, a, b)) + # weibull_max.cdf(x, a, scale=b) 1 - cdf_weibull(-x, a, b) + # weibull_max.logcdf(x, a, scale=b) log(1 - cdf_weibull(-x, a, b)) + # weibull_max.sf(x, a, scale=b) cdf_weibull(-x, a, b) + # weibull_max.logsf(x, a, scale=b) log(cdf_weibull(-x, a, b)) + # ----------------------------------------------------------------- + x = 1.5 + a = 2.0 + b = 3.0 + + # weibull_min + + p = stats.weibull_min.pdf(x, a, scale=b) + assert_allclose(p, np.exp(-0.25)/3) + + lp = stats.weibull_min.logpdf(x, a, scale=b) + assert_allclose(lp, -0.25 - np.log(3)) + + c = stats.weibull_min.cdf(x, a, scale=b) + assert_allclose(c, -special.expm1(-0.25)) + + lc = stats.weibull_min.logcdf(x, a, scale=b) + assert_allclose(lc, np.log(-special.expm1(-0.25))) + + s = stats.weibull_min.sf(x, a, scale=b) + assert_allclose(s, np.exp(-0.25)) + + ls = stats.weibull_min.logsf(x, a, scale=b) + assert_allclose(ls, -0.25) + + # Also test using a large value x, for which computing the survival + # function using the CDF would result in 0. + s = stats.weibull_min.sf(30, 2, scale=3) + assert_allclose(s, np.exp(-100)) + + ls = stats.weibull_min.logsf(30, 2, scale=3) + assert_allclose(ls, -100) + + # weibull_max + x = -1.5 + + p = stats.weibull_max.pdf(x, a, scale=b) + assert_allclose(p, np.exp(-0.25)/3) + + lp = stats.weibull_max.logpdf(x, a, scale=b) + assert_allclose(lp, -0.25 - np.log(3)) + + c = stats.weibull_max.cdf(x, a, scale=b) + assert_allclose(c, np.exp(-0.25)) + + lc = stats.weibull_max.logcdf(x, a, scale=b) + assert_allclose(lc, -0.25) + + s = stats.weibull_max.sf(x, a, scale=b) + assert_allclose(s, -special.expm1(-0.25)) + + ls = stats.weibull_max.logsf(x, a, scale=b) + assert_allclose(ls, np.log(-special.expm1(-0.25))) + + # Also test using a value of x close to 0, for which computing the + # survival function using the CDF would result in 0. + s = stats.weibull_max.sf(-1e-9, 2, scale=3) + assert_allclose(s, -special.expm1(-1/9000000000000000000)) + + ls = stats.weibull_max.logsf(-1e-9, 2, scale=3) + assert_allclose(ls, np.log(-special.expm1(-1/9000000000000000000))) + + @pytest.mark.parametrize('scale', [1.0, 0.1]) + def test_delta_cdf(self, scale): + # Expected value computed with mpmath: + # + # def weibull_min_sf(x, k, scale): + # x = mpmath.mpf(x) + # k = mpmath.mpf(k) + # scale =mpmath.mpf(scale) + # return mpmath.exp(-(x/scale)**k) + # + # >>> import mpmath + # >>> mpmath.mp.dps = 60 + # >>> sf1 = weibull_min_sf(7.5, 3, 1) + # >>> sf2 = weibull_min_sf(8.0, 3, 1) + # >>> float(sf1 - sf2) + # 6.053624060118734e-184 + # + delta = stats.weibull_min._delta_cdf(scale*7.5, scale*8, 3, + scale=scale) + assert_allclose(delta, 6.053624060118734e-184) + + def test_fit_min(self): + rng = np.random.default_rng(5985959307161735394) + + c, loc, scale = 2, 3.5, 0.5 # arbitrary, valid parameters + dist = stats.weibull_min(c, loc, scale) + rvs = dist.rvs(size=100, random_state=rng) + + # test that MLE still honors guesses and fixed parameters + c2, loc2, scale2 = stats.weibull_min.fit(rvs, 1.5, floc=3) + c3, loc3, scale3 = stats.weibull_min.fit(rvs, 1.6, floc=3) + assert loc2 == loc3 == 3 # fixed parameter is respected + assert c2 != c3 # different guess -> (slightly) different outcome + # quality of fit is tested elsewhere + + # test that MoM honors fixed parameters, accepts (but ignores) guesses + c4, loc4, scale4 = stats.weibull_min.fit(rvs, 3, fscale=3, method='mm') + assert scale4 == 3 + # because scale was fixed, only the mean and skewness will be matched + dist4 = stats.weibull_min(c4, loc4, scale4) + res = dist4.stats(moments='ms') + ref = np.mean(rvs), stats.skew(rvs) + assert_allclose(res, ref) + + # reference values were computed via mpmath + # from mpmath import mp + # def weibull_sf_mpmath(x, c): + # x = mp.mpf(x) + # c = mp.mpf(c) + # return float(mp.exp(-x**c)) + + @pytest.mark.parametrize('x, c, ref', [(50, 1, 1.9287498479639178e-22), + (1000, 0.8, + 8.131269637872743e-110)]) + def test_sf_isf(self, x, c, ref): + assert_allclose(stats.weibull_min.sf(x, c), ref, rtol=5e-14) + assert_allclose(stats.weibull_min.isf(ref, c), x, rtol=5e-14) + + +class TestDweibull: + def test_entropy(self): + # Test that dweibull entropy follows that of weibull_min. + # (Generic tests check that the dweibull entropy is consistent + # with its PDF. As for accuracy, dweibull entropy should be just + # as accurate as weibull_min entropy. Checks of accuracy against + # a reference need only be applied to the fundamental distribution - + # weibull_min.) + rng = np.random.default_rng(8486259129157041777) + c = 10**rng.normal(scale=100, size=10) + res = stats.dweibull.entropy(c) + ref = stats.weibull_min.entropy(c) - np.log(0.5) + assert_allclose(res, ref, rtol=1e-15) + + def test_sf(self): + # test that for positive values the dweibull survival function is half + # the weibull_min survival function + rng = np.random.default_rng(8486259129157041777) + c = 10**rng.normal(scale=1, size=10) + x = 10 * rng.uniform() + res = stats.dweibull.sf(x, c) + ref = 0.5 * stats.weibull_min.sf(x, c) + assert_allclose(res, ref, rtol=1e-15) + + +class TestTruncWeibull: + + def test_pdf_bounds(self): + # test bounds + y = stats.truncweibull_min.pdf([0.1, 2.0], 2.0, 0.11, 1.99) + assert_equal(y, [0.0, 0.0]) + + def test_logpdf(self): + y = stats.truncweibull_min.logpdf(2.0, 1.0, 2.0, np.inf) + assert_equal(y, 0.0) + + # hand calculation + y = stats.truncweibull_min.logpdf(2.0, 1.0, 2.0, 4.0) + assert_allclose(y, 0.14541345786885884) + + def test_ppf_bounds(self): + # test bounds + y = stats.truncweibull_min.ppf([0.0, 1.0], 2.0, 0.1, 2.0) + assert_equal(y, [0.1, 2.0]) + + def test_cdf_to_ppf(self): + q = [0., 0.1, .25, 0.50, 0.75, 0.90, 1.] + x = stats.truncweibull_min.ppf(q, 2., 0., 3.) + q_out = stats.truncweibull_min.cdf(x, 2., 0., 3.) + assert_allclose(q, q_out) + + def test_sf_to_isf(self): + q = [0., 0.1, .25, 0.50, 0.75, 0.90, 1.] + x = stats.truncweibull_min.isf(q, 2., 0., 3.) + q_out = stats.truncweibull_min.sf(x, 2., 0., 3.) + assert_allclose(q, q_out) + + def test_munp(self): + c = 2. + a = 1. + b = 3. + + def xnpdf(x, n): + return x**n*stats.truncweibull_min.pdf(x, c, a, b) + + m0 = stats.truncweibull_min.moment(0, c, a, b) + assert_equal(m0, 1.) + + m1 = stats.truncweibull_min.moment(1, c, a, b) + m1_expected, _ = quad(lambda x: xnpdf(x, 1), a, b) + assert_allclose(m1, m1_expected) + + m2 = stats.truncweibull_min.moment(2, c, a, b) + m2_expected, _ = quad(lambda x: xnpdf(x, 2), a, b) + assert_allclose(m2, m2_expected) + + m3 = stats.truncweibull_min.moment(3, c, a, b) + m3_expected, _ = quad(lambda x: xnpdf(x, 3), a, b) + assert_allclose(m3, m3_expected) + + m4 = stats.truncweibull_min.moment(4, c, a, b) + m4_expected, _ = quad(lambda x: xnpdf(x, 4), a, b) + assert_allclose(m4, m4_expected) + + def test_reference_values(self): + a = 1. + b = 3. + c = 2. + x_med = np.sqrt(1 - np.log(0.5 + np.exp(-(8. + np.log(2.))))) + + cdf = stats.truncweibull_min.cdf(x_med, c, a, b) + assert_allclose(cdf, 0.5) + + lc = stats.truncweibull_min.logcdf(x_med, c, a, b) + assert_allclose(lc, -np.log(2.)) + + ppf = stats.truncweibull_min.ppf(0.5, c, a, b) + assert_allclose(ppf, x_med) + + sf = stats.truncweibull_min.sf(x_med, c, a, b) + assert_allclose(sf, 0.5) + + ls = stats.truncweibull_min.logsf(x_med, c, a, b) + assert_allclose(ls, -np.log(2.)) + + isf = stats.truncweibull_min.isf(0.5, c, a, b) + assert_allclose(isf, x_med) + + def test_compare_weibull_min(self): + # Verify that the truncweibull_min distribution gives the same results + # as the original weibull_min + x = 1.5 + c = 2.0 + a = 0.0 + b = np.inf + scale = 3.0 + + p = stats.weibull_min.pdf(x, c, scale=scale) + p_trunc = stats.truncweibull_min.pdf(x, c, a, b, scale=scale) + assert_allclose(p, p_trunc) + + lp = stats.weibull_min.logpdf(x, c, scale=scale) + lp_trunc = stats.truncweibull_min.logpdf(x, c, a, b, scale=scale) + assert_allclose(lp, lp_trunc) + + cdf = stats.weibull_min.cdf(x, c, scale=scale) + cdf_trunc = stats.truncweibull_min.cdf(x, c, a, b, scale=scale) + assert_allclose(cdf, cdf_trunc) + + lc = stats.weibull_min.logcdf(x, c, scale=scale) + lc_trunc = stats.truncweibull_min.logcdf(x, c, a, b, scale=scale) + assert_allclose(lc, lc_trunc) + + s = stats.weibull_min.sf(x, c, scale=scale) + s_trunc = stats.truncweibull_min.sf(x, c, a, b, scale=scale) + assert_allclose(s, s_trunc) + + ls = stats.weibull_min.logsf(x, c, scale=scale) + ls_trunc = stats.truncweibull_min.logsf(x, c, a, b, scale=scale) + assert_allclose(ls, ls_trunc) + + # # Also test using a large value x, for which computing the survival + # # function using the CDF would result in 0. + s = stats.truncweibull_min.sf(30, 2, a, b, scale=3) + assert_allclose(s, np.exp(-100)) + + ls = stats.truncweibull_min.logsf(30, 2, a, b, scale=3) + assert_allclose(ls, -100) + + def test_compare_weibull_min2(self): + # Verify that the truncweibull_min distribution PDF and CDF results + # are the same as those calculated from truncating weibull_min + c, a, b = 2.5, 0.25, 1.25 + x = np.linspace(a, b, 100) + + pdf1 = stats.truncweibull_min.pdf(x, c, a, b) + cdf1 = stats.truncweibull_min.cdf(x, c, a, b) + + norm = stats.weibull_min.cdf(b, c) - stats.weibull_min.cdf(a, c) + pdf2 = stats.weibull_min.pdf(x, c) / norm + cdf2 = (stats.weibull_min.cdf(x, c) - stats.weibull_min.cdf(a, c))/norm + + np.testing.assert_allclose(pdf1, pdf2) + np.testing.assert_allclose(cdf1, cdf2) + + +class TestRdist: + def test_rdist_cdf_gh1285(self): + # check workaround in rdist._cdf for issue gh-1285. + distfn = stats.rdist + values = [0.001, 0.5, 0.999] + assert_almost_equal(distfn.cdf(distfn.ppf(values, 541.0), 541.0), + values, decimal=5) + + def test_rdist_beta(self): + # rdist is a special case of stats.beta + x = np.linspace(-0.99, 0.99, 10) + c = 2.7 + assert_almost_equal(0.5*stats.beta(c/2, c/2).pdf((x + 1)/2), + stats.rdist(c).pdf(x)) + + # reference values were computed via mpmath + # from mpmath import mp + # mp.dps = 200 + # def rdist_sf_mpmath(x, c): + # x = mp.mpf(x) + # c = mp.mpf(c) + # return float(mp.betainc(c/2, c/2, (x+1)/2, mp.one, regularized=True)) + @pytest.mark.parametrize( + "x, c, ref", + [ + (0.0001, 541, 0.49907251345565845), + (0.1, 241, 0.06000788166249205), + (0.5, 441, 1.0655898106047832e-29), + (0.8, 341, 6.025478373732215e-78), + ] + ) + def test_rdist_sf(self, x, c, ref): + assert_allclose(stats.rdist.sf(x, c), ref, rtol=5e-14) + + +class TestTrapezoid: + def test_reduces_to_triang(self): + modes = [0, 0.3, 0.5, 1] + for mode in modes: + x = [0, mode, 1] + assert_almost_equal(stats.trapezoid.pdf(x, mode, mode), + stats.triang.pdf(x, mode)) + assert_almost_equal(stats.trapezoid.cdf(x, mode, mode), + stats.triang.cdf(x, mode)) + + def test_reduces_to_uniform(self): + x = np.linspace(0, 1, 10) + assert_almost_equal(stats.trapezoid.pdf(x, 0, 1), stats.uniform.pdf(x)) + assert_almost_equal(stats.trapezoid.cdf(x, 0, 1), stats.uniform.cdf(x)) + + def test_cases(self): + # edge cases + assert_almost_equal(stats.trapezoid.pdf(0, 0, 0), 2) + assert_almost_equal(stats.trapezoid.pdf(1, 1, 1), 2) + assert_almost_equal(stats.trapezoid.pdf(0.5, 0, 0.8), + 1.11111111111111111) + assert_almost_equal(stats.trapezoid.pdf(0.5, 0.2, 1.0), + 1.11111111111111111) + + # straightforward case + assert_almost_equal(stats.trapezoid.pdf(0.1, 0.2, 0.8), 0.625) + assert_almost_equal(stats.trapezoid.pdf(0.5, 0.2, 0.8), 1.25) + assert_almost_equal(stats.trapezoid.pdf(0.9, 0.2, 0.8), 0.625) + + assert_almost_equal(stats.trapezoid.cdf(0.1, 0.2, 0.8), 0.03125) + assert_almost_equal(stats.trapezoid.cdf(0.2, 0.2, 0.8), 0.125) + assert_almost_equal(stats.trapezoid.cdf(0.5, 0.2, 0.8), 0.5) + assert_almost_equal(stats.trapezoid.cdf(0.9, 0.2, 0.8), 0.96875) + assert_almost_equal(stats.trapezoid.cdf(1.0, 0.2, 0.8), 1.0) + + def test_moments_and_entropy(self): + # issue #11795: improve precision of trapezoid stats + # Apply formulas from Wikipedia for the following parameters: + a, b, c, d = -3, -1, 2, 3 # => 1/3, 5/6, -3, 6 + p1, p2, loc, scale = (b-a) / (d-a), (c-a) / (d-a), a, d-a + h = 2 / (d+c-b-a) + + def moment(n): + return (h * ((d**(n+2) - c**(n+2)) / (d-c) + - (b**(n+2) - a**(n+2)) / (b-a)) / + (n+1) / (n+2)) + + mean = moment(1) + var = moment(2) - mean**2 + entropy = 0.5 * (d-c+b-a) / (d+c-b-a) + np.log(0.5 * (d+c-b-a)) + assert_almost_equal(stats.trapezoid.mean(p1, p2, loc, scale), + mean, decimal=13) + assert_almost_equal(stats.trapezoid.var(p1, p2, loc, scale), + var, decimal=13) + assert_almost_equal(stats.trapezoid.entropy(p1, p2, loc, scale), + entropy, decimal=13) + + # Check boundary cases where scipy d=0 or d=1. + assert_almost_equal(stats.trapezoid.mean(0, 0, -3, 6), -1, decimal=13) + assert_almost_equal(stats.trapezoid.mean(0, 1, -3, 6), 0, decimal=13) + assert_almost_equal(stats.trapezoid.var(0, 1, -3, 6), 3, decimal=13) + + def test_trapezoid_vect(self): + # test that array-valued shapes and arguments are handled + c = np.array([0.1, 0.2, 0.3]) + d = np.array([0.5, 0.6])[:, None] + x = np.array([0.15, 0.25, 0.9]) + v = stats.trapezoid.pdf(x, c, d) + + cc, dd, xx = np.broadcast_arrays(c, d, x) + + res = np.empty(xx.size, dtype=xx.dtype) + ind = np.arange(xx.size) + for i, x1, c1, d1 in zip(ind, xx.ravel(), cc.ravel(), dd.ravel()): + res[i] = stats.trapezoid.pdf(x1, c1, d1) + + assert_allclose(v, res.reshape(v.shape), atol=1e-15) + + # Check that the stats() method supports vector arguments. + v = np.asarray(stats.trapezoid.stats(c, d, moments="mvsk")) + cc, dd = np.broadcast_arrays(c, d) + res = np.empty((cc.size, 4)) # 4 stats returned per value + ind = np.arange(cc.size) + for i, c1, d1 in zip(ind, cc.ravel(), dd.ravel()): + res[i] = stats.trapezoid.stats(c1, d1, moments="mvsk") + + assert_allclose(v, res.T.reshape(v.shape), atol=1e-15) + + def test_trapezoid_fit_convergence_gh23503(self): + # gh-23503 reported that trapezoid.fit would consistently converge to a + # triangular distribution unless starting values were provided. Check that this + # is resolved. + + # Generate test data from a trapezoidal distribution + rng = np.random.default_rng(23842359234598263956) + true_args = 0.3, 0.7, -1, 2 + true_dist = stats.trapezoid(*true_args) + x = true_dist.rvs(1000, random_state=rng) + + # fit to data + fitted_args = stats.trapezoid.fit(x) + + # Should not converge to triangular distribution (c=d=1) + fitted_c, fitted_d = fitted_args[:2] + assert not np.allclose(fitted_c, 1, atol=0.1) + assert not np.allclose(fitted_d, 1, atol=0.1) + + # objective function is better than with true values of parameters + true_llf = stats.trapezoid.nnlf(true_args, x) + fitted_llf = stats.trapezoid.nnlf(fitted_args, x) + assert fitted_llf < true_llf + + +class TestTriang: + def test_edge_cases(self): + with np.errstate(all='raise'): + assert_equal(stats.triang.pdf(0, 0), 2.) + assert_equal(stats.triang.pdf(0.5, 0), 1.) + assert_equal(stats.triang.pdf(1, 0), 0.) + + assert_equal(stats.triang.pdf(0, 1), 0) + assert_equal(stats.triang.pdf(0.5, 1), 1.) + assert_equal(stats.triang.pdf(1, 1), 2) + + assert_equal(stats.triang.cdf(0., 0.), 0.) + assert_equal(stats.triang.cdf(0.5, 0.), 0.75) + assert_equal(stats.triang.cdf(1.0, 0.), 1.0) + + assert_equal(stats.triang.cdf(0., 1.), 0.) + assert_equal(stats.triang.cdf(0.5, 1.), 0.25) + assert_equal(stats.triang.cdf(1., 1.), 1) + + +class TestMaxwell: + + # reference values were computed with wolfram alpha + # erfc(x/sqrt(2)) + sqrt(2/pi) * x * e^(-x^2/2) + + @pytest.mark.parametrize("x, ref", + [(20, 2.2138865931011177e-86), + (0.01, 0.999999734046458435)]) + def test_sf(self, x, ref): + assert_allclose(stats.maxwell.sf(x), ref, rtol=1e-14) + + # reference values were computed with wolfram alpha + # sqrt(2) * sqrt(Q^(-1)(3/2, q)) + + @pytest.mark.parametrize("q, ref", + [(0.001, 4.033142223656157022), + (0.9999847412109375, 0.0385743284050381), + (2**-55, 8.95564974719481)]) + def test_isf(self, q, ref): + assert_allclose(stats.maxwell.isf(q), ref, rtol=1e-15) + + def test_logcdf(self): + # Reference value computed with mpmath. + ref = -1.8729310110194814e-17 + logcdf = stats.maxwell.logcdf(9) + assert_allclose(logcdf, ref, rtol=5e-15) + + def test_logsf(self): + # Reference value computed with mpmath. + ref = -2.6596152026762177e-25 + logsf = stats.maxwell.logsf(1e-8) + assert_allclose(logsf, ref, rtol=5e-15) + + +class TestMielke: + def test_moments(self): + k, s = 4.642, 0.597 + # n-th moment exists only if n < s + assert_equal(stats.mielke(k, s).moment(1), np.inf) + assert_equal(stats.mielke(k, 1.0).moment(1), np.inf) + assert_(np.isfinite(stats.mielke(k, 1.01).moment(1))) + + def test_burr_equivalence(self): + x = np.linspace(0.01, 100, 50) + k, s = 2.45, 5.32 + assert_allclose(stats.burr.pdf(x, s, k/s), stats.mielke.pdf(x, k, s)) + + +class TestBurr: + def test_endpoints_7491(self): + # gh-7491 + # Compute the pdf at the left endpoint dst.a. + data = [ + [stats.fisk, (1,), 1], + [stats.burr, (0.5, 2), 1], + [stats.burr, (1, 1), 1], + [stats.burr, (2, 0.5), 1], + [stats.burr12, (1, 0.5), 0.5], + [stats.burr12, (1, 1), 1.0], + [stats.burr12, (1, 2), 2.0]] + + ans = [_f.pdf(_f.a, *_args) for _f, _args, _ in data] + correct = [_correct_ for _f, _args, _correct_ in data] + assert_array_almost_equal(ans, correct) + + ans = [_f.logpdf(_f.a, *_args) for _f, _args, _ in data] + correct = [np.log(_correct_) for _f, _args, _correct_ in data] + assert_array_almost_equal(ans, correct) + + def test_burr_stats_9544(self): + # gh-9544. Test from gh-9978 + c, d = 5.0, 3 + mean, variance = stats.burr(c, d).stats() + # mean = sc.beta(3 + 1/5, 1. - 1/5) * 3 = 1.4110263... + # var = sc.beta(3 + 2 / 5, 1. - 2 / 5) * 3 - + # (sc.beta(3 + 1 / 5, 1. - 1 / 5) * 3) ** 2 + mean_hc, variance_hc = 1.4110263183925857, 0.22879948026191643 + assert_allclose(mean, mean_hc) + assert_allclose(variance, variance_hc) + + def test_burr_nan_mean_var_9544(self): + # gh-9544. Test from gh-9978 + c, d = 0.5, 3 + mean, variance = stats.burr(c, d).stats() + assert_(np.isnan(mean)) + assert_(np.isnan(variance)) + c, d = 1.5, 3 + mean, variance = stats.burr(c, d).stats() + assert_(np.isfinite(mean)) + assert_(np.isnan(variance)) + + c, d = 0.5, 3 + e1, e2, e3, e4 = stats.burr._munp(np.array([1, 2, 3, 4]), c, d) + assert_(np.isnan(e1)) + assert_(np.isnan(e2)) + assert_(np.isnan(e3)) + assert_(np.isnan(e4)) + c, d = 1.5, 3 + e1, e2, e3, e4 = stats.burr._munp([1, 2, 3, 4], c, d) + assert_(np.isfinite(e1)) + assert_(np.isnan(e2)) + assert_(np.isnan(e3)) + assert_(np.isnan(e4)) + c, d = 2.5, 3 + e1, e2, e3, e4 = stats.burr._munp([1, 2, 3, 4], c, d) + assert_(np.isfinite(e1)) + assert_(np.isfinite(e2)) + assert_(np.isnan(e3)) + assert_(np.isnan(e4)) + c, d = 3.5, 3 + e1, e2, e3, e4 = stats.burr._munp([1, 2, 3, 4], c, d) + assert_(np.isfinite(e1)) + assert_(np.isfinite(e2)) + assert_(np.isfinite(e3)) + assert_(np.isnan(e4)) + c, d = 4.5, 3 + e1, e2, e3, e4 = stats.burr._munp([1, 2, 3, 4], c, d) + assert_(np.isfinite(e1)) + assert_(np.isfinite(e2)) + assert_(np.isfinite(e3)) + assert_(np.isfinite(e4)) + + def test_burr_isf(self): + # reference values were computed via the reference distribution, e.g. + # mp.dps = 100 + # Burr(c=5, d=3).isf([0.1, 1e-10, 1e-20, 1e-40]) + c, d = 5.0, 3.0 + q = [0.1, 1e-10, 1e-20, 1e-40] + ref = [1.9469686558286508, 124.57309395989076, 12457.309396155173, + 124573093.96155174] + assert_allclose(stats.burr.isf(q, c, d), ref, rtol=1e-14) + + +class TestBurr12: + + @pytest.mark.parametrize('scale, expected', + [(1.0, 2.3283064359965952e-170), + (3.5, 5.987114417447875e-153)]) + def test_delta_cdf(self, scale, expected): + # Expected value computed with mpmath: + # + # def burr12sf(x, c, d, scale): + # x = mpmath.mpf(x) + # c = mpmath.mpf(c) + # d = mpmath.mpf(d) + # scale = mpmath.mpf(scale) + # return (mpmath.mp.one + (x/scale)**c)**(-d) + # + # >>> import mpmath + # >>> mpmath.mp.dps = 60 + # >>> float(burr12sf(2e5, 4, 8, 1) - burr12sf(4e5, 4, 8, 1)) + # 2.3283064359965952e-170 + # >>> float(burr12sf(2e5, 4, 8, 3.5) - burr12sf(4e5, 4, 8, 3.5)) + # 5.987114417447875e-153 + # + delta = stats.burr12._delta_cdf(2e5, 4e5, 4, 8, scale=scale) + assert_allclose(delta, expected, rtol=1e-13) + + def test_moments_edge(self): + # gh-18838 reported that burr12 moments could be invalid; see above. + # Check that this is resolved in an edge case where c*d == n, and + # compare the results against those produced by Mathematica, e.g. + # `SinghMaddalaDistribution[2, 2, 1]` at Wolfram Alpha. + c, d = 2, 2 + mean = np.pi/4 + var = 1 - np.pi**2/16 + skew = np.pi**3/(32*var**1.5) + kurtosis = np.nan + ref = [mean, var, skew, kurtosis] + res = stats.burr12(c, d).stats('mvsk') + assert_allclose(res, ref, rtol=1e-14) + + # Reference values were computed with mpmath using mp.dps = 80 + # and then cast to float. + @pytest.mark.parametrize( + 'p, c, d, ref', + [(1e-12, 20, 0.5, 15.848931924611135), + (1e-19, 20, 0.5, 79.43282347242815), + (1e-12, 0.25, 35, 2.0888618213462466), + (1e-80, 0.25, 35, 1360930951.7972188)] + ) + def test_isf_near_zero(self, p, c, d, ref): + x = stats.burr12.isf(p, c, d) + assert_allclose(x, ref, rtol=1e-14) + + +class TestStudentizedRange: + # For alpha = .05, .01, and .001, and for each value of + # v = [1, 3, 10, 20, 120, inf], a Q was picked from each table for + # k = [2, 8, 14, 20]. + + # these arrays are written with `k` as column, and `v` as rows. + # Q values are taken from table 3: + # https://www.jstor.org/stable/2237810 + q05 = [17.97, 45.40, 54.33, 59.56, + 4.501, 8.853, 10.35, 11.24, + 3.151, 5.305, 6.028, 6.467, + 2.950, 4.768, 5.357, 5.714, + 2.800, 4.363, 4.842, 5.126, + 2.772, 4.286, 4.743, 5.012] + q01 = [90.03, 227.2, 271.8, 298.0, + 8.261, 15.64, 18.22, 19.77, + 4.482, 6.875, 7.712, 8.226, + 4.024, 5.839, 6.450, 6.823, + 3.702, 5.118, 5.562, 5.827, + 3.643, 4.987, 5.400, 5.645] + q001 = [900.3, 2272, 2718, 2980, + 18.28, 34.12, 39.69, 43.05, + 6.487, 9.352, 10.39, 11.03, + 5.444, 7.313, 7.966, 8.370, + 4.772, 6.039, 6.448, 6.695, + 4.654, 5.823, 6.191, 6.411] + qs = np.concatenate((q05, q01, q001)) + ps = [.95, .99, .999] + vs = [1, 3, 10, 20, 120, np.inf] + ks = [2, 8, 14, 20] + + data = list(zip(product(ps, vs, ks), qs)) + + # A small selection of large-v cases generated with R's `ptukey` + # Each case is in the format (q, k, v, r_result) + r_data = [ + (0.1, 3, 9001, 0.002752818526842), + (1, 10, 1000, 0.000526142388912), + (1, 3, np.inf, 0.240712641229283), + (4, 3, np.inf, 0.987012338626815), + (1, 10, np.inf, 0.000519869467083), + ] + + @pytest.mark.slow + def test_cdf_against_tables(self): + for pvk, q in self.data: + p_expected, v, k = pvk + res_p = stats.studentized_range.cdf(q, k, v) + assert_allclose(res_p, p_expected, rtol=1e-4) + + @pytest.mark.xslow + def test_ppf_against_tables(self): + for pvk, q_expected in self.data: + p, v, k = pvk + res_q = stats.studentized_range.ppf(p, k, v) + assert_allclose(res_q, q_expected, rtol=5e-4) + + path_prefix = os.path.dirname(__file__) + relative_path = "data/studentized_range_mpmath_ref.json" + with open(os.path.join(path_prefix, relative_path)) as file: + pregenerated_data = json.load(file) + + @pytest.mark.parametrize("case_result", pregenerated_data["cdf_data"]) + def test_cdf_against_mp(self, case_result): + src_case = case_result["src_case"] + mp_result = case_result["mp_result"] + qkv = src_case["q"], src_case["k"], src_case["v"] + res = stats.studentized_range.cdf(*qkv) + + assert_allclose(res, mp_result, + atol=src_case["expected_atol"], + rtol=src_case["expected_rtol"]) + + @pytest.mark.parametrize("case_result", pregenerated_data["pdf_data"]) + def test_pdf_against_mp(self, case_result): + src_case = case_result["src_case"] + mp_result = case_result["mp_result"] + qkv = src_case["q"], src_case["k"], src_case["v"] + res = stats.studentized_range.pdf(*qkv) + + assert_allclose(res, mp_result, + atol=src_case["expected_atol"], + rtol=src_case["expected_rtol"]) + + @pytest.mark.xslow + @pytest.mark.xfail_on_32bit("intermittent RuntimeWarning: invalid value.") + @pytest.mark.parametrize("case_result", pregenerated_data["moment_data"]) + def test_moment_against_mp(self, case_result): + src_case = case_result["src_case"] + mp_result = case_result["mp_result"] + mkv = src_case["m"], src_case["k"], src_case["v"] + + # Silence invalid value encountered warnings. Actual problems will be + # caught by the result comparison. + with np.errstate(invalid='ignore'): + res = stats.studentized_range.moment(*mkv) + + assert_allclose(res, mp_result, + atol=src_case["expected_atol"], + rtol=src_case["expected_rtol"]) + + @pytest.mark.slow + def test_pdf_integration(self): + k, v = 3, 10 + # Test whether PDF integration is 1 like it should be. + res = quad(stats.studentized_range.pdf, 0, np.inf, args=(k, v)) + assert_allclose(res[0], 1) + + @pytest.mark.xslow + def test_pdf_against_cdf(self): + k, v = 3, 10 + + # Test whether the integrated PDF matches the CDF using cumulative + # integration. Use a small step size to reduce error due to the + # summation. This is slow, but tests the results well. + x = np.arange(0, 10, step=0.01) + + y_cdf = stats.studentized_range.cdf(x, k, v)[1:] + y_pdf_raw = stats.studentized_range.pdf(x, k, v) + y_pdf_cumulative = cumulative_trapezoid(y_pdf_raw, x) + + # Because of error caused by the summation, use a relatively large rtol + assert_allclose(y_pdf_cumulative, y_cdf, rtol=1e-4) + + @pytest.mark.parametrize("r_case_result", r_data) + def test_cdf_against_r(self, r_case_result): + # Test large `v` values using R + q, k, v, r_res = r_case_result + with np.errstate(invalid='ignore'): + res = stats.studentized_range.cdf(q, k, v) + assert_allclose(res, r_res) + + @pytest.mark.xslow + @pytest.mark.xfail_on_32bit("intermittent RuntimeWarning: invalid value.") + def test_moment_vectorization(self): + # Test moment broadcasting. Calls `_munp` directly because + # `rv_continuous.moment` is broken at time of writing. See gh-12192 + + # Silence invalid value encountered warnings. Actual problems will be + # caught by the result comparison. + with np.errstate(invalid='ignore'): + m = stats.studentized_range._munp([1, 2], [4, 5], [10, 11]) + + assert_allclose(m.shape, (2,)) + + with pytest.raises(ValueError, match="...could not be broadcast..."): + stats.studentized_range._munp(1, [4, 5], [10, 11, 12]) + + @pytest.mark.xslow + def test_fitstart_valid(self): + with warnings.catch_warnings(), np.errstate(invalid="ignore"): + # the integration warning message may differ + warnings.simplefilter("ignore", IntegrationWarning) + k, df, _, _ = stats.studentized_range._fitstart([1, 2, 3]) + assert_(stats.studentized_range._argcheck(k, df)) + + def test_infinite_df(self): + # Check that the CDF and PDF infinite and normal integrators + # roughly match for a high df case + res = stats.studentized_range.pdf(3, 10, np.inf) + res_finite = stats.studentized_range.pdf(3, 10, 99999) + assert_allclose(res, res_finite, atol=1e-4, rtol=1e-4) + + res = stats.studentized_range.cdf(3, 10, np.inf) + res_finite = stats.studentized_range.cdf(3, 10, 99999) + assert_allclose(res, res_finite, atol=1e-4, rtol=1e-4) + + def test_df_cutoff(self): + # Test that the CDF and PDF properly switch integrators at df=100,000. + # The infinite integrator should be different enough that it fails + # an allclose assertion. Also sanity check that using the same + # integrator does pass the allclose with a 1-df difference, which + # should be tiny. + + res = stats.studentized_range.pdf(3, 10, 100000) + res_finite = stats.studentized_range.pdf(3, 10, 99999) + res_sanity = stats.studentized_range.pdf(3, 10, 99998) + assert_raises(AssertionError, assert_allclose, res, res_finite, + atol=1e-6, rtol=1e-6) + assert_allclose(res_finite, res_sanity, atol=1e-6, rtol=1e-6) + + res = stats.studentized_range.cdf(3, 10, 100000) + res_finite = stats.studentized_range.cdf(3, 10, 99999) + res_sanity = stats.studentized_range.cdf(3, 10, 99998) + assert_raises(AssertionError, assert_allclose, res, res_finite, + atol=1e-6, rtol=1e-6) + assert_allclose(res_finite, res_sanity, atol=1e-6, rtol=1e-6) + + def test_clipping(self): + # The result of this computation was -9.9253938401489e-14 on some + # systems. The correct result is very nearly zero, but should not be + # negative. + q, k, v = 34.6413996195345746, 3, 339 + p = stats.studentized_range.sf(q, k, v) + assert_allclose(p, 0, atol=1e-10) + assert p >= 0 + + +class TestTukeyLambda: + + @pytest.mark.parametrize( + 'lam', + [0.0, -1.0, -2.0, np.array([[-1.0], [0.0], [-2.0]])] + ) + def test_pdf_nonpositive_lambda(self, lam): + # Make sure that Tukey-Lambda distribution correctly handles + # non-positive lambdas. + # This is a crude test--it just checks that all the PDF values + # are finite and greater than 0. + x = np.linspace(-5.0, 5.0, 101) + p = stats.tukeylambda.pdf(x, lam) + assert np.isfinite(p).all() + assert (p > 0.0).all() + + def test_pdf_mixed_lambda(self): + # Another crude test of the behavior of the PDF method. + x = np.linspace(-5.0, 5.0, 101) + lam = np.array([[-1.0], [0.0], [2.0]]) + p = stats.tukeylambda.pdf(x, lam) + assert np.isfinite(p).all() + # For p[0] and p[1], where lam <= 0, the support is (-inf, inf), + # so the PDF should be nonzero everywhere (assuming we aren't so + # far in the tails that we get underflow). + assert (p[:2] > 0.0).all() + # For p[2], where lam=2.0, the support is [-0.5, 0.5], so in pdf(x), + # some values should be positive and some should be 0. + assert (p[2] > 0.0).any() + assert (p[2] == 0.0).any() + + def test_support(self): + lam = np.array([-1.75, -0.5, 0.0, 0.25, 0.5, 2.0]) + a, b = stats.tukeylambda.support(lam) + expected_b = np.array([np.inf, np.inf, np.inf, 4, 2, 0.5]) + assert_equal(b, expected_b) + assert_equal(a, -expected_b) + + def test_pdf_support_boundary(self): + # Verify that tukeylambda.pdf() doesn't generate a + # warning when evaluated at the bounds of the support. + # For lam=0.5, the support is (-2, 2). + p = stats.tukeylambda.pdf([-2.0, 2.0], 0.5) + assert_equal(p, [0.0, 0.0]) + + def test_tukeylambda_stats_ticket_1545(self): + # Some test for the variance and kurtosis of the Tukey Lambda distr. + # See test_tukeylamdba_stats.py for more tests. + + mv = stats.tukeylambda.stats(0, moments='mvsk') + # Known exact values: + expected = [0, np.pi**2/3, 0, 1.2] + assert_almost_equal(mv, expected, decimal=10) + + mv = stats.tukeylambda.stats(3.13, moments='mvsk') + # 'expected' computed with mpmath. + expected = [0, 0.0269220858861465102, 0, -0.898062386219224104] + assert_almost_equal(mv, expected, decimal=10) + + mv = stats.tukeylambda.stats(0.14, moments='mvsk') + # 'expected' computed with mpmath. + expected = [0, 2.11029702221450250, 0, -0.02708377353223019456] + assert_almost_equal(mv, expected, decimal=10) + + @pytest.mark.xfail(reason="Precision in tail hasn't been fixed in xsf") + def test_large_argument_ticket_21370(self): + # Check that survival function goes to 0 as x gets large + x = [1e4, 1e6, 1e8, 1e10, 1e12, 1e24] + lam = -0.5 + sf = stats.tukeylambda.sf(x, lam) + # Check that the sf is 0 for large x + assert sf[-1] == 0.0 + # Check that the sf is always decreasing except when it is 0 + assert_array_less(np.diff(sf[sf > 0]), 0) + + cdf = stats.tukeylambda.cdf(x, lam) + assert cdf[-1] == 1.0 + # Check that the cdf is always increasing except when it saturates + # to 1.0 + assert np.all(np.diff(cdf[cdf < 1]) > 0) + +class TestLevy: + + def test_levy_cdf_ppf(self): + # Test levy.cdf, including small arguments. + x = np.array([1000, 1.0, 0.5, 0.1, 0.01, 0.001]) + + # Expected values were calculated separately with mpmath. + # E.g. + # >>> mpmath.mp.dps = 100 + # >>> x = mpmath.mp.mpf('0.01') + # >>> cdf = mpmath.erfc(mpmath.sqrt(1/(2*x))) + expected = np.array([0.9747728793699604, + 0.3173105078629141, + 0.1572992070502851, + 0.0015654022580025495, + 1.523970604832105e-23, + 1.795832784800726e-219]) + + y = stats.levy.cdf(x) + assert_allclose(y, expected, rtol=1e-10) + + # ppf(expected) should get us back to x. + xx = stats.levy.ppf(expected) + assert_allclose(xx, x, rtol=1e-13) + + def test_levy_sf(self): + # Large values, far into the tail of the distribution. + x = np.array([1e15, 1e25, 1e35, 1e50]) + # Expected values were calculated with mpmath. + expected = np.array([2.5231325220201597e-08, + 2.52313252202016e-13, + 2.52313252202016e-18, + 7.978845608028653e-26]) + y = stats.levy.sf(x) + assert_allclose(y, expected, rtol=1e-14) + + # The expected values for levy.isf(p) were calculated with mpmath. + # For loc=0 and scale=1, the inverse SF can be computed with + # + # import mpmath + # + # def levy_invsf(p): + # return 1/(2*mpmath.erfinv(p)**2) + # + # For example, with mpmath.mp.dps set to 60, float(levy_invsf(1e-20)) + # returns 6.366197723675814e+39. + # + @pytest.mark.parametrize('p, expected_isf', + [(1e-20, 6.366197723675814e+39), + (1e-8, 6366197723675813.0), + (0.375, 4.185810119346273), + (0.875, 0.42489442055310134), + (0.999, 0.09235685880262713), + (0.9999999962747097, 0.028766845244146945)]) + def test_levy_isf(self, p, expected_isf): + x = stats.levy.isf(p) + assert_allclose(x, expected_isf, atol=5e-15) + + def test_levy_logcdf(self): + x = 1e50 + ref = -7.978845608028653e-26 + logcdf = stats.levy.logcdf(x) + assert_allclose(logcdf, ref, rtol=5e-15) + + def test_levy_logsf(self): + x = 5e-3 + ref = -2.0884875837625492e-45 + logsf = stats.levy.logsf(x) + assert_allclose(logsf, ref, rtol=5e-15) + + +def test_540_567(): + # test for nan returned in tickets 540, 567 + assert_almost_equal(stats.norm.cdf(-1.7624320982), 0.03899815971089126, + decimal=10, err_msg='test_540_567') + assert_almost_equal(stats.norm.cdf(-1.7624320983), 0.038998159702449846, + decimal=10, err_msg='test_540_567') + assert_almost_equal(stats.norm.cdf(1.38629436112, loc=0.950273420309, + scale=0.204423758009), + 0.98353464004309321, + decimal=10, err_msg='test_540_567') + + +@pytest.mark.skipif(DOCSTRINGS_STRIPPED, reason="docstrings stripped") +def test_regression_ticket_1421(): + assert_('pdf(x, mu, loc=0, scale=1)' not in stats.poisson.__doc__) + assert_('pmf(x,' in stats.poisson.__doc__) + + +def test_nan_arguments_gh_issue_1362(): + with np.errstate(invalid='ignore'): + assert_(np.isnan(stats.t.logcdf(1, np.nan))) + assert_(np.isnan(stats.t.cdf(1, np.nan))) + assert_(np.isnan(stats.t.logsf(1, np.nan))) + assert_(np.isnan(stats.t.sf(1, np.nan))) + assert_(np.isnan(stats.t.pdf(1, np.nan))) + assert_(np.isnan(stats.t.logpdf(1, np.nan))) + assert_(np.isnan(stats.t.ppf(1, np.nan))) + assert_(np.isnan(stats.t.isf(1, np.nan))) + + assert_(np.isnan(stats.bernoulli.logcdf(np.nan, 0.5))) + assert_(np.isnan(stats.bernoulli.cdf(np.nan, 0.5))) + assert_(np.isnan(stats.bernoulli.logsf(np.nan, 0.5))) + assert_(np.isnan(stats.bernoulli.sf(np.nan, 0.5))) + assert_(np.isnan(stats.bernoulli.pmf(np.nan, 0.5))) + assert_(np.isnan(stats.bernoulli.logpmf(np.nan, 0.5))) + assert_(np.isnan(stats.bernoulli.ppf(np.nan, 0.5))) + assert_(np.isnan(stats.bernoulli.isf(np.nan, 0.5))) + + +def test_frozen_fit_ticket_1536(): + rng = np.random.default_rng(5678) + true = np.array([0.25, 0., 0.5]) + x = stats.lognorm.rvs(true[0], true[1], true[2], size=100, random_state=rng) + + with np.errstate(divide='ignore'): + params = np.array(stats.lognorm.fit(x, floc=0.)) + + assert_almost_equal(params, true, decimal=2) + + params = np.array(stats.lognorm.fit(x, fscale=0.5, loc=0)) + assert_almost_equal(params, true, decimal=2) + + params = np.array(stats.lognorm.fit(x, f0=0.25, loc=0)) + assert_almost_equal(params, true, decimal=2) + + params = np.array(stats.lognorm.fit(x, f0=0.25, floc=0)) + assert_almost_equal(params, true, decimal=2) + + rng = np.random.default_rng(5678) + loc = 1 + floc = 0.9 + x = stats.norm.rvs(loc, 2., size=100, random_state=rng) + params = np.array(stats.norm.fit(x, floc=floc)) + expected = np.array([floc, np.sqrt(((x-floc)**2).mean())]) + assert_almost_equal(params, expected, decimal=4) + + +def test_regression_ticket_1530(): + # Check the starting value works for Cauchy distribution fit. + rng = np.random.default_rng(654321) + rvs = stats.cauchy.rvs(size=100, random_state=rng) + params = stats.cauchy.fit(rvs) + expected = (0.045, 1.142) + assert_almost_equal(params, expected, decimal=1) + + +def test_gh_pr_4806(): + # Check starting values for Cauchy distribution fit. + rng = np.random.RandomState(1234) + x = rng.randn(42) + for offset in 10000.0, 1222333444.0: + loc, scale = stats.cauchy.fit(x + offset) + assert_allclose(loc, offset, atol=1.0) + assert_allclose(scale, 0.6, atol=1.0) + + +def test_poisson_logpmf_ticket_1436(): + assert_(np.isfinite(stats.poisson.logpmf(1500, 200))) + + +def test_powerlaw_stats(): + """Test the powerlaw stats function. + + This unit test is also a regression test for ticket 1548. + + The exact values are: + mean: + mu = a / (a + 1) + variance: + sigma**2 = a / ((a + 2) * (a + 1) ** 2) + skewness: + One formula (see https://en.wikipedia.org/wiki/Skewness) is + gamma_1 = (E[X**3] - 3*mu*E[X**2] + 2*mu**3) / sigma**3 + A short calculation shows that E[X**k] is a / (a + k), so gamma_1 + can be implemented as + n = a/(a+3) - 3*(a/(a+1))*a/(a+2) + 2*(a/(a+1))**3 + d = sqrt(a/((a+2)*(a+1)**2)) ** 3 + gamma_1 = n/d + Either by simplifying, or by a direct calculation of mu_3 / sigma**3, + one gets the more concise formula: + gamma_1 = -2.0 * ((a - 1) / (a + 3)) * sqrt((a + 2) / a) + kurtosis: (See https://en.wikipedia.org/wiki/Kurtosis) + The excess kurtosis is + gamma_2 = mu_4 / sigma**4 - 3 + A bit of calculus and algebra (sympy helps) shows that + mu_4 = 3*a*(3*a**2 - a + 2) / ((a+1)**4 * (a+2) * (a+3) * (a+4)) + so + gamma_2 = 3*(3*a**2 - a + 2) * (a+2) / (a*(a+3)*(a+4)) - 3 + which can be rearranged to + gamma_2 = 6 * (a**3 - a**2 - 6*a + 2) / (a*(a+3)*(a+4)) + """ + cases = [(1.0, (0.5, 1./12, 0.0, -1.2)), + (2.0, (2./3, 2./36, -0.56568542494924734, -0.6))] + for a, exact_mvsk in cases: + mvsk = stats.powerlaw.stats(a, moments="mvsk") + assert_array_almost_equal(mvsk, exact_mvsk) + + +def test_powerlaw_edge(): + # Regression test for gh-3986. + p = stats.powerlaw.logpdf(0, 1) + assert_equal(p, 0.0) + + +def test_exponpow_edge(): + # Regression test for gh-3982. + p = stats.exponpow.logpdf(0, 1) + assert_equal(p, 0.0) + + # Check pdf and logpdf at x = 0 for other values of b. + p = stats.exponpow.pdf(0, [0.25, 1.0, 1.5]) + assert_equal(p, [np.inf, 1.0, 0.0]) + p = stats.exponpow.logpdf(0, [0.25, 1.0, 1.5]) + assert_equal(p, [np.inf, 0.0, -np.inf]) + + +class TestGenGamma: + def test_gengamma_edge(self): + # Regression test for gh-3985. + p = stats.gengamma.pdf(0, 1, 1) + assert_equal(p, 1.0) + + @pytest.mark.parametrize("a, c, ref, tol", + [(1500000.0, 1, 8.529426144018633, 1e-15), + (1e+30, 1, 35.95771492811536, 1e-15), + (1e+100, 1, 116.54819318290696, 1e-15), + (3e3, 1, 5.422011196659015, 1e-13), + (3e6, -1e100, -236.29663213396054, 1e-15), + (3e60, 1e-100, 1.3925371786831085e+102, 1e-15)]) + def test_gengamma_extreme_entropy(self, a, c, ref, tol): + # The reference values were calculated with mpmath: + # from mpmath import mp + # mp.dps = 500 + # + # def gen_entropy(a, c): + # a, c = mp.mpf(a), mp.mpf(c) + # val = mp.digamma(a) + # h = (a * (mp.one - val) + val/c + mp.loggamma(a) - mp.log(abs(c))) + # return float(h) + assert_allclose(stats.gengamma.entropy(a, c), ref, rtol=tol) + + def test_gengamma_endpoint_with_neg_c(self): + p = stats.gengamma.pdf(0, 1, -1) + assert p == 0.0 + logp = stats.gengamma.logpdf(0, 1, -1) + assert logp == -np.inf + + def test_gengamma_munp(self): + # Regression tests for gh-4724. + p = stats.gengamma._munp(-2, 200, 1.) + assert_almost_equal(p, 1./199/198) + + p = stats.gengamma._munp(-2, 10, 1.) + assert_almost_equal(p, 1./9/8) + + def test_gengamma_logpdf_broadcasting_gh24574(self): + # gh-24574 reported a broadcasting error when `x` included 0s. + assert_allclose(stats.gengamma.logpdf([0, 1, 1], 1, -1), [-np.inf, -1, -1]) + + +def test_ksone_fit_freeze(): + # Regression test for ticket #1638. + d = np.array( + [-0.18879233, 0.15734249, 0.18695107, 0.27908787, -0.248649, + -0.2171497, 0.12233512, 0.15126419, 0.03119282, 0.4365294, + 0.08930393, -0.23509903, 0.28231224, -0.09974875, -0.25196048, + 0.11102028, 0.1427649, 0.10176452, 0.18754054, 0.25826724, + 0.05988819, 0.0531668, 0.21906056, 0.32106729, 0.2117662, + 0.10886442, 0.09375789, 0.24583286, -0.22968366, -0.07842391, + -0.31195432, -0.21271196, 0.1114243, -0.13293002, 0.01331725, + -0.04330977, -0.09485776, -0.28434547, 0.22245721, -0.18518199, + -0.10943985, -0.35243174, 0.06897665, -0.03553363, -0.0701746, + -0.06037974, 0.37670779, -0.21684405]) + + with np.errstate(invalid='ignore'): + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", + "The maximum number of subdivisions .50. has been achieved.", + IntegrationWarning, + ) + warnings.filterwarnings( + "ignore", + "floating point number truncated to an integer", + RuntimeWarning, + ) + stats.ksone.fit(d) + + +@pytest.mark.xfail(platform.machine() == "sparc64", + reason="complex special functions known to fail on sparc64 (gh-22577)") +def test_norm_logcdf(): + # Test precision of the logcdf of the normal distribution. + # This precision was enhanced in ticket 1614. + x = -np.asarray(list(range(0, 120, 4))) + # Values from R + expected = [-0.69314718, -10.36010149, -35.01343716, -75.41067300, + -131.69539607, -203.91715537, -292.09872100, -396.25241451, + -516.38564863, -652.50322759, -804.60844201, -972.70364403, + -1156.79057310, -1356.87055173, -1572.94460885, -1805.01356068, + -2053.07806561, -2317.13866238, -2597.19579746, -2893.24984493, + -3205.30112136, -3533.34989701, -3877.39640444, -4237.44084522, + -4613.48339520, -5005.52420869, -5413.56342187, -5837.60115548, + -6277.63751711, -6733.67260303] + + assert_allclose(stats.norm().logcdf(x), expected, atol=1e-8) + + # also test the complex-valued code path + assert_allclose(stats.norm().logcdf(x + 1e-14j).real, expected, atol=1e-8) + + # test the accuracy: d(logcdf)/dx = pdf / cdf \equiv exp(logpdf - logcdf) + deriv = (stats.norm.logcdf(x + 1e-10j)/1e-10).imag + deriv_expected = np.exp(stats.norm.logpdf(x) - stats.norm.logcdf(x)) + assert_allclose(deriv, deriv_expected, atol=1e-10) + + +def test_levy_l_sf(): + # Test levy_l.sf for small arguments. + x = np.array([-0.016, -0.01, -0.005, -0.0015]) + # Expected values were calculated with mpmath. + expected = np.array([2.6644463892359302e-15, + 1.523970604832107e-23, + 2.0884875837625492e-45, + 5.302850374626878e-147]) + y = stats.levy_l.sf(x) + assert_allclose(y, expected, rtol=1e-13) + + +def test_levy_l_isf(): + # Test roundtrip sf(isf(p)), including a small input value. + p = np.array([3.0e-15, 0.25, 0.99]) + x = stats.levy_l.isf(p) + q = stats.levy_l.sf(x) + assert_allclose(q, p, rtol=5e-14) + + +def test_hypergeom_interval_1802(): + # these two had endless loops + assert_equal(stats.hypergeom.interval(.95, 187601, 43192, 757), + (152.0, 197.0)) + assert_equal(stats.hypergeom.interval(.945, 187601, 43192, 757), + (152.0, 197.0)) + # this was working also before + assert_equal(stats.hypergeom.interval(.94, 187601, 43192, 757), + (153.0, 196.0)) + + # degenerate case .a == .b + assert_equal(stats.hypergeom.ppf(0.02, 100, 100, 8), 8) + assert_equal(stats.hypergeom.ppf(1, 100, 100, 8), 8) + + +def test_distribution_too_many_args(): + rng = np.random.default_rng(5976604568) + # Check that a TypeError is raised when too many args are given to a method + # Regression test for ticket 1815. + x = np.linspace(0.1, 0.7, num=5) + assert_raises(TypeError, stats.gamma.pdf, x, 2, 3, loc=1.0) + assert_raises(TypeError, stats.gamma.pdf, x, 2, 3, 4, loc=1.0) + assert_raises(TypeError, stats.gamma.pdf, x, 2, 3, 4, 5) + assert_raises(TypeError, stats.gamma.pdf, x, 2, 3, loc=1.0, scale=0.5) + assert_raises(TypeError, stats.gamma.rvs, 2., 3, loc=1.0, scale=0.5, + random_state=rng) + assert_raises(TypeError, stats.gamma.cdf, x, 2., 3, loc=1.0, scale=0.5) + assert_raises(TypeError, stats.gamma.ppf, x, 2., 3, loc=1.0, scale=0.5) + assert_raises(TypeError, stats.gamma.stats, 2., 3, loc=1.0, scale=0.5) + assert_raises(TypeError, stats.gamma.entropy, 2., 3, loc=1.0, scale=0.5) + assert_raises(TypeError, stats.gamma.fit, x, 2., 3, loc=1.0, scale=0.5) + + # These should not give errors + stats.gamma.pdf(x, 2, 3) # loc=3 + stats.gamma.pdf(x, 2, 3, 4) # loc=3, scale=4 + stats.gamma.stats(2., 3) + stats.gamma.stats(2., 3, 4) + stats.gamma.stats(2., 3, 4, 'mv') + stats.gamma.rvs(2., 3, 4, 5, random_state=rng) + stats.gamma.fit(stats.gamma.rvs(2., size=7, random_state=rng), 2.) + + # Also for a discrete distribution + stats.geom.pmf(x, 2, loc=3) # no error, loc=3 + assert_raises(TypeError, stats.geom.pmf, x, 2, 3, 4) + assert_raises(TypeError, stats.geom.pmf, x, 2, 3, loc=4) + + # And for distributions with 0, 2 and 3 args respectively + assert_raises(TypeError, stats.expon.pdf, x, 3, loc=1.0) + assert_raises(TypeError, stats.exponweib.pdf, x, 3, 4, 5, loc=1.0) + assert_raises(TypeError, stats.exponweib.pdf, x, 3, 4, 5, 0.1, 0.1) + assert_raises(TypeError, stats.ncf.pdf, x, 3, 4, 5, 6, loc=1.0) + assert_raises(TypeError, stats.ncf.pdf, x, 3, 4, 5, 6, 1.0, scale=0.5) + stats.ncf.pdf(x, 3, 4, 5, 6, 1.0) # 3 args, plus loc/scale + + +def test_ncx2_tails_ticket_955(): + # Trac #955 -- check that the cdf computed by special functions + # matches the integrated pdf + a = stats.ncx2.cdf(np.arange(20, 25, 0.2), 2, 1.07458615e+02) + b = stats.ncx2._cdfvec(np.arange(20, 25, 0.2), 2, 1.07458615e+02) + assert_allclose(a, b, rtol=1e-3, atol=0) + + +def test_ncx2_tails_pdf(): + # ncx2.pdf does not return nans in extreme tails(example from gh-1577) + # NB: this is to check that nan_to_num is not needed in ncx2.pdf + with warnings.catch_warnings(): + warnings.simplefilter('error', RuntimeWarning) + assert_equal(stats.ncx2.pdf(1, np.arange(340, 350), 2), 0) + logval = stats.ncx2.logpdf(1, np.arange(340, 350), 2) + + assert_(np.isneginf(logval).all()) + + # Verify logpdf has extended precision when pdf underflows to 0 + with warnings.catch_warnings(): + warnings.simplefilter('error', RuntimeWarning) + assert_equal(stats.ncx2.pdf(10000, 3, 12), 0) + assert_allclose(stats.ncx2.logpdf(10000, 3, 12), -4662.444377524883) + + +@pytest.mark.parametrize('method, expected', [ + ('cdf', np.array([2.497951336e-09, 3.437288941e-10])), + ('pdf', np.array([1.238579980e-07, 1.710041145e-08])), + ('logpdf', np.array([-15.90413011, -17.88416331])), + ('ppf', np.array([4.865182052, 7.017182271])) +]) +def test_ncx2_zero_nc(method, expected): + # gh-5441 + # ncx2 with nc=0 is identical to chi2 + # Comparison to R (v3.5.1) + # > options(digits=10) + # > pchisq(0.1, df=10, ncp=c(0,4)) + # > dchisq(0.1, df=10, ncp=c(0,4)) + # > dchisq(0.1, df=10, ncp=c(0,4), log=TRUE) + # > qchisq(0.1, df=10, ncp=c(0,4)) + + result = getattr(stats.ncx2, method)(0.1, nc=[0, 4], df=10) + assert_allclose(result, expected, atol=1e-15) + + +def test_ncx2_zero_nc_rvs(): + # gh-5441 + # ncx2 with nc=0 is identical to chi2 + result = stats.ncx2.rvs(df=10, nc=0, random_state=1) + expected = stats.chi2.rvs(df=10, random_state=1) + assert_allclose(result, expected, atol=1e-15) + + +def test_ncx2_gh12731(): + # test that gh-12731 is resolved; previously these were all 0.5 + nc = 10**np.arange(5, 10) + assert_equal(stats.ncx2.cdf(1e4, df=1, nc=nc), 0) + + +def test_ncx2_gh8665(): + # test that gh-8665 is resolved; previously this tended to nonzero value + x = np.array([4.99515382e+00, 1.07617327e+01, 2.31854502e+01, + 4.99515382e+01, 1.07617327e+02, 2.31854502e+02, + 4.99515382e+02, 1.07617327e+03, 2.31854502e+03, + 4.99515382e+03, 1.07617327e+04, 2.31854502e+04, + 4.99515382e+04]) + nu, lam = 20, 499.51538166556196 + + sf = stats.ncx2.sf(x, df=nu, nc=lam) + # computed in R. Couldn't find a survival function implementation + # options(digits=16) + # x <- c(4.99515382e+00, 1.07617327e+01, 2.31854502e+01, 4.99515382e+01, + # 1.07617327e+02, 2.31854502e+02, 4.99515382e+02, 1.07617327e+03, + # 2.31854502e+03, 4.99515382e+03, 1.07617327e+04, 2.31854502e+04, + # 4.99515382e+04) + # nu <- 20 + # lam <- 499.51538166556196 + # 1 - pchisq(x, df = nu, ncp = lam) + sf_expected = [1.0000000000000000, 1.0000000000000000, 1.0000000000000000, + 1.0000000000000000, 1.0000000000000000, 0.9999999999999888, + 0.6646525582135460, 0.0000000000000000, 0.0000000000000000, + 0.0000000000000000, 0.0000000000000000, 0.0000000000000000, + 0.0000000000000000] + assert_allclose(sf, sf_expected, atol=1e-12) + + +def test_ncx2_gh11777(): + # regression test for gh-11777: + # At high values of degrees of freedom df, ensure the pdf of ncx2 does + # not get clipped to zero when the non-centrality parameter is + # sufficiently less than df + df = 6700 + nc = 5300 + x = np.linspace(stats.ncx2.ppf(0.001, df, nc), + stats.ncx2.ppf(0.999, df, nc), num=10000) + ncx2_pdf = stats.ncx2.pdf(x, df, nc) + gauss_approx = stats.norm.pdf(x, df + nc, np.sqrt(2 * df + 4 * nc)) + # use huge tolerance as we're only looking for obvious discrepancy + assert_allclose(ncx2_pdf, gauss_approx, atol=1e-4) + + +# Expected values for foldnorm.sf were computed with mpmath: +# +# from mpmath import mp +# mp.dps = 60 +# def foldcauchy_sf(x, c): +# x = mp.mpf(x) +# c = mp.mpf(c) +# return mp.one - (mp.atan(x - c) + mp.atan(x + c))/mp.pi +# +# E.g. +# +# >>> float(foldcauchy_sf(2, 1)) +# 0.35241638234956674 +# +@pytest.mark.parametrize('x, c, expected', + [(2, 1, 0.35241638234956674), + (2, 2, 0.5779791303773694), + (1e13, 1, 6.366197723675813e-14), + (2e16, 1, 3.183098861837907e-17), + (1e13, 2e11, 6.368745221764519e-14), + (0.125, 200, 0.999998010612169)]) +def test_foldcauchy_sf(x, c, expected): + sf = stats.foldcauchy.sf(x, c) + assert_allclose(sf, expected, 2e-15) + + +# The same mpmath code shown in the comments above test_foldcauchy_sf() +# is used to create these expected values. +@pytest.mark.parametrize('x, expected', + [(2, 0.2951672353008665), + (1e13, 6.366197723675813e-14), + (2e16, 3.183098861837907e-17), + (5e80, 1.2732395447351629e-81)]) +def test_halfcauchy_sf(x, expected): + sf = stats.halfcauchy.sf(x) + assert_allclose(sf, expected, 2e-15) + + +# Expected value computed with mpmath: +# expected = mp.cot(mp.pi*p/2) +@pytest.mark.parametrize('p, expected', + [(0.9999995, 7.853981633329977e-07), + (0.975, 0.039290107007669675), + (0.5, 1.0), + (0.01, 63.65674116287158), + (1e-14, 63661977236758.13), + (5e-80, 1.2732395447351627e+79)]) +def test_halfcauchy_isf(p, expected): + x = stats.halfcauchy.isf(p) + assert_allclose(x, expected) + + +def test_foldnorm_zero(): + # Parameter value c=0 was not enabled, see gh-2399. + rv = stats.foldnorm(0, scale=1) + assert_equal(rv.cdf(0), 0) # rv.cdf(0) previously resulted in: nan + + +# Expected values for foldnorm.sf were computed with mpmath: +# +# from mpmath import mp +# mp.dps = 60 +# def foldnorm_sf(x, c): +# x = mp.mpf(x) +# c = mp.mpf(c) +# return mp.ncdf(-x+c) + mp.ncdf(-x-c) +# +# E.g. +# +# >>> float(foldnorm_sf(2, 1)) +# 0.16000515196308715 +# +@pytest.mark.parametrize('x, c, expected', + [(2, 1, 0.16000515196308715), + (20, 1, 8.527223952630977e-81), + (10, 15, 0.9999997133484281), + (25, 15, 7.619853024160525e-24)]) +def test_foldnorm_sf(x, c, expected): + sf = stats.foldnorm.sf(x, c) + assert_allclose(sf, expected, 1e-14) + + +def test_stats_shapes_argcheck(): + # stats method was failing for vector shapes if some of the values + # were outside of the allowed range, see gh-2678 + mv3 = stats.invgamma.stats([0.0, 0.5, 1.0], 1, 0.5) # 0 is not a legal `a` + mv2 = stats.invgamma.stats([0.5, 1.0], 1, 0.5) + mv2_augmented = tuple(np.r_[np.nan, _] for _ in mv2) + assert_equal(mv2_augmented, mv3) + + # -1 is not a legal shape parameter + mv3 = stats.lognorm.stats([2, 2.4, -1]) + mv2 = stats.lognorm.stats([2, 2.4]) + mv2_augmented = tuple(np.r_[_, np.nan] for _ in mv2) + assert_equal(mv2_augmented, mv3) + + # FIXME: this is only a quick-and-dirty test of a quick-and-dirty bugfix. + # stats method with multiple shape parameters is not properly vectorized + # anyway, so some distributions may or may not fail. + + +# Test subclassing distributions w/ explicit shapes + +class _distr_gen(stats.rv_continuous): + def _pdf(self, x, a): + return 42 + + +class _distr2_gen(stats.rv_continuous): + def _cdf(self, x, a): + return 42 * a + x + + +class _distr3_gen(stats.rv_continuous): + def _pdf(self, x, a, b): + return a + b + + def _cdf(self, x, a): + # Different # of shape params from _pdf, to be able to check that + # inspection catches the inconsistency. + return 42 * a + x + + +class _distr6_gen(stats.rv_continuous): + # Two shape parameters (both _pdf and _cdf defined, consistent shapes.) + def _pdf(self, x, a, b): + return a*x + b + + def _cdf(self, x, a, b): + return 42 * a + x + + +class TestSubclassingExplicitShapes: + # Construct a distribution w/ explicit shapes parameter and test it. + + def test_correct_shapes(self): + dummy_distr = _distr_gen(name='dummy', shapes='a') + assert_equal(dummy_distr.pdf(1, a=1), 42) + + def test_wrong_shapes_1(self): + dummy_distr = _distr_gen(name='dummy', shapes='A') + assert_raises(TypeError, dummy_distr.pdf, 1, **dict(a=1)) + + def test_wrong_shapes_2(self): + dummy_distr = _distr_gen(name='dummy', shapes='a, b, c') + dct = dict(a=1, b=2, c=3) + assert_raises(TypeError, dummy_distr.pdf, 1, **dct) + + def test_shapes_string(self): + # shapes must be a string + dct = dict(name='dummy', shapes=42) + assert_raises(TypeError, _distr_gen, **dct) + + def test_shapes_identifiers_1(self): + # shapes must be a comma-separated list of valid python identifiers + dct = dict(name='dummy', shapes='(!)') + assert_raises(SyntaxError, _distr_gen, **dct) + + def test_shapes_identifiers_2(self): + dct = dict(name='dummy', shapes='4chan') + assert_raises(SyntaxError, _distr_gen, **dct) + + def test_shapes_identifiers_3(self): + dct = dict(name='dummy', shapes='m(fti)') + assert_raises(SyntaxError, _distr_gen, **dct) + + def test_shapes_identifiers_nodefaults(self): + dct = dict(name='dummy', shapes='a=2') + assert_raises(SyntaxError, _distr_gen, **dct) + + def test_shapes_args(self): + dct = dict(name='dummy', shapes='*args') + assert_raises(SyntaxError, _distr_gen, **dct) + + def test_shapes_kwargs(self): + dct = dict(name='dummy', shapes='**kwargs') + assert_raises(SyntaxError, _distr_gen, **dct) + + def test_shapes_keywords(self): + # python keywords cannot be used for shape parameters + dct = dict(name='dummy', shapes='a, b, c, lambda') + assert_raises(SyntaxError, _distr_gen, **dct) + + def test_shapes_signature(self): + # test explicit shapes which agree w/ the signature of _pdf + class _dist_gen(stats.rv_continuous): + def _pdf(self, x, a): + return stats.norm._pdf(x) * a + + dist = _dist_gen(shapes='a') + assert_equal(dist.pdf(0.5, a=2), stats.norm.pdf(0.5)*2) + + def test_shapes_signature_inconsistent(self): + # test explicit shapes which do not agree w/ the signature of _pdf + class _dist_gen(stats.rv_continuous): + def _pdf(self, x, a): + return stats.norm._pdf(x) * a + + dist = _dist_gen(shapes='a, b') + assert_raises(TypeError, dist.pdf, 0.5, **dict(a=1, b=2)) + + def test_star_args(self): + # test _pdf with only starargs + # NB: **kwargs of pdf will never reach _pdf + class _dist_gen(stats.rv_continuous): + def _pdf(self, x, *args): + extra_kwarg = args[0] + return stats.norm._pdf(x) * extra_kwarg + + dist = _dist_gen(shapes='extra_kwarg') + assert_equal(dist.pdf(0.5, extra_kwarg=33), stats.norm.pdf(0.5)*33) + assert_equal(dist.pdf(0.5, 33), stats.norm.pdf(0.5)*33) + assert_raises(TypeError, dist.pdf, 0.5, **dict(xxx=33)) + + def test_star_args_2(self): + # test _pdf with named & starargs + # NB: **kwargs of pdf will never reach _pdf + class _dist_gen(stats.rv_continuous): + def _pdf(self, x, offset, *args): + extra_kwarg = args[0] + return stats.norm._pdf(x) * extra_kwarg + offset + + dist = _dist_gen(shapes='offset, extra_kwarg') + assert_equal(dist.pdf(0.5, offset=111, extra_kwarg=33), + stats.norm.pdf(0.5)*33 + 111) + assert_equal(dist.pdf(0.5, 111, 33), + stats.norm.pdf(0.5)*33 + 111) + + def test_extra_kwarg(self): + # **kwargs to _pdf are ignored. + # this is a limitation of the framework (_pdf(x, *goodargs)) + class _distr_gen(stats.rv_continuous): + def _pdf(self, x, *args, **kwargs): + # _pdf should handle *args, **kwargs itself. Here "handling" + # is ignoring *args and looking for ``extra_kwarg`` and using + # that. + extra_kwarg = kwargs.pop('extra_kwarg', 1) + return stats.norm._pdf(x) * extra_kwarg + + dist = _distr_gen(shapes='extra_kwarg') + assert_equal(dist.pdf(1, extra_kwarg=3), stats.norm.pdf(1)) + + def test_shapes_empty_string(self): + # shapes='' is equivalent to shapes=None + class _dist_gen(stats.rv_continuous): + def _pdf(self, x): + return stats.norm.pdf(x) + + dist = _dist_gen(shapes='') + assert_equal(dist.pdf(0.5), stats.norm.pdf(0.5)) + + +class TestSubclassingNoShapes: + # Construct a distribution w/o explicit shapes parameter and test it. + + def test_only__pdf(self): + dummy_distr = _distr_gen(name='dummy') + assert_equal(dummy_distr.pdf(1, a=1), 42) + + def test_only__cdf(self): + # _pdf is determined from _cdf by taking numerical derivative + dummy_distr = _distr2_gen(name='dummy') + assert_almost_equal(dummy_distr.pdf(1, a=1), 1) + + @pytest.mark.skipif(DOCSTRINGS_STRIPPED, reason="docstring stripped") + def test_signature_inspection(self): + # check that _pdf signature inspection works correctly, and is used in + # the class docstring + dummy_distr = _distr_gen(name='dummy') + assert_equal(dummy_distr.numargs, 1) + assert_equal(dummy_distr.shapes, 'a') + res = re.findall(r'logpdf\(x, a, loc=0, scale=1\)', + dummy_distr.__doc__) + assert_(len(res) == 1) + + @pytest.mark.skipif(DOCSTRINGS_STRIPPED, reason="docstring stripped") + def test_signature_inspection_2args(self): + # same for 2 shape params and both _pdf and _cdf defined + dummy_distr = _distr6_gen(name='dummy') + assert_equal(dummy_distr.numargs, 2) + assert_equal(dummy_distr.shapes, 'a, b') + res = re.findall(r'logpdf\(x, a, b, loc=0, scale=1\)', + dummy_distr.__doc__) + assert_(len(res) == 1) + + def test_signature_inspection_2args_incorrect_shapes(self): + # both _pdf and _cdf defined, but shapes are inconsistent: raises + assert_raises(TypeError, _distr3_gen, name='dummy') + + def test_defaults_raise(self): + # default arguments should raise + class _dist_gen(stats.rv_continuous): + def _pdf(self, x, a=42): + return 42 + assert_raises(TypeError, _dist_gen, **dict(name='dummy')) + + def test_starargs_raise(self): + # without explicit shapes, *args are not allowed + class _dist_gen(stats.rv_continuous): + def _pdf(self, x, a, *args): + return 42 + assert_raises(TypeError, _dist_gen, **dict(name='dummy')) + + def test_kwargs_raise(self): + # without explicit shapes, **kwargs are not allowed + class _dist_gen(stats.rv_continuous): + def _pdf(self, x, a, **kwargs): + return 42 + assert_raises(TypeError, _dist_gen, **dict(name='dummy')) + + +@pytest.mark.skipif(DOCSTRINGS_STRIPPED, reason="docstring stripped") +def test_docstrings(): + badones = [r',\s*,', r'\(\s*,', r'^\s*:'] + for distname in stats.__all__: + dist = getattr(stats, distname) + if isinstance(dist, (stats.rv_discrete | stats.rv_continuous)): + for regex in badones: + assert_(re.search(regex, dist.__doc__) is None) + + +def test_infinite_input(): + assert_almost_equal(stats.skellam.sf(np.inf, 10, 11), 0) + assert_almost_equal(stats.ncx2._cdf(np.inf, 8, 0.1), 1) + + +def test_lomax_accuracy(): + # regression test for gh-4033 + p = stats.lomax.ppf(stats.lomax.cdf(1e-100, 1), 1) + assert_allclose(p, 1e-100) + + +def test_truncexpon_accuracy(): + # regression test for gh-4035 + p = stats.truncexpon.ppf(stats.truncexpon.cdf(1e-100, 1), 1) + assert_allclose(p, 1e-100) + + +def test_rayleigh_accuracy(): + # regression test for gh-4034 + p = stats.rayleigh.isf(stats.rayleigh.sf(9, 1), 1) + assert_almost_equal(p, 9.0, decimal=15) + + +def test_genextreme_give_no_warnings(): + """regression test for gh-6219""" + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + + stats.genextreme.cdf(.5, 0) + stats.genextreme.pdf(.5, 0) + stats.genextreme.ppf(.5, 0) + stats.genextreme.logpdf(-np.inf, 0.0) + number_of_warnings_thrown = len(w) + assert_equal(number_of_warnings_thrown, 0) + + +def test_moments_gh22400(): + # Regression test for gh-22400 + # Check for correct results at c=0 with no warnings. While we're at it, + # check that NaN and sufficiently negative input produce NaNs, and output + # with `c=1` also agrees with reference values. + res = np.asarray(stats.genextreme.stats([0.0, np.nan, 1, -1.5], moments='mvsk')) + + # Reference values for c=0 (Wikipedia) + mean = np.euler_gamma + var = np.pi**2 / 6 + skew = 12 * np.sqrt(6) * special.zeta(3) / np.pi**3 + kurt = 12 / 5 + ref_0 = [mean, var, skew, kurt] + ref_1 = ref_3 = [np.nan]*4 + ref_2 = [0, 1, -2, 6] # Wolfram Alpha, MaxStableDistribution[0, 1, -1] + + assert_allclose(res[:, 0], ref_0, rtol=1e-14) + assert_equal(res[:, 1], ref_1) + assert_allclose(res[:, 2], ref_2, rtol=1e-14) + assert_equal(res[:, 3], ref_3) + + +def test_genextreme_entropy(): + # regression test for gh-5181 + euler_gamma = 0.5772156649015329 + + h = stats.genextreme.entropy(-1.0) + assert_allclose(h, 2*euler_gamma + 1, rtol=1e-14) + + h = stats.genextreme.entropy(0) + assert_allclose(h, euler_gamma + 1, rtol=1e-14) + + h = stats.genextreme.entropy(1.0) + assert_equal(h, 1) + + h = stats.genextreme.entropy(-2.0, scale=10) + assert_allclose(h, euler_gamma*3 + np.log(10) + 1, rtol=1e-14) + + h = stats.genextreme.entropy(10) + assert_allclose(h, -9*euler_gamma + 1, rtol=1e-14) + + h = stats.genextreme.entropy(-10) + assert_allclose(h, 11*euler_gamma + 1, rtol=1e-14) + + +def test_genextreme_sf_isf(): + # Expected values were computed using mpmath: + # + # import mpmath + # + # def mp_genextreme_sf(x, xi, mu=0, sigma=1): + # # Formula from wikipedia, which has a sign convention for xi that + # # is the opposite of scipy's shape parameter. + # if xi != 0: + # t = mpmath.power(1 + ((x - mu)/sigma)*xi, -1/xi) + # else: + # t = mpmath.exp(-(x - mu)/sigma) + # return 1 - mpmath.exp(-t) + # + # >>> mpmath.mp.dps = 1000 + # >>> s = mp_genextreme_sf(mpmath.mp.mpf("1e8"), mpmath.mp.mpf("0.125")) + # >>> float(s) + # 1.6777205262585625e-57 + # >>> s = mp_genextreme_sf(mpmath.mp.mpf("7.98"), mpmath.mp.mpf("-0.125")) + # >>> float(s) + # 1.52587890625e-21 + # >>> s = mp_genextreme_sf(mpmath.mp.mpf("7.98"), mpmath.mp.mpf("0")) + # >>> float(s) + # 0.00034218086528426593 + + x = 1e8 + s = stats.genextreme.sf(x, -0.125) + assert_allclose(s, 1.6777205262585625e-57) + x2 = stats.genextreme.isf(s, -0.125) + assert_allclose(x2, x) + + x = 7.98 + s = stats.genextreme.sf(x, 0.125) + assert_allclose(s, 1.52587890625e-21) + x2 = stats.genextreme.isf(s, 0.125) + assert_allclose(x2, x) + + x = 7.98 + s = stats.genextreme.sf(x, 0) + assert_allclose(s, 0.00034218086528426593) + x2 = stats.genextreme.isf(s, 0) + assert_allclose(x2, x) + + +def test_burr12_ppf_small_arg(): + prob = 1e-16 + quantile = stats.burr12.ppf(prob, 2, 3) + # The expected quantile was computed using mpmath: + # >>> import mpmath + # >>> mpmath.mp.dps = 100 + # >>> prob = mpmath.mpf('1e-16') + # >>> c = mpmath.mpf(2) + # >>> d = mpmath.mpf(3) + # >>> float(((1-prob)**(-1/d) - 1)**(1/c)) + # 5.7735026918962575e-09 + assert_allclose(quantile, 5.7735026918962575e-09) + + +def test_invweibull_fit(): + """ + Test fitting invweibull to data. + + Here is a the same calculation in R: + + > library(evd) + > library(fitdistrplus) + > x = c(1, 1.25, 2, 2.5, 2.8, 3, 3.8, 4, 5, 8, 10, 12, 64, 99) + > result = fitdist(x, 'frechet', control=list(reltol=1e-13), + + fix.arg=list(loc=0), start=list(shape=2, scale=3)) + > result + Fitting of the distribution ' frechet ' by maximum likelihood + Parameters: + estimate Std. Error + shape 1.048482 0.2261815 + scale 3.099456 0.8292887 + Fixed parameters: + value + loc 0 + + """ + + def optimizer(func, x0, args=(), disp=0): + return fmin(func, x0, args=args, disp=disp, xtol=1e-12, ftol=1e-12) + + x = np.array([1, 1.25, 2, 2.5, 2.8, 3, 3.8, 4, 5, 8, 10, 12, 64, 99]) + c, loc, scale = stats.invweibull.fit(x, floc=0, optimizer=optimizer) + assert_allclose(c, 1.048482, rtol=5e-6) + assert loc == 0 + assert_allclose(scale, 3.099456, rtol=5e-6) + + +# Expected values were computed with mpmath. +@pytest.mark.parametrize('x, c, expected', + [(3, 1.5, 0.175064510070713299327), + (2000, 1.5, 1.11802773877318715787e-5), + (2000, 9.25, 2.92060308832269637092e-31), + (1e15, 1.5, 3.16227766016837933199884e-23)]) +def test_invweibull_sf(x, c, expected): + computed = stats.invweibull.sf(x, c) + assert_allclose(computed, expected, rtol=1e-15) + + +# Expected values were computed with mpmath. +@pytest.mark.parametrize('p, c, expected', + [(0.5, 2.5, 1.15789669836468183976), + (3e-18, 5, 3195.77171838060906447)]) +def test_invweibull_isf(p, c, expected): + computed = stats.invweibull.isf(p, c) + assert_allclose(computed, expected, rtol=1e-15) + + +@pytest.mark.parametrize( + 'df1,df2,x', + [(2, 2, [-0.5, 0.2, 1.0, 2.3]), + (4, 11, [-0.5, 0.2, 1.0, 2.3]), + (7, 17, [1, 2, 3, 4, 5])] +) +def test_ncf_edge_case(df1, df2, x): + # Test for edge case described in gh-11660. + # Non-central Fisher distribution when nc = 0 + # should be the same as Fisher distribution. + nc = 0 + expected_cdf = stats.f.cdf(x, df1, df2) + calculated_cdf = stats.ncf.cdf(x, df1, df2, nc) + assert_allclose(expected_cdf, calculated_cdf, rtol=1e-14) + + # when ncf_gen._skip_pdf will be used instead of generic pdf, + # this additional test will be useful. + expected_pdf = stats.f.pdf(x, df1, df2) + calculated_pdf = stats.ncf.pdf(x, df1, df2, nc) + assert_allclose(expected_pdf, calculated_pdf, rtol=1e-6) + + +def test_ncf_variance(): + # Regression test for gh-10658 (incorrect variance formula for ncf). + # The correct value of ncf.var(2, 6, 4), 42.75, can be verified with, for + # example, Wolfram Alpha with the expression + # Variance[NoncentralFRatioDistribution[2, 6, 4]] + # or with the implementation of the noncentral F distribution in the C++ + # library Boost. + v = stats.ncf.var(2, 6, 4) + assert_allclose(v, 42.75, rtol=1e-14) + + +def test_ncf_cdf_spotcheck(): + # Regression test for gh-15582 testing against values from R/MATLAB + # Generate check_val from R or MATLAB as follows: + # R: pf(20, df1 = 6, df2 = 33, ncp = 30.4) = 0.998921 + # MATLAB: ncfcdf(20, 6, 33, 30.4) = 0.998921 + scipy_val = stats.ncf.cdf(20, 6, 33, 30.4) + check_val = 0.998921 + assert_allclose(check_val, np.round(scipy_val, decimals=6)) + + +def test_ncf_ppf_issue_17026(): + # Regression test for gh-17026 + x = np.linspace(0, 1, 600) + x[0] = 1e-16 + par = (0.1, 2, 5, 0, 1) + q = stats.ncf.ppf(x, *par) + q0 = [stats.ncf.ppf(xi, *par) for xi in x] + assert_allclose(q, q0) + + +class TestHistogram: + def setup_method(self): + self.rng = np.random.default_rng(6561174822) + + # We have 8 bins + # [1,2), [2,3), [3,4), [4,5), [5,6), [6,7), [7,8), [8,9) + # But actually np.histogram will put the last 9 also in the [8,9) bin! + # Therefore there is a slight difference below for the last bin, from + # what you might have expected. + histogram = np.histogram([1, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 7, 7, 7, 8, 8, 9], bins=8) + self.template = stats.rv_histogram(histogram) + + data = stats.norm.rvs(loc=1.0, scale=2.5, size=10000, random_state=self.rng) + norm_histogram = np.histogram(data, bins=50) + self.norm_template = stats.rv_histogram(norm_histogram) + + def test_pdf(self): + values = np.array([0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, + 5.0, 5.5, 6.0, 6.5, 7.0, 7.5, 8.0, 8.5, 9.0, 9.5]) + pdf_values = np.asarray([0.0/25.0, 0.0/25.0, 1.0/25.0, 1.0/25.0, + 2.0/25.0, 2.0/25.0, 3.0/25.0, 3.0/25.0, + 4.0/25.0, 4.0/25.0, 5.0/25.0, 5.0/25.0, + 4.0/25.0, 4.0/25.0, 3.0/25.0, 3.0/25.0, + 3.0/25.0, 3.0/25.0, 0.0/25.0, 0.0/25.0]) + assert_allclose(self.template.pdf(values), pdf_values) + + # Test explicitly the corner cases: + # As stated above the pdf in the bin [8,9) is greater than + # one would naively expect because np.histogram putted the 9 + # into the [8,9) bin. + assert_almost_equal(self.template.pdf(8.0), 3.0/25.0) + assert_almost_equal(self.template.pdf(8.5), 3.0/25.0) + # 9 is outside our defined bins [8,9) hence the pdf is already 0 + # for a continuous distribution this is fine, because a single value + # does not have a finite probability! + assert_almost_equal(self.template.pdf(9.0), 0.0/25.0) + assert_almost_equal(self.template.pdf(10.0), 0.0/25.0) + + x = np.linspace(-2, 2, 10) + assert_allclose(self.norm_template.pdf(x), + stats.norm.pdf(x, loc=1.0, scale=2.5), rtol=0.1) + + def test_cdf_ppf(self): + values = np.array([0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, + 5.0, 5.5, 6.0, 6.5, 7.0, 7.5, 8.0, 8.5, 9.0, 9.5]) + cdf_values = np.asarray([0.0/25.0, 0.0/25.0, 0.0/25.0, 0.5/25.0, + 1.0/25.0, 2.0/25.0, 3.0/25.0, 4.5/25.0, + 6.0/25.0, 8.0/25.0, 10.0/25.0, 12.5/25.0, + 15.0/25.0, 17.0/25.0, 19.0/25.0, 20.5/25.0, + 22.0/25.0, 23.5/25.0, 25.0/25.0, 25.0/25.0]) + assert_allclose(self.template.cdf(values), cdf_values) + # First three and last two values in cdf_value are not unique + assert_allclose(self.template.ppf(cdf_values[2:-1]), values[2:-1]) + + # Test of cdf and ppf are inverse functions + x = np.linspace(1.0, 9.0, 100) + assert_allclose(self.template.ppf(self.template.cdf(x)), x) + x = np.linspace(0.0, 1.0, 100) + assert_allclose(self.template.cdf(self.template.ppf(x)), x) + + x = np.linspace(-2, 2, 10) + assert_allclose(self.norm_template.cdf(x), + stats.norm.cdf(x, loc=1.0, scale=2.5), rtol=0.1) + + def test_rvs(self): + N = 10000 + sample = self.template.rvs(size=N, random_state=self.rng) + assert_equal(np.sum(sample < 1.0), 0.0) + assert_allclose(np.sum(sample <= 2.0), 1.0/25.0 * N, rtol=0.2) + assert_allclose(np.sum(sample <= 2.5), 2.0/25.0 * N, rtol=0.2) + assert_allclose(np.sum(sample <= 3.0), 3.0/25.0 * N, rtol=0.1) + assert_allclose(np.sum(sample <= 3.5), 4.5/25.0 * N, rtol=0.1) + assert_allclose(np.sum(sample <= 4.0), 6.0/25.0 * N, rtol=0.1) + assert_allclose(np.sum(sample <= 4.5), 8.0/25.0 * N, rtol=0.1) + assert_allclose(np.sum(sample <= 5.0), 10.0/25.0 * N, rtol=0.05) + assert_allclose(np.sum(sample <= 5.5), 12.5/25.0 * N, rtol=0.05) + assert_allclose(np.sum(sample <= 6.0), 15.0/25.0 * N, rtol=0.05) + assert_allclose(np.sum(sample <= 6.5), 17.0/25.0 * N, rtol=0.05) + assert_allclose(np.sum(sample <= 7.0), 19.0/25.0 * N, rtol=0.05) + assert_allclose(np.sum(sample <= 7.5), 20.5/25.0 * N, rtol=0.05) + assert_allclose(np.sum(sample <= 8.0), 22.0/25.0 * N, rtol=0.05) + assert_allclose(np.sum(sample <= 8.5), 23.5/25.0 * N, rtol=0.05) + assert_allclose(np.sum(sample <= 9.0), 25.0/25.0 * N, rtol=0.05) + assert_allclose(np.sum(sample <= 9.0), 25.0/25.0 * N, rtol=0.05) + assert_equal(np.sum(sample > 9.0), 0.0) + + def test_munp(self): + for n in range(4): + assert_allclose(self.norm_template._munp(n), + stats.norm(1.0, 2.5).moment(n), rtol=0.05) + + def test_entropy(self): + assert_allclose(self.norm_template.entropy(), + stats.norm.entropy(loc=1.0, scale=2.5), rtol=0.05) + + +def test_histogram_non_uniform(): + # Tests rv_histogram works even for non-uniform bin widths + counts, bins = ([1, 1], [0, 1, 1001]) + + dist = stats.rv_histogram((counts, bins), density=False) + np.testing.assert_allclose(dist.pdf([0.5, 200]), [0.5, 0.0005]) + assert dist.median() == 1 + + dist = stats.rv_histogram((counts, bins), density=True) + np.testing.assert_allclose(dist.pdf([0.5, 200]), 1/1001) + assert dist.median() == 1001/2 + + # Omitting density produces a warning for non-uniform bins... + message = "Bin widths are not constant. Assuming..." + with pytest.warns(RuntimeWarning, match=message): + dist = stats.rv_histogram((counts, bins)) + assert dist.median() == 1001/2 # default is like `density=True` + + # ... but not for uniform bins + dist = stats.rv_histogram((counts, [0, 1, 2])) + assert dist.median() == 1 + + +class TestLogUniform: + def test_alias(self): + # This test makes sure that "reciprocal" and "loguniform" are + # aliases of the same distribution and that both are log-uniform + rng = np.random.default_rng(98643218961) + rv = stats.loguniform(10 ** -3, 10 ** 0) + rvs = rv.rvs(size=10000, random_state=rng) + + rng = np.random.default_rng(98643218961) + rv2 = stats.reciprocal(10 ** -3, 10 ** 0) + rvs2 = rv2.rvs(size=10000, random_state=rng) + + assert_allclose(rvs2, rvs) + + vals, _ = np.histogram(np.log10(rvs), bins=10) + assert 900 <= vals.min() <= vals.max() <= 1100 + assert np.abs(np.median(vals) - 1000) <= 10 + + @pytest.mark.parametrize("method", ['mle', 'mm']) + def test_fit_override(self, method): + # loguniform is overparameterized, so check that fit override enforces + # scale=1 unless fscale is provided by the user + rng = np.random.default_rng(98643218961) + rvs = stats.loguniform.rvs(0.1, 1, size=1000, random_state=rng) + + a, b, loc, scale = stats.loguniform.fit(rvs, method=method) + assert scale == 1 + + a, b, loc, scale = stats.loguniform.fit(rvs, fscale=2, method=method) + assert scale == 2 + + def test_overflow(self): + # original formulation had overflow issues; check that this is resolved + # Extensive accuracy tests elsewhere, no need to test all methods + rng = np.random.default_rng(7136519550773909093) + a, b = 1e-200, 1e200 + dist = stats.loguniform(a, b) + + # test roundtrip error + cdf = rng.uniform(0, 1, size=1000) + assert_allclose(dist.cdf(dist.ppf(cdf)), cdf) + rvs = dist.rvs(size=1000, random_state=rng) + assert_allclose(dist.ppf(dist.cdf(rvs)), rvs) + + # test a property of the pdf (and that there is no overflow) + x = 10.**np.arange(-200, 200) + pdf = dist.pdf(x) # no overflow + assert_allclose(pdf[:-1]/pdf[1:], 10) + + # check munp against wikipedia reference + mean = (b - a)/(np.log(b) - np.log(a)) + assert_allclose(dist.mean(), mean) + + +class TestArgus: + def test_argus_rvs_large_chi(self): + # test that the algorithm can handle large values of chi + x = stats.argus.rvs(50, size=500, random_state=325) + assert_almost_equal(stats.argus(50).mean(), x.mean(), decimal=4) + + @pytest.mark.parametrize('chi, random_state', [ + [0.1, 325], # chi <= 0.5: rejection method case 1 + [1.3, 155], # 0.5 < chi <= 1.8: rejection method case 2 + [3.5, 135] # chi > 1.8: transform conditional Gamma distribution + ]) + def test_rvs(self, chi, random_state): + x = stats.argus.rvs(chi, size=500, random_state=random_state) + _, p = stats.kstest(x, "argus", (chi, )) + assert_(p > 0.05) + + @pytest.mark.parametrize('chi', [1e-9, 1e-6]) + def test_rvs_small_chi(self, chi): + # test for gh-11699 => rejection method case 1 can even handle chi=0 + # the CDF of the distribution for chi=0 is 1 - (1 - x**2)**(3/2) + # test rvs against distribution of limit chi=0 + r = stats.argus.rvs(chi, size=500, random_state=890981) + _, p = stats.kstest(r, lambda x: 1 - (1 - x**2)**(3/2)) + assert_(p > 0.05) + + # Expected values were computed with mpmath. + @pytest.mark.parametrize('chi, expected_mean', + [(1, 0.6187026683551835), + (10, 0.984805536783744), + (40, 0.9990617659702923), + (60, 0.9995831885165300), + (99, 0.9998469348663028)]) + def test_mean(self, chi, expected_mean): + m = stats.argus.mean(chi, scale=1) + assert_allclose(m, expected_mean, rtol=1e-13) + + # Expected values were computed with mpmath. + @pytest.mark.parametrize('chi, expected_var, rtol', + [(1, 0.05215651254197807, 1e-13), + (10, 0.00015805472008165595, 1e-11), + (40, 5.877763210262901e-07, 1e-8), + (60, 1.1590179389611416e-07, 1e-8), + (99, 1.5623277006064666e-08, 1e-8)]) + def test_var(self, chi, expected_var, rtol): + v = stats.argus.var(chi, scale=1) + assert_allclose(v, expected_var, rtol=rtol) + + # Expected values were computed with mpmath (code: see gh-13370). + @pytest.mark.parametrize('chi, expected, rtol', + [(0.9, 0.07646314974436118, 1e-14), + (0.5, 0.015429797891863365, 1e-14), + (0.1, 0.0001325825293278049, 1e-14), + (0.01, 1.3297677078224565e-07, 1e-15), + (1e-3, 1.3298072023958999e-10, 1e-14), + (1e-4, 1.3298075973486862e-13, 1e-14), + (1e-6, 1.32980760133771e-19, 1e-14), + (1e-9, 1.329807601338109e-28, 1e-15)]) + def test_argus_phi_small_chi(self, chi, expected, rtol): + assert_allclose(_argus_phi(chi), expected, rtol=rtol) + + # Expected values were computed with mpmath (code: see gh-13370). + @pytest.mark.parametrize( + 'chi, expected', + [(0.5, (0.28414073302940573, 1.2742227939992954, 1.2381254688255896)), + (0.2, (0.296172952995264, 1.2951290588110516, 1.1865767100877576)), + (0.1, (0.29791447523536274, 1.29806307956989, 1.1793168289857412)), + (0.01, (0.2984904104866452, 1.2990283628160553, 1.1769268414080531)), + (1e-3, (0.298496172925224, 1.2990380082487925, 1.176902956021053)), + (1e-4, (0.29849623054991836, 1.2990381047023793, 1.1769027171686324)), + (1e-6, (0.2984962311319278, 1.2990381056765605, 1.1769027147562232)), + (1e-9, (0.298496231131986, 1.299038105676658, 1.1769027147559818))]) + def test_pdf_small_chi(self, chi, expected): + x = np.array([0.1, 0.5, 0.9]) + assert_allclose(stats.argus.pdf(x, chi), expected, rtol=1e-13) + + # Expected values were computed with mpmath (code: see gh-13370). + @pytest.mark.parametrize( + 'chi, expected', + [(0.5, (0.9857660526895221, 0.6616565930168475, 0.08796070398429937)), + (0.2, (0.9851555052359501, 0.6514666238985464, 0.08362690023746594)), + (0.1, (0.9850670974995661, 0.6500061310508574, 0.08302050640683846)), + (0.01, (0.9850378582451867, 0.6495239242251358, 0.08282109244852445)), + (1e-3, (0.9850375656906663, 0.6495191015522573, 0.08281910005231098)), + (1e-4, (0.9850375627651049, 0.6495190533254682, 0.08281908012852317)), + (1e-6, (0.9850375627355568, 0.6495190528383777, 0.08281907992729293)), + (1e-9, (0.9850375627355538, 0.649519052838329, 0.0828190799272728))]) + def test_sf_small_chi(self, chi, expected): + x = np.array([0.1, 0.5, 0.9]) + assert_allclose(stats.argus.sf(x, chi), expected, rtol=1e-14) + + # Expected values were computed with mpmath. + @pytest.mark.parametrize( + 'x, chi, expected', + [(0.9999999, 0.25, 9.113252974162428e-11), + (0.9999999, 3.0, 6.616650419714568e-10), + (0.999999999, 2.5, 4.130195911418939e-13), + (0.999999999, 10.0, 2.3788319094393724e-11)]) + def test_sf_near_1(self, x, chi, expected): + sf = stats.argus.sf(x, chi) + assert_allclose(sf, expected, rtol=5e-15) + + # Expected values were computed with mpmath (code: see gh-13370). + @pytest.mark.parametrize( + 'chi, expected', + [(0.5, (0.0142339473104779, 0.3383434069831524, 0.9120392960157007)), + (0.2, (0.014844494764049919, 0.34853337610145363, 0.916373099762534)), + (0.1, (0.014932902500433911, 0.34999386894914264, 0.9169794935931616)), + (0.01, (0.014962141754813293, 0.35047607577486417, 0.9171789075514756)), + (1e-3, (0.01496243430933372, 0.35048089844774266, 0.917180899947689)), + (1e-4, (0.014962437234895118, 0.3504809466745317, 0.9171809198714769)), + (1e-6, (0.01496243726444329, 0.3504809471616223, 0.9171809200727071)), + (1e-9, (0.014962437264446245, 0.350480947161671, 0.9171809200727272))]) + def test_cdf_small_chi(self, chi, expected): + x = np.array([0.1, 0.5, 0.9]) + assert_allclose(stats.argus.cdf(x, chi), expected, rtol=1e-12) + + # Expected values were computed with mpmath (code: see gh-13370). + @pytest.mark.parametrize( + 'chi, expected, rtol', + [(0.5, (0.5964284712757741, 0.052890651988588604), 1e-12), + (0.101, (0.5893490968089076, 0.053017469847275685), 1e-11), + (0.1, (0.5893431757009437, 0.05301755449499372), 1e-13), + (0.01, (0.5890515677940915, 0.05302167905837031), 1e-13), + (1e-3, (0.5890486520005177, 0.053021719862088104), 1e-13), + (1e-4, (0.5890486228426105, 0.0530217202700811), 1e-13), + (1e-6, (0.5890486225481156, 0.05302172027420182), 1e-13), + (1e-9, (0.5890486225480862, 0.05302172027420224), 1e-13)]) + def test_stats_small_chi(self, chi, expected, rtol): + val = stats.argus.stats(chi, moments='mv') + assert_allclose(val, expected, rtol=rtol) + + +class TestNakagami: + def setup_method(self): + self.rng = np.random.default_rng(9626839526) + + def test_logpdf(self): + # Test nakagami logpdf for an input where the PDF is smaller + # than can be represented with 64 bit floating point. + # The expected value of logpdf was computed with mpmath: + # + # def logpdf(x, nu): + # x = mpmath.mpf(x) + # nu = mpmath.mpf(nu) + # return (mpmath.log(2) + nu*mpmath.log(nu) - + # mpmath.loggamma(nu) + (2*nu - 1)*mpmath.log(x) - + # nu*x**2) + # + nu = 2.5 + x = 25 + logp = stats.nakagami.logpdf(x, nu) + assert_allclose(logp, -1546.9253055607549) + + def test_sf_isf(self): + # Test nakagami sf and isf when the survival function + # value is very small. + # The expected value of the survival function was computed + # with mpmath: + # + # def sf(x, nu): + # x = mpmath.mpf(x) + # nu = mpmath.mpf(nu) + # return mpmath.gammainc(nu, nu*x*x, regularized=True) + # + nu = 2.5 + x0 = 5.0 + sf = stats.nakagami.sf(x0, nu) + assert_allclose(sf, 2.736273158588307e-25, rtol=1e-13) + # Check round trip back to x0. + x1 = stats.nakagami.isf(sf, nu) + assert_allclose(x1, x0, rtol=1e-13) + + def test_logcdf(self): + x = 8 + nu = 0.5 + # Reference value computed with mpmath. + ref = -1.2441921148543576e-15 + logcdf = stats.nakagami.logcdf(x, nu) + assert_allclose(logcdf, ref, rtol=5e-15) + + def test_logsf(self): + x = 0.05 + nu = 12 + # Reference value computed with mpmath. + ref = -1.0791764722337046e-27 + logsf = stats.nakagami.logsf(x, nu) + assert_allclose(logsf, ref, rtol=5e-15) + + @pytest.mark.parametrize("m, ref", + [(5, -0.097341814372152), + (0.5, 0.7257913526447274), + (10, -0.43426184310934907)]) + def test_entropy(self, m, ref): + # from sympy import * + # from mpmath import mp + # import numpy as np + # v, x = symbols('v, x', real=True, positive=True) + # pdf = 2 * v ** v / gamma(v) * x ** (2 * v - 1) * exp(-v * x ** 2) + # h = simplify(simplify(integrate(-pdf * log(pdf), (x, 0, oo)))) + # entropy = lambdify(v, h, 'mpmath') + # mp.dps = 200 + # nu = 5 + # ref = np.float64(entropy(mp.mpf(nu))) + # print(ref) + assert_allclose(stats.nakagami.entropy(m), ref, rtol=1.1e-14) + + @pytest.mark.parametrize("m, ref", + [(1e-100, -5.0e+99), (1e-10, -4999999965.442979), + (9.999e6, -7.333206478668433), (1.001e7, -7.3337562313259825), + (1e10, -10.787134112333835), (1e100, -114.40346329705756)]) + def test_extreme_nu(self, m, ref): + assert_allclose(stats.nakagami.entropy(m), ref) + + def test_entropy_overflow(self): + assert np.isfinite(stats.nakagami._entropy(1e100)) + assert np.isfinite(stats.nakagami._entropy(1e-100)) + + @pytest.mark.parametrize("nu, ref", + [(1e10, 0.9999999999875), + (1e3, 0.9998750078173821), + (1e-10, 1.772453850659802e-05)]) + def test_mean(self, nu, ref): + # reference values were computed with mpmath + # from mpmath import mp + # mp.dps = 500 + # nu = mp.mpf(1e10) + # float(mp.rf(nu, mp.mpf(0.5))/mp.sqrt(nu)) + assert_allclose(stats.nakagami.mean(nu), ref, rtol=1e-12) + + @pytest.mark.xfail(reason="Fit of nakagami not reliable, see gh-10908.") + @pytest.mark.parametrize('nu', [1.6, 2.5, 3.9]) + @pytest.mark.parametrize('loc', [25.0, 10, 35]) + @pytest.mark.parametrize('scale', [13, 5, 20]) + def test_fit(self, nu, loc, scale): + # Regression test for gh-13396 (21/27 cases failed previously) + # The first tuple of the parameters' values is discussed in gh-10908 + N = 100 + samples = stats.nakagami.rvs(size=N, nu=nu, loc=loc, + scale=scale, random_state=self.rng) + nu_est, loc_est, scale_est = stats.nakagami.fit(samples) + assert_allclose(nu_est, nu, rtol=0.2) + assert_allclose(loc_est, loc, rtol=0.2) + assert_allclose(scale_est, scale, rtol=0.2) + + def dlogl_dnu(nu, loc, scale): + return ((-2*nu + 1) * np.sum(1/(samples - loc)) + + 2*nu/scale**2 * np.sum(samples - loc)) + + def dlogl_dloc(nu, loc, scale): + return (N * (1 + np.log(nu) - polygamma(0, nu)) + + 2 * np.sum(np.log((samples - loc) / scale)) + - np.sum(((samples - loc) / scale)**2)) + + def dlogl_dscale(nu, loc, scale): + return (- 2 * N * nu / scale + + 2 * nu / scale ** 3 * np.sum((samples - loc) ** 2)) + + assert_allclose(dlogl_dnu(nu_est, loc_est, scale_est), 0, atol=1e-3) + assert_allclose(dlogl_dloc(nu_est, loc_est, scale_est), 0, atol=1e-3) + assert_allclose(dlogl_dscale(nu_est, loc_est, scale_est), 0, atol=1e-3) + + @pytest.mark.parametrize('loc', [25.0, 10, 35]) + @pytest.mark.parametrize('scale', [13, 5, 20]) + def test_fit_nu(self, loc, scale): + # For nu = 0.5, we have analytical values for + # the MLE of the loc and the scale + nu = 0.5 + n = 100 + samples = stats.nakagami.rvs(size=n, nu=nu, loc=loc, + scale=scale, random_state=self.rng) + nu_est, loc_est, scale_est = stats.nakagami.fit(samples, f0=nu) + + # Analytical values + loc_theo = np.min(samples) + scale_theo = np.sqrt(np.mean((samples - loc_est) ** 2)) + + assert_allclose(nu_est, nu, rtol=1e-7) + assert_allclose(loc_est, loc_theo, rtol=1e-7) + assert_allclose(scale_est, scale_theo, rtol=1e-7) + + +class TestWrapCauchy: + def setup_method(self): + self.rng = np.random.default_rng(2439107123) + + def test_cdf_shape_broadcasting(self): + # Regression test for gh-13791. + # Check that wrapcauchy.cdf broadcasts the shape parameter + # correctly. + c = np.array([[0.03, 0.25], [0.5, 0.75]]) + x = np.array([[1.0], [4.0]]) + p = stats.wrapcauchy.cdf(x, c) + assert p.shape == (2, 2) + scalar_values = [stats.wrapcauchy.cdf(x1, c1) + for (x1, c1) in np.nditer((x, c))] + assert_allclose(p.ravel(), scalar_values, rtol=1e-13) + + def test_cdf_center(self): + p = stats.wrapcauchy.cdf(np.pi, 0.03) + assert_allclose(p, 0.5, rtol=1e-14) + + def test_cdf(self): + x1 = 1.0 # less than pi + x2 = 4.0 # greater than pi + c = 0.75 + p = stats.wrapcauchy.cdf([x1, x2], c) + cr = (1 + c)/(1 - c) + assert_allclose(p[0], np.arctan(cr*np.tan(x1/2))/np.pi) + assert_allclose(p[1], 1 - np.arctan(cr*np.tan(np.pi - x2/2))/np.pi) + + @pytest.mark.parametrize('c', [1e-10, 1e-1]) + @pytest.mark.parametrize('loc', [-100, -2*np.pi, -np.pi, 0, np.pi, 2*np.pi, 100]) + @pytest.mark.parametrize('scale', [1e-10, 1, 1e10]) + def test_rvs_lie_on_circle(self, c, loc, scale): + # Check that the random variates lie in range [0, 2*pi] + x = stats.wrapcauchy.rvs(c=c, loc=loc, scale=scale, + size=1000, random_state=self.rng) + assert np.all(x >= 0) + assert np.all(x <= 2 * np.pi) + + +def test_rvs_no_size_error(): + # _rvs methods must have parameter `size`; see gh-11394 + class rvs_no_size_gen(stats.rv_continuous): + def _rvs(self): + return 1 + + rvs_no_size = rvs_no_size_gen(name='rvs_no_size') + rng = np.random.default_rng(1334886239) + with assert_raises(TypeError, match=r"_rvs\(\) got (an|\d) unexpected"): + rvs_no_size.rvs(random_state=rng) + + +@pytest.mark.parametrize('distname, args', invdistdiscrete + invdistcont) +def test_support_gh13294_regression(distname, args): + if distname in skip_test_support_gh13294_regression: + pytest.skip(f"skipping test for the support method for " + f"distribution {distname}.") + dist = getattr(stats, distname) + # test support method with invalid arguments + if isinstance(dist, stats.rv_continuous): + # test with valid scale + if len(args) != 0: + a0, b0 = dist.support(*args) + assert_equal(a0, np.nan) + assert_equal(b0, np.nan) + # test with invalid scale + # For some distributions, that take no parameters, + # the case of only invalid scale occurs and hence, + # it is implicitly tested in this test case. + loc1, scale1 = 0, -1 + a1, b1 = dist.support(*args, loc1, scale1) + assert_equal(a1, np.nan) + assert_equal(b1, np.nan) + else: + a, b = dist.support(*args) + assert_equal(a, np.nan) + assert_equal(b, np.nan) + + +def test_support_broadcasting_gh13294_regression(): + a0, b0 = stats.norm.support([0, 0, 0, 1], [1, 1, 1, -1]) + ex_a0 = np.array([-np.inf, -np.inf, -np.inf, np.nan]) + ex_b0 = np.array([np.inf, np.inf, np.inf, np.nan]) + assert_equal(a0, ex_a0) + assert_equal(b0, ex_b0) + assert a0.shape == ex_a0.shape + assert b0.shape == ex_b0.shape + + a1, b1 = stats.norm.support([], []) + ex_a1, ex_b1 = np.array([]), np.array([]) + assert_equal(a1, ex_a1) + assert_equal(b1, ex_b1) + assert a1.shape == ex_a1.shape + assert b1.shape == ex_b1.shape + + a2, b2 = stats.norm.support([0, 0, 0, 1], [-1]) + ex_a2 = np.array(4*[np.nan]) + ex_b2 = np.array(4*[np.nan]) + assert_equal(a2, ex_a2) + assert_equal(b2, ex_b2) + assert a2.shape == ex_a2.shape + assert b2.shape == ex_b2.shape + + +def test_stats_broadcasting_gh14953_regression(): + # test case in gh14953 + loc = [0., 0.] + scale = [[1.], [2.], [3.]] + assert_equal(stats.norm.var(loc, scale), [[1., 1.], [4., 4.], [9., 9.]]) + # test some edge cases + loc = np.empty((0, )) + scale = np.empty((1, 0)) + assert stats.norm.var(loc, scale).shape == (1, 0) + + +# Check a few values of the cosine distribution's cdf, sf, ppf and +# isf methods. Expected values were computed with mpmath. + +@pytest.mark.parametrize('x, expected', + [(-3.14159, 4.956444476505336e-19), + (3.14, 0.9999999998928399)]) +def test_cosine_cdf_sf(x, expected): + assert_allclose(stats.cosine.cdf(x), expected) + assert_allclose(stats.cosine.sf(-x), expected) + + +@pytest.mark.parametrize('p, expected', + [(1e-6, -3.1080612413765905), + (1e-17, -3.141585429601399), + (0.975, 2.1447547020964923)]) +def test_cosine_ppf_isf(p, expected): + assert_allclose(stats.cosine.ppf(p), expected) + assert_allclose(stats.cosine.isf(p), -expected) + + +def test_cosine_logpdf_endpoints(): + logp = stats.cosine.logpdf([-np.pi, np.pi]) + # reference value calculated using mpmath assuming `np.cos(-1)` is four + # floating point numbers too high. See gh-18382. + assert_array_less(logp, -37.18838327496655) + + +def test_distr_params_lists(): + # distribution objects are extra distributions added in + # test_discrete_basic. All other distributions are strings (names) + # and so we only choose those to compare whether both lists match. + discrete_distnames = {name for name, _ in distdiscrete + if isinstance(name, str)} + invdiscrete_distnames = {name for name, _ in invdistdiscrete} + assert discrete_distnames == invdiscrete_distnames + + cont_distnames = {name for name, _ in distcont} + invcont_distnames = {name for name, _ in invdistcont} + assert cont_distnames == invcont_distnames + + +def test_moment_order_4(): + # gh-13655 reported that if a distribution has a `_stats` method that + # accepts the `moments` parameter, then if the distribution's `moment` + # method is called with `order=4`, the faster/more accurate`_stats` gets + # called, but the results aren't used, and the generic `_munp` method is + # called to calculate the moment anyway. This tests that the issue has + # been fixed. + # stats.skewnorm._stats accepts the `moments` keyword + stats.skewnorm._stats(a=0, moments='k') # no failure = has `moments` + # When `moment` is called, `_stats` is used, so the moment is very accurate + # (exactly equal to Pearson's kurtosis of the normal distribution, 3) + assert stats.skewnorm.moment(order=4, a=0) == 3.0 + # At the time of gh-13655, skewnorm._munp() used the generic method + # to compute its result, which was inefficient and not very accurate. + # At that time, the following assertion would fail. skewnorm._munp() + # has since been made more accurate and efficient, so now this test + # is expected to pass. + assert stats.skewnorm._munp(4, 0) == 3.0 + + +class TestRelativisticBW: + @pytest.fixture + def ROOT_pdf_sample_data(self): + """Sample data points for pdf computed with CERN's ROOT + + See - https://root.cern/ + + Uses ROOT.TMath.BreitWignerRelativistic, available in ROOT + versions 6.27+ + + pdf calculated for Z0 Boson, W Boson, and Higgs Boson for + x in `np.linspace(0, 200, 401)`. + """ + data = np.load( + Path(__file__).parent / + 'data/rel_breitwigner_pdf_sample_data_ROOT.npy' + ) + data = np.rec.fromarrays(data.T, names='x,pdf,rho,gamma') + return data + + @pytest.mark.parametrize( + "rho,gamma,rtol", [ + (36.545206797050334, 2.4952, 5e-14), # Z0 Boson + (38.55107913669065, 2.085, 1e-14), # W Boson + (96292.3076923077, 0.0013, 5e-13), # Higgs Boson + ] + ) + def test_pdf_against_ROOT(self, ROOT_pdf_sample_data, rho, gamma, rtol): + data = ROOT_pdf_sample_data[ + (ROOT_pdf_sample_data['rho'] == rho) + & (ROOT_pdf_sample_data['gamma'] == gamma) + ] + x, pdf = data['x'], data['pdf'] + assert_allclose( + pdf, stats.rel_breitwigner.pdf(x, rho, scale=gamma), rtol=rtol + ) + + @pytest.mark.parametrize("rho, Gamma, rtol", [ + (36.545206797050334, 2.4952, 5e-13), # Z0 Boson + (38.55107913669065, 2.085, 5e-13), # W Boson + (96292.3076923077, 0.0013, 5e-10), # Higgs Boson + ] + ) + def test_pdf_against_simple_implementation(self, rho, Gamma, rtol): + # reference implementation straight from formulas on Wikipedia [1] + def pdf(E, M, Gamma): + gamma = np.sqrt(M**2 * (M**2 + Gamma**2)) + k = (2 * np.sqrt(2) * M * Gamma * gamma + / (np.pi * np.sqrt(M**2 + gamma))) + return k / ((E**2 - M**2)**2 + M**2*Gamma**2) + # get reasonable values at which to evaluate the CDF + p = np.linspace(0.05, 0.95, 10) + x = stats.rel_breitwigner.ppf(p, rho, scale=Gamma) + res = stats.rel_breitwigner.pdf(x, rho, scale=Gamma) + ref = pdf(x, rho*Gamma, Gamma) + assert_allclose(res, ref, rtol=rtol) + + @pytest.mark.xslow + @pytest.mark.parametrize( + "rho,gamma", [ + pytest.param( + 36.545206797050334, 2.4952, marks=pytest.mark.slow + ), # Z0 Boson + pytest.param( + 38.55107913669065, 2.085, marks=pytest.mark.xslow + ), # W Boson + pytest.param( + 96292.3076923077, 0.0013, marks=pytest.mark.xslow + ), # Higgs Boson + ] + ) + def test_fit_floc(self, rho, gamma): + """Tests fit for cases where floc is set. + + `rel_breitwigner` has special handling for these cases. + """ + seed = 6936804688480013683 + rng = np.random.default_rng(seed) + data = stats.rel_breitwigner.rvs( + rho, scale=gamma, size=1000, random_state=rng + ) + fit = stats.rel_breitwigner.fit(data, floc=0) + assert_allclose((fit[0], fit[2]), (rho, gamma), rtol=2e-1) + assert fit[1] == 0 + # Check again with fscale set. + fit = stats.rel_breitwigner.fit(data, floc=0, fscale=gamma) + assert_allclose(fit[0], rho, rtol=1e-2) + assert (fit[1], fit[2]) == (0, gamma) + + +class TestJohnsonSU: + @pytest.mark.parametrize("case", [ # a, b, loc, scale, m1, m2, g1, g2 + (-0.01, 1.1, 0.02, 0.0001, 0.02000137427557091, + 2.1112742956578063e-08, 0.05989781342460999, 20.36324408592951-3), + (2.554395574161155, 2.2482281679651965, 0, 1, -1.54215386737391, + 0.7629882028469993, -1.256656139406788, 6.303058419339775-3)]) + def test_moment_gh18071(self, case): + # gh-18071 reported an IntegrationWarning emitted by johnsonsu.stats + # Check that the warning is no longer emitted and that the values + # are accurate compared against results from Mathematica. + # Reference values from Mathematica, e.g. + # Mean[JohnsonDistribution["SU",-0.01, 1.1, 0.02, 0.0001]] + res = stats.johnsonsu.stats(*case[:4], moments='mvsk') + assert_allclose(res, case[4:], rtol=1e-14) + + +class TestTruncPareto: + def test_pdf(self): + # PDF is that of the truncated pareto distribution + b, c = 1.8, 5.3 + x = np.linspace(1.8, 5.3) + res = stats.truncpareto(b, c).pdf(x) + ref = stats.pareto(b).pdf(x) / stats.pareto(b).cdf(c) + assert_allclose(res, ref) + + def test_pdf_negative(self): + # truncpareto is equivalent to more general powerlaw from gh-23648 + # exponent of truncpareto is negative in this case + a, xmin, xmax = 4, 3, 5 + x = np.linspace(xmin, xmax) + + # compute reference using PDF from gh-23648 + C = a / (xmax ** a - xmin ** a) + ref = C * x ** (a - 1) + + # compute using `truncpareto` with negative exponent + b = -a + c = xmax / xmin + scale = xmin + loc = 0 + X = stats.truncpareto(b, c, loc, scale) + + assert_allclose(X.pdf(x), ref) + assert_allclose(X.logpdf(x), np.log(X.pdf(x))) + # indexing avoids RuntimeWarning with `np.log(0)` + assert_allclose(X.logcdf(x[1:]), np.log(X.cdf(x[1:]))) + assert_allclose(X.logsf(x[:-1]), np.log(X.sf(x[:-1]))) + + @pytest.mark.parametrize('fix_loc', [True, False]) + @pytest.mark.parametrize('fix_scale', [True, False]) + @pytest.mark.parametrize('fix_b', [True, False]) + @pytest.mark.parametrize('fix_c', [True, False]) + def test_fit(self, fix_loc, fix_scale, fix_b, fix_c): + + rng = np.random.default_rng(6747363148258237171) + b, c, loc, scale = 1.8, 5.3, 1, 2.5 + dist = stats.truncpareto(b, c, loc=loc, scale=scale) + data = dist.rvs(size=500, random_state=rng) + + kwds = {} + if fix_loc: + kwds['floc'] = loc + if fix_scale: + kwds['fscale'] = scale + if fix_b: + kwds['f0'] = b + if fix_c: + kwds['f1'] = c + + if fix_loc and fix_scale and fix_b and fix_c: + message = "All parameters fixed. There is nothing to optimize." + with pytest.raises(RuntimeError, match=message): + stats.truncpareto.fit(data, **kwds) + else: + _assert_less_or_close_loglike(stats.truncpareto, data, **kwds) + + +class TestKappa3: + def test_sf(self): + # During development of gh-18822, we found that the override of + # kappa3.sf could experience overflow where the version in main did + # not. Check that this does not happen in final implementation. + sf0 = 1 - stats.kappa3.cdf(0.5, 1e5) + sf1 = stats.kappa3.sf(0.5, 1e5) + assert_allclose(sf1, sf0) + + +class TestIrwinHall: + unif = stats.uniform(0, 1) + ih1 = stats.irwinhall(1) + ih10 = stats.irwinhall(10) + + def test_stats_ih10(self): + # from Wolfram Alpha "mean variance skew kurtosis UniformSumDistribution[10]" + # W|A uses Pearson's definition of kurtosis so subtract 3 + # should be exact integer division converted to fp64, without any further ops + assert_array_max_ulp(self.ih10.stats('mvsk'), (5, 10/12, 0, -3/25)) + + def test_moments_ih10(self): + # from Wolfram Alpha "values moments UniformSumDistribution[10]" + # algo should use integer division converted to fp64, without any further ops + # so these should be precise to the ulpm if not exact + vals = [5, 155 / 6, 275 / 2, 752, 12650 / 3, + 677465 / 28, 567325 / 4, + 15266213 / 18, 10333565 / 2] + moments = [self.ih10.moment(n+1) for n in range(len(vals))] + assert_array_max_ulp(moments, vals) + # also from Wolfram Alpha "50th moment UniformSumDistribution[10]" + m50 = self.ih10.moment(50) + m50_exact = 17453002755350010529309685557285098151740985685/4862 + assert_array_max_ulp(m50, m50_exact) + + def test_pdf_ih1_unif(self): + # IH(1) PDF is by definition U(0,1) + # we should be too, but differences in floating point eval order happen + # it's unclear if we can get down to the single ulp for doubles unless + # quads are used we're within 6-10 ulps otherwise (across sf/cdf/pdf) + # which is pretty good + + pts = np.linspace(0, 1, 100) + pdf_unif = self.unif.pdf(pts) + pdf_ih1 = self.ih1.pdf(pts) + assert_array_max_ulp(pdf_ih1, pdf_unif, maxulp=10) + + def test_pdf_ih2_triangle(self): + # IH(2) PDF is a triangle + ih2 = stats.irwinhall(2) + npts = 101 + pts = np.linspace(0, 2, npts) + expected = np.linspace(0, 2, npts) + expected[(npts + 1) // 2:] = 2 - expected[(npts + 1) // 2:] + pdf_ih2 = ih2.pdf(pts) + assert_array_max_ulp(pdf_ih2, expected, maxulp=10) + + def test_cdf_ih1_unif(self): + # CDF of IH(1) should be identical to uniform + pts = np.linspace(0, 1, 100) + cdf_unif = self.unif.cdf(pts) + cdf_ih1 = self.ih1.cdf(pts) + + assert_array_max_ulp(cdf_ih1, cdf_unif, maxulp=10) + + def test_cdf(self): + # CDF of IH is symmetric so CDF should be 0.5 at n/2 + n = np.arange(1, 10) + ih = stats.irwinhall(n) + ih_cdf = ih.cdf(n / 2) + exact = np.repeat(1/2, len(n)) + # should be identically 1/2 but fp order of eval differences happen + assert_array_max_ulp(ih_cdf, exact, maxulp=10) + + def test_cdf_ih10_exact(self): + # from Wolfram Alpha "values CDF[UniformSumDistribution[10], x] x=0 to x=10" + # symmetric about n/2, i.e., cdf[n-x] = 1-cdf[x] = sf[x] + vals = [0, 1 / 3628800, 169 / 604800, 24427 / 1814400, + 252023 / 1814400, 1 / 2, 1562377 / 1814400, + 1789973 / 1814400, 604631 / 604800, + 3628799 / 3628800, 1] + + # essentially a test of bspline evaluation + # this and the other ones are mostly to detect regressions + assert_array_max_ulp(self.ih10.cdf(np.arange(11)), vals, maxulp=10) + + assert_array_max_ulp(self.ih10.cdf(1/10), 1/36288000000000000, maxulp=10) + ref = 36287999999999999/36288000000000000 + assert_array_max_ulp(self.ih10.cdf(99/10), ref, maxulp=10) + + def test_pdf_ih10_exact(self): + # from Wolfram Alpha "values PDF[UniformSumDistribution[10], x] x=0 to x=10" + # symmetric about n/2 = 5 + vals = [0, 1 / 362880, 251 / 181440, 913 / 22680, 44117 / 181440] + vals += [15619 / 36288] + vals[::-1] + assert_array_max_ulp(self.ih10.pdf(np.arange(11)), vals, maxulp=10) + + def test_sf_ih10_exact(self): + assert_allclose(self.ih10.sf(np.arange(11)), 1 - self.ih10.cdf(np.arange(11))) + # from Wolfram Alpha "SurvivalFunction[UniformSumDistribution[10],x] at x=1/10" + # and symmetry about n/2 = 5 + # W|A returns 1 for CDF @ x=9.9 + ref = 36287999999999999/36288000000000000 + assert_array_max_ulp(self.ih10.sf(1/10), ref, maxulp=10) + + +class TestDParetoLognorm: + def test_against_R(self): + # Test against R implementation in `distributionsrd` + # library(distributionsrd) + # options(digits=16) + # x = 1.1 + # b = 2 + # a = 1.5 + # m = 3 + # s = 1.2 + # ddoubleparetolognormal(x, b, a, m, s) + # pdoubleparetolognormal(x, b, a, m, s) + x, m, s, a, b = 1.1, 3, 1.2, 1.5, 2 + dist = stats.dpareto_lognorm(m, s, a, b) + np.testing.assert_allclose(dist.pdf(x), 0.02490187219085912) + np.testing.assert_allclose(dist.cdf(x), 0.01664024173822796) + + +# Cases are (distribution name, log10 of smallest probability mass to test, +# log10 of the complement of the largest probability mass to test, atol, +# rtol). None uses default values. +@pytest.mark.parametrize("case", [("kappa3", None, None, None, None), + ("loglaplace", None, None, None, None), + ("lognorm", None, None, None, None), + ("lomax", None, None, None, None), + ("pareto", None, None, None, None),]) +def test_sf_isf_overrides(case): + # Test that SF is the inverse of ISF. Supplements + # `test_continuous_basic.check_sf_isf` for distributions with overridden + # `sf` and `isf` methods. + distname, lp1, lp2, atol, rtol = case + + lpm = np.log10(0.5) # log10 of the probability mass at the median + lp1 = lp1 or -290 + lp2 = lp2 or -14 + atol = atol or 0 + rtol = rtol or 1e-12 + dist = getattr(stats, distname) + params = dict(distcont)[distname] + dist_frozen = dist(*params) + + # Test (very deep) right tail to median. We can benchmark with random + # (loguniform) points, but strictly logspaced points are fine for tests. + ref = np.logspace(lp1, lpm) + res = dist_frozen.sf(dist_frozen.isf(ref)) + assert_allclose(res, ref, atol=atol, rtol=rtol) + + # test median to left tail + ref = 1 - np.logspace(lp2, lpm, 20) + res = dist_frozen.sf(dist_frozen.isf(ref)) + assert_allclose(res, ref, atol=atol, rtol=rtol) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/test_entropy.py b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_entropy.py new file mode 100644 index 0000000000000000000000000000000000000000..1302d21a8eedf9804176df9d9096a86b325d3558 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_entropy.py @@ -0,0 +1,317 @@ +import math +import pytest +from pytest import raises as assert_raises + +import numpy as np + +from scipy import stats +from scipy.stats import norm, expon # type: ignore[attr-defined] +from scipy._lib._array_api import make_xp_test_case +from scipy._lib._array_api_no_0d import (xp_assert_close, xp_assert_equal, + xp_assert_less) + +lazy_xp_modules = [stats] +skip_xp_backends = pytest.mark.skip_xp_backends + +@make_xp_test_case(stats.entropy) +class TestEntropy: + def test_entropy_positive(self, xp): + # See ticket #497 + pk = xp.asarray([0.5, 0.2, 0.3]) + qk = xp.asarray([0.1, 0.25, 0.65]) + eself = stats.entropy(pk, pk) + edouble = stats.entropy(pk, qk) + xp_assert_close(eself, xp.asarray(0.), atol=1e-16) + xp_assert_less(-edouble, xp.asarray(0.)) + + def test_entropy_base(self, xp): + pk = xp.ones(16) + S = stats.entropy(pk, base=2.) + xp_assert_less(xp.abs(S - 4.), xp.asarray(1.e-5)) + + qk = xp.ones(16) + qk = xp.where(xp.arange(16) < 8, 2., qk) + S = stats.entropy(pk, qk) + S2 = stats.entropy(pk, qk, base=2.) + xp_assert_less(xp.abs(S/S2 - math.log(2.)), xp.asarray(1.e-5)) + + def test_entropy_zero(self, xp): + # Test for PR-479 + x = xp.asarray([0., 1., 2.]) + xp_assert_close(stats.entropy(x), + xp.asarray(0.63651416829481278)) + + def test_entropy_2d(self, xp): + pk = xp.asarray([[0.1, 0.2], [0.6, 0.3], [0.3, 0.5]]) + qk = xp.asarray([[0.2, 0.1], [0.3, 0.6], [0.5, 0.3]]) + xp_assert_close(stats.entropy(pk, qk), + xp.asarray([0.1933259, 0.18609809])) + + def test_entropy_2d_zero(self, xp): + pk = xp.asarray([[0.1, 0.2], [0.6, 0.3], [0.3, 0.5]]) + qk = xp.asarray([[0.0, 0.1], [0.3, 0.6], [0.5, 0.3]]) + xp_assert_close(stats.entropy(pk, qk), + xp.asarray([xp.inf, 0.18609809])) + + pk = xp.asarray([[0.0, 0.2], [0.6, 0.3], [0.3, 0.5]]) + xp_assert_close(stats.entropy(pk, qk), + xp.asarray([0.17403988, 0.18609809])) + + def test_entropy_base_2d_nondefault_axis(self, xp): + pk = xp.asarray([[0.1, 0.2], [0.6, 0.3], [0.3, 0.5]]) + xp_assert_close(stats.entropy(pk, axis=1), + xp.asarray([0.63651417, 0.63651417, 0.66156324])) + + def test_entropy_2d_nondefault_axis(self, xp): + pk = xp.asarray([[0.1, 0.2], [0.6, 0.3], [0.3, 0.5]]) + qk = xp.asarray([[0.2, 0.1], [0.3, 0.6], [0.5, 0.3]]) + xp_assert_close(stats.entropy(pk, qk, axis=1), + xp.asarray([0.23104906, 0.23104906, 0.12770641])) + + def test_entropy_raises_value_error(self, xp): + pk = xp.asarray([[0.1, 0.2], [0.6, 0.3], [0.3, 0.5]]) + qk = xp.asarray([[0.1, 0.2], [0.6, 0.3]]) + message = "Array shapes are incompatible for broadcasting." + with pytest.raises(ValueError, match=message): + stats.entropy(pk, qk) + + def test_base_entropy_with_axis_0_is_equal_to_default(self, xp): + pk = xp.asarray([[0.1, 0.2], [0.6, 0.3], [0.3, 0.5]]) + xp_assert_close(stats.entropy(pk, axis=0), + stats.entropy(pk)) + + def test_entropy_with_axis_0_is_equal_to_default(self, xp): + pk = xp.asarray([[0.1, 0.2], [0.6, 0.3], [0.3, 0.5]]) + qk = xp.asarray([[0.2, 0.1], [0.3, 0.6], [0.5, 0.3]]) + xp_assert_close(stats.entropy(pk, qk, axis=0), + stats.entropy(pk, qk)) + + def test_base_entropy_transposed(self, xp): + pk = xp.asarray([[0.1, 0.2], [0.6, 0.3], [0.3, 0.5]]) + xp_assert_close(stats.entropy(pk.T), + stats.entropy(pk, axis=1)) + + def test_entropy_transposed(self, xp): + pk = xp.asarray([[0.1, 0.2], [0.6, 0.3], [0.3, 0.5]]) + qk = xp.asarray([[0.2, 0.1], [0.3, 0.6], [0.5, 0.3]]) + xp_assert_close(stats.entropy(pk.T, qk.T), + stats.entropy(pk, qk, axis=1)) + + def test_entropy_broadcasting(self, xp): + rng = np.random.default_rng(74187315492831452) + x = xp.asarray(rng.random(3)) + y = xp.asarray(rng.random((2, 1))) + res = stats.entropy(x, y, axis=-1) + xp_assert_close(res[0], stats.entropy(x, y[0, ...])) + xp_assert_close(res[1], stats.entropy(x, y[1, ...])) + + def test_entropy_shape_mismatch(self, xp): + x = xp.ones((10, 1, 12)) + y = xp.ones((11, 2)) + message = "Array shapes are incompatible for broadcasting." + with pytest.raises(ValueError, match=message): + stats.entropy(x, y) + + def test_input_validation(self, xp): + x = xp.ones(10) + message = "`base` must be a positive number." + with pytest.raises(ValueError, match=message): + stats.entropy(x, base=-2) + + +@make_xp_test_case(stats.differential_entropy) +class TestDifferentialEntropy: + """ + Vasicek results are compared with the R package vsgoftest. + + # library(vsgoftest) + # + # samp <- c() + # entropy.estimate(x = samp, window = ) + + """ + methods = pytest.mark.parametrize('method', [ + "vasicek", + "van es", + pytest.param( + "correa", + marks=[ + skip_xp_backends("array_api_strict", reason="Invalid fancy indexing"), + skip_xp_backends("dask.array", reason="Invalid fancy indexing"), + ], + ), + "ebrahimi", + ]) + + def test_differential_entropy_vasicek(self, xp): + + random_state = np.random.RandomState(0) + values = random_state.standard_normal(100) + values = xp.asarray(values.tolist()) + + entropy = stats.differential_entropy(values, method='vasicek') + xp_assert_close(entropy, xp.asarray(1.342551187000946)) + + entropy = stats.differential_entropy(values, window_length=1, + method='vasicek') + xp_assert_close(entropy, xp.asarray(1.122044177725947)) + + entropy = stats.differential_entropy(values, window_length=8, + method='vasicek') + xp_assert_close(entropy, xp.asarray(1.349401487550325)) + + def test_differential_entropy_vasicek_2d_nondefault_axis(self, xp): + random_state = np.random.RandomState(0) + values = random_state.standard_normal((3, 100)) + values = xp.asarray(values.tolist()) + + entropy = stats.differential_entropy(values, axis=1, method='vasicek') + ref = xp.asarray([1.342551187000946, 1.341825903922332, 1.293774601883585]) + xp_assert_close(entropy, ref) + + entropy = stats.differential_entropy(values, axis=1, window_length=1, + method='vasicek') + ref = xp.asarray([1.122044177725947, 1.10294413850758, 1.129615790292772]) + xp_assert_close(entropy, ref) + + entropy = stats.differential_entropy(values, axis=1, window_length=8, + method='vasicek') + ref = xp.asarray([1.349401487550325, 1.338514126301301, 1.292331889365405]) + xp_assert_close(entropy, ref) + + + def test_differential_entropy_raises_value_error(self, xp): + random_state = np.random.RandomState(0) + values = random_state.standard_normal((3, 100)) + values = xp.asarray(values.tolist()) + + error_str = ( + r"Window length \({window_length}\) must be positive and less " + r"than half the sample size \({sample_size}\)." + ) + + sample_size = values.shape[1] + + for window_length in {-1, 0, sample_size//2, sample_size}: + + formatted_error_str = error_str.format( + window_length=window_length, + sample_size=sample_size, + ) + + with assert_raises(ValueError, match=formatted_error_str): + stats.differential_entropy( + values, + window_length=window_length, + axis=1, + ) + + def test_base_differential_entropy_with_axis_0_is_equal_to_default(self, xp): + random_state = np.random.RandomState(0) + values = random_state.standard_normal((100, 3)) + values = xp.asarray(values.tolist()) + + entropy = stats.differential_entropy(values, axis=0) + default_entropy = stats.differential_entropy(values) + xp_assert_close(entropy, default_entropy) + + def test_base_differential_entropy_transposed(self, xp): + random_state = np.random.RandomState(0) + values = random_state.standard_normal((3, 100)) + values = xp.asarray(values.tolist()) + + xp_assert_close( + stats.differential_entropy(values.T), + stats.differential_entropy(values, axis=1), + ) + + def test_input_validation(self, xp): + x = np.ones(10) + x = xp.asarray(x.tolist()) + + message = "`base` must be a positive number or `None`." + with pytest.raises(ValueError, match=message): + stats.differential_entropy(x, base=-2) + + message = "`method` must be one of..." + with pytest.raises(ValueError, match=message): + stats.differential_entropy(x, method='ekki-ekki') + + def test_window_length_is_none(self, xp): + rng = np.random.default_rng(358923459826738562) + x = xp.asarray(rng.random(size=10)) + ref = stats.differential_entropy(x) + res = stats.differential_entropy(x, window_length=None) + xp_assert_close(res, ref, rtol=0.005) + + @methods + def test_consistency(self, method, xp): + # test that method is a consistent estimator + n = 10000 if method == 'correa' else 1000000 + rvs = stats.norm.rvs(size=n, random_state=0) + rvs = xp.asarray(rvs.tolist()) + expected = xp.asarray(float(stats.norm.entropy())) + res = stats.differential_entropy(rvs, method=method) + xp_assert_close(res, expected, rtol=0.005) + + # values from differential_entropy reference [6], table 1, n=50, m=7 + norm_rmse_std_cases = { # method: (RMSE, STD) + 'vasicek': (0.198, 0.109), + 'van es': (0.212, 0.110), + 'correa': (0.135, 0.112), + 'ebrahimi': (0.128, 0.109) + } + + # values from differential_entropy reference [6], table 2, n=50, m=7 + expon_rmse_std_cases = { # method: (RMSE, STD) + 'vasicek': (0.194, 0.148), + 'van es': (0.179, 0.149), + 'correa': (0.155, 0.152), + 'ebrahimi': (0.151, 0.148) + } + + rmse_std_cases = {norm: norm_rmse_std_cases, + expon: expon_rmse_std_cases} + + @methods + @pytest.mark.parametrize('dist', [norm, expon]) + def test_rmse_std(self, method, dist, xp): + # test that RMSE and standard deviation of estimators matches values + # given in differential_entropy reference [6]. Incidentally, also + # tests vectorization. + reps, n, m = 10000, 50, 7 + expected = self.rmse_std_cases[dist][method] + rmse_expected, std_expected = xp.asarray(expected[0]), xp.asarray(expected[1]) + rvs = dist.rvs(size=(reps, n), random_state=0) + rvs = xp.asarray(rvs.tolist()) + true_entropy = xp.asarray(float(dist.entropy())) + res = stats.differential_entropy(rvs, window_length=m, + method=method, axis=-1) + xp_assert_close(xp.sqrt(xp.mean((res - true_entropy)**2)), + rmse_expected, atol=0.005) + xp_assert_close(xp.std(res, correction=0), std_expected, atol=0.002) + + @pytest.mark.parametrize('n, method', [ + (8, 'van es'), + (12, 'ebrahimi'), + (1001, 'vasicek') + ]) + def test_method_auto(self, n, method, xp): + rvs = stats.norm.rvs(size=(n,), random_state=0) + rvs = xp.asarray(rvs.tolist()) + res1 = stats.differential_entropy(rvs) + res2 = stats.differential_entropy(rvs, method=method) + xp_assert_equal(res1, res2) + + @methods + @pytest.mark.parametrize('dtype', [None, 'float32', 'float64']) + def test_dtypes_gh21192(self, method, dtype, xp): + # gh-21192 noted a change in the output of method='ebrahimi' + # with integer input. Check that the output is consistent regardless + # of input dtype. + x = [1, 1, 2, 3, 3, 4, 5, 5, 6, 7, 8, 9, 10, 11] + dtype_in = getattr(xp, str(dtype), None) + dtype_out = getattr(xp, str(dtype), xp.asarray(1.).dtype) + res = stats.differential_entropy(xp.asarray(x, dtype=dtype_in), method=method) + ref = stats.differential_entropy(xp.asarray(x, dtype=xp.float64), method=method) + xp_assert_close(res, xp.asarray(ref, dtype=dtype_out)[()]) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/test_fast_gen_inversion.py b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_fast_gen_inversion.py new file mode 100644 index 0000000000000000000000000000000000000000..dc57dd17d4562909f0a21b23a6ff2768c7373e3a --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_fast_gen_inversion.py @@ -0,0 +1,434 @@ +import pytest +import warnings +import numpy as np +from numpy.testing import assert_array_equal, assert_allclose +from copy import deepcopy +from scipy.stats.sampling import FastGeneratorInversion +from scipy import stats +from scipy._lib._testutils import IS_MUSL + + +def test_bad_args(): + # loc and scale must be scalar + with pytest.raises(ValueError, match="loc must be scalar"): + FastGeneratorInversion(stats.norm(loc=(1.2, 1.3))) + with pytest.raises(ValueError, match="scale must be scalar"): + FastGeneratorInversion(stats.norm(scale=[1.5, 5.7])) + + with pytest.raises(ValueError, match="'test' cannot be used to seed"): + FastGeneratorInversion(stats.norm(), random_state="test") + + msg = "Each of the 1 shape parameters must be a scalar" + with pytest.raises(ValueError, match=msg): + FastGeneratorInversion(stats.gamma([1.3, 2.5])) + + with pytest.raises(ValueError, match="`dist` must be a frozen"): + FastGeneratorInversion("xy") + + with pytest.raises(ValueError, match="Distribution 'truncnorm' is not"): + FastGeneratorInversion(stats.truncnorm(1.3, 4.5)) + + +def test_random_state(): + # fixed seed + gen = FastGeneratorInversion(stats.norm(), random_state=68734509) + x1 = gen.rvs(size=10) + gen.random_state = 68734509 + x2 = gen.rvs(size=10) + assert_array_equal(x1, x2) + + # Generator + urng = np.random.default_rng(20375857) + gen = FastGeneratorInversion(stats.norm(), random_state=urng) + x1 = gen.rvs(size=10) + gen.random_state = np.random.default_rng(20375857) + x2 = gen.rvs(size=10) + assert_array_equal(x1, x2) + + # RandomState + urng = np.random.RandomState(2364) + gen = FastGeneratorInversion(stats.norm(), random_state=urng) + x1 = gen.rvs(size=10) + gen.random_state = np.random.RandomState(2364) + x2 = gen.rvs(size=10) + assert_array_equal(x1, x2) + + # if evaluate_error is called, it must not interfere with the random_state + # used by rvs + gen = FastGeneratorInversion(stats.norm(), random_state=68734509) + x1 = gen.rvs(size=10) + _ = gen.evaluate_error(size=5) # this will generate 5 uniform rvs + x2 = gen.rvs(size=10) + gen.random_state = 68734509 + x3 = gen.rvs(size=20) + assert_array_equal(x2, x3[10:]) + + +dists_with_params = [ + ("alpha", (3.5,)), + ("anglit", ()), + ("argus", (3.5,)), + ("argus", (5.1,)), + ("beta", (1.5, 0.9)), + ("cosine", ()), + ("betaprime", (2.5, 3.3)), + ("bradford", (1.2,)), + ("burr", (1.3, 2.4)), + ("burr12", (0.7, 1.2)), + ("cauchy", ()), + ("chi2", (3.5,)), + ("chi", (4.5,)), + ("crystalball", (0.7, 1.2)), + ("expon", ()), + ("gamma", (1.5,)), + ("gennorm", (2.7,)), + ("gumbel_l", ()), + ("gumbel_r", ()), + ("hypsecant", ()), + ("invgauss", (3.1,)), + ("invweibull", (1.5,)), + ("laplace", ()), + ("logistic", ()), + ("maxwell", ()), + ("moyal", ()), + ("norm", ()), + ("pareto", (1.3,)), + ("powerlaw", (7.6,)), + ("rayleigh", ()), + ("semicircular", ()), + ("t", (5.7,)), + ("wald", ()), + ("weibull_max", (2.4,)), + ("weibull_min", (1.2,)), +] + + +@pytest.mark.parametrize(("distname, args"), dists_with_params) +def test_rvs_and_ppf(distname, args): + # check sample against rvs generated by rv_continuous + urng = np.random.default_rng(9807324628097097) + rng1 = getattr(stats, distname)(*args) + rvs1 = rng1.rvs(size=500, random_state=urng) + rng2 = FastGeneratorInversion(rng1, random_state=urng) + rvs2 = rng2.rvs(size=500) + assert stats.cramervonmises_2samp(rvs1, rvs2).pvalue > 0.01 + + # check ppf + q = [0.001, 0.1, 0.5, 0.9, 0.999] + assert_allclose(rng1.ppf(q), rng2.ppf(q), atol=1e-10) + + +@pytest.mark.parametrize(("distname, args"), dists_with_params) +def test_u_error(distname, args): + # check sample against rvs generated by rv_continuous + dist = getattr(stats, distname)(*args) + with warnings.catch_warnings(): + # filter the warnings thrown by UNU.RAN + warnings.simplefilter("ignore", RuntimeWarning) + rng = FastGeneratorInversion(dist) + u_error, x_error = rng.evaluate_error( + size=10_000, random_state=9807324628097097, x_error=False + ) + assert u_error <= 1e-10 + + +@pytest.mark.xslow +@pytest.mark.xfail(reason="geninvgauss CDF is not accurate") +def test_geninvgauss_uerror(): + dist = stats.geninvgauss(3.2, 1.5) + rng = FastGeneratorInversion(dist) + err = rng.evaluate_error(size=10_000, random_state=67982) + assert err[0] < 1e-10 + + +# TODO: add more distributions +@pytest.mark.skipif(IS_MUSL, reason="Hits RecursionError, see gh-23172") +@pytest.mark.fail_slow(5) +@pytest.mark.parametrize(("distname, args"), [("beta", (0.11, 0.11))]) +def test_error_extreme_params(distname, args): + # take extreme parameters where u-error might not be below the tolerance + # due to limitations of floating point arithmetic + with warnings.catch_warnings(): + # filter the warnings thrown by UNU.RAN for such extreme parameters + warnings.simplefilter("ignore", RuntimeWarning) + dist = getattr(stats, distname)(*args) + rng = FastGeneratorInversion(dist) + u_error, x_error = rng.evaluate_error( + size=10_000, random_state=980732462809709732623, x_error=True + ) + if u_error >= 2.5 * 1e-10: + assert x_error < 1e-9 + + +def test_evaluate_error_inputs(): + gen = FastGeneratorInversion(stats.norm()) + with pytest.raises(ValueError, match="size must be an integer"): + gen.evaluate_error(size=3.5) + with pytest.raises(ValueError, match="size must be an integer"): + gen.evaluate_error(size=(3, 3)) + + +def test_rvs_ppf_loc_scale(): + loc, scale = 3.5, 2.3 + dist = stats.norm(loc=loc, scale=scale) + rng = FastGeneratorInversion(dist, random_state=1234) + r = rng.rvs(size=1000) + r_rescaled = (r - loc) / scale + assert stats.cramervonmises(r_rescaled, "norm").pvalue > 0.01 + q = [0.001, 0.1, 0.5, 0.9, 0.999] + assert_allclose(rng._ppf(q), rng.ppf(q), atol=1e-10) + + +def test_domain(): + # only a basic check that the domain argument is passed to the + # UNU.RAN generators + rng = FastGeneratorInversion(stats.norm(), domain=(-1, 1)) + r = rng.rvs(size=100) + assert -1 <= r.min() < r.max() <= 1 + + # if loc and scale are used, new domain is loc + scale*domain + loc, scale = 3.5, 1.3 + dist = stats.norm(loc=loc, scale=scale) + rng = FastGeneratorInversion(dist, domain=(-1.5, 2)) + r = rng.rvs(size=100) + lb, ub = loc - scale * 1.5, loc + scale * 2 + assert lb <= r.min() < r.max() <= ub + + +@pytest.mark.parametrize(("distname, args, expected"), + [("beta", (3.5, 2.5), (0, 1)), + ("norm", (), (-np.inf, np.inf))]) +def test_support(distname, args, expected): + # test that the support is updated if truncation and loc/scale are applied + # use beta distribution since it is a transformed betaprime distribution, + # so it is important that the correct support is considered + # (i.e., the support of beta is (0,1), while betaprime is (0, inf)) + dist = getattr(stats, distname)(*args) + rng = FastGeneratorInversion(dist) + assert_array_equal(rng.support(), expected) + rng.loc = 1 + rng.scale = 2 + assert_array_equal(rng.support(), 1 + 2*np.array(expected)) + + +@pytest.mark.parametrize(("distname, args"), + [("beta", (3.5, 2.5)), ("norm", ())]) +def test_support_truncation(distname, args): + # similar test for truncation + dist = getattr(stats, distname)(*args) + rng = FastGeneratorInversion(dist, domain=(0.5, 0.7)) + assert_array_equal(rng.support(), (0.5, 0.7)) + rng.loc = 1 + rng.scale = 2 + assert_array_equal(rng.support(), (1 + 2 * 0.5, 1 + 2 * 0.7)) + + +def test_domain_shift_truncation(): + # center of norm is zero, it should be shifted to the left endpoint of + # domain. if this was not the case, PINV in UNURAN would raise a warning + # as the center is not inside the domain + with warnings.catch_warnings(): + warnings.simplefilter("error") + rng = FastGeneratorInversion(stats.norm(), domain=(1, 2)) + r = rng.rvs(size=100) + assert 1 <= r.min() < r.max() <= 2 + + +def test_non_rvs_methods_with_domain(): + # as a first step, compare truncated normal against stats.truncnorm + rng = FastGeneratorInversion(stats.norm(), domain=(2.3, 3.2)) + trunc_norm = stats.truncnorm(2.3, 3.2) + # take values that are inside and outside the domain + x = (2.0, 2.4, 3.0, 3.4) + p = (0.01, 0.5, 0.99) + assert_allclose(rng._cdf(x), trunc_norm.cdf(x)) + assert_allclose(rng._ppf(p), trunc_norm.ppf(p)) + loc, scale = 2, 3 + rng.loc = 2 + rng.scale = 3 + trunc_norm = stats.truncnorm(2.3, 3.2, loc=loc, scale=scale) + x = np.array(x) * scale + loc + assert_allclose(rng._cdf(x), trunc_norm.cdf(x)) + assert_allclose(rng._ppf(p), trunc_norm.ppf(p)) + + # do another sanity check with beta distribution + # in that case, it is important to use the correct domain since beta + # is a transformation of betaprime which has a different support + rng = FastGeneratorInversion(stats.beta(2.5, 3.5), domain=(0.3, 0.7)) + rng.loc = 2 + rng.scale = 2.5 + # the support is 2.75, , 3.75 (2 + 2.5 * 0.3, 2 + 2.5 * 0.7) + assert_array_equal(rng.support(), (2.75, 3.75)) + x = np.array([2.74, 2.76, 3.74, 3.76]) + # the cdf needs to be zero outside of the domain + y_cdf = rng._cdf(x) + assert_array_equal((y_cdf[0], y_cdf[3]), (0, 1)) + assert np.min(y_cdf[1:3]) > 0 + # ppf needs to map 0 and 1 to the boundaries + assert_allclose(rng._ppf(y_cdf), (2.75, 2.76, 3.74, 3.75)) + + +def test_non_rvs_methods_without_domain(): + norm_dist = stats.norm() + rng = FastGeneratorInversion(norm_dist) + x = np.linspace(-3, 3, num=10) + p = (0.01, 0.5, 0.99) + assert_allclose(rng._cdf(x), norm_dist.cdf(x)) + assert_allclose(rng._ppf(p), norm_dist.ppf(p)) + loc, scale = 0.5, 1.3 + rng.loc = loc + rng.scale = scale + norm_dist = stats.norm(loc=loc, scale=scale) + assert_allclose(rng._cdf(x), norm_dist.cdf(x)) + assert_allclose(rng._ppf(p), norm_dist.ppf(p)) + +@pytest.mark.parametrize(("domain, x"), + [(None, 0.5), + ((0, 1), 0.5), + ((0, 1), 1.5)]) +def test_scalar_inputs(domain, x): + """ pdf, cdf etc should map scalar values to scalars. check with and + w/o domain since domain impacts pdf, cdf etc + Take x inside and outside of domain """ + rng = FastGeneratorInversion(stats.norm(), domain=domain) + assert np.isscalar(rng._cdf(x)) + assert np.isscalar(rng._ppf(0.5)) + + +def test_domain_argus_large_chi(): + # for large chi, the Gamma distribution is used and the domain has to be + # transformed. this is a test to ensure that the transformation works + chi, lb, ub = 5.5, 0.25, 0.75 + rng = FastGeneratorInversion(stats.argus(chi), domain=(lb, ub)) + rng.random_state = 4574 + r = rng.rvs(size=500) + assert lb <= r.min() < r.max() <= ub + # perform goodness of fit test with conditional cdf + cdf = stats.argus(chi).cdf + prob = cdf(ub) - cdf(lb) + assert stats.cramervonmises(r, lambda x: cdf(x) / prob).pvalue > 0.05 + + +def test_setting_loc_scale(): + rng = FastGeneratorInversion(stats.norm(), random_state=765765864) + r1 = rng.rvs(size=1000) + rng.loc = 3.0 + rng.scale = 2.5 + r2 = rng.rvs(1000) + # rescaled r2 should be again standard normal + assert stats.cramervonmises_2samp(r1, (r2 - 3) / 2.5).pvalue > 0.05 + # reset values to default loc=0, scale=1 + rng.loc = 0 + rng.scale = 1 + r2 = rng.rvs(1000) + assert stats.cramervonmises_2samp(r1, r2).pvalue > 0.05 + + +def test_ignore_shape_range(): + msg = "No generator is defined for the shape parameters" + with pytest.raises(ValueError, match=msg): + rng = FastGeneratorInversion(stats.t(0.03)) + rng = FastGeneratorInversion(stats.t(0.03), ignore_shape_range=True) + # we can ignore the recommended range of shape parameters + # but u-error can be expected to be too large in that case + u_err, _ = rng.evaluate_error(size=1000, random_state=234) + assert u_err >= 1e-6 + +@pytest.mark.xfail_on_32bit( + "NumericalInversePolynomial.qrvs fails for Win 32-bit" +) +class TestQRVS: + def test_input_validation(self): + gen = FastGeneratorInversion(stats.norm()) + + match = "`qmc_engine` must be an instance of..." + with pytest.raises(ValueError, match=match): + gen.qrvs(qmc_engine=0) + + match = "`d` must be consistent with dimension of `qmc_engine`." + with pytest.raises(ValueError, match=match): + gen.qrvs(d=3, qmc_engine=stats.qmc.Halton(2)) + + qrngs = [None, stats.qmc.Sobol(1, seed=0), stats.qmc.Halton(3, seed=0)] + # `size=None` should not add anything to the shape, `size=1` should + sizes = [ + (None, tuple()), + (1, (1,)), + (4, (4,)), + ((4,), (4,)), + ((2, 4), (2, 4)), + ] + # Neither `d=None` nor `d=1` should add anything to the shape + ds = [(None, tuple()), (1, tuple()), (3, (3,))] + + @pytest.mark.parametrize("qrng", qrngs) + @pytest.mark.parametrize("size_in, size_out", sizes) + @pytest.mark.parametrize("d_in, d_out", ds) + def test_QRVS_shape_consistency(self, qrng, size_in, size_out, + d_in, d_out): + gen = FastGeneratorInversion(stats.norm()) + + # If d and qrng.d are inconsistent, an error is raised + if d_in is not None and qrng is not None and qrng.d != d_in: + match = "`d` must be consistent with dimension of `qmc_engine`." + with pytest.raises(ValueError, match=match): + gen.qrvs(size_in, d=d_in, qmc_engine=qrng) + return + + # Sometimes d is really determined by qrng + if d_in is None and qrng is not None and qrng.d != 1: + d_out = (qrng.d,) + + shape_expected = size_out + d_out + + qrng2 = deepcopy(qrng) + qrvs = gen.qrvs(size=size_in, d=d_in, qmc_engine=qrng) + if size_in is not None: + assert qrvs.shape == shape_expected + + if qrng2 is not None: + uniform = qrng2.random(np.prod(size_in) or 1) + qrvs2 = stats.norm.ppf(uniform).reshape(shape_expected) + assert_allclose(qrvs, qrvs2, atol=1e-12) + + def test_QRVS_size_tuple(self): + # QMCEngine samples are always of shape (n, d). When `size` is a tuple, + # we set `n = prod(size)` in the call to qmc_engine.random, transform + # the sample, and reshape it to the final dimensions. When we reshape, + # we need to be careful, because the _columns_ of the sample returned + # by a QMCEngine are "independent"-ish, but the elements within the + # columns are not. We need to make sure that this doesn't get mixed up + # by reshaping: qrvs[..., i] should remain "independent"-ish of + # qrvs[..., i+1], but the elements within qrvs[..., i] should be + # transformed from the same low-discrepancy sequence. + + gen = FastGeneratorInversion(stats.norm()) + + size = (3, 4) + d = 5 + qrng = stats.qmc.Halton(d, seed=0) + qrng2 = stats.qmc.Halton(d, seed=0) + + uniform = qrng2.random(np.prod(size)) + + qrvs = gen.qrvs(size=size, d=d, qmc_engine=qrng) + qrvs2 = stats.norm.ppf(uniform) + + for i in range(d): + sample = qrvs[..., i] + sample2 = qrvs2[:, i].reshape(size) + assert_allclose(sample, sample2, atol=1e-12) + + +def test_burr_overflow(): + # this case leads to an overflow error if math.exp is used + # in the definition of the burr pdf instead of np.exp + # a direct implementation of the PDF as x**(-c-1) / (1+x**(-c))**(d+1) + # also leads to an overflow error in the setup + args = (1.89128135, 0.30195177) + with warnings.catch_warnings(): + # filter potential overflow warning + warnings.simplefilter("ignore", RuntimeWarning) + gen = FastGeneratorInversion(stats.burr(*args)) + u_error, _ = gen.evaluate_error(random_state=4326) + assert u_error <= 1e-10 diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/test_fit.py b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_fit.py new file mode 100644 index 0000000000000000000000000000000000000000..e77cb931308948fb48b080ba180e20b7b9e9aefb --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_fit.py @@ -0,0 +1,1092 @@ +import os +import warnings + +import numpy as np +from numpy.testing import assert_allclose, assert_equal +import pytest +from scipy import stats +from scipy.optimize import differential_evolution + +from .test_continuous_basic import distcont +from scipy.stats._distn_infrastructure import FitError +from scipy.stats._distr_params import distdiscrete +from scipy.stats import goodness_of_fit + + +# this is not a proper statistical test for convergence, but only +# verifies that the estimate and true values don't differ by too much + +fit_sizes = [1000, 5000, 10000] # sample sizes to try + +thresh_percent = 0.25 # percent of true parameters for fail cut-off +thresh_min = 0.75 # minimum difference estimate - true to fail test + +mle_failing_fits = [ + 'dpareto_lognorm', + 'gausshyper', + 'genexpon', + 'gengamma', + 'irwinhall', + 'kappa4', + 'ksone', + 'kstwo', + 'ncf', + 'ncx2', + 'truncexpon', + 'tukeylambda', + 'vonmises', + 'levy_stable', + 'truncpareto', + 'truncweibull_min', + 'studentized_range', +] + +# these pass but are XSLOW (>1s) +mle_Xslow_fits = ['betaprime', 'crystalball', 'exponweib', 'f', 'geninvgauss', + 'jf_skew_t', 'nct', 'recipinvgauss', 'rel_breitwigner', + 'vonmises_line'] + +# The MLE fit method of these distributions doesn't perform well when all +# parameters are fit, so test them with the location fixed at 0. +mle_use_floc0 = [ + 'burr', + 'chi', + 'chi2', + 'mielke', + 'pearson3', + 'genhalflogistic', + 'rdist', + 'pareto', + 'powerlaw', # distfn.nnlf(est2, rvs) > distfn.nnlf(est1, rvs) otherwise + 'powerlognorm', + 'wrapcauchy', + 'rel_breitwigner', +] + +mm_failing_fits = ['alpha', 'betaprime', 'burr', 'burr12', 'cauchy', 'chi', + 'chi2', 'crystalball', 'dgamma', 'dpareto_lognorm', 'dweibull', + 'f', 'fatiguelife', 'fisk', 'foldcauchy', 'genextreme', + 'gengamma', 'genhyperbolic', 'gennorm', 'genpareto', + 'halfcauchy', 'invgamma', 'invweibull', 'irwinhall', 'jf_skew_t', + 'johnsonsu', 'kappa3', 'ksone', 'kstwo', 'landau', 'levy', 'levy_l', + 'levy_stable', 'loglaplace', 'lomax', 'mielke', 'nakagami', + 'ncf', 'nct', 'ncx2', 'pareto', 'powerlognorm', 'powernorm', + 'rel_breitwigner', 'skewcauchy', 't', 'triang', + 'truncpareto', 'truncweibull_min', 'tukeylambda', + 'studentized_range'] + +# not sure if these fail, but they caused my patience to fail +mm_XXslow_fits = ['argus', 'exponpow', 'exponweib', 'gausshyper', 'genexpon', + 'genhalflogistic', 'gompertz', 'johnsonsb', + 'kappa4', 'kstwobign', 'recipinvgauss', + 'truncexpon', 'vonmises', 'vonmises_line'] + +# these pass but are XSLOW (>1s) +mm_Xslow_fits = ['wrapcauchy'] + +failing_fits = {"MM": mm_failing_fits + mm_XXslow_fits, "MLE": mle_failing_fits} +xslow_fits = {"MM": mm_Xslow_fits, "MLE": mle_Xslow_fits} +fail_interval_censored = {"truncpareto"} + +# Don't run the fit test on these: +skip_fit = [ + 'erlang', # Subclass of gamma, generates a warning. + 'genhyperbolic', 'norminvgauss', # too slow +] + + +def cases_test_cont_fit(): + # this tests the closeness of the estimated parameters to the true + # parameters with fit method of continuous distributions + # Note: is slow, some distributions don't converge with sample + # size <= 10000 + for distname, arg in distcont: + if distname not in skip_fit: + yield distname, arg + + +@pytest.mark.slow +@pytest.mark.parametrize('distname,arg', list(cases_test_cont_fit())) +@pytest.mark.parametrize('method', ["MLE", "MM"]) +def test_cont_fit(distname, arg, method): + run_xfail = int(os.getenv('SCIPY_XFAIL', default=False)) + run_xslow = int(os.getenv('SCIPY_XSLOW', default=False)) + + if distname in failing_fits[method] and not run_xfail: + # The generic `fit` method can't be expected to work perfectly for all + # distributions, data, and guesses. Some failures are expected. + msg = "Failure expected; set environment variable SCIPY_XFAIL=1 to run." + pytest.xfail(msg) + + if distname in xslow_fits[method] and not run_xslow: + msg = "Very slow; set environment variable SCIPY_XSLOW=1 to run." + pytest.skip(msg) + + distfn = getattr(stats, distname) + + truearg = np.hstack([arg, [0.0, 1.0]]) + diffthreshold = np.max(np.vstack([truearg*thresh_percent, + np.full(distfn.numargs+2, thresh_min)]), + 0) + + for fit_size in fit_sizes: + # Note that if a fit succeeds, the other fit_sizes are skipped + rng = np.random.default_rng(1234) + + with np.errstate(all='ignore'): + rvs = distfn.rvs(size=fit_size, *arg, random_state=rng) + if method == 'MLE' and distfn.name in mle_use_floc0: + kwds = {'floc': 0} + else: + kwds = {} + # start with default values + est = distfn.fit(rvs, method=method, **kwds) + if method == 'MLE': + # Trivial test of the use of CensoredData. The fit() method + # will check that data contains no actual censored data, and + # do a regular uncensored fit. + data1 = stats.CensoredData(rvs) + est1 = distfn.fit(data1, **kwds) + msg = ('Different results fitting uncensored data wrapped as' + f' CensoredData: {distfn.name}: est={est} est1={est1}') + assert_allclose(est1, est, rtol=1e-10, err_msg=msg) + if method == 'MLE' and distname not in fail_interval_censored: + # Convert the first `nic` values in rvs to interval-censored + # values. The interval is small, so est2 should be close to + # est. + nic = 15 + interval = np.column_stack((rvs, rvs)) + interval[:nic, 0] *= 0.99 + interval[:nic, 1] *= 1.01 + interval.sort(axis=1) + data2 = stats.CensoredData(interval=interval) + est2 = distfn.fit(data2, **kwds) + msg = ('Different results fitting interval-censored' + f' data: {distfn.name}: est={est} est2={est2}') + assert_allclose(est2, est, rtol=0.05, err_msg=msg) + + diff = est - truearg + + # threshold for location + diffthreshold[-2] = np.max([np.abs(rvs.mean())*thresh_percent, + thresh_min]) + + if np.any(np.isnan(est)): + raise AssertionError('nan returned in fit') + else: + if np.all(np.abs(diff) <= diffthreshold): + break + else: + txt = f'parameter: {str(truearg)}\n' + txt += f'estimated: {str(est)}\n' + txt += f'diff : {str(diff)}\n' + raise AssertionError(f'fit not very good in {distfn.name}\n' + txt) + + +def _check_loc_scale_mle_fit(name, data, desired, atol=None): + d = getattr(stats, name) + actual = d.fit(data)[-2:] + assert_allclose(actual, desired, atol=atol, + err_msg=f'poor mle fit of (loc, scale) in {name}') + + +def test_non_default_loc_scale_mle_fit(): + data = np.array([1.01, 1.78, 1.78, 1.78, 1.88, 1.88, 1.88, 2.00]) + _check_loc_scale_mle_fit('uniform', data, [1.01, 0.99], 1e-3) + _check_loc_scale_mle_fit('expon', data, [1.01, 0.73875], 1e-3) + + +def test_expon_fit(): + """gh-6167""" + data = [0, 0, 0, 0, 2, 2, 2, 2] + phat = stats.expon.fit(data, floc=0) + assert_allclose(phat, [0, 1.0], atol=1e-3) + + +def test_fit_error(): + data = np.concatenate([np.zeros(29), np.ones(21)]) + message = "Optimization converged to parameters that are..." + with pytest.raises(FitError, match=message), \ + pytest.warns(RuntimeWarning): + stats.beta.fit(data) + + +@pytest.mark.parametrize("dist, params", + [(stats.norm, (0.5, 2.5)), # type: ignore[attr-defined] + (stats.binom, (10, 0.3, 2))]) # type: ignore[attr-defined] +def test_nnlf_and_related_methods(dist, params): + rng = np.random.default_rng(983459824) + + if hasattr(dist, 'pdf'): + logpxf = dist.logpdf + else: + logpxf = dist.logpmf + + x = dist.rvs(*params, size=100, random_state=rng) + ref = -logpxf(x, *params).sum() + res1 = dist.nnlf(params, x) + res2 = dist._penalized_nnlf(params, x) + assert_allclose(res1, ref) + assert_allclose(res2, ref) + + +def cases_test_fit_mle(): + # These fail default test or hang + skip_basic_fit = {'argus', 'irwinhall', 'foldnorm', 'truncpareto', + 'truncweibull_min', 'ksone', 'levy_stable', + 'studentized_range', 'kstwo', + 'beta', 'nakagami', 'truncnorm', # don't meet tolerance + 'poisson_binom'} # vector-valued shape parameter + + # Please keep this list in alphabetical order... + slow_basic_fit = {'alpha', 'arcsine', 'betaprime', 'binom', 'bradford', 'burr12', + 'chi', 'crystalball', 'dweibull', 'erlang', 'exponnorm', + 'exponpow', 'f', 'fatiguelife', 'fisk', 'foldcauchy', 'gamma', + 'genexpon', 'genextreme', 'gennorm', 'genpareto', + 'gompertz', 'invgamma', 'invgauss', 'invweibull', + 'jf_skew_t', 'johnsonsb', 'johnsonsu', 'kappa3', + 'kstwobign', 'loglaplace', 'lognorm', 'lomax', 'mielke', + 'nbinom', 'norminvgauss', + 'pareto', 'pearson3', 'powerlaw', 'powernorm', + 'randint', 'rdist', 'recipinvgauss', 'rice', 'skewnorm', + 't', 'uniform', 'weibull_max', 'weibull_min', 'wrapcauchy', + 'zipfian'} + + # Please keep this list in alphabetical order... + xslow_basic_fit = {'betabinom', 'betanbinom', 'burr', 'dpareto_lognorm', + 'exponweib', 'gausshyper', 'gengamma', 'genhalflogistic', + 'genhyperbolic', 'geninvgauss', + 'hypergeom', 'kappa4', 'loguniform', + 'ncf', 'nchypergeom_fisher', 'nchypergeom_wallenius', + 'nct', 'ncx2', 'nhypergeom', + 'powerlognorm', 'reciprocal', 'rel_breitwigner', + 'skellam', 'trapezoid', 'triang', + 'tukeylambda', 'vonmises'} + + for dist in dict(distdiscrete + distcont): + if dist in skip_basic_fit or not isinstance(dist, str): + reason = "tested separately" + yield pytest.param(dist, marks=pytest.mark.skip(reason=reason)) + elif dist in slow_basic_fit: + reason = "too slow (>= 0.25s)" + yield pytest.param(dist, marks=pytest.mark.slow(reason=reason)) + elif dist in xslow_basic_fit: + reason = "too slow (>= 1.0s)" + yield pytest.param(dist, marks=pytest.mark.xslow(reason=reason)) + else: + yield dist + + +def cases_test_fit_mse(): + # the first four are so slow that I'm not sure whether they would pass + skip_basic_fit = {'levy_stable', 'studentized_range', 'ksone', 'skewnorm', + 'irwinhall', # hangs + 'norminvgauss', # super slow (~1 hr) but passes + 'kstwo', # very slow (~25 min) but passes + 'geninvgauss', # quite slow (~4 minutes) but passes + 'gausshyper', 'genhyperbolic', # integration warnings + 'tukeylambda', # close, but doesn't meet tolerance + 'vonmises', # can have negative CDF; doesn't play nice + 'arcsine', 'argus', 'powerlaw', 'rdist', # don't meet tolerance + 'poisson_binom', # vector-valued shape parameter + } + + # Please keep this list in alphabetical order... + slow_basic_fit = {'alpha', 'anglit', 'betabinom', 'bradford', + 'chi', 'chi2', 'crystalball', 'dweibull', + 'erlang', 'exponnorm', 'exponpow', 'exponweib', + 'fatiguelife', 'fisk', 'foldcauchy', 'foldnorm', + 'gamma', 'genexpon', 'genextreme', 'genhalflogistic', + 'genlogistic', 'genpareto', 'gompertz', + 'hypergeom', 'invweibull', + 'johnsonsu', 'kappa3', 'kstwobign', + 'laplace_asymmetric', 'loggamma', 'loglaplace', + 'lognorm', 'lomax', + 'maxwell', 'nhypergeom', + 'pareto', 'powernorm', 'randint', 'recipinvgauss', + 'semicircular', + 't', 'triang', 'truncexpon', 'truncpareto', + 'uniform', + 'wald', 'weibull_max', 'weibull_min', 'wrapcauchy', + 'zipfian'} + + # Please keep this list in alphabetical order... + xslow_basic_fit = {'argus', 'beta', 'betaprime', 'burr', 'burr12', + 'dgamma', 'dpareto_lognorm', 'f', 'gengamma', 'gennorm', + 'invgamma', 'invgauss', 'jf_skew_t', + 'johnsonsb', 'kappa4', 'loguniform', 'mielke', + 'nakagami', 'ncf', 'nchypergeom_fisher', + 'nchypergeom_wallenius', 'nct', 'ncx2', + 'pearson3', 'powerlognorm', + 'reciprocal', 'rel_breitwigner', 'rice', + 'trapezoid', 'truncnorm', 'truncweibull_min', + 'vonmises_line'} + + warns_basic_fit = {'skellam'} # can remove mark after gh-14901 is resolved + + for dist in dict(distdiscrete + distcont): + if dist in skip_basic_fit or not isinstance(dist, str): + reason = "Fails. Oh well." + yield pytest.param(dist, marks=pytest.mark.skip(reason=reason)) + elif dist in slow_basic_fit: + reason = "too slow (>= 0.25s)" + yield pytest.param(dist, marks=pytest.mark.slow(reason=reason)) + elif dist in xslow_basic_fit: + reason = "too slow (>= 1.0s)" + yield pytest.param(dist, marks=pytest.mark.xslow(reason=reason)) + elif dist in warns_basic_fit: + mark = pytest.mark.filterwarnings('ignore::RuntimeWarning') + yield pytest.param(dist, marks=mark) + else: + yield dist + + +def cases_test_fitstart(): + for distname, shapes in dict(distcont).items(): + if (not isinstance(distname, str) or + distname in {'studentized_range', 'recipinvgauss'}): # slow + continue + yield distname, shapes + + +@pytest.mark.parametrize('distname, shapes', list(cases_test_fitstart())) +def test_fitstart(distname, shapes): + dist = getattr(stats, distname) + rng = np.random.default_rng(216342614) + data = rng.random(10) + + with np.errstate(invalid='ignore', divide='ignore'): # irrelevant to test + guess = dist._fitstart(data) + + assert dist._argcheck(*guess[:-2]) + + +def assert_nlff_less_or_close(dist, data, params1, params0, rtol=1e-7, atol=0, + nlff_name='nnlf'): + nlff = getattr(dist, nlff_name) + nlff1 = nlff(params1, data) + nlff0 = nlff(params0, data) + if not (nlff1 < nlff0): + np.testing.assert_allclose(nlff1, nlff0, rtol=rtol, atol=atol) + + +class TestFit: + dist = stats.binom # type: ignore[attr-defined] + seed = 654634816187 + rng = np.random.default_rng(seed) + data = stats.binom.rvs(5, 0.5, size=100, random_state=rng) # type: ignore[attr-defined] # noqa: E501 + shape_bounds_a = [(1, 10), (0, 1)] + shape_bounds_d = {'n': (1, 10), 'p': (0, 1)} + atol = 5e-2 + rtol = 1e-2 + tols = {'atol': atol, 'rtol': rtol} + + def opt(self, *args, rng=1, **kwds): + return differential_evolution(*args, rng=rng, **kwds) + + def test_dist_iv(self): + message = "`dist` must be an instance of..." + with pytest.raises(ValueError, match=message): + stats.fit(10, self.data, self.shape_bounds_a) + + def test_data_iv(self): + message = "`data` must be exactly one-dimensional." + with pytest.raises(ValueError, match=message): + stats.fit(self.dist, [[1, 2, 3]], self.shape_bounds_a) + + message = "All elements of `data` must be finite numbers." + with pytest.raises(ValueError, match=message): + stats.fit(self.dist, [1, 2, 3, np.nan], self.shape_bounds_a) + with pytest.raises(ValueError, match=message): + stats.fit(self.dist, [1, 2, 3, np.inf], self.shape_bounds_a) + with pytest.raises(ValueError, match=message): + stats.fit(self.dist, ['1', '2', '3'], self.shape_bounds_a) + + def test_bounds_iv(self): + message = "Bounds provided for the following unrecognized..." + shape_bounds = {'n': (1, 10), 'p': (0, 1), '1': (0, 10)} + with pytest.warns(RuntimeWarning, match=message): + stats.fit(self.dist, self.data, shape_bounds) + + message = "Each element of a `bounds` sequence must be a tuple..." + shape_bounds = [(1, 10, 3), (0, 1)] + with pytest.raises(ValueError, match=message): + stats.fit(self.dist, self.data, shape_bounds) + + message = "Each element of `bounds` must be a tuple specifying..." + shape_bounds = [(1, 10, 3), (0, 1, 0.5)] + with pytest.raises(ValueError, match=message): + stats.fit(self.dist, self.data, shape_bounds) + shape_bounds = [1, 0] + with pytest.raises(ValueError, match=message): + stats.fit(self.dist, self.data, shape_bounds) + + message = "A `bounds` sequence must contain at least 2 elements..." + shape_bounds = [(1, 10)] + with pytest.raises(ValueError, match=message): + stats.fit(self.dist, self.data, shape_bounds) + + message = "A `bounds` sequence may not contain more than 3 elements..." + bounds = [(1, 10), (1, 10), (1, 10), (1, 10)] + with pytest.raises(ValueError, match=message): + stats.fit(self.dist, self.data, bounds) + + message = "There are no values for `p` on the interval..." + shape_bounds = {'n': (1, 10), 'p': (1, 0)} + with pytest.raises(ValueError, match=message): + stats.fit(self.dist, self.data, shape_bounds) + + message = "There are no values for `n` on the interval..." + shape_bounds = [(10, 1), (0, 1)] + with pytest.raises(ValueError, match=message): + stats.fit(self.dist, self.data, shape_bounds) + + message = "There are no integer values for `n` on the interval..." + shape_bounds = [(1.4, 1.6), (0, 1)] + with pytest.raises(ValueError, match=message): + stats.fit(self.dist, self.data, shape_bounds) + + message = "The intersection of user-provided bounds for `n`" + with pytest.raises(ValueError, match=message): + stats.fit(self.dist, self.data) + shape_bounds = [(-np.inf, np.inf), (0, 1)] + with pytest.raises(ValueError, match=message): + stats.fit(self.dist, self.data, shape_bounds) + + def test_guess_iv(self): + message = "Guesses provided for the following unrecognized..." + guess = {'n': 1, 'p': 0.5, '1': 255} + with pytest.warns(RuntimeWarning, match=message): + stats.fit(self.dist, self.data, self.shape_bounds_d, guess=guess) + + message = "Each element of `guess` must be a scalar..." + guess = {'n': 1, 'p': 'hi'} + with pytest.raises(ValueError, match=message): + stats.fit(self.dist, self.data, self.shape_bounds_d, guess=guess) + guess = [1, 'f'] + with pytest.raises(ValueError, match=message): + stats.fit(self.dist, self.data, self.shape_bounds_d, guess=guess) + guess = [[1, 2]] + with pytest.raises(ValueError, match=message): + stats.fit(self.dist, self.data, self.shape_bounds_d, guess=guess) + + message = "A `guess` sequence must contain at least 2..." + guess = [1] + with pytest.raises(ValueError, match=message): + stats.fit(self.dist, self.data, self.shape_bounds_d, guess=guess) + + message = "A `guess` sequence may not contain more than 3..." + guess = [1, 2, 3, 4] + with pytest.raises(ValueError, match=message): + stats.fit(self.dist, self.data, self.shape_bounds_d, guess=guess) + + message = "Guess for parameter `n` rounded.*|Guess for parameter `p` clipped.*" + guess = {'n': 4.5, 'p': -0.5} + with pytest.warns(RuntimeWarning, match=message): + stats.fit(self.dist, self.data, self.shape_bounds_d, guess=guess) + + message = "Guess for parameter `loc` rounded..." + guess = [5, 0.5, 0.5] + with pytest.warns(RuntimeWarning, match=message): + stats.fit(self.dist, self.data, self.shape_bounds_d, guess=guess) + + message = "Guess for parameter `p` clipped..." + guess = {'n': 5, 'p': -0.5} + with pytest.warns(RuntimeWarning, match=message): + stats.fit(self.dist, self.data, self.shape_bounds_d, guess=guess) + + message = "Guess for parameter `loc` clipped..." + guess = [5, 0.5, 1] + with pytest.warns(RuntimeWarning, match=message): + stats.fit(self.dist, self.data, self.shape_bounds_d, guess=guess) + + def basic_fit_test(self, dist_name, method, rng=1): + + N = 5000 + dist_data = dict(distcont + distdiscrete) + rng = np.random.default_rng(self.seed) + dist = getattr(stats, dist_name) + shapes = np.array(dist_data[dist_name]) + bounds = np.empty((len(shapes) + 2, 2), dtype=np.float64) + bounds[:-2, 0] = shapes/10.**np.sign(shapes) + bounds[:-2, 1] = shapes*10.**np.sign(shapes) + bounds[-2] = (0, 10) + bounds[-1] = (1e-16, 10) + loc = rng.uniform(*bounds[-2]) + scale = rng.uniform(*bounds[-1]) + ref = list(dist_data[dist_name]) + [loc, scale] + + if getattr(dist, 'pmf', False): + ref = ref[:-1] + ref[-1] = np.floor(loc) + data = dist.rvs(*ref, size=N, random_state=rng) + bounds = bounds[:-1] + if getattr(dist, 'pdf', False): + data = dist.rvs(*ref, size=N, random_state=rng) + + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", "overflow encountered", RuntimeWarning) + res = stats.fit(dist, data, bounds, method=method, + optimizer=self.opt) + + nlff_names = {'mle': 'nnlf', 'mse': '_penalized_nlpsf'} + nlff_name = nlff_names[method] + assert_nlff_less_or_close(dist, data, res.params, ref, **self.tols, + nlff_name=nlff_name) + + @pytest.mark.parametrize("dist_name", list(cases_test_fit_mle())) + def test_basic_fit_mle(self, dist_name): + self.basic_fit_test(dist_name, "mle", rng=5) + + @pytest.mark.parametrize("dist_name", list(cases_test_fit_mse())) + def test_basic_fit_mse(self, dist_name): + self.basic_fit_test(dist_name, "mse", rng=2) + + @pytest.mark.slow + def test_arcsine(self): + # Can't guarantee that all distributions will fit all data with + # arbitrary bounds. This distribution just happens to fail above. + # Try something slightly different. + N = 1000 + rng = np.random.default_rng(self.seed) + dist = stats.arcsine + shapes = (1., 2.) + data = dist.rvs(*shapes, size=N, random_state=rng) + shape_bounds = {'loc': (0.1, 10), 'scale': (0.1, 10)} + res = stats.fit(dist, data, shape_bounds, method='mse', optimizer=self.opt) + assert_nlff_less_or_close(dist, data, res.params, shapes, + nlff_name='_penalized_nlpsf', **self.tols) + + @pytest.mark.parametrize("method", ('mle', 'mse')) + def test_argus(self, method): + # Can't guarantee that all distributions will fit all data with + # arbitrary bounds. This distribution just happens to fail above. + # Try something slightly different. + N = 1000 + rng = np.random.default_rng(self.seed) + dist = stats.argus + shapes = (1., 2., 3.) + data = dist.rvs(*shapes, size=N, random_state=rng) + shape_bounds = {'chi': (0.1, 10), 'loc': (0.1, 10), 'scale': (0.1, 10)} + res = stats.fit(dist, data, shape_bounds, optimizer=self.opt, method=method) + nlff_name = {'mle': 'nnlf', 'mse': '_penalized_nlpsf'}[method] + assert_nlff_less_or_close(dist, data, res.params, shapes, **self.tols, + nlff_name=nlff_name) + + @pytest.mark.xslow + def test_beta(self): + # Can't guarantee that all distributions will fit all data with + # arbitrary bounds. This distribution just happens to fail above. + # Try something slightly different. + N = 1000 + rng = np.random.default_rng(self.seed) + dist = stats.beta + shapes = (2.3098496451481823, 0.62687954300963677, 1., 2.) + data = dist.rvs(*shapes, size=N, random_state=rng) + shape_bounds = {'a': (0.1, 10), 'b':(0.1, 10), + 'loc': (0.1, 10), 'scale': (0.1, 10)} + res = stats.fit(dist, data, shape_bounds, method='mle', optimizer=self.opt) + assert_nlff_less_or_close(dist, data, res.params, shapes, + nlff_name='nnlf', **self.tols) + + def test_foldnorm(self): + # Can't guarantee that all distributions will fit all data with + # arbitrary bounds. This distribution just happens to fail above. + # Try something slightly different. + N = 1000 + rng = np.random.default_rng(self.seed) + dist = stats.foldnorm + shapes = (1.952125337355587, 2., 3.) + data = dist.rvs(*shapes, size=N, random_state=rng) + shape_bounds = {'c': (0.1, 10), 'loc': (0.1, 10), 'scale': (0.1, 10)} + res = stats.fit(dist, data, shape_bounds, optimizer=self.opt) + + assert_nlff_less_or_close(dist, data, res.params, shapes, **self.tols) + + def test_nakagami(self): + # Can't guarantee that all distributions will fit all data with + # arbitrary bounds. This distribution just happens to fail above. + # Try something slightly different. + N = 1000 + rng = np.random.default_rng(self.seed) + dist = stats.nakagami + shapes = (4.9673794866666237, 1., 2.) + data = dist.rvs(*shapes, size=N, random_state=rng) + shape_bounds = {'nu':(0.1, 10), 'loc': (0.1, 10), 'scale': (0.1, 10)} + res = stats.fit(dist, data, shape_bounds, method='mle', optimizer=self.opt) + assert_nlff_less_or_close(dist, data, res.params, shapes, + nlff_name='nnlf', **self.tols) + + @pytest.mark.slow + def test_powerlaw(self): + # Can't guarantee that all distributions will fit all data with + # arbitrary bounds. This distribution just happens to fail above. + # Try something slightly different. + N = 1000 + rng = np.random.default_rng(self.seed) + dist = stats.powerlaw + shapes = (1.6591133289905851, 1., 2.) + data = dist.rvs(*shapes, size=N, random_state=rng) + shape_bounds = {'a': (0.1, 10), 'loc': (0.1, 10), 'scale': (0.1, 10)} + res = stats.fit(dist, data, shape_bounds, method='mse', optimizer=self.opt) + assert_nlff_less_or_close(dist, data, res.params, shapes, + nlff_name='_penalized_nlpsf', **self.tols) + + def test_truncpareto(self): + # Can't guarantee that all distributions will fit all data with + # arbitrary bounds. This distribution just happens to fail above. + # Try something slightly different. + N = 1000 + rng = np.random.default_rng(self.seed) + dist = stats.truncpareto + shapes = (1.8, 5.3, 2.3, 4.1) + data = dist.rvs(*shapes, size=N, random_state=rng) + shape_bounds = [(0.1, 10)]*4 + res = stats.fit(dist, data, shape_bounds, optimizer=self.opt) + + assert_nlff_less_or_close(dist, data, res.params, shapes, **self.tols) + + @pytest.mark.slow + def test_truncweibull_min(self): + # Can't guarantee that all distributions will fit all data with + # arbitrary bounds. This distribution just happens to fail above. + # Try something slightly different. + N = 1000 + rng = np.random.default_rng(self.seed) + dist = stats.truncweibull_min + shapes = (2.5, 0.25, 1.75, 2., 3.) + data = dist.rvs(*shapes, size=N, random_state=rng) + shape_bounds = [(0.1, 10)]*5 + res = stats.fit(dist, data, shape_bounds, optimizer=self.opt) + + assert_nlff_less_or_close(dist, data, res.params, shapes, **self.tols) + + def test_missing_shape_bounds(self): + # some distributions have a small domain w.r.t. a parameter, e.g. + # $p \in [0, 1]$ for binomial distribution + # User does not need to provide these because the intersection of the + # user's bounds (none) and the distribution's domain is finite + N = 1000 + rng = np.random.default_rng(self.seed) + + dist = stats.binom + n, p, loc = 10, 0.65, 0 + data = dist.rvs(n, p, loc=loc, size=N, random_state=rng) + shape_bounds = {'n': np.array([0, 20])} # check arrays are OK, too + res = stats.fit(dist, data, shape_bounds, optimizer=self.opt) + assert_allclose(res.params, (n, p, loc), **self.tols) + + dist = stats.bernoulli + p, loc = 0.314159, 0 + data = dist.rvs(p, loc=loc, size=N, random_state=rng) + res = stats.fit(dist, data, optimizer=self.opt) + assert_allclose(res.params, (p, loc), **self.tols) + + def test_fit_only_loc_scale(self): + # fit only loc + N = 5000 + rng = np.random.default_rng(self.seed) + + dist = stats.norm + loc, scale = 1.5, 1 + data = dist.rvs(loc=loc, size=N, random_state=rng) + loc_bounds = (0, 5) + bounds = {'loc': loc_bounds} + res = stats.fit(dist, data, bounds, optimizer=self.opt) + assert_allclose(res.params, (loc, scale), **self.tols) + + # fit only scale + loc, scale = 0, 2.5 + data = dist.rvs(scale=scale, size=N, random_state=rng) + scale_bounds = (0.01, 5) + bounds = {'scale': scale_bounds} + res = stats.fit(dist, data, bounds, optimizer=self.opt) + assert_allclose(res.params, (loc, scale), **self.tols) + + # fit only loc and scale + dist = stats.norm + loc, scale = 1.5, 2.5 + data = dist.rvs(loc=loc, scale=scale, size=N, random_state=rng) + bounds = {'loc': loc_bounds, 'scale': scale_bounds} + res = stats.fit(dist, data, bounds, optimizer=self.opt) + assert_allclose(res.params, (loc, scale), **self.tols) + + def test_everything_fixed(self): + N = 5000 + rng = np.random.default_rng(self.seed) + + dist = stats.norm + loc, scale = 1.5, 2.5 + data = dist.rvs(loc=loc, scale=scale, size=N, random_state=rng) + + # loc, scale fixed to 0, 1 by default + res = stats.fit(dist, data) + assert_allclose(res.params, (0, 1), **self.tols) + + # loc, scale explicitly fixed + bounds = {'loc': (loc, loc), 'scale': (scale, scale)} + res = stats.fit(dist, data, bounds) + assert_allclose(res.params, (loc, scale), **self.tols) + + # `n` gets fixed during polishing + dist = stats.binom + n, p, loc = 10, 0.65, 0 + data = dist.rvs(n, p, loc=loc, size=N, random_state=rng) + shape_bounds = {'n': (0, 20), 'p': (0.65, 0.65)} + res = stats.fit(dist, data, shape_bounds, optimizer=self.opt) + assert_allclose(res.params, (n, p, loc), **self.tols) + + def test_failure(self): + N = 5000 + rng = np.random.default_rng(self.seed) + + dist = stats.nbinom + shapes = (5, 0.5) + data = dist.rvs(*shapes, size=N, random_state=rng) + + assert data.min() == 0 + # With lower bounds on location at 0.5, likelihood is zero + bounds = [(0, 30), (0, 1), (0.5, 10)] + res = stats.fit(dist, data, bounds) + message = "Optimization converged to parameter values that are" + assert res.message.startswith(message) + assert res.success is False + + @pytest.mark.xslow + def test_guess(self): + # Test that guess helps DE find the desired solution + N = 2000 + # With some seeds, `fit` doesn't need a guess + rng = np.random.default_rng(196390444561) + dist = stats.nhypergeom + params = (20, 7, 12, 0) + bounds = [(2, 200), (0.7, 70), (1.2, 120), (0, 10)] + + data = dist.rvs(*params, size=N, random_state=rng) + + res = stats.fit(dist, data, bounds, optimizer=self.opt) + assert not np.allclose(res.params, params, **self.tols) + + res = stats.fit(dist, data, bounds, guess=params, optimizer=self.opt) + assert_allclose(res.params, params, **self.tols) + + def test_mse_accuracy_1(self): + # Test maximum spacing estimation against example from Wikipedia + # https://en.wikipedia.org/wiki/Maximum_spacing_estimation#Examples + data = [2, 4] + dist = stats.expon + bounds = {'loc': (0, 0), 'scale': (1e-8, 10)} + res_mle = stats.fit(dist, data, bounds=bounds, method='mle') + assert_allclose(res_mle.params.scale, 3, atol=1e-3) + res_mse = stats.fit(dist, data, bounds=bounds, method='mse') + assert_allclose(res_mse.params.scale, 3.915, atol=1e-3) + + def test_mse_accuracy_2(self): + # Test maximum spacing estimation against example from Wikipedia + # https://en.wikipedia.org/wiki/Maximum_spacing_estimation#Examples + rng = np.random.default_rng(9843212616816518964) + + dist = stats.uniform + n = 10 + data = dist(3, 6).rvs(size=n, random_state=rng) + bounds = {'loc': (0, 10), 'scale': (1e-8, 10)} + res = stats.fit(dist, data, bounds=bounds, method='mse') + # (loc=3.608118420015416, scale=5.509323262055043) + + x = np.sort(data) + a = (n*x[0] - x[-1])/(n - 1) + b = (n*x[-1] - x[0])/(n - 1) + ref = a, b-a # (3.6081133632151503, 5.509328130317254) + assert_allclose(res.params, ref, rtol=1e-4) + + +# Data from Matlab: https://www.mathworks.com/help/stats/lillietest.html +examgrades = [65, 61, 81, 88, 69, 89, 55, 84, 86, 84, 71, 81, 84, 81, 78, 67, + 96, 66, 73, 75, 59, 71, 69, 63, 79, 76, 63, 85, 87, 88, 80, 71, + 65, 84, 71, 75, 81, 79, 64, 65, 84, 77, 70, 75, 84, 75, 73, 92, + 90, 79, 80, 71, 73, 71, 58, 79, 73, 64, 77, 82, 81, 59, 54, 82, + 57, 79, 79, 73, 74, 82, 63, 64, 73, 69, 87, 68, 81, 73, 83, 73, + 80, 73, 73, 71, 66, 78, 64, 74, 68, 67, 75, 75, 80, 85, 74, 76, + 80, 77, 93, 70, 86, 80, 81, 83, 68, 60, 85, 64, 74, 82, 81, 77, + 66, 85, 75, 81, 69, 60, 83, 72] + + +class TestGoodnessOfFit: + + def test_gof_iv(self): + dist = stats.norm + x = [1, 2, 3] + + message = r"`dist` must be a \(non-frozen\) instance of..." + with pytest.raises(TypeError, match=message): + goodness_of_fit(stats.norm(), x) + + message = "`data` must be a one-dimensional array of numbers." + with pytest.raises(ValueError, match=message): + goodness_of_fit(dist, [[1, 2, 3]]) + + message = "`statistic` must be one of..." + with pytest.raises(ValueError, match=message): + goodness_of_fit(dist, x, statistic='mm') + + message = "`n_mc_samples` must be an integer." + with pytest.raises(TypeError, match=message): + goodness_of_fit(dist, x, n_mc_samples=1000.5) + + message = "SeedSequence expects int or sequence" + with pytest.raises(TypeError, match=message): + goodness_of_fit(dist, x, rng='herring') + + def test_against_ks(self): + rng = np.random.default_rng(8517426291317196949) + x = examgrades + known_params = {'loc': np.mean(x), 'scale': np.std(x, ddof=1)} + res = goodness_of_fit(stats.norm, x, known_params=known_params, + statistic='ks', rng=rng) + ref = stats.kstest(x, stats.norm(**known_params).cdf, method='exact') + assert_allclose(res.statistic, ref.statistic) # ~0.0848 + assert_allclose(res.pvalue, ref.pvalue, atol=5e-3) # ~0.335 + + def test_against_lilliefors(self): + rng = np.random.default_rng(2291803665717442724) + x = examgrades + # preserve use of old random_state during SPEC 7 transition + res = goodness_of_fit(stats.norm, x, statistic='ks', random_state=rng) + known_params = {'loc': np.mean(x), 'scale': np.std(x, ddof=1)} + ref = stats.kstest(x, stats.norm(**known_params).cdf, method='exact') + assert_allclose(res.statistic, ref.statistic) # ~0.0848 + assert_allclose(res.pvalue, 0.0348, atol=5e-3) + + def test_against_cvm(self): + rng = np.random.default_rng(8674330857509546614) + x = examgrades + known_params = {'loc': np.mean(x), 'scale': np.std(x, ddof=1)} + res = goodness_of_fit(stats.norm, x, known_params=known_params, + statistic='cvm', rng=rng) + ref = stats.cramervonmises(x, stats.norm(**known_params).cdf) + assert_allclose(res.statistic, ref.statistic) # ~0.090 + assert_allclose(res.pvalue, ref.pvalue, atol=5e-3) # ~0.636 + + def test_against_anderson_case_0(self): + # "Case 0" is where loc and scale are known [1] + rng = np.random.default_rng(7384539336846690410) + x = np.arange(1, 101) + # loc that produced critical value of statistic found w/ root_scalar + known_params = {'loc': 45.01575354024957, 'scale': 30} + res = goodness_of_fit(stats.norm, x, known_params=known_params, + statistic='ad', rng=rng) + assert_allclose(res.statistic, 2.492) # See [1] Table 1A 1.0 + assert_allclose(res.pvalue, 0.05, atol=5e-3) + + def test_against_anderson_case_1(self): + # "Case 1" is where scale is known and loc is fit [1] + rng = np.random.default_rng(5040212485680146248) + x = np.arange(1, 101) + # scale that produced critical value of statistic found w/ root_scalar + known_params = {'scale': 29.957112639101933} + res = goodness_of_fit(stats.norm, x, known_params=known_params, + statistic='ad', rng=rng) + assert_allclose(res.statistic, 0.908) # See [1] Table 1B 1.1 + assert_allclose(res.pvalue, 0.1, atol=5e-3) + + def test_against_anderson_case_2(self): + # "Case 2" is where loc is known and scale is fit [1] + rng = np.random.default_rng(726693985720914083) + x = np.arange(1, 101) + # loc that produced critical value of statistic found w/ root_scalar + known_params = {'loc': 44.5680212261933} + res = goodness_of_fit(stats.norm, x, known_params=known_params, + statistic='ad', rng=rng) + assert_allclose(res.statistic, 2.904) # See [1] Table 1B 1.2 + assert_allclose(res.pvalue, 0.025, atol=5e-3) + + def test_against_anderson_case_3(self): + # "Case 3" is where both loc and scale are fit [1] + rng = np.random.default_rng(6763691329830218206) + # c that produced critical value of statistic found w/ root_scalar + x = stats.skewnorm.rvs(1.4477847789132101, loc=1, scale=2, size=100, + random_state=rng) + res = goodness_of_fit(stats.norm, x, statistic='ad', rng=rng) + assert_allclose(res.statistic, 0.559) # See [1] Table 1B 1.2 + assert_allclose(res.pvalue, 0.15, atol=5e-3) + + @pytest.mark.xslow + def test_against_anderson_gumbel_r(self): + rng = np.random.default_rng(7302761058217743) + # c that produced critical value of statistic found w/ root_scalar + x = stats.genextreme(0.051896837188595134, loc=0.5, + scale=1.5).rvs(size=1000, random_state=rng) + res = goodness_of_fit(stats.gumbel_r, x, statistic='ad', rng=rng) + ref = stats.anderson(x, dist='gumbel_r', method='interpolate') + assert_allclose(res.statistic, ref.statistic) + assert_allclose(res.pvalue, ref.pvalue, atol=5e-3) + + def test_against_filliben_norm(self): + # Test against `stats.fit` ref. [7] Section 8 "Example" + rng = np.random.default_rng(8024266430745011915) + y = [6, 1, -4, 8, -2, 5, 0] + known_params = {'loc': 0, 'scale': 1} + res = stats.goodness_of_fit(stats.norm, y, known_params=known_params, + statistic="filliben", rng=rng) + # Slight discrepancy presumably due to roundoff in Filliben's + # calculation. Using exact order statistic medians instead of + # Filliben's approximation doesn't account for it. + assert_allclose(res.statistic, 0.98538, atol=1e-4) + assert 0.75 < res.pvalue < 0.9 + + # Using R's ppcc library: + # library(ppcc) + # options(digits=16) + # x < - c(6, 1, -4, 8, -2, 5, 0) + # set.seed(100) + # ppccTest(x, "qnorm", ppos="Filliben") + # Discrepancy with + assert_allclose(res.statistic, 0.98540957187084, rtol=2e-5) + assert_allclose(res.pvalue, 0.8875, rtol=2e-3) + + def test_filliben_property(self): + # Filliben's statistic should be independent of data location and scale + rng = np.random.default_rng(8535677809395478813) + x = rng.normal(loc=10, scale=0.5, size=100) + res = stats.goodness_of_fit(stats.norm, x, + statistic="filliben", rng=rng) + known_params = {'loc': 0, 'scale': 1} + ref = stats.goodness_of_fit(stats.norm, x, known_params=known_params, + statistic="filliben", rng=rng) + assert_allclose(res.statistic, ref.statistic, rtol=1e-15) + + @pytest.mark.parametrize('case', [(25, [.928, .937, .950, .958, .966]), + (50, [.959, .965, .972, .977, .981]), + (95, [.977, .979, .983, .986, .989])]) + def test_against_filliben_norm_table(self, case): + # Test against `stats.fit` ref. [7] Table 1 + rng = np.random.default_rng(504569995557928957) + n, ref = case + x = rng.random(n) + known_params = {'loc': 0, 'scale': 1} + res = stats.goodness_of_fit(stats.norm, x, known_params=known_params, + statistic="filliben", rng=rng) + percentiles = np.array([0.005, 0.01, 0.025, 0.05, 0.1]) + res = stats.scoreatpercentile(res.null_distribution, percentiles*100) + assert_allclose(res, ref, atol=2e-3) + + @pytest.mark.xslow + @pytest.mark.parametrize('case', [(5, 0.95772790260469, 0.4755), + (6, 0.95398832257958, 0.3848), + (7, 0.9432692889277, 0.2328)]) + def test_against_ppcc(self, case): + # Test against R ppcc, e.g. + # library(ppcc) + # options(digits=16) + # x < - c(0.52325412, 1.06907699, -0.36084066, 0.15305959, 0.99093194) + # set.seed(100) + # ppccTest(x, "qrayleigh", ppos="Filliben") + n, ref_statistic, ref_pvalue = case + rng = np.random.default_rng(7777775561439803116) + x = rng.normal(size=n) + res = stats.goodness_of_fit(stats.rayleigh, x, statistic="filliben", + rng=rng) + assert_allclose(res.statistic, ref_statistic, rtol=1e-4) + assert_allclose(res.pvalue, ref_pvalue, atol=1.5e-2) + + def test_params_effects(self): + # Ensure that `guessed_params`, `fit_params`, and `known_params` have + # the intended effects. + rng = np.random.default_rng(9121950977643805391) + x = stats.skewnorm.rvs(-5.044559778383153, loc=1, scale=2, size=50, + random_state=rng) + + # Show that `guessed_params` don't fit to the guess, + # but `fit_params` and `known_params` respect the provided fit + guessed_params = {'c': 13.4} + fit_params = {'scale': 13.73} + known_params = {'loc': -13.85} + rng = np.random.default_rng(9121950977643805391) + res1 = goodness_of_fit(stats.weibull_min, x, n_mc_samples=2, + guessed_params=guessed_params, + fit_params=fit_params, + known_params=known_params, rng=rng) + assert not np.allclose(res1.fit_result.params.c, 13.4) + assert_equal(res1.fit_result.params.scale, 13.73) + assert_equal(res1.fit_result.params.loc, -13.85) + + # Show that changing the guess changes the parameter that gets fit, + # and it changes the null distribution + guessed_params = {'c': 2} + rng = np.random.default_rng(9121950977643805391) + res2 = goodness_of_fit(stats.weibull_min, x, n_mc_samples=2, + guessed_params=guessed_params, + fit_params=fit_params, + known_params=known_params, rng=rng) + assert not np.allclose(res2.fit_result.params.c, + res1.fit_result.params.c, rtol=1e-8) + assert not np.allclose(res2.null_distribution, + res1.null_distribution, rtol=1e-8) + assert_equal(res2.fit_result.params.scale, 13.73) + assert_equal(res2.fit_result.params.loc, -13.85) + + # If we set all parameters as fit_params and known_params, + # they're all fixed to those values, but the null distribution + # varies. + fit_params = {'c': 13.4, 'scale': 13.73} + rng = np.random.default_rng(9121950977643805391) + res3 = goodness_of_fit(stats.weibull_min, x, n_mc_samples=2, + guessed_params=guessed_params, + fit_params=fit_params, + known_params=known_params, rng=rng) + assert_equal(res3.fit_result.params.c, 13.4) + assert_equal(res3.fit_result.params.scale, 13.73) + assert_equal(res3.fit_result.params.loc, -13.85) + assert not np.allclose(res3.null_distribution, res1.null_distribution) + + def test_custom_statistic(self): + # Test support for custom statistic function. + + # References: + # [1] Pyke, R. (1965). "Spacings". Journal of the Royal Statistical + # Society: Series B (Methodological), 27(3): 395-436. + # [2] Burrows, P. M. (1979). "Selected Percentage Points of + # Greenwood's Statistics". Journal of the Royal Statistical + # Society. Series A (General), 142(2): 256-258. + + # Use the Greenwood statistic for illustration; see [1, p.402]. + def greenwood(dist, data, *, axis): + x = np.sort(data, axis=axis) + y = dist.cdf(x) + d = np.diff(y, axis=axis, prepend=0, append=1) + return np.sum(d ** 2, axis=axis) + + # Run the Monte Carlo test with sample size = 5 on a fully specified + # null distribution, and compare the simulated quantiles to the exact + # ones given in [2, Table 1, column (n = 5)]. + rng = np.random.default_rng(9121950977643805391) + data = stats.expon.rvs(size=5, random_state=rng) + result = goodness_of_fit(stats.expon, data, + known_params={'loc': 0, 'scale': 1}, + statistic=greenwood, rng=rng) + p = [.01, .05, .1, .2, .3, .4, .5, .6, .7, .8, .9, .95, .99] + exact_quantiles = [ + .183863, .199403, .210088, .226040, .239947, .253677, .268422, + .285293, .306002, .334447, .382972, .432049, .547468] + simulated_quantiles = np.quantile(result.null_distribution, p) + assert_allclose(simulated_quantiles, exact_quantiles, atol=0.005) + +class TestFitResult: + def test_plot_iv(self): + rng = np.random.default_rng(1769658657308472721) + data = stats.norm.rvs(0, 1, size=100, random_state=rng) + + def optimizer(*args, **kwargs): + return differential_evolution(*args, **kwargs, rng=rng) + + bounds = [(0, 30), (0, 1)] + res = stats.fit(stats.norm, data, bounds, optimizer=optimizer) + try: + import matplotlib # noqa: F401 + message = r"`plot_type` must be one of \{'..." + with pytest.raises(ValueError, match=message): + res.plot(plot_type='llama') + except (ModuleNotFoundError, ImportError): + message = r"matplotlib must be installed to use method `plot`." + with pytest.raises(ModuleNotFoundError, match=message): + res.plot(plot_type='llama') diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/test_generation/__pycache__/reference_distribution_infrastructure_tests.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_generation/__pycache__/reference_distribution_infrastructure_tests.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5d1d158e475cf2fa6f3fc8adf6523f48bdf95524 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_generation/__pycache__/reference_distribution_infrastructure_tests.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/test_generation/__pycache__/reference_distributions.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_generation/__pycache__/reference_distributions.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..63718962616d86be9efb005d10e8556218239bb5 Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_generation/__pycache__/reference_distributions.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/test_generation/__pycache__/studentized_range_mpmath_ref.cpython-312.pyc b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_generation/__pycache__/studentized_range_mpmath_ref.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4a1fc1af02a98fc2c615eeb431c886958991f85e Binary files /dev/null and b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_generation/__pycache__/studentized_range_mpmath_ref.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/test_generation/generate_fisher_exact_results_from_r.R b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_generation/generate_fisher_exact_results_from_r.R new file mode 100644 index 0000000000000000000000000000000000000000..f49f9fb7ab10701233ebc411dfe2a362f87719a2 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_generation/generate_fisher_exact_results_from_r.R @@ -0,0 +1,84 @@ +# This R script generates the Python file fisher_exact_results_from_r.py. +# To run this script, use +# $ Rscript generate_fisher_exact_results_from_r.R + +sink("fisher_exact_results_from_r.py") + +options(digits=15) + +# Note that when converted to a matrix, `byrow=TRUE` is used, so +# the first two values of each data set become the first row of +# the contingency table. E.g. +# > matrix(c(100, 2, 1000, 5), nrow=2, ncol=2, byrow=TRUE) +# [,1] [,2] +# [1,] 100 2 +# [2,] 1000 5 +# +table_data = list( + c(100, 2, 1000, 5), + c(2, 7, 8, 2), + c(5, 1, 10, 10), + c(5, 15, 20, 20), + c(5, 16, 16, 25), + c(10, 5, 10, 1), + c(10, 5, 10, 0), + c(5, 0, 1, 4), + c(0, 5, 1, 4), + c(5, 1, 0, 4), + c(0, 1, 3, 2), + c(200, 7, 8, 300), + c(28, 21, 6, 1957), + c(190, 800, 200, 900) +) +confidence.levels = c(0.95, 0.99) +alternatives = c("two.sided", "less", "greater") + +cat("# DO NOT EDIT THIS FILE!", fill=TRUE) +cat("# This file was generated by the R script", fill=TRUE) +cat("# generate_fisher_exact_results_from_r.R", fill=TRUE) +cat("# The script was run with", R.version.string, "at ") +s = sprintf("%s", Sys.time()) +cat(s, fill=TRUE) +cat("", fill=TRUE) +cat("", fill=TRUE) +cat("from collections import namedtuple", fill=TRUE) +cat("import numpy as np", fill=TRUE) +cat("", fill=TRUE) +cat("", fill=TRUE) +cat("Inf = np.inf", fill=TRUE) +cat("", fill=TRUE) +cat("Parameters = namedtuple('Parameters',", fill=TRUE) +cat(" ['table', 'confidence_level', 'alternative'])", fill=TRUE) +cat("RResults = namedtuple('RResults',", fill=TRUE) +cat(" ['pvalue', 'conditional_odds_ratio',", fill=TRUE) +cat(" 'conditional_odds_ratio_ci'])", fill=TRUE) +cat("data = [", fill=TRUE) +for (alternative in alternatives) { + for (conf.level in confidence.levels) { + for (data in table_data) { + table = matrix(data, nrow=2, ncol=2, byrow=TRUE) + #result = fisher.test(table, alternative=alternative, conf.level=conf.level) + result = fisher.test(table, alternative=alternative, conf.level=conf.level) + cat(" (") + cat("Parameters(table=") + s = sprintf("[[%.16g, %.16g], [%.16g, %.16g]],", table[1, 1], table[1, 2], table[2, 1], table[2, 2]) + cat(s, fill=TRUE) + s = sprintf(" confidence_level=%.16g,", conf.level) + cat(s, fill=TRUE) + s = sprintf(" alternative='%s'),", alternative) + cat(s, fill=TRUE) + cat(" RResults(") + s = sprintf("pvalue=%.16g,", result$p.value) + cat(s, fill=TRUE) + s = sprintf(" conditional_odds_ratio=%.16g,", result$estimate) + cat(s, fill=TRUE) + s = sprintf(" conditional_odds_ratio_ci=(%.16g,", result$conf.int[1]) + cat(s, fill=TRUE) + s = sprintf(" %.16g))),", result$conf.int[2]) + cat(s, fill=TRUE) + } + } +} +cat("]", fill=TRUE) + +sink() diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/test_generation/reference_distribution_infrastructure_tests.py b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_generation/reference_distribution_infrastructure_tests.py new file mode 100644 index 0000000000000000000000000000000000000000..9f44d24da8804b8bf9c84685f6892dd11723d933 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_generation/reference_distribution_infrastructure_tests.py @@ -0,0 +1,90 @@ +# Note: this file is to be run locally, not on CI. It is only for tests of the +# reference distribution *infrastructure*; unit tests of SciPy distributions +# do not go here. + +import numpy as np +import pytest + +from scipy import stats +from numpy.testing import assert_allclose +import scipy.stats.tests.test_generation.reference_distributions as rd +import mpmath +from mpmath import mp + +def test_basic(): + + message = "`mpmath.mp.dps <= 15`. Set a higher precision..." + with pytest.raises(RuntimeError, match=message): + rd.Normal() + + mpmath.dps = 20 + message = "`mpmath.dps` has been assigned. This is not intended usage..." + with pytest.raises(RuntimeError, match=message): + rd.Normal() + del mpmath.dps + + mp.dps = 20 # high enough to pass, not unreasonably slow + + # Basic tests of the mpmath distribution infrastructure using a SciPy + # distribution as a reference. The intent is just to make sure that the + # implementations do not have *mistakes* and that broadcasting is working + # as expected. The accuracy is what it is. + + rng = np.random.default_rng(6716188855217730280) + + x = rng.random(size=3) + a = rng.random(size=(2, 1)) + rtol = 1e-15 + + dist = rd.SkewNormal(a=a) + dist_ref = stats.skewnorm(a) + + assert_allclose(dist.pdf(x), dist_ref.pdf(x), rtol=rtol) + assert_allclose(dist.cdf(x), dist_ref.cdf(x), rtol=rtol) + assert_allclose(dist.sf(x), dist_ref.sf(x), rtol=rtol) + assert_allclose(dist.ppf(x), dist_ref.ppf(x), rtol=rtol) + assert_allclose(dist.isf(x), dist_ref.isf(x), rtol=rtol) + assert_allclose(dist.logpdf(x), dist_ref.logpdf(x), rtol=rtol) + assert_allclose(dist.logcdf(x), dist_ref.logcdf(x), rtol=rtol) + assert_allclose(dist.logsf(x), dist_ref.logsf(x), rtol=rtol) + assert_allclose(dist.support(), dist_ref.support(), rtol=rtol) + assert_allclose(dist.entropy(), dist_ref.entropy(), rtol=rtol) + assert_allclose(dist.mean(), dist_ref.mean(), rtol=rtol) + assert_allclose(dist.var(), dist_ref.var(), rtol=rtol) + assert_allclose(dist.skew(), dist_ref.stats('s'), rtol=rtol) + assert_allclose(dist.kurtosis(), dist_ref.stats('k'), rtol=rtol) + + +def test_complementary_method_use(): + # Show that complementary methods are used as expected. + # E.g., use 1 - CDF to compute SF if CDF is overridden but SF is not + + mp.dps = 50 + x = np.linspace(0, 1, 10) + class MyDist(rd.ReferenceDistribution): + def _cdf(self, x): + return x + + dist = MyDist() + assert_allclose(dist.sf(x), 1 - dist.cdf(x)) + + class MyDist(rd.ReferenceDistribution): + def _sf(self, x): + return 1-x + + dist = MyDist() + assert_allclose(dist.cdf(x), 1 - dist.sf(x)) + + class MyDist(rd.ReferenceDistribution): + def _ppf(self, x, guess): + return x + + dist = MyDist() + assert_allclose(dist.isf(x), dist.ppf(1-x)) + + class MyDist(rd.ReferenceDistribution): + def _isf(self, x, guess): + return 1-x + + dist = MyDist() + assert_allclose(dist.ppf(x), dist.isf(1-x)) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/test_generation/reference_distributions.py b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_generation/reference_distributions.py new file mode 100644 index 0000000000000000000000000000000000000000..037b16a2d0c88ddf75416fa1324d010d35e0b363 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_generation/reference_distributions.py @@ -0,0 +1,430 @@ +import numpy as np +import mpmath +from mpmath import mp + + +class ReferenceDistribution: + """Minimalist distribution infrastructure for generating reference data. + + The purpose is to generate reference values for unit tests of SciPy + distribution accuracy and robustness. + + Handles array input with standard broadcasting rules, and method + implementations are easily compared against their mathematical definitions. + No attempt is made to handle edge cases or be fast, and arbitrary precision + arithmetic is trusted for accuracy rather than making the method + implementations "smart". + + Notes + ----- + + In this infrastructure, distributions families are classes, and + fully-specified distributions (i.e. with definite values of all family + parameters) are instances of these classes. Typically, the public methods + accept as input only the argument at which the at which the function is to + be evaluated. Unlike SciPy distributions, they never accept values of + distribution family shape, location, or scale parameters. A few + other parameters are noteworthy: + + - All methods accept `dtype` to control the output data type. The default + is `np.float64`, but `object` or `mp.mpf` may be + specified to output the full `mpf`. + - `ppf`/`isf` accept a `guess` because they use a scalar rootfinder + to invert the `cdf`/`sf`. This is passed directly into the `x0` method + of `mpmath.findroot`; see its documentation for details. + - moment accepts `order`, an integer that specifies the order of the (raw) + moment, and `center`, which is the value about which the moment is + taken. The default is to calculate the mean and use it to calculate + central moments; passing ``0`` results in a noncentral moment. For + efficiency, the mean can be passed explicitly if it is already known. + + Follow the example of SkewNormal to generate new reference distributions, + overriding only `__init__` and `_pdf`*. Use the reference distributions to + generate reference values for unit tests of SciPy distribution method + precision and robustness (e.g. for extreme arguments). If the a SciPy + methods implementation is independent and yet the output matches reference + values generated with this infrastructure, it is unlikely that the SciPy + and reference values are both inaccurate. + + * If the SciPy output *doesn't* match and the cause appears to be + inaccuracy of the reference values (e.g. due to numerical issues that + mpmath's arbitrary precision arithmetic doesn't handle), then it may be + appropriate to override a method of the reference distribution rather than + relying on the generic implementation. Otherwise, hesitate to override + methods: the generic implementations are mathematically correct and easy + to verify, whereas an override introduces many possibilities of mistakes, + requires more time to write, and requires more time to review. + + In general, do not create custom unit tests to ensure that + SciPy distribution methods are *correct* (in the sense of being consistent + with the rest of the distribution methods); generic tests take care of + that. + """ + + def __init__(self, **kwargs): + try: + if mpmath.dps is not None: + message = ("`mpmath.dps` has been assigned. This is not " + "intended usage; instead, assign the desired " + "precision to `mpmath.mp.dps` (e.g. `from mpmath " + "as mp; mp.dps = 50.") + raise RuntimeError(message) + except AttributeError: + mpmath.dps = None + + if mp.dps <= 15: + message = ("`mpmath.mp.dps <= 15`. Set a higher precision (e.g." + "`50`) to use this distribution.") + raise RuntimeError(message) + + self._params = {key:self._make_mpf_array(val) + for key, val in kwargs.items()} + + def _make_mpf_array(self, x): + shape = np.shape(x) + x = np.asarray(x, dtype=np.float64).ravel() + return np.asarray([mp.mpf(xi) for xi in x]).reshape(shape)[()] + + def _pdf(self, x): + raise NotImplementedError("_pdf must be overridden.") + + def _cdf(self, x, **kwargs): + if ((self._cdf.__func__ is ReferenceDistribution._cdf) + and (self._sf.__func__ is not ReferenceDistribution._sf)): + return mp.one - self._sf(x, **kwargs) + + a, b = self._support(**kwargs) + res = mp.quad(lambda x: self._pdf(x, **kwargs), (a, x)) + res = res if res < 0.5 else mp.one - self._sf(x, **kwargs) + return res + + def _sf(self, x, **kwargs): + if ((self._sf.__func__ is ReferenceDistribution._sf) + and (self._cdf.__func__ is not ReferenceDistribution._cdf)): + return mp.one - self._cdf(x, **kwargs) + + a, b = self._support(**kwargs) + res = mp.quad(lambda x: self._pdf(x, **kwargs), (x, b)) + res = res if res < 0.5 else mp.one - self._cdf(x, **kwargs) + return res + + def _ppf(self, p, guess=0, **kwargs): + if ((self._ppf.__func__ is ReferenceDistribution._ppf) + and (self._isf.__func__ is not ReferenceDistribution._isf)): + return self._isf(mp.one - p, guess, **kwargs) + def f(x): + return self._cdf(x, **kwargs) - p + return mp.findroot(f, guess) + + def _isf(self, p, guess=0, **kwargs): + if ((self._isf.__func__ is ReferenceDistribution._isf) + and (self._ppf.__func__ is not ReferenceDistribution._ppf)): + return self._ppf(mp.one - p, guess, **kwargs) + def f(x): + return self._sf(x, **kwargs) - p + return mp.findroot(f, guess) + + def _logpdf(self, x, **kwargs): + return mp.log(self._pdf(x, **kwargs)) + + def _logcdf(self, x, **kwargs): + return mp.log(self._cdf(x, **kwargs)) + + def _logsf(self, x, **kwargs): + return mp.log(self._sf(x, **kwargs)) + + def _support(self, **kwargs): + return -mp.inf, mp.inf + + def _entropy(self, **kwargs): + def integrand(x): + logpdf = self._logpdf(x, **kwargs) + pdf = mp.exp(logpdf) + return -pdf*logpdf + + a, b = self._support(**kwargs) + return mp.quad(integrand, (a, b)) + + def _mean(self, **kwargs): + return self._moment(order=1, center=0, **kwargs) + + def _var(self, **kwargs): + mu = self._mean(**kwargs) + return self._moment(order=2, center=mu, **kwargs) + + def _skew(self, **kwargs): + mu = self._mean(**kwargs) + u2 = self._moment(order=2, center=mu, **kwargs) + sigma = mp.sqrt(u2) + u3 = self._moment(order=3, center=mu, **kwargs) + return u3 / sigma**3 + + def _kurtosis(self, **kwargs): + mu = self._mean(**kwargs) + u2 = self._moment(order=2, center=mu, **kwargs) + u4 = self._moment(order=4, center=mu, **kwargs) + return u4 / u2**2 - 3 + + def _moment(self, order, center, **kwargs): + def integrand(x): + return self._pdf(x, **kwargs)*(x - center)**order + + if center is None: + center = self._mean(**kwargs) + + a, b = self._support(**kwargs) + return mp.quad(integrand, (a, b)) + + def pdf(self, x, dtype=np.float64): + fun = np.vectorize(self._pdf) + x = self._make_mpf_array(x) + res = fun(x, **self._params) + return np.asarray(res, dtype=dtype)[()] + + def cdf(self, x, dtype=np.float64): + fun = np.vectorize(self._cdf) + x = self._make_mpf_array(x) + res = fun(x, **self._params) + return np.asarray(res, dtype=dtype)[()] + + def sf(self, x, dtype=np.float64): + fun = np.vectorize(self._sf) + x = self._make_mpf_array(x) + res = fun(x, **self._params) + return np.asarray(res, dtype=dtype)[()] + + def ppf(self, x, guess=0, dtype=np.float64): + fun = np.vectorize(self._ppf, excluded={1}) # don't vectorize guess + x = self._make_mpf_array(x) + res = fun(x, guess, **self._params) + return np.asarray(res, dtype=dtype)[()] + + def isf(self, x, guess=0, dtype=np.float64): + fun = np.vectorize(self._isf, excluded={1}) # don't vectorize guess + x = self._make_mpf_array(x) + res = fun(x, guess, **self._params) + return np.asarray(res, dtype=dtype)[()] + + def logpdf(self, x, dtype=np.float64): + fun = np.vectorize(self._logpdf) + x = self._make_mpf_array(x) + res = fun(x, **self._params) + return np.asarray(res, dtype=dtype)[()] + + def logcdf(self, x, dtype=np.float64): + fun = np.vectorize(self._logcdf) + x = self._make_mpf_array(x) + res = fun(x, **self._params) + return np.asarray(res, dtype=dtype)[()] + + def logsf(self, x, dtype=np.float64): + fun = np.vectorize(self._logsf) + x = self._make_mpf_array(x) + res = fun(x, **self._params) + return np.asarray(res, dtype=dtype)[()] + + def support(self, dtype=np.float64): + fun = np.vectorize(self._support) + res = fun(**self._params) + return np.asarray(res, dtype=dtype)[()] + + def entropy(self, dtype=np.float64): + fun = np.vectorize(self._entropy) + res = fun(**self._params) + return np.asarray(res, dtype=dtype)[()] + + def mean(self, dtype=np.float64): + fun = np.vectorize(self._mean) + res = fun(**self._params) + return np.asarray(res, dtype=dtype)[()] + + def var(self, dtype=np.float64): + fun = np.vectorize(self._var) + res = fun(**self._params) + return np.asarray(res, dtype=dtype)[()] + + def skew(self, dtype=np.float64): + fun = np.vectorize(self._skew) + res = fun(**self._params) + return np.asarray(res, dtype=dtype)[()] + + def kurtosis(self, dtype=np.float64): + fun = np.vectorize(self._kurtosis) + res = fun(**self._params) + return np.asarray(res, dtype=dtype)[()] + + def moment(self, order, center=None, dtype=np.float64): + fun = np.vectorize(self._moment) + order = self._make_mpf_array(order) + res = fun(order, **self._params) + return np.asarray(res, dtype=dtype)[()] + + +class SkewNormal(ReferenceDistribution): + """Reference implementation of the SkewNormal distribution. + + Follow the example here to generate new reference distributions. + Use the reference distributions to generate reference values of + distributions functions. For now, copy-paste the output into unit + tests. Full code to generate reference values does not need to be + included as a comment in the test; just refer to the reference + distribution used and the settings (e.g. mp.dps=50). + """ + + def __init__(self, *, a): + # Overriding __init__ is not necessary, but it allows IDEs to hint at + # shape parameters. All parameters are keyword only to avoid the + # ambiguity inherent in positional arguments. The infrastructure does + # not take care of location and scale; nonetheless, assume standard + # location and scale. Typically, there is no need to test the SciPy + # distribution infrastructure's treatment of location and scale + # separately for a specific distribution. + super().__init__(a=a) + + def _support(self, a): + # Override _support if the support of the distribution is a subset of + # the real line + return -mp.inf, mp.inf + + def _pdf(self, x, a): + # Write PDFs following a scholarly reference as closely as possible. + # Trust mpmath for the accuracy, and don't worry about speed. What's + # important is the ease of verifying the PDF against the reference. If + # the result is inaccurate, it will not match SciPy's output (whether + # SciPy is accurate or not). If this occurs, try increasing dps before + # implementing a numerically favorable (but presumably more complex) + # implementation. + return 2 * mp.npdf(x) * mp.ncdf(a * x) + + # Avoid overriding other methods unless the generic implementation is + # believed to be inaccurate (e.g. due to numerical difficulties) or it is + # too slow. Why? Less code to write, less code to review, and a guarantee + # that there is no *mistake* in the implementation (e.g. wrong formula). + + +class BetaPrime(ReferenceDistribution): + + def __init__(self, *, a, b): + super().__init__(a=a, b=b) + + def _support(self, **kwargs): + return mp.zero, mp.inf + + def _logpdf(self, x, a, b): + return (a - mp.one)*mp.log(x) - (a + b)*mp.log1p(x) - mp.log(mp.beta(a, b)) + + def _pdf(self, x, a, b): + return mp.exp(self._logpdf(x=x, a=a, b=b)) + + def _sf(self, x, a, b): + return 1.0 - mp.betainc(a, b, 0, x/(1+x), regularized=True) + + +class Burr(ReferenceDistribution): + + def __init__(self, *, c, d): + super().__init__(c=c, d=d) + + def _support(self, c, d): + return 0, mp.inf + + def _pdf(self, x, c, d): + return c * d * x ** (-c - 1) * (1 + x ** (-c)) ** (-d - 1) + + def _ppf(self, p, guess, c, d): + return (p**(-1.0/d) - 1)**(-1.0/c) + + +class LogLaplace(ReferenceDistribution): + + def __init__(self, *, c): + super().__init__(c=c) + + def _support(self, c): + return 0, mp.inf + + def _pdf(self, x, c): + if x < mp.one: + return c / 2 * x**(c - mp.one) + else: + return c / 2 * x**(-c - mp.one) + + def _ppf(self, q, guess, c): + if q < 0.5: + return (2.0 * q)**(mp.one / c) + else: + return (2 * (mp.one - q))**(-mp.one / c) + + +class LogNormal(ReferenceDistribution): + + def __init__(self, *, s): + super().__init__(s=s) + + def _support(self, s): + return 0, mp.inf + + def _pdf(self, x, s): + return ( + mp.one / (s * x * mp.sqrt(2 * mp.pi)) + * mp.exp(-mp.one / 2 * (mp.log(x) / s)**2) + ) + + def _cdf(self, x, s): + return mp.ncdf(mp.log(x) / s) + + +class Normal(ReferenceDistribution): + + def _pdf(self, x): + return mp.npdf(x) + + +class NormInvGauss(ReferenceDistribution): + + def __init__(self, *, alpha, beta): + super().__init__(alpha=alpha, beta=beta) + + def _pdf(self, x, alpha, beta): + # Implemented as described in https://www.jstor.org/stable/4616433 + # Equations 2.1 - 2.3 + q = mp.sqrt(1 + x**2) + a = mp.pi**-1 * alpha * mp.exp(mp.sqrt(alpha**2 - beta**2)) + return a * q**-1 * mp.besselk(1, alpha*q) * mp.exp(beta*x) + + +class Pearson3(ReferenceDistribution): + def __init__(self, *, skew): + super().__init__(skew=skew) + + def _pdf(self, x, skew): + b = 2 / skew + a = b**2 + c = -b + res = abs(b)/mp.gamma(a) * (b*(x-c))**(a-1) * mp.exp(-b*(x-c)) + return res if abs(res.real) == res else 0 + + +class StudentT(ReferenceDistribution): + + def __init(self, *, df): + super().__init__(df=df) + + def _pdf(self, x, df): + return (mp.gamma((df + mp.one)/2)/(mp.sqrt(df * mp.pi) * mp.gamma(df/2)) + * (mp.one + x*x/df)**(-(df + mp.one)/2)) + + +class TruncExpon(ReferenceDistribution): + + def __init__(self, *, b): + super().__init__(b=b) + + def _support(self, b): + return 0, b + + def _pdf(self, x, b): + return -mp.exp(-x)/mp.expm1(-b) + + def _sf(self, x, b): + return (mp.exp(-b) - mp.exp(-x))/mp.expm1(-b) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/test_generation/studentized_range_mpmath_ref.py b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_generation/studentized_range_mpmath_ref.py new file mode 100644 index 0000000000000000000000000000000000000000..2f67a3453e08561cfab7f2d85b34a58d7e345992 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_generation/studentized_range_mpmath_ref.py @@ -0,0 +1,252 @@ +# To run this script, run +# `python studentized_range_mpmath_ref.py` +# in the "scipy/stats/tests/" directory + +# This script generates a JSON file "./data/studentized_range_mpmath_ref.json" +# that is used to compare the accuracy of `studentized_range` functions against +# precise (20 DOP) results generated using `mpmath`. + +# Equations in this file have been taken from +# https://en.wikipedia.org/wiki/Studentized_range_distribution +# and have been checked against the following reference: +# Lund, R. E., and J. R. Lund. "Algorithm AS 190: Probabilities and +# Upper Quantiles for the Studentized Range." Journal of the Royal +# Statistical Society. Series C (Applied Statistics), vol. 32, no. 2, +# 1983, pp. 204-210. JSTOR, www.jstor.org/stable/2347300. Accessed 18 +# Feb. 2021. + +# Note: I would have preferred to use pickle rather than JSON, but - +# due to security concerns - decided against it. +import itertools +from collections import namedtuple +import json +import time + +import os +from multiprocessing import Pool, cpu_count + +from mpmath import gamma, pi, sqrt, quad, inf, mpf, mp +from mpmath import npdf as phi +from mpmath import ncdf as Phi + +results_filepath = "data/studentized_range_mpmath_ref.json" +num_pools = max(cpu_count() - 1, 1) + +MPResult = namedtuple("MPResult", ["src_case", "mp_result"]) + +CdfCase = namedtuple("CdfCase", + ["q", "k", "v", "expected_atol", "expected_rtol"]) + +MomentCase = namedtuple("MomentCase", + ["m", "k", "v", "expected_atol", "expected_rtol"]) + +# Load previously generated JSON results, or init a new dict if none exist +if os.path.isfile(results_filepath): + res_dict = json.load(open(results_filepath)) +else: + res_dict = dict() + +# Frame out data structure. Store data with the function type as a top level +# key to allow future expansion +res_dict["COMMENT"] = ("!!!!!! THIS FILE WAS AUTOGENERATED BY RUNNING " + "`python studentized_range_mpmath_ref.py` !!!!!!") +res_dict.setdefault("cdf_data", []) +res_dict.setdefault("pdf_data", []) +res_dict.setdefault("moment_data", []) + +general_atol, general_rtol = 1e-11, 1e-11 + +mp.dps = 24 + +cp_q = [0.1, 1, 4, 10] +cp_k = [3, 10, 20] +cp_nu = [3, 10, 20, 50, 100, 120] + +cdf_pdf_cases = [ + CdfCase(*case, + general_atol, + general_rtol) + for case in + itertools.product(cp_q, cp_k, cp_nu) +] + +mom_atol, mom_rtol = 1e-9, 1e-9 +# These are EXTREMELY slow - Multiple days each in worst case. +moment_cases = [ + MomentCase(i, 3, 10, mom_atol, mom_rtol) + for i in range(5) +] + + +def write_data(): + """Writes the current res_dict to the target JSON file""" + with open(results_filepath, mode="w") as f: + json.dump(res_dict, f, indent=2) + + +def to_dict(named_tuple): + """Converts a namedtuple to a dict""" + return dict(named_tuple._asdict()) + + +def mp_res_to_dict(mp_result): + """Formats an MPResult namedtuple into a dict for JSON dumping""" + return { + "src_case": to_dict(mp_result.src_case), + + # np assert can't handle mpf, so take the accuracy hit here. + "mp_result": float(mp_result.mp_result) + } + + +def cdf_mp(q, k, nu): + """Straightforward implementation of studentized range CDF""" + q, k, nu = mpf(q), mpf(k), mpf(nu) + + def inner(s, z): + return phi(z) * (Phi(z + q * s) - Phi(z)) ** (k - 1) + + def outer(s, z): + return s ** (nu - 1) * phi(sqrt(nu) * s) * inner(s, z) + + def whole(s, z): + return (sqrt(2 * pi) * k * nu ** (nu / 2) + / (gamma(nu / 2) * 2 ** (nu / 2 - 1)) * outer(s, z)) + + res = quad(whole, [0, inf], [-inf, inf], + method="gauss-legendre", maxdegree=10) + return res + + +def pdf_mp(q, k, nu): + """Straightforward implementation of studentized range PDF""" + q, k, nu = mpf(q), mpf(k), mpf(nu) + + def inner(s, z): + return phi(z + q * s) * phi(z) * (Phi(z + q * s) - Phi(z)) ** (k - 2) + + def outer(s, z): + return s ** nu * phi(sqrt(nu) * s) * inner(s, z) + + def whole(s, z): + return (sqrt(2 * pi) * k * (k - 1) * nu ** (nu / 2) + / (gamma(nu / 2) * 2 ** (nu / 2 - 1)) * outer(s, z)) + + res = quad(whole, [0, inf], [-inf, inf], + method="gauss-legendre", maxdegree=10) + return res + + +def moment_mp(m, k, nu): + """Implementation of the studentized range moment""" + m, k, nu = mpf(m), mpf(k), mpf(nu) + + def inner(q, s, z): + return phi(z + q * s) * phi(z) * (Phi(z + q * s) - Phi(z)) ** (k - 2) + + def outer(q, s, z): + return s ** nu * phi(sqrt(nu) * s) * inner(q, s, z) + + def pdf(q, s, z): + return (sqrt(2 * pi) * k * (k - 1) * nu ** (nu / 2) + / (gamma(nu / 2) * 2 ** (nu / 2 - 1)) * outer(q, s, z)) + + def whole(q, s, z): + return q ** m * pdf(q, s, z) + + res = quad(whole, [0, inf], [0, inf], [-inf, inf], + method="gauss-legendre", maxdegree=10) + return res + + +def result_exists(set_key, case): + """Searches the results dict for a result in the set that matches a case. + Returns True if such a case exists.""" + if set_key not in res_dict: + raise ValueError(f"{set_key} not present in data structure!") + + case_dict = to_dict(case) + existing_res = list(filter( + lambda res: res["src_case"] == case_dict, # dict comparison + res_dict[set_key])) + + return len(existing_res) > 0 + + +def run(case, run_lambda, set_key, index=0, total_cases=0): + """Runs the single passed case, returning an mp dictionary and index""" + t_start = time.perf_counter() + + res = run_lambda(case) + + print(f"Finished {index + 1}/{total_cases} in batch. " + f"(Took {time.perf_counter() - t_start}s)") + + return index, set_key, mp_res_to_dict(MPResult(case, res)) + + +def write_result(res): + """A callback for completed jobs. Inserts and writes a calculated result + to file.""" + index, set_key, result_dict = res + res_dict[set_key].insert(index, result_dict) + write_data() + + +def run_cases(cases, run_lambda, set_key): + """Runs an array of cases and writes to file""" + # Generate jobs to run from cases that do not have a result in + # the previously loaded JSON. + job_arg = [(case, run_lambda, set_key, index, len(cases)) + for index, case in enumerate(cases) + if not result_exists(set_key, case)] + + print(f"{len(cases) - len(job_arg)}/{len(cases)} cases won't be " + f"calculated because their results already exist.") + + jobs = [] + pool = Pool(num_pools) + + # Run all using multiprocess + for case in job_arg: + jobs.append(pool.apply_async(run, args=case, callback=write_result)) + + pool.close() + pool.join() + + +def run_pdf(case): + return pdf_mp(case.q, case.k, case.v) + + +def run_cdf(case): + return cdf_mp(case.q, case.k, case.v) + + +def run_moment(case): + return moment_mp(case.m, case.k, case.v) + + +def main(): + t_start = time.perf_counter() + + total_cases = 2 * len(cdf_pdf_cases) + len(moment_cases) + print(f"Processing {total_cases} test cases") + + print(f"Running 1st batch ({len(cdf_pdf_cases)} PDF cases). " + f"These take about 30s each.") + run_cases(cdf_pdf_cases, run_pdf, "pdf_data") + + print(f"Running 2nd batch ({len(cdf_pdf_cases)} CDF cases). " + f"These take about 30s each.") + run_cases(cdf_pdf_cases, run_cdf, "cdf_data") + + print(f"Running 3rd batch ({len(moment_cases)} moment cases). " + f"These take about anywhere from a few hours to days each.") + run_cases(moment_cases, run_moment, "moment_data") + + print(f"Test data generated in {time.perf_counter() - t_start}s") + + +if __name__ == "__main__": + main() diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/test_hypotests.py b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_hypotests.py new file mode 100644 index 0000000000000000000000000000000000000000..a5762185f7b8b4363e469c3974b20106fad7cb0b --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_hypotests.py @@ -0,0 +1,2248 @@ +from itertools import product + +import numpy as np +import functools +import pytest +from numpy.testing import assert_, assert_equal, assert_allclose +from pytest import raises as assert_raises + +from scipy import stats, special +from scipy.stats import distributions +from scipy.stats._hypotests import (epps_singleton_2samp, cramervonmises, + _cdf_cvm, cramervonmises_2samp, + _pval_cvm_2samp_exact, barnard_exact, + boschloo_exact) +from scipy.stats._mannwhitneyu import mannwhitneyu, _mwu_state, _MWU +from scipy._lib._testutils import _TestPythranFunc +from scipy._external import array_api_extra as xpx +from scipy._lib._array_api import (make_xp_test_case, xp_default_dtype, is_numpy, + eager_warns, xp_ravel, is_jax) +from scipy._lib._array_api_no_0d import xp_assert_equal, xp_assert_close +from scipy.stats._axis_nan_policy import SmallSampleWarning, too_small_1d_not_omit + +lazy_xp_modules = [stats] +skip_xp_backends = pytest.mark.skip_xp_backends + + +@make_xp_test_case(epps_singleton_2samp) +class TestEppsSingleton: + def test_epps_singleton_iv(self, xp): + # partial input validation test relevant to the addition of JIT support + # TODO: complete input validation tests + x = xp.asarray([-0.35, 2.55, 1.73, 0.73, 0.35]) + y = xp.asarray([-1.15, -0.15, 2.48, 3.25, 3.71]) + t = xp.asarray([0.4, -0.4]) + if is_jax(xp): + w, p = epps_singleton_2samp(x, y, t=t) + xp_assert_equal(w, xp.asarray(xp.nan)) + xp_assert_equal(p, xp.asarray(xp.nan)) + else: + message = "t must contain positive elements only." + with pytest.raises(ValueError, match=message): + epps_singleton_2samp(x, y, t=t) + + @pytest.mark.parametrize('dtype', [None, 'float32', 'float64']) + def test_statistic_1(self, dtype, xp): + # first example in Goerg & Kaiser, also in original paper of + # Epps & Singleton. Note: values do not match exactly, the + # value of the interquartile range varies depending on how + # quantiles are computed + dtype = xp_default_dtype(xp) if dtype is None else getattr(xp, dtype) + x = xp.asarray([-0.35, 2.55, 1.73, 0.73, 0.35, + 2.69, 0.46, -0.94, -0.37, 12.07], dtype=dtype) + y = xp.asarray([-1.15, -0.15, 2.48, 3.25, 3.71, + 4.29, 5.00, 7.74, 8.38, 8.60], dtype=dtype) + w, p = epps_singleton_2samp(x, y) + xp_assert_close(w, xp.asarray(15.14, dtype=dtype), atol=0.03) + xp_assert_close(p, xp.asarray(0.00442, dtype=dtype), atol=0.0001) + + def test_statistic_2(self, xp): + # second example in Goerg & Kaiser, again not a perfect match + x = xp.asarray([0, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, + 5, 5, 5, 5, 6, 10, 10, 10, 10]) + y = xp.asarray([10, 4, 0, 5, 10, 10, 0, 5, 6, 7, + 10, 3, 1, 7, 0, 8, 1, 5, 8, 10]) + w, p = epps_singleton_2samp(x, y) + xp_assert_close(w, xp.asarray(8.900), atol=1e-3) + xp_assert_close(p, xp.asarray(0.06364), atol=5e-5) + + def test_epps_singleton_array_like(self): # only relevant for NumPy + x, y = np.arange(30), np.arange(28) + + w1, p1 = epps_singleton_2samp(list(x), list(y)) + w2, p2 = epps_singleton_2samp(tuple(x), tuple(y)) + w3, p3 = epps_singleton_2samp(x, y) + + assert_(w1 == w2 == w3) + assert_(p1 == p2 == p3) + + def test_epps_singleton_size(self, xp): + # warns if sample contains fewer than 5 elements + x, y = xp.asarray([1, 2, 3, 4]), xp.arange(10) + with eager_warns(SmallSampleWarning, match=too_small_1d_not_omit, xp=xp): + res = epps_singleton_2samp(x, y) + xp_assert_equal(res.statistic, xp.asarray(xp.nan)) + xp_assert_equal(res.pvalue, xp.asarray(xp.nan)) + + def test_epps_singleton_nonfinite(self, xp): + rng = np.random.default_rng(83249872384543) + x = rng.random(size=(10, 11)) + y = rng.random(size=(10, 12)) + i = np.asarray([1, 4, 9]) # arbitrary rows + + w_ref, p_ref = epps_singleton_2samp(x, y, axis=-1) + w_ref[i] = np.nan + p_ref[i] = np.nan + + x[i[0], 0] = np.nan + x[i[1], 1] = np.inf + y[i[2], 2] = -np.inf + + x, y = xp.asarray(x), xp.asarray(y) + w_res, p_res = epps_singleton_2samp(x, y, axis=-1) + xp_assert_close(w_res, xp.asarray(w_ref)) + xp_assert_close(p_res, xp.asarray(p_ref)) + + +@make_xp_test_case(stats.cramervonmises) +class TestCvm: + # the expected values of the cdfs are taken from Table 1 in + # Csorgo / Faraway: The Exact and Asymptotic Distribution of + # Cramér-von Mises Statistics, 1996. + @pytest.mark.parametrize('n, x, ref', [ + (4, [0.02983, 0.04111, 0.12331, 0.94251], [0.01, 0.05, 0.5, 0.999]), + (10, [0.02657, 0.03830, 0.12068, 0.56643], [0.01, 0.05, 0.5, 0.975]), + (1000, [0.02481, 0.03658, 0.11889, 1.16120], [0.01, 0.05, 0.5, 0.999]), + (None, [0.02480, 0.03656, 0.11888, 1.16204], [0.01, 0.05, 0.5, 0.999]), + ]) + def test_cdf_ref(self, n, x, ref, xp): + xp_assert_close(_cdf_cvm(xp.asarray(x), n), xp.asarray(ref), atol=1e-4) + + def test_cdf_support(self, xp): + # cdf has support on [1/(12*n), n/3] + xp_assert_equal(_cdf_cvm(xp.asarray([1/(12*533), 533/3]), 533), + xp.asarray([0., 1.])) + xp_assert_equal(_cdf_cvm(xp.asarray([1/(12*(27 + 1)), (27 + 1)/3]), 27), + xp.asarray([0., 1.])) + + def test_cdf_large_n(self, xp): + # test that asymptotic cdf and cdf for large samples are close + x = xp.asarray([0.02480, 0.03656, 0.11888, 1.16204, 100]) + xp_assert_close(_cdf_cvm(x, 10000), _cdf_cvm(x), atol=1e-4) + + def test_large_x(self, xp): + # for large values of x and n, the series used to compute the cdf + # converges slowly. + # this leads to bug in R package goftest and MAPLE code that is + # the basis of the implementation in scipy + # note: cdf = 1 for x >= 1000/3 and n = 1000 + x = xp.asarray(333.3, dtype=xp.float64) + assert (0.99999 < _cdf_cvm(x, 1000) < 1.0) + assert (0.99999 < _cdf_cvm(x) < 1.0) + + def test_low_p(self, xp): + # _cdf_cvm can return values larger than 1. In that case, we just + # return a p-value of zero. + n = 12 + res = cramervonmises(xp.ones(n)*0.8, special.ndtr) + assert _cdf_cvm(res.statistic, n, xp=xp) > 1.0 + xp_assert_equal(res.pvalue, xp.asarray(0.)) + + @pytest.mark.parametrize('x', [(), [1.5]]) + def test_invalid_input(self, x, xp): + with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit): + res = cramervonmises(xp.asarray(x), special.ndtr) + xp_assert_equal(res.statistic, xp.asarray(xp.nan)) + xp_assert_equal(res.pvalue, xp.asarray(xp.nan)) + + @pytest.mark.parametrize('dtype', [None, 'float32', 'float64']) + def test_values_R(self, dtype, xp): + dtype = xp_default_dtype(xp) if dtype is None else getattr(xp, dtype) + # compared against R package goftest, version 1.1.1 + # library(goftest) + # options(digits=16) + # cvm.test(c(-1.7, 2, 0, 1.3, 4, 0.1, 0.6), "pnorm") + res = cramervonmises(xp.asarray([-1.7, 2, 0, 1.3, 4, 0.1, 0.6], dtype=dtype), + special.ndtr) + xp_assert_close(res.statistic, xp.asarray(0.28815604599198, dtype=dtype)) + xp_assert_close(res.pvalue, xp.asarray( 0.1453465252039, dtype=dtype)) + + # cvm.test(c(-1.7, 2, 0, 1.3, 4, 0.1, 0.6), "pnorm", mean = 3, sd = 1.5) + res = cramervonmises(xp.asarray([-1.7, 2, 0, 1.3, 4, 0.1, 0.6], dtype=dtype), + lambda x: special.ndtr((x - 3)/1.5)) + xp_assert_close(res.statistic, xp.asarray(0.94266847977072, dtype=dtype)) + xp_assert_close(res.pvalue, xp.asarray(0.002026416728467, dtype=dtype)) + + # cvm.test(c(1, 2, 5, 1.4, 0.14, 11, 13, 0.9, 7.5), "pexp") + res = cramervonmises( + xp.asarray([1, 2, 5, 1.4, 0.14, 11, 13, 0.9, 7.5], dtype=dtype), + lambda x: -xp.expm1(-x)) + xp_assert_close(res.statistic, xp.asarray(0.84218540922393, dtype=dtype)) + xp_assert_close(res.pvalue, xp.asarray(0.004433406063014, dtype=dtype)) + + def test_str_cdf(self, xp): + if not is_numpy(xp): + message = "`cdf` must be a callable if `rvs` is a non-NumPy array." + with pytest.raises(ValueError, match=message): + cramervonmises(xp.asarray([1, 2, 3]), "beta") + return + + x, args = np.arange(5), (1.4, 0.7) + ref = cramervonmises(x, distributions.expon.cdf) + res = cramervonmises(x, "expon") + assert_equal((res.statistic, res.pvalue), (ref.statistic, ref.pvalue)) + + ref = cramervonmises(x, distributions.beta.cdf, args) + res = cramervonmises(x, "beta", args) + assert_equal((res.statistic, res.pvalue), (ref.statistic, ref.pvalue)) + + +@make_xp_test_case(stats.mannwhitneyu) +class TestMannWhitneyU: + + # All magic numbers are from R wilcox.test unless otherwise specified + # https://rdrr.io/r/stats/wilcox.test.html + + # --- Test Input Validation --- + + def get_zstatistic_from_pvalue(self, p, U, n1, n2, alternative): + if alternative == 'less': + z = special.ndtri(p) + elif alternative == 'greater': + z = -special.ndtri(p) + else: + z = special.ndtri(p/2) if U < n1*n2/2 else -special.ndtri(p/2) + return z + + def test_empty(self, xp): + x = xp.asarray([1, 2]) # generic, valid inputs + y = xp.asarray([3, 4]) + empty = xp.asarray([], dtype=x.dtype) + nan = xp.asarray(xp.nan) + + with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit): + res = mannwhitneyu(x, empty) + xp_assert_close(res.statistic, nan) + xp_assert_close(res.pvalue, nan) + + with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit): + res = mannwhitneyu(empty, y) + xp_assert_close(res.statistic, nan) + xp_assert_close(res.pvalue, nan) + + with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit): + res = mannwhitneyu(empty, empty) + xp_assert_close(res.statistic, nan) + xp_assert_close(res.pvalue, nan) + + def test_input_validation(self, xp): + x = xp.asarray([1, 2]) # generic, valid inputs + y = xp.asarray([3, 4]) + with assert_raises(ValueError, match="`use_continuity` must be one"): + mannwhitneyu(x, y, use_continuity='ekki') + with assert_raises(ValueError, match="`alternative` must be one of"): + mannwhitneyu(x, y, alternative='ekki') + with assert_raises(ValueError, match="`axis` must be an integer"): + mannwhitneyu(x, y, axis=1.5) + with assert_raises(ValueError, match="`method` must be one of"): + mannwhitneyu(x, y, method='ekki') + + def test_auto(self, xp): + # Test that default method ('auto') chooses intended method + if is_jax(xp): + message = "`method='auto'` is incompatible with JAX arrays." + with pytest.raises(ValueError, match=message): + mannwhitneyu(xp.arange(10), xp.arange(10)) + return + + rng = np.random.default_rng(923823782530925934) + n = 8 # threshold to switch from exact to asymptotic + + # both inputs are smaller than threshold; should use exact + x = xp.asarray(rng.random(n-1)) + y = xp.asarray(rng.random(n-1)) + auto = mannwhitneyu(x, y) + asymptotic = mannwhitneyu(x, y, method='asymptotic') + exact = mannwhitneyu(x, y, method='exact') + assert auto.pvalue == exact.pvalue + assert auto.pvalue != asymptotic.pvalue + + # one input is smaller than threshold; should use exact + x = xp.asarray(rng.random(n-1)) + y = xp.asarray(rng.random(n+1)) + auto = mannwhitneyu(x, y) + asymptotic = mannwhitneyu(x, y, method='asymptotic') + exact = mannwhitneyu(x, y, method='exact') + assert auto.pvalue == exact.pvalue + assert auto.pvalue != asymptotic.pvalue + + # other input is smaller than threshold; should use exact + auto = mannwhitneyu(y, x) + asymptotic = mannwhitneyu(x, y, method='asymptotic') + exact = mannwhitneyu(x, y, method='exact') + assert auto.pvalue == exact.pvalue + assert auto.pvalue != asymptotic.pvalue + + # both inputs are larger than threshold; should use asymptotic + x = xp.asarray(rng.random(n+1)) + y = xp.asarray(rng.random(n+1)) + auto = mannwhitneyu(x, y) + asymptotic = mannwhitneyu(x, y, method='asymptotic') + exact = mannwhitneyu(x, y, method='exact') + assert auto.pvalue != exact.pvalue + assert auto.pvalue == asymptotic.pvalue + + # both inputs are smaller than threshold, but there is a tie + # should use asymptotic + x = xp.asarray(rng.random(n-1)) + y = xp.asarray(rng.random(n-1)) + y = xpx.at(y)[3].set(x[3]) + auto = mannwhitneyu(x, y) + asymptotic = mannwhitneyu(x, y, method='asymptotic') + exact = mannwhitneyu(x, y, method='exact') + assert auto.pvalue != exact.pvalue + assert auto.pvalue == asymptotic.pvalue + + # --- Test Basic Functionality --- + + x = [210.052110, 110.190630, 307.918612] + y = [436.08811482466416, 416.37397329768191, 179.96975939463582, + 197.8118754228619, 34.038757281225756, 138.54220550921517, + 128.7769351470246, 265.92721427951852, 275.6617533155341, + 592.34083395416258, 448.73177590617018, 300.61495185038905, + 187.97508449019588] + + # This test was written for mann_whitney_u in gh-4933. + # Originally, the p-values for alternatives were swapped; + # this has been corrected and the tests have been refactored for + # compactness, but otherwise the tests are unchanged. + # R code for comparison, e.g.: + # options(digits = 16) + # x = c(210.052110, 110.190630, 307.918612) + # y = c(436.08811482466416, 416.37397329768191, 179.96975939463582, + # 197.8118754228619, 34.038757281225756, 138.54220550921517, + # 128.7769351470246, 265.92721427951852, 275.6617533155341, + # 592.34083395416258, 448.73177590617018, 300.61495185038905, + # 187.97508449019588) + # wilcox.test(x, y, alternative="g", exact=TRUE) + cases_basic = [[{"alternative": 'two-sided', "method": "asymptotic"}, + (16., 0.6865041817876)], + [{"alternative": 'less', "method": "asymptotic"}, + (16., 0.3432520908938)], + [{"alternative": 'greater', "method": "asymptotic"}, + (16., 0.7047591913255)], + [{"alternative": 'two-sided', "method": "exact"}, + (16., 0.7035714285714)], + [{"alternative": 'less', "method": "exact"}, + (16., 0.3517857142857)], + [{"alternative": 'greater', "method": "exact"}, + (16., 0.6946428571429)]] + + @pytest.mark.parametrize(("kwds", "expected"), cases_basic) + @pytest.mark.parametrize("dtype", [None, 'float32', 'float64']) + def test_basic(self, kwds, expected, dtype, xp): + dtype = xp_default_dtype(xp) if dtype is None else getattr(xp, dtype) + x, y = xp.asarray(self.x, dtype=dtype), xp.asarray(self.y, dtype=dtype) + res = mannwhitneyu(x, y, **kwds) + xp_assert_close(res.statistic, xp.asarray(expected[0], dtype=dtype)) + xp_assert_close(res.pvalue, xp.asarray(expected[1], dtype=dtype)) + if kwds['method'] == 'asymptotic': + z = self.get_zstatistic_from_pvalue(res.pvalue, res.statistic, + x.shape[0], y.shape[0], + alternative=kwds['alternative']) + xp_assert_close(res.zstatistic, xp.asarray(z, dtype=dtype)) + + cases_continuity = [[{"alternative": 'two-sided', "use_continuity": True}, + (23., 0.6865041817876)], + [{"alternative": 'less', "use_continuity": True}, + (23., 0.7047591913255)], + [{"alternative": 'greater', "use_continuity": True}, + (23., 0.3432520908938)], + [{"alternative": 'two-sided', "use_continuity": False}, + (23., 0.6377328900502)], + [{"alternative": 'less', "use_continuity": False}, + (23., 0.6811335549749)], + [{"alternative": 'greater', "use_continuity": False}, + (23., 0.3188664450251)]] + + @pytest.mark.parametrize(("kwds", "expected"), cases_continuity) + def test_continuity(self, kwds, expected, xp): + # When x and y are interchanged, less and greater p-values should + # swap (compare to above). This wouldn't happen if the continuity + # correction were applied in the wrong direction. Note that less and + # greater p-values do not sum to 1 when continuity correction is on, + # which is what we'd expect. Also check that results match R when + # continuity correction is turned off. + # Note that method='asymptotic' -> exact=FALSE + # and use_continuity=False -> correct=FALSE, e.g.: + # wilcox.test(x, y, alternative="t", exact=FALSE, correct=FALSE) + x, y = xp.asarray(self.x), xp.asarray(self.y) + res = mannwhitneyu(y, x, method='asymptotic', **kwds) + xp_assert_close(res.statistic, xp.asarray(expected[0])) + xp_assert_close(res.pvalue, xp.asarray(expected[1])) + z = self.get_zstatistic_from_pvalue(res.pvalue, res.statistic, + x.shape[0], y.shape[0], + alternative=kwds['alternative']) + xp_assert_close(res.zstatistic, xp.asarray(z)) + + def test_tie_correct(self, xp): + # Test tie correction against R's wilcox.test + # options(digits = 16) + # x = c(1, 2, 3, 4) + # y = c(1, 2, 3, 4, 5) + # wilcox.test(x, y, exact=FALSE) + x = xp.asarray([1., 2., 3., 4.]) + y0 = xp.asarray([1., 2., 3., 4., 5.]) + dy = xp.asarray([0., 1., 0., 1., 0.])*0.01 + dy2 = xp.asarray([0., 0., 1., 0., 0.])*0.01 + y = xp.stack([y0-0.01, y0-dy, y0-dy2, y0, y0+dy2, y0+dy, y0+0.01]) + res = mannwhitneyu(x, y, axis=-1, method="asymptotic") + U_expected = [10, 9, 8.5, 8, 7.5, 7, 6] + p_expected = [1, 0.9017048037317, 0.804080657472, 0.7086240584439, + 0.6197963884941, 0.5368784563079, 0.3912672792826] + xp_assert_equal(res.statistic, xp.asarray(U_expected)) + xp_assert_close(res.pvalue, xp.asarray(p_expected)) + + # --- Test Exact Distribution of U --- + + # These are tabulated values of the CDF of the exact distribution of + # the test statistic from pg 52 of reference [1] (Mann-Whitney Original) + pn3 = {1: [0.25, 0.5, 0.75], 2: [0.1, 0.2, 0.4, 0.6], + 3: [0.05, .1, 0.2, 0.35, 0.5, 0.65]} + pn4 = {1: [0.2, 0.4, 0.6], 2: [0.067, 0.133, 0.267, 0.4, 0.6], + 3: [0.028, 0.057, 0.114, 0.2, .314, 0.429, 0.571], + 4: [0.014, 0.029, 0.057, 0.1, 0.171, 0.243, 0.343, 0.443, 0.557]} + pm5 = {1: [0.167, 0.333, 0.5, 0.667], + 2: [0.047, 0.095, 0.19, 0.286, 0.429, 0.571], + 3: [0.018, 0.036, 0.071, 0.125, 0.196, 0.286, 0.393, 0.5, 0.607], + 4: [0.008, 0.016, 0.032, 0.056, 0.095, 0.143, + 0.206, 0.278, 0.365, 0.452, 0.548], + 5: [0.004, 0.008, 0.016, 0.028, 0.048, 0.075, 0.111, + 0.155, 0.21, 0.274, 0.345, .421, 0.5, 0.579]} + pm6 = {1: [0.143, 0.286, 0.428, 0.571], + 2: [0.036, 0.071, 0.143, 0.214, 0.321, 0.429, 0.571], + 3: [0.012, 0.024, 0.048, 0.083, 0.131, + 0.19, 0.274, 0.357, 0.452, 0.548], + 4: [0.005, 0.01, 0.019, 0.033, 0.057, 0.086, 0.129, + 0.176, 0.238, 0.305, 0.381, 0.457, 0.543], # the last element + # of the previous list, 0.543, has been modified from 0.545; + # I assume it was a typo + 5: [0.002, 0.004, 0.009, 0.015, 0.026, 0.041, 0.063, 0.089, + 0.123, 0.165, 0.214, 0.268, 0.331, 0.396, 0.465, 0.535], + 6: [0.001, 0.002, 0.004, 0.008, 0.013, 0.021, 0.032, 0.047, + 0.066, 0.09, 0.12, 0.155, 0.197, 0.242, 0.294, 0.350, + 0.409, 0.469, 0.531]} + + def test_exact_distribution(self): + # I considered parametrize. I decided against it. + setattr(_mwu_state, 's', _MWU(0, 0)) + + p_tables = {3: self.pn3, 4: self.pn4, 5: self.pm5, 6: self.pm6} + for n, table in p_tables.items(): + for m, p in table.items(): + # check p-value against table + u = np.arange(0, len(p)) + _mwu_state.s.set_shapes(m, n) + assert_allclose(_mwu_state.s.cdf(k=u), p, atol=1e-3) + + # check identity CDF + SF - PMF = 1 + # ( In this implementation, SF(U) includes PMF(U) ) + u2 = np.arange(0, m*n+1) + assert_allclose(_mwu_state.s.cdf(k=u2) + + _mwu_state.s.sf(k=u2) + - _mwu_state.s.pmf(k=u2), 1) + + # check symmetry about mean of U, i.e. pmf(U) = pmf(m*n-U) + pmf = _mwu_state.s.pmf(k=u2) + assert_allclose(pmf, pmf[::-1]) + + # check symmetry w.r.t. interchange of m, n + _mwu_state.s.set_shapes(n, m) + pmf2 = _mwu_state.s.pmf(k=u2) + assert_allclose(pmf, pmf2) + + def test_asymptotic_behavior(self, xp): + rng = np.random.default_rng(12543) + + # for small samples, the asymptotic test is not very accurate + x = xp.asarray(rng.random(5)) + y = xp.asarray(rng.random(5)) + res1 = mannwhitneyu(x, y, method="exact") + res2 = mannwhitneyu(x, y, method="asymptotic") + assert res1.statistic == res2.statistic + assert xp.abs(res1.pvalue - res2.pvalue) > 1e-2 + + # for large samples, they agree reasonably well + x = xp.asarray(rng.random(40)) + y = xp.asarray(rng.random(40)) + res1 = mannwhitneyu(x, y, method="exact") + res2 = mannwhitneyu(x, y, method="asymptotic") + assert res1.statistic == res2.statistic + assert xp.abs(res1.pvalue - res2.pvalue) < 1e-3 + + # --- Test Corner Cases --- + + def test_exact_U_equals_mean(self, xp): + # Test U == m*n/2 with exact method + # Without special treatment, two-sided p-value > 1 because both + # one-sided p-values are > 0.5 + x, y = xp.asarray([1., 2., 3.]), xp.asarray([1.5, 2.5]) + res_l = mannwhitneyu(x, y, alternative="less", method="exact") + res_g = mannwhitneyu(x, y, alternative="greater", method="exact") + xp_assert_equal(res_l.pvalue, res_g.pvalue) + assert res_l.pvalue > 0.5 + + res = mannwhitneyu(x, y, alternative="two-sided", method="exact") + xp_assert_equal(res.statistic, xp.asarray(3.)) + xp_assert_equal(res.pvalue, xp.asarray(1.)) + # U == m*n/2 for asymptotic case tested in test_gh_2118 + # The reason it's tricky for the asymptotic test has to do with + # continuity correction. + + cases_scalar = [[{"alternative": 'two-sided', "method": "asymptotic"}, + (0., 1.)], + [{"alternative": 'less', "method": "asymptotic"}, + (0., 0.5)], + [{"alternative": 'greater', "method": "asymptotic"}, + (0., 0.977249868052)], + [{"alternative": 'two-sided', "method": "exact"}, (0., 1)], + [{"alternative": 'less', "method": "exact"}, (0., 0.5)], + [{"alternative": 'greater', "method": "exact"}, (0., 1)]] + + @pytest.mark.parametrize(("kwds", "result"), cases_scalar) + def test_scalar_data(self, kwds, result): # not important to preserve w/ array API + # just making sure scalars work + assert_allclose(mannwhitneyu(1, 2, **kwds), result) + + def test_equal_scalar_data(self): # not important to preserve w/ array API + # when two scalars are equal, there is an -0.5/0 in the asymptotic + # approximation. R gives pvalue=1.0 for alternatives 'less' and + # 'greater' but NA for 'two-sided'. + assert_equal(mannwhitneyu(1, 1, method="exact"), (0.5, 1)) + assert_equal(mannwhitneyu(1, 1, method="asymptotic", alternative='less'), + (0.5, 1)) + assert_equal(mannwhitneyu(1, 1, method="asymptotic", alternative='greater'), + (0.5, 1)) + assert_equal(mannwhitneyu(1, 1, method="asymptotic"), + (0.5, np.nan)) + + # without continuity correction, this definitely becomes 0/0, which really + # is undefined + assert_equal(mannwhitneyu(1, 1, method="asymptotic", alternative='less', + use_continuity=False), (0.5, np.nan)) + assert_equal(mannwhitneyu(1, 1, method="asymptotic", alternative='greater', + use_continuity=False), (0.5, np.nan)) + assert_equal(mannwhitneyu(1, 1, method="asymptotic", + use_continuity=False), (0.5, np.nan)) + + # --- Test Enhancements / Bug Reports --- + + @pytest.mark.parametrize("method", ["asymptotic", "exact"]) + def test_gh_12837_11113(self, method, xp): + # Test that behavior for broadcastable nd arrays is appropriate: + # output shape is correct and all values are equal to when the test + # is performed on one pair of samples at a time. + # Tests that gh-12837 and gh-11113 (requests for n-d input) + # are resolved + rng = np.random.default_rng(6083743794) + + # arrays are broadcastable except for axis = -3 + axis = -3 + m, n = 7, 10 # sample sizes + x = rng.random((m, 3, 8)) + y = rng.random((6, n, 1, 8)) + 0.1 + res = mannwhitneyu(xp.asarray(x), xp.asarray(y), method=method, axis=axis) + + shape = (6, 3, 8) # appropriate shape of outputs, given inputs + assert res.pvalue.shape == shape + assert res.statistic.shape == shape + + # move axis of test to end for simplicity + x, y = np.moveaxis(x, axis, -1), np.moveaxis(y, axis, -1) + + x = x[None, ...] # give x a zeroth dimension + assert x.ndim == y.ndim + + x = np.broadcast_to(x, shape + (m,)) + y = np.broadcast_to(y, shape + (n,)) + assert x.shape[:-1] == shape + assert y.shape[:-1] == shape + + # loop over pairs of samples + statistics = np.zeros(shape) + pvalues = np.zeros(shape) + for indices in product(*[range(i) for i in shape]): + xi = x[indices] + yi = y[indices] + temp = mannwhitneyu(xi, yi, method=method) + statistics[indices] = temp.statistic + pvalues[indices] = temp.pvalue + + xp_assert_close(res.pvalue, xp.asarray(pvalues), atol=1e-16) + xp_assert_close(res.statistic, xp.asarray(statistics), atol=1e-16) + + def test_gh_11355(self, xp): + # Test for correct behavior with NaN/Inf in input + x = [1, 2, 3, 4] + y = [3, 6, 7, 8, 9, 3, 2, 1, 4, 4, 5] + res1 = mannwhitneyu(xp.asarray(x), xp.asarray(y), method='asymptotic') + + # Inf is not a problem. This is a rank test, and it's the largest value + x[0] = 1. # ensure floating point + y[4] = np.inf + res2 = mannwhitneyu(xp.asarray(x), xp.asarray(y), method='asymptotic') + + xp_assert_equal(res1.statistic, res2.statistic) + xp_assert_equal(res1.pvalue, res2.pvalue) + + @skip_xp_backends("jax.numpy", reason="needs jax-ml/jax#34490") + def test_gh11355_nan(self, xp): + # NaNs should propagate by default. + x = [1., 2., 3., 4.] + y = [3, 6, 7, np.nan, 9, 3, 2, 1, 4, 4, 5] + res3 = mannwhitneyu(xp.asarray(x), xp.asarray(y)) + xp_assert_equal(res3.statistic, xp.asarray(xp.nan)) + xp_assert_equal(res3.pvalue, xp.asarray(xp.nan)) + + cases_11355 = [([1., 2, 3, 4], + [3, 6, 7, 8, np.inf, 3, 2, 1, 4, 4, 5], + 10., 0.1297704873477), + ([1., 2, 3, 4], + [3, 6, 7, 8, np.inf, np.inf, 2, 1, 4, 4, 5], + 8.5, 0.08735617507695), + ([1, 2, np.inf, 4], + [3, 6, 7, 8, np.inf, 3, 2, 1, 4, 4, 5], + 17.5, 0.5988856695752), + ([1, 2, np.inf, 4], + [3, 6, 7, 8, np.inf, np.inf, 2, 1, 4, 4, 5], + 16., 0.4687165824462), + ([1, np.inf, np.inf, 4], + [3, 6, 7, 8, np.inf, np.inf, 2, 1, 4, 4, 5], + 24.5, 0.7912517950119)] + + @pytest.mark.parametrize(("x", "y", "statistic", "pvalue"), cases_11355) + def test_gh_11355b(self, x, y, statistic, pvalue, xp): + # Test for correct behavior with NaN/Inf in input + res = mannwhitneyu(xp.asarray(x), xp.asarray(y), method='asymptotic') + xp_assert_close(res.statistic, xp.asarray(statistic), atol=1e-12) + xp_assert_close(res.pvalue, xp.asarray(pvalue), atol=1e-12) + + cases_9184 = [[True, "less", "asymptotic", 0.900775348204], + [True, "greater", "asymptotic", 0.1223118025635], + [True, "two-sided", "asymptotic", 0.244623605127], + [False, "less", "asymptotic", 0.8896643190401], + [False, "greater", "asymptotic", 0.1103356809599], + [False, "two-sided", "asymptotic", 0.2206713619198], + [True, "less", "exact", 0.8967698967699], + [True, "greater", "exact", 0.1272061272061], + [True, "two-sided", "exact", 0.2544122544123]] + + @pytest.mark.parametrize(("use_continuity", "alternative", + "method", "pvalue_exp"), cases_9184) + def test_gh_9184(self, use_continuity, alternative, method, pvalue_exp, xp): + # gh-9184 might be considered a doc-only bug. Please see the + # documentation to confirm that mannwhitneyu correctly notes + # that the output statistic is that of the first sample (x). In any + # case, check the case provided there against output from R. + # R code: + # options(digits=16) + # x <- c(0.80, 0.83, 1.89, 1.04, 1.45, 1.38, 1.91, 1.64, 0.73, 1.46) + # y <- c(1.15, 0.88, 0.90, 0.74, 1.21) + # wilcox.test(x, y, alternative = "less", exact = FALSE) + # wilcox.test(x, y, alternative = "greater", exact = FALSE) + # wilcox.test(x, y, alternative = "two.sided", exact = FALSE) + # wilcox.test(x, y, alternative = "less", exact = FALSE, + # correct=FALSE) + # wilcox.test(x, y, alternative = "greater", exact = FALSE, + # correct=FALSE) + # wilcox.test(x, y, alternative = "two.sided", exact = FALSE, + # correct=FALSE) + # wilcox.test(x, y, alternative = "less", exact = TRUE) + # wilcox.test(x, y, alternative = "greater", exact = TRUE) + # wilcox.test(x, y, alternative = "two.sided", exact = TRUE) + statistic_exp = 35. + x = xp.asarray([0.80, 0.83, 1.89, 1.04, 1.45, 1.38, 1.91, 1.64, 0.73, 1.46]) + y = xp.asarray([1.15, 0.88, 0.90, 0.74, 1.21]) + res = mannwhitneyu(x, y, use_continuity=use_continuity, + alternative=alternative, method=method) + xp_assert_equal(res.statistic, xp.asarray(statistic_exp)) + xp_assert_close(res.pvalue, xp.asarray(pvalue_exp)) + + @skip_xp_backends("jax.numpy", reason="needs jax-ml/jax#34490") + def test_gh_4067(self, xp): + # Test for correct behavior with all NaN input - default is propagate + nan = xp.asarray(xp.nan) + a = xp.stack([nan, nan, nan, nan, nan]) + b = xp.stack([nan, nan, nan, nan, nan]) + res = mannwhitneyu(a, b) + xp_assert_equal(res.statistic, xp.asarray(nan)) + xp_assert_equal(res.pvalue, nan) + + # All cases checked against R wilcox.test, e.g. + # options(digits=16) + # x = c(1, 2, 3) + # y = c(1.5, 2.5) + # wilcox.test(x, y, exact=FALSE, alternative='less') + + cases_2118 = [[[1., 2., 3.], [1.5, 2.5], "greater", (3., 0.6135850036578)], + [[1., 2., 3.], [1.5, 2.5], "less", (3., 0.6135850036578)], + [[1., 2., 3.], [1.5, 2.5], "two-sided", (3., 1.0)], + [[1, 2, 3], [2], "greater", (1.5, 0.681324055883)], + [[1, 2, 3], [2], "less", (1.5, 0.681324055883)], + [[1, 2, 3], [2], "two-sided", (1.5, 1.)], + [[1, 2], [1, 2], "greater", (2., 0.667497228949)], + [[1, 2], [1, 2], "less", (2., 0.667497228949)], + [[1, 2], [1, 2], "two-sided", (2., 1.)]] + + @pytest.mark.parametrize(["x", "y", "alternative", "expected"], cases_2118) + def test_gh_2118(self, x, y, alternative, expected, xp): + # test cases in which U == m*n/2 when method is asymptotic + # applying continuity correction could result in p-value > 1 + res = mannwhitneyu(xp.asarray(x), xp.asarray(y), use_continuity=True, + alternative=alternative, method="asymptotic") + rtol = 1e-6 if xp_default_dtype(xp) == xp.float32 else 1e-12 + xp_assert_close(res.statistic, xp.asarray(expected[0]), rtol=rtol) + xp_assert_close(res.pvalue, xp.asarray(expected[1]), rtol=rtol) + + def test_gh19692_smaller_table(self, xp): + # In gh-19692, we noted that the shape of the cache used in calculating + # p-values was dependent on the order of the inputs because the sample + # sizes n1 and n2 changed. This was indicative of unnecessary cache + # growth and redundant calculation. Check that this is resolved. + rng = np.random.default_rng(7600451795963068007) + m, n = 5, 11 + x = xp.asarray(rng.random(size=m).tolist()) + y = xp.asarray(rng.random(size=n).tolist()) + + setattr(_mwu_state, 's', _MWU(0, 0)) + _mwu_state.s.reset() # reset cache + + res = stats.mannwhitneyu(x, y, method='exact') + shape = _mwu_state.s.configurations.shape + assert shape[-1] == min(res.statistic, m*n - res.statistic) + 1 + stats.mannwhitneyu(y, x, method='exact') + assert shape == _mwu_state.s.configurations.shape # same with reversed sizes + + # Also, we weren't exploiting the symmetry of the null distribution + # to its full potential. Ensure that the null distribution is not + # evaluated explicitly for `k > m*n/2`. + _mwu_state.s.reset() # reset cache + stats.mannwhitneyu(x, 0*y, method='exact', alternative='greater') + shape = _mwu_state.s.configurations.shape + assert shape[-1] == 1 # k is smallest possible + stats.mannwhitneyu(0*x, y, method='exact', alternative='greater') + assert shape == _mwu_state.s.configurations.shape + + @pytest.mark.parametrize('alternative', ['less', 'greater', 'two-sided']) + def test_permutation_method(self, alternative, xp): + rng = np.random.default_rng(7600451795963068007) + x = xp.asarray(rng.random(size=(2, 5))) + y = xp.asarray(rng.random(size=(2, 6))) + res = stats.mannwhitneyu(x, y, method=stats.PermutationMethod(), + alternative=alternative, axis=1) + res2 = stats.mannwhitneyu(x, y, method='exact', + alternative=alternative, axis=1) + xp_assert_close(res.statistic, res2.statistic, rtol=1e-15) + xp_assert_close(res.pvalue, res2.pvalue, rtol=1e-15) + + # Old tests moved from test_stats. Source of magic numbers unknown. + X = [19.8958398126694, 19.5452691647182, 19.0577309166425, 21.716543054589, + 20.3269502208702, 20.0009273294025, 19.3440043632957, 20.4216806548105, + 19.0649894736528, 18.7808043120398, 19.3680942943298, 19.4848044069953, + 20.7514611265663, 19.0894948874598, 19.4975522356628, 18.9971170734274, + 20.3239606288208, 20.6921298083835, 19.0724259532507, 18.9825187935021, + 19.5144462609601, 19.8256857844223, 20.5174677102032, 21.1122407995892, + 17.9490854922535, 18.2847521114727, 20.1072217648826, 18.6439891962179, + 20.4970638083542, 19.5567594734914] + + Y = [19.2790668029091, 16.993808441865, 18.5416338448258, 17.2634018833575, + 19.1577183624616, 18.5119655377495, 18.6068455037221, 18.8358343362655, + 19.0366413269742, 18.1135025515417, 19.2201873866958, 17.8344909022841, + 18.2894380745856, 18.6661374133922, 19.9688601693252, 16.0672254617636, + 19.00596360572, 19.201561539032, 19.0487501090183, 19.0847908674356] + + rtol = 1e-14 + + def test_mannwhitneyu_one_sided(self, xp): + X, Y = xp.asarray(self.X), xp.asarray(self.Y) + kwargs = dict(method='asymptotic') + u1, p1 = stats.mannwhitneyu(X, Y, alternative='less', **kwargs) + u2, p2 = stats.mannwhitneyu(Y, X, alternative='greater', **kwargs) + u3, p3 = stats.mannwhitneyu(X, Y, alternative='greater', **kwargs) + u4, p4 = stats.mannwhitneyu(Y, X, alternative='less', **kwargs) + + xp_assert_equal(p1, p2) + xp_assert_equal(p3, p4) + assert p1 != p3 + xp_assert_equal(u1, xp.asarray(498.)) + xp_assert_equal(u2, xp.asarray(102.)) + xp_assert_equal(u3, xp.asarray(498.)) + xp_assert_equal(u4, xp.asarray(102.)) + assert_allclose(p1, xp.asarray(0.999957683256589), rtol=self.rtol) + rtol = self.rtol if X.dtype == xp.float64 else 5e-4 + assert_allclose(p3, xp.asarray(4.5941632666275e-05), rtol=rtol, atol=1e-16) + + def test_mannwhitneyu_two_sided(self, xp): + X, Y = xp.asarray(self.X), xp.asarray(self.Y) + kwargs = dict(alternative='two-sided', method='asymptotic') + u1, p1 = stats.mannwhitneyu(X, Y, **kwargs) + u2, p2 = stats.mannwhitneyu(Y, X, **kwargs) + + xp_assert_equal(p1, p2) + xp_assert_equal(u1, xp.asarray(498.)) + xp_assert_equal(u2, xp.asarray(102.)) + rtol = self.rtol if X.dtype == xp.float64 else 5e-4 + xp_assert_close(p1, xp.asarray(9.188326533255e-05), rtol=rtol, atol=1e-16) + + def test_mannwhitneyu_no_correct_one_sided(self, xp): + X, Y = xp.asarray(self.X), xp.asarray(self.Y) + kwargs = dict(use_continuity=False, method='asymptotic') + u1, p1 = stats.mannwhitneyu(X, Y, alternative='less', **kwargs) + u2, p2 = stats.mannwhitneyu(Y, X, alternative='greater', **kwargs) + u3, p3 = stats.mannwhitneyu(X, Y, alternative='greater', **kwargs) + u4, p4 = stats.mannwhitneyu(Y, X, alternative='less', **kwargs) + + xp_assert_equal(p1, p2) + xp_assert_equal(p3, p4) + assert p1 != p3 + xp_assert_equal(u1, xp.asarray(498.)) + xp_assert_equal(u2, xp.asarray(102.)) + xp_assert_equal(u3, xp.asarray(498.)) + xp_assert_equal(u4, xp.asarray(102.)) + rtol = self.rtol if X.dtype == xp.float64 else 5e-4 + xp_assert_close(p1, xp.asarray(0.999955905990004), rtol=rtol, atol=1e-16) + xp_assert_close(p3, xp.asarray(4.40940099958089e-05), rtol=rtol, atol=1e-16) + + def test_mannwhitneyu_no_correct_two_sided(self, xp): + X, Y = xp.asarray(self.X), xp.asarray(self.Y) + kwargs = dict(use_continuity=False, method='asymptotic') + u1, p1 = stats.mannwhitneyu(X, Y, **kwargs) + u2, p2 = stats.mannwhitneyu(Y, X, **kwargs) + + xp_assert_equal(p1, p2) + xp_assert_equal(u1, xp.asarray(498.)) + xp_assert_equal(u2, xp.asarray(102.)) + rtol = self.rtol if X.dtype == xp.float64 else 5e-4 + xp_assert_close(p1, xp.asarray(8.81880199916178e-05), rtol=rtol, atol=1e-16) + + def test_mannwhitneyu_ones(self, xp): + # test for gh-1428 + x = xp.asarray([1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., + 1., 1., 1., 1., 1., 1., 1., 2., 1., 1., 1., 1., 1., 1., + 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., + 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., + 1., 1., 1., 1., 1., 1., 1., 2., 1., 1., 1., 1., 1., 1., + 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., + 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 2., + 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., + 1., 1., 2., 1., 1., 1., 1., 2., 1., 1., 2., 1., 1., 2., + 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., + 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 2., 1., + 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., + 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., + 1., 1., 1., 1., 1., 1., 1., 2., 1., 1., 1., 1., 1., 1., + 1., 1., 1., 2., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., + 1., 1., 1., 1., 1., 1., 1., 1., 3., 1., 1., 1., 1., 1., + 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., + 1., 1., 1., 1., 1., 1.]) + + y = xp.asarray([1., 1., 1., 1., 1., 1., 1., 2., 1., 2., 1., 1., 1., 1., + 2., 1., 1., 1., 2., 1., 1., 1., 1., 1., 2., 1., 1., 3., + 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 2., 1., 2., 1., + 1., 1., 1., 1., 1., 2., 1., 1., 1., 1., 1., 1., 1., 1., + 1., 1., 1., 1., 1., 1., 1., 2., 1., 1., 1., 1., 1., 2., + 2., 1., 1., 2., 1., 1., 2., 1., 2., 1., 1., 1., 1., 2., + 2., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., + 1., 2., 1., 1., 1., 1., 1., 2., 2., 2., 1., 1., 1., 1., + 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., + 2., 1., 1., 2., 1., 1., 1., 1., 2., 1., 1., 1., 1., 1., + 1., 1., 1., 1., 1., 1., 1., 2., 1., 1., 1., 2., 1., 1., + 1., 1., 1., 1.]) + + # p-value from R, e.g. wilcox.test(x, y, alternative="g") + kwargs = dict(method='asymptotic') + res = stats.mannwhitneyu(x, y, alternative='less', **kwargs) + xp_assert_close(res.statistic, xp.asarray(16980.5)) + xp_assert_close(res.pvalue, xp.asarray(2.8214327656317373e-5)) + res = stats.mannwhitneyu(x, y, alternative='greater', **kwargs) + xp_assert_close(res.statistic, xp.asarray(16980.5)) + xp_assert_close(res.pvalue, xp.asarray(0.9999719954296)) + res = stats.mannwhitneyu(x, y, alternative='two-sided', **kwargs) + xp_assert_close(res.statistic, xp.asarray(16980.5)) + xp_assert_close(res.pvalue, xp.asarray(5.642865531266e-5)) + + +class TestSomersD(_TestPythranFunc): + def setup_method(self): + self.dtypes = self.ALL_INTEGER + self.ALL_FLOAT + self.arguments = {0: (np.arange(10), + self.ALL_INTEGER + self.ALL_FLOAT), + 1: (np.arange(10), + self.ALL_INTEGER + self.ALL_FLOAT)} + input_array = [self.arguments[idx][0] for idx in self.arguments] + # In this case, self.partialfunc can simply be stats.somersd, + # since `alternative` is an optional argument. If it is required, + # we can use functools.partial to freeze the value, because + # we only mainly test various array inputs, not str, etc. + self.partialfunc = functools.partial(stats.somersd, + alternative='two-sided') + self.expected = self.partialfunc(*input_array) + + def pythranfunc(self, *args): + res = self.partialfunc(*args) + assert_allclose(res.statistic, self.expected.statistic, atol=1e-15) + assert_allclose(res.pvalue, self.expected.pvalue, atol=1e-15) + + def test_pythranfunc_keywords(self): + # Not specifying the optional keyword args + table = [[27, 25, 14, 7, 0], [7, 14, 18, 35, 12], [1, 3, 2, 7, 17]] + res1 = stats.somersd(table) + # Specifying the optional keyword args with default value + optional_args = self.get_optional_args(stats.somersd) + res2 = stats.somersd(table, **optional_args) + # Check if the results are the same in two cases + assert_allclose(res1.statistic, res2.statistic, atol=1e-15) + assert_allclose(res1.pvalue, res2.pvalue, atol=1e-15) + + def test_like_kendalltau(self): + # All tests correspond with one in test_stats.py `test_kendalltau` + + # case without ties, con-dis equal zero + x = [5, 2, 1, 3, 6, 4, 7, 8] + y = [5, 2, 6, 3, 1, 8, 7, 4] + # Cross-check with result from SAS FREQ: + expected = (0.000000000000000, 1.000000000000000) + res = stats.somersd(x, y) + assert_allclose(res.statistic, expected[0], atol=1e-15) + assert_allclose(res.pvalue, expected[1], atol=1e-15) + + # case without ties, con-dis equal zero + x = [0, 5, 2, 1, 3, 6, 4, 7, 8] + y = [5, 2, 0, 6, 3, 1, 8, 7, 4] + # Cross-check with result from SAS FREQ: + expected = (0.000000000000000, 1.000000000000000) + res = stats.somersd(x, y) + assert_allclose(res.statistic, expected[0], atol=1e-15) + assert_allclose(res.pvalue, expected[1], atol=1e-15) + + # case without ties, con-dis close to zero + x = [5, 2, 1, 3, 6, 4, 7] + y = [5, 2, 6, 3, 1, 7, 4] + # Cross-check with result from SAS FREQ: + expected = (-0.142857142857140, 0.630326953157670) + res = stats.somersd(x, y) + assert_allclose(res.statistic, expected[0], atol=1e-15) + assert_allclose(res.pvalue, expected[1], atol=1e-15) + + # simple case without ties + x = np.arange(10) + y = np.arange(10) + # Cross-check with result from SAS FREQ: + # SAS p value is not provided. + expected = (1.000000000000000, 0) + res = stats.somersd(x, y) + assert_allclose(res.statistic, expected[0], atol=1e-15) + assert_allclose(res.pvalue, expected[1], atol=1e-15) + + # swap a couple values and a couple more + x = np.arange(10) + y = np.array([0, 2, 1, 3, 4, 6, 5, 7, 8, 9]) + # Cross-check with result from SAS FREQ: + expected = (0.911111111111110, 0.000000000000000) + res = stats.somersd(x, y) + assert_allclose(res.statistic, expected[0], atol=1e-15) + assert_allclose(res.pvalue, expected[1], atol=1e-15) + + # same in opposite direction + x = np.arange(10) + y = np.arange(10)[::-1] + # Cross-check with result from SAS FREQ: + # SAS p value is not provided. + expected = (-1.000000000000000, 0) + res = stats.somersd(x, y) + assert_allclose(res.statistic, expected[0], atol=1e-15) + assert_allclose(res.pvalue, expected[1], atol=1e-15) + + # swap a couple values and a couple more + x = np.arange(10) + y = np.array([9, 7, 8, 6, 5, 3, 4, 2, 1, 0]) + # Cross-check with result from SAS FREQ: + expected = (-0.9111111111111111, 0.000000000000000) + res = stats.somersd(x, y) + assert_allclose(res.statistic, expected[0], atol=1e-15) + assert_allclose(res.pvalue, expected[1], atol=1e-15) + + # with some ties + x1 = [12, 2, 1, 12, 2] + x2 = [1, 4, 7, 1, 0] + # Cross-check with result from SAS FREQ: + expected = (-0.500000000000000, 0.304901788178780) + res = stats.somersd(x1, x2) + assert_allclose(res.statistic, expected[0], atol=1e-15) + assert_allclose(res.pvalue, expected[1], atol=1e-15) + + # with only ties in one or both inputs + # SAS will not produce an output for these: + # NOTE: No statistics are computed for x * y because x has fewer + # than 2 nonmissing levels. + # WARNING: No OUTPUT data set is produced for this table because a + # row or column variable has fewer than 2 nonmissing levels and no + # statistics are computed. + + res = stats.somersd([2, 2, 2], [2, 2, 2]) + assert_allclose(res.statistic, np.nan) + assert_allclose(res.pvalue, np.nan) + + res = stats.somersd([2, 0, 2], [2, 2, 2]) + assert_allclose(res.statistic, np.nan) + assert_allclose(res.pvalue, np.nan) + + res = stats.somersd([2, 2, 2], [2, 0, 2]) + assert_allclose(res.statistic, np.nan) + assert_allclose(res.pvalue, np.nan) + + res = stats.somersd([0], [0]) + assert_allclose(res.statistic, np.nan) + assert_allclose(res.pvalue, np.nan) + + # empty arrays provided as input + res = stats.somersd([], []) + assert_allclose(res.statistic, np.nan) + assert_allclose(res.pvalue, np.nan) + + # test unequal length inputs + x = np.arange(10.) + y = np.arange(20.) + assert_raises(ValueError, stats.somersd, x, y) + + def test_asymmetry(self): + # test that somersd is asymmetric w.r.t. input order and that + # convention is as described: first input is row variable & independent + # data is from Wikipedia: + # https://en.wikipedia.org/wiki/Somers%27_D + # but currently that example contradicts itself - it says X is + # independent yet take D_XY + + x = [1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 1, 2, + 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3] + y = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2] + # Cross-check with result from SAS FREQ: + d_cr = 0.272727272727270 + d_rc = 0.342857142857140 + p = 0.092891940883700 # same p-value for either direction + res = stats.somersd(x, y) + assert_allclose(res.statistic, d_cr, atol=1e-15) + assert_allclose(res.pvalue, p, atol=1e-4) + assert_equal(res.table.shape, (3, 2)) + res = stats.somersd(y, x) + assert_allclose(res.statistic, d_rc, atol=1e-15) + assert_allclose(res.pvalue, p, atol=1e-15) + assert_equal(res.table.shape, (2, 3)) + + def test_somers_original(self): + # test against Somers' original paper [1] + + # Table 5A + # Somers' convention was column IV + table = np.array([[8, 2], [6, 5], [3, 4], [1, 3], [2, 3]]) + # Our convention (and that of SAS FREQ) is row IV + table = table.T + dyx = 129/340 + assert_allclose(stats.somersd(table).statistic, dyx) + + # table 7A - d_yx = 1 + table = np.array([[25, 0], [85, 0], [0, 30]]) + dxy, dyx = 3300/5425, 3300/3300 + assert_allclose(stats.somersd(table).statistic, dxy) + assert_allclose(stats.somersd(table.T).statistic, dyx) + + # table 7B - d_yx < 0 + table = np.array([[25, 0], [0, 30], [85, 0]]) + dyx = -1800/3300 + assert_allclose(stats.somersd(table.T).statistic, dyx) + + def test_contingency_table_with_zero_rows_cols(self): + # test that zero rows/cols in contingency table don't affect result + + N = 100 + shape = 4, 6 + size = np.prod(shape) + + rng = np.random.RandomState(0) + s = stats.multinomial.rvs(N, p=np.ones(size)/size, + random_state=rng).reshape(shape) + res = stats.somersd(s) + + s2 = np.insert(s, 2, np.zeros(shape[1]), axis=0) + res2 = stats.somersd(s2) + + s3 = np.insert(s, 2, np.zeros(shape[0]), axis=1) + res3 = stats.somersd(s3) + + s4 = np.insert(s2, 2, np.zeros(shape[0]+1), axis=1) + res4 = stats.somersd(s4) + + # Cross-check with result from SAS FREQ: + assert_allclose(res.statistic, -0.116981132075470, atol=1e-15) + assert_allclose(res.statistic, res2.statistic) + assert_allclose(res.statistic, res3.statistic) + assert_allclose(res.statistic, res4.statistic) + + assert_allclose(res.pvalue, 0.156376448188150, atol=1e-15) + assert_allclose(res.pvalue, res2.pvalue) + assert_allclose(res.pvalue, res3.pvalue) + assert_allclose(res.pvalue, res4.pvalue) + + def test_invalid_contingency_tables(self): + N = 100 + shape = 4, 6 + size = np.prod(shape) + + rng = np.random.default_rng(0) + # start with a valid contingency table + s = stats.multinomial.rvs(N, p=np.ones(size)/size, + random_state=rng).reshape(shape) + + s5 = s - 2 + message = "All elements of the contingency table must be non-negative" + with assert_raises(ValueError, match=message): + stats.somersd(s5) + + s6 = s + 0.01 + message = "All elements of the contingency table must be integer" + with assert_raises(ValueError, match=message): + stats.somersd(s6) + + message = ("At least two elements of the contingency " + "table must be nonzero.") + with assert_raises(ValueError, match=message): + stats.somersd([[]]) + + with assert_raises(ValueError, match=message): + stats.somersd([[1]]) + + s7 = np.zeros((3, 3)) + with assert_raises(ValueError, match=message): + stats.somersd(s7) + + s7[0, 1] = 1 + with assert_raises(ValueError, match=message): + stats.somersd(s7) + + def test_only_ranks_matter(self): + # only ranks of input data should matter + x = [1, 2, 3] + x2 = [-1, 2.1, np.inf] + y = [3, 2, 1] + y2 = [0, -0.5, -np.inf] + res = stats.somersd(x, y) + res2 = stats.somersd(x2, y2) + assert_equal(res.statistic, res2.statistic) + assert_equal(res.pvalue, res2.pvalue) + + def test_contingency_table_return(self): + # check that contingency table is returned + x = np.arange(10) + y = np.arange(10) + res = stats.somersd(x, y) + assert_equal(res.table, np.eye(10)) + + def test_somersd_alternative(self): + # Test alternative parameter, asymptotic method (due to tie) + + # Based on scipy.stats.test_stats.TestCorrSpearman2::test_alternative + x1 = [1, 2, 3, 4, 5] + x2 = [5, 6, 7, 8, 7] + + # strong positive correlation + expected = stats.somersd(x1, x2, alternative="two-sided") + assert expected.statistic > 0 + + # rank correlation > 0 -> large "less" p-value + res = stats.somersd(x1, x2, alternative="less") + assert_equal(res.statistic, expected.statistic) + assert_allclose(res.pvalue, 1 - (expected.pvalue / 2)) + + # rank correlation > 0 -> small "greater" p-value + res = stats.somersd(x1, x2, alternative="greater") + assert_equal(res.statistic, expected.statistic) + assert_allclose(res.pvalue, expected.pvalue / 2) + + # reverse the direction of rank correlation + x2.reverse() + + # strong negative correlation + expected = stats.somersd(x1, x2, alternative="two-sided") + assert expected.statistic < 0 + + # rank correlation < 0 -> large "greater" p-value + res = stats.somersd(x1, x2, alternative="greater") + assert_equal(res.statistic, expected.statistic) + assert_allclose(res.pvalue, 1 - (expected.pvalue / 2)) + + # rank correlation < 0 -> small "less" p-value + res = stats.somersd(x1, x2, alternative="less") + assert_equal(res.statistic, expected.statistic) + assert_allclose(res.pvalue, expected.pvalue / 2) + + with pytest.raises(ValueError, match="`alternative` must be..."): + stats.somersd(x1, x2, alternative="ekki-ekki") + + @pytest.mark.parametrize("positive_correlation", (False, True)) + def test_somersd_perfect_correlation(self, positive_correlation): + # Before the addition of `alternative`, perfect correlation was + # treated as a special case. Now it is treated like any other case, but + # make sure there are no divide by zero warnings or associated errors + + x1 = np.arange(10) + x2 = x1 if positive_correlation else np.flip(x1) + expected_statistic = 1 if positive_correlation else -1 + + # perfect correlation -> small "two-sided" p-value (0) + res = stats.somersd(x1, x2, alternative="two-sided") + assert res.statistic == expected_statistic + assert res.pvalue == 0 + + # rank correlation > 0 -> large "less" p-value (1) + res = stats.somersd(x1, x2, alternative="less") + assert res.statistic == expected_statistic + assert res.pvalue == (1 if positive_correlation else 0) + + # rank correlation > 0 -> small "greater" p-value (0) + res = stats.somersd(x1, x2, alternative="greater") + assert res.statistic == expected_statistic + assert res.pvalue == (0 if positive_correlation else 1) + + def test_somersd_large_inputs_gh18132(self): + # Test that large inputs where potential overflows could occur give + # the expected output. This is tested in the case of binary inputs. + # See gh-18126. + + # generate lists of random classes 1-2 (binary) + classes = [1, 2] + n_samples = 10 ** 6 + rng = np.random.default_rng(6889320191) + x = rng.choice(classes, n_samples) + y = rng.choice(classes, n_samples) + + # get value to compare with: sklearn output + # from sklearn import metrics + # val_auc_sklearn = metrics.roc_auc_score(x, y) + # # convert to the Gini coefficient (Gini = (AUC*2)-1) + # val_sklearn = 2 * val_auc_sklearn - 1 + val_sklearn = 0.000624401938730923 + + # calculate the Somers' D statistic, which should be equal to the + # result of val_sklearn until approximately machine precision + val_scipy = stats.somersd(x, y).statistic + assert_allclose(val_sklearn, val_scipy, atol=1e-15) + + +class TestBarnardExact: + """Some tests to show that barnard_exact() works correctly.""" + + @pytest.mark.parametrize( + "input_sample,expected", + [ + ([[43, 40], [10, 39]], (3.555406779643, 0.000362832367)), + ([[100, 2], [1000, 5]], (-1.776382925679, 0.135126970878)), + ([[2, 7], [8, 2]], (-2.518474945157, 0.019210815430)), + ([[5, 1], [10, 10]], (1.449486150679, 0.156277546306)), + ([[5, 15], [20, 20]], (-1.851640199545, 0.066363501421)), + ([[5, 16], [20, 25]], (-1.609639949352, 0.116984852192)), + ([[10, 5], [10, 1]], (-1.449486150679, 0.177536588915)), + ([[5, 0], [1, 4]], (2.581988897472, 0.013671875000)), + ([[0, 1], [3, 2]], (-1.095445115010, 0.509667991877)), + ([[0, 2], [6, 4]], (-1.549193338483, 0.197019618792)), + ([[2, 7], [8, 2]], (-2.518474945157, 0.019210815430)), + ], + ) + def test_precise(self, input_sample, expected): + """The expected values have been generated by R, using a resolution + for the nuisance parameter of 1e-6 : + ```R + library(Barnard) + options(digits=10) + barnard.test(43, 40, 10, 39, dp=1e-6, pooled=TRUE) + ``` + """ + res = barnard_exact(input_sample) + statistic, pvalue = res.statistic, res.pvalue + assert_allclose([statistic, pvalue], expected) + + @pytest.mark.parametrize( + "input_sample,expected", + [ + ([[43, 40], [10, 39]], (3.920362887717, 0.000289470662)), + ([[100, 2], [1000, 5]], (-1.139432816087, 0.950272080594)), + ([[2, 7], [8, 2]], (-3.079373904042, 0.020172119141)), + ([[5, 1], [10, 10]], (1.622375939458, 0.150599922226)), + ([[5, 15], [20, 20]], (-1.974771239528, 0.063038448651)), + ([[5, 16], [20, 25]], (-1.722122973346, 0.133329494287)), + ([[10, 5], [10, 1]], (-1.765469659009, 0.250566655215)), + ([[5, 0], [1, 4]], (5.477225575052, 0.007812500000)), + ([[0, 1], [3, 2]], (-1.224744871392, 0.509667991877)), + ([[0, 2], [6, 4]], (-1.732050807569, 0.197019618792)), + ([[2, 7], [8, 2]], (-3.079373904042, 0.020172119141)), + ], + ) + def test_pooled_param(self, input_sample, expected): + """The expected values have been generated by R, using a resolution + for the nuisance parameter of 1e-6 : + ```R + library(Barnard) + options(digits=10) + barnard.test(43, 40, 10, 39, dp=1e-6, pooled=FALSE) + ``` + """ + res = barnard_exact(input_sample, pooled=False) + statistic, pvalue = res.statistic, res.pvalue + assert_allclose([statistic, pvalue], expected) + + def test_raises(self): + # test we raise an error for wrong input number of nuisances. + error_msg = ( + "Number of points `n` must be strictly positive, found 0" + ) + with assert_raises(ValueError, match=error_msg): + barnard_exact([[1, 2], [3, 4]], n=0) + + # test we raise an error for wrong shape of input. + error_msg = "The input `table` must be of shape \\(2, 2\\)." + with assert_raises(ValueError, match=error_msg): + barnard_exact(np.arange(6).reshape(2, 3)) + + # Test all values must be positives + error_msg = "All values in `table` must be nonnegative." + with assert_raises(ValueError, match=error_msg): + barnard_exact([[-1, 2], [3, 4]]) + + # Test value error on wrong alternative param + error_msg = ( + "`alternative` should be one of {'two-sided', 'less', 'greater'}," + " found .*" + ) + with assert_raises(ValueError, match=error_msg): + barnard_exact([[1, 2], [3, 4]], "not-correct") + + @pytest.mark.parametrize( + "input_sample,expected", + [ + ([[0, 0], [4, 3]], (1.0, 0)), + ], + ) + def test_edge_cases(self, input_sample, expected): + res = barnard_exact(input_sample) + statistic, pvalue = res.statistic, res.pvalue + assert_equal(pvalue, expected[0]) + assert_equal(statistic, expected[1]) + + @pytest.mark.parametrize( + "input_sample,expected", + [ + ([[0, 5], [0, 10]], (1.0, np.nan)), + ([[5, 0], [10, 0]], (1.0, np.nan)), + ], + ) + def test_row_or_col_zero(self, input_sample, expected): + res = barnard_exact(input_sample) + statistic, pvalue = res.statistic, res.pvalue + assert_equal(pvalue, expected[0]) + assert_equal(statistic, expected[1]) + + @pytest.mark.parametrize( + "input_sample,expected", + [ + ([[2, 7], [8, 2]], (-2.518474945157, 0.009886140845)), + ([[7, 200], [300, 8]], (-21.320036698460, 0.0)), + ([[21, 28], [1957, 6]], (-30.489638143953, 0.0)), + ], + ) + @pytest.mark.parametrize("alternative", ["greater", "less"]) + def test_less_greater(self, input_sample, expected, alternative): + """ + "The expected values have been generated by R, using a resolution + for the nuisance parameter of 1e-6 : + ```R + library(Barnard) + options(digits=10) + a = barnard.test(2, 7, 8, 2, dp=1e-6, pooled=TRUE) + a$p.value[1] + ``` + In this test, we are using the "one-sided" return value `a$p.value[1]` + to test our pvalue. + """ + expected_stat, less_pvalue_expect = expected + + if alternative == "greater": + input_sample = np.array(input_sample)[:, ::-1] + expected_stat = -expected_stat + + res = barnard_exact(input_sample, alternative=alternative) + statistic, pvalue = res.statistic, res.pvalue + assert_allclose( + [statistic, pvalue], [expected_stat, less_pvalue_expect], atol=1e-7 + ) + + +class TestBoschlooExact: + """Some tests to show that boschloo_exact() works correctly.""" + + ATOL = 1e-7 + + @pytest.mark.parametrize( + "input_sample,expected", + [ + ([[2, 7], [8, 2]], (0.01852173, 0.009886142)), + ([[5, 1], [10, 10]], (0.9782609, 0.9450994)), + ([[5, 16], [20, 25]], (0.08913823, 0.05827348)), + ([[10, 5], [10, 1]], (0.1652174, 0.08565611)), + ([[5, 0], [1, 4]], (1, 1)), + ([[0, 1], [3, 2]], (0.5, 0.34375)), + ([[2, 7], [8, 2]], (0.01852173, 0.009886142)), + ([[7, 12], [8, 3]], (0.06406797, 0.03410916)), + ([[10, 24], [25, 37]], (0.2009359, 0.1512882)), + ], + ) + def test_less(self, input_sample, expected): + """The expected values have been generated by R, using a resolution + for the nuisance parameter of 1e-8 : + ```R + library(Exact) + options(digits=10) + data <- matrix(c(43, 10, 40, 39), 2, 2, byrow=TRUE) + a = exact.test(data, method="Boschloo", alternative="less", + tsmethod="central", np.interval=TRUE, beta=1e-8) + ``` + """ + res = boschloo_exact(input_sample, alternative="less") + statistic, pvalue = res.statistic, res.pvalue + assert_allclose([statistic, pvalue], expected, atol=self.ATOL) + + @pytest.mark.parametrize( + "input_sample,expected", + [ + ([[43, 40], [10, 39]], (0.0002875544, 0.0001615562)), + ([[2, 7], [8, 2]], (0.9990149, 0.9918327)), + ([[5, 1], [10, 10]], (0.1652174, 0.09008534)), + ([[5, 15], [20, 20]], (0.9849087, 0.9706997)), + ([[5, 16], [20, 25]], (0.972349, 0.9524124)), + ([[5, 0], [1, 4]], (0.02380952, 0.006865367)), + ([[0, 1], [3, 2]], (1, 1)), + ([[0, 2], [6, 4]], (1, 1)), + ([[2, 7], [8, 2]], (0.9990149, 0.9918327)), + ([[7, 12], [8, 3]], (0.9895302, 0.9771215)), + ([[10, 24], [25, 37]], (0.9012936, 0.8633275)), + ], + ) + def test_greater(self, input_sample, expected): + """The expected values have been generated by R, using a resolution + for the nuisance parameter of 1e-8 : + ```R + library(Exact) + options(digits=10) + data <- matrix(c(43, 10, 40, 39), 2, 2, byrow=TRUE) + a = exact.test(data, method="Boschloo", alternative="greater", + tsmethod="central", np.interval=TRUE, beta=1e-8) + ``` + """ + res = boschloo_exact(input_sample, alternative="greater") + statistic, pvalue = res.statistic, res.pvalue + assert_allclose([statistic, pvalue], expected, atol=self.ATOL) + + @pytest.mark.parametrize( + "input_sample,expected", + [ + ([[43, 40], [10, 39]], (0.0002875544, 0.0003231115)), + ([[2, 7], [8, 2]], (0.01852173, 0.01977228)), + ([[5, 1], [10, 10]], (0.1652174, 0.1801707)), + ([[5, 16], [20, 25]], (0.08913823, 0.116547)), + ([[5, 0], [1, 4]], (0.02380952, 0.01373073)), + ([[0, 1], [3, 2]], (0.5, 0.6875)), + ([[2, 7], [8, 2]], (0.01852173, 0.01977228)), + ([[7, 12], [8, 3]], (0.06406797, 0.06821831)), + ], + ) + def test_two_sided(self, input_sample, expected): + """The expected values have been generated by R, using a resolution + for the nuisance parameter of 1e-8 : + ```R + library(Exact) + options(digits=10) + data <- matrix(c(43, 10, 40, 39), 2, 2, byrow=TRUE) + a = exact.test(data, method="Boschloo", alternative="two.sided", + tsmethod="central", np.interval=TRUE, beta=1e-8) + ``` + """ + res = boschloo_exact(input_sample, alternative="two-sided", n=64) + # Need n = 64 for python 32-bit + statistic, pvalue = res.statistic, res.pvalue + assert_allclose([statistic, pvalue], expected, atol=self.ATOL) + + def test_raises(self): + # test we raise an error for wrong input number of nuisances. + error_msg = ( + "Number of points `n` must be strictly positive, found 0" + ) + with assert_raises(ValueError, match=error_msg): + boschloo_exact([[1, 2], [3, 4]], n=0) + + # test we raise an error for wrong shape of input. + error_msg = "The input `table` must be of shape \\(2, 2\\)." + with assert_raises(ValueError, match=error_msg): + boschloo_exact(np.arange(6).reshape(2, 3)) + + # Test all values must be positives + error_msg = "All values in `table` must be nonnegative." + with assert_raises(ValueError, match=error_msg): + boschloo_exact([[-1, 2], [3, 4]]) + + # Test value error on wrong alternative param + error_msg = ( + r"`alternative` should be one of \('two-sided', 'less', " + r"'greater'\), found .*" + ) + with assert_raises(ValueError, match=error_msg): + boschloo_exact([[1, 2], [3, 4]], "not-correct") + + @pytest.mark.parametrize( + "input_sample,expected", + [ + ([[0, 5], [0, 10]], (np.nan, np.nan)), + ([[5, 0], [10, 0]], (np.nan, np.nan)), + ], + ) + def test_row_or_col_zero(self, input_sample, expected): + res = boschloo_exact(input_sample) + statistic, pvalue = res.statistic, res.pvalue + assert_equal(pvalue, expected[0]) + assert_equal(statistic, expected[1]) + + def test_two_sided_gt_1(self): + # Check that returned p-value does not exceed 1 even when twice + # the minimum of the one-sided p-values does. See gh-15345. + tbl = [[1, 1], [13, 12]] + pl = boschloo_exact(tbl, alternative='less').pvalue + pg = boschloo_exact(tbl, alternative='greater').pvalue + assert 2*min(pl, pg) > 1 + pt = boschloo_exact(tbl, alternative='two-sided').pvalue + assert pt == 1.0 + + @pytest.mark.parametrize("alternative", ("less", "greater")) + def test_against_fisher_exact(self, alternative): + # Check that the statistic of `boschloo_exact` is the same as the + # p-value of `fisher_exact` (for one-sided tests). See gh-15345. + tbl = [[2, 7], [8, 2]] + boschloo_stat = boschloo_exact(tbl, alternative=alternative).statistic + fisher_p = stats.fisher_exact(tbl, alternative=alternative)[1] + assert_allclose(boschloo_stat, fisher_p) + + +@make_xp_test_case(cramervonmises_2samp) +class TestCvm_2samp: + @pytest.mark.parametrize('args', [([], np.arange(5)), + (np.arange(5), [1])]) + def test_too_small_input(self, args, xp): + args = (xp.asarray(arg, dtype=xp_default_dtype(xp)) for arg in args) + with eager_warns(SmallSampleWarning, match=too_small_1d_not_omit, xp=xp): + res = cramervonmises_2samp(*args) + xp_assert_equal(res.statistic, xp.asarray(xp.nan)) + xp_assert_equal(res.pvalue, xp.asarray(xp.nan)) + + def test_invalid_input(self, xp): + y = xp.arange(5) + msg = 'method must be either auto, exact or asymptotic' + with pytest.raises(ValueError, match=msg): + cramervonmises_2samp(y, y, 'xyz') + + def test_list_input(self): # list input only relevant for NumPy + x = [2, 3, 4, 7, 6] + y = [0.2, 0.7, 12, 18] + r1 = cramervonmises_2samp(x, y) + r2 = cramervonmises_2samp(np.array(x), np.array(y)) + assert_equal((r1.statistic, r1.pvalue), (r2.statistic, r2.pvalue)) + + @pytest.mark.parametrize('dtype', [None, 'float32', 'float64']) + def test_example_conover(self, dtype, xp): + # Example 2 in Section 6.2 of W.J. Conover: Practical Nonparametric + # Statistics, 1971. + dtype = xp_default_dtype(xp) if dtype is None else getattr(xp, dtype) + x = xp.asarray([7.6, 8.4, 8.6, 8.7, 9.3, 9.9, 10.1, 10.6, 11.2], dtype=dtype) + y = xp.asarray([5.2, 5.7, 5.9, 6.5, 6.8, 8.2, 9.1, 9.8, + 10.8, 11.3, 11.5, 12.3, 12.5, 13.4, 14.6], dtype=dtype) + r = cramervonmises_2samp(x, y) + xp_assert_close(r.statistic, xp.asarray(0.262, dtype=dtype), atol=1e-3) + xp_assert_close(r.pvalue, xp.asarray(.18, dtype=dtype), atol=1e-2) + + @pytest.mark.parametrize('statistic, m, n, pval', + [(710, 5, 6, 48./462), + (1897, 7, 7, 117./1716), + (576, 4, 6, 2./210), + (1764, 6, 7, 2./1716)]) + def test_exact_pvalue(self, statistic, m, n, pval): # only implemented w/ NumPy + # the exact values are taken from Anderson: On the distribution of the + # two-sample Cramer-von-Mises criterion, 1962. + # The values are taken from Table 2, 3, 4 and 5 + assert_equal(_pval_cvm_2samp_exact(statistic, m, n), pval) + + @pytest.mark.xslow + def test_large_sample(self, xp): + # for large samples, the statistic U gets very large + # do a sanity check that p-value is not 0, 1 or nan + rng = np.random.default_rng(4367) + x = distributions.norm.rvs(size=1000000, random_state=rng) + y = distributions.norm.rvs(size=900000, random_state=rng) + x, y = xp.asarray(x), xp.asarray(y) + r = cramervonmises_2samp(x, y) + assert 0 < r.pvalue < 1 + r = cramervonmises_2samp(x, y+0.1) + assert 0 < r.pvalue < 1 + + def test_exact_vs_asymptotic(self, xp): + rng = np.random.RandomState(0) + x = xp.asarray(rng.random(7)) + y = xp.asarray(rng.random(8)) + r1 = cramervonmises_2samp(x, y, method='exact') + r2 = cramervonmises_2samp(x, y, method='asymptotic') + xp_assert_equal(r1.statistic, r2.statistic) + xp_assert_close(r1.pvalue, r2.pvalue, atol=1e-2) + + def test_method_auto(self, xp): + x = xp.arange(20.) + y = xp.asarray([0.5, 4.7, 13.1]) + r1 = cramervonmises_2samp(x, y, method='exact') + r2 = cramervonmises_2samp(x, y, method='auto') + xp_assert_equal(r1.pvalue, r2.pvalue) + # switch to asymptotic if one sample has more than 20 observations + x = xp.arange(21.) + r1 = cramervonmises_2samp(x, y, method='asymptotic') + r2 = cramervonmises_2samp(x, y, method='auto') + xp_assert_equal(r1.pvalue, r2.pvalue) + + def test_same_input(self, xp): + # make sure trivial edge case can be handled + # note that _cdf_cvm_inf(0) = nan. implementation avoids nan by + # returning pvalue=1 for very small values of the statistic + x = xp.arange(15) + res = cramervonmises_2samp(x, x) + xp_assert_equal(res.statistic, xp.asarray(0.)) + xp_assert_equal(res.pvalue, xp.asarray(1.)) + # check exact p-value + res = cramervonmises_2samp(x[:4], x[:4]) + xp_assert_equal(res.statistic, xp.asarray(0.)) + xp_assert_equal(res.pvalue, xp.asarray(1.)) + + +class TestTukeyHSD: + + data_same_size = ([24.5, 23.5, 26.4, 27.1, 29.9], + [28.4, 34.2, 29.5, 32.2, 30.1], + [26.1, 28.3, 24.3, 26.2, 27.8]) + data_diff_size = ([24.5, 23.5, 26.28, 26.4, 27.1, 29.9, 30.1, 30.1], + [28.4, 34.2, 29.5, 32.2, 30.1], + [26.1, 28.3, 24.3, 26.2, 27.8]) + extreme_size = ([24.5, 23.5, 26.4], + [28.4, 34.2, 29.5, 32.2, 30.1, 28.4, 34.2, 29.5, 32.2, + 30.1], + [26.1, 28.3, 24.3, 26.2, 27.8]) + + sas_same_size = """ + Comparison LowerCL Difference UpperCL Significance + 2 - 3 0.6908830568 4.34 7.989116943 1 + 2 - 1 0.9508830568 4.6 8.249116943 1 + 3 - 2 -7.989116943 -4.34 -0.6908830568 1 + 3 - 1 -3.389116943 0.26 3.909116943 0 + 1 - 2 -8.249116943 -4.6 -0.9508830568 1 + 1 - 3 -3.909116943 -0.26 3.389116943 0 + """ + + sas_diff_size = """ + Comparison LowerCL Difference UpperCL Significance + 2 - 1 0.2679292645 3.645 7.022070736 1 + 2 - 3 0.5934764007 4.34 8.086523599 1 + 1 - 2 -7.022070736 -3.645 -0.2679292645 1 + 1 - 3 -2.682070736 0.695 4.072070736 0 + 3 - 2 -8.086523599 -4.34 -0.5934764007 1 + 3 - 1 -4.072070736 -0.695 2.682070736 0 + """ + + sas_extreme = """ + Comparison LowerCL Difference UpperCL Significance + 2 - 3 1.561605075 4.34 7.118394925 1 + 2 - 1 2.740784879 6.08 9.419215121 1 + 3 - 2 -7.118394925 -4.34 -1.561605075 1 + 3 - 1 -1.964526566 1.74 5.444526566 0 + 1 - 2 -9.419215121 -6.08 -2.740784879 1 + 1 - 3 -5.444526566 -1.74 1.964526566 0 + """ + + @pytest.mark.parametrize("data,res_expect_str,atol", + ((data_same_size, sas_same_size, 1e-4), + (data_diff_size, sas_diff_size, 1e-4), + (extreme_size, sas_extreme, 1e-10), + ), + ids=["equal size sample", + "unequal sample size", + "extreme sample size differences"]) + def test_compare_sas(self, data, res_expect_str, atol): + ''' + SAS code used to generate results for each sample: + DATA ACHE; + INPUT BRAND RELIEF; + CARDS; + 1 24.5 + ... + 3 27.8 + ; + ods graphics on; ODS RTF;ODS LISTING CLOSE; + PROC ANOVA DATA=ACHE; + CLASS BRAND; + MODEL RELIEF=BRAND; + MEANS BRAND/TUKEY CLDIFF; + TITLE 'COMPARE RELIEF ACROSS MEDICINES - ANOVA EXAMPLE'; + ods output CLDiffs =tc; + proc print data=tc; + format LowerCL 17.16 UpperCL 17.16 Difference 17.16; + title "Output with many digits"; + RUN; + QUIT; + ODS RTF close; + ODS LISTING; + ''' + res_expect = np.asarray(res_expect_str.replace(" - ", " ").split()[5:], + dtype=float).reshape((6, 6)) + res_tukey = stats.tukey_hsd(*data) + conf = res_tukey.confidence_interval() + # loop over the comparisons + for i, j, l, s, h, sig in res_expect: + i, j = int(i) - 1, int(j) - 1 + assert_allclose(conf.low[i, j], l, atol=atol) + assert_allclose(res_tukey.statistic[i, j], s, atol=atol) + assert_allclose(conf.high[i, j], h, atol=atol) + assert_allclose((res_tukey.pvalue[i, j] <= .05), sig == 1) + + matlab_sm_siz = """ + 1 2 -8.2491590248597 -4.6 -0.9508409751403 0.0144483269098 + 1 3 -3.9091590248597 -0.26 3.3891590248597 0.9803107240900 + 2 3 0.6908409751403 4.34 7.9891590248597 0.0203311368795 + """ + + matlab_diff_sz = """ + 1 2 -7.02207069748501 -3.645 -0.26792930251500 0.03371498443080 + 1 3 -2.68207069748500 0.695 4.07207069748500 0.85572267328807 + 2 3 0.59347644287720 4.34 8.08652355712281 0.02259047020620 + """ + + @pytest.mark.parametrize("data,res_expect_str,atol", + ((data_same_size, matlab_sm_siz, 1e-12), + (data_diff_size, matlab_diff_sz, 1e-7)), + ids=["equal size sample", + "unequal size sample"]) + def test_compare_matlab(self, data, res_expect_str, atol): + """ + vals = [24.5, 23.5, 26.4, 27.1, 29.9, 28.4, 34.2, 29.5, 32.2, 30.1, + 26.1, 28.3, 24.3, 26.2, 27.8] + names = {'zero', 'zero', 'zero', 'zero', 'zero', 'one', 'one', 'one', + 'one', 'one', 'two', 'two', 'two', 'two', 'two'} + [p,t,stats] = anova1(vals,names,"off"); + [c,m,h,nms] = multcompare(stats, "CType","hsd"); + """ + res_expect = np.asarray(res_expect_str.split(), + dtype=float).reshape((3, 6)) + res_tukey = stats.tukey_hsd(*data) + conf = res_tukey.confidence_interval() + # loop over the comparisons + for i, j, l, s, h, p in res_expect: + i, j = int(i) - 1, int(j) - 1 + assert_allclose(conf.low[i, j], l, atol=atol) + assert_allclose(res_tukey.statistic[i, j], s, atol=atol) + assert_allclose(conf.high[i, j], h, atol=atol) + assert_allclose(res_tukey.pvalue[i, j], p, atol=atol) + + def test_compare_r(self): + """ + Testing against results and p-values from R: + from: https://www.rdocumentation.org/packages/stats/versions/3.6.2/ + topics/TukeyHSD + > require(graphics) + > summary(fm1 <- aov(breaks ~ tension, data = warpbreaks)) + > TukeyHSD(fm1, "tension", ordered = TRUE) + > plot(TukeyHSD(fm1, "tension")) + Tukey multiple comparisons of means + 95% family-wise confidence level + factor levels have been ordered + Fit: aov(formula = breaks ~ tension, data = warpbreaks) + $tension + """ + str_res = """ + diff lwr upr p adj + 2 - 3 4.722222 -4.8376022 14.28205 0.4630831 + 1 - 3 14.722222 5.1623978 24.28205 0.0014315 + 1 - 2 10.000000 0.4401756 19.55982 0.0384598 + """ + res_expect = np.asarray(str_res.replace(" - ", " ").split()[5:], + dtype=float).reshape((3, 6)) + data = ([26, 30, 54, 25, 70, 52, 51, 26, 67, + 27, 14, 29, 19, 29, 31, 41, 20, 44], + [18, 21, 29, 17, 12, 18, 35, 30, 36, + 42, 26, 19, 16, 39, 28, 21, 39, 29], + [36, 21, 24, 18, 10, 43, 28, 15, 26, + 20, 21, 24, 17, 13, 15, 15, 16, 28]) + + res_tukey = stats.tukey_hsd(*data) + conf = res_tukey.confidence_interval() + # loop over the comparisons + for i, j, s, l, h, p in res_expect: + i, j = int(i) - 1, int(j) - 1 + # atols are set to the number of digits present in the r result. + assert_allclose(conf.low[i, j], l, atol=1e-7) + assert_allclose(res_tukey.statistic[i, j], s, atol=1e-6) + assert_allclose(conf.high[i, j], h, atol=1e-5) + assert_allclose(res_tukey.pvalue[i, j], p, atol=1e-7) + + def test_engineering_stat_handbook(self): + ''' + Example sourced from: + https://www.itl.nist.gov/div898/handbook/prc/section4/prc471.htm + ''' + group1 = [6.9, 5.4, 5.8, 4.6, 4.0] + group2 = [8.3, 6.8, 7.8, 9.2, 6.5] + group3 = [8.0, 10.5, 8.1, 6.9, 9.3] + group4 = [5.8, 3.8, 6.1, 5.6, 6.2] + res = stats.tukey_hsd(group1, group2, group3, group4) + conf = res.confidence_interval() + lower = np.asarray([ + [0, 0, 0, -2.25], + [.29, 0, -2.93, .13], + [1.13, 0, 0, .97], + [0, 0, 0, 0]]) + upper = np.asarray([ + [0, 0, 0, 1.93], + [4.47, 0, 1.25, 4.31], + [5.31, 0, 0, 5.15], + [0, 0, 0, 0]]) + + for (i, j) in [(1, 0), (2, 0), (0, 3), (1, 2), (2, 3)]: + assert_allclose(conf.low[i, j], lower[i, j], atol=1e-2) + assert_allclose(conf.high[i, j], upper[i, j], atol=1e-2) + + def test_rand_symm(self): + # test some expected identities of the results + rng = np.random.default_rng(2699550179) + data = rng.random((3, 100)) + res = stats.tukey_hsd(*data) + conf = res.confidence_interval() + # the confidence intervals should be negated symmetric of each other + assert_equal(conf.low, -conf.high.T) + # the `high` and `low` center diagonals should be the same since the + # mean difference in a self comparison is 0. + assert_equal(np.diagonal(conf.high), conf.high[0, 0]) + assert_equal(np.diagonal(conf.low), conf.low[0, 0]) + # statistic array should be antisymmetric with zeros on the diagonal + assert_equal(res.statistic, -res.statistic.T) + assert_equal(np.diagonal(res.statistic), 0) + # p-values should be symmetric and 1 when compared to itself + assert_equal(res.pvalue, res.pvalue.T) + assert_equal(np.diagonal(res.pvalue), 1) + + def test_no_inf(self): + with assert_raises(ValueError, match="...must be finite."): + stats.tukey_hsd([1, 2, 3], [2, np.inf], [6, 7, 3]) + + def test_is_1d(self): + with assert_raises(ValueError, match="...must be one-dimensional"): + stats.tukey_hsd([[1, 2], [2, 3]], [2, 5], [5, 23, 6]) + + def test_no_empty(self): + with assert_raises(ValueError, match="...must be greater than one"): + stats.tukey_hsd([], [2, 5], [4, 5, 6]) + + def test_equal_var_input_validation(self): + msg = "Expected a boolean value for 'equal_var'" + with assert_raises(TypeError, match=msg): + stats.tukey_hsd([1, 2, 3], [2, 5], [6, 7], equal_var="False") + + @pytest.mark.parametrize("nargs", (0, 1)) + def test_not_enough_treatments(self, nargs): + with assert_raises(ValueError, match="...more than 1 treatment."): + stats.tukey_hsd(*([[23, 7, 3]] * nargs)) + + @pytest.mark.parametrize("cl", [-.5, 0, 1, 2]) + def test_conf_level_invalid(self, cl): + with assert_raises(ValueError, match="must be between 0 and 1"): + r = stats.tukey_hsd([23, 7, 3], [3, 4], [9, 4]) + r.confidence_interval(cl) + + def test_2_args_ttest(self): + # that with 2 treatments the `pvalue` is equal to that of `ttest_ind` + res_tukey = stats.tukey_hsd(*self.data_diff_size[:2]) + res_ttest = stats.ttest_ind(*self.data_diff_size[:2]) + assert_allclose(res_ttest.pvalue, res_tukey.pvalue[0, 1]) + assert_allclose(res_ttest.pvalue, res_tukey.pvalue[1, 0]) + + +class TestGamesHowell: + # data with unequal variances + data_same_size = ([24., 23., 31., 51.], + [34., 18., 18., 26.], + [17., 68., 59., 7.]) + + data_diff_size = ([30., 23., 51.], + [-81., 71., -27., 63.], + [42., 11., 29., 19., 50.], + [23., 22., 20., 18., 9.]) + + spss_same_size = """ + Mean Diff Lower Bound Upper Bound Sig + 0 - 1 8.25000000 -16.5492749527311 33.0492749527311 0.558733632413559 + 0 - 2 -5.50000000 -63.6702454316458 52.6702454316458 0.941147750599221 + 1 - 2 -13.7500000 -74.3174374251372 46.8174374251372 0.682983914946841 + """ + + spss_diff_size = """ + Mean Diff Lower Bound Upper Bound Sig + 0 - 1 28.16666667 -141.985416377670 198.318749711003 0.8727542747886180 + 0 - 2 4.466666667 -37.2830676783904 46.2164010117237 0.9752628408671710 + 0 - 3 16.26666667 -35.0933112382470 67.6266445715803 0.4262506151302880 + 1 - 2 -23.70000000 -195.315617201249 147.915617201249 0.9148950609000590 + 1 - 3 -11.90000000 -188.105478728519 164.305478728519 0.9861432250093960 + 2 - 3 11.80000000 -16.2894857524254 39.8894857524254 0.4755344436335670 + """ + + @pytest.mark.xslow + @pytest.mark.parametrize("data, res_expect_str", + ((data_same_size, spss_same_size), + (data_diff_size, spss_diff_size)), + ids=["equal size sample", + "unequal sample size"]) + def test_compare_spss(self, data, res_expect_str): + """ + DATA LIST LIST /Group (F1.0) Value (F8.2). + BEGIN DATA + 0 24 + 0 23 + 0 31 + 0 51 + 1 34 + 1 18 + 1 18 + 1 26 + 2 17 + 2 68 + 2 59 + 2 7 + END DATA. + + ONEWAY Value BY Group + /MISSING ANALYSIS + /POSTHOC=GH ALPHA(0.05). + """ + res_expect = np.asarray( + res_expect_str.replace(" - ", " ").split()[7:], + dtype=float).reshape(-1, 6) + res_games = stats.tukey_hsd(*data, equal_var=False) + conf = res_games.confidence_interval() + # loop over the comparisons + for i, j, s, l, h, p in res_expect: + i, j = int(i), int(j) + assert_allclose(res_games.statistic[i, j], s, atol=1e-8) + assert_allclose(res_games.pvalue[i, j], p, atol=1e-8) + assert_allclose(conf.low[i, j], l, atol=1e-6) + assert_allclose(conf.high[i, j], h, atol=1e-5) + + r_same_size = """ + q value Pr(>|q|) + 1 - 0 == 0 -1.5467805948856344 0.55873362851759 + 2 - 0 == 0 0.4726721776628535 0.94114775035993 + 2 - 1 == 0 1.246837541297872 0.68298393799782 + """ + + r_diff_size = """ + q value Pr(>|q|) + 1 - 0 == 0 -1.0589317485313876 0.87275427357438 + 2 - 0 == 0 -0.5716222106144833 0.97526284087419 + 3 - 0 == 0 -2.6209678382077000 0.42625067714691 + 2 - 1 == 0 0.8971899898179028 0.91489506061850 + 3 - 1 == 0 0.4579447210555352 0.98614322544695 + 3 - 2 == 0 -2.198800177874794 0.47553444364614 + """ + + @pytest.mark.parametrize("data, res_expect_str", + ((data_same_size, r_same_size), + (data_diff_size, r_diff_size)), + ids=["equal size sample", + "unequal sample size"]) + def test_compare_r(self, data, res_expect_str): + """ + games-howell is provided by PMCMRplus package + https://search.r-project.org/CRAN/refmans/PMCMRplus/html/gamesHowellTest.html + > library("PMCMRplus") + > options(digits=16) + > table = data.frame( + values = c(24., 23., 31., 51., 34., 18., 18., 26., 17., 68., 59., 7.), + groups = c("0", "0", "0", "0", "1", "1", "1", "1", "2", "2", "2", "2") + ) + > table$groups = as.factor(table$groups) + > fit <-aov(values ~ groups, table) + > res = gamesHowellTest(fit) + > summary(res) + """ + res_expect = np.asarray( + res_expect_str.replace(" - ", " ") + .replace(" == ", " ").split()[3:], + dtype=float).reshape(-1, 5) + res_games = stats.tukey_hsd(*data, equal_var=False) + # loop over the comparisons + # note confidence intervals are not provided by PMCMRplus + for j, i, _, _, p in res_expect: + i, j = int(i), int(j) + assert_allclose(res_games.pvalue[i, j], p, atol=1e-7) + + # Data validation test has been covered by TestTukeyHSD + # like empty, 1d, inf, and lack of tretments + # because games_howell leverage _tukey_hsd_iv() + +class TestPoissonMeansTest: + @pytest.mark.parametrize("c1, n1, c2, n2, p_expect", ( + # example from [1], 6. Illustrative examples: Example 1 + [0, 100, 3, 100, 0.0884], + [2, 100, 6, 100, 0.1749] + )) + def test_paper_examples(self, c1, n1, c2, n2, p_expect): + res = stats.poisson_means_test(c1, n1, c2, n2) + assert_allclose(res.pvalue, p_expect, atol=1e-4) + + @pytest.mark.parametrize("c1, n1, c2, n2, p_expect, alt, d", ( + # These test cases are produced by the wrapped fortran code from the + # original authors. Using a slightly modified version of this fortran, + # found here, https://github.com/nolanbconaway/poisson-etest, + # additional tests were created. + [20, 10, 20, 10, 0.9999997568929630, 'two-sided', 0], + [10, 10, 10, 10, 0.9999998403241203, 'two-sided', 0], + [50, 15, 1, 1, 0.09920321053409643, 'two-sided', .05], + [3, 100, 20, 300, 0.12202725450896404, 'two-sided', 0], + [3, 12, 4, 20, 0.40416087318539173, 'greater', 0], + [4, 20, 3, 100, 0.008053640402974236, 'greater', 0], + # publishing paper does not include a `less` alternative, + # so it was calculated with switched argument order and + # alternative="greater" + [4, 20, 3, 10, 0.3083216325432898, 'less', 0], + [1, 1, 50, 15, 0.09322998607245102, 'less', 0] + )) + def test_fortran_authors(self, c1, n1, c2, n2, p_expect, alt, d): + res = stats.poisson_means_test(c1, n1, c2, n2, alternative=alt, diff=d) + assert_allclose(res.pvalue, p_expect, atol=2e-6, rtol=1e-16) + + def test_different_results(self): + # The implementation in Fortran is known to break down at higher + # counts and observations, so we expect different results. By + # inspection we can infer the p-value to be near one. + count1, count2 = 10000, 10000 + nobs1, nobs2 = 10000, 10000 + res = stats.poisson_means_test(count1, nobs1, count2, nobs2) + assert_allclose(res.pvalue, 1) + + def test_less_than_zero_lambda_hat2(self): + # demonstrates behavior that fixes a known fault from original Fortran. + # p-value should clearly be near one. + count1, count2 = 0, 0 + nobs1, nobs2 = 1, 1 + res = stats.poisson_means_test(count1, nobs1, count2, nobs2) + assert_allclose(res.pvalue, 1) + + def test_input_validation(self): + count1, count2 = 0, 0 + nobs1, nobs2 = 1, 1 + + # test non-integral events + message = '`k1` and `k2` must be integers.' + with assert_raises(TypeError, match=message): + stats.poisson_means_test(.7, nobs1, count2, nobs2) + with assert_raises(TypeError, match=message): + stats.poisson_means_test(count1, nobs1, .7, nobs2) + + # test negative events + message = '`k1` and `k2` must be greater than or equal to 0.' + with assert_raises(ValueError, match=message): + stats.poisson_means_test(-1, nobs1, count2, nobs2) + with assert_raises(ValueError, match=message): + stats.poisson_means_test(count1, nobs1, -1, nobs2) + + # test negative sample size + message = '`n1` and `n2` must be greater than 0.' + with assert_raises(ValueError, match=message): + stats.poisson_means_test(count1, -1, count2, nobs2) + with assert_raises(ValueError, match=message): + stats.poisson_means_test(count1, nobs1, count2, -1) + + # test negative difference + message = 'diff must be greater than or equal to 0.' + with assert_raises(ValueError, match=message): + stats.poisson_means_test(count1, nobs1, count2, nobs2, diff=-1) + + # test invalid alternative + message = 'Alternative must be one of ...' + with assert_raises(ValueError, match=message): + stats.poisson_means_test(1, 2, 1, 2, alternative='error') + + +@make_xp_test_case(stats.bws_test) +class TestBWSTest: + + def test_bws_input_validation(self, xp): + rng = np.random.default_rng(4571775098104213308) + + x, y = rng.random(size=(2, 7)) + x, y = xp.asarray(x), xp.asarray(y) + + message = '`axis` must be an integer or None.' + with pytest.raises(ValueError, match=message): + stats.bws_test(x, y, axis=1.5) + + message = '`axis` is not compatible with the shapes of the inputs.' + with pytest.raises(ValueError, match=message): + stats.bws_test(x, y, axis=3) + + message = '`x` and `y` must contain...' + with pytest.raises(ValueError, match=message): + stats.bws_test(x, xp.asarray([], dtype=x.dtype)) + + message = 'alternative` must be one of...' + with pytest.raises(ValueError, match=message): + stats.bws_test(x, y, alternative='ekki-ekki') + + message = 'method` must be an instance of...' + with pytest.raises(ValueError, match=message): + stats.bws_test(x, y, method=42) + + @pytest.mark.parametrize('dtype', [None, 'float32', 'float64']) + def test_against_published_reference(self, dtype, xp): + # Test against Example 2 in bws_test Reference [1], pg 9 + # https://link.springer.com/content/pdf/10.1007/BF02762032.pdf + dtype = xp_default_dtype(xp) if dtype is None else getattr(xp, dtype) + x = xp.asarray([1, 2, 3, 4, 6, 7, 8], dtype=dtype) + y = xp.asarray([5, 9, 10, 11, 12, 13, 14], dtype=dtype) + res = stats.bws_test(x, y, alternative='two-sided') + xp_assert_close(res.statistic, xp.asarray(5.132, dtype=dtype), atol=1e-3) + xp_assert_close(res.pvalue, xp.asarray(10/3432, dtype=dtype)) + + @pytest.mark.parametrize(('alternative', 'statistic', 'pvalue'), + [('two-sided', 1.7510204081633, 0.1264422777777), + ('less', -1.7510204081633, 0.05754662004662), + ('greater', -1.7510204081633, 0.9424533799534)]) + def test_against_R(self, alternative, statistic, pvalue, xp): + # Test against R library BWStest function bws_test + # library(BWStest) + # options(digits=16) + # x = c(...) + # y = c(...) + # bws_test(x, y, alternative='two.sided') + rng = np.random.default_rng(4571775098104213308) + x, y = rng.random(size=(2, 7)) + x, y = xp.asarray(x, dtype=xp.float64), xp.asarray(y, dtype=xp.float64) + res = stats.bws_test(x, y, alternative=alternative) + statistic = xp.asarray(statistic, dtype=xp.float64) + pvalue = xp.asarray(pvalue, dtype=xp.float64) + xp_assert_close(res.statistic, statistic, rtol=1e-13) + xp_assert_close(res.pvalue, pvalue, atol=1e-2, rtol=1e-1) + + @pytest.mark.parametrize(('alternative', 'statistic', 'pvalue'), + [('two-sided', 1.142629265891, 0.2903950180801), + ('less', 0.99629665877411, 0.8545660222131), + ('greater', 0.99629665877411, 0.1454339777869)]) + def test_against_R_imbalanced(self, alternative, statistic, pvalue, xp): + # Test against R library BWStest function bws_test + # library(BWStest) + # options(digits=16) + # x = c(...) + # y = c(...) + # bws_test(x, y, alternative='two.sided') + rng = np.random.default_rng(5429015622386364034) + x = rng.random(size=9) + y = rng.random(size=8) + x, y = xp.asarray(x), xp.asarray(y) + res = stats.bws_test(x, y, alternative=alternative) + x, y = xp.asarray(x, dtype=xp.float64), xp.asarray(y, dtype=xp.float64) + res = stats.bws_test(x, y, alternative=alternative) + statistic = xp.asarray(statistic, dtype=xp.float64) + pvalue = xp.asarray(pvalue, dtype=xp.float64) + xp_assert_close(res.statistic, statistic, rtol=1e-13) + xp_assert_close(res.pvalue, pvalue, atol=1e-2, rtol=1e-1) + + def test_method(self, xp): + # Test that `method` parameter has the desired effect + rng = np.random.default_rng(1520514347193347862) + x, y = rng.random(size=(2, 10)) + x, y = xp.asarray(x), xp.asarray(y) + + rng = np.random.default_rng(1520514347193347862) + method = stats.PermutationMethod(n_resamples=10, rng=rng) + res1 = stats.bws_test(x, y, method=method) + + assert res1.null_distribution.shape[0] == 10 + + rng = np.random.default_rng(1520514347193347862) + method = stats.PermutationMethod(n_resamples=10, rng=rng) + res2 = stats.bws_test(x, y, method=method) + + xp_assert_close(res1.null_distribution, res2.null_distribution) + + rng = np.random.default_rng(5205143471933478621) + method = stats.PermutationMethod(n_resamples=10, rng=rng) + res3 = stats.bws_test(x, y, method=method) + + assert xp.all(xp.abs(res3.null_distribution - res1.null_distribution) > 0.1) + + def test_directions(self, xp): + # Sanity check of the sign of the one-sided statistic + rng = np.random.default_rng(1520514347193347862) + x = xp.asarray(rng.random(size=5)) + y = x - 1. + + pmin = xp.asarray(1 / 252, dtype=xp.float64) + pmax = xp.asarray(1, dtype=xp.float64) + + res = stats.bws_test(x, y, alternative='greater') + assert res.statistic > 0 + xp_assert_equal(res.pvalue, pmin) + + res = stats.bws_test(x, y, alternative='less') + assert res.statistic > 0 + xp_assert_equal(res.pvalue, pmax) + + res = stats.bws_test(y, x, alternative='less') + assert res.statistic < 0 + xp_assert_equal(res.pvalue, pmin) + + res = stats.bws_test(y, x, alternative='greater') + assert res.statistic < 0 + xp_assert_equal(res.pvalue, pmax) + + @pytest.mark.parametrize('axis', [0, 1]) + def test_nd(self, axis, xp): + rng = np.random.default_rng(1520514347193347862) + z = rng.random(size=(2, 3, 5)) + + x, y = z + res = stats.bws_test(xp.asarray(x), xp.asarray(y), axis=axis) + + x, y = np.moveaxis(z, axis+1, -1) + ref_statistic, ref_pvalue = [], [] + for x_, y_ in zip(x, y): + ref = stats.bws_test(xp.asarray(x_), xp.asarray(y_)) + ref_statistic.append(ref.statistic) + ref_pvalue.append(ref.pvalue) + ref_statistic, ref_pvalue = xp.stack(ref_statistic), xp.stack(ref_pvalue) + + xp_assert_close(res.statistic, ref_statistic) + xp_assert_close(res.pvalue, ref_pvalue) + + def test_axis_None(self, xp): + rng = np.random.default_rng(5205143471933478621) + x, y = rng.random(size=(2, 2, 3)) + x, y = xp.asarray(x), xp.asarray(y) + res = stats.bws_test(x, y, axis=None) + ref = stats.bws_test(xp_ravel(x), xp_ravel(y)) + xp_assert_close(res.statistic, ref.statistic) + xp_assert_close(res.pvalue, ref.pvalue) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/test_kdeoth.py b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_kdeoth.py new file mode 100644 index 0000000000000000000000000000000000000000..a4ceed5e477d7ae23d5f17324371bd6753fd7334 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_kdeoth.py @@ -0,0 +1,678 @@ +from scipy import stats, linalg, integrate +import numpy as np +from numpy.testing import (assert_almost_equal, assert_, assert_equal, + assert_array_almost_equal, + assert_array_almost_equal_nulp, assert_allclose) +import pytest +from pytest import raises as assert_raises + + +def test_kde_1d(): + #some basic tests comparing to normal distribution + rng = np.random.default_rng(8765678) + n_basesample = 500 + xn = rng.normal(0, 1, n_basesample) + xnmean = xn.mean() + xnstd = xn.std(ddof=1) + + # get kde for original sample + gkde = stats.gaussian_kde(xn) + + # evaluate the density function for the kde for some points + xx = np.asarray([0.1, 0.5, 0.9]) + loc, scale = gkde.dataset, np.sqrt(gkde.covariance) + assert_allclose( + gkde(xx), + stats.norm.pdf(xx[:, None], loc=loc, scale=scale).sum(axis=-1) / gkde.n, + rtol=5e-14 + ) + + xs = np.linspace(-7, 7, 501) + kdepdf = gkde.evaluate(xs) + normpdf = stats.norm.pdf(xs, loc=xnmean, scale=xnstd) + intervall = xs[1] - xs[0] + + assert_(np.sum((kdepdf - normpdf)**2)*intervall < 0.01) + prob1 = gkde.integrate_box_1d(xnmean, np.inf) + prob2 = gkde.integrate_box_1d(-np.inf, xnmean) + assert_almost_equal(prob1, 0.5, decimal=1) + assert_almost_equal(prob2, 0.5, decimal=1) + assert_almost_equal(gkde.integrate_box(xnmean, np.inf), prob1, decimal=13) + assert_almost_equal(gkde.integrate_box(-np.inf, xnmean), prob2, decimal=13) + + assert_almost_equal(gkde.integrate_kde(gkde), + (kdepdf**2).sum()*intervall, decimal=2) + assert_almost_equal(gkde.integrate_gaussian(xnmean, xnstd**2), + (kdepdf*normpdf).sum()*intervall, decimal=2) + + +def test_kde_1d_weighted(): + #some basic tests comparing to normal distribution + rng = np.random.default_rng(8765678) + n_basesample = 500 + xn = rng.normal(0, 1, n_basesample) + wn = rng.random(n_basesample) + xnmean = np.average(xn, weights=wn) + xnstd = np.sqrt(np.average((xn-xnmean)**2, weights=wn)) + + # get kde for original sample + gkde = stats.gaussian_kde(xn, weights=wn) + + # evaluate the density function for the kde for some points + # evaluate the density function for the kde for some points + xx = np.asarray([0.1, 0.5, 0.9]) + loc, scale = gkde.dataset, np.sqrt(gkde.covariance) + + pdf = stats.norm.pdf + assert_allclose( + gkde(xx), + np.sum(pdf(xx[:, None], loc=loc, scale=scale) * gkde.weights, axis=-1), + rtol=5e-14 + ) + + xs = np.linspace(-7, 7, 501) + kdepdf = gkde.evaluate(xs) + normpdf = stats.norm.pdf(xs, loc=xnmean, scale=xnstd) + intervall = xs[1] - xs[0] + + assert_(np.sum((kdepdf - normpdf)**2)*intervall < 0.01) + prob1 = gkde.integrate_box_1d(xnmean, np.inf) + prob2 = gkde.integrate_box_1d(-np.inf, xnmean) + assert_almost_equal(prob1, 0.5, decimal=1) + assert_almost_equal(prob2, 0.5, decimal=1) + assert_almost_equal(gkde.integrate_box(xnmean, np.inf), prob1, decimal=13) + assert_almost_equal(gkde.integrate_box(-np.inf, xnmean), prob2, decimal=13) + + assert_almost_equal(gkde.integrate_kde(gkde), + (kdepdf**2).sum()*intervall, decimal=2) + assert_almost_equal(gkde.integrate_gaussian(xnmean, xnstd**2), + (kdepdf*normpdf).sum()*intervall, decimal=2) + + +@pytest.mark.parametrize("n_basesample", + [ + 20, + pytest.param(500, marks=[pytest.mark.xslow]) + ] +) +def test_kde_2d(n_basesample): + #some basic tests comparing to normal distribution + rng = np.random.default_rng(8765678) + + mean = np.array([1.0, 3.0]) + covariance = np.array([[1.0, 2.0], [2.0, 6.0]]) + + # Need transpose (shape (2, 500)) for kde + xn = rng.multivariate_normal(mean, covariance, size=n_basesample).T + + # get kde for original sample + gkde = stats.gaussian_kde(xn) + + # evaluate vs multivariate normal, using the KDE definition + xx = np.asarray([[1, 2], [3, 4], [5, 6]]) + arg = xx[:, None, :] - gkde.dataset.T + pdf = stats.multivariate_normal.pdf + assert_allclose( + gkde(xx.T), + pdf(arg, cov=gkde.covariance).sum(axis=-1) / gkde.n, + rtol=5e-14 + ) + + # ... and cdf + cdf = stats.multivariate_normal.cdf + lo, hi = [-1, -2], [0, 0] + lo_, hi_ = lo - gkde.dataset.T, hi - gkde.dataset.T + assert_allclose( + gkde.integrate_box(lo, hi, rng=rng), + cdf(hi_, lower_limit=lo_, cov=gkde.covariance, rng=rng).sum(axis=-1) / gkde.n, + rtol=5e-7 + ) + + # evaluate the density function for the kde for some points + x, y = np.mgrid[-7:7:500j, -7:7:500j] + grid_coords = np.vstack([x.ravel(), y.ravel()]) + kdepdf = gkde.evaluate(grid_coords) + kdepdf = kdepdf.reshape(500, 500) + + normpdf = stats.multivariate_normal.pdf(np.dstack([x, y]), + mean=mean, cov=covariance) + intervall = y.ravel()[1] - y.ravel()[0] + + assert_(np.sum((kdepdf - normpdf)**2) * (intervall**2) < 0.01) + + small = -1e100 + large = 1e100 + prob1 = gkde.integrate_box([small, mean[1]], [large, large], rng=rng) + prob2 = gkde.integrate_box([small, small], [large, mean[1]], rng=rng) + + assert_almost_equal(prob1, 0.5, decimal=1) + assert_almost_equal(prob2, 0.5, decimal=1) + assert_almost_equal(gkde.integrate_kde(gkde), + (kdepdf**2).sum()*(intervall**2), decimal=2) + assert_almost_equal(gkde.integrate_gaussian(mean, covariance), + (kdepdf*normpdf).sum()*(intervall**2), decimal=2) + + +@pytest.mark.parametrize("n_basesample", + [ + 20, + pytest.param(500, marks=[pytest.mark.xslow]) + ] +) +def test_kde_2d_weighted(n_basesample): + #some basic tests comparing to normal distribution + rng = np.random.RandomState(8765678) + + mean = np.array([1.0, 3.0]) + covariance = np.array([[1.0, 2.0], [2.0, 6.0]]) + + # Need transpose (shape (2, 500)) for kde + xn = rng.multivariate_normal(mean, covariance, size=n_basesample).T + wn = rng.rand(n_basesample) + + # get kde for original sample + gkde = stats.gaussian_kde(xn, weights=wn) + + + # evaluate vs multivariate normal, using the kde definition + xx = np.asarray([[1, 2], [3, 4], [5, 6]]) + arg = xx[:, None, :] - gkde.dataset.T + pdf = stats.multivariate_normal.pdf + assert_allclose( + gkde(xx.T), + np.sum(pdf(arg, cov=gkde.covariance) * gkde.weights, axis=-1), + rtol=5e-14 + ) + + # ... and cdf + cdf = stats.multivariate_normal.cdf + lo, hi = [-1, -2], [0, 0] + lo_, hi_ = lo - gkde.dataset.T, hi - gkde.dataset.T + assert_allclose( + gkde.integrate_box(lo, hi, rng=rng), + np.sum(cdf(hi_, lower_limit=lo_, cov=gkde.covariance, rng=rng) * + gkde.weights, axis=-1), + rtol=5e-6 + ) + + # evaluate the density function for the kde for some points + x, y = np.mgrid[-7:7:500j, -7:7:500j] + grid_coords = np.vstack([x.ravel(), y.ravel()]) + kdepdf = gkde.evaluate(grid_coords) + kdepdf = kdepdf.reshape(500, 500) + + normpdf = stats.multivariate_normal.pdf(np.dstack([x, y]), + mean=mean, cov=covariance) + intervall = y.ravel()[1] - y.ravel()[0] + + assert_(np.sum((kdepdf - normpdf)**2) * (intervall**2) < 0.01) + + small = -1e100 + large = 1e100 + prob1 = gkde.integrate_box([small, mean[1]], [large, large], rng=rng) + prob2 = gkde.integrate_box([small, small], [large, mean[1]], rng=rng) + + assert_almost_equal(prob1, 0.5, decimal=1) + assert_almost_equal(prob2, 0.5, decimal=1) + assert_almost_equal(gkde.integrate_kde(gkde), + (kdepdf**2).sum()*(intervall**2), decimal=2) + assert_almost_equal(gkde.integrate_gaussian(mean, covariance), + (kdepdf*normpdf).sum()*(intervall**2), decimal=2) + + +def test_kde_bandwidth_method(): + def scotts_factor(kde_obj): + """Same as default, just check that it works.""" + return np.power(kde_obj.n, -1./(kde_obj.d+4)) + + rng = np.random.default_rng(8765678) + n_basesample = 50 + xn = rng.normal(0, 1, n_basesample) + + # Default + gkde = stats.gaussian_kde(xn) + # Supply a callable + gkde2 = stats.gaussian_kde(xn, bw_method=scotts_factor) + # Supply a scalar + gkde3 = stats.gaussian_kde(xn, bw_method=gkde.factor) + + xs = np.linspace(-7,7,51) + kdepdf = gkde.evaluate(xs) + kdepdf2 = gkde2.evaluate(xs) + assert_almost_equal(kdepdf, kdepdf2) + kdepdf3 = gkde3.evaluate(xs) + assert_almost_equal(kdepdf, kdepdf3) + + assert_raises(ValueError, stats.gaussian_kde, xn, bw_method='wrongstring') + + +def test_kde_bandwidth_method_weighted(): + def scotts_factor(kde_obj): + """Same as default, just check that it works.""" + return np.power(kde_obj.neff, -1./(kde_obj.d+4)) + + rng = np.random.default_rng(8765678) + n_basesample = 50 + xn = rng.normal(0, 1, n_basesample) + + # Default + gkde = stats.gaussian_kde(xn) + # Supply a callable + gkde2 = stats.gaussian_kde(xn, bw_method=scotts_factor) + # Supply a scalar + gkde3 = stats.gaussian_kde(xn, bw_method=gkde.factor) + + xs = np.linspace(-7,7,51) + kdepdf = gkde.evaluate(xs) + kdepdf2 = gkde2.evaluate(xs) + assert_almost_equal(kdepdf, kdepdf2) + kdepdf3 = gkde3.evaluate(xs) + assert_almost_equal(kdepdf, kdepdf3) + + assert_raises(ValueError, stats.gaussian_kde, xn, bw_method='wrongstring') + + +# Subclasses that should stay working (extracted from various sources). +# Unfortunately the earlier design of gaussian_kde made it necessary for users +# to create these kinds of subclasses, or call _compute_covariance() directly. + +class _kde_subclass1(stats.gaussian_kde): + def __init__(self, dataset): + self.dataset = np.atleast_2d(dataset) + self.d, self.n = self.dataset.shape + self.covariance_factor = self.scotts_factor + self._compute_covariance() + + +class _kde_subclass2(stats.gaussian_kde): + def __init__(self, dataset): + self.covariance_factor = self.scotts_factor + super().__init__(dataset) + + +class _kde_subclass4(stats.gaussian_kde): + def covariance_factor(self): + return 0.5 * self.silverman_factor() + + +def test_gaussian_kde_subclassing(): + x1 = np.array([-7, -5, 1, 4, 5], dtype=float) + xs = np.linspace(-10, 10, num=50) + + # gaussian_kde itself + kde = stats.gaussian_kde(x1) + ys = kde(xs) + + # subclass 1 + kde1 = _kde_subclass1(x1) + y1 = kde1(xs) + assert_array_almost_equal_nulp(ys, y1, nulp=10) + + # subclass 2 + kde2 = _kde_subclass2(x1) + y2 = kde2(xs) + assert_array_almost_equal_nulp(ys, y2, nulp=10) + + # subclass 3 was removed because we have no obligation to maintain support + # for user invocation of private methods + + # subclass 4 + kde4 = _kde_subclass4(x1) + y4 = kde4(x1) + y_expected = [0.06292987, 0.06346938, 0.05860291, 0.08657652, 0.07904017] + + assert_array_almost_equal(y_expected, y4, decimal=6) + + # Not a subclass, but check for use of _compute_covariance() + kde5 = kde + kde5.covariance_factor = lambda: kde.factor + kde5._compute_covariance() + y5 = kde5(xs) + assert_array_almost_equal_nulp(ys, y5, nulp=10) + + +def test_gaussian_kde_covariance_caching(): + x1 = np.array([-7, -5, 1, 4, 5], dtype=float) + xs = np.linspace(-10, 10, num=5) + # These expected values are from scipy 0.10, before some changes to + # gaussian_kde. They were not compared with any external reference. + y_expected = [0.02463386, 0.04689208, 0.05395444, 0.05337754, 0.01664475] + + # Set the bandwidth, then reset it to the default. + kde = stats.gaussian_kde(x1) + kde.set_bandwidth(bw_method=0.5) + kde.set_bandwidth(bw_method='scott') + y2 = kde(xs) + + assert_array_almost_equal(y_expected, y2, decimal=7) + + +def test_gaussian_kde_monkeypatch(): + """Ugly, but people may rely on this. See scipy pull request 123, + specifically the linked ML thread "Width of the Gaussian in stats.kde". + If it is necessary to break this later on, that is to be discussed on ML. + """ + x1 = np.array([-7, -5, 1, 4, 5], dtype=float) + xs = np.linspace(-10, 10, num=50) + + # The old monkeypatched version to get at Silverman's Rule. + kde = stats.gaussian_kde(x1) + kde.covariance_factor = kde.silverman_factor + kde._compute_covariance() + y1 = kde(xs) + + # The new saner version. + kde2 = stats.gaussian_kde(x1, bw_method='silverman') + y2 = kde2(xs) + + assert_array_almost_equal_nulp(y1, y2, nulp=10) + + +def test_kde_integer_input(): + """Regression test for #1181.""" + x1 = np.arange(5) + kde = stats.gaussian_kde(x1) + y_expected = [0.13480721, 0.18222869, 0.19514935, 0.18222869, 0.13480721] + assert_array_almost_equal(kde(x1), y_expected, decimal=6) + + +_ftypes = ['float32', 'float64', 'float96', 'float128', 'int32', 'int64'] + + +@pytest.mark.filterwarnings("ignore::DeprecationWarning") +@pytest.mark.parametrize("bw_type", _ftypes + ["scott", "silverman"]) +@pytest.mark.parametrize("dtype", _ftypes) +def test_kde_output_dtype(dtype, bw_type): + # Check whether the datatypes are available + dtype = getattr(np, dtype, None) + + if bw_type in ["scott", "silverman"]: + bw = bw_type + else: + bw_type = getattr(np, bw_type, None) + bw = bw_type(3) if bw_type else None + + if any(dt is None for dt in [dtype, bw]): + pytest.skip() + + weights = np.arange(5, dtype=dtype) + dataset = np.arange(5, dtype=dtype) + k = stats.gaussian_kde(dataset, bw_method=bw, weights=weights) + points = np.arange(5, dtype=dtype) + result = k(points) + # weights are always cast to float64 + assert result.dtype == np.result_type(dataset, points, np.float64(weights), + k.factor) + + +def test_pdf_logpdf_validation(): + rng = np.random.default_rng(64202298293133848336925499069837723291) + xn = rng.standard_normal((2, 10)) + gkde = stats.gaussian_kde(xn) + xs = rng.standard_normal((3, 10)) + + msg = "points have dimension 3, dataset has dimension 2" + with pytest.raises(ValueError, match=msg): + gkde.logpdf(xs) + + +def test_pdf_logpdf(): + rng = np.random.default_rng(1) + n_basesample = 50 + xn = rng.normal(0, 1, n_basesample) + + # Default + gkde = stats.gaussian_kde(xn) + + xs = np.linspace(-15, 12, 25) + pdf = gkde.evaluate(xs) + pdf2 = gkde.pdf(xs) + assert_almost_equal(pdf, pdf2, decimal=12) + + logpdf = np.log(pdf) + logpdf2 = gkde.logpdf(xs) + assert_almost_equal(logpdf, logpdf2, decimal=12) + + # There are more points than data + gkde = stats.gaussian_kde(xs) + pdf = np.log(gkde.evaluate(xn)) + pdf2 = gkde.logpdf(xn) + assert_almost_equal(pdf, pdf2, decimal=12) + + +def test_pdf_logpdf_weighted(): + rng = np.random.default_rng(1) + n_basesample = 50 + xn = rng.normal(0, 1, n_basesample) + wn = rng.random(n_basesample) + + # Default + gkde = stats.gaussian_kde(xn, weights=wn) + + xs = np.linspace(-15, 12, 25) + pdf = gkde.evaluate(xs) + pdf2 = gkde.pdf(xs) + assert_almost_equal(pdf, pdf2, decimal=12) + + logpdf = np.log(pdf) + logpdf2 = gkde.logpdf(xs) + assert_almost_equal(logpdf, logpdf2, decimal=12) + + # There are more points than data + rng = np.random.default_rng(4531935345) + gkde = stats.gaussian_kde(xs, weights=rng.random(len(xs))) + pdf = np.log(gkde.evaluate(xn)) + pdf2 = gkde.logpdf(xn) + assert_almost_equal(pdf, pdf2, decimal=12) + + +def test_marginal_1_axis(): + rng = np.random.default_rng(6111799263660870475) + n_data = 50 + n_dim = 10 + dataset = rng.normal(size=(n_dim, n_data)) + points = rng.normal(size=(n_dim, 3)) + + dimensions = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9]) # dimensions to keep + + kde = stats.gaussian_kde(dataset) + marginal = kde.marginal(dimensions) + pdf = marginal.pdf(points[dimensions]) + + def marginal_pdf_single(point): + def f(x): + x = np.concatenate(([x], point[dimensions])) + return kde.pdf(x)[0] + return integrate.quad(f, -np.inf, np.inf)[0] + + def marginal_pdf(points): + return np.apply_along_axis(marginal_pdf_single, axis=0, arr=points) + + ref = marginal_pdf(points) + + assert_allclose(pdf, ref, rtol=1e-6) + + +@pytest.mark.xslow +def test_marginal_2_axis(): + rng = np.random.default_rng(6111799263660870475) + n_data = 30 + n_dim = 4 + dataset = rng.normal(size=(n_dim, n_data)) + points = rng.normal(size=(n_dim, 3)) + + dimensions = np.array([1, 3]) # dimensions to keep + + kde = stats.gaussian_kde(dataset) + marginal = kde.marginal(dimensions) + pdf = marginal.pdf(points[dimensions]) + + def marginal_pdf(points): + def marginal_pdf_single(point): + def f(y, x): + w, z = point[dimensions] + x = np.array([x, w, y, z]) + return kde.pdf(x)[0] + return integrate.dblquad(f, -np.inf, np.inf, -np.inf, np.inf)[0] + + return np.apply_along_axis(marginal_pdf_single, axis=0, arr=points) + + ref = marginal_pdf(points) + + assert_allclose(pdf, ref, rtol=1e-6) + + +def test_marginal_iv(): + # test input validation + rng = np.random.default_rng(6111799263660870475) + n_data = 30 + n_dim = 4 + dataset = rng.normal(size=(n_dim, n_data)) + points = rng.normal(size=(n_dim, 3)) + + kde = stats.gaussian_kde(dataset) + + # check that positive and negative indices are equivalent + dimensions1 = [-1, 1] + marginal1 = kde.marginal(dimensions1) + pdf1 = marginal1.pdf(points[dimensions1]) + + dimensions2 = [3, -3] + marginal2 = kde.marginal(dimensions2) + pdf2 = marginal2.pdf(points[dimensions2]) + + assert_equal(pdf1, pdf2) + + # IV for non-integer dimensions + message = "Elements of `dimensions` must be integers..." + with pytest.raises(ValueError, match=message): + kde.marginal([1, 2.5]) + + # IV for uniqueness + message = "All elements of `dimensions` must be unique." + with pytest.raises(ValueError, match=message): + kde.marginal([1, 2, 2]) + + # IV for non-integer dimensions + message = (r"Dimensions \[-5 6\] are invalid for a distribution in 4...") + with pytest.raises(ValueError, match=message): + kde.marginal([1, -5, 6]) + + +@pytest.mark.xslow +def test_logpdf_overflow(): + # regression test for gh-12988; testing against linalg instability for + # very high dimensionality kde + rng = np.random.default_rng(1) + n_dimensions = 2500 + n_samples = 5000 + xn = np.array([rng.normal(0, 1, n_samples) + (n) for n in range( + 0, n_dimensions)]) + + # Default + gkde = stats.gaussian_kde(xn) + + logpdf = gkde.logpdf(np.arange(0, n_dimensions)) + np.testing.assert_equal(np.isneginf(logpdf[0]), False) + np.testing.assert_equal(np.isnan(logpdf[0]), False) + + +def test_weights_intact(): + # regression test for gh-9709: weights are not modified + rng = np.random.default_rng(12345) + vals = rng.lognormal(size=100) + weights = rng.choice([1.0, 10.0, 100], size=vals.size) + orig_weights = weights.copy() + + stats.gaussian_kde(np.log10(vals), weights=weights) + assert_allclose(weights, orig_weights, atol=1e-14, rtol=1e-14) + + +def test_weights_integer(): + # integer weights are OK, cf gh-9709 (comment) + values = [0.2, 13.5, 21.0, 75.0, 99.0] + weights = [1, 2, 4, 8, 16] # a list of integers + pdf_i = stats.gaussian_kde(values, weights=weights) + pdf_f = stats.gaussian_kde(values, weights=np.float64(weights)) + + xn = [0.3, 11, 88] + assert_allclose(pdf_i.evaluate(xn), + pdf_f.evaluate(xn), atol=1e-14, rtol=1e-14) + + +def test_seed(): + # Test the seed option of the resample method + def test_seed_sub(gkde_trail): + n_sample = 200 + # The results should be different without using seed + samp1 = gkde_trail.resample(n_sample) + samp2 = gkde_trail.resample(n_sample) + assert_raises( + AssertionError, assert_allclose, samp1, samp2, atol=1e-13 + ) + # Use integer seed + seed = 831 + samp1 = gkde_trail.resample(n_sample, seed=seed) + samp2 = gkde_trail.resample(n_sample, seed=seed) + assert_allclose(samp1, samp2, atol=1e-13) + # Use RandomState + rstate1 = np.random.RandomState(seed=138) + samp1 = gkde_trail.resample(n_sample, seed=rstate1) + rstate2 = np.random.RandomState(seed=138) + samp2 = gkde_trail.resample(n_sample, seed=rstate2) + assert_allclose(samp1, samp2, atol=1e-13) + + # check that np.random.Generator can be used (numpy >= 1.17) + if hasattr(np.random, 'default_rng'): + # obtain a np.random.Generator object + rng = np.random.default_rng(1234) + gkde_trail.resample(n_sample, seed=rng) + + rng = np.random.default_rng(8765678) + n_basesample = 500 + wn = rng.random(n_basesample) + # Test 1D case + xn_1d = rng.normal(0, 1, n_basesample) + + gkde_1d = stats.gaussian_kde(xn_1d) + test_seed_sub(gkde_1d) + gkde_1d_weighted = stats.gaussian_kde(xn_1d, weights=wn) + test_seed_sub(gkde_1d_weighted) + + # Test 2D case + mean = np.array([1.0, 3.0]) + covariance = np.array([[1.0, 2.0], [2.0, 6.0]]) + xn_2d = rng.multivariate_normal(mean, covariance, size=n_basesample).T + + gkde_2d = stats.gaussian_kde(xn_2d) + test_seed_sub(gkde_2d) + gkde_2d_weighted = stats.gaussian_kde(xn_2d, weights=wn) + test_seed_sub(gkde_2d_weighted) + + +def test_singular_data_covariance_gh10205(): + # When the data lie in a lower-dimensional subspace and this causes + # and exception, check that the error message is informative. + rng = np.random.default_rng(2321583144339784787) + mu = np.array([1, 10, 20]) + sigma = np.array([[4, 10, 0], [10, 25, 0], [0, 0, 100]]) + data = rng.multivariate_normal(mu, sigma, 1000) + try: # doesn't raise any error on some platforms, and that's OK + stats.gaussian_kde(data.T) + except linalg.LinAlgError: + msg = "The data appears to lie in a lower-dimensional subspace..." + with assert_raises(linalg.LinAlgError, match=msg): + stats.gaussian_kde(data.T) + + +def test_fewer_points_than_dimensions_gh17436(): + # When the number of points is fewer than the number of dimensions, the + # the covariance matrix would be singular, and the exception tested in + # test_singular_data_covariance_gh10205 would occur. However, sometimes + # this occurs when the user passes in the transpose of what `gaussian_kde` + # expects. This can result in a huge covariance matrix, so bail early. + rng = np.random.default_rng(2046127537594925772) + rvs = rng.multivariate_normal(np.zeros(3), np.eye(3), size=5) + message = "Number of dimensions is greater than number of samples..." + with pytest.raises(ValueError, match=message): + stats.gaussian_kde(rvs) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/test_marray.py b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_marray.py new file mode 100644 index 0000000000000000000000000000000000000000..cbae7321f6acdf1222655ebac391dc874821a66f --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_marray.py @@ -0,0 +1,613 @@ +import numpy as np +import pytest +from scipy import stats + +from scipy._lib._array_api import xp_assert_close, xp_assert_equal, _count_nonmasked +from scipy._lib._array_api import make_xp_pytest_param, make_xp_test_case +from scipy._lib._array_api import SCIPY_ARRAY_API, is_torch +from scipy.stats._stats_py import _xp_mean, _xp_var + + +marray = pytest.importorskip('marray') +pytestmark = [ + pytest.mark.skipif( + not SCIPY_ARRAY_API, + reason=( + "special function dispatch to marray required for these tests" + " is hidden behind SCIPY_ARRAY_API flag." + ), + ), +] + +skip_backend = pytest.mark.skip_xp_backends + +def get_arrays(n_arrays, *, dtype='float64', xp=np, shape=(7, 8), all_unique=True, + seed=84912165484321): + mxp = marray._get_namespace(xp) + rng = np.random.default_rng(seed) + + datas, masks = [], [] + for i in range(n_arrays): + data = (rng.random(size=shape) if all_unique + else rng.integers(np.min(shape) // 2, size=shape)) + if dtype.startswith('complex'): + data = 10*data * 10j*rng.standard_normal(size=shape) + data = data.astype(dtype) + datas.append(data) + mask = rng.random(size=shape) > 0.75 + masks.append(mask) + + marrays = [] + nan_arrays = [] + for array, mask in zip(datas, masks): + marrays.append(mxp.asarray(array, mask=mask)) + nan_array = array.copy() + nan_array[mask] = xp.nan + nan_arrays.append(nan_array) + + return mxp, marrays, nan_arrays + + +@skip_backend('jax.numpy', reason="JAX doesn't allow item assignment.") +@pytest.mark.parametrize('fun, kwargs', [ + make_xp_pytest_param(stats.gmean, {}), + make_xp_pytest_param(stats.hmean, {}), + make_xp_pytest_param(stats.pmean, {'p': 2}), + make_xp_pytest_param(stats.expectile, {'alpha': 0.4}), + make_xp_pytest_param(_xp_mean, {}), +]) +@pytest.mark.parametrize('weighted', [False, True]) +@pytest.mark.parametrize('axis', [0, 1, None]) +def test_one_sample_weighted_reducing(fun, kwargs, weighted, axis, xp): + mxp, marrays, narrays = get_arrays(2, xp=xp) + mweights = marrays[1] if weighted else None + nweights = narrays[1] if weighted else None + res = fun(marrays[0], weights=mweights, axis=axis, **kwargs) + ref = fun(narrays[0], weights=nweights, nan_policy='omit', axis=axis, **kwargs) + xp_assert_close(res.data, xp.asarray(ref)) + + +@skip_backend('dask.array', reason='Arrays need `device` attribute: dask/dask#11711') +@skip_backend('jax.numpy', reason="JAX doesn't allow item assignment.") +@pytest.mark.parametrize('fun, kwargs', + [make_xp_pytest_param(stats.moment, {'order': 2}), + make_xp_pytest_param(stats.moment, {'center': 0.5}), + make_xp_pytest_param(stats.skew, {}), + make_xp_pytest_param(stats.skew, {'bias': False}), + make_xp_pytest_param(stats.kurtosis, {}), + make_xp_pytest_param(stats.kurtosis, {'fisher': False, 'bias': False}), + make_xp_pytest_param(stats.sem, {}), + make_xp_pytest_param(stats.sem, {'ddof': 2}), + make_xp_pytest_param(stats.kstat, {'n': 1}), + make_xp_pytest_param(stats.kstat, {'n': 2}), + make_xp_pytest_param(stats.kstat, {'n': 3}), + make_xp_pytest_param(stats.kstat, {'n': 4}), + make_xp_pytest_param(stats.kstatvar, {'n': 1}), + make_xp_pytest_param(stats.kstatvar, {'n': 2}), + make_xp_pytest_param(stats.circmean, {}), + make_xp_pytest_param(stats.circmean, {'low': 0, 'high': 1}), + make_xp_pytest_param(stats.circvar, {}), + make_xp_pytest_param(stats.circvar, {'low': 0, 'high': 1}), + make_xp_pytest_param(stats.circstd, {}), + make_xp_pytest_param(stats.circstd, {'low': 0, 'high': 1, 'normalize':True}), + make_xp_pytest_param(stats.gstd, {}), + make_xp_pytest_param(stats.gstd, {'ddof': 2}), + make_xp_pytest_param(_xp_var, {}), + make_xp_pytest_param(_xp_var, {'correction': 1, 'keepdims': True}), + make_xp_pytest_param(stats.variation, {}), + make_xp_pytest_param(stats.variation, {'ddof': 1}), + make_xp_pytest_param(stats.tmean, dict(limits=(0.1, 0.9))), + make_xp_pytest_param(stats.tvar, dict(limits=(0.1, 0.9), inclusive=(False, True))), + make_xp_pytest_param(stats.tstd, dict(limits=(0.1, 0.9), inclusive=(True, False))), + make_xp_pytest_param(stats.tsem, dict(limits=(0.1, 0.9), inclusive=(False,)*2)), + make_xp_pytest_param(stats.tmin, dict(lowerlimit=0.5, inclusive=True)), + make_xp_pytest_param(stats.tmax, dict(upperlimit=0.5, inclusive=False)), + make_xp_pytest_param(stats.iqr, {'interpolation': 'nearest'}), + make_xp_pytest_param(stats.iqr, {'rng': (10, 90), 'scale': 'normal'}), + make_xp_pytest_param(stats.median_abs_deviation, {}), + make_xp_pytest_param(stats.median_abs_deviation, {'scale': 'normal'}), + make_xp_pytest_param(stats.trim_mean, {'proportiontocut': 0.1}), + ]) +@pytest.mark.parametrize('axis', [0, 1, None]) +def test_one_sample_reducing(fun, kwargs, axis, xp): + mxp, marrays, narrays = get_arrays(1, xp=xp) + kwargs = dict(axis=axis) | kwargs + res = fun(marrays[0], **kwargs) + ref = fun(narrays[0], nan_policy='omit', **kwargs) + xp_assert_close(res.data, xp.asarray(ref)) + + +@make_xp_test_case(stats.mode) +@pytest.mark.parametrize('dtype', ['int32', 'float64']) +@pytest.mark.parametrize('shape', [10, (7, 8)]) +@pytest.mark.parametrize('axis', [0, 1, None]) +def test_mode(dtype, shape, axis, xp): + mxp, marrays, narrays = get_arrays(1, shape=shape, all_unique=False, xp=xp) + res = stats.mode(mxp.astype(marrays[0], getattr(mxp, dtype))) + ref = stats.mode(*narrays, nan_policy='omit') + xp_assert_close(res.mode.data, xp.asarray(ref.mode.astype(dtype))) + xp_assert_close(res.count.data, xp.asarray(ref.count)) + + +@make_xp_test_case(stats.describe) +@skip_backend('dask.array', reason='Arrays need `device` attribute: dask/dask#11711') +@skip_backend('jax.numpy', reason="JAX doesn't allow item assignment.") +@pytest.mark.parametrize('axis', [0, 1, None]) +@pytest.mark.parametrize('kwargs', [{}, {'bias': False}]) +def test_describe(axis, kwargs, xp): + mxp, marrays, narrays = get_arrays(1, xp=xp) + kwargs = dict(axis=axis) | kwargs + res = stats.describe(marrays[0], **kwargs) + ref = stats.describe(narrays[0], nan_policy='omit', **kwargs) + xp_assert_close(res.nobs.data, xp.asarray(ref.nobs)) + xp_assert_close(res.minmax[0].data, xp.asarray(ref.minmax[0].data)) + xp_assert_close(res.minmax[1].data, xp.asarray(ref.minmax[1].data)) + # copy reference arrays due to torch complaint about non-writeable buffer + xp_assert_close(res.variance.data, + xp.asarray(np.asarray(ref.variance.data, copy=True))) + xp_assert_close(res.skewness.data, + xp.asarray(np.asarray(ref.skewness.data, copy=True))) + xp_assert_close(res.kurtosis.data, + xp.asarray(np.asarray(ref.kurtosis.data, copy=True))) + + +@skip_backend('dask.array', reason='shape (nan,) in _xp_var when ddof=1') +@skip_backend('jax.numpy', reason="JAX doesn't allow item assignment.") +@pytest.mark.parametrize('fun', [make_xp_pytest_param(stats.zscore), + make_xp_pytest_param(stats.gzscore), + make_xp_pytest_param(stats.zmap)]) +@pytest.mark.parametrize('ddof', [0, 1]) +@pytest.mark.parametrize('axis', [0, 1, None]) +def test_one_sample_non_reducing(fun, ddof, axis, xp): + mxp, marrays, narrays = (get_arrays(2, xp=xp) if fun == stats.zmap + else get_arrays(1, xp=xp)) + res = fun(*marrays, ddof=ddof, axis=axis) + ref = xp.asarray(fun(*narrays, ddof=ddof, nan_policy='omit', axis=axis)) + xp_assert_close(res.data[~res.mask], ref[~xp.isnan(ref)]) + xp_assert_equal(res.mask, marrays[0].mask) + + +@skip_backend('dask.array', reason='Arrays need `device` attribute: dask/dask#11711') +@skip_backend('jax.numpy', reason="JAX doesn't allow item assignment.") +@pytest.mark.parametrize('f', [make_xp_pytest_param(stats.ttest_1samp), + make_xp_pytest_param(stats.ttest_rel), + make_xp_pytest_param(stats.ttest_ind)]) +@pytest.mark.parametrize('alternative', ['less', 'greater', 'two-sided']) +@pytest.mark.parametrize('axis', [0, 1, None]) +def test_ttests(f, alternative, axis, xp): + f_name = f.__name__ + mxp, marrays, narrays = get_arrays(2, xp=xp) + if f_name == 'ttest_1samp': + marrays[1] = mxp.mean(marrays[1], axis=axis, keepdims=axis is not None) + narrays[1] = np.nanmean(narrays[1], axis=axis, keepdims=axis is not None) + res = f(*marrays, alternative=alternative, axis=axis) + ref = f(*narrays, alternative=alternative, nan_policy='omit', axis=axis) + xp_assert_close(res.statistic.data, xp.asarray(ref.statistic)) + xp_assert_close(res.pvalue.data, xp.asarray(ref.pvalue)) + res_ci = res.confidence_interval() + ref_ci = ref.confidence_interval() + xp_assert_close(res_ci.low.data, xp.asarray(ref_ci.low)) + xp_assert_close(res_ci.high.data, xp.asarray(ref_ci.high)) + + +@skip_backend('dask.array', reason='Arrays need `device` attribute: dask/dask#11711') +@skip_backend('jax.numpy', reason="JAX doesn't allow item assignment.") +@pytest.mark.filterwarnings("ignore::scipy.stats._axis_nan_policy.SmallSampleWarning") +@pytest.mark.parametrize('f, args', [ + make_xp_pytest_param(stats.skewtest, tuple()), + make_xp_pytest_param(stats.kurtosistest, tuple()), + make_xp_pytest_param(stats.normaltest, tuple()), + make_xp_pytest_param(stats.jarque_bera, tuple()), + make_xp_pytest_param(stats.cramervonmises, (stats.norm.cdf,)), # type:ignore[attr-defined] +]) +@pytest.mark.parametrize('alternative', ['less', 'greater', 'two-sided']) +@pytest.mark.parametrize('axis', [0, 1, None]) +def test_goodness_of_fit(f, args, alternative, axis, xp): + mxp, marrays, narrays = get_arrays(1, xp=xp, shape=(10, 11)) + + if f in {stats.skewtest, stats.kurtosistest}: + kwds = {'alternative': alternative} + else: + if alternative != 'greater': + pytest.skip(f'str({f.__name__} does not support multiple alternatives.') + kwds = {} + + res = f(*marrays, *args, **kwds, axis=axis) + ref = f(*narrays, *args, **kwds, nan_policy='omit', axis=axis) + + xp_assert_close(res.statistic.data, xp.asarray(ref.statistic)) + xp_assert_close(res.pvalue.data, xp.asarray(ref.pvalue), atol=1e-15) + + +@skip_backend('dask.array', reason='Arrays need `device` attribute: dask/dask#11711') +@skip_backend('jax.numpy', reason="JAX doesn't allow item assignment.") +@pytest.mark.parametrize('f, lambda_', [ + make_xp_pytest_param(stats.chisquare, {}), + make_xp_pytest_param(stats.power_divergence, {'lambda_': 'pearson'}), + make_xp_pytest_param(stats.power_divergence, {'lambda_': 'log-likelihood'}), + make_xp_pytest_param(stats.power_divergence, {'lambda_': 'freeman-tukey'}), + make_xp_pytest_param(stats.power_divergence, + {'lambda_': 'mod-log-likelihood'}), + make_xp_pytest_param(stats.power_divergence, {'lambda_': 'neyman'}), + make_xp_pytest_param(stats.power_divergence, {'lambda_': 'cressie-read'}), +]) +@pytest.mark.parametrize('ddof', [0, 1]) +@pytest.mark.parametrize('axis', [0, 1, None]) +def test_power_divergence_chisquare(f, lambda_, ddof, axis, xp): + mxp, marrays, narrays = get_arrays(2, xp=xp, shape=(5, 6)) + + kwargs = dict(axis=axis, ddof=ddof) + + # test 1-arg + res = f(marrays[0], **lambda_, **kwargs) + ref = stats.power_divergence(narrays[0], nan_policy='omit', **lambda_, **kwargs) + + xp_assert_close(res.statistic.data, xp.asarray(ref[0])) + xp_assert_close(res.pvalue.data, xp.asarray(ref[1])) + + # test 2-arg + common_mask = np.isnan(narrays[0]) | np.isnan(narrays[1]) + normalize = (np.nansum(narrays[1] * ~common_mask, axis=axis, keepdims=True) + / np.nansum(narrays[0] * ~common_mask, axis=axis, keepdims=True)) + marrays[0] *= xp.asarray(normalize) + narrays[0] *= normalize + + res = f(*marrays, **lambda_, **kwargs) + ref = stats.power_divergence(*narrays, nan_policy='omit', **lambda_, **kwargs) + + xp_assert_close(res.statistic.data, xp.asarray(ref[0])) + xp_assert_close(res.pvalue.data, xp.asarray(ref[1])) + + +@make_xp_test_case(stats.combine_pvalues) +@skip_backend('jax.numpy', reason="JAX doesn't allow item assignment.") +@pytest.mark.parametrize('method', ['fisher', 'pearson', 'mudholkar_george', + 'tippett', 'stouffer']) +@pytest.mark.parametrize('axis', [0, 1, None]) +def test_combine_pvalues(method, axis, xp): + mxp, marrays, narrays = get_arrays(2, xp=xp, shape=(10, 11)) + + kwargs = dict(method=method, axis=axis) + res = stats.combine_pvalues(marrays[0], **kwargs) + ref = stats.combine_pvalues(narrays[0], nan_policy='omit', **kwargs) + + xp_assert_close(res.statistic.data, xp.asarray(ref.statistic)) + xp_assert_close(res.pvalue.data, xp.asarray(ref.pvalue)) + + if method != 'stouffer': + return + + # test method='stouffer' with weights + res = stats.combine_pvalues(marrays[0], weights=marrays[1], **kwargs) + ref = stats.combine_pvalues(narrays[0], weights=narrays[1], + nan_policy='omit', **kwargs) + + xp_assert_close(res.statistic.data, xp.asarray(ref.statistic)) + xp_assert_close(res.pvalue.data, xp.asarray(ref.pvalue)) + + +@make_xp_test_case(stats.ttest_ind_from_stats) +@skip_backend('jax.numpy', reason="JAX doesn't allow item assignment.") +@pytest.mark.parametrize("alternative", ['less', 'greater', 'two-sided']) +@pytest.mark.parametrize("equal_var", [False, True]) +def test_ttest_ind_from_stats(alternative, equal_var, xp): + shape = (10, 11) + mxp, marrays, narrays = get_arrays(6, xp=xp, shape=shape) + mask = np.sum(np.stack([np.isnan(arg) for arg in narrays]), axis=0).astype(bool) + narrays = [arg[~mask] for arg in narrays] + marrays[2], marrays[5] = marrays[2] * 100, marrays[5] * 100 + narrays[2], narrays[5] = narrays[2] * 100, narrays[5] * 100 + + kwargs = dict(alternative=alternative, equal_var=equal_var) + res = stats.ttest_ind_from_stats(*marrays, **kwargs) + ref = stats.ttest_ind_from_stats(*narrays, **kwargs) + + mask = xp.asarray(mask) + assert xp.any(mask) and xp.any(~mask) + xp_assert_close(res.statistic.data[~mask], xp.asarray(ref.statistic)) + xp_assert_close(res.pvalue.data[~mask], xp.asarray(ref.pvalue)) + xp_assert_close(res.statistic.mask, mask) + xp_assert_close(res.pvalue.mask, mask) + assert res.statistic.shape == shape + assert res.pvalue.shape == shape + + +def test_length_nonmasked_marray_iterable_axis_raises(): + xp = marray._get_namespace(np) + + data = [[1.0, 2.0], [3.0, 4.0]] + mask = [[False, False], [True, False]] + marr = xp.asarray(data, mask=mask) + + # Axis tuples are not currently supported for MArray input. + # This test can be removed after support is added. + with pytest.raises(NotImplementedError, + match="`axis` must be an integer or None for use with `MArray`"): + _count_nonmasked(marr, axis=(0, 1), xp=xp) + + +@make_xp_test_case(stats.directional_stats) +@skip_backend('jax.numpy', reason="JAX doesn't allow item assignment.") +@pytest.mark.filterwarnings("ignore::RuntimeWarning") # mdhaber/marray#120 +@pytest.mark.parametrize('normalize', [False, True]) +@pytest.mark.parametrize('axis', [0, 1]) +def test_directional_stats(normalize, axis, xp): + mxp, marrays, narrays = get_arrays(1, shape=(19, 20, 3), xp=xp) + res = stats.directional_stats(*marrays, normalize=normalize, axis=axis) + + x, = narrays + if axis == 0: + x = np.swapaxes(x, 0, 1) + + for i in range(x.shape[0]): + xi = x[i] + xi = xi[~np.any(np.isnan(xi), axis=-1), :] + ref = stats.directional_stats(xi, normalize=normalize) + xp_assert_close(res.mean_direction.data[i, ...], + xp.asarray(ref.mean_direction)) + xp_assert_close(res.mean_resultant_length.data[i], + xp.asarray(ref.mean_resultant_length)[()]) + assert not xp.any(res.mean_direction.mask) + assert not xp.any(res.mean_resultant_length.mask) + + +@make_xp_test_case(stats.wilcoxon) +@skip_backend('jax.numpy', reason="JAX doesn't allow item assignment.") +@pytest.mark.parametrize('n_samples', [1, 2]) +@pytest.mark.parametrize('zero_method', ['zsplit']) # TODO: add 'wilcox', 'pratt' +@pytest.mark.parametrize('correction', [False, True]) +@pytest.mark.parametrize('alternative', ['less', 'greater', 'two-sided']) +@pytest.mark.parametrize('method', ['asymptotic']) # TODO: add 'exact', 'auto' +@pytest.mark.parametrize('axis', [0, 1, None]) +def test_wilcoxon(n_samples, zero_method, correction, alternative, method, axis, xp): + mxp, marrays, narrays = get_arrays(n_samples, xp=xp, seed=84912165484322) + kwargs = dict(zero_method=zero_method, correction=correction, + alternative=alternative, method=method) + res = stats.wilcoxon(*marrays, axis=axis, **kwargs) + ref = stats.wilcoxon(*narrays, nan_policy='omit', axis=axis, **kwargs) + xp_assert_close(res.statistic.data, xp.asarray(ref.statistic)) + xp_assert_close(res.pvalue.data, xp.asarray(ref.pvalue)) + + +@make_xp_test_case(stats.mannwhitneyu) +@skip_backend('jax.numpy', reason="JAX doesn't allow item assignment.") +@pytest.mark.parametrize('use_continuity', [False, True]) +@pytest.mark.parametrize('alternative', ['less', 'greater', 'two-sided']) +@pytest.mark.parametrize('method', ['asymptotic']) # TODO: add 'exact', 'auto' +@pytest.mark.parametrize('axis', [0, 1, None]) +def test_mannwhitneyu(use_continuity, alternative, method, axis, xp): + mxp, marrays, narrays = get_arrays(2, xp=xp, seed=84912165484322) + kwargs = dict(use_continuity=use_continuity, alternative=alternative, method=method) + res = stats.mannwhitneyu(*marrays, axis=axis, **kwargs) + ref = stats.mannwhitneyu(*narrays, nan_policy='omit', axis=axis, **kwargs) + xp_assert_close(res.statistic.data, xp.asarray(ref.statistic)) + xp_assert_close(res.pvalue.data, xp.asarray(ref.pvalue)) + + +@skip_backend('jax.numpy', reason="JAX doesn't allow item assignment.") +@pytest.mark.parametrize('fun', [make_xp_pytest_param(stats.ks_1samp), + make_xp_pytest_param(stats.kstest)]) +@pytest.mark.parametrize('method', ['exact', 'approx', 'asymp']) # auto == exact +@pytest.mark.parametrize('alternative', ['less', 'greater', 'two-sided']) +@pytest.mark.parametrize('axis', [0, 1, None]) +def test_ks_1samp(fun, method, alternative, axis, xp): + mxp, marrays, narrays = get_arrays(1, xp=xp, seed=84912165484322) + kwargs = dict(method=method, alternative=alternative, axis=axis) + res = fun(*marrays, stats.norm.cdf, **kwargs) + ref = stats.ks_1samp(*narrays, stats.norm.cdf, nan_policy='omit', **kwargs) + xp_assert_close(res.statistic.data, xp.asarray(ref.statistic)) + xp_assert_close(res.pvalue.data, xp.asarray(ref.pvalue)) + xp_assert_equal(res.statistic_location.data, xp.asarray(ref.statistic_location)) + xp_assert_equal(res.statistic_sign.data, + xp.asarray(ref.statistic_sign, dtype=xp.int8)) + + +@skip_backend('jax.numpy', reason="JAX doesn't allow item assignment.") +@pytest.mark.parametrize('fun, kwargs', [ + make_xp_pytest_param(stats.brunnermunzel, {}), + make_xp_pytest_param(stats.brunnermunzel, {'distribution': 'normal'}), + make_xp_pytest_param(stats.cramervonmises_2samp, {'method': 'exact'}), + # make_xp_pytest_param(stats.cramervonmises_2samp, {}), # TODO: add methods +]) +@pytest.mark.parametrize('axis', [0, 1, None]) +def test_two_sample_tests(fun, kwargs, axis, xp): + if fun == stats.cramervonmises_2samp and axis is None: + pytest.skip("Sample too large for exact method.") + mxp, marrays, narrays = get_arrays(2, xp=xp, seed=84912165484322) + res = fun(*marrays, axis=axis, **kwargs) + ref = fun(*narrays, nan_policy='omit', axis=axis, **kwargs) + xp_assert_close(res.statistic.data, xp.asarray(ref.statistic)) + xp_assert_close(res.pvalue.data, xp.asarray(ref.pvalue)) + + +@make_xp_test_case(stats.ks_2samp) +@skip_backend('jax.numpy', reason="JAX doesn't allow item assignment.") +@pytest.mark.parametrize('fun', [make_xp_pytest_param(stats.ks_2samp), + make_xp_pytest_param(stats.kstest)]) +@pytest.mark.parametrize('method', ['exact', 'asymp', 'auto']) # auto == exact +@pytest.mark.parametrize('alternative', ['less', 'greater', 'two-sided']) +@pytest.mark.parametrize('axis', [0, 1, None]) +def test_ks_2samp(fun, method, alternative, axis, xp): + mxp, marrays, narrays = get_arrays(2, xp=xp, seed=84912165484322) + kwargs = dict(method=method, alternative=alternative, axis=axis) + res = fun(*marrays, **kwargs) + ref = stats.ks_2samp(*narrays, nan_policy='omit', **kwargs) + xp_assert_close(res.statistic.data, xp.asarray(ref.statistic)) + xp_assert_close(res.pvalue.data, xp.asarray(ref.pvalue)) + # with this random data, there often multiple locations where the statistic assumes + # the most extreme value, so we can't expect these to always match + # xp_assert_equal(res.statistic_location.data, xp.asarray(ref.statistic_location)) + xp_assert_equal(res.statistic_sign.data, + xp.asarray(ref.statistic_sign, dtype=xp.int8)) + + +@make_xp_test_case(stats.kendalltau) +class TestKendallTau: + @pytest.mark.parametrize('method', ['asymptotic', 'exact']) + @pytest.mark.parametrize('variant', ['b', 'c']) + @pytest.mark.parametrize('alternative', ['less', 'greater', 'two-sided']) + @pytest.mark.parametrize('axis', [0, 1, None]) + def test_omit_masked_elements(self, method, variant, alternative, axis, xp): + mxp, marrays, narrays = get_arrays(2, shape=(17, 18), + xp=xp, seed=84912165484322) + kwargs = dict(method=method, variant=variant, alternative=alternative) + res = stats.kendalltau(*marrays, **kwargs, axis=axis) + ref = stats.kendalltau(*narrays, **kwargs, nan_policy='omit', axis=axis) + xp_assert_close(res.statistic.data, xp.asarray(ref.statistic)) + xp_assert_close(res.pvalue.data, xp.asarray(ref.pvalue)) + + @pytest.mark.parametrize('nan_policy, message', [ + ('propagate', "`nan_policy='propagate'` is incompatible with MArray input."), + ('raise', 'The input contains nan values.') + ]) + def test_input_validation(self, nan_policy, message, xp): + mxp = marray._get_namespace(xp) + x = mxp.asarray([1, 2, 3, xp.nan]) + with pytest.raises(ValueError, match=message): + stats.kendalltau(x, x, nan_policy=nan_policy) + + +@skip_backend('dask.array', reason='Arrays need `device` attribute: dask/dask#11711') +@skip_backend('jax.numpy', reason="JAX doesn't allow item assignment.") +@pytest.mark.parametrize('fun, kwargs', [ + make_xp_pytest_param(stats.bartlett, {}), + make_xp_pytest_param(stats.alexandergovern, {}), + make_xp_pytest_param(stats.levene, {'center': 'median'}), + make_xp_pytest_param(stats.levene, {'center': 'mean'}), + make_xp_pytest_param(stats.levene, {'center': 'trimmed'}), + make_xp_pytest_param(stats.f_oneway, {'equal_var': True}), + make_xp_pytest_param(stats.f_oneway, {'equal_var': False}), + make_xp_pytest_param(stats.kruskal, {}), + make_xp_pytest_param(stats.fligner, {'center': 'median'}), + make_xp_pytest_param(stats.fligner, {'center': 'mean'}), + make_xp_pytest_param(stats.fligner, {'center': 'trimmed'}), +]) +@pytest.mark.parametrize('axis', [0, 1, None]) +def test_k_sample_tests(fun, kwargs, axis, xp): + mxp, marrays, narrays = get_arrays(3, xp=xp) + res = fun(*marrays, axis=axis, **kwargs) + ref = fun(*narrays, nan_policy='omit', axis=axis, **kwargs) + xp_assert_close(res.statistic.data, xp.asarray(ref.statistic)) + xp_assert_close(res.pvalue.data, xp.asarray(ref.pvalue)) + + +@skip_backend('jax.numpy', reason="JAX currently incompatible with marray") +@pytest.mark.parametrize('fun, kwargs', [ + make_xp_pytest_param(stats.friedmanchisquare, {}), +]) +@pytest.mark.parametrize('axis', [0, 1, None]) +def test_k_sample_paired_tests(fun, kwargs, axis, xp): + mxp, marrays, narrays = get_arrays(3, shape=(8, 9), xp=xp) + res = fun(*marrays, axis=axis, **kwargs) + ref = fun(*narrays, nan_policy='omit', axis=axis, **kwargs) + xp_assert_close(res.statistic.data, xp.asarray(ref.statistic)) + xp_assert_close(res.pvalue.data, xp.asarray(ref.pvalue)) + + +@skip_backend('dask.array', reason='Arrays need `device` attribute: dask/dask#11711') +@skip_backend('jax.numpy', reason="JAX doesn't allow item assignment.") +@pytest.mark.parametrize('f', [ + make_xp_pytest_param(stats.pearsonr), + make_xp_pytest_param(stats.pointbiserialr), + make_xp_pytest_param(stats.spearmanrho), +]) +@pytest.mark.parametrize('alternative', ['less', 'greater', 'two-sided']) +@pytest.mark.parametrize('axis', [0, 1, None]) +def test_correlation(f, alternative, axis, xp): + mxp, marrays, narrays = get_arrays(2, xp=xp) + + kwargs = {} if f == stats.pointbiserialr else {'alternative': alternative} + res = f(*marrays, **kwargs, axis=axis) + + # `pearsonr` does not have `axis_nan_policy`, so do this manually + x, y = narrays + if axis == 0: + x, y = x.T, y.T + elif axis is None: + x, y = x.ravel()[np.newaxis, :], y.ravel()[np.newaxis, :] + + for i in range(x.shape[0]): + xi, yi = x[i, ...], y[i, ...] + i = () if axis is None else i + + mask = np.isnan(xi) | np.isnan(yi) + ref = f(xi[~mask], yi[~mask], **kwargs) + + atol = 1e-7 if (is_torch(xp) and f == stats.spearmanrho) else 0. + xp_assert_close(res.statistic.data[i], xp.asarray(ref.statistic)[()], atol=atol) + xp_assert_close(res.pvalue.data[i], xp.asarray(ref.pvalue)[()], atol=atol) + + if f == stats.pearsonr: + res_ci_low, res_ci_high = res.confidence_interval() + ref_ci_low, ref_ci_high = ref.confidence_interval() + xp_assert_close(res_ci_low.data[i], xp.asarray(ref_ci_low)[()]) + xp_assert_close(res_ci_high.data[i], xp.asarray(ref_ci_high)[()]) + + +@skip_backend('jax.numpy', reason="JAX doesn't allow item assignment.") +@skip_backend('array_api_strict', reason="issue with clip.") +@pytest.mark.parametrize('f, method', [ + make_xp_pytest_param(stats.siegelslopes, {'method':'hierarchical'}), + make_xp_pytest_param(stats.siegelslopes, {'method':'separate'}), + make_xp_pytest_param(stats.theilslopes, {'method': 'joint'}), + make_xp_pytest_param(stats.theilslopes, {'method': 'separate'}), + make_xp_pytest_param(stats.linregress, {'alternative': 'less'}), + make_xp_pytest_param(stats.linregress, {'alternative': 'greater'}), + make_xp_pytest_param(stats.linregress, {'alternative': 'two-sided'}), +]) +@pytest.mark.parametrize('axis', [0, 1, None]) +def test_slopes_intercepts(f, method, axis, xp): + mxp, marrays, narrays = get_arrays(2, shape=(19, 20), seed=84912165484320, xp=xp) + res = f(*marrays, axis=axis, **method) + ref = f(*narrays, nan_policy='omit', axis=axis, **method) + + xp_assert_close(res.slope.data, xp.asarray(ref.slope)) + xp_assert_close(res.intercept.data, xp.asarray(ref.intercept)) + + if f == stats.theilslopes: + xp_assert_close(res.low_slope.data, xp.asarray(ref.low_slope)) + xp_assert_close(res.high_slope.data, xp.asarray(ref.high_slope)) + elif f == stats.linregress: + xp_assert_close(res.rvalue.data, xp.asarray(ref.rvalue)) + xp_assert_close(res.pvalue.data, xp.asarray(ref.pvalue)) + xp_assert_close(res.stderr.data, xp.asarray(ref.stderr)) + xp_assert_close(res.intercept_stderr.data, xp.asarray(ref.intercept_stderr)) + + +@make_xp_test_case(stats.entropy) +@skip_backend('dask.array', reason="Dask doesn't like the /= when base is not None.") +@skip_backend('jax.numpy', reason="JAX doesn't allow item assignment.") +@pytest.mark.parametrize('qk', [False, True]) +@pytest.mark.parametrize('base', [None, 2]) +@pytest.mark.parametrize('axis', [0, 1, None]) +def test_entropy(qk, base, axis, xp): + mxp, marrays, narrays = get_arrays(2 if qk else 1, xp=xp) + res = stats.entropy(*marrays, base=base, axis=axis) + ref = stats.entropy(*narrays, base=base, nan_policy='omit', axis=axis) + xp_assert_close(res.data, xp.asarray(ref)) + + +@make_xp_test_case(stats.rankdata) +@skip_backend('jax.numpy', reason="JAX currently incompatible with marray") +@pytest.mark.parametrize('method', ['average', 'min', 'max', 'dense', 'ordinal']) +@pytest.mark.parametrize('axis', [0, 1, None]) +def test_rankdata(method, axis, xp): + mxp, marrays, narrays = get_arrays(1, xp=xp) + res = stats.rankdata(*marrays, method=method, axis=axis) + ref = stats.rankdata(*narrays, method=method, nan_policy='omit', axis=axis) + xp_assert_close(res.data[~res.mask], xp.asarray(ref[~np.isnan(ref)])) + xp_assert_close(res.mask, xp.asarray(np.isnan(ref))) + + +@make_xp_test_case(stats.obrientransform) +@skip_backend('jax.numpy', reason="JAX currently incompatible with marray") +@pytest.mark.parametrize('dtype', ['float32', 'float64']) +@pytest.mark.parametrize('n_arrays', [1, 3]) +def test_obrientransform(dtype, n_arrays, xp): + # obrientransform is not yet vectorized + mxp, marrays, narrays = get_arrays(n_arrays, dtype=dtype, shape=(25,), xp=xp) + res = stats.obrientransform(*marrays) + ref = stats.obrientransform(*narrays, nan_policy='omit') + for res_i, ref_i in zip(res, ref): + xp_assert_close(res_i.data[~res_i.mask], + xp.asarray(ref_i[~np.isnan(ref_i)], dtype=getattr(xp, dtype))) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/test_mgc.py b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_mgc.py new file mode 100644 index 0000000000000000000000000000000000000000..fd4d27d7c1647b0ab72cd668ccd0509f02b4684a --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_mgc.py @@ -0,0 +1,233 @@ +import pytest +from pytest import raises as assert_raises, warns as assert_warns + +import numpy as np +from numpy.testing import assert_approx_equal, assert_allclose, assert_equal + +from scipy.spatial.distance import cdist +from scipy import stats + +class TestMGCErrorWarnings: + """ Tests errors and warnings derived from MGC. + """ + def test_error_notndarray(self): + # raises error if x or y is not an ndarray + x = np.arange(20) + y = [5] * 20 + assert_raises(ValueError, stats.multiscale_graphcorr, x, y) + assert_raises(ValueError, stats.multiscale_graphcorr, y, x) + + def test_error_shape(self): + # raises error if number of samples different (n) + x = np.arange(100).reshape(25, 4) + y = x.reshape(10, 10) + assert_raises(ValueError, stats.multiscale_graphcorr, x, y) + + def test_error_lowsamples(self): + # raises error if samples are low (< 3) + x = np.arange(3) + y = np.arange(3) + assert_raises(ValueError, stats.multiscale_graphcorr, x, y) + + def test_error_nans(self): + # raises error if inputs contain NaNs + x = np.arange(20, dtype=float) + x[0] = np.nan + assert_raises(ValueError, stats.multiscale_graphcorr, x, x) + + y = np.arange(20) + assert_raises(ValueError, stats.multiscale_graphcorr, x, y) + + def test_error_wrongdisttype(self): + # raises error if metric is not a function + x = np.arange(20) + compute_distance = 0 + assert_raises(ValueError, stats.multiscale_graphcorr, x, x, + compute_distance=compute_distance) + + @pytest.mark.parametrize("reps", [ + -1, # reps is negative + '1', # reps is not integer + ]) + def test_error_reps(self, reps): + # raises error if reps is negative + x = np.arange(20) + assert_raises(ValueError, stats.multiscale_graphcorr, x, x, reps=reps) + + def test_warns_reps(self): + # raises warning when reps is less than 1000 + x = np.arange(20) + reps = 100 + assert_warns(RuntimeWarning, stats.multiscale_graphcorr, x, x, reps=reps) + + def test_error_infty(self): + # raises error if input contains infinities + x = np.arange(20) + y = np.ones(20) * np.inf + assert_raises(ValueError, stats.multiscale_graphcorr, x, y) + + +class TestMGCStat: + """ Test validity of MGC test statistic + """ + def setup_method(self): + self.rng = np.random.default_rng(1266219746) + + def _simulations(self, samps=100, dims=1, sim_type="", rng=None): + rng = rng or self.rng + # linear simulation + if sim_type == "linear": + x = rng.uniform(-1, 1, size=(samps, 1)) + y = x + 0.3 * rng.random(size=(x.size, 1)) + + # spiral simulation + elif sim_type == "nonlinear": + unif = np.array(self.rng.uniform(0, 5, size=(samps, 1))) + x = unif * np.cos(np.pi * unif) + y = (unif * np.sin(np.pi * unif) + + 0.4*self.rng.random(size=(x.size, 1))) + + # independence (tests type I simulation) + elif sim_type == "independence": + u = self.rng.standard_normal(size=(samps, 1)) + v = self.rng.standard_normal(size=(samps, 1)) + u_2 = self.rng.binomial(1, p=0.5, size=(samps, 1)) + v_2 = self.rng.binomial(1, p=0.5, size=(samps, 1)) + x = u/3 + 2*u_2 - 1 + y = v/3 + 2*v_2 - 1 + + # raises error if not approved sim_type + else: + raise ValueError("sim_type must be linear, nonlinear, or " + "independence") + + # add dimensions of noise for higher dimensions + if dims > 1: + dims_noise = self.rng.standard_normal(size=(samps, dims-1)) + x = np.concatenate((x, dims_noise), axis=1) + + return x, y + + @pytest.mark.xslow + @pytest.mark.parametrize("sim_type, obs_stat, obs_pvalue", [ + # Reference values produced by `multiscale_graphcorr` -> + # this only guards against unintended changes + ("linear", 0.970137188683, 0.000999000999), # test linear simulation + ("nonlinear", 0.149843048137, 0.000999000999), # test spiral simulation + ("independence", -0.003965353120, 0.4675324675) # test independence simulation + ]) + def test_oned(self, sim_type, obs_stat, obs_pvalue): + # generate x and y + rng = np.random.default_rng(8157117705) + x, y = self._simulations(samps=100, dims=1, sim_type=sim_type, rng=rng) + + # test stat and pvalue + stat, pvalue, _ = stats.multiscale_graphcorr(x, y, random_state=rng) + assert_allclose(stat, obs_stat) + assert_allclose(pvalue, obs_pvalue) + + @pytest.mark.xslow + @pytest.mark.parametrize("sim_type, obs_stat, obs_pvalue", [ + # Reference values produced by `multiscale_graphcorr` -> + # this only guards against unintended changes + ("linear", 0.193063672972, 0.000999000999), # test linear simulation + ("nonlinear", 0.010042844939, 0.216783216783), # test spiral simulation + ]) + def test_fived(self, sim_type, obs_stat, obs_pvalue): + # generate x and y + rng = np.random.default_rng(8157117705) + x, y = self._simulations(samps=100, dims=5, sim_type=sim_type, rng=rng) + + # test stat and pvalue + stat, pvalue, _ = stats.multiscale_graphcorr(x, y, random_state=rng) + assert_allclose(stat, obs_stat) + assert_allclose(pvalue, obs_pvalue) + + @pytest.mark.xslow + def test_twosamp(self): + # generate x and y + x = self.rng.binomial(100, 0.5, size=(100, 5)) + y = self.rng.standard_normal(size=(80, 5)) + + # test stat and pvalue + stat, pvalue, _ = stats.multiscale_graphcorr(x, y, random_state=self.rng) + assert_approx_equal(stat, 1.0, significant=1) + assert_approx_equal(pvalue, 0.001, significant=1) + + # generate x and y + y = self.rng.standard_normal(size=(100, 5)) + + # test stat and pvalue + stat, pvalue, _ = stats.multiscale_graphcorr(x, y, is_twosamp=True, + random_state=self.rng) + assert_approx_equal(stat, 1.0, significant=1) + assert_approx_equal(pvalue, 0.001, significant=1) + + @pytest.mark.xslow + def test_workers(self): + # generate x and y + x, y = self._simulations(samps=100, dims=1, sim_type="linear") + + # test stat and pvalue + stat, pvalue, _ = stats.multiscale_graphcorr(x, y, workers=2, + random_state=self.rng) + assert_approx_equal(stat, 0.97, significant=1) + assert_approx_equal(pvalue, 0.001, significant=1) + + @pytest.mark.xslow + def test_random_state(self): + # generate x and y + x, y = self._simulations(samps=100, dims=1, sim_type="linear") + + # test stat and pvalue + stat, pvalue, _ = stats.multiscale_graphcorr(x, y, random_state=1) + assert_approx_equal(stat, 0.97, significant=1) + assert_approx_equal(pvalue, 0.001, significant=1) + + @pytest.mark.xslow + def test_dist_perm(self): + # generate x and y + x, y = self._simulations(samps=100, dims=1, sim_type="nonlinear") + distx = cdist(x, x, metric="euclidean") + disty = cdist(y, y, metric="euclidean") + + stat_dist, pvalue_dist, _ = stats.multiscale_graphcorr(distx, disty, + compute_distance=None, + random_state=1) + assert_approx_equal(stat_dist, 0.163, significant=1) + assert_approx_equal(pvalue_dist, 0.001, significant=1) + + @pytest.mark.fail_slow(20) # all other tests are XSLOW; we need at least one to run + @pytest.mark.slow + def test_pvalue_literature(self): + # generate x and y + x, y = self._simulations(samps=100, dims=1, sim_type="linear") + + # test stat and pvalue + _, pvalue, _ = stats.multiscale_graphcorr(x, y, random_state=1) + assert_allclose(pvalue, 1/1001) + + def test_zero_distance_matrix_index_error(self): + # regression test for zero distance + # matrix IndexError issue; see gh-19769 + n = 6 + x = np.arange(n).reshape(-1, 1) + + # valid distance matrix + distx = cdist(x, x) + + # degenerate all-zero distance matrix + disty = np.zeros((n, n)) + + # test stat and pvalue + res = stats.multiscale_graphcorr(distx, disty, compute_distance=None) + assert_allclose(res.statistic, 0.0) + assert_allclose(res.pvalue, 1.0) + + @pytest.mark.xslow + def test_alias(self): + # generate x and y + x, y = self._simulations(samps=100, dims=1, sim_type="linear") + + res = stats.multiscale_graphcorr(x, y, random_state=1) + assert_equal(res.stat, res.statistic) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/test_morestats.py b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_morestats.py new file mode 100644 index 0000000000000000000000000000000000000000..e76b24c30556ec287ca173ab0aa2b7db991446c0 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_morestats.py @@ -0,0 +1,3906 @@ +# Author: Travis Oliphant, 2002 +# +# Further enhancements and tests added by numerous SciPy developers. +# +import math +import re +import sys +import warnings +from functools import partial + +import numpy as np +from numpy.random import RandomState +from numpy.testing import (assert_array_equal, assert_almost_equal, + assert_array_less, assert_array_almost_equal, + assert_, assert_allclose, assert_equal) +import pytest +from pytest import raises as assert_raises + +from scipy import optimize, stats, special +from scipy.stats._morestats import (_abw_state, _get_As_weibull, _Avals_weibull, + _yeojohnson_transform) +from .common_tests import check_named_results +from .._hypotests import _get_wilcoxon_distr, _get_wilcoxon_distr2 +from scipy.stats._ansari_swilk_statistics import swilk +from scipy.stats._binomtest import _binary_search_for_binom_tst +from scipy.stats._distr_params import distcont +from scipy.stats._axis_nan_policy import (SmallSampleWarning, too_small_nd_omit, + too_small_1d_omit, too_small_1d_not_omit) + +import scipy._external.array_api_extra as xpx +from scipy._lib._array_api import (is_torch, make_xp_test_case, eager_warns, xp_ravel, + is_numpy, xp_default_dtype, is_array_api_strict, + is_jax, is_lazy_array) +from scipy._lib._array_api_no_0d import ( + xp_assert_close, + xp_assert_equal, + xp_assert_less, +) + +lazy_xp_modules = [stats] +skip_xp_backends = pytest.mark.skip_xp_backends + +distcont = dict(distcont) # type: ignore + +# Matplotlib is not a scipy dependency but is optionally used in probplot, so +# check if it's available +try: + import matplotlib + matplotlib.rcParams['backend'] = 'Agg' + import matplotlib.pyplot as plt + have_matplotlib = True +except Exception: + have_matplotlib = False + + +# test data gear.dat from NIST for Levene and Bartlett test +# https://www.itl.nist.gov/div898/handbook/eda/section3/eda3581.htm +g1 = [1.006, 0.996, 0.998, 1.000, 0.992, 0.993, 1.002, 0.999, 0.994, 1.000] +g2 = [0.998, 1.006, 1.000, 1.002, 0.997, 0.998, 0.996, 1.000, 1.006, 0.988] +g3 = [0.991, 0.987, 0.997, 0.999, 0.995, 0.994, 1.000, 0.999, 0.996, 0.996] +g4 = [1.005, 1.002, 0.994, 1.000, 0.995, 0.994, 0.998, 0.996, 1.002, 0.996] +g5 = [0.998, 0.998, 0.982, 0.990, 1.002, 0.984, 0.996, 0.993, 0.980, 0.996] +g6 = [1.009, 1.013, 1.009, 0.997, 0.988, 1.002, 0.995, 0.998, 0.981, 0.996] +g7 = [0.990, 1.004, 0.996, 1.001, 0.998, 1.000, 1.018, 1.010, 0.996, 1.002] +g8 = [0.998, 1.000, 1.006, 1.000, 1.002, 0.996, 0.998, 0.996, 1.002, 1.006] +g9 = [1.002, 0.998, 0.996, 0.995, 0.996, 1.004, 1.004, 0.998, 0.999, 0.991] +g10 = [0.991, 0.995, 0.984, 0.994, 0.997, 0.997, 0.991, 0.998, 1.004, 0.997] + + +# The loggamma RVS stream is changing due to gh-13349; this version +# preserves the old stream so that tests don't change. +def _old_loggamma_rvs(*args, **kwargs): + return np.log(stats.gamma.rvs(*args, **kwargs)) + + +class TestBayes_mvs: + def test_basic(self): + # Expected values in this test simply taken from the function. For + # some checks regarding correctness of implementation, see review in + # gh-674 + data = [6, 9, 12, 7, 8, 8, 13] + mean, var, std = stats.bayes_mvs(data) + assert_almost_equal(mean.statistic, 9.0) + assert_allclose(mean.minmax, (7.103650222492964, 10.896349777507034), + rtol=1e-6) + + assert_almost_equal(var.statistic, 10.0) + assert_allclose(var.minmax, (3.1767242068607087, 24.45910381334018), + rtol=1e-09) + + assert_almost_equal(std.statistic, 2.9724954732045084, decimal=14) + assert_allclose(std.minmax, (1.7823367265645145, 4.9456146050146312), + rtol=1e-14) + + def test_empty_input(self): + assert_raises(ValueError, stats.bayes_mvs, []) + + def test_result_attributes(self): + x = np.arange(15) + attributes = ('statistic', 'minmax') + res = stats.bayes_mvs(x) + + for i in res: + check_named_results(i, attributes) + + +class TestMvsdist: + def test_basic(self): + data = [6, 9, 12, 7, 8, 8, 13] + mean, var, std = stats.mvsdist(data) + assert_almost_equal(mean.mean(), 9.0) + assert_allclose(mean.interval(0.9), (7.103650222492964, + 10.896349777507034), rtol=1e-14) + + assert_almost_equal(var.mean(), 10.0) + assert_allclose(var.interval(0.9), (3.1767242068607087, + 24.45910381334018), rtol=1e-09) + + assert_almost_equal(std.mean(), 2.9724954732045084, decimal=14) + assert_allclose(std.interval(0.9), (1.7823367265645145, + 4.9456146050146312), rtol=1e-14) + + def test_empty_input(self): + assert_raises(ValueError, stats.mvsdist, []) + + def test_bad_arg(self): + # Raise ValueError if fewer than two data points are given. + data = [1] + assert_raises(ValueError, stats.mvsdist, data) + + def test_warns(self): + # regression test for gh-5270 + # make sure there are no spurious divide-by-zero warnings + with warnings.catch_warnings(): + warnings.simplefilter('error', RuntimeWarning) + [x.mean() for x in stats.mvsdist([1, 2, 3])] + [x.mean() for x in stats.mvsdist([1, 2, 3, 4, 5])] + + +@make_xp_test_case(stats.shapiro) +class TestShapiro: + def test_basic(self, xp): + # reference values generated using R shapiro.test, e.g. + # options(digits=16) + # x = c(0.11, 7.87, 4.61, 10.14, 7.95, 3.14, 0.46, + # 4.43, 0.21, 4.75, 0.71, 1.52, 3.24, + # 0.93, 0.42, 4.97, 9.53, 4.55, 0.47, 6.66) + # shapiro.test(x) + x1 = [0.11, 7.87, 4.61, 10.14, 7.95, 3.14, 0.46, + 4.43, 0.21, 4.75, 0.71, 1.52, 3.24, + 0.93, 0.42, 4.97, 9.53, 4.55, 0.47, 6.66] + shapiro_test = stats.shapiro(xp.asarray(x1)) + w, pw = shapiro_test + xp_assert_close(w, xp.asarray(0.90047287931756)) + xp_assert_close(shapiro_test.statistic, xp.asarray(0.90047287931756)) + xp_assert_close(pw, xp.asarray(0.04208957522226)) + xp_assert_close(shapiro_test.pvalue, xp.asarray(0.04208957522226)) + + x2 = [1.36, 1.14, 2.92, 2.55, 1.46, 1.06, 5.27, -1.11, + 3.48, 1.10, 0.88, -0.51, 1.46, 0.52, 6.20, 1.69, + 0.08, 3.67, 2.81, 3.49] + shapiro_test = stats.shapiro(xp.asarray(x2)) + xp_assert_close(shapiro_test.statistic, xp.asarray(0.95902694603234)) + xp_assert_close(shapiro_test.pvalue, xp.asarray(0.5245979304707)) + + x3 = stats.norm.rvs(loc=5, scale=3, size=100, random_state=12345678) + shapiro_test = stats.shapiro(xp.asarray(x3.tolist())) + xp_assert_close(shapiro_test.statistic, xp.asarray(0.97728027037175)) + xp_assert_close(shapiro_test.pvalue, xp.asarray(0.08143656270016)) + + @pytest.mark.parametrize('dtype', [None, 'float32', 'float64']) + def test_basic2(self, dtype, xp): + # # Data from original paper; additional precision from R shapiro.test + x4 = [0.139, 0.157, 0.175, 0.256, 0.344, 0.413, 0.503, 0.577, 0.614, + 0.655, 0.954, 1.392, 1.557, 1.648, 1.690, 1.994, 2.174, 2.206, + 3.245, 3.510, 3.571, 4.354, 4.980, 6.084, 8.351] + dtype = {'dtype': dtype if dtype is None else getattr(xp, dtype)} + W_expected = xp.asarray(0.83466627531817, **dtype) + p_expected = xp.asarray(0.000913490481813, **dtype) + shapiro_test = stats.shapiro(xp.asarray(x4, **dtype)) + xp_assert_close(shapiro_test.statistic, xp.asarray(W_expected, **dtype)) + xp_assert_close(shapiro_test.pvalue, xp.asarray(p_expected, **dtype)) + + @skip_xp_backends('jax.numpy', reason='lazy backend -> limited input validation') + @skip_xp_backends('dask.array', reason='lazy backend -> limited input validation') + @pytest.mark.parametrize('x', ([], [1], [1, 2])) + def test_not_enough_values(self, x, xp): + with eager_warns(SmallSampleWarning, match=too_small_1d_not_omit, xp=xp): + res = stats.shapiro(xp.asarray(x)) + xp_assert_equal(res.statistic, xp.asarray(xp.nan)) + xp_assert_equal(res.pvalue, xp.asarray(xp.nan)) + + def test_nan_input(self, xp): + x = xp.arange(10.) + x = xpx.at(x)[9].set(xp.nan) + + shapiro_test = stats.shapiro(xp.asarray(x)) + xp_assert_equal(shapiro_test.statistic, xp.asarray(xp.nan)) + xp_assert_equal(shapiro_test.pvalue, xp.asarray(xp.nan)) + + def test_gh14462(self, xp): + # shapiro is theoretically location-invariant, but when the magnitude + # of the values is much greater than the variance, there can be + # numerical issues. Fixed by subtracting median from the data. + # See gh-14462. + trans_val, maxlog = stats.boxcox([122500, 474400, 110400]) + dtype = xp.float64 # we need some precision to work with + res = stats.shapiro(xp.asarray(trans_val, dtype=dtype)) + + # Reference from R: + # options(digits=16) + # x = c(0.00000000e+00, 3.39996924e-08, -6.35166875e-09) + # shapiro.test(x) + ref_statistic = xp.asarray(0.86468431705371, dtype=dtype) + ref_pvalue = xp.asarray(0.2805581751566, dtype=dtype) + + xp_assert_close(res.statistic, ref_statistic, rtol=5e-7) + xp_assert_close(res.pvalue, ref_pvalue, rtol=5e-7) + + def test_length_3_gh18322(self, xp): + # gh-18322 reported that the p-value could be negative for input of + # length 3. Check that this is resolved. + res = stats.shapiro(xp.asarray([0.6931471805599453, 0.0, 0.0])) + assert res.pvalue >= 0 + + # R `shapiro.test` doesn't produce an accurate p-value in the case + # above. Check that the formula used in `stats.shapiro` is not wrong. + # options(digits=16) + # x = c(-0.7746653110021126, -0.4344432067942129, 1.8157053280290931) + # shapiro.test(x) + x = [-0.7746653110021126, -0.4344432067942129, 1.8157053280290931] + res = stats.shapiro(xp.asarray(x)) + xp_assert_close(res.statistic, xp.asarray(0.84658770645509)) + xp_assert_close(res.pvalue, xp.asarray(0.2313666489882)) + + @pytest.mark.parametrize('n', [3, 4, 5, 8, 11, 20, 100, 200, 500, 1000, 2000, 5000]) + def test_against_as181(self, n, xp): + rng = np.random.default_rng(n) + x = rng.standard_normal(n) + + a = np.zeros(n // 2, dtype=np.float64) + y = np.sort(x) - x[n // 2] + ref_statistic, ref_pvalue, _ = swilk(y, a, 0) + + x = xp.asarray(x) + res = stats.shapiro(x) + ref_statistic = xp.asarray(ref_statistic, dtype=x.dtype) + ref_pvalue = xp.asarray(ref_pvalue, dtype=x.dtype) + + xp_assert_close(res.statistic, ref_statistic, rtol=5e-7) + xp_assert_close(res.pvalue, ref_pvalue, rtol=5e-7) + + +@pytest.mark.filterwarnings("ignore: As of SciPy 1.17: FutureWarning") +class TestAnderson: + def test_normal(self): + rs = RandomState(1234567890) + x1 = rs.standard_exponential(size=50) + x2 = rs.standard_normal(size=50) + A, crit, sig = stats.anderson(x1) + assert_array_less(crit[:-1], A) + A, crit, sig = stats.anderson(x2) + assert_array_less(A, crit[-2:]) + + v = np.ones(10) + v[0] = 0 + A, crit, sig = stats.anderson(v) + # The expected statistic 3.208057 was computed independently of scipy. + # For example, in R: + # > library(nortest) + # > v <- rep(1, 10) + # > v[1] <- 0 + # > result <- ad.test(v) + # > result$statistic + # A + # 3.208057 + assert_allclose(A, 3.208057) + + def test_expon(self): + rs = RandomState(1234567890) + x1 = rs.standard_exponential(size=50) + x2 = rs.standard_normal(size=50) + A, crit, sig = stats.anderson(x1, 'expon') + assert_array_less(A, crit[-2:]) + with np.errstate(all='ignore'): + A, crit, sig = stats.anderson(x2, 'expon') + assert_(A > crit[-1]) + + def test_gumbel(self): + # Regression test for gh-6306. Before that issue was fixed, + # this case would return a2=inf. + v = np.ones(100) + v[0] = 0.0 + a2, crit, sig = stats.anderson(v, 'gumbel') + # A brief reimplementation of the calculation of the statistic. + n = len(v) + xbar, s = stats.gumbel_l.fit(v) + logcdf = stats.gumbel_l.logcdf(v, xbar, s) + logsf = stats.gumbel_l.logsf(v, xbar, s) + i = np.arange(1, n+1) + expected_a2 = -n - np.mean((2*i - 1) * (logcdf + logsf[::-1])) + + assert_allclose(a2, expected_a2) + + def test_bad_arg(self): + assert_raises(ValueError, stats.anderson, [1], dist='plate_of_shrimp') + + def test_result_attributes(self): + rs = RandomState(1234567890) + x = rs.standard_exponential(size=50) + res = stats.anderson(x) + attributes = ('statistic', 'critical_values', 'significance_level') + check_named_results(res, attributes) + + def test_gumbel_l(self): + # gh-2592, gh-6337 + # Adds support to 'gumbel_r' and 'gumbel_l' as valid inputs for dist. + rs = RandomState(1234567890) + x = rs.gumbel(size=100) + A1, crit1, sig1 = stats.anderson(x, 'gumbel') + A2, crit2, sig2 = stats.anderson(x, 'gumbel_l') + + assert_allclose(A2, A1) + + def test_gumbel_r(self): + # gh-2592, gh-6337 + # Adds support to 'gumbel_r' and 'gumbel_l' as valid inputs for dist. + rs = RandomState(1234567890) + x1 = rs.gumbel(size=100) + x2 = np.ones(100) + # A constant array is a degenerate case and breaks gumbel_r.fit, so + # change one value in x2. + x2[0] = 0.996 + A1, crit1, sig1 = stats.anderson(x1, 'gumbel_r') + A2, crit2, sig2 = stats.anderson(x2, 'gumbel_r') + + assert_array_less(A1, crit1[-2:]) + assert_(A2 > crit2[-1]) + + def test_weibull_min_case_A(self): + # data and reference values from `anderson` reference [7] + x = np.array([225, 171, 198, 189, 189, 135, 162, 135, 117, 162]) + res = stats.anderson(x, 'weibull_min') + m, loc, scale = res.fit_result.params + assert_allclose((m, loc, scale), (2.38, 99.02, 78.23), rtol=2e-3) + assert_allclose(res.statistic, 0.260, rtol=1e-3) + assert res.statistic < res.critical_values[0] + + c = 1 / m # ~0.42 + assert_allclose(c, 1/2.38, rtol=2e-3) + # interpolate between rows for c=0.4 and c=0.45, indices -3 and -2 + As40 = _Avals_weibull[-3] + As45 = _Avals_weibull[-2] + As_ref = As40 + (c - 0.4)/(0.45 - 0.4) * (As45 - As40) + # atol=1e-3 because results are rounded up to the next third decimal + assert np.all(res.critical_values > As_ref) + assert_allclose(res.critical_values, As_ref, atol=1e-3) + + def test_weibull_min_case_B(self): + # From `anderson` reference [7] + x = np.array([74, 57, 48, 29, 502, 12, 70, 21, + 29, 386, 59, 27, 153, 26, 326]) + message = "Maximum likelihood estimation has converged to " + with pytest.raises(ValueError, match=message): + stats.anderson(x, 'weibull_min') + + def test_weibull_warning_error(self): + # Check for warning message when there are too few observations + # This is also an example in which an error occurs during fitting + x = -np.array([225, 75, 57, 168, 107, 12, 61, 43, 29]) + wmessage = "Critical values of the test statistic are given for the..." + emessage = "An error occurred while fitting the Weibull distribution..." + wcontext = pytest.warns(UserWarning, match=wmessage) + econtext = pytest.raises(ValueError, match=emessage) + with wcontext, econtext: + stats.anderson(x, 'weibull_min') + + @pytest.mark.parametrize('distname', + ['norm', 'expon', 'gumbel_l', 'extreme1', + 'gumbel', 'gumbel_r', 'logistic', 'weibull_min']) + def test_anderson_fit_params(self, distname): + # check that anderson now returns a FitResult + rng = np.random.default_rng(330691555377792039) + real_distname = ('gumbel_l' if distname in {'extreme1', 'gumbel'} + else distname) + dist = getattr(stats, real_distname) + params = distcont[real_distname] + x = dist.rvs(*params, size=1000, random_state=rng) + res = stats.anderson(x, distname) + assert res.fit_result.success + + def test_anderson_weibull_As(self): + m = 1 # "when mi < 2, so that c > 0.5, the last line...should be used" + assert_equal(_get_As_weibull(1/m), _Avals_weibull[-1]) + m = np.inf + assert_equal(_get_As_weibull(1/m), _Avals_weibull[0]) + + +class TestAndersonMethod: + def test_warning(self): + message = "As of SciPy 1.17, users..." + with pytest.warns(FutureWarning, match=message): + stats.anderson([1, 2, 3], 'norm') + + def test_method_input_validation(self): + message = "`method` must be either..." + with pytest.raises(ValueError, match=message): + stats.anderson([1, 2, 3], 'norm', method='ekki-ekki') + + def test_monte_carlo_method(self): + rng = np.random.default_rng(94982389149239) + + message = "The `rvs` attribute..." + with pytest.warns(UserWarning, match=message): + method = stats.MonteCarloMethod(rvs=rng.random) + stats.anderson([1, 2, 3], 'norm', method=method) + + message = "The `batch` attribute..." + with pytest.warns(UserWarning, match=message): + method = stats.MonteCarloMethod(batch=10) + stats.anderson([1, 2, 3], 'norm', method=method) + + method = stats.MonteCarloMethod(n_resamples=9, rng=rng) + res = stats.anderson([1, 2, 3], 'norm', method=method) + ten_p = res.pvalue * 10 + # p-value will always be divisible by n_resamples + 1 + assert np.round(ten_p) == ten_p + + method = stats.MonteCarloMethod(rng=np.random.default_rng(23495984827)) + ref = stats.anderson([1, 2, 3, 4, 5], 'norm', method=method) + method = stats.MonteCarloMethod(rng=np.random.default_rng(23495984827)) + res = stats.anderson([1, 2, 3, 4, 5], 'norm', method=method) + assert res.pvalue == ref.pvalue # same random state -> same p-value + method = stats.MonteCarloMethod(rng=np.random.default_rng(23495984828)) + res = stats.anderson([1, 2, 3, 4, 5], 'norm', method=method) + assert res.pvalue != ref.pvalue # different random state -> different p-value + + @pytest.mark.parametrize('dist_name, seed', + [('norm', 4202165767275), + ('expon', 9094400417269), + pytest.param('logistic', 3776634590070, marks=pytest.mark.xslow), + pytest.param('gumbel_l', 7966588969335, marks=pytest.mark.xslow), + pytest.param('gumbel_r', 1886450383828, marks=pytest.mark.xslow)]) + def test_method_consistency(self, dist_name, seed): + dist = getattr(stats, dist_name) + rng = np.random.default_rng(seed) + x = dist.rvs(size=50, random_state=rng) + ref = stats.anderson(x, dist_name, method='interpolate') + res = stats.anderson(x, dist_name, method=stats.MonteCarloMethod(rng=rng)) + np.testing.assert_allclose(res.statistic, ref.statistic) + np.testing.assert_allclose(res.pvalue, ref.pvalue, atol=0.005) + + @pytest.mark.parametrize('dist_name', + ['norm', 'expon', 'logistic', 'gumbel_l', 'gumbel_r', 'weibull_min']) + def test_interpolate_saturation(self, dist_name): + dist = getattr(stats, dist_name) + rng = np.random.default_rng(4202165767276) + args = (3.5,) if dist_name == 'weibull_min' else tuple() + x = dist.rvs(*args, size=50, random_state=rng) + + with pytest.warns(FutureWarning): + res = stats.anderson(x, dist_name) + pvalues = (1 - np.asarray(res.significance_level) if dist_name == 'weibull_min' + else np.asarray(res.significance_level) / 100) + pvalue_min = np.min(pvalues) + pvalue_max = np.max(pvalues) + statistic_min = np.min(res.critical_values) + statistic_max = np.max(res.critical_values) + + # data drawn from distribution -> low statistic / high p-value + res = stats.anderson(x, dist_name, method='interpolate') + assert res.statistic < statistic_min + assert res.pvalue == pvalue_max + + # data not from distribution -> high statistic / low p-value + res = stats.anderson(rng.random(size=50), dist_name, method='interpolate') + assert res.statistic > statistic_max + assert res.pvalue == pvalue_min + + +@pytest.mark.filterwarnings("ignore:Parameter `variant`...:UserWarning") +class TestAndersonKSamp: + def test_example1a(self): + # Example data from Scholz & Stephens (1987), originally + # published in Lehmann (1995, Nonparametrics, Statistical + # Methods Based on Ranks, p. 309) + # Pass a mixture of lists and arrays + t1 = [38.7, 41.5, 43.8, 44.5, 45.5, 46.0, 47.7, 58.0] + t2 = np.array([39.2, 39.3, 39.7, 41.4, 41.8, 42.9, 43.3, 45.8]) + t3 = np.array([34.0, 35.0, 39.0, 40.0, 43.0, 43.0, 44.0, 45.0]) + t4 = np.array([34.0, 34.8, 34.8, 35.4, 37.2, 37.8, 41.2, 42.8]) + + Tk, tm, p = stats.anderson_ksamp((t1, t2, t3, t4), midrank=False) + + assert_almost_equal(Tk, 4.449, 3) + assert_array_almost_equal([0.4985, 1.3237, 1.9158, 2.4930, 3.2459], + tm[0:5], 4) + assert_allclose(p, 0.0021, atol=0.00025) + + def test_example1b(self): + # Example data from Scholz & Stephens (1987), originally + # published in Lehmann (1995, Nonparametrics, Statistical + # Methods Based on Ranks, p. 309) + # Pass arrays + t1 = np.array([38.7, 41.5, 43.8, 44.5, 45.5, 46.0, 47.7, 58.0]) + t2 = np.array([39.2, 39.3, 39.7, 41.4, 41.8, 42.9, 43.3, 45.8]) + t3 = np.array([34.0, 35.0, 39.0, 40.0, 43.0, 43.0, 44.0, 45.0]) + t4 = np.array([34.0, 34.8, 34.8, 35.4, 37.2, 37.8, 41.2, 42.8]) + Tk, tm, p = stats.anderson_ksamp((t1, t2, t3, t4), midrank=True) + + assert_almost_equal(Tk, 4.480, 3) + assert_array_almost_equal([0.4985, 1.3237, 1.9158, 2.4930, 3.2459], + tm[0:5], 4) + assert_allclose(p, 0.0020, atol=0.00025) + + @pytest.mark.xslow + def test_example2a(self): + # Example data taken from an earlier technical report of + # Scholz and Stephens + # Pass lists instead of arrays + t1 = [194, 15, 41, 29, 33, 181] + t2 = [413, 14, 58, 37, 100, 65, 9, 169, 447, 184, 36, 201, 118] + t3 = [34, 31, 18, 18, 67, 57, 62, 7, 22, 34] + t4 = [90, 10, 60, 186, 61, 49, 14, 24, 56, 20, 79, 84, 44, 59, 29, + 118, 25, 156, 310, 76, 26, 44, 23, 62] + t5 = [130, 208, 70, 101, 208] + t6 = [74, 57, 48, 29, 502, 12, 70, 21, 29, 386, 59, 27] + t7 = [55, 320, 56, 104, 220, 239, 47, 246, 176, 182, 33] + t8 = [23, 261, 87, 7, 120, 14, 62, 47, 225, 71, 246, 21, 42, 20, 5, + 12, 120, 11, 3, 14, 71, 11, 14, 11, 16, 90, 1, 16, 52, 95] + t9 = [97, 51, 11, 4, 141, 18, 142, 68, 77, 80, 1, 16, 106, 206, 82, + 54, 31, 216, 46, 111, 39, 63, 18, 191, 18, 163, 24] + t10 = [50, 44, 102, 72, 22, 39, 3, 15, 197, 188, 79, 88, 46, 5, 5, 36, + 22, 139, 210, 97, 30, 23, 13, 14] + t11 = [359, 9, 12, 270, 603, 3, 104, 2, 438] + t12 = [50, 254, 5, 283, 35, 12] + t13 = [487, 18, 100, 7, 98, 5, 85, 91, 43, 230, 3, 130] + t14 = [102, 209, 14, 57, 54, 32, 67, 59, 134, 152, 27, 14, 230, 66, + 61, 34] + + samples = (t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14) + Tk, tm, p = stats.anderson_ksamp(samples, midrank=False) + assert_almost_equal(Tk, 3.288, 3) + assert_array_almost_equal([0.5990, 1.3269, 1.8052, 2.2486, 2.8009], + tm[0:5], 4) + assert_allclose(p, 0.0041, atol=0.00025) + + rng = np.random.default_rng(6989860141921615054) + method = stats.PermutationMethod(n_resamples=9999, rng=rng) + res = stats.anderson_ksamp(samples, midrank=False, method=method) + assert_array_equal(res.statistic, Tk) + assert_array_equal(res.critical_values, tm) + assert_allclose(res.pvalue, p, atol=6e-4) + + def test_example2b(self): + # Example data taken from an earlier technical report of + # Scholz and Stephens + t1 = [194, 15, 41, 29, 33, 181] + t2 = [413, 14, 58, 37, 100, 65, 9, 169, 447, 184, 36, 201, 118] + t3 = [34, 31, 18, 18, 67, 57, 62, 7, 22, 34] + t4 = [90, 10, 60, 186, 61, 49, 14, 24, 56, 20, 79, 84, 44, 59, 29, + 118, 25, 156, 310, 76, 26, 44, 23, 62] + t5 = [130, 208, 70, 101, 208] + t6 = [74, 57, 48, 29, 502, 12, 70, 21, 29, 386, 59, 27] + t7 = [55, 320, 56, 104, 220, 239, 47, 246, 176, 182, 33] + t8 = [23, 261, 87, 7, 120, 14, 62, 47, 225, 71, 246, 21, 42, 20, 5, + 12, 120, 11, 3, 14, 71, 11, 14, 11, 16, 90, 1, 16, 52, 95] + t9 = [97, 51, 11, 4, 141, 18, 142, 68, 77, 80, 1, 16, 106, 206, 82, + 54, 31, 216, 46, 111, 39, 63, 18, 191, 18, 163, 24] + t10 = [50, 44, 102, 72, 22, 39, 3, 15, 197, 188, 79, 88, 46, 5, 5, 36, + 22, 139, 210, 97, 30, 23, 13, 14] + t11 = [359, 9, 12, 270, 603, 3, 104, 2, 438] + t12 = [50, 254, 5, 283, 35, 12] + t13 = [487, 18, 100, 7, 98, 5, 85, 91, 43, 230, 3, 130] + t14 = [102, 209, 14, 57, 54, 32, 67, 59, 134, 152, 27, 14, 230, 66, + 61, 34] + + Tk, tm, p = stats.anderson_ksamp((t1, t2, t3, t4, t5, t6, t7, t8, + t9, t10, t11, t12, t13, t14), + midrank=True) + + assert_almost_equal(Tk, 3.294, 3) + assert_array_almost_equal([0.5990, 1.3269, 1.8052, 2.2486, 2.8009], + tm[0:5], 4) + assert_allclose(p, 0.0041, atol=0.00025) + + def test_R_kSamples(self): + # test values generates with R package kSamples + # package version 1.2-6 (2017-06-14) + # r1 = 1:100 + # continuous case (no ties) --> version 1 + # res <- kSamples::ad.test(r1, r1 + 40.5) + # res$ad[1, "T.AD"] # 41.105 + # res$ad[1, " asympt. P-value"] # 5.8399e-18 + # + # discrete case (ties allowed) --> version 2 (here: midrank=True) + # res$ad[2, "T.AD"] # 41.235 + # + # res <- kSamples::ad.test(r1, r1 + .5) + # res$ad[1, "T.AD"] # -1.2824 + # res$ad[1, " asympt. P-value"] # 1 + # res$ad[2, "T.AD"] # -1.2944 + # + # res <- kSamples::ad.test(r1, r1 + 7.5) + # res$ad[1, "T.AD"] # 1.4923 + # res$ad[1, " asympt. P-value"] # 0.077501 + # + # res <- kSamples::ad.test(r1, r1 + 6) + # res$ad[2, "T.AD"] # 0.63892 + # res$ad[2, " asympt. P-value"] # 0.17981 + # + # res <- kSamples::ad.test(r1, r1 + 11.5) + # res$ad[1, "T.AD"] # 4.5042 + # res$ad[1, " asympt. P-value"] # 0.00545 + # + # res <- kSamples::ad.test(r1, r1 + 13.5) + # res$ad[1, "T.AD"] # 6.2982 + # res$ad[1, " asympt. P-value"] # 0.00118 + + x1 = np.linspace(1, 100, 100) + # test case: different distributions;p-value floored at 0.001 + # test case for issue #5493 / #8536 + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", 'p-value floored', UserWarning) + s, _, p = stats.anderson_ksamp([x1, x1 + 40.5], midrank=False) + assert_almost_equal(s, 41.105, 3) + assert_equal(p, 0.001) + + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", 'p-value floored', UserWarning) + s, _, p = stats.anderson_ksamp([x1, x1 + 40.5]) + assert_almost_equal(s, 41.235, 3) + assert_equal(p, 0.001) + + # test case: similar distributions --> p-value capped at 0.25 + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", 'p-value capped', UserWarning) + s, _, p = stats.anderson_ksamp([x1, x1 + .5], midrank=False) + assert_almost_equal(s, -1.2824, 4) + assert_equal(p, 0.25) + + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", 'p-value capped', UserWarning) + s, _, p = stats.anderson_ksamp([x1, x1 + .5]) + assert_almost_equal(s, -1.2944, 4) + assert_equal(p, 0.25) + + # test case: check interpolated p-value in [0.01, 0.25] (no ties) + s, _, p = stats.anderson_ksamp([x1, x1 + 7.5], midrank=False) + assert_almost_equal(s, 1.4923, 4) + assert_allclose(p, 0.0775, atol=0.005, rtol=0) + + # test case: check interpolated p-value in [0.01, 0.25] (w/ ties) + s, _, p = stats.anderson_ksamp([x1, x1 + 6]) + assert_almost_equal(s, 0.6389, 4) + assert_allclose(p, 0.1798, atol=0.005, rtol=0) + + # test extended critical values for p=0.001 and p=0.005 + s, _, p = stats.anderson_ksamp([x1, x1 + 11.5], midrank=False) + assert_almost_equal(s, 4.5042, 4) + assert_allclose(p, 0.00545, atol=0.0005, rtol=0) + + s, _, p = stats.anderson_ksamp([x1, x1 + 13.5], midrank=False) + assert_almost_equal(s, 6.2982, 4) + assert_allclose(p, 0.00118, atol=0.0001, rtol=0) + + def test_not_enough_samples(self): + assert_raises(ValueError, stats.anderson_ksamp, np.ones(5)) + + def test_no_distinct_observations(self): + assert_raises(ValueError, stats.anderson_ksamp, + (np.ones(5), np.ones(5))) + + def test_empty_sample(self): + assert_raises(ValueError, stats.anderson_ksamp, (np.ones(5), [])) + + def test_result_attributes(self): + # Pass a mixture of lists and arrays + t1 = [38.7, 41.5, 43.8, 44.5, 45.5, 46.0, 47.7, 58.0] + t2 = np.array([39.2, 39.3, 39.7, 41.4, 41.8, 42.9, 43.3, 45.8]) + res = stats.anderson_ksamp((t1, t2), midrank=False) + + attributes = ('statistic', 'critical_values', 'significance_level') + check_named_results(res, attributes) + + assert_equal(res.significance_level, res.pvalue) + + +class TestAndersonKSampVariant: + def test_variant_values(self): + x = [1, 2, 2, 3, 4, 5] + y = [1, 2, 3, 4, 4, 5, 6, 6, 6, 7] + message = "Parameter `variant` has been introduced..." + with pytest.warns(UserWarning, match=message): + ref = stats.anderson_ksamp((x, y)) + assert len(ref) == 3 and hasattr(ref, 'critical_values') + + with pytest.warns(UserWarning, match=message): + res = stats.anderson_ksamp((x, y), midrank=True) + assert_equal(res.statistic, ref.statistic) + assert_equal(res.pvalue, ref.pvalue) + assert len(res) == 3 and hasattr(res, 'critical_values') + + with pytest.warns(UserWarning, match=message): + res = stats.anderson_ksamp((x, y), midrank=False, variant='midrank') + assert_equal(res.statistic, ref.statistic) + assert_equal(res.pvalue, ref.pvalue) + assert not hasattr(res, 'critical_values') + + res = stats.anderson_ksamp((x, y), variant='midrank') + assert_equal(res.statistic, ref.statistic) + assert_equal(res.pvalue, ref.pvalue) + assert not hasattr(res, 'critical_values') + + with pytest.warns(UserWarning, match=message): + ref = stats.anderson_ksamp((x, y), midrank=False) + assert len(ref) == 3 and hasattr(ref, 'critical_values') + + with pytest.warns(UserWarning, match=message): + res = stats.anderson_ksamp((x, y), midrank=True, variant='right') + assert_equal(res.statistic, ref.statistic) + assert_equal(res.pvalue, ref.pvalue) + assert not hasattr(res, 'critical_values') + + res = stats.anderson_ksamp((x, y), variant='right') + assert_equal(res.statistic, ref.statistic) + assert_equal(res.pvalue, ref.pvalue) + assert not hasattr(res, 'critical_values') + + def test_variant_input_validation(self): + x = np.arange(10) + message = "`variant` must be one of 'midrank', 'right', or 'continuous'." + with pytest.raises(ValueError, match=message): + stats.anderson_ksamp((x, x), variant='Camelot') + + @pytest.mark.parametrize('n_samples', [2, 3]) + def test_variant_continuous(self, n_samples): + rng = np.random.default_rng(20182053007) + samples = rng.random((n_samples, 15)) + 0.1*np.arange(n_samples)[:, np.newaxis] + ref = stats.anderson_ksamp(samples, variant='right') + res = stats.anderson_ksamp(samples, variant='continuous') + assert_allclose(res.statistic, ref.statistic) + assert_allclose(res.pvalue, ref.pvalue) + + +@make_xp_test_case(stats.ansari) +class TestAnsari: + + def test_small(self, xp): + x = xp.asarray([1, 2, 3, 3, 4]) + y = xp.asarray([3, 2, 6, 1, 6, 1, 4, 1]) + W, pval = stats.ansari(x, y, method='asymptotic') + xp_assert_close(W, xp.asarray(23.5)) + xp_assert_close(pval, xp.asarray(0.13499256881897437)) + + def test_approx(self, xp): + ramsay = xp.asarray([111, 107, 100, 99, 102, 106, 109, 108, 104, 99, + 101, 96, 97, 102, 107, 113, 116, 113, 110, 98]) + parekh = xp.asarray([107, 108, 106, 98, 105, 103, 110, 105, 104, + 100, 96, 108, 103, 104, 114, 114, 113, 108, + 106, 99]) + + W, pval = stats.ansari(ramsay, parekh, method='asymptotic') + xp_assert_close(W, xp.asarray(185.5)) + xp_assert_close(pval, xp.asarray(0.18145819972867083)) + + def test_exact(self, xp): + x, y = xp.asarray([1, 2, 3, 4]), xp.asarray([15, 5, 20, 8, 10, 12]) + W, pval = stats.ansari(x, y, method='exact') + xp_assert_close(W, xp.asarray(10.0)) + xp_assert_close(pval, xp.asarray(0.533333333333333333)) + + @pytest.mark.parametrize('args', [([], [1.]), ([1.], [])]) + def test_bad_arg(self, args, xp): + args = [xp.asarray(arg) for arg in args] + with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit): + res = stats.ansari(*args) + xp_assert_equal(res.statistic, xp.asarray(xp.nan)) + xp_assert_equal(res.pvalue, xp.asarray(xp.nan)) + + def test_bad_alternative(self, xp): + # invalid value for alternative must raise a ValueError + x1 = xp.asarray([1, 2, 3, 4]) + x2 = xp.asarray([5, 6, 7, 8]) + match = "'alternative' must be 'two-sided'" + with assert_raises(ValueError, match=match): + stats.ansari(x1, x2, alternative='foo') + + def test_alternative_exact(self, xp): + x1 = xp.asarray([-5, 1, 5, 10, 15, 20, 25.]) # high scale, loc=10 + x2 = xp.asarray([7.5, 8.5, 9.5, 10.5, 11.5, 12.5]) # low scale, loc=10 + # ratio of scales is greater than 1. So, the + # p-value must be high when `alternative='less'` + # and low when `alternative='greater'`. + statistic, pval = stats.ansari(x1, x2, method='exact') + pval_l = stats.ansari(x1, x2, alternative='less', method='exact').pvalue + pval_g = stats.ansari(x1, x2, alternative='greater', method='exact').pvalue + assert pval_l > 0.95 + assert pval_g < 0.05 # level of significance. + # also check if the p-values sum up to 1 plus the probability + # mass under the calculated statistic. + prob = _abw_state.a.pmf(float(statistic), x1.shape[0], x2.shape[0]) + prob = xp.asarray(float(prob)) + xp_assert_close(pval_g + pval_l, 1 + prob, atol=1e-12) + # also check if one of the one-sided p-value equals half the + # two-sided p-value and the other one-sided p-value is its + # compliment. + xp_assert_close(pval_g, pval/2, atol=1e-12) + xp_assert_close(pval_l, 1+prob-pval/2, atol=1e-12) + # sanity check. The result should flip if + # we exchange x and y. + pval_l_reverse = stats.ansari(x2, x1, alternative='less', + method='exact').pvalue + pval_g_reverse = stats.ansari(x2, x1, alternative='greater', + method='exact').pvalue + assert pval_l_reverse < 0.05 + assert pval_g_reverse > 0.95 + + @pytest.mark.parametrize( + 'x, y, alternative, expected', + # the tests are designed in such a way that the + # if else statement in ansari test for exact + # mode is covered. + [([1, 2, 3, 4], [5, 6, 7, 8], 'less', 0.6285714285714), + ([1, 2, 3, 4], [5, 6, 7, 8], 'greater', 0.6285714285714), + ([1, 2, 3], [4, 5, 6, 7, 8], 'less', 0.8928571428571), + ([1, 2, 3], [4, 5, 6, 7, 8], 'greater', 0.2857142857143), + ([1, 2, 3, 4, 5], [6, 7, 8], 'less', 0.2857142857143), + ([1, 2, 3, 4, 5], [6, 7, 8], 'greater', 0.8928571428571)] + ) + def test_alternative_exact_with_R(self, x, y, alternative, expected, xp): + # testing with R on arbitrary data + # Sample R code used for the third test case above: + # ```R + # > options(digits=16) + # > x <- c(1,2,3) + # > y <- c(4,5,6,7,8) + # > ansari.test(x, y, alternative='less', exact=TRUE) + # + # Ansari-Bradley test + # + # data: x and y + # AB = 6, p-value = 0.8928571428571 + # alternative hypothesis: true ratio of scales is less than 1 + # + # ``` + x, y = xp.asarray(x), xp.asarray(y) + pval = stats.ansari(x, y, alternative=alternative, method='exact').pvalue + xp_assert_close(pval, xp.asarray(expected), atol=1e-12) + + def test_alternative_approx(self, xp): + # intuitive tests for approximation + x1 = xp.asarray(stats.norm.rvs(0, 5, size=100, random_state=123)) + x2 = xp.asarray(stats.norm.rvs(0, 2, size=100, random_state=123)) + # for m > 55 or n > 55, the test should automatically + # switch to approximation. + pval_l = stats.ansari(x1, x2, alternative='less', method='asymptotic').pvalue + pval_g = stats.ansari(x1, x2, alternative='greater', method='asymptotic').pvalue + xp_assert_close(pval_l, xp.asarray(1.0, dtype=xp.float64), atol=1e-12) + xp_assert_close(pval_g, xp.asarray(0.0, dtype=xp.float64), atol=1e-12) + # also check if one of the one-sided p-value equals half the + # two-sided p-value and the other one-sided p-value is its + # compliment. + x1 = xp.asarray(stats.norm.rvs(0, 2, size=60, random_state=123)) + x2 = xp.asarray(stats.norm.rvs(0, 1.5, size=60, random_state=123)) + pval = stats.ansari(x1, x2, method='asymptotic').pvalue + pval_l = stats.ansari(x1, x2, alternative='less', method='asymptotic').pvalue + pval_g = stats.ansari(x1, x2, alternative='greater', method='asymptotic').pvalue + xp_assert_close(pval_g, pval/2, atol=1e-12) + xp_assert_close(pval_l, 1-pval/2, atol=1e-12) + + @pytest.mark.parametrize('dtype', [None, 'float32', 'float64']) + @pytest.mark.parametrize('n', [10, 100]) # affects code path + @pytest.mark.parametrize('ties', [False, True]) # affects code path + def test_dtypes(self, dtype, n, ties, xp): + if is_jax(xp) and dtype != 'float64': + pytest.xfail("p-value calculation works natively with 'float64 only") + dtype = xp_default_dtype(xp) if dtype is None else getattr(xp, dtype) + rng = np.random.default_rng(78587342806484) + x, y = rng.integers(6, size=(2, n)) if ties else rng.random(size=(2, n)) + method = {'method': 'exact' if ((n <= 55) and not ties) else 'asymptotic'} + ref = stats.ansari(x, y, **method) + res = stats.ansari(xp.asarray(x, dtype=dtype), xp.asarray(y, dtype=dtype), + **method) + xp_assert_close(res.statistic, xp.asarray(ref.statistic, dtype=dtype)) + xp_assert_close(res.pvalue, xp.asarray(ref.pvalue, dtype=dtype)) + + @pytest.mark.parametrize('alternative', ['less', 'greater', 'two-sided']) + def test_permutation_method(self, alternative, xp): + # test that permutation test can reproduce results of method='exact', + # but can also be configured to perform randomized test + rng = np.random.default_rng(6123643029) + x = xp.asarray(rng.random(7).tolist()) + y = xp.asarray(rng.random(6).tolist()) + res = stats.ansari(x, y, alternative=alternative, + method=stats.PermutationMethod(rng=rng)) + ref = stats.ansari(x, y, alternative=alternative, method='exact') + xp_assert_close(res.statistic, xp.asarray(ref.statistic)) + xp_assert_close(res.pvalue, xp.asarray(ref.pvalue)) + + if not is_lazy_array(x): # randomized permutation test has side-effects + res = stats.ansari(x, y, alternative=alternative, + method=stats.PermutationMethod(rng=rng, n_resamples=99)) + xp_assert_close(res.statistic, xp.asarray(ref.statistic)) + assert res.pvalue != ref.pvalue + assert res.pvalue == xp.round(res.pvalue*100)/100 # 99+1 in denominator + + def test_method(self, xp): + # test that `method` selection works and 'auto' behaves as expected + # expected p-values computed with `ansari.test` (R) + # e.g. `ansari.test(x, y, exact=False)` + rng = np.random.default_rng(6123643029) + + if is_jax(xp): + message = "`method` must be 'exact', 'asymptotic', or an instance..." + with pytest.raises(ValueError, match=message): + stats.ansari(xp.zeros(4), xp.ones(5)) + return + + # default is exact, but asymptotic can be selected + x = xp.asarray(rng.random(54).tolist()) + y = xp.asarray(rng.random(54).tolist()) + res1 = stats.ansari(x, y) + res2 = stats.ansari(x, y, method='exact') + res3 = stats.ansari(x, y, method='asymptotic') + xp_assert_close(res1.pvalue, res2.pvalue) + xp_assert_close(res3.pvalue, xp.asarray(0.01259674075933)) + + # default is asymptotic, but exact can be selected + x = xp.asarray(rng.random(55).tolist()) + y = xp.asarray(rng.random(55).tolist()) + res1 = stats.ansari(x, y) + res2 = stats.ansari(x, y, method='asymptotic') + res3 = stats.ansari(x, y, method='exact') + xp_assert_close(res1.pvalue, res2.pvalue) + xp_assert_close(res3.pvalue, xp.asarray(0.7613489076855)) + + # default is asymptotic because of ties + x = xp.asarray(rng.integers(10, size=20).tolist()) + y = xp.asarray(rng.integers(10, size=20).tolist()) + res1 = stats.ansari(x, y) + res2 = stats.ansari(x, y, method='asymptotic') + xp_assert_close(res1.pvalue, res2.pvalue) + + +@make_xp_test_case(stats.bartlett) +class TestBartlett: + def test_data(self, xp): + # https://www.itl.nist.gov/div898/handbook/eda/section3/eda357.htm + args = [g1, g2, g3, g4, g5, g6, g7, g8, g9, g10] + args = [xp.asarray(arg) for arg in args] + T, pval = stats.bartlett(*args) + xp_assert_close(T, xp.asarray(20.78587342806484)) + xp_assert_close(pval, xp.asarray(0.0136358632781)) + + def test_too_few_args(self, xp): + message = "Must enter at least two input sample vectors." + with pytest.raises(ValueError, match=message): + stats.bartlett(xp.asarray([1.])) + + def test_result_attributes(self, xp): + args = [g1, g2, g3, g4, g5, g6, g7, g8, g9, g10] + args = [xp.asarray(arg) for arg in args] + res = stats.bartlett(*args) + attributes = ('statistic', 'pvalue') + check_named_results(res, attributes, xp=xp) + + @pytest.mark.filterwarnings("ignore:invalid value encountered") # Dask + def test_empty_arg(self, xp): + args = (g1, g2, g3, g4, g5, g6, g7, g8, g9, g10, []) + args = [xp.asarray(arg) for arg in args] + + with eager_warns(SmallSampleWarning, match=too_small_1d_not_omit, xp=xp): + res = stats.bartlett(*args) + + NaN = xp.asarray(xp.nan) + xp_assert_equal(res.statistic, NaN) + xp_assert_equal(res.pvalue, NaN) + + def test_negative_pvalue_gh21152(self, xp): + a = xp.asarray([10.1, 10.2, 10.3, 10.4], dtype=xp.float32) + b = xp.asarray([10.15, 10.25, 10.35, 10.45], dtype=xp.float32) + c = xp.asarray([10.05, 10.15, 10.25, 10.35], dtype=xp.float32) + res = stats.bartlett(a, b, c) + assert xp.all(res.statistic >= 0) + + +@make_xp_test_case(stats.levene) +class TestLevene: + + def test_data(self, xp): + # https://www.itl.nist.gov/div898/handbook/eda/section3/eda35a.htm + args = [g1, g2, g3, g4, g5, g6, g7, g8, g9, g10] + args = [xp.asarray(arg) for arg in args] + W, pval = stats.levene(*args) + xp_assert_close(W, xp.asarray(1.7059176930008939)) + xp_assert_close(pval, xp.asarray(0.0990829755522)) + + def test_mean(self, xp): + # numbers from R: leveneTest in package car + args = [g1, g2, g3, g4, g5, g6, g7, g8, g9, g10] + args = [xp.asarray(arg) for arg in args] + W, pval = stats.levene(*args, center="mean") + xp_assert_close(W, xp.asarray(2.15945985647285)) + xp_assert_close(pval, xp.asarray(0.032236826559783)) + + def test_trimmed1(self, xp): + # Test that center='trimmed' gives the same result as center='mean' + # when proportiontocut=0. + args = (xp.asarray(g1), xp.asarray(g2), xp.asarray(g3)) + W1, pval1 = stats.levene(*args, center='mean') + W2, pval2 = stats.levene(*args, center='trimmed', proportiontocut=0.0) + xp_assert_close(W1, W2) + xp_assert_close(pval1, pval2) + + def test_trimmed2(self, xp): + # numbers from R: leveneTest in package car + args = [g1, g2, g3, g4, g5, g6, g7, g8, g9, g10] + args = [xp.asarray(arg) for arg in args] + W, pval = stats.levene(*args, center="trimmed", proportiontocut=0.25) + xp_assert_close(W, xp.asarray(2.07712845686874)) + xp_assert_close(pval, xp.asarray(0.0397269688035377)) + + def test_equal_mean_median(self, xp): + x = np.linspace(-1, 1, 21) + rng = np.random.default_rng(4058827756) + x2 = rng.permutation(x) + y = x**3 + x, x2, y = xp.asarray(x), xp.asarray(x2), xp.asarray(y) + W1, pval1 = stats.levene(x, y, center='mean') + W2, pval2 = stats.levene(x2, y, center='median') + xp_assert_close(W1, W2) + xp_assert_close(pval1, pval2) + + def test_bad_center_value(self, xp): + x = xp.linspace(-1, 1, 21) + message = "center must be 'mean', 'median' or 'trimmed'." + with pytest.raises(ValueError, match=message): + stats.levene(x, x, center='trim') + + def test_too_few_args(self, xp): + message = "Must provide at least two samples." + with pytest.raises(ValueError, match=message): + stats.levene(xp.asarray([1])) + + def test_result_attributes(self, xp): + args = [g1, g2, g3, g4, g5, g6, g7, g8, g9, g10] + args = [xp.asarray(arg) for arg in args] + res = stats.levene(*args) + attributes = ('statistic', 'pvalue') + check_named_results(res, attributes, xp=xp) + + +@make_xp_test_case(stats.binomtest) +class TestBinomTest: + """Tests for stats.binomtest.""" + + # Expected results here are from R binom.test, e.g. + # options(digits=16) + # binom.test(484, 967, p=0.48) + @skip_xp_backends("jax.numpy", reason="'two-sided' is incompatible with JAX") + @pytest.mark.parametrize("k, n, p, ref, rtol", + # aarch64 observed rtol: 1.5e-11 + [(10079999, 21000000, 0.48, 1.0, 1e-10), + (10079990, 21000000, 0.48, 0.9966892187965, 1e-10), + (10080009, 21000000, 0.48, 0.9970377203856, 1e-10), + (10080017, 21000000, 0.48, 0.9940754817328, 1e-9)]) + def test_two_sided_pvalues1(self, k, n, p, ref, rtol, xp): + # `tol` could be stricter on most architectures, but the value + # here is limited by accuracy of `binom.cdf` for large inputs on + # Linux_Python_37_32bit_full and aarch64 + dtype = xp.float64 # because this tests precision + res = stats.binomtest(k, n, xp.asarray(p, dtype=dtype)) + xp_assert_close(res.pvalue, xp.asarray(ref, dtype=dtype), rtol=rtol) + + @skip_xp_backends("jax.numpy", reason="'two-sided' is incompatible with JAX") + @pytest.mark.parametrize("k, n, p, ref, rtol", + # no aarch64 failure with 1e-15, preemptive bump + [(9, 21, 0.48, 0.6689672431939, 1e-10), + (4, 21, 0.48, 0.0081395634521, 1e-10), + (11, 21, 0.48, 0.8278629664608, 1e-10), + (7, 21, 0.48, 0.1966772901718, 1e-10), + (3, 10, 0.5, 0.34375, 1e-10), + (2, 2, 0.4, 0.16, 1e-10), + (2, 4, 0.3, 0.5884, 1e-10)]) + def test_two_sided_pvalues2(self, k, n, p, ref, rtol, xp): + dtype = xp.float64 # because this tests precision + res = stats.binomtest(k, n, xp.asarray(p, dtype=dtype)) + xp_assert_close(res.pvalue, xp.asarray(ref, dtype=dtype), rtol=rtol) + + @skip_xp_backends("jax.numpy", reason="'two-sided' is incompatible with JAX") + @pytest.mark.parametrize("k, n, p, ref, rtol", + # no aarch64 failure with 1e-15, preemptive bump + [(484, 967, 0.5, 1.0, 1e-10), + (3, 47, 3/47, 1.0, 1e-10), + (13, 46, 13/46, 1.0, 1e-10), + (15, 44, 15/44, 1.0, 1e-10), + (7, 13, 0.5, 1.0, 1e-10), + (6, 11, 0.5, 1.0, 1e-10)]) + def test_edge_cases(self, k, n, p, ref, rtol, xp): + dtype = xp.float64 + res = stats.binomtest(k, n, xp.asarray(p, dtype=dtype)) + xp_assert_close(res.pvalue, xp.asarray(ref, dtype=dtype), rtol=rtol) + + # tests private function, so leave NumPy-only + def test_binary_srch_for_binom_tst(self): + # Test that old behavior of binomtest is maintained + # by the new binary search method in cases where d + # exactly equals the input on one side. + n = 10 + p = 0.5 + k = 3 + # First test for the case where k > mode of PMF + i = np.arange(np.ceil(p * n), n+1) + d = stats.binom.pmf(k, n, p) + # Old way of calculating y, probably consistent with R. + y1 = np.sum(stats.binom.pmf(i, n, p) <= d, axis=0) + # New way with binary search. + ix = _binary_search_for_binom_tst(lambda x1: + -stats.binom.pmf(x1, n, p), + -d, np.ceil(p * n), n, xp=np) + y2 = n - ix + int(d == stats.binom.pmf(ix, n, p)) + assert_allclose(y1, y2, rtol=1e-9) + # Now test for the other side. + k = 7 + i = np.arange(np.floor(p * n) + 1) + d = stats.binom.pmf(k, n, p) + # Old way of calculating y. + y1 = np.sum(stats.binom.pmf(i, n, p) <= d, axis=0) + # New way with binary search. + ix = _binary_search_for_binom_tst(lambda x1: + stats.binom.pmf(x1, n, p), + d, 0, np.floor(p * n), xp=np) + y2 = ix + 1 + assert_allclose(y1, y2, rtol=1e-9) + + # Expected results here are from R 3.6.2 binom.test + @skip_xp_backends("array_api_strict", reason="needs find_root") + @skip_xp_backends("jax.numpy", reason="needs find_root") + @pytest.mark.parametrize('alternative, pval, ci_low, ci_high', + [('less', 0.148831050443, + 0.0, 0.2772002496709138), + ('greater', 0.9004695898947, + 0.1366613252458672, 1.0), + ('two-sided', 0.2983720970096, + 0.1266555521019559, 0.2918426890886281)]) + def test_confidence_intervals1(self, alternative, pval, ci_low, ci_high, xp): + dtype = xp.float64 + res = stats.binomtest(20, n=100, p=xp.asarray(0.25, dtype=dtype), + alternative=alternative) + xp_assert_close(res.pvalue, xp.asarray(pval, dtype=dtype), rtol=1e-12) + xp_assert_equal(res.statistic, xp.asarray(0.2, dtype=dtype)) + ci = res.proportion_ci(confidence_level=0.95) + xp_assert_close(ci.low, xp.asarray(ci_low, dtype=dtype), rtol=1e-12) + xp_assert_close(ci.high, xp.asarray(ci_high, dtype=dtype), rtol=1e-12) + + # Expected results here are from R 3.6.2 binom.test. + @skip_xp_backends("array_api_strict", reason="needs find_root") + @skip_xp_backends("jax.numpy", reason="needs find_root") + @pytest.mark.parametrize('alternative, pval, ci_low, ci_high', + [('less', + 0.005656361, 0.0, 0.1872093), + ('greater', + 0.9987146, 0.008860761, 1.0), + ('two-sided', + 0.01191714, 0.006872485, 0.202706269)]) + def test_confidence_intervals2(self, alternative, pval, ci_low, ci_high, xp): + dtype = xp.float64 + res = stats.binomtest(3, n=50, p=xp.asarray(0.2, dtype=dtype), + alternative=alternative) + xp_assert_close(res.pvalue, xp.asarray(pval, dtype=dtype), rtol=1e-6) + xp_assert_equal(res.statistic, xp.asarray(0.06, dtype=dtype)) + ci = res.proportion_ci(confidence_level=0.99) + xp_assert_close(ci.low, xp.asarray(ci_low, dtype=dtype), rtol=1e-6) + xp_assert_close(ci.high, xp.asarray(ci_high, dtype=dtype), rtol=1e-6) + + # Expected results here are from R 3.6.2 binom.test. + @skip_xp_backends("array_api_strict", reason="needs find_root") + @skip_xp_backends("jax.numpy", reason="needs find_root") + @pytest.mark.parametrize('alternative, pval, ci_high', + [('less', 0.05631351, 0.2588656), + ('greater', 1.0, 1.0), + ('two-sided', 0.07604122, 0.3084971)]) + def test_confidence_interval_exact_k0(self, alternative, pval, ci_high, xp): + # Test with k=0, n = 10. + dtype = xp.float64 + res = stats.binomtest(0, n=10, p=xp.asarray(0.25, dtype=dtype), + alternative=alternative) + xp_assert_close(res.pvalue, xp.asarray(pval, dtype=dtype), rtol=1e-6) + ci = res.proportion_ci(confidence_level=0.95) + xp_assert_equal(ci.low, xp.asarray(0.0, dtype=dtype)) + xp_assert_close(ci.high, xp.asarray(ci_high, dtype=dtype), rtol=1e-6) + + # Expected results here are from R 3.6.2 binom.test. + @skip_xp_backends("array_api_strict", reason="needs find_root") + @skip_xp_backends("jax.numpy", reason="needs find_root") + @pytest.mark.parametrize('alternative, pval, ci_low', + [('less', 1.0, 0.0), + ('greater', 9.536743e-07, 0.7411344), + ('two-sided', 9.536743e-07, 0.6915029)]) + def test_confidence_interval_exact_k_is_n(self, alternative, pval, ci_low, xp): + # Test with k = n = 10. + dtype = xp.float64 + res = stats.binomtest(10, n=10, p=xp.asarray(0.25, dtype=dtype), + alternative=alternative) + xp_assert_close(res.pvalue, xp.asarray(pval, dtype=dtype), rtol=1e-6) + ci = res.proportion_ci(confidence_level=0.95) + xp_assert_equal(ci.high, xp.asarray(1.0, dtype=dtype)) + xp_assert_close(ci.low, xp.asarray(ci_low, dtype=dtype), rtol=1e-6) + + # Expected results are from the prop.test function in R 3.6.2. + @skip_xp_backends("jax.numpy", reason="'two-sided' is incompatible with JAX") + @pytest.mark.parametrize( + 'k, alternative, corr, conf, ci_low, ci_high', + [[3, 'two-sided', True, 0.95, 0.08094782, 0.64632928], + [3, 'two-sided', True, 0.99, 0.0586329, 0.7169416], + [3, 'two-sided', False, 0.95, 0.1077913, 0.6032219], + [3, 'two-sided', False, 0.99, 0.07956632, 0.6799753], + [3, 'less', True, 0.95, 0.0, 0.6043476], + [3, 'less', True, 0.99, 0.0, 0.6901811], + [3, 'less', False, 0.95, 0.0, 0.5583002], + [3, 'less', False, 0.99, 0.0, 0.6507187], + [3, 'greater', True, 0.95, 0.09644904, 1.0], + [3, 'greater', True, 0.99, 0.06659141, 1.0], + [3, 'greater', False, 0.95, 0.1268766, 1.0], + [3, 'greater', False, 0.99, 0.08974147, 1.0], + + [0, 'two-sided', True, 0.95, 0.0, 0.3445372], + [0, 'two-sided', False, 0.95, 0.0, 0.2775328], + [0, 'less', True, 0.95, 0.0, 0.2847374], + [0, 'less', False, 0.95, 0.0, 0.212942], + [0, 'greater', True, 0.95, 0.0, 1.0], + [0, 'greater', False, 0.95, 0.0, 1.0], + + [10, 'two-sided', True, 0.95, 0.6554628, 1.0], + [10, 'two-sided', False, 0.95, 0.7224672, 1.0], + [10, 'less', True, 0.95, 0.0, 1.0], + [10, 'less', False, 0.95, 0.0, 1.0], + [10, 'greater', True, 0.95, 0.7152626, 1.0], + [10, 'greater', False, 0.95, 0.787058, 1.0]] + ) + def test_ci_wilson_method(self, k, alternative, corr, conf, + ci_low, ci_high, xp): + dtype = xp.float64 + res = stats.binomtest(k, n=10, p=xp.asarray(0.1, dtype=dtype), + alternative=alternative) + if corr: + method = 'wilsoncc' + else: + method = 'wilson' + ci = res.proportion_ci(confidence_level=conf, method=method) + xp_assert_close(ci.low, xp.asarray(ci_low, dtype=dtype), rtol=1e-6) + xp_assert_close(ci.high, xp.asarray(ci_high, dtype=dtype), rtol=1e-6) + + @skip_xp_backends("jax.numpy", reason="'two-sided' is incompatible with JAX") + def test_estimate_equals_hypothesized_prop(self, xp): + # Test the special case where the estimated proportion equals + # the hypothesized proportion. When alternative is 'two-sided', + # the p-value is 1. + res = stats.binomtest(4, 16, xp.asarray(0.25)) + xp_assert_equal(res.statistic, xp.asarray(0.25)) + xp_assert_equal(res.pvalue, xp.asarray(1.0)) + + def test_invalid_confidence_level(self, xp): + res = stats.binomtest(3, n=10, p=xp.asarray(0.1), alternative='less') + message = r"confidence_level \(-1\) must be in the interval" + with pytest.raises(ValueError, match=message): + res.proportion_ci(confidence_level=-1) + + def test_invalid_ci_method(self, xp): + res = stats.binomtest(3, n=10, p=xp.asarray(0.1), alternative='greater') + with pytest.raises(ValueError, match=r"method \('plate of shrimp'\) must be"): + res.proportion_ci(method="plate of shrimp") + + def test_invalid_alternative(self, xp): + with pytest.raises(ValueError, match=r"alternative \('ekki'\) not..."): + stats.binomtest(3, n=10, p=xp.asarray(0.1), alternative='ekki') + + def test_alias(self, xp): + res = stats.binomtest(3, n=10, p=xp.asarray(0.1), alternative='greater') + xp_assert_equal(res.proportion_estimate, res.statistic) + + @pytest.mark.skipif(sys.maxsize <= 2**32, reason="32-bit does not overflow") + def test_boost_overflow_raises(self): # NumPy-specific + # Boost.Math error policy should raise exceptions in Python + with pytest.raises(OverflowError, match='Error in function...'): + stats.binomtest(5, 6, p=sys.float_info.min) + + @pytest.mark.parametrize("k, n, p", + [(-1, 10, 0.5), (11, 10, 0.5), (5.5, 10, 0.5), (np.nan, 10, 0.5), + (0, 0, 0.5), (5, 10.5, 0.5), (5, np.nan, 0.5), + (5, 10, -0.1), (5, 10, 1.1), (5, 10, np.nan)]) + def test_invalid(self, k, n, p, xp): + res = stats.binomtest(k, n, xp.asarray(p), alternative='less') + xp_assert_equal(res.statistic, xp.asarray(np.nan)) + xp_assert_equal(res.pvalue, xp.asarray(np.nan)) + + @pytest.mark.parametrize("alternative", ['less', 'greater', 'two-sided']) + @pytest.mark.parametrize("method", ['exact', 'wilson', 'wilsoncc']) + def test_scalar_0d_in_scalar_out(self, alternative, method): + # Tests that scalar / 0-D NumPy array input are equivalent and that both + # result in NumPy scalars (as confirmed by xp_assert_equal). + res = stats.binomtest(3, 11, 0.4, alternative=alternative) + ref = stats.binomtest(np.asarray(3), np.asarray(11), np.asarray(0.4), + alternative=alternative) + + xp_assert_equal(res.statistic, ref.statistic) + xp_assert_equal(res.pvalue, ref.pvalue) + xp_assert_equal(res.n, ref.n) + xp_assert_equal(res.k, ref.k) + + res = res.proportion_ci(method=method) + ref = ref.proportion_ci(method=method) + + xp_assert_equal(res.low, ref.low) + xp_assert_equal(res.high, ref.high) + + @pytest.mark.parametrize("dtype", [None, 'float32', 'float64']) + @pytest.mark.parametrize("alternative", ['less', 'greater', 'two-sided']) + @pytest.mark.parametrize("method", ['exact', 'wilson', 'wilsoncc']) + def test_dtype(self, dtype, alternative, method, xp): + # Tests that output dtype is as expected + dtype = dtype if dtype is None else getattr(xp, dtype) + k = xp.asarray(3, dtype=dtype) + n = xp.asarray(11, dtype=dtype) + p = xp.asarray(0.4, dtype=dtype) + if is_jax(xp) and alternative=='two-sided': + message = "`alternative='two-sided'` is incompatible with JAX arrays." + with pytest.raises(ValueError, match=message): + stats.binomtest(k, n, p) + return + + res = stats.binomtest(k, n, p, alternative=alternative) + ref = stats.binomtest(3, 11, 0.4, alternative=alternative) + + xp_assert_close(res.statistic, xp.asarray(float(ref.statistic), dtype=dtype)) + xp_assert_close(res.pvalue, xp.asarray(float(ref.pvalue), dtype=dtype)) + xp_assert_close(res.n, xp.asarray(float(ref.n), dtype=dtype)) + xp_assert_close(res.k, xp.asarray(float(ref.k), dtype=dtype)) + + if (is_array_api_strict(xp) or is_jax(xp)) and method == 'exact': + # array API strict and JAX don't support exact CI right now + return + + res = res.proportion_ci(method=method) + ref = ref.proportion_ci(method=method) + + xp_assert_close(res.low, xp.asarray(float(ref.low), dtype=dtype)) + xp_assert_close(res.high, xp.asarray(float(ref.high), dtype=dtype)) + + @pytest.mark.parametrize("shape", [(), (7, 8, 9)]) + @pytest.mark.parametrize("alternative", ["less", "greater", "two-sided"]) + @pytest.mark.parametrize("method", ["exact", "wilson", "wilsoncc"]) + def test_ndarray(self, shape, alternative, method, xp): + rng = np.random.default_rng(2150248640) + k = rng.integers(-1, 11, size=shape) + n = rng.integers(-1, 11, size=shape) + p = rng.uniform(-0.1, 1.1, size=shape) + + if is_jax(xp) and alternative=='two-sided': + pytest.skip("`alternative='two-sided'` is incompatible with JAX arrays.") + + res = stats.binomtest(xp.asarray(k), xp.asarray(n), xp.asarray(p), + alternative=alternative) + + @np.vectorize(excluded='alternative') + def binomtest_1d(k, n, p, alternative): + ref = stats.binomtest(k, n, p, alternative=alternative) + ci = ref.proportion_ci(method=method) + return ref.k, ref.n, ref.statistic, ref.pvalue, ci.low, ci.high + + ref_k, ref_n, ref_statistic, ref_pvalue, ci_low, ci_high = binomtest_1d( + k, n, p, alternative) + xp_assert_close(res.k, xp.asarray(ref_k)) + xp_assert_close(res.n, xp.asarray(ref_n)) + xp_assert_close(res.statistic, xp.asarray(ref_statistic)) + xp_assert_close(res.pvalue, xp.asarray(ref_pvalue)) + + if (is_array_api_strict(xp) or is_jax(xp)) and method == 'exact': + # array API strict and JAX don't support exact CI right now + return + + ci = res.proportion_ci(method=method) + xp_assert_close(ci.low, xp.asarray(ci_low)) + xp_assert_close(ci.high, xp.asarray(ci_high)) + + @skip_xp_backends('jax.numpy', reason="'two-sided' alternative needs root finder") + def test_binomtest(self, xp): + # precision tests compared to R for ticket:986 + pp = xp.concat((xp.linspace(0.1, 0.2, 5), + xp.linspace(0.45, 0.65, 5), + xp.linspace(0.85, 0.95, 5))) + n = 501 + x = 450 + results = [0.0, 0.0, 1.0159969301994141e-304, + 2.9752418572150531e-275, 7.7668382922535275e-250, + 2.3381250925167094e-099, 7.8284591587323951e-081, + 9.9155947819961383e-065, 2.8729390725176308e-050, + 1.7175066298388421e-037, 0.0021070691951093692, + 0.12044570587262322, 0.88154763174802508, 0.027120993063129286, + 2.6102587134694721e-006] + + xp_assert_close(stats.binomtest(x, n, pp).pvalue, xp.asarray(results)) + + xp_assert_close(stats.binomtest(50, 100, xp.asarray(0.1)).pvalue, + xp.asarray(5.8320387857343647e-024)) + + @skip_xp_backends('jax.numpy', reason="'two-sided' alternative needs root finder") + def test_binomtest2(self, xp): + # test added for issue #2384 + res2 = [ + [1.0, 1.0], + [0.5, 1.0, 0.5], + [0.25, 1.00, 1.00, 0.25], + [0.125, 0.625, 1.000, 0.625, 0.125], + [0.0625, 0.3750, 1.0000, 1.0000, 0.3750, 0.0625], + [0.03125, 0.21875, 0.68750, 1.00000, 0.68750, 0.21875, 0.03125], + [0.015625, 0.125000, 0.453125, 1.000000, 1.000000, 0.453125, 0.125000, + 0.015625], + [0.0078125, 0.0703125, 0.2890625, 0.7265625, 1.0000000, 0.7265625, + 0.2890625, 0.0703125, 0.0078125], + [0.00390625, 0.03906250, 0.17968750, 0.50781250, 1.00000000, + 1.00000000, 0.50781250, 0.17968750, 0.03906250, 0.00390625], + [0.001953125, 0.021484375, 0.109375000, 0.343750000, 0.753906250, + 1.000000000, 0.753906250, 0.343750000, 0.109375000, 0.021484375, + 0.001953125] + ] + for k in range(1, 11): + v = xp.arange(k+1, dtype=xp.float64) + res1 = stats.binomtest(v, k, 0.5).pvalue + xp_assert_close(res1, xp.asarray(res2[k-1], dtype=xp.float64)) + + @skip_xp_backends('jax.numpy', reason="'two-sided' alternative needs root finder") + def test_binomtest3(self, xp): + # test added for issue #2384 + # test when x == n*p and neighbors + v = xp.arange(1., 11.)[:, xp.newaxis] + k = xp.arange(2., 11.) + shape = (v.shape[0], k.shape[0]) + + res3 = stats.binomtest(v, v*k, 1./k).pvalue + xp_assert_close(res3, xp.ones(shape)) + + # > bt=c() + # > for(i in as.single(1:10)) { + # + for(k in as.single(2:10)) { + # + bt = c(bt, binom.test(i-1, k*i,(1/k))$p.value); + # + print(c(i+1, k*i,(1/k))) + # + } + # + } + binom_testm1 = xp.asarray([ + 0.5, 0.5555555555555556, 0.578125, 0.5904000000000003, + 0.5981224279835393, 0.603430543396034, 0.607304096221924, + 0.610255656871054, 0.612579511000001, 0.625, 0.670781893004115, + 0.68853759765625, 0.6980101120000006, 0.703906431368616, + 0.70793209416498, 0.7108561134173507, 0.713076544331419, + 0.714820192935702, 0.6875, 0.7268709038256367, 0.7418963909149174, + 0.74986110468096, 0.7548015520398076, 0.7581671424768577, + 0.760607984787832, 0.762459425024199, 0.7639120677676575, 0.7265625, + 0.761553963657302, 0.774800934828818, 0.7818005980538996, + 0.78613491480358, 0.789084353140195, 0.7912217659828884, + 0.79284214559524, 0.794112956558801, 0.75390625, 0.7856929451142176, + 0.7976688481430754, 0.8039848974727624, 0.807891868948366, + 0.8105487660137676, 0.812473307174702, 0.8139318233591120, + 0.815075399104785, 0.7744140625, 0.8037322594985427, + 0.814742863657656, 0.8205425178645808, 0.8241275984172285, + 0.8265645374416, 0.8283292196088257, 0.829666291102775, + 0.8307144686362666, 0.7905273437499996, 0.8178712053954738, + 0.828116983756619, 0.833508948940494, 0.8368403871552892, + 0.839104213210105, 0.840743186196171, 0.84198481438049, + 0.8429580531563676, 0.803619384765625, 0.829338573944648, + 0.8389591907548646, 0.84401876783902, 0.84714369697889, + 0.8492667010581667, 0.850803474598719, 0.851967542858308, + 0.8528799045949524, 0.8145294189453126, 0.838881732845347, + 0.847979024541911, 0.852760894015685, 0.8557134656773457, + 0.8577190131799202, 0.85917058278431, 0.860270010472127, + 0.861131648404582, 0.823802947998047, 0.846984756807511, + 0.855635653643743, 0.860180994825685, 0.86298688573253, + 0.864892525675245, 0.866271647085603, 0.867316125625004, + 0.8681346531755114 + ]) + + # > bt=c() + # > for(i in as.single(1:10)) { + # + for(k in as.single(2:10)) { + # + bt = c(bt, binom.test(i+1, k*i,(1/k))$p.value); + # + print(c(i+1, k*i,(1/k))) + # + } + # + } + + binom_testp1 = xp.asarray([ + 0.5, 0.259259259259259, 0.26171875, 0.26272, 0.2632244513031551, + 0.2635138663069203, 0.2636951804161073, 0.2638162407564354, + 0.2639010709000002, 0.625, 0.4074074074074074, 0.42156982421875, + 0.4295746560000003, 0.43473045988554, 0.4383309503172684, + 0.4409884859402103, 0.4430309389962837, 0.444649849401104, 0.6875, + 0.4927602499618962, 0.5096031427383425, 0.5189636628480, + 0.5249280070771274, 0.5290623300865124, 0.5320974248125793, + 0.5344204730474308, 0.536255847400756, 0.7265625, 0.5496019313526808, + 0.5669248746708034, 0.576436455045805, 0.5824538812831795, + 0.5866053321547824, 0.589642781414643, 0.5919618019300193, + 0.593790427805202, 0.75390625, 0.590868349763505, 0.607983393277209, + 0.617303847446822, 0.623172512167948, 0.627208862156123, + 0.6301556891501057, 0.632401894928977, 0.6341708982290303, + 0.7744140625, 0.622562037497196, 0.639236102912278, 0.648263335014579, + 0.65392850011132, 0.657816519817211, 0.660650782947676, + 0.662808780346311, 0.6645068560246006, 0.7905273437499996, + 0.6478843304312477, 0.6640468318879372, 0.6727589686071775, + 0.6782129857784873, 0.681950188903695, 0.684671508668418, + 0.686741824999918, 0.688369886732168, 0.803619384765625, + 0.668716055304315, 0.684360013879534, 0.6927642396829181, + 0.6980155964704895, 0.701609591890657, 0.7042244320992127, + 0.7062125081341817, 0.707775152962577, 0.8145294189453126, + 0.686243374488305, 0.7013873696358975, 0.709501223328243, + 0.714563595144314, 0.718024953392931, 0.7205416252126137, + 0.722454130389843, 0.723956813292035, 0.823802947998047, + 0.701255953767043, 0.715928221686075, 0.723772209289768, + 0.7286603031173616, 0.7319999279787631, 0.7344267920995765, + 0.736270323773157, 0.737718376096348 + ]) + + k, v = xp.asarray(k, dtype=xp.float64), xp.asarray(v, dtype=xp.float64) + binom_testp1 = xp.reshape(xp.asarray(binom_testp1, dtype=xp.float64), shape) + binom_testm1 = xp.reshape(xp.asarray(binom_testm1, dtype=xp.float64), shape) + + res4_p1 = stats.binomtest(v+1, v*k, 1./k).pvalue + res4_m1 = stats.binomtest(v-1, v*k, 1./k).pvalue + + xp_assert_close(res4_p1, binom_testp1) + xp_assert_close(res4_m1, binom_testm1) + + +@make_xp_test_case(stats.fligner) +class TestFligner: + + def _perturb(self, g, rng=124987234782812): + # g arrays have ties to which statistic is very sensitive; break them + rng = np.random.default_rng(rng) + return (np.asarray(g) + 1e-10 * rng.standard_normal(len(g))).tolist() + + @pytest.mark.parametrize('dtype', [None, 'float32', 'float64']) + def test_data(self, dtype, xp): + # numbers from R: fligner.test in package stats + dtype = xp_default_dtype(xp) if dtype is None else getattr(xp, dtype) + x1 = xp.arange(5, dtype=dtype) + res = stats.fligner(x1, x1**2) + ref = (xp.asarray(3.2282229927203536, dtype=dtype), + xp.asarray(0.072379187848207877, dtype=dtype)) + xp_assert_close(res[0], ref[0]) + xp_assert_close(res[1], ref[1]) + + def test_trimmed1(self, xp): + # Perturb input to break ties in the transformed data + # See https://github.com/scipy/scipy/pull/8042 for more details + rng = np.random.default_rng(9952379681) + g1_ = xp.asarray(self._perturb(g1, rng=rng)) + g2_ = xp.asarray(self._perturb(g2, rng=rng)) + g3_ = xp.asarray(self._perturb(g3, rng=rng)) + # Test that center='trimmed' gives the same result as center='mean' + # when proportiontocut=0. + Xsq1, pval1 = stats.fligner(g1_, g2_, g3_, center='mean') + Xsq2, pval2 = stats.fligner(g1_, g2_, g3_, center='trimmed', + proportiontocut=0.0) + xp_assert_close(Xsq1, Xsq2) + xp_assert_close(pval1, pval2) + + @skip_xp_backends(np_only=True, reason="inconsistent tie-breaking across backends") + def test_trimmed_nonregression(self, xp): + # This is a non-regression test + # Expected results are *not* from an external gold standard, + # we're just making sure the results remain consistent + # in the future in case of changes + args = [g1, g2, g3, g4, g5, g6, g7, g8, g9, g10] + args = [xp.asarray(arg, dtype=xp.float64) for arg in args] + W, pval = stats.fligner(*args, center="trimmed", proportiontocut=0.25) + xp_assert_close(W, xp.asarray(15.953569890010614), rtol=5e-14) + xp_assert_close(pval, xp.asarray(0.06785752327432863), rtol=5e-14) + + def test_trimmed_consistency(self, xp): + # Tests for consistency across multiple backends when ties are broken + rng = np.random.default_rng(4839206199) + args = [g1, g2, g3, g4, g5, g6, g7, g8, g9, g10] + args = [self._perturb(arg, rng=rng) for arg in args] + ref = stats.fligner(*args, center="trimmed", proportiontocut=0.25) + args = [xp.asarray(arg, dtype=xp.float64) for arg in args] + res = stats.fligner(*args, center="trimmed", proportiontocut=0.25) + xp_assert_close(res.statistic, xp.asarray(ref.statistic), rtol=5e-14) + xp_assert_close(res.pvalue, xp.asarray(ref.pvalue), rtol=5e-14) + + def test_bad_center_value(self, xp): + x = xp.linspace(-1, 1, 21) + message = "center must be 'mean', 'median' or 'trimmed'." + with pytest.raises(ValueError, match=message): + stats.fligner(x, x, center='trim') + + def test_bad_num_args(self, xp): + # Too few args raises ValueError. + message = "Must provide at least two samples." + with pytest.raises(ValueError, match=message): + stats.fligner(xp.asarray([1, 2])) + + def test_empty_arg(self, xp): + x = xp.arange(5.) + with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit): + res = stats.fligner(x, x**2, xp.asarray([])) + xp_assert_equal(res.statistic, xp.asarray(xp.nan)) + xp_assert_equal(res.pvalue, xp.asarray(xp.nan)) + + +def mood_cases_with_ties(): + # Generate random `x` and `y` arrays with ties both between and within the + # samples. Expected results are (statistic, pvalue) from SAS. + expected_results = [(-1.76658511464992, .0386488678399305), + (-.694031428192304, .2438312498647250), + (-1.15093525352151, .1248794365836150)] + seeds = [23453254, 1298352315, 987234597] + for si, seed in enumerate(seeds): + rng = np.random.default_rng(seed) + xy = rng.random(100) + # Generate random indices to make ties + tie_ind = rng.integers(low=0, high=99, size=5) + # Generate a random number of ties for each index. + num_ties_per_ind = rng.integers(low=1, high=5, size=5) + # At each `tie_ind`, mark the next `n` indices equal to that value. + for i, n in zip(tie_ind, num_ties_per_ind): + for j in range(i + 1, i + n): + xy[j] = xy[i] + # scramble order of xy before splitting into `x, y` + rng.shuffle(xy) + x, y = np.split(xy, 2) + yield x, y, 'less', *expected_results[si] + + +@make_xp_test_case(stats.mood) +class TestMood: + @pytest.mark.parametrize("x,y,alternative,stat_expect,p_expect", + list(mood_cases_with_ties())) + def test_against_SAS(self, x, y, alternative, stat_expect, p_expect, xp): + """ + Example code used to generate SAS output: + DATA myData; + INPUT X Y; + CARDS; + 1 0 + 1 1 + 1 2 + 1 3 + 1 4 + 2 0 + 2 1 + 2 4 + 2 9 + 2 16 + ods graphics on; + proc npar1way mood data=myData ; + class X; + ods output MoodTest=mt; + proc contents data=mt; + proc print data=mt; + format Prob1 17.16 Prob2 17.16 Statistic 17.16 Z 17.16 ; + title "Mood Two-Sample Test"; + proc print data=myData; + title "Data for above results"; + run; + """ + x, y = xp.asarray(x.tolist()), xp.asarray(y.tolist()) + statistic, pvalue = stats.mood(x, y, alternative=alternative) + xp_assert_close(statistic, xp.asarray(stat_expect), atol=1e-16) + xp_assert_close(pvalue, xp.asarray(p_expect), atol=1e-16) + + @pytest.mark.parametrize("dtype", [None, 'float32', 'float64']) + @pytest.mark.parametrize("alternative, expected", + [('two-sided', (1.019938533549930, + .3077576129778760)), + ('less', (1.019938533549930, + 1 - .1538788064889380)), + ('greater', (1.019938533549930, + .1538788064889380))]) + def test_against_SAS_2(self, dtype, alternative, expected, xp): + # Code to run in SAS in above function + dtype = xp_default_dtype(xp) if dtype is None else getattr(xp, dtype) + x = [111, 107, 100, 99, 102, 106, 109, 108, 104, 99, + 101, 96, 97, 102, 107, 113, 116, 113, 110, 98] + y = [107, 108, 106, 98, 105, 103, 110, 105, 104, 100, + 96, 108, 103, 104, 114, 114, 113, 108, 106, 99] + x, y = xp.asarray(x, dtype=dtype), xp.asarray(y, dtype=dtype) + res = stats.mood(x, y, alternative=alternative) + xp_assert_close(res.statistic, xp.asarray(expected[0], dtype=dtype)) + xp_assert_close(res.pvalue, xp.asarray(expected[1], dtype=dtype)) + + def test_mood_order_of_args(self, xp): + # z should change sign when the order of arguments changes, pvalue + # should not change + rng = np.random.default_rng(4058827756) + x1 = xp.asarray(rng.standard_normal((10, 1))) + x2 = xp.asarray(rng.standard_normal((15, 1))) + z1, p1 = stats.mood(x1, x2) + z2, p2 = stats.mood(x2, x1) + xp_assert_close(z2, -z1) + xp_assert_close(p2, p1) + + def test_mood_with_axis_none(self, xp): + # Test with axis = None, compare with results from R + x1 = [-0.626453810742332, 0.183643324222082, -0.835628612410047, + 1.59528080213779, 0.329507771815361, -0.820468384118015, + 0.487429052428485, 0.738324705129217, 0.575781351653492, + -0.305388387156356, 1.51178116845085, 0.389843236411431, + -0.621240580541804, -2.2146998871775, 1.12493091814311, + -0.0449336090152309, -0.0161902630989461, 0.943836210685299, + 0.821221195098089, 0.593901321217509] + + x2 = [-0.896914546624981, 0.184849184646742, 1.58784533120882, + -1.13037567424629, -0.0802517565509893, 0.132420284381094, + 0.707954729271733, -0.23969802417184, 1.98447393665293, + -0.138787012119665, 0.417650750792556, 0.981752777463662, + -0.392695355503813, -1.03966897694891, 1.78222896030858, + -2.31106908460517, 0.878604580921265, 0.035806718015226, + 1.01282869212708, 0.432265154539617, 2.09081920524915, + -1.19992581964387, 1.58963820029007, 1.95465164222325, + 0.00493777682814261, -2.45170638784613, 0.477237302613617, + -0.596558168631403, 0.792203270299649, 0.289636710177348] + + x1 = xp.reshape(xp.asarray(x1), (10, 2)) + x2 = xp.reshape(xp.asarray(x2), (15, 2)) + res = stats.mood(x1, x2, axis=None) + xp_assert_close(res.statistic, xp.asarray(-1.31716607555)) + xp_assert_close(res.pvalue, xp.asarray(0.18778296257)) + + @pytest.mark.parametrize('rng_method, args', [('standard_normal', tuple()), + ('integers', (8,))]) + def test_mood_2d(self, rng_method, args, xp): + # Test if the results of mood test in 2-D vectorized call are consistent + # with result when looping over the slices. + ny = 5 + rng = np.random.default_rng(3602349075) + rng_method = getattr(rng, rng_method) + x1 = rng_method(*args, size=(10, ny)) + x2 = rng_method(*args, size=(15, ny)) + dtype = xp.float64 + res = stats.mood(xp.asarray(x1, dtype=dtype), xp.asarray(x2, dtype=dtype)) + + for j in range(ny): + ref = stats.mood(x1[:, j], x2[:, j]) + xp_assert_close(res.statistic[j], xp.asarray(ref.statistic)) + xp_assert_close(res.pvalue[j], xp.asarray(ref.pvalue)) + + # inverse order of dimensions + x1 = x1.transpose() + x2 = x2.transpose() + res = stats.mood(xp.asarray(x1, dtype=dtype), xp.asarray(x2, dtype=dtype), + axis=1) + + for i in range(ny): + # check axis handling is self consistent + ref = stats.mood(x1[i, :], x2[i, :]) + xp_assert_close(res.statistic[i], xp.asarray(ref.statistic), atol=1e-14) + xp_assert_close(res.pvalue[i], xp.asarray(ref.pvalue)) + + @pytest.mark.parametrize('rng_method, args', [('standard_normal', tuple()), + ('integers', (8,))]) + def test_mood_3d(self, rng_method, args, xp): + shape = (10, 5, 6) + rng = np.random.default_rng(3602349075) + rng_method = getattr(rng, rng_method) + x1 = xp.asarray(rng_method(*args, size=shape)) + x2 = xp.asarray(rng_method(*args, size=shape)) + + for axis in range(3): + res = stats.mood(x1, x2, axis=axis) + # Tests that result for 3-D arrays is equal to that for the + # same calculation on a set of 1-D arrays taken from the + # 3-D array + axes_idx = ([1, 2], [0, 2], [0, 1]) # the two axes != axis + for i in range(shape[axes_idx[axis][0]]): + for j in range(shape[axes_idx[axis][1]]): + if axis == 0: + slice1 = x1[:, i, j] + slice2 = x2[:, i, j] + elif axis == 1: + slice1 = x1[i, :, j] + slice2 = x2[i, :, j] + else: + slice1 = x1[i, j, :] + slice2 = x2[i, j, :] + + ref = stats.mood(slice1, slice2) + xp_assert_close(res.statistic[i, j], ref.statistic, atol=1e-15) + xp_assert_close(res.pvalue[i, j], ref.pvalue) + + def test_mood_bad_arg(self, xp): + # Warns when the sum of the lengths of the args is less than 3 + with eager_warns(SmallSampleWarning, match=too_small_1d_not_omit, xp=xp): + res = stats.mood(xp.asarray([1.]), xp.asarray([])) + xp_assert_equal(res.statistic, xp.asarray(np.nan)) + xp_assert_equal(res.pvalue, xp.asarray(np.nan)) + + @pytest.mark.parametrize("dtype", [None, 'float32', 'float64']) + def test_mood_alternative(self, dtype, xp): + dtype = xp_default_dtype(xp) if dtype is None else getattr(xp, dtype) + + rng = np.random.RandomState(0) + x = stats.norm.rvs(scale=0.75, size=100, random_state=rng) + y = stats.norm.rvs(scale=1.25, size=100, random_state=rng) + x, y = xp.asarray(x, dtype=dtype), xp.asarray(y, dtype=dtype) + + stat1, p1 = stats.mood(x, y, alternative='two-sided') + stat2, p2 = stats.mood(x, y, alternative='less') + stat3, p3 = stats.mood(x, y, alternative='greater') + + assert stat1 == stat2 == stat3 + xp_assert_close(p1, xp.asarray(0., dtype=dtype), atol=1e-7) + xp_assert_close(p2, xp.asarray(p1/2, dtype=dtype)) + xp_assert_close(p3, xp.asarray(1 - p1/2, dtype=dtype)) + + with pytest.raises(ValueError, match="`alternative` must be..."): + stats.mood(x, y, alternative='ekki-ekki') + + +class TestProbplot: + + def test_basic(self): + x = stats.norm.rvs(size=20, random_state=12345) + osm, osr = stats.probplot(x, fit=False) + osm_expected = [-1.8241636, -1.38768012, -1.11829229, -0.91222575, + -0.73908135, -0.5857176, -0.44506467, -0.31273668, + -0.18568928, -0.06158146, 0.06158146, 0.18568928, + 0.31273668, 0.44506467, 0.5857176, 0.73908135, + 0.91222575, 1.11829229, 1.38768012, 1.8241636] + assert_allclose(osr, np.sort(x)) + assert_allclose(osm, osm_expected) + + res, res_fit = stats.probplot(x, fit=True) + res_fit_expected = [1.05361841, 0.31297795, 0.98741609] + assert_allclose(res_fit, res_fit_expected) + + def test_sparams_keyword(self): + x = stats.norm.rvs(size=100, random_state=123456) + # Check that None, () and 0 (loc=0, for normal distribution) all work + # and give the same results + osm1, osr1 = stats.probplot(x, sparams=None, fit=False) + osm2, osr2 = stats.probplot(x, sparams=0, fit=False) + osm3, osr3 = stats.probplot(x, sparams=(), fit=False) + assert_allclose(osm1, osm2) + assert_allclose(osm1, osm3) + assert_allclose(osr1, osr2) + assert_allclose(osr1, osr3) + # Check giving (loc, scale) params for normal distribution + osm, osr = stats.probplot(x, sparams=(), fit=False) + + def test_dist_keyword(self): + x = stats.norm.rvs(size=20, random_state=12345) + osm1, osr1 = stats.probplot(x, fit=False, dist='t', sparams=(3,)) + osm2, osr2 = stats.probplot(x, fit=False, dist=stats.t, sparams=(3,)) + assert_allclose(osm1, osm2) + assert_allclose(osr1, osr2) + + assert_raises(ValueError, stats.probplot, x, dist='wrong-dist-name') + assert_raises(AttributeError, stats.probplot, x, dist=[]) + + class custom_dist: + """Some class that looks just enough like a distribution.""" + def ppf(self, q): + return stats.norm.ppf(q, loc=2) + + osm1, osr1 = stats.probplot(x, sparams=(2,), fit=False) + osm2, osr2 = stats.probplot(x, dist=custom_dist(), fit=False) + assert_allclose(osm1, osm2) + assert_allclose(osr1, osr2) + + @pytest.mark.skipif(not have_matplotlib, reason="no matplotlib") + def test_plot_kwarg(self): + fig = plt.figure() + fig.add_subplot(111) + x = stats.t.rvs(3, size=100, random_state=7654321) + res1, fitres1 = stats.probplot(x, plot=plt) + plt.close() + res2, fitres2 = stats.probplot(x, plot=None) + res3 = stats.probplot(x, fit=False, plot=plt) + plt.close() + res4 = stats.probplot(x, fit=False, plot=None) + # Check that results are consistent between combinations of `fit` and + # `plot` keywords. + assert_(len(res1) == len(res2) == len(res3) == len(res4) == 2) + assert_allclose(res1, res2) + assert_allclose(res1, res3) + assert_allclose(res1, res4) + assert_allclose(fitres1, fitres2) + + # Check that a Matplotlib Axes object is accepted + fig = plt.figure() + ax = fig.add_subplot(111) + stats.probplot(x, fit=False, plot=ax) + plt.close() + + def test_probplot_bad_args(self): + # Raise ValueError when given an invalid distribution. + assert_raises(ValueError, stats.probplot, [1], dist="plate_of_shrimp") + + def test_empty(self): + assert_equal(stats.probplot([], fit=False), + (np.array([]), np.array([]))) + assert_equal(stats.probplot([], fit=True), + ((np.array([]), np.array([])), + (np.nan, np.nan, 0.0))) + + def test_array_of_size_one(self): + message = "One or more sample arguments is too small..." + with (np.errstate(invalid='ignore'), + pytest.warns(SmallSampleWarning, match=message)): + assert_equal(stats.probplot([1], fit=True), + ((np.array([0.]), np.array([1])), + (np.nan, np.nan, np.nan))) + + +@make_xp_test_case(stats.wilcoxon) +class TestWilcoxon: + def test_wilcoxon_bad_arg(self, xp): + # Raise ValueError when two args of different lengths are given or + # zero_method is unknown. + x = xp.asarray([1, 2]) + d = xp.asarray([1]*10) + message = "`zero_method` must be one of..." + with pytest.raises(ValueError, match=message): + stats.wilcoxon(x, x, "dummy...") + message = "`alternative` must be one of" + with pytest.raises(ValueError, match=message): + stats.wilcoxon(x, x, alternative="dummy") + message = "`method` must be one of..." + with pytest.raises(ValueError, match=message): + stats.wilcoxon(d, method="xyz") + + def test_zero_diff(self, xp): + x = xp.arange(20) + # pratt and wilcox do not work if x - y == 0 and method == "asymptotic" + # => warning may be emitted and p-value is nan + with np.errstate(invalid="ignore"): + w, p = stats.wilcoxon(x, x, "wilcox", method="asymptotic") + xp_assert_equal(w, xp.asarray(0.0)) + xp_assert_equal(p, xp.asarray(xp.nan)) + w, p = stats.wilcoxon(x, x, "pratt", method="asymptotic") + xp_assert_equal(w, xp.asarray(0.0)) + xp_assert_equal(p, xp.asarray(xp.nan)) + # ranksum is n*(n+1)/2, split in half if zero_method == "zsplit" + w, p = stats.wilcoxon(x, x, "zsplit", method="asymptotic") + xp_assert_equal(w, xp.asarray(20*21/4)) + xp_assert_equal(p, xp.asarray(1.0)) + + def test_pratt(self, xp): + # regression test for gh-6805: p-value matches value from R package + # coin (wilcoxsign_test) reported in the issue + x = xp.asarray([1, 2, 3, 4]) + y = xp.asarray([1, 2, 3, 5]) + res = stats.wilcoxon(x, y, zero_method="pratt", method="asymptotic", + correction=False) + xp_assert_close(res.statistic, xp.asarray(0.0)) + xp_assert_close(res.pvalue, xp.asarray(0.31731050786291415)) + + def test_wilcoxon_arg_type(self): + # Should be able to accept list as arguments. + # Address issue 6070. + arr = [1, 2, 3, 0, -1, 3, 1, 2, 1, 1, 2] + + _ = stats.wilcoxon(arr, zero_method="pratt", method="asymptotic") + _ = stats.wilcoxon(arr, zero_method="zsplit", method="asymptotic") + _ = stats.wilcoxon(arr, zero_method="wilcox", method="asymptotic") + + def test_accuracy_wilcoxon(self, xp): + freq = [1, 4, 16, 15, 8, 4, 5, 1, 2] + nums = range(-4, 5) + x = xp.asarray(np.concatenate([[u] * v for u, v in zip(nums, freq)])) + y = xp.zeros_like(x) + + T, p = stats.wilcoxon(x, y, "pratt", method="asymptotic", + correction=False) + xp_assert_close(T, xp.asarray(423.)) + xp_assert_close(p, xp.asarray(0.0031724568006762576)) + + T, p = stats.wilcoxon(x, y, "zsplit", method="asymptotic", + correction=False) + xp_assert_equal(T, xp.asarray(441.)) + xp_assert_close(p, xp.asarray(0.0032145343172473055)) + + T, p = stats.wilcoxon(x, y, "wilcox", method="asymptotic", + correction=False) + xp_assert_equal(T, xp.asarray(327.)) + xp_assert_close(p, xp.asarray(0.00641346115861)) + + # Test the 'correction' option, using values computed in R with: + # > options(digits=16) + # > wilcox.test(x, y, paired=TRUE, exact=FALSE, correct={FALSE,TRUE}) + x = xp.asarray([120, 114, 181, 188, 180, 146, 121, 191, 132, 113, 127, 112]) + y = xp.asarray([133, 143, 119, 189, 112, 199, 198, 113, 115, 121, 142, 187]) + T, p = stats.wilcoxon(x, y, correction=False, method="asymptotic") + xp_assert_equal(T, xp.asarray(34.)) + xp_assert_close(p, xp.asarray(0.6948866023724735)) + T, p = stats.wilcoxon(x, y, correction=True, method="asymptotic") + xp_assert_equal(T, xp.asarray(34.)) + xp_assert_close(p, xp.asarray(0.7240816609153895)) + + @pytest.mark.parametrize("kwarg", + [{"method": "approx"}, {"mode": "approx"}, {"mode": "asymptotic"}]) + def test_approx_mode(self, kwarg, xp): + # Check that `mode` is still an alias of keyword `method`, + # and `"approx"` is still an alias of argument `"asymptotic"` + x = xp.asarray([3, 5, 23, 7, 243, 58, 98, 2, 8, -3, 9, 11]) + y = xp.asarray([2, -2, 1, 23, 0, 5, 12, 18, 99, 12, 17, 27]) + res = stats.wilcoxon(x, y, "wilcox", **kwarg) + ref = stats.wilcoxon(x, y, "wilcox", method="asymptotic") + xp_assert_equal(res.statistic, ref.statistic) + xp_assert_equal(res.pvalue, ref.pvalue) + + def test_wilcoxon_result_attributes(self, xp): + x = xp.asarray([120, 114, 181, 188, 180, 146, 121, 191, 132, 113, 127, 112]) + y = xp.asarray([133, 143, 119, 189, 112, 199, 198, 113, 115, 121, 142, 187]) + res = stats.wilcoxon(x, y, correction=False, method="asymptotic") + attributes = ('statistic', 'pvalue') + check_named_results(res, attributes, xp=xp) + + def test_wilcoxon_has_zstatistic(self, xp): + rng = np.random.default_rng(89426135444) + x, y = rng.random(15), rng.random(15) + x, y = xp.asarray(x), xp.asarray(y) + + res = stats.wilcoxon(x, y, method="asymptotic") + ref = special.ndtri(res.pvalue/2) + xp_assert_close(res.zstatistic, ref) + + if is_jax(xp): + return + + res = stats.wilcoxon(x, y, method="exact") + assert not hasattr(res, 'zstatistic') + + res = stats.wilcoxon(x, y) + assert not hasattr(res, 'zstatistic') + + def test_wilcoxon_tie(self, xp): + # Regression test for gh-2391. + # Corresponding R code is: + # > result = wilcox.test(rep(0.1, 10), exact=FALSE, correct=FALSE) + # > result$p.value + # [1] 0.001565402258002551 + # > result = wilcox.test(rep(0.1, 10), exact=FALSE, correct=TRUE) + # > result$p.value + # [1] 0.00190419504300439 + d = xp.asarray([0.1] * 10) + expected_stat = xp.asarray(0.0) + + stat, p = stats.wilcoxon(d, method="asymptotic", correction=False) + expected_p = xp.asarray(0.001565402258002551) + xp_assert_equal(stat, expected_stat) + xp_assert_close(p, expected_p) + + stat, p = stats.wilcoxon(d, method="asymptotic", correction=True) + expected_p = xp.asarray(0.00190419504300439) + xp_assert_equal(stat, expected_stat) + xp_assert_close(p, expected_p) + + @pytest.mark.parametrize('dtype', [None, 'float32', 'float64']) + def test_onesided(self, dtype, xp): + # tested against "R version 4.0.3 (2020-10-10)" + # + # x <- c(125, 115, 130, 140, 140, 115, 140, 125, 140, 135) + # y <- c(110, 122, 125, 120, 140, 124, 123, 137, 135, 145) + # cfg <- list(x = x, y = y, paired = TRUE, exact = FALSE) + # do.call(wilcox.test, c(cfg, list(alternative = "less", correct = FALSE))) + # do.call(wilcox.test, c(cfg, list(alternative = "less", correct = TRUE))) + # do.call(wilcox.test, c(cfg, list(alternative = "greater", correct = FALSE))) + # do.call(wilcox.test, c(cfg, list(alternative = "greater", correct = TRUE))) + + dtype = dtype if dtype is None else getattr(xp, dtype) + x = xp.asarray([125, 115, 130, 140, 140, 115, 140, 125, 140, 135], dtype=dtype) + y = xp.asarray([110, 122, 125, 120, 140, 124, 123, 137, 135, 145], dtype=dtype) + + w_ref = xp.asarray(27.0, dtype=dtype) + w, p = stats.wilcoxon(x, y, alternative="less", method="asymptotic", + correction=False) + xp_assert_equal(w, w_ref) + xp_assert_close(p, xp.asarray(0.7031847042787, dtype=dtype)) + + w, p = stats.wilcoxon(x, y, alternative="less", correction=True, + method="asymptotic") + xp_assert_equal(w, w_ref) + xp_assert_close(p, xp.asarray(0.72336564289, dtype=dtype)) + + w, p = stats.wilcoxon(x, y, alternative="greater", + method="asymptotic", correction=False) + xp_assert_equal(w, w_ref) + xp_assert_close(p, xp.asarray(0.2968152957213, dtype=dtype)) + + w, p = stats.wilcoxon(x, y, alternative="greater", + correction=True, method="asymptotic") + xp_assert_equal(w, w_ref) + xp_assert_close(p, xp.asarray(0.3176446594176, dtype=dtype)) + + def test_exact_basic(self): # exact distribution is NumPy-only + for n in range(1, 51): + pmf1 = _get_wilcoxon_distr(n) + pmf2 = _get_wilcoxon_distr2(n) + assert_equal(n*(n+1)/2 + 1, len(pmf1)) + assert_equal(sum(pmf1), 1) + assert_array_almost_equal(pmf1, pmf2) + + @skip_xp_backends("jax.numpy", reason="`method='exact'` is incompatible with JAX") + @pytest.mark.parametrize('dtype', [None, 'float32', 'float64']) + def test_exact_pval(self, dtype, xp): + # expected values computed with "R version 4.0.3 (2020-10-10)" + # options(digits=16) + # x < - c(1.81, 0.82, 1.56, -0.48, 0.81, 1.28, -1.04, 0.23, -0.75, 0.14) + # y < - c(0.71, 0.65, -0.2, 0.85, -1.1, -0.45, -0.84, -0.24, -0.68, -0.76) + # result = wilcox.test(x - y, exact=TRUE, alternative='l', correct=TRUE) + # result$p.value + dtype = dtype if dtype is None else getattr(xp, dtype) + x = xp.asarray([1.81, 0.82, 1.56, -0.48, 0.81, 1.28, -1.04, 0.23, + -0.75, 0.14], dtype=dtype) + y = xp.asarray([0.71, 0.65, -0.2, 0.85, -1.1, -0.45, -0.84, -0.24, + -0.68, -0.76], dtype=dtype) + _, p = stats.wilcoxon(x, y, alternative="two-sided", method="exact") + xp_assert_close(p, xp.asarray(0.10546875, dtype=dtype)) + _, p = stats.wilcoxon(x, y, alternative="less", method="exact") + xp_assert_close(p, xp.asarray(0.95800781256, dtype=dtype)) + _, p = stats.wilcoxon(x, y, alternative="greater", method="exact") + xp_assert_close(p, xp.asarray(0.052734375, dtype=dtype)) + + x = xp.arange(0., 20., dtype=dtype) + 0.5 + y = xp.arange(20., 0., -1., dtype=dtype) + _, p = stats.wilcoxon(x, y, alternative="two-sided", method="exact") + xp_assert_close(p, xp.asarray(0.8694877624511719, dtype=dtype)) + _, p = stats.wilcoxon(x, y, alternative="less", method="exact") + xp_assert_close(p, xp.asarray(0.4347438812255859, dtype=dtype)) + _, p = stats.wilcoxon(x, y, alternative="greater", method="exact") + xp_assert_close(p, xp.asarray(0.5795888900756836, dtype=dtype)) + + # These inputs were chosen to give a W statistic that is either the + # center of the distribution (when the length of the support is odd), or + # the value to the left of the center (when the length of the support is + # even). Also, the numbers are chosen so that the W statistic is the + # sum of the positive values. + + @skip_xp_backends("jax.numpy", reason="`method='exact'` is incompatible with JAX") + @pytest.mark.parametrize('x', [[-1, -2, 3], + [-1, 2, -3, -4, 5], + [-1, -2, 3, -4, -5, -6, 7, 8]]) + def test_exact_p_1(self, x, xp): + w, p = stats.wilcoxon(xp.asarray(x)) + x = np.array(x) + wtrue = x[x > 0].sum() + xp_assert_equal(w, xp.asarray(float(wtrue))) + xp_assert_equal(p, xp.asarray(1.0)) + + def test_auto(self, xp): + # auto default to exact if there are no ties and n <= 50 + x = xp.arange(0., 50.) + 0.5 + y = xp.arange(50., 0., -1.) + + if is_jax(xp): # + message = "When using `wilcoxon` with `jax.numpy` arrays..." + with pytest.raises(ValueError, match=message): + stats.wilcoxon(x, y, method="auto") + with pytest.raises(ValueError, match=message): + stats.wilcoxon(x, y, method="exact") + return + + xp_assert_equal(stats.wilcoxon(x, y).pvalue, + stats.wilcoxon(x, y, method="exact").pvalue) + + # n <= 50: if there are zeros in d = x-y, use PermutationMethod + pm = stats.PermutationMethod() + d = np.arange(-2, 5) + w, p = stats.wilcoxon(d) + # rerunning the test gives the same results since n_resamples + # is large enough to get deterministic results if n <= 13 + # so we do not need to use a seed. to avoid longer runtimes of the + # test, use n=7 only. For n=13, see test_auto_permutation_edge_case + assert_equal((w, p), stats.wilcoxon(d, method=pm)) + + # for larger vectors (n > 13) with ties/zeros, use asymptotic test + d = xp.arange(-5, 9) # zero + _, p = stats.wilcoxon(d) + xp_assert_equal(stats.wilcoxon(d, method="asymptotic").pvalue, xp.asarray(p)) + + d = xpx.at(d)[d == 0].set(1) # tie + _, p = stats.wilcoxon(d) + xp_assert_equal(stats.wilcoxon(d, method="asymptotic").pvalue, xp.asarray(p)) + + # use approximation for samples > 50 + d = xp.arange(1, 52) + _, p = stats.wilcoxon(d) + xp_assert_equal(stats.wilcoxon(d, method="asymptotic").pvalue, xp.asarray(p)) + + @pytest.mark.xslow + @skip_xp_backends("jax.numpy", reason="lazy -> limited `method` choices") + def test_auto_permutation_edge_case(self, xp): + # Check that `PermutationMethod()` is used and results are deterministic when + # `method='auto'`, there are zeros or ties in `d = x-y`, and `len(d) <= 13`. + d = np.arange(-5, 8) # zero + res = stats.wilcoxon(xp.asarray(d)) + # generated with stats.wilcoxon(d, method=PermutationMethod()) + w, p = xp.asarray([27.5, 0.3955078125]) + xp_assert_equal(res.statistic, w) + xp_assert_equal(res.pvalue, p) + + d[d == 0] = 1 # tie + res = stats.wilcoxon(xp.asarray(d)) + # generated with stats.wilcoxon(d, method=PermutationMethod()) + w, p = xp.asarray([32, 0.3779296875]) + xp_assert_equal(res.statistic, w) + xp_assert_equal(res.pvalue, p) + + @pytest.mark.parametrize('size', [3, 5, 10]) + def test_permutation_method(self, size, xp): + rng = np.random.default_rng(92348034828501345) + x = rng.random(size=size).tolist() + res = stats.wilcoxon(xp.asarray(x), method=stats.PermutationMethod()) + ref = stats.wilcoxon(x, method='exact') # all backends test against NumPy + dtype = xp_default_dtype(xp) + xp_assert_equal(res.statistic, xp.asarray(ref.statistic, dtype=dtype)) + xp_assert_equal(res.pvalue, xp.asarray(ref.pvalue, dtype=dtype)) + + x = xp.asarray(rng.random(size=size*10)) + rng = np.random.default_rng(59234803482850134) + pm = stats.PermutationMethod(n_resamples=99, rng=rng) + ref = stats.wilcoxon(x, method=pm) + # preserve use of old random_state during SPEC 7 transition + rng = np.random.default_rng(59234803482850134) + pm = stats.PermutationMethod(n_resamples=99, random_state=rng) + res = stats.wilcoxon(x, method=pm) + + xp_assert_equal(xp.round(res.pvalue*100)/100, res.pvalue) # n_resamples used + xp_assert_equal(res.pvalue, ref.pvalue) # rng/random_state used + + @skip_xp_backends("jax.numpy", reason="`method='auto'` is incompatible with JAX") + def test_method_auto_nan_propagate_ND_length_gt_50_gh20591(self, xp): + # When method!='asymptotic', nan_policy='propagate', and a slice of + # a >1 dimensional array input contained NaN, the result object of + # `wilcoxon` could (under yet other conditions) return `zstatistic` + # for some slices but not others. This resulted in an error because + # `apply_along_axis` would have to create a ragged array. + # Check that this is resolved. + rng = np.random.default_rng(235889269872456) + A = rng.normal(size=(51, 2)) # length along slice > exact threshold + A[5, 1] = np.nan + A = xp.asarray(A) + res = stats.wilcoxon(A) + ref = stats.wilcoxon(A, method='asymptotic') + xp_assert_close(res.statistic, ref.statistic) + xp_assert_close(res.pvalue, ref.pvalue) + assert hasattr(ref, 'zstatistic') + assert not hasattr(res, 'zstatistic') + + @pytest.mark.parametrize('method', ['exact', 'asymptotic']) + def test_symmetry_gh19872_gh20752(self, method, xp): + if is_jax(xp) and method == 'exact': + pytest.skip("`method='exact'` is incompatible with JAX") + # Check that one-sided exact tests obey required symmetry. Bug reported + # in gh-19872 and again in gh-20752; example from gh-19872 is more concise: + var1 = xp.asarray([62, 66, 61, 68, 74, 62, 68, 62, 55, 59]) + var2 = xp.asarray([71, 71, 69, 61, 75, 71, 77, 72, 62, 65]) + ref = stats.wilcoxon(var1, var2, alternative='less', method=method) + res = stats.wilcoxon(var2, var1, alternative='greater', method=method) + max_statistic = var1.shape[0] * (var1.shape[0] + 1) / 2 + assert int(res.statistic) != res.statistic + xp_assert_close(max_statistic - res.statistic, ref.statistic) + xp_assert_close(res.pvalue, ref.pvalue) + + @pytest.mark.parametrize("method", ('exact', stats.PermutationMethod())) + def test_all_zeros_exact(self, method, xp): + if is_jax(xp) and method == 'exact': + pytest.skip("`method='exact'` is incompatible with JAX") + # previously, this raised a RuntimeWarning when calculating Z, even + # when the Z value was not needed. Confirm that this no longer + # occurs when `method` is 'exact' or a `PermutationMethod`. + res = stats.wilcoxon(xp.zeros(5), method=method) + xp_assert_close(res.statistic, xp.asarray(0.)) + xp_assert_close(res.pvalue, xp.asarray(1.)) + + def test_wilcoxon_axis_broadcasting_errors_gh22051(self, xp): + # In previous versions of SciPy, `wilcoxon` gave an incorrect error + # message when `AxisError` was not found in the base NumPy namespace. + # Check that this is resolved with and without the ANP decorator. + x = xp.asarray([1, 2, 3]) + y = xp.asarray([4, 5, 6]) + + message = "Array shapes are incompatible for broadcasting." + with pytest.raises(ValueError, match=message): + stats.wilcoxon(x, y[:-1]) + + if not is_numpy(xp): + return # hard to generalize the rest + + message = "operands could not be broadcast together with..." + with pytest.raises(ValueError, match=message): + stats.wilcoxon(x, y[:-1], _no_deco=True) + + AxisError = getattr(np, 'AxisError', None) or np.exceptions.AxisError + message = "source: axis 3 is out of bounds for array of dimension 1" + with pytest.raises(AxisError, match=message): + stats.wilcoxon(x, y, axis=3) + + message = "`axis` must be compatible with the shape..." + with pytest.raises(AxisError, match=message): + stats.wilcoxon(x, y, axis=3, _no_deco=True) + + +# data for k-statistics tests from +# https://cran.r-project.org/web/packages/kStatistics/kStatistics.pdf +# see nKS "Examples" +x_kstat = [16.34, 10.76, 11.84, 13.55, 15.85, 18.20, 7.51, 10.22, 12.52, 14.68, + 16.08, 19.43, 8.12, 11.20, 12.95, 14.77, 16.83, 19.80, 8.55, 11.58, + 12.10, 15.02, 16.83, 16.98, 19.92, 9.47, 11.68, 13.41, 15.35, 19.11] + + +@make_xp_test_case(stats.kstat) +class TestKstat: + def test_moments_normal_distribution(self, xp): + rng = np.random.RandomState(32149) + data = xp.asarray(rng.randn(12345), dtype=xp.float64) + moments = xp.stack([stats.kstat(data, n) for n in [1, 2, 3, 4]]) + + expected = xp.asarray([0.011315, 1.017931, 0.05811052, 0.0754134], + dtype=data.dtype) + xp_assert_close(moments, expected, rtol=1e-4) + + # test equivalence with `stats.moment` + m1 = stats.moment(data, order=1) + m2 = stats.moment(data, order=2) + m3 = stats.moment(data, order=3) + xp_assert_close(xp.stack((m1, m2, m3)), expected[:-1], atol=0.02, rtol=1e-2) + + @pytest.mark.filterwarnings("ignore:invalid value encountered") # Dask + def test_empty_input(self, xp): + with eager_warns(SmallSampleWarning, match=too_small_1d_not_omit, xp=xp): + res = stats.kstat(xp.asarray([])) + xp_assert_equal(res, xp.asarray(xp.nan)) + + def test_nan_input(self, xp): + data = xp.arange(10.) + data = xp.where(data == 6, xp.nan, data) + + xp_assert_equal(stats.kstat(data), xp.asarray(xp.nan)) + + @pytest.mark.parametrize('n', [0, 4.001]) + def test_kstat_bad_arg(self, n, xp): + # Raise ValueError if n > 4 or n < 1. + data = xp.arange(10) + message = 'k-statistics only supported for 1<=n<=4' + with pytest.raises(ValueError, match=message): + stats.kstat(data, n=n) + + @pytest.mark.parametrize('case', [(1, 14.02166666666667), + (2, 12.65006954022974), + (3, -1.447059503280798), + (4, -141.6682291883626)]) + def test_against_R(self, case, xp): + # Test against reference values computed with R kStatistics, e.g. + # options(digits=16) + # library(kStatistics) + # data <-c (16.34, 10.76, 11.84, 13.55, 15.85, 18.20, 7.51, 10.22, + # 12.52, 14.68, 16.08, 19.43, 8.12, 11.20, 12.95, 14.77, + # 16.83, 19.80, 8.55, 11.58, 12.10, 15.02, 16.83, 16.98, + # 19.92, 9.47, 11.68, 13.41, 15.35, 19.11) + # nKS(4, data) + n, ref = case + res = stats.kstat(xp.asarray(x_kstat), n) + xp_assert_close(res, xp.asarray(ref)) + + +@make_xp_test_case(stats.kstatvar) +class TestKstatVar: + @pytest.mark.filterwarnings("ignore:invalid value encountered") # Dask + def test_empty_input(self, xp): + x = xp.asarray([]) + with eager_warns(SmallSampleWarning, match=too_small_1d_not_omit, xp=xp): + res = stats.kstatvar(x) + xp_assert_equal(res, xp.asarray(xp.nan)) + + def test_nan_input(self, xp): + data = xp.arange(10.) + data = xp.where(data == 6, xp.nan, data) + + xp_assert_equal(stats.kstat(data), xp.asarray(xp.nan)) + + def test_bad_arg(self, xp): + # Raise ValueError is n is not 1 or 2. + data = xp.asarray([1]) + n = 10 + message = 'Only n=1 or n=2 supported.' + with pytest.raises(ValueError, match=message): + stats.kstatvar(data, n=n) + + def test_against_R_mathworld(self, xp): + # Test against reference values computed using formulas exactly as + # they appear at https://mathworld.wolfram.com/k-Statistic.html + # This is *really* similar to how they appear in the implementation, + # but that could change, and this should not. + n = len(x_kstat) + k2 = 12.65006954022974 # see source code in TestKstat + k4 = -141.6682291883626 + + res = stats.kstatvar(xp.asarray(x_kstat), 1) + ref = k2 / n + xp_assert_close(res, xp.asarray(ref)) + + res = stats.kstatvar(xp.asarray(x_kstat), 2) + # *unbiased estimator* for var(k2) + ref = (2*k2**2*n + (n-1)*k4) / (n * (n+1)) + xp_assert_close(res, xp.asarray(ref)) + + +class TestPpccPlot: + def setup_method(self): + self.x = _old_loggamma_rvs(5, size=500, random_state=7654321) + 5 + + def test_basic(self): + N = 5 + svals, ppcc = stats.ppcc_plot(self.x, -10, 10, N=N) + ppcc_expected = [0.21139644, 0.21384059, 0.98766719, 0.97980182, + 0.93519298] + assert_allclose(svals, np.linspace(-10, 10, num=N)) + assert_allclose(ppcc, ppcc_expected) + + def test_dist(self): + # Test that we can specify distributions both by name and as objects. + svals1, ppcc1 = stats.ppcc_plot(self.x, -10, 10, dist='tukeylambda') + svals2, ppcc2 = stats.ppcc_plot(self.x, -10, 10, + dist=stats.tukeylambda) + assert_allclose(svals1, svals2, rtol=1e-20) + assert_allclose(ppcc1, ppcc2, rtol=1e-20) + # Test that 'tukeylambda' is the default dist + svals3, ppcc3 = stats.ppcc_plot(self.x, -10, 10) + assert_allclose(svals1, svals3, rtol=1e-20) + assert_allclose(ppcc1, ppcc3, rtol=1e-20) + + @pytest.mark.skipif(not have_matplotlib, reason="no matplotlib") + def test_plot_kwarg(self): + # Check with the matplotlib.pyplot module + fig = plt.figure() + ax = fig.add_subplot(111) + stats.ppcc_plot(self.x, -20, 20, plot=plt) + fig.delaxes(ax) + + # Check that a Matplotlib Axes object is accepted + ax = fig.add_subplot(111) + stats.ppcc_plot(self.x, -20, 20, plot=ax) + plt.close() + + def test_invalid_inputs(self): + # `b` has to be larger than `a` + assert_raises(ValueError, stats.ppcc_plot, self.x, 1, 0) + + # Raise ValueError when given an invalid distribution. + assert_raises(ValueError, stats.ppcc_plot, [1, 2, 3], 0, 1, + dist="plate_of_shrimp") + + def test_empty(self): + # For consistency with probplot return for one empty array, + # ppcc contains all zeros and svals is the same as for normal array + # input. + svals, ppcc = stats.ppcc_plot([], 0, 1) + assert_allclose(svals, np.linspace(0, 1, num=80)) + assert_allclose(ppcc, np.zeros(80, dtype=float)) + + +class TestPpccMax: + def test_ppcc_max_bad_arg(self): + # Raise ValueError when given an invalid distribution. + data = [1] + assert_raises(ValueError, stats.ppcc_max, data, dist="plate_of_shrimp") + + def test_ppcc_max_basic(self): + x = stats.tukeylambda.rvs(-0.7, loc=2, scale=0.5, size=10000, + random_state=1234567) + 1e4 + assert_almost_equal(stats.ppcc_max(x), -0.71215366521264145, decimal=7) + + def test_dist(self): + x = stats.tukeylambda.rvs(-0.7, loc=2, scale=0.5, size=10000, + random_state=1234567) + 1e4 + + # Test that we can specify distributions both by name and as objects. + max1 = stats.ppcc_max(x, dist='tukeylambda') + max2 = stats.ppcc_max(x, dist=stats.tukeylambda) + assert_almost_equal(max1, -0.71215366521264145, decimal=5) + assert_almost_equal(max2, -0.71215366521264145, decimal=5) + + # Test that 'tukeylambda' is the default dist + max3 = stats.ppcc_max(x) + assert_almost_equal(max3, -0.71215366521264145, decimal=5) + + def test_brack(self): + x = stats.tukeylambda.rvs(-0.7, loc=2, scale=0.5, size=10000, + random_state=1234567) + 1e4 + assert_raises(ValueError, stats.ppcc_max, x, brack=(0.0, 1.0, 0.5)) + + assert_almost_equal(stats.ppcc_max(x, brack=(0, 1)), + -0.71215366521264145, decimal=7) + + assert_almost_equal(stats.ppcc_max(x, brack=(-2, 2)), + -0.71215366521264145, decimal=7) + + +@make_xp_test_case(stats.boxcox_llf) +class TestBoxcox_llf: + + @pytest.mark.parametrize("dtype", ["float32", "float64"]) + def test_basic(self, dtype, xp): + dt = getattr(xp, dtype) + x = stats.norm.rvs(size=10000, loc=10, random_state=54321) + lmbda = 1 + llf = stats.boxcox_llf(lmbda, xp.asarray(x, dtype=dt)) + llf_expected = -x.size / 2. * np.log(np.sum(x.std()**2)) + xp_assert_close(llf, xp.asarray(llf_expected, dtype=dt)) + + @skip_xp_backends(np_only=True, + reason='array-likes only accepted for NumPy backend.') + def test_array_like(self, xp): + x = stats.norm.rvs(size=100, loc=10, random_state=54321) + lmbda = 1 + llf = stats.boxcox_llf(lmbda, x) + llf2 = stats.boxcox_llf(lmbda, list(x)) + xp_assert_close(llf, llf2, rtol=1e-12) + + def test_2d_input(self, xp): + # Note: boxcox_llf() was already working with 2-D input (sort of), so + # keep it like that. boxcox() doesn't work with 2-D input though, due + # to brent() returning a scalar. + x = stats.norm.rvs(size=100, loc=10, random_state=54321) + lmbda = 1 + llf = stats.boxcox_llf(lmbda, x) + llf2 = stats.boxcox_llf(lmbda, np.vstack([x, x]).T) + xp_assert_close(xp.asarray([llf, llf]), xp.asarray(llf2), rtol=1e-12) + + def test_empty(self, xp): + message = "One or more sample arguments is too small..." + with eager_warns(SmallSampleWarning, match=message, xp=xp): + assert xp.isnan(xp.asarray(stats.boxcox_llf(1, xp.asarray([])))) + + def test_gh_6873(self, xp): + # Regression test for gh-6873. + # This example was taken from gh-7534, a duplicate of gh-6873. + data = xp.asarray([198.0, 233.0, 233.0, 392.0]) + llf = stats.boxcox_llf(-8, data) + # The expected value was computed with mpmath. + xp_assert_close(llf, xp.asarray(-17.93934208579061)) + + def test_instability_gh20021(self, xp): + data = xp.asarray([2003, 1950, 1997, 2000, 2009], dtype=xp.float64) + llf = stats.boxcox_llf(1e-8, data) + # The expected value was computed with mpsci, set mpmath.mp.dps=100 + # expect float64 output for integer input + xp_assert_close(llf, xp.asarray(-15.32401272869016598, dtype=xp.float64), + rtol=5e-7) # bumped tolerance from 1e-7 for Accelerate + + def test_axis(self, xp): + data = xp.asarray([[100, 200], [300, 400]]) + llf_axis_0 = stats.boxcox_llf(1, data, axis=0) + llf_0 = xp.stack([ + stats.boxcox_llf(1, data[:, 0]), + stats.boxcox_llf(1, data[:, 1]), + ]) + xp_assert_close(llf_axis_0, llf_0) + llf_axis_1 = stats.boxcox_llf(1, data, axis=1) + llf_1 = xp.stack([ + stats.boxcox_llf(1, data[0, :]), + stats.boxcox_llf(1, data[1, :]), + ]) + xp_assert_close(llf_axis_1, llf_1) + + +# This is the data from GitHub user Qukaiyi, given as an example +# of a data set that caused boxcox to fail. +_boxcox_data = [ + 15957, 112079, 1039553, 711775, 173111, 307382, 183155, 53366, 760875, + 207500, 160045, 473714, 40194, 440319, 133261, 265444, 155590, 36660, + 904939, 55108, 138391, 339146, 458053, 63324, 1377727, 1342632, 41575, + 68685, 172755, 63323, 368161, 199695, 538214, 167760, 388610, 398855, + 1001873, 364591, 1320518, 194060, 194324, 2318551, 196114, 64225, 272000, + 198668, 123585, 86420, 1925556, 695798, 88664, 46199, 759135, 28051, + 345094, 1977752, 51778, 82746, 638126, 2560910, 45830, 140576, 1603787, + 57371, 548730, 5343629, 2298913, 998813, 2156812, 423966, 68350, 145237, + 131935, 1600305, 342359, 111398, 1409144, 281007, 60314, 242004, 113418, + 246211, 61940, 95858, 957805, 40909, 307955, 174159, 124278, 241193, + 872614, 304180, 146719, 64361, 87478, 509360, 167169, 933479, 620561, + 483333, 97416, 143518, 286905, 597837, 2556043, 89065, 69944, 196858, + 88883, 49379, 916265, 1527392, 626954, 54415, 89013, 2883386, 106096, + 402697, 45578, 349852, 140379, 34648, 757343, 1305442, 2054757, 121232, + 606048, 101492, 51426, 1820833, 83412, 136349, 1379924, 505977, 1303486, + 95853, 146451, 285422, 2205423, 259020, 45864, 684547, 182014, 784334, + 174793, 563068, 170745, 1195531, 63337, 71833, 199978, 2330904, 227335, + 898280, 75294, 2011361, 116771, 157489, 807147, 1321443, 1148635, 2456524, + 81839, 1228251, 97488, 1051892, 75397, 3009923, 2732230, 90923, 39735, + 132433, 225033, 337555, 1204092, 686588, 1062402, 40362, 1361829, 1497217, + 150074, 551459, 2019128, 39581, 45349, 1117187, 87845, 1877288, 164448, + 10338362, 24942, 64737, 769946, 2469124, 2366997, 259124, 2667585, 29175, + 56250, 74450, 96697, 5920978, 838375, 225914, 119494, 206004, 430907, + 244083, 219495, 322239, 407426, 618748, 2087536, 2242124, 4736149, 124624, + 406305, 240921, 2675273, 4425340, 821457, 578467, 28040, 348943, 48795, + 145531, 52110, 1645730, 1768364, 348363, 85042, 2673847, 81935, 169075, + 367733, 135474, 383327, 1207018, 93481, 5934183, 352190, 636533, 145870, + 55659, 146215, 73191, 248681, 376907, 1606620, 169381, 81164, 246390, + 236093, 885778, 335969, 49266, 381430, 307437, 350077, 34346, 49340, + 84715, 527120, 40163, 46898, 4609439, 617038, 2239574, 159905, 118337, + 120357, 430778, 3799158, 3516745, 54198, 2970796, 729239, 97848, 6317375, + 887345, 58198, 88111, 867595, 210136, 1572103, 1420760, 574046, 845988, + 509743, 397927, 1119016, 189955, 3883644, 291051, 126467, 1239907, 2556229, + 411058, 657444, 2025234, 1211368, 93151, 577594, 4842264, 1531713, 305084, + 479251, 20591, 1466166, 137417, 897756, 594767, 3606337, 32844, 82426, + 1294831, 57174, 290167, 322066, 813146, 5671804, 4425684, 895607, 450598, + 1048958, 232844, 56871, 46113, 70366, 701618, 97739, 157113, 865047, + 194810, 1501615, 1765727, 38125, 2733376, 40642, 437590, 127337, 106310, + 4167579, 665303, 809250, 1210317, 45750, 1853687, 348954, 156786, 90793, + 1885504, 281501, 3902273, 359546, 797540, 623508, 3672775, 55330, 648221, + 266831, 90030, 7118372, 735521, 1009925, 283901, 806005, 2434897, 94321, + 309571, 4213597, 2213280, 120339, 64403, 8155209, 1686948, 4327743, + 1868312, 135670, 3189615, 1569446, 706058, 58056, 2438625, 520619, 105201, + 141961, 179990, 1351440, 3148662, 2804457, 2760144, 70775, 33807, 1926518, + 2362142, 186761, 240941, 97860, 1040429, 1431035, 78892, 484039, 57845, + 724126, 3166209, 175913, 159211, 1182095, 86734, 1921472, 513546, 326016, + 1891609 +] + + +class TransformTest: + def test_nan_policy(self): + transform = getattr(stats, self.transform) + normmax = getattr(stats, f"{self.transform}_normmax") + special_transform = getattr(special, self.transform, _yeojohnson_transform) + + rng = np.random.default_rng(5908294582456923546) + x = rng.random(10) + x[3] = np.nan + + # lmb provided + lmb = 1.0 + + with pytest.raises(ValueError, match="The input contains nan values"): + transform(x, lmb, nan_policy='raise') + + res = transform(x, lmb, nan_policy='propagate') + np.testing.assert_allclose(res, special_transform(x, 1.)) + + res = transform(x, lmb, nan_policy='omit') + np.testing.assert_allclose(res, transform(x, lmb, nan_policy='propagate')) + + # lmb not provided + with pytest.raises(ValueError, match="The input contains nan values"): + transform(x, nan_policy='raise') + + res = transform(x, nan_policy='propagate') + np.testing.assert_equal(res[0], special_transform(x, np.nan)) + np.testing.assert_equal(res[1], np.nan) + + kwarg = {'method': 'mle'} if transform == stats.boxcox else {} + res = transform(x, nan_policy='omit') + lmb = normmax(x, nan_policy='omit', **kwarg) + ref = transform(x, lmb) + np.testing.assert_allclose(res[0], ref) + np.testing.assert_allclose(res[1], lmb) + + +class TestBoxcox(TransformTest): + transform = 'boxcox' + + def test_fixed_lmbda(self): + x = _old_loggamma_rvs(5, size=50, random_state=12345) + 5 + xt = stats.boxcox(x, lmbda=1) + assert_allclose(xt, x - 1) + xt = stats.boxcox(x, lmbda=-1) + assert_allclose(xt, 1 - 1/x) + + xt = stats.boxcox(x, lmbda=0) + assert_allclose(xt, np.log(x)) + + # Also test that array_like input works + xt = stats.boxcox(list(x), lmbda=0) + assert_allclose(xt, np.log(x)) + + # test that constant input is accepted; see gh-12225 + xt = stats.boxcox(np.ones(10), 2) + assert_equal(xt, np.zeros(10)) + + def test_lmbda_None(self): + # Start from normal rv's, do inverse transform to check that + # optimization function gets close to the right answer. + lmbda = 2.5 + x = stats.norm.rvs(loc=10, size=50000, random_state=1245) + x_inv = (x * lmbda + 1)**(-lmbda) + xt, maxlog = stats.boxcox(x_inv) + + assert_almost_equal(maxlog, -1 / lmbda, decimal=2) + + def test_alpha(self): + rng = np.random.RandomState(1234) + x = _old_loggamma_rvs(5, size=50, random_state=rng) + 5 + + # Some regular values for alpha, on a small sample size + _, _, interval = stats.boxcox(x, alpha=0.75) + assert_allclose(interval, [4.004485780226041, 5.138756355035744]) + _, _, interval = stats.boxcox(x, alpha=0.05) + assert_allclose(interval, [1.2138178554857557, 8.209033272375663]) + + # Try some extreme values, see we don't hit the N=500 limit + x = _old_loggamma_rvs(7, size=500, random_state=rng) + 15 + _, _, interval = stats.boxcox(x, alpha=0.001) + assert_allclose(interval, [0.3988867, 11.40553131]) + _, _, interval = stats.boxcox(x, alpha=0.999) + assert_allclose(interval, [5.83316246, 5.83735292]) + + def test_boxcox_bad_arg(self): + # Raise ValueError if any data value is negative. + x = np.array([-1, 2]) + assert_raises(ValueError, stats.boxcox, x) + # Raise ValueError if data is constant. + assert_raises(ValueError, stats.boxcox, np.array([1])) + # Raise ValueError if data is not 1-dimensional. + assert_raises(ValueError, stats.boxcox, np.array([[1], [2]])) + + def test_empty(self): + assert_(stats.boxcox([]).shape == (0,)) + + def test_gh_6873(self): + # Regression test for gh-6873. + y, lam = stats.boxcox(_boxcox_data) + # The expected value of lam was computed with the function + # powerTransform in the R library 'car'. I trust that value + # to only about five significant digits. + assert_allclose(lam, -0.051654, rtol=1e-5) + + @pytest.mark.parametrize("bounds", [(-1, 1), (1.1, 2), (-2, -1.1)]) + def test_bounded_optimizer_within_bounds(self, bounds): + # Define custom optimizer with bounds. + def optimizer(fun): + return optimize.minimize_scalar(fun, bounds=bounds, + method="bounded") + + _, lmbda = stats.boxcox(_boxcox_data, lmbda=None, optimizer=optimizer) + assert bounds[0] < lmbda < bounds[1] + + def test_bounded_optimizer_against_unbounded_optimizer(self): + # Test whether setting bounds on optimizer excludes solution from + # unbounded optimizer. + + # Get unbounded solution. + _, lmbda = stats.boxcox(_boxcox_data, lmbda=None) + + # Set tolerance and bounds around solution. + bounds = (lmbda + 0.1, lmbda + 1) + options = {'xatol': 1e-12} + + def optimizer(fun): + return optimize.minimize_scalar(fun, bounds=bounds, + method="bounded", options=options) + + # Check bounded solution. Lower bound should be active. + _, lmbda_bounded = stats.boxcox(_boxcox_data, lmbda=None, + optimizer=optimizer) + assert lmbda_bounded != lmbda + assert_allclose(lmbda_bounded, bounds[0]) + + @pytest.mark.parametrize("optimizer", ["str", (1, 2), 0.1]) + def test_bad_optimizer_type_raises_error(self, optimizer): + # Check if error is raised if string, tuple or float is passed + with pytest.raises(ValueError, match="`optimizer` must be a callable"): + stats.boxcox(_boxcox_data, lmbda=None, optimizer=optimizer) + + def test_bad_optimizer_value_raises_error(self): + # Check if error is raised if `optimizer` function does not return + # `OptimizeResult` object + + # Define test function that always returns 1 + def optimizer(fun): + return 1 + + message = "return an object containing the optimal `lmbda`" + with pytest.raises(ValueError, match=message): + stats.boxcox(_boxcox_data, lmbda=None, optimizer=optimizer) + + @pytest.mark.parametrize( + "bad_x", [np.array([1, -42, 12345.6]), np.array([np.inf, 42, 1])] + ) + def test_negative_x_value_raises_error(self, bad_x): + """Test boxcox_normmax raises ValueError if x contains non-positive values.""" + message = "only positive, finite, real numbers" + with pytest.raises(ValueError, match=message): + stats.boxcox_normmax(bad_x) + + @pytest.mark.parametrize('x', [ + # Attempt to trigger overflow in power expressions. + np.array([2003.0, 1950.0, 1997.0, 2000.0, 2009.0, + 2009.0, 1980.0, 1999.0, 2007.0, 1991.0]), + # Attempt to trigger overflow with a large optimal lambda. + np.array([2003.0, 1950.0, 1997.0, 2000.0, 2009.0]), + # Attempt to trigger overflow with large data. + np.array([2003.0e200, 1950.0e200, 1997.0e200, 2000.0e200, 2009.0e200]) + ]) + def test_overflow(self, x): + with pytest.warns(UserWarning, match="The optimal lambda is"): + xt_bc, lam_bc = stats.boxcox(x) + assert np.all(np.isfinite(xt_bc)) + + def test_confidence_interval_nan_policy(self): + # other outputs with `nan_policy` tested in superclass + rng = np.random.default_rng(5908294582456923546) + x = rng.random(10) + x[3] = np.nan + alpha = 0.5 + + with pytest.raises(ValueError, match="The input contains nan values"): + stats.boxcox(x, alpha=alpha, nan_policy='raise') + + res = stats.boxcox(x, alpha=alpha, nan_policy='propagate')[2] + np.testing.assert_equal(res, (np.nan, np.nan)) + + res = stats.boxcox(x, alpha=alpha, nan_policy='omit')[2] + ref = stats.boxcox(x[~np.isnan(x)], alpha=alpha)[2] + np.testing.assert_allclose(res, ref) + + +class NormmaxTest: + def test_nan_policy(self): + transform_normmax = getattr(stats, self.transform_normmax) + rng = np.random.default_rng(5908294582456923546) + x = rng.random(10) + x[3] = np.nan + + with pytest.raises(ValueError, match="The input contains nan values"): + transform_normmax(x, nan_policy='raise') + + res = transform_normmax(x, nan_policy='propagate') + np.testing.assert_equal(res, np.nan) + + res = transform_normmax(x, nan_policy='omit') + ref = transform_normmax(x[~np.isnan(x)]) + np.testing.assert_allclose(res, ref) + + +class TestBoxcoxNormmax(NormmaxTest): + transform_normmax = 'boxcox_normmax' + + def setup_method(self): + self.x = _old_loggamma_rvs(5, size=50, random_state=12345) + 5 + + def test_pearsonr(self): + maxlog = stats.boxcox_normmax(self.x) + assert_allclose(maxlog, 1.804465, rtol=1e-6) + + def test_mle(self): + maxlog = stats.boxcox_normmax(self.x, method='mle') + assert_allclose(maxlog, 1.758101, rtol=1e-6) + + # Check that boxcox() uses 'mle' + _, maxlog_boxcox = stats.boxcox(self.x) + assert_allclose(maxlog_boxcox, maxlog) + + def test_all(self): + maxlog_all = stats.boxcox_normmax(self.x, method='all') + assert_allclose(maxlog_all, [1.804465, 1.758101], rtol=1e-6) + + @pytest.mark.parametrize("method", ["mle", "pearsonr", "all"]) + @pytest.mark.parametrize("bounds", [(-1, 1), (1.1, 2), (-2, -1.1)]) + def test_bounded_optimizer_within_bounds(self, method, bounds): + + def optimizer(fun): + return optimize.minimize_scalar(fun, bounds=bounds, + method="bounded") + + maxlog = stats.boxcox_normmax(self.x, method=method, + optimizer=optimizer) + assert np.all(bounds[0] < maxlog) + assert np.all(maxlog < bounds[1]) + + @pytest.mark.slow + def test_user_defined_optimizer(self): + # tests an optimizer that is not based on scipy.optimize.minimize + lmbda = stats.boxcox_normmax(self.x) + lmbda_rounded = np.round(lmbda, 5) + lmbda_range = np.linspace(lmbda_rounded-0.01, lmbda_rounded+0.01, 1001) + + class MyResult: + pass + + def optimizer(fun): + # brute force minimum over the range + objs = [] + for lmbda in lmbda_range: + objs.append(fun(lmbda)) + res = MyResult() + res.x = lmbda_range[np.argmin(objs)] + return res + + lmbda2 = stats.boxcox_normmax(self.x, optimizer=optimizer) + assert lmbda2 != lmbda # not identical + assert_allclose(lmbda2, lmbda, 1e-5) # but as close as it should be + + def test_user_defined_optimizer_and_brack_raises_error(self): + optimizer = optimize.minimize_scalar + + # Using default `brack=None` with user-defined `optimizer` works as + # expected. + stats.boxcox_normmax(self.x, brack=None, optimizer=optimizer) + + # Using user-defined `brack` with user-defined `optimizer` is expected + # to throw an error. Instead, users should specify + # optimizer-specific parameters in the optimizer function itself. + with pytest.raises(ValueError, match="`brack` must be None if " + "`optimizer` is given"): + + stats.boxcox_normmax(self.x, brack=(-2.0, 2.0), + optimizer=optimizer) + + @pytest.mark.parametrize( + 'x', ([2003.0, 1950.0, 1997.0, 2000.0, 2009.0], + [0.50000471, 0.50004979, 0.50005902, 0.50009312, 0.50001632])) + def test_overflow(self, x): + message = "The optimal lambda is..." + with pytest.warns(UserWarning, match=message): + lmbda = stats.boxcox_normmax(x, method='mle') + assert np.isfinite(special.boxcox(x, lmbda)).all() + # 10000 is safety factor used in boxcox_normmax + ymax = np.finfo(np.float64).max / 10000 + x_treme = np.max(x) if lmbda > 0 else np.min(x) + y_extreme = special.boxcox(x_treme, lmbda) + assert_allclose(y_extreme, ymax * np.sign(lmbda)) + + def test_negative_ymax(self): + with pytest.raises(ValueError, match="`ymax` must be strictly positive"): + stats.boxcox_normmax(self.x, ymax=-1) + + @pytest.mark.parametrize("x", [ + # positive overflow in float64 + np.array([2003.0, 1950.0, 1997.0, 2000.0, 2009.0], + dtype=np.float64), + # negative overflow in float64 + np.array([0.50000471, 0.50004979, 0.50005902, 0.50009312, 0.50001632], + dtype=np.float64), + # positive overflow in float32 + np.array([200.3, 195.0, 199.7, 200.0, 200.9], + dtype=np.float32), + # negative overflow in float32 + np.array([2e-30, 1e-30, 1e-30, 1e-30, 1e-30, 1e-30], + dtype=np.float32), + ]) + @pytest.mark.parametrize("ymax", [1e10, 1e30, None]) + # TODO: add method "pearsonr" after fix overflow issue + @pytest.mark.parametrize("method", ["mle"]) + def test_user_defined_ymax_input_float64_32(self, x, ymax, method): + # Test the maximum of the transformed data close to ymax + with pytest.warns(UserWarning, match="The optimal lambda is"): + kwarg = {'ymax': ymax} if ymax is not None else {} + lmb = stats.boxcox_normmax(x, method=method, **kwarg) + x_treme = [np.min(x), np.max(x)] + ymax_res = max(abs(stats.boxcox(x_treme, lmb))) + if ymax is None: + # 10000 is safety factor used in boxcox_normmax + ymax = np.finfo(x.dtype).max / 10000 + assert_allclose(ymax, ymax_res, rtol=1e-5) + + @pytest.mark.parametrize("x", [ + # positive overflow in float32 but not float64 + [200.3, 195.0, 199.7, 200.0, 200.9], + # negative overflow in float32 but not float64 + [2e-30, 1e-30, 1e-30, 1e-30, 1e-30, 1e-30], + ]) + # TODO: add method "pearsonr" after fix overflow issue + @pytest.mark.parametrize("method", ["mle"]) + def test_user_defined_ymax_inf(self, x, method): + x_32 = np.asarray(x, dtype=np.float32) + x_64 = np.asarray(x, dtype=np.float64) + + # assert overflow with float32 but not float64 + with pytest.warns(UserWarning, match="The optimal lambda is"): + stats.boxcox_normmax(x_32, method=method) + stats.boxcox_normmax(x_64, method=method) + + # compute the true optimal lambda then compare them + lmb_32 = stats.boxcox_normmax(x_32, ymax=np.inf, method=method) + lmb_64 = stats.boxcox_normmax(x_64, ymax=np.inf, method=method) + assert_allclose(lmb_32, lmb_64, rtol=1e-2) + + def test_nan_policy_all(self): + # `nan_policy` with other `method`s tested in superclass + rng = np.random.default_rng(5908294582456923546) + x = rng.random(10) + x[3] = np.nan + + with pytest.raises(ValueError, match="The input contains nan values"): + stats.boxcox_normmax(x, method='all', nan_policy='raise') + + res = stats.boxcox_normmax(x, method='all', nan_policy='propagate') + np.testing.assert_equal(res, (np.nan, np.nan)) + + res = stats.boxcox_normmax(x, method='all', nan_policy='omit') + ref = stats.boxcox_normmax(x[~np.isnan(x)], method='all') + np.testing.assert_allclose(res, ref) + + +class TestBoxcoxNormplot: + def setup_method(self): + self.x = _old_loggamma_rvs(5, size=500, random_state=7654321) + 5 + + def test_basic(self): + N = 5 + lmbdas, ppcc = stats.boxcox_normplot(self.x, -10, 10, N=N) + ppcc_expected = [0.57783375, 0.83610988, 0.97524311, 0.99756057, + 0.95843297] + assert_allclose(lmbdas, np.linspace(-10, 10, num=N)) + assert_allclose(ppcc, ppcc_expected) + + @pytest.mark.skipif(not have_matplotlib, reason="no matplotlib") + def test_plot_kwarg(self): + # Check with the matplotlib.pyplot module + fig = plt.figure() + ax = fig.add_subplot(111) + stats.boxcox_normplot(self.x, -20, 20, plot=plt) + fig.delaxes(ax) + + # Check that a Matplotlib Axes object is accepted + ax = fig.add_subplot(111) + stats.boxcox_normplot(self.x, -20, 20, plot=ax) + plt.close() + + def test_invalid_inputs(self): + # `lb` has to be larger than `la` + assert_raises(ValueError, stats.boxcox_normplot, self.x, 1, 0) + # `x` can not contain negative values + assert_raises(ValueError, stats.boxcox_normplot, [-1, 1], 0, 1) + + def test_empty(self): + assert_(stats.boxcox_normplot([], 0, 1).size == 0) + + +@make_xp_test_case(stats.yeojohnson_llf) +class TestYeojohnson_llf: + + def test_array_like(self): + # array_like not applicable with SCIPY_ARRAY_API=1 + x = stats.norm.rvs(size=100, loc=0, random_state=54321) + lmbda = 1 + llf = stats.yeojohnson_llf(lmbda, x) + llf2 = stats.yeojohnson_llf(lmbda, list(x)) + assert_allclose(llf, llf2, rtol=1e-12) + + def test_2d_input(self, xp): + x = stats.norm.rvs(size=100, loc=10, random_state=54321) + x = xp.asarray(x) + lmbda = 1 + ref = stats.yeojohnson_llf(lmbda, x) + res = stats.yeojohnson_llf(lmbda, xp.stack([x, x]).T) + xp_assert_close(res, xp.stack((ref, ref)), rtol=1e-12) + + def test_empty(self, xp): + message = "One or more sample arguments is too small..." + with eager_warns(SmallSampleWarning, match=message, xp=xp): + assert xp.isnan(stats.yeojohnson_llf(1, xp.asarray([]))) + + @skip_xp_backends('jax.numpy', reason="JIT can't special-case for all >0 / <0") + def test_gh24172(self, xp): + # Test all negative and all positive data + data = xp.asarray([10, 10, 10, 9.9], dtype=xp.float64) + # The expected value was computed with mpsci, set mpmath.mp.dps=100 + # 1. Test precision loss + xp_assert_close(stats.yeojohnson_llf(-14, data), + xp.asarray(12.418595783381280, dtype=xp.float64), rtol=1e-7) + xp_assert_close(stats.yeojohnson_llf(16, -data), + xp.asarray(12.418595783381280, dtype=xp.float64), rtol=1e-7) + # 2. Test overflow + xp_assert_close(stats.yeojohnson_llf(-20, data), + xp.asarray(12.360966379200528, dtype=xp.float64), rtol=1e-7) + xp_assert_close(stats.yeojohnson_llf(20, -data), + xp.asarray(12.380287243698629, dtype=xp.float64), rtol=1e-7) + + @pytest.mark.parametrize("lmb, ref", + [(-1., 6.757930444097559), + (0., 11.90755408530727), + (1., 14.179952211901629), + (2., 13.040657538043835)]) + def test_against_reference(self, lmb, ref, xp): + # Reference values generated with mpsci, e.g. + # import numpy as np + # from mpmath import mp + # from mpsci.stats import yeojohnson_llf + # mp.dps = 100 + # rng = np.random.default_rng(78435782834992002) + # x = rng.uniform(-1, 1, size=25) + # float(yeojohnson_llf(-1, x)) # 6.757930444097559 + rng = np.random.default_rng(78435782834992002) + x = rng.uniform(-1, 1, size=25) + res = stats.yeojohnson_llf(lmb, xp.asarray(x.tolist())) + xp_assert_close(res, xp.asarray(ref)) + + +class TestYeojohnson(TransformTest): + transform = 'yeojohnson' + + def test_fixed_lmbda(self): + rng = np.random.RandomState(12345) + + # Test positive input + x = _old_loggamma_rvs(5, size=50, random_state=rng) + 5 + assert np.all(x > 0) + xt = stats.yeojohnson(x, lmbda=1) + assert_allclose(xt, x) + xt = stats.yeojohnson(x, lmbda=-1) + assert_allclose(xt, 1 - 1 / (x + 1)) + xt = stats.yeojohnson(x, lmbda=0) + assert_allclose(xt, np.log(x + 1)) + xt = stats.yeojohnson(x, lmbda=1) + assert_allclose(xt, x) + + # Test negative input + x = _old_loggamma_rvs(5, size=50, random_state=rng) - 5 + assert np.all(x < 0) + xt = stats.yeojohnson(x, lmbda=2) + assert_allclose(xt, -np.log(-x + 1)) + xt = stats.yeojohnson(x, lmbda=1) + assert_allclose(xt, x) + xt = stats.yeojohnson(x, lmbda=3) + assert_allclose(xt, 1 / (-x + 1) - 1) + + # test both positive and negative input + x = _old_loggamma_rvs(5, size=50, random_state=rng) - 2 + assert not np.all(x < 0) + assert not np.all(x >= 0) + pos = x >= 0 + xt = stats.yeojohnson(x, lmbda=1) + assert_allclose(xt[pos], x[pos]) + xt = stats.yeojohnson(x, lmbda=-1) + assert_allclose(xt[pos], 1 - 1 / (x[pos] + 1)) + xt = stats.yeojohnson(x, lmbda=0) + assert_allclose(xt[pos], np.log(x[pos] + 1)) + xt = stats.yeojohnson(x, lmbda=1) + assert_allclose(xt[pos], x[pos]) + + neg = ~pos + xt = stats.yeojohnson(x, lmbda=2) + assert_allclose(xt[neg], -np.log(-x[neg] + 1)) + xt = stats.yeojohnson(x, lmbda=1) + assert_allclose(xt[neg], x[neg]) + xt = stats.yeojohnson(x, lmbda=3) + assert_allclose(xt[neg], 1 / (-x[neg] + 1) - 1) + + @pytest.mark.parametrize('lmbda', [0, .1, .5, 2]) + def test_lmbda_None(self, lmbda): + # Start from normal rv's, do inverse transform to check that + # optimization function gets close to the right answer. + + def _inverse_transform(x, lmbda): + x_inv = np.zeros(x.shape, dtype=x.dtype) + pos = x >= 0 + + # when x >= 0 + if abs(lmbda) < np.spacing(1.): + x_inv[pos] = np.exp(x[pos]) - 1 + else: # lmbda != 0 + x_inv[pos] = np.power(x[pos] * lmbda + 1, 1 / lmbda) - 1 + + # when x < 0 + if abs(lmbda - 2) > np.spacing(1.): + x_inv[~pos] = 1 - np.power(-(2 - lmbda) * x[~pos] + 1, + 1 / (2 - lmbda)) + else: # lmbda == 2 + x_inv[~pos] = 1 - np.exp(-x[~pos]) + + return x_inv + + n_samples = 20000 + rng = np.random.RandomState(1234567) + x = rng.normal(loc=0, scale=1, size=(n_samples)) + + x_inv = _inverse_transform(x, lmbda) + xt, maxlog = stats.yeojohnson(x_inv) + + assert_allclose(maxlog, lmbda, atol=1e-2) + + assert_almost_equal(0, np.linalg.norm(x - xt) / n_samples, decimal=2) + assert_almost_equal(0, xt.mean(), decimal=1) + assert_almost_equal(1, xt.std(), decimal=1) + + def test_empty(self): + assert_(stats.yeojohnson([]).shape == (0,)) + + def test_array_like(self): + x = stats.norm.rvs(size=100, loc=0, random_state=54321) + xt1, _ = stats.yeojohnson(x) + xt2, _ = stats.yeojohnson(list(x)) + assert_allclose(xt1, xt2, rtol=1e-12) + + @pytest.mark.parametrize('dtype', [np.complex64, np.complex128]) + def test_input_dtype_complex(self, dtype): + x = np.arange(6, dtype=dtype) + err_msg = ('Yeo-Johnson transformation is not defined for complex ' + 'numbers.') + with pytest.raises(ValueError, match=err_msg): + stats.yeojohnson(x) + + @pytest.mark.parametrize('dtype', [np.int8, np.uint8, np.int16, np.int32]) + def test_input_dtype_integer(self, dtype): + x_int = np.arange(8, dtype=dtype) + x_float = np.arange(8, dtype=np.float64) + xt_int, lmbda_int = stats.yeojohnson(x_int) + xt_float, lmbda_float = stats.yeojohnson(x_float) + assert_allclose(xt_int, xt_float, rtol=1e-7) + assert_allclose(lmbda_int, lmbda_float, rtol=1e-7) + + def test_input_high_variance(self): + # non-regression test for gh-10821 + x = np.array([3251637.22, 620695.44, 11642969.00, 2223468.22, + 85307500.00, 16494389.89, 917215.88, 11642969.00, + 2145773.87, 4962000.00, 620695.44, 651234.50, + 1907876.71, 4053297.88, 3251637.22, 3259103.08, + 9547969.00, 20631286.23, 12807072.08, 2383819.84, + 90114500.00, 17209575.46, 12852969.00, 2414609.99, + 2170368.23]) + xt_yeo, lam_yeo = stats.yeojohnson(x) + xt_box, lam_box = stats.boxcox(x + 1) + assert_allclose(xt_yeo, xt_box, rtol=1e-6) + assert_allclose(lam_yeo, lam_box, rtol=1e-6) + + @pytest.mark.parametrize('x', [ + np.array([1.0, float("inf"), 2.0]), + np.array([1.0, -float("inf"), 2.0]), + np.array([-1.0, float("inf"), -float("inf"), 1.0]) + ]) + def test_nonfinite_input(self, x): + with pytest.raises(ValueError, match='Yeo-Johnson input must be finite'): + xt_yeo, lam_yeo = stats.yeojohnson(x) + + @pytest.mark.parametrize('x', [ + # Attempt to trigger overflow in power expressions. + np.array([2003.0, 1950.0, 1997.0, 2000.0, 2009.0, + 2009.0, 1980.0, 1999.0, 2007.0, 1991.0]), + # Attempt to trigger overflow with a large optimal lambda. + np.array([2003.0, 1950.0, 1997.0, 2000.0, 2009.0]), + # Attempt to trigger overflow with large data. + np.array([2003.0e200, 1950.0e200, 1997.0e200, 2000.0e200, 2009.0e200]) + ]) + def test_overflow(self, x): + # non-regression test for gh-18389 + + def optimizer(fun, lam_yeo): + out = optimize.fminbound(fun, -lam_yeo, lam_yeo, xtol=1.48e-08) + result = optimize.OptimizeResult() + result.x = out + return result + + with np.errstate(all="raise"): + xt_yeo, lam_yeo = stats.yeojohnson(x) + xt_box, lam_box = stats.boxcox( + x + 1, optimizer=partial(optimizer, lam_yeo=lam_yeo)) + assert np.isfinite(np.var(xt_yeo)) + assert np.isfinite(np.var(xt_box)) + assert_allclose(lam_yeo, lam_box, rtol=1e-6) + assert_allclose(xt_yeo, xt_box, rtol=1e-4) + + @pytest.mark.parametrize('x', [ + np.array([2003.0, 1950.0, 1997.0, 2000.0, 2009.0, + 2009.0, 1980.0, 1999.0, 2007.0, 1991.0]), + np.array([2003.0, 1950.0, 1997.0, 2000.0, 2009.0]) + ]) + @pytest.mark.parametrize('scale', [1, 1e-12, 1e-32, 1e-150, 1e32, 1e200]) + @pytest.mark.parametrize('sign', [1, -1]) + def test_overflow_underflow_signed_data(self, x, scale, sign): + # non-regression test for gh-18389 + with np.errstate(all="raise"): + xt_yeo, lam_yeo = stats.yeojohnson(sign * x * scale) + assert np.all(np.sign(sign * x) == np.sign(xt_yeo)) + assert np.isfinite(lam_yeo) + assert np.isfinite(np.var(xt_yeo)) + + @pytest.mark.parametrize('x', [ + np.array([0, 1, 2, 3]), + np.array([0, -1, 2, -3]), + np.array([0, 0, 0]) + ]) + @pytest.mark.parametrize('sign', [1, -1]) + @pytest.mark.parametrize('brack', [None, (-2, 2)]) + def test_integer_signed_data(self, x, sign, brack): + with np.errstate(all="raise"): + x_int = sign * x + x_float = x_int.astype(np.float64) + lam_yeo_int = stats.yeojohnson_normmax(x_int, brack=brack) + xt_yeo_int = stats.yeojohnson(x_int, lmbda=lam_yeo_int) + lam_yeo_float = stats.yeojohnson_normmax(x_float, brack=brack) + xt_yeo_float = stats.yeojohnson(x_float, lmbda=lam_yeo_float) + assert np.all(np.sign(x_int) == np.sign(xt_yeo_int)) + assert np.isfinite(lam_yeo_int) + assert np.isfinite(np.var(xt_yeo_int)) + assert lam_yeo_int == lam_yeo_float + assert np.all(xt_yeo_int == xt_yeo_float) + + +class TestYeojohnsonNormmax(NormmaxTest): + transform_normmax = 'yeojohnson_normmax' + + def setup_method(self): + self.x = _old_loggamma_rvs(5, size=50, random_state=12345) + 5 + + def test_mle(self): + maxlog = stats.yeojohnson_normmax(self.x) + assert_allclose(maxlog, 1.876393, rtol=1e-6) + + def test_darwin_example(self): + # test from original paper "A new family of power transformations to + # improve normality or symmetry" by Yeo and Johnson. + x = [6.1, -8.4, 1.0, 2.0, 0.7, 2.9, 3.5, 5.1, 1.8, 3.6, 7.0, 3.0, 9.3, + 7.5, -6.0] + lmbda = stats.yeojohnson_normmax(x) + assert np.allclose(lmbda, 1.305, atol=1e-3) + + +@make_xp_test_case(stats.circmean, stats.circvar, stats.circstd) +class TestCircFuncs: + # In gh-5747, the R package `circular` was used to calculate reference + # values for the circular variance, e.g.: + # library(circular) + # options(digits=16) + # x = c(0, 2*pi/3, 5*pi/3) + # var.circular(x) + @pytest.mark.parametrize("test_func,expected", + [(stats.circmean, 0.167690146), + (stats.circvar, 0.006455174000787767), + (stats.circstd, 6.520702116)]) + def test_circfuncs(self, test_func, expected, xp): + x = xp.asarray([355., 5., 2., 359., 10., 350.]) + xp_assert_close(test_func(x, high=360), xp.asarray(expected)) + + def test_circfuncs_small(self, xp): + # Default tolerances won't work here because the reference values + # are approximations. Ensure all array types work in float64 to + # avoid needing separate float32 and float64 tolerances. + x = xp.asarray([20, 21, 22, 18, 19, 20.5, 19.2], dtype=xp.float64) + M1 = xp.mean(x) + M2 = stats.circmean(x, high=360) + xp_assert_close(M2, M1, rtol=1e-5) + + V1 = xp.var(x*xp.pi/180, correction=0) + # for small variations, circvar is approximately half the + # linear variance + V1 = V1 / 2. + V2 = stats.circvar(x, high=360) + xp_assert_close(V2, V1, rtol=1e-4) + + S1 = xp.std(x, correction=0) + S2 = stats.circstd(x, high=360) + xp_assert_close(S2, S1, rtol=1e-4) + + @pytest.mark.parametrize("test_func, numpy_func", + [(stats.circmean, np.mean), + (stats.circvar, np.var), + (stats.circstd, np.std)]) + def test_circfuncs_close(self, test_func, numpy_func, xp): + # circfuncs should handle very similar inputs (gh-12740) + x = np.asarray([0.12675364631578953] * 10 + [0.12675365920187928] * 100) + circstat = test_func(xp.asarray(x)) + normal = xp.asarray(numpy_func(x)) + xp_assert_close(circstat, normal, atol=2e-8) + + @pytest.mark.parametrize('circfunc', [stats.circmean, + stats.circvar, + stats.circstd]) + def test_circmean_axis(self, circfunc, xp): + x = xp.asarray([[355, 5, 2, 359, 10, 350], + [351, 7, 4, 352, 9, 349], + [357, 9, 8, 358, 4, 356.]]) + res = circfunc(x, high=360) + ref = circfunc(xp.reshape(x, (-1,)), high=360) + xp_assert_close(res, xp.asarray(ref)) + + res = circfunc(x, high=360, axis=1) + ref = [circfunc(x[i, :], high=360) for i in range(x.shape[0])] + xp_assert_close(res, xp.stack(ref)) + + res = circfunc(x, high=360, axis=0) + ref = [circfunc(x[:, i], high=360) for i in range(x.shape[1])] + xp_assert_close(res, xp.stack(ref)) + + @pytest.mark.parametrize("test_func,expected", + [(stats.circmean, 0.167690146), + (stats.circvar, 0.006455174270186603), + (stats.circstd, 6.520702116)]) + def test_circfuncs_array_like(self, test_func, expected, xp): + x = xp.asarray([355, 5, 2, 359, 10, 350.]) + xp_assert_close(test_func(x, high=360), xp.asarray(expected)) + + @pytest.mark.parametrize("test_func", [stats.circmean, stats.circvar, + stats.circstd]) + def test_empty(self, test_func, xp): + dtype = xp.float64 + x = xp.asarray([], dtype=dtype) + with eager_warns(SmallSampleWarning, match=too_small_1d_not_omit, xp=xp): + res = test_func(x) + + xp_assert_equal(res, xp.asarray(xp.nan, dtype=dtype)) + + @pytest.mark.parametrize("test_func", [stats.circmean, stats.circvar, + stats.circstd]) + def test_nan_propagate(self, test_func, xp): + x = xp.asarray([355, 5, 2, 359, 10, 350, np.nan]) + xp_assert_equal(test_func(x, high=360), xp.asarray(xp.nan)) + + @pytest.mark.parametrize("test_func,expected", + [(stats.circmean, + {None: np.nan, 0: 355.66582264, 1: 0.28725053}), + (stats.circvar, + {None: np.nan, + 0: 0.002570671054089924, + 1: 0.005545914017677123}), + (stats.circstd, + {None: np.nan, 0: 4.11093193, 1: 6.04265394})]) + def test_nan_propagate_array(self, test_func, expected, xp): + x = xp.asarray([[355, 5, 2, 359, 10, 350, 1], + [351, 7, 4, 352, 9, 349, np.nan], + [1, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan]]) + for axis in expected.keys(): + out = test_func(x, high=360, axis=axis) + if axis is None: + xp_assert_equal(out, xp.asarray(xp.nan)) + else: + xp_assert_close(out[0], xp.asarray(expected[axis])) + xp_assert_equal(out[1:], xp.full_like(out[1:], xp.nan)) + + def test_circmean_scalar(self, xp): + x = xp.asarray(1.)[()] + M1 = x + M2 = stats.circmean(x) + xp_assert_close(M2, M1, rtol=1e-5) + + def test_circmean_range(self, xp): + # regression test for gh-6420: circmean(..., high, low) must be + # between `high` and `low` + m = stats.circmean(xp.arange(0, 2, 0.1), xp.pi, -xp.pi) + xp_assert_less(m, xp.asarray(xp.pi)) + xp_assert_less(-m, xp.asarray(xp.pi)) + + def test_circfuncs_uint8(self, xp): + # regression test for gh-7255: overflow when working with + # numpy uint8 data type + x = xp.asarray([150, 10], dtype=xp.uint8) + xp_assert_close(stats.circmean(x, high=180), xp.asarray(170.0)) + xp_assert_close(stats.circvar(x, high=180), xp.asarray(0.2339555554617)) + xp_assert_close(stats.circstd(x, high=180), xp.asarray(20.91551378)) + + def test_circstd_zero(self, xp): + # circstd() of a single number should return positive zero. + y = stats.circstd(xp.asarray([0])) + assert math.copysign(1.0, y) == 1.0 + + def test_circmean_accuracy_tiny_input(self, xp): + # For tiny x such that sin(x) == x and cos(x) == 1.0 numerically, + # circmean(x) should return x because atan2(sin(x), cos(x)) == x. + # This test verifies this. + # + # The purpose of this test is not to show that circmean() is + # accurate in the last digit for certain input, because this is + # neither guaranteed not particularly useful. Rather, it is a + # "white-box" sanity check that no undue loss of precision is + # introduced by conversion between (high - low) and (2 * pi). + + x = xp.linspace(1e-9, 6e-9, 50) + assert xp.all(xp.sin(x) == x) and xp.all(xp.cos(x) == 1.0) + + m = (x * (2 * xp.pi) / (2 * xp.pi)) != x + assert xp.any(m) + x = x[m] + + y = stats.circmean(x[:, None], axis=1) + assert xp.all(y == x) + + def test_circmean_accuracy_huge_input(self, xp): + # White-box test that circmean() does not introduce undue loss of + # numerical accuracy by eagerly rotating the input. This is detected + # by supplying a huge input x such that (x - low) == x numerically. + x = xp.asarray(1e17, dtype=xp.float64) + y = math.atan2(xp.sin(x), xp.cos(x)) # -2.6584887370946806 + expected = xp.asarray(y, dtype=xp.float64) + actual = stats.circmean(x, high=xp.pi, low=-xp.pi) + xp_assert_close(actual, expected, rtol=1e-15, atol=0.0) + + +class TestCircFuncsNanPolicy: + # `nan_policy` is implemented by the `_axis_nan_policy` decorator, which is + # not yet array-API compatible. When it is array-API compatible, the generic + # tests run on every function will be much stronger than these, so these + # will not be necessary. So I don't see a need to make these array-API compatible; + # when the time comes, they can just be removed. + @pytest.mark.parametrize("test_func,expected", + [(stats.circmean, + {None: 359.4178026893944, + 0: np.array([353.0, 6.0, 3.0, 355.5, 9.5, + 349.5]), + 1: np.array([0.16769015, 358.66510252])}), + (stats.circvar, + {None: 0.008396678483192477, + 0: np.array([1.9997969, 0.4999873, 0.4999873, + 6.1230956, 0.1249992, 0.1249992] + )*(np.pi/180)**2, + 1: np.array([0.006455174270186603, + 0.01016767581393285])}), + (stats.circstd, + {None: 7.440570778057074, + 0: np.array([2.00020313, 1.00002539, 1.00002539, + 3.50108929, 0.50000317, + 0.50000317]), + 1: np.array([6.52070212, 8.19138093])})]) + def test_nan_omit_array(self, test_func, expected): + x = np.array([[355, 5, 2, 359, 10, 350, np.nan], + [351, 7, 4, 352, 9, 349, np.nan], + [np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan]]) + for axis in expected.keys(): + if axis is None: + out = test_func(x, high=360, nan_policy='omit', axis=axis) + assert_allclose(out, expected[axis], rtol=1e-7) + else: + with pytest.warns(SmallSampleWarning, match=too_small_nd_omit): + out = test_func(x, high=360, nan_policy='omit', axis=axis) + assert_allclose(out[:-1], expected[axis], rtol=1e-7) + assert_(np.isnan(out[-1])) + + @pytest.mark.parametrize("test_func,expected", + [(stats.circmean, 0.167690146), + (stats.circvar, 0.006455174270186603), + (stats.circstd, 6.520702116)]) + def test_nan_omit(self, test_func, expected): + x = [355, 5, 2, 359, 10, 350, np.nan] + assert_allclose(test_func(x, high=360, nan_policy='omit'), + expected, rtol=1e-7) + + @pytest.mark.parametrize("test_func", [stats.circmean, stats.circvar, + stats.circstd]) + def test_nan_omit_all(self, test_func): + x = [np.nan, np.nan, np.nan, np.nan, np.nan] + with pytest.warns(SmallSampleWarning, match=too_small_1d_omit): + assert_(np.isnan(test_func(x, nan_policy='omit'))) + + @pytest.mark.parametrize("test_func", [stats.circmean, stats.circvar, + stats.circstd]) + def test_nan_omit_all_axis(self, test_func): + with pytest.warns(SmallSampleWarning, match=too_small_nd_omit): + x = np.array([[np.nan, np.nan, np.nan, np.nan, np.nan], + [np.nan, np.nan, np.nan, np.nan, np.nan]]) + out = test_func(x, nan_policy='omit', axis=1) + assert_(np.isnan(out).all()) + assert_(len(out) == 2) + + @pytest.mark.parametrize("x", + [[355, 5, 2, 359, 10, 350, np.nan], + np.array([[355, 5, 2, 359, 10, 350, np.nan], + [351, 7, 4, 352, np.nan, 9, 349]])]) + @pytest.mark.parametrize("test_func", [stats.circmean, stats.circvar, + stats.circstd]) + def test_nan_raise(self, test_func, x): + assert_raises(ValueError, test_func, x, high=360, nan_policy='raise') + + @pytest.mark.parametrize("x", + [[355, 5, 2, 359, 10, 350, np.nan], + np.array([[355, 5, 2, 359, 10, 350, np.nan], + [351, 7, 4, 352, np.nan, 9, 349]])]) + @pytest.mark.parametrize("test_func", [stats.circmean, stats.circvar, + stats.circstd]) + def test_bad_nan_policy(self, test_func, x): + assert_raises(ValueError, test_func, x, high=360, nan_policy='foobar') + + +class TestMedianTest: + + def test_bad_n_samples(self): + # median_test requires at least two samples. + assert_raises(ValueError, stats.median_test, [1, 2, 3]) + + def test_empty_sample(self): + # Each sample must contain at least one value. + assert_raises(ValueError, stats.median_test, [], [1, 2, 3]) + + def test_empty_when_ties_ignored(self): + # The grand median is 1, and all values in the first argument are + # equal to the grand median. With ties="ignore", those values are + # ignored, which results in the first sample being (in effect) empty. + # This should raise a ValueError. + assert_raises(ValueError, stats.median_test, + [1, 1, 1, 1], [2, 0, 1], [2, 0], ties="ignore") + + def test_empty_contingency_row(self): + # The grand median is 1, and with the default ties="below", all the + # values in the samples are counted as being below the grand median. + # This would result a row of zeros in the contingency table, which is + # an error. + assert_raises(ValueError, stats.median_test, [1, 1, 1], [1, 1, 1]) + + # With ties="above", all the values are counted as above the + # grand median. + assert_raises(ValueError, stats.median_test, [1, 1, 1], [1, 1, 1], + ties="above") + + def test_bad_ties(self): + assert_raises(ValueError, stats.median_test, [1, 2, 3], [4, 5], + ties="foo") + + def test_bad_nan_policy(self): + assert_raises(ValueError, stats.median_test, [1, 2, 3], [4, 5], + nan_policy='foobar') + + def test_bad_keyword(self): + assert_raises(TypeError, stats.median_test, [1, 2, 3], [4, 5], + foo="foo") + + def test_simple(self): + x = [1, 2, 3] + y = [1, 2, 3] + stat, p, med, tbl = stats.median_test(x, y) + + # The median is floating point, but this equality test should be safe. + assert_equal(med, 2.0) + + assert_array_equal(tbl, [[1, 1], [2, 2]]) + + # The expected values of the contingency table equal the contingency + # table, so the statistic should be 0 and the p-value should be 1. + assert_equal(stat, 0) + assert_equal(p, 1) + + def test_ties_options(self): + # Test the contingency table calculation. + x = [1, 2, 3, 4] + y = [5, 6] + z = [7, 8, 9] + # grand median is 5. + + # Default 'ties' option is "below". + stat, p, m, tbl = stats.median_test(x, y, z) + assert_equal(m, 5) + assert_equal(tbl, [[0, 1, 3], [4, 1, 0]]) + + stat, p, m, tbl = stats.median_test(x, y, z, ties="ignore") + assert_equal(m, 5) + assert_equal(tbl, [[0, 1, 3], [4, 0, 0]]) + + stat, p, m, tbl = stats.median_test(x, y, z, ties="above") + assert_equal(m, 5) + assert_equal(tbl, [[0, 2, 3], [4, 0, 0]]) + + def test_nan_policy_options(self): + x = [1, 2, np.nan] + y = [4, 5, 6] + mt1 = stats.median_test(x, y, nan_policy='propagate') + s, p, m, t = stats.median_test(x, y, nan_policy='omit') + + assert_equal(mt1, (np.nan, np.nan, np.nan, None)) + assert_allclose(s, 0.31250000000000006) + assert_allclose(p, 0.57615012203057869) + assert_equal(m, 4.0) + assert_equal(t, np.array([[0, 2], [2, 1]])) + assert_raises(ValueError, stats.median_test, x, y, nan_policy='raise') + + def test_basic(self): + # median_test calls chi2_contingency to compute the test statistic + # and p-value. Make sure it hasn't screwed up the call... + + x = [1, 2, 3, 4, 5] + y = [2, 4, 6, 8] + + stat, p, m, tbl = stats.median_test(x, y) + assert_equal(m, 4) + assert_equal(tbl, [[1, 2], [4, 2]]) + + exp_stat, exp_p, dof, e = stats.chi2_contingency(tbl) + assert_allclose(stat, exp_stat) + assert_allclose(p, exp_p) + + stat, p, m, tbl = stats.median_test(x, y, lambda_=0) + assert_equal(m, 4) + assert_equal(tbl, [[1, 2], [4, 2]]) + + exp_stat, exp_p, dof, e = stats.chi2_contingency(tbl, lambda_=0) + assert_allclose(stat, exp_stat) + assert_allclose(p, exp_p) + + stat, p, m, tbl = stats.median_test(x, y, correction=False) + assert_equal(m, 4) + assert_equal(tbl, [[1, 2], [4, 2]]) + + exp_stat, exp_p, dof, e = stats.chi2_contingency(tbl, correction=False) + assert_allclose(stat, exp_stat) + assert_allclose(p, exp_p) + + @pytest.mark.parametrize("correction", [False, True]) + def test_result(self, correction): + x = [1, 2, 3] + y = [1, 2, 3] + + res = stats.median_test(x, y, correction=correction) + assert_equal((res.statistic, res.pvalue, res.median, res.table), res) + + +@make_xp_test_case(stats.directional_stats) +class TestDirectionalStats: + # Reference implementations are not available + def test_directional_stats_correctness(self, xp): + # Data from Fisher: Dispersion on a sphere, 1953 and + # Mardia and Jupp, Directional Statistics. + decl = -np.deg2rad(np.array([343.2, 62., 36.9, 27., 359., + 5.7, 50.4, 357.6, 44.])) + incl = -np.deg2rad(np.array([66.1, 68.7, 70.1, 82.1, 79.5, + 73., 69.3, 58.8, 51.4])) + data = np.stack((np.cos(incl) * np.cos(decl), + np.cos(incl) * np.sin(decl), + np.sin(incl)), + axis=1) + + decl = xp.asarray(decl.tolist()) + incl = xp.asarray(incl.tolist()) + data = xp.asarray(data.tolist()) + + dirstats = stats.directional_stats(data) + directional_mean = dirstats.mean_direction + + reference_mean = xp.asarray([0.2984, -0.1346, -0.9449]) + xp_assert_close(directional_mean, reference_mean, atol=1e-4) + + @pytest.mark.parametrize('angles, ref', [ + ([-np.pi/2, np.pi/2], 1.), + ([0, 2 * np.pi], 0.) + ]) + def test_directional_stats_2d_special_cases(self, angles, ref, xp): + angles = xp.asarray(angles) + ref = xp.asarray(ref) + data = xp.stack([xp.cos(angles), xp.sin(angles)], axis=1) + res = 1 - stats.directional_stats(data).mean_resultant_length + xp_assert_close(res, ref) + + def test_directional_stats_2d(self, xp): + # Test that for circular data directional_stats + # yields the same result as circmean/circvar + rng = np.random.default_rng(0xec9a6899d5a2830e0d1af479dbe1fd0c) + testdata = xp.asarray(2 * xp.pi * rng.random((1000, ))) + testdata_vector = xp.stack((xp.cos(testdata), + xp.sin(testdata)), + axis=1) + dirstats = stats.directional_stats(testdata_vector) + directional_mean = dirstats.mean_direction + directional_mean_angle = xp.atan2(directional_mean[1], directional_mean[0]) + directional_mean_angle = directional_mean_angle % (2 * xp.pi) + circmean = stats.circmean(testdata) + xp_assert_close(directional_mean_angle, circmean) + + directional_var = 1. - dirstats.mean_resultant_length + circular_var = stats.circvar(testdata) + xp_assert_close(directional_var, circular_var) + + def test_directional_mean_higher_dim(self, xp): + # test that directional_stats works for higher dimensions + # here a 4D array is reduced over axis = 2 + data = xp.asarray([[0.8660254, 0.5, 0.], + [0.8660254, -0.5, 0.]]) + full_array = xp.asarray(xp.tile(data, (2, 2, 2, 1))) + expected = xp.asarray([[[1., 0., 0.], + [1., 0., 0.]], + [[1., 0., 0.], + [1., 0., 0.]]]) + dirstats = stats.directional_stats(full_array, axis=2) + xp_assert_close(dirstats.mean_direction, expected) + + @skip_xp_backends(np_only=True, reason='checking array-like input') + def test_directional_stats_list_ndarray_input(self, xp): + # test that list and numpy array inputs yield same results + data = [[0.8660254, 0.5, 0.], [0.8660254, -0.5, 0]] + data_array = xp.asarray(data, dtype=xp.float64) + ref = stats.directional_stats(data) + res = stats.directional_stats(data_array) + xp_assert_close(res.mean_direction, + xp.asarray(ref.mean_direction)) + xp_assert_close(res.mean_resultant_length, + xp.asarray(res.mean_resultant_length)) + + def test_directional_stats_1d_error(self, xp): + # test that one-dimensional data raises ValueError + data = xp.ones((5, )) + message = (r"samples must at least be two-dimensional. " + r"Instead samples has shape: (5,)") + with pytest.raises(ValueError, match=re.escape(message)): + stats.directional_stats(data) + + @pytest.mark.parametrize("dtype", ["float32", "float64"]) + def test_directional_stats_normalize(self, dtype, xp): + # test that directional stats calculations yield same results + # for unnormalized input with normalize=True and normalized + # input with normalize=False + data = np.array([[0.8660254, 0.5, 0.], + [1.7320508, -1., 0.]], dtype=dtype) + res = stats.directional_stats(xp.asarray(data), normalize=True) + normalized_data = data / np.linalg.norm(data, axis=-1, + keepdims=True) + ref = stats.directional_stats(normalized_data, normalize=False) + xp_assert_close(res.mean_direction, + xp.asarray(ref.mean_direction)) + xp_assert_close(res.mean_resultant_length, + xp.asarray(ref.mean_resultant_length)) + + +@make_xp_test_case(stats.false_discovery_control) +class TestFDRControl: + @skip_xp_backends(eager_only=True) + def test_input_validation_ps(self, xp): + message = "All values in `ps` must lie between 0. and 1." + with pytest.raises(ValueError, match=message): + stats.false_discovery_control(xp.asarray([-1, 0.5, 0.7])) + with pytest.raises(ValueError, match=message): + stats.false_discovery_control(xp.asarray([0.5, 0.7, 2])) + with pytest.raises(ValueError, match=message): + stats.false_discovery_control(xp.asarray([0.5, 0.7, xp.nan])) + + def test_input_validation(self, xp): + message = "`ps` must contain only real numbers." + with pytest.raises(ValueError, match=message): + stats.false_discovery_control(xp.asarray([1+1j, 2+2j])) + + message = "Unrecognized `method` 'YAK'" + with pytest.raises(ValueError, match=message): + stats.false_discovery_control(xp.asarray([0.5, 0.7, 0.9]), method='YAK') + + message = "`axis` must be an integer or `None`" + with pytest.raises(ValueError, match=message): + stats.false_discovery_control(xp.asarray([0.5, 0.7, 0.9]), axis=1.5) + with pytest.raises(ValueError, match=message): + stats.false_discovery_control(xp.asarray([0.5, 0.7, 0.9]), axis=(1, 2)) + + def test_against_TileStats(self, xp): + # See reference [3] of false_discovery_control + ps = xp.asarray([0.005, 0.009, 0.019, 0.022, 0.051, 0.101, 0.361, 0.387]) + res = stats.false_discovery_control(ps) + ref = xp.asarray([0.036, 0.036, 0.044, 0.044, 0.082, 0.135, 0.387, 0.387]) + xp_assert_close(res, ref, atol=1e-3) + + @pytest.mark.parametrize("case", + [([0.24617028, 0.01140030, 0.05652047, 0.06841983, + 0.07989886, 0.01841490, 0.17540784, 0.06841983, + 0.06841983, 0.25464082], 'bh'), + ([0.72102493, 0.03339112, 0.16554665, 0.20039952, + 0.23402122, 0.05393666, 0.51376399, 0.20039952, + 0.20039952, 0.74583488], 'by')]) + def test_against_R(self, case, xp): + # Test against p.adjust, e.g. + # p = c(0.22155325, 0.00114003,..., 0.0364813 , 0.25464082) + # p.adjust(p, "BY") + ref, method = case + rng = np.random.default_rng(6134137338861652935) + ps = stats.loguniform.rvs(1e-3, 0.5, size=10, random_state=rng).tolist() + ps[3] = ps[7] # force a tie + res = stats.false_discovery_control(xp.asarray(ps), method=method) + xp_assert_close(res, xp.asarray(ref), atol=1e-6) + + def test_axis_None(self, xp): + rng = np.random.default_rng(6134137338861652935) + ps = stats.loguniform.rvs(1e-3, 0.5, size=(3, 4, 5), random_state=rng) + ps = xp.asarray(ps) + res = stats.false_discovery_control(ps, axis=None) + ref = stats.false_discovery_control(xp_ravel(ps)) + xp_assert_equal(res, ref) + + @pytest.mark.parametrize("axis", [0, 1, -1]) + def test_axis(self, axis, xp): + rng = np.random.default_rng(6134137338861652935) + ps = stats.loguniform.rvs(1e-3, 0.5, size=(3, 4, 5), random_state=rng) + res = stats.false_discovery_control(xp.asarray(ps), axis=axis) + ref = np.apply_along_axis(stats.false_discovery_control, axis, ps) + xp_assert_close(res, xp.asarray(ref)) # torch isn't *equal* + + def test_edge_cases(self, xp): + ps = xp.asarray([0.25]) + xp_assert_equal(stats.false_discovery_control(ps), ps) + + ps = xp.asarray([]) + xp_assert_equal(stats.false_discovery_control(ps), ps) + + if is_numpy(xp): + xp_assert_equal(stats.false_discovery_control(0.25), 0.25) + + +@pytest.mark.uses_xp_capabilities(False, reason="not used in this test yet") +class TestCommonAxis: + # More thorough testing of `axis` in `test_axis_nan_policy`, + # but those tests aren't run with array API yet. This class + # is in `test_morestats` instead of `test_axis_nan_policy` + # because there is no reason to run `test_axis_nan_policy` + # with the array API CI job right now. + + @pytest.mark.parametrize('case', [(stats.sem, {}), + (stats.kstat, {'n': 4}), + (stats.kstat, {'n': 2}), + (stats.variation, {})]) + def test_axis(self, case, xp): + if is_torch(xp) and case[0] == stats.variation: + pytest.xfail(reason="copysign doesn't accept scalar array-api-compat#271") + fun, kwargs = case + rng = np.random.default_rng(24598245982345) + x = xp.asarray(rng.random((6, 7))) + + res = fun(x, **kwargs, axis=0) + ref = xp.stack([fun(x[:, i], **kwargs) for i in range(x.shape[1])]) + xp_assert_close(res, ref) + + res = fun(x, **kwargs, axis=1) + ref = xp.stack([fun(x[i, :], **kwargs) for i in range(x.shape[0])]) + xp_assert_close(res, ref) + + res = fun(x, **kwargs, axis=None) + ref = fun(xp.reshape(x, (-1,)), **kwargs) + xp_assert_close(res, ref) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/test_mstats_basic.py b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_mstats_basic.py new file mode 100644 index 0000000000000000000000000000000000000000..475ca50f7dd001d4d04326cc04a258ff739c3154 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_mstats_basic.py @@ -0,0 +1,2146 @@ +""" +Tests for the stats.mstats module (support for masked arrays) +""" +import warnings +import platform + +import numpy as np +from numpy import nan +import numpy.ma as ma +from numpy.ma import masked, nomask + +import scipy.stats.mstats as mstats +from scipy import stats +from .common_tests import check_named_results +import pytest +from pytest import raises as assert_raises +from numpy.ma.testutils import (assert_equal, assert_almost_equal, + assert_array_almost_equal, + assert_array_almost_equal_nulp, assert_, + assert_allclose, assert_array_equal) +from scipy.stats import _mstats_basic, _stats_py +from scipy.conftest import skip_xp_invalid_arg +from scipy.stats._axis_nan_policy import SmallSampleWarning, too_small_1d_not_omit + +class TestMquantiles: + def test_mquantiles_limit_keyword(self): + # Regression test for Trac ticket #867 + data = np.array([[6., 7., 1.], + [47., 15., 2.], + [49., 36., 3.], + [15., 39., 4.], + [42., 40., -999.], + [41., 41., -999.], + [7., -999., -999.], + [39., -999., -999.], + [43., -999., -999.], + [40., -999., -999.], + [36., -999., -999.]]) + desired = [[19.2, 14.6, 1.45], + [40.0, 37.5, 2.5], + [42.8, 40.05, 3.55]] + quants = mstats.mquantiles(data, axis=0, limit=(0, 50)) + assert_almost_equal(quants, desired) + + +def check_equal_gmean(array_like, desired, axis=None, dtype=None, rtol=1e-7): + # Note this doesn't test when axis is not specified + x = mstats.gmean(array_like, axis=axis, dtype=dtype) + assert_allclose(x, desired, rtol=rtol) + assert_equal(x.dtype, dtype) + + +def check_equal_hmean(array_like, desired, axis=None, dtype=None, rtol=1e-7): + x = stats.hmean(array_like, axis=axis, dtype=dtype) + assert_allclose(x, desired, rtol=rtol) + assert_equal(x.dtype, dtype) + + +@skip_xp_invalid_arg +class TestGeoMean: + def test_1d(self): + a = [1, 2, 3, 4] + desired = np.power(1*2*3*4, 1./4.) + check_equal_gmean(a, desired, rtol=1e-14) + + def test_1d_ma(self): + # Test a 1d masked array + a = ma.array([10, 20, 30, 40, 50, 60, 70, 80, 90, 100]) + desired = 45.2872868812 + check_equal_gmean(a, desired) + + a = ma.array([1, 2, 3, 4], mask=[0, 0, 0, 1]) + desired = np.power(1*2*3, 1./3.) + check_equal_gmean(a, desired, rtol=1e-14) + + def test_1d_ma_value(self): + # Test a 1d masked array with a masked value + a = np.ma.array([10, 20, 30, 40, 50, 60, 70, 80, 90, 100], + mask=[0, 0, 0, 0, 0, 0, 0, 0, 0, 1]) + desired = 41.4716627439 + check_equal_gmean(a, desired) + + def test_1d_ma0(self): + # Test a 1d masked array with zero element + a = np.ma.array([10, 20, 30, 40, 50, 60, 70, 80, 90, 0]) + desired = 0 + check_equal_gmean(a, desired) + + def test_1d_ma_inf(self): + # Test a 1d masked array with negative element + a = np.ma.array([10, 20, 30, 40, 50, 60, 70, 80, 90, -1]) + desired = np.nan + with np.errstate(invalid='ignore'): + check_equal_gmean(a, desired) + + @pytest.mark.skipif(not hasattr(np, 'float96'), + reason='cannot find float96 so skipping') + def test_1d_float96(self): + a = ma.array([1, 2, 3, 4], mask=[0, 0, 0, 1]) + desired_dt = np.power(1*2*3, 1./3.).astype(np.float96) + check_equal_gmean(a, desired_dt, dtype=np.float96, rtol=1e-14) + + def test_2d_ma(self): + a = ma.array([[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]], + mask=[[0, 0, 0, 0], [1, 0, 0, 1], [0, 1, 1, 0]]) + desired = np.array([1, 2, 3, 4]) + check_equal_gmean(a, desired, axis=0, rtol=1e-14) + + desired = ma.array([np.power(1*2*3*4, 1./4.), + np.power(2*3, 1./2.), + np.power(1*4, 1./2.)]) + check_equal_gmean(a, desired, axis=-1, rtol=1e-14) + + # Test a 2d masked array + a = [[10, 20, 30, 40], [50, 60, 70, 80], [90, 100, 110, 120]] + desired = 52.8885199 + check_equal_gmean(np.ma.array(a), desired) + + +@skip_xp_invalid_arg +class TestHarMean: + def test_1d(self): + a = ma.array([1, 2, 3, 4], mask=[0, 0, 0, 1]) + desired = 3. / (1./1 + 1./2 + 1./3) + check_equal_hmean(a, desired, rtol=1e-14) + + a = np.ma.array([10, 20, 30, 40, 50, 60, 70, 80, 90, 100]) + desired = 34.1417152147 + check_equal_hmean(a, desired) + + a = np.ma.array([10, 20, 30, 40, 50, 60, 70, 80, 90, 100], + mask=[0, 0, 0, 0, 0, 0, 0, 0, 0, 1]) + desired = 31.8137186141 + check_equal_hmean(a, desired) + + @pytest.mark.skipif(not hasattr(np, 'float96'), + reason='cannot find float96 so skipping') + def test_1d_float96(self): + a = ma.array([1, 2, 3, 4], mask=[0, 0, 0, 1]) + desired_dt = np.asarray(3. / (1./1 + 1./2 + 1./3), dtype=np.float96) + check_equal_hmean(a, desired_dt, dtype=np.float96) + + def test_2d(self): + a = ma.array([[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]], + mask=[[0, 0, 0, 0], [1, 0, 0, 1], [0, 1, 1, 0]]) + desired = ma.array([1, 2, 3, 4]) + check_equal_hmean(a, desired, axis=0, rtol=1e-14) + + desired = [4./(1/1.+1/2.+1/3.+1/4.), 2./(1/2.+1/3.), 2./(1/1.+1/4.)] + check_equal_hmean(a, desired, axis=-1, rtol=1e-14) + + a = [[10, 20, 30, 40], [50, 60, 70, 80], [90, 100, 110, 120]] + desired = 38.6696271841 + check_equal_hmean(np.ma.array(a), desired) + + +class TestRanking: + def test_ranking(self): + x = ma.array([0,1,1,1,2,3,4,5,5,6,]) + assert_almost_equal(mstats.rankdata(x), + [1,3,3,3,5,6,7,8.5,8.5,10]) + x[[3,4]] = masked + assert_almost_equal(mstats.rankdata(x), + [1,2.5,2.5,0,0,4,5,6.5,6.5,8]) + assert_almost_equal(mstats.rankdata(x, use_missing=True), + [1,2.5,2.5,4.5,4.5,4,5,6.5,6.5,8]) + x = ma.array([0,1,5,1,2,4,3,5,1,6,]) + assert_almost_equal(mstats.rankdata(x), + [1,3,8.5,3,5,7,6,8.5,3,10]) + x = ma.array([[0,1,1,1,2], [3,4,5,5,6,]]) + assert_almost_equal(mstats.rankdata(x), + [[1,3,3,3,5], [6,7,8.5,8.5,10]]) + assert_almost_equal(mstats.rankdata(x, axis=1), + [[1,3,3,3,5], [1,2,3.5,3.5,5]]) + assert_almost_equal(mstats.rankdata(x,axis=0), + [[1,1,1,1,1], [2,2,2,2,2,]]) + + +class TestCorr: + def test_pearsonr(self): + # Tests some computations of Pearson's r + x = ma.arange(10) + with warnings.catch_warnings(): + # The tests in this context are edge cases, with perfect + # correlation or anticorrelation, or totally masked data. + # None of these should trigger a RuntimeWarning. + warnings.simplefilter("error", RuntimeWarning) + + assert_almost_equal(mstats.pearsonr(x, x)[0], 1.0) + assert_almost_equal(mstats.pearsonr(x, x[::-1])[0], -1.0) + + x = ma.array(x, mask=True) + pr = mstats.pearsonr(x, x) + assert_(pr[0] is masked) + assert_(pr[1] is masked) + + x1 = ma.array([-1.0, 0.0, 1.0]) + y1 = ma.array([0, 0, 3]) + r, p = mstats.pearsonr(x1, y1) + assert_almost_equal(r, np.sqrt(3)/2) + assert_almost_equal(p, 1.0/3) + + # (x2, y2) have the same unmasked data as (x1, y1). + mask = [False, False, False, True] + x2 = ma.array([-1.0, 0.0, 1.0, 99.0], mask=mask) + y2 = ma.array([0, 0, 3, -1], mask=mask) + r, p = mstats.pearsonr(x2, y2) + assert_almost_equal(r, np.sqrt(3)/2) + assert_almost_equal(p, 1.0/3) + + def test_pearsonr_misaligned_mask(self): + mx = np.ma.masked_array([1, 2, 3, 4, 5, 6], mask=[0, 1, 0, 0, 0, 0]) + my = np.ma.masked_array([9, 8, 7, 6, 5, 9], mask=[0, 0, 1, 0, 0, 0]) + x = np.array([1, 4, 5, 6]) + y = np.array([9, 6, 5, 9]) + mr, mp = mstats.pearsonr(mx, my) + r, p = stats.pearsonr(x, y) + assert_equal(mr, r) + assert_equal(mp, p) + + def test_spearmanr(self): + # Tests some computations of Spearman's rho + (x, y) = ([5.05,6.75,3.21,2.66], [1.65,2.64,2.64,6.95]) + assert_almost_equal(mstats.spearmanr(x,y)[0], -0.6324555) + (x, y) = ([5.05,6.75,3.21,2.66,np.nan],[1.65,2.64,2.64,6.95,np.nan]) + (x, y) = (ma.fix_invalid(x), ma.fix_invalid(y)) + assert_almost_equal(mstats.spearmanr(x,y)[0], -0.6324555) + + x = [2.0, 47.4, 42.0, 10.8, 60.1, 1.7, 64.0, 63.1, + 1.0, 1.4, 7.9, 0.3, 3.9, 0.3, 6.7] + y = [22.6, 8.3, 44.4, 11.9, 24.6, 0.6, 5.7, 41.6, + 0.0, 0.6, 6.7, 3.8, 1.0, 1.2, 1.4] + assert_almost_equal(mstats.spearmanr(x,y)[0], 0.6887299) + x = [2.0, 47.4, 42.0, 10.8, 60.1, 1.7, 64.0, 63.1, + 1.0, 1.4, 7.9, 0.3, 3.9, 0.3, 6.7, np.nan] + y = [22.6, 8.3, 44.4, 11.9, 24.6, 0.6, 5.7, 41.6, + 0.0, 0.6, 6.7, 3.8, 1.0, 1.2, 1.4, np.nan] + (x, y) = (ma.fix_invalid(x), ma.fix_invalid(y)) + assert_almost_equal(mstats.spearmanr(x,y)[0], 0.6887299) + # Next test is to make sure calculation uses sufficient precision. + # The denominator's value is ~n^3 and used to be represented as an + # int. 2000**3 > 2**32 so these arrays would cause overflow on + # some machines. + x = list(range(2000)) + y = list(range(2000)) + y[0], y[9] = y[9], y[0] + y[10], y[434] = y[434], y[10] + y[435], y[1509] = y[1509], y[435] + # rho = 1 - 6 * (2 * (9^2 + 424^2 + 1074^2))/(2000 * (2000^2 - 1)) + # = 1 - (1 / 500) + # = 0.998 + assert_almost_equal(mstats.spearmanr(x,y)[0], 0.998) + + # test for namedtuple attributes + res = mstats.spearmanr(x, y) + attributes = ('correlation', 'pvalue') + check_named_results(res, attributes, ma=True) + + def test_spearmanr_alternative(self): + # check against R + # options(digits=16) + # cor.test(c(2.0, 47.4, 42.0, 10.8, 60.1, 1.7, 64.0, 63.1, + # 1.0, 1.4, 7.9, 0.3, 3.9, 0.3, 6.7), + # c(22.6, 8.3, 44.4, 11.9, 24.6, 0.6, 5.7, 41.6, + # 0.0, 0.6, 6.7, 3.8, 1.0, 1.2, 1.4), + # alternative='two.sided', method='spearman') + x = [2.0, 47.4, 42.0, 10.8, 60.1, 1.7, 64.0, 63.1, + 1.0, 1.4, 7.9, 0.3, 3.9, 0.3, 6.7] + y = [22.6, 8.3, 44.4, 11.9, 24.6, 0.6, 5.7, 41.6, + 0.0, 0.6, 6.7, 3.8, 1.0, 1.2, 1.4] + + r_exp = 0.6887298747763864 # from cor.test + + r, p = mstats.spearmanr(x, y) + assert_allclose(r, r_exp) + assert_allclose(p, 0.004519192910756) + + r, p = mstats.spearmanr(x, y, alternative='greater') + assert_allclose(r, r_exp) + assert_allclose(p, 0.002259596455378) + + r, p = mstats.spearmanr(x, y, alternative='less') + assert_allclose(r, r_exp) + assert_allclose(p, 0.9977404035446) + + # intuitive test (with obvious positive correlation) + rng = np.random.default_rng(9607138567) + n = 100 + x = np.linspace(0, 5, n) + y = 0.1*x + rng.random(n) # y is positively correlated w/ x + + stat1, p1 = mstats.spearmanr(x, y) + + stat2, p2 = mstats.spearmanr(x, y, alternative="greater") + assert_allclose(p2, p1 / 2) # positive correlation -> small p + + stat3, p3 = mstats.spearmanr(x, y, alternative="less") + assert_allclose(p3, 1 - p1 / 2) # positive correlation -> large p + + assert stat1 == stat2 == stat3 + + with pytest.raises(ValueError, match="alternative must be 'less'..."): + mstats.spearmanr(x, y, alternative="ekki-ekki") + + @pytest.mark.skipif(platform.machine() == 'ppc64le', + reason="fails/crashes on ppc64le") + def test_kendalltau(self): + # check case with maximum disorder and p=1 + x = ma.array(np.array([9, 2, 5, 6])) + y = ma.array(np.array([4, 7, 9, 11])) + # Cross-check with exact result from R: + # cor.test(x,y,method="kendall",exact=1) + expected = [0.0, 1.0] + assert_almost_equal(np.asarray(mstats.kendalltau(x, y)), expected) + + # simple case without ties + x = ma.array(np.arange(10)) + y = ma.array(np.arange(10)) + # Cross-check with exact result from R: + # cor.test(x,y,method="kendall",exact=1) + expected = [1.0, 5.511463844797e-07] + assert_almost_equal(np.asarray(mstats.kendalltau(x, y)), expected) + + # check exception in case of invalid method keyword + assert_raises(ValueError, mstats.kendalltau, x, y, method='banana') + + # swap a couple of values + b = y[1] + y[1] = y[2] + y[2] = b + # Cross-check with exact result from R: + # cor.test(x,y,method="kendall",exact=1) + expected = [0.9555555555555556, 5.511463844797e-06] + assert_almost_equal(np.asarray(mstats.kendalltau(x, y)), expected) + + # swap a couple more + b = y[5] + y[5] = y[6] + y[6] = b + # Cross-check with exact result from R: + # cor.test(x,y,method="kendall",exact=1) + expected = [0.9111111111111111, 2.976190476190e-05] + assert_almost_equal(np.asarray(mstats.kendalltau(x, y)), expected) + + # same in opposite direction + x = ma.array(np.arange(10)) + y = ma.array(np.arange(10)[::-1]) + # Cross-check with exact result from R: + # cor.test(x,y,method="kendall",exact=1) + expected = [-1.0, 5.511463844797e-07] + assert_almost_equal(np.asarray(mstats.kendalltau(x, y)), expected) + + # swap a couple of values + b = y[1] + y[1] = y[2] + y[2] = b + # Cross-check with exact result from R: + # cor.test(x,y,method="kendall",exact=1) + expected = [-0.9555555555555556, 5.511463844797e-06] + assert_almost_equal(np.asarray(mstats.kendalltau(x, y)), expected) + + # swap a couple more + b = y[5] + y[5] = y[6] + y[6] = b + # Cross-check with exact result from R: + # cor.test(x,y,method="kendall",exact=1) + expected = [-0.9111111111111111, 2.976190476190e-05] + assert_almost_equal(np.asarray(mstats.kendalltau(x, y)), expected) + + # Tests some computations of Kendall's tau + x = ma.fix_invalid([5.05, 6.75, 3.21, 2.66, np.nan]) + y = ma.fix_invalid([1.65, 26.5, -5.93, 7.96, np.nan]) + z = ma.fix_invalid([1.65, 2.64, 2.64, 6.95, np.nan]) + assert_almost_equal(np.asarray(mstats.kendalltau(x, y)), + [+0.3333333, 0.75]) + assert_almost_equal(np.asarray(mstats.kendalltau(x, y, method='asymptotic')), + [+0.3333333, 0.4969059]) + assert_almost_equal(np.asarray(mstats.kendalltau(x, z)), + [-0.5477226, 0.2785987]) + # + x = ma.fix_invalid([0, 0, 0, 0, 20, 20, 0, 60, 0, 20, + 10, 10, 0, 40, 0, 20, 0, 0, 0, 0, 0, np.nan]) + y = ma.fix_invalid([0, 80, 80, 80, 10, 33, 60, 0, 67, 27, + 25, 80, 80, 80, 80, 80, 80, 0, 10, 45, np.nan, 0]) + result = mstats.kendalltau(x, y) + assert_almost_equal(np.asarray(result), [-0.1585188, 0.4128009]) + + # test for namedtuple attributes + attributes = ('correlation', 'pvalue') + check_named_results(result, attributes, ma=True) + + def test_overflow_gh6061(self): + # Regression test for GitHub issue #6061 - Overflow on Windows + x = np.arange(2000, dtype=float) + x = np.ma.masked_greater(x, 1995) + y = np.arange(2000, dtype=float) + y = np.concatenate((y[1000:], y[:1000])) + assert_(np.isfinite(stats.mstats.kendalltau(x,y)[1])) + + def test_kendalltau_vs_mstats_basic(self): + rng = np.random.RandomState(42) + for s in range(3, 10): + a = [] + # Generate rankings with ties + for i in range(s): + a += [i]*i + b = list(a) + rng.shuffle(a) + rng.shuffle(b) + expected = mstats.kendalltau(a, b) + actual = stats.kendalltau(a, b) + assert_almost_equal(actual[0], expected[0]) + assert_almost_equal(actual[1], expected[1]) + + @pytest.mark.skipif(platform.machine() == 'ppc64le', + reason="fails/crashes on ppc64le") + @pytest.mark.slow + def test_kendalltau_large(self): + # make sure internal variable use correct precision with + # larger arrays + x = np.arange(2000, dtype=float) + x = ma.masked_greater(x, 1995) + y = np.arange(2000, dtype=float) + y = np.concatenate((y[1000:], y[:1000])) + assert_(np.isfinite(mstats.kendalltau(x, y)[1])) + + def test_kendalltau_seasonal(self): + # Tests the seasonal Kendall tau. + x = [[nan, nan, 4, 2, 16, 26, 5, 1, 5, 1, 2, 3, 1], + [4, 3, 5, 3, 2, 7, 3, 1, 1, 2, 3, 5, 3], + [3, 2, 5, 6, 18, 4, 9, 1, 1, nan, 1, 1, nan], + [nan, 6, 11, 4, 17, nan, 6, 1, 1, 2, 5, 1, 1]] + x = ma.fix_invalid(x).T + output = mstats.kendalltau_seasonal(x) + assert_almost_equal(output['global p-value (indep)'], 0.008, 3) + assert_almost_equal(output['seasonal p-value'].round(2), + [0.18,0.53,0.20,0.04]) + + @pytest.mark.parametrize("method", ("exact", "asymptotic")) + @pytest.mark.parametrize("alternative", ("two-sided", "greater", "less")) + def test_kendalltau_mstats_vs_stats(self, method, alternative): + # Test that mstats.kendalltau and stats.kendalltau with + # nan_policy='omit' matches behavior of stats.kendalltau + # Accuracy of the alternatives is tested in stats/tests/test_stats.py + + rng = np.random.default_rng(8755235945) + n = 50 + x = rng.random(n) + y = rng.random(n) + mask = rng.random(n) > 0.5 + + x_masked = ma.array(x, mask=mask) + y_masked = ma.array(y, mask=mask) + res_masked = mstats.kendalltau( + x_masked, y_masked, method=method, alternative=alternative) + + x_compressed = x_masked.compressed() + y_compressed = y_masked.compressed() + res_compressed = stats.kendalltau( + x_compressed, y_compressed, method=method, alternative=alternative) + + x[mask] = np.nan + y[mask] = np.nan + res_nan = stats.kendalltau( + x, y, method=method, nan_policy='omit', alternative=alternative) + + assert_allclose(res_masked, res_compressed) + assert_allclose(res_nan, res_compressed) + + def test_kendall_p_exact_medium(self): + # Test for the exact method with medium samples (some n >= 171) + # expected values generated using SymPy + expectations = {(100, 2393): 0.62822615287956040664, + (101, 2436): 0.60439525773513602669, + (170, 0): 2.755801935583541e-307, + (171, 0): 0.0, + (171, 1): 2.755801935583541e-307, + (172, 1): 0.0, + (200, 9797): 0.74753983745929675209, + (201, 9656): 0.40959218958120363618} + for nc, expected in expectations.items(): + res = _mstats_basic._kendall_p_exact(nc[0], nc[1]) + assert_almost_equal(res, expected) + + @pytest.mark.xslow + def test_kendall_p_exact_large(self): + # Test for the exact method with large samples (n >= 171) + # expected values generated using SymPy + expectations = {(400, 38965): 0.48444283672113314099, + (401, 39516): 0.66363159823474837662, + (800, 156772): 0.42265448483120932055, + (801, 157849): 0.53437553412194416236, + (1600, 637472): 0.84200727400323538419, + (1601, 630304): 0.34465255088058593946} + + for nc, expected in expectations.items(): + res = _mstats_basic._kendall_p_exact(nc[0], nc[1]) + assert_almost_equal(res, expected) + + @skip_xp_invalid_arg + # mstats.pointbiserialr returns a NumPy float for the statistic, but converts + # it to a masked array with no masked elements before calling `special.betainc`, + # which won't accept masked arrays when `SCIPY_ARRAY_API=1`. + def test_pointbiserial(self): + x = [1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, -1] + y = [14.8, 13.8, 12.4, 10.1, 7.1, 6.1, 5.8, 4.6, 4.3, 3.5, 3.3, 3.2, + 3.0, 2.8, 2.8, 2.5, 2.4, 2.3, 2.1, 1.7, 1.7, 1.5, 1.3, 1.3, 1.2, + 1.2, 1.1, 0.8, 0.7, 0.6, 0.5, 0.2, 0.2, 0.1, np.nan] + assert_almost_equal(mstats.pointbiserialr(x, y)[0], 0.36149, 5) + + # test for namedtuple attributes + res = mstats.pointbiserialr(x, y) + attributes = ('correlation', 'pvalue') + check_named_results(res, attributes, ma=True) + + +@skip_xp_invalid_arg +class TestTrimming: + + def test_trim(self): + a = ma.arange(10) + assert_equal(mstats.trim(a), [0,1,2,3,4,5,6,7,8,9]) + a = ma.arange(10) + assert_equal(mstats.trim(a,(2,8)), [None,None,2,3,4,5,6,7,8,None]) + a = ma.arange(10) + assert_equal(mstats.trim(a,limits=(2,8),inclusive=(False,False)), + [None,None,None,3,4,5,6,7,None,None]) + a = ma.arange(10) + assert_equal(mstats.trim(a,limits=(0.1,0.2),relative=True), + [None,1,2,3,4,5,6,7,None,None]) + + a = ma.arange(12) + a[[0,-1]] = a[5] = masked + assert_equal(mstats.trim(a, (2,8)), + [None, None, 2, 3, 4, None, 6, 7, 8, None, None, None]) + + x = ma.arange(100).reshape(10, 10) + expected = [1]*10 + [0]*70 + [1]*20 + trimx = mstats.trim(x, (0.1,0.2), relative=True, axis=None) + assert_equal(trimx._mask.ravel(), expected) + trimx = mstats.trim(x, (0.1,0.2), relative=True, axis=0) + assert_equal(trimx._mask.ravel(), expected) + trimx = mstats.trim(x, (0.1,0.2), relative=True, axis=-1) + assert_equal(trimx._mask.T.ravel(), expected) + + # same as above, but with an extra masked row inserted + x = ma.arange(110).reshape(11, 10) + x[1] = masked + expected = [1]*20 + [0]*70 + [1]*20 + trimx = mstats.trim(x, (0.1,0.2), relative=True, axis=None) + assert_equal(trimx._mask.ravel(), expected) + trimx = mstats.trim(x, (0.1,0.2), relative=True, axis=0) + assert_equal(trimx._mask.ravel(), expected) + trimx = mstats.trim(x.T, (0.1,0.2), relative=True, axis=-1) + assert_equal(trimx.T._mask.ravel(), expected) + + def test_trim_old(self): + x = ma.arange(100) + assert_equal(mstats.trimboth(x).count(), 60) + assert_equal(mstats.trimtail(x,tail='r').count(), 80) + x[50:70] = masked + trimx = mstats.trimboth(x) + assert_equal(trimx.count(), 48) + assert_equal(trimx._mask, [1]*16 + [0]*34 + [1]*20 + [0]*14 + [1]*16) + x._mask = nomask + x = x.reshape((10,10)) + assert_equal(mstats.trimboth(x).count(), 60) + assert_equal(mstats.trimtail(x).count(), 80) + + def test_trimr(self): + x = ma.arange(10) + result = mstats.trimr(x, limits=(0.15, 0.14), inclusive=(False, False)) + expected = ma.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + mask=[1, 1, 0, 0, 0, 0, 0, 0, 0, 1]) + assert_equal(result, expected) + assert_equal(result.mask, expected.mask) + + def test_trimmedmean(self): + data = ma.array([77, 87, 88,114,151,210,219,246,253,262, + 296,299,306,376,428,515,666,1310,2611]) + assert_almost_equal(mstats.trimmed_mean(data,0.1), 343, 0) + assert_almost_equal(mstats.trimmed_mean(data,(0.1,0.1)), 343, 0) + assert_almost_equal(mstats.trimmed_mean(data,(0.2,0.2)), 283, 0) + + def test_trimmedvar(self): + # Basic test. Additional tests of all arguments, edge cases, + # input validation, and proper treatment of masked arrays are needed. + rng = np.random.default_rng(3262323289434724460) + data_orig = rng.random(size=20) + data = np.sort(data_orig) + data = ma.array(data, mask=[1, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1]) + assert_allclose(mstats.trimmed_var(data_orig, 0.1), data.var()) + + def test_trimmedstd(self): + # Basic test. Additional tests of all arguments, edge cases, + # input validation, and proper treatment of masked arrays are needed. + rng = np.random.default_rng(7121029245207162780) + data_orig = rng.random(size=20) + data = np.sort(data_orig) + data = ma.array(data, mask=[1, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1]) + assert_allclose(mstats.trimmed_std(data_orig, 0.1), data.std()) + + def test_trimmed_stde(self): + data = ma.array([77, 87, 88,114,151,210,219,246,253,262, + 296,299,306,376,428,515,666,1310,2611]) + assert_almost_equal(mstats.trimmed_stde(data,(0.2,0.2)), 56.13193, 5) + assert_almost_equal(mstats.trimmed_stde(data,0.2), 56.13193, 5) + + def test_winsorization(self): + data = ma.array([77, 87, 88,114,151,210,219,246,253,262, + 296,299,306,376,428,515,666,1310,2611]) + assert_almost_equal(mstats.winsorize(data,(0.2,0.2)).var(ddof=1), + 21551.4, 1) + assert_almost_equal( + mstats.winsorize(data, (0.2,0.2),(False,False)).var(ddof=1), + 11887.3, 1) + data[5] = masked + winsorized = mstats.winsorize(data) + assert_equal(winsorized.mask, data.mask) + + def test_winsorization_nan(self): + data = ma.array([np.nan, np.nan, 0, 1, 2]) + assert_raises(ValueError, mstats.winsorize, data, (0.05, 0.05), + nan_policy='raise') + # Testing propagate (default behavior) + assert_equal(mstats.winsorize(data, (0.4, 0.4)), + ma.array([2, 2, 2, 2, 2])) + assert_equal(mstats.winsorize(data, (0.8, 0.8)), + ma.array([np.nan, np.nan, np.nan, np.nan, np.nan])) + assert_equal(mstats.winsorize(data, (0.4, 0.4), nan_policy='omit'), + ma.array([np.nan, np.nan, 2, 2, 2])) + assert_equal(mstats.winsorize(data, (0.8, 0.8), nan_policy='omit'), + ma.array([np.nan, np.nan, 2, 2, 2])) + + +@skip_xp_invalid_arg +class TestMoments: + # Comparison numbers are found using R v.1.5.1 + # note that length(testcase) = 4 + # testmathworks comes from documentation for the + # Statistics Toolbox for Matlab and can be found at both + # https://www.mathworks.com/help/stats/kurtosis.html + # https://www.mathworks.com/help/stats/skewness.html + # Note that both test cases came from here. + testcase = [1,2,3,4] + testmathworks = ma.fix_invalid([1.165, 0.6268, 0.0751, 0.3516, -0.6965, + np.nan]) + testcase_2d = ma.array( + np.array([[0.05245846, 0.50344235, 0.86589117, 0.36936353, 0.46961149], + [0.11574073, 0.31299969, 0.45925772, 0.72618805, 0.75194407], + [0.67696689, 0.91878127, 0.09769044, 0.04645137, 0.37615733], + [0.05903624, 0.29908861, 0.34088298, 0.66216337, 0.83160998], + [0.64619526, 0.94894632, 0.27855892, 0.0706151, 0.39962917]]), + mask=np.array([[True, False, False, True, False], + [True, True, True, False, True], + [False, False, False, False, False], + [True, True, True, True, True], + [False, False, True, False, False]], dtype=bool)) + + def _assert_equal(self, actual, expect, *, shape=None, dtype=None): + expect = np.asarray(expect) + if shape is not None: + expect = np.broadcast_to(expect, shape) + assert_array_equal(actual, expect) + if dtype is None: + dtype = expect.dtype + assert actual.dtype == dtype + + def test_moment(self): + y = mstats.moment(self.testcase,1) + assert_almost_equal(y,0.0,10) + y = mstats.moment(self.testcase,2) + assert_almost_equal(y,1.25) + y = mstats.moment(self.testcase,3) + assert_almost_equal(y,0.0) + y = mstats.moment(self.testcase,4) + assert_almost_equal(y,2.5625) + + # check array_like input for moment + y = mstats.moment(self.testcase, [1, 2, 3, 4]) + assert_allclose(y, [0, 1.25, 0, 2.5625]) + + # check moment input consists only of integers + y = mstats.moment(self.testcase, 0.0) + assert_allclose(y, 1.0) + assert_raises(ValueError, mstats.moment, self.testcase, 1.2) + y = mstats.moment(self.testcase, [1.0, 2, 3, 4.0]) + assert_allclose(y, [0, 1.25, 0, 2.5625]) + + # test empty input + y = mstats.moment([]) + self._assert_equal(y, np.nan, dtype=np.float64) + y = mstats.moment(np.array([], dtype=np.float32)) + self._assert_equal(y, np.nan, dtype=np.float32) + y = mstats.moment(np.zeros((1, 0)), axis=0) + self._assert_equal(y, [], shape=(0,), dtype=np.float64) + y = mstats.moment([[]], axis=1) + self._assert_equal(y, np.nan, shape=(1,), dtype=np.float64) + y = mstats.moment([[]], moment=[0, 1], axis=0) + self._assert_equal(y, [], shape=(2, 0)) + + x = np.arange(10.) + x[9] = np.nan + assert_equal(mstats.moment(x, 2), ma.masked) # NaN value is ignored + + def test_variation(self): + y = mstats.variation(self.testcase) + assert_almost_equal(y,0.44721359549996, 10) + + def test_variation_ddof(self): + # test variation with delta degrees of freedom + # regression test for gh-13341 + a = np.array([1, 2, 3, 4, 5]) + y = mstats.variation(a, ddof=1) + assert_almost_equal(y, 0.5270462766947299) + + def test_skewness(self): + y = mstats.skew(self.testmathworks) + assert_almost_equal(y,-0.29322304336607,10) + y = mstats.skew(self.testmathworks,bias=0) + assert_almost_equal(y,-0.437111105023940,10) + y = mstats.skew(self.testcase) + assert_almost_equal(y,0.0,10) + + # test that skew works on multidimensional masked arrays + correct_2d = ma.array( + np.array([0.6882870394455785, 0, 0.2665647526856708, + 0, -0.05211472114254485]), + mask=np.array([False, False, False, True, False], dtype=bool) + ) + assert_allclose(mstats.skew(self.testcase_2d, 1), correct_2d) + for i, row in enumerate(self.testcase_2d): + assert_almost_equal(mstats.skew(row), correct_2d[i]) + + correct_2d_bias_corrected = ma.array( + np.array([1.685952043212545, 0.0, 0.3973712716070531, 0, + -0.09026534484117164]), + mask=np.array([False, False, False, True, False], dtype=bool) + ) + assert_allclose(mstats.skew(self.testcase_2d, 1, bias=False), + correct_2d_bias_corrected) + for i, row in enumerate(self.testcase_2d): + assert_almost_equal(mstats.skew(row, bias=False), + correct_2d_bias_corrected[i]) + + # Check consistency between stats and mstats implementations + assert_allclose(mstats.skew(self.testcase_2d[2, :]), + stats.skew(self.testcase_2d[2, :])) + + def test_kurtosis(self): + # Set flags for axis = 0 and fisher=0 (Pearson's definition of kurtosis + # for compatibility with Matlab) + y = mstats.kurtosis(self.testmathworks, 0, fisher=0, bias=1) + assert_almost_equal(y, 2.1658856802973, 10) + # Note that MATLAB has confusing docs for the following case + # kurtosis(x,0) gives an unbiased estimate of Pearson's skewness + # kurtosis(x) gives a biased estimate of Fisher's skewness (Pearson-3) + # The MATLAB docs imply that both should give Fisher's + y = mstats.kurtosis(self.testmathworks, fisher=0, bias=0) + assert_almost_equal(y, 3.663542721189047, 10) + y = mstats.kurtosis(self.testcase, 0, 0) + assert_almost_equal(y, 1.64) + + # test that kurtosis works on multidimensional masked arrays + correct_2d = ma.array(np.array([-1.5, -3., -1.47247052385, 0., + -1.26979517952]), + mask=np.array([False, False, False, True, + False], dtype=bool)) + assert_array_almost_equal(mstats.kurtosis(self.testcase_2d, 1), + correct_2d) + for i, row in enumerate(self.testcase_2d): + assert_almost_equal(mstats.kurtosis(row), correct_2d[i]) + + correct_2d_bias_corrected = ma.array( + np.array([-1.5, -3., -1.88988209538, 0., -0.5234638463918877]), + mask=np.array([False, False, False, True, False], dtype=bool)) + assert_array_almost_equal(mstats.kurtosis(self.testcase_2d, 1, + bias=False), + correct_2d_bias_corrected) + for i, row in enumerate(self.testcase_2d): + assert_almost_equal(mstats.kurtosis(row, bias=False), + correct_2d_bias_corrected[i]) + + # Check consistency between stats and mstats implementations + assert_array_almost_equal_nulp(mstats.kurtosis(self.testcase_2d[2, :]), + stats.kurtosis(self.testcase_2d[2, :]), + nulp=4) + + +class TestMode: + def test_mode(self): + a1 = [0,0,0,1,1,1,2,3,3,3,3,4,5,6,7] + a2 = np.reshape(a1, (3,5)) + a3 = np.array([1,2,3,4,5,6]) + a4 = np.reshape(a3, (3,2)) + ma1 = ma.masked_where(ma.array(a1) > 2, a1) + ma2 = ma.masked_where(a2 > 2, a2) + ma3 = ma.masked_where(a3 < 2, a3) + ma4 = ma.masked_where(ma.array(a4) < 2, a4) + assert_equal(mstats.mode(a1, axis=None), (3,4)) + assert_equal(mstats.mode(a1, axis=0), (3,4)) + assert_equal(mstats.mode(ma1, axis=None), (0,3)) + assert_equal(mstats.mode(a2, axis=None), (3,4)) + assert_equal(mstats.mode(ma2, axis=None), (0,3)) + assert_equal(mstats.mode(a3, axis=None), (1,1)) + assert_equal(mstats.mode(ma3, axis=None), (2,1)) + assert_equal(mstats.mode(a2, axis=0), ([[0,0,0,1,1]], [[1,1,1,1,1]])) + assert_equal(mstats.mode(ma2, axis=0), ([[0,0,0,1,1]], [[1,1,1,1,1]])) + assert_equal(mstats.mode(a2, axis=-1), ([[0],[3],[3]], [[3],[3],[1]])) + assert_equal(mstats.mode(ma2, axis=-1), ([[0],[1],[0]], [[3],[1],[0]])) + assert_equal(mstats.mode(ma4, axis=0), ([[3,2]], [[1,1]])) + assert_equal(mstats.mode(ma4, axis=-1), ([[2],[3],[5]], [[1],[1],[1]])) + + a1_res = mstats.mode(a1, axis=None) + + # test for namedtuple attributes + attributes = ('mode', 'count') + check_named_results(a1_res, attributes, ma=True) + + def test_mode_modifies_input(self): + # regression test for gh-6428: mode(..., axis=None) may not modify + # the input array + im = np.zeros((100, 100)) + im[:50, :] += 1 + im[:, :50] += 1 + cp = im.copy() + mstats.mode(im, None) + assert_equal(im, cp) + + +class TestPercentile: + def setup_method(self): + self.a1 = [3, 4, 5, 10, -3, -5, 6] + self.a2 = [3, -6, -2, 8, 7, 4, 2, 1] + self.a3 = [3., 4, 5, 10, -3, -5, -6, 7.0] + + def test_percentile(self): + x = np.arange(8) * 0.5 + assert_equal(mstats.scoreatpercentile(x, 0), 0.) + assert_equal(mstats.scoreatpercentile(x, 100), 3.5) + assert_equal(mstats.scoreatpercentile(x, 50), 1.75) + + def test_2D(self): + x = ma.array([[1, 1, 1], + [1, 1, 1], + [4, 4, 3], + [1, 1, 1], + [1, 1, 1]]) + assert_equal(mstats.scoreatpercentile(x, 50), [1, 1, 1]) + + +@skip_xp_invalid_arg +class TestVariability: + """ Comparison numbers are found using R v.1.5.1 + note that length(testcase) = 4 + """ + testcase = ma.fix_invalid([1,2,3,4,np.nan]) + + def test_sem(self): + # This is not in R, so used: sqrt(var(testcase)*3/4) / sqrt(3) + y = mstats.sem(self.testcase) + assert_almost_equal(y, 0.6454972244) + n = self.testcase.count() + assert_allclose(mstats.sem(self.testcase, ddof=0) * np.sqrt(n/(n-2)), + mstats.sem(self.testcase, ddof=2)) + + def test_zmap(self): + # This is not in R, so tested by using: + # (testcase[i]-mean(testcase,axis=0)) / sqrt(var(testcase)*3/4) + y = mstats.zmap(self.testcase, self.testcase) + desired_unmaskedvals = ([-1.3416407864999, -0.44721359549996, + 0.44721359549996, 1.3416407864999]) + assert_array_almost_equal(desired_unmaskedvals, + y.data[y.mask == False], decimal=12) # noqa: E712 + + def test_zscore(self): + # This is not in R, so tested by using: + # (testcase[i]-mean(testcase,axis=0)) / sqrt(var(testcase)*3/4) + y = mstats.zscore(self.testcase) + desired = ma.fix_invalid([-1.3416407864999, -0.44721359549996, + 0.44721359549996, 1.3416407864999, np.nan]) + assert_almost_equal(desired, y, decimal=12) + + +@skip_xp_invalid_arg +class TestMisc: + + def test_obrientransform(self): + args = [[5]*5+[6]*11+[7]*9+[8]*3+[9]*2+[10]*2, + [6]+[7]*2+[8]*4+[9]*9+[10]*16] + result = [5*[3.1828]+11*[0.5591]+9*[0.0344]+3*[1.6086]+2*[5.2817]+2*[11.0538], + [10.4352]+2*[4.8599]+4*[1.3836]+9*[0.0061]+16*[0.7277]] + assert_almost_equal(np.round(mstats.obrientransform(*args).T, 4), + result, 4) + + def test_ks_2samp(self): + x = [[nan,nan, 4, 2, 16, 26, 5, 1, 5, 1, 2, 3, 1], + [4, 3, 5, 3, 2, 7, 3, 1, 1, 2, 3, 5, 3], + [3, 2, 5, 6, 18, 4, 9, 1, 1, nan, 1, 1, nan], + [nan, 6, 11, 4, 17, nan, 6, 1, 1, 2, 5, 1, 1]] + x = ma.fix_invalid(x).T + (winter, spring, summer, fall) = x.T + + assert_almost_equal(np.round(mstats.ks_2samp(winter, spring), 4), + (0.1818, 0.9628)) + assert_almost_equal(np.round(mstats.ks_2samp(winter, spring, 'g'), 4), + (0.1469, 0.6886)) + assert_almost_equal(np.round(mstats.ks_2samp(winter, spring, 'l'), 4), + (0.1818, 0.6011)) + + def test_friedmanchisq(self): + # No missing values + args = ([9.0,9.5,5.0,7.5,9.5,7.5,8.0,7.0,8.5,6.0], + [7.0,6.5,7.0,7.5,5.0,8.0,6.0,6.5,7.0,7.0], + [6.0,8.0,4.0,6.0,7.0,6.5,6.0,4.0,6.5,3.0]) + result = mstats.friedmanchisquare(*args) + assert_almost_equal(result[0], 10.4737, 4) + assert_almost_equal(result[1], 0.005317, 6) + # Missing values + x = [[nan,nan, 4, 2, 16, 26, 5, 1, 5, 1, 2, 3, 1], + [4, 3, 5, 3, 2, 7, 3, 1, 1, 2, 3, 5, 3], + [3, 2, 5, 6, 18, 4, 9, 1, 1,nan, 1, 1,nan], + [nan, 6, 11, 4, 17,nan, 6, 1, 1, 2, 5, 1, 1]] + x = ma.fix_invalid(x) + result = mstats.friedmanchisquare(*x) + assert_almost_equal(result[0], 2.0156, 4) + assert_almost_equal(result[1], 0.5692, 4) + + # test for namedtuple attributes + attributes = ('statistic', 'pvalue') + check_named_results(result, attributes, ma=True) + + # copied from `test_stats.py` + array = np.ma.asarray + x1 = [array([0.763, 0.599, 0.954, 0.628, 0.882, 0.936, 0.661, + 0.583, 0.775, 1.0, 0.94, 0.619, 0.972, 0.957]), + array([0.768, 0.591, 0.971, 0.661, 0.888, 0.931, 0.668, + 0.583, 0.838, 1.0, 0.962, 0.666, 0.981, 0.978]), + array([0.771, 0.590, 0.968, 0.654, 0.886, 0.916, 0.609, + 0.563, 0.866, 1.0, 0.965, 0.614, 0.9751, 0.946]), + array([0.798, 0.569, 0.967, 0.657, 0.898, 0.931, 0.685, + 0.625, 0.875, 1.0, 0.962, 0.669, 0.975, 0.970])] + + # From "Bioestadistica para las ciencias de la salud" Xf=18.95 p<0.001: + # x2 = [array([4, 3, 5, 3, 5, 3, 2, 5, 4, 4, 4, 3]), + # array([2, 2, 1, 2, 3, 1, 2, 3, 2, 1, 1, 3]), + # array([2, 4, 3, 3, 4, 3, 3, 4, 4, 1, 2, 1]), + # array([3, 5, 4, 3, 4, 4, 3, 3, 3, 4, 4, 4])] + + # From Jerrorl H. Zar, "Biostatistical Analysis"(example 12.6), + # Xf=10.68, 0.005 < p < 0.01: + # Probability from this example is inexact + # using Chisquare approximation of Friedman Chisquare. + x3 = [array([7.0, 9.9, 8.5, 5.1, 10.3]), + array([5.3, 5.7, 4.7, 3.5, 7.7]), + array([4.9, 7.6, 5.5, 2.8, 8.4]), + array([8.8, 8.9, 8.1, 3.3, 9.1])] + + # test using mstats + assert_array_almost_equal(mstats.friedmanchisquare(*x1), + (10.2283464566929, 0.0167215803284414)) + + # the following fails + # assert_array_almost_equal(mstats.friedmanchisquare(*x2), + # (18.9428571428571, 0.000280938375189499)) + + assert_array_almost_equal(mstats.friedmanchisquare(*x3), + (10.68, 0.0135882729582176)) + assert_raises(ValueError, mstats.friedmanchisquare, x3[0], x3[1]) + + +def test_regress_simple(): + # Regress a line with sinusoidal noise. Test for #1273. + x = np.linspace(0, 100, 100) + y = 0.2 * np.linspace(0, 100, 100) + 10 + y += np.sin(np.linspace(0, 20, 100)) + + result = mstats.linregress(x, y) + + # Result is of a correct class and with correct fields + lr = _stats_py.LinregressResult + assert_(isinstance(result, lr)) + attributes = ('slope', 'intercept', 'rvalue', 'pvalue', 'stderr') + check_named_results(result, attributes, ma=True) + assert 'intercept_stderr' in dir(result) + + # Slope and intercept are estimated correctly + assert_almost_equal(result.slope, 0.19644990055858422) + assert_almost_equal(result.intercept, 10.211269918932341) + assert_almost_equal(result.stderr, 0.002395781449783862) + assert_almost_equal(result.intercept_stderr, 0.13866936078570702) + + +def test_linregress_identical_x(): + rng = np.random.default_rng(74578245698) + x = np.zeros(10) + y = rng.random(10) + msg = "Cannot calculate a linear regression if all x values are identical" + with assert_raises(ValueError, match=msg): + mstats.linregress(x, y) + + +class TestTheilslopes: + def test_theilslopes(self): + # Test for basic slope and intercept. + slope, intercept, lower, upper = mstats.theilslopes([0, 1, 1]) + assert_almost_equal(slope, 0.5) + assert_almost_equal(intercept, 0.5) + + slope, intercept, lower, upper = mstats.theilslopes([0, 1, 1], + method='joint') + assert_almost_equal(slope, 0.5) + assert_almost_equal(intercept, 0.0) + + # Test for correct masking. + y = np.ma.array([0, 1, 100, 1], mask=[False, False, True, False]) + slope, intercept, lower, upper = mstats.theilslopes(y) + # These reference values are incorrect. With 100 masked, the outputs + # should be the same as above. + assert_almost_equal(slope, 1./3) + assert_almost_equal(intercept, 2./3) + + slope, intercept, lower, upper = mstats.theilslopes(y, + method='joint') + assert_almost_equal(slope, 1./3) + assert_almost_equal(intercept, 0.0) + + # Test of confidence intervals from example in Sen (1968). + x = [1, 2, 3, 4, 10, 12, 18] + y = [9, 15, 19, 20, 45, 55, 78] + slope, intercept, lower, upper = mstats.theilslopes(y, x, 0.07) + assert_almost_equal(slope, 4) + assert_almost_equal(intercept, 4.0) + assert_almost_equal(upper, 4.38, decimal=2) + assert_almost_equal(lower, 3.71, decimal=2) + + slope, intercept, lower, upper = mstats.theilslopes(y, x, 0.07, + method='joint') + assert_almost_equal(slope, 4) + assert_almost_equal(intercept, 6.0) + assert_almost_equal(upper, 4.38, decimal=2) + assert_almost_equal(lower, 3.71, decimal=2) + + + def test_theilslopes_warnings(self): + # Test `theilslopes` with degenerate input; see gh-15943 + msg = "All `x` coordinates.*|Mean of empty slice|invalid value encountered.*" + with pytest.warns(RuntimeWarning, match=msg): + res = mstats.theilslopes([0, 1], [0, 0]) + assert np.all(np.isnan(res)) + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", "invalid value encountered...", RuntimeWarning) + res = mstats.theilslopes([0, 0, 0], [0, 1, 0]) + assert_allclose(res, (0, 0, np.nan, np.nan)) + + + def test_theilslopes_namedtuple_consistency(self): + """ + Simple test to ensure tuple backwards-compatibility of the returned + TheilslopesResult object + """ + y = [1, 2, 4] + x = [4, 6, 8] + slope, intercept, low_slope, high_slope = mstats.theilslopes(y, x) + result = mstats.theilslopes(y, x) + + # note all four returned values are distinct here + assert_equal(slope, result.slope) + assert_equal(intercept, result.intercept) + assert_equal(low_slope, result.low_slope) + assert_equal(high_slope, result.high_slope) + + def test_gh19678_uint8(self): + # `theilslopes` returned unexpected results when `y` was an unsigned type. + # Check that this is resolved. + rng = np.random.default_rng(2549824598234528) + y = rng.integers(0, 255, size=10, dtype=np.uint8) + res = stats.theilslopes(y, y) + np.testing.assert_allclose(res.slope, 1) + + +def test_siegelslopes(): + # method should be exact for straight line + y = 2 * np.arange(10) + 0.5 + assert_equal(mstats.siegelslopes(y), (2.0, 0.5)) + assert_equal(mstats.siegelslopes(y, method='separate'), (2.0, 0.5)) + + x = 2 * np.arange(10) + y = 5 * x - 3.0 + assert_equal(mstats.siegelslopes(y, x), (5.0, -3.0)) + assert_equal(mstats.siegelslopes(y, x, method='separate'), (5.0, -3.0)) + + # method is robust to outliers: brekdown point of 50% + y[:4] = 1000 + assert_equal(mstats.siegelslopes(y, x), (5.0, -3.0)) + + # if there are no outliers, results should be comparable to linregress + x = np.arange(10) + y = -2.3 + 0.3*x + stats.norm.rvs(size=10, random_state=231) + slope_ols, intercept_ols, _, _, _ = stats.linregress(x, y) + + slope, intercept = mstats.siegelslopes(y, x) + assert_allclose(slope, slope_ols, rtol=0.1) + assert_allclose(intercept, intercept_ols, rtol=0.1) + + slope, intercept = mstats.siegelslopes(y, x, method='separate') + assert_allclose(slope, slope_ols, rtol=0.1) + assert_allclose(intercept, intercept_ols, rtol=0.1) + + +def test_siegelslopes_namedtuple_consistency(): + """ + Simple test to ensure tuple backwards-compatibility of the returned + SiegelslopesResult object. + """ + y = [1, 2, 4] + x = [4, 6, 8] + slope, intercept = mstats.siegelslopes(y, x) + result = mstats.siegelslopes(y, x) + + # note both returned values are distinct here + assert_equal(slope, result.slope) + assert_equal(intercept, result.intercept) + + +def test_sen_seasonal_slopes(): + rng = np.random.default_rng(5765986256978575148) + x = rng.random(size=(100, 4)) + intra_slope, inter_slope = mstats.sen_seasonal_slopes(x) + + # reference implementation from the `sen_seasonal_slopes` documentation + def dijk(yi): + n = len(yi) + x = np.arange(n) + dy = yi - yi[:, np.newaxis] + dx = x - x[:, np.newaxis] + mask = np.triu(np.ones((n, n), dtype=bool), k=1) + return dy[mask]/dx[mask] + + for i in range(4): + assert_allclose(np.median(dijk(x[:, i])), intra_slope[i]) + + all_slopes = np.concatenate([dijk(x[:, i]) for i in range(x.shape[1])]) + assert_allclose(np.median(all_slopes), inter_slope) + + +def test_plotting_positions(): + # Regression test for #1256 + pos = mstats.plotting_positions(np.arange(3), 0, 0) + assert_array_almost_equal(pos.data, np.array([0.25, 0.5, 0.75])) + + +@skip_xp_invalid_arg +class TestNormalitytests: + + def test_vs_nonmasked(self): + x = np.array((-2, -1, 0, 1, 2, 3)*4)**2 + assert_array_almost_equal(mstats.normaltest(x), + stats.normaltest(x)) + assert_array_almost_equal(mstats.skewtest(x), + stats.skewtest(x)) + assert_array_almost_equal(mstats.kurtosistest(x), + stats.kurtosistest(x)) + + funcs = [stats.normaltest, stats.skewtest, stats.kurtosistest] + mfuncs = [mstats.normaltest, mstats.skewtest, mstats.kurtosistest] + x = [1, 2, 3, 4] + for func, mfunc in zip(funcs, mfuncs): + with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit): + res = func(x) + assert np.isnan(res.statistic) + assert np.isnan(res.pvalue) + assert_raises(ValueError, mfunc, x) + + def test_axis_None(self): + # Test axis=None (equal to axis=0 for 1-D input) + x = np.array((-2,-1,0,1,2,3)*4)**2 + assert_allclose(mstats.normaltest(x, axis=None), mstats.normaltest(x)) + assert_allclose(mstats.skewtest(x, axis=None), mstats.skewtest(x)) + assert_allclose(mstats.kurtosistest(x, axis=None), + mstats.kurtosistest(x)) + + def test_maskedarray_input(self): + # Add some masked values, test result doesn't change + x = np.array((-2, -1, 0, 1, 2, 3)*4)**2 + xm = np.ma.array(np.r_[np.inf, x, 10], + mask=np.r_[True, [False] * x.size, True]) + assert_allclose(mstats.normaltest(xm), stats.normaltest(x)) + assert_allclose(mstats.skewtest(xm), stats.skewtest(x)) + assert_allclose(mstats.kurtosistest(xm), stats.kurtosistest(x)) + + def test_nd_input(self): + x = np.array((-2, -1, 0, 1, 2, 3)*4)**2 + x_2d = np.vstack([x] * 2).T + for func in [mstats.normaltest, mstats.skewtest, mstats.kurtosistest]: + res_1d = func(x) + res_2d = func(x_2d) + assert_allclose(res_2d[0], [res_1d[0]] * 2) + assert_allclose(res_2d[1], [res_1d[1]] * 2) + + def test_normaltest_result_attributes(self): + x = np.array((-2, -1, 0, 1, 2, 3)*4)**2 + res = mstats.normaltest(x) + attributes = ('statistic', 'pvalue') + check_named_results(res, attributes, ma=True) + + def test_kurtosistest_result_attributes(self): + x = np.array((-2, -1, 0, 1, 2, 3)*4)**2 + res = mstats.kurtosistest(x) + attributes = ('statistic', 'pvalue') + check_named_results(res, attributes, ma=True) + + def test_regression_9033(self): + # x clearly non-normal but power of negative denom needs + # to be handled correctly to reject normality + counts = [128, 0, 58, 7, 0, 41, 16, 0, 0, 167] + x = np.hstack([np.full(c, i) for i, c in enumerate(counts)]) + assert_equal(mstats.kurtosistest(x)[1] < 0.01, True) + + @pytest.mark.parametrize("test", ["skewtest", "kurtosistest"]) + @pytest.mark.parametrize("alternative", ["less", "greater"]) + def test_alternative(self, test, alternative): + x = stats.norm.rvs(loc=10, scale=2.5, size=30, random_state=123) + + stats_test = getattr(stats, test) + mstats_test = getattr(mstats, test) + + z_ex, p_ex = stats_test(x, alternative=alternative) + z, p = mstats_test(x, alternative=alternative) + assert_allclose(z, z_ex, atol=1e-12) + assert_allclose(p, p_ex, atol=1e-12) + + # test with masked arrays + x[1:5] = np.nan + x = np.ma.masked_array(x, mask=np.isnan(x)) + z_ex, p_ex = stats_test(x.compressed(), alternative=alternative) + z, p = mstats_test(x, alternative=alternative) + assert_allclose(z, z_ex, atol=1e-12) + assert_allclose(p, p_ex, atol=1e-12) + + def test_bad_alternative(self): + x = stats.norm.rvs(size=20, random_state=123) + msg = r"`alternative` must be..." + + with pytest.raises(ValueError, match=msg): + mstats.skewtest(x, alternative='error') + + with pytest.raises(ValueError, match=msg): + mstats.kurtosistest(x, alternative='error') + + +class TestFOneway: + def test_result_attributes(self): + a = np.array([655, 788], dtype=np.uint16) + b = np.array([789, 772], dtype=np.uint16) + res = mstats.f_oneway(a, b) + attributes = ('statistic', 'pvalue') + check_named_results(res, attributes, ma=True) + + +class TestMannwhitneyu: + # data from gh-1428 + x = np.array([1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., + 1., 1., 1., 1., 1., 1., 1., 2., 1., 1., 1., 1., 1., 1., + 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., + 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., + 1., 1., 1., 1., 1., 1., 1., 2., 1., 1., 1., 1., 1., 1., + 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., + 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 2., + 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., + 1., 1., 2., 1., 1., 1., 1., 2., 1., 1., 2., 1., 1., 2., + 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., + 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 2., 1., + 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., + 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., + 1., 1., 1., 1., 1., 1., 1., 2., 1., 1., 1., 1., 1., 1., + 1., 1., 1., 2., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., + 1., 1., 1., 1., 1., 1., 1., 1., 3., 1., 1., 1., 1., 1., + 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., + 1., 1., 1., 1., 1., 1.]) + + y = np.array([1., 1., 1., 1., 1., 1., 1., 2., 1., 2., 1., 1., 1., 1., + 2., 1., 1., 1., 2., 1., 1., 1., 1., 1., 2., 1., 1., 3., + 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 2., 1., 2., 1., + 1., 1., 1., 1., 1., 2., 1., 1., 1., 1., 1., 1., 1., 1., + 1., 1., 1., 1., 1., 1., 1., 2., 1., 1., 1., 1., 1., 2., + 2., 1., 1., 2., 1., 1., 2., 1., 2., 1., 1., 1., 1., 2., + 2., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., + 1., 2., 1., 1., 1., 1., 1., 2., 2., 2., 1., 1., 1., 1., + 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., + 2., 1., 1., 2., 1., 1., 1., 1., 2., 1., 1., 1., 1., 1., + 1., 1., 1., 1., 1., 1., 1., 2., 1., 1., 1., 2., 1., 1., + 1., 1., 1., 1.]) + + def test_result_attributes(self): + res = mstats.mannwhitneyu(self.x, self.y) + attributes = ('statistic', 'pvalue') + check_named_results(res, attributes, ma=True) + + def test_against_stats(self): + # gh-4641 reported that stats.mannwhitneyu returned half the p-value + # of mstats.mannwhitneyu. Default alternative of stats.mannwhitneyu + # is now two-sided, so they match. + res1 = mstats.mannwhitneyu(self.x, self.y) + res2 = stats.mannwhitneyu(self.x, self.y) + assert res1.statistic == res2.statistic + assert_allclose(res1.pvalue, res2.pvalue) + + +class TestKruskal: + def test_result_attributes(self): + x = [1, 3, 5, 7, 9] + y = [2, 4, 6, 8, 10] + + res = mstats.kruskal(x, y) + attributes = ('statistic', 'pvalue') + check_named_results(res, attributes, ma=True) + + +# TODO: for all ttest functions, add tests with masked array inputs +class TestTtest_rel: + def setup_method(self): + self.rng = np.random.default_rng(9373599542) + + def test_vs_nonmasked(self): + outcome = self.rng.standard_normal((20, 4)) + [0, 0, 1, 2] + + # 1-D inputs + res1 = stats.ttest_rel(outcome[:, 0], outcome[:, 1]) + res2 = mstats.ttest_rel(outcome[:, 0], outcome[:, 1]) + assert_allclose(res1, res2) + + # 2-D inputs + res1 = stats.ttest_rel(outcome[:, 0], outcome[:, 1], axis=None) + res2 = mstats.ttest_rel(outcome[:, 0], outcome[:, 1], axis=None) + assert_allclose(res1, res2) + res1 = stats.ttest_rel(outcome[:, :2], outcome[:, 2:], axis=0) + res2 = mstats.ttest_rel(outcome[:, :2], outcome[:, 2:], axis=0) + assert_allclose(res1, res2) + + # Check default is axis=0 + res3 = mstats.ttest_rel(outcome[:, :2], outcome[:, 2:]) + assert_allclose(res2, res3) + + def test_fully_masked(self): + outcome = ma.masked_array(self.rng.standard_normal((3, 2)), + mask=[[1, 1, 1], [0, 0, 0]]) + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", "invalid value encountered in absolute", RuntimeWarning) + for pair in [(outcome[:, 0], outcome[:, 1]), + ([np.nan, np.nan], [1.0, 2.0])]: + t, p = mstats.ttest_rel(*pair) + assert_array_equal(t, (np.nan, np.nan)) + assert_array_equal(p, (np.nan, np.nan)) + + def test_result_attributes(self): + outcome = self.rng.standard_normal((20, 4)) + [0, 0, 1, 2] + + res = mstats.ttest_rel(outcome[:, 0], outcome[:, 1]) + attributes = ('statistic', 'pvalue') + check_named_results(res, attributes, ma=True) + + def test_invalid_input_size(self): + assert_raises(ValueError, mstats.ttest_rel, + np.arange(10), np.arange(11)) + x = np.arange(24) + assert_raises(ValueError, mstats.ttest_rel, + x.reshape(2, 3, 4), x.reshape(2, 4, 3), axis=1) + assert_raises(ValueError, mstats.ttest_rel, + x.reshape(2, 3, 4), x.reshape(2, 4, 3), axis=2) + + def test_empty(self): + res1 = mstats.ttest_rel([], []) + assert_(np.all(np.isnan(res1))) + + def test_zero_division(self): + t, p = mstats.ttest_ind([0, 0, 0], [1, 1, 1]) + assert_equal((np.abs(t), p), (np.inf, 0)) + + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", "invalid value encountered in absolute", RuntimeWarning) + t, p = mstats.ttest_ind([0, 0, 0], [0, 0, 0]) + assert_array_equal(t, np.array([np.nan, np.nan])) + assert_array_equal(p, np.array([np.nan, np.nan])) + + def test_bad_alternative(self): + msg = r"alternative must be 'less', 'greater' or 'two-sided'" + with pytest.raises(ValueError, match=msg): + mstats.ttest_ind([1, 2, 3], [4, 5, 6], alternative='foo') + + @pytest.mark.parametrize("alternative", ["less", "greater"]) + def test_alternative(self, alternative): + x = stats.norm.rvs(loc=10, scale=5, size=25, random_state=42) + y = stats.norm.rvs(loc=8, scale=2, size=25, random_state=42) + + t_ex, p_ex = stats.ttest_rel(x, y, alternative=alternative) + t, p = mstats.ttest_rel(x, y, alternative=alternative) + assert_allclose(t, t_ex, rtol=1e-14) + assert_allclose(p, p_ex, rtol=1e-14) + + # test with masked arrays + x[1:10] = np.nan + y[1:10] = np.nan + x = np.ma.masked_array(x, mask=np.isnan(x)) + y = np.ma.masked_array(y, mask=np.isnan(y)) + t, p = mstats.ttest_rel(x, y, alternative=alternative) + t_ex, p_ex = stats.ttest_rel(x.compressed(), y.compressed(), + alternative=alternative) + assert_allclose(t, t_ex, rtol=1e-14) + assert_allclose(p, p_ex, rtol=1e-14) + + +class TestTtest_ind: + def setup_method(self): + self.rng = np.random.default_rng(4776115069) + + def test_vs_nonmasked(self): + outcome = self.rng.standard_normal((20, 4)) + [0, 0, 1, 2] + + # 1-D inputs + res1 = stats.ttest_ind(outcome[:, 0], outcome[:, 1]) + res2 = mstats.ttest_ind(outcome[:, 0], outcome[:, 1]) + assert_allclose(res1, res2) + + # 2-D inputs + res1 = stats.ttest_ind(outcome[:, 0], outcome[:, 1], axis=None) + res2 = mstats.ttest_ind(outcome[:, 0], outcome[:, 1], axis=None) + assert_allclose(res1, res2) + res1 = stats.ttest_ind(outcome[:, :2], outcome[:, 2:], axis=0) + res2 = mstats.ttest_ind(outcome[:, :2], outcome[:, 2:], axis=0) + assert_allclose(res1, res2) + + # Check default is axis=0 + res3 = mstats.ttest_ind(outcome[:, :2], outcome[:, 2:]) + assert_allclose(res2, res3) + + # Check equal_var + res4 = stats.ttest_ind(outcome[:, 0], outcome[:, 1], equal_var=True) + res5 = mstats.ttest_ind(outcome[:, 0], outcome[:, 1], equal_var=True) + assert_allclose(res4, res5) + res4 = stats.ttest_ind(outcome[:, 0], outcome[:, 1], equal_var=False) + res5 = mstats.ttest_ind(outcome[:, 0], outcome[:, 1], equal_var=False) + assert_allclose(res4, res5) + + def test_fully_masked(self): + outcome = ma.masked_array(self.rng.standard_normal((3, 2)), + mask=[[1, 1, 1], [0, 0, 0]]) + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", "invalid value encountered in absolute", RuntimeWarning) + for pair in [(outcome[:, 0], outcome[:, 1]), + ([np.nan, np.nan], [1.0, 2.0])]: + t, p = mstats.ttest_ind(*pair) + assert_array_equal(t, (np.nan, np.nan)) + assert_array_equal(p, (np.nan, np.nan)) + + def test_result_attributes(self): + outcome = self.rng.standard_normal((20, 4)) + [0, 0, 1, 2] + + res = mstats.ttest_ind(outcome[:, 0], outcome[:, 1]) + attributes = ('statistic', 'pvalue') + check_named_results(res, attributes, ma=True) + + def test_empty(self): + res1 = mstats.ttest_ind([], []) + assert_(np.all(np.isnan(res1))) + + def test_zero_division(self): + t, p = mstats.ttest_ind([0, 0, 0], [1, 1, 1]) + assert_equal((np.abs(t), p), (np.inf, 0)) + + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", "invalid value encountered in absolute", RuntimeWarning) + t, p = mstats.ttest_ind([0, 0, 0], [0, 0, 0]) + assert_array_equal(t, (np.nan, np.nan)) + assert_array_equal(p, (np.nan, np.nan)) + + t, p = mstats.ttest_ind([0, 0, 0], [1, 1, 1], equal_var=False) + assert_equal((np.abs(t), p), (np.inf, 0)) + assert_array_equal(mstats.ttest_ind([0, 0, 0], [0, 0, 0], + equal_var=False), (np.nan, np.nan)) + + def test_bad_alternative(self): + msg = r"alternative must be 'less', 'greater' or 'two-sided'" + with pytest.raises(ValueError, match=msg): + mstats.ttest_ind([1, 2, 3], [4, 5, 6], alternative='foo') + + @pytest.mark.parametrize("alternative", ["less", "greater"]) + def test_alternative(self, alternative): + x = stats.norm.rvs(loc=10, scale=2, size=100, random_state=123) + y = stats.norm.rvs(loc=8, scale=2, size=100, random_state=123) + + t_ex, p_ex = stats.ttest_ind(x, y, alternative=alternative) + t, p = mstats.ttest_ind(x, y, alternative=alternative) + assert_allclose(t, t_ex, rtol=1e-14) + assert_allclose(p, p_ex, rtol=1e-14) + + # test with masked arrays + x[1:10] = np.nan + y[80:90] = np.nan + x = np.ma.masked_array(x, mask=np.isnan(x)) + y = np.ma.masked_array(y, mask=np.isnan(y)) + t_ex, p_ex = stats.ttest_ind(x.compressed(), y.compressed(), + alternative=alternative) + t, p = mstats.ttest_ind(x, y, alternative=alternative) + assert_allclose(t, t_ex, rtol=1e-14) + assert_allclose(p, p_ex, rtol=1e-14) + + +class TestTtest_1samp: + def setup_method(self): + self.rng = np.random.default_rng(6043813830) + + def test_vs_nonmasked(self): + outcome = self.rng.standard_normal((20, 4)) + [0, 0, 1, 2] + + # 1-D inputs + res1 = stats.ttest_1samp(outcome[:, 0], 1) + res2 = mstats.ttest_1samp(outcome[:, 0], 1) + assert_allclose(res1, res2) + + def test_fully_masked(self): + outcome = ma.masked_array(self.rng.standard_normal(3), mask=[1, 1, 1]) + expected = (np.nan, np.nan) + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", "invalid value encountered in absolute", RuntimeWarning) + for pair in [((np.nan, np.nan), 0.0), (outcome, 0.0)]: + t, p = mstats.ttest_1samp(*pair) + assert_array_equal(p, expected) + assert_array_equal(t, expected) + + def test_result_attributes(self): + outcome = self.rng.standard_normal((20, 4)) + [0, 0, 1, 2] + + res = mstats.ttest_1samp(outcome[:, 0], 1) + attributes = ('statistic', 'pvalue') + check_named_results(res, attributes, ma=True) + + def test_empty(self): + res1 = mstats.ttest_1samp([], 1) + assert_(np.all(np.isnan(res1))) + + def test_zero_division(self): + t, p = mstats.ttest_1samp([0, 0, 0], 1) + assert_equal((np.abs(t), p), (np.inf, 0)) + + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", "invalid value encountered in absolute", RuntimeWarning) + t, p = mstats.ttest_1samp([0, 0, 0], 0) + assert_(np.isnan(t)) + assert_array_equal(p, (np.nan, np.nan)) + + def test_bad_alternative(self): + msg = r"alternative must be 'less', 'greater' or 'two-sided'" + with pytest.raises(ValueError, match=msg): + mstats.ttest_1samp([1, 2, 3], 4, alternative='foo') + + @pytest.mark.parametrize("alternative", ["less", "greater"]) + def test_alternative(self, alternative): + x = stats.norm.rvs(loc=10, scale=2, size=100, random_state=123) + + t_ex, p_ex = stats.ttest_1samp(x, 9, alternative=alternative) + t, p = mstats.ttest_1samp(x, 9, alternative=alternative) + assert_allclose(t, t_ex, rtol=1e-14) + assert_allclose(p, p_ex, rtol=1e-14) + + # test with masked arrays + x[1:10] = np.nan + x = np.ma.masked_array(x, mask=np.isnan(x)) + t_ex, p_ex = stats.ttest_1samp(x.compressed(), 9, + alternative=alternative) + t, p = mstats.ttest_1samp(x, 9, alternative=alternative) + assert_allclose(t, t_ex, rtol=1e-14) + assert_allclose(p, p_ex, rtol=1e-14) + + +class TestDescribe: + """ + Tests for mstats.describe. + + Note that there are also tests for `mstats.describe` in the + class TestCompareWithStats. + """ + def test_basic_with_axis(self): + # This is a basic test that is also a regression test for gh-7303. + a = np.ma.masked_array([[0, 1, 2, 3, 4, 9], + [5, 5, 0, 9, 3, 3]], + mask=[[0, 0, 0, 0, 0, 1], + [0, 0, 1, 1, 0, 0]]) + result = mstats.describe(a, axis=1) + assert_equal(result.nobs, [5, 4]) + amin, amax = result.minmax + assert_equal(amin, [0, 3]) + assert_equal(amax, [4, 5]) + assert_equal(result.mean, [2.0, 4.0]) + assert_equal(result.variance, [2.0, 1.0]) + assert_equal(result.skewness, [0.0, 0.0]) + assert_allclose(result.kurtosis, [-1.3, -2.0]) + + +@skip_xp_invalid_arg +class TestCompareWithStats: + """ + Class to compare mstats results with stats results. + + It is in general assumed that scipy.stats is at a more mature stage than + stats.mstats. If a routine in mstats results in similar results like in + scipy.stats, this is considered also as a proper validation of scipy.mstats + routine. + + Different sample sizes are used for testing, as some problems between stats + and mstats are dependent on sample size. + + Author: Alexander Loew + + NOTE that some tests fail. This might be caused by + a) actual differences or bugs between stats and mstats + b) numerical inaccuracies + c) different definitions of routine interfaces + + These failures need to be checked. Current workaround is to have disabled these + tests, but issuing reports on scipy-dev + + """ + def get_n(self): + """ Returns list of sample sizes to be used for comparison. """ + return [1000, 100, 10, 5] + + def generate_xy_sample(self, n): + # This routine generates numpy arrays and corresponding masked arrays + # with the same data, but additional masked values + rng = np.random.RandomState(1234567) + x = rng.randn(n) + y = x + rng.randn(n) + xm = np.full(len(x) + 5, 1e16) + ym = np.full(len(y) + 5, 1e16) + xm[0:len(x)] = x + ym[0:len(y)] = y + mask = xm > 9e15 + xm = np.ma.array(xm, mask=mask) + ym = np.ma.array(ym, mask=mask) + return x, y, xm, ym + + def generate_xy_sample2D(self, n, nx): + x = np.full((n, nx), np.nan) + y = np.full((n, nx), np.nan) + xm = np.full((n+5, nx), np.nan) + ym = np.full((n+5, nx), np.nan) + + for i in range(nx): + x[:, i], y[:, i], dx, dy = self.generate_xy_sample(n) + + xm[0:n, :] = x[0:n] + ym[0:n, :] = y[0:n] + xm = np.ma.array(xm, mask=np.isnan(xm)) + ym = np.ma.array(ym, mask=np.isnan(ym)) + return x, y, xm, ym + + def test_linregress(self): + for n in self.get_n(): + x, y, xm, ym = self.generate_xy_sample(n) + result1 = stats.linregress(x, y) + result2 = stats.mstats.linregress(xm, ym) + assert_allclose(np.asarray(result1), np.asarray(result2)) + + def test_pearsonr(self): + for n in self.get_n(): + x, y, xm, ym = self.generate_xy_sample(n) + r, p = stats.pearsonr(x, y) + rm, pm = stats.mstats.pearsonr(xm, ym) + + assert_almost_equal(r, rm, decimal=14) + assert_almost_equal(p, pm, decimal=14) + + def test_spearmanr(self): + for n in self.get_n(): + x, y, xm, ym = self.generate_xy_sample(n) + r, p = stats.spearmanr(x, y) + rm, pm = stats.mstats.spearmanr(xm, ym) + assert_almost_equal(r, rm, 14) + assert_almost_equal(p, pm, 14) + + def test_spearmanr_backcompat_useties(self): + # A regression test to ensure we don't break backwards compat + # more than we have to (see gh-9204). + x = np.arange(6) + assert_raises(ValueError, mstats.spearmanr, x, x, False) + + def test_gmean(self): + for n in self.get_n(): + x, y, xm, ym = self.generate_xy_sample(n) + r = stats.gmean(abs(x)) + rm = stats.mstats.gmean(abs(xm)) + assert_allclose(r, rm, rtol=1e-13) + + r = stats.gmean(abs(y)) + rm = stats.mstats.gmean(abs(ym)) + assert_allclose(r, rm, rtol=1e-13) + + def test_hmean(self): + for n in self.get_n(): + x, y, xm, ym = self.generate_xy_sample(n) + + r = stats.hmean(abs(x)) + rm = stats.mstats.hmean(abs(xm)) + assert_almost_equal(r, rm, 10) + + r = stats.hmean(abs(y)) + rm = stats.mstats.hmean(abs(ym)) + assert_almost_equal(r, rm, 10) + + def test_skew(self): + for n in self.get_n(): + x, y, xm, ym = self.generate_xy_sample(n) + + r = stats.skew(x) + rm = stats.mstats.skew(xm) + assert_almost_equal(r, rm, 10) + + r = stats.skew(y) + rm = stats.mstats.skew(ym) + assert_almost_equal(r, rm, 10) + + def test_moment(self): + for n in self.get_n(): + x, y, xm, ym = self.generate_xy_sample(n) + + r = stats.moment(x) + rm = stats.mstats.moment(xm) + assert_almost_equal(r, rm, 10) + + r = stats.moment(y) + rm = stats.mstats.moment(ym) + assert_almost_equal(r, rm, 10) + + def test_zscore(self): + for n in self.get_n(): + x, y, xm, ym = self.generate_xy_sample(n) + + # reference solution + zx = (x - x.mean()) / x.std() + zy = (y - y.mean()) / y.std() + + # validate stats + assert_allclose(stats.zscore(x), zx, rtol=1e-10) + assert_allclose(stats.zscore(y), zy, rtol=1e-10) + + # compare stats and mstats + assert_allclose(stats.zscore(x), stats.mstats.zscore(xm[0:len(x)]), + rtol=1e-10) + assert_allclose(stats.zscore(y), stats.mstats.zscore(ym[0:len(y)]), + rtol=1e-10) + + def test_kurtosis(self): + for n in self.get_n(): + x, y, xm, ym = self.generate_xy_sample(n) + r = stats.kurtosis(x) + rm = stats.mstats.kurtosis(xm) + assert_almost_equal(r, rm, 10) + + r = stats.kurtosis(y) + rm = stats.mstats.kurtosis(ym) + assert_almost_equal(r, rm, 10) + + def test_sem(self): + # example from stats.sem doc + a = np.arange(20).reshape(5, 4) + am = np.ma.array(a) + r = stats.sem(a, ddof=1) + rm = stats.mstats.sem(am, ddof=1) + + assert_allclose(r, 2.82842712, atol=1e-5) + assert_allclose(rm, 2.82842712, atol=1e-5) + + for n in self.get_n(): + x, y, xm, ym = self.generate_xy_sample(n) + assert_almost_equal(stats.mstats.sem(xm, axis=None, ddof=0), + stats.sem(x, axis=None, ddof=0), decimal=13) + assert_almost_equal(stats.mstats.sem(ym, axis=None, ddof=0), + stats.sem(y, axis=None, ddof=0), decimal=13) + assert_almost_equal(stats.mstats.sem(xm, axis=None, ddof=1), + stats.sem(x, axis=None, ddof=1), decimal=13) + assert_almost_equal(stats.mstats.sem(ym, axis=None, ddof=1), + stats.sem(y, axis=None, ddof=1), decimal=13) + + def test_describe(self): + for n in self.get_n(): + x, y, xm, ym = self.generate_xy_sample(n) + r = stats.describe(x, ddof=1) + rm = stats.mstats.describe(xm, ddof=1) + for ii in range(6): + assert_almost_equal(np.asarray(r[ii]), + np.asarray(rm[ii]), + decimal=12) + + def test_describe_result_attributes(self): + actual = mstats.describe(np.arange(5)) + attributes = ('nobs', 'minmax', 'mean', 'variance', 'skewness', + 'kurtosis') + check_named_results(actual, attributes, ma=True) + + def test_rankdata(self): + for n in self.get_n(): + x, y, xm, ym = self.generate_xy_sample(n) + r = stats.rankdata(x) + rm = stats.mstats.rankdata(x) + assert_allclose(r, rm) + + def test_tmean(self): + for n in self.get_n(): + x, y, xm, ym = self.generate_xy_sample(n) + assert_almost_equal(stats.tmean(x),stats.mstats.tmean(xm), 14) + assert_almost_equal(stats.tmean(y),stats.mstats.tmean(ym), 14) + + def test_tmax(self): + for n in self.get_n(): + x, y, xm, ym = self.generate_xy_sample(n) + assert_almost_equal(stats.tmax(x,2.), + stats.mstats.tmax(xm,2.), 10) + assert_almost_equal(stats.tmax(y,2.), + stats.mstats.tmax(ym,2.), 10) + + assert_almost_equal(stats.tmax(x, upperlimit=3.), + stats.mstats.tmax(xm, upperlimit=3.), 10) + assert_almost_equal(stats.tmax(y, upperlimit=3.), + stats.mstats.tmax(ym, upperlimit=3.), 10) + + def test_tmin(self): + for n in self.get_n(): + x, y, xm, ym = self.generate_xy_sample(n) + assert_equal(stats.tmin(x), stats.mstats.tmin(xm)) + assert_equal(stats.tmin(y), stats.mstats.tmin(ym)) + + assert_almost_equal(stats.tmin(x, lowerlimit=-1.), + stats.mstats.tmin(xm, lowerlimit=-1.), 10) + assert_almost_equal(stats.tmin(y, lowerlimit=-1.), + stats.mstats.tmin(ym, lowerlimit=-1.), 10) + + def test_zmap(self): + for n in self.get_n(): + x, y, xm, ym = self.generate_xy_sample(n) + z = stats.zmap(x, y) + zm = stats.mstats.zmap(xm, ym) + assert_allclose(z, zm[0:len(z)], atol=1e-10) + + def test_variation(self): + for n in self.get_n(): + x, y, xm, ym = self.generate_xy_sample(n) + assert_almost_equal(stats.variation(x), stats.mstats.variation(xm), + decimal=12) + assert_almost_equal(stats.variation(y), stats.mstats.variation(ym), + decimal=12) + + def test_tvar(self): + for n in self.get_n(): + x, y, xm, ym = self.generate_xy_sample(n) + assert_almost_equal(stats.tvar(x), stats.mstats.tvar(xm), + decimal=12) + assert_almost_equal(stats.tvar(y), stats.mstats.tvar(ym), + decimal=12) + + def test_trimboth(self): + a = np.arange(20) + b = stats.trimboth(a, 0.1) + bm = stats.mstats.trimboth(a, 0.1) + assert_allclose(np.sort(b), bm.data[~bm.mask]) + + def test_tsem(self): + for n in self.get_n(): + x, y, xm, ym = self.generate_xy_sample(n) + assert_almost_equal(stats.tsem(x), stats.mstats.tsem(xm), + decimal=14) + assert_almost_equal(stats.tsem(y), stats.mstats.tsem(ym), + decimal=14) + assert_almost_equal(stats.tsem(x, limits=(-2., 2.)), + stats.mstats.tsem(xm, limits=(-2., 2.)), + decimal=14) + + def test_skewtest(self): + # this test is for 1D data + for n in self.get_n(): + if n > 8: + x, y, xm, ym = self.generate_xy_sample(n) + r = stats.skewtest(x) + rm = stats.mstats.skewtest(xm) + assert_allclose(r, rm) + + def test_skewtest_result_attributes(self): + x = np.array((-2, -1, 0, 1, 2, 3)*4)**2 + res = mstats.skewtest(x) + attributes = ('statistic', 'pvalue') + check_named_results(res, attributes, ma=True) + + def test_skewtest_2D_notmasked(self): + # a normal ndarray is passed to the masked function + rng = np.random.default_rng(2790153686) + x = rng.random((20, 2)) * 20. + r = stats.skewtest(x) + rm = stats.mstats.skewtest(x) + assert_allclose(np.asarray(r), np.asarray(rm)) + + def test_skewtest_2D_WithMask(self): + nx = 2 + for n in self.get_n(): + if n > 8: + x, y, xm, ym = self.generate_xy_sample2D(n, nx) + r = stats.skewtest(x) + rm = stats.mstats.skewtest(xm) + + assert_allclose(r[0][0], rm[0][0], rtol=1e-14) + assert_allclose(r[0][1], rm[0][1], rtol=1e-14) + + def test_normaltest(self): + with np.errstate(over='raise'), warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", "`kurtosistest` p-value may be inaccurate", UserWarning) + warnings.filterwarnings( + "ignore", "kurtosistest only valid for n>=20", UserWarning) + for n in self.get_n(): + if n > 8: + x, y, xm, ym = self.generate_xy_sample(n) + r = stats.normaltest(x) + rm = stats.mstats.normaltest(xm) + assert_allclose(np.asarray(r), np.asarray(rm)) + + def test_find_repeats(self): + x = np.asarray([1, 1, 2, 2, 3, 3, 3, 4, 4, 4, 4]).astype('float') + tmp = np.asarray([1, 1, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5]).astype('float') + mask = (tmp == 5.) + xm = np.ma.array(tmp, mask=mask) + x_orig, xm_orig = x.copy(), xm.copy() + + unique, unique_counts = np.unique(x, return_counts=True) + r = unique[unique_counts > 1], unique_counts[unique_counts > 1] + rm = stats.mstats.find_repeats(xm) + + assert_equal(r, rm) + assert_equal(x, x_orig) + assert_equal(xm, xm_orig) + + # This crazy behavior is expected by count_tied_groups, but is not + # in the docstring... + _, counts = stats.mstats.find_repeats([]) + assert_equal(counts, np.array(0, dtype=np.intp)) + + def test_kendalltau(self): + for n in self.get_n(): + x, y, xm, ym = self.generate_xy_sample(n) + r = stats.kendalltau(x, y) + rm = stats.mstats.kendalltau(xm, ym) + assert_almost_equal(r[0], rm[0], decimal=10) + assert_almost_equal(r[1], rm[1], decimal=7) + + def test_obrientransform(self): + for n in self.get_n(): + x, y, xm, ym = self.generate_xy_sample(n) + r = np.stack(stats.obrientransform(x)) + rm = stats.mstats.obrientransform(xm) + assert_almost_equal(r.T, rm[0:len(x)]) + + def test_ks_1samp(self): + """Checks that mstats.ks_1samp and stats.ks_1samp agree on masked arrays.""" + for mode in ['auto', 'exact', 'asymp']: + with warnings.catch_warnings(): + for alternative in ['less', 'greater', 'two-sided']: + for n in self.get_n(): + x, y, xm, ym = self.generate_xy_sample(n) + res1 = stats.ks_1samp(x, stats.norm.cdf, + alternative=alternative, mode=mode) + res2 = stats.mstats.ks_1samp(xm, stats.norm.cdf, + alternative=alternative, mode=mode) + assert_equal(np.asarray(res1), np.asarray(res2)) + res3 = stats.ks_1samp(xm, stats.norm.cdf, + alternative=alternative, mode=mode) + assert_equal(np.asarray(res1), np.asarray(res3)) + + def test_kstest_1samp(self): + """ + Checks that 1-sample mstats.kstest and stats.kstest agree on masked arrays. + """ + for mode in ['auto', 'exact', 'asymp']: + with warnings.catch_warnings(): + for alternative in ['less', 'greater', 'two-sided']: + for n in self.get_n(): + x, y, xm, ym = self.generate_xy_sample(n) + res1 = stats.kstest(x, 'norm', + alternative=alternative, mode=mode) + res2 = stats.mstats.kstest(xm, 'norm', + alternative=alternative, mode=mode) + assert_equal(np.asarray(res1), np.asarray(res2)) + res3 = stats.kstest(xm, 'norm', + alternative=alternative, mode=mode) + assert_equal(np.asarray(res1), np.asarray(res3)) + + def test_ks_2samp(self): + """Checks that mstats.ks_2samp and stats.ks_2samp agree on masked arrays. + gh-8431""" + for mode in ['auto', 'exact', 'asymp']: + with warnings.catch_warnings(): + if mode in ['auto', 'exact']: + message = "ks_2samp: Exact calculation unsuccessful." + warnings.filterwarnings("ignore", message, RuntimeWarning) + for alternative in ['less', 'greater', 'two-sided']: + for n in self.get_n(): + x, y, xm, ym = self.generate_xy_sample(n) + res1 = stats.ks_2samp(x, y, + alternative=alternative, mode=mode) + res2 = stats.mstats.ks_2samp(xm, ym, + alternative=alternative, mode=mode) + assert_equal(np.asarray(res1), np.asarray(res2)) + res3 = stats.ks_2samp(xm, y, + alternative=alternative, mode=mode) + assert_equal(np.asarray(res1), np.asarray(res3)) + + def test_kstest_2samp(self): + """ + Checks that 2-sample mstats.kstest and stats.kstest agree on masked arrays. + """ + for mode in ['auto', 'exact', 'asymp']: + with warnings.catch_warnings(): + if mode in ['auto', 'exact']: + message = "ks_2samp: Exact calculation unsuccessful." + warnings.filterwarnings("ignore", message, RuntimeWarning) + for alternative in ['less', 'greater', 'two-sided']: + for n in self.get_n(): + x, y, xm, ym = self.generate_xy_sample(n) + res1 = stats.kstest(x, y, + alternative=alternative, mode=mode) + res2 = stats.mstats.kstest(xm, ym, + alternative=alternative, mode=mode) + assert_equal(np.asarray(res1), np.asarray(res2)) + res3 = stats.kstest(xm, y, + alternative=alternative, mode=mode) + assert_equal(np.asarray(res1), np.asarray(res3)) + + +class TestBrunnerMunzel: + # Data from (Lumley, 1996) + X = np.ma.masked_invalid([1, 2, 1, 1, 1, np.nan, 1, 1, + 1, 1, 1, 2, 4, 1, 1, np.nan]) + Y = np.ma.masked_invalid([3, 3, 4, 3, np.nan, 1, 2, 3, 1, 1, 5, 4]) + significant = 14 + + def test_brunnermunzel_one_sided(self): + # Results are compared with R's lawstat package. + u1, p1 = mstats.brunnermunzel(self.X, self.Y, alternative='less') + u2, p2 = mstats.brunnermunzel(self.Y, self.X, alternative='greater') + u3, p3 = mstats.brunnermunzel(self.X, self.Y, alternative='greater') + u4, p4 = mstats.brunnermunzel(self.Y, self.X, alternative='less') + + assert_almost_equal(p1, p2, decimal=self.significant) + assert_almost_equal(p3, p4, decimal=self.significant) + assert_(p1 != p3) + assert_almost_equal(u1, 3.1374674823029505, + decimal=self.significant) + assert_almost_equal(u2, -3.1374674823029505, + decimal=self.significant) + assert_almost_equal(u3, 3.1374674823029505, + decimal=self.significant) + assert_almost_equal(u4, -3.1374674823029505, + decimal=self.significant) + assert_almost_equal(p1, 0.0028931043330757342, + decimal=self.significant) + assert_almost_equal(p3, 0.99710689566692423, + decimal=self.significant) + + def test_brunnermunzel_two_sided(self): + # Results are compared with R's lawstat package. + u1, p1 = mstats.brunnermunzel(self.X, self.Y, alternative='two-sided') + u2, p2 = mstats.brunnermunzel(self.Y, self.X, alternative='two-sided') + + assert_almost_equal(p1, p2, decimal=self.significant) + assert_almost_equal(u1, 3.1374674823029505, + decimal=self.significant) + assert_almost_equal(u2, -3.1374674823029505, + decimal=self.significant) + assert_almost_equal(p1, 0.0057862086661515377, + decimal=self.significant) + + def test_brunnermunzel_default(self): + # The default value for alternative is two-sided + u1, p1 = mstats.brunnermunzel(self.X, self.Y) + u2, p2 = mstats.brunnermunzel(self.Y, self.X) + + assert_almost_equal(p1, p2, decimal=self.significant) + assert_almost_equal(u1, 3.1374674823029505, + decimal=self.significant) + assert_almost_equal(u2, -3.1374674823029505, + decimal=self.significant) + assert_almost_equal(p1, 0.0057862086661515377, + decimal=self.significant) + + def test_brunnermunzel_alternative_error(self): + alternative = "error" + distribution = "t" + assert_(alternative not in ["two-sided", "greater", "less"]) + assert_raises(ValueError, + mstats.brunnermunzel, + self.X, + self.Y, + alternative, + distribution) + + def test_brunnermunzel_distribution_norm(self): + u1, p1 = mstats.brunnermunzel(self.X, self.Y, distribution="normal") + u2, p2 = mstats.brunnermunzel(self.Y, self.X, distribution="normal") + assert_almost_equal(p1, p2, decimal=self.significant) + assert_almost_equal(u1, 3.1374674823029505, + decimal=self.significant) + assert_almost_equal(u2, -3.1374674823029505, + decimal=self.significant) + assert_almost_equal(p1, 0.0017041417600383024, + decimal=self.significant) + + def test_brunnermunzel_distribution_error(self): + alternative = "two-sided" + distribution = "error" + assert_(alternative not in ["t", "normal"]) + assert_raises(ValueError, + mstats.brunnermunzel, + self.X, + self.Y, + alternative, + distribution) + + def test_brunnermunzel_empty_imput(self): + u1, p1 = mstats.brunnermunzel(self.X, []) + u2, p2 = mstats.brunnermunzel([], self.Y) + u3, p3 = mstats.brunnermunzel([], []) + + assert_(np.isnan(u1)) + assert_(np.isnan(p1)) + assert_(np.isnan(u2)) + assert_(np.isnan(p2)) + assert_(np.isnan(u3)) + assert_(np.isnan(p3)) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/test_mstats_extras.py b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_mstats_extras.py new file mode 100644 index 0000000000000000000000000000000000000000..8b14455b417317a23abda8efe5297e8f4839ffd2 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_mstats_extras.py @@ -0,0 +1,172 @@ +import numpy as np +import numpy.ma as ma +import scipy.stats.mstats as ms + +from numpy.testing import (assert_equal, assert_almost_equal, assert_, + assert_allclose) + + +def test_compare_medians_ms(): + x = np.arange(7) + y = x + 10 + assert_almost_equal(ms.compare_medians_ms(x, y), 0) + + y2 = np.linspace(0, 1, num=10) + assert_almost_equal(ms.compare_medians_ms(x, y2), 0.017116406778) + + +def test_hdmedian(): + # 1-D array + x = ma.arange(11) + assert_allclose(ms.hdmedian(x), 5, rtol=1e-14) + x.mask = ma.make_mask(x) + x.mask[:7] = False + assert_allclose(ms.hdmedian(x), 3, rtol=1e-14) + + # Check that `var` keyword returns a value. TODO: check whether returned + # value is actually correct. + assert_(ms.hdmedian(x, var=True).size == 2) + + # 2-D array + x2 = ma.arange(22).reshape((11, 2)) + assert_allclose(ms.hdmedian(x2, axis=0), [10, 11]) + x2.mask = ma.make_mask(x2) + x2.mask[:7, :] = False + assert_allclose(ms.hdmedian(x2, axis=0), [6, 7]) + + +def test_rsh(): + rng = np.random.default_rng(806795795) + x = rng.standard_normal(100) + res = ms.rsh(x) + # Just a sanity check that the code runs and output shape is correct. + # TODO: check that implementation is correct. + assert_(res.shape == x.shape) + + # Check points keyword + res = ms.rsh(x, points=[0, 1.]) + assert_(res.size == 2) + + +def test_mjci(): + # Tests the Marits-Jarrett estimator + data = ma.array([77, 87, 88,114,151,210,219,246,253,262, + 296,299,306,376,428,515,666,1310,2611]) + assert_almost_equal(ms.mjci(data),[55.76819,45.84028,198.87875],5) + + +def test_trimmed_mean_ci(): + # Tests the confidence intervals of the trimmed mean. + data = ma.array([545,555,558,572,575,576,578,580, + 594,605,635,651,653,661,666]) + assert_almost_equal(ms.trimmed_mean(data,0.2), 596.2, 1) + assert_equal(np.round(ms.trimmed_mean_ci(data,(0.2,0.2)),1), + [561.8, 630.6]) + + +def test_idealfourths(): + # Tests ideal-fourths + test = np.arange(100) + assert_almost_equal(np.asarray(ms.idealfourths(test)), + [24.416667,74.583333],6) + test_2D = test.repeat(3).reshape(-1,3) + assert_almost_equal(ms.idealfourths(test_2D, axis=0), + [[24.416667,24.416667,24.416667], + [74.583333,74.583333,74.583333]],6) + assert_almost_equal(ms.idealfourths(test_2D, axis=1), + test.repeat(2).reshape(-1,2)) + test = [0, 0] + _result = ms.idealfourths(test) + assert_(np.isnan(_result).all()) + + +class TestQuantiles: + data = [0.706560797,0.727229578,0.990399276,0.927065621,0.158953014, + 0.887764025,0.239407086,0.349638551,0.972791145,0.149789972, + 0.936947700,0.132359948,0.046041972,0.641675031,0.945530547, + 0.224218684,0.771450991,0.820257774,0.336458052,0.589113496, + 0.509736129,0.696838829,0.491323573,0.622767425,0.775189248, + 0.641461450,0.118455200,0.773029450,0.319280007,0.752229111, + 0.047841438,0.466295911,0.583850781,0.840581845,0.550086491, + 0.466470062,0.504765074,0.226855960,0.362641207,0.891620942, + 0.127898691,0.490094097,0.044882048,0.041441695,0.317976349, + 0.504135618,0.567353033,0.434617473,0.636243375,0.231803616, + 0.230154113,0.160011327,0.819464108,0.854706985,0.438809221, + 0.487427267,0.786907310,0.408367937,0.405534192,0.250444460, + 0.995309248,0.144389588,0.739947527,0.953543606,0.680051621, + 0.388382017,0.863530727,0.006514031,0.118007779,0.924024803, + 0.384236354,0.893687694,0.626534881,0.473051932,0.750134705, + 0.241843555,0.432947602,0.689538104,0.136934797,0.150206859, + 0.474335206,0.907775349,0.525869295,0.189184225,0.854284286, + 0.831089744,0.251637345,0.587038213,0.254475554,0.237781276, + 0.827928620,0.480283781,0.594514455,0.213641488,0.024194386, + 0.536668589,0.699497811,0.892804071,0.093835427,0.731107772] + + def test_hdquantiles(self): + data = self.data + assert_almost_equal(ms.hdquantiles(data,[0., 1.]), + [0.006514031, 0.995309248]) + hdq = ms.hdquantiles(data,[0.25, 0.5, 0.75]) + assert_almost_equal(hdq, [0.253210762, 0.512847491, 0.762232442,]) + + data = np.array(data).reshape(10,10) + hdq = ms.hdquantiles(data,[0.25,0.5,0.75],axis=0) + assert_almost_equal(hdq[:,0], ms.hdquantiles(data[:,0],[0.25,0.5,0.75])) + assert_almost_equal(hdq[:,-1], ms.hdquantiles(data[:,-1],[0.25,0.5,0.75])) + hdq = ms.hdquantiles(data,[0.25,0.5,0.75],axis=0,var=True) + assert_almost_equal(hdq[...,0], + ms.hdquantiles(data[:,0],[0.25,0.5,0.75],var=True)) + assert_almost_equal(hdq[...,-1], + ms.hdquantiles(data[:,-1],[0.25,0.5,0.75], var=True)) + + def test_hdquantiles_sd(self): + # Standard deviation is a jackknife estimator, so we can check if + # the efficient version (hdquantiles_sd) matches a rudimentary, + # but clear version here. + + hd_std_errs = ms.hdquantiles_sd(self.data) + + # jacknnife standard error, Introduction to the Bootstrap Eq. 11.5 + n = len(self.data) + jdata = np.broadcast_to(self.data, (n, n)) + jselector = np.logical_not(np.eye(n)) # leave out one sample each row + jdata = jdata[jselector].reshape(n, n-1) + jdist = ms.hdquantiles(jdata, axis=1) + jdist_mean = np.mean(jdist, axis=0) + jstd = ((n-1)/n * np.sum((jdist - jdist_mean)**2, axis=0))**.5 + + assert_almost_equal(hd_std_errs, jstd) + # Test actual values for good measure + assert_almost_equal(hd_std_errs, [0.0379258, 0.0380656, 0.0380013]) + + two_data_points = ms.hdquantiles_sd([1, 2]) + assert_almost_equal(two_data_points, [0.5, 0.5, 0.5]) + + def test_mquantiles_cimj(self): + # Only test that code runs, implementation not checked for correctness + ci_lower, ci_upper = ms.mquantiles_cimj(self.data) + assert_(ci_lower.size == ci_upper.size == 3) + + +def test_median_cihs(): + # Basic test against R library EnvStats function `eqnpar`, e.g. + # library(EnvStats) + # options(digits=8) + # x = c(0.88612955, 0.35242375, 0.66240904, 0.94617974, 0.10929913, + # 0.76699506, 0.88550655, 0.62763754, 0.76818588, 0.68506508, + # 0.88043148, 0.03911248, 0.93805564, 0.95326961, 0.25291112, + # 0.16128487, 0.49784577, 0.24588924, 0.6597, 0.92239679) + # eqnpar(x, p=0.5, + # ci.method = "interpolate", approx.conf.level = 0.95, ci = TRUE) + rng = np.random.default_rng(8824288259505800535) + x = rng.random(size=20) + assert_allclose(ms.median_cihs(x), (0.38663198, 0.88431272)) + + # SciPy's 90% CI upper limit doesn't match that of EnvStats eqnpar. SciPy + # doesn't look wrong, and it agrees with a different reference, + # `median_confint_hs` from `hoehleatsu/quantileCI`. + # In (e.g.) Colab with R runtime: + # devtools::install_github("hoehleatsu/quantileCI") + # library(quantileCI) + # median_confint_hs(x=x, conf.level=0.90, interpolate=TRUE) + assert_allclose(ms.median_cihs(x, 0.1), (0.48319773366, 0.88094268050)) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/test_multicomp.py b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_multicomp.py new file mode 100644 index 0000000000000000000000000000000000000000..2860b142c38a339f96c50af92bd452c6de95e84d --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_multicomp.py @@ -0,0 +1,405 @@ +import copy + +import numpy as np +import pytest +from numpy.testing import assert_allclose + +from scipy import stats +from scipy.stats._multicomp import _pvalue_dunnett, DunnettResult + + +class TestDunnett: + # For the following tests, p-values were computed using Matlab, e.g. + # sample = [18. 15. 18. 16. 17. 15. 14. 14. 14. 15. 15.... + # 14. 15. 14. 22. 18. 21. 21. 10. 10. 11. 9.... + # 25. 26. 17.5 16. 15.5 14.5 22. 22. 24. 22.5 29.... + # 24.5 20. 18. 18.5 17.5 26.5 13. 16.5 13. 13. 13.... + # 28. 27. 34. 31. 29. 27. 24. 23. 38. 36. 25.... + # 38. 26. 22. 36. 27. 27. 32. 28. 31.... + # 24. 27. 33. 32. 28. 19. 37. 31. 36. 36.... + # 34. 38. 32. 38. 32.... + # 26. 24. 26. 25. 29. 29.5 16.5 36. 44.... + # 25. 27. 19.... + # 25. 20.... + # 28.]; + # j = [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ... + # 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ... + # 0 0 0 0... + # 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1... + # 2 2 2 2 2 2 2 2 2... + # 3 3 3... + # 4 4... + # 5]; + # [~, ~, stats] = anova1(sample, j, "off"); + # [results, ~, ~, gnames] = multcompare(stats, ... + # "CriticalValueType", "dunnett", ... + # "Approximate", false); + # tbl = array2table(results, "VariableNames", ... + # ["Group", "Control Group", "Lower Limit", ... + # "Difference", "Upper Limit", "P-value"]); + # tbl.("Group") = gnames(tbl.("Group")); + # tbl.("Control Group") = gnames(tbl.("Control Group")) + + # Matlab doesn't report the statistic, so the statistics were + # computed using R multcomp `glht`, e.g.: + # library(multcomp) + # options(digits=16) + # control < - c(18.0, 15.0, 18.0, 16.0, 17.0, 15.0, 14.0, 14.0, 14.0, + # 15.0, 15.0, 14.0, 15.0, 14.0, 22.0, 18.0, 21.0, 21.0, + # 10.0, 10.0, 11.0, 9.0, 25.0, 26.0, 17.5, 16.0, 15.5, + # 14.5, 22.0, 22.0, 24.0, 22.5, 29.0, 24.5, 20.0, 18.0, + # 18.5, 17.5, 26.5, 13.0, 16.5, 13.0, 13.0, 13.0, 28.0, + # 27.0, 34.0, 31.0, 29.0, 27.0, 24.0, 23.0, 38.0, 36.0, + # 25.0, 38.0, 26.0, 22.0, 36.0, 27.0, 27.0, 32.0, 28.0, + # 31.0) + # t < - c(24.0, 27.0, 33.0, 32.0, 28.0, 19.0, 37.0, 31.0, 36.0, 36.0, + # 34.0, 38.0, 32.0, 38.0, 32.0) + # w < - c(26.0, 24.0, 26.0, 25.0, 29.0, 29.5, 16.5, 36.0, 44.0) + # x < - c(25.0, 27.0, 19.0) + # y < - c(25.0, 20.0) + # z < - c(28.0) + # + # groups = factor(rep(c("control", "t", "w", "x", "y", "z"), + # times=c(length(control), length(t), length(w), + # length(x), length(y), length(z)))) + # df < - data.frame(response=c(control, t, w, x, y, z), + # group=groups) + # model < - aov(response + # ~group, data = df) + # test < - glht(model=model, + # linfct=mcp(group="Dunnett"), + # alternative="g") + # summary(test) + # confint(test) + # p-values agreed with those produced by Matlab to at least atol=1e-3 + + # From Matlab's documentation on multcompare + samples_1 = [ + [ + 24.0, 27.0, 33.0, 32.0, 28.0, 19.0, 37.0, 31.0, 36.0, 36.0, + 34.0, 38.0, 32.0, 38.0, 32.0 + ], + [26.0, 24.0, 26.0, 25.0, 29.0, 29.5, 16.5, 36.0, 44.0], + [25.0, 27.0, 19.0], + [25.0, 20.0], + [28.0] + ] + control_1 = [ + 18.0, 15.0, 18.0, 16.0, 17.0, 15.0, 14.0, 14.0, 14.0, 15.0, 15.0, + 14.0, 15.0, 14.0, 22.0, 18.0, 21.0, 21.0, 10.0, 10.0, 11.0, 9.0, + 25.0, 26.0, 17.5, 16.0, 15.5, 14.5, 22.0, 22.0, 24.0, 22.5, 29.0, + 24.5, 20.0, 18.0, 18.5, 17.5, 26.5, 13.0, 16.5, 13.0, 13.0, 13.0, + 28.0, 27.0, 34.0, 31.0, 29.0, 27.0, 24.0, 23.0, 38.0, 36.0, 25.0, + 38.0, 26.0, 22.0, 36.0, 27.0, 27.0, 32.0, 28.0, 31.0 + ] + pvalue_1 = [4.727e-06, 0.022346, 0.97912, 0.99953, 0.86579] # Matlab + # Statistic, alternative p-values, and CIs computed with R multcomp `glht` + p_1_twosided = [1e-4, 0.02237, 0.97913, 0.99953, 0.86583] + p_1_greater = [1e-4, 0.011217, 0.768500, 0.896991, 0.577211] + p_1_less = [1, 1, 0.99660, 0.98398, .99953] + statistic_1 = [5.27356, 2.91270, 0.60831, 0.27002, 0.96637] + ci_1_twosided = [[5.3633917835622, 0.7296142201217, -8.3879817106607, + -11.9090753452911, -11.7655021543469], + [15.9709832164378, 13.8936496687672, 13.4556900439941, + 14.6434503452911, 25.4998771543469]] + ci_1_greater = [5.9036402398526, 1.4000632918725, -7.2754756323636, + -10.5567456382391, -9.8675629499576] + ci_1_less = [15.4306165948619, 13.2230539537359, 12.3429406339544, + 13.2908248513211, 23.6015228251660] + pvalues_1 = dict(twosided=p_1_twosided, less=p_1_less, greater=p_1_greater) + cis_1 = dict(twosided=ci_1_twosided, less=ci_1_less, greater=ci_1_greater) + case_1 = dict(samples=samples_1, control=control_1, statistic=statistic_1, + pvalues=pvalues_1, cis=cis_1) + + # From Dunnett1955 comparing with R's DescTools: DunnettTest + samples_2 = [[9.76, 8.80, 7.68, 9.36], [12.80, 9.68, 12.16, 9.20, 10.55]] + control_2 = [7.40, 8.50, 7.20, 8.24, 9.84, 8.32] + pvalue_2 = [0.6201, 0.0058] + # Statistic, alternative p-values, and CIs computed with R multcomp `glht` + p_2_twosided = [0.6201020, 0.0058254] + p_2_greater = [0.3249776, 0.0029139] + p_2_less = [0.91676, 0.99984] + statistic_2 = [0.85703, 3.69375] + ci_2_twosided = [[-1.2564116462124, 0.8396273539789], + [2.5564116462124, 4.4163726460211]] + ci_2_greater = [-0.9588591188156, 1.1187563667543] + ci_2_less = [2.2588591188156, 4.1372436332457] + pvalues_2 = dict(twosided=p_2_twosided, less=p_2_less, greater=p_2_greater) + cis_2 = dict(twosided=ci_2_twosided, less=ci_2_less, greater=ci_2_greater) + case_2 = dict(samples=samples_2, control=control_2, statistic=statistic_2, + pvalues=pvalues_2, cis=cis_2) + + samples_3 = [[55, 64, 64], [55, 49, 52], [50, 44, 41]] + control_3 = [55, 47, 48] + pvalue_3 = [0.0364, 0.8966, 0.4091] + # Statistic, alternative p-values, and CIs computed with R multcomp `glht` + p_3_twosided = [0.036407, 0.896539, 0.409295] + p_3_greater = [0.018277, 0.521109, 0.981892] + p_3_less = [0.99944, 0.90054, 0.20974] + statistic_3 = [3.09073, 0.56195, -1.40488] + ci_3_twosided = [[0.7529028025053, -8.2470971974947, -15.2470971974947], + [21.2470971974947, 12.2470971974947, 5.2470971974947]] + ci_3_greater = [2.4023682323149, -6.5976317676851, -13.5976317676851] + ci_3_less = [19.5984402363662, 10.5984402363662, 3.5984402363662] + pvalues_3 = dict(twosided=p_3_twosided, less=p_3_less, greater=p_3_greater) + cis_3 = dict(twosided=ci_3_twosided, less=ci_3_less, greater=ci_3_greater) + case_3 = dict(samples=samples_3, control=control_3, statistic=statistic_3, + pvalues=pvalues_3, cis=cis_3) + + # From Thomson and Short, + # Mucociliary function in health, chronic obstructive airway disease, + # and asbestosis, Journal of Applied Physiology, 1969. Table 1 + # Comparing with R's DescTools: DunnettTest + samples_4 = [[3.8, 2.7, 4.0, 2.4], [2.8, 3.4, 3.7, 2.2, 2.0]] + control_4 = [2.9, 3.0, 2.5, 2.6, 3.2] + pvalue_4 = [0.5832, 0.9982] + # Statistic, alternative p-values, and CIs computed with R multcomp `glht` + p_4_twosided = [0.58317, 0.99819] + p_4_greater = [0.30225, 0.69115] + p_4_less = [0.91929, 0.65212] + statistic_4 = [0.90875, -0.05007] + ci_4_twosided = [[-0.6898153448579, -1.0333456251632], + [1.4598153448579, 0.9933456251632]] + ci_4_greater = [-0.5186459268412, -0.8719655502147 ] + ci_4_less = [1.2886459268412, 0.8319655502147] + pvalues_4 = dict(twosided=p_4_twosided, less=p_4_less, greater=p_4_greater) + cis_4 = dict(twosided=ci_4_twosided, less=ci_4_less, greater=ci_4_greater) + case_4 = dict(samples=samples_4, control=control_4, statistic=statistic_4, + pvalues=pvalues_4, cis=cis_4) + + @pytest.mark.parametrize( + 'rho, n_groups, df, statistic, pvalue, alternative', + [ + # From Dunnett1955 + # Tables 1a and 1b pages 1117-1118 + (0.5, 1, 10, 1.81, 0.05, "greater"), # different than two-sided + (0.5, 3, 10, 2.34, 0.05, "greater"), + (0.5, 2, 30, 1.99, 0.05, "greater"), + (0.5, 5, 30, 2.33, 0.05, "greater"), + (0.5, 4, 12, 3.32, 0.01, "greater"), + (0.5, 7, 12, 3.56, 0.01, "greater"), + (0.5, 2, 60, 2.64, 0.01, "greater"), + (0.5, 4, 60, 2.87, 0.01, "greater"), + (0.5, 4, 60, [2.87, 2.21], [0.01, 0.05], "greater"), + # Tables 2a and 2b pages 1119-1120 + (0.5, 1, 10, 2.23, 0.05, "two-sided"), # two-sided + (0.5, 3, 10, 2.81, 0.05, "two-sided"), + (0.5, 2, 30, 2.32, 0.05, "two-sided"), + (0.5, 3, 20, 2.57, 0.05, "two-sided"), + (0.5, 4, 12, 3.76, 0.01, "two-sided"), + (0.5, 7, 12, 4.08, 0.01, "two-sided"), + (0.5, 2, 60, 2.90, 0.01, "two-sided"), + (0.5, 4, 60, 3.14, 0.01, "two-sided"), + (0.5, 4, 60, [3.14, 2.55], [0.01, 0.05], "two-sided"), + ], + ) + def test_critical_values( + self, rho, n_groups, df, statistic, pvalue, alternative + ): + rng = np.random.default_rng(165250594791731684851746311027739134893) + rho = np.full((n_groups, n_groups), rho) + np.fill_diagonal(rho, 1) + + statistic = np.array(statistic) + res = _pvalue_dunnett( + rho=rho, df=df, statistic=statistic, + alternative=alternative, + rng=rng + ) + assert_allclose(res, pvalue, atol=5e-3) + + @pytest.mark.parametrize( + 'samples, control, pvalue, statistic', + [ + (samples_1, control_1, pvalue_1, statistic_1), + (samples_2, control_2, pvalue_2, statistic_2), + (samples_3, control_3, pvalue_3, statistic_3), + (samples_4, control_4, pvalue_4, statistic_4), + ] + ) + def test_basic(self, samples, control, pvalue, statistic): + rng = np.random.default_rng(11681140010308601919115036826969764808) + + res = stats.dunnett(*samples, control=control, rng=rng) + + assert isinstance(res, DunnettResult) + assert_allclose(res.statistic, statistic, rtol=5e-5) + assert_allclose(res.pvalue, pvalue, rtol=1e-2, atol=1e-4) + + @pytest.mark.parametrize( + 'alternative', + ['two-sided', 'less', 'greater'] + ) + def test_ttest_ind(self, alternative): + # check that `dunnett` agrees with `ttest_ind` + # when there are only two groups + rng = np.random.default_rng(114184017807316971636137493526995620351) + + for _ in range(10): + sample = rng.integers(-100, 100, size=(10,)) + control = rng.integers(-100, 100, size=(10,)) + + # preserve use of old random_state during SPEC 7 transition + res = stats.dunnett( + sample, control=control, + alternative=alternative, random_state=rng + ) + ref = stats.ttest_ind( + sample, control, + alternative=alternative + ) + + assert_allclose(res.statistic, ref.statistic, rtol=1e-3, atol=1e-5) + assert_allclose(res.pvalue, ref.pvalue, rtol=1e-3, atol=1e-5) + + @pytest.mark.parametrize( + 'alternative, pvalue', + [ + ('less', [0, 1]), + ('greater', [1, 0]), + ('two-sided', [0, 0]), + ] + ) + def test_alternatives(self, alternative, pvalue): + rng = np.random.default_rng(114184017807316971636137493526995620351) + + # width of 20 and min diff between samples/control is 60 + # and maximal diff would be 100 + sample_less = rng.integers(0, 20, size=(10,)) + control = rng.integers(80, 100, size=(10,)) + sample_greater = rng.integers(160, 180, size=(10,)) + + res = stats.dunnett( + sample_less, sample_greater, control=control, + alternative=alternative, rng=rng + ) + assert_allclose(res.pvalue, pvalue, atol=1e-7) + + ci = res.confidence_interval() + # two-sided is comparable for high/low + if alternative == 'less': + assert np.isneginf(ci.low).all() + assert -100 < ci.high[0] < -60 + assert 60 < ci.high[1] < 100 + elif alternative == 'greater': + assert -100 < ci.low[0] < -60 + assert 60 < ci.low[1] < 100 + assert np.isposinf(ci.high).all() + elif alternative == 'two-sided': + assert -100 < ci.low[0] < -60 + assert 60 < ci.low[1] < 100 + assert -100 < ci.high[0] < -60 + assert 60 < ci.high[1] < 100 + + @pytest.mark.parametrize("case", [case_1, case_2, case_3, case_4]) + @pytest.mark.parametrize("alternative", ['less', 'greater', 'two-sided']) + def test_against_R_multicomp_glht(self, case, alternative): + rng = np.random.default_rng(189117774084579816190295271136455278291) + samples = case['samples'] + control = case['control'] + alternatives = {'less': 'less', 'greater': 'greater', + 'two-sided': 'twosided'} + p_ref = case['pvalues'][alternative.replace('-', '')] + + res = stats.dunnett(*samples, control=control, alternative=alternative, + rng=rng) + # atol can't be tighter because R reports some pvalues as "< 1e-4" + assert_allclose(res.pvalue, p_ref, rtol=5e-3, atol=1e-4) + + ci_ref = case['cis'][alternatives[alternative]] + if alternative == "greater": + ci_ref = [ci_ref, np.inf] + elif alternative == "less": + ci_ref = [-np.inf, ci_ref] + assert res._ci is None + assert res._ci_cl is None + ci = res.confidence_interval(confidence_level=0.95) + assert_allclose(ci.low, ci_ref[0], rtol=5e-3, atol=1e-5) + assert_allclose(ci.high, ci_ref[1], rtol=5e-3, atol=1e-5) + + # re-run to use the cached value "is" to check id as same object + assert res._ci is ci + assert res._ci_cl == 0.95 + ci_ = res.confidence_interval(confidence_level=0.95) + assert ci_ is ci + + @pytest.mark.parametrize('alternative', ["two-sided", "less", "greater"]) + def test_str(self, alternative): + rng = np.random.default_rng(189117774084579816190295271136455278291) + + res = stats.dunnett( + *self.samples_3, control=self.control_3, alternative=alternative, + rng=rng + ) + + # check some str output + res_str = str(res) + assert '(Sample 2 - Control)' in res_str + assert '95.0%' in res_str + + if alternative == 'less': + assert '-inf' in res_str + assert '19.' in res_str + elif alternative == 'greater': + assert 'inf' in res_str + assert '-13.' in res_str + else: + assert 'inf' not in res_str + assert '21.' in res_str + + def test_warnings(self): + rng = np.random.default_rng(189117774084579816190295271136455278291) + + res = stats.dunnett( + *self.samples_3, control=self.control_3, rng=rng + ) + msg = r"Computation of the confidence interval did not converge" + with pytest.warns(UserWarning, match=msg): + res._allowance(tol=1e-5) + + def test_raises(self): + samples, control = self.samples_3, self.control_3 + + # alternative + with pytest.raises(ValueError, match="alternative must be"): + stats.dunnett(*samples, control=control, alternative='bob') + + # 2D for a sample + samples_ = copy.deepcopy(samples) + samples_[0] = [samples_[0]] + with pytest.raises(ValueError, match="must be 1D arrays"): + stats.dunnett(*samples_, control=control) + + # 2D for control + control_ = copy.deepcopy(control) + control_ = [control_] + with pytest.raises(ValueError, match="must be 1D arrays"): + stats.dunnett(*samples, control=control_) + + # No obs in a sample + samples_ = copy.deepcopy(samples) + samples_[1] = [] + with pytest.raises(ValueError, match="at least 1 observation"): + stats.dunnett(*samples_, control=control) + + # No obs in control + control_ = [] + with pytest.raises(ValueError, match="at least 1 observation"): + stats.dunnett(*samples, control=control_) + + res = stats.dunnett(*samples, control=control) + with pytest.raises(ValueError, match="Confidence level must"): + res.confidence_interval(confidence_level=3) + + @pytest.mark.filterwarnings("ignore:Computation of the confidence") + @pytest.mark.parametrize('n_samples', [1, 2, 3]) + def test_shapes(self, n_samples): + rng = np.random.default_rng(689448934110805334) + samples = rng.normal(size=(n_samples, 10)) + control = rng.normal(size=10) + res = stats.dunnett(*samples, control=control, rng=rng) + assert res.statistic.shape == (n_samples,) + assert res.pvalue.shape == (n_samples,) + ci = res.confidence_interval() + assert ci.low.shape == (n_samples,) + assert ci.high.shape == (n_samples,) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/test_multivariate.py b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_multivariate.py new file mode 100644 index 0000000000000000000000000000000000000000..46690b575a0c56c250d624bad87272f5a1fabd2e --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_multivariate.py @@ -0,0 +1,5052 @@ +""" +Test functions for multivariate normal, t, and related distributions. + +""" +import pickle +from dataclasses import dataclass + +from numpy.testing import (assert_allclose, assert_almost_equal, + assert_array_almost_equal, assert_equal, + assert_array_less, assert_) +import pytest +from pytest import raises as assert_raises + +from .test_continuous_basic import check_distribution_rvs + +import numpy as np + +import scipy.linalg + +from scipy.stats._multivariate import (_PSD, + _lnB, + multivariate_normal_frozen) +from scipy.stats import (multivariate_normal, multivariate_hypergeom, + matrix_normal, special_ortho_group, ortho_group, + random_correlation, unitary_group, dirichlet, + beta, wishart, multinomial, invwishart, chi2, + invgamma, norm, uniform, ks_2samp, kstest, binom, + hypergeom, multivariate_t, cauchy, normaltest, + random_table, uniform_direction, vonmises_fisher, + dirichlet_multinomial, vonmises, matrix_t) + +from scipy.stats import _covariance, Covariance +from scipy.stats._continuous_distns import _norm_pdf as norm_pdf +from scipy import stats + +from scipy.integrate import tanhsinh, cubature, quad +from scipy.integrate import romb, qmc_quad, dblquad, tplquad +from scipy.special import multigammaln +import scipy.special as special + +from .common_tests import check_random_state_property +from .data._mvt import _qsimvtv + +from unittest.mock import patch + + +def assert_close(res, ref, *args, **kwargs): + res, ref = np.asarray(res), np.asarray(ref) + assert_allclose(res, ref, *args, **kwargs) + assert_equal(res.shape, ref.shape) + + +class TestCovariance: + + def test_input_validation(self): + + message = "The input `precision` must be a square, two-dimensional..." + with pytest.raises(ValueError, match=message): + _covariance.CovViaPrecision(np.ones(2)) + + message = "`precision.shape` must equal `covariance.shape`." + with pytest.raises(ValueError, match=message): + _covariance.CovViaPrecision(np.eye(3), covariance=np.eye(2)) + + message = "The input `diagonal` must be a one-dimensional array..." + with pytest.raises(ValueError, match=message): + _covariance.CovViaDiagonal("alpaca") + + message = "The input `cholesky` must be a square, two-dimensional..." + with pytest.raises(ValueError, match=message): + _covariance.CovViaCholesky(np.ones(2)) + + message = "The input `eigenvalues` must be a one-dimensional..." + with pytest.raises(ValueError, match=message): + _covariance.CovViaEigendecomposition(("alpaca", np.eye(2))) + + message = "The input `eigenvectors` must be a square..." + with pytest.raises(ValueError, match=message): + _covariance.CovViaEigendecomposition((np.ones(2), "alpaca")) + + message = "The shapes of `eigenvalues` and `eigenvectors` must be..." + with pytest.raises(ValueError, match=message): + _covariance.CovViaEigendecomposition(([1, 2, 3], np.eye(2))) + + _covariance_preprocessing = {"Diagonal": np.diag, + "Precision": np.linalg.inv, + "Cholesky": np.linalg.cholesky, + "Eigendecomposition": np.linalg.eigh, + "PSD": lambda x: + _PSD(x, allow_singular=True)} + _all_covariance_types = np.array(list(_covariance_preprocessing)) + _matrices = {"diagonal full rank": np.diag([1, 2, 3]), + "general full rank": [[5, 1, 3], [1, 6, 4], [3, 4, 7]], + "diagonal singular": np.diag([1, 0, 3]), + "general singular": [[5, -1, 0], [-1, 5, 0], [0, 0, 0]]} + _cov_types = {"diagonal full rank": _all_covariance_types, + "general full rank": _all_covariance_types[1:], + "diagonal singular": _all_covariance_types[[0, -2, -1]], + "general singular": _all_covariance_types[-2:]} + + @pytest.mark.parametrize("cov_type_name", _all_covariance_types[:-1]) + def test_factories(self, cov_type_name): + A = np.diag([1, 2, 3]) + x = [-4, 2, 5] + + cov_type = getattr(_covariance, f"CovVia{cov_type_name}") + preprocessing = self._covariance_preprocessing[cov_type_name] + factory = getattr(Covariance, f"from_{cov_type_name.lower()}") + + res = factory(preprocessing(A)) + ref = cov_type(preprocessing(A)) + assert type(res) is type(ref) + assert_allclose(res.whiten(x), ref.whiten(x)) + + @pytest.mark.parametrize("matrix_type", list(_matrices)) + @pytest.mark.parametrize("cov_type_name", _all_covariance_types) + def test_covariance(self, matrix_type, cov_type_name): + message = (f"CovVia{cov_type_name} does not support {matrix_type} " + "matrices") + if cov_type_name not in self._cov_types[matrix_type]: + pytest.skip(message) + + A = self._matrices[matrix_type] + cov_type = getattr(_covariance, f"CovVia{cov_type_name}") + preprocessing = self._covariance_preprocessing[cov_type_name] + + psd = _PSD(A, allow_singular=True) + + # test properties + cov_object = cov_type(preprocessing(A)) + assert_close(cov_object.log_pdet, psd.log_pdet) + assert_equal(cov_object.rank, psd.rank) + assert_equal(cov_object.shape, np.asarray(A).shape) + assert_close(cov_object.covariance, np.asarray(A)) + + # test whitening/coloring 1D x + rng = np.random.default_rng(5292808890472453840) + x = rng.random(size=3) + res = cov_object.whiten(x) + ref = x @ psd.U + # res != ref in general; but res @ res == ref @ ref + assert_close(res @ res, ref @ ref) + if hasattr(cov_object, "_colorize") and "singular" not in matrix_type: + # CovViaPSD does not have _colorize + assert_close(cov_object.colorize(res), x) + + # test whitening/coloring 3D x + x = rng.random(size=(2, 4, 3)) + res = cov_object.whiten(x) + ref = x @ psd.U + assert_close((res**2).sum(axis=-1), (ref**2).sum(axis=-1)) + if hasattr(cov_object, "_colorize") and "singular" not in matrix_type: + assert_close(cov_object.colorize(res), x) + + # gh-19197 reported that multivariate normal `rvs` produced incorrect + # results when a singular Covariance object was produce using + # `from_eigenvalues`. This was due to an issue in `colorize` with + # singular covariance matrices. Check this edge case, which is skipped + # in the previous tests. + if hasattr(cov_object, "_colorize"): + res = cov_object.colorize(np.eye(len(A))) + assert_close(res.T @ res, A) + + @pytest.mark.parametrize("size", [None, tuple(), 1, (2, 4, 3)]) + @pytest.mark.parametrize("matrix_type", list(_matrices)) + @pytest.mark.parametrize("cov_type_name", _all_covariance_types) + def test_mvn_with_covariance(self, size, matrix_type, cov_type_name): + message = (f"CovVia{cov_type_name} does not support {matrix_type} " + "matrices") + if cov_type_name not in self._cov_types[matrix_type]: + pytest.skip(message) + + A = self._matrices[matrix_type] + cov_type = getattr(_covariance, f"CovVia{cov_type_name}") + preprocessing = self._covariance_preprocessing[cov_type_name] + + mean = [0.1, 0.2, 0.3] + cov_object = cov_type(preprocessing(A)) + mvn = multivariate_normal + dist0 = multivariate_normal(mean, A, allow_singular=True) + dist1 = multivariate_normal(mean, cov_object, allow_singular=True) + + rng = np.random.default_rng(5292808890472453840) + x = rng.multivariate_normal(mean, A, size=size) + rng = np.random.default_rng(5292808890472453840) + x1 = mvn.rvs(mean, cov_object, size=size, random_state=rng) + rng = np.random.default_rng(5292808890472453840) + x2 = mvn(mean, cov_object, seed=rng).rvs(size=size) + if isinstance(cov_object, _covariance.CovViaPSD): + assert_close(x1, np.squeeze(x)) # for backward compatibility + assert_close(x2, np.squeeze(x)) + else: + assert_equal(x1.shape, x.shape) + assert_equal(x2.shape, x.shape) + assert_close(x2, x1) + + assert_close(mvn.pdf(x, mean, cov_object), dist0.pdf(x)) + assert_close(dist1.pdf(x), dist0.pdf(x)) + assert_close(mvn.logpdf(x, mean, cov_object), dist0.logpdf(x)) + assert_close(dist1.logpdf(x), dist0.logpdf(x)) + assert_close(mvn.entropy(mean, cov_object), dist0.entropy()) + assert_close(dist1.entropy(), dist0.entropy()) + + @pytest.mark.parametrize("size", [tuple(), (2, 4, 3)]) + @pytest.mark.parametrize("cov_type_name", _all_covariance_types) + def test_mvn_with_covariance_cdf(self, size, cov_type_name): + # This is split from the test above because it's slow to be running + # with all matrix types, and there's no need because _mvn.mvnun + # does the calculation. All Covariance needs to do is pass is + # provide the `covariance` attribute. + matrix_type = "diagonal full rank" + A = self._matrices[matrix_type] + cov_type = getattr(_covariance, f"CovVia{cov_type_name}") + preprocessing = self._covariance_preprocessing[cov_type_name] + + mean = [0.1, 0.2, 0.3] + cov_object = cov_type(preprocessing(A)) + mvn = multivariate_normal + dist0 = multivariate_normal(mean, A, allow_singular=True) + dist1 = multivariate_normal(mean, cov_object, allow_singular=True) + + rng = np.random.default_rng(5292808890472453840) + x = rng.multivariate_normal(mean, A, size=size) + + assert_close(mvn.cdf(x, mean, cov_object), dist0.cdf(x)) + assert_close(dist1.cdf(x), dist0.cdf(x)) + assert_close(mvn.logcdf(x, mean, cov_object), dist0.logcdf(x)) + assert_close(dist1.logcdf(x), dist0.logcdf(x)) + + def test_covariance_instantiation(self): + message = "The `Covariance` class cannot be instantiated directly." + with pytest.raises(NotImplementedError, match=message): + Covariance() + + @pytest.mark.filterwarnings("ignore::RuntimeWarning") # matrix not PSD + def test_gh9942(self): + # Originally there was a mistake in the `multivariate_normal_frozen` + # `rvs` method that caused all covariance objects to be processed as + # a `_CovViaPSD`. Ensure that this is resolved. + A = np.diag([1, 2, -1e-8]) + n = A.shape[0] + mean = np.zeros(n) + + # Error if the matrix is processed as a `_CovViaPSD` + with pytest.raises(ValueError, match="The input matrix must be..."): + multivariate_normal(mean, A).rvs() + + # No error if it is provided as a `CovViaEigendecomposition` + seed = 3562050283508273023 + rng1 = np.random.default_rng(seed) + rng2 = np.random.default_rng(seed) + cov = Covariance.from_eigendecomposition(np.linalg.eigh(A)) + rv = multivariate_normal(mean, cov) + res = rv.rvs(random_state=rng1) + ref = multivariate_normal.rvs(mean, cov, random_state=rng2) + assert_equal(res, ref) + + def test_gh19197(self): + # gh-19197 reported that multivariate normal `rvs` produced incorrect + # results when a singular Covariance object was produce using + # `from_eigenvalues`. Check that this specific issue is resolved; + # a more general test is included in `test_covariance`. + mean = np.ones(2) + cov = Covariance.from_eigendecomposition((np.zeros(2), np.eye(2))) + dist = scipy.stats.multivariate_normal(mean=mean, cov=cov) + rvs = dist.rvs(size=None) + assert_equal(rvs, mean) + + cov = scipy.stats.Covariance.from_eigendecomposition( + (np.array([1., 0.]), np.array([[1., 0.], [0., 400.]]))) + dist = scipy.stats.multivariate_normal(mean=mean, cov=cov) + rvs = dist.rvs(size=None) + assert rvs[0] != mean[0] + assert rvs[1] == mean[1] + + +def _random_covariance(dim, evals, rng, singular=False): + # Generates random covariance matrix with dimensionality `dim` and + # eigenvalues `evals` using provided Generator `rng`. Randomly sets + # some evals to zero if `singular` is True. + A = rng.random((dim, dim)) + A = A @ A.T + _, v = np.linalg.eigh(A) + if singular: + zero_eigs = rng.normal(size=dim) > 0 + evals[zero_eigs] = 0 + cov = v @ np.diag(evals) @ v.T + return cov + + +def _sample_orthonormal_matrix(n): + rng = np.random.default_rng(9086764251) + M = rng.standard_normal((n, n)) + u, s, v = scipy.linalg.svd(M) + return u + +def marginal_pdf(X, X_ndim, dimensions, x): + """Integrate marginalized dimensions of multivariate + probability distribution to calculate the marginalized + distribution. + """ + # Sort input data based on order of dimensions + dimensions = np.asarray(dimensions) + dimensions[dimensions < 0] += X_ndim + dim_sort_idx = dimensions.argsort() + x = x[:, dim_sort_idx] + + i_marginalize = np.ones(X_ndim, dtype=bool) + i_marginalize[dimensions] = False + + def g(z): + y = np.empty((z.shape[0], x.shape[0], X_ndim)) + y[..., i_marginalize] = z[:, np.newaxis, :] + y[..., ~i_marginalize] = x + return X.pdf(y) + + inf = np.full(X_ndim - len(dimensions), np.inf) + return cubature(g, -inf, inf).estimate + +@dataclass +class MVNProblem: + """Instantiate a multivariate normal integration problem with special structure. + + When covariance matrix is a correlation matrix where the off-diagonal entries + ``covar[i, j] == lambdas[i]*lambdas[j]`` for ``i != j``, then the multidimensional + integral reduces to a simpler univariate integral that can be numerically integrated + easily. + + The ``generate_*()`` classmethods provide a few options for creating variations + of this problem. + + References + ---------- + .. [1] Tong, Y.L. "The Multivariate Normal Distribution". + Springer-Verlag. p192. 1990. + """ + ndim : int + low : np.ndarray + high : np.ndarray + lambdas : np.ndarray + covar : np.ndarray + target_val : float + target_err : float + + #: The `generator_halves()` case has an analytically-known true value that we'll + #: record here. It remain None for most cases, though. + true_val : float | None = None + + def __init__(self, ndim, low, high, lambdas): + super().__init__() + self.ndim = ndim + self.low = low + self.high = high + self.lambdas = lambdas + + self.covar = np.outer(self.lambdas, self.lambdas) + np.fill_diagonal(self.covar, 1.0) + self.find_target() + + @classmethod + def generate_semigeneral(cls, ndim, rng=None): + """Random lambdas, random upper bounds, infinite lower bounds. + """ + rng = np.random.default_rng(rng) + low = np.full(ndim, -np.inf) + high = rng.uniform(0.0, np.sqrt(ndim), size=ndim) + lambdas = rng.uniform(-1.0, 1.0, size=ndim) + + self = cls( + ndim=ndim, + low=low, + high=high, + lambdas=lambdas, + ) + return self + + @classmethod + def generate_constant(cls, ndim, rng=None): + """Constant off-diagonal covariance, random upper bounds, infinite lower bounds. + """ + rng = np.random.default_rng(rng) + low = np.full(ndim, -np.inf) + high = rng.uniform(0.0, np.sqrt(ndim), size=ndim) + sigma = np.sqrt(rng.uniform(0.0, 1.0)) + lambdas = np.full(ndim, sigma) + + self = cls( + ndim=ndim, + low=low, + high=high, + lambdas=lambdas, + ) + return self + + @classmethod + def generate_halves(cls, ndim, rng=None): + """Off-diagonal covariance of 0.5, negative orthant bounds. + + True analytically-derived answer is 1/(ndim+1). + """ + low = np.full(ndim, -np.inf) + high = np.zeros(ndim) + lambdas = np.sqrt(0.5) + + self = cls( + ndim=ndim, + low=low, + high=high, + lambdas=lambdas, + ) + self.true_val = 1 / (ndim+1) + return self + + def find_target(self, **kwds): + """Perform the simplified integral and store the results. + """ + d = dict( + a=-9.0, + b=+9.0, + ) + d.update(kwds) + self.target_val, self.target_err = quad(self.univariate_func, **d) + + def _univariate_term(self, t): + """The parameter-specific term of the univariate integrand, + for separate plotting. + """ + denom = np.sqrt(1 - self.lambdas**2) + return np.prod( + special.ndtr((self.high + self.lambdas*t[:, np.newaxis]) / denom) - + special.ndtr((self.low + self.lambdas*t[:, np.newaxis]) / denom), + axis=1, + ) + + def univariate_func(self, t): + """Univariate integrand. + """ + t = np.atleast_1d(t) + return np.squeeze(norm_pdf(t) * self._univariate_term(t)) + + def plot_integrand(self): + """Plot the univariate integrand and its component terms for understanding. + """ + from matplotlib import pyplot as plt + + t = np.linspace(-9.0, 9.0, 1001) + plt.plot(t, norm_pdf(t), label=r'$\phi(t)$') + plt.plot(t, self._univariate_term(t), label=r'$f(t)$') + plt.plot(t, self.univariate_func(t), label=r'$f(t)*phi(t)$') + plt.legend() + + +@dataclass +class SingularMVNProblem: + """Instantiate a multivariate normal integration problem with a special singular + covariance structure. + + When covariance matrix is a correlation matrix where the off-diagonal entries + ``covar[i, j] == -lambdas[i]*lambdas[j]`` for ``i != j``, and + ``sum(lambdas**2 / (1+lambdas**2)) == 1``, then the matrix is singular, and + the multidimensional integral reduces to a simpler univariate integral that + can be numerically integrated fairly easily. + + The lower bound must be infinite, though the upper bounds can be general. + + References + ---------- + .. [1] Kwong, K.-S. (1995). "Evaluation of the one-sided percentage points of the + singular multivariate normal distribution." Journal of Statistical + Computation and Simulation, 51(2-4), 121-135. + :doi:`10.1080/00949659508811627`. + """ + ndim : int + low : np.ndarray + high : np.ndarray + lambdas : np.ndarray + covar : np.ndarray + target_val : float + target_err : float + + def __init__(self, ndim, high, lambdas): + self.ndim = ndim + self.high = high + self.lambdas = lambdas + + self.low = np.full(ndim, -np.inf) + self.covar = -np.outer(self.lambdas, self.lambdas) + np.fill_diagonal(self.covar, 1.0) + self.find_target() + + @classmethod + def generate_semiinfinite(cls, ndim, rng=None): + """Singular lambdas, random upper bounds. + """ + rng = np.random.default_rng(rng) + high = rng.uniform(0.0, np.sqrt(ndim), size=ndim) + p = rng.dirichlet(np.full(ndim, 1.0)) + lambdas = np.sqrt(p / (1-p)) * rng.choice([-1.0, 1.0], size=ndim) + + self = cls( + ndim=ndim, + high=high, + lambdas=lambdas, + ) + return self + + def find_target(self, **kwds): + d = dict( + a=-9.0, + b=+9.0, + ) + d.update(kwds) + self.target_val, self.target_err = quad(self.univariate_func, **d) + + def _univariate_term(self, t): + denom = np.sqrt(1 + self.lambdas**2) + i1 = np.prod( + special.ndtr((self.high - 1j*self.lambdas*t[:, np.newaxis]) / denom), + axis=1, + ) + i2 = np.prod( + special.ndtr((-self.high + 1j*self.lambdas*t[:, np.newaxis]) / denom), + axis=1, + ) + # The imaginary part is an odd function, so it can be ignored; it will integrate + # out to 0. + return (i1 - (-1)**self.ndim * i2).real + + def univariate_func(self, t): + t = np.atleast_1d(t) + return (norm_pdf(t) * self._univariate_term(t)).squeeze() + + def plot_integrand(self): + """Plot the univariate integrand and its component terms for understanding. + """ + from matplotlib import pyplot as plt + + t = np.linspace(-9.0, 9.0, 1001) + plt.plot(t, norm_pdf(t), label=r'$\phi(t)$') + plt.plot(t, self._univariate_term(t), label=r'$f(t)$') + plt.plot(t, self.univariate_func(t), label=r'$f(t)*phi(t)$') + plt.ylim(-0.1, 1.1) + plt.legend() + + +class TestMultivariateNormal: + def test_input_shape(self): + mu = np.arange(3) + cov = np.identity(2) + assert_raises(ValueError, multivariate_normal.pdf, (0, 1), mu, cov) + assert_raises(ValueError, multivariate_normal.pdf, (0, 1, 2), mu, cov) + assert_raises(ValueError, multivariate_normal.cdf, (0, 1), mu, cov) + assert_raises(ValueError, multivariate_normal.cdf, (0, 1, 2), mu, cov) + + def test_scalar_values(self): + rng = np.random.default_rng(1234) + + # When evaluated on scalar data, the pdf should return a scalar + x, mean, cov = 1.5, 1.7, 2.5 + pdf = multivariate_normal.pdf(x, mean, cov) + assert_equal(pdf.ndim, 0) + + # When evaluated on a single vector, the pdf should return a scalar + x = rng.standard_normal(5) + mean = rng.standard_normal(5) + cov = np.abs(rng.standard_normal(5)) # Diagonal values for cov. matrix + pdf = multivariate_normal.pdf(x, mean, cov) + assert_equal(pdf.ndim, 0) + + # When evaluated on scalar data, the cdf should return a scalar + x, mean, cov = 1.5, 1.7, 2.5 + cdf = multivariate_normal.cdf(x, mean, cov) + assert_equal(cdf.ndim, 0) + + # When evaluated on a single vector, the cdf should return a scalar + x = rng.standard_normal(5) + mean = rng.standard_normal(5) + cov = np.abs(rng.standard_normal(5)) # Diagonal values for cov. matrix + cdf = multivariate_normal.cdf(x, mean, cov) + assert_equal(cdf.ndim, 0) + + def test_logpdf(self): + # Check that the log of the pdf is in fact the logpdf + rng = np.random.default_rng(1234) + x = rng.standard_normal(5) + mean = rng.standard_normal(5) + cov = np.abs(rng.standard_normal(5)) + d1 = multivariate_normal.logpdf(x, mean, cov) + d2 = multivariate_normal.pdf(x, mean, cov) + assert_allclose(d1, np.log(d2)) + + def test_logpdf_default_values(self): + # Check that the log of the pdf is in fact the logpdf + # with default parameters Mean=None and cov = 1 + rng = np.random.default_rng(1234) + x = rng.standard_normal(5) + d1 = multivariate_normal.logpdf(x) + d2 = multivariate_normal.pdf(x) + # check whether default values are being used + d3 = multivariate_normal.logpdf(x, None, 1) + d4 = multivariate_normal.pdf(x, None, 1) + assert_allclose(d1, np.log(d2)) + assert_allclose(d3, np.log(d4)) + + def test_logcdf(self): + # Check that the log of the cdf is in fact the logcdf + rng = np.random.default_rng(1234) + x = rng.standard_normal(5) + mean = rng.standard_normal(5) + cov = np.abs(rng.standard_normal(5)) + d1 = multivariate_normal.logcdf(x, mean, cov) + d2 = multivariate_normal.cdf(x, mean, cov) + assert_allclose(d1, np.log(d2)) + + def test_logcdf_default_values(self): + # Check that the log of the cdf is in fact the logcdf + # with default parameters Mean=None and cov = 1 + rng = np.random.default_rng(1234) + x = rng.standard_normal(5) + d1 = multivariate_normal.logcdf(x) + d2 = multivariate_normal.cdf(x) + # check whether default values are being used + d3 = multivariate_normal.logcdf(x, None, 1) + d4 = multivariate_normal.cdf(x, None, 1) + assert_allclose(d1, np.log(d2)) + assert_allclose(d3, np.log(d4)) + + def test_rank(self): + # Check that the rank is detected correctly. + rng = np.random.default_rng(1234) + n = 4 + mean = rng.standard_normal(n) + for expected_rank in range(1, n + 1): + s = rng.standard_normal((n, expected_rank)) + cov = np.dot(s, s.T) + distn = multivariate_normal(mean, cov, allow_singular=True) + assert_equal(distn.cov_object.rank, expected_rank) + + def test_degenerate_distributions(self): + rng = np.random.default_rng(1234) + for n in range(1, 5): + z = rng.standard_normal(n) + for k in range(1, n): + # Sample a small covariance matrix. + s = rng.standard_normal((k, k)) + cov_kk = np.dot(s, s.T) + + # Embed the small covariance matrix into a larger singular one. + cov_nn = np.zeros((n, n)) + cov_nn[:k, :k] = cov_kk + + # Embed part of the vector in the same way + x = np.zeros(n) + x[:k] = z[:k] + + # Define a rotation of the larger low rank matrix. + u = _sample_orthonormal_matrix(n) + cov_rr = np.dot(u, np.dot(cov_nn, u.T)) + y = np.dot(u, x) + + # Check some identities. + distn_kk = multivariate_normal(np.zeros(k), cov_kk, + allow_singular=True) + distn_nn = multivariate_normal(np.zeros(n), cov_nn, + allow_singular=True) + distn_rr = multivariate_normal(np.zeros(n), cov_rr, + allow_singular=True) + assert_equal(distn_kk.cov_object.rank, k) + assert_equal(distn_nn.cov_object.rank, k) + assert_equal(distn_rr.cov_object.rank, k) + pdf_kk = distn_kk.pdf(x[:k]) + pdf_nn = distn_nn.pdf(x) + pdf_rr = distn_rr.pdf(y) + assert_allclose(pdf_kk, pdf_nn) + assert_allclose(pdf_kk, pdf_rr) + logpdf_kk = distn_kk.logpdf(x[:k]) + logpdf_nn = distn_nn.logpdf(x) + logpdf_rr = distn_rr.logpdf(y) + assert_allclose(logpdf_kk, logpdf_nn) + assert_allclose(logpdf_kk, logpdf_rr) + + # Add an orthogonal component and find the density + y_orth = y + u[:, -1] + pdf_rr_orth = distn_rr.pdf(y_orth) + logpdf_rr_orth = distn_rr.logpdf(y_orth) + + # Ensure that this has zero probability + assert_equal(pdf_rr_orth, 0.0) + assert_equal(logpdf_rr_orth, -np.inf) + + def test_degenerate_array(self): + # Test that we can generate arrays of random variate from a degenerate + # multivariate normal, and that the pdf for these samples is non-zero + # (i.e. samples from the distribution lie on the subspace) + k = 10 + for n in range(2, 6): + for r in range(1, n): + mn = np.zeros(n) + u = _sample_orthonormal_matrix(n)[:, :r] + vr = np.dot(u, u.T) + X = multivariate_normal.rvs(mean=mn, cov=vr, size=k) + + pdf = multivariate_normal.pdf(X, mean=mn, cov=vr, + allow_singular=True) + assert_equal(pdf.size, k) + assert np.all(pdf > 0.0) + + logpdf = multivariate_normal.logpdf(X, mean=mn, cov=vr, + allow_singular=True) + assert_equal(logpdf.size, k) + assert np.all(logpdf > -np.inf) + + def test_large_pseudo_determinant(self): + # Check that large pseudo-determinants are handled appropriately. + + # Construct a singular diagonal covariance matrix + # whose pseudo determinant overflows double precision. + large_total_log = 1000.0 + npos = 100 + nzero = 2 + large_entry = np.exp(large_total_log / npos) + n = npos + nzero + cov = np.zeros((n, n), dtype=float) + np.fill_diagonal(cov, large_entry) + cov[-nzero:, -nzero:] = 0 + + # Check some determinants. + assert_equal(scipy.linalg.det(cov), 0) + assert_equal(scipy.linalg.det(cov[:npos, :npos]), np.inf) + assert_allclose(np.linalg.slogdet(cov[:npos, :npos]), + (1, large_total_log)) + + # Check the pseudo-determinant. + psd = _PSD(cov) + assert_allclose(psd.log_pdet, large_total_log) + + def test_broadcasting(self): + rng = np.random.RandomState(1234) + n = 4 + + # Construct a random covariance matrix. + data = rng.randn(n, n) + cov = np.dot(data, data.T) + mean = rng.randn(n) + + # Construct an ndarray which can be interpreted as + # a 2x3 array whose elements are random data vectors. + X = rng.randn(2, 3, n) + + # Check that multiple data points can be evaluated at once. + desired_pdf = multivariate_normal.pdf(X, mean, cov) + desired_cdf = multivariate_normal.cdf(X, mean, cov) + for i in range(2): + for j in range(3): + actual = multivariate_normal.pdf(X[i, j], mean, cov) + assert_allclose(actual, desired_pdf[i,j]) + # Repeat for cdf + actual = multivariate_normal.cdf(X[i, j], mean, cov) + assert_allclose(actual, desired_cdf[i,j], rtol=1e-3) + + def test_normal_1D(self): + # The probability density function for a 1D normal variable should + # agree with the standard normal distribution in scipy.stats.distributions + x = np.linspace(0, 2, 10) + mean, cov = 1.2, 0.9 + scale = cov**0.5 + d1 = norm.pdf(x, mean, scale) + d2 = multivariate_normal.pdf(x, mean, cov) + assert_allclose(d1, d2) + # The same should hold for the cumulative distribution function + d1 = norm.cdf(x, mean, scale) + d2 = multivariate_normal.cdf(x, mean, cov) + assert_allclose(d1, d2) + + def test_marginalization(self): + # Integrating out one of the variables of a 2D Gaussian should + # yield a 1D Gaussian + mean = np.array([2.5, 3.5]) + cov = np.array([[.5, 0.2], [0.2, .6]]) + n = 2 ** 8 + 1 # Number of samples + delta = 6 / (n - 1) # Grid spacing + + v = np.linspace(0, 6, n) + xv, yv = np.meshgrid(v, v) + pos = np.empty((n, n, 2)) + pos[:, :, 0] = xv + pos[:, :, 1] = yv + pdf = multivariate_normal.pdf(pos, mean, cov) + + # Marginalize over x and y axis + margin_x = romb(pdf, delta, axis=0) + margin_y = romb(pdf, delta, axis=1) + + # Compare with standard normal distribution + gauss_x = norm.pdf(v, loc=mean[0], scale=cov[0, 0] ** 0.5) + gauss_y = norm.pdf(v, loc=mean[1], scale=cov[1, 1] ** 0.5) + assert_allclose(margin_x, gauss_x, rtol=1e-2, atol=1e-2) + assert_allclose(margin_y, gauss_y, rtol=1e-2, atol=1e-2) + + def test_frozen(self): + # The frozen distribution should agree with the regular one + rng = np.random.default_rng(1234) + x = rng.standard_normal(5) + mean = rng.standard_normal(5) + cov = np.abs(rng.standard_normal(5)) + norm_frozen = multivariate_normal(mean, cov) + assert_allclose(norm_frozen.pdf(x), multivariate_normal.pdf(x, mean, cov)) + assert_allclose(norm_frozen.logpdf(x), + multivariate_normal.logpdf(x, mean, cov)) + assert_allclose(norm_frozen.cdf(x), multivariate_normal.cdf(x, mean, cov)) + assert_allclose(norm_frozen.logcdf(x), + multivariate_normal.logcdf(x, mean, cov)) + + @pytest.mark.parametrize( + 'covariance', + [ + np.eye(2), + Covariance.from_diagonal([1, 1]), + ] + ) + def test_frozen_multivariate_normal_exposes_attributes(self, covariance): + mean = np.ones((2,)) + cov_should_be = np.eye(2) + norm_frozen = multivariate_normal(mean, covariance) + assert np.allclose(norm_frozen.mean, mean) + assert np.allclose(norm_frozen.cov, cov_should_be) + + def test_pseudodet_pinv(self): + # Make sure that pseudo-inverse and pseudo-det agree on cutoff + + # Assemble random covariance matrix with large and small eigenvalues + rng = np.random.default_rng(1234) + n = 7 + x = rng.standard_normal((n, n)) + cov = np.dot(x, x.T) + s, u = scipy.linalg.eigh(cov) + s = np.full(n, 0.5) + s[0] = 1.0 + s[-1] = 1e-7 + cov = np.dot(u, np.dot(np.diag(s), u.T)) + + # Set cond so that the lowest eigenvalue is below the cutoff + cond = 1e-5 + psd = _PSD(cov, cond=cond) + psd_pinv = _PSD(psd.pinv, cond=cond) + + # Check that the log pseudo-determinant agrees with the sum + # of the logs of all but the smallest eigenvalue + assert_allclose(psd.log_pdet, np.sum(np.log(s[:-1]))) + # Check that the pseudo-determinant of the pseudo-inverse + # agrees with 1 / pseudo-determinant + assert_allclose(-psd.log_pdet, psd_pinv.log_pdet) + + def test_exception_nonsquare_cov(self): + cov = [[1, 2, 3], [4, 5, 6]] + assert_raises(ValueError, _PSD, cov) + + def test_exception_nonfinite_cov(self): + cov_nan = [[1, 0], [0, np.nan]] + assert_raises(ValueError, _PSD, cov_nan) + cov_inf = [[1, 0], [0, np.inf]] + assert_raises(ValueError, _PSD, cov_inf) + + def test_exception_non_psd_cov(self): + cov = [[1, 0], [0, -1]] + assert_raises(ValueError, _PSD, cov) + + def test_exception_singular_cov(self): + rng = np.random.default_rng(1234) + x = rng.standard_normal(5) + mean = rng.standard_normal(5) + cov = np.ones((5, 5)) + e = np.linalg.LinAlgError + assert_raises(e, multivariate_normal, mean, cov) + assert_raises(e, multivariate_normal.pdf, x, mean, cov) + assert_raises(e, multivariate_normal.logpdf, x, mean, cov) + assert_raises(e, multivariate_normal.cdf, x, mean, cov) + assert_raises(e, multivariate_normal.logcdf, x, mean, cov) + + # Message used to be "singular matrix", but this is more accurate. + # See gh-15508 + cov = [[1., 0.], [1., 1.]] + msg = "When `allow_singular is False`, the input matrix" + with pytest.raises(np.linalg.LinAlgError, match=msg): + multivariate_normal(cov=cov) + + def test_R_values(self): + # Compare the multivariate pdf with some values precomputed + # in R version 3.0.1 (2013-05-16) on Mac OS X 10.6. + + # The values below were generated by the following R-script: + # > library(mnormt) + # > x <- seq(0, 2, length=5) + # > y <- 3*x - 2 + # > z <- x + cos(y) + # > mu <- c(1, 3, 2) + # > Sigma <- matrix(c(1,2,0,2,5,0.5,0,0.5,3), 3, 3) + # > r_pdf <- dmnorm(cbind(x,y,z), mu, Sigma) + r_pdf = np.array([0.0002214706, 0.0013819953, 0.0049138692, + 0.0103803050, 0.0140250800]) + + x = np.linspace(0, 2, 5) + y = 3 * x - 2 + z = x + np.cos(y) + r = np.array([x, y, z]).T + + mean = np.array([1, 3, 2], 'd') + cov = np.array([[1, 2, 0], [2, 5, .5], [0, .5, 3]], 'd') + + pdf = multivariate_normal.pdf(r, mean, cov) + assert_allclose(pdf, r_pdf, atol=1e-10) + + # Compare the multivariate cdf with some values precomputed + # in R version 3.3.2 (2016-10-31) on Debian GNU/Linux. + + # The values below were generated by the following R-script: + # > library(mnormt) + # > x <- seq(0, 2, length=5) + # > y <- 3*x - 2 + # > z <- x + cos(y) + # > mu <- c(1, 3, 2) + # > Sigma <- matrix(c(1,2,0,2,5,0.5,0,0.5,3), 3, 3) + # > r_cdf <- pmnorm(cbind(x,y,z), mu, Sigma) + r_cdf = np.array([0.0017866215, 0.0267142892, 0.0857098761, + 0.1063242573, 0.2501068509]) + + cdf = multivariate_normal.cdf(r, mean, cov) + assert_allclose(cdf, r_cdf, atol=2e-5) + + # Also test bivariate cdf with some values precomputed + # in R version 3.3.2 (2016-10-31) on Debian GNU/Linux. + + # The values below were generated by the following R-script: + # > library(mnormt) + # > x <- seq(0, 2, length=5) + # > y <- 3*x - 2 + # > mu <- c(1, 3) + # > Sigma <- matrix(c(1,2,2,5), 2, 2) + # > r_cdf2 <- pmnorm(cbind(x,y), mu, Sigma) + r_cdf2 = np.array([0.01262147, 0.05838989, 0.18389571, + 0.40696599, 0.66470577]) + + r2 = np.array([x, y]).T + + mean2 = np.array([1, 3], 'd') + cov2 = np.array([[1, 2], [2, 5]], 'd') + + cdf2 = multivariate_normal.cdf(r2, mean2, cov2) + assert_allclose(cdf2, r_cdf2, atol=1e-5) + + def test_multivariate_normal_rvs_zero_covariance(self): + mean = np.zeros(2) + covariance = np.zeros((2, 2)) + model = multivariate_normal(mean, covariance, allow_singular=True) + sample = model.rvs() + assert_equal(sample, [0, 0]) + + def test_rvs_shape(self): + # Check that rvs parses the mean and covariance correctly, and returns + # an array of the right shape + N = 300 + d = 4 + sample = multivariate_normal.rvs(mean=np.zeros(d), cov=1, size=N) + assert_equal(sample.shape, (N, d)) + + sample = multivariate_normal.rvs(mean=None, + cov=np.array([[2, .1], [.1, 1]]), + size=N) + assert_equal(sample.shape, (N, 2)) + + u = multivariate_normal(mean=0, cov=1) + sample = u.rvs(N) + assert_equal(sample.shape, (N, )) + + def test_large_sample(self): + # Generate large sample and compare sample mean and sample covariance + # with mean and covariance matrix. + + rng = np.random.RandomState(2846) + + n = 3 + mean = rng.randn(n) + M = rng.randn(n, n) + cov = np.dot(M, M.T) + size = 5000 + + sample = multivariate_normal.rvs(mean, cov, size, random_state=rng) + + assert_allclose(np.cov(sample.T), cov, rtol=1e-1) + assert_allclose(sample.mean(0), mean, rtol=1e-1) + + def test_entropy(self): + rng = np.random.RandomState(2846) + + n = 3 + mean = rng.randn(n) + M = rng.randn(n, n) + cov = np.dot(M, M.T) + + rv = multivariate_normal(mean, cov) + + # Check that frozen distribution agrees with entropy function + assert_almost_equal(rv.entropy(), multivariate_normal.entropy(mean, cov)) + # Compare entropy with manually computed expression involving + # the sum of the logs of the eigenvalues of the covariance matrix + eigs = np.linalg.eig(cov)[0] + desired = 1 / 2 * (n * (np.log(2 * np.pi) + 1) + np.sum(np.log(eigs))) + assert_almost_equal(desired, rv.entropy()) + + def test_lnB(self): + alpha = np.array([1, 1, 1]) + desired = .5 # e^lnB = 1/2 for [1, 1, 1] + + assert_almost_equal(np.exp(_lnB(alpha)), desired) + + def test_cdf_with_lower_limit_arrays(self): + # test CDF with lower limit in several dimensions + rng = np.random.default_rng(2408071309372769818) + mean = [0, 0] + cov = np.eye(2) + a = rng.random((4, 3, 2))*6 - 3 + b = rng.random((4, 3, 2))*6 - 3 + + cdf1 = multivariate_normal.cdf(b, mean, cov, lower_limit=a) + + cdf2a = multivariate_normal.cdf(b, mean, cov) + cdf2b = multivariate_normal.cdf(a, mean, cov) + ab1 = np.concatenate((a[..., 0:1], b[..., 1:2]), axis=-1) + ab2 = np.concatenate((a[..., 1:2], b[..., 0:1]), axis=-1) + cdf2ab1 = multivariate_normal.cdf(ab1, mean, cov) + cdf2ab2 = multivariate_normal.cdf(ab2, mean, cov) + cdf2 = cdf2a + cdf2b - cdf2ab1 - cdf2ab2 + + assert_allclose(cdf1, cdf2) + + def test_cdf_with_lower_limit_consistency(self): + # check that multivariate normal CDF functions are consistent + rng = np.random.default_rng(2408071309372769818) + mean = rng.random(3) + cov = rng.random((3, 3)) + cov = cov @ cov.T + a = rng.random((2, 3))*6 - 3 + b = rng.random((2, 3))*6 - 3 + + cdf1 = multivariate_normal.cdf(b, mean, cov, lower_limit=a) + cdf2 = multivariate_normal(mean, cov).cdf(b, lower_limit=a) + cdf3 = np.exp(multivariate_normal.logcdf(b, mean, cov, lower_limit=a)) + cdf4 = np.exp(multivariate_normal(mean, cov).logcdf(b, lower_limit=a)) + + assert_allclose(cdf2, cdf1, rtol=1e-4) + assert_allclose(cdf3, cdf1, rtol=1e-4) + assert_allclose(cdf4, cdf1, rtol=1e-4) + + def test_cdf_signs(self): + # check that sign of output is correct when np.any(lower > x) + mean = np.zeros(3) + cov = np.eye(3) + b = [[1, 1, 1], [0, 0, 0], [1, 0, 1], [0, 1, 0]] + a = [[0, 0, 0], [1, 1, 1], [0, 1, 0], [1, 0, 1]] + # when odd number of elements of b < a, output is negative + expected_signs = np.array([1, -1, -1, 1]) + cdf = multivariate_normal.cdf(b, mean, cov, lower_limit=a) + assert_allclose(cdf, cdf[0]*expected_signs) + + @pytest.mark.slow + @pytest.mark.parametrize("ndim", [2, 3]) + def test_cdf_vs_cubature(self, ndim): + rng = np.random.default_rng(123) + a = rng.uniform(size=(ndim, ndim)) + cov = a.T @ a + m = rng.uniform(size=ndim) + dist = multivariate_normal(mean=m, cov=cov) + x = rng.uniform(low=-3, high=3, size=(ndim,)) + cdf = dist.cdf(x) + dist_i = multivariate_normal(mean=[0]*ndim, cov=cov) + cdf_i = cubature(dist_i.pdf, [-np.inf]*ndim, x - m).estimate + assert_allclose(cdf, cdf_i, atol=5e-6) + + def test_cdf_known(self): + # https://github.com/scipy/scipy/pull/17410#issuecomment-1312628547 + for ndim in range(2, 12): + cov = np.full((ndim, ndim), 0.5) + np.fill_diagonal(cov, 1.) + dist = multivariate_normal([0]*ndim, cov=cov) + assert_allclose( + dist.cdf([0]*ndim), + 1. / (1. + ndim), + atol=5e-5 + ) + + @pytest.mark.parametrize("ndim", range(2, 10)) + @pytest.mark.parametrize("seed", [0xdeadbeef, 0xdd24528764c9773579731c6b022b48e2]) + def test_cdf_vs_univariate(self, seed, ndim): + rng = np.random.default_rng(seed) + case = MVNProblem.generate_semigeneral(ndim=ndim, rng=rng) + assert (case.low == -np.inf).all() + + dist = multivariate_normal(mean=[0]*ndim, cov=case.covar) + cdf_val = dist.cdf(case.high, rng=rng) + assert_allclose(cdf_val, case.target_val, atol=5e-5) + + @pytest.mark.parametrize("ndim", range(2, 11)) + @pytest.mark.parametrize("seed", [0xdeadbeef, 0xdd24528764c9773579731c6b022b48e2]) + def test_cdf_vs_univariate_2(self, seed, ndim): + rng = np.random.default_rng(seed) + case = MVNProblem.generate_constant(ndim=ndim, rng=rng) + assert (case.low == -np.inf).all() + + dist = multivariate_normal(mean=[0]*ndim, cov=case.covar) + cdf_val = dist.cdf(case.high, rng=rng) + assert_allclose(cdf_val, case.target_val, atol=5e-5) + + @pytest.mark.parametrize("ndim", range(4, 11)) + @pytest.mark.parametrize("seed", [0xdeadbeef, 0xdd24528764c9773579731c6b022b48e4]) + def test_cdf_vs_univariate_singular(self, seed, ndim): + # NB: ndim = 2, 3 has much poorer accuracy than ndim > 3 for many seeds. + # No idea why. + rng = np.random.default_rng(seed) + case = SingularMVNProblem.generate_semiinfinite(ndim=ndim, rng=rng) + assert (case.low == -np.inf).all() + + dist = multivariate_normal(mean=[0]*ndim, cov=case.covar, allow_singular=True, + # default maxpts is too slow, limit it here + maxpts=10_000*case.covar.shape[0] + ) + cdf_val = dist.cdf(case.high, rng=rng) + assert_allclose(cdf_val, case.target_val, atol=1e-3) + + def test_mean_cov(self): + # test the interaction between a Covariance object and mean + P = np.diag(1 / np.array([1, 2, 3])) + cov_object = _covariance.CovViaPrecision(P) + + message = "`cov` represents a covariance matrix in 3 dimensions..." + with pytest.raises(ValueError, match=message): + multivariate_normal.entropy([0, 0], cov_object) + + with pytest.raises(ValueError, match=message): + multivariate_normal([0, 0], cov_object) + + x = [0.5, 0.5, 0.5] + ref = multivariate_normal.pdf(x, [0, 0, 0], cov_object) + assert_equal(multivariate_normal.pdf(x, cov=cov_object), ref) + + ref = multivariate_normal.pdf(x, [1, 1, 1], cov_object) + assert_equal(multivariate_normal.pdf(x, 1, cov=cov_object), ref) + + def test_fit_wrong_fit_data_shape(self): + data = [1, 3] + error_msg = "`x` must be two-dimensional." + with pytest.raises(ValueError, match=error_msg): + multivariate_normal.fit(data) + + @pytest.mark.parametrize('dim', (3, 5)) + def test_fit_correctness(self, dim): + rng = np.random.default_rng(4385269356937404) + x = rng.random((100, dim)) + mean_est, cov_est = multivariate_normal.fit(x) + mean_ref, cov_ref = np.mean(x, axis=0), np.cov(x.T, ddof=0) + assert_allclose(mean_est, mean_ref, atol=1e-15) + assert_allclose(cov_est, cov_ref, rtol=5e-15) + + def test_fit_both_parameters_fixed(self): + data = np.full((2, 1), 3) + mean_fixed = 1. + cov_fixed = np.atleast_2d(1.) + mean, cov = multivariate_normal.fit(data, fix_mean=mean_fixed, + fix_cov=cov_fixed) + assert_equal(mean, mean_fixed) + assert_equal(cov, cov_fixed) + + @pytest.mark.parametrize('fix_mean', [np.zeros((2, 2)), + np.zeros((3, ))]) + def test_fit_fix_mean_input_validation(self, fix_mean): + msg = ("`fix_mean` must be a one-dimensional array the same " + "length as the dimensionality of the vectors `x`.") + with pytest.raises(ValueError, match=msg): + multivariate_normal.fit(np.eye(2), fix_mean=fix_mean) + + @pytest.mark.parametrize('fix_cov', [np.zeros((2, )), + np.zeros((3, 2)), + np.zeros((4, 4))]) + def test_fit_fix_cov_input_validation_dimension(self, fix_cov): + msg = ("`fix_cov` must be a two-dimensional square array " + "of same side length as the dimensionality of the " + "vectors `x`.") + with pytest.raises(ValueError, match=msg): + multivariate_normal.fit(np.eye(3), fix_cov=fix_cov) + + def test_fit_fix_cov_not_positive_semidefinite(self): + error_msg = "`fix_cov` must be symmetric positive semidefinite." + with pytest.raises(ValueError, match=error_msg): + fix_cov = np.array([[1., 0.], [0., -1.]]) + multivariate_normal.fit(np.eye(2), fix_cov=fix_cov) + + def test_fit_fix_mean(self): + rng = np.random.default_rng(4385269356937404) + loc = rng.random(3) + A = rng.random((3, 3)) + cov = np.dot(A, A.T) + samples = multivariate_normal.rvs(mean=loc, cov=cov, size=100, + random_state=rng) + mean_free, cov_free = multivariate_normal.fit(samples) + logp_free = multivariate_normal.logpdf(samples, mean=mean_free, + cov=cov_free).sum() + mean_fix, cov_fix = multivariate_normal.fit(samples, fix_mean=loc) + assert_equal(mean_fix, loc) + logp_fix = multivariate_normal.logpdf(samples, mean=mean_fix, + cov=cov_fix).sum() + # test that fixed parameters result in lower likelihood than free + # parameters + assert logp_fix < logp_free + # test that a small perturbation of the resulting parameters + # has lower likelihood than the estimated parameters + A = rng.random((3, 3)) + m = 1e-8 * np.dot(A, A.T) + cov_perturbed = cov_fix + m + logp_perturbed = (multivariate_normal.logpdf(samples, + mean=mean_fix, + cov=cov_perturbed) + ).sum() + assert logp_perturbed < logp_fix + + def test_fit_fix_cov(self): + rng = np.random.default_rng(4385269356937404) + loc = rng.random(3) + A = rng.random((3, 3)) + cov = np.dot(A, A.T) + samples = multivariate_normal.rvs(mean=loc, cov=cov, + size=100, random_state=rng) + mean_free, cov_free = multivariate_normal.fit(samples) + logp_free = multivariate_normal.logpdf(samples, mean=mean_free, + cov=cov_free).sum() + mean_fix, cov_fix = multivariate_normal.fit(samples, fix_cov=cov) + assert_equal(mean_fix, np.mean(samples, axis=0)) + assert_equal(cov_fix, cov) + logp_fix = multivariate_normal.logpdf(samples, mean=mean_fix, + cov=cov_fix).sum() + # test that fixed parameters result in lower likelihood than free + # parameters + assert logp_fix < logp_free + # test that a small perturbation of the resulting parameters + # has lower likelihood than the estimated parameters + mean_perturbed = mean_fix + 1e-8 * rng.random(3) + logp_perturbed = (multivariate_normal.logpdf(samples, + mean=mean_perturbed, + cov=cov_fix) + ).sum() + assert logp_perturbed < logp_fix + + +class TestMarginal: + @pytest.mark.parametrize('dist,kwargs', [(multivariate_normal, {}), + (multivariate_t, {'df': 4})]) + @pytest.mark.parametrize('X_ndim', [3]) + @pytest.mark.parametrize('dimensions', [[1], [-1, 1]]) + @pytest.mark.parametrize('frozen', [True, False]) + @pytest.mark.parametrize('cov_object', [True, False]) + def test_marginal_distribution(self, dist, X_ndim, dimensions, frozen, + cov_object, kwargs): + rng = np.random.default_rng(413911473) + loc = rng.standard_normal(X_ndim) + A = rng.standard_normal((X_ndim, X_ndim)) + scale = A @ A.T + + if cov_object and dist == multivariate_t: + pytest.skip('`multivariate_t` does not accept a `Covariance` object') + elif cov_object: + scale = _covariance.CovViaPrecision(scale) + + # number of points at which to evaluate marginal PDF + x = np.random.standard_normal((4, len(dimensions))) + X = dist(loc, scale, **kwargs) + + if frozen: + Y = X.marginal(dimensions) + res = Y.pdf(x) + else: + Y = dist.marginal(dimensions, loc, scale, **kwargs) + res = Y.pdf(x) + + ref = marginal_pdf(X, X_ndim, dimensions, x) + assert_allclose(ref, res) + + @pytest.mark.parametrize('dist', [multivariate_normal, multivariate_t]) + def test_marginal_input_validation(self, dist): + rng = np.random.default_rng(413911473) + mean = rng.standard_normal(3) + A = rng.standard_normal((3, 3)) + cov = A @ A.T + + X = dist(mean, cov) + + msg = r"Dimensions \[3\] are invalid .*" + with pytest.raises(ValueError, match=msg): + X.marginal(3) + + with pytest.raises(ValueError, match=msg): + X.marginal([0, 1, 2, 3]) + + msg = r"All elements of `dimensions` must be unique." + with pytest.raises(ValueError, match=msg): + X.marginal([2, -1]) + + with pytest.raises(ValueError, match=msg): + X.marginal([[0, 1]]) + + msg = r"Elements of `dimensions` must be integers." + with pytest.raises(ValueError, match=msg): + X.marginal([1.1, 2.0]) + + @pytest.mark.parametrize('dist', [multivariate_normal, multivariate_t]) + def test_marginal_special_cases(self, dist): + rng = np.random.default_rng(413911473) + loc = rng.standard_normal(3) + A = rng.standard_normal((3, 3)) + scale = A @ A.T + + X = dist(loc, scale) + + msg = r"Cannot marginalize all dimensions." + with pytest.raises(ValueError, match=msg): + X.marginal([]) + + +class TestMatrixNormal: + + def test_bad_input(self): + # Check that bad inputs raise errors + num_rows = 4 + num_cols = 3 + M = np.full((num_rows,num_cols), 0.3) + U = 0.5 * np.identity(num_rows) + np.full((num_rows, num_rows), 0.5) + V = 0.7 * np.identity(num_cols) + np.full((num_cols, num_cols), 0.3) + + # Incorrect dimensions + assert_raises(ValueError, matrix_normal, np.zeros((5,4,3))) + assert_raises(ValueError, matrix_normal, M, np.zeros(10), V) + assert_raises(ValueError, matrix_normal, M, U, np.zeros(10)) + assert_raises(ValueError, matrix_normal, M, U, U) + assert_raises(ValueError, matrix_normal, M, V, V) + assert_raises(ValueError, matrix_normal, M.T, U, V) + + e = np.linalg.LinAlgError + # Singular covariance for the rvs method of a non-frozen instance + assert_raises(e, matrix_normal.rvs, + M, U, np.ones((num_cols, num_cols))) + assert_raises(e, matrix_normal.rvs, + M, np.ones((num_rows, num_rows)), V) + # Singular covariance for a frozen instance + assert_raises(e, matrix_normal, M, U, np.ones((num_cols, num_cols))) + assert_raises(e, matrix_normal, M, np.ones((num_rows, num_rows)), V) + + def test_default_inputs(self): + # Check that default argument handling works + num_rows = 4 + num_cols = 3 + M = np.full((num_rows,num_cols), 0.3) + U = 0.5 * np.identity(num_rows) + np.full((num_rows, num_rows), 0.5) + V = 0.7 * np.identity(num_cols) + np.full((num_cols, num_cols), 0.3) + Z = np.zeros((num_rows, num_cols)) + Zr = np.zeros((num_rows, 1)) + Zc = np.zeros((1, num_cols)) + Ir = np.identity(num_rows) + Ic = np.identity(num_cols) + I1 = np.identity(1) + + assert_equal(matrix_normal.rvs(mean=M, rowcov=U, colcov=V).shape, + (num_rows, num_cols)) + assert_equal(matrix_normal.rvs(mean=M).shape, + (num_rows, num_cols)) + assert_equal(matrix_normal.rvs(rowcov=U).shape, + (num_rows, 1)) + assert_equal(matrix_normal.rvs(colcov=V).shape, + (1, num_cols)) + assert_equal(matrix_normal.rvs(mean=M, colcov=V).shape, + (num_rows, num_cols)) + assert_equal(matrix_normal.rvs(mean=M, rowcov=U).shape, + (num_rows, num_cols)) + assert_equal(matrix_normal.rvs(rowcov=U, colcov=V).shape, + (num_rows, num_cols)) + + assert_equal(matrix_normal(mean=M).rowcov, Ir) + assert_equal(matrix_normal(mean=M).colcov, Ic) + assert_equal(matrix_normal(rowcov=U).mean, Zr) + assert_equal(matrix_normal(rowcov=U).colcov, I1) + assert_equal(matrix_normal(colcov=V).mean, Zc) + assert_equal(matrix_normal(colcov=V).rowcov, I1) + assert_equal(matrix_normal(mean=M, rowcov=U).colcov, Ic) + assert_equal(matrix_normal(mean=M, colcov=V).rowcov, Ir) + assert_equal(matrix_normal(rowcov=U, colcov=V).mean, Z) + + def test_covariance_expansion(self): + # Check that covariance can be specified with scalar or vector + num_rows = 4 + num_cols = 3 + M = np.full((num_rows, num_cols), 0.3) + Uv = np.full(num_rows, 0.2) + Us = 0.2 + Vv = np.full(num_cols, 0.1) + Vs = 0.1 + + Ir = np.identity(num_rows) + Ic = np.identity(num_cols) + + assert_equal(matrix_normal(mean=M, rowcov=Uv, colcov=Vv).rowcov, + 0.2*Ir) + assert_equal(matrix_normal(mean=M, rowcov=Uv, colcov=Vv).colcov, + 0.1*Ic) + assert_equal(matrix_normal(mean=M, rowcov=Us, colcov=Vs).rowcov, + 0.2*Ir) + assert_equal(matrix_normal(mean=M, rowcov=Us, colcov=Vs).colcov, + 0.1*Ic) + + def test_frozen_matrix_normal(self): + for i in range(1,5): + for j in range(1,5): + M = np.full((i,j), 0.3) + U = 0.5 * np.identity(i) + np.full((i,i), 0.5) + V = 0.7 * np.identity(j) + np.full((j,j), 0.3) + + frozen = matrix_normal(mean=M, rowcov=U, colcov=V) + + rvs1 = frozen.rvs(random_state=1234) + rvs2 = matrix_normal.rvs(mean=M, rowcov=U, colcov=V, + random_state=1234) + assert_equal(rvs1, rvs2) + + X = frozen.rvs(random_state=1234) + + pdf1 = frozen.pdf(X) + pdf2 = matrix_normal.pdf(X, mean=M, rowcov=U, colcov=V) + assert_equal(pdf1, pdf2) + + logpdf1 = frozen.logpdf(X) + logpdf2 = matrix_normal.logpdf(X, mean=M, rowcov=U, colcov=V) + assert_equal(logpdf1, logpdf2) + + def test_matches_multivariate(self): + # Check that the pdfs match those obtained by vectorising and + # treating as a multivariate normal. + for i in range(1,5): + for j in range(1,5): + M = np.full((i,j), 0.3) + U = 0.5 * np.identity(i) + np.full((i,i), 0.5) + V = 0.7 * np.identity(j) + np.full((j,j), 0.3) + + frozen = matrix_normal(mean=M, rowcov=U, colcov=V) + X = frozen.rvs(random_state=1234) + pdf1 = frozen.pdf(X) + logpdf1 = frozen.logpdf(X) + entropy1 = frozen.entropy() + + vecX = X.T.flatten() + vecM = M.T.flatten() + cov = np.kron(V,U) + pdf2 = multivariate_normal.pdf(vecX, mean=vecM, cov=cov) + logpdf2 = multivariate_normal.logpdf(vecX, mean=vecM, cov=cov) + entropy2 = multivariate_normal.entropy(mean=vecM, cov=cov) + + assert_allclose(pdf1, pdf2, rtol=1E-10) + assert_allclose(logpdf1, logpdf2, rtol=1E-10) + assert_allclose(entropy1, entropy2) + + def test_array_input(self): + # Check array of inputs has the same output as the separate entries. + num_rows = 4 + num_cols = 3 + M = np.full((num_rows,num_cols), 0.3) + U = 0.5 * np.identity(num_rows) + np.full((num_rows, num_rows), 0.5) + V = 0.7 * np.identity(num_cols) + np.full((num_cols, num_cols), 0.3) + N = 10 + + frozen = matrix_normal(mean=M, rowcov=U, colcov=V) + X1 = frozen.rvs(size=N, random_state=1234) + X2 = frozen.rvs(size=N, random_state=4321) + X = np.concatenate((X1[np.newaxis,:,:,:],X2[np.newaxis,:,:,:]), axis=0) + assert_equal(X.shape, (2, N, num_rows, num_cols)) + + array_logpdf = frozen.logpdf(X) + assert_equal(array_logpdf.shape, (2, N)) + for i in range(2): + for j in range(N): + separate_logpdf = matrix_normal.logpdf(X[i,j], mean=M, + rowcov=U, colcov=V) + assert_allclose(separate_logpdf, array_logpdf[i,j], 1E-10) + + def test_moments(self): + # Check that the sample moments match the parameters + num_rows = 4 + num_cols = 3 + M = np.full((num_rows,num_cols), 0.3) + U = 0.5 * np.identity(num_rows) + np.full((num_rows, num_rows), 0.5) + V = 0.7 * np.identity(num_cols) + np.full((num_cols, num_cols), 0.3) + N = 1000 + + frozen = matrix_normal(mean=M, rowcov=U, colcov=V) + X = frozen.rvs(size=N, random_state=1234) + + sample_mean = np.mean(X,axis=0) + assert_allclose(sample_mean, M, atol=0.1) + + sample_colcov = np.cov(X.reshape(N*num_rows,num_cols).T) + assert_allclose(sample_colcov, V, atol=0.1) + + sample_rowcov = np.cov(np.swapaxes(X,1,2).reshape( + N*num_cols,num_rows).T) + assert_allclose(sample_rowcov, U, atol=0.1) + + def test_samples(self): + # Regression test to ensure that we always generate the same stream of + # random variates. + actual = matrix_normal.rvs( + mean=np.array([[1, 2], [3, 4]]), + rowcov=np.array([[4, -1], [-1, 2]]), + colcov=np.array([[5, 1], [1, 10]]), + random_state=np.random.default_rng(0), + size=2 + ) + expected = np.array( + [[[1.56228264238181, -1.24136424071189], + [2.46865788392114, 6.22964440489445]], + [[3.86405716144353, 10.73714311429529], + [2.59428444080606, 5.79987854490876]]] + ) + assert_allclose(actual, expected) + + +class TestMatrixT: + + def test_bad_input(self): + # Check that bad inputs raise errors + num_rows = 4 + num_cols = 3 + df = 5 + M = np.full((num_rows, num_cols), 0.3) + U = 0.5 * np.identity(num_rows) + np.full((num_rows, num_rows), 0.5) + V = 0.7 * np.identity(num_cols) + np.full((num_cols, num_cols), 0.3) + + # Nonpositive degrees of freedom + with pytest.raises(ValueError, match="Degrees of freedom must be positive."): + matrix_t(df=0) + + # Incorrect dimensions + with pytest.raises(ValueError, match="Array `mean` must be 2D."): + matrix_t(mean=np.zeros((5, 4, 3))) + + with pytest.raises(ValueError, match="Array `mean` has invalid shape."): + matrix_t(mean=np.zeros((4, 3, 0))) + + with pytest.raises(ValueError, match="Array `row_spread` has invalid shape."): + matrix_t(row_spread=np.ones((1, 0))) + + with pytest.raises( + ValueError, match="Array `row_spread` must be a scalar or a 2D array." + ): + matrix_t(row_spread=np.ones((1, 2, 3))) + + with pytest.raises(ValueError, match="Array `row_spread` must be square."): + matrix_t(row_spread=np.ones((1, 2))) + + with pytest.raises(ValueError, match="Array `col_spread` has invalid shape."): + matrix_t(col_spread=np.ones((1, 0))) + + with pytest.raises( + ValueError, match="Array `col_spread` must be a scalar or a 2D array." + ): + matrix_t(col_spread=np.ones((1, 2, 3))) + + with pytest.raises(ValueError, match="Array `col_spread` must be square."): + matrix_t(col_spread=np.ones((1, 2))) + + with pytest.raises( + ValueError, + match="Arrays `mean` and `row_spread` must have the same number " + "of rows.", + ): + matrix_t(mean=M, row_spread=V) + + with pytest.raises( + ValueError, + match="Arrays `mean` and `col_spread` must have the same number " + "of columns.", + ): + matrix_t(mean=M, col_spread=U) + + # Incorrect dimension of input matrix + with pytest.raises( + ValueError, + match="The shape of array `X` is not conformal with " + "the distribution parameters.", + ): + matrix_t.pdf(X=np.zeros((num_rows, num_rows)), mean=M) + + # Singular covariance for a non-frozen instance + + with pytest.raises( + np.linalg.LinAlgError, + match="Internal potrf return info", + ): + matrix_t.rvs(M, U, np.ones((num_cols, num_cols)), df) + + with pytest.raises( + np.linalg.LinAlgError, + match="Internal potrf return info", + ): + matrix_t.rvs(M, np.ones((num_rows, num_rows)), V, df) + + # Singular covariance for a frozen instance + + with pytest.raises( + np.linalg.LinAlgError, + match="When `allow_singular is False`, the input matrix must be " + "symmetric positive definite.", + ): + matrix_t(M, U, np.ones((num_cols, num_cols)), df) + + with pytest.raises( + np.linalg.LinAlgError, + match="When `allow_singular is False`, the input matrix must be " + "symmetric positive definite.", + ): + matrix_t(M, np.ones((num_rows, num_rows)), V, df) + + def test_default_inputs(self): + # Check that default argument handling works + num_rows = 4 + num_cols = 3 + df = 5 + M = np.full((num_rows, num_cols), 0.3) + U = 0.5 * np.identity(num_rows) + np.full((num_rows, num_rows), 0.5) + V = 0.7 * np.identity(num_cols) + np.full((num_cols, num_cols), 0.3) + Z = np.zeros((num_rows, num_cols)) + Zr = np.zeros((num_rows, 1)) + Zc = np.zeros((1, num_cols)) + Ir = np.identity(num_rows) + Ic = np.identity(num_cols) + I1 = np.identity(1) + dfdefault = 1 + + assert_equal( + matrix_t.rvs(mean=M, row_spread=U, col_spread=V, df=df).shape, + (num_rows, num_cols), + ) + assert_equal(matrix_t.rvs(mean=M).shape, (num_rows, num_cols)) + assert_equal(matrix_t.rvs(row_spread=U).shape, (num_rows, 1)) + assert_equal(matrix_t.rvs(col_spread=V).shape, (1, num_cols)) + assert_equal(matrix_t.rvs(mean=M, col_spread=V).shape, (num_rows, num_cols)) + assert_equal(matrix_t.rvs(mean=M, row_spread=U).shape, (num_rows, num_cols)) + assert_equal( + matrix_t.rvs(row_spread=U, col_spread=V).shape, (num_rows, num_cols) + ) + + assert_equal(matrix_t().df, dfdefault) + assert_equal(matrix_t(mean=M).row_spread, Ir) + assert_equal(matrix_t(mean=M).col_spread, Ic) + assert_equal(matrix_t(row_spread=U).mean, Zr) + assert_equal(matrix_t(row_spread=U).col_spread, I1) + assert_equal(matrix_t(col_spread=V).mean, Zc) + assert_equal(matrix_t(col_spread=V).row_spread, I1) + assert_equal(matrix_t(mean=M, row_spread=U).col_spread, Ic) + assert_equal(matrix_t(mean=M, col_spread=V).row_spread, Ir) + assert_equal(matrix_t(row_spread=U, col_spread=V, df=df).mean, Z) + + def test_covariance_expansion(self): + # Check that covariance can be specified with scalar or vector + num_rows = 4 + num_cols = 3 + df = 1 + M = np.full((num_rows, num_cols), 0.3) + Uv = np.full(num_rows, 0.2) + Us = 0.2 + Vv = np.full(num_cols, 0.1) + Vs = 0.1 + + Ir = np.identity(num_rows) + Ic = np.identity(num_cols) + + assert_equal( + matrix_t(mean=M, row_spread=Uv, col_spread=Vv, df=df).row_spread, 0.2 * Ir + ) + assert_equal( + matrix_t(mean=M, row_spread=Uv, col_spread=Vv, df=df).col_spread, 0.1 * Ic + ) + assert_equal( + matrix_t(mean=M, row_spread=Us, col_spread=Vs, df=df).row_spread, 0.2 * Ir + ) + assert_equal( + matrix_t(mean=M, row_spread=Us, col_spread=Vs, df=df).col_spread, 0.1 * Ic + ) + + @pytest.mark.parametrize("i", range(1, 4)) + @pytest.mark.parametrize("j", range(1, 4)) + def test_frozen_matrix_t(self, i, j): + + M = np.full((i, j), 0.3) + U = 0.5 * np.identity(i) + np.full((i, i), 0.5) + V = 0.7 * np.identity(j) + np.full((j, j), 0.3) + df = i + j + + frozen = matrix_t(mean=M, row_spread=U, col_spread=V, df=df) + + rvs1 = frozen.rvs(random_state=1234) + rvs2 = matrix_t.rvs( + mean=M, row_spread=U, col_spread=V, df=df, random_state=1234 + ) + assert_equal(rvs1, rvs2) + + X = frozen.rvs(random_state=1234) + + pdf1 = frozen.pdf(X) + pdf2 = matrix_t.pdf(X, mean=M, row_spread=U, col_spread=V, df=df) + assert_equal(pdf1, pdf2) + + logpdf1 = frozen.logpdf(X) + logpdf2 = matrix_t.logpdf(X, mean=M, row_spread=U, col_spread=V, df=df) + assert_equal(logpdf1, logpdf2) + + def test_array_input(self): + # Check array of inputs has the same output as the separate entries. + num_rows = 4 + num_cols = 3 + M = np.full((num_rows, num_cols), 0.3) + U = 0.5 * np.identity(num_rows) + np.full((num_rows, num_rows), 0.5) + V = 0.7 * np.identity(num_cols) + np.full((num_cols, num_cols), 0.3) + df = 1 + N = 10 + + frozen = matrix_t(mean=M, row_spread=U, col_spread=V, df=df) + X1 = frozen.rvs(size=N, random_state=1234) + X2 = frozen.rvs(size=N, random_state=4321) + X = np.concatenate((X1[np.newaxis, :, :, :], X2[np.newaxis, :, :, :]), axis=0) + assert_equal(X.shape, (2, N, num_rows, num_cols)) + + array_logpdf = frozen.logpdf(X) + logpdf_shape = array_logpdf.shape + assert_equal(logpdf_shape, (2, N)) + for i in range(2): + for j in range(N): + separate_logpdf = matrix_t.logpdf( + X[i, j], mean=M, row_spread=U, col_spread=V, df=df + ) + assert_allclose(separate_logpdf, array_logpdf[i, j], 1e-10) + + @staticmethod + def relative_error(vec1: np.ndarray, vec2: np.ndarray): + numerator = np.linalg.norm(vec1 - vec2) ** 2 + denominator = np.linalg.norm(vec1) ** 2 + np.linalg.norm(vec2) ** 2 + return numerator / denominator + + @staticmethod + def matrix_divergence(mat_true: np.ndarray, mat_est: np.ndarray) -> float: + mat_true_psd = _PSD(mat_true, allow_singular=False) + mat_est_psd = _PSD(mat_est, allow_singular=False) + if (np.exp(mat_est_psd.log_pdet) <= 0) or (np.exp(mat_true_psd.log_pdet) <= 0): + return np.inf + trace_term = np.trace(mat_est_psd.pinv @ mat_true) + log_detratio = mat_est_psd.log_pdet - mat_true_psd.log_pdet + return (trace_term + log_detratio - len(mat_true)) / 2 + + @staticmethod + def vec(a_mat: np.ndarray) -> np.ndarray: + """ + For an (m,n) array `a_mat` the output `vec(a_mat)` is an (m*n, 1) + array formed by stacking the columns of `a_mat` in the order in + which they occur in `a_mat`. + """ + assert a_mat.ndim == 2 + return a_mat.T.reshape((a_mat.size,)) + + def test_moments(self): + r""" + Gupta and Nagar (2000) Theorem 4.3.1 (p.135) + -------------------------------------------- + The covariance of the vectorized matrix variate t-distribution equals + $ (V \otimes U) / (\text{df} - 2)$, where $\otimes$ + denotes the usual Kronecker product. + """ + + df = 5 + num_rows = 4 + num_cols = 3 + M = np.full((num_rows, num_cols), 0.3) + U = 0.5 * np.identity(num_rows) + np.full((num_rows, num_rows), 0.5) + V = 0.7 * np.identity(num_cols) + np.full((num_cols, num_cols), 0.3) + N = 10**4 + atol = 1e-1 + + frozen = matrix_t(mean=M, row_spread=U, col_spread=V, df=df) + X = frozen.rvs(size=N, random_state=42) + + relerr = self.relative_error(M, X.mean(axis=0)) + assert_close(relerr, 0, atol=atol) + + cov_vec_true = np.kron(V, U) / (df - 2) + cov_vec_rvs = np.cov(np.array([self.vec(x) for x in X]), rowvar=False) + kl = self.matrix_divergence(cov_vec_true, cov_vec_rvs) + assert_close(kl, 0, atol=atol) + + def test_pdf_against_julia(self): + """ + Test values generated from Julia. + + Dockerfile + ---------- + FROM julia:1.11.5 + RUN julia -e 'using Pkg; Pkg.add("Distributions"); Pkg.add("PDMats")' + WORKDIR /usr/src + + Commands + -------- + using DelimitedFiles + using Distributions + using PDMats + using Random + Random.seed!(42) + ν = 5 + M = [1 2 3; 4 5 6] + Σ = PDMats.PDMat([1 0.5; 0.5 1]) + Ω = PDMats.PDMat([1 0.3 0.2; 0.3 1 0.4; 0.2 0.4 1]) + dist = MatrixTDist(ν, M, Σ, Ω) + samples = rand(dist, 10) + pdfs = [pdf(dist, s) for s in samples] + """ + + df = 5 + M = np.array([[1, 2, 3], [4, 5, 6]]) + U = np.array([[1, 0.5], [0.5, 1]]) + V = np.array([[1, 0.3, 0.2], [0.3, 1, 0.4], [0.2, 0.4, 1]]) + + rtol = 1e-10 + + samples_j = np.array( + [ + [ + [0.958884881003464, 2.328976673167312, 2.936195396714506], + [3.656388568544394, 5.677549814962506, 6.292509556719057] + ], + [ + [0.830992685140180, 2.588946865508210, 3.310327469315906], + [3.850637198786261, 5.106074165416971, 6.403143979925566] + ], + [ + [1.572053537500711, 1.760828063560249, 2.812123062636012], + [4.156334686390513, 5.075942019982631, 5.827004350136873] + ], + [ + [1.683810860278459, 2.801203900480317, 4.054517744825265], + [4.778239956376877, 5.070613721477604, 6.640349743267192] + ], + [ + [0.443183825511296, 2.072092271247398, 3.045385527559403], + [4.374387994815022, 5.083432151729137, 5.958013783940404] + ], + [ + [0.311591337218329, 1.162836182564980, 2.562167762547456], + [3.079154928756626, 4.202325496476140, 5.485839479663457] + ], + [ + [0.943713128785340, 1.923800464789872, 2.511941262351750], + [4.124882619205123, 4.889406461458511, 5.689675454116582] + ], + [ + [1.487852512870631, 1.933859334657448, 2.681311906634522], + [4.124418827930267, 5.335204598518954, 5.988120342017037] + ], + [ + [1.002470749319751, 1.386785511789551, 2.890832331097640], + [4.372884362128993, 4.729718562700068, 6.732322315921552] + ], + [ + [1.421351511333299, 2.106946903600814, 2.654619331838720], + [4.188693248790616, 5.336439611284261, 5.279121290355546] + ] + ] + ) + + pdfs_j = np.array( + [ + 0.082798951655369, + 0.119993852401118, + 0.151969434727803, + 0.003620324481841, + 0.072538716346179, + 0.027002666410192, + 0.485180162388507, + 0.135740468069511, + 0.013619162593841, + 0.034813885519299 + ] + ) + + pdfs_py = matrix_t.pdf(samples_j, mean=M, row_spread=U, col_spread=V, df=df) + + assert_allclose(pdfs_j, pdfs_py, rtol=rtol) + + def test_pdf_against_mathematica(self): + """ + Test values generated from Mathematica 13.0.0 for Linux x86 (64-bit) + Release ID 13.0.0.0 (7522564, 2021120311723), Patch Level 0 + + mu={{1,2,3},{4,5,6}}; + sigma={{1,0.5},{0.5,1}}; + omega={{1,0.3,0.2},{0.3,1,0.4},{0.2,0.4,1}}; + df=5; + sampleSize=10; + SeedRandom[42]; + dist=MatrixTDistribution[mu,sigma,omega,df]; + samples=SetPrecision[RandomVariate[dist,sampleSize],15]; + pdfs=SetPrecision[PDF[dist,#]&/@samples,15]; + """ + + df = 5 + M = np.array([[1, 2, 3], [4, 5, 6]]) + U = np.array([[1, 0.5], [0.5, 1]]) + V = np.array([[1, 0.3, 0.2], [0.3, 1, 0.4], [0.2, 0.4, 1]]) + + rtol = 1e-10 + + samples_m = np.array( + [ + [ + [0.639971699425374, 2.171718671534955, 2.575826093352771], + [4.031082477912233, 5.021680958526638, 6.268126154787008], + ], + [ + [1.164842884206232, 2.526297099993045, 3.781375229865069], + [3.912979114956833, 4.202714884504189, 5.661830748993523], + ], + [ + [1.00461853907369, 2.080028751298565, 3.406489485602410], + [3.993327716320432, 5.655909265966448, 6.578059791357837], + ], + [ + [0.80625209501374, 2.529009560674907, 2.807513313302189], + [3.722896768794995, 5.26987322525995, 5.801155613199776], + ], + [ + [0.445816208657817, 3.224059910964103, 2.954990980541423], + [3.451520519442941, 7.064424621385415, 5.438834195890955], + ], + [ + [0.919232769636664, 2.374572300756703, 3.495118928313048], + [3.924447237903237, 5.627654256287447, 5.806104608153957], + ], + [ + [2.014242004090113, 1.377018127709871, 3.114064311468686], + [3.88881648137925, 4.603482820518904, 5.714205489738063], + ], + [ + [1.322000147426889, 2.602135838377777, 2.558921028724319], + [4.50534702030683, 5.861137323151889, 5.181872548334852], + ], + [ + [1.448743656862261, 2.053847557652242, 3.637321543241769], + [4.097711403906707, 4.506916241403669, 5.68010653497977], + ], + [ + [1.045187318995198, 1.645467189679729, 3.284396214544507], + [3.648493466445393, 5.004212508553601, 6.301624351328048], + ], + ] + ) + + pdfs_m = np.array( + [ + 0.085671937131824, + 0.004821273644067, + 0.105978034029754, + 0.174250448808208, + 3.945711836053583e-05, + 0.027158790350349, + 0.00299095120309, + 0.005594546018078, + 0.025788366971310, + 0.120210733598845, + ] + ) + + pdfs_py = matrix_t.pdf(samples_m, mean=M, row_spread=U, col_spread=V, df=df) + + assert_allclose(pdfs_m, pdfs_py, rtol=rtol) + + def test_samples(self): + df = 5 + num_rows = 4 + num_cols = 3 + M = np.full((num_rows, num_cols), 0.3) + U = 0.5 * np.identity(num_rows) + np.full((num_rows, num_rows), 0.5) + V = 0.7 * np.identity(num_cols) + np.full((num_cols, num_cols), 0.3) + N = 10**4 + rtol = 0.05 + + # `rvs` performs Cholesky-inverse-Wishart sampling on the smaller + # dimension of `mean` + + frozen = matrix_t(mean=M, row_spread=U, col_spread=V, df=df) + X = frozen.rvs(size=N, random_state=42) # column-wise rvs + m = X.mean(0) + + frozenT = matrix_t(mean=M.T, row_spread=V, col_spread=U, df=df) + XT = frozenT.rvs(size=N, random_state=42) # row-wise rvs + mT = XT.mean(0) + + # Gupta and Nagar (2000) Theorem 4.3.3 (p.137) + # -------------------------------------------- + # If T follows a matrix variate t-distribution with mean M and row_spread U + # and col_spread V and df degrees of freedom, then its transpose T.T follows + # a matrix variate t-distribution with mean M.T and row_spread V and + # col_spread U and df degrees of freedom. + + assert_allclose(M, m, rtol=rtol) + assert_allclose(M.T, mT, rtol=rtol) + assert_allclose(m, mT.T, rtol=rtol) + assert_allclose(m.T, mT, rtol=rtol) + + @pytest.mark.parametrize("shape_case", ["row", "col"]) + def test_against_multivariate_t(self, shape_case): + r""" + Gupta and Nagar (2000) p.133f + When the number of rows or the number of columns equals 1 the + matrix t reduces to the multivariate t. But, the matrix t + is parameterized by raw 2nd moments whereas the multivariate t is + parameterized by a covariance (raw 2nd central moment normalized by df). + We can see the difference by comparing the author's notation + $t_p(n, \omega, \mathbf{\mu}, \Sigma)$ + for a matrix t with a single column + to the formula (4.1.2) for the PDF of the multivariate t. + """ + rtol = 1e-6 + df = 5 + + if shape_case == "row": + num_rows = 1 + num_cols = 3 + row_spread = 1 + col_spread = np.array([[1, 0.3, 0.2], [0.3, 1, 0.4], [0.2, 0.4, 1]]) + shape = col_spread / df + else: # shape_case == "col" + num_rows = 3 + num_cols = 1 + row_spread = np.array([[1, 0.3, 0.2], [0.3, 1, 0.4], [0.2, 0.4, 1]]) + col_spread=1 + shape = row_spread / df + + M = np.full((num_rows, num_cols), 0.3) + + t_mat = matrix_t( + mean=M, row_spread=row_spread, col_spread=col_spread, df=df + ) + t_mvt = multivariate_t(loc=M.squeeze(), shape=shape, df=df) + + X = t_mat.rvs(size=3, random_state=42) + t_mat_logpdf = t_mat.logpdf(X) + t_mvt_logpdf = t_mvt.logpdf(X.squeeze()) + assert_allclose(t_mvt_logpdf, t_mat_logpdf, rtol=rtol) + + +class TestDirichlet: + + def test_frozen_dirichlet(self): + rng = np.random.default_rng(2846) + + n = rng.integers(1, 32) + alpha = rng.uniform(10e-10, 100, n) + + d = dirichlet(alpha) + + assert_equal(d.var(), dirichlet.var(alpha)) + assert_equal(d.mean(), dirichlet.mean(alpha)) + assert_equal(d.entropy(), dirichlet.entropy(alpha)) + num_tests = 10 + for i in range(num_tests): + x = rng.uniform(10e-10, 100, n) + x /= np.sum(x) + assert_equal(d.pdf(x[:-1]), dirichlet.pdf(x[:-1], alpha)) + assert_equal(d.logpdf(x[:-1]), dirichlet.logpdf(x[:-1], alpha)) + + def test_numpy_rvs_shape_compatibility(self): + rng = np.random.default_rng(2846) + alpha = np.array([1.0, 2.0, 3.0]) + x = rng.dirichlet(alpha, size=7) + assert_equal(x.shape, (7, 3)) + assert_raises(ValueError, dirichlet.pdf, x, alpha) + assert_raises(ValueError, dirichlet.logpdf, x, alpha) + dirichlet.pdf(x.T, alpha) + dirichlet.pdf(x.T[:-1], alpha) + dirichlet.logpdf(x.T, alpha) + dirichlet.logpdf(x.T[:-1], alpha) + + def test_alpha_with_zeros(self): + rng = np.random.default_rng(2846) + alpha = [1.0, 0.0, 3.0] + # don't pass invalid alpha to np.random.dirichlet + x = rng.dirichlet(np.maximum(1e-9, alpha), size=7).T + assert_raises(ValueError, dirichlet.pdf, x, alpha) + assert_raises(ValueError, dirichlet.logpdf, x, alpha) + + def test_alpha_with_negative_entries(self): + rng = np.random.default_rng(2846) + alpha = [1.0, -2.0, 3.0] + # don't pass invalid alpha to np.random.dirichlet + x = rng.dirichlet(np.maximum(1e-9, alpha), size=7).T + assert_raises(ValueError, dirichlet.pdf, x, alpha) + assert_raises(ValueError, dirichlet.logpdf, x, alpha) + + def test_data_with_zeros(self): + alpha = np.array([1.0, 2.0, 3.0, 4.0]) + x = np.array([0.1, 0.0, 0.2, 0.7]) + dirichlet.pdf(x, alpha) + dirichlet.logpdf(x, alpha) + alpha = np.array([1.0, 1.0, 1.0, 1.0]) + assert_almost_equal(dirichlet.pdf(x, alpha), 6) + assert_almost_equal(dirichlet.logpdf(x, alpha), np.log(6)) + + def test_data_with_zeros_and_small_alpha(self): + alpha = np.array([1.0, 0.5, 3.0, 4.0]) + x = np.array([0.1, 0.0, 0.2, 0.7]) + assert_raises(ValueError, dirichlet.pdf, x, alpha) + assert_raises(ValueError, dirichlet.logpdf, x, alpha) + + def test_data_with_negative_entries(self): + alpha = np.array([1.0, 2.0, 3.0, 4.0]) + x = np.array([0.1, -0.1, 0.3, 0.7]) + assert_raises(ValueError, dirichlet.pdf, x, alpha) + assert_raises(ValueError, dirichlet.logpdf, x, alpha) + + def test_data_with_too_large_entries(self): + alpha = np.array([1.0, 2.0, 3.0, 4.0]) + x = np.array([0.1, 1.1, 0.3, 0.7]) + assert_raises(ValueError, dirichlet.pdf, x, alpha) + assert_raises(ValueError, dirichlet.logpdf, x, alpha) + + def test_data_too_deep_c(self): + alpha = np.array([1.0, 2.0, 3.0]) + x = np.full((2, 7, 7), 1 / 14) + assert_raises(ValueError, dirichlet.pdf, x, alpha) + assert_raises(ValueError, dirichlet.logpdf, x, alpha) + + def test_alpha_too_deep(self): + alpha = np.array([[1.0, 2.0], [3.0, 4.0]]) + x = np.full((2, 2, 7), 1 / 4) + assert_raises(ValueError, dirichlet.pdf, x, alpha) + assert_raises(ValueError, dirichlet.logpdf, x, alpha) + + def test_alpha_correct_depth(self): + alpha = np.array([1.0, 2.0, 3.0]) + x = np.full((3, 7), 1 / 3) + dirichlet.pdf(x, alpha) + dirichlet.logpdf(x, alpha) + + def test_non_simplex_data(self): + alpha = np.array([1.0, 2.0, 3.0]) + x = np.full((3, 7), 1 / 2) + assert_raises(ValueError, dirichlet.pdf, x, alpha) + assert_raises(ValueError, dirichlet.logpdf, x, alpha) + + def test_data_vector_too_short(self): + alpha = np.array([1.0, 2.0, 3.0, 4.0]) + x = np.full((2, 7), 1 / 2) + assert_raises(ValueError, dirichlet.pdf, x, alpha) + assert_raises(ValueError, dirichlet.logpdf, x, alpha) + + def test_data_vector_too_long(self): + alpha = np.array([1.0, 2.0, 3.0, 4.0]) + x = np.full((5, 7), 1 / 5) + assert_raises(ValueError, dirichlet.pdf, x, alpha) + assert_raises(ValueError, dirichlet.logpdf, x, alpha) + + def test_mean_var_cov(self): + # Reference values calculated by hand and confirmed with Mathematica, e.g. + # `Covariance[DirichletDistribution[{ 1, 0.8, 0.2, 10^-300}]]` + alpha = np.array([1., 0.8, 0.2]) + d = dirichlet(alpha) + + expected_mean = [0.5, 0.4, 0.1] + expected_var = [1. / 12., 0.08, 0.03] + expected_cov = [ + [ 1. / 12, -1. / 15, -1. / 60], + [-1. / 15, 2. / 25, -1. / 75], + [-1. / 60, -1. / 75, 3. / 100], + ] + + assert_array_almost_equal(d.mean(), expected_mean) + assert_array_almost_equal(d.var(), expected_var) + assert_array_almost_equal(d.cov(), expected_cov) + + def test_scalar_values(self): + alpha = np.array([0.2]) + d = dirichlet(alpha) + + # For alpha of length 1, mean and var should be scalar instead of array + assert_equal(d.mean().ndim, 0) + assert_equal(d.var().ndim, 0) + + assert_equal(d.pdf([1.]).ndim, 0) + assert_equal(d.logpdf([1.]).ndim, 0) + + def test_K_and_K_minus_1_calls_equal(self): + # Test that calls with K and K-1 entries yield the same results. + rng = np.random.default_rng(2846) + + n = rng.integers(1, 32) + alpha = rng.uniform(10e-10, 100, n) + + d = dirichlet(alpha) + num_tests = 10 + for i in range(num_tests): + x = rng.uniform(10e-10, 100, n) + x /= np.sum(x) + assert_almost_equal(d.pdf(x[:-1]), d.pdf(x)) + + def test_multiple_entry_calls(self): + # Test that calls with multiple x vectors as matrix work + rng = np.random.default_rng(2846) + + n = rng.integers(1, 32) + alpha = rng.uniform(10e-10, 100, n) + d = dirichlet(alpha) + + num_tests = 10 + num_multiple = 5 + xm = None + for i in range(num_tests): + for m in range(num_multiple): + x = rng.uniform(10e-10, 100, n) + x /= np.sum(x) + if xm is not None: + xm = np.vstack((xm, x)) + else: + xm = x + rm = d.pdf(xm.T) + rs = None + for xs in xm: + r = d.pdf(xs) + if rs is not None: + rs = np.append(rs, r) + else: + rs = r + assert_array_almost_equal(rm, rs) + + def test_2D_dirichlet_is_beta(self): + rng = np.random.default_rng(2846) + + alpha = rng.uniform(10e-10, 100, 2) + d = dirichlet(alpha) + b = beta(alpha[0], alpha[1]) + + num_tests = 10 + for i in range(num_tests): + x = rng.uniform(10e-10, 100, 2) + x /= np.sum(x) + assert_almost_equal(b.pdf(x), d.pdf([x])) + + assert_almost_equal(b.mean(), d.mean()[0]) + assert_almost_equal(b.var(), d.var()[0]) + + +def test_multivariate_normal_dimensions_mismatch(): + # Regression test for GH #3493. Check that setting up a PDF with a mean of + # length M and a covariance matrix of size (N, N), where M != N, raises a + # ValueError with an informative error message. + mu = np.array([0.0, 0.0]) + sigma = np.array([[1.0]]) + + assert_raises(ValueError, multivariate_normal, mu, sigma) + + # A simple check that the right error message was passed along. Checking + # that the entire message is there, word for word, would be somewhat + # fragile, so we just check for the leading part. + try: + multivariate_normal(mu, sigma) + except ValueError as e: + msg = "Dimension mismatch" + assert_equal(str(e)[:len(msg)], msg) + + +class TestWishart: + def test_scale_dimensions(self): + # Test that we can call the Wishart with various scale dimensions + + # Test case: dim=1, scale=1 + true_scale = np.array(1, ndmin=2) + scales = [ + 1, # scalar + [1], # iterable + np.array(1), # 0-dim + np.r_[1], # 1-dim + np.array(1, ndmin=2) # 2-dim + ] + for scale in scales: + w = wishart(1, scale) + assert_equal(w.scale, true_scale) + assert_equal(w.scale.shape, true_scale.shape) + + # Test case: dim=2, scale=[[1,0] + # [0,2] + true_scale = np.array([[1,0], + [0,2]]) + scales = [ + [1,2], # iterable + np.r_[1,2], # 1-dim + np.array([[1,0], # 2-dim + [0,2]]) + ] + for scale in scales: + w = wishart(2, scale) + assert_equal(w.scale, true_scale) + assert_equal(w.scale.shape, true_scale.shape) + + # We cannot call with a df < dim - 1 + assert_raises(ValueError, wishart, 1, np.eye(2)) + + # But we can call with dim - 1 < df < dim + wishart(1.1, np.eye(2)) # no error + # see gh-5562 + + # We cannot call with a 3-dimension array + scale = np.array(1, ndmin=3) + assert_raises(ValueError, wishart, 1, scale) + + def test_quantile_dimensions(self): + # Test that we can call the Wishart rvs with various quantile dimensions + + # If dim == 1, consider x.shape = [1,1,1] + X = [ + 1, # scalar + [1], # iterable + np.array(1), # 0-dim + np.r_[1], # 1-dim + np.array(1, ndmin=2), # 2-dim + np.array([1], ndmin=3) # 3-dim + ] + + w = wishart(1,1) + density = w.pdf(np.array(1, ndmin=3)) + for x in X: + assert_equal(w.pdf(x), density) + + # If dim == 1, consider x.shape = [1,1,*] + X = [ + [1,2,3], # iterable + np.r_[1,2,3], # 1-dim + np.array([1,2,3], ndmin=3) # 3-dim + ] + + w = wishart(1,1) + density = w.pdf(np.array([1,2,3], ndmin=3)) + for x in X: + assert_equal(w.pdf(x), density) + + # If dim == 2, consider x.shape = [2,2,1] + # where x[:,:,*] = np.eye(1)*2 + X = [ + 2, # scalar + [2,2], # iterable + np.array(2), # 0-dim + np.r_[2,2], # 1-dim + np.array([[2,0], + [0,2]]), # 2-dim + np.array([[2,0], + [0,2]])[:,:,np.newaxis] # 3-dim + ] + + w = wishart(2,np.eye(2)) + density = w.pdf(np.array([[2,0], + [0,2]])[:,:,np.newaxis]) + for x in X: + assert_equal(w.pdf(x), density) + + def test_frozen(self): + # Test that the frozen and non-frozen Wishart gives the same answers + + # Construct an arbitrary positive definite scale matrix + dim = 4 + scale = np.diag(np.arange(dim)+1) + scale[np.tril_indices(dim, k=-1)] = np.arange(dim * (dim-1) // 2) + scale = np.dot(scale.T, scale) + + # Construct a collection of positive definite matrices to test the PDF + X = [] + for i in range(5): + x = np.diag(np.arange(dim)+(i+1)**2) + x[np.tril_indices(dim, k=-1)] = np.arange(dim * (dim-1) // 2) + x = np.dot(x.T, x) + X.append(x) + X = np.array(X).T + + # Construct a 1D and 2D set of parameters + parameters = [ + (10, 1, np.linspace(0.1, 10, 5)), # 1D case + (10, scale, X) + ] + + for (df, scale, x) in parameters: + w = wishart(df, scale) + assert_equal(w.var(), wishart.var(df, scale)) + assert_equal(w.mean(), wishart.mean(df, scale)) + assert_equal(w.mode(), wishart.mode(df, scale)) + assert_equal(w.entropy(), wishart.entropy(df, scale)) + assert_equal(w.pdf(x), wishart.pdf(x, df, scale)) + + def test_wishart_2D_rvs(self): + dim = 3 + df = 10 + + # Construct a simple non-diagonal positive definite matrix + scale = np.eye(dim) + scale[0,1] = 0.5 + scale[1,0] = 0.5 + + # Construct frozen Wishart random variables + w = wishart(df, scale) + + # Get the generated random variables from a known seed + rng = np.random.RandomState(248042) + w_rvs = wishart.rvs(df, scale, random_state=rng) + rng = np.random.RandomState(248042) + frozen_w_rvs = w.rvs(random_state=rng) + + # Manually calculate what it should be, based on the Bartlett (1933) + # decomposition of a Wishart into D A A' D', where D is the Cholesky + # factorization of the scale matrix and A is the lower triangular matrix + # with the square root of chi^2 variates on the diagonal and N(0,1) + # variates in the lower triangle. + rng = np.random.RandomState(248042) + covariances = rng.normal(size=3) + variances = np.r_[ + rng.chisquare(df), + rng.chisquare(df-1), + rng.chisquare(df-2), + ]**0.5 + + # Construct the lower-triangular A matrix + A = np.diag(variances) + A[np.tril_indices(dim, k=-1)] = covariances + + # Wishart random variate + D = np.linalg.cholesky(scale) + DA = D.dot(A) + manual_w_rvs = np.dot(DA, DA.T) + + # Test for equality + assert_allclose(w_rvs, manual_w_rvs) + assert_allclose(frozen_w_rvs, manual_w_rvs) + + def test_1D_is_chisquared(self): + # The 1-dimensional Wishart with an identity scale matrix is just a + # chi-squared distribution. + # Test variance, mean, entropy, pdf + # Kolgomorov-Smirnov test for rvs + rng = np.random.default_rng(482974) + + sn = 500 + dim = 1 + scale = np.eye(dim) + + df_range = np.arange(1, 10, 2, dtype=float) + X = np.linspace(0.1,10,num=10) + for df in df_range: + w = wishart(df, scale) + c = chi2(df) + + # Statistics + assert_allclose(w.var(), c.var()) + assert_allclose(w.mean(), c.mean()) + assert_allclose(w.entropy(), c.entropy()) + + # PDF + assert_allclose(w.pdf(X), c.pdf(X)) + + # rvs + rvs = w.rvs(size=sn, random_state=rng) + args = (df,) + alpha = 0.01 + check_distribution_rvs('chi2', args, alpha, rvs) + + def test_is_scaled_chisquared(self): + # The 2-dimensional Wishart with an arbitrary scale matrix can be + # transformed to a scaled chi-squared distribution. + # For :math:`S \sim W_p(V,n)` and :math:`\lambda \in \mathbb{R}^p` we have + # :math:`\lambda' S \lambda \sim \lambda' V \lambda \times \chi^2(n)` + rng = np.random.default_rng(482974) + + sn = 500 + df = 10 + dim = 4 + # Construct an arbitrary positive definite matrix + scale = np.diag(np.arange(4)+1) + scale[np.tril_indices(4, k=-1)] = np.arange(6) + scale = np.dot(scale.T, scale) + # Use :math:`\lambda = [1, \dots, 1]'` + lamda = np.ones((dim,1)) + sigma_lamda = lamda.T.dot(scale).dot(lamda).squeeze() + w = wishart(df, sigma_lamda) + c = chi2(df, scale=sigma_lamda) + + # Statistics + assert_allclose(w.var(), c.var()) + assert_allclose(w.mean(), c.mean()) + assert_allclose(w.entropy(), c.entropy()) + + # PDF + X = np.linspace(0.1,10,num=10) + assert_allclose(w.pdf(X), c.pdf(X)) + + # rvs + rvs = w.rvs(size=sn, random_state=rng) + args = (df,0,sigma_lamda) + alpha = 0.01 + check_distribution_rvs('chi2', args, alpha, rvs) + +class TestMultinomial: + def test_logpmf(self): + vals1 = multinomial.logpmf((3,4), 7, (0.3, 0.7)) + assert_allclose(vals1, -1.483270127243324, rtol=1e-8) + + vals2 = multinomial.logpmf([3, 4], 0, [.3, .7]) + assert vals2 == -np.inf + + vals3 = multinomial.logpmf([0, 0], 0, [.3, .7]) + assert vals3 == 0 + + vals4 = multinomial.logpmf([3, 4], 0, [-2, 3]) + assert_allclose(vals4, np.nan, rtol=1e-8) + + def test_reduces_binomial(self): + # test that the multinomial pmf reduces to the binomial pmf in the 2d + # case + val1 = multinomial.logpmf((3, 4), 7, (0.3, 0.7)) + val2 = binom.logpmf(3, 7, 0.3) + assert_allclose(val1, val2, rtol=1e-8) + + val1 = multinomial.pmf((6, 8), 14, (0.1, 0.9)) + val2 = binom.pmf(6, 14, 0.1) + assert_allclose(val1, val2, rtol=1e-8) + + def test_R(self): + # test against the values produced by this R code + # (https://stat.ethz.ch/R-manual/R-devel/library/stats/html/Multinom.html) + # X <- t(as.matrix(expand.grid(0:3, 0:3))); X <- X[, colSums(X) <= 3] + # X <- rbind(X, 3:3 - colSums(X)); dimnames(X) <- list(letters[1:3], NULL) + # X + # apply(X, 2, function(x) dmultinom(x, prob = c(1,2,5))) + + n, p = 3, [1./8, 2./8, 5./8] + r_vals = {(0, 0, 3): 0.244140625, (1, 0, 2): 0.146484375, + (2, 0, 1): 0.029296875, (3, 0, 0): 0.001953125, + (0, 1, 2): 0.292968750, (1, 1, 1): 0.117187500, + (2, 1, 0): 0.011718750, (0, 2, 1): 0.117187500, + (1, 2, 0): 0.023437500, (0, 3, 0): 0.015625000} + for x in r_vals: + assert_allclose(multinomial.pmf(x, n, p), r_vals[x], atol=1e-14) + + @pytest.mark.parametrize("n", [0, 3]) + def test_rvs_np(self, n): + # test that .rvs agrees w/numpy when `p` is valid + rndm = np.random.RandomState(123) + sc_rvs = multinomial.rvs(n, [1/3.]*3, size=7, random_state=123) + np_rvs = rndm.multinomial(n, [1/3.]*3, size=7) + assert_equal(sc_rvs, np_rvs) + + rndm = np.random.RandomState(123) + sc_rvs = multinomial.rvs(n, [1/5.]*5, size=7, random_state=123) + np_rvs = rndm.multinomial(n, [1/5.]*5, size=7) + assert_equal(sc_rvs, np_rvs) + + def test_pmf(self): + vals0 = multinomial.pmf((5,), 5, (1,)) + assert_allclose(vals0, 1, rtol=1e-8) + + vals1 = multinomial.pmf((3,4), 7, (.3, .7)) + assert_allclose(vals1, .22689449999999994, rtol=1e-8) + + vals2 = multinomial.pmf([[[3,5],[0,8]], [[-1, 9], [1, 1]]], 8, + (.1, .9)) + assert_allclose(vals2, [[.03306744, .43046721], [0, 0]], rtol=1e-8) + + x = np.empty((0,2), dtype=np.float64) + vals3 = multinomial.pmf(x, 4, (.3, .7)) + assert_equal(vals3, np.empty([], dtype=np.float64)) + + vals4 = multinomial.pmf([1,2], 4, (.3, .7)) + assert_allclose(vals4, 0, rtol=1e-8) + + vals5 = multinomial.pmf([3, 3, 0], 6, [2/3.0, 1/3.0, 0]) + assert_allclose(vals5, 0.219478737997, rtol=1e-8) + + vals5 = multinomial.pmf([0, 0, 0], 0, [2/3.0, 1/3.0, 0]) + assert vals5 == 1 + + vals6 = multinomial.pmf([2, 1, 0], 0, [2/3.0, 1/3.0, 0]) + assert vals6 == 0 + + def test_pmf_broadcasting(self): + vals0 = multinomial.pmf([1, 2], 3, [[.1, .9], [.2, .8]]) + assert_allclose(vals0, [.243, .384], rtol=1e-8) + + vals1 = multinomial.pmf([1, 2], [3, 4], [.1, .9]) + assert_allclose(vals1, [.243, 0], rtol=1e-8) + + vals2 = multinomial.pmf([[[1, 2], [1, 1]]], 3, [.1, .9]) + assert_allclose(vals2, [[.243, 0]], rtol=1e-8) + + vals3 = multinomial.pmf([1, 2], [[[3], [4]]], [.1, .9]) + assert_allclose(vals3, [[[.243], [0]]], rtol=1e-8) + + vals4 = multinomial.pmf([[1, 2], [1,1]], [[[[3]]]], [.1, .9]) + assert_allclose(vals4, [[[[.243, 0]]]], rtol=1e-8) + + @pytest.mark.parametrize("n", [0, 5]) + def test_cov(self, n): + cov1 = multinomial.cov(n, (.2, .3, .5)) + cov2 = [[n*.2*.8, -n*.2*.3, -n*.2*.5], + [-n*.3*.2, n*.3*.7, -n*.3*.5], + [-n*.5*.2, -n*.5*.3, n*.5*.5]] + assert_allclose(cov1, cov2, rtol=1e-8) + + def test_cov_broadcasting(self): + cov1 = multinomial.cov(5, [[.1, .9], [.2, .8]]) + cov2 = [[[.45, -.45],[-.45, .45]], [[.8, -.8], [-.8, .8]]] + assert_allclose(cov1, cov2, rtol=1e-8) + + cov3 = multinomial.cov([4, 5], [.1, .9]) + cov4 = [[[.36, -.36], [-.36, .36]], [[.45, -.45], [-.45, .45]]] + assert_allclose(cov3, cov4, rtol=1e-8) + + cov5 = multinomial.cov([4, 5], [[.3, .7], [.4, .6]]) + cov6 = [[[4*.3*.7, -4*.3*.7], [-4*.3*.7, 4*.3*.7]], + [[5*.4*.6, -5*.4*.6], [-5*.4*.6, 5*.4*.6]]] + assert_allclose(cov5, cov6, rtol=1e-8) + + @pytest.mark.parametrize("n", [0, 2]) + def test_entropy(self, n): + # this is equivalent to a binomial distribution with n=2, so the + # entropy .77899774929 is easily computed "by hand" + ent0 = multinomial.entropy(n, [.2, .8]) + assert_allclose(ent0, binom.entropy(n, .2), rtol=1e-8) + + def test_entropy_broadcasting(self): + ent0 = multinomial.entropy([2, 3], [.2, .8]) + assert_allclose(ent0, [binom.entropy(2, .2), binom.entropy(3, .2)], + rtol=1e-8) + + ent1 = multinomial.entropy([7, 8], [[.3, .7], [.4, .6]]) + assert_allclose(ent1, [binom.entropy(7, .3), binom.entropy(8, .4)], + rtol=1e-8) + + ent2 = multinomial.entropy([[7], [8]], [[.3, .7], [.4, .6]]) + assert_allclose(ent2, + [[binom.entropy(7, .3), binom.entropy(7, .4)], + [binom.entropy(8, .3), binom.entropy(8, .4)]], + rtol=1e-8) + + @pytest.mark.parametrize("n", [0, 5]) + def test_mean(self, n): + mean1 = multinomial.mean(n, [.2, .8]) + assert_allclose(mean1, [n*.2, n*.8], rtol=1e-8) + + def test_mean_broadcasting(self): + mean1 = multinomial.mean([5, 6], [.2, .8]) + assert_allclose(mean1, [[5*.2, 5*.8], [6*.2, 6*.8]], rtol=1e-8) + + def test_frozen(self): + # The frozen distribution should agree with the regular one + n = 12 + pvals = (.1, .2, .3, .4) + x = [[0,0,0,12],[0,0,1,11],[0,1,1,10],[1,1,1,9],[1,1,2,8]] + x = np.asarray(x, dtype=np.float64) + mn_frozen = multinomial(n, pvals) + assert_allclose(mn_frozen.pmf(x), multinomial.pmf(x, n, pvals)) + assert_allclose(mn_frozen.logpmf(x), multinomial.logpmf(x, n, pvals)) + assert_allclose(mn_frozen.entropy(), multinomial.entropy(n, pvals)) + + def test_gh_11860(self): + # gh-11860 reported cases in which the adjustments made by multinomial + # to the last element of `p` can cause `nan`s even when the input is + # essentially valid. Check that a pathological case returns a finite, + # nonzero result. (This would fail in main before the PR.) + n = 88 + rng = np.random.default_rng(8879715917488330089) + p = rng.random(n) + p[-1] = 1e-30 + p /= np.sum(p) + x = np.ones(n) + logpmf = multinomial.logpmf(x, n, p) + assert np.isfinite(logpmf) + + @pytest.mark.parametrize('dtype', [np.float32, np.float64]) + def test_gh_22565(self, dtype): + # Same issue as gh-11860 above, essentially, but the original + # fix didn't completely solve the problem. + n = 19 + p = np.asarray([0.2, 0.2, 0.2, 0.2, 0.2], dtype=dtype) + res1 = multinomial.pmf(x=[1, 2, 5, 7, 4], n=n, p=p) + res2 = multinomial.pmf(x=[1, 2, 4, 5, 7], n=n, p=p) + np.testing.assert_allclose(res1, res2, rtol=1e-15) + + @pytest.mark.parametrize('psum', [0.75, 1.25]) + def test_gh_22585(self, psum): + # gh-22585 warned that beginning in SciPy 1.18, rows of p that did not sum to + # 1.0 would produce NaNs. Check that this has been implemented. + rng = np.random.default_rng(8879715917488330089) + d, n = 5, 19 # arbitrary + p0 = rng.random(d) + p = p0 / np.sum(p0) + p_ = p * psum + x = multinomial.rvs(n=n, p=p) + + assert np.all(np.isfinite(x)) + with pytest.raises(ValueError, match="`multinomial.rvs` requires..."): + multinomial.rvs(n=n, p=p_) + + assert np.isfinite(multinomial.pmf(x, n=n, p=p)) + assert np.isnan(multinomial.pmf(x, n=n, p=p_)) + + assert np.isfinite(multinomial.pmf(x, n=n, p=p)) + assert np.isnan(multinomial.pmf(x, n=n, p=p_)) + + assert np.isfinite(multinomial.entropy(n=n, p=p)) + assert np.isnan(multinomial.entropy(n=n, p=p_)) + + assert np.all(np.isfinite(multinomial.cov(n=n, p=p))) + assert np.all(np.isnan(multinomial.cov(n=n, p=p_))) + + +class TestInvwishart: + def test_frozen(self): + # Test that the frozen and non-frozen inverse Wishart gives the same + # answers + + # Construct an arbitrary positive definite scale matrix + dim = 4 + scale = np.diag(np.arange(dim)+1) + scale[np.tril_indices(dim, k=-1)] = np.arange(dim*(dim-1)/2) + scale = np.dot(scale.T, scale) + + # Construct a collection of positive definite matrices to test the PDF + X = [] + for i in range(5): + x = np.diag(np.arange(dim)+(i+1)**2) + x[np.tril_indices(dim, k=-1)] = np.arange(dim*(dim-1)/2) + x = np.dot(x.T, x) + X.append(x) + X = np.array(X).T + + # Construct a 1D and 2D set of parameters + parameters = [ + (10, 1, np.linspace(0.1, 10, 5)), # 1D case + (10, scale, X) + ] + + for (df, scale, x) in parameters: + iw = invwishart(df, scale) + assert_equal(iw.var(), invwishart.var(df, scale)) + assert_equal(iw.mean(), invwishart.mean(df, scale)) + assert_equal(iw.mode(), invwishart.mode(df, scale)) + assert_allclose(iw.pdf(x), invwishart.pdf(x, df, scale)) + + def test_1D_is_invgamma(self): + # The 1-dimensional inverse Wishart with an identity scale matrix is + # just an inverse gamma distribution. + # Test variance, mean, pdf, entropy + # Kolgomorov-Smirnov test for rvs + rng = np.random.RandomState(482974) + + sn = 500 + dim = 1 + scale = np.eye(dim) + + df_range = np.arange(5, 20, 2, dtype=float) + X = np.linspace(0.1,10,num=10) + for df in df_range: + iw = invwishart(df, scale) + ig = invgamma(df/2, scale=1./2) + + # Statistics + assert_allclose(iw.var(), ig.var()) + assert_allclose(iw.mean(), ig.mean()) + + # PDF + assert_allclose(iw.pdf(X), ig.pdf(X)) + + # rvs + rvs = iw.rvs(size=sn, random_state=rng) + args = (df/2, 0, 1./2) + alpha = 0.01 + check_distribution_rvs('invgamma', args, alpha, rvs) + + # entropy + assert_allclose(iw.entropy(), ig.entropy()) + + def test_invwishart_2D_rvs(self): + dim = 3 + df = 10 + + # Construct a simple non-diagonal positive definite matrix + scale = np.eye(dim) + scale[0,1] = 0.5 + scale[1,0] = 0.5 + + # Construct frozen inverse-Wishart random variables + iw = invwishart(df, scale) + + # Get the generated random variables from a known seed + rng = np.random.RandomState(608072) + iw_rvs = invwishart.rvs(df, scale, random_state=rng) + rng = np.random.RandomState(608072) + frozen_iw_rvs = iw.rvs(random_state=rng) + + # Manually calculate what it should be, based on the decomposition in + # https://arxiv.org/abs/2310.15884 of an invers-Wishart into L L', + # where L A = D, D is the Cholesky factorization of the scale matrix, + # and A is the lower triangular matrix with the square root of chi^2 + # variates on the diagonal and N(0,1) variates in the lower triangle. + # the diagonal chi^2 variates in this A are reversed compared to those + # in the Bartlett decomposition A for Wishart rvs. + rng = np.random.RandomState(608072) + covariances = rng.normal(size=3) + variances = np.r_[ + rng.chisquare(df-2), + rng.chisquare(df-1), + rng.chisquare(df), + ]**0.5 + + # Construct the lower-triangular A matrix + A = np.diag(variances) + A[np.tril_indices(dim, k=-1)] = covariances + + # inverse-Wishart random variate + D = np.linalg.cholesky(scale) + L = np.linalg.solve(A.T, D.T).T + manual_iw_rvs = np.dot(L, L.T) + + # Test for equality + assert_allclose(iw_rvs, manual_iw_rvs) + assert_allclose(frozen_iw_rvs, manual_iw_rvs) + + def test_sample_mean(self): + """Test that sample mean consistent with known mean.""" + # Construct an arbitrary positive definite scale matrix + df = 10 + sample_size = 20_000 + for dim in [1, 5]: + scale = np.diag(np.arange(dim) + 1) + scale[np.tril_indices(dim, k=-1)] = np.arange(dim * (dim - 1) / 2) + scale = np.dot(scale.T, scale) + + dist = invwishart(df, scale) + Xmean_exp = dist.mean() + Xvar_exp = dist.var() + Xmean_std = (Xvar_exp / sample_size)**0.5 # asymptotic SE of mean estimate + + X = dist.rvs(size=sample_size, random_state=1234) + Xmean_est = X.mean(axis=0) + + ntests = dim*(dim + 1)//2 + fail_rate = 0.01 / ntests # correct for multiple tests + max_diff = norm.ppf(1 - fail_rate / 2) + assert np.allclose( + (Xmean_est - Xmean_exp) / Xmean_std, + 0, + atol=max_diff, + ) + + def test_logpdf_4x4(self): + """Regression test for gh-8844.""" + X = np.array([[2, 1, 0, 0.5], + [1, 2, 0.5, 0.5], + [0, 0.5, 3, 1], + [0.5, 0.5, 1, 2]]) + Psi = np.array([[9, 7, 3, 1], + [7, 9, 5, 1], + [3, 5, 8, 2], + [1, 1, 2, 9]]) + nu = 6 + prob = invwishart.logpdf(X, nu, Psi) + # Explicit calculation from the formula on wikipedia. + p = X.shape[0] + sig, logdetX = np.linalg.slogdet(X) + sig, logdetPsi = np.linalg.slogdet(Psi) + M = np.linalg.solve(X, Psi) + expected = ((nu/2)*logdetPsi + - (nu*p/2)*np.log(2) + - multigammaln(nu/2, p) + - (nu + p + 1)/2*logdetX + - 0.5*M.trace()) + assert_allclose(prob, expected) + + +class TestSpecialOrthoGroup: + def test_reproducibility(self): + x = special_ortho_group.rvs(3, random_state=np.random.default_rng(514)) + expected = np.array([[-0.93200988, 0.01533561, -0.36210826], + [0.35742128, 0.20446501, -0.91128705], + [0.06006333, -0.97875374, -0.19604469]]) + assert_array_almost_equal(x, expected) + + def test_invalid_dim(self): + assert_raises(ValueError, special_ortho_group.rvs, None) + assert_raises(ValueError, special_ortho_group.rvs, (2, 2)) + assert_raises(ValueError, special_ortho_group.rvs, -1) + assert_raises(ValueError, special_ortho_group.rvs, 2.5) + + def test_frozen_matrix(self): + dim = 7 + frozen = special_ortho_group(dim) + + rvs1 = frozen.rvs(random_state=1234) + rvs2 = special_ortho_group.rvs(dim, random_state=1234) + + assert_equal(rvs1, rvs2) + + def test_det_and_ortho(self): + xs = [special_ortho_group.rvs(dim) + for dim in range(2,12) + for i in range(3)] + + # Test that determinants are always +1 + dets = [np.linalg.det(x) for x in xs] + assert_allclose(dets, [1.]*30, rtol=1e-13) + + # Test that these are orthogonal matrices + for x in xs: + assert_array_almost_equal(np.dot(x, x.T), + np.eye(x.shape[0])) + + def test_haar(self): + # Test that the distribution is constant under rotation + # Every column should have the same distribution + # Additionally, the distribution should be invariant under another rotation + + # Generate samples + dim = 5 + samples = 1000 # Not too many, or the test takes too long + ks_prob = .05 + xs = special_ortho_group.rvs( + dim, size=samples, random_state=np.random.default_rng(513) + ) + + # Dot a few rows (0, 1, 2) with unit vectors (0, 2, 4, 3), + # effectively picking off entries in the matrices of xs. + # These projections should all have the same distribution, + # establishing rotational invariance. We use the two-sided + # KS test to confirm this. + # We could instead test that angles between random vectors + # are uniformly distributed, but the below is sufficient. + # It is not feasible to consider all pairs, so pick a few. + els = ((0,0), (0,2), (1,4), (2,3)) + #proj = {(er, ec): [x[er][ec] for x in xs] for er, ec in els} + proj = {(er, ec): sorted([x[er][ec] for x in xs]) for er, ec in els} + pairs = [(e0, e1) for e0 in els for e1 in els if e0 > e1] + ks_tests = [ks_2samp(proj[p0], proj[p1])[1] for (p0, p1) in pairs] + assert_array_less([ks_prob]*len(pairs), ks_tests) + + def test_one_by_one(self): + # Test that the distribution is a delta function at the identity matrix + # when dim=1 + assert_allclose(special_ortho_group.rvs(1, size=1000), 1, rtol=1e-13) + + def test_zero_by_zero(self): + assert_equal(special_ortho_group.rvs(0, size=4).shape, (4, 0, 0)) + + +class TestOrthoGroup: + def test_reproducibility(self): + seed = 514 + rng = np.random.RandomState(seed) + x = ortho_group.rvs(3, random_state=rng) + x2 = ortho_group.rvs(3, random_state=seed) + # Note this matrix has det -1, distinguishing O(N) from SO(N) + assert_almost_equal(np.linalg.det(x), -1) + expected = np.array([[0.381686, -0.090374, 0.919863], + [0.905794, -0.161537, -0.391718], + [-0.183993, -0.98272, -0.020204]]) + assert_array_almost_equal(x, expected) + assert_array_almost_equal(x2, expected) + + def test_invalid_dim(self): + assert_raises(ValueError, ortho_group.rvs, None) + assert_raises(ValueError, ortho_group.rvs, (2, 2)) + assert_raises(ValueError, ortho_group.rvs, -1) + assert_raises(ValueError, ortho_group.rvs, 2.5) + + def test_frozen_matrix(self): + dim = 7 + frozen = ortho_group(dim) + frozen_seed = ortho_group(dim, seed=1234) + + rvs1 = frozen.rvs(random_state=1234) + rvs2 = ortho_group.rvs(dim, random_state=1234) + rvs3 = frozen_seed.rvs(size=1) + + assert_equal(rvs1, rvs2) + assert_equal(rvs1, rvs3) + + def test_det_and_ortho(self): + xs = [[ortho_group.rvs(dim) + for i in range(10)] + for dim in range(2,12)] + + # Test that abs determinants are always +1 + dets = np.array([[np.linalg.det(x) for x in xx] for xx in xs]) + assert_allclose(np.fabs(dets), np.ones(dets.shape), rtol=1e-13) + + # Test that these are orthogonal matrices + for xx in xs: + for x in xx: + assert_array_almost_equal(np.dot(x, x.T), + np.eye(x.shape[0])) + + @pytest.mark.parametrize("dim", [2, 5, 10, 20]) + def test_det_distribution_gh18272(self, dim): + # Test that positive and negative determinants are equally likely. + rng = np.random.default_rng(6796248956179332344) + dist = ortho_group(dim=dim) + rvs = dist.rvs(size=5000, random_state=rng) + dets = scipy.linalg.det(rvs) + k = np.sum(dets > 0) + n = len(dets) + res = stats.binomtest(k, n) + low, high = res.proportion_ci(confidence_level=0.95) + assert low < 0.5 < high + + def test_haar(self): + # Test that the distribution is constant under rotation + # Every column should have the same distribution + # Additionally, the distribution should be invariant under another rotation + + # Generate samples + dim = 5 + samples = 1000 # Not too many, or the test takes too long + ks_prob = .05 + rng = np.random.RandomState(518) # Note that the test is sensitive to seed too + xs = ortho_group.rvs(dim, size=samples, random_state=rng) + + # Dot a few rows (0, 1, 2) with unit vectors (0, 2, 4, 3), + # effectively picking off entries in the matrices of xs. + # These projections should all have the same distribution, + # establishing rotational invariance. We use the two-sided + # KS test to confirm this. + # We could instead test that angles between random vectors + # are uniformly distributed, but the below is sufficient. + # It is not feasible to consider all pairs, so pick a few. + els = ((0,0), (0,2), (1,4), (2,3)) + #proj = {(er, ec): [x[er][ec] for x in xs] for er, ec in els} + proj = {(er, ec): sorted([x[er][ec] for x in xs]) for er, ec in els} + pairs = [(e0, e1) for e0 in els for e1 in els if e0 > e1] + ks_tests = [ks_2samp(proj[p0], proj[p1])[1] for (p0, p1) in pairs] + assert_array_less([ks_prob]*len(pairs), ks_tests) + + def test_one_by_one(self): + # Test that the 1x1 distribution gives ±1 with equal probability. + dim = 1 + xs = ortho_group.rvs(dim, size=5000, random_state=np.random.default_rng(514)) + assert_allclose(np.abs(xs), 1, rtol=1e-13) + k = np.sum(xs > 0) + n = len(xs) + res = stats.binomtest(k, n) + low, high = res.proportion_ci(confidence_level=0.95) + assert low < 0.5 < high + + def test_zero_by_zero(self): + assert_equal(special_ortho_group.rvs(0, size=4).shape, (4, 0, 0)) + + @pytest.mark.slow + def test_pairwise_distances(self): + # Test that the distribution of pairwise distances is close to correct. + rng = np.random.RandomState(514) + + def random_ortho(dim, random_state): + u, _s, v = np.linalg.svd(random_state.normal(size=(dim, dim))) + return np.dot(u, v) + + for dim in range(2, 6): + def generate_test_statistics(rvs, N=1000, eps=1e-10): + stats = np.array([ + np.sum((rvs(dim=dim, random_state=rng) - + rvs(dim=dim, random_state=rng))**2) + for _ in range(N) + ]) + # Add a bit of noise to account for numeric accuracy. + stats += rng.uniform(-eps, eps, size=stats.shape) + return stats + + expected = generate_test_statistics(random_ortho) + actual = generate_test_statistics(scipy.stats.ortho_group.rvs) + + _D, p = scipy.stats.ks_2samp(expected, actual) + + assert_array_less(.05, p) + + +class TestRandomCorrelation: + def test_reproducibility(self): + rng = np.random.RandomState(514) + eigs = (.5, .8, 1.2, 1.5) + x = random_correlation.rvs(eigs, random_state=rng) + x2 = random_correlation.rvs(eigs, random_state=514) + expected = np.array([[1., -0.184851, 0.109017, -0.227494], + [-0.184851, 1., 0.231236, 0.326669], + [0.109017, 0.231236, 1., -0.178912], + [-0.227494, 0.326669, -0.178912, 1.]]) + assert_array_almost_equal(x, expected) + assert_array_almost_equal(x2, expected) + + def test_invalid_eigs(self): + assert_raises(ValueError, random_correlation.rvs, None) + assert_raises(ValueError, random_correlation.rvs, 'test') + assert_raises(ValueError, random_correlation.rvs, 2.5) + assert_raises(ValueError, random_correlation.rvs, [2.5]) + assert_raises(ValueError, random_correlation.rvs, [[1,2],[3,4]]) + assert_raises(ValueError, random_correlation.rvs, [2.5, -.5]) + assert_raises(ValueError, random_correlation.rvs, [1, 2, .1]) + + def test_frozen_matrix(self): + eigs = (.5, .8, 1.2, 1.5) + frozen = random_correlation(eigs) + frozen_seed = random_correlation(eigs, seed=514) + + rvs1 = random_correlation.rvs(eigs, random_state=514) + rvs2 = frozen.rvs(random_state=514) + rvs3 = frozen_seed.rvs() + + assert_equal(rvs1, rvs2) + assert_equal(rvs1, rvs3) + + def test_definition(self): + # Test the definition of a correlation matrix in several dimensions: + # + # 1. Det is product of eigenvalues (and positive by construction + # in examples) + # 2. 1's on diagonal + # 3. Matrix is symmetric + + def norm(i, e): + return i*e/sum(e) + + rng = np.random.RandomState(123) + + eigs = [norm(i, rng.uniform(size=i)) for i in range(2, 6)] + eigs.append([4,0,0,0]) + + ones = [[1.]*len(e) for e in eigs] + xs = [random_correlation.rvs(e, random_state=rng) for e in eigs] + + # Test that determinants are products of eigenvalues + # These are positive by construction + # Could also test that the eigenvalues themselves are correct, + # but this seems sufficient. + dets = [np.fabs(np.linalg.det(x)) for x in xs] + dets_known = [np.prod(e) for e in eigs] + assert_allclose(dets, dets_known, rtol=1e-13, atol=1e-13) + + # Test for 1's on the diagonal + diags = [np.diag(x) for x in xs] + for a, b in zip(diags, ones): + assert_allclose(a, b, rtol=1e-13) + + # Correlation matrices are symmetric + for x in xs: + assert_allclose(x, x.T, rtol=1e-13) + + def test_to_corr(self): + # Check some corner cases in to_corr + + # ajj == 1 + m = np.array([[0.1, 0], [0, 1]], dtype=float) + m = random_correlation._to_corr(m) + assert_allclose(m, np.array([[1, 0], [0, 0.1]])) + + # Floating point overflow; fails to compute the correct + # rotation, but should still produce some valid rotation + # rather than infs/nans + with np.errstate(over='ignore'): + g = np.array([[0, 1], [-1, 0]]) + + m0 = np.array([[1e300, 0], [0, np.nextafter(1, 0)]], dtype=float) + m = random_correlation._to_corr(m0.copy()) + assert_allclose(m, g.T.dot(m0).dot(g)) + + m0 = np.array([[0.9, 1e300], [1e300, 1.1]], dtype=float) + m = random_correlation._to_corr(m0.copy()) + assert_allclose(m, g.T.dot(m0).dot(g)) + + # Zero discriminant; should set the first diag entry to 1 + m0 = np.array([[2, 1], [1, 2]], dtype=float) + m = random_correlation._to_corr(m0.copy()) + assert_allclose(m[0,0], 1) + + # Slightly negative discriminant; should be approx correct still + m0 = np.array([[2 + 1e-7, 1], [1, 2]], dtype=float) + m = random_correlation._to_corr(m0.copy()) + assert_allclose(m[0,0], 1) + + +class TestUniformDirection: + @pytest.mark.parametrize("dim", [1, 3]) + @pytest.mark.parametrize("size", [None, 1, 5, (5, 4)]) + def test_samples(self, dim, size): + # test that samples have correct shape and norm 1 + rng = np.random.default_rng(2777937887058094419) + uniform_direction_dist = uniform_direction(dim, seed=rng) + samples = uniform_direction_dist.rvs(size) + mean, cov = np.zeros(dim), np.eye(dim) + expected_shape = rng.multivariate_normal(mean, cov, size=size).shape + assert samples.shape == expected_shape + norms = np.linalg.norm(samples, axis=-1) + assert_allclose(norms, 1.) + + @pytest.mark.parametrize("dim", [None, 0, (2, 2), 2.5]) + def test_invalid_dim(self, dim): + message = ("Dimension of vector must be specified, " + "and must be an integer greater than 0.") + with pytest.raises(ValueError, match=message): + uniform_direction.rvs(dim) + + def test_frozen_distribution(self): + dim = 5 + frozen = uniform_direction(dim) + frozen_seed = uniform_direction(dim, seed=514) + + rvs1 = frozen.rvs(random_state=514) + rvs2 = uniform_direction.rvs(dim, random_state=514) + rvs3 = frozen_seed.rvs() + + assert_equal(rvs1, rvs2) + assert_equal(rvs1, rvs3) + + @pytest.mark.parametrize("dim", [2, 5, 8]) + def test_uniform(self, dim): + rng = np.random.default_rng(1036978481269651776) + spherical_dist = uniform_direction(dim, seed=rng) + # generate random, orthogonal vectors + v1, v2 = spherical_dist.rvs(size=2) + v2 -= v1 @ v2 * v1 + v2 /= np.linalg.norm(v2) + assert_allclose(v1 @ v2, 0, atol=1e-14) # orthogonal + # generate data and project onto orthogonal vectors + samples = spherical_dist.rvs(size=10000) + s1 = samples @ v1 + s2 = samples @ v2 + angles = np.arctan2(s1, s2) + # test that angles follow a uniform distribution + # normalize angles to range [0, 1] + angles += np.pi + angles /= 2*np.pi + # perform KS test + uniform_dist = uniform() + kstest_result = kstest(angles, uniform_dist.cdf) + assert kstest_result.pvalue > 0.05 + + +class TestUnitaryGroup: + def test_reproducibility(self): + rng = np.random.RandomState(514) + x = unitary_group.rvs(3, random_state=rng) + x2 = unitary_group.rvs(3, random_state=514) + + expected = np.array( + [[0.308771+0.360312j, 0.044021+0.622082j, 0.160327+0.600173j], + [0.732757+0.297107j, 0.076692-0.4614j, -0.394349+0.022613j], + [-0.148844+0.357037j, -0.284602-0.557949j, 0.607051+0.299257j]] + ) + + assert_array_almost_equal(x, expected) + assert_array_almost_equal(x2, expected) + + def test_invalid_dim(self): + assert_raises(ValueError, unitary_group.rvs, None) + assert_raises(ValueError, unitary_group.rvs, (2, 2)) + assert_raises(ValueError, unitary_group.rvs, -1) + assert_raises(ValueError, unitary_group.rvs, 2.5) + + def test_frozen_matrix(self): + dim = 7 + frozen = unitary_group(dim) + frozen_seed = unitary_group(dim, seed=514) + + rvs1 = frozen.rvs(random_state=514) + rvs2 = unitary_group.rvs(dim, random_state=514) + rvs3 = frozen_seed.rvs(size=1) + + assert_equal(rvs1, rvs2) + assert_equal(rvs1, rvs3) + + def test_unitarity(self): + xs = [unitary_group.rvs(dim) + for dim in range(2,12) + for i in range(3)] + + # Test that these are unitary matrices + for x in xs: + assert_allclose(np.dot(x, x.conj().T), np.eye(x.shape[0]), atol=1e-15) + + def test_haar(self): + # Test that the eigenvalues, which lie on the unit circle in + # the complex plane, are uncorrelated. + + # Generate samples + for dim in (1, 5): + samples = 1000 # Not too many, or the test takes too long + # Note that the test is sensitive to seed too + xs = unitary_group.rvs( + dim, size=samples, random_state=np.random.default_rng(514) + ) + + # The angles "x" of the eigenvalues should be uniformly distributed + # Overall this seems to be a necessary but weak test of the distribution. + eigs = np.vstack([scipy.linalg.eigvals(x) for x in xs]) + x = np.arctan2(eigs.imag, eigs.real) + res = kstest(x.ravel(), uniform(-np.pi, 2*np.pi).cdf) + assert_(res.pvalue > 0.05) + + def test_zero_by_zero(self): + assert_equal(unitary_group.rvs(0, size=4).shape, (4, 0, 0)) + + +class TestMultivariateT: + + # These tests were created by running vpa(mvtpdf(...)) in MATLAB. The + # function takes no `mu` parameter. The tests were run as + # + # >> ans = vpa(mvtpdf(x - mu, shape, df)); + # + PDF_TESTS = [( + # x + [ + [1, 2], + [4, 1], + [2, 1], + [2, 4], + [1, 4], + [4, 1], + [3, 2], + [3, 3], + [4, 4], + [5, 1], + ], + # loc + [0, 0], + # shape + [ + [1, 0], + [0, 1] + ], + # df + 4, + # ans + [ + 0.013972450422333741737457302178882, + 0.0010998721906793330026219646100571, + 0.013972450422333741737457302178882, + 0.00073682844024025606101402363634634, + 0.0010998721906793330026219646100571, + 0.0010998721906793330026219646100571, + 0.0020732579600816823488240725481546, + 0.00095660371505271429414668515889275, + 0.00021831953784896498569831346792114, + 0.00037725616140301147447000396084604 + ] + + ), ( + # x + [ + [0.9718, 0.1298, 0.8134], + [0.4922, 0.5522, 0.7185], + [0.3010, 0.1491, 0.5008], + [0.5971, 0.2585, 0.8940], + [0.5434, 0.5287, 0.9507], + ], + # loc + [-1, 1, 50], + # shape + [ + [1.0000, 0.5000, 0.2500], + [0.5000, 1.0000, -0.1000], + [0.2500, -0.1000, 1.0000], + ], + # df + 8, + # ans + [ + 0.00000000000000069609279697467772867405511133763, + 0.00000000000000073700739052207366474839369535934, + 0.00000000000000069522909962669171512174435447027, + 0.00000000000000074212293557998314091880208889767, + 0.00000000000000077039675154022118593323030449058, + ] + )] + + @pytest.mark.parametrize("x, loc, shape, df, ans", PDF_TESTS) + def test_pdf_correctness(self, x, loc, shape, df, ans): + dist = multivariate_t(loc, shape, df, seed=0) + val = dist.pdf(x) + assert_array_almost_equal(val, ans) + + @pytest.mark.parametrize("x, loc, shape, df, ans", PDF_TESTS) + def test_logpdf_correct(self, x, loc, shape, df, ans): + dist = multivariate_t(loc, shape, df, seed=0) + val1 = dist.pdf(x) + val2 = dist.logpdf(x) + assert_array_almost_equal(np.log(val1), val2) + + # https://github.com/scipy/scipy/issues/10042#issuecomment-576795195 + def test_mvt_with_df_one_is_cauchy(self): + x = [9, 7, 4, 1, -3, 9, 0, -3, -1, 3] + val = multivariate_t.pdf(x, df=1) + ans = cauchy.pdf(x) + assert_array_almost_equal(val, ans) + + def test_mvt_with_high_df_is_approx_normal(self): + # `normaltest` returns the chi-squared statistic and the associated + # p-value. The null hypothesis is that `x` came from a normal + # distribution, so a low p-value represents rejecting the null, i.e. + # that it is unlikely that `x` came a normal distribution. + P_VAL_MIN = 0.1 + + dist = multivariate_t(0, 1, df=100000, seed=1) + samples = dist.rvs(size=100000) + _, p = normaltest(samples) + assert (p > P_VAL_MIN) + + dist = multivariate_t([-2, 3], [[10, -1], [-1, 10]], df=100000, + seed=42) + samples = dist.rvs(size=100000) + _, p = normaltest(samples) + assert ((p > P_VAL_MIN).all()) + + @pytest.mark.thread_unsafe(reason="uses mocking") + @patch('scipy.stats.multivariate_normal._logpdf') + def test_mvt_with_inf_df_calls_normal(self, mock): + dist = multivariate_t(0, 1, df=np.inf, seed=7) + assert isinstance(dist, multivariate_normal_frozen) + multivariate_t.pdf(0, df=np.inf) + assert mock.call_count == 1 + multivariate_t.logpdf(0, df=np.inf) + assert mock.call_count == 2 + + def test_shape_correctness(self): + # pdf and logpdf should return scalar when the + # number of samples in x is one. + dim = 4 + loc = np.zeros(dim) + shape = np.eye(dim) + df = 4.5 + x = np.zeros(dim) + res = multivariate_t(loc, shape, df).pdf(x) + assert np.isscalar(res) + res = multivariate_t(loc, shape, df).logpdf(x) + assert np.isscalar(res) + + # pdf() and logpdf() should return probabilities of shape + # (n_samples,) when x has n_samples. + n_samples = 7 + rng = np.random.default_rng(2767231913) + x = rng.random((n_samples, dim)) + res = multivariate_t(loc, shape, df).pdf(x) + assert (res.shape == (n_samples,)) + res = multivariate_t(loc, shape, df).logpdf(x) + assert (res.shape == (n_samples,)) + + # rvs() should return scalar unless a size argument is applied. + res = multivariate_t(np.zeros(1), np.eye(1), 1).rvs() + assert np.isscalar(res) + + # rvs() should return vector of shape (size,) if size argument + # is applied. + size = 7 + res = multivariate_t(np.zeros(1), np.eye(1), 1).rvs(size=size) + assert (res.shape == (size,)) + + def test_default_arguments(self): + dist = multivariate_t() + assert_equal(dist.loc, [0]) + assert_equal(dist.shape, [[1]]) + assert (dist.df == 1) + + DEFAULT_ARGS_TESTS = [ + (None, None, None, 0, 1, 1), + (None, None, 7, 0, 1, 7), + (None, [[7, 0], [0, 7]], None, [0, 0], [[7, 0], [0, 7]], 1), + (None, [[7, 0], [0, 7]], 7, [0, 0], [[7, 0], [0, 7]], 7), + ([7, 7], None, None, [7, 7], [[1, 0], [0, 1]], 1), + ([7, 7], None, 7, [7, 7], [[1, 0], [0, 1]], 7), + ([7, 7], [[7, 0], [0, 7]], None, [7, 7], [[7, 0], [0, 7]], 1), + ([7, 7], [[7, 0], [0, 7]], 7, [7, 7], [[7, 0], [0, 7]], 7) + ] + + @pytest.mark.parametrize("loc, shape, df, loc_ans, shape_ans, df_ans", + DEFAULT_ARGS_TESTS) + def test_default_args(self, loc, shape, df, loc_ans, shape_ans, df_ans): + dist = multivariate_t(loc=loc, shape=shape, df=df) + assert_equal(dist.loc, loc_ans) + assert_equal(dist.shape, shape_ans) + assert (dist.df == df_ans) + + ARGS_SHAPES_TESTS = [ + (-1, 2, 3, [-1], [[2]], 3), + ([-1], [2], 3, [-1], [[2]], 3), + (np.array([-1]), np.array([2]), 3, [-1], [[2]], 3) + ] + + @pytest.mark.parametrize("loc, shape, df, loc_ans, shape_ans, df_ans", + ARGS_SHAPES_TESTS) + def test_scalar_list_and_ndarray_arguments(self, loc, shape, df, loc_ans, + shape_ans, df_ans): + dist = multivariate_t(loc, shape, df) + assert_equal(dist.loc, loc_ans) + assert_equal(dist.shape, shape_ans) + assert_equal(dist.df, df_ans) + + def test_argument_error_handling(self): + # `loc` should be a one-dimensional vector. + loc = [[1, 1]] + assert_raises(ValueError, + multivariate_t, + **dict(loc=loc)) + + # `shape` should be scalar or square matrix. + shape = [[1, 1], [2, 2], [3, 3]] + assert_raises(ValueError, + multivariate_t, + **dict(loc=loc, shape=shape)) + + # `df` should be greater than zero. + loc = np.zeros(2) + shape = np.eye(2) + df = -1 + assert_raises(ValueError, + multivariate_t, + **dict(loc=loc, shape=shape, df=df)) + df = 0 + assert_raises(ValueError, + multivariate_t, + **dict(loc=loc, shape=shape, df=df)) + + def test_reproducibility(self): + rng = np.random.RandomState(4) + loc = rng.uniform(size=3) + shape = np.eye(3) + dist1 = multivariate_t(loc, shape, df=3, seed=2) + dist2 = multivariate_t(loc, shape, df=3, seed=2) + samples1 = dist1.rvs(size=10) + samples2 = dist2.rvs(size=10) + assert_equal(samples1, samples2) + + def test_allow_singular(self): + # Make shape singular and verify error was raised. + args = dict(loc=[0,0], shape=[[0,0],[0,1]], df=1, allow_singular=False) + assert_raises(np.linalg.LinAlgError, multivariate_t, **args) + + @pytest.mark.parametrize("size", [(10, 3), (5, 6, 4, 3)]) + @pytest.mark.parametrize("dim", [2, 3, 4, 5]) + @pytest.mark.parametrize("df", [1., 2., np.inf]) + def test_rvs(self, size, dim, df): + dist = multivariate_t(np.zeros(dim), np.eye(dim), df) + rvs = dist.rvs(size=size) + assert rvs.shape == size + (dim, ) + + def test_cdf_signs(self): + # check that sign of output is correct when np.any(lower > x) + mean = np.zeros(3) + cov = np.eye(3) + df = 10 + b = [[1, 1, 1], [0, 0, 0], [1, 0, 1], [0, 1, 0]] + a = [[0, 0, 0], [1, 1, 1], [0, 1, 0], [1, 0, 1]] + # when odd number of elements of b < a, output is negative + expected_signs = np.array([1, -1, -1, 1]) + cdf = multivariate_normal.cdf(b, mean, cov, df, lower_limit=a) + assert_allclose(cdf, cdf[0]*expected_signs) + + @pytest.mark.parametrize('dim', [1, 2, 5]) + def test_cdf_against_multivariate_normal(self, dim): + # Check accuracy against MVN randomly-generated cases + self.cdf_against_mvn_test(dim) + + @pytest.mark.parametrize('dim', [3, 6, 9]) + def test_cdf_against_multivariate_normal_singular(self, dim): + # Check accuracy against MVN for randomly-generated singular cases + self.cdf_against_mvn_test(3, True) + + def cdf_against_mvn_test(self, dim, singular=False): + # Check for accuracy in the limit that df -> oo and MVT -> MVN + rng = np.random.default_rng(413722918996573) + n = 3 + + w = 10**rng.uniform(-2, 1, size=dim) + cov = _random_covariance(dim, w, rng, singular) + + mean = 10**rng.uniform(-1, 2, size=dim) * np.sign(rng.normal(size=dim)) + a = -10**rng.uniform(-1, 2, size=(n, dim)) + mean + b = 10**rng.uniform(-1, 2, size=(n, dim)) + mean + + res = stats.multivariate_t.cdf(b, mean, cov, df=10000, lower_limit=a, + allow_singular=True, random_state=rng) + ref = stats.multivariate_normal.cdf(b, mean, cov, allow_singular=True, + lower_limit=a) + assert_allclose(res, ref, atol=5e-4) + + def test_cdf_against_univariate_t(self): + rng = np.random.default_rng(413722918996573) + cov = 2 + mean = 0 + x = rng.normal(size=10, scale=np.sqrt(cov)) + df = 3 + + res = stats.multivariate_t.cdf(x, mean, cov, df, lower_limit=-np.inf, + random_state=rng) + ref = stats.t.cdf(x, df, mean, np.sqrt(cov)) + incorrect = stats.norm.cdf(x, mean, np.sqrt(cov)) + + assert_allclose(res, ref, atol=5e-4) # close to t + assert np.all(np.abs(res - incorrect) > 1e-3) # not close to normal + + @pytest.mark.parametrize("dim", [2, 3, 5, 10]) + @pytest.mark.parametrize("seed", [3363958638, 7891119608, 3887698049, + 5013150848, 1495033423, 6170824608]) + @pytest.mark.parametrize("singular", [False, True]) + def test_cdf_against_qsimvtv(self, dim, seed, singular): + if singular and seed != 3363958638: + pytest.skip('Agreement with qsimvtv is not great in singular case') + rng = np.random.default_rng(seed) + w = 10**rng.uniform(-2, 2, size=dim) + cov = _random_covariance(dim, w, rng, singular) + mean = rng.random(dim) + a = -rng.random(dim) + b = rng.random(dim) + df = rng.random() * 5 + + # no lower limit + res = stats.multivariate_t.cdf(b, mean, cov, df, random_state=rng, + allow_singular=True) + with np.errstate(invalid='ignore'): + ref = _qsimvtv(20000, df, cov, np.inf*a, b - mean, rng)[0] + assert_allclose(res, ref, atol=2e-4, rtol=1e-3) + + # with lower limit + res = stats.multivariate_t.cdf(b, mean, cov, df, lower_limit=a, + random_state=rng, allow_singular=True) + with np.errstate(invalid='ignore'): + ref = _qsimvtv(20000, df, cov, a - mean, b - mean, rng)[0] + assert_allclose(res, ref, atol=1e-4, rtol=1e-3) + + @pytest.mark.slow + def test_cdf_against_generic_integrators(self): + # Compare result against generic numerical integrators + dim = 3 + rng = np.random.default_rng(41372291899657) + w = 10 ** rng.uniform(-1, 1, size=dim) + cov = _random_covariance(dim, w, rng, singular=True) + mean = rng.random(dim) + a = -rng.random(dim) + b = rng.random(dim) + df = rng.random() * 5 + + res = stats.multivariate_t.cdf(b, mean, cov, df, random_state=rng, + lower_limit=a) + + def integrand(x): + return stats.multivariate_t.pdf(x.T, mean, cov, df) + + ref = qmc_quad(integrand, a, b, qrng=stats.qmc.Halton(d=dim, seed=rng)) + assert_allclose(res, ref.integral, rtol=1e-3) + + def integrand(*zyx): + return stats.multivariate_t.pdf(zyx[::-1], mean, cov, df) + + ref = tplquad(integrand, a[0], b[0], a[1], b[1], a[2], b[2]) + assert_allclose(res, ref[0], rtol=1e-3) + + def test_against_matlab(self): + # Test against matlab mvtcdf: + # C = [6.21786909 0.2333667 7.95506077; + # 0.2333667 29.67390923 16.53946426; + # 7.95506077 16.53946426 19.17725252] + # df = 1.9559939787727658 + # mvtcdf([0, 0, 0], C, df) % 0.2523 + rng = np.random.default_rng(2967390923) + cov = np.array([[ 6.21786909, 0.2333667 , 7.95506077], + [ 0.2333667 , 29.67390923, 16.53946426], + [ 7.95506077, 16.53946426, 19.17725252]]) + df = 1.9559939787727658 + dist = stats.multivariate_t(shape=cov, df=df) + res = dist.cdf([0, 0, 0], random_state=rng) + ref = 0.2523 + assert_allclose(res, ref, rtol=1e-3) + + def test_frozen(self): + seed = 4137229573 + rng = np.random.default_rng(seed) + loc = rng.uniform(size=3) + x = rng.uniform(size=3) + loc + shape = np.eye(3) + df = rng.random() + args = (loc, shape, df) + + rng_frozen = np.random.default_rng(seed) + rng_unfrozen = np.random.default_rng(seed) + dist = stats.multivariate_t(*args, seed=rng_frozen) + assert_equal(dist.cdf(x), + multivariate_t.cdf(x, *args, random_state=rng_unfrozen)) + + def test_vectorized(self): + dim = 4 + n = (2, 3) + rng = np.random.default_rng(413722918996573) + A = rng.random(size=(dim, dim)) + cov = A @ A.T + mean = rng.random(dim) + x = rng.random(n + (dim,)) + df = rng.random() * 5 + + res = stats.multivariate_t.cdf(x, mean, cov, df, random_state=rng) + + def _cdf_1d(x): + return _qsimvtv(10000, df, cov, -np.inf*x, x-mean, rng)[0] + + ref = np.apply_along_axis(_cdf_1d, -1, x) + assert_allclose(res, ref, atol=1e-4, rtol=1e-3) + + @pytest.mark.parametrize("dim", (3, 7)) + def test_against_analytical(self, dim): + rng = np.random.default_rng(413722918996573) + A = scipy.linalg.toeplitz(c=[1] + [0.5] * (dim - 1)) + res = stats.multivariate_t(shape=A).cdf([0] * dim, random_state=rng) + ref = 1 / (dim + 1) + assert_allclose(res, ref, rtol=5e-5) + + def test_entropy_inf_df(self): + cov = np.eye(3, 3) + df = np.inf + mvt_entropy = stats.multivariate_t.entropy(shape=cov, df=df) + mvn_entropy = stats.multivariate_normal.entropy(None, cov) + assert mvt_entropy == mvn_entropy + + @pytest.mark.parametrize("df", [1, 10, 100]) + def test_entropy_1d(self, df): + mvt_entropy = stats.multivariate_t.entropy(shape=1., df=df) + t_entropy = stats.t.entropy(df=df) + assert_allclose(mvt_entropy, t_entropy, rtol=1e-13) + + # entropy reference values were computed via numerical integration + # + # def integrand(x, y, mvt): + # vec = np.array([x, y]) + # return mvt.logpdf(vec) * mvt.pdf(vec) + + # def multivariate_t_entropy_quad_2d(df, cov): + # dim = cov.shape[0] + # loc = np.zeros((dim, )) + # mvt = stats.multivariate_t(loc, cov, df) + # limit = 100 + # return -integrate.dblquad(integrand, -limit, limit, -limit, limit, + # args=(mvt, ))[0] + + @pytest.mark.parametrize("df, cov, ref, tol", + [(10, np.eye(2, 2), 3.0378770664093313, 1e-14), + (100, np.array([[0.5, 1], [1, 10]]), + 3.55102424550609, 1e-8)]) + def test_entropy_vs_numerical_integration(self, df, cov, ref, tol): + loc = np.zeros((2, )) + mvt = stats.multivariate_t(loc, cov, df) + assert_allclose(mvt.entropy(), ref, rtol=tol) + + @pytest.mark.parametrize( + "df, dim, ref, tol", + [ + (10, 1, 1.5212624929756808, 1e-15), + (100, 1, 1.4289633653182439, 1e-13), + (500, 1, 1.420939531869349, 1e-14), + (1e20, 1, 1.4189385332046727, 1e-15), + (1e100, 1, 1.4189385332046727, 1e-15), + (10, 10, 15.069150450832911, 1e-15), + (1000, 10, 14.19936546446673, 1e-13), + (1e20, 10, 14.189385332046728, 1e-15), + (1e100, 10, 14.189385332046728, 1e-15), + (10, 100, 148.28902883192654, 1e-15), + (1000, 100, 141.99155538003762, 1e-14), + (1e20, 100, 141.8938533204673, 1e-15), + (1e100, 100, 141.8938533204673, 1e-15), + ] + ) + def test_extreme_entropy(self, df, dim, ref, tol): + # Reference values were calculated with mpmath: + # from mpmath import mp + # mp.dps = 500 + # + # def mul_t_mpmath_entropy(dim, df=1): + # dim = mp.mpf(dim) + # df = mp.mpf(df) + # halfsum = (dim + df)/2 + # half_df = df/2 + # + # return float( + # -mp.loggamma(halfsum) + mp.loggamma(half_df) + # + dim / 2 * mp.log(df * mp.pi) + # + halfsum * (mp.digamma(halfsum) - mp.digamma(half_df)) + # + 0.0 + # ) + mvt = stats.multivariate_t(shape=np.eye(dim), df=df) + assert_allclose(mvt.entropy(), ref, rtol=tol) + + def test_entropy_with_covariance(self): + # Generated using np.randn(5, 5) and then rounding + # to two decimal places + _A = np.array([ + [1.42, 0.09, -0.49, 0.17, 0.74], + [-1.13, -0.01, 0.71, 0.4, -0.56], + [1.07, 0.44, -0.28, -0.44, 0.29], + [-1.5, -0.94, -0.67, 0.73, -1.1], + [0.17, -0.08, 1.46, -0.32, 1.36] + ]) + # Set cov to be a symmetric positive semi-definite matrix + cov = _A @ _A.T + + # Test the asymptotic case. For large degrees of freedom + # the entropy approaches the multivariate normal entropy. + df = 1e20 + mul_t_entropy = stats.multivariate_t.entropy(shape=cov, df=df) + mul_norm_entropy = multivariate_normal(None, cov=cov).entropy() + assert_allclose(mul_t_entropy, mul_norm_entropy, rtol=1e-15) + + # Test the regular case. For a dim of 5 the threshold comes out + # to be approximately 766.45. So using slightly + # different dfs on each site of the threshold, the entropies + # are being compared. + df1 = 765 + df2 = 768 + _entropy1 = stats.multivariate_t.entropy(shape=cov, df=df1) + _entropy2 = stats.multivariate_t.entropy(shape=cov, df=df2) + assert_allclose(_entropy1, _entropy2, rtol=1e-5) + + def test_logpdf_df_inf_gh19930(self): + # `multivariate_t._logpdf` (and `logpdf`/`pdf`) was not working with infinite + # `df` after an update to `multivariate_normal._logpdf`. + + # Reproducible example from the issue + res = multivariate_t.logpdf(1, 1, 1, df=np.inf) + ref = multivariate_normal.logpdf(1, 1, 1) + assert_allclose(res, ref) + + # More extensive test + # Generate a valid multivariate normal distribution and corresponding MVT + rng = np.random.default_rng(324893259825) + mean = rng.random(3) + cov = rng.random((3, 3)) + np.eye(3)*3 + cov = cov.T + cov + X = multivariate_normal(mean=mean, cov=cov) + Y = multivariate_t(loc=mean, shape=cov, df=np.inf) + + # compare the pdf and logpdf at 10 random points + x = X.rvs(10) + assert_allclose(Y.logpdf(x), X.logpdf(x)) + assert_allclose(Y.pdf(x), X.pdf(x)) + + +class TestMultivariateHypergeom: + @pytest.mark.parametrize( + "x, m, n, expected", + [ + # Ground truth value from R dmvhyper + ([3, 4], [5, 10], 7, -1.119814), + # test for `n=0` + ([3, 4], [5, 10], 0, -np.inf), + # test for `x < 0` + ([-3, 4], [5, 10], 7, -np.inf), + # test for `m < 0` (RuntimeWarning issue) + ([3, 4], [-5, 10], 7, np.nan), + # test for all `m < 0` and `x.sum() != n` + ([[1, 2], [3, 4]], [[-4, -6], [-5, -10]], + [3, 7], [np.nan, np.nan]), + # test for `x < 0` and `m < 0` (RuntimeWarning issue) + ([-3, 4], [-5, 10], 1, np.nan), + # test for `x > m` + ([1, 11], [10, 1], 12, np.nan), + # test for `m < 0` (RuntimeWarning issue) + ([1, 11], [10, -1], 12, np.nan), + # test for `n < 0` + ([3, 4], [5, 10], -7, np.nan), + # test for `x.sum() != n` + ([3, 3], [5, 10], 7, -np.inf) + ] + ) + def test_logpmf(self, x, m, n, expected): + vals = multivariate_hypergeom.logpmf(x, m, n) + assert_allclose(vals, expected, rtol=1e-6) + + def test_reduces_hypergeom(self): + # test that the multivariate_hypergeom pmf reduces to the + # hypergeom pmf in the 2d case. + val1 = multivariate_hypergeom.pmf(x=[3, 1], m=[10, 5], n=4) + val2 = hypergeom.pmf(k=3, M=15, n=4, N=10) + assert_allclose(val1, val2, rtol=1e-8) + + val1 = multivariate_hypergeom.pmf(x=[7, 3], m=[15, 10], n=10) + val2 = hypergeom.pmf(k=7, M=25, n=10, N=15) + assert_allclose(val1, val2, rtol=1e-8) + + def test_rvs(self): + # test if `rvs` is unbiased and large sample size converges + # to the true mean. + rv = multivariate_hypergeom(m=[3, 5], n=4) + rvs = rv.rvs(size=1000, random_state=123) + assert_allclose(rvs.mean(0), rv.mean(), rtol=1e-2) + + def test_rvs_broadcasting(self): + rv = multivariate_hypergeom(m=[[3, 5], [5, 10]], n=[4, 9]) + rvs = rv.rvs(size=(1000, 2), random_state=123) + assert_allclose(rvs.mean(0), rv.mean(), rtol=1e-2) + + @pytest.mark.parametrize('m, n', ( + ([0, 0, 20, 0, 0], 5), ([0, 0, 0, 0, 0], 0), + ([0, 0], 0), ([0], 0) + )) + def test_rvs_gh16171(self, m, n): + res = multivariate_hypergeom.rvs(m, n) + m = np.asarray(m) + res_ex = m.copy() + res_ex[m != 0] = n + assert_equal(res, res_ex) + + @pytest.mark.parametrize( + "x, m, n, expected", + [ + ([5], [5], 5, 1), + ([3, 4], [5, 10], 7, 0.3263403), + # Ground truth value from R dmvhyper + ([[[3, 5], [0, 8]], [[-1, 9], [1, 1]]], + [5, 10], [[8, 8], [8, 2]], + [[0.3916084, 0.006993007], [0, 0.4761905]]), + # test with empty arrays. + (np.array([], dtype=int), np.array([], dtype=int), 0, []), + ([1, 2], [4, 5], 5, 0), + # Ground truth value from R dmvhyper + ([3, 3, 0], [5, 6, 7], 6, 0.01077354) + ] + ) + def test_pmf(self, x, m, n, expected): + vals = multivariate_hypergeom.pmf(x, m, n) + assert_allclose(vals, expected, rtol=1e-7) + + @pytest.mark.parametrize( + "x, m, n, expected", + [ + ([3, 4], [[5, 10], [10, 15]], 7, [0.3263403, 0.3407531]), + ([[1], [2]], [[3], [4]], [1, 3], [1., 0.]), + ([[[1], [2]]], [[3], [4]], [1, 3], [[1., 0.]]), + ([[1], [2]], [[[[3]]]], [1, 3], [[[1., 0.]]]) + ] + ) + def test_pmf_broadcasting(self, x, m, n, expected): + vals = multivariate_hypergeom.pmf(x, m, n) + assert_allclose(vals, expected, rtol=1e-7) + + def test_cov(self): + cov1 = multivariate_hypergeom.cov(m=[3, 7, 10], n=12) + cov2 = [[0.64421053, -0.26526316, -0.37894737], + [-0.26526316, 1.14947368, -0.88421053], + [-0.37894737, -0.88421053, 1.26315789]] + assert_allclose(cov1, cov2, rtol=1e-8) + + def test_cov_broadcasting(self): + cov1 = multivariate_hypergeom.cov(m=[[7, 9], [10, 15]], n=[8, 12]) + cov2 = [[[1.05, -1.05], [-1.05, 1.05]], + [[1.56, -1.56], [-1.56, 1.56]]] + assert_allclose(cov1, cov2, rtol=1e-8) + + cov3 = multivariate_hypergeom.cov(m=[[4], [5]], n=[4, 5]) + cov4 = [[[0.]], [[0.]]] + assert_allclose(cov3, cov4, rtol=1e-8) + + cov5 = multivariate_hypergeom.cov(m=[7, 9], n=[8, 12]) + cov6 = [[[1.05, -1.05], [-1.05, 1.05]], + [[0.7875, -0.7875], [-0.7875, 0.7875]]] + assert_allclose(cov5, cov6, rtol=1e-8) + + def test_var(self): + # test with hypergeom + var0 = multivariate_hypergeom.var(m=[10, 5], n=4) + var1 = hypergeom.var(M=15, n=4, N=10) + assert_allclose(var0, var1, rtol=1e-8) + + def test_var_broadcasting(self): + var0 = multivariate_hypergeom.var(m=[10, 5], n=[4, 8]) + var1 = multivariate_hypergeom.var(m=[10, 5], n=4) + var2 = multivariate_hypergeom.var(m=[10, 5], n=8) + assert_allclose(var0[0], var1, rtol=1e-8) + assert_allclose(var0[1], var2, rtol=1e-8) + + var3 = multivariate_hypergeom.var(m=[[10, 5], [10, 14]], n=[4, 8]) + var4 = [[0.6984127, 0.6984127], [1.352657, 1.352657]] + assert_allclose(var3, var4, rtol=1e-8) + + var5 = multivariate_hypergeom.var(m=[[5], [10]], n=[5, 10]) + var6 = [[0.], [0.]] + assert_allclose(var5, var6, rtol=1e-8) + + def test_mean(self): + # test with hypergeom + mean0 = multivariate_hypergeom.mean(m=[10, 5], n=4) + mean1 = hypergeom.mean(M=15, n=4, N=10) + assert_allclose(mean0[0], mean1, rtol=1e-8) + + mean2 = multivariate_hypergeom.mean(m=[12, 8], n=10) + mean3 = [12.*10./20., 8.*10./20.] + assert_allclose(mean2, mean3, rtol=1e-8) + + def test_mean_broadcasting(self): + mean0 = multivariate_hypergeom.mean(m=[[3, 5], [10, 5]], n=[4, 8]) + mean1 = [[3.*4./8., 5.*4./8.], [10.*8./15., 5.*8./15.]] + assert_allclose(mean0, mean1, rtol=1e-8) + + def test_mean_edge_cases(self): + mean0 = multivariate_hypergeom.mean(m=[0, 0, 0], n=0) + assert_equal(mean0, [0., 0., 0.]) + + mean1 = multivariate_hypergeom.mean(m=[1, 0, 0], n=2) + assert_equal(mean1, [np.nan, np.nan, np.nan]) + + mean2 = multivariate_hypergeom.mean(m=[[1, 0, 0], [1, 0, 1]], n=2) + assert_allclose(mean2, [[np.nan, np.nan, np.nan], [1., 0., 1.]], + rtol=1e-17) + + mean3 = multivariate_hypergeom.mean(m=np.array([], dtype=int), n=0) + assert_equal(mean3, []) + assert_(mean3.shape == (0, )) + + def test_var_edge_cases(self): + var0 = multivariate_hypergeom.var(m=[0, 0, 0], n=0) + assert_allclose(var0, [0., 0., 0.], rtol=1e-16) + + var1 = multivariate_hypergeom.var(m=[1, 0, 0], n=2) + assert_equal(var1, [np.nan, np.nan, np.nan]) + + var2 = multivariate_hypergeom.var(m=[[1, 0, 0], [1, 0, 1]], n=2) + assert_allclose(var2, [[np.nan, np.nan, np.nan], [0., 0., 0.]], + rtol=1e-17) + + var3 = multivariate_hypergeom.var(m=np.array([], dtype=int), n=0) + assert_equal(var3, []) + assert_(var3.shape == (0, )) + + def test_cov_edge_cases(self): + cov0 = multivariate_hypergeom.cov(m=[1, 0, 0], n=1) + cov1 = [[0., 0., 0.], [0., 0., 0.], [0., 0., 0.]] + assert_allclose(cov0, cov1, rtol=1e-17) + + cov3 = multivariate_hypergeom.cov(m=[0, 0, 0], n=0) + cov4 = [[0., 0., 0.], [0., 0., 0.], [0., 0., 0.]] + assert_equal(cov3, cov4) + + cov5 = multivariate_hypergeom.cov(m=np.array([], dtype=int), n=0) + cov6 = np.array([], dtype=np.float64).reshape(0, 0) + assert_allclose(cov5, cov6, rtol=1e-17) + assert_(cov5.shape == (0, 0)) + + def test_frozen(self): + # The frozen distribution should agree with the regular one + n = 12 + m = [7, 9, 11, 13] + x = [[0, 0, 0, 12], [0, 0, 1, 11], [0, 1, 1, 10], + [1, 1, 1, 9], [1, 1, 2, 8]] + x = np.asarray(x, dtype=int) + mhg_frozen = multivariate_hypergeom(m, n) + assert_allclose(mhg_frozen.pmf(x), + multivariate_hypergeom.pmf(x, m, n)) + assert_allclose(mhg_frozen.logpmf(x), + multivariate_hypergeom.logpmf(x, m, n)) + assert_allclose(mhg_frozen.var(), multivariate_hypergeom.var(m, n)) + assert_allclose(mhg_frozen.cov(), multivariate_hypergeom.cov(m, n)) + + def test_invalid_params(self): + assert_raises(ValueError, multivariate_hypergeom.pmf, 5, 10, 5) + assert_raises(ValueError, multivariate_hypergeom.pmf, 5, [10], 5) + assert_raises(ValueError, multivariate_hypergeom.pmf, [5, 4], [10], 5) + assert_raises(TypeError, multivariate_hypergeom.pmf, [5.5, 4.5], + [10, 15], 5) + assert_raises(TypeError, multivariate_hypergeom.pmf, [5, 4], + [10.5, 15.5], 5) + assert_raises(TypeError, multivariate_hypergeom.pmf, [5, 4], + [10, 15], 5.5) + + +class TestRandomTable: + def get_rng(self): + return np.random.default_rng(628174795866951638) + + def test_process_parameters(self): + message = "`row` must be one-dimensional" + with pytest.raises(ValueError, match=message): + random_table([[1, 2]], [1, 2]) + + message = "`col` must be one-dimensional" + with pytest.raises(ValueError, match=message): + random_table([1, 2], [[1, 2]]) + + message = "each element of `row` must be non-negative" + with pytest.raises(ValueError, match=message): + random_table([1, -1], [1, 2]) + + message = "each element of `col` must be non-negative" + with pytest.raises(ValueError, match=message): + random_table([1, 2], [1, -2]) + + message = "sums over `row` and `col` must be equal" + with pytest.raises(ValueError, match=message): + random_table([1, 2], [1, 0]) + + message = "each element of `row` must be an integer" + with pytest.raises(ValueError, match=message): + random_table([2.1, 2.1], [1, 1, 2]) + + message = "each element of `col` must be an integer" + with pytest.raises(ValueError, match=message): + random_table([1, 2], [1.1, 1.1, 1]) + + row = [1, 3] + col = [2, 1, 1] + r, c, n = random_table._process_parameters([1, 3], [2, 1, 1]) + assert_equal(row, r) + assert_equal(col, c) + assert n == np.sum(row) + + @pytest.mark.parametrize("scale,method", + ((1, "boyett"), (100, "patefield"))) + def test_process_rvs_method_on_None(self, scale, method): + row = np.array([1, 3]) * scale + col = np.array([2, 1, 1]) * scale + + ct = random_table + expected = ct.rvs(row, col, method=method, random_state=1) + got = ct.rvs(row, col, method=None, random_state=1) + + assert_equal(expected, got) + + def test_process_rvs_method_bad_argument(self): + row = [1, 3] + col = [2, 1, 1] + + # order of items in set is random, so cannot check that + message = "'foo' not recognized, must be one of" + with pytest.raises(ValueError, match=message): + random_table.rvs(row, col, method="foo") + + @pytest.mark.parametrize('frozen', (True, False)) + @pytest.mark.parametrize('log', (True, False)) + def test_pmf_logpmf(self, frozen, log): + # The pmf is tested through random sample generation + # with Boyett's algorithm, whose implementation is simple + # enough to verify manually for correctness. + rng = self.get_rng() + row = [2, 6] + col = [1, 3, 4] + rvs = random_table.rvs(row, col, size=1000, + method="boyett", random_state=rng) + + obj = random_table(row, col) if frozen else random_table + method = getattr(obj, "logpmf" if log else "pmf") + if not frozen: + original_method = method + + def method(x): + return original_method(x, row, col) + pmf = (lambda x: np.exp(method(x))) if log else method + + unique_rvs, counts = np.unique(rvs, axis=0, return_counts=True) + + # rough accuracy check + p = pmf(unique_rvs) + assert_allclose(p * len(rvs), counts, rtol=0.1) + + # accept any iterable + p2 = pmf(list(unique_rvs[0])) + assert_equal(p2, p[0]) + + # accept high-dimensional input and 2d input + rvs_nd = rvs.reshape((10, 100) + rvs.shape[1:]) + p = pmf(rvs_nd) + assert p.shape == (10, 100) + for i in range(p.shape[0]): + for j in range(p.shape[1]): + pij = p[i, j] + rvij = rvs_nd[i, j] + qij = pmf(rvij) + assert_equal(pij, qij) + + # probability is zero if column marginal does not match + x = [[0, 1, 1], [2, 1, 3]] + assert_equal(np.sum(x, axis=-1), row) + p = pmf(x) + assert p == 0 + + # probability is zero if row marginal does not match + x = [[0, 1, 2], [1, 2, 2]] + assert_equal(np.sum(x, axis=-2), col) + p = pmf(x) + assert p == 0 + + # response to invalid inputs + message = "`x` must be at least two-dimensional" + with pytest.raises(ValueError, match=message): + pmf([1]) + + message = "`x` must contain only integral values" + with pytest.raises(ValueError, match=message): + pmf([[1.1]]) + + message = "`x` must contain only integral values" + with pytest.raises(ValueError, match=message): + pmf([[np.nan]]) + + message = "`x` must contain only non-negative values" + with pytest.raises(ValueError, match=message): + pmf([[-1]]) + + message = "shape of `x` must agree with `row`" + with pytest.raises(ValueError, match=message): + pmf([[1, 2, 3]]) + + message = "shape of `x` must agree with `col`" + with pytest.raises(ValueError, match=message): + pmf([[1, 2], + [3, 4]]) + + @pytest.mark.parametrize("method", ("boyett", "patefield")) + def test_rvs_mean(self, method): + # test if `rvs` is unbiased and large sample size converges + # to the true mean. + rng = self.get_rng() + row = [2, 6] + col = [1, 3, 4] + rvs = random_table.rvs(row, col, size=1000, method=method, + random_state=rng) + mean = random_table.mean(row, col) + assert_equal(np.sum(mean), np.sum(row)) + assert_allclose(rvs.mean(0), mean, atol=0.05) + assert_equal(rvs.sum(axis=-1), np.broadcast_to(row, (1000, 2))) + assert_equal(rvs.sum(axis=-2), np.broadcast_to(col, (1000, 3))) + + def test_rvs_cov(self): + # test if `rvs` generated with patefield and boyett algorithms + # produce approximately the same covariance matrix + rng = self.get_rng() + row = [2, 6] + col = [1, 3, 4] + rvs1 = random_table.rvs(row, col, size=10000, method="boyett", + random_state=rng) + rvs2 = random_table.rvs(row, col, size=10000, method="patefield", + random_state=rng) + cov1 = np.var(rvs1, axis=0) + cov2 = np.var(rvs2, axis=0) + assert_allclose(cov1, cov2, atol=0.02) + + @pytest.mark.parametrize("method", ("boyett", "patefield")) + def test_rvs_size(self, method): + row = [2, 6] + col = [1, 3, 4] + + # test size `None` + rv = random_table.rvs(row, col, method=method, + random_state=self.get_rng()) + assert rv.shape == (2, 3) + + # test size 1 + rv2 = random_table.rvs(row, col, size=1, method=method, + random_state=self.get_rng()) + assert rv2.shape == (1, 2, 3) + assert_equal(rv, rv2[0]) + + # test size 0 + rv3 = random_table.rvs(row, col, size=0, method=method, + random_state=self.get_rng()) + assert rv3.shape == (0, 2, 3) + + # test other valid size + rv4 = random_table.rvs(row, col, size=20, method=method, + random_state=self.get_rng()) + assert rv4.shape == (20, 2, 3) + + rv5 = random_table.rvs(row, col, size=(4, 5), method=method, + random_state=self.get_rng()) + assert rv5.shape == (4, 5, 2, 3) + + assert_allclose(rv5.reshape(20, 2, 3), rv4, rtol=1e-15) + + # test invalid size + message = "`size` must be a non-negative integer or `None`" + with pytest.raises(ValueError, match=message): + random_table.rvs(row, col, size=-1, method=method, + random_state=self.get_rng()) + + with pytest.raises(ValueError, match=message): + random_table.rvs(row, col, size=np.nan, method=method, + random_state=self.get_rng()) + + @pytest.mark.parametrize("method", ("boyett", "patefield")) + def test_rvs_method(self, method): + # This test assumes that pmf is correct and checks that random samples + # follow this probability distribution. This seems like a circular + # argument, since pmf is checked in test_pmf_logpmf with random samples + # generated with the rvs method. This test is not redundant, because + # test_pmf_logpmf intentionally uses rvs generation with Boyett only, + # but here we test both Boyett and Patefield. + row = [2, 6] + col = [1, 3, 4] + + ct = random_table + rvs = ct.rvs(row, col, size=100000, method=method, + random_state=self.get_rng()) + + unique_rvs, counts = np.unique(rvs, axis=0, return_counts=True) + + # generated frequencies should match expected frequencies + p = ct.pmf(unique_rvs, row, col) + assert_allclose(p * len(rvs), counts, rtol=0.02) + + @pytest.mark.parametrize("method", ("boyett", "patefield")) + def test_rvs_with_zeros_in_col_row(self, method): + row = [0, 1, 0] + col = [1, 0, 0, 0] + d = random_table(row, col) + rv = d.rvs(1000, method=method, random_state=self.get_rng()) + expected = np.zeros((1000, len(row), len(col))) + expected[...] = [[0, 0, 0, 0], + [1, 0, 0, 0], + [0, 0, 0, 0]] + assert_equal(rv, expected) + + @pytest.mark.parametrize("method", (None, "boyett", "patefield")) + @pytest.mark.parametrize("col", ([], [0])) + @pytest.mark.parametrize("row", ([], [0])) + def test_rvs_with_edge_cases(self, method, row, col): + d = random_table(row, col) + rv = d.rvs(10, method=method, random_state=self.get_rng()) + expected = np.zeros((10, len(row), len(col))) + assert_equal(rv, expected) + + @pytest.mark.parametrize('v', (1, 2)) + def test_rvs_rcont(self, v): + # This test checks the internal low-level interface. + # It is implicitly also checked by the other test_rvs* calls. + import scipy.stats._rcont as _rcont + + row = np.array([1, 3], dtype=np.int64) + col = np.array([2, 1, 1], dtype=np.int64) + + rvs = getattr(_rcont, f"rvs_rcont{v}") + + ntot = np.sum(row) + result = rvs(row, col, ntot, 1, self.get_rng()) + + assert result.shape == (1, len(row), len(col)) + assert np.sum(result) == ntot + + def test_frozen(self): + row = [2, 6] + col = [1, 3, 4] + d = random_table(row, col, seed=self.get_rng()) + + sample = d.rvs() + + expected = random_table.mean(row, col) + assert_equal(expected, d.mean()) + + expected = random_table.pmf(sample, row, col) + assert_equal(expected, d.pmf(sample)) + + expected = random_table.logpmf(sample, row, col) + assert_equal(expected, d.logpmf(sample)) + + @pytest.mark.parametrize("method", ("boyett", "patefield")) + def test_rvs_frozen(self, method): + row = [2, 6] + col = [1, 3, 4] + d = random_table(row, col, seed=self.get_rng()) + + expected = random_table.rvs(row, col, size=10, method=method, + random_state=self.get_rng()) + got = d.rvs(size=10, method=method) + assert_equal(expected, got) + + +def check_pickling(distfn, args): + # check that a distribution instance pickles and unpickles + # pay special attention to the random_state property + + # save the random_state (restore later) + rndm = distfn.random_state + + distfn.random_state = 1234 + distfn.rvs(*args, size=8) + s = pickle.dumps(distfn) + r0 = distfn.rvs(*args, size=8) + + unpickled = pickle.loads(s) + r1 = unpickled.rvs(*args, size=8) + assert_equal(r0, r1) + + # restore the random_state + distfn.random_state = rndm + + +@pytest.mark.thread_unsafe(reason="uses numpy global random state and monkey-patching") +def test_random_state_property(): + scale = np.eye(3) + scale[0, 1] = 0.5 + scale[1, 0] = 0.5 + dists = [ + [multivariate_normal, ()], + [dirichlet, (np.array([1.]), )], + [wishart, (10, scale)], + [invwishart, (10, scale)], + [multinomial, (5, [0.5, 0.4, 0.1])], + [ortho_group, (2,)], + [special_ortho_group, (2,)] + ] + for distfn, args in dists: + check_random_state_property(distfn, args) + check_pickling(distfn, args) + + +class TestVonMises_Fisher: + @pytest.mark.parametrize("dim", [2, 3, 4, 6]) + @pytest.mark.parametrize("size", [None, 1, 5, (5, 4)]) + def test_samples(self, dim, size): + # test that samples have correct shape and norm 1 + rng = np.random.default_rng(2777937887058094419) + mu = np.full((dim, ), 1/np.sqrt(dim)) + vmf_dist = vonmises_fisher(mu, 1, seed=rng) + samples = vmf_dist.rvs(size) + mean, cov = np.zeros(dim), np.eye(dim) + expected_shape = rng.multivariate_normal(mean, cov, size=size).shape + assert samples.shape == expected_shape + norms = np.linalg.norm(samples, axis=-1) + assert_allclose(norms, 1.) + + @pytest.mark.parametrize("dim", [5, 8]) + @pytest.mark.parametrize("kappa", [1e15, 1e20, 1e30]) + def test_sampling_high_concentration(self, dim, kappa): + # test that no warnings are encountered for high values + rng = np.random.default_rng(2777937887058094419) + mu = np.full((dim, ), 1/np.sqrt(dim)) + vmf_dist = vonmises_fisher(mu, kappa, seed=rng) + vmf_dist.rvs(10) + + def test_two_dimensional_mu(self): + mu = np.ones((2, 2)) + msg = "'mu' must have one-dimensional shape." + with pytest.raises(ValueError, match=msg): + vonmises_fisher(mu, 1) + + def test_wrong_norm_mu(self): + mu = np.ones((2, )) + msg = "'mu' must be a unit vector of norm 1." + with pytest.raises(ValueError, match=msg): + vonmises_fisher(mu, 1) + + def test_one_entry_mu(self): + mu = np.ones((1, )) + msg = "'mu' must have at least two entries." + with pytest.raises(ValueError, match=msg): + vonmises_fisher(mu, 1) + + @pytest.mark.parametrize("kappa", [-1, (5, 3)]) + def test_kappa_validation(self, kappa): + msg = "'kappa' must be a positive scalar." + with pytest.raises(ValueError, match=msg): + vonmises_fisher([1, 0], kappa) + + @pytest.mark.parametrize("kappa", [0, 0.]) + def test_kappa_zero(self, kappa): + msg = ("For 'kappa=0' the von Mises-Fisher distribution " + "becomes the uniform distribution on the sphere " + "surface. Consider using 'scipy.stats.uniform_direction' " + "instead.") + with pytest.raises(ValueError, match=msg): + vonmises_fisher([1, 0], kappa) + + + @pytest.mark.parametrize("method", [vonmises_fisher.pdf, + vonmises_fisher.logpdf]) + def test_invalid_shapes_pdf_logpdf(self, method): + x = np.array([1., 0., 0]) + msg = ("The dimensionality of the last axis of 'x' must " + "match the dimensionality of the von Mises Fisher " + "distribution.") + with pytest.raises(ValueError, match=msg): + method(x, [1, 0], 1) + + @pytest.mark.parametrize("method", [vonmises_fisher.pdf, + vonmises_fisher.logpdf]) + def test_unnormalized_input(self, method): + x = np.array([0.5, 0.]) + msg = "'x' must be unit vectors of norm 1 along last dimension." + with pytest.raises(ValueError, match=msg): + method(x, [1, 0], 1) + + # Expected values of the vonmises-fisher logPDF were computed via mpmath + # from mpmath import mp + # import numpy as np + # mp.dps = 50 + # def logpdf_mpmath(x, mu, kappa): + # dim = mu.size + # halfdim = mp.mpf(0.5 * dim) + # kappa = mp.mpf(kappa) + # const = (kappa**(halfdim - mp.one)/((2*mp.pi)**halfdim * \ + # mp.besseli(halfdim -mp.one, kappa))) + # return float(const * mp.exp(kappa*mp.fdot(x, mu))) + + @pytest.mark.parametrize('x, mu, kappa, reference', + [(np.array([1., 0., 0.]), np.array([1., 0., 0.]), + 1e-4, 0.0795854295583605), + (np.array([1., 0., 0]), np.array([0., 0., 1.]), + 1e-4, 0.07957747141331854), + (np.array([1., 0., 0.]), np.array([1., 0., 0.]), + 100, 15.915494309189533), + (np.array([1., 0., 0]), np.array([0., 0., 1.]), + 100, 5.920684802611232e-43), + (np.array([1., 0., 0.]), + np.array([np.sqrt(0.98), np.sqrt(0.02), 0.]), + 2000, 5.930499050746588e-07), + (np.array([1., 0., 0]), np.array([1., 0., 0.]), + 2000, 318.3098861837907), + (np.array([1., 0., 0., 0., 0.]), + np.array([1., 0., 0., 0., 0.]), + 2000, 101371.86957712633), + (np.array([1., 0., 0., 0., 0.]), + np.array([np.sqrt(0.98), np.sqrt(0.02), 0., + 0, 0.]), + 2000, 0.00018886808182653578), + (np.array([1., 0., 0., 0., 0.]), + np.array([np.sqrt(0.8), np.sqrt(0.2), 0., + 0, 0.]), + 2000, 2.0255393314603194e-87)]) + def test_pdf_accuracy(self, x, mu, kappa, reference): + pdf = vonmises_fisher(mu, kappa).pdf(x) + assert_allclose(pdf, reference, rtol=1e-13) + + # Expected values of the vonmises-fisher logPDF were computed via mpmath + # from mpmath import mp + # import numpy as np + # mp.dps = 50 + # def logpdf_mpmath(x, mu, kappa): + # dim = mu.size + # halfdim = mp.mpf(0.5 * dim) + # kappa = mp.mpf(kappa) + # two = mp.mpf(2.) + # const = (kappa**(halfdim - mp.one)/((two*mp.pi)**halfdim * \ + # mp.besseli(halfdim - mp.one, kappa))) + # return float(mp.log(const * mp.exp(kappa*mp.fdot(x, mu)))) + + @pytest.mark.parametrize('x, mu, kappa, reference', + [(np.array([1., 0., 0.]), np.array([1., 0., 0.]), + 1e-4, -2.5309242486359573), + (np.array([1., 0., 0]), np.array([0., 0., 1.]), + 1e-4, -2.5310242486359575), + (np.array([1., 0., 0.]), np.array([1., 0., 0.]), + 100, 2.767293119578746), + (np.array([1., 0., 0]), np.array([0., 0., 1.]), + 100, -97.23270688042125), + (np.array([1., 0., 0.]), + np.array([np.sqrt(0.98), np.sqrt(0.02), 0.]), + 2000, -14.337987284534103), + (np.array([1., 0., 0]), np.array([1., 0., 0.]), + 2000, 5.763025393132737), + (np.array([1., 0., 0., 0., 0.]), + np.array([1., 0., 0., 0., 0.]), + 2000, 11.526550911307156), + (np.array([1., 0., 0., 0., 0.]), + np.array([np.sqrt(0.98), np.sqrt(0.02), 0., + 0, 0.]), + 2000, -8.574461766359684), + (np.array([1., 0., 0., 0., 0.]), + np.array([np.sqrt(0.8), np.sqrt(0.2), 0., + 0, 0.]), + 2000, -199.61906708886113)]) + def test_logpdf_accuracy(self, x, mu, kappa, reference): + logpdf = vonmises_fisher(mu, kappa).logpdf(x) + assert_allclose(logpdf, reference, rtol=1e-14) + + # Expected values of the vonmises-fisher entropy were computed via mpmath + # from mpmath import mp + # import numpy as np + # mp.dps = 50 + # def entropy_mpmath(dim, kappa): + # mu = np.full((dim, ), 1/np.sqrt(dim)) + # kappa = mp.mpf(kappa) + # halfdim = mp.mpf(0.5 * dim) + # logconstant = (mp.log(kappa**(halfdim - mp.one) + # /((2*mp.pi)**halfdim + # * mp.besseli(halfdim -mp.one, kappa))) + # return float(-logconstant - kappa * mp.besseli(halfdim, kappa)/ + # mp.besseli(halfdim -1, kappa)) + + @pytest.mark.parametrize('dim, kappa, reference', + [(3, 1e-4, 2.531024245302624), + (3, 100, -1.7672931195787458), + (5, 5000, -11.359032310024453), + (8, 1, 3.4189526482545527)]) + def test_entropy_accuracy(self, dim, kappa, reference): + mu = np.full((dim, ), 1/np.sqrt(dim)) + entropy = vonmises_fisher(mu, kappa).entropy() + assert_allclose(entropy, reference, rtol=2e-14) + + @pytest.mark.parametrize("method", [vonmises_fisher.pdf, + vonmises_fisher.logpdf]) + def test_broadcasting(self, method): + # test that pdf and logpdf values are correctly broadcasted + testshape = (2, 2) + rng = np.random.default_rng(2777937887058094419) + x = uniform_direction(3).rvs(testshape, random_state=rng) + mu = np.full((3, ), 1/np.sqrt(3)) + kappa = 5 + result_all = method(x, mu, kappa) + assert result_all.shape == testshape + for i in range(testshape[0]): + for j in range(testshape[1]): + current_val = method(x[i, j, :], mu, kappa) + assert_allclose(current_val, result_all[i, j], rtol=1e-15) + + def test_vs_vonmises_2d(self): + # test that in 2D, von Mises-Fisher yields the same results + # as the von Mises distribution + rng = np.random.default_rng(2777937887058094419) + mu = np.array([0, 1]) + mu_angle = np.arctan2(mu[1], mu[0]) + kappa = 20 + vmf = vonmises_fisher(mu, kappa) + vonmises_dist = vonmises(loc=mu_angle, kappa=kappa) + vectors = uniform_direction(2).rvs(10, random_state=rng) + angles = np.arctan2(vectors[:, 1], vectors[:, 0]) + assert_allclose(vonmises_dist.entropy(), vmf.entropy()) + assert_allclose(vonmises_dist.pdf(angles), vmf.pdf(vectors)) + assert_allclose(vonmises_dist.logpdf(angles), vmf.logpdf(vectors)) + + @pytest.mark.parametrize("dim", [2, 3, 6]) + @pytest.mark.parametrize("kappa, mu_tol, kappa_tol", + [(1, 5e-2, 5e-2), + (10, 1e-2, 1e-2), + (100, 5e-3, 2e-2), + (1000, 1e-3, 2e-2)]) + def test_fit_accuracy(self, dim, kappa, mu_tol, kappa_tol): + mu = np.full((dim, ), 1/np.sqrt(dim)) + vmf_dist = vonmises_fisher(mu, kappa) + rng = np.random.default_rng(2777937887058094419) + n_samples = 10000 + samples = vmf_dist.rvs(n_samples, random_state=rng) + mu_fit, kappa_fit = vonmises_fisher.fit(samples) + angular_error = np.arccos(mu.dot(mu_fit)) + assert_allclose(angular_error, 0., atol=mu_tol, rtol=0) + assert_allclose(kappa, kappa_fit, rtol=kappa_tol) + + def test_fit_error_one_dimensional_data(self): + x = np.zeros((3, )) + msg = "'x' must be two dimensional." + with pytest.raises(ValueError, match=msg): + vonmises_fisher.fit(x) + + def test_fit_error_unnormalized_data(self): + x = np.ones((3, 3)) + msg = "'x' must be unit vectors of norm 1 along last dimension." + with pytest.raises(ValueError, match=msg): + vonmises_fisher.fit(x) + + def test_frozen_distribution(self): + mu = np.array([0, 0, 1]) + kappa = 5 + frozen = vonmises_fisher(mu, kappa) + frozen_seed = vonmises_fisher(mu, kappa, seed=514) + + rvs1 = frozen.rvs(random_state=514) + rvs2 = vonmises_fisher.rvs(mu, kappa, random_state=514) + rvs3 = frozen_seed.rvs() + + assert_equal(rvs1, rvs2) + assert_equal(rvs1, rvs3) + + +class TestDirichletMultinomial: + @classmethod + def get_params(self, m): + rng = np.random.default_rng(28469824356873456) + alpha = rng.uniform(0, 100, size=2) + x = rng.integers(1, 20, size=(m, 2)) + n = x.sum(axis=-1) + return rng, m, alpha, n, x + + def test_frozen(self): + rng = np.random.default_rng(28469824356873456) + + alpha = rng.uniform(0, 100, 10) + x = rng.integers(0, 10, 10) + n = np.sum(x, axis=-1) + + d = dirichlet_multinomial(alpha, n) + assert_equal(d.logpmf(x), dirichlet_multinomial.logpmf(x, alpha, n)) + assert_equal(d.pmf(x), dirichlet_multinomial.pmf(x, alpha, n)) + assert_equal(d.mean(), dirichlet_multinomial.mean(alpha, n)) + assert_equal(d.var(), dirichlet_multinomial.var(alpha, n)) + assert_equal(d.cov(), dirichlet_multinomial.cov(alpha, n)) + + def test_pmf_logpmf_against_R(self): + # # Compare PMF against R's extraDistr ddirmnon + # # library(extraDistr) + # # options(digits=16) + # ddirmnom(c(1, 2, 3), 6, c(3, 4, 5)) + x = np.array([1, 2, 3]) + n = np.sum(x) + alpha = np.array([3, 4, 5]) + res = dirichlet_multinomial.pmf(x, alpha, n) + logres = dirichlet_multinomial.logpmf(x, alpha, n) + ref = 0.08484162895927638 + assert_allclose(res, ref) + assert_allclose(logres, np.log(ref)) + assert res.shape == logres.shape == () + + # library(extraDistr) + # options(digits=16) + # ddirmnom(c(4, 3, 2, 0, 2, 3, 5, 7, 4, 7), 37, + # c(45.01025314, 21.98739582, 15.14851365, 80.21588671, + # 52.84935481, 25.20905262, 53.85373737, 4.88568118, + # 89.06440654, 20.11359466)) + rng = np.random.default_rng(28469824356873456) + alpha = rng.uniform(0, 100, 10) + x = rng.integers(0, 10, 10) + n = np.sum(x, axis=-1) + res = dirichlet_multinomial(alpha, n).pmf(x) + logres = dirichlet_multinomial.logpmf(x, alpha, n) + ref = 3.65409306285992e-16 + assert_allclose(res, ref) + assert_allclose(logres, np.log(ref)) + + def test_pmf_logpmf_support(self): + # when the sum of the category counts does not equal the number of + # trials, the PMF is zero + rng, m, alpha, n, x = self.get_params(1) + n += 1 + assert_equal(dirichlet_multinomial(alpha, n).pmf(x), 0) + assert_equal(dirichlet_multinomial(alpha, n).logpmf(x), -np.inf) + + rng, m, alpha, n, x = self.get_params(10) + i = rng.random(size=10) > 0.5 + x[i] = np.round(x[i] * 2) # sum of these x does not equal n + assert_equal(dirichlet_multinomial(alpha, n).pmf(x)[i], 0) + assert_equal(dirichlet_multinomial(alpha, n).logpmf(x)[i], -np.inf) + assert np.all(dirichlet_multinomial(alpha, n).pmf(x)[~i] > 0) + assert np.all(dirichlet_multinomial(alpha, n).logpmf(x)[~i] > -np.inf) + + def test_dimensionality_one(self): + # if the dimensionality is one, there is only one possible outcome + n = 6 # number of trials + alpha = [10] # concentration parameters + x = np.asarray([n]) # counts + dist = dirichlet_multinomial(alpha, n) + + assert_equal(dist.pmf(x), 1) + assert_equal(dist.pmf(x+1), 0) + assert_equal(dist.logpmf(x), 0) + assert_equal(dist.logpmf(x+1), -np.inf) + assert_equal(dist.mean(), n) + assert_equal(dist.var(), 0) + assert_equal(dist.cov(), 0) + + def test_n_is_zero(self): + # similarly, only one possible outcome if n is zero + n = 0 + alpha = np.asarray([1., 1.]) + x = np.asarray([0, 0]) + dist = dirichlet_multinomial(alpha, n) + + assert_equal(dist.pmf(x), 1) + assert_equal(dist.pmf(x+1), 0) + assert_equal(dist.logpmf(x), 0) + assert_equal(dist.logpmf(x+1), -np.inf) + assert_equal(dist.mean(), [0, 0]) + assert_equal(dist.var(), [0, 0]) + assert_equal(dist.cov(), [[0, 0], [0, 0]]) + + @pytest.mark.parametrize('method_name', ['pmf', 'logpmf']) + def test_against_betabinom_pmf(self, method_name): + rng, m, alpha, n, x = self.get_params(100) + + method = getattr(dirichlet_multinomial(alpha, n), method_name) + ref_method = getattr(stats.betabinom(n, *alpha.T), method_name) + + res = method(x) + ref = ref_method(x.T[0]) + assert_allclose(res, ref) + + @pytest.mark.parametrize('method_name', ['mean', 'var']) + def test_against_betabinom_moments(self, method_name): + rng, m, alpha, n, x = self.get_params(100) + + method = getattr(dirichlet_multinomial(alpha, n), method_name) + ref_method = getattr(stats.betabinom(n, *alpha.T), method_name) + + res = method()[:, 0] + ref = ref_method() + assert_allclose(res, ref) + + def test_moments(self): + rng = np.random.default_rng(28469824356873456) + dim = 5 + n = rng.integers(1, 100) + alpha = rng.random(size=dim) * 10 + dist = dirichlet_multinomial(alpha, n) + + # Generate a random sample from the distribution using NumPy + m = 100000 + p = rng.dirichlet(alpha, size=m) + x = rng.multinomial(n, p, size=m) + + assert_allclose(dist.mean(), np.mean(x, axis=0), rtol=5e-3) + assert_allclose(dist.var(), np.var(x, axis=0), rtol=1e-2) + assert dist.mean().shape == dist.var().shape == (dim,) + + cov = dist.cov() + assert cov.shape == (dim, dim) + assert_allclose(cov, np.cov(x.T), rtol=2e-2) + assert_equal(np.diag(cov), dist.var()) + assert np.all(scipy.linalg.eigh(cov)[0] > 0) # positive definite + + def test_input_validation(self): + # valid inputs + x0 = np.array([1, 2, 3]) + n0 = np.sum(x0) + alpha0 = np.array([3, 4, 5]) + + text = "`x` must contain only non-negative integers." + with assert_raises(ValueError, match=text): + dirichlet_multinomial.logpmf([1, -1, 3], alpha0, n0) + with assert_raises(ValueError, match=text): + dirichlet_multinomial.logpmf([1, 2.1, 3], alpha0, n0) + + text = "`alpha` must contain only positive values." + with assert_raises(ValueError, match=text): + dirichlet_multinomial.logpmf(x0, [3, 0, 4], n0) + with assert_raises(ValueError, match=text): + dirichlet_multinomial.logpmf(x0, [3, -1, 4], n0) + + text = "`n` must be a non-negative integer." + with assert_raises(ValueError, match=text): + dirichlet_multinomial.logpmf(x0, alpha0, 49.1) + with assert_raises(ValueError, match=text): + dirichlet_multinomial.logpmf(x0, alpha0, -1) + + x = np.array([1, 2, 3, 4]) + alpha = np.array([3, 4, 5]) + text = "`x` and `alpha` must be broadcastable." + with assert_raises(ValueError, match=text): + dirichlet_multinomial.logpmf(x, alpha, x.sum()) + + @pytest.mark.parametrize('method', ['pmf', 'logpmf']) + def test_broadcasting_pmf(self, method): + alpha = np.array([[3, 4, 5], [4, 5, 6], [5, 5, 7], [8, 9, 10]]) + n = np.array([[6], [7], [8]]) + x = np.array([[1, 2, 3], [2, 2, 3]]).reshape((2, 1, 1, 3)) + method = getattr(dirichlet_multinomial, method) + res = method(x, alpha, n) + assert res.shape == (2, 3, 4) + for i in range(len(x)): + for j in range(len(n)): + for k in range(len(alpha)): + res_ijk = res[i, j, k] + ref = method(x[i].squeeze(), alpha[k].squeeze(), n[j].squeeze()) + assert_allclose(res_ijk, ref) + + @pytest.mark.parametrize('method_name', ['mean', 'var', 'cov']) + def test_broadcasting_moments(self, method_name): + alpha = np.array([[3, 4, 5], [4, 5, 6], [5, 5, 7], [8, 9, 10]]) + n = np.array([[6], [7], [8]]) + method = getattr(dirichlet_multinomial, method_name) + res = method(alpha, n) + assert res.shape == (3, 4, 3) if method_name != 'cov' else (3, 4, 3, 3) + for j in range(len(n)): + for k in range(len(alpha)): + res_ijk = res[j, k] + ref = method(alpha[k].squeeze(), n[j].squeeze()) + assert_allclose(res_ijk, ref) + + +class TestNormalInverseGamma: + + def test_marginal_x(self): + # According to [1], sqrt(a * lmbda / b) * (x - u) should follow a t-distribution + # with 2*a degrees of freedom. Test that this is true of the PDF and random + # variates. + rng = np.random.default_rng(8925849245) + mu, lmbda, a, b = rng.random(4) + + norm_inv_gamma = stats.normal_inverse_gamma(mu, lmbda, a, b) + t = stats.t(2*a, loc=mu, scale=1/np.sqrt(a * lmbda / b)) + + # Test PDF + x = np.linspace(-5, 5, 11) + res = tanhsinh(lambda s2, x: norm_inv_gamma.pdf(x, s2), 0, np.inf, args=(x,)) + ref = t.pdf(x) + assert_allclose(res.integral, ref) + + # Test RVS + res = norm_inv_gamma.rvs(size=10000, random_state=rng) + _, pvalue = stats.ks_1samp(res[0], t.cdf) + assert pvalue > 0.1 + + def test_marginal_s2(self): + # According to [1], s2 should follow an inverse gamma distribution with + # shapes a, b (where b is the scale in our parameterization). Test that + # this is true of the PDF and random variates. + rng = np.random.default_rng(8925849245) + mu, lmbda, a, b = rng.random(4) + + norm_inv_gamma = stats.normal_inverse_gamma(mu, lmbda, a, b) + inv_gamma = stats.invgamma(a, scale=b) + + # Test PDF + s2 = np.linspace(0.1, 10, 10) + res = tanhsinh(lambda x, s2: norm_inv_gamma.pdf(x, s2), + -np.inf, np.inf, args=(s2,)) + ref = inv_gamma.pdf(s2) + assert_allclose(res.integral, ref) + + # Test RVS + res = norm_inv_gamma.rvs(size=10000, random_state=rng) + _, pvalue = stats.ks_1samp(res[1], inv_gamma.cdf) + assert pvalue > 0.1 + + def test_pdf_logpdf(self): + # Check that PDF and log-PDF are consistent + rng = np.random.default_rng(8925849245) + mu, lmbda, a, b = rng.random((4, 20)) - 0.25 # make some invalid + x, s2 = rng.random(size=(2, 20)) - 0.25 + res = stats.normal_inverse_gamma(mu, lmbda, a, b).pdf(x, s2) + ref = stats.normal_inverse_gamma.logpdf(x, s2, mu, lmbda, a, b) + assert_allclose(res, np.exp(ref)) + + def test_invalid_and_special_cases(self): + # Test cases that are handled by input validation rather than the formulas + rng = np.random.default_rng(8925849245) + mu, lmbda, a, b = rng.random(4) + x, s2 = rng.random(2) + + res = stats.normal_inverse_gamma(np.nan, lmbda, a, b).pdf(x, s2) + assert_equal(res, np.nan) + + res = stats.normal_inverse_gamma(mu, -1, a, b).pdf(x, s2) + assert_equal(res, np.nan) + + res = stats.normal_inverse_gamma(mu, lmbda, 0, b).pdf(x, s2) + assert_equal(res, np.nan) + + res = stats.normal_inverse_gamma(mu, lmbda, a, -1).pdf(x, s2) + assert_equal(res, np.nan) + + res = stats.normal_inverse_gamma(mu, lmbda, a, b).pdf(x, -1) + assert_equal(res, 0) + + # PDF with out-of-support s2 is not zero if shape parameter is invalid + res = stats.normal_inverse_gamma(mu, [-1, np.nan], a, b).pdf(x, -1) + assert_equal(res, np.nan) + + res = stats.normal_inverse_gamma(mu, -1, a, b).mean() + assert_equal(res, (np.nan, np.nan)) + + res = stats.normal_inverse_gamma(mu, lmbda, -1, b).var() + assert_equal(res, (np.nan, np.nan)) + + with pytest.raises(ValueError, match="Domain error in arguments..."): + stats.normal_inverse_gamma(mu, lmbda, a, -1).rvs() + + def test_broadcasting(self): + # Test methods with broadcastable array parameters. Roughly speaking, the + # shapes should be the broadcasted shapes of all arguments, and the raveled + # outputs should be the same as the outputs with raveled inputs. + rng = np.random.default_rng(8925849245) + b = rng.random(2) + a = rng.random((3, 1)) + 2 # for defined moments + lmbda = rng.random((4, 1, 1)) + mu = rng.random((5, 1, 1, 1)) + s2 = rng.random((6, 1, 1, 1, 1)) + x = rng.random((7, 1, 1, 1, 1, 1)) + dist = stats.normal_inverse_gamma(mu, lmbda, a, b) + + # Test PDF and log-PDF + broadcasted = np.broadcast_arrays(x, s2, mu, lmbda, a, b) + broadcasted_raveled = [np.ravel(arr) for arr in broadcasted] + + res = dist.pdf(x, s2) + assert res.shape == broadcasted[0].shape + assert_allclose(res.ravel(), + stats.normal_inverse_gamma.pdf(*broadcasted_raveled)) + + res = dist.logpdf(x, s2) + assert res.shape == broadcasted[0].shape + assert_allclose(res.ravel(), + stats.normal_inverse_gamma.logpdf(*broadcasted_raveled)) + + # Test moments + broadcasted = np.broadcast_arrays(mu, lmbda, a, b) + broadcasted_raveled = [np.ravel(arr) for arr in broadcasted] + + res = dist.mean() + assert res[0].shape == broadcasted[0].shape + assert_allclose((res[0].ravel(), res[1].ravel()), + stats.normal_inverse_gamma.mean(*broadcasted_raveled)) + + res = dist.var() + assert res[0].shape == broadcasted[0].shape + assert_allclose((res[0].ravel(), res[1].ravel()), + stats.normal_inverse_gamma.var(*broadcasted_raveled)) + + # Test RVS + size = (6, 5, 4, 3, 2) + rng = np.random.default_rng(2348923985324) + res = dist.rvs(size=size, random_state=rng) + rng = np.random.default_rng(2348923985324) + shape = 6, 5*4*3*2 + ref = stats.normal_inverse_gamma.rvs(*broadcasted_raveled, size=shape, + random_state=rng) + assert_allclose((res[0].reshape(shape), res[1].reshape(shape)), ref) + + @pytest.mark.slow + @pytest.mark.fail_slow(10) + def test_moments(self): + # Test moments against quadrature + rng = np.random.default_rng(8925849245) + mu, lmbda, a, b = rng.random(4) + a += 2 # ensure defined + + dist = stats.normal_inverse_gamma(mu, lmbda, a, b) + res = dist.mean() + + ref = dblquad(lambda s2, x: dist.pdf(x, s2) * x, -np.inf, np.inf, 0, np.inf) + assert_allclose(res[0], ref[0], rtol=1e-6) + + ref = dblquad(lambda s2, x: dist.pdf(x, s2) * s2, -np.inf, np.inf, 0, np.inf) + assert_allclose(res[1], ref[0], rtol=1e-6) + + @pytest.mark.parametrize('dtype', [np.int32, np.float16, np.float32, np.float64]) + def test_dtype(self, dtype): + rng = np.random.default_rng(8925849245) + x, s2, mu, lmbda, a, b = rng.uniform(3, 10, size=6).astype(dtype) + dtype_out = np.result_type(1.0, dtype) + dist = stats.normal_inverse_gamma(mu, lmbda, a, b) + assert dist.rvs()[0].dtype == dtype_out + assert dist.rvs()[1].dtype == dtype_out + assert dist.mean()[0].dtype == dtype_out + assert dist.mean()[1].dtype == dtype_out + assert dist.var()[0].dtype == dtype_out + assert dist.var()[1].dtype == dtype_out + assert dist.logpdf(x, s2).dtype == dtype_out + assert dist.pdf(x, s2).dtype == dtype_out diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/test_new_distributions.py b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_new_distributions.py new file mode 100644 index 0000000000000000000000000000000000000000..b266b07b858ee1571e988c59af23e6348a219e4d --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_new_distributions.py @@ -0,0 +1,17 @@ +# file for distribution-specific tests with new infrastructure (UnivariateDistribution) +import numpy as np +from numpy.testing import assert_allclose +from scipy import stats + +class TestBinomial: + def test_gh23708_binomial_logcdf_method_complement(self): + # gh-23708 found that `logcdf` method='complement' was inaccurate in the tails + x = np.asarray([0., 18.]) + X = stats.Binomial(n=np.asarray([18.]), p=np.asarray(0.71022842)) + assert_allclose(X.logcdf(x, method='complement'), X.logcdf(x), rtol=1e-15) + assert_allclose(X.logccdf(x, method='complement'), X.logccdf(x), rtol=1e-15) + + # going even deeper into the tails + X = stats.Binomial(n=100, p=0.5) + assert_allclose(X.logcdf(0, method='complement'), X.logpmf(0), rtol=1e-15) + assert_allclose(X.logccdf(99, method='complement'), X.logpmf(100), rtol=1e-15) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/test_odds_ratio.py b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_odds_ratio.py new file mode 100644 index 0000000000000000000000000000000000000000..14b5ca88d0fca9edb8c3c161c06e52054d594e62 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_odds_ratio.py @@ -0,0 +1,148 @@ +import pytest +import numpy as np +from numpy.testing import assert_equal, assert_allclose +from .._discrete_distns import nchypergeom_fisher, hypergeom +from scipy.stats._odds_ratio import odds_ratio +from .data.fisher_exact_results_from_r import data + + +class TestOddsRatio: + + @pytest.mark.parametrize('parameters, rresult', data) + def test_results_from_r(self, parameters, rresult): + alternative = parameters.alternative.replace('.', '-') + result = odds_ratio(parameters.table) + # The results computed by R are not very accurate. + if result.statistic < 400: + or_rtol = 5e-4 + ci_rtol = 2e-2 + else: + or_rtol = 5e-2 + ci_rtol = 1e-1 + assert_allclose(result.statistic, + rresult.conditional_odds_ratio, rtol=or_rtol) + ci = result.confidence_interval(parameters.confidence_level, + alternative) + assert_allclose((ci.low, ci.high), rresult.conditional_odds_ratio_ci, + rtol=ci_rtol) + + # Also do a self-check for the conditional odds ratio. + # With the computed conditional odds ratio as the noncentrality + # parameter of the noncentral hypergeometric distribution with + # parameters table.sum(), table[0].sum(), and table[:,0].sum() as + # total, ngood and nsample, respectively, the mean of the distribution + # should equal table[0, 0]. + cor = result.statistic + table = np.array(parameters.table) + total = table.sum() + ngood = table[0].sum() + nsample = table[:, 0].sum() + # nchypergeom_fisher does not allow the edge cases where the + # noncentrality parameter is 0 or inf, so handle those values + # separately here. + if cor == 0: + nchg_mean = hypergeom.support(total, ngood, nsample)[0] + elif cor == np.inf: + nchg_mean = hypergeom.support(total, ngood, nsample)[1] + else: + nchg_mean = nchypergeom_fisher.mean(total, ngood, nsample, cor) + assert_allclose(nchg_mean, table[0, 0], rtol=1e-13) + + # Check that the confidence interval is correct. + alpha = 1 - parameters.confidence_level + if alternative == 'two-sided': + if ci.low > 0: + sf = nchypergeom_fisher.sf(table[0, 0] - 1, + total, ngood, nsample, ci.low) + assert_allclose(sf, alpha/2, rtol=1e-11) + if np.isfinite(ci.high): + cdf = nchypergeom_fisher.cdf(table[0, 0], + total, ngood, nsample, ci.high) + assert_allclose(cdf, alpha/2, rtol=1e-11) + elif alternative == 'less': + if np.isfinite(ci.high): + cdf = nchypergeom_fisher.cdf(table[0, 0], + total, ngood, nsample, ci.high) + assert_allclose(cdf, alpha, rtol=1e-11) + else: + # alternative == 'greater' + if ci.low > 0: + sf = nchypergeom_fisher.sf(table[0, 0] - 1, + total, ngood, nsample, ci.low) + assert_allclose(sf, alpha, rtol=1e-11) + + @pytest.mark.parametrize('table', [ + [[0, 0], [5, 10]], + [[5, 10], [0, 0]], + [[0, 5], [0, 10]], + [[5, 0], [10, 0]], + ]) + def test_row_or_col_zero(self, table): + result = odds_ratio(table) + assert_equal(result.statistic, np.nan) + ci = result.confidence_interval() + assert_equal((ci.low, ci.high), (0, np.inf)) + + @pytest.mark.parametrize("case", + [[0.95, 'two-sided', 0.4879913, 2.635883], + [0.90, 'two-sided', 0.5588516, 2.301663]]) + def test_sample_odds_ratio_ci(self, case): + # Compare the sample odds ratio confidence interval to the R function + # oddsratio.wald from the epitools package, e.g. + # > library(epitools) + # > table = matrix(c(10, 20, 41, 93), nrow=2, ncol=2, byrow=TRUE) + # > result = oddsratio.wald(table) + # > result$measure + # odds ratio with 95% C.I. + # Predictor estimate lower upper + # Exposed1 1.000000 NA NA + # Exposed2 1.134146 0.4879913 2.635883 + + confidence_level, alternative, ref_low, ref_high = case + table = [[10, 20], [41, 93]] + result = odds_ratio(table, kind='sample') + assert_allclose(result.statistic, 1.134146, rtol=1e-6) + ci = result.confidence_interval(confidence_level, alternative) + assert_allclose([ci.low, ci.high], [ref_low, ref_high], rtol=1e-6) + + @pytest.mark.slow + @pytest.mark.parametrize('alternative', ['less', 'greater', 'two-sided']) + def test_sample_odds_ratio_one_sided_ci(self, alternative): + # can't find a good reference for one-sided CI, so bump up the sample + # size and compare against the conditional odds ratio CI + table = [[1000, 2000], [4100, 9300]] + res = odds_ratio(table, kind='sample') + ref = odds_ratio(table, kind='conditional') + assert_allclose(res.statistic, ref.statistic, atol=1e-5) + assert_allclose(res.confidence_interval(alternative=alternative), + ref.confidence_interval(alternative=alternative), + atol=2e-3) + + @pytest.mark.parametrize('kind', ['sample', 'conditional']) + @pytest.mark.parametrize('bad_table', [123, "foo", [10, 11, 12]]) + def test_invalid_table_shape(self, kind, bad_table): + with pytest.raises(ValueError, match="Invalid shape"): + odds_ratio(bad_table, kind=kind) + + def test_invalid_table_type(self): + with pytest.raises(ValueError, match='must be an array of integers'): + odds_ratio([[1.0, 3.4], [5.0, 9.9]]) + + def test_negative_table_values(self): + with pytest.raises(ValueError, match='must be nonnegative'): + odds_ratio([[1, 2], [3, -4]]) + + def test_invalid_kind(self): + with pytest.raises(ValueError, match='`kind` must be'): + odds_ratio([[10, 20], [30, 14]], kind='magnetoreluctance') + + def test_invalid_alternative(self): + result = odds_ratio([[5, 10], [2, 32]]) + with pytest.raises(ValueError, match='`alternative` must be'): + result.confidence_interval(alternative='depleneration') + + @pytest.mark.parametrize('level', [-0.5, 1.5]) + def test_invalid_confidence_level(self, level): + result = odds_ratio([[5, 10], [2, 32]]) + with pytest.raises(ValueError, match='must be between 0 and 1'): + result.confidence_interval(confidence_level=level) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/test_qmc.py b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_qmc.py new file mode 100644 index 0000000000000000000000000000000000000000..650a16e975c30e6df5b5a2b197d92d2a7f972ed3 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_qmc.py @@ -0,0 +1,1535 @@ +import os +from collections import Counter +from itertools import combinations, product + +import pytest +import numpy as np +from numpy.testing import (assert_allclose, assert_equal, assert_array_equal, + assert_array_less) + +from scipy.spatial import distance +from scipy.stats import shapiro +from scipy.stats._sobol import _test_find_index, _test_low_0_bit +from scipy.stats import qmc +from scipy.stats._qmc import ( + van_der_corput, n_primes, primes_from_2_to, + update_discrepancy, QMCEngine, _l1_norm, + _perturb_discrepancy, _lloyd_centroidal_voronoi_tessellation +) + + +class TestUtils: + def test_scale(self): + # 1d scalar + space = [[0], [1], [0.5]] + out = [[-2], [6], [2]] + scaled_space = qmc.scale(space, l_bounds=-2, u_bounds=6) + + assert_allclose(scaled_space, out) + + # 2d space + space = [[0, 0], [1, 1], [0.5, 0.5]] + bounds = np.array([[-2, 0], [6, 5]]) + out = [[-2, 0], [6, 5], [2, 2.5]] + + scaled_space = qmc.scale(space, l_bounds=bounds[0], u_bounds=bounds[1]) + + assert_allclose(scaled_space, out) + + scaled_back_space = qmc.scale(scaled_space, l_bounds=bounds[0], + u_bounds=bounds[1], reverse=True) + assert_allclose(scaled_back_space, space) + + # broadcast + space = [[0, 0, 0], [1, 1, 1], [0.5, 0.5, 0.5]] + l_bounds, u_bounds = 0, [6, 5, 3] + out = [[0, 0, 0], [6, 5, 3], [3, 2.5, 1.5]] + + scaled_space = qmc.scale(space, l_bounds=l_bounds, u_bounds=u_bounds) + + assert_allclose(scaled_space, out) + + def test_scale_random(self): + rng = np.random.default_rng(317589836511269190194010915937762468165) + sample = rng.random((30, 10)) + a = -rng.random(10) * 10 + b = rng.random(10) * 10 + scaled = qmc.scale(sample, a, b, reverse=False) + unscaled = qmc.scale(scaled, a, b, reverse=True) + assert_allclose(unscaled, sample) + + def test_scale_errors(self): + with pytest.raises(ValueError, match=r"Sample is not a 2D array"): + space = [0, 1, 0.5] + qmc.scale(space, l_bounds=-2, u_bounds=6) + + with pytest.raises(ValueError, match=r"Bounds are not consistent"): + space = [[0, 0], [1, 1], [0.5, 0.5]] + bounds = np.array([[-2, 6], [6, 5]]) + qmc.scale(space, l_bounds=bounds[0], u_bounds=bounds[1]) + + with pytest.raises(ValueError, match=r"'l_bounds' and 'u_bounds'" + r" must be broadcastable"): + space = [[0, 0], [1, 1], [0.5, 0.5]] + l_bounds, u_bounds = [-2, 0, 2], [6, 5] + qmc.scale(space, l_bounds=l_bounds, u_bounds=u_bounds) + + with pytest.raises(ValueError, match=r"'l_bounds' and 'u_bounds'" + r" must be broadcastable"): + space = [[0, 0], [1, 1], [0.5, 0.5]] + bounds = np.array([[-2, 0, 2], [6, 5, 5]]) + qmc.scale(space, l_bounds=bounds[0], u_bounds=bounds[1]) + + with pytest.raises(ValueError, match=r"Sample is not in unit " + r"hypercube"): + space = [[0, 0], [1, 1.5], [0.5, 0.5]] + bounds = np.array([[-2, 0], [6, 5]]) + qmc.scale(space, l_bounds=bounds[0], u_bounds=bounds[1]) + + with pytest.raises(ValueError, match=r"Sample is out of bounds"): + out = [[-2, 0], [6, 5], [8, 2.5]] + bounds = np.array([[-2, 0], [6, 5]]) + qmc.scale(out, l_bounds=bounds[0], u_bounds=bounds[1], + reverse=True) + + def test_discrepancy(self): + space_1 = np.array([[1, 3], [2, 6], [3, 2], [4, 5], [5, 1], [6, 4]]) + space_1 = (2.0 * space_1 - 1.0) / (2.0 * 6.0) + space_2 = np.array([[1, 5], [2, 4], [3, 3], [4, 2], [5, 1], [6, 6]]) + space_2 = (2.0 * space_2 - 1.0) / (2.0 * 6.0) + + # From Fang et al. Design and modeling for computer experiments, 2006 + assert_allclose(qmc.discrepancy(space_1), 0.0081, atol=1e-4) + assert_allclose(qmc.discrepancy(space_2), 0.0105, atol=1e-4) + + # From Zhou Y.-D. et al. Mixture discrepancy for quasi-random point + # sets. Journal of Complexity, 29 (3-4), pp. 283-301, 2013. + # Example 4 on Page 298 + sample = np.array([[2, 1, 1, 2, 2, 2], + [1, 2, 2, 2, 2, 2], + [2, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 2, 2], + [1, 2, 2, 2, 1, 1], + [2, 2, 2, 2, 1, 1], + [2, 2, 2, 1, 2, 2]]) + sample = (2.0 * sample - 1.0) / (2.0 * 2.0) + + assert_allclose(qmc.discrepancy(sample, method='MD'), 2.5000, + atol=1e-4) + assert_allclose(qmc.discrepancy(sample, method='WD'), 1.3680, + atol=1e-4) + assert_allclose(qmc.discrepancy(sample, method='CD'), 0.3172, + atol=1e-4) + + # From Tim P. et al. Minimizing the L2 and Linf star discrepancies + # of a single point in the unit hypercube. JCAM, 2005 + # Table 1 on Page 283 + for dim in [2, 4, 8, 16, 32, 64]: + ref = np.sqrt(3**(-dim)) + assert_allclose(qmc.discrepancy(np.array([[1]*dim]), + method='L2-star'), ref) + + def test_discrepancy_errors(self): + sample = np.array([[1, 3], [2, 6], [3, 2], [4, 5], [5, 1], [6, 4]]) + + with pytest.raises( + ValueError, match=r"Sample is not in unit hypercube" + ): + qmc.discrepancy(sample) + + with pytest.raises(ValueError, match=r"Sample is not a 2D array"): + qmc.discrepancy([1, 3]) + + sample = [[0, 0], [1, 1], [0.5, 0.5]] + with pytest.raises(ValueError, match=r"'toto' is not a valid ..."): + qmc.discrepancy(sample, method="toto") + + def test_discrepancy_parallel(self, monkeypatch): + sample = np.array([[2, 1, 1, 2, 2, 2], + [1, 2, 2, 2, 2, 2], + [2, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 2, 2], + [1, 2, 2, 2, 1, 1], + [2, 2, 2, 2, 1, 1], + [2, 2, 2, 1, 2, 2]]) + sample = (2.0 * sample - 1.0) / (2.0 * 2.0) + + assert_allclose(qmc.discrepancy(sample, method='MD', workers=8), + 2.5000, + atol=1e-4) + assert_allclose(qmc.discrepancy(sample, method='WD', workers=8), + 1.3680, + atol=1e-4) + assert_allclose(qmc.discrepancy(sample, method='CD', workers=8), + 0.3172, + atol=1e-4) + + # From Tim P. et al. Minimizing the L2 and Linf star discrepancies + # of a single point in the unit hypercube. JCAM, 2005 + # Table 1 on Page 283 + for dim in [2, 4, 8, 16, 32, 64]: + ref = np.sqrt(3 ** (-dim)) + assert_allclose(qmc.discrepancy(np.array([[1] * dim]), + method='L2-star', workers=-1), ref) + + monkeypatch.setattr(os, 'cpu_count', lambda: None) + with pytest.raises(NotImplementedError, match="Cannot determine the"): + qmc.discrepancy(sample, workers=-1) + + with pytest.raises(ValueError, match="Invalid number of workers..."): + qmc.discrepancy(sample, workers=-2) + + def test_geometric_discrepancy_errors(self): + sample = np.array([[1, 3], [2, 6], [3, 2], [4, 5], [5, 1], [6, 4]]) + + with pytest.raises(ValueError, match=r"Sample is not in unit hypercube"): + qmc.geometric_discrepancy(sample) + + with pytest.raises(ValueError, match=r"Sample is not a 2D array"): + qmc.geometric_discrepancy([1, 3]) + + sample = [[0, 0], [1, 1], [0.5, 0.5]] + with pytest.raises(ValueError, match=r"'toto' is not a valid ..."): + qmc.geometric_discrepancy(sample, method="toto") + + sample = np.array([[0, 0], [0, 0], [0, 1]]) + with pytest.warns(UserWarning, match="Sample contains duplicate points."): + qmc.geometric_discrepancy(sample) + + sample = np.array([[0.5, 0.5]]) + with pytest.raises(ValueError, match="Sample must contain at least two points"): + qmc.geometric_discrepancy(sample) + + def test_geometric_discrepancy(self): + sample = np.array([[0, 0], [1, 1]]) + assert_allclose(qmc.geometric_discrepancy(sample), np.sqrt(2)) + assert_allclose(qmc.geometric_discrepancy(sample, method="mst"), np.sqrt(2)) + + sample = np.array([[0, 0], [0, 1], [0.5, 1]]) + assert_allclose(qmc.geometric_discrepancy(sample), 0.5) + assert_allclose(qmc.geometric_discrepancy(sample, method="mst"), 0.75) + + sample = np.array([[0, 0], [0.25, 0.25], [1, 1]]) + assert_allclose(qmc.geometric_discrepancy(sample), np.sqrt(2) / 4) + assert_allclose(qmc.geometric_discrepancy(sample, method="mst"), np.sqrt(2) / 2) + assert_allclose(qmc.geometric_discrepancy(sample, metric="chebyshev"), 0.25) + assert_allclose( + qmc.geometric_discrepancy(sample, method="mst", metric="chebyshev"), 0.5 + ) + + rng = np.random.default_rng(191468432622931918890291693003068437394) + sample = qmc.LatinHypercube(d=3, rng=rng).random(50) + assert_allclose(qmc.geometric_discrepancy(sample), 0.05106012076093356) + assert_allclose( + qmc.geometric_discrepancy(sample, method='mst'), 0.19704396643366182 + ) + + @pytest.mark.xfail( + reason="minimum_spanning_tree ignores zero distances (#18892)", + strict=True, + ) + @pytest.mark.thread_unsafe + def test_geometric_discrepancy_mst_with_zero_distances(self): + sample = np.array([[0, 0], [0, 0], [0, 1]]) + assert_allclose(qmc.geometric_discrepancy(sample, method='mst'), 0.5) + + def test_update_discrepancy(self): + # From Fang et al. Design and modeling for computer experiments, 2006 + space_1 = np.array([[1, 3], [2, 6], [3, 2], [4, 5], [5, 1], [6, 4]]) + space_1 = (2.0 * space_1 - 1.0) / (2.0 * 6.0) + + disc_init = qmc.discrepancy(space_1[:-1], iterative=True) + disc_iter = update_discrepancy(space_1[-1], space_1[:-1], disc_init) + + assert_allclose(disc_iter, 0.0081, atol=1e-4) + + # n QMCEngine: + # preserve use of `seed` during SPEC 7 transition because + # some tests rely on behavior with integer `seed` (which is + # different from behavior with integer `rng`) + if self.can_scramble: + return self.qmce(scramble=scramble, seed=rng, **kwargs) + else: + if scramble: + pytest.skip() + else: + return self.qmce(seed=rng, **kwargs) + + def reference(self, scramble: bool) -> np.ndarray: + return self.scramble_nd if scramble else self.unscramble_nd + + @pytest.mark.parametrize("scramble", scramble, ids=ids) + def test_0dim(self, scramble): + engine = self.engine(d=0, scramble=scramble) + sample = engine.random(4) + assert_array_equal(np.empty((4, 0)), sample) + + @pytest.mark.parametrize("scramble", scramble, ids=ids) + def test_0sample(self, scramble): + engine = self.engine(d=2, scramble=scramble) + sample = engine.random(0) + assert_array_equal(np.empty((0, 2)), sample) + + @pytest.mark.parametrize("scramble", scramble, ids=ids) + def test_1sample(self, scramble): + engine = self.engine(d=2, scramble=scramble) + sample = engine.random(1) + assert (1, 2) == sample.shape + + @pytest.mark.parametrize("scramble", scramble, ids=ids) + def test_bounds(self, scramble): + engine = self.engine(d=100, scramble=scramble) + sample = engine.random(512) + assert np.all(sample >= 0) + assert np.all(sample <= 1) + + @pytest.mark.parametrize("scramble", scramble, ids=ids) + def test_sample(self, scramble): + ref_sample = self.reference(scramble=scramble) + engine = self.engine(d=2, scramble=scramble) + sample = engine.random(n=len(ref_sample)) + + assert_allclose(sample, ref_sample, atol=1e-1) + assert engine.num_generated == len(ref_sample) + + @pytest.mark.parametrize("scramble", scramble, ids=ids) + def test_continuing(self, scramble): + engine = self.engine(d=2, scramble=scramble) + ref_sample = engine.random(n=8) + + engine = self.engine(d=2, scramble=scramble) + + n_half = len(ref_sample) // 2 + + _ = engine.random(n=n_half) + sample = engine.random(n=n_half) + assert_allclose(sample, ref_sample[n_half:], atol=1e-1) + + @pytest.mark.parametrize("scramble", scramble, ids=ids) + @pytest.mark.parametrize( + "rng", + ( + 170382760648021597650530316304495310428, + lambda: np.random.default_rng(170382760648021597650530316304495310428), + pytest.param(None, marks=pytest.mark.thread_unsafe), + ), + ) + def test_reset(self, scramble, rng): + if callable(rng): + # Initialize local RNG here to make it thread-local in pytest-run-parallel + rng = rng() + engine = self.engine(d=2, scramble=scramble, rng=rng) + ref_sample = engine.random(n=8) + + engine.reset() + assert engine.num_generated == 0 + + sample = engine.random(n=8) + assert_allclose(sample, ref_sample) + + @pytest.mark.parametrize("scramble", scramble, ids=ids) + def test_fast_forward(self, scramble): + engine = self.engine(d=2, scramble=scramble) + ref_sample = engine.random(n=8) + + engine = self.engine(d=2, scramble=scramble) + + engine.fast_forward(4) + sample = engine.random(n=4) + + assert_allclose(sample, ref_sample[4:], atol=1e-1) + + # alternate fast forwarding with sampling + engine.reset() + even_draws = [] + for i in range(8): + if i % 2 == 0: + even_draws.append(engine.random()) + else: + engine.fast_forward(1) + assert_allclose( + ref_sample[[i for i in range(8) if i % 2 == 0]], + np.concatenate(even_draws), + atol=1e-5 + ) + + @pytest.mark.parametrize("scramble", [True]) + def test_distribution(self, scramble): + d = 50 + engine = self.engine(d=d, scramble=scramble) + sample = engine.random(1024) + assert_allclose( + np.mean(sample, axis=0), np.repeat(0.5, d), atol=1e-2 + ) + assert_allclose( + np.percentile(sample, 25, axis=0), np.repeat(0.25, d), atol=1e-2 + ) + assert_allclose( + np.percentile(sample, 75, axis=0), np.repeat(0.75, d), atol=1e-2 + ) + + def test_raises_optimizer(self): + message = r"'toto' is not a valid optimization method" + with pytest.raises(ValueError, match=message): + self.engine(d=1, scramble=False, optimization="toto") + + @pytest.mark.parametrize( + "optimization,metric", + [ + ("random-CD", qmc.discrepancy), + ("lloyd", lambda sample: -_l1_norm(sample))] + ) + def test_optimizers(self, optimization, metric): + engine = self.engine(d=2, scramble=False) + sample_ref = engine.random(n=64) + metric_ref = metric(sample_ref) + + optimal_ = self.engine(d=2, scramble=False, optimization=optimization) + sample_ = optimal_.random(n=64) + metric_ = metric(sample_) + + assert metric_ < metric_ref + + def test_consume_prng_state(self): + rng = np.random.default_rng(0xa29cabb11cfdf44ff6cac8bec254c2a0) + sample = [] + for i in range(3): + engine = self.engine(d=2, scramble=True, rng=rng) + sample.append(engine.random(4)) + + with pytest.raises(AssertionError, match="Arrays are not equal"): + assert_equal(sample[0], sample[1]) + with pytest.raises(AssertionError, match="Arrays are not equal"): + assert_equal(sample[0], sample[2]) + + +class TestHalton(QMCEngineTests): + qmce = qmc.Halton + can_scramble = True + # theoretical values known from Van der Corput + unscramble_nd = np.array([[0, 0], [1 / 2, 1 / 3], + [1 / 4, 2 / 3], [3 / 4, 1 / 9], + [1 / 8, 4 / 9], [5 / 8, 7 / 9], + [3 / 8, 2 / 9], [7 / 8, 5 / 9]]) + # theoretical values unknown: convergence properties checked + scramble_nd = np.array([[0.50246036, 0.93382481], + [0.00246036, 0.26715815], + [0.75246036, 0.60049148], + [0.25246036, 0.8227137 ], + [0.62746036, 0.15604704], + [0.12746036, 0.48938037], + [0.87746036, 0.71160259], + [0.37746036, 0.04493592]]) + + def test_workers(self): + ref_sample = self.reference(scramble=True) + engine = self.engine(d=2, scramble=True) + sample = engine.random(n=len(ref_sample), workers=8) + + assert_allclose(sample, ref_sample, atol=1e-3) + + # worker + integers + engine.reset() + ref_sample = engine.integers(10) + engine.reset() + sample = engine.integers(10, workers=8) + assert_equal(sample, ref_sample) + + +class TestLHS(QMCEngineTests): + qmce = qmc.LatinHypercube + can_scramble = True + + def test_continuing(self, *args): + pytest.skip("Not applicable: not a sequence.") + + def test_fast_forward(self, *args): + pytest.skip("Not applicable: not a sequence.") + + def test_sample(self, *args): + pytest.skip("Not applicable: the value of reference sample is" + " implementation dependent.") + + @pytest.mark.parametrize("strength", [1, 2]) + @pytest.mark.parametrize("scramble", [False, True]) + @pytest.mark.parametrize("optimization", [None, "random-CD"]) + def test_sample_stratified(self, optimization, scramble, strength): + rng = np.random.default_rng(37511836202578819870665127532742111260) + p = 5 + n = p**2 + d = 6 + + engine = qmc.LatinHypercube(d=d, scramble=scramble, + strength=strength, + optimization=optimization, + rng=rng) + sample = engine.random(n=n) + assert sample.shape == (n, d) + assert engine.num_generated == n + + # centering stratifies samples in the middle of equal segments: + # * inter-sample distance is constant in 1D sub-projections + # * after ordering, columns are equal + expected1d = (np.arange(n) + 0.5) / n + expected = np.broadcast_to(expected1d, (d, n)).T + assert np.any(sample != expected) + + sorted_sample = np.sort(sample, axis=0) + tol = 0.5 / n if scramble else 0 + + assert_allclose(sorted_sample, expected, atol=tol) + assert np.any(sample - expected > tol) + + if strength == 2 and optimization is None: + unique_elements = np.arange(p) + desired = set(product(unique_elements, unique_elements)) + + for i, j in combinations(range(engine.d), 2): + samples_2d = sample[:, [i, j]] + res = (samples_2d * p).astype(int) + res_set = {tuple(row) for row in res} + assert_equal(res_set, desired) + + def test_optimizer_1d(self): + # discrepancy measures are invariant under permuting factors and runs + engine = self.engine(d=1, scramble=False) + sample_ref = engine.random(n=64) + + optimal_ = self.engine(d=1, scramble=False, optimization="random-CD") + sample_ = optimal_.random(n=64) + + assert_array_equal(sample_ref, sample_) + + def test_raises(self): + message = r"not a valid strength" + with pytest.raises(ValueError, match=message): + qmc.LatinHypercube(1, strength=3) + + message = r"n is not the square of a prime number" + with pytest.raises(ValueError, match=message): + engine = qmc.LatinHypercube(d=2, strength=2) + engine.random(16) + + message = r"n is not the square of a prime number" + with pytest.raises(ValueError, match=message): + engine = qmc.LatinHypercube(d=2, strength=2) + engine.random(5) # because int(sqrt(5)) would result in 2 + + message = r"n is too small for d" + with pytest.raises(ValueError, match=message): + engine = qmc.LatinHypercube(d=5, strength=2) + engine.random(9) + + +class TestSobol(QMCEngineTests): + qmce = qmc.Sobol + can_scramble = True + # theoretical values from Joe Kuo2010 + unscramble_nd = np.array([[0., 0.], + [0.5, 0.5], + [0.75, 0.25], + [0.25, 0.75], + [0.375, 0.375], + [0.875, 0.875], + [0.625, 0.125], + [0.125, 0.625]]) + + # theoretical values unknown: convergence properties checked + scramble_nd = np.array([[0.25331921, 0.41371179], + [0.8654213, 0.9821167], + [0.70097554, 0.03664616], + [0.18027647, 0.60895735], + [0.10521339, 0.21897069], + [0.53019685, 0.66619033], + [0.91122276, 0.34580743], + [0.45337471, 0.78912079]]) + + def test_warning(self): + with pytest.warns(UserWarning, match=r"The balance properties of " + r"Sobol' points"): + engine = qmc.Sobol(1) + engine.random(10) + + def test_random_base2(self): + engine = qmc.Sobol(2, scramble=False) + sample = engine.random_base2(2) + assert_array_equal(self.unscramble_nd[:4], sample) + + # resampling still having N=2**n + sample = engine.random_base2(2) + assert_array_equal(self.unscramble_nd[4:8], sample) + + # resampling again but leading to N!=2**n + with pytest.raises(ValueError, match=r"The balance properties of " + r"Sobol' points"): + engine.random_base2(2) + + def test_raise(self): + with pytest.raises(ValueError, match=r"Maximum supported " + r"dimensionality"): + qmc.Sobol(qmc.Sobol.MAXDIM + 1) + + with pytest.raises(ValueError, match=r"Maximum supported " + r"'bits' is 64"): + qmc.Sobol(1, bits=65) + + def test_high_dim(self): + engine = qmc.Sobol(1111, scramble=False) + count1 = Counter(engine.random().flatten().tolist()) + count2 = Counter(engine.random().flatten().tolist()) + assert_equal(count1, Counter({0.0: 1111})) + assert_equal(count2, Counter({0.5: 1111})) + + @pytest.mark.parametrize("bits", [2, 3]) + def test_bits(self, bits): + engine = qmc.Sobol(2, scramble=False, bits=bits) + ns = 2**bits + sample = engine.random(ns) + assert_array_equal(self.unscramble_nd[:ns], sample) + + with pytest.raises(ValueError, match="increasing `bits`"): + engine.random() + + def test_64bits(self): + engine = qmc.Sobol(2, scramble=False, bits=64) + sample = engine.random(8) + assert_array_equal(self.unscramble_nd, sample) + + +class TestLow0Bit: + def test_examples(self): + test_vector = [ + # from low_0_bit's docstring + (0b0000, 1), + (0b0001, 2), + (0b0010, 1), + (0b0101, 2), + (0b0111, 4), + # gh-23409 + (2 ** 32 - 1, 33), + (2 ** 32, 1), + (2 ** 33 - 1, 34), + (2 ** 64 - 1, 65), + ] + for in_, out in test_vector: + assert_equal(_test_low_0_bit(in_), out) + + +class TestPoisson(QMCEngineTests): + qmce = qmc.PoissonDisk + can_scramble = False + + def test_bounds(self, *args): + pytest.skip("Too costly in memory.") + + def test_fast_forward(self, *args): + pytest.skip("Not applicable: recursive process.") + + def test_sample(self, *args): + pytest.skip("Not applicable: the value of reference sample is" + " implementation dependent.") + + def test_continuing(self, *args): + # can continue a sampling, but will not preserve the same order + # because candidates are lost, so we will not select the same center + radius = 0.05 + ns = 6 + engine = self.engine(d=2, radius=radius, scramble=False) + + sample_init = engine.random(n=ns) + assert len(sample_init) <= ns + assert l2_norm(sample_init) >= radius + + sample_continued = engine.random(n=ns) + assert len(sample_continued) <= ns + assert l2_norm(sample_continued) >= radius + + sample = np.concatenate([sample_init, sample_continued], axis=0) + assert len(sample) <= ns * 2 + assert l2_norm(sample) >= radius + + def test_mindist(self): + rng = np.random.default_rng(132074951149370773672162394161442690287) + ns = 50 + + low, high = 0.08, 0.2 + radii = (high - low) * rng.random(5) + low + + dimensions = [1, 3, 4] + hypersphere_methods = ["volume", "surface"] + + gen = product(dimensions, radii, hypersphere_methods) + + for d, radius, hypersphere in gen: + engine = self.qmce( + d=d, radius=radius, hypersphere=hypersphere, rng=rng + ) + sample = engine.random(ns) + + assert len(sample) <= ns + assert l2_norm(sample) >= radius + + @pytest.mark.parametrize("l_bounds, u_bounds", # add bounds to test gh-22819 + [(None, None), ([-5, -5], [5, 5]), ([1.1, 1.1], [5.1, 5.1])]) + def test_fill_space(self, l_bounds, u_bounds): + radius = 0.2 + engine = self.qmce(d=2, radius=radius, l_bounds=l_bounds, u_bounds=u_bounds) + + sample = engine.fill_space() + # rtol to accommodate Accelerate; see gh-24486 + rtol = 1e-6 + assert l2_norm(sample) >= radius * (1 - rtol) + + def test_bounds_shift_scale(self): + # test a reasonable property: as long as shape of region is a hypercube, + # bounds just shift and scale the sample. + radius = 0.2 # arbitrary parameters + shift = np.asarray([-2.1, 3.4]) + scale = 2.6 + + rng = np.random.default_rng(8504196751) + engine = self.qmce(d=2, radius=radius*scale, rng=rng, + l_bounds=shift, u_bounds=shift + scale) + res = engine.fill_space() + + rng = np.random.default_rng(8504196751) + engine = self.qmce(d=2, radius=radius, rng=rng) + ref = engine.fill_space() + + # circle packing problem is np complex + assert l2_norm(res) >= radius + assert_allclose(res, ref*scale + shift) + + @pytest.mark.parametrize("l_bounds", [[-1, -2, -1], [1, 2, 1]]) + def test_sample_inside_lower_bounds(self, l_bounds): + radius = 0.2 + u_bounds=[3, 3, 2] + engine = self.qmce( + d=3, radius=radius, l_bounds=l_bounds, u_bounds=u_bounds + ) + sample = engine.random(30) + + for point in sample: + assert_array_less(point, u_bounds) + assert_array_less(l_bounds, point) + + @pytest.mark.parametrize("u_bounds", [[-1, -2, -1], [1, 2, 1]]) + def test_sample_inside_upper_bounds(self, u_bounds): + radius = 0.2 + l_bounds=[-3, -3, -2] + engine = self.qmce( + d=3, radius=radius, l_bounds=l_bounds, u_bounds=u_bounds + ) + sample = engine.random(30) + + for point in sample: + assert_array_less(point, u_bounds) + assert_array_less(l_bounds, point) + + def test_inconsistent_bound_value(self): + radius = 0.2 + l_bounds=[3, 2, 1] + u_bounds=[-1, -2, -1] + with pytest.raises( + ValueError, + match="Bounds are not consistent 'l_bounds' < 'u_bounds'"): + self.qmce(d=3, radius=radius, l_bounds=l_bounds, u_bounds=u_bounds) + + @pytest.mark.parametrize("u_bounds", [[-1, -2, -1], [-1, -2]]) + @pytest.mark.parametrize("l_bounds", [[3, 2]]) + def test_inconsistent_bounds(self, u_bounds, l_bounds): + radius = 0.2 + with pytest.raises( + ValueError, + match="'l_bounds' and 'u_bounds' must be broadcastable and respect" + " the sample dimension"): + self.qmce( + d=3, radius=radius, + l_bounds=l_bounds, u_bounds=u_bounds + ) + + def test_raises(self): + message = r"'toto' is not a valid hypersphere sampling" + with pytest.raises(ValueError, match=message): + qmc.PoissonDisk(1, hypersphere="toto") + + +class TestMultinomialQMC: + def test_validations(self): + # negative Ps + p = np.array([0.12, 0.26, -0.05, 0.35, 0.22]) + with pytest.raises(ValueError, match=r"Elements of pvals must " + r"be non-negative."): + qmc.MultinomialQMC(p, n_trials=10) + + # sum of P too large + p = np.array([0.12, 0.26, 0.1, 0.35, 0.22]) + message = r"Elements of pvals must sum to 1." + with pytest.raises(ValueError, match=message): + qmc.MultinomialQMC(p, n_trials=10) + + p = np.array([0.12, 0.26, 0.05, 0.35, 0.22]) + + message = r"Dimension of `engine` must be 1." + with pytest.raises(ValueError, match=message): + qmc.MultinomialQMC(p, n_trials=10, engine=qmc.Sobol(d=2)) + + message = r"`engine` must be an instance of..." + with pytest.raises(ValueError, match=message): + qmc.MultinomialQMC(p, n_trials=10, engine=np.random.default_rng()) + + @pytest.mark.filterwarnings('ignore::UserWarning') + def test_MultinomialBasicDraw(self): + rng = np.random.default_rng(6955663962957011631562466584467607969) + p = np.array([0.12, 0.26, 0.05, 0.35, 0.22]) + n_trials = 100 + expected = np.atleast_2d(n_trials * p).astype(int) + # preserve use of legacy keyword during SPEC 7 transition + engine = qmc.MultinomialQMC(p, n_trials=n_trials, seed=rng) + assert_allclose(engine.random(1), expected, atol=1) + + def test_MultinomialDistribution(self): + rng = np.random.default_rng(77797854505813727292048130876699859000) + p = np.array([0.12, 0.26, 0.05, 0.35, 0.22]) + engine = qmc.MultinomialQMC(p, n_trials=8192, rng=rng) + draws = engine.random(1) + assert_allclose(draws / np.sum(draws), np.atleast_2d(p), atol=1e-4) + + def test_FindIndex(self): + p_cumulative = np.array([0.1, 0.4, 0.45, 0.6, 0.75, 0.9, 0.99, 1.0]) + size = len(p_cumulative) + assert_equal(_test_find_index(p_cumulative, size, 0.0), 0) + assert_equal(_test_find_index(p_cumulative, size, 0.4), 2) + assert_equal(_test_find_index(p_cumulative, size, 0.44999), 2) + assert_equal(_test_find_index(p_cumulative, size, 0.45001), 3) + assert_equal(_test_find_index(p_cumulative, size, 1.0), size - 1) + + @pytest.mark.filterwarnings('ignore::UserWarning') + def test_other_engine(self): + # same as test_MultinomialBasicDraw with different engine + rng = np.random.default_rng(283753519042773243071753037669078065412) + p = np.array([0.12, 0.26, 0.05, 0.35, 0.22]) + n_trials = 100 + expected = np.atleast_2d(n_trials * p).astype(int) + base_engine = qmc.Sobol(1, scramble=True, rng=rng) + engine = qmc.MultinomialQMC(p, n_trials=n_trials, engine=base_engine, + rng=rng) + assert_allclose(engine.random(1), expected, atol=1) + + +class TestNormalQMC: + def test_NormalQMC(self): + # d = 1 + engine = qmc.MultivariateNormalQMC(mean=np.zeros(1)) + samples = engine.random() + assert_equal(samples.shape, (1, 1)) + samples = engine.random(n=5) + assert_equal(samples.shape, (5, 1)) + # d = 2 + engine = qmc.MultivariateNormalQMC(mean=np.zeros(2)) + samples = engine.random() + assert_equal(samples.shape, (1, 2)) + samples = engine.random(n=5) + assert_equal(samples.shape, (5, 2)) + + def test_NormalQMCInvTransform(self): + # d = 1 + engine = qmc.MultivariateNormalQMC( + mean=np.zeros(1), inv_transform=True) + samples = engine.random() + assert_equal(samples.shape, (1, 1)) + samples = engine.random(n=5) + assert_equal(samples.shape, (5, 1)) + # d = 2 + engine = qmc.MultivariateNormalQMC( + mean=np.zeros(2), inv_transform=True) + samples = engine.random() + assert_equal(samples.shape, (1, 2)) + samples = engine.random(n=5) + assert_equal(samples.shape, (5, 2)) + + def test_NormalQMCSeeded(self): + # test even dimension + rng = np.random.default_rng(274600237797326520096085022671371676017) + # preserve use of legacy keyword during SPEC 7 transition + engine = qmc.MultivariateNormalQMC( + mean=np.zeros(2), inv_transform=False, seed=rng) + samples = engine.random(n=2) + samples_expected = np.array([[-0.932001, -0.522923], + [-1.477655, 0.846851]]) + assert_allclose(samples, samples_expected, atol=1e-4) + + # test odd dimension + rng = np.random.default_rng(274600237797326520096085022671371676017) + engine = qmc.MultivariateNormalQMC( + mean=np.zeros(3), inv_transform=False, rng=rng) + samples = engine.random(n=2) + samples_expected = np.array([[-0.932001, -0.522923, 0.036578], + [-1.778011, 0.912428, -0.065421]]) + assert_allclose(samples, samples_expected, atol=1e-4) + + # same test with another engine + rng = np.random.default_rng(274600237797326520096085022671371676017) + base_engine = qmc.Sobol(4, scramble=True, rng=rng) + engine = qmc.MultivariateNormalQMC( + mean=np.zeros(3), inv_transform=False, + engine=base_engine, rng=rng + ) + samples = engine.random(n=2) + samples_expected = np.array([[-0.932001, -0.522923, 0.036578], + [-1.778011, 0.912428, -0.065421]]) + assert_allclose(samples, samples_expected, atol=1e-4) + + def test_NormalQMCSeededInvTransform(self): + # test even dimension + rng = np.random.default_rng(288527772707286126646493545351112463929) + engine = qmc.MultivariateNormalQMC( + mean=np.zeros(2), rng=rng, inv_transform=True) + samples = engine.random(n=2) + samples_expected = np.array([[-0.913237, -0.964026], + [0.255904, 0.003068]]) + assert_allclose(samples, samples_expected, atol=1e-4) + + # test odd dimension + rng = np.random.default_rng(288527772707286126646493545351112463929) + engine = qmc.MultivariateNormalQMC( + mean=np.zeros(3), rng=rng, inv_transform=True) + samples = engine.random(n=2) + samples_expected = np.array([[-0.913237, -0.964026, 0.355501], + [0.699261, 2.90213 , -0.6418]]) + assert_allclose(samples, samples_expected, atol=1e-4) + + def test_other_engine(self): + for d in (0, 1, 2): + base_engine = qmc.Sobol(d=d, scramble=False) + engine = qmc.MultivariateNormalQMC(mean=np.zeros(d), + engine=base_engine, + inv_transform=True) + samples = engine.random() + assert_equal(samples.shape, (1, d)) + + def test_NormalQMCShapiro(self): + rng = np.random.default_rng(13242) + engine = qmc.MultivariateNormalQMC(mean=np.zeros(2), rng=rng) + samples = engine.random(n=256) + assert all(np.abs(samples.mean(axis=0)) < 1e-2) + assert all(np.abs(samples.std(axis=0) - 1) < 1e-2) + # perform Shapiro-Wilk test for normality + for i in (0, 1): + _, pval = shapiro(samples[:, i]) + assert pval > 0.9 + # make sure samples are uncorrelated + cov = np.cov(samples.transpose()) + assert np.abs(cov[0, 1]) < 1e-2 + + def test_NormalQMCShapiroInvTransform(self): + rng = np.random.default_rng(32344554) + engine = qmc.MultivariateNormalQMC( + mean=np.zeros(2), inv_transform=True, rng=rng) + samples = engine.random(n=256) + assert all(np.abs(samples.mean(axis=0)) < 1e-2) + assert all(np.abs(samples.std(axis=0) - 1) < 1e-2) + # perform Shapiro-Wilk test for normality + for i in (0, 1): + _, pval = shapiro(samples[:, i]) + assert pval > 0.9 + # make sure samples are uncorrelated + cov = np.cov(samples.transpose()) + assert np.abs(cov[0, 1]) < 1e-2 + + +class TestMultivariateNormalQMC: + + def test_validations(self): + + message = r"Dimension of `engine` must be consistent" + with pytest.raises(ValueError, match=message): + qmc.MultivariateNormalQMC([0], engine=qmc.Sobol(d=2)) + + message = r"Dimension of `engine` must be consistent" + with pytest.raises(ValueError, match=message): + qmc.MultivariateNormalQMC([0, 0, 0], engine=qmc.Sobol(d=4)) + + message = r"`engine` must be an instance of..." + with pytest.raises(ValueError, match=message): + qmc.MultivariateNormalQMC([0, 0], engine=np.random.default_rng()) + + message = r"Covariance matrix not PSD." + with pytest.raises(ValueError, match=message): + qmc.MultivariateNormalQMC([0, 0], [[1, 2], [2, 1]]) + + message = r"Covariance matrix is not symmetric." + with pytest.raises(ValueError, match=message): + qmc.MultivariateNormalQMC([0, 0], [[1, 0], [2, 1]]) + + message = r"Dimension mismatch between mean and covariance." + with pytest.raises(ValueError, match=message): + qmc.MultivariateNormalQMC([0], [[1, 0], [0, 1]]) + + def test_MultivariateNormalQMCNonPD(self): + # try with non-pd but psd cov; should work + engine = qmc.MultivariateNormalQMC( + [0, 0, 0], [[1, 0, 1], [0, 1, 1], [1, 1, 2]], + ) + assert engine._corr_matrix is not None + + def test_MultivariateNormalQMC(self): + # d = 1 scalar + engine = qmc.MultivariateNormalQMC(mean=0, cov=5) + samples = engine.random() + assert_equal(samples.shape, (1, 1)) + samples = engine.random(n=5) + assert_equal(samples.shape, (5, 1)) + + # d = 2 list + engine = qmc.MultivariateNormalQMC(mean=[0, 1], cov=[[1, 0], [0, 1]]) + samples = engine.random() + assert_equal(samples.shape, (1, 2)) + samples = engine.random(n=5) + assert_equal(samples.shape, (5, 2)) + + # d = 3 np.array + mean = np.array([0, 1, 2]) + cov = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) + engine = qmc.MultivariateNormalQMC(mean, cov) + samples = engine.random() + assert_equal(samples.shape, (1, 3)) + samples = engine.random(n=5) + assert_equal(samples.shape, (5, 3)) + + def test_MultivariateNormalQMCInvTransform(self): + # d = 1 scalar + engine = qmc.MultivariateNormalQMC(mean=0, cov=5, inv_transform=True) + samples = engine.random() + assert_equal(samples.shape, (1, 1)) + samples = engine.random(n=5) + assert_equal(samples.shape, (5, 1)) + + # d = 2 list + engine = qmc.MultivariateNormalQMC( + mean=[0, 1], cov=[[1, 0], [0, 1]], inv_transform=True, + ) + samples = engine.random() + assert_equal(samples.shape, (1, 2)) + samples = engine.random(n=5) + assert_equal(samples.shape, (5, 2)) + + # d = 3 np.array + mean = np.array([0, 1, 2]) + cov = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) + engine = qmc.MultivariateNormalQMC(mean, cov, inv_transform=True) + samples = engine.random() + assert_equal(samples.shape, (1, 3)) + samples = engine.random(n=5) + assert_equal(samples.shape, (5, 3)) + + def test_MultivariateNormalQMCSeeded(self): + # test even dimension + rng = np.random.default_rng(180182791534511062935571481899241825000) + a = rng.standard_normal((2, 2)) + A = a @ a.transpose() + np.diag(rng.random(2)) + engine = qmc.MultivariateNormalQMC(np.array([0, 0]), A, + inv_transform=False, rng=rng) + samples = engine.random(n=2) + samples_expected = np.array([[-0.64419, -0.882413], + [0.837199, 2.045301]]) + assert_allclose(samples, samples_expected, atol=1e-4) + + # test odd dimension + rng = np.random.default_rng(180182791534511062935571481899241825000) + a = rng.standard_normal((3, 3)) + A = a @ a.transpose() + np.diag(rng.random(3)) + engine = qmc.MultivariateNormalQMC(np.array([0, 0, 0]), A, + inv_transform=False, rng=rng) + samples = engine.random(n=2) + samples_expected = np.array([[-0.693853, -1.265338, -0.088024], + [1.620193, 2.679222, 0.457343]]) + assert_allclose(samples, samples_expected, atol=1e-4) + + def test_MultivariateNormalQMCSeededInvTransform(self): + # test even dimension + rng = np.random.default_rng(224125808928297329711992996940871155974) + a = rng.standard_normal((2, 2)) + A = a @ a.transpose() + np.diag(rng.random(2)) + engine = qmc.MultivariateNormalQMC( + np.array([0, 0]), A, rng=rng, inv_transform=True + ) + samples = engine.random(n=2) + samples_expected = np.array([[0.682171, -3.114233], + [-0.098463, 0.668069]]) + assert_allclose(samples, samples_expected, atol=1e-4) + + # test odd dimension + rng = np.random.default_rng(224125808928297329711992996940871155974) + a = rng.standard_normal((3, 3)) + A = a @ a.transpose() + np.diag(rng.random(3)) + engine = qmc.MultivariateNormalQMC( + np.array([0, 0, 0]), A, rng=rng, inv_transform=True + ) + samples = engine.random(n=2) + samples_expected = np.array([[0.988061, -1.644089, -0.877035], + [-1.771731, 1.096988, 2.024744]]) + assert_allclose(samples, samples_expected, atol=1e-4) + + def test_MultivariateNormalQMCShapiro(self): + # test the standard case + rng = np.random.default_rng(188960007281846377164494575845971640) + engine = qmc.MultivariateNormalQMC( + mean=[0, 0], cov=[[1, 0], [0, 1]], rng=rng + ) + samples = engine.random(n=256) + assert all(np.abs(samples.mean(axis=0)) < 1e-2) + assert all(np.abs(samples.std(axis=0) - 1) < 1e-2) + # perform Shapiro-Wilk test for normality + for i in (0, 1): + _, pval = shapiro(samples[:, i]) + assert pval > 0.9 + # make sure samples are uncorrelated + cov = np.cov(samples.transpose()) + assert np.abs(cov[0, 1]) < 1e-2 + + # test the correlated, non-zero mean case + engine = qmc.MultivariateNormalQMC( + mean=[1.0, 2.0], cov=[[1.5, 0.5], [0.5, 1.5]], rng=rng + ) + samples = engine.random(n=256) + assert all(np.abs(samples.mean(axis=0) - [1, 2]) < 1e-2) + assert all(np.abs(samples.std(axis=0) - np.sqrt(1.5)) < 1e-2) + # perform Shapiro-Wilk test for normality + for i in (0, 1): + _, pval = shapiro(samples[:, i]) + assert pval > 0.9 + # check covariance + cov = np.cov(samples.transpose()) + assert np.abs(cov[0, 1] - 0.5) < 1e-2 + + def test_MultivariateNormalQMCShapiroInvTransform(self): + # test the standard case + rng = np.random.default_rng(200089821034563288698994840831440331329) + engine = qmc.MultivariateNormalQMC( + mean=[0, 0], cov=[[1, 0], [0, 1]], rng=rng, inv_transform=True + ) + samples = engine.random(n=256) + assert all(np.abs(samples.mean(axis=0)) < 1e-2) + assert all(np.abs(samples.std(axis=0) - 1) < 1e-2) + # perform Shapiro-Wilk test for normality + for i in (0, 1): + _, pval = shapiro(samples[:, i]) + assert pval > 0.9 + # make sure samples are uncorrelated + cov = np.cov(samples.transpose()) + assert np.abs(cov[0, 1]) < 1e-2 + + # test the correlated, non-zero mean case + engine = qmc.MultivariateNormalQMC( + mean=[1.0, 2.0], + cov=[[1.5, 0.5], [0.5, 1.5]], + rng=rng, + inv_transform=True, + ) + samples = engine.random(n=256) + assert all(np.abs(samples.mean(axis=0) - [1, 2]) < 1e-2) + assert all(np.abs(samples.std(axis=0) - np.sqrt(1.5)) < 1e-2) + # perform Shapiro-Wilk test for normality + for i in (0, 1): + _, pval = shapiro(samples[:, i]) + assert pval > 0.9 + # check covariance + cov = np.cov(samples.transpose()) + assert np.abs(cov[0, 1] - 0.5) < 1e-2 + + def test_MultivariateNormalQMCDegenerate(self): + # X, Y iid standard Normal and Z = X + Y, random vector (X, Y, Z) + rng = np.random.default_rng(16320637417581448357869821654290448620) + engine = qmc.MultivariateNormalQMC( + mean=[0.0, 0.0, 0.0], + cov=[[1.0, 0.0, 1.0], [0.0, 1.0, 1.0], [1.0, 1.0, 2.0]], + rng=rng, + ) + samples = engine.random(n=512) + assert all(np.abs(samples.mean(axis=0)) < 1e-2) + assert np.abs(np.std(samples[:, 0]) - 1) < 1e-2 + assert np.abs(np.std(samples[:, 1]) - 1) < 1e-2 + assert np.abs(np.std(samples[:, 2]) - np.sqrt(2)) < 1e-2 + for i in (0, 1, 2): + _, pval = shapiro(samples[:, i]) + assert pval > 0.8 + cov = np.cov(samples.transpose()) + assert np.abs(cov[0, 1]) < 1e-2 + assert np.abs(cov[0, 2] - 1) < 1e-2 + # check to see if X + Y = Z almost exactly + assert all(np.abs(samples[:, 0] + samples[:, 1] - samples[:, 2]) + < 1e-5) + + +class TestLloyd: + def test_lloyd(self): + # quite sensible seed as it can go up before going further down + rng = np.random.RandomState(1809831) + sample = rng.uniform(0, 1, size=(128, 2)) + base_l1 = _l1_norm(sample) + base_l2 = l2_norm(sample) + + for _ in range(4): + sample_lloyd = _lloyd_centroidal_voronoi_tessellation( + sample, maxiter=1, + ) + curr_l1 = _l1_norm(sample_lloyd) + curr_l2 = l2_norm(sample_lloyd) + + # higher is better for the distance measures + assert base_l1 < curr_l1 + assert base_l2 < curr_l2 + + base_l1 = curr_l1 + base_l2 = curr_l2 + + sample = sample_lloyd + + def test_lloyd_non_mutating(self): + """ + Verify that the input samples are not mutated in place and that they do + not share memory with the output. + """ + sample_orig = np.array([[0.1, 0.1], + [0.1, 0.2], + [0.2, 0.1], + [0.2, 0.2]]) + sample_copy = sample_orig.copy() + new_sample = _lloyd_centroidal_voronoi_tessellation( + sample=sample_orig + ) + assert_allclose(sample_orig, sample_copy) + assert not np.may_share_memory(sample_orig, new_sample) + + def test_lloyd_errors(self): + with pytest.raises(ValueError, match=r"`sample` is not a 2D array"): + sample = [0, 1, 0.5] + _lloyd_centroidal_voronoi_tessellation(sample) + + msg = r"`sample` dimension is not >= 2" + with pytest.raises(ValueError, match=msg): + sample = [[0], [0.4], [1]] + _lloyd_centroidal_voronoi_tessellation(sample) + + msg = r"`sample` is not in unit hypercube" + with pytest.raises(ValueError, match=msg): + sample = [[-1.1, 0], [0.1, 0.4], [1, 2]] + _lloyd_centroidal_voronoi_tessellation(sample) + + +# mindist +def l2_norm(sample): + return distance.pdist(sample).min() + + +@pytest.mark.parametrize('engine', [qmc.Halton, qmc.Sobol, + qmc.LatinHypercube, qmc.PoissonDisk]) +def test_deterministic(engine): + seed_number = 2359834584 + + rng = np.random.RandomState(seed_number) + res1 = engine(d=1, seed=rng).random(4) + rng = np.random.RandomState(seed_number) + res2 = engine(d=1, seed=rng).random(4) + assert_equal(res1, res2) + + rng = np.random.default_rng(seed_number) + res1 = engine(d=1, seed=rng).random(4) + res2 = engine(d=1, rng=seed_number).random(4) + assert_equal(res1, res2) + rng = np.random.default_rng(seed_number) + res3 = engine(d=1, rng=rng).random(4) + assert_equal(res2, res1) + assert_equal(res3, res1) + + message = "got multiple values for argument now known as `rng`" + with pytest.raises(TypeError, match=message): + engine(d=1, rng=seed_number, seed=seed_number) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/test_quantile.py b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_quantile.py new file mode 100644 index 0000000000000000000000000000000000000000..4caa6341adb0123c783a60f324617d0fcaed7090 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_quantile.py @@ -0,0 +1,774 @@ +import pytest +import numpy as np + +from scipy import stats +from scipy.stats._quantile import (_xp_searchsorted, _estimated_cdf_methods, + _estimated_cdf_discontinuous_methods, _estimated_cdf_continuous_methods) +from scipy._lib._array_api import ( + xp_default_dtype, + is_numpy, + is_torch, + is_jax, + is_cupy, + is_array_api_strict, + make_xp_test_case, + SCIPY_ARRAY_API, + xp_size, + xp_copy, +) +from scipy._lib._array_api_no_0d import xp_assert_close, xp_assert_equal +from scipy._lib._util import _apply_over_batch +import scipy._external.array_api_extra as xpx +from scipy.stats._axis_nan_policy import _broadcast_arrays + +skip_xp_backends = pytest.mark.skip_xp_backends + +lazy_xp_modules = [stats] + +@_apply_over_batch(('x', 1), ('p', 1)) +def quantile_reference_last_axis(x, p, nan_policy, method): + if nan_policy == 'omit': + x = x[~np.isnan(x)] + p_mask = np.isnan(p) + p = p.copy() + p[p_mask] = 0.5 + if method == 'harrell-davis': + # hdquantiles returns masked element if length along axis is 1 (bug) + res = (np.full_like(p, x[0]) if x.size == 1 + else stats.mstats.hdquantiles(x, p).data) + elif method.startswith('round'): + res = winsor_reference_1d(np.sort(x), p, method) + else: + res = np.quantile(x, p, method=method) + + res = np.asarray(res) + if nan_policy == 'propagate' and np.any(np.isnan(x)): + res[:] = np.nan + + res[p_mask] = np.nan + return res + + +@np.vectorize(excluded={0, 2}) # type: ignore[call-arg] +def winsor_reference_1d(y, p, method): + # Adapted directly from the documentation + # Note: `y` is the sorted data array + n = len(y) + if method == 'round_nearest': + j = int(np.round(p * n) if p < 0.5 else np.round(n * p - 1)) + elif method == 'round_outward': + j = int(np.floor(p * n) if p < 0.5 else np.ceil(n * p - 1)) + elif method == 'round_inward': + j = int(np.ceil(p * n) if p < 0.5 else np.floor(n * p - 1)) + return y[j] + + +def quantile_reference(x, p, *, axis, nan_policy, keepdims, method): + x, p = np.moveaxis(x, axis, -1), np.moveaxis(np.atleast_1d(p), axis, -1) + res = quantile_reference_last_axis(x, p, nan_policy, method) + res = np.moveaxis(res, -1, axis) + if not keepdims: + res = np.squeeze(res, axis=axis) + return res + + +@make_xp_test_case(stats.quantile) +class TestQuantile: + + def test_input_validation(self, xp): + x = xp.asarray([1, 2, 3]) + p = xp.asarray(0.5) + + message = "`x` must have real dtype." + with pytest.raises(ValueError, match=message): + stats.quantile(xp.asarray([True, False]), p) + with pytest.raises(ValueError): + stats.quantile(xp.asarray([1+1j, 2]), p) + + message = "`p` must have real floating dtype." + with pytest.raises(ValueError, match=message): + stats.quantile(x, xp.asarray([0, 1])) + + message = "`weights` must have real dtype." + with pytest.raises(ValueError, match=message): + stats.quantile(x, p, weights=xp.astype(x, xp.complex64)) + + message = "`axis` must be an integer or None." + with pytest.raises(ValueError, match=message): + stats.quantile(x, p, axis=0.5) + with pytest.raises(ValueError, match=message): + stats.quantile(x, p, axis=(0, -1)) + + message = "`axis` is not compatible with the shapes of the inputs." + with pytest.raises(ValueError, match=message): + stats.quantile(x, p, axis=2) + + if not is_jax(xp): # no data-dependent input validation for lazy arrays + message = "The input contains nan values" + with pytest.raises(ValueError, match=message): + stats.quantile(xp.asarray([xp.nan, 1, 2]), p, nan_policy='raise') + + message = "`method` must be one of..." + with pytest.raises(ValueError, match=message): + stats.quantile(x, p, method='a duck') + + message = "`method='harrell-davis'` does not support `weights`." + with pytest.raises(ValueError, match=message): + stats.quantile(x, p, weights=x, method='harrell-davis') + + message = "`method='round_nearest'` does not support `weights`." + with pytest.raises(ValueError, match=message): + stats.quantile(x, p, weights=x, method='round_nearest') + + message = "If specified, `keepdims` must be True or False." + with pytest.raises(ValueError, match=message): + stats.quantile(x, p, keepdims=42) + + message = "`keepdims` may be False only if the length of `p` along `axis` is 1." + with pytest.raises(ValueError, match=message): + stats.quantile(x, xp.asarray([0.5, 0.6]), keepdims=False) + + + def _get_weights_x_rep(self, x, axis, rng): + x = np.swapaxes(x, axis, -1) + ndim = x.ndim + x = np.atleast_2d(x) + counts = rng.integers(10, size=x.shape[-1], dtype=np.int32) + x_rep = [] + weights = [] + for x_ in x: + counts_ = rng.permuted(counts) + x_rep.append(np.repeat(x_, counts_)) + weights.append(counts_) + x_rep, weights = np.stack(x_rep), np.stack(weights) + if ndim < 2: + x_rep, weights = np.squeeze(x_rep, axis=0), np.squeeze(weights, axis=0) + x_rep, weights = np.swapaxes(x_rep, -1, axis), np.swapaxes(weights, -1, axis) + weights = np.asarray(weights, dtype=x.dtype) + return weights, x_rep + + + @skip_xp_backends(cpu_only=True, reason="PyTorch doesn't have `betainc`.", + exceptions=['cupy']) + @pytest.mark.parametrize('method', + ['inverted_cdf', 'averaged_inverted_cdf', 'closest_observation', + 'hazen', 'interpolated_inverted_cdf', 'linear', + 'median_unbiased', 'normal_unbiased', 'weibull', + 'harrell-davis', 'round_nearest', 'round_outward', 'round_inward', + '_lower', '_higher', '_midpoint', '_nearest']) + @pytest.mark.parametrize('shape_x, shape_p, axis', + [(10, None, -1), (10, 10, -1), (10, (2, 3), -1), ((10, 2), None, 0)]) + @pytest.mark.parametrize('weights', [False, True]) + def test_against_reference(self, method, shape_x, shape_p, axis, weights, xp): + # Test all methods with various data shapes + if weights and (method.startswith('_') or method.startswith('round') + or method=='harrell-davis'): + pytest.skip('`weights` not supported by private (legacy) methods.') + dtype = xp_default_dtype(xp) + rng = np.random.default_rng(23458924568734956) + x = rng.random(size=shape_x) + p = rng.random(size=shape_p) + + if weights: + weights, x_rep = self._get_weights_x_rep(x, axis, rng) + else: + weights, x_rep = None, x + + ref = quantile_reference( + x_rep, p, method=method[1:] if method.startswith('_') else method, + axis=axis, nan_policy='propagate', keepdims=shape_p is not None) + + x, p = xp.asarray(x, dtype=dtype), xp.asarray(p, dtype=dtype) + weights = weights if weights is None else xp.asarray(weights, dtype=dtype) + res = stats.quantile(x, p, method=method, weights=weights, axis=axis) + + xp_assert_close(res, xp.asarray(ref, dtype=dtype)) + + @pytest.mark.filterwarnings("ignore:torch.searchsorted:UserWarning") + @skip_xp_backends(cpu_only=True, reason="PyTorch doesn't have `betainc`.", + exceptions=['cupy', 'jax.numpy']) + @pytest.mark.parametrize('axis', [0, 1]) + @pytest.mark.parametrize('keepdims', [False, True]) + @pytest.mark.parametrize('nan_policy', ['omit', 'propagate', 'marray']) + @pytest.mark.parametrize('dtype', ['float32', 'float64']) + @pytest.mark.parametrize('method', ['linear', 'harrell-davis', 'round_nearest']) + @pytest.mark.parametrize('weights', [False, True]) + def test_against_reference_2(self, axis, keepdims, nan_policy, + dtype, method, weights, xp): + # Test some methods with various combinations of arguments + if is_jax(xp) and nan_policy == 'marray': # mdhaber/marray#146 + pytest.skip("`marray` currently incompatible with JAX") + if weights and method in {'harrell-davis', 'round_nearest'}: + pytest.skip("These methods don't yet support weights") + rng = np.random.default_rng(23458924568734956) + shape = (5, 6) + x = rng.random(size=shape).astype(dtype) + p = rng.random(size=shape).astype(dtype) + mask = rng.random(size=shape) > 0.8 + assert np.any(mask) + x[mask] = np.nan + if not keepdims: + p = np.mean(p, axis=axis, keepdims=True) + + # inject p = 0 and p = 1 to test edge cases + # Currently would fail with CuPy/JAX (cupy/cupy#8934, jax-ml/jax#21900); + # remove the `if` when those are resolved. + if is_numpy(xp): + p0 = p.ravel() + p0[1] = 0. + p0[-2] = 1. + + dtype = getattr(xp, dtype) + + if weights: + weights, x_rep = self._get_weights_x_rep(x, axis, rng) + weights = weights if weights is None else xp.asarray(weights) + else: + weights, x_rep = None, x + + if nan_policy == 'marray': + if not SCIPY_ARRAY_API: + pytest.skip("MArray is only available if SCIPY_ARRAY_API=1") + if weights is not None: + pytest.skip("MArray is not yet compatible with weights") + marray = pytest.importorskip('marray') + kwargs = dict(axis=axis, keepdims=keepdims, method=method) + mxp = marray._get_namespace(xp) + x_mp = mxp.asarray(x, mask=mask) + weights = weights if weights is None else mxp.asarray(weights) + res = stats.quantile(x_mp, mxp.asarray(p), weights=weights, **kwargs) + ref = quantile_reference(x_rep, p, nan_policy='omit', **kwargs) + xp_assert_close(res.data, xp.asarray(ref, dtype=dtype)) + return + + kwargs = dict(axis=axis, keepdims=keepdims, + nan_policy=nan_policy, method=method) + res = stats.quantile(xp.asarray(x), xp.asarray(p), weights=weights, **kwargs) + ref = quantile_reference(x_rep, p, **kwargs) + xp_assert_close(res, xp.asarray(ref, dtype=dtype)) + + def test_integer_input_output_dtype(self, xp): + res = stats.quantile(xp.arange(10, dtype=xp.int64), 0.5) + assert res.dtype == xp_default_dtype(xp) + + @pytest.mark.parametrize('x, p, ref, kwargs', + [([], 0.5, np.nan, {}), + ([1, 2, 3], [-1, 0, 1, 1.5, np.nan], [np.nan, 1, 3, np.nan, np.nan], {}), + ([1, 2, 3], [], [], {}), + ([[np.nan, 2]], 0.5, [np.nan, 2], {'nan_policy': 'omit'}), + ([[], []], 0.5, np.full(2, np.nan), {'axis': -1}), + ([[], []], 0.5, np.zeros((0,)), {'axis': 0, 'keepdims': False}), + ([[], []], 0.5, np.zeros((1, 0)), {'axis': 0, 'keepdims': True}), + ([], [0.5, 0.6], np.full(2, np.nan), {}), + (np.arange(1, 28).reshape((3, 3, 3)), 0.5, [[[14.]]], + {'axis': None, 'keepdims': True}), + ([[1, 2], [3, 4]], [0.25, 0.5, 0.75], [[1.75, 2.5, 3.25]], + {'axis': None, 'keepdims': True}), + # Known issue: + # ([1, 2, 3], 0.5, 2., {'weights': [0, 0, 0]}) + # See https://github.com/scipy/scipy/pull/23941#issuecomment-3503554361 + ]) + def test_edge_cases(self, x, p, ref, kwargs, xp): + default_dtype = xp_default_dtype(xp) + x, p, ref = xp.asarray(x), xp.asarray(p), xp.asarray(ref, dtype=default_dtype) + res = stats.quantile(x, p, **kwargs) + xp_assert_equal(res, ref) + + @pytest.mark.parametrize('axis', [0, 1, 2]) + @pytest.mark.parametrize('keepdims', [False, True]) + def test_size_0(self, axis, keepdims, xp): + shape = [3, 4, 0] + out_shape = shape.copy() + if keepdims: + out_shape[axis] = 1 + else: + out_shape.pop(axis) + res = stats.quantile(xp.zeros(tuple(shape)), 0.5, axis=axis, keepdims=keepdims) + assert res.shape == tuple(out_shape) + + @pytest.mark.parametrize('method', + ['inverted_cdf', 'averaged_inverted_cdf', 'closest_observation', + '_lower', '_higher', '_midpoint', '_nearest']) + def test_transition(self, method, xp): + # test that values of discontinuous estimators are correct when + # p*n + m - 1 is integral. + if method == 'closest_observation' and np.__version__ < '2.0.1': + pytest.skip('Bug in np.quantile (numpy/numpy#26656) fixed in 2.0.1') + x = np.arange(8., dtype=np.float64) + p = np.arange(0, 1.03125, 0.03125) + res = stats.quantile(xp.asarray(x), xp.asarray(p), method=method) + ref = np.quantile(x, p, method=method[1:] if method.startswith('_') else method) + xp_assert_equal(res, xp.asarray(ref, dtype=xp.float64)) + + @pytest.mark.parametrize('zero_weights', [False, True]) + def test_weights_against_numpy(self, zero_weights, xp): + if is_numpy(xp) and xp.__version__ < "2.1.3" and zero_weights: + pytest.skip('`Bug in np.quantile (numpy/numpy#27563) fixed in 2.1.3') + dtype = xp_default_dtype(xp) + rng = np.random.default_rng(85468924398205602) + method = 'inverted_cdf' + x = rng.random(size=100) + weights = rng.random(size=100) + if zero_weights: + weights[weights < 0.5] = 0 + p = np.linspace(0., 1., 300) + res = stats.quantile(xp.asarray(x, dtype=dtype), xp.asarray(p, dtype=dtype), + method=method, weights=xp.asarray(weights, dtype=dtype)) + ref = np.quantile(x, p, method=method, weights=weights) + xp_assert_close(res, xp.asarray(ref, dtype=dtype)) + + @pytest.mark.parametrize('method', + ['inverted_cdf', 'averaged_inverted_cdf', 'closest_observation', 'hazen', + 'interpolated_inverted_cdf', 'linear','median_unbiased', 'normal_unbiased', + 'weibull']) + def test_zero_weights(self, method, xp): + rng = np.random.default_rng(85468924398205602) + + # test 1-D versus eliminating zero-weighted values + n = 100 + x = xp.asarray(rng.random(size=n)) + x0 = xp_copy(x) + p = xp.asarray(rng.random(size=n)) + i_zero = xp.asarray(rng.random(size=n) < 0.1) + weights = xp.asarray(rng.random(size=n)) + weights = xp.where(i_zero, 0., weights) + res = stats.quantile(x, p, weights=weights, method=method) + ref = stats.quantile(x[~i_zero], p, weights=weights[~i_zero], method=method) + xp_assert_close(res, ref) + xp_assert_equal(x, x0) # no input mutation + + # test multi-D versus `nan_policy='omit'` + shape = (5, 100) + x = xp.asarray(rng.random(size=shape)) + x0 = xp_copy(x) + p = xp.asarray(rng.random(size=shape)) + i_zero = xp.asarray(rng.random(size=shape) < 0.1) + weights = xp.asarray(rng.random(size=shape)) + x_nanned = xp.where(i_zero, xp.nan, x) + weights_zeroed = xp.where(i_zero, 0., weights) + res = stats.quantile(x, p, weights=weights_zeroed, method=method, axis=-1) + ref = stats.quantile(x_nanned, p, weights=weights, + nan_policy='omit', method=method, axis=-1) + xp_assert_close(res, ref) + xp_assert_equal(x, x0) # no input mutation + + @pytest.mark.filterwarnings("ignore:torch.searchsorted:UserWarning") + @pytest.mark.parametrize('method', + ['inverted_cdf', 'averaged_inverted_cdf', 'closest_observation', 'hazen', + 'interpolated_inverted_cdf', 'linear','median_unbiased', 'normal_unbiased', + 'weibull']) + @pytest.mark.parametrize('shape', [50, (50, 3)]) + def test_unity_weights(self, method, shape, xp): + # Check that result is unchanged if all weights are `1.0` + rng = np.random.default_rng(28546892439820560) + x = xp.asarray(rng.random(size=shape)) + p = xp.asarray(rng.random(size=shape)) + weights = xp.ones_like(x) + res = stats.quantile(x, p, weights=weights, method=method) + ref = stats.quantile(x, p, method=method) + xp_assert_close(res, ref) + + @skip_xp_backends(cpu_only=True, reason="PyTorch doesn't have `betainc`.", + exceptions=['cupy', 'jax.numpy']) + def test_all_nan_harrell_davis_gh24707(self, xp): + # While working on gh-24707, there was a case in which if *all* elements of one + # slice were NaN, only some elements of another slice were NaN, and + # `nan_policy='omit'`, then the NaNs would not be ignored in the other slice. + # The same test case could fail with `nan_policy='propagate'` for a different + # reason, if the fix were not made carefully. Check that both these cases are + # resolved. + kwargs = dict(method='harrell-davis', axis=-1) + x = xp.asarray([[xp.nan, xp.nan, xp.nan], [xp.nan, 2, 3]]) + + res = stats.quantile(x, 0.5, **kwargs, nan_policy='omit') + xp_assert_close(res, xp.asarray([xp.nan, 2.5])) + + res = stats.quantile(x, 0.5, **kwargs, nan_policy='propagate') + xp_assert_close(res, xp.asarray([xp.nan, xp.nan])) + + +@_apply_over_batch(('a', 1), ('v', 1)) +def np_searchsorted(a, v, side): + return np.searchsorted(a, v, side=side) + + +@make_xp_test_case(_xp_searchsorted) +class Test_XPSearchsorted: + @pytest.mark.parametrize('side', ['left', 'right']) + @pytest.mark.parametrize('ties', [False, True]) + @pytest.mark.parametrize('shape', [0, 1, 2, 10, 11, 1000, 10001, + (2, 0), (0, 2), (2, 10), (2, 3, 11)]) + @pytest.mark.parametrize('nans_x', [False, True]) + @pytest.mark.parametrize('infs_x', [False, True]) + def test_nd(self, side, ties, shape, nans_x, infs_x, xp): + if nans_x and is_torch(xp): + pytest.skip('torch sorts NaNs differently') + rng = np.random.default_rng(945298725498274853) + if ties: + x = rng.integers(5, size=shape) + else: + x = rng.random(shape) + # float32 is to accommodate JAX - nextafter with `float64` is too small? + x = np.asarray(x, dtype=np.float32) + xr = np.nextafter(x, np.inf) + xl = np.nextafter(x, -np.inf) + x_ = np.asarray([-np.inf, np.inf, np.nan]) + x_ = np.broadcast_to(x_, x.shape[:-1] + (3,)) + y = rng.permuted(np.concatenate((xl, x, xr, x_), axis=-1), axis=-1) + if nans_x: + mask = rng.random(shape) < 0.1 + x[mask] = np.nan + if infs_x: + mask = rng.random(shape) < 0.1 + x[mask] = -np.inf + mask = rng.random(shape) > 0.9 + x[mask] = np.inf + x = np.sort(x, axis=-1) + x, y = np.asarray(x, dtype=np.float64), np.asarray(y, dtype=np.float64) + xp_default_int = xp.asarray(1).dtype + if xp_size(x) == 0 and x.ndim > 0 and x.shape[-1] != 0: + ref = xp.empty(x.shape[:-1] + (y.shape[-1],), dtype=xp_default_int) + else: + ref = xp.asarray(np_searchsorted(x, y, side=side), dtype=xp_default_int) + x, y = xp.asarray(x), xp.asarray(y) + res = _xp_searchsorted(x, y, side=side) + xp_assert_equal(res, ref) + + +@_apply_over_batch(('x', 1), ('y', 1)) +def estimated_cdf_reference_last_axis(x, y, nan_policy, method): + i_nan = np.isnan(x) + if nan_policy == 'propagate' and np.any(i_nan): + return np.full_like(y, np.nan) + elif nan_policy == 'omit': + x = x[~i_nan] + return stats.estimated_cdf(x, y, keepdims=True, method=method) + + +def estimated_cdf_reference(x, y, *, axis=0, nan_policy='propagate', + keepdims=None, method='linear'): + x, y = _broadcast_arrays((x, y), axis=axis) + x, y = np.moveaxis(x, axis, -1), np.moveaxis(y, axis, -1) + res = estimated_cdf_reference_last_axis(x, y, nan_policy, method) + res = np.moveaxis(res, -1, axis) + if not keepdims: + res = np.squeeze(res, axis=axis) + return res + + +# avoid variable collection order issues +_estimated_cdf_methods_list = sorted(list(_estimated_cdf_methods)) + + +@make_xp_test_case(stats.estimated_cdf) +class TestEstimatedCDF: + def test_input_validation(self, xp): + x = xp.asarray([1, 2, 3]) + y = xp.asarray(2) + + message = "`x` must have real dtype." + with pytest.raises(ValueError, match=message): + stats.estimated_cdf(xp.asarray([True, False]), y) + with pytest.raises(ValueError): + stats.estimated_cdf(xp.asarray([1+1j, 2]), y) + + message = "`y` must have real dtype." + with pytest.raises(ValueError, match=message): + stats.estimated_cdf(x, xp.asarray([0+1j, 1])) + + message = "`axis` must be an integer or None." + with pytest.raises(ValueError, match=message): + stats.estimated_cdf(x, y, axis=0.5) + with pytest.raises(ValueError, match=message): + stats.estimated_cdf(x, y, axis=(0, -1)) + + message = "`axis` is not compatible with the shapes of the inputs." + with pytest.raises(ValueError, match=message): + stats.estimated_cdf(x, y, axis=2) + + if not is_jax(xp): # no data-dependent input validation for lazy arrays + message = "The input contains nan values" + with pytest.raises(ValueError, match=message): + stats.estimated_cdf(xp.asarray([xp.nan, 1, 2]), y, nan_policy='raise') + + message = "method` must be one of..." + with pytest.raises(ValueError, match=message): + stats.estimated_cdf(x, y, method='a duck') + + message = "If specified, `keepdims` must be True or False." + with pytest.raises(ValueError, match=message): + stats.estimated_cdf(x, y, keepdims=42) + + message = "`keepdims` may be False only if the length of `y` along `axis` is 1." + with pytest.raises(ValueError, match=message): + stats.estimated_cdf(x, xp.asarray([0.5, 0.6]), keepdims=False) + + @pytest.mark.parametrize('method', _estimated_cdf_methods_list) + @pytest.mark.parametrize('x_shape', [2, 10, 11, 100, 1001, (2, 10), (2, 3, 11)]) + @pytest.mark.parametrize('y_shape', [None, 25]) + @pytest.mark.parametrize('ties', [False, True]) + def test_against_quantile(self, method, x_shape, y_shape, ties, xp): + discontinuous = method in _estimated_cdf_discontinuous_methods + dtype = xp_default_dtype(xp) # removed parameterization to speed up tests + rng = np.random.default_rng(394529872549827485) + y_shape = x_shape if y_shape is None else y_shape + + if ties: + x = xp.asarray(rng.integers(9, size=x_shape), dtype=dtype) + else: + x = xp.asarray(rng.standard_normal(size=x_shape), dtype=dtype) + + p = xp.asarray(rng.random(size=y_shape), dtype=dtype) + y = stats.quantile(x, p, method=method, axis=-1) + res = stats.estimated_cdf(x, y, method=method, axis=-1) + ref = xp.broadcast_to(p, (*x.shape[:-1], y.shape[-1])) + + # check that `quantile` is the inverse of `estimated_cdf` + # note that for discontinuous methods, res is right on the cusp of a transition, + # and there can be a tiny bit of error to the right or left. We shift it left + # to ensure we're on the correct side of the transition, producing the same `y2` + # as if the probability calculation were exact. + res = res - 1e-6 if discontinuous else res + y2 = stats.quantile(x, res, method=method, axis=-1) + atol = 1e-5 if dtype == xp.float32 else 1e-12 + xp_assert_close(y2, y, atol=atol) + + # if there are ties or method is discontinuous, `quantile` is not invertible + if ties or discontinuous: + return + + # `quantile` is not invertible outside this domain + a, b = _estimated_cdf_continuous_methods[method] + n = x.shape[-1] + p_min = (1 - a) / (n + 1 - a - b) + p_max = (n - a) / (n + 1 - a - b) + i_very_low = y < xp.min(x, axis=-1, keepdims=True) + i_very_high = y > xp.max(x, axis=-1, keepdims=True) + i_low = (ref <= p_min) & ~i_very_low + i_high = (ref >= p_max) & ~i_very_high + i_ok = ~(i_low | i_high | i_very_low | i_very_high) + + # check for correct inversion within the domain + xp_assert_close(res[i_ok], ref[i_ok]) + + # check that all other values get mapped to bottom or top of range + kwargs = dict(check_shape=False, check_dtype=False, check_0d=True) + xp_assert_close(res[i_low], xp.asarray(p_min), **kwargs) + xp_assert_close(res[i_high], xp.asarray(p_max), **kwargs) + xp_assert_close(res[i_very_low], xp.asarray(0.0), **kwargs) + xp_assert_close(res[i_very_high], xp.asarray(1.0), **kwargs) + + @pytest.mark.filterwarnings("ignore:torch.searchsorted:UserWarning") + @pytest.mark.parametrize('axis', [0, 1]) + @pytest.mark.parametrize('keepdims', [False, True]) + @pytest.mark.parametrize('nan_policy', ['propagate', 'omit', 'marray']) + @pytest.mark.parametrize('dtype', ['float32', 'float64']) + @pytest.mark.parametrize('nans', [False, True]) + @pytest.mark.parametrize('meth', ['linear', 'inverted_cdf']) + def test_against_reference(self, axis, keepdims, nan_policy, dtype, nans, meth, xp): + if is_jax(xp) and nan_policy == 'marray': # mdhaber/marray#146 + pytest.skip("`marray` currently incompatible with JAX") + rng = np.random.default_rng(23458924568734956) + shape = (5, 6) + x = rng.standard_normal(size=shape).astype(dtype) + y = rng.standard_normal(size=shape).astype(dtype) + + mask = None + if nans: + mask = rng.random(size=shape) > 0.8 + assert np.any(mask) + x[mask] = np.nan + + if not keepdims: + y = np.mean(y, axis=axis, keepdims=True) + + dtype = getattr(xp, dtype) + + if nan_policy == 'marray': + if not SCIPY_ARRAY_API: + pytest.skip("MArray is only available if SCIPY_ARRAY_API=1") + marray = pytest.importorskip('marray') + kwargs = dict(axis=axis, keepdims=keepdims, method=meth) + mxp = marray._get_namespace(xp) + x_mp = mxp.asarray(x, mask=mask) + res = stats.estimated_cdf(x_mp, mxp.asarray(y), **kwargs) + ref = estimated_cdf_reference(x, y, nan_policy='omit', **kwargs) + xp_assert_close(res.data, xp.asarray(ref, dtype=dtype)) + return + + kwargs = dict(axis=axis, keepdims=keepdims, + nan_policy=nan_policy, method=meth) + res = stats.estimated_cdf(xp.asarray(x), xp.asarray(y), **kwargs) + ref = estimated_cdf_reference(x, y, **kwargs) + xp_assert_close(res, xp.asarray(ref, dtype=dtype)) + + @pytest.mark.skip_xp_backends('torch', reason='issues with sorting NaNs') + @pytest.mark.parametrize('n', [50, 500]) + @pytest.mark.parametrize('method, ab', _estimated_cdf_continuous_methods.items()) + def test_plotting_positions(self, n, method, ab, xp): + a, b = ab + rng = np.random.default_rng(539452987254982748) + x = rng.standard_normal(n) + + mask = rng.random(n) < 0.1 + x[mask] = np.nan + mask = xp.asarray(mask) + + x_masked = np.ma.masked_invalid(x) + ref = stats.mstats.plotting_positions(x_masked, a, b) + ref = xp.asarray(ref.data) + + x = xp.asarray(x) + res = stats.estimated_cdf(x, x, nan_policy='omit', method=method) + + xp_assert_close(res[~mask], ref[~mask]) + assert xp.all(xp.isnan(res[mask])) + + @pytest.mark.parametrize('ties', [False, True]) + def test_against_ecdf_percentileofscore(self, ties, xp): + rng = np.random.default_rng(853945298725498274) + n = 50 + dtype = xp_default_dtype(xp) + x = rng.integers(10, size=n) if ties else rng.standard_normal(size=n) + y = rng.integers(10, size=25) if ties else rng.standard_normal(size=25) + ref = stats.ecdf(x).cdf.evaluate(y) + ref2 = stats.percentileofscore(x, y, 'weak') + x, y = xp.asarray(x, dtype=dtype), xp.asarray(y, dtype=dtype) + res = stats.estimated_cdf(x, y, method='inverted_cdf') + ref, ref2 = xp.asarray(ref, dtype=dtype), xp.asarray(ref2, dtype=dtype) + xp_assert_close(res, ref) + xp_assert_close(res, ref2 / 100) + + def test_integer_input_output_dtype(self, xp): + x = xp.arange(10, dtype=xp.int64) + res = stats.estimated_cdf(x, x) + assert res.dtype == xp_default_dtype(xp) + + @pytest.mark.parametrize('nan_policy', ['propagate', 'omit', 'marray']) + @pytest.mark.parametrize('method', _estimated_cdf_methods_list) + def test_size_one_sample(self, nan_policy, method, xp): + discontinuous = method in _estimated_cdf_discontinuous_methods + x = xp.arange(10.) + y = xp.asarray([0., -1., 1.]) + n = np.asarray(1.) if is_array_api_strict(xp) else xp.asarray(1.) + with np.errstate(divide='ignore', invalid='ignore'): # for method = 'linear' + if discontinuous: + ref = xp.asarray([1., 0., 1.]) + else: + a, b = _estimated_cdf_continuous_methods[method] + ref = xp.asarray([float((n - a) / (n + 1 - a - b)), 0., 1.]) + + if nan_policy == 'propagate': + x = x[:1] + kwargs = {'nan_policy': 'propagate'} + elif nan_policy == 'omit': + x = xpx.at(x)[1:].set(xp.nan) + kwargs = {'nan_policy': 'omit'} + elif nan_policy == 'marray': + if is_jax(xp): + pytest.skip("JAX currently incompatible with `marray`") + if not SCIPY_ARRAY_API: + pytest.skip("MArray is only available if SCIPY_ARRAY_API=1") + marray = pytest.importorskip('marray') + mxp = marray._get_namespace(xp) + mask = (x > 0.) + x = mxp.asarray(x, mask=mask) + y = mxp.asarray(y) + kwargs = {} + + with np.errstate(divide='ignore', invalid='ignore'): # for method = 'linear' + res = stats.estimated_cdf(x, y, method=method, **kwargs) + res = res.data if nan_policy == 'marray' else res + xp_assert_close(res, ref) + + # skipping marray due to mdhaber/marray#24 + @pytest.mark.parametrize('nan_policy', ['propagate', 'omit']) + @pytest.mark.parametrize('method', _estimated_cdf_methods_list) + def test_size_zero_sample(self, nan_policy, method, xp): + x = xp.arange(10.) + y = xp.asarray([0., -1., 1.]) # this should work + ref = xp.full_like(y, xp.nan) + + if nan_policy == 'propagate': + x = x[0:0] + kwargs = {'nan_policy': 'propagate'} + elif nan_policy == 'omit': + x = xpx.at(x)[:].set(xp.nan) + kwargs = {'nan_policy': 'omit'} + elif nan_policy == 'marray': + if not SCIPY_ARRAY_API: + pytest.skip("MArray is only available if SCIPY_ARRAY_API=1") + if is_jax(xp): + pytest.skip("JAX currently incompatible with `marray`") + marray = pytest.importorskip('marray') + mxp = marray._get_namespace(xp) + mask = (x >= 0.) + x = mxp.asarray(x, mask=mask) + y = mxp.asarray(y) + kwargs = {} + + with np.errstate(divide='ignore', invalid='ignore'): # for method = 'linear' + res = stats.estimated_cdf(x, y, method=method, **kwargs) + + if nan_policy == 'marray': + assert xp.all(res.mask) + else: + xp_assert_close(res, ref) + + @pytest.mark.parametrize('x, y, ref, kwargs', + [ + ([], 0.5, np.nan, {}), + ([1, 2, 3], [0.999, 3.001, np.nan], [0., 1., np.nan], {}), + ([1, 2, 3], [], [], {}), + ([[np.nan, 2]], 2, [np.nan, 0.5], {'nan_policy': 'omit', 'method': 'weibull'}), + ([[], []], 0.5, np.full(2, np.nan), {'axis': -1}), + ([[], []], 0.5, np.zeros((0,)), {'axis': 0, 'keepdims': False}), + ([[], []], 0.5, np.zeros((1, 0)), {'axis': 0, 'keepdims': True}), + ([], [0.5, 0.6], np.full(2, np.nan), {}), + (np.arange(1, 28).reshape((3, 3, 3)), 14., [[[0.5]]], + {'axis': None, 'keepdims': True}), + ([[1, 2], [3, 4]], [1.75, 2.5, 3.25], [[0.25, 0.5, 0.75]], + {'axis': None, 'keepdims': True}), + ([1, 2, 3], [-np.inf, np.inf], [0.0, 1.0], {}), + # It is our choice how much effort and computational overhead we want to put + # into adjusting for insane edge cases like when `x` contains infinite values, + # especially when `y` does, too. + # One practical argument would be that y = +/- inf should produce the same + # results as an extremely large finite number, in which case the 0th element + # of the 'linear' result should be `0`. + # Another argument would be for producing NaN below wherever y = +/- inf. + # Another would be that it's not appropriate to spend significant computation + # correcting these edge cases; we should just document what we do. + # In any case, this is the current status. + ([-np.inf, -1, 0, 1, np.inf], [-np.inf, -2, -1, 0, 1, 2, np.inf], + [0.25, 0.25, 0.25, 0.5 , 0.75, 0.75, np.nan], {'method':'linear'}), + ([-np.inf, -1, 0, 1, np.inf], [-np.inf, -2, -1, 0, 1, 2, np.inf], + [0.2, 0.2, 0.4, 0.6, 0.8, 0.8, 1.], {'method': 'inverted_cdf'}), + ]) + def test_edge_cases(self, x, y, ref, kwargs, xp): + if kwargs.get('method', None) == 'weibull' and is_torch(xp): + pytest.skip('data-apis/array-api-compat#360') + if kwargs.get('axis', None) == -1 and is_cupy(xp): + pytest.skip('Fails; need to investigate.') + default_dtype = xp_default_dtype(xp) + x, y, ref = xp.asarray(x), xp.asarray(y), xp.asarray(ref, dtype=default_dtype) + res = stats.estimated_cdf(x, y, **kwargs) + xp_assert_close(res, ref, rtol=1e-15) + + @pytest.mark.skip_xp_backends('jax.numpy', reason="-1e-45 is not less than 0?") + @pytest.mark.parametrize('method', _estimated_cdf_discontinuous_methods.keys()) + def test_transition(self, method, xp): + # test that values of discontinuous estimators are as expected around + # transition point + x = np.arange(8., dtype=np.float64) + xl, xr = np.nextafter(x, -np.inf), np.nextafter(x, np.inf) + offset = 0.5 if method == 'closest_observation' else 0.0 + ref_r = (x + 1 + offset) / 8 + ref_r[-1] = 1.0 # value is greater than or equal to the maximum observation + ref_l = (x + offset) / 8 + ref_l[0] = 0.0 # value is less than the minimum observation + x, xl, xr = xp.asarray(x), xp.asarray(xl), xp.asarray(xr) + ref_l, ref_r = xp.asarray(ref_l), xp.asarray(ref_r) + xp_assert_equal(stats.estimated_cdf(x, x, method=method), ref_r) + xp_assert_equal(stats.estimated_cdf(x, xr, method=method), ref_r) + xp_assert_equal(stats.estimated_cdf(x, xl, method=method), ref_l) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/test_rank.py b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_rank.py new file mode 100644 index 0000000000000000000000000000000000000000..1d8e89cfea0315e30d1ab0c45e8bdab282360761 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_rank.py @@ -0,0 +1,350 @@ +import numpy as np +from numpy.testing import assert_equal, assert_array_equal +import pytest + +from scipy import stats +from scipy.conftest import skip_xp_invalid_arg +from scipy.stats import rankdata, tiecorrect +from scipy._lib._array_api import (xp_assert_equal, make_xp_test_case, xp_result_type, + xp_default_dtype) + +skip_xp_backends = pytest.mark.skip_xp_backends + +class TestTieCorrect: + + def test_empty(self): + """An empty array requires no correction, should return 1.0.""" + ranks = np.array([], dtype=np.float64) + c = tiecorrect(ranks) + assert_equal(c, 1.0) + + def test_one(self): + """A single element requires no correction, should return 1.0.""" + ranks = np.array([1.0], dtype=np.float64) + c = tiecorrect(ranks) + assert_equal(c, 1.0) + + def test_no_correction(self): + """Arrays with no ties require no correction.""" + ranks = np.arange(2.0) + c = tiecorrect(ranks) + assert_equal(c, 1.0) + ranks = np.arange(3.0) + c = tiecorrect(ranks) + assert_equal(c, 1.0) + + def test_basic(self): + """Check a few basic examples of the tie correction factor.""" + # One tie of two elements + ranks = np.array([1.0, 2.5, 2.5]) + c = tiecorrect(ranks) + T = 2.0 + N = ranks.size + expected = 1.0 - (T**3 - T) / (N**3 - N) + assert_equal(c, expected) + + # One tie of two elements (same as above, but tie is not at the end) + ranks = np.array([1.5, 1.5, 3.0]) + c = tiecorrect(ranks) + T = 2.0 + N = ranks.size + expected = 1.0 - (T**3 - T) / (N**3 - N) + assert_equal(c, expected) + + # One tie of three elements + ranks = np.array([1.0, 3.0, 3.0, 3.0]) + c = tiecorrect(ranks) + T = 3.0 + N = ranks.size + expected = 1.0 - (T**3 - T) / (N**3 - N) + assert_equal(c, expected) + + # Two ties, lengths 2 and 3. + ranks = np.array([1.5, 1.5, 4.0, 4.0, 4.0]) + c = tiecorrect(ranks) + T1 = 2.0 + T2 = 3.0 + N = ranks.size + expected = 1.0 - ((T1**3 - T1) + (T2**3 - T2)) / (N**3 - N) + assert_equal(c, expected) + + def test_overflow(self): + ntie, k = 2000, 5 + a = np.repeat(np.arange(k), ntie) + n = a.size # ntie * k + out = tiecorrect(rankdata(a)) + assert_equal(out, 1.0 - k * (ntie**3 - ntie) / float(n**3 - n)) + + +@make_xp_test_case(stats.rankdata) +class TestRankData: + + def desired_dtype(self, x, *, xp): + return xp_result_type(x, force_floating=True, xp=xp) + + def test_empty(self, xp): + """stats.rankdata of empty array should return an empty array.""" + a = xp.asarray([], dtype=xp.int64) + r = rankdata(a) + xp_assert_equal(r, xp.asarray([], dtype=self.desired_dtype(a, xp=xp))) + + def test_list(self): + # test that NumPy still accepts lists + r = rankdata([]) + assert_array_equal(r, np.array([])) + + r = rankdata([40, 10, 30, 10, 50]) + assert_equal(r, [4.0, 1.5, 3.0, 1.5, 5.0]) + + @pytest.mark.parametrize("shape", [(0, 1, 2)]) + @pytest.mark.parametrize("axis", [None, *range(3)]) + def test_empty_multidim(self, shape, axis, xp): + a = xp.empty(shape, dtype=xp.int64) + r = rankdata(a, axis=axis) + expected_shape = (0,) if axis is None else shape + xp_assert_equal(r, xp.empty(expected_shape, dtype=self.desired_dtype(a, xp=xp))) + + def test_one(self, xp): + """Check stats.rankdata with an array of length 1.""" + data = [100] + a = xp.asarray(data, dtype=xp.int64) + r = rankdata(a) + xp_assert_equal(r, xp.asarray([1.0], dtype=self.desired_dtype(a, xp=xp))) + + def test_basic(self, xp): + """Basic tests of stats.rankdata.""" + desired_dtype = xp_default_dtype(xp) + + data = [100, 10, 50] + expected = xp.asarray([3.0, 1.0, 2.0], dtype=desired_dtype) + a = xp.asarray(data, dtype=xp.int64) + r = rankdata(a) + xp_assert_equal(r, expected) + + data = [40, 10, 30, 10, 50] + expected = xp.asarray([4.0, 1.5, 3.0, 1.5, 5.0], dtype=desired_dtype) + a = xp.asarray(data, dtype=xp.int64) + r = rankdata(a) + xp_assert_equal(r, expected) + + data = [20, 20, 20, 10, 10, 10] + expected = xp.asarray([5.0, 5.0, 5.0, 2.0, 2.0, 2.0], dtype=desired_dtype) + a = xp.asarray(data, dtype=xp.int64) + r = rankdata(a) + xp_assert_equal(r, expected) + + # # The docstring states explicitly that the argument is flattened. + a2d = xp.reshape(a, (2, 3)) + r = rankdata(a2d) + xp_assert_equal(r, expected) + + @skip_xp_invalid_arg + def test_rankdata_object_string(self): + + def min_rank(a): + return [1 + sum(i < j for i in a) for j in a] + + def max_rank(a): + return [sum(i <= j for i in a) for j in a] + + def ordinal_rank(a): + return min_rank([(x, i) for i, x in enumerate(a)]) + + def average_rank(a): + return [(i + j) / 2.0 for i, j in zip(min_rank(a), max_rank(a))] + + def dense_rank(a): + b = np.unique(a) + return [1 + sum(i < j for i in b) for j in a] + + rankf = dict(min=min_rank, max=max_rank, ordinal=ordinal_rank, + average=average_rank, dense=dense_rank) + + def check_ranks(a): + for method in 'min', 'max', 'dense', 'ordinal', 'average': + out = rankdata(a, method=method) + assert_array_equal(out, rankf[method](a)) + + val = ['foo', 'bar', 'qux', 'xyz', 'abc', 'efg', 'ace', 'qwe', 'qaz'] + check_ranks(np.random.choice(val, 200)) + check_ranks(np.random.choice(val, 200).astype('object')) + + val = np.array([0, 1, 2, 2.718, 3, 3.141], dtype='object') + check_ranks(np.random.choice(val, 200).astype('object')) + + @pytest.mark.skip_xp_backends("torch", reason="`take_along_axis` fails with uint64") + def test_large_uint(self, xp): + data = xp.asarray([2**60, 2**60+1], dtype=xp.uint64) + r = rankdata(data) + desired_dtype = self.desired_dtype(data, xp=xp) + xp_assert_equal(r, xp.asarray([1.0, 2.0], dtype=desired_dtype)) + + def test_large_int(self, xp): + data = xp.asarray([2**60, 2**60+1], dtype=xp.int64) + r = rankdata(data) + desired_dtype = self.desired_dtype(data, xp=xp) + xp_assert_equal(r, xp.asarray([1.0, 2.0], dtype=desired_dtype)) + + data = xp.asarray([2**60, -2**60+1], dtype=xp.int64) + r = rankdata(data) + xp_assert_equal(r, xp.asarray([2.0, 1.0], dtype=desired_dtype)) + + @pytest.mark.parametrize('n', [10000, 100000, 1000000]) + def test_big_tie(self, n, xp): + data = xp.ones(n) + r = rankdata(data) + expected_rank = 0.5 * (n + 1) + ref = xp.asarray(expected_rank * data, dtype=self.desired_dtype(data, xp=xp)) + xp_assert_equal(r, ref) + + def test_axis(self, xp): + data = xp.asarray([[0, 2, 1], [4, 2, 2]]) + + expected0 = xp.asarray([[1., 1.5, 1.], [2., 1.5, 2.]]) + r0 = rankdata(data, axis=0) + xp_assert_equal(r0, expected0) + + expected1 = xp.asarray([[1., 3., 2.], [3., 1.5, 1.5]]) + r1 = rankdata(data, axis=1) + xp_assert_equal(r1, expected1) + + methods= ["average", "min", "max", "dense", "ordinal"] + + @pytest.mark.parametrize("axis", [0, 1]) + @pytest.mark.parametrize("method", methods) + def test_size_0_axis(self, axis, method, xp): + shape = (3, 0) + data = xp.zeros(shape) + desired_dtype = self.desired_dtype(data, xp=xp) + r = rankdata(data, method=method, axis=axis) + assert_equal(r.shape, shape) + assert_equal(r.dtype, desired_dtype) + xp_assert_equal(r, xp.empty(shape, dtype=desired_dtype)) + + @pytest.mark.parametrize('axis', range(3)) + @pytest.mark.parametrize('method', methods) + def test_nan_policy_omit_3d(self, axis, method): + shape = (20, 21, 22) + rng = np.random.RandomState(23983242) + + a = rng.random(size=shape) + i = rng.random(size=shape) < 0.4 + j = rng.random(size=shape) < 0.1 + k = rng.random(size=shape) < 0.1 + a[i] = np.nan + a[j] = -np.inf + a[k] - np.inf + + def rank_1d_omit(a, method): + out = np.zeros_like(a) + i = np.isnan(a) + a_compressed = a[~i] + res = rankdata(a_compressed, method) + out[~i] = res + out[i] = np.nan + return out + + def rank_omit(a, method, axis): + return np.apply_along_axis(lambda a: rank_1d_omit(a, method), + axis, a) + + res = rankdata(a, method, axis=axis, nan_policy='omit') + res0 = rank_omit(a, method, axis=axis) + + assert_array_equal(res, res0) + + def test_nan_policy_2d_axis_none(self): + # 2 2d-array test with axis=None + data = [[0, np.nan, 3], + [4, 2, np.nan], + [1, 2, 2]] + assert_array_equal(rankdata(data, axis=None, nan_policy='omit'), + [1., np.nan, 6., 7., 4., np.nan, 2., 4., 4.]) + assert_array_equal(rankdata(data, axis=None, nan_policy='propagate'), + [np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, + np.nan, np.nan, np.nan]) + + def test_nan_policy_raise(self): + # 1 1d-array test + data = [0, 2, 3, -2, np.nan, np.nan] + with pytest.raises(ValueError, match="The input contains nan"): + rankdata(data, nan_policy='raise') + + # 2 2d-array test + data = [[0, np.nan, 3], + [4, 2, np.nan], + [np.nan, 2, 2]] + + with pytest.raises(ValueError, match="The input contains nan"): + rankdata(data, axis=0, nan_policy="raise") + + with pytest.raises(ValueError, match="The input contains nan"): + rankdata(data, axis=1, nan_policy="raise") + + def test_nan_policy_propagate(self): + # 1 1d-array test + data = [0, 2, 3, -2, np.nan, np.nan] + assert_array_equal(rankdata(data, nan_policy='propagate'), + [np.nan, np.nan, np.nan, np.nan, np.nan, np.nan]) + + # 2 2d-array test + data = [[0, np.nan, 3], + [4, 2, np.nan], + [1, 2, 2]] + assert_array_equal(rankdata(data, axis=0, nan_policy='propagate'), + [[1, np.nan, np.nan], + [3, np.nan, np.nan], + [2, np.nan, np.nan]]) + assert_array_equal(rankdata(data, axis=1, nan_policy='propagate'), + [[np.nan, np.nan, np.nan], + [np.nan, np.nan, np.nan], + [1, 2.5, 2.5]]) + + _rankdata_cases = ( + # values, method, expected + ([], 'average', []), + ([], 'min', []), + ([], 'max', []), + ([], 'dense', []), + ([], 'ordinal', []), + # + ([100], 'average', [1.0]), + ([100], 'min', [1.0]), + ([100], 'max', [1.0]), + ([100], 'dense', [1.0]), + ([100], 'ordinal', [1.0]), + # + ([100, 100, 100], 'average', [2.0, 2.0, 2.0]), + ([100, 100, 100], 'min', [1.0, 1.0, 1.0]), + ([100, 100, 100], 'max', [3.0, 3.0, 3.0]), + ([100, 100, 100], 'dense', [1.0, 1.0, 1.0]), + ([100, 100, 100], 'ordinal', [1.0, 2.0, 3.0]), + # + ([100, 300, 200], 'average', [1.0, 3.0, 2.0]), + ([100, 300, 200], 'min', [1.0, 3.0, 2.0]), + ([100, 300, 200], 'max', [1.0, 3.0, 2.0]), + ([100, 300, 200], 'dense', [1.0, 3.0, 2.0]), + ([100, 300, 200], 'ordinal', [1.0, 3.0, 2.0]), + # + ([100, 200, 300, 200], 'average', [1.0, 2.5, 4.0, 2.5]), + ([100, 200, 300, 200], 'min', [1.0, 2.0, 4.0, 2.0]), + ([100, 200, 300, 200], 'max', [1.0, 3.0, 4.0, 3.0]), + ([100, 200, 300, 200], 'dense', [1.0, 2.0, 3.0, 2.0]), + ([100, 200, 300, 200], 'ordinal', [1.0, 2.0, 4.0, 3.0]), + # + ([100, 200, 300, 200, 100], 'average', [1.5, 3.5, 5.0, 3.5, 1.5]), + ([100, 200, 300, 200, 100], 'min', [1.0, 3.0, 5.0, 3.0, 1.0]), + ([100, 200, 300, 200, 100], 'max', [2.0, 4.0, 5.0, 4.0, 2.0]), + ([100, 200, 300, 200, 100], 'dense', [1.0, 2.0, 3.0, 2.0, 1.0]), + ([100, 200, 300, 200, 100], 'ordinal', [1.0, 3.0, 5.0, 4.0, 2.0]), + # + ([10] * 30, 'ordinal', np.arange(1.0, 31.0)), + ) + + @pytest.mark.parametrize('case', _rankdata_cases) + def test_cases(self, case, xp): + values, method, expected = case + values = xp.asarray(values) + r = rankdata(xp.asarray(values), method=method) + ref = xp.asarray(expected, dtype=self.desired_dtype(values, xp=xp)) + xp_assert_equal(r, ref) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/test_relative_risk.py b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_relative_risk.py new file mode 100644 index 0000000000000000000000000000000000000000..b75e64d929f319465b1f1d62af4fb2096c2ab2ac --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_relative_risk.py @@ -0,0 +1,95 @@ +import pytest +import numpy as np +from numpy.testing import assert_allclose, assert_equal +from scipy.stats.contingency import relative_risk + + +# Test just the calculation of the relative risk, including edge +# cases that result in a relative risk of 0, inf or nan. +@pytest.mark.parametrize( + 'exposed_cases, exposed_total, control_cases, control_total, expected_rr', + [(1, 4, 3, 8, 0.25 / 0.375), + (0, 10, 5, 20, 0), + (0, 10, 0, 20, np.nan), + (5, 15, 0, 20, np.inf)] +) +def test_relative_risk(exposed_cases, exposed_total, + control_cases, control_total, expected_rr): + result = relative_risk(exposed_cases, exposed_total, + control_cases, control_total) + assert_allclose(result.relative_risk, expected_rr, rtol=1e-13) + + +def test_relative_risk_confidence_interval(): + result = relative_risk(exposed_cases=16, exposed_total=128, + control_cases=24, control_total=256) + rr = result.relative_risk + ci = result.confidence_interval(confidence_level=0.95) + # The corresponding calculation in R using the epitools package. + # + # > library(epitools) + # > c <- matrix(c(232, 112, 24, 16), nrow=2) + # > result <- riskratio(c) + # > result$measure + # risk ratio with 95% C.I. + # Predictor estimate lower upper + # Exposed1 1.000000 NA NA + # Exposed2 1.333333 0.7347317 2.419628 + # + # The last line is the result that we want. + assert_allclose(rr, 4/3) + assert_allclose((ci.low, ci.high), (0.7347317, 2.419628), rtol=5e-7) + + +def test_relative_risk_ci_conflevel0(): + result = relative_risk(exposed_cases=4, exposed_total=12, + control_cases=5, control_total=30) + rr = result.relative_risk + assert_allclose(rr, 2.0, rtol=1e-14) + ci = result.confidence_interval(0) + assert_allclose((ci.low, ci.high), (2.0, 2.0), rtol=1e-12) + + +def test_relative_risk_ci_conflevel1(): + result = relative_risk(exposed_cases=4, exposed_total=12, + control_cases=5, control_total=30) + ci = result.confidence_interval(1) + assert_equal((ci.low, ci.high), (0, np.inf)) + + +def test_relative_risk_ci_edge_cases_00(): + result = relative_risk(exposed_cases=0, exposed_total=12, + control_cases=0, control_total=30) + assert_equal(result.relative_risk, np.nan) + ci = result.confidence_interval() + assert_equal((ci.low, ci.high), (np.nan, np.nan)) + + +def test_relative_risk_ci_edge_cases_01(): + result = relative_risk(exposed_cases=0, exposed_total=12, + control_cases=1, control_total=30) + assert_equal(result.relative_risk, 0) + ci = result.confidence_interval() + assert_equal((ci.low, ci.high), (0.0, np.nan)) + + +def test_relative_risk_ci_edge_cases_10(): + result = relative_risk(exposed_cases=1, exposed_total=12, + control_cases=0, control_total=30) + assert_equal(result.relative_risk, np.inf) + ci = result.confidence_interval() + assert_equal((ci.low, ci.high), (np.nan, np.inf)) + + +@pytest.mark.parametrize('ec, et, cc, ct', [(0, 0, 10, 20), + (-1, 10, 1, 5), + (1, 10, 0, 0), + (1, 10, -1, 4)]) +def test_relative_risk_bad_value(ec, et, cc, ct): + with pytest.raises(ValueError, match="must be an integer not less than"): + relative_risk(ec, et, cc, ct) + + +def test_relative_risk_bad_type(): + with pytest.raises(TypeError, match="must be an integer"): + relative_risk(1, 10, 2.0, 40) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/test_resampling.py b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_resampling.py new file mode 100644 index 0000000000000000000000000000000000000000..ec900da2d5a05570054596286f82c67f96fbdc0d --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_resampling.py @@ -0,0 +1,2084 @@ +import warnings + +import pytest + +import numpy as np +from numpy.testing import assert_allclose, assert_equal + +from scipy._lib._util import rng_integers +from scipy._lib._array_api import (is_numpy, make_xp_test_case, xp_default_dtype, + xp_size, array_namespace, _xp_copy_to_numpy, + is_lazy_array, eager_warns) +from scipy._lib._array_api_no_0d import xp_assert_close, xp_assert_equal +from scipy._external import array_api_extra as xpx +from scipy import stats, special +from scipy.fft.tests.test_fftlog import skip_xp_backends +from scipy.optimize import root + +from scipy.stats import bootstrap, monte_carlo_test, permutation_test, power +import scipy.stats._resampling as _resampling + + +lazy_xp_modules = [stats] + + +@make_xp_test_case(bootstrap) +class TestBootstrap: + @skip_xp_backends('numpy', reason='NumPy does not raise') + def test_bootstrap_iv_other(self, xp): + message = f"When using array library {xp.__name__}" + with pytest.raises(TypeError, match=message): + bootstrap((xp.asarray([1, 2, 3]),), lambda x: xp.mean(x)) + + def test_bootstrap_iv(self, xp): + sample = xp.asarray([1, 2, 3]) + + message = "`data` must contain at least one sample." + with pytest.raises(ValueError, match=message): + bootstrap(tuple(), xp.mean) + + message = "each sample in `data` must contain two or more observations..." + with pytest.raises(ValueError, match=message): + bootstrap((sample, xp.asarray([1])), xp.mean) + + message = ("When `paired is True`, all samples must have the same length ") + with pytest.raises(ValueError, match=message): + bootstrap((sample, xp.asarray([1, 2, 3, 4])), xp.mean, paired=True) + + message = "`vectorized` must be `True`, `False`, or `None`." + with pytest.raises(ValueError, match=message): + bootstrap(sample, xp.mean, vectorized='ekki') + + message = "`axis` must be an integer." + with pytest.raises(ValueError, match=message): + bootstrap((sample,), xp.mean, axis=1.5) + + message = "could not convert string to float" + with pytest.raises(ValueError, match=message): + bootstrap((sample,), xp.mean, confidence_level='ni') + + message = "`n_resamples` must be a non-negative integer." + with pytest.raises(ValueError, match=message): + bootstrap((sample,), xp.mean, n_resamples=-1000) + + message = "`n_resamples` must be a non-negative integer." + with pytest.raises(ValueError, match=message): + bootstrap((sample,), xp.mean, n_resamples=1000.5) + + message = "`batch` must be a positive integer or None." + with pytest.raises(ValueError, match=message): + bootstrap((sample,), xp.mean, batch=-1000) + + message = "`batch` must be a positive integer or None." + with pytest.raises(ValueError, match=message): + bootstrap((sample,), xp.mean, batch=1000.5) + + message = "`method` must be in" + with pytest.raises(ValueError, match=message): + bootstrap((sample,), xp.mean, method='ekki') + + message = "`bootstrap_result` must have attribute `bootstrap_distribution'" + with pytest.raises(ValueError, match=message): + bootstrap((sample,), xp.mean, bootstrap_result=10) + + message = "Either `bootstrap_result.bootstrap_distribution.size`" + with pytest.raises(ValueError, match=message): + bootstrap((sample,), xp.mean, n_resamples=0) + + message = "SeedSequence expects int or sequence of ints" + with pytest.raises(TypeError, match=message): + bootstrap((sample,), xp.mean, rng='herring') + + @pytest.mark.parametrize("method", ['basic', 'percentile', 'BCa']) + @pytest.mark.parametrize("axis", [0, 1, 2]) + def test_bootstrap_batch(self, method, axis, xp): + # for one-sample statistics, batch size shouldn't affect the result + rng = np.random.RandomState(0) + + x = rng.rand(10, 11, 12) + # SPEC-007 leave one call with random_state to ensure it still works + res1 = bootstrap((xp.asarray(x),), xp.mean, batch=None, method=method, + random_state=0, axis=axis, n_resamples=100) + rng = np.random.RandomState(0) + res2 = bootstrap((xp.asarray(x),), xp.mean, batch=10, method=method, + axis=axis, n_resamples=100, random_state=rng) + + xp_assert_close(res2.confidence_interval.low, res1.confidence_interval.low) + xp_assert_close(res2.confidence_interval.high, res1.confidence_interval.high) + xp_assert_close(res2.standard_error, res1.standard_error) + + @pytest.mark.parametrize("method", ['basic', 'percentile', 'BCa']) + def test_bootstrap_paired(self, method, xp): + # test that `paired` works as expected + rng = np.random.RandomState(0) + n = 100 + x = xp.asarray(rng.rand(n)) + y = xp.asarray(rng.rand(n)) + + def my_statistic(x, y, axis=-1): + return xp.mean((x-y)**2, axis=axis) + + def my_paired_statistic(i, axis=-1): + a = x[i] + b = y[i] + res = my_statistic(a, b) + return res + + i = xp.arange(x.shape[0]) + + res1 = bootstrap((i,), my_paired_statistic, rng=0) + res2 = bootstrap((x, y), my_statistic, paired=True, rng=0) + + xp_assert_close(res1.confidence_interval.low, res2.confidence_interval.low) + xp_assert_close(res1.confidence_interval.high, res2.confidence_interval.high) + xp_assert_close(res1.standard_error, res2.standard_error) + + @pytest.mark.parametrize("method", ['basic', 'percentile', 'BCa']) + @pytest.mark.parametrize("axis", [0, 1, 2]) + @pytest.mark.parametrize("paired", [True, False]) + def test_bootstrap_vectorized(self, method, axis, paired, xp): + # test that paired is vectorized as expected: when samples are tiled, + # CI and standard_error of each axis-slice is the same as those of the + # original 1d sample + + rng = np.random.RandomState(0) + + def my_statistic(x, y, z, axis=-1): + return xp.mean(x, axis=axis) + xp.mean(y, axis=axis) + xp.mean(z, axis=axis) + + shape = 10, 11, 12 + n_samples = shape[axis] + + x = rng.rand(n_samples) + y = rng.rand(n_samples) + z = rng.rand(n_samples) + x, y, z = xp.asarray(x), xp.asarray(y), xp.asarray(z) + + res1 = bootstrap((x, y, z), my_statistic, paired=paired, method=method, + rng=0, axis=0, n_resamples=100) + assert (res1.bootstrap_distribution.shape + == res1.standard_error.shape + (100,)) + + reshape = [1, 1, 1] + reshape[axis] = n_samples + reshape = tuple(reshape) + x = xp.broadcast_to(xp.reshape(x, reshape), shape) + y = xp.broadcast_to(xp.reshape(y, reshape), shape) + z = xp.broadcast_to(xp.reshape(z, reshape), shape) + res2 = bootstrap((x, y, z), my_statistic, paired=paired, method=method, + rng=0, axis=axis, n_resamples=100) + + result_shape = list(shape) + result_shape.pop(axis) + + ref_ci_low = xp.broadcast_to(res2.confidence_interval.low, result_shape) + ref_ci_high = xp.broadcast_to(res2.confidence_interval.high, result_shape) + ref_ci_standard_error = xp.broadcast_to(res2.standard_error, result_shape) + + xp_assert_close(res2.confidence_interval.low, ref_ci_low) + xp_assert_close(res2.confidence_interval.high, ref_ci_high) + xp_assert_close(res2.standard_error, ref_ci_standard_error) + + @pytest.mark.slow + @pytest.mark.xfail_on_32bit("MemoryError with BCa observed in CI") + @pytest.mark.parametrize("method", ['basic', 'percentile', 'BCa']) + def test_bootstrap_against_theory(self, method, xp): + # based on https://www.statology.org/confidence-intervals-python/ + rng = np.random.default_rng(2442101192988600726) + data = stats.norm.rvs(loc=5, scale=2, size=5000, random_state=rng) + alpha = 0.95 + dist = stats.t(df=len(data)-1, loc=np.mean(data), scale=stats.sem(data)) + ref_low, ref_high = dist.interval(confidence=alpha) + ref_se = dist.std() + + config = dict(data=(xp.asarray(data),), statistic=xp.mean, n_resamples=5000, + method=method, rng=rng) + res = bootstrap(**config, confidence_level=alpha) + + xp_assert_close(res.confidence_interval.low, xp.asarray(ref_low), rtol=5e-4) + xp_assert_close(res.confidence_interval.high, xp.asarray(ref_high), rtol=5e-4) + xp_assert_close(res.standard_error, xp.asarray(ref_se), atol=3e-4) + + config.update(dict(n_resamples=0, bootstrap_result=res)) + res = bootstrap(**config, confidence_level=alpha, alternative='less') + xp_assert_close(res.confidence_interval.high, + xp.asarray(dist.ppf(alpha)), rtol=5e-4) + + config.update(dict(n_resamples=0, bootstrap_result=res)) + res = bootstrap(**config, confidence_level=alpha, alternative='greater') + xp_assert_close(res.confidence_interval.low, + xp.asarray(dist.ppf(1-alpha)), rtol=5e-4) + + tests_R = [("basic", 23.77, 79.12), + ("percentile", 28.86, 84.21), + ("BCa", 32.31, 91.43)] + + @pytest.mark.parametrize("method, ref_low, ref_high", tests_R) + def test_bootstrap_against_R(self, method, ref_low, ref_high, xp): + # Compare against R's "boot" library + # library(boot) + + # stat <- function (x, a) { + # mean(x[a]) + # } + + # x <- c(10, 12, 12.5, 12.5, 13.9, 15, 21, 22, + # 23, 34, 50, 81, 89, 121, 134, 213) + + # # Use a large value so we get a few significant digits for the CI. + # n = 1000000 + # bootresult = boot(x, stat, n) + # result <- boot.ci(bootresult) + # print(result) + x = xp.asarray([10, 12, 12.5, 12.5, 13.9, 15, 21, 22, + 23, 34, 50, 81, 89, 121, 134, 213]) + res = bootstrap((x,), xp.mean, n_resamples=1000000, method=method, rng=0) + xp_assert_close(res.confidence_interval.low, xp.asarray(ref_low), rtol=0.005) + xp_assert_close(res.confidence_interval.high, xp.asarray(ref_high), rtol=0.005) + + def test_multisample_BCa_against_R(self, xp): + # Because bootstrap is stochastic, it's tricky to test against reference + # behavior. Here, we show that SciPy's BCa CI matches R wboot's BCa CI + # much more closely than the other SciPy CIs do. + + # arbitrary skewed data + x = xp.asarray([0.75859206, 0.5910282, -0.4419409, -0.36654601, + 0.34955357, -1.38835871, 0.76735821]) + y = xp.asarray([1.41186073, 0.49775975, 0.08275588, 0.24086388, + 0.03567057, 0.52024419, 0.31966611, 1.32067634]) + + # a multi-sample statistic for which the BCa CI tends to be different + # from the other CIs + def statistic(x, y, axis): + s1 = stats.skew(x, axis=axis) + s2 = stats.skew(y, axis=axis) + return s1 - s2 + + # compute confidence intervals using each method + rng = np.random.default_rng(468865032284792692) + + res_basic = stats.bootstrap((x, y), statistic, method='basic', + batch=100, rng=rng) + res_percent = stats.bootstrap((x, y), statistic, method='percentile', + batch=100, rng=rng) + res_bca = stats.bootstrap((x, y), statistic, method='bca', + batch=100, rng=rng) + + # compute midpoints so we can compare just one number for each + mid_basic = xp.mean(xp.stack(res_basic.confidence_interval)) + mid_percent = xp.mean(xp.stack(res_percent.confidence_interval)) + mid_bca = xp.mean(xp.stack(res_bca.confidence_interval)) + + # reference for BCA CI computed using R wboot package: + # library(wBoot) + # library(moments) + + # x = c(0.75859206, 0.5910282, -0.4419409, -0.36654601, + # 0.34955357, -1.38835871, 0.76735821) + # y = c(1.41186073, 0.49775975, 0.08275588, 0.24086388, + # 0.03567057, 0.52024419, 0.31966611, 1.32067634) + + # twoskew <- function(x1, y1) {skewness(x1) - skewness(y1)} + # boot.two.bca(x, y, skewness, conf.level = 0.95, + # R = 9999, stacked = FALSE) + mid_wboot = -1.5519 + + # compute percent difference relative to wboot BCA method + diff_basic = (mid_basic - mid_wboot)/abs(mid_wboot) + diff_percent = (mid_percent - mid_wboot)/abs(mid_wboot) + diff_bca = (mid_bca - mid_wboot)/abs(mid_wboot) + + # SciPy's BCa CI midpoint is much closer than that of the other methods + assert diff_basic < -0.15 + assert diff_percent > 0.15 + assert abs(diff_bca) < 0.03 + + def test_BCa_acceleration_against_reference(self, xp): + # Compare the (deterministic) acceleration parameter for a multi-sample + # problem against a reference value. The example is from [1], but Efron's + # value seems inaccurate. Straightforward code for computing the + # reference acceleration (0.011008228344026734) is available at: + # https://github.com/scipy/scipy/pull/16455#issuecomment-1193400981 + + y = xp.asarray([10., 27., 31., 40., 46., 50., 52., 104., 146.]) + z = xp.asarray([16., 23., 38., 94., 99., 141., 197.]) + + def statistic(z, y, axis=0): + return xp.mean(z, axis=axis) - xp.mean(y, axis=axis) + + data = [z, y] + res = stats.bootstrap(data, statistic) + + axis = -1 + alpha = 0.95 + theta_hat_b = res.bootstrap_distribution + batch = 100 + _, _, a_hat = _resampling._bca_interval(data, statistic, axis, alpha, + theta_hat_b, batch, xp) + xp_assert_close(a_hat, xp.asarray(0.011008228344026734)) + + tests_against_itself_1samp = {"basic": 1789, + "percentile": 1790, + "BCa": 1789} + + @pytest.mark.slow + @pytest.mark.parametrize("method, expected", + tests_against_itself_1samp.items()) + def test_bootstrap_against_itself_1samp(self, method, expected, xp): + # The expected values in this test were generated using bootstrap + # to check for unintended changes in behavior. The test also makes sure + # that bootstrap works with multi-sample statistics and that the + # `axis` argument works as expected / function is vectorized. + rng = np.random.default_rng(9123847) + + n = 100 # size of sample + n_resamples = 999 # number of bootstrap resamples used to form each CI + confidence_level = 0.9 + + # The true mean is 5 + dist = stats.norm(loc=5, scale=1) + stat_true = float(dist.mean()) + + # Do the same thing 2000 times. (The code is fully vectorized.) + n_replications = 2000 + data = dist.rvs(size=(n_replications, n), random_state=rng) + res = bootstrap((xp.asarray(data),), + statistic=xp.mean, + confidence_level=confidence_level, + n_resamples=n_resamples, + batch=50, + method=method, + axis=-1, + rng=rng) + ci = res.confidence_interval + + # ci contains vectors of lower and upper confidence interval bounds + ci_contains_true = xp.count_nonzero((ci[0] < stat_true) & (stat_true < ci[1])) + assert ci_contains_true == expected + + # ci_contains_true is not inconsistent with confidence_level + pvalue = stats.binomtest(int(ci_contains_true), n_replications, + confidence_level).pvalue + assert pvalue > 0.1 + + tests_against_itself_2samp = {"basic": 892, + "percentile": 890} + + @pytest.mark.slow + @pytest.mark.parametrize("method, expected", + tests_against_itself_2samp.items()) + def test_bootstrap_against_itself_2samp(self, method, expected, xp): + # The expected values in this test were generated using bootstrap + # to check for unintended changes in behavior. The test also makes sure + # that bootstrap works with multi-sample statistics and that the + # `axis` argument works as expected / function is vectorized. + rng = np.random.RandomState(0) + + n1 = 100 # size of sample 1 + n2 = 120 # size of sample 2 + n_resamples = 999 # number of bootstrap resamples used to form each CI + confidence_level = 0.9 + + # The statistic we're interested in is the difference in means + def my_stat(data1, data2, axis=-1): + mean1 = xp.mean(data1, axis=axis) + mean2 = xp.mean(data2, axis=axis) + return mean1 - mean2 + + # The true difference in the means is -0.1 + dist1 = stats.norm(loc=0, scale=1) + dist2 = stats.norm(loc=0.1, scale=1) + stat_true = float(dist1.mean() - dist2.mean()) + + # Do the same thing 1000 times. (The code is fully vectorized.) + n_replications = 1000 + data1 = dist1.rvs(size=(n_replications, n1), random_state=rng) + data2 = dist2.rvs(size=(n_replications, n2), random_state=rng) + res = bootstrap((xp.asarray(data1), xp.asarray(data2)), + statistic=my_stat, + confidence_level=confidence_level, + n_resamples=n_resamples, + batch=50, + method=method, + axis=-1, + random_state=rng) + ci = res.confidence_interval + + # ci contains vectors of lower and upper confidence interval bounds + ci_contains_true = xp.count_nonzero((ci[0] < stat_true) & (stat_true < ci[1])) + assert ci_contains_true == expected + + # ci_contains_true is not inconsistent with confidence_level + pvalue = stats.binomtest(int(ci_contains_true), n_replications, + confidence_level).pvalue + assert pvalue > 0.1 + + @pytest.mark.parametrize("method", ["basic", "percentile", "BCa"]) + @pytest.mark.parametrize("axis", [0, 1]) + @pytest.mark.parametrize("dtype", ['float32', 'float64']) + def test_bootstrap_vectorized_3samp(self, method, axis, dtype, xp): + def statistic(*data, axis=0): + # an arbitrary, vectorized statistic + return sum(xp.mean(sample, axis=axis) for sample in data) + + def statistic_1d(*data): + # the same statistic, not vectorized + for sample in data: + assert sample.ndim == 1 + return np.asarray(sum(sample.mean() for sample in data), dtype=dtype) + + rng = np.random.RandomState(0) + x = rng.rand(4, 5).astype(dtype) + y = rng.rand(4, 5).astype(dtype) + z = rng.rand(4, 5).astype(dtype) + res1 = bootstrap((xp.asarray(x), xp.asarray(y), xp.asarray(z)), + statistic, vectorized=True, axis=axis, n_resamples=100, + method=method, rng=0) + res2 = bootstrap((x, y, z), statistic_1d, vectorized=False, + axis=axis, n_resamples=100, method=method, rng=0) + + rtol = 1e-6 if dtype == 'float32' else 1e-14 + xp_assert_close(res1.confidence_interval.low, + xp.asarray(res2.confidence_interval.low), rtol=rtol) + xp_assert_close(res1.confidence_interval.high, + xp.asarray(res2.confidence_interval.high), rtol=rtol) + xp_assert_close(res1.standard_error, xp.asarray(res2.standard_error), rtol=rtol) + + @pytest.mark.xfail_on_32bit("Failure is not concerning; see gh-14107") + @pytest.mark.parametrize("method", ["basic", "percentile", "BCa"]) + @pytest.mark.parametrize("axis", [0, 1]) + def test_bootstrap_vectorized_1samp(self, method, axis, xp): + def statistic(x, axis=0): + # an arbitrary, vectorized statistic + return xp.mean(x, axis=axis) + + def statistic_1d(x): + # the same statistic, not vectorized + assert x.ndim == 1 + return x.mean(axis=0) + + rng = np.random.default_rng(7939952824) + x = rng.random((2, 3)) + res1 = bootstrap((xp.asarray(x),), statistic, vectorized=True, axis=axis, + n_resamples=100, batch=None, method=method, rng=0) + res2 = bootstrap((x,), statistic_1d, vectorized=False, axis=axis, + n_resamples=100, batch=10, method=method, rng=0) + xp_assert_close(res1.confidence_interval.low, + xp.asarray(res2.confidence_interval.low)) + xp_assert_close(res1.confidence_interval.high, + xp.asarray(res2.confidence_interval.high)) + xp_assert_close(res1.standard_error, xp.asarray(res2.standard_error)) + + @pytest.mark.parametrize("method", ["basic", "percentile", "BCa"]) + def test_bootstrap_degenerate(self, method, xp): + data = xp.full((35,), 10000.) + if method == "BCa": + with np.errstate(invalid='ignore'): + msg = "The BCa confidence interval cannot be calculated" + with eager_warns(stats.DegenerateDataWarning, match=msg, xp=xp): + res = bootstrap([data, ], xp.mean, method=method) + xp_assert_equal(res.confidence_interval.low, xp.asarray(xp.nan)) + xp_assert_equal(res.confidence_interval.high, xp.asarray(xp.nan)) + else: + res = bootstrap([data, ], xp.mean, method=method) + xp_assert_equal(res.confidence_interval.low, xp.asarray(10000.)) + xp_assert_equal(res.confidence_interval.high, xp.asarray(10000.)) + xp_assert_close(res.standard_error, xp.asarray(0.), atol=1e-12) + + @pytest.mark.parametrize("method", ["BCa", "basic", "percentile"]) + def test_bootstrap_gh15678(self, method, xp): + # Check that gh-15678 is fixed: when statistic function returned a Python + # float, method="BCa" failed when trying to add a dimension to the float + rng = np.random.default_rng(354645618886684) + dist = stats.norm(loc=2, scale=4) + data = dist.rvs(size=100, random_state=rng) + res = bootstrap((xp.asarray(data),), stats.skew, method=method, n_resamples=100, + rng=np.random.default_rng(9563)) + # this always worked because np.apply_along_axis returns NumPy data type + ref = bootstrap((data,), stats.skew, method=method, n_resamples=100, + rng=np.random.default_rng(9563), vectorized=False) + xp_assert_close(res.confidence_interval.low, + xp.asarray(ref.confidence_interval.low)) + xp_assert_close(res.confidence_interval.high, + xp.asarray(ref.confidence_interval.high)) + xp_assert_close(res.standard_error, xp.asarray(ref.standard_error)) + + def test_bootstrap_min(self, xp): + # Check that gh-15883 is fixed: percentileofscore should + # behave according to the 'mean' behavior and not trigger nan for BCa + rng = np.random.default_rng(1891289180021102) + dist = stats.norm(loc=2, scale=4) + data = dist.rvs(size=100, random_state=rng) + true_min = np.min(data) + data = xp.asarray(data) + res = bootstrap((data,), xp.min, method="BCa", n_resamples=100, + rng=np.random.default_rng(3942)) + xp_assert_equal(res.confidence_interval.low, xp.asarray(true_min)) + res2 = bootstrap((-data,), xp.max, method="BCa", n_resamples=100, + rng=np.random.default_rng(3942)) + xp_assert_close(-res.confidence_interval.low, res2.confidence_interval.high) + xp_assert_close(-res.confidence_interval.high, res2.confidence_interval.low) + + @pytest.mark.parametrize("additional_resamples", [0, 1000]) + def test_re_bootstrap(self, additional_resamples, xp): + # Test behavior of parameter `bootstrap_result` + rng = np.random.default_rng(8958153316228384) + x = rng.random(size=100) + + n1 = 1000 + n2 = additional_resamples + n3 = n1 + additional_resamples + + rng = np.random.default_rng(296689032789913033) + res = stats.bootstrap((xp.asarray(x),), xp.mean, n_resamples=n1, rng=rng, + confidence_level=0.95, method='percentile') + res = stats.bootstrap((xp.asarray(x),), xp.mean, n_resamples=n2, rng=rng, + confidence_level=0.90, method='BCa', + bootstrap_result=res) + + rng = np.random.default_rng(296689032789913033) + ref = stats.bootstrap((xp.asarray(x),), xp.mean, n_resamples=n3, rng=rng, + confidence_level=0.90, method='BCa') + + xp_assert_close(res.confidence_interval.low, + xp.asarray(ref.confidence_interval.low), + rtol=1e-14) + xp_assert_close(res.confidence_interval.high, + xp.asarray(ref.confidence_interval.high), + rtol=1e-14) + xp_assert_close(res.standard_error, xp.asarray(ref.standard_error), rtol=1e-14) + + @pytest.mark.xfail_on_32bit("Sensitive to machine precision") + @pytest.mark.parametrize("method", ['basic', 'percentile', 'BCa']) + def test_bootstrap_alternative(self, method, xp): + rng = np.random.default_rng(5894822712842015040) + dist = stats.norm(loc=2, scale=4) + data = (xp.asarray(dist.rvs(size=(100), random_state=rng)),) + + config = dict(data=data, statistic=xp.std, rng=rng, axis=-1) + t = stats.bootstrap(**config, confidence_level=0.9) + + config.update(dict(n_resamples=0, bootstrap_result=t)) + l = stats.bootstrap(**config, confidence_level=0.95, alternative='less') + g = stats.bootstrap(**config, confidence_level=0.95, alternative='greater') + + xp_assert_close(l.confidence_interval.high, t.confidence_interval.high, + rtol=1e-14) + xp_assert_close(g.confidence_interval.low, t.confidence_interval.low, + rtol=1e-14) + assert xp.isinf(l.confidence_interval.low) and l.confidence_interval.low < 0 + assert xp.isinf(g.confidence_interval.high) and g.confidence_interval.high > 0 + + with pytest.raises(ValueError, match='`alternative` must be one of'): + stats.bootstrap(**config, alternative='ekki-ekki') + + def test_jackknife_resample(self, xp): + shape = 3, 4, 5, 6 + rng = np.random.default_rng(5274950392) + x = rng.random(size=shape) + y = next(_resampling._jackknife_resample(xp.asarray(x), xp=xp)) + + for i in range(shape[-1]): + # each resample is indexed along second to last axis + # (last axis is the one the statistic will be taken over / consumed) + slc = y[..., i, :] + expected = np.delete(x, i, axis=-1) + + xp_assert_equal(slc, xp.asarray(expected)) + + y2 = list(_resampling._jackknife_resample(xp.asarray(x), batch=2, xp=xp)) + xp_assert_equal(xp.concat(y2, axis=-2), y) + + @pytest.mark.skip_xp_backends("array_api_strict", + reason="Test uses ... + fancy indexing") + @pytest.mark.parametrize("rng_name", ["RandomState", "default_rng"]) + def test_bootstrap_resample(self, rng_name, xp): + rng_gen = getattr(np.random, rng_name) + rng1 = rng_gen(3949441460) + rng2 = rng_gen(3949441460) + + n_resamples = 10 + shape = 3, 4, 5, 6 + + rng = np.random.default_rng(5274950392) + x = xp.asarray(rng.random(shape)) + y = _resampling._bootstrap_resample(x, n_resamples, rng=rng1, xp=xp) + + for i in range(n_resamples): + # each resample is indexed along second to last axis + # (last axis is the one the statistic will be taken over / consumed) + slc = y[..., i, :] + + js = xp.asarray(rng_integers(rng2, 0, shape[-1], shape[-1])) + expected = x[..., js] + + xp_assert_equal(slc, expected) + + @pytest.mark.parametrize("score", [0, 0.5, 1]) + @pytest.mark.parametrize("axis", [0, 1, 2]) + def test_percentile_of_score(self, score, axis, xp): + shape = 10, 20, 30 + rng = np.random.default_rng(5903363153) + x = rng.random(shape) + dtype = xp_default_dtype(xp) + p = _resampling._percentile_of_score(xp.asarray(x, dtype=dtype), + xp.asarray(score, dtype=dtype), + axis=-1, xp=xp) + + def vectorized_pos(a, score, axis): + return np.apply_along_axis(stats.percentileofscore, axis, a, score) + + p2 = vectorized_pos(x, score, axis=-1)/100 + + xp_assert_close(p, xp.asarray(p2, dtype=dtype), rtol=1e-15) + + @pytest.mark.parametrize("axis", [0, 1, 2]) + def test_vectorize_statistic(self, axis): + # test that _vectorize_statistic vectorizes a statistic along `axis` + # only used with NumPy arrays + + def statistic(*data, axis): + # an arbitrary, vectorized statistic + return sum(sample.mean(axis) for sample in data) + + def statistic_1d(*data): + # the same statistic, not vectorized + for sample in data: + assert sample.ndim == 1 + return statistic(*data, axis=0) + + # vectorize the non-vectorized statistic + statistic2 = _resampling._vectorize_statistic(statistic_1d) + + rng = np.random.RandomState(0) + x = rng.rand(4, 5, 6) + y = rng.rand(4, 1, 6) + z = rng.rand(1, 5, 6) + + res1 = statistic(x, y, z, axis=axis) + res2 = statistic2(x, y, z, axis=axis) + assert_allclose(res1, res2) + + @pytest.mark.slow + @pytest.mark.parametrize("method", ["basic", "percentile", "BCa"]) + def test_vector_valued_statistic(self, method, xp): + # Generate 95% confidence interval around MLE of normal distribution + # parameters. Repeat 100 times, each time on sample of size 100. + # Check that confidence interval contains true parameters ~95 times. + # Confidence intervals are estimated and stochastic; a test failure + # does not necessarily indicate that something is wrong. More important + # than values of `counts` below is that the shapes of the outputs are + # correct. + + rng = np.random.default_rng(2196847219) + params = 1, 0.5 + sample = xp.asarray(rng.normal(*params, size=(100, 100))) + + def statistic(data, axis): + return xp.stack([xp.mean(data, axis=axis), + xp.std(data, axis=axis, correction=1)]) + + res = bootstrap((sample,), statistic, method=method, axis=-1, + n_resamples=9999, batch=200, random_state=rng) + + params = xp.asarray([1, 0.5]) + counts = xp.count_nonzero((res.confidence_interval.low.T < params) + & (res.confidence_interval.high.T > params), + axis=0) + assert xp.all(counts >= 90) + assert xp.all(counts <= 100) + assert res.confidence_interval.low.shape == (2, 100) + assert res.confidence_interval.high.shape == (2, 100) + assert res.standard_error.shape == (2, 100) + assert res.bootstrap_distribution.shape == (2, 100, 9999) + + @pytest.mark.slow + @pytest.mark.filterwarnings('ignore::RuntimeWarning') + def test_vector_valued_statistic_gh17715(self): + # gh-17715 reported a mistake introduced in the extension of BCa to + # multi-sample statistics; a `len` should have been `.shape[-1]`. Check + # that this is resolved. + # If this is resolved for NumPy, it's resolved for the rest, + # let's only run this for NumPy. + + rng = np.random.default_rng(141921000979291141) + + def concordance(x, y, axis): + xm = x.mean(axis) + ym = y.mean(axis) + cov = ((x - xm[..., None]) * (y - ym[..., None])).mean(axis) + return (2 * cov) / (x.var(axis) + y.var(axis) + (xm - ym) ** 2) + + def statistic(tp, tn, fp, fn, axis): + actual = tp + fp + expected = tp + fn + return np.nan_to_num(concordance(actual, expected, axis)) + + def statistic_extradim(*args, axis): + return statistic(*args, axis)[np.newaxis, ...] + + data = [[4, 0, 0, 2], # (tp, tn, fp, fn) + [2, 1, 2, 1], + [0, 6, 0, 0], + [0, 6, 3, 0], + [0, 8, 1, 0]] + data = np.array(data).T + + res = bootstrap(data, statistic_extradim, rng=rng, paired=True) + ref = bootstrap(data, statistic, rng=rng, paired=True) + assert_allclose(res.confidence_interval.low[0], + ref.confidence_interval.low, atol=1e-15) + assert_allclose(res.confidence_interval.high[0], + ref.confidence_interval.high, atol=1e-15) + + # torch doesn't have T distribution CDF and can't fall back to NumPy on GPU + @pytest.mark.skip_xp_backends(np_only=True, exceptions=['cupy']) + def test_gh_20850(self, xp): + rng = np.random.default_rng(2085020850) + x = xp.asarray(rng.random((10, 2))) + y = xp.asarray(rng.random((11, 2))) + def statistic(x, y, axis): + return stats.ttest_ind(x, y, axis=axis).statistic + + # The shapes do *not* need to be the same along axis + stats.bootstrap((x, y), statistic) + stats.bootstrap((x.T, y.T), statistic, axis=1) + # But even when the shapes *are* the same along axis, the lengths + # along other dimensions have to be the same (or `bootstrap` warns). + message = "Array shapes are incompatible for broadcasting." + with pytest.raises(ValueError, match=message): + stats.bootstrap((x, y[:10, 0]), statistic) # this won't work after 1.16 + stats.bootstrap((x, y[:10, 0:1]), statistic) # this will + stats.bootstrap((x.T, y.T[0:1, :10]), statistic, axis=1) # this will + +# --- Test Monte Carlo Hypothesis Test --- # + +@make_xp_test_case(monte_carlo_test) +class TestMonteCarloHypothesisTest: + atol = 2.5e-2 # for comparing p-value + + def get_rvs(self, rvs_in, rs, dtype=None, xp=np): + return lambda *args, **kwds: xp.asarray(rvs_in(*args, random_state=rs, **kwds), + dtype=dtype) + + def get_statistic(self, xp): + def statistic(x, axis): + m = xp.mean(x, axis=axis) + v = xp.var(x, axis=axis, correction=1) + n = x.shape[axis] + return m / (v/n)**0.5 + # return stats.ttest_1samp(x, popmean=0., axis=axis).statistic) + return statistic + + def test_input_validation(self, xp): + # test that the appropriate error messages are raised for invalid input + + data = xp.asarray([1., 2., 3.]) + def stat(x, axis=None): + return xp.mean(x, axis=axis) + + message = "Array shapes are incompatible for broadcasting." + temp = (xp.zeros((2, 5)), xp.zeros((3, 5))) + rvs = (stats.norm.rvs, stats.norm.rvs) + with pytest.raises(ValueError, match=message): + monte_carlo_test(temp, rvs, lambda x, y, axis: 1, axis=-1) + + message = "`axis` must be an integer." + with pytest.raises(ValueError, match=message): + monte_carlo_test(data, stats.norm.rvs, stat, axis=1.5) + + message = "`vectorized` must be `True`, `False`, or `None`." + with pytest.raises(ValueError, match=message): + monte_carlo_test(data, stats.norm.rvs, stat, vectorized=1.5) + + message = "`rvs` must be callable or sequence of callables." + with pytest.raises(TypeError, match=message): + monte_carlo_test(data, None, stat) + with pytest.raises(TypeError, match=message): + temp = xp.asarray([[1., 2.], [3., 4.]]) + monte_carlo_test(temp, [lambda x: x, None], stat) + + message = "If `rvs` is a sequence..." + with pytest.raises(ValueError, match=message): + temp = xp.asarray([[1., 2., 3.]]) + monte_carlo_test(temp, [lambda x: x, lambda x: x], stat) + + message = "`statistic` must be callable." + with pytest.raises(TypeError, match=message): + monte_carlo_test(data, stats.norm.rvs, None) + + message = "`n_resamples` must be a positive integer." + with pytest.raises(ValueError, match=message): + monte_carlo_test(data, stats.norm.rvs, stat, n_resamples=-1000) + + message = "`n_resamples` must be a positive integer." + with pytest.raises(ValueError, match=message): + monte_carlo_test(data, stats.norm.rvs, stat, n_resamples=1000.5) + + message = "`batch` must be a positive integer or None." + with pytest.raises(ValueError, match=message): + monte_carlo_test(data, stats.norm.rvs, stat, batch=-1000) + + message = "`batch` must be a positive integer or None." + with pytest.raises(ValueError, match=message): + monte_carlo_test(data, stats.norm.rvs, stat, batch=1000.5) + + message = "`alternative` must be in..." + with pytest.raises(ValueError, match=message): + monte_carlo_test(data, stats.norm.rvs, stat, alternative='ekki') + + # *If* this raises a value error, make sure it has the intended message + message = "Signature inspection of statistic" + def rvs(size): + return xp.asarray(stats.norm.rvs(size=size)) + try: + monte_carlo_test(data, rvs, xp.mean) + except ValueError as e: + assert str(e).startswith(message) + + def test_input_validation_xp(self, xp): + def non_vectorized_statistic(x): + return xp.mean(x) + + message = "`statistic` must be vectorized..." + sample = xp.asarray([1., 2., 3.]) + if is_numpy(xp): + monte_carlo_test(sample, stats.norm.rvs, non_vectorized_statistic) + return + + with pytest.raises(ValueError, match=message): + monte_carlo_test(sample, stats.norm.rvs, non_vectorized_statistic) + with pytest.raises(ValueError, match=message): + monte_carlo_test(sample, stats.norm.rvs, xp.mean, vectorized=False) + + @pytest.mark.xslow + def test_batch(self, xp): + # make sure that the `batch` parameter is respected by checking the + # maximum batch size provided in calls to `statistic` + rng = np.random.default_rng(23492340193) + x = xp.asarray(rng.standard_normal(size=10)) + + def statistic(x, axis): + batch_size = 1 if x.ndim == 1 else x.shape[0] + statistic.batch_size = max(batch_size, statistic.batch_size) + statistic.counter += 1 + return self.get_statistic(xp)(x, axis=axis) + statistic.counter = 0 + statistic.batch_size = 0 + + kwds = {'sample': x, 'statistic': statistic, + 'n_resamples': 1000, 'vectorized': True} + + kwds['rvs'] = self.get_rvs(stats.norm.rvs, np.random.default_rng(328423), xp=xp) + res1 = monte_carlo_test(batch=1, **kwds) + assert_equal(statistic.counter, 1001) + assert_equal(statistic.batch_size, 1) + + kwds['rvs'] = self.get_rvs(stats.norm.rvs, np.random.default_rng(328423), xp=xp) + statistic.counter = 0 + res2 = monte_carlo_test(batch=50, **kwds) + assert_equal(statistic.counter, 21) + assert_equal(statistic.batch_size, 50) + + kwds['rvs'] = self.get_rvs(stats.norm.rvs, np.random.default_rng(328423), xp=xp) + statistic.counter = 0 + res3 = monte_carlo_test(**kwds) + assert_equal(statistic.counter, 2) + assert_equal(statistic.batch_size, 1000) + + xp_assert_equal(res1.pvalue, res3.pvalue) + xp_assert_equal(res2.pvalue, res3.pvalue) + + @pytest.mark.parametrize('axis', range(-3, 3)) + def test_axis_dtype(self, axis, xp): + # test that Nd-array samples are handled correctly for valid values + # of the `axis` parameter; also make sure non-default dtype is maintained + rng = np.random.default_rng(2389234) + size = [2, 3, 4] + size[axis] = 100 + + # Determine non-default dtype + dtype_default = xp.asarray(1.).dtype + dtype_str = 'float32'if ("64" in str(dtype_default)) else 'float64' + dtype_np = getattr(np, dtype_str) + dtype = getattr(xp, dtype_str) + + # ttest_1samp is CPU array-API compatible, but it would be good to + # include CuPy in this test. We'll perform ttest_1samp with a + # NumPy array, but all the rest with be done with fully array-API + # compatible code. + x = rng.standard_normal(size=size, dtype=dtype_np) + expected = stats.ttest_1samp(x, popmean=0., axis=axis) + + x = xp.asarray(x, dtype=dtype) + statistic = self.get_statistic(xp) + rvs = self.get_rvs(stats.norm.rvs, rng, dtype=dtype, xp=xp) + + res = monte_carlo_test(x, rvs, statistic, vectorized=True, + n_resamples=20000, axis=axis) + + ref_statistic = xp.asarray(expected.statistic, dtype=dtype) + ref_pvalue = xp.asarray(expected.pvalue, dtype=dtype) + xp_assert_close(res.statistic, ref_statistic) + xp_assert_close(res.pvalue, ref_pvalue, atol=self.atol) + + @pytest.mark.parametrize('alternative', ("two-sided", "less", "greater")) + def test_alternative(self, alternative, xp): + # test that `alternative` is working as expected + rng = np.random.default_rng(65723433) + + x = rng.standard_normal(size=30) + ref = stats.ttest_1samp(x, 0., alternative=alternative) + + x = xp.asarray(x) + statistic = self.get_statistic(xp) + rvs = self.get_rvs(stats.norm.rvs, rng, xp=xp) + + res = monte_carlo_test(x, rvs, statistic, alternative=alternative) + + xp_assert_close(res.statistic, xp.asarray(ref.statistic)) + xp_assert_close(res.pvalue, xp.asarray(ref.pvalue), atol=self.atol) + + + # Tests below involve statistics that are not yet array-API compatible. + # They can be converted when the statistics are converted. + @pytest.mark.slow + @pytest.mark.parametrize('alternative', ("less", "greater")) + @pytest.mark.parametrize('a', np.linspace(-0.5, 0.5, 5)) # skewness + def test_against_ks_1samp(self, alternative, a): + # test that monte_carlo_test can reproduce pvalue of ks_1samp + rng = np.random.default_rng(65723433) + + x = stats.skewnorm.rvs(a=a, size=30, random_state=rng) + expected = stats.ks_1samp(x, stats.norm.cdf, alternative=alternative) + + def statistic1d(x): + return stats.ks_1samp(x, stats.norm.cdf, mode='asymp', + alternative=alternative).statistic + + norm_rvs = self.get_rvs(stats.norm.rvs, rng) + res = monte_carlo_test(x, norm_rvs, statistic1d, + n_resamples=1000, vectorized=False, + alternative=alternative) + + assert_allclose(res.statistic, expected.statistic) + if alternative == 'greater': + assert_allclose(res.pvalue, expected.pvalue, atol=self.atol) + elif alternative == 'less': + assert_allclose(1-res.pvalue, expected.pvalue, atol=self.atol) + + @pytest.mark.parametrize('hypotest', (stats.skewtest, stats.kurtosistest)) + @pytest.mark.parametrize('alternative', ("less", "greater", "two-sided")) + @pytest.mark.parametrize('a', np.linspace(-2, 2, 5)) # skewness + def test_against_normality_tests(self, hypotest, alternative, a): + # test that monte_carlo_test can reproduce pvalue of normality tests + rng = np.random.default_rng(85723405) + + x = stats.skewnorm.rvs(a=a, size=150, random_state=rng) + expected = hypotest(x, alternative=alternative) + + def statistic(x, axis): + return hypotest(x, axis=axis).statistic + + norm_rvs = self.get_rvs(stats.norm.rvs, rng) + res = monte_carlo_test(x, norm_rvs, statistic, vectorized=True, + alternative=alternative) + + assert_allclose(res.statistic, expected.statistic) + assert_allclose(res.pvalue, expected.pvalue, atol=self.atol) + + @pytest.mark.parametrize('a', np.arange(-2, 3)) # skewness parameter + def test_against_normaltest(self, a): + # test that monte_carlo_test can reproduce pvalue of normaltest + rng = np.random.default_rng(12340513) + + x = stats.skewnorm.rvs(a=a, size=150, random_state=rng) + expected = stats.normaltest(x) + + def statistic(x, axis): + return stats.normaltest(x, axis=axis).statistic + + norm_rvs = self.get_rvs(stats.norm.rvs, rng) + res = monte_carlo_test(x, norm_rvs, statistic, vectorized=True, + alternative='greater') + + assert_allclose(res.statistic, expected.statistic) + assert_allclose(res.pvalue, expected.pvalue, atol=self.atol) + + @pytest.mark.xslow + @pytest.mark.parametrize('a', np.linspace(-0.5, 0.5, 5)) # skewness + def test_against_cramervonmises(self, a): + # test that monte_carlo_test can reproduce pvalue of cramervonmises + rng = np.random.default_rng(234874135) + + x = stats.skewnorm.rvs(a=a, size=30, random_state=rng) + expected = stats.cramervonmises(x, stats.norm.cdf) + + def statistic1d(x): + return stats.cramervonmises(x, stats.norm.cdf).statistic + + norm_rvs = self.get_rvs(stats.norm.rvs, rng) + res = monte_carlo_test(x, norm_rvs, statistic1d, + n_resamples=1000, vectorized=False, + alternative='greater') + + assert_allclose(res.statistic, expected.statistic) + assert_allclose(res.pvalue, expected.pvalue, atol=self.atol) + + @pytest.mark.slow + @pytest.mark.parametrize('dist_name', ['norm', 'logistic']) + @pytest.mark.parametrize('target_statistic', [0.6, 0.7, 0.8]) + def test_against_anderson(self, dist_name, target_statistic): + # test that monte_carlo_test can reproduce results of `anderson`. + + # find the skewness for which the sample statistic is within the range of + # values tubulated by `anderson` + + def fun(a): + rng = np.random.default_rng(394295467) + x = stats.tukeylambda.rvs(a, size=100, random_state=rng) + expected = stats.anderson(x, dist_name, method='interpolate') + return expected.statistic - target_statistic + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + sol = root(fun, x0=0) + assert sol.success + + # get the significance level (p-value) associated with that critical + # value + a = sol.x[0] + rng = np.random.default_rng(394295467) + x = stats.tukeylambda.rvs(a, size=100, random_state=rng) + expected = stats.anderson(x, dist_name, method='interpolate') + expected_stat = expected.statistic + expected_p = expected.pvalue + + # perform equivalent Monte Carlo test and compare results + def statistic1d(x): + return stats.anderson(x, dist_name, method='interpolate').statistic + + dist_rvs = self.get_rvs(getattr(stats, dist_name).rvs, rng) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + res = monte_carlo_test(x, dist_rvs, + statistic1d, n_resamples=1000, + vectorized=False, alternative='greater') + + assert_allclose(res.statistic, expected_stat) + assert_allclose(res.pvalue, expected_p, atol=2*self.atol) + + def test_p_never_zero(self): + # Use biased estimate of p-value to ensure that p-value is never zero + # per monte_carlo_test reference [1] + rng = np.random.default_rng(2190176673029737545) + x = np.zeros(100) + res = monte_carlo_test(x, rng.random, np.mean, + vectorized=True, alternative='less') + assert res.pvalue == 0.0001 + + def test_against_ttest_ind(self): + # test that `monte_carlo_test` can reproduce results of `ttest_ind`. + rng = np.random.default_rng(219017667302737545) + data = rng.random(size=(2, 5)), rng.random(size=7) # broadcastable + rvs = rng.normal, rng.normal + def statistic(x, y, axis): + return stats.ttest_ind(x, y, axis=axis).statistic + + res = stats.monte_carlo_test(data, rvs, statistic, axis=-1) + ref = stats.ttest_ind(data[0], [data[1]], axis=-1) + assert_allclose(res.statistic, ref.statistic) + assert_allclose(res.pvalue, ref.pvalue, rtol=2e-2) + + def test_against_f_oneway(self): + # test that `monte_carlo_test` can reproduce results of `f_oneway`. + rng = np.random.default_rng(219017667302737545) + data = (rng.random(size=(2, 100)), rng.random(size=(2, 101)), + rng.random(size=(2, 102)), rng.random(size=(2, 103))) + rvs = rng.normal, rng.normal, rng.normal, rng.normal + + def statistic(*args, axis): + return stats.f_oneway(*args, axis=axis).statistic + + res = stats.monte_carlo_test(data, rvs, statistic, axis=-1, + alternative='greater') + ref = stats.f_oneway(*data, axis=-1) + + assert_allclose(res.statistic, ref.statistic) + assert_allclose(res.pvalue, ref.pvalue, atol=1e-2) + + @pytest.mark.fail_slow(2) + @pytest.mark.xfail_on_32bit("Statistic may not depend on sample order on 32-bit") + def test_finite_precision_statistic(self): + # Some statistics return numerically distinct values when the values + # should be equal in theory. Test that `monte_carlo_test` accounts + # for this in some way. + rng = np.random.default_rng(2549824598234528) + n_resamples = 9999 + def rvs(size): + return 1. * stats.bernoulli(p=0.333).rvs(size=size, random_state=rng) + + x = rvs(100) + res = stats.monte_carlo_test(x, rvs, np.var, alternative='less', + n_resamples=n_resamples) + # show that having a tolerance matters + c0 = np.sum(res.null_distribution <= res.statistic) + c1 = np.sum(res.null_distribution <= res.statistic*(1+1e-15)) + assert c0 != c1 + assert res.pvalue == (c1 + 1)/(n_resamples + 1) + + +@make_xp_test_case(power) +class TestPower: + def xp_normal(self, rng, *, xp, dtype=None): + dtype = xp_default_dtype(xp) if dtype is None else dtype + return lambda size: xp.asarray(rng.normal(size=size), dtype=dtype) + + def test_input_validation(self, xp): + # test that the appropriate error messages are raised for invalid input + rng = np.random.default_rng(8519895914314711673) + + test = stats.ttest_ind + rvs = (self.xp_normal(rng, xp=xp), self.xp_normal(rng, xp=xp)) + n_observations = (xp.asarray(10), xp.asarray(12)) + + message = "`vectorized` must be `True`, `False`, or `None`." + with pytest.raises(ValueError, match=message): + power(test, rvs, n_observations, vectorized=1.5) + + message = "`rvs` must be callable or sequence of callables." + with pytest.raises(TypeError, match=message): + power(test, None, n_observations) + with pytest.raises(TypeError, match=message): + power(test, (self.xp_normal(rng, xp=xp), 'ekki'), n_observations) + + message = "If `rvs` is a sequence..." + with pytest.raises(ValueError, match=message): + power(test, (self.xp_normal(rng, xp=xp),), n_observations) + with pytest.raises(ValueError, match=message): + power(test, rvs, (10,)) + + message = "`significance` must be of floating point dtype." + with pytest.raises(ValueError, match=message): + power(test, rvs, n_observations, significance=xp.asarray(True)) + + significance = xp.asarray(2.) + if not is_lazy_array(significance): + message = "All elements of `significance` must be between 0. and 1." + with pytest.raises(ValueError, match=message): + power(test, rvs, n_observations, significance=significance) + + message = "`kwargs` must be a dictionary" + with pytest.raises(TypeError, match=message): + power(test, rvs, n_observations, kwargs=(1, 2, 3)) + + message = ( + "not be broadcast" + "|Chunks do not add" + "|Incompatible shapes" + "|Attempting to broadcast" + ) + with pytest.raises((ValueError, RuntimeError), match=message): + power(test, rvs, (xp.asarray([10, 11]), xp.asarray([12, 13, 14]))) + with pytest.raises((ValueError, RuntimeError), match=message): + power(test, rvs, (xp.asarray([10, 11]), xp.asarray([12, 13])), + kwargs={'x': xp.asarray([1, 2, 3])}) + + message = "`test` must be callable" + with pytest.raises(TypeError, match=message): + power(None, rvs, n_observations) + + message = "`n_resamples` must be a positive integer" + with pytest.raises(ValueError, match=message): + power(test, rvs, n_observations, n_resamples=-10) + with pytest.raises(ValueError, match=message): + power(test, rvs, n_observations, n_resamples=10.5) + + message = "`batch` must be a positive integer" + with pytest.raises(ValueError, match=message): + power(test, rvs, n_observations, batch=-10) + with pytest.raises(ValueError, match=message): + power(test, rvs, n_observations, batch=10.5) + + @pytest.mark.slow + def test_batch(self, xp): + # make sure that the `batch` parameter is respected by checking the + # maximum batch size provided in calls to `test` + rng = np.random.default_rng(23492340193) + + def test(x, axis): + batch_size = 1 if x.ndim == 1 else x.shape[0] + test.batch_size = max(batch_size, test.batch_size) + test.counter += 1 + return stats.ttest_1samp(x, xp.asarray(0.), axis=axis).pvalue + test.counter = 0 + test.batch_size = 0 + + kwds = dict(test=test, + n_observations=xp.asarray(10), + n_resamples=1000) + + rng = np.random.default_rng(23492340193) + res1 = power(**kwds, rvs=self.xp_normal(rng, xp=xp), batch=1) + assert test.counter == 1000 + assert test.batch_size == 1 + + rng = np.random.default_rng(23492340193) + test.counter = 0 + res2 = power(**kwds, rvs=self.xp_normal(rng, xp=xp), batch=50) + assert test.counter == 20 + assert test.batch_size == 50 + + rng = np.random.default_rng(23492340193) + test.counter = 0 + res3 = power(**kwds, rvs=self.xp_normal(rng, xp=xp), batch=1000) + assert test.counter == 1 + assert test.batch_size == 1000 + + xp_assert_equal(res1.power, res3.power) + xp_assert_equal(res2.power, res3.power) + + @pytest.mark.slow + def test_vectorization(self, xp): + # Test that `power` is vectorized as expected + rng = np.random.default_rng(25495254834552) + alternatives = {-1: 'less', 0:'two-sided', 1: 'greater'} + + # Single vectorized call + popmeans = xp.asarray([0, 0.2]) + def test(x, alternative, axis=-1): + # ensure that popmeans axis is zeroth and orthogonal to the rest + popmeans_expanded = xpx.expand_dims(popmeans, + axis=tuple(range(1, x.ndim + 1))) + alternative = alternatives[int(alternative)] + return stats.ttest_1samp(x, popmeans_expanded, alternative=alternative, + axis=axis) + + # nx and kwargs broadcast against one another + nx = xp.asarray([10, 15, 20, 50, 100])[:, xp.newaxis] + kwargs = {'alternative': xp.asarray([-1, 0, 1])} + + # This dimension is added to the beginning + significance = xp.asarray([0.01, 0.025, 0.05, 0.1]) + res = stats.power(test, self.xp_normal(rng, xp=xp), nx, + significance=significance, kwargs=kwargs) + + # Looping over all combinations + ref = [] + for significance_i in significance: + for i in range(nx.shape[0]): + nx_i = nx[i, ...] + for alternative_i in kwargs['alternative']: + for j in range(popmeans.shape[0]): + popmean_j = popmeans[j] + def test2(x, axis=-1): + return stats.ttest_1samp(x, popmean_j, axis=axis, + alternative=alternatives[int(alternative_i)]) + + tmp = stats.power(test2, self.xp_normal(rng, xp=xp), nx_i, + significance=significance_i) + ref.append(tmp.power) + ref = xp.reshape(xp.stack(ref), res.power.shape) + + # Show that results are similar + xp_assert_close(res.power, ref, rtol=2e-2, atol=1e-2) + + @pytest.mark.skip_xp_backends(cpu_only=True, + exceptions=['cupy', 'jax.numpy', 'dask.array']) + def test_ttest_ind_null(self, xp): + # Check that the p-values of `ttest_ind` are uniformly distributed under + # the null hypothesis + rng = np.random.default_rng(254952548345528) + + test = stats.ttest_ind + n_observations = (xp.asarray(rng.integers(10, 100, size=(10))), + xp.asarray(rng.integers(10, 100, size=(10)))) + rvs = self.xp_normal(rng, xp=xp), self.xp_normal(rng, xp=xp) + significance = xp.asarray([0.01, 0.05, 0.1]) + res = stats.power(test, rvs, n_observations, significance=significance) + significance = xp.broadcast_to(significance[:, xp.newaxis], res.power.shape) + xp_assert_close(res.power, significance, atol=1e-2) + + @pytest.mark.skip_xp_backends('array_api_strict', + reason='currently combines integer and float arrays') + @pytest.mark.skip_xp_backends(cpu_only=True, + exceptions=['cupy', 'jax.numpy', 'dask.array']) + def test_ttest_1samp_power(self, xp): + # Check simulated ttest_1samp power against reference + rng = np.random.default_rng(254952548345528) + + # Reference values computed with statmodels + # import numpy as np + # from statsmodels.stats.power import tt_solve_power + # tt_solve_power = np.vectorize(tt_solve_power) + # tt_solve_power([0.1, 0.5, 0.9], [[10], [20]], [[[0.01]], [[0.05]]]) + ref = [[[0.0126515 , 0.10269751, 0.40415802], + [0.01657775, 0.29734608, 0.86228288]], + [[0.0592903 , 0.29317561, 0.71718121], + [0.07094116, 0.56450441, 0.96815163]]] + + kwargs = {'popmean': xp.asarray([0.1, 0.5, 0.9])} + n_observations = xp.asarray([[10], [20]]) + significance = xp.asarray([0.01, 0.05]) + res = stats.power(stats.ttest_1samp, self.xp_normal(rng, xp=xp), n_observations, + significance=significance, kwargs=kwargs) + xp_assert_close(res.power, xp.asarray(ref), atol=1e-2) + + +@make_xp_test_case(permutation_test) +class TestPermutationTest: + + rtol = 1e-14 + + def setup_method(self): + self.rng = np.random.default_rng(7170559330470561044) + + # -- Input validation -- # + def test_permutation_test_iv(self, xp): + + def stat(x, y, axis): + return stats.ttest_ind((x, y), axis).statistic + + data = (xp.asarray([1, 2, 3]), xp.asarray([1, 2, 3])) + + message = "each sample in `data` must contain two or more ..." + with pytest.raises(ValueError, match=message): + permutation_test((data[0], xp.asarray([0])), stat) + + message = "`data` must be a tuple containing at least two samples" + with pytest.raises(ValueError, match=message): + permutation_test((1,), stat) + with pytest.raises(TypeError, match=message): + permutation_test(1, stat) + + message = "`axis` must be an integer." + with pytest.raises(ValueError, match=message): + permutation_test(data, stat, axis=1.5) + + message = "`permutation_type` must be in..." + with pytest.raises(ValueError, match=message): + permutation_test(data, stat, + permutation_type="ekki") + + message = "`vectorized` must be `True`, `False`, or `None`." + with pytest.raises(ValueError, match=message): + permutation_test(data, stat, vectorized=1.5) + + message = "`n_resamples` must be a positive integer." + with pytest.raises(ValueError, match=message): + permutation_test(data, stat, n_resamples=-1000) + + message = "`n_resamples` must be a positive integer." + with pytest.raises(ValueError, match=message): + permutation_test(data, stat, n_resamples=1000.5) + + message = "`batch` must be a positive integer or None." + with pytest.raises(ValueError, match=message): + permutation_test(data, stat, batch=-1000) + + message = "`batch` must be a positive integer or None." + with pytest.raises(ValueError, match=message): + permutation_test(data, stat, batch=1000.5) + + message = "`alternative` must be in..." + with pytest.raises(ValueError, match=message): + permutation_test(data, stat, alternative='ekki') + + message = "SeedSequence expects int or sequence of ints" + with pytest.raises(TypeError, match=message): + permutation_test(data, stat, rng='herring') + + # -- Test Parameters -- # + # SPEC-007 leave one call with seed to check it still works + @pytest.mark.parametrize('random_state', [np.random.RandomState, + np.random.default_rng]) + @pytest.mark.parametrize('permutation_type', + ['pairings', 'samples', 'independent']) + def test_batch(self, permutation_type, random_state, xp): + # make sure that the `batch` parameter is respected by checking the + # maximum batch size provided in calls to `statistic` + x = xp.asarray(self.rng.random(10)) + y = xp.asarray(self.rng.random(10)) + + def statistic(x, y, axis): + batch_size = 1 if x.ndim == 1 else x.shape[0] + statistic.batch_size = max(batch_size, statistic.batch_size) + statistic.counter += 1 + return xp.mean(x, axis=axis) - xp.mean(y, axis=axis) + statistic.counter = 0 + statistic.batch_size = 0 + + kwds = {'n_resamples': 100, 'permutation_type': permutation_type, + 'vectorized': True} + res1 = stats.permutation_test((x, y), statistic, batch=1, + random_state=random_state(0), **kwds) + assert statistic.counter == 101 + assert statistic.batch_size == 1 + + statistic.counter = 0 + res2 = stats.permutation_test((x, y), statistic, batch=50, + random_state=random_state(0), **kwds) + assert statistic.counter == 3 + assert statistic.batch_size == 50 + + statistic.counter = 0 + res3 = stats.permutation_test((x, y), statistic, batch=100, + random_state=random_state(0), **kwds) + assert statistic.counter == 2 + assert statistic.batch_size == 100 + + xp_assert_equal(res1.pvalue, res3.pvalue) + xp_assert_equal(res2.pvalue, res3.pvalue) + + # SPEC-007 leave at least one call with seed to check it still works + @pytest.mark.parametrize('random_state', [np.random.RandomState, + np.random.default_rng]) + @pytest.mark.parametrize('permutation_type, exact_size', + [('pairings', special.factorial(3)**2), + ('samples', 2**3), + ('independent', special.binom(6, 3))]) + def test_permutations(self, permutation_type, exact_size, random_state, xp): + # make sure that the `permutations` parameter is respected by checking + # the size of the null distribution + x = xp.asarray(self.rng.random(3)) + y = xp.asarray(self.rng.random(3)) + + def statistic(x, y, axis): + return xp.mean(x, axis=axis) - xp.mean(y, axis=axis) + + kwds = {'permutation_type': permutation_type, + 'vectorized': True} + res = stats.permutation_test((x, y), statistic, n_resamples=3, + random_state=random_state(0), **kwds) + assert xp_size(res.null_distribution) == 3 + + res = stats.permutation_test((x, y), statistic, **kwds) + assert xp_size(res.null_distribution) == exact_size + + # -- Randomized Permutation Tests -- # + + # To get reasonable accuracy, these next three tests are somewhat slow. + # Originally, I had them passing for all combinations of permutation type, + # alternative, and RNG, but that takes too long for CI. Instead, split + # into three tests, each testing a particular combination of the three + # parameters. + + def test_randomized_test_against_exact_both(self, xp): + # check that the randomized and exact tests agree to reasonable + # precision for permutation_type='both + + alternative, rng = 'less', 0 + + nx, ny, permutations = 8, 9, 24000 + assert special.binom(nx + ny, nx) > permutations + + rng = np.random.default_rng(8235259808) + x = xp.asarray(rng.standard_normal(size=nx)) + y = xp.asarray(rng.standard_normal(size=ny)) + data = x, y + + def statistic(x, y, axis): + return xp.mean(x, axis=axis) - xp.mean(y, axis=axis) + + kwds = {'vectorized': True, 'permutation_type': 'independent', + 'batch': 100, 'alternative': alternative, 'rng': rng} + res = permutation_test(data, statistic, n_resamples=permutations, **kwds) + res2 = permutation_test(data, statistic, n_resamples=xp.inf, **kwds) + + assert res.statistic == res2.statistic + xp_assert_close(res.pvalue, res2.pvalue, atol=1e-2) + + @pytest.mark.slow() + def test_randomized_test_against_exact_samples(self, xp): + # check that the randomized and exact tests agree to reasonable + # precision for permutation_type='samples' + + alternative, rng = 'greater', None + + nx, ny, permutations = 15, 15, 32000 + assert 2**nx > permutations + + rng = np.random.default_rng(8235259808) + x = xp.asarray(rng.standard_normal(size=nx)) + y = xp.asarray(rng.standard_normal(size=ny)) + data = x, y + + def statistic(x, y, axis): + return xp.mean(x - y, axis=axis) + + kwds = {'vectorized': True, 'permutation_type': 'samples', + 'batch': 100, 'alternative': alternative, 'rng': rng} + res = permutation_test(data, statistic, n_resamples=permutations, **kwds) + res2 = permutation_test(data, statistic, n_resamples=xp.inf, **kwds) + + assert res.statistic == res2.statistic + xp_assert_close(res.pvalue, res2.pvalue, atol=1e-2) + + # I only need to skip torch on GPU because it doesn't have betaincc for pearsonr + @pytest.mark.skip_xp_backends(cpu_only=True, exceptions=['cupy', 'jax.numpy']) + def test_randomized_test_against_exact_pairings(self, xp): + # check that the randomized and exact tests agree to reasonable + # precision for permutation_type='pairings' + + alternative, rng = 'two-sided', self.rng + + nx, ny, permutations = 8, 8, 40000 + assert special.factorial(nx) > permutations + + rng = np.random.default_rng(8235259808) + x = xp.asarray(rng.standard_normal(size=nx)) + y = xp.asarray(rng.standard_normal(size=ny)) + data = [x] + + def statistic(x, axis): + return stats.pearsonr(x, y, axis=axis).statistic + + kwds = {'vectorized': True, 'permutation_type': 'samples', + 'batch': 100, 'alternative': alternative, 'rng': rng} + res = permutation_test(data, statistic, n_resamples=permutations, **kwds) + res2 = permutation_test(data, statistic, n_resamples=xp.inf, **kwds) + + assert res.statistic == res2.statistic + xp_assert_close(res.pvalue, res2.pvalue, atol=1e-2) + + # -- Independent (Unpaired) Sample Tests -- # + + @pytest.mark.skip_xp_backends(eager_only=True) # TODO: change to jax_jit=False + @pytest.mark.parametrize('alternative', ("less", "greater", "two-sided")) + def test_against_ks_2samp(self, alternative, xp): + x = self.rng.normal(size=4, scale=1) + y = self.rng.normal(size=5, loc=3, scale=3) + + expected = stats.ks_2samp(x, y, alternative=alternative, mode='exact') + + def statistic(x, y, axis): + # todo: use `xp` as backend when `ks_2samp` supports more backends + x, y = _xp_copy_to_numpy(x), _xp_copy_to_numpy(y) + res = stats.ks_2samp(x, y, axis=axis, mode='asymp', alternative=alternative) + res = xp.asarray(res.statistic) + return res[()] if res.ndim == 0 else res + + # ks_2samp is always a one-tailed 'greater' test + # it's the statistic that changes (D+ vs D- vs max(D+, D-)) + x, y = xp.asarray(x), xp.asarray(y) + res = permutation_test((x, y), statistic, n_resamples=np.inf, + alternative='greater', rng=self.rng) + + xp_assert_close(res.statistic, xp.asarray(expected.statistic), rtol=self.rtol) + xp_assert_close(res.pvalue, xp.asarray(expected.pvalue), rtol=self.rtol) + + @pytest.mark.skip_xp_backends(eager_only=True) # TODO: change to jax_jit=False + @pytest.mark.parametrize('alternative', ("less", "greater", "two-sided")) + def test_against_ansari(self, alternative, xp): + x = self.rng.normal(size=4, scale=1) + y = self.rng.normal(size=5, scale=3) + + # ansari has a different convention for 'alternative' + alternative_correspondence = {"less": "greater", + "greater": "less", + "two-sided": "two-sided"} + alternative_scipy = alternative_correspondence[alternative] + expected = stats.ansari(x, y, alternative=alternative_scipy) + + def statistic(x, y, axis): + # todo: use `xp` as backend when `ansari` is translated to array API + x, y = _xp_copy_to_numpy(x), _xp_copy_to_numpy(y) + res = stats.ansari(x, y, axis=axis) + res = xp.asarray(res.statistic) + return res[()] if res.ndim == 0 else res + + x, y = xp.asarray(x), xp.asarray(y) + res = permutation_test((x, y), statistic, n_resamples=np.inf, + alternative=alternative, rng=self.rng) + + xp_assert_close(res.statistic, xp.asarray(expected.statistic), rtol=self.rtol) + xp_assert_close(res.pvalue, xp.asarray(expected.pvalue), rtol=self.rtol) + + @skip_xp_backends('cupy', reason='needs mannwhitneyu') + @skip_xp_backends(eager_only=True) # mannwhitneyu does input validation + @pytest.mark.parametrize('alternative', ("less", "greater", "two-sided")) + def test_against_mannwhitneyu(self, alternative, xp): + x = stats.uniform.rvs(size=(3, 5, 2), loc=0, random_state=self.rng) + y = stats.uniform.rvs(size=(3, 5, 2), loc=0.05, random_state=self.rng) + + expected = stats.mannwhitneyu(x, y, axis=1, alternative=alternative) + + def statistic(x, y, axis): + return stats.mannwhitneyu(x, y, axis=axis, method='asymptotic').statistic + + x, y = xp.asarray(x), xp.asarray(y) + res = permutation_test((x, y), statistic, vectorized=True, + n_resamples=xp.inf, alternative=alternative, + axis=1, rng=self.rng) + + xp_assert_close(res.statistic, xp.asarray(expected.statistic), rtol=self.rtol) + xp_assert_close(res.pvalue, xp.asarray(expected.pvalue), rtol=self.rtol) + + @skip_xp_backends('cupy', reason='needs cramervonmises_2samp') + @skip_xp_backends(eager_only=True) # cramervonmises_2samp does input validation + @skip_xp_backends(cpu_only=True) # torch doesn't have `kv` + def test_against_cvm(self, xp): + x = stats.norm.rvs(size=4, scale=1, random_state=self.rng) + y = stats.norm.rvs(size=5, loc=3, scale=3, random_state=self.rng) + + expected = stats.cramervonmises_2samp(x, y, method='exact') + + def statistic(x, y, axis): + res = stats.cramervonmises_2samp(x, y, axis=axis, method='asymptotic') + return res.statistic + + # cramervonmises_2samp has only one alternative, greater + x, y = xp.asarray(x), xp.asarray(y) + res = permutation_test((x, y), statistic, n_resamples=np.inf, + alternative='greater', rng=self.rng) + + xp_assert_close(res.statistic, xp.asarray(expected.statistic), rtol=self.rtol) + xp_assert_close(res.pvalue, xp.asarray(expected.pvalue), rtol=self.rtol) + + @skip_xp_backends(eager_only=True) # kruskal does input validation + @pytest.mark.parametrize('axis', (-1, 2)) + def test_vectorized_nsamp_ptype_both(self, axis, xp): + # statistic only available for NumPy + + # Test that permutation_test with permutation_type='independent' works + # properly for a 3-sample statistic with nd array samples of different + # (but compatible) shapes and ndims. Show that exact permutation test + # and random permutation tests approximate SciPy's asymptotic pvalues + # and that exact and random permutation test results are even closer + # to one another (than they are to the asymptotic results). + + # Three samples, different (but compatible) shapes with different ndims + rng = np.random.default_rng(6709265303529651545) + x = rng.random(size=(3)) + y = rng.random(size=(1, 3, 2)) + z = rng.random(size=(2, 1, 4)) + data = (x, y, z) + + expected = stats.kruskal(*data, axis=axis) + + # Define the statistic (and pvalue for comparison) + def statistic(*data, axis): + return stats.kruskal(*data, axis=axis).statistic + + # Calculate exact and randomized permutation results + kwds = {'axis': axis, 'alternative': 'greater', + 'permutation_type': 'independent', 'rng': rng} + data = [xp.asarray(data_) for data_ in data] + res = permutation_test(data, statistic, n_resamples=xp.inf, **kwds) + res2 = permutation_test(data, statistic, n_resamples=1000, **kwds) + + # Check results + xp_assert_close(res.statistic, xp.asarray(expected.statistic), rtol=self.rtol*5) + xp_assert_close(res.statistic, res2.statistic, rtol=self.rtol*5) + xp_assert_close(res.pvalue, xp.asarray(expected.pvalue), atol=6e-2) + xp_assert_close(res.pvalue, res2.pvalue, atol=3e-2) + + # -- Paired-Sample Tests -- # + + @pytest.mark.skip_xp_backends(eager_only=True) # TODO: change to jax_jit=False + @pytest.mark.parametrize('alternative', ("less", "greater", "two-sided")) + def test_against_wilcoxon(self, alternative, xp): + x = stats.uniform.rvs(size=(3, 6, 2), loc=0, random_state=self.rng) + y = stats.uniform.rvs(size=(3, 6, 2), loc=0.05, random_state=self.rng) + expected = stats.wilcoxon(x, y, alternative=alternative, axis=1) + + # We'll check both 1- and 2-sample versions of the same test; + # we expect identical results to wilcoxon in all cases. + def statistic_1samp_1d(z, axis): + # todo: use `xp` as backend when `wilcoxon` is translated to array API + # 'less' ensures we get the same of two statistics every time + z = _xp_copy_to_numpy(z) + res = stats.wilcoxon(z, alternative='less', axis=axis) + res = xp.asarray(res.statistic) + return res[()] if res.ndim == 0 else res + + def statistic_2samp_1d(x, y, axis): + # todo: use `xp` as backend when `wilcoxon` is translated to array API + x, y = _xp_copy_to_numpy(x), _xp_copy_to_numpy(y) + res = stats.wilcoxon(x, y, alternative='less', axis=axis) + res = xp.asarray(res.statistic) + return res[()] if res.ndim == 0 else res + + x, y = xp.asarray(x), xp.asarray(y) + kwds = {'axis': 1, 'alternative': alternative, 'permutation_type': 'samples', + 'rng': self.rng, 'n_resamples': np.inf} + res1 = permutation_test((x-y,), statistic_1samp_1d, **kwds) + res2 = permutation_test((x, y), statistic_2samp_1d, **kwds) + + # `wilcoxon` returns a different statistic with 'two-sided' + xp_assert_close(res1.statistic, res2.statistic, rtol=self.rtol) + if alternative != 'two-sided': + xp_assert_close(res2.statistic, xp.asarray(expected.statistic), + rtol=self.rtol) + + xp_assert_close(res2.pvalue, xp.asarray(expected.pvalue), rtol=self.rtol) + xp_assert_close(res1.pvalue, res2.pvalue, rtol=self.rtol) + + @pytest.mark.parametrize('alternative', ("less", "greater", "two-sided")) + def test_against_binomtest(self, alternative, xp): + + x = self.rng.integers(0, 2, size=10) + x[x == 0] = -1 + # More naturally, the test would flip elements between 0 and one. + # However, permutation_test will flip the _signs_ of the elements. + # So we have to work with +1/-1 instead of 1/0. + + def statistic(x, axis=0): + xp_ = array_namespace(x) + return xp_.count_nonzero(x > 0, axis=axis) + + k, n, p = statistic(x), 10, 0.5 + expected = stats.binomtest(k, n, p, alternative=alternative) + + res = stats.permutation_test((xp.asarray(x, dtype=xp.float64),), statistic, + vectorized=True, + permutation_type='samples', n_resamples=xp.inf, + rng=self.rng, alternative=alternative) + xp_assert_close(res.pvalue, xp.asarray(expected.pvalue), rtol=self.rtol) + + # -- Exact Association Tests -- # + + @pytest.mark.skip_xp_backends(eager_only=True) # TODO: change to jax_jit=False + def test_against_kendalltau(self, xp): + x = self.rng.normal(size=6) + y = x + self.rng.normal(size=6) + + expected = stats.kendalltau(x, y, method='exact') + + def statistic(x, axis): + # todo: use `xp` as backend when `kendalltau` is translated to array API + x = _xp_copy_to_numpy(x) + res = stats.kendalltau(x, y, method='asymptotic', axis=axis) + res = xp.asarray(res.statistic) + return res[()] if res.ndim == 0 else res + + # kendalltau currently has only one alternative, two-sided + x = xp.asarray(x) + res = permutation_test((x,), statistic, permutation_type='pairings', + n_resamples=np.inf, rng=self.rng) + + xp_assert_close(res.statistic, xp.asarray(expected.statistic), rtol=self.rtol) + xp_assert_close(res.pvalue, xp.asarray(expected.pvalue), rtol=self.rtol) + + @pytest.mark.parametrize('alternative', ('less', 'greater', 'two-sided')) + def test_against_fisher_exact(self, alternative, xp): + # x and y are binary random variables with some dependence + rng = np.random.default_rng(6235696159000529929) + x = (rng.random(7) > 0.6).astype(float) + y = (rng.random(7) + 0.25*x > 0.6).astype(float) + tab = stats.contingency.crosstab(x, y)[1] + + x, y = xp.asarray(x), xp.asarray(y) + def statistic(x, axis): + return xp.count_nonzero((x == 1) & (y == 1), axis=axis) + + res = permutation_test((x,), statistic, permutation_type='pairings', + n_resamples=xp.inf, alternative=alternative, + rng=rng) + res2 = stats.fisher_exact(tab, alternative=alternative) + + xp_assert_close(res.pvalue, xp.asarray(res2.pvalue, dtype=x.dtype)) + + @pytest.mark.xslow() + @pytest.mark.parametrize('axis', (-2, 1)) + def test_vectorized_nsamp_ptype_samples(self, axis): + # statistic only available for NumPy, and it's a pain to vectorize + + # Test that permutation_test with permutation_type='samples' works + # properly for a 3-sample statistic with nd array samples of different + # (but compatible) shapes and ndims. Show that exact permutation test + # reproduces SciPy's exact pvalue and that random permutation test + # approximates it. + + x = self.rng.random(size=(2, 4, 3)) + y = self.rng.random(size=(1, 4, 3)) + z = self.rng.random(size=(2, 4, 1)) + x = stats.rankdata(x, axis=axis) + y = stats.rankdata(y, axis=axis) + z = stats.rankdata(z, axis=axis) + y = y[0] # to check broadcast with different ndim + data = (x, y, z) + + def statistic1d(*data): + return stats.page_trend_test(data, ranked=True, + method='asymptotic').statistic + + def pvalue1d(*data): + return stats.page_trend_test(data, ranked=True, + method='exact').pvalue + + statistic = _resampling._vectorize_statistic(statistic1d) + pvalue = _resampling._vectorize_statistic(pvalue1d) + + expected_statistic = statistic(*np.broadcast_arrays(*data), axis=axis) + expected_pvalue = pvalue(*np.broadcast_arrays(*data), axis=axis) + + # Let's forgive this use of an integer seed, please. + kwds = {'vectorized': False, 'axis': axis, 'alternative': 'greater', + 'permutation_type': 'pairings', 'rng': 0} + res = permutation_test(data, statistic1d, n_resamples=np.inf, **kwds) + res2 = permutation_test(data, statistic1d, n_resamples=5000, **kwds) + + assert_allclose(res.statistic, expected_statistic, rtol=self.rtol) + assert_allclose(res.statistic, res2.statistic, rtol=self.rtol) + assert_allclose(res.pvalue, expected_pvalue, rtol=self.rtol) + assert_allclose(res.pvalue, res2.pvalue, atol=3e-2) + + # -- Test Against External References -- # + + tie_case_1 = {'x': [1, 2, 3, 4], 'y': [1.5, 2, 2.5], + 'expected_less': 0.2000000000, + 'expected_2sided': 0.4, # 2*expected_less + 'expected_Pr_gte_S_mean': 0.3428571429, # see note below + 'expected_statistic': 7.5, + 'expected_avg': 9.142857, 'expected_std': 1.40698} + tie_case_2 = {'x': [111, 107, 100, 99, 102, 106, 109, 108], + 'y': [107, 108, 106, 98, 105, 103, 110, 105, 104], + 'expected_less': 0.1555738379, + 'expected_2sided': 0.3111476758, + 'expected_Pr_gte_S_mean': 0.2969971205, # see note below + 'expected_statistic': 32.5, + 'expected_avg': 38.117647, 'expected_std': 5.172124} + + @pytest.mark.skip_xp_backends(eager_only=True) # TODO: change to jax_jit=False + @pytest.mark.xslow() # only the second case is slow, really + @pytest.mark.parametrize('case', (tie_case_1, tie_case_2)) + def test_with_ties(self, case, xp): + """ + Results above from SAS PROC NPAR1WAY, e.g. + + DATA myData; + INPUT X Y; + CARDS; + 1 1 + 1 2 + 1 3 + 1 4 + 2 1.5 + 2 2 + 2 2.5 + ods graphics on; + proc npar1way AB data=myData; + class X; + EXACT; + run; + ods graphics off; + + Note: SAS provides Pr >= |S-Mean|, which is different from our + definition of a two-sided p-value. + + """ + + x = case['x'] + y = case['y'] + + expected_statistic = xp.asarray(case['expected_statistic']) + expected_less = xp.asarray(case['expected_less']) + expected_2sided = xp.asarray(case['expected_2sided']) + expected_Pr_gte_S_mean = xp.asarray(case['expected_Pr_gte_S_mean']) + expected_avg = xp.asarray(case['expected_avg']) + expected_std = xp.asarray(case['expected_std']) + + def statistic(x, y, axis): + # todo: use `xp` as backend when `ansari` is translated to array API + x, y = _xp_copy_to_numpy(x), _xp_copy_to_numpy(y) + res = stats.ansari(x, y, axis=axis) + res = xp.asarray(res.statistic) + return res[()] if res.ndim == 0 else res + + dtype = xp_default_dtype(xp) + x, y = xp.asarray(x, dtype=dtype), xp.asarray(y, dtype=dtype) + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", "Ties preclude use of exact statistic", UserWarning) + res = permutation_test((x, y), statistic, n_resamples=np.inf, + alternative='less') + res2 = permutation_test((x, y), statistic, n_resamples=np.inf, + alternative='two-sided') + + xp_assert_close(res.statistic, expected_statistic, rtol=self.rtol) + xp_assert_close(res.pvalue, expected_less, atol=1e-10) + xp_assert_close(res2.pvalue, expected_2sided, atol=1e-10) + xp_assert_close(xp.mean(res2.null_distribution), expected_avg, rtol=1e-6) + xp_assert_close(xp.std(res2.null_distribution), expected_std, rtol=1e-6) + + # SAS provides Pr >= |S-Mean|; might as well check against that, too + S = res.statistic + mean = xp.mean(res.null_distribution) + n = res.null_distribution.shape[0] + Pr_gte_S_mean = xp.astype(xp.count_nonzero( + xp.abs(res.null_distribution-mean) >= xp.abs(S-mean)), S.dtype) / n + xp_assert_close(Pr_gte_S_mean, expected_Pr_gte_S_mean) + + @pytest.mark.slow + @pytest.mark.parametrize('alternative, expected_pvalue', + (('less', 0.9708333333333), + ('greater', 0.05138888888889), + ('two-sided', 0.1027777777778))) + # I only need to skip torch on GPU because it doesn't have betaincc for pearsonr + @pytest.mark.skip_xp_backends(cpu_only=True, exceptions=['cupy', 'jax.numpy']) + @pytest.mark.skip_xp_backends(eager_only=True) # TODO: change to jax_jit=False + def test_against_spearmanr_in_R(self, alternative, expected_pvalue, xp): + """ + Results above from R cor.test, e.g. + + options(digits=16) + x <- c(1.76405235, 0.40015721, 0.97873798, + 2.2408932, 1.86755799, -0.97727788) + y <- c(2.71414076, 0.2488, 0.87551913, + 2.6514917, 2.01160156, 0.47699563) + cor.test(x, y, method = "spearm", alternative = "t") + """ + # data comes from + # np.random.seed(0) + # x = stats.norm.rvs(size=6) + # y = x + stats.norm.rvs(size=6) + x = xp.asarray([1.76405235, 0.40015721, 0.97873798, + 2.2408932, 1.86755799, -0.97727788]) + y = xp.asarray([2.71414076, 0.2488, 0.87551913, + 2.6514917, 2.01160156, 0.47699563]) + expected_statistic = 0.7714285714285715 + + y = xp.asarray(stats.rankdata(_xp_copy_to_numpy(y))) + def statistic(x, axis): + # `spearmanr` is not array api compatible, but `pearsonr` is. So for now + # use _xp_copy_to_numpy just for ranking so we can run this test w/ CuPy. + # TODO: use `xp` as backend when cupy works with `rankdata` + x = xp.asarray(stats.rankdata(_xp_copy_to_numpy(x), axis=axis)) + return stats.pearsonr(x, y, axis=axis).statistic + + res = permutation_test((x,), statistic, permutation_type='pairings', + n_resamples=xp.inf, alternative=alternative) + + xp_assert_close(res.statistic, xp.asarray(expected_statistic), rtol=self.rtol) + xp_assert_close(res.pvalue, xp.asarray(expected_pvalue), atol=1e-13) + + @pytest.mark.parametrize("batch", (-1, 0)) + def test_batch_generator_iv(self, batch): + with pytest.raises(ValueError, match="`batch` must be positive."): + list(_resampling._batch_generator([1, 2, 3], batch)) + + batch_generator_cases = [(range(0), 3, []), + (range(6), 3, [[0, 1, 2], [3, 4, 5]]), + (range(8), 3, [[0, 1, 2], [3, 4, 5], [6, 7]])] + + @pytest.mark.parametrize("iterable, batch, expected", + batch_generator_cases) + def test_batch_generator(self, iterable, batch, expected): + got = list(_resampling._batch_generator(iterable, batch)) + assert got == expected + + @pytest.mark.fail_slow(2) + # I only need to skip torch on GPU because it doesn't have betaincc for pearsonr + @pytest.mark.skip_xp_backends(cpu_only=True, exceptions=['cupy', 'jax.numpy']) + def test_finite_precision_statistic(self, xp): + # Some statistics return numerically distinct values when the values + # should be equal in theory. Test that `permutation_test` accounts + # for this in some way. + x = xp.asarray([1., 2., 4., 3.], dtype=xp.float64) + y = xp.asarray([2., 4., 6., 8.], dtype=xp.float64) + + def statistic(x, y, axis): + return stats.pearsonr(x, y, axis=axis)[0] + + res = stats.permutation_test((x, y), statistic, + permutation_type='pairings') + r, pvalue, null = res.statistic, res.pvalue, res.null_distribution + + correct_p = 2 * float(xp.count_nonzero(null >= r - 1e-14)) / null.shape[0] + assert pvalue == correct_p == 1/3 + # Compare against other exact correlation tests using R corr.test + # options(digits=16) + # x = c(1, 2, 4, 3) + # y = c(2, 4, 6, 8) + # cor.test(x, y, alternative = "t", method = "spearman") # 0.333333333 + # cor.test(x, y, alternative = "t", method = "kendall") # 0.333333333 + + def test_nan_statistic(self, xp): + # Where the observed statistic is NaN, the p-value should be NaN + # (Whether a single NaN in the permutation distribution should make the p-value + # NaN is debatable. Users can choose for themselves.) + rng = np.random.default_rng(8951482112) + dtype = xp_default_dtype(xp) + x = xp.asarray(rng.random(5), dtype=dtype) + x_nan = xp.asarray([0, 0, 0, xp.nan, 0]) + y = xp.asarray(rng.random(6), dtype=dtype) + + def statistic(x, y, axis): + return xp.mean(x, axis=axis) - xp.mean(y, axis=axis) + + kwds = {'permutation_type': 'independent', 'rng': rng, 'axis': -1} + + ref = permutation_test((x, y), statistic, **kwds) + + data = (xp.stack([x, x + x_nan]), y) + res = permutation_test(data, statistic, **kwds) + + NaN = xp.asarray(xp.nan, dtype=dtype) + xp_assert_equal(res.statistic[0], ref.statistic) + xp_assert_equal(res.pvalue[0], ref.pvalue) + xp_assert_equal(res.statistic[1], NaN) + xp_assert_equal(res.pvalue[1], NaN) + + +def test_all_partitions_concatenated(): + # make sure that _all_paritions_concatenated produces the correct number + # of partitions of the data into samples of the given sizes and that + # all are unique + n = np.array([3, 2, 4], dtype=int) + nc = np.cumsum(n) + + all_partitions = set() + counter = 0 + for partition_concatenated in _resampling._all_partitions_concatenated(n): + counter += 1 + partitioning = np.split(partition_concatenated, nc[:-1]) + all_partitions.add(tuple([frozenset(i) for i in partitioning])) + + expected = np.prod([special.binom(sum(n[i:]), sum(n[i+1:])) + for i in range(len(n)-1)]) + + assert_equal(counter, expected) + assert_equal(len(all_partitions), expected) + + +@pytest.mark.parametrize('fun_name', + ['bootstrap', 'permutation_test', 'monte_carlo_test']) +def test_parameter_vectorized(fun_name): + # Check that parameter `vectorized` is working as desired for all + # resampling functions. Results don't matter; just don't fail asserts. + rng = np.random.default_rng(75245098234592) + sample = rng.random(size=10) + + def rvs(size): # needed by `monte_carlo_test` + return stats.norm.rvs(size=size, random_state=rng) + + fun_options = {'bootstrap': {'data': (sample,), 'rng': rng, + 'method': 'percentile'}, + 'permutation_test': {'data': (sample,), 'rng': rng, + 'permutation_type': 'samples'}, + 'monte_carlo_test': {'sample': sample, 'rvs': rvs}} + common_options = {'n_resamples': 100} + + fun = getattr(stats, fun_name) + options = fun_options[fun_name] + options.update(common_options) + + def statistic(x, axis): + assert x.ndim > 1 or np.array_equal(x, sample) + return np.mean(x, axis=axis) + fun(statistic=statistic, vectorized=None, **options) + fun(statistic=statistic, vectorized=True, **options) + + def statistic(x): + assert x.ndim == 1 + return np.mean(x) + fun(statistic=statistic, vectorized=None, **options) + fun(statistic=statistic, vectorized=False, **options) + + +class TestMonteCarloMethod: + def test_rvs_and_random_state(self): + message = "Use of `rvs` and `rng` are mutually exclusive." + rng = np.random.default_rng(34982345) + with pytest.raises(ValueError, match=message): + stats.MonteCarloMethod(rvs=rng.random, rng=rng) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/test_sampling.py b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_sampling.py new file mode 100644 index 0000000000000000000000000000000000000000..6d50354d10e6713b6effeede7e92be226df01483 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_sampling.py @@ -0,0 +1,1455 @@ +import threading +import pickle +import pytest +from copy import deepcopy +import platform +import sys +import math +import warnings + +import numpy as np +from numpy.testing import assert_allclose, assert_equal +from scipy.stats.sampling import ( + TransformedDensityRejection, + DiscreteAliasUrn, + DiscreteGuideTable, + NumericalInversePolynomial, + NumericalInverseHermite, + RatioUniforms, + SimpleRatioUniforms, + UNURANError +) +from pytest import raises as assert_raises +from scipy import stats +from scipy import special +from scipy.stats import chisquare, cramervonmises +from scipy.stats._distr_params import distdiscrete, distcont +from scipy._lib._util import check_random_state +from scipy._lib._gcutils import assert_deallocated + + +# common test data: this data can be shared between all the tests. + + +# Normal distribution shared between all the continuous methods +class StandardNormal: + def pdf(self, x): + # normalization constant needed for NumericalInverseHermite + return 1./np.sqrt(2.*np.pi) * np.exp(-0.5 * x*x) + + def dpdf(self, x): + return 1./np.sqrt(2.*np.pi) * -x * np.exp(-0.5 * x*x) + + def cdf(self, x): + return special.ndtr(x) + + +all_methods = [ + ("TransformedDensityRejection", {"dist": StandardNormal()}), + ("DiscreteAliasUrn", {"dist": [0.02, 0.18, 0.8]}), + ("DiscreteGuideTable", {"dist": [0.02, 0.18, 0.8]}), + ("NumericalInversePolynomial", {"dist": StandardNormal()}), + ("NumericalInverseHermite", {"dist": StandardNormal()}), + ("SimpleRatioUniforms", {"dist": StandardNormal(), "mode": 0}) +] + +floaterr = r"must be real number, not list" +# Make sure an internal error occurs in UNU.RAN when invalid callbacks are +# passed. Moreover, different generators throw different error messages. +# So, in case of an `UNURANError`, we do not validate the error message. +bad_pdfs_common = [ + # Negative PDF + (lambda x: -x, UNURANError, r"..."), + # Returning wrong type + (lambda x: [], TypeError, floaterr), + # Undefined name inside the function + (lambda x: foo, NameError, r"name 'foo' is not defined"), # type: ignore[name-defined] # noqa: F821, E501 + # Infinite value returned => Overflow error. + (lambda x: np.inf, UNURANError, r"..."), + # NaN value => internal error in UNU.RAN + (lambda x: np.nan, UNURANError, r"..."), + # signature of PDF wrong + (lambda: 1.0, TypeError, r"takes 0 positional arguments but 1 was given") +] + + +# same approach for dpdf +bad_dpdf_common = [ + # Infinite value returned. + (lambda x: np.inf, UNURANError, r"..."), + # NaN value => internal error in UNU.RAN + (lambda x: np.nan, UNURANError, r"..."), + # Returning wrong type + (lambda x: [], TypeError, floaterr), + # Undefined name inside the function + (lambda x: foo, NameError, r"name 'foo' is not defined"), # type: ignore[name-defined] # noqa: F821, E501 + # signature of dPDF wrong + (lambda: 1.0, TypeError, r"takes 0 positional arguments but 1 was given") +] + + +# same approach for logpdf +bad_logpdfs_common = [ + # Returning wrong type + (lambda x: [], TypeError, floaterr), + # Undefined name inside the function + (lambda x: foo, NameError, r"name 'foo' is not defined"), # type: ignore[name-defined] # noqa: F821, E501 + # Infinite value returned => Overflow error. + (lambda x: np.inf, UNURANError, r"..."), + # NaN value => internal error in UNU.RAN + (lambda x: np.nan, UNURANError, r"..."), + # signature of logpdf wrong + (lambda: 1.0, TypeError, r"takes 0 positional arguments but 1 was given") +] + + +bad_pv_common = [ + ([], r"must contain at least one element"), + ([[1.0, 0.0]], r"wrong number of dimensions \(expected 1, got 2\)"), + ([0.2, 0.4, np.nan, 0.8], r"must contain only finite / non-nan values"), + ([0.2, 0.4, np.inf, 0.8], r"must contain only finite / non-nan values"), + ([0.0, 0.0], r"must contain at least one non-zero value"), +] + + +# size of the domains is incorrect +bad_sized_domains = [ + # > 2 elements in the domain + ((1, 2, 3), ValueError, r"must be a length 2 tuple"), + # empty domain + ((), ValueError, r"must be a length 2 tuple") +] + +# domain values are incorrect +bad_domains = [ + ((2, 1), UNURANError, r"left >= right"), + ((1, 1), UNURANError, r"left >= right"), +] + +# infinite and nan values present in domain. +inf_nan_domains = [ + # left >= right + ((10, 10), UNURANError, r"left >= right"), + ((np.inf, np.inf), UNURANError, r"left >= right"), + ((-np.inf, -np.inf), UNURANError, r"left >= right"), + ((np.inf, -np.inf), UNURANError, r"left >= right"), + # Also include nans in some of the domains. + ((-np.inf, np.nan), ValueError, r"only non-nan values"), + ((np.nan, np.inf), ValueError, r"only non-nan values") +] + +# `nan` values present in domain. Some distributions don't support +# infinite tails, so don't mix the nan values with infinities. +nan_domains = [ + ((0, np.nan), ValueError, r"only non-nan values"), + ((np.nan, np.nan), ValueError, r"only non-nan values") +] + + +# all the methods should throw errors for nan, bad sized, and bad valued +# domains. +@pytest.mark.parametrize("domain, err, msg", + bad_domains + bad_sized_domains + + nan_domains) # type: ignore[operator] +@pytest.mark.parametrize("method, kwargs", all_methods) +def test_bad_domain(domain, err, msg, method, kwargs): + Method = getattr(stats.sampling, method) + with pytest.raises(err, match=msg): + Method(**kwargs, domain=domain) + + +@pytest.mark.parametrize("method, kwargs", all_methods) +def test_random_state(method, kwargs): + Method = getattr(stats.sampling, method) + + # simple seed that works for any version of NumPy + seed = 123 + rng1 = Method(**kwargs, random_state=seed) + rng2 = Method(**kwargs, random_state=seed) + assert_equal(rng1.rvs(100), rng2.rvs(100)) + + # global seed + rng = np.random.RandomState(123) + rng1 = Method(**kwargs) + rvs1 = rng1.rvs(100, random_state=rng) + np.random.seed(None) # valid use of np.random.seed + rng2 = Method(**kwargs, random_state=123) + rvs2 = rng2.rvs(100) + assert_equal(rvs1, rvs2) + + # Generator seed for new NumPy + # when a RandomState is given, it should take the bitgen_t + # member of the class and create a Generator instance. + seed1 = np.random.RandomState(np.random.MT19937(123)) + seed2 = np.random.Generator(np.random.MT19937(123)) + rng1 = Method(**kwargs, random_state=seed1) + rng2 = Method(**kwargs, random_state=seed2) + assert_equal(rng1.rvs(100), rng2.rvs(100)) + + +def test_set_random_state(): + rng1 = TransformedDensityRejection(StandardNormal(), random_state=123) + rng2 = TransformedDensityRejection(StandardNormal()) + rng2.set_random_state(123) + assert_equal(rng1.rvs(100), rng2.rvs(100)) + rng = TransformedDensityRejection(StandardNormal(), random_state=123) + rvs1 = rng.rvs(100) + rng.set_random_state(123) + rvs2 = rng.rvs(100) + assert_equal(rvs1, rvs2) + + +def test_threading_behaviour(): + # Test if the API is thread-safe. + # This verifies if the lock mechanism and the use of `PyErr_Occurred` + # is correct. + errors = {"err1": None, "err2": None} + + class Distribution: + def __init__(self, pdf_msg): + self.pdf_msg = pdf_msg + + def pdf(self, x): + if 49.9 < x < 50.0: + raise ValueError(self.pdf_msg) + return x + + def dpdf(self, x): + return 1 + + def func1(): + dist = Distribution('foo') + rng = TransformedDensityRejection(dist, domain=(10, 100), + random_state=12) + try: + rng.rvs(100000) + except ValueError as e: + errors['err1'] = e.args[0] + + def func2(): + dist = Distribution('bar') + rng = TransformedDensityRejection(dist, domain=(10, 100), + random_state=2) + try: + rng.rvs(100000) + except ValueError as e: + errors['err2'] = e.args[0] + + t1 = threading.Thread(target=func1) + t2 = threading.Thread(target=func2) + + t1.start() + t2.start() + + t1.join() + t2.join() + + assert errors['err1'] == 'foo' + assert errors['err2'] == 'bar' + + +@pytest.mark.parametrize("method, kwargs", all_methods) +def test_pickle(method, kwargs): + Method = getattr(stats.sampling, method) + rng1 = Method(**kwargs, random_state=123) + obj = pickle.dumps(rng1) + rng2 = pickle.loads(obj) + assert_equal(rng1.rvs(100), rng2.rvs(100)) + + +@pytest.mark.parametrize("size", [None, 0, (0, ), 1, (10, 3), (2, 3, 4, 5), + (0, 0), (0, 1)]) +def test_rvs_size(size): + # As the `rvs` method is present in the base class and shared between + # all the classes, we can just test with one of the methods. + rng = TransformedDensityRejection(StandardNormal()) + if size is None: + assert np.isscalar(rng.rvs(size)) + else: + if np.isscalar(size): + size = (size, ) + assert rng.rvs(size).shape == size + + +def test_with_scipy_distribution(): + # test if the setup works with SciPy's rv_frozen distributions + dist = stats.norm() + urng = np.random.default_rng(0) + rng = NumericalInverseHermite(dist, random_state=urng) + u = np.linspace(0, 1, num=100) + check_cont_samples(rng, dist, dist.stats()) + assert_allclose(dist.ppf(u), rng.ppf(u)) + # test if it works with `loc` and `scale` + dist = stats.norm(loc=10., scale=5.) + rng = NumericalInverseHermite(dist, random_state=urng) + check_cont_samples(rng, dist, dist.stats()) + assert_allclose(dist.ppf(u), rng.ppf(u)) + # check for discrete distributions + dist = stats.binom(10, 0.2) + rng = DiscreteAliasUrn(dist, random_state=urng) + domain = dist.support() + pv = dist.pmf(np.arange(domain[0], domain[1]+1)) + check_discr_samples(rng, pv, dist.stats()) + + +def test_NumericalInverseHermite_refcycle(): + # test if NumericalInverseHermite contains a reference cycle + dist = stats.norm() + urng = np.random.default_rng(0) + with assert_deallocated(NumericalInverseHermite, dist, random_state=urng) as rng: + u = np.linspace(0, 1, num=100) + check_cont_samples(rng, dist, dist.stats()) + assert_allclose(dist.ppf(u), rng.ppf(u)) + del rng + + +def check_cont_samples(rng, dist, mv_ex, rtol=1e-7, atol=1e-1): + rvs = rng.rvs(100000) + mv = rvs.mean(), rvs.var() + # test the moments only if the variance is finite + if np.isfinite(mv_ex[1]): + assert_allclose(mv, mv_ex, rtol=rtol, atol=atol) + # Cramer Von Mises test for goodness-of-fit + rvs = rng.rvs(500) + dist.cdf = np.vectorize(dist.cdf) + pval = cramervonmises(rvs, dist.cdf).pvalue + assert pval > 0.1 + + +def check_discr_samples(rng, pv, mv_ex, rtol=1e-3, atol=1e-1): + rvs = rng.rvs(100000) + # test if the first few moments match + mv = rvs.mean(), rvs.var() + assert_allclose(mv, mv_ex, rtol=rtol, atol=atol) + # normalize + pv = pv / pv.sum() + # chi-squared test for goodness-of-fit + obs_freqs = np.zeros_like(pv) + _, freqs = np.unique(rvs, return_counts=True) + freqs = freqs / freqs.sum() + obs_freqs[:freqs.size] = freqs + pval = chisquare(obs_freqs, pv).pvalue + assert pval > 0.1 + + +def test_warning_center_not_in_domain(): + # UNURAN will warn if the center provided or the one computed w/o the + # domain is outside of the domain + msg = "102 : center moved into domain of distribution" + with pytest.warns(RuntimeWarning, match=msg): + NumericalInversePolynomial(StandardNormal(), center=0, domain=(3, 5)) + with pytest.warns(RuntimeWarning, match=msg): + NumericalInversePolynomial(StandardNormal(), domain=(3, 5)) + + +@pytest.mark.parametrize('method', ["SimpleRatioUniforms", + "NumericalInversePolynomial", + "TransformedDensityRejection"]) +def test_error_mode_not_in_domain(method): + # UNURAN raises an error if the mode is not in the domain + # the behavior is different compared to the case that center is not in the + # domain. mode is supposed to be the exact value, center can be an + # approximate value + Method = getattr(stats.sampling, method) + msg = "17 : mode not in domain" + with pytest.raises(UNURANError, match=msg): + Method(StandardNormal(), mode=0, domain=(3, 5)) + + +@pytest.mark.parametrize('method', ["NumericalInverseHermite", + "NumericalInversePolynomial"]) +class TestQRVS: + def test_input_validation(self, method): + match = "`qmc_engine` must be an instance of..." + with pytest.raises(ValueError, match=match): + Method = getattr(stats.sampling, method) + gen = Method(StandardNormal()) + gen.qrvs(qmc_engine=0) + + # issues with QMCEngines and old NumPy + Method = getattr(stats.sampling, method) + gen = Method(StandardNormal()) + + match = "`d` must be consistent with dimension of `qmc_engine`." + with pytest.raises(ValueError, match=match): + gen.qrvs(d=3, qmc_engine=stats.qmc.Halton(2)) + + qrngs = [None, stats.qmc.Sobol(1, seed=0), stats.qmc.Halton(3, seed=0)] + # `size=None` should not add anything to the shape, `size=1` should + sizes = [(None, tuple()), (1, (1,)), (4, (4,)), + ((4,), (4,)), ((2, 4), (2, 4))] # type: ignore + # Neither `d=None` nor `d=1` should add anything to the shape + ds = [(None, tuple()), (1, tuple()), (3, (3,))] + + @pytest.mark.parametrize('qrng', qrngs) + @pytest.mark.parametrize('size_in, size_out', sizes) + @pytest.mark.parametrize('d_in, d_out', ds) + @pytest.mark.thread_unsafe(reason="fails in parallel") + def test_QRVS_shape_consistency(self, qrng, size_in, size_out, + d_in, d_out, method): + w32 = sys.platform == "win32" and platform.architecture()[0] == "32bit" + if w32 and method == "NumericalInversePolynomial": + pytest.xfail("NumericalInversePolynomial.qrvs fails for Win " + "32-bit") + + dist = StandardNormal() + Method = getattr(stats.sampling, method) + gen = Method(dist) + + # If d and qrng.d are inconsistent, an error is raised + if d_in is not None and qrng is not None and qrng.d != d_in: + match = "`d` must be consistent with dimension of `qmc_engine`." + with pytest.raises(ValueError, match=match): + gen.qrvs(size_in, d=d_in, qmc_engine=qrng) + return + + # Sometimes d is really determined by qrng + if d_in is None and qrng is not None and qrng.d != 1: + d_out = (qrng.d,) + + shape_expected = size_out + d_out + + qrng2 = deepcopy(qrng) + qrvs = gen.qrvs(size=size_in, d=d_in, qmc_engine=qrng) + if size_in is not None: + assert qrvs.shape == shape_expected + + if qrng2 is not None: + uniform = qrng2.random(np.prod(size_in) or 1) + qrvs2 = stats.norm.ppf(uniform).reshape(shape_expected) + assert_allclose(qrvs, qrvs2, atol=1e-12) + + def test_QRVS_size_tuple(self, method): + # QMCEngine samples are always of shape (n, d). When `size` is a tuple, + # we set `n = prod(size)` in the call to qmc_engine.random, transform + # the sample, and reshape it to the final dimensions. When we reshape, + # we need to be careful, because the _columns_ of the sample returned + # by a QMCEngine are "independent"-ish, but the elements within the + # columns are not. We need to make sure that this doesn't get mixed up + # by reshaping: qrvs[..., i] should remain "independent"-ish of + # qrvs[..., i+1], but the elements within qrvs[..., i] should be + # transformed from the same low-discrepancy sequence. + + dist = StandardNormal() + Method = getattr(stats.sampling, method) + gen = Method(dist) + + size = (3, 4) + d = 5 + qrng = stats.qmc.Halton(d, seed=0) + qrng2 = stats.qmc.Halton(d, seed=0) + + uniform = qrng2.random(np.prod(size)) + + qrvs = gen.qrvs(size=size, d=d, qmc_engine=qrng) + qrvs2 = stats.norm.ppf(uniform) + + for i in range(d): + sample = qrvs[..., i] + sample2 = qrvs2[:, i].reshape(size) + assert_allclose(sample, sample2, atol=1e-12) + + +class TestTransformedDensityRejection: + # Simple Custom Distribution + class dist0: + def pdf(self, x): + return 3/4 * (1-x*x) + + def dpdf(self, x): + return 3/4 * (-2*x) + + def cdf(self, x): + return 3/4 * (x - x**3/3 + 2/3) + + def support(self): + return -1, 1 + + # Standard Normal Distribution + class dist1: + def pdf(self, x): + return stats.norm._pdf(x / 0.1) + + def dpdf(self, x): + return -x / 0.01 * stats.norm._pdf(x / 0.1) + + def cdf(self, x): + return stats.norm._cdf(x / 0.1) + + # pdf with piecewise linear function as transformed density + # with T = -1/sqrt with shift. Taken from UNU.RAN test suite + # (from file t_tdr_ps.c) + class dist2: + def __init__(self, shift): + self.shift = shift + + def pdf(self, x): + x -= self.shift + y = 1. / (abs(x) + 1.) + return 0.5 * y * y + + def dpdf(self, x): + x -= self.shift + y = 1. / (abs(x) + 1.) + y = y * y * y + return y if (x < 0.) else -y + + def cdf(self, x): + x -= self.shift + if x <= 0.: + return 0.5 / (1. - x) + else: + return 1. - 0.5 / (1. + x) + + dists = [dist0(), dist1(), dist2(0.), dist2(10000.)] + + # exact mean and variance of the distributions in the list dists + mv0 = [0., 4./15.] + mv1 = [0., 0.01] + mv2 = [0., np.inf] + mv3 = [10000., np.inf] + mvs = [mv0, mv1, mv2, mv3] + + @pytest.mark.parametrize("dist, mv_ex", + list(zip(dists, mvs))) + @pytest.mark.thread_unsafe(reason="deadlocks for unknown reasons") + def test_basic(self, dist, mv_ex): + with warnings.catch_warnings(): + # filter the warnings thrown by UNU.RAN + warnings.simplefilter("ignore", RuntimeWarning) + rng = TransformedDensityRejection(dist, random_state=42) + check_cont_samples(rng, dist, mv_ex) + + # PDF 0 everywhere => bad construction points + bad_pdfs = [(lambda x: 0, UNURANError, r"50 : bad construction points.")] + bad_pdfs += bad_pdfs_common # type: ignore[arg-type] + + @pytest.mark.parametrize("pdf, err, msg", bad_pdfs) + def test_bad_pdf(self, pdf, err, msg): + class dist: + pass + dist.pdf = pdf + dist.dpdf = lambda x: 1 # an arbitrary dPDF + with pytest.raises(err, match=msg): + TransformedDensityRejection(dist) + + @pytest.mark.parametrize("dpdf, err, msg", bad_dpdf_common) + def test_bad_dpdf(self, dpdf, err, msg): + class dist: + pass + dist.pdf = lambda x: x + dist.dpdf = dpdf + with pytest.raises(err, match=msg): + TransformedDensityRejection(dist, domain=(1, 10)) + + # test domains with inf + nan in them. need to write a custom test for + # this because not all methods support infinite tails. + @pytest.mark.parametrize("domain, err, msg", inf_nan_domains) + def test_inf_nan_domains(self, domain, err, msg): + with pytest.raises(err, match=msg): + TransformedDensityRejection(StandardNormal(), domain=domain) + + @pytest.mark.parametrize("construction_points", [-1, 0, 0.1]) + def test_bad_construction_points_scalar(self, construction_points): + with pytest.raises(ValueError, match=r"`construction_points` must be " + r"a positive integer."): + TransformedDensityRejection( + StandardNormal(), construction_points=construction_points + ) + + def test_bad_construction_points_array(self): + # empty array + construction_points = [] + with pytest.raises(ValueError, match=r"`construction_points` must " + r"either be a " + r"scalar or a non-empty array."): + TransformedDensityRejection( + StandardNormal(), construction_points=construction_points + ) + + # construction_points not monotonically increasing + construction_points = [1, 1, 1, 1, 1, 1] + with pytest.warns(RuntimeWarning, match=r"33 : starting points not " + r"strictly monotonically " + r"increasing"): + TransformedDensityRejection( + StandardNormal(), construction_points=construction_points + ) + + # construction_points containing nans + construction_points = [np.nan, np.nan, np.nan] + with pytest.raises(UNURANError, match=r"50 : bad construction " + r"points."): + TransformedDensityRejection( + StandardNormal(), construction_points=construction_points + ) + + # construction_points out of domain + construction_points = [-10, 10] + with pytest.warns(RuntimeWarning, match=r"50 : starting point out of " + r"domain"): + TransformedDensityRejection( + StandardNormal(), domain=(-3, 3), + construction_points=construction_points + ) + + @pytest.mark.parametrize("c", [-1., np.nan, np.inf, 0.1, 1.]) + def test_bad_c(self, c): + msg = r"`c` must either be -0.5 or 0." + with pytest.raises(ValueError, match=msg): + TransformedDensityRejection(StandardNormal(), c=-1.) + + u = [np.linspace(0, 1, num=1000), [], [[]], [np.nan], + [-np.inf, np.nan, np.inf], 0, + [[np.nan, 0.5, 0.1], [0.2, 0.4, np.inf], [-2, 3, 4]]] + + @pytest.mark.parametrize("u", u) + def test_ppf_hat(self, u): + # Increase the `max_squeeze_hat_ratio` so the ppf_hat is more + # accurate. + rng = TransformedDensityRejection(StandardNormal(), + max_squeeze_hat_ratio=0.9999) + # Older versions of NumPy throw RuntimeWarnings for comparisons + # with nan. + with warnings.catch_warnings(): + msg = "invalid value encountered in " + warnings.filterwarnings("ignore", msg + "greater", RuntimeWarning) + warnings.filterwarnings("ignore", msg + "greater_equal", RuntimeWarning) + warnings.filterwarnings("ignore", msg + "less", RuntimeWarning) + warnings.filterwarnings("ignore", msg + "less_equal", RuntimeWarning) + res = rng.ppf_hat(u) + expected = stats.norm.ppf(u) + assert_allclose(res, expected, rtol=1e-3, atol=1e-5) + assert res.shape == expected.shape + + def test_bad_dist(self): + # Empty distribution + class dist: + ... + + msg = r"`pdf` required but not found." + with pytest.raises(ValueError, match=msg): + TransformedDensityRejection(dist) + + # dPDF not present in dist + class dist: + pdf = lambda x: 1-x*x # noqa: E731 + + msg = r"`dpdf` required but not found." + with pytest.raises(ValueError, match=msg): + TransformedDensityRejection(dist) + + +class TestDiscreteAliasUrn: + # DAU fails on these probably because of large domains and small + # computation errors in PMF. Mean/SD match but chi-squared test fails. + basic_fail_dists = { + 'nchypergeom_fisher', # numerical errors on tails + 'nchypergeom_wallenius', # numerical errors on tails + 'randint' # fails on 32-bit ubuntu + } + + @pytest.mark.parametrize("distname, params", distdiscrete) + def test_basic(self, distname, params): + if distname in self.basic_fail_dists: + msg = ("DAU fails on these probably because of large domains " + "and small computation errors in PMF.") + pytest.skip(msg) + if not isinstance(distname, str): + dist = distname + else: + dist = getattr(stats, distname) + dist = dist(*params) + domain = dist.support() + if not np.isfinite(domain[1] - domain[0]): + # DAU only works with finite domain. So, skip the distributions + # with infinite tails. + pytest.skip("DAU only works with a finite domain.") + k = np.arange(domain[0], domain[1]+1) + pv = dist.pmf(k) + mv_ex = dist.stats('mv') + rng = DiscreteAliasUrn(dist, random_state=42) + check_discr_samples(rng, pv, mv_ex) + + # Can't use bad_pmf_common here as we evaluate PMF early on to avoid + # unhelpful errors from UNU.RAN. + bad_pmf = [ + # inf returned + (lambda x: np.inf, ValueError, + r"must contain only finite / non-nan values"), + # nan returned + (lambda x: np.nan, ValueError, + r"must contain only finite / non-nan values"), + # all zeros + (lambda x: 0.0, ValueError, + r"must contain at least one non-zero value"), + # Undefined name inside the function + (lambda x: foo, NameError, # type: ignore[name-defined] # noqa: F821 + r"name 'foo' is not defined"), + # Returning wrong type. + (lambda x: [], ValueError, + r"setting an array element with a sequence."), + # probabilities < 0 + (lambda x: -x, UNURANError, + r"50 : probability < 0"), + # signature of PMF wrong + (lambda: 1.0, TypeError, + r"takes 0 positional arguments but 1 was given") + ] + + @pytest.mark.parametrize("pmf, err, msg", bad_pmf) + def test_bad_pmf(self, pmf, err, msg): + class dist: + pass + dist.pmf = pmf + with pytest.raises(err, match=msg): + DiscreteAliasUrn(dist, domain=(1, 10)) + + @pytest.mark.parametrize("pv", [[0.18, 0.02, 0.8], + [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]]) + def test_sampling_with_pv(self, pv): + pv = np.asarray(pv, dtype=np.float64) + rng = DiscreteAliasUrn(pv, random_state=123) + rng.rvs(100_000) + pv = pv / pv.sum() + variates = np.arange(0, len(pv)) + # test if the first few moments match + m_expected = np.average(variates, weights=pv) + v_expected = np.average((variates - m_expected) ** 2, weights=pv) + mv_expected = m_expected, v_expected + check_discr_samples(rng, pv, mv_expected) + + @pytest.mark.parametrize("pv, msg", bad_pv_common) + def test_bad_pv(self, pv, msg): + with pytest.raises(ValueError, match=msg): + DiscreteAliasUrn(pv) + + # DAU doesn't support infinite tails. So, it should throw an error when + # inf is present in the domain. + inf_domain = [(-np.inf, np.inf), (np.inf, np.inf), (-np.inf, -np.inf), + (0, np.inf), (-np.inf, 0)] + + @pytest.mark.parametrize("domain", inf_domain) + def test_inf_domain(self, domain): + with pytest.raises(ValueError, match=r"must be finite"): + DiscreteAliasUrn(stats.binom(10, 0.2), domain=domain) + + def test_bad_urn_factor(self): + with pytest.warns(RuntimeWarning, match=r"relative urn size < 1."): + DiscreteAliasUrn([0.5, 0.5], urn_factor=-1) + + def test_bad_args(self): + msg = (r"`domain` must be provided when the " + r"probability vector is not available.") + + class dist: + def pmf(self, x): + return x + + with pytest.raises(ValueError, match=msg): + DiscreteAliasUrn(dist) + + def test_gh19359(self): + pv = special.softmax(np.ones((1533,))) + rng = DiscreteAliasUrn(pv, random_state=42) + # check the correctness + check_discr_samples(rng, pv, (1532 / 2, (1532**2 - 1) / 12), + rtol=5e-3) + + +class TestNumericalInversePolynomial: + # Simple Custom Distribution + class dist0: + def pdf(self, x): + return 3/4 * (1-x*x) + + def cdf(self, x): + return 3/4 * (x - x**3/3 + 2/3) + + def support(self): + return -1, 1 + + # Standard Normal Distribution + class dist1: + def pdf(self, x): + return stats.norm._pdf(x / 0.1) + + def cdf(self, x): + return stats.norm._cdf(x / 0.1) + + # Sin 2 distribution + # / 0.05 + 0.45*(1 +sin(2 Pi x)) if |x| <= 1 + # f(x) = < + # \ 0 otherwise + # Taken from UNU.RAN test suite (from file t_pinv.c) + class dist2: + def pdf(self, x): + return 0.05 + 0.45 * (1 + np.sin(2*np.pi*x)) + + def cdf(self, x): + return (0.05*(x + 1) + + 0.9*(1. + 2.*np.pi*(1 + x) - np.cos(2.*np.pi*x)) / + (4.*np.pi)) + + def support(self): + return -1, 1 + + # Sin 10 distribution + # / 0.05 + 0.45*(1 +sin(2 Pi x)) if |x| <= 5 + # f(x) = < + # \ 0 otherwise + # Taken from UNU.RAN test suite (from file t_pinv.c) + class dist3: + def pdf(self, x): + return 0.2 * (0.05 + 0.45 * (1 + np.sin(2*np.pi*x))) + + def cdf(self, x): + return x/10. + 0.5 + 0.09/(2*np.pi) * (np.cos(10*np.pi) - + np.cos(2*np.pi*x)) + + def support(self): + return -5, 5 + + dists = [dist0(), dist1(), dist2(), dist3()] + + # exact mean and variance of the distributions in the list dists + mv0 = [0., 4./15.] + mv1 = [0., 0.01] + mv2 = [-0.45/np.pi, 2/3*0.5 - 0.45**2/np.pi**2] + mv3 = [-0.45/np.pi, 0.2 * 250/3 * 0.5 - 0.45**2/np.pi**2] + mvs = [mv0, mv1, mv2, mv3] + + @pytest.mark.thread_unsafe(reason="deadlocks for unknown reasons") + @pytest.mark.parametrize("dist, mv_ex", + list(zip(dists, mvs))) + def test_basic(self, dist, mv_ex): + rng = NumericalInversePolynomial(dist, random_state=42) + check_cont_samples(rng, dist, mv_ex) + + @pytest.mark.xslow + @pytest.mark.parametrize("distname, params", distcont) + def test_basic_all_scipy_dists(self, distname, params): + + very_slow_dists = ['anglit', 'gausshyper', 'kappa4', + 'ksone', 'kstwo', 'levy_l', + 'levy_stable', 'studentized_range', + 'trapezoid', 'triang', 'vonmises'] + # for these distributions, some assertions fail due to minor + # numerical differences. They can be avoided either by changing + # the seed or by increasing the u_resolution. + fail_dists = ['chi2', 'fatiguelife', 'gibrat', + 'halfgennorm', 'lognorm', 'ncf', + 'ncx2', 'pareto', 't'] + # for these distributions, skip the check for agreement between sample + # moments and true moments. We cannot expect them to pass due to the + # high variance of sample moments. + skip_sample_moment_check = ['rel_breitwigner'] + + if distname in very_slow_dists: + pytest.skip(f"PINV too slow for {distname}") + if distname in fail_dists: + pytest.skip(f"PINV fails for {distname}") + dist = (getattr(stats, distname) + if isinstance(distname, str) + else distname) + dist = dist(*params) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + rng = NumericalInversePolynomial(dist, random_state=42) + if distname in skip_sample_moment_check: + return + check_cont_samples(rng, dist, [dist.mean(), dist.var()]) + + @pytest.mark.parametrize("pdf, err, msg", bad_pdfs_common) + def test_bad_pdf(self, pdf, err, msg): + class dist: + pass + dist.pdf = pdf + with pytest.raises(err, match=msg): + NumericalInversePolynomial(dist, domain=[0, 5]) + + @pytest.mark.parametrize("logpdf, err, msg", bad_logpdfs_common) + def test_bad_logpdf(self, logpdf, err, msg): + class dist: + pass + dist.logpdf = logpdf + with pytest.raises(err, match=msg): + NumericalInversePolynomial(dist, domain=[0, 5]) + + # test domains with inf + nan in them. need to write a custom test for + # this because not all methods support infinite tails. + @pytest.mark.parametrize("domain, err, msg", inf_nan_domains) + def test_inf_nan_domains(self, domain, err, msg): + with pytest.raises(err, match=msg): + NumericalInversePolynomial(StandardNormal(), domain=domain) + + u = [ + # test if quantile 0 and 1 return -inf and inf respectively and check + # the correctness of the PPF for equidistant points between 0 and 1. + np.linspace(0, 1, num=10000), + # test the PPF method for empty arrays + [], [[]], + # test if nans and infs return nan result. + [np.nan], [-np.inf, np.nan, np.inf], + # test if a scalar is returned for a scalar input. + 0, + # test for arrays with nans, values greater than 1 and less than 0, + # and some valid values. + [[np.nan, 0.5, 0.1], [0.2, 0.4, np.inf], [-2, 3, 4]] + ] + + @pytest.mark.parametrize("u", u) + def test_ppf(self, u): + dist = StandardNormal() + rng = NumericalInversePolynomial(dist, u_resolution=1e-14) + # Older versions of NumPy throw RuntimeWarnings for comparisons + # with nan. + with warnings.catch_warnings(): + msg = "invalid value encountered in " + warnings.filterwarnings("ignore", msg + "greater", RuntimeWarning) + warnings.filterwarnings("ignore", msg + "greater_equal", RuntimeWarning) + warnings.filterwarnings("ignore", msg + "less", RuntimeWarning) + warnings.filterwarnings("ignore", msg + "less_equal", RuntimeWarning) + res = rng.ppf(u) + expected = stats.norm.ppf(u) + assert_allclose(res, expected, rtol=1e-11, atol=1e-11) + assert res.shape == expected.shape + + x = [np.linspace(-10, 10, num=10000), [], [[]], [np.nan], + [-np.inf, np.nan, np.inf], 0, + [[np.nan, 0.5, 0.1], [0.2, 0.4, np.inf], [-np.inf, 3, 4]]] + + @pytest.mark.parametrize("x", x) + def test_cdf(self, x): + dist = StandardNormal() + rng = NumericalInversePolynomial(dist, u_resolution=1e-14) + # Older versions of NumPy throw RuntimeWarnings for comparisons + # with nan. + with warnings.catch_warnings(): + msg = "invalid value encountered in " + warnings.filterwarnings("ignore", msg + "greater", RuntimeWarning) + warnings.filterwarnings("ignore", msg + "greater_equal", RuntimeWarning) + warnings.filterwarnings("ignore", msg + "less", RuntimeWarning) + warnings.filterwarnings("ignore", msg + "less_equal", RuntimeWarning) + res = rng.cdf(x) + expected = stats.norm.cdf(x) + assert_allclose(res, expected, rtol=1e-11, atol=1e-11) + assert res.shape == expected.shape + + @pytest.mark.slow + def test_u_error(self): + dist = StandardNormal() + rng = NumericalInversePolynomial(dist, u_resolution=1e-10) + max_error, mae = rng.u_error() + assert max_error < 1e-10 + assert mae <= max_error + rng = NumericalInversePolynomial(dist, u_resolution=1e-14) + max_error, mae = rng.u_error() + assert max_error < 1e-14 + assert mae <= max_error + + bad_orders = [1, 4.5, 20, np.inf, np.nan] + bad_u_resolution = [1e-20, 1e-1, np.inf, np.nan] + + @pytest.mark.parametrize("order", bad_orders) + def test_bad_orders(self, order): + dist = StandardNormal() + + msg = r"`order` must be an integer in the range \[3, 17\]." + with pytest.raises(ValueError, match=msg): + NumericalInversePolynomial(dist, order=order) + + @pytest.mark.parametrize("u_resolution", bad_u_resolution) + def test_bad_u_resolution(self, u_resolution): + msg = r"`u_resolution` must be between 1e-15 and 1e-5." + with pytest.raises(ValueError, match=msg): + NumericalInversePolynomial(StandardNormal(), + u_resolution=u_resolution) + + def test_bad_args(self): + + class BadDist: + def cdf(self, x): + return stats.norm._cdf(x) + + dist = BadDist() + msg = r"Either of the methods `pdf` or `logpdf` must be specified" + with pytest.raises(ValueError, match=msg): + rng = NumericalInversePolynomial(dist) + + dist = StandardNormal() + rng = NumericalInversePolynomial(dist) + msg = r"`sample_size` must be greater than or equal to 1000." + with pytest.raises(ValueError, match=msg): + rng.u_error(10) + + class Distribution: + def pdf(self, x): + return np.exp(-0.5 * x*x) + + dist = Distribution() + rng = NumericalInversePolynomial(dist) + msg = r"Exact CDF required but not found." + with pytest.raises(ValueError, match=msg): + rng.u_error() + + def test_logpdf_pdf_consistency(self): + # 1. check that PINV works with pdf and logpdf only + # 2. check that generated ppf is the same (up to a small tolerance) + + class MyDist: + pass + + # create generator from dist with only pdf + dist_pdf = MyDist() + dist_pdf.pdf = lambda x: math.exp(-x*x/2) + rng1 = NumericalInversePolynomial(dist_pdf) + + # create dist with only logpdf + dist_logpdf = MyDist() + dist_logpdf.logpdf = lambda x: -x*x/2 + rng2 = NumericalInversePolynomial(dist_logpdf) + + q = np.linspace(1e-5, 1-1e-5, num=100) + assert_allclose(rng1.ppf(q), rng2.ppf(q)) + + +class TestNumericalInverseHermite: + # / (1 +sin(2 Pi x))/2 if |x| <= 1 + # f(x) = < + # \ 0 otherwise + # Taken from UNU.RAN test suite (from file t_hinv.c) + class dist0: + def pdf(self, x): + return 0.5*(1. + np.sin(2.*np.pi*x)) + + def dpdf(self, x): + return np.pi*np.cos(2.*np.pi*x) + + def cdf(self, x): + return (1. + 2.*np.pi*(1 + x) - np.cos(2.*np.pi*x)) / (4.*np.pi) + + def support(self): + return -1, 1 + + # / Max(sin(2 Pi x)),0)Pi/2 if -1 < x <0.5 + # f(x) = < + # \ 0 otherwise + # Taken from UNU.RAN test suite (from file t_hinv.c) + class dist1: + def pdf(self, x): + if (x <= -0.5): + return np.sin((2. * np.pi) * x) * 0.5 * np.pi + if (x < 0.): + return 0. + if (x <= 0.5): + return np.sin((2. * np.pi) * x) * 0.5 * np.pi + + def dpdf(self, x): + if (x <= -0.5): + return np.cos((2. * np.pi) * x) * np.pi * np.pi + if (x < 0.): + return 0. + if (x <= 0.5): + return np.cos((2. * np.pi) * x) * np.pi * np.pi + + def cdf(self, x): + if (x <= -0.5): + return 0.25 * (1 - np.cos((2. * np.pi) * x)) + if (x < 0.): + return 0.5 + if (x <= 0.5): + return 0.75 - 0.25 * np.cos((2. * np.pi) * x) + + def support(self): + return -1, 0.5 + + dists = [dist0(), dist1()] + + # exact mean and variance of the distributions in the list dists + mv0 = [-1/(2*np.pi), 1/3 - 1/(4*np.pi*np.pi)] + mv1 = [-1/4, 3/8-1/(2*np.pi*np.pi) - 1/16] + mvs = [mv0, mv1] + + @pytest.mark.parametrize("dist, mv_ex", + list(zip(dists, mvs))) + @pytest.mark.parametrize("order", [3, 5]) + @pytest.mark.thread_unsafe + def test_basic(self, dist, mv_ex, order): + rng = NumericalInverseHermite(dist, order=order, random_state=42) + check_cont_samples(rng, dist, mv_ex) + + # test domains with inf + nan in them. need to write a custom test for + # this because not all methods support infinite tails. + @pytest.mark.parametrize("domain, err, msg", inf_nan_domains) + def test_inf_nan_domains(self, domain, err, msg): + with pytest.raises(err, match=msg): + NumericalInverseHermite(StandardNormal(), domain=domain) + + def basic_test_all_scipy_dists(self, distname, shapes): + slow_dists = {'ksone', 'kstwo', 'levy_stable', 'skewnorm'} + fail_dists = {'beta', 'gausshyper', 'geninvgauss', 'ncf', 'nct', + 'norminvgauss', 'genhyperbolic', 'studentized_range', + 'vonmises', 'kappa4', 'invgauss', 'wald'} + + if distname in slow_dists: + pytest.skip("Distribution is too slow") + if distname in fail_dists: + # specific reasons documented in gh-13319 + # https://github.com/scipy/scipy/pull/13319#discussion_r626188955 + pytest.xfail("Fails - usually due to inaccurate CDF/PDF") + + rng = np.random.default_rng(0) + + dist = getattr(stats, distname)(*shapes) + fni = NumericalInverseHermite(dist) + + x = rng.random(10) + p_tol = np.max(np.abs(dist.ppf(x)-fni.ppf(x))/np.abs(dist.ppf(x))) + u_tol = np.max(np.abs(dist.cdf(fni.ppf(x)) - x)) + + assert p_tol < 1e-8 + assert u_tol < 1e-12 + + @pytest.mark.filterwarnings('ignore::RuntimeWarning') + @pytest.mark.xslow + @pytest.mark.parametrize(("distname", "shapes"), distcont) + def test_basic_all_scipy_dists(self, distname, shapes): + # if distname == "truncnorm": + # pytest.skip("Tested separately") + self.basic_test_all_scipy_dists(distname, shapes) + + @pytest.mark.fail_slow(5) + @pytest.mark.filterwarnings('ignore::RuntimeWarning') + @pytest.mark.parallel_threads_limit(4) # Very slow + def test_basic_truncnorm_gh17155(self): + self.basic_test_all_scipy_dists("truncnorm", (0.1, 2)) + + def test_input_validation(self): + match = r"`order` must be either 1, 3, or 5." + with pytest.raises(ValueError, match=match): + NumericalInverseHermite(StandardNormal(), order=2) + + match = "`cdf` required but not found" + with pytest.raises(ValueError, match=match): + NumericalInverseHermite("norm") + + match = "could not convert string to float" + with pytest.raises(ValueError, match=match): + NumericalInverseHermite(StandardNormal(), + u_resolution='ekki') + + rngs = [None, 0, np.random.RandomState(0)] + rngs.append(np.random.default_rng(0)) # type: ignore + sizes = [(None, tuple()), (8, (8,)), ((4, 5, 6), (4, 5, 6))] + + @pytest.mark.parametrize('rng', rngs) + @pytest.mark.parametrize('size_in, size_out', sizes) + @pytest.mark.thread_unsafe + def test_RVS(self, rng, size_in, size_out): + dist = StandardNormal() + fni = NumericalInverseHermite(dist) + + rng2 = deepcopy(rng) + rvs = fni.rvs(size=size_in, random_state=rng) + if size_in is not None: + assert rvs.shape == size_out + + if rng2 is not None: + rng2 = check_random_state(rng2) + uniform = rng2.uniform(size=size_in) + rvs2 = stats.norm.ppf(uniform) + assert_allclose(rvs, rvs2) + + def test_inaccurate_CDF(self): + # CDF function with inaccurate tail cannot be inverted; see gh-13319 + # https://github.com/scipy/scipy/pull/13319#discussion_r626188955 + shapes = (2.3098496451481823, 0.6268795430096368) + match = ("98 : one or more intervals very short; possibly due to " + "numerical problems with a pole or very flat tail") + + # fails with default tol + with pytest.warns(RuntimeWarning, match=match): + NumericalInverseHermite(stats.beta(*shapes)) + + # no error with coarser tol + NumericalInverseHermite(stats.beta(*shapes), u_resolution=1e-8) + + def test_custom_distribution(self): + dist1 = StandardNormal() + fni1 = NumericalInverseHermite(dist1) + + dist2 = stats.norm() + fni2 = NumericalInverseHermite(dist2) + + assert_allclose(fni1.rvs(random_state=0), fni2.rvs(random_state=0)) + + u = [ + # check the correctness of the PPF for equidistant points between + # 0.02 and 0.98. + np.linspace(0., 1., num=10000), + # test the PPF method for empty arrays + [], [[]], + # test if nans and infs return nan result. + [np.nan], [-np.inf, np.nan, np.inf], + # test if a scalar is returned for a scalar input. + 0, + # test for arrays with nans, values greater than 1 and less than 0, + # and some valid values. + [[np.nan, 0.5, 0.1], [0.2, 0.4, np.inf], [-2, 3, 4]] + ] + + @pytest.mark.parametrize("u", u) + def test_ppf(self, u): + dist = StandardNormal() + rng = NumericalInverseHermite(dist, u_resolution=1e-12) + # Older versions of NumPy throw RuntimeWarnings for comparisons + # with nan. + with warnings.catch_warnings(): + msg = "invalid value encountered in " + warnings.filterwarnings("ignore", msg + "greater", RuntimeWarning) + warnings.filterwarnings("ignore", msg + "greater_equal", RuntimeWarning) + warnings.filterwarnings("ignore", msg + "less", RuntimeWarning) + warnings.filterwarnings("ignore", msg + "less_equal", RuntimeWarning) + res = rng.ppf(u) + expected = stats.norm.ppf(u) + assert_allclose(res, expected, rtol=1e-9, atol=3e-10) + assert res.shape == expected.shape + + @pytest.mark.slow + def test_u_error(self): + dist = StandardNormal() + rng = NumericalInverseHermite(dist, u_resolution=1e-10) + max_error, mae = rng.u_error() + assert max_error < 1e-10 + assert mae <= max_error + with warnings.catch_warnings(): + # ignore warning about u-resolution being too small. + warnings.simplefilter("ignore", RuntimeWarning) + rng = NumericalInverseHermite(dist, u_resolution=1e-14) + max_error, mae = rng.u_error() + assert max_error < 1e-14 + assert mae <= max_error + + +class TestDiscreteGuideTable: + basic_fail_dists = { + 'nchypergeom_fisher', # numerical errors on tails + 'nchypergeom_wallenius', # numerical errors on tails + 'randint' # fails on 32-bit ubuntu + } + + def test_guide_factor_gt3_raises_warning(self): + pv = [0.1, 0.3, 0.6] + urng = np.random.default_rng() + with pytest.warns(RuntimeWarning): + DiscreteGuideTable(pv, random_state=urng, guide_factor=7) + + def test_guide_factor_zero_raises_warning(self): + pv = [0.1, 0.3, 0.6] + urng = np.random.default_rng() + with pytest.warns(RuntimeWarning): + DiscreteGuideTable(pv, random_state=urng, guide_factor=0) + + def test_negative_guide_factor_raises_warning(self): + # This occurs from the UNU.RAN wrapper automatically. + # however it already gives a useful warning + # Here we just test that a warning is raised. + pv = [0.1, 0.3, 0.6] + urng = np.random.default_rng() + with pytest.warns(RuntimeWarning): + DiscreteGuideTable(pv, random_state=urng, guide_factor=-1) + + @pytest.mark.parametrize("distname, params", distdiscrete) + def test_basic(self, distname, params): + if distname in self.basic_fail_dists: + msg = ("DGT fails on these probably because of large domains " + "and small computation errors in PMF.") + pytest.skip(msg) + + if not isinstance(distname, str): + dist = distname + else: + dist = getattr(stats, distname) + + dist = dist(*params) + domain = dist.support() + + if not np.isfinite(domain[1] - domain[0]): + # DGT only works with finite domain. So, skip the distributions + # with infinite tails. + pytest.skip("DGT only works with a finite domain.") + + k = np.arange(domain[0], domain[1]+1) + pv = dist.pmf(k) + mv_ex = dist.stats('mv') + rng = DiscreteGuideTable(dist, random_state=42) + check_discr_samples(rng, pv, mv_ex) + + u = [ + # the correctness of the PPF for equidistant points between 0 and 1. + np.linspace(0, 1, num=10000), + # test the PPF method for empty arrays + [], [[]], + # test if nans and infs return nan result. + [np.nan], [-np.inf, np.nan, np.inf], + # test if a scalar is returned for a scalar input. + 0, + # test for arrays with nans, values greater than 1 and less than 0, + # and some valid values. + [[np.nan, 0.5, 0.1], [0.2, 0.4, np.inf], [-2, 3, 4]] + ] + + @pytest.mark.parametrize('u', u) + def test_ppf(self, u): + n, p = 4, 0.1 + dist = stats.binom(n, p) + rng = DiscreteGuideTable(dist, random_state=42) + + # Older versions of NumPy throw RuntimeWarnings for comparisons + # with nan. + with warnings.catch_warnings(): + msg = "invalid value encountered in " + warnings.filterwarnings("ignore", msg + "greater", RuntimeWarning) + warnings.filterwarnings("ignore", msg + "greater_equal", RuntimeWarning) + warnings.filterwarnings("ignore", msg + "less", RuntimeWarning) + warnings.filterwarnings("ignore", msg + "less_equal", RuntimeWarning) + res = rng.ppf(u) + expected = stats.binom.ppf(u, n, p) + assert_equal(res.shape, expected.shape) + assert_equal(res, expected) + + @pytest.mark.parametrize("pv, msg", bad_pv_common) + def test_bad_pv(self, pv, msg): + with pytest.raises(ValueError, match=msg): + DiscreteGuideTable(pv) + + # DGT doesn't support infinite tails. So, it should throw an error when + # inf is present in the domain. + inf_domain = [(-np.inf, np.inf), (np.inf, np.inf), (-np.inf, -np.inf), + (0, np.inf), (-np.inf, 0)] + + @pytest.mark.parametrize("domain", inf_domain) + def test_inf_domain(self, domain): + with pytest.raises(ValueError, match=r"must be finite"): + DiscreteGuideTable(stats.binom(10, 0.2), domain=domain) + + +class TestSimpleRatioUniforms: + # pdf with piecewise linear function as transformed density + # with T = -1/sqrt with shift. Taken from UNU.RAN test suite + # (from file t_srou.c) + class dist: + def __init__(self, shift): + self.shift = shift + self.mode = shift + + def pdf(self, x): + x -= self.shift + y = 1. / (abs(x) + 1.) + return 0.5 * y * y + + def cdf(self, x): + x -= self.shift + if x <= 0.: + return 0.5 / (1. - x) + else: + return 1. - 0.5 / (1. + x) + + dists = [dist(0.), dist(10000.)] + + # exact mean and variance of the distributions in the list dists + mv1 = [0., np.inf] + mv2 = [10000., np.inf] + mvs = [mv1, mv2] + + @pytest.mark.parametrize("dist, mv_ex", + list(zip(dists, mvs))) + @pytest.mark.thread_unsafe + def test_basic(self, dist, mv_ex): + rng = SimpleRatioUniforms(dist, mode=dist.mode, random_state=42) + check_cont_samples(rng, dist, mv_ex) + rng = SimpleRatioUniforms(dist, mode=dist.mode, + cdf_at_mode=dist.cdf(dist.mode), + random_state=42) + check_cont_samples(rng, dist, mv_ex) + + # test domains with inf + nan in them. need to write a custom test for + # this because not all methods support infinite tails. + @pytest.mark.parametrize("domain, err, msg", inf_nan_domains) + def test_inf_nan_domains(self, domain, err, msg): + with pytest.raises(err, match=msg): + SimpleRatioUniforms(StandardNormal(), domain=domain) + + def test_bad_args(self): + # pdf_area < 0 + with pytest.raises(ValueError, match=r"`pdf_area` must be > 0"): + SimpleRatioUniforms(StandardNormal(), mode=0, pdf_area=-1) + + +class TestRatioUniforms: + def test_rv_generation(self): + # use KS test to check distribution of rvs + # normal distribution + f = stats.norm.pdf + v = np.sqrt(f(np.sqrt(2))) * np.sqrt(2) + u = np.sqrt(f(0)) + gen = RatioUniforms(f, umax=u, vmin=-v, vmax=v, random_state=12345) + assert_equal(stats.kstest(gen.rvs(2500), 'norm')[1] > 0.25, True) + + # exponential distribution + gen = RatioUniforms(lambda x: np.exp(-x), umax=1, + vmin=0, vmax=2*np.exp(-1), random_state=12345) + assert_equal(stats.kstest(gen.rvs(1000), 'expon')[1] > 0.25, True) + + def test_shape(self): + # test shape of return value depending on size parameter + f = stats.norm.pdf + v = np.sqrt(f(np.sqrt(2))) * np.sqrt(2) + u = np.sqrt(f(0)) + + gen1 = RatioUniforms(f, umax=u, vmin=-v, vmax=v, random_state=1234) + gen2 = RatioUniforms(f, umax=u, vmin=-v, vmax=v, random_state=1234) + gen3 = RatioUniforms(f, umax=u, vmin=-v, vmax=v, random_state=1234) + r1, r2, r3 = gen1.rvs(3), gen2.rvs((3,)), gen3.rvs((3, 1)) + assert_equal(r1, r2) + assert_equal(r2, r3.flatten()) + assert_equal(r1.shape, (3,)) + assert_equal(r3.shape, (3, 1)) + + gen4 = RatioUniforms(f, umax=u, vmin=-v, vmax=v, random_state=12) + gen5 = RatioUniforms(f, umax=u, vmin=-v, vmax=v, random_state=12) + r4, r5 = gen4.rvs(size=(3, 3, 3)), gen5.rvs(size=27) + assert_equal(r4.flatten(), r5) + assert_equal(r4.shape, (3, 3, 3)) + + gen6 = RatioUniforms(f, umax=u, vmin=-v, vmax=v, random_state=1234) + gen7 = RatioUniforms(f, umax=u, vmin=-v, vmax=v, random_state=1234) + gen8 = RatioUniforms(f, umax=u, vmin=-v, vmax=v, random_state=1234) + r6, r7, r8 = gen6.rvs(), gen7.rvs(1), gen8.rvs((1,)) + assert_equal(r6, r7) + assert_equal(r7, r8) + + def test_random_state(self): + f = stats.norm.pdf + v = np.sqrt(f(np.sqrt(2))) * np.sqrt(2) + umax = np.sqrt(f(0)) + gen1 = RatioUniforms(f, umax=umax, vmin=-v, vmax=v, random_state=1234) + r1 = gen1.rvs(10) + rng = np.random.RandomState(1234) + gen2 = RatioUniforms(f, umax=umax, vmin=-v, vmax=v, random_state=rng) + r2 = gen2.rvs(10) + assert_equal(r1, r2) + + def test_exceptions(self): + f = stats.norm.pdf + # need vmin < vmax + with assert_raises(ValueError, match="vmin must be smaller than vmax"): + RatioUniforms(pdf=f, umax=1, vmin=3, vmax=1) + with assert_raises(ValueError, match="vmin must be smaller than vmax"): + RatioUniforms(pdf=f, umax=1, vmin=1, vmax=1) + # need umax > 0 + with assert_raises(ValueError, match="umax must be positive"): + RatioUniforms(pdf=f, umax=-1, vmin=1, vmax=3) + with assert_raises(ValueError, match="umax must be positive"): + RatioUniforms(pdf=f, umax=0, vmin=1, vmax=3) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/test_sensitivity_analysis.py b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_sensitivity_analysis.py new file mode 100644 index 0000000000000000000000000000000000000000..9c8d53040380808c03358b905878cc4a8b9b9679 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_sensitivity_analysis.py @@ -0,0 +1,310 @@ +import numpy as np +from numpy.testing import assert_allclose, assert_array_less +import pytest + +from scipy import stats +from scipy.stats import sobol_indices +from scipy.stats._resampling import BootstrapResult +from scipy.stats._sensitivity_analysis import ( + BootstrapSobolResult, f_ishigami, sample_AB, sample_A_B +) + + +@pytest.fixture(scope='session') +def ishigami_ref_indices(): + """Reference values for Ishigami from Saltelli2007. + + Chapter 4, exercise 5 pages 179-182. + """ + a = 7. + b = 0.1 + + var = 0.5 + a**2/8 + b*np.pi**4/5 + b**2*np.pi**8/18 + v1 = 0.5 + b*np.pi**4/5 + b**2*np.pi**8/50 + v2 = a**2/8 + v3 = 0 + v12 = 0 + # v13: mistake in the book, see other derivations e.g. in 10.1002/nme.4856 + v13 = b**2*np.pi**8*8/225 + v23 = 0 + + s_first = np.array([v1, v2, v3])/var + s_second = np.array([ + [0., 0., v13], + [v12, 0., v23], + [v13, v23, 0.] + ])/var + s_total = s_first + s_second.sum(axis=1) + + return s_first, s_total + + +def f_ishigami_vec(x): + """Output of shape (2, n).""" + res = f_ishigami(x) + return res, res + + +class TestSobolIndices: + + dists = [ + stats.uniform(loc=-np.pi, scale=2*np.pi) # type: ignore[attr-defined] + ] * 3 + + def test_sample_AB(self): + # (d, n) + A = np.array( + [[1, 4, 7, 10], + [2, 5, 8, 11], + [3, 6, 9, 12]] + ) + B = A + 100 + # (d, d, n) + ref = np.array( + [[[101, 104, 107, 110], + [2, 5, 8, 11], + [3, 6, 9, 12]], + [[1, 4, 7, 10], + [102, 105, 108, 111], + [3, 6, 9, 12]], + [[1, 4, 7, 10], + [2, 5, 8, 11], + [103, 106, 109, 112]]] + ) + AB = sample_AB(A=A, B=B) + assert_allclose(AB, ref) + + @pytest.mark.xslow + @pytest.mark.xfail_on_32bit("Can't create large array for test") + @pytest.mark.parametrize( + 'func', + [f_ishigami, pytest.param(f_ishigami_vec, marks=pytest.mark.slow)], + ids=['scalar', 'vector'] + ) + def test_ishigami(self, ishigami_ref_indices, func): + rng = np.random.default_rng(28631265345463262246170309650372465332) + res = sobol_indices( + func=func, n=4096, + dists=self.dists, + rng=rng + ) + + if func.__name__ == 'f_ishigami_vec': + ishigami_ref_indices = [ + [ishigami_ref_indices[0], ishigami_ref_indices[0]], + [ishigami_ref_indices[1], ishigami_ref_indices[1]] + ] + + assert_allclose(res.first_order, ishigami_ref_indices[0], atol=1e-2) + assert_allclose(res.total_order, ishigami_ref_indices[1], atol=1e-2) + + assert res._bootstrap_result is None + bootstrap_res = res.bootstrap(n_resamples=99) + assert isinstance(bootstrap_res, BootstrapSobolResult) + assert isinstance(res._bootstrap_result, BootstrapResult) + + assert res._bootstrap_result.confidence_interval.low.shape[0] == 2 + assert res._bootstrap_result.confidence_interval.low[1].shape \ + == res.first_order.shape + + assert bootstrap_res.first_order.confidence_interval.low.shape \ + == res.first_order.shape + assert bootstrap_res.total_order.confidence_interval.low.shape \ + == res.total_order.shape + + assert_array_less( + bootstrap_res.first_order.confidence_interval.low, res.first_order + ) + assert_array_less( + res.first_order, bootstrap_res.first_order.confidence_interval.high + ) + assert_array_less( + bootstrap_res.total_order.confidence_interval.low, res.total_order + ) + assert_array_less( + res.total_order, bootstrap_res.total_order.confidence_interval.high + ) + + # call again to use previous results and change a param + assert isinstance( + res.bootstrap(confidence_level=0.9, n_resamples=99), + BootstrapSobolResult + ) + assert isinstance(res._bootstrap_result, BootstrapResult) + + def test_func_dict(self, ishigami_ref_indices): + rng = np.random.default_rng(28631265345463262246170309650372465332) + n = 4096 + dists = [ + stats.uniform(loc=-np.pi, scale=2*np.pi), + stats.uniform(loc=-np.pi, scale=2*np.pi), + stats.uniform(loc=-np.pi, scale=2*np.pi) + ] + + A, B = sample_A_B(n=n, dists=dists, rng=rng) + AB = sample_AB(A=A, B=B) + + func = { + 'f_A': f_ishigami(A).reshape(1, -1), + 'f_B': f_ishigami(B).reshape(1, -1), + 'f_AB': f_ishigami(AB).reshape((3, 1, -1)) + } + + # preserve use of old random_state during SPEC 7 transition + res = sobol_indices( + func=func, n=n, + dists=dists, + rng=rng + ) + assert_allclose(res.first_order, ishigami_ref_indices[0], atol=1e-2) + + res = sobol_indices( + func=func, n=n, + rng=rng + ) + assert_allclose(res.first_order, ishigami_ref_indices[0], atol=1e-2) + # Ideally should be exactly equal but since f_ishigami + # uses floating point operations, so exact equality + # might not be possible (due to flakiness in computation). + # So, assert_allclose is used with default parameters + # Regression test for https://github.com/scipy/scipy/issues/21383 + assert_allclose(f_ishigami(A).reshape(1, -1), func['f_A']) + assert_allclose(f_ishigami(B).reshape(1, -1), func['f_B']) + assert_allclose(f_ishigami(AB).reshape((3, 1, -1)), func['f_AB']) + + def test_method(self, ishigami_ref_indices): + def jansen_sobol(f_A, f_B, f_AB): + """Jansen for S and Sobol' for St. + + From Saltelli2010, table 2 formulations (c) and (e).""" + var = np.var([f_A, f_B], axis=(0, -1)) + + s = (var - 0.5*np.mean((f_B - f_AB)**2, axis=-1)) / var + st = np.mean(f_A*(f_A - f_AB), axis=-1) / var + + return s.T, st.T + + rng = np.random.default_rng(28631265345463262246170309650372465332) + res = sobol_indices( + func=f_ishigami, n=4096, + dists=self.dists, + method=jansen_sobol, + rng=rng + ) + + assert_allclose(res.first_order, ishigami_ref_indices[0], atol=1e-2) + assert_allclose(res.total_order, ishigami_ref_indices[1], atol=1e-2) + + def jansen_sobol_typed( + f_A: np.ndarray, f_B: np.ndarray, f_AB: np.ndarray + ) -> tuple[np.ndarray, np.ndarray]: + return jansen_sobol(f_A, f_B, f_AB) + + _ = sobol_indices( + func=f_ishigami, n=8, + dists=self.dists, + method=jansen_sobol_typed, + rng=rng + ) + + def test_normalization(self, ishigami_ref_indices): + rng = np.random.default_rng(28631265345463262246170309650372465332) + res = sobol_indices( + func=lambda x: f_ishigami(x) + 1000, n=4096, + dists=self.dists, + rng=rng + ) + + assert_allclose(res.first_order, ishigami_ref_indices[0], atol=1e-2) + assert_allclose(res.total_order, ishigami_ref_indices[1], atol=1e-2) + + def test_constant_function(self, ishigami_ref_indices): + + def f_ishigami_vec_const(x): + """Output of shape (3, n).""" + res = f_ishigami(x) + return res, res * 0 + 10, res + + rng = np.random.default_rng(28631265345463262246170309650372465332) + res = sobol_indices( + func=f_ishigami_vec_const, n=4096, + dists=self.dists, + rng=rng + ) + + ishigami_vec_indices = [ + [ishigami_ref_indices[0], [0, 0, 0], ishigami_ref_indices[0]], + [ishigami_ref_indices[1], [0, 0, 0], ishigami_ref_indices[1]] + ] + + assert_allclose(res.first_order, ishigami_vec_indices[0], atol=1e-2) + assert_allclose(res.total_order, ishigami_vec_indices[1], atol=1e-2) + + @pytest.mark.xfail_on_32bit("Can't create large array for test") + def test_more_converged(self, ishigami_ref_indices): + rng = np.random.default_rng(28631265345463262246170309650372465332) + res = sobol_indices( + func=f_ishigami, n=2**19, # 524288 + dists=self.dists, + rng=rng + ) + + assert_allclose(res.first_order, ishigami_ref_indices[0], atol=1e-4) + assert_allclose(res.total_order, ishigami_ref_indices[1], atol=1e-4) + + def test_raises(self): + + message = r"Each distribution in `dists` must have method `ppf`" + with pytest.raises(ValueError, match=message): + sobol_indices(n=0, func=f_ishigami, dists="uniform") + + with pytest.raises(ValueError, match=message): + sobol_indices(n=0, func=f_ishigami, dists=[lambda x: x]) + + message = r"The balance properties of Sobol'" + with pytest.raises(ValueError, match=message): + sobol_indices(n=7, func=f_ishigami, dists=[stats.uniform()]) + + with pytest.raises(ValueError, match=message): + sobol_indices(n=4.1, func=f_ishigami, dists=[stats.uniform()]) + + message = r"'toto' is not a valid 'method'" + with pytest.raises(ValueError, match=message): + sobol_indices(n=0, func=f_ishigami, method='toto') + + message = r"must have the following signature" + with pytest.raises(ValueError, match=message): + sobol_indices(n=0, func=f_ishigami, method=lambda x: x) + + message = r"'dists' must be defined when 'func' is a callable" + with pytest.raises(ValueError, match=message): + sobol_indices(n=0, func=f_ishigami) + + def func_wrong_shape_output(x): + return x.reshape(-1, 1) + + message = r"'func' output should have a shape" + with pytest.raises(ValueError, match=message): + sobol_indices( + n=2, func=func_wrong_shape_output, dists=[stats.uniform()] + ) + + message = r"When 'func' is a dictionary" + with pytest.raises(ValueError, match=message): + sobol_indices( + n=2, func={'f_A': [], 'f_AB': []}, dists=[stats.uniform()] + ) + + with pytest.raises(ValueError, match=message): + # f_B malformed + sobol_indices( + n=2, + func={'f_A': [1, 2], 'f_B': [3], 'f_AB': [5, 6, 7, 8]}, + ) + + with pytest.raises(ValueError, match=message): + # f_AB malformed + sobol_indices( + n=2, + func={'f_A': [1, 2], 'f_B': [3, 4], 'f_AB': [5, 6, 7]}, + ) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/test_stats.py b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_stats.py new file mode 100644 index 0000000000000000000000000000000000000000..0ac5db382f04d249b6c05d4f0e940fe75d26486a --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_stats.py @@ -0,0 +1,9890 @@ +""" Test functions for stats module + + WRITTEN BY LOUIS LUANGKESORN FOR THE STATS MODULE + BASED ON WILKINSON'S STATISTICS QUIZ + https://www.stanford.edu/~clint/bench/wilk.txt + + Additional tests by a host of SciPy developers. +""" +import math +import os +import warnings +from collections import namedtuple +from itertools import product +import hypothesis.extra.numpy as npst +import hypothesis +import contextlib + +from numpy.testing import (assert_, assert_equal, + assert_almost_equal, assert_array_almost_equal, + assert_array_equal, assert_approx_equal, + assert_allclose) +import pytest +from pytest import raises as assert_raises +from numpy import array, arange, power +import numpy as np + +import scipy.stats as stats +from scipy.stats._ksstats import kolmogn +from scipy.special._testutils import FuncData +from scipy import optimize, special +from .common_tests import check_named_results +from scipy.stats._axis_nan_policy import (SmallSampleWarning, + too_small_nd_omit, too_small_nd_not_omit, + too_small_1d_omit, too_small_1d_not_omit) +from scipy.stats._stats_py import (_chk_asarray, _moment, + LinregressResult, _xp_mean, _xp_var, _SimpleChi2) +from scipy._lib._util import AxisError +from scipy.conftest import skip_xp_invalid_arg +from scipy._lib._array_api import (array_namespace, eager_warns, is_lazy_array, + is_numpy, is_torch, xp_default_dtype, xp_size, + SCIPY_ARRAY_API, make_xp_test_case, xp_ravel, + xp_swapaxes, xp_result_type, is_jax, is_cupy, + xp_copy, xp_promote, make_xp_pytest_param) +from scipy._lib._array_api_no_0d import xp_assert_close, xp_assert_equal, xp_assert_less +import scipy._external.array_api_extra as xpx +from scipy._lib._util import _apply_over_batch + + +lazy_xp_modules = [stats] +skip_xp_backends = pytest.mark.skip_xp_backends +xfail_xp_backends = pytest.mark.xfail_xp_backends + +""" Numbers in docstrings beginning with 'W' refer to the section numbers + and headings found in the STATISTICS QUIZ of Leland Wilkinson. These are + considered to be essential functionality. True testing and + evaluation of a statistics package requires use of the + NIST Statistical test data. See McCoullough(1999) Assessing The Reliability + of Statistical Software for a test methodology and its + implementation in testing SAS, SPSS, and S-Plus +""" + +# Datasets +# These data sets are from the nasty.dat sets used by Wilkinson +# For completeness, I should write the relevant tests and count them as failures +# Somewhat acceptable, since this is still beta software. It would count as a +# good target for 1.0 status +X = array([1,2,3,4,5,6,7,8,9], float) +ZERO = array([0,0,0,0,0,0,0,0,0], float) +BIG = array([99999991,99999992,99999993,99999994,99999995,99999996,99999997, + 99999998,99999999], float) +LITTLE = array([0.99999991,0.99999992,0.99999993,0.99999994,0.99999995,0.99999996, + 0.99999997,0.99999998,0.99999999], float) +HUGE = array([1e+12,2e+12,3e+12,4e+12,5e+12,6e+12,7e+12,8e+12,9e+12], float) +TINY = array([1e-12,2e-12,3e-12,4e-12,5e-12,6e-12,7e-12,8e-12,9e-12], float) +ROUND = array([0.5,1.5,2.5,3.5,4.5,5.5,6.5,7.5,8.5], float) + + +class TestTrimmedStats: + # TODO: write these tests to handle missing values properly + dprec = np.finfo(np.float64).precision + + @make_xp_test_case(stats.tmean) + def test_tmean(self, xp): + default_dtype = xp_default_dtype(xp) + x = xp.asarray(X, dtype=default_dtype) + + y = stats.tmean(x, (2, 8), (True, True)) + xp_assert_close(y, xp.asarray(5.0)) + + y1 = stats.tmean(x, limits=(2, 8), inclusive=(False, False)) + y2 = stats.tmean(x, limits=None) + xp_assert_close(y1, y2) + + x_2d = xp.reshape(xp.arange(63.), (9, 7)) + y = stats.tmean(x_2d, axis=None) + xp_assert_close(y, xp.mean(x_2d)) + + y = stats.tmean(x_2d, axis=0) + xp_assert_close(y, xp.mean(x_2d, axis=0)) + + y = stats.tmean(x_2d, axis=1) + xp_assert_close(y, xp.mean(x_2d, axis=1)) + + y = stats.tmean(x_2d, limits=(2, 61), axis=None) + xp_assert_close(y, xp.asarray(31.5)) + + y = stats.tmean(x_2d, limits=(2, 21), axis=0) + y_true = [14, 11.5, 9, 10, 11, 12, 13] + xp_assert_close(y, xp.asarray(y_true)) + + y = stats.tmean(x_2d, limits=(2, 21), inclusive=(True, False), axis=0) + y_true = [10.5, 11.5, 9, 10, 11, 12, 13] + xp_assert_close(y, xp.asarray(y_true)) + + x_2d_with_nan = xpx.at(x_2d)[-1, -3:].set(xp.nan, copy=True) + y = stats.tmean(x_2d_with_nan, limits=(1, 13), axis=0) + y_true = [7, 4.5, 5.5, 6.5, xp.nan, xp.nan, xp.nan] + xp_assert_close(y, xp.asarray(y_true)) + + y = stats.tmean(x_2d, limits=(2, 21), axis=1) + y_true = [4, 10, 17, 21, xp.nan, xp.nan, xp.nan, xp.nan, xp.nan] + xp_assert_close(y, xp.asarray(y_true)) + + y = stats.tmean(x_2d, limits=(2, 21), + inclusive=(False, True), axis=1) + y_true = [4.5, 10, 17, 21, xp.nan, xp.nan, xp.nan, xp.nan, xp.nan] + xp_assert_close(y, xp.asarray(y_true)) + + @make_xp_test_case(stats.tvar) + @pytest.mark.filterwarnings( + "ignore:invalid value encountered in divide:RuntimeWarning:dask" + ) + def test_tvar(self, xp): + x = xp.asarray(X.tolist()) # use default dtype of xp + + y = stats.tvar(x, limits=(2, 8), inclusive=(True, True)) + xp_assert_close(y, xp.asarray(4.6666666666666661)) + + y = stats.tvar(x, limits=None) + xp_assert_close(y, xp.var(x, correction=1)) + + x_2d = xp.reshape(xp.arange(63.), (9, 7)) + y = stats.tvar(x_2d, axis=None) + xp_assert_close(y, xp.var(x_2d, correction=1)) + + y = stats.tvar(x_2d, axis=0) + xp_assert_close(y, xp.full((7,), 367.5)) + + y = stats.tvar(x_2d, axis=1) + xp_assert_close(y, xp.full((9,), 4.66666667)) + + # Limiting some values along one axis + y = stats.tvar(x_2d, limits=(1, 5), axis=1, inclusive=(True, True)) + xp_assert_close(y[0], xp.asarray(2.5)) + + # Limiting all values along one axis + y = stats.tvar(x_2d, limits=(0, 6), axis=1, inclusive=(True, True)) + xp_assert_close(y[0], xp.asarray(4.666666666666667)) + xp_assert_equal(y[1], xp.asarray(xp.nan)) + + @make_xp_test_case(stats.tstd) + def test_tstd(self, xp): + x = xp.asarray(X.tolist()) # use default dtype of xp + + y = stats.tstd(x, (2, 8), (True, True)) + xp_assert_close(y, xp.asarray(2.1602468994692865)) + + y = stats.tstd(x, limits=None) + xp_assert_close(y, xp.std(x, correction=1)) + + @make_xp_test_case(stats.tmin) + def test_tmin(self, xp): + x = xp.arange(10.) + xp_assert_equal(stats.tmin(x), xp.asarray(0.)) + xp_assert_equal(stats.tmin(x, lowerlimit=0), xp.asarray(0.)) + xp_assert_equal(stats.tmin(x, lowerlimit=0, inclusive=False), xp.asarray(1.)) + + x = xp.reshape(x, (5, 2)) + xp_assert_equal(stats.tmin(x, lowerlimit=0, inclusive=False), + xp.asarray([2., 1.])) + xp_assert_equal(stats.tmin(x, axis=1), xp.asarray([0., 2., 4., 6., 8.])) + xp_assert_equal(stats.tmin(x, axis=None), xp.asarray(0.)) + + x = xpx.at(xp.arange(10.), 9).set(xp.nan) + xp_assert_equal(stats.tmin(x), xp.asarray(xp.nan)) + + # check that if a full slice is masked, the output returns a + # nan instead of a garbage value. + x = xp.reshape(xp.arange(16), (4, 4)) + res = stats.tmin(x, lowerlimit=4, axis=1) + xp_assert_equal(res, xp.asarray([np.nan, 4, 8, 12])) + + @pytest.mark.parametrize('tfun', [stats.tmin, stats.tmax]) + def test_tmin_tmax_scalar_input(self, tfun): # scalar inputs are NumPy only + assert_equal(tfun(4), 4) + + @skip_xp_backends(eager_only=True, reason="lazy -> reduced nan_policy capabilities") + @pytest.mark.parametrize('tfun, ref', [make_xp_pytest_param(stats.tmin, 0.), + make_xp_pytest_param(stats.tmax, 9.)]) + def test_tmin_tmax_nanpolicy(self, tfun, ref, xp): + x = xp.arange(10.) + x[8] = xp.nan + xp_assert_equal(tfun(x, nan_policy='omit'), xp.asarray(ref)) + msg = "The input contains nan values" + with assert_raises(ValueError, match=msg): + tfun(x, nan_policy='raise') + msg = "nan_policy must be one of..." + with assert_raises(ValueError, match=msg): + tfun(x, nan_policy='foobar') + + @make_xp_test_case(stats.tmax) + def test_tmax(self, xp): + x = xp.arange(10.) + xp_assert_equal(stats.tmax(x), xp.asarray(9.)) + xp_assert_equal(stats.tmax(x, upperlimit=9), xp.asarray(9.)) + xp_assert_equal(stats.tmax(x, upperlimit=9, inclusive=False), xp.asarray(8.)) + + x = xp.reshape(x, (5, 2)) + xp_assert_equal(stats.tmax(x, upperlimit=9, inclusive=False), + xp.asarray([8., 7.])) + xp_assert_equal(stats.tmax(x, axis=1), xp.asarray([1., 3., 5., 7., 9.])) + xp_assert_equal(stats.tmax(x, axis=None), xp.asarray(9.)) + + x = xpx.at(xp.arange(10.), 9).set(xp.nan) + xp_assert_equal(stats.tmax(x), xp.asarray(xp.nan)) + + # check that if a full slice is masked, the output returns a + # nan instead of a garbage value. + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", "All-NaN slice encountered", RuntimeWarning) + x = xp.reshape(xp.arange(16), (4, 4)) + res = stats.tmax(x, upperlimit=11, axis=1) + xp_assert_equal(res, xp.asarray([3, 7, 11, np.nan])) + + @make_xp_test_case(stats.tmin, stats.tmax) + def test_tmin_tmax_int_dtype(self, xp): + x = xp.reshape(xp.arange(10, dtype=xp.int16), (2, 5)).T + + # When tmin/tmax don't need to inject any NaNs, + # retain the input dtype. Dask/JAX can't inspect + # the data so they always return float. + expect_dtype = xp_default_dtype(xp) if is_lazy_array(x) else x.dtype + xp_assert_equal(stats.tmin(x), xp.asarray([0, 5], dtype=expect_dtype)) + xp_assert_equal(stats.tmax(x), xp.asarray([4, 9], dtype=expect_dtype)) + + # When they do inject NaNs, all backends behave the same. + xp_assert_equal(stats.tmin(x, lowerlimit=6), xp.asarray([xp.nan, 6.])) + xp_assert_equal(stats.tmax(x, upperlimit=3), xp.asarray([3., xp.nan])) + + @skip_xp_backends(eager_only=True, reason="Only with data-dependent output dtype") + @make_xp_test_case(stats.tmin, stats.tmax) + def test_gh_22626(self, xp): + # Test that `tmin`/`tmax` returns exact result with outrageously large integers + x = xp.arange(2**62, 2**62+10) + xp_assert_equal(stats.tmin(x[None, :]), x) + xp_assert_equal(stats.tmax(x[None, :]), x) + + @make_xp_test_case(stats.tsem) + def test_tsem(self, xp): + x = xp.asarray(X.tolist()) # use default dtype of xp + + y = stats.tsem(x, limits=(3, 8), inclusive=(False, True)) + y_ref = xp.asarray([4., 5., 6., 7., 8.]) + xp_assert_close(y, xp.std(y_ref, correction=1) / xp_size(y_ref)**0.5) + xp_assert_close(stats.tsem(x, limits=[-1, 10]), stats.tsem(x, limits=None)) + + +class TestPearsonrWilkinson: + """ W.II.D. Compute a correlation matrix on all the variables. + + All the correlations, except for ZERO and MISS, should be exactly 1. + ZERO and MISS should have undefined or missing correlations with the + other variables. The same should go for SPEARMAN correlations, if + your program has them. + """ + + def test_pXX(self): + y = stats.pearsonr(X,X) + r = y[0] + assert_approx_equal(r,1.0) + + def test_pXBIG(self): + y = stats.pearsonr(X,BIG) + r = y[0] + assert_approx_equal(r,1.0) + + def test_pXLITTLE(self): + y = stats.pearsonr(X,LITTLE) + r = y[0] + assert_approx_equal(r,1.0) + + def test_pXHUGE(self): + y = stats.pearsonr(X,HUGE) + r = y[0] + assert_approx_equal(r,1.0) + + def test_pXTINY(self): + y = stats.pearsonr(X,TINY) + r = y[0] + assert_approx_equal(r,1.0) + + def test_pXROUND(self): + y = stats.pearsonr(X,ROUND) + r = y[0] + assert_approx_equal(r,1.0) + + def test_pBIGBIG(self): + y = stats.pearsonr(BIG,BIG) + r = y[0] + assert_approx_equal(r,1.0) + + def test_pBIGLITTLE(self): + y = stats.pearsonr(BIG,LITTLE) + r = y[0] + assert_approx_equal(r,1.0) + + def test_pBIGHUGE(self): + y = stats.pearsonr(BIG,HUGE) + r = y[0] + assert_approx_equal(r,1.0) + + def test_pBIGTINY(self): + y = stats.pearsonr(BIG,TINY) + r = y[0] + assert_approx_equal(r,1.0) + + def test_pBIGROUND(self): + y = stats.pearsonr(BIG,ROUND) + r = y[0] + assert_approx_equal(r,1.0) + + def test_pLITTLELITTLE(self): + y = stats.pearsonr(LITTLE,LITTLE) + r = y[0] + assert_approx_equal(r,1.0) + + def test_pLITTLEHUGE(self): + y = stats.pearsonr(LITTLE,HUGE) + r = y[0] + assert_approx_equal(r,1.0) + + def test_pLITTLETINY(self): + y = stats.pearsonr(LITTLE,TINY) + r = y[0] + assert_approx_equal(r,1.0) + + def test_pLITTLEROUND(self): + y = stats.pearsonr(LITTLE,ROUND) + r = y[0] + assert_approx_equal(r,1.0) + + def test_pHUGEHUGE(self): + y = stats.pearsonr(HUGE,HUGE) + r = y[0] + assert_approx_equal(r,1.0) + + def test_pHUGETINY(self): + y = stats.pearsonr(HUGE,TINY) + r = y[0] + assert_approx_equal(r,1.0) + + def test_pHUGEROUND(self): + y = stats.pearsonr(HUGE,ROUND) + r = y[0] + assert_approx_equal(r,1.0) + + def test_pTINYTINY(self): + y = stats.pearsonr(TINY,TINY) + r = y[0] + assert_approx_equal(r,1.0) + + def test_pTINYROUND(self): + y = stats.pearsonr(TINY,ROUND) + r = y[0] + assert_approx_equal(r,1.0) + + def test_pROUNDROUND(self): + y = stats.pearsonr(ROUND,ROUND) + r = y[0] + assert_approx_equal(r,1.0) + + +@make_xp_test_case(stats.pearsonr) +class TestPearsonr: + def test_pearsonr_result_attributes(self, xp): + res = stats.pearsonr(xp.asarray(X), xp.asarray(X)) + attributes = ('correlation', 'pvalue') + check_named_results(res, attributes, xp=xp) + xp_assert_equal(res.correlation, res.statistic) + + def test_r_almost_exactly_pos1(self, xp): + a = xp.arange(3.0) + r, prob = stats.pearsonr(a, a) + + xp_assert_close(r, xp.asarray(1.0), atol=1e-15) + # With n = len(a) = 3, the error in prob grows like the + # square root of the error in r. + xp_assert_close(prob, xp.asarray(0.0), atol=np.sqrt(2*np.spacing(1.0))) + + def test_r_almost_exactly_neg1(self, xp): + a = xp.arange(3.0) + r, prob = stats.pearsonr(a, -a) + + xp_assert_close(r, xp.asarray(-1.0), atol=1e-15) + # With n = len(a) = 3, the error in prob grows like the + # square root of the error in r. + xp_assert_close(prob, xp.asarray(0.0), atol=np.sqrt(2*np.spacing(1.0))) + + def test_basic(self, xp): + # A basic test, with a correlation coefficient + # that is not 1 or -1. + a = xp.asarray([-1, 0, 1]) + b = xp.asarray([0, 0, 3]) + r, prob = stats.pearsonr(a, b) + xp_assert_close(r, xp.asarray(3**0.5/2)) + xp_assert_close(prob, xp.asarray(1/3)) + + def test_constant_input(self, xp): + # Zero variance input + # See https://github.com/scipy/scipy/issues/3728 + x = xp.asarray([0.667, 0.667, 0.667]) + y = xp.asarray([0.123, 0.456, 0.789]) + msg = "An input array is constant" + with eager_warns(stats.ConstantInputWarning, match=msg, xp=xp): + r, p = stats.pearsonr(x, y) + xp_assert_close(r, xp.asarray(xp.nan)) + xp_assert_close(p, xp.asarray(xp.nan)) + + @pytest.mark.parametrize('dtype', ['float32', 'float64']) + def test_near_constant_input(self, dtype, xp): + npdtype = getattr(np, dtype) + dtype = getattr(xp, dtype) + # Near constant input (but not constant): + x = xp.asarray([2, 2, 2 + np.spacing(2, dtype=npdtype)], dtype=dtype) + y = xp.asarray([3, 3, 3 + 6*np.spacing(3, dtype=npdtype)], dtype=dtype) + msg = "An input array is nearly constant; the computed" + with eager_warns(stats.NearConstantInputWarning, match=msg, xp=xp): + # r and p are garbage, so don't bother checking them in this case. + # (The exact value of r would be 1.) + stats.pearsonr(x, y) + + def test_very_small_input_values(self, xp): + # Very small values in an input. A naive implementation will + # suffer from underflow. + # See https://github.com/scipy/scipy/issues/9353 + x = xp.asarray([0.004434375, 0.004756007, 0.003911996, 0.0038005, 0.003409971], + dtype=xp.float64) + y = xp.asarray([2.48e-188, 7.41e-181, 4.09e-208, 2.08e-223, 2.66e-245], + dtype=xp.float64) + r, p = stats.pearsonr(x, y) + + # The expected values were computed using mpmath with 80 digits + # of precision. + xp_assert_close(r, xp.asarray(0.7272930540750450, dtype=xp.float64)) + xp_assert_close(p, xp.asarray(0.1637805429533202, dtype=xp.float64)) + + def test_very_large_input_values(self, xp): + # Very large values in an input. A naive implementation will + # suffer from overflow. + # See https://github.com/scipy/scipy/issues/8980 + x = 1e90*xp.asarray([0, 0, 0, 1, 1, 1, 1], dtype=xp.float64) + y = 1e90*xp.arange(7, dtype=xp.float64) + + r, p = stats.pearsonr(x, y) + + # The expected values were computed using mpmath with 80 digits + # of precision. + xp_assert_close(r, xp.asarray(0.8660254037844386, dtype=xp.float64)) + xp_assert_close(p, xp.asarray(0.011724811003954638, dtype=xp.float64)) + + def test_extremely_large_input_values(self, xp): + # Extremely large values in x and y. These values would cause the + # product sigma_x * sigma_y to overflow if the two factors were + # computed independently. + x = xp.asarray([2.3e200, 4.5e200, 6.7e200, 8e200], dtype=xp.float64) + y = xp.asarray([1.2e199, 5.5e200, 3.3e201, 1.0e200], dtype=xp.float64) + r, p = stats.pearsonr(x, y) + + # The expected values were computed using mpmath with 80 digits + # of precision. + xp_assert_close(r, xp.asarray(0.351312332103289, dtype=xp.float64)) + xp_assert_close(p, xp.asarray(0.648687667896711, dtype=xp.float64)) + + @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") + @pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning:dask") + def test_length_two_pos1(self, xp): + # Inputs with length 2. + # See https://github.com/scipy/scipy/issues/7730 + x = xp.asarray([1., 2.]) + y = xp.asarray([3., 5.]) + res = stats.pearsonr(x, y) + r, p = res + one = xp.asarray(1.) + xp_assert_equal(r, one) + xp_assert_equal(p, one) + low, high = res.confidence_interval() + xp_assert_equal(low, -one) + xp_assert_equal(high, one) + + @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") + @pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning:dask") + def test_length_two_neg1(self, xp): + # Inputs with length 2. + # See https://github.com/scipy/scipy/issues/7730 + x = xp.asarray([2., 1.]) + y = xp.asarray([3., 5.]) + res = stats.pearsonr(x, y) + r, p = res + one = xp.asarray(1.) + xp_assert_equal(r, -one) + xp_assert_equal(p, one) + low, high = res.confidence_interval() + xp_assert_equal(low, -one) + xp_assert_equal(high, one) + + @pytest.mark.filterwarnings("ignore:invalid value encountered in divide") + def test_length_two_constant_input(self, xp): + # Zero variance input + # See https://github.com/scipy/scipy/issues/3728 + # and https://github.com/scipy/scipy/issues/7730 + x = xp.asarray([0.667, 0.667]) + y = xp.asarray([0.123, 0.456]) + msg = "An input array is constant" + with eager_warns(stats.ConstantInputWarning, match=msg, xp=xp): + r, p = stats.pearsonr(x, y) + xp_assert_close(r, xp.asarray(xp.nan)) + xp_assert_close(p, xp.asarray(xp.nan)) + + # Expected values computed with R 3.6.2 cor.test, e.g. + # options(digits=16) + # x <- c(1, 2, 3, 4) + # y <- c(0, 1, 0.5, 1) + # cor.test(x, y, method = "pearson", alternative = "g") + # correlation coefficient and p-value for alternative='two-sided' + # calculated with mpmath agree to 16 digits. + @pytest.mark.parametrize('alternative, pval, rlow, rhigh, sign', + [('two-sided', 0.325800137536, -0.814938968841, 0.99230697523, 1), + ('less', 0.8370999312316, -1., 0.985600937290653, 1), + ('greater', 0.1629000687684, -0.6785654158217636, 1., 1), + ('two-sided', 0.325800137536, -0.992306975236, 0.81493896884, -1), + ('less', 0.1629000687684, -1.0, 0.6785654158217636, -1), + ('greater', 0.8370999312316, -0.985600937290653, 1.0, -1)]) + def test_basic_example(self, alternative, pval, rlow, rhigh, sign, xp): + x = xp.asarray([1., 2., 3., 4.]) + y = xp.asarray([0., 1., 0.5, 1.]) * sign + result = stats.pearsonr(x, y, alternative=alternative) + xp_assert_close(result.statistic, xp.asarray(0.6741998624632421*sign)) + xp_assert_close(result.pvalue, xp.asarray(pval)) + ci = result.confidence_interval() + xp_assert_close(ci.low, xp.asarray(rlow)) + xp_assert_close(ci.high, xp.asarray(rhigh)) + + def test_negative_correlation_pvalue_gh17795(self, xp): + x = xp.arange(10.) + y = -x + test_greater = stats.pearsonr(x, y, alternative='greater') + test_less = stats.pearsonr(x, y, alternative='less') + xp_assert_close(test_greater.pvalue, xp.asarray(1.)) + xp_assert_close(test_less.pvalue, xp.asarray(0.), atol=1e-20) + + @pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning:dask") + def test_length3_r_exactly_negative_one(self, xp): + x = xp.asarray([1., 2., 3.]) + y = xp.asarray([5., -4., -13.]) + res = stats.pearsonr(x, y) + + # The expected r and p are exact. + r, p = res + one = xp.asarray(1.0) + xp_assert_close(r, -one) + xp_assert_close(p, 0*one, atol=1e-7) + low, high = res.confidence_interval() + xp_assert_equal(low, -one) + xp_assert_equal(high, one) + + def test_input_validation(self): + # Arraylike is np only + x = [1, 2, 3] + y = [4] + message = '`x` and `y` must have the same length along `axis`.' + with pytest.raises(ValueError, match=message): + stats.pearsonr(x, y) + + x = [1, 2, 3] + y = [4, 5] + message = '`x` and `y` must be broadcastable.' + with pytest.raises(ValueError, match=message): + stats.pearsonr(x, y) + + x = [1] + y = [2] + message = '`x` and `y` must have length at least 2.' + with pytest.raises(ValueError, match=message): + stats.pearsonr(x, y) + + x = [-1j, -2j, -3.0j] + y = [-1j, -2j, -3.0j] + message = 'This function does not support complex data' + with pytest.raises(ValueError, match=message): + stats.pearsonr(x, y) + + message = "`method` must be an instance of..." + with pytest.raises(ValueError, match=message): + stats.pearsonr([1, 2], [3, 4], method="asymptotic") + + res = stats.pearsonr([1, 2], [3, 4]) + with pytest.raises(ValueError, match=message): + res.confidence_interval(method="exact") + + @pytest.mark.fail_slow(10) + @pytest.mark.xfail_on_32bit("Monte Carlo method needs > a few kB of memory") + @pytest.mark.parametrize('alternative', ('less', 'greater', 'two-sided')) + @pytest.mark.parametrize('method_name', + ('permutation', 'monte_carlo', 'monte_carlo2')) + def test_resampling_pvalue(self, method_name, alternative): + rng = np.random.default_rng(24623935790378923) + size = (2, 100) if method_name == 'permutation' else (2, 1000) + x = rng.normal(size=size) + y = rng.normal(size=size) + methods = {'permutation': stats.PermutationMethod(rng=rng), + 'monte_carlo': stats.MonteCarloMethod(rvs=(rng.normal,)*2), + 'monte_carlo2': stats.MonteCarloMethod(rng=1294)} + method = methods[method_name] + res = stats.pearsonr(x, y, alternative=alternative, method=method, axis=-1) + ref = stats.pearsonr(x, y, alternative=alternative, axis=-1) + assert_allclose(res.statistic, ref.statistic, rtol=1e-15) + assert_allclose(res.pvalue, ref.pvalue, rtol=1e-2, atol=1e-3) + + if method_name == 'monte_carlo2': + method = stats.MonteCarloMethod(rng=1294) + res2 = stats.pearsonr(x, y, alternative=alternative, method=method, axis=-1) + assert_equal(res2.statistic, res.statistic) + assert_equal(res2.pvalue, res.pvalue) + + @pytest.mark.slow + @pytest.mark.parametrize('alternative', ('less', 'greater', 'two-sided')) + def test_bootstrap_ci(self, alternative): + rng = np.random.default_rng(2462935790378923) + x = rng.normal(size=(2, 100)) + y = rng.normal(size=(2, 100)) + res = stats.pearsonr(x, y, alternative=alternative, axis=-1) + + # preserve use of old random_state during SPEC 7 transition + rng = np.random.default_rng(724358723498249852) + method = stats.BootstrapMethod(random_state=rng) + res_ci = res.confidence_interval(method=method) + ref_ci = res.confidence_interval() + assert_allclose(res_ci, ref_ci, atol=1.5e-2) + + # `rng` is the new argument name` + rng = np.random.default_rng(724358723498249852) + method = stats.BootstrapMethod(rng=rng) + res_ci2 = res.confidence_interval(method=method) + assert_allclose(res_ci2, res_ci) + + @pytest.mark.parametrize('axis', [0, 1]) + def test_axis01(self, axis): + rng = np.random.default_rng(38572345825) + shape = (9, 10) + x, y = rng.normal(size=(2,) + shape) + res = stats.pearsonr(x, y, axis=axis) + ci = res.confidence_interval() + if axis == 0: + x, y = x.T, y.T + for i in range(x.shape[0]): + res_i = stats.pearsonr(x[i], y[i]) + ci_i = res_i.confidence_interval() + assert_allclose(res.statistic[i], res_i.statistic) + assert_allclose(res.pvalue[i], res_i.pvalue) + assert_allclose(ci.low[i], ci_i.low) + assert_allclose(ci.high[i], ci_i.high) + + def test_axis_None(self): + rng = np.random.default_rng(38572345825) + shape = (9, 10) + x, y = rng.normal(size=(2,) + shape) + res = stats.pearsonr(x, y, axis=None) + ci = res.confidence_interval() + ref = stats.pearsonr(x.ravel(), y.ravel()) + ci_ref = ref.confidence_interval() + assert_allclose(res.statistic, ref.statistic) + assert_allclose(res.pvalue, ref.pvalue) + assert_allclose(ci, ci_ref) + + def test_nd_input_validation(self, xp): + x = y = xp.ones((2, 5)) + message = '`axis` must be an integer.' + with pytest.raises(ValueError, match=message): + stats.pearsonr(x, y, axis=1.5) + + message = '`x` and `y` must have the same length along `axis`' + with pytest.raises(ValueError, match=message): + stats.pearsonr(x, xp.ones((2, 1)), axis=1) + + message = '`x` and `y` must have length at least 2.' + with pytest.raises(ValueError, match=message): + stats.pearsonr(xp.ones((2, 1)), xp.ones((2, 1)), axis=1) + + message = '`x` and `y` must be broadcastable.' + with pytest.raises(ValueError, match=message): + stats.pearsonr(x, xp.ones((3, 5)), axis=1) + + message = '`method` must be `None` if arguments are not NumPy arrays.' + if not is_numpy(xp): + x = xp.arange(10) + with pytest.raises(ValueError, match=message): + stats.pearsonr(x, x, method=stats.PermutationMethod()) + + @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") + @pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning:dask") + def test_nd_special_cases(self, xp): + rng = np.random.default_rng(34989235492245) + + x0, y0 = rng.random((4, 5)), rng.random((4, 5)) + x0[0, ...] = 1 + y0[1, ...] = 2 + x, y = xp.asarray(x0), xp.asarray(y0) + message = 'An input array is constant' + with eager_warns(stats.ConstantInputWarning, match=message, xp=xp): + res = stats.pearsonr(x, y, axis=1) + ci = res.confidence_interval() + nans = xp.asarray([xp.nan, xp.nan], dtype=xp.float64) + xp_assert_equal(res.statistic[0:2], nans) + xp_assert_equal(res.pvalue[0:2], nans) + xp_assert_equal(ci.low[0:2], nans) + xp_assert_equal(ci.high[0:2], nans) + assert xp.all(xp.isfinite(res.statistic[2:])) + assert xp.all(xp.isfinite(res.pvalue[2:])) + assert xp.all(xp.isfinite(ci.low[2:])) + assert xp.all(xp.isfinite(ci.high[2:])) + + x0[0, 0], y0[1, 1] = 1 + 1e-15, 2 + 1e-15 + x, y = xp.asarray(x0), xp.asarray(y0) + message = 'An input array is nearly constant' + with eager_warns(stats.NearConstantInputWarning, match=message, xp=xp): + stats.pearsonr(x, y, axis=1) + + # length 2 along axis + x = xp.asarray([[1, 2], [1, 2], [2, 1], [2, 1.]]) + y = xp.asarray([[1, 2], [2, 1], [1, 2], [2, 1.]]) + ones = xp.ones(4) + res = stats.pearsonr(x, y, axis=-1) + ci = res.confidence_interval() + xp_assert_close(res.statistic, xp.asarray([1, -1, -1, 1.])) + xp_assert_close(res.pvalue, ones) + xp_assert_close(ci.low, -ones) + xp_assert_close(ci.high, ones) + + def test_different_dimensionality(self, xp): + # For better or for worse, there is one difference between the broadcasting + # behavior of most stats functions and NumPy gufuncs / NEP 5: gufuncs `axis` + # refers to the core dimension *before* prepending `1`s to the array shapes + # to match dimensionality; SciPy's prepends `1`s first. For instance, in + # SciPy, `vecdot` would work just like `xp.sum(x * y, axis=axis)`, but this + # is NOT true of NumPy. The discrepancy only arises when there are multiple + # arguments with different dimensionality and positive indices are used, + # which is probably why it hasn't been a problem. There are pros and cons of + # each convention, and we might want to consider changing our behavior in + # SciPy 2.0. For now, preserve consistency / backward compatibility. + rng = np.random.default_rng(45834598265019344) + x = rng.random((3, 10)) + y = rng.random(10) + res = stats.pearsonr(x, y, axis=1) + ref = stats.pearsonr(x, y, axis=-1) + assert_equal(res.statistic, ref.statistic) + + @pytest.mark.parametrize('axis', [0, 1, None]) + @pytest.mark.parametrize('alternative', ['less', 'greater', 'two-sided']) + def test_array_api(self, xp, axis, alternative): + rng = np.random.default_rng(2134418962) + x, y = rng.normal(size=(2, 10, 11)) + res = stats.pearsonr(xp.asarray(x), xp.asarray(y), + axis=axis, alternative=alternative) + ref = stats.pearsonr(x, y, axis=axis, alternative=alternative) + xp_assert_close(res.statistic, xp.asarray(ref.statistic)) + xp_assert_close(res.pvalue, xp.asarray(ref.pvalue)) + + res_ci = res.confidence_interval() + ref_ci = ref.confidence_interval() + xp_assert_close(res_ci.low, xp.asarray(ref_ci.low)) + xp_assert_close(res_ci.high, xp.asarray(ref_ci.high)) + + +class TestFisherExact: + """Some tests to show that fisher_exact() works correctly. + + Note that in SciPy 0.9.0 this was not working well for large numbers due to + inaccuracy of the hypergeom distribution (see #1218). Fixed now. + + Also note that R and SciPy have different argument formats for their + hypergeometric distribution functions. + + R: + > phyper(18999, 99000, 110000, 39000, lower.tail = FALSE) + [1] 1.701815e-09 + """ + + def test_basic(self): + fisher_exact = stats.fisher_exact + + res = fisher_exact([[14500, 20000], [30000, 40000]])[1] + assert_approx_equal(res, 0.01106, significant=4) + res = fisher_exact([[100, 2], [1000, 5]])[1] + assert_approx_equal(res, 0.1301, significant=4) + res = fisher_exact([[2, 7], [8, 2]])[1] + assert_approx_equal(res, 0.0230141, significant=6) + res = fisher_exact([[5, 1], [10, 10]])[1] + assert_approx_equal(res, 0.1973244, significant=6) + res = fisher_exact([[5, 15], [20, 20]])[1] + assert_approx_equal(res, 0.0958044, significant=6) + res = fisher_exact([[5, 16], [20, 25]])[1] + assert_approx_equal(res, 0.1725862, significant=6) + res = fisher_exact([[10, 5], [10, 1]])[1] + assert_approx_equal(res, 0.1973244, significant=6) + res = fisher_exact([[5, 0], [1, 4]])[1] + assert_approx_equal(res, 0.04761904, significant=6) + res = fisher_exact([[0, 1], [3, 2]])[1] + assert_approx_equal(res, 1.0) + res = fisher_exact([[0, 2], [6, 4]])[1] + assert_approx_equal(res, 0.4545454545) + res = fisher_exact([[2, 7], [8, 2]]) + assert_approx_equal(res[1], 0.0230141, significant=6) + assert_approx_equal(res[0], 4.0 / 56) + + def test_precise(self): + # results from R + # + # R defines oddsratio differently (see Notes section of fisher_exact + # docstring), so those will not match. We leave them in anyway, in + # case they will be useful later on. We test only the p-value. + tablist = [ + ([[100, 2], [1000, 5]], (2.505583993422285e-001, 1.300759363430016e-001)), + ([[2, 7], [8, 2]], (8.586235135736206e-002, 2.301413756522114e-002)), + ([[5, 1], [10, 10]], (4.725646047336584e+000, 1.973244147157190e-001)), + ([[5, 15], [20, 20]], (3.394396617440852e-001, 9.580440012477637e-002)), + ([[5, 16], [20, 25]], (3.960558326183334e-001, 1.725864953812994e-001)), + ([[10, 5], [10, 1]], (2.116112781158483e-001, 1.973244147157190e-001)), + ([[10, 5], [10, 0]], (0.000000000000000e+000, 6.126482213438734e-002)), + ([[5, 0], [1, 4]], (np.inf, 4.761904761904762e-002)), + ([[0, 5], [1, 4]], (0.000000000000000e+000, 1.000000000000000e+000)), + ([[5, 1], [0, 4]], (np.inf, 4.761904761904758e-002)), + ([[0, 1], [3, 2]], (0.000000000000000e+000, 1.000000000000000e+000)) + ] + for table, res_r in tablist: + res = stats.fisher_exact(np.asarray(table)) + np.testing.assert_almost_equal(res[1], res_r[1], decimal=11, + verbose=True) + + def test_gh4130(self): + # Previously, a fudge factor used to distinguish between theoretically + # and numerically different probability masses was 1e-4; it has been + # tightened to fix gh4130. Accuracy checked against R fisher.test. + # options(digits=16) + # table <- matrix(c(6, 108, 37, 200), nrow = 2) + # fisher.test(table, alternative = "t") + x = [[6, 37], [108, 200]] + res = stats.fisher_exact(x) + assert_allclose(res[1], 0.005092697748126) + + # case from https://github.com/brentp/fishers_exact_test/issues/27 + # That package has an (absolute?) fudge factor of 1e-6; too big + x = [[22, 0], [0, 102]] + res = stats.fisher_exact(x) + assert_allclose(res[1], 7.175066786244549e-25) + + # case from https://github.com/brentp/fishers_exact_test/issues/1 + x = [[94, 48], [3577, 16988]] + res = stats.fisher_exact(x) + assert_allclose(res[1], 2.069356340993818e-37) + + def test_gh9231(self): + # Previously, fisher_exact was extremely slow for this table + # As reported in gh-9231, the p-value should be very nearly zero + x = [[5829225, 5692693], [5760959, 5760959]] + res = stats.fisher_exact(x) + assert_allclose(res[1], 0, atol=1e-170) + + @pytest.mark.slow + def test_large_numbers(self): + # Test with some large numbers. Regression test for #1401 + pvals = [5.56e-11, 2.666e-11, 1.363e-11] # from R + for pval, num in zip(pvals, [75, 76, 77]): + res = stats.fisher_exact([[17704, 496], [1065, num]])[1] + assert_approx_equal(res, pval, significant=4) + + res = stats.fisher_exact([[18000, 80000], [20000, 90000]])[1] + assert_approx_equal(res, 0.2751, significant=4) + + def test_raises(self): + # test we raise an error for wrong number of dimensions. + message = "The input `table` must have two dimensions." + with pytest.raises(ValueError, match=message): + stats.fisher_exact(np.arange(6)) + + def test_row_or_col_zero(self): + tables = ([[0, 0], [5, 10]], + [[5, 10], [0, 0]], + [[0, 5], [0, 10]], + [[5, 0], [10, 0]]) + for table in tables: + oddsratio, pval = stats.fisher_exact(table) + assert_equal(pval, 1.0) + assert_equal(oddsratio, np.nan) + + def test_less_greater(self): + tables = ( + # Some tables to compare with R: + [[2, 7], [8, 2]], + [[200, 7], [8, 300]], + [[28, 21], [6, 1957]], + [[190, 800], [200, 900]], + # Some tables with simple exact values + # (includes regression test for ticket #1568): + [[0, 2], [3, 0]], + [[1, 1], [2, 1]], + [[2, 0], [1, 2]], + [[0, 1], [2, 3]], + [[1, 0], [1, 4]], + ) + pvals = ( + # from R: + [0.018521725952066501, 0.9990149169715733], + [1.0, 2.0056578803889148e-122], + [1.0, 5.7284374608319831e-44], + [0.7416227, 0.2959826], + # Exact: + [0.1, 1.0], + [0.7, 0.9], + [1.0, 0.3], + [2./3, 1.0], + [1.0, 1./3], + ) + for table, pval in zip(tables, pvals): + res = [] + res.append(stats.fisher_exact(table, alternative="less")[1]) + res.append(stats.fisher_exact(table, alternative="greater")[1]) + assert_allclose(res, pval, atol=0, rtol=1e-7) + + def test_gh3014(self): + # check if issue #3014 has been fixed. + # before, this would have risen a ValueError + odds, pvalue = stats.fisher_exact([[1, 2], [9, 84419233]]) + + @pytest.mark.parametrize("alternative", ['two-sided', 'less', 'greater']) + def test_result(self, alternative): + table = np.array([[14500, 20000], [30000, 40000]]) + res = stats.fisher_exact(table, alternative=alternative) + assert_equal((res.statistic, res.pvalue), res) + + def test_input_validation_edge_cases_rxc(self): + rng = np.random.default_rng(2345783457834572345) + table = np.asarray([[2, 7], [8, 2]]) + + message = r"`alternative` must be the default \(None\) unless..." + with pytest.raises(ValueError, match=message): + method = stats.PermutationMethod(rng=rng) + stats.fisher_exact(table, method=method, alternative='less') + + message = "...not recognized; if provided, `method` must be an..." + with pytest.raises(ValueError, match=message): + method = stats.BootstrapMethod(rng=rng) + stats.fisher_exact(table, method=method) + + message = "If the `method` argument of `fisher_exact` is an..." + with pytest.raises(ValueError, match=message): + method = stats.MonteCarloMethod(rvs=stats.norm.rvs) + stats.fisher_exact(table, method=method) + + message = "`table` must have at least one row and one column." + with pytest.raises(ValueError, match=message): + stats.fisher_exact(np.zeros((0, 1))) + + # Specical case: when there is only one table with given marginals, the + # PMF of that case is 1.0, so the p-value is 1.0 + np.testing.assert_equal(stats.fisher_exact([[1, 2, 3]]), (1, 1)) + np.testing.assert_equal(stats.fisher_exact([[1], [2], [3]]), (1, 1)) + np.testing.assert_equal(stats.fisher_exact(np.zeros((2, 3))), (1, 1)) + + + + @pytest.mark.fail_slow(10) + @pytest.mark.slow() + def test_resampling_2x2(self): + rng = np.random.default_rng(2345783457834572345) + table = np.asarray([[2, 7], [8, 2]]) + ref = stats.fisher_exact(table) + ref_pvalue = ref.pvalue + ref_stat = stats.random_table(table.sum(axis=1), table.sum(axis=0)).pmf(table) + + method = stats.MonteCarloMethod(rng=rng) + res = stats.fisher_exact(table, method=method) + assert_allclose(res.pvalue, ref_pvalue, atol=0.0025) + assert_equal(res.statistic, ref_stat) + + method = stats.PermutationMethod(rng=rng) + res = stats.fisher_exact(table, method=method) + assert_allclose(res.pvalue, ref.pvalue, atol=0.0025) + assert_equal(res.statistic, ref_stat) + + @pytest.mark.fail_slow(10) + @pytest.mark.slow() + def test_resampling_rxc(self): + # Compare against R fisher.exact + # options(digits=16) + # MP6 < - rbind( + # c(1, 2, 2, 1, 1, 0, 1), + # c(2, 0, 0, 2, 3, 0, 0), + # c(0, 1, 1, 1, 2, 7, 3), + # c(1, 1, 2, 0, 0, 0, 1), + # c(0, 1, 1, 1, 1, 0, 0)) + # fisher.test(MP6) + + table = [[1, 2, 2, 1, 1, 0, 1], + [2, 0, 0, 2, 3, 0, 0], + [0, 1, 1, 1, 2, 7, 3], + [1, 1, 2, 0, 0, 0, 1], + [0, 1, 1, 1, 1, 0, 0]] + table = np.asarray(table) + + ref_pvalue = 0.03928964365533 + rng = np.random.default_rng(3928964365533) + + method = stats.PermutationMethod(rng=rng) + res = stats.fisher_exact(table, method=method) + assert_allclose(res.pvalue, ref_pvalue, atol=5e-4) + + method = stats.MonteCarloMethod(rng=rng, n_resamples=99999) + res = stats.fisher_exact(table, method=method) + assert_allclose(res.pvalue, ref_pvalue, atol=5e-4) + + @pytest.mark.xslow() + def test_resampling_exact_2x2(self): + # Test that exact permutation p-value matches result of `fisher_exact` + rng = np.random.default_rng(2345783457834572345) + method = stats.PermutationMethod(rng=rng) + + for a in range(1, 3): + for b in range(1, 3): + for c in range(1, 3): + for d in range(1, 4): + table = np.asarray([[a, b], [c, d]]) + ref = stats.fisher_exact(table) + res = stats.fisher_exact(table, method=method) + assert_allclose(res.pvalue, ref.pvalue, atol=1e-14) + + +class TestCorrSpearmanr: + """ W.II.D. Compute a correlation matrix on all the variables. + + All the correlations, except for ZERO and MISS, should be exactly 1. + ZERO and MISS should have undefined or missing correlations with the + other variables. The same should go for SPEARMAN correlations, if + your program has them. + """ + + def setup_method(self): + self.rng = np.random.default_rng(228584263) + + def test_scalar(self): + y = stats.spearmanr(4., 2.) + assert_(np.isnan(y).all()) + + def test_uneven_lengths(self): + assert_raises(ValueError, stats.spearmanr, [1, 2, 1], [8, 9]) + assert_raises(ValueError, stats.spearmanr, [1, 2, 1], 8) + + def test_uneven_2d_shapes(self): + # Different number of columns should work - those just get concatenated. + x = self.rng.standard_normal((4, 3)) + y = self.rng.standard_normal((4, 2)) + assert stats.spearmanr(x, y).statistic.shape == (5, 5) + assert stats.spearmanr(x.T, y.T, axis=1).pvalue.shape == (5, 5) + + assert_raises(ValueError, stats.spearmanr, x, y, axis=1) + assert_raises(ValueError, stats.spearmanr, x.T, y.T) + + def test_ndim_too_high(self): + x = self.rng.standard_normal((4, 3, 2)) + assert_raises(ValueError, stats.spearmanr, x) + assert_raises(ValueError, stats.spearmanr, x, x) + assert_raises(ValueError, stats.spearmanr, x, None, None) + # But should work with axis=None (raveling axes) for two input arrays + assert_allclose(stats.spearmanr(x, x, axis=None), + stats.spearmanr(x.flatten(), x.flatten(), axis=0)) + + def test_nan_policy(self): + x = np.arange(10.) + x[9] = np.nan + assert_array_equal(stats.spearmanr(x, x), (np.nan, np.nan)) + assert_array_equal(stats.spearmanr(x, x, nan_policy='omit'), + (1.0, 0.0)) + assert_raises(ValueError, stats.spearmanr, x, x, nan_policy='raise') + assert_raises(ValueError, stats.spearmanr, x, x, nan_policy='foobar') + + def test_nan_policy_bug_12458(self): + rng = np.random.default_rng(8119864466) + x = rng.random((5, 10)) + k = 6 + x[:, k] = np.nan + y = np.delete(x, k, axis=1) + corx, px = stats.spearmanr(x, nan_policy='omit') + cory, py = stats.spearmanr(y) + corx = np.delete(np.delete(corx, k, axis=1), k, axis=0) + px = np.delete(np.delete(px, k, axis=1), k, axis=0) + assert_allclose(corx, cory, atol=1e-14) + assert_allclose(px, py, atol=1e-14) + + def test_nan_policy_bug_12411(self): + m = 5 + n = 10 + x = self.rng.standard_normal((m, n)) + x[1, 0] = np.nan + x[3, -1] = np.nan + corr, pvalue = stats.spearmanr(x, axis=1, nan_policy="propagate") + res = [[stats.spearmanr(x[i, :], x[j, :]).statistic for i in range(m)] + for j in range(m)] + assert_allclose(corr, res) + + @pytest.mark.parametrize("axis", [0, 1]) + def test_constant_input_gh22816(self, axis): + X = np.zeros((10, 4)) + X[:, 2] = np.arange(0, 10) + X[:, 3] = -np.arange(0, 10) + + corr = np.array([[np.nan, np.nan, np.nan, np.nan], + [np.nan, np.nan, np.nan, np.nan], + [np.nan, np.nan, 1., -1.], + [np.nan, np.nan, -1., 1.]]) + if axis == 1: + X = X.T + with pytest.warns(stats._warnings_errors.ConstantInputWarning): + res = stats.spearmanr(X, axis=axis) + assert_allclose(res.statistic, corr) + assert_allclose(res.pvalue, 0*corr, atol=1e-50) + + def test_sXX(self): + y = stats.spearmanr(X,X) + r = y[0] + assert_approx_equal(r,1.0) + + def test_sXBIG(self): + y = stats.spearmanr(X,BIG) + r = y[0] + assert_approx_equal(r,1.0) + + def test_sXLITTLE(self): + y = stats.spearmanr(X,LITTLE) + r = y[0] + assert_approx_equal(r,1.0) + + def test_sXHUGE(self): + y = stats.spearmanr(X,HUGE) + r = y[0] + assert_approx_equal(r,1.0) + + def test_sXTINY(self): + y = stats.spearmanr(X,TINY) + r = y[0] + assert_approx_equal(r,1.0) + + def test_sXROUND(self): + y = stats.spearmanr(X,ROUND) + r = y[0] + assert_approx_equal(r,1.0) + + def test_sBIGBIG(self): + y = stats.spearmanr(BIG,BIG) + r = y[0] + assert_approx_equal(r,1.0) + + def test_sBIGLITTLE(self): + y = stats.spearmanr(BIG,LITTLE) + r = y[0] + assert_approx_equal(r,1.0) + + def test_sBIGHUGE(self): + y = stats.spearmanr(BIG,HUGE) + r = y[0] + assert_approx_equal(r,1.0) + + def test_sBIGTINY(self): + y = stats.spearmanr(BIG,TINY) + r = y[0] + assert_approx_equal(r,1.0) + + def test_sBIGROUND(self): + y = stats.spearmanr(BIG,ROUND) + r = y[0] + assert_approx_equal(r,1.0) + + def test_sLITTLELITTLE(self): + y = stats.spearmanr(LITTLE,LITTLE) + r = y[0] + assert_approx_equal(r,1.0) + + def test_sLITTLEHUGE(self): + y = stats.spearmanr(LITTLE,HUGE) + r = y[0] + assert_approx_equal(r,1.0) + + def test_sLITTLETINY(self): + y = stats.spearmanr(LITTLE,TINY) + r = y[0] + assert_approx_equal(r,1.0) + + def test_sLITTLEROUND(self): + y = stats.spearmanr(LITTLE,ROUND) + r = y[0] + assert_approx_equal(r,1.0) + + def test_sHUGEHUGE(self): + y = stats.spearmanr(HUGE,HUGE) + r = y[0] + assert_approx_equal(r,1.0) + + def test_sHUGETINY(self): + y = stats.spearmanr(HUGE,TINY) + r = y[0] + assert_approx_equal(r,1.0) + + def test_sHUGEROUND(self): + y = stats.spearmanr(HUGE,ROUND) + r = y[0] + assert_approx_equal(r,1.0) + + def test_sTINYTINY(self): + y = stats.spearmanr(TINY,TINY) + r = y[0] + assert_approx_equal(r,1.0) + + def test_sTINYROUND(self): + y = stats.spearmanr(TINY,ROUND) + r = y[0] + assert_approx_equal(r,1.0) + + def test_sROUNDROUND(self): + y = stats.spearmanr(ROUND,ROUND) + r = y[0] + assert_approx_equal(r,1.0) + + def test_spearmanr_result_attributes(self): + res = stats.spearmanr(X, X) + attributes = ('correlation', 'pvalue') + check_named_results(res, attributes) + assert_equal(res.correlation, res.statistic) + + def test_1d_vs_2d(self): + x1 = [1, 2, 3, 4, 5, 6] + x2 = [1, 2, 3, 4, 6, 5] + res1 = stats.spearmanr(x1, x2) + res2 = stats.spearmanr(np.asarray([x1, x2]).T) + assert_allclose(res1, res2) + + def test_1d_vs_2d_nans(self): + # Now the same with NaNs present. Regression test for gh-9103. + for nan_policy in ['propagate', 'omit']: + x1 = [1, np.nan, 3, 4, 5, 6] + x2 = [1, 2, 3, 4, 6, np.nan] + res1 = stats.spearmanr(x1, x2, nan_policy=nan_policy) + res2 = stats.spearmanr(np.asarray([x1, x2]).T, nan_policy=nan_policy) + assert_allclose(res1, res2) + + def test_3cols(self): + x1 = np.arange(6) + x2 = -x1 + x3 = np.array([0, 1, 2, 3, 5, 4]) + x = np.asarray([x1, x2, x3]).T + actual = stats.spearmanr(x) + expected_corr = np.array([[1, -1, 0.94285714], + [-1, 1, -0.94285714], + [0.94285714, -0.94285714, 1]]) + expected_pvalue = np.zeros((3, 3), dtype=float) + expected_pvalue[2, 0:2] = 0.00480466472 + expected_pvalue[0:2, 2] = 0.00480466472 + + assert_allclose(actual.statistic, expected_corr) + assert_allclose(actual.pvalue, expected_pvalue) + + def test_gh_9103(self): + # Regression test for gh-9103. + x = np.array([[np.nan, 3.0, 4.0, 5.0, 5.1, 6.0, 9.2], + [5.0, np.nan, 4.1, 4.8, 4.9, 5.0, 4.1], + [0.5, 4.0, 7.1, 3.8, 8.0, 5.1, 7.6]]).T + corr = np.array([[np.nan, np.nan, np.nan], + [np.nan, np.nan, np.nan], + [np.nan, np.nan, 1.]]) + assert_allclose(stats.spearmanr(x, nan_policy='propagate').statistic, + corr) + + res = stats.spearmanr(x, nan_policy='omit').statistic + assert_allclose((res[0][1], res[0][2], res[1][2]), + (0.2051957, 0.4857143, -0.4707919), rtol=1e-6) + + def test_gh_8111(self): + # Regression test for gh-8111 (different result for float/int/bool). + n = 100 + rng = np.random.RandomState(234568) + x = rng.rand(n) + m = rng.rand(n) > 0.7 + + # bool against float, no nans + a = (x > .5) + b = np.array(x) + res1 = stats.spearmanr(a, b, nan_policy='omit').statistic + + # bool against float with NaNs + b[m] = np.nan + res2 = stats.spearmanr(a, b, nan_policy='omit').statistic + + # int against float with NaNs + a = a.astype(np.int32) + res3 = stats.spearmanr(a, b, nan_policy='omit').statistic + + expected = [0.865895477, 0.866100381, 0.866100381] + assert_allclose([res1, res2, res3], expected) + + +class TestCorrSpearmanr2: + """Some further tests of the spearmanr function.""" + + def test_spearmanr_vs_r(self): + # Cross-check with R: + # cor.test(c(1,2,3,4,5),c(5,6,7,8,7),method="spearmanr") + x1 = [1, 2, 3, 4, 5] + x2 = [5, 6, 7, 8, 7] + expected = (0.82078268166812329, 0.088587005313543798) + res = stats.spearmanr(x1, x2) + assert_approx_equal(res[0], expected[0]) + assert_approx_equal(res[1], expected[1]) + + def test_empty_arrays(self): + assert_equal(stats.spearmanr([], []), (np.nan, np.nan)) + + def test_normal_draws(self): + rng = np.random.RandomState(7546) + x = np.array([rng.normal(loc=1, scale=1, size=500), + rng.normal(loc=1, scale=1, size=500)]) + corr = [[1.0, 0.3], + [0.3, 1.0]] + x = np.dot(np.linalg.cholesky(corr), x) + expected = (0.28659685838743354, 6.579862219051161e-11) + res = stats.spearmanr(x[0], x[1]) + assert_approx_equal(res[0], expected[0]) + assert_approx_equal(res[1], expected[1]) + + def test_corr_1(self): + assert_approx_equal(stats.spearmanr([1, 1, 2], [1, 1, 2])[0], 1.0) + + def test_nan_policies(self): + x = np.arange(10.) + x[9] = np.nan + assert_array_equal(stats.spearmanr(x, x), (np.nan, np.nan)) + assert_allclose(stats.spearmanr(x, x, nan_policy='omit'), + (1.0, 0)) + assert_raises(ValueError, stats.spearmanr, x, x, nan_policy='raise') + assert_raises(ValueError, stats.spearmanr, x, x, nan_policy='foobar') + + def test_unequal_lengths(self): + x = np.arange(10.) + y = np.arange(20.) + assert_raises(ValueError, stats.spearmanr, x, y) + + def test_omit_paired_value(self): + x1 = [1, 2, 3, 4] + x2 = [8, 7, 6, np.nan] + res1 = stats.spearmanr(x1, x2, nan_policy='omit') + res2 = stats.spearmanr(x1[:3], x2[:3], nan_policy='omit') + assert_equal(res1, res2) + + def test_gh_issue_6061_windows_overflow(self): + x = list(range(2000)) + y = list(range(2000)) + y[0], y[9] = y[9], y[0] + y[10], y[434] = y[434], y[10] + y[435], y[1509] = y[1509], y[435] + # rho = 1 - 6 * (2 * (9^2 + 424^2 + 1074^2))/(2000 * (2000^2 - 1)) + # = 1 - (1 / 500) + # = 0.998 + x.append(np.nan) + y.append(3.0) + assert_almost_equal(stats.spearmanr(x, y, nan_policy='omit')[0], 0.998) + + def test_tie0(self): + # with only ties in one or both inputs + warn_msg = "An input array is constant" + with pytest.warns(stats.ConstantInputWarning, match=warn_msg): + r, p = stats.spearmanr([2, 2, 2], [2, 2, 2]) + assert_equal(r, np.nan) + assert_equal(p, np.nan) + r, p = stats.spearmanr([2, 0, 2], [2, 2, 2]) + assert_equal(r, np.nan) + assert_equal(p, np.nan) + r, p = stats.spearmanr([2, 2, 2], [2, 0, 2]) + assert_equal(r, np.nan) + assert_equal(p, np.nan) + + def test_tie1(self): + # Data + x = [1.0, 2.0, 3.0, 4.0] + y = [1.0, 2.0, 2.0, 3.0] + # Ranks of the data, with tie-handling. + xr = [1.0, 2.0, 3.0, 4.0] + yr = [1.0, 2.5, 2.5, 4.0] + # Result of spearmanr should be the same as applying + # pearsonr to the ranks. + sr = stats.spearmanr(x, y) + pr = stats.pearsonr(xr, yr) + assert_almost_equal(sr, pr) + + def test_tie2(self): + # Test tie-handling if inputs contain nan's + # Data without nan's + x1 = [1, 2, 2.5, 2] + y1 = [1, 3, 2.5, 4] + # Same data with nan's + x2 = [1, 2, 2.5, 2, np.nan] + y2 = [1, 3, 2.5, 4, np.nan] + + # Results for two data sets should be the same if nan's are ignored + sr1 = stats.spearmanr(x1, y1) + sr2 = stats.spearmanr(x2, y2, nan_policy='omit') + assert_almost_equal(sr1, sr2) + + def test_ties_axis_1(self): + z1 = np.array([[1, 1, 1, 1], [1, 2, 3, 4]]) + z2 = np.array([[1, 2, 3, 4], [1, 1, 1, 1]]) + z3 = np.array([[1, 1, 1, 1], [1, 1, 1, 1]]) + warn_msg = "An input array is constant" + with pytest.warns(stats.ConstantInputWarning, match=warn_msg): + r, p = stats.spearmanr(z1, axis=1) + assert_equal(r, np.nan) + assert_equal(p, np.nan) + r, p = stats.spearmanr(z2, axis=1) + assert_equal(r, np.nan) + assert_equal(p, np.nan) + r, p = stats.spearmanr(z3, axis=1) + assert_equal(r, np.nan) + assert_equal(p, np.nan) + + def test_gh_11111(self): + x = np.array([1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]) + y = np.array([0, 0.009783728115345005, 0, 0, 0.0019759230121848587, + 0.0007535430349118562, 0.0002661781514710257, 0, 0, + 0.0007835762419683435]) + warn_msg = "An input array is constant" + with pytest.warns(stats.ConstantInputWarning, match=warn_msg): + r, p = stats.spearmanr(x, y) + assert_equal(r, np.nan) + assert_equal(p, np.nan) + + def test_index_error(self): + x = np.array([1.0, 7.0, 2.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]) + y = np.array([0, 0.009783728115345005, 0, 0, 0.0019759230121848587, + 0.0007535430349118562, 0.0002661781514710257, 0, 0, + 0.0007835762419683435]) + assert_raises(ValueError, stats.spearmanr, x, y, axis=2) + + def test_alternative(self): + # Test alternative parameter + + # Simple test - Based on the above ``test_spearmanr_vs_r`` + x1 = [1, 2, 3, 4, 5] + x2 = [5, 6, 7, 8, 7] + + # strong positive correlation + expected = (0.82078268166812329, 0.088587005313543798) + + # correlation > 0 -> large "less" p-value + res = stats.spearmanr(x1, x2, alternative="less") + assert_approx_equal(res[0], expected[0]) + assert_approx_equal(res[1], 1 - (expected[1] / 2)) + + # correlation > 0 -> small "less" p-value + res = stats.spearmanr(x1, x2, alternative="greater") + assert_approx_equal(res[0], expected[0]) + assert_approx_equal(res[1], expected[1] / 2) + + with pytest.raises(ValueError, match="`alternative` must be 'less'..."): + stats.spearmanr(x1, x2, alternative="ekki-ekki") + + @pytest.mark.parametrize("alternative", ('two-sided', 'less', 'greater')) + def test_alternative_nan_policy(self, alternative): + # Test nan policies + x1 = [1, 2, 3, 4, 5] + x2 = [5, 6, 7, 8, 7] + x1nan = x1 + [np.nan] + x2nan = x2 + [np.nan] + + # test nan_policy="propagate" + assert_array_equal(stats.spearmanr(x1nan, x2nan), (np.nan, np.nan)) + + # test nan_policy="omit" + res_actual = stats.spearmanr(x1nan, x2nan, nan_policy='omit', + alternative=alternative) + res_expected = stats.spearmanr(x1, x2, alternative=alternative) + assert_allclose(res_actual, res_expected) + + # test nan_policy="raise" + message = 'The input contains nan values' + with pytest.raises(ValueError, match=message): + stats.spearmanr(x1nan, x2nan, nan_policy='raise', + alternative=alternative) + + # test invalid nan_policy + message = "nan_policy must be one of..." + with pytest.raises(ValueError, match=message): + stats.spearmanr(x1nan, x2nan, nan_policy='ekki-ekki', + alternative=alternative) + + +@make_xp_test_case(stats.kendalltau) +class TestKendallTau: + @pytest.mark.parametrize('x, y, statistic, pvalue', [ + # For the cases without ties, both variants should give the same + # result. Reference values result from R: + # cor.test(x, y, method="kendall", exact=1) + ([5, 2, 1, 3, 6, 4, 7, 8], [5, 2, 6, 3, 1, 8, 7, 4], 0.0, 1.0), + ([0, 5, 2, 1, 3, 6, 4, 7, 8], [5, 2, 0, 6, 3, 1, 8, 7, 4], 0.0, 1.0), + ([5, 2, 1, 3, 6, 4, 7], [5, 2, 6, 3, 1, 7, 4], -0.14285714286, 0.77261904762), + ([2, 1, 3, 6, 4, 7, 8], [2, 6, 3, 1, 8, 7, 4], 0.047619047619, 1.0), + ([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + 1.0, 5.511463844797e-07), + ([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [0, 2, 1, 3, 4, 5, 6, 7, 8, 9], + 0.9555555555555556, 5.511463844797e-06), + ([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [0, 2, 1, 3, 4, 6, 5, 7, 8, 9], + 0.9111111111111111, 2.976190476190e-05), + ([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [9, 8, 7, 6, 5, 4, 3, 2, 1, 0], + -1.0, 5.511463844797e-07), + ([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [9, 7, 8, 6, 5, 4, 3, 2, 1, 0], + -0.9555555555555556, 5.511463844797e-06), + ([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [9, 7, 8, 6, 5, 3, 4, 2, 1, 0], + -0.9111111111111111, 2.976190476190e-05), + ]) + @pytest.mark.parametrize('variant', ['b', 'c']) + def test_kendalltau_no_ties(self, x, y, statistic, pvalue, variant, xp): + res = stats.kendalltau(xp.asarray(x), xp.asarray(y), variant=variant) + xp_assert_close(res[0], xp.asarray(statistic)) + xp_assert_close(res[1], xp.asarray(pvalue)) + + def test_kendalltau_ties(self, xp): + # Check a case where variants are different + # Example values found from Kendall (1970). + # P-value is the same for the both variants + x = xp.asarray([1, 2, 2, 4, 4, 6, 6, 8, 9, 9]) + y = xp.asarray([1, 2, 4, 4, 4, 4, 8, 8, 8, 10]) + expected = 0.85895569 + xp_assert_close(stats.kendalltau(x, y, variant='b')[0], xp.asarray(expected)) + expected = 0.825 + xp_assert_close(stats.kendalltau(x, y, variant='c')[0], xp.asarray(expected)) + + # tau-b with some ties + # Cross-check with R: + # cor.test(c(12, 2, 1, 12, 2), c(1, 4, 7, 1, 0), method="kendall", exact=FALSE) + x1 = xp.asarray([12, 2, 1, 12, 2]) + x2 = xp.asarray([1, 4, 7, 1, 0]) + expected = (-0.47140452079103173, 0.28274545993277478) + res = stats.kendalltau(x1, x2) + assert_approx_equal(res[0], xp.asarray(expected[0])) + assert_approx_equal(res[1], xp.asarray(expected[1])) + + # this should result in 1 for tau-b but not tau-c + x = xp.asarray([1, 1, 2]) + xp_assert_close(stats.kendalltau(x, x, variant='b')[0], xp.asarray(1.0)) + xp_assert_close(stats.kendalltau(x, x, variant='c')[0], xp.asarray(0.88888888)) + + def test_input_validation_edge_cases(self, xp): + x = xp.asarray([1, 2, 2, 4, 4, 6, 6, 8, 9, 9]) + y = xp.asarray([1, 2, 2, 4, 4, 4, 8, 8, 8, 10]) + + # check exception in case of ties and method='exact' requested + message = "Ties found; exact method cannot be used." + with pytest.raises(ValueError, match=message): + stats.kendalltau(x, y, method='exact') + + # check exception in case of invalid method keyword + message = "Unknown `method` 'banana' specified." + with pytest.raises(ValueError, match=message): + stats.kendalltau(x, y, method='banana') + + # check exception in case of invalid variant keyword + message = "Unknown `variant` 'rms' specified." + with pytest.raises(ValueError, match=message): + stats.kendalltau(x, y, variant='rms') + + # empty arrays provided as input + with eager_warns(SmallSampleWarning, match="One or more sample...", xp=xp): + res = stats.kendalltau(xp.asarray([]), xp.asarray([])) + xp_assert_equal(res.statistic, xp.asarray(xp.nan)) + xp_assert_equal(res.pvalue, xp.asarray(xp.nan)) + + # test too small + with pytest.warns(SmallSampleWarning, match="One or more sample..."): + tau, p_value = stats.kendalltau(xp.asarray([0]), xp.asarray([0])) + xp_assert_equal(tau, xp.asarray(np.nan)) + xp_assert_equal(p_value, xp.asarray(np.nan)) + + # test unequal length inputs + x = np.arange(10.) + y = np.arange(20.) + message = "Array shapes are incompatible for broadcasting." + with pytest.raises(ValueError, match=message): + stats.kendalltau(x, y) + + @pytest.mark.parametrize('variant', ['b', 'c']) + def test_attributes(self, variant, xp): + x1 = xp.asarray([1, 2, 2, 4, 4, 6, 6, 8, 9, 9]) + x2 = xp.asarray([1, 2, 2, 4, 4, 4, 8, 8, 8, 10]) + # test for namedtuple attribute results + attributes = ('correlation', 'pvalue') + res = stats.kendalltau(x1, x2, variant=variant) + check_named_results(res, attributes, xp=xp) + xp_assert_equal(res.correlation, res.statistic) + + @pytest.mark.parametrize('x, y', [ + ([2, 2, 2], [2, 2, 2]), + ([2, 0, 2], [2, 2, 2]), + ([2, 2, 2], [2, 0, 2]), + ]) + @pytest.mark.parametrize('variant', ['b', 'c']) + def test_degenerate_input(self, x, y, variant, xp): + # with only ties in one or both inputs in tau-b or tau-c + x, y = xp.asarray(x), xp.asarray(y) + res = stats.kendalltau(x, y, variant=variant) + xp_assert_equal(res.statistic, xp.asarray(xp.nan)) + xp_assert_equal(res.pvalue, xp.asarray(xp.nan)) + + @pytest.mark.parametrize('dtype', [None, 'float32', 'float64']) + def test_large_arrays(self, dtype, xp): + # check with larger arrays + dtype = xp_default_dtype(xp) if dtype is None else getattr(xp, dtype) + rng = np.random.RandomState(7546) + x = np.array([rng.normal(loc=1, scale=1, size=500), + rng.normal(loc=1, scale=1, size=500)]) + corr = [[1.0, 0.3], + [0.3, 1.0]] + x = np.dot(np.linalg.cholesky(corr), x) + x0 = xp.asarray(x[0], dtype=dtype) + x1 = xp.asarray(x[1], dtype=dtype) + expected = (0.19291382765531062, 1.1337095377742629e-10) + res = stats.kendalltau(x0, x1) + xp_assert_close(res[0], xp.asarray(expected[0], dtype=dtype)) + xp_assert_close(res[1], xp.asarray(expected[1], dtype=dtype)) + + def test_nan_policy(self, xp): + # test nan_policy + x = np.arange(10.) + x[9] = np.nan + x = xp.asarray(x.tolist()) + + res = stats.kendalltau(x, x) + xp_assert_equal(res.statistic, xp.asarray(xp.nan)) + xp_assert_equal(res.pvalue, xp.asarray(xp.nan)) + + res = stats.kendalltau(x, x, nan_policy='omit') + xp_assert_close(res.statistic, xp.asarray(1.0)) + xp_assert_close(res.pvalue, xp.asarray(5.5114638e-6), rtol=1e-06) + + res = stats.kendalltau(x, x, nan_policy='omit', method='asymptotic') + xp_assert_close(res.statistic, xp.asarray(1.0)) + xp_assert_close(res.pvalue, xp.asarray(0.00017455009626808976), rtol=1e-06) + + message = "The input contains nan values" + with pytest.raises(ValueError, match=message): + stats.kendalltau(x, x, nan_policy='raise') + + message = "nan_policy must be one of..." + with pytest.raises(ValueError, match=message): + stats.kendalltau(x, x, nan_policy='foobar') + + def test_kendalltau_nan_2nd_arg_gh6134(self, xp): + # regression test for gh-6134: nans in the second arg were not handled + x = xp.asarray([1., 2., 3., 4.]) + y = xp.asarray([np.nan, 2.4, 3.4, 3.4]) + + r1 = stats.kendalltau(x, y, nan_policy='omit') + r2 = stats.kendalltau(x[1:], y[1:]) + xp_assert_close(r1.statistic, r2.statistic, atol=1e-15) + + @pytest.mark.thread_unsafe(reason="fails in parallel") + def test_kendalltau_gh18139_overflow(self, xp): + # gh-18139 reported an overflow in `kendalltau` that appeared after + # SciPy 0.15.1. Check that this particular overflow does not occur. + # (Test would fail if warning were emitted.) + import random + random.seed(6272161) + classes = [1, 2, 3, 4, 5, 6, 7] + n_samples = 2 * 10 ** 5 + x = xp.asarray(random.choices(classes, k=n_samples)) + y = xp.asarray(random.choices(classes, k=n_samples)) + res = stats.kendalltau(x, y) + # Reference value from SciPy 0.15.1 + xp_assert_close(res.statistic, xp.asarray(0.0011816493905730343)) + # Reference p-value from `permutation_test` w/ n_resamples=9999 (default). + # Expected to be accurate to at least two digits. + xp_assert_close(res.pvalue, xp.asarray(0.4894), atol=2e-3) + + def test_kendall_tau_large_nan_policy(self, xp): + n = 172 + # Test omit policy + x = np.arange(n + 1).astype(float) + y = np.arange(n + 1).astype(float) + y[-1] = np.nan + x, y = xp.asarray(x.tolist()), xp.asarray(y.tolist()) + _, pval = stats.kendalltau(x, y, method='exact', nan_policy='omit') + xp_assert_equal(pval, xp.asarray(0.0)) + + +@make_xp_test_case(stats.kendalltau) +class TestKendallTauAlternative: + def test_kendalltau_alternative_asymptotic(self, xp): + # Test alternative parameter, asymptotic method (due to tie) + + # Based on TestCorrSpearman2::test_alternative + x1 = [1, 2, 3, 4, 5] + x2 = [5, 6, 7, 8, 7] + + # strong positive correlation + expected = stats.kendalltau(xp.asarray(x1), xp.asarray(x2), + alternative="two-sided") + assert expected[0] > 0 + + # rank correlation > 0 -> large "less" p-value + res = stats.kendalltau(xp.asarray(x1), xp.asarray(x2), alternative="less") + xp_assert_equal(res[0], xp.asarray(expected[0])) + xp_assert_close(res[1], xp.asarray(1 - (expected[1] / 2))) + + # rank correlation > 0 -> small "greater" p-value + res = stats.kendalltau(xp.asarray(x1), xp.asarray(x2), alternative="greater") + xp_assert_equal(res[0], xp.asarray(expected[0])) + xp_assert_close(res[1], xp.asarray(expected[1] / 2)) + + # reverse the direction of rank correlation + x2.reverse() + + # strong negative correlation + expected = stats.kendalltau(xp.asarray(x1), xp.asarray(x2), + alternative="two-sided") + assert expected[0] < 0 + + # rank correlation < 0 -> large "greater" p-value + res = stats.kendalltau(xp.asarray(x1), xp.asarray(x2), alternative="greater") + xp_assert_equal(res[0], xp.asarray(expected[0])) + xp_assert_close(res[1], xp.asarray(1 - (expected[1] / 2))) + + # rank correlation < 0 -> small "less" p-value + res = stats.kendalltau(xp.asarray(x1), xp.asarray(x2), alternative="less") + xp_assert_equal(res[0], xp.asarray(expected[0])) + xp_assert_close(res[1], xp.asarray(expected[1] / 2)) + + with pytest.raises(ValueError, match="`alternative` must be 'less'..."): + stats.kendalltau(xp.asarray(x1), xp.asarray(x2), alternative="ekki-ekki") + + # There are a lot of special cases considered in the calculation of the + # exact p-value, so we test each separately. We also need to test + # separately when the observed statistic is in the left tail vs the right + # tail because the code leverages symmetry of the null distribution; to + # do that we use the same test case but negate one of the samples. + # Reference values computed using R cor.test, e.g. + # options(digits=16) + # x <- c(44.4, 45.9, 41.9, 53.3, 44.7, 44.1, 50.7, 45.2, 60.1) + # y <- c( 2.6, 3.1, 2.5, 5.0, 3.6, 4.0, 5.2, 2.8, 3.8) + # cor.test(x, y, method = "kendall", alternative = "g") + + alternatives = ('less', 'two-sided', 'greater') + p_n1 = [np.nan, np.nan, np.nan] + p_n2 = [1.0, 1.0, 0.5] + p_c0 = [1.0, 0.3333333333333, 0.1666666666667] + p_c1 = [0.9583333333333, 0.3333333333333, 0.1666666666667] + p_no_correlation = [0.5916666666667, 1.0, 0.5916666666667] + p_no_correlationb = [0.5475694444444, 1.0, 0.5475694444444] + p_n_lt_171 = [0.9624118165785, 0.1194389329806, 0.0597194664903] + p_n_lt_171b = [0.246236925303, 0.4924738506059, 0.755634083327] + p_n_lt_171c = [0.9847475308925, 0.03071385306533, 0.01535692653267] + + def exact_test(self, x, y, alternative, rev, stat_expected, p_expected, xp): + x, y = xp.asarray(x), xp.asarray(y) + if rev: + y = -y + stat_expected *= -1 + res = stats.kendalltau(x, y, method='exact', alternative=alternative) + xp_assert_close(res.statistic, xp.asarray(stat_expected)) + xp_assert_close(res.pvalue, xp.asarray(p_expected)) + + case_R_n1 = (list(zip(alternatives, p_n1, [False]*3)) + + list(zip(alternatives, reversed(p_n1), [True]*3))) + + @pytest.mark.parametrize("alternative, p_expected, rev", case_R_n1) + def test_against_R_n1(self, alternative, p_expected, rev, xp): + x, y = [1], [2] + stat_expected = np.nan + with pytest.warns(SmallSampleWarning, match="One or more sample..."): + self.exact_test(x, y, alternative, rev, stat_expected, p_expected, xp=xp) + + case_R_n2 = (list(zip(alternatives, p_n2, [False]*3)) + + list(zip(alternatives, reversed(p_n2), [True]*3))) + + @pytest.mark.parametrize("alternative, p_expected, rev", case_R_n2) + def test_against_R_n2(self, alternative, p_expected, rev, xp): + x, y = [1, 2], [3, 4] + stat_expected = 0.9999999999999998 + self.exact_test(x, y, alternative, rev, stat_expected, p_expected, xp=xp) + + case_R_c0 = (list(zip(alternatives, p_c0, [False]*3)) + + list(zip(alternatives, reversed(p_c0), [True]*3))) + + @pytest.mark.parametrize("alternative, p_expected, rev", case_R_c0) + def test_against_R_c0(self, alternative, p_expected, rev, xp): + x, y = [1, 2, 3], [1, 2, 3] + stat_expected = 1. + self.exact_test(x, y, alternative, rev, stat_expected, p_expected, xp=xp) + + case_R_c1 = (list(zip(alternatives, p_c1, [False]*3)) + + list(zip(alternatives, reversed(p_c1), [True]*3))) + + @pytest.mark.parametrize("alternative, p_expected, rev", case_R_c1) + def test_against_R_c1(self, alternative, p_expected, rev, xp): + x, y = [1, 2, 3, 4], [1, 2, 4, 3] + stat_expected = 0.6666666666666667 + self.exact_test(x, y, alternative, rev, stat_expected, p_expected, xp=xp) + + case_R_no_corr = (list(zip(alternatives, p_no_correlation, [False]*3)) + + list(zip(alternatives, reversed(p_no_correlation), + [True]*3))) + + @pytest.mark.parametrize("alternative, p_expected, rev", case_R_no_corr) + def test_against_R_no_correlation(self, alternative, p_expected, rev, xp): + x, y = [1, 2, 3, 4, 5], [1, 5, 4, 2, 3] + stat_expected = 0. + self.exact_test(x, y, alternative, rev, stat_expected, p_expected, xp=xp) + + case_no_cor_b = (list(zip(alternatives, p_no_correlationb, [False]*3)) + + list(zip(alternatives, reversed(p_no_correlationb), + [True]*3))) + + @pytest.mark.parametrize("alternative, p_expected, rev", case_no_cor_b) + def test_against_R_no_correlationb(self, alternative, p_expected, rev, xp): + x, y = [1, 2, 3, 4, 5, 6, 7, 8], [8, 6, 1, 3, 2, 5, 4, 7] + stat_expected = 0. + self.exact_test(x, y, alternative, rev, stat_expected, p_expected, xp=xp) + + case_R_lt_171 = (list(zip(alternatives, p_n_lt_171, [False]*3)) + + list(zip(alternatives, reversed(p_n_lt_171), [True]*3))) + + @pytest.mark.parametrize("alternative, p_expected, rev", case_R_lt_171) + def test_against_R_lt_171(self, alternative, p_expected, rev, xp): + # Data from Hollander & Wolfe (1973), p. 187f. + # Used from https://rdrr.io/r/stats/cor.test.html + x = [44.4, 45.9, 41.9, 53.3, 44.7, 44.1, 50.7, 45.2, 60.1] + y = [2.6, 3.1, 2.5, 5.0, 3.6, 4.0, 5.2, 2.8, 3.8] + stat_expected = 0.4444444444444445 + self.exact_test(x, y, alternative, rev, stat_expected, p_expected, xp=xp) + + case_R_lt_171b = (list(zip(alternatives, p_n_lt_171b, [False]*3)) + + list(zip(alternatives, reversed(p_n_lt_171b), + [True]*3))) + + @pytest.mark.parametrize("alternative, p_expected, rev", case_R_lt_171b) + def test_against_R_lt_171b(self, alternative, p_expected, rev, xp): + rng = np.random.RandomState(0) + x = rng.rand(100).tolist() + y = rng.rand(100).tolist() + stat_expected = -0.04686868686868687 + self.exact_test(x, y, alternative, rev, stat_expected, p_expected, xp=xp) + + case_R_lt_171c = (list(zip(alternatives, p_n_lt_171c, [False]*3)) + + list(zip(alternatives, reversed(p_n_lt_171c), + [True]*3))) + + @pytest.mark.parametrize("alternative, p_expected, rev", case_R_lt_171c) + def test_against_R_lt_171c(self, alternative, p_expected, rev, xp): + rng = np.random.RandomState(0) + x = rng.rand(170).tolist() + y = rng.rand(170).tolist() + stat_expected = 0.1115906717716673 + self.exact_test(x, y, alternative, rev, stat_expected, p_expected, xp=xp) + + case_gt_171 = (list(zip(alternatives, [False]*3)) + + list(zip(alternatives, [True]*3))) + + @pytest.mark.parametrize("alternative, rev", case_gt_171) + def test_gt_171(self, alternative, rev, xp): + rng = np.random.RandomState(0) + x = xp.asarray(rng.rand(400).tolist()) + y = xp.asarray(rng.rand(400).tolist()) + res0 = stats.kendalltau(x, y, method='exact', alternative=alternative) + res1 = stats.kendalltau(x, y, method='asymptotic', alternative=alternative) + xp_assert_equal(res0[0], res1[0]) + xp_assert_close(res0[1], res1[1], rtol=1e-3) + + @pytest.mark.parametrize("method", ('exact', 'asymptotic')) + @pytest.mark.parametrize("alternative", ('two-sided', 'less', 'greater')) + def test_nan_policy(self, method, alternative, xp): + # Test nan policies + x1 = [1, 2, 3, 4, 5] + x2 = [5, 6, 7, 8, 9] + x1nan = x1 + [np.nan] + x2nan = x2 + [np.nan] + x1, x2 = xp.asarray(x1), xp.asarray(x2) + x1nan, x2nan = xp.asarray(x1nan), xp.asarray(x2nan) + + # test nan_policy="propagate" + res_actual = stats.kendalltau(x1nan, x2nan, + method=method, alternative=alternative) + xp_assert_equal(res_actual.statistic, xp.asarray(xp.nan)) + xp_assert_equal(res_actual.pvalue, xp.asarray(xp.nan)) + + # test nan_policy="omit" + res_actual = stats.kendalltau(x1nan, x2nan, nan_policy='omit', + method=method, alternative=alternative) + res_expected = stats.kendalltau(x1, x2, method=method, + alternative=alternative) + xp_assert_equal(res_actual.statistic, res_expected.statistic) + xp_assert_equal(res_actual.pvalue, res_expected.pvalue) + + # test nan_policy="raise" + message = 'The input contains nan values' + with pytest.raises(ValueError, match=message): + stats.kendalltau(x1nan, x2nan, nan_policy='raise', + method=method, alternative=alternative) + + # test invalid nan_policy + message = "nan_policy must be one of..." + with pytest.raises(ValueError, match=message): + stats.kendalltau(x1nan, x2nan, nan_policy='ekki-ekki', + method=method, alternative=alternative) + + +class TestWeightedTau: + def test_weightedtau(self): + x = [12, 2, 1, 12, 2] + y = [1, 4, 7, 1, 0] + tau, p_value = stats.weightedtau(x, y) + assert_approx_equal(tau, -0.56694968153682723) + assert_equal(np.nan, p_value) + tau, p_value = stats.weightedtau(x, y, additive=False) + assert_approx_equal(tau, -0.62205716951801038) + assert_equal(np.nan, p_value) + # This must be exactly Kendall's tau + tau, p_value = stats.weightedtau(x, y, weigher=lambda x: 1) + assert_approx_equal(tau, -0.47140452079103173) + assert_equal(np.nan, p_value) + + # test for namedtuple attribute results + res = stats.weightedtau(x, y) + attributes = ('correlation', 'pvalue') + check_named_results(res, attributes) + assert_equal(res.correlation, res.statistic) + + # Asymmetric, ranked version + tau, p_value = stats.weightedtau(x, y, rank=None) + assert_approx_equal(tau, -0.4157652301037516) + assert_equal(np.nan, p_value) + tau, p_value = stats.weightedtau(y, x, rank=None) + assert_approx_equal(tau, -0.7181341329699029) + assert_equal(np.nan, p_value) + tau, p_value = stats.weightedtau(x, y, rank=None, additive=False) + assert_approx_equal(tau, -0.40644850966246893) + assert_equal(np.nan, p_value) + tau, p_value = stats.weightedtau(y, x, rank=None, additive=False) + assert_approx_equal(tau, -0.83766582937355172) + assert_equal(np.nan, p_value) + tau, p_value = stats.weightedtau(x, y, rank=False) + assert_approx_equal(tau, -0.51604397940261848) + assert_equal(np.nan, p_value) + # This must be exactly Kendall's tau + tau, p_value = stats.weightedtau(x, y, rank=True, weigher=lambda x: 1) + assert_approx_equal(tau, -0.47140452079103173) + assert_equal(np.nan, p_value) + tau, p_value = stats.weightedtau(y, x, rank=True, weigher=lambda x: 1) + assert_approx_equal(tau, -0.47140452079103173) + assert_equal(np.nan, p_value) + # Test argument conversion + tau, p_value = stats.weightedtau(np.asarray(x, dtype=np.float64), y) + assert_approx_equal(tau, -0.56694968153682723) + tau, p_value = stats.weightedtau(np.asarray(x, dtype=np.int16), y) + assert_approx_equal(tau, -0.56694968153682723) + tau, p_value = stats.weightedtau(np.asarray(x, dtype=np.float64), + np.asarray(y, dtype=np.float64)) + assert_approx_equal(tau, -0.56694968153682723) + # All ties + with pytest.warns(SmallSampleWarning, match="One or more sample..."): + tau, p_value = stats.weightedtau([], []) + assert_equal(np.nan, tau) + assert_equal(np.nan, p_value) + with pytest.warns(SmallSampleWarning, match="One or more sample..."): + tau, p_value = stats.weightedtau([0], [0]) + assert_equal(np.nan, tau) + assert_equal(np.nan, p_value) + # Size mismatches + assert_raises(ValueError, stats.weightedtau, [0, 1], [0, 1, 2]) + assert_raises(ValueError, stats.weightedtau, [0, 1], [0, 1], [0, 1, 2]) + # NaNs + x = [12, 2, 1, 12, 2] + y = [1, 4, 7, 1, np.nan] + tau, p_value = stats.weightedtau(x, y) + assert_approx_equal(tau, -0.56694968153682723) + x = [12, 2, np.nan, 12, 2] + tau, p_value = stats.weightedtau(x, y) + assert_approx_equal(tau, -0.56694968153682723) + # NaNs when the dtype of x and y are all np.float64 + x = [12.0, 2.0, 1.0, 12.0, 2.0] + y = [1.0, 4.0, 7.0, 1.0, np.nan] + tau, p_value = stats.weightedtau(x, y) + assert_approx_equal(tau, -0.56694968153682723) + x = [12.0, 2.0, np.nan, 12.0, 2.0] + tau, p_value = stats.weightedtau(x, y) + assert_approx_equal(tau, -0.56694968153682723) + # NaNs when there are more than one NaN in x or y + x = [12.0, 2.0, 1.0, 12.0, 1.0] + y = [1.0, 4.0, 7.0, 1.0, 1.0] + tau, p_value = stats.weightedtau(x, y) + assert_approx_equal(tau, -0.6615242347139803) + x = [12.0, 2.0, np.nan, 12.0, np.nan] + tau, p_value = stats.weightedtau(x, y) + assert_approx_equal(tau, -0.6615242347139803) + y = [np.nan, 4.0, 7.0, np.nan, np.nan] + tau, p_value = stats.weightedtau(x, y) + assert_approx_equal(tau, -0.6615242347139803) + + + def test_segfault_issue_9710(self): + # https://github.com/scipy/scipy/issues/9710 + # This test was created to check segfault + # In issue SEGFAULT only repros in optimized builds after calling + # the function twice + message = "One or more sample arguments is too small" + with pytest.warns(SmallSampleWarning, match=message): + stats.weightedtau([1], [1.0]) + stats.weightedtau([1], [1.0]) + # The code below also caused SEGFAULT + stats.weightedtau([np.nan], [52]) + + + def test_weightedtau_vs_quadratic(self): + # Trivial quadratic implementation, all parameters mandatory + def wkq(x, y, rank, weigher, add): + tot = conc = disc = u = v = 0 + for (i, j) in product(range(len(x)), range(len(x))): + w = weigher(rank[i]) + weigher(rank[j]) if add \ + else weigher(rank[i]) * weigher(rank[j]) + tot += w + if x[i] == x[j]: + u += w + if y[i] == y[j]: + v += w + if x[i] < x[j] and y[i] < y[j] or x[i] > x[j] and y[i] > y[j]: + conc += w + elif x[i] < x[j] and y[i] > y[j] or x[i] > x[j] and y[i] < y[j]: + disc += w + return (conc - disc) / np.sqrt(tot - u) / np.sqrt(tot - v) + + def weigher(x): + return 1. / (x + 1) + + rng = np.random.default_rng(42) + for s in range(3,10): + a = [] + # Generate rankings with ties + for i in range(s): + a += [i]*i + b = list(a) + rng.shuffle(a) + rng.shuffle(b) + # First pass: use element indices as ranks + rank = np.arange(len(a), dtype=np.intp) + for _ in range(2): + for add in [True, False]: + expected = wkq(a, b, rank, weigher, add) + actual = stats.weightedtau(a, b, rank, weigher, add).statistic + assert_approx_equal(expected, actual) + # Second pass: use a random rank + rng.shuffle(rank) + + +@make_xp_test_case(stats.linregress) +class TestRegression: + def test_linregressBIGX(self, xp): + # W.II.F. Regress BIG on X. + dtype = xp.float64 # needed for accurate results + result = stats.linregress(xp.asarray(X), xp.asarray(BIG)) + xp_assert_close(result.intercept, xp.asarray(99999990., dtype=dtype)) + xp_assert_close(result.rvalue, xp.asarray(1.0, dtype=dtype)) + # The uncertainty ought to be almost zero + # since all points lie on a line + zero = xp.asarray(0.0, dtype=dtype) + xp_assert_close(result.stderr, zero, atol=1e-7) # atol for JAX + xp_assert_close(result.intercept_stderr, zero, atol=1e-7) + + def test_regressXX(self, xp): + # W.IV.B. Regress X on X. + # The constant should be exactly 0 and the regression coefficient + # should be 1. This is a perfectly valid regression and the + # program should not complain. + result = stats.linregress(xp.asarray(X.tolist()), xp.asarray(X.tolist())) + xp_assert_close(result.intercept, xp.asarray(0.0), atol=1e-15) + xp_assert_close(result.rvalue, xp.asarray(1.0)) + # The uncertainly on regression through two points ought to be 0 + xp_assert_close(result.stderr, xp.asarray(0.0), atol=1e-7) + xp_assert_close(result.intercept_stderr, xp.asarray(0.0), atol=1e-7) + + # W.IV.C. Regress X on BIG and LITTLE (two predictors). The program + # should tell you that this model is "singular" because BIG and + # LITTLE are linear combinations of each other. Cryptic error + # messages are unacceptable here. Singularity is the most + # fundamental regression error. + # + # Need to figure out how to handle multiple linear regression. + # This is not obvious + + def test_regressZEROX(self, xp): + # W.IV.D. Regress ZERO on X. + # The program should inform you that ZERO has no variance or it should + # go ahead and compute the regression and report a correlation and + # total sum of squares of exactly 0. + result = stats.linregress(xp.asarray(X.tolist()), xp.asarray(ZERO.tolist())) + xp_assert_close(result.intercept, xp.asarray(0.0)) + with eager_warns(stats.ConstantInputWarning, match="An input array...", xp=xp): + ref_rvalue = stats.pearsonr(xp.asarray(X.tolist()), + xp.asarray(ZERO.tolist())).statistic + xp_assert_close(result.rvalue, ref_rvalue) + + def test_regress_simple(self, xp): + # Regress a line with sinusoidal noise. + x = xp.linspace(0, 100, 100) + y = 0.2 * xp.linspace(0, 100, 100) + 10 + y = y + xp.sin(xp.linspace(0, 20, 100)) + + result = stats.linregress(x, y) + lr = LinregressResult + assert isinstance(result, lr) + xp_assert_close(result.stderr, xp.asarray(2.3957814497838803e-3)) + + def test_regress_alternative(self, xp): + # test alternative parameter + x = xp.linspace(0, 100, 100) + y = 0.2 * xp.linspace(0, 100, 100) + 10 # slope is greater than zero + y = y + xp.sin(xp.linspace(0, 20, 100)) + + with pytest.raises(ValueError, match="`alternative` must be 'less'..."): + stats.linregress(x, y, alternative="ekki-ekki") + + res1 = stats.linregress(x, y, alternative="two-sided") + + # slope is greater than zero, so "less" p-value should be large + res2 = stats.linregress(x, y, alternative="less") + xp_assert_close(res2.pvalue, 1 - (res1.pvalue / 2)) + + # slope is greater than zero, so "greater" p-value should be small + res3 = stats.linregress(x, y, alternative="greater") + xp_assert_close(res3.pvalue, res1.pvalue / 2) + + assert res1.rvalue == res2.rvalue == res3.rvalue + + @pytest.mark.parametrize("dtype", [None, 'float32', 'float64']) + def test_regress_against_R(self, dtype, xp): + # test against R `lm` + # options(digits=16) + # x <- c(151, 174, 138, 186, 128, 136, 179, 163, 152, 131) + # y <- c(63, 81, 56, 91, 47, 57, 76, 72, 62, 48) + # relation <- lm(y~x) + # print(summary(relation)) + dtype = dtype if dtype is None else getattr(xp, dtype) + x = xp.asarray([151, 174, 138, 186, 128, 136, 179, 163, 152, 131], dtype=dtype) + y = xp.asarray([63, 81, 56, 91, 47, 57, 76, 72, 62, 48], dtype=dtype) + res = stats.linregress(x, y, alternative="two-sided") + # expected values from R's `lm` above + xp_assert_close(res.slope, xp.asarray(0.6746104491292, dtype=dtype)) + xp_assert_close(res.intercept, xp.asarray(-38.4550870760770, dtype=dtype)) + xp_assert_close(res.rvalue, xp.sqrt(xp.asarray(0.95478224775, dtype=dtype))) + xp_assert_close(res.pvalue, xp.asarray(1.16440531074e-06, dtype=dtype)) + xp_assert_close(res.stderr, xp.asarray(0.0519051424731, dtype=dtype)) + xp_assert_close(res.intercept_stderr, xp.asarray(8.0490133029927, dtype=dtype)) + + def test_linregress(self, xp): + # compared with multivariate ols with pinv + x = np.arange(11) + y = np.arange(5, 16) + y[[(1), (-2)]] -= 1 + y[[(0), (-1)]] += 1 + + result = stats.linregress(xp.asarray(x), xp.asarray(y)) + + # This test used to use 'assert_array_almost_equal' but its + # formulation got confusing since LinregressResult became + # _lib._bunch._make_tuple_bunch instead of namedtuple + # (for backwards compatibility, see PR #12983) + def assert_ae(x, y): + return xp_assert_close(x, xp.asarray(y), atol=1e-14) + assert_ae(result.slope, 1.0) + assert_ae(result.intercept, 5.0) + assert_ae(result.rvalue, 0.98229948625750) + assert_ae(result.pvalue, 7.45259691e-008) + assert_ae(result.stderr, 0.063564172616372733) + assert_ae(result.intercept_stderr, 0.37605071654517686) + + def test_regress_simple_negative_cor(self, xp): + # If the slope of the regression is negative the factor R tend + # to -1 not 1. Sometimes rounding errors makes it < -1 + # leading to stderr being NaN. + a, n = 1e-71, 100000 + dtype = xp.float64 + x = xp.linspace(a, 2 * a, n, dtype=dtype) # doesn't pass with float32 + y = xp.linspace(2 * a, a, n, dtype=dtype) + result = stats.linregress(x, y) + + # Make sure propagated numerical errors + # did not bring rvalue below -1 (or were coerced) + assert result.rvalue >= -1 + xp_assert_close(result.rvalue, xp.asarray(-1., dtype=dtype)) + + # slope and intercept stderror should stay numeric + assert not xp.isnan(result.stderr) + assert not xp.isnan(result.intercept_stderr) + + def test_linregress_result_attributes(self, xp): + x = xp.linspace(0, 100, 100) + y = 0.2 * xp.linspace(0, 100, 100) + 10 + y = y + xp.sin(xp.linspace(0, 20, 100)) + result = stats.linregress(x, y) + + # Result is of a correct class + lr = LinregressResult + assert isinstance(result, lr) + + # LinregressResult elements have correct names + attributes = ('slope', 'intercept', 'rvalue', 'pvalue', 'stderr') + check_named_results(result, attributes, xp=xp) + # Also check that the extra attribute (intercept_stderr) is present + assert 'intercept_stderr' in dir(result) + + def test_regress_two_inputs(self, xp): + # Regress a simple line formed by two points. + x = xp.arange(2) + y = xp.arange(3, 5) + result = stats.linregress(x, y) + + xp_assert_close(result.slope, xp.asarray(1.0)) + xp_assert_close(result.intercept, xp.asarray(3.0)) + xp_assert_close(result.rvalue, xp.asarray(1.0)) + + NaN = xp.asarray(xp.nan) + xp_assert_equal(result.pvalue, NaN) + xp_assert_equal(result.stderr, NaN) + xp_assert_equal(result.intercept_stderr, NaN) + + def test_regress_two_inputs_horizontal_line(self, xp): + # Regress a horizontal line formed by two points. + x = xp.arange(2.) + y = xp.ones(2) + result = stats.linregress(x, y) + + # Horizontal line + xp_assert_close(result.slope, xp.asarray(0.0)) + xp_assert_close(result.intercept, xp.asarray(1.0)) + + NaN = xp.asarray(xp.nan) + xp_assert_equal(result.rvalue, NaN) + xp_assert_equal(result.pvalue, NaN) + xp_assert_equal(result.stderr, NaN) + xp_assert_equal(result.intercept_stderr, NaN) + + def test_nist_norris(self, xp): + # If this causes a lint failure in the future, please note the history of + # requests to allow extra whitespace in table formatting (e.g. gh-12367). + # Also see https://github.com/scipy/scipy/wiki/Why-do-we-not-use-an-auto%E2%80%90formatter%3F # noqa: E501 + x = [ 0.2, 337.4, 118.2, 884.6, 10.1, 226.5, + 666.3, 996.3, 448.6, 777.0, 558.2, 0.4, + 0.6, 775.5, 666.9, 338.0, 447.5, 11.6, + 556.0, 228.1, 995.8, 887.6, 120.2, 0.3, + 0.3, 556.8, 339.1, 887.2, 999.0, 779.0, + 11.1, 118.3, 229.2, 669.1, 448.9, 0.5] + + y = [ 0.1, 338.8, 118.1, 888.0, 9.2, 228.1, + 668.5, 998.5, 449.1, 778.9, 559.2, 0.3, + 0.1, 778.1, 668.8, 339.3, 448.9, 10.8, + 557.7, 228.3, 998.0, 888.8, 119.6, 0.3, + 0.6, 557.6, 339.3, 888.0, 998.5, 778.9, + 10.2, 117.6, 228.9, 668.4, 449.2, 0.2] + + result = stats.linregress(xp.asarray(x), xp.asarray(y)) + + xp_assert_close(result.slope, xp.asarray(1.00211681802045)) + xp_assert_close(result.intercept, xp.asarray(-0.262323073774029)) + xp_assert_close(result.rvalue**2, xp.asarray(0.999993745883712)) + xp_assert_close(result.pvalue, xp.asarray(0.0), atol=1e-80) + if result.pvalue.dtype == xp.float64: # these are inaccurate with float32 + xp_assert_close(result.stderr, xp.asarray(0.00042979684820)) + xp_assert_close(result.intercept_stderr, xp.asarray(0.23281823430153)) + + def test_compare_to_polyfit(self, xp): + x = np.linspace(0, 100, 100) + y = 0.2 * np.linspace(0, 100, 100) + 10 + y += np.sin(np.linspace(0, 20, 100)) + result = stats.linregress(xp.asarray(x), xp.asarray(y)) + poly = np.polyfit(x, y, 1) # Fit 1st degree polynomial + + # Make sure linear regression slope and intercept + # match with results from numpy polyfit + xp_assert_close(result.slope, xp.asarray(poly[0])) + xp_assert_close(result.intercept, xp.asarray(poly[1])) + + def test_linregress_two_points_nan_inference(self, xp): + # Test for gh-24684 + x = xp.asarray([0., 1.]) + y = xp.asarray([0., 1.]) + + res = stats.linregress(x, y) + + NaN = xp.asarray(xp.nan) + xp_assert_equal(res.pvalue, NaN) + xp_assert_equal(res.stderr, NaN) + xp_assert_equal(res.intercept_stderr, NaN) + + # Point estimates should still be correct + xp_assert_close(res.slope, xp.asarray(1.0)) + xp_assert_close(res.intercept, xp.asarray(0.0)) + + def test_empty_input(self, xp): + with eager_warns(SmallSampleWarning, match="One or more sample...", xp=xp): + res = stats.linregress(xp.asarray([]), xp.asarray([])) + NaN = xp.asarray(xp.nan) + xp_assert_equal(res.slope, NaN) + xp_assert_equal(res.intercept, NaN) + xp_assert_equal(res.rvalue, NaN) + xp_assert_equal(res.pvalue, NaN) + xp_assert_equal(res.stderr, NaN) + xp_assert_equal(res.intercept_stderr, NaN) + + def test_nan_input(self, xp): + x = np.arange(10.) + x[9] = np.nan + x = xp.asarray(x.tolist()) + + with np.errstate(invalid="ignore"): + result = stats.linregress(x, x) + + # Make sure the result still comes back as `LinregressResult` + lr = LinregressResult + assert isinstance(result, lr) + NaN = xp.asarray(xp.nan) + xp_assert_equal(result.slope, NaN) + xp_assert_equal(result.intercept, NaN) + xp_assert_equal(result.rvalue, NaN) + xp_assert_equal(result.pvalue, NaN) + xp_assert_equal(result.stderr, NaN) + xp_assert_equal(result.intercept_stderr, NaN) + + def test_identical_x(self, xp): + rng = np.random.default_rng(7872425088) + x = xp.zeros(10) + y = xp.asarray(rng.random(10).tolist()) + with np.errstate(invalid='ignore'): + res = stats.linregress(x, y) + NaN = xp.asarray(xp.nan) + xp_assert_equal(res.slope, NaN) + xp_assert_equal(res.intercept, NaN) + xp_assert_equal(res.rvalue, NaN) + xp_assert_equal(res.pvalue, NaN) + xp_assert_equal(res.stderr, NaN) + xp_assert_equal(res.intercept_stderr, NaN) + + +class HistFunctionsTest: + def test_input_validation(self, xp): + histfun = getattr(stats, self.histfun) + a = xp.arange(20) + numbins = 10 + defaultreallimits = (0, 20) + weights = xp.ones(20) + + message = "If specified, `defaultreallimits` must be..." + with pytest.raises(ValueError, match=message): + histfun(a, numbins, 10) + with pytest.raises(ValueError, match=message): + histfun(a, numbins, (1, 2, 3)) + with pytest.raises(ValueError, match=message): + histfun(a, numbins, defaultreallimits[::-1]) + + message = r"`a` and \(if specified\) `weights` must have real dtype." + with pytest.raises(ValueError, match=message): + histfun(xp.astype(a, xp.complex128), numbins, defaultreallimits) + with pytest.raises(ValueError, match=message): + histfun(a, numbins, defaultreallimits, xp.astype(weights, xp.complex128)) + + if not is_lazy_array(weights): + message = "All `weights` must be non-negative." + with pytest.raises(ValueError, match=message): + histfun(a, numbins, defaultreallimits, -weights) + + @pytest.mark.parametrize("dtype", [None, 'float32', 'float64']) + @pytest.mark.parametrize("edge_points", [False, True]) + @pytest.mark.parametrize("specify_limits", [False, True]) + @pytest.mark.parametrize("specify_weights", [False, True]) + def test_against_reference(self, dtype, edge_points, specify_limits, + specify_weights, xp): + rng = np.random.default_rng(5194553734) + nbins = int(rng.integers(10, 15)) + + if edge_points and not specify_limits: + pytest.skip("Limits must be specified to determine edge points.") + if edge_points and (is_jax(xp) + or (dtype != 'float64' and is_torch(xp)) + or (dtype != 'float64' and is_cupy(xp))): + pytest.skip("Some backends are having trouble with edge point arithmetic.") + + a = np.astype(rng.random(100), dtype) + + if specify_limits: + limits = np.astype(np.sort(rng.random(2)), dtype) + range_ = limits + else: + # copy-pasted from documentation to generate reference values + s = (1 / 2)*(a.max() - a.min()) / (nbins - 1) + range_ = (a.min() - s, a.max() + s) + limits = None + + if edge_points: + _, edges = np.histogram(a, bins=nbins, range=range_, density=False) + a2 = np.repeat(edges, rng.integers(5, size=edges.shape).tolist()) + a = np.concat([a, a2], dtype=dtype) + + weights = np.astype(rng.random(a.shape), dtype) if specify_weights else None + + bin_counts, ref_bins = np.histogram(a, bins=nbins, range=range_, density=False) + ref_hist, _ = np.histogram(a, bins=nbins, range=range_, + weights=weights, density=False) + + dtype = xp_default_dtype(xp) if dtype is None else getattr(xp, dtype) + histfun = getattr(stats, self.histfun) + weights = weights if weights is None else xp.asarray(weights, dtype=dtype) + res = histfun(xp.asarray(a, dtype=dtype), nbins, limits, weights) + ref_hist = xp.asarray(ref_hist, dtype=dtype) + + rtol = 1e-12 if dtype == xp.float64 else 2e-5 + if histfun == stats.cumfreq: + ref_cumcount = xp.cumulative_sum(ref_hist) + xp_assert_close(res.cumcount, ref_cumcount, rtol=rtol) + else: + den = a.shape[0] if weights is None else xp.sum(weights) + ref_frequency = xp.asarray(ref_hist/den, dtype=dtype) + xp_assert_close(res.frequency, ref_frequency, rtol=rtol) + xp_assert_close(res.lowerlimit, xp.asarray(ref_bins[0], dtype=dtype)) + xp_assert_close(res.binsize, xp.asarray(ref_bins[1] - ref_bins[0], dtype=dtype)) + xp_assert_close(res.extrapoints, xp.asarray(a.size - sum(bin_counts.tolist()))) + + +@make_xp_test_case(stats.cumfreq) +class TestCumfreq(HistFunctionsTest): + histfun = 'cumfreq' + + def test_cumfreq(self, xp): + x = xp.asarray([1, 4, 2, 1, 3, 1]) + cumfreqs, lowlim, binsize, extrapoints = stats.cumfreq(x, numbins=4) + xp_assert_equal(cumfreqs, xp.asarray([3., 4., 5., 6.])) + cumfreqs, lowlim, binsize, extrapoints = stats.cumfreq( + x, numbins=4, defaultreallimits=(1.5, 5)) + assert extrapoints == 3 + + # test for namedtuple attribute results + attributes = ('cumcount', 'lowerlimit', 'binsize', 'extrapoints') + res = stats.cumfreq(x, numbins=4, defaultreallimits=(1.5, 5)) + check_named_results(res, attributes, xp=xp) + + +@make_xp_test_case(stats.relfreq) +class TestRelfreq(HistFunctionsTest): + histfun = 'relfreq' + + def test_relfreq(self, xp): + a = xp.asarray([1, 4, 2, 1, 3, 1]) + relfreqs, lowlim, binsize, extrapoints = stats.relfreq(a, numbins=4) + xp_assert_close(relfreqs, xp.asarray([0.5, 0.16666667, 0.16666667, 0.16666667])) + + # test for namedtuple attribute results + attributes = ('frequency', 'lowerlimit', 'binsize', 'extrapoints') + res = stats.relfreq(a, numbins=4) + check_named_results(res, attributes, xp=xp) + + def test_relfreq_array_like(self): # array_like is np only + # check array_like input is accepted + a = [1, 4, 2, 1, 3, 1] + res, _, _, _ = stats.relfreq(a, numbins=4) + ref, _, _, _ = stats.relfreq(np.asarray(a), numbins=4) + xp_assert_equal(res, ref) + + +class TestScoreatpercentile: + def setup_method(self): + self.a1 = [3, 4, 5, 10, -3, -5, 6] + self.a2 = [3, -6, -2, 8, 7, 4, 2, 1] + self.a3 = [3., 4, 5, 10, -3, -5, -6, 7.0] + + def test_basic(self): + x = arange(8) * 0.5 + assert_equal(stats.scoreatpercentile(x, 0), 0.) + assert_equal(stats.scoreatpercentile(x, 100), 3.5) + assert_equal(stats.scoreatpercentile(x, 50), 1.75) + + def test_fraction(self): + scoreatperc = stats.scoreatpercentile + + # Test defaults + assert_equal(scoreatperc(list(range(10)), 50), 4.5) + assert_equal(scoreatperc(list(range(10)), 50, (2,7)), 4.5) + assert_equal(scoreatperc(list(range(100)), 50, limit=(1, 8)), 4.5) + assert_equal(scoreatperc(np.array([1, 10,100]), 50, (10,100)), 55) + assert_equal(scoreatperc(np.array([1, 10,100]), 50, (1,10)), 5.5) + + # explicitly specify interpolation_method 'fraction' (the default) + assert_equal(scoreatperc(list(range(10)), 50, interpolation_method='fraction'), + 4.5) + assert_equal(scoreatperc(list(range(10)), 50, limit=(2, 7), + interpolation_method='fraction'), + 4.5) + assert_equal(scoreatperc(list(range(100)), 50, limit=(1, 8), + interpolation_method='fraction'), + 4.5) + assert_equal(scoreatperc(np.array([1, 10,100]), 50, (10, 100), + interpolation_method='fraction'), + 55) + assert_equal(scoreatperc(np.array([1, 10,100]), 50, (1,10), + interpolation_method='fraction'), + 5.5) + + def test_lower_higher(self): + scoreatperc = stats.scoreatpercentile + + # interpolation_method 'lower'/'higher' + assert_equal(scoreatperc(list(range(10)), 50, + interpolation_method='lower'), 4) + assert_equal(scoreatperc(list(range(10)), 50, + interpolation_method='higher'), 5) + assert_equal(scoreatperc(list(range(10)), 50, (2,7), + interpolation_method='lower'), 4) + assert_equal(scoreatperc(list(range(10)), 50, limit=(2,7), + interpolation_method='higher'), 5) + assert_equal(scoreatperc(list(range(100)), 50, (1,8), + interpolation_method='lower'), 4) + assert_equal(scoreatperc(list(range(100)), 50, (1,8), + interpolation_method='higher'), 5) + assert_equal(scoreatperc(np.array([1, 10, 100]), 50, (10, 100), + interpolation_method='lower'), 10) + assert_equal(scoreatperc(np.array([1, 10, 100]), 50, limit=(10, 100), + interpolation_method='higher'), 100) + assert_equal(scoreatperc(np.array([1, 10, 100]), 50, (1, 10), + interpolation_method='lower'), 1) + assert_equal(scoreatperc(np.array([1, 10, 100]), 50, limit=(1, 10), + interpolation_method='higher'), 10) + + def test_sequence_per(self): + x = arange(8) * 0.5 + expected = np.array([0, 3.5, 1.75]) + res = stats.scoreatpercentile(x, [0, 100, 50]) + assert_allclose(res, expected) + assert_(isinstance(res, np.ndarray)) + # Test with ndarray. Regression test for gh-2861 + assert_allclose(stats.scoreatpercentile(x, np.array([0, 100, 50])), + expected) + # Also test combination of 2-D array, axis not None and array-like per + res2 = stats.scoreatpercentile(np.arange(12).reshape((3,4)), + np.array([0, 1, 100, 100]), axis=1) + expected2 = array([[0, 4, 8], + [0.03, 4.03, 8.03], + [3, 7, 11], + [3, 7, 11]]) + assert_allclose(res2, expected2) + + def test_axis(self): + scoreatperc = stats.scoreatpercentile + x = arange(12).reshape(3, 4) + + assert_equal(scoreatperc(x, (25, 50, 100)), [2.75, 5.5, 11.0]) + + r0 = [[2, 3, 4, 5], [4, 5, 6, 7], [8, 9, 10, 11]] + assert_equal(scoreatperc(x, (25, 50, 100), axis=0), r0) + + r1 = [[0.75, 4.75, 8.75], [1.5, 5.5, 9.5], [3, 7, 11]] + assert_equal(scoreatperc(x, (25, 50, 100), axis=1), r1) + + x = array([[1, 1, 1], + [1, 1, 1], + [4, 4, 3], + [1, 1, 1], + [1, 1, 1]]) + score = stats.scoreatpercentile(x, 50) + assert_equal(score.shape, ()) + assert_equal(score, 1.0) + score = stats.scoreatpercentile(x, 50, axis=0) + assert_equal(score.shape, (3,)) + assert_equal(score, [1, 1, 1]) + + def test_exception(self): + assert_raises(ValueError, stats.scoreatpercentile, [1, 2], 56, + interpolation_method='foobar') + assert_raises(ValueError, stats.scoreatpercentile, [1], 101) + assert_raises(ValueError, stats.scoreatpercentile, [1], -1) + + def test_empty(self): + assert_equal(stats.scoreatpercentile([], 50), np.nan) + assert_equal(stats.scoreatpercentile(np.array([[], []]), 50), np.nan) + assert_equal(stats.scoreatpercentile([], [50, 99]), [np.nan, np.nan]) + + +@make_xp_test_case(stats.mode) +class TestMode: + + def test_empty(self, xp): + with eager_warns(SmallSampleWarning, match=too_small_1d_not_omit, xp=xp): + vals, counts = stats.mode(xp.asarray([])) + xp_assert_equal(vals, xp.asarray(xp.nan)) + xp_assert_equal(counts, xp.asarray(0.)) + + def test_scalar(self): + vals, counts = stats.mode(4.) + assert_equal(vals, np.array([4.])) + assert_equal(counts, np.array([1])) + + def test_basic(self, xp): + data1 = xp.asarray([3, 5, 1, 10, 23, 3, 2, 6, 8, 6, 10, 6]) + vals = stats.mode(data1) + xp_assert_equal(vals[0], xp.asarray(6)) + xp_assert_equal(vals[1], xp.asarray(3)) + + def test_axes_keepdims(self, xp): + data1 = [10, 10, 30, 40] + data2 = [10, 10, 10, 10] + data3 = [20, 10, 20, 20] + data4 = [30, 30, 30, 30] + data5 = [40, 30, 30, 30] + arr = xp.asarray([data1, data2, data3, data4, data5]) + + vals = stats.mode(arr, axis=None, keepdims=True) + xp_assert_equal(vals[0], xp.asarray([[30]])) + xp_assert_equal(vals[1], xp.asarray([[8]])) + + vals = stats.mode(arr, axis=0, keepdims=True) + xp_assert_equal(vals[0], xp.asarray([[10, 10, 30, 30]])) + xp_assert_equal(vals[1], xp.asarray([[2, 3, 3, 2]])) + + vals = stats.mode(arr, axis=1, keepdims=True) + xp_assert_equal(vals[0], xp.asarray([[10], [10], [20], [30], [30]])) + xp_assert_equal(vals[1], xp.asarray([[2], [4], [3], [4], [3]])) + + def test_axes(self, xp): + data1 = [10, 10, 30, 40] + data2 = [10, 10, 10, 10] + data3 = [20, 10, 20, 20] + data4 = [30, 30, 30, 30] + data5 = [40, 30, 30, 30] + arr = xp.asarray([data1, data2, data3, data4, data5]) + + vals = stats.mode(arr, axis=None) + xp_assert_equal(vals[0], xp.asarray(30)) + xp_assert_equal(vals[1], xp.asarray(8)) + + vals = stats.mode(arr, axis=0) + xp_assert_equal(vals[0], xp.asarray([10, 10, 30, 30])) + xp_assert_equal(vals[1], xp.asarray([2, 3, 3, 2])) + + vals = stats.mode(arr, axis=1) + xp_assert_equal(vals[0], xp.asarray([10, 10, 20, 30, 30])) + xp_assert_equal(vals[1], xp.asarray([2, 4, 3, 4, 3])) + + @pytest.mark.parametrize('axis', range(-4, 0)) + def test_negative_axes_gh_15375(self, axis, xp): + rng = np.random.default_rng(7090348401) + a = xp.asarray(rng.random((10, 11, 12, 13))) + res0 = stats.mode(a, axis=a.ndim+axis) + res1 = stats.mode(a, axis=axis) + xp_assert_equal(res0.mode, res1.mode) + xp_assert_equal(res0.count, res1.count) + + def test_mode_result_attributes(self, xp): + data1 = xp.asarray([3, 5, 1, 10, 23, 3, 2, 6, 8, 6, 10, 6]) + data2 = xp.asarray([]) + actual = stats.mode(data1) + attributes = ('mode', 'count') + check_named_results(actual, attributes, xp=xp) + with eager_warns(SmallSampleWarning, match=too_small_1d_not_omit, xp=xp): + actual2 = stats.mode(data2) + check_named_results(actual2, attributes, xp=xp) + + def test_nan_propagate(self, xp): + data1 = xp.asarray([3, np.nan, 5, 1, 10, 23, 3, 2, 6, 8, 6, 10, 6]) + actual = stats.mode(data1) + xp_assert_equal(actual[0], xp.asarray(6, dtype=data1.dtype)) + xp_assert_equal(actual[1], xp.asarray(3)) + + @skip_xp_backends(eager_only=True, reason="lazy arrays don't do 'raise'.") + def test_nan_omit(self, xp): + data1 = xp.asarray([3, np.nan, 5, 1, 10, 23, 3, 2, 6, 8, 6, 10, 6]) + res = stats.mode(data1, nan_policy='omit') + xp_assert_equal(res.mode, xp.asarray(6.)) + xp_assert_equal(res.count, xp.asarray(3)) + assert_raises(ValueError, stats.mode, data1, nan_policy='raise') + assert_raises(ValueError, stats.mode, data1, nan_policy='foobar') + + @skip_xp_backends(eager_only=True, reason="lazy arrays don't do 'omit'.") + @pytest.mark.parametrize("data", [ + [3, 5, 1, 1, 3.], + [3, np.nan, 5, 1, 1, 3], + [3, 5, 1.], + [3, np.nan, 5, 1], + ]) + @pytest.mark.parametrize('keepdims', [False, True]) + def test_smallest_equal(self, data, keepdims, xp): + result = stats.mode(xp.asarray(data), nan_policy='omit', keepdims=keepdims) + if keepdims: + xp_assert_equal(result[0][0], xp.asarray(1.)) + else: + xp_assert_equal(result[0], xp.asarray(1.)) + + @pytest.mark.parametrize('axis', range(-3, 3)) + def test_mode_shape_gh_9955(self, axis, xp): + rng = np.random.default_rng(984213899) + a = xp.asarray(rng.uniform(size=(3, 4, 5))) + res = stats.mode(a, axis=axis, keepdims=False) + reference_shape = list(a.shape) + reference_shape.pop(axis) + np.testing.assert_array_equal(res.mode.shape, reference_shape) + np.testing.assert_array_equal(res.count.shape, reference_shape) + + def test_nan_policy_propagate_gh_9815(self, xp): + # mode should treat np.nan as it would any other object when + # nan_policy='propagate' + a = xp.asarray([2, np.nan, 1, np.nan]) + res = stats.mode(a) + assert xp.isnan(res.mode) and res.count == 2 + + def test_keepdims_empty(self, xp): + # test empty arrays + a = xp.zeros((1, 2, 3, 0)) + + res = stats.mode(a, axis=1, keepdims=False) + assert res.mode.shape == res.count.shape == (1, 3, 0) + + res = stats.mode(a, axis=1, keepdims=True) + assert res.mode.shape == res.count.shape == (1, 1, 3, 0) + + def test_keepdims_nonempty(selfself, xp): + # test nan_policy='propagate' + a = xp.asarray([[1, 3, 3, np.nan], [1, 1, np.nan, 1]]) + + res = stats.mode(a, axis=1, keepdims=False) + xp_assert_equal(res.mode, xp.asarray([3., 1.])) + xp_assert_equal(res.count, xp.asarray([2, 3])) + + res = stats.mode(a, axis=1, keepdims=True) + xp_assert_equal(res.mode, xp.asarray([[3.], [1.]])) + xp_assert_equal(res.count, xp.asarray([[2], [3]])) + + a = xp.asarray(a) + res = stats.mode(a, axis=None, keepdims=False) + ref = stats.mode(xp_ravel(a), keepdims=False) + xp_assert_equal(res.mode, ref.mode) + xp_assert_equal(res.count, ref.count) + assert res.mode.shape == ref.mode.shape == () + + res = stats.mode(a, axis=None, keepdims=True) + ref = stats.mode(xp_ravel(a), keepdims=True) + xp_assert_equal(xp_ravel(res.mode), xp_ravel(ref.mode)) + assert res.mode.shape == (1, 1) + xp_assert_equal(xp_ravel(res.count), xp_ravel(ref.count)) + assert res.count.shape == (1, 1) + + def test_keepdims_nan_omit(self): + # test nan_policy='omit' + a = [[1, np.nan, np.nan, np.nan, 1], + [np.nan, np.nan, np.nan, np.nan, 2], + [1, 2, np.nan, 5, 5]] + + res = stats.mode(a, axis=1, keepdims=False, nan_policy='omit') + assert_array_equal(res.mode, [1, 2, 5]) + assert_array_equal(res.count, [2, 1, 2]) + + res = stats.mode(a, axis=1, keepdims=True, nan_policy='omit') + assert_array_equal(res.mode, [[1], [2], [5]]) + assert_array_equal(res.count, [[2], [1], [2]]) + + a = np.array(a) + res = stats.mode(a, axis=None, keepdims=False, nan_policy='omit') + ref = stats.mode(a.ravel(), keepdims=False, nan_policy='omit') + assert_array_equal(res, ref) + assert res.mode.shape == ref.mode.shape == () + + res = stats.mode(a, axis=None, keepdims=True, nan_policy='omit') + ref = stats.mode(a.ravel(), keepdims=True, nan_policy='omit') + assert_equal(res.mode.ravel(), ref.mode.ravel()) + assert res.mode.shape == (1, 1) + assert_equal(res.count.ravel(), ref.count.ravel()) + assert res.count.shape == (1, 1) + + @pytest.mark.parametrize("nan_policy", ['propagate', 'omit']) + def test_gh16955(self, nan_policy): + # Check that bug reported in gh-16955 is resolved + shape = (4, 3) + data = np.ones(shape) + data[0, 0] = np.nan + res = stats.mode(a=data, axis=1, keepdims=False, nan_policy=nan_policy) + assert_array_equal(res.mode, [1, 1, 1, 1]) + assert_array_equal(res.count, [2, 3, 3, 3]) + + # Test with input from gh-16595. Support for non-numeric input + # was deprecated, so check for the appropriate error. + my_dtype = np.dtype([('asdf', np.uint8), ('qwer', np.float64, (3,))]) + test = np.zeros(10, dtype=my_dtype) + message = "Argument `a` is not....|An argument has dtype...|The DType..." + with pytest.raises(TypeError, match=message): + stats.mode(test, nan_policy=nan_policy) + + def test_gh9955(self): + # The behavior of mode with empty slices (whether the input was empty + # or all elements were omitted) was inconsistent. Test that this is + # resolved: the mode of an empty slice is NaN and the count is zero. + with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit): + res = stats.mode([]) + ref = (np.nan, 0) + assert_equal(res, ref) + + with pytest.warns(SmallSampleWarning, match=too_small_1d_omit): + res = stats.mode([np.nan], nan_policy='omit') + assert_equal(res, ref) + + a = [[10., 20., 20.], [np.nan, np.nan, np.nan]] + with pytest.warns(SmallSampleWarning, match=too_small_nd_omit): + res = stats.mode(a, axis=1, nan_policy='omit') + ref = ([20, np.nan], [2, 0]) + assert_equal(res, ref) + + res = stats.mode(a, axis=1, nan_policy='propagate') + ref = ([20, np.nan], [2, 3]) + assert_equal(res, ref) + + z = np.array([[], []]) + with pytest.warns(SmallSampleWarning, match=too_small_nd_not_omit): + res = stats.mode(z, axis=1) + ref = ([np.nan, np.nan], [0, 0]) + assert_equal(res, ref) + + @pytest.mark.filterwarnings('ignore::RuntimeWarning') # np.mean warns + @pytest.mark.parametrize('z', [np.empty((0, 1, 2)), np.empty((1, 1, 2))]) + def test_gh17214(self, z, xp): + z = xp.asarray(z) + if z.size == 0: + with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit): + res = stats.mode(z, axis=None, keepdims=True) + else: + res = stats.mode(z, axis=None, keepdims=True) + ref = xp.mean(z, axis=None, keepdims=True) + assert res[0].shape == res[1].shape == ref.shape == (1, 1, 1) + + def test_raise_non_numeric_gh18254(self): + + class ArrLike: + def __init__(self, x): + self._x = x + + def __array__(self, dtype=None, copy=None): + return self._x.astype(object) + + message = ("...only boolean and numerical dtypes..." if SCIPY_ARRAY_API + else "Cannot interpret...") + + with pytest.raises(TypeError, match=message): + stats.mode(ArrLike(np.arange(3))) + + message = ("...only boolean and numerical dtypes..." if SCIPY_ARRAY_API + else "Argument `a` is not recognized as numeric.") + with pytest.raises(TypeError, match=message): + stats.mode(np.arange(3, dtype=object)) + + +@make_xp_test_case(stats.sem) +class TestSEM: + + testcase = [1., 2., 3., 4.] + scalar_testcase = 4. + + @pytest.mark.filterwarnings("ignore:invalid value encountered in divide") + def test_sem_scalar(self, xp): + # This is not in R, so used: + # sqrt(var(testcase)*3/4)/sqrt(3) + + # y = stats.sem(self.shoes[0]) + # assert_approx_equal(y,0.775177399) + scalar_testcase = xp.asarray(self.scalar_testcase)[()] + if is_numpy(xp): + with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit): + y = stats.sem(scalar_testcase) + else: + # Other array types can emit a variety of warnings. + with warnings.catch_warnings(): + warnings.simplefilter("ignore", UserWarning) + warnings.simplefilter("ignore", RuntimeWarning) + y = stats.sem(scalar_testcase) + assert xp.isnan(y) + + def test_sem(self, xp): + testcase = xp.asarray(self.testcase) + y = stats.sem(testcase) + xp_assert_close(y, xp.asarray(0.6454972244)) + n = len(self.testcase) + xp_assert_close(stats.sem(testcase, ddof=0) * (n/(n-2))**0.5, + stats.sem(testcase, ddof=2)) + + x = xp.arange(10.) + x = xp.where(x == 9, xp.nan, x) + xp_assert_equal(stats.sem(x), xp.asarray(xp.nan)) + + @skip_xp_backends(eager_only=True, reason='lazy -> reduced nan_policy capabilities') + def test_sem_nan_policy(self, xp): + x = xp.arange(10.) + x[9] = xp.nan + xp_assert_close(stats.sem(x, nan_policy='omit'), xp.asarray(0.9128709291752769)) + message = "The input contains nan values" + with pytest.raises(ValueError, match=message): + stats.sem(x, nan_policy='raise') + message = "nan_policy must be one of..." + with pytest.raises(ValueError, match=message): + stats.sem(x, nan_policy='foobar') + + +@make_xp_test_case(stats.zmap) +class TestZmap: + + @pytest.mark.parametrize( + 'x, y', + [([1., 2., 3., 4.], [1., 2., 3., 4.]), + ([1., 2., 3.], [0., 1., 2., 3., 4.])] + ) + def test_zmap(self, x, y, xp): + # For these simple cases, calculate the expected result directly + # by using the formula for the z-score. + x, y = xp.asarray(x), xp.asarray(y) + expected = (x - xp.mean(y)) / xp.std(y, correction=0) + z = stats.zmap(x, y) + xp_assert_close(z, expected) + + def test_zmap_axis(self, xp): + # Test use of 'axis' keyword in zmap. + x = xp.asarray([[0.0, 0.0, 1.0, 1.0], + [1.0, 1.0, 1.0, 2.0], + [2.0, 0.0, 2.0, 0.0]]) + + t1 = 1.0/(2.0/3)**0.5 + t2 = 3.**0.5/3 + t3 = 2.**0.5 + + z0 = stats.zmap(x, x, axis=0) + z1 = stats.zmap(x, x, axis=1) + + z0_expected = [[-t1, -t3/2, -t3/2, 0.0], + [0.0, t3, -t3/2, t1], + [t1, -t3/2, t3, -t1]] + z1_expected = [[-1.0, -1.0, 1.0, 1.0], + [-t2, -t2, -t2, 3.**0.5], + [1.0, -1.0, 1.0, -1.0]] + z0_expected = xp.asarray(z0_expected) + z1_expected = xp.asarray(z1_expected) + + xp_assert_close(z0, z0_expected) + xp_assert_close(z1, z1_expected) + + def test_zmap_ddof(self, xp): + # Test use of 'ddof' keyword in zmap. + x = xp.asarray([[0.0, 0.0, 1.0, 1.0], + [0.0, 1.0, 2.0, 3.0]]) + + z = stats.zmap(x, x, axis=1, ddof=1) + + z0_expected = xp.asarray([-0.5, -0.5, 0.5, 0.5])/(1.0/3**0.5) + z1_expected = xp.asarray([-1.5, -0.5, 0.5, 1.5])/(5./3)**0.5 + xp_assert_close(z[0, :], z0_expected) + xp_assert_close(z[1, :], z1_expected) + + @pytest.mark.parametrize('ddof', [0, 2]) + def test_zmap_nan_policy_omit(self, ddof, xp): + # nans in `scores` are propagated, regardless of `nan_policy`. + # `nan_policy` only affects how nans in `compare` are handled. + scores = xp.asarray([-3, -1, 2, np.nan]) + compare = xp.asarray([-8, -3, 2, 7, 12, np.nan]) + z = stats.zmap(scores, compare, ddof=ddof, nan_policy='omit') + # exclude nans from compare, don't use isnan + mask since that messes up + # dask + ref = stats.zmap(scores, compare[:5], ddof=ddof) + xp_assert_close(z, ref) + + @pytest.mark.parametrize('ddof', [0, 2]) + def test_zmap_nan_policy_omit_with_axis(self, ddof, xp): + scores = xp.reshape(xp.arange(-5.0, 9.0), (2, -1)) + compare = np.reshape(np.linspace(-8, 6, 24), (2, -1)) + compare[0, 4] = np.nan + compare[0, 6] = np.nan + compare[1, 1] = np.nan + # convert from numpy since some libraries like dask + # can't handle the data-dependent shapes from the isnan masking + compare_0_notna = xp.asarray(compare[0, :][~np.isnan(compare[0, :])]) + compare_1_notna = xp.asarray(compare[1, :][~np.isnan(compare[1, :])]) + compare = xp.asarray(compare) + + z = stats.zmap(scores, compare, nan_policy='omit', axis=1, ddof=ddof) + res0 = stats.zmap(scores[0, :], compare_0_notna, + ddof=ddof) + res1 = stats.zmap(scores[1, :], compare_1_notna, + ddof=ddof) + expected = xp.stack((res0, res1)) + xp_assert_close(z, expected) + + @skip_xp_backends(eager_only=True, reason="lazy arrays don't do 'raise'.") + def test_zmap_nan_policy_raise(self, xp): + scores = xp.asarray([1, 2, 3]) + compare = xp.asarray([-8, -3, 2, 7, 12, xp.nan]) + with pytest.raises(ValueError, match='input contains nan'): + stats.zmap(scores, compare, nan_policy='raise') + + @pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning:dask") + @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") + def test_degenerate_input(self, xp): + scores = xp.arange(3) + compare = xp.ones(3) + ref = xp.asarray([-xp.inf, xp.nan, xp.inf]) + with eager_warns(RuntimeWarning, match="Precision loss occurred...", xp=xp): + res = stats.zmap(scores, compare) + xp_assert_equal(res, ref) + + @pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning:dask") + def test_complex_gh22404(self, xp): + res = stats.zmap(xp.asarray([1, 2, 3, 4]), xp.asarray([1, 1j, -1, -1j])) + ref = xp.asarray([1.+0.j, 2.+0.j, 3.+0.j, 4.+0.j]) + xp_assert_close(res, ref) + + +@make_xp_test_case(stats.zscore) +class TestZscore: + def test_zscore(self, xp): + # not in R, so tested by using: + # (testcase[i] - mean(testcase, axis=0)) / sqrt(var(testcase) * 3/4) + y = stats.zscore(xp.asarray([1, 2, 3, 4])) + desired = [-1.3416407864999, -0.44721359549996, + 0.44721359549996, 1.3416407864999] + xp_assert_close(y, xp.asarray(desired)) + + def test_zscore_axis(self, xp): + # Test use of 'axis' keyword in zscore. + x = xp.asarray([[0.0, 0.0, 1.0, 1.0], + [1.0, 1.0, 1.0, 2.0], + [2.0, 0.0, 2.0, 0.0]]) + + t1 = 1.0/(2.0/3)**0.5 + t2 = 3**0.5/3 + t3 = 2**0.5 + + z0 = stats.zscore(x, axis=0) + z1 = stats.zscore(x, axis=1) + + z0_expected = [[-t1, -t3/2, -t3/2, 0.0], + [0.0, t3, -t3/2, t1], + [t1, -t3/2, t3, -t1]] + z1_expected = [[-1.0, -1.0, 1.0, 1.0], + [-t2, -t2, -t2, 3**0.5], + [1.0, -1.0, 1.0, -1.0]] + + xp_assert_close(z0, xp.asarray(z0_expected)) + xp_assert_close(z1, xp.asarray(z1_expected)) + + def test_zscore_ddof(self, xp): + # Test use of 'ddof' keyword in zscore. + x = xp.asarray([[0.0, 0.0, 1.0, 1.0], + [0.0, 1.0, 2.0, 3.0]]) + + z = stats.zscore(x, axis=1, ddof=1) + + z0_expected = xp.asarray([-0.5, -0.5, 0.5, 0.5])/(1.0/3**0.5) + z1_expected = xp.asarray([-1.5, -0.5, 0.5, 1.5])/((5./3)**0.5) + xp_assert_close(z[0, :], z0_expected) + xp_assert_close(z[1, :], z1_expected) + + def test_zscore_nan_propagate(self, xp): + x = xp.asarray([1, 2, np.nan, 4, 5]) + z = stats.zscore(x, nan_policy='propagate') + xp_assert_equal(z, xp.full(x.shape, xp.nan)) + + def test_zscore_nan_omit(self, xp): + x = xp.asarray([1, 2, xp.nan, 4, 5]) + + z = stats.zscore(x, nan_policy='omit') + + expected = xp.asarray([-1.2649110640673518, + -0.6324555320336759, + xp.nan, + 0.6324555320336759, + 1.2649110640673518 + ]) + xp_assert_close(z, expected) + + def test_zscore_nan_omit_with_ddof(self, xp): + x = xp.asarray([xp.nan, 1.0, 3.0, 5.0, 7.0, 9.0]) + z = stats.zscore(x, ddof=1, nan_policy='omit') + expected = xp.concat([xp.asarray([xp.nan]), stats.zscore(x[1:], ddof=1)]) + xp_assert_close(z, expected) + + @skip_xp_backends(eager_only=True, reason="lazy arrays don't do 'raise'.") + def test_zscore_nan_raise(self, xp): + x = xp.asarray([1, 2, xp.nan, 4, 5]) + with pytest.raises(ValueError, match="The input contains nan..."): + stats.zscore(x, nan_policy='raise') + + def test_zscore_constant_input_1d(self, xp): + x = xp.asarray([-0.087] * 3) + with eager_warns(RuntimeWarning, match="Precision loss occurred...", xp=xp): + z = stats.zscore(x) + xp_assert_equal(z, xp.full(x.shape, xp.nan)) + + @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") + def test_zscore_constant_input_2d(self, xp): + x = xp.asarray([[10.0, 10.0, 10.0, 10.0], + [10.0, 11.0, 12.0, 13.0]]) + with eager_warns(RuntimeWarning, match="Precision loss occurred...", xp=xp): + z0 = stats.zscore(x, axis=0) + xp_assert_close(z0, xp.asarray([[xp.nan, -1.0, -1.0, -1.0], + [xp.nan, 1.0, 1.0, 1.0]])) + + with eager_warns(RuntimeWarning, match="Precision loss occurred...", xp=xp): + z1 = stats.zscore(x, axis=1) + xp_assert_equal(z1, xp.stack([xp.asarray([xp.nan, xp.nan, xp.nan, xp.nan]), + stats.zscore(x[1, :])])) + + z = stats.zscore(x, axis=None) + xp_assert_equal(z, xp.reshape(stats.zscore(xp.reshape(x, (-1,))), x.shape)) + + y = xp.ones((3, 6)) + with eager_warns(RuntimeWarning, match="Precision loss occurred...", xp=xp): + z = stats.zscore(y, axis=None) + xp_assert_equal(z, xp.full_like(y, xp.nan)) + + @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") + def test_zscore_constant_input_2d_nan_policy_omit(self, xp): + x = xp.asarray([[10.0, 10.0, 10.0, 10.0], + [10.0, 11.0, 12.0, xp.nan], + [10.0, 12.0, xp.nan, 10.0]]) + s = (3/2)**0.5 + s2 = 2**0.5 + + with eager_warns(RuntimeWarning, match="Precision loss occurred...", xp=xp): + z0 = stats.zscore(x, nan_policy='omit', axis=0) + xp_assert_close(z0, xp.asarray([[xp.nan, -s, -1.0, xp.nan], + [xp.nan, 0, 1.0, xp.nan], + [xp.nan, s, xp.nan, xp.nan]])) + + with eager_warns(RuntimeWarning, match="Precision loss occurred...", xp=xp): + z1 = stats.zscore(x, nan_policy='omit', axis=1) + xp_assert_close(z1, xp.asarray([[xp.nan, xp.nan, xp.nan, xp.nan], + [-s, 0, s, xp.nan], + [-s2/2, s2, xp.nan, -s2/2]])) + + @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") + def test_zscore_2d_all_nan_row(self, xp): + # A row is all nan, and we use axis=1. + x = xp.asarray([[np.nan, np.nan, np.nan, np.nan], + [10.0, 10.0, 12.0, 12.0]]) + z = stats.zscore(x, nan_policy='omit', axis=1) + xp_assert_close(z, xp.asarray([[np.nan, np.nan, np.nan, np.nan], + [-1.0, -1.0, 1.0, 1.0]])) + + @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") + def test_zscore_2d_all_nan(self, xp): + # The entire 2d array is nan, and we use axis=None. + y = xp.full((2, 3), xp.nan) + z = stats.zscore(y, nan_policy='omit', axis=None) + xp_assert_equal(z, y) + + @pytest.mark.parametrize('x', [np.array([]), np.zeros((3, 0, 5))]) + def test_zscore_empty_input(self, x, xp): + x = xp.asarray(x) + z = stats.zscore(x) + xp_assert_equal(z, x) + + @skip_xp_invalid_arg + def test_zscore_masked_element_0_gh19039(self, xp): + # zscore returned all NaNs when 0th element was masked. See gh-19039. + rng = np.random.default_rng(8675309) + x = rng.standard_normal(10) + mask = np.zeros_like(x) + y = np.ma.masked_array(x, mask) + y.mask[0] = True + + ref = stats.zscore(x[1:]) # compute reference from non-masked elements + assert not np.any(np.isnan(ref)) + res = stats.zscore(y) + assert_allclose(res[1:], ref) + res = stats.zscore(y, axis=None) + assert_allclose(res[1:], ref) + + y[1:] = y[1] # when non-masked elements are identical, result is nan + with pytest.warns(RuntimeWarning, match="Precision loss occurred..."): + res = stats.zscore(y) + assert_equal(res[1:], np.nan) + with pytest.warns(RuntimeWarning, match="Precision loss occurred..."): + res = stats.zscore(y, axis=None) + assert_equal(res[1:], np.nan) + + +@make_xp_test_case(stats.gzscore) +class TestGZscore: + def test_gzscore_normal_array(self, xp): + x = np.asarray([1, 2, 3, 4]) + z = stats.gzscore(xp.asarray(x)) + desired = np.log(x / stats.gmean(x)) / np.log(stats.gstd(x, ddof=0)) + xp_assert_close(z, xp.asarray(desired, dtype=xp.asarray(1.).dtype)) + + @skip_xp_invalid_arg + def test_gzscore_masked_array(self): + x = np.array([1, 2, -1, 3, 4]) + mask = [0, 0, 1, 0, 0] + mx = np.ma.masked_array(x, mask=mask) + z = stats.gzscore(mx) + desired = ([-1.526072095151, -0.194700599824, np.inf, 0.584101799472, + 1.136670895503]) + desired = np.ma.masked_array(desired, mask=mask) + assert_allclose(z.compressed(), desired.compressed()) + assert_allclose(z.mask, desired.mask) + assert isinstance(z, np.ma.MaskedArray) + + +@make_xp_test_case(stats.median_abs_deviation) +class TestMedianAbsDeviation: + def setup_method(self): + self.dat_nan = [2.20, 2.20, 2.4, 2.4, 2.5, 2.7, 2.8, 2.9, + 3.03, 3.03, 3.10, 3.37, 3.4, 3.4, 3.4, 3.5, + 3.6, 3.7, 3.7, 3.7, 3.7, 3.77, 5.28, np.nan] + self.dat = [2.20, 2.20, 2.4, 2.4, 2.5, 2.7, 2.8, 2.9, 3.03, + 3.03, 3.10, 3.37, 3.4, 3.4, 3.4, 3.5, 3.6, 3.7, + 3.7, 3.7, 3.7, 3.77, 5.28, 28.95] + + def test_median_abs_deviation(self, xp): + xp_assert_close(stats.median_abs_deviation(xp.asarray(self.dat), axis=None), + xp.asarray(0.355)) + dat = xp.reshape(xp.asarray(self.dat), (6, 4)) + mad = stats.median_abs_deviation(dat, axis=0) + mad_expected = xp.asarray([0.435, 0.5, 0.45, 0.4]) + xp_assert_close(mad, mad_expected) + + @skip_xp_backends('jax.numpy', reason='lazy -> no nan_policy="omit"') + def test_mad_nan_omit(self, xp): + mad = stats.median_abs_deviation(xp.asarray(self.dat_nan), nan_policy='omit') + xp_assert_close(mad, xp.asarray(0.34)) + + def test_axis_and_nan(self, xp): + x = xp.asarray([[1.0, 2.0, 3.0, 4.0, np.nan], + [1.0, 4.0, 5.0, 8.0, 9.0]]) + mad = stats.median_abs_deviation(x, axis=1) + xp_assert_close(mad, xp.asarray([np.nan, 3.0])) + + @skip_xp_backends('jax.numpy', reason='lazy -> no nan_policy="omit"') + def test_nan_policy_omit_with_inf(self, xp): + z = xp.asarray([1, 3, 4, 6, 99, np.nan, np.inf]) + mad = stats.median_abs_deviation(z, nan_policy='omit') + xp_assert_close(mad, xp.asarray(3.0)) + + @pytest.mark.parametrize('axis', [0, 1, 2, None]) + def test_size_zero_with_axis(self, axis, xp): + x = xp.zeros((3, 0, 4)) + context = (eager_warns(SmallSampleWarning, match='too small', xp=np) + if axis in {1, None} else contextlib.nullcontext()) + with context: + mad = stats.median_abs_deviation(x, axis=axis) + xp_assert_close(mad, xp.full_like(xp.sum(x, axis=axis), fill_value=xp.nan)) + + @pytest.mark.parametrize('nan_policy, expected', + [('omit', [np.nan, 1.5, 1.5]), + ('propagate', [np.nan, np.nan, 1.5])]) + def test_nan_policy_with_axis(self, nan_policy, expected, xp): + if nan_policy=='omit' and not is_numpy(xp): + pytest.skip("nan_policy='omit' with n-d input only supported by NumPy") + + x = xp.asarray([[np.nan, np.nan, np.nan, np.nan, np.nan, np.nan], + [1, 5, 3, 6, np.nan, np.nan], + [5, 6, 7, 9, 9, 10]]) + + context = (eager_warns(SmallSampleWarning, match="too small", xp=np) + if nan_policy == 'omit' else contextlib.nullcontext()) + with context: + mad = stats.median_abs_deviation(x, nan_policy=nan_policy, axis=1) + xp_assert_close(mad, xp.asarray(expected)) + + @pytest.mark.parametrize('axis, expected', + [(1, [2.5, 2.0, 12.0]), (None, 4.5)]) + def test_center_mean_with_nan(self, axis, expected): + # nan_policy='omit' with multidimensional input only supported by NumPy + x = np.array([[1, 2, 4, 9, np.nan], + [0, 1, 1, 1, 12], + [-10, -10, -10, 20, 20]]) + mad = stats.median_abs_deviation(x, center=np.mean, nan_policy='omit', + axis=axis) + xp_assert_close(mad, expected, rtol=1e-15, atol=1e-15) + + def test_center_not_callable(self, xp): + with pytest.raises(TypeError, match='callable'): + stats.median_abs_deviation(xp.asarray([1, 2, 3, 5]), center=99) + + +@make_xp_test_case(stats.iqr) +class TestIQR: + + @pytest.mark.parametrize('dtype', [None, 'float32', 'float64']) + def test_basic(self, dtype, xp): + x = np.arange(8) * 0.5 + np.random.shuffle(x) + dtype = xp_default_dtype(xp) if dtype is None else getattr(xp, dtype) + xp_assert_equal(stats.iqr(xp.asarray(x, dtype=dtype)), + xp.asarray(1.75, dtype=dtype)) + + def test_api(self, xp): + d = xp.ones((5, 5)) + stats.iqr(d) + stats.iqr(d, None) + stats.iqr(d, 1) + stats.iqr(d, (0, 1)) + stats.iqr(d, None, (10, 90)) + stats.iqr(d, None, (30, 20), 1.0) + stats.iqr(d, None, (25, 75), 1.5, 'propagate') + + @skip_xp_backends('jax.numpy', reason='lazy -> reduced nan_policy capabilities') + def test_api_eager(self, xp): + d = xp.ones((5, 5)) + stats.iqr(d, None, (50, 50), 'normal', 'raise', 'linear') + stats.iqr(d, None, (25, 75), -0.4, 'omit', 'lower', True) + + @pytest.mark.parametrize('x', [[], np.arange(0)]) + def test_empty(self, x, xp): + with eager_warns(SmallSampleWarning, match=too_small_1d_not_omit, xp=xp): + xp_assert_equal(stats.iqr(xp.asarray(x)), xp.asarray(xp.nan)) + + def test_constant(self, xp): + # Constant array always gives 0 + x = xp.ones((7, 4)) + zero = xp.asarray(0.0) + xp_assert_equal(stats.iqr(x), zero) + xp_assert_equal(stats.iqr(x, axis=0), xp.zeros(4)) + xp_assert_equal(stats.iqr(x, axis=1), xp.zeros(7)) + xp_assert_equal(stats.iqr(x, interpolation='linear'), zero) + xp_assert_equal(stats.iqr(x, interpolation='midpoint'), zero) + xp_assert_equal(stats.iqr(x, interpolation='nearest'), zero) + xp_assert_equal(stats.iqr(x, interpolation='lower'), zero) + xp_assert_equal(stats.iqr(x, interpolation='higher'), zero) + + # 0 only along constant dimensions + # This also tests much of `axis` + y = xp.ones((4, 5, 6)) * xp.arange(6.) + xp_assert_equal(stats.iqr(y, axis=0), xp.zeros((5, 6))) + xp_assert_equal(stats.iqr(y, axis=1), xp.zeros((4, 6))) + xp_assert_equal(stats.iqr(y, axis=2), xp.full((4, 5), 2.5)) + xp_assert_equal(stats.iqr(y, axis=(0, 1)), xp.zeros(6)) + xp_assert_equal(stats.iqr(y, axis=(0, 2)), xp.full(5, 3.)) + xp_assert_equal(stats.iqr(y, axis=(1, 2)), xp.full(4, 3.)) + + def test_scalarlike(self, xp): + x = xp.arange(1.) + 7.0 + xp_assert_equal(stats.iqr(x[0]), xp.asarray(0.0)) + xp_assert_equal(stats.iqr(x), xp.asarray(0.0)) + xp_assert_equal(stats.iqr(x, keepdims=True), xp.asarray([0.0])) + + def test_2D(self, xp): + x = xp.reshape(xp.arange(15), (3, 5)) + xp_assert_equal(stats.iqr(x), xp.asarray(7.0)) + xp_assert_equal(stats.iqr(x, axis=0), xp.full(5, 5.)) + xp_assert_equal(stats.iqr(x, axis=1), xp.full(3, 2.)) + xp_assert_equal(stats.iqr(x, axis=(0, 1)), xp.asarray(7.0)) + xp_assert_equal(stats.iqr(x, axis=(1, 0)), xp.asarray(7.0)) + + def test_axis(self, xp): + # The `axis` keyword is also put through its paces in `test_keepdims`. + o = np.random.normal(size=(71, 23)) + x = np.dstack([o] * 10) # x.shape = (71, 23, 10) + o, x = xp.asarray(o), xp.asarray(x) + q = xp.broadcast_to(stats.iqr(o), (10,)) + + xp_assert_equal(stats.iqr(x, axis=(0, 1)), q) + x = xp.moveaxis(x, -1, 0) # x.shape = (10, 71, 23) + xp_assert_equal(stats.iqr(x, axis=(2, 1)), q) + x = xp_swapaxes(x,0, 1, xp=xp) # x.shape = (71, 10, 23) + xp_assert_equal(stats.iqr(x, axis=(0, 2)), q) + x = xp_swapaxes(x,0, 1, xp=xp) # x.shape = (10, 71, 23) + + xp_assert_equal(stats.iqr(x, axis=(0, 1, 2)), + stats.iqr(x, axis=None)) + xp_assert_equal(stats.iqr(x, axis=(0,)), + stats.iqr(x, axis=0)) + + d = np.arange(3 * 5 * 7 * 11) + # Older versions of numpy only shuffle along axis=0. + # Not sure about newer, don't care. + np.random.shuffle(d) + d = d.reshape((3, 5, 7, 11)) + d = xp.asarray(d) + xp_assert_equal(stats.iqr(d, axis=(0, 1, 2))[0], + stats.iqr(xp_ravel(d[:,:,:, 0]))) + xp_assert_equal(stats.iqr(d, axis=(0, 1, 3))[1], + stats.iqr(xp_ravel(d[:,:, 1,:]))) + xp_assert_equal(stats.iqr(d, axis=(3, 1, -4))[2], + stats.iqr(xp_ravel(d[:,:, 2,:]))) + xp_assert_equal(stats.iqr(d, axis=(3, 1, 2))[2], + stats.iqr(xp_ravel(d[2,:,:,:]))) + xp_assert_equal(stats.iqr(d, axis=(3, 2))[2, 1], + stats.iqr(xp_ravel(d[2, 1,:,:]))) + xp_assert_equal(stats.iqr(d, axis=(1, -2))[2, 1], + stats.iqr(xp_ravel(d[2, :, :, 1]))) + xp_assert_equal(stats.iqr(d, axis=(1, 3))[2, 2], + stats.iqr(xp_ravel(d[2, :, 2,:]))) + + with pytest.raises(AxisError, match='`axis` is out of bounds...'): + stats.iqr(d, axis=4) + with pytest.raises(ValueError, match='`axis` must contain only...'): + stats.iqr(d, axis=(0, 0)) + + def test_rng(self, xp): + x = xp.arange(5) + xp_assert_close(stats.iqr(x), xp.asarray(2.)) + xp_assert_close(stats.iqr(x, rng=(25, 87.5)), xp.asarray(2.5)) + xp_assert_close(stats.iqr(x, rng=(12.5, 75)), xp.asarray(2.5)) + xp_assert_close(stats.iqr(x, rng=(10, 50)), xp.asarray(1.6)) # 3-1.4 + + message = r"Elements of `rng` must be in the range \[0, 100\]." + with pytest.raises(ValueError, match=message): + stats.iqr(x, rng=(0, 101)) + + message = "`rng` must not contain NaNs." + with pytest.raises(ValueError, match=message): + stats.iqr(x, rng=(np.nan, 25)) + + message = "`rng` must be a two element sequence." + with pytest.raises(TypeError, match=message): + stats.iqr(x, rng=(0, 50, 60)) + + def test_interpolation(self, xp): + x = xp.arange(5) + y = xp.arange(4) + # Default + xp_assert_equal(stats.iqr(x), xp.asarray(2.)) + xp_assert_equal(stats.iqr(y), xp.asarray(1.5)) + # Linear + xp_assert_equal(stats.iqr(x, interpolation='linear'), xp.asarray(2.)) + xp_assert_equal(stats.iqr(y, interpolation='linear'), xp.asarray(1.5)) + # Higher + xp_assert_equal(stats.iqr(x, interpolation='higher'), xp.asarray(2.)) + xp_assert_equal(stats.iqr(x, rng=(25, 80), interpolation='higher'), + xp.asarray(3.)) + xp_assert_equal(stats.iqr(y, interpolation='higher'), xp.asarray(2.)) + # Lower (will generally, but not always be the same as higher) + xp_assert_equal(stats.iqr(x, interpolation='lower'), xp.asarray(2.)) + xp_assert_equal(stats.iqr(x, rng=(25, 80), interpolation='lower'), + xp.asarray(2.)) + xp_assert_equal(stats.iqr(y, interpolation='lower'), xp.asarray(2.)) + # Nearest + xp_assert_equal(stats.iqr(x, interpolation='nearest'), xp.asarray(2.)) + xp_assert_equal(stats.iqr(y, interpolation='nearest'), xp.asarray(1.)) + # Midpoint + xp_assert_equal(stats.iqr(x, interpolation='midpoint'), xp.asarray(2.)) + xp_assert_equal(stats.iqr(x, rng=(25, 80), interpolation='midpoint'), + xp.asarray(2.5)) + xp_assert_equal(stats.iqr(y, interpolation='midpoint'), xp.asarray(2.)) + + # Check all method= values new in numpy 1.22.0 are accepted + for method in ('inverted_cdf', 'averaged_inverted_cdf', + 'closest_observation', 'interpolated_inverted_cdf', + 'hazen', 'weibull', 'median_unbiased', + 'normal_unbiased'): + stats.iqr(y, interpolation=method) + + with pytest.raises(ValueError, match='`method` must be one of...'): + stats.iqr(x, interpolation='foobar') + + def test_keepdims(self, xp): + # Also tests most of `axis` + x = xp.ones((3, 5, 7, 11)) + assert_equal(stats.iqr(x, axis=None, keepdims=False).shape, ()) + assert_equal(stats.iqr(x, axis=2, keepdims=False).shape, (3, 5, 11)) + assert_equal(stats.iqr(x, axis=(0, 1), keepdims=False).shape, (7, 11)) + assert_equal(stats.iqr(x, axis=(0, 3), keepdims=False).shape, (5, 7)) + assert_equal(stats.iqr(x, axis=(1,), keepdims=False).shape, (3, 7, 11)) + assert_equal(stats.iqr(x, (0, 1, 2, 3), keepdims=False).shape, ()) + assert_equal(stats.iqr(x, axis=(0, 1, 3), keepdims=False).shape, (7,)) + + assert_equal(stats.iqr(x, axis=None, keepdims=True).shape, (1, 1, 1, 1)) + assert_equal(stats.iqr(x, axis=2, keepdims=True).shape, (3, 5, 1, 11)) + assert_equal(stats.iqr(x, axis=(0, 1), keepdims=True).shape, (1, 1, 7, 11)) + assert_equal(stats.iqr(x, axis=(0, 3), keepdims=True).shape, (1, 5, 7, 1)) + assert_equal(stats.iqr(x, axis=(1,), keepdims=True).shape, (3, 1, 7, 11)) + assert_equal(stats.iqr(x, (0, 1, 2, 3), keepdims=True).shape, (1, 1, 1, 1)) + assert_equal(stats.iqr(x, axis=(0, 1, 3), keepdims=True).shape, (1, 1, 7, 1)) + + @skip_xp_backends('jax.numpy', reason='lazy -> reduced nan_policy capabilitiesy') + def test_nanpolicy(self, xp): + x = xp.reshape(xp.arange(15.0), (3, 5)) + + # No NaNs + xp_assert_equal(stats.iqr(x, nan_policy='propagate'), xp.asarray(7.)) + xp_assert_equal(stats.iqr(x, nan_policy='omit'), xp.asarray(7.)) + xp_assert_equal(stats.iqr(x, nan_policy='raise'), xp.asarray(7.)) + + # Yes NaNs + x = xpx.at(x)[1, 2].set(xp.nan) + xp_assert_equal(stats.iqr(x, nan_policy='propagate'), + xp.asarray(xp.nan)) + xp_assert_equal(stats.iqr(x, axis=0, nan_policy='propagate'), + xp.asarray([5, 5, xp.nan, 5, 5])) + xp_assert_equal(stats.iqr(x, axis=1, nan_policy='propagate'), + xp.asarray([2, xp.nan, 2])) + + xp_assert_equal(stats.iqr(x, nan_policy='omit'), xp.asarray(7.5)) + + message = "The input contains nan values" + with pytest.raises(ValueError, match=message): + stats.iqr(x, nan_policy='raise') + + # Bad policy + message = "nan_policy must be one of..." + with pytest.raises(ValueError, match=message): + stats.iqr(x, nan_policy='barfood') + + @skip_xp_backends(np_only=True, + reason="nan_policy w/ multidimensional arrays only available w/ NumPy") + def test_nanpolicy_nd(self, xp): + x = xp.reshape(xp.arange(15.0), (3, 5)) + x[1, 2] = xp.nan + xp_assert_equal(stats.iqr(x, axis=0, nan_policy='omit'), + xp.full(5, 5.)) + xp_assert_equal(stats.iqr(x, axis=1, nan_policy='omit'), + xp.asarray([2, 2.5, 2])) + + message = "The input contains nan values" + with pytest.raises(ValueError, match=message): + stats.iqr(x, axis=0, nan_policy='raise') + with pytest.raises(ValueError, match=message): + stats.iqr(x, axis=1, nan_policy='raise') + + def test_scale_no_nans(self, xp): + x = xp.reshape(xp.arange(15.0), (3, 5)) + xp_assert_equal(stats.iqr(x, scale=1.0), xp.asarray(7.)) + xp_assert_close(stats.iqr(x, scale='normal'), xp.asarray(7 / 1.3489795)) + xp_assert_equal(stats.iqr(x, scale=2.0), xp.asarray(3.5)) + + def test_scale_with_nans(self, xp): + x = xp.reshape(xp.arange(15.0), (3, 5)) + x = xpx.at(x)[1, 2].set(xp.nan) + nan = xp.asarray(xp.nan) + xp_assert_equal(stats.iqr(x, scale=1.0, nan_policy='propagate'), nan) + xp_assert_equal(stats.iqr(x, scale='normal', nan_policy='propagate'), nan) + xp_assert_equal(stats.iqr(x, scale=2.0, nan_policy='propagate'), nan) + + def test_invalid_scale(self, xp): + x = xp.arange(15.0) + message = "foobar not a valid scale for `iqr`" + with pytest.raises(ValueError, match=message): + stats.iqr(x, scale='foobar') + + @skip_xp_backends("jax.numpy", reason="lazy -> reduced nan_policy capabilities") + def test_scale_nan_policy_omit(self, xp): + x = xp.reshape(xp.arange(15.0), (3, 5)) + x = xpx.at(x)[1, 2].set(xp.nan) + xp_assert_equal(stats.iqr(x, scale=1.0, nan_policy='omit'), xp.asarray(7.5)) + xp_assert_close(stats.iqr(x, scale='normal', nan_policy='omit'), + xp.asarray(7.5 / 1.3489795)) + xp_assert_equal(stats.iqr(x, scale=2.0, nan_policy='omit'), xp.asarray(3.75)) + + def test_scale_nanpolicy_nd(self, xp): + # axis=1 chosen to show behavior with both nans and without + x = xp.reshape(xp.arange(15.0), (3, 5)) + x = xpx.at(x)[1, 2].set(xp.nan) + + xp_assert_equal(stats.iqr(x, axis=1, scale=1.0, nan_policy='propagate'), + xp.asarray([2, np.nan, 2])) + xp_assert_close(stats.iqr(x, axis=1, scale='normal', nan_policy='propagate'), + xp.asarray([2, np.nan, 2]) / 1.3489795) + xp_assert_equal(stats.iqr(x, axis=1, scale=2.0, nan_policy='propagate'), + xp.asarray([1, np.nan, 1])) + + def test_rng_order(self, xp): + # test that order of `rng` doesn't matter (as documented) + x = xp.arange(8.) * 0.5 + res = stats.iqr(x, rng=(75, 25)) + ref = stats.iqr(x) + xp_assert_equal(res, ref) + + +@make_xp_test_case(stats.moment) +class TestMoments: + """ + Comparison numbers are found using R v.1.5.1 + note that length(testcase) = 4 + testmathworks comes from documentation for the + Statistics Toolbox for Matlab and can be found at both + https://www.mathworks.com/help/stats/kurtosis.html + https://www.mathworks.com/help/stats/skewness.html + Note that both test cases came from here. + """ + testcase = [1., 2., 3., 4.] + scalar_testcase = 4. + rng = np.random.default_rng(2285049930) + testcase_moment_accuracy = rng.random(42) + + @pytest.mark.parametrize('size', [10, (10, 2)]) + @pytest.mark.parametrize('m, c', list(product((0, 1, 2, 3), (None, 0, 1)))) + def test_moment_center_scalar_moment(self, size, m, c, xp): + rng = np.random.default_rng(6581432544381372042) + x = xp.asarray(rng.random(size=size)) + res = stats.moment(x, m, center=c) + c = xp.mean(x, axis=0) if c is None else c + ref = xp.sum((x - c)**m, axis=0)/x.shape[0] + xp_assert_close(res, ref, atol=1e-16) + + @pytest.mark.parametrize('size', [10, (10, 2)]) + @pytest.mark.parametrize('c', (None, 0, 1)) + def test_moment_center_array_moment(self, size, c, xp): + rng = np.random.default_rng(1706828300224046506) + x = xp.asarray(rng.random(size=size)) + m = [0, 1, 2, 3] + res = stats.moment(x, m, center=c) + ref = xp.concat([stats.moment(x, i, center=c)[xp.newaxis, ...] for i in m]) + xp_assert_close(res, ref) # JAX JIT doesn't pass with equality + + def test_moment(self, xp): + # mean((testcase-mean(testcase))**power,axis=0),axis=0))**power)) + testcase = xp.asarray(self.testcase) + + y = stats.moment(xp.asarray(self.scalar_testcase)) + xp_assert_close(y, xp.asarray(0.0)) + + y = stats.moment(testcase, 0) + xp_assert_close(y, xp.asarray(1.0)) + y = stats.moment(testcase, 1) + xp_assert_close(y, xp.asarray(0.0)) + y = stats.moment(testcase, 2) + xp_assert_close(y, xp.asarray(1.25)) + y = stats.moment(testcase, 3) + xp_assert_close(y, xp.asarray(0.0)) + y = stats.moment(testcase, 4) + xp_assert_close(y, xp.asarray(2.5625)) + + # check array_like input for moment + y = stats.moment(testcase, [1, 2, 3, 4]) + xp_assert_close(y, xp.asarray([0, 1.25, 0, 2.5625])) + + # check floating point integer order + y = stats.moment(testcase, 0.0) + xp_assert_close(y, xp.asarray(1.0)) + + y = stats.moment(testcase, [1.0, 2, 3, 4.0]) + xp_assert_close(y, xp.asarray([0, 1.25, 0, 2.5625])) + + @skip_xp_backends("jax.numpy", reason="array value-dependent error") + def test_order_input_validation(self, xp): + testcase = xp.asarray(self.testcase) + message = 'All elements of `order` must be integral.' + with pytest.raises(ValueError, match=message): + stats.moment(testcase, 1.2) + + def test_zero_size(self, xp): + message = "One or more sample arguments..." + with eager_warns(SmallSampleWarning, match=message, xp=xp): + y = stats.moment(xp.asarray([])) + xp_assert_equal(y, xp.asarray(xp.nan)) + with eager_warns(SmallSampleWarning, match=message, xp=xp): + y = stats.moment(xp.asarray([], dtype=xp.float32)) + xp_assert_equal(y, xp.asarray(xp.nan, dtype=xp.float32)) + + message = "All axis-slices of one or more" + with eager_warns(SmallSampleWarning, match=message, xp=xp): + y = stats.moment(xp.asarray([[]]), axis=1) + xp_assert_equal(y, xp.asarray([xp.nan])) + + y = stats.moment(xp.zeros((1, 0)), axis=0) + xp_assert_equal(y, xp.empty((0,))) + + y = stats.moment(xp.asarray([[]]), order=[0, 1], axis=0) + xp_assert_equal(y, xp.empty((2, 0))) + + def test_nan_policy(self): + x = np.arange(10.) + x[9] = np.nan + assert_equal(stats.moment(x, 2), np.nan) + assert_almost_equal(stats.moment(x, nan_policy='omit'), 0.0) + assert_raises(ValueError, stats.moment, x, nan_policy='raise') + assert_raises(ValueError, stats.moment, x, nan_policy='foobar') + + @pytest.mark.parametrize('dtype', ['float32', 'float64', 'complex128']) + @pytest.mark.parametrize('expect, order', [(0, 1), (1, 0)]) + def test_constant_moments(self, dtype, expect, order, xp): + if dtype=='complex128' and is_torch(xp): + message = "torch can't round complex input; data-apis/array-api-compat#407" + pytest.skip(message) + dtype = getattr(xp, dtype) + rng = np.random.default_rng(3824693518) + x = xp.asarray(rng.random(5), dtype=dtype) + y = stats.moment(x, order=order) + xp_assert_equal(y, xp.asarray(expect, dtype=dtype)) + + message = "Precision loss occurred in moment calculation" + with eager_warns(RuntimeWarning, match=message, xp=xp): + y = stats.moment(xp.broadcast_to(x, (6, 5)), axis=0, order=order) + xp_assert_equal(y, xp.full((5,), expect, dtype=dtype)) + + with eager_warns(RuntimeWarning, match=message, xp=xp): + y = stats.moment(xp.broadcast_to(x, (1, 2, 3, 4, 5)), axis=2, + order=order) + xp_assert_equal(y, xp.full((1, 2, 4, 5), expect, dtype=dtype)) + + y = stats.moment(xp.broadcast_to(x, (1, 2, 3, 4, 5)), axis=None, + order=order) + xp_assert_equal(y, xp.full((), expect, dtype=dtype)) + + def test_moment_propagate_nan(self, xp): + # Check that the shape of the result is the same for inputs + # with and without nans, cf gh-5817 + a = xp.reshape(xp.arange(8.), (2, -1)) + a = xpx.at(a)[1, 0].set(xp.nan) + + mm = stats.moment(xp.asarray(a), 2, axis=1) + xp_assert_close(mm, xp.asarray([1.25, xp.nan]), atol=1e-15) + + def test_moment_empty_order(self, xp): + # tests moment with empty `order` list + with pytest.raises(ValueError, match=r"`order` must be a scalar or a" + r" non-empty 1D array."): + stats.moment(xp.asarray([1, 2, 3, 4]), order=[]) + + def test_rename_moment_order(self, xp): + # Parameter 'order' was formerly known as 'moment'. The old name + # has not been deprecated, so it must continue to work. + x = xp.arange(10) + res = stats.moment(x, moment=3) + ref = stats.moment(x, order=3) + xp_assert_equal(res, ref) + + def test_moment_accuracy(self): + # 'moment' must have a small enough error compared to the slower + # but very accurate numpy.power() implementation. + tc_no_mean = (self.testcase_moment_accuracy + - np.mean(self.testcase_moment_accuracy)) + assert_allclose(np.power(tc_no_mean, 42).mean(), + stats.moment(self.testcase_moment_accuracy, 42)) + + @pytest.mark.parametrize('order', [0, 1, 2, 3]) + @pytest.mark.parametrize('axis', [-1, 0, 1]) + @pytest.mark.parametrize('center', [None, 0.]) + def test_moment_array_api(self, xp, order, axis, center): + rng = np.random.default_rng(34823589259425) + x = rng.random(size=(5, 6, 7)) + res = stats.moment(xp.asarray(x), order, axis=axis, center=center) + center = center if center is None else np.asarray(center) + ref = xp.asarray(_moment(x, order, axis, center=center)[()]) + xp_assert_close(res, ref) + + +class SkewKurtosisTest: + scalar_testcase = 4. + testcase = [1., 2., 3., 4.] + testmathworks = [1.165, 0.6268, 0.0751, 0.3516, -0.6965] + + def test_empty_1d(self, xp): + x = xp.asarray([]) + with eager_warns(SmallSampleWarning, match=too_small_1d_not_omit, xp=xp): + res = self.stat_fun(x) + + xp_assert_equal(res, xp.asarray(xp.nan)) + + +@make_xp_test_case(stats.skew) +class TestSkew(SkewKurtosisTest): + def stat_fun(self, x): + return stats.skew(x) + + @pytest.mark.filterwarnings( + "ignore:invalid value encountered:RuntimeWarning:dask" + ) + def test_skewness(self, xp): + # Scalar test case + y = stats.skew(xp.asarray(self.scalar_testcase)) + xp_assert_close(y, xp.asarray(xp.nan)) + # sum((testmathworks-mean(testmathworks,axis=0))**3,axis=0) / + # ((sqrt(var(testmathworks)*4/5))**3)/5 + y = stats.skew(xp.asarray(self.testmathworks)) + xp_assert_close(y, xp.asarray(-0.29322304336607), atol=1e-10) + y = stats.skew(xp.asarray(self.testmathworks), bias=0) + xp_assert_close(y, xp.asarray(-0.437111105023940), atol=1e-10) + y = stats.skew(xp.asarray(self.testcase)) + xp_assert_close(y, xp.asarray(0.0), atol=1e-10) + + def test_nan_policy(self): + # initially, nan_policy is ignored with alternative backends + x = np.arange(10.) + x[9] = np.nan + with np.errstate(invalid='ignore'): + assert_equal(stats.skew(x), np.nan) + assert_equal(stats.skew(x, nan_policy='omit'), 0.) + assert_raises(ValueError, stats.skew, x, nan_policy='raise') + assert_raises(ValueError, stats.skew, x, nan_policy='foobar') + + def test_skewness_scalar(self): + # `skew` must return a scalar for 1-dim input (only for NumPy arrays) + assert_equal(stats.skew(arange(10)), 0.0) + + def test_skew_propagate_nan(self, xp): + # Check that the shape of the result is the same for inputs + # with and without nans, cf gh-5817 + a = xp.arange(8.) + a = xp.reshape(a, (2, -1)) + a = xpx.at(a)[1, 0].set(xp.nan) + with np.errstate(invalid='ignore'): + s = stats.skew(xp.asarray(a), axis=1) + xp_assert_equal(s, xp.asarray([0, xp.nan])) + + def test_skew_constant_value(self, xp): + # Skewness of a constant input should be NaN (gh-16061) + a = xp.repeat(xp.asarray([-0.27829495]), 10) + + with eager_warns(RuntimeWarning, match="Precision loss occurred", xp=xp): + xp_assert_equal(stats.skew(a), xp.asarray(xp.nan)) + xp_assert_equal(stats.skew(a*2.**50), xp.asarray(xp.nan)) + xp_assert_equal(stats.skew(a/2.**50), xp.asarray(xp.nan)) + xp_assert_equal(stats.skew(a, bias=False), xp.asarray(xp.nan)) + + # # similarly, from gh-11086: + a = xp.asarray([14.3]*7) + xp_assert_equal(stats.skew(a), xp.asarray(xp.nan)) + a = 1. + xp.arange(-3., 4)*1e-16 + xp_assert_equal(stats.skew(a), xp.asarray(xp.nan)) + + def test_precision_loss_gh15554(self, xp): + # gh-15554 was one of several issues that have reported problems with + # constant or near-constant input. We can't always fix these, but + # make sure there's a warning. + with eager_warns(RuntimeWarning, match="Precision loss occurred", xp=xp): + rng = np.random.default_rng(34095309370) + a = rng.random(size=(100, 10)) + a[:, 0] = 1.01 + stats.skew(xp.asarray(a)) + + @pytest.mark.parametrize('axis', [-1, 0, 2, None]) + @pytest.mark.parametrize('bias', [False, True]) + def test_vectorization(self, xp, axis, bias): + # Behavior with array input is barely tested above. Compare + # against naive implementation. + rng = np.random.default_rng(1283413549926) + x = xp.asarray(rng.random((3, 4, 5))) + + def skewness(a, axis, bias): + # Simple implementation of skewness + if axis is None: + a = xp.reshape(a, (-1,)) + axis = 0 + mean = xp.mean(a, axis=axis, keepdims=True) + mu3 = xp.mean((a - mean)**3, axis=axis) + std = xp.std(a, axis=axis) + res = mu3 / std ** 3 + if not bias: + n = a.shape[axis] + res *= ((n - 1.0) * n) ** 0.5 / (n - 2.0) + return res + + res = stats.skew(x, axis=axis, bias=bias) + ref = skewness(x, axis=axis, bias=bias) + xp_assert_close(res, ref) + + +@make_xp_test_case(stats.kurtosis) +class TestKurtosis(SkewKurtosisTest): + def stat_fun(self, x): + return stats.kurtosis(x) + + @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") + def test_kurtosis(self, xp): + # Scalar test case + y = stats.kurtosis(xp.asarray(self.scalar_testcase)) + assert xp.isnan(y) + + # sum((testcase-mean(testcase,axis=0))**4,axis=0) + # / ((sqrt(var(testcase)*3/4))**4) + # / 4 + # + # sum((test2-mean(testmathworks,axis=0))**4,axis=0) + # / ((sqrt(var(testmathworks)*4/5))**4) + # / 5 + # + # Set flags for axis = 0 and + # fisher=0 (Pearson's defn of kurtosis for compatibility with Matlab) + y = stats.kurtosis(xp.asarray(self.testmathworks), 0, fisher=0, bias=1) + xp_assert_close(y, xp.asarray(2.1658856802973)) + + # Note that MATLAB has confusing docs for the following case + # kurtosis(x,0) gives an unbiased estimate of Pearson's skewness + # kurtosis(x) gives a biased estimate of Fisher's skewness (Pearson-3) + # The MATLAB docs imply that both should give Fisher's + y = stats.kurtosis(xp.asarray(self.testmathworks), fisher=0, bias=0) + xp_assert_close(y, xp.asarray(3.663542721189047)) + y = stats.kurtosis(xp.asarray(self.testcase), 0, 0) + xp_assert_close(y, xp.asarray(1.64)) + + x = xp.arange(10.) + x = xp.where(x == 8, xp.nan, x) + xp_assert_equal(stats.kurtosis(x), xp.asarray(xp.nan)) + + def test_kurtosis_nan_policy(self): + # nan_policy only for NumPy right now + x = np.arange(10.) + x[9] = np.nan + assert_almost_equal(stats.kurtosis(x, nan_policy='omit'), -1.230000) + assert_raises(ValueError, stats.kurtosis, x, nan_policy='raise') + assert_raises(ValueError, stats.kurtosis, x, nan_policy='foobar') + + def test_kurtosis_array_scalar(self): + # "array scalars" do not exist in other backends + assert_equal(type(stats.kurtosis([1, 2, 3])), np.float64) + + def test_kurtosis_propagate_nan(self): + # nan_policy only for NumPy right now + # Check that the shape of the result is the same for inputs + # with and without nans, cf gh-5817 + a = np.arange(8).reshape(2, -1).astype(float) + a[1, 0] = np.nan + k = stats.kurtosis(a, axis=1, nan_policy="propagate") + np.testing.assert_allclose(k, [-1.36, np.nan], atol=1e-15) + + def test_kurtosis_constant_value(self, xp): + # Kurtosis of a constant input should be NaN (gh-16061) + a = xp.asarray([-0.27829495]*10) + with eager_warns(RuntimeWarning, match="Precision loss occurred", xp=xp): + assert xp.isnan(stats.kurtosis(a, fisher=False)) + assert xp.isnan(stats.kurtosis(a * float(2**50), fisher=False)) + assert xp.isnan(stats.kurtosis(a / float(2**50), fisher=False)) + assert xp.isnan(stats.kurtosis(a, fisher=False, bias=False)) + + @pytest.mark.parametrize('axis', [-1, 0, 2, None]) + @pytest.mark.parametrize('bias', [False, True]) + @pytest.mark.parametrize('fisher', [False, True]) + def test_vectorization(self, xp, axis, bias, fisher): + # Behavior with array input is not tested above. Compare + # against naive implementation. + rng = np.random.default_rng(1283413549926) + x = xp.asarray(rng.random((4, 5, 6))) + + def kurtosis(a, axis, bias, fisher): + # Simple implementation of kurtosis + if axis is None: + a = xp.reshape(a, (-1,)) + axis = 0 + mean = xp.mean(a, axis=axis, keepdims=True) + mu4 = xp.mean((a - mean)**4, axis=axis) + mu2 = xp.var(a, axis=axis, correction=0) + if bias: + res = mu4 / mu2**2 - 3 + else: + n = a.shape[axis] + # https://en.wikipedia.org/wiki/Kurtosis#Standard_unbiased_estimator + res = (n-1) / ((n-2) * (n-3)) * ((n + 1) * mu4/mu2**2 - 3*(n-1)) + + # I know it looks strange to subtract then add 3, + # but it is simpler than the alternatives + return res if fisher else res + 3 + + res = stats.kurtosis(x, axis=axis, bias=bias, fisher=fisher) + ref = kurtosis(x, axis=axis, bias=bias, fisher=fisher) + xp_assert_close(res, ref) + + +@hypothesis.strategies.composite +def ttest_data_axis_strategy(draw): + # draw an array under shape and value constraints + elements = dict(allow_nan=False, allow_infinity=False) + shape = npst.array_shapes(min_dims=1, min_side=2) + # The test that uses this, `test_pvalue_ci`, uses `float64` to test + # extreme `alpha`. It could be adjusted to test a dtype-dependent + # range of `alpha` if this strategy is needed to generate other floats. + data = draw(npst.arrays(dtype=np.float64, elements=elements, shape=shape)) + + # determine axes over which nonzero variance can be computed accurately + ok_axes = [] + # Locally, I don't need catch_warnings or simplefilter, and I can just + # suppress RuntimeWarning. I include all that in hope of getting the same + # behavior on CI. + with warnings.catch_warnings(): + warnings.simplefilter("error") + for axis in range(len(data.shape)): + with contextlib.suppress(Exception): + var = stats.moment(data, order=2, axis=axis) + if np.all(var > 0) and np.all(np.isfinite(var)): + ok_axes.append(axis) + # if there are no valid axes, tell hypothesis to try a different example + hypothesis.assume(ok_axes) + + # draw one of the valid axes + axis = draw(hypothesis.strategies.sampled_from(ok_axes)) + + return data, axis + + +@make_xp_test_case(stats.ttest_1samp) +class TestTTest_1samp: + # Preserving original test cases. + # Recomputed statistics and p-values with R t.test, e.g. + # options(digits=16) + # t.test(c(-1., 0., 1.), mu=2) + X1 = [-1., 0., 1.] + X2 = [0., 1., 2.] + T1_0 = 0. + P1_0 = 1. + T1_1 = -1.7320508075689 + P1_1 = 0.2254033307585 + T1_2 = -3.4641016151378 + P1_2 = 0.07417990022745 + T2_0 = 1.7320508075689 + P2_0 = 0.2254033307585 + P1_1_l = P1_1 / 2 + P1_1_g = 1 - (P1_1 / 2) + + @pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning:dask") + @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") + def test_onesample(self, xp): + with warnings.catch_warnings(), \ + np.errstate(invalid="ignore", divide="ignore"): + warnings.filterwarnings( + "ignore", "Degrees of freedom <= 0 for slice", RuntimeWarning) + a = xp.asarray(4.) if not is_numpy(xp) else 4. + t, p = stats.ttest_1samp(a, 3.) + xp_assert_equal(t, xp.asarray(xp.nan)) + xp_assert_equal(p, xp.asarray(xp.nan)) + + t, p = stats.ttest_1samp(xp.asarray(self.X1), 0.) + xp_assert_close(t, xp.asarray(self.T1_0)) + xp_assert_close(p, xp.asarray(self.P1_0)) + + res = stats.ttest_1samp(xp.asarray(self.X1), 0.) + attributes = ('statistic', 'pvalue') + check_named_results(res, attributes, xp=xp) + + t, p = stats.ttest_1samp(xp.asarray(self.X2), 0.) + + xp_assert_close(t, xp.asarray(self.T2_0)) + xp_assert_close(p, xp.asarray(self.P2_0)) + + t, p = stats.ttest_1samp(xp.asarray(self.X1), 1.) + + xp_assert_close(t, xp.asarray(self.T1_1)) + xp_assert_close(p, xp.asarray(self.P1_1)) + + t, p = stats.ttest_1samp(xp.asarray(self.X1), 2.) + + xp_assert_close(t, xp.asarray(self.T1_2)) + xp_assert_close(p, xp.asarray(self.P1_2)) + + def test_onesample_nan_policy_propagate(self, xp): + x = stats.norm.rvs(loc=5, scale=10, size=51, random_state=7654567) + x[50] = np.nan + x = xp.asarray(x, dtype=xp_default_dtype(xp)) + + res = stats.ttest_1samp(x, 5.0) + xp_assert_equal(res.statistic, xp.asarray(xp.nan)) + xp_assert_equal(res.pvalue, xp.asarray(xp.nan)) + + @skip_xp_backends(eager_only=True, reason="lazy arrays don't do 'raise'.") + def test_onesample_nan_policy_omit_raise(self, xp): + x = stats.norm.rvs(loc=5, scale=10, size=51, random_state=7654567) + x[50] = np.nan + x = xp.asarray(x, dtype=xp_default_dtype(xp)) + + res = stats.ttest_1samp(x, 5.0, nan_policy='omit') + xp_assert_close(res.statistic, xp.asarray(-1.6412624074367159)) + xp_assert_close(res.pvalue, xp.asarray(0.107147027334048005)) + + with pytest.raises(ValueError, match="The input contains nan values"): + stats.ttest_1samp(x, 5.0, nan_policy='raise') + + with pytest.raises(ValueError, match="nan_policy must be one of"): + stats.ttest_1samp(x, 5.0, nan_policy='foobar') + + + @pytest.mark.filterwarnings("ignore:divide by zero encountered in divide") + def test_1samp_alternative(self, xp): + message = "`alternative` must be 'less', 'greater', or 'two-sided'." + with pytest.raises(ValueError, match=message): + stats.ttest_1samp(xp.asarray(self.X1), 0., alternative="error") + + t, p = stats.ttest_1samp(xp.asarray(self.X1), 1., alternative="less") + xp_assert_close(p, xp.asarray(self.P1_1_l)) + xp_assert_close(t, xp.asarray(self.T1_1)) + + t, p = stats.ttest_1samp(xp.asarray(self.X1), 1., alternative="greater") + xp_assert_close(p, xp.asarray(self.P1_1_g)) + xp_assert_close(t, xp.asarray(self.T1_1)) + + @skip_xp_backends('jax.numpy', reason='Generic stdtrit mutates array.') + @pytest.mark.parametrize("alternative", ['two-sided', 'less', 'greater']) + def test_1samp_ci_1d(self, xp, alternative): + # test confidence interval method against reference values + rng = np.random.default_rng(8066178009154342972) + n = 10 + x = rng.normal(size=n, loc=1.5, scale=2) + popmean = rng.normal() # this shouldn't affect confidence interval + # Reference values generated with R t.test: + # options(digits=16) + # x = c(2.75532884, 0.93892217, 0.94835861, 1.49489446, -0.62396595, + # -1.88019867, -1.55684465, 4.88777104, 5.15310979, 4.34656348) + # t.test(x, conf.level=0.85, alternative='l') + dtype = xp.asarray(1.0).dtype + x = xp.asarray(x, dtype=dtype) + popmean = xp.asarray(popmean, dtype=dtype) + + ref = {'two-sided': [0.3594423211709136, 2.9333455028290860], + 'greater': [0.7470806207371626, np.inf], + 'less': [-np.inf, 2.545707203262837]} + res = stats.ttest_1samp(x, popmean=popmean, alternative=alternative) + ci = res.confidence_interval(confidence_level=0.85) + xp_assert_close(ci.low, xp.asarray(ref[alternative][0])) + xp_assert_close(ci.high, xp.asarray(ref[alternative][1])) + xp_assert_equal(res.df, xp.asarray(n-1)) + + def test_1samp_ci_iv(self, xp): + # test `confidence_interval` method input validation + res = stats.ttest_1samp(xp.arange(10.), 0.) + message = '`confidence_level` must be a number between 0 and 1.' + with pytest.raises(ValueError, match=message): + res.confidence_interval(confidence_level=10) + + @skip_xp_backends(np_only=True, reason='Too slow.') + @pytest.mark.xslow + @hypothesis.given(alpha=hypothesis.strategies.floats(1e-15, 1-1e-15), + data_axis=ttest_data_axis_strategy()) + @pytest.mark.parametrize('alternative', ['less', 'greater']) + def test_pvalue_ci(self, alpha, data_axis, alternative, xp): + # test relationship between one-sided p-values and confidence intervals + data, axis = data_axis + data = xp.asarray(data) + res = stats.ttest_1samp(data, 0., + alternative=alternative, axis=axis) + l, u = res.confidence_interval(confidence_level=alpha) + popmean = l if alternative == 'greater' else u + popmean = xp.expand_dims(popmean, axis=axis) + res = stats.ttest_1samp(data, popmean, alternative=alternative, axis=axis) + shape = list(data.shape) + shape.pop(axis) + # `float64` is used to correspond with extreme range of `alpha` + ref = xp.broadcast_to(xp.asarray(1-alpha, dtype=xp.float64), shape) + xp_assert_close(res.pvalue, ref) + + +class TestPercentileOfScore: + + def f(self, *args, **kwargs): + return stats.percentileofscore(*args, **kwargs) + + @pytest.mark.parametrize("kind, result", [("rank", 40), + ("mean", 35), + ("strict", 30), + ("weak", 40)]) + def test_unique(self, kind, result): + a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + assert_equal(self.f(a, 4, kind=kind), result) + + @pytest.mark.parametrize("kind, result", [("rank", 45), + ("mean", 40), + ("strict", 30), + ("weak", 50)]) + def test_multiple2(self, kind, result): + a = [1, 2, 3, 4, 4, 5, 6, 7, 8, 9] + assert_equal(self.f(a, 4, kind=kind), result) + + @pytest.mark.parametrize("kind, result", [("rank", 50), + ("mean", 45), + ("strict", 30), + ("weak", 60)]) + def test_multiple3(self, kind, result): + a = [1, 2, 3, 4, 4, 4, 5, 6, 7, 8] + assert_equal(self.f(a, 4, kind=kind), result) + + @pytest.mark.parametrize("kind, result", [("rank", 30), + ("mean", 30), + ("strict", 30), + ("weak", 30)]) + def test_missing(self, kind, result): + a = [1, 2, 3, 5, 6, 7, 8, 9, 10, 11] + assert_equal(self.f(a, 4, kind=kind), result) + + @pytest.mark.parametrize("kind, result", [("rank", 40), + ("mean", 35), + ("strict", 30), + ("weak", 40)]) + def test_large_numbers(self, kind, result): + a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] + assert_equal(self.f(a, 40, kind=kind), result) + + @pytest.mark.parametrize("kind, result", [("rank", 50), + ("mean", 45), + ("strict", 30), + ("weak", 60)]) + def test_large_numbers_multiple3(self, kind, result): + a = [10, 20, 30, 40, 40, 40, 50, 60, 70, 80] + assert_equal(self.f(a, 40, kind=kind), result) + + @pytest.mark.parametrize("kind, result", [("rank", 30), + ("mean", 30), + ("strict", 30), + ("weak", 30)]) + def test_large_numbers_missing(self, kind, result): + a = [10, 20, 30, 50, 60, 70, 80, 90, 100, 110] + assert_equal(self.f(a, 40, kind=kind), result) + + @pytest.mark.parametrize("kind, result", [("rank", [0, 10, 100, 100]), + ("mean", [0, 5, 95, 100]), + ("strict", [0, 0, 90, 100]), + ("weak", [0, 10, 100, 100])]) + def test_boundaries(self, kind, result): + a = [10, 20, 30, 50, 60, 70, 80, 90, 100, 110] + assert_equal(self.f(a, [0, 10, 110, 200], kind=kind), result) + + @pytest.mark.parametrize("kind, result", [("rank", [0, 10, 100]), + ("mean", [0, 5, 95]), + ("strict", [0, 0, 90]), + ("weak", [0, 10, 100])]) + def test_inf(self, kind, result): + a = [1, 2, 3, 4, 5, 6, 7, 8, 9, +np.inf] + assert_equal(self.f(a, [-np.inf, 1, +np.inf], kind=kind), result) + + cases = [("propagate", [], 1, np.nan), + ("propagate", [np.nan], 1, np.nan), + ("propagate", [np.nan], [0, 1, 2], [np.nan, np.nan, np.nan]), + ("propagate", [1, 2], [1, 2, np.nan], [50, 100, np.nan]), + ("omit", [1, 2, np.nan], [0, 1, 2], [0, 50, 100]), + ("omit", [1, 2], [0, 1, np.nan], [0, 50, np.nan]), + ("omit", [np.nan, np.nan], [0, 1, 2], [np.nan, np.nan, np.nan])] + + @pytest.mark.parametrize("policy, a, score, result", cases) + def test_nans_ok(self, policy, a, score, result): + assert_equal(self.f(a, score, nan_policy=policy), result) + + cases = [ + ("raise", [1, 2, 3, np.nan], [1, 2, 3], + "The input contains nan values"), + ("raise", [1, 2, 3], [1, 2, 3, np.nan], + "The input contains nan values"), + ] + + @pytest.mark.parametrize("policy, a, score, message", cases) + def test_nans_fail(self, policy, a, score, message): + with assert_raises(ValueError, match=message): + self.f(a, score, nan_policy=policy) + + @pytest.mark.parametrize("shape", [ + (6, ), + (2, 3), + (2, 1, 3), + (2, 1, 1, 3), + ]) + def test_nd(self, shape): + a = np.array([0, 1, 2, 3, 4, 5]) + scores = a.reshape(shape) + results = scores*10 + a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + assert_equal(self.f(a, scores, kind="rank"), results) + + def test_multidimensional_error(self): + # gh-21563 reported that `percentileofscore` accepted multidimensional + # arrays but did not produce meaningful results. + message = "`a` must be 1-dimensional." + with pytest.raises(ValueError, match=message): + stats.percentileofscore(np.ones((3, 3)), 1) + + +PowerDivCase = namedtuple('Case', # type: ignore[name-match] + ['f_obs', 'f_exp', 'ddof', 'axis', + 'chi2', # Pearson's + 'log', # G-test (log-likelihood) + 'mod_log', # Modified log-likelihood + 'cr', # Cressie-Read (lambda=2/3) + ]) + +# The details of the first two elements in power_div_1d_cases are used +# in a test in TestPowerDivergence. Check that code before making +# any changes here. +power_div_1d_cases = [ + # Use the default f_exp. + PowerDivCase(f_obs=[4, 8, 12, 8], f_exp=None, ddof=0, axis=None, + chi2=4, + log=2*(4*np.log(4/8) + 12*np.log(12/8)), + mod_log=2*(8*np.log(8/4) + 8*np.log(8/12)), + cr=(4*((4/8)**(2/3) - 1) + 12*((12/8)**(2/3) - 1))/(5/9)), + # Give a non-uniform f_exp. + PowerDivCase(f_obs=[4, 8, 12, 8], f_exp=[2, 16, 12, 2], ddof=0, axis=None, + chi2=24, + log=2*(4*np.log(4/2) + 8*np.log(8/16) + 8*np.log(8/2)), + mod_log=2*(2*np.log(2/4) + 16*np.log(16/8) + 2*np.log(2/8)), + cr=(4*((4/2)**(2/3) - 1) + 8*((8/16)**(2/3) - 1) + + 8*((8/2)**(2/3) - 1))/(5/9)), + # f_exp is a scalar. + PowerDivCase(f_obs=[4, 8, 12, 8], f_exp=8, ddof=0, axis=None, + chi2=4, + log=2*(4*np.log(4/8) + 12*np.log(12/8)), + mod_log=2*(8*np.log(8/4) + 8*np.log(8/12)), + cr=(4*((4/8)**(2/3) - 1) + 12*((12/8)**(2/3) - 1))/(5/9)), + # f_exp equal to f_obs. + PowerDivCase(f_obs=[3, 5, 7, 9], f_exp=[3, 5, 7, 9], ddof=0, axis=0, + chi2=0, log=0, mod_log=0, cr=0), +] + + +power_div_empty_cases = [ + # Shape is (0,)--a data set with length 0. The computed + # test statistic should be 0. + PowerDivCase(f_obs=[], + f_exp=None, ddof=0, axis=0, + chi2=0, log=0, mod_log=0, cr=0), + # Shape is (0, 3). This is 3 data sets, but each data set has + # length 0, so the computed test statistic should be [0, 0, 0]. + PowerDivCase(f_obs=np.array([[],[],[]]).T, + f_exp=None, ddof=0, axis=0, + chi2=[0, 0, 0], + log=[0, 0, 0], + mod_log=[0, 0, 0], + cr=[0, 0, 0]), + # Shape is (3, 0). This represents an empty collection of + # data sets in which each data set has length 3. The test + # statistic should be an empty array. + PowerDivCase(f_obs=np.array([[],[],[]]), + f_exp=None, ddof=0, axis=0, + chi2=[], + log=[], + mod_log=[], + cr=[]), +] + + +@make_xp_test_case(stats.power_divergence) +class TestPowerDivergence: + + def check_power_divergence(self, f_obs, f_exp, ddof, axis, lambda_, + expected_stat, xp): + dtype = xp.asarray(1.).dtype + + f_obs = xp.asarray(f_obs, dtype=dtype) + f_exp = xp.asarray(f_exp, dtype=dtype) if f_exp is not None else f_exp + + if axis is None: + num_obs = xp_size(f_obs) + else: + arrays = (xp.broadcast_arrays(f_obs, f_exp) if f_exp is not None + else (f_obs,)) + num_obs = arrays[0].shape[axis] + + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", "Mean of empty slice", RuntimeWarning) + stat, p = stats.power_divergence( + f_obs, f_exp=f_exp, ddof=ddof, + axis=axis, lambda_=lambda_) + xp_assert_close(stat, xp.asarray(expected_stat, dtype=dtype)) + + if lambda_ == 1 or lambda_ == "pearson": + # Also test stats.chisquare. + stat, p = stats.chisquare(f_obs, f_exp=f_exp, ddof=ddof, + axis=axis) + xp_assert_close(stat, xp.asarray(expected_stat, dtype=dtype)) + + ddof = np.asarray(ddof) + expected_p = stats.distributions.chi2.sf(expected_stat, + num_obs - 1 - ddof) + xp_assert_close(p, xp.asarray(expected_p, dtype=dtype)) + + @pytest.mark.parametrize('case', power_div_1d_cases) + @pytest.mark.parametrize('lambda_stat', + [(None, 'chi2'), ('pearson', 'chi2'), (1, 'chi2'), + ('log-likelihood', 'log'), ('mod-log-likelihood', 'mod_log'), + ('cressie-read', 'cr'), (2/3, 'cr')]) + def test_basic(self, case, lambda_stat, xp): + lambda_, attr = lambda_stat + expected_stat = getattr(case, attr) + self.check_power_divergence(case.f_obs, case.f_exp, case.ddof, case.axis, + lambda_, expected_stat, xp) + + def test_axis(self, xp): + case0 = power_div_1d_cases[0] + case1 = power_div_1d_cases[1] + f_obs = np.vstack((case0.f_obs, case1.f_obs)) + f_exp = np.vstack((np.ones_like(case0.f_obs)*np.mean(case0.f_obs), + case1.f_exp)) + # Check the four computational code paths in power_divergence + # using a 2D array with axis=1. + f_obs = xp.asarray(f_obs) + f_exp = xp.asarray(f_exp) if f_exp is not None else f_exp + self.check_power_divergence( + f_obs, f_exp, 0, 1, + "pearson", [case0.chi2, case1.chi2], xp=xp) + self.check_power_divergence( + f_obs, f_exp, 0, 1, + "log-likelihood", [case0.log, case1.log], xp=xp) + self.check_power_divergence( + f_obs, f_exp, 0, 1, + "mod-log-likelihood", [case0.mod_log, case1.mod_log], xp=xp) + self.check_power_divergence( + f_obs, f_exp, 0, 1, + "cressie-read", [case0.cr, case1.cr], xp=xp) + # Reshape case0.f_obs to shape (2,2), and use axis=None. + # The result should be the same. + f_obs_reshape = xp.reshape(xp.asarray(case0.f_obs), (2, 2)) + self.check_power_divergence( + f_obs_reshape, None, 0, None, + "pearson", case0.chi2, xp=xp) + + def test_ddof_broadcasting(self, xp): + # Test that ddof broadcasts correctly. + # ddof does not affect the test statistic. It is broadcast + # with the computed test statistic for the computation of + # the p value. + + case0 = power_div_1d_cases[0] + case1 = power_div_1d_cases[1] + # Create 4x2 arrays of observed and expected frequencies. + f_obs = np.vstack((case0.f_obs, case1.f_obs)).T + f_exp = np.vstack((np.ones_like(case0.f_obs)*np.mean(case0.f_obs), + case1.f_exp)).T + + expected_chi2 = [case0.chi2, case1.chi2] + + dtype = xp.asarray(1.).dtype + f_obs = xp.asarray(f_obs, dtype=dtype) + f_exp = xp.asarray(f_exp, dtype=dtype) + expected_chi2 = xp.asarray(expected_chi2, dtype=dtype) + + # ddof has shape (2, 1). This is broadcast with the computed + # statistic, so p will have shape (2,2). + ddof = xp.asarray([[0], [1]]) + + stat, p = stats.power_divergence(f_obs, f_exp, ddof=ddof) + xp_assert_close(stat, expected_chi2) + + # Compute the p values separately, passing in scalars for ddof. + _, p0 = stats.power_divergence(f_obs, f_exp, ddof=ddof[0, 0]) + _, p1 = stats.power_divergence(f_obs, f_exp, ddof=ddof[1, 0]) + + expected_p = xp.concat((p0[xp.newaxis, :], p1[xp.newaxis, :]), axis=0) + xp_assert_close(p, expected_p) + + @pytest.mark.parametrize('case', power_div_empty_cases) + @pytest.mark.parametrize('lambda_stat', + [('pearson', 'chi2'), ('log-likelihood', 'log'), + ('mod-log-likelihood', 'mod_log'), + ('cressie-read', 'cr'), (2/3, 'cr')]) + def test_empty_cases(self, case, lambda_stat, xp): + lambda_, attr = lambda_stat + expected_stat = getattr(case, attr) + with warnings.catch_warnings(): + self.check_power_divergence( + case.f_obs, case.f_exp, case.ddof, case.axis, + lambda_, expected_stat, xp) + + def test_power_divergence_result_attributes(self, xp): + f_obs = power_div_1d_cases[0].f_obs + f_exp = power_div_1d_cases[0].f_exp + ddof = power_div_1d_cases[0].ddof + axis = power_div_1d_cases[0].axis + dtype = xp.asarray(1.).dtype + f_obs = xp.asarray(f_obs, dtype=dtype) + # f_exp is None + + res = stats.power_divergence(f_obs, f_exp=f_exp, ddof=ddof, + axis=axis, lambda_="pearson") + attributes = ('statistic', 'pvalue') + check_named_results(res, attributes, xp=xp) + + def test_power_divergence_gh_12282(self, xp): + # The sums of observed and expected frequencies must match + f_obs = xp.asarray([[10., 20.], [30., 20.]]) + f_exp = xp.asarray([[5., 15.], [35., 25.]]) + stat, pval = stats.power_divergence(f_obs, f_exp=f_exp) + xp_assert_close(stat, xp.asarray([5.71428571, 2.66666667])) + xp_assert_close(pval, xp.asarray([0.01682741, 0.10247043])) + + if is_lazy_array(f_obs): + res = stats.power_divergence(f_obs, f_exp=xp.asarray([30., 60.])) + xp_assert_equal(res.statistic, xp.asarray([xp.nan, xp.nan])) + xp_assert_equal(res.pvalue, xp.asarray([xp.nan, xp.nan])) + + res = stats.power_divergence(f_obs, f_exp=f_exp, axis=1) + xp_assert_equal(res.statistic, xp.asarray([xp.nan, xp.nan])) + xp_assert_equal(res.pvalue, xp.asarray([xp.nan, xp.nan])) + + # only slices with unequal sums result in NaN + f_exp = xp.asarray([[5., 25.], [35., 25.]]) + res = stats.power_divergence(f_obs, f_exp=f_exp) + xp_assert_close(res.statistic, xp.asarray([stat[0], xp.nan])) + xp_assert_close(res.pvalue, xp.asarray([pval[0], xp.nan])) + else: + message = 'For each axis slice...' + with pytest.raises(ValueError, match=message): + stats.power_divergence(f_obs, f_exp=xp.asarray([30., 60.])) + with pytest.raises(ValueError, match=message): + stats.power_divergence(f_obs, f_exp=f_exp, axis=1) + + def test_power_divergence_against_cressie_read_data(self, xp): + # Test stats.power_divergence against tables 4 and 5 from + # Cressie and Read, "Multimonial Goodness-of-Fit Tests", + # J. R. Statist. Soc. B (1984), Vol 46, No. 3, pp. 440-464. + # This tests the calculation for several values of lambda. + + # Table 4 data recalculated for greater precision according to: + # Shelby J. Haberman, Analysis of Qualitative Data: Volume 1 + # Introductory Topics, Academic Press, New York, USA (1978). + obs = xp.asarray([15., 11., 14., 17., 5., 11., 10., 4., 8., + 10., 7., 9., 11., 3., 6., 1., 1., 4.]) + beta = -0.083769 # Haberman (1978), p. 15 + i = xp.arange(1., obs.shape[0] + 1.) + alpha = xp.log(xp.sum(obs) / xp.sum(xp.exp(beta*i))) + expected_counts = xp.exp(alpha + beta*i) + + # `table4` holds just the second and third columns from Table 4. + table4 = xp.concat((obs[xp.newaxis, :], + expected_counts[xp.newaxis, :])).T + + table5 = np.asarray([ + # lambda, statistic + -10.0, 72.2e3, + -5.0, 28.9e1, + -3.0, 65.6, + -2.0, 40.6, + -1.5, 34.0, + -1.0, 29.5, + -0.5, 26.5, + 0.0, 24.6, + 0.5, 23.4, + 0.67, 23.1, + 1.0, 22.7, + 1.5, 22.6, + 2.0, 22.9, + 3.0, 24.8, + 5.0, 35.5, + 10.0, 21.4e1, + ]) + table5 = np.reshape(table5, (-1, 2)) + + for i in range(table5.shape[0]): + lambda_, expected_stat = float(table5[i, 0]), float(table5[i, 1]) + stat, p = stats.power_divergence(table4[:, 0], table4[:, 1], + lambda_=lambda_) + xp_assert_close(stat, xp.asarray(expected_stat), rtol=5e-3) + + +@make_xp_test_case(stats.chisquare) +class TestChisquare: + def test_chisquare_12282a(self, xp): + # Currently `chisquare` is implemented via power_divergence + # in case that ever changes, perform a basic test like + # test_power_divergence_gh_12282 + f_obs = xp.asarray([10., 20.]) + f_exp = xp.asarray([30., 60.]) + if is_lazy_array(f_obs): + res = stats.chisquare(f_obs, f_exp=f_exp) + xp_assert_equal(res.statistic, xp.asarray(xp.nan)) + xp_assert_equal(res.pvalue, xp.asarray(xp.nan)) + else: + with assert_raises(ValueError, match='For each axis slice...'): + stats.chisquare(f_obs, f_exp=f_exp) + + def test_chisquare_12282b(self, xp): + # Check that users can now disable the sum check tested in + # test_chisquare_12282a. Also, confirm that statistic and p-value + # are as expected. + rng = np.random.default_rng(3843874358728234) + n = 10 + lam = rng.uniform(1000, 2000, size=n) + x = rng.poisson(lam) + lam = xp.asarray(lam) + x = xp.asarray(x, dtype=lam.dtype) + res = stats.chisquare(x, f_exp=lam, ddof=-1, sum_check=False) + # Poisson is approximately normal with mean and variance lam + z = (x - lam) / xp.sqrt(lam) + statistic = xp.sum(z**2) + xp_assert_close(res.statistic, statistic) + # Sum of `n` squared standard normal variables follows chi2 with `n` DoF + X2 = _SimpleChi2(xp.asarray(n, dtype=statistic.dtype)) + xp_assert_close(res.pvalue, X2.sf(statistic)) + + @pytest.mark.parametrize("n, dtype", [(200, 'uint8'), (1000000, 'int32')]) + def test_chiquare_data_types_attributes(self, n, dtype, xp): + # Regression test for gh-10159 and gh-18368 + dtype = getattr(xp, dtype) + obs = xp.asarray([n, 0], dtype=dtype) + exp = xp.asarray([n // 2, n // 2], dtype=dtype) + res = stats.chisquare(obs, exp) + stat, p = res + xp_assert_close(stat, xp.asarray(n, dtype=xp.asarray(1.).dtype), rtol=1e-13) + # check that attributes are identical to unpacked outputs - see gh-18368 + xp_assert_equal(res.statistic, stat) + xp_assert_equal(res.pvalue, p) + + +@make_xp_test_case(stats.friedmanchisquare) +class TestFriedmanChiSquare: + # verified with matlab and R + # From Demsar "Statistical Comparisons of Classifiers over Multiple Data Sets" + # 2006, Xf=9.28 (no tie handling, tie corrected Xf >=9.28) + x1 = [[0.763, 0.599, 0.954, 0.628, 0.882, 0.936, 0.661, + 0.583, 0.775, 1.0, 0.94, 0.619, 0.972, 0.957], + [0.768, 0.591, 0.971, 0.661, 0.888, 0.931, 0.668, + 0.583, 0.838, 1.0, 0.962, 0.666, 0.981, 0.978], + [0.771, 0.590, 0.968, 0.654, 0.886, 0.916, 0.609, + 0.563, 0.866, 1.0, 0.965, 0.614, 0.9751, 0.946], + [0.798, 0.569, 0.967, 0.657, 0.898, 0.931, 0.685, + 0.625, 0.875, 1.0, 0.962, 0.669, 0.975, 0.970]] + ref1 = (10.2283464566929, 0.0167215803284414) + + # From "Bioestadistica para las ciencias de la salud" Xf=18.95 p<0.001: + x2 = [[4, 3, 5, 3, 5, 3, 2, 5, 4, 4, 4, 3], + [2, 2, 1, 2, 3, 1, 2, 3, 2, 1, 1, 3], + [2, 4, 3, 3, 4, 3, 3, 4, 4, 1, 2, 1], + [3, 5, 4, 3, 4, 4, 3, 3, 3, 4, 4, 4]] + ref2 = (18.9428571428571, 0.000280938375189499) + + # From Jerrorl H. Zar, "Biostatistical Analysis"(example 12.6), + # Xf=10.68, 0.005 < p < 0.01: + # Probability from this example is inexact + # using Chisquare approximation of Friedman Chisquare. + x3 = [[7.0, 9.9, 8.5, 5.1, 10.3], + [5.3, 5.7, 4.7, 3.5, 7.7], + [4.9, 7.6, 5.5, 2.8, 8.4], + [8.8, 8.9, 8.1, 3.3, 9.1]] + ref3 = (10.68, 0.0135882729582176) + + @pytest.mark.parametrize("dtype", [None, "float32", "float64"]) + @pytest.mark.parametrize("data, ref", [(x1, ref1), (x2, ref2), (x3, ref3)]) + def test_against_references(self, dtype, data, ref, xp): + dtype = dtype if dtype is None else getattr(xp, dtype) + data = [xp.asarray(array, dtype=dtype) for array in data] + res = stats.friedmanchisquare(*data) + xp_assert_close(res.statistic, xp.asarray(ref[0], dtype=dtype)) + xp_assert_close(res.pvalue, xp.asarray(ref[1], dtype=dtype)) + + def test_too_few_samples(self, xp): + message = "At least 3 samples must be given" + with pytest.raises(ValueError, match=message): + stats.friedmanchisquare(xp.asarray(self.x3[0]), xp.asarray(self.x3[1])) + + +@make_xp_test_case(stats.kstest) +class TestKSTest: + """Tests kstest and ks_1samp agree with K-S various sizes, alternatives, modes.""" + + def _test_kstest_and_ks1samp(self, x, alternative, mode='auto', decimal=14): + result = stats.kstest(x, 'norm', alternative=alternative, mode=mode) + result_1samp = stats.ks_1samp(x, special.ndtr, + alternative=alternative, mode=mode) + xp_assert_close(result.statistic, result_1samp.statistic) + xp_assert_close(result.pvalue, result_1samp.pvalue) + xp_assert_equal(result.statistic_location, result.statistic_location) + xp_assert_equal(result.statistic_sign, result.statistic_sign) + + def test_namedtuple_attributes(self, xp): + x = xp.linspace(-1, 1, 9) + # test for namedtuple attribute results + attributes = ('statistic', 'pvalue') + res = stats.kstest(x, special.ndtr) + check_named_results(res, attributes, xp=xp) + + def test_agree_with_ks_1samp(self, xp): + x = xp.linspace(-1, 1, 9) + self._test_kstest_and_ks1samp(x, 'two-sided') + + x = xp.linspace(-15, 15, 9) + self._test_kstest_and_ks1samp(x, 'two-sided') + + x = [-1.23, 0.06, -0.60, 0.17, 0.66, -0.17, -0.08, 0.27, -0.98, -0.99] + x = xp.asarray(x) + self._test_kstest_and_ks1samp(x, 'two-sided') + self._test_kstest_and_ks1samp(x, 'greater', mode='exact') + self._test_kstest_and_ks1samp(x, 'less', mode='exact') + + # parametrize over function-specific options to make sure they're passed through; + # `test_axis_nan_policy` takes care of the rest. + @pytest.mark.parametrize("method", ["auto", "exact", "asymp"]) + @pytest.mark.parametrize("alternative", ['two-sided', 'less', 'greater']) + def test_agree_with_ks_2samp(self, method, alternative, xp): + rng = np.random.default_rng(236114087) + x, y = rng.random((2, 9, 10)) + x, y = xp.asarray(x), xp.asarray(y) + res = stats.kstest(x, y, alternative=alternative, method=method) + ref = stats.ks_2samp(x, y, alternative=alternative, method=method) + xp_assert_equal(res.statistic, ref.statistic) + xp_assert_equal(res.pvalue, ref.pvalue) + xp_assert_equal(res.statistic_location, ref.statistic_location) + xp_assert_equal(res.statistic_sign, ref.statistic_sign) + + def test_pm_inf_gh20386(self, xp): + # Check that gh-20386 is resolved - `kstest` does not + # return NaNs when both -inf and inf are in sample. + vals = xp.asarray([-xp.inf, 0, 1, xp.inf]) + def cdf(x): # Cauchy CDF + return xp.atan2(xp.ones_like(x), -x) / xp.pi + res = stats.kstest(vals, cdf) + ref = stats.kstest(vals, cdf, _no_deco=True) + assert xp.isfinite(res.statistic) + assert xp.isfinite(res.pvalue) + assert xp.isfinite(res.statistic_location) + assert xp.isfinite(res.statistic_sign) + xp_assert_equal(res.statistic, ref.statistic) + xp_assert_equal(res.pvalue, ref.pvalue) + xp_assert_equal(res.statistic_location, ref.statistic_location) + xp_assert_equal(res.statistic_sign, ref.statistic_sign) + + # missing: no test that uses *args + + +@make_xp_test_case(stats.ks_1samp) +class TestKSOneSample: + """ + Tests kstest and ks_samp 1-samples with K-S various sizes, alternatives, modes. + """ + + def _testOne(self, x, alternative, expected_statistic, expected_prob, *, + mode='auto', dtype, xp): + rtol = 5e-14 if dtype == xp.float64 else 1e-5 + res = stats.ks_1samp(x, special.ndtr, alternative=alternative, mode=mode) + ref_statistic = xp.asarray(expected_statistic, dtype=dtype) + ref_pvalue = xp.asarray(expected_prob, dtype=dtype) + xp_assert_close(res.statistic, ref_statistic, rtol=rtol) + xp_assert_close(res.pvalue, ref_pvalue, rtol=rtol) + + @pytest.mark.parametrize('dtype', [None, 'float32', 'float64']) + def test_agree_with_r(self, dtype, xp): + # comparing with some values from R + dtype = xp_default_dtype(xp) if dtype is None else getattr(xp, dtype) + x = xp.linspace(-1, 1, 9, dtype=dtype) + self._testOne(x, 'two-sided', 0.15865525393145705, 0.95164069201518386, + dtype=dtype, xp=xp) + + x = xp.linspace(-15, 15, 9, dtype=dtype) + self._testOne(x, 'two-sided', 0.44435602715924361, 0.038850140086788665, + dtype=dtype, xp=xp) + + x = [-1.23, 0.06, -0.60, 0.17, 0.66, -0.17, -0.08, 0.27, -0.98, -0.99] + x = xp.asarray(x, dtype=dtype) + self._testOne(x, 'two-sided', 0.293580126801961, 0.293408463684361, + dtype=dtype, xp=xp) + self._testOne(x, 'greater', 0.293580126801961, 0.146988835042376, mode='exact', + dtype=dtype, xp=xp) + self._testOne(x, 'less', 0.109348552425692, 0.732768892470675, mode='exact', + dtype=dtype, xp=xp) + + @pytest.mark.parametrize('dtype', [None, 'float32', 'float64']) + def test_known_examples(self, xp, dtype): + # the following tests rely on deterministically replicated rvs + dtype = xp_default_dtype(xp) if dtype is None else getattr(xp, dtype) + x = stats.norm.rvs(loc=0.2, size=100, random_state=987654321) + x = xp.asarray(x, dtype=dtype) + self._testOne(x, 'two-sided', 0.12464329735846891, 0.089444888711820769, + mode='asymp', xp=xp, dtype=dtype) + self._testOne(x, 'less', 0.12464329735846891, 0.040989164077641749, + xp=xp, dtype=dtype) + self._testOne(x, 'greater', 0.0072115233216310994, 0.98531158590396228, + xp=xp, dtype=dtype) + + # this is a test of the exact p-value calculation, available only with NumPy. + def test_ks1samp_allpaths(self): + # Check NaN input, output. + assert_(np.isnan(kolmogn(np.nan, 1, True))) + with assert_raises(ValueError, match='n is not integral: 1.5'): + kolmogn(1.5, 1, True) + assert_(np.isnan(kolmogn(-1, 1, True))) + + dataset = np.asarray([ + # Check x out of range + (101, 1, True, 1.0), + (101, 1.1, True, 1.0), + (101, 0, True, 0.0), + (101, -0.1, True, 0.0), + + (32, 1.0 / 64, True, 0.0), # Ruben-Gambino + (32, 1.0 / 64, False, 1.0), # Ruben-Gambino + + # Miller + (32, 0.5, True, 0.9999999363163307), + # Miller 2 * special.smirnov(32, 0.5) + (32, 0.5, False, 6.368366937916623e-08), + + # Check some other paths + (32, 1.0 / 8, True, 0.34624229979775223), + (32, 1.0 / 4, True, 0.9699508336558085), + (1600, 0.49, False, 0.0), + # 2 * special.smirnov(1600, 1/16.0) + (1600, 1 / 16.0, False, 7.0837876229702195e-06), + # _kolmogn_DMTW + (1600, 14 / 1600, False, 0.99962357317602), + # _kolmogn_PelzGood + (1600, 1 / 32, False, 0.08603386296651416), + ]) + FuncData(kolmogn, dataset, (0, 1, 2), 3).check(dtypes=[int, float, bool]) + + @pytest.mark.parametrize("ksfunc", [stats.kstest, stats.ks_1samp]) + @pytest.mark.parametrize("alternative, x6val, ref_location, ref_sign", + [('greater', 6., 6., +1), + ('less', 7., 7., -1), + ('two-sided', 6., 6., +1), + ('two-sided', 7., 7., -1)]) + def test_location_sign(self, ksfunc, alternative, + x6val, ref_location, ref_sign, xp): + # Test that location and sign corresponding with statistic are as + # expected. (Test is designed to be easy to predict.) + x = xp.arange(10.) + 0.5 + x = xpx.at(x)[6].set(x6val) + # cdf = stats.uniform(scale=10).cdf + def cdf(x): return x / 10. + res = ksfunc(xp.asarray(x), cdf, alternative=alternative) + rtol = 1e-15 if x.dtype == xp.float64 else 1e-6 + xp_assert_close(res.statistic, xp.asarray(0.1), rtol=rtol) + xp_assert_equal(res.statistic_location, xp.asarray(ref_location)) + xp_assert_equal(res.statistic_sign, xp.asarray(ref_sign, dtype=xp.int8)) + + def test_invalid_method_raises(self): + # gh-24737: unrecognized method strings must raise ValueError, not + # silently fall through to the 'approx' else-clause. + rng = np.random.default_rng(42) + x = rng.standard_normal(10) + with pytest.raises(ValueError, match="Invalid value for method"): + stats.ks_1samp(x, special.ndtr, method='any_other_string') + # must fire for one-sided alternatives too, not just two-sided + with pytest.raises(ValueError, match="Invalid value for method"): + stats.ks_1samp(x, special.ndtr, method='typo', + alternative='greater') + with pytest.raises(ValueError, match="Invalid value for method"): + stats.ks_1samp(x, special.ndtr, method='typo', + alternative='less') + # kstest wrapper must also raise + with pytest.raises(ValueError, match="Invalid value for method"): + stats.kstest(x, stats.norm.cdf, method='any_other_string') + +@make_xp_test_case(stats.ks_2samp) +class TestKSTwoSamples: + """Tests 2-samples with K-S various sizes, alternatives, modes.""" + + def _check_warnings(self, warn_list, expected_type, expected_len): + """ + Checks that all of the warnings from a list returned by + `warnings.catch_all(record=True)` are of the required type and that the list + contains expected number of warnings. + """ + assert_equal(len(warn_list), expected_len, "number of warnings") + for warn_ in warn_list: + assert_(warn_.category is expected_type) + + def _testOne(self, x1, x2, alternative, ref_statistic, ref_pvalue, + mode='auto', *, xp): + x1, x2 = xp.asarray(x1), xp.asarray(x2) + dtype = xp_result_type(x1, x2, force_floating=True, xp=xp) + res_statistic, res_pvalue = stats.ks_2samp(x1, x2, alternative, mode=mode) + ref_statistic = xp.asarray(ref_statistic, dtype=dtype) + ref_pvalue = xp.asarray(ref_pvalue, dtype=dtype) + xp_assert_close(res_statistic, ref_statistic) + # The original tests used assert_array_almost_equal, which has an absolute + # tolerance by default. It would be good to test with only a relative tolerance, + # but that would require computing p-values with another reference. R would be + # the natural choice, but it clips tiny p-values. + xp_assert_close(res_pvalue, ref_pvalue, atol=1e-14) + + def testSmall(self, xp): + self._testOne([0], [1], 'two-sided', 1.0/1, 1.0, xp=xp) + self._testOne([0], [1], 'greater', 1.0/1, 0.5, xp=xp) + self._testOne([0], [1], 'less', 0.0/1, 1.0, xp=xp) + self._testOne([1], [0], 'two-sided', 1.0/1, 1.0, xp=xp) + self._testOne([1], [0], 'greater', 0.0/1, 1.0, xp=xp) + self._testOne([1], [0], 'less', 1.0/1, 0.5, xp=xp) + + def testTwoVsThree(self, xp): + data1 = xp.asarray([1.0, 2.0]) + data1p = data1 + 0.01 + data1m = data1 - 0.01 + data2 = xp.asarray([1.0, 2.0, 3.0]) + self._testOne(data1p, data2, 'two-sided', 1.0 / 3, 1.0, xp=xp) + self._testOne(data1p, data2, 'greater', 1.0 / 3, 0.7, xp=xp) + self._testOne(data1p, data2, 'less', 1.0 / 3, 0.7, xp=xp) + self._testOne(data1m, data2, 'two-sided', 2.0 / 3, 0.6, xp=xp) + self._testOne(data1m, data2, 'greater', 2.0 / 3, 0.3, xp=xp) + self._testOne(data1m, data2, 'less', 0, 1.0, xp=xp) + + def testTwoVsFour(self, xp): + data1 = xp.asarray([1.0, 2.0]) + data1p = data1 + 0.01 + data1m = data1 - 0.01 + data2 = xp.asarray([1.0, 2.0, 3.0, 4.0]) + self._testOne(data1p, data2, 'two-sided', 2.0 / 4, 14.0/15, xp=xp) + self._testOne(data1p, data2, 'greater', 2.0 / 4, 8.0/15, xp=xp) + self._testOne(data1p, data2, 'less', 1.0 / 4, 12.0/15, xp=xp) + + self._testOne(data1m, data2, 'two-sided', 3.0 / 4, 6.0/15, xp=xp) + self._testOne(data1m, data2, 'greater', 3.0 / 4, 3.0/15, xp=xp) + self._testOne(data1m, data2, 'less', 0, 1.0, xp=xp) + + def test100_100(self, xp): + x100 = xp.linspace(1, 100, 100) + x100_2_p1 = x100 + 2 + 0.1 + x100_2_m1 = x100 + 2 - 0.1 + self._testOne(x100, x100_2_p1, 'two-sided', 3.0 / 100, 0.999999999996206, xp=xp) + self._testOne(x100, x100_2_p1, 'greater', 3.0 / 100, 0.9143290114276248, xp=xp) + self._testOne(x100, x100_2_p1, 'less', 0, 1.0, xp=xp) + self._testOne(x100, x100_2_m1, 'two-sided', 2.0 / 100, 1.0, xp=xp) + self._testOne(x100, x100_2_m1, 'greater', 2.0 / 100, 0.960978450786184, xp=xp) + self._testOne(x100, x100_2_m1, 'less', 0, 1.0, xp=xp) + + def test100_110(self, xp): + x100 = xp.linspace(1, 100, 100) + x110 = xp.linspace(1, 100, 110) + x110_20_p1 = x110 + 20 + 0.1 + x110_20_m1 = x110 + 20 - 0.1 + # 100, 110 + self._testOne(x100, x110_20_p1, 'two-sided', + 232.0 / 1100, 0.015739183865607353, xp=xp) + self._testOne(x100, x110_20_p1, 'greater', + 232.0 / 1100, 0.007869594319053203, xp=xp) + self._testOne(x100, x110_20_p1, 'less', + 0, 1, xp=xp) + self._testOne(x100, x110_20_m1, 'two-sided', + 229.0 / 1100, 0.017803803861026313, xp=xp) + self._testOne(x100, x110_20_m1, 'greater', + 229.0 / 1100, 0.008901905958245056, xp=xp) + self._testOne(x100, x110_20_m1, 'less', + 0.0, 1.0, xp=xp) + + def testRepeatedValues(self, xp): + x2233 = xp.asarray([2] * 3 + [3] * 4 + [5] * 5 + [6] * 4) + x3344 = x2233 + 1 + x2356 = xp.asarray([2] * 3 + [3] * 4 + [5] * 10 + [6] * 4) + x3467 = xp.asarray([3] * 10 + [4] * 2 + [6] * 10 + [7] * 4) + self._testOne(x2233, x3344, 'two-sided', 5.0/16, 0.4262934613454952, xp=xp) + self._testOne(x2233, x3344, 'greater', 5.0/16, 0.21465428276573786, xp=xp) + self._testOne(x2233, x3344, 'less', 0.0/16, 1.0, xp=xp) + self._testOne(x2356, x3467, 'two-sided', 190.0/21/26, 0.0919245790168125, xp=xp) + self._testOne(x2356, x3467, 'greater', 190.0/21/26, 0.0459633806858544, xp=xp) + self._testOne(x2356, x3467, 'less', 70.0/21/26, 0.6121593130022775, xp=xp) + + def testEqualSizes(self, xp): + data2 = xp.asarray([1.0, 2.0, 3.0]) + self._testOne(data2, data2+1, 'two-sided', 1.0/3, 1.0, xp=xp) + self._testOne(data2, data2+1, 'greater', 1.0/3, 0.75, xp=xp) + self._testOne(data2, data2+1, 'less', 0.0/3, 1., xp=xp) + self._testOne(data2, data2+0.5, 'two-sided', 1.0/3, 1.0, xp=xp) + self._testOne(data2, data2+0.5, 'greater', 1.0/3, 0.75, xp=xp) + self._testOne(data2, data2+0.5, 'less', 0.0/3, 1., xp=xp) + self._testOne(data2, data2-0.5, 'two-sided', 1.0/3, 1.0, xp=xp) + self._testOne(data2, data2-0.5, 'greater', 0.0/3, 1.0, xp=xp) + self._testOne(data2, data2-0.5, 'less', 1.0/3, 0.75, xp=xp) + + @pytest.mark.slow + def testMiddlingBoth(self, xp): + # 500, 600 + n1, n2 = 500, 600 + delta = 1.0/n1/n2/2/2 + x = xp.linspace(1., 200., n1) - delta + y = xp.linspace(2., 200., n2) + self._testOne(x, y, 'two-sided', 2000.0 / n1 / n2, 1.0, + mode='auto', xp=xp) + self._testOne(x, y, 'two-sided', 2000.0 / n1 / n2, 1.0, + mode='asymp', xp=xp) + self._testOne(x, y, 'greater', 2000.0 / n1 / n2, 0.9697596024683929, + mode='asymp', xp=xp) + self._testOne(x, y, 'less', 500.0 / n1 / n2, 0.9968735843165021, + mode='asymp', xp=xp) + with warnings.catch_warnings(): + message = "ks_2samp: Exact calculation unsuccessful." + warnings.filterwarnings("ignore", message, RuntimeWarning) + self._testOne(x, y, 'greater', 2000.0 / n1 / n2, 0.9697596024683929, + mode='exact', xp=xp) + self._testOne(x, y, 'less', 500.0 / n1 / n2, 0.9968735843165021, + mode='exact', xp=xp) + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + self._testOne(x, y, 'less', 500.0 / n1 / n2, 0.9968735843165021, + mode='exact', xp=xp) + self._check_warnings(w, RuntimeWarning, 1) + + @pytest.mark.slow + def testMediumBoth(self, xp): + # 1000, 1100 + n1, n2 = 1000, 1100 + delta = 1.0/n1/n2/2/2 + x = xp.linspace(1., 200., n1) - delta + y = xp.linspace(2., 200., n2) + self._testOne(x, y, 'two-sided', 6600.0 / n1 / n2, 1.0, + mode='asymp', xp=xp) + self._testOne(x, y, 'two-sided', 6600.0 / n1 / n2, 1.0, + mode='auto', xp=xp) + self._testOne(x, y, 'greater', 6600.0 / n1 / n2, 0.9573185808092622, + mode='asymp', xp=xp) + self._testOne(x, y, 'less', 1000.0 / n1 / n2, 0.9982410869433984, + mode='asymp', xp=xp) + + with warnings.catch_warnings(): + message = "ks_2samp: Exact calculation unsuccessful." + warnings.filterwarnings("ignore", message, RuntimeWarning) + self._testOne(x, y, 'greater', 6600.0 / n1 / n2, 0.9573185808092622, + mode='exact', xp=xp) + self._testOne(x, y, 'less', 1000.0 / n1 / n2, 0.9982410869433984, + mode='exact', xp=xp) + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + self._testOne(x, y, 'less', 1000.0 / n1 / n2, 0.9982410869433984, + mode='exact', xp=xp) + self._check_warnings(w, RuntimeWarning, 1) + + def testLarge(self, xp): + # 10000, 110 + n1, n2 = 10000, 110 + lcm = n1*11.0 + delta = 1.0/n1/n2/2/2 + x = xp.linspace(1., 200., n1) - delta + y = xp.linspace(2., 100., n2) + self._testOne(x, y, 'two-sided', 55275.0 / lcm, 4.2188474935755949e-15, xp=xp) + self._testOne(x, y, 'greater', 561.0 / lcm, 0.99115454582047591, xp=xp) + self._testOne(x, y, 'less', 55275.0 / lcm, 3.1317328311518713e-26, xp=xp) + + def test_gh11184(self, xp): + # 3000, 3001, exact two-sided + rng = np.random.RandomState(123456) + x = rng.normal(size=3000).tolist() + y = rng.normal(size=3001, scale=1.5).tolist() + self._testOne(x, y, 'two-sided', 0.11292880151060758, 2.7755575615628914e-15, + mode='asymp', xp=xp) + self._testOne(x, y, 'two-sided', 0.11292880151060758, 2.7755575615628914e-15, + mode='exact', xp=xp) + + @pytest.mark.xslow + def test_gh11184_bigger(self, xp): + # 10000, 10001, exact two-sided + rng = np.random.RandomState(123456) + x = rng.normal(size=10000).tolist() + y = rng.normal(size=10001, scale=1.5).tolist() + self._testOne(x, y, 'two-sided', 0.10597913208679133, 3.3149311398483503e-49, + mode='asymp', xp=xp) + self._testOne(x, y, 'two-sided', 0.10597913208679133, 2.7755575615628914e-15, + mode='exact', xp=xp) + self._testOne(x, y, 'greater', 0.10597913208679133, 2.7947433906389253e-41, + mode='asymp', xp=xp) + self._testOne(x, y, 'less', 0.09658002199780022, 2.7947433906389253e-41, + mode='asymp', xp=xp) + + @pytest.mark.xslow + def test_gh12999(self, xp): + rng = np.random.RandomState(123456) + for x in range(1000, 12000, 1000): + vals1 = xp.asarray(rng.normal(size=(x))) + vals2 = xp.asarray(rng.normal(size=(x + 10), loc=0.5)) + exact = stats.ks_2samp(vals1, vals2, mode='exact').pvalue + asymp = stats.ks_2samp(vals1, vals2, mode='asymp').pvalue + # these two p-values should be in line with each other + xp_assert_less(exact, 3 * asymp) + xp_assert_less(asymp, 3 * exact) + + @pytest.mark.slow + def testLargeBoth(self, xp): + # 10000, 11000 + n1, n2 = 10000, 11000 + lcm = n1*11.0 + delta = 1.0/n1/n2/2/2 + x = xp.linspace(1, 200, n1) - delta + y = xp.linspace(2, 200, n2) + self._testOne(x, y, 'two-sided', 563.0 / lcm, 0.9990660108966576, + mode='asymp', xp=xp) + self._testOne(x, y, 'two-sided', 563.0 / lcm, 0.9990456491488628, + mode='exact', xp=xp) + self._testOne(x, y, 'two-sided', 563.0 / lcm, 0.9990660108966576, + mode='auto', xp=xp) + self._testOne(x, y, 'greater', 563.0 / lcm, 0.7561851877420673, xp=xp) + self._testOne(x, y, 'less', 10.0 / lcm, 0.9998239693191724, xp=xp) + with warnings.catch_warnings(): + message = "ks_2samp: Exact calculation unsuccessful." + warnings.filterwarnings("ignore", message, RuntimeWarning) + self._testOne(x, y, 'greater', 563.0 / lcm, 0.7561851877420673, + mode='exact', xp=xp) + self._testOne(x, y, 'less', 10.0 / lcm, 0.9998239693191724, + mode='exact', xp=xp) + + def testNamedAttributes(self, xp): + # test for namedtuple attribute results + attributes = ('statistic', 'pvalue') + res = stats.ks_2samp(xp.asarray([1, 2]), xp.asarray([3])) + check_named_results(res, attributes, xp=xp) + + @pytest.mark.slow + @skip_xp_backends(np_only=True) + def test_some_code_paths(self): + # Check that some code paths are executed + from scipy.stats._stats_py import ( + _count_paths_outside_method, + _compute_outer_prob_inside_method + ) + + _compute_outer_prob_inside_method(1, 1, 1, 1) + _count_paths_outside_method(1000, 1, 1, 1001) + + with np.errstate(invalid='raise'): + assert_raises(FloatingPointError, _count_paths_outside_method, + 1100, 1099, 1, 1) + assert_raises(FloatingPointError, _count_paths_outside_method, + 2000, 1000, 1, 1) + + @pytest.mark.parametrize('case', (([], [1.]), ([1.], []), ([], []))) + def test_argument_checking(self, case, xp): + # Check that an empty array warns + with eager_warns(SmallSampleWarning, match=too_small_1d_not_omit, xp=xp): + res = stats.ks_2samp(xp.asarray(case[0]), xp.asarray(case[1])) + xp_assert_equal(res.statistic, xp.asarray(np.nan)) + xp_assert_equal(res.pvalue, xp.asarray(np.nan)) + + @pytest.mark.xslow + def test_gh12218(self, xp): + """Ensure gh-12218 is fixed.""" + # gh-1228 triggered a TypeError calculating sqrt(n1*n2*(n1+n2)). + # n1, n2 both large integers, the product exceeded 2^64 + rng = np.random.default_rng(8751495592) + n1 = 2097152 # 2*^21 + rvs1 = xp.asarray(stats.uniform.rvs(size=n1, random_state=rng).tolist()) + rvs2 = rvs1 + 1 # Exact value of rvs2 doesn't matter. + stats.ks_2samp(rvs1, rvs2, alternative='greater', mode='asymp') + stats.ks_2samp(rvs1, rvs2, alternative='less', mode='asymp') + stats.ks_2samp(rvs1, rvs2, alternative='two-sided', mode='asymp') + + @pytest.mark.parametrize("dtype", [None, 'float32', 'float64']) + def test_warnings_gh_14019(self, dtype, xp): + dtype = xp_default_dtype(xp) if dtype is None else getattr(xp, dtype) + # Check that RuntimeWarning is raised when method='auto' and exact + # p-value calculation fails. See gh-14019. + rng = np.random.RandomState(seed=23493549) + # random samples of the same size as in the issue + data1 = xp.asarray(rng.random(size=881) + 0.5, dtype=dtype) + data2 = xp.asarray(rng.random(size=369), dtype=dtype) + message = "ks_2samp: Exact calculation unsuccessful" + with eager_warns(RuntimeWarning, match=message, xp=xp): + res = stats.ks_2samp(data1, data2, alternative='less') + xp_assert_close(res.pvalue, xp.asarray(0., dtype=dtype), atol=1e-14) + + @pytest.mark.parametrize("ksfunc", [stats.kstest, stats.ks_2samp]) + @pytest.mark.parametrize("alternative, x6val, ref_location, ref_sign", + [('greater', 5.9, 5.9, +1), + ('less', 6.1, 6.0, -1), + ('two-sided', 5.9, 5.9, +1), + ('two-sided', 6.1, 6.0, -1)]) + def test_location_sign(self, ksfunc, alternative, + x6val, ref_location, ref_sign, xp): + # Test that location and sign corresponding with statistic are as + # expected. (Test is designed to be easy to predict.) + x = xp.arange(10., dtype=xp.float64) + y = xp_copy(x) + x = xpx.at(x)[6].set(x6val) + res = stats.ks_2samp(x, y, alternative=alternative) + xp_assert_close(res.statistic, xp.asarray(0.1, dtype=xp.float64), rtol=1e-15) + assert res.statistic_location == ref_location + assert res.statistic_sign == ref_sign + + +@pytest.mark.filterwarnings('ignore:divide by zero:RuntimeWarning:dask') +@pytest.mark.filterwarnings('ignore:invalid value:RuntimeWarning:dask') +@make_xp_test_case(stats.ttest_rel) +class TestTTestRel: + def test_ttest_rel_xp(self, xp): + # stats.ttest_rel had no tests using the xp fixture. As a temporary + # measure to get tools/check_xp_untested.py to pass, a portion of + # test_ttest_rel has been converted. It might seem unnecessary to + # require tests for a trivial wrapper of a well-tested function, but + # this seems simpler than having a way to carve out exceptions to + # check_xp_untested. + tr = xp.asarray(0.81248591389165692, dtype=xp.float64) + pr = xp.asarray(0.41846234511362157, dtype=xp.float64) + + rvs1 = xp.linspace(1, 100, 100, dtype=xp.float64) + rvs2 = xp.linspace(1.01, 99.989, 100, dtype=xp.float64) + rvs1_2D = xp.stack( + [ + xp.linspace(1,100, 100, dtype=xp.float64), + xp.linspace(1.01, 99.989, 100, dtype=xp.float64) + ] + ) + rvs2_2D = xp.stack( + [ + xp.linspace(1.01, 99.989, 100, dtype=xp.float64), + xp.linspace(1, 100, 100, dtype=xp.float64) + ] + ) + + t, p = stats.ttest_rel(rvs1, rvs2, axis=0) + xp_assert_close(t, tr) + xp_assert_close(p, pr) + + t, p = stats.ttest_rel(rvs1_2D.T, rvs2_2D.T, axis=0) + xp_assert_close(t, xp.stack([tr, -tr])) + xp_assert_close(p, xp.stack([pr, pr])) + + t, p = stats.ttest_rel(rvs1_2D, rvs2_2D, axis=1) + xp_assert_close(t, xp.stack([tr, -tr])) + xp_assert_close(p, xp.stack([pr, pr])) + + def test_ttest_rel(self, xp): + dtype = xp.float64 # doesn't pass with default tolerances otherwise + + # regression test + tr = xp.asarray(0.81248591389165692, dtype=dtype) + pr = xp.asarray(0.41846234511362157, dtype=dtype) + tpr = xp.stack([tr, -tr]), xp.stack([pr, pr]) + + rvs1 = xp.linspace(1, 100, 100, dtype=dtype) + rvs2 = xp.linspace(1.01, 99.989, 100, dtype=dtype) + rvs1_2D = xp.stack([xp.linspace(1, 100, 100, dtype=dtype), + xp.linspace(1.01, 99.989, 100, dtype=dtype)]) + rvs2_2D = xp.stack([xp.linspace(1.01, 99.989, 100, dtype=dtype), + xp.linspace(1, 100, 100, dtype=dtype)]) + + t, p = stats.ttest_rel(rvs1, rvs2, axis=0) + xp_assert_close(t, tr) + xp_assert_close(p, pr) + t,p = stats.ttest_rel(rvs1_2D.T, rvs2_2D.T, axis=0) + xp_assert_close(t, tpr[0]) + xp_assert_close(p, tpr[1]) + t,p = stats.ttest_rel(rvs1_2D, rvs2_2D, axis=1) + xp_assert_close(t, tpr[0]) + xp_assert_close(p, tpr[1]) + + # test scalars + with warnings.catch_warnings(), \ + np.errstate(invalid="ignore", divide="ignore"): + warnings.filterwarnings( + "ignore", "Degrees of freedom <= 0 for slice", RuntimeWarning) + t, p = stats.ttest_rel(xp.asarray(4.), xp.asarray(3.)) + assert xp.isnan(t) + assert xp.isnan(p) + + # test for namedtuple attribute results + attributes = ('statistic', 'pvalue') + res = stats.ttest_rel(rvs1, rvs2, axis=0) + check_named_results(res, attributes, xp=xp) + + # test on 3 dimensions + rvs1_3D = xp.stack([rvs1_2D, rvs1_2D, rvs1_2D]) + rvs2_3D = xp.stack([rvs2_2D, rvs2_2D, rvs2_2D]) + t, p = stats.ttest_rel(rvs1_3D, rvs2_3D, axis=-1) + xp_assert_close(t, xp.stack((tpr[0],)*3)) + xp_assert_close(p, xp.stack((tpr[1],)*3)) + + t, p = stats.ttest_rel(xp.moveaxis(rvs1_3D, 2, 1), + xp.moveaxis(rvs2_3D, 2, 1), + axis=1) + xp_assert_close(t, xp.stack((tpr[0],)*3)) + xp_assert_close(p, xp.stack((tpr[1],)*3)) + + # test alternative parameter + message = "`alternative` must be 'less', 'greater', or 'two-sided'." + with pytest.raises(ValueError, match=message): + stats.ttest_rel(rvs1, rvs2, alternative="error") + + t, p = stats.ttest_rel(rvs1, rvs2, axis=0, alternative="less") + xp_assert_close(p, xp.asarray(1 - pr/2)) + xp_assert_close(t, xp.asarray(tr)) + + t, p = stats.ttest_rel(rvs1, rvs2, axis=0, alternative="greater") + xp_assert_close(p, xp.asarray(pr/2)) + xp_assert_close(t, xp.asarray(tr)) + + def test_nan_policy_propagate(self, xp): + # check nan policy + rng = np.random.RandomState(12345678) + x = stats.norm.rvs(loc=5, scale=10, size=501, random_state=rng) + x[500] = np.nan + y = (stats.norm.rvs(loc=5, scale=10, size=501, random_state=rng) + + stats.norm.rvs(scale=0.2, size=501, random_state=rng)) + y[500] = np.nan + x, y = xp.asarray(x.tolist()), xp.asarray(y.tolist()) + + res = stats.ttest_rel(x, x) + xp_assert_equal(res.statistic, xp.asarray(xp.nan)) + xp_assert_equal(res.pvalue, xp.asarray(xp.nan)) + + @skip_xp_backends(eager_only=True, reason='lazy->limited nan_policy support') + def test_nan_policy(self, xp): + # check nan policy + rng = np.random.RandomState(12345678) + x = stats.norm.rvs(loc=5, scale=10, size=501, random_state=rng) + x[500] = np.nan + y = (stats.norm.rvs(loc=5, scale=10, size=501, random_state=rng) + + stats.norm.rvs(scale=0.2, size=501, random_state=rng)) + y[500] = np.nan + x, y = xp.asarray(x.tolist()), xp.asarray(y.tolist()) + + res = stats.ttest_rel(x, y, nan_policy='omit') + xp_assert_close(res.statistic, xp.asarray(0.25299925303978066)) + xp_assert_close(res.pvalue, xp.asarray(0.8003729814201519)) + + message = "The input contains nan values" + with pytest.raises(ValueError, match=message): + stats.ttest_rel(x, y, nan_policy='raise') + + message = "nan_policy must be one of" + with pytest.raises(ValueError, match=message): + stats.ttest_rel(x, y, nan_policy='foobar') + + def test_edge_cases(self, xp): + # test zero division problem + with eager_warns(RuntimeWarning, match="Precision loss occurred", xp=xp): + t, p = stats.ttest_rel(xp.asarray([0, 0, 0]), xp.asarray([1, 1, 1])) + + xp_assert_equal(xp.abs(t), xp.asarray(xp.inf)) + xp_assert_equal(p, xp.asarray(0.0)) + + with np.errstate(invalid="ignore"): + res = stats.ttest_rel(xp.asarray([0, 0, 0]), xp.asarray([0, 0, 0])) + xp_assert_equal(res.statistic, xp.asarray(xp.nan)) + xp_assert_equal(res.pvalue, xp.asarray(xp.nan)) + + anan = xp.asarray([[1., np.nan], [-1., 1.]]) + res = stats.ttest_rel(anan, xp.zeros((2, 2))) + xp_assert_equal(res.statistic, xp.asarray([0, np.nan])) + xp_assert_equal(res.pvalue, xp.asarray([1, np.nan])) + + # test incorrect input shape raise an error + x = xp.arange(24) + message = ("Array shapes are incompatible for broadcasting.|" + "operands could not be broadcast together") + with pytest.raises(ValueError, match=message): + stats.ttest_rel(xp.reshape(x, (8, 3)), xp.reshape(x, (2, 3, 4))) + + @skip_xp_backends(np_only=True, reason="multidimensional 'omit' is np-only") + def test_multidimensional_nan_policy_omit(self, xp): + rvs1_2D = np.stack([np.linspace(1, 100, 100), + np.linspace(1.01, 99.989, 100)]) + rvs2_2D = np.stack([np.linspace(1.01, 99.989, 100), + np.linspace(1, 100, 100)]) + rvs1_2D[:, 20:30] = np.nan + rvs2_2D[:, 15:25] = np.nan + + # Convert from two-sided p-values to one sided using T result data. + @np.vectorize + def converter(t, p, alt): + if (t < 0 and alt == "less") or (t > 0 and alt == "greater"): + return p / 2 + return 1 - (p / 2) + + with pytest.warns(SmallSampleWarning, match=too_small_nd_omit): + tr, pr = stats.ttest_rel(rvs1_2D, rvs2_2D, 0, nan_policy='omit') + + with pytest.warns(SmallSampleWarning, match=too_small_nd_omit): + t, p = stats.ttest_rel(rvs1_2D, rvs2_2D, 0, + nan_policy='omit', alternative='less') + assert_allclose(t, tr, rtol=1e-14) + with np.errstate(invalid='ignore'): + assert_allclose(p, converter(tr, pr, 'less'), rtol=1e-14) + + with pytest.warns(SmallSampleWarning, match=too_small_nd_omit): + t, p = stats.ttest_rel(rvs1_2D, rvs2_2D, 0, + nan_policy='omit', alternative='greater') + assert_allclose(t, tr, rtol=1e-14) + with np.errstate(invalid='ignore'): + assert_allclose(p, converter(tr, pr, 'greater'), rtol=1e-14) + + @skip_xp_backends(eager_only=True, reason="lazy -> limited nan_policy support") + def test_ttest_rel_nan_2nd_arg(self, xp): + # regression test for gh-6134: nans in the second arg were not handled + x = xp.asarray([xp.nan, 2.0, 3.0, 4.0]) + y = xp.asarray([1.0, 2.0, 1.0, 2.0]) + + r1 = stats.ttest_rel(x, y, nan_policy='omit') + r2 = stats.ttest_rel(y, x, nan_policy='omit') + xp_assert_close(r2.statistic, -r1.statistic) + xp_assert_close(r2.pvalue, r1.pvalue) + + # NB: arguments are paired when NaNs are dropped + r3 = stats.ttest_rel(y[1:], x[1:]) + xp_assert_close(r2.statistic, r3.statistic) + xp_assert_close(r2.pvalue, r3.pvalue) + + # .. and this is consistent with R. R code: + # x = c(NA, 2.0, 3.0, 4.0) + # y = c(1.0, 2.0, 1.0, 2.0) + # t.test(x, y, paired=TRUE)), + xp_assert_close(r2.statistic, xp.asarray(-2.), atol=1e-4) + xp_assert_close(r2.pvalue, xp.asarray(0.1835), atol=1e-4) + + def test_ttest_rel_empty_1d_returns_nan(self, xp): + # Two empty inputs should return a TtestResult containing nan + # for both values. + with eager_warns(SmallSampleWarning, match=too_small_1d_not_omit, xp=xp): + result = stats.ttest_rel(xp.asarray([]), xp.asarray([])) + assert isinstance(result, stats._stats_py.TtestResult) + xp_assert_equal(result.statistic, xp.asarray(xp.nan)) + xp_assert_equal(result.pvalue, xp.asarray(xp.nan)) + + @skip_xp_backends("dask.array", reason="dask fails this one") + @pytest.mark.parametrize('b, expected_shape', + [(np.empty((1, 5, 0)), (3, 5)), + (np.empty((1, 0, 0)), (3, 0))]) + def test_ttest_rel_axis_size_zero(self, b, expected_shape, xp): + # In this test, the length of the axis dimension is zero. + # The results should be arrays containing nan with shape + # given by the broadcast nonaxis dimensions. + a = xp.empty((3, 1, 0)) + b = xp.asarray(b, dtype=a.dtype) + with warnings.catch_warnings(): + # first case should warn, second shouldn't? + warnings.filterwarnings("ignore", too_small_nd_not_omit, SmallSampleWarning) + result = stats.ttest_rel(a, b, axis=-1) + assert isinstance(result, stats._stats_py.TtestResult) + expected_value = xp.full(expected_shape, fill_value=xp.nan) + xp_assert_equal(result.statistic, expected_value) + xp_assert_equal(result.pvalue, expected_value) + + def test_ttest_rel_nonaxis_size_zero(self, xp): + # In this test, the length of the axis dimension is nonzero, + # but one of the nonaxis dimensions has length 0. Check that + # we still get the correctly broadcast shape, which is (5, 0) + # in this case. + a = xp.empty((1, 8, 0)) + b = xp.empty((5, 8, 1)) + result = stats.ttest_rel(a, b, axis=1) + assert isinstance(result, stats._stats_py.TtestResult) + assert result.statistic.shape == (5, 0) + assert result.pvalue.shape == (5, 0) + + @skip_xp_backends("jax.numpy", reason="needs stdtrit; lazy->can't use root finder") + @pytest.mark.parametrize("alternative", ['two-sided', 'less', 'greater']) + def test_ttest_rel_ci_1d(self, alternative, xp): + # test confidence interval method against reference values + rng = np.random.default_rng(3749065329432213059) + n = 10 + x = xp.asarray(rng.normal(size=n, loc=1.5, scale=2).tolist()) + y = xp.asarray(rng.normal(size=n, loc=2, scale=2).tolist()) + # Reference values generated with R t.test: + # options(digits=16) + # x = c(1.22825792, 1.63950485, 4.39025641, 0.68609437, 2.03813481, + # -1.20040109, 1.81997937, 1.86854636, 2.94694282, 3.94291373) + # y = c(3.49961496, 1.53192536, 5.53620083, 2.91687718, 0.04858043, + # 3.78505943, 3.3077496 , 2.30468892, 3.42168074, 0.56797592) + # t.test(x, y, paired=TRUE, conf.level=0.85, alternative='l') + + ref = {'two-sided': [-1.912194489914035, 0.400169725914035], + 'greater': [-1.563944820311475, np.inf], + 'less': [-np.inf, 0.05192005631147523]} + res = stats.ttest_rel(x, y, alternative=alternative) + ci = res.confidence_interval(confidence_level=0.85) + xp_assert_close(ci.low, xp.asarray(ref[alternative][0])) + xp_assert_close(ci.high, xp.asarray(ref[alternative][1])) + xp_assert_close(res.df, xp.asarray(n-1)) + + +@pytest.mark.parametrize("test_fun, args", [ + make_xp_pytest_param(stats.ttest_1samp, (np.arange(10), 0)), + make_xp_pytest_param(stats.ttest_rel, (np.arange(10), np.arange(10)))]) +def test_ttest_ci_iv(test_fun, args, xp): + # test `confidence_interval` method input validation + res = test_fun(*(xp.asarray(arg) for arg in args)) + message = '`confidence_level` must be a number between 0 and 1.' + with pytest.raises(ValueError, match=message): + res.confidence_interval(confidence_level=10) + + +def _desc_stats(x1, x2, axis=0, *, xp=None): + xp = array_namespace(x1, x2) if xp is None else xp + + def _stats(x, axis=0): + x = xp.asarray(x) + mu = xp.mean(x, axis=axis) + std = xp.std(x, axis=axis, correction=1) + nobs = x.shape[axis] + return mu, std, nobs + + return _stats(x1, axis) + _stats(x2, axis) + + +@make_xp_test_case(stats.ttest_ind, stats.ttest_ind_from_stats) +class TestTTestInd: + def test_ttest_ind(self, xp): + # regression test + tr = xp.asarray(1.0912746897927283) + pr = xp.asarray(0.27647818616351882) + tr_2D = xp.stack([tr, -tr]) + pr_2D = xp.stack([pr, pr]) + + rvs1 = xp.linspace(5, 105, 100) + rvs2 = xp.linspace(1, 100, 100) + rvs1_2D = xp.stack([rvs1, rvs2]) + rvs2_2D = xp.stack([rvs2, rvs1]) + + res = stats.ttest_ind(rvs1, rvs2, axis=0) + t, p = res # check that result object can be unpacked + xp_assert_close(t, tr) + xp_assert_close(p, pr) + + res = stats.ttest_ind_from_stats(*_desc_stats(rvs1, rvs2)) + t, p = res # check that result object can be unpacked + xp_assert_close(t, tr) + xp_assert_close(p, pr) + + res = stats.ttest_ind(rvs1_2D.T, rvs2_2D.T, axis=0) + xp_assert_close(res.statistic, tr_2D) + xp_assert_close(res.pvalue, pr_2D) + + res = stats.ttest_ind_from_stats(*_desc_stats(rvs1_2D.T, rvs2_2D.T)) + xp_assert_close(res.statistic, tr_2D) + xp_assert_close(res.pvalue, pr_2D) + + res = stats.ttest_ind(rvs1_2D, rvs2_2D, axis=1) + xp_assert_close(res.statistic, tr_2D) + xp_assert_close(res.pvalue, pr_2D) + + res = stats.ttest_ind_from_stats(*_desc_stats(rvs1_2D, rvs2_2D, axis=1)) + xp_assert_close(res.statistic, tr_2D) + xp_assert_close(res.pvalue, pr_2D) + + # test on 3 dimensions removed because generic tests in + # test_axis_nan_policy are much stronger + + # test alternative parameter + message = "`alternative` must be 'less', 'greater', or 'two-sided'." + with pytest.raises(ValueError, match=message): + stats.ttest_ind(rvs1, rvs2, alternative = "error") + + args = _desc_stats(rvs1_2D.T, rvs2_2D.T) + with pytest.raises(ValueError, match=message): + stats.ttest_ind_from_stats(*args, alternative = "error") + + t, p = stats.ttest_ind(rvs1, rvs2, alternative="less") + xp_assert_close(p, 1 - (pr/2)) + xp_assert_close(t, tr) + + t, p = stats.ttest_ind(rvs1, rvs2, alternative="greater") + xp_assert_close(p, pr/2) + xp_assert_close(t, tr) + + # Check that ttest_ind_from_stats agrees with ttest_ind + res1 = stats.ttest_ind(rvs1_2D.T, rvs2_2D.T, axis=0, alternative="less") + args = _desc_stats(rvs1_2D.T, rvs2_2D.T) + res2 = stats.ttest_ind_from_stats(*args, alternative="less") + xp_assert_close(res1.statistic, res2.statistic) + xp_assert_close(res1.pvalue, res2.pvalue) + + res1 = stats.ttest_ind(rvs1_2D.T, rvs2_2D.T, axis=0, alternative="less") + args = _desc_stats(rvs1_2D.T, rvs2_2D.T) + res2 = stats.ttest_ind_from_stats(*args, alternative="less") + xp_assert_close(res1.statistic, res2.statistic) + xp_assert_close(res1.pvalue, res2.pvalue) + + # test NaNs + NaN = xp.asarray(xp.nan) + rvs1 = xp.where(xp.arange(rvs1.shape[0]) == 0, NaN, rvs1) + + res = stats.ttest_ind(rvs1, rvs2, axis=0) + xp_assert_equal(res.statistic, NaN) + xp_assert_equal(res.pvalue, NaN) + + res = stats.ttest_ind_from_stats(*_desc_stats(rvs1, rvs2)) + xp_assert_equal(res.statistic, NaN) + xp_assert_equal(res.pvalue, NaN) + + @skip_xp_backends(eager_only=True, reason='lazy -> limited nan_policy support') + def test_special_cases(self, xp): + # check nan policy + rng = np.random.default_rng(22199381) + x = stats.norm.rvs(loc=5, scale=10, size=501, random_state=rng) + y = stats.norm.rvs(loc=5, scale=10, size=500, random_state=rng) + x[500] = np.nan + x, y = xp.asarray(x.tolist()), xp.asarray(y.tolist()) + + res = stats.ttest_ind(x, y) + xp_assert_equal(res.statistic, xp.asarray(xp.nan)) + xp_assert_equal(res.pvalue, xp.asarray(xp.nan)) + + # test zero division problem + with eager_warns(RuntimeWarning, match="Precision loss occurred", xp=xp): + t, p = stats.ttest_ind(xp.asarray([0, 0, 0]), xp.asarray([1, 1, 1])) + xp_assert_equal(t, xp.asarray(-xp.inf)) + xp_assert_equal(p, xp.asarray(0.)) + + x = xp.asarray([0, 0, 0]) + res = stats.ttest_ind(x, x) + xp_assert_equal(res.statistic, xp.asarray(xp.nan)) + xp_assert_equal(res.pvalue, xp.asarray(xp.nan)) + + # check that nan in input array result in nan output + anan = xp.asarray([[1, np.nan], [-1, 1]]) + res = stats.ttest_ind(anan, xp.zeros((2, 2))) + xp_assert_equal(res.statistic, xp.asarray([0, xp.nan])) + xp_assert_equal(res.pvalue, xp.asarray([1, xp.nan])) + + @skip_xp_backends(eager_only=True, reason='lazy -> limited nan_policy support') + def test_ttest_ind_nan_policy(self, xp): + rng = np.random.default_rng(61481957) + x = stats.norm.rvs(loc=5, scale=10, size=501, random_state=rng) + y = stats.norm.rvs(loc=5, scale=10, size=500, random_state=rng) + x[500] = np.nan + x, y = xp.asarray(x.tolist()), xp.asarray(y.tolist()) + + res = stats.ttest_ind(x, y, nan_policy='omit') + ref = stats.ttest_ind(x[~xp.isnan(x)], y, nan_policy='omit') + xp_assert_close(res.statistic, ref.statistic) + xp_assert_close(res.pvalue, ref.pvalue) + + message = "The input contains nan values" + with pytest.raises(ValueError, match=message): + stats.ttest_ind(x, y, nan_policy='raise') + + message = "nan_policy must be one of" + with pytest.raises(ValueError, match=message): + stats.ttest_ind(x, y, nan_policy='foobar') + + def test_3D_nan_policy_omit(self): # ND nan_policy='omit' is NumPy only + rvs1 = np.linspace(5, 105, 100) + rvs2 = np.linspace(1, 100, 100) + rvs1_2D = np.array([rvs1, rvs2]) + rvs2_2D = np.array([rvs2, rvs1]) + rvs1_3D = np.dstack([rvs1_2D, rvs1_2D, rvs1_2D]) + rvs2_3D = np.dstack([rvs2_2D, rvs2_2D, rvs2_2D]) + + rvs1_3D[:, :, 10:15] = np.nan + rvs2_3D[:, :, 6:12] = np.nan + + # Convert from two-sided p-values to one sided using T result data. + def convert(t, p, alt): + if (t < 0 and alt == "less") or (t > 0 and alt == "greater"): + return p / 2 + return 1 - (p / 2) + converter = np.vectorize(convert) + + tr, pr = stats.ttest_ind(rvs1_3D, rvs2_3D, axis=0, nan_policy='omit') + + t, p = stats.ttest_ind(rvs1_3D, rvs2_3D, axis=0, nan_policy='omit', + alternative='less') + assert_allclose(t, tr, rtol=1e-14) + assert_allclose(p, converter(tr, pr, 'less'), rtol=1e-14) + + t, p = stats.ttest_ind(rvs1_3D, rvs2_3D, axis=0, nan_policy='omit', + alternative='greater') + assert_allclose(t, tr, rtol=1e-14) + assert_allclose(p, converter(tr, pr, 'greater'), rtol=1e-14) + + + def test_ttest_ind_scalar(self): # ttest_ind for scalar input is legacy/np-only + # test scalars + with warnings.catch_warnings(), np.errstate(invalid="ignore"): + warnings.filterwarnings( + "ignore", "Degrees of freedom <= 0 for slice", RuntimeWarning) + t, p = stats.ttest_ind(4., 3.) + assert np.isnan(t) + assert np.isnan(p) + + +@make_xp_test_case(stats.ttest_ind) +@skip_xp_backends('dask.array', reason="Dask doesn't support ResamplingMethods") +@pytest.mark.filterwarnings("ignore:Arguments...:DeprecationWarning") +class TestTTestIndResampling: + N = 20 + + # data for most tests + rng = np.random.default_rng(169708062) + a = np.vstack((np.arange(3*N//4), rng.random(3*N//4))) + b = np.vstack((np.arange(N//4) + 100, rng.random(N//4))) + + # data for equal variance tests + a2 = np.arange(10) + b2 = np.arange(10) + 100 + + # data for exact test + a3 = [1, 2] + b3 = [3, 4] + + # data for bigger test + rvs1 = stats.norm.rvs(loc=5, scale=10, # type: ignore + size=500, random_state=rng).reshape(100, 5).T + rvs2 = stats.norm.rvs(loc=8, scale=20, size=100, random_state=rng) # type: ignore + + p_d = [1/1001, (676+1)/1001] # desired pvalues + p_d_gen = [1/1001, (672 + 1)/1001] # desired pvalues for Generator seed + p_d_big = [(993+1)/1001, (685+1)/1001, (840+1)/1001, + (955+1)/1001, (255+1)/1001] + + params = [ + (a, b, {"axis": 1}, p_d), # basic test + (a.T, b.T, {'axis': 0}, p_d), # along axis 0 + (a[0, :], b[0, :], {'axis': None}, p_d[0]), # 1d data + (a[0, :].tolist(), b[0, :].tolist(), {'axis': None}, p_d[0]), + # different seeds + (a, b, {'random_state': 0, "axis": 1}, p_d), + (a, b, {'random_state': np.random.RandomState(0), "axis": 1}, p_d), + (a2, b2, {'equal_var': True}, 1/1001), # equal variances + (rvs1, rvs2, {'axis': -1, 'random_state': 0}, p_d_big), # bigger test + (a3, b3, {}, 1/3), # exact test + (a, b, {'random_state': np.random.default_rng(0), "axis": 1}, p_d_gen), + ] + + @pytest.mark.parametrize("alternative", ['less', 'greater', 'two-sided']) + @pytest.mark.parametrize("shape", [(12,), (2, 12)]) + def test_permutation_method(self, alternative, shape, xp): + rng = np.random.default_rng(2348934579834565) + x = xp.asarray(rng.random(size=shape).tolist()) + y = xp.asarray(rng.random(size=13).tolist()) + + kwargs = dict(n_resamples=999) + + # Use ttest_ind with `method` + rng = np.random.default_rng(348934579834565) + method = stats.PermutationMethod(rng=rng, **kwargs) + res = stats.ttest_ind(x, y, axis=-1, alternative=alternative, method=method) + + # Use `permutation_test` directly + def statistic(x, y, axis): return stats.ttest_ind(x, y, axis=axis).statistic + rng = np.random.default_rng(348934579834565) + ref = stats.permutation_test((x, y), statistic, axis=-1, rng=rng, + alternative=alternative, **kwargs) + + xp_assert_close(res.statistic, ref.statistic) + xp_assert_close(res.pvalue, ref.pvalue) + + # Sanity check against theoretical t-test + ref = stats.ttest_ind(x, y, axis=-1, alternative=alternative) + xp_assert_close(res.statistic, ref.statistic) + xp_assert_close(res.pvalue, ref.pvalue, rtol=3e-2) + + @pytest.mark.parametrize("alternative", ['less', 'greater', 'two-sided']) + @pytest.mark.parametrize("shape", [(12,), (2, 12)]) + def test_monte_carlo_method(self, alternative, shape, xp): + rng = np.random.default_rng(2348934579834565) + x = xp.asarray(rng.random(size=shape).tolist()) + y = xp.asarray(rng.random(size=13).tolist()) + + kwargs = dict(n_resamples=999) + + # Use `monte_carlo` directly + def statistic(x, y, axis): + x, y = xp.asarray(x), xp.asarray(y) + return stats.ttest_ind(x, y, axis=axis).statistic + + rng = np.random.default_rng(348934579834565) + rvs = [rng.standard_normal, rng.standard_normal] + ref = stats.monte_carlo_test((x, y), rvs=rvs, statistic=statistic, axis=-1, + alternative=alternative, **kwargs) + + # Use ttest_ind with `method` + rng = np.random.default_rng(348934579834565) + rvs = [rng.standard_normal, rng.standard_normal] + method = stats.MonteCarloMethod(rvs=rvs, **kwargs) + res = stats.ttest_ind(x, y, axis=-1, alternative=alternative, method=method) + xp_assert_close(res.statistic, ref.statistic) + xp_assert_close(res.pvalue, ref.pvalue) + + # Passing `rng` instead of `rvs` + method = stats.MonteCarloMethod(rng=348934579834565, **kwargs) + res = stats.ttest_ind(x, y, axis=-1, alternative=alternative, method=method) + xp_assert_close(res.statistic, ref.statistic) + xp_assert_close(res.pvalue, ref.pvalue) + + # Sanity check against theoretical t-test + ref = stats.ttest_ind(x, y, axis=-1, alternative=alternative) + xp_assert_close(res.statistic, ref.statistic) + xp_assert_close(res.pvalue, ref.pvalue, rtol=6e-2) + + def test_resampling_input_validation(self, xp): + message = "`method` must be an instance of `PermutationMethod`, an instance..." + with pytest.raises(ValueError, match=message): + x, y = xp.asarray([1, 2, 3]), xp.asarray([4, 5, 6]) + stats.ttest_ind(x, y, method='migratory') + + +@make_xp_test_case(stats.ttest_ind) +class TestTTestIndCommon: + # for tests that are performed on variations of the t-test (e.g. trimmed) + @pytest.mark.parametrize("kwds", [{'trim': .2}, {}], + ids=["trim", "basic"]) + @pytest.mark.parametrize('equal_var', [True, False], + ids=['equal_var', 'unequal_var']) + def test_ttest_many_dims_fast(self, kwds, equal_var, xp): + # Test that ttest_ind works on many-dimensional arrays + rng = np.random.default_rng(3815288136) + a = xp.asarray(rng.random((5, 4, 4, 7, 1, 6)).tolist()) + b = xp.asarray(rng.random((4, 1, 8, 2, 6)).tolist()) + res = stats.ttest_ind(a, b, axis=-3, **kwds) + + # compare fully-vectorized t-test against t-test on smaller slice + i, j, k = 2, 3, 1 + a2 = a[i, :, j, :, 0, :] + b2 = b[:, 0, :, k, :] + res2 = stats.ttest_ind(a2, b2, axis=-2, **kwds) + xp_assert_close(res.statistic[i, :, j, k, :], res2.statistic) + xp_assert_close(res.pvalue[i, :, j, k, :], res2.pvalue) + + @pytest.mark.xslow + @pytest.mark.parametrize("kwds", [{'trim': .2}, {}], + ids=["trim", "basic"]) + @pytest.mark.parametrize('equal_var', [True, False], + ids=['equal_var', 'unequal_var']) + @skip_xp_backends('array_api_strict', reason="indexing in test is too fancy") + @skip_xp_backends('jax.numpy', reason="indexing in test is too fancy") + @skip_xp_backends('dask.array', reason="too slow") + def test_ttest_many_dims_full(self, kwds, equal_var, xp): + # compare against t-test on one axis-slice at a time + rng = np.random.default_rng(3815288136) + a = rng.random((5, 4, 4, 7, 1, 6)).tolist() + b = rng.random((4, 1, 8, 2, 6)).tolist() + res = stats.ttest_ind(xp.asarray(a), xp.asarray(b), axis=-3, **kwds) + + # manually broadcast with tile; move axis to end to simplify + x = np.moveaxis(np.tile(a, (1, 1, 1, 1, 2, 1)), -3, -1) + y = np.moveaxis(np.tile(b, (5, 1, 4, 1, 1, 1)), -3, -1) + x, y = xp.asarray(x.tolist()), xp.asarray(y.tolist()) + + shape = x.shape[:-1] + statistics = xp.zeros(shape) + pvalues = xp.zeros(shape) + for indices in product(*(range(i) for i in shape)): + xi = x[indices] # use tuple to index single axis slice + yi = y[indices] + res3 = stats.ttest_ind(xi, yi, axis=-1, **kwds) + statistics[indices] = res3.statistic + pvalues[indices] = res3.pvalue + + xp_assert_close(res.statistic, statistics) + xp_assert_close(res.pvalue, pvalues) + + @pytest.mark.parametrize("kwds", [{'trim': .2}, {}], + ids=["trim", "basic"]) + @pytest.mark.parametrize("axis", [-1, 0]) + def test_nans_on_axis(self, kwds, axis, xp): + # confirm that with `nan_policy='propagate'`, NaN results are returned + # on the correct location + rng = np.random.default_rng(363836384995579937222) + a = rng.integers(10, size=(5, 3, 10)).astype('float') + b = rng.integers(10, size=(5, 3, 10)).astype('float') + # set some indices in `a` and `b` to be `np.nan`. + a[0][2][3] = np.nan + b[2][0][6] = np.nan + a, b = xp.asarray(a.tolist()), xp.asarray(b.tolist()) + + # arbitrarily use `np.sum` as a baseline for which indices should be NaNs + expected = xp.isnan(xp.sum(a + b, axis=axis)) + + # multidimensional inputs to `t.sf(np.abs(t), df)` with NaNs on some + # indices throws an warning. See issue gh-13844 + with warnings.catch_warnings(), np.errstate(invalid="ignore"): + warnings.filterwarnings( + "ignore", "invalid value encountered in less_equal", RuntimeWarning) + warnings.filterwarnings("ignore", "Precision loss occurred", RuntimeWarning) + res = stats.ttest_ind(a, b, axis=axis, **kwds) + + p_nans = xp.isnan(res.pvalue) + xp_assert_equal(p_nans, expected) + statistic_nans = xp.isnan(res.statistic) + xp_assert_equal(statistic_nans, expected) + + +@make_xp_test_case(stats.ttest_ind) +class TestTTestTrimmed: + params = [ + [[1., 2., 3.], [1.1, 2.9, 4.2], 0.53619490753126731, -0.6864951273557258, .2], + [[56., 128.6, 12, 123.8, 64.34, 78, 763.3], [1.1, 2.9, 4.2], + 0.00998909252078421, 4.591598691181999, .2], + [[56., 128.6, 12., 123.8, 64.34, 78., 763.3], [1.1, 2.9, 4.2], + 0.10512380092302633, 2.832256715395378, .32], + [[2.7, 2.7, 1.1, 3.0, 1.9, 3.0, 3.8, 3.8, 0.3, 1.9, 1.9], + [6.5, 5.4, 8.1, 3.5, 0.5, 3.8, 6.8, 4.9, 9.5, 6.2, 4.1], + 0.002878909511344, -4.2461168970325, .2], + [[-0.84504783, 0.13366078, 3.53601757, -0.62908581, 0.54119466, + -1.16511574, -0.08836614, 1.18495416, 2.48028757, -1.58925028, + -1.6706357, 0.3090472, -2.12258305, 0.3697304, -1.0415207, + -0.57783497, -0.90997008, 1.09850192, 0.41270579, -1.4927376], + [1.2725522, 1.1657899, 2.7509041, 1.2389013, -0.9490494, -1.0752459, + 1.1038576, 2.9912821, 3.5349111, 0.4171922, 1.0168959, -0.7625041, + -0.4300008, 3.0431921, 1.6035947, 0.5285634, -0.7649405, 1.5575896, + 1.3670797, 1.1726023], 0.005293305834235, -3.0983317739483, .2]] + + @pytest.mark.parametrize("a, b, pr, tr, trim", params) + @pytest.mark.parametrize("dtype", [None, 'float32', 'float64']) + def test_ttest_compare_r(self, a, b, pr, tr, trim, dtype, xp): + ''' + Using PairedData's yuen.t.test method. Something to note is that there + are at least 3 R packages that come with a trimmed t-test method, and + comparisons were made between them. It was found that PairedData's + method's results match this method, SAS, and one of the other R + methods. A notable discrepancy was the DescTools implementation of the + function, which only sometimes agreed with SAS, WRS2, PairedData and + this implementation. For this reason, most comparisons in R are made + against PairedData's method. + + Rather than providing the input and output for all evaluations, here is + a representative example: + > library(PairedData) + > a <- c(1, 2, 3) + > b <- c(1.1, 2.9, 4.2) + > options(digits=16) + > yuen.t.test(a, b, tr=.2) + + Two-sample Yuen test, trim=0.2 + + data: x and y + t = -0.68649512735573, df = 3.4104431643464, p-value = 0.5361949075313 + alternative hypothesis: true difference in trimmed means is not equal + to 0 + 95 percent confidence interval: + -3.912777195645217 2.446110528978550 + sample estimates: + trimmed mean of x trimmed mean of y + 2.000000000000000 2.73333333333333 + ''' + dtype = dtype if dtype is None else getattr(xp, dtype) + a, b = xp.asarray(a, dtype=dtype), xp.asarray(b, dtype=dtype) + statistic, pvalue = stats.ttest_ind(a, b, trim=trim, equal_var=False) + xp_assert_close(statistic, xp.asarray(tr, dtype=dtype)) + xp_assert_close(pvalue, xp.asarray(pr, dtype=dtype)) + + def test_compare_SAS(self, xp): + # Source of the data used in this test: + # https://support.sas.com/resources/papers/proceedings14/1660-2014.pdf + a = [12, 14, 18, 25, 32, 44, 12, 14, 18, 25, 32, 44] + b = [17, 22, 14, 12, 30, 29, 19, 17, 22, 14, 12, 30, 29, 19] + a, b = xp.asarray(a), xp.asarray(b) + # In this paper, a trimming percentage of 5% is used. However, + # in their implementation, the number of values trimmed is rounded to + # the nearest whole number. However, consistent with + # `scipy.stats.trimmed_mean`, this test truncates to the lower + # whole number. In this example, the paper notes that 1 value is + # trimmed off of each side. 9% replicates this amount of trimming. + statistic, pvalue = stats.ttest_ind(a, b, trim=.09, equal_var=False) + xp_assert_close(pvalue, xp.asarray(0.514522), atol=1e-6) + xp_assert_close(statistic, xp.asarray(0.669169), atol=1e-6) + + def test_equal_var(self, xp): + ''' + The PairedData library only supports unequal variances. To compare + samples with equal variances, the multicon library is used. + > library(multicon) + > a <- c(2.7, 2.7, 1.1, 3.0, 1.9, 3.0, 3.8, 3.8, 0.3, 1.9, 1.9) + > b <- c(6.5, 5.4, 8.1, 3.5, 0.5, 3.8, 6.8, 4.9, 9.5, 6.2, 4.1) + > dv = c(a,b) + > iv = c(rep('a', length(a)), rep('b', length(b))) + > yuenContrast(dv~ iv, EQVAR = TRUE) + $Ms + N M wgt + a 11 2.442857142857143 1 + b 11 5.385714285714286 -1 + + $test + stat df crit p + results -4.246116897032513 12 2.178812829667228 0.00113508833897713 + ''' + a = [2.7, 2.7, 1.1, 3.0, 1.9, 3.0, 3.8, 3.8, 0.3, 1.9, 1.9] + b = [6.5, 5.4, 8.1, 3.5, 0.5, 3.8, 6.8, 4.9, 9.5, 6.2, 4.1] + a, b = xp.asarray(a), xp.asarray(b) + # `equal_var=True` is default + statistic, pvalue = stats.ttest_ind(a, b, trim=.2) + xp_assert_close(pvalue, xp.asarray(0.00113508833897713)) + xp_assert_close(statistic, xp.asarray(-4.246116897032513)) + + @pytest.mark.parametrize('alt,pr,tr', + (('greater', 0.9985605452443, -4.2461168970325), + ('less', 0.001439454755672, -4.2461168970325),), + ) + def test_alternatives(self, alt, pr, tr, xp): + ''' + > library(PairedData) + > a <- c(2.7,2.7,1.1,3.0,1.9,3.0,3.8,3.8,0.3,1.9,1.9) + > b <- c(6.5,5.4,8.1,3.5,0.5,3.8,6.8,4.9,9.5,6.2,4.1) + > options(digits=16) + > yuen.t.test(a, b, alternative = 'greater') + ''' + a = [2.7, 2.7, 1.1, 3.0, 1.9, 3.0, 3.8, 3.8, 0.3, 1.9, 1.9] + b = [6.5, 5.4, 8.1, 3.5, 0.5, 3.8, 6.8, 4.9, 9.5, 6.2, 4.1] + a, b = xp.asarray(a), xp.asarray(b) + statistic, pvalue = stats.ttest_ind(a, b, trim=.2, equal_var=False, + alternative=alt) + xp_assert_close(pvalue, xp.asarray(pr)) + xp_assert_close(statistic, xp.asarray(tr)) + + @pytest.mark.parametrize("trim", [-.2, .5, 1]) + def test_trim_bounds_error(self, trim, xp): + match = "Trimming percentage should be 0 <= `trim` < .5." + with assert_raises(ValueError, match=match): + stats.ttest_ind(xp.asarray([1, 2]), xp.asarray([2, 1]), trim=trim) + + +@make_xp_test_case(stats.ttest_ind) +class TestTTestCI: + # indices in order [alternative={two-sided, less, greater}, + # equal_var={False, True}, trim={0, 0.2}] + # reference values in order `statistic, df, pvalue, low, high` + # equal_var=False reference values computed with R PairedData yuen.t.test: + # + # library(PairedData) + # options(digits=16) + # a < - c(0.88236329, 0.97318744, 0.4549262, 0.97893335, 0.0606677, + # 0.44013366, 0.55806018, 0.40151434, 0.14453315, 0.25860601, + # 0.20202162) + # b < - c(0.93455277, 0.42680603, 0.49751939, 0.14152846, 0.711435, + # 0.77669667, 0.20507578, 0.78702772, 0.94691855, 0.32464958, + # 0.3873582, 0.35187468, 0.21731811) + # yuen.t.test(a, b, tr=0, conf.level = 0.9, alternative = 'l') + # + # equal_var=True reference values computed with R multicon yuenContrast: + # + # library(multicon) + # options(digits=16) + # a < - c(0.88236329, 0.97318744, 0.4549262, 0.97893335, 0.0606677, + # 0.44013366, 0.55806018, 0.40151434, 0.14453315, 0.25860601, + # 0.20202162) + # b < - c(0.93455277, 0.42680603, 0.49751939, 0.14152846, 0.711435, + # 0.77669667, 0.20507578, 0.78702772, 0.94691855, 0.32464958, + # 0.3873582, 0.35187468, 0.21731811) + # dv = c(a, b) + # iv = c(rep('a', length(a)), rep('b', length(b))) + # yuenContrast(dv~iv, EQVAR = FALSE, alternative = 'unequal', tr = 0.2) + r = np.empty(shape=(3, 2, 2, 5)) + r[0, 0, 0] = [-0.2314607, 19.894435, 0.8193209, -0.247220294, 0.188729943] + r[1, 0, 0] = [-0.2314607, 19.894435, 0.40966045, -np.inf, 0.1382426469] + r[2, 0, 0] = [-0.2314607, 19.894435, 0.5903395, -0.1967329982, np.inf] + r[0, 0, 1] = [-0.2452886, 11.427896, 0.8105823, -0.34057446, 0.25847383] + r[1, 0, 1] = [-0.2452886, 11.427896, 0.40529115, -np.inf, 0.1865829074] + r[2, 0, 1] = [-0.2452886, 11.427896, 0.5947089, -0.268683541, np.inf] + # confidence interval not available for equal_var=True + r[0, 1, 0] = [-0.2345625322555006, 22, 0.8167175905643815, np.nan, np.nan] + r[1, 1, 0] = [-0.2345625322555006, 22, 0.4083587952821908, np.nan, np.nan] + r[2, 1, 0] = [-0.2345625322555006, 22, 0.5916412047178092, np.nan, np.nan] + r[0, 1, 1] = [-0.2505369406507428, 14, 0.8058115135702835, np.nan, np.nan] + r[1, 1, 1] = [-0.2505369406507428, 14, 0.4029057567851417, np.nan, np.nan] + r[2, 1, 1] = [-0.2505369406507428, 14, 0.5970942432148583, np.nan, np.nan] + @pytest.mark.parametrize('alternative', ['two-sided', 'less', 'greater']) + @pytest.mark.parametrize('equal_var', [False, True]) + @pytest.mark.parametrize('trim', [0, 0.2]) + @skip_xp_backends('jax.numpy', reason='Generic stdtrit mutates array.') + def test_confidence_interval(self, alternative, equal_var, trim, xp): + if equal_var and trim: + pytest.xfail('Discrepancy in `main`; needs further investigation.') + + if trim and not is_numpy(xp): + pytest.skip('`trim` is only compatible with NumPy input') + + rng = np.random.default_rng(3810954496107292580) + x = xp.asarray(rng.random(11)) + y = xp.asarray(rng.random(13)) + + res = stats.ttest_ind(x, y, alternative=alternative, + equal_var=equal_var, trim=trim) + + alternatives = {'two-sided': 0, 'less': 1, 'greater': 2} + ref = self.r[alternatives[alternative], int(equal_var), int(np.ceil(trim))] + statistic, df, pvalue, low, high = ref + + rtol = 1e-7 # only 7 digits in reference + xp_assert_close(res.statistic, xp.asarray(statistic), rtol=rtol) + xp_assert_close(res.df, xp.asarray(df), rtol=rtol) + xp_assert_close(res.pvalue, xp.asarray(pvalue), rtol=rtol) + + if not equal_var: # CI not available when `equal_var is True` + ci = res.confidence_interval(0.9) + xp_assert_close(ci.low, xp.asarray(low), rtol=rtol) + xp_assert_close(ci.high, xp.asarray(high), rtol=rtol) + + +@make_xp_test_case(stats.ttest_ind) +class TestTTestIndMore: + @make_xp_test_case(stats.ttest_ind_from_stats) + def test_ttest_ind_with_uneq_var(self, xp): + # check vs. R `t.test`, e.g. + # options(digits=20) + # a = c(1., 2., 3.) + # b = c(1.1, 2.9, 4.2) + # t.test(a, b, equal.var=FALSE) + + a = xp.asarray([1., 2., 3.]) + b = xp.asarray([1.1, 2.9, 4.2]) + pr = xp.asarray(0.53619490753126686) + tr = xp.asarray(-0.686495127355726265) + + t, p = stats.ttest_ind(a, b, equal_var=False) + xp_assert_close(t, tr) + xp_assert_close(p, pr) + + t, p = stats.ttest_ind_from_stats(*_desc_stats(a, b), equal_var=False) + xp_assert_close(t, tr) + xp_assert_close(p, pr) + + a = xp.asarray([1., 2., 3., 4.]) + pr = xp.asarray(0.84354139131608252) + tr = xp.asarray(-0.210866331595072315) + + t, p = stats.ttest_ind(a, b, equal_var=False) + xp_assert_close(t, tr) + xp_assert_close(p, pr) + + t, p = stats.ttest_ind_from_stats(*_desc_stats(a, b), equal_var=False) + xp_assert_close(t, tr) + xp_assert_close(p, pr) + + # regression test + tr = xp.asarray(1.0912746897927283) + tr_uneq_n = xp.asarray(0.66745638708050492) + pr = xp.asarray(0.27647831993021388) + pr_uneq_n = xp.asarray(0.50873585065616544) + tr_2D = xp.stack([tr, -tr]) + pr_2D = xp.stack([pr, pr]) + + rvs3 = xp.linspace(1, 100, 25) + rvs2 = xp.linspace(1, 100, 100) + rvs1 = xp.linspace(5, 105, 100) + rvs1_2D = xp.stack([rvs1, rvs2]) + rvs2_2D = xp.stack([rvs2, rvs1]) + + t, p = stats.ttest_ind(rvs1, rvs2, axis=0, equal_var=False) + xp_assert_close(t, tr) + xp_assert_close(p, pr) + + t, p = stats.ttest_ind_from_stats(*_desc_stats(rvs1, rvs2), equal_var=False) + xp_assert_close(t, tr) + xp_assert_close(p, pr) + + t, p = stats.ttest_ind(rvs1, rvs3, axis=0, equal_var=False) + xp_assert_close(t, tr_uneq_n) + xp_assert_close(p, pr_uneq_n) + + t, p = stats.ttest_ind_from_stats(*_desc_stats(rvs1, rvs3), equal_var=False) + xp_assert_close(t, tr_uneq_n) + xp_assert_close(p, pr_uneq_n) + + res = stats.ttest_ind(rvs1_2D.T, rvs2_2D.T, axis=0, equal_var=False) + xp_assert_close(res.statistic, tr_2D) + xp_assert_close(res.pvalue, pr_2D) + + args = _desc_stats(rvs1_2D.T, rvs2_2D.T) + res = stats.ttest_ind_from_stats(*args, equal_var=False) + xp_assert_close(res.statistic, tr_2D) + xp_assert_close(res.pvalue, pr_2D) + + res = stats.ttest_ind(rvs1_2D, rvs2_2D, axis=1, equal_var=False) + xp_assert_close(res.statistic, tr_2D) + xp_assert_close(res.pvalue, pr_2D) + + args = _desc_stats(rvs1_2D, rvs2_2D, axis=1) + res = stats.ttest_ind_from_stats(*args, equal_var=False) + xp_assert_close(res.statistic, tr_2D) + xp_assert_close(res.pvalue, pr_2D) + + @pytest.mark.filterwarnings( + "ignore:divide by zero encountered:RuntimeWarning" + ) # for dask + @pytest.mark.filterwarnings( + "ignore:invalid value encountered:RuntimeWarning" + ) # for dask + def test_ttest_ind_zero_division(self, xp): + # test zero division problem + x = xp.zeros(3) + y = xp.ones(3) + + with eager_warns(RuntimeWarning, match="Precision loss occurred", xp=xp): + t, p = stats.ttest_ind(x, y, equal_var=False) + + xp_assert_equal(t, xp.asarray(-xp.inf)) + xp_assert_equal(p, xp.asarray(0.)) + + with np.errstate(all='ignore'): + t, p = stats.ttest_ind(x, x, equal_var=False) + xp_assert_equal(t, xp.asarray(xp.nan)) + xp_assert_equal(p, xp.asarray(xp.nan)) + + # check that nan in input array result in nan output + anan = xp.asarray([[1, xp.nan], [-1, 1]]) + t, p = stats.ttest_ind(anan, xp.zeros((2, 2)), equal_var=False) + xp_assert_equal(t, xp.asarray([0., np.nan])) + xp_assert_equal(p, xp.asarray([1., np.nan])) + + def test_ttest_ind_nan_2nd_arg(self): + # regression test for gh-6134: nans in the second arg were not handled + x = [np.nan, 2.0, 3.0, 4.0] + y = [1.0, 2.0, 1.0, 2.0] + + r1 = stats.ttest_ind(x, y, nan_policy='omit') + r2 = stats.ttest_ind(y, x, nan_policy='omit') + assert_allclose(r2.statistic, -r1.statistic, atol=1e-15) + assert_allclose(r2.pvalue, r1.pvalue, atol=1e-15) + + # NB: arguments are not paired when NaNs are dropped + r3 = stats.ttest_ind(y, x[1:]) + assert_allclose(r2, r3, atol=1e-15) + + # .. and this is consistent with R. R code: + # x = c(NA, 2.0, 3.0, 4.0) + # y = c(1.0, 2.0, 1.0, 2.0) + # t.test(x, y, var.equal=TRUE) + assert_allclose(r2, (-2.5354627641855498, 0.052181400457057901), + atol=1e-15) + + def test_ttest_ind_empty_1d_returns_nan(self, xp): + # Two empty inputs should return a TtestResult containing nan + # for both values. + with eager_warns(SmallSampleWarning, match=too_small_1d_not_omit, xp=xp): + res = stats.ttest_ind(xp.asarray([]), xp.asarray([])) + assert isinstance(res, stats._stats_py.TtestResult) + NaN = xp.asarray(xp.nan)[()] + xp_assert_equal(res.statistic, NaN) + xp_assert_equal(res.pvalue, NaN) + + @pytest.mark.parametrize('b, expected_shape', + [(np.empty((1, 5, 0)), (3, 5)), + (np.empty((1, 0, 0)), (3, 0))]) + def test_ttest_ind_axis_size_zero(self, b, expected_shape, xp): + # In this test, the length of the axis dimension is zero. + # The results should be arrays containing nan with shape + # given by the broadcast nonaxis dimensions. + a = xp.empty((3, 1, 0)) + b = xp.asarray(b, dtype=a.dtype) + with warnings.catch_warnings(): + # first case should warn, second shouldn't? + warnings.filterwarnings("ignore", too_small_nd_not_omit, SmallSampleWarning) + res = stats.ttest_ind(a, b, axis=-1) + assert isinstance(res, stats._stats_py.TtestResult) + expected_value = xp.full(expected_shape, fill_value=xp.nan) + xp_assert_equal(res.statistic, expected_value) + xp_assert_equal(res.pvalue, expected_value) + + def test_ttest_ind_nonaxis_size_zero(self, xp): + # In this test, the length of the axis dimension is nonzero, + # but one of the nonaxis dimensions has length 0. Check that + # we still get the correctly broadcast shape, which is (5, 0) + # in this case. + a = xp.empty((1, 8, 0)) + b = xp.empty((5, 8, 1)) + res = stats.ttest_ind(a, b, axis=1) + assert isinstance(res, stats._stats_py.TtestResult) + assert res.statistic.shape ==(5, 0) + assert res.pvalue.shape == (5, 0) + + def test_ttest_ind_nonaxis_size_zero_different_lengths(self, xp): + # In this test, the length of the axis dimension is nonzero, + # and that size is different in the two inputs, + # and one of the nonaxis dimensions has length 0. Check that + # we still get the correctly broadcast shape, which is (5, 0) + # in this case. + a = xp.empty((1, 7, 0)) + b = xp.empty((5, 8, 1)) + res = stats.ttest_ind(a, b, axis=1) + assert isinstance(res, stats._stats_py.TtestResult) + assert res.statistic.shape ==(5, 0) + assert res.pvalue.shape == (5, 0) + + @pytest.mark.skip_xp_backends(cpu_only=True, reason='Test uses ks_1samp') + @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") + @pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning:dask") + @pytest.mark.uses_xp_capabilities(False, reason="not used in this test yet") + def test_ttest_uniform_pvalues(self, xp): + # test that p-values are uniformly distributed under the null hypothesis + rng = np.random.default_rng(246834602926842) + x = xp.asarray(rng.normal(size=(10000, 2))) + y = xp.asarray(rng.normal(size=(10000, 1))) + q = rng.uniform(size=100) + + res = stats.ttest_ind(x, y, equal_var=True, axis=-1) + pvalue = np.asarray(res.pvalue) + # TODO: isolate use of alt backend to ttest_ind + assert stats.ks_1samp(pvalue, stats.uniform().cdf).pvalue > 0.1 + assert_allclose(np.quantile(pvalue, q), q, atol=1e-2) + + res = stats.ttest_ind(y, x, equal_var=True, axis=-1) + pvalue = np.asarray(res.pvalue) + assert stats.ks_1samp(pvalue, stats.uniform().cdf).pvalue > 0.1 + assert_allclose(np.quantile(pvalue, q), q, atol=1e-2) + + # reference values from R: + # options(digits=16) + # t.test(c(2, 3, 5), c(1.5), var.equal=TRUE) + x, y = xp.asarray([2., 3., 5.]), xp.asarray([1.5]) + + res = stats.ttest_ind(x, y, equal_var=True) + xp_assert_close(res.statistic, xp.asarray(1.0394023007754)) + xp_assert_close(res.pvalue, xp.asarray(0.407779907736)) + + +@make_xp_test_case(stats.ttest_ind_from_stats) +class TestTTestIndFromStats: + def test_gh5686(self, xp): + mean1, mean2 = xp.asarray([1, 2]), xp.asarray([3, 4]) + std1, std2 = xp.asarray([5, 3]), xp.asarray([4, 5]) + nobs1, nobs2 = xp.asarray([130, 140]), xp.asarray([100, 150]) + # This will raise a TypeError unless gh-5686 is fixed. + stats.ttest_ind_from_stats(mean1, std1, nobs1, mean2, std2, nobs2) + + @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning") + def test_ttest_ind_from_stats_inputs_zero(self, xp): + # Regression test for gh-6409. + zero = xp.asarray(0.) + six = xp.asarray(6.) + NaN = xp.asarray(xp.nan) + res = stats.ttest_ind_from_stats(zero, zero, six, zero, zero, six, + equal_var=False) + xp_assert_equal(res.statistic, NaN) + xp_assert_equal(res.pvalue, NaN) + + +@make_xp_test_case(stats.ttest_1samp) +class TestTTest1Samp: + def _convert_pvalue_alternative(self, t, p, alt, xp): + # test alternative parameter + # Convert from two-sided p-values to one sided using T result data. + less = xp.asarray(alt == "less") + greater = xp.asarray(alt == "greater") + i = ((t < 0) & less) | ((t > 0) & greater) + return xp.where(i, p/2, 1 - p/2) + + @pytest.mark.slow + @pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning:dask") + @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") + def test_ttest_1samp_new(self, xp): + rng = np.random.default_rng(88123290) + n1, n2, n3 = (10, 15, 20) + rvn1 = stats.norm.rvs(loc=5, scale=10, size=(n1, n2, n3), random_state=rng) + rvn1 = xp.asarray(rvn1) + + # check multidimensional array and correct axis handling + # deterministic rvn1 and rvn2 would be better as in test_ttest_rel + popmean = xp.ones((1, n2, n3)) + t1, p1 = stats.ttest_1samp(rvn1, popmean, axis=0) + t2, p2 = stats.ttest_1samp(rvn1, 1., axis=0) + t3, p3 = stats.ttest_1samp(rvn1[:, 0, 0], 1.) + xp_assert_close(t1, t2, rtol=1e-14) + xp_assert_close(t1[0, 0], t3, rtol=1e-14) + assert_equal(t1.shape, (n2, n3)) + + popmean = xp.ones((n1, 1, n3)) + t1, p1 = stats.ttest_1samp(rvn1, popmean, axis=1) + t2, p2 = stats.ttest_1samp(rvn1, 1., axis=1) + t3, p3 = stats.ttest_1samp(rvn1[0, :, 0], 1.) + xp_assert_close(t1, t2, rtol=1e-14) + xp_assert_close(t1[0, 0], t3, rtol=1e-14) + assert_equal(t1.shape, (n1, n3)) + + popmean = xp.ones((n1, n2, 1)) + t1, p1 = stats.ttest_1samp(rvn1, popmean, axis=2) + t2, p2 = stats.ttest_1samp(rvn1, 1., axis=2) + t3, p3 = stats.ttest_1samp(rvn1[0, 0, :], 1.) + xp_assert_close(t1, t2, rtol=1e-14) + xp_assert_close(t1[0, 0], t3, rtol=1e-14) + assert_equal(t1.shape, (n1, n2)) + + # test zero division problem + t, p = stats.ttest_1samp(xp.asarray([0., 0., 0.]), 1.) + xp_assert_equal(xp.abs(t), xp.asarray(xp.inf)) + xp_assert_equal(p, xp.asarray(0.)) + + tr, pr = stats.ttest_1samp(rvn1[:, :, :], 1.) + + t, p = stats.ttest_1samp(rvn1[:, :, :], 1., alternative="greater") + pc = self._convert_pvalue_alternative(tr, pr, "greater", xp) + xp_assert_close(p, pc) + xp_assert_close(t, tr) + + t, p = stats.ttest_1samp(rvn1[:, :, :], 1., alternative="less") + pc = self._convert_pvalue_alternative(tr, pr, "less", xp) + xp_assert_close(p, pc) + xp_assert_close(t, tr) + + with np.errstate(all='ignore'): + res = stats.ttest_1samp(xp.asarray([0., 0., 0.]), 0.) + xp_assert_equal(res.statistic, xp.asarray(xp.nan)) + xp_assert_equal(res.pvalue, xp.asarray(xp.nan)) + + # check that nan in input array result in nan output + anan = xp.asarray([[1., np.nan], [-1., 1.]]) + res = stats.ttest_1samp(anan, 0.) + xp_assert_equal(res.statistic, xp.asarray([0., xp.nan])) + xp_assert_equal(res.pvalue, xp.asarray([1., xp.nan])) + + @skip_xp_backends(eager_only=True, reason="lazy -> reduced nan_policy capabilities") + def test_ttest_1samp_new_omit(self, xp): + rng = np.random.default_rng(4008400329) + n1, n2, n3 = (5, 10, 15) + rvn1 = stats.norm.rvs(loc=5, scale=10, size=(n1, n2, n3), random_state=rng) + rvn1 = xp.asarray(rvn1) + + rvn1[0:2, 1:3, 4:8] = xp.nan + + tr, pr = stats.ttest_1samp(rvn1[:, :, :], 1., nan_policy='omit') + + t, p = stats.ttest_1samp(rvn1[:, :, :], 1., nan_policy='omit', + alternative="greater") + pc = self._convert_pvalue_alternative(tr, pr, "greater", xp) + xp_assert_close(p, pc) + xp_assert_close(t, tr) + + t, p = stats.ttest_1samp(rvn1[:, :, :], 1., nan_policy='omit', + alternative="less") + pc = self._convert_pvalue_alternative(tr, pr, "less", xp) + xp_assert_close(p, pc) + xp_assert_close(t, tr) + + @pytest.mark.skip_xp_backends('jax.numpy', reason='Generic stdtrit mutates array.') + def test_ttest_1samp_popmean_array(self, xp): + # when popmean.shape[axis] != 1, raise an error + # if the user wants to test multiple null hypotheses simultaneously, + # use standard broadcasting rules + rng = np.random.default_rng(2913300596553337193) + x = rng.random(size=(1, 15, 20)) + x = xp.asarray(x) + + message = r"`popmean.shape\[axis\]` must equal 1." + popmean = xp.asarray(rng.random(size=(5, 2, 20))) + with pytest.raises(ValueError, match=message): + stats.ttest_1samp(x, popmean=popmean, axis=-2) + + popmean = xp.asarray(rng.random(size=(5, 1, 20))) + res = stats.ttest_1samp(x, popmean=popmean, axis=-2) + assert res.statistic.shape == (5, 20) + + l, u = res.confidence_interval() + l = xp.expand_dims(l, axis=-2) + u = xp.expand_dims(u, axis=-2) + + res = stats.ttest_1samp(x, popmean=l, axis=-2) + ref = xp.broadcast_to(xp.asarray(0.05, dtype=xp.float64), res.pvalue.shape) + xp_assert_close(res.pvalue, ref) + + res = stats.ttest_1samp(x, popmean=u, axis=-2) + xp_assert_close(res.pvalue, ref) + + +@make_xp_test_case(stats.describe) +class TestDescribe: + @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") + @pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning:dask") + def test_describe_scalar(self, xp): + with warnings.catch_warnings(), \ + np.errstate(invalid="ignore", divide="ignore"): + warnings.filterwarnings( + "ignore", "Degrees of freedom <= 0 for slice", RuntimeWarning) + n, mm, m, v, sk, kurt = stats.describe(xp.asarray(4.)[()]) + assert n == 1 + xp_assert_equal(mm[0], xp.asarray(4.0)) + xp_assert_equal(mm[1], xp.asarray(4.0)) + xp_assert_equal(m, xp.asarray(4.0)) + xp_assert_equal(v ,xp.asarray(xp.nan)) + xp_assert_equal(sk, xp.asarray(xp.nan)) + xp_assert_equal(kurt, xp.asarray(xp.nan)) + + def test_describe_numbers(self, xp): + x = xp.concat((xp.ones((3, 4)), xp.full((2, 4), 2.))) + nc = 5 + mmc = (xp.asarray([1., 1., 1., 1.]), xp.asarray([2., 2., 2., 2.])) + mc = xp.asarray([1.4, 1.4, 1.4, 1.4]) + vc = xp.asarray([0.3, 0.3, 0.3, 0.3]) + skc = xp.asarray([0.40824829046386357] * 4) + kurtc = xp.asarray([-1.833333333333333] * 4) + n, mm, m, v, sk, kurt = stats.describe(x) + assert n == nc + xp_assert_equal(mm[0], mmc[0]) + xp_assert_equal(mm[1], mmc[1]) + xp_assert_close(m, mc, rtol=4 * xp.finfo(m.dtype).eps) + xp_assert_close(v, vc, rtol=4 * xp.finfo(m.dtype).eps) + xp_assert_close(sk, skc) + xp_assert_close(kurt, kurtc) + + n, mm, m, v, sk, kurt = stats.describe(x.T, axis=1) + assert n == nc + xp_assert_equal(mm[0], mmc[0]) + xp_assert_equal(mm[1], mmc[1]) + xp_assert_close(m, mc, rtol=4 * xp.finfo(m.dtype).eps) + xp_assert_close(v, vc, rtol=4 * xp.finfo(m.dtype).eps) + xp_assert_close(sk, skc) + xp_assert_close(kurt, kurtc) + + def describe_nan_policy_omit_test(self): + x = np.arange(10.) + x[9] = np.nan + + nc, mmc = (9, (0.0, 8.0)) + mc = 4.0 + vc = 7.5 + skc = 0.0 + kurtc = -1.2300000000000002 + n, mm, m, v, sk, kurt = stats.describe(x, nan_policy='omit') + assert_equal(n, nc) + assert_equal(mm, mmc) + assert_equal(m, mc) + assert_equal(v, vc) + assert_array_almost_equal(sk, skc) + assert_array_almost_equal(kurt, kurtc, decimal=13) + + def test_describe_nan_policy_other(self, xp): + x = xp.arange(10.) + x = xp.where(x==9, xp.nan, x) + + if is_lazy_array(x): + with pytest.raises(TypeError, match='not supported for lazy arrays'): + stats.describe(x, nan_policy='raise') + else: + with pytest.raises(ValueError, match='The input contains nan values'): + stats.describe(x, nan_policy='raise') + + n, mm, m, v, sk, kurt = stats.describe(x, nan_policy='propagate') + ref = xp.asarray(xp.nan)[()] + assert n == 10 + xp_assert_equal(mm[0], ref) + xp_assert_equal(mm[1], ref) + xp_assert_equal(m, ref) + xp_assert_equal(v, ref) + xp_assert_equal(sk, ref) + xp_assert_equal(kurt, ref) + + if is_numpy(xp): + self.describe_nan_policy_omit_test() + elif is_lazy_array(x): + with pytest.raises(TypeError, match='not supported for lazy arrays'): + stats.describe(x, nan_policy='omit') + + message = 'nan_policy must be one of...' + with pytest.raises(ValueError, match=message): + stats.describe(x, nan_policy='foobar') + + def test_describe_result_attributes(self): + # some result attributes are tuples, which aren't meant to be compared + # with `xp_assert_close` + actual = stats.describe(np.arange(5.)) + attributes = ('nobs', 'minmax', 'mean', 'variance', 'skewness', 'kurtosis') + check_named_results(actual, attributes) + + def test_describe_ddof(self, xp): + x = xp.concat((xp.ones((3, 4)), xp.full((2, 4), 2.))) + nc = 5 + mmc = (xp.asarray([1., 1., 1., 1.]), xp.asarray([2., 2., 2., 2.])) + mc = xp.asarray([1.4, 1.4, 1.4, 1.4]) + vc = xp.asarray([0.24, 0.24, 0.24, 0.24]) + skc = xp.asarray([0.40824829046386357] * 4) + kurtc = xp.asarray([-1.833333333333333] * 4) + n, mm, m, v, sk, kurt = stats.describe(x, ddof=0) + assert n == nc + xp_assert_equal(mm[0], mmc[0]) + xp_assert_equal(mm[1], mmc[1]) + xp_assert_close(m, mc) + xp_assert_close(v, vc) + xp_assert_close(sk, skc) + xp_assert_close(kurt, kurtc) + + def test_describe_axis_none(self, xp): + x = xp.concat((xp.ones((3, 4)), xp.full((2, 4), 2.))) + + # expected values + nc = 20 + mmc = (xp.asarray(1.0), xp.asarray(2.0)) + mc = xp.asarray(1.3999999999999999) + vc = xp.asarray(0.25263157894736848) + skc = xp.asarray(0.4082482904638634) + kurtc = xp.asarray(-1.8333333333333333) + + # actual values + n, mm, m, v, sk, kurt = stats.describe(x, axis=None) + + assert n == nc + xp_assert_equal(mm[0], mmc[0]) + xp_assert_equal(mm[1], mmc[1]) + xp_assert_close(m, mc) + xp_assert_close(v, vc) + xp_assert_close(sk, skc) + xp_assert_close(kurt, kurtc) + + def test_describe_empty(self, xp): + message = "The input must not be empty." + with pytest.raises(ValueError, match=message): + stats.describe(xp.asarray([])) + + @skip_xp_backends("array_api_strict", reason="min/max don't accept boolean input") + def test_boolean_input_gh24847(self, xp): + # gh-24847 reported that `describe` failed with boolean input + rng = np.random.default_rng(3248923598734583) + n = 20 + x = xp.asarray(rng.random(n) < 0.5) + res = stats.describe(x) + xp_assert_equal(res.nobs, xp.asarray(n, dtype=xp.int64)) + xp_assert_equal(res.minmax[0], xp.min(x)) + xp_assert_equal(res.minmax[1], xp.max(x)) + x = xp_promote(x, force_floating=True, xp=xp) + xp_assert_close(res.mean, xp.mean(x)) + xp_assert_close(res.variance, xp.var(x, correction=1)) + xp_assert_close(res.skewness, stats.skew(x)) + xp_assert_close(res.kurtosis, stats.kurtosis(x)) + + +class NormalityTests: + + @pytest.mark.parametrize("alternative", ['two-sided', 'less', 'greater']) + def test_against_R(self, alternative, xp): + # testa against R `dagoTest` from package `fBasics` + # library(fBasics) + # options(digits=16) + # x = c(-2, -1, 0, 1, 2, 3)**2 + # x = rep(x, times=4) + # test_result <- dagoTest(x) + # test_result@test$statistic + # test_result@test$p.value + test_name = self.test_name + test_fun = getattr(stats, test_name) + ref_statistic= xp.asarray(self.case_ref[0]) + ref_pvalue = xp.asarray(self.case_ref[1]) + + kwargs = {} + if alternative in {'less', 'greater'}: + if test_name in {'skewtest', 'kurtosistest'}: + ref_pvalue = ref_pvalue/2 if alternative == "less" else 1-ref_pvalue/2 + ref_pvalue = 1-ref_pvalue if test_name == 'skewtest' else ref_pvalue + kwargs['alternative'] = alternative + else: + pytest.skip('`alternative` not available for `normaltest`') + + x = xp.asarray((-2, -1, 0, 1, 2, 3.)*4)**2 + res = test_fun(x, **kwargs) + res_statistic, res_pvalue = res + xp_assert_close(res_statistic, ref_statistic) + xp_assert_close(res_pvalue, ref_pvalue) + check_named_results(res, ('statistic', 'pvalue'), xp=xp) + + def test_nan(self, xp): + # nan in input -> nan output (default nan_policy='propagate') + test_fun = getattr(stats, self.test_name) + x = xp.arange(30.) + NaN = xp.asarray(xp.nan, dtype=x.dtype) + x = xp.where(x == 29, NaN, x) + with np.errstate(invalid="ignore"): + res = test_fun(x) + xp_assert_equal(res.statistic, NaN) + xp_assert_equal(res.pvalue, NaN) + + +@make_xp_test_case(stats.skewtest) +class TestSkewTest(NormalityTests): + test_name = 'skewtest' + case_ref = (1.98078826090875881, 0.04761502382843208) # statistic, pvalue + + def test_intuitive(self, xp): + # intuitive tests; see gh-13549. skewnorm with parameter 1 has skew > 0 + a1 = stats.skewnorm.rvs(a=1, size=10000, random_state=123) + a1_xp = xp.asarray(a1) + pval = stats.skewtest(a1_xp, alternative='greater').pvalue + xp_assert_close(pval, xp.asarray(0.0, dtype=a1_xp.dtype), atol=9e-6) + + def test_skewtest_too_few_observations(self, xp): + # Regression test for ticket #1492. + # skewtest requires at least 8 observations; 7 should warn and return NaN. + stats.skewtest(xp.arange(8.0)) + + x = xp.arange(7.0) + + with eager_warns(SmallSampleWarning, match=too_small_1d_not_omit, xp=xp): + res = stats.skewtest(x) + NaN = xp.asarray(xp.nan) + xp_assert_equal(res.statistic, NaN) + xp_assert_equal(res.pvalue, NaN) + + +@make_xp_test_case(stats.kurtosistest) +class TestKurtosisTest(NormalityTests): + test_name = 'kurtosistest' + case_ref = (-0.01403734404759738, 0.98880018772590561) # statistic, pvalue + + def test_intuitive(self, xp): + # intuitive tests; see gh-13549. excess kurtosis of laplace is 3 > 0 + a2 = stats.laplace.rvs(size=10000, random_state=123) + a2_xp = xp.asarray(a2) + pval = stats.kurtosistest(a2_xp, alternative='greater').pvalue + xp_assert_close(pval, xp.asarray(0.0, dtype=a2_xp.dtype), atol=1e-15) + + def test_gh9033_regression(self, xp): + # regression test for issue gh-9033: x clearly non-normal but power of + # negative denom needs to be handled correctly to reject normality + counts = [128, 0, 58, 7, 0, 41, 16, 0, 0, 167] + x = np.hstack([np.full(c, i) for i, c in enumerate(counts)]) + x = xp.asarray(x, dtype=xp.float64) + assert stats.kurtosistest(x)[1] < 0.01 + + def test_kurtosistest_too_few_observations(self, xp): + # kurtosistest requires at least 5 observations; 4 should warn and return NaN. + # Regression test for ticket #1425. + stats.kurtosistest(xp.arange(5.0)) + + with eager_warns(SmallSampleWarning, match=too_small_1d_not_omit, xp=xp): + res = stats.kurtosistest(xp.arange(4.)) + NaN = xp.asarray(xp.nan) + xp_assert_equal(res.statistic, NaN) + xp_assert_equal(res.pvalue, NaN) + + +@make_xp_test_case(stats.normaltest) +class TestNormalTest(NormalityTests): + test_name = 'normaltest' + case_ref = (3.92371918158185551, 0.14059672529747502) # statistic, pvalue + + def test_too_few_observations(self, xp): + stats.normaltest(xp.arange(8.)) + + # 1D sample has too few observations -> warning / NaN output + with eager_warns(SmallSampleWarning, match=too_small_1d_not_omit, xp=xp): + res = stats.normaltest(xp.arange(7.)) + NaN = xp.asarray(xp.nan) + xp_assert_equal(res.statistic, NaN) + xp_assert_equal(res.pvalue, NaN) + + +class TestRankSums: + + @pytest.mark.parametrize('alternative', ['less', 'greater', 'two-sided']) + def test_ranksums_is_mannwhitneyu(self, alternative): + rng = np.random.default_rng(3417115752) + x, y = rng.random((2, 10)) + + # ranksums pval = mannwhitneyu pval w/out continuity or tie correction + res1 = stats.ranksums(x, y, alternative=alternative) + res2 = stats.mannwhitneyu(x, y, use_continuity=False, + alternative=alternative, method='asymptotic') + assert_allclose(res1.pvalue, res2.pvalue) + assert_allclose(res1.statistic, res2.zstatistic) + + def test_ranksums_named_results(self): + res = stats.ranksums(np.arange(10), np.arange(10)) + check_named_results(res, ('statistic', 'pvalue')) + + def test_input_validation(self): + with assert_raises(ValueError, match="`alternative` must be 'less'"): + stats.ranksums(np.arange(10), np.arange(10), alternative='foobar') + + +@make_xp_test_case(stats.jarque_bera) +class TestJarqueBera: + def test_jarque_bera_against_R(self, xp): + # library(tseries) + # options(digits=16) + # x < - rnorm(5) + # jarque.bera.test(x) + x = [-0.160104223201523288, 1.131262000934478040, -0.001235254523709458, + -0.776440091309490987, -2.072959999533182884] + x = xp.asarray(x) + ref = xp.asarray([0.17651605223752, 0.9155246169805]) + res = stats.jarque_bera(x) + xp_assert_close(res.statistic, ref[0]) + xp_assert_close(res.pvalue, ref[1]) + + @skip_xp_backends(np_only=True) + def test_jarque_bera_array_like(self, xp): + # array-like only relevant for NumPy + rng = np.random.default_rng(9294968266) + x = rng.standard_normal(size=100000) + + jb_test1 = JB1, p1 = stats.jarque_bera(list(x)) + jb_test2 = JB2, p2 = stats.jarque_bera(tuple(x)) + jb_test3 = JB3, p3 = stats.jarque_bera(x.reshape(2, 50000)) + + assert JB1 == JB2 == JB3 == jb_test1.statistic == jb_test2.statistic == jb_test3.statistic # noqa: E501 + assert p1 == p2 == p3 == jb_test1.pvalue == jb_test2.pvalue == jb_test3.pvalue + + def test_jarque_bera_too_few_observations(self, xp): + x = xp.asarray([]) + + with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit): + res = stats.jarque_bera(x) + NaN = xp.asarray(xp.nan) + xp_assert_equal(res.statistic, NaN) + xp_assert_equal(res.pvalue, NaN) + + def test_axis(self, xp): + rng = np.random.RandomState(seed=122398129) + x = xp.asarray(rng.random(size=(2, 45))) + + res = stats.jarque_bera(x, axis=None) + ref = stats.jarque_bera(xp.reshape(x, (-1,))) + xp_assert_equal(res.statistic, ref.statistic) + xp_assert_equal(res.pvalue, ref.pvalue) + + res = stats.jarque_bera(x, axis=1) + s0, p0 = stats.jarque_bera(x[0, :]) + s1, p1 = stats.jarque_bera(x[1, :]) + xp_assert_close(res.statistic, xp.stack([s0, s1])) + xp_assert_close(res.pvalue, xp.stack([p0, p1])) + + resT = stats.jarque_bera(x.T, axis=0) + xp_assert_close(res.statistic, resT.statistic) + xp_assert_close(res.pvalue, resT.pvalue) + + +@make_xp_test_case(stats.pointbiserialr) +class TestPointBiserialR: + def test_pointbiserial(self, xp): + # same as mstats test except for the nan + # Test data: https://web.archive.org/web/20060504220742/https://support.sas.com/ctx/samples/index.jsp?sid=490&tab=output + x = [1,0,1,1,1,1,0,1,0,0,0,1,1,0,0,0,1,1,1,0,0,0,0,0,0,0,0,1,0, + 0,0,0,0,1.] + y = [14.8,13.8,12.4,10.1,7.1,6.1,5.8,4.6,4.3,3.5,3.3,3.2,3.0, + 2.8,2.8,2.5,2.4,2.3,2.1,1.7,1.7,1.5,1.3,1.3,1.2,1.2,1.1, + 0.8,0.7,0.6,0.5,0.2,0.2,0.1] + res = stats.pointbiserialr(xp.asarray(x), xp.asarray(y)) + xp_assert_close(res[0], xp.asarray(0.36149), atol=1e-5) + + # test for namedtuple attribute results + attributes = ('correlation', 'pvalue') + check_named_results(res, attributes, xp=xp) + xp_assert_equal(res.correlation, res.statistic) + + +@make_xp_test_case(stats.obrientransform) +class TestObrientransform: + def test_basic(self, xp): + # A couple tests calculated by hand. + x1 = xp.asarray([0, 2, 4]) + t1 = stats.obrientransform(x1) + expected = xp.asarray([7., -2., 7.]) + xp_assert_close(t1[0][:], expected) + + x2 = xp.asarray([0, 3, 6, 9]) + t2 = stats.obrientransform(x2) + expected = xp.asarray([30., 0., 0., 30.]) + xp_assert_close(t2[0][:], expected) + + # Test two arguments. + a, b = stats.obrientransform(x1, x2) + xp_assert_close(a, t1[0][:]) + xp_assert_close(b, t2[0][:]) + + # Test three arguments. + a, b, c = stats.obrientransform(x1, x2, x1) + xp_assert_close(a, t1[0][:]) + xp_assert_close(b, t2[0][:]) + xp_assert_close(c, t1[0][:]) + + def test_something(self, xp): + # This is a regression test to check np.var replacement. + # The author of this test didn't separately verify the numbers. + x1 = xp.arange(5) + ref = xp.asarray( + [[5.41666667, 1.04166667, -0.41666667, 1.04166667, 5.41666667], + [21.66666667, 4.16666667, -1.66666667, 4.16666667, 21.66666667]]) + res = stats.obrientransform(x1, 2*x1) + xp_assert_close(res[0], ref[0, ...]) + xp_assert_close(res[1], ref[1, ...]) + + def test_reference(self, xp): + # Example from "O'Brien Test for Homogeneity of Variance" by Herve Abdi. + values = np.arange(5, 11) + reps = [5, 11, 9, 3, 2, 2] + data = np.repeat(values, reps) + transformed_values = np.asarray([3.1828, 0.5591, 0.0344, + 1.6086, 5.2817, 11.0538]) + expected = np.repeat(transformed_values, reps) + result = stats.obrientransform(xp.asarray(data)) + xp_assert_close(result[0][:], xp.asarray(expected.tolist()), rtol=1e-3) + + def test_nan_policy(self, xp): + rng = np.random.default_rng(4284359689201882838835) + x = rng.random(10) + x[3] = np.nan + x = xp.asarray(x) + + # nan_policy='raise' + if not is_lazy_array(x): + with pytest.raises(ValueError, match="The input contains nan values"): + stats.obrientransform(x, nan_policy='raise') + else: + with pytest.raises(TypeError, match="nan_policy='raise' is not supported"): + stats.obrientransform(x, nan_policy='raise') + + # nan_policy='propagate' + res = stats.obrientransform(x, nan_policy='propagate')[0] + xp_assert_equal(res, xp.full_like(x, xp.nan)) + + # nan_policy='omit' + i = xp.isnan(x) + res = stats.obrientransform(x, nan_policy='omit')[0] + ref = stats.obrientransform(x[~i])[0] + xp_assert_equal(res[i], x[i]) + xp_assert_close(res[~i], ref) + + +def check_equal_xmean(*args, xp, mean_fun, axis=None, dtype=None, + rtol=None, weights=None): + # Note this doesn't test when axis is not specified + dtype = dtype or xp.float64 + if len(args) == 2: + array_like, desired = args + else: + array_like, p, desired = args + array_like = xp.asarray(array_like, dtype=dtype) + dtype_reference = (xp_result_type(array_like, force_floating=True, xp=xp) + if xp != np.ma else None) + desired = xp.asarray(desired, dtype=dtype_reference) + weights = xp.asarray(weights, dtype=dtype) if weights is not None else weights + args = (array_like,) if len(args) == 2 else (array_like, p) + x = mean_fun(*args, axis=axis, weights=weights) + xp_assert_close(x, desired, rtol=rtol) + + +def check_equal_gmean(*args, **kwargs): + return check_equal_xmean(*args, mean_fun=stats.gmean, **kwargs) + + +def check_equal_hmean(*args, **kwargs): + return check_equal_xmean(*args, mean_fun=stats.hmean, **kwargs) + + +def check_equal_pmean(*args, **kwargs): + return check_equal_xmean(*args, mean_fun=stats.pmean, **kwargs) + + +@make_xp_test_case(stats.hmean) +class TestHMean: + @pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning:dask") + def test_0(self, xp): + a = [1, 0, 2] + desired = 0 + check_equal_hmean(a, desired, xp=xp) + + @pytest.mark.parametrize('dtype', [None, 'int64', 'float32', 'float64']) + def test_1d(self, dtype, xp): + dtype = dtype if dtype is None else getattr(xp, dtype) + + # Test a 1d case + a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] + desired = 34.1417152147 + check_equal_hmean(a, desired, dtype=dtype, xp=xp) + + a = [1, 2, 3, 4] + desired = 4. / (1. / 1 + 1. / 2 + 1. / 3 + 1. / 4) + check_equal_hmean(a, desired, dtype=dtype, xp=xp) + + @pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning:dask") + @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") + def test_1d_with_zero(self, xp): + a = np.array([1, 0]) + desired = 0.0 + check_equal_hmean(a, desired, xp=xp, rtol=0.0) + + @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") + @pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning:dask") + def test_1d_with_negative_value(self, xp): + a = np.array([1, 0, -1]) + message = "The harmonic mean is only defined..." + with eager_warns(RuntimeWarning, match=message, xp=xp): + check_equal_hmean(a, xp.nan, xp=xp, rtol=0.0) + + # Note the next tests use axis=None as default, not axis=0 + def test_2d(self, xp): + # Test a 2d case + a = [[10, 20, 30, 40], [50, 60, 70, 80], [90, 100, 110, 120]] + desired = 38.6696271841 + check_equal_hmean(np.array(a), desired, xp=xp) + + def test_2d_axis0(self, xp): + # Test a 2d case with axis=0 + a = [[10, 20, 30, 40], [50, 60, 70, 80], [90, 100, 110, 120]] + desired = np.array([22.88135593, 39.13043478, 52.90076336, 65.45454545]) + check_equal_hmean(a, desired, axis=0, xp=xp) + + @pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning:dask") + def test_2d_axis0_with_zero(self, xp): + a = [[10, 0, 30, 40], [50, 60, 70, 80], [90, 100, 110, 120]] + desired = np.array([22.88135593, 0.0, 52.90076336, 65.45454545]) + check_equal_hmean(a, desired, axis=0, xp=xp) + + def test_2d_axis1(self, xp): + # Test a 2d case with axis=1 + a = [[10, 20, 30, 40], [50, 60, 70, 80], [90, 100, 110, 120]] + desired = np.array([19.2, 63.03939962, 103.80078637]) + check_equal_hmean(a, desired, axis=1, xp=xp) + + @pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning:dask") + def test_2d_axis1_with_zero(self, xp): + a = [[10, 0, 30, 40], [50, 60, 70, 80], [90, 100, 110, 120]] + desired = np.array([0.0, 63.03939962, 103.80078637]) + check_equal_hmean(a, desired, axis=1, xp=xp) + + @skip_xp_backends( + np_only=True, + reason='array-likes only supported for NumPy backend', + ) + def test_weights_1d_list(self, xp): + # Desired result from: + # https://www.hackmath.net/en/math-problem/35871 + a = [2, 10, 6] + weights = [10, 5, 3] + desired = 3. + # all the other tests use `check_equal_hmean`, which now converts + # the input to an xp-array before calling `hmean`. This time, check + # that the function still accepts the lists of ints. + res = stats.hmean(a, weights=weights) + xp_assert_close(res, np.asarray(desired), rtol=1e-5) + + def test_weights_1d(self, xp): + # Desired result from: + # https://www.hackmath.net/en/math-problem/35871 + a = np.asarray([2, 10, 6]) + weights = np.asarray([10, 5, 3]) + desired = 3 + check_equal_hmean(a, desired, weights=weights, rtol=1e-5, xp=xp) + + def test_weights_2d_axis0(self, xp): + # Desired result from: + # https://www.hackmath.net/en/math-problem/35871 + a = np.array([[2, 5], [10, 5], [6, 5]]) + weights = np.array([[10, 1], [5, 1], [3, 1]]) + desired = np.array([3, 5]) + check_equal_hmean(a, desired, axis=0, weights=weights, rtol=1e-5, xp=xp) + + def test_weights_2d_axis1(self, xp): + # Desired result from: + # https://www.hackmath.net/en/math-problem/35871 + a = np.array([[2, 10, 6], [7, 7, 7]]) + weights = np.array([[10, 5, 3], [1, 1, 1]]) + desired = np.array([3, 7]) + check_equal_hmean(a, desired, axis=1, weights=weights, rtol=1e-5, xp=xp) + + @skip_xp_invalid_arg + def test_weights_masked_1d_array(self, xp): + # Desired result from: + # https://www.hackmath.net/en/math-problem/35871 + a = np.array([2., 10., 6., 42.]) + weights = np.ma.array([10., 5., 3., 42.], mask=[0, 0, 0, 1]) + desired = 3. + xp = np.ma # check_equal_hmean uses xp.asarray; this will preserve the mask + check_equal_hmean(a, desired, weights=weights, rtol=1e-5, + dtype=np.float64, xp=xp) + + +@make_xp_test_case(stats.gmean) +class TestGMean: + @pytest.mark.filterwarnings( + "ignore:divide by zero encountered in log:RuntimeWarning:dask" + ) + def test_0(self, xp): + a = [1, 0, 2] + desired = 0 + check_equal_gmean(a, desired, xp=xp) + + @pytest.mark.parametrize('dtype', [None, 'int64', 'float32', 'float64']) + def test_1d(self, dtype, xp): + dtype = dtype if dtype is None else getattr(xp, dtype) + + # Test a 1d case + a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] + desired = 45.2872868812 + check_equal_gmean(a, desired, dtype=dtype, xp=xp) + + a = [1, 2, 3, 4] + desired = power(1 * 2 * 3 * 4, 1. / 4.) + check_equal_gmean(a, desired, dtype=dtype, rtol=1e-14, xp=xp) + + # Note the next tests use axis=None as default, not axis=0 + def test_2d(self, xp): + # Test a 2d case + a = [[10, 20, 30, 40], [50, 60, 70, 80], [90, 100, 110, 120]] + desired = 52.8885199 + check_equal_gmean(a, desired, xp=xp) + + def test_2d_axis0(self, xp): + # Test a 2d case with axis=0 + a = [[10, 20, 30, 40], [50, 60, 70, 80], [90, 100, 110, 120]] + desired = np.array([35.56893304, 49.32424149, 61.3579244, 72.68482371]) + check_equal_gmean(a, desired, axis=0, xp=xp) + + a = array([[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]]) + desired = array([1, 2, 3, 4]) + check_equal_gmean(a, desired, axis=0, rtol=1e-14, xp=xp) + + def test_2d_axis1(self, xp): + # Test a 2d case with axis=1 + a = [[10, 20, 30, 40], [50, 60, 70, 80], [90, 100, 110, 120]] + desired = np.array([22.13363839, 64.02171746, 104.40086817]) + check_equal_gmean(a, desired, axis=1, xp=xp) + + a = array([[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]]) + v = power(1 * 2 * 3 * 4, 1. / 4.) + desired = array([v, v, v]) + check_equal_gmean(a, desired, axis=1, rtol=1e-14, xp=xp) + + def test_large_values(self, xp): + a = array([1e100, 1e200, 1e300]) + desired = 1e200 + check_equal_gmean(a, desired, rtol=1e-13, xp=xp) + + @pytest.mark.filterwarnings( + "ignore:divide by zero encountered in log:RuntimeWarning:dask" + ) + def test_1d_with_0(self, xp): + # Test a 1d case with zero element + a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 0] + desired = 0.0 # due to exp(-inf)=0 + with np.errstate(all='ignore'): + check_equal_gmean(a, desired, xp=xp) + + @pytest.mark.filterwarnings( + "ignore:invalid value encountered in log:RuntimeWarning:dask" + ) + def test_1d_neg(self, xp): + # Test a 1d case with negative element + a = [10, 20, 30, 40, 50, 60, 70, 80, 90, -1] + desired = np.nan # due to log(-1) = nan + with np.errstate(invalid='ignore'): + check_equal_gmean(a, desired, xp=xp) + + @skip_xp_backends( + np_only=True, + reason='array-likes only supported for NumPy backend', + ) + def test_weights_1d_list(self, xp): + # Desired result from: + # https://www.dummies.com/education/math/business-statistics/how-to-find-the-weighted-geometric-mean-of-a-data-set/ + a = [1, 2, 3, 4, 5] + weights = [2, 5, 6, 4, 3] + desired = 2.77748 + + # all the other tests use `check_equal_gmean`, which now converts + # the input to an xp-array before calling `gmean`. This time, check + # that the function still accepts the lists of ints. + res = stats.gmean(a, weights=weights) + xp_assert_close(res, np.asarray(desired), rtol=1e-5) + + def test_weights_1d(self, xp): + # Desired result from: + # https://www.dummies.com/education/math/business-statistics/how-to-find-the-weighted-geometric-mean-of-a-data-set/ + a = np.array([1, 2, 3, 4, 5]) + weights = np.array([2, 5, 6, 4, 3]) + desired = 2.77748 + check_equal_gmean(a, desired, weights=weights, rtol=1e-5, xp=xp) + + @skip_xp_invalid_arg + def test_weights_masked_1d_array(self, xp): + # Desired result from: + # https://www.dummies.com/education/math/business-statistics/how-to-find-the-weighted-geometric-mean-of-a-data-set/ + a = np.array([1., 2., 3., 4., 5., 6.]) + weights = np.ma.array([2., 5., 6., 4., 3., 5.], mask=[0, 0, 0, 0, 0, 1]) + desired = 2.77748 + xp = np.ma # check_equal_gmean uses xp.asarray; this will preserve the mask + check_equal_gmean(a, desired, weights=weights, rtol=1e-5, + dtype=np.float64, xp=xp) + + +@make_xp_test_case(stats.pmean) +class TestPMean: + + def pmean_reference(a, p): + return (np.sum(a**p) / a.size)**(1/p) + + def wpmean_reference(a, p, weights): + return (np.sum(weights * a**p) / np.sum(weights))**(1/p) + + def test_bad_exponent(self, xp): + with pytest.raises(ValueError, match='Power mean only defined for'): + stats.pmean(xp.asarray([1, 2, 3]), xp.asarray([0])) + + @pytest.mark.parametrize('dtype', [None, 'int64', 'float32', 'float64']) + def test_1d(self, dtype, xp): + dtype = dtype if dtype is None else getattr(xp, dtype) + + a, p = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100], 3.5 + desired = TestPMean.pmean_reference(np.array(a), p) + check_equal_pmean(a, p, desired, dtype=dtype, xp=xp) + + a, p = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100], -2.5 + desired = TestPMean.pmean_reference(np.array(a), p) + check_equal_pmean(a, p, desired, dtype=dtype, xp=xp) + + a, p = [1, 2, 3, 4], 2 + desired = np.sqrt((1**2 + 2**2 + 3**2 + 4**2) / 4) + check_equal_pmean(a, p, desired, dtype=dtype, xp=xp) + + @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") + @pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning:dask") + def test_1d_with_zero(self, xp): + a, p = np.array([1, 0]), -1 + desired = 0.0 + check_equal_pmean(a, p, desired, rtol=0.0, xp=xp) + + def test_1d_with_negative_value(self, xp): + a, p = np.array([1, 0, -1]), 1.23 + message = "The power mean is only defined..." + with eager_warns(RuntimeWarning, match=message, xp=xp): + check_equal_pmean(a, p, xp.nan, xp=xp) + + @pytest.mark.parametrize( + ("a", "p"), + [([[10, 20], [50, 60], [90, 100]], -0.5), + (np.array([[10, 20], [50, 60], [90, 100]]), 0.5)] + ) + def test_2d_axisnone(self, a, p, xp): + desired = TestPMean.pmean_reference(np.array(a), p) + check_equal_pmean(a, p, desired, xp=xp) + + @pytest.mark.parametrize( + ("a", "p"), + [([[10, 20, 30, 40], [50, 60, 70, 80], [90, 100, 110, 120]], -0.5), + ([[10, 0, 30, 40], [50, 60, 70, 80], [90, 100, 110, 120]], 0.5)] + ) + def test_2d_axis0(self, a, p, xp): + desired = [ + TestPMean.pmean_reference( + np.array([a[i][j] for i in range(len(a))]), p + ) + for j in range(len(a[0])) + ] + check_equal_pmean(a, p, desired, axis=0, xp=xp) + + @pytest.mark.parametrize( + ("a", "p"), + [([[10, 20, 30, 40], [50, 60, 70, 80], [90, 100, 110, 120]], -0.5), + ([[10, 0, 30, 40], [50, 60, 70, 80], [90, 100, 110, 120]], 0.5)] + ) + def test_2d_axis1(self, a, p, xp): + desired = [TestPMean.pmean_reference(np.array(a_), p) for a_ in a] + check_equal_pmean(a, p, desired, axis=1, xp=xp) + + def test_weights_1d(self, xp): + a, p = [2, 10, 6], -1.23456789 + weights = [10, 5, 3] + desired = TestPMean.wpmean_reference(np.array(a), p, weights) + check_equal_pmean(a, p, desired, weights=weights, rtol=1e-5, xp=xp) + + @skip_xp_backends( + np_only=True, + reason='array-likes only supported for NumPy backend', + ) + def test_weights_1d_list(self, xp): + a, p = [2, 10, 6], -1.23456789 + weights = [10, 5, 3] + desired = TestPMean.wpmean_reference(np.array(a), p, weights) + # all the other tests use `check_equal_pmean`, which now converts + # the input to an xp-array before calling `pmean`. This time, check + # that the function still accepts the lists of ints. + res = stats.pmean(a, p, weights=weights) + xp_assert_close(res, np.asarray(desired), rtol=1e-5) + + @skip_xp_invalid_arg + def test_weights_masked_1d_array(self, xp): + a, p = np.array([2., 10., 6., 42.]), 1 + weights = np.ma.array([10., 5., 3., 42.], mask=[0, 0, 0, 1]) + desired = np.average(a, weights=weights) + xp = np.ma # check_equal_pmean uses xp.asarray; this will preserve the mask + check_equal_pmean(a, p, desired, weights=weights, rtol=1e-5, + dtype=np.float64, xp=xp) + + @pytest.mark.parametrize( + ("axis", "fun_name", "p"), + [(None, "wpmean_reference", 9.87654321), + (0, "gmean", 0), + (1, "hmean", -1)] + ) + def test_weights_2d(self, axis, fun_name, p, xp): + if fun_name == 'wpmean_reference': + def fun(a, axis, weights): + return TestPMean.wpmean_reference(a, p, weights) + else: + fun = getattr(stats, fun_name) + a = np.array([[2, 5], [10, 5], [6, 5]]) + weights = np.array([[10, 1], [5, 1], [3, 1]]) + desired = fun(a, axis=axis, weights=weights) + check_equal_pmean(a, p, desired, axis=axis, weights=weights, rtol=1e-5, xp=xp) + + def test_infinite_p_gh23111(self): + # gh-23111 reported that `pmean` didn't work properly with infinite `p`; + # check that this raises an appropriate error message + message = "Power mean only implemented for finite `p`" + with pytest.raises(NotImplementedError, match=message): + stats.pmean([2], np.inf) + + + @pytest.mark.parametrize( + "p, weights, ref", + [ + # import numpy as np + # from mpmath import mp + # mp.dps = 1000 + # + # x = [50., 100., 300., 70., 25., 100., 2.] + # w = [1., 2., 1., 1., 1., 2., 1.] + # + # x = np.asarray([mp.mpf(x_) for x_ in x]) + # w = np.asarray([mp.mpf(w_) for w_ in w]) + # p = mp.mpf(2e-16) + # + # res = np.average(x**p)**(1/p) + # float(res) # 47.23984586445856 + # + (2e-16, None, 47.23984586445856), + # + # res = np.average(x**p, weights=w)**(1/p) + # float(res) # 55.80644618389137 + # + (2e-16, [1., 2., 1., 1., 1., 2., 1.], 55.80644618389137), + ], + ) + def test_small_p_gh23407(self, xp, p, weights, ref): + # gh-23407 reported that pmean had + # poor numerical behavior for very small nonzero p + x = xp.asarray( + [50., 100., 300., 70., 25., 100., 2.] + ) + w = None if weights is None else xp.asarray(weights) + + res = stats.pmean(x, p, weights=w) + xp_assert_close(res, xp.asarray(ref)) + + +@make_xp_test_case(stats.gstd) +class TestGSTD: + # must add 1 as `gstd` is only defined for positive values + array_1d = (np.arange(2 * 3 * 4) + 1).tolist() + gstd_array_1d = 2.294407613602 + array_3d = np.reshape(array_1d, (2, 3, 4)).tolist() + + def test_1d_array(self, xp): + gstd_actual = stats.gstd(xp.asarray(self.array_1d)) + xp_assert_close(gstd_actual, xp.asarray(self.gstd_array_1d)) + + @skip_xp_backends(np_only=True, reason="Only NumPy supports array-like input") + def test_1d_numeric_array_like_input(self, xp): + gstd_actual = stats.gstd(tuple(self.array_1d)) + assert_allclose(gstd_actual, self.gstd_array_1d) + + @skip_xp_invalid_arg + def test_raises_error_non_numeric_input(self, xp): + message = "could not convert string to float|The DType..." + with pytest.raises((ValueError, TypeError), match=message): + stats.gstd('You cannot take the logarithm of a string.') + + @pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning:dask") + @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") + @pytest.mark.parametrize('bad_value', (0, -1, np.inf, np.nan)) + def test_returns_nan_invalid_value(self, bad_value, xp): + x = xp.asarray(self.array_1d + [bad_value]) + if np.isfinite(bad_value) and not is_lazy_array(x): + message = "The geometric standard deviation is only defined..." + with pytest.warns(RuntimeWarning, match=message): + res = stats.gstd(x) + else: + res = stats.gstd(x) + xp_assert_equal(res, xp.asarray(np.nan)) + + def test_propagates_nan_values(self, xp): + a = xp.asarray([[1, 1, 1, 16], [xp.nan, 1, 2, 3]]) + gstd_actual = stats.gstd(a, axis=1) + xp_assert_close(gstd_actual, xp.asarray([4, np.nan])) + + def test_ddof_equal_to_number_of_observations(self, xp): + x = xp.asarray(self.array_1d) + res = stats.gstd(x, ddof=x.shape[0]) + xp_assert_equal(res, xp.asarray(xp.nan)) + + def test_3d_array(self, xp): + x = xp.asarray(self.array_3d) + gstd_actual = stats.gstd(x, axis=None) + ref = xp.asarray(self.gstd_array_1d) + xp_assert_close(gstd_actual, ref) + + def test_3d_array_axis_type_tuple(self, xp): + x = xp.asarray(self.array_3d) + gstd_actual = stats.gstd(x, axis=(1, 2)) + ref = xp.asarray([2.12939215, 1.22120169]) + xp_assert_close(gstd_actual, ref) + + def test_3d_array_axis_0(self, xp): + x = xp.asarray(self.array_3d) + gstd_actual = stats.gstd(x, axis=0) + gstd_desired = xp.asarray([ + [6.1330555493918, 3.958900210120, 3.1206598248344, 2.6651441426902], + [2.3758135028411, 2.174581428192, 2.0260062829505, 1.9115518327308], + [1.8205343606803, 1.746342404566, 1.6846557065742, 1.6325269194382] + ]) + xp_assert_close(gstd_actual, gstd_desired) + + def test_3d_array_axis_1(self, xp): + x = xp.asarray(self.array_3d) + gstd_actual = stats.gstd(x, axis=1) + gstd_desired = xp.asarray([ + [3.118993630946, 2.275985934063, 1.933995977619, 1.742896469724], + [1.271693593916, 1.254158641801, 1.238774141609, 1.225164057869] + ]) + xp_assert_close(gstd_actual, gstd_desired) + + def test_3d_array_axis_2(self, xp): + x = xp.asarray(self.array_3d) + gstd_actual = stats.gstd(x, axis=2) + gstd_desired = xp.asarray([ + [1.8242475707664, 1.2243686572447, 1.1318311657788], + [1.0934830582351, 1.0724479791887, 1.0591498540749] + ]) + xp_assert_close(gstd_actual, gstd_desired) + + +class TestTrim: + # test trim functions + def test_trim1(self): + a = np.arange(11) + assert_equal(np.sort(stats.trim1(a, 0.1)), np.arange(10)) + assert_equal(np.sort(stats.trim1(a, 0.2)), np.arange(9)) + assert_equal(np.sort(stats.trim1(a, 0.2, tail='left')), + np.arange(2, 11)) + assert_equal(np.sort(stats.trim1(a, 3/11., tail='left')), + np.arange(3, 11)) + assert_equal(stats.trim1(a, 1.0), []) + assert_equal(stats.trim1(a, 1.0, tail='left'), []) + + # empty input + assert_equal(stats.trim1([], 0.1), []) + assert_equal(stats.trim1([], 3/11., tail='left'), []) + assert_equal(stats.trim1([], 4/6.), []) + + # test axis + a = np.arange(24).reshape(6, 4) + ref = np.arange(4, 24).reshape(5, 4) # first row trimmed + + axis = 0 + trimmed = stats.trim1(a, 0.2, tail='left', axis=axis) + assert_equal(np.sort(trimmed, axis=axis), ref) + + axis = 1 + trimmed = stats.trim1(a.T, 0.2, tail='left', axis=axis) + assert_equal(np.sort(trimmed, axis=axis), ref.T) + + def test_trimboth(self): + a = np.arange(11) + assert_equal(np.sort(stats.trimboth(a, 3/11.)), np.arange(3, 8)) + assert_equal(np.sort(stats.trimboth(a, 0.2)), + np.array([2, 3, 4, 5, 6, 7, 8])) + assert_equal(np.sort(stats.trimboth(np.arange(24).reshape(6, 4), 0.2)), + np.arange(4, 20).reshape(4, 4)) + assert_equal(np.sort(stats.trimboth(np.arange(24).reshape(4, 6).T, + 2/6.)), + np.array([[2, 8, 14, 20], [3, 9, 15, 21]])) + assert_raises(ValueError, stats.trimboth, + np.arange(24).reshape(4, 6).T, 4/6.) + + # empty input + assert_equal(stats.trimboth([], 0.1), []) + assert_equal(stats.trimboth([], 3/11.), []) + assert_equal(stats.trimboth([], 4/6.), []) + + +@make_xp_test_case(stats.trim_mean) +class TestTrimMean: + def test_trim_mean(self, xp): + # don't use pre-sorted arrays + idx = np.array([3, 5, 0, 1, 2, 4]) + a2 = np.arange(24).reshape(6, 4)[idx, :] + a3 = np.arange(24).reshape(6, 4, order='F')[idx, :] + + xp_assert_equal(stats.trim_mean(xp.asarray(a3), 2/6.), + xp.asarray([2.5, 8.5, 14.5, 20.5])) + xp_assert_equal(stats.trim_mean(xp.asarray(a2), 2/6.), + xp.asarray([10., 11., 12., 13.])) + + idx4 = np.array([1, 0, 3, 2]) + a4 = np.arange(24).reshape(4, 6)[idx4, :] + xp_assert_equal(stats.trim_mean(xp.asarray(a4), 2/6.), + xp.asarray([9., 10., 11., 12., 13., 14.])) + + # shuffled arange(24) as array_like + a = [7, 11, 12, 21, 16, 6, 22, 1, 5, 0, 18, 10, 17, 9, 19, 15, 23, + 20, 2, 14, 4, 13, 8, 3] + xp_assert_equal(stats.trim_mean(xp.asarray(a), 2/6.), xp.asarray(11.5)) + xp_assert_equal(stats.trim_mean(xp.asarray([5, 4, 3, 1, 2, 0]), 2/6.), + xp.asarray(2.5)) + + # check axis argument + rng = np.random.default_rng(3417115752) + a = rng.integers(20, size=(5, 6, 4, 7)) + a = xp.asarray(a) + for axis in [0, 1, 2, 3, -1]: + res1 = stats.trim_mean(a, 2/6., axis=axis) + res2 = stats.trim_mean(xp.moveaxis(a, axis, 0), 2/6.) + xp_assert_equal(res1, res2) + + res1 = stats.trim_mean(a, 2/6., axis=None) + res2 = stats.trim_mean(xp_ravel(a), 2/6.) + xp_assert_equal(res1, res2) + + with pytest.raises(ValueError, match="Proportion too big."): + stats.trim_mean(a, 0.6) + + @skip_xp_backends('dask.array', reason="lazy -> no _axis_nan_policy") + def test_empty_input(self, xp): + # empty input + with pytest.warns(SmallSampleWarning, match='too small'): + xp_assert_equal(stats.trim_mean(xp.asarray([]), 0.0), xp.asarray(xp.nan)) + with pytest.warns(SmallSampleWarning, match='too small'): + xp_assert_equal(stats.trim_mean(xp.asarray([]), 0.6), xp.asarray(xp.nan)) + + +@make_xp_test_case(stats.sigmaclip) +class TestSigmaClip: + def test_sigmaclip1(self, xp): + a = xp.concat((xp.linspace(9.5, 10.5, 31), + xp.linspace(0., 20., 5))) + fact = 4 # default + c, low, upp = stats.sigmaclip(a) + assert xp.min(c) > low + assert xp.max(c) < upp + xp_assert_equal(low, xp.mean(c) - fact*xp.std(c)) + xp_assert_equal(upp, xp.mean(c) + fact*xp.std(c)) + assert c.shape == a.shape + + def test_sigmaclip2(self, xp): + a = xp.concat((xp.linspace(9.5, 10.5, 31), + xp.linspace(0., 20., 5))) + fact = 1.5 + c, low, upp = stats.sigmaclip(a, fact, fact) + assert xp.min(c) > low + assert xp.max(c) < upp + xp_assert_equal(low, xp.mean(c) - fact*xp.std(c)) + xp_assert_equal(upp, xp.mean(c) + fact*xp.std(c)) + assert c.shape == (4,) + assert a.shape == (36,) + + def test_sigmaclip3(self, xp): + a = xp.concat((xp.linspace(9.5, 10.5, 11), + xp.linspace(-100., -50., 3))) + fact = 1.8 + c, low, upp = stats.sigmaclip(a, fact, fact) + assert xp.min(c) > low + assert xp.max(c) < upp + xp_assert_equal(low, xp.mean(c) - fact*xp.std(c)) + xp_assert_equal(upp, xp.mean(c) + fact*xp.std(c)) + xp_assert_equal(c, xp.linspace(9.5, 10.5, 11)) + + def test_sigmaclip_result_attributes(self, xp): + a = xp.concat((xp.linspace(9.5, 10.5, 11), + xp.linspace(-100., -50., 3))) + fact = 1.8 + res = stats.sigmaclip(a, fact, fact) + attributes = ('clipped', 'lower', 'upper') + check_named_results(res, attributes, xp=xp) + + def test_std_zero(self, xp): + # regression test #8632 + x = xp.ones(10) + xp_assert_equal(stats.sigmaclip(x)[0], x) + + @pytest.mark.parametrize("dtype", [None, 'float32', 'float64']) + def test_nan_policy(self, dtype, xp): + dtype = None if dtype is None else getattr(xp, dtype) + rng = np.random.default_rng(243587919486245906119) + x = rng.random(10) + x[3] = np.nan + x = xp.asarray(x.tolist(), dtype=dtype) + + # nan_policy='raise' + if not is_lazy_array(x): + with pytest.raises(ValueError, match="The input contains nan values"): + stats.sigmaclip(x, nan_policy='raise') + else: + with pytest.raises(TypeError, match="nan_policy='raise' is not supported"): + stats.sigmaclip(x, nan_policy='raise') + + # nan_policy='propagate' + res = stats.sigmaclip(x, nan_policy='propagate') + xp_assert_equal(res.clipped, xp.asarray([], dtype=dtype)) + xp_assert_equal(res.lower, xp.asarray(xp.nan, dtype=dtype)) + xp_assert_equal(res.upper, xp.asarray(xp.nan, dtype=dtype)) + + # nan_policy='omit' + res = stats.sigmaclip(x, nan_policy='omit') + ref = stats.sigmaclip(x[~xp.isnan(x)]) + xp_assert_equal(res.clipped, ref.clipped) + xp_assert_equal(res.lower, ref.lower) + xp_assert_equal(res.upper, ref.upper) + + +@make_xp_test_case(stats.alexandergovern) +class TestAlexanderGovern: + def test_compare_dtypes(self): + # leave this NumPy only + args = [[13, 13, 13, 13, 13, 13, 13, 12, 12], + [14, 13, 12, 12, 12, 12, 12, 11, 11], + [14, 14, 13, 13, 13, 13, 13, 12, 12], + [15, 14, 13, 13, 13, 12, 12, 12, 11]] + args_int16 = [np.asarray(arg, dtype=np.int16) for arg in args] + args_int32 = [np.asarray(arg, dtype=np.int32) for arg in args] + args_uint8 = [np.asarray(arg, dtype=np.uint8) for arg in args] + args_float64 = [np.asarray(arg, dtype=np.float64) for arg in args] + + res_int16 = stats.alexandergovern(*args_int16) + res_int32 = stats.alexandergovern(*args_int32) + res_uint8 = stats.alexandergovern(*args_uint8) + res_float64 = stats.alexandergovern(*args_float64) + + assert (res_int16.pvalue == res_int32.pvalue == + res_uint8.pvalue == res_float64.pvalue) + assert (res_int16.statistic == res_int32.statistic == + res_uint8.statistic == res_float64.statistic) + + @skip_xp_backends('dask.array', reason="Requires `_axis_nan_policy` decorator") + @pytest.mark.parametrize('case',[([1, 2], []), ([1, 2], 2), ([1, 2], [2])]) + def test_too_small_inputs(self, case, xp): + # input array is of size zero or too small + dtype = xp_default_dtype(xp) + case = xp.asarray(case[0], dtype=dtype), xp.asarray(case[1], dtype=dtype) + with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit): + res = stats.alexandergovern(*case) + xp_assert_equal(res.statistic, xp.asarray(xp.nan)) + xp_assert_equal(res.pvalue, xp.asarray(xp.nan)) + + @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning") + def test_bad_inputs(self, xp): + # inputs are not finite (infinity) + samples = xp.asarray([1., 2.]), xp.asarray([xp.inf, xp.inf]) + res = stats.alexandergovern(*samples) + xp_assert_equal(res.statistic, xp.asarray(xp.nan)) + xp_assert_equal(res.pvalue, xp.asarray(xp.nan)) + + def test_compare_r(self, xp): + ''' + Data generated in R with + > set.seed(1) + > library("onewaytests") + > library("tibble") + > y <- c(rnorm(40, sd=10), + + rnorm(30, sd=15), + + rnorm(20, sd=20)) + > x <- c(rep("one", times=40), + + rep("two", times=30), + + rep("eight", times=20)) + > x <- factor(x) + > ag.test(y ~ x, tibble(y,x)) + + Alexander-Govern Test (alpha = 0.05) + ------------------------------------------------------------- + data : y and x + + statistic : 1.359941 + parameter : 2 + p.value : 0.5066321 + + Result : Difference is not statistically significant. + ------------------------------------------------------------- + Example adapted from: + https://eval-serv2.metpsy.uni-jena.de/wiki-metheval-hp/index.php/R_FUN_Alexander-Govern + + ''' + one = [-6.264538107423324, 1.8364332422208225, -8.356286124100471, + 15.952808021377916, 3.295077718153605, -8.204683841180152, + 4.874290524284853, 7.383247051292173, 5.757813516534923, + -3.0538838715635603, 15.11781168450848, 3.898432364114311, + -6.2124058054180376, -22.146998871774997, 11.249309181431082, + -0.4493360901523085, -0.16190263098946087, 9.438362106852992, + 8.212211950980885, 5.939013212175088, 9.189773716082183, + 7.821363007310671, 0.745649833651906, -19.89351695863373, + 6.198257478947102, -0.5612873952900078, -1.557955067053293, + -14.707523838992744, -4.781500551086204, 4.179415601997024, + 13.58679551529044, -1.0278772734299553, 3.876716115593691, + -0.5380504058290512, -13.770595568286065, -4.149945632996798, + -3.942899537103493, -0.5931339671118566, 11.000253719838831, + 7.631757484575442] + + two = [-2.4678539438038034, -3.8004252020476135, 10.454450631071062, + 8.34994798010486, -10.331335418242798, -10.612427354431794, + 5.468729432052455, 11.527993867731237, -1.6851931822534207, + 13.216615896813222, 5.971588205506021, -9.180395898761569, + 5.116795371366372, -16.94044644121189, 21.495355525515556, + 29.7059984775879, -5.508322146997636, -15.662019394747961, + 8.545794411636193, -2.0258190582123654, 36.024266407571645, + -0.5886000409975387, 10.346090436761651, 0.4200323817099909, + -11.14909813323608, 2.8318844927151434, -27.074379433365568, + 21.98332292344329, 2.2988000731784655, 32.58917505543229] + + eight = [9.510190577993251, -14.198928618436291, 12.214527069781099, + -18.68195263288503, -25.07266800478204, 5.828924710349257, + -8.86583746436866, 0.02210703263248262, 1.4868264830332811, + -11.79041892376144, -11.37337465637004, -2.7035723024766414, + 23.56173993146409, -30.47133600859524, 11.878923752568431, + 6.659007424270365, 21.261996745527256, -6.083678472686013, + 7.400376198325763, 5.341975815444621] + + one, two, eight = xp.asarray(one), xp.asarray(two), xp.asarray(eight) + soln = stats.alexandergovern(one, two, eight) + xp_assert_close(soln.statistic, xp.asarray(1.3599405447999450836)) + xp_assert_close(soln.pvalue, xp.asarray(0.50663205309676440091)) + + def test_compare_scholar(self, xp): + ''' + Data taken from 'The Modification and Evaluation of the + Alexander-Govern Test in Terms of Power' by Kingsley Ochuko, T., + Abdullah, S., Binti Zain, Z., & Soaad Syed Yahaya, S. (2015). + ''' + young = [482.43, 484.36, 488.84, 495.15, 495.24, 502.69, 504.62, + 518.29, 519.1, 524.1, 524.12, 531.18, 548.42, 572.1, 584.68, + 609.09, 609.53, 666.63, 676.4] + middle = [335.59, 338.43, 353.54, 404.27, 437.5, 469.01, 485.85, + 487.3, 493.08, 494.31, 499.1, 886.41] + old = [519.01, 528.5, 530.23, 536.03, 538.56, 538.83, 557.24, 558.61, + 558.95, 565.43, 586.39, 594.69, 629.22, 645.69, 691.84] + young, middle, old = xp.asarray(young), xp.asarray(middle), xp.asarray(old) + soln = stats.alexandergovern(young, middle, old) + xp_assert_close(soln.statistic, xp.asarray(5.3237), atol=1e-3) + xp_assert_close(soln.pvalue, xp.asarray(0.06982), atol=1e-4) + + # verify with ag.test in r + ''' + > library("onewaytests") + > library("tibble") + > young <- c(482.43, 484.36, 488.84, 495.15, 495.24, 502.69, 504.62, + + 518.29, 519.1, 524.1, 524.12, 531.18, 548.42, 572.1, + + 584.68, 609.09, 609.53, 666.63, 676.4) + > middle <- c(335.59, 338.43, 353.54, 404.27, 437.5, 469.01, 485.85, + + 487.3, 493.08, 494.31, 499.1, 886.41) + > old <- c(519.01, 528.5, 530.23, 536.03, 538.56, 538.83, 557.24, + + 558.61, 558.95, 565.43, 586.39, 594.69, 629.22, + + 645.69, 691.84) + > young_fct <- c(rep("young", times=19)) + > middle_fct <-c(rep("middle", times=12)) + > old_fct <- c(rep("old", times=15)) + > ag.test(a ~ b, tibble(a=c(young, middle, old), b=factor(c(young_fct, + + middle_fct, old_fct)))) + + Alexander-Govern Test (alpha = 0.05) + ------------------------------------------------------------- + data : a and b + + statistic : 5.324629 + parameter : 2 + p.value : 0.06978651 + + Result : Difference is not statistically significant. + ------------------------------------------------------------- + + ''' + xp_assert_close(soln.statistic, xp.asarray(5.324629), rtol=2e-6) + xp_assert_close(soln.pvalue, xp.asarray(0.06978651), rtol=2e-6) + + def test_compare_scholar3(self, xp): + ''' + Data taken from 'Robustness And Comparative Power Of WelchAspin, + Alexander-Govern And Yuen Tests Under Non-Normality And Variance + Heteroscedasticity', by Ayed A. Almoied. 2017. Page 34-37. + https://digitalcommons.wayne.edu/cgi/viewcontent.cgi?article=2775&context=oa_dissertations + ''' + x1 = [-1.77559, -1.4113, -0.69457, -0.54148, -0.18808, -0.07152, + 0.04696, 0.051183, 0.148695, 0.168052, 0.422561, 0.458555, + 0.616123, 0.709968, 0.839956, 0.857226, 0.929159, 0.981442, + 0.999554, 1.642958] + x2 = [-1.47973, -1.2722, -0.91914, -0.80916, -0.75977, -0.72253, + -0.3601, -0.33273, -0.28859, -0.09637, -0.08969, -0.01824, + 0.260131, 0.289278, 0.518254, 0.683003, 0.877618, 1.172475, + 1.33964, 1.576766] + x1, x2 = xp.asarray(x1), xp.asarray(x2) + soln = stats.alexandergovern(x1, x2) + xp_assert_close(soln.statistic, xp.asarray(0.713526), atol=1e-5) + xp_assert_close(soln.pvalue, xp.asarray(0.398276), atol=1e-5) + + ''' + tested in ag.test in R: + > library("onewaytests") + > library("tibble") + > x1 <- c(-1.77559, -1.4113, -0.69457, -0.54148, -0.18808, -0.07152, + + 0.04696, 0.051183, 0.148695, 0.168052, 0.422561, 0.458555, + + 0.616123, 0.709968, 0.839956, 0.857226, 0.929159, 0.981442, + + 0.999554, 1.642958) + > x2 <- c(-1.47973, -1.2722, -0.91914, -0.80916, -0.75977, -0.72253, + + -0.3601, -0.33273, -0.28859, -0.09637, -0.08969, -0.01824, + + 0.260131, 0.289278, 0.518254, 0.683003, 0.877618, 1.172475, + + 1.33964, 1.576766) + > x1_fact <- c(rep("x1", times=20)) + > x2_fact <- c(rep("x2", times=20)) + > a <- c(x1, x2) + > b <- factor(c(x1_fact, x2_fact)) + > ag.test(a ~ b, tibble(a, b)) + Alexander-Govern Test (alpha = 0.05) + ------------------------------------------------------------- + data : a and b + + statistic : 0.7135182 + parameter : 1 + p.value : 0.3982783 + + Result : Difference is not statistically significant. + ------------------------------------------------------------- + ''' + xp_assert_close(soln.statistic, xp.asarray(0.7135182), rtol=2e-6) + xp_assert_close(soln.pvalue, xp.asarray(0.3982783), rtol=2e-6) + + def test_nan_policy_propagate(self, xp): + args = xp.asarray([1., 2., 3., 4.]), xp.asarray([1, xp.nan]) + # default nan_policy is 'propagate' + res = stats.alexandergovern(*args) + xp_assert_equal(res.pvalue, xp.asarray(xp.nan)) + xp_assert_equal(res.statistic, xp.asarray(xp.nan)) + + @skip_xp_backends(eager_only=True, reason="lazy -> reduced nan_policy capabilities") + def test_nan_policy_raise(self, xp): + args = xp.asarray([1., 2., 3., 4.]), xp.asarray([1., xp.nan]) + with assert_raises(ValueError, match="The input contains nan values"): + stats.alexandergovern(*args, nan_policy='raise') + + @skip_xp_backends(eager_only=True, reason="lazy -> reduced nan_policy capabilities") + def test_nan_policy_omit(self, xp): + args_nan = xp.asarray([1, 2, 3, xp.nan, 4]), xp.asarray([1, xp.nan, 19, 25]) + args_no_nan = xp.asarray([1, 2, 3, 4]), xp.asarray([1, 19, 25]) + res_nan = stats.alexandergovern(*args_nan, nan_policy='omit') + res_no_nan = stats.alexandergovern(*args_no_nan) + xp_assert_equal(res_nan.pvalue, res_no_nan.pvalue) + xp_assert_equal(res_nan.statistic, res_no_nan.statistic) + + @skip_xp_backends(eager_only=True, reason="lazy -> no data-dependent warnings") + def test_constant_input(self, xp): + # Zero variance input, consistent with `stats.pearsonr` + x1 = xp.asarray([0.667, 0.667, 0.667]) + x2 = xp.asarray([0.123, 0.456, 0.789]) + with pytest.warns(RuntimeWarning, match="Precision loss occurred..."): + res = stats.alexandergovern(x1, x2) + xp_assert_equal(res.statistic, xp.asarray(xp.nan)) + xp_assert_equal(res.pvalue, xp.asarray(xp.nan)) + + @skip_xp_backends('dask.array', reason="Requires `_axis_nan_policy` decorator") + @pytest.mark.parametrize('axis', [0, 1, None]) + def test_2d_input(self, xp, axis): + # much more extensive testing for NumPy in `test_axis_nan_policy.py` + rng = np.random.default_rng(23498389754392854235) + shape = (11, 12) + samples = [rng.random(shape) for i in range(3)] + xp_samples = [xp.asarray(sample) for sample in samples] + ref = stats.alexandergovern(*samples, axis=axis) + res = stats.alexandergovern(*xp_samples, axis=axis) + xp_assert_close(res.statistic, xp.asarray(ref.statistic)) + xp_assert_close(res.pvalue, xp.asarray(ref.pvalue)) + + +@make_xp_test_case(stats.f_oneway) +class TestFOneWay: + + def test_trivial(self, xp): + # A trivial test of stats.f_oneway, with F=0. + F, p = stats.f_oneway(xp.asarray([0, 2]), xp.asarray([0, 2])) + xp_assert_equal(F, xp.asarray(0.0)) + xp_assert_equal(p, xp.asarray(1.0)) + + @pytest.mark.parametrize("dtype", [None, "float32", "float64"]) + def test_basic(self, dtype, xp): + # Despite being a floating point calculation, this data should + # result in F being exactly 2.0. + dtype = None if dtype is None else getattr(xp, dtype) + F, p = stats.f_oneway(xp.asarray([0, 2], dtype=dtype), + xp.asarray([2, 4], dtype=dtype)) + xp_assert_equal(F, xp.asarray(2.0, dtype=dtype)) + xp_assert_close(p, xp.asarray(1 - 0.5**0.5, dtype=dtype)) + + @pytest.mark.parametrize("dtype", [None, "float32", "float64"]) + def test_unequal_var(self, dtype, xp): + # toy samples with unequal variances and different observations + dtype = None if dtype is None else getattr(xp, dtype) + samples = [xp.asarray([-50.42, 40.31, -18.09, 35.58, -6.8, 0.22], dtype=dtype), + xp.asarray([23.44, 4.5, 15.1, 9.66], dtype=dtype), + xp.asarray([11.94, 11.1 , 9.87, 9.09, 3.33], dtype=dtype)] + + F, p = stats.f_oneway(*samples, equal_var=False) + + # R language as benchmark + # group1 <- c(-50.42, 40.31, -18.09, 35.58, -6.8, 0.22) + # group2 <- c(23.44, 4.5, 15.1, 9.66) + # group3 <- c(11.94, 11.1 , 9.87, 9.09, 3.33) + # + # data <- data.frame( + # value = c(group1, group2, group3), + # group = factor(c(rep("G1", length(group1)), + # rep("G2", length(group2)), + # rep("G3", length(group3)))) + # ) + # welch_anova <- oneway.test(value ~ group, data = data, var.equal = FALSE) + # welch_anova$statistic + ## F: 0.609740409019517 + # welch_anova$p.value + ## 0.574838941286302 + + xp_assert_close(F, xp.asarray(0.609740409019517, dtype=dtype)) + xp_assert_close(p, xp.asarray(0.574838941286302, dtype=dtype)) + + def test_equal_var_input_validation(self, xp): + samples = [xp.asarray([-50.42, 40.31, -18.09, 35.58, -6.8, 0.22]), + xp.asarray([23.44, 4.5, 15.1, 9.66]), + xp.asarray([11.94, 11.1 , 9.87, 9.09, 3.33])] + + message = "Expected a boolean value for 'equal_var'" + with pytest.raises(TypeError, match=message): + stats.f_oneway(*samples, equal_var="False") + + def test_known_exact(self, xp): + # Another trivial dataset for which the exact F and p can be + # calculated on most platforms + F, p = stats.f_oneway(xp.asarray([2]), xp.asarray([2]), + xp.asarray([2, 3, 4])) + xp_assert_close(F, xp.asarray(3/5)) # assert_equal fails on some CI platforms + xp_assert_close(p, xp.asarray(5/8)) + + def test_large_integer_array(self, xp): + a = xp.asarray([655, 788], dtype=xp.uint16) + b = xp.asarray([789, 772], dtype=xp.uint16) + F, p = stats.f_oneway(a, b) + # The expected value was verified by computing it with mpmath with + # 40 digits of precision. + xp_assert_close(F, xp.asarray(0.77450216931805540)) + + def test_result_attributes(self, xp): + a = xp.asarray([655, 788], dtype=xp.uint16) + b = xp.asarray([789, 772], dtype=xp.uint16) + res = stats.f_oneway(a, b) + attributes = ('statistic', 'pvalue') + check_named_results(res, attributes, xp=xp) + + @pytest.mark.parametrize('test_case', + ['SiRstv', 'SmLs01', 'SmLs02', 'SmLs03', 'AtmWtAg', 'SmLs04', 'SmLs05', + 'SmLs06', 'SmLs07', 'SmLs08', 'SmLs09']) + def test_nist(self, test_case, xp): + # These are the nist ANOVA files. They can be found at: + # https://www.itl.nist.gov/div898/strd/anova/anova.html + filename = test_case + ".dat" + rtol = 1e-7 + fname = os.path.abspath(os.path.join(os.path.dirname(__file__), + 'data/nist_anova', filename)) + with open(fname) as f: + content = f.read().split('\n') + certified = [line.split() for line in content[40:48] + if line.strip()] + dataf = np.loadtxt(fname, skiprows=60) + y, x = dataf.T + y = y.astype(int) + caty = np.unique(y) + f = float(certified[0][-1]) + + xlist = [xp.asarray(x[y == i]) for i in caty] + res = stats.f_oneway(*xlist) + + # With the hard test cases we relax the tolerance a bit. + hard_tc = ('SmLs07', 'SmLs08', 'SmLs09') + if test_case in hard_tc: + rtol = 1e-4 + + xp_assert_close(res[0], xp.asarray(f, dtype=xp.float64), rtol=rtol) + + @pytest.mark.filterwarnings('ignore') # Dask emits NumPy warnings + @pytest.mark.parametrize("a, b, expected", [ + (np.array([42, 42, 42]), np.array([7, 7, 7]), (np.inf, 0.)), + (np.array([42, 42, 42]), np.array([42, 42, 42]), (np.nan, np.nan)) + ]) + def test_constant_input(self, a, b, expected, xp): + # For more details, look on https://github.com/scipy/scipy/issues/11669 + f, p = stats.f_oneway(xp.asarray(a), xp.asarray(b)) + xp_assert_equal(f, xp.asarray(expected[0])) + xp_assert_equal(p, xp.asarray(expected[1])) + + @skip_xp_backends('dask.array', reason='needs _axis_nan_policy') + @pytest.mark.parametrize('axis', [-2, -1, 0, 1]) + def test_2d_inputs(self, axis, xp): + a = np.array([[1, 4, 3, 3], + [2, 5, 3, 3], + [3, 6, 3, 3], + [2, 3, 3, 3], + [1, 4, 3, 3]], dtype=np.float64) + b = np.array([[3, 1, 5, 3], + [4, 6, 5, 3], + [4, 3, 5, 3], + [1, 5, 5, 3], + [5, 5, 5, 3], + [2, 3, 5, 3], + [8, 2, 5, 3], + [2, 2, 5, 3]], dtype=np.float64) + c = np.array([[4, 3, 4, 3], + [4, 2, 4, 3], + [5, 4, 4, 3], + [5, 4, 4, 3]], dtype=np.float64) + + if axis in [-1, 1]: + a = a.T + b = b.T + c = c.T + take_axis = 0 + else: + take_axis = 1 + + f, p = stats.f_oneway(xp.asarray(a), xp.asarray(b), xp.asarray(c), + axis=axis) + + # Verify that the result computed with the 2d arrays matches + # the result of calling f_oneway individually on each slice. + for j in [0, 1]: + fj, pj = stats.f_oneway(np.take(a, j, take_axis), + np.take(b, j, take_axis), + np.take(c, j, take_axis)) + xp_assert_close(f[j], xp.asarray(fj)) + xp_assert_close(p[j], xp.asarray(pj)) + for j in [2, 3]: + fj, pj = stats.f_oneway(np.take(a, j, take_axis), + np.take(b, j, take_axis), + np.take(c, j, take_axis)) + xp_assert_close(f[j], xp.asarray(fj)) + xp_assert_close(p[j], xp.asarray(pj)) + + @skip_xp_backends('dask.array', reason='needs _axis_nan_policy') + def test_3d_inputs(self, xp): + # Some 3-d arrays. (There is nothing special about the values.) + a = xp.reshape(1/xp.arange(1.0, 4*5*7 + 1., dtype=xp.float64), (4, 5, 7)) + b = xp.reshape(2/xp.arange(1.0, 4*8*7 + 1., dtype=xp.float64), (4, 8, 7)) + c = xp.reshape(1/xp.arange(1.0, 4*4*7 + 1., dtype=xp.float64), (4, 4, 7)) + c = xp.cos(c) + + f, p = stats.f_oneway(a, b, c, axis=1) + + assert f.shape == (4, 7) + assert p.shape == (4, 7) + + for i in range(a.shape[0]): + for j in range(a.shape[2]): + fij, pij = stats.f_oneway(a[i, :, j], b[i, :, j], c[i, :, j]) + xp_assert_close(fij, f[i, j]) + xp_assert_close(pij, p[i, j]) + + def test_length0_1d_error(self, xp): + # Require at least one value in each group. + with eager_warns(SmallSampleWarning, match=too_small_1d_not_omit, xp=xp): + result = stats.f_oneway(xp.asarray([1., 2., 3.]), xp.asarray([]), + xp.asarray([4., 5., 6., 7.])) + xp_assert_equal(result.statistic, xp.asarray(xp.nan)) + xp_assert_equal(result.pvalue, xp.asarray(xp.nan)) + + @skip_xp_backends('dask.array', reason='needs _axis_nan_policy') + def test_length0_2d_error(self, xp): + with eager_warns(SmallSampleWarning, match=too_small_nd_not_omit, xp=xp): + ncols = 3 + a = xp.ones((4, ncols)) + b = xp.ones((0, ncols)) + c = xp.ones((5, ncols)) + f, p = stats.f_oneway(a, b, c) + nans = xp.full((ncols,), fill_value=xp.nan) + xp_assert_equal(f, nans) + xp_assert_equal(p, nans) + + def test_all_length_one(self, xp): + samples = xp.asarray([10]), xp.asarray([11]), xp.asarray([12]), xp.asarray([13]) + with eager_warns(SmallSampleWarning, xp=xp): + result = stats.f_oneway(*samples) + xp_assert_equal(result.statistic, xp.asarray(xp.nan)) + xp_assert_equal(result.pvalue, xp.asarray(xp.nan)) + + @skip_xp_backends('dask.array', reason='lazy->reduced input validation') + @pytest.mark.parametrize('args', [(), ([1, 2, 3],)]) + def test_too_few_inputs(self, args, xp): + args = [xp.asarray(arg) for arg in args] + message = "At least two samples are required..." + with pytest.raises(TypeError, match=message): + stats.f_oneway(*args) + + @skip_xp_backends('dask.array', reason='lazy->reduced input validation') + def test_axis_error(self, xp): + a = xp.ones((3, 4)) + b = xp.ones((5, 4)) + with pytest.raises(AxisError): + stats.f_oneway(a, b, axis=2) + + def test_bad_shapes(self, xp): + a = xp.ones((3, 4)) + b = xp.ones((5, 4)) + with pytest.raises(ValueError): + stats.f_oneway(a, b, axis=1) + + +@make_xp_test_case(stats.kruskal) +class TestKruskal: + def test_array_like(self): + h, p = stats.kruskal([1], [2]) + assert_equal(h, 1.0) + assert_allclose(p, stats.chi2.sf(h, 1)) + + def test_simple(self, xp): + x = xp.asarray([1]) + y = xp.asarray([2]) + h, p = stats.kruskal(x, y) + xp_assert_close(h, xp.asarray(1.0)) + dtype = xp_default_dtype(xp) + xp_assert_close(p, xp.asarray(stats.chi2.sf(1, 1), dtype=dtype)) + + @pytest.mark.parametrize("dtype", ['float32', 'float64', None]) + def test_basic(self, xp, dtype): + dtype = xp_default_dtype(xp) if dtype is None else getattr(xp, dtype) + x = xp.asarray([1, 3, 5, 7, 9], dtype=dtype) + y = xp.asarray([2, 4, 6, 8, 10], dtype=dtype) + h, p = stats.kruskal(x, y) + xp_assert_close(h, xp.asarray(3/11, dtype=dtype)) + xp_assert_close(p, xp.asarray(stats.chi2.sf(3/11, 1), dtype=dtype)) + + def test_simple_tie(self, xp): + x = [1] + y = [1, 2] + h, p = stats.kruskal(xp.asarray(x), xp.asarray(y)) + + h_uncorr = 1.5**2 + 2*2.25**2 - 12 + corr = 0.75 + expected = xp.asarray(h_uncorr / corr) # 0.5 + + # Since the expression is simple and the exact answer is 0.5, it + # should be safe to use assert_equal(). + xp_assert_equal(h, expected) + xp_assert_close(p, special.chdtrc(xp.asarray(1.), expected)) + + def test_another_tie(self, xp): + x = [1, 1, 1, 2] + y = [2, 2, 2, 2] + h, p = stats.kruskal(xp.asarray(x), xp.asarray(y)) + + h_uncorr = (12. / 8. / 9.) * 4 * (3**2 + 6**2) - 3 * 9 + corr = 1 - float(3**3 - 3 + 5**3 - 5) / (8**3 - 8) + expected = xp.asarray(h_uncorr / corr) + + xp_assert_close(h, expected) + xp_assert_close(p, special.chdtrc(xp.asarray(1.), expected)) + + def test_three_groups(self, xp): + # A test of stats.kruskal with three groups, with ties. + x = [1, 1, 1] + y = [2, 2, 2] + z = [2, 2] + h, p = stats.kruskal(xp.asarray(x), xp.asarray(y), xp.asarray(z)) + + h_uncorr = (12. / 8. / 9.) * (3*2**2 + 3*6**2 + 2*6**2) - 3 * 9 # 5.0 + corr = 1 - float(3**3 - 3 + 5**3 - 5) / (8**3 - 8) + expected = xp.asarray(h_uncorr / corr) # 7.0 + + xp_assert_close(h, expected) + xp_assert_close(p, special.chdtrc(xp.asarray(2.), expected)) + + def test_empty(self, xp): + # A test of stats.kruskal with three groups, with ties. + x = xp.asarray([1, 1, 1]) + y = xp.asarray([2, 2, 2]) + z = xp.asarray([], dtype=y.dtype) + with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit): + res = stats.kruskal(x, y, z) + xp_assert_equal(res.statistic, xp.asarray(xp.nan)) + xp_assert_equal(res.pvalue, xp.asarray(xp.nan)) + + @skip_xp_backends('jax.numpy', reason='needs jax-ml/jax#34628') + def test_nan_policy_propagate(self, xp): + x = xp.arange(10.) + x = xpx.at(x)[9].set(xp.nan) + + res = stats.kruskal(x, x) + xp_assert_equal(res.statistic, xp.asarray(xp.nan)) + xp_assert_equal(res.pvalue, xp.asarray(xp.nan)) + + @skip_xp_backends('jax.numpy', reason='lazy -> reduced nan_policy capabilities') + @xfail_xp_backends('cupy', reason='cupy returns small but nonzero res.statistic') + def test_nan_policy_omit_raise(self, xp): + x = xp.arange(10.) + x = xpx.at(x)[9].set(xp.nan) + + res = stats.kruskal(x, x, nan_policy='omit') + xp_assert_equal(res.statistic, xp.asarray(0.0)) + xp_assert_equal(res.pvalue, xp.asarray(1.0)) + + with pytest.raises(ValueError, match='The input contains nan values'): + stats.kruskal(x, x, nan_policy='raise') + + with pytest.raises(ValueError, match='nan_policy must be one of...'): + stats.kruskal(x, x, nan_policy='foobar') + + def test_large_samples(self, xp): + # Test to see if large samples are handled correctly. + n = 50000 + rng = np.random.default_rng(1808365978) + x = xp.asarray(rng.standard_normal(n)) + y = xp.asarray(rng.standard_normal(n) + 50) + h, p = stats.kruskal(x, y) + xp_assert_close(p, xp.asarray(0., dtype=x.dtype)) + + def test_no_args_gh20661(self, xp): + message = r"Need at least two groups in stats.kruskal\(\)" + with pytest.raises(ValueError, match=message): + stats.kruskal() + + +@make_xp_test_case(stats.combine_pvalues) +class TestCombinePvalues: + # Reference values computed using the following R code: + # options(digits=16) + # library(metap) + # x = c(0.01, 0.2, 0.3) + # sumlog(x) # fisher + # sumz(x) # stouffer + # sumlog(1-x) # pearson (negative statistic and complement of p-value) + # minimump(x) # tippett + @pytest.mark.parametrize( + "method, expected_statistic, expected_pvalue", + [("fisher", 14.83716180549625 , 0.02156175132483465), + ("stouffer", 2.131790594240385, 0.01651203260896294), + ("pearson", -1.179737662212887, 1-0.9778736999143087), + ("tippett", 0.01, 0.02970100000000002), + # mudholkar_george: library(transite); p_combine(x, method="MG") + ("mudholkar_george", 6.828712071641684, 0.01654551838539527)]) + def test_reference_values(self, xp, method, expected_statistic, expected_pvalue): + x = [.01, .2, .3] + res = stats.combine_pvalues(xp.asarray(x), method=method) + xp_assert_close(res.statistic, xp.asarray(expected_statistic)) + xp_assert_close(res.pvalue, xp.asarray(expected_pvalue)) + + @pytest.mark.parametrize( + # Reference values computed using R `metap` `sumz`: + # options(digits=16) + # library(metap) + # x = c(0.01, 0.2, 0.3) + # sumz(x, weights=c(1., 1., 1.)) + # sumz(x, weights=c(1., 4., 9.)) + "weights, expected_statistic, expected_pvalue", + [([1., 1., 1.], 2.131790594240385, 0.01651203260896294), + ([1., 4., 9.], 1.051815015753598, 0.1464422142261314)]) + def test_weighted_stouffer(self, xp, weights, expected_statistic, expected_pvalue): + x = xp.asarray([.01, .2, .3]) + res = stats.combine_pvalues(x, method='stouffer', weights=xp.asarray(weights)) + xp_assert_close(res.statistic, xp.asarray(expected_statistic)) + xp_assert_close(res.pvalue, xp.asarray(expected_pvalue)) + + methods = ["fisher", "pearson", "tippett", "stouffer", "mudholkar_george"] + + @pytest.mark.parametrize("variant", ["single", "all", "random"]) + @pytest.mark.parametrize("method", methods) + def test_monotonicity(self, variant, method, xp): + # Test that result increases monotonically with respect to input. + m, n = 10, 7 + rng = np.random.default_rng(278448169958891062669391462690811630763) + + # `pvaluess` is an m × n array of p values. Each row corresponds to + # a set of p values to be combined with p values increasing + # monotonically down one column (single), simultaneously down each + # column (all), or independently down each column (random). + if variant == "single": + pvaluess = xp.broadcast_to(xp.asarray(rng.random(n)), (m, n)) + pvaluess = xp.concat([xp.reshape(xp.linspace(0.1, 0.9, m), (-1, 1)), + pvaluess[:, 1:]], axis=1) + elif variant == "all": + pvaluess = xp.broadcast_to(xp.linspace(0.1, 0.9, m), (n, m)).T + elif variant == "random": + pvaluess = xp.sort(xp.asarray(rng.uniform(0, 1, size=(m, n))), axis=0) + + combined_pvalues = xp.asarray([ + float(stats.combine_pvalues(pvaluess[i, :], method=method)[1]) + for i in range(pvaluess.shape[0]) + ]) + assert xp.all(combined_pvalues[1:] - combined_pvalues[:-1] >= 0) + + @pytest.mark.parametrize("method", methods) + def test_result(self, method, xp): + res = stats.combine_pvalues(xp.asarray([.01, .2, .3]), method=method) + xp_assert_equal(res.statistic, res[0]) + xp_assert_equal(res.pvalue, res[1]) + + @pytest.mark.parametrize("method", methods) + @pytest.mark.parametrize("axis", [0, 1, None]) + def test_axis(self, method, axis, xp): + rng = np.random.default_rng(234892349810482) + x = xp.asarray(rng.random(size=(2, 10))) + x = x.T if (axis == 0) else x + res = stats.combine_pvalues(x, axis=axis, method=method) + + if axis is None: + x = xp.reshape(x, (-1,)) + ref = stats.combine_pvalues(x, method=method) + xp_assert_close(res.statistic, ref.statistic) + xp_assert_close(res.pvalue, ref.pvalue) + return + + x = x.T if (axis == 0) else x + x0, x1 = x[0, :], x[1, :] + ref0 = stats.combine_pvalues(x0, method=method) + ref1 = stats.combine_pvalues(x1, method=method) + + xp_assert_close(res.statistic[0], ref0.statistic) + xp_assert_close(res.statistic[1], ref1.statistic) + xp_assert_close(res.pvalue[0], ref0.pvalue) + xp_assert_close(res.pvalue[1], ref1.pvalue) + + +class TestCdfDistanceValidation: + """ + Test that _cdf_distance() (via wasserstein_distance()) raises ValueErrors + for bad inputs. + """ + + def test_distinct_value_and_weight_lengths(self): + # When the number of weights does not match the number of values, + # a ValueError should be raised. + assert_raises(ValueError, stats.wasserstein_distance, + [1], [2], [4], [3, 1]) + assert_raises(ValueError, stats.wasserstein_distance, [1], [2], [1, 0]) + + def test_zero_weight(self): + # When a distribution is given zero weight, a ValueError should be + # raised. + assert_raises(ValueError, stats.wasserstein_distance, + [0, 1], [2], [0, 0]) + assert_raises(ValueError, stats.wasserstein_distance, + [0, 1], [2], [3, 1], [0]) + + def test_negative_weights(self): + # A ValueError should be raised if there are any negative weights. + assert_raises(ValueError, stats.wasserstein_distance, + [0, 1], [2, 2], [1, 1], [3, -1]) + + def test_empty_distribution(self): + # A ValueError should be raised when trying to measure the distance + # between something and nothing. + assert_raises(ValueError, stats.wasserstein_distance, [], [2, 2]) + assert_raises(ValueError, stats.wasserstein_distance, [1], []) + + def test_inf_weight(self): + # An inf weight is not valid. + assert_raises(ValueError, stats.wasserstein_distance, + [1, 2, 1], [1, 1], [1, np.inf, 1], [1, 1]) + + +class TestWassersteinDistanceND: + """ Tests for wasserstein_distance_nd() output values. + """ + + def test_published_values(self): + # Compare against published values and manually computed results. + # The values and computed result are posted at James D. McCaffrey's blog, + # https://jamesmccaffrey.wordpress.com/2018/03/05/earth-mover-distance + # -wasserstein-metric-example-calculation/ + u = [(1,1), (1,1), (1,1), (1,1), (1,1), (1,1), (1,1), (1,1), (1,1), (1,1), + (4,2), (6,1), (6,1)] + v = [(2,1), (2,1), (3,2), (3,2), (3,2), (5,1), (5,1), (5,1), (5,1), (5,1), + (5,1), (5,1), (7,1)] + + res = stats.wasserstein_distance_nd(u, v) + # In original post, the author kept two decimal places for ease of calculation. + # This test uses the more precise value of distance to get the precise results. + # For comparison, please see the table and figure in the original blog post. + flow = np.array([2., 3., 5., 1., 1., 1.]) + dist = np.array([1.00, 5**0.5, 4.00, 2**0.5, 1.00, 1.00]) + ref = np.sum(flow * dist)/np.sum(flow) + assert_allclose(res, ref) + + @pytest.mark.parametrize('n_value', (4, 15, 35)) + @pytest.mark.parametrize('ndim', (3, 4, 7)) + @pytest.mark.parametrize('max_repeats', (5, 10)) + def test_same_distribution_nD(self, ndim, n_value, max_repeats): + # Any distribution moved to itself should have a Wasserstein distance + # of zero. + rng = np.random.default_rng(363836384995579937222333) + repeats = rng.integers(1, max_repeats, size=n_value, dtype=int) + + u_values = rng.random(size=(n_value, ndim)) + v_values = np.repeat(u_values, repeats, axis=0) + v_weights = rng.random(np.sum(repeats)) + range_repeat = np.repeat(np.arange(len(repeats)), repeats) + u_weights = np.bincount(range_repeat, weights=v_weights) + index = rng.permutation(len(v_weights)) + v_values, v_weights = v_values[index], v_weights[index] + + res = stats.wasserstein_distance_nd(u_values, v_values, u_weights, v_weights) + assert_allclose(res, 0, atol=1e-15) + + @pytest.mark.parametrize('nu', (8, 9, 38)) + @pytest.mark.parametrize('nv', (8, 12, 17)) + @pytest.mark.parametrize('ndim', (3, 5, 23)) + def test_collapse_nD(self, nu, nv, ndim): + # test collapse for n dimensional values + # Collapsing a n-D distribution to a point distribution at zero + # is equivalent to taking the average of the norm of data. + rng = np.random.default_rng(38573488467338826109) + u_values = rng.random(size=(nu, ndim)) + v_values = np.zeros((nv, ndim)) + u_weights = rng.random(size=nu) + v_weights = rng.random(size=nv) + ref = np.average(np.linalg.norm(u_values, axis=1), weights=u_weights) + res = stats.wasserstein_distance_nd(u_values, v_values, u_weights, v_weights) + assert_allclose(res, ref) + + @pytest.mark.parametrize('nu', (8, 16, 32)) + @pytest.mark.parametrize('nv', (8, 16, 32)) + @pytest.mark.parametrize('ndim', (1, 2, 6)) + def test_zero_weight_nD(self, nu, nv, ndim): + # Values with zero weight have no impact on the Wasserstein distance. + rng = np.random.default_rng(38573488467338826109) + u_values = rng.random(size=(nu, ndim)) + v_values = rng.random(size=(nv, ndim)) + u_weights = rng.random(size=nu) + v_weights = rng.random(size=nv) + ref = stats.wasserstein_distance_nd(u_values, v_values, u_weights, v_weights) + + add_row, nrows = rng.integers(0, nu, size=2) + add_value = rng.random(size=(nrows, ndim)) + u_values = np.insert(u_values, add_row, add_value, axis=0) + u_weights = np.insert(u_weights, add_row, np.zeros(nrows), axis=0) + res = stats.wasserstein_distance_nd(u_values, v_values, u_weights, v_weights) + assert_allclose(res, ref) + + def test_inf_values(self): + # Inf values can lead to an inf distance or trigger a RuntimeWarning + # (and return NaN) if the distance is undefined. + uv, vv, uw = [[1, 1], [2, 1]], [[np.inf, -np.inf]], [1, 1] + distance = stats.wasserstein_distance_nd(uv, vv, uw) + assert_equal(distance, np.inf) + with np.errstate(invalid='ignore'): + uv, vv = [[np.inf, np.inf]], [[np.inf, -np.inf]] + distance = stats.wasserstein_distance_nd(uv, vv) + assert_equal(distance, np.nan) + + @pytest.mark.parametrize('nu', (10, 15, 20)) + @pytest.mark.parametrize('nv', (10, 15, 20)) + @pytest.mark.parametrize('ndim', (1, 3, 5)) + def test_multi_dim_nD(self, nu, nv, ndim): + # Adding dimension on distributions do not affect the result + rng = np.random.default_rng(2736495738494849509) + u_values = rng.random(size=(nu, ndim)) + v_values = rng.random(size=(nv, ndim)) + u_weights = rng.random(size=nu) + v_weights = rng.random(size=nv) + ref = stats.wasserstein_distance_nd(u_values, v_values, u_weights, v_weights) + + add_dim = rng.integers(0, ndim) + add_value = rng.random() + + u_values = np.insert(u_values, add_dim, add_value, axis=1) + v_values = np.insert(v_values, add_dim, add_value, axis=1) + res = stats.wasserstein_distance_nd(u_values, v_values, u_weights, v_weights) + assert_allclose(res, ref) + + @pytest.mark.parametrize('nu', (7, 13, 19)) + @pytest.mark.parametrize('nv', (7, 13, 19)) + @pytest.mark.parametrize('ndim', (2, 4, 7)) + def test_orthogonal_nD(self, nu, nv, ndim): + # orthogonal transformations do not affect the result of the + # wasserstein_distance + rng = np.random.default_rng(34746837464536) + u_values = rng.random(size=(nu, ndim)) + v_values = rng.random(size=(nv, ndim)) + u_weights = rng.random(size=nu) + v_weights = rng.random(size=nv) + ref = stats.wasserstein_distance_nd(u_values, v_values, u_weights, v_weights) + + dist = stats.ortho_group(ndim) + transform = dist.rvs(random_state=rng) + shift = rng.random(size=ndim) + res = stats.wasserstein_distance_nd(u_values @ transform + shift, + v_values @ transform + shift, + u_weights, v_weights) + assert_allclose(res, ref) + + def test_error_code(self): + rng = np.random.default_rng(52473644737485644836320101) + with pytest.raises(ValueError, match='Invalid input values. The inputs'): + u_values = rng.random(size=(4, 10, 15)) + v_values = rng.random(size=(6, 2, 7)) + _ = stats.wasserstein_distance_nd(u_values, v_values) + with pytest.raises(ValueError, match='Invalid input values. Dimensions'): + u_values = rng.random(size=(15,)) + v_values = rng.random(size=(3, 15)) + _ = stats.wasserstein_distance_nd(u_values, v_values) + with pytest.raises(ValueError, + match='Invalid input values. If two-dimensional'): + u_values = rng.random(size=(2, 10)) + v_values = rng.random(size=(2, 2)) + _ = stats.wasserstein_distance_nd(u_values, v_values) + + @pytest.mark.parametrize('u_size', [1, 10, 50]) + @pytest.mark.parametrize('v_size', [1, 10, 50]) + def test_optimization_vs_analytical(self, u_size, v_size): + rng = np.random.default_rng(45634745675) + # Test when u_weights = None, v_weights = None + u_values = rng.random(size=(u_size, 1)) + v_values = rng.random(size=(v_size, 1)) + u_values_flat = u_values.ravel() + v_values_flat = v_values.ravel() + # These three calculations are done using different backends + # but they must be equal + d1 = stats.wasserstein_distance(u_values_flat, v_values_flat) + d2 = stats.wasserstein_distance_nd(u_values, v_values) + d3 = stats.wasserstein_distance_nd(u_values_flat, v_values_flat) + assert_allclose(d2, d1) + assert_allclose(d3, d1) + # Test with u_weights and v_weights specified. + u_weights = rng.random(size=u_size) + v_weights = rng.random(size=v_size) + d1 = stats.wasserstein_distance(u_values_flat, v_values_flat, + u_weights, v_weights) + d2 = stats.wasserstein_distance_nd(u_values, v_values, + u_weights, v_weights) + d3 = stats.wasserstein_distance_nd(u_values_flat, v_values_flat, + u_weights, v_weights) + assert_allclose(d2, d1) + assert_allclose(d3, d1) + + +class TestWassersteinDistance: + """ Tests for wasserstein_distance() output values. + """ + + def test_simple(self): + # For basic distributions, the value of the Wasserstein distance is + # straightforward. + assert_allclose( + stats.wasserstein_distance([0, 1], [0], [1, 1], [1]), + .5) + assert_allclose(stats.wasserstein_distance( + [0, 1], [0], [3, 1], [1]), + .25) + assert_allclose(stats.wasserstein_distance( + [0, 2], [0], [1, 1], [1]), + 1) + assert_allclose(stats.wasserstein_distance( + [0, 1, 2], [1, 2, 3]), + 1) + + def test_same_distribution(self): + # Any distribution moved to itself should have a Wasserstein distance + # of zero. + assert_equal(stats.wasserstein_distance([1, 2, 3], [2, 1, 3]), 0) + assert_equal( + stats.wasserstein_distance([1, 1, 1, 4], [4, 1], + [1, 1, 1, 1], [1, 3]), + 0) + + def test_shift(self): + # If the whole distribution is shifted by x, then the Wasserstein + # distance should be the norm of x. + assert_allclose(stats.wasserstein_distance([0], [1]), 1) + assert_allclose(stats.wasserstein_distance([-5], [5]), 10) + assert_allclose( + stats.wasserstein_distance([1, 2, 3, 4, 5], [11, 12, 13, 14, 15]), + 10) + assert_allclose( + stats.wasserstein_distance([4.5, 6.7, 2.1], [4.6, 7, 9.2], + [3, 1, 1], [1, 3, 1]), + 2.5) + + def test_combine_weights(self): + # Assigning a weight w to a value is equivalent to including that value + # w times in the value array with weight of 1. + assert_allclose( + stats.wasserstein_distance( + [0, 0, 1, 1, 1, 1, 5], [0, 3, 3, 3, 3, 4, 4], + [1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1]), + stats.wasserstein_distance([5, 0, 1], [0, 4, 3], + [1, 2, 4], [1, 2, 4])) + + def test_collapse(self): + # Collapsing a distribution to a point distribution at zero is + # equivalent to taking the average of the absolute values of the + # values. + u = np.arange(-10, 30, 0.3) + v = np.zeros_like(u) + assert_allclose( + stats.wasserstein_distance(u, v), + np.mean(np.abs(u))) + + u_weights = np.arange(len(u)) + v_weights = u_weights[::-1] + assert_allclose( + stats.wasserstein_distance(u, v, u_weights, v_weights), + np.average(np.abs(u), weights=u_weights)) + + def test_zero_weight(self): + # Values with zero weight have no impact on the Wasserstein distance. + assert_allclose( + stats.wasserstein_distance([1, 2, 100000], [1, 1], + [1, 1, 0], [1, 1]), + stats.wasserstein_distance([1, 2], [1, 1], [1, 1], [1, 1])) + + def test_inf_values(self): + # Inf values can lead to an inf distance or trigger a RuntimeWarning + # (and return NaN) if the distance is undefined. + assert_equal( + stats.wasserstein_distance([1, 2, np.inf], [1, 1]), + np.inf) + assert_equal( + stats.wasserstein_distance([1, 2, np.inf], [-np.inf, 1]), + np.inf) + assert_equal( + stats.wasserstein_distance([1, -np.inf, np.inf], [1, 1]), + np.inf) + with pytest.warns(RuntimeWarning, match="invalid value"): + assert_equal( + stats.wasserstein_distance([1, 2, np.inf], [np.inf, 1]), + np.nan) + + +class TestEnergyDistance: + """ Tests for energy_distance() output values. + """ + + def test_simple(self): + # For basic distributions, the value of the energy distance is + # straightforward. + assert_almost_equal( + stats.energy_distance([0, 1], [0], [1, 1], [1]), + np.sqrt(2) * .5) + assert_almost_equal(stats.energy_distance( + [0, 1], [0], [3, 1], [1]), + np.sqrt(2) * .25) + assert_almost_equal(stats.energy_distance( + [0, 2], [0], [1, 1], [1]), + 2 * .5) + assert_almost_equal( + stats.energy_distance([0, 1, 2], [1, 2, 3]), + np.sqrt(2) * (3*(1./3**2))**.5) + + def test_same_distribution(self): + # Any distribution moved to itself should have a energy distance of + # zero. + assert_equal(stats.energy_distance([1, 2, 3], [2, 1, 3]), 0) + assert_equal( + stats.energy_distance([1, 1, 1, 4], [4, 1], [1, 1, 1, 1], [1, 3]), + 0) + + def test_shift(self): + # If a single-point distribution is shifted by x, then the energy + # distance should be sqrt(2) * sqrt(x). + assert_almost_equal(stats.energy_distance([0], [1]), np.sqrt(2)) + assert_almost_equal( + stats.energy_distance([-5], [5]), + np.sqrt(2) * 10**.5) + + def test_combine_weights(self): + # Assigning a weight w to a value is equivalent to including that value + # w times in the value array with weight of 1. + assert_almost_equal( + stats.energy_distance([0, 0, 1, 1, 1, 1, 5], [0, 3, 3, 3, 3, 4, 4], + [1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1]), + stats.energy_distance([5, 0, 1], [0, 4, 3], [1, 2, 4], [1, 2, 4])) + + def test_zero_weight(self): + # Values with zero weight have no impact on the energy distance. + assert_almost_equal( + stats.energy_distance([1, 2, 100000], [1, 1], [1, 1, 0], [1, 1]), + stats.energy_distance([1, 2], [1, 1], [1, 1], [1, 1])) + + def test_inf_values(self): + # Inf values can lead to an inf distance or trigger a RuntimeWarning + # (and return NaN) if the distance is undefined. + assert_equal(stats.energy_distance([1, 2, np.inf], [1, 1]), np.inf) + assert_equal( + stats.energy_distance([1, 2, np.inf], [-np.inf, 1]), + np.inf) + assert_equal( + stats.energy_distance([1, -np.inf, np.inf], [1, 1]), + np.inf) + with pytest.warns(RuntimeWarning, match="invalid value"): + assert_equal( + stats.energy_distance([1, 2, np.inf], [np.inf, 1]), + np.nan) + + +@make_xp_test_case(stats.brunnermunzel) +class TestBrunnerMunzel: + # Data from (Lumley, 1996) + X = [1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 4, 1, 1] + Y = [3, 3, 4, 3, 1, 2, 3, 1, 1, 5, 4] + + def test_brunnermunzel_one_sided(self, xp): + # Results are compared with R's lawstat package. + X, Y = xp.asarray(self.X), xp.asarray(self.Y) + u1, p1 = stats.brunnermunzel(X, Y, alternative='less') + u2, p2 = stats.brunnermunzel(Y, X, alternative='greater') + u3, p3 = stats.brunnermunzel(X, Y, alternative='greater') + u4, p4 = stats.brunnermunzel(Y, X, alternative='less') + + xp_assert_close(p1, p2) + xp_assert_close(p3, p4) + assert p1 != p3 + xp_assert_close(u1, xp.asarray(3.1374674823029505)) + xp_assert_close(u2, xp.asarray(-3.1374674823029505)) + xp_assert_close(u3, xp.asarray(3.1374674823029505)) + xp_assert_close(u4, xp.asarray(-3.1374674823029505)) + + xp_assert_close(p1, xp.asarray(0.0028931043330757342)) + xp_assert_close(p3, xp.asarray(0.99710689566692423)) + + def test_brunnermunzel_two_sided(self, xp): + # Results are compared with R's lawstat package. + X, Y = xp.asarray(self.X), xp.asarray(self.Y) + u1, p1 = stats.brunnermunzel(X, Y, alternative='two-sided') + u2, p2 = stats.brunnermunzel(Y, X, alternative='two-sided') + + xp_assert_close(p1, xp.asarray(p2)) + xp_assert_close(u1, xp.asarray(3.1374674823029505)) + xp_assert_close(u2, xp.asarray(-3.1374674823029505)) + xp_assert_close(p1, xp.asarray(0.0057862086661515377)) + + def test_brunnermunzel_default(self, xp): + # The default value for alternative is two-sided + X, Y = xp.asarray(self.X), xp.asarray(self.Y) + u1, p1 = stats.brunnermunzel(X, Y) + u2, p2 = stats.brunnermunzel(Y, X) + + xp_assert_close(p1, p2) + xp_assert_close(u1, xp.asarray(3.1374674823029505)) + xp_assert_close(u2, xp.asarray(-3.1374674823029505)) + xp_assert_close(p1, xp.asarray(0.0057862086661515377)) + + def test_brunnermunzel_alternative_error(self, xp): + alternative = "error" + distribution = "t" + nan_policy = "propagate" + assert alternative not in ["two-sided", "greater", "less"] + message = "`alternative` must be 'less', 'greater', or 'two-sided'." + with pytest.raises(ValueError, match=message): + stats.brunnermunzel(xp.asarray(self.X), xp.asarray(self.Y), + alternative, distribution, nan_policy) + + def test_brunnermunzel_distribution_norm(self, xp): + X, Y = xp.asarray(self.X), xp.asarray(self.Y) + u1, p1 = stats.brunnermunzel(X, Y, distribution="normal") + u2, p2 = stats.brunnermunzel(Y, X, distribution="normal") + xp_assert_close(p1, xp.asarray(p2)) + xp_assert_close(u1, xp.asarray(3.1374674823029505)) + xp_assert_close(u2, xp.asarray(-3.1374674823029505)) + xp_assert_close(p1, xp.asarray(0.0017041417600383024)) + + def test_brunnermunzel_distribution_error(self, xp): + alternative = "two-sided" + distribution = "error" + nan_policy = "propagate" + assert distribution not in ["t", "normal"] + message = "distribution should be 't' or 'normal'" + with pytest.raises(ValueError, match=message): + stats.brunnermunzel(xp.asarray(self.X), xp.asarray(self.Y), + alternative, distribution, nan_policy) + + @pytest.mark.parametrize("kwarg_update", [{'y': []}, {'x': []}, + {'x': [], 'y': []}]) + def test_brunnermunzel_empty_imput(self, kwarg_update, xp): + kwargs = {'x': self.X, 'y': self.Y} + kwargs.update(kwarg_update) + kwargs = {key:xp.asarray(val, dtype=xp_default_dtype(xp)) + for key, val in kwargs.items()} + with eager_warns(SmallSampleWarning, match=too_small_1d_not_omit, xp=xp): + statistic, pvalue = stats.brunnermunzel(**kwargs) + xp_assert_equal(statistic, xp.asarray(xp.nan)) + xp_assert_equal(pvalue, xp.asarray(xp.nan)) + + @skip_xp_backends('jax.numpy', reason='needs jax-ml/jax#34628') + def test_brunnermunzel_nan_input_propagate(self, xp): + X = xp.asarray([1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 4, 1, 1, xp.nan]) + Y = xp.asarray([3, 3, 4, 3, 1, 2, 3, 1, 1, 5, 4.]) + u1, p1 = stats.brunnermunzel(X, Y, nan_policy="propagate") + u2, p2 = stats.brunnermunzel(Y, X, nan_policy="propagate") + + xp_assert_equal(u1, xp.asarray(xp.nan)) + xp_assert_equal(p1, xp.asarray(xp.nan)) + xp_assert_equal(u2, xp.asarray(xp.nan)) + xp_assert_equal(p2, xp.asarray(xp.nan)) + + @skip_xp_backends('jax.numpy', reason='lazy -> reduced nan_policy capabilities') + def test_brunnermunzel_nan_input_raise(self, xp): + X = xp.asarray([1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 4, 1, 1, xp.nan]) + Y = xp.asarray([3, 3, 4, 3, 1, 2, 3, 1, 1, 5, 4.]) + alternative = "two-sided" + distribution = "t" + nan_policy = "raise" + + message = "The input contains nan values" + with pytest.raises(ValueError, match=message): + stats.brunnermunzel(X, Y, alternative, distribution, nan_policy) + with pytest.raises(ValueError, match=message): + stats.brunnermunzel(Y, X, alternative, distribution, nan_policy) + + @skip_xp_backends('jax.numpy', reason='lazy -> reduced nan_policy capabilities') + def test_brunnermunzel_nan_input_omit(self, xp): + X = xp.asarray([1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 4, 1, 1, np.nan]) + Y = xp.asarray([3, 3, 4, 3, 1, 2, 3, 1, 1, 5, 4.]) + u1, p1 = stats.brunnermunzel(X, Y, nan_policy="omit") + u2, p2 = stats.brunnermunzel(Y, X, nan_policy="omit") + + xp_assert_close(p1, p2) + xp_assert_close(u1, xp.asarray(3.1374674823029505)) + xp_assert_close(u2, xp.asarray(-3.1374674823029505)) + xp_assert_close(p1, xp.asarray(0.0057862086661515377)) + + @pytest.mark.parametrize("vectorized_call", [False, True]) + def test_brunnermunzel_return_nan(self, vectorized_call, xp): + """ tests that a warning is emitted when p is nan + p-value with t-distributions can be nan (0/0) (see gh-15843) + """ + x = xp.asarray([1., 2., 3.]) + y = xp.asarray([5., 6., 7., 8., 9.]) + + if vectorized_call: + x = xp.stack((x, x)).T + y = xp.stack((y, y)).T + + msg = "p-value cannot be estimated|divide by zero|invalid value encountered" + with eager_warns(RuntimeWarning, match=msg, xp=xp): + stats.brunnermunzel(x, y, distribution="t") + + def test_brunnermunzel_normal_dist(self, xp): + """ tests that a p is 0 for datasets that cause p->nan + when t-distribution is used (see gh-15843) + """ + x = xp.asarray([1., 2., 3.]) + y = xp.asarray([5., 6., 7., 8., 9.]) + + with eager_warns(RuntimeWarning, match='divide by zero', xp=xp): + _, p = stats.brunnermunzel(x, y, distribution="normal") + xp_assert_equal(p, xp.asarray(0.)) + + +@_apply_over_batch(('x', 1), ('q', 1), ('p', 1)) +def quantile_test_reference(x, q, p, alternative): + res = stats.quantile_test(x, q=q, p=p, alternative=alternative) + return res.statistic, res.pvalue, *res.confidence_interval() + + +@make_xp_test_case(stats.quantile_test) +class TestQuantileTest: + r""" Test the non-parametric quantile test, + including the computation of confidence intervals + """ + + def test_quantile_test_iv(self, xp): + x = xp.asarray([1, 2, 3]) + + message = "`x` must be an array of numbers." + with pytest.raises(ValueError, match=message): + stats.quantile_test(xp.astype(x, xp.bool)) + + message = "`q` must be a scalar or array of numbers." + with pytest.raises(ValueError, match=message): + stats.quantile_test(x, q=False) + + message = "`p` must be a scalar or array of floats." + with pytest.raises(ValueError, match=message): + stats.quantile_test(x, p=2) + + # should p == 0. / p == 1. be valid? + p = xp.asarray([-1., 0., 1., 1.5, xp.nan]) + res = stats.quantile_test(x, p=p) + xp_assert_equal(res.statistic, xp.full_like(p, xp.nan)) + xp_assert_equal(res.statistic_type, xp.full_like(p, xp.nan)) + xp_assert_equal(res.pvalue, xp.full_like(p, xp.nan)) + + message = "`axis` must be an integer or None." + with pytest.raises(ValueError, match=message): + stats.quantile_test(x, axis=2.5) + + message = "`axis` is not compatible with the shapes of the inputs." + with pytest.raises(ValueError, match=message): + stats.quantile_test(x, axis=2) + + message = "If specified, `keepdims` must be True or False." + with pytest.raises(ValueError, match=message): + stats.quantile_test(x, keepdims=10) + + message = ("`keepdims` may be False only if...") + with pytest.raises(ValueError, match=message): + stats.quantile_test(x, p=xp.asarray([0.1, 0.2]), keepdims=False) + + message = "`alternative` must be one of..." + with pytest.raises(ValueError, match=message): + stats.quantile_test(x, alternative='one-sided') + + message = "`confidence_level` must be a number between 0 and 1." + with pytest.raises(ValueError, match=message): + stats.quantile_test(x).confidence_interval(1) + + @pytest.mark.parametrize( + 'p, alpha, lb, ub, alternative', + [[0.3, 0.95, 1.221402758160170, 1.476980793882643, 'two-sided'], + [0.5, 0.9, 1.506817785112854, 1.803988415397857, 'two-sided'], + [0.25, 0.95, -np.inf, 1.39096812846378, 'less'], + [0.8, 0.9, 2.117000016612675, np.inf, 'greater']] + ) + def test_R_ci_quantile(self, p, alpha, lb, ub, alternative, xp): + # Test against R library `confintr` function `ci_quantile`, e.g. + # library(confintr) + # options(digits=16) + # x <- exp(seq(0, 1, by = 0.01)) + # ci_quantile(x, q = 0.3)$interval + # ci_quantile(x, q = 0.5, probs = c(0.05, 0.95))$interval + # ci_quantile(x, q = 0.25, probs = c(0, 0.95))$interval + # ci_quantile(x, q = 0.8, probs = c(0.1, 1))$interval + x = xp.exp(xp.arange(0.0, 1.01, 0.01)) + res = stats.quantile_test(x, p=p, alternative=alternative) + ci = res.confidence_interval(alpha) + xp_assert_close(ci.low, xp.asarray(lb)) + xp_assert_close(ci.high, xp.asarray(ub)) + + @pytest.mark.parametrize( + 'q, p, alternative, ref', + [[1.2, 0.3, 'two-sided', 0.01515567517648], + [1.8, 0.5, 'two-sided', 0.1109183496606]] + ) + def test_R_pvalue(self, q, p, alternative, ref, xp): + # Test against R library `snpar` function `quant.test`, e.g. + # library(snpar) + # options(digits=16) + # x < - exp(seq(0, 1, by=0.01)) + # quant.test(x, q=1.2, p=0.3, exact=TRUE, alternative='t') + x = xp.exp(xp.arange(0.0, 1.01, 0.01)) + res = stats.quantile_test(x, q=q, p=p, alternative=alternative) + xp_assert_close(res.pvalue, xp.asarray(ref)) + + @pytest.mark.parametrize('case', ['continuous', 'discrete']) + @pytest.mark.parametrize('alternative', ['less', 'greater']) + @pytest.mark.parametrize('alpha', [0.9, 0.95]) + def test_pval_ci_match(self, case, alternative, alpha, xp): + # Verify that the following statement holds: + + # The 95% confidence interval corresponding with alternative='less' + # has -inf as its lower bound, and the upper bound `xu` is the greatest + # element from the sample `x` such that: + # `stats.quantile_test(x, q=xu, p=p, alternative='less').pvalue`` + # will be greater than 5%. + + # And the corresponding statement for the alternative='greater' case. + + seed = int((7**len(case) + len(alternative))*alpha) + rng = np.random.default_rng(seed) + if case == 'continuous': + p, q = rng.random(size=2) + rvs = rng.random(size=100) + else: + rvs = rng.integers(1, 11, size=100) + p = rng.random() + q = rng.integers(1, 11) + + rvs, p, q = xp.asarray(rvs.tolist()), float(p), float(q) + res = stats.quantile_test(rvs, q=q, p=p, alternative=alternative) + ci = res.confidence_interval(confidence_level=alpha) + + # select elements inside the confidence interval based on alternative + if alternative == 'less': + i_inside = xp.astype(rvs, ci.high.dtype) <= ci.high + else: + i_inside = xp.astype(rvs, ci.low.dtype) >= ci.low + + for x in rvs[i_inside]: + res = stats.quantile_test(rvs, q=x, p=p, alternative=alternative) + assert res.pvalue > 1 - alpha + + for x in rvs[~i_inside]: + res = stats.quantile_test(rvs, q=x, p=p, alternative=alternative) + assert res.pvalue < 1 - alpha + + @pytest.mark.parametrize('dtype', [None, 'float32', 'float64']) + def test_match_conover_examples(self, dtype, xp): + # Test against the examples in [1] (Conover Practical Nonparametric + # Statistics Third Edition) pg 139 + + # Example 1 + # Data is [189, 233, 195, 160, 212, 176, 231, 185, 199, 213, 202, 193, + # 174, 166, 248] + # Two-sided test of whether the upper quartile (p=0.75) equals 193 + # (q=193). Conover shows that 7 of the observations are less than or + # equal to 193, and "for the binomial random variable Y, P(Y<=7) = + # 0.0173", so the two-sided p-value is twice that, 0.0346. + dtype = xp_default_dtype(xp) if dtype is None else getattr(xp, dtype) + x = xp.asarray([189, 233, 195, 160, 212, 176, 231, 185, + 199, 213, 202, 193, 174, 166, 248], dtype=dtype) + pvalue_ref = 0.0346 + res = stats.quantile_test(x, q=193, p=0.75, alternative='two-sided') + xp_assert_close(res.pvalue, xp.asarray(pvalue_ref, dtype=dtype), rtol=1e-5) + + # Example 2 + # Conover doesn't give explicit data, just that 8 out of 112 + # observations are 60 or less. The test is whether the median time is + # equal to 60 against the alternative that the median is greater than + # 60. The p-value is calculated as P(Y<=8), where Y is again a binomial + # distributed random variable, now with p=0.5 and n=112. Conover uses a + # normal approximation, but we can easily calculate the CDF of the + # binomial distribution. + x = xp.asarray([59]*8 + [61]*(112-8), dtype=dtype) + pvalue_ref = float(stats.binom(p=0.5, n=112).pmf(k=8)) + res = stats.quantile_test(x, q=60, p=0.5, alternative='greater') + xp_assert_close(res.pvalue, xp.asarray(pvalue_ref, dtype=dtype), atol=1e-10) + + @skip_xp_backends('array_api_strict', reason='indexing in _apply_over_batch') + @pytest.mark.parametrize('alternative', ['less', 'greater', 'two-sided']) + @pytest.mark.parametrize('axis', [0, 1]) + def test_multidimensional(self, alternative, axis, xp): + rng = np.random.default_rng(85468924398205602) + x = rng.random(size=(2, 20)) + p = rng.random(size=(1, 3,)) + q = p + rng.random(size=(2, 1))*0.01 + + x, p, q = (xp.asarray(arr.tolist()) for arr in (x, p, q)) + + ref = quantile_test_reference(x, q, p, alternative=alternative) + ref_statistic, ref_pvalue, ref_low, ref_high = ref + + if axis == 0: + x, p, q = x.T, p.T, q.T + res = stats.quantile_test(x, q=q, p=p, alternative=alternative, axis=axis) + res_statistic, res_pvalue = res.statistic, res.pvalue + res_low, res_high = res.confidence_interval() + if axis == 0: + res_statistic, res_pvalue = res_statistic.T, res_pvalue.T + res_low, res_high = res_low.T, res_high.T + + xp_assert_close(res_statistic, ref_statistic) + xp_assert_close(res_pvalue, ref_pvalue) + xp_assert_close(res_low, res_low) + xp_assert_close(res_low, ref_low) + + def test_zero_size(self, xp): + rng = np.random.default_rng(883771738488451943) + x_shape = (2, 50) + qp_shape = (2, 10) + x = rng.random(x_shape) + q = rng.random(qp_shape) + p = q + rng.random(qp_shape) * 1e-2 + + x, p, q = (xp.asarray(arr.tolist()) for arr in (x, p, q)) + + # case 1: p/q is size zero. + qp_zero = xp.empty((0, *qp_shape)) + out = qp_zero + res = stats.quantile_test(x, q=qp_zero, p=qp_zero, axis=-1) + ci = res.confidence_interval() + # different default int on different platforms + xp_assert_equal(res.statistic, xp.asarray(out)) + xp_assert_equal(res.statistic_type, xp.asarray(out)) + xp_assert_equal(res.pvalue, out) + xp_assert_equal(ci.low, out) + xp_assert_equal(ci.high, out) + + # case 2: x is size zero with nonzero length along axis. + x_zero = xp.empty((0, *x_shape)) + out = xp.empty((0, *qp_shape)) + res = stats.quantile_test(x_zero, q=q, p=p, axis=-1) + ci = res.confidence_interval() + # different default int on different platforms + xp_assert_equal(res.statistic, xp.asarray(out)) + xp_assert_equal(res.statistic_type, xp.asarray(out)) + xp_assert_equal(res.pvalue, out) + xp_assert_equal(ci.low, out) + xp_assert_equal(ci.high, out) + + # case 3: x has zero length along axis. + x_zero = xp.empty((x.shape[0], 0)) + out = xp.full(qp_shape, xp.nan) + res = stats.quantile_test(x_zero, q=q, p=p, axis=-1) + ci = res.confidence_interval() + # different default int on different platforms + xp_assert_equal(res.statistic, out) + xp_assert_equal(res.statistic_type, out) + xp_assert_equal(res.pvalue, out) + xp_assert_equal(ci.low, out) + xp_assert_equal(ci.high, out) + + def test_nans(self, xp): + rng = np.random.default_rng(2920028761208905) + x = rng.random((10, 50)) + q = rng.random((10, 10)) + p = q + rng.random((10, 10)) * 1e-2 + x, p, q = (xp.asarray(arr.tolist()) for arr in (x, p, q)) + + ref = stats.quantile_test(x, q=q, p=p, axis=-1) + ref_low, ref_high = ref.confidence_interval() + + x_ = xp_copy(x) + i_nan = xp.asarray(rng.random(x.shape) < 0.01) + x_ = xpx.at(x_)[i_nan].set(xp.nan) + i_nan_out = xp.any(i_nan, axis=-1) + assert not xp.all(i_nan_out) + res = stats.quantile_test(x_, q=q, p=p, axis=-1) + res_low, res_high = res.confidence_interval() + + assert xp.all(xp.isnan(res.statistic[i_nan_out])) + assert xp.all(xp.isnan(res.statistic_type[i_nan_out])) + assert xp.all(xp.isnan(res.pvalue[i_nan_out])) + assert xp.all(xp.isnan(res_low[i_nan_out])) + assert xp.all(xp.isnan(res_high[i_nan_out])) + xp_assert_equal(res.statistic[~i_nan_out], ref.statistic[~i_nan_out]) + xp_assert_equal(res.statistic_type[~i_nan_out], ref.statistic_type[~i_nan_out]) + xp_assert_equal(res.pvalue[~i_nan_out], ref.pvalue[~i_nan_out]) + xp_assert_equal(res_low[~i_nan_out], ref_low[~i_nan_out]) + xp_assert_equal(res_high[~i_nan_out], ref_high[~i_nan_out]) + + i_nan_q = xp.asarray(rng.random(q.shape) < 0.01) + i_nan_p = xp.asarray(rng.random(p.shape) < 0.01) + assert xp.any(i_nan_q) + assert xp.any(i_nan_p) + q = xpx.at(q)[xp.broadcast_to(i_nan_q, q.shape)].set(xp.nan) + p = xpx.at(p)[xp.broadcast_to(i_nan_p, p.shape)].set(xp.nan) + + i_nan_out = xp.asarray(i_nan_q | i_nan_p) + assert not xp.all(i_nan_out) + + res = stats.quantile_test(x, q=q, p=p, axis=-1) + res_low, res_high = res.confidence_interval() + assert xp.all(xp.isnan(res.statistic[i_nan_out])) + assert xp.all(xp.isnan(res.statistic_type[i_nan_out])) + assert xp.all(xp.isnan(res.pvalue[i_nan_out])) + assert xp.all(xp.isnan(res_low[i_nan_out])) + assert xp.all(xp.isnan(res_high[i_nan_out])) + xp_assert_equal(res.statistic[~i_nan_out], ref.statistic[~i_nan_out]) + xp_assert_equal(res.statistic_type[~i_nan_out], ref.statistic_type[~i_nan_out]) + xp_assert_equal(res.pvalue[~i_nan_out], ref.pvalue[~i_nan_out]) + xp_assert_equal(res_low[~i_nan_out], ref_low[~i_nan_out]) + xp_assert_equal(res_high[~i_nan_out], ref_high[~i_nan_out]) + + def test_axis_none(self, xp): + rng = np.random.default_rng(883771738488451943) + x_shape = (2, 50) + qp_shape = (2, 10) + x = rng.random(x_shape) + q = rng.random(qp_shape) + p = q + rng.random(qp_shape) * 1e-2 + x, p, q = (xp.asarray(arr.tolist()) for arr in (x, p, q)) + + res = stats.quantile_test(x, q=q, p=p, axis=None) + res_low, res_high = res.confidence_interval() + ref = stats.quantile_test(xp_ravel(x), q=xp_ravel(q), p=xp_ravel(p), axis=None) + ref_low, ref_high = ref.confidence_interval() + xp_assert_equal(res.statistic, ref.statistic[xp.newaxis, :]) + xp_assert_equal(res.statistic_type, ref.statistic_type[xp.newaxis, :]) + xp_assert_equal(res.pvalue, ref.pvalue[xp.newaxis, :]) + xp_assert_equal(res_low, ref_low[xp.newaxis, :]) + xp_assert_equal(res_high, ref_high[xp.newaxis, :]) + + res = stats.quantile_test(x, q=0.5, p=0.5, axis=None) + res_low, res_high = res.confidence_interval() + ref = stats.quantile_test(xp_ravel(x), q=0.5, p=0.5, axis=None) + ref_low, ref_high = ref.confidence_interval() + xp_assert_equal(res.statistic, ref.statistic) + xp_assert_equal(res.statistic_type, ref.statistic_type) + xp_assert_equal(res.pvalue, ref.pvalue) + xp_assert_equal(res_low, ref_low) + xp_assert_equal(res_high, ref_high) + + @pytest.mark.filterwarnings("ignore:torch:UserWarning") + @pytest.mark.parametrize("x_shape, qp_shape, axis, keepdims, res_shape", [ + ((1, 3), (1,), None, False, ()), + ((1, 3), (1,), None, True, (1, 1)), + ((1, 3), (2, 1), -1, False, (2,)), + ((1, 3), (2, 1), -1, True, (2, 1)), + ]) + def test_keepdims(self, x_shape, qp_shape, axis, keepdims, res_shape, xp): + rng = np.random.default_rng(883771738488451943) + x, qp = xp.asarray(rng.random(x_shape)), xp.asarray(rng.random(qp_shape)) + res = stats.quantile_test(x, q=qp, p=qp, axis=axis, keepdims=keepdims) + assert res.statistic.shape == res_shape + + +class TestPageTrendTest: + def setup_method(self): + self.rng = np.random.default_rng(1808365978) + + # expected statistic and p-values generated using R at + # https://rdrr.io/cran/cultevo/, e.g. + # library(cultevo) + # data = rbind(c(72, 47, 73, 35, 47, 96, 30, 59, 41, 36, 56, 49, 81, 43, + # 70, 47, 28, 28, 62, 20, 61, 20, 80, 24, 50), + # c(68, 52, 60, 34, 44, 20, 65, 88, 21, 81, 48, 31, 31, 67, + # 69, 94, 30, 24, 40, 87, 70, 43, 50, 96, 43), + # c(81, 13, 85, 35, 79, 12, 92, 86, 21, 64, 16, 64, 68, 17, + # 16, 89, 71, 43, 43, 36, 54, 13, 66, 51, 55)) + # result = page.test(data, verbose=FALSE) + # Most test cases generated to achieve common critical p-values so that + # results could be checked (to limited precision) against tables in + # scipy.stats.page_trend_test reference [1] + + rng = np.random.default_rng(3113562111) + data_3_25 = rng.random((3, 25)) + rng = np.random.default_rng(3113562111) + data_10_26 = rng.random((10, 26)) + + ts = [ + (12949, 0.275539045444, False, 'asymptotic', data_3_25), + (47221, 0.5703651063709, False, 'asymptotic', data_10_26), + (12332, 0.7722477197436702, False, 'asymptotic', + [[72, 47, 73, 35, 47, 96, 30, 59, 41, 36, 56, 49, 81, + 43, 70, 47, 28, 28, 62, 20, 61, 20, 80, 24, 50], + [68, 52, 60, 34, 44, 20, 65, 88, 21, 81, 48, 31, 31, + 67, 69, 94, 30, 24, 40, 87, 70, 43, 50, 96, 43], + [81, 13, 85, 35, 79, 12, 92, 86, 21, 64, 16, 64, 68, + 17, 16, 89, 71, 43, 43, 36, 54, 13, 66, 51, 55]]), + (266, 4.121656378600823e-05, False, 'exact', + [[1.5, 4., 8.3, 5, 19, 11], + [5, 4, 3.5, 10, 20, 21], + [8.4, 3.2, 10, 12, 14, 15]]), + (332, 0.9566400920502488, True, 'exact', + [[4, 3, 2, 1], [4, 3, 2, 1], [4, 3, 2, 1], [4, 3, 2, 1], + [4, 3, 2, 1], [4, 3, 2, 1], [4, 3, 2, 1], [4, 3, 2, 1], + [3, 4, 1, 2], [1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4], + [1, 2, 3, 4], [1, 2, 3, 4]]), + (241, 0.9622210164861476, True, 'exact', + [[3, 2, 1], [3, 2, 1], [3, 2, 1], [3, 2, 1], [3, 2, 1], [3, 2, 1], + [3, 2, 1], [3, 2, 1], [3, 2, 1], [3, 2, 1], [3, 2, 1], [3, 2, 1], + [3, 2, 1], [2, 1, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3], + [1, 2, 3], [1, 2, 3], [1, 2, 3]]), + (197, 0.9619432897162209, True, 'exact', + [[6, 5, 4, 3, 2, 1], [6, 5, 4, 3, 2, 1], [1, 3, 4, 5, 2, 6]]), + (423, 0.9590458306880073, True, 'exact', + [[5, 4, 3, 2, 1], [5, 4, 3, 2, 1], [5, 4, 3, 2, 1], + [5, 4, 3, 2, 1], [5, 4, 3, 2, 1], [5, 4, 3, 2, 1], + [4, 1, 3, 2, 5], [1, 2, 3, 4, 5], [1, 2, 3, 4, 5], + [1, 2, 3, 4, 5]]), + (217, 0.9693058575034678, True, 'exact', + [[3, 2, 1], [3, 2, 1], [3, 2, 1], [3, 2, 1], [3, 2, 1], [3, 2, 1], + [3, 2, 1], [3, 2, 1], [3, 2, 1], [3, 2, 1], [3, 2, 1], [3, 2, 1], + [2, 1, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3], + [1, 2, 3]]), + (395, 0.991530289351305, True, 'exact', + [[7, 6, 5, 4, 3, 2, 1], [7, 6, 5, 4, 3, 2, 1], + [6, 5, 7, 4, 3, 2, 1], [1, 2, 3, 4, 5, 6, 7]]), + (117, 0.9997817843373017, True, 'exact', + [[3, 2, 1], [3, 2, 1], [3, 2, 1], [3, 2, 1], [3, 2, 1], [3, 2, 1], + [3, 2, 1], [3, 2, 1], [3, 2, 1], [2, 1, 3], [1, 2, 3]]), + ] + + @pytest.mark.parametrize("L, p, ranked, method, data", ts) + def test_accuracy(self, L, p, ranked, method, data): + res = stats.page_trend_test(data, ranked=ranked, method=method) + assert_equal(L, res.statistic) + assert_allclose(p, res.pvalue) + assert_equal(method, res.method) + + ts2 = [ + (542, 0.9481266260876332, True, 'exact', + [[10, 9, 8, 7, 6, 5, 4, 3, 2, 1], + [1, 8, 4, 7, 6, 5, 9, 3, 2, 10]]), + (1322, 0.9993113928199309, True, 'exact', + [[10, 9, 8, 7, 6, 5, 4, 3, 2, 1], [10, 9, 8, 7, 6, 5, 4, 3, 2, 1], + [10, 9, 8, 7, 6, 5, 4, 3, 2, 1], [9, 2, 8, 7, 6, 5, 4, 3, 10, 1], + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]]), + (2286, 0.9908688345484833, True, 'exact', + [[8, 7, 6, 5, 4, 3, 2, 1], [8, 7, 6, 5, 4, 3, 2, 1], + [8, 7, 6, 5, 4, 3, 2, 1], [8, 7, 6, 5, 4, 3, 2, 1], + [8, 7, 6, 5, 4, 3, 2, 1], [8, 7, 6, 5, 4, 3, 2, 1], + [8, 7, 6, 5, 4, 3, 2, 1], [8, 7, 6, 5, 4, 3, 2, 1], + [8, 7, 6, 5, 4, 3, 2, 1], [1, 3, 5, 6, 4, 7, 2, 8], + [1, 2, 3, 4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7, 8], + [1, 2, 3, 4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7, 8], + [1, 2, 3, 4, 5, 6, 7, 8]]), + ] + # only the first of these appears slow because intermediate data are + # cached and used on the rest + @pytest.mark.parametrize("L, p, ranked, method, data", ts2) + @pytest.mark.slow() + def test_accuracy2(self, L, p, ranked, method, data): + res = stats.page_trend_test(data, ranked=ranked, method=method) + assert_equal(L, res.statistic) + assert_allclose(p, res.pvalue) + assert_equal(method, res.method) + + def test_options(self): + rng = np.random.default_rng(183973867) + m, n = 10, 20 + predicted_ranks = np.arange(1, n+1) + perm = rng.permutation(np.arange(n)) + data = rng.random((m, n)) + ranks = stats.rankdata(data, axis=1) + res1 = stats.page_trend_test(ranks) + res2 = stats.page_trend_test(ranks, ranked=True) + res3 = stats.page_trend_test(data, ranked=False) + res4 = stats.page_trend_test(ranks, predicted_ranks=predicted_ranks) + res5 = stats.page_trend_test(ranks[:, perm], + predicted_ranks=predicted_ranks[perm]) + assert_equal(res1.statistic, res2.statistic) + assert_equal(res1.statistic, res3.statistic) + assert_equal(res1.statistic, res4.statistic) + assert_equal(res1.statistic, res5.statistic) + + def test_Ames_assay(self): + # test from _page_trend_test.py [2] page 151; data on page 144 + data = [[101, 117, 111], [91, 90, 107], [103, 133, 121], + [136, 140, 144], [190, 161, 201], [146, 120, 116]] + data = np.array(data).T + predicted_ranks = np.arange(1, 7) + + res = stats.page_trend_test(data, ranked=False, + predicted_ranks=predicted_ranks, + method="asymptotic") + assert_equal(res.statistic, 257) + assert_almost_equal(res.pvalue, 0.0035, decimal=4) + + res = stats.page_trend_test(data, ranked=False, + predicted_ranks=predicted_ranks, + method="exact") + assert_equal(res.statistic, 257) + assert_almost_equal(res.pvalue, 0.0023, decimal=4) + + def test_input_validation(self): + # test data not a 2d array + with assert_raises(ValueError, match="`data` must be a 2d array."): + stats.page_trend_test(None) + with assert_raises(ValueError, match="`data` must be a 2d array."): + stats.page_trend_test([]) + with assert_raises(ValueError, match="`data` must be a 2d array."): + stats.page_trend_test([1, 2]) + with assert_raises(ValueError, match="`data` must be a 2d array."): + stats.page_trend_test([[[1]]]) + + # test invalid dimensions + rng = np.random.default_rng(2482566048) + with assert_raises(ValueError, match="Page's L is only appropriate"): + stats.page_trend_test(rng.random((1, 3))) + with assert_raises(ValueError, match="Page's L is only appropriate"): + stats.page_trend_test(rng.random((2, 2))) + + # predicted ranks must include each integer [1, 2, 3] exactly once + message = "`predicted_ranks` must include each integer" + with assert_raises(ValueError, match=message): + stats.page_trend_test(data=[[1, 2, 3], [1, 2, 3]], + predicted_ranks=[0, 1, 2]) + with assert_raises(ValueError, match=message): + stats.page_trend_test(data=[[1, 2, 3], [1, 2, 3]], + predicted_ranks=[1.1, 2, 3]) + with assert_raises(ValueError, match=message): + stats.page_trend_test(data=[[1, 2, 3], [1, 2, 3]], + predicted_ranks=[1, 2, 3, 3]) + with assert_raises(ValueError, match=message): + stats.page_trend_test(data=[[1, 2, 3], [1, 2, 3]], + predicted_ranks="invalid") + + # test improperly ranked data + with assert_raises(ValueError, match="`data` is not properly ranked"): + stats.page_trend_test([[0, 2, 3], [1, 2, 3]], True) + with assert_raises(ValueError, match="`data` is not properly ranked"): + stats.page_trend_test([[1, 2, 3], [1, 2, 4]], True) + + # various + with assert_raises(ValueError, match="`data` contains NaNs"): + stats.page_trend_test([[1, 2, 3], [1, 2, np.nan]], + ranked=False) + with assert_raises(ValueError, match="`method` must be in"): + stats.page_trend_test(data=[[1, 2, 3], [1, 2, 3]], + method="ekki") + with assert_raises(TypeError, match="`ranked` must be boolean."): + stats.page_trend_test(data=[[1, 2, 3], [1, 2, 3]], + ranked="ekki") + + +@make_xp_test_case(stats.expectile) +class TestExpectile: + @pytest.mark.parametrize('dtype', [None, 'float32', 'float64']) + def test_same_as_mean(self, dtype, xp): + dtype = dtype if dtype is None else getattr(xp, dtype) + rng = np.random.default_rng(42) + + x = xp.asarray(rng.random(size=20).tolist(), dtype=dtype) + xp_assert_close(stats.expectile(x, 0.5), xp.mean(x)) + + w = xp.asarray(rng.random(size=20).tolist(), dtype=dtype) + xp_assert_close(stats.expectile(x, 0.5, weights=w), _xp_mean(x, weights=w)) + + def test_minimum(self, xp): + rng = np.random.default_rng(42) + x = xp.asarray(rng.random(size=20)) + xp_assert_close(stats.expectile(x, alpha=0), xp.min(x)) + + def test_maximum(self, xp): + rng = np.random.default_rng(42) + x = xp.asarray(rng.random(size=20)) + xp_assert_close(stats.expectile(x, alpha=1), xp.max(x)) + + def test_weights(self, xp): + # expectile should minimize `fun` defined below; see + # F. Sobotka and T. Kneib, "Geoadditive expectile regression", + # Computational Statistics and Data Analysis 56 (2012) 755-767 + # https://doi.org/10.1016/j.csda.2010.11.015 + rng = np.random.default_rng(1856392524598679138) + + def fun(u, a, alpha, weights): + w = np.full_like(a, fill_value=alpha) + w[a <= u] = 1 - alpha + return np.sum(w * weights * (a - u)**2) + + def expectile2(a, alpha, weights): + bracket = np.min(a), np.max(a) + return optimize.minimize_scalar(fun, bracket=bracket, + args=(a, alpha, weights)).x + + n = 10 + a = rng.random(n) + alpha = rng.random() + weights = rng.random(n) + + res = stats.expectile(xp.asarray(a), alpha, weights=xp.asarray(weights)) + ref = expectile2(a, alpha, weights) + xp_assert_close(res, xp.asarray(ref)) + + @pytest.mark.parametrize( + "alpha", [0.2, 0.5 - 1e-12, 0.5, 0.5 + 1e-12, 0.8] + ) + @pytest.mark.parametrize("n", [20, 2000]) + def test_expectile_properties(self, alpha, n, xp): + """ + See Section 6 of + I. Steinwart, C. Pasin, R.C. Williamson & S. Zhang (2014). + "Elicitation and Identification of Properties". COLT. + http://proceedings.mlr.press/v35/steinwart14.html + + and + + Propositions 5, 6, 7 of + F. Bellini, B. Klar, and A. Müller and E. Rosazza Gianin (2013). + "Generalized Quantiles as Risk Measures" + http://doi.org/10.2139/ssrn.2225751 + """ + rng = np.random.default_rng(42) + x = xp.asarray(rng.normal(size=n)) + + # 0. definite / constancy + # Let T(X) denote the expectile of rv X ~ F. + # T(c) = c for constant c + for c in [-5., 0., 0.5]: + xp_assert_close( + stats.expectile(xp.full(shape=n, fill_value=c), alpha=alpha), + xp.asarray(c) + ) + + # 1. translation equivariance + # T(X + c) = T(X) + c + c = xp.asarray(rng.exponential()) + xp_assert_close( + stats.expectile(x + c, alpha=alpha), + stats.expectile(x, alpha=alpha) + c, + ) + xp_assert_close( + stats.expectile(x - c, alpha=alpha), + stats.expectile(x, alpha=alpha) - c, + ) + + # 2. positively homogeneity + # T(cX) = c * T(X) for c > 0 + xp_assert_close( + stats.expectile(c * x, alpha=alpha), + c * stats.expectile(x, alpha=alpha), + ) + + # 3. subadditivity + # Note that subadditivity holds for alpha >= 0.5. + # T(X + Y) <= T(X) + T(Y) + # For alpha = 0.5, i.e. the mean, strict equality holds. + # For alpha < 0.5, one can use property 6. to show + # T(X + Y) >= T(X) + T(Y) + y = xp.asarray(rng.logistic(size=n, loc=10)) # different distribution than x + if alpha == 0.5: + def assert_op(a, b): + xp_assert_close(a, b) + + elif alpha > 0.5: + def assert_op(a, b): + assert a < b + + else: + def assert_op(a, b): + assert a > b + + assert_op( + stats.expectile(x + y, alpha=alpha), + stats.expectile(x, alpha=alpha) + + stats.expectile(y, alpha=alpha) + ) + + # 4. monotonicity + # This holds for first order stochastic dominance X: + # X >= Y whenever P(X <= x) < P(Y <= x) + # T(X) <= T(Y) whenever X <= Y + y = xp.asarray(rng.normal(size=n, loc=5)) + assert ( + stats.expectile(x, alpha=alpha) <= stats.expectile(y, alpha=alpha) + ) + + # 5. convexity for alpha > 0.5, concavity for alpha < 0.5 + # convexity is + # T((1 - c) X + c Y) <= (1 - c) T(X) + c T(Y) for 0 <= c <= 1 + y = xp.asarray(rng.logistic(size=n, loc=10)) + for c in [0.1, 0.5, 0.8]: + assert_op( + stats.expectile((1-c)*x + c*y, alpha=alpha), + (1-c) * stats.expectile(x, alpha=alpha) + + c * stats.expectile(y, alpha=alpha) + ) + + # 6. negative argument + # T_{alpha}(-X) = -T_{1-alpha}(X) + xp_assert_close( + stats.expectile(-x, alpha=alpha), + -stats.expectile(x, alpha=1-alpha), + ) + + @pytest.mark.parametrize("n", [20, 2000]) + def test_monotonicity_in_alpha(self, n, xp): + rng = np.random.default_rng(42) + x = xp.asarray(rng.pareto(a=2, size=n)) + e_list = [] + alpha_seq = np.logspace(-15, np.log10(0.5), 100) + # sorted list of unique alpha values in interval (0, 1) + for alpha in np.r_[0, alpha_seq, 1 - alpha_seq[:-1:-1], 1]: + e_list.append(stats.expectile(x, alpha=float(alpha))) + assert xp.all(xp.diff(xp.asarray(e_list)) > 0) + + +@make_xp_test_case(stats.lmoment) +class TestLMoment: + # data from https://github.com/scipy/scipy/issues/19460 + data = [0.87, 0.87, 1.29, 1.5, 1.7, 0.66, 1.5, 0.5, 1., 1.25, 2.3, + 1.03, 2.85, 0.68, 1.74, 1.94, 0.63, 2.04, 1.2, 0.64, 2.05, 0.97, + 2.81, 1.02, 2.76, 0.86, 1.36, 1.29, 1.68, 0.72, 1.67, 1.15, 3.26, + 0.93, 0.83, 0.91, 0.92, 2.32, 1.12, 3.21, 1.23, 1.22, 1.29, 2.08, + 0.64, 2.83, 2.68, 1.77, 0.69, 1.69, 0.7, 1.83, 2.25, 1.23, 1.17, + 0.94, 1.22, 0.76, 0.69, 0.48, 1.04, 2.49, 1.38, 1.57, 1.79, 1.59, + 1.3, 1.54, 1.07, 1.03, 0.76, 2.35, 2.05, 2.02, 2.36, 1.59, 0.97, + 1.63, 1.66, 0.94, 1.45, 1.26, 1.25, 0.68, 2.96, 0.8, 1.16, 0.82, + 0.64, 0.87, 1.33, 1.28, 1.26, 1.19, 1.24, 1.12, 1.45, 1.03, 1.37, + 1.4, 1.35, 1.28, 1.04, 1.31, 0.87, 0.96, 2.55, 1.72, 1.05, 1.15, + 1.73, 1.03, 1.53, 2.41, 1.36, 2.08, 0.92, 0.73, 1.56, 1.94, 0.78] + + not_integers = [1.5, [1, 2, 3.5], math.nan, math.inf] + + def test_dtype_iv(self, xp): + message = '`sample` must be an array of real numbers.' + with pytest.raises(ValueError, match=message): + stats.lmoment(xp.asarray(self.data, dtype=xp.complex128)) + + @skip_xp_invalid_arg + def test_dtype_iv_non_numeric(self): + message = '`sample` must be an array of real numbers.' + with pytest.raises(ValueError, match=message): + stats.lmoment(np.array(self.data, dtype=object)) + + @pytest.mark.parametrize('order', not_integers + [0, -1, [], [[1, 2, 3]]]) + def test_order_iv(self, order, xp): + if is_jax(xp) and np.isscalar(order) and order < 1: + pytest.skip("Order is treated as an array; can't do value-based IV") + message = '`order` must be a scalar or a non-empty...' + with pytest.raises(ValueError, match=message): + stats.lmoment(xp.asarray(self.data), order=order) + + @pytest.mark.parametrize('axis', not_integers) + def test_axis_iv(self, axis, xp): + message = '`axis` must be an integer' + with pytest.raises(ValueError, match=message): + stats.lmoment(xp.asarray(self.data), axis=axis) + + @pytest.mark.parametrize('sorted', not_integers) + def test_sorted_iv(self, sorted, xp): + message = '`sorted` must be True or False.' + with pytest.raises(ValueError, match=message): + stats.lmoment(xp.asarray(self.data), sorted=sorted) + + @pytest.mark.parametrize('standardize', not_integers) + def test_standardize_iv(self, standardize, xp): + message = '`standardize` must be True or False.' + with pytest.raises(ValueError, match=message): + stats.lmoment(xp.asarray(self.data), standardize=standardize) + + @pytest.mark.parametrize('order', [1, 4, [1, 2, 3, 4]]) + @pytest.mark.parametrize('standardize', [False, True]) + @pytest.mark.parametrize('presorted', [False, True]) + def test_lmoment(self, order, standardize, presorted, xp): + # Reference values from R package `lmom` + # options(digits=16) + # library(lmom) + # data= c(0.87, 0.87,..., 1.94, 0.78) + # samlmu(data) + ref = xp.asarray([1.4087603305785130, 0.3415936639118458, + 0.2189964482831403, 0.1328186463415905]) + + if not standardize: + ref = xpx.at(ref)[2:].multiply(ref[1]) + + data = sorted(self.data) if presorted else self.data + data = xp.asarray(data) + + res = stats.lmoment(data, order, standardize=standardize, sorted=presorted) + xp_assert_close(res, ref[xp.asarray(order)-1]) + + def test_dtype(self, xp): + dtype = xp.float32 + sample = xp.asarray(self.data) + res = stats.lmoment(xp.astype(sample, dtype)) + ref = xp.astype(stats.lmoment(sample), dtype) + xp_assert_close(res, ref, rtol=1e-4) + + dtype = xp.int64 + sample = xp.asarray([1, 2, 3, 4, 5]) + res = stats.lmoment(xp.astype(sample, dtype)) + ref = stats.lmoment(xp.astype(sample, xp_default_dtype(xp))) + xp_assert_close(res, ref) + + @pytest.mark.parametrize("axis", [0, 1]) + def test_axis(self, axis, xp): + # nd input is tested extensively in `test_axis_nan_policy`, but only for NumPy + rng = np.random.default_rng(234923498149931248151) + x = rng.random(size=(10, 11)) + res = stats.lmoment(xp.asarray(x), axis=axis) + ref = xp.asarray(stats.lmoment(x, axis=axis)) + xp_assert_close(res, ref) + + +@make_xp_test_case(_xp_mean) +class TestXP_Mean: + @pytest.mark.parametrize('axis', [None, 1, -1, (-2, 2)]) + @pytest.mark.parametrize('weights', [None, True]) + @pytest.mark.parametrize('keepdims', [False, True]) + def test_xp_mean_basic(self, xp, axis, weights, keepdims): + rng = np.random.default_rng(90359458245906) + x = rng.random((3, 4, 5)) + x_xp = xp.asarray(x) + w = w_xp = None + + if weights: + w = rng.random((1, 5)) + w_xp = xp.asarray(w) + x, w = np.broadcast_arrays(x, w) + + res = _xp_mean(x_xp, weights=w_xp, axis=axis, keepdims=keepdims) + ref = np.average(x, weights=w, axis=axis, keepdims=keepdims) + + xp_assert_close(res, xp.asarray(ref)) + + def test_non_broadcastable(self, xp): + # non-broadcastable x and weights + x, w = xp.arange(10.), xp.zeros(5) + message = "Array shapes are incompatible for broadcasting." + with pytest.raises(ValueError, match=message): + _xp_mean(x, weights=w) + + @pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning:dask") + @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") + def test_special_cases(self, xp): + # weights sum to zero + weights = xp.asarray([-1., 0., 1.]) + + res = _xp_mean(xp.asarray([1., 1., 1.]), weights=weights) + xp_assert_close(res, xp.asarray(xp.nan)) + + res = _xp_mean(xp.asarray([2., 1., 1.]), weights=weights) + xp_assert_close(res, xp.asarray(-np.inf)) + + res = _xp_mean(xp.asarray([1., 1., 2.]), weights=weights) + xp_assert_close(res, xp.asarray(np.inf)) + + @pytest.mark.filterwarnings( + "ignore:invalid value encountered:RuntimeWarning" + ) # for dask + def test_nan_policy(self, xp): + x = xp.arange(10.) + mask = (x == 3) + x = xp.where(mask, xp.nan, x) + + # nan_policy='raise' raises an error + if is_lazy_array(x): + with pytest.raises(TypeError, match='not supported for lazy arrays'): + _xp_mean(x, nan_policy='raise') + else: + with pytest.raises(ValueError, match='The input contains nan values'): + _xp_mean(x, nan_policy='raise') + + # `nan_policy='propagate'` is the default, and the result is NaN + res1 = _xp_mean(x) + res2 = _xp_mean(x, nan_policy='propagate') + ref = xp.asarray(xp.nan) + xp_assert_equal(res1, ref) + xp_assert_equal(res2, ref) + + # `nan_policy='omit'` omits NaNs in `x` + res = _xp_mean(x, nan_policy='omit') + ref = xp.mean(x[~mask]) + xp_assert_close(res, ref) + + # `nan_policy='omit'` omits NaNs in `weights`, too + weights = xp.ones(10) + weights = xp.where(mask, xp.nan, weights) + res = _xp_mean(xp.arange(10.), weights=weights, nan_policy='omit') + ref = xp.mean(x[~mask]) + xp_assert_close(res, ref) + + @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning") + def test_nan_policy_warns(self, xp): + x = xp.arange(10.) + x = xp.where(x == 3, xp.nan, x) + + # Check for warning if omitting NaNs causes empty slice + message = 'After omitting NaNs...' + with eager_warns(RuntimeWarning, match=message, xp=xp): + res = _xp_mean(x * np.nan, nan_policy='omit') + ref = xp.asarray(xp.nan) + xp_assert_equal(res, ref) + + def test_empty(self, xp): + message = 'One or more sample arguments is too small...' + with pytest.warns(SmallSampleWarning, match=message): + res = _xp_mean(xp.asarray([])) + ref = xp.asarray(xp.nan) + xp_assert_equal(res, ref) + + message = "All axis-slices of one or more sample arguments..." + with pytest.warns(SmallSampleWarning, match=message): + res = _xp_mean(xp.asarray([[]]), axis=1) + ref = xp.asarray([xp.nan]) + xp_assert_equal(res, ref) + + res = _xp_mean(xp.asarray([[]]), axis=0) + ref = xp.asarray([]) + xp_assert_equal(res, ref) + + @pytest.mark.filterwarnings( + "ignore:overflow encountered in reduce:RuntimeWarning" + ) # for dask + def test_dtype(self, xp): + max = xp.finfo(xp.float32).max + x_np = np.asarray([max, max], dtype=np.float32) + x_xp = xp.asarray(x_np) + + # Overflow occurs for float32 input + with np.errstate(over='ignore'): + res = _xp_mean(x_xp) + ref = np.mean(x_np) + np.testing.assert_equal(ref, np.inf) + xp_assert_close(res, xp.asarray(ref)) + + # correct result is returned if `float64` is used + res = _xp_mean(x_xp, dtype=xp.float64) + ref = xp.asarray(np.mean(np.asarray(x_np, dtype=np.float64))) + xp_assert_close(res, ref) + + def test_integer(self, xp): + # integer inputs are converted to the appropriate float + x = xp.arange(10) + y = xp.arange(10.) + xp_assert_equal(_xp_mean(x), _xp_mean(y)) + xp_assert_equal(_xp_mean(y, weights=x), _xp_mean(y, weights=y)) + + def test_complex_gh22404(self, xp): + rng = np.random.default_rng(90359458245906) + x, y, wx, wy = rng.random((4, 20)) + res = _xp_mean(xp.asarray(x + y*1j), weights=xp.asarray(wx + wy*1j)) + ref = np.average(x + y*1j, weights=wx + wy*1j) + xp_assert_close(res, xp.asarray(ref)) + + +@make_xp_test_case(_xp_var) +class TestXP_Var: + @pytest.mark.parametrize('axis', [None, 1, -1, (-2, 2)]) + @pytest.mark.parametrize('keepdims', [False, True]) + @pytest.mark.parametrize('correction', [0, 1]) + @pytest.mark.parametrize('nan_policy', ['propagate', 'omit']) + def test_xp_var_basic(self, xp, axis, keepdims, correction, nan_policy): + rng = np.random.default_rng(90359458245906) + x = rng.random((3, 4, 5)) + var_ref = np.var + + if nan_policy == 'omit': + nan_mask = rng.random(size=x.shape) > 0.5 + x[nan_mask] = np.nan + var_ref = np.nanvar + + x_xp = xp.asarray(x) + + res = _xp_var(x_xp, axis=axis, keepdims=keepdims, correction=correction, + nan_policy=nan_policy) + + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", "Degrees of freedom <= 0 for slice", RuntimeWarning) + ref = var_ref(x, axis=axis, keepdims=keepdims, ddof=correction) + + xp_assert_close(res, xp.asarray(ref)) + + def test_special_cases(self, xp): + # correction too big + res = _xp_var(xp.asarray([1., 2.]), correction=3) + xp_assert_close(res, xp.asarray(xp.nan)) + + def test_nan_policy(self, xp): + x = xp.arange(10.) + mask = (x == 3) + x = xp.where(mask, xp.nan, x) + + # `nan_policy='propagate'` is the default, and the result is NaN + res1 = _xp_var(x) + res2 = _xp_var(x, nan_policy='propagate') + ref = xp.asarray(xp.nan) + xp_assert_equal(res1, ref) + xp_assert_equal(res2, ref) + + # `nan_policy='omit'` omits NaNs in `x` + res = _xp_var(x, nan_policy='omit') + ref = xp.var(x[~mask]) + xp_assert_close(res, ref) + + @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning") + def test_nan_policy_warns(self, xp): + x = xp.arange(10.) + x = xp.where(x == 3, xp.nan, x) + + # Check for warning if omitting NaNs causes empty slice + message = 'After omitting NaNs...' + with eager_warns(RuntimeWarning, match=message, xp=xp): + res = _xp_var(x * np.nan, nan_policy='omit') + ref = xp.asarray(xp.nan) + xp_assert_equal(res, ref) + + @skip_xp_backends(eager_only=True) + def test_nan_policy_raise(self, xp): + # nan_policy='raise' raises an error when NaNs are present + message = 'The input contains nan values' + with pytest.raises(ValueError, match=message): + _xp_var(xp.asarray([1, 2, xp.nan]), nan_policy='raise') + + def test_empty(self, xp): + message = 'One or more sample arguments is too small...' + with pytest.warns(SmallSampleWarning, match=message): + res = _xp_var(xp.asarray([])) + ref = xp.asarray(xp.nan) + xp_assert_equal(res, ref) + + message = "All axis-slices of one or more sample arguments..." + with pytest.warns(SmallSampleWarning, match=message): + res = _xp_var(xp.asarray([[]]), axis=1) + ref = xp.asarray([xp.nan]) + xp_assert_equal(res, ref) + + res = _xp_var(xp.asarray([[]]), axis=0) + ref = xp.asarray([]) + xp_assert_equal(res, ref) + + @pytest.mark.filterwarnings( + "ignore:overflow encountered in reduce:RuntimeWarning" + ) # Overflow occurs for float32 input + def test_dtype(self, xp): + max = xp.finfo(xp.float32).max + x_np = np.asarray([max, max/2], dtype=np.float32) + x_xp = xp.asarray(x_np) + + res = _xp_var(x_xp) + ref = np.var(x_np) + np.testing.assert_equal(ref, np.inf) + xp_assert_close(res, xp.asarray(ref)) + + # correct result is returned if `float64` is used + res = _xp_var(x_xp, dtype=xp.float64) + ref = xp.asarray(np.var(np.asarray(x_np, dtype=np.float64))) + xp_assert_close(res, ref) + + def test_integer(self, xp): + # integer inputs are converted to the appropriate float + x = xp.arange(10) + y = xp.arange(10.) + xp_assert_equal(_xp_var(x), _xp_var(y)) + + def test_complex_gh22404(self, xp): + rng = np.random.default_rng(90359458245906) + x, y = rng.random((2, 20)) + res = _xp_var(xp.asarray(x + y*1j)) + ref = np.var(x + y*1j) + xp_assert_close(res, xp.asarray(ref), check_dtype=False) + + +@pytest.mark.uses_xp_capabilities(False, reason="private") +def test_chk_asarray(xp): + rng = np.random.default_rng(2348923425434) + x0 = rng.random(size=(2, 3, 4)) + x = xp.asarray(x0) + + axis = 1 + x_out, axis_out = _chk_asarray(x, axis=axis, xp=xp) + xp_assert_equal(x_out, xp.asarray(x0)) + assert_equal(axis_out, axis) + + axis = None + x_out, axis_out = _chk_asarray(x, axis=axis, xp=xp) + xp_assert_equal(x_out, xp.asarray(x0.ravel())) + assert_equal(axis_out, 0) + + axis = 2 + x_out, axis_out = _chk_asarray(x[0, 0, 0], axis=axis, xp=xp) + xp_assert_equal(x_out, xp.asarray(np.atleast_1d(x0[0, 0, 0]))) + assert_equal(axis_out, axis) + + +@skip_xp_backends("jax.numpy", reason="JAX+wilcoxon+'exact' incompatible") +@pytest.mark.parametrize("fun", [ + make_xp_pytest_param(stats.wilcoxon), + make_xp_pytest_param(stats.ks_1samp), + make_xp_pytest_param(stats.ks_2samp), + make_xp_pytest_param(stats.kstest) +]) +def test_rename_mode_method(fun, xp): + rng = np.random.default_rng(23498459284629827814) + x = xp.asarray(rng.random(10)) + y = xp.asarray(rng.random(10)) + + if fun == stats.wilcoxon: + args = (x,) + elif fun == stats.ks_1samp: + args = (x, special.ndtr) + else: + args = (x, y) + + res = fun(*args, method='exact') + res2 = fun(*args, mode='exact') + xp_assert_equal(res.statistic, res2.statistic) + xp_assert_equal(res.pvalue, res2.pvalue) + + err = rf"{fun.__name__}\(\) got multiple values for argument" + with pytest.raises(TypeError, match=err): + fun(*args, method='exact', mode='exact') diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/test_survival.py b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_survival.py new file mode 100644 index 0000000000000000000000000000000000000000..60a5fef27ae281c028ade8af523bdf1f62f8f664 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_survival.py @@ -0,0 +1,466 @@ +import pytest +import numpy as np +from numpy.testing import assert_equal, assert_allclose +from scipy import stats +from scipy.stats import _survival + + +def _kaplan_meier_reference(times, censored): + # This is a very straightforward implementation of the Kaplan-Meier + # estimator that does almost everything differently from the implementation + # in stats.ecdf. + + # Begin by sorting the raw data. Note that the order of death and loss + # at a given time matters: death happens first. See [2] page 461: + # "These conventions may be paraphrased by saying that deaths recorded as + # of an age t are treated as if they occurred slightly before t, and losses + # recorded as of an age t are treated as occurring slightly after t." + # We implement this by sorting the data first by time, then by `censored`, + # (which is 0 when there is a death and 1 when there is only a loss). + dtype = [('time', float), ('censored', int)] + data = np.array([(t, d) for t, d in zip(times, censored)], dtype=dtype) + data = np.sort(data, order=('time', 'censored')) + times = data['time'] + died = np.logical_not(data['censored']) + + m = times.size + n = np.arange(m, 0, -1) # number at risk + sf = np.cumprod((n - died) / n) + + # Find the indices of the *last* occurrence of unique times. The + # corresponding entries of `times` and `sf` are what we want. + _, indices = np.unique(times[::-1], return_index=True) + ref_times = times[-indices - 1] + ref_sf = sf[-indices - 1] + return ref_times, ref_sf + + +class TestSurvival: + + @staticmethod + def get_random_sample(rng, n_unique): + # generate random sample + unique_times = rng.random(n_unique) + # convert to `np.int32` to resolve `np.repeat` failure in 32-bit CI + repeats = rng.integers(1, 4, n_unique).astype(np.int32) + times = rng.permuted(np.repeat(unique_times, repeats)) + censored = rng.random(size=times.size) > rng.random() + sample = stats.CensoredData.right_censored(times, censored) + return sample, times, censored + + def test_input_validation(self): + message = '`sample` must be a one-dimensional sequence.' + with pytest.raises(ValueError, match=message): + stats.ecdf([[1]]) + with pytest.raises(ValueError, match=message): + stats.ecdf(1) + + message = '`sample` must not contain nan' + with pytest.raises(ValueError, match=message): + stats.ecdf([np.nan]) + + message = 'Currently, only uncensored and right-censored data...' + with pytest.raises(NotImplementedError, match=message): + stats.ecdf(stats.CensoredData.left_censored([1], censored=[True])) + + message = 'method` must be one of...' + res = stats.ecdf([1, 2, 3]) + with pytest.raises(ValueError, match=message): + res.cdf.confidence_interval(method='ekki-ekki') + with pytest.raises(ValueError, match=message): + res.sf.confidence_interval(method='shrubbery') + + message = 'confidence_level` must be a scalar between 0 and 1' + with pytest.raises(ValueError, match=message): + res.cdf.confidence_interval(-1) + with pytest.raises(ValueError, match=message): + res.sf.confidence_interval([0.5, 0.6]) + + message = 'The confidence interval is undefined at some observations.' + with pytest.warns(RuntimeWarning, match=message): + ci = res.cdf.confidence_interval() + + message = 'Confidence interval bounds do not implement...' + with pytest.raises(NotImplementedError, match=message): + ci.low.confidence_interval() + with pytest.raises(NotImplementedError, match=message): + ci.high.confidence_interval() + + def test_edge_cases(self): + res = stats.ecdf([]) + assert_equal(res.cdf.quantiles, []) + assert_equal(res.cdf.probabilities, []) + + res = stats.ecdf([1]) + assert_equal(res.cdf.quantiles, [1]) + assert_equal(res.cdf.probabilities, [1]) + + def test_unique(self): + # Example with unique observations; `stats.ecdf` ref. [1] page 80 + sample = [6.23, 5.58, 7.06, 6.42, 5.20] + res = stats.ecdf(sample) + ref_x = np.sort(np.unique(sample)) + ref_cdf = np.arange(1, 6) / 5 + ref_sf = 1 - ref_cdf + assert_equal(res.cdf.quantiles, ref_x) + assert_equal(res.cdf.probabilities, ref_cdf) + assert_equal(res.sf.quantiles, ref_x) + assert_equal(res.sf.probabilities, ref_sf) + + def test_nonunique(self): + # Example with non-unique observations; `stats.ecdf` ref. [1] page 82 + sample = [0, 2, 1, 2, 3, 4] + res = stats.ecdf(sample) + ref_x = np.sort(np.unique(sample)) + ref_cdf = np.array([1/6, 2/6, 4/6, 5/6, 1]) + ref_sf = 1 - ref_cdf + assert_equal(res.cdf.quantiles, ref_x) + assert_equal(res.cdf.probabilities, ref_cdf) + assert_equal(res.sf.quantiles, ref_x) + assert_equal(res.sf.probabilities, ref_sf) + + def test_evaluate_methods(self): + # Test CDF and SF `evaluate` methods + rng = np.random.default_rng(1162729143302572461) + sample, _, _ = self.get_random_sample(rng, 15) + res = stats.ecdf(sample) + x = res.cdf.quantiles + xr = x + np.diff(x, append=x[-1]+1)/2 # right shifted points + + assert_equal(res.cdf.evaluate(x), res.cdf.probabilities) + assert_equal(res.cdf.evaluate(xr), res.cdf.probabilities) + assert_equal(res.cdf.evaluate(x[0]-1), 0) # CDF starts at 0 + assert_equal(res.cdf.evaluate([-np.inf, np.inf]), [0, 1]) + + assert_equal(res.sf.evaluate(x), res.sf.probabilities) + assert_equal(res.sf.evaluate(xr), res.sf.probabilities) + assert_equal(res.sf.evaluate(x[0]-1), 1) # SF starts at 1 + assert_equal(res.sf.evaluate([-np.inf, np.inf]), [1, 0]) + + # ref. [1] page 91 + t1 = [37, 43, 47, 56, 60, 62, 71, 77, 80, 81] # times + d1 = [0, 0, 1, 1, 0, 0, 0, 1, 1, 1] # 1 means deaths (not censored) + r1 = [1, 1, 0.875, 0.75, 0.75, 0.75, 0.75, 0.5, 0.25, 0] # reference SF + + # https://sphweb.bumc.bu.edu/otlt/mph-modules/bs/bs704_survival/BS704_Survival5.html + t2 = [8, 12, 26, 14, 21, 27, 8, 32, 20, 40] + d2 = [1, 1, 1, 1, 1, 1, 0, 0, 0, 0] + r2 = [0.9, 0.788, 0.675, 0.675, 0.54, 0.405, 0.27, 0.27, 0.27] + t3 = [33, 28, 41, 48, 48, 25, 37, 48, 25, 43] + d3 = [1, 1, 1, 0, 0, 0, 0, 0, 0, 0] + r3 = [1, 0.875, 0.75, 0.75, 0.6, 0.6, 0.6] + + # https://sphweb.bumc.bu.edu/otlt/mph-modules/bs/bs704_survival/bs704_survival4.html + t4 = [24, 3, 11, 19, 24, 13, 14, 2, 18, 17, + 24, 21, 12, 1, 10, 23, 6, 5, 9, 17] + d4 = [0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1] + r4 = [0.95, 0.95, 0.897, 0.844, 0.844, 0.844, 0.844, 0.844, 0.844, + 0.844, 0.76, 0.676, 0.676, 0.676, 0.676, 0.507, 0.507] + + # https://www.real-statistics.com/survival-analysis/kaplan-meier-procedure/confidence-interval-for-the-survival-function/ + t5 = [3, 5, 8, 10, 5, 5, 8, 12, 15, 14, 2, 11, 10, 9, 12, 5, 8, 11] + d5 = [1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1] + r5 = [0.944, 0.889, 0.722, 0.542, 0.542, 0.542, 0.361, 0.181, 0.181, 0.181] + + @pytest.mark.parametrize("case", [(t1, d1, r1), (t2, d2, r2), (t3, d3, r3), + (t4, d4, r4), (t5, d5, r5)]) + def test_right_censored_against_examples(self, case): + # test `ecdf` against other implementations on example problems + times, died, ref = case + sample = stats.CensoredData.right_censored(times, np.logical_not(died)) + res = stats.ecdf(sample) + assert_allclose(res.sf.probabilities, ref, atol=1e-3) + assert_equal(res.sf.quantiles, np.sort(np.unique(times))) + + # test reference implementation against other implementations + res = _kaplan_meier_reference(times, np.logical_not(died)) + assert_equal(res[0], np.sort(np.unique(times))) + assert_allclose(res[1], ref, atol=1e-3) + + @pytest.mark.parametrize('seed', [182746786639392128, 737379171436494115, + 576033618403180168, 308115465002673650]) + def test_right_censored_against_reference_implementation(self, seed): + # test `ecdf` against reference implementation on random problems + rng = np.random.default_rng(seed) + n_unique = rng.integers(10, 100) + sample, times, censored = self.get_random_sample(rng, n_unique) + res = stats.ecdf(sample) + ref = _kaplan_meier_reference(times, censored) + assert_allclose(res.sf.quantiles, ref[0]) + assert_allclose(res.sf.probabilities, ref[1]) + + # If all observations are uncensored, the KM estimate should match + # the usual estimate for uncensored data + sample = stats.CensoredData(uncensored=times) + res = _survival._ecdf_right_censored(sample) # force Kaplan-Meier + ref = stats.ecdf(times) + assert_equal(res[0], ref.sf.quantiles) + assert_allclose(res[1], ref.cdf.probabilities, rtol=1e-14) + assert_allclose(res[2], ref.sf.probabilities, rtol=1e-14) + + def test_right_censored_ci(self): + # test "greenwood" confidence interval against example 4 (URL above). + times, died = self.t4, self.d4 + sample = stats.CensoredData.right_censored(times, np.logical_not(died)) + res = stats.ecdf(sample) + ref_allowance = [0.096, 0.096, 0.135, 0.162, 0.162, 0.162, 0.162, + 0.162, 0.162, 0.162, 0.214, 0.246, 0.246, 0.246, + 0.246, 0.341, 0.341] + + sf_ci = res.sf.confidence_interval() + cdf_ci = res.cdf.confidence_interval() + allowance = res.sf.probabilities - sf_ci.low.probabilities + + assert_allclose(allowance, ref_allowance, atol=1e-3) + assert_allclose(sf_ci.low.probabilities, + np.clip(res.sf.probabilities - allowance, 0, 1)) + assert_allclose(sf_ci.high.probabilities, + np.clip(res.sf.probabilities + allowance, 0, 1)) + assert_allclose(cdf_ci.low.probabilities, + np.clip(res.cdf.probabilities - allowance, 0, 1)) + assert_allclose(cdf_ci.high.probabilities, + np.clip(res.cdf.probabilities + allowance, 0, 1)) + + # test "log-log" confidence interval against Mathematica + # e = {24, 3, 11, 19, 24, 13, 14, 2, 18, 17, 24, 21, 12, 1, 10, 23, 6, 5, + # 9, 17} + # ci = {1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0} + # R = EventData[e, ci] + # S = SurvivalModelFit[R] + # S["PointwiseIntervals", ConfidenceLevel->0.95, + # ConfidenceTransform->"LogLog"] + + ref_low = [0.694743, 0.694743, 0.647529, 0.591142, 0.591142, 0.591142, + 0.591142, 0.591142, 0.591142, 0.591142, 0.464605, 0.370359, + 0.370359, 0.370359, 0.370359, 0.160489, 0.160489] + ref_high = [0.992802, 0.992802, 0.973299, 0.947073, 0.947073, 0.947073, + 0.947073, 0.947073, 0.947073, 0.947073, 0.906422, 0.856521, + 0.856521, 0.856521, 0.856521, 0.776724, 0.776724] + sf_ci = res.sf.confidence_interval(method='log-log') + assert_allclose(sf_ci.low.probabilities, ref_low, atol=1e-6) + assert_allclose(sf_ci.high.probabilities, ref_high, atol=1e-6) + + def test_right_censored_ci_example_5(self): + # test "exponential greenwood" confidence interval against example 5 + times, died = self.t5, self.d5 + sample = stats.CensoredData.right_censored(times, np.logical_not(died)) + res = stats.ecdf(sample) + lower = np.array([0.66639, 0.624174, 0.456179, 0.287822, 0.287822, + 0.287822, 0.128489, 0.030957, 0.030957, 0.030957]) + upper = np.array([0.991983, 0.970995, 0.87378, 0.739467, 0.739467, + 0.739467, 0.603133, 0.430365, 0.430365, 0.430365]) + + sf_ci = res.sf.confidence_interval(method='log-log') + cdf_ci = res.cdf.confidence_interval(method='log-log') + + assert_allclose(sf_ci.low.probabilities, lower, atol=1e-5) + assert_allclose(sf_ci.high.probabilities, upper, atol=1e-5) + assert_allclose(cdf_ci.low.probabilities, 1-upper, atol=1e-5) + assert_allclose(cdf_ci.high.probabilities, 1-lower, atol=1e-5) + + # Test against R's `survival` library `survfit` function, 90%CI + # library(survival) + # options(digits=16) + # time = c(3, 5, 8, 10, 5, 5, 8, 12, 15, 14, 2, 11, 10, 9, 12, 5, 8, 11) + # status = c(1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1) + # res = survfit(Surv(time, status) + # ~1, conf.type = "log-log", conf.int = 0.90) + # res$time; res$lower; res$upper + low = [0.74366748406861172, 0.68582332289196246, 0.50596835651480121, + 0.32913131413336727, 0.32913131413336727, 0.32913131413336727, + 0.15986912028781664, 0.04499539918147757, 0.04499539918147757, + 0.04499539918147757] + high = [0.9890291867238429, 0.9638835422144144, 0.8560366823086629, + 0.7130167643978450, 0.7130167643978450, 0.7130167643978450, + 0.5678602982997164, 0.3887616766886558, 0.3887616766886558, + 0.3887616766886558] + sf_ci = res.sf.confidence_interval(method='log-log', + confidence_level=0.9) + assert_allclose(sf_ci.low.probabilities, low) + assert_allclose(sf_ci.high.probabilities, high) + + # And with conf.type = "plain" + low = [0.8556383113628162, 0.7670478794850761, 0.5485720663578469, + 0.3441515412527123, 0.3441515412527123, 0.3441515412527123, + 0.1449184105424544, 0., 0., 0.] + high = [1., 1., 0.8958723780865975, 0.7391817920806210, + 0.7391817920806210, 0.7391817920806210, 0.5773038116797676, + 0.3642270254596720, 0.3642270254596720, 0.3642270254596720] + sf_ci = res.sf.confidence_interval(confidence_level=0.9) + assert_allclose(sf_ci.low.probabilities, low) + assert_allclose(sf_ci.high.probabilities, high) + + def test_right_censored_ci_nans(self): + # test `ecdf` confidence interval on a problem that results in NaNs + times, died = self.t1, self.d1 + sample = stats.CensoredData.right_censored(times, np.logical_not(died)) + res = stats.ecdf(sample) + + # Reference values generated with Matlab + # format long + # t = [37 43 47 56 60 62 71 77 80 81]; + # d = [0 0 1 1 0 0 0 1 1 1]; + # censored = ~d1; + # [f, x, flo, fup] = ecdf(t, 'Censoring', censored, 'Alpha', 0.05); + x = [37, 47, 56, 77, 80, 81] + flo = [np.nan, 0, 0, 0.052701464070711, 0.337611126231790, np.nan] + fup = [np.nan, 0.35417230377, 0.5500569798, 0.9472985359, 1.0, np.nan] + i = np.searchsorted(res.cdf.quantiles, x) + + message = "The confidence interval is undefined at some observations" + with pytest.warns(RuntimeWarning, match=message): + ci = res.cdf.confidence_interval() + + # Matlab gives NaN as the first element of the CIs. Mathematica agrees, + # but R's survfit does not. It makes some sense, but it's not what the + # formula gives, so skip that element. + assert_allclose(ci.low.probabilities[i][1:], flo[1:]) + assert_allclose(ci.high.probabilities[i][1:], fup[1:]) + + # [f, x, flo, fup] = ecdf(t, 'Censoring', censored, 'Function', + # 'survivor', 'Alpha', 0.05); + flo = [np.nan, 0.64582769623, 0.449943020228, 0.05270146407, 0, np.nan] + fup = [np.nan, 1.0, 1.0, 0.947298535929289, 0.662388873768210, np.nan] + i = np.searchsorted(res.cdf.quantiles, x) + + with pytest.warns(RuntimeWarning, match=message): + ci = res.sf.confidence_interval() + + assert_allclose(ci.low.probabilities[i][1:], flo[1:]) + assert_allclose(ci.high.probabilities[i][1:], fup[1:]) + + # With the same data, R's `survival` library `survfit` function + # doesn't produce the leading NaN + # library(survival) + # options(digits=16) + # time = c(37, 43, 47, 56, 60, 62, 71, 77, 80, 81) + # status = c(0, 0, 1, 1, 0, 0, 0, 1, 1, 1) + # res = survfit(Surv(time, status) + # ~1, conf.type = "plain", conf.int = 0.95) + # res$time + # res$lower + # res$upper + low = [1., 1., 0.64582769623233816, 0.44994302022779326, + 0.44994302022779326, 0.44994302022779326, 0.44994302022779326, + 0.05270146407071086, 0., np.nan] + high = [1., 1., 1., 1., 1., 1., 1., 0.9472985359292891, + 0.6623888737682101, np.nan] + assert_allclose(ci.low.probabilities, low) + assert_allclose(ci.high.probabilities, high) + + # It does with conf.type="log-log", as do we + with pytest.warns(RuntimeWarning, match=message): + ci = res.sf.confidence_interval(method='log-log') + low = [np.nan, np.nan, 0.38700001403202522, 0.31480711370551911, + 0.31480711370551911, 0.31480711370551911, 0.31480711370551911, + 0.08048821148507734, 0.01049958986680601, np.nan] + high = [np.nan, np.nan, 0.9813929658789660, 0.9308983170906275, + 0.9308983170906275, 0.9308983170906275, 0.9308983170906275, + 0.8263946341076415, 0.6558775085110887, np.nan] + assert_allclose(ci.low.probabilities, low) + assert_allclose(ci.high.probabilities, high) + + def test_right_censored_against_uncensored(self): + rng = np.random.default_rng(7463952748044886637) + sample = rng.integers(10, 100, size=1000) + censored = np.zeros_like(sample) + censored[np.argmax(sample)] = True + res = stats.ecdf(sample) + ref = stats.ecdf(stats.CensoredData.right_censored(sample, censored)) + assert_equal(res.sf.quantiles, ref.sf.quantiles) + assert_equal(res.sf._n, ref.sf._n) + assert_equal(res.sf._d[:-1], ref.sf._d[:-1]) # difference @ [-1] + assert_allclose(res.sf._sf[:-1], ref.sf._sf[:-1], rtol=1e-14) + + def test_plot_iv(self): + rng = np.random.default_rng(1769658657308472721) + n_unique = rng.integers(10, 100) + sample, _, _ = self.get_random_sample(rng, n_unique) + res = stats.ecdf(sample) + + try: + import matplotlib.pyplot as plt # noqa: F401 + res.sf.plot() # no other errors occur + except (ModuleNotFoundError, ImportError): + message = r"matplotlib must be installed to use method `plot`." + with pytest.raises(ModuleNotFoundError, match=message): + res.sf.plot() + + +class TestLogRank: + + @pytest.mark.parametrize( + "x, y, statistic, pvalue", + # Results validate with R + # library(survival) + # options(digits=16) + # + # futime_1 <- c(8, 12, 26, 14, 21, 27, 8, 32, 20, 40) + # fustat_1 <- c(1, 1, 1, 1, 1, 1, 0, 0, 0, 0) + # rx_1 <- c(0, 0, 0, 0, 0, 0, 0, 0, 0, 0) + # + # futime_2 <- c(33, 28, 41, 48, 48, 25, 37, 48, 25, 43) + # fustat_2 <- c(1, 1, 1, 0, 0, 0, 0, 0, 0, 0) + # rx_2 <- c(1, 1, 1, 1, 1, 1, 1, 1, 1, 1) + # + # futime <- c(futime_1, futime_2) + # fustat <- c(fustat_1, fustat_2) + # rx <- c(rx_1, rx_2) + # + # survdiff(formula = Surv(futime, fustat) ~ rx) + # + # Also check against another library which handle alternatives + # library(nph) + # logrank.test(futime, fustat, rx, alternative = "two.sided") + # res["test"] + [( + # https://sphweb.bumc.bu.edu/otlt/mph-modules/bs/bs704_survival/BS704_Survival5.html + # uncensored, censored + [[8, 12, 26, 14, 21, 27], [8, 32, 20, 40]], + [[33, 28, 41], [48, 48, 25, 37, 48, 25, 43]], + # chi2, ["two-sided", "less", "greater"] + 6.91598157449, + [0.008542873404, 0.9957285632979385, 0.004271436702061537] + ), + ( + # https://sphweb.bumc.bu.edu/otlt/mph-modules/bs/bs704_survival/BS704_Survival5.html + [[19, 6, 5, 4], [20, 19, 17, 14]], + [[16, 21, 7], [21, 15, 18, 18, 5]], + 0.835004855038, + [0.3608293039, 0.8195853480676912, 0.1804146519323088] + ), + ( + # Bland, Altman, "The logrank test", BMJ, 2004 + # https://www.bmj.com/content/328/7447/1073.short + [[6, 13, 21, 30, 37, 38, 49, 50, 63, 79, 86, 98, 202, 219], + [31, 47, 80, 82, 82, 149]], + [[10, 10, 12, 13, 14, 15, 16, 17, 18, 20, 24, 24, 25, 28, 30, + 33, 35, 37, 40, 40, 46, 48, 76, 81, 82, 91, 112, 181], + [34, 40, 70]], + 7.49659416854, + [0.006181578637, 0.003090789318730882, 0.9969092106812691] + )] + ) + def test_log_rank(self, x, y, statistic, pvalue): + x = stats.CensoredData(uncensored=x[0], right=x[1]) + y = stats.CensoredData(uncensored=y[0], right=y[1]) + + for i, alternative in enumerate(["two-sided", "less", "greater"]): + res = stats.logrank(x=x, y=y, alternative=alternative) + + # we return z and use the normal distribution while other framework + # return z**2. The p-value are directly comparable, but we have to + # square the statistic + assert_allclose(res.statistic**2, statistic, atol=1e-10) + assert_allclose(res.pvalue, pvalue[i], atol=1e-10) + + def test_raises(self): + sample = stats.CensoredData([1, 2]) + + msg = r"`y` must be" + with pytest.raises(ValueError, match=msg): + stats.logrank(x=sample, y=[[1, 2]]) + + msg = r"`x` must be" + with pytest.raises(ValueError, match=msg): + stats.logrank(x=[[1, 2]], y=sample) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/test_tukeylambda_stats.py b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_tukeylambda_stats.py new file mode 100644 index 0000000000000000000000000000000000000000..8041658599a6ed5b2cd70def1a92bffe0d851792 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_tukeylambda_stats.py @@ -0,0 +1,85 @@ +import numpy as np +from numpy.testing import assert_allclose, assert_equal + +from scipy.stats._tukeylambda_stats import (tukeylambda_variance, + tukeylambda_kurtosis) + + +def test_tukeylambda_stats_known_exact(): + """Compare results with some known exact formulas.""" + # Some exact values of the Tukey Lambda variance and kurtosis: + # lambda var kurtosis + # 0 pi**2/3 6/5 (logistic distribution) + # 0.5 4 - pi (5/3 - pi/2)/(pi/4 - 1)**2 - 3 + # 1 1/3 -6/5 (uniform distribution on (-1,1)) + # 2 1/12 -6/5 (uniform distribution on (-1/2, 1/2)) + + # lambda = 0 + var = tukeylambda_variance(0) + assert_allclose(var, np.pi**2 / 3, atol=1e-12) + kurt = tukeylambda_kurtosis(0) + assert_allclose(kurt, 1.2, atol=1e-10) + + # lambda = 0.5 + var = tukeylambda_variance(0.5) + assert_allclose(var, 4 - np.pi, atol=1e-12) + kurt = tukeylambda_kurtosis(0.5) + desired = (5./3 - np.pi/2) / (np.pi/4 - 1)**2 - 3 + assert_allclose(kurt, desired, atol=1e-10) + + # lambda = 1 + var = tukeylambda_variance(1) + assert_allclose(var, 1.0 / 3, atol=1e-12) + kurt = tukeylambda_kurtosis(1) + assert_allclose(kurt, -1.2, atol=1e-10) + + # lambda = 2 + var = tukeylambda_variance(2) + assert_allclose(var, 1.0 / 12, atol=1e-12) + kurt = tukeylambda_kurtosis(2) + assert_allclose(kurt, -1.2, atol=1e-10) + + +def test_tukeylambda_stats_mpmath(): + """Compare results with some values that were computed using mpmath.""" + a10 = dict(atol=1e-10, rtol=0) + a12 = dict(atol=1e-12, rtol=0) + data = [ + # lambda variance kurtosis + [-0.1, 4.78050217874253547, 3.78559520346454510], + [-0.0649, 4.16428023599895777, 2.52019675947435718], + [-0.05, 3.93672267890775277, 2.13129793057777277], + [-0.001, 3.30128380390964882, 1.21452460083542988], + [0.001, 3.27850775649572176, 1.18560634779287585], + [0.03125, 2.95927803254615800, 0.804487555161819980], + [0.05, 2.78281053405464501, 0.611604043886644327], + [0.0649, 2.65282386754100551, 0.476834119532774540], + [1.2, 0.242153920578588346, -1.23428047169049726], + [10.0, 0.00095237579757703597, 2.37810697355144933], + [20.0, 0.00012195121951131043, 7.37654321002709531], + ] + + for lam, var_expected, kurt_expected in data: + var = tukeylambda_variance(lam) + assert_allclose(var, var_expected, **a12) + kurt = tukeylambda_kurtosis(lam) + assert_allclose(kurt, kurt_expected, **a10) + + # Test with vector arguments (most of the other tests are for single + # values). + lam, var_expected, kurt_expected = zip(*data) + var = tukeylambda_variance(lam) + assert_allclose(var, var_expected, **a12) + kurt = tukeylambda_kurtosis(lam) + assert_allclose(kurt, kurt_expected, **a10) + + +def test_tukeylambda_stats_invalid(): + """Test values of lambda outside the domains of the functions.""" + lam = [-1.0, -0.5] + var = tukeylambda_variance(lam) + assert_equal(var, np.array([np.nan, np.inf])) + + lam = [-1.0, -0.25] + kurt = tukeylambda_kurtosis(lam) + assert_equal(kurt, np.array([np.nan, np.inf])) diff --git a/venv/lib/python3.12/site-packages/scipy/stats/tests/test_variation.py b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_variation.py new file mode 100644 index 0000000000000000000000000000000000000000..2a07ac1bfcdf85e9be609ff7a5c1445f32e22cba --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/stats/tests/test_variation.py @@ -0,0 +1,205 @@ +import math + +import numpy as np +import pytest + +from scipy._lib._util import AxisError +from scipy._lib._array_api import make_xp_test_case, eager_warns, is_numpy +from scipy._lib._array_api_no_0d import xp_assert_equal, xp_assert_close +from scipy._external import array_api_extra as xpx + +from scipy import stats +from scipy.stats import variation +from scipy.stats._axis_nan_policy import (too_small_nd_omit, too_small_nd_not_omit, + SmallSampleWarning) + +lazy_xp_modules = [stats] +skip_xp_backends = pytest.mark.skip_xp_backends + + +@make_xp_test_case(variation) +class TestVariation: + """ + Test class for scipy.stats.variation + """ + + def test_ddof(self, xp): + x = xp.arange(9.0) + xp_assert_close(variation(x, ddof=1), xp.asarray(math.sqrt(60/8)/4)) + + @pytest.mark.parametrize('sgn', [1, -1]) + def test_sign(self, sgn, xp): + x = xp.asarray([1., 2., 3., 4., 5.]) + v = variation(sgn*x) + expected = xp.asarray(sgn*math.sqrt(2)/3) + xp_assert_close(v, expected, rtol=1e-10) + + @skip_xp_backends(np_only=True, reason="test plain python scalar input") + def test_scalar(self, xp): + # A scalar is treated like a 1-d sequence with length 1. + assert variation(4.0) == 0.0 + + @pytest.mark.parametrize('nan_policy, expected', + [('propagate', np.nan), + ('omit', math.sqrt(20/3)/4)]) + @skip_xp_backends(eager_only=True, reason='lazy -> limited nan_policy support') + def test_variation_nan(self, nan_policy, expected, xp): + x = xp.arange(10.) + x[9] = xp.nan + xp_assert_close(variation(x, nan_policy=nan_policy), xp.asarray(expected)) + + @skip_xp_backends(eager_only=True, reason='lazy -> limited nan_policy support') + def test_nan_policy_raise(self, xp): + x = xp.asarray([1.0, 2.0, xp.nan, 3.0]) + with pytest.raises(ValueError, match='input contains nan'): + variation(x, nan_policy='raise') + + @skip_xp_backends("dask.array", reason='needs `_axis_nan_policy`') + def test_bad_nan_policy(self, xp): + with pytest.raises(ValueError, match='must be one of'): + variation(xp.asarray([1, 2, 3]), nan_policy='foobar') + + @skip_xp_backends("dask.array", reason='needs `_axis_nan_policy`') + def test_keepdims(self, xp): + x = xp.reshape(xp.arange(10), (2, 5)) + y = variation(x, axis=1, keepdims=True) + expected = 2**0.5 / xp.asarray([[2.], [7.]]) + xp_assert_close(y, expected) + + @skip_xp_backends("dask.array", reason='needs `_axis_nan_policy`') + @pytest.mark.parametrize('axis, expected', + [(0, np.empty((1, 0)).tolist()), + (1, np.full((5, 1), fill_value=np.nan).tolist())]) + def test_keepdims_size0(self, axis, expected, xp): + x = xp.zeros((5, 0)) + if axis == 1: + with pytest.warns(SmallSampleWarning, match=too_small_nd_not_omit): + y = variation(x, axis=axis, keepdims=True) + else: + y = variation(x, axis=axis, keepdims=True) + xp_assert_equal(y, xp.asarray(expected)) + + @skip_xp_backends("dask.array", reason='needs `_axis_nan_policy`') + @pytest.mark.parametrize('incr, expected_fill', [(0, np.inf), (1, np.nan)]) + def test_keepdims_and_ddof_eq_len_plus_incr(self, incr, expected_fill, xp): + x = xp.asarray([[1, 1, 2, 2], [1, 2, 3, 3]]) + y = variation(x, axis=1, ddof=x.shape[1] + incr, keepdims=True) + xp_assert_equal(y, xp.full((2, 1), fill_value=expected_fill)) + + @skip_xp_backends("dask.array", reason='needs `_axis_nan_policy`') + def test_propagate_nan(self, xp): + # Check that the shape of the result is the same for inputs + # with and without nans, cf gh-5817 + a = xp.reshape(xp.arange(8.), (2, -1)) + a = xpx.at(a)[1, 0].set(xp.nan) + v = variation(a, axis=1, nan_policy="propagate") + xp_assert_close(v, xp.asarray([math.sqrt(5/4)/1.5, xp.nan])) + + def test_axis_none(self, xp): + # Check that `variation` computes the result on the flattened + # input when axis is None. + y = variation(xp.asarray([[0, 1], [2, 3]]), axis=None) + xp_assert_close(y, xp.asarray(math.sqrt(5/4)/1.5)) + + def test_bad_axis(self, xp): + # Check that an invalid axis raises np.exceptions.AxisError. + x = xp.asarray([[1, 2, 3], [4, 5, 6]]) + with pytest.raises((AxisError, IndexError)): + variation(x, axis=10) + + @pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning:dask") + def test_mean_zero(self, xp): + # Check that `variation` returns inf for a sequence that is not + # identically zero but whose mean is zero. + x = xp.asarray([10., -3., 1., -4., -4.]) + y = variation(x) + xp_assert_equal(y, xp.asarray(xp.inf)) + + x2 = xp.stack([x, -10.*x]) + y2 = variation(x2, axis=1) + xp_assert_equal(y2, xp.asarray([xp.inf, xp.inf])) + + @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") + @pytest.mark.parametrize('x', [[0.]*5, [1, 2, np.inf, 9]]) + def test_return_nan(self, x, xp): + x = xp.asarray(x) + # Test some cases where `variation` returns nan. + y = variation(x) + xp_assert_equal(y, xp.asarray(xp.nan, dtype=x.dtype)) + + @pytest.mark.filterwarnings('ignore:Invalid value encountered:RuntimeWarning:dask') + @pytest.mark.parametrize('axis, expected', + [(0, []), (1, [np.nan]*3), (None, np.nan)]) + def test_2d_size_zero_with_axis(self, axis, expected, xp): + x = xp.empty((3, 0)) + if axis != 0: + # specific message depends on `axis`, and `SmallSampleWarning` + # is specific enough. + with eager_warns(SmallSampleWarning, xp=xp): + y = variation(x, axis=axis) + else: + y = variation(x, axis=axis) + xp_assert_equal(y, xp.asarray(expected)) + + @pytest.mark.filterwarnings('ignore:divide by zero encountered:RuntimeWarning:dask') + def test_neg_inf(self, xp): + # Edge case that produces -inf: ddof equals the number of non-nan + # values, the values are not constant, and the mean is negative. + x1 = xp.asarray([-3., -5.]) + xp_assert_equal(variation(x1, ddof=2), xp.asarray(-xp.inf)) + + @skip_xp_backends(np_only=True, reason='only NumPy supports ND nan_policy="omit"') + def test_neg_inf_nan(self, xp): + x2 = xp.asarray([[xp.nan, 1, -10, xp.nan], + [-20, -3, xp.nan, xp.nan]]) + xp_assert_equal(variation(x2, axis=1, ddof=2, nan_policy='omit'), + xp.asarray([-xp.inf, -xp.inf])) + + @pytest.mark.parametrize("nan_policy", ['propagate', 'omit']) + def test_combined_edge_cases(self, nan_policy, xp): + if not is_numpy(xp): + pytest.skip("Only NumPy supports ND nan_policy='omit'") + x = xp.asarray([[0, 10, xp.nan, 1], + [0, -5, xp.nan, 2], + [0, -5, xp.nan, 3]]) + if nan_policy == 'omit': + with pytest.warns(SmallSampleWarning, match=too_small_nd_omit): + y = variation(x, axis=0, nan_policy=nan_policy) + else: + y = variation(x, axis=0, nan_policy=nan_policy) + xp_assert_close(y, xp.asarray([xp.nan, xp.inf, xp.nan, math.sqrt(2/3)/2])) + + @skip_xp_backends(np_only=True, reason='only NumPy supports ND nan_policy="omit"') + @pytest.mark.parametrize( + 'ddof, expected', + [(0, [np.sqrt(1/6), np.sqrt(5/8), np.inf, 0, np.nan, 0.0, np.nan]), + (1, [0.5, np.sqrt(5/6), np.inf, 0, np.nan, 0, np.nan]), + (2, [np.sqrt(0.5), np.sqrt(5/4), np.inf, np.nan, np.nan, 0, np.nan])] + ) + def test_more_nan_policy_omit_tests(self, ddof, expected, xp): + # The slightly strange formatting in the follow array is my attempt to + # maintain a clean tabular arrangement of the data while satisfying + # the demands of pycodestyle. Currently, E201 and E241 are not + # disabled by the `noqa` annotation. + nan = xp.nan + x = xp.asarray([[1.0, 2.0, nan, 3.0], + [0.0, 4.0, 3.0, 1.0], + [nan, -.5, 0.5, nan], + [nan, 9.0, 9.0, nan], + [nan, nan, nan, nan], + [3.0, 3.0, 3.0, 3.0], + [0.0, 0.0, 0.0, 0.0]]) + with pytest.warns(SmallSampleWarning, match=too_small_nd_omit): + v = variation(x, axis=1, ddof=ddof, nan_policy='omit') + xp_assert_close(v, expected) + + @skip_xp_backends(eager_only=True, reason='lazy -> limited nan_policy support') + def test_variation_ddof(self, xp): + # test variation with delta degrees of freedom + # regression test for gh-13341 + a = xp.asarray([1., 2., 3., 4., 5.]) + nan_a = xp.asarray([1, 2, 3, xp.nan, 4, 5, xp.nan]) + y = variation(a, ddof=1) + nan_y = variation(nan_a, nan_policy="omit", ddof=1) + xp_assert_close(y, xp.asarray(math.sqrt(5/2)/3)) + assert y == nan_y diff --git a/venv/lib/python3.12/site-packages/scipy/version.py b/venv/lib/python3.12/site-packages/scipy/version.py new file mode 100644 index 0000000000000000000000000000000000000000..7b5916676f0da28d5e9b581853314ec581917ab2 --- /dev/null +++ b/venv/lib/python3.12/site-packages/scipy/version.py @@ -0,0 +1,12 @@ + +""" +Module to expose more detailed version info for the installed `scipy` +""" +version = "1.18.0" +full_version = version +short_version = version.split('.dev')[0] +git_revision = "54ef5423f2e4376230ec3bfda6912a07a50958e3" +release = 'dev' not in version and '+' not in version + +if not release: + version = full_version diff --git a/venv/lib/python3.12/site-packages/setuptools/__init__.py b/venv/lib/python3.12/site-packages/setuptools/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..87ca47bbc9fd4d1e5083d4dedb7a6c86f43c73fc --- /dev/null +++ b/venv/lib/python3.12/site-packages/setuptools/__init__.py @@ -0,0 +1,256 @@ +"""Extensions to the 'distutils' for large or complex distributions""" +# mypy: disable_error_code=override +# Command.reinitialize_command has an extra **kw param that distutils doesn't have +# Can't disable on the exact line because distutils doesn't exists on Python 3.12 +# and mypy isn't aware of distutils_hack, causing distutils.core.Command to be Any, +# and a [unused-ignore] to be raised on 3.12+ + +from __future__ import annotations + +import functools +import os +import sys +from abc import abstractmethod +from collections.abc import Mapping +from typing import TYPE_CHECKING, TypeVar, overload + +sys.path.extend(((vendor_path := os.path.join(os.path.dirname(os.path.dirname(__file__)), 'setuptools', '_vendor')) not in sys.path) * [vendor_path]) # fmt: skip +# workaround for #4476 +sys.modules.pop('backports', None) + +import _distutils_hack.override # noqa: F401 + +from . import logging, monkey +from .depends import Require +from .discovery import PackageFinder, PEP420PackageFinder +from .dist import Distribution +from .extension import Extension +from .version import __version__ as __version__ +from .warnings import SetuptoolsDeprecationWarning + +import distutils.core + +__all__ = [ + 'setup', + 'Distribution', + 'Command', + 'Extension', + 'Require', + 'SetuptoolsDeprecationWarning', + 'find_packages', + 'find_namespace_packages', +] + +_CommandT = TypeVar("_CommandT", bound="_Command") + +bootstrap_install_from = None + +find_packages = PackageFinder.find +find_namespace_packages = PEP420PackageFinder.find + + +def _install_setup_requires(attrs): + # Note: do not use `setuptools.Distribution` directly, as + # our PEP 517 backend patch `distutils.core.Distribution`. + class MinimalDistribution(distutils.core.Distribution): + """ + A minimal version of a distribution for supporting the + fetch_build_eggs interface. + """ + + def __init__(self, attrs: Mapping[str, object]) -> None: + _incl = 'dependency_links', 'setup_requires' + filtered = {k: attrs[k] for k in set(_incl) & set(attrs)} + super().__init__(filtered) + # Prevent accidentally triggering discovery with incomplete set of attrs + self.set_defaults._disable() + + def _get_project_config_files(self, filenames=None): + """Ignore ``pyproject.toml``, they are not related to setup_requires""" + try: + cfg, _toml = super()._split_standard_project_metadata(filenames) + except Exception: + return filenames, () + return cfg, () + + def finalize_options(self): + """ + Disable finalize_options to avoid building the working set. + Ref #2158. + """ + + dist = MinimalDistribution(attrs) + + # Honor setup.cfg's options. + dist.parse_config_files(ignore_option_errors=True) + if dist.setup_requires: + _fetch_build_eggs(dist) + + +def _fetch_build_eggs(dist: Distribution): + try: + dist.fetch_build_eggs(dist.setup_requires) + except Exception as ex: + msg = """ + It is possible a package already installed in your system + contains an version that is invalid according to PEP 440. + You can try `pip install --use-pep517` as a workaround for this problem, + or rely on a new virtual environment. + + If the problem refers to a package that is not installed yet, + please contact that package's maintainers or distributors. + """ + if "InvalidVersion" in ex.__class__.__name__: + if hasattr(ex, "add_note"): + ex.add_note(msg) # PEP 678 + else: + dist.announce(f"\n{msg}\n") + raise + + +def setup(**attrs) -> Distribution: + logging.configure() + # Make sure we have any requirements needed to interpret 'attrs'. + _install_setup_requires(attrs) + # Override return type of distutils.core.Distribution with setuptools.dist.Distribution + # (implicitly implemented via `setuptools.monkey.patch_all`). + return distutils.core.setup(**attrs) # type: ignore[return-value] + + +setup.__doc__ = distutils.core.setup.__doc__ + +if TYPE_CHECKING: + # Work around a mypy issue where type[T] can't be used as a base: https://github.com/python/mypy/issues/10962 + from distutils.core import Command as _Command +else: + _Command = monkey.get_unpatched(distutils.core.Command) + + +class Command(_Command): + """ + Setuptools internal actions are organized using a *command design pattern*. + This means that each action (or group of closely related actions) executed during + the build should be implemented as a ``Command`` subclass. + + These commands are abstractions and do not necessarily correspond to a command that + can (or should) be executed via a terminal, in a CLI fashion (although historically + they would). + + When creating a new command from scratch, custom defined classes **SHOULD** inherit + from ``setuptools.Command`` and implement a few mandatory methods. + Between these mandatory methods, are listed: + :meth:`initialize_options`, :meth:`finalize_options` and :meth:`run`. + + A useful analogy for command classes is to think of them as subroutines with local + variables called "options". The options are "declared" in :meth:`initialize_options` + and "defined" (given their final values, aka "finalized") in :meth:`finalize_options`, + both of which must be defined by every command class. The "body" of the subroutine, + (where it does all the work) is the :meth:`run` method. + Between :meth:`initialize_options` and :meth:`finalize_options`, ``setuptools`` may set + the values for options/attributes based on user's input (or circumstance), + which means that the implementation should be careful to not overwrite values in + :meth:`finalize_options` unless necessary. + + Please note that other commands (or other parts of setuptools) may also overwrite + the values of the command's options/attributes multiple times during the build + process. + Therefore it is important to consistently implement :meth:`initialize_options` and + :meth:`finalize_options`. For example, all derived attributes (or attributes that + depend on the value of other attributes) **SHOULD** be recomputed in + :meth:`finalize_options`. + + When overwriting existing commands, custom defined classes **MUST** abide by the + same APIs implemented by the original class. They also **SHOULD** inherit from the + original class. + """ + + command_consumes_arguments = False + distribution: Distribution # override distutils.dist.Distribution with setuptools.dist.Distribution + + dry_run = False # type: ignore[assignment] # pyright: ignore[reportAssignmentType] (until #4689; see #4872) + """ + For compatibility with vendored bdist_wheel. + https://github.com/pypa/setuptools/pull/4872/files#r1986395142 + """ + + def __init__(self, dist: Distribution, **kw) -> None: + """ + Construct the command for dist, updating + vars(self) with any keyword parameters. + """ + super().__init__(dist) + vars(self).update(kw) + + @overload + def reinitialize_command( + self, command: str, reinit_subcommands: bool = False, **kw + ) -> Command: ... # override distutils.cmd.Command with setuptools.Command + @overload + def reinitialize_command( + self, command: _CommandT, reinit_subcommands: bool = False, **kw + ) -> _CommandT: ... + def reinitialize_command( + self, command: str | _Command, reinit_subcommands: bool = False, **kw + ) -> Command | _Command: + cmd = _Command.reinitialize_command(self, command, reinit_subcommands) + vars(cmd).update(kw) + return cmd # pyright: ignore[reportReturnType] # pypa/distutils#307 + + @abstractmethod + def initialize_options(self) -> None: + """ + Set or (reset) all options/attributes/caches used by the command + to their default values. Note that these values may be overwritten during + the build. + """ + raise NotImplementedError + + @abstractmethod + def finalize_options(self) -> None: + """ + Set final values for all options/attributes used by the command. + Most of the time, each option/attribute/cache should only be set if it does not + have any value yet (e.g. ``if self.attr is None: self.attr = val``). + """ + raise NotImplementedError + + @abstractmethod + def run(self) -> None: + """ + Execute the actions intended by the command. + (Side effects **SHOULD** only take place when :meth:`run` is executed, + for example, creating new files or writing to the terminal output). + """ + raise NotImplementedError + + +def _find_all_simple(path): + """ + Find all files under 'path' + """ + results = ( + os.path.join(base, file) + for base, dirs, files in os.walk(path, followlinks=True) + for file in files + ) + return filter(os.path.isfile, results) + + +def findall(dir=os.curdir): + """ + Find all files under 'dir' and return the list of full filenames. + Unless dir is '.', return full filenames with dir prepended. + """ + files = _find_all_simple(dir) + if dir == os.curdir: + make_rel = functools.partial(os.path.relpath, start=dir) + files = map(make_rel, files) + return list(files) + + +class sic(str): + """Treat this string as-is (https://en.wikipedia.org/wiki/Sic)""" + + +# Apply monkey patches +monkey.patch_all() diff --git a/venv/lib/python3.12/site-packages/setuptools/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/setuptools/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8afe1ac31624fb9378b14a04c3b04f1ab98ca662 Binary files /dev/null and b/venv/lib/python3.12/site-packages/setuptools/__pycache__/__init__.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/setuptools/__pycache__/_core_metadata.cpython-312.pyc b/venv/lib/python3.12/site-packages/setuptools/__pycache__/_core_metadata.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..57adcb3613a9e31fe7e0e99a7ee8cf4ea07103b7 Binary files /dev/null and b/venv/lib/python3.12/site-packages/setuptools/__pycache__/_core_metadata.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/setuptools/__pycache__/_discovery.cpython-312.pyc b/venv/lib/python3.12/site-packages/setuptools/__pycache__/_discovery.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..66dac9a3817984fd67fc96637e98c76f13f50f56 Binary files /dev/null and b/venv/lib/python3.12/site-packages/setuptools/__pycache__/_discovery.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/setuptools/__pycache__/_entry_points.cpython-312.pyc b/venv/lib/python3.12/site-packages/setuptools/__pycache__/_entry_points.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e8e0e786d0b4a5a318eb2fba9af7fa1e4321945b Binary files /dev/null and b/venv/lib/python3.12/site-packages/setuptools/__pycache__/_entry_points.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/setuptools/__pycache__/_imp.cpython-312.pyc b/venv/lib/python3.12/site-packages/setuptools/__pycache__/_imp.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0587eed56e67859ebce18e1ca43c803a3ee3c519 Binary files /dev/null and b/venv/lib/python3.12/site-packages/setuptools/__pycache__/_imp.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/setuptools/__pycache__/_importlib.cpython-312.pyc b/venv/lib/python3.12/site-packages/setuptools/__pycache__/_importlib.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..081ead716b89121609368f0b5b0a29c253909902 Binary files /dev/null and b/venv/lib/python3.12/site-packages/setuptools/__pycache__/_importlib.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/setuptools/__pycache__/_itertools.cpython-312.pyc b/venv/lib/python3.12/site-packages/setuptools/__pycache__/_itertools.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..037b1361e6bf3ad3e8361d93280ed34f430e71ff Binary files /dev/null and b/venv/lib/python3.12/site-packages/setuptools/__pycache__/_itertools.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/setuptools/__pycache__/_normalization.cpython-312.pyc b/venv/lib/python3.12/site-packages/setuptools/__pycache__/_normalization.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bce2b7c41fc50294f8b1400e472b9a0c523280c8 Binary files /dev/null and b/venv/lib/python3.12/site-packages/setuptools/__pycache__/_normalization.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/setuptools/__pycache__/_path.cpython-312.pyc b/venv/lib/python3.12/site-packages/setuptools/__pycache__/_path.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6fd0bcf88f3dd9b25247b5d567b60f6989a7e45a Binary files /dev/null and b/venv/lib/python3.12/site-packages/setuptools/__pycache__/_path.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/setuptools/__pycache__/_reqs.cpython-312.pyc b/venv/lib/python3.12/site-packages/setuptools/__pycache__/_reqs.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..950d7b9685ab0e05dd26d8bae44e6bd124ce6090 Binary files /dev/null and b/venv/lib/python3.12/site-packages/setuptools/__pycache__/_reqs.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/setuptools/__pycache__/_scripts.cpython-312.pyc b/venv/lib/python3.12/site-packages/setuptools/__pycache__/_scripts.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a5e8fcf3f397ffdf9e87128f07c18c2f35651f3e Binary files /dev/null and b/venv/lib/python3.12/site-packages/setuptools/__pycache__/_scripts.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/setuptools/__pycache__/_shutil.cpython-312.pyc b/venv/lib/python3.12/site-packages/setuptools/__pycache__/_shutil.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..274fcff7f60ebb05a48e1c9b29e768466ec5c748 Binary files /dev/null and b/venv/lib/python3.12/site-packages/setuptools/__pycache__/_shutil.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/setuptools/__pycache__/_static.cpython-312.pyc b/venv/lib/python3.12/site-packages/setuptools/__pycache__/_static.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ffae43197abc33b9bd59ba556c89d6d071394870 Binary files /dev/null and b/venv/lib/python3.12/site-packages/setuptools/__pycache__/_static.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/setuptools/__pycache__/archive_util.cpython-312.pyc b/venv/lib/python3.12/site-packages/setuptools/__pycache__/archive_util.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..77c7158bc9e4464948dd26d0fe6e97479483c5bc Binary files /dev/null and b/venv/lib/python3.12/site-packages/setuptools/__pycache__/archive_util.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/setuptools/__pycache__/build_meta.cpython-312.pyc b/venv/lib/python3.12/site-packages/setuptools/__pycache__/build_meta.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7299dee1abbd381cfb3768df7a97956ab11e5cec Binary files /dev/null and b/venv/lib/python3.12/site-packages/setuptools/__pycache__/build_meta.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/setuptools/__pycache__/depends.cpython-312.pyc b/venv/lib/python3.12/site-packages/setuptools/__pycache__/depends.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..67cb61d32cc50d15a8c7674fd05599d997a6acfe Binary files /dev/null and b/venv/lib/python3.12/site-packages/setuptools/__pycache__/depends.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/setuptools/__pycache__/discovery.cpython-312.pyc b/venv/lib/python3.12/site-packages/setuptools/__pycache__/discovery.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d30c47cf670b409308009012ff950a4ec36e26b4 Binary files /dev/null and b/venv/lib/python3.12/site-packages/setuptools/__pycache__/discovery.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/setuptools/__pycache__/dist.cpython-312.pyc b/venv/lib/python3.12/site-packages/setuptools/__pycache__/dist.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..010c64561e65d1f258f3ce8e53ca424a590b086f Binary files /dev/null and b/venv/lib/python3.12/site-packages/setuptools/__pycache__/dist.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/setuptools/__pycache__/errors.cpython-312.pyc b/venv/lib/python3.12/site-packages/setuptools/__pycache__/errors.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a40ab833fd1e7e817c499371983bac29cd3e0e33 Binary files /dev/null and b/venv/lib/python3.12/site-packages/setuptools/__pycache__/errors.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/setuptools/__pycache__/extension.cpython-312.pyc b/venv/lib/python3.12/site-packages/setuptools/__pycache__/extension.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fe4ea86efb9cc6b1e3174656581cd65313ef6027 Binary files /dev/null and b/venv/lib/python3.12/site-packages/setuptools/__pycache__/extension.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/setuptools/__pycache__/glob.cpython-312.pyc b/venv/lib/python3.12/site-packages/setuptools/__pycache__/glob.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3d6ec7026ecff080e0718886142b8bd1d809cc2b Binary files /dev/null and b/venv/lib/python3.12/site-packages/setuptools/__pycache__/glob.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/setuptools/__pycache__/installer.cpython-312.pyc b/venv/lib/python3.12/site-packages/setuptools/__pycache__/installer.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ee848b91f267c4f717eacf7fe0689be477023383 Binary files /dev/null and b/venv/lib/python3.12/site-packages/setuptools/__pycache__/installer.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/setuptools/__pycache__/launch.cpython-312.pyc b/venv/lib/python3.12/site-packages/setuptools/__pycache__/launch.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4be001a14609e41035ff96347b6e3a82465e241e Binary files /dev/null and b/venv/lib/python3.12/site-packages/setuptools/__pycache__/launch.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/setuptools/__pycache__/logging.cpython-312.pyc b/venv/lib/python3.12/site-packages/setuptools/__pycache__/logging.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..346b42c67398cc02e666cec24083c10f4c4f18af Binary files /dev/null and b/venv/lib/python3.12/site-packages/setuptools/__pycache__/logging.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/setuptools/__pycache__/modified.cpython-312.pyc b/venv/lib/python3.12/site-packages/setuptools/__pycache__/modified.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e860dffd64697467a41fddd010195babb93859ed Binary files /dev/null and b/venv/lib/python3.12/site-packages/setuptools/__pycache__/modified.cpython-312.pyc differ diff --git a/venv/lib/python3.12/site-packages/setuptools/__pycache__/monkey.cpython-312.pyc b/venv/lib/python3.12/site-packages/setuptools/__pycache__/monkey.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..26285fea3cc9e8e171ce23f682c28893bda3c7b9 Binary files /dev/null and b/venv/lib/python3.12/site-packages/setuptools/__pycache__/monkey.cpython-312.pyc differ